Merge "Disable the Accessibility Shortcut dialog on TV."
diff --git a/.clang-format b/.clang-format
index 03af56d..d60d33c 100644
--- a/.clang-format
+++ b/.clang-format
@@ -9,5 +9,17 @@
 ConstructorInitializerIndentWidth: 6
 ContinuationIndentWidth: 8
 IndentWidth: 4
+JavaImportGroups:
+- android
+- androidx
+- com.android
+- dalvik
+- libcore
+- com
+- junit
+- net
+- org
+- java
+- javax
 PenaltyBreakBeforeFirstCallParameter: 100000
 SpacesBeforeTrailingComments: 1
diff --git a/Android.bp b/Android.bp
index 0315c12..3d0188a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -95,17 +95,20 @@
         ":platform-compat-native-aidl",
 
         // AIDL sources from external directories
+        ":android.hardware.biometrics.common-V3-java-source",
+        ":android.hardware.biometrics.fingerprint-V3-java-source",
         ":android.hardware.gnss-V2-java-source",
         ":android.hardware.graphics.common-V3-java-source",
-        ":android.hardware.security.keymint-V2-java-source",
+        ":android.hardware.keymaster-V4-java-source",
+        ":android.hardware.security.keymint-V3-java-source",
         ":android.hardware.security.secureclock-V1-java-source",
-        ":android.hardware.tv.tuner-V1-java-source",
+        ":android.hardware.tv.tuner-V2-java-source",
         ":android.security.apc-java-source",
         ":android.security.authorization-java-source",
         ":android.security.legacykeystore-java-source",
         ":android.security.maintenance-java-source",
         ":android.security.metrics-java-source",
-        ":android.system.keystore2-V1-java-source",
+        ":android.system.keystore2-V3-java-source",
         ":credstore_aidl",
         ":dumpstate_aidl",
         ":framework_native_aidl",
@@ -202,7 +205,7 @@
         "android.hardware.contexthub-V1.0-java",
         "android.hardware.contexthub-V1.1-java",
         "android.hardware.contexthub-V1.2-java",
-        "android.hardware.contexthub-V1-java",
+        "android.hardware.contexthub-V2-java",
         "android.hardware.gnss-V1.0-java",
         "android.hardware.gnss-V2.1-java",
         "android.hardware.health-V1.0-java-constants",
@@ -213,12 +216,13 @@
         "android.hardware.radio-V1.4-java",
         "android.hardware.radio-V1.5-java",
         "android.hardware.radio-V1.6-java",
-        "android.hardware.radio.data-V1-java",
-        "android.hardware.radio.messaging-V1-java",
-        "android.hardware.radio.modem-V1-java",
+        "android.hardware.radio.data-V2-java",
+        "android.hardware.radio.ims-V1-java",
+        "android.hardware.radio.messaging-V2-java",
+        "android.hardware.radio.modem-V2-java",
         "android.hardware.radio.network-V2-java",
-        "android.hardware.radio.sim-V1-java",
-        "android.hardware.radio.voice-V1-java",
+        "android.hardware.radio.sim-V2-java",
+        "android.hardware.radio.voice-V2-java",
         "android.hardware.thermal-V1.0-java-constants",
         "android.hardware.thermal-V1.0-java",
         "android.hardware.thermal-V1.1-java",
@@ -331,7 +335,10 @@
             "packages/modules/Bluetooth/framework/aidl-export",
             "packages/modules/Connectivity/framework/aidl-export",
             "packages/modules/Media/apex/aidl/stable",
+            "hardware/interfaces/biometrics/common/aidl",
+            "hardware/interfaces/biometrics/fingerprint/aidl",
             "hardware/interfaces/graphics/common/aidl",
+            "hardware/interfaces/keymaster/aidl",
         ],
     },
     dxflags: [
@@ -616,7 +623,10 @@
             "packages/modules/Bluetooth/framework/aidl-export",
             "packages/modules/Connectivity/framework/aidl-export",
             "packages/modules/Media/apex/aidl/stable",
+            "hardware/interfaces/biometrics/common/aidl",
+            "hardware/interfaces/biometrics/fingerprint/aidl",
             "hardware/interfaces/graphics/common/aidl",
+            "hardware/interfaces/keymaster/aidl",
         ],
     },
     // These are libs from framework-internal-utils that are required (i.e. being referenced)
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 6a323d6..a46ecce 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -84,7 +84,6 @@
         ":framework-connectivity-sources",
         ":framework-bluetooth-sources",
         ":framework-connectivity-tiramisu-updatable-sources",
-        ":framework-federatedcompute-sources",
         ":framework-graphics-srcs",
         ":framework-mediaprovider-sources",
         ":framework-nearby-sources",
@@ -184,6 +183,7 @@
 /////////////////////////////////////////////////////////////////////
 
 framework_docs_only_args = " -android -manifest $(location core/res/AndroidManifest.xml) " +
+    "-metalavaApiSince " +
     "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " +
     "-overview $(location core/java/overview.html) " +
     // Federate Support Library references against local API file.
@@ -375,7 +375,7 @@
     ],
     proofread_file: "ds-docs-proofread.txt",
     args: framework_docs_only_args +
-        " -toroot / -yamlV2 -metalavaApiSince -samplegroup Admin " +
+        " -toroot / -yamlV2 -samplegroup Admin " +
         " -samplegroup Background " +
         " -samplegroup Connectivity " +
         " -samplegroup Content " +
diff --git a/GAME_MANAGER_OWNERS b/GAME_MANAGER_OWNERS
index 502a9e36..b65c43a 100644
--- a/GAME_MANAGER_OWNERS
+++ b/GAME_MANAGER_OWNERS
@@ -1,2 +1,3 @@
 lpy@google.com
-timvp@google.com
+chingtangyu@google.com
+xwxw@google.com
diff --git a/OWNERS b/OWNERS
index d4d1936..09a721f 100644
--- a/OWNERS
+++ b/OWNERS
@@ -15,6 +15,7 @@
 narayan@google.com #{LAST_RESORT_SUGGESTION}
 ogunwale@google.com #{LAST_RESORT_SUGGESTION}
 roosa@google.com #{LAST_RESORT_SUGGESTION}
+smoreland@google.com #{LAST_RESORT_SUGGESTION}
 svetoslavganov@android.com #{LAST_RESORT_SUGGESTION}
 svetoslavganov@google.com #{LAST_RESORT_SUGGESTION}
 yamasani@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 0e08496..272b4f6 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -328,10 +328,12 @@
 
 java_library {
     name: "android_test_stubs_current",
-    // Modules do not have test APIs, but we want to include their SystemApis, like we include
-    // the SystemApi of framework-non-updatable-sources.
     static_libs: [
-        "all-modules-system-stubs",
+        // Updatable modules do not have test APIs, but we want to include their SystemApis, like we
+        // include the SystemApi of framework-non-updatable-sources.
+        "all-updatable-modules-system-stubs",
+        // Non-updatable modules on the other hand can have test APIs, so include their test-stubs.
+        "all-non-updatable-modules-test-stubs",
         "android-non-updatable.stubs.test",
         "private-stub-annotations-jar",
     ],
diff --git a/TEST_MAPPING b/TEST_MAPPING
index e178583..a48ce0c 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -15,6 +15,22 @@
       ]
     }
   ],
+  "presubmit-pm": [
+    {
+      "name": "PackageManagerServiceServerTests",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ],
   "presubmit": [
     {
       "name": "ManagedProvisioningTests",
@@ -167,6 +183,20 @@
          "exclude-annotation": "org.junit.Ignore"
        }
      ]
+   },
+   {
+     "name": "PackageManagerServiceServerTests",
+     "options": [
+       {
+         "include-annotation": "android.platform.test.annotations.Presubmit"
+       },
+       {
+         "exclude-annotation": "androidx.test.filters.FlakyTest"
+       },
+       {
+         "exclude-annotation": "org.junit.Ignore"
+       }
+     ]
    }
  ]
 }
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp
index 98e4f45..9366ff2d 100644
--- a/apct-tests/perftests/core/Android.bp
+++ b/apct-tests/perftests/core/Android.bp
@@ -62,4 +62,10 @@
 
     test_suites: ["device-tests"],
     certificate: "platform",
+
+    errorprone: {
+        javacflags: [
+            "-Xep:ReturnValueIgnored:WARN",
+        ],
+    },
 }
diff --git a/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java b/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
index 2ef68ca..05a3e12 100644
--- a/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
@@ -118,7 +118,7 @@
         int got = count.get();
         if (n != got) {
             throw new IllegalStateException(
-                    String.format("Only %i of %i objects finalized?", got, n));
+                    String.format("Only %d of %d objects finalized?", got, n));
         }
     }
 }
diff --git a/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java b/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java
index 0802072..0cce6ad 100644
--- a/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java
@@ -18,11 +18,11 @@
 
 import android.content.Context;
 import android.hardware.display.DisplayManager;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
 import android.provider.Settings;
 import android.view.Display;
 
-import androidx.benchmark.BenchmarkState;
-import androidx.benchmark.junit4.BenchmarkRule;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -38,7 +38,7 @@
     private static final float DELTA = 0.001f;
 
     @Rule
-    public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+    public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
     private DisplayManager mDisplayManager;
     private Context mContext;
@@ -51,7 +51,7 @@
 
     @Test
     public void testBrightnessChanges() throws Exception {
-        final BenchmarkState state = mBenchmarkRule.getState();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         Settings.System.putInt(mContext.getContentResolver(),
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
                 Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml
index 63e5983..5befa1f 100644
--- a/apct-tests/perftests/multiuser/AndroidManifest.xml
+++ b/apct-tests/perftests/multiuser/AndroidManifest.xml
@@ -35,4 +35,8 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
             android:targetPackage="com.android.perftests.multiuser"/>
 
+    <queries>
+        <package android:name="perftests.multiuser.apps.dummyapp" />
+    </queries>
+
 </manifest>
diff --git a/apct-tests/perftests/windowmanager/AndroidManifest.xml b/apct-tests/perftests/windowmanager/AndroidManifest.xml
index 95ede34..532a0fc 100644
--- a/apct-tests/perftests/windowmanager/AndroidManifest.xml
+++ b/apct-tests/perftests/windowmanager/AndroidManifest.xml
@@ -17,6 +17,10 @@
     package="com.android.perftests.wm">
 
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.READ_LOGS" />
+    <!-- For perfetto trace files -->
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
     <application>
         <uses-library android:name="android.test.runner" />
@@ -26,6 +30,9 @@
             <action android:name="com.android.perftests.core.PERFTEST" />
           </intent-filter>
         </activity>
+
+        <activity android:name="android.wm.InTaskTransitionTest$TestActivity"
+            android:process=":test" />
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java b/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java
new file mode 100644
index 0000000..2d2cf1c8
--- /dev/null
+++ b/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.perftests.utils.PerfTestActivity;
+import android.view.WindowManagerGlobal;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+/** Measure the performance of warm launch activity in the same task. */
+public class InTaskTransitionTest extends WindowManagerPerfTestBase
+        implements RemoteCallback.OnResultListener {
+
+    private static final long TIMEOUT_MS = 5000;
+
+    @Rule
+    public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
+
+    private final TransitionMetricsReader mMetricsReader = new TransitionMetricsReader();
+
+    @Test
+    @ManualBenchmarkState.ManualBenchmarkTest(
+            targetTestDurationNs = 20 * TIME_1_S_IN_NS,
+            statsReport = @ManualBenchmarkState.StatsReport(
+                    flags = ManualBenchmarkState.StatsReport.FLAG_ITERATION
+                            | ManualBenchmarkState.StatsReport.FLAG_MEAN
+                            | ManualBenchmarkState.StatsReport.FLAG_MAX))
+    public void testStartActivityInSameTask() {
+        final Context context = getInstrumentation().getContext();
+        final Activity activity = getInstrumentation().startActivitySync(
+                new Intent(context, PerfTestActivity.class)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        final Intent next = new Intent(context, TestActivity.class);
+        next.putExtra(TestActivity.CALLBACK, new RemoteCallback(this));
+
+        final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        long measuredTimeNs = 0;
+
+        boolean readerStarted = false;
+        while (state.keepRunning(measuredTimeNs)) {
+            if (!readerStarted && !state.isWarmingUp()) {
+                mMetricsReader.setCheckpoint();
+                readerStarted = true;
+            }
+            final long startTime = SystemClock.elapsedRealtimeNanos();
+            activity.startActivity(next);
+            synchronized (mMetricsReader) {
+                try {
+                    mMetricsReader.wait(TIMEOUT_MS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            measuredTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
+        }
+
+        for (TransitionMetricsReader.TransitionMetrics metrics : mMetricsReader.getMetrics()) {
+            if (metrics.mTransitionDelayMs > 0) {
+                state.addExtraResult("transitionDelayMs", metrics.mTransitionDelayMs);
+            }
+            if (metrics.mWindowsDrawnDelayMs > 0) {
+                state.addExtraResult("windowsDrawnDelayMs", metrics.mWindowsDrawnDelayMs);
+            }
+        }
+    }
+
+    @Override
+    public void onResult(Bundle result) {
+        // The test activity is destroyed.
+        synchronized (mMetricsReader) {
+            mMetricsReader.notifyAll();
+        }
+    }
+
+    /** The test activity runs on a different process to trigger metrics logs. */
+    public static class TestActivity extends Activity implements Runnable {
+        static final String CALLBACK = "callback";
+
+        private RemoteCallback mCallback;
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mCallback = getIntent().getParcelableExtra(CALLBACK, RemoteCallback.class);
+            if (mCallback != null) {
+                Looper.myLooper().getQueue().addIdleHandler(() -> {
+                    new Thread(this).start();
+                    return false;
+                });
+            }
+        }
+
+        @Override
+        public void run() {
+            // Wait until transition animation is finished and then finish self.
+            try {
+                WindowManagerGlobal.getWindowManagerService()
+                        .syncInputTransactions(true /* waitForAnimations */);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            finish();
+        }
+
+        @Override
+        protected void onDestroy() {
+            super.onDestroy();
+            if (mCallback != null) {
+                getMainThreadHandler().post(() -> mCallback.sendResult(null));
+            }
+        }
+    }
+}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
index 4b1982f..aea0326 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
@@ -18,10 +18,17 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS;
+
 import android.app.Activity;
 import android.content.Intent;
+import android.metrics.LogMaker;
+import android.metrics.MetricsReader;
 import android.perftests.utils.PerfTestActivity;
 import android.perftests.utils.WindowPerfTestBase;
+import android.util.SparseArray;
 
 import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
 import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
@@ -31,6 +38,7 @@
 import org.junit.runners.model.Statement;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.concurrent.TimeUnit;
 
 public class WindowManagerPerfTestBase extends WindowPerfTestBase {
@@ -124,4 +132,42 @@
             }
         }
     }
+
+    static class TransitionMetricsReader {
+        final MetricsReader mMetricsReader = new MetricsReader();
+
+        static class TransitionMetrics {
+            int mTransitionDelayMs;
+            int mWindowsDrawnDelayMs;
+        }
+
+        TransitionMetrics[] getMetrics() {
+            mMetricsReader.read(0);
+            final ArrayList<LogMaker> logs = new ArrayList<>();
+            final LogMaker logTemplate = new LogMaker(APP_TRANSITION);
+            while (mMetricsReader.hasNext()) {
+                final LogMaker b = mMetricsReader.next();
+                if (logTemplate.isSubsetOf(b)) {
+                    logs.add(b);
+                }
+            }
+
+            final TransitionMetrics[] infoArray = new TransitionMetrics[logs.size()];
+            for (int i = 0; i < infoArray.length; i++) {
+                final LogMaker log = logs.get(i);
+                final SparseArray<Object> data = log.getEntries();
+                final TransitionMetrics info = new TransitionMetrics();
+                infoArray[i] = info;
+                info.mTransitionDelayMs =
+                        (int) data.get(APP_TRANSITION_DELAY_MS, -1);
+                info.mWindowsDrawnDelayMs =
+                        (int) data.get(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS, -1);
+            }
+            return infoArray;
+        }
+
+        void setCheckpoint() {
+            mMetricsReader.checkpoint();
+        }
+    }
 }
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 9ac3e41..9d363c8 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -80,6 +80,7 @@
 import android.os.RemoteCallback;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -619,7 +620,7 @@
         return blobInfos;
     }
 
-    private void deleteBlobInternal(long blobId, int callingUid) {
+    private void deleteBlobInternal(long blobId) {
         synchronized (mBlobsLock) {
             mBlobsMap.entrySet().removeIf(entry -> {
                 final BlobMetadata blobMetadata = entry.getValue();
@@ -1612,10 +1613,7 @@
         @Override
         @NonNull
         public List<BlobInfo> queryBlobsForUser(@UserIdInt int userId) {
-            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-                throw new SecurityException("Only system uid is allowed to call "
-                        + "queryBlobsForUser()");
-            }
+            verifyCallerIsSystemUid("queryBlobsForUser");
 
             final int resolvedUserId = userId == USER_CURRENT
                     ? ActivityManager.getCurrentUser() : userId;
@@ -1629,13 +1627,9 @@
 
         @Override
         public void deleteBlob(long blobId) {
-            final int callingUid = Binder.getCallingUid();
-            if (callingUid != Process.SYSTEM_UID) {
-                throw new SecurityException("Only system uid is allowed to call "
-                        + "deleteBlob()");
-            }
+            verifyCallerIsSystemUid("deleteBlob");
 
-            deleteBlobInternal(blobId, callingUid);
+            deleteBlobInternal(blobId);
         }
 
         @Override
@@ -1716,6 +1710,18 @@
             return new BlobStoreManagerShellCommand(BlobStoreManagerService.this).exec(this,
                     in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args);
         }
+
+        /**
+         * Verify if the caller is an admin user's app with system uid
+         */
+        private void verifyCallerIsSystemUid(final String operation) {
+            if (UserHandle.getCallingAppId() != Process.SYSTEM_UID
+                    || !mContext.getSystemService(UserManager.class)
+                    .isUserAdmin(UserHandle.getCallingUserId())) {
+                throw new SecurityException("Only admin user's app with system uid"
+                        + "are allowed to call #" + operation);
+            }
+        }
     }
 
     static final class DumpArgs {
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/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index f59e7a4..4242cf8 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -16,11 +16,15 @@
 
 package android.app;
 
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.app.job.IJobScheduler;
+import android.app.job.IUserVisibleJobObserver;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
 import android.app.job.JobSnapshot;
 import android.app.job.JobWorkItem;
+import android.content.Context;
 import android.os.RemoteException;
 
 import java.util.List;
@@ -32,12 +36,14 @@
  * Note android.app.job is the better package to put this class, but we can't move it there
  * because that'd break robolectric. Grr.
  *
- * @hide 
+ * @hide
  */
 public class JobSchedulerImpl extends JobScheduler {
     IJobScheduler mBinder;
+    private final Context mContext;
 
-    public JobSchedulerImpl(IJobScheduler binder) {
+    public JobSchedulerImpl(@NonNull Context context, IJobScheduler binder) {
+        mContext = context;
         mBinder = binder;
     }
 
@@ -103,6 +109,33 @@
     }
 
     @Override
+    public int getPendingJobReason(int jobId) {
+        try {
+            return mBinder.getPendingJobReason(jobId);
+        } catch (RemoteException e) {
+            return PENDING_JOB_REASON_UNDEFINED;
+        }
+    }
+
+    @Override
+    public boolean canRunLongJobs() {
+        try {
+            return mBinder.canRunLongJobs(mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean hasRunLongJobsPermission(String packageName, int userId) {
+        try {
+            return mBinder.hasRunLongJobsPermission(packageName, userId);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    @Override
     public List<JobInfo> getStartedJobs() {
         try {
             return mBinder.getStartedJobs();
@@ -119,4 +152,37 @@
             return null;
         }
     }
+
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+    @Override
+    public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
+        try {
+            mBinder.registerUserVisibleJobObserver(observer);
+        } catch (RemoteException e) {
+        }
+    }
+
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+    @Override
+    public void unregisterUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
+        try {
+            mBinder.unregisterUserVisibleJobObserver(observer);
+        } catch (RemoteException e) {
+        }
+    }
+
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+    @Override
+    public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
+        try {
+            mBinder.stopUserVisibleJobsForUser(packageName, userId);
+        } catch (RemoteException e) {
+        }
+    }
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
index d281da0..96494ec 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.app.job;
 
+import android.app.Notification;
 import android.app.job.JobWorkItem;
 
 /**
@@ -30,6 +31,25 @@
  */
 interface IJobCallback {
     /**
+     * Immediate callback to the system after sending a data transfer download progress request
+     * signal; used to quickly detect ANR.
+     *
+     * @param jobId Unique integer used to identify this job.
+     * @param workId Unique integer used to identify a specific work item.
+     * @param transferredBytes How much data has been downloaded, in bytes.
+     */
+    void acknowledgeGetTransferredDownloadBytesMessage(int jobId, int workId,
+            long transferredBytes);
+    /**
+     * Immediate callback to the system after sending a data transfer upload progress request
+     * signal; used to quickly detect ANR.
+     *
+     * @param jobId Unique integer used to identify this job.
+     * @param workId Unique integer used to identify a specific work item.
+     * @param transferredBytes How much data has been uploaded, in bytes.
+     */
+    void acknowledgeGetTransferredUploadBytesMessage(int jobId, int workId, long transferredBytes);
+    /**
      * Immediate callback to the system after sending a start signal, used to quickly detect ANR.
      *
      * @param jobId Unique integer used to identify this job.
@@ -65,4 +85,37 @@
      */
     @UnsupportedAppUsage
     void jobFinished(int jobId, boolean reschedule);
+    /*
+     * Inform JobScheduler of a change in the estimated transfer payload.
+     *
+     * @param jobId Unique integer used to identify this job.
+     * @param item The particular JobWorkItem this progress is associated with, if any.
+     * @param downloadBytes How many bytes the app expects to download.
+     * @param uploadBytes How many bytes the app expects to upload.
+     */
+    void updateEstimatedNetworkBytes(int jobId, in JobWorkItem item,
+            long downloadBytes, long uploadBytes);
+    /*
+     * Update JobScheduler of how much data the job has successfully transferred.
+     *
+     * @param jobId Unique integer used to identify this job.
+     * @param item The particular JobWorkItem this progress is associated with, if any.
+     * @param transferredDownloadBytes The number of bytes that have successfully been downloaded.
+     * @param transferredUploadBytes The number of bytes that have successfully been uploaded.
+     */
+    void updateTransferredNetworkBytes(int jobId, in JobWorkItem item,
+            long transferredDownloadBytes, long transferredUploadBytes);
+    /**
+     * Provide JobScheduler with a notification to post and tie to this job's
+     * lifecycle.
+     * This is required for all user-initiated job and optional for other jobs.
+     *
+     * @param jobId Unique integer used to identify this job.
+     * @param notificationId The ID for this notification, as per
+     *                       {@link android.app.NotificationManager#notify(int, Notification)}.
+     * @param notification The notification to be displayed.
+     * @param jobEndNotificationPolicy The policy to apply to the notification when the job stops.
+     */
+    void setNotification(int jobId, int notificationId,
+            in Notification notification, int jobEndNotificationPolicy);
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
index 3006f50..c87a2af 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
@@ -16,6 +16,7 @@
 
 package android.app.job;
 
+import android.app.job.IUserVisibleJobObserver;
 import android.app.job.JobInfo;
 import android.app.job.JobSnapshot;
 import android.app.job.JobWorkItem;
@@ -33,6 +34,15 @@
     void cancelAll();
     ParceledListSlice getAllPendingJobs();
     JobInfo getPendingJob(int jobId);
+    int getPendingJobReason(int jobId);
+    boolean canRunLongJobs(String packageName);
+    boolean hasRunLongJobsPermission(String packageName, int userId);
     List<JobInfo> getStartedJobs();
     ParceledListSlice getAllJobSnapshots();
+    @EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
+    void registerUserVisibleJobObserver(in IUserVisibleJobObserver observer);
+    @EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
+    void unregisterUserVisibleJobObserver(in IUserVisibleJobObserver observer);
+    @EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
+    void stopUserVisibleJobsForUser(String packageName, int userId);
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
index 22ad252..2bb82bd 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
@@ -17,6 +17,7 @@
 package android.app.job;
 
 import android.app.job.JobParameters;
+import android.app.job.JobWorkItem;
 
 /**
  * Interface that the framework uses to communicate with application code that implements a
@@ -31,4 +32,8 @@
     /** Stop execution of application's job. */
     @UnsupportedAppUsage
     void stopJob(in JobParameters jobParams);
+    /** Update JS of how much data has been downloaded. */
+    void getTransferredDownloadBytes(in JobParameters jobParams, in JobWorkItem jobWorkItem);
+    /** Update JS of how much data has been uploaded. */
+    void getTransferredUploadBytes(in JobParameters jobParams, in JobWorkItem jobWorkItem);
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/IUserVisibleJobObserver.aidl b/apex/jobscheduler/framework/java/android/app/job/IUserVisibleJobObserver.aidl
new file mode 100644
index 0000000..f65a47d
--- /dev/null
+++ b/apex/jobscheduler/framework/java/android/app/job/IUserVisibleJobObserver.aidl
@@ -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.
+ */
+
+package android.app.job;
+
+import android.app.job.UserVisibleJobSummary;
+
+/**
+ * IPC protocol to know about user-visible job activity.
+ *
+ * @hide
+ */
+oneway interface IUserVisibleJobObserver {
+    /**
+     * Notify the client of all changes to a user-visible jobs' state.
+     * @param summary A token/summary that uniquely identifies and details a single running job
+     * @param isRunning whether the job is currently running or not
+     */
+    void onUserVisibleJobStateChanged(in UserVisibleJobSummary summary, boolean isRunning);
+}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 4c849fe..9caf99e 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -396,6 +396,13 @@
     public static final int FLAG_EXPEDITED = 1 << 4;
 
     /**
+     * Whether it's a data transfer job or not.
+     *
+     * @hide
+     */
+    public static final int FLAG_DATA_TRANSFER = 1 << 5;
+
+    /**
      * @hide
      */
     public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0;
@@ -723,6 +730,14 @@
     }
 
     /**
+     * @see JobInfo.Builder#setDataTransfer(boolean)
+     * @hide
+     */
+    public boolean isDataTransfer() {
+        return (flags & FLAG_DATA_TRANSFER) != 0;
+    }
+
+    /**
      * @see JobInfo.Builder#setImportantWhileForeground(boolean)
      */
     public boolean isImportantWhileForeground() {
@@ -1816,6 +1831,52 @@
         }
 
         /**
+         * Indicates that this job will be used to transfer data to or from a remote server. The
+         * system could attempt to run a data transfer job longer than a regular job if the data
+         * being transferred is potentially very large and can take a long time to complete.
+         *
+         * <p>
+         * The app must hold the {@link android.Manifest.permission#RUN_LONG_JOBS} permission to
+         * use this API. JobScheduler will throw a {@link SecurityException} if an app without the
+         * permission granted attempts to schedule a data transfer job.
+         *
+         * <p>
+         * You must provide an estimate of the payload size via
+         * {@link #setEstimatedNetworkBytes(long, long)} when scheduling the job or use
+         * {@link JobService#updateEstimatedNetworkBytes(JobParameters, long, long)} or
+         * {@link JobService#updateEstimatedNetworkBytes(JobParameters, JobWorkItem, long, long)}
+         * shortly after the job starts.
+         *
+         * <p>
+         * For user-initiated transfers that must be started immediately, call
+         * {@link #setExpedited(boolean) setExpedited(true)}. Otherwise, the system may defer the
+         * job to a more opportune time. Using {@link #setExpedited(boolean) setExpedited(true)}
+         * with this API will only be allowed for foreground apps and when the user has clearly
+         * interacted with the app. {@link #setExpedited(boolean) setExpedited(true)} will return
+         * {@link JobScheduler#RESULT_FAILURE} for a data transfer job if the app is in the
+         * background. Apps that successfully schedule data transfer jobs with
+         * {@link #setExpedited(boolean) setExpedited(true)} will not have quotas applied to them,
+         * though they may still be stopped for system health or constraint reasons. The system will
+         * also give a user the ability to stop a data transfer job via the Task Manager.
+         *
+         * <p>
+         * If you want to perform more than one data transfer job, consider enqueuing multiple
+         * {@link JobWorkItem JobWorkItems} along with {@link #setDataTransfer(boolean)}.
+         *
+         * @see JobInfo#isDataTransfer()
+         * @hide
+         */
+        @NonNull
+        public Builder setDataTransfer(boolean dataTransfer) {
+            if (dataTransfer) {
+                mFlags |= FLAG_DATA_TRANSFER;
+            } else {
+                mFlags &= (~FLAG_DATA_TRANSFER);
+            }
+            return this;
+        }
+
+        /**
          * Setting this to true indicates that this job is important while the scheduling app
          * is in the foreground or on the temporary whitelist for background restrictions.
          * This means that the system will relax doze restrictions on this job during this time.
@@ -2062,8 +2123,9 @@
                         "An expedited job must be high or max priority. Don't use expedited jobs"
                                 + " for unimportant tasks.");
             }
-            if ((constraintFlags & ~CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0
-                    || (flags & ~(FLAG_EXPEDITED | FLAG_EXEMPT_FROM_APP_STANDBY)) != 0) {
+            if (((constraintFlags & ~CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0
+                    || (flags & ~(FLAG_EXPEDITED | FLAG_EXEMPT_FROM_APP_STANDBY
+                                    | FLAG_DATA_TRANSFER)) != 0)) {
                 throw new IllegalArgumentException(
                         "An expedited job can only have network and storage-not-low constraints");
             }
@@ -2072,6 +2134,24 @@
                         "Can't call addTriggerContentUri() on an expedited job");
             }
         }
+
+        if ((flags & FLAG_DATA_TRANSFER) != 0) {
+            if (backoffPolicy == BACKOFF_POLICY_LINEAR) {
+                throw new IllegalArgumentException(
+                        "A data transfer job cannot have a linear backoff policy.");
+            }
+            if (hasLateConstraint) {
+                throw new IllegalArgumentException("A data transfer job cannot have a deadline");
+            }
+            if ((flags & FLAG_PREFETCH) != 0) {
+                throw new IllegalArgumentException(
+                        "A data transfer job cannot also be a prefetch job");
+            }
+            if (networkRequest == null) {
+                throw new IllegalArgumentException(
+                        "A data transfer job must specify a valid network type");
+            }
+        }
     }
 
     /**
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index ed72530..0205430 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -98,6 +98,12 @@
      */
     public static final int INTERNAL_STOP_REASON_SUCCESSFUL_FINISH =
             JobProtoEnums.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH; // 10.
+    /**
+     * The user stopped the job via some UI (eg. Task Manager).
+     * @hide
+     */
+    public static final int INTERNAL_STOP_REASON_USER_UI_STOP =
+            JobProtoEnums.INTERNAL_STOP_REASON_USER_UI_STOP; // 11.
 
     /**
      * All the stop reason codes. This should be regarded as an immutable array at runtime.
@@ -121,6 +127,7 @@
             INTERNAL_STOP_REASON_DATA_CLEARED,
             INTERNAL_STOP_REASON_RTC_UPDATED,
             INTERNAL_STOP_REASON_SUCCESSFUL_FINISH,
+            INTERNAL_STOP_REASON_USER_UI_STOP,
     };
 
     /**
@@ -141,6 +148,7 @@
             case INTERNAL_STOP_REASON_DATA_CLEARED: return "data_cleared";
             case INTERNAL_STOP_REASON_RTC_UPDATED: return "rtc_updated";
             case INTERNAL_STOP_REASON_SUCCESSFUL_FINISH: return "successful_finish";
+            case INTERNAL_STOP_REASON_USER_UI_STOP: return "user_ui_stop";
             default: return "unknown:" + reasonCode;
         }
     }
@@ -230,7 +238,7 @@
     public static final int STOP_REASON_APP_STANDBY = 12;
     /**
      * The user stopped the job. This can happen either through force-stop, adb shell commands,
-     * or uninstalling.
+     * uninstalling, or some other UI.
      */
     public static final int STOP_REASON_USER = 13;
     /** The system is doing some processing that requires stopping this job. */
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index dfdb290..659db9f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -22,8 +22,16 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.usage.UsageStatsManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.ClipData;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.NetworkRequest;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.PersistableBundle;
 
@@ -93,6 +101,16 @@
  */
 @SystemService(Context.JOB_SCHEDULER_SERVICE)
 public abstract class JobScheduler {
+    /**
+     * Whether to throw an exception when an app doesn't properly implement all the necessary
+     * data transfer APIs.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    public static final long THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION = 255371817L;
+
     /** @hide */
     @IntDef(prefix = { "RESULT_" }, value = {
             RESULT_FAILURE,
@@ -119,6 +137,132 @@
      */
     public static final int RESULT_SUCCESS = 1;
 
+    /** The job doesn't exist. */
+    public static final int PENDING_JOB_REASON_INVALID_JOB_ID = -2;
+    /** The job is currently running and is therefore not pending. */
+    public static final int PENDING_JOB_REASON_EXECUTING = -1;
+    /**
+     * There is no known reason why the job is pending.
+     * If additional reasons are added on newer Android versions, the system may return this reason
+     * to apps whose target SDK is not high enough to expect that reason.
+     */
+    public static final int PENDING_JOB_REASON_UNDEFINED = 0;
+    /**
+     * The app is in a state that prevents the job from running
+     * (eg. the {@link JobService} component is disabled).
+     */
+    public static final int PENDING_JOB_REASON_APP = 1;
+    /**
+     * The current standby bucket prevents the job from running.
+     *
+     * @see UsageStatsManager#STANDBY_BUCKET_RESTRICTED
+     */
+    public static final int PENDING_JOB_REASON_APP_STANDBY = 2;
+    /**
+     * The app is restricted from running in the background.
+     *
+     * @see ActivityManager#isBackgroundRestricted()
+     * @see PackageManager#isInstantApp()
+     */
+    public static final int PENDING_JOB_REASON_BACKGROUND_RESTRICTION = 3;
+    /**
+     * The requested battery-not-low constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW = 4;
+    /**
+     * The requested charging constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresCharging(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5;
+    /**
+     * The requested connectivity constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest)
+     * @see JobInfo.Builder#setRequiredNetworkType(int)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6;
+    /**
+     * The requested content trigger constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#addTriggerContentUri(JobInfo.TriggerContentUri)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7;
+    /**
+     * The requested idle constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresDeviceIdle(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8;
+    /**
+     * The minimum latency has not transpired.
+     *
+     * @see JobInfo.Builder#setMinimumLatency(long)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9;
+    /**
+     * The system's estimate of when the app will be launched is far away enough to warrant delaying
+     * this job.
+     *
+     * @see JobInfo#isPrefetch()
+     * @see JobInfo.Builder#setPrefetch(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10;
+    /**
+     * The requested storage-not-low constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresStorageNotLow(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW = 11;
+    /**
+     * The job is being deferred due to the device state (eg. Doze, battery saver, memory usage,
+     * thermal status, etc.).
+     */
+    public static final int PENDING_JOB_REASON_DEVICE_STATE = 12;
+    /**
+     * JobScheduler thinks it can defer this job to a more optimal running time.
+     */
+    public static final int PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION = 13;
+    /**
+     * The app has consumed all of its current quota.
+     *
+     * @see UsageStatsManager#getAppStandbyBucket()
+     * @see JobParameters#STOP_REASON_QUOTA
+     */
+    public static final int PENDING_JOB_REASON_QUOTA = 14;
+    /**
+     * JobScheduler is respecting one of the user's actions (eg. force stop or adb shell commands)
+     * to defer this job.
+     */
+    public static final int PENDING_JOB_REASON_USER = 15;
+
+    /** @hide */
+    @IntDef(prefix = {"PENDING_JOB_REASON_"}, value = {
+            PENDING_JOB_REASON_UNDEFINED,
+            PENDING_JOB_REASON_APP,
+            PENDING_JOB_REASON_APP_STANDBY,
+            PENDING_JOB_REASON_BACKGROUND_RESTRICTION,
+            PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW,
+            PENDING_JOB_REASON_CONSTRAINT_CHARGING,
+            PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY,
+            PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER,
+            PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE,
+            PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY,
+            PENDING_JOB_REASON_CONSTRAINT_PREFETCH,
+            PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW,
+            PENDING_JOB_REASON_DEVICE_STATE,
+            PENDING_JOB_REASON_EXECUTING,
+            PENDING_JOB_REASON_INVALID_JOB_ID,
+            PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION,
+            PENDING_JOB_REASON_QUOTA,
+            PENDING_JOB_REASON_USER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PendingJobReason {
+    }
+
     /**
      * Schedule a job to be executed.  Will replace any currently scheduled job with the same
      * ID with the new information in the {@link JobInfo}.  If a job with the given ID is currently
@@ -236,6 +380,32 @@
     public abstract @Nullable JobInfo getPendingJob(int jobId);
 
     /**
+     * Returns a reason why the job is pending and not currently executing. If there are multiple
+     * reasons why a job may be pending, this will only return one of them.
+     */
+    @PendingJobReason
+    public int getPendingJobReason(int jobId) {
+        return PENDING_JOB_REASON_UNDEFINED;
+    }
+
+    /**
+     * Returns {@code true} if the calling app currently holds the
+     * {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs.
+     */
+    public boolean canRunLongJobs() {
+        return false;
+    }
+
+    /**
+     * Returns {@code true} if the app currently holds the
+     * {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs.
+     * @hide
+     */
+    public boolean hasRunLongJobsPermission(@NonNull String packageName, @UserIdInt int userId) {
+        return false;
+    }
+
+    /**
      * <b>For internal system callers only!</b>
      * Returns a list of all currently-executing jobs.
      * @hide
@@ -252,4 +422,32 @@
      */
     @SuppressWarnings("HiddenAbstractMethod")
     public abstract List<JobSnapshot> getAllJobSnapshots();
-}
\ No newline at end of file
+
+    /**
+     * @hide
+     */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+    @SuppressWarnings("HiddenAbstractMethod")
+    public abstract void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer);
+
+    /**
+     * @hide
+     */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+    @SuppressWarnings("HiddenAbstractMethod")
+    public abstract void unregisterUserVisibleJobObserver(
+            @NonNull IUserVisibleJobObserver observer);
+
+    /**
+     * @hide
+     */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+    @SuppressWarnings("HiddenAbstractMethod")
+    public abstract void stopUserVisibleJobsForUser(@NonNull String packageName, int userId);
+}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
index 7b287d5..f56e1ee 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
@@ -44,9 +44,9 @@
      * <p>If this is called from other places, it throws a {@link IllegalStateException).
      */
     public static void registerServiceWrappers() {
-        SystemServiceRegistry.registerStaticService(
+        SystemServiceRegistry.registerContextAwareService(
                 Context.JOB_SCHEDULER_SERVICE, JobScheduler.class,
-                (b) -> new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b)));
+                (context, b) -> new JobSchedulerImpl(context, IJobScheduler.Stub.asInterface(b)));
         SystemServiceRegistry.registerContextAwareService(
                 Context.DEVICE_IDLE_CONTROLLER, DeviceIdleManager.class,
                 (context, b) -> new DeviceIdleManager(
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index d184d44..e88e979 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -16,10 +16,21 @@
 
 package android.app.job;
 
+import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION;
+
+import android.annotation.BytesLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
 import android.app.Service;
+import android.compat.Compatibility;
 import android.content.Intent;
 import android.os.IBinder;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * <p>Entry point for the callback from the {@link android.app.job.JobScheduler}.</p>
  * <p>This is the base class that handles asynchronous requests that were previously scheduled. You
@@ -57,6 +68,32 @@
     public static final String PERMISSION_BIND =
             "android.permission.BIND_JOB_SERVICE";
 
+    /**
+     * Detach the notification supplied to
+     * {@link #setNotification(JobParameters, int, Notification, int)} when the job ends.
+     * The notification will remain shown even after JobScheduler stops the job.
+     *
+     * @hide
+     */
+    public static final int JOB_END_NOTIFICATION_POLICY_DETACH = 0;
+    /**
+     * Cancel and remove the notification supplied to
+     * {@link #setNotification(JobParameters, int, Notification, int)} when the job ends.
+     * The notification will be removed from the notification shade.
+     *
+     * @hide
+     */
+    public static final int JOB_END_NOTIFICATION_POLICY_REMOVE = 1;
+
+    /** @hide */
+    @IntDef(prefix = {"JOB_END_NOTIFICATION_POLICY_"}, value = {
+            JOB_END_NOTIFICATION_POLICY_DETACH,
+            JOB_END_NOTIFICATION_POLICY_REMOVE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface JobEndNotificationPolicy {
+    }
+
     private JobServiceEngine mEngine;
 
     /** @hide */
@@ -72,6 +109,28 @@
                 public boolean onStopJob(JobParameters params) {
                     return JobService.this.onStopJob(params);
                 }
+
+                @Override
+                @BytesLong
+                public long getTransferredDownloadBytes(@NonNull JobParameters params,
+                        @Nullable JobWorkItem item) {
+                    if (item == null) {
+                        return JobService.this.getTransferredDownloadBytes(params);
+                    } else {
+                        return JobService.this.getTransferredDownloadBytes(params, item);
+                    }
+                }
+
+                @Override
+                @BytesLong
+                public long getTransferredUploadBytes(@NonNull JobParameters params,
+                        @Nullable JobWorkItem item) {
+                    if (item == null) {
+                        return JobService.this.getTransferredUploadBytes(params);
+                    } else {
+                        return JobService.this.getTransferredUploadBytes(params, item);
+                    }
+                }
             };
         }
         return mEngine.getBinder();
@@ -171,4 +230,204 @@
      * to end the job entirely.  Regardless of the value returned, your job must stop executing.
      */
     public abstract boolean onStopJob(JobParameters params);
+
+    /**
+     * Update how much data this job will transfer. This method can
+     * be called multiple times within the first 30 seconds after
+     * {@link #onStartJob(JobParameters)} has been called. Only
+     * one call will be heeded after that time has passed.
+     *
+     * This method (or an overload) must be called within the first
+     * 30 seconds for a data transfer job if a payload size estimate
+     * was not provided at the time of scheduling.
+     *
+     * @hide
+     * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+     */
+    public final void updateEstimatedNetworkBytes(@NonNull JobParameters params,
+            @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
+        mEngine.updateEstimatedNetworkBytes(params, null, downloadBytes, uploadBytes);
+    }
+
+    /**
+     * Update how much data will transfer for the JobWorkItem. This
+     * method can be called multiple times within the first 30 seconds
+     * after {@link #onStartJob(JobParameters)} has been called.
+     * Only one call will be heeded after that time has passed.
+     *
+     * This method (or an overload) must be called within the first
+     * 30 seconds for a data transfer job if a payload size estimate
+     * was not provided at the time of scheduling.
+     *
+     * @hide
+     * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+     */
+    public final void updateEstimatedNetworkBytes(@NonNull JobParameters params,
+            @NonNull JobWorkItem jobWorkItem,
+            @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
+        mEngine.updateEstimatedNetworkBytes(params, jobWorkItem, downloadBytes, uploadBytes);
+    }
+
+    /**
+     * Tell JobScheduler how much data has successfully been transferred for the data transfer job.
+     * @hide
+     */
+    public final void updateTransferredNetworkBytes(@NonNull JobParameters params,
+            @BytesLong long transferredDownloadBytes, @BytesLong long transferredUploadBytes) {
+        mEngine.updateTransferredNetworkBytes(params, null,
+                transferredDownloadBytes, transferredUploadBytes);
+    }
+
+    /**
+     * Tell JobScheduler how much data has been transferred for the data transfer
+     * {@link JobWorkItem}.
+     * @hide
+     */
+    public final void updateTransferredNetworkBytes(@NonNull JobParameters params,
+            @NonNull JobWorkItem item,
+            @BytesLong long transferredDownloadBytes, @BytesLong long transferredUploadBytes) {
+        mEngine.updateTransferredNetworkBytes(params, item,
+                transferredDownloadBytes, transferredUploadBytes);
+    }
+
+    /**
+     * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+     * will call this if the job has specified positive estimated download bytes and
+     * {@link #updateTransferredNetworkBytes(JobParameters, long, long)}
+     * hasn't been called recently.
+     *
+     * <p>
+     * This must be implemented for all data transfer jobs.
+     *
+     * @hide
+     * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+     * @see JobInfo#NETWORK_BYTES_UNKNOWN
+     */
+    // TODO(255371817): specify the actual time JS will wait for progress before requesting
+    @BytesLong
+    public long getTransferredDownloadBytes(@NonNull JobParameters params) {
+        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+            // Regular jobs don't have to implement this and JobScheduler won't call this API for
+            // non-data transfer jobs.
+            throw new RuntimeException("Not implemented. Must override in a subclass.");
+        }
+        return 0;
+    }
+
+    /**
+     * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+     * will call this if the job has specified positive estimated upload bytes and
+     * {@link #updateTransferredNetworkBytes(JobParameters, long, long)}
+     * hasn't been called recently.
+     *
+     * <p>
+     * This must be implemented for all data transfer jobs.
+     *
+     * @hide
+     * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+     * @see JobInfo#NETWORK_BYTES_UNKNOWN
+     */
+    // TODO(255371817): specify the actual time JS will wait for progress before requesting
+    @BytesLong
+    public long getTransferredUploadBytes(@NonNull JobParameters params) {
+        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+            // Regular jobs don't have to implement this and JobScheduler won't call this API for
+            // non-data transfer jobs.
+            throw new RuntimeException("Not implemented. Must override in a subclass.");
+        }
+        return 0;
+    }
+
+    /**
+     * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+     * will call this if the job has specified positive estimated download bytes and
+     * {@link #updateTransferredNetworkBytes(JobParameters, JobWorkItem, long, long)}
+     * hasn't been called recently and the job has
+     * {@link JobWorkItem JobWorkItems} that have been
+     * {@link JobParameters#dequeueWork dequeued} but not
+     * {@link JobParameters#completeWork(JobWorkItem) completed}.
+     *
+     * <p>
+     * This must be implemented for all data transfer jobs.
+     *
+     * @hide
+     * @see JobInfo#NETWORK_BYTES_UNKNOWN
+     */
+    // TODO(255371817): specify the actual time JS will wait for progress before requesting
+    @BytesLong
+    public long getTransferredDownloadBytes(@NonNull JobParameters params,
+            @NonNull JobWorkItem item) {
+        if (item == null) {
+            return getTransferredDownloadBytes(params);
+        }
+        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+            // Regular jobs don't have to implement this and JobScheduler won't call this API for
+            // non-data transfer jobs.
+            throw new RuntimeException("Not implemented. Must override in a subclass.");
+        }
+        return 0;
+    }
+
+    /**
+     * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+     * will call this if the job has specified positive estimated upload bytes and
+     * {@link #updateTransferredNetworkBytes(JobParameters, JobWorkItem, long, long)}
+     * hasn't been called recently and the job has
+     * {@link JobWorkItem JobWorkItems} that have been
+     * {@link JobParameters#dequeueWork dequeued} but not
+     * {@link JobParameters#completeWork(JobWorkItem) completed}.
+     *
+     * <p>
+     * This must be implemented for all data transfer jobs.
+     *
+     * @hide
+     * @see JobInfo#NETWORK_BYTES_UNKNOWN
+     */
+    // TODO(255371817): specify the actual time JS will wait for progress before requesting
+    @BytesLong
+    public long getTransferredUploadBytes(@NonNull JobParameters params,
+            @NonNull JobWorkItem item) {
+        if (item == null) {
+            return getTransferredUploadBytes(params);
+        }
+        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+            // Regular jobs don't have to implement this and JobScheduler won't call this API for
+            // non-data transfer jobs.
+            throw new RuntimeException("Not implemented. Must override in a subclass.");
+        }
+        return 0;
+    }
+
+    /**
+     * Provide JobScheduler with a notification to post and tie to this job's lifecycle.
+     * This is required for all user-initiated jobs
+     * (scheduled via {link JobInfo.Builder#setUserInitiated(boolean)}) and optional for
+     * other jobs. If the app does not call this method for a required notification within
+     * 10 seconds after {@link #onStartJob(JobParameters)} is called,
+     * the system will trigger an ANR and stop this job.
+     *
+     * <p>
+     * Note that certain types of jobs
+     * (e.g. {@link JobInfo.Builder#setDataTransfer data transfer jobs}) may require the
+     * notification to have certain characteristics and their documentation will state
+     * any such requirements.
+     *
+     * <p>
+     * JobScheduler will not remember this notification after the job has finished running,
+     * so apps must call this every time the job is started (if required or desired).
+     *
+     * @param params                   The parameters identifying this job, as supplied to
+     *                                 the job in the {@link #onStartJob(JobParameters)} callback.
+     * @param notificationId           The ID for this notification, as per
+     *                                 {@link android.app.NotificationManager#notify(int,
+     *                                 Notification)}.
+     * @param notification             The notification to be displayed.
+     * @param jobEndNotificationPolicy The policy to apply to the notification when the job stops.
+     * @hide
+     */
+    public final void setNotification(@NonNull JobParameters params, int notificationId,
+            @NonNull Notification notification,
+            @JobEndNotificationPolicy int jobEndNotificationPolicy) {
+        mEngine.setNotification(params, notificationId, notification, jobEndNotificationPolicy);
+    }
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index 3d43d20..53e452f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -16,7 +16,14 @@
 
 package android.app.job;
 
+import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
 import android.app.Service;
+import android.compat.Compatibility;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.IBinder;
@@ -25,6 +32,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.os.SomeArgs;
+
 import java.lang.ref.WeakReference;
 
 /**
@@ -51,6 +60,22 @@
      * Message that the client has completed execution of this job.
      */
     private static final int MSG_JOB_FINISHED = 2;
+    /**
+     * Message that will result in a call to
+     * {@link #getTransferredDownloadBytes(JobParameters, JobWorkItem)}.
+     */
+    private static final int MSG_GET_TRANSFERRED_DOWNLOAD_BYTES = 3;
+    /**
+     * Message that will result in a call to
+     * {@link #getTransferredUploadBytes(JobParameters, JobWorkItem)}.
+     */
+    private static final int MSG_GET_TRANSFERRED_UPLOAD_BYTES = 4;
+    /** Message that the client wants to update JobScheduler of the data transfer progress. */
+    private static final int MSG_UPDATE_TRANSFERRED_NETWORK_BYTES = 5;
+    /** Message that the client wants to update JobScheduler of the estimated transfer size. */
+    private static final int MSG_UPDATE_ESTIMATED_NETWORK_BYTES = 6;
+    /** Message that the client wants to give JobScheduler a notification to tie to the job. */
+    private static final int MSG_SET_NOTIFICATION = 7;
 
     private final IJobService mBinder;
 
@@ -68,6 +93,32 @@
         }
 
         @Override
+        public void getTransferredDownloadBytes(@NonNull JobParameters jobParams,
+                @Nullable JobWorkItem jobWorkItem) throws RemoteException {
+            JobServiceEngine service = mService.get();
+            if (service != null) {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = jobParams;
+                args.arg2 = jobWorkItem;
+                service.mHandler.obtainMessage(MSG_GET_TRANSFERRED_DOWNLOAD_BYTES, args)
+                        .sendToTarget();
+            }
+        }
+
+        @Override
+        public void getTransferredUploadBytes(@NonNull JobParameters jobParams,
+                @Nullable JobWorkItem jobWorkItem) throws RemoteException {
+            JobServiceEngine service = mService.get();
+            if (service != null) {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = jobParams;
+                args.arg2 = jobWorkItem;
+                service.mHandler.obtainMessage(MSG_GET_TRANSFERRED_UPLOAD_BYTES, args)
+                        .sendToTarget();
+            }
+        }
+
+        @Override
         public void startJob(JobParameters jobParams) throws RemoteException {
             JobServiceEngine service = mService.get();
             if (service != null) {
@@ -98,9 +149,9 @@
 
         @Override
         public void handleMessage(Message msg) {
-            final JobParameters params = (JobParameters) msg.obj;
             switch (msg.what) {
-                case MSG_EXECUTE_JOB:
+                case MSG_EXECUTE_JOB: {
+                    final JobParameters params = (JobParameters) msg.obj;
                     try {
                         boolean workOngoing = JobServiceEngine.this.onStartJob(params);
                         ackStartMessage(params, workOngoing);
@@ -109,7 +160,9 @@
                         throw new RuntimeException(e);
                     }
                     break;
-                case MSG_STOP_JOB:
+                }
+                case MSG_STOP_JOB: {
+                    final JobParameters params = (JobParameters) msg.obj;
                     try {
                         boolean ret = JobServiceEngine.this.onStopJob(params);
                         ackStopMessage(params, ret);
@@ -118,7 +171,9 @@
                         throw new RuntimeException(e);
                     }
                     break;
-                case MSG_JOB_FINISHED:
+                }
+                case MSG_JOB_FINISHED: {
+                    final JobParameters params = (JobParameters) msg.obj;
                     final boolean needsReschedule = (msg.arg2 == 1);
                     IJobCallback callback = params.getCallback();
                     if (callback != null) {
@@ -132,19 +187,135 @@
                         Log.e(TAG, "finishJob() called for a nonexistent job id.");
                     }
                     break;
+                }
+                case MSG_GET_TRANSFERRED_DOWNLOAD_BYTES: {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final JobParameters params = (JobParameters) args.arg1;
+                    final JobWorkItem item = (JobWorkItem) args.arg2;
+                    try {
+                        long ret = JobServiceEngine.this.getTransferredDownloadBytes(params, item);
+                        ackGetTransferredDownloadBytesMessage(params, item, ret);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Application unable to handle getTransferredDownloadBytes.", e);
+                        throw new RuntimeException(e);
+                    }
+                    args.recycle();
+                    break;
+                }
+                case MSG_GET_TRANSFERRED_UPLOAD_BYTES: {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final JobParameters params = (JobParameters) args.arg1;
+                    final JobWorkItem item = (JobWorkItem) args.arg2;
+                    try {
+                        long ret = JobServiceEngine.this.getTransferredUploadBytes(params, item);
+                        ackGetTransferredUploadBytesMessage(params, item, ret);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Application unable to handle getTransferredUploadBytes.", e);
+                        throw new RuntimeException(e);
+                    }
+                    args.recycle();
+                    break;
+                }
+                case MSG_UPDATE_TRANSFERRED_NETWORK_BYTES: {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final JobParameters params = (JobParameters) args.arg1;
+                    IJobCallback callback = params.getCallback();
+                    if (callback != null) {
+                        try {
+                            callback.updateTransferredNetworkBytes(params.getJobId(),
+                                    (JobWorkItem) args.arg2, args.argl1, args.argl2);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Error updating data transfer progress to system:"
+                                    + " binder has gone away.");
+                        }
+                    } else {
+                        Log.e(TAG, "updateDataTransferProgress() called for a nonexistent job id.");
+                    }
+                    args.recycle();
+                    break;
+                }
+                case MSG_UPDATE_ESTIMATED_NETWORK_BYTES: {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final JobParameters params = (JobParameters) args.arg1;
+                    IJobCallback callback = params.getCallback();
+                    if (callback != null) {
+                        try {
+                            callback.updateEstimatedNetworkBytes(params.getJobId(),
+                                    (JobWorkItem) args.arg2, args.argl1, args.argl2);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Error updating estimated transfer size to system:"
+                                    + " binder has gone away.");
+                        }
+                    } else {
+                        Log.e(TAG,
+                                "updateEstimatedNetworkBytes() called for a nonexistent job id.");
+                    }
+                    args.recycle();
+                    break;
+                }
+                case MSG_SET_NOTIFICATION: {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final JobParameters params = (JobParameters) args.arg1;
+                    final Notification notification = (Notification) args.arg2;
+                    IJobCallback callback = params.getCallback();
+                    if (callback != null) {
+                        try {
+                            callback.setNotification(params.getJobId(),
+                                    args.argi1, notification, args.argi2);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Error providing notification: binder has gone away.");
+                        }
+                    } else {
+                        Log.e(TAG, "setNotification() called for a nonexistent job.");
+                    }
+                    args.recycle();
+                    break;
+                }
                 default:
                     Log.e(TAG, "Unrecognised message received.");
                     break;
             }
         }
 
+        private void ackGetTransferredDownloadBytesMessage(@NonNull JobParameters params,
+                @Nullable JobWorkItem item, long progress) {
+            final IJobCallback callback = params.getCallback();
+            final int jobId = params.getJobId();
+            final int workId = item == null ? -1 : item.getWorkId();
+            if (callback != null) {
+                try {
+                    callback.acknowledgeGetTransferredDownloadBytesMessage(jobId, workId, progress);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "System unreachable for returning progress.");
+                }
+            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Attempting to ack a job that has already been processed.");
+            }
+        }
+
+        private void ackGetTransferredUploadBytesMessage(@NonNull JobParameters params,
+                @Nullable JobWorkItem item, long progress) {
+            final IJobCallback callback = params.getCallback();
+            final int jobId = params.getJobId();
+            final int workId = item == null ? -1 : item.getWorkId();
+            if (callback != null) {
+                try {
+                    callback.acknowledgeGetTransferredUploadBytesMessage(jobId, workId, progress);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "System unreachable for returning progress.");
+                }
+            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Attempting to ack a job that has already been processed.");
+            }
+        }
+
         private void ackStartMessage(JobParameters params, boolean workOngoing) {
             final IJobCallback callback = params.getCallback();
             final int jobId = params.getJobId();
             if (callback != null) {
                 try {
                     callback.acknowledgeStartMessage(jobId, workOngoing);
-                } catch(RemoteException e) {
+                } catch (RemoteException e) {
                     Log.e(TAG, "System unreachable for starting job.");
                 }
             } else {
@@ -213,4 +384,96 @@
         m.arg2 = needsReschedule ? 1 : 0;
         m.sendToTarget();
     }
-}
\ No newline at end of file
+
+    /**
+     * Engine's request to get how much data has been downloaded.
+     *
+     * @hide
+     * @see JobService#getTransferredDownloadBytes()
+     */
+    @BytesLong
+    public long getTransferredDownloadBytes(@NonNull JobParameters params,
+            @Nullable JobWorkItem item) {
+        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+            throw new RuntimeException("Not implemented. Must override in a subclass.");
+        }
+        return 0;
+    }
+
+    /**
+     * Engine's request to get how much data has been uploaded.
+     *
+     * @hide
+     * @see JobService#getTransferredUploadBytes()
+     */
+    @BytesLong
+    public long getTransferredUploadBytes(@NonNull JobParameters params,
+            @Nullable JobWorkItem item) {
+        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+            throw new RuntimeException("Not implemented. Must override in a subclass.");
+        }
+        return 0;
+    }
+
+    /**
+     * Call in to engine to report data transfer progress.
+     *
+     * @hide
+     * @see JobService#updateTransferredNetworkBytes(JobParameters, long, long)
+     */
+    public void updateTransferredNetworkBytes(@NonNull JobParameters params,
+            @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+        if (params == null) {
+            throw new NullPointerException("params");
+        }
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = params;
+        args.arg2 = item;
+        args.argl1 = downloadBytes;
+        args.argl2 = uploadBytes;
+        mHandler.obtainMessage(MSG_UPDATE_TRANSFERRED_NETWORK_BYTES, args).sendToTarget();
+    }
+
+    /**
+     * Call in to engine to report data transfer progress.
+     *
+     * @hide
+     * @see JobService#updateEstimatedNetworkBytes(JobParameters, JobWorkItem, long, long)
+     */
+    public void updateEstimatedNetworkBytes(@NonNull JobParameters params,
+            @NonNull JobWorkItem item,
+            @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
+        if (params == null) {
+            throw new NullPointerException("params");
+        }
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = params;
+        args.arg2 = item;
+        args.argl1 = downloadBytes;
+        args.argl2 = uploadBytes;
+        mHandler.obtainMessage(MSG_UPDATE_ESTIMATED_NETWORK_BYTES, args).sendToTarget();
+    }
+
+    /**
+     * Give JobScheduler a notification to tie to this job's lifecycle.
+     *
+     * @hide
+     * @see JobService#setNotification(JobParameters, int, Notification, int)
+     */
+    public void setNotification(@NonNull JobParameters params, int notificationId,
+            @NonNull Notification notification,
+            @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
+        if (params == null) {
+            throw new NullPointerException("params");
+        }
+        if (notification == null) {
+            throw new NullPointerException("notification");
+        }
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = params;
+        args.arg2 = notification;
+        args.argi1 = notificationId;
+        args.argi2 = jobEndNotificationPolicy;
+        mHandler.obtainMessage(MSG_SET_NOTIFICATION, args).sendToTarget();
+    }
+}
diff --git a/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.aidl b/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.aidl
new file mode 100644
index 0000000..5160b42
--- /dev/null
+++ b/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.aidl
@@ -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.
+ */
+
+package android.app.job;
+
+parcelable UserVisibleJobSummary;
diff --git a/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java b/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java
new file mode 100644
index 0000000..afcbe7d
--- /dev/null
+++ b/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java
@@ -0,0 +1,122 @@
+/*
+ * 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.job;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Summary of a scheduled job that the user is meant to be aware of.
+ *
+ * @hide
+ */
+public class UserVisibleJobSummary implements Parcelable {
+    private final int mCallingUid;
+    private final int mSourceUserId;
+    @NonNull
+    private final String mSourcePackageName;
+    private final int mJobId;
+
+    public UserVisibleJobSummary(int callingUid, int sourceUserId,
+            @NonNull String sourcePackageName, int jobId) {
+        mCallingUid = callingUid;
+        mSourceUserId = sourceUserId;
+        mSourcePackageName = sourcePackageName;
+        mJobId = jobId;
+    }
+
+    protected UserVisibleJobSummary(Parcel in) {
+        mCallingUid = in.readInt();
+        mSourceUserId = in.readInt();
+        mSourcePackageName = in.readString();
+        mJobId = in.readInt();
+    }
+
+    public int getCallingUid() {
+        return mCallingUid;
+    }
+
+    public int getJobId() {
+        return mJobId;
+    }
+
+    public int getSourceUserId() {
+        return mSourceUserId;
+    }
+
+    public String getSourcePackageName() {
+        return mSourcePackageName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof UserVisibleJobSummary)) return false;
+        UserVisibleJobSummary that = (UserVisibleJobSummary) o;
+        return mCallingUid == that.mCallingUid
+                && mSourceUserId == that.mSourceUserId
+                && mSourcePackageName.equals(that.mSourcePackageName)
+                && mJobId == that.mJobId;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 0;
+        result = 31 * result + mCallingUid;
+        result = 31 * result + mSourceUserId;
+        result = 31 * result + mSourcePackageName.hashCode();
+        result = 31 * result + mJobId;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "UserVisibleJobSummary{"
+                + "callingUid=" + mCallingUid
+                + ", sourceUserId=" + mSourceUserId
+                + ", sourcePackageName='" + mSourcePackageName + "'"
+                + ", jobId=" + mJobId
+                + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mCallingUid);
+        dest.writeInt(mSourceUserId);
+        dest.writeString(mSourcePackageName);
+        dest.writeInt(mJobId);
+    }
+
+    public static final Creator<UserVisibleJobSummary> CREATOR =
+            new Creator<UserVisibleJobSummary>() {
+                @Override
+                public UserVisibleJobSummary createFromParcel(Parcel in) {
+                    return new UserVisibleJobSummary(in);
+                }
+
+                @Override
+                public UserVisibleJobSummary[] newArray(int size) {
+                    return new UserVisibleJobSummary[size];
+                }
+            };
+}
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/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index bd475e9..e2d302f 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -29,8 +29,10 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
+import android.app.BroadcastOptions;
 import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
@@ -313,7 +315,9 @@
     private Sensor mMotionSensor;
     private LocationRequest mLocationRequest;
     private Intent mIdleIntent;
+    private Bundle mIdleIntentOptions;
     private Intent mLightIdleIntent;
+    private Bundle mLightIdleIntentOptions;
     private AnyMotionDetector mAnyMotionDetector;
     private final AppStateTrackerImpl mAppStateTracker;
     @GuardedBy("this")
@@ -740,8 +744,10 @@
         }
     };
 
-    private final BroadcastReceiver mIdleStartedDoneReceiver = new BroadcastReceiver() {
-        @Override public void onReceive(Context context, Intent intent) {
+    private final IIntentReceiver mIdleStartedDoneReceiver = new IIntentReceiver.Stub() {
+        @Override
+        public void performReceive(Intent intent, int resultCode, String data, Bundle extras,
+                boolean ordered, boolean sticky, int sendingUser) {
             // When coming out of a deep idle, we will add in some delay before we allow
             // the system to settle down and finish the maintenance window.  This is
             // to give a chance for any pending work to be scheduled.
@@ -1795,10 +1801,12 @@
                     } catch (RemoteException e) {
                     }
                     if (deepChanged) {
-                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mIdleIntentOptions);
                     }
                     if (lightChanged) {
-                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mLightIdleIntentOptions);
                     }
                     EventLogTags.writeDeviceIdleOnComplete();
                     mGoingIdleWakeLock.release();
@@ -1816,13 +1824,15 @@
                     }
                     if (deepChanged) {
                         incActiveIdleOps();
-                        getContext().sendOrderedBroadcastAsUser(mIdleIntent, UserHandle.ALL,
-                                null, mIdleStartedDoneReceiver, null, 0, null, null);
+                        mLocalActivityManager.broadcastIntentWithCallback(mIdleIntent,
+                                mIdleStartedDoneReceiver, null, UserHandle.USER_ALL,
+                                null, null, mIdleIntentOptions);
                     }
                     if (lightChanged) {
                         incActiveIdleOps();
-                        getContext().sendOrderedBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
-                                null, mIdleStartedDoneReceiver, null, 0, null, null);
+                        mLocalActivityManager.broadcastIntentWithCallback(mLightIdleIntent,
+                                mIdleStartedDoneReceiver, null, UserHandle.USER_ALL,
+                                null, null, mLightIdleIntentOptions);
                     }
                     // Always start with one active op for the message being sent here.
                     // Now we are done!
@@ -1844,10 +1854,12 @@
                     } catch (RemoteException e) {
                     }
                     if (deepChanged) {
-                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mIdleIntentOptions);
                     }
                     if (lightChanged) {
-                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
+                                null /* receiverPermission */, mLightIdleIntentOptions);
                     }
                     EventLogTags.writeDeviceIdleOffComplete();
                 } break;
@@ -2526,6 +2538,9 @@
                 mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
                 mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                         | Intent.FLAG_RECEIVER_FOREGROUND);
+                final BroadcastOptions options = BroadcastOptions.makeBasic();
+                options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+                mIdleIntentOptions = mLightIdleIntentOptions = options.toBundle();
 
                 IntentFilter filter = new IntentFilter();
                 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
@@ -4806,6 +4821,9 @@
                     Binder.restoreCallingIdentity(token);
                 }
             } else {
+                if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
+                    return -1;
+                }
                 synchronized (this) {
                     for (int j=0; j<mPowerSaveWhitelistAppsExceptIdle.size(); j++) {
                         pw.print("system-excidle,");
@@ -4867,6 +4885,9 @@
                 pw.println("[-r] requires a package name");
                 return -1;
             } else {
+                if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
+                    return -1;
+                }
                 dumpTempWhitelistSchedule(pw, false);
             }
         } else if ("except-idle-whitelist".equals(cmd)) {
@@ -4942,6 +4963,9 @@
                     Binder.restoreCallingIdentity(token);
                 }
             } else {
+                if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
+                    return -1;
+                }
                 synchronized (this) {
                     for (int j = 0; j < mPowerSaveWhitelistApps.size(); j++) {
                         pw.print(mPowerSaveWhitelistApps.keyAt(j));
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index bedfa7f..29e730d 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -84,6 +84,7 @@
 import android.content.PermissionChecker;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserPackage;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.BatteryManager;
@@ -120,7 +121,6 @@
 import android.util.IntArray;
 import android.util.Log;
 import android.util.LongArrayQueue;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseArrayMap;
@@ -402,7 +402,7 @@
             public long lastUsage;
         }
         /** Map of {package, user} -> {quotaInfo} */
-        private final ArrayMap<Pair<String, Integer>, QuotaInfo> mQuotaBuffer = new ArrayMap<>();
+        private final ArrayMap<UserPackage, QuotaInfo> mQuotaBuffer = new ArrayMap<>();
 
         private long mMaxDuration;
 
@@ -414,11 +414,11 @@
             if (quota <= 0) {
                 return;
             }
-            final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
-            QuotaInfo currentQuotaInfo = mQuotaBuffer.get(packageUser);
+            final UserPackage userPackage = UserPackage.of(userId, packageName);
+            QuotaInfo currentQuotaInfo = mQuotaBuffer.get(userPackage);
             if (currentQuotaInfo == null) {
                 currentQuotaInfo = new QuotaInfo();
-                mQuotaBuffer.put(packageUser, currentQuotaInfo);
+                mQuotaBuffer.put(userPackage, currentQuotaInfo);
             }
             currentQuotaInfo.remainingQuota = quota;
             currentQuotaInfo.expirationTime = nowElapsed + mMaxDuration;
@@ -426,8 +426,8 @@
 
         /** Returns if the supplied package has reserve quota to fire at the given time. */
         boolean hasQuota(String packageName, int userId, long triggerElapsed) {
-            final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
-            final QuotaInfo quotaInfo = mQuotaBuffer.get(packageUser);
+            final UserPackage userPackage = UserPackage.of(userId, packageName);
+            final QuotaInfo quotaInfo = mQuotaBuffer.get(userPackage);
 
             return quotaInfo != null && quotaInfo.remainingQuota > 0
                     && triggerElapsed <= quotaInfo.expirationTime;
@@ -438,8 +438,8 @@
          * required.
          */
         void recordUsage(String packageName, int userId, long nowElapsed) {
-            final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
-            final QuotaInfo quotaInfo = mQuotaBuffer.get(packageUser);
+            final UserPackage userPackage = UserPackage.of(userId, packageName);
+            final QuotaInfo quotaInfo = mQuotaBuffer.get(userPackage);
 
             if (quotaInfo == null) {
                 Slog.wtf(TAG, "Temporary quota being consumed at " + nowElapsed
@@ -479,26 +479,26 @@
 
         void removeForUser(int userId) {
             for (int i = mQuotaBuffer.size() - 1; i >= 0; i--) {
-                final Pair<String, Integer> packageUserKey = mQuotaBuffer.keyAt(i);
-                if (packageUserKey.second == userId) {
+                final UserPackage userPackageKey = mQuotaBuffer.keyAt(i);
+                if (userPackageKey.userId == userId) {
                     mQuotaBuffer.removeAt(i);
                 }
             }
         }
 
         void removeForPackage(String packageName, int userId) {
-            final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
-            mQuotaBuffer.remove(packageUser);
+            final UserPackage userPackage = UserPackage.of(userId, packageName);
+            mQuotaBuffer.remove(userPackage);
         }
 
         void dump(IndentingPrintWriter pw, long nowElapsed) {
             pw.increaseIndent();
             for (int i = 0; i < mQuotaBuffer.size(); i++) {
-                final Pair<String, Integer> packageUser = mQuotaBuffer.keyAt(i);
+                final UserPackage userPackage = mQuotaBuffer.keyAt(i);
                 final QuotaInfo quotaInfo = mQuotaBuffer.valueAt(i);
-                pw.print(packageUser.first);
+                pw.print(userPackage.packageName);
                 pw.print(", u");
-                pw.print(packageUser.second);
+                pw.print(userPackage.userId);
                 pw.print(": ");
                 if (quotaInfo == null) {
                     pw.print("--");
@@ -522,8 +522,7 @@
      */
     @VisibleForTesting
     static class AppWakeupHistory {
-        private ArrayMap<Pair<String, Integer>, LongArrayQueue> mPackageHistory =
-                new ArrayMap<>();
+        private final ArrayMap<UserPackage, LongArrayQueue> mPackageHistory = new ArrayMap<>();
         private long mWindowSize;
 
         AppWakeupHistory(long windowSize) {
@@ -531,11 +530,11 @@
         }
 
         void recordAlarmForPackage(String packageName, int userId, long nowElapsed) {
-            final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
-            LongArrayQueue history = mPackageHistory.get(packageUser);
+            final UserPackage userPackage = UserPackage.of(userId, packageName);
+            LongArrayQueue history = mPackageHistory.get(userPackage);
             if (history == null) {
                 history = new LongArrayQueue();
-                mPackageHistory.put(packageUser, history);
+                mPackageHistory.put(userPackage, history);
             }
             if (history.size() == 0 || history.peekLast() < nowElapsed) {
                 history.addLast(nowElapsed);
@@ -545,16 +544,16 @@
 
         void removeForUser(int userId) {
             for (int i = mPackageHistory.size() - 1; i >= 0; i--) {
-                final Pair<String, Integer> packageUserKey = mPackageHistory.keyAt(i);
-                if (packageUserKey.second == userId) {
+                final UserPackage userPackageKey = mPackageHistory.keyAt(i);
+                if (userPackageKey.userId == userId) {
                     mPackageHistory.removeAt(i);
                 }
             }
         }
 
         void removeForPackage(String packageName, int userId) {
-            final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
-            mPackageHistory.remove(packageUser);
+            final UserPackage userPackage = UserPackage.of(userId, packageName);
+            mPackageHistory.remove(userPackage);
         }
 
         private void snapToWindow(LongArrayQueue history) {
@@ -564,7 +563,7 @@
         }
 
         int getTotalWakeupsInWindow(String packageName, int userId) {
-            final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId));
+            final LongArrayQueue history = mPackageHistory.get(UserPackage.of(userId, packageName));
             return (history == null) ? 0 : history.size();
         }
 
@@ -573,7 +572,7 @@
          *          (1=1st-last=the ultimate wakeup and 2=2nd-last=the penultimate wakeup)
          */
         long getNthLastWakeupForPackage(String packageName, int userId, int n) {
-            final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId));
+            final LongArrayQueue history = mPackageHistory.get(UserPackage.of(userId, packageName));
             if (history == null) {
                 return 0;
             }
@@ -584,11 +583,11 @@
         void dump(IndentingPrintWriter pw, long nowElapsed) {
             pw.increaseIndent();
             for (int i = 0; i < mPackageHistory.size(); i++) {
-                final Pair<String, Integer> packageUser = mPackageHistory.keyAt(i);
+                final UserPackage userPackage = mPackageHistory.keyAt(i);
                 final LongArrayQueue timestamps = mPackageHistory.valueAt(i);
-                pw.print(packageUser.first);
+                pw.print(userPackage.packageName);
                 pw.print(", u");
-                pw.print(packageUser.second);
+                pw.print(userPackage.userId);
                 pw.print(": ");
                 // limit dumping to a max of 100 values
                 final int lastIdx = Math.max(0, timestamps.size() - 100);
@@ -1501,13 +1500,13 @@
      *                       null indicates all
      * @return True if there was any reordering done to the current list.
      */
-    boolean reorderAlarmsBasedOnStandbyBuckets(ArraySet<Pair<String, Integer>> targetPackages) {
+    boolean reorderAlarmsBasedOnStandbyBuckets(ArraySet<UserPackage> targetPackages) {
         final long start = mStatLogger.getTime();
 
         final boolean changed = mAlarmStore.updateAlarmDeliveries(a -> {
-            final Pair<String, Integer> packageUser =
-                    Pair.create(a.sourcePackage, UserHandle.getUserId(a.creatorUid));
-            if (targetPackages != null && !targetPackages.contains(packageUser)) {
+            final UserPackage userPackage =
+                    UserPackage.of(UserHandle.getUserId(a.creatorUid), a.sourcePackage);
+            if (targetPackages != null && !targetPackages.contains(userPackage)) {
                 return false;
             }
             return adjustDeliveryTimeBasedOnBucketLocked(a);
@@ -1524,13 +1523,13 @@
      *                       null indicates all
      * @return True if there was any reordering done to the current list.
      */
-    boolean reorderAlarmsBasedOnTare(ArraySet<Pair<String, Integer>> targetPackages) {
+    boolean reorderAlarmsBasedOnTare(ArraySet<UserPackage> targetPackages) {
         final long start = mStatLogger.getTime();
 
         final boolean changed = mAlarmStore.updateAlarmDeliveries(a -> {
-            final Pair<String, Integer> packageUser =
-                    Pair.create(a.sourcePackage, UserHandle.getUserId(a.creatorUid));
-            if (targetPackages != null && !targetPackages.contains(packageUser)) {
+            final UserPackage userPackage =
+                    UserPackage.of(UserHandle.getUserId(a.creatorUid), a.sourcePackage);
+            if (targetPackages != null && !targetPackages.contains(userPackage)) {
                 return false;
             }
             return adjustDeliveryTimeBasedOnTareLocked(a);
@@ -4786,8 +4785,7 @@
                                     }
                                 }
                             }
-                            final ArraySet<Pair<String, Integer>> triggerPackages =
-                                    new ArraySet<>();
+                            final ArraySet<UserPackage> triggerPackages = new ArraySet<>();
                             final IntArray wakeupUids = new IntArray();
                             for (int i = 0; i < triggerList.size(); i++) {
                                 final Alarm a = triggerList.get(i);
@@ -4796,13 +4794,13 @@
                                 }
                                 if (mConstants.USE_TARE_POLICY) {
                                     if (!isExemptFromTare(a)) {
-                                        triggerPackages.add(Pair.create(
-                                                a.sourcePackage,
-                                                UserHandle.getUserId(a.creatorUid)));
+                                        triggerPackages.add(UserPackage.of(
+                                                UserHandle.getUserId(a.creatorUid),
+                                                a.sourcePackage));
                                     }
                                 } else if (!isExemptFromAppStandby(a)) {
-                                    triggerPackages.add(Pair.create(
-                                            a.sourcePackage, UserHandle.getUserId(a.creatorUid)));
+                                    triggerPackages.add(UserPackage.of(
+                                            UserHandle.getUserId(a.creatorUid), a.sourcePackage));
                                 }
                             }
                             if (wakeupUids.size() > 0 && mBatteryStatsInternal != null) {
@@ -4990,8 +4988,8 @@
                 case TEMPORARY_QUOTA_CHANGED:
                 case APP_STANDBY_BUCKET_CHANGED:
                     synchronized (mLock) {
-                        final ArraySet<Pair<String, Integer>> filterPackages = new ArraySet<>();
-                        filterPackages.add(Pair.create((String) msg.obj, msg.arg1));
+                        final ArraySet<UserPackage> filterPackages = new ArraySet<>();
+                        filterPackages.add(UserPackage.of(msg.arg1, (String) msg.obj));
                         if (reorderAlarmsBasedOnStandbyBuckets(filterPackages)) {
                             rescheduleKernelAlarmsLocked();
                             updateNextAlarmClockLocked();
@@ -5004,8 +5002,8 @@
                         final int userId = msg.arg1;
                         final String packageName = (String) msg.obj;
 
-                        final ArraySet<Pair<String, Integer>> filterPackages = new ArraySet<>();
-                        filterPackages.add(Pair.create(packageName, userId));
+                        final ArraySet<UserPackage> filterPackages = new ArraySet<>();
+                        filterPackages.add(UserPackage.of(userId, packageName));
                         if (reorderAlarmsBasedOnTare(filterPackages)) {
                             rescheduleKernelAlarmsLocked();
                             updateNextAlarmClockLocked();
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 52dc01b..16201b2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.job;
 
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
@@ -99,6 +101,18 @@
     static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR =
             CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular";
     private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = STANDARD_CONCURRENCY_LIMIT / 2;
+    @VisibleForTesting
+    static final String KEY_ENABLE_MAX_WAIT_TIME_BYPASS =
+            CONFIG_KEY_PREFIX_CONCURRENCY + "enable_max_wait_time_bypass";
+    private static final boolean DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS = true;
+    private static final String KEY_MAX_WAIT_EJ_MS =
+            CONFIG_KEY_PREFIX_CONCURRENCY + "max_wait_ej_ms";
+    @VisibleForTesting
+    static final long DEFAULT_MAX_WAIT_EJ_MS = 5 * MINUTE_IN_MILLIS;
+    private static final String KEY_MAX_WAIT_REGULAR_MS =
+            CONFIG_KEY_PREFIX_CONCURRENCY + "max_wait_regular_ms";
+    @VisibleForTesting
+    static final long DEFAULT_MAX_WAIT_REGULAR_MS = 30 * MINUTE_IN_MILLIS;
 
     /**
      * Set of possible execution types that a job can have. The actual type(s) of a job are based
@@ -313,6 +327,7 @@
     private final ArraySet<ContextAssignment> mRecycledIdle = new ArraySet<>();
     private final ArrayList<ContextAssignment> mRecycledPreferredUidOnly = new ArrayList<>();
     private final ArrayList<ContextAssignment> mRecycledStoppable = new ArrayList<>();
+    private final AssignmentInfo mRecycledAssignmentInfo = new AssignmentInfo();
 
     private final Pools.Pool<ContextAssignment> mContextAssignmentPool =
             new Pools.SimplePool<>(MAX_RETAINED_OBJECTS);
@@ -353,6 +368,20 @@
      */
     private int mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR;
 
+    private boolean mMaxWaitTimeBypassEnabled = DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS;
+
+    /**
+     * The maximum time an expedited job would have to be potentially waiting for an available
+     * slot before we would consider creating a new slot for it.
+     */
+    private long mMaxWaitEjMs = DEFAULT_MAX_WAIT_EJ_MS;
+
+    /**
+     * The maximum time a regular job would have to be potentially waiting for an available
+     * slot before we would consider creating a new slot for it.
+     */
+    private long mMaxWaitRegularMs = DEFAULT_MAX_WAIT_REGULAR_MS;
+
     /** Current memory trim level. */
     private int mLastMemoryTrimLevel;
 
@@ -386,7 +415,7 @@
     @VisibleForTesting
     JobConcurrencyManager(JobSchedulerService service, Injector injector) {
         mService = service;
-        mLock = mService.mLock;
+        mLock = mService.getLock();
         mContext = service.getTestableContext();
         mInjector = injector;
 
@@ -666,7 +695,8 @@
         }
 
         prepareForAssignmentDeterminationLocked(
-                mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+                mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
+                mRecycledAssignmentInfo);
 
         if (DEBUG) {
             Slog.d(TAG, printAssignments("running jobs initial",
@@ -674,7 +704,8 @@
         }
 
         determineAssignmentsLocked(
-                mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+                mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
+                mRecycledAssignmentInfo);
 
         if (DEBUG) {
             Slog.d(TAG, printAssignments("running jobs final",
@@ -686,7 +717,8 @@
         carryOutAssignmentChangesLocked(mRecycledChanged);
 
         cleanUpAfterAssignmentChangesLocked(
-                mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+                mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
+                mRecycledAssignmentInfo);
 
         noteConcurrency();
     }
@@ -695,7 +727,8 @@
     @GuardedBy("mLock")
     void prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
             final List<ContextAssignment> preferredUidOnly,
-            final List<ContextAssignment> stoppable) {
+            final List<ContextAssignment> stoppable,
+            final AssignmentInfo info) {
         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
         final List<JobServiceContext> activeServices = mActiveServices;
 
@@ -709,6 +742,8 @@
         updateNonRunningPrioritiesLocked(pendingJobQueue, true);
 
         final int numRunningJobs = activeServices.size();
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+        long minPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE;
         for (int i = 0; i < numRunningJobs; ++i) {
             final JobServiceContext jsc = activeServices.get(i);
             final JobStatus js = jsc.getRunningJobLocked();
@@ -723,12 +758,18 @@
             if (js != null) {
                 mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType());
                 assignment.workType = jsc.getRunningJobWorkType();
+                if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
+                    info.numRunningTopEj++;
+                }
             }
 
             assignment.preferredUid = jsc.getPreferredUid();
             if ((assignment.shouldStopJobReason = shouldStopRunningJobLocked(jsc)) != null) {
                 stoppable.add(assignment);
             } else {
+                assignment.timeUntilStoppableMs = jsc.getRemainingGuaranteedTimeMs(nowElapsed);
+                minPreferredUidOnlyWaitingTimeMs =
+                        Math.min(minPreferredUidOnlyWaitingTimeMs, assignment.timeUntilStoppableMs);
                 preferredUidOnly.add(assignment);
             }
         }
@@ -754,6 +795,11 @@
         }
 
         mWorkCountTracker.onCountDone();
+        // Set 0 if there were no preferred UID only contexts to indicate no waiting time due
+        // to such jobs.
+        info.minPreferredUidOnlyWaitingTimeMs =
+                minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE
+                        ? 0 : minPreferredUidOnlyWaitingTimeMs;
     }
 
     @VisibleForTesting
@@ -761,12 +807,14 @@
     void determineAssignmentsLocked(final ArraySet<ContextAssignment> changed,
             final ArraySet<ContextAssignment> idle,
             final List<ContextAssignment> preferredUidOnly,
-            final List<ContextAssignment> stoppable) {
+            final List<ContextAssignment> stoppable,
+            @NonNull AssignmentInfo info) {
         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
         final List<JobServiceContext> activeServices = mActiveServices;
         pendingJobQueue.resetIterator();
         JobStatus nextPending;
         int projectedRunningCount = activeServices.size();
+        long minChangedWaitingTimeMs = Long.MAX_VALUE;
         while ((nextPending = pendingJobQueue.next()) != null) {
             if (mRunningJobs.contains(nextPending)) {
                 // Should never happen.
@@ -785,6 +833,14 @@
                         + " to: " + nextPending);
             }
 
+            // Factoring minChangedWaitingTimeMs into the min waiting time effectively limits
+            // the number of additional contexts that are created due to long waiting times.
+            // By factoring it in, we imply that the new slot will be available for other
+            // pending jobs that could be designated as waiting too long, and those other jobs
+            // would only have to wait for the new slots to become available.
+            final long minWaitingTimeMs =
+                    Math.min(info.minPreferredUidOnlyWaitingTimeMs, minChangedWaitingTimeMs);
+
             // Find an available slot for nextPending. The context should be one of the following:
             // 1. Unused
             // 2. Its job should have used up its minimum execution guarantee so it
@@ -812,13 +868,6 @@
                 }
             }
             if (selectedContext == null && stoppable.size() > 0) {
-                int topEjCount = 0;
-                for (int r = mRunningJobs.size() - 1; r >= 0; --r) {
-                    JobStatus js = mRunningJobs.valueAt(r);
-                    if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
-                        topEjCount++;
-                    }
-                }
                 for (int s = stoppable.size() - 1; s >= 0; --s) {
                     final ContextAssignment assignment = stoppable.get(s);
                     final JobStatus runningJob = assignment.context.getRunningJobLocked();
@@ -833,12 +882,21 @@
                     //    app was on TOP, the app is still TOP, but there are too many TOP+EJs
                     //    running (because we don't want them to starve out other apps and the
                     //    current job has already run for the minimum guaranteed time).
+                    // 5. This new job could be waiting for too long for a slot to open up
                     boolean canReplace = isTopEj; // Case 1
                     if (!canReplace && !isInOverage) {
                         final int currentJobBias = mService.evaluateJobBiasLocked(runningJob);
                         canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2
                                 || currentJobBias < JobInfo.BIAS_TOP_APP // Case 3
-                                || topEjCount > .5 * mWorkTypeConfig.getMaxTotal(); // Case 4
+                                // Case 4
+                                || info.numRunningTopEj > .5 * mWorkTypeConfig.getMaxTotal();
+                    }
+                    if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5
+                        if (nextPending.shouldTreatAsExpeditedJob()) {
+                            canReplace = minWaitingTimeMs >= mMaxWaitEjMs;
+                        } else {
+                            canReplace = minWaitingTimeMs >= mMaxWaitRegularMs;
+                        }
                     }
                     if (canReplace) {
                         int replaceWorkType = mWorkCountTracker.canJobStart(allWorkTypes,
@@ -860,6 +918,7 @@
             }
             if (selectedContext == null && (!isInOverage || isTopEj)) {
                 int lowestBiasSeen = Integer.MAX_VALUE;
+                long newMinPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE;
                 for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
                     final ContextAssignment assignment = preferredUidOnly.get(p);
                     final JobStatus runningJob = assignment.context.getRunningJobLocked();
@@ -872,6 +931,13 @@
                     }
 
                     if (selectedContext == null || lowestBiasSeen > jobBias) {
+                        if (selectedContext != null) {
+                            // We're no longer using the previous context, so factor it into the
+                            // calculation.
+                            newMinPreferredUidOnlyWaitingTimeMs = Math.min(
+                                    newMinPreferredUidOnlyWaitingTimeMs,
+                                    selectedContext.timeUntilStoppableMs);
+                        }
                         // Step down the preemption threshold - wind up replacing
                         // the lowest-bias running job
                         lowestBiasSeen = jobBias;
@@ -880,11 +946,17 @@
                         assignment.preemptReasonCode = JobParameters.STOP_REASON_PREEMPT;
                         // In this case, we're just going to preempt a low bias job, we're not
                         // actually starting a job, so don't set startingJob to true.
+                    } else {
+                        // We're not going to use this context, so factor it into the calculation.
+                        newMinPreferredUidOnlyWaitingTimeMs = Math.min(
+                                newMinPreferredUidOnlyWaitingTimeMs,
+                                assignment.timeUntilStoppableMs);
                     }
                 }
                 if (selectedContext != null) {
                     selectedContext.newJob = nextPending;
                     preferredUidOnly.remove(selectedContext);
+                    info.minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
                 }
             }
             // Make sure to run EJs for the TOP app immediately.
@@ -901,6 +973,9 @@
                     selectedContext = null;
                 }
                 if (selectedContext == null) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Allowing additional context because EJ would wait too long");
+                    }
                     selectedContext = mContextAssignmentPool.acquire();
                     if (selectedContext == null) {
                         selectedContext = new ContextAssignment();
@@ -913,6 +988,35 @@
                     selectedContext.newWorkType =
                             (workType != WORK_TYPE_NONE) ? workType : WORK_TYPE_TOP;
                 }
+            } else if (selectedContext == null && mMaxWaitTimeBypassEnabled) {
+                final boolean wouldBeWaitingTooLong = nextPending.shouldTreatAsExpeditedJob()
+                        ? minWaitingTimeMs >= mMaxWaitEjMs
+                        : minWaitingTimeMs >= mMaxWaitRegularMs;
+                if (wouldBeWaitingTooLong) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Allowing additional context because job would wait too long");
+                    }
+                    selectedContext = mContextAssignmentPool.acquire();
+                    if (selectedContext == null) {
+                        selectedContext = new ContextAssignment();
+                    }
+                    selectedContext.context = mIdleContexts.size() > 0
+                            ? mIdleContexts.removeAt(mIdleContexts.size() - 1)
+                            : createNewJobServiceContext();
+                    selectedContext.newJob = nextPending;
+                    final int workType = mWorkCountTracker.canJobStart(allWorkTypes);
+                    if (workType != WORK_TYPE_NONE) {
+                        selectedContext.newWorkType = workType;
+                    } else {
+                        // Use the strongest work type possible for this job.
+                        for (int type = 1; type <= ALL_WORK_TYPES; type = type << 1) {
+                            if ((type & allWorkTypes) != 0) {
+                                selectedContext.newWorkType = type;
+                                break;
+                            }
+                        }
+                    }
+                }
             }
             final PackageStats packageStats = getPkgStatsLocked(
                     nextPending.getSourceUserId(), nextPending.getSourcePackageName());
@@ -923,6 +1027,8 @@
                 }
                 if (selectedContext.newJob != null) {
                     projectedRunningCount++;
+                    minChangedWaitingTimeMs = Math.min(minChangedWaitingTimeMs,
+                            mService.getMinJobExecutionGuaranteeMs(selectedContext.newJob));
                 }
                 packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob());
             }
@@ -967,7 +1073,8 @@
     private void cleanUpAfterAssignmentChangesLocked(final ArraySet<ContextAssignment> changed,
             final ArraySet<ContextAssignment> idle,
             final List<ContextAssignment> preferredUidOnly,
-            final List<ContextAssignment> stoppable) {
+            final List<ContextAssignment> stoppable,
+            final AssignmentInfo assignmentInfo) {
         for (int s = stoppable.size() - 1; s >= 0; --s) {
             final ContextAssignment assignment = stoppable.get(s);
             assignment.clear();
@@ -988,6 +1095,7 @@
         idle.clear();
         stoppable.clear();
         preferredUidOnly.clear();
+        assignmentInfo.clear();
         mWorkCountTracker.resetStagingCount();
         mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
     }
@@ -1067,7 +1175,7 @@
 
             if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()
                     && restriction.isJobRestricted(jobStatus)) {
-                jsc.cancelExecutingJobLocked(restriction.getReason(),
+                jsc.cancelExecutingJobLocked(restriction.getStopReason(),
                         restriction.getInternalReason(),
                         JobParameters.getInternalReasonCodeDescription(
                                 restriction.getInternalReason()));
@@ -1076,6 +1184,22 @@
     }
 
     @GuardedBy("mLock")
+    void stopUserVisibleJobsLocked(int userId, @NonNull String packageName,
+            @JobParameters.StopReason int reason, int internalReasonCode) {
+        for (int i = mActiveServices.size() - 1; i >= 0; --i) {
+            final JobServiceContext jsc = mActiveServices.get(i);
+            final JobStatus jobStatus = jsc.getRunningJobLocked();
+
+            if (jobStatus != null && userId == jobStatus.getSourceUserId()
+                    && jobStatus.getSourcePackageName().equals(packageName)
+                    && jobStatus.isUserVisibleJob()) {
+                jsc.cancelExecutingJobLocked(reason, internalReasonCode,
+                        JobParameters.getInternalReasonCodeDescription(internalReasonCode));
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
     void stopNonReadyActiveJobsLocked() {
         for (int i = 0; i < mActiveServices.size(); i++) {
             JobServiceContext serviceContext = mActiveServices.get(i);
@@ -1100,7 +1224,7 @@
                 final JobRestriction restriction = mService.checkIfRestricted(running);
                 if (restriction != null) {
                     final int internalReasonCode = restriction.getInternalReason();
-                    serviceContext.cancelExecutingJobLocked(restriction.getReason(),
+                    serviceContext.cancelExecutingJobLocked(restriction.getStopReason(),
                             internalReasonCode,
                             "restricted due to "
                                     + JobParameters.getInternalReasonCodeDescription(
@@ -1216,6 +1340,7 @@
                 mActivePkgStats.add(
                         jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
                         packageStats);
+                mService.resetPendingJobReasonCache(jobStatus);
             }
             if (mService.getPendingJobQueue().remove(jobStatus)) {
                 mService.mJobPackageTracker.noteNonpending(jobStatus);
@@ -1251,13 +1376,37 @@
         }
 
         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
-        if (mActiveServices.size() >= STANDARD_CONCURRENCY_LIMIT || pendingJobQueue.size() == 0) {
+        if (pendingJobQueue.size() == 0) {
             worker.clearPreferredUid();
-            // We're over the limit (because the TOP app scheduled a lot of EJs). Don't start
-            // running anything new until we get back below the limit.
             noteConcurrency();
             return;
         }
+        if (mActiveServices.size() >= STANDARD_CONCURRENCY_LIMIT) {
+            final boolean respectConcurrencyLimit;
+            if (!mMaxWaitTimeBypassEnabled) {
+                respectConcurrencyLimit = true;
+            } else {
+                long minWaitingTimeMs = Long.MAX_VALUE;
+                final long nowElapsed = sElapsedRealtimeClock.millis();
+                for (int i = mActiveServices.size() - 1; i >= 0; --i) {
+                    minWaitingTimeMs = Math.min(minWaitingTimeMs,
+                            mActiveServices.get(i).getRemainingGuaranteedTimeMs(nowElapsed));
+                }
+                final boolean wouldBeWaitingTooLong =
+                        mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0
+                                ? minWaitingTimeMs >= mMaxWaitEjMs
+                                : minWaitingTimeMs >= mMaxWaitRegularMs;
+                respectConcurrencyLimit = !wouldBeWaitingTooLong;
+            }
+            if (respectConcurrencyLimit) {
+                worker.clearPreferredUid();
+                // We're over the limit (because the TOP app scheduled a lot of EJs), but we should
+                // be able to stop the other jobs soon so don't start running anything new until we
+                // get back below the limit.
+                noteConcurrency();
+                return;
+            }
+        }
 
         if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) {
             updateCounterConfigLocked();
@@ -1609,6 +1758,14 @@
         mPkgConcurrencyLimitRegular = Math.max(1, Math.min(STANDARD_CONCURRENCY_LIMIT,
                 properties.getInt(
                         KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR)));
+
+        mMaxWaitTimeBypassEnabled = properties.getBoolean(
+                KEY_ENABLE_MAX_WAIT_TIME_BYPASS, DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS);
+        // EJ max wait must be in the range [0, infinity).
+        mMaxWaitEjMs = Math.max(0, properties.getLong(KEY_MAX_WAIT_EJ_MS, DEFAULT_MAX_WAIT_EJ_MS));
+        // Regular max wait must be in the range [EJ max wait, infinity).
+        mMaxWaitRegularMs = Math.max(mMaxWaitEjMs,
+                properties.getLong(KEY_MAX_WAIT_REGULAR_MS, DEFAULT_MAX_WAIT_REGULAR_MS));
     }
 
     @GuardedBy("mLock")
@@ -1622,6 +1779,9 @@
             pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
             pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println();
             pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println();
+            pw.print(KEY_ENABLE_MAX_WAIT_TIME_BYPASS, mMaxWaitTimeBypassEnabled).println();
+            pw.print(KEY_MAX_WAIT_EJ_MS, mMaxWaitEjMs).println();
+            pw.print(KEY_MAX_WAIT_REGULAR_MS, mMaxWaitRegularMs).println();
             pw.println();
             CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
             pw.println();
@@ -2382,6 +2542,7 @@
         public int workType = WORK_TYPE_NONE;
         public String preemptReason;
         public int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
+        public long timeUntilStoppableMs;
         public String shouldStopJobReason;
         public JobStatus newJob;
         public int newWorkType = WORK_TYPE_NONE;
@@ -2392,12 +2553,24 @@
             workType = WORK_TYPE_NONE;
             preemptReason = null;
             preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
+            timeUntilStoppableMs = 0;
             shouldStopJobReason = null;
             newJob = null;
             newWorkType = WORK_TYPE_NONE;
         }
     }
 
+    @VisibleForTesting
+    static final class AssignmentInfo {
+        public long minPreferredUidOnlyWaitingTimeMs;
+        public int numRunningTopEj;
+
+        void clear() {
+            minPreferredUidOnlyWaitingTimeMs = 0;
+            numRunningTopEj = 0;
+        }
+    }
+
     // TESTING HELPERS
 
     @VisibleForTesting
@@ -2406,6 +2579,15 @@
         final PackageStats packageStats =
                 getPackageStatsForTesting(job.getSourceUserId(), job.getSourcePackageName());
         packageStats.adjustRunningCount(true, job.shouldTreatAsExpeditedJob());
+
+        final JobServiceContext context;
+        if (mIdleContexts.size() > 0) {
+            context = mIdleContexts.removeAt(mIdleContexts.size() - 1);
+        } else {
+            context = createNewJobServiceContext();
+        }
+        context.executeRunnableJob(job, mWorkCountTracker.canJobStart(getJobWorkTypes(job)));
+        mActiveServices.add(context);
     }
 
     @VisibleForTesting
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 6375d0d..ad6eff0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -16,11 +16,14 @@
 
 package com.android.server.job;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -31,6 +34,7 @@
 import android.app.IUidObserver;
 import android.app.compat.CompatChanges;
 import android.app.job.IJobScheduler;
+import android.app.job.IUserVisibleJobObserver;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobProtoEnums;
@@ -38,6 +42,7 @@
 import android.app.job.JobService;
 import android.app.job.JobSnapshot;
 import android.app.job.JobWorkItem;
+import android.app.job.UserVisibleJobSummary;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.compat.annotation.ChangeId;
@@ -47,6 +52,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.PermissionChecker;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
@@ -67,6 +73,7 @@
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -177,7 +184,7 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     private static final long REQUIRE_NETWORK_CONSTRAINT_FOR_NETWORK_JOB_WORK_ITEMS = 241104082L;
 
-    @VisibleForTesting
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public static Clock sSystemClock = Clock.systemUTC();
 
     private abstract static class MySimpleClock extends Clock {
@@ -247,6 +254,8 @@
     static final int MSG_UID_IDLE = 7;
     static final int MSG_CHECK_CHANGED_JOB_LIST = 8;
     static final int MSG_CHECK_MEDIA_EXEMPTION = 9;
+    static final int MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS = 10;
+    static final int MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE = 11;
 
     /** List of controllers that will notify this service of updates to jobs. */
     final List<StateController> mControllers;
@@ -278,6 +287,9 @@
     @GuardedBy("mLock")
     private final SparseArray<String> mCloudMediaProviderPackages = new SparseArray<>();
 
+    private final RemoteCallbackList<IUserVisibleJobObserver> mUserVisibleJobObservers =
+            new RemoteCallbackList<>();
+
     private final CountQuotaTracker mQuotaTracker;
     private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
     private static final String QUOTA_TRACKER_SCHEDULE_LOGGED =
@@ -362,6 +374,9 @@
     @GuardedBy("mLock")
     private final ArraySet<JobStatus> mChangedJobList = new ArraySet<>();
 
+    @GuardedBy("mPendingJobReasonCache") // Use its own lock to avoid blocking JS processing
+    private final SparseArray<SparseIntArray> mPendingJobReasonCache = new SparseArray<>();
+
     /**
      * Named indices into standby bucket arrays, for clarity in referring to
      * specific buckets' bookkeeping.
@@ -432,6 +447,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:
@@ -453,6 +469,10 @@
                                 runtimeUpdated = true;
                             }
                             break;
+                        case Constants.KEY_PERSIST_IN_SPLIT_FILES:
+                            mConstants.updatePersistingConstantsLocked();
+                            mJobs.setUseSplitFiles(mConstants.PERSIST_IN_SPLIT_FILES);
+                            break;
                         default:
                             if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
                                     && !concurrencyUpdated) {
@@ -509,6 +529,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 =
@@ -534,12 +556,15 @@
         private static final String KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS =
                 "runtime_min_high_priority_guarantee_ms";
 
+        private static final String KEY_PERSIST_IN_SPLIT_FILES = "persist_in_split_files";
+
         private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
         private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
         private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
         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;
@@ -559,6 +584,7 @@
         public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
         @VisibleForTesting
         static final long DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS = 5 * MINUTE_IN_MILLIS;
+        static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true;
         private static final boolean DEFAULT_USE_TARE_POLICY = false;
 
         /**
@@ -589,6 +615,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
@@ -669,6 +700,12 @@
                 DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS;
 
         /**
+         * Whether to persist jobs in split files (by UID). If false, all persisted jobs will be
+         * saved in a single file.
+         */
+        public boolean PERSIST_IN_SPLIT_FILES = DEFAULT_PERSIST_IN_SPLIT_FILES;
+
+        /**
          * If true, use TARE policy for job limiting. If false, use quotas.
          */
         public boolean USE_TARE_POLICY = DEFAULT_USE_TARE_POLICY;
@@ -700,6 +737,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() {
@@ -723,6 +763,11 @@
                     DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC);
         }
 
+        private void updatePersistingConstantsLocked() {
+            PERSIST_IN_SPLIT_FILES = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_PERSIST_IN_SPLIT_FILES, DEFAULT_PERSIST_IN_SPLIT_FILES);
+        }
+
         private void updatePrefetchConstantsLocked() {
             PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = DeviceConfig.getLong(
                     DeviceConfig.NAMESPACE_JOB_SCHEDULER,
@@ -797,6 +842,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();
@@ -822,6 +868,8 @@
             pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
                     .println();
 
+            pw.print(KEY_PERSIST_IN_SPLIT_FILES, PERSIST_IN_SPLIT_FILES).println();
+
             pw.print(Settings.Global.ENABLE_TARE, USE_TARE_POLICY).println();
 
             pw.decreaseIndent();
@@ -1277,7 +1325,16 @@
                     jobStatus.getJob().isPrefetch(),
                     jobStatus.getJob().getPriority(),
                     jobStatus.getEffectivePriority(),
-                    jobStatus.getNumFailures());
+                    jobStatus.getNumPreviousAttempts(),
+                    jobStatus.getJob().getMaxExecutionDelayMillis(),
+                    /* isDeadlineConstraintSatisfied */ false,
+                    /* isCharging */ false,
+                    /* batteryNotLow */ false,
+                    /* storageNotLow */false,
+                    /* timingDelayConstraintSatisfied */ false,
+                    /* isDeviceIdle */ false,
+                    /* hasConnectivityConstraintSatisfied */ false,
+                    /* hasContentTriggerConstraintSatisfied */ false);
 
             // 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
@@ -1314,6 +1371,134 @@
         }
     }
 
+    @JobScheduler.PendingJobReason
+    private int getPendingJobReason(int uid, int jobId) {
+        int reason;
+        // Some apps may attempt to query this frequently, so cache the reason under a separate lock
+        // so that the rest of JS processing isn't negatively impacted.
+        synchronized (mPendingJobReasonCache) {
+            SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
+            if (jobIdToReason != null) {
+                reason = jobIdToReason.get(jobId, JobScheduler.PENDING_JOB_REASON_UNDEFINED);
+                if (reason != JobScheduler.PENDING_JOB_REASON_UNDEFINED) {
+                    return reason;
+                }
+            }
+        }
+        synchronized (mLock) {
+            reason = getPendingJobReasonLocked(uid, jobId);
+            if (DEBUG) {
+                Slog.v(TAG, "getPendingJobReason(" + uid + "," + jobId + ")=" + reason);
+            }
+        }
+        synchronized (mPendingJobReasonCache) {
+            SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
+            if (jobIdToReason == null) {
+                jobIdToReason = new SparseIntArray();
+                mPendingJobReasonCache.put(uid, jobIdToReason);
+            }
+            jobIdToReason.put(jobId, reason);
+        }
+        return reason;
+    }
+
+    @JobScheduler.PendingJobReason
+    @GuardedBy("mLock")
+    private int getPendingJobReasonLocked(int uid, int jobId) {
+        // Very similar code to isReadyToBeExecutedLocked.
+
+        JobStatus job = mJobs.getJobByUidAndJobId(uid, jobId);
+        if (job == null) {
+            // Job doesn't exist.
+            return JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID;
+        }
+
+        if (isCurrentlyRunningLocked(job)) {
+            return JobScheduler.PENDING_JOB_REASON_EXECUTING;
+        }
+
+        final boolean jobReady = job.isReady();
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " ready=" + jobReady);
+        }
+
+        if (!jobReady) {
+            return job.getPendingJobReason();
+        }
+
+        final boolean userStarted = areUsersStartedLocked(job);
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " userStarted=" + userStarted);
+        }
+        if (!userStarted) {
+            return JobScheduler.PENDING_JOB_REASON_USER;
+        }
+
+        final boolean backingUp = mBackingUpUids.get(job.getSourceUid());
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " backingUp=" + backingUp);
+        }
+
+        if (backingUp) {
+            // TODO: Should we make a special reason for this?
+            return JobScheduler.PENDING_JOB_REASON_APP;
+        }
+
+        JobRestriction restriction = checkIfRestricted(job);
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " restriction=" + restriction);
+        }
+        if (restriction != null) {
+            return restriction.getPendingReason();
+        }
+
+        // The following can be a little more expensive (especially jobActive, since we need to
+        // go through the array of all potentially active jobs), so we are doing them
+        // later...  but still before checking with the package manager!
+        final boolean jobPending = mPendingJobQueue.contains(job);
+
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " pending=" + jobPending);
+        }
+
+        if (jobPending) {
+            // We haven't started the job for some reason. Presumably, there are too many jobs
+            // running.
+            return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+        }
+
+        final boolean jobActive = mConcurrencyManager.isJobRunningLocked(job);
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " active=" + jobActive);
+        }
+        if (jobActive) {
+            return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+        }
+
+        // Validate that the defined package+service is still present & viable.
+        final boolean componentUsable = isComponentUsable(job);
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " componentUsable=" + componentUsable);
+        }
+        if (!componentUsable) {
+            return JobScheduler.PENDING_JOB_REASON_APP;
+        }
+
+        return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+    }
+
     public JobInfo getPendingJob(int uid, int jobId) {
         synchronized (mLock) {
             ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
@@ -1327,6 +1512,14 @@
         }
     }
 
+    private void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) {
+        synchronized (mLock) {
+            mConcurrencyManager.stopUserVisibleJobsLocked(userId, packageName,
+                    JobParameters.STOP_REASON_USER,
+                    JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
+        }
+    }
+
     private final Consumer<JobStatus> mCancelJobDueToUserRemovalConsumer = (toRemove) -> {
         // There's no guarantee that the process has been stopped by the time we get
         // here, but since this is a user-initiated action, it should be fine to just
@@ -1346,6 +1539,9 @@
         synchronized (mLock) {
             mJobs.removeJobsOfUnlistedUsers(umi.getUserIds());
         }
+        synchronized (mPendingJobReasonCache) {
+            mPendingJobReasonCache.clear();
+        }
     }
 
     private void cancelJobsForPackageAndUidLocked(String pkgName, int uid,
@@ -1476,7 +1672,16 @@
                     cancelled.getJob().isPrefetch(),
                     cancelled.getJob().getPriority(),
                     cancelled.getEffectivePriority(),
-                    cancelled.getNumFailures());
+                    cancelled.getNumPreviousAttempts(),
+                    cancelled.getJob().getMaxExecutionDelayMillis(),
+                    cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE),
+                    cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_CHARGING),
+                    cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW),
+                    cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW),
+                    cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
+                    cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
+                    cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
+                    cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
         }
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
@@ -1820,12 +2025,15 @@
             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);
+        // Clear potentially cached INVALID_JOB_ID reason.
+        resetPendingJobReasonCache(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);
             }
@@ -1842,6 +2050,13 @@
         // Deal with any remaining work items in the old job.
         jobStatus.stopTrackingJobLocked(incomingJob);
 
+        synchronized (mPendingJobReasonCache) {
+            SparseIntArray reasonCache = mPendingJobReasonCache.get(jobStatus.getUid());
+            if (reasonCache != null) {
+                reasonCache.delete(jobStatus.getJobId());
+            }
+        }
+
         // Remove from store as well as controllers.
         final boolean removed = mJobs.remove(jobStatus, removeFromPersisted);
         if (!removed) {
@@ -1858,12 +2073,22 @@
         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;
     }
 
+    /** Remove the pending job reason for this job from the cache. */
+    void resetPendingJobReasonCache(@NonNull JobStatus jobStatus) {
+        synchronized (mPendingJobReasonCache) {
+            final SparseIntArray reasons = mPendingJobReasonCache.get(jobStatus.getUid());
+            if (reasons != null) {
+                reasons.delete(jobStatus.getJobId());
+            }
+        }
+    }
+
     /** Return {@code true} if the specified job is currently executing. */
     @GuardedBy("mLock")
     public boolean isCurrentlyRunningLocked(JobStatus job) {
@@ -1903,7 +2128,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 +2136,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()) {
@@ -1941,9 +2178,10 @@
         }
         delayMillis =
                 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
+        // TODO(255767350): demote all jobs to regular for user stops so they don't keep privileges
         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 +2272,7 @@
                     + newLatestRuntimeElapsed);
             return new JobStatus(periodicToReschedule,
                     elapsedNow + period - flex, elapsedNow + period,
-                    0 /* backoffAttempt */,
+                    0 /* numFailures */, 0 /* numSystemStops */,
                     sSystemClock.millis() /* lastSuccessfulRunTime */,
                     periodicToReschedule.getLastFailedRunTime());
         }
@@ -2049,7 +2287,7 @@
         }
         return new JobStatus(periodicToReschedule,
                 newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
-                0 /* backoffAttempt */,
+                0 /* numFailures */, 0 /* numSystemStops */,
                 sSystemClock.millis() /* lastSuccessfulRunTime */,
                 periodicToReschedule.getLastFailedRunTime());
     }
@@ -2093,7 +2331,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)) {
@@ -2145,11 +2383,20 @@
     public void onControllerStateChanged(@Nullable ArraySet<JobStatus> changedJobs) {
         if (changedJobs == null) {
             mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+            synchronized (mPendingJobReasonCache) {
+                mPendingJobReasonCache.clear();
+            }
         } else if (changedJobs.size() > 0) {
             synchronized (mLock) {
                 mChangedJobList.addAll(changedJobs);
             }
             mHandler.obtainMessage(MSG_CHECK_CHANGED_JOB_LIST).sendToTarget();
+            synchronized (mPendingJobReasonCache) {
+                for (int i = changedJobs.size() - 1; i >= 0; --i) {
+                    final JobStatus job = changedJobs.valueAt(i);
+                    resetPendingJobReasonCache(job);
+                }
+            }
         }
     }
 
@@ -2282,6 +2529,52 @@
                         args.recycle();
                         break;
                     }
+
+                    case MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS: {
+                        final IUserVisibleJobObserver observer =
+                                (IUserVisibleJobObserver) message.obj;
+                        synchronized (mLock) {
+                            for (int i = mConcurrencyManager.mActiveServices.size() - 1; i >= 0;
+                                    --i) {
+                                JobServiceContext context =
+                                        mConcurrencyManager.mActiveServices.get(i);
+                                final JobStatus jobStatus = context.getRunningJobLocked();
+                                if (jobStatus != null && jobStatus.isUserVisibleJob()) {
+                                    try {
+                                        observer.onUserVisibleJobStateChanged(
+                                                jobStatus.getUserVisibleJobSummary(),
+                                                /* isRunning */ true);
+                                    } catch (RemoteException e) {
+                                        // Will be unregistered automatically by
+                                        // RemoteCallbackList's dead-object tracking,
+                                        // so don't need to remove it here.
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                        break;
+                    }
+
+                    case MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE: {
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        final JobServiceContext context = (JobServiceContext) args.arg1;
+                        final JobStatus jobStatus = (JobStatus) args.arg2;
+                        final UserVisibleJobSummary summary = jobStatus.getUserVisibleJobSummary();
+                        final boolean isRunning = args.argi1 == 1;
+                        for (int i = mUserVisibleJobObservers.beginBroadcast() - 1; i >= 0; --i) {
+                            try {
+                                mUserVisibleJobObservers.getBroadcastItem(i)
+                                        .onUserVisibleJobStateChanged(summary, isRunning);
+                            } catch (RemoteException e) {
+                                // Will be unregistered automatically by RemoteCallbackList's
+                                // dead-object tracking, so nothing we need to do here.
+                            }
+                        }
+                        mUserVisibleJobObservers.finishBroadcast();
+                        args.recycle();
+                        break;
+                    }
                 }
                 maybeRunPendingJobsLocked();
             }
@@ -2427,7 +2720,7 @@
                     shouldForceBatchJob =
                             mPrefetchController.getNextEstimatedLaunchTimeLocked(job)
                                     > relativelySoonCutoffTime;
-                } else if (job.getNumFailures() > 0) {
+                } else if (job.getNumPreviousAttempts() > 0) {
                     shouldForceBatchJob = false;
                 } else {
                     final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -2503,6 +2796,23 @@
                 if (DEBUG) {
                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
                 }
+                final int numRunnableJobs = runnableJobs.size();
+                if (numRunnableJobs > 0) {
+                    synchronized (mPendingJobReasonCache) {
+                        for (int i = 0; i < numRunnableJobs; ++i) {
+                            final JobStatus job = runnableJobs.get(i);
+                            SparseIntArray reasons = mPendingJobReasonCache.get(job.getUid());
+                            if (reasons == null) {
+                                reasons = new SparseIntArray();
+                                mPendingJobReasonCache.put(job.getUid(), reasons);
+                            }
+                            // We're force batching these jobs, so consider it an optimization
+                            // policy reason.
+                            reasons.put(job.getJobId(),
+                                    JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
+                        }
+                    }
+                }
             }
 
             // Be ready for next time
@@ -2778,6 +3088,16 @@
         return adjustJobBias(bias, job);
     }
 
+    void informObserversOfUserVisibleJobChange(JobServiceContext context, JobStatus jobStatus,
+            boolean isRunning) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = context;
+        args.arg2 = jobStatus;
+        args.argi1 = isRunning ? 1 : 0;
+        mHandler.obtainMessage(MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE, args)
+                .sendToTarget();
+    }
+
     private final class BatteryStateTracker extends BroadcastReceiver {
         /**
          * Track whether we're "charging", where charging means that we're ready to commit to
@@ -3305,6 +3625,18 @@
         }
 
         @Override
+        public int getPendingJobReason(int jobId) throws RemoteException {
+            final int uid = Binder.getCallingUid();
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return JobSchedulerService.this.getPendingJobReason(uid, jobId);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
         public JobInfo getPendingJob(int jobId) throws RemoteException {
             final int uid = Binder.getCallingUid();
 
@@ -3345,6 +3677,39 @@
             }
         }
 
+        @Override
+        public boolean canRunLongJobs(@NonNull String packageName) {
+            final int callingUid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(callingUid);
+            final int packageUid = mLocalPM.getPackageUid(packageName, 0, userId);
+            if (callingUid != packageUid) {
+                throw new SecurityException("Uid " + callingUid
+                        + " cannot query canRunLongJobs for package " + packageName);
+            }
+
+            return checkRunLongJobsPermission(packageUid, packageName);
+        }
+
+        @Override
+        public boolean hasRunLongJobsPermission(@NonNull String packageName,
+                @UserIdInt int userId) {
+            final int uid = mLocalPM.getPackageUid(packageName, 0, userId);
+            final int callingUid = Binder.getCallingUid();
+            if (callingUid != uid && !UserHandle.isCore(callingUid)) {
+                throw new SecurityException("Uid " + callingUid
+                        + " cannot query canRunLongJobs for package " + packageName);
+            }
+
+            return checkRunLongJobsPermission(uid, packageName);
+        }
+
+        private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
+            // Returns true if both the appop and permission are granted.
+            return PermissionChecker.checkPermissionForPreflight(getContext(),
+                    android.Manifest.permission.RUN_LONG_JOBS, PermissionChecker.PID_UNKNOWN,
+                    packageUid, packageName) == PermissionChecker.PERMISSION_GRANTED;
+        }
+
         /**
          * "dumpsys" infrastructure
          */
@@ -3455,6 +3820,35 @@
                 return new ParceledListSlice<>(snapshots);
             }
         }
+
+        @Override
+        @EnforcePermission(allOf = {MANAGE_ACTIVITY_TASKS, INTERACT_ACROSS_USERS_FULL})
+        public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
+            if (observer == null) {
+                throw new NullPointerException("observer");
+            }
+            mUserVisibleJobObservers.register(observer);
+            mHandler.obtainMessage(MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS, observer)
+                    .sendToTarget();
+        }
+
+        @Override
+        @EnforcePermission(allOf = {MANAGE_ACTIVITY_TASKS, INTERACT_ACROSS_USERS_FULL})
+        public void unregisterUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
+            if (observer == null) {
+                throw new NullPointerException("observer");
+            }
+            mUserVisibleJobObservers.unregister(observer);
+        }
+
+        @Override
+        @EnforcePermission(allOf = {"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
+        public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
+            if (packageName == null) {
+                throw new NullPointerException("packageName");
+            }
+            JobSchedulerService.this.stopUserVisibleJobsInternal(packageName, userId);
+        }
     }
 
     // Shell command infrastructure: run the given job immediately
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..fead68e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -17,12 +17,16 @@
 package com.android.server.job;
 
 import static android.app.job.JobInfo.getPriorityString;
+import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_DETACH;
+import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_REMOVE;
 
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
+import android.annotation.BytesLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Notification;
 import android.app.job.IJobCallback;
 import android.app.job.IJobService;
 import android.app.job.JobInfo;
@@ -57,6 +61,7 @@
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.job.controllers.JobStatus;
+import com.android.server.notification.NotificationManagerInternal;
 import com.android.server.tare.EconomicPolicy;
 import com.android.server.tare.EconomyManagerInternal;
 import com.android.server.tare.JobSchedulerEconomicPolicy;
@@ -116,6 +121,7 @@
     private final EconomyManagerInternal mEconomyManagerInternal;
     private final JobPackageTracker mJobPackageTracker;
     private final PowerManager mPowerManager;
+    private final NotificationManagerInternal mNotificationManagerInternal;
     private PowerManager.WakeLock mWakeLock;
 
     // Execution state.
@@ -168,6 +174,11 @@
     /** The absolute maximum amount of time the job can run */
     private long mMaxExecutionTimeMillis;
 
+    private int mNotificationId;
+    private Notification mNotification;
+    private int mNotificationPid;
+    private int mNotificationJobStopPolicy;
+
     /**
      * The stop reason for a pending cancel. If there's not pending cancel, then the value should be
      * {@link JobParameters#STOP_REASON_UNDEFINED}.
@@ -187,6 +198,18 @@
         public long mStoppedTime;
 
         @Override
+        public void acknowledgeGetTransferredDownloadBytesMessage(int jobId, int workId,
+                @BytesLong long transferredBytes) {
+            doAcknowledgeGetTransferredDownloadBytesMessage(this, jobId, workId, transferredBytes);
+        }
+
+        @Override
+        public void acknowledgeGetTransferredUploadBytesMessage(int jobId, int workId,
+                @BytesLong long transferredBytes) {
+            doAcknowledgeGetTransferredUploadBytesMessage(this, jobId, workId, transferredBytes);
+        }
+
+        @Override
         public void acknowledgeStartMessage(int jobId, boolean ongoing) {
             doAcknowledgeStartMessage(this, jobId, ongoing);
         }
@@ -210,6 +233,24 @@
         public void jobFinished(int jobId, boolean reschedule) {
             doJobFinished(this, jobId, reschedule);
         }
+
+        @Override
+        public void updateEstimatedNetworkBytes(int jobId, JobWorkItem item,
+                long downloadBytes, long uploadBytes) {
+            doUpdateEstimatedNetworkBytes(this, jobId, item, downloadBytes, uploadBytes);
+        }
+
+        @Override
+        public void updateTransferredNetworkBytes(int jobId, JobWorkItem item,
+                long downloadBytes, long uploadBytes) {
+            doUpdateTransferredNetworkBytes(this, jobId, item, downloadBytes, uploadBytes);
+        }
+
+        @Override
+        public void setNotification(int jobId, int notificationId,
+                Notification notification, int jobEndNotificationPolicy) {
+            doSetNotification(this, jobId, notificationId, notification, jobEndNotificationPolicy);
+        }
     }
 
     JobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager,
@@ -219,6 +260,7 @@
         mService = service;
         mBatteryStats = batteryStats;
         mEconomyManagerInternal = LocalServices.getService(EconomyManagerInternal.class);
+        mNotificationManagerInternal = LocalServices.getService(NotificationManagerInternal.class);
         mJobPackageTracker = tracker;
         mCallbackHandler = new JobServiceHandler(looper);
         mJobConcurrencyManager = concurrencyManager;
@@ -363,7 +405,16 @@
                     job.getJob().isPrefetch(),
                     job.getJob().getPriority(),
                     job.getEffectivePriority(),
-                    job.getNumFailures());
+                    job.getNumPreviousAttempts(),
+                    job.getJob().getMaxExecutionDelayMillis(),
+                    isDeadlineExpired,
+                    job.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_CHARGING),
+                    job.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW),
+                    job.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW),
+                    job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
+                    job.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
+                    job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
+                    job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
             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.
@@ -454,6 +505,10 @@
         return mTimeoutElapsed;
     }
 
+    long getRemainingGuaranteedTimeMs(long nowElapsed) {
+        return Math.max(0, mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis - nowElapsed);
+    }
+
     boolean isWithinExecutionGuaranteeTime() {
         return sElapsedRealtimeClock.millis()
                 < mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis;
@@ -493,6 +548,16 @@
         }
     }
 
+    private void doAcknowledgeGetTransferredDownloadBytesMessage(JobCallback jobCallback, int jobId,
+            int workId, @BytesLong long transferredBytes) {
+        // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+    }
+
+    private void doAcknowledgeGetTransferredUploadBytesMessage(JobCallback jobCallback, int jobId,
+            int workId, @BytesLong long transferredBytes) {
+        // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+    }
+
     void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) {
         doCallback(cb, reschedule, null);
     }
@@ -545,6 +610,59 @@
         }
     }
 
+    private void doSetNotification(JobCallback cb, int jodId, int notificationId,
+            Notification notification, int jobEndNotificationPolicy) {
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                if (!verifyCallerLocked(cb)) {
+                    return;
+                }
+                if (callingUid != mRunningJob.getUid()) {
+                    Slog.wtfStack(TAG, "Calling UID isn't the same as running job's UID...");
+                    throw new SecurityException("Can't post notification on behalf of another app");
+                }
+                if (notification == null) {
+                    throw new NullPointerException("notification");
+                }
+                if (notification.getSmallIcon() == null) {
+                    throw new IllegalArgumentException("small icon required");
+                }
+                final String callingPkgName = mRunningJob.getServiceComponent().getPackageName();
+                if (null == mNotificationManagerInternal.getNotificationChannel(
+                        callingPkgName, callingUid, notification.getChannelId())) {
+                    throw new IllegalArgumentException("invalid notification channel");
+                }
+                if (jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_DETACH
+                        && jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_REMOVE) {
+                    throw new IllegalArgumentException("invalid job end notification policy");
+                }
+                // TODO(260848384): ensure apps can't cancel the notification for user-initiated job
+                mNotificationManagerInternal.enqueueNotification(
+                        callingPkgName, callingPkgName, callingUid, callingPid, /* tag */ null,
+                        notificationId, notification, UserHandle.getUserId(callingUid));
+                mNotificationId = notificationId;
+                mNotification = notification;
+                mNotificationPid = callingPid;
+                mNotificationJobStopPolicy = jobEndNotificationPolicy;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void doUpdateTransferredNetworkBytes(JobCallback jobCallback, int jobId,
+            @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+        // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+    }
+
+    private void doUpdateEstimatedNetworkBytes(JobCallback jobCallback, int jobId,
+            @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+        // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+    }
+
     /**
      * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
      * we intend to send to the client - we stop sending work when the service is unbound so until
@@ -826,6 +944,9 @@
                     return;
                 }
                 scheduleOpTimeOutLocked();
+                if (mRunningJob.isUserVisibleJob()) {
+                    mService.informObserversOfUserVisibleJobChange(this, mRunningJob, true);
+                }
                 break;
             default:
                 Slog.e(TAG, "Handling started job but job wasn't starting! Was "
@@ -1032,7 +1153,16 @@
                 completedJob.getJob().isPrefetch(),
                 completedJob.getJob().getPriority(),
                 completedJob.getEffectivePriority(),
-                completedJob.getNumFailures());
+                completedJob.getNumPreviousAttempts(),
+                completedJob.getJob().getMaxExecutionDelayMillis(),
+                mParams.isOverrideDeadlineExpired(),
+                completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_CHARGING),
+                completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW),
+                completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW),
+                completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
+                completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
+                completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
+                completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
         if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
             Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
                     completedJob.getTag(), getId());
@@ -1049,6 +1179,13 @@
                     JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT,
                     String.valueOf(mRunningJob.getJobId()));
         }
+        if (mNotification != null
+                && mNotificationJobStopPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE) {
+            final String callingPkgName = completedJob.getServiceComponent().getPackageName();
+            mNotificationManagerInternal.cancelNotification(
+                    callingPkgName, callingPkgName, completedJob.getUid(), mNotificationPid,
+                    /* tag */ null, mNotificationId, UserHandle.getUserId(completedJob.getUid()));
+        }
         if (mWakeLock != null) {
             mWakeLock.release();
         }
@@ -1066,7 +1203,11 @@
         mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
         mPendingInternalStopReason = 0;
         mPendingDebugStopReason = null;
+        mNotification = null;
         removeOpTimeOutLocked();
+        if (completedJob.isUserVisibleJob()) {
+            mService.informObserversOfUserVisibleJobChange(this, completedJob, false);
+        }
         mCompletedListener.onJobCompletedLocked(completedJob, internalStopReason, reschedule);
         mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
     }
@@ -1090,6 +1231,7 @@
     private void scheduleOpTimeOutLocked() {
         removeOpTimeOutLocked();
 
+        // TODO(260848384): enforce setNotification timeout for user-initiated jobs
         final long timeoutMillis;
         switch (mVerb) {
             case VERB_EXECUTING:
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..a1153e3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -40,6 +40,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.SystemConfigFileCommitEventLogger;
 import android.util.Xml;
 
@@ -47,6 +48,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.BitUtils;
+import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.IoThread;
 import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
@@ -89,6 +91,11 @@
 
     /** Threshold to adjust how often we want to write to the db. */
     private static final long JOB_PERSIST_DELAY = 2000L;
+    @VisibleForTesting
+    static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
+    private static final int ALL_UIDS = -1;
+    @VisibleForTesting
+    static final int INVALID_UID = -2;
 
     final Object mLock;
     final Object mWriteScheduleLock;    // used solely for invariants around write scheduling
@@ -105,13 +112,20 @@
     @GuardedBy("mWriteScheduleLock")
     private boolean mWriteInProgress;
 
+    @GuardedBy("mWriteScheduleLock")
+    private boolean mSplitFileMigrationNeeded;
+
     private static final Object sSingletonLock = new Object();
     private final SystemConfigFileCommitEventLogger mEventLogger;
     private final AtomicFile mJobsFile;
+    private final File mJobFileDirectory;
+    private final SparseBooleanArray mPendingJobWriteUids = new SparseBooleanArray();
     /** Handler backed by IoThread for writing to disk. */
     private final Handler mIoHandler = IoThread.getHandler();
     private static JobStore sSingleton;
 
+    private boolean mUseSplitFiles = JobSchedulerService.Constants.DEFAULT_PERSIST_IN_SPLIT_FILES;
+
     private JobStorePersistStats mPersistInfo = new JobStorePersistStats();
 
     /** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
@@ -144,10 +158,10 @@
         mContext = context;
 
         File systemDir = new File(dataDir, "system");
-        File jobDir = new File(systemDir, "job");
-        jobDir.mkdirs();
+        mJobFileDirectory = new File(systemDir, "job");
+        mJobFileDirectory.mkdirs();
         mEventLogger = new SystemConfigFileCommitEventLogger("jobs");
-        mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"), mEventLogger);
+        mJobsFile = createJobFile(new File(mJobFileDirectory, "jobs.xml"));
 
         mJobSet = new JobSet();
 
@@ -162,12 +176,21 @@
         // an incorrect historical timestamp.  That's fine; at worst we'll reboot with
         // a *correct* timestamp, see a bunch of overdue jobs, and run them; then
         // settle into normal operation.
-        mXmlTimestamp = mJobsFile.getLastModifiedTime();
+        mXmlTimestamp = mJobsFile.exists()
+                ? mJobsFile.getLastModifiedTime() : mJobFileDirectory.lastModified();
         mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
 
         readJobMapFromDisk(mJobSet, mRtcGood);
     }
 
+    private AtomicFile createJobFile(String baseName) {
+        return createJobFile(new File(mJobFileDirectory, baseName + ".xml"));
+    }
+
+    private AtomicFile createJobFile(File file) {
+        return new AtomicFile(file, mEventLogger);
+    }
+
     public boolean jobTimesInflatedValid() {
         return mRtcGood;
     }
@@ -194,7 +217,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,21 +226,20 @@
     }
 
     /**
-     * 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()) {
+            mPendingJobWriteUids.put(jobStatus.getUid(), true);
             maybeWriteStatusToDiskAsync();
         }
         if (DEBUG) {
             Slog.d(TAG, "Added job status to store: " + jobStatus);
         }
-        return replaced;
     }
 
     /**
@@ -226,6 +248,9 @@
     @VisibleForTesting
     public void addForTesting(JobStatus jobStatus) {
         mJobSet.add(jobStatus);
+        if (jobStatus.isPersisted()) {
+            mPendingJobWriteUids.put(jobStatus.getUid(), true);
+        }
     }
 
     boolean containsJob(JobStatus jobStatus) {
@@ -259,12 +284,24 @@
             return false;
         }
         if (removeFromPersisted && jobStatus.isPersisted()) {
+            mPendingJobWriteUids.put(jobStatus.getUid(), true);
             maybeWriteStatusToDiskAsync();
         }
         return removed;
     }
 
     /**
+     * Like {@link #remove(JobStatus, boolean)}, but doesn't schedule a disk write.
+     */
+    @VisibleForTesting
+    public void removeForTesting(JobStatus jobStatus) {
+        mJobSet.remove(jobStatus);
+        if (jobStatus.isPersisted()) {
+            mPendingJobWriteUids.put(jobStatus.getUid(), true);
+        }
+    }
+
+    /**
      * Remove the jobs of users not specified in the keepUserIds.
      * @param keepUserIds Array of User IDs whose jobs should be kept and not removed.
      */
@@ -275,6 +312,7 @@
     @VisibleForTesting
     public void clear() {
         mJobSet.clear();
+        mPendingJobWriteUids.put(ALL_UIDS, true);
         maybeWriteStatusToDiskAsync();
     }
 
@@ -284,6 +322,36 @@
     @VisibleForTesting
     public void clearForTesting() {
         mJobSet.clear();
+        mPendingJobWriteUids.put(ALL_UIDS, true);
+    }
+
+    void setUseSplitFiles(boolean useSplitFiles) {
+        synchronized (mLock) {
+            if (mUseSplitFiles != useSplitFiles) {
+                mUseSplitFiles = useSplitFiles;
+                migrateJobFilesAsync();
+            }
+        }
+    }
+
+    /**
+     * The same as above but does not schedule writing. This makes perf benchmarks more stable.
+     */
+    @VisibleForTesting
+    public void setUseSplitFilesForTesting(boolean useSplitFiles) {
+        final boolean changed;
+        synchronized (mLock) {
+            changed = mUseSplitFiles != useSplitFiles;
+            if (changed) {
+                mUseSplitFiles = useSplitFiles;
+                mPendingJobWriteUids.put(ALL_UIDS, true);
+            }
+        }
+        if (changed) {
+            synchronized (mWriteScheduleLock) {
+                mSplitFileMigrationNeeded = true;
+            }
+        }
     }
 
     /**
@@ -347,6 +415,14 @@
 
     /** Version of the db schema. */
     private static final int JOBS_FILE_VERSION = 1;
+    /**
+     * For legacy reasons, this tag is used to encapsulate the entire job list.
+     */
+    private static final String XML_TAG_JOB_INFO = "job-info";
+    /**
+     * For legacy reasons, this tag represents a single {@link JobStatus} object.
+     */
+    private static final String XML_TAG_JOB = "job";
     /** Tag corresponds to constraints this job needs. */
     private static final String XML_TAG_PARAMS_CONSTRAINTS = "constraints";
     /** Tag corresponds to execution parameters. */
@@ -354,6 +430,16 @@
     private static final String XML_TAG_ONEOFF = "one-off";
     private static final String XML_TAG_EXTRAS = "extras";
 
+    private void migrateJobFilesAsync() {
+        synchronized (mLock) {
+            mPendingJobWriteUids.put(ALL_UIDS, true);
+        }
+        synchronized (mWriteScheduleLock) {
+            mSplitFileMigrationNeeded = true;
+            maybeWriteStatusToDiskAsync();
+        }
+    }
+
     /**
      * Every time the state changes we write all the jobs in one swath, instead of trying to
      * track incremental changes.
@@ -446,15 +532,98 @@
         return values;
     }
 
+    @VisibleForTesting
+    static int extractUidFromJobFileName(@NonNull File file) {
+        final String fileName = file.getName();
+        if (fileName.startsWith(JOB_FILE_SPLIT_PREFIX)) {
+            try {
+                final int subEnd = fileName.length() - 4; // -4 for ".xml"
+                final int uid = Integer.parseInt(
+                        fileName.substring(JOB_FILE_SPLIT_PREFIX.length(), subEnd));
+                if (uid < 0) {
+                    return INVALID_UID;
+                }
+                return uid;
+            } catch (Exception e) {
+                Slog.e(TAG, "Unexpected file name format", e);
+            }
+        }
+        return INVALID_UID;
+    }
+
     /**
      * Runnable that writes {@link #mJobSet} out to xml.
      * NOTE: This Runnable locks on mLock
      */
     private final Runnable mWriteRunnable = new Runnable() {
+        private final SparseArray<AtomicFile> mJobFiles = new SparseArray<>();
+        private final CopyConsumer mPersistedJobCopier = new CopyConsumer();
+
+        class CopyConsumer implements Consumer<JobStatus> {
+            private final SparseArray<List<JobStatus>> mJobStoreCopy = new SparseArray<>();
+            private boolean mCopyAllJobs;
+
+            private void prepare() {
+                mCopyAllJobs = !mUseSplitFiles || mPendingJobWriteUids.get(ALL_UIDS);
+                if (mUseSplitFiles) {
+                    // Put the set of changed UIDs in the copy list so that we update each file,
+                    // especially if we've dropped all jobs for that UID.
+                    if (mPendingJobWriteUids.get(ALL_UIDS)) {
+                        // ALL_UIDS is only used when we switch file splitting policy or for tests,
+                        // so going through the file list here shouldn't be
+                        // a large performance hit on user devices.
+
+                        final File[] files;
+                        try {
+                            files = mJobFileDirectory.listFiles();
+                        } catch (SecurityException e) {
+                            Slog.wtf(TAG, "Not allowed to read job file directory", e);
+                            return;
+                        }
+                        if (files == null) {
+                            Slog.wtfStack(TAG, "Couldn't get job file list");
+                        } else {
+                            for (File file : files) {
+                                final int uid = extractUidFromJobFileName(file);
+                                if (uid != INVALID_UID) {
+                                    mJobStoreCopy.put(uid, new ArrayList<>());
+                                }
+                            }
+                        }
+                    } else {
+                        for (int i = 0; i < mPendingJobWriteUids.size(); ++i) {
+                            mJobStoreCopy.put(mPendingJobWriteUids.keyAt(i), new ArrayList<>());
+                        }
+                    }
+                } else {
+                    // Single file mode.
+                    // Put the catchall UID in the copy list so that we update the single file,
+                    // especially if we've dropped all persisted jobs.
+                    mJobStoreCopy.put(ALL_UIDS, new ArrayList<>());
+                }
+            }
+
+            @Override
+            public void accept(JobStatus jobStatus) {
+                final int uid = mUseSplitFiles ? jobStatus.getUid() : ALL_UIDS;
+                if (jobStatus.isPersisted() && (mCopyAllJobs || mPendingJobWriteUids.get(uid))) {
+                    List<JobStatus> uidJobList = mJobStoreCopy.get(uid);
+                    if (uidJobList == null) {
+                        uidJobList = new ArrayList<>();
+                        mJobStoreCopy.put(uid, uidJobList);
+                    }
+                    uidJobList.add(new JobStatus(jobStatus));
+                }
+            }
+
+            private void reset() {
+                mJobStoreCopy.clear();
+            }
+        }
+
         @Override
         public void run() {
             final long startElapsed = sElapsedRealtimeClock.millis();
-            final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
             // Intentionally allow new scheduling of a write operation *before* we clone
             // the job set.  If we reset it to false after cloning, there's a window in
             // which no new write will be scheduled but mLock is not held, i.e. a new
@@ -471,48 +640,90 @@
                 }
                 mWriteInProgress = true;
             }
+            final boolean useSplitFiles;
             synchronized (mLock) {
                 // Clone the jobs so we can release the lock before writing.
-                mJobSet.forEachJob(null, (job) -> {
-                    if (job.isPersisted()) {
-                        storeCopy.add(new JobStatus(job));
-                    }
-                });
+                useSplitFiles = mUseSplitFiles;
+                mPersistedJobCopier.prepare();
+                mJobSet.forEachJob(null, mPersistedJobCopier);
+                mPendingJobWriteUids.clear();
             }
-            writeJobsMapImpl(storeCopy);
+            mPersistInfo.countAllJobsSaved = 0;
+            mPersistInfo.countSystemServerJobsSaved = 0;
+            mPersistInfo.countSystemSyncManagerJobsSaved = 0;
+            for (int i = mPersistedJobCopier.mJobStoreCopy.size() - 1; i >= 0; --i) {
+                AtomicFile file;
+                if (useSplitFiles) {
+                    final int uid = mPersistedJobCopier.mJobStoreCopy.keyAt(i);
+                    file = mJobFiles.get(uid);
+                    if (file == null) {
+                        file = createJobFile(JOB_FILE_SPLIT_PREFIX + uid);
+                        mJobFiles.put(uid, file);
+                    }
+                } else {
+                    file = mJobsFile;
+                }
+                if (DEBUG) {
+                    Slog.d(TAG, "Writing for " + mPersistedJobCopier.mJobStoreCopy.keyAt(i)
+                            + " to " + file.getBaseFile().getName() + ": "
+                            + mPersistedJobCopier.mJobStoreCopy.valueAt(i).size() + " jobs");
+                }
+                writeJobsMapImpl(file, mPersistedJobCopier.mJobStoreCopy.valueAt(i));
+            }
             if (DEBUG) {
                 Slog.v(TAG, "Finished writing, took " + (sElapsedRealtimeClock.millis()
                         - startElapsed) + "ms");
             }
+            mPersistedJobCopier.reset();
+            if (!useSplitFiles) {
+                mJobFiles.clear();
+            }
+            // Update the last modified time of the directory to aid in RTC time verification
+            // (see the JobStore constructor).
+            mJobFileDirectory.setLastModified(sSystemClock.millis());
             synchronized (mWriteScheduleLock) {
+                if (mSplitFileMigrationNeeded) {
+                    final File[] files = mJobFileDirectory.listFiles();
+                    for (File file : files) {
+                        if (useSplitFiles) {
+                            if (!file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
+                                // Delete the now unused file so there's no confusion in the future.
+                                file.delete();
+                            }
+                        } else if (file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
+                            // Delete the now unused file so there's no confusion in the future.
+                            file.delete();
+                        }
+                    }
+                }
                 mWriteInProgress = false;
                 mWriteScheduleLock.notifyAll();
             }
         }
 
-        private void writeJobsMapImpl(List<JobStatus> jobList) {
+        private void writeJobsMapImpl(@NonNull AtomicFile file, @NonNull List<JobStatus> jobList) {
             int numJobs = 0;
             int numSystemJobs = 0;
             int numSyncJobs = 0;
             mEventLogger.setStartTime(SystemClock.uptimeMillis());
-            try (FileOutputStream fos = mJobsFile.startWrite()) {
+            try (FileOutputStream fos = file.startWrite()) {
                 TypedXmlSerializer out = Xml.resolveSerializer(fos);
                 out.startDocument(null, true);
                 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
 
-                out.startTag(null, "job-info");
+                out.startTag(null, XML_TAG_JOB_INFO);
                 out.attribute(null, "version", Integer.toString(JOBS_FILE_VERSION));
                 for (int i=0; i<jobList.size(); i++) {
                     JobStatus jobStatus = jobList.get(i);
                     if (DEBUG) {
                         Slog.d(TAG, "Saving job " + jobStatus.getJobId());
                     }
-                    out.startTag(null, "job");
+                    out.startTag(null, XML_TAG_JOB);
                     addAttributesToJobTag(out, jobStatus);
                     writeConstraintsToXml(out, jobStatus);
                     writeExecutionCriteriaToXml(out, jobStatus);
                     writeBundleToXml(jobStatus.getJob().getExtras(), out);
-                    out.endTag(null, "job");
+                    out.endTag(null, XML_TAG_JOB);
 
                     numJobs++;
                     if (jobStatus.getUid() == Process.SYSTEM_UID) {
@@ -522,10 +733,10 @@
                         }
                     }
                 }
-                out.endTag(null, "job-info");
+                out.endTag(null, XML_TAG_JOB_INFO);
                 out.endDocument();
 
-                mJobsFile.finishWrite(fos);
+                file.finishWrite(fos);
             } catch (IOException e) {
                 if (DEBUG) {
                     Slog.v(TAG, "Error writing out job data.", e);
@@ -535,9 +746,9 @@
                     Slog.d(TAG, "Error persisting bundle.", e);
                 }
             } finally {
-                mPersistInfo.countAllJobsSaved = numJobs;
-                mPersistInfo.countSystemServerJobsSaved = numSystemJobs;
-                mPersistInfo.countSystemSyncManagerJobsSaved = numSyncJobs;
+                mPersistInfo.countAllJobsSaved += numJobs;
+                mPersistInfo.countSystemServerJobsSaved += numSystemJobs;
+                mPersistInfo.countSystemSyncManagerJobsSaved += numSyncJobs;
             }
         }
 
@@ -602,9 +813,11 @@
          *       because currently store is not including everything (like, UIDs, bandwidth,
          *       signal strength etc. are lost).
          */
-        private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
+        private void writeConstraintsToXml(TypedXmlSerializer out, JobStatus jobStatus)
+                throws IOException {
             out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
             if (jobStatus.hasConnectivityConstraint()) {
+                final JobInfo job = jobStatus.getJob();
                 final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
                 out.attribute(null, "net-capabilities-csv", intArrayToString(
                         network.getCapabilities()));
@@ -612,6 +825,18 @@
                         network.getForbiddenCapabilities()));
                 out.attribute(null, "net-transport-types-csv", intArrayToString(
                         network.getTransportTypes()));
+                if (job.getEstimatedNetworkDownloadBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                    out.attributeLong(null, "estimated-download-bytes",
+                            job.getEstimatedNetworkDownloadBytes());
+                }
+                if (job.getEstimatedNetworkUploadBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                    out.attributeLong(null, "estimated-upload-bytes",
+                            job.getEstimatedNetworkUploadBytes());
+                }
+                if (job.getMinimumNetworkChunkBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                    out.attributeLong(null, "minimum-network-chunk-bytes",
+                            job.getMinimumNetworkChunkBytes());
+                }
             }
             if (jobStatus.hasIdleConstraint()) {
                 out.attribute(null, "idle", Boolean.toString(true));
@@ -722,54 +947,87 @@
 
         @Override
         public void run() {
+            if (!mJobFileDirectory.isDirectory()) {
+                Slog.wtf(TAG, "jobs directory isn't a directory O.O");
+                mJobFileDirectory.mkdirs();
+                return;
+            }
+
             int numJobs = 0;
             int numSystemJobs = 0;
             int numSyncJobs = 0;
             List<JobStatus> jobs;
-            try (FileInputStream fis = mJobsFile.openRead()) {
-                synchronized (mLock) {
-                    jobs = readJobMapImpl(fis, rtcGood);
-                    if (jobs != null) {
-                        long now = sElapsedRealtimeClock.millis();
-                        for (int i=0; i<jobs.size(); i++) {
-                            JobStatus js = jobs.get(i);
-                            js.prepareLocked();
-                            js.enqueueTime = now;
-                            this.jobSet.add(js);
+            final File[] files;
+            try {
+                files = mJobFileDirectory.listFiles();
+            } catch (SecurityException e) {
+                Slog.wtf(TAG, "Not allowed to read job file directory", e);
+                return;
+            }
+            if (files == null) {
+                Slog.wtfStack(TAG, "Couldn't get job file list");
+                return;
+            }
+            boolean needFileMigration = false;
+            long nowElapsed = sElapsedRealtimeClock.millis();
+            for (File file : files) {
+                final AtomicFile aFile = createJobFile(file);
+                try (FileInputStream fis = aFile.openRead()) {
+                    synchronized (mLock) {
+                        jobs = readJobMapImpl(fis, rtcGood, nowElapsed);
+                        if (jobs != null) {
+                            for (int i = 0; i < jobs.size(); i++) {
+                                JobStatus js = jobs.get(i);
+                                js.prepareLocked();
+                                js.enqueueTime = nowElapsed;
+                                this.jobSet.add(js);
 
-                            numJobs++;
-                            if (js.getUid() == Process.SYSTEM_UID) {
-                                numSystemJobs++;
-                                if (isSyncJob(js)) {
-                                    numSyncJobs++;
+                                numJobs++;
+                                if (js.getUid() == Process.SYSTEM_UID) {
+                                    numSystemJobs++;
+                                    if (isSyncJob(js)) {
+                                        numSyncJobs++;
+                                    }
                                 }
                             }
                         }
                     }
+                } catch (FileNotFoundException e) {
+                    // mJobFileDirectory.listFiles() gave us this file...why can't we find it???
+                    Slog.e(TAG, "Could not find jobs file: " + file.getName());
+                } catch (XmlPullParserException | IOException e) {
+                    Slog.wtf(TAG, "Error in " + file.getName(), e);
+                } catch (Exception e) {
+                    // Crashing at this point would result in a boot loop, so live with a general
+                    // Exception for system stability's sake.
+                    Slog.wtf(TAG, "Unexpected exception", e);
                 }
-            } catch (FileNotFoundException e) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Could not find jobs file, probably there was nothing to load.");
-                }
-            } catch (XmlPullParserException | IOException e) {
-                Slog.wtf(TAG, "Error jobstore xml.", e);
-            } catch (Exception e) {
-                // Crashing at this point would result in a boot loop, so live with a general
-                // Exception for system stability's sake.
-                Slog.wtf(TAG, "Unexpected exception", e);
-            } finally {
-                if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
-                    mPersistInfo.countAllJobsLoaded = numJobs;
-                    mPersistInfo.countSystemServerJobsLoaded = numSystemJobs;
-                    mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs;
+                if (mUseSplitFiles) {
+                    if (!file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
+                        // We're supposed to be using the split file architecture, but we still have
+                        // the old job file around. Fully migrate and remove the old file.
+                        needFileMigration = true;
+                    }
+                } else if (file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
+                    // We're supposed to be using the legacy single file architecture, but we still
+                    // have some job split files around. Fully migrate and remove the split files.
+                    needFileMigration = true;
                 }
             }
+            if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
+                mPersistInfo.countAllJobsLoaded = numJobs;
+                mPersistInfo.countSystemServerJobsLoaded = numSystemJobs;
+                mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs;
+            }
             Slog.i(TAG, "Read " + numJobs + " jobs");
+            if (needFileMigration) {
+                migrateJobFilesAsync();
+            }
         }
 
-        private List<JobStatus> readJobMapImpl(InputStream fis, boolean rtcIsGood)
+        private List<JobStatus> readJobMapImpl(InputStream fis, boolean rtcIsGood, long nowElapsed)
                 throws XmlPullParserException, IOException {
-            XmlPullParser parser = Xml.resolvePullParser(fis);
+            TypedXmlPullParser parser = Xml.resolvePullParser(fis);
 
             int eventType = parser.getEventType();
             while (eventType != XmlPullParser.START_TAG &&
@@ -785,28 +1043,24 @@
             }
 
             String tagName = parser.getName();
-            if ("job-info".equals(tagName)) {
+            if (XML_TAG_JOB_INFO.equals(tagName)) {
                 final List<JobStatus> jobs = new ArrayList<JobStatus>();
-                final int version;
+                final int version = parser.getAttributeInt(null, "version");
                 // Read in version info.
-                try {
-                    version = Integer.parseInt(parser.getAttributeValue(null, "version"));
-                    if (version > JOBS_FILE_VERSION || version < 0) {
-                        Slog.d(TAG, "Invalid version number, aborting jobs file read.");
-                        return null;
-                    }
-                } catch (NumberFormatException e) {
-                    Slog.e(TAG, "Invalid version number, aborting jobs file read.");
+                if (version > JOBS_FILE_VERSION || version < 0) {
+                    Slog.d(TAG, "Invalid version number, aborting jobs file read.");
                     return null;
                 }
+
                 eventType = parser.next();
                 do {
                     // Read each <job/>
                     if (eventType == XmlPullParser.START_TAG) {
                         tagName = parser.getName();
                         // Start reading job.
-                        if ("job".equals(tagName)) {
-                            JobStatus persistedJob = restoreJobFromXml(rtcIsGood, parser, version);
+                        if (XML_TAG_JOB.equals(tagName)) {
+                            JobStatus persistedJob =
+                                    restoreJobFromXml(rtcIsGood, parser, version, nowElapsed);
                             if (persistedJob != null) {
                                 if (DEBUG) {
                                     Slog.d(TAG, "Read out " + persistedJob);
@@ -829,8 +1083,8 @@
          *               will take the parser into the body of the job tag.
          * @return Newly instantiated job holding all the information we just read out of the xml tag.
          */
-        private JobStatus restoreJobFromXml(boolean rtcIsGood, XmlPullParser parser,
-                int schemaVersion) throws XmlPullParserException, IOException {
+        private JobStatus restoreJobFromXml(boolean rtcIsGood, TypedXmlPullParser parser,
+                int schemaVersion, long nowElapsed) throws XmlPullParserException, IOException {
             JobInfo.Builder jobBuilder;
             int uid, sourceUserId;
             long lastSuccessfulRunTime;
@@ -921,18 +1175,9 @@
             }
 
             // Tuple of (earliest runtime, latest runtime) in UTC.
-            final Pair<Long, Long> rtcRuntimes;
-            try {
-                rtcRuntimes = buildRtcExecutionTimesFromXml(parser);
-            } catch (NumberFormatException e) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Error parsing execution time parameters, skipping.");
-                }
-                return null;
-            }
+            final Pair<Long, Long> rtcRuntimes = buildRtcExecutionTimesFromXml(parser);
 
-            final long elapsedNow = sElapsedRealtimeClock.millis();
-            Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, elapsedNow);
+            Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, nowElapsed);
 
             if (XML_TAG_PERIODIC.equals(parser.getName())) {
                 try {
@@ -945,8 +1190,8 @@
                     // from now. This is the latest the periodic could be pushed out. This could
                     // happen if the periodic ran early (at flex time before period), and then the
                     // device rebooted.
-                    if (elapsedRuntimes.second > elapsedNow + periodMillis + flexMillis) {
-                        final long clampedLateRuntimeElapsed = elapsedNow + flexMillis
+                    if (elapsedRuntimes.second > nowElapsed + periodMillis + flexMillis) {
+                        final long clampedLateRuntimeElapsed = nowElapsed + flexMillis
                                 + periodMillis;
                         final long clampedEarlyRuntimeElapsed = clampedLateRuntimeElapsed
                                 - flexMillis;
@@ -971,11 +1216,11 @@
             } else if (XML_TAG_ONEOFF.equals(parser.getName())) {
                 try {
                     if (elapsedRuntimes.first != JobStatus.NO_EARLIEST_RUNTIME) {
-                        jobBuilder.setMinimumLatency(elapsedRuntimes.first - elapsedNow);
+                        jobBuilder.setMinimumLatency(elapsedRuntimes.first - nowElapsed);
                     }
                     if (elapsedRuntimes.second != JobStatus.NO_LATEST_RUNTIME) {
                         jobBuilder.setOverrideDeadline(
-                                elapsedRuntimes.second - elapsedNow);
+                                elapsedRuntimes.second - nowElapsed);
                     }
                 } catch (NumberFormatException e) {
                     Slog.d(TAG, "Error reading job execution criteria, skipping.");
@@ -1044,7 +1289,7 @@
 
             // And now we're done
             final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
-                    sourceUserId, elapsedNow);
+                    sourceUserId, nowElapsed);
             JobStatus js = new JobStatus(
                     builtJob, uid, sourcePackageName, sourceUserId,
                     appBucket, sourceTag,
@@ -1054,9 +1299,10 @@
             return js;
         }
 
-        private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
+        private JobInfo.Builder buildBuilderFromXml(TypedXmlPullParser parser)
+                throws XmlPullParserException {
             // Pull out required fields from <job> attributes.
-            int jobId = Integer.parseInt(parser.getAttributeValue(null, "jobid"));
+            int jobId = parser.getAttributeInt(null, "jobid");
             String packageName = parser.getAttributeValue(null, "package");
             String className = parser.getAttributeValue(null, "class");
             ComponentName cname = new ComponentName(packageName, className);
@@ -1075,7 +1321,7 @@
          * reading, but in order to avoid issues with OEM-defined flags, the accepted capabilities
          * are limited to that(maxNetCapabilityInR & maxTransportInR) defined in R.
          */
-        private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser)
+        private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, TypedXmlPullParser parser)
                 throws XmlPullParserException, IOException {
             String val;
             String netCapabilitiesLong = null;
@@ -1112,7 +1358,17 @@
                 for (int transport : stringToIntArray(netTransportTypesIntArray)) {
                     builder.addTransportType(transport);
                 }
-                jobBuilder.setRequiredNetwork(builder.build());
+                jobBuilder
+                        .setRequiredNetwork(builder.build())
+                        .setEstimatedNetworkBytes(
+                                parser.getAttributeLong(null,
+                                        "estimated-download-bytes", JobInfo.NETWORK_BYTES_UNKNOWN),
+                                parser.getAttributeLong(null,
+                                        "estimated-upload-bytes", JobInfo.NETWORK_BYTES_UNKNOWN))
+                        .setMinimumNetworkChunkBytes(
+                                parser.getAttributeLong(null,
+                                        "minimum-network-chunk-bytes",
+                                        JobInfo.NETWORK_BYTES_UNKNOWN));
             } else if (netCapabilitiesLong != null && netTransportTypesLong != null) {
                 // Format used on R- builds. Drop any unexpected capabilities and transports.
                 final NetworkRequest.Builder builder = new NetworkRequest.Builder()
@@ -1140,6 +1396,8 @@
                     }
                 }
                 jobBuilder.setRequiredNetwork(builder.build());
+                // Estimated bytes weren't persisted on R- builds, so no point querying for the
+                // attributes here.
             } else {
                 // Read legacy values
                 val = parser.getAttributeValue(null, "connectivity");
@@ -1201,20 +1459,13 @@
          * @return A Pair of timestamps in UTC wall-clock time.  The first is the earliest
          *     time at which the job is to become runnable, and the second is the deadline at
          *     which it becomes overdue to execute.
-         * @throws NumberFormatException
          */
-        private Pair<Long, Long> buildRtcExecutionTimesFromXml(XmlPullParser parser)
-                throws NumberFormatException {
-            String val;
+        private Pair<Long, Long> buildRtcExecutionTimesFromXml(TypedXmlPullParser parser) {
             // Pull out execution time data.
-            val = parser.getAttributeValue(null, "delay");
-            final long earliestRunTimeRtc = (val != null)
-                    ? Long.parseLong(val)
-                    : JobStatus.NO_EARLIEST_RUNTIME;
-            val = parser.getAttributeValue(null, "deadline");
-            final long latestRunTimeRtc = (val != null)
-                    ? Long.parseLong(val)
-                    : JobStatus.NO_LATEST_RUNTIME;
+            final long earliestRunTimeRtc =
+                    parser.getAttributeLong(null, "delay", JobStatus.NO_EARLIEST_RUNTIME);
+            final long latestRunTimeRtc =
+                    parser.getAttributeLong(null, "deadline", JobStatus.NO_LATEST_RUNTIME);
             return Pair.create(earliestRunTimeRtc, latestRunTimeRtc);
         }
     }
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..83b6a8e 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
@@ -29,10 +29,13 @@
 import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
 
 import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
 import android.app.AppGlobals;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
 import android.app.job.JobWorkItem;
+import android.app.job.UserVisibleJobSummary;
 import android.content.ClipData;
 import android.content.ComponentName;
 import android.net.Network;
@@ -95,17 +98,24 @@
     static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;  // 1 << 2
     static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
     static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3
-    static final int CONSTRAINT_TIMING_DELAY = 1<<31;
-    static final int CONSTRAINT_DEADLINE = 1<<30;
-    static final int CONSTRAINT_CONNECTIVITY = 1 << 28;
+    public static final int CONSTRAINT_TIMING_DELAY = 1 << 31;
+    public static final int CONSTRAINT_DEADLINE = 1 << 30;
+    public static final int CONSTRAINT_CONNECTIVITY = 1 << 28;
     static final int CONSTRAINT_TARE_WEALTH = 1 << 27; // Implicit constraint
-    static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
+    public static final int CONSTRAINT_CONTENT_TRIGGER = 1 << 26;
     static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint
     static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24;      // Implicit constraint
     static final int CONSTRAINT_PREFETCH = 1 << 23;
     static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
     static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
 
+    private static final int IMPLICIT_CONSTRAINTS = 0
+            | CONSTRAINT_BACKGROUND_NOT_RESTRICTED
+            | CONSTRAINT_DEVICE_NOT_DOZING
+            | CONSTRAINT_FLEXIBLE
+            | CONSTRAINT_TARE_WEALTH
+            | CONSTRAINT_WITHIN_QUOTA;
+
     // The following set of dynamic constraints are for specific use cases (as explained in their
     // relative naming and comments). Right now, they apply different constraints, which is fine,
     // but if in the future, we have overlapping dynamic constraint sets, removing one constraint
@@ -241,10 +251,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.
      */
@@ -434,6 +456,12 @@
      */
     private boolean mExpeditedTareApproved;
 
+    /**
+     * Summary describing this job. Lazily created in {@link #getUserVisibleJobSummary()}
+     * since not every job will need it.
+     */
+    private UserVisibleJobSummary mUserVisibleJobSummary;
+
     /////// Booleans that track if a job is ready to run. They should be updated whenever dependent
     /////// states change.
 
@@ -488,6 +516,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 +527,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 +565,7 @@
         this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
         this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
         this.numFailures = numFailures;
+        mNumSystemStops = numSystemStops;
 
         int requiredConstraints = job.getConstraintFlags();
         if (job.getRequiredNetwork() != null) {
@@ -576,7 +607,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 +657,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 +685,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 +704,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 +747,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);
@@ -746,7 +778,6 @@
                 executingWork.add(work);
                 work.bumpDeliveryCount();
             }
-            updateNetworkBytesLocked();
             return work;
         }
         return null;
@@ -774,6 +805,7 @@
                 if (work.getWorkId() == workId) {
                     executingWork.remove(i);
                     ungrantWorkItem(work);
+                    updateNetworkBytesLocked();
                     return true;
                 }
             }
@@ -868,10 +900,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 +1110,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();
@@ -1282,6 +1345,27 @@
     }
 
     /**
+     * Return a summary that uniquely identifies the underlying job.
+     */
+    @NonNull
+    public UserVisibleJobSummary getUserVisibleJobSummary() {
+        if (mUserVisibleJobSummary == null) {
+            mUserVisibleJobSummary = new UserVisibleJobSummary(
+                    callingUid, getSourceUserId(), getSourcePackageName(), getJobId());
+        }
+        return mUserVisibleJobSummary;
+    }
+
+    /**
+     * @return true if this is a job whose execution should be made visible to the user.
+     */
+    public boolean isUserVisibleJob() {
+        // TODO(255767350): limit to user-initiated jobs
+        // Placeholder implementation until we have the code in
+        return shouldTreatAsExpeditedJob();
+    }
+
+    /**
      * @return true if the job is exempted from Doze restrictions and therefore allowed to run
      * in Doze.
      */
@@ -1566,7 +1650,103 @@
         }
     }
 
-    boolean isConstraintSatisfied(int constraint) {
+    /**
+     * If {@link #isReady()} returns false, this will return a single reason why the job isn't
+     * ready. If {@link #isReady()} returns true, this will return
+     * {@link JobScheduler#PENDING_JOB_REASON_UNDEFINED}.
+     */
+    @JobScheduler.PendingJobReason
+    public int getPendingJobReason() {
+        final int unsatisfiedConstraints = ~satisfiedConstraints
+                & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS);
+        if ((CONSTRAINT_BACKGROUND_NOT_RESTRICTED & unsatisfiedConstraints) != 0) {
+            // The BACKGROUND_NOT_RESTRICTED constraint could be unsatisfied either because
+            // the app is background restricted, or because we're restricting background work
+            // in battery saver. Assume that background restriction is the reason apps that
+            // jobs are not ready, and battery saver otherwise.
+            // This has the benefit of being consistent for background restricted apps
+            // (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of
+            // battery saver state.
+            if (mIsUserBgRestricted) {
+                return JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION;
+            }
+            return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+        }
+        if ((CONSTRAINT_BATTERY_NOT_LOW & unsatisfiedConstraints) != 0) {
+            if ((CONSTRAINT_BATTERY_NOT_LOW & requiredConstraints) != 0) {
+                // The developer requested this constraint, so it makes sense to return the
+                // explicit constraint reason.
+                return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW;
+            }
+            // Hard-coding right now since the current dynamic constraint sets don't overlap
+            // TODO: return based on active dynamic constraint sets when they start overlapping
+            return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+        }
+        if ((CONSTRAINT_CHARGING & unsatisfiedConstraints) != 0) {
+            if ((CONSTRAINT_CHARGING & requiredConstraints) != 0) {
+                // The developer requested this constraint, so it makes sense to return the
+                // explicit constraint reason.
+                return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING;
+            }
+            // Hard-coding right now since the current dynamic constraint sets don't overlap
+            // TODO: return based on active dynamic constraint sets when they start overlapping
+            return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+        }
+        if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY;
+        }
+        if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER;
+        }
+        if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+        }
+        if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION;
+        }
+        if ((CONSTRAINT_IDLE & unsatisfiedConstraints) != 0) {
+            if ((CONSTRAINT_IDLE & requiredConstraints) != 0) {
+                // The developer requested this constraint, so it makes sense to return the
+                // explicit constraint reason.
+                return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE;
+            }
+            // Hard-coding right now since the current dynamic constraint sets don't overlap
+            // TODO: return based on active dynamic constraint sets when they start overlapping
+            return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+        }
+        if ((CONSTRAINT_PREFETCH & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH;
+        }
+        if ((CONSTRAINT_STORAGE_NOT_LOW & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW;
+        }
+        if ((CONSTRAINT_TARE_WEALTH & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_QUOTA;
+        }
+        if ((CONSTRAINT_TIMING_DELAY & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY;
+        }
+        if ((CONSTRAINT_WITHIN_QUOTA & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_QUOTA;
+        }
+
+        if (getEffectiveStandbyBucket() == NEVER_INDEX) {
+            Slog.wtf(TAG, "App in NEVER bucket querying pending job reason");
+            // The user hasn't officially launched this app.
+            return JobScheduler.PENDING_JOB_REASON_USER;
+        }
+        if (serviceProcessName != null) {
+            return JobScheduler.PENDING_JOB_REASON_APP;
+        }
+
+        if (!isReady()) {
+            Slog.wtf(TAG, "Unknown reason job isn't ready");
+        }
+        return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+    }
+
+    /** @return whether or not the @param constraint is satisfied */
+    public boolean isConstraintSatisfied(int constraint) {
         return (satisfiedConstraints&constraint) != 0;
     }
 
@@ -1857,6 +2037,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 +2566,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 +2766,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..fc60228 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
@@ -31,6 +31,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.expresslog.Counter;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
 
@@ -79,7 +80,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
@@ -88,6 +89,8 @@
             // will never be unsatisfied (our time base can not go backwards).
             final long nowElapsedMillis = sElapsedRealtimeClock.millis();
             if (job.hasDeadlineConstraint() && evaluateDeadlineConstraint(job, nowElapsedMillis)) {
+                // We're intentionally excluding jobs whose deadlines have passed
+                // (mostly like deadlines of 0) when the job was scheduled.
                 return;
             } else if (job.hasTimingDelayConstraint() && evaluateTimingDelayConstraint(job,
                     nowElapsedMillis)) {
@@ -134,8 +137,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();
@@ -159,6 +161,9 @@
                     // Scheduler.
                     mStateChangedListener.onRunJobNow(job);
                 }
+                mTrackedJobs.remove(job);
+                Counter.logIncrement(
+                        "job_scheduler.value_job_scheduler_job_deadline_expired_counter");
             } else if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_DEADLINE)) {
                 // This job's deadline is earlier than the current set alarm. Update the alarm.
                 setDeadlineExpiredAlarmLocked(job.getLatestRunTimeElapsed(),
@@ -170,8 +175,11 @@
                 && job.getEarliestRunTime() <= mNextDelayExpiredElapsedMillis) {
             // Since this is just the delay, we don't need to rush the Scheduler to run the job
             // immediately if the constraint is satisfied here.
-            if (!evaluateTimingDelayConstraint(job, nowElapsedMillis)
-                    && wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
+            if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) {
+                if (canStopTrackingJobLocked(job)) {
+                    mTrackedJobs.remove(job);
+                }
+            } else if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
                 // This job's delay is earlier than the current set alarm. Update the alarm.
                 setDelayExpiredAlarmLocked(job.getEarliestRunTime(),
                         mService.deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()));
@@ -229,6 +237,8 @@
                         // Scheduler.
                         mStateChangedListener.onRunJobNow(job);
                     }
+                    Counter.logIncrement(
+                            "job_scheduler.value_job_scheduler_job_deadline_expired_counter");
                     it.remove();
                 } else {  // Sorted by expiry time, so take the next one and stop.
                     if (!wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_DEADLINE)) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
index 4067541..7aab67a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
@@ -18,6 +18,7 @@
 
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
 import android.util.IndentingPrintWriter;
 import android.util.proto.ProtoOutputStream;
 
@@ -28,20 +29,23 @@
  * Used by {@link JobSchedulerService} to impose additional restrictions regarding whether jobs
  * should be scheduled or not based on the state of the system/device.
  * Every restriction is associated with exactly one stop reason, which could be retrieved using
- * {@link #getReason()} (and the internal reason via {@link #getInternalReason()}).
+ * {@link #getStopReason()}, one pending reason (retrievable via {@link #getPendingReason()},
+ * (and the internal reason via {@link #getInternalReason()}).
  * Note, that this is not taken into account for the jobs that have
  * {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias or higher.
  */
 public abstract class JobRestriction {
 
     final JobSchedulerService mService;
-    private final int mReason;
+    private final int mStopReason;
+    private final int mPendingReason;
     private final int mInternalReason;
 
-    JobRestriction(JobSchedulerService service, @JobParameters.StopReason int reason,
-            int internalReason) {
+    protected JobRestriction(JobSchedulerService service, @JobParameters.StopReason int stopReason,
+            @JobScheduler.PendingJobReason int pendingReason, int internalReason) {
         mService = service;
-        mReason = reason;
+        mPendingReason = pendingReason;
+        mStopReason = stopReason;
         mInternalReason = internalReason;
     }
 
@@ -70,10 +74,15 @@
     public void dumpConstants(ProtoOutputStream proto) {
     }
 
-    /** @return reason code for the Restriction. */
+    @JobScheduler.PendingJobReason
+    public final int getPendingReason() {
+        return mPendingReason;
+    }
+
+    /** @return stop reason code for the Restriction. */
     @JobParameters.StopReason
-    public final int getReason() {
-        return mReason;
+    public final int getStopReason() {
+        return mStopReason;
     }
 
     public final int getInternalReason() {
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 ca2fd60..830031e 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
@@ -18,6 +18,7 @@
 
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
 import android.os.PowerManager;
 import android.os.PowerManager.OnThermalStatusChangedListener;
 import android.util.IndentingPrintWriter;
@@ -42,6 +43,7 @@
 
     public ThermalStatusRestriction(JobSchedulerService service) {
         super(service, JobParameters.STOP_REASON_DEVICE_STATE,
+                JobScheduler.PENDING_JOB_REASON_DEVICE_STATE,
                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
     }
 
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..b84c8a4 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;
@@ -285,7 +286,7 @@
 
         for (int i = 0; i < pkgNames.size(); ++i) {
             final String pkgName = pkgNames.valueAt(i);
-            final boolean isVip = mIrs.isVip(userId, pkgName);
+            final boolean isVip = mIrs.isVip(userId, pkgName, nowElapsed);
             SparseArrayMap<String, OngoingEvent> ongoingEvents =
                     mCurrentOngoingEvents.get(userId, pkgName);
             if (ongoingEvents != null) {
@@ -320,7 +321,7 @@
         final long nowElapsed = SystemClock.elapsedRealtime();
         final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
 
-        final boolean isVip = mIrs.isVip(userId, pkgName);
+        final boolean isVip = mIrs.isVip(userId, pkgName, nowElapsed);
         SparseArrayMap<String, OngoingEvent> ongoingEvents =
                 mCurrentOngoingEvents.get(userId, pkgName);
         if (ongoingEvents != null) {
@@ -396,7 +397,7 @@
                 if (actionAffordabilityNotes != null) {
                     final int size = actionAffordabilityNotes.size();
                     final long newBalance = getBalanceLocked(userId, pkgName);
-                    final boolean isVip = mIrs.isVip(userId, pkgName);
+                    final boolean isVip = mIrs.isVip(userId, pkgName, nowElapsed);
                     for (int n = 0; n < size; ++n) {
                         final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
                         note.recalculateCosts(economicPolicy, userId, pkgName);
@@ -502,7 +503,8 @@
                     "Tried to adjust system balance for " + appToString(userId, pkgName));
             return;
         }
-        if (mIrs.isVip(userId, pkgName)) {
+        final boolean isVip = mIrs.isVip(userId, pkgName);
+        if (isVip) {
             // This could happen if the app was made a VIP after it started performing actions.
             // Continue recording the transaction for debugging purposes, but don't let it change
             // any numbers.
@@ -535,7 +537,6 @@
                     mActionAffordabilityNotes.get(userId, pkgName);
             if (actionAffordabilityNotes != null) {
                 final long newBalance = ledger.getCurrentBalance();
-                final boolean isVip = mIrs.isVip(userId, pkgName);
                 for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
                     final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
                     final boolean isAffordable = isVip
@@ -824,12 +825,11 @@
     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")
     void onUserRemovedLocked(final int userId) {
-        mScribe.discardLedgersLocked(userId);
         mCurrentOngoingEvents.delete(userId);
         mBalanceThresholdAlarmQueue.removeAlarmsForUserId(userId);
     }
@@ -959,7 +959,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 +972,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 +980,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 +1071,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/InstalledPackageInfo.java b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
index fcb3e67..1ff389d 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
@@ -16,14 +16,20 @@
 
 package com.android.server.tare;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppGlobals;
+import android.content.Context;
+import android.content.PermissionChecker;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.InstallSourceInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.os.RemoteException;
 
+import com.android.internal.util.ArrayUtils;
+
 /** POJO to cache only the information about installed packages that TARE cares about. */
 class InstalledPackageInfo {
     static final int NO_UID = -1;
@@ -31,14 +37,22 @@
     public final int uid;
     public final String packageName;
     public final boolean hasCode;
+    public final boolean isSystemInstaller;
     @Nullable
     public final String installerPackageName;
 
-    InstalledPackageInfo(@NonNull PackageInfo packageInfo) {
+    InstalledPackageInfo(@NonNull Context context, @NonNull PackageInfo packageInfo) {
         final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
         uid = applicationInfo == null ? NO_UID : applicationInfo.uid;
         packageName = packageInfo.packageName;
         hasCode = applicationInfo != null && applicationInfo.hasCode();
+        isSystemInstaller = applicationInfo != null
+                && ArrayUtils.indexOf(
+                packageInfo.requestedPermissions, Manifest.permission.INSTALL_PACKAGES) >= 0
+                && PackageManager.PERMISSION_GRANTED
+                == PermissionChecker.checkPermissionForPreflight(context,
+                Manifest.permission.INSTALL_PACKAGES, PermissionChecker.PID_UNKNOWN,
+                applicationInfo.uid, packageName);
         InstallSourceInfo installSourceInfo = null;
         try {
             installSourceInfo = AppGlobals.getPackageManager().getInstallSourceInfo(packageName);
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..4001d9b 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -64,6 +64,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArrayMap;
+import android.util.SparseLongArray;
 import android.util.SparseSetArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -108,6 +109,16 @@
     /** The amount of time to delay reclamation by after boot. */
     private static final long RECLAMATION_STARTUP_DELAY_MS = 30_000L;
     /**
+     * The amount of time after TARE has first been set up that a system installer will be allowed
+     * expanded credit privileges.
+     */
+    static final long INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS = 7 * DAY_IN_MILLIS;
+    /**
+     * The amount of time to wait after TARE has first been set up before considering adjusting the
+     * stock/consumption limit.
+     */
+    private static final long STOCK_ADJUSTMENT_FIRST_SETUP_GRACE_PERIOD_MS = 5 * DAY_IN_MILLIS;
+    /**
      * The battery level above which we may consider quantitative easing (increasing the consumption
      * limit).
      */
@@ -127,7 +138,7 @@
     private static final long STOCK_RECALCULATION_MIN_DATA_DURATION_MS = 8 * HOUR_IN_MILLIS;
     private static final int PACKAGE_QUERY_FLAGS =
             PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                    | PackageManager.MATCH_APEX;
+                    | PackageManager.MATCH_APEX | PackageManager.GET_PERMISSIONS;
 
     /** Global lock for all resource economy state. */
     private final Object mLock = new Object();
@@ -179,6 +190,13 @@
     @GuardedBy("mLock")
     private final SparseArrayMap<String, Boolean> mVipOverrides = new SparseArrayMap<>();
 
+    /**
+     * Set of temporary Very Important Packages and when their VIP status ends, in the elapsed
+     * realtime ({@link android.annotation.ElapsedRealtimeLong}) timebase.
+     */
+    @GuardedBy("mLock")
+    private final SparseArrayMap<String, Long> mTemporaryVips = new SparseArrayMap<>();
+
     /** Set of apps each installer is responsible for installing. */
     @GuardedBy("mLock")
     private final SparseArrayMap<String, ArraySet<String>> mInstallers = new SparseArrayMap<>();
@@ -308,6 +326,7 @@
     private static final int MSG_PROCESS_USAGE_EVENT = 2;
     private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 3;
     private static final int MSG_NOTIFY_STATE_CHANGE_LISTENER = 4;
+    private static final int MSG_CLEAN_UP_TEMP_VIP_LIST = 5;
     private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";
 
     /**
@@ -413,6 +432,13 @@
         return userPkgs;
     }
 
+    @Nullable
+    InstalledPackageInfo getInstalledPackageInfo(final int userId, @NonNull final String pkgName) {
+        synchronized (mLock) {
+            return mPkgCache.get(userId, pkgName);
+        }
+    }
+
     @GuardedBy("mLock")
     long getConsumptionLimitLocked() {
         return mCurrentBatteryLevel * mScribe.getSatiatedConsumptionLimitLocked() / 100;
@@ -429,6 +455,11 @@
         return mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit();
     }
 
+
+    long getRealtimeSinceFirstSetupMs() {
+        return mScribe.getRealtimeSinceFirstSetupMs(SystemClock.elapsedRealtime());
+    }
+
     int getUid(final int userId, @NonNull final String pkgName) {
         synchronized (mPackageToUidCache) {
             Integer uid = mPackageToUidCache.get(userId, pkgName);
@@ -470,6 +501,10 @@
     }
 
     boolean isVip(final int userId, @NonNull String pkgName) {
+        return isVip(userId, pkgName, SystemClock.elapsedRealtime());
+    }
+
+    boolean isVip(final int userId, @NonNull String pkgName, final long nowElapsed) {
         synchronized (mLock) {
             final Boolean override = mVipOverrides.get(userId, pkgName);
             if (override != null) {
@@ -481,6 +516,12 @@
             // operate.
             return true;
         }
+        synchronized (mLock) {
+            final Long expirationTimeElapsed = mTemporaryVips.get(userId, pkgName);
+            if (expirationTimeElapsed != null) {
+                return nowElapsed <= expirationTimeElapsed;
+            }
+        }
         return false;
     }
 
@@ -569,7 +610,7 @@
             mPackageToUidCache.add(userId, pkgName, uid);
         }
         synchronized (mLock) {
-            final InstalledPackageInfo ipo = new InstalledPackageInfo(packageInfo);
+            final InstalledPackageInfo ipo = new InstalledPackageInfo(getContext(), packageInfo);
             final InstalledPackageInfo oldIpo = mPkgCache.add(userId, pkgName, ipo);
             maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             mUidToPackageCache.add(uid, pkgName);
@@ -626,11 +667,16 @@
             final List<PackageInfo> pkgs =
                     mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
             for (int i = pkgs.size() - 1; i >= 0; --i) {
-                final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i));
+                final InstalledPackageInfo ipo =
+                        new InstalledPackageInfo(getContext(), pkgs.get(i));
                 final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
                 maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             }
             mAgent.grantBirthrightsLocked(userId);
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            mScribe.setUserAddedTimeLocked(userId, nowElapsed);
+            grantInstallersTemporaryVipStatusLocked(userId,
+                    nowElapsed, INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS);
         }
     }
 
@@ -647,6 +693,7 @@
             mInstallers.delete(userId);
             mPkgCache.delete(userId);
             mAgent.onUserRemovedLocked(userId);
+            mScribe.onUserRemovedLocked(userId);
         }
     }
 
@@ -659,6 +706,10 @@
             maybeAdjustDesiredStockLevelLocked();
             return;
         }
+        if (getRealtimeSinceFirstSetupMs() < STOCK_ADJUSTMENT_FIRST_SETUP_GRACE_PERIOD_MS) {
+            // Things can be very tumultuous soon after first setup.
+            return;
+        }
         // We don't need to increase the limit if the device runs out of consumable credits
         // when the battery is low.
         final long remainingConsumableCakes = mScribe.getRemainingConsumableCakesLocked();
@@ -670,7 +721,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));
@@ -687,6 +738,10 @@
         if (!mConfigObserver.ENABLE_TIP3) {
             return;
         }
+        if (getRealtimeSinceFirstSetupMs() < STOCK_ADJUSTMENT_FIRST_SETUP_GRACE_PERIOD_MS) {
+            // Things can be very tumultuous soon after first setup.
+            return;
+        }
         // Don't adjust the limit too often or while the battery is low.
         final long now = getCurrentTimeMillis();
         if ((now - mScribe.getLastStockRecalculationTimeLocked()) < STOCK_RECALCULATION_DELAY_MS
@@ -701,6 +756,10 @@
             return;
         }
         final long totalDischargeMah = mAnalyst.getBatteryScreenOffDischargeMah();
+        if (totalDischargeMah == 0) {
+            Slog.i(TAG, "Total discharge was 0");
+            return;
+        }
         final long batteryCapacityMah = mBatteryManagerInternal.getBatteryFullCharge() / 1000;
         final long estimatedLifeHours = batteryCapacityMah * totalScreenOffDurationMs
                 / totalDischargeMah / HOUR_IN_MILLIS;
@@ -720,12 +779,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;
@@ -772,6 +831,28 @@
     }
 
     @GuardedBy("mLock")
+    private void grantInstallersTemporaryVipStatusLocked(int userId, long nowElapsed,
+            long grantDurationMs) {
+        final long grantEndTimeElapsed = nowElapsed + grantDurationMs;
+        final int uIdx = mPkgCache.indexOfKey(userId);
+        if (uIdx < 0) {
+            return;
+        }
+        for (int pIdx = mPkgCache.numElementsForKey(uIdx) - 1; pIdx >= 0; --pIdx) {
+            final InstalledPackageInfo ipo = mPkgCache.valueAt(uIdx, pIdx);
+
+            if (ipo.isSystemInstaller) {
+                final Long currentGrantEndTimeElapsed = mTemporaryVips.get(userId, ipo.packageName);
+                if (currentGrantEndTimeElapsed == null
+                        || currentGrantEndTimeElapsed < grantEndTimeElapsed) {
+                    mTemporaryVips.add(userId, ipo.packageName, grantEndTimeElapsed);
+                }
+            }
+        }
+        mHandler.sendEmptyMessageDelayed(MSG_CLEAN_UP_TEMP_VIP_LIST, grantDurationMs);
+    }
+
+    @GuardedBy("mLock")
     private void processUsageEventLocked(final int userId, @NonNull UsageEvents.Event event) {
         if (!mIsEnabled) {
             return;
@@ -866,7 +947,8 @@
             final List<PackageInfo> pkgs =
                     mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
             for (int i = pkgs.size() - 1; i >= 0; --i) {
-                final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i));
+                final InstalledPackageInfo ipo =
+                        new InstalledPackageInfo(getContext(), pkgs.get(i));
                 final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
                 maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             }
@@ -949,17 +1031,23 @@
         synchronized (mLock) {
             mCompleteEconomicPolicy.setup(mConfigObserver.getAllDeviceConfigProperties());
             loadInstalledPackageListLocked();
+            final SparseLongArray timeSinceUsersAdded;
             final boolean isFirstSetup = !mScribe.recordExists();
+            final long nowElapsed = SystemClock.elapsedRealtime();
             if (isFirstSetup) {
                 mAgent.grantBirthrightsLocked();
                 mScribe.setConsumptionLimitLocked(
                         mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
+                // Set the last reclamation time to now so we don't start reclaiming assets
+                // too early.
+                mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
+                timeSinceUsersAdded = new SparseLongArray();
             } 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());
@@ -967,6 +1055,21 @@
                     // Adjust the supply in case battery level changed while the device was off.
                     adjustCreditSupplyLocked(true);
                 }
+                timeSinceUsersAdded = mScribe.getRealtimeSinceUsersAddedLocked(nowElapsed);
+            }
+
+            final int[] userIds = LocalServices.getService(UserManagerInternal.class).getUserIds();
+            for (int userId : userIds) {
+                final long timeSinceUserAddedMs = timeSinceUsersAdded.get(userId, 0);
+                // Temporarily mark installers as VIPs so they aren't subject to credit
+                // limits and policies on first boot.
+                if (timeSinceUserAddedMs < INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS) {
+                    final long remainingGraceDurationMs =
+                            INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS - timeSinceUserAddedMs;
+
+                    grantInstallersTemporaryVipStatusLocked(userId, nowElapsed,
+                            remainingGraceDurationMs);
+                }
             }
             scheduleUnusedWealthReclamationLocked();
         }
@@ -1075,6 +1178,36 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
+                case MSG_CLEAN_UP_TEMP_VIP_LIST: {
+                    removeMessages(MSG_CLEAN_UP_TEMP_VIP_LIST);
+
+                    synchronized (mLock) {
+                        final long nowElapsed = SystemClock.elapsedRealtime();
+
+                        long earliestExpiration = Long.MAX_VALUE;
+                        for (int u = 0; u < mTemporaryVips.numMaps(); ++u) {
+                            final int userId = mTemporaryVips.keyAt(u);
+
+                            for (int p = mTemporaryVips.numElementsForKeyAt(u) - 1; p >= 0; --p) {
+                                final String pkgName = mTemporaryVips.keyAt(u, p);
+                                final Long expiration = mTemporaryVips.valueAt(u, p);
+
+                                if (expiration == null || expiration < nowElapsed) {
+                                    mTemporaryVips.delete(userId, pkgName);
+                                } else {
+                                    earliestExpiration = Math.min(earliestExpiration, expiration);
+                                }
+                            }
+                        }
+
+                        if (earliestExpiration < Long.MAX_VALUE) {
+                            sendEmptyMessageDelayed(MSG_CLEAN_UP_TEMP_VIP_LIST,
+                                    earliestExpiration - nowElapsed);
+                        }
+                    }
+                }
+                break;
+
                 case MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER: {
                     final SomeArgs args = (SomeArgs) msg.obj;
                     final int userId = args.argi1;
@@ -1442,17 +1575,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());
@@ -1555,6 +1687,7 @@
             boolean printedVips = false;
             pw.println();
             pw.print("VIPs:");
+            pw.increaseIndent();
             for (int u = 0; u < mVipOverrides.numMaps(); ++u) {
                 final int userId = mVipOverrides.keyAt(u);
 
@@ -1573,6 +1706,32 @@
             } else {
                 pw.print(" None");
             }
+            pw.decreaseIndent();
+            pw.println();
+
+            boolean printedTempVips = false;
+            pw.println();
+            pw.print("Temp VIPs:");
+            pw.increaseIndent();
+            for (int u = 0; u < mTemporaryVips.numMaps(); ++u) {
+                final int userId = mTemporaryVips.keyAt(u);
+
+                for (int p = 0; p < mTemporaryVips.numElementsForKeyAt(u); ++p) {
+                    final String pkgName = mTemporaryVips.keyAt(u, p);
+
+                    printedTempVips = true;
+                    pw.println();
+                    pw.print(appToString(userId, pkgName));
+                    pw.print("=");
+                    pw.print(mTemporaryVips.valueAt(u, p));
+                }
+            }
+            if (printedTempVips) {
+                pw.println();
+            } else {
+                pw.print(" None");
+            }
+            pw.decreaseIndent();
             pw.println();
 
             pw.println();
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..c2a6e43 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;
@@ -115,6 +117,7 @@
 import static com.android.server.tare.Modifier.COST_MODIFIER_DEVICE_IDLE;
 import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE;
 import static com.android.server.tare.Modifier.COST_MODIFIER_PROCESS_STATE;
+import static com.android.server.tare.TareUtils.appToString;
 import static com.android.server.tare.TareUtils.cakeToString;
 
 import android.annotation.NonNull;
@@ -159,7 +162,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;
@@ -207,6 +211,22 @@
         if (mIrs.isPackageRestricted(userId, pkgName)) {
             return 0;
         }
+        final InstalledPackageInfo ipo = mIrs.getInstalledPackageInfo(userId, pkgName);
+        if (ipo == null) {
+            Slog.wtfStack(TAG,
+                    "Tried to get max balance of invalid app: " + appToString(userId, pkgName));
+        } else {
+            // A system installer's max balance is elevated for some time after first boot so
+            // they can use jobs to download and install apps.
+            if (ipo.isSystemInstaller) {
+                final long timeSinceFirstSetupMs = mIrs.getRealtimeSinceFirstSetupMs();
+                final boolean stillExempted = timeSinceFirstSetupMs
+                        < InternalResourceService.INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS;
+                if (stillExempted) {
+                    return mMaxSatiatedConsumptionLimit;
+                }
+            }
+        }
         return mMaxSatiatedBalance;
     }
 
@@ -216,8 +236,13 @@
     }
 
     @Override
-    long getHardSatiatedConsumptionLimit() {
-        return mHardSatiatedConsumptionLimit;
+    long getMinSatiatedConsumptionLimit() {
+        return mMinSatiatedConsumptionLimit;
+    }
+
+    @Override
+    long getMaxSatiatedConsumptionLimit() {
+        return mMaxSatiatedConsumptionLimit;
     }
 
     @NonNull
@@ -260,12 +285,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 +448,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/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index ee448b5..b41c0d1 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Environment;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -33,6 +34,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseArrayMap;
+import android.util.SparseLongArray;
 import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
@@ -88,6 +90,7 @@
             "lastStockRecalculationTime";
     private static final String XML_ATTR_REMAINING_CONSUMABLE_CAKES = "remainingConsumableCakes";
     private static final String XML_ATTR_CONSUMPTION_LIMIT = "consumptionLimit";
+    private static final String XML_ATTR_TIME_SINCE_FIRST_SETUP_MS = "timeSinceFirstSetup";
     private static final String XML_ATTR_PR_DISCHARGE = "discharge";
     private static final String XML_ATTR_PR_BATTERY_LEVEL = "batteryLevel";
     private static final String XML_ATTR_PR_PROFIT = "profit";
@@ -112,6 +115,11 @@
     private final InternalResourceService mIrs;
     private final Analyst mAnalyst;
 
+    /**
+     * The value of elapsed realtime since TARE was first setup that was read from disk.
+     * This will only be changed when the persisted file is read.
+     */
+    private long mLoadedTimeSinceFirstSetup;
     @GuardedBy("mIrs.getLock()")
     private long mLastReclamationTime;
     @GuardedBy("mIrs.getLock()")
@@ -122,6 +130,9 @@
     private long mRemainingConsumableCakes;
     @GuardedBy("mIrs.getLock()")
     private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>();
+    /** Offsets used to calculate the total realtime since each user was added. */
+    @GuardedBy("mIrs.getLock()")
+    private final SparseLongArray mRealtimeSinceUsersAddedOffsets = new SparseLongArray();
 
     private final Runnable mCleanRunnable = this::cleanupLedgers;
     private final Runnable mWriteRunnable = this::writeState;
@@ -163,8 +174,9 @@
     }
 
     @GuardedBy("mIrs.getLock()")
-    void discardLedgersLocked(final int userId) {
+    void onUserRemovedLocked(final int userId) {
         mLedgers.delete(userId);
+        mRealtimeSinceUsersAddedOffsets.delete(userId);
         postWrite();
     }
 
@@ -215,6 +227,11 @@
         return sum;
     }
 
+    /** Returns the cumulative elapsed realtime since TARE was first setup. */
+    long getRealtimeSinceFirstSetupMs(long nowElapsed) {
+        return mLoadedTimeSinceFirstSetup + nowElapsed;
+    }
+
     /** Returns the total amount of cakes that remain to be consumed. */
     @GuardedBy("mIrs.getLock()")
     long getRemainingConsumableCakesLocked() {
@@ -222,6 +239,16 @@
     }
 
     @GuardedBy("mIrs.getLock()")
+    SparseLongArray getRealtimeSinceUsersAddedLocked(long nowElapsed) {
+        final SparseLongArray realtimes = new SparseLongArray();
+        for (int i = mRealtimeSinceUsersAddedOffsets.size() - 1; i >= 0; --i) {
+            realtimes.put(mRealtimeSinceUsersAddedOffsets.keyAt(i),
+                    mRealtimeSinceUsersAddedOffsets.valueAt(i) + nowElapsed);
+        }
+        return realtimes;
+    }
+
+    @GuardedBy("mIrs.getLock()")
     void loadFromDiskLocked() {
         mLedgers.clear();
         if (!recordExists()) {
@@ -276,7 +303,8 @@
                 }
             }
 
-            final long endTimeCutoff = System.currentTimeMillis() - MAX_TRANSACTION_AGE_MS;
+            final long now = System.currentTimeMillis();
+            final long endTimeCutoff = now - MAX_TRANSACTION_AGE_MS;
             long earliestEndTime = Long.MAX_VALUE;
             for (eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT;
                     eventType = parser.next()) {
@@ -294,6 +322,12 @@
                                 parser.getAttributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME);
                         mLastStockRecalculationTime = parser.getAttributeLong(null,
                                 XML_ATTR_LAST_STOCK_RECALCULATION_TIME, 0);
+                        mLoadedTimeSinceFirstSetup =
+                                parser.getAttributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
+                                        // If there's no recorded time since first setup, then
+                                        // offset the current elapsed time so it doesn't shift the
+                                        // timing too much.
+                                        -SystemClock.elapsedRealtime());
                         mSatiatedConsumptionLimit =
                                 parser.getAttributeLong(null, XML_ATTR_CONSUMPTION_LIMIT,
                                         mIrs.getInitialSatiatedConsumptionLimitLocked());
@@ -356,6 +390,13 @@
     }
 
     @GuardedBy("mIrs.getLock()")
+    void setUserAddedTimeLocked(int userId, long timeElapsed) {
+        // Use the current time as an offset so that when we persist the time, it correctly persists
+        // as "time since now".
+        mRealtimeSinceUsersAddedOffsets.put(userId, -timeElapsed);
+    }
+
+    @GuardedBy("mIrs.getLock()")
     void tearDownLocked() {
         TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable);
         TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable);
@@ -486,6 +527,14 @@
             // Don't return early since we need to go through all the ledger tags and get to the end
             // of the user tag.
         }
+        if (curUser != UserHandle.USER_NULL) {
+            mRealtimeSinceUsersAddedOffsets.put(curUser,
+                            parser.getAttributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
+                                    // If there's no recorded time since first setup, then
+                                    // offset the current elapsed time so it doesn't shift the
+                                    // timing too much.
+                                    -SystemClock.elapsedRealtime()));
+        }
         long earliestEndTime = Long.MAX_VALUE;
 
         for (int eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT;
@@ -630,6 +679,8 @@
                 out.attributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME, mLastReclamationTime);
                 out.attributeLong(null,
                         XML_ATTR_LAST_STOCK_RECALCULATION_TIME, mLastStockRecalculationTime);
+                out.attributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
+                        mLoadedTimeSinceFirstSetup + SystemClock.elapsedRealtime());
                 out.attributeLong(null, XML_ATTR_CONSUMPTION_LIMIT, mSatiatedConsumptionLimit);
                 out.attributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_CAKES,
                         mRemainingConsumableCakes);
@@ -665,6 +716,9 @@
 
         out.startTag(null, XML_TAG_USER);
         out.attributeInt(null, XML_ATTR_USER_ID, userId);
+        out.attributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
+                mRealtimeSinceUsersAddedOffsets.get(userId,
+                        mLoadedTimeSinceFirstSetup + SystemClock.elapsedRealtime()));
         for (int pIdx = mLedgers.numElementsForKey(userId) - 1; pIdx >= 0; --pIdx) {
             final String pkgName = mLedgers.keyAt(uIdx, pIdx);
             final Ledger ledger = mLedgers.get(userId, pkgName);
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 67d711c..b1c8b51 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -63,6 +63,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.AppOpsManager;
 import android.app.usage.AppStandbyInfo;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManager.ForcedReasons;
@@ -108,6 +109,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 import android.util.TimeUtils;
 import android.view.Display;
@@ -116,6 +118,8 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
@@ -279,6 +283,13 @@
     @GuardedBy("mPendingIdleStateChecks")
     private final SparseLongArray mPendingIdleStateChecks = new SparseLongArray();
 
+    /**
+     * Map of uids to their current app-op mode for
+     * {@link AppOpsManager#OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY}.
+     */
+    @GuardedBy("mSystemExemptionAppOpMode")
+    private final SparseIntArray mSystemExemptionAppOpMode = new SparseIntArray();
+
     // Cache the active network scorer queried from the network scorer service
     private volatile String mCachedNetworkScorer = null;
     // The last time the network scorer service was queried
@@ -488,6 +499,7 @@
 
     private AppWidgetManager mAppWidgetManager;
     private PackageManager mPackageManager;
+    private AppOpsManager mAppOpsManager;
     Injector mInjector;
 
     private static class Pool<T> {
@@ -647,6 +659,28 @@
             settingsObserver.start();
 
             mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class);
+            mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+            IAppOpsService iAppOpsService = mInjector.getAppOpsService();
+            try {
+                iAppOpsService.startWatchingMode(
+                        AppOpsManager.OP_SYSTEM_EXEMPT_FROM_APP_STANDBY,
+                        /*packageName=*/ null,
+                        new IAppOpsCallback.Stub() {
+                            @Override
+                            public void opChanged(int op, int uid, String packageName) {
+                                final int userId = UserHandle.getUserId(uid);
+                                synchronized (mSystemExemptionAppOpMode) {
+                                    mSystemExemptionAppOpMode.delete(uid);
+                                }
+                                mHandler.obtainMessage(
+                                        MSG_CHECK_PACKAGE_IDLE_STATE, userId, uid, packageName)
+                                        .sendToTarget();
+                            }
+                        });
+            } catch (RemoteException e) {
+                // Should not happen.
+                Slog.wtf(TAG, "Failed start watching for app op", e);
+            }
 
             mInjector.registerDisplayListener(mDisplayListener, mHandler);
             synchronized (mAppIdleLock) {
@@ -1417,6 +1451,23 @@
                 return STANDBY_BUCKET_EXEMPTED;
             }
 
+            final int uid = UserHandle.getUid(userId, appId);
+            synchronized (mSystemExemptionAppOpMode) {
+                if (mSystemExemptionAppOpMode.indexOfKey(uid) >= 0) {
+                    if (mSystemExemptionAppOpMode.get(uid)
+                            == AppOpsManager.MODE_ALLOWED) {
+                        return STANDBY_BUCKET_EXEMPTED;
+                    }
+                } else {
+                    int mode = mAppOpsManager.checkOpNoThrow(
+                            AppOpsManager.OP_SYSTEM_EXEMPT_FROM_APP_STANDBY, uid, packageName);
+                    mSystemExemptionAppOpMode.put(uid, mode);
+                    if (mode == AppOpsManager.MODE_ALLOWED) {
+                        return STANDBY_BUCKET_EXEMPTED;
+                    }
+                }
+            }
+
             if (mAppWidgetManager != null
                     && mInjector.isBoundWidgetPackage(mAppWidgetManager, packageName, userId)) {
                 return STANDBY_BUCKET_ACTIVE;
@@ -2129,6 +2180,12 @@
                     clearAppIdleForPackage(pkgName, userId);
                 }
             }
+            synchronized (mSystemExemptionAppOpMode) {
+                if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+                    mSystemExemptionAppOpMode.delete(UserHandle.getUid(userId, getAppId(pkgName)));
+                }
+            }
+
         }
     }
 
@@ -2524,6 +2581,11 @@
             }
         }
 
+        IAppOpsService getAppOpsService() {
+            return IAppOpsService.Stub.asInterface(
+                    ServiceManager.getService(Context.APP_OPS_SERVICE));
+        }
+
         /**
          * Returns {@code true} if the supplied package is the wellbeing app. Otherwise,
          * returns {@code false}.
diff --git a/api/Android.bp b/api/Android.bp
index a3e64a5..b0ce9af 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -99,7 +99,6 @@
         "framework-connectivity",
         "framework-connectivity-t",
         "framework-devicelock",
-        "framework-federatedcompute",
         "framework-graphics",
         "framework-healthconnect",
         "framework-media",
@@ -113,6 +112,7 @@
         "framework-sdksandbox",
         "framework-tethering",
         "framework-uwb",
+        "framework-virtualization",
         "framework-wifi",
         "i18n.module.public.api",
     ],
diff --git a/api/api.go b/api/api.go
index 6a6c493..c91ff81 100644
--- a/api/api.go
+++ b/api/api.go
@@ -27,8 +27,18 @@
 const art = "art.module.public.api"
 const conscrypt = "conscrypt.module.public.api"
 const i18n = "i18n.module.public.api"
+const virtualization = "framework-virtualization"
 
 var core_libraries_modules = []string{art, conscrypt, i18n}
+// List of modules that are not yet updatable, and hence they can still compile
+// against hidden APIs. These modules are filtered out when building the
+// updatable-framework-module-impl (because updatable-framework-module-impl is
+// built against module_current SDK). Instead they are directly statically
+// linked into the all-framework-module-lib, which is building against hidden
+// APIs.
+// In addition, the modules in this list are allowed to contribute to test APIs
+// stubs.
+var non_updatable_modules = []string{virtualization}
 
 // The intention behind this soong plugin is to generate a number of "merged"
 // API-related modules that would otherwise require a large amount of very
@@ -238,9 +248,33 @@
 }
 
 func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
+	// First create the all-updatable-modules-system-stubs
+	{
+		updatable_modules := removeAll(modules, non_updatable_modules)
+		props := libraryProps{}
+		props.Name = proptools.StringPtr("all-updatable-modules-system-stubs")
+		props.Static_libs = transformArray(updatable_modules, "", ".stubs.system")
+		props.Sdk_version = proptools.StringPtr("module_current")
+		props.Visibility = []string{"//frameworks/base"}
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+	// Now merge all-updatable-modules-system-stubs and stubs from non-updatable modules
+	// into all-modules-system-stubs.
+	{
+		props := libraryProps{}
+		props.Name = proptools.StringPtr("all-modules-system-stubs")
+		props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.system")
+		props.Static_libs = append(props.Static_libs, "all-updatable-modules-system-stubs")
+		props.Sdk_version = proptools.StringPtr("module_current")
+		props.Visibility = []string{"//frameworks/base"}
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+}
+
+func createMergedTestStubsForNonUpdatableModules(ctx android.LoadHookContext) {
 	props := libraryProps{}
-	props.Name = proptools.StringPtr("all-modules-system-stubs")
-	props.Static_libs = transformArray(modules, "", ".stubs.system")
+	props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs")
+	props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.test")
 	props.Sdk_version = proptools.StringPtr("module_current")
 	props.Visibility = []string{"//frameworks/base"}
 	ctx.CreateModule(java.LibraryFactory, &props)
@@ -249,12 +283,31 @@
 func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) {
 	// This module is for the "framework-all" module, which should not include the core libraries.
 	modules = removeAll(modules, core_libraries_modules)
-	props := libraryProps{}
-	props.Name = proptools.StringPtr("all-framework-module-impl")
-	props.Static_libs = transformArray(modules, "", ".impl")
-	props.Sdk_version = proptools.StringPtr("module_current")
-	props.Visibility = []string{"//frameworks/base"}
-	ctx.CreateModule(java.LibraryFactory, &props)
+	// Remove the modules that belong to non-updatable APEXes since those are allowed to compile
+	// against unstable APIs.
+	modules = removeAll(modules, non_updatable_modules)
+	// First create updatable-framework-module-impl, which contains all updatable modules.
+	// This module compiles against module_lib SDK.
+	{
+		props := libraryProps{}
+		props.Name = proptools.StringPtr("updatable-framework-module-impl")
+		props.Static_libs = transformArray(modules, "", ".impl")
+		props.Sdk_version = proptools.StringPtr("module_current")
+		props.Visibility = []string{"//frameworks/base"}
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+
+	// Now create all-framework-module-impl, which contains updatable-framework-module-impl
+	// and all non-updatable modules. This module compiles against hidden APIs.
+	{
+		props := libraryProps{}
+		props.Name = proptools.StringPtr("all-framework-module-impl")
+		props.Static_libs = transformArray(non_updatable_modules, "", ".impl")
+		props.Static_libs = append(props.Static_libs, "updatable-framework-module-impl")
+		props.Sdk_version = proptools.StringPtr("core_platform")
+		props.Visibility = []string{"//frameworks/base"}
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
 }
 
 func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) {
@@ -333,6 +386,7 @@
 
 	createMergedPublicStubs(ctx, bootclasspath)
 	createMergedSystemStubs(ctx, bootclasspath)
+	createMergedTestStubsForNonUpdatableModules(ctx)
 	createMergedFrameworkModuleLibStubs(ctx, bootclasspath)
 	createMergedFrameworkImpl(ctx, bootclasspath)
 
diff --git a/boot/Android.bp b/boot/Android.bp
index 7839918..c9a3bd0 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -76,10 +76,6 @@
             module: "com.android.devicelock-bootclasspath-fragment",
         },
         {
-            apex: "com.android.federatedcompute",
-            module: "com.android.federatedcompute-bootclasspath-fragment",
-        },
-        {
             apex: "com.android.healthconnect",
             module: "com.android.healthconnect-bootclasspath-fragment",
         },
@@ -136,6 +132,10 @@
             apex: "com.android.car.framework",
             module: "com.android.car.framework-bootclasspath-fragment",
         },
+        {
+            apex: "com.android.virt",
+            module: "com.android.virt-bootclasspath-fragment",
+        },
     ],
 
     // Additional information needed by hidden api processing.
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index d8b348e..528ce86 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -9358,8 +9358,8 @@
 android.widget.inline.InlinePresentationSpec$BaseBuilder
 android.widget.inline.InlinePresentationSpec$Builder
 android.widget.inline.InlinePresentationSpec
-android.window.BackEvent$1
-android.window.BackEvent
+android.window.BackMotionEvent$1
+android.window.BackMotionEvent
 android.window.ClientWindowFrames$1
 android.window.ClientWindowFrames
 android.window.CompatOnBackInvokedCallback
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 8be8cda..80512f7 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -130,14 +130,14 @@
     uniform sampler2D uTexture;
     uniform float uFade;
     uniform float uColorProgress;
-    uniform vec4 uStartColor0;
-    uniform vec4 uStartColor1;
-    uniform vec4 uStartColor2;
-    uniform vec4 uStartColor3;
-    uniform vec4 uEndColor0;
-    uniform vec4 uEndColor1;
-    uniform vec4 uEndColor2;
-    uniform vec4 uEndColor3;
+    uniform vec3 uStartColor0;
+    uniform vec3 uStartColor1;
+    uniform vec3 uStartColor2;
+    uniform vec3 uStartColor3;
+    uniform vec3 uEndColor0;
+    uniform vec3 uEndColor1;
+    uniform vec3 uEndColor2;
+    uniform vec3 uEndColor3;
     varying highp vec2 vUv;
     void main() {
         vec4 mask = texture2D(uTexture, vUv);
@@ -150,12 +150,12 @@
             * step(cWhiteMaskThreshold, g)
             * step(cWhiteMaskThreshold, b)
             * step(cWhiteMaskThreshold, a);
-        vec4 color = r * mix(uStartColor0, uEndColor0, uColorProgress)
+        vec3 color = r * mix(uStartColor0, uEndColor0, uColorProgress)
                 + g * mix(uStartColor1, uEndColor1, uColorProgress)
                 + b * mix(uStartColor2, uEndColor2, uColorProgress)
                 + a * mix(uStartColor3, uEndColor3, uColorProgress);
-        color = mix(color, vec4(vec3((r + g + b + a) * 0.25), 1.0), useWhiteMask);
-        gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade)) * color.a;
+        color = mix(color, vec3((r + g + b + a) * 0.25), useWhiteMask);
+        gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade));
     })";
 static const char IMAGE_FRAG_SHADER_SOURCE[] = R"(
     precision mediump float;
@@ -1112,6 +1112,11 @@
 
         int nextReadPos;
 
+        if (strlen(l) == 0) {
+            s = ++endl;
+            continue;
+        }
+
         int topLineNumbers = sscanf(l, "%d %d %d %d", &width, &height, &fps, &progress);
         if (topLineNumbers == 3 || topLineNumbers == 4) {
             // SLOGD("> w=%d, h=%d, fps=%d, progress=%d", width, height, fps, progress);
@@ -1439,12 +1444,12 @@
     for (int i = 0; i < DYNAMIC_COLOR_COUNT; i++) {
         float *startColor = mAnimation->startColors[i];
         float *endColor = mAnimation->endColors[i];
-        glUniform4f(glGetUniformLocation(mImageShader,
+        glUniform3f(glGetUniformLocation(mImageShader,
             (U_START_COLOR_PREFIX + std::to_string(i)).c_str()),
-            startColor[0], startColor[1], startColor[2], 1 /* alpha */);
-        glUniform4f(glGetUniformLocation(mImageShader,
+            startColor[0], startColor[1], startColor[2]);
+        glUniform3f(glGetUniformLocation(mImageShader,
             (U_END_COLOR_PREFIX + std::to_string(i)).c_str()),
-            endColor[0], endColor[1], endColor[2], 1 /* alpha */);
+            endColor[0], endColor[1], endColor[2]);
     }
     mImageColorProgressLocation = glGetUniformLocation(mImageShader, U_COLOR_PROGRESS);
 }
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 4f8faca..5f06c97 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -71,6 +71,7 @@
     host_supported: true,
     srcs: [
         "libidmap2/**/*.cpp",
+        "self_targeting/*.cpp",
     ],
     export_include_dirs: ["include"],
     target: {
@@ -222,6 +223,7 @@
     },
     data: [
         "tests/data/**/*.apk",
+        "tests/data/**/*.png",
     ],
     compile_multilib: "first",
     test_options: {
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 4431164..10947dc 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -39,6 +39,7 @@
 #include "idmap2/PrettyPrintVisitor.h"
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
+#include <fcntl.h>
 
 using android::base::StringPrintf;
 using android::binder::Status;
@@ -238,6 +239,9 @@
     if (res.dataType == Res_value::TYPE_STRING) {
       builder.SetResourceValue(res.resourceName, res.dataType, res.stringData.value(),
             res.configuration.value_or(std::string()));
+    } else if (res.binaryData.has_value()) {
+      builder.SetResourceValue(res.resourceName, res.binaryData->get(),
+            res.configuration.value_or(std::string()));
     } else {
       builder.SetResourceValue(res.resourceName, res.dataType, res.data,
             res.configuration.value_or(std::string()));
@@ -264,6 +268,7 @@
                              file_name.c_str(), kMaxFileNameLength));
     }
   } while (std::filesystem::exists(path));
+  builder.setFrroPath(path);
 
   const uid_t uid = IPCThreadState::self()->getCallingUid();
   if (!UidHasWriteAccessToPath(uid, path)) {
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index c773e11..3ad6d58 100644
--- a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -24,5 +24,6 @@
     int dataType;
     int data;
     @nullable @utf8InCpp String stringData;
+    @nullable ParcelFileDescriptor binaryData;
     @nullable @utf8InCpp String configuration;
 }
\ No newline at end of file
diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
index 5e189f2..7b38bd1 100644
--- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
+++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
@@ -39,7 +39,7 @@
   void Write8(uint8_t value);
   void Write16(uint16_t value);
   void Write32(uint32_t value);
-  void WriteString(const StringPiece& value);
+  void WriteString(StringPiece value);
   std::ostream& stream_;
 };
 
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index 05b0618..9f57710 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -28,6 +28,7 @@
 
 #include "idmap2/ResourceContainer.h"
 #include "idmap2/Result.h"
+#include <binder/ParcelFileDescriptor.h>
 
 namespace android::idmap2 {
 
@@ -45,6 +46,15 @@
                               const std::string& data_string_value,
                               const std::string& configuration);
 
+    Builder& SetResourceValue(const std::string& resource_name,
+                              std::optional<android::base::borrowed_fd>&& binary_value,
+                              const std::string& configuration);
+
+    inline Builder& setFrroPath(std::string frro_path) {
+      frro_path_ = std::move(frro_path);
+      return *this;
+    }
+
     WARN_UNUSED Result<FabricatedOverlay> Build();
 
    private:
@@ -53,6 +63,7 @@
       DataType data_type;
       DataValue data_value;
       std::string data_string_value;
+      std::optional<android::base::borrowed_fd> data_binary_value;
       std::string configuration;
     };
 
@@ -60,6 +71,7 @@
     std::string name_;
     std::string target_package_name_;
     std::string target_overlayable_;
+    std::string frro_path_;
     std::vector<Entry> entries_;
   };
 
@@ -79,10 +91,14 @@
 
   explicit FabricatedOverlay(pb::FabricatedOverlay&& overlay,
                              std::string&& string_pool_data_,
+                             std::vector<android::base::borrowed_fd> binary_files_,
+                             off_t total_binary_bytes_,
                              std::optional<uint32_t> crc_from_disk = {});
 
   pb::FabricatedOverlay overlay_pb_;
   std::string string_pool_data_;
+  std::vector<android::base::borrowed_fd> binary_files_;
+  uint32_t total_binary_bytes_;
   std::optional<uint32_t> crc_from_disk_;
   mutable std::optional<SerializedData> data_;
 
diff --git a/cmds/idmap2/include/idmap2/ResourceContainer.h b/cmds/idmap2/include/idmap2/ResourceContainer.h
index 2452ff0..4d28321 100644
--- a/cmds/idmap2/include/idmap2/ResourceContainer.h
+++ b/cmds/idmap2/include/idmap2/ResourceContainer.h
@@ -46,14 +46,6 @@
   ~TargetResourceContainer() override = default;
 };
 
-struct OverlayManifestInfo {
-  std::string package_name;     // NOLINT(misc-non-private-member-variables-in-classes)
-  std::string name;             // NOLINT(misc-non-private-member-variables-in-classes)
-  std::string target_package;   // NOLINT(misc-non-private-member-variables-in-classes)
-  std::string target_name;      // NOLINT(misc-non-private-member-variables-in-classes)
-  ResourceId resource_mapping;  // NOLINT(misc-non-private-member-variables-in-classes)
-};
-
 struct OverlayData {
   struct ResourceIdValue {
     // The overlay resource id.
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index af4dd89..c2b0abe 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -19,6 +19,7 @@
 
 #include <optional>
 #include <string>
+#include <android-base/unique_fd.h>
 
 #include "androidfw/AssetManager2.h"
 #include "idmap2/Result.h"
@@ -29,18 +30,19 @@
 #define EXTRACT_ENTRY(resid) (0x0000ffff & (resid))
 
 // use typedefs to let the compiler warn us about implicit casts
-using ResourceId = uint32_t;  // 0xpptteeee
+using ResourceId = android::ResourceId;  // 0xpptteeee
 using PackageId = uint8_t;    // pp in 0xpptteeee
 using TypeId = uint8_t;       // tt in 0xpptteeee
 using EntryId = uint16_t;     // eeee in 0xpptteeee
 
-using DataType = uint8_t;    // Res_value::dataType
-using DataValue = uint32_t;  // Res_value::data
+using DataType = android::DataType;    // Res_value::dataType
+using DataValue = android::DataValue;  // Res_value::data
 
 struct TargetValue {
   DataType data_type;
   DataValue data_value;
   std::string data_string_value;
+  std::optional<android::base::borrowed_fd> data_binary_value;
 };
 
 struct TargetValueWithConfig {
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
index 4b271a1..8976924 100644
--- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -38,7 +38,7 @@
   stream_.write(reinterpret_cast<char*>(&x), sizeof(uint32_t));
 }
 
-void BinaryStreamVisitor::WriteString(const StringPiece& value) {
+void BinaryStreamVisitor::WriteString(StringPiece value) {
   // pad with null to nearest word boundary;
   size_t padding_size = CalculatePadding(value.size());
   Write32(value.size());
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index bde9b0b..dd5be21c 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -16,6 +16,10 @@
 
 #include "idmap2/FabricatedOverlay.h"
 
+#include <sys/stat.h>   // umask
+#include <sys/types.h>  // umask
+
+#include <android-base/file.h>
 #include <androidfw/ResourceUtils.h>
 #include <androidfw/StringPool.h>
 #include <google/protobuf/io/coded_stream.h>
@@ -51,9 +55,13 @@
 
 FabricatedOverlay::FabricatedOverlay(pb::FabricatedOverlay&& overlay,
                                      std::string&& string_pool_data,
+                                     std::vector<android::base::borrowed_fd> binary_files,
+                                     off_t total_binary_bytes,
                                      std::optional<uint32_t> crc_from_disk)
     : overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)),
     string_pool_data_(std::move(string_pool_data)),
+    binary_files_(std::move(binary_files)),
+    total_binary_bytes_(total_binary_bytes),
     crc_from_disk_(crc_from_disk) {
 }
 
@@ -72,22 +80,31 @@
 FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
     const std::string& resource_name, uint8_t data_type, uint32_t data_value,
     const std::string& configuration) {
-  entries_.emplace_back(Entry{resource_name, data_type, data_value, "", configuration});
+  entries_.emplace_back(
+      Entry{resource_name, data_type, data_value, "", std::nullopt, configuration});
   return *this;
 }
 
 FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
     const std::string& resource_name, uint8_t data_type, const std::string& data_string_value,
     const std::string& configuration) {
-  entries_.emplace_back(Entry{resource_name, data_type, 0, data_string_value, configuration});
+  entries_.emplace_back(
+      Entry{resource_name, data_type, 0, data_string_value, std::nullopt, configuration});
+  return *this;
+}
+
+FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
+    const std::string& resource_name, std::optional<android::base::borrowed_fd>&& binary_value,
+    const std::string& configuration) {
+  entries_.emplace_back(Entry{resource_name, 0, 0, "", binary_value, configuration});
   return *this;
 }
 
 Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
-  using ConfigMap = std::map<std::string, TargetValue>;
-  using EntryMap = std::map<std::string, ConfigMap>;
-  using TypeMap = std::map<std::string, EntryMap>;
-  using PackageMap = std::map<std::string, TypeMap>;
+  using ConfigMap = std::map<std::string, TargetValue, std::less<>>;
+  using EntryMap = std::map<std::string, ConfigMap, std::less<>>;
+  using TypeMap = std::map<std::string, EntryMap, std::less<>>;
+  using PackageMap = std::map<std::string, TypeMap, std::less<>>;
   PackageMap package_map;
   android::StringPool string_pool;
   for (const auto& res_entry : entries_) {
@@ -99,8 +116,7 @@
       return Error("failed to parse resource name '%s'", res_entry.resource_name.c_str());
     }
 
-    std::string package_name =
-        package_substr.empty() ? target_package_name_ : package_substr.to_string();
+    std::string_view package_name = package_substr.empty() ? target_package_name_ : package_substr;
     if (type_name.empty()) {
       return Error("resource name '%s' missing type name", res_entry.resource_name.c_str());
     }
@@ -116,17 +132,14 @@
                     .first;
     }
 
-    auto type = package->second.find(type_name.to_string());
+    auto type = package->second.find(type_name);
     if (type == package->second.end()) {
-      type =
-          package->second
-              .insert(std::make_pair(type_name.to_string(), EntryMap()))
-              .first;
+      type = package->second.insert(std::make_pair(type_name, EntryMap())).first;
     }
 
-    auto entry = type->second.find(entry_name.to_string());
+    auto entry = type->second.find(entry_name);
     if (entry == type->second.end()) {
-      entry = type->second.insert(std::make_pair(entry_name.to_string(), ConfigMap())).first;
+      entry = type->second.insert(std::make_pair(entry_name, ConfigMap())).first;
     }
 
     auto value = entry->second.find(res_entry.configuration);
@@ -135,7 +148,7 @@
     }
 
     value->second = TargetValue{res_entry.data_type, res_entry.data_value,
-        res_entry.data_string_value};
+        res_entry.data_string_value, res_entry.data_binary_value};
   }
 
   pb::FabricatedOverlay overlay_pb;
@@ -144,6 +157,11 @@
   overlay_pb.set_target_package_name(target_package_name_);
   overlay_pb.set_target_overlayable(target_overlayable_);
 
+  std::vector<android::base::borrowed_fd> binary_files;
+  size_t total_binary_bytes = 0;
+  // 16 for the number of bytes in the frro file before the binary data
+  const size_t FRRO_HEADER_SIZE = 16;
+
   for (auto& package : package_map) {
     auto package_pb = overlay_pb.add_packages();
     package_pb->set_name(package.first);
@@ -162,6 +180,20 @@
           if (value.second.data_type == Res_value::TYPE_STRING) {
             auto ref = string_pool.MakeRef(value.second.data_string_value);
             pb_value->set_data_value(ref.index());
+          } else if (value.second.data_binary_value.has_value()) {
+              pb_value->set_data_type(Res_value::TYPE_STRING);
+              struct stat s;
+              if (fstat(value.second.data_binary_value->get(), &s) == -1) {
+                return Error("unable to get size of binary file: %d", errno);
+              }
+              std::string uri
+                  = StringPrintf("frro:/%s?offset=%d&size=%d", frro_path_.c_str(),
+                                 static_cast<int> (FRRO_HEADER_SIZE + total_binary_bytes),
+                                 static_cast<int> (s.st_size));
+              total_binary_bytes += s.st_size;
+              binary_files.emplace_back(value.second.data_binary_value->get());
+              auto ref = string_pool.MakeRef(std::move(uri));
+              pb_value->set_data_value(ref.index());
           } else {
             pb_value->set_data_value(value.second.data_value);
           }
@@ -169,10 +201,10 @@
       }
     }
   }
-
   android::BigBuffer string_buffer(kBufferSize);
   android::StringPool::FlattenUtf8(&string_buffer, string_pool, nullptr);
-  return FabricatedOverlay(std::move(overlay_pb), string_buffer.to_string());
+  return FabricatedOverlay(std::move(overlay_pb), string_buffer.to_string(),
+      std::move(binary_files), total_binary_bytes);
 }
 
 Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stream) {
@@ -190,7 +222,7 @@
     return Error("Failed to read fabricated overlay version.");
   }
 
-  if (version != 1 && version != 2) {
+  if (version < 1 || version > 3) {
     return Error("Invalid fabricated overlay version '%u'.", version);
   }
 
@@ -201,7 +233,14 @@
 
   pb::FabricatedOverlay overlay{};
   std::string sp_data;
-  if (version == 2) {
+  uint32_t total_binary_bytes;
+  if (version == 3) {
+    if (!Read32(stream, &total_binary_bytes)) {
+      return Error("Failed read total binary bytes.");
+    }
+    stream.seekg(total_binary_bytes, std::istream::cur);
+  }
+  if (version >= 2) {
     uint32_t sp_size;
     if (!Read32(stream, &sp_size)) {
       return Error("Failed read string pool size.");
@@ -211,20 +250,15 @@
       return Error("Failed to read string pool.");
     }
     sp_data = buf;
-
-    if (!overlay.ParseFromIstream(&stream)) {
-      return Error("Failed read fabricated overlay proto.");
-    }
-  } else {
-    if (!overlay.ParseFromIstream(&stream)) {
-      return Error("Failed read fabricated overlay proto.");
-    }
+  }
+  if (!overlay.ParseFromIstream(&stream)) {
+    return Error("Failed read fabricated overlay proto.");
   }
 
   // If the proto version is the latest version, then the contents of the proto must be the same
   // when the proto is re-serialized; otherwise, the crc must be calculated because migrating the
   // proto to the latest version will likely change the contents of the fabricated overlay.
-  return FabricatedOverlay(std::move(overlay), std::move(sp_data),
+  return FabricatedOverlay(std::move(overlay), std::move(sp_data), {}, total_binary_bytes,
                            version == kFabricatedOverlayCurrentVersion
                                                    ? std::optional<uint32_t>(crc)
                                                    : std::nullopt);
@@ -274,6 +308,14 @@
   Write32(stream, kFabricatedOverlayMagic);
   Write32(stream, kFabricatedOverlayCurrentVersion);
   Write32(stream, (*data)->pb_crc);
+  Write32(stream, total_binary_bytes_);
+  std::string file_contents;
+  for (const android::base::borrowed_fd fd : binary_files_) {
+    if (!ReadFdToString(fd, &file_contents)) {
+      return Error("Failed to read binary file data.");
+    }
+    stream.write(file_contents.data(), file_contents.length());
+  }
   Write32(stream, (*data)->sp_data.length());
   stream.write((*data)->sp_data.data(), (*data)->sp_data.length());
   if (stream.bad()) {
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 813dff1..7c0b937 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -317,7 +317,7 @@
   }
 
   std::unique_ptr<IdmapData> data(new IdmapData());
-  data->string_pool_data_ = resource_mapping.GetStringPoolData().to_string();
+  data->string_pool_data_ = std::string(resource_mapping.GetStringPoolData());
   uint32_t inline_value_count = 0;
   std::set<std::string> config_set;
   for (const auto& mapping : resource_mapping.GetTargetToOverlayMap()) {
diff --git a/cmds/idmap2/libidmap2/PolicyUtils.cpp b/cmds/idmap2/libidmap2/PolicyUtils.cpp
index 4e3f54d2..76c70ca 100644
--- a/cmds/idmap2/libidmap2/PolicyUtils.cpp
+++ b/cmds/idmap2/libidmap2/PolicyUtils.cpp
@@ -53,7 +53,7 @@
 
   for (const auto& policy : kPolicyStringToFlag) {
     if ((bitmask & policy.second) != 0) {
-      policies.emplace_back(policy.first.to_string());
+      policies.emplace_back(policy.first);
     }
   }
 
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index bb31c11..b2300ce 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -89,7 +89,7 @@
     // If the overlay supplies a target overlayable name, the resource must belong to the
     // overlayable defined with the specified name to be overlaid.
     return Error(R"(<overlay> android:targetName "%s" does not match overlayable name "%s")",
-                 overlay_info.target_name.c_str(), (*overlayable_info)->name.c_str());
+                 overlay_info.target_name.c_str(), (*overlayable_info)->name.data());
   }
 
   // Enforce policy restrictions if the resource is declared as overlayable.
diff --git a/cmds/idmap2/self_targeting/SelfTargeting.cpp b/cmds/idmap2/self_targeting/SelfTargeting.cpp
new file mode 100644
index 0000000..a8aa033
--- /dev/null
+++ b/cmds/idmap2/self_targeting/SelfTargeting.cpp
@@ -0,0 +1,205 @@
+/*
+ * 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 <sys/stat.h>
+
+#include <fstream>
+#include <optional>
+
+#define LOG_TAG "SelfTargeting"
+
+#include "androidfw/ResourceTypes.h"
+#include "idmap2/BinaryStreamVisitor.h"
+#include "idmap2/FabricatedOverlay.h"
+#include "idmap2/Idmap.h"
+#include "idmap2/Result.h"
+
+using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+using android::idmap2::BinaryStreamVisitor;
+using android::idmap2::Idmap;
+using android::idmap2::OverlayResourceContainer;
+
+namespace android::self_targeting {
+
+constexpr const mode_t kIdmapFilePermission = S_IRUSR | S_IWUSR;  // u=rw-, g=---, o=---
+
+extern "C" bool
+CreateFrroFile(std::string& out_err_result, const std::string& packageName,
+               const std::string& overlayName, const std::string& targetPackageName,
+               const std::optional<std::string>& targetOverlayable,
+               const std::vector<FabricatedOverlayEntryParameters>& entries_params,
+               const std::string& frro_file_path) {
+    android::idmap2::FabricatedOverlay::Builder builder(packageName, overlayName,
+                                                        targetPackageName);
+    if (targetOverlayable.has_value()) {
+        builder.SetOverlayable(targetOverlayable.value_or(std::string()));
+    }
+    for (const auto& entry_params : entries_params) {
+        const auto dataType = entry_params.data_type;
+        if (entry_params.data_binary_value.has_value()) {
+            builder.SetResourceValue(entry_params.resource_name, *entry_params.data_binary_value,
+                                     entry_params.configuration);
+        } else  if (dataType >= Res_value::TYPE_FIRST_INT && dataType <= Res_value::TYPE_LAST_INT) {
+           builder.SetResourceValue(entry_params.resource_name, dataType,
+                                    entry_params.data_value, entry_params.configuration);
+        } else if (dataType == Res_value::TYPE_STRING) {
+           builder.SetResourceValue(entry_params.resource_name, dataType,
+                                    entry_params.data_string_value , entry_params.configuration);
+        } else {
+            out_err_result = base::StringPrintf("Unsupported data type %d", dataType);
+            return false;
+        }
+    }
+
+    const auto frro = builder.Build();
+    std::ofstream fout(frro_file_path);
+    if (fout.fail()) {
+        out_err_result = base::StringPrintf("open output stream fail %s", std::strerror(errno));
+        return false;
+    }
+    auto result = frro->ToBinaryStream(fout);
+    if (!result) {
+        unlink(frro_file_path.c_str());
+        out_err_result = base::StringPrintf("to stream fail %s", result.GetErrorMessage().c_str());
+        return false;
+    }
+    fout.close();
+    if (fout.fail()) {
+        unlink(frro_file_path.c_str());
+        out_err_result = base::StringPrintf("output stream fail %s", std::strerror(errno));
+        return false;
+    }
+    if (chmod(frro_file_path.c_str(), kIdmapFilePermission) == -1) {
+        out_err_result = base::StringPrintf("Failed to change the file permission %s",
+                                            frro_file_path.c_str());
+        return false;
+    }
+    return true;
+}
+
+static PolicyBitmask GetFulfilledPolicy(const bool isSystem, const bool isVendor,
+                                        const bool isProduct, const bool isTargetSignature,
+                                        const bool isOdm, const bool isOem) {
+    auto fulfilled_policy = static_cast<PolicyBitmask>(PolicyFlags::PUBLIC);
+
+    if (isSystem) {
+        fulfilled_policy |= PolicyFlags::SYSTEM_PARTITION;
+    }
+    if (isVendor) {
+        fulfilled_policy |= PolicyFlags::VENDOR_PARTITION;
+    }
+    if (isProduct) {
+        fulfilled_policy |= PolicyFlags::PRODUCT_PARTITION;
+    }
+    if (isOdm) {
+        fulfilled_policy |= PolicyFlags::ODM_PARTITION;
+    }
+    if (isOem) {
+        fulfilled_policy |= PolicyFlags::OEM_PARTITION;
+    }
+    if (isTargetSignature) {
+        fulfilled_policy |= PolicyFlags::SIGNATURE;
+    }
+
+    // Not support actor_signature and config_overlay_signature
+    fulfilled_policy &=
+            ~(PolicyFlags::ACTOR_SIGNATURE | PolicyFlags::CONFIG_SIGNATURE);
+
+    ALOGV(
+            "fulfilled_policy = 0x%08x, isSystem = %d, isVendor = %d, isProduct = %d,"
+            " isTargetSignature = %d, isOdm = %d, isOem = %d,",
+            fulfilled_policy, isSystem, isVendor, isProduct, isTargetSignature, isOdm, isOem);
+    return fulfilled_policy;
+}
+
+extern "C" bool
+CreateIdmapFile(std::string& out_err, const std::string& targetPath, const std::string& overlayPath,
+                const std::string& idmapPath, const std::string& overlayName,
+                const bool isSystem, const bool isVendor, const bool isProduct,
+                const bool isTargetSignature, const bool isOdm, const bool isOem) {
+    // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap
+    // guarantees that existing memory maps will continue to be valid and unaffected. The file must
+    // be deleted before attempting to create the idmap, so that if idmap  creation fails, the
+    // overlay will no longer be usable.
+    unlink(idmapPath.c_str());
+
+    const auto target = idmap2::TargetResourceContainer::FromPath(targetPath);
+    if (!target) {
+        out_err = base::StringPrintf("Failed to load target %s because of %s", targetPath.c_str(),
+                                     target.GetErrorMessage().c_str());
+        return false;
+    }
+
+    const auto overlay = OverlayResourceContainer::FromPath(overlayPath);
+    if (!overlay) {
+        out_err = base::StringPrintf("Failed to load overlay %s because of %s", overlayPath.c_str(),
+                                     overlay.GetErrorMessage().c_str());
+        return false;
+    }
+
+    // Overlay self target process. Only allow self-targeting types.
+    const auto fulfilled_policies = GetFulfilledPolicy(isSystem, isVendor, isProduct,
+                                                       isTargetSignature, isOdm, isOem);
+
+    const auto idmap = Idmap::FromContainers(**target, **overlay, overlayName,
+                                             fulfilled_policies, true /* enforce_overlayable */);
+    if (!idmap) {
+        out_err = base::StringPrintf("Failed to create idmap because of %s",
+                                     idmap.GetErrorMessage().c_str());
+        return false;
+    }
+
+    std::ofstream fout(idmapPath.c_str());
+    if (fout.fail()) {
+        out_err = base::StringPrintf("Failed to create idmap %s because of %s", idmapPath.c_str(),
+                                     strerror(errno));
+        return false;
+    }
+
+    BinaryStreamVisitor visitor(fout);
+    (*idmap)->accept(&visitor);
+    fout.close();
+    if (fout.fail()) {
+        unlink(idmapPath.c_str());
+        out_err = base::StringPrintf("Failed to write idmap %s because of %s", idmapPath.c_str(),
+                                     strerror(errno));
+        return false;
+    }
+    if (chmod(idmapPath.c_str(), kIdmapFilePermission) == -1) {
+        out_err = base::StringPrintf("Failed to change the file permission %s", idmapPath.c_str());
+        return false;
+    }
+    return true;
+}
+
+extern "C" bool
+GetFabricatedOverlayInfo(std::string& out_err, const std::string& overlay_path,
+                         OverlayManifestInfo& out_info) {
+    const auto overlay = idmap2::FabricatedOverlayContainer::FromPath(overlay_path);
+    if (!overlay) {
+        out_err = base::StringPrintf("Failed to write idmap %s because of %s",
+                                     overlay_path.c_str(), strerror(errno));
+        return false;
+    }
+
+    out_info = (*overlay)->GetManifestInfo();
+
+    return true;
+}
+
+}  // namespace android::self_targeting
+
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
index e804c87..e13a0eb 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -17,6 +17,7 @@
 #include <android-base/file.h>
 #include <gtest/gtest.h>
 #include <idmap2/FabricatedOverlay.h>
+#include "TestHelpers.h"
 
 #include <fstream>
 #include <utility>
@@ -41,6 +42,10 @@
 }
 
 TEST(FabricatedOverlayTests, SetResourceValue) {
+  auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+  auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+  ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
+
   auto overlay =
       FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
           .SetResourceValue(
@@ -54,6 +59,8 @@
               Res_value::TYPE_STRING,
               "foobar",
               "en-rUS-normal-xxhdpi-v21")
+          .SetResourceValue("com.example.target:drawable/dr1", fd, "port-xxhdpi-v7")
+          .setFrroPath("/foo/bar/biz.frro")
           .Build();
   ASSERT_TRUE(overlay);
   auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
@@ -67,19 +74,28 @@
 
   auto pairs = container->GetOverlayData(*info);
   ASSERT_TRUE(pairs);
-  ASSERT_EQ(4U, pairs->pairs.size());
+  ASSERT_EQ(5U, pairs->pairs.size());
   auto string_pool = ResStringPool(pairs->string_pool_data->data.get(),
                                         pairs->string_pool_data->data_length, false);
 
   auto& it = pairs->pairs[0];
-  ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+  ASSERT_EQ("com.example.target:drawable/dr1", it.resource_name);
   auto entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
+  ASSERT_EQ(std::string("frro://foo/bar/biz.frro?offset=16&size=8341"),
+      string_pool.string8At(entry->value.data_value).value_or(""));
+  ASSERT_EQ(Res_value::TYPE_STRING, entry->value.data_type);
+  ASSERT_EQ("port-xxhdpi-v7", entry->config);
+
+  it = pairs->pairs[1];
+  ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+  entry = std::get_if<TargetValueWithConfig>(&it.value);
+  ASSERT_NE(nullptr, entry);
   ASSERT_EQ(1U, entry->value.data_value);
   ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->value.data_type);
   ASSERT_EQ("port", entry->config);
 
-  it = pairs->pairs[1];
+  it = pairs->pairs[2];
   ASSERT_EQ("com.example.target:string/int3", it.resource_name);
   entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
@@ -87,7 +103,7 @@
   ASSERT_EQ(Res_value::TYPE_REFERENCE, entry->value.data_type);
   ASSERT_EQ("xxhdpi-v7", entry->config);
 
-  it = pairs->pairs[2];
+  it = pairs->pairs[3];
   ASSERT_EQ("com.example.target:string/string1", it.resource_name);
   entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
@@ -95,7 +111,7 @@
   ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->value.data_value).value_or(""));
   ASSERT_EQ("en-rUS-normal-xxhdpi-v21", entry->config);
 
-  it = pairs->pairs[3];
+  it = pairs->pairs[4];
   ASSERT_EQ("com.example.target.split:integer/int2", it.resource_name);
   entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 7b7dc17..b473f26 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -260,11 +260,17 @@
   auto target = TargetResourceContainer::FromPath(target_apk_path);
   ASSERT_TRUE(target);
 
+  auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+  auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+  ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
+
   auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
                   .SetOverlayable("TestResources")
                   .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "land-xxhdpi-v7")
                   .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "land")
                   .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "xxhdpi-v7")
+                  .SetResourceValue("drawable/dr1", fd, "port-xxhdpi-v7")
+                  .setFrroPath("/foo/bar/biz.frro")
                   .Build();
 
   ASSERT_TRUE(frro);
@@ -293,14 +299,19 @@
   auto string_pool_data = data->GetStringPoolData();
   auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
 
+  std::u16string expected_uri = u"frro://foo/bar/biz.frro?offset=16&size=8341";
+  uint32_t uri_index
+      = string_pool.indexOfString(expected_uri.data(), expected_uri.length()).value_or(-1);
 
   const auto& target_inline_entries = data->GetTargetInlineEntries();
-  ASSERT_EQ(target_inline_entries.size(), 3U);
-  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1, "land-xxhdpi-v7",
+  ASSERT_EQ(target_inline_entries.size(), 4U);
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::drawable::dr1, "port-xxhdpi-v7",
+                             Res_value::TYPE_STRING, uri_index);
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::integer::int1, "land-xxhdpi-v7",
                              Res_value::TYPE_INT_DEC, 2U);
-  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1, "land",
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str1, "land",
                              Res_value::TYPE_REFERENCE, 0x7f010000);
-  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str2, "xxhdpi-v7",
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[3], R::target::string::str2, "xxhdpi-v7",
                              Res_value::TYPE_STRING,
                              (uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1));
 }
diff --git a/cmds/idmap2/tests/R.h b/cmds/idmap2/tests/R.h
index ad998b9..80c062d 100644
--- a/cmds/idmap2/tests/R.h
+++ b/cmds/idmap2/tests/R.h
@@ -26,24 +26,27 @@
 // clang-format off
 namespace R::target {
   namespace integer {  // NOLINT(runtime/indentation_namespace)
-    constexpr ResourceId int1 = 0x7f010000;
+    constexpr ResourceId int1 = 0x7f020000;
+  }
+  namespace drawable {
+    constexpr ResourceId dr1 = 0x7f010000;
   }
   namespace string {  // NOLINT(runtime/indentation_namespace)
-    constexpr ResourceId not_overlayable = 0x7f020003;
-    constexpr ResourceId other = 0x7f020004;
-    constexpr ResourceId policy_actor = 0x7f020005;
-    constexpr ResourceId policy_config_signature = 0x7f020006;
-    constexpr ResourceId policy_odm = 0x7f020007;
-    constexpr ResourceId policy_oem = 0x7f020008;
-    constexpr ResourceId policy_product = 0x7f020009;
-    constexpr ResourceId policy_public = 0x7f02000a;
-    constexpr ResourceId policy_signature = 0x7f02000b;
-    constexpr ResourceId policy_system = 0x7f02000c;
-    constexpr ResourceId policy_system_vendor = 0x7f02000d;
-    constexpr ResourceId str1 = 0x7f02000e;
-    constexpr ResourceId str2 = 0x7f02000f;
-    constexpr ResourceId str3 = 0x7f020010;
-    constexpr ResourceId str4 = 0x7f020011;
+    constexpr ResourceId not_overlayable = 0x7f030003;
+    constexpr ResourceId other = 0x7f030004;
+    constexpr ResourceId policy_actor = 0x7f030005;
+    constexpr ResourceId policy_config_signature = 0x7f030006;
+    constexpr ResourceId policy_odm = 0x7f030007;
+    constexpr ResourceId policy_oem = 0x7f030008;
+    constexpr ResourceId policy_product = 0x7f030009;
+    constexpr ResourceId policy_public = 0x7f03000a;
+    constexpr ResourceId policy_signature = 0x7f03000b;
+    constexpr ResourceId policy_system = 0x7f03000c;
+    constexpr ResourceId policy_system_vendor = 0x7f03000d;
+    constexpr ResourceId str1 = 0x7f03000e;
+    constexpr ResourceId str2 = 0x7f03000f;
+    constexpr ResourceId str3 = 0x7f030010;
+    constexpr ResourceId str4 = 0x7f030011;
   }  // namespace string
 }  // namespace R::target
 
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index 7112eeb..68164e2 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -79,22 +79,22 @@
   ASSERT_CONTAINS_REGEX(ADDRESS "00000000  config count", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000004  overlay entry count", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "0000000a  string pool index offset", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  target id: integer/int1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  target id: integer/int1", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  overlay id: integer/int1", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f02000e  target id: string/str1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f03000e  target id: string/str1", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000b  overlay id: string/str1", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f020010  target id: string/str3", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f030010  target id: string/str3", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000c  overlay id: string/str3", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f020011  target id: string/str4", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f030011  target id: string/str4", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000d  overlay id: string/str4", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  overlay id: integer/int1", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f010000  target id: integer/int1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  target id: integer/int1", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000b  overlay id: string/str1", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f02000e  target id: string/str1", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f03000e  target id: string/str1", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000c  overlay id: string/str3", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f020010  target id: string/str3", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f030010  target id: string/str3", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f02000d  overlay id: string/str4", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "7f020011  target id: string/str4", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "7f030011  target id: string/str4", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "000000b4  string pool size", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "........  string pool", stream.str());
 }
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 016d427..380e462 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -23,6 +23,7 @@
 #include <memory>
 #include <string>
 
+#include <fcntl.h>
 #include "R.h"
 #include "TestConstants.h"
 #include "TestHelpers.h"
@@ -76,7 +77,12 @@
   auto target_map = mapping.GetTargetToOverlayMap();
   auto entry_map = target_map.find(target_resource);
   if (entry_map == target_map.end()) {
-    return Error("Failed to find mapping for target resource");
+    std::string keys;
+    for (const auto &pair : target_map) {
+      keys.append(fmt::format("0x{:x}", pair.first)).append(" ");
+    }
+    return Error(R"(Failed to find mapping for target resource "0x%02x": "%s")",
+        target_resource, keys.c_str());
   }
 
   auto actual_overlay_resource = std::get_if<ResourceId>(&entry_map->second);
@@ -108,7 +114,12 @@
   auto target_map = mapping.GetTargetToOverlayMap();
   auto entry_map = target_map.find(target_resource);
   if (entry_map == target_map.end()) {
-    return Error("Failed to find mapping for target resource");
+    std::string keys;
+    for (const auto &pair : target_map) {
+      keys.append(fmt::format("{:x}", pair.first)).append(" ");
+    }
+    return Error(R"(Failed to find mapping for target resource "0x%02x": "%s")",
+        target_resource, keys.c_str());
   }
 
   auto config_map = std::get_if<ConfigMap>(&entry_map->second);
@@ -193,11 +204,16 @@
 }
 
 TEST(ResourceMappingTests, FabricatedOverlay) {
+  auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+  auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+  ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
   auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
                   .SetOverlayable("TestResources")
                   .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "")
                   .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "")
                   .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "")
+                  .SetResourceValue("drawable/dr1", fd, "")
+                  .setFrroPath("/foo/bar/biz.frro")
                   .Build();
 
   ASSERT_TRUE(frro);
@@ -214,11 +230,16 @@
   auto string_pool_data = res.GetStringPoolData();
   auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
 
-  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
+  std::u16string expected_uri = u"frro://foo/bar/biz.frro?offset=16&size=8341";
+  uint32_t uri_index
+      = string_pool.indexOfString(expected_uri.data(), expected_uri.length()).value_or(-1);
+
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 4U);
   ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U);
   ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, 0x7f010000));
   ASSERT_RESULT(MappingExists(res, R::target::string::str2, Res_value::TYPE_STRING,
                               (uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1)));
+  ASSERT_RESULT(MappingExists(res, R::target::drawable::dr1, Res_value::TYPE_STRING, uri_index));
   ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 2U));
 }
 
diff --git a/cmds/idmap2/tests/TestConstants.h b/cmds/idmap2/tests/TestConstants.h
index d5799ad..794d622 100644
--- a/cmds/idmap2/tests/TestConstants.h
+++ b/cmds/idmap2/tests/TestConstants.h
@@ -19,8 +19,8 @@
 
 namespace android::idmap2::TestConstants {
 
-constexpr const auto TARGET_CRC = 0x7c2d4719;
-constexpr const auto TARGET_CRC_STRING = "7c2d4719";
+constexpr const auto TARGET_CRC = 0xa960a69;
+constexpr const auto TARGET_CRC_STRING = "0a960a69";
 
 constexpr const auto OVERLAY_CRC = 0xb71095cf;
 constexpr const auto OVERLAY_CRC_STRING = "b71095cf";
diff --git a/cmds/idmap2/tests/data/overlay/res/drawable/android.png b/cmds/idmap2/tests/data/overlay/res/drawable/android.png
new file mode 100644
index 0000000..b7317b0
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/drawable/android.png
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/build b/cmds/idmap2/tests/data/target/build
index e6df742..cd13a7e 100755
--- a/cmds/idmap2/tests/data/target/build
+++ b/cmds/idmap2/tests/data/target/build
@@ -17,5 +17,7 @@
 rm compiled.flata
 
 aapt2 compile res/values/values.xml -o .
-aapt2 link --manifest AndroidManifest.xml -A assets -o target-no-overlayable.apk values_values.arsc.flat
-rm values_values.arsc.flat
\ No newline at end of file
+aapt2 compile res/drawable/dr1.png -o .
+aapt2 link --manifest AndroidManifest.xml -A assets -o target-no-overlayable.apk values_values.arsc.flat drawable_dr1.png.flat
+rm values_values.arsc.flat
+rm drawable_dr1.png.flat
diff --git a/cmds/idmap2/tests/data/target/res/drawable/dr1.png b/cmds/idmap2/tests/data/target/res/drawable/dr1.png
new file mode 100644
index 0000000..1a56e68
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/res/drawable/dr1.png
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/res/values/overlayable.xml b/cmds/idmap2/tests/data/target/res/values/overlayable.xml
index 57e6c43..aac9081 100644
--- a/cmds/idmap2/tests/data/target/res/values/overlayable.xml
+++ b/cmds/idmap2/tests/data/target/res/values/overlayable.xml
@@ -63,6 +63,7 @@
         <item type="string" name="y" />
         <item type="string" name="z" />
         <item type="integer" name="int1" />
+        <item type="drawable" name="dr1" />
     </policy>
 </overlayable>
 
diff --git a/cmds/idmap2/tests/data/target/target-no-overlayable.apk b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
index cc3491d..680eeb6 100644
--- a/cmds/idmap2/tests/data/target/target-no-overlayable.apk
+++ b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/target.apk b/cmds/idmap2/tests/data/target/target.apk
index 4a58c5e..145e737 100644
--- a/cmds/idmap2/tests/data/target/target.apk
+++ b/cmds/idmap2/tests/data/target/target.apk
Binary files differ
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index 7d80493..26e20f6 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -29,12 +29,18 @@
 import java.util.function.Consumer;
 import java.util.concurrent.Executor;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class UsbCommand extends Svc.Command {
     public UsbCommand() {
         super("usb");
     }
 
+    /**
+     * Counter for tracking UsbOperation operations.
+     */
+    private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
     @Override
     public String shortHelp() {
         return "Control Usb state";
@@ -92,8 +98,10 @@
 
             if ("setFunctions".equals(args[1])) {
                 try {
+                    int operationId = sUsbOperationCount.incrementAndGet();
+                    System.out.println("setCurrentFunctions opId:" + operationId);
                     usbMgr.setCurrentFunctions(UsbManager.usbFunctionsFromString(
-                            args.length >= 3 ? args[2] : ""));
+                            args.length >= 3 ? args[2] : ""), operationId);
                 } catch (RemoteException e) {
                     System.err.println("Error communicating with UsbManager: " + e);
                 }
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/config/preloaded-classes b/config/preloaded-classes
index f750249..fa60140 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -9389,8 +9389,8 @@
 android.widget.inline.InlinePresentationSpec$BaseBuilder
 android.widget.inline.InlinePresentationSpec$Builder
 android.widget.inline.InlinePresentationSpec
-android.window.BackEvent$1
-android.window.BackEvent
+android.window.BackMotionEvent$1
+android.window.BackMotionEvent
 android.window.ClientWindowFrames$1
 android.window.ClientWindowFrames
 android.window.CompatOnBackInvokedCallback
diff --git a/core/api/current.txt b/core/api/current.txt
index cfaacfe..434b60d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -84,12 +84,26 @@
     field public static final String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES";
     field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
     field public static final String DELIVER_COMPANION_MESSAGES = "android.permission.DELIVER_COMPANION_MESSAGES";
+    field public static final String DETECT_SCREEN_CAPTURE = "android.permission.DETECT_SCREEN_CAPTURE";
     field public static final String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
     field public static final String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
     field public static final String DUMP = "android.permission.DUMP";
     field public static final String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
     field public static final String FACTORY_TEST = "android.permission.FACTORY_TEST";
     field public static final String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE";
+    field public static final String FOREGROUND_SERVICE_CAMERA = "android.permission.FOREGROUND_SERVICE_CAMERA";
+    field public static final String FOREGROUND_SERVICE_CONNECTED_DEVICE = "android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE";
+    field public static final String FOREGROUND_SERVICE_DATA_SYNC = "android.permission.FOREGROUND_SERVICE_DATA_SYNC";
+    field public static final String FOREGROUND_SERVICE_FILE_MANAGEMENT = "android.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT";
+    field public static final String FOREGROUND_SERVICE_HEALTH = "android.permission.FOREGROUND_SERVICE_HEALTH";
+    field public static final String FOREGROUND_SERVICE_LOCATION = "android.permission.FOREGROUND_SERVICE_LOCATION";
+    field public static final String FOREGROUND_SERVICE_MEDIA_PLAYBACK = "android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK";
+    field public static final String FOREGROUND_SERVICE_MEDIA_PROJECTION = "android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION";
+    field public static final String FOREGROUND_SERVICE_MICROPHONE = "android.permission.FOREGROUND_SERVICE_MICROPHONE";
+    field public static final String FOREGROUND_SERVICE_PHONE_CALL = "android.permission.FOREGROUND_SERVICE_PHONE_CALL";
+    field public static final String FOREGROUND_SERVICE_REMOTE_MESSAGING = "android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING";
+    field public static final String FOREGROUND_SERVICE_SPECIAL_USE = "android.permission.FOREGROUND_SERVICE_SPECIAL_USE";
+    field public static final String FOREGROUND_SERVICE_SYSTEM_EXEMPTED = "android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED";
     field public static final String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
     field public static final String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
     field public static final String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
@@ -143,6 +157,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";
@@ -1027,6 +1042,7 @@
     field public static final int max = 16843062; // 0x1010136
     field public static final int maxAspectRatio = 16844128; // 0x1010560
     field public static final int maxButtonHeight = 16844029; // 0x10104fd
+    field public static final int maxConcurrentSessionsCount;
     field public static final int maxDate = 16843584; // 0x1010340
     field public static final int maxEms = 16843095; // 0x1010157
     field public static final int maxHeight = 16843040; // 0x1010120
@@ -1246,6 +1262,7 @@
     field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
     field public static final int required = 16843406; // 0x101028e
     field public static final int requiredAccountType = 16843734; // 0x10103d6
+    field public static final int requiredDisplayCategory;
     field public static final int requiredFeature = 16844116; // 0x1010554
     field public static final int requiredForAllUsers = 16843728; // 0x10103d0
     field public static final int requiredNotFeature = 16844117; // 0x1010555
@@ -1493,7 +1510,6 @@
     field public static final int targetCellWidth = 16844340; // 0x1010634
     field public static final int targetClass = 16842799; // 0x101002f
     field @Deprecated public static final int targetDescriptions = 16843680; // 0x10103a0
-    field public static final int targetDisplayCategory;
     field public static final int targetId = 16843740; // 0x10103dc
     field public static final int targetName = 16843853; // 0x101044d
     field public static final int targetPackage = 16842785; // 0x1010021
@@ -3073,6 +3089,7 @@
 
   public abstract class AccessibilityService extends android.app.Service {
     ctor public AccessibilityService();
+    method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl);
     method public boolean clearCache();
     method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);
     method public final void disableSelf();
@@ -3100,6 +3117,7 @@
     method public boolean onGesture(@NonNull android.accessibilityservice.AccessibilityGestureEvent);
     method public abstract void onInterrupt();
     method protected boolean onKeyEvent(android.view.KeyEvent);
+    method public void onMotionEvent(@NonNull android.view.MotionEvent);
     method protected void onServiceConnected();
     method public void onSystemActionsChanged();
     method public final boolean performGlobalAction(int);
@@ -3110,10 +3128,13 @@
     method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
     method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region);
     method public void takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
+    method public void takeScreenshotOfWindow(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
     field public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 1; // 0x1
     field public static final int ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT = 3; // 0x3
     field public static final int ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY = 4; // 0x4
+    field public static final int ERROR_TAKE_SCREENSHOT_INVALID_WINDOW = 5; // 0x5
     field public static final int ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS = 2; // 0x2
+    field public static final int ERROR_TAKE_SCREENSHOT_SECURE_WINDOW = 6; // 0x6
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
     field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
@@ -3254,6 +3275,7 @@
     method @Deprecated public String getDescription();
     method public String getId();
     method public int getInteractiveUiTimeoutMillis();
+    method public int getMotionEventSources();
     method public int getNonInteractiveUiTimeoutMillis();
     method public android.content.pm.ResolveInfo getResolveInfo();
     method public String getSettingsActivityName();
@@ -3263,6 +3285,7 @@
     method @Nullable public CharSequence loadIntro(@NonNull android.content.pm.PackageManager);
     method public CharSequence loadSummary(android.content.pm.PackageManager);
     method public void setInteractiveUiTimeoutMillis(@IntRange(from=0) int);
+    method public void setMotionEventSources(int);
     method public void setNonInteractiveUiTimeoutMillis(@IntRange(from=0) int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
@@ -4604,23 +4627,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
@@ -5272,6 +5296,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.ForegroundServiceStartNotAllowedException> CREATOR;
   }
 
+  public abstract class ForegroundServiceTypeException extends android.app.ServiceStartNotAllowedException {
+    ctor public ForegroundServiceTypeException(@NonNull String);
+  }
+
   @Deprecated public class Fragment implements android.content.ComponentCallbacks2 android.view.View.OnCreateContextMenuListener {
     ctor @Deprecated public Fragment();
     method @Deprecated public void dump(String, java.io.FileDescriptor, java.io.PrintWriter, String[]);
@@ -5586,6 +5614,7 @@
     method public int getGameMode();
     method public void setGameState(@NonNull android.app.GameState);
     field public static final int GAME_MODE_BATTERY = 3; // 0x3
+    field public static final int GAME_MODE_CUSTOM = 4; // 0x4
     field public static final int GAME_MODE_PERFORMANCE = 2; // 0x2
     field public static final int GAME_MODE_STANDARD = 1; // 0x1
     field public static final int GAME_MODE_UNSUPPORTED = 0; // 0x0
@@ -5711,6 +5740,13 @@
     method @Deprecated public void setIntentRedelivery(boolean);
   }
 
+  public final class InvalidForegroundServiceTypeException extends android.app.ForegroundServiceTypeException implements android.os.Parcelable {
+    ctor public InvalidForegroundServiceTypeException(@NonNull String);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.InvalidForegroundServiceTypeException> CREATOR;
+  }
+
   public class KeyguardManager {
     method @RequiresPermission(android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) public void addKeyguardLockedStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.KeyguardLockedStateListener);
     method @Deprecated public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence);
@@ -5865,6 +5901,13 @@
     method public void showDialog();
   }
 
+  public final class MissingForegroundServiceTypeException extends android.app.ForegroundServiceTypeException implements android.os.Parcelable {
+    ctor public MissingForegroundServiceTypeException(@NonNull String);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.MissingForegroundServiceTypeException> CREATOR;
+  }
+
   public class NativeActivity extends android.app.Activity implements android.view.InputQueue.Callback android.view.SurfaceHolder.Callback2 android.view.ViewTreeObserver.OnGlobalLayoutListener {
     ctor public NativeActivity();
     method public void onGlobalLayout();
@@ -6930,10 +6973,11 @@
     method @Deprecated public void onStart(android.content.Intent, int);
     method public int onStartCommand(android.content.Intent, int, int);
     method public void onTaskRemoved(android.content.Intent);
+    method public void onTimeout(int);
     method public void onTrimMemory(int);
     method public boolean onUnbind(android.content.Intent);
     method public final void startForeground(int, android.app.Notification);
-    method public final void startForeground(int, @NonNull android.app.Notification, int);
+    method public final void startForeground(int, @NonNull android.app.Notification, @RequiresPermission int);
     method @Deprecated public final void stopForeground(boolean);
     method public final void stopForeground(int);
     method public final void stopSelf();
@@ -7246,6 +7290,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.os.ParcelFileDescriptor getWallpaperFile(int);
     method public int getWallpaperId(int);
     method public android.app.WallpaperInfo getWallpaperInfo();
+    method @Nullable public android.app.WallpaperInfo getWallpaperInfo(int);
     method public boolean hasResourceWallpaper(@RawRes int);
     method public boolean isSetWallpaperAllowed();
     method public boolean isWallpaperSupported();
@@ -7666,6 +7711,7 @@
     method public boolean updateOverrideApn(@NonNull android.content.ComponentName, int, @NonNull android.telephony.data.ApnSetting);
     method public void wipeData(int);
     method public void wipeData(int, @NonNull CharSequence);
+    method public void wipeDevice(int);
     field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE";
     field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
@@ -8433,12 +8479,32 @@
 
   public abstract class JobScheduler {
     ctor public JobScheduler();
+    method public boolean canRunLongJobs();
     method public abstract void cancel(int);
     method public abstract void cancelAll();
     method public abstract int enqueue(@NonNull android.app.job.JobInfo, @NonNull android.app.job.JobWorkItem);
     method @NonNull public abstract java.util.List<android.app.job.JobInfo> getAllPendingJobs();
     method @Nullable public abstract android.app.job.JobInfo getPendingJob(int);
+    method public int getPendingJobReason(int);
     method public abstract int schedule(@NonNull android.app.job.JobInfo);
+    field public static final int PENDING_JOB_REASON_APP = 1; // 0x1
+    field public static final int PENDING_JOB_REASON_APP_STANDBY = 2; // 0x2
+    field public static final int PENDING_JOB_REASON_BACKGROUND_RESTRICTION = 3; // 0x3
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW = 4; // 0x4
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5; // 0x5
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6; // 0x6
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7; // 0x7
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9; // 0x9
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10; // 0xa
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW = 11; // 0xb
+    field public static final int PENDING_JOB_REASON_DEVICE_STATE = 12; // 0xc
+    field public static final int PENDING_JOB_REASON_EXECUTING = -1; // 0xffffffff
+    field public static final int PENDING_JOB_REASON_INVALID_JOB_ID = -2; // 0xfffffffe
+    field public static final int PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION = 13; // 0xd
+    field public static final int PENDING_JOB_REASON_QUOTA = 14; // 0xe
+    field public static final int PENDING_JOB_REASON_UNDEFINED = 0; // 0x0
+    field public static final int PENDING_JOB_REASON_USER = 15; // 0xf
     field public static final int RESULT_FAILURE = 0; // 0x0
     field public static final int RESULT_SUCCESS = 1; // 0x1
   }
@@ -8954,9 +9020,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();
@@ -9037,6 +9112,11 @@
     method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
     field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
     field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
+    field public static final int RESULT_CANCELED = 0; // 0x0
+    field public static final int RESULT_DISCOVERY_TIMEOUT = 2; // 0x2
+    field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
+    field public static final int RESULT_OK = -1; // 0xffffffff
+    field public static final int RESULT_USER_REJECTED = 1; // 0x1
   }
 
   public abstract static class CompanionDeviceManager.Callback {
@@ -10286,6 +10366,7 @@
     field @Deprecated @RequiresPermission("android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS") public static final String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
     field public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
     field public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
+    field public static final String ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE";
     field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
     field public static final String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT";
     field public static final String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED";
@@ -10381,6 +10462,7 @@
     field public static final String ACTION_SEND_MULTIPLE = "android.intent.action.SEND_MULTIPLE";
     field public static final String ACTION_SET_WALLPAPER = "android.intent.action.SET_WALLPAPER";
     field public static final String ACTION_SHOW_APP_INFO = "android.intent.action.SHOW_APP_INFO";
+    field public static final String ACTION_SHOW_OUTPUT_SWITCHER = "android.intent.action.SHOW_OUTPUT_SWITCHER";
     field public static final String ACTION_SHOW_WORK_APPS = "android.intent.action.SHOW_WORK_APPS";
     field public static final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN";
     field public static final String ACTION_SYNC = "android.intent.action.SYNC";
@@ -10533,6 +10615,7 @@
     field public static final String EXTRA_UID = "android.intent.extra.UID";
     field public static final String EXTRA_USER = "android.intent.extra.USER";
     field public static final String EXTRA_USER_INITIATED = "android.intent.extra.USER_INITIATED";
+    field public static final String EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE";
     field public static final int FILL_IN_ACTION = 1; // 0x1
     field public static final int FILL_IN_CATEGORIES = 4; // 0x4
     field public static final int FILL_IN_CLIP_DATA = 128; // 0x80
@@ -11143,10 +11226,10 @@
     field public String parentActivityName;
     field public String permission;
     field public int persistableMode;
+    field @Nullable public String requiredDisplayCategory;
     field public int screenOrientation;
     field public int softInputMode;
     field public String targetActivity;
-    field @Nullable public String targetDisplayCategory;
     field public String taskAffinity;
     field public int theme;
     field public int uiOptions;
@@ -11607,6 +11690,7 @@
 
   public class PackageInstaller {
     method public void abandonSession(int);
+    method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
     method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
     method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession();
     method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions();
@@ -11651,10 +11735,39 @@
     field public static final int STATUS_SUCCESS = 0; // 0x0
   }
 
+  public static final class PackageInstaller.InstallConstraints implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean isRequireAppNotForeground();
+    method public boolean isRequireAppNotInteracting();
+    method public boolean isRequireAppNotTopVisible();
+    method public boolean isRequireDeviceIdle();
+    method public boolean isRequireNotInCall();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraints> CREATOR;
+    field @NonNull public static final android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE;
+  }
+
+  public static final class PackageInstaller.InstallConstraints.Builder {
+    ctor public PackageInstaller.InstallConstraints.Builder();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints build();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle();
+    method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall();
+  }
+
+  public static final class PackageInstaller.InstallConstraintsResult implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean isAllConstraintsSatisfied();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraintsResult> CREATOR;
+  }
+
   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);
@@ -11665,7 +11778,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);
   }
@@ -11679,6 +11792,7 @@
     method @NonNull public int[] getChildSessionIds();
     method @NonNull public String[] getNames() throws java.io.IOException;
     method public int getParentSessionId();
+    method public boolean isKeepApplicationEnabledSetting();
     method public boolean isMultiPackage();
     method public boolean isStaged();
     method @NonNull public java.io.InputStream openRead(@NonNull String) throws java.io.IOException;
@@ -11730,6 +11844,7 @@
     method public boolean hasParentSessionId();
     method public boolean isActive();
     method public boolean isCommitted();
+    method public boolean isKeepApplicationEnabledSetting();
     method public boolean isMultiPackage();
     method public boolean isSealed();
     method public boolean isStaged();
@@ -11762,6 +11877,7 @@
     method public void setInstallLocation(int);
     method public void setInstallReason(int);
     method public void setInstallScenario(int);
+    method public void setKeepApplicationEnabledSetting();
     method public void setMultiPackage();
     method public void setOriginatingUid(int);
     method public void setOriginatingUri(@Nullable android.net.Uri);
@@ -12097,6 +12213,7 @@
     field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND = "android.hardware.touchscreen.multitouch.jazzhand";
     field public static final String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
     field public static final String FEATURE_USB_HOST = "android.hardware.usb.host";
+    field public static final String FEATURE_UWB = "android.hardware.uwb";
     field public static final String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
     field public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking";
     field @Deprecated public static final String FEATURE_VR_MODE = "android.software.vr.mode";
@@ -12158,6 +12275,7 @@
     field public static final int PERMISSION_DENIED = -1; // 0xffffffff
     field public static final int PERMISSION_GRANTED = 0; // 0x0
     field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES";
+    field public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE = "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
     field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
     field public static final int SIGNATURE_MATCH = 0; // 0x0
     field public static final int SIGNATURE_NEITHER_SIGNED = 1; // 0x1
@@ -12371,16 +12489,22 @@
     field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
     field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
     field public static final int FLAG_USE_APP_ZYGOTE = 8; // 0x8
-    field public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
-    field public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
-    field public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
-    field public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CAMERA}, anyOf={android.Manifest.permission.CAMERA}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
+    field @Deprecated @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
+    field @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT) public static final int FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT = 4096; // 0x1000
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
     field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff
-    field public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
-    field public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 32; // 0x20
-    field public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128; // 0x80
-    field public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
-    field public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4
+    field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
+    field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 32; // 0x20
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_MICROPHONE}, anyOf={android.Manifest.permission.CAPTURE_AUDIO_OUTPUT, android.Manifest.permission.RECORD_AUDIO}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128; // 0x80
+    field @Deprecated public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL}, anyOf={android.Manifest.permission.MANAGE_OWN_CALLS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4
+    field @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING) public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 512; // 0x200
+    field public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 2048; // 0x800
+    field @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE) public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1073741824; // 0x40000000
+    field @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED) public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1024; // 0x400
     field public int flags;
     field public String permission;
   }
@@ -12920,11 +13044,21 @@
 
 package android.credentials {
 
-  public final class CreateCredentialRequest implements android.os.Parcelable {
-    ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle);
+  public final class ClearCredentialStateRequest implements android.os.Parcelable {
+    ctor public ClearCredentialStateRequest(@NonNull android.os.Bundle);
     method public int describeContents();
     method @NonNull public android.os.Bundle getData();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.ClearCredentialStateRequest> CREATOR;
+  }
+
+  public final class CreateCredentialRequest implements android.os.Parcelable {
+    ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
+    method public int describeContents();
+    method @NonNull public android.os.Bundle getCandidateQueryData();
+    method @NonNull public android.os.Bundle getCredentialData();
     method @NonNull public String getType();
+    method public boolean requireSystemProvider();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CreateCredentialRequest> CREATOR;
   }
@@ -12947,8 +13081,9 @@
   }
 
   public final class CredentialManager {
-    method public void executeCreateCredential(@NonNull android.credentials.CreateCredentialRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CredentialManagerException>);
-    method public void executeGetCredential(@NonNull android.credentials.GetCredentialRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.CredentialManagerException>);
+    method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.CredentialManagerException>);
+    method public void executeCreateCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CredentialManagerException>);
+    method public void executeGetCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.CredentialManagerException>);
   }
 
   public class CredentialManagerException extends java.lang.Exception {
@@ -12961,10 +13096,11 @@
   }
 
   public final class GetCredentialOption implements android.os.Parcelable {
-    ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle);
+    ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle, boolean);
     method public int describeContents();
     method @NonNull public android.os.Bundle getData();
     method @NonNull public String getType();
+    method public boolean requireSystemProvider();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialOption> CREATOR;
   }
@@ -15376,7 +15512,6 @@
   }
 
   public static class PathIterator.Segment {
-    ctor public PathIterator.Segment(@NonNull int, @NonNull float[], float);
     method public float getConicWeight();
     method @NonNull public float[] getPoints();
     method @NonNull public int getVerb();
@@ -16572,6 +16707,7 @@
     field public static final int FONT_WEIGHT_NORMAL = 400; // 0x190
     field public static final int FONT_WEIGHT_SEMI_BOLD = 600; // 0x258
     field public static final int FONT_WEIGHT_THIN = 100; // 0x64
+    field public static final int FONT_WEIGHT_UNSPECIFIED = -1; // 0xffffffff
   }
 
   public final class FontVariationAxis {
@@ -17596,6 +17732,7 @@
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Rational> CONTROL_AE_COMPENSATION_STEP;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> CONTROL_AE_LOCK_AVAILABLE;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AF_AVAILABLE_MODES;
+    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> CONTROL_AUTOFRAMING_AVAILABLE;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_EFFECTS;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.Capability[]> CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_CAPABILITIES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_MODES;
@@ -17759,6 +17896,7 @@
     method @NonNull public <T> java.util.List<android.util.Size> getExtensionSupportedSizes(int, @NonNull Class<T>);
     method @NonNull public java.util.List<android.util.Size> getExtensionSupportedSizes(int, int);
     method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions();
+    method public boolean isCaptureProcessProgressAvailable(int);
     field public static final int EXTENSION_AUTOMATIC = 0; // 0x0
     field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1
     field public static final int EXTENSION_BOKEH = 2; // 0x2
@@ -17778,6 +17916,7 @@
   public abstract static class CameraExtensionSession.ExtensionCaptureCallback {
     ctor public CameraExtensionSession.ExtensionCaptureCallback();
     method public void onCaptureFailed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest);
+    method public void onCaptureProcessProgressed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, @IntRange(from=0, to=100) int);
     method public void onCaptureProcessStarted(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest);
     method public void onCaptureResultAvailable(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, @NonNull android.hardware.camera2.TotalCaptureResult);
     method public void onCaptureSequenceAborted(@NonNull android.hardware.camera2.CameraExtensionSession, int);
@@ -17898,6 +18037,12 @@
     field public static final int CONTROL_AF_TRIGGER_CANCEL = 2; // 0x2
     field public static final int CONTROL_AF_TRIGGER_IDLE = 0; // 0x0
     field public static final int CONTROL_AF_TRIGGER_START = 1; // 0x1
+    field public static final int CONTROL_AUTOFRAMING_AUTO = 2; // 0x2
+    field public static final int CONTROL_AUTOFRAMING_OFF = 0; // 0x0
+    field public static final int CONTROL_AUTOFRAMING_ON = 1; // 0x1
+    field public static final int CONTROL_AUTOFRAMING_STATE_CONVERGED = 2; // 0x2
+    field public static final int CONTROL_AUTOFRAMING_STATE_FRAMING = 1; // 0x1
+    field public static final int CONTROL_AUTOFRAMING_STATE_INACTIVE = 0; // 0x0
     field public static final int CONTROL_AWB_MODE_AUTO = 1; // 0x1
     field public static final int CONTROL_AWB_MODE_CLOUDY_DAYLIGHT = 6; // 0x6
     field public static final int CONTROL_AWB_MODE_DAYLIGHT = 5; // 0x5
@@ -18145,6 +18290,7 @@
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AF_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AF_REGIONS;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AF_TRIGGER;
+    field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AUTOFRAMING;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> CONTROL_AWB_LOCK;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AWB_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
@@ -18235,6 +18381,8 @@
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AF_SCENE_CHANGE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AF_STATE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AF_TRIGGER;
+    field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AUTOFRAMING;
+    field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AUTOFRAMING_STATE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_AWB_LOCK;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AWB_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
@@ -18423,7 +18571,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
@@ -18438,7 +18586,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 {
@@ -19479,6 +19627,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();
@@ -19512,6 +19661,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);
@@ -19661,6 +19811,7 @@
     method public int describeContents();
     method @NonNull public android.location.GnssClock getClock();
     method @NonNull public java.util.Collection<android.location.GnssAutomaticGainControl> getGnssAutomaticGainControls();
+    method public boolean getIsFullTracking();
     method @NonNull public java.util.Collection<android.location.GnssMeasurement> getMeasurements();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssMeasurementsEvent> CREATOR;
@@ -19672,6 +19823,7 @@
     method @NonNull public android.location.GnssMeasurementsEvent build();
     method @NonNull public android.location.GnssMeasurementsEvent.Builder setClock(@NonNull android.location.GnssClock);
     method @NonNull public android.location.GnssMeasurementsEvent.Builder setGnssAutomaticGainControls(@NonNull java.util.Collection<android.location.GnssAutomaticGainControl>);
+    method @NonNull public android.location.GnssMeasurementsEvent.Builder setIsFullTracking(boolean);
     method @NonNull public android.location.GnssMeasurementsEvent.Builder setMeasurements(@NonNull java.util.Collection<android.location.GnssMeasurement>);
   }
 
@@ -19724,6 +19876,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);
@@ -20040,6 +20202,15 @@
 
 }
 
+package android.location.altitude {
+
+  public final class AltitudeConverter {
+    ctor public AltitudeConverter();
+    method @WorkerThread public void addMslAltitude(@NonNull android.content.Context, @NonNull android.location.Location) throws java.io.IOException;
+  }
+
+}
+
 package android.location.provider {
 
   public final class ProviderProperties implements android.os.Parcelable {
@@ -20190,6 +20361,7 @@
     field public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24; // 0x18
     field public static final int TYPE_BUS = 21; // 0x15
     field public static final int TYPE_DOCK = 13; // 0xd
+    field public static final int TYPE_DOCK_ANALOG = 31; // 0x1f
     field public static final int TYPE_FM = 14; // 0xe
     field public static final int TYPE_FM_TUNER = 16; // 0x10
     field public static final int TYPE_HDMI = 9; // 0x9
@@ -20316,7 +20488,10 @@
     field public static final int ENCODING_DRA = 28; // 0x1c
     field public static final int ENCODING_DTS = 7; // 0x7
     field public static final int ENCODING_DTS_HD = 8; // 0x8
-    field public static final int ENCODING_DTS_UHD = 27; // 0x1b
+    field public static final int ENCODING_DTS_HD_MA = 29; // 0x1d
+    field @Deprecated public static final int ENCODING_DTS_UHD = 27; // 0x1b
+    field public static final int ENCODING_DTS_UHD_P1 = 27; // 0x1b
+    field public static final int ENCODING_DTS_UHD_P2 = 30; // 0x1e
     field public static final int ENCODING_E_AC3 = 6; // 0x6
     field public static final int ENCODING_E_AC3_JOC = 18; // 0x12
     field public static final int ENCODING_IEC61937 = 13; // 0xd
@@ -20805,8 +20980,11 @@
     method public void onRoutingChanged(android.media.AudioRouting);
   }
 
-  public final class AudioTimestamp {
+  public final class AudioTimestamp implements android.os.Parcelable {
     ctor public AudioTimestamp();
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioTimestamp> CREATOR;
     field public static final int TIMEBASE_BOOTTIME = 1; // 0x1
     field public static final int TIMEBASE_MONOTONIC = 0; // 0x0
     field public long framePosition;
@@ -21871,6 +22049,11 @@
     field public static final int AVCProfileHigh422 = 32; // 0x20
     field public static final int AVCProfileHigh444 = 64; // 0x40
     field public static final int AVCProfileMain = 2; // 0x2
+    field public static final int DTS_HDProfileHRA = 1; // 0x1
+    field public static final int DTS_HDProfileLBR = 2; // 0x2
+    field public static final int DTS_HDProfileMA = 4; // 0x4
+    field public static final int DTS_UHDProfileP1 = 1; // 0x1
+    field public static final int DTS_UHDProfileP2 = 2; // 0x2
     field public static final int DolbyVisionLevel8k30 = 1024; // 0x400
     field public static final int DolbyVisionLevel8k60 = 2048; // 0x800
     field public static final int DolbyVisionLevelFhd24 = 4; // 0x4
@@ -23427,6 +23610,7 @@
     method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference);
     method public void registerTransferCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.TransferCallback);
     method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener);
+    method public void setRouteListingPreference(@Nullable android.media.RouteListingPreference);
     method public void stop();
     method public void transferTo(@NonNull android.media.MediaRoute2Info);
     method public void unregisterControllerCallback(@NonNull android.media.MediaRouter2.ControllerCallback);
@@ -23800,6 +23984,28 @@
     method @NonNull public android.media.RouteDiscoveryPreference.Builder setShouldPerformActiveScan(boolean);
   }
 
+  public final class RouteListingPreference implements android.os.Parcelable {
+    ctor public RouteListingPreference(@NonNull java.util.List<android.media.RouteListingPreference.Item>);
+    method public int describeContents();
+    method @NonNull public java.util.List<android.media.RouteListingPreference.Item> getItems();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference> CREATOR;
+  }
+
+  public static final class RouteListingPreference.Item implements android.os.Parcelable {
+    ctor public RouteListingPreference.Item(@NonNull String, int, int);
+    method public int describeContents();
+    method public int getDisableReason();
+    method public int getFlags();
+    method @NonNull public String getRouteId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference.Item> CREATOR;
+    field public static final int DISABLE_REASON_NONE = 0; // 0x0
+    field public static final int DISABLE_REASON_SUBSCRIPTION_REQUIRED = 1; // 0x1
+    field public static final int FLAG_ONGOING_SESSION = 1; // 0x1
+    field public static final int FLAG_SUGGESTED_ROUTE = 2; // 0x2
+  }
+
   public final class RoutingSessionInfo implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public String getClientPackageName();
@@ -26518,6 +26724,7 @@
     method public boolean onKeyMultiple(int, int, @NonNull android.view.KeyEvent);
     method public boolean onKeyUp(int, @NonNull android.view.KeyEvent);
     method public void onMediaViewSizeChanged(@Px int, @Px int);
+    method public void onRecordingStarted(@NonNull String);
     method public abstract void onRelease();
     method public void onResetInteractiveApp();
     method public abstract boolean onSetSurface(@Nullable android.view.Surface);
@@ -26543,6 +26750,7 @@
     method @CallSuper public void requestCurrentChannelUri();
     method @CallSuper public void requestCurrentTvInputId();
     method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+    method @CallSuper public void requestStartRecording(@Nullable android.net.Uri);
     method @CallSuper public void requestStreamVolume();
     method @CallSuper public void requestTrackInfoList();
     method @CallSuper public void sendPlaybackCommandRequest(@NonNull String, @Nullable android.os.Bundle);
@@ -26574,6 +26782,7 @@
     method public boolean dispatchUnhandledInputEvent(@NonNull android.view.InputEvent);
     method @Nullable public android.media.tv.interactive.TvInteractiveAppView.OnUnhandledInputEventListener getOnUnhandledInputEventListener();
     method public void notifyError(@NonNull String, @NonNull android.os.Bundle);
+    method public void notifyRecordingStarted(@NonNull String);
     method public void onAttachedToWindow();
     method public void onDetachedFromWindow();
     method public void onLayout(boolean, int, int, int, int);
@@ -26615,6 +26824,7 @@
     method public void onRequestCurrentChannelUri(@NonNull String);
     method public void onRequestCurrentTvInputId(@NonNull String);
     method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+    method public void onRequestStartRecording(@NonNull String, @Nullable android.net.Uri);
     method public void onRequestStreamVolume(@NonNull String);
     method public void onRequestTrackInfoList(@NonNull String);
     method public void onSetVideoBounds(@NonNull String, @NonNull android.graphics.Rect);
@@ -27658,6 +27868,15 @@
 
 package android.nfc {
 
+  public final class AvailableNfcAntenna implements android.os.Parcelable {
+    ctor public AvailableNfcAntenna(int, int);
+    method public int describeContents();
+    method public int getLocationX();
+    method public int getLocationY();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.AvailableNfcAntenna> CREATOR;
+  }
+
   public class FormatException extends java.lang.Exception {
     ctor public FormatException();
     ctor public FormatException(String);
@@ -27719,6 +27938,7 @@
     method @Deprecated public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
     method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
     method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
+    method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
     method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
     method @Deprecated public boolean invokeBeam(android.app.Activity);
     method public boolean isEnabled();
@@ -27781,6 +28001,17 @@
     method public void onTagDiscovered(android.nfc.Tag);
   }
 
+  public final class NfcAntennaInfo implements android.os.Parcelable {
+    ctor public NfcAntennaInfo(int, int, boolean, @NonNull java.util.List<android.nfc.AvailableNfcAntenna>);
+    method public int describeContents();
+    method @NonNull public java.util.List<android.nfc.AvailableNfcAntenna> getAvailableNfcAntennas();
+    method public int getDeviceHeight();
+    method public int getDeviceWidth();
+    method public boolean isDeviceFoldable();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NfcAntennaInfo> CREATOR;
+  }
+
   public final class NfcEvent {
     field public final android.nfc.NfcAdapter nfcAdapter;
     field public final int peerLlcpMajorVersion;
@@ -32138,7 +32369,12 @@
   public static class PerformanceHintManager.Session implements java.io.Closeable {
     method public void close();
     method public void reportActualWorkDuration(long);
+    method public void sendHint(int);
     method public void updateTargetWorkDuration(long);
+    field public static final int CPU_LOAD_DOWN = 1; // 0x1
+    field public static final int CPU_LOAD_RESET = 2; // 0x2
+    field public static final int CPU_LOAD_RESUME = 3; // 0x3
+    field public static final int CPU_LOAD_UP = 0; // 0x0
   }
 
   public final class PersistableBundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
@@ -32237,6 +32473,7 @@
     method public static final boolean is64Bit();
     method public static boolean isApplicationUid(int);
     method public static final boolean isIsolated();
+    method public static final boolean isIsolatedUid(int);
     method public static final boolean isSdkSandbox();
     method public static final void killProcess(int);
     method public static final int myPid();
@@ -32511,7 +32748,6 @@
     method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.QUERY_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle);
     method public android.os.Bundle getUserRestrictions();
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
-    method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}) public java.util.List<android.os.UserHandle> getVisibleUsers();
     method public boolean hasUserRestriction(String);
     method public boolean isDemoUser();
     method public static boolean isHeadlessSystemUserMode();
@@ -32525,7 +32761,6 @@
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunningOrStopping(android.os.UserHandle);
     method public boolean isUserUnlocked();
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserUnlocked(android.os.UserHandle);
-    method public boolean isUserVisible();
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.MODIFY_QUIET_MODE"}, conditional=true) public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle);
     method public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle, int);
     method @Deprecated public boolean setRestrictionsChallenge(String);
@@ -32545,6 +32780,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";
@@ -32587,6 +32823,7 @@
     field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi";
     field public static final String DISALLOW_SMS = "no_sms";
     field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
+    field public static final String DISALLOW_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio";
     field public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password";
     field public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
     field public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
@@ -35722,6 +35959,7 @@
     method public static boolean canDrawOverlays(android.content.Context);
     field public static final String ACTION_ACCESSIBILITY_SETTINGS = "android.settings.ACCESSIBILITY_SETTINGS";
     field public static final String ACTION_ADD_ACCOUNT = "android.settings.ADD_ACCOUNT_SETTINGS";
+    field public static final String ACTION_ADVANCED_MEMORY_PROTECTION_SETTINGS = "android.settings.ADVANCED_MEMORY_PROTECTION_SETTINGS";
     field public static final String ACTION_AIRPLANE_MODE_SETTINGS = "android.settings.AIRPLANE_MODE_SETTINGS";
     field public static final String ACTION_ALL_APPS_NOTIFICATION_SETTINGS = "android.settings.ALL_APPS_NOTIFICATION_SETTINGS";
     field public static final String ACTION_APN_SETTINGS = "android.settings.APN_SETTINGS";
@@ -35763,6 +36001,7 @@
     field public static final String ACTION_MANAGE_ALL_SIM_PROFILES_SETTINGS = "android.settings.MANAGE_ALL_SIM_PROFILES_SETTINGS";
     field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
     field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
+    field public static final String ACTION_MANAGE_APP_LONG_RUNNING_JOBS = "android.settings.MANAGE_APP_LONG_RUNNING_JOBS";
     field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
     field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
     field public static final String ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING = "android.settings.MANAGE_SUPERVISOR_RESTRICTED_SETTING";
@@ -37917,6 +38156,7 @@
     field public static final int ERROR_PERMISSION_DENIED = 5; // 0x5
     field public static final int ERROR_UNIMPLEMENTED = 12; // 0xc
     field public static final int ERROR_USER_AUTHENTICATION_REQUIRED = 2; // 0x2
+    field public static final int RETRY_AFTER_NEXT_REBOOT = 4; // 0x4
     field public static final int RETRY_NEVER = 1; // 0x1
     field public static final int RETRY_WHEN_CONNECTIVITY_AVAILABLE = 3; // 0x3
     field public static final int RETRY_WITH_EXPONENTIAL_BACKOFF = 2; // 0x2
@@ -39171,6 +39411,149 @@
 
 }
 
+package android.service.credentials {
+
+  public final class Action implements android.os.Parcelable {
+    ctor public Action(@NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent);
+    method public int describeContents();
+    method @NonNull public android.app.PendingIntent getPendingIntent();
+    method @NonNull public android.app.slice.Slice getSlice();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.Action> CREATOR;
+  }
+
+  public final class BeginCreateCredentialRequest implements android.os.Parcelable {
+    ctor public BeginCreateCredentialRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method @NonNull public String getCallingPackage();
+    method @NonNull public android.os.Bundle getData();
+    method @NonNull public String getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.BeginCreateCredentialRequest> CREATOR;
+  }
+
+  public final class BeginCreateCredentialResponse implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.service.credentials.CreateEntry> getCreateEntries();
+    method @Nullable public android.service.credentials.CreateEntry getRemoteCreateEntry();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.BeginCreateCredentialResponse> CREATOR;
+  }
+
+  public static final class BeginCreateCredentialResponse.Builder {
+    ctor public BeginCreateCredentialResponse.Builder();
+    method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder addCreateEntry(@NonNull android.service.credentials.CreateEntry);
+    method @NonNull public android.service.credentials.BeginCreateCredentialResponse build();
+    method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setCreateEntries(@NonNull java.util.List<android.service.credentials.CreateEntry>);
+    method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.CreateEntry);
+  }
+
+  public final class CreateCredentialRequest implements android.os.Parcelable {
+    ctor public CreateCredentialRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method @NonNull public String getCallingPackage();
+    method @NonNull public android.os.Bundle getData();
+    method @NonNull public String getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CreateCredentialRequest> CREATOR;
+  }
+
+  public final class CreateEntry implements android.os.Parcelable {
+    ctor public CreateEntry(@NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent);
+    method public int describeContents();
+    method @NonNull public android.app.PendingIntent getPendingIntent();
+    method @NonNull public android.app.slice.Slice getSlice();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CreateEntry> CREATOR;
+  }
+
+  public final class CredentialEntry implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.credentials.Credential getCredential();
+    method @Nullable public android.app.PendingIntent getPendingIntent();
+    method @NonNull public android.app.slice.Slice getSlice();
+    method @NonNull public String getType();
+    method public boolean isAutoSelectAllowed();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CredentialEntry> CREATOR;
+  }
+
+  public static final class CredentialEntry.Builder {
+    ctor public CredentialEntry.Builder(@NonNull String, @NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent);
+    ctor public CredentialEntry.Builder(@NonNull String, @NonNull android.app.slice.Slice, @NonNull android.credentials.Credential);
+    method @NonNull public android.service.credentials.CredentialEntry build();
+    method @NonNull public android.service.credentials.CredentialEntry.Builder setAutoSelectAllowed(@NonNull boolean);
+  }
+
+  public class CredentialProviderException extends java.lang.Exception {
+    ctor public CredentialProviderException(int, @NonNull String, @NonNull Throwable);
+    ctor public CredentialProviderException(int, @NonNull String);
+    ctor public CredentialProviderException(int, @NonNull Throwable);
+    ctor public CredentialProviderException(int);
+    method public int getErrorCode();
+    field public static final int ERROR_UNKNOWN = 0; // 0x0
+  }
+
+  public abstract class CredentialProviderService extends android.app.Service {
+    ctor public CredentialProviderService();
+    method public abstract void onBeginCreateCredential(@NonNull android.service.credentials.BeginCreateCredentialRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.BeginCreateCredentialResponse,android.service.credentials.CredentialProviderException>);
+    method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public abstract void onGetCredentials(@NonNull android.service.credentials.GetCredentialsRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.GetCredentialsResponse,android.service.credentials.CredentialProviderException>);
+    field public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
+    field public static final String EXTRA_CREATE_CREDENTIAL_REQUEST = "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST";
+    field public static final String EXTRA_CREATE_CREDENTIAL_RESULT = "android.service.credentials.extra.CREATE_CREDENTIAL_RESULT";
+    field public static final String EXTRA_CREDENTIAL_RESULT = "android.service.credentials.extra.CREDENTIAL_RESULT";
+    field public static final String EXTRA_ERROR = "android.service.credentials.extra.ERROR";
+    field public static final String EXTRA_GET_CREDENTIALS_CONTENT_RESULT = "android.service.credentials.extra.GET_CREDENTIALS_CONTENT_RESULT";
+    field public static final String SERVICE_INTERFACE = "android.service.credentials.CredentialProviderService";
+  }
+
+  public final class CredentialsResponseContent implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.service.credentials.Action> getActions();
+    method @NonNull public java.util.List<android.service.credentials.CredentialEntry> getCredentialEntries();
+    method @Nullable public android.service.credentials.CredentialEntry getRemoteCredentialEntry();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CredentialsResponseContent> CREATOR;
+  }
+
+  public static final class CredentialsResponseContent.Builder {
+    ctor public CredentialsResponseContent.Builder();
+    method @NonNull public android.service.credentials.CredentialsResponseContent.Builder addAction(@NonNull android.service.credentials.Action);
+    method @NonNull public android.service.credentials.CredentialsResponseContent.Builder addCredentialEntry(@NonNull android.service.credentials.CredentialEntry);
+    method @NonNull public android.service.credentials.CredentialsResponseContent build();
+    method @NonNull public android.service.credentials.CredentialsResponseContent.Builder setActions(@NonNull java.util.List<android.service.credentials.Action>);
+    method @NonNull public android.service.credentials.CredentialsResponseContent.Builder setCredentialEntries(@NonNull java.util.List<android.service.credentials.CredentialEntry>);
+    method @NonNull public android.service.credentials.CredentialsResponseContent.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.CredentialEntry);
+  }
+
+  public final class GetCredentialsRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getCallingPackage();
+    method @NonNull public java.util.List<android.credentials.GetCredentialOption> getGetCredentialOptions();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.GetCredentialsRequest> CREATOR;
+  }
+
+  public static final class GetCredentialsRequest.Builder {
+    ctor public GetCredentialsRequest.Builder(@NonNull String);
+    method @NonNull public android.service.credentials.GetCredentialsRequest.Builder addGetCredentialOption(@NonNull android.credentials.GetCredentialOption);
+    method @NonNull public android.service.credentials.GetCredentialsRequest build();
+    method @NonNull public android.service.credentials.GetCredentialsRequest.Builder setGetCredentialOptions(@NonNull java.util.List<android.credentials.GetCredentialOption>);
+  }
+
+  public final class GetCredentialsResponse implements android.os.Parcelable {
+    method @NonNull public static android.service.credentials.GetCredentialsResponse createWithAuthentication(@NonNull android.service.credentials.Action);
+    method @NonNull public static android.service.credentials.GetCredentialsResponse createWithResponseContent(@NonNull android.service.credentials.CredentialsResponseContent);
+    method public int describeContents();
+    method @Nullable public android.service.credentials.Action getAuthenticationAction();
+    method @Nullable public android.service.credentials.CredentialsResponseContent getCredentialsResponseContent();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.GetCredentialsResponse> CREATOR;
+  }
+
+}
+
 package android.service.dreams {
 
   public class DreamService extends android.app.Service implements android.view.Window.Callback {
@@ -39875,7 +40258,7 @@
   public abstract class WallpaperService extends android.app.Service {
     ctor public WallpaperService();
     method public final android.os.IBinder onBind(android.content.Intent);
-    method public abstract android.service.wallpaper.WallpaperService.Engine onCreateEngine();
+    method @MainThread public abstract android.service.wallpaper.WallpaperService.Engine onCreateEngine();
     field public static final String SERVICE_INTERFACE = "android.service.wallpaper.WallpaperService";
     field public static final String SERVICE_META_DATA = "android.service.wallpaper";
   }
@@ -39887,23 +40270,25 @@
     method public int getDesiredMinimumWidth();
     method @Nullable public android.content.Context getDisplayContext();
     method public android.view.SurfaceHolder getSurfaceHolder();
+    method public int getWallpaperFlags();
     method public boolean isPreview();
     method public boolean isVisible();
     method public void notifyColorsChanged();
-    method public void onApplyWindowInsets(android.view.WindowInsets);
-    method public android.os.Bundle onCommand(String, int, int, int, android.os.Bundle, boolean);
-    method @Nullable public android.app.WallpaperColors onComputeColors();
-    method public void onCreate(android.view.SurfaceHolder);
-    method public void onDesiredSizeChanged(int, int);
-    method public void onDestroy();
-    method public void onOffsetsChanged(float, float, float, float, int, int);
-    method public void onSurfaceChanged(android.view.SurfaceHolder, int, int, int);
-    method public void onSurfaceCreated(android.view.SurfaceHolder);
-    method public void onSurfaceDestroyed(android.view.SurfaceHolder);
-    method public void onSurfaceRedrawNeeded(android.view.SurfaceHolder);
-    method public void onTouchEvent(android.view.MotionEvent);
-    method public void onVisibilityChanged(boolean);
-    method public void onZoomChanged(@FloatRange(from=0.0f, to=1.0f) float);
+    method @MainThread public void onApplyWindowInsets(android.view.WindowInsets);
+    method @MainThread public android.os.Bundle onCommand(String, int, int, int, android.os.Bundle, boolean);
+    method @MainThread @Nullable public android.app.WallpaperColors onComputeColors();
+    method @MainThread public void onCreate(android.view.SurfaceHolder);
+    method @MainThread public void onDesiredSizeChanged(int, int);
+    method @MainThread public void onDestroy();
+    method @MainThread public void onOffsetsChanged(float, float, float, float, int, int);
+    method @MainThread public void onSurfaceChanged(android.view.SurfaceHolder, int, int, int);
+    method @MainThread public void onSurfaceCreated(android.view.SurfaceHolder);
+    method @MainThread public void onSurfaceDestroyed(android.view.SurfaceHolder);
+    method @MainThread public void onSurfaceRedrawNeeded(android.view.SurfaceHolder);
+    method @MainThread public void onTouchEvent(android.view.MotionEvent);
+    method @MainThread public void onVisibilityChanged(boolean);
+    method @MainThread public void onWallpaperFlagsChanged(int);
+    method @MainThread public void onZoomChanged(@FloatRange(from=0.0f, to=1.0f) float);
     method public void setOffsetNotificationsEnabled(boolean);
     method public void setTouchEventsEnabled(boolean);
   }
@@ -40338,6 +40723,7 @@
     field public static final String EVENT_DISPLAY_DIAGNOSTIC_MESSAGE = "android.telecom.event.DISPLAY_DIAGNOSTIC_MESSAGE";
     field public static final String EXTRA_DIAGNOSTIC_MESSAGE = "android.telecom.extra.DIAGNOSTIC_MESSAGE";
     field public static final String EXTRA_DIAGNOSTIC_MESSAGE_ID = "android.telecom.extra.DIAGNOSTIC_MESSAGE_ID";
+    field public static final String EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB = "android.telecom.extra.IS_SUPPRESSED_BY_DO_NOT_DISTURB";
     field public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS = "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
     field public static final String EXTRA_SILENT_RINGING_REQUESTED = "android.telecom.extra.SILENT_RINGING_REQUESTED";
     field public static final String EXTRA_SUGGESTED_PHONE_ACCOUNTS = "android.telecom.extra.SUGGESTED_PHONE_ACCOUNTS";
@@ -40395,6 +40781,7 @@
     method public int getCallerNumberVerificationStatus();
     method public final long getConnectTimeMillis();
     method @Nullable public String getContactDisplayName();
+    method @Nullable public android.net.Uri getContactPhotoUri();
     method public long getCreationTimeMillis();
     method public android.telecom.DisconnectCause getDisconnectCause();
     method public android.os.Bundle getExtras();
@@ -41591,7 +41978,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";
@@ -41690,7 +42077,7 @@
     field public static final String KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY = "gsm_roaming_networks_string_array";
     field public static final String KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL = "has_in_call_noise_suppression_bool";
     field public static final String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
-    field public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool";
+    field @Deprecated public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool";
     field public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
     field public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
     field public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool";
@@ -41703,6 +42090,7 @@
     field public static final String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
     field public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
     field public static final String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
+    field public static final String KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL = "include_lte_for_nr_advanced_threshold_bandwidth_bool";
     field public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
     field public static final String KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL = "is_opportunistic_subscription_bool";
     field public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool";
@@ -41728,6 +42116,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";
@@ -41758,7 +42147,9 @@
     field public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG = "opportunistic_network_ping_pong_time_long";
     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_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT = "premium_capability_maximum_daily_notification_count_int";
+    field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT = "premium_capability_maximum_monthly_notification_count_int";
+    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";
@@ -41828,6 +42219,7 @@
     field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
     field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
     field public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool";
+    field public static final String KEY_VONR_ON_BY_DEFAULT_BOOL = "vonr_on_by_default_bool";
     field public static final String KEY_VONR_SETTING_VISIBILITY_BOOL = "vonr_setting_visibility_bool";
     field public static final String KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL = "vt_upgrade_supported_for_downgraded_rtt_call";
     field public static final String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
@@ -43416,14 +43808,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
@@ -43432,14 +43828,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
@@ -43930,6 +44335,8 @@
     field public static final int APPTYPE_USIM = 2; // 0x2
     field public static final int AUTHTYPE_EAP_AKA = 129; // 0x81
     field public static final int AUTHTYPE_EAP_SIM = 128; // 0x80
+    field public static final int AUTHTYPE_GBA_BOOTSTRAP = 132; // 0x84
+    field public static final int AUTHTYPE_GBA_NAF_KEY_EXTERNAL = 133; // 0x85
     field public static final int CALL_COMPOSER_STATUS_OFF = 0; // 0x0
     field public static final int CALL_COMPOSER_STATUS_ON = 1; // 0x1
     field public static final int CALL_STATE_IDLE = 0; // 0x0
@@ -44012,7 +44419,6 @@
     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 @Deprecated public static final long NETWORK_TYPE_BITMASK_LTE_CA = 262144L; // 0x40000L
@@ -44032,7 +44438,7 @@
     field public static final int NETWORK_TYPE_HSPA = 10; // 0xa
     field public static final int NETWORK_TYPE_HSPAP = 15; // 0xf
     field public static final int NETWORK_TYPE_HSUPA = 9; // 0x9
-    field public static final int NETWORK_TYPE_IDEN = 11; // 0xb
+    field @Deprecated public static final int NETWORK_TYPE_IDEN = 11; // 0xb
     field public static final int NETWORK_TYPE_IWLAN = 18; // 0x12
     field public static final int NETWORK_TYPE_LTE = 13; // 0xd
     field public static final int NETWORK_TYPE_NR = 20; // 0x14
@@ -44048,11 +44454,12 @@
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED = 3; // 0x3
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED = 7; // 0x7
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR = 8; // 0x8
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED = 13; // 0xd
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED = 10; // 0xa
-    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13; // 0xd
     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_NOT_DEFAULT_DATA_SUBSCRIPTION = 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
@@ -45310,6 +45717,19 @@
     method public int previousStartBoundary(@IntRange(from=0) int);
   }
 
+  public class Highlights {
+    method @NonNull public android.graphics.Paint getPaint(int);
+    method @NonNull public int[] getRanges(int);
+    method public int getSize();
+  }
+
+  public static final class Highlights.Builder {
+    ctor public Highlights.Builder();
+    method @NonNull public android.text.Highlights.Builder addRange(@NonNull android.graphics.Paint, int, int);
+    method @NonNull public android.text.Highlights.Builder addRanges(@NonNull android.graphics.Paint, @NonNull int...);
+    method @NonNull public android.text.Highlights build();
+  }
+
   public class Html {
     method public static String escapeHtml(CharSequence);
     method @Deprecated public static android.text.Spanned fromHtml(String);
@@ -45401,6 +45821,9 @@
     ctor protected Layout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float);
     method public void draw(android.graphics.Canvas);
     method public void draw(android.graphics.Canvas, android.graphics.Path, android.graphics.Paint, int);
+    method public void draw(@NonNull android.graphics.Canvas, @Nullable java.util.List<android.graphics.Path>, @Nullable java.util.List<android.graphics.Paint>, @Nullable android.graphics.Path, @Nullable android.graphics.Paint, int);
+    method public void drawBackground(@NonNull android.graphics.Canvas);
+    method public void drawText(@NonNull android.graphics.Canvas);
     method public void fillCharacterBounds(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull float[], @IntRange(from=0) int);
     method public final android.text.Layout.Alignment getAlignment();
     method public abstract int getBottomPadding();
@@ -45568,6 +45991,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);
@@ -48327,6 +48758,7 @@
     method public float getRefreshRate();
     method public int getRotation();
     method @Nullable public android.view.RoundedCorner getRoundedCorner(int);
+    method @NonNull public android.view.DisplayShape getShape();
     method @Deprecated public void getSize(android.graphics.Point);
     method public int getState();
     method public android.view.Display.Mode[] getSupportedModes();
@@ -48357,7 +48789,7 @@
     method public float getDesiredMaxAverageLuminance();
     method public float getDesiredMaxLuminance();
     method public float getDesiredMinLuminance();
-    method public int[] getSupportedHdrTypes();
+    method @Deprecated public int[] getSupportedHdrTypes();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.Display.HdrCapabilities> CREATOR;
     field public static final int HDR_TYPE_DOLBY_VISION = 1; // 0x1
@@ -48374,6 +48806,7 @@
     method public int getPhysicalHeight();
     method public int getPhysicalWidth();
     method public float getRefreshRate();
+    method @NonNull public int[] getSupportedHdrTypes();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.Display.Mode> CREATOR;
   }
@@ -48407,6 +48840,13 @@
     method @NonNull public android.view.DisplayCutout.Builder setWaterfallInsets(@NonNull android.graphics.Insets);
   }
 
+  public final class DisplayShape implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.graphics.Path getPath();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.DisplayShape> CREATOR;
+  }
+
   public final class DragAndDropPermissions implements android.os.Parcelable {
     method public int describeContents();
     method public void release();
@@ -48557,6 +48997,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
@@ -49408,6 +49854,7 @@
     method public float getY(int);
     method public float getYPrecision();
     method public boolean isButtonPressed(int);
+    method @Nullable public static android.view.MotionEvent obtain(long, long, int, int, @NonNull android.view.MotionEvent.PointerProperties[], @NonNull android.view.MotionEvent.PointerCoords[], int, int, float, float, int, int, int, int, int, int);
     method public static android.view.MotionEvent obtain(long, long, int, int, android.view.MotionEvent.PointerProperties[], android.view.MotionEvent.PointerCoords[], int, int, float, float, int, int, int, int);
     method @Deprecated public static android.view.MotionEvent obtain(long, long, int, int, int[], android.view.MotionEvent.PointerCoords[], int, float, float, int, int, int, int);
     method public static android.view.MotionEvent obtain(long, long, int, float, float, float, float, int, float, float, int, int);
@@ -49574,16 +50021,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, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>);
     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
@@ -49592,19 +50036,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();
+  }
+
+  public static final class PixelCopy.Request.Builder {
+    method @NonNull public android.view.PixelCopy.Request build();
+    method @NonNull public static android.view.PixelCopy.Request.Builder ofSurface(@NonNull android.view.Surface);
+    method @NonNull public static android.view.PixelCopy.Request.Builder ofSurface(@NonNull android.view.SurfaceView);
+    method @NonNull public static android.view.PixelCopy.Request.Builder ofWindow(@NonNull android.view.Window);
+    method @NonNull public static android.view.PixelCopy.Request.Builder ofWindow(@NonNull android.view.View);
+    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 {
@@ -49848,6 +50301,7 @@
   public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable {
     ctor public SurfaceControlViewHost.SurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage);
     method public int describeContents();
+    method @NonNull public android.view.SurfaceControl getSurfaceControl();
     method public void notifyConfigurationChanged(@NonNull android.content.res.Configuration);
     method public void notifyDetachedFromWindow();
     method public void release();
@@ -50052,7 +50506,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);
@@ -50090,7 +50544,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();
@@ -50167,6 +50621,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);
@@ -50382,9 +50837,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();
@@ -50533,6 +50988,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);
@@ -50869,7 +51325,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);
   }
 
@@ -51099,7 +51555,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);
@@ -51772,6 +52228,7 @@
     method @Deprecated @NonNull public android.view.WindowInsets consumeStableInsets();
     method @Deprecated @NonNull public android.view.WindowInsets consumeSystemWindowInsets();
     method @Nullable public android.view.DisplayCutout getDisplayCutout();
+    method @Nullable public android.view.DisplayShape getDisplayShape();
     method @NonNull public android.graphics.Insets getInsets(int);
     method @NonNull public android.graphics.Insets getInsetsIgnoringVisibility(int);
     method @Deprecated @NonNull public android.graphics.Insets getMandatorySystemGestureInsets();
@@ -51807,6 +52264,7 @@
     ctor public WindowInsets.Builder(@NonNull android.view.WindowInsets);
     method @NonNull public android.view.WindowInsets build();
     method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
+    method @NonNull public android.view.WindowInsets.Builder setDisplayShape(@NonNull android.view.DisplayShape);
     method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets);
     method @NonNull public android.view.WindowInsets.Builder setInsetsIgnoringVisibility(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException;
     method @Deprecated @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets);
@@ -51836,6 +52294,7 @@
     method public static int statusBars();
     method public static int systemBars();
     method public static int systemGestures();
+    method public static int systemOverlays();
     method public static int tappableElement();
   }
 
@@ -52272,6 +52731,7 @@
     method public CharSequence getClassName();
     method public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo getCollectionInfo();
     method public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo getCollectionItemInfo();
+    method @Nullable public CharSequence getContainerTitle();
     method public CharSequence getContentDescription();
     method public int getDrawingOrder();
     method public CharSequence getError();
@@ -52283,6 +52743,7 @@
     method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
     method public int getLiveRegion();
     method public int getMaxTextLength();
+    method @NonNull public java.time.Duration getMinDurationBetweenContentChanges();
     method public int getMovementGranularities();
     method public CharSequence getPackageName();
     method @Nullable public CharSequence getPaneTitle();
@@ -52301,6 +52762,7 @@
     method public String getViewIdResourceName();
     method public android.view.accessibility.AccessibilityWindowInfo getWindow();
     method public int getWindowId();
+    method public boolean hasRequestInitialAccessibilityFocus();
     method public boolean isAccessibilityFocused();
     method public boolean isCheckable();
     method public boolean isChecked();
@@ -52348,6 +52810,7 @@
     method public void setClickable(boolean);
     method public void setCollectionInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionInfo);
     method public void setCollectionItemInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo);
+    method public void setContainerTitle(@Nullable CharSequence);
     method public void setContentDescription(CharSequence);
     method public void setContentInvalid(boolean);
     method public void setContextClickable(boolean);
@@ -52369,6 +52832,7 @@
     method public void setLiveRegion(int);
     method public void setLongClickable(boolean);
     method public void setMaxTextLength(int);
+    method public void setMinDurationBetweenContentChanges(@NonNull java.time.Duration);
     method public void setMovementGranularities(int);
     method public void setMultiLine(boolean);
     method public void setPackageName(CharSequence);
@@ -52378,6 +52842,7 @@
     method public void setPassword(boolean);
     method public void setQueryFromAppProcessEnabled(@NonNull android.view.View, boolean);
     method public void setRangeInfo(android.view.accessibility.AccessibilityNodeInfo.RangeInfo);
+    method public void setRequestInitialAccessibilityFocus(boolean);
     method public void setScreenReaderFocusable(boolean);
     method public void setScrollable(boolean);
     method public void setSelected(boolean);
@@ -53312,6 +53777,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;
@@ -53332,9 +53798,10 @@
     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 {
+  public final class DeleteGesture extends android.view.inputmethod.PreviewableHandwritingGesture implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.graphics.RectF getDeletionArea();
     method public int getGranularity();
@@ -53350,7 +53817,7 @@
     method @NonNull public android.view.inputmethod.DeleteGesture.Builder setGranularity(int);
   }
 
-  public final class DeleteRangeGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
+  public final class DeleteRangeGesture extends android.view.inputmethod.PreviewableHandwritingGesture implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.graphics.RectF getDeletionEndArea();
     method @NonNull public android.graphics.RectF getDeletionStartArea();
@@ -53392,11 +53859,13 @@
     method @Nullable public CharSequence getInitialTextAfterCursor(@IntRange(from=0) int, int);
     method @Nullable public CharSequence getInitialTextBeforeCursor(@IntRange(from=0) int, int);
     method public int getInitialToolType();
+    method @NonNull public java.util.Set<java.lang.Class<? extends android.view.inputmethod.PreviewableHandwritingGesture>> getSupportedHandwritingGesturePreviews();
     method @NonNull public java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>> getSupportedHandwritingGestures();
     method public final void makeCompatible(int);
     method public void setInitialSurroundingSubText(@NonNull CharSequence, int);
     method public void setInitialSurroundingText(@NonNull CharSequence);
     method public void setInitialToolType(int);
+    method public void setSupportedHandwritingGesturePreviews(@NonNull java.util.Set<java.lang.Class<? extends android.view.inputmethod.PreviewableHandwritingGesture>>);
     method public void setSupportedHandwritingGestures(@NonNull java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>>);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.EditorInfo> CREATOR;
@@ -53571,10 +54040,12 @@
     method public default void performHandwritingGesture(@NonNull android.view.inputmethod.HandwritingGesture, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.IntConsumer);
     method public boolean performPrivateCommand(String, android.os.Bundle);
     method public default boolean performSpellCheck();
+    method public default boolean previewHandwritingGesture(@NonNull android.view.inputmethod.PreviewableHandwritingGesture, @Nullable android.os.CancellationSignal);
     method public default boolean replaceText(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
     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);
@@ -53586,6 +54057,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
@@ -53776,6 +54248,7 @@
     method @NonNull public String getLanguageTag();
     method @Deprecated @NonNull public String getLocale();
     method public String getMode();
+    method @NonNull public CharSequence getNameOverride();
     method public int getNameResId();
     method public boolean isAsciiCapable();
     method public boolean isAuxiliary();
@@ -53796,6 +54269,7 @@
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeId(int);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeLocale(String);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeMode(String);
+    method @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeNameOverride(@NonNull CharSequence);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeNameResId(int);
   }
 
@@ -53829,6 +54303,9 @@
     method @NonNull public android.view.inputmethod.JoinOrSplitGesture.Builder setJoinOrSplitPoint(@NonNull android.graphics.PointF);
   }
 
+  public abstract class PreviewableHandwritingGesture extends android.view.inputmethod.HandwritingGesture {
+  }
+
   public final class RemoveSpaceGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.graphics.PointF getEndPoint();
@@ -53844,7 +54321,7 @@
     method @NonNull public android.view.inputmethod.RemoveSpaceGesture.Builder setPoints(@NonNull android.graphics.PointF, @NonNull android.graphics.PointF);
   }
 
-  public final class SelectGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
+  public final class SelectGesture extends android.view.inputmethod.PreviewableHandwritingGesture implements android.os.Parcelable {
     method public int describeContents();
     method public int getGranularity();
     method @NonNull public android.graphics.RectF getSelectionArea();
@@ -53860,7 +54337,7 @@
     method @NonNull public android.view.inputmethod.SelectGesture.Builder setSelectionArea(@NonNull android.graphics.RectF);
   }
 
-  public final class SelectRangeGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
+  public final class SelectRangeGesture extends android.view.inputmethod.PreviewableHandwritingGesture implements android.os.Parcelable {
     method public int describeContents();
     method public int getGranularity();
     method @NonNull public android.graphics.RectF getSelectionEndArea();
@@ -53889,6 +54366,61 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.SurroundingText> CREATOR;
   }
 
+  public final class TextAppearanceInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public String getFontFeatureSettings();
+    method @Nullable public String getFontVariationSettings();
+    method @ColorInt public int getHighlightTextColor();
+    method @ColorInt public int getHintTextColor();
+    method public float getLetterSpacing();
+    method public int getLineBreakStyle();
+    method public int getLineBreakWordStyle();
+    method @ColorInt public int getLinkTextColor();
+    method @ColorInt public int getShadowColor();
+    method @Px public float getShadowDx();
+    method @Px public float getShadowDy();
+    method @Px public float getShadowRadius();
+    method @Nullable public String getSystemFontFamilyName();
+    method @ColorInt public int getTextColor();
+    method @IntRange(from=android.graphics.fonts.FontStyle.FONT_WEIGHT_UNSPECIFIED, 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 static final class TextAppearanceInfo.Builder {
+    ctor public TextAppearanceInfo.Builder();
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo build();
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setAllCaps(boolean);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setElegantTextHeight(boolean);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFallbackLineSpacing(boolean);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFontFeatureSettings(@Nullable String);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFontVariationSettings(@Nullable String);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setHighlightTextColor(@ColorInt int);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setHintTextColor(@ColorInt int);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLetterSpacing(float);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLineBreakStyle(int);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLineBreakWordStyle(int);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLinkTextColor(@ColorInt int);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowColor(@ColorInt int);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowDx(@Px float);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowDy(@Px float);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowRadius(@Px float);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setSystemFontFamilyName(@Nullable String);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextColor(@ColorInt int);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextFontWeight(@IntRange(from=android.graphics.fonts.FontStyle.FONT_WEIGHT_UNSPECIFIED, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) int);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextLocales(@NonNull android.os.LocaleList);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextScaleX(float);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextSize(@Px float);
+    method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextStyle(int);
+  }
+
   public final class TextAttribute implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.os.PersistableBundle getExtras();
@@ -53904,6 +54436,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();
@@ -58062,6 +58638,7 @@
     method public boolean getFreezesText();
     method public int getGravity();
     method @ColorInt public int getHighlightColor();
+    method @Nullable public android.text.Highlights getHighlights();
     method public CharSequence getHint();
     method public final android.content.res.ColorStateList getHintTextColors();
     method public int getHyphenationFrequency();
@@ -58191,6 +58768,7 @@
     method public void setGravity(int);
     method public void setHeight(int);
     method public void setHighlightColor(@ColorInt int);
+    method public void setHighlights(@Nullable android.text.Highlights);
     method public final void setHint(CharSequence);
     method public final void setHint(@StringRes int);
     method public final void setHintTextColor(@ColorInt int);
@@ -58613,6 +59191,22 @@
 
 package android.window {
 
+  public final class BackEvent {
+    ctor public BackEvent(float, float, float, int);
+    method @FloatRange(from=0, to=1) public float getProgress();
+    method public int getSwipeEdge();
+    method public float getTouchX();
+    method public float getTouchY();
+    field public static final int EDGE_LEFT = 0; // 0x0
+    field public static final int EDGE_RIGHT = 1; // 0x1
+  }
+
+  public interface OnBackAnimationCallback extends android.window.OnBackInvokedCallback {
+    method public default void onBackCancelled();
+    method public default void onBackProgressed(@NonNull android.window.BackEvent);
+    method public default void onBackStarted(@NonNull android.window.BackEvent);
+  }
+
   public interface OnBackInvokedCallback {
     method public void onBackInvoked();
   }
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 88efcce..3efb0c6 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -121,6 +121,7 @@
     field public static final int GADGET_HAL_V1_0 = 10; // 0xa
     field public static final int GADGET_HAL_V1_1 = 11; // 0xb
     field public static final int GADGET_HAL_V1_2 = 12; // 0xc
+    field public static final int GADGET_HAL_V2_0 = 20; // 0x14
     field public static final int USB_DATA_TRANSFER_RATE_10G = 10240; // 0x2800
     field public static final int USB_DATA_TRANSFER_RATE_20G = 20480; // 0x5000
     field public static final int USB_DATA_TRANSFER_RATE_40G = 40960; // 0xa000
@@ -297,6 +298,14 @@
 
 package android.os {
 
+  public class ArtModuleServiceManager {
+    method @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getArtdServiceRegisterer();
+  }
+
+  public static final class ArtModuleServiceManager.ServiceRegisterer {
+    method @Nullable public android.os.IBinder waitForService();
+  }
+
   public final class BatteryStatsManager {
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void reportNetworkInterfaceForTransports(@NonNull String, @NonNull int[]) throws java.lang.RuntimeException;
   }
@@ -340,10 +349,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);
@@ -353,9 +358,9 @@
   }
 
   public final class ServiceManager {
+    method @NonNull public static String[] getDeclaredInstances(@NonNull String);
     method public static boolean isDeclared(@NonNull String);
     method @Nullable public static android.os.IBinder waitForDeclaredService(@NonNull String);
-    method @Nullable public static android.os.IBinder waitForService(@NonNull String);
   }
 
   public class StatsServiceManager {
@@ -414,9 +419,58 @@
   }
 
   public final class DeviceConfig {
+    field public static final String NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS = "activity_manager_ca";
     field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
+    field public static final String NAMESPACE_APP_CLONING = "app_cloning";
     field public static final String NAMESPACE_APP_STANDBY = "app_standby";
+    field public static final String NAMESPACE_ARC_APP_COMPAT = "arc_app_compat";
+    field public static final String NAMESPACE_CONFIGURATION = "configuration";
+    field public static final String NAMESPACE_CONNECTIVITY_THERMAL_POWER_MANAGER = "connectivity_thermal_power_manager";
+    field public static final String NAMESPACE_CONTACTS_PROVIDER = "contacts_provider";
     field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
+    field public static final String NAMESPACE_DEVICE_POLICY_MANAGER = "device_policy_manager";
+    field public static final String NAMESPACE_GAME_OVERLAY = "game_overlay";
+    field public static final String NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS = "intelligence_content_suggestions";
+    field public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor";
+    field public static final String NAMESPACE_LATENCY_TRACKER = "latency_tracker";
+    field public static final String NAMESPACE_MEMORY_SAFETY_NATIVE = "memory_safety_native";
+    field public static final String NAMESPACE_MGLRU_NATIVE = "mglru_native";
+    field public static final String NAMESPACE_REMOTE_KEY_PROVISIONING_NATIVE = "remote_key_provisioning_native";
+    field public static final String NAMESPACE_ROTATION_RESOLVER = "rotation_resolver";
+    field public static final String NAMESPACE_SETTINGS_STATS = "settings_stats";
+    field public static final String NAMESPACE_SETTINGS_UI = "settings_ui";
+    field public static final String NAMESPACE_TARE = "tare";
+    field public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
+    field public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot";
+    field public static final String NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE = "virtualization_framework_native";
+    field public static final String NAMESPACE_VOICE_INTERACTION = "voice_interaction";
+    field public static final String NAMESPACE_WEAR = "wear";
+    field public static final String NAMESPACE_WIDGET = "widget";
+    field public static final String NAMESPACE_WINDOW_MANAGER = "window_manager";
+  }
+
+  public static class DeviceConfig.Properties {
+    ctor public DeviceConfig.Properties(@NonNull String, @Nullable java.util.Map<java.lang.String,java.lang.String>);
+  }
+
+  public final class Settings {
+    field public static final int RESET_MODE_PACKAGE_DEFAULTS = 1; // 0x1
+    field public static final int RESET_MODE_TRUSTED_DEFAULTS = 4; // 0x4
+    field public static final int RESET_MODE_UNTRUSTED_CHANGES = 3; // 0x3
+    field public static final int RESET_MODE_UNTRUSTED_DEFAULTS = 2; // 0x2
+  }
+
+  public static final class Settings.Config extends android.provider.Settings.NameValueTable {
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean deleteString(@NonNull String, @NonNull String);
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static java.util.Map<java.lang.String,java.lang.String> getStrings(@NonNull String, @NonNull java.util.List<java.lang.String>);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static int getSyncDisabledMode();
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean putString(@NonNull String, @NonNull String, @Nullable String, boolean);
+    method public static void registerContentObserver(@Nullable String, boolean, @NonNull android.database.ContentObserver);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setStrings(@NonNull String, @NonNull java.util.Map<java.lang.String,java.lang.String>) throws android.provider.DeviceConfig.BadConfigException;
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void setSyncDisabledMode(int);
+    method public static void unregisterContentObserver(@NonNull android.database.ContentObserver);
   }
 
   public static final class Settings.Global extends android.provider.Settings.NameValueTable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a382ecf..2aaa8e6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -79,6 +79,7 @@
     field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
     field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
     field public static final String BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE = "android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE";
+    field public static final String BIND_WEARABLE_SENSING_SERVICE = "android.permission.BIND_WEARABLE_SENSING_SERVICE";
     field public static final String BLUETOOTH_MAP = "android.permission.BLUETOOTH_MAP";
     field public static final String BRICK = "android.permission.BRICK";
     field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
@@ -199,6 +200,7 @@
     field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
     field public static final String MANAGE_WALLPAPER_EFFECTS_GENERATION = "android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION";
     field public static final String MANAGE_WEAK_ESCROW_TOKEN = "android.permission.MANAGE_WEAK_ESCROW_TOKEN";
+    field public static final String MANAGE_WEARABLE_SENSING_SERVICE = "android.permission.MANAGE_WEARABLE_SENSING_SERVICE";
     field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE";
     field public static final String MARK_DEVICE_ORGANIZATION_OWNED = "android.permission.MARK_DEVICE_ORGANIZATION_OWNED";
     field public static final String MEDIA_RESOURCE_OVERRIDE_PID = "android.permission.MEDIA_RESOURCE_OVERRIDE_PID";
@@ -241,7 +243,7 @@
     field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
     field public static final String PROVISION_DEMO_DEVICE = "android.permission.PROVISION_DEMO_DEVICE";
     field public static final String QUERY_ADMIN_POLICY = "android.permission.QUERY_ADMIN_POLICY";
-    field public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
+    field @Deprecated public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
     field public static final String QUERY_USERS = "android.permission.QUERY_USERS";
     field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION";
     field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION";
@@ -344,7 +346,7 @@
     field public static final String UPDATE_DOMAIN_VERIFICATION_USER_SELECTION = "android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION";
     field public static final String UPDATE_FONTS = "android.permission.UPDATE_FONTS";
     field public static final String UPDATE_LOCK = "android.permission.UPDATE_LOCK";
-    field public static final String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES";
+    field @Deprecated public static final String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES";
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
     field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
     field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS";
@@ -512,6 +514,10 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public boolean startProfile(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public boolean stopProfile(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean switchUser(@NonNull android.os.UserHandle);
+    field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2
+    field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1
+    field public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 4; // 0x4
+    field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0
   }
 
   public static interface ActivityManager.OnUidImportanceListener {
@@ -524,10 +530,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 +593,8 @@
     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_READ_WRITE_HEALTH_DATA = "android:read_write_health_data";
     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";
@@ -775,13 +785,19 @@
   }
 
   public class BroadcastOptions {
+    method public void clearDeliveryGroupMatchingFilter();
+    method public void clearDeliveryGroupMatchingKey();
     method public void clearDeliveryGroupPolicy();
     method public void clearRequireCompatChange();
+    method @Nullable public android.content.IntentFilter getDeliveryGroupMatchingFilter();
+    method @Nullable public String getDeliveryGroupMatchingKey();
     method public int getDeliveryGroupPolicy();
     method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
     method public static android.app.BroadcastOptions makeBasic();
     method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
     method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
+    method public void setDeliveryGroupMatchingFilter(@NonNull android.content.IntentFilter);
+    method public void setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
     method public void setDeliveryGroupPolicy(int);
     method public void setDontSendToRestrictedApps(boolean);
     method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
@@ -803,17 +819,50 @@
   public final class GameManager {
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public android.app.GameModeInfo getGameModeInfo(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public void setGameMode(@NonNull String, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public void updateCustomGameModeConfiguration(@NonNull String, @NonNull android.app.GameModeConfiguration);
+  }
+
+  public final class GameModeConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getFpsOverride();
+    method public float getScalingFactor();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.GameModeConfiguration> CREATOR;
+    field public static final int FPS_OVERRIDE_NONE = 0; // 0x0
+  }
+
+  public static final class GameModeConfiguration.Builder {
+    ctor public GameModeConfiguration.Builder();
+    ctor public GameModeConfiguration.Builder(@NonNull android.app.GameModeConfiguration);
+    method @NonNull public android.app.GameModeConfiguration build();
+    method @NonNull public android.app.GameModeConfiguration.Builder setFpsOverride(int);
+    method @NonNull public android.app.GameModeConfiguration.Builder setScalingFactor(float);
   }
 
   public final class GameModeInfo implements android.os.Parcelable {
-    ctor public GameModeInfo(int, @NonNull int[]);
+    ctor @Deprecated public GameModeInfo(int, @NonNull int[]);
     method public int describeContents();
     method public int getActiveGameMode();
     method @NonNull public int[] getAvailableGameModes();
+    method @Nullable public android.app.GameModeConfiguration getGameModeConfiguration(int);
+    method @NonNull public int[] getOverriddenGameModes();
+    method public boolean isDownscalingAllowed();
+    method public boolean isFpsOverrideAllowed();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.GameModeInfo> CREATOR;
   }
 
+  public static final class GameModeInfo.Builder {
+    ctor public GameModeInfo.Builder();
+    method @NonNull public android.app.GameModeInfo build();
+    method @NonNull public android.app.GameModeInfo.Builder setActiveGameMode(@NonNull int);
+    method @NonNull public android.app.GameModeInfo.Builder setAvailableGameModes(@NonNull int[]);
+    method @NonNull public android.app.GameModeInfo.Builder setDownscalingAllowed(boolean);
+    method @NonNull public android.app.GameModeInfo.Builder setFpsOverrideAllowed(boolean);
+    method @NonNull public android.app.GameModeInfo.Builder setGameModeConfiguration(int, @NonNull android.app.GameModeConfiguration);
+    method @NonNull public android.app.GameModeInfo.Builder setOverriddenGameModes(@NonNull int[]);
+  }
+
   public abstract class InstantAppResolverService extends android.app.Service {
     ctor public InstantAppResolverService();
     method public final void attachBaseContext(android.content.Context);
@@ -1074,6 +1123,7 @@
     method @FloatRange(from=0.0f, to=1.0f) @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount();
     method public void setDisplayOffset(android.os.IBinder, int, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName);
+    method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponentWithFlags(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public void setWallpaperDimAmount(@FloatRange(from=0.0f, to=1.0f) float);
   }
 
@@ -1105,6 +1155,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
     method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public java.util.Set<java.lang.Integer> getApplicationExemptions(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
@@ -1130,6 +1181,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, android.Manifest.permission.PROVISION_DEMO_DEVICE}) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
     method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
     method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
@@ -1151,6 +1203,7 @@
     field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
     field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
     field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER";
+    field public static final int EXEMPT_FROM_APP_STANDBY = 0; // 0x0
     field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER";
     field public static final String EXTRA_LOST_MODE_LOCATION = "android.app.extra.LOST_MODE_LOCATION";
     field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
@@ -1421,6 +1474,7 @@
     method @RequiresPermission(android.Manifest.permission.BACKUP) public void cancelBackups();
     method @RequiresPermission(android.Manifest.permission.BACKUP) public void excludeKeysFromRestore(@NonNull String, @NonNull java.util.List<java.lang.String>);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public long getAvailableRestoreToken(String);
+    method @NonNull public android.app.backup.BackupRestoreEventLogger getBackupRestoreEventLogger(@NonNull android.app.backup.BackupAgent);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public android.content.Intent getConfigurationIntent(String);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public String getCurrentTransport();
     method @Nullable @RequiresPermission(android.Manifest.permission.BACKUP) public android.content.ComponentName getCurrentTransportComponent();
@@ -1459,6 +1513,7 @@
   public class BackupManagerMonitor {
     ctor public BackupManagerMonitor();
     method public void onEvent(android.os.Bundle);
+    field public static final String EXTRA_LOG_AGENT_LOGGING_RESULTS = "android.app.backup.extra.LOG_AGENT_LOGGING_RESULTS";
     field public static final String EXTRA_LOG_CANCEL_ALL = "android.app.backup.extra.LOG_CANCEL_ALL";
     field public static final String EXTRA_LOG_EVENT_CATEGORY = "android.app.backup.extra.LOG_EVENT_CATEGORY";
     field public static final String EXTRA_LOG_EVENT_ID = "android.app.backup.extra.LOG_EVENT_ID";
@@ -1477,6 +1532,7 @@
     field public static final int LOG_EVENT_CATEGORY_AGENT = 2; // 0x2
     field public static final int LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY = 3; // 0x3
     field public static final int LOG_EVENT_CATEGORY_TRANSPORT = 1; // 0x1
+    field public static final int LOG_EVENT_ID_AGENT_LOGGING_RESULTS = 52; // 0x34
     field public static final int LOG_EVENT_ID_APK_NOT_INSTALLED = 40; // 0x28
     field public static final int LOG_EVENT_ID_APP_HAS_NO_AGENT = 28; // 0x1c
     field public static final int LOG_EVENT_ID_BACKUP_DISABLED = 13; // 0xd
@@ -1537,6 +1593,33 @@
     field public final long bytesTransferred;
   }
 
+  public class BackupRestoreEventLogger {
+    method public void logBackupMetaData(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, @NonNull String);
+    method public void logItemsBackedUp(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int);
+    method public void logItemsBackupFailed(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int, @android.app.backup.BackupRestoreEventLogger.BackupRestoreError @Nullable String);
+    method public void logItemsRestoreFailed(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int, @android.app.backup.BackupRestoreEventLogger.BackupRestoreError @Nullable String);
+    method public void logItemsRestored(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int);
+    method public void logRestoreMetadata(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, @NonNull String);
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BackupRestoreEventLogger.BackupRestoreDataType {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BackupRestoreEventLogger.BackupRestoreError {
+  }
+
+  public static final class BackupRestoreEventLogger.DataTypeResult implements android.os.Parcelable {
+    ctor public BackupRestoreEventLogger.DataTypeResult(@NonNull String);
+    method public int describeContents();
+    method @android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull public String getDataType();
+    method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getErrors();
+    method public int getFailCount();
+    method @Nullable public byte[] getMetadataHash();
+    method public int getSuccessCount();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.backup.BackupRestoreEventLogger.DataTypeResult> CREATOR;
+  }
+
   public class BackupTransport {
     ctor public BackupTransport();
     method public int abortFullRestore();
@@ -1551,6 +1634,7 @@
     method public int finishBackup();
     method public void finishRestore();
     method public android.app.backup.RestoreSet[] getAvailableRestoreSets();
+    method @Nullable public android.app.backup.BackupManagerMonitor getBackupManagerMonitor();
     method public long getBackupQuota(String, boolean);
     method public android.os.IBinder getBinder();
     method public long getCurrentRestoreSet();
@@ -2822,6 +2906,21 @@
 
 }
 
+package android.app.wearable {
+
+  public class WearableSensingManager {
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideData(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    field public static final int STATUS_ACCESS_DENIED = 5; // 0x5
+    field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3
+    field public static final int STATUS_SUCCESS = 1; // 0x1
+    field public static final int STATUS_UNKNOWN = 0; // 0x0
+    field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+    field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
+  }
+
+}
+
 package android.apphibernation {
 
   public class AppHibernationManager {
@@ -2885,13 +2984,19 @@
   public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
     method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+    method @NonNull public android.content.Context createContext();
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback);
     method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
-    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualDpad createVirtualDpad(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
-    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
-    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
-    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
+    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @NonNull java.util.List<java.lang.String>, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualDpad createVirtualDpad(@NonNull android.hardware.input.VirtualDpadConfig);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.input.VirtualKeyboardConfig);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.input.VirtualMouseConfig);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method public int getDeviceId();
+    method @Nullable public android.companion.virtual.sensor.VirtualSensor getVirtualSensor(int, @NonNull String);
     method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
@@ -2905,26 +3010,36 @@
     method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
     method public int getDefaultActivityPolicy();
     method public int getDefaultNavigationPolicy();
+    method public int getDefaultRecentsPolicy();
+    method public int getDevicePolicy(int);
     method public int getLockState();
     method @Nullable public String getName();
     method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
+    method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
     field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR;
+    field public static final int DEVICE_POLICY_CUSTOM = 1; // 0x1
+    field public static final int DEVICE_POLICY_DEFAULT = 0; // 0x0
     field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1
     field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
     field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
     field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+    field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
+    field public static final int RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS = 1; // 0x1
   }
 
   public static final class VirtualDeviceParams.Builder {
     ctor public VirtualDeviceParams.Builder();
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig);
     method @NonNull public android.companion.virtual.VirtualDeviceParams build();
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDefaultRecentsPolicy(int);
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
@@ -2977,6 +3092,50 @@
 
 }
 
+package android.companion.virtual.sensor {
+
+  public class VirtualSensor {
+    method @NonNull public String getName();
+    method public int getType();
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent);
+  }
+
+  public static interface VirtualSensor.SensorStateChangeCallback {
+    method public void onStateChanged(boolean, @NonNull java.time.Duration, @NonNull java.time.Duration);
+  }
+
+  public final class VirtualSensorConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getName();
+    method public int getType();
+    method @Nullable public String getVendor();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR;
+  }
+
+  public static final class VirtualSensorConfig.Builder {
+    ctor public VirtualSensorConfig.Builder(int, @NonNull String);
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build();
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setStateChangeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensor.SensorStateChangeCallback);
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String);
+  }
+
+  public final class VirtualSensorEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public long getTimestampNanos();
+    method @NonNull public float[] getValues();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorEvent> CREATOR;
+  }
+
+  public static final class VirtualSensorEvent.Builder {
+    ctor public VirtualSensorEvent.Builder(@NonNull float[]);
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent build();
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent.Builder setTimestampNanos(long);
+  }
+
+}
+
 package android.content {
 
   public class ApexEnvironment {
@@ -3036,6 +3195,7 @@
     field public static final String APP_HIBERNATION_SERVICE = "app_hibernation";
     field public static final String APP_INTEGRITY_SERVICE = "app_integrity";
     field public static final String APP_PREDICTION_SERVICE = "app_prediction";
+    field public static final String AUDIO_DEVICE_VOLUME_SERVICE = "audio_device_volume";
     field public static final String BACKUP_SERVICE = "backup";
     field public static final String BATTERY_STATS_SERVICE = "batterystats";
     field public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000
@@ -3069,8 +3229,10 @@
     field public static final String TRANSLATION_MANAGER_SERVICE = "translation";
     field public static final String UI_TRANSLATION_SERVICE = "ui_translation";
     field public static final String UWB_SERVICE = "uwb";
+    field public static final String VIRTUALIZATION_SERVICE = "virtualization";
     field public static final String VR_SERVICE = "vrmanager";
     field public static final String WALLPAPER_EFFECTS_GENERATION_SERVICE = "wallpaper_effects_generation";
+    field public static final String WEARABLE_SENSING_SERVICE = "wearable_sensing";
     field public static final String WIFI_NL80211_SERVICE = "wifinl80211";
     field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
     field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
@@ -3166,7 +3328,9 @@
   }
 
   public class IntentFilter implements android.os.Parcelable {
+    method @NonNull public final android.os.PersistableBundle getExtras();
     method public final int getOrder();
+    method public final void setExtras(@NonNull android.os.PersistableBundle);
     method public final void setOrder(int);
   }
 
@@ -3498,6 +3662,7 @@
     field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
     field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
     field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg";
+    field public static final String FEATURE_VIRTUALIZATION_FRAMEWORK = "android.software.virtualization_framework";
     field public static final int FLAGS_PERMISSION_RESERVED_PERMISSION_CONTROLLER = -268435456; // 0xf0000000
     field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
     field public static final int FLAG_PERMISSION_AUTO_REVOKED = 131072; // 0x20000
@@ -3607,13 +3772,16 @@
     field @Deprecated public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
     field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
     field public static final int PROTECTION_FLAG_KNOWN_SIGNER = 134217728; // 0x8000000
+    field public static final int PROTECTION_FLAG_MODULE = 4194304; // 0x400000
     field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
     field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
     field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
     field public static final int PROTECTION_FLAG_ROLE = 67108864; // 0x4000000
     field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
+    field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
     field @Deprecated public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000
     field @Nullable public final String backgroundPermission;
+    field @NonNull public java.util.Set<java.lang.String> knownCerts;
     field @StringRes public int requestRes;
   }
 
@@ -4077,15 +4245,18 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public java.util.List<java.lang.String> getAllowedCecSettingStringValues(@NonNull String);
     method @Nullable public android.hardware.hdmi.HdmiClient getClient(int);
     method @NonNull public java.util.List<android.hardware.hdmi.HdmiDeviceInfo> getConnectedDevices();
+    method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getEarcEnabled();
     method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getHdmiCecEnabled();
     method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getHdmiCecVersion();
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getHdmiCecVolumeControlEnabled();
     method public int getPhysicalAddress();
     method @Nullable public android.hardware.hdmi.HdmiPlaybackClient getPlaybackClient();
+    method @NonNull public java.util.List<android.hardware.hdmi.HdmiPortInfo> getPortInfo();
     method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public String getPowerControlMode();
     method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public String getPowerStateChangeOnActiveSourceLost();
     method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getRoutingControl();
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSadPresenceInQuery(@NonNull String);
+    method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSoundbarMode();
     method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient();
     method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSystemAudioControl();
     method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSystemAudioModeMuting();
@@ -4098,6 +4269,7 @@
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void removeHdmiCecEnabledChangeListener(@NonNull android.hardware.hdmi.HdmiControlManager.CecSettingChangeListener);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void removeHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener);
     method public void setActiveSource(@NonNull android.hardware.hdmi.HdmiDeviceInfo);
+    method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setEarcEnabled(@NonNull int);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setHdmiCecEnabled(@NonNull int);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setHdmiCecVersion(@NonNull int);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setHdmiCecVolumeControlEnabled(int);
@@ -4106,6 +4278,7 @@
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setRoutingControl(@NonNull int);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSadPresenceInQuery(@NonNull String, int);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSadsPresenceInQuery(@NonNull java.util.List<java.lang.String>, int);
+    method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSoundbarMode(int);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setStandbyMode(boolean);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSystemAudioControl(@NonNull int);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSystemAudioModeMuting(@NonNull int);
@@ -4133,6 +4306,7 @@
     field public static final String CEC_SETTING_NAME_QUERY_SAD_TRUEHD = "query_sad_truehd";
     field public static final String CEC_SETTING_NAME_QUERY_SAD_WMAPRO = "query_sad_wmapro";
     field public static final String CEC_SETTING_NAME_ROUTING_CONTROL = "routing_control";
+    field public static final String CEC_SETTING_NAME_SOUNDBAR_MODE = "soundbar_mode";
     field public static final String CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL = "system_audio_control";
     field public static final String CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING = "system_audio_mode_muting";
     field public static final String CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP = "tv_send_standby_on_sleep";
@@ -4152,6 +4326,8 @@
     field public static final int DEVICE_EVENT_ADD_DEVICE = 1; // 0x1
     field public static final int DEVICE_EVENT_REMOVE_DEVICE = 2; // 0x2
     field public static final int DEVICE_EVENT_UPDATE_DEVICE = 3; // 0x3
+    field public static final int EARC_FEATURE_DISABLED = 0; // 0x0
+    field public static final int EARC_FEATURE_ENABLED = 1; // 0x1
     field public static final String EXTRA_MESSAGE_EXTRA_PARAM1 = "android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1";
     field public static final String EXTRA_MESSAGE_ID = "android.hardware.hdmi.extra.MESSAGE_ID";
     field public static final int HDMI_CEC_CONTROL_DISABLED = 0; // 0x0
@@ -4211,6 +4387,9 @@
     field public static final int RESULT_TIMEOUT = 1; // 0x1
     field public static final int ROUTING_CONTROL_DISABLED = 0; // 0x0
     field public static final int ROUTING_CONTROL_ENABLED = 1; // 0x1
+    field public static final String SETTING_NAME_EARC_ENABLED = "earc_enabled";
+    field public static final int SOUNDBAR_MODE_DISABLED = 0; // 0x0
+    field public static final int SOUNDBAR_MODE_ENABLED = 1; // 0x1
     field public static final int SYSTEM_AUDIO_CONTROL_DISABLED = 0; // 0x0
     field public static final int SYSTEM_AUDIO_CONTROL_ENABLED = 1; // 0x1
     field public static final int SYSTEM_AUDIO_MODE_MUTING_DISABLED = 0; // 0x0
@@ -4385,7 +4564,7 @@
 
   public class HdmiSwitchClient extends android.hardware.hdmi.HdmiClient {
     method public int getDeviceType();
-    method @NonNull public java.util.List<android.hardware.hdmi.HdmiPortInfo> getPortInfo();
+    method @Deprecated @NonNull public java.util.List<android.hardware.hdmi.HdmiPortInfo> getPortInfo();
     method public void selectPort(int, @NonNull android.hardware.hdmi.HdmiSwitchClient.OnSelectListener);
     method public void selectPort(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.hdmi.HdmiSwitchClient.OnSelectListener);
   }
@@ -4466,6 +4645,34 @@
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendKeyEvent(@NonNull android.hardware.input.VirtualKeyEvent);
   }
 
+  public final class VirtualDpadConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualDpadConfig> CREATOR;
+  }
+
+  public static final class VirtualDpadConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualDpadConfig.Builder> {
+    ctor public VirtualDpadConfig.Builder();
+    method @NonNull public android.hardware.input.VirtualDpadConfig build();
+  }
+
+  public abstract class VirtualInputDeviceConfig {
+    ctor protected VirtualInputDeviceConfig(@NonNull android.hardware.input.VirtualInputDeviceConfig.Builder<? extends android.hardware.input.VirtualInputDeviceConfig.Builder<?>>);
+    ctor protected VirtualInputDeviceConfig(@NonNull android.os.Parcel);
+    method public int getAssociatedDisplayId();
+    method @NonNull public String getInputDeviceName();
+    method public int getProductId();
+    method public int getVendorId();
+  }
+
+  public abstract static class VirtualInputDeviceConfig.Builder<T extends android.hardware.input.VirtualInputDeviceConfig.Builder<T>> {
+    ctor public VirtualInputDeviceConfig.Builder();
+    method @NonNull public T setAssociatedDisplayId(int);
+    method @NonNull public T setInputDeviceName(@NonNull String);
+    method @NonNull public T setProductId(int);
+    method @NonNull public T setVendorId(int);
+  }
+
   public final class VirtualKeyEvent implements android.os.Parcelable {
     method public int describeContents();
     method public int getAction();
@@ -4488,6 +4695,17 @@
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendKeyEvent(@NonNull android.hardware.input.VirtualKeyEvent);
   }
 
+  public final class VirtualKeyboardConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualKeyboardConfig> CREATOR;
+  }
+
+  public static final class VirtualKeyboardConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualKeyboardConfig.Builder> {
+    ctor public VirtualKeyboardConfig.Builder();
+    method @NonNull public android.hardware.input.VirtualKeyboardConfig build();
+  }
+
   public class VirtualMouse implements java.io.Closeable {
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.graphics.PointF getCursorPosition();
@@ -4518,6 +4736,17 @@
     method @NonNull public android.hardware.input.VirtualMouseButtonEvent.Builder setButtonCode(int);
   }
 
+  public final class VirtualMouseConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualMouseConfig> CREATOR;
+  }
+
+  public static final class VirtualMouseConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualMouseConfig.Builder> {
+    ctor public VirtualMouseConfig.Builder();
+    method @NonNull public android.hardware.input.VirtualMouseConfig build();
+  }
+
   public final class VirtualMouseRelativeEvent implements android.os.Parcelable {
     method public int describeContents();
     method public float getRelativeX();
@@ -4584,6 +4813,21 @@
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendTouchEvent(@NonNull android.hardware.input.VirtualTouchEvent);
   }
 
+  public final class VirtualTouchscreenConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getHeightInPixels();
+    method public int getWidthInPixels();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualTouchscreenConfig> CREATOR;
+  }
+
+  public static final class VirtualTouchscreenConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualTouchscreenConfig.Builder> {
+    ctor public VirtualTouchscreenConfig.Builder();
+    method @NonNull public android.hardware.input.VirtualTouchscreenConfig build();
+    method @NonNull public android.hardware.input.VirtualTouchscreenConfig.Builder setHeightInPixels(int);
+    method @NonNull public android.hardware.input.VirtualTouchscreenConfig.Builder setWidthInPixels(int);
+  }
+
 }
 
 package android.hardware.lights {
@@ -5438,6 +5682,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setCurrentFunctions(long);
     field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_ACCESSORY_HANDSHAKE = "android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE";
     field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED";
+    field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_COMPLIANCE_CHANGED = "android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED";
     field public static final String ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE";
     field public static final String EXTRA_ACCESSORY_HANDSHAKE_END = "android.hardware.usb.extra.ACCESSORY_HANDSHAKE_END";
     field public static final String EXTRA_ACCESSORY_START = "android.hardware.usb.extra.ACCESSORY_START";
@@ -5465,6 +5710,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USB) public android.hardware.usb.UsbPortStatus getStatus();
     method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void resetUsbPort(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setRoles(int, int);
+    method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public boolean supportsComplianceWarnings();
     field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL = 1; // 0x1
     field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED = 2; // 0x2
     field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER = 4; // 0x4
@@ -5490,6 +5736,7 @@
 
   public final class UsbPortStatus implements android.os.Parcelable {
     method public int describeContents();
+    method @CheckResult @NonNull public int[] getComplianceWarnings();
     method public int getCurrentDataRole();
     method public int getCurrentMode();
     method public int getCurrentPowerRole();
@@ -5500,6 +5747,10 @@
     method public boolean isPowerTransferLimited();
     method public boolean isRoleCombinationSupported(int, int);
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int COMPLIANCE_WARNING_BC_1_2 = 3; // 0x3
+    field public static final int COMPLIANCE_WARNING_DEBUG_ACCESSORY = 2; // 0x2
+    field public static final int COMPLIANCE_WARNING_MISSING_RP = 4; // 0x4
+    field public static final int COMPLIANCE_WARNING_OTHER = 1; // 0x1
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.usb.UsbPortStatus> CREATOR;
     field public static final int DATA_ROLE_DEVICE = 2; // 0x2
     field public static final int DATA_ROLE_HOST = 1; // 0x1
@@ -5552,6 +5803,28 @@
     method @NonNull public android.location.CorrelationVector.Builder setSamplingWidthMeters(@FloatRange(from=0.0f, fromInclusive=false) double);
   }
 
+  public final class Country implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getCountryIso();
+    method public int getSource();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int COUNTRY_SOURCE_LOCALE = 3; // 0x3
+    field public static final int COUNTRY_SOURCE_LOCATION = 1; // 0x1
+    field public static final int COUNTRY_SOURCE_NETWORK = 0; // 0x0
+    field public static final int COUNTRY_SOURCE_SIM = 2; // 0x2
+    field @NonNull public static final android.os.Parcelable.Creator<android.location.Country> CREATOR;
+  }
+
+  public class CountryDetector {
+    method public void addCountryListener(@NonNull android.location.CountryListener, @Nullable android.os.Looper);
+    method @Nullable public android.location.Country detectCountry();
+    method public void removeCountryListener(@NonNull android.location.CountryListener);
+  }
+
+  public interface CountryListener {
+    method public void onCountryDetected(@NonNull android.location.Country);
+  }
+
   public final class GnssCapabilities implements android.os.Parcelable {
     method @Deprecated public boolean hasMeasurementCorrectionsReflectingPane();
     method @Deprecated public boolean hasNavMessages();
@@ -6207,7 +6480,7 @@
   }
 
   public class AudioDeviceVolumeManager {
-    ctor public AudioDeviceVolumeManager(@NonNull android.content.Context);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.VolumeInfo getDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
   }
 
@@ -6343,12 +6616,20 @@
   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_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
@@ -6536,8 +6817,9 @@
     method public int getMaxVolumeIndex();
     method public int getMinVolumeIndex();
     method public int getStreamType();
-    method @Nullable public android.media.audiopolicy.AudioVolumeGroup getVolumeGroup();
+    method @NonNull public android.media.audiopolicy.AudioVolumeGroup getVolumeGroup();
     method public int getVolumeIndex();
+    method public boolean hasMuteCommand();
     method public boolean hasStreamType();
     method public boolean hasVolumeGroup();
     method public boolean isMuted();
@@ -7120,6 +7402,7 @@
 
   public class Tuner implements java.lang.AutoCloseable {
     ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @Nullable String, int);
+    method public int applyFrontend(@NonNull android.media.tv.tuner.frontend.FrontendInfo);
     method public int cancelScanning();
     method public int cancelTuning();
     method public void clearOnTuneEventListener();
@@ -7140,6 +7423,7 @@
     method @NonNull public java.util.List<android.media.tv.tuner.frontend.FrontendStatusReadiness> getFrontendStatusReadiness(@NonNull int[]);
     method @IntRange(from=0xffffffff) public int getMaxNumberOfFrontends(int);
     method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean hasUnusedFrontend(int);
+    method public boolean isLnaSupported();
     method public boolean isLowestPriority(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
     method @Nullable public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
@@ -7150,7 +7434,6 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_SHARED_FILTER) public static android.media.tv.tuner.filter.SharedFilter openSharedFilter(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.filter.SharedFilterCallback);
     method @Nullable public android.media.tv.tuner.filter.TimeFilter openTimeFilter();
     method public int removeOutputPid(@IntRange(from=0) int);
-    method public int requestFrontendById(int);
     method public int scan(@NonNull android.media.tv.tuner.frontend.FrontendSettings, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.ScanCallback);
     method public int setLnaEnabled(boolean);
     method public int setMaxNumberOfFrontends(int, @IntRange(from=0) int);
@@ -7339,6 +7622,7 @@
     field public static final int VIDEO_STREAM_TYPE_VC1 = 7; // 0x7
     field public static final int VIDEO_STREAM_TYPE_VP8 = 8; // 0x8
     field public static final int VIDEO_STREAM_TYPE_VP9 = 9; // 0x9
+    field public static final int VIDEO_STREAM_TYPE_VVC = 13; // 0xd
   }
 
   public static class AvSettings.Builder {
@@ -7531,6 +7815,7 @@
     field public static final int INDEX_TYPE_SC = 1; // 0x1
     field public static final int INDEX_TYPE_SC_AVC = 3; // 0x3
     field public static final int INDEX_TYPE_SC_HEVC = 2; // 0x2
+    field public static final int INDEX_TYPE_SC_VVC = 4; // 0x4
     field public static final int MPT_INDEX_AUDIO = 262144; // 0x40000
     field public static final int MPT_INDEX_MPT = 65536; // 0x10000
     field public static final int MPT_INDEX_TIMESTAMP_TARGET_AUDIO = 1048576; // 0x100000
@@ -7553,6 +7838,13 @@
     field public static final int SC_INDEX_SEQUENCE = 8; // 0x8
     field public static final int SC_INDEX_SI_SLICE = 128; // 0x80
     field public static final int SC_INDEX_SP_SLICE = 256; // 0x100
+    field public static final int SC_VVC_INDEX_AUD = 64; // 0x40
+    field public static final int SC_VVC_INDEX_SLICE_CRA = 4; // 0x4
+    field public static final int SC_VVC_INDEX_SLICE_GDR = 8; // 0x8
+    field public static final int SC_VVC_INDEX_SLICE_IDR_N_LP = 2; // 0x2
+    field public static final int SC_VVC_INDEX_SLICE_IDR_W_RADL = 1; // 0x1
+    field public static final int SC_VVC_INDEX_SPS = 32; // 0x20
+    field public static final int SC_VVC_INDEX_VPS = 16; // 0x10
     field public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG = 4096; // 0x1000
     field public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED = 8; // 0x8
     field public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED = 4; // 0x4
@@ -9131,6 +9423,7 @@
   }
 
   public class WifiNl80211Manager {
+    ctor public WifiNl80211Manager(@NonNull android.content.Context, @NonNull android.os.IBinder);
     method public void abortScan(@NonNull String);
     method public void enableVerboseLogging(boolean);
     method @NonNull public int[] getChannelsMhzForBand(int);
@@ -9670,6 +9963,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 +9972,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);
@@ -9830,6 +10123,10 @@
     field public static final int STATUS_WAITING_REBOOT = 5; // 0x5
   }
 
+  public final class Trace {
+    field public static final long TRACE_TAG_AIDL = 16777216L; // 0x1000000L
+  }
+
   public class UpdateEngine {
     ctor public UpdateEngine();
     method @NonNull @WorkerThread public android.os.UpdateEngine.AllocateSpaceResult allocateSpace(@NonNull String, @NonNull String[]);
@@ -9914,6 +10211,8 @@
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.NewUserResponse createUser(@NonNull android.os.NewUserRequest);
     method @NonNull public java.util.List<android.os.UserHandle> getAllProfiles();
     method @NonNull public java.util.List<android.os.UserHandle> getEnabledProfiles();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getMainUser();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getPreviousForegroundUser();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableProfileCount(@NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableUserCount(@NonNull String);
@@ -9927,15 +10226,17 @@
     method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public int getUserRestrictionSource(String, android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle);
     method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public int getUserSwitchability();
+    method @NonNull @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public java.util.Set<android.os.UserHandle> getVisibleUsers();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isAdminUser();
     method public boolean isCloneProfile();
     method public boolean isCredentialSharableWithParent();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isGuestUser();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isMainUser();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int);
     method public boolean isMediaSharedWithParent();
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isPrimaryUser();
+    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isPrimaryUser();
     method public static boolean isRemoveResultSuccessful(int);
     method public boolean isRestrictedProfile();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public boolean isRestrictedProfile(@NonNull android.os.UserHandle);
@@ -9943,6 +10244,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isUserOfType(@NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public boolean isUserVisible();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
@@ -10121,7 +10423,7 @@
     method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID, @RequiresPermission int) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public int getExternalStorageMountMode(int, @NonNull String);
     method public static boolean hasIsolatedStorage();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException;
+    method public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException;
     field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
     field public static final int MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE = 4; // 0x4
     field public static final int MOUNT_MODE_EXTERNAL_DEFAULT = 1; // 0x1
@@ -10406,7 +10708,7 @@
   }
 
   public final class DeviceConfig {
-    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
+    method public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean deleteProperty(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static boolean getBoolean(@NonNull String, @NonNull String, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static float getFloat(@NonNull String, @NonNull String, float);
@@ -10414,17 +10716,22 @@
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static long getLong(@NonNull String, @NonNull String, long);
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...);
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getProperty(@NonNull String, @NonNull String);
+    method @NonNull public static java.util.List<java.lang.String> getPublicNamespaces();
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static int getSyncDisabledMode();
     method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties) throws android.provider.DeviceConfig.BadConfigException;
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void setSyncDisabledMode(int);
     field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
     field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot";
     field public static final String NAMESPACE_ADSERVICES = "adservices";
     field public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE = "ambient_context_manager_service";
+    field public static final String NAMESPACE_ANDROID = "android";
     field public static final String NAMESPACE_APPSEARCH = "appsearch";
     field public static final String NAMESPACE_APP_COMPAT = "app_compat";
+    field public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
     field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation";
     field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
     field public static final String NAMESPACE_AUTOFILL = "autofill";
@@ -10436,12 +10743,16 @@
     field public static final String NAMESPACE_CAPTIVEPORTALLOGIN = "captive_portal_login";
     field public static final String NAMESPACE_CLIPBOARD = "clipboard";
     field public static final String NAMESPACE_CONNECTIVITY = "connectivity";
+    field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
     field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
     field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot";
     field public static final String NAMESPACE_DISPLAY_MANAGER = "display_manager";
     field public static final String NAMESPACE_GAME_DRIVER = "game_driver";
+    field public static final String NAMESPACE_HDMI_CONTROL = "hdmi_control";
+    field public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager";
     field public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
     field public static final String NAMESPACE_INTELLIGENCE_ATTENTION = "intelligence_attention";
+    field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
     field public static final String NAMESPACE_LMKD_NATIVE = "lmkd_native";
     field public static final String NAMESPACE_LOCATION = "location";
     field public static final String NAMESPACE_MEDIA = "media";
@@ -10463,6 +10774,7 @@
     field public static final String NAMESPACE_RUNTIME_NATIVE_BOOT = "runtime_native_boot";
     field public static final String NAMESPACE_SCHEDULER = "scheduler";
     field public static final String NAMESPACE_SDK_SANDBOX = "sdk_sandbox";
+    field public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
     field public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
     field public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
     field public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
@@ -10478,6 +10790,7 @@
     field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
     field public static final String NAMESPACE_TRANSPARENCY_METADATA = "transparency_metadata";
     field public static final String NAMESPACE_UWB = "uwb";
+    field public static final String NAMESPACE_WEARABLE_SENSING = "wearable_sensing";
     field public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot";
   }
 
@@ -11889,11 +12202,39 @@
     method public abstract void onStopUpdates();
     method public final void reportPermanentFailure(@NonNull Throwable);
     method public final void reportSuggestion(@NonNull android.service.timezone.TimeZoneProviderSuggestion);
+    method public final void reportSuggestion(@NonNull android.service.timezone.TimeZoneProviderSuggestion, @NonNull android.service.timezone.TimeZoneProviderStatus);
     method public final void reportUncertain();
+    method public final void reportUncertain(@NonNull android.service.timezone.TimeZoneProviderStatus);
     field public static final String PRIMARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = "android.service.timezone.PrimaryLocationTimeZoneProviderService";
     field public static final String SECONDARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = "android.service.timezone.SecondaryLocationTimeZoneProviderService";
   }
 
+  public final class TimeZoneProviderStatus implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getConnectivityDependencyStatus();
+    method public int getLocationDetectionDependencyStatus();
+    method public int getTimeZoneResolutionOperationStatus();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.timezone.TimeZoneProviderStatus> CREATOR;
+    field public static final int DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT = 4; // 0x4
+    field public static final int DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS = 6; // 0x6
+    field public static final int DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS = 5; // 0x5
+    field public static final int DEPENDENCY_STATUS_NOT_APPLICABLE = 1; // 0x1
+    field public static final int DEPENDENCY_STATUS_OK = 2; // 0x2
+    field public static final int DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE = 3; // 0x3
+    field public static final int OPERATION_STATUS_FAILED = 3; // 0x3
+    field public static final int OPERATION_STATUS_NOT_APPLICABLE = 1; // 0x1
+    field public static final int OPERATION_STATUS_OK = 2; // 0x2
+  }
+
+  public static final class TimeZoneProviderStatus.Builder {
+    ctor public TimeZoneProviderStatus.Builder();
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus build();
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setConnectivityDependencyStatus(int);
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setLocationDetectionDependencyStatus(int);
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setTimeZoneResolutionOperationStatus(int);
+  }
+
   public final class TimeZoneProviderSuggestion implements android.os.Parcelable {
     method public int describeContents();
     method public long getElapsedRealtimeMillis();
@@ -12060,6 +12401,7 @@
     method @Nullable public android.media.AudioTimestamp getTimestamp();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordAudioStream> CREATOR;
+    field public static final String KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES = "android.service.voice.key.AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES";
   }
 
   public static final class HotwordAudioStream.Builder {
@@ -12085,6 +12427,7 @@
     method public static int getMaxScore();
     method @Nullable public android.media.MediaSyncEvent getMediaSyncEvent();
     method public int getPersonalizedScore();
+    method public int getProximity();
     method public int getScore();
     method public boolean isHotwordDetectionPersonalized();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -12098,6 +12441,9 @@
     field public static final int CONFIDENCE_LEVEL_VERY_HIGH = 6; // 0x6
     field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordDetectedResult> CREATOR;
     field public static final int HOTWORD_OFFSET_UNSET = -1; // 0xffffffff
+    field public static final int PROXIMITY_FAR = 2; // 0x2
+    field public static final int PROXIMITY_NEAR = 1; // 0x1
+    field public static final int PROXIMITY_UNKNOWN = -1; // 0xffffffff
   }
 
   public static final class HotwordDetectedResult.Builder {
@@ -12186,7 +12532,7 @@
 
   public class WallpaperService.Engine {
     method public boolean isInAmbientMode();
-    method public void onAmbientModeChanged(boolean, long);
+    method @MainThread public void onAmbientModeChanged(boolean, long);
   }
 
 }
@@ -12228,6 +12574,18 @@
 
 }
 
+package android.service.wearable {
+
+  public abstract class WearableSensingService extends android.app.Service {
+    ctor public WearableSensingService();
+    method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method @BinderThread public abstract void onDataProvided(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @BinderThread public abstract void onDataStreamProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    field public static final String SERVICE_INTERFACE = "android.service.wearable.WearableSensingService";
+  }
+
+}
+
 package android.telecom {
 
   @Deprecated public class AudioState implements android.os.Parcelable {
@@ -12434,6 +12792,8 @@
     field public static final int CS_BOUND = 6; // 0x6
     field public static final int DIRECT_TO_VM_FINISHED = 103; // 0x67
     field public static final int DIRECT_TO_VM_INITIATED = 102; // 0x66
+    field public static final int DND_CHECK_COMPLETED = 110; // 0x6e
+    field public static final int DND_CHECK_INITIATED = 109; // 0x6d
     field public static final int FILTERING_COMPLETED = 107; // 0x6b
     field public static final int FILTERING_INITIATED = 106; // 0x6a
     field public static final int FILTERING_TIMED_OUT = 108; // 0x6c
@@ -12473,6 +12833,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telecom.ParcelableCallAnalytics.EventTiming> CREATOR;
     field public static final int DIRECT_TO_VM_FINISHED_TIMING = 8; // 0x8
     field public static final int DISCONNECT_TIMING = 2; // 0x2
+    field public static final int DND_PRE_CALL_PRE_CHECK_TIMING = 12; // 0xc
     field public static final int FILTERING_COMPLETED_TIMING = 10; // 0xa
     field public static final int FILTERING_TIMED_OUT_TIMING = 11; // 0xb
     field public static final int HOLD_TIMING = 3; // 0x3
@@ -12640,14 +13001,14 @@
     method @NonNull public android.telephony.BarringInfo createLocationInfoSanitizedCopy();
   }
 
-  public final class CallAttributes implements android.os.Parcelable {
-    ctor public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality);
-    method public int describeContents();
-    method @NonNull public android.telephony.CallQuality getCallQuality();
-    method public int getNetworkType();
-    method @NonNull public android.telephony.PreciseCallState getPreciseCallState();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
+  @Deprecated public final class CallAttributes implements android.os.Parcelable {
+    ctor @Deprecated public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality);
+    method @Deprecated public int describeContents();
+    method @Deprecated @NonNull public android.telephony.CallQuality getCallQuality();
+    method @Deprecated public int getNetworkType();
+    method @Deprecated @NonNull public android.telephony.PreciseCallState getPreciseCallState();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
+    field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
   }
 
   public final class CallForwardingInfo implements android.os.Parcelable {
@@ -12727,6 +13088,28 @@
     method @NonNull public android.telephony.CallQuality.Builder setUplinkCallQualityLevel(int);
   }
 
+  public final class CallState implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.telephony.CallQuality getCallQuality();
+    method public int getCallState();
+    method public int getImsCallServiceType();
+    method @Nullable public String getImsCallSessionId();
+    method public int getImsCallType();
+    method public int getNetworkType();
+    method public void writeToParcel(@Nullable android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallState> CREATOR;
+  }
+
+  public static final class CallState.Builder {
+    ctor public CallState.Builder(int);
+    method @NonNull public android.telephony.CallState build();
+    method @NonNull public android.telephony.CallState.Builder setCallQuality(@Nullable android.telephony.CallQuality);
+    method @NonNull public android.telephony.CallState.Builder setImsCallServiceType(int);
+    method @NonNull public android.telephony.CallState.Builder setImsCallSessionId(@Nullable String);
+    method @NonNull public android.telephony.CallState.Builder setImsCallType(int);
+    method @NonNull public android.telephony.CallState.Builder setNetworkType(int);
+  }
+
   public class CarrierConfigManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName();
     method @NonNull public static android.os.PersistableBundle getDefaultConfig();
@@ -13113,6 +13496,7 @@
     field public static final int PRECISE_CALL_STATE_HOLDING = 2; // 0x2
     field public static final int PRECISE_CALL_STATE_IDLE = 0; // 0x0
     field public static final int PRECISE_CALL_STATE_INCOMING = 5; // 0x5
+    field public static final int PRECISE_CALL_STATE_INCOMING_SETUP = 9; // 0x9
     field public static final int PRECISE_CALL_STATE_NOT_VALID = -1; // 0xffffffff
     field public static final int PRECISE_CALL_STATE_WAITING = 6; // 0x6
   }
@@ -13476,7 +13860,8 @@
   }
 
   public static interface TelephonyCallback.CallAttributesListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
+    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallStatesChanged(@NonNull java.util.List<android.telephony.CallState>);
   }
 
   public static interface TelephonyCallback.DataEnabledListener {
@@ -13704,6 +14089,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
@@ -14118,6 +14504,7 @@
     ctor public QualifiedNetworksService.NetworkAvailabilityProvider(int);
     method public abstract void close();
     method public final int getSlotIndex();
+    method public void reportEmergencyDataNetworkPreferredTransportChanged(int);
     method public void reportThrottleStatusChanged(@NonNull java.util.List<android.telephony.data.ThrottleStatus>);
     method public final void updateQualifiedNetworkTypes(int, @NonNull java.util.List<java.lang.Integer>);
   }
@@ -14200,7 +14587,8 @@
     field public static final int RESULT_CALLER_NOT_ALLOWED = -3; // 0xfffffffd
     field public static final int RESULT_EUICC_NOT_FOUND = -2; // 0xfffffffe
     field public static final int RESULT_OK = 0; // 0x0
-    field public static final int RESULT_PROFILE_NOT_FOUND = 1; // 0x1
+    field public static final int RESULT_PROFILE_DOES_NOT_EXIST = -4; // 0xfffffffc
+    field @Deprecated public static final int RESULT_PROFILE_NOT_FOUND = 1; // 0x1
     field public static final int RESULT_UNKNOWN_ERROR = -1; // 0xffffffff
   }
 
@@ -14574,6 +14962,7 @@
     field public static final int CALL_RESTRICT_CAUSE_HD = 3; // 0x3
     field public static final int CALL_RESTRICT_CAUSE_NONE = 0; // 0x0
     field public static final int CALL_RESTRICT_CAUSE_RAT = 1; // 0x1
+    field public static final int CALL_TYPE_NONE = 0; // 0x0
     field public static final int CALL_TYPE_VIDEO_N_VOICE = 3; // 0x3
     field public static final int CALL_TYPE_VOICE = 2; // 0x2
     field public static final int CALL_TYPE_VOICE_N_VIDEO = 1; // 0x1
@@ -15367,6 +15756,16 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.SipMessage> CREATOR;
   }
 
+  public final class SrvccCall implements android.os.Parcelable {
+    ctor public SrvccCall(@NonNull String, int, @NonNull android.telephony.ims.ImsCallProfile);
+    method public int describeContents();
+    method @NonNull public String getCallId();
+    method @NonNull public android.telephony.ims.ImsCallProfile getImsCallProfile();
+    method public int getPreciseCallState();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.SrvccCall> CREATOR;
+  }
+
 }
 
 package android.telephony.ims.feature {
@@ -15427,6 +15826,10 @@
     method public final void notifyCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.MmTelFeature.MmTelCapabilities);
     method public final void notifyIncomingCall(@NonNull android.telephony.ims.stub.ImsCallSessionImplBase, @NonNull android.os.Bundle);
     method public final void notifyRejectedCall(@NonNull android.telephony.ims.ImsCallProfile, @NonNull android.telephony.ims.ImsReasonInfo);
+    method public void notifySrvccCanceled();
+    method public void notifySrvccCompleted();
+    method public void notifySrvccFailed();
+    method public void notifySrvccStarted(@NonNull java.util.function.Consumer<java.util.List<android.telephony.ims.SrvccCall>>);
     method public final void notifyVoiceMessageCountUpdate(int);
     method public void onFeatureReady();
     method public void onFeatureRemoved();
@@ -15624,6 +16027,7 @@
   public class ImsSmsImplBase {
     ctor public ImsSmsImplBase();
     method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int);
+    method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int, @NonNull byte[]);
     method public void acknowledgeSmsReport(int, @IntRange(from=0, to=65535) int, int);
     method public String getSmsFormat();
     method public void onReady();
@@ -15921,10 +16325,23 @@
 
 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();
+    method @NonNull public final java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAndEnabledServices();
+    method @NonNull public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
+    method public void interrupt();
+    method public void onAccessibilityEvent(@NonNull android.view.accessibility.AccessibilityEvent);
+    method public void onProxyConnected();
+    method public void setInstalledAndEnabledServices(@NonNull java.util.List<android.accessibilityservice.AccessibilityServiceInfo>);
+  }
+
   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/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 025e862..47588d9 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -3,6 +3,10 @@
     Method should return Collection<CharSequence> (or subclass) instead of raw array; was `java.lang.CharSequence[]`
 
 
+ExecutorRegistration: android.location.CountryDetector#addCountryListener(android.location.CountryListener, android.os.Looper):
+    Registration methods should have overload that accepts delivery Executor: `addCountryListener`
+
+
 GenericException: android.app.prediction.AppPredictor#finalize():
     Methods must not throw generic exceptions (`java.lang.Throwable`)
 GenericException: android.hardware.location.ContextHubClient#finalize():
@@ -127,6 +131,8 @@
     SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.PackageItemInfo.dumpFront) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.content.pm.ResolveInfo#dump(android.util.Printer, String):
     SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.ResolveInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.location.CountryDetector#addCountryListener(android.location.CountryListener, android.os.Looper):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.location.CountryDetector.addCountryListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.location.Location#dump(android.util.Printer, String):
     SAM-compatible parameters (such as parameter 1, "pw", in android.location.Location.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ef74a3e..43d4530 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";
@@ -142,11 +143,7 @@
     field public static final int PROCESS_CAPABILITY_ALL = 15; // 0xf
     field public static final int PROCESS_CAPABILITY_ALL_EXPLICIT = 1; // 0x1
     field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6
-    field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2
-    field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1
-    field public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 4; // 0x4
     field public static final int PROCESS_CAPABILITY_NETWORK = 8; // 0x8
-    field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0
     field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4
     field public static final int PROCESS_STATE_TOP = 2; // 0x2
     field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff
@@ -419,7 +416,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 {
@@ -443,6 +440,7 @@
     method @NonNull public java.util.Set<java.lang.String> getAdoptedShellPermissions();
     method @Deprecated public boolean grantRuntimePermission(String, String, android.os.UserHandle);
     method public boolean injectInputEvent(@NonNull android.view.InputEvent, boolean, boolean);
+    method public void injectInputEventToInputFilter(@NonNull android.view.InputEvent);
     method @Deprecated public boolean revokeRuntimePermission(String, String, android.os.UserHandle);
     method public void syncInputTransactions();
     method public void syncInputTransactions(boolean);
@@ -462,6 +460,8 @@
 
   public class WallpaperManager {
     method @Nullable public android.graphics.Bitmap getBitmap();
+    method @Nullable public android.graphics.Rect peekBitmapDimensions();
+    method @Nullable public android.graphics.Rect peekBitmapDimensions(int);
     method public boolean shouldEnableWideColorGamut();
     method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
   }
@@ -757,6 +757,7 @@
     method public int getUserId();
     method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
     method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
+    field public static final String ATTENTION_SERVICE = "attention";
     field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
     field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
     field public static final String DREAM_SERVICE = "dream";
@@ -861,10 +862,6 @@
     field public static final String SYSTEM_SHARED_LIBRARY_SHARED = "android.ext.shared";
   }
 
-  public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
-    field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
-  }
-
   public final class ProviderInfoList implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public static android.content.pm.ProviderInfoList fromList(@NonNull java.util.List<android.content.pm.ProviderInfo>);
@@ -903,6 +900,7 @@
     method public boolean isFull();
     method public boolean isGuest();
     method public boolean isInitialized();
+    method public boolean isMain();
     method public boolean isManagedProfile();
     method public boolean isPrimary();
     method public boolean isProfile();
@@ -921,6 +919,7 @@
     field public static final int FLAG_FULL = 1024; // 0x400
     field @Deprecated public static final int FLAG_GUEST = 4; // 0x4
     field public static final int FLAG_INITIALIZED = 16; // 0x10
+    field public static final int FLAG_MAIN = 16384; // 0x4000
     field @Deprecated public static final int FLAG_MANAGED_PROFILE = 32; // 0x20
     field public static final int FLAG_PRIMARY = 1; // 0x1
     field public static final int FLAG_PROFILE = 4096; // 0x1000
@@ -1041,6 +1040,7 @@
     method @NonNull public static android.util.Pair<java.util.List<android.graphics.Typeface>,java.util.List<android.graphics.Typeface>> changeDefaultFontForTest(@NonNull java.util.List<android.graphics.Typeface>, @NonNull java.util.List<android.graphics.Typeface>);
     method @NonNull public static long[] deserializeFontMap(@NonNull java.nio.ByteBuffer, @NonNull java.util.Map<java.lang.String,android.graphics.Typeface>) throws java.io.IOException;
     method @Nullable public static android.os.SharedMemory getSystemFontMapSharedMemory();
+    method public void releaseNativeObjectForTest();
     method @NonNull public static android.os.SharedMemory serializeFontMap(@NonNull java.util.Map<java.lang.String,android.graphics.Typeface>) throws android.system.ErrnoException, java.io.IOException;
   }
 
@@ -1251,7 +1251,9 @@
     method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode();
     field public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; // 0x2
     field public static final int SWITCHING_TYPE_NONE = 0; // 0x0
+    field public static final int SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY = 3; // 0x3
     field public static final int SWITCHING_TYPE_WITHIN_GROUPS = 1; // 0x1
+    field public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 16384; // 0x4000
     field public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 512; // 0x200
   }
 
@@ -1315,6 +1317,14 @@
 
 }
 
+package android.hardware.location {
+
+  public final class ContextHubManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public long[] getPreloadedNanoAppIds(@NonNull android.hardware.location.ContextHubInfo);
+  }
+
+}
+
 package android.hardware.soundtrigger {
 
   public class KeyphraseEnrollmentInfo {
@@ -1501,13 +1511,33 @@
     method public static boolean isEncodingLinearPcm(int);
   }
 
+  public final class AudioHalVersionInfo implements java.lang.Comparable<android.media.AudioHalVersionInfo> android.os.Parcelable {
+    method public int compareTo(@NonNull android.media.AudioHalVersionInfo);
+    method public int describeContents();
+    method public int getHalType();
+    method public int getMajorVersion();
+    method public int getMinorVersion();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.media.AudioHalVersionInfo AIDL_1_0;
+    field public static final int AUDIO_HAL_TYPE_AIDL = 1; // 0x1
+    field public static final int AUDIO_HAL_TYPE_HIDL = 0; // 0x0
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioHalVersionInfo> CREATOR;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_2_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_4_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_5_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_6_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_7_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_7_1;
+    field @NonNull public static final java.util.List<android.media.AudioHalVersionInfo> VERSIONS;
+  }
+
   public class AudioManager {
     method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
     method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat);
     method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
     method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFadeOutDurationOnFocusLossMillis(@NonNull android.media.AudioAttributes);
-    method @Nullable public static String getHalVersion();
+    method @Nullable public static android.media.AudioHalVersionInfo getHalVersion();
     method public static final int[] getPublicStreamTypes();
     method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
     method public int getStreamMinVolumeInt(int);
@@ -1897,6 +1927,7 @@
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
     method public static boolean isSplitSystemUser();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String);
     method public boolean isUsersOnSecondaryDisplaysSupported();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
   }
@@ -1906,6 +1937,7 @@
   }
 
   public abstract class VibrationEffect implements android.os.Parcelable {
+    method @Nullable public abstract long[] computeCreateWaveformOffOnTimingsOrNull();
     method public static android.os.VibrationEffect get(int);
     method public static android.os.VibrationEffect get(int, boolean);
     method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context);
@@ -1920,6 +1952,7 @@
   }
 
   public static final class VibrationEffect.Composed extends android.os.VibrationEffect {
+    method @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull();
     method public long getDuration();
     method public int getRepeatIndex();
     method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments();
@@ -2177,17 +2210,6 @@
     field public static final android.net.Uri CORP_CONTENT_URI;
   }
 
-  public final class DeviceConfig {
-    field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
-    field public static final String NAMESPACE_ANDROID = "android";
-    field public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
-    field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
-    field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
-    field public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager";
-    field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
-    field public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
-  }
-
   public interface InputMethodManagerDeviceConfig {
     field public static final String KEY_HIDE_IME_WHEN_NO_EDITOR_FOCUS = "hide_ime_when_no_editor_focus";
   }
@@ -2496,6 +2518,10 @@
     method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setKeyphraseRecognitionExtras(@NonNull java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
   }
 
+  public abstract class HotwordDetectionService extends android.app.Service {
+    field public static final boolean ENABLE_PROXIMITY_RESULT = true;
+  }
+
   public final class VisibleActivityInfo implements android.os.Parcelable {
     ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
   }
@@ -2619,13 +2645,23 @@
     method public int getCarrierIdListVersion();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<java.lang.String> getCertsFromCarrierPrivilegeAccessRules();
     method @NonNull public java.util.List<android.telephony.data.ApnSetting> getDevicePolicyOverrideApns(@NonNull android.content.Context);
+    method @NonNull public android.util.Pair<java.lang.Integer,java.lang.Integer> getHalVersion(int);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
-    method public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
+    method @Deprecated public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
     method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
     method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
     method public void setCarrierTestOverride(String, String, String, String, String, String, String, String, String);
     method @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
+    field public static final int HAL_SERVICE_DATA = 1; // 0x1
+    field public static final int HAL_SERVICE_IMS = 7; // 0x7
+    field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
+    field public static final int HAL_SERVICE_MODEM = 3; // 0x3
+    field public static final int HAL_SERVICE_NETWORK = 4; // 0x4
+    field public static final int HAL_SERVICE_SIM = 5; // 0x5
+    field public static final int HAL_SERVICE_VOICE = 6; // 0x6
+    field public static final android.util.Pair HAL_VERSION_UNKNOWN;
+    field public static final android.util.Pair HAL_VERSION_UNSUPPORTED;
     field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff
   }
 
@@ -2869,6 +2905,10 @@
     method @NonNull public android.view.Display.Mode.Builder setResolution(int, int);
   }
 
+  public final class DisplayShape implements android.os.Parcelable {
+    method @NonNull public static android.view.DisplayShape fromSpecString(@NonNull String, float, int, int);
+  }
+
   public class FocusFinder {
     method public static void sort(android.view.View[], int, int, android.view.ViewGroup, boolean);
   }
@@ -3156,12 +3196,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
   }
 
@@ -3251,6 +3291,7 @@
 
   public class Spinner extends android.widget.AbsSpinner implements android.content.DialogInterface.OnClickListener {
     method public boolean isPopupShowing();
+    method public void onClick(int);
   }
 
   @android.widget.RemoteViews.RemoteView public class TextClock extends android.widget.TextView {
@@ -3293,6 +3334,18 @@
 
 package android.window {
 
+  public final class BackNavigationInfo implements android.os.Parcelable {
+    method @NonNull public static String typeToString(int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.window.BackNavigationInfo> CREATOR;
+    field public static final String KEY_TRIGGER_BACK = "TriggerBack";
+    field public static final int TYPE_CALLBACK = 4; // 0x4
+    field public static final int TYPE_CROSS_ACTIVITY = 2; // 0x2
+    field public static final int TYPE_CROSS_TASK = 3; // 0x3
+    field public static final int TYPE_DIALOG_CLOSE = 0; // 0x0
+    field public static final int TYPE_RETURN_TO_HOME = 1; // 0x1
+    field public static final int TYPE_UNDEFINED = -1; // 0xffffffff
+  }
+
   public final class DisplayAreaAppearedInfo implements android.os.Parcelable {
     ctor public DisplayAreaAppearedInfo(@NonNull android.window.DisplayAreaInfo, @NonNull android.view.SurfaceControl);
     method public int describeContents();
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index f9b8a30..8e21d8c 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -567,6 +567,10 @@
     Missing nullability on parameter `destAddress` in method `checkSmsShortCodeDestination`
 MissingNullability: android.telephony.SmsManager#checkSmsShortCodeDestination(String, String) parameter #1:
     Missing nullability on parameter `countryIso` in method `checkSmsShortCodeDestination`
+MissingNullability: android.telephony.TelephonyManager#HAL_VERSION_UNKNOWN:
+    Missing nullability on field `HAL_VERSION_UNKNOWN` in class `class android.telephony.TelephonyManager`
+MissingNullability: android.telephony.TelephonyManager#HAL_VERSION_UNSUPPORTED:
+    Missing nullability on field `HAL_VERSION_UNSUPPORTED` in class `class android.telephony.TelephonyManager`
 MissingNullability: android.telephony.TelephonyManager#getLine1AlphaTag():
     Missing nullability on method `getLine1AlphaTag` return
 MissingNullability: android.telephony.TelephonyManager#getRadioHalVersion():
diff --git a/core/java/Android.bp b/core/java/Android.bp
index af0fa39..128e3de 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -417,6 +417,17 @@
     ],
 }
 
+// This file group is used by service fuzzer
+filegroup {
+    name: "framework-core-sources-for-fuzzers",
+    srcs: [
+        "android/os/IInterface.java",
+        "android/os/Binder.java",
+        "android/os/IBinder.java",
+        "android/os/Parcelable.java",
+    ],
+}
+
 aidl_interface {
     name: "android.os.statsbootstrap_aidl",
     unstable: true,
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index e86d2f3..968ed87 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -54,9 +54,10 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
+import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
-import android.view.SurfaceView;
+import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.WindowManagerImpl;
 import android.view.accessibility.AccessibilityCache;
@@ -696,7 +697,8 @@
             ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
             ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
             ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT,
-            ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY
+            ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
+            ERROR_TAKE_SCREENSHOT_INVALID_WINDOW
     })
     public @interface ScreenshotErrorCode {}
 
@@ -728,6 +730,18 @@
     public static final int ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY = 4;
 
     /**
+     * The status of taking screenshot is failure and the reason is invalid accessibility window Id.
+     */
+    public static final int ERROR_TAKE_SCREENSHOT_INVALID_WINDOW = 5;
+
+    /**
+     * The status of taking screenshot is failure and the reason is the window contains secure
+     * content.
+     * @see WindowManager.LayoutParams#FLAG_SECURE
+     */
+    public static final int ERROR_TAKE_SCREENSHOT_SECURE_WINDOW = 6;
+
+    /**
      * The interval time of calling
      * {@link AccessibilityService#takeScreenshot(int, Executor, Consumer)} API.
      * @hide
@@ -784,6 +798,8 @@
 
     private FingerprintGestureController mFingerprintGestureController;
 
+    private int mMotionEventSources;
+
     /**
      * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
      *
@@ -807,7 +823,11 @@
             for (int i = 0; i < mMagnificationControllers.size(); i++) {
                 mMagnificationControllers.valueAt(i).onServiceConnectedLocked();
             }
-            updateInputMethod(getServiceInfo());
+            final AccessibilityServiceInfo info = getServiceInfo();
+            if (info != null) {
+                updateInputMethod(info);
+                mMotionEventSources = info.getMotionEventSources();
+            }
         }
         if (mSoftKeyboardController != null) {
             mSoftKeyboardController.onServiceConnected();
@@ -933,6 +953,25 @@
     }
 
     /**
+     * Callback that allows an accessibility service to observe generic {@link MotionEvent}s.
+     * <p>
+     * Prefer {@link TouchInteractionController} to observe and control touchscreen events,
+     * including touch gestures. If this or any enabled service is using
+     * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} then
+     * {@link #onMotionEvent} will not receive touchscreen events.
+     * </p>
+     * <p>
+     * <strong>Note:</strong> The service must first request to listen to events using
+     * {@link AccessibilityServiceInfo#setMotionEventSources}.
+     * {@link MotionEvent}s from sources in {@link AccessibilityServiceInfo#getMotionEventSources()}
+     * are not sent to the rest of the system. To stop listening to events from a given source, call
+     * {@link AccessibilityServiceInfo#setMotionEventSources} with that source removed.
+     * </p>
+     * @param event The event to be processed.
+     */
+    public void onMotionEvent(@NonNull MotionEvent event) { }
+
+    /**
      * Gets the windows on the screen of the default display. This method returns only the windows
      * that a sighted user can interact with, as opposed to all windows.
      * For example, if there is a modal dialog shown and the user cannot touch
@@ -2454,8 +2493,8 @@
      * </p>
      * <p>
      * <strong>Note:</strong> If the view with {@link AccessibilityNodeInfo#FOCUS_INPUT}
-     * is on an embedded view hierarchy which is embedded in a {@link SurfaceView} via
-     * {@link SurfaceView#setChildSurfacePackage}, there is a limitation that this API
+     * is on an embedded view hierarchy which is embedded in a {@link android.view.SurfaceView} via
+     * {@link android.view.SurfaceView#setChildSurfacePackage}, there is a limitation that this API
      * won't be able to find the node for the view. It's because views don't know about
      * the embedded hierarchies. Instead, you could traverse all the nodes to find the
      * focus.
@@ -2508,6 +2547,7 @@
     public final void setServiceInfo(AccessibilityServiceInfo info) {
         mInfo = info;
         updateInputMethod(info);
+        mMotionEventSources = info.getMotionEventSources();
         sendServiceInfo();
     }
 
@@ -2568,6 +2608,7 @@
      * @param executor Executor on which to run the callback.
      * @param callback The callback invoked when taking screenshot has succeeded or failed.
      *                 See {@link TakeScreenshotCallback} for details.
+     * @see #takeScreenshotOfWindow
      */
     public void takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
             @NonNull TakeScreenshotCallback callback) {
@@ -2589,7 +2630,8 @@
                 final HardwareBuffer hardwareBuffer =
                         result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, android.hardware.HardwareBuffer.class);
                 final ParcelableColorSpace colorSpace =
-                        result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, android.graphics.ParcelableColorSpace.class);
+                        result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE,
+                                android.graphics.ParcelableColorSpace.class);
                 final ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer,
                         colorSpace.getColorSpace(),
                         result.getLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP));
@@ -2601,6 +2643,37 @@
     }
 
     /**
+     * Takes a screenshot of the specified window and returns it via an
+     * {@link AccessibilityService.ScreenshotResult}. You can use {@link Bitmap#wrapHardwareBuffer}
+     * to construct the bitmap from the ScreenshotResult's payload.
+     * <p>
+     * <strong>Note:</strong> In order to take screenshots your service has
+     * to declare the capability to take screenshot by setting the
+     * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+     * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+     * </p>
+     * <p>
+     * Both this method and {@link #takeScreenshot} can be used for machine learning-based visual
+     * screen understanding. Use <code>takeScreenshotOfWindow</code> if your target window might be
+     * visually underneath an accessibility overlay (from your or another accessibility service) in
+     * order to capture the window contents without the screenshot being covered by the overlay
+     * contents drawn on the screen.
+     * </p>
+     *
+     * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
+     * @param executor Executor on which to run the callback.
+     * @param callback The callback invoked when taking screenshot has succeeded or failed.
+     *                 See {@link TakeScreenshotCallback} for details.
+     * @see #takeScreenshot
+     */
+    public void takeScreenshotOfWindow(int accessibilityWindowId,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull TakeScreenshotCallback callback) {
+        AccessibilityInteractionClient.getInstance(this).takeScreenshotOfWindow(
+                        mConnectionId, accessibilityWindowId, executor, callback);
+    }
+
+    /**
      * Sets the strokeWidth and color of the accessibility focus rectangle.
      * <p>
      * <strong>Note:</strong> This setting persists until this or another active
@@ -2678,7 +2751,7 @@
 
             @Override
             public void onMotionEvent(MotionEvent event) {
-                AccessibilityService.this.onMotionEvent(event);
+                AccessibilityService.this.sendMotionEventToCallback(event);
             }
 
             @Override
@@ -3113,7 +3186,8 @@
         private final @NonNull ColorSpace mColorSpace;
         private final long mTimestamp;
 
-        private ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
+        /** @hide */
+        public ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
                 @NonNull ColorSpace colorSpace, long timestamp) {
             Preconditions.checkNotNull(hardwareBuffer, "hardwareBuffer cannot be null");
             Preconditions.checkNotNull(colorSpace, "colorSpace cannot be null");
@@ -3312,14 +3386,23 @@
         }
     }
 
-    void onMotionEvent(MotionEvent event) {
-        TouchInteractionController controller;
-        synchronized (mLock) {
-            int displayId = event.getDisplayId();
-            controller = mTouchInteractionControllers.get(displayId);
+    void sendMotionEventToCallback(MotionEvent event) {
+        boolean sendingTouchEventToTouchInteractionController = false;
+        if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
+            TouchInteractionController controller;
+            synchronized (mLock) {
+                int displayId = event.getDisplayId();
+                controller = mTouchInteractionControllers.get(displayId);
+            }
+            if (controller != null) {
+                sendingTouchEventToTouchInteractionController = true;
+                controller.onMotionEvent(event);
+            }
         }
-        if (controller != null) {
-            controller.onMotionEvent(event);
+        final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
+        if ((mMotionEventSources & eventSourceWithoutClass) != 0
+                && !sendingTouchEventToTouchInteractionController) {
+            onMotionEvent(event);
         }
     }
 
@@ -3332,4 +3415,28 @@
             controller.onStateChanged(state);
         }
     }
+
+    /**
+     * Attaches a {@link android.view.SurfaceControl} containing an accessibility overlay to the
+     * specified display. This type of overlay should be used for content that does not need to
+     * track the location and size of Views in the currently active app e.g. service configuration
+     * or general service UI. To remove this overlay and free the associated resources, use
+     * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.
+     *
+     * @param displayId the display to which the SurfaceControl should be attached.
+     * @param sc the SurfaceControl containing the overlay content
+     */
+    public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) {
+        Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
+        final IAccessibilityServiceConnection connection =
+                AccessibilityInteractionClient.getConnection(mConnectionId);
+        if (connection == null) {
+            return;
+        }
+        try {
+            connection.attachAccessibilityOverlayToDisplay(displayId, sc);
+        } catch (RemoteException re) {
+            throw new RuntimeException(re);
+        }
+    }
 }
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 295eaaf..ae57959 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -612,6 +612,12 @@
     private boolean mIsAccessibilityTool = false;
 
     /**
+     * The bit mask of {@link android.view.InputDevice} sources that the accessibility
+     * service wants to listen to for generic {@link android.view.MotionEvent}s.
+     */
+    private int mMotionEventSources = 0;
+
+    /**
      * Creates a new instance.
      */
     public AccessibilityServiceInfo() {
@@ -785,6 +791,7 @@
         mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout;
         mInteractiveUiTimeout = other.mInteractiveUiTimeout;
         flags = other.flags;
+        mMotionEventSources = other.mMotionEventSources;
         // NOTE: Ensure that only properties that are safe to be modified by the service itself
         // are included here (regardless of hidden setters, etc.).
     }
@@ -956,6 +963,44 @@
     }
 
     /**
+     * Returns the bit mask of {@link android.view.InputDevice} sources that the accessibility
+     * service wants to listen to for generic {@link android.view.MotionEvent}s.
+     */
+    public int getMotionEventSources() {
+        return mMotionEventSources;
+    }
+
+    /**
+     * Sets the bit mask of {@link android.view.InputDevice} sources that the accessibility
+     * service wants to listen to for generic {@link android.view.MotionEvent}s.
+     *
+     * <p>
+     * Note: including an {@link android.view.InputDevice} source that does not send
+     * {@link android.view.MotionEvent}s is effectively a no-op for that source, since you will
+     * not receive any events from that source.
+     * </p>
+     * <p>
+     * Allowed sources include:
+     * <li>{@link android.view.InputDevice#SOURCE_MOUSE}</li>
+     * <li>{@link android.view.InputDevice#SOURCE_STYLUS}</li>
+     * <li>{@link android.view.InputDevice#SOURCE_BLUETOOTH_STYLUS}</li>
+     * <li>{@link android.view.InputDevice#SOURCE_TRACKBALL}</li>
+     * <li>{@link android.view.InputDevice#SOURCE_MOUSE_RELATIVE}</li>
+     * <li>{@link android.view.InputDevice#SOURCE_TOUCHPAD}</li>
+     * <li>{@link android.view.InputDevice#SOURCE_TOUCH_NAVIGATION}</li>
+     * <li>{@link android.view.InputDevice#SOURCE_ROTARY_ENCODER}</li>
+     * <li>{@link android.view.InputDevice#SOURCE_JOYSTICK}</li>
+     * <li>{@link android.view.InputDevice#SOURCE_SENSOR}</li>
+     * </p>
+     *
+     * @param motionEventSources A bit mask of {@link android.view.InputDevice} sources.
+     * @see AccessibilityService#onMotionEvent
+     */
+    public void setMotionEventSources(int motionEventSources) {
+        mMotionEventSources = motionEventSources;
+    }
+
+    /**
      * The localized summary of the accessibility service.
      * <p>
      *    <strong>Statically set from
@@ -1179,6 +1224,7 @@
         parcel.writeBoolean(mIsAccessibilityTool);
         parcel.writeString(mTileServiceName);
         parcel.writeInt(mIntroResId);
+        parcel.writeInt(mMotionEventSources);
     }
 
     private void initFromParcel(Parcel parcel) {
@@ -1203,6 +1249,7 @@
         mIsAccessibilityTool = parcel.readBoolean();
         mTileServiceName = parcel.readString();
         mIntroResId = parcel.readInt();
+        mMotionEventSources = parcel.readInt();
     }
 
     @Override
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 9abce3a..da13e73 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -25,11 +25,13 @@
 import android.os.RemoteCallback;
 import android.view.MagnificationSpec;
 import android.view.MotionEvent;
+import android.view.SurfaceControl;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 import android.view.accessibility.AccessibilityWindowInfo;
 import java.util.List;
+import android.window.ScreenCapture;
 
 /**
  * Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService.
@@ -122,6 +124,10 @@
 
     void takeScreenshot(int displayId, in RemoteCallback callback);
 
+    void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+        in ScreenCapture.ScreenCaptureListener listener,
+        IAccessibilityInteractionConnectionCallback callback);
+
     void setGestureDetectionPassthroughRegion(int displayId, in Region region);
 
     void setTouchExplorationPassthroughRegion(int displayId, in Region region);
@@ -150,4 +156,5 @@
     void setInstalledAndEnabledServices(in List<AccessibilityServiceInfo> infos);
 
     List<AccessibilityServiceInfo> getInstalledAndEnabledServices();
+    void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl sc);
 }
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index c2c065b..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";
@@ -154,6 +154,8 @@
         public void addAccount(IAccountAuthenticatorResponse response, String accountType,
                 String authTokenType, String[] features, Bundle options)
                 throws RemoteException {
+            super.addAccount_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "addAccount: accountType " + accountType
                         + ", authTokenType " + authTokenType
@@ -184,6 +186,8 @@
         @Override
         public void confirmCredentials(IAccountAuthenticatorResponse response,
                 Account account, Bundle options) throws RemoteException {
+            super.confirmCredentials_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "confirmCredentials: " + account);
             }
@@ -210,6 +214,8 @@
         public void getAuthTokenLabel(IAccountAuthenticatorResponse response,
                 String authTokenType)
                 throws RemoteException {
+            super.getAuthTokenLabel_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "getAuthTokenLabel: authTokenType " + authTokenType);
             }
@@ -235,6 +241,8 @@
         public void getAuthToken(IAccountAuthenticatorResponse response,
                 Account account, String authTokenType, Bundle loginOptions)
                 throws RemoteException {
+            super.getAuthToken_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "getAuthToken: " + account
                         + ", authTokenType " + authTokenType);
@@ -262,6 +270,8 @@
         @Override
         public void updateCredentials(IAccountAuthenticatorResponse response, Account account,
                 String authTokenType, Bundle loginOptions) throws RemoteException {
+            super.updateCredentials_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "updateCredentials: " + account
                         + ", authTokenType " + authTokenType);
@@ -291,6 +301,8 @@
         @Override
         public void editProperties(IAccountAuthenticatorResponse response,
                 String accountType) throws RemoteException {
+            super.editProperties_enforcePermission();
+
             try {
                 final Bundle result = AbstractAccountAuthenticator.this.editProperties(
                     new AccountAuthenticatorResponse(response), accountType);
@@ -306,6 +318,8 @@
         @Override
         public void hasFeatures(IAccountAuthenticatorResponse response,
                 Account account, String[] features) throws RemoteException {
+            super.hasFeatures_enforcePermission();
+
             try {
                 final Bundle result = AbstractAccountAuthenticator.this.hasFeatures(
                     new AccountAuthenticatorResponse(response), account, features);
@@ -321,6 +335,8 @@
         @Override
         public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response,
                 Account account) throws RemoteException {
+            super.getAccountRemovalAllowed_enforcePermission();
+
             try {
                 final Bundle result = AbstractAccountAuthenticator.this.getAccountRemovalAllowed(
                     new AccountAuthenticatorResponse(response), account);
@@ -336,6 +352,8 @@
         @Override
         public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response,
                 Account account) throws RemoteException {
+            super.getAccountCredentialsForCloning_enforcePermission();
+
             try {
                 final Bundle result =
                         AbstractAccountAuthenticator.this.getAccountCredentialsForCloning(
@@ -353,6 +371,8 @@
         public void addAccountFromCredentials(IAccountAuthenticatorResponse response,
                 Account account,
                 Bundle accountCredentials) throws RemoteException {
+            super.addAccountFromCredentials_enforcePermission();
+
             try {
                 final Bundle result =
                         AbstractAccountAuthenticator.this.addAccountFromCredentials(
@@ -371,6 +391,8 @@
         public void startAddAccountSession(IAccountAuthenticatorResponse response,
                 String accountType, String authTokenType, String[] features, Bundle options)
                 throws RemoteException {
+            super.startAddAccountSession_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG,
                         "startAddAccountSession: accountType " + accountType
@@ -403,6 +425,8 @@
                 Account account,
                 String authTokenType,
                 Bundle loginOptions) throws RemoteException {
+            super.startUpdateCredentialsSession_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "startUpdateCredentialsSession: "
                         + account
@@ -441,6 +465,8 @@
                 IAccountAuthenticatorResponse response,
                 String accountType,
                 Bundle sessionBundle) throws RemoteException {
+            super.finishSession_enforcePermission();
+
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "finishSession: accountType " + accountType);
             }
@@ -468,6 +494,8 @@
                 IAccountAuthenticatorResponse response,
                 Account account,
                 String statusToken) throws RemoteException {
+            super.isCredentialsUpdateSuggested_enforcePermission();
+
             try {
                 final Bundle result = AbstractAccountAuthenticator.this
                         .isCredentialsUpdateSuggested(
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index a573776..dbdee07 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
@@ -655,6 +623,9 @@
      *
      * <p>No permission is required to call this method.
      *
+     * <p>Caller targeting API level 34 and above, the results are filtered
+     * by the rules of <a href="/training/basics/intents/package-visibility">package visibility</a>.
+     *
      * @return An array of {@link AuthenticatorDescription} for every
      *     authenticator known to the AccountManager service.  Empty (never
      *     null) if no authenticators are known.
@@ -731,7 +702,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);
     }
 
@@ -1061,7 +1032,9 @@
      * @param password The password to associate with the account, null for none
      * @param extras String values to use for the account's userdata, null for none
      * @param visibility Map from packageName to visibility values which will be set before account
-     *        is added. See {@link #getAccountVisibility} for possible values.
+     *        is added. See {@link #getAccountVisibility} for possible values. Declaring
+     *        <a href="/training/basics/intents/package-visibility">package visibility</a> needs for
+     *        package names in the map is needed, if the caller is targeting API level 34 and above.
      *
      * @return True if the account was successfully added, false if the account already exists, the
      *         account is null, or another error occurs.
@@ -1145,7 +1118,9 @@
      * the specified account.
      *
      * @param account {@link Account} to update visibility
-     * @param packageName Package name of the application to modify account visibility
+     * @param packageName Package name of the application to modify account visibility. Declaring
+     *        <a href="/training/basics/intents/package-visibility">package visibility</a> needs
+     *        for it is needed, if the caller is targeting API level 34 and above.
      * @param visibility New visibility value
      *
      * @return True, if visibility value was successfully updated.
@@ -1177,7 +1152,9 @@
      * @param account {@link Account} to get visibility
      * @param packageName Package name of the application to get account visibility
      *
-     * @return int Visibility of given account.
+     * @return int Visibility of given account. For the caller targeting API level 34 and above,
+     * {@link #VISIBILITY_NOT_VISIBLE} is returned if the given package is filtered by the rules of
+     * <a href="/training/basics/intents/package-visibility">package visibility</a>.
      */
     public @AccountVisibility int getAccountVisibility(Account account, String packageName) {
         if (account == null)
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index 4d4a4d7..e447d86 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -402,7 +402,7 @@
                 mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage,
                         mCallingUid);
                 intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
-                startActivityForResult(intent, REQUEST_ADD_ACCOUNT);
+                startActivityForResult(new Intent(intent), REQUEST_ADD_ACCOUNT);
                 return;
             }
         } catch (OperationCanceledException e) {
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java
index 1403ba2..df8a50a 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -19,10 +19,10 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.Log;
 import android.view.Choreographer;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
 /**
@@ -78,7 +78,7 @@
      * store visible (foreground) requestors; if the set size reaches zero, there are no
      * objects in the foreground and it is time to pause animators.
      */
-    private final ArraySet<Object> mAnimatorRequestors = new ArraySet<>();
+    private final ArrayList<WeakReference<Object>> mAnimatorRequestors = new ArrayList<>();
 
     private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
         @Override
@@ -141,19 +141,9 @@
      * tracking obsolete+enabled requestors.
      */
     public static void removeRequestor(Object requestor) {
-        getInstance().removeRequestorImpl(requestor);
-    }
-
-    private void removeRequestorImpl(Object requestor) {
-        // Also request disablement, in case that requestor was the sole object keeping
-        // animators un-paused
-        requestAnimatorsEnabled(false, requestor);
-        mAnimatorRequestors.remove(requestor);
+        getInstance().requestAnimatorsEnabledImpl(false, requestor);
         if (LOCAL_LOGV) {
-            Log.v(TAG, "removeRequestorImpl for " + requestor);
-            for (int i = 0; i < mAnimatorRequestors.size(); ++i) {
-                Log.v(TAG, "animatorRequesters " + i + " = " + mAnimatorRequestors.valueAt(i));
-            }
+            Log.v(TAG, "removeRequestor for " + requestor);
         }
     }
 
@@ -173,10 +163,36 @@
     private void requestAnimatorsEnabledImpl(boolean enable, Object requestor) {
         boolean wasEmpty = mAnimatorRequestors.isEmpty();
         setAnimatorPausingEnabled(isPauseBgAnimationsEnabledInSystemProperties());
-        if (enable) {
-            mAnimatorRequestors.add(requestor);
-        } else {
-            mAnimatorRequestors.remove(requestor);
+        synchronized (mAnimatorRequestors) {
+            // Only store WeakRef objects to avoid leaks
+            if (enable) {
+                // First, check whether such a reference is already on the list
+                WeakReference<Object> weakRef = null;
+                for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) {
+                    WeakReference<Object> ref = mAnimatorRequestors.get(i);
+                    Object referent = ref.get();
+                    if (referent == requestor) {
+                        weakRef = ref;
+                    } else if (referent == null) {
+                        // Remove any reference that has been cleared
+                        mAnimatorRequestors.remove(i);
+                    }
+                }
+                if (weakRef == null) {
+                    weakRef = new WeakReference<>(requestor);
+                    mAnimatorRequestors.add(weakRef);
+                }
+            } else {
+                for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) {
+                    WeakReference<Object> ref = mAnimatorRequestors.get(i);
+                    Object referent = ref.get();
+                    if (referent == requestor || referent == null) {
+                        // remove requested item or item that has been cleared
+                        mAnimatorRequestors.remove(i);
+                    }
+                }
+                // If a reference to the requestor wasn't in the list, nothing to remove
+            }
         }
         if (!sAnimatorPausingEnabled) {
             // Resume any animators that have been paused in the meantime, otherwise noop
@@ -198,9 +214,12 @@
             }
         }
         if (LOCAL_LOGV) {
-            Log.v(TAG, enable ? "enable" : "disable" + " animators for " + requestor);
+            Log.v(TAG, (enable ? "enable" : "disable") + " animators for " + requestor
+                    + " with pauseDelay of " + Animator.getBackgroundPauseDelay());
             for (int i = 0; i < mAnimatorRequestors.size(); ++i) {
-                Log.v(TAG, "animatorRequesters " + i + " = " + mAnimatorRequestors.valueAt(i));
+                Log.v(TAG, "animatorRequestors " + i + " = "
+                        + mAnimatorRequestors.get(i) + " with referent "
+                        + mAnimatorRequestors.get(i).get());
             }
         }
     }
@@ -219,12 +238,14 @@
             return;
         }
         for (int i = 0; i < mAnimationCallbacks.size(); ++i) {
-            Animator animator = ((Animator) mAnimationCallbacks.get(i));
-            if (animator != null
-                    && animator.getTotalDuration() == Animator.DURATION_INFINITE
-                    && !animator.isPaused()) {
-                mPausedAnimators.add(animator);
-                animator.pause();
+            AnimationFrameCallback callback = mAnimationCallbacks.get(i);
+            if (callback instanceof Animator) {
+                Animator animator = ((Animator) callback);
+                if (animator.getTotalDuration() == Animator.DURATION_INFINITE
+                        && !animator.isPaused()) {
+                    mPausedAnimators.add(animator);
+                    animator.pause();
+                }
             }
         }
     };
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 32d0d75..30ff052 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1685,7 +1685,7 @@
                 .isOnBackInvokedCallbackEnabled(this);
         if (aheadOfTimeBack) {
             // Add onBackPressed as default back behavior.
-            mDefaultBackCallback = this::navigateBack;
+            mDefaultBackCallback = this::onBackInvoked;
             getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
         }
     }
@@ -4003,22 +4003,19 @@
         if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) {
             return;
         }
-        navigateBack();
+        onBackInvoked();
     }
 
-    private void navigateBack() {
-        if (!isTaskRoot()) {
-            // If the activity is not the root of the task, allow finish to proceed normally.
-            finishAfterTransition();
-            return;
-        }
-        // Inform activity task manager that the activity received a back press while at the
-        // root of the task. This call allows ActivityTaskManager to intercept or move the task
-        // to the back.
-        ActivityClient.getInstance().onBackPressedOnTaskRoot(mToken,
+    private void onBackInvoked() {
+        // Inform activity task manager that the activity received a back press.
+        // This call allows ActivityTaskManager to intercept or move the task
+        // to the back when needed.
+        ActivityClient.getInstance().onBackPressed(mToken,
                 new RequestFinishCallback(new WeakReference<>(this)));
 
-        getAutofillClientController().onActivityBackPressed(mIntent);
+        if (isTaskRoot()) {
+            getAutofillClientController().onActivityBackPressed(mIntent);
+        }
     }
 
     /**
@@ -8364,11 +8361,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 +8418,7 @@
 
         mActivityTransitionState.enterReady(this);
         dispatchActivityPostStarted();
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
     /**
@@ -8423,7 +8427,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 +8463,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 +8483,6 @@
                     + mComponent.getClassName());
         }
         dispatchActivityPreResumed();
-        performRestart(true /* start */, reason);
 
         mFragments.execPendingActions();
 
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index d1e6780..324b8e7 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -227,12 +227,13 @@
     }
 
     /**
-     * Returns the windowing mode of the task that hosts the activity, or {@code -1} if task is not
-     * found.
+     * Returns the {@link Configuration} of the task which hosts the Activity, or {@code null} if
+     * the task {@link Configuration} cannot be obtained.
      */
-    public int getTaskWindowingMode(IBinder activityToken) {
+    @Nullable
+    public Configuration getTaskConfiguration(IBinder activityToken) {
         try {
-            return getActivityClientController().getTaskWindowingMode(activityToken);
+            return getActivityClientController().getTaskConfiguration(activityToken);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -525,9 +526,9 @@
         }
     }
 
-    void onBackPressedOnTaskRoot(IBinder token, IRequestFinishCallback callback) {
+    void onBackPressed(IBinder token, IRequestFinishCallback callback) {
         try {
-            getActivityClientController().onBackPressedOnTaskRoot(token, callback);
+            getActivityClientController().onBackPressed(token, callback);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d6c10ae..83963fd 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -628,19 +628,19 @@
     public @interface ProcessCapability {}
 
     /** @hide Process does not have any capability */
-    @TestApi
+    @SystemApi
     public static final int PROCESS_CAPABILITY_NONE = 0;
 
     /** @hide Process can access location while in foreground */
-    @TestApi
+    @SystemApi
     public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 0;
 
     /** @hide Process can access camera while in foreground */
-    @TestApi
+    @SystemApi
     public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 1;
 
     /** @hide Process can access microphone while in foreground */
-    @TestApi
+    @SystemApi
     public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2;
 
     /** @hide Process can access network despite any power saving resrictions */
@@ -4398,8 +4398,6 @@
      *
      * @throws UnsupportedOperationException if the device does not support background users on
      * secondary displays.
-     * @throws IllegalArgumentException if the display doesn't exist or is not a valid display to
-     * start secondary users on.
      *
      * @hide
      */
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 308e996..c51e8ae 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);
 
@@ -571,13 +577,6 @@
     public abstract void stopAppForUser(String pkg, @UserIdInt int userId);
 
     /**
-     * If the given app has any FGSs whose notifications are in the given channel,
-     * stop them.
-     */
-    public abstract void stopForegroundServicesForChannel(String pkg, @UserIdInt int userId,
-            String channelId);
-
-    /**
      * Registers the specified {@code processObserver} to be notified of future changes to
      * process state.
      */
@@ -643,6 +642,8 @@
      * using the rules of package visibility. Returns extras with legitimate package info that the
      * receiver is able to access, or {@code null} if none of the packages is visible to the
      * receiver.
+     * @param serialized Specifies whether or not the broadcast should be delivered to the
+     *                   receivers in a serial order.
      *
      * @see com.android.server.am.ActivityManagerService#broadcastIntentWithFeature(
      *      IApplicationThread, String, Intent, String, IIntentReceiver, int, String, Bundle,
@@ -656,6 +657,19 @@
             @Nullable Bundle bOptions);
 
     /**
+     * Variant of
+     * {@link #broadcastIntent(Intent, IIntentReceiver, String[], boolean, int, int[], BiFunction, Bundle)}
+     * that allows sender to receive a finish callback once the broadcast delivery is completed,
+     * but provides no ordering guarantee for how the broadcast is delivered to receivers.
+     */
+    public abstract int broadcastIntentWithCallback(Intent intent,
+            IIntentReceiver resultTo,
+            String[] requiredPermissions,
+            int userId, int[] appIdAllowList,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+            @Nullable Bundle bOptions);
+
+    /**
      * Add uid to the ActivityManagerService PendingStartActivityUids list.
      * @param uid uid
      * @param pid pid of the ProcessRecord that is pending top.
@@ -888,4 +902,9 @@
      */
     public abstract void registerNetworkPolicyUidObserver(@NonNull IUidObserver observer,
             int which, int cutpoint, @NonNull String callingPackage);
+
+    /**
+     * Return all client package names of a service.
+     */
+    public abstract ArraySet<String> getClientPackages(String servicePackageName);
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 67d4416..a4c9f8c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -40,7 +40,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
-import android.app.RemoteServiceException.CannotDeliverBroadcastException;
 import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
 import android.app.RemoteServiceException.CrashedByAdbException;
 import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
@@ -48,6 +47,8 @@
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
 import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
 import android.app.servertransaction.ActivityRelaunchItem;
@@ -173,7 +174,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 +244,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 +408,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 +436,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;
@@ -510,6 +502,7 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     static volatile Handler sMainThreadHandler;  // set once in main()
+    private long mStartSeq; // Only accesssed from the main thread
 
     Bundle mCoreSettings = null;
 
@@ -518,8 +511,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 +590,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;
 
@@ -805,7 +802,7 @@
         ApplicationInfo appInfo;
         int backupMode;
         int userId;
-        int operationType;
+        @BackupDestination int backupDestination;
         public String toString() {
             return "CreateBackupAgentData{appInfo=" + appInfo
                     + " backupAgent=" + appInfo.backupAgentName
@@ -1023,6 +1020,12 @@
         int flags;
     }
 
+    // A list of receivers and an index into the receiver to be processed next.
+    static final class ReceiverList {
+        List<ReceiverInfo> receivers;
+        int index;
+    }
+
     private class ApplicationThread extends IApplicationThread.Stub {
         private static final String DB_CONNECTION_INFO_HEADER = "  %8s %8s %14s %5s %5s %5s  %s";
         private static final String DB_CONNECTION_INFO_FORMAT = "  %8s %8s %14s %5d %5d %5d  %s";
@@ -1039,13 +1042,28 @@
             sendMessage(H.RECEIVER, r);
         }
 
+        public final void scheduleReceiverList(List<ReceiverInfo> info) throws RemoteException {
+            for (int i = 0; i < info.size(); i++) {
+                ReceiverInfo r = info.get(i);
+                if (r.registered) {
+                    scheduleRegisteredReceiver(r.receiver, r.intent,
+                            r.resultCode, r.data, r.extras, r.ordered, r.sticky,
+                            r.sendingUser, r.processState);
+                } else {
+                    scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
+                            r.resultCode, r.data, r.extras, r.sync,
+                            r.sendingUser, r.processState);
+                }
+            }
+        }
+
         public final void scheduleCreateBackupAgent(ApplicationInfo app,
-                int backupMode, int userId, int operationType) {
+                int backupMode, int userId, @BackupDestination int backupDestination) {
             CreateBackupAgentData d = new CreateBackupAgentData();
             d.appInfo = app;
             d.backupMode = backupMode;
             d.userId = userId;
-            d.operationType = operationType;
+            d.backupDestination = backupDestination;
 
             sendMessage(H.CREATE_BACKUP_AGENT, d);
         }
@@ -1111,6 +1129,11 @@
         }
 
         @Override
+        public final void scheduleTimeoutService(IBinder token, int startId) {
+            sendMessage(H.TIMEOUT_SERVICE, token, startId);
+        }
+
+        @Override
         public final void bindApplication(String processName, ApplicationInfo appInfo,
                 String sdkSandboxClientAppVolumeUuid, String sdkSandboxClientAppPackage,
                 ProviderInfoList providerList, ComponentName instrumentationName,
@@ -1999,9 +2022,6 @@
             case ForegroundServiceDidNotStartInTimeException.TYPE_ID:
                 throw generateForegroundServiceDidNotStartInTimeException(message, extras);
 
-            case CannotDeliverBroadcastException.TYPE_ID:
-                throw new CannotDeliverBroadcastException(message);
-
             case CannotPostForegroundServiceNotificationException.TYPE_ID:
                 throw new CannotPostForegroundServiceNotificationException(message);
 
@@ -2090,6 +2110,7 @@
         public static final int SET_CONTENT_CAPTURE_OPTIONS_CALLBACK = 164;
         public static final int DUMP_GFXINFO = 165;
         public static final int DUMP_RESOURCES = 166;
+        public static final int TIMEOUT_SERVICE = 167;
 
         public static final int INSTRUMENT_WITHOUT_RESTART = 170;
         public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
@@ -2144,6 +2165,7 @@
                     case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
                         return "FINISH_INSTRUMENTATION_WITHOUT_RESTART";
                     case DUMP_RESOURCES: return "DUMP_RESOURCES";
+                    case TIMEOUT_SERVICE: return "TIMEOUT_SERVICE";
                 }
             }
             return Integer.toString(code);
@@ -2210,6 +2232,11 @@
                     schedulePurgeIdler();
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     break;
+                case TIMEOUT_SERVICE:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceTimeout");
+                    handleTimeoutService((IBinder) msg.obj, msg.arg1);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
                 case CONFIGURATION_CHANGED:
                     mConfigurationController.handleConfigurationChanged((Configuration) msg.obj);
                     break;
@@ -3654,8 +3681,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 +3740,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()
@@ -4398,7 +4426,8 @@
                     context.setOuterContext(agent);
                     agent.attach(context);
 
-                    agent.onCreate(UserHandle.of(data.userId), data.operationType);
+                    agent.onCreate(UserHandle.of(data.userId), data.backupDestination,
+                            getOperationTypeFromBackupMode(data.backupMode));
                     binder = agent.onBind();
                     backupAgents.put(packageName, agent);
                 } catch (Exception e) {
@@ -4426,6 +4455,22 @@
         }
     }
 
+    @OperationType
+    private static int getOperationTypeFromBackupMode(int backupMode) {
+        switch (backupMode) {
+            case ApplicationThreadConstants.BACKUP_MODE_RESTORE:
+            case ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL:
+                return OperationType.RESTORE;
+            case ApplicationThreadConstants.BACKUP_MODE_FULL:
+            case ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL:
+                return OperationType.BACKUP;
+            default:
+                Slog.w(TAG, "Invalid backup mode when initialising BackupAgent: "
+                        + backupMode);
+                return OperationType.UNKNOWN;
+        }
+    }
+
     private String getBackupAgentName(CreateBackupAgentData data) {
         String agentName = data.appInfo.backupAgentName;
         // full backup operation but no app-supplied agent?  use the default implementation
@@ -4734,6 +4779,29 @@
         //Slog.i(TAG, "Running services: " + mServices);
     }
 
+    private void handleTimeoutService(IBinder token, int startId) {
+        Service s = mServices.get(token);
+        if (s != null) {
+            try {
+                if (localLOGV) Slog.v(TAG, "Timeout short service " + s);
+
+                // Unlike other service callbacks, we don't do serviceDoneExecuting() here.
+                // "service executing" state is used to boost the procstate / oom-adj, but
+                // for short-FGS timeout, we have a specific control for them anyway, so
+                // we don't have to do that.
+                s.callOnTimeout(startId);
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(s, e)) {
+                    throw new RuntimeException(
+                            "Unable to call onTimeout on service " + s
+                                    + ": " + e.toString(), e);
+                }
+                Slog.i(TAG, "handleTimeoutService: exception for " + token, e);
+            }
+        } else {
+            Slog.wtf(TAG, "handleTimeoutService: token=" + token + " not found.");
+        }
+    }
     /**
      * Resume the activity.
      * @param r Target activity record.
@@ -5268,7 +5336,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 +5353,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 +5501,6 @@
             }
         }
         r.setState(ON_DESTROY);
-        mLastReportedWindowingMode.remove(r.activity.getActivityToken());
         schedulePurgeIdler();
         synchronized (this) {
             if (mSplashScreenGlobal != null) {
@@ -5865,7 +5932,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 +5940,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 +5948,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 +6079,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 +6098,7 @@
         if (wasInMultiWindowMode != nowInMultiWindowMode) {
             activity.dispatchMultiWindowModeChanged(nowInMultiWindowMode, newConfiguration);
         }
-        mLastReportedWindowingMode.put(token, newWindowingMode);
+        r.mLastReportedWindowingMode = newWindowingMode;
     }
 
     /**
@@ -6384,23 +6450,28 @@
     }
 
     private void handleTrimMemory(int level) {
-        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory");
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory: " + level);
+        }
         if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);
 
-        if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
-            PropertyInvalidatedCache.onTrimMemory();
-        }
+        try {
+            if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
+                PropertyInvalidatedCache.onTrimMemory();
+            }
 
-        final ArrayList<ComponentCallbacks2> callbacks =
-                collectComponentCallbacks(true /* includeUiContexts */);
+            final ArrayList<ComponentCallbacks2> callbacks =
+                    collectComponentCallbacks(true /* includeUiContexts */);
 
-        final int N = callbacks.size();
-        for (int i = 0; i < N; i++) {
-            callbacks.get(i).onTrimMemory(level);
+            final int N = callbacks.size();
+            for (int i = 0; i < N; i++) {
+                callbacks.get(i).onTrimMemory(level);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         }
 
         WindowManagerGlobal.getInstance().trimMemory(level);
-        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
         if (SystemProperties.getInt("debug.am.run_gc_trim_level", Integer.MAX_VALUE) <= level) {
             unscheduleGcIdler();
@@ -6635,34 +6706,6 @@
         StrictMode.initThreadDefaults(data.appInfo);
         StrictMode.initVmDefaults(data.appInfo);
 
-        if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
-            // XXX should have option to change the port.
-            Debug.changeDebugPort(8100);
-            if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) {
-                Slog.w(TAG, "Application " + data.info.getPackageName()
-                      + " is waiting for the debugger on port 8100...");
-
-                IActivityManager mgr = ActivityManager.getService();
-                try {
-                    mgr.showWaitingForDebugger(mAppThread, true);
-                } catch (RemoteException ex) {
-                    throw ex.rethrowFromSystemServer();
-                }
-
-                Debug.waitForDebugger();
-
-                try {
-                    mgr.showWaitingForDebugger(mAppThread, false);
-                } catch (RemoteException ex) {
-                    throw ex.rethrowFromSystemServer();
-                }
-
-            } else {
-                Slog.w(TAG, "Application " + data.info.getPackageName()
-                      + " can be debugged on port 8100...");
-            }
-        }
-
         // Allow binder tracing, and application-generated systrace messages if we're profileable.
         boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
         boolean isAppProfileable = isAppDebuggable || data.appInfo.isProfileable();
@@ -6695,6 +6738,13 @@
             ii = null;
         }
 
+        final IActivityManager mgr = ActivityManager.getService();
+        try {
+            mgr.finishAttachApplication(mStartSeq);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+
         final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
         mConfigurationController.updateLocaleListFromAppContext(appContext);
 
@@ -6762,6 +6812,35 @@
         Application app;
         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
         final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
+
+        // Wait for debugger after we have notified the system to finish attach application
+        if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
+            // XXX should have option to change the port.
+            Debug.changeDebugPort(8100);
+            if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) {
+                Slog.w(TAG, "Application " + data.info.getPackageName()
+                        + " is waiting for the debugger on port 8100...");
+
+                try {
+                    mgr.showWaitingForDebugger(mAppThread, true);
+                } catch (RemoteException ex) {
+                    throw ex.rethrowFromSystemServer();
+                }
+
+                Debug.waitForDebugger();
+
+                try {
+                    mgr.showWaitingForDebugger(mAppThread, false);
+                } catch (RemoteException ex) {
+                    throw ex.rethrowFromSystemServer();
+                }
+
+            } else {
+                Slog.w(TAG, "Application " + data.info.getPackageName()
+                        + " can be debugged on port 8100...");
+            }
+        }
+
         try {
             // If the app is being launched for full backup or restore, bring it up in
             // a restricted environment with the base application class.
@@ -7602,6 +7681,8 @@
         sCurrentActivityThread = this;
         mConfigurationController = new ConfigurationController(this);
         mSystemThread = system;
+        mStartSeq = startSeq;
+
         if (!system) {
             android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                     UserHandle.myUserId());
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1b972e0..d5879fb 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -42,6 +42,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.database.DatabaseUtils;
+import android.healthconnect.HealthConnectManager;
 import android.media.AudioAttributes.AttributeUsage;
 import android.os.Binder;
 import android.os.Build;
@@ -1360,9 +1361,87 @@
      */
     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;
+
+    /**
+     * An app op for reading/writing health connect data.
+     *
+     * @hide
+     */
+    public static final int OP_READ_WRITE_HEALTH_DATA = AppProtoEnums.APP_OP_READ_WRITE_HEALTH_DATA;
+
+    /**
+     * Use foreground service with the type
+     * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
+     *
+     * @hide
+     */
+    public static final int OP_FOREGROUND_SERVICE_SPECIAL_USE =
+            AppProtoEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE;
+
+    /**
+     * Exempt from start foreground service from background restriction.
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final int OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION =
+            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION;
+
+    /**
+     * Exempt from start foreground service from background with while in user permission
+     * restriction.
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final int OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION =
+            AppProtoEnums
+                    .APP_OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION;
+
+    /**
+     * Hide foreground service stop button in quick settings.
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final int OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON =
+            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 123;
+    public static final int _NUM_OP = 131;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1833,6 +1912,23 @@
     @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";
+
+    /**
+     * An app op for reading/writing health connect data.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String OPSTR_READ_WRITE_HEALTH_DATA =
+            "android:read_write_health_data";
 
     /**
      * Record audio from near-field microphone (ie. TV remote)
@@ -1853,6 +1949,68 @@
      */
     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";
+
+    /**
+     * Start a foreground service with the type "specialUse".
+     *
+     * @hide
+     */
+    public static final String OPSTR_FOREGROUND_SERVICE_SPECIAL_USE =
+            "android:foreground_service_special_use";
+
+    /**
+     * Exempt from start foreground service from background restriction.
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final String OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION =
+            "android:system_exempt_from_fgs_bg_start_restriction";
+
+    /**
+     * Exempt from start foreground service from background with while in user permission
+     * restriction.
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final String
+            OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION =
+            "android:system_exempt_from_fgs_bg_start_while_in_use_permission_restriction";
+
+    /**
+     * Hide foreground service stop button in quick settings.
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final String OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON =
+            "android:system_exempt_from_fgs_stop_button";
+
     /** {@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} */
@@ -1948,6 +2106,8 @@
             OP_MANAGE_MEDIA,
             OP_TURN_SCREEN_ON,
             OP_RUN_LONG_JOBS,
+            OP_READ_MEDIA_VISUAL_USER_SELECTED,
+            OP_FOREGROUND_SERVICE_SPECIAL_USE,
     };
 
     static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2329,9 +2489,38 @@
                 "RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO").setDefaultMode(
                 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()
+                .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(),
+        new AppOpInfo.Builder(OP_READ_WRITE_HEALTH_DATA, OPSTR_READ_WRITE_HEALTH_DATA,
+                "READ_WRITE_HEALTH_DATA").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_FOREGROUND_SERVICE_SPECIAL_USE,
+                OPSTR_FOREGROUND_SERVICE_SPECIAL_USE, "FOREGROUND_SERVICE_SPECIAL_USE")
+                .setPermission(Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE).build(),
+        new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION,
+                OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION,
+                "SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION").build(),
+        new AppOpInfo.Builder(
+                OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION,
+                OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION,
+                "SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION")
+                .build(),
+        new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON,
+                OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON,
+                "SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON").build()
     };
 
+    // The number of longs needed to form a full bitmask of app ops
+    private static final int BITMASK_LEN = ((_NUM_OP - 1) / Long.SIZE) + 1;
+
     /**
      * @hide
      */
@@ -2366,8 +2555,8 @@
      * @see #getNotedOpCollectionMode
      * @see #collectNotedOpSync
      */
-    private static final ThreadLocal<ArrayMap<String, long[]>> sAppOpsNotedInThisBinderTransaction =
-            new ThreadLocal<>();
+    private static final ThreadLocal<ArrayMap<String, BitSet>>
+            sAppOpsNotedInThisBinderTransaction = new ThreadLocal<>();
 
     static {
         if (sAppOpInfos.length != _NUM_OP) {
@@ -2384,12 +2573,6 @@
                 sPermToOp.put(sAppOpInfos[op].permission, op);
             }
         }
-
-        if ((_NUM_OP + Long.SIZE - 1) / Long.SIZE != 2) {
-            // The code currently assumes that the length of sAppOpsNotedInThisBinderTransaction is
-            // two longs
-            throw new IllegalStateException("notedAppOps collection code assumes < 128 appops");
-        }
     }
 
     /** Config used to control app ops access messages sampling */
@@ -2486,7 +2669,14 @@
     @TestApi
     public static int permissionToOpCode(String permission) {
         Integer boxedOpCode = sPermToOp.get(permission);
-        return boxedOpCode != null ? boxedOpCode : OP_NONE;
+        if (boxedOpCode != null) {
+            return boxedOpCode;
+        }
+        if (permission != null && HealthConnectManager.isHealthPermission(
+                ActivityThread.currentApplication(), permission)) {
+            return OP_READ_WRITE_HEALTH_DATA;
+        }
+        return OP_NONE;
     }
 
     /**
@@ -7150,10 +7340,14 @@
      */
     public static @Nullable String permissionToOp(@NonNull String permission) {
         final Integer opCode = sPermToOp.get(permission);
-        if (opCode == null) {
-            return null;
+        if (opCode != null) {
+            return sAppOpInfos[opCode].name;
         }
-        return sAppOpInfos[opCode].name;
+        if (HealthConnectManager.isHealthPermission(ActivityThread.currentApplication(),
+                permission)) {
+            return sAppOpInfos[OP_READ_WRITE_HEALTH_DATA].name;
+        }
+        return null;
     }
 
     /**
@@ -8382,8 +8576,9 @@
      */
     public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
             @Nullable String message, boolean skipProxyOperation) {
-        return startProxyOpNoThrow(op, attributionSource, message, skipProxyOperation,
-                ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_CHAIN_ID_NONE);
+        return startProxyOpNoThrow(attributionSource.getToken(), op, attributionSource, message,
+                skipProxyOperation, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_FLAGS_NONE,
+                ATTRIBUTION_CHAIN_ID_NONE);
     }
 
     /**
@@ -8395,7 +8590,8 @@
      *
      * @hide
      */
-    public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
+    public int startProxyOpNoThrow(@NonNull IBinder clientId, int op,
+            @NonNull AttributionSource attributionSource,
             @Nullable String message, boolean skipProxyOperation, @AttributionFlags
             int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
             int attributionChainId) {
@@ -8413,7 +8609,7 @@
                 }
             }
 
-            SyncNotedAppOp syncOp = mService.startProxyOperation(op,
+            SyncNotedAppOp syncOp = mService.startProxyOperation(clientId, op,
                     attributionSource, false, collectionMode == COLLECT_ASYNC, message,
                     shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
                     proxiedAttributionFlags, attributionChainId);
@@ -8511,9 +8707,10 @@
      */
     public void finishProxyOp(@NonNull String op, int proxiedUid,
             @NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) {
-        finishProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
+        IBinder token = mContext.getAttributionSource().getToken();
+        finishProxyOp(token, op, new AttributionSource(mContext.getAttributionSource(),
                 new AttributionSource(proxiedUid, proxiedPackageName,  proxiedAttributionTag,
-                        mContext.getAttributionSource().getToken())), /*skipProxyOperation*/ false);
+                        token)), /*skipProxyOperation*/ false);
     }
 
     /**
@@ -8528,10 +8725,11 @@
      *
      * @hide
      */
-    public void finishProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource,
-            boolean skipProxyOperation) {
+    public void finishProxyOp(@NonNull IBinder clientId, @NonNull String op,
+            @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
         try {
-            mService.finishProxyOperation(strOpToOp(op), attributionSource, skipProxyOperation);
+            mService.finishProxyOperation(clientId, strOpToOp(op), attributionSource,
+                    skipProxyOperation);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -8613,10 +8811,10 @@
      */
     public static class PausedNotedAppOpsCollection {
         final int mUid;
-        final @Nullable ArrayMap<String, long[]> mCollectedNotedAppOps;
+        final @Nullable ArrayMap<String, BitSet> mCollectedNotedAppOps;
 
         PausedNotedAppOpsCollection(int uid, @Nullable ArrayMap<String,
-                long[]> collectedNotedAppOps) {
+                BitSet> collectedNotedAppOps) {
             mUid = uid;
             mCollectedNotedAppOps = collectedNotedAppOps;
         }
@@ -8634,7 +8832,7 @@
     public static @Nullable PausedNotedAppOpsCollection pauseNotedAppOpsCollection() {
         Integer previousUid = sBinderThreadCallingUid.get();
         if (previousUid != null) {
-            ArrayMap<String, long[]> previousCollectedNotedAppOps =
+            ArrayMap<String, BitSet> previousCollectedNotedAppOps =
                     sAppOpsNotedInThisBinderTransaction.get();
 
             sBinderThreadCallingUid.remove();
@@ -8708,23 +8906,19 @@
         // We are inside of a two-way binder call. Delivered to caller via
         // {@link #prefixParcelWithAppOpsIfNeeded}
         int op = sOpStrToOp.get(syncOp.getOp());
-        ArrayMap<String, long[]> appOpsNoted = sAppOpsNotedInThisBinderTransaction.get();
+        ArrayMap<String, BitSet> appOpsNoted = sAppOpsNotedInThisBinderTransaction.get();
         if (appOpsNoted == null) {
             appOpsNoted = new ArrayMap<>(1);
             sAppOpsNotedInThisBinderTransaction.set(appOpsNoted);
         }
 
-        long[] appOpsNotedForAttribution = appOpsNoted.get(syncOp.getAttributionTag());
+        BitSet appOpsNotedForAttribution = appOpsNoted.get(syncOp.getAttributionTag());
         if (appOpsNotedForAttribution == null) {
-            appOpsNotedForAttribution = new long[2];
+            appOpsNotedForAttribution = new BitSet(_NUM_OP);
             appOpsNoted.put(syncOp.getAttributionTag(), appOpsNotedForAttribution);
         }
 
-        if (op < 64) {
-            appOpsNotedForAttribution[0] |= 1L << op;
-        } else {
-            appOpsNotedForAttribution[1] |= 1L << (op - 64);
-        }
+        appOpsNotedForAttribution.set(op);
     }
 
     /** @hide */
@@ -8798,7 +8992,7 @@
      */
     // TODO (b/186872903) Refactor how sync noted ops are propagated.
     public static void prefixParcelWithAppOpsIfNeeded(@NonNull Parcel p) {
-        ArrayMap<String, long[]> notedAppOps = sAppOpsNotedInThisBinderTransaction.get();
+        ArrayMap<String, BitSet> notedAppOps = sAppOpsNotedInThisBinderTransaction.get();
         if (notedAppOps == null) {
             return;
         }
@@ -8810,8 +9004,15 @@
 
         for (int i = 0; i < numAttributionWithNotesAppOps; i++) {
             p.writeString(notedAppOps.keyAt(i));
-            p.writeLong(notedAppOps.valueAt(i)[0]);
-            p.writeLong(notedAppOps.valueAt(i)[1]);
+            // Bitmask's toLongArray will truncate the array, if upper bits arent used
+            long[] notedOpsMask = notedAppOps.valueAt(i).toLongArray();
+            for (int j = 0; j < BITMASK_LEN; j++) {
+                if (j < notedOpsMask.length) {
+                    p.writeLong(notedOpsMask[j]);
+                } else {
+                    p.writeLong(0);
+                }
+            }
         }
     }
 
@@ -8830,12 +9031,13 @@
 
         for (int i = 0; i < numAttributionsWithNotedAppOps; i++) {
             String attributionTag = p.readString();
-            long[] rawNotedAppOps = new long[2];
-            rawNotedAppOps[0] = p.readLong();
-            rawNotedAppOps[1] = p.readLong();
+            long[] rawNotedAppOps = new long[BITMASK_LEN];
+            for (int j = 0; j < rawNotedAppOps.length; j++) {
+                rawNotedAppOps[j] = p.readLong();
+            }
+            BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
 
-            if (rawNotedAppOps[0] != 0 || rawNotedAppOps[1] != 0) {
-                BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
+            if (!notedAppOps.isEmpty()) {
 
                 synchronized (sLock) {
                     for (int code = notedAppOps.nextSetBit(0); code != -1;
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index 4d6e4ae..43023fe 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -26,13 +26,11 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.util.function.DecFunction;
 import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.QuintConsumer;
 import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriFunction;
 import com.android.internal.util.function.UndecFunction;
 
 /**
@@ -135,6 +133,7 @@
         /**
          * Allows overriding start proxy operation behavior.
          *
+         * @param clientId The client calling start, represented by an IBinder
          * @param code The op code to start.
          * @param attributionSource The permission identity of the caller.
          * @param startIfModeDefault Whether to start the op of the mode is default.
@@ -148,11 +147,12 @@
          * @param superImpl The super implementation.
          * @return The app op note result.
          */
-        SyncNotedAppOp startProxyOperation(int code, @NonNull AttributionSource attributionSource,
-                boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
-                boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
-                int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
-                int attributionChainId, @NonNull DecFunction<Integer, AttributionSource, Boolean,
+        SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
+                @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
+                boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+                boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+                @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
+                @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean,
                         Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
                         SyncNotedAppOp> superImpl);
 
@@ -176,10 +176,15 @@
          *
          * @param code The op code to finish.
          * @param attributionSource The permission identity of the caller.
+         * @param skipProxyOperation Whether to skip the proxy in the proxy/proxied operation
+         * @param clientId The client calling finishProxyOperation
+         * @param superImpl The "standard" implementation to potentially call
          */
-        void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
+        void finishProxyOperation(@NonNull IBinder clientId, int code,
+                @NonNull AttributionSource attributionSource,
                 boolean skipProxyOperation,
-                @NonNull TriFunction<Integer, AttributionSource, Boolean, Void> superImpl);
+                @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean,
+                        Void> superImpl);
     }
 
     /**
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 5517c57..871d15e 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -407,6 +407,15 @@
      */
     public static final int SUBREASON_PACKAGE_UPDATE = 25;
 
+    /**
+     * The process was killed because of undelivered broadcasts; this would be set only when the
+     * reason is {@link #REASON_OTHER}.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_UNDELIVERED_BROADCAST = 26;
+
     // If there is any OEM code which involves additional app kill reasons, it should
     // be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
 
@@ -579,6 +588,7 @@
         SUBREASON_STOP_APP,
         SUBREASON_KILL_BACKGROUND,
         SUBREASON_PACKAGE_UPDATE,
+        SUBREASON_UNDELIVERED_BROADCAST,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SubReason {}
@@ -1283,6 +1293,8 @@
                 return "KILL BACKGROUND";
             case SUBREASON_PACKAGE_UPDATE:
                 return "PACKAGE UPDATE";
+            case SUBREASON_UNDELIVERED_BROADCAST:
+                return "UNDELIVERED BROADCAST";
             default:
                 return "UNKNOWN";
         }
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 48638d1..9d5c01a 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -63,12 +63,12 @@
     private long mRequireCompatChangeId = CHANGE_INVALID;
     private boolean mRequireCompatChangeEnabled = true;
     private boolean mIsAlarmBroadcast = false;
-    private boolean mIsInteractiveBroadcast = false;
     private long mIdForResponseEvent;
     private @Nullable IntentFilter mRemoveMatchingFilter;
     private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
-    private @Nullable String mDeliveryGroupKey;
+    private @Nullable String mDeliveryGroupMatchingKey;
     private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
+    private @Nullable IntentFilter mDeliveryGroupMatchingFilter;
 
     /**
      * Change ID which is invalid.
@@ -171,13 +171,6 @@
             "android:broadcast.is_alarm";
 
     /**
-     * Corresponds to {@link #setInteractiveBroadcast(boolean)}
-     * @hide
-     */
-    public static final String KEY_INTERACTIVE_BROADCAST =
-            "android:broadcast.is_interactive";
-
-    /**
      * @hide
      * @deprecated Use {@link android.os.PowerExemptionManager#
      * TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead.
@@ -214,10 +207,10 @@
             "android:broadcast.deliveryGroupPolicy";
 
     /**
-     * Corresponds to {@link #setDeliveryGroupKey(String, String)}.
+     * Corresponds to {@link #setDeliveryGroupMatchingKey(String, String)}.
      */
     private static final String KEY_DELIVERY_GROUP_KEY =
-            "android:broadcast.deliveryGroupKey";
+            "android:broadcast.deliveryGroupMatchingKey";
 
     /**
      * Corresponds to {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
@@ -226,6 +219,12 @@
             "android:broadcast.deliveryGroupExtrasMerger";
 
     /**
+     * Corresponds to {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
+     */
+    private static final String KEY_DELIVERY_GROUP_MATCHING_FILTER =
+            "android:broadcast.deliveryGroupMatchingFilter";
+
+    /**
      * The list of delivery group policies which specify how multiple broadcasts belonging to
      * the same delivery group has to be handled.
      * @hide
@@ -308,14 +307,15 @@
         mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
         mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
         mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
-        mIsInteractiveBroadcast = opts.getBoolean(KEY_INTERACTIVE_BROADCAST, false);
         mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
                 IntentFilter.class);
         mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
                 DELIVERY_GROUP_POLICY_ALL);
-        mDeliveryGroupKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
+        mDeliveryGroupMatchingKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
         mDeliveryGroupExtrasMerger = opts.getParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER,
                 BundleMerger.class);
+        mDeliveryGroupMatchingFilter = opts.getParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER,
+                IntentFilter.class);
     }
 
     /**
@@ -629,27 +629,6 @@
     }
 
     /**
-     * When set, this broadcast will be understood as having originated from
-     * some direct interaction by the user such as a notification tap or button
-     * press.  Only the OS itself may use this option.
-     * @hide
-     * @param broadcastIsInteractive
-     * @see #isInteractiveBroadcast()
-     */
-    public void setInteractiveBroadcast(boolean broadcastIsInteractive) {
-        mIsInteractiveBroadcast = broadcastIsInteractive;
-    }
-
-    /**
-     * Did this broadcast originate with a direct user interaction?
-     * @return true if this broadcast is the result of an interaction, false otherwise
-     * @hide
-     */
-    public boolean isInteractiveBroadcast() {
-        return mIsInteractiveBroadcast;
-    }
-
-    /**
      * Did this broadcast originate from a push message from the server?
      *
      * @return true if this broadcast is a push message, false otherwise.
@@ -763,7 +742,7 @@
 
     /**
      * Clears any previously set delivery group policies using
-     * {@link #setDeliveryGroupKey(String, String)} and resets the delivery group policy to
+     * {@link #setDeliveryGroupMatchingKey(String, String)} and resets the delivery group policy to
      * the default value ({@link #DELIVERY_GROUP_POLICY_ALL}).
      *
      * @hide
@@ -775,22 +754,92 @@
 
     /**
      * Set namespace and key to identify the delivery group that this broadcast belongs to.
-     * If no namespace and key is set, then by default {@link Intent#filterEquals(Intent)} will be
-     * used to identify the delivery group.
+     *
+     * <p> If {@code namespace} and {@code key} are specified, then another broadcast will be
+     * considered to be in the same delivery group as this iff it has the same {@code namespace}
+     * and {@code key}.
+     *
+     * <p> If neither matching key using this API nor matching filter using
+     * {@link #setDeliveryGroupMatchingFilter(IntentFilter)} is specified, then by default
+     * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
      *
      * @hide
      */
-    public void setDeliveryGroupKey(@NonNull String namespace, @NonNull String key) {
-        Preconditions.checkArgument(!namespace.contains("/"),
-                "namespace should not contain '/'");
-        Preconditions.checkArgument(!key.contains("/"),
-                "key should not contain '/'");
-        mDeliveryGroupKey = namespace + "/" + key;
+    @SystemApi
+    public void setDeliveryGroupMatchingKey(@NonNull String namespace, @NonNull String key) {
+        Preconditions.checkArgument(!namespace.contains(":"),
+                "namespace should not contain ':'");
+        Preconditions.checkArgument(!key.contains(":"),
+                "key should not contain ':'");
+        mDeliveryGroupMatchingKey = namespace + ":" + key;
     }
 
-    /** @hide */
-    public String getDeliveryGroupKey() {
-        return mDeliveryGroupKey;
+    /**
+     * Return the namespace and key that is used to identify the delivery group that this
+     * broadcast belongs to.
+     *
+     * @return the delivery group namespace and key that was previously set using
+     *         {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code :}.
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public String getDeliveryGroupMatchingKey() {
+        return mDeliveryGroupMatchingKey;
+    }
+
+    /**
+     * Clears the namespace and key that was previously set using
+     * {@link #setDeliveryGroupMatchingKey(String, String)}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void clearDeliveryGroupMatchingKey() {
+        mDeliveryGroupMatchingKey = null;
+    }
+
+    /**
+     * Set the {@link IntentFilter} object to identify the delivery group that this broadcast
+     * belongs to.
+     *
+     * <p> If a {@code matchingFilter} is specified, then another broadcast will be considered
+     * to be in the same delivery group as this iff the {@code matchingFilter} matches it's intent.
+     *
+     * <p> If neither matching key using {@link #setDeliveryGroupMatchingKey(String, String)} nor
+     * matching filter using this API is specified, then by default
+     * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void setDeliveryGroupMatchingFilter(@NonNull IntentFilter matchingFilter) {
+        mDeliveryGroupMatchingFilter = Objects.requireNonNull(matchingFilter);
+    }
+
+    /**
+     * Return the {@link IntentFilter} object that is used to identify the delivery group
+     * that this broadcast belongs to.
+     *
+     * @return the {@link IntentFilter} object that was previously set using
+     *         {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public IntentFilter getDeliveryGroupMatchingFilter() {
+        return mDeliveryGroupMatchingFilter;
+    }
+
+    /**
+     * Clears the {@link IntentFilter} object that was previously set using
+     * {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void clearDeliveryGroupMatchingFilter() {
+        mDeliveryGroupMatchingFilter = null;
     }
 
     /**
@@ -803,16 +852,33 @@
      * @hide
      */
     public void setDeliveryGroupExtrasMerger(@NonNull BundleMerger extrasMerger) {
-        Preconditions.checkNotNull(extrasMerger);
-        mDeliveryGroupExtrasMerger = extrasMerger;
+        mDeliveryGroupExtrasMerger = Objects.requireNonNull(extrasMerger);
     }
 
-    /** @hide */
-    public @Nullable BundleMerger getDeliveryGroupExtrasMerger() {
+    /**
+     * Return the {@link BundleMerger} that specifies how to merge the extras data from
+     * broadcasts in a delivery group.
+     *
+     * @return the {@link BundleMerger} object that was previously set using
+     *         {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
+     * @hide
+     */
+    @Nullable
+    public BundleMerger getDeliveryGroupExtrasMerger() {
         return mDeliveryGroupExtrasMerger;
     }
 
     /**
+     * Clear the {@link BundleMerger} object that was previously set using
+     * {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
+     *
+     * @hide
+     */
+    public void clearDeliveryGroupExtrasMerger() {
+        mDeliveryGroupExtrasMerger = null;
+    }
+
+    /**
      * Returns the created options as a Bundle, which can be passed to
      * {@link android.content.Context#sendBroadcast(android.content.Intent)
      * Context.sendBroadcast(Intent)} and related methods.
@@ -836,9 +902,6 @@
         if (mIsAlarmBroadcast) {
             b.putBoolean(KEY_ALARM_BROADCAST, true);
         }
-        if (mIsInteractiveBroadcast) {
-            b.putBoolean(KEY_INTERACTIVE_BROADCAST, true);
-        }
         if (mMinManifestReceiverApiLevel != 0) {
             b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel);
         }
@@ -870,8 +933,8 @@
         if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
             b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
         }
-        if (mDeliveryGroupKey != null) {
-            b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupKey);
+        if (mDeliveryGroupMatchingKey != null) {
+            b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKey);
         }
         if (mDeliveryGroupPolicy == DELIVERY_GROUP_POLICY_MERGED) {
             if (mDeliveryGroupExtrasMerger != null) {
@@ -882,6 +945,9 @@
                         + "when delivery group policy is 'MERGED'");
             }
         }
+        if (mDeliveryGroupMatchingFilter != null) {
+            b.putParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER, mDeliveryGroupMatchingFilter);
+        }
         return b.isEmpty() ? null : b;
     }
 }
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index 4e5e384..74db39f 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.annotation.RequiresPermission;
 import android.os.Bundle;
 
 /**
@@ -45,8 +46,15 @@
     public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION =
             "android.pendingIntent.backgroundActivityAllowedByPermission";
 
+    /**
+     * Corresponds to {@link #setInteractive(boolean)}
+     * @hide
+     */
+    public static final String KEY_INTERACTIVE = "android:component.isInteractive";
+
     private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT;
     private boolean mPendingIntentBalAllowedByPermission = false;
+    private boolean mIsInteractive = false;
 
     ComponentOptions() {
     }
@@ -61,6 +69,29 @@
         setPendingIntentBackgroundActivityLaunchAllowedByPermission(
                 opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
                         false));
+        mIsInteractive = opts.getBoolean(KEY_INTERACTIVE, false);
+    }
+
+    /**
+     * When set, a broadcast will be understood as having originated from
+     * some direct interaction by the user such as a notification tap or button
+     * press.  Only the OS itself may use this option.
+     * @hide
+     * @param interactive
+     * @see #isInteractive()
+     */
+    @RequiresPermission(android.Manifest.permission.COMPONENT_OPTION_INTERACTIVE)
+    public void setInteractive(boolean interactive) {
+        mIsInteractive = interactive;
+    }
+
+    /**
+     * Did this PendingIntent send originate with a direct user interaction?
+     * @return true if this is the result of an interaction, false otherwise
+     * @hide
+     */
+    public boolean isInteractive() {
+        return mIsInteractive;
     }
 
     /**
@@ -103,6 +134,9 @@
             b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
                     mPendingIntentBalAllowedByPermission);
         }
+        if (mIsInteractive) {
+            b.putBoolean(KEY_INTERACTIVE, true);
+        }
         return b;
     }
 }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 10cdf53..042bdd7 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1814,12 +1814,6 @@
             }
         }
         try {
-            ActivityThread thread = ActivityThread.currentActivityThread();
-            Instrumentation instrumentation = thread.getInstrumentation();
-            if (instrumentation.isInstrumenting()
-                    && ((flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
-                flags = flags | Context.RECEIVER_EXPORTED;
-            }
             final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
                     mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
                     AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index ae3a9e6..fae6887 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -188,7 +188,17 @@
     }
 
     @Override
-    public WallpaperInfo getWallpaperInfo(int userId) {
+    public WallpaperInfo getWallpaperInfoForUser(int userId) {
+        return unsupported();
+    }
+
+    @Override
+    public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which) {
+        return unsupported();
+    }
+
+    @Override
+    public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which, int userId) {
         return unsupported();
     }
 
diff --git a/core/java/android/app/ForegroundServiceTypeException.java b/core/java/android/app/ForegroundServiceTypeException.java
new file mode 100644
index 0000000..9a9180e
--- /dev/null
+++ b/core/java/android/app/ForegroundServiceTypeException.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+
+/**
+ * Base exception thrown when an app tries to start a foreground {@link Service}
+ * without a valid type.
+ */
+public abstract class ForegroundServiceTypeException extends ServiceStartNotAllowedException {
+    /**
+     * Constructor.
+     */
+    public ForegroundServiceTypeException(@NonNull String message) {
+        super(message);
+    }
+}
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
new file mode 100644
index 0000000..e99e360
--- /dev/null
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -0,0 +1,1145 @@
+/*
+ * 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;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.compat.CompatChanges;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.Overridable;
+import android.content.Context;
+import android.content.PermissionChecker;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.pm.ServiceInfo.ForegroundServiceType;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+import android.healthconnect.HealthConnectManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.permission.PermissionCheckerManager;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.compat.CompatibilityChangeConfig;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.util.ArrayUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * This class enforces the policies around the foreground service types.
+ *
+ * @hide
+ */
+public abstract class ForegroundServiceTypePolicy {
+    static final String TAG = "ForegroundServiceTypePolicy";
+    static final boolean DEBUG_FOREGROUND_SERVICE_TYPE_POLICY = false;
+
+    /**
+     * The FGS type enforcement:
+     * deprecating the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+     *
+     * <p>Starting a FGS with this type (equivalent of no type) from apps with
+     * targetSdkVersion {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+     * result in a warning in the log.</p>
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+    @Overridable
+    public static final long FGS_TYPE_NONE_DEPRECATION_CHANGE_ID = 255042465L;
+
+    /**
+     * The FGS type enforcement:
+     * disabling the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+     *
+     * <p>Starting a FGS with this type (equivalent of no type) from apps with
+     * targetSdkVersion {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+     * result in an exception.</p>
+     *
+     * @hide
+     */
+    // TODO (b/254661666): Change to @EnabledAfter(T)
+    @ChangeId
+    @Disabled
+    @Overridable
+    public static final long FGS_TYPE_NONE_DISABLED_CHANGE_ID = 255038118L;
+
+    /**
+     * The FGS type enforcement:
+     * deprecating the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}.
+     *
+     * <p>Starting a FGS with this type from apps with targetSdkVersion
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+     * result in a warning in the log.</p>
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+    @Overridable
+    public static final long FGS_TYPE_DATA_SYNC_DEPRECATION_CHANGE_ID = 255039210L;
+
+    /**
+     * The FGS type enforcement:
+     * disabling the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}.
+     *
+     * <p>Starting a FGS with this type from apps with targetSdkVersion
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+     * result in an exception.</p>
+     *
+     * @hide
+     */
+    // TODO (b/254661666): Change to @EnabledSince(U) in next OS release
+    @ChangeId
+    @Disabled
+    @Overridable
+    public static final long FGS_TYPE_DATA_SYNC_DISABLED_CHANGE_ID = 255659651L;
+
+    /**
+     * The FGS type enforcement: Starting a FGS from apps with targetSdkVersion
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later but without the required
+     * permissions associated with the FGS type will result in a SecurityException.
+     *
+     * @hide
+     */
+    // TODO (b/254661666): Change to @EnabledAfter(T)
+    @ChangeId
+    @Disabled
+    @Overridable
+    public static final long FGS_TYPE_PERMISSION_CHANGE_ID = 254662522L;
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MANIFEST =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_MANIFEST,
+            FGS_TYPE_NONE_DEPRECATION_CHANGE_ID,
+            FGS_TYPE_NONE_DISABLED_CHANGE_ID,
+            null,
+            null
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_NONE =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_NONE,
+            FGS_TYPE_NONE_DEPRECATION_CHANGE_ID,
+            FGS_TYPE_NONE_DISABLED_CHANGE_ID,
+            null,
+            null
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_DATA_SYNC =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_DATA_SYNC,
+            FGS_TYPE_DATA_SYNC_DEPRECATION_CHANGE_ID,
+            FGS_TYPE_DATA_SYNC_DISABLED_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC)
+            }, true),
+            null
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MEDIA_PLAYBACK =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK)
+            }, true),
+            null
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_PHONE_CALL}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_PHONE_CALL =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_PHONE_CALL,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.MANAGE_OWN_CALLS)
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_LOCATION}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_LOCATION =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_LOCATION,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_LOCATION)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.ACCESS_COARSE_LOCATION),
+                new RegularPermission(Manifest.permission.ACCESS_FINE_LOCATION),
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_CONNECTED_DEVICE =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.BLUETOOTH_CONNECT),
+                new RegularPermission(Manifest.permission.CHANGE_NETWORK_STATE),
+                new RegularPermission(Manifest.permission.CHANGE_WIFI_STATE),
+                new RegularPermission(Manifest.permission.CHANGE_WIFI_MULTICAST_STATE),
+                new RegularPermission(Manifest.permission.NFC),
+                new RegularPermission(Manifest.permission.TRANSMIT_IR),
+                new UsbDevicePermission(),
+                new UsbAccessoryPermission(),
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MEDIA_PROJECTION =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT),
+                new AppOpPermission(AppOpsManager.OP_PROJECT_MEDIA)
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_CAMERA}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_CAMERA =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_CAMERA,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_CAMERA)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.CAMERA),
+                new RegularPermission(Manifest.permission.SYSTEM_CAMERA),
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MICROPHONE}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MICROPHONE =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_MICROPHONE,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MICROPHONE)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD),
+                new RegularPermission(Manifest.permission.CAPTURE_AUDIO_OUTPUT),
+                new RegularPermission(Manifest.permission.CAPTURE_MEDIA_OUTPUT),
+                new RegularPermission(Manifest.permission.CAPTURE_TUNER_AUDIO_INPUT),
+                new RegularPermission(Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT),
+                new RegularPermission(Manifest.permission.RECORD_AUDIO),
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_HEALTH}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_HEALTH =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_HEALTH,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_HEALTH)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION),
+                new RegularPermission(Manifest.permission.BODY_SENSORS),
+                new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS),
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_REMOTE_MESSAGING =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING)
+            }, true),
+            null
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_SYSTEM_EXEMPTED =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.SCHEDULE_EXACT_ALARM),
+                new RegularPermission(Manifest.permission.USE_EXACT_ALARM),
+                new AppOpPermission(AppOpsManager.OP_ACTIVATE_VPN),
+                new AppOpPermission(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN),
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_SHORT_SERVICE =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            null /* no type specific permissions */, null /* no type specific permissions */
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_FILE_MANAGEMENT =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT)
+            }, true),
+            null
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_SPECIAL_USE =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE)
+            }, true),
+            null
+    );
+
+    /**
+     * Foreground service policy check result code: this one is not actually being used.
+     *
+     * @hide
+     */
+    public static final int FGS_TYPE_POLICY_CHECK_UNKNOWN =
+            AppProtoEnums.FGS_TYPE_POLICY_CHECK_UNKNOWN;
+
+    /**
+     * Foreground service policy check result code: okay to go.
+     *
+     * @hide
+     */
+    public static final int FGS_TYPE_POLICY_CHECK_OK =
+            AppProtoEnums.FGS_TYPE_POLICY_CHECK_OK;
+
+    /**
+     * Foreground service policy check result code: this foreground service type is deprecated.
+     *
+     * @hide
+     */
+    public static final int FGS_TYPE_POLICY_CHECK_DEPRECATED =
+            AppProtoEnums.FGS_TYPE_POLICY_CHECK_DEPRECATED;
+
+    /**
+     * Foreground service policy check result code: this foreground service type is disabled.
+     *
+     * @hide
+     */
+    public static final int FGS_TYPE_POLICY_CHECK_DISABLED =
+            AppProtoEnums.FGS_TYPE_POLICY_CHECK_DISABLED;
+
+    /**
+     * Foreground service policy check result code: the caller doesn't have permission to start
+     * foreground service with this type, but the policy is permissive.
+     *
+     * @hide
+     */
+    public static final int FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE =
+            AppProtoEnums.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE;
+
+    /**
+     * Foreground service policy check result code: the caller doesn't have permission to start
+     * foreground service with this type, and the policy is enforced.
+     *
+     * @hide
+     */
+    public static final int FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED =
+            AppProtoEnums.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED;
+
+    /**
+     * @hide
+     */
+    @IntDef(flag = true, prefix = { "FGS_TYPE_POLICY_CHECK_" }, value = {
+         FGS_TYPE_POLICY_CHECK_UNKNOWN,
+         FGS_TYPE_POLICY_CHECK_OK,
+         FGS_TYPE_POLICY_CHECK_DEPRECATED,
+         FGS_TYPE_POLICY_CHECK_DISABLED,
+         FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE,
+         FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ForegroundServicePolicyCheckCode{}
+
+    /**
+     * @return The policy info for the given type.
+     */
+    @NonNull
+    public abstract ForegroundServiceTypePolicyInfo getForegroundServiceTypePolicyInfo(
+            @ForegroundServiceType int type, @ForegroundServiceType int defaultToType);
+
+    /**
+     * Run check on the foreground service type policy for the given uid/pid
+     *
+     * @hide
+     */
+    @ForegroundServicePolicyCheckCode
+    public abstract int checkForegroundServiceTypePolicy(@NonNull Context context,
+            @NonNull String packageName, int callerUid, int callerPid, boolean allowWhileInUse,
+            @NonNull ForegroundServiceTypePolicyInfo policy);
+
+    @GuardedBy("sLock")
+    private static ForegroundServiceTypePolicy sDefaultForegroundServiceTypePolicy = null;
+
+    private static final Object sLock = new Object();
+
+    /**
+     * Return the default policy for FGS type.
+     */
+    public static @NonNull ForegroundServiceTypePolicy getDefaultPolicy() {
+        synchronized (sLock) {
+            if (sDefaultForegroundServiceTypePolicy == null) {
+                sDefaultForegroundServiceTypePolicy = new DefaultForegroundServiceTypePolicy();
+            }
+            return sDefaultForegroundServiceTypePolicy;
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * @hide
+     */
+    public ForegroundServiceTypePolicy() {
+    }
+
+    /**
+     * This class represents the policy for a specific FGS service type.
+     *
+     * @hide
+     */
+    public static final class ForegroundServiceTypePolicyInfo {
+        /**
+         * The foreground service type.
+         */
+        final @ForegroundServiceType int mType;
+
+        /**
+         * The change id to tell if this FGS type is deprecated.
+         *
+         * <p>A 0 indicates it's not deprecated.</p>
+         */
+        final long mDeprecationChangeId;
+
+        /**
+         * The change id to tell if this FGS type is disabled.
+         *
+         * <p>A 0 indicates it's not disabled.</p>
+         */
+        final long mDisabledChangeId;
+
+        /**
+         * The required permissions to start a foreground with this type, all of them
+         * MUST have been granted.
+         */
+        final @Nullable ForegroundServiceTypePermissions mAllOfPermissions;
+
+        /**
+         * The required permissions to start a foreground with this type, any one of them
+         * being granted is sufficient.
+         */
+        final @Nullable ForegroundServiceTypePermissions mAnyOfPermissions;
+
+        /**
+         * A customized check for the permissions.
+         */
+        @Nullable ForegroundServiceTypePermission mCustomPermission;
+
+        /**
+         * Not a real change id, but a place holder.
+         */
+        private static final long INVALID_CHANGE_ID = 0L;
+
+        /**
+         * @return {@code true} if the given change id is valid.
+         */
+        private static boolean isValidChangeId(long changeId) {
+            return changeId != INVALID_CHANGE_ID;
+        }
+
+        /**
+         * Construct a new instance.
+         *
+         * @hide
+         */
+        public ForegroundServiceTypePolicyInfo(@ForegroundServiceType int type,
+                long deprecationChangeId, long disabledChangeId,
+                @Nullable ForegroundServiceTypePermissions allOfPermissions,
+                @Nullable ForegroundServiceTypePermissions anyOfPermissions) {
+            mType = type;
+            mDeprecationChangeId = deprecationChangeId;
+            mDisabledChangeId = disabledChangeId;
+            mAllOfPermissions = allOfPermissions;
+            mAnyOfPermissions = anyOfPermissions;
+        }
+
+        /**
+         * @return The foreground service type.
+         */
+        @ForegroundServiceType
+        public int getForegroundServiceType() {
+            return mType;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = toPermissionString(new StringBuilder());
+            sb.append("type=0x");
+            sb.append(Integer.toHexString(mType));
+            sb.append(" deprecationChangeId=");
+            sb.append(mDeprecationChangeId);
+            sb.append(" disabledChangeId=");
+            sb.append(mDisabledChangeId);
+            sb.append(" customPermission=");
+            sb.append(mCustomPermission);
+            return sb.toString();
+        }
+
+        /**
+         * @return The required permissions.
+         */
+        public String toPermissionString() {
+            return toPermissionString(new StringBuilder()).toString();
+        }
+
+        private StringBuilder toPermissionString(StringBuilder sb) {
+            if (mAllOfPermissions != null) {
+                sb.append("all of the permissions ");
+                sb.append(mAllOfPermissions.toString());
+                sb.append(' ');
+            }
+            if (mAnyOfPermissions != null) {
+                sb.append("any of the permissions ");
+                sb.append(mAnyOfPermissions.toString());
+                sb.append(' ');
+            }
+            return sb;
+        }
+
+        /**
+         * @hide
+         */
+        public void setCustomPermission(
+                @Nullable ForegroundServiceTypePermission customPermission) {
+            mCustomPermission = customPermission;
+        }
+
+        /**
+         * @return The name of the permissions which are all required.
+         *         It may contain app op names.
+         *
+         * For test only.
+         */
+        public @NonNull Optional<String[]> getRequiredAllOfPermissionsForTest(
+                @NonNull Context context) {
+            if (mAllOfPermissions == null) {
+                return Optional.empty();
+            }
+            return Optional.of(mAllOfPermissions.toStringArray(context));
+        }
+
+        /**
+         * @return The name of the permissions where any of the is granted is sufficient.
+         *         It may contain app op names.
+         *
+         * For test only.
+         */
+        public @NonNull Optional<String[]> getRequiredAnyOfPermissionsForTest(
+                @NonNull Context context) {
+            if (mAnyOfPermissions == null) {
+                return Optional.empty();
+            }
+            return Optional.of(mAnyOfPermissions.toStringArray(context));
+        }
+
+        /**
+         * Whether or not this type is disabled.
+         */
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        public boolean isTypeDisabled(int callerUid) {
+            return isValidChangeId(mDisabledChangeId)
+                    && CompatChanges.isChangeEnabled(mDisabledChangeId, callerUid);
+        }
+
+        /**
+         * Override the type disabling change Id.
+         *
+         * For test only.
+         */
+        public void setTypeDisabledForTest(boolean disabled, @NonNull String packageName)
+                throws RemoteException {
+            overrideChangeIdForTest(mDisabledChangeId, disabled, packageName);
+        }
+
+        /**
+         * clear the type disabling change Id.
+         *
+         * For test only.
+         */
+        public void clearTypeDisabledForTest(@NonNull String packageName) throws RemoteException {
+            clearOverrideForTest(mDisabledChangeId, packageName);
+        }
+
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        boolean isTypeDeprecated(int callerUid) {
+            return isValidChangeId(mDeprecationChangeId)
+                    && CompatChanges.isChangeEnabled(mDeprecationChangeId, callerUid);
+        }
+
+        private void overrideChangeIdForTest(long changeId, boolean enable, String packageName)
+                throws RemoteException {
+            if (!isValidChangeId(changeId)) {
+                return;
+            }
+            final ArraySet<Long> enabled = new ArraySet<>();
+            final ArraySet<Long> disabled = new ArraySet<>();
+            if (enable) {
+                enabled.add(changeId);
+            } else {
+                disabled.add(changeId);
+            }
+            final CompatibilityChangeConfig overrides = new CompatibilityChangeConfig(
+                    new Compatibility.ChangeConfig(enabled, disabled));
+            IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+                        ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+            platformCompat.setOverridesForTest(overrides, packageName);
+        }
+
+        private void clearOverrideForTest(long changeId, @NonNull String packageName)
+                throws RemoteException {
+            IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+                        ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+            platformCompat.clearOverrideForTest(changeId, packageName);
+        }
+    }
+
+    /**
+     * This represents the set of permissions that's going to be required
+     * for a specific service type.
+     *
+     * @hide
+     */
+    public static class ForegroundServiceTypePermissions {
+        /**
+         * The set of the permissions to be required.
+         */
+        final @NonNull ForegroundServiceTypePermission[] mPermissions;
+
+        /**
+         * Are we requiring all of the permissions to be granted or any of them.
+         */
+        final boolean mAllOf;
+
+        /**
+         * Constructor.
+         */
+        public ForegroundServiceTypePermissions(
+                @NonNull ForegroundServiceTypePermission[] permissions, boolean allOf) {
+            mPermissions = permissions;
+            mAllOf = allOf;
+        }
+
+        /**
+         * Check the permissions.
+         */
+        @PackageManager.PermissionResult
+        public int checkPermissions(@NonNull Context context, int callerUid, int callerPid,
+                @NonNull String packageName, boolean allowWhileInUse) {
+            if (mAllOf) {
+                for (ForegroundServiceTypePermission perm : mPermissions) {
+                    final int result = perm.checkPermission(context, callerUid, callerPid,
+                            packageName, allowWhileInUse);
+                    if (result != PERMISSION_GRANTED) {
+                        return PERMISSION_DENIED;
+                    }
+                }
+                return PERMISSION_GRANTED;
+            } else {
+                boolean anyOfGranted = false;
+                for (ForegroundServiceTypePermission perm : mPermissions) {
+                    final int result = perm.checkPermission(context, callerUid, callerPid,
+                            packageName, allowWhileInUse);
+                    if (result == PERMISSION_GRANTED) {
+                        anyOfGranted = true;
+                        break;
+                    }
+                }
+                return anyOfGranted ? PERMISSION_GRANTED : PERMISSION_DENIED;
+            }
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("allOf=");
+            sb.append(mAllOf);
+            sb.append(' ');
+            sb.append('[');
+            for (int i = 0; i < mPermissions.length; i++) {
+                if (i > 0) {
+                    sb.append(", ");
+                }
+                sb.append(mPermissions[i].toString());
+            }
+            sb.append(']');
+            return sb.toString();
+        }
+
+        @NonNull String[] toStringArray(Context context) {
+            final ArrayList<String> list = new ArrayList<>();
+            for (int i = 0; i < mPermissions.length; i++) {
+                mPermissions[i].addToList(context, list);
+            }
+            return list.toArray(new String[list.size()]);
+        }
+    }
+
+    /**
+     * This represents a permission that's going to be required for a specific service type.
+     *
+     * @hide
+     */
+    public abstract static class ForegroundServiceTypePermission {
+        /**
+         * The name of this permission.
+         */
+        protected final @NonNull String mName;
+
+        /**
+         * Constructor.
+         */
+        public ForegroundServiceTypePermission(@NonNull String name) {
+            mName = name;
+        }
+
+        /**
+         * Check if the given uid/pid/package has the access to the permission.
+         */
+        @PackageManager.PermissionResult
+        public abstract int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+                @NonNull String packageName, boolean allowWhileInUse);
+
+        @Override
+        public String toString() {
+            return mName;
+        }
+
+        void addToList(@NonNull Context context, @NonNull ArrayList<String> list) {
+            list.add(mName);
+        }
+    }
+
+    /**
+     * This represents a regular Android permission to be required for a specific service type.
+     */
+    static class RegularPermission extends ForegroundServiceTypePermission {
+        RegularPermission(@NonNull String name) {
+            super(name);
+        }
+
+        @Override
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @PackageManager.PermissionResult
+        public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+                String packageName, boolean allowWhileInUse) {
+            return checkPermission(context, mName, callerUid, callerPid, packageName,
+                    allowWhileInUse);
+        }
+
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @PackageManager.PermissionResult
+        int checkPermission(@NonNull Context context, @NonNull String name, int callerUid,
+                int callerPid, String packageName, boolean allowWhileInUse) {
+            // Simple case, check if it's already granted.
+            @PackageManager.PermissionResult int result;
+            if ((result = PermissionChecker.checkPermissionForPreflight(context, name,
+                    callerPid, callerUid, packageName)) == PERMISSION_GRANTED) {
+                return PERMISSION_GRANTED;
+            }
+            if (allowWhileInUse && result == PermissionCheckerManager.PERMISSION_SOFT_DENIED) {
+                // Check its appops
+                final int opCode = AppOpsManager.permissionToOpCode(name);
+                final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+                if (opCode != AppOpsManager.OP_NONE) {
+                    final int currentMode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, callerUid,
+                            packageName);
+                    if (currentMode == MODE_FOREGROUND) {
+                        // It's in foreground only mode and we're allowing while-in-use.
+                        return PERMISSION_GRANTED;
+                    }
+                }
+            }
+            return PERMISSION_DENIED;
+        }
+    }
+
+    /**
+     * This represents an app op permission to be required for a specific service type.
+     */
+    static class AppOpPermission extends ForegroundServiceTypePermission {
+        final int mOpCode;
+
+        AppOpPermission(int opCode) {
+            super(AppOpsManager.opToPublicName(opCode));
+            mOpCode = opCode;
+        }
+
+        @Override
+        @PackageManager.PermissionResult
+        public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+                String packageName, boolean allowWhileInUse) {
+            final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+            final int mode = appOpsManager.unsafeCheckOpRawNoThrow(mOpCode, callerUid, packageName);
+            return (mode == MODE_ALLOWED || (allowWhileInUse && mode == MODE_FOREGROUND))
+                    ? PERMISSION_GRANTED : PERMISSION_DENIED;
+        }
+    }
+
+    /**
+     * This represents a special Android permission to be required for accessing usb devices.
+     */
+    static class UsbDevicePermission extends ForegroundServiceTypePermission {
+        UsbDevicePermission() {
+            super("USB Device");
+        }
+
+        @Override
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @PackageManager.PermissionResult
+        public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+                String packageName, boolean allowWhileInUse) {
+            final UsbManager usbManager = context.getSystemService(UsbManager.class);
+            final HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
+            if (!ArrayUtils.isEmpty(devices)) {
+                for (UsbDevice device : devices.values()) {
+                    if (usbManager.hasPermission(device, packageName, callerPid, callerUid)) {
+                        return PERMISSION_GRANTED;
+                    }
+                }
+            }
+            return PERMISSION_DENIED;
+        }
+    }
+
+    /**
+     * This represents a special Android permission to be required for accessing usb accessories.
+     */
+    static class UsbAccessoryPermission extends ForegroundServiceTypePermission {
+        UsbAccessoryPermission() {
+            super("USB Accessory");
+        }
+
+        @Override
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @PackageManager.PermissionResult
+        public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+                String packageName, boolean allowWhileInUse) {
+            final UsbManager usbManager = context.getSystemService(UsbManager.class);
+            final UsbAccessory[] accessories = usbManager.getAccessoryList();
+            if (!ArrayUtils.isEmpty(accessories)) {
+                for (UsbAccessory accessory: accessories) {
+                    if (usbManager.hasPermission(accessory, callerPid, callerUid)) {
+                        return PERMISSION_GRANTED;
+                    }
+                }
+            }
+            return PERMISSION_DENIED;
+        }
+    }
+
+    static class HealthConnectPermission extends RegularPermission {
+        private @Nullable String[] mPermissionNames;
+
+        HealthConnectPermission() {
+            super("Health Connect");
+        }
+
+        @Override
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @PackageManager.PermissionResult
+        public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+                String packageName, boolean allowWhileInUse) {
+            final String[] perms = getPermissions(context);
+            for (String perm : perms) {
+                if (checkPermission(context, perm, callerUid, callerPid,
+                        packageName, allowWhileInUse) == PERMISSION_GRANTED) {
+                    return PERMISSION_GRANTED;
+                }
+            }
+            return PERMISSION_DENIED;
+        }
+
+        @Override
+        void addToList(@NonNull Context context, @NonNull ArrayList<String> list) {
+            final String[] perms = getPermissions(context);
+            for (String perm : perms) {
+                list.add(perm);
+            }
+        }
+
+        private @NonNull String[] getPermissions(@NonNull Context context) {
+            if (mPermissionNames != null) {
+                return mPermissionNames;
+            }
+            final Set<String> healthPerms = HealthConnectManager.getHealthPermissions(context);
+            return mPermissionNames = healthPerms.toArray(new String[healthPerms.size()]);
+        }
+    }
+
+    /**
+     * The default policy for the foreground service types.
+     *
+     * @hide
+     */
+    public static class DefaultForegroundServiceTypePolicy extends ForegroundServiceTypePolicy {
+        private final SparseArray<ForegroundServiceTypePolicyInfo> mForegroundServiceTypePolicies =
+                new SparseArray<>();
+
+        /**
+         * Constructor
+         */
+        public DefaultForegroundServiceTypePolicy() {
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MANIFEST,
+                    FGS_TYPE_POLICY_MANIFEST);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_NONE,
+                    FGS_TYPE_POLICY_NONE);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_DATA_SYNC,
+                    FGS_TYPE_POLICY_DATA_SYNC);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+                    FGS_TYPE_POLICY_MEDIA_PLAYBACK);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_PHONE_CALL,
+                    FGS_TYPE_POLICY_PHONE_CALL);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_LOCATION,
+                    FGS_TYPE_POLICY_LOCATION);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
+                    FGS_TYPE_POLICY_CONNECTED_DEVICE);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION,
+                    FGS_TYPE_POLICY_MEDIA_PROJECTION);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_CAMERA,
+                    FGS_TYPE_POLICY_CAMERA);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MICROPHONE,
+                    FGS_TYPE_POLICY_MICROPHONE);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_HEALTH,
+                    FGS_TYPE_POLICY_HEALTH);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING,
+                    FGS_TYPE_POLICY_REMOTE_MESSAGING);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+                    FGS_TYPE_POLICY_SYSTEM_EXEMPTED);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
+                    FGS_TYPE_POLICY_SHORT_SERVICE);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT,
+                    FGS_TYPE_POLICY_FILE_MANAGEMENT);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
+                    FGS_TYPE_POLICY_SPECIAL_USE);
+        }
+
+        @Override
+        public ForegroundServiceTypePolicyInfo getForegroundServiceTypePolicyInfo(
+                @ForegroundServiceType int type, @ForegroundServiceType int defaultToType) {
+            ForegroundServiceTypePolicyInfo info = mForegroundServiceTypePolicies.get(type);
+            if (info == null) {
+                // Unknown type, fallback to the defaultToType
+                info = mForegroundServiceTypePolicies.get(defaultToType);
+                if (info == null) {
+                    // It shouldn't happen.
+                    throw new IllegalArgumentException("Invalid default fgs type " + defaultToType);
+                }
+            }
+            return info;
+        }
+
+        @Override
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @ForegroundServicePolicyCheckCode
+        public int checkForegroundServiceTypePolicy(Context context, String packageName,
+                int callerUid, int callerPid, boolean allowWhileInUse,
+                @NonNull ForegroundServiceTypePolicyInfo policy) {
+            // Has this FGS type been disabled and not allowed to use anymore?
+            if (policy.isTypeDisabled(callerUid)) {
+                return FGS_TYPE_POLICY_CHECK_DISABLED;
+            }
+            int permissionResult = PERMISSION_DENIED;
+            // Do we have the permission to start FGS with this type.
+            if (policy.mAllOfPermissions != null) {
+                permissionResult = policy.mAllOfPermissions.checkPermissions(context,
+                        callerUid, callerPid, packageName, allowWhileInUse);
+            }
+            // If it has the "all of" permissions granted, check the "any of" ones.
+            if (permissionResult == PERMISSION_GRANTED) {
+                boolean checkCustomPermission = true;
+                // Check the "any of" permissions.
+                if (policy.mAnyOfPermissions != null) {
+                    permissionResult = policy.mAnyOfPermissions.checkPermissions(context,
+                            callerUid, callerPid, packageName, allowWhileInUse);
+                    if (permissionResult == PERMISSION_GRANTED) {
+                        // We have one of them granted, no need to check custom permissions.
+                        checkCustomPermission = false;
+                    }
+                }
+                // If we have a customized permission checker, also call it now.
+                if (checkCustomPermission && policy.mCustomPermission != null) {
+                    permissionResult = policy.mCustomPermission.checkPermission(context,
+                            callerUid, callerPid, packageName, allowWhileInUse);
+                }
+            }
+            if (permissionResult != PERMISSION_GRANTED) {
+                return (CompatChanges.isChangeEnabled(
+                        FGS_TYPE_PERMISSION_CHANGE_ID, callerUid))
+                        ? FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED
+                        : FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE;
+            }
+            // Has this FGS type been deprecated?
+            if (policy.isTypeDeprecated(callerUid)) {
+                return FGS_TYPE_POLICY_CHECK_DEPRECATED;
+            }
+            return FGS_TYPE_POLICY_CHECK_OK;
+        }
+    }
+}
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index a5adaa4..c6fa064 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -26,6 +26,9 @@
 import android.annotation.TestApi;
 import android.annotation.UserHandleAware;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -51,6 +54,7 @@
             GAME_MODE_STANDARD, // 1
             GAME_MODE_PERFORMANCE, // 2
             GAME_MODE_BATTERY, // 3
+            GAME_MODE_CUSTOM, // 4
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface GameMode {
@@ -79,6 +83,15 @@
      */
     public static final int GAME_MODE_BATTERY = 3;
 
+    /**
+     * Custom game mode that has user-provided configuration overrides.
+     * <p>
+     * Custom game mode is expected to be handled only by the platform using users'
+     * preferred config. It is currently not allowed to opt in custom mode in game mode XML file nor
+     * expected to perform app-based optimizations when activated.
+     */
+    public static final int GAME_MODE_CUSTOM = 4;
+
     GameManager(Context context, Handler handler) throws ServiceNotFoundException {
         mContext = context;
         mService = IGameManagerService.Stub.asInterface(
@@ -93,19 +106,23 @@
      * application is not a game, always return {@link #GAME_MODE_UNSUPPORTED}.
      * <p>
      * Developers should call this API every time the application is resumed.
+     * <p>
+     * If a game's <code>targetSdkVersion</code> is {@link android.os.Build.VERSION_CODES#TIRAMISU}
+     * or lower, and when the game mode is set to {@link #GAME_MODE_CUSTOM} which is available in
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or newer, this API will return
+     * {@link #GAME_MODE_STANDARD} instead for backward compatibility.
      */
     public @GameMode int getGameMode() {
-        try {
-            return mService.getGameMode(mContext.getPackageName(), mContext.getUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return getGameModeImpl(mContext.getPackageName(),
+                mContext.getApplicationInfo().targetSdkVersion);
     }
 
     /**
      * Gets the game mode for the given package.
      * <p>
      * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
+     * <p>
+     * Also see {@link #getGameMode()} on how it handles SDK version compatibility.
      *
      * @hide
      */
@@ -113,11 +130,32 @@
     @UserHandleAware
     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
     public @GameMode int getGameMode(@NonNull String packageName) {
+        final int targetSdkVersion;
         try {
-            return mService.getGameMode(packageName, mContext.getUserId());
+            final ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(
+                    packageName, PackageManager.ApplicationInfoFlags.of(0));
+            targetSdkVersion = applicationInfo.targetSdkVersion;
+        } catch (PackageManager.NameNotFoundException ex) {
+            return GAME_MODE_UNSUPPORTED;
+        }
+        return getGameModeImpl(packageName, targetSdkVersion);
+    }
+
+    // This target SDK version check can be performed against any game by a privileged app, and
+    // we don't want a binder call each time to check on behalf of an app using CompatChange.
+    @SuppressWarnings("AndroidFrameworkCompatChange")
+    private @GameMode int getGameModeImpl(@NonNull String packageName, int targetSdkVersion) {
+        final int gameMode;
+        try {
+            gameMode = mService.getGameMode(packageName,
+                    mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
+        if (gameMode == GAME_MODE_CUSTOM && targetSdkVersion <= Build.VERSION_CODES.TIRAMISU) {
+            return GAME_MODE_STANDARD;
+        }
+        return gameMode;
     }
 
     /**
@@ -149,7 +187,9 @@
      * Sets the game mode for the given package.
      * <p>
      * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
-     *
+     * <p>
+     * Setting the game mode on a non-game application or setting a game to
+     * {@link #GAME_MODE_UNSUPPORTED} will have no effect.
      * @hide
      */
     @SystemApi
@@ -173,7 +213,7 @@
     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
     public @GameMode int[] getAvailableGameModes(@NonNull String packageName) {
         try {
-            return mService.getAvailableGameModes(packageName);
+            return mService.getAvailableGameModes(packageName, mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -241,4 +281,26 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Updates the config for the game's {@link #GAME_MODE_CUSTOM} mode.
+     * <p>
+     * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
+     *
+     * @param packageName The package name of the game to update
+     * @param gameModeConfig The configuration to use for game mode interventions
+     * @hide
+     */
+    @SystemApi
+    @UserHandleAware
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+    public void updateCustomGameModeConfiguration(@NonNull String packageName,
+            @NonNull GameModeConfiguration gameModeConfig) {
+        try {
+            mService.updateCustomGameModeConfiguration(packageName, gameModeConfig,
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/GameModeConfiguration.aidl b/core/java/android/app/GameModeConfiguration.aidl
new file mode 100644
index 0000000..14ac9cd
--- /dev/null
+++ b/core/java/android/app/GameModeConfiguration.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+/** @hide*/
+parcelable GameModeConfiguration;
\ No newline at end of file
diff --git a/core/java/android/app/GameModeConfiguration.java b/core/java/android/app/GameModeConfiguration.java
new file mode 100644
index 0000000..d8be814
--- /dev/null
+++ b/core/java/android/app/GameModeConfiguration.java
@@ -0,0 +1,188 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.Display;
+
+import androidx.annotation.FloatRange;
+import androidx.annotation.IntRange;
+
+import com.android.internal.annotations.Immutable;
+import com.android.internal.util.Preconditions;
+
+/**
+ * GameModeConfiguration is the game's platform configuration for a game mode.
+ * <p>
+ * Only the game modes that are enabled by OEMs will have an active configuration, whereas game
+ * modes opted in by the game will not.
+ *
+ * @hide
+ */
+@Immutable
+@SystemApi
+public final class GameModeConfiguration implements Parcelable {
+    // Default value indicating that no FPS override will be applied as game intervention, or
+    // default to the current display mode's frame rate.
+    public static final int FPS_OVERRIDE_NONE = 0;
+
+    public static final @NonNull Creator<GameModeConfiguration> CREATOR = new Creator<>() {
+        @Override
+        public GameModeConfiguration createFromParcel(Parcel in) {
+            return new GameModeConfiguration(in);
+        }
+
+        @Override
+        public GameModeConfiguration[] newArray(int size) {
+            return new GameModeConfiguration[size];
+        }
+    };
+
+    /**
+     * Builder for {@link GameModeConfiguration}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        /** Constructs a new Builder for a game mode’s configuration. */
+        public Builder() {
+        }
+
+        /** Constructs a new builder by copying from an existing game mode configuration. */
+        public Builder(@NonNull GameModeConfiguration configuration) {
+            mFpsOverride = configuration.mFpsOverride;
+            mScalingFactor = configuration.mScalingFactor;
+        }
+
+        /**
+         * Sets the scaling factor used for game resolution downscaling.
+         * <br>
+         *
+         * @param scalingFactor the desired scaling factor ranged from 0.1 to 1.0 inclusively
+         * @throws IllegalArgumentException if the scaling factor is not in range of [0.1, 1.0]
+         */
+        @NonNull
+        public GameModeConfiguration.Builder setScalingFactor(
+                @FloatRange(from = 0.1, to = 1.0) float scalingFactor) {
+            Preconditions.checkArgument(scalingFactor >= 0.1 && scalingFactor <= 1.0,
+                    "Scaling factor should fall between 0.1 and 1.0 (inclusive)");
+            mScalingFactor = scalingFactor;
+            return this;
+        }
+
+        /**
+         * Sets the FPS override used for game frame rate throttling.
+         * <br>
+         * The list of valid throttled frame rates can be queried by
+         * <ol>
+         * <li>Obtain display modes by calling {@link Display#getSupportedModes}
+         * <li>For each mode, get valid FPS by getting the divisor of the
+         * {@link Display.Mode#getRefreshRate()} that is >= 30,
+         * e.g. when Display.Mode#getRefreshRate() is 120 Hz, the valid FPS
+         * of this mode is 120, 60, 40, 30
+         * <li>Aggregate the valid FPS of each mode to get the full list
+         * </ol>
+         * <br>
+         *
+         * @param fpsOverride the desired non-negative FPS override value, default to
+         *                    {@link #FPS_OVERRIDE_NONE}.
+         * @throws IllegalArgumentException if the provided value is negative
+         */
+        @NonNull
+        public GameModeConfiguration.Builder setFpsOverride(@IntRange(from = 0) int fpsOverride) {
+            Preconditions.checkArgument(fpsOverride >= 0,
+                    "FPS override should be non-negative");
+            mFpsOverride = fpsOverride;
+            return this;
+        }
+
+        /**
+         * Builds a GameModeConfiguration.
+         */
+        @NonNull
+        public GameModeConfiguration build() {
+            return new GameModeConfiguration(mScalingFactor, mFpsOverride);
+        }
+
+        ;
+        private float mScalingFactor;
+        private int mFpsOverride;
+    }
+
+    GameModeConfiguration(float scalingFactor, int fpsOverride) {
+        this.mScalingFactor = scalingFactor;
+        this.mFpsOverride = fpsOverride;
+    }
+
+    GameModeConfiguration(Parcel in) {
+        this.mScalingFactor = in.readFloat();
+        this.mFpsOverride = in.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeFloat(mScalingFactor);
+        dest.writeInt(mFpsOverride);
+    }
+
+    /**
+     * Gets the scaling factor used for game resolution downscaling.
+     */
+    public float getScalingFactor() {
+        return mScalingFactor;
+    }
+
+    /**
+     * Gets the FPS override used for frame rate throttling.
+     */
+    public int getFpsOverride() {
+        return mFpsOverride;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof GameModeConfiguration)) {
+            return false;
+        }
+        GameModeConfiguration config = (GameModeConfiguration) obj;
+        return config.mFpsOverride == this.mFpsOverride
+                && config.mScalingFactor == this.mScalingFactor;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 7;
+        result = 31 * result + mFpsOverride;
+        result = 31 * result + Float.floatToIntBits(mScalingFactor);
+        return result;
+    }
+
+    private final float mScalingFactor;
+    private final int mFpsOverride;
+}
diff --git a/core/java/android/app/GameModeInfo.java b/core/java/android/app/GameModeInfo.java
index fe0ac35..7dcb3909 100644
--- a/core/java/android/app/GameModeInfo.java
+++ b/core/java/android/app/GameModeInfo.java
@@ -20,9 +20,34 @@
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArrayMap;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+import java.util.Map;
 
 /**
  * GameModeInfo returned from {@link GameManager#getGameModeInfo(String)}.
+ *
+ * Developers can enable game modes or interventions by adding
+ * <pre>{@code
+ * <meta-data android:name="android.game_mode_intervention"
+ *   android:resource="@xml/GAME_MODE_CONFIG_FILE" />
+ * }</pre>
+ * to the <pre>{@code <application>}</pre>, where the GAME_MODE_CONFIG_FILE is an XML file that
+ * specifies the game mode enablement and intervention configuration:
+ * <pre>{@code
+ * <game-mode-config xmlns:android="http://schemas.android.com/apk/res/android"
+ *   android:gameModePerformance="true"
+ *   android:gameModeBattery="false"
+ *   android:allowGameDownscaling="true"
+ *   android:allowGameFpsOverride="false"
+ * />
+ * }</pre>
+ *
  * @hide
  */
 @SystemApi
@@ -40,52 +65,196 @@
         }
     };
 
-    public GameModeInfo(@GameManager.GameMode int activeGameMode,
-            @NonNull @GameManager.GameMode int[] availableGameModes) {
-        mActiveGameMode = activeGameMode;
-        mAvailableGameModes = availableGameModes;
+    /**
+     * Builder for {@link GameModeInfo}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        /** Constructs a new Builder for a game mode info. */
+        public Builder() {
+        }
+
+        /**
+         * Sets the available game modes.
+         */
+        @NonNull
+        public GameModeInfo.Builder setAvailableGameModes(
+                @NonNull @GameManager.GameMode int[] availableGameModes) {
+            mAvailableGameModes = availableGameModes;
+            return this;
+        }
+
+        /**
+         * Sets the overridden game modes.
+         */
+        @NonNull
+        public GameModeInfo.Builder setOverriddenGameModes(
+                @NonNull @GameManager.GameMode int[] overriddenGameModes) {
+            mOverriddenGameModes = overriddenGameModes;
+            return this;
+        }
+
+        /**
+         * Sets the active game mode.
+         */
+        @NonNull
+        public GameModeInfo.Builder setActiveGameMode(
+                @NonNull @GameManager.GameMode int activeGameMode) {
+            mActiveGameMode = activeGameMode;
+            return this;
+        }
+
+        /**
+         * Sets the downscaling intervention flag.
+         */
+        @NonNull
+        public GameModeInfo.Builder setDownscalingAllowed(boolean allowed) {
+            mIsDownscalingAllowed = allowed;
+            return this;
+        }
+
+        /**
+         * Sets the FPS override flag.
+         */
+        @NonNull
+        public GameModeInfo.Builder setFpsOverrideAllowed(boolean allowed) {
+            mIsFpsOverrideAllowed = allowed;
+            return this;
+        }
+
+        /**
+         * Sets the GameModeConfiguration for a game mode.
+         */
+        @NonNull
+        public GameModeInfo.Builder setGameModeConfiguration(
+                @GameManager.GameMode int gameMode,
+                @NonNull GameModeConfiguration gameModeConfiguration) {
+            mConfigMap.put(gameMode, gameModeConfiguration);
+            return this;
+        }
+
+        /**
+         * Builds a GameModeInfo.
+         */
+        @NonNull
+        public GameModeInfo build() {
+            return new GameModeInfo(mActiveGameMode, mAvailableGameModes, mOverriddenGameModes,
+                    mIsDownscalingAllowed, mIsFpsOverrideAllowed, mConfigMap);
+        }
+
+        private @GameManager.GameMode int[] mAvailableGameModes = new int[]{};
+        private @GameManager.GameMode int[] mOverriddenGameModes = new int[]{};
+        private @GameManager.GameMode int mActiveGameMode;
+        private boolean mIsDownscalingAllowed;
+        private boolean mIsFpsOverrideAllowed;
+        private Map<Integer, GameModeConfiguration> mConfigMap = new ArrayMap<>();
     }
 
-    GameModeInfo(Parcel in) {
+    /**
+     * Creates a game mode info.
+     *
+     * @deprecated Use the {@link Builder} instead.
+     */
+    public GameModeInfo(@GameManager.GameMode int activeGameMode,
+            @NonNull @GameManager.GameMode int[] availableGameModes) {
+        this(activeGameMode, availableGameModes, new int[]{}, true, true, new ArrayMap<>());
+    }
+
+    private GameModeInfo(@GameManager.GameMode int activeGameMode,
+            @NonNull @GameManager.GameMode int[] availableGameModes,
+            @NonNull @GameManager.GameMode int[] overriddenGameModes, boolean isDownscalingAllowed,
+            boolean isFpsOverrideAllowed, @NonNull Map<Integer, GameModeConfiguration> configMap) {
+        mActiveGameMode = activeGameMode;
+        mAvailableGameModes = Arrays.copyOf(availableGameModes, availableGameModes.length);
+        mOverriddenGameModes = Arrays.copyOf(overriddenGameModes, overriddenGameModes.length);
+        mIsDownscalingAllowed = isDownscalingAllowed;
+        mIsFpsOverrideAllowed = isFpsOverrideAllowed;
+        mConfigMap = configMap;
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public GameModeInfo(Parcel in) {
         mActiveGameMode = in.readInt();
-        final int availableGameModesCount = in.readInt();
-        mAvailableGameModes = new int[availableGameModesCount];
-        in.readIntArray(mAvailableGameModes);
+        mAvailableGameModes = in.createIntArray();
+        mOverriddenGameModes = in.createIntArray();
+        mIsDownscalingAllowed = in.readBoolean();
+        mIsFpsOverrideAllowed = in.readBoolean();
+        mConfigMap = new ArrayMap<>();
+        in.readMap(mConfigMap,
+                getClass().getClassLoader(), Integer.class, GameModeConfiguration.class);
     }
 
     /**
      * Returns the {@link GameManager.GameMode} the application is currently using.
-     * Developers can enable game modes by adding
-     * <code>
-     *     <meta-data android:name="android.game_mode_intervention"
-     *             android:resource="@xml/GAME_MODE_CONFIG_FILE" />
-     * </code>
-     * to the {@link <application> tag}, where the GAME_MODE_CONFIG_FILE is an XML file that
-     * specifies the game mode enablement and configuration:
-     * <code>
-     *     <game-mode-config xmlns:android="http://schemas.android.com/apk/res/android"
-     *         android:gameModePerformance="true"
-     *         android:gameModeBattery="false"
-     *     />
-     * </code>
      */
     public @GameManager.GameMode int getActiveGameMode() {
         return mActiveGameMode;
     }
 
     /**
-     * The collection of {@link GameManager.GameMode GameModes} that can be applied to the game.
+     * Gets the collection of {@link GameManager.GameMode} that can be applied to the game.
+     * <p>
+     * Available games include all game modes that are either supported by the OEM in device
+     * config, or overridden by the game developers in game mode config XML, plus the default
+     * enabled modes for any game including {@link GameManager#GAME_MODE_STANDARD} and
+     * {@link GameManager#GAME_MODE_CUSTOM}.
+     * <p>
+     * Also see {@link GameModeInfo}.
      */
     @NonNull
     public @GameManager.GameMode int[] getAvailableGameModes() {
-        return mAvailableGameModes;
+        return Arrays.copyOf(mAvailableGameModes, mAvailableGameModes.length);
     }
 
-    // Ideally there should be callback that the caller can register to know when the available
-    // GameMode and/or the active GameMode is changed, however, there's no concrete use case
-    // at the moment so there's no callback mechanism introduced    .
+    /**
+     * Gets the collection of {@link GameManager.GameMode} that are overridden by the game.
+     * <p>
+     * Also see {@link GameModeInfo}.
+     */
+    @NonNull
+    public @GameManager.GameMode int[] getOverriddenGameModes() {
+        return Arrays.copyOf(mOverriddenGameModes, mOverriddenGameModes.length);
+    }
+
+    /**
+     * Gets the current game mode configuration of a game mode.
+     * <p>
+     * The game mode can be null if it's overridden by the game itself, or not configured in device
+     * config nor set by the user as custom game mode configuration.
+     */
+    public @Nullable GameModeConfiguration getGameModeConfiguration(
+            @GameManager.GameMode int gameMode) {
+        return mConfigMap.get(gameMode);
+    }
+
+    /**
+     * Returns if downscaling is allowed (not opted out) by the game in their Game Mode config.
+     * <p>
+     * Also see {@link GameModeInfo}.
+     */
+    public boolean isDownscalingAllowed() {
+        return mIsDownscalingAllowed;
+    }
+
+    /**
+     * Returns if FPS override is allowed (not opted out) by the game in their Game Mode config.
+     * <p>
+     * Also see {@link GameModeInfo}.
+     */
+    public boolean isFpsOverrideAllowed() {
+        return mIsFpsOverrideAllowed;
+    }
+
+
     private final @GameManager.GameMode int[] mAvailableGameModes;
+    private final @GameManager.GameMode int[] mOverriddenGameModes;
     private final @GameManager.GameMode int mActiveGameMode;
+    private final boolean mIsDownscalingAllowed;
+    private final boolean mIsFpsOverrideAllowed;
+    private final Map<Integer, GameModeConfiguration> mConfigMap;
 
     @Override
     public int describeContents() {
@@ -95,7 +264,10 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mActiveGameMode);
-        dest.writeInt(mAvailableGameModes.length);
         dest.writeIntArray(mAvailableGameModes);
+        dest.writeIntArray(mOverriddenGameModes);
+        dest.writeBoolean(mIsDownscalingAllowed);
+        dest.writeBoolean(mIsFpsOverrideAllowed);
+        dest.writeMap(mConfigMap);
     }
 }
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 9aa67bc..8b655b9 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -78,7 +78,11 @@
     boolean willActivityBeVisible(in IBinder token);
     int getDisplayId(in IBinder activityToken);
     int getTaskForActivity(in IBinder token, in boolean onlyRoot);
-    int getTaskWindowingMode(in IBinder activityToken);
+    /**
+     * Returns the {@link Configuration} of the task which hosts the Activity, or {@code null} if
+     * the task {@link Configuration} cannot be obtained.
+     */
+    Configuration getTaskConfiguration(in IBinder activityToken);
     IBinder getActivityTokenBelow(IBinder token);
     ComponentName getCallingActivity(in IBinder token);
     String getCallingPackage(in IBinder token);
@@ -145,10 +149,9 @@
     void unregisterRemoteAnimations(in IBinder token);
 
     /**
-     * Reports that an Activity received a back key press when there were no additional activities
-     * on the back stack.
+     * Reports that an Activity received a back key press.
      */
-    oneway void onBackPressedOnTaskRoot(in IBinder activityToken,
+    oneway void onBackPressed(in IBinder activityToken,
             in IRequestFinishCallback callback);
 
     /** Reports that the splash screen view has attached to activity.  */
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 7475ef8..040111c 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -147,6 +147,7 @@
     oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map,
             boolean abortBroadcast, int flags);
     void attachApplication(in IApplicationThread app, long startSeq);
+    void finishAttachApplication(long startSeq);
     List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
     @UnsupportedAppUsage
     void moveTaskToFront(in IApplicationThread caller, in String callingPackage, int task,
@@ -304,7 +305,7 @@
     @UnsupportedAppUsage
     void resumeAppSwitches();
     boolean bindBackupAgent(in String packageName, int backupRestoreMode, int targetUserId,
-            int operationType);
+            int backupDestination);
     void backupAgentCreated(in String packageName, in IBinder agent, int userId);
     void unbindBackupAgent(in ApplicationInfo appInfo);
     int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
@@ -718,8 +719,8 @@
 
     /**
      * Control the app freezer state. Returns true in case of success, false if the operation
-     * didn't succeed (for example, when the app freezer isn't supported). 
-     * Handling the freezer state via this method is reentrant, that is it can be 
+     * didn't succeed (for example, when the app freezer isn't supported).
+     * Handling the freezer state via this method is reentrant, that is it can be
      * disabled and re-enabled multiple times in parallel. As long as there's a 1:1 disable to
      * enable match, the freezer is re-enabled at last enable only.
      * @param enable set it to true to enable the app freezer, false to disable it.
@@ -798,4 +799,7 @@
      * <p>Typically used only by automotive builds when the vehicle has multiple displays.
      */
     @nullable int[] getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+    /** Returns if the service is a short-service is still "alive" and past the timeout. */
+    boolean shouldServiceTimeOut(in ComponentName className, in IBinder token);
 }
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 843b684..3984fee 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -20,6 +20,7 @@
 import android.app.IInstrumentationWatcher;
 import android.app.IUiAutomationConnection;
 import android.app.ProfilerInfo;
+import android.app.ReceiverInfo;
 import android.app.ResultInfo;
 import android.app.servertransaction.ClientTransaction;
 import android.content.AutofillOptions;
@@ -66,6 +67,9 @@
             in CompatibilityInfo compatInfo,
             int resultCode, in String data, in Bundle extras, boolean sync,
             int sendingUser, int processState);
+
+    void scheduleReceiverList(in List<ReceiverInfo> info);
+
     @UnsupportedAppUsage
     void scheduleCreateService(IBinder token, in ServiceInfo info,
             in CompatibilityInfo compatInfo, int processState);
@@ -165,4 +169,5 @@
     void updateUiTranslationState(IBinder activityToken, int state, in TranslationSpec sourceSpec,
             in TranslationSpec targetSpec, in List<AutofillId> viewIds,
             in UiTranslationSpec uiTranslationSpec);
+    void scheduleTimeoutService(IBinder token, int startId);
 }
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index 37c5cab..8111184 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -16,9 +16,12 @@
 
 package android.app;
 
+import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.IBackupCallback;
 import android.app.backup.IBackupManager;
 import android.os.ParcelFileDescriptor;
+
+import com.android.internal.infra.AndroidFuture;
  
 /**
  * Interface presented by applications being asked to participate in the
@@ -193,4 +196,14 @@
      * @param message The message to be passed to the agent's application in an exception.
      */
     void fail(String message);
+
+    /**
+     * Provides the logging results that were accumulated in the BackupAgent during a backup or
+     * restore operation. This method should be called after the agent completes its backup or
+     * restore.
+     *
+     * @param resultsFuture a future that is completed with the logging results.
+     */
+    void getLoggerResults(
+            in AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> resultsFuture);
 }
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 481e7b0..3d6ab6f 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -16,21 +16,37 @@
 
 package android.app;
 
+import android.app.GameModeConfiguration;
 import android.app.GameModeInfo;
 import android.app.GameState;
+import android.app.IGameModeListener;
 
 /**
  * @hide
  */
 interface IGameManagerService {
     int getGameMode(String packageName, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     void setGameMode(String packageName, int gameMode, int userId);
-    int[] getAvailableGameModes(String packageName);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
+    int[] getAvailableGameModes(String packageName, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     boolean isAngleEnabled(String packageName, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     void notifyGraphicsEnvironmentSetup(String packageName, int userId);
     void setGameState(String packageName, in GameState gameState, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     GameModeInfo getGameModeInfo(String packageName, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_GAME_SERVICE)")
     void setGameServiceProvider(String packageName);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     void updateResolutionScalingFactor(String packageName, int gameMode, float scalingFactor, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     float getResolutionScalingFactor(String packageName, int gameMode, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
+    void updateCustomGameModeConfiguration(String packageName, in GameModeConfiguration gameModeConfig, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
+    void addGameModeListener(IGameModeListener gameModeListener);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
+    void removeGameModeListener(IGameModeListener gameModeListener);
 }
diff --git a/core/java/android/app/IGameModeListener.aidl b/core/java/android/app/IGameModeListener.aidl
new file mode 100644
index 0000000..77fcac0
--- /dev/null
+++ b/core/java/android/app/IGameModeListener.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+/** @hide */
+interface IGameModeListener {
+    /**
+     * Called when the game mode of the user has changed.
+     */
+    void onGameModeChanged(String packageName, int gameModeFrom, int gameModeTo, int userId);
+}
diff --git a/core/java/android/app/ILocaleManager.aidl b/core/java/android/app/ILocaleManager.aidl
index 3002c8b..c38b64f 100644
--- a/core/java/android/app/ILocaleManager.aidl
+++ b/core/java/android/app/ILocaleManager.aidl
@@ -33,7 +33,7 @@
      /**
       * Sets a specified app’s app-specific UI locales.
       */
-     void setApplicationLocales(String packageName, int userId, in LocaleList locales);
+     void setApplicationLocales(String packageName, int userId, in LocaleList locales, boolean fromDelegate);
 
      /**
       * Returns the specified app's app-specific locales.
@@ -45,4 +45,4 @@
        */
      LocaleList getSystemLocales();
 
- }
\ No newline at end of file
+ }
diff --git a/core/java/android/app/IRequestFinishCallback.aidl b/core/java/android/app/IRequestFinishCallback.aidl
index 22c20c8..72426df 100644
--- a/core/java/android/app/IRequestFinishCallback.aidl
+++ b/core/java/android/app/IRequestFinishCallback.aidl
@@ -18,7 +18,7 @@
 
 /**
  * This callback allows ActivityTaskManager to ask the calling Activity
- * to finish in response to a call to onBackPressedOnTaskRoot.
+ * to finish in response to a call to onBackPressed.
  *
  * {@hide}
  */
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 623af5f..fbb0748 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -40,6 +40,7 @@
     void connect(IAccessibilityServiceClient client, int flags);
     void disconnect();
     boolean injectInputEvent(in InputEvent event, boolean sync, boolean waitForAnimations);
+    void injectInputEventToInputFilter(in InputEvent event);
     void syncInputTransactions(boolean waitForAnimations);
     boolean setRotation(int rotation);
     Bitmap takeScreenshot(in Rect crop);
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 167de46..b1ed152 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -50,9 +50,10 @@
             IWallpaperManagerCallback completion, int userId);
 
     /**
-     * Set the live wallpaper. This only affects the system wallpaper.
+     * Set the live wallpaper.
      */
-    void setWallpaperComponentChecked(in ComponentName name, in String callingPackage, int userId);
+    void setWallpaperComponentChecked(in ComponentName name, in String callingPackage, int which,
+            int userId);
 
     /**
      * Set the live wallpaper. This only affects the system wallpaper.
@@ -89,6 +90,12 @@
     WallpaperInfo getWallpaperInfo(int userId);
 
     /**
+     * If the current wallpaper for destination `which` is a live wallpaper component, return the
+     * information about that wallpaper.  Otherwise, if it is a static image, simply return null.
+     */
+    WallpaperInfo getWallpaperInfoWithFlags(int which, int userId);
+
+    /**
      * Clear the system wallpaper.
      */
     void clearWallpaper(in String callingPackage, int which, int userId);
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
index 2e83308..99f864c 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -57,8 +57,7 @@
  * @deprecated IntentService is subject to all the
  *   <a href="{@docRoot}about/versions/oreo/background.html">background execution limits</a>
  *   imposed with Android 8.0 (API level 26). Consider using {@link androidx.work.WorkManager}
- *   or {@link androidx.core.app.JobIntentService}, which uses jobs
- *   instead of services when running on Android 8.0 or higher.
+ *   instead.
  */
 @Deprecated
 public abstract class IntentService extends Service {
diff --git a/core/java/android/app/InvalidForegroundServiceTypeException.java b/core/java/android/app/InvalidForegroundServiceTypeException.java
new file mode 100644
index 0000000..6ff0262
--- /dev/null
+++ b/core/java/android/app/InvalidForegroundServiceTypeException.java
@@ -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 android.app;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Exception thrown when an app tries to start a foreground {@link Service} with an invalid type.
+ */
+public final class InvalidForegroundServiceTypeException
+        extends ForegroundServiceTypeException implements Parcelable {
+    /**
+     * Constructor.
+     */
+    public InvalidForegroundServiceTypeException(@NonNull String message) {
+        super(message);
+    }
+
+    InvalidForegroundServiceTypeException(@NonNull Parcel source) {
+        super(source.readString());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(getMessage());
+    }
+
+    public static final @NonNull Creator<android.app.InvalidForegroundServiceTypeException>
+            CREATOR = new Creator<android.app.InvalidForegroundServiceTypeException>() {
+                @NonNull
+                public android.app.InvalidForegroundServiceTypeException createFromParcel(
+                        Parcel source) {
+                    return new android.app.InvalidForegroundServiceTypeException(source);
+                }
+
+                @NonNull
+                public android.app.InvalidForegroundServiceTypeException[] newArray(int size) {
+                    return new android.app.InvalidForegroundServiceTypeException[size];
+                }
+            };
+}
diff --git a/core/java/android/app/LocaleManager.java b/core/java/android/app/LocaleManager.java
index 0e2b098..70c014f 100644
--- a/core/java/android/app/LocaleManager.java
+++ b/core/java/android/app/LocaleManager.java
@@ -71,7 +71,7 @@
      */
     @UserHandleAware
     public void setApplicationLocales(@NonNull LocaleList locales) {
-        setApplicationLocales(mContext.getPackageName(), locales);
+        setApplicationLocales(mContext.getPackageName(), locales, false);
     }
 
     /**
@@ -100,9 +100,14 @@
     @RequiresPermission(Manifest.permission.CHANGE_CONFIGURATION)
     @UserHandleAware
     public void setApplicationLocales(@NonNull String appPackageName, @NonNull LocaleList locales) {
+        setApplicationLocales(appPackageName, locales, true);
+    }
+
+    private void setApplicationLocales(@NonNull String appPackageName, @NonNull LocaleList locales,
+            boolean fromDelegate) {
         try {
             mService.setApplicationLocales(appPackageName, mContext.getUser().getIdentifier(),
-                    locales);
+                    locales, fromDelegate);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/MissingForegroundServiceTypeException.java b/core/java/android/app/MissingForegroundServiceTypeException.java
new file mode 100644
index 0000000..c9b2006
--- /dev/null
+++ b/core/java/android/app/MissingForegroundServiceTypeException.java
@@ -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 android.app;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Exception thrown when an app tries to start a foreground {@link Service} without a type.
+ */
+public final class MissingForegroundServiceTypeException
+        extends ForegroundServiceTypeException implements Parcelable {
+    /**
+     * Constructor.
+     */
+    public MissingForegroundServiceTypeException(@NonNull String message) {
+        super(message);
+    }
+
+    MissingForegroundServiceTypeException(@NonNull Parcel source) {
+        super(source.readString());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(getMessage());
+    }
+
+    public static final @NonNull Creator<android.app.MissingForegroundServiceTypeException>
+            CREATOR = new Creator<android.app.MissingForegroundServiceTypeException>() {
+                @NonNull
+                public android.app.MissingForegroundServiceTypeException createFromParcel(
+                        Parcel source) {
+                    return new android.app.MissingForegroundServiceTypeException(source);
+                }
+
+                @NonNull
+                public android.app.MissingForegroundServiceTypeException[] newArray(int size) {
+                    return new android.app.MissingForegroundServiceTypeException[size];
+                }
+            };
+}
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index f3fc468..f2eced3 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -11,6 +11,7 @@
 per-file ApplicationThreadConstants.java = file:/services/core/java/com/android/server/am/OWNERS
 per-file BroadcastOptions.java = file:/services/core/java/com/android/server/am/OWNERS
 per-file ContentProviderHolder* = file:/services/core/java/com/android/server/am/OWNERS
+per-file *ForegroundService* = file:/services/core/java/com/android/server/am/OWNERS
 per-file IActivityController.aidl = file:/services/core/java/com/android/server/am/OWNERS
 per-file IActivityManager.aidl = file:/services/core/java/com/android/server/am/OWNERS
 per-file IApplicationThread.aidl = file:/services/core/java/com/android/server/am/OWNERS
@@ -29,10 +30,12 @@
 per-file Service* = file:/services/core/java/com/android/server/am/OWNERS
 per-file SystemServiceRegistry.java = file:/services/core/java/com/android/server/am/OWNERS
 per-file *UserSwitchObserver* = file:/services/core/java/com/android/server/am/OWNERS
-per-file UiAutomation* = file:/services/accessibility/OWNERS
+per-file *UiAutomation* = file:/services/accessibility/OWNERS
 per-file GameManager* = file:/GAME_MANAGER_OWNERS
+per-file GameMode* = file:/GAME_MANAGER_OWNERS
 per-file GameState* = file:/GAME_MANAGER_OWNERS
 per-file IGameManager* = file:/GAME_MANAGER_OWNERS
+per-file IGameMode* = file:/GAME_MANAGER_OWNERS
 
 # ActivityThread
 per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS
diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java
index 3f1844e..96d874e 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -157,13 +157,18 @@
         }
 
         /**
-         * Sets the source bounds hint. These bounds are only used when an activity first enters
-         * picture-in-picture, and describe the bounds in window coordinates of activity entering
-         * picture-in-picture that will be visible following the transition. For the best effect,
-         * these bounds should also match the aspect ratio in the arguments.
+         * Sets the window-coordinate bounds of an activity transitioning to picture-in-picture.
+         * The bounds is the area of an activity that will be visible in the transition to
+         * picture-in-picture mode. For the best effect, these bounds should also match the
+         * aspect ratio in the arguments.
+         *
+         * In Android 12+ these bounds are also reused to improve the exit transition from 
+         * picture-in-picture mode. See
+         * <a href="{@docRoot}develop/ui/views/picture-in-picture#smoother-exit">Support
+         * smoother animations when exiting out of PiP mode</a> for more details.
          *
          * @param launchBounds window-coordinate bounds indicating the area of the activity that
-         * will still be visible following the transition into picture-in-picture (eg. the video
+         * will still be visible following the transition into picture-in-picture (e.g. the video
          * view bounds in a video player)
          *
          * @return this builder instance.
diff --git a/core/java/android/app/ReceiverInfo.aidl b/core/java/android/app/ReceiverInfo.aidl
new file mode 100644
index 0000000..d90eee7
--- /dev/null
+++ b/core/java/android/app/ReceiverInfo.aidl
@@ -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.app;
+
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.os.Bundle;
+
+/**
+ * Collect the information needed for manifest and registered receivers into a single structure
+ * that can be the element of a list.  All fields are already parcelable.
+ * @hide
+ */
+parcelable ReceiverInfo {
+    /**
+     * Fields common to registered and manifest receivers.
+     */
+    Intent intent;
+    String data;
+    Bundle extras;
+    int sendingUser;
+    int processState;
+    int resultCode;
+
+    /**
+     * True if this instance represents a registered receiver and false if this instance
+     * represents a manifest receiver.
+     */
+    boolean registered;
+
+    /**
+     * Fields used only for registered receivers.
+     */
+    IIntentReceiver receiver;
+    boolean ordered;
+    boolean sticky;
+
+    /**
+     * Fields used only for manifest receivers.
+     */
+    ActivityInfo activityInfo;
+    CompatibilityInfo compatInfo;
+    boolean sync;
+}
diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java
index e220627..620adbe 100644
--- a/core/java/android/app/RemoteServiceException.java
+++ b/core/java/android/app/RemoteServiceException.java
@@ -72,21 +72,6 @@
 
     /**
      * Exception used to crash an app process when the system received a RemoteException
-     * while delivering a broadcast to an app process.
-     *
-     * @hide
-     */
-    public static class CannotDeliverBroadcastException extends RemoteServiceException {
-        /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
-        public static final int TYPE_ID = 2;
-
-        public CannotDeliverBroadcastException(String msg) {
-            super(msg);
-        }
-    }
-
-    /**
-     * Exception used to crash an app process when the system received a RemoteException
      * while posting a notification of a foreground service.
      *
      * @hide
@@ -94,7 +79,7 @@
     public static class CannotPostForegroundServiceNotificationException
             extends RemoteServiceException {
         /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
-        public static final int TYPE_ID = 3;
+        public static final int TYPE_ID = 2;
 
         public CannotPostForegroundServiceNotificationException(String msg) {
             super(msg);
@@ -109,7 +94,7 @@
      */
     public static class BadForegroundServiceNotificationException extends RemoteServiceException {
         /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
-        public static final int TYPE_ID = 4;
+        public static final int TYPE_ID = 3;
 
         public BadForegroundServiceNotificationException(String msg) {
             super(msg);
@@ -125,7 +110,7 @@
     public static class MissingRequestPasswordComplexityPermissionException
             extends RemoteServiceException {
         /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
-        public static final int TYPE_ID = 5;
+        public static final int TYPE_ID = 4;
 
         public MissingRequestPasswordComplexityPermissionException(String msg) {
             super(msg);
@@ -139,7 +124,7 @@
      */
     public static class CrashedByAdbException extends RemoteServiceException {
         /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
-        public static final int TYPE_ID = 6;
+        public static final int TYPE_ID = 5;
 
         public CrashedByAdbException(String msg) {
             super(msg);
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index d275c83..7e6386e 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -41,6 +41,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -51,7 +52,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -173,6 +173,7 @@
 
     /**
      * The ApkAssets that are being referenced in the wild that we can reuse.
+     * Used as a lock for itself as well.
      */
     private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
 
@@ -286,38 +287,43 @@
      * try as hard as possible to release any open FDs.
      */
     public void invalidatePath(String path) {
+        final List<ResourcesImpl> implsToFlush = new ArrayList<>();
         synchronized (mLock) {
-            int count = 0;
-
             for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
                 final ResourcesKey key = mResourceImpls.keyAt(i);
                 if (key.isPathReferenced(path)) {
-                    ResourcesImpl impl = mResourceImpls.removeAt(i).get();
-                    if (impl != null) {
-                        impl.flushLayoutCache();
+                    ResourcesImpl resImpl = mResourceImpls.removeAt(i).get();
+                    if (resImpl != null) {
+                        implsToFlush.add(resImpl);
                     }
-                    count++;
                 }
             }
-
-            Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
-
+        }
+        for (int i = 0; i < implsToFlush.size(); i++) {
+            implsToFlush.get(i).flushLayoutCache();
+        }
+        final List<ApkAssets> assetsToClose = new ArrayList<>();
+        synchronized (mCachedApkAssets) {
             for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
                 final ApkKey key = mCachedApkAssets.keyAt(i);
                 if (key.path.equals(path)) {
-                    WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.removeAt(i);
-                    if (apkAssetsRef != null && apkAssetsRef.get() != null) {
-                        apkAssetsRef.get().close();
+                    final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.removeAt(i);
+                    final ApkAssets apkAssets = apkAssetsRef != null ? apkAssetsRef.get() : null;
+                    if (apkAssets != null) {
+                        assetsToClose.add(apkAssets);
                     }
                 }
             }
         }
+        for (int i = 0; i < assetsToClose.size(); i++) {
+            assetsToClose.get(i).close();
+        }
+        Log.i(TAG,
+                "Invalidated " + implsToFlush.size() + " asset managers that referenced " + path);
     }
 
     public Configuration getConfiguration() {
-        synchronized (mLock) {
-            return mResConfiguration;
-        }
+        return mResConfiguration;
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -402,14 +408,12 @@
      * @param resources The {@link Resources} backing the display adjustments.
      */
     public Display getAdjustedDisplay(final int displayId, Resources resources) {
-        synchronized (mLock) {
-            final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
-            if (dm == null) {
-                // may be null early in system startup
-                return null;
-            }
-            return dm.getCompatibleDisplay(displayId, resources);
+        final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
+        if (dm == null) {
+            // may be null early in system startup
+            return null;
         }
+        return dm.getCompatibleDisplay(displayId, resources);
     }
 
     /**
@@ -446,16 +450,14 @@
         ApkAssets apkAssets;
 
         // Optimistically check if this ApkAssets exists somewhere else.
-        synchronized (mLock) {
-            final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(key);
-            if (apkAssetsRef != null) {
-                apkAssets = apkAssetsRef.get();
-                if (apkAssets != null && apkAssets.isUpToDate()) {
-                    return apkAssets;
-                } else {
-                    // Clean up the reference.
-                    mCachedApkAssets.remove(key);
-                }
+        final WeakReference<ApkAssets> apkAssetsRef;
+        synchronized (mCachedApkAssets) {
+            apkAssetsRef = mCachedApkAssets.get(key);
+        }
+        if (apkAssetsRef != null) {
+            apkAssets = apkAssetsRef.get();
+            if (apkAssets != null && apkAssets.isUpToDate()) {
+                return apkAssets;
             }
         }
 
@@ -472,7 +474,7 @@
             apkAssets = ApkAssets.loadFromPath(key.path, flags);
         }
 
-        synchronized (mLock) {
+        synchronized (mCachedApkAssets) {
             mCachedApkAssets.put(key, new WeakReference<>(apkAssets));
         }
 
@@ -584,28 +586,33 @@
      * @hide
      */
     public void dump(String prefix, PrintWriter printWriter) {
+        final int references;
+        final int resImpls;
         synchronized (mLock) {
-            IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
-            for (int i = 0; i < prefix.length() / 2; i++) {
-                pw.increaseIndent();
-            }
-
-            pw.println("ResourcesManager:");
-            pw.increaseIndent();
-            pw.print("total apks: ");
-            pw.println(countLiveReferences(mCachedApkAssets.values()));
-
-            pw.print("resources: ");
-
-            int references = countLiveReferences(mResourceReferences);
+            int refs = countLiveReferences(mResourceReferences);
             for (ActivityResources activityResources : mActivityResourceReferences.values()) {
-                references += activityResources.countLiveReferences();
+                refs += activityResources.countLiveReferences();
             }
-            pw.println(references);
-
-            pw.print("resource impls: ");
-            pw.println(countLiveReferences(mResourceImpls.values()));
+            references = refs;
+            resImpls = countLiveReferences(mResourceImpls.values());
         }
+        final int liveAssets;
+        synchronized (mCachedApkAssets) {
+            liveAssets = countLiveReferences(mCachedApkAssets.values());
+        }
+
+        final var pw = new IndentingPrintWriter(printWriter, "  ");
+        for (int i = 0; i < prefix.length() / 2; i++) {
+            pw.increaseIndent();
+        }
+        pw.println("ResourcesManager:");
+        pw.increaseIndent();
+        pw.print("total apks: ");
+        pw.println(liveAssets);
+        pw.print("resources: ");
+        pw.println(references);
+        pw.print("resource impls: ");
+        pw.println(resImpls);
     }
 
     private Configuration generateConfig(@NonNull ResourcesKey key) {
diff --git a/core/java/android/app/SearchableInfo.java b/core/java/android/app/SearchableInfo.java
index 5388282..bd5d105 100644
--- a/core/java/android/app/SearchableInfo.java
+++ b/core/java/android/app/SearchableInfo.java
@@ -396,6 +396,17 @@
         private final String mSuggestActionMsg;
         private final String mSuggestActionMsgColumn;
 
+        public static final Parcelable.Creator<ActionKeyInfo> CREATOR =
+                new Parcelable.Creator<ActionKeyInfo>() {
+                    public ActionKeyInfo createFromParcel(Parcel in) {
+                        return new ActionKeyInfo(in);
+                    }
+
+                    public ActionKeyInfo[] newArray(int size) {
+                        return new ActionKeyInfo[size];
+                    }
+                };
+
         /**
          * Create one object using attributeset as input data.
          * @param activityContext runtime context of the activity that the action key information
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 7635138..0ab96a9 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -17,10 +17,13 @@
 package android.app;
 
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.text.TextUtils.formatSimple;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
@@ -33,6 +36,7 @@
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.contentcapture.ContentCaptureManager;
@@ -726,78 +730,140 @@
      * for more details.
      * </div>
      *
+     * <div class="caution">
+     * <p><strong>Note:</strong>
+     * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+     * or higher are not allowed to start foreground services without specifying a valid
+     * foreground service type in the manifest attribute
+     * {@link android.R.attr#foregroundServiceType}.
+     * See
+     * <a href="{@docRoot}/about/versions/14/behavior-changes-14">
+     * Behavior changes: Apps targeting Android 14
+     * </a>
+     * for more details.
+     * </div>
+     *
      * @throws ForegroundServiceStartNotAllowedException
      * If the app targeting API is
      * {@link android.os.Build.VERSION_CODES#S} or later, and the service is restricted from
      * becoming foreground service due to background restriction.
+     * @throws InvalidForegroundServiceTypeException
+     * If the app targeting API is
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later, and the manifest attribute
+     * {@link android.R.attr#foregroundServiceType} is set to invalid types(i.e.
+     * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}).
+     * @throws MissingForegroundServiceTypeException
+     * If the app targeting API is
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later, and the manifest attribute
+     * {@link android.R.attr#foregroundServiceType} is not set.
+     * @throws SecurityException If the app targeting API is
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later and doesn't have the
+     * permission to start the foreground service with the specified type in the manifest attribute
+     * {@link android.R.attr#foregroundServiceType}.
      *
      * @param id The identifier for this notification as per
      * {@link NotificationManager#notify(int, Notification)
      * NotificationManager.notify(int, Notification)}; must not be 0.
      * @param notification The Notification to be displayed.
-     * 
+     *
      * @see #stopForeground(boolean)
      */
     public final void startForeground(int id, Notification notification) {
         try {
+            final ComponentName comp = new ComponentName(this, mClassName);
             mActivityManager.setServiceForeground(
-                    new ComponentName(this, mClassName), mToken, id,
+                    comp, mToken, id,
                     notification, 0, FOREGROUND_SERVICE_TYPE_MANIFEST);
             clearStartForegroundServiceStackTrace();
+            logForegroundServiceStart(comp, FOREGROUND_SERVICE_TYPE_MANIFEST);
         } catch (RemoteException ex) {
         }
     }
 
-  /**
-   * An overloaded version of {@link #startForeground(int, Notification)} with additional
-   * foregroundServiceType parameter.
-   *
-   * <p>Apps built with SDK version {@link android.os.Build.VERSION_CODES#Q} or later can specify
-   * the foreground service types using attribute {@link android.R.attr#foregroundServiceType} in
-   * service element of manifest file. The value of attribute
-   * {@link android.R.attr#foregroundServiceType} can be multiple flags ORed together.</p>
-   *
-   * <p>The foregroundServiceType parameter must be a subset flags of what is specified in manifest
-   * attribute {@link android.R.attr#foregroundServiceType}, if not, an IllegalArgumentException is
-   * thrown. Specify foregroundServiceType parameter as
-   * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST} to use all flags that
-   * is specified in manifest attribute foregroundServiceType.</p>
-   *
-   * <div class="caution">
-   * <p><strong>Note:</strong>
-   * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#S},
-   * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
-   * or higher are not allowed to start foreground services from the background.
-   * See
-   * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
-   * Behavior changes: Apps targeting Android 12
-   * </a>
-   * for more details.
-   * </div>
-   *
-   * @param id The identifier for this notification as per
-   * {@link NotificationManager#notify(int, Notification)
-   * NotificationManager.notify(int, Notification)}; must not be 0.
-   * @param notification The Notification to be displayed.
-   * @param foregroundServiceType must be a subset flags of manifest attribute
-   * {@link android.R.attr#foregroundServiceType} flags.
-   *
-   * @throws IllegalArgumentException if param foregroundServiceType is not subset of manifest
-   *     attribute {@link android.R.attr#foregroundServiceType}.
-   * @throws ForegroundServiceStartNotAllowedException
-   * If the app targeting API is
-   * {@link android.os.Build.VERSION_CODES#S} or later, and the service is restricted from
-   * becoming foreground service due to background restriction.
-   *
-   * @see android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST
-   */
+    /**
+     * An overloaded version of {@link #startForeground(int, Notification)} with additional
+     * foregroundServiceType parameter.
+     *
+     * <p>Apps built with SDK version {@link android.os.Build.VERSION_CODES#Q} or later can specify
+     * the foreground service types using attribute {@link android.R.attr#foregroundServiceType} in
+     * service element of manifest file. The value of attribute
+     * {@link android.R.attr#foregroundServiceType} can be multiple flags ORed together.</p>
+     *
+     * <p>The foregroundServiceType parameter must be a subset flags of what is specified in
+     * manifest attribute {@link android.R.attr#foregroundServiceType}, if not, an
+     * IllegalArgumentException is thrown. Specify foregroundServiceType parameter as
+     * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST} to use all flags that
+     * is specified in manifest attribute foregroundServiceType.</p>
+     *
+     * <div class="caution">
+     * <p><strong>Note:</strong>
+     * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#S},
+     * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
+     * or higher are not allowed to start foreground services from the background.
+     * See
+     * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
+     * Behavior changes: Apps targeting Android 12
+     * </a>
+     * for more details.
+     * </div>
+     *
+     * <div class="caution">
+     * <p><strong>Note:</strong>
+     * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+     * or higher are not allowed to start foreground services without specifying a valid
+     * foreground service type in the manifest attribute
+     * {@link android.R.attr#foregroundServiceType}, and the parameter {@code foregroundServiceType}
+     * here must not be the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+     * See
+     * <a href="{@docRoot}/about/versions/14/behavior-changes-14">
+     * Behavior changes: Apps targeting Android 14
+     * </a>
+     * for more details.
+     * </div>
+     *
+     * @param id The identifier for this notification as per
+     * {@link NotificationManager#notify(int, Notification)
+     * NotificationManager.notify(int, Notification)}; must not be 0.
+     * @param notification The Notification to be displayed.
+     * @param foregroundServiceType must be a subset flags of manifest attribute
+     * {@link android.R.attr#foregroundServiceType} flags; must not be
+     * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+     *
+     * @throws IllegalArgumentException if param foregroundServiceType is not subset of manifest
+     *     attribute {@link android.R.attr#foregroundServiceType}.
+     * @throws ForegroundServiceStartNotAllowedException
+     * If the app targeting API is
+     * {@link android.os.Build.VERSION_CODES#S} or later, and the service is restricted from
+     * becoming foreground service due to background restriction.
+     * @throws InvalidForegroundServiceTypeException
+     * If the app targeting API is
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later, and the manifest attribute
+     * {@link android.R.attr#foregroundServiceType} or the param {@code foregroundServiceType}
+     * is set to invalid types(i.e.{@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}).
+     * @throws MissingForegroundServiceTypeException
+     * If the app targeting API is
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later, and the manifest attribute
+     * {@link android.R.attr#foregroundServiceType} is not set and the param
+     * {@code foregroundServiceType} is set to {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST}.
+     * @throws SecurityException If the app targeting API is
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later and doesn't have the
+     * permission to start the foreground service with the specified type in
+     * {@code foregroundServiceType}.
+     * {@link android.R.attr#foregroundServiceType}.
+     *
+     * @see android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST
+     */
     public final void startForeground(int id, @NonNull Notification notification,
-            @ForegroundServiceType int foregroundServiceType) {
+            @RequiresPermission @ForegroundServiceType int foregroundServiceType) {
         try {
+            final ComponentName comp = new ComponentName(this, mClassName);
             mActivityManager.setServiceForeground(
-                    new ComponentName(this, mClassName), mToken, id,
+                    comp, mToken, id,
                     notification, 0, foregroundServiceType);
             clearStartForegroundServiceStackTrace();
+            logForegroundServiceStart(comp, foregroundServiceType);
         } catch (RemoteException ex) {
         }
     }
@@ -848,6 +914,7 @@
             mActivityManager.setServiceForeground(
                     new ComponentName(this, mClassName), mToken, 0, null,
                     notificationBehavior, 0);
+            logForegroundServiceStopIfNecessary();
         } catch (RemoteException ex) {
         }
     }
@@ -943,6 +1010,7 @@
      */
     public final void detachAndCleanUp() {
         mToken = null;
+        logForegroundServiceStopIfNecessary();
     }
 
     final String getClassName() {
@@ -976,6 +1044,50 @@
     private boolean mStartCompatibility = false;
 
     /**
+     * This will be set to the title of the system trace when this service is started as
+     * a foreground service, and will be set to null when it's no longer in foreground
+     * service state.
+     */
+    @GuardedBy("mForegroundServiceTraceTitleLock")
+    private @Nullable String mForegroundServiceTraceTitle = null;
+
+    private final Object mForegroundServiceTraceTitleLock = new Object();
+
+    private static final String TRACE_TRACK_NAME_FOREGROUND_SERVICE = "FGS";
+
+    private void logForegroundServiceStart(ComponentName comp,
+            @ForegroundServiceType int foregroundServiceType) {
+        synchronized (mForegroundServiceTraceTitleLock) {
+            if (mForegroundServiceTraceTitle == null) {
+                mForegroundServiceTraceTitle = formatSimple("comp=%s type=%s",
+                        comp.toShortString(), Integer.toHexString(foregroundServiceType));
+                // The service is not in foreground state, emit a start event.
+                Trace.asyncTraceForTrackBegin(TRACE_TAG_ACTIVITY_MANAGER,
+                        TRACE_TRACK_NAME_FOREGROUND_SERVICE,
+                        mForegroundServiceTraceTitle,
+                        System.identityHashCode(this));
+            } else {
+                // The service is already in foreground state, emit an one-off event.
+                Trace.instantForTrack(TRACE_TAG_ACTIVITY_MANAGER,
+                        TRACE_TRACK_NAME_FOREGROUND_SERVICE,
+                        mForegroundServiceTraceTitle);
+            }
+        }
+    }
+
+    private void logForegroundServiceStopIfNecessary() {
+        synchronized (mForegroundServiceTraceTitleLock) {
+            if (mForegroundServiceTraceTitle != null) {
+                Trace.asyncTraceForTrackEnd(TRACE_TAG_ACTIVITY_MANAGER,
+                        TRACE_TRACK_NAME_FOREGROUND_SERVICE,
+                        mForegroundServiceTraceTitle,
+                        System.identityHashCode(this));
+                mForegroundServiceTraceTitle = null;
+            }
+        }
+    }
+
+    /**
      * This keeps track of the stacktrace where Context.startForegroundService() was called
      * for each service class. We use that when we crash the app for not calling
      * {@link #startForeground} in time, in {@link ActivityThread#throwRemoteServiceException}.
@@ -1004,4 +1116,34 @@
             return sStartForegroundServiceStackTraces.get(className);
         }
     }
+
+    /** @hide */
+    public final void callOnTimeout(int startId) {
+        // Note, because all the service callbacks (and other similar callbacks, e.g. activity
+        // callbacks) are delivered using the main handler, it's possible the service is already
+        // stopped when before this method is called, so we do a double check here.
+        if (mToken == null) {
+            Log.w(TAG, "Service already destroyed, skipping onTimeout()");
+            return;
+        }
+        try {
+            if (!mActivityManager.shouldServiceTimeOut(
+                    new ComponentName(this, mClassName), mToken)) {
+                Log.w(TAG, "Service no longer relevant, skipping onTimeout()");
+                return;
+            }
+        } catch (RemoteException ex) {
+        }
+        onTimeout(startId);
+    }
+
+    /**
+     * Callback called on timeout for {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
+     * See {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE} for more details.
+     *
+     * @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
+     * the service started.
+     */
+    public void onTimeout(int startId) {
+    }
 }
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 6d28972..f63f406 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -229,10 +229,15 @@
     public static final int CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = 1;
     /** @hide */
     public static final int CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = 2;
+    /** @hide */
+    public static final int CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE = 3;
 
     /**
      * Session flag for {@link #registerSessionListener} indicating the listener
-     * is interested in sessions on the keygaurd
+     * is interested in sessions on the keygaurd.
+     * Keyguard Session Boundaries:
+     *     START_SESSION: device starts going to sleep OR the keyguard is newly shown
+     *     END_SESSION: device starts going to sleep OR keyguard is no longer showing
      * @hide
      */
     public static final int SESSION_KEYGUARD = 1 << 0;
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/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index aaa3d21..3730a6f 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -42,7 +42,6 @@
 import android.app.time.TimeManager;
 import android.app.timedetector.TimeDetector;
 import android.app.timedetector.TimeDetectorImpl;
-import android.app.timezone.RulesManager;
 import android.app.timezonedetector.TimeZoneDetector;
 import android.app.timezonedetector.TimeZoneDetectorImpl;
 import android.app.trust.TrustManager;
@@ -52,6 +51,8 @@
 import android.app.usage.UsageStatsManager;
 import android.app.wallpapereffectsgeneration.IWallpaperEffectsGenerationManager;
 import android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager;
+import android.app.wearable.IWearableSensingManager;
+import android.app.wearable.WearableSensingManager;
 import android.apphibernation.AppHibernationManager;
 import android.appwidget.AppWidgetManager;
 import android.bluetooth.BluetoothFrameworkInitializer;
@@ -59,6 +60,7 @@
 import android.companion.ICompanionDeviceManager;
 import android.companion.virtual.IVirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceManager;
+import android.compat.Compatibility;
 import android.content.ClipboardManager;
 import android.content.ContentCaptureOptions;
 import android.content.Context;
@@ -119,6 +121,7 @@
 import android.location.ICountryDetector;
 import android.location.ILocationManager;
 import android.location.LocationManager;
+import android.media.AudioDeviceVolumeManager;
 import android.media.AudioManager;
 import android.media.MediaFrameworkInitializer;
 import android.media.MediaFrameworkPlatformInitializer;
@@ -210,6 +213,7 @@
 import android.service.persistentdata.IPersistentDataBlockService;
 import android.service.persistentdata.PersistentDataBlockManager;
 import android.service.vr.IVrManager;
+import android.system.virtualmachine.VirtualizationFrameworkInitializer;
 import android.telecom.TelecomManager;
 import android.telephony.MmsManager;
 import android.telephony.TelephonyFrameworkInitializer;
@@ -344,6 +348,13 @@
                 return new AudioManager(ctx);
             }});
 
+        registerService(Context.AUDIO_DEVICE_VOLUME_SERVICE, AudioDeviceVolumeManager.class,
+                new CachedServiceFetcher<AudioDeviceVolumeManager>() {
+            @Override
+            public AudioDeviceVolumeManager createService(ContextImpl ctx) {
+                return new AudioDeviceVolumeManager(ctx);
+            }});
+
         registerService(Context.MEDIA_ROUTER_SERVICE, MediaRouter.class,
                 new CachedServiceFetcher<MediaRouter>() {
             @Override
@@ -1084,7 +1095,10 @@
                 new CachedServiceFetcher<OverlayManager>() {
             @Override
             public OverlayManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getServiceOrThrow(Context.OVERLAY_SERVICE);
+                final IBinder b =
+                        (Compatibility.isChangeEnabled(OverlayManager.SELF_TARGETING_OVERLAY))
+                                ? ServiceManager.getService(Context.OVERLAY_SERVICE)
+                                : ServiceManager.getServiceOrThrow(Context.OVERLAY_SERVICE);
                 return new OverlayManager(ctx, IOverlayManager.Stub.asInterface(b));
             }});
 
@@ -1282,13 +1296,6 @@
             }
         });
 
-        registerService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, RulesManager.class,
-                new CachedServiceFetcher<RulesManager>() {
-            @Override
-            public RulesManager createService(ContextImpl ctx) {
-                return new RulesManager(ctx.getOuterContext());
-            }});
-
         registerService(Context.CROSS_PROFILE_APPS_SERVICE, CrossProfileApps.class,
                 new CachedServiceFetcher<CrossProfileApps>() {
                     @Override
@@ -1539,6 +1546,17 @@
                                 IAmbientContextManager.Stub.asInterface(iBinder);
                         return new AmbientContextManager(ctx.getOuterContext(), manager);
                     }});
+        registerService(Context.WEARABLE_SENSING_SERVICE, WearableSensingManager.class,
+                new CachedServiceFetcher<WearableSensingManager>() {
+                    @Override
+                    public WearableSensingManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder iBinder = ServiceManager.getServiceOrThrow(
+                                Context.WEARABLE_SENSING_SERVICE);
+                        IWearableSensingManager manager =
+                                IWearableSensingManager.Stub.asInterface(iBinder);
+                        return new WearableSensingManager(ctx.getOuterContext(), manager);
+                    }});
 
         sInitializing = true;
         try {
@@ -1566,6 +1584,7 @@
             NearbyFrameworkInitializer.registerServiceWrappers();
             OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers();
             DeviceLockFrameworkInitializer.registerServiceWrappers();
+            VirtualizationFrameworkInitializer.registerServiceWrappers();
         } finally {
             // If any of the above code throws, we're in a pretty bad shape and the process
             // will likely crash, but we'll reset it just in case there's an exception handler...
@@ -1611,6 +1630,7 @@
                 case Context.INCREMENTAL_SERVICE:
                 case Context.ETHERNET_SERVICE:
                 case Context.CONTEXTHUB_SERVICE:
+                case Context.VIRTUALIZATION_SERVICE:
                     return null;
             }
             Slog.wtf(TAG, "Manager wrapper not available: " + name);
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 0f26818..f133c8a 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": [
                 {
@@ -192,6 +201,23 @@
             "file_patterns": [
                 "(/|^)PropertyInvalidatedCache.java"
             ]
+        },
+        {
+            "name": "FrameworksCoreGameManagerTests",
+            "options": [
+                {
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
+                },
+                {
+                    "exclude-annotation": "org.junit.Ignore"
+                },
+                {
+                    "include-filter": "android.app"
+                }
+            ],
+            "file_patterns": [
+                "(/|^)GameManager[^/]*", "(/|^)GameMode[^/]*"
+            ]
         }
     ],
     "presubmit-large": [
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/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 2718054..814f38b 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -793,6 +793,24 @@
     }
 
     /**
+     * Injects an arbitrary {@link InputEvent} to the accessibility input filter, for use in testing
+     * the accessibility input filter.
+     *
+     * Events injected to the input subsystem using the standard {@link #injectInputEvent} method
+     * skip the accessibility input filter to avoid feedback loops.
+     *
+     * @hide
+     */
+    @TestApi
+    public void injectInputEventToInputFilter(@NonNull InputEvent event) {
+        try {
+            mUiAutomationConnection.injectInputEventToInputFilter(event);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while injecting input event to input filter", re);
+        }
+    }
+
+    /**
      * Sets the system settings values that control the scaling factor for animations. The scale
      * controls the animation playback speed for animations that respect these settings. Animations
      * that do not respect the settings values will not be affected by this function. A lower scale
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 0201c12..3e4e7cb 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -178,6 +178,11 @@
     }
 
     @Override
+    public void injectInputEventToInputFilter(InputEvent event) throws RemoteException {
+        mAccessibilityManager.injectInputEventToInputFilter(event);
+    }
+
+    @Override
     public void syncInputTransactions(boolean waitForAnimations) {
         synchronized (mLock) {
             throwIfCalledByNotTrustedUidLocked();
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 95e9c22..f5d657c 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -67,7 +67,6 @@
 import android.os.RemoteException;
 import android.os.StrictMode;
 import android.os.SystemProperties;
-import android.service.wallpaper.WallpaperService;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -359,13 +358,37 @@
         }
     }
 
+    /**
+     * Convenience class representing a cached wallpaper bitmap and associated data.
+     */
+    private static class CachedWallpaper {
+        final Bitmap mCachedWallpaper;
+        final int mCachedWallpaperUserId;
+        @SetWallpaperFlags final int mWhich;
+
+        CachedWallpaper(Bitmap cachedWallpaper, int cachedWallpaperUserId,
+                @SetWallpaperFlags int which) {
+            mCachedWallpaper = cachedWallpaper;
+            mCachedWallpaperUserId = cachedWallpaperUserId;
+            mWhich = which;
+        }
+
+        /**
+         * Returns true if this object represents a valid cached bitmap for the given parameters,
+         * otherwise false.
+         */
+        boolean isValid(int userId, @SetWallpaperFlags int which) {
+            return userId == mCachedWallpaperUserId && which == mWhich
+                    && !mCachedWallpaper.isRecycled();
+        }
+    }
+
     private static class Globals extends IWallpaperManagerCallback.Stub {
         private final IWallpaperManager mService;
         private boolean mColorCallbackRegistered;
         private final ArrayList<Pair<OnColorsChangedListener, Handler>> mColorListeners =
                 new ArrayList<>();
-        private Bitmap mCachedWallpaper;
-        private int mCachedWallpaperUserId;
+        private CachedWallpaper mCachedWallpaper;
         private Bitmap mDefaultWallpaper;
         private Handler mMainLooperHandler;
         private ArrayMap<LocalWallpaperColorConsumer, ArraySet<RectF>> mLocalColorCallbackAreas =
@@ -521,10 +544,7 @@
         }
 
         WallpaperColors getWallpaperColors(int which, int userId, int displayId) {
-            if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
-                throw new IllegalArgumentException(
-                        "Must request colors for exactly one kind of wallpaper");
-            }
+            checkExactlyOneWallpaperFlagSet(which);
 
             try {
                 return mService.getWallpaperColors(which, userId, displayId);
@@ -540,6 +560,15 @@
                     false /* hardware */, cmProxy);
         }
 
+        /**
+         * Retrieves the current wallpaper Bitmap, caching the result. If this fails and
+         * `returnDefault` is set, returns the Bitmap for the default wallpaper; otherwise returns
+         * null.
+         *
+         * More sophisticated caching might a) store and compare the wallpaper ID so that
+         * consecutive calls for FLAG_SYSTEM and FLAG_LOCK could return the cached wallpaper if
+         * no lock screen wallpaper is set, or b) separately cache home and lock screen wallpaper.
+         */
         public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
                 @SetWallpaperFlags int which, int userId, boolean hardware,
                 ColorManagementProxy cmProxy) {
@@ -553,16 +582,14 @@
                 }
             }
             synchronized (this) {
-                if (mCachedWallpaper != null && mCachedWallpaperUserId == userId
-                        && !mCachedWallpaper.isRecycled()) {
-                    return mCachedWallpaper;
+                if (mCachedWallpaper != null && mCachedWallpaper.isValid(userId, which)) {
+                    return mCachedWallpaper.mCachedWallpaper;
                 }
                 mCachedWallpaper = null;
-                mCachedWallpaperUserId = 0;
+                Bitmap currentWallpaper = null;
                 try {
-                    mCachedWallpaper = getCurrentWallpaperLocked(
-                            context, userId, hardware, cmProxy);
-                    mCachedWallpaperUserId = userId;
+                    currentWallpaper = getCurrentWallpaperLocked(
+                            context, which, userId, hardware, cmProxy);
                 } catch (OutOfMemoryError e) {
                     Log.w(TAG, "Out of memory loading the current wallpaper: " + e);
                 } catch (SecurityException e) {
@@ -574,8 +601,9 @@
                         throw e;
                     }
                 }
-                if (mCachedWallpaper != null) {
-                    return mCachedWallpaper;
+                if (currentWallpaper != null) {
+                    mCachedWallpaper = new CachedWallpaper(currentWallpaper, userId, which);
+                    return currentWallpaper;
                 }
             }
             if (returnDefault) {
@@ -591,7 +619,9 @@
             return null;
         }
 
-        public Rect peekWallpaperDimensions(Context context, boolean returnDefault, int userId) {
+        @Nullable
+        public Rect peekWallpaperDimensions(Context context, boolean returnDefault,
+                @SetWallpaperFlags int which, int userId) {
             if (mService != null) {
                 try {
                     if (!mService.isWallpaperSupported(context.getOpPackageName())) {
@@ -604,11 +634,10 @@
 
             Rect dimensions = null;
             synchronized (this) {
-                ParcelFileDescriptor pfd = null;
-                try {
-                    Bundle params = new Bundle();
-                    pfd = mService.getWallpaperWithFeature(context.getOpPackageName(),
-                            context.getAttributionTag(), this, FLAG_SYSTEM, params, userId);
+                Bundle params = new Bundle();
+                try (ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
+                        context.getOpPackageName(), context.getAttributionTag(), this, which,
+                        params, userId)) {
                     // Let's peek user wallpaper first.
                     if (pfd != null) {
                         BitmapFactory.Options options = new BitmapFactory.Options();
@@ -618,19 +647,14 @@
                     }
                 } catch (RemoteException ex) {
                     Log.w(TAG, "peek wallpaper dimensions failed", ex);
-                } finally {
-                    if (pfd != null) {
-                        try {
-                            pfd.close();
-                        } catch (IOException ignored) {
-                        }
-                    }
+                } catch (IOException ignored) {
+                    // This is only thrown on close and can be safely ignored.
                 }
             }
             // If user wallpaper is unavailable, may be the default one instead.
             if ((dimensions == null || dimensions.width() == 0 || dimensions.height() == 0)
                     && returnDefault) {
-                InputStream is = openDefaultWallpaper(context, FLAG_SYSTEM);
+                InputStream is = openDefaultWallpaper(context, which);
                 if (is != null) {
                     try {
                         BitmapFactory.Options options = new BitmapFactory.Options();
@@ -648,13 +672,12 @@
         void forgetLoadedWallpaper() {
             synchronized (this) {
                 mCachedWallpaper = null;
-                mCachedWallpaperUserId = 0;
                 mDefaultWallpaper = null;
             }
         }
 
-        private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware,
-                ColorManagementProxy cmProxy) {
+        private Bitmap getCurrentWallpaperLocked(Context context, @SetWallpaperFlags int which,
+                int userId, boolean hardware, ColorManagementProxy cmProxy) {
             if (mService == null) {
                 Log.w(TAG, "WallpaperService not running");
                 return null;
@@ -663,7 +686,7 @@
             try {
                 Bundle params = new Bundle();
                 ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
-                        context.getOpPackageName(), context.getAttributionTag(), this, FLAG_SYSTEM,
+                        context.getOpPackageName(), context.getAttributionTag(), this, which,
                         params, userId);
 
                 if (pfd != null) {
@@ -857,9 +880,7 @@
             throw new RuntimeException(new DeadSystemException());
         }
 
-        if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
-            throw new IllegalArgumentException("Must request exactly one kind of wallpaper");
-        }
+        checkExactlyOneWallpaperFlagSet(which);
 
         Resources resources = mContext.getResources();
         horizontalAlignment = Math.max(0, Math.min(1, horizontalAlignment));
@@ -1115,6 +1136,19 @@
     }
 
     /**
+     * Like {@link #getDrawable()} but returns a Bitmap.
+     *
+     * @param hardware Asks for a hardware backed bitmap.
+     * @param which Specifies home or lock screen
+     * @see Bitmap.Config#HARDWARE
+     * @hide
+     */
+    @Nullable
+    public Bitmap getBitmap(boolean hardware, @SetWallpaperFlags int which) {
+        return getBitmapAsUser(mContext.getUserId(), hardware, which);
+    }
+
+    /**
      * Like {@link #getDrawable()} but returns a Bitmap for the provided user.
      *
      * @hide
@@ -1125,14 +1159,42 @@
     }
 
     /**
+     * Like {@link #getDrawable()} but returns a Bitmap for the provided user.
+     *
+     * @param which Specifies home or lock screen
+     * @hide
+     */
+    public Bitmap getBitmapAsUser(int userId, boolean hardware, @SetWallpaperFlags int which) {
+        final ColorManagementProxy cmProxy = getColorManagementProxy();
+        return sGlobals.peekWallpaperBitmap(mContext, true, which, userId, hardware, cmProxy);
+    }
+
+    /**
      * Peek the dimensions of system wallpaper of the user without decoding it.
      *
      * @return the dimensions of system wallpaper
      * @hide
      */
+    @TestApi
+    @Nullable
     public Rect peekBitmapDimensions() {
-        return sGlobals.peekWallpaperDimensions(
-                mContext, true /* returnDefault */, mContext.getUserId());
+        return peekBitmapDimensions(FLAG_SYSTEM);
+    }
+
+    /**
+     * Peek the dimensions of given wallpaper of the user without decoding it.
+     *
+     * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or
+     *     {@link #FLAG_LOCK}.
+     * @return the dimensions of system wallpaper
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    public Rect peekBitmapDimensions(@SetWallpaperFlags int which) {
+        checkExactlyOneWallpaperFlagSet(which);
+        return sGlobals.peekWallpaperDimensions(mContext, true /* returnDefault */, which,
+                mContext.getUserId());
     }
 
     /**
@@ -1281,9 +1343,7 @@
      */
     @UnsupportedAppUsage
     public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, int userId) {
-        if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
-            throw new IllegalArgumentException("Must request exactly one kind of wallpaper");
-        }
+        checkExactlyOneWallpaperFlagSet(which);
 
         if (sGlobals.mService == null) {
             Log.w(TAG, "WallpaperService not running");
@@ -1318,29 +1378,57 @@
     }
 
     /**
-     * Returns the information about the wallpaper if the current wallpaper is
-     * a live wallpaper component. Otherwise, if the wallpaper is a static image,
-     * this returns null.
+     * Returns the information about the home screen wallpaper if its current wallpaper is a live
+     * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
      */
     public WallpaperInfo getWallpaperInfo() {
-        return getWallpaperInfo(mContext.getUserId());
+        return getWallpaperInfoForUser(mContext.getUserId());
     }
 
     /**
-     * Returns the information about the wallpaper if the current wallpaper is
-     * a live wallpaper component. Otherwise, if the wallpaper is a static image,
-     * this returns null.
+     * Returns the information about the home screen wallpaper if its current wallpaper is a live
+     * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
      *
      * @param userId Owner of the wallpaper.
      * @hide
      */
-    public WallpaperInfo getWallpaperInfo(int userId) {
+    public WallpaperInfo getWallpaperInfoForUser(int userId) {
+        return getWallpaperInfo(FLAG_SYSTEM, userId);
+    }
+
+    /**
+     * Returns the information about the home screen wallpaper if its current wallpaper is a live
+     * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, this
+     * returns null.
+     *
+     * @param which Specifies wallpaper to request (home or lock).
+     * @throws IllegalArgumentException if {@code which} is not exactly one of
+     * {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}.
+     */
+    @Nullable
+    public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which) {
+        return getWallpaperInfo(which, mContext.getUserId());
+    }
+
+    /**
+     * Returns the information about the designated wallpaper if its current wallpaper is a live
+     * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, this
+     * returns null.
+     *
+     * @param which Specifies wallpaper to request (home or lock).
+     * @param userId Owner of the wallpaper.
+     * @throws IllegalArgumentException if {@code which} is not exactly one of
+     * {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}.
+     * @hide
+     */
+    public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which, int userId) {
+        checkExactlyOneWallpaperFlagSet(which);
         try {
             if (sGlobals.mService == null) {
                 Log.w(TAG, "WallpaperService not running");
                 throw new RuntimeException(new DeadSystemException());
             } else {
-                return sGlobals.mService.getWallpaperInfo(userId);
+                return sGlobals.mService.getWallpaperInfoWithFlags(which, userId);
             }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1985,7 +2073,10 @@
     }
 
     /**
-     * Set the live wallpaper.
+     * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render
+     * wallpaper, usually in order to set a live wallpaper.
+     *
+     * @param name Name of the component to use.
      *
      * @hide
      */
@@ -2053,43 +2144,72 @@
     }
 
     /**
-     * Set the live wallpaper.
+     * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render
+     * wallpaper, usually in order to set a live wallpaper.
      *
      * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
      * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
      * another user's wallpaper.
      *
+     * @param name Name of the component to use.
+     * @param userId User for whom the component should be set.
+     *
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT)
     @UnsupportedAppUsage
     public boolean setWallpaperComponent(ComponentName name, int userId) {
-        if (sGlobals.mService == null) {
-            Log.w(TAG, "WallpaperService not running");
-            throw new RuntimeException(new DeadSystemException());
-        }
-        try {
-            sGlobals.mService.setWallpaperComponentChecked(name, mContext.getOpPackageName(),
-                    userId);
-            return true;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return setWallpaperComponentWithFlags(name, FLAG_SYSTEM | FLAG_LOCK, userId);
     }
 
     /**
-     * Set the live wallpaper for the given screen(s).
+     * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render
+     * wallpaper, usually in order to set a live wallpaper, for a given wallpaper destination.
      *
      * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
      * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
      * another user's wallpaper.
      *
+     * @param name Name of the component to use.
+     * @param which Specifies wallpaper destination (home and/or lock).
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT)
+    public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
+            @SetWallpaperFlags int which) {
+        return setWallpaperComponentWithFlags(name, which, mContext.getUserId());
+    }
+
+    /**
+     * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render
+     * wallpaper, usually in order to set a live wallpaper, for a given wallpaper destination.
+     *
+     * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
+     * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
+     * another user's wallpaper.
+     *
+     * @param name Name of the component to use.
+     * @param which Specifies wallpaper destination (home and/or lock).
+     * @param userId User for whom the component should be set.
+     *
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT)
     public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
-            @SetWallpaperFlags int which) {
-        return setWallpaperComponent(name);
+            @SetWallpaperFlags int which, int userId) {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperManagerService not running");
+            throw new RuntimeException(new DeadSystemException());
+        }
+        try {
+            sGlobals.mService.setWallpaperComponentChecked(name, mContext.getOpPackageName(),
+                    which, userId);
+            return true;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -2413,6 +2533,13 @@
         return mCmProxy;
     }
 
+    private static void checkExactlyOneWallpaperFlagSet(@SetWallpaperFlags int which) {
+        if (which == FLAG_SYSTEM || which == FLAG_LOCK) {
+            return;
+        }
+        throw new IllegalArgumentException("Must specify exactly one kind of wallpaper");
+    }
+
     /**
      * A hidden class to help {@link Globals#getCurrentWallpaperLocked} handle color management.
      * @hide
@@ -2456,7 +2583,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
@@ -2487,7 +2619,7 @@
          *
          * @param colors Wallpaper color info, {@code null} when not available.
          * @param which A combination of {@link #FLAG_LOCK} and {@link #FLAG_SYSTEM}
-         * @see WallpaperService.Engine#onComputeColors()
+         * @see android.service.wallpaper.WallpaperService.Engine#onComputeColors()
          */
         void onColorsChanged(@Nullable WallpaperColors colors, int which);
 
@@ -2499,7 +2631,7 @@
          * @param colors Wallpaper color info, {@code null} when not available.
          * @param which A combination of {@link #FLAG_LOCK} and {@link #FLAG_SYSTEM}
          * @param userId Owner of the wallpaper
-         * @see WallpaperService.Engine#onComputeColors()
+         * @see android.service.wallpaper.WallpaperService.Engine#onComputeColors()
          * @hide
          */
         default void onColorsChanged(@Nullable WallpaperColors colors, int which, int userId) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f4cee5a..ecea1bb 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -103,6 +103,7 @@
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.net.NetworkUtilsInternal;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 import com.android.org.conscrypt.TrustedCertificateStore;
 
@@ -171,6 +172,7 @@
     private final boolean mParentInstance;
     private final DevicePolicyResourcesManager mResourcesManager;
 
+
     /** @hide */
     public DevicePolicyManager(Context context, IDevicePolicyManager service) {
         this(context, service, false);
@@ -3822,6 +3824,27 @@
     public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1;
 
     /**
+     * Prevent an app from being placed into app standby buckets, such that it will not be subject
+     * to device resources restrictions as a result of app standby buckets.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int EXEMPT_FROM_APP_STANDBY =  0;
+
+    /**
+     * Exemptions to platform restrictions, given to an application through
+     * {@link #setApplicationExemptions(String, Set)}.
+     *
+     * @hide
+     */
+    @IntDef(prefix = { "EXEMPT_FROM_"}, value = {
+            EXEMPT_FROM_APP_STANDBY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ApplicationExemptionConstants {}
+
+    /**
      * Broadcast action: notify system apps (e.g. settings, SysUI, etc) that the device management
      * resources with IDs {@link #EXTRA_RESOURCE_IDS} has been updated, the updated resources can be
      * retrieved using {@link DevicePolicyResourcesManager#getDrawable} and
@@ -3914,6 +3937,34 @@
         throw new SecurityException("not implemented");
     }
 
+    // TODO: Expose this as SystemAPI once we add the query API
+    /**
+     * @hide
+     */
+    public static final String AUTO_TIMEZONE_POLICY = "autoTimezone";
+
+    /**
+     * @hide
+     */
+    public static final String PERMISSION_GRANT_POLICY_KEY = "permissionGrant";
+
+    // TODO: Expose this as SystemAPI once we add the query API
+    /**
+     * @hide
+     */
+    public static String PERMISSION_GRANT_POLICY(
+            @NonNull String packageName, @NonNull String permission) {
+        Objects.requireNonNull(packageName);
+        Objects.requireNonNull(permission);
+        return PERMISSION_GRANT_POLICY_KEY + "_" + packageName + "_" + permission;
+    }
+
+    // TODO: Expose this as SystemAPI once we add the query API
+    /**
+     * @hide
+     */
+    public static final String LOCK_TASK_POLICY = "lockTask";
+
     /**
      * 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
@@ -6207,46 +6258,46 @@
     public static final int WIPE_SILENTLY = 0x0008;
 
     /**
-     * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
-     * other users will remain unaffected. Calling from the primary user will cause the device to
-     * reboot, erasing all device data - including all the secondary users and their data - while
-     * booting up.
-     * <p>
-     * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to
-     * be able to call this method; if it has not, a security exception will be thrown.
-     *
-     * If the caller is a profile owner of an organization-owned managed profile, it may
-     * additionally call this method on the parent instance.
-     * Calling this method on the parent {@link DevicePolicyManager} instance would wipe the
-     * entire device, while calling it on the current profile instance would relinquish the device
-     * for personal use, removing the managed profile and all policies set by the profile owner.
+     * See {@link #wipeData(int, CharSequence)}
      *
      * @param flags Bit mask of additional options: currently supported flags are
-     *            {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
-     *            {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
-     * @throws SecurityException if the calling application does not own an active administrator
-     *            that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} or is not granted the
-     *            {@link android.Manifest.permission#MASTER_CLEAR} permission.
+     *              {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
+     *              {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
+     * @throws SecurityException     if the calling application does not own an active
+     *                               administrator
+     *                               that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is
+     *                               not granted the
+     *                               {@link android.Manifest.permission#MASTER_CLEAR} permission.
+     * @throws IllegalStateException if called on last full-user or system-user
+     * @see #wipeDevice(int)
+     * @see #wipeData(int, CharSequence)
      */
     public void wipeData(int flags) {
-        wipeDataInternal(flags, "");
+        wipeDataInternal(flags,
+                /* wipeReasonForUser= */ "",
+                /* factoryReset= */ false);
     }
 
     /**
-     * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
-     * other users will remain unaffected, the provided reason for wiping data can be shown to
-     * user. Calling from the primary user will cause the device to reboot, erasing all device data
-     * - including all the secondary users and their data - while booting up. In this case, we don't
-     * show the reason to the user since the device would be factory reset.
-     * <p>
-     * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to
-     * be able to call this method; if it has not, a security exception will be thrown.
+     * Ask that all user data be wiped.
      *
-     * If the caller is a profile owner of an organization-owned managed profile, it may
-     * additionally call this method on the parent instance.
-     * Calling this method on the parent {@link DevicePolicyManager} instance would wipe the
-     * entire device, while calling it on the current profile instance would relinquish the device
-     * for personal use, removing the managed profile and all policies set by the profile owner.
+     * <p>
+     * If called as a secondary user or managed profile, the user itself and its associated user
+     * data will be wiped. In particular, If the caller is a profile owner of an
+     * organization-owned managed profile, calling this method will relinquish the device for
+     * personal use, removing the managed profile and all policies set by the profile owner.
+     * </p>
+     *
+     * <p>
+     * Calling this method from the primary user will only work if the calling app is targeting
+     * Android 13 or below, in which case it will cause the device to reboot, erasing all device
+     * data - including all the secondary users and their data - while booting up. If an app
+     * targeting Android 13+ is calling this method from the primary user or last full user,
+     * {@link IllegalStateException} will be thrown.
+     * </p>
+     *
+     * If an app wants to wipe the entire device irrespective of which user they are from, they
+     * should use {@link #wipeDevice} instead.
      *
      * @param flags Bit mask of additional options: currently supported flags are
      *            {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA} and
@@ -6254,30 +6305,61 @@
      * @param reason a string that contains the reason for wiping data, which can be
      *            presented to the user.
      * @throws SecurityException if the calling application does not own an active administrator
-     *            that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} or is not granted the
+     *            that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is not granted the
      *            {@link android.Manifest.permission#MASTER_CLEAR} permission.
      * @throws IllegalArgumentException if the input reason string is null or empty, or if
      *            {@link #WIPE_SILENTLY} is set.
+     * @throws IllegalStateException if called on last full-user or system-user
+     * @see #wipeDevice(int)
+     * @see #wipeData(int)
      */
     public void wipeData(int flags, @NonNull CharSequence reason) {
         Objects.requireNonNull(reason, "reason string is null");
         Preconditions.checkStringNotEmpty(reason, "reason string is empty");
         Preconditions.checkArgument((flags & WIPE_SILENTLY) == 0, "WIPE_SILENTLY cannot be set");
-        wipeDataInternal(flags, reason.toString());
+        wipeDataInternal(flags, reason.toString(), /* factoryReset= */ false);
     }
 
     /**
-     * Internal function for both {@link #wipeData(int)} and
-     * {@link #wipeData(int, CharSequence)} to call.
+     * Ask that the device be wiped and factory reset.
      *
+     * <p>
+     * The calling Device Owner or Organization Owned Profile Owner must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to be able to call this method; if it has
+     * not, a security exception will be thrown.
+     *
+     * @param flags Bit mask of additional options: currently supported flags are
+     *              {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
+     *              {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
+     * @throws SecurityException if the calling application does not own an active administrator
+     *                           that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is not
+     *                           granted the {@link android.Manifest.permission#MASTER_CLEAR}
+     *                           permission.
      * @see #wipeData(int)
      * @see #wipeData(int, CharSequence)
-     * @hide
      */
-    private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
+    // TODO(b/255323293) Add host-side tests
+    public void wipeDevice(int flags) {
+        wipeDataInternal(flags,
+                /* wipeReasonForUser= */ "",
+                /* factoryReset= */ true);
+    }
+
+    /**
+     * Internal function for {@link #wipeData(int)}, {@link #wipeData(int, CharSequence)}
+     * and {@link #wipeDevice(int)} to call.
+     *
+     * @hide
+     * @see #wipeData(int)
+     * @see #wipeData(int, CharSequence)
+     * @see #wipeDevice(int)
+     */
+    private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser,
+            boolean factoryReset) {
         if (mService != null) {
             try {
-                mService.wipeDataWithReason(flags, wipeReasonForUser, mParentInstance);
+                mService.wipeDataWithReason(flags, wipeReasonForUser, mParentInstance,
+                        factoryReset);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -8642,7 +8724,7 @@
     public void reportFailedPasswordAttempt(int userHandle) {
         if (mService != null) {
             try {
-                mService.reportFailedPasswordAttempt(userHandle);
+                mService.reportFailedPasswordAttempt(userHandle, mParentInstance);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -14695,6 +14777,95 @@
     }
 
     /**
+     * Service-specific error code used in {@link #setApplicationExemptions(String, Set)} and
+     * {@link #getApplicationExemptions(String)}.
+     * @hide
+     */
+    public static final int ERROR_PACKAGE_NAME_NOT_FOUND = 1;
+
+    /**
+     * Called by an application with the
+     * {@link  android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS} permission, to
+     * grant platform restriction exemptions to a given application.
+     *
+     * @param  packageName The package name of the application to be exempt.
+     * @param  exemptions The set of exemptions to be applied.
+     * @throws SecurityException If the caller does not have
+     *             {@link  android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS}
+     * @throws NameNotFoundException If either the package is not installed or the package is not
+     *              visible to the caller.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS)
+    public void setApplicationExemptions(@NonNull String packageName,
+            @NonNull @ApplicationExemptionConstants Set<Integer> exemptions)
+            throws NameNotFoundException {
+        throwIfParentInstance("setApplicationExemptions");
+        if (mService != null) {
+            try {
+                mService.setApplicationExemptions(packageName,
+                        ArrayUtils.convertToIntArray(new ArraySet<>(exemptions)));
+            } catch (ServiceSpecificException e) {
+                switch (e.errorCode) {
+                    case ERROR_PACKAGE_NAME_NOT_FOUND:
+                        throw new NameNotFoundException(e.getMessage());
+                    default:
+                        throw new RuntimeException(
+                                "Unknown error setting application exemptions: " + e.errorCode, e);
+                }
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns all the platform restriction exemptions currently applied to an application. Called
+     * by an application with the
+     * {@link  android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS} permission.
+     *
+     * @param  packageName The package name to check.
+     * @return A set of platform restrictions an application is exempt from.
+     * @throws SecurityException If the caller does not have
+     *             {@link  android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS}
+     * @throws NameNotFoundException If either the package is not installed or the package is not
+     *              visible to the caller.
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS)
+    public Set<Integer> getApplicationExemptions(@NonNull String packageName)
+            throws NameNotFoundException {
+        throwIfParentInstance("getApplicationExemptions");
+        if (mService == null) {
+            return Collections.emptySet();
+        }
+        try {
+            return intArrayToSet(mService.getApplicationExemptions(packageName));
+        } catch (ServiceSpecificException e) {
+            switch (e.errorCode) {
+                case ERROR_PACKAGE_NAME_NOT_FOUND:
+                    throw new NameNotFoundException(e.getMessage());
+                default:
+                    throw new RuntimeException(
+                            "Unknown error getting application exemptions: " + e.errorCode, e);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private Set<Integer> intArrayToSet(int[] array) {
+        Set<Integer> set = new ArraySet<>();
+        for (int item : array) {
+            set.add(item);
+        }
+        return set;
+    }
+
+    /**
      * Called by a device owner or a profile owner to disable user control over apps. User will not
      * be able to clear app data or force-stop packages. When called by a device owner, applies to
      * all users on the device. Starting from Android 13, packages with user control disabled are
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index b6f0916..537b8d4 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -269,4 +269,9 @@
      * {@link #supportsResetOp(int)} is true.
      */
     public abstract void resetOp(int op, String packageName, @UserIdInt int userId);
+
+    /**
+     * Returns whether new "turn off work" behavior is enabled via feature flag.
+     */
+    public abstract boolean isKeepProfilesRunningEnabled();
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 75bfc25..8a40265 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -117,7 +117,10 @@
 
     void lockNow(int flags, boolean parent);
 
-    void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent);
+    /**
+    * @param factoryReset only applicable when `targetSdk >= U`, either tries to factoryReset/fail or removeUser/fail otherwise
+    **/
+    void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent, boolean factoryReset);
 
     void setFactoryResetProtectionPolicy(in ComponentName who, in FactoryResetProtectionPolicy policy);
     FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who);
@@ -161,7 +164,7 @@
     boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle);
 
     void reportPasswordChanged(in PasswordMetrics metrics, int userId);
-    void reportFailedPasswordAttempt(int userHandle);
+    void reportFailedPasswordAttempt(int userHandle, boolean parent);
     void reportSuccessfulPasswordAttempt(int userHandle);
     void reportFailedBiometricAttempt(int userHandle);
     void reportSuccessfulBiometricAttempt(int userHandle);
@@ -565,4 +568,7 @@
     boolean shouldAllowBypassingDevicePolicyManagementRoleQualification();
 
     List<UserHandle> getPolicyManagedProfiles(in UserHandle userHandle);
+
+    void setApplicationExemptions(String packageName, in int[]exemptions);
+    int[] getApplicationExemptions(String packageName);
 }
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index b1b59b0..e323e89 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -20,7 +20,8 @@
 import android.annotation.Nullable;
 import android.app.IBackupAgent;
 import android.app.QueuedWork;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
 import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -41,6 +42,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
 
 import libcore.io.IoUtils;
 
@@ -136,7 +138,7 @@
 public abstract class BackupAgent extends ContextWrapper {
     private static final String TAG = "BackupAgent";
     private static final boolean DEBUG = false;
-    private static final int DEFAULT_OPERATION_TYPE = OperationType.BACKUP;
+    private static final int DEFAULT_BACKUP_DESTINATION = BackupDestination.CLOUD;
 
     /** @hide */
     public static final int RESULT_SUCCESS = 0;
@@ -202,10 +204,11 @@
 
     Handler mHandler = null;
 
+    @Nullable private volatile BackupRestoreEventLogger mLogger = null;
     @Nullable private UserHandle mUser;
      // This field is written from the main thread (in onCreate), and read in a Binder thread (in
      // onFullBackup that is called from system_server via Binder).
-    @OperationType private volatile int mOperationType = DEFAULT_OPERATION_TYPE;
+    @BackupDestination private volatile int mBackupDestination = DEFAULT_BACKUP_DESTINATION;
 
     Handler getHandler() {
         if (mHandler == null) {
@@ -234,6 +237,20 @@
         } catch (InterruptedException e) { /* ignored */ }
     }
 
+    /**
+     * Get a logger to record app-specific backup and restore events that are happening during a
+     * backup or restore operation.
+     *
+     * <p>The logger instance had been created by the system with the correct {@link
+     * BackupRestoreEventLogger.OperationType} that corresponds to the operation the {@code
+     * BackupAgent} is currently handling.
+     *
+     * @hide
+     */
+    @Nullable
+    public BackupRestoreEventLogger getBackupRestoreEventLogger() {
+        return mLogger;
+    }
 
     public BackupAgent() {
         super(null);
@@ -249,13 +266,6 @@
     }
 
     /**
-     * @hide
-     */
-    public void onCreate(UserHandle user) {
-        onCreate(user, DEFAULT_OPERATION_TYPE);
-    }
-
-    /**
      * Provided as a convenience for agent implementations that need an opportunity
      * to do one-time initialization before the actual backup or restore operation
      * is begun with information about the calling user.
@@ -263,11 +273,33 @@
      *
      * @hide
      */
-    public void onCreate(UserHandle user, @OperationType int operationType) {
+    public void onCreate(UserHandle user) {
         onCreate();
+    }
 
+    /**
+     * @deprecated Use {@link BackupAgent#onCreate(UserHandle, int, int)} instead.
+     *
+     * @hide
+     */
+    @Deprecated
+    public void onCreate(UserHandle user, @BackupDestination int backupDestination) {
         mUser = user;
-        mOperationType = operationType;
+        mBackupDestination = backupDestination;
+
+        onCreate(user);
+    }
+
+    /**
+    * @hide
+    */
+    public void onCreate(UserHandle user, @BackupDestination int backupDestination,
+            @OperationType int operationType) {
+        mUser = user;
+        mBackupDestination = backupDestination;
+        mLogger = new BackupRestoreEventLogger(operationType);
+
+        onCreate(user, backupDestination);
     }
 
     /**
@@ -414,7 +446,7 @@
      */
     public void onFullBackup(FullBackupDataOutput data) throws IOException {
         FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this,
-                mOperationType);
+                mBackupDestination);
         if (!backupScheme.isFullBackupEnabled(data.getTransportFlags())) {
             return;
         }
@@ -624,7 +656,7 @@
         if (includeMap == null || includeMap.size() == 0) {
             // Do entire sub-tree for the provided token.
             fullBackupFileTree(packageName, domainToken,
-                    FullBackup.getBackupScheme(this, mOperationType)
+                    FullBackup.getBackupScheme(this, mBackupDestination)
                             .tokenToDirectoryPath(domainToken),
                     filterSet, traversalExcludeSet, data);
         } else if (includeMap.get(domainToken) != null) {
@@ -796,7 +828,7 @@
                                             ArraySet<String> systemExcludes,
             FullBackupDataOutput output) {
         // Pull out the domain and set it aside to use when making the tarball.
-        String domainPath = FullBackup.getBackupScheme(this, mOperationType)
+        String domainPath = FullBackup.getBackupScheme(this, mBackupDestination)
                 .tokenToDirectoryPath(domain);
         if (domainPath == null) {
             // Should never happen.
@@ -908,7 +940,7 @@
     }
 
     private boolean isFileEligibleForRestore(File destination) throws IOException {
-        FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mOperationType);
+        FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mBackupDestination);
         if (!bs.isFullRestoreEnabled()) {
             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
                 Log.v(FullBackup.TAG_XML_PARSER,
@@ -982,7 +1014,7 @@
                 + " domain=" + domain + " relpath=" + path + " mode=" + mode
                 + " mtime=" + mtime);
 
-        basePath = FullBackup.getBackupScheme(this, mOperationType).tokenToDirectoryPath(
+        basePath = FullBackup.getBackupScheme(this, mBackupDestination).tokenToDirectoryPath(
                 domain);
         if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
             mode = -1;  // < 0 is a token to skip attempting a chmod()
@@ -1305,6 +1337,16 @@
                 }
             }
         }
+
+        @Override
+        public void getLoggerResults(
+                AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> in) {
+            if (mLogger != null) {
+                in.complete(mLogger.getLoggingResults());
+            } else {
+                in.complete(Collections.emptyList());
+            }
+        }
     }
 
     static class FailRunnable implements Runnable {
diff --git a/core/java/android/app/backup/BackupAnnotations.java b/core/java/android/app/backup/BackupAnnotations.java
new file mode 100644
index 0000000..d922861
--- /dev/null
+++ b/core/java/android/app/backup/BackupAnnotations.java
@@ -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 android.app.backup;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Annotations related to Android Backup&Restore.
+ *
+ * @hide
+ */
+public class BackupAnnotations {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            OperationType.UNKNOWN,
+            OperationType.BACKUP,
+            OperationType.RESTORE,
+    })
+    public @interface OperationType {
+        int UNKNOWN = -1;
+        int BACKUP = 0;
+        int RESTORE = 1;
+    }
+
+    /**
+     * Denotes where the backup data is going (e.g. to the cloud or directly to the other device)
+     * during backup or where it is coming from during restore.
+     *
+     * @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            BackupDestination.CLOUD,
+            BackupDestination.DEVICE_TRANSFER,
+            BackupDestination.ADB_BACKUP
+    })
+    public @interface BackupDestination {
+        // A cloud backup.
+        int CLOUD = 0;
+        // A device to device migration.
+        int DEVICE_TRANSFER = 1;
+        // An adb backup.
+        int ADB_BACKUP = 2;
+    }
+}
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index d2c7972..7255c3e 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -200,22 +200,6 @@
     @SystemApi
     public static final int ERROR_TRANSPORT_INVALID = -2;
 
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-        OperationType.BACKUP,
-        OperationType.MIGRATION,
-        OperationType.ADB_BACKUP,
-    })
-    public @interface OperationType {
-        // A backup / restore to / from an off-device location, e.g. cloud.
-        int BACKUP = 0;
-        // A direct transfer to another device.
-        int MIGRATION = 1;
-        // Backup via adb, data saved on the host machine.
-        int ADB_BACKUP = 3;
-    }
-
     private Context mContext;
     @UnsupportedAppUsage
     private static IBackupManager sService;
@@ -1034,6 +1018,29 @@
         }
     }
 
+    /**
+     * Get an instance of {@link BackupRestoreEventLogger} to report B&R related events during an
+     * ongoing backup or restore operation.
+     *
+     * @param backupAgent the agent currently running a B&R operation.
+     *
+     * @return an instance of {@code BackupRestoreEventLogger} or {@code null} if the agent has not
+     *         finished initialisation, i.e. {@link BackupAgent#onCreate()} has not been called yet.
+     * @throws IllegalStateException if called before the agent has finished initialisation.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public BackupRestoreEventLogger getBackupRestoreEventLogger(@NonNull BackupAgent backupAgent) {
+        BackupRestoreEventLogger logger = backupAgent.getBackupRestoreEventLogger();
+        if (logger == null) {
+            throw new IllegalStateException("Attempting to get logger on an uninitialised "
+                    + "BackupAgent");
+        }
+        return backupAgent.getBackupRestoreEventLogger();
+    }
+
     /*
      * We wrap incoming binder calls with a private class implementation that
      * redirects them into main-thread actions.  This serializes the backup
diff --git a/core/java/android/app/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java
index 07e7688a..d134ca2 100644
--- a/core/java/android/app/backup/BackupManagerMonitor.java
+++ b/core/java/android/app/backup/BackupManagerMonitor.java
@@ -129,6 +129,13 @@
    */
   public static final String EXTRA_LOG_OLD_VERSION = "android.app.backup.extra.LOG_OLD_VERSION";
 
+  /**
+   * ParcelableList: when we have an event of id LOG_EVENT_ID_AGENT_LOGGING_RESULTS we send a list
+   * of {@link android.app.backup.BackupRestoreEventLogger.DataTypeResult}.
+   */
+  public static final String EXTRA_LOG_AGENT_LOGGING_RESULTS =
+          "android.app.backup.extra.LOG_AGENT_LOGGING_RESULTS";
+
   // TODO complete this list with all log messages. And document properly.
   public static final int LOG_EVENT_ID_FULL_BACKUP_CANCEL = 4;
   public static final int LOG_EVENT_ID_ILLEGAL_KEY = 5;
@@ -171,15 +178,10 @@
   public static final int LOG_EVENT_ID_WIDGET_UNKNOWN_VERSION = 48;
   public static final int LOG_EVENT_ID_NO_PACKAGES = 49;
   public static final int LOG_EVENT_ID_TRANSPORT_IS_NULL = 50;
+  /** The transport returned {@link BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED}. */
+  public static final int LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = 51;
 
-    /**
-     * The transport returned {@link BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED}.
-     */
-    public static final int LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = 51;
-
-
-
-
+  public static final int LOG_EVENT_ID_AGENT_LOGGING_RESULTS = 52;
 
   /**
    * This method will be called each time something important happens on BackupManager.
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.aidl b/core/java/android/app/backup/BackupRestoreEventLogger.aidl
new file mode 100644
index 0000000..d6ef4e6
--- /dev/null
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.aidl
@@ -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.
+ */
+
+package android.app.backup;
+
+parcelable BackupRestoreEventLogger.DataTypeResult;
\ No newline at end of file
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index 760c6f0..5805826 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -16,9 +16,14 @@
 
 package android.app.backup;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.app.backup.BackupAnnotations.OperationType;
 import android.util.Slog;
 
 import java.lang.annotation.Retention;
@@ -31,7 +36,6 @@
 import java.util.List;
 import java.util.Map;
 
-// TODO(b/244436184): Make this @SystemApi
 /**
  * Class to log B&R stats for each data type that is backed up and restored by the calling app.
  *
@@ -42,29 +46,19 @@
  *
  * @hide
  */
+@SystemApi
 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.
+     *
+     * @hide
      */
     public static final int DATA_TYPES_ALLOWED = 15;
 
     /**
-     * Operation types for which this logger can be used.
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-            OperationType.BACKUP,
-            OperationType.RESTORE
-    })
-    @interface OperationType {
-        int BACKUP = 1;
-        int RESTORE = 2;
-    }
-
-    /**
      * Denotes that the annotated element identifies a data type as required by the logging methods
      * of {@code BackupRestoreEventLogger}
      */
@@ -87,6 +81,8 @@
      *                      {@link OperationType}. Attempts to use logging methods that don't match
      *                      the specified operation type will be rejected (e.g. use backup methods
      *                      for a restore logger and vice versa).
+     *
+     * @hide
      */
     public BackupRestoreEventLogger(@OperationType int operationType) {
         mOperationType = operationType;
@@ -111,11 +107,9 @@
      *
      * @param dataType the type of data being backed.
      * @param count number of items of the given type that have been successfully backed up.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
-        return logSuccess(OperationType.BACKUP, dataType, count);
+    public void logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
+        logSuccess(OperationType.BACKUP, dataType, count);
     }
 
     /**
@@ -130,12 +124,10 @@
      * @param dataType the type of data being backed.
      * @param count number of items of the given type that have failed to back up.
      * @param error optional, the error that has caused the failure.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
+    public void logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
             @Nullable @BackupRestoreError String error) {
-        return logFailure(OperationType.BACKUP, dataType, count, error);
+        logFailure(OperationType.BACKUP, dataType, count, error);
     }
 
     /**
@@ -151,12 +143,10 @@
      *
      * @param dataType the type of data being backed up.
      * @param metaData the metadata associated with the data type.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
+    public void logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
             @NonNull String metaData) {
-        return logMetaData(OperationType.BACKUP, dataType, metaData);
+        logMetaData(OperationType.BACKUP, dataType, metaData);
     }
 
     /**
@@ -172,11 +162,9 @@
      *
      * @param dataType the type of data being restored.
      * @param count number of items of the given type that have been successfully restored.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
-        return logSuccess(OperationType.RESTORE, dataType, count);
+    public void logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
+        logSuccess(OperationType.RESTORE, dataType, count);
     }
 
     /**
@@ -193,12 +181,10 @@
      * @param dataType the type of data being restored.
      * @param count number of items of the given type that have failed to restore.
      * @param error optional, the error that has caused the failure.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
+    public void logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
             @Nullable @BackupRestoreError String error) {
-        return logFailure(OperationType.RESTORE, dataType, count, error);
+        logFailure(OperationType.RESTORE, dataType, count, error);
     }
 
     /**
@@ -216,12 +202,10 @@
      *
      * @param dataType the type of data being restored.
      * @param metadata the metadata associated with the data type.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
+    public void logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
             @NonNull  String metadata) {
-        return logMetaData(OperationType.RESTORE, dataType, metadata);
+        logMetaData(OperationType.RESTORE, dataType, metadata);
     }
 
     /**
@@ -240,52 +224,47 @@
      *
      * @hide
      */
-    public @OperationType int getOperationType() {
+    @OperationType
+    public int getOperationType() {
         return mOperationType;
     }
 
-    private boolean logSuccess(@OperationType int operationType,
+    private void logSuccess(@OperationType int operationType,
             @BackupRestoreDataType String dataType, int count) {
         DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
         if (dataTypeResult == null) {
-            return false;
+            return;
         }
 
         dataTypeResult.mSuccessCount += count;
         mResults.put(dataType, dataTypeResult);
-
-        return true;
     }
 
-    private boolean logFailure(@OperationType int operationType,
+    private void logFailure(@OperationType int operationType,
             @NonNull @BackupRestoreDataType String dataType, int count,
             @Nullable @BackupRestoreError String error) {
         DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
         if (dataTypeResult == null) {
-            return false;
+            return;
         }
 
         dataTypeResult.mFailCount += count;
         if (error != null) {
             dataTypeResult.mErrors.merge(error, count, Integer::sum);
         }
-
-        return true;
     }
 
-    private boolean logMetaData(@OperationType int operationType,
+    private void logMetaData(@OperationType int operationType,
             @NonNull @BackupRestoreDataType String dataType, @NonNull String metaData) {
         if (mHashDigest == null) {
-            return false;
+            return;
         }
         DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
         if (dataTypeResult == null) {
-            return false;
+            return;
         }
 
         dataTypeResult.mMetadataHash = getMetaDataHash(metaData);
-
-        return true;
     }
 
     /**
@@ -325,7 +304,7 @@
     /**
      * Encapsulate logging results for a single data type.
      */
-    public static class DataTypeResult {
+    public static final class DataTypeResult implements Parcelable {
         @BackupRestoreDataType
         private final String mDataType;
         private int mSuccessCount;
@@ -333,7 +312,7 @@
         private final Map<String, Integer> mErrors = new HashMap<>();
         private byte[] mMetadataHash;
 
-        public DataTypeResult(String dataType) {
+        public DataTypeResult(@NonNull String dataType) {
             mDataType = dataType;
         }
 
@@ -375,5 +354,58 @@
         public byte[] getMetadataHash() {
             return mMetadataHash;
         }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeString(mDataType);
+
+            dest.writeInt(mSuccessCount);
+
+            dest.writeInt(mFailCount);
+
+            Bundle errorsBundle = new Bundle();
+            for (Map.Entry<String, Integer> e : mErrors.entrySet()) {
+                errorsBundle.putInt(e.getKey(), e.getValue());
+            }
+            dest.writeBundle(errorsBundle);
+
+            dest.writeByteArray(mMetadataHash);
+        }
+
+        @NonNull
+        public static final Parcelable.Creator<DataTypeResult> CREATOR =
+                new Parcelable.Creator<>() {
+                    public DataTypeResult createFromParcel(Parcel in) {
+                        String dataType = in.readString();
+
+                        int successCount = in.readInt();
+
+                        int failCount = in.readInt();
+
+                        Map<String, Integer> errors = new ArrayMap<>();
+                        Bundle errorsBundle = in.readBundle(getClass().getClassLoader());
+                        for (String key : errorsBundle.keySet()) {
+                            errors.put(key, errorsBundle.getInt(key));
+                        }
+
+                        byte[] metadataHash = in.createByteArray();
+
+                        DataTypeResult result = new DataTypeResult(dataType);
+                        result.mSuccessCount = successCount;
+                        result.mFailCount = failCount;
+                        result.mErrors.putAll(errors);
+                        result.mMetadataHash = metadataHash;
+                        return result;
+                    }
+
+                    public DataTypeResult[] newArray(int size) {
+                        return new DataTypeResult[size];
+                    }
+                };
     }
 }
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 90e9df4..f56c8c3 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -661,8 +661,6 @@
      *
      * <p>Backups requested from outside the framework may pass in a monitor with the request,
      * however backups initiated by the framework will call this method to retrieve one.
-     *
-     * @hide
      */
     @Nullable
     public BackupManagerMonitor getBackupManagerMonitor() {
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index bf9a9b0..6371871 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -16,10 +16,9 @@
 
 package android.app.backup;
 
-import static android.app.backup.BackupManager.OperationType;
-
 import android.annotation.Nullable;
 import android.annotation.StringDef;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
@@ -123,20 +122,20 @@
 
     /**
      * Identify {@link BackupScheme} object by package and operation type
-     * (see {@link OperationType}) it corresponds to.
+     * (see {@link BackupDestination}) it corresponds to.
      */
     private static class BackupSchemeId {
         final String mPackageName;
-        @OperationType final int mOperationType;
+        @BackupDestination final int mBackupDestination;
 
-        BackupSchemeId(String packageName, @OperationType int operationType) {
+        BackupSchemeId(String packageName, @BackupDestination int backupDestination) {
             mPackageName = packageName;
-            mOperationType = operationType;
+            mBackupDestination = backupDestination;
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(mPackageName, mOperationType);
+            return Objects.hash(mPackageName, mBackupDestination);
         }
 
         @Override
@@ -149,7 +148,7 @@
             }
             BackupSchemeId that = (BackupSchemeId) object;
             return Objects.equals(mPackageName, that.mPackageName) &&
-                    Objects.equals(mOperationType, that.mOperationType);
+                    Objects.equals(mBackupDestination, that.mBackupDestination);
         }
     }
 
@@ -164,19 +163,20 @@
             new ArrayMap<>();
 
     static synchronized BackupScheme getBackupScheme(Context context,
-            @OperationType int operationType) {
-        BackupSchemeId backupSchemeId = new BackupSchemeId(context.getPackageName(), operationType);
+            @BackupDestination int backupDestination) {
+        BackupSchemeId backupSchemeId = new BackupSchemeId(context.getPackageName(),
+                backupDestination);
         BackupScheme backupSchemeForPackage =
                 kPackageBackupSchemeMap.get(backupSchemeId);
         if (backupSchemeForPackage == null) {
-            backupSchemeForPackage = new BackupScheme(context, operationType);
+            backupSchemeForPackage = new BackupScheme(context, backupDestination);
             kPackageBackupSchemeMap.put(backupSchemeId, backupSchemeForPackage);
         }
         return backupSchemeForPackage;
     }
 
     public static BackupScheme getBackupSchemeForTest(Context context) {
-        BackupScheme testing = new BackupScheme(context, OperationType.BACKUP);
+        BackupScheme testing = new BackupScheme(context, BackupDestination.CLOUD);
         testing.mExcludes = new ArraySet();
         testing.mIncludes = new ArrayMap();
         return testing;
@@ -303,7 +303,7 @@
 
         final int mDataExtractionRules;
         final int mFullBackupContent;
-        @OperationType final int mOperationType;
+        @BackupDestination final int mBackupDestination;
         final PackageManager mPackageManager;
         final StorageManager mStorageManager;
         final String mPackageName;
@@ -426,12 +426,12 @@
          */
         ArraySet<PathWithRequiredFlags> mExcludes;
 
-        BackupScheme(Context context, @OperationType int operationType) {
+        BackupScheme(Context context, @BackupDestination int backupDestination) {
             ApplicationInfo applicationInfo = context.getApplicationInfo();
 
             mDataExtractionRules = applicationInfo.dataExtractionRulesRes;
             mFullBackupContent = applicationInfo.fullBackupContent;
-            mOperationType = operationType;
+            mBackupDestination = backupDestination;
             mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
             mPackageManager = context.getPackageManager();
             mPackageName = context.getPackageName();
@@ -568,7 +568,7 @@
                 }
 
                 try {
-                    parseSchemeForOperationType(mOperationType);
+                    parseSchemeForBackupDestination(mBackupDestination);
                 } catch (PackageManager.NameNotFoundException e) {
                     // Throw it as an IOException
                     throw new IOException(e);
@@ -576,12 +576,12 @@
             }
         }
 
-        private void parseSchemeForOperationType(@OperationType int operationType)
+        private void parseSchemeForBackupDestination(@BackupDestination int backupDestination)
                 throws PackageManager.NameNotFoundException, IOException, XmlPullParserException {
-            String configSection = getConfigSectionForOperationType(operationType);
+            String configSection = getConfigSectionForBackupDestination(backupDestination);
             if (configSection == null) {
-                Slog.w(TAG, "Given operation type isn't supported by backup scheme: "
-                        + operationType);
+                Slog.w(TAG, "Given backup destination isn't supported by backup scheme: "
+                        + backupDestination);
                 return;
             }
 
@@ -600,7 +600,7 @@
                 }
             }
 
-            if (operationType == OperationType.MIGRATION
+            if (backupDestination == BackupDestination.DEVICE_TRANSFER
                     && CompatChanges.isChangeEnabled(IGNORE_FULL_BACKUP_CONTENT_IN_D2D)) {
                 mIsUsingNewScheme = true;
                 return;
@@ -615,11 +615,12 @@
         }
 
         @Nullable
-        private String getConfigSectionForOperationType(@OperationType int operationType)  {
-            switch (operationType) {
-                case OperationType.BACKUP:
+        private String getConfigSectionForBackupDestination(
+                @BackupDestination int backupDestination)  {
+            switch (backupDestination) {
+                case BackupDestination.CLOUD:
                     return ConfigSection.CLOUD_BACKUP;
-                case OperationType.MIGRATION:
+                case BackupDestination.DEVICE_TRANSFER:
                     return ConfigSection.DEVICE_TRANSFER;
                 default:
                     return null;
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index 2581daa..d628b7f 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -30,6 +30,8 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import dalvik.system.CloseGuard;
 
 import java.util.List;
@@ -79,6 +81,7 @@
     private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
 
     private final AppPredictionSessionId mSessionId;
+    @GuardedBy("itself")
     private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
 
     /**
@@ -94,7 +97,7 @@
         IBinder b = ServiceManager.getService(Context.APP_PREDICTION_SERVICE);
         mPredictionManager = IPredictionManager.Stub.asInterface(b);
         mSessionId = new AppPredictionSessionId(
-                context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
+                context.getPackageName() + ":" + UUID.randomUUID(), context.getUserId());
         try {
             mPredictionManager.createPredictionSession(predictionContext, mSessionId, getToken());
         } catch (RemoteException e) {
@@ -155,6 +158,15 @@
      */
     public void registerPredictionUpdates(@NonNull @CallbackExecutor Executor callbackExecutor,
             @NonNull AppPredictor.Callback callback) {
+        synchronized (mRegisteredCallbacks) {
+            registerPredictionUpdatesLocked(callbackExecutor, callback);
+        }
+    }
+
+    @GuardedBy("mRegisteredCallbacks")
+    private void registerPredictionUpdatesLocked(
+            @NonNull @CallbackExecutor Executor callbackExecutor,
+            @NonNull AppPredictor.Callback callback) {
         if (mIsClosed.get()) {
             throw new IllegalStateException("This client has already been destroyed.");
         }
@@ -183,6 +195,13 @@
      * @param callback The callback to be unregistered.
      */
     public void unregisterPredictionUpdates(@NonNull AppPredictor.Callback callback) {
+        synchronized (mRegisteredCallbacks) {
+            unregisterPredictionUpdatesLocked(callback);
+        }
+    }
+
+    @GuardedBy("mRegisteredCallbacks")
+    private void unregisterPredictionUpdatesLocked(@NonNull AppPredictor.Callback callback) {
         if (mIsClosed.get()) {
             throw new IllegalStateException("This client has already been destroyed.");
         }
@@ -235,7 +254,7 @@
         }
 
         try {
-            mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice(targets),
+            mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice<>(targets),
                     new CallbackWrapper(callbackExecutor, callback));
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to sort targets", e);
@@ -251,19 +270,25 @@
         if (!mIsClosed.getAndSet(true)) {
             mCloseGuard.close();
 
-            // Do destroy;
-            try {
-                mPredictionManager.onDestroyPredictionSession(mSessionId);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to notify app target event", e);
-                e.rethrowAsRuntimeException();
+            synchronized (mRegisteredCallbacks) {
+                destroySessionLocked();
             }
-            mRegisteredCallbacks.clear();
         } else {
             throw new IllegalStateException("This client has already been destroyed.");
         }
     }
 
+    @GuardedBy("mRegisteredCallbacks")
+    private void destroySessionLocked() {
+        try {
+            mPredictionManager.onDestroyPredictionSession(mSessionId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to notify app target event", e);
+            e.rethrowAsRuntimeException();
+        }
+        mRegisteredCallbacks.clear();
+    }
+
     @Override
     protected void finalize() throws Throwable {
         try {
diff --git a/core/java/android/app/search/SearchSession.java b/core/java/android/app/search/SearchSession.java
index 2cd1d96..10db337 100644
--- a/core/java/android/app/search/SearchSession.java
+++ b/core/java/android/app/search/SearchSession.java
@@ -23,9 +23,11 @@
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.util.Log;
 
 import dalvik.system.CloseGuard;
@@ -70,7 +72,7 @@
  * @hide
  */
 @SystemApi
-public final class SearchSession implements AutoCloseable{
+public final class SearchSession implements AutoCloseable {
 
     private static final String TAG = SearchSession.class.getSimpleName();
     private static final boolean DEBUG = false;
@@ -229,7 +231,14 @@
                 if (DEBUG) {
                     Log.d(TAG, "CallbackWrapper.onResult result=" + result.getList());
                 }
-                mExecutor.execute(() -> mCallback.accept(result.getList()));
+                List<SearchTarget> list = result.getList();
+                if (list.size() > 0) {
+                    Bundle bundle = list.get(0).getExtras();
+                    if (bundle != null) {
+                        bundle.putLong("key_ipc_start", SystemClock.elapsedRealtime());
+                    }
+                }
+                mExecutor.execute(() -> mCallback.accept(list));
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 30a6c31..ee14708 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -176,7 +176,6 @@
     /** Write to Parcel. */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeStrongBinder(mClient.asBinder());
         final boolean writeActivityToken = mActivityToken != null;
         dest.writeBoolean(writeActivityToken);
         if (writeActivityToken) {
@@ -192,7 +191,6 @@
 
     /** Read from Parcel. */
     private ClientTransaction(Parcel in) {
-        mClient = (IApplicationThread) in.readStrongBinder();
         final boolean readActivityToken = in.readBoolean();
         if (readActivityToken) {
             mActivityToken = in.readStrongBinder();
diff --git a/core/java/android/app/time/DetectorStatusTypes.java b/core/java/android/app/time/DetectorStatusTypes.java
new file mode 100644
index 0000000..3643fc9
--- /dev/null
+++ b/core/java/android/app/time/DetectorStatusTypes.java
@@ -0,0 +1,227 @@
+/*
+ * 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.time;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A set of constants that can relate to time or time zone detector status.
+ *
+ * <ul>
+ *     <li>Detector status - the status of the overall detector.</li>
+ *     <li>Detection algorithm status - the status of an algorithm that a detector can use.
+ *     Each detector is expected to have one or more known algorithms to detect its chosen property,
+ *     e.g. for time zone devices can have a "location" detection algorithm, where the device's
+ *     location is used to detect the time zone.</li>
+ * </ul>
+ *
+ * @hide
+ */
+public final class DetectorStatusTypes {
+
+    /** A status code for a detector. */
+    @IntDef(prefix = "DETECTOR_STATUS_", value = {
+            DETECTOR_STATUS_UNKNOWN,
+            DETECTOR_STATUS_NOT_SUPPORTED,
+            DETECTOR_STATUS_NOT_RUNNING,
+            DETECTOR_STATUS_RUNNING,
+    })
+    @Target(ElementType.TYPE_USE)
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DetectorStatus {}
+
+    /**
+     * The detector status is unknown. Expected only for use as a placeholder before the actual
+     * status is known.
+     */
+    public static final @DetectorStatus int DETECTOR_STATUS_UNKNOWN = 0;
+
+    /** The detector is not supported on this device. */
+    public static final @DetectorStatus int DETECTOR_STATUS_NOT_SUPPORTED = 1;
+
+    /** The detector is supported but is not running. */
+    public static final @DetectorStatus int DETECTOR_STATUS_NOT_RUNNING = 2;
+
+    /** The detector is supported and is running. */
+    public static final @DetectorStatus int DETECTOR_STATUS_RUNNING = 3;
+
+    private DetectorStatusTypes() {}
+
+    /**
+     * A status code for a detection algorithm.
+     */
+    @IntDef(prefix = "DETECTION_ALGORITHM_STATUS_", value = {
+            DETECTION_ALGORITHM_STATUS_UNKNOWN,
+            DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED,
+            DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+            DETECTION_ALGORITHM_STATUS_RUNNING,
+    })
+    @Target(ElementType.TYPE_USE)
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DetectionAlgorithmStatus {}
+
+    /**
+     * The detection algorithm status is unknown. Expected only for use as a placeholder before the
+     * actual status is known.
+     */
+    public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_UNKNOWN = 0;
+
+    /** The detection algorithm is not supported on this device. */
+    public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED = 1;
+
+    /** The detection algorithm supported but is not running. */
+    public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_NOT_RUNNING = 2;
+
+    /** The detection algorithm supported and is running. */
+    public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_RUNNING = 3;
+
+    /**
+     * Validates the supplied value is one of the known {@code DETECTOR_STATUS_} constants and
+     * returns it if it is valid. {@link #DETECTOR_STATUS_UNKNOWN} is considered valid.
+     *
+     * @throws IllegalArgumentException if the value is not recognized
+     */
+    public static @DetectorStatus int requireValidDetectorStatus(
+            @DetectorStatus int detectorStatus) {
+        if (detectorStatus < DETECTOR_STATUS_UNKNOWN || detectorStatus > DETECTOR_STATUS_RUNNING) {
+            throw new IllegalArgumentException("Invalid detector status: " + detectorStatus);
+        }
+        return detectorStatus;
+    }
+
+    /**
+     * Returns a string for each {@code DETECTOR_STATUS_} constant. See also
+     * {@link #detectorStatusFromString(String)}.
+     *
+     * @throws IllegalArgumentException if the value is not recognized
+     */
+    @NonNull
+    public static String detectorStatusToString(@DetectorStatus int detectorStatus) {
+        switch (detectorStatus) {
+            case DETECTOR_STATUS_UNKNOWN:
+                return "UNKNOWN";
+            case DETECTOR_STATUS_NOT_SUPPORTED:
+                return "NOT_SUPPORTED";
+            case DETECTOR_STATUS_NOT_RUNNING:
+                return "NOT_RUNNING";
+            case DETECTOR_STATUS_RUNNING:
+                return "RUNNING";
+            default:
+                throw new IllegalArgumentException("Unknown status: " + detectorStatus);
+        }
+    }
+
+    /**
+     * Returns {@code DETECTOR_STATUS_} constant value from a string. See also
+     * {@link #detectorStatusToString(int)}.
+     *
+     * @throws IllegalArgumentException if the value is not recognized or is invalid
+     */
+    public static @DetectorStatus int detectorStatusFromString(
+            @Nullable String detectorStatusString) {
+        if (TextUtils.isEmpty(detectorStatusString)) {
+            throw new IllegalArgumentException("Empty status: " + detectorStatusString);
+        }
+
+        switch (detectorStatusString) {
+            case "UNKNOWN":
+                return DETECTOR_STATUS_UNKNOWN;
+            case "NOT_SUPPORTED":
+                return DETECTOR_STATUS_NOT_SUPPORTED;
+            case "NOT_RUNNING":
+                return DETECTOR_STATUS_NOT_RUNNING;
+            case "RUNNING":
+                return DETECTOR_STATUS_RUNNING;
+            default:
+                throw new IllegalArgumentException("Unknown status: " + detectorStatusString);
+        }
+    }
+
+    /**
+     * Validates the supplied value is one of the known {@code DETECTION_ALGORITHM_} constants and
+     * returns it if it is valid. {@link #DETECTION_ALGORITHM_STATUS_UNKNOWN} is considered valid.
+     *
+     * @throws IllegalArgumentException if the value is not recognized
+     */
+    public static @DetectionAlgorithmStatus int requireValidDetectionAlgorithmStatus(
+            @DetectionAlgorithmStatus int detectionAlgorithmStatus) {
+        if (detectionAlgorithmStatus < DETECTION_ALGORITHM_STATUS_UNKNOWN
+                || detectionAlgorithmStatus > DETECTION_ALGORITHM_STATUS_RUNNING) {
+            throw new IllegalArgumentException(
+                    "Invalid detection algorithm: " + detectionAlgorithmStatus);
+        }
+        return detectionAlgorithmStatus;
+    }
+
+    /**
+     * Returns a string for each {@code DETECTION_ALGORITHM_} constant. See also
+     * {@link #detectionAlgorithmStatusFromString(String)}
+     *
+     * @throws IllegalArgumentException if the value is not recognized
+     */
+    @NonNull
+    public static String detectionAlgorithmStatusToString(
+            @DetectionAlgorithmStatus int detectorAlgorithmStatus) {
+        switch (detectorAlgorithmStatus) {
+            case DETECTION_ALGORITHM_STATUS_UNKNOWN:
+                return "UNKNOWN";
+            case DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED:
+                return "NOT_SUPPORTED";
+            case DETECTION_ALGORITHM_STATUS_NOT_RUNNING:
+                return "NOT_RUNNING";
+            case DETECTION_ALGORITHM_STATUS_RUNNING:
+                return "RUNNING";
+            default:
+                throw new IllegalArgumentException("Unknown status: " + detectorAlgorithmStatus);
+        }
+    }
+
+    /**
+     * Returns {@code DETECTION_ALGORITHM_} constant value from a string. See also
+     * {@link #detectionAlgorithmStatusToString(int)} (String)}
+     *
+     * @throws IllegalArgumentException if the value is not recognized or is invalid
+     */
+    public static @DetectionAlgorithmStatus int detectionAlgorithmStatusFromString(
+            @Nullable String detectorAlgorithmStatusString) {
+
+        if (TextUtils.isEmpty(detectorAlgorithmStatusString)) {
+            throw new IllegalArgumentException("Empty status: " + detectorAlgorithmStatusString);
+        }
+
+        switch (detectorAlgorithmStatusString) {
+            case "UNKNOWN":
+                return DETECTION_ALGORITHM_STATUS_UNKNOWN;
+            case "NOT_SUPPORTED":
+                return DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+            case "NOT_RUNNING":
+                return DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+            case "RUNNING":
+                return DETECTION_ALGORITHM_STATUS_RUNNING;
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown status: " + detectorAlgorithmStatusString);
+        }
+    }
+}
diff --git a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl
new file mode 100644
index 0000000..7184b12
--- /dev/null
+++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl
@@ -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.
+ */
+
+package android.app.time;
+
+parcelable LocationTimeZoneAlgorithmStatus;
diff --git a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
new file mode 100644
index 0000000..6b5e667
--- /dev/null
+++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
@@ -0,0 +1,411 @@
+/*
+ * 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.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
+import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusFromString;
+import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusToString;
+import static android.app.time.DetectorStatusTypes.requireValidDetectionAlgorithmStatus;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.timezone.TimeZoneProviderStatus;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Information about the status of the location-based time zone detection algorithm.
+ *
+ * @hide
+ */
+public final class LocationTimeZoneAlgorithmStatus implements Parcelable {
+
+    /**
+     * An enum that describes a location time zone provider's status.
+     *
+     * @hide
+     */
+    @IntDef(prefix = "PROVIDER_STATUS_", value = {
+            PROVIDER_STATUS_NOT_PRESENT,
+            PROVIDER_STATUS_NOT_READY,
+            PROVIDER_STATUS_IS_CERTAIN,
+            PROVIDER_STATUS_IS_UNCERTAIN,
+    })
+    @Target(ElementType.TYPE_USE)
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ProviderStatus {}
+
+    /**
+     * Indicates a provider is not present because it has not been configured, the configuration
+     * is bad, or the provider has reported a permanent failure.
+     */
+    public static final @ProviderStatus int PROVIDER_STATUS_NOT_PRESENT = 1;
+
+    /**
+     * Indicates a provider has not reported it is certain or uncertain. This may be because it has
+     * just started running, or it has been stopped.
+     */
+    public static final @ProviderStatus int PROVIDER_STATUS_NOT_READY = 2;
+
+    /**
+     * Indicates a provider last reported it is certain.
+     */
+    public static final @ProviderStatus int PROVIDER_STATUS_IS_CERTAIN = 3;
+
+    /**
+     * Indicates a provider last reported it is uncertain.
+     */
+    public static final @ProviderStatus int PROVIDER_STATUS_IS_UNCERTAIN = 4;
+
+    /**
+     * An instance used when the location algorithm is not supported by the device.
+     */
+    public static final LocationTimeZoneAlgorithmStatus NOT_SUPPORTED =
+            new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED,
+                    PROVIDER_STATUS_NOT_PRESENT, null, PROVIDER_STATUS_NOT_PRESENT, null);
+
+    /**
+     * An instance used when the location algorithm is running, but has not reported an event.
+     */
+    public static final LocationTimeZoneAlgorithmStatus RUNNING_NOT_REPORTED =
+            new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+                    PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+
+    /**
+     * An instance used when the location algorithm is supported but not running.
+     */
+    public static final LocationTimeZoneAlgorithmStatus NOT_RUNNING =
+            new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+                    PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+
+    private final @DetectionAlgorithmStatus int mStatus;
+    private final @ProviderStatus int mPrimaryProviderStatus;
+    // May be populated when mPrimaryProviderReportedStatus == PROVIDER_STATUS_IS_CERTAIN
+    // or PROVIDER_STATUS_IS_UNCERTAIN
+    @Nullable private final TimeZoneProviderStatus mPrimaryProviderReportedStatus;
+
+    private final @ProviderStatus int mSecondaryProviderStatus;
+    // May be populated when mSecondaryProviderReportedStatus == PROVIDER_STATUS_IS_CERTAIN
+    // or PROVIDER_STATUS_IS_UNCERTAIN
+    @Nullable private final TimeZoneProviderStatus mSecondaryProviderReportedStatus;
+
+    public LocationTimeZoneAlgorithmStatus(
+            @DetectionAlgorithmStatus int status,
+            @ProviderStatus int primaryProviderStatus,
+            @Nullable TimeZoneProviderStatus primaryProviderReportedStatus,
+            @ProviderStatus int secondaryProviderStatus,
+            @Nullable TimeZoneProviderStatus secondaryProviderReportedStatus) {
+
+        mStatus = requireValidDetectionAlgorithmStatus(status);
+        mPrimaryProviderStatus = requireValidProviderStatus(primaryProviderStatus);
+        mPrimaryProviderReportedStatus = primaryProviderReportedStatus;
+        mSecondaryProviderStatus = requireValidProviderStatus(secondaryProviderStatus);
+        mSecondaryProviderReportedStatus = secondaryProviderReportedStatus;
+
+        boolean primaryProviderHasReported = hasProviderReported(primaryProviderStatus);
+        boolean primaryProviderReportedStatusPresent = primaryProviderReportedStatus != null;
+        if (!primaryProviderHasReported && primaryProviderReportedStatusPresent) {
+            throw new IllegalArgumentException(
+                    "primaryProviderReportedStatus=" + primaryProviderReportedStatus
+                            + ", primaryProviderStatus="
+                            + providerStatusToString(primaryProviderStatus));
+        }
+
+        boolean secondaryProviderHasReported = hasProviderReported(secondaryProviderStatus);
+        boolean secondaryProviderReportedStatusPresent = secondaryProviderReportedStatus != null;
+        if (!secondaryProviderHasReported && secondaryProviderReportedStatusPresent) {
+            throw new IllegalArgumentException(
+                    "secondaryProviderReportedStatus=" + secondaryProviderReportedStatus
+                            + ", secondaryProviderStatus="
+                            + providerStatusToString(secondaryProviderStatus));
+        }
+
+        // If the algorithm isn't running, providers can't report.
+        if (status != DETECTION_ALGORITHM_STATUS_RUNNING
+                && (primaryProviderHasReported || secondaryProviderHasReported)) {
+            throw new IllegalArgumentException(
+                    "algorithmStatus=" + detectionAlgorithmStatusToString(status)
+                            + ", primaryProviderReportedStatus=" + primaryProviderReportedStatus
+                            + ", secondaryProviderReportedStatus="
+                            + secondaryProviderReportedStatus);
+        }
+    }
+
+    /**
+     * Returns the status value of the detection algorithm.
+     */
+    public @DetectionAlgorithmStatus int getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * Returns the status of the primary location time zone provider as categorized by the detection
+     * algorithm.
+     */
+    public @ProviderStatus int getPrimaryProviderStatus() {
+        return mPrimaryProviderStatus;
+    }
+
+    /**
+     * Returns the status of the primary location time zone provider as reported by the provider
+     * itself. Can be {@code null} when the provider hasn't reported, or omitted when it has.
+     */
+    @Nullable
+    public TimeZoneProviderStatus getPrimaryProviderReportedStatus() {
+        return mPrimaryProviderReportedStatus;
+    }
+
+    /**
+     * Returns the status of the secondary location time zone provider as categorized by the
+     * detection algorithm.
+     */
+    public @ProviderStatus int getSecondaryProviderStatus() {
+        return mSecondaryProviderStatus;
+    }
+
+    /**
+     * Returns the status of the secondary location time zone provider as reported by the provider
+     * itself. Can be {@code null} when the provider hasn't reported, or omitted when it has.
+     */
+    @Nullable
+    public TimeZoneProviderStatus getSecondaryProviderReportedStatus() {
+        return mSecondaryProviderReportedStatus;
+    }
+
+    @Override
+    public String toString() {
+        return "LocationTimeZoneAlgorithmStatus{"
+                + "mAlgorithmStatus=" + detectionAlgorithmStatusToString(mStatus)
+                + ", mPrimaryProviderStatus=" + providerStatusToString(mPrimaryProviderStatus)
+                + ", mPrimaryProviderReportedStatus=" + mPrimaryProviderReportedStatus
+                + ", mSecondaryProviderStatus=" + providerStatusToString(mSecondaryProviderStatus)
+                + ", mSecondaryProviderReportedStatus=" + mSecondaryProviderReportedStatus
+                + '}';
+    }
+
+    /**
+     * Parses a {@link LocationTimeZoneAlgorithmStatus} from a toString() string for manual
+     * command-line testing.
+     */
+    @NonNull
+    public static LocationTimeZoneAlgorithmStatus parseCommandlineArg(@NonNull String arg) {
+        // Note: "}" has to be escaped on Android with "\\}" because the regexp library is not based
+        // on OpenJDK code.
+        Pattern pattern = Pattern.compile("LocationTimeZoneAlgorithmStatus\\{"
+                + "mAlgorithmStatus=(.+)"
+                + ", mPrimaryProviderStatus=([^,]+)"
+                + ", mPrimaryProviderReportedStatus=(null|TimeZoneProviderStatus\\{[^}]+\\})"
+                + ", mSecondaryProviderStatus=([^,]+)"
+                + ", mSecondaryProviderReportedStatus=(null|TimeZoneProviderStatus\\{[^}]+\\})"
+                + "\\}"
+        );
+        Matcher matcher = pattern.matcher(arg);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("Unable to parse algorithm status arg: " + arg);
+        }
+        @DetectionAlgorithmStatus int algorithmStatus =
+                detectionAlgorithmStatusFromString(matcher.group(1));
+        @ProviderStatus int primaryProviderStatus = providerStatusFromString(matcher.group(2));
+        TimeZoneProviderStatus primaryProviderReportedStatus =
+                parseTimeZoneProviderStatusOrNull(matcher.group(3));
+        @ProviderStatus int secondaryProviderStatus = providerStatusFromString(matcher.group(4));
+        TimeZoneProviderStatus secondaryProviderReportedStatus =
+                parseTimeZoneProviderStatusOrNull(matcher.group(5));
+        return new LocationTimeZoneAlgorithmStatus(
+                algorithmStatus, primaryProviderStatus, primaryProviderReportedStatus,
+                secondaryProviderStatus, secondaryProviderReportedStatus);
+    }
+
+    @Nullable
+    private static TimeZoneProviderStatus parseTimeZoneProviderStatusOrNull(
+            String providerReportedStatusString) {
+        TimeZoneProviderStatus providerReportedStatus;
+        if ("null".equals(providerReportedStatusString)) {
+            providerReportedStatus = null;
+        } else {
+            providerReportedStatus =
+                    TimeZoneProviderStatus.parseProviderStatus(providerReportedStatusString);
+        }
+        return providerReportedStatus;
+    }
+
+    @NonNull
+    public static final Creator<LocationTimeZoneAlgorithmStatus> CREATOR = new Creator<>() {
+        @Override
+        public LocationTimeZoneAlgorithmStatus createFromParcel(Parcel in) {
+            @DetectionAlgorithmStatus int algorithmStatus = in.readInt();
+            @ProviderStatus int primaryProviderStatus = in.readInt();
+            TimeZoneProviderStatus primaryProviderReportedStatus =
+                    in.readParcelable(getClass().getClassLoader(), TimeZoneProviderStatus.class);
+            @ProviderStatus int secondaryProviderStatus = in.readInt();
+            TimeZoneProviderStatus secondaryProviderReportedStatus =
+                    in.readParcelable(getClass().getClassLoader(), TimeZoneProviderStatus.class);
+            return new LocationTimeZoneAlgorithmStatus(
+                    algorithmStatus, primaryProviderStatus, primaryProviderReportedStatus,
+                    secondaryProviderStatus, secondaryProviderReportedStatus);
+        }
+
+        @Override
+        public LocationTimeZoneAlgorithmStatus[] newArray(int size) {
+            return new LocationTimeZoneAlgorithmStatus[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeInt(mStatus);
+        parcel.writeInt(mPrimaryProviderStatus);
+        parcel.writeParcelable(mPrimaryProviderReportedStatus, flags);
+        parcel.writeInt(mSecondaryProviderStatus);
+        parcel.writeParcelable(mSecondaryProviderReportedStatus, flags);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        LocationTimeZoneAlgorithmStatus that = (LocationTimeZoneAlgorithmStatus) o;
+        return mStatus == that.mStatus
+                && mPrimaryProviderStatus == that.mPrimaryProviderStatus
+                && Objects.equals(
+                        mPrimaryProviderReportedStatus, that.mPrimaryProviderReportedStatus)
+                && mSecondaryProviderStatus == that.mSecondaryProviderStatus
+                && Objects.equals(
+                        mSecondaryProviderReportedStatus, that.mSecondaryProviderReportedStatus);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mStatus,
+                mPrimaryProviderStatus, mPrimaryProviderReportedStatus,
+                mSecondaryProviderStatus, mSecondaryProviderReportedStatus);
+    }
+
+    /**
+     * Returns {@code true} if the algorithm status could allow the time zone detector to enter
+     * telephony fallback mode.
+     */
+    public boolean couldEnableTelephonyFallback() {
+        if (mStatus == DETECTION_ALGORITHM_STATUS_UNKNOWN
+                || mStatus == DETECTION_ALGORITHM_STATUS_NOT_RUNNING
+                || mStatus == DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED) {
+            // This method is not expected to be called on objects with these statuses. Fallback
+            // should not be enabled if it is.
+            return false;
+        }
+
+        // mStatus == DETECTOR_STATUS_RUNNING.
+
+        boolean primarySuggestsFallback = false;
+        if (mPrimaryProviderStatus == PROVIDER_STATUS_NOT_PRESENT) {
+            primarySuggestsFallback = true;
+        } else if (mPrimaryProviderStatus == PROVIDER_STATUS_IS_UNCERTAIN
+                && mPrimaryProviderReportedStatus != null) {
+            primarySuggestsFallback = mPrimaryProviderReportedStatus.couldEnableTelephonyFallback();
+        }
+
+        boolean secondarySuggestsFallback = false;
+        if (mSecondaryProviderStatus == PROVIDER_STATUS_NOT_PRESENT) {
+            secondarySuggestsFallback = true;
+        } else if (mSecondaryProviderStatus == PROVIDER_STATUS_IS_UNCERTAIN
+                && mSecondaryProviderReportedStatus != null) {
+            secondarySuggestsFallback =
+                    mSecondaryProviderReportedStatus.couldEnableTelephonyFallback();
+        }
+        return primarySuggestsFallback && secondarySuggestsFallback;
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    @NonNull
+    public static String providerStatusToString(@ProviderStatus int providerStatus) {
+        switch (providerStatus) {
+            case PROVIDER_STATUS_NOT_PRESENT:
+                return "NOT_PRESENT";
+            case PROVIDER_STATUS_NOT_READY:
+                return "NOT_READY";
+            case PROVIDER_STATUS_IS_CERTAIN:
+                return "IS_CERTAIN";
+            case PROVIDER_STATUS_IS_UNCERTAIN:
+                return "IS_UNCERTAIN";
+            default:
+                throw new IllegalArgumentException("Unknown status: " + providerStatus);
+        }
+    }
+
+    /** @hide */
+    @VisibleForTesting public static @ProviderStatus int providerStatusFromString(
+            @Nullable String providerStatusString) {
+        if (TextUtils.isEmpty(providerStatusString)) {
+            throw new IllegalArgumentException("Empty status: " + providerStatusString);
+        }
+
+        switch (providerStatusString) {
+            case "NOT_PRESENT":
+                return PROVIDER_STATUS_NOT_PRESENT;
+            case "NOT_READY":
+                return PROVIDER_STATUS_NOT_READY;
+            case "IS_CERTAIN":
+                return PROVIDER_STATUS_IS_CERTAIN;
+            case "IS_UNCERTAIN":
+                return PROVIDER_STATUS_IS_UNCERTAIN;
+            default:
+                throw new IllegalArgumentException("Unknown status: " + providerStatusString);
+        }
+    }
+
+    private static boolean hasProviderReported(@ProviderStatus int providerStatus) {
+        return providerStatus == PROVIDER_STATUS_IS_CERTAIN
+                || providerStatus == PROVIDER_STATUS_IS_UNCERTAIN;
+    }
+
+    /** @hide */
+    @VisibleForTesting public static @ProviderStatus int requireValidProviderStatus(
+            @ProviderStatus int providerStatus) {
+        if (providerStatus < PROVIDER_STATUS_NOT_PRESENT
+                || providerStatus > PROVIDER_STATUS_IS_UNCERTAIN) {
+            throw new IllegalArgumentException(
+                    "Invalid provider status: " + providerStatus);
+        }
+        return providerStatus;
+    }
+}
diff --git a/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl
new file mode 100644
index 0000000..0eb5b63
--- /dev/null
+++ b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl
@@ -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.
+ */
+
+package android.app.time;
+
+parcelable TelephonyTimeZoneAlgorithmStatus;
diff --git a/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java
new file mode 100644
index 0000000..95240c0
--- /dev/null
+++ b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java
@@ -0,0 +1,96 @@
+/*
+ * 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.time;
+
+import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusToString;
+import static android.app.time.DetectorStatusTypes.requireValidDetectionAlgorithmStatus;
+
+import android.annotation.NonNull;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Information about the status of the telephony-based time zone detection algorithm.
+ *
+ * @hide
+ */
+public final class TelephonyTimeZoneAlgorithmStatus implements Parcelable {
+
+    private final @DetectionAlgorithmStatus int mAlgorithmStatus;
+
+    public TelephonyTimeZoneAlgorithmStatus(@DetectionAlgorithmStatus int algorithmStatus) {
+        mAlgorithmStatus = requireValidDetectionAlgorithmStatus(algorithmStatus);
+    }
+
+    /**
+     * Returns the status of the detection algorithm.
+     */
+    public @DetectionAlgorithmStatus int getAlgorithmStatus() {
+        return mAlgorithmStatus;
+    }
+
+    @Override
+    public String toString() {
+        return "TelephonyTimeZoneAlgorithmStatus{"
+                + "mAlgorithmStatus=" + detectionAlgorithmStatusToString(mAlgorithmStatus)
+                + '}';
+    }
+
+    @NonNull
+    public static final Creator<TelephonyTimeZoneAlgorithmStatus> CREATOR = new Creator<>() {
+        @Override
+        public TelephonyTimeZoneAlgorithmStatus createFromParcel(Parcel in) {
+            @DetectionAlgorithmStatus int algorithmStatus = in.readInt();
+            return new TelephonyTimeZoneAlgorithmStatus(algorithmStatus);
+        }
+
+        @Override
+        public TelephonyTimeZoneAlgorithmStatus[] newArray(int size) {
+            return new TelephonyTimeZoneAlgorithmStatus[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeInt(mAlgorithmStatus);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TelephonyTimeZoneAlgorithmStatus that = (TelephonyTimeZoneAlgorithmStatus) o;
+        return mAlgorithmStatus == that.mAlgorithmStatus;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAlgorithmStatus);
+    }
+}
diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
index cd91b04..4684c6a 100644
--- a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
@@ -23,27 +23,40 @@
 import android.os.Parcelable;
 
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
- * A pair containing a user's {@link TimeZoneCapabilities} and {@link TimeZoneConfiguration}.
+ * An object containing a user's {@link TimeZoneCapabilities} and {@link TimeZoneConfiguration}.
  *
  * @hide
  */
 @SystemApi
 public final class TimeZoneCapabilitiesAndConfig implements Parcelable {
 
-    public static final @NonNull Creator<TimeZoneCapabilitiesAndConfig> CREATOR =
-            new Creator<TimeZoneCapabilitiesAndConfig>() {
-                public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
-                    return TimeZoneCapabilitiesAndConfig.createFromParcel(in);
-                }
+    public static final @NonNull Creator<TimeZoneCapabilitiesAndConfig> CREATOR = new Creator<>() {
+        public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
+            return TimeZoneCapabilitiesAndConfig.createFromParcel(in);
+        }
 
-                public TimeZoneCapabilitiesAndConfig[] newArray(int size) {
-                    return new TimeZoneCapabilitiesAndConfig[size];
-                }
-            };
+        public TimeZoneCapabilitiesAndConfig[] newArray(int size) {
+            return new TimeZoneCapabilitiesAndConfig[size];
+        }
+    };
 
-
+    /**
+     * The time zone detector status.
+     *
+     * Implementation note for future platform engineers: This field is only needed by SettingsUI
+     * initially and so it has not been added to the SDK API. {@link TimeZoneDetectorStatus}
+     * contains details about the internals of the time zone detector so thought should be given to
+     * abstraction / exposing a lightweight version if something unbundled needs access to detector
+     * details. Also, that could be good time to add separate APIs for bundled components, or add
+     * new APIs that return something more extensible and generic like a Bundle or a less
+     * constraining name. See also {@link
+     * TimeManager#addTimeZoneDetectorListener(Executor, TimeManager.TimeZoneDetectorListener)},
+     * which notified of changes to any fields in this class, including the detector status.
+     */
+    @NonNull private final TimeZoneDetectorStatus mDetectorStatus;
     @NonNull private final TimeZoneCapabilities mCapabilities;
     @NonNull private final TimeZoneConfiguration mConfiguration;
 
@@ -53,26 +66,41 @@
      * @hide
      */
     public TimeZoneCapabilitiesAndConfig(
+            @NonNull TimeZoneDetectorStatus detectorStatus,
             @NonNull TimeZoneCapabilities capabilities,
             @NonNull TimeZoneConfiguration configuration) {
-        this.mCapabilities = Objects.requireNonNull(capabilities);
-        this.mConfiguration = Objects.requireNonNull(configuration);
+        mDetectorStatus = Objects.requireNonNull(detectorStatus);
+        mCapabilities = Objects.requireNonNull(capabilities);
+        mConfiguration = Objects.requireNonNull(configuration);
     }
 
     @NonNull
     private static TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
-        TimeZoneCapabilities capabilities = in.readParcelable(null, android.app.time.TimeZoneCapabilities.class);
-        TimeZoneConfiguration configuration = in.readParcelable(null, android.app.time.TimeZoneConfiguration.class);
-        return new TimeZoneCapabilitiesAndConfig(capabilities, configuration);
+        TimeZoneDetectorStatus detectorStatus =
+                in.readParcelable(null, TimeZoneDetectorStatus.class);
+        TimeZoneCapabilities capabilities = in.readParcelable(null, TimeZoneCapabilities.class);
+        TimeZoneConfiguration configuration = in.readParcelable(null, TimeZoneConfiguration.class);
+        return new TimeZoneCapabilitiesAndConfig(detectorStatus, capabilities, configuration);
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mDetectorStatus, flags);
         dest.writeParcelable(mCapabilities, flags);
         dest.writeParcelable(mConfiguration, flags);
     }
 
     /**
+     * Returns the time zone detector's status.
+     *
+     * @hide
+     */
+    @NonNull
+    public TimeZoneDetectorStatus getDetectorStatus() {
+        return mDetectorStatus;
+    }
+
+    /**
      * Returns the user's time zone behavior capabilities.
      */
     @NonNull
@@ -102,7 +130,8 @@
             return false;
         }
         TimeZoneCapabilitiesAndConfig that = (TimeZoneCapabilitiesAndConfig) o;
-        return mCapabilities.equals(that.mCapabilities)
+        return mDetectorStatus.equals(that.mDetectorStatus)
+                && mCapabilities.equals(that.mCapabilities)
                 && mConfiguration.equals(that.mConfiguration);
     }
 
@@ -114,7 +143,8 @@
     @Override
     public String toString() {
         return "TimeZoneCapabilitiesAndConfig{"
-                + "mCapabilities=" + mCapabilities
+                + "mDetectorStatus=" + mDetectorStatus
+                + ", mCapabilities=" + mCapabilities
                 + ", mConfiguration=" + mConfiguration
                 + '}';
     }
diff --git a/core/java/android/app/time/TimeZoneDetectorStatus.aidl b/core/java/android/app/time/TimeZoneDetectorStatus.aidl
new file mode 100644
index 0000000..32204df
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneDetectorStatus.aidl
@@ -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.
+ */
+
+package android.app.time;
+
+parcelable TimeZoneDetectorStatus;
diff --git a/core/java/android/app/time/TimeZoneDetectorStatus.java b/core/java/android/app/time/TimeZoneDetectorStatus.java
new file mode 100644
index 0000000..1637463
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneDetectorStatus.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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DetectorStatus;
+import static android.app.time.DetectorStatusTypes.requireValidDetectorStatus;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Information about the status of the automatic time zone detector. Used by SettingsUI to display
+ * status information to the user.
+ *
+ * @hide
+ */
+public final class TimeZoneDetectorStatus implements Parcelable {
+
+    private final @DetectorStatus int mDetectorStatus;
+    @NonNull private final TelephonyTimeZoneAlgorithmStatus mTelephonyTimeZoneAlgorithmStatus;
+    @NonNull private final LocationTimeZoneAlgorithmStatus mLocationTimeZoneAlgorithmStatus;
+
+    public TimeZoneDetectorStatus(
+            @DetectorStatus int detectorStatus,
+            @NonNull TelephonyTimeZoneAlgorithmStatus telephonyTimeZoneAlgorithmStatus,
+            @NonNull LocationTimeZoneAlgorithmStatus locationTimeZoneAlgorithmStatus) {
+        mDetectorStatus = requireValidDetectorStatus(detectorStatus);
+        mTelephonyTimeZoneAlgorithmStatus =
+                Objects.requireNonNull(telephonyTimeZoneAlgorithmStatus);
+        mLocationTimeZoneAlgorithmStatus = Objects.requireNonNull(locationTimeZoneAlgorithmStatus);
+    }
+
+    public @DetectorStatus int getDetectorStatus() {
+        return mDetectorStatus;
+    }
+
+    @NonNull
+    public TelephonyTimeZoneAlgorithmStatus getTelephonyTimeZoneAlgorithmStatus() {
+        return mTelephonyTimeZoneAlgorithmStatus;
+    }
+
+    @NonNull
+    public LocationTimeZoneAlgorithmStatus getLocationTimeZoneAlgorithmStatus() {
+        return mLocationTimeZoneAlgorithmStatus;
+    }
+
+    @Override
+    public String toString() {
+        return "TimeZoneDetectorStatus{"
+                + "mDetectorStatus=" + DetectorStatusTypes.detectorStatusToString(mDetectorStatus)
+                + ", mTelephonyTimeZoneAlgorithmStatus=" + mTelephonyTimeZoneAlgorithmStatus
+                + ", mLocationTimeZoneAlgorithmStatus=" + mLocationTimeZoneAlgorithmStatus
+                + '}';
+    }
+
+    public static final @NonNull Creator<TimeZoneDetectorStatus> CREATOR = new Creator<>() {
+        @Override
+        public TimeZoneDetectorStatus createFromParcel(Parcel in) {
+            @DetectorStatus int detectorStatus = in.readInt();
+            TelephonyTimeZoneAlgorithmStatus telephonyTimeZoneAlgorithmStatus =
+                    in.readParcelable(getClass().getClassLoader(),
+                            TelephonyTimeZoneAlgorithmStatus.class);
+            LocationTimeZoneAlgorithmStatus locationTimeZoneAlgorithmStatus =
+                    in.readParcelable(getClass().getClassLoader(),
+                            LocationTimeZoneAlgorithmStatus.class);
+            return new TimeZoneDetectorStatus(detectorStatus,
+                    telephonyTimeZoneAlgorithmStatus, locationTimeZoneAlgorithmStatus);
+        }
+
+        @Override
+        public TimeZoneDetectorStatus[] newArray(int size) {
+            return new TimeZoneDetectorStatus[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeInt(mDetectorStatus);
+        parcel.writeParcelable(mTelephonyTimeZoneAlgorithmStatus, flags);
+        parcel.writeParcelable(mLocationTimeZoneAlgorithmStatus, flags);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TimeZoneDetectorStatus that = (TimeZoneDetectorStatus) o;
+        return mDetectorStatus == that.mDetectorStatus
+                && mTelephonyTimeZoneAlgorithmStatus.equals(that.mTelephonyTimeZoneAlgorithmStatus)
+                && mLocationTimeZoneAlgorithmStatus.equals(that.mLocationTimeZoneAlgorithmStatus);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDetectorStatus, mTelephonyTimeZoneAlgorithmStatus,
+                mLocationTimeZoneAlgorithmStatus);
+    }
+}
diff --git a/core/java/android/app/timezone/Callback.java b/core/java/android/app/timezone/Callback.java
deleted file mode 100644
index e3840be..0000000
--- a/core/java/android/app/timezone/Callback.java
+++ /dev/null
@@ -1,80 +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 android.app.timezone;
-
-import android.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Callback interface for receiving information about an async time zone operation.
- * The methods will be called on your application's main thread.
- *
- * @hide
- */
-public abstract class Callback {
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "SUCCESS", "ERROR_" }, value = {
-            SUCCESS,
-            ERROR_UNKNOWN_FAILURE,
-            ERROR_INSTALL_BAD_DISTRO_STRUCTURE,
-            ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION,
-            ERROR_INSTALL_RULES_TOO_OLD,
-            ERROR_INSTALL_VALIDATION_ERROR
-    })
-    public @interface AsyncResultCode {}
-
-    /**
-     * Indicates that an operation succeeded.
-     */
-    public static final int SUCCESS = 0;
-
-    /**
-     * Indicates an install / uninstall did not fully succeed for an unknown reason.
-     */
-    public static final int ERROR_UNKNOWN_FAILURE = 1;
-
-    /**
-     * Indicates an install failed because of a structural issue with the provided distro,
-     * e.g. it wasn't in the right format or the contents were structured incorrectly.
-     */
-    public static final int ERROR_INSTALL_BAD_DISTRO_STRUCTURE = 2;
-
-    /**
-     * Indicates an install failed because of a versioning issue with the provided distro,
-     * e.g. it was created for a different version of Android.
-     */
-    public static final int ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION = 3;
-
-    /**
-     * Indicates an install failed because the rules provided are too old for the device,
-     * e.g. the Android device shipped with a newer rules version.
-     */
-    public static final int ERROR_INSTALL_RULES_TOO_OLD = 4;
-
-    /**
-     * Indicates an install failed because the distro contents failed validation.
-     */
-    public static final int ERROR_INSTALL_VALIDATION_ERROR = 5;
-
-    /**
-     * Reports the result of an async time zone operation.
-     */
-    public abstract void onFinished(@AsyncResultCode int status);
-}
diff --git a/core/java/android/app/timezone/DistroFormatVersion.java b/core/java/android/app/timezone/DistroFormatVersion.java
deleted file mode 100644
index 13ecaf5..0000000
--- a/core/java/android/app/timezone/DistroFormatVersion.java
+++ /dev/null
@@ -1,120 +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 android.app.timezone;
-
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Versioning information about a distro's format or a device's supported format.
- *
- * <p>The following properties are included:
- * <dl>
- *     <dt>majorVersion</dt>
- *     <dd>the major distro format version. Major versions differences are not compatible - e.g.
- *     2 is not compatible with 1 or 3.</dd>
- *     <dt>minorVersion</dt>
- *     <dd>the minor distro format version. Minor versions should be backwards compatible iff the
- *     major versions match exactly, i.e. version 2.2 will be compatible with 2.1 devices but not
- *     2.3 devices.</dd>
- * </dl>
- *
- * @hide
- */
-public final class DistroFormatVersion implements Parcelable {
-
-    private final int mMajorVersion;
-    private final int mMinorVersion;
-
-    public DistroFormatVersion(int majorVersion, int minorVersion) {
-        mMajorVersion = Utils.validateVersion("major", majorVersion);
-        mMinorVersion = Utils.validateVersion("minor", minorVersion);
-    }
-
-    public static final @android.annotation.NonNull Creator<DistroFormatVersion> CREATOR = new Creator<DistroFormatVersion>() {
-        public DistroFormatVersion createFromParcel(Parcel in) {
-            int majorVersion = in.readInt();
-            int minorVersion = in.readInt();
-            return new DistroFormatVersion(majorVersion, minorVersion);
-        }
-
-        public DistroFormatVersion[] newArray(int size) {
-            return new DistroFormatVersion[size];
-        }
-    };
-
-    public int getMajorVersion() {
-        return mMajorVersion;
-    }
-
-    public int getMinorVersion() {
-        return mMinorVersion;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(mMajorVersion);
-        out.writeInt(mMinorVersion);
-    }
-
-    /**
-     * If this object describes a device's supported version and the parameter describes a distro's
-     * version, this method returns whether the device would accept the distro.
-     */
-    public boolean supports(DistroFormatVersion distroFormatVersion) {
-        return mMajorVersion == distroFormatVersion.mMajorVersion
-                && mMinorVersion <= distroFormatVersion.mMinorVersion;
-    }
-
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        DistroFormatVersion that = (DistroFormatVersion) o;
-
-        if (mMajorVersion != that.mMajorVersion) {
-            return false;
-        }
-        return mMinorVersion == that.mMinorVersion;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mMajorVersion;
-        result = 31 * result + mMinorVersion;
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "DistroFormatVersion{"
-                + "mMajorVersion=" + mMajorVersion
-                + ", mMinorVersion=" + mMinorVersion
-                + '}';
-    }
-}
diff --git a/core/java/android/app/timezone/DistroRulesVersion.java b/core/java/android/app/timezone/DistroRulesVersion.java
deleted file mode 100644
index 54937b8..0000000
--- a/core/java/android/app/timezone/DistroRulesVersion.java
+++ /dev/null
@@ -1,132 +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 android.app.timezone;
-
-import static android.app.timezone.Utils.validateRulesVersion;
-import static android.app.timezone.Utils.validateVersion;
-
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Versioning information about a set of time zone rules.
- *
- * <p>The following properties are included:
- * <dl>
- *     <dt>rulesVersion</dt>
- *     <dd>the IANA rules version. e.g. "2017a"</dd>
- *     <dt>revision</dt>
- *     <dd>the revision for the rules. Allows there to be several revisions for a given IANA rules
- *     release. Numerically higher is newer.</dd>
- * </dl>
- *
- * @hide
- */
-public final class DistroRulesVersion implements Parcelable {
-
-    private final String mRulesVersion;
-    private final int mRevision;
-
-    public DistroRulesVersion(String rulesVersion, int revision) {
-        mRulesVersion = validateRulesVersion("rulesVersion", rulesVersion);
-        mRevision = validateVersion("revision", revision);
-    }
-
-    public static final @android.annotation.NonNull Creator<DistroRulesVersion> CREATOR = new Creator<DistroRulesVersion>() {
-        public DistroRulesVersion createFromParcel(Parcel in) {
-            String rulesVersion = in.readString();
-            int revision = in.readInt();
-            return new DistroRulesVersion(rulesVersion, revision);
-        }
-
-        public DistroRulesVersion[] newArray(int size) {
-            return new DistroRulesVersion[size];
-        }
-    };
-
-    public String getRulesVersion() {
-        return mRulesVersion;
-    }
-
-    public int getRevision() {
-        return mRevision;
-    }
-
-    /**
-     * Returns true if this DistroRulesVersion is older than the one supplied. It returns false if
-     * it is the same or newer. This method compares the {@code rulesVersion} and the
-     * {@code revision}.
-     */
-    public boolean isOlderThan(DistroRulesVersion distroRulesVersion) {
-        int rulesComparison = mRulesVersion.compareTo(distroRulesVersion.mRulesVersion);
-        if (rulesComparison < 0) {
-            return true;
-        }
-        if (rulesComparison > 0) {
-            return false;
-        }
-        return mRevision < distroRulesVersion.mRevision;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeString(mRulesVersion);
-        out.writeInt(mRevision);
-    }
-
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        DistroRulesVersion that = (DistroRulesVersion) o;
-
-        if (mRevision != that.mRevision) {
-            return false;
-        }
-        return mRulesVersion.equals(that.mRulesVersion);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mRulesVersion.hashCode();
-        result = 31 * result + mRevision;
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "DistroRulesVersion{"
-                + "mRulesVersion='" + mRulesVersion + '\''
-                + ", mRevision='" + mRevision + '\''
-                + '}';
-    }
-
-    public String toDumpString() {
-        return mRulesVersion + "," + mRevision;
-    }
-}
diff --git a/core/java/android/app/timezone/ICallback.aidl b/core/java/android/app/timezone/ICallback.aidl
deleted file mode 100644
index 519ef1a..0000000
--- a/core/java/android/app/timezone/ICallback.aidl
+++ /dev/null
@@ -1,27 +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 android.app.timezone;
-
-/**
- * Callback interface for a timezone updater to receive information about the success or failure of
- * an installation/uninstallation attempt.
- *
- * {@hide}
- */
-oneway interface ICallback {
-    void onFinished(int error);
-}
\ No newline at end of file
diff --git a/core/java/android/app/timezone/IRulesManager.aidl b/core/java/android/app/timezone/IRulesManager.aidl
deleted file mode 100644
index 40f3fd2..0000000
--- a/core/java/android/app/timezone/IRulesManager.aidl
+++ /dev/null
@@ -1,84 +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 android.app.timezone;
-
-import android.app.timezone.ICallback;
-import android.app.timezone.RulesState;
-import android.os.ParcelFileDescriptor;
-
- /**
-  * Interface to the TimeZone Rules Manager Service.
-  *
-  * <p>This interface is only intended for system apps to call. They should use the
-  * {@link android.app.timezone.RulesManager} class rather than going through this
-  * Binder interface directly. See {@link android.app.timezone.RulesManager} for more complete
-  * documentation.
-  *
-  * {@hide}
-  */
-interface IRulesManager {
-
-    /**
-     * Returns information about the current time zone rules state such as the IANA version of
-     * the system and any currently installed distro. This method is intended to allow clients to
-     * determine if the current state can be improved; for example by passing the information to a
-     * server that may provide a new distro for download.
-     */
-    RulesState getRulesState();
-
-    /**
-     * Requests installation of the supplied distro. The distro must have been checked for integrity
-     * by the caller or have been received via a trusted mechanism.
-     *
-     * @param distroFileDescriptor the file descriptor for the distro
-     * @param checkToken an optional token provided if the install was triggered in response to a
-     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
-     * @param callback the {@link ICallback} to receive callbacks related to the
-     *     installation
-     * @return zero if the installation will be attempted; nonzero on error
-     */
-    int requestInstall(in ParcelFileDescriptor distroFileDescriptor, in byte[] checkToken,
-            ICallback callback);
-
-    /**
-     * Requests uninstallation of the currently installed distro (leaving the device with no
-     * distro installed).
-     *
-     * @param checkToken an optional token provided if the uninstall was triggered in response to a
-     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
-     * @param callback the {@link ICallback} to receive callbacks related to the
-     *     uninstall
-     * @return zero if the uninstallation will be attempted; nonzero on error
-     */
-    int requestUninstall(in byte[] checkToken, ICallback callback);
-
-    /**
-     * Requests the system does not modify the currently installed time zone distro, if any. This
-     * method records the fact that a time zone check operation triggered by the system is now
-     * complete and there was nothing to do. The token passed should be the one presented when the
-     * check was triggered.
-     *
-     * <p>Note: Passing {@code success == false} may result in more checks being triggered. Clients
-     * should be careful not to pass false if the failure is unlikely to resolve by itself.
-     *
-     * @param checkToken an optional token provided if the install was triggered in response to a
-     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
-     * @param success true if the check was successful, false if it was not successful but may
-     *     succeed if it is retried
-     */
-    void requestNothing(in byte[] token, boolean success);
-}
diff --git a/core/java/android/app/timezone/OWNERS b/core/java/android/app/timezone/OWNERS
deleted file mode 100644
index 04d78f2..0000000
--- a/core/java/android/app/timezone/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# Bug component: 24949
-# Internal APIs related to APK-based time zone rule updates.
-# Deprecated, deletion tracked by b/148144561
-include /services/core/java/com/android/server/timezone/OWNERS
diff --git a/core/java/android/app/timezone/RulesManager.java b/core/java/android/app/timezone/RulesManager.java
deleted file mode 100644
index fe83113..0000000
--- a/core/java/android/app/timezone/RulesManager.java
+++ /dev/null
@@ -1,235 +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 android.app.timezone;
-
-import android.annotation.IntDef;
-import android.content.Context;
-import android.os.Handler;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-
-/**
- * The interface through which a time zone update application interacts with the Android system
- * to handle time zone rule updates.
- *
- * <p>This interface is intended for use with the default APK-based time zone rules update
- * application but it can also be used by OEMs if that mechanism is turned off using configuration.
- * All callers must possess the {@link android.Manifest.permission#UPDATE_TIME_ZONE_RULES} system
- * permission unless otherwise stated.
- *
- * <p>When using the default mechanism, when properly configured the Android system will send a
- * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with a
- * {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} extra to the time zone rules updater application
- * when it detects that it or the OEM's APK containing time zone rules data has been modified. The
- * updater application is then responsible for calling one of
- * {@link #requestInstall(ParcelFileDescriptor, byte[], Callback)},
- * {@link #requestUninstall(byte[], Callback)} or
- * {@link #requestNothing(byte[], boolean)}, indicating, respectively, whether a new time zone rules
- * distro should be installed, the current distro should be uninstalled, or there is nothing to do
- * (or that the correct operation could not be determined due to an error). In each case the updater
- * must pass the {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} value it received from the intent
- * back so the system in the {@code checkToken} parameter.
- *
- * <p>If OEMs want to handle their own time zone rules updates, perhaps via a server-side component
- * rather than an APK, then they should disable the default triggering mechanism in config and are
- * responsible for triggering their own update checks / installs / uninstalls. In this case the
- * "check token" parameter can be left null and there is never any need to call
- * {@link #requestNothing(byte[], boolean)}.
- *
- * <p>OEMs should not mix the default mechanism and their own as this could lead to conflicts and
- * unnecessary checks being triggered.
- *
- * <p>Applications obtain this using {@link android.app.Activity#getSystemService(String)} with
- * {@link Context#TIME_ZONE_RULES_MANAGER_SERVICE}.
- * @hide
- */
-public final class RulesManager {
-    private static final String TAG = "timezone.RulesManager";
-    private static final boolean DEBUG = false;
-
-    /**
-     * The action of the intent that the Android system will broadcast when a time zone rules update
-     * operation has been successfully staged  (i.e. to be applied next reboot) or unstaged.
-     *
-     * <p>See {@link #EXTRA_OPERATION_STAGED}
-     *
-     * <p>This is a protected intent that can only be sent by the system.
-     */
-    public static final String ACTION_RULES_UPDATE_OPERATION =
-            "com.android.intent.action.timezone.RULES_UPDATE_OPERATION";
-
-    /**
-     * The key for a boolean extra for the {@link #ACTION_RULES_UPDATE_OPERATION} intent used to
-     * indicate whether the operation was a "stage" or an "unstage".
-     */
-    public static final String EXTRA_OPERATION_STAGED = "staged";
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "SUCCESS", "ERROR_" }, value = {
-            SUCCESS,
-            ERROR_UNKNOWN_FAILURE,
-            ERROR_OPERATION_IN_PROGRESS
-    })
-    public @interface ResultCode {}
-
-    /**
-     * Indicates that an operation succeeded.
-     */
-    public static final int SUCCESS = 0;
-
-    /**
-     * Indicates that an install/uninstall cannot be initiated because there is one already in
-     * progress.
-     */
-    public static final int ERROR_OPERATION_IN_PROGRESS = 1;
-
-    /**
-     * Indicates an install / uninstall did not fully succeed for an unknown reason.
-     */
-    public static final int ERROR_UNKNOWN_FAILURE = 2;
-
-    private final Context mContext;
-    private final IRulesManager mIRulesManager;
-
-    public RulesManager(Context context) {
-        mContext = context;
-        mIRulesManager = IRulesManager.Stub.asInterface(
-                ServiceManager.getService(Context.TIME_ZONE_RULES_MANAGER_SERVICE));
-    }
-
-    /**
-     * Returns information about the current time zone rules state such as the IANA version of
-     * the system and any currently installed distro. This method allows clients to determine the
-     * current device state, perhaps to see if it can be improved; for example by passing the
-     * information to a server that may provide a new distro for download.
-     *
-     * <p>Callers must possess the {@link android.Manifest.permission#QUERY_TIME_ZONE_RULES} system
-     * permission.
-     */
-    public RulesState getRulesState() {
-        try {
-            logDebug("mIRulesManager.getRulesState()");
-            RulesState rulesState = mIRulesManager.getRulesState();
-            logDebug("mIRulesManager.getRulesState() returned " + rulesState);
-            return rulesState;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Requests installation of the supplied distro. The distro must have been checked for integrity
-     * by the caller or have been received via a trusted mechanism.
-     *
-     * @param distroFileDescriptor the file descriptor for the distro
-     * @param checkToken an optional token provided if the install was triggered in response to a
-     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
-     * @param callback the {@link Callback} to receive callbacks related to the installation
-     * @return {@link #SUCCESS} if the installation will be attempted
-     */
-    @ResultCode
-    public int requestInstall(
-            ParcelFileDescriptor distroFileDescriptor, byte[] checkToken, Callback callback)
-            throws IOException {
-
-        ICallback iCallback = new CallbackWrapper(mContext, callback);
-        try {
-            logDebug("mIRulesManager.requestInstall()");
-            return mIRulesManager.requestInstall(distroFileDescriptor, checkToken, iCallback);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Requests uninstallation of the currently installed distro (leaving the device with no
-     * distro installed).
-     *
-     * @param checkToken an optional token provided if the uninstall was triggered in response to a
-     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
-     * @param callback the {@link Callback} to receive callbacks related to the uninstall
-     * @return {@link #SUCCESS} if the uninstallation will be attempted
-     */
-    @ResultCode
-    public int requestUninstall(byte[] checkToken, Callback callback) {
-        ICallback iCallback = new CallbackWrapper(mContext, callback);
-        try {
-            logDebug("mIRulesManager.requestUninstall()");
-            return mIRulesManager.requestUninstall(checkToken, iCallback);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /*
-     * We wrap incoming binder calls with a private class implementation that
-     * redirects them into main-thread actions.  This serializes the backup
-     * progress callbacks nicely within the usual main-thread lifecycle pattern.
-     */
-    private class CallbackWrapper extends ICallback.Stub {
-        final Handler mHandler;
-        final Callback mCallback;
-
-        CallbackWrapper(Context context, Callback callback) {
-            mCallback = callback;
-            mHandler = new Handler(context.getMainLooper());
-        }
-
-        // Binder calls into this object just enqueue on the main-thread handler
-        @Override
-        public void onFinished(int status) {
-            logDebug("mCallback.onFinished(status), status=" + status);
-            mHandler.post(() -> mCallback.onFinished(status));
-        }
-    }
-
-    /**
-     * Requests the system does not modify the currently installed time zone distro, if any. This
-     * method records the fact that a time zone check operation triggered by the system is now
-     * complete and there was nothing to do. The token passed should be the one presented when the
-     * check was triggered.
-     *
-     * <p>Note: Passing {@code success == false} may result in more checks being triggered. Clients
-     * should be careful not to pass false if the failure is unlikely to resolve by itself.
-     *
-     * @param checkToken an optional token provided if the install was triggered in response to a
-     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
-     * @param succeeded true if the check was successful, false if it was not successful but may
-     *     succeed if it is retried
-     */
-    public void requestNothing(byte[] checkToken, boolean succeeded) {
-        try {
-            logDebug("mIRulesManager.requestNothing() with token=" + Arrays.toString(checkToken));
-            mIRulesManager.requestNothing(checkToken, succeeded);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    static void logDebug(String msg) {
-        if (DEBUG) {
-            Log.v(TAG, msg);
-        }
-    }
-}
diff --git a/core/java/android/app/timezone/RulesState.aidl b/core/java/android/app/timezone/RulesState.aidl
deleted file mode 100644
index 665220d..0000000
--- a/core/java/android/app/timezone/RulesState.aidl
+++ /dev/null
@@ -1,19 +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 android.app.timezone;
-
-parcelable RulesState;
\ No newline at end of file
diff --git a/core/java/android/app/timezone/RulesState.java b/core/java/android/app/timezone/RulesState.java
deleted file mode 100644
index 516ad03..0000000
--- a/core/java/android/app/timezone/RulesState.java
+++ /dev/null
@@ -1,302 +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 android.app.timezone;
-
-import static android.app.timezone.Utils.validateConditionalNull;
-import static android.app.timezone.Utils.validateNotNull;
-import static android.app.timezone.Utils.validateRulesVersion;
-
-import android.annotation.IntDef;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Description of the state of time zone rules on a device.
- *
- * <p>The following properties are included:
- * <dl>
- *     <dt>baseRulesVersion</dt>
- *     <dd>the IANA rules version that shipped with the OS. Always present. e.g. "2017a".</dd>
- *     <dt>distroFormatVersionSupported</dt>
- *     <dd>the distro format version supported by this device. Always present.</dd>
- *     <dt>operationInProgress</dt>
- *     <dd>{@code true} if there is an install / uninstall operation currently happening.</dd>
- *     <dt>stagedOperationType</dt>
- *     <dd>one of {@link #STAGED_OPERATION_UNKNOWN}, {@link #STAGED_OPERATION_NONE},
- *     {@link #STAGED_OPERATION_UNINSTALL} and {@link #STAGED_OPERATION_INSTALL} indicating whether
- *     there is a currently staged time zone distro operation. {@link #STAGED_OPERATION_UNKNOWN} is
- *     used when {@link #isOperationInProgress()} is {@code true}. Staged operations currently
- *     require a reboot to become active.</dd>
- *     <dt>stagedDistroRulesVersion</dt>
- *     <dd>[present if distroStagedState == STAGED_STATE_INSTALL], the rules version of the distro
- *     currently staged for installation.</dd>
- *     <dt>distroStatus</dt>
- *     <dd>{@link #DISTRO_STATUS_INSTALLED} if there is a time zone distro installed and active,
- *     {@link #DISTRO_STATUS_NONE} if there is no active installed distro.
- *     {@link #DISTRO_STATUS_UNKNOWN} is used when {@link #isOperationInProgress()} is {@code true}.
- *     </dd>
- *     <dt>installedDistroRulesVersion</dt>
- *     <dd>[present if distroStatus == {@link #DISTRO_STATUS_INSTALLED}], the rules version of the
- *     installed and active distro.</dd>
- * </dl>
- *
- * @hide
- */
-public final class RulesState implements Parcelable {
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "STAGED_OPERATION_" }, value = {
-            STAGED_OPERATION_UNKNOWN,
-            STAGED_OPERATION_NONE,
-            STAGED_OPERATION_UNINSTALL,
-            STAGED_OPERATION_INSTALL
-    })
-    private @interface StagedOperationType {}
-
-    /** Staged state could not be determined. */
-    public static final int STAGED_OPERATION_UNKNOWN = 0;
-    /** Nothing is staged. */
-    public static final int STAGED_OPERATION_NONE = 1;
-    /** An uninstall is staged. */
-    public static final int STAGED_OPERATION_UNINSTALL = 2;
-    /** An install is staged. */
-    public static final int STAGED_OPERATION_INSTALL = 3;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "DISTRO_STATUS_" }, value = {
-            DISTRO_STATUS_UNKNOWN,
-            DISTRO_STATUS_NONE,
-            DISTRO_STATUS_INSTALLED
-    })
-    private @interface DistroStatus {}
-
-    /** The current distro status could not be determined. */
-    public static final int DISTRO_STATUS_UNKNOWN = 0;
-    /** There is no active installed time zone distro. */
-    public static final int DISTRO_STATUS_NONE = 1;
-    /** The is an active, installed time zone distro. */
-    public static final int DISTRO_STATUS_INSTALLED = 2;
-
-    private static final byte BYTE_FALSE = 0;
-    private static final byte BYTE_TRUE = 1;
-
-    private final String mBaseRulesVersion;
-    private final DistroFormatVersion mDistroFormatVersionSupported;
-    private final boolean mOperationInProgress;
-    @StagedOperationType private final int mStagedOperationType;
-    @Nullable private final DistroRulesVersion mStagedDistroRulesVersion;
-    @DistroStatus private final int mDistroStatus;
-    @Nullable private final DistroRulesVersion mInstalledDistroRulesVersion;
-
-    public RulesState(String baseRulesVersion, DistroFormatVersion distroFormatVersionSupported,
-            boolean operationInProgress,
-            @StagedOperationType int stagedOperationType,
-            @Nullable DistroRulesVersion stagedDistroRulesVersion,
-            @DistroStatus int distroStatus,
-            @Nullable DistroRulesVersion installedDistroRulesVersion) {
-        this.mBaseRulesVersion = validateRulesVersion("baseRulesVersion", baseRulesVersion);
-        this.mDistroFormatVersionSupported =
-                validateNotNull("distroFormatVersionSupported", distroFormatVersionSupported);
-        this.mOperationInProgress = operationInProgress;
-
-        if (operationInProgress && stagedOperationType != STAGED_OPERATION_UNKNOWN) {
-            throw new IllegalArgumentException(
-                    "stagedOperationType != STAGED_OPERATION_UNKNOWN");
-        }
-        this.mStagedOperationType = validateStagedOperation(stagedOperationType);
-        this.mStagedDistroRulesVersion = validateConditionalNull(
-                mStagedOperationType == STAGED_OPERATION_INSTALL /* requireNotNull */,
-                "stagedDistroRulesVersion", stagedDistroRulesVersion);
-
-        this.mDistroStatus = validateDistroStatus(distroStatus);
-        this.mInstalledDistroRulesVersion = validateConditionalNull(
-                mDistroStatus == DISTRO_STATUS_INSTALLED/* requireNotNull */,
-                "installedDistroRulesVersion", installedDistroRulesVersion);
-    }
-
-    public String getBaseRulesVersion() {
-        return mBaseRulesVersion;
-    }
-
-    public boolean isOperationInProgress() {
-        return mOperationInProgress;
-    }
-
-    public @StagedOperationType int getStagedOperationType() {
-        return mStagedOperationType;
-    }
-
-    /**
-     * Returns the staged rules version when {@link #getStagedOperationType()} is
-     * {@link #STAGED_OPERATION_INSTALL}.
-     */
-    public @Nullable DistroRulesVersion getStagedDistroRulesVersion() {
-        return mStagedDistroRulesVersion;
-    }
-
-    public @DistroStatus int getDistroStatus() {
-        return mDistroStatus;
-    }
-
-    /**
-     * Returns the installed rules version when {@link #getDistroStatus()} is
-     * {@link #DISTRO_STATUS_INSTALLED}.
-     */
-    public @Nullable DistroRulesVersion getInstalledDistroRulesVersion() {
-        return mInstalledDistroRulesVersion;
-    }
-
-    /**
-     * Returns true if a distro in the specified format is supported on this device.
-     */
-    public boolean isDistroFormatVersionSupported(DistroFormatVersion distroFormatVersion) {
-        return mDistroFormatVersionSupported.supports(distroFormatVersion);
-    }
-
-    /**
-     * Returns true if the base data files contain IANA rules data that are newer than the
-     * distro IANA rules version supplied, i.e. true when the version specified would be "worse"
-     * than the one that is in the base data. Returns false if the base version is the
-     * same or older, i.e. false when the version specified would be "better" than the one that is
-     * in the base set.
-     */
-    public boolean isBaseVersionNewerThan(DistroRulesVersion distroRulesVersion) {
-        return mBaseRulesVersion.compareTo(distroRulesVersion.getRulesVersion()) > 0;
-    }
-
-    public static final @android.annotation.NonNull Parcelable.Creator<RulesState> CREATOR =
-            new Parcelable.Creator<RulesState>() {
-        public RulesState createFromParcel(Parcel in) {
-            return RulesState.createFromParcel(in);
-        }
-
-        public RulesState[] newArray(int size) {
-            return new RulesState[size];
-        }
-    };
-
-    private static RulesState createFromParcel(Parcel in) {
-        String baseRulesVersion = in.readString();
-        DistroFormatVersion distroFormatVersionSupported = in.readParcelable(null, android.app.timezone.DistroFormatVersion.class);
-        boolean operationInProgress = in.readByte() == BYTE_TRUE;
-        int distroStagedState = in.readByte();
-        DistroRulesVersion stagedDistroRulesVersion = in.readParcelable(null, android.app.timezone.DistroRulesVersion.class);
-        int installedDistroStatus = in.readByte();
-        DistroRulesVersion installedDistroRulesVersion = in.readParcelable(null, android.app.timezone.DistroRulesVersion.class);
-        return new RulesState(baseRulesVersion, distroFormatVersionSupported, operationInProgress,
-                distroStagedState, stagedDistroRulesVersion,
-                installedDistroStatus, installedDistroRulesVersion);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeString(mBaseRulesVersion);
-        out.writeParcelable(mDistroFormatVersionSupported, 0);
-        out.writeByte(mOperationInProgress ? BYTE_TRUE : BYTE_FALSE);
-        out.writeByte((byte) mStagedOperationType);
-        out.writeParcelable(mStagedDistroRulesVersion, 0);
-        out.writeByte((byte) mDistroStatus);
-        out.writeParcelable(mInstalledDistroRulesVersion, 0);
-    }
-
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        RulesState that = (RulesState) o;
-
-        if (mOperationInProgress != that.mOperationInProgress) {
-            return false;
-        }
-        if (mStagedOperationType != that.mStagedOperationType) {
-            return false;
-        }
-        if (mDistroStatus != that.mDistroStatus) {
-            return false;
-        }
-        if (!mBaseRulesVersion.equals(that.mBaseRulesVersion)) {
-            return false;
-        }
-        if (!mDistroFormatVersionSupported.equals(that.mDistroFormatVersionSupported)) {
-            return false;
-        }
-        if (mStagedDistroRulesVersion != null ? !mStagedDistroRulesVersion
-                .equals(that.mStagedDistroRulesVersion) : that.mStagedDistroRulesVersion != null) {
-            return false;
-        }
-        return mInstalledDistroRulesVersion != null ? mInstalledDistroRulesVersion
-                .equals(that.mInstalledDistroRulesVersion)
-                : that.mInstalledDistroRulesVersion == null;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mBaseRulesVersion.hashCode();
-        result = 31 * result + mDistroFormatVersionSupported.hashCode();
-        result = 31 * result + (mOperationInProgress ? 1 : 0);
-        result = 31 * result + mStagedOperationType;
-        result = 31 * result + (mStagedDistroRulesVersion != null ? mStagedDistroRulesVersion
-                .hashCode()
-                : 0);
-        result = 31 * result + mDistroStatus;
-        result = 31 * result + (mInstalledDistroRulesVersion != null ? mInstalledDistroRulesVersion
-                .hashCode() : 0);
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "RulesState{"
-                + "mBaseRulesVersion='" + mBaseRulesVersion + '\''
-                + ", mDistroFormatVersionSupported=" + mDistroFormatVersionSupported
-                + ", mOperationInProgress=" + mOperationInProgress
-                + ", mStagedOperationType=" + mStagedOperationType
-                + ", mStagedDistroRulesVersion=" + mStagedDistroRulesVersion
-                + ", mDistroStatus=" + mDistroStatus
-                + ", mInstalledDistroRulesVersion=" + mInstalledDistroRulesVersion
-                + '}';
-    }
-
-    private static int validateStagedOperation(int stagedOperationType) {
-        if (stagedOperationType < STAGED_OPERATION_UNKNOWN
-                || stagedOperationType > STAGED_OPERATION_INSTALL) {
-            throw new IllegalArgumentException("Unknown operation type=" + stagedOperationType);
-        }
-        return stagedOperationType;
-    }
-
-    private static int validateDistroStatus(int distroStatus) {
-        if (distroStatus < DISTRO_STATUS_UNKNOWN || distroStatus > DISTRO_STATUS_INSTALLED) {
-            throw new IllegalArgumentException("Unknown distro status=" + distroStatus);
-        }
-        return distroStatus;
-    }
-}
diff --git a/core/java/android/app/timezone/RulesUpdaterContract.java b/core/java/android/app/timezone/RulesUpdaterContract.java
deleted file mode 100644
index 74ed658..0000000
--- a/core/java/android/app/timezone/RulesUpdaterContract.java
+++ /dev/null
@@ -1,89 +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 android.app.timezone;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.ParcelFileDescriptor;
-import android.os.UserHandle;
-
-/**
- * Constants related to the contract between the Android system and the privileged time zone updater
- * application.
- *
- * @hide
- */
-public final class RulesUpdaterContract {
-
-    /**
-     * The system permission possessed by the Android system that allows it to trigger time zone
-     * update checks. The updater should be configured to require this permission when registering
-     * for {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intents.
-     */
-    public static final String TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION =
-            android.Manifest.permission.TRIGGER_TIME_ZONE_RULES_CHECK;
-
-    /**
-     * The system permission possessed by the time zone rules updater app that allows it to update
-     * device time zone rules. The Android system requires this permission for calls made to
-     * {@link RulesManager}.
-     */
-    public static final String UPDATE_TIME_ZONE_RULES_PERMISSION =
-            android.Manifest.permission.UPDATE_TIME_ZONE_RULES;
-
-    /**
-     * The action of the intent that the Android system will broadcast. The intent will be targeted
-     * at the configured updater application's package meaning the term "broadcast" only loosely
-     * applies.
-     */
-    public static final String ACTION_TRIGGER_RULES_UPDATE_CHECK =
-            "com.android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK";
-
-    /**
-     * The extra containing the {@code byte[]} that should be passed to
-     * {@link RulesManager#requestInstall(ParcelFileDescriptor, byte[], Callback)},
-     * {@link RulesManager#requestUninstall(byte[], Callback)} and
-     * {@link RulesManager#requestNothing(byte[], boolean)} methods when the
-     * {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intent has been processed.
-     */
-    public static final String EXTRA_CHECK_TOKEN =
-            "com.android.intent.extra.timezone.CHECK_TOKEN";
-
-    /**
-     * Creates an intent that would trigger a time zone rules update check.
-     */
-    public static Intent createUpdaterIntent(String updaterPackageName) {
-        Intent intent = new Intent(RulesUpdaterContract.ACTION_TRIGGER_RULES_UPDATE_CHECK);
-        intent.setPackage(updaterPackageName);
-        intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
-        return intent;
-    }
-
-    /**
-     * Broadcasts an {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with the
-     * {@link #EXTRA_CHECK_TOKEN} that triggers an update check, including the required receiver
-     * permission.
-     */
-    public static void sendBroadcast(Context context, String updaterAppPackageName,
-            byte[] checkTokenBytes) {
-        Intent intent = createUpdaterIntent(updaterAppPackageName);
-        intent.putExtra(EXTRA_CHECK_TOKEN, checkTokenBytes);
-        context.sendBroadcastAsUser(
-                intent, UserHandle.SYSTEM,
-                RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION);
-    }
-}
diff --git a/core/java/android/app/timezone/Utils.java b/core/java/android/app/timezone/Utils.java
deleted file mode 100644
index 8dd3fb7..0000000
--- a/core/java/android/app/timezone/Utils.java
+++ /dev/null
@@ -1,67 +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 android.app.timezone;
-
-/**
- * Shared code for android.app.timezone classes.
- */
-final class Utils {
-    private Utils() {}
-
-    static int validateVersion(String type, int version) {
-        if (version < 0 || version > 999) {
-            throw new IllegalArgumentException("Invalid " + type + " version=" + version);
-        }
-        return version;
-    }
-
-    static String validateRulesVersion(String type, String rulesVersion) {
-        validateNotNull(type, rulesVersion);
-
-        if (rulesVersion.isEmpty()) {
-            throw new IllegalArgumentException(type + " must not be empty");
-        }
-        return rulesVersion;
-    }
-
-    /** Validates that {@code object} is not null. Always returns {@code object}. */
-    static <T> T validateNotNull(String type, T object) {
-        if (object == null) {
-            throw new NullPointerException(type + " == null");
-        }
-        return object;
-    }
-
-    /**
-     * If {@code requireNotNull} is {@code true} calls {@link #validateNotNull(String, Object)},
-     * and {@link #validateNull(String, Object)} otherwise. Returns {@code object}.
-     */
-    static <T> T validateConditionalNull(boolean requireNotNull, String type, T object) {
-        if (requireNotNull) {
-            return validateNotNull(type, object);
-        } else {
-            return validateNull(type, object);
-        }
-    }
-
-    /** Validates that {@code object} is null. Always returns null. */
-    static <T> T validateNull(String type, T object) {
-        if (object != null) {
-            throw new IllegalArgumentException(type + " != null");
-        }
-        return null;
-    }
-}
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index 0e9e28b..f357fb2 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -81,11 +81,11 @@
     String SHELL_COMMAND_SET_GEO_DETECTION_ENABLED = "set_geo_detection_enabled";
 
     /**
-     * A shell command that injects a geolocation time zone suggestion (as if from the
+     * A shell command that injects a location algorithm event (as if from the
      * location_time_zone_manager).
      * @hide
      */
-    String SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE = "suggest_geo_location_time_zone";
+    String SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT = "handle_location_algorithm_event";
 
     /**
      * A shell command that injects a manual time zone suggestion (as if from the SettingsUI or
diff --git a/core/java/android/app/wearable/IWearableSensingManager.aidl b/core/java/android/app/wearable/IWearableSensingManager.aidl
new file mode 100644
index 0000000..ff37bd8
--- /dev/null
+++ b/core/java/android/app/wearable/IWearableSensingManager.aidl
@@ -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 android.app.wearable;
+
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.SharedMemory;
+
+/**
+ * Interface for a WearableSensingManager for managing wearable sensing service.
+ *
+ * @hide
+ */
+interface IWearableSensingManager {
+     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
+     void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
+     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
+     void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
+}
\ No newline at end of file
diff --git a/core/java/android/app/wearable/OWNERS b/core/java/android/app/wearable/OWNERS
new file mode 100644
index 0000000..073e2d7
--- /dev/null
+++ b/core/java/android/app/wearable/OWNERS
@@ -0,0 +1,3 @@
+charliewang@google.com
+oni@google.com
+volnov@google.com
\ No newline at end of file
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
new file mode 100644
index 0000000..a71590b
--- /dev/null
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -0,0 +1,193 @@
+/*
+ * 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.wearable;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.content.Context;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.service.wearable.WearableSensingService;
+import android.system.OsConstants;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Allows granted apps to manage the WearableSensingService.
+ * Applications are responsible for managing the connection to Wearables. Applications can choose
+ * to provide a data stream to the WearableSensingService to use for
+ * computing {@link AmbientContextEvent}s. Applications can also optionally provide their own
+ * defined data to power the detection of {@link AmbientContextEvent}s.
+ * Methods on this class requires the caller to hold and be granted the
+ * {@link Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE}.
+ *
+ * @hide
+ */
+
+@SystemApi
+@SystemService(Context.WEARABLE_SENSING_SERVICE)
+public class WearableSensingManager {
+    /**
+     * The bundle key for the service status query result, used in
+     * {@code RemoteCallback#sendResult}.
+     *
+     * @hide
+     */
+    public static final String STATUS_RESPONSE_BUNDLE_KEY =
+            "android.app.wearable.WearableSensingStatusBundleKey";
+
+
+    /**
+     * An unknown status.
+     */
+    public static final int STATUS_UNKNOWN = 0;
+
+    /**
+     * The value of the status code that indicates success.
+     */
+    public static final int STATUS_SUCCESS = 1;
+
+    /**
+     * The value of the status code that indicates one or more of the
+     * requested events are not supported.
+     */
+    public static final int STATUS_UNSUPPORTED = 2;
+
+    /**
+     * The value of the status code that indicates service not available.
+     */
+    public static final int STATUS_SERVICE_UNAVAILABLE = 3;
+
+    /**
+     * The value of the status code that there's no connection to the wearable.
+     */
+    public static final int STATUS_WEARABLE_UNAVAILABLE = 4;
+
+    /**
+     * The value of the status code that the app is not granted access.
+     */
+    public static final int STATUS_ACCESS_DENIED = 5;
+
+    /** @hide */
+    @IntDef(prefix = { "STATUS_" }, value = {
+            STATUS_UNKNOWN,
+            STATUS_SUCCESS,
+            STATUS_UNSUPPORTED,
+            STATUS_SERVICE_UNAVAILABLE,
+            STATUS_WEARABLE_UNAVAILABLE,
+            STATUS_ACCESS_DENIED
+    }) public @interface StatusCode {}
+
+    private final Context mContext;
+    private final IWearableSensingManager mService;
+
+    /**
+     * {@hide}
+     */
+    public WearableSensingManager(Context context, IWearableSensingManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Provides a data stream to the WearableSensingService that's backed by the
+     * parcelFileDescriptor, and sends the result to the {@link Consumer} right after the call.
+     * This is used by applications that will also provide an implementation of
+     * an isolated WearableSensingService. If the data stream was provided successfully
+     * {@link WearableSensingManager#STATUS_SUCCESS} will be provided.
+     *
+     * @param parcelFileDescriptor The data stream to provide
+     * @param executor Executor on which to run the consumer callback
+     * @param statusConsumer A consumer that handles the status codes, which is returned
+     *                 right after the call.
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
+    public void provideDataStream(
+            @NonNull ParcelFileDescriptor parcelFileDescriptor,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull @StatusCode Consumer<Integer> statusConsumer) {
+        try {
+            RemoteCallback callback = new RemoteCallback(result -> {
+                int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> statusConsumer.accept(status));
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            });
+            mService.provideDataStream(parcelFileDescriptor, callback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets configuration and provides read-only data in a {@link PersistableBundle} that may be
+     * used by the WearableSensingService, and sends the result to the {@link Consumer}
+     * right after the call. It is dependent on the application to
+     * define the type of data to provide. This is used by applications that will also
+     * provide an implementation of an isolated WearableSensingService. If the data was
+     * provided successfully {@link WearableSensingManager#STATUS_SUCCESS} will be povided.
+     *
+     * @param data Application configuration data to provide to the {@link WearableSensingService}.
+     *             PersistableBundle does not allow any remotable objects or other contents
+     *             that can be used to communicate with other processes.
+     * @param sharedMemory The unrestricted data blob to
+     *                     provide to the {@link WearableSensingService}. Use this to provide the
+     *                     sensing models data or other such data to the trusted process.
+     *                     The sharedMemory must be read only and protected with
+     *                     {@link OsConstants.PROT_READ}.
+     *                     Other operations will be removed by the system.
+     * @param executor Executor on which to run the consumer callback
+     * @param statusConsumer A consumer that handles the status codes, which is returned
+     *                     right after the call
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
+    public void provideData(
+            @NonNull PersistableBundle data, @Nullable SharedMemory sharedMemory,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull @StatusCode Consumer<Integer> statusConsumer) {
+        try {
+            RemoteCallback callback = new RemoteCallback(result -> {
+                int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> statusConsumer.accept(status));
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            });
+            mService.provideData(data, sharedMemory, callback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+}
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/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 90973c3..0d890a4 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -21,6 +21,7 @@
 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER;
 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -56,6 +57,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -84,50 +87,77 @@
     private static final boolean DEBUG = false;
     private static final String LOG_TAG = "CDM_CompanionDeviceManager";
 
+    /** @hide */
+    @IntDef(prefix = {"RESULT_"}, value = {
+            RESULT_OK,
+            RESULT_CANCELED,
+            RESULT_USER_REJECTED,
+            RESULT_DISCOVERY_TIMEOUT,
+            RESULT_INTERNAL_ERROR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ResultCode {}
+
     /**
-     * The result code to propagate back to the originating activity, indicates the association
-     * dialog is explicitly declined by the users.
-     *
-     * @hide
+     * The result code to propagate back to the user activity, indicates the association
+     * is created successfully.
+     */
+    public static final int RESULT_OK = -1;
+
+    /**
+     * The result code to propagate back to the user activity, indicates if the association dialog
+     * is implicitly cancelled.
+     * E.g. phone is locked, switch to another app or press outside the dialog.
+     */
+    public static final int RESULT_CANCELED = 0;
+
+    /**
+     * The result code to propagate back to the user activity, indicates the association dialog
+     * is explicitly declined by the users.
      */
     public static final int RESULT_USER_REJECTED = 1;
 
     /**
-     * The result code to propagate back to the originating activity, indicates the association
+     * The result code to propagate back to the user activity, indicates the association
      * dialog is dismissed if there's no device found after 20 seconds.
-     *
-     * @hide
      */
     public static final int RESULT_DISCOVERY_TIMEOUT = 2;
 
     /**
-     * The result code to propagate back to the originating activity, indicates the internal error
+     * The result code to propagate back to the user activity, indicates the internal error
      * in CompanionDeviceManager.
-     *
-     * @hide
      */
     public static final int RESULT_INTERNAL_ERROR = 3;
 
     /**
-     *  Requesting applications will receive the String in {@link Callback#onFailure} if the
-     *  association dialog is explicitly declined by the users. e.g. press the Don't allow button.
+     * Requesting applications will receive the String in {@link Callback#onFailure} if the
+     * association dialog is explicitly declined by the users. E.g. press the Don't allow
+     * button.
      *
      * @hide
      */
     public static final String REASON_USER_REJECTED = "user_rejected";
 
     /**
-     *  Requesting applications will receive the String in {@link Callback#onFailure} if there's
-     *  no device found after 20 seconds.
+     * Requesting applications will receive the String in {@link Callback#onFailure} if there's
+     * no devices found after 20 seconds.
      *
      * @hide
      */
     public static final String REASON_DISCOVERY_TIMEOUT = "discovery_timeout";
 
     /**
-     *  Requesting applications will receive the String in {@link Callback#onFailure} if the
-     *  association dialog is in-explicitly declined by the users. e.g. phone is locked, switch to
-     *  another app or press outside the dialog.
+     * Requesting applications will receive the String in {@link Callback#onFailure} if there's
+     * an internal error.
+     *
+     * @hide
+     */
+    public static final String REASON_INTERNAL_ERROR = "internal_error";
+
+    /**
+     * Requesting applications will receive the String in {@link Callback#onFailure} if the
+     * association dialog is implicitly cancelled. E.g. phone is locked, switch to
+     * another app or press outside the dialog.
      *
      * @hide
      */
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 295d69d..5c47ea2 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -19,13 +19,20 @@
 import android.app.PendingIntent;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
 import android.graphics.Point;
 import android.graphics.PointF;
+import android.hardware.input.VirtualDpadConfig;
+import android.hardware.input.VirtualKeyboardConfig;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseConfig;
 import android.hardware.input.VirtualMouseRelativeEvent;
 import android.hardware.input.VirtualMouseScrollEvent;
 import android.hardware.input.VirtualTouchEvent;
+import android.hardware.input.VirtualTouchscreenConfig;
 import android.os.ResultReceiver;
 
 /**
@@ -61,32 +68,22 @@
             IAudioConfigChangedCallback configChangedCallback);
 
     void onAudioSessionEnded();
-
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
     void createVirtualDpad(
-            int displayId,
-            String inputDeviceName,
-            int vendorId,
-            int productId,
+            in VirtualDpadConfig config,
             IBinder token);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
     void createVirtualKeyboard(
-            int displayId,
-            String inputDeviceName,
-            int vendorId,
-            int productId,
+            in VirtualKeyboardConfig config,
             IBinder token);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
     void createVirtualMouse(
-            int displayId,
-            String inputDeviceName,
-            int vendorId,
-            int productId,
+            in VirtualMouseConfig config,
             IBinder token);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
     void createVirtualTouchscreen(
-            int displayId,
-            String inputDeviceName,
-            int vendorId,
-            int productId,
-            IBinder token,
-            in Point screenSize);
+            in VirtualTouchscreenConfig config,
+            IBinder token);
     void unregisterInputDevice(IBinder token);
     int getInputDeviceId(IBinder token);
     boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event);
@@ -97,6 +94,24 @@
     boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
 
     /**
+     * Creates a virtual sensor, capable of injecting sensor events into the system.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    void createVirtualSensor(IBinder tokenm, in VirtualSensorConfig config);
+
+    /**
+     * Removes the sensor corresponding to the given token from the system.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    void unregisterSensor(IBinder token);
+
+    /**
+     * Sends an event to the virtual sensor corresponding to the given token.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event);
+
+    /**
      * Launches a pending intent on the given display that is owned by this virtual device.
      */
     void launchPendingIntent(
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 82d7534..7d6336a 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -52,6 +52,11 @@
     List<VirtualDevice> getVirtualDevices();
 
     /**
+     * Returns the device policy for the given virtual device and policy type.
+     */
+    int getDevicePolicy(int deviceId, int policyType);
+
+    /**
      * Creates a virtual display owned by a particular virtual device.
      *
      * @param virtualDisplayConfig The configuration used in creating the display
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 0bb86fb..0e6cfb1 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -22,12 +22,15 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.PendingIntent;
 import android.companion.AssociationInfo;
 import android.companion.virtual.audio.VirtualAudioDevice;
 import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
+import android.companion.virtual.sensor.VirtualSensor;
+import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Point;
@@ -38,9 +41,13 @@
 import android.hardware.display.VirtualDisplay;
 import android.hardware.display.VirtualDisplayConfig;
 import android.hardware.input.VirtualDpad;
+import android.hardware.input.VirtualDpadConfig;
 import android.hardware.input.VirtualKeyboard;
+import android.hardware.input.VirtualKeyboardConfig;
 import android.hardware.input.VirtualMouse;
+import android.hardware.input.VirtualMouseConfig;
 import android.hardware.input.VirtualTouchscreen;
+import android.hardware.input.VirtualTouchscreenConfig;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -58,6 +65,7 @@
 import java.lang.annotation.Target;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.IntConsumer;
 
@@ -67,7 +75,6 @@
 @SystemService(Context.VIRTUAL_DEVICE_SERVICE)
 public final class VirtualDeviceManager {
 
-    private static final boolean DEBUG = false;
     private static final String TAG = "VirtualDeviceManager";
 
     private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS =
@@ -76,7 +83,7 @@
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
-                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
+                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
 
     /**
      * The default device ID, which is the ID of the primary (non-virtual) device.
@@ -88,6 +95,26 @@
      */
     public static final int INVALID_DEVICE_ID = -1;
 
+    /**
+     * Broadcast Action: A Virtual Device was removed.
+     *
+     * <p class="note">This is a protected intent that can only be sent by the system.</p>
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_VIRTUAL_DEVICE_REMOVED =
+            "android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED";
+
+    /**
+     * Int intent extra to be used with {@link #ACTION_VIRTUAL_DEVICE_REMOVED}.
+     * Contains the identifier of the virtual device, which was removed.
+     *
+     * @hide
+     */
+    public static final String EXTRA_VIRTUAL_DEVICE_ID =
+            "android.companion.virtual.extra.VIRTUAL_DEVICE_ID";
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(
@@ -182,6 +209,28 @@
     }
 
     /**
+     * Returns the device policy for the given virtual device and policy type.
+     *
+     * <p>In case the virtual device identifier is not valid, or there's no explicitly specified
+     * policy for that device and policy type, then
+     * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned.
+     *
+     * @hide
+     */
+    public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
+            int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to retrieve device policy; no virtual device manager service.");
+            return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+        }
+        try {
+            return mService.getDevicePolicy(deviceId, policyType);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * A virtual device has its own virtual display, audio output, microphone, and camera etc. The
      * creator of a virtual device can take the output from the virtual display and stream it over
      * to another device, and inject input events that are received from the remote device.
@@ -228,7 +277,10 @@
                 };
         @Nullable
         private VirtualAudioDevice mVirtualAudioDevice;
+        @NonNull
+        private List<VirtualSensor> mVirtualSensors = new ArrayList<>();
 
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         private VirtualDevice(
                 IVirtualDeviceManager service,
                 Context context,
@@ -242,6 +294,10 @@
                     associationId,
                     params,
                     mActivityListenerBinder);
+            final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs();
+            for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
+                mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i)));
+            }
         }
 
         /**
@@ -256,6 +312,35 @@
         }
 
         /**
+         * @return A new Context bound to this device. This is a convenience method equivalent to
+         * calling {@link Context#createDeviceContext(int)} with the device id of this device.
+         */
+        public @NonNull Context createContext() {
+            try {
+                return mContext.createDeviceContext(mVirtualDevice.getDeviceId());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Returns this device's sensor with the given type and name, if any.
+         *
+         * @see VirtualDeviceParams.Builder#addVirtualSensorConfig
+         *
+         * @param type The type of the sensor.
+         * @param name The name of the sensor.
+         * @return The matching sensor if found, {@code null} otherwise.
+         */
+        @Nullable
+        public VirtualSensor getVirtualSensor(int type, @NonNull String name) {
+            return mVirtualSensors.stream()
+                    .filter(sensor -> sensor.getType() == type && sensor.getName().equals(name))
+                    .findAny()
+                    .orElse(null);
+        }
+
+        /**
          * Launches a given pending intent on the give display ID.
          *
          * @param displayId The display to launch the pending intent on. This display must be
@@ -328,14 +413,72 @@
                 @VirtualDisplayFlag int flags,
                 @Nullable @CallbackExecutor Executor executor,
                 @Nullable VirtualDisplay.Callback callback) {
-            // TODO(b/205343547): Handle display groups properly instead of creating a new display
-            //  group for every new virtual display created using this API.
-            // belongs to the same display group.
             VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
                     getVirtualDisplayName(), width, height, densityDpi)
                     .setSurface(surface)
                     .setFlags(getVirtualDisplayFlags(flags))
                     .build();
+            return createVirtualDisplayInternal(config, executor, callback);
+        }
+
+        /**
+         * Creates a virtual display for this virtual device. All displays created on the same
+         * device belongs to the same display group.
+         *
+         * @param width The width of the virtual display in pixels, must be greater than 0.
+         * @param height The height of the virtual display in pixels, must be greater than 0.
+         * @param densityDpi The density of the virtual display in dpi, must be greater than 0.
+         * @param displayCategories The categories of the virtual display, indicating the type of
+         * activities allowed to run on the display. Activities can declare their type using
+         * {@link android.content.pm.ActivityInfo#requiredDisplayCategory}.
+         * @param surface The surface to which the content of the virtual display should
+         * be rendered, or null if there is none initially. The surface can also be set later using
+         * {@link VirtualDisplay#setSurface(Surface)}.
+         * @param flags A combination of virtual display flags accepted by
+         * {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are
+         * automatically set for all virtual devices:
+         * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC VIRTUAL_DISPLAY_FLAG_PUBLIC} and
+         * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+         * VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
+         * @param executor The executor on which {@code callback} will be invoked. This is ignored
+         * if {@code callback} is {@code null}. If {@code callback} is specified, this executor must
+         * not be null.
+         * @param callback Callback to call when the state of the {@link VirtualDisplay} changes
+         * @return The newly created virtual display, or {@code null} if the application could
+         * not create the virtual display.
+         *
+         * @see DisplayManager#createVirtualDisplay
+         */
+        @Nullable
+        public VirtualDisplay createVirtualDisplay(
+                @IntRange(from = 1) int width,
+                @IntRange(from = 1) int height,
+                @IntRange(from = 1) int densityDpi,
+                @NonNull List<String> displayCategories,
+                @Nullable Surface surface,
+                @VirtualDisplayFlag int flags,
+                @Nullable @CallbackExecutor Executor executor,
+                @Nullable VirtualDisplay.Callback callback) {
+            VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
+                    getVirtualDisplayName(), width, height, densityDpi)
+                    .setDisplayCategories(displayCategories)
+                    .setSurface(surface)
+                    .setFlags(getVirtualDisplayFlags(flags))
+                    .build();
+            return createVirtualDisplayInternal(config, executor, callback);
+        }
+
+        /**
+         * @hide
+         */
+        @Nullable
+        private VirtualDisplay createVirtualDisplayInternal(
+                @NonNull VirtualDisplayConfig config,
+                @Nullable @CallbackExecutor Executor executor,
+                @Nullable VirtualDisplay.Callback callback) {
+            // TODO(b/205343547): Handle display groups properly instead of creating a new display
+            //  group for every new virtual display created using this API.
+            // belongs to the same display group.
             IVirtualDisplayCallback callbackWrapper =
                     new DisplayManagerGlobal.VirtualDisplayCallback(callback, executor);
             final int displayId;
@@ -357,6 +500,7 @@
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         public void close() {
             try {
+                // This also takes care of unregistering all virtual sensors.
                 mVirtualDevice.close();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -370,23 +514,15 @@
         /**
          * Creates a virtual dpad.
          *
-         * @param display the display that the events inputted through this device should target
-         * @param inputDeviceName the name to call this input device
-         * @param vendorId the PCI vendor id
-         * @param productId the product id, as defined by the vendor
+         * @param config the configurations of the virtual Dpad.
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         @NonNull
-        public VirtualDpad createVirtualDpad(
-                @NonNull VirtualDisplay display,
-                @NonNull String inputDeviceName,
-                int vendorId,
-                int productId) {
+        public VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
             try {
                 final IBinder token = new Binder(
-                        "android.hardware.input.VirtualDpad:" + inputDeviceName);
-                mVirtualDevice.createVirtualDpad(display.getDisplay().getDisplayId(),
-                        inputDeviceName, vendorId, productId, token);
+                        "android.hardware.input.VirtualDpad:" + config.getInputDeviceName());
+                mVirtualDevice.createVirtualDpad(config, token);
                 return new VirtualDpad(mVirtualDevice, token);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -396,23 +532,15 @@
         /**
          * Creates a virtual keyboard.
          *
-         * @param display the display that the events inputted through this device should target
-         * @param inputDeviceName the name to call this input device
-         * @param vendorId the PCI vendor id
-         * @param productId the product id, as defined by the vendor
+         * @param config the configurations of the virtual keyboard.
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         @NonNull
-        public VirtualKeyboard createVirtualKeyboard(
-                @NonNull VirtualDisplay display,
-                @NonNull String inputDeviceName,
-                int vendorId,
-                int productId) {
+        public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) {
             try {
                 final IBinder token = new Binder(
-                        "android.hardware.input.VirtualKeyboard:" + inputDeviceName);
-                mVirtualDevice.createVirtualKeyboard(display.getDisplay().getDisplayId(),
-                        inputDeviceName, vendorId, productId, token);
+                        "android.hardware.input.VirtualKeyboard:" + config.getInputDeviceName());
+                mVirtualDevice.createVirtualKeyboard(config, token);
                 return new VirtualKeyboard(mVirtualDevice, token);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -420,26 +548,90 @@
         }
 
         /**
+         * Creates a virtual keyboard.
+         *
+         * @param display         the display that the events inputted through this device should
+         *                        target
+         * @param inputDeviceName the name to call this input device
+         * @param vendorId        the PCI vendor id
+         * @param productId       the product id, as defined by the vendor
+         * @see #createVirtualKeyboard(VirtualKeyboardConfig config)
+         * @deprecated Use {@link #createVirtualKeyboard(VirtualKeyboardConfig config)} instead
+         */
+        @Deprecated
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @NonNull
+        public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualDisplay display,
+                @NonNull String inputDeviceName, int vendorId, int productId) {
+            VirtualKeyboardConfig keyboardConfig =
+                    new VirtualKeyboardConfig.Builder()
+                            .setVendorId(vendorId)
+                            .setProductId(productId)
+                            .setInputDeviceName(inputDeviceName)
+                            .setAssociatedDisplayId(display.getDisplay().getDisplayId())
+                            .build();
+            return createVirtualKeyboard(keyboardConfig);
+        }
+
+        /**
+         * Creates a virtual mouse.
+         *
+         * @param config the configurations of the virtual mouse.
+         */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @NonNull
+        public VirtualMouse createVirtualMouse(@NonNull VirtualMouseConfig config) {
+            try {
+                final IBinder token = new Binder(
+                        "android.hardware.input.VirtualMouse:" + config.getInputDeviceName());
+                mVirtualDevice.createVirtualMouse(config, token);
+                return new VirtualMouse(mVirtualDevice, token);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Creates a virtual mouse.
          *
-         * @param display the display that the events inputted through this device should target
+         * @param display         the display that the events inputted through this device should
+         *                        target
          * @param inputDeviceName the name to call this input device
-         * @param vendorId the PCI vendor id
-         * @param productId the product id, as defined by the vendor
+         * @param vendorId        the PCI vendor id
+         * @param productId       the product id, as defined by the vendor
+         * @see #createVirtualMouse(VirtualMouseConfig config)
+         * @deprecated Use {@link #createVirtualMouse(VirtualMouseConfig config)} instead
+         * *
+         */
+        @Deprecated
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @NonNull
+        public VirtualMouse createVirtualMouse(@NonNull VirtualDisplay display,
+                @NonNull String inputDeviceName, int vendorId, int productId) {
+            VirtualMouseConfig mouseConfig =
+                    new VirtualMouseConfig.Builder()
+                            .setVendorId(vendorId)
+                            .setProductId(productId)
+                            .setInputDeviceName(inputDeviceName)
+                            .setAssociatedDisplayId(display.getDisplay().getDisplayId())
+                            .build();
+            return createVirtualMouse(mouseConfig);
+        }
+
+        /**
+         * Creates a virtual touchscreen.
+         *
+         * @param config the configurations of the virtual touchscreen.
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         @NonNull
-        public VirtualMouse createVirtualMouse(
-                @NonNull VirtualDisplay display,
-                @NonNull String inputDeviceName,
-                int vendorId,
-                int productId) {
+        public VirtualTouchscreen createVirtualTouchscreen(
+                @NonNull VirtualTouchscreenConfig config) {
             try {
                 final IBinder token = new Binder(
-                        "android.hardware.input.VirtualMouse:" + inputDeviceName);
-                mVirtualDevice.createVirtualMouse(display.getDisplay().getDisplayId(),
-                        inputDeviceName, vendorId, productId, token);
-                return new VirtualMouse(mVirtualDevice, token);
+                        "android.hardware.input.VirtualTouchscreen:" + config.getInputDeviceName());
+                mVirtualDevice.createVirtualTouchscreen(config, token);
+                return new VirtualTouchscreen(mVirtualDevice, token);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -448,29 +640,32 @@
         /**
          * Creates a virtual touchscreen.
          *
-         * @param display the display that the events inputted through this device should target
+         * @param display         the display that the events inputted through this device should
+         *                        target
          * @param inputDeviceName the name to call this input device
-         * @param vendorId the PCI vendor id
-         * @param productId the product id, as defined by the vendor
+         * @param vendorId        the PCI vendor id
+         * @param productId       the product id, as defined by the vendor
+         * @see #createVirtualTouchscreen(VirtualTouchscreenConfig config)
+         * @deprecated Use {@link #createVirtualTouchscreen(VirtualTouchscreenConfig config)}
+         * instead
          */
+        @Deprecated
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         @NonNull
-        public VirtualTouchscreen createVirtualTouchscreen(
-                @NonNull VirtualDisplay display,
-                @NonNull String inputDeviceName,
-                int vendorId,
-                int productId) {
-            try {
-                final IBinder token = new Binder(
-                        "android.hardware.input.VirtualTouchscreen:" + inputDeviceName);
-                final Point size = new Point();
-                display.getDisplay().getSize(size);
-                mVirtualDevice.createVirtualTouchscreen(display.getDisplay().getDisplayId(),
-                        inputDeviceName, vendorId, productId, token, size);
-                return new VirtualTouchscreen(mVirtualDevice, token);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
+        public VirtualTouchscreen createVirtualTouchscreen(@NonNull VirtualDisplay display,
+                @NonNull String inputDeviceName, int vendorId, int productId) {
+            final Point size = new Point();
+            display.getDisplay().getSize(size);
+            VirtualTouchscreenConfig touchscreenConfig =
+                    new VirtualTouchscreenConfig.Builder()
+                            .setVendorId(vendorId)
+                            .setProductId(productId)
+                            .setInputDeviceName(inputDeviceName)
+                            .setAssociatedDisplayId(display.getDisplay().getDisplayId())
+                            .setWidthInPixels(size.x)
+                            .setHeightInPixels(size.y)
+                            .build();
+            return createVirtualTouchscreen(touchscreenConfig);
         }
 
         /**
@@ -542,6 +737,28 @@
         }
 
         /**
+         * Creates a virtual sensor, capable of injecting sensor events into the system. Only for
+         * internal use, since device sensors must remain valid for the entire lifetime of the
+         * device.
+         *
+         * @param config The configuration of the sensor.
+         * @hide
+         */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @NonNull
+        public VirtualSensor createVirtualSensor(@NonNull VirtualSensorConfig config) {
+            Objects.requireNonNull(config);
+            try {
+                final IBinder token = new Binder(
+                        "android.hardware.sensor.VirtualSensor:" + config.getName());
+                mVirtualDevice.createVirtualSensor(token, config);
+                return new VirtualSensor(config.getType(), config.getName(), mVirtualDevice, token);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Adds an activity listener to listen for events such as top activity change or virtual
          * display task stack became empty.
          *
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index d40c9d6..be77f2b 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -23,19 +23,22 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
 import android.util.ArraySet;
-
-import com.android.internal.util.Preconditions;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
@@ -103,6 +106,60 @@
      */
     public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1;
 
+    /** @hide */
+    @IntDef(prefix = "DEVICE_POLICY_",  value = {DEVICE_POLICY_DEFAULT, DEVICE_POLICY_CUSTOM})
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    public @interface DevicePolicy {}
+
+    /**
+     * Indicates that there is no special logic for this virtual device and it should be treated
+     * the same way as the default device, keeping the default behavior unchanged.
+     */
+    public static final int DEVICE_POLICY_DEFAULT = 0;
+
+    /**
+     * Indicates that there is custom logic, specific to this virtual device, which should be
+     * triggered instead of the default behavior.
+     */
+    public static final int DEVICE_POLICY_CUSTOM = 1;
+
+    /**
+     * Any relevant component must be able to interpret the correct meaning of a custom policy for
+     * a given policy type.
+     * @hide
+     */
+    @IntDef(prefix = "POLICY_TYPE_",  value = {POLICY_TYPE_SENSORS})
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    public @interface PolicyType {}
+
+    /**
+     * Tells the sensor framework how to handle sensor requests from contexts associated with this
+     * virtual device, namely the sensors returned by
+     * {@link android.hardware.SensorManager#getSensorList}:
+     *
+     * <ul>
+     *     <li>{@link #DEVICE_POLICY_DEFAULT}: Return the sensors of the default device.
+     *     <li>{@link #DEVICE_POLICY_CUSTOM}: Return the sensors of the virtual device. Note that if
+     *     the virtual device did not create any virtual sensors, then an empty list is returned.
+     * </ul>
+     */
+    public static final int POLICY_TYPE_SENSORS = 0;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = "RECENTS_POLICY_",
+            value = {RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS})
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    public @interface RecentsPolicy {}
+
+    /**
+     * If set, activities launched on this virtual device are allowed to appear in the host device
+     * of the recently launched activities list.
+     */
+    public static final int RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS = 1 << 0;
+
     private final int mLockState;
     @NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
     @NonNull private final ArraySet<ComponentName> mAllowedCrossTaskNavigations;
@@ -114,6 +171,11 @@
     @ActivityPolicy
     private final int mDefaultActivityPolicy;
     @Nullable private final String mName;
+    // Mapping of @PolicyType to @DevicePolicy
+    @NonNull private final SparseIntArray mDevicePolicies;
+    @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
+    @RecentsPolicy
+    private final int mDefaultRecentsPolicy;
 
     private VirtualDeviceParams(
             @LockState int lockState,
@@ -124,22 +186,25 @@
             @NonNull Set<ComponentName> allowedActivities,
             @NonNull Set<ComponentName> blockedActivities,
             @ActivityPolicy int defaultActivityPolicy,
-            @Nullable String name) {
-        Preconditions.checkNotNull(usersWithMatchingAccounts);
-        Preconditions.checkNotNull(allowedCrossTaskNavigations);
-        Preconditions.checkNotNull(blockedCrossTaskNavigations);
-        Preconditions.checkNotNull(allowedActivities);
-        Preconditions.checkNotNull(blockedActivities);
-
+            @Nullable String name,
+            @NonNull SparseIntArray devicePolicies,
+            @NonNull List<VirtualSensorConfig> virtualSensorConfigs,
+            @RecentsPolicy int defaultRecentsPolicy) {
         mLockState = lockState;
-        mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
-        mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations);
-        mBlockedCrossTaskNavigations = new ArraySet<>(blockedCrossTaskNavigations);
+        mUsersWithMatchingAccounts =
+                new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
+        mAllowedCrossTaskNavigations =
+                new ArraySet<>(Objects.requireNonNull(allowedCrossTaskNavigations));
+        mBlockedCrossTaskNavigations =
+                new ArraySet<>(Objects.requireNonNull(blockedCrossTaskNavigations));
         mDefaultNavigationPolicy = defaultNavigationPolicy;
-        mAllowedActivities = new ArraySet<>(allowedActivities);
-        mBlockedActivities = new ArraySet<>(blockedActivities);
+        mAllowedActivities = new ArraySet<>(Objects.requireNonNull(allowedActivities));
+        mBlockedActivities = new ArraySet<>(Objects.requireNonNull(blockedActivities));
         mDefaultActivityPolicy = defaultActivityPolicy;
         mName = name;
+        mDevicePolicies = Objects.requireNonNull(devicePolicies);
+        mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
+        mDefaultRecentsPolicy = defaultRecentsPolicy;
     }
 
     @SuppressWarnings("unchecked")
@@ -153,6 +218,10 @@
         mBlockedActivities = (ArraySet<ComponentName>) parcel.readArraySet(null);
         mDefaultActivityPolicy = parcel.readInt();
         mName = parcel.readString8();
+        mDevicePolicies = parcel.readSparseIntArray();
+        mVirtualSensorConfigs = new ArrayList<>();
+        parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR);
+        mDefaultRecentsPolicy = parcel.readInt();
     }
 
     /**
@@ -258,6 +327,35 @@
         return mName;
     }
 
+    /**
+     * Returns the policy specified for this policy type, or {@link #DEVICE_POLICY_DEFAULT} if no
+     * policy for this type has been explicitly specified.
+     *
+     * @see Builder#setDevicePolicy
+     */
+    public @DevicePolicy int getDevicePolicy(@PolicyType int policyType) {
+        return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
+    }
+
+    /**
+     * Returns the configurations for all sensors that should be created for this device.
+     *
+     * @see Builder#addVirtualSensorConfig
+     */
+    public @NonNull List<VirtualSensorConfig> getVirtualSensorConfigs() {
+        return mVirtualSensorConfigs;
+    }
+
+    /**
+     * Returns the policy of how to handle activities in recents.
+     *
+     * @see RecentsPolicy
+     */
+    @RecentsPolicy
+    public int getDefaultRecentsPolicy() {
+        return mDefaultRecentsPolicy;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -274,6 +372,9 @@
         dest.writeArraySet(mBlockedActivities);
         dest.writeInt(mDefaultActivityPolicy);
         dest.writeString8(mName);
+        dest.writeSparseIntArray(mDevicePolicies);
+        dest.writeTypedList(mVirtualSensorConfigs);
+        dest.writeInt(mDefaultRecentsPolicy);
     }
 
     @Override
@@ -285,6 +386,18 @@
             return false;
         }
         VirtualDeviceParams that = (VirtualDeviceParams) o;
+        final int devicePoliciesCount = mDevicePolicies.size();
+        if (devicePoliciesCount != that.mDevicePolicies.size()) {
+            return false;
+        }
+        for (int i = 0; i < devicePoliciesCount; i++) {
+            if (mDevicePolicies.keyAt(i) != that.mDevicePolicies.keyAt(i)) {
+                return false;
+            }
+            if (mDevicePolicies.valueAt(i) != that.mDevicePolicies.valueAt(i)) {
+                return false;
+            }
+        }
         return mLockState == that.mLockState
                 && mUsersWithMatchingAccounts.equals(that.mUsersWithMatchingAccounts)
                 && Objects.equals(mAllowedCrossTaskNavigations, that.mAllowedCrossTaskNavigations)
@@ -293,15 +406,22 @@
                 && Objects.equals(mAllowedActivities, that.mAllowedActivities)
                 && Objects.equals(mBlockedActivities, that.mBlockedActivities)
                 && mDefaultActivityPolicy == that.mDefaultActivityPolicy
-                && Objects.equals(mName, that.mName);
+                && Objects.equals(mName, that.mName)
+                && mDefaultRecentsPolicy == that.mDefaultRecentsPolicy;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(
+        int hashCode = Objects.hash(
                 mLockState, mUsersWithMatchingAccounts, mAllowedCrossTaskNavigations,
-                mBlockedCrossTaskNavigations, mDefaultNavigationPolicy,  mAllowedActivities,
-                mBlockedActivities, mDefaultActivityPolicy, mName);
+                mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities,
+                mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies,
+                mDefaultRecentsPolicy);
+        for (int i = 0; i < mDevicePolicies.size(); i++) {
+            hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
+            hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
+        }
+        return hashCode;
     }
 
     @Override
@@ -317,6 +437,8 @@
                 + " mBlockedActivities=" + mBlockedActivities
                 + " mDefaultActivityPolicy=" + mDefaultActivityPolicy
                 + " mName=" + mName
+                + " mDevicePolicies=" + mDevicePolicies
+                + " mDefaultRecentsPolicy=" + mDefaultRecentsPolicy
                 + ")";
     }
 
@@ -350,6 +472,9 @@
         private int mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
         private boolean mDefaultActivityPolicyConfigured = false;
         @Nullable private String mName;
+        @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
+        @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
+        private int mDefaultRecentsPolicy;
 
         /**
          * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -389,8 +514,7 @@
         @NonNull
         public Builder setUsersWithMatchingAccounts(
                 @NonNull Set<UserHandle> usersWithMatchingAccounts) {
-            Preconditions.checkNotNull(usersWithMatchingAccounts);
-            mUsersWithMatchingAccounts = usersWithMatchingAccounts;
+            mUsersWithMatchingAccounts = Objects.requireNonNull(usersWithMatchingAccounts);
             return this;
         }
 
@@ -413,7 +537,6 @@
         @NonNull
         public Builder setAllowedCrossTaskNavigations(
                 @NonNull Set<ComponentName> allowedCrossTaskNavigations) {
-            Preconditions.checkNotNull(allowedCrossTaskNavigations);
             if (mDefaultNavigationPolicyConfigured
                     && mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_BLOCKED) {
                 throw new IllegalArgumentException(
@@ -422,7 +545,7 @@
             }
             mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_BLOCKED;
             mDefaultNavigationPolicyConfigured = true;
-            mAllowedCrossTaskNavigations = allowedCrossTaskNavigations;
+            mAllowedCrossTaskNavigations = Objects.requireNonNull(allowedCrossTaskNavigations);
             return this;
         }
 
@@ -445,7 +568,6 @@
         @NonNull
         public Builder setBlockedCrossTaskNavigations(
                 @NonNull Set<ComponentName> blockedCrossTaskNavigations) {
-            Preconditions.checkNotNull(blockedCrossTaskNavigations);
             if (mDefaultNavigationPolicyConfigured
                      && mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_ALLOWED) {
                 throw new IllegalArgumentException(
@@ -454,7 +576,7 @@
             }
             mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED;
             mDefaultNavigationPolicyConfigured = true;
-            mBlockedCrossTaskNavigations = blockedCrossTaskNavigations;
+            mBlockedCrossTaskNavigations = Objects.requireNonNull(blockedCrossTaskNavigations);
             return this;
         }
 
@@ -473,7 +595,6 @@
          */
         @NonNull
         public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) {
-            Preconditions.checkNotNull(allowedActivities);
             if (mDefaultActivityPolicyConfigured
                     && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_BLOCKED) {
                 throw new IllegalArgumentException(
@@ -481,7 +602,7 @@
             }
             mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_BLOCKED;
             mDefaultActivityPolicyConfigured = true;
-            mAllowedActivities = allowedActivities;
+            mAllowedActivities = Objects.requireNonNull(allowedActivities);
             return this;
         }
 
@@ -500,7 +621,6 @@
          */
         @NonNull
         public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) {
-            Preconditions.checkNotNull(blockedActivities);
             if (mDefaultActivityPolicyConfigured
                     && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_ALLOWED) {
                 throw new IllegalArgumentException(
@@ -508,7 +628,7 @@
             }
             mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
             mDefaultActivityPolicyConfigured = true;
-            mBlockedActivities = blockedActivities;
+            mBlockedActivities = Objects.requireNonNull(blockedActivities);
             return this;
         }
 
@@ -528,10 +648,75 @@
         }
 
         /**
+         * Specifies a policy for this virtual device.
+         *
+         * <p>Policies define the system behavior that may be specific for this virtual device. A
+         * policy can be defined for each {@code PolicyType}, but they are all optional.
+         *
+         * @param policyType the type of policy, i.e. which behavior to specify a policy for.
+         * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
+         */
+        @NonNull
+        public Builder setDevicePolicy(@PolicyType int policyType, @DevicePolicy int devicePolicy) {
+            mDevicePolicies.put(policyType, devicePolicy);
+            return this;
+        }
+
+        /**
+         * Adds a configuration for a sensor that should be created for this virtual device.
+         *
+         * <p>Device sensors must remain valid for the entire lifetime of the device, hence they are
+         * created together with the device itself, and removed when the device is removed.
+         *
+         * <p>Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_SENSORS}.
+         *
+         * @see android.companion.virtual.sensor.VirtualSensor
+         * @see #setDevicePolicy
+         */
+        @NonNull
+        public Builder addVirtualSensorConfig(@NonNull VirtualSensorConfig virtualSensorConfig) {
+            mVirtualSensorConfigs.add(Objects.requireNonNull(virtualSensorConfig));
+            return this;
+        }
+
+        /**
+         * Sets the policy to indicate how activities are handled in recents.
+         *
+         * @param defaultRecentsPolicy A policy specifying how to handle activities in recents.
+         */
+        @NonNull
+        public Builder setDefaultRecentsPolicy(@RecentsPolicy int defaultRecentsPolicy) {
+            mDefaultRecentsPolicy = defaultRecentsPolicy;
+            return this;
+        }
+
+        /**
          * Builds the {@link VirtualDeviceParams} instance.
+         *
+         * @throws IllegalArgumentException if there's mismatch between policy definition and
+         * the passed parameters or if there are sensor configs with the same type and name.
+         *
          */
         @NonNull
         public VirtualDeviceParams build() {
+            if (!mVirtualSensorConfigs.isEmpty()
+                    && (mDevicePolicies.get(POLICY_TYPE_SENSORS, DEVICE_POLICY_DEFAULT)
+                            != DEVICE_POLICY_CUSTOM)) {
+                throw new IllegalArgumentException(
+                        "DEVICE_POLICY_CUSTOM for POLICY_TYPE_SENSORS is required for creating "
+                                + "virtual sensors.");
+            }
+            SparseArray<Set<String>> sensorNameByType = new SparseArray();
+            for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) {
+                VirtualSensorConfig config = mVirtualSensorConfigs.get(i);
+                Set<String> sensorNames = sensorNameByType.get(config.getType(), new ArraySet<>());
+                if (!sensorNames.add(config.getName())) {
+                    throw new IllegalArgumentException(
+                            "Sensor names must be unique for a particular sensor type.");
+                }
+                sensorNameByType.put(config.getType(), sensorNames);
+            }
+
             return new VirtualDeviceParams(
                     mLockState,
                     mUsersWithMatchingAccounts,
@@ -541,7 +726,10 @@
                     mAllowedActivities,
                     mBlockedActivities,
                     mDefaultActivityPolicy,
-                    mName);
+                    mName,
+                    mDevicePolicies,
+                    mVirtualSensorConfigs,
+                    mDefaultRecentsPolicy);
         }
     }
 }
diff --git a/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl b/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl
new file mode 100644
index 0000000..b99cc7e
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.companion.virtual.sensor;
+
+/**
+ * Interface for notification of listener registration changes for a virtual sensor.
+ *
+ * @hide
+ */
+oneway interface IVirtualSensorStateChangeCallback {
+
+    /**
+     * Called when the registered listeners to a virtual sensor have changed.
+     *
+     * @param enabled Whether the sensor is enabled.
+     * @param samplingPeriodMicros The requested sensor's sampling period in microseconds.
+     * @param batchReportingLatencyMicros The requested maximum time interval in microseconds
+     * between the delivery of two batches of sensor events.
+     */
+    void onStateChanged(boolean enabled, int samplingPeriodMicros, int batchReportLatencyMicros);
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
new file mode 100644
index 0000000..58a5387
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -0,0 +1,100 @@
+/*
+ * 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.companion.virtual.sensor;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.hardware.Sensor;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.time.Duration;
+
+/**
+ * Representation of a sensor on a remote device, capable of sending events, such as an
+ * accelerometer or a gyroscope.
+ *
+ * This registers the sensor device with the sensor framework as a runtime sensor.
+ *
+ * @hide
+ */
+@SystemApi
+public class VirtualSensor {
+
+    /**
+     * Interface for notification of listener registration changes for a virtual sensor.
+     */
+    public interface SensorStateChangeCallback {
+        /**
+         * Called when the registered listeners to a virtual sensor have changed.
+         *
+         * @param enabled Whether the sensor is enabled.
+         * @param samplingPeriod The requested sampling period of the sensor.
+         * @param batchReportLatency The requested maximum time interval between the delivery of two
+         * batches of sensor events.
+         */
+        void onStateChanged(boolean enabled, @NonNull Duration samplingPeriod,
+                @NonNull Duration batchReportLatency);
+    }
+
+    private final int mType;
+    private final String mName;
+    private final IVirtualDevice mVirtualDevice;
+    private final IBinder mToken;
+
+    /**
+     * @hide
+     */
+    public VirtualSensor(int type, String name, IVirtualDevice virtualDevice, IBinder token) {
+        mType = type;
+        mName = name;
+        mVirtualDevice = virtualDevice;
+        mToken = token;
+    }
+
+    /**
+     * Returns the type of the sensor.
+     *
+     * @see Sensor#getType()
+     * @see <a href="https://source.android.com/devices/sensors/sensor-types">Sensor types</a>
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the name of the sensor.
+     */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Send a sensor event to the system.
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void sendEvent(@NonNull VirtualSensorEvent event) {
+        try {
+            mVirtualDevice.sendSensorEvent(mToken, event);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl
new file mode 100644
index 0000000..48b463a
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl
@@ -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.
+ */
+
+package android.companion.virtual.sensor;
+
+parcelable VirtualSensorConfig;
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
new file mode 100644
index 0000000..eb2f9dd
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -0,0 +1,212 @@
+/*
+ * 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.companion.virtual.sensor;
+
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.hardware.Sensor;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.time.Duration;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Configuration for creation of a virtual sensor.
+ * @see VirtualSensor
+ * @hide
+ */
+@SystemApi
+public final class VirtualSensorConfig implements Parcelable {
+
+    private final int mType;
+    @NonNull
+    private final String mName;
+    @Nullable
+    private final String mVendor;
+    @Nullable
+    private final IVirtualSensorStateChangeCallback mStateChangeCallback;
+
+    private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor,
+            @Nullable IVirtualSensorStateChangeCallback stateChangeCallback) {
+        mType = type;
+        mName = name;
+        mVendor = vendor;
+        mStateChangeCallback = stateChangeCallback;
+    }
+
+    private VirtualSensorConfig(@NonNull Parcel parcel) {
+        mType = parcel.readInt();
+        mName = parcel.readString8();
+        mVendor = parcel.readString8();
+        mStateChangeCallback =
+                IVirtualSensorStateChangeCallback.Stub.asInterface(parcel.readStrongBinder());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeInt(mType);
+        parcel.writeString8(mName);
+        parcel.writeString8(mVendor);
+        parcel.writeStrongBinder(
+                mStateChangeCallback != null ? mStateChangeCallback.asBinder() : null);
+    }
+
+    /**
+     * Returns the type of the sensor.
+     *
+     * @see Sensor#getType()
+     * @see <a href="https://source.android.com/devices/sensors/sensor-types">Sensor types</a>
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the name of the sensor, which must be unique per sensor type for each virtual device.
+     */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the vendor string of the sensor.
+     * @see Builder#setVendor
+     */
+    @Nullable
+    public String getVendor() {
+        return mVendor;
+    }
+
+    /**
+     * Returns the callback to get notified about changes in the sensor listeners.
+     * @hide
+     */
+    @Nullable
+    public IVirtualSensorStateChangeCallback getStateChangeCallback() {
+        return mStateChangeCallback;
+    }
+
+    /**
+     * Builder for {@link VirtualSensorConfig}.
+     */
+    public static final class Builder {
+
+        private final int mType;
+        @NonNull
+        private final String mName;
+        @Nullable
+        private String mVendor;
+        @Nullable
+        private IVirtualSensorStateChangeCallback mStateChangeCallback;
+
+        private static class SensorStateChangeCallbackDelegate
+                extends IVirtualSensorStateChangeCallback.Stub {
+            @NonNull
+            private final Executor mExecutor;
+            @NonNull
+            private final VirtualSensor.SensorStateChangeCallback mCallback;
+
+            SensorStateChangeCallbackDelegate(@NonNull @CallbackExecutor Executor executor,
+                    @NonNull VirtualSensor.SensorStateChangeCallback callback) {
+                mCallback = callback;
+                mExecutor = executor;
+            }
+            @Override
+            public void onStateChanged(boolean enabled, int samplingPeriodMicros,
+                    int batchReportLatencyMicros) {
+                final Duration samplingPeriod =
+                        Duration.ofNanos(MICROSECONDS.toNanos(samplingPeriodMicros));
+                final Duration batchReportingLatency =
+                        Duration.ofNanos(MICROSECONDS.toNanos(batchReportLatencyMicros));
+                mExecutor.execute(() -> mCallback.onStateChanged(
+                        enabled, samplingPeriod, batchReportingLatency));
+            }
+        }
+
+        /**
+         * Creates a new builder.
+         *
+         * @param type The type of the sensor, matching {@link Sensor#getType}.
+         * @param name The name of the sensor. Must be unique among all sensors with the same type
+         * that belong to the same virtual device.
+         */
+        public Builder(int type, @NonNull String name) {
+            mType = type;
+            mName = Objects.requireNonNull(name);
+        }
+
+        /**
+         * Creates a new {@link VirtualSensorConfig}.
+         */
+        @NonNull
+        public VirtualSensorConfig build() {
+            return new VirtualSensorConfig(mType, mName, mVendor, mStateChangeCallback);
+        }
+
+        /**
+         * Sets the vendor string of the sensor.
+         */
+        @NonNull
+        public VirtualSensorConfig.Builder setVendor(@Nullable String vendor) {
+            mVendor = vendor;
+            return this;
+        }
+
+        /**
+         * Sets the callback to get notified about changes in the sensor listeners.
+         *
+         * @param executor The executor where the callback is executed on.
+         * @param callback The callback to get notified when the state of the sensor
+         * listeners has changed, see {@link VirtualSensor.SensorStateChangeCallback}
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public VirtualSensorConfig.Builder setStateChangeCallback(
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull VirtualSensor.SensorStateChangeCallback callback) {
+            mStateChangeCallback = new SensorStateChangeCallbackDelegate(
+                    Objects.requireNonNull(executor),
+                    Objects.requireNonNull(callback));
+            return this;
+        }
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<VirtualSensorConfig> CREATOR =
+            new Parcelable.Creator<>() {
+                public VirtualSensorConfig createFromParcel(Parcel source) {
+                    return new VirtualSensorConfig(source);
+                }
+
+                public VirtualSensorConfig[] newArray(int size) {
+                    return new VirtualSensorConfig[size];
+                }
+            };
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl
new file mode 100644
index 0000000..9943946
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl
@@ -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.
+ */
+
+package android.companion.virtual.sensor;
+
+parcelable VirtualSensorEvent;
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
new file mode 100644
index 0000000..01b4975
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
@@ -0,0 +1,145 @@
+/*
+ * 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.companion.virtual.sensor;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.hardware.Sensor;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+
+
+/**
+ * A sensor event that originated from a virtual device's sensor.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualSensorEvent implements Parcelable {
+
+    @NonNull
+    private float[] mValues;
+    private long mTimestampNanos;
+
+    private VirtualSensorEvent(@NonNull float[] values, long timestampNanos) {
+        mValues = values;
+        mTimestampNanos = timestampNanos;
+    }
+
+    private VirtualSensorEvent(@NonNull Parcel parcel) {
+        final int valuesLength = parcel.readInt();
+        mValues = new float[valuesLength];
+        parcel.readFloatArray(mValues);
+        mTimestampNanos = parcel.readLong();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+        parcel.writeInt(mValues.length);
+        parcel.writeFloatArray(mValues);
+        parcel.writeLong(mTimestampNanos);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the values of this sensor event. The length and contents depend on the sensor type.
+     *
+     * @see android.hardware.SensorEvent#values
+     * @see Sensor#getType()
+     * @see <a href="https://source.android.com/devices/sensors/sensor-types">Sensor types</a>
+     */
+    @NonNull
+    public float[] getValues() {
+        return mValues;
+    }
+
+    /**
+     * The time in nanoseconds at which the event happened. For a given sensor, each new sensor
+     * event should be monotonically increasing.
+     *
+     * @see Builder#setTimestampNanos(long)
+     */
+    public long getTimestampNanos() {
+        return mTimestampNanos;
+    }
+
+    /**
+     * Builder for {@link VirtualSensorEvent}.
+     */
+    public static final class Builder {
+
+        @NonNull
+        private float[] mValues;
+        private long mTimestampNanos = 0;
+
+        /**
+         * Creates a new builder.
+         *
+         * @param values the values of the sensor event.
+         * @see android.hardware.SensorEvent#values
+         */
+        public Builder(@NonNull float[] values) {
+            mValues = values;
+        }
+
+        /**
+         * Creates a new {@link VirtualSensorEvent}.
+         */
+        @NonNull
+        public VirtualSensorEvent build() {
+            if (mValues == null || mValues.length == 0) {
+                throw new IllegalArgumentException(
+                        "Cannot build virtual sensor event with no values.");
+            }
+            if (mTimestampNanos <= 0) {
+                mTimestampNanos = SystemClock.elapsedRealtimeNanos();
+            }
+            return new VirtualSensorEvent(mValues, mTimestampNanos);
+        }
+
+        /**
+         * Sets the timestamp of this event. For a given sensor, each new sensor event should be
+         * monotonically increasing using the same time base as
+         * {@link android.os.SystemClock#elapsedRealtimeNanos()}.
+         *
+         * If not explicitly set, the current timestamp is used for the sensor event.
+         *
+         * @see android.hardware.SensorEvent#timestamp
+         */
+        @NonNull
+        public Builder setTimestampNanos(long timestampNanos) {
+            mTimestampNanos = timestampNanos;
+            return this;
+        }
+    }
+
+    public static final @NonNull Parcelable.Creator<VirtualSensorEvent> CREATOR =
+            new Parcelable.Creator<>() {
+                public VirtualSensorEvent createFromParcel(Parcel source) {
+                    return new VirtualSensorEvent(source);
+                }
+
+                public VirtualSensorEvent[] newArray(int size) {
+                    return new VirtualSensorEvent[size];
+                }
+            };
+}
diff --git a/core/java/android/content/ActivityNotFoundException.java b/core/java/android/content/ActivityNotFoundException.java
index 16149bb..5b50189 100644
--- a/core/java/android/content/ActivityNotFoundException.java
+++ b/core/java/android/content/ActivityNotFoundException.java
@@ -31,5 +31,4 @@
     {
         super(name);
     }
-};
-
+}
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 1df0fa8..9f9fd3c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3826,7 +3826,6 @@
             STORAGE_SERVICE,
             STORAGE_STATS_SERVICE,
             WALLPAPER_SERVICE,
-            TIME_ZONE_RULES_MANAGER_SERVICE,
             VIBRATOR_MANAGER_SERVICE,
             VIBRATOR_SERVICE,
             //@hide: STATUS_BAR_SERVICE,
@@ -3851,6 +3850,7 @@
             WIFI_RTT_RANGING_SERVICE,
             NSD_SERVICE,
             AUDIO_SERVICE,
+            AUDIO_DEVICE_VOLUME_SERVICE,
             AUTH_SERVICE,
             FINGERPRINT_SERVICE,
             //@hide: FACE_SERVICE,
@@ -3939,6 +3939,7 @@
             DISPLAY_HASH_SERVICE,
             CREDENTIAL_SERVICE,
             DEVICE_LOCK_SERVICE,
+            VIRTUALIZATION_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -4694,6 +4695,18 @@
     public static final String AUDIO_SERVICE = "audio";
 
     /**
+     * @hide
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.media.AudioDeviceVolumeManager} for handling management of audio device
+     * (e.g. speaker, USB headset) volume.
+     *
+     * @see #getSystemService(String)
+     * @see android.media.AudioDeviceVolumeManager
+     */
+    @SystemApi
+    public static final String AUDIO_DEVICE_VOLUME_SERVICE = "audio_device_volume";
+
+    /**
      * Use with {@link #getSystemService(String)} to retrieve a {@link
      * android.media.MediaTranscodingManager} for transcoding media.
      *
@@ -4917,6 +4930,7 @@
      * @see android.server.attention.AttentionManagerService
      * @hide
      */
+    @TestApi
     public static final String ATTENTION_SERVICE = "attention";
 
     /**
@@ -5771,15 +5785,6 @@
     public static final String VR_SERVICE = "vrmanager";
 
     /**
-     * Use with {@link #getSystemService(String)} to retrieve an
-     * {@link android.app.timezone.ITimeZoneRulesManager}.
-     * @hide
-     *
-     * @see #getSystemService(String)
-     */
-    public static final String TIME_ZONE_RULES_MANAGER_SERVICE = "timezone";
-
-    /**
      * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.content.pm.CrossProfileApps} for cross profile operations.
      *
@@ -6074,6 +6079,17 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.app.wearable.WearableSensingManager}.
+     *
+     * @see #getSystemService(String)
+     * @see WearableSensingManager
+     * @hide
+     */
+    @SystemApi
+    public static final String WEARABLE_SENSING_SERVICE = "wearable_sensing";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.healthconnect.HealthConnectManager}.
      *
      * @see #getSystemService(String)
@@ -6099,6 +6115,20 @@
     public static final String DEVICE_LOCK_SERVICE = "device_lock";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.system.virtualmachine.VirtualMachineManager}.
+     *
+     * <p>On devices without {@link PackageManager#FEATURE_VIRTUALIZATION_FRAMEWORK} system feature
+     * the {@link #getSystemService(String)} will return {@code null}.
+     *
+     * @see #getSystemService(String)
+     * @see android.system.virtualmachine.VirtualMachineManager
+     * @hide
+     */
+    @SystemApi
+    public static final String VIRTUALIZATION_SERVICE = "virtualization";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
@@ -6171,7 +6201,7 @@
      */
     @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)")
     @PackageManager.PermissionResult
-    @PermissionMethod
+    @PermissionMethod(orSelf = true)
     public abstract int checkCallingOrSelfPermission(@NonNull @PermissionName String permission);
 
     /**
@@ -6239,7 +6269,7 @@
      *
      * @see #checkCallingOrSelfPermission(String)
      */
-    @PermissionMethod
+    @PermissionMethod(orSelf = true)
     public abstract void enforceCallingOrSelfPermission(
             @NonNull @PermissionName String permission, @Nullable String message);
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index f2ebec6..86a672f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3561,6 +3561,17 @@
     public static final String ACTION_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON";
 
     /**
+     * Broadcast action: Launch System output switcher. Includes a single extra field,
+     * {@link #EXTRA_PACKAGE_NAME}, which specifies the package name of the calling app
+     * so that the system can get the corresponding MediaSession for the output switcher.
+     *
+     * @see #EXTRA_PACKAGE_NAME
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SHOW_OUTPUT_SWITCHER =
+            "android.intent.action.SHOW_OUTPUT_SWITCHER";
+
+    /**
      * Broadcast Action:  The "Camera Button" was pressed.  Includes a single
      * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
      * caused the broadcast.
@@ -3878,7 +3889,7 @@
             "android.intent.action.USER_INITIALIZE";
 
     /**
-     * Sent when a user switch is happening, causing the process's user to be
+     * Sent after a user switch is complete, if the switch caused the process's user to be
      * brought to the foreground.  This is only sent to receivers registered
      * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
      * Context.registerReceiver}.  It is sent to the user that is going to the
@@ -3890,7 +3901,7 @@
             "android.intent.action.USER_FOREGROUND";
 
     /**
-     * Sent when a user switch is happening, causing the process's user to be
+     * Sent after a user switch is complete, if the switch caused the process's user to be
      * sent to the background.  This is only sent to receivers registered
      * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
      * Context.registerReceiver}.  It is sent to the user that is going to the
@@ -4031,6 +4042,11 @@
      * the current state of the user.
      * @hide
      */
+    /*
+     * This broadcast is sent after the user switch is complete. In case a task needs to be done
+     * while the switch is happening (i.e. while the screen is frozen to hide UI jank), please use
+     * ActivityManagerService.registerUserSwitchObserver method.
+     */
     @SystemApi
     public static final String ACTION_USER_SWITCHED =
             "android.intent.action.USER_SWITCHED";
@@ -5091,6 +5107,21 @@
     public static final String ACTION_VIEW_LOCUS = "android.intent.action.VIEW_LOCUS";
 
     /**
+     * Activity Action: Starts a note-taking activity that can be used to create a note. This action
+     * can be used to start an activity on the lock screen. Activity should ensure to appropriately
+     * handle privacy sensitive data and features when launched on the lock screen. See
+     * {@link android.app.KeyguardManager} for lock screen checks.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE";
+
+    /**
+     * A boolean extra used with {@link #ACTION_CREATE_NOTE} indicating whether the launched
+     * note-taking activity should show a UI that is suitable to use with stylus input.
+     */
+    public static final String EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE";
+
+    /**
      * Broadcast Action: Sent to the integrity component when a package
      * needs to be verified. The data contains the package URI along with other relevant
      * information.
@@ -6093,6 +6124,14 @@
     public static final String EXTRA_REPLACING = "android.intent.extra.REPLACING";
 
     /**
+     * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
+     * intents to indicate that this is a system update uninstall.
+     * @hide
+     */
+    public static final String EXTRA_SYSTEM_UPDATE_UNINSTALL =
+            "android.intent.extra.SYSTEM_UPDATE_UNINSTALL";
+
+    /**
      * Used as an int extra field in {@link android.app.AlarmManager} pending intents
      * to tell the application being invoked how many pending alarms are being
      * delivered with the intent.  For one-shot alarms this will always be 1.
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index b3435b1..bff27d3 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -23,11 +23,14 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.net.Uri;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PatternMatcher;
+import android.os.PersistableBundle;
 import android.text.TextUtils;
 import android.util.AndroidException;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Printer;
 import android.util.proto.ProtoOutputStream;
@@ -170,6 +173,13 @@
     private static final String NAME_STR = "name";
     private static final String ACTION_STR = "action";
     private static final String AUTO_VERIFY_STR = "autoVerify";
+    private static final String EXTRAS_STR = "extras";
+
+    private static final int[] EMPTY_INT_ARRAY = new int[0];
+    private static final long[] EMPTY_LONG_ARRAY = new long[0];
+    private static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
+    private static final String[] EMPTY_STRING_ARRAY = new String[0];
+    private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
 
     /**
      * The filter {@link #setPriority} value at which system high-priority
@@ -266,6 +276,11 @@
      * that were not in the Intent.
      */
     public static final int NO_MATCH_CATEGORY = -4;
+    /**
+     * That filter didn't match due to different extras data.
+     * @hide
+     */
+    public static final int NO_MATCH_EXTRAS = -5;
 
     /**
      * HTTP scheme.
@@ -302,7 +317,7 @@
     @UnsupportedAppUsage
     private int mOrder;
     @UnsupportedAppUsage
-    private final ArrayList<String> mActions;
+    private final ArraySet<String> mActions;
     private ArrayList<String> mCategories = null;
     private ArrayList<String> mDataSchemes = null;
     private ArrayList<PatternMatcher> mDataSchemeSpecificParts = null;
@@ -313,6 +328,7 @@
     private ArrayList<String> mMimeGroups = null;
     private boolean mHasStaticPartialTypes = false;
     private boolean mHasDynamicPartialTypes = false;
+    private PersistableBundle mExtras = null;
 
     private static final int STATE_VERIFY_AUTO         = 0x00000001;
     private static final int STATE_NEED_VERIFY         = 0x00000010;
@@ -433,7 +449,7 @@
      */
     public IntentFilter() {
         mPriority = 0;
-        mActions = new ArrayList<String>();
+        mActions = new ArraySet<>();
     }
 
     /**
@@ -445,7 +461,7 @@
      */
     public IntentFilter(String action) {
         mPriority = 0;
-        mActions = new ArrayList<String>();
+        mActions = new ArraySet<>();
         addAction(action);
     }
 
@@ -468,7 +484,7 @@
     public IntentFilter(String action, String dataType)
         throws MalformedMimeTypeException {
         mPriority = 0;
-        mActions = new ArrayList<String>();
+        mActions = new ArraySet<>();
         addAction(action);
         addDataType(dataType);
     }
@@ -481,7 +497,7 @@
     public IntentFilter(IntentFilter o) {
         mPriority = o.mPriority;
         mOrder = o.mOrder;
-        mActions = new ArrayList<String>(o.mActions);
+        mActions = new ArraySet<>(o.mActions);
         if (o.mCategories != null) {
             mCategories = new ArrayList<String>(o.mCategories);
         }
@@ -506,6 +522,9 @@
         if (o.mMimeGroups != null) {
             mMimeGroups = new ArrayList<String>(o.mMimeGroups);
         }
+        if (o.mExtras != null) {
+            mExtras = new PersistableBundle(o.mExtras);
+        }
         mHasStaticPartialTypes = o.mHasStaticPartialTypes;
         mHasDynamicPartialTypes = o.mHasDynamicPartialTypes;
         mVerifyState = o.mVerifyState;
@@ -742,9 +761,7 @@
      * @param action Name of the action to match, such as Intent.ACTION_VIEW.
      */
     public final void addAction(String action) {
-        if (!mActions.contains(action)) {
-            mActions.add(action.intern());
-        }
+        mActions.add(action.intern());
     }
 
     /**
@@ -758,7 +775,7 @@
      * Return an action in the filter.
      */
     public final String getAction(int index) {
-        return mActions.get(index);
+        return mActions.valueAt(index);
     }
 
     /**
@@ -797,8 +814,11 @@
             if (ignoreActions == null) {
                 return !mActions.isEmpty();
             }
+            if (mActions.size() > ignoreActions.size()) {
+                return true;    // some actions are definitely not ignored
+            }
             for (int i = mActions.size() - 1; i >= 0; i--) {
-                if (!ignoreActions.contains(mActions.get(i))) {
+                if (!ignoreActions.contains(mActions.valueAt(i))) {
                     return true;
                 }
             }
@@ -1766,6 +1786,423 @@
     }
 
     /**
+     * Match this filter against an Intent's extras. An intent must
+     * have all the extras specified by the filter with the same values,
+     * for the match to succeed.
+     *
+     * <p> An intent con have other extras in addition to those specified
+     * by the filter and it would not affect whether the match succeeds or not.
+     *
+     * @param extras The extras included in the intent, as returned by
+     *               Intent.getExtras().
+     *
+     * @return If all extras match (success), null; else the name of the
+     *         first extra that didn't match.
+     *
+     * @hide
+     */
+    private String matchExtras(@Nullable Bundle extras) {
+        if (mExtras == null) {
+            return null;
+        }
+        final Set<String> keys = mExtras.keySet();
+        for (String key : keys) {
+            if (extras == null) {
+                return key;
+            }
+            final Object value = mExtras.get(key);
+            final Object otherValue = extras.get(key);
+            if (otherValue == null || value.getClass() != otherValue.getClass()
+                    || !Objects.deepEquals(value, otherValue)) {
+                return key;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, int value) {
+        Objects.requireNonNull(name);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putInt(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, int)} or
+     *         {@code 0} if no value has been set.
+     * @hide
+     */
+    public final int getIntExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? 0 : mExtras.getInt(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, @NonNull int[] value) {
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(value);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putIntArray(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, int[])} or
+     *         an empty int array if no value has been set.
+     * @hide
+     */
+    @NonNull
+    public final int[] getIntArrayExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? EMPTY_INT_ARRAY : mExtras.getIntArray(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, long value) {
+        Objects.requireNonNull(name);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putLong(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, long)} or
+     *         {@code 0L} if no value has been set.
+     * @hide
+     */
+    public final long getLongExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? 0L : mExtras.getLong(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, @NonNull long[] value) {
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(value);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putLongArray(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, long[])} or
+     *         an empty long array if no value has been set.
+     * @hide
+     */
+    @NonNull
+    public final long[] getLongArrayExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? EMPTY_LONG_ARRAY : mExtras.getLongArray(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, double value) {
+        Objects.requireNonNull(name);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putDouble(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, double)} or
+     *         {@code 0.0} if no value has been set.
+     * @hide
+     */
+    @NonNull
+    public final double getDoubleExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? 0.0 : mExtras.getDouble(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, @NonNull double[] value) {
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(value);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putDoubleArray(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, double[])} or
+     *         an empty double array if no value has been set.
+     * @hide
+     */
+    @NonNull
+    public final double[] getDoubleArrayExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? EMPTY_DOUBLE_ARRAY : mExtras.getDoubleArray(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, @NonNull String value) {
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(value);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putString(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, String)} or a
+     *         {@code null} if no value has been set.
+     * @hide
+     */
+    @Nullable
+    public final String getStringExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? null : mExtras.getString(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, @NonNull String[] value) {
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(value);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putStringArray(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, String[])} or
+     *         an empty string array if no value has been set.
+     * @hide
+     */
+    @NonNull
+    public final String[] getStringArrayExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? EMPTY_STRING_ARRAY : mExtras.getStringArray(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, boolean value) {
+        Objects.requireNonNull(name);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putBoolean(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, boolean)} or
+     *         {@code false} if no value has been set.
+     * @hide
+     */
+    public final boolean getBooleanExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? false : mExtras.getBoolean(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, @NonNull boolean[] value) {
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(value);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putBooleanArray(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, boolean[])} or
+     *         an empty boolean array if no value has been set.
+     * @hide
+     */
+    @NonNull
+    public final boolean[] getBooleanArrayExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? EMPTY_BOOLEAN_ARRAY : mExtras.getBooleanArray(name);
+    }
+
+    /**
+     * Returns whether or not an extra with {@code name} is included in the filter.
+     *
+     * @return {@code true} if an extra with {@code name} is included in the filter.
+     *         Otherwise {@code false}.
+     * @hide
+     */
+    public final boolean hasExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? false : mExtras.containsKey(name);
+    }
+
+    /**
+     * Set the intent extras to match against. For this filter to match an
+     * intent, it must contain all the {@code extras} set.
+     *
+     * <p> Subsequent calls to this method  will override any previously set extras.
+     *
+     * @param extras The intent extras to match against.
+     * @hide
+     */
+    @SystemApi
+    public final void setExtras(@NonNull PersistableBundle extras) {
+        mExtras = extras;
+    }
+
+    /**
+     * Return the intent extras included in this filter.
+     *
+     * @return the extras that were previously set using {@link #setExtras(PersistableBundle)} or
+     *         an empty {@link PersistableBundle} object if no extras were set.
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public final PersistableBundle getExtras() {
+        return mExtras == null ? new PersistableBundle() : mExtras;
+    }
+
+    /**
      * Return a {@link Predicate} which tests whether this filter matches the
      * given <var>intent</var>.
      * <p>
@@ -1816,7 +2253,9 @@
             boolean resolve, String logTag) {
         String type = resolve ? intent.resolveType(resolver) : intent.getType();
         return match(intent.getAction(), type, intent.getScheme(),
-                     intent.getData(), intent.getCategories(), logTag);
+                     intent.getData(), intent.getCategories(), logTag,
+                     false /* supportWildcards */, null /* ignoreActions */,
+                     intent.getExtras());
     }
 
     /**
@@ -1866,6 +2305,19 @@
     public final int match(String action, String type, String scheme,
             Uri data, Set<String> categories, String logTag, boolean supportWildcards,
             @Nullable Collection<String> ignoreActions) {
+        return match(action, type, scheme, data, categories, logTag, supportWildcards,
+                ignoreActions, null /* extras */);
+    }
+
+    /**
+     * Variant of {@link #match(String, String, String, Uri, Set, String, boolean, Collection)}
+     * that supports matching the extra values in the intent.
+     *
+     * @hide
+     */
+    public final int match(String action, String type, String scheme,
+            Uri data, Set<String> categories, String logTag, boolean supportWildcards,
+            @Nullable Collection<String> ignoreActions, @Nullable Bundle extras) {
         if (action != null && !matchAction(action, supportWildcards, ignoreActions)) {
             if (false) Log.v(
                 logTag, "No matching action " + action + " for " + this);
@@ -1895,6 +2347,14 @@
             return NO_MATCH_CATEGORY;
         }
 
+        String extraMismatch = matchExtras(extras);
+        if (extraMismatch != null) {
+            if (false) {
+                Log.v(logTag, "Mismatch for extra key " + extraMismatch + " for " + this);
+            }
+            return NO_MATCH_EXTRAS;
+        }
+
         // It would be nice to treat container activities as more
         // important than ones that can be embedded, but this is not the way...
         if (false) {
@@ -1918,7 +2378,7 @@
         int N = countActions();
         for (int i=0; i<N; i++) {
             serializer.startTag(null, ACTION_STR);
-            serializer.attribute(null, NAME_STR, mActions.get(i));
+            serializer.attribute(null, NAME_STR, mActions.valueAt(i));
             serializer.endTag(null, ACTION_STR);
         }
         N = countCategories();
@@ -1996,6 +2456,15 @@
             }
             serializer.endTag(null, PATH_STR);
         }
+        if (mExtras != null) {
+            serializer.startTag(null, EXTRAS_STR);
+            try {
+                mExtras.saveToXml(serializer);
+            } catch (XmlPullParserException e) {
+                throw new IllegalStateException("Failed to write extras: " + mExtras.toString(), e);
+            }
+            serializer.endTag(null, EXTRAS_STR);
+        }
     }
 
     /**
@@ -2122,6 +2591,8 @@
                 } else if ((path=parser.getAttributeValue(null, SUFFIX_STR)) != null) {
                     addDataPath(path, PatternMatcher.PATTERN_SUFFIX);
                 }
+            } else if (tagName.equals(EXTRAS_STR)) {
+                mExtras = PersistableBundle.restoreFromXml(parser);
             } else {
                 Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName);
             }
@@ -2185,6 +2656,9 @@
             proto.write(IntentFilterProto.HAS_PARTIAL_TYPES, hasPartialTypes());
         }
         proto.write(IntentFilterProto.GET_AUTO_VERIFY, getAutoVerify());
+        if (mExtras != null) {
+            mExtras.dumpDebug(proto, IntentFilterProto.EXTRAS);
+        }
         proto.end(token);
     }
 
@@ -2295,6 +2769,11 @@
             sb.append(prefix); sb.append("AutoVerify="); sb.append(getAutoVerify());
             du.println(sb.toString());
         }
+        if (mExtras != null) {
+            sb.setLength(0);
+            sb.append(prefix); sb.append("mExtras="); sb.append(mExtras.toString());
+            du.println(sb.toString());
+        }
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<IntentFilter> CREATOR
@@ -2313,7 +2792,7 @@
     }
 
     public final void writeToParcel(Parcel dest, int flags) {
-        dest.writeStringList(mActions);
+        dest.writeStringArray(mActions.toArray(new String[mActions.size()]));
         if (mCategories != null) {
             dest.writeInt(1);
             dest.writeStringList(mCategories);
@@ -2377,6 +2856,12 @@
         dest.writeInt(getAutoVerify() ? 1 : 0);
         dest.writeInt(mInstantAppVisibility);
         dest.writeInt(mOrder);
+        if (mExtras != null) {
+            dest.writeInt(1);
+            mExtras.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
     }
 
     /**
@@ -2407,8 +2892,9 @@
 
     /** @hide */
     public IntentFilter(Parcel source) {
-        mActions = new ArrayList<String>();
-        source.readStringList(mActions);
+        List<String> actions = new ArrayList<>();
+        source.readStringList(actions);
+        mActions = new ArraySet<>(actions);
         if (source.readInt() != 0) {
             mCategories = new ArrayList<String>();
             source.readStringList(mCategories);
@@ -2456,6 +2942,9 @@
         setAutoVerify(source.readInt() > 0);
         setVisibilityToInstantApp(source.readInt());
         mOrder = source.readInt();
+        if (source.readInt() != 0) {
+            mExtras = PersistableBundle.CREATOR.createFromParcel(source);
+        }
     }
 
     private boolean hasPartialTypes() {
diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java
index f888813..1b5f64c 100644
--- a/core/java/android/content/integrity/AtomicFormula.java
+++ b/core/java/android/content/integrity/AtomicFormula.java
@@ -261,8 +261,8 @@
             }
             LongAtomicFormula that = (LongAtomicFormula) o;
             return getKey() == that.getKey()
-                    && mValue == that.mValue
-                    && mOperator == that.mOperator;
+                    && Objects.equals(mValue, that.mValue)
+                    && Objects.equals(mOperator, that.mOperator);
         }
 
         @Override
@@ -628,7 +628,7 @@
                 return false;
             }
             BooleanAtomicFormula that = (BooleanAtomicFormula) o;
-            return getKey() == that.getKey() && mValue == that.mValue;
+            return getKey() == that.getKey() && Objects.equals(mValue, that.mValue);
         }
 
         @Override
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index 3ca0560..99fc5a3 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -16,15 +16,23 @@
 
 package android.content.om;
 
+import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.FabricatedOverlayInternal;
 import android.os.FabricatedOverlayInternalEntry;
+import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
+import android.util.TypedValue;
 
+import com.android.internal.content.om.OverlayManagerImpl;
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Objects;
 
 /**
  * Fabricated Runtime Resource Overlays (FRROs) are overlays generated ar runtime.
@@ -80,49 +88,155 @@
         }
 
         /**
+         * Constructs a builder for building a fabricated overlay.
+         *
+         * @param name a name used to uniquely identify the fabricated overlay owned by the caller
+         *             itself.
+         * @param targetPackage the name of the package to overlay
+         */
+        public Builder(@NonNull String name, @NonNull String targetPackage) {
+            mName = OverlayManagerImpl.checkOverlayNameValid(name);
+            mTargetPackage =
+                    Preconditions.checkStringNotEmpty(
+                            targetPackage, "'targetPackage' must not be empty nor null");
+            mOwningPackage = ""; // The package name is filled in OverlayManager.commit
+        }
+
+        /**
          * Sets the name of the overlayable resources to overlay (can be null).
          */
+        @NonNull
         public Builder setTargetOverlayable(@Nullable String targetOverlayable) {
             mTargetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
             return this;
         }
 
         /**
-         * Sets the value of the fabricated overlay
+         * Ensure the resource name is in the form [package]:type/entry.
+         *
+         * @param name name of the target resource to overlay (in the form [package]:type/entry)
+         * @return the valid name
+         */
+        private static String ensureValidResourceName(@NonNull String name) {
+            Objects.requireNonNull(name);
+            final int slashIndex = name.indexOf('/'); /* must contain '/' */
+            final int colonIndex = name.indexOf(':'); /* ':' should before '/' if ':' exist */
+
+            // The minimum length of resource type is "id".
+            Preconditions.checkArgument(
+                    slashIndex >= 0 /* It must contain the type name */
+                    && colonIndex != 0 /* 0 means the package name is empty */
+                    && (slashIndex - colonIndex) > 2 /* The shortest length of type is "id" */,
+                    "\"%s\" is invalid resource name",
+                    name);
+            return name;
+        }
+
+        /**
+         * Sets the value of the fabricated overlay for the integer-like types.
          *
          * @param resourceName name of the target resource to overlay (in the form
-         *                     [package]:type/entry)
+         *     [package]:type/entry)
          * @param dataType the data type of the new value
          * @param value the unsigned 32 bit integer representing the new value
-         *
+         * @return the builder itself
+         * @see #setResourceValue(String, int, int, String)
          * @see android.util.TypedValue#type
          */
-        public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT)
+                        int dataType,
+                int value) {
+            return setResourceValue(resourceName, dataType, value, null /* configuration */);
+        }
+
+        /**
+         * Sets the value of the fabricated overlay for the integer-like types with the
+         * configuration.
+         *
+         * @param resourceName name of the target resource to overlay (in the form
+         *     [package]:type/entry)
+         * @param dataType the data type of the new value
+         * @param value the unsigned 32 bit integer representing the new value
+         * @param configuration The string representation of the config this overlay is enabled for
+         * @see android.util.TypedValue#type
+         */
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT)
+                        int dataType,
+                int value,
+                @Nullable String configuration) {
+            ensureValidResourceName(resourceName);
+
             final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
             entry.resourceName = resourceName;
-            entry.dataType = dataType;
+            entry.dataType =
+                    Preconditions.checkArgumentInRange(
+                            dataType,
+                            TypedValue.TYPE_FIRST_INT,
+                            TypedValue.TYPE_LAST_INT,
+                            "dataType");
             entry.data = value;
+            entry.configuration = configuration;
             mEntries.add(entry);
             return this;
         }
 
+        /** @hide */
+        @IntDef(
+                prefix = {"OVERLAY_TYPE"},
+                value = {
+                    TypedValue.TYPE_STRING,
+                })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface StringTypeOverlayResource {}
+
         /**
-         * Sets the value of the fabricated overlay
+         * Sets the value of the fabricated overlay for the string-like type.
          *
          * @param resourceName name of the target resource to overlay (in the form
-         *                     [package]:type/entry)
+         *     [package]:type/entry)
          * @param dataType the data type of the new value
-         * @param value the unsigned 32 bit integer representing the new value
-         * @param configuration The string representation of the config this overlay is enabled for
-         *
+         * @param value the string representing the new value
+         * @return the builder itself
          * @see android.util.TypedValue#type
          */
-        public Builder setResourceValue(@NonNull String resourceName, int dataType, int value,
-                String configuration) {
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @StringTypeOverlayResource int dataType,
+                @NonNull String value) {
+            return setResourceValue(resourceName, dataType, value, null /* configuration */);
+        }
+
+        /**
+         * Sets the value of the fabricated overlay for the string-like type with the configuration.
+         *
+         * @param resourceName name of the target resource to overlay (in the form
+         *     [package]:type/entry)
+         * @param dataType the data type of the new value
+         * @param value the string representing the new value
+         * @param configuration The string representation of the config this overlay is enabled for
+         * @see android.util.TypedValue#type
+         */
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @StringTypeOverlayResource int dataType,
+                @NonNull String value,
+                @Nullable String configuration) {
+            ensureValidResourceName(resourceName);
+
             final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
             entry.resourceName = resourceName;
-            entry.dataType = dataType;
-            entry.data = value;
+            entry.dataType =
+                    Preconditions.checkArgumentInRange(
+                            dataType, TypedValue.TYPE_STRING, TypedValue.TYPE_FRACTION, "dataType");
+            entry.stringData = Objects.requireNonNull(value);
             entry.configuration = configuration;
             mEntries.add(entry);
             return this;
@@ -132,44 +246,32 @@
          * Sets the value of the fabricated overlay
          *
          * @param resourceName name of the target resource to overlay (in the form
-         *                     [package]:type/entry)
-         * @param dataType the data type of the new value
-         * @param value the string representing the new value
-         *
-         * @see android.util.TypedValue#type
-         */
-        public Builder setResourceValue(@NonNull String resourceName, int dataType, String value) {
-            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
-            entry.resourceName = resourceName;
-            entry.dataType = dataType;
-            entry.stringData = value;
-            mEntries.add(entry);
-            return this;
-        }
-
-        /**
-         * Sets the value of the fabricated overlay
-         *
-         * @param resourceName name of the target resource to overlay (in the form
-         *                     [package]:type/entry)
-         * @param dataType the data type of the new value
-         * @param value the string representing the new value
+         *     [package]:type/entry)
+         * @param value the file descriptor whose contents are the value of the frro
          * @param configuration The string representation of the config this overlay is enabled for
-         *
-         * @see android.util.TypedValue#type
+         * @return the builder itself
          */
-        public Builder setResourceValue(@NonNull String resourceName, int dataType, String value,
-                String configuration) {
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @NonNull ParcelFileDescriptor value,
+                @Nullable String configuration) {
+            ensureValidResourceName(resourceName);
+
             final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
             entry.resourceName = resourceName;
-            entry.dataType = dataType;
-            entry.stringData = value;
+            entry.binaryData = Objects.requireNonNull(value);
             entry.configuration = configuration;
             mEntries.add(entry);
             return this;
         }
 
-        /** Builds an immutable fabricated overlay. */
+        /**
+         * Builds an immutable fabricated overlay.
+         *
+         * @return the fabricated overlay
+         */
+        @NonNull
         public FabricatedOverlay build() {
             final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
             overlay.packageName = mOwningPackage;
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index c66f49c..a470de2 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -51,6 +51,7 @@
             STATE_ENABLED_IMMUTABLE,
             // @Deprecated STATE_TARGET_IS_BEING_REPLACED,
             STATE_OVERLAY_IS_BEING_REPLACED,
+            STATE_SYSTEM_UPDATE_UNINSTALL,
     })
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -128,6 +129,14 @@
     public static final int STATE_ENABLED_IMMUTABLE = 6;
 
     /**
+     * The target package needs to be refreshed as a result of a system update uninstall, which
+     * must recalculate the state of overlays against the newly enabled system package, which may
+     * differ in resources/policy from the /data variant that was uninstalled.
+     * @hide
+     */
+    public static final int STATE_SYSTEM_UPDATE_UNINSTALL = 7;
+
+    /**
      * Overlay category: theme.
      * <p>
      * Change how Android (including the status bar, dialogs, ...) looks.
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 94275ae..ed1f6a2 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -17,6 +17,7 @@
 package android.content.om;
 
 import android.annotation.NonNull;
+import android.annotation.NonUiContext;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -25,12 +26,16 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 
+import com.android.internal.content.om.OverlayManagerImpl;
+
+import java.io.IOException;
 import java.util.List;
 
 /**
@@ -76,6 +81,7 @@
 
     private final IOverlayManager mService;
     private final Context mContext;
+    private final OverlayManagerImpl mOverlayManagerImpl;
 
     /**
      * Pre R a {@link java.lang.SecurityException} would only be thrown by setEnabled APIs (e
@@ -92,6 +98,21 @@
     private static final long THROW_SECURITY_EXCEPTIONS = 147340954;
 
     /**
+     * Applications can use OverlayManager to create overlays to overlay on itself resources. The
+     * overlay target is itself and the work range is only in caller application.
+     *
+     * <p>In {@link android.content.Context#getSystemService(String)}, it crashes because of {@link
+     * java.lang.NullPointerException} if the parameter is OverlayManager. if the self-targeting is
+     * enabled, the caller application can get the OverlayManager instance to use self-targeting
+     * functionality.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final long SELF_TARGETING_OVERLAY = 205919743;
+
+    /**
      * Creates a new instance.
      *
      * @param context The current context in which to operate.
@@ -102,6 +123,7 @@
     public OverlayManager(Context context, IOverlayManager service) {
         mContext = context;
         mService = service;
+        mOverlayManagerImpl = new OverlayManagerImpl(context);
     }
 
     /** @hide */
@@ -286,6 +308,17 @@
      * @hide
      */
     public void commit(@NonNull final OverlayManagerTransaction transaction) {
+        if (transaction.isSelfTargetingTransaction()
+                || mService == null
+                || mService.asBinder() == null) {
+            try {
+                commitSelfTarget(transaction);
+            } catch (PackageManager.NameNotFoundException | IOException e) {
+                throw new RuntimeException(e);
+            }
+            return;
+        }
+
         try {
             mService.commit(transaction);
         } catch (RemoteException e) {
@@ -317,4 +350,48 @@
             throw e;
         }
     }
+
+    /**
+     * Get a OverlayManagerTransaction.Builder to build out a overlay manager transaction.
+     *
+     * @return a builder of the overlay manager transaction.
+     * @hide
+     */
+    @NonNull
+    public OverlayManagerTransaction.Builder beginTransaction() {
+        return new OverlayManagerTransaction.Builder(this);
+    }
+
+    /**
+     * Commit the self-targeting transaction to register or unregister overlays.
+     *
+     * <p>Applications can request OverlayManager to register overlays and unregister the registered
+     * overlays via {@link OverlayManagerTransaction}.
+     *
+     * @throws IOException if there is a file operation error.
+     * @throws PackageManager.NameNotFoundException if the package name is not found.
+     * @hide
+     */
+    @NonUiContext
+    void commitSelfTarget(@NonNull final OverlayManagerTransaction transaction)
+            throws PackageManager.NameNotFoundException, IOException {
+        synchronized (mOverlayManagerImpl) {
+            mOverlayManagerImpl.commit(transaction);
+        }
+    }
+
+    /**
+     * Get the related information of overlays for {@code targetPackageName}.
+     *
+     * @param targetPackageName the target package name
+     * @return a list of overlay information
+     * @hide
+     */
+    @NonNull
+    @NonUiContext
+    public List<OverlayInfo> getOverlayInfosForTarget(@NonNull final String targetPackageName) {
+        synchronized (mOverlayManagerImpl) {
+            return mOverlayManagerImpl.getOverlayInfosForTarget(targetPackageName);
+        }
+    }
 }
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index 868dab2..42b3ef3 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -20,19 +20,22 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.NonUiContext;
 import android.annotation.Nullable;
-import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
 
+import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 
 /**
  * Container for a batch of requests to the OverlayManagerService.
@@ -53,13 +56,16 @@
     // TODO: remove @hide from this class when OverlayManager is added to the
     // SDK, but keep OverlayManagerTransaction.Request @hidden
     private final List<Request> mRequests;
+    private final OverlayManager mOverlayManager;
 
-    OverlayManagerTransaction(@NonNull final List<Request> requests) {
+    OverlayManagerTransaction(
+            @NonNull final List<Request> requests, @Nullable OverlayManager overlayManager) {
         checkNotNull(requests);
         if (requests.contains(null)) {
             throw new IllegalArgumentException("null request");
         }
         mRequests = requests;
+        mOverlayManager = overlayManager;
     }
 
     private OverlayManagerTransaction(@NonNull final Parcel source) {
@@ -72,6 +78,7 @@
             final Bundle extras = source.readBundle(null);
             mRequests.add(new Request(request, overlay, userId, extras));
         }
+        mOverlayManager = null;
     }
 
     @Override
@@ -156,6 +163,20 @@
      */
     public static class Builder {
         private final List<Request> mRequests = new ArrayList<>();
+        @Nullable private final OverlayManager mOverlayManager;
+
+        public Builder() {
+            mOverlayManager = null;
+        }
+
+        /**
+         * The transaction builder for self-targeting.
+         *
+         * @param overlayManager is not null if the transaction is for self-targeting.
+         */
+        Builder(@NonNull OverlayManager overlayManager) {
+            mOverlayManager = Objects.requireNonNull(overlayManager);
+        }
 
         /**
          * Request that an overlay package be enabled and change its loading
@@ -205,7 +226,10 @@
          *
          * @hide
          */
+        @NonNull
         public Builder registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
+            Objects.requireNonNull(overlay);
+
             final Bundle extras = new Bundle();
             extras.putParcelable(Request.BUNDLE_FABRICATED_OVERLAY, overlay.mOverlay);
             mRequests.add(new Request(Request.TYPE_REGISTER_FABRICATED, overlay.getIdentifier(),
@@ -220,7 +244,10 @@
          *
          * @hide
          */
+        @NonNull
         public Builder unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
+            Objects.requireNonNull(overlay);
+
             mRequests.add(new Request(Request.TYPE_UNREGISTER_FABRICATED, overlay,
                     UserHandle.USER_ALL));
             return this;
@@ -233,8 +260,9 @@
          * @see OverlayManager#commit
          * @return a new transaction
          */
+        @NonNull
         public OverlayManagerTransaction build() {
-            return new OverlayManagerTransaction(mRequests);
+            return new OverlayManagerTransaction(mRequests, mOverlayManager);
         }
     }
 
@@ -269,4 +297,23 @@
             return new OverlayManagerTransaction[size];
         }
     };
+
+    /**
+     * Commit the overlay manager transaction to register or unregister overlays for self-targeting.
+     *
+     * <p>Applications can register overlays and unregister the registered overlays via {@link
+     * OverlayManagerTransaction}.
+     *
+     * @throws IOException if there is a file operation error.
+     * @throws PackageManager.NameNotFoundException if the package name is not found.
+     * @hide
+     */
+    @NonUiContext
+    public void commit() throws PackageManager.NameNotFoundException, IOException {
+        mOverlayManager.commitSelfTarget(this);
+    }
+
+    boolean isSelfTargetingTransaction() {
+        return mOverlayManager != null;
+    }
 }
diff --git a/core/java/android/content/om/TEST_MAPPING b/core/java/android/content/om/TEST_MAPPING
index 6185cf6..a9aaf1a 100644
--- a/core/java/android/content/om/TEST_MAPPING
+++ b/core/java/android/content/om/TEST_MAPPING
@@ -12,6 +12,9 @@
       "name": "OverlayDeviceTests"
     },
     {
+      "name": "SelfTargetingOverlayDeviceTests"
+    },
+    {
       "name": "OverlayHostTests"
     },
     {
@@ -35,6 +38,9 @@
         },
         {
           "include-filter": "android.content.om.cts"
+        },
+        {
+          "include-filter": "android.content.res.loader.cts"
         }
       ]
     }
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index fda4119..dab57fd 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -221,21 +221,20 @@
     public String launchToken;
 
     /**
-     * Specifies the category of the target display the activity is expected to run on. Set from
-     * the {@link android.R.attr#targetDisplayCategory} attribute. Upon creation, a virtual display
-     * can specify which display categories it supports and one of the category must be present in
-     * the activity's manifest to allow this activity to run. The default value is {@code null},
-     * which indicates the activity does not belong to a restricted display category and thus can
-     * only run on a display that didn't specify any display categories. Each activity can only
-     * specify one category it targets to but a virtual display can support multiple restricted
-     * categories.
-     *
+     * Specifies the required display category of the activity. Set from the
+     * {@link android.R.attr#requiredDisplayCategory} attribute. Upon creation, a display can
+     * specify which display categories it supports and one of the category must be present
+     * in the {@code <activity>} element to allow this activity to run. The default value is
+     * {@code null}, which indicates the activity does not have a required display category and
+     * thus can only run on a display that didn't specify any display categories. Each activity
+     * can only specify one required category but a display can support multiple display categories.
+     * <p>
      * This field should be formatted as a Java-language-style free form string(for example,
      * com.google.automotive_entertainment), which may contain uppercase or lowercase letters ('A'
      * through 'Z'), numbers, and underscores ('_') but may only start with letters.
      */
     @Nullable
-    public String targetDisplayCategory;
+    public String requiredDisplayCategory;
 
     /**
      * Activity can not be resized and always occupies the fullscreen area with all windows fully
@@ -1330,7 +1329,7 @@
         mMaxAspectRatio = orig.mMaxAspectRatio;
         mMinAspectRatio = orig.mMinAspectRatio;
         supportsSizeChanges = orig.supportsSizeChanges;
-        targetDisplayCategory = orig.targetDisplayCategory;
+        requiredDisplayCategory = orig.requiredDisplayCategory;
     }
 
     /**
@@ -1669,8 +1668,8 @@
         if (mKnownActivityEmbeddingCerts != null) {
             pw.println(prefix + "knownActivityEmbeddingCerts=" + mKnownActivityEmbeddingCerts);
         }
-        if (targetDisplayCategory != null) {
-            pw.println(prefix + "targetDisplayCategory=" + targetDisplayCategory);
+        if (requiredDisplayCategory != null) {
+            pw.println(prefix + "requiredDisplayCategory=" + requiredDisplayCategory);
         }
         super.dumpBack(pw, prefix, dumpFlags);
     }
@@ -1718,7 +1717,7 @@
         dest.writeFloat(mMinAspectRatio);
         dest.writeBoolean(supportsSizeChanges);
         sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
-        dest.writeString8(targetDisplayCategory);
+        dest.writeString8(requiredDisplayCategory);
     }
 
     /**
@@ -1844,7 +1843,7 @@
         if (mKnownActivityEmbeddingCerts.isEmpty()) {
             mKnownActivityEmbeddingCerts = null;
         }
-        targetDisplayCategory = source.readString8();
+        requiredDisplayCategory = source.readString8();
     }
 
     /**
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 12911d6..1e928bd 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -23,6 +23,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.VersionedPackage;
 import android.content.IntentSender;
+import android.os.RemoteCallback;
 
 import android.graphics.Bitmap;
 
@@ -66,4 +67,6 @@
 
     void setAllowUnlimitedSilentUpdates(String installerPackageName);
     void setSilentUpdatesThrottleTime(long throttleTimeInSeconds);
+    void checkInstallConstraints(String installerPackageName, in List<String> packageNames,
+            in PackageInstaller.InstallConstraints constraints, in RemoteCallback callback);
 }
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 1fc6bda..7d9c64a 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -61,4 +61,6 @@
     int getInstallFlags();
 
     void requestUserPreapproval(in PackageInstaller.PreapprovalDetails details, in IntentSender statusReceiver);
+
+    boolean isKeepApplicationEnabledSetting();
 }
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index b95f8bb..0a7d079 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -1,13 +1,11 @@
 # Bug component: 36137
 
-patb@google.com
+file:/PACKAGE_MANAGER_OWNERS
 
-per-file Package* = file:/PACKAGE_MANAGER_OWNERS
 per-file PackageParser.java = set noparent
 per-file PackageParser.java = chiuwinson@google.com,patb@google.com
 per-file *Capability* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
 per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
-per-file AppSearchPerson.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
 per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
 per-file UserInfo* = file:/MULTIUSER_OWNERS
 per-file *UserProperties* = file:/MULTIUSER_OWNERS
diff --git a/core/java/android/content/pm/PackageInstaller.aidl b/core/java/android/content/pm/PackageInstaller.aidl
index 833919e..ab9d4f3 100644
--- a/core/java/android/content/pm/PackageInstaller.aidl
+++ b/core/java/android/content/pm/PackageInstaller.aidl
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+parcelable PackageInstaller.InstallConstraints;
 parcelable PackageInstaller.SessionParams;
 parcelable PackageInstaller.SessionInfo;
 parcelable PackageInstaller.PreapprovalDetails;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index d2fb1fb..1f01ae9 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -36,6 +36,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.ActivityManager;
@@ -57,6 +58,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.os.ParcelableException;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -89,6 +91,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Offers the ability to install, upgrade, and remove applications on the
@@ -854,6 +857,34 @@
     }
 
     /**
+     * Check if install constraints are satisfied for the given packages.
+     *
+     * Note this query result is just a hint and subject to race because system states could
+     * change anytime in-between this query and committing the session.
+     *
+     * The result is returned by a callback because some constraints might take a long time
+     * to evaluate.
+     */
+    public void checkInstallConstraints(@NonNull List<String> packageNames,
+            @NonNull InstallConstraints constraints,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<InstallConstraintsResult> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        try {
+            var remoteCallback = new RemoteCallback(b -> {
+                executor.execute(() -> {
+                    callback.accept(b.getParcelable("result", InstallConstraintsResult.class));
+                });
+            });
+            mInstaller.checkInstallConstraints(
+                    mInstallerPackageName, packageNames, constraints, remoteCallback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Events for observing session lifecycle.
      * <p>
      * A typical session lifecycle looks like this:
@@ -1717,6 +1748,18 @@
                 e.rethrowFromSystemServer();
             }
         }
+
+        /**
+         * @return {@code true} if this session will keep the existing application enabled setting
+         * after installation.
+         */
+        public boolean isKeepApplicationEnabledSetting() {
+            try {
+                return mSession.isKeepApplicationEnabledSetting();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
@@ -1855,6 +1898,8 @@
         public boolean forceQueryableOverride;
         /** {@hide} */
         public int requireUserAction = USER_ACTION_UNSPECIFIED;
+        /** {@hide} */
+        public boolean keepApplicationEnabledSetting = false;
 
         /**
          * Construct parameters for a new package install session.
@@ -1899,6 +1944,7 @@
             rollbackDataPolicy = source.readInt();
             requireUserAction = source.readInt();
             packageSource = source.readInt();
+            keepApplicationEnabledSetting = source.readBoolean();
         }
 
         /** {@hide} */
@@ -1929,6 +1975,7 @@
             ret.rollbackDataPolicy = rollbackDataPolicy;
             ret.requireUserAction = requireUserAction;
             ret.packageSource = packageSource;
+            ret.keepApplicationEnabledSetting = keepApplicationEnabledSetting;
             return ret;
         }
 
@@ -2415,6 +2462,14 @@
             this.installScenario = installScenario;
         }
 
+        /**
+         * Request to keep the original application enabled setting. This will prevent the
+         * application from being enabled if it was previously in a disabled state.
+         */
+        public void setKeepApplicationEnabledSetting() {
+            this.keepApplicationEnabledSetting = true;
+        }
+
         /** {@hide} */
         public void dump(IndentingPrintWriter pw) {
             pw.printPair("mode", mode);
@@ -2443,6 +2498,7 @@
             pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode);
             pw.printPair("dataLoaderParams", dataLoaderParams);
             pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
+            pw.printPair("keepApplicationEnabledSetting", keepApplicationEnabledSetting);
             pw.println();
         }
 
@@ -2483,6 +2539,7 @@
             dest.writeInt(rollbackDataPolicy);
             dest.writeInt(requireUserAction);
             dest.writeInt(packageSource);
+            dest.writeBoolean(keepApplicationEnabledSetting);
         }
 
         public static final Parcelable.Creator<SessionParams>
@@ -2684,6 +2741,9 @@
         /** @hide */
         public boolean isPreapprovalRequested;
 
+        /** @hide */
+        public boolean keepApplicationEnabledSetting;
+
         /** {@hide} */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public SessionInfo() {
@@ -2737,6 +2797,7 @@
             requireUserAction = source.readInt();
             installerUid = source.readInt();
             packageSource = source.readInt();
+            keepApplicationEnabledSetting = source.readBoolean();
         }
 
         /**
@@ -3268,6 +3329,14 @@
             return installerUid;
         }
 
+        /**
+         * Returns {@code true} if this session will keep the existing application enabled setting
+         * after installation.
+         */
+        public boolean isKeepApplicationEnabledSetting() {
+            return keepApplicationEnabledSetting;
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -3317,6 +3386,7 @@
             dest.writeInt(requireUserAction);
             dest.writeInt(installerUid);
             dest.writeInt(packageSource);
+            dest.writeBoolean(keepApplicationEnabledSetting);
         }
 
         public static final Parcelable.Creator<SessionInfo>
@@ -3346,7 +3416,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 +3458,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 +3487,7 @@
          * The label representing the app to be installed.
          */
         @DataClass.Generated.Member
-        public @NonNull String getLabel() {
+        public @NonNull CharSequence getLabel() {
             return mLabel;
         }
 
@@ -3461,7 +3531,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 +3549,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 +3589,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 +3615,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 +3666,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() {}
 
@@ -3608,4 +3678,362 @@
         // End of generated code
 
     }
+
+    /**
+     * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}.
+     */
+    @DataClass(genParcelable = true, genHiddenConstructor = true)
+    public static final class InstallConstraintsResult implements Parcelable {
+        /**
+         * True if all constraints are satisfied.
+         */
+        private boolean mAllConstraintsSatisfied;
+
+
+
+        // Code below generated by codegen v1.0.23.
+        //
+        // DO NOT MODIFY!
+        // CHECKSTYLE:OFF Generated code
+        //
+        // To regenerate run:
+        // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java
+        //
+        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+        //   Settings > Editor > Code Style > Formatter Control
+        //@formatter:off
+
+
+        /**
+         * Creates a new InstallConstraintsResult.
+         *
+         * @param allConstraintsSatisfied
+         *   True if all constraints are satisfied.
+         * @hide
+         */
+        @DataClass.Generated.Member
+        public InstallConstraintsResult(
+                boolean allConstraintsSatisfied) {
+            this.mAllConstraintsSatisfied = allConstraintsSatisfied;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        /**
+         * True if all constraints are satisfied.
+         */
+        @DataClass.Generated.Member
+        public boolean isAllConstraintsSatisfied() {
+            return mAllConstraintsSatisfied;
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            // You can override field parcelling by defining methods like:
+            // void parcelFieldName(Parcel dest, int flags) { ... }
+
+            byte flg = 0;
+            if (mAllConstraintsSatisfied) flg |= 0x1;
+            dest.writeByte(flg);
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public int describeContents() { return 0; }
+
+        /** @hide */
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        @DataClass.Generated.Member
+        /* package-private */ InstallConstraintsResult(@NonNull Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            byte flg = in.readByte();
+            boolean allConstraintsSatisfied = (flg & 0x1) != 0;
+
+            this.mAllConstraintsSatisfied = allConstraintsSatisfied;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @DataClass.Generated.Member
+        public static final @NonNull Parcelable.Creator<InstallConstraintsResult> CREATOR
+                = new Parcelable.Creator<InstallConstraintsResult>() {
+            @Override
+            public InstallConstraintsResult[] newArray(int size) {
+                return new InstallConstraintsResult[size];
+            }
+
+            @Override
+            public InstallConstraintsResult createFromParcel(@NonNull Parcel in) {
+                return new InstallConstraintsResult(in);
+            }
+        };
+
+        @DataClass.Generated(
+                time = 1668650523745L,
+                codegenVersion = "1.0.23",
+                sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
+                inputSignatures = "private  boolean mAllConstraintsSatisfied\nclass InstallConstraintsResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
+        @Deprecated
+        private void __metadata() {}
+
+
+        //@formatter:on
+        // End of generated code
+
+    }
+
+    /**
+     * A class to encapsulate constraints for installation.
+     *
+     * When used with {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}, it
+     * specifies the conditions to check against for the packages in question. This can be used
+     * by app stores to deliver auto updates without disrupting the user experience (referred as
+     * gentle update) - for example, an app store might hold off updates when it find out the
+     * app to update is interacting with the user.
+     *
+     * Use {@link Builder} to create a new instance and call mutator methods to add constraints.
+     * If no mutators were called, default constraints will be generated which implies no
+     * constraints. It is recommended to use preset constraints which are useful in most
+     * cases.
+     *
+     * For the purpose of gentle update, it is recommended to always use {@link #GENTLE_UPDATE}
+     * for the system knows best how to do it. It will also benefits the installer as the
+     * platform evolves and add more constraints to improve the accuracy and efficiency of
+     * gentle update.
+     *
+     * Note the constraints are applied transitively. If app Foo is used by app Bar (via shared
+     * library or bounded service), the constraints will also be applied to Bar.
+     */
+    @DataClass(genParcelable = true, genHiddenConstructor = true)
+    public static final class InstallConstraints implements Parcelable {
+        /**
+         * Preset constraints suitable for gentle update.
+         */
+        @NonNull
+        public static final InstallConstraints GENTLE_UPDATE =
+                new Builder().requireAppNotInteracting().build();
+
+        private final boolean mRequireDeviceIdle;
+        private final boolean mRequireAppNotForeground;
+        private final boolean mRequireAppNotInteracting;
+        private final boolean mRequireAppNotTopVisible;
+        private final boolean mRequireNotInCall;
+
+        /**
+         * Builder class for constructing {@link InstallConstraints}.
+         */
+        public static final class Builder {
+            private boolean mRequireDeviceIdle;
+            private boolean mRequireAppNotForeground;
+            private boolean mRequireAppNotInteracting;
+            private boolean mRequireAppNotTopVisible;
+            private boolean mRequireNotInCall;
+
+            /**
+             * This constraint requires the device is idle.
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireDeviceIdle() {
+                mRequireDeviceIdle = true;
+                return this;
+            }
+
+            /**
+             * This constraint requires the app in question is not in the foreground.
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireAppNotForeground() {
+                mRequireAppNotForeground = true;
+                return this;
+            }
+
+            /**
+             * This constraint requires the app in question is not interacting with the user.
+             * User interaction includes:
+             * <ul>
+             *     <li>playing or recording audio/video</li>
+             *     <li>sending or receiving network data</li>
+             *     <li>being visible to the user</li>
+             * </ul>
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireAppNotInteracting() {
+                mRequireAppNotInteracting = true;
+                return this;
+            }
+
+            /**
+             * This constraint requires the app in question is not top-visible to the user.
+             * A top-visible app is showing UI at the top of the screen that the user is
+             * interacting with.
+             *
+             * Note this constraint is a subset of {@link #requireAppNotForeground()}
+             * because a top-visible app is also a foreground app. This is also a subset
+             * of {@link #requireAppNotInteracting()} because a top-visible app is interacting
+             * with the user.
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireAppNotTopVisible() {
+                mRequireAppNotTopVisible = true;
+                return this;
+            }
+
+            /**
+             * This constraint requires there is no ongoing call in the device.
+             */
+            @SuppressLint("BuilderSetStyle")
+            @NonNull
+            public Builder requireNotInCall() {
+                mRequireNotInCall = true;
+                return this;
+            }
+
+            /**
+             * Builds a new {@link InstallConstraints} instance.
+             */
+            @NonNull
+            public InstallConstraints build() {
+                return new InstallConstraints(mRequireDeviceIdle, mRequireAppNotForeground,
+                        mRequireAppNotInteracting, mRequireAppNotTopVisible, mRequireNotInCall);
+            }
+        }
+
+
+
+        // Code below generated by codegen v1.0.23.
+        //
+        // DO NOT MODIFY!
+        // CHECKSTYLE:OFF Generated code
+        //
+        // To regenerate run:
+        // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java
+        //
+        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+        //   Settings > Editor > Code Style > Formatter Control
+        //@formatter:off
+
+
+        /**
+         * Creates a new InstallConstraints.
+         *
+         * @hide
+         */
+        @DataClass.Generated.Member
+        public InstallConstraints(
+                boolean requireDeviceIdle,
+                boolean requireAppNotForeground,
+                boolean requireAppNotInteracting,
+                boolean requireAppNotTopVisible,
+                boolean requireNotInCall) {
+            this.mRequireDeviceIdle = requireDeviceIdle;
+            this.mRequireAppNotForeground = requireAppNotForeground;
+            this.mRequireAppNotInteracting = requireAppNotInteracting;
+            this.mRequireAppNotTopVisible = requireAppNotTopVisible;
+            this.mRequireNotInCall = requireNotInCall;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireDeviceIdle() {
+            return mRequireDeviceIdle;
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireAppNotForeground() {
+            return mRequireAppNotForeground;
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireAppNotInteracting() {
+            return mRequireAppNotInteracting;
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireAppNotTopVisible() {
+            return mRequireAppNotTopVisible;
+        }
+
+        @DataClass.Generated.Member
+        public boolean isRequireNotInCall() {
+            return mRequireNotInCall;
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            // You can override field parcelling by defining methods like:
+            // void parcelFieldName(Parcel dest, int flags) { ... }
+
+            byte flg = 0;
+            if (mRequireDeviceIdle) flg |= 0x1;
+            if (mRequireAppNotForeground) flg |= 0x2;
+            if (mRequireAppNotInteracting) flg |= 0x4;
+            if (mRequireAppNotTopVisible) flg |= 0x8;
+            if (mRequireNotInCall) flg |= 0x10;
+            dest.writeByte(flg);
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public int describeContents() { return 0; }
+
+        /** @hide */
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        @DataClass.Generated.Member
+        /* package-private */ InstallConstraints(@NonNull Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            byte flg = in.readByte();
+            boolean requireDeviceIdle = (flg & 0x1) != 0;
+            boolean requireAppNotForeground = (flg & 0x2) != 0;
+            boolean requireAppNotInteracting = (flg & 0x4) != 0;
+            boolean requireAppNotTopVisible = (flg & 0x8) != 0;
+            boolean requireNotInCall = (flg & 0x10) != 0;
+
+            this.mRequireDeviceIdle = requireDeviceIdle;
+            this.mRequireAppNotForeground = requireAppNotForeground;
+            this.mRequireAppNotInteracting = requireAppNotInteracting;
+            this.mRequireAppNotTopVisible = requireAppNotTopVisible;
+            this.mRequireNotInCall = requireNotInCall;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @DataClass.Generated.Member
+        public static final @NonNull Parcelable.Creator<InstallConstraints> CREATOR
+                = new Parcelable.Creator<InstallConstraints>() {
+            @Override
+            public InstallConstraints[] newArray(int size) {
+                return new InstallConstraints[size];
+            }
+
+            @Override
+            public InstallConstraints createFromParcel(@NonNull Parcel in) {
+                return new InstallConstraints(in);
+            }
+        };
+
+        @DataClass.Generated(
+                time = 1668650523752L,
+                codegenVersion = "1.0.23",
+                sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
+                inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final  boolean mRequireDeviceIdle\nprivate final  boolean mRequireAppNotForeground\nprivate final  boolean mRequireAppNotInteracting\nprivate final  boolean mRequireAppNotTopVisible\nprivate final  boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mRequireDeviceIdle\nprivate  boolean mRequireAppNotForeground\nprivate  boolean mRequireAppNotInteracting\nprivate  boolean mRequireAppNotTopVisible\nprivate  boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
+        @Deprecated
+        private void __metadata() {}
+
+
+        //@formatter:on
+        // End of generated code
+
+    }
+
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index aa86af9..ec490d1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -165,6 +165,21 @@
             "android.internal.PROPERTY_NO_APP_DATA_STORAGE";
 
     /**
+     * &lt;service&gt; level {@link android.content.pm.PackageManager.Property} tag specifying
+     * the actual use case of the service if it's foreground service with the type
+     * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
+     *
+     * <p>
+     * For example:
+     * &lt;service&gt;
+     *   &lt;property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+     *     android:value="foo"/&gt;
+     * &lt;/service&gt;
+     */
+    public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE =
+            "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
+
+    /**
      * A property value set within the manifest.
      * <p>
      * The value of a property will only have a single type, as defined by
@@ -189,12 +204,12 @@
         @VisibleForTesting
         public Property(@NonNull String name, int type,
                 @NonNull String packageName, @Nullable String className) {
-            assert name != null;
-            assert type >= TYPE_BOOLEAN && type <= TYPE_STRING;
-            assert packageName != null;
-            this.mName = name;
+            if (type < TYPE_BOOLEAN || type > TYPE_STRING) {
+                throw new IllegalArgumentException("Invalid type");
+            }
+            this.mName = Objects.requireNonNull(name);
             this.mType = type;
-            this.mPackageName = packageName;
+            this.mPackageName = Objects.requireNonNull(packageName);
             this.mClassName = className;
         }
         /** @hide */
@@ -442,9 +457,8 @@
          */
         public ComponentEnabledSetting(@NonNull ComponentName componentName,
                 @EnabledState int newState, @EnabledFlags int flags) {
-            Objects.nonNull(componentName);
             mPackageName = null;
-            mComponentName = componentName;
+            mComponentName = Objects.requireNonNull(componentName);
             mEnabledState = newState;
             mEnabledFlags = flags;
         }
@@ -460,8 +474,7 @@
          */
         public ComponentEnabledSetting(@NonNull String packageName,
                 @EnabledState int newState, @EnabledFlags int flags) {
-            Objects.nonNull(packageName);
-            mPackageName = packageName;
+            mPackageName = Objects.requireNonNull(packageName);
             mComponentName = null;
             mEnabledState = newState;
             mEnabledFlags = flags;
@@ -1946,6 +1959,14 @@
     public static final int INSTALL_FAILED_MISSING_SPLIT = -28;
 
     /**
+     * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+     * if the new package targets a deprecated SDK version.
+     *
+     * @hide
+     */
+    public static final int INSTALL_FAILED_DEPRECATED_SDK_VERSION = -29;
+
+    /**
      * Installation parse return code: this is passed in the
      * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser was given a path that is not a
      * file, or does not end with the expected '.apk' extension.
@@ -2946,6 +2967,18 @@
     public static final String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures()} and {@link #hasSystemFeature(String)}.
+     * This feature indicates whether device supports
+     * <a href="https://source.android.com/docs/core/virtualization">Android Virtualization Framework</a>.
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_VIRTUALIZATION_FRAMEWORK =
+            "android.software.virtualization_framework";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature(String, int)}: If this feature is supported, the Vulkan
      * implementation on this device is hardware accelerated, and the Vulkan native API will
@@ -3407,7 +3440,6 @@
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device is capable of communicating with
      * other devices via ultra wideband.
-     * @hide
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_UWB = "android.hardware.uwb";
@@ -3817,7 +3849,10 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
-     * The device supports running activities on secondary displays.
+     * The device supports running activities on secondary displays. Displays here
+     * refers to both physical and virtual displays. Disabling this feature can impact
+     * support for application projection use-cases and support for virtual devices
+     * on the device.
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS
@@ -9594,6 +9629,7 @@
             case INSTALL_FAILED_ABORTED: return "INSTALL_FAILED_ABORTED";
             case INSTALL_FAILED_BAD_DEX_METADATA: return "INSTALL_FAILED_BAD_DEX_METADATA";
             case INSTALL_FAILED_MISSING_SPLIT: return "INSTALL_FAILED_MISSING_SPLIT";
+            case INSTALL_FAILED_DEPRECATED_SDK_VERSION: return "INSTALL_FAILED_DEPRECATED_SDK_VERSION";
             case INSTALL_FAILED_BAD_SIGNATURE: return "INSTALL_FAILED_BAD_SIGNATURE";
             case INSTALL_FAILED_WRONG_INSTALLED_VERSION: return "INSTALL_FAILED_WRONG_INSTALLED_VERSION";
             case INSTALL_FAILED_PROCESS_NOT_DEFINED: return "INSTALL_FAILED_PROCESS_NOT_DEFINED";
@@ -9651,6 +9687,7 @@
             case INSTALL_FAILED_ABORTED: return PackageInstaller.STATUS_FAILURE_ABORTED;
             case INSTALL_FAILED_MISSING_SPLIT: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
             case INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE: return PackageInstaller.STATUS_FAILURE_BLOCKED;
+            case INSTALL_FAILED_DEPRECATED_SDK_VERSION: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
             default: return PackageInstaller.STATUS_FAILURE;
         }
     }
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index bb88486..4fa80d7 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -20,8 +20,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
-import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -33,6 +33,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
 import java.util.Set;
 
 /**
@@ -182,7 +183,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 0x8000;
 
     /**
@@ -249,6 +250,16 @@
 
     /**
      * Additional flag for {@link #protectionLevel}, corresponding
+     * to the <code>module</code> value of
+     * {@link android.R.attr#protectionLevel}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int PROTECTION_FLAG_MODULE = 0x400000;
+
+    /**
+     * Additional flag for {@link #protectionLevel}, corresponding
      * to the <code>companion</code> value of
      * {@link android.R.attr#protectionLevel}.
      *
@@ -319,6 +330,7 @@
             PROTECTION_FLAG_RECENTS,
             PROTECTION_FLAG_ROLE,
             PROTECTION_FLAG_KNOWN_SIGNER,
+            PROTECTION_FLAG_MODULE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProtectionFlags {}
@@ -486,7 +498,10 @@
      *
      * @hide
      */
-    public @Nullable Set<String> knownCerts;
+    // Already being used as mutable and most other fields in this class are also mutable.
+    @SuppressLint("MutableBareField")
+    @SystemApi
+    public @NonNull Set<String> knownCerts = Collections.emptySet();
 
     /** @hide */
     public static int fixProtectionLevel(int level) {
@@ -589,6 +604,9 @@
         if ((level & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0) {
             protLevel.append("|knownSigner");
         }
+        if ((level & PermissionInfo.PROTECTION_FLAG_MODULE) != 0) {
+            protLevel.append(("|module"));
+        }
         return protLevel.toString();
     }
 
@@ -620,6 +638,8 @@
         descriptionRes = orig.descriptionRes;
         requestRes = orig.requestRes;
         nonLocalizedDescription = orig.nonLocalizedDescription;
+        // Note that knownCerts wasn't properly copied before Android U.
+        knownCerts = orig.knownCerts;
     }
 
     /**
diff --git a/core/java/android/content/pm/PermissionMethod.java b/core/java/android/content/pm/PermissionMethod.java
index ba97342..647c696 100644
--- a/core/java/android/content/pm/PermissionMethod.java
+++ b/core/java/android/content/pm/PermissionMethod.java
@@ -33,4 +33,20 @@
  */
 @Retention(CLASS)
 @Target({METHOD})
-public @interface PermissionMethod {}
+public @interface PermissionMethod {
+    /**
+     * Hard-coded list of permissions checked by this method
+     */
+    @PermissionName String[] value() default {};
+    /**
+     * If true, the check passes if the caller
+     * has any ONE of the supplied permissions
+     */
+    boolean anyOf() default false;
+    /**
+     * Signifies that the permission check passes if
+     * the calling process OR the current process has
+     * the permission
+     */
+    boolean orSelf() default false;
+}
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 88d7004..fc2c532 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -16,7 +16,9 @@
 
 package android.content.pm;
 
+import android.Manifest;
 import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Printer;
@@ -100,7 +102,15 @@
 
     /**
      * The default foreground service type if not been set in manifest file.
+     *
+     * <p>Apps targeting API level {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and
+     * later should NOT use this type,
+     * calling {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
+     * this type will get a {@link android.app.InvalidForegroundServiceTypeException}.</p>
+     *
+     * @deprecated Do not use.
      */
+    @Deprecated
     public static final int FOREGROUND_SERVICE_TYPE_NONE = 0;
 
     /**
@@ -108,14 +118,36 @@
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Data(photo, file, account) upload/download, backup/restore, import/export, fetch,
      * transfer over network between device and cloud.
+     *
+     * <p>Apps targeting API level {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and
+     * later should NOT use this type:
+     * calling {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
+     * this type on devices running {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} is still
+     * allowed, but calling it with this type on devices running future platform releases may get a
+     * {@link android.app.InvalidForegroundServiceTypeException}.</p>
+     *
+     * @deprecated Use {@link android.app.job.JobInfo.Builder} data transfer APIs instead.
      */
+    @RequiresPermission(
+            value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC,
+            conditional = true
+    )
+    @Deprecated
     public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1 << 0;
 
     /**
      * Constant corresponding to <code>mediaPlayback</code> in
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Music, video, news or other media playback.
+     *
+     * <p>Starting foreground service with this type from apps targeting API level
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PLAYBACK}.
      */
+    @RequiresPermission(
+            value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK,
+            conditional = true
+    )
     public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 1 << 1;
 
     /**
@@ -123,28 +155,94 @@
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Ongoing operations related to phone calls, video conferencing,
      * or similar interactive communication.
+     *
+     * <p>Starting foreground service with this type from apps targeting API level
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_PHONE_CALL} and
+     * {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
      */
+    @RequiresPermission(
+            allOf = {
+                Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL,
+            },
+            anyOf = {
+                Manifest.permission.MANAGE_OWN_CALLS,
+            },
+            conditional = true
+    )
     public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 1 << 2;
 
     /**
      * Constant corresponding to <code>location</code> in
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * GPS, map, navigation location update.
+     *
+     * <p>Starting foreground service with this type from apps targeting API level
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_LOCATION} and one of the
+     * following permissions:
+     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION},
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
      */
+    @RequiresPermission(
+            allOf = {
+                Manifest.permission.FOREGROUND_SERVICE_LOCATION,
+            },
+            anyOf = {
+                Manifest.permission.ACCESS_COARSE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION,
+            },
+            conditional = true
+    )
     public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 1 << 3;
 
     /**
      * Constant corresponding to <code>connectedDevice</code> in
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Auto, bluetooth, TV or other devices connection, monitoring and interaction.
+     *
+     * <p>Starting foreground service with this type from apps targeting API level
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_CONNECTED_DEVICE} and one of the
+     * following permissions:
+     * {@link android.Manifest.permission#BLUETOOTH_CONNECT},
+     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE},
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE},
+     * {@link android.Manifest.permission#CHANGE_WIFI_MULTICAST_STATE},
+     * {@link android.Manifest.permission#NFC},
+     * {@link android.Manifest.permission#TRANSMIT_IR},
+     * or has been granted the access to one of the attached USB devices/accessories.
      */
+    @RequiresPermission(
+            allOf = {
+                Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE,
+            },
+            anyOf = {
+                Manifest.permission.BLUETOOTH_CONNECT,
+                Manifest.permission.CHANGE_NETWORK_STATE,
+                Manifest.permission.CHANGE_WIFI_STATE,
+                Manifest.permission.CHANGE_WIFI_MULTICAST_STATE,
+                Manifest.permission.NFC,
+                Manifest.permission.TRANSMIT_IR,
+            },
+            conditional = true
+    )
     public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 1 << 4;
 
     /**
      * Constant corresponding to {@code mediaProjection} in
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Managing a media projection session, e.g for screen recording or taking screenshots.
+     *
+     * <p>Starting foreground service with this type from apps targeting API level
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PROJECTION}, and the user must
+     * have allowed the screen capture request from this app.
      */
+    @RequiresPermission(
+            value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION,
+            conditional = true
+    )
     public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 1 << 5;
 
     /**
@@ -155,7 +253,21 @@
      * above, a foreground service will not be able to access the camera if this type is not
      * specified in the manifest and in
      * {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+     *
+     * <p>Starting foreground service with this type from apps targeting API level
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_CAMERA} and
+     * {@link android.Manifest.permission#CAMERA}.
      */
+    @RequiresPermission(
+            allOf = {
+                Manifest.permission.FOREGROUND_SERVICE_CAMERA,
+            },
+            anyOf = {
+                Manifest.permission.CAMERA,
+            },
+            conditional = true
+    )
     public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 1 << 6;
 
     /**
@@ -166,17 +278,204 @@
      * above, a foreground service will not be able to access the microphone if this type is not
      * specified in the manifest and in
      * {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+     *
+     * <p>Starting foreground service with this type from apps targeting API level
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_MICROPHONE} and one of the following
+     * permissions:
+     * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT},
+     * {@link android.Manifest.permission#RECORD_AUDIO}.
      */
+    @RequiresPermission(
+            allOf = {
+                Manifest.permission.FOREGROUND_SERVICE_MICROPHONE,
+            },
+            anyOf = {
+                Manifest.permission.CAPTURE_AUDIO_OUTPUT,
+                Manifest.permission.RECORD_AUDIO,
+            },
+            conditional = true
+    )
     public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 1 << 7;
 
     /**
-     * The number of foreground service types, this doesn't include
-     * the {@link #FOREGROUND_SERVICE_TYPE_MANIFEST} and {@link #FOREGROUND_SERVICE_TYPE_NONE}
-     * as they're not real service types.
+     * Constant corresponding to {@code health} in
+     * the {@link android.R.attr#foregroundServiceType} attribute.
+     * Health, wellness and fitness.
+     *
+     * <p>The caller app is required to have the permissions
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_HEALTH} and one of the following
+     * permissions:
+     * {@link android.Manifest.permission#ACTIVITY_RECOGNITION},
+     * {@link android.Manifest.permission#BODY_SENSORS},
+     * {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS},
+     * or one of the {@code "android.permission.health.*"} permissions defined in the
+     * {@link android.healthconnect.HealthPermissions}.
+     */
+    @RequiresPermission(
+            allOf = {
+                Manifest.permission.FOREGROUND_SERVICE_HEALTH,
+            },
+            anyOf = {
+                Manifest.permission.ACTIVITY_RECOGNITION,
+                Manifest.permission.BODY_SENSORS,
+                Manifest.permission.HIGH_SAMPLING_RATE_SENSORS,
+            },
+            conditional = true
+    )
+    public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 1 << 8;
+
+    /**
+     * Constant corresponding to {@code remoteMessaging} in
+     * the {@link android.R.attr#foregroundServiceType} attribute.
+     * Messaging use cases which host local server to relay messages across devices.
+     */
+    @RequiresPermission(
+            value = Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING
+    )
+    public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 1 << 9;
+
+    /**
+     * Constant corresponding to {@code systemExempted} in
+     * the {@link android.R.attr#foregroundServiceType} attribute.
+     * The system exmpted foreground service use cases.
+     *
+     * <p class="note">Note, apps are allowed to use this type only in the following cases:
+     * <ul>
+     *   <li>App has a UID &lt; {@link android.os.Process#FIRST_APPLICATION_UID}</li>
+     *   <li>App is on Doze allowlist</li>
+     *   <li>Device is running in <a href="https://android.googlesource.com/platform/frameworks/base/+/master/packages/SystemUI/docs/demo_mode.md">Demo Mode</a></li>
+     *   <li><a href="https://source.android.com/devices/tech/admin/provision">Device owner app</a><li>
+     *   <li><a href="https://source.android.com/devices/tech/admin/managed-profiles">Profile owner apps</a><li>
+     *   <li>Persistent apps</li>
+     *   <li><a href="https://source.android.com/docs/core/connect/carrier">Carrier privileged apps</a></li>
+     *   <li>Apps that have the {@code android.app.role.RoleManager#ROLE_EMERGENCY} role</li>
+     *   <li>Headless system apps</li>
+     *   <li><a href="{@docRoot}guide/topics/admin/device-admin">Device admin apps</a></li>
+     *   <li>Active VPN apps</li>
+     *   <li>Apps holding {@link Manifest.permission#SCHEDULE_EXACT_ALARM} or
+     *       {@link Manifest.permission#USE_EXACT_ALARM} permission.</li>
+     * </ul>
+     * </p>
+     */
+    @RequiresPermission(
+            value = Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED
+    )
+    public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1 << 10;
+
+    /**
+     * A foreground service type for "short-lived" services, which corresponds to
+     * {@code shortService} in the {@link android.R.attr#foregroundServiceType} attribute in the
+     * manifest.
+     *
+     * <p>Unlike other foreground service types, this type is not associated with a specific use
+     * case, and it will not require any special permissions
+     * (besides {@link Manifest.permission#FOREGROUND_SERVICE}).
+     *
+     * However, this type has the following restrictions.
+     *
+     * <ul>
+     *     <li>
+     *         The type has a 1 minute timeout.
+     *         A foreground service of this type must be stopped within the timeout by
+     *         {@link android.app.Service#stopSelf),
+     *         or {@link android.content.Context#stopService).
+     *         {@link android.app.Service#stopForeground) will also work, which will demote the
+     *         service to a "background" service, which will soon be stopped by the system.
+     *
+     *         <p>The system will <em>not</em> automatically stop it.
+     *
+     *         <p>If the service isn't stopped within the timeout,
+     *         {@link android.app.Service#onTimeout(int)} will be called.
+     *         If the service is still not stopped after the callback,
+     *         the app will be declared an ANR.
+     *
+     *     <li>
+     *         A foreground service of this type cannot be made "sticky"
+     *         (see {@link android.app.Service#START_STICKY}). That is, if an app is killed
+     *         due to a crash or out-of memory while it's running a short foregorund-service,
+     *         the system will not restart the service.
+     *     <li>
+     *         Other foreground services cannot be started from short foreground services.
+     *         Unlike other foreground service types, when an app is running in the background
+     *         while only having a "short" foreground service, it's not allowed to start
+     *         other foreground services, due to the restriction describe here:
+     *         <a href="/guide/components/foreground-services#background-start-restrictions>
+     *             Restrictions on background starts
+     *         </a>
+     * </ul>
+     *
+     * @see android.app.Service#onTimeout(int)
+     */
+    public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 1 << 11;
+
+    /**
+     * Constant corresponding to {@code fileManagement} in
+     * the {@link android.R.attr#foregroundServiceType} attribute.
+     * The file management use case which manages files/directories, often involving file I/O
+     * across the file system.
+     */
+    @RequiresPermission(
+            value = Manifest.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT
+    )
+    public static final int FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT = 1 << 12;
+
+    /**
+     * Constant corresponding to {@code specialUse} in
+     * the {@link android.R.attr#foregroundServiceType} attribute.
+     * Use cases that can't be categorized into any other foreground service types, but also
+     * can't use {@link android.app.job.JobInfo.Builder} APIs.
+     *
+     * <p>The use of this foreground service type may be restricted. Additionally, apps must declare
+     * a service-level {@link PackageManager#PROPERTY_SPECIAL_USE_FGS_SUBTYPE &lt;property&gt;} in
+     * {@code AndroidManifest.xml} as a hint of what the exact use case here is.
+     * Here is an example:
+     * <pre>
+     *  &lt;uses-permission
+     *      android:name="android.permissions.FOREGROUND_SERVICE_SPECIAL_USE"
+     *  /&gt;
+     *  &lt;service
+     *      android:name=".MySpecialForegroundService"
+     *      android:foregroundServiceType="specialUse"&gt;
+     *      &lt;property
+     *          android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+     *          android:value="foo"
+     *      /&gt;
+     * &lt;/service&gt;
+     * </pre>
+     *
+     * In a future release of Android, if the above foreground service type {@code foo} is supported
+     * by the platform, to offer the backward compatibility, the app could specify
+     * the {@code android:maxSdkVersion} attribute in the &lt;uses-permission&gt; section,
+     * and also add the foreground service type {@code foo} into
+     * the {@code android:foregroundServiceType}, therefore the same app could be installed
+     * in both platforms.
+     * <pre>
+     *  &lt;uses-permission
+     *      android:name="android.permissions.FOREGROUND_SERVICE_SPECIAL_USE"
+     *      android:maxSdkVersion="last_sdk_version_without_type_foo"
+     *  /&gt;
+     *  &lt;service
+     *      android:name=".MySpecialForegroundService"
+     *      android:foregroundServiceType="specialUse|foo"&gt;
+     *      &lt;property
+     *          android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+     *          android:value="foo"
+     *      /&gt;
+     * &lt;/service&gt;
+     * </pre>
+     */
+    @RequiresPermission(
+            value = Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE
+    )
+    public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1 << 30;
+
+    /**
+     * The max index being used in the definition of foreground service types.
      *
      * @hide
      */
-    public static final int NUM_OF_FOREGROUND_SERVICE_TYPES = 8;
+    public static final int FOREGROUND_SERVICE_TYPES_MAX_INDEX = 30;
 
     /**
      * A special value indicates to use all types set in manifest file.
@@ -199,7 +498,13 @@
             FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
             FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION,
             FOREGROUND_SERVICE_TYPE_CAMERA,
-            FOREGROUND_SERVICE_TYPE_MICROPHONE
+            FOREGROUND_SERVICE_TYPE_MICROPHONE,
+            FOREGROUND_SERVICE_TYPE_HEALTH,
+            FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING,
+            FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+            FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
+            FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT,
+            FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ForegroundServiceType {}
@@ -275,6 +580,18 @@
                 return "camera";
             case FOREGROUND_SERVICE_TYPE_MICROPHONE:
                 return "microphone";
+            case FOREGROUND_SERVICE_TYPE_HEALTH:
+                return "health";
+            case FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING:
+                return "remoteMessaging";
+            case FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED:
+                return "systemExempted";
+            case FOREGROUND_SERVICE_TYPE_SHORT_SERVICE:
+                return "shortService";
+            case FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT:
+                return "fileManagement";
+            case FOREGROUND_SERVICE_TYPE_SPECIAL_USE:
+                return "specialUse";
             default:
                 return "unknown";
         }
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 1f83d75..295df5c 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -50,6 +50,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
+import java.lang.IllegalArgumentException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -1360,7 +1361,9 @@
         @NonNull
         public Builder setIntents(@NonNull Intent[] intents) {
             Objects.requireNonNull(intents, "intents cannot be null");
-            Objects.requireNonNull(intents.length, "intents cannot be empty");
+            if (intents.length == 0) {
+                throw new IllegalArgumentException("intents cannot be empty");
+            }
             for (Intent intent : intents) {
                 Objects.requireNonNull(intent, "intents cannot contain null");
                 Objects.requireNonNull(intent.getAction(), "intent's action must be set");
@@ -1398,7 +1401,9 @@
         @NonNull
         public Builder setPersons(@NonNull Person[] persons) {
             Objects.requireNonNull(persons, "persons cannot be null");
-            Objects.requireNonNull(persons.length, "persons cannot be empty");
+            if (persons.length == 0) {
+                throw new IllegalArgumentException("persons cannot be empty");
+            }
             for (Person person : persons) {
                 Objects.requireNonNull(person, "persons cannot contain null");
             }
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 1c1f58a..45e0efb 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -43,6 +43,36 @@
       "name": "ApkVerityTest"
     },
     {
+      "name": "CtsAppFgsTestCases",
+      "file_patterns": ["(/|^)ServiceInfo[^/]*"],
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsShortFgsTestCases",
+      "file_patterns": ["(/|^)ServiceInfo[^/]*"],
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
       "name": "CtsIncrementalInstallHostTestCases",
       "options": [
         {
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 9baa6ba..2be0323 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -159,6 +159,18 @@
     public static final int FLAG_EPHEMERAL_ON_CREATE = 0x00002000;
 
     /**
+     * Indicates that this user is the designated main user on the device. This user may have access
+     * to certain features which are limited to at most one user.
+     *
+     * <p>Currently, this will be the first user to go through setup on the device, but in future
+     * releases this status may be transferable or may even not be given to any users.
+     *
+     * <p>This is not necessarily the system user. For example, it will not be the system user on
+     * devices for which {@link UserManager#isHeadlessSystemUserMode()} returns true.
+     */
+    public static final int FLAG_MAIN = 0x00004000;
+
+    /**
      * @hide
      */
     @IntDef(flag = true, prefix = "FLAG_", value = {
@@ -175,7 +187,8 @@
             FLAG_FULL,
             FLAG_SYSTEM,
             FLAG_PROFILE,
-            FLAG_EPHEMERAL_ON_CREATE
+            FLAG_EPHEMERAL_ON_CREATE,
+            FLAG_MAIN
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserInfoFlag {
@@ -369,6 +382,13 @@
     }
 
     /**
+     * @see #FLAG_MAIN
+     */
+    public boolean isMain() {
+        return (flags & FLAG_MAIN) == FLAG_MAIN;
+    }
+
+    /**
      * Returns true if the user is a split system user.
      * <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
      * the method always returns false.
diff --git a/core/java/android/content/pm/UserPackage.java b/core/java/android/content/pm/UserPackage.java
new file mode 100644
index 0000000..7ca92c3
--- /dev/null
+++ b/core/java/android/content/pm/UserPackage.java
@@ -0,0 +1,134 @@
+/*
+ * 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 {
+    private static final boolean ENABLE_CACHING = true;
+
+    @UserIdInt
+    public final int userId;
+    public final String packageName;
+
+    private static final Object sCacheLock = new Object();
+    @GuardedBy("sCacheLock")
+    private static final SparseArrayMap<String, UserPackage> sCache = new SparseArrayMap<>();
+
+    private static final class NoPreloadHolder {
+        /** Set of userIDs to cache objects for. */
+        @GuardedBy("sCacheLock")
+        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) {
+        if (!ENABLE_CACHING) {
+            return new UserPackage(userId, packageName);
+        }
+
+        synchronized (sCacheLock) {
+            if (!ArrayUtils.contains(NoPreloadHolder.sUserIds, userId)) {
+                // Don't cache objects for invalid userIds.
+                return new UserPackage(userId, packageName);
+            }
+
+            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) {
+        if (!ENABLE_CACHING) {
+            return;
+        }
+
+        synchronized (sCacheLock) {
+            sCache.delete(userId, packageName);
+        }
+    }
+
+    /** Indicate the list of valid user IDs on the device. */
+    public static void setValidUserIds(@NonNull int[] userIds) {
+        if (!ENABLE_CACHING) {
+            return;
+        }
+
+        userIds = userIds.clone();
+        synchronized (sCacheLock) {
+            NoPreloadHolder.sUserIds = userIds;
+
+            for (int u = sCache.numMaps() - 1; u >= 0; --u) {
+                final int userId = sCache.keyAt(u);
+                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..fd35378 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -43,17 +43,26 @@
     // 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";
+    private static final String ATTR_INHERIT_DEVICE_POLICY = "inheritDevicePolicy";
+    private static final String ATTR_USE_PARENTS_CONTACTS = "useParentsContacts";
 
     /** 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,
+            INDEX_INHERIT_DEVICE_POLICY,
+            INDEX_USE_PARENTS_CONTACTS,
     })
     @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;
+    private static final int INDEX_INHERIT_DEVICE_POLICY = 3;
+    private static final int INDEX_USE_PARENTS_CONTACTS = 4;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -87,6 +96,68 @@
     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;
+
+    /**
+     * Possible values for whether (and from whom) to inherit select user restrictions
+     * or device policies.
+     *
+     * @hide
+     */
+    @IntDef(prefix = "INHERIT_DEVICE_POLICY", value = {
+            INHERIT_DEVICE_POLICY_NO,
+            INHERIT_DEVICE_POLICY_FROM_PARENT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface InheritDevicePolicy {
+    }
+    /**
+     * Suggests that the given user profile should not inherit user restriction or device policy
+     * from any other user. This is the default value for any new user type.
+     * @hide
+     */
+    public static final int INHERIT_DEVICE_POLICY_NO = 0;
+    /**
+     * Suggests that the given user profile should inherit select user restrictions or
+     * device policies from its parent profile.
+     *
+     *<p> All the user restrictions and device policies would be not propagated to the profile
+     * with this property value. The {(TODO:b/256978256) @link DevicePolicyEngine}
+     * uses this property to determine and propagate only select ones to the given profile.
+     *
+     * @hide
+     */
+    public static final int INHERIT_DEVICE_POLICY_FROM_PARENT = 1;
+
+    /**
      * 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,
@@ -127,9 +198,12 @@
         if (exposeAllFields) {
             // Add items that require exposeAllFields to be true (strictest permission level).
             setStartWithParent(orig.getStartWithParent());
+            setInheritDevicePolicy(orig.getInheritDevicePolicy());
         }
         if (hasManagePermission) {
             // Add items that require MANAGE_USERS or stronger.
+            setShowInSettings(orig.getShowInSettings());
+            setUseParentsContacts(orig.getUseParentsContacts());
         }
         if (hasQueryOrManagePermission) {
             // Add items that require QUERY_USERS or stronger.
@@ -183,6 +257,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
@@ -199,6 +300,60 @@
     }
     private boolean mStartWithParent;
 
+    /**
+     * Return whether, and how, select user restrictions or device policies should be inherited
+     * from other user.
+     *
+     * Possible return values include
+     * {@link #INHERIT_DEVICE_POLICY_FROM_PARENT} or {@link #INHERIT_DEVICE_POLICY_NO}
+     *
+     * @hide
+     */
+    public @InheritDevicePolicy int getInheritDevicePolicy() {
+        if (isPresent(INDEX_INHERIT_DEVICE_POLICY)) return mInheritDevicePolicy;
+        if (mDefaultProperties != null) return mDefaultProperties.mInheritDevicePolicy;
+        throw new SecurityException("You don't have permission to query inheritDevicePolicy");
+    }
+    /** @hide */
+    public void setInheritDevicePolicy(@InheritDevicePolicy int val) {
+        this.mInheritDevicePolicy = val;
+        setPresent(INDEX_INHERIT_DEVICE_POLICY);
+    }
+    private @InheritDevicePolicy int mInheritDevicePolicy;
+
+    /**
+     * Returns whether the current user must use parent user's contacts. If true, writes to the
+     * ContactsProvider corresponding to the current user will be disabled and reads will be
+     * redirected to the parent.
+     *
+     * This only applies to users that have parents (i.e. profiles) and is used to ensure
+     * they can access contacts from the parent profile. This will be generally inapplicable for
+     * non-profile users.
+     *
+     * Please note that in case of the clone profiles, only the allow-listed apps would be allowed
+     * to access contacts across profiles and other apps will not see any contacts.
+     * TODO(b/256126819) Add link to the method returning apps allow-listed for app-cloning
+     *
+     * @return whether contacts access from an associated profile is enabled for the user
+     * @hide
+     */
+    public boolean getUseParentsContacts() {
+        if (isPresent(INDEX_USE_PARENTS_CONTACTS)) return mUseParentsContacts;
+        if (mDefaultProperties != null) return mDefaultProperties.mUseParentsContacts;
+        throw new SecurityException("You don't have permission to query useParentsContacts");
+    }
+    /** @hide */
+    public void setUseParentsContacts(boolean val) {
+        this.mUseParentsContacts = val;
+        setPresent(INDEX_USE_PARENTS_CONTACTS);
+    }
+    /**
+     * Indicates whether the current user should use parent user's contacts.
+     * If this property is set true, the user will be blocked from storing any contacts in its
+     * own contacts database and will serve all read contacts calls through the parent's contacts.
+     */
+    private boolean mUseParentsContacts;
+
     @Override
     public String toString() {
         // Please print in increasing order of PropertyIndex.
@@ -206,6 +361,9 @@
                 + "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent)
                 + ", mShowInLauncher=" + getShowInLauncher()
                 + ", mStartWithParent=" + getStartWithParent()
+                + ", mShowInSettings=" + getShowInSettings()
+                + ", mInheritDevicePolicy=" + getInheritDevicePolicy()
+                + ", mUseParentsContacts=" + getUseParentsContacts()
                 + "}";
     }
 
@@ -219,6 +377,9 @@
         pw.println(prefix + "    mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent));
         pw.println(prefix + "    mShowInLauncher=" + getShowInLauncher());
         pw.println(prefix + "    mStartWithParent=" + getStartWithParent());
+        pw.println(prefix + "    mShowInSettings=" + getShowInSettings());
+        pw.println(prefix + "    mInheritDevicePolicy=" + getInheritDevicePolicy());
+        pw.println(prefix + "    mUseParentsContacts=" + getUseParentsContacts());
     }
 
     /**
@@ -258,6 +419,15 @@
                 case ATTR_START_WITH_PARENT:
                     setStartWithParent(parser.getAttributeBoolean(i));
                     break;
+                case ATTR_SHOW_IN_SETTINGS:
+                    setShowInSettings(parser.getAttributeInt(i));
+                    break;
+                case ATTR_INHERIT_DEVICE_POLICY:
+                    setInheritDevicePolicy(parser.getAttributeInt(i));
+                    break;
+                case ATTR_USE_PARENTS_CONTACTS:
+                    setUseParentsContacts(parser.getAttributeBoolean(i));
+                    break;
                 default:
                     Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
             }
@@ -281,6 +451,17 @@
         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);
+        }
+        if (isPresent(INDEX_INHERIT_DEVICE_POLICY)) {
+            serializer.attributeInt(null, ATTR_INHERIT_DEVICE_POLICY,
+                    mInheritDevicePolicy);
+        }
+        if (isPresent(INDEX_USE_PARENTS_CONTACTS)) {
+            serializer.attributeBoolean(null, ATTR_USE_PARENTS_CONTACTS,
+                    mUseParentsContacts);
+        }
     }
 
     // For use only with an object that has already had any permission-lacking fields stripped out.
@@ -289,6 +470,9 @@
         dest.writeLong(mPropertiesPresent);
         dest.writeInt(mShowInLauncher);
         dest.writeBoolean(mStartWithParent);
+        dest.writeInt(mShowInSettings);
+        dest.writeInt(mInheritDevicePolicy);
+        dest.writeBoolean(mUseParentsContacts);
     }
 
     /**
@@ -301,6 +485,9 @@
         mPropertiesPresent = source.readLong();
         mShowInLauncher = source.readInt();
         mStartWithParent = source.readBoolean();
+        mShowInSettings = source.readInt();
+        mInheritDevicePolicy = source.readInt();
+        mUseParentsContacts = source.readBoolean();
     }
 
     @Override
@@ -327,6 +514,9 @@
         // 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;
+        private @InheritDevicePolicy int mInheritDevicePolicy = INHERIT_DEVICE_POLICY_NO;
+        private boolean mUseParentsContacts = false;
 
         public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
             mShowInLauncher = showInLauncher;
@@ -338,21 +528,48 @@
             return this;
         }
 
+        /** Sets the value for {@link #mShowInSettings} */
+        public Builder setShowInSettings(@ShowInSettings int showInSettings) {
+            mShowInSettings = showInSettings;
+            return this;
+        }
+
+        /** Sets the value for {@link #mInheritDevicePolicy}*/
+        public Builder setInheritDevicePolicy(
+                @InheritDevicePolicy int inheritRestrictionsDevicePolicy) {
+            mInheritDevicePolicy = inheritRestrictionsDevicePolicy;
+            return this;
+        }
+
+        public Builder setUseParentsContacts(boolean useParentsContacts) {
+            mUseParentsContacts = useParentsContacts;
+            return this;
+        }
+
         /** Builds a UserProperties object with *all* values populated. */
         public UserProperties build() {
             return new UserProperties(
                     mShowInLauncher,
-                    mStartWithParent);
+                    mStartWithParent,
+                    mShowInSettings,
+                    mInheritDevicePolicy,
+                    mUseParentsContacts);
         }
     } // 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,
+            @InheritDevicePolicy int inheritDevicePolicy,
+            boolean useParentsContacts) {
 
         mDefaultProperties = null;
         setShowInLauncher(showInLauncher);
         setStartWithParent(startWithParent);
+        setShowInSettings(showInSettings);
+        setInheritDevicePolicy(inheritDevicePolicy);
+        setUseParentsContacts(useParentsContacts);
     }
 }
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 7a5ac8e..143c00d 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -26,6 +26,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import dalvik.annotation.optimization.CriticalNative;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -459,7 +461,7 @@
     private static native @NonNull String nativeGetAssetPath(long ptr);
     private static native @NonNull String nativeGetDebugName(long ptr);
     private static native long nativeGetStringBlock(long ptr);
-    private static native boolean nativeIsUpToDate(long ptr);
+    @CriticalNative private static native boolean nativeIsUpToDate(long ptr);
     private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
     private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
             String overlayableName) throws IOException;
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 6ce2242..ce6e1c7 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -563,6 +563,9 @@
         if (applyToSize) {
             inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f);
             inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f);
+
+            float fontScale = inoutDm.scaledDensity / inoutDm.density;
+            inoutDm.fontScaleConverter = FontScaleConverterFactory.forScale(fontScale);
         }
     }
 
diff --git a/core/java/android/content/res/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java
new file mode 100644
index 0000000..457225d
--- /dev/null
+++ b/core/java/android/content/res/FontScaleConverter.java
@@ -0,0 +1,144 @@
+/*
+ * 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.res;
+
+import android.annotation.NonNull;
+import android.util.MathUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * A lookup table for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
+ * "dp" dimension according to a non-linear curve.
+ *
+ * <p>This is meant to improve readability at larger font scales: larger fonts will scale up more
+ * slowly than smaller fonts, so we don't get ridiculously huge fonts that don't fit on the screen.
+ *
+ * <p>The thinking here is that large fonts are already big enough to read, but we still want to
+ * scale them slightly to preserve the visual hierarchy when compared to smaller fonts.
+ *
+ * @hide
+ */
+public class FontScaleConverter {
+
+    @VisibleForTesting
+    final float[] mFromSpValues;
+    @VisibleForTesting
+    final float[] mToDpValues;
+
+    /**
+     * Creates a lookup table for the given conversions.
+     *
+     * <p>Any "sp" value not in the lookup table will be derived via linear interpolation.
+     *
+     * <p>The arrays must be sorted ascending and monotonically increasing.
+     *
+     * @param fromSp array of dimensions in SP
+     * @param toDp array of dimensions in DP that correspond to an SP value in fromSp
+     *
+     * @throws IllegalArgumentException if the array lengths don't match or are empty
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public FontScaleConverter(@NonNull float[] fromSp, @NonNull float[] toDp) {
+        if (fromSp.length != toDp.length || fromSp.length == 0) {
+            throw new IllegalArgumentException("Array lengths must match and be nonzero");
+        }
+
+        mFromSpValues = fromSp;
+        mToDpValues = toDp;
+    }
+
+    /**
+     * Convert a dimension in "sp" to "dp" using the lookup table.
+     *
+     * @hide
+     */
+    public float convertSpToDp(float sp) {
+        final float spPositive = Math.abs(sp);
+        // TODO(b/247861374): find a match at a higher index?
+        final float sign = Math.signum(sp);
+        // We search for exact matches only, even if it's just a little off. The interpolation will
+        // handle any non-exact matches.
+        final int index = Arrays.binarySearch(mFromSpValues, spPositive);
+        if (index >= 0) {
+            // exact match, return the matching dp
+            return sign * mToDpValues[index];
+        } else {
+            // must be a value in between index and index + 1: interpolate.
+            final int lowerIndex = -(index + 1) - 1;
+
+            final float startSp;
+            final float endSp;
+            final float startDp;
+            final float endDp;
+
+            if (lowerIndex >= mFromSpValues.length - 1) {
+                // It's past our lookup table. Determine the last elements' scaling factor and use.
+                startSp = mFromSpValues[mFromSpValues.length - 1];
+                startDp = mToDpValues[mFromSpValues.length - 1];
+
+                if (startSp == 0) return 0;
+
+                final float scalingFactor = startDp / startSp;
+                return sp * scalingFactor;
+            } else if (lowerIndex == -1) {
+                // It's smaller than the smallest value in our table. Interpolate from 0.
+                startSp = 0;
+                startDp = 0;
+                endSp = mFromSpValues[0];
+                endDp = mToDpValues[0];
+            } else {
+                startSp = mFromSpValues[lowerIndex];
+                endSp = mFromSpValues[lowerIndex + 1];
+                startDp = mToDpValues[lowerIndex];
+                endDp = mToDpValues[lowerIndex + 1];
+            }
+
+            return sign * MathUtils.constrainedMap(startDp, endDp, startSp, endSp, spPositive);
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof FontScaleConverter)) return false;
+        FontScaleConverter that = (FontScaleConverter) o;
+        return Arrays.equals(mFromSpValues, that.mFromSpValues)
+                && Arrays.equals(mToDpValues, that.mToDpValues);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Arrays.hashCode(mFromSpValues);
+        result = 31 * result + Arrays.hashCode(mToDpValues);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "FontScaleConverter{"
+                + "fromSpValues="
+                + Arrays.toString(mFromSpValues)
+                + ", toDpValues="
+                + Arrays.toString(mToDpValues)
+                + '}';
+    }
+}
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
new file mode 100644
index 0000000..c77a372
--- /dev/null
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -0,0 +1,119 @@
+/*
+ * 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.res;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Stores lookup tables for creating {@link FontScaleConverter}s at various scales.
+ *
+ * @hide
+ */
+public class FontScaleConverterFactory {
+    private static final float SCALE_KEY_MULTIPLIER = 100f;
+
+    @VisibleForTesting
+    static final SparseArray<FontScaleConverter> LOOKUP_TABLES = new SparseArray<>();
+
+    static {
+        // These were generated by frameworks/base/tools/fonts/font-scaling-array-generator.js and
+        // manually tweaked for optimum readability.
+        put(
+                /* scaleKey= */ 1.15f,
+                new FontScaleConverter(
+                        /* fromSp= */
+                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                        /* toDp=   */
+                        new float[] { 9.2f, 11.5f, 13.8f, 16.1f, 20.7f,   23f, 27.6f, 34.5f,  115})
+        );
+
+        put(
+                /* scaleKey= */ 1.3f,
+                new FontScaleConverter(
+                        /* fromSp= */
+                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                        /* toDp=   */
+                        new float[] {10.4f,   13f, 15.6f, 18.2f, 23.4f,   26f, 31.2f,   39f,  130})
+        );
+
+        put(
+                /* scaleKey= */ 1.5f,
+                new FontScaleConverter(
+                        /* fromSp= */
+                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                        /* toDp=   */
+                        new float[] {  12f,   15f,   18f,   21f,   27f,   30f,   36f,   45f,  150})
+        );
+
+        put(
+                /* scaleKey= */ 1.8f,
+                new FontScaleConverter(
+                        /* fromSp= */
+                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                        /* toDp=   */
+                        new float[] {14.4f,   18f, 21.6f, 25.2f, 32.4f,   36f, 43.2f,   54f,  180})
+        );
+
+        put(
+                /* scaleKey= */ 2f,
+                new FontScaleConverter(
+                        /* fromSp= */
+                        new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                        /* toDp=   */
+                        new float[] {  16f,   20f,   24f,   28f,   36f,   40f,   48f,   60f,  200})
+        );
+
+    }
+
+    private FontScaleConverterFactory() {}
+
+    /**
+     * Finds a matching FontScaleConverter for the given fontScale factor.
+     *
+     * @param fontScale the scale factor, usually from {@link Configuration#fontScale}.
+     *
+     * @return a converter for the given scale, or null if non-linear scaling should not be used.
+     *
+     * @hide
+     */
+    @Nullable
+    public static FontScaleConverter forScale(float fontScale) {
+        if (fontScale <= 1) {
+            // We don't need non-linear curves for shrinking text or for 100%.
+            // Also, fontScale==0 should not have a curve either
+            return null;
+        }
+
+        FontScaleConverter lookupTable = get(fontScale);
+        // TODO(b/247861716): interpolate between two tables when null
+
+        return lookupTable;
+    }
+
+    private static void put(float scaleKey, @NonNull FontScaleConverter fontScaleConverter) {
+        LOOKUP_TABLES.put((int) (scaleKey * SCALE_KEY_MULTIPLIER), fontScaleConverter);
+    }
+
+    @Nullable
+    private static FontScaleConverter get(float scaleKey) {
+        return LOOKUP_TABLES.get((int) (scaleKey * SCALE_KEY_MULTIPLIER));
+    }
+}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index ff07291..c2b3769 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -40,8 +40,10 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.DrawableContainer;
 import android.icu.text.PluralRules;
+import android.net.Uri;
 import android.os.Build;
 import android.os.LocaleList;
+import android.os.ParcelFileDescriptor;
 import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
@@ -59,6 +61,8 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
@@ -430,6 +434,8 @@
                 // Protect against an unset fontScale.
                 mMetrics.scaledDensity = mMetrics.density *
                         (mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f);
+                mMetrics.fontScaleConverter =
+                        FontScaleConverterFactory.forScale(mConfiguration.fontScale);
 
                 final int width, height;
                 if (mMetrics.widthPixels >= mMetrics.heightPixels) {
@@ -799,7 +805,21 @@
     private Drawable decodeImageDrawable(@NonNull AssetInputStream ais,
             @NonNull Resources wrapper, @NonNull TypedValue value) {
         ImageDecoder.Source src = new ImageDecoder.AssetInputStreamSource(ais,
-                            wrapper, value);
+                wrapper, value);
+        try {
+            return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+            });
+        } catch (IOException ioe) {
+            // This is okay. This may be something that ImageDecoder does not
+            // support, like SVG.
+            return null;
+        }
+    }
+
+    @Nullable
+    private Drawable decodeImageDrawable(@NonNull FileInputStream fis, @NonNull Resources wrapper) {
+        ImageDecoder.Source src = ImageDecoder.createSource(wrapper, fis);
         try {
             return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
@@ -860,6 +880,17 @@
                     } else {
                         dr = loadXmlDrawable(wrapper, value, id, density, file);
                     }
+                } else if (file.startsWith("frro://")) {
+                    Uri uri = Uri.parse(file);
+                    File f = new File('/' + uri.getHost() + uri.getPath());
+                    ParcelFileDescriptor pfd = ParcelFileDescriptor.open(f,
+                            ParcelFileDescriptor.MODE_READ_ONLY);
+                    AssetFileDescriptor afd = new AssetFileDescriptor(
+                            pfd,
+                            Long.parseLong(uri.getQueryParameter("offset")),
+                            Long.parseLong(uri.getQueryParameter("size")));
+                    FileInputStream is = afd.createInputStream();
+                    dr = decodeImageDrawable(is, wrapper);
                 } else {
                     final InputStream is = mAssets.openNonAsset(
                             value.assetCookie, file, AssetManager.ACCESS_STREAMING);
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index 463dcac..a5a1fa689 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -18,7 +18,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.Context;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
 import android.content.pm.ApplicationInfo;
 import android.content.res.ApkAssets;
 import android.content.res.AssetFileDescriptor;
@@ -27,11 +30,17 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.om.OverlayManagerImpl;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
 
 import java.io.Closeable;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
 
 /**
  * Provides methods to load resources data from APKs ({@code .apk}) and resources tables
@@ -63,6 +72,48 @@
     }
 
     /**
+     * Creates a ResourcesProvider instance from the specified overlay information.
+     *
+     * <p>In order to enable the registered overlays, an application can create a {@link
+     * ResourcesProvider} instance according to the specified {@link OverlayInfo} instance and put
+     * them into a {@link ResourcesLoader} instance. The application calls {@link
+     * android.content.res.Resources#addLoaders(ResourcesLoader...)} to load the overlays.
+     *
+     * @param overlayInfo is the information about the specified overlay
+     * @return the resources provider instance for the {@code overlayInfo}
+     * @throws IOException when the files can't be loaded.
+     * @see OverlayManager#getOverlayInfosForTarget(String) to get the list of overlay info.
+     * @hide
+     */
+    @SuppressLint("WrongConstant") // TODO(b/238713267): ApkAssets blocks PROPERTY_LOADER
+    @NonNull
+    public static ResourcesProvider loadOverlay(@NonNull OverlayInfo overlayInfo)
+            throws IOException {
+        Objects.requireNonNull(overlayInfo);
+        Preconditions.checkArgument(overlayInfo.isFabricated(), "Not accepted overlay");
+        Preconditions.checkStringNotEmpty(
+                overlayInfo.getTargetOverlayableName(), "Without overlayable name");
+        final String overlayName =
+                OverlayManagerImpl.checkOverlayNameValid(overlayInfo.getOverlayName());
+        final String path =
+                Preconditions.checkStringNotEmpty(
+                        overlayInfo.getBaseCodePath(), "Invalid base path");
+
+        final Path frroPath = Path.of(path);
+        if (!Files.isRegularFile(frroPath)) {
+            throw new FileNotFoundException("The frro file not found");
+        }
+        final Path idmapPath = frroPath.getParent().resolve(overlayName + ".idmap");
+        if (!Files.isRegularFile(idmapPath)) {
+            throw new FileNotFoundException("The idmap file not found");
+        }
+
+        return new ResourcesProvider(
+                ApkAssets.loadOverlayFromPath(
+                        idmapPath.toString(), 0 /* flags: self targeting overlay */));
+    }
+
+    /**
      * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
      *
      * <p>The file descriptor is duplicated and the original may be closed by the application at any
diff --git a/core/java/android/credentials/ClearCredentialStateRequest.aidl b/core/java/android/credentials/ClearCredentialStateRequest.aidl
new file mode 100644
index 0000000..2679ee4
--- /dev/null
+++ b/core/java/android/credentials/ClearCredentialStateRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable ClearCredentialStateRequest;
\ No newline at end of file
diff --git a/core/java/android/credentials/ClearCredentialStateRequest.java b/core/java/android/credentials/ClearCredentialStateRequest.java
new file mode 100644
index 0000000..33afbed
--- /dev/null
+++ b/core/java/android/credentials/ClearCredentialStateRequest.java
@@ -0,0 +1,85 @@
+/*
+ * 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;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * A request class for clearing a user's credential state from the credential providers.
+ */
+public final class ClearCredentialStateRequest implements Parcelable {
+
+    /** The request data. */
+    @NonNull
+    private final Bundle mData;
+
+    /** Returns the request data. */
+    @NonNull
+    public Bundle getData() {
+        return mData;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBundle(mData);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "ClearCredentialStateRequest {data=" + mData + "}";
+    }
+
+    /**
+     * Constructs a {@link ClearCredentialStateRequest}.
+     *
+     * @param data the request data
+     */
+    public ClearCredentialStateRequest(@NonNull Bundle data) {
+        mData = requireNonNull(data, "data must not be null");
+    }
+
+    private ClearCredentialStateRequest(@NonNull Parcel in) {
+        Bundle data = in.readBundle();
+        mData = data;
+        AnnotationValidations.validate(NonNull.class, null, mData);
+    }
+
+    public static final @NonNull Creator<ClearCredentialStateRequest> CREATOR =
+            new Creator<ClearCredentialStateRequest>() {
+        @Override
+        public ClearCredentialStateRequest[] newArray(int size) {
+            return new ClearCredentialStateRequest[size];
+        }
+
+        @Override
+        public ClearCredentialStateRequest createFromParcel(@NonNull Parcel in) {
+            return new ClearCredentialStateRequest(in);
+        }
+    };
+}
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index 22ef230..4589039 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -39,10 +39,17 @@
     private final String mType;
 
     /**
-     * The request data.
+     * The full credential creation request data.
      */
     @NonNull
-    private final Bundle mData;
+    private final Bundle mCredentialData;
+
+    /**
+     * The partial request data that will be sent to the provider during the initial creation
+     * candidate query stage.
+     */
+    @NonNull
+    private final Bundle mCandidateQueryData;
 
     /**
      * Determines whether or not the request must only be fulfilled by a system provider.
@@ -58,18 +65,39 @@
     }
 
     /**
-     * Returns the request data.
+     * Returns the full credential creation request data.
+     *
+     * For security reason, a provider will receive the request data in two stages. First it gets
+     * a partial request, {@link #getCandidateQueryData()} that do not contain sensitive user
+     * information; it uses this information to provide credential creation candidates that the
+     * [@code CredentialManager] will show to the user. Next, this full request data will be sent to
+     * a provider only if the user further grants the consent by choosing a candidate from the
+     * provider.
      */
     @NonNull
-    public Bundle getData() {
-        return mData;
+    public Bundle getCredentialData() {
+        return mCredentialData;
+    }
+
+    /**
+     * Returns the partial request data that will be sent to the provider during the initial
+     * creation candidate query stage.
+     *
+     * For security reason, a provider will receive the request data in two stages. First it gets
+     * this partial request that do not contain sensitive user information; it uses this information
+     * to provide credential creation candidates that the [@code CredentialManager] will show to
+     * the user. Next, the full request data, {@link #getCredentialData()}, will be sent to a
+     * provider only if the user further grants the consent by choosing a candidate from the
+     * provider.
+     */
+    @NonNull
+    public Bundle getCandidateQueryData() {
+        return mCandidateQueryData;
     }
 
     /**
      * Returns true if the request must only be fulfilled by a system provider, and false
      * otherwise.
-     *
-     * @hide
      */
     public boolean requireSystemProvider() {
         return mRequireSystemProvider;
@@ -78,7 +106,8 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString8(mType);
-        dest.writeBundle(mData);
+        dest.writeBundle(mCredentialData);
+        dest.writeBundle(mCandidateQueryData);
         dest.writeBoolean(mRequireSystemProvider);
     }
 
@@ -91,7 +120,8 @@
     public String toString() {
         return "CreateCredentialRequest {"
                 + "type=" + mType
-                + ", data=" + mData
+                + ", credentialData=" + mCredentialData
+                + ", candidateQueryData=" + mCandidateQueryData
                 + ", requireSystemProvider=" + mRequireSystemProvider
                 + "}";
     }
@@ -100,44 +130,37 @@
      * Constructs a {@link CreateCredentialRequest}.
      *
      * @param type the requested credential type
-     * @param data the request data
-     *
-     * @throws IllegalArgumentException If type is empty
-     */
-    public CreateCredentialRequest(@NonNull String type, @NonNull Bundle data) {
-        this(type, data, /*requireSystemProvider=*/ false);
-    }
-
-    /**
-     * Constructs a {@link CreateCredentialRequest}.
-     *
-     * @param type the requested credential type
-     * @param data the request data
-     * @param requireSystemProvider whether or not the request must only be fulfilled by a system
-     *                              provider
+     * @param credentialData the full credential creation request data
+     * @param candidateQueryData the partial request data that will be sent to the provider
+     *                           during the initial creation candidate query stage
+     * @param requireSystemProvider whether the request must only be fulfilled by a system provider
      *
      * @throws IllegalArgumentException If type is empty.
-     *
-     * @hide
      */
     public CreateCredentialRequest(
             @NonNull String type,
-            @NonNull Bundle data,
+            @NonNull Bundle credentialData,
+            @NonNull Bundle candidateQueryData,
             boolean requireSystemProvider) {
         mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
-        mData = requireNonNull(data, "data must not be null");
+        mCredentialData = requireNonNull(credentialData, "credentialData must not be null");
+        mCandidateQueryData = requireNonNull(candidateQueryData,
+                "candidateQueryData must not be null");
         mRequireSystemProvider = requireSystemProvider;
     }
 
     private CreateCredentialRequest(@NonNull Parcel in) {
         String type = in.readString8();
-        Bundle data = in.readBundle();
+        Bundle credentialData = in.readBundle();
+        Bundle candidateQueryData = in.readBundle();
         boolean requireSystemProvider = in.readBoolean();
 
         mType = type;
         AnnotationValidations.validate(NonNull.class, null, mType);
-        mData = data;
-        AnnotationValidations.validate(NonNull.class, null, mData);
+        mCredentialData = credentialData;
+        AnnotationValidations.validate(NonNull.class, null, mCredentialData);
+        mCandidateQueryData = candidateQueryData;
+        AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
         mRequireSystemProvider = requireSystemProvider;
     }
 
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 30ee118..1efac6c 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -22,7 +22,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
+import android.app.Activity;
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.IntentSender;
 import android.os.CancellationSignal;
 import android.os.ICancellationSignal;
 import android.os.OutcomeReceiver;
@@ -62,18 +65,21 @@
      * <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 activity the activity used to launch any UI needed
+     * @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,
+            @NonNull Activity activity,
             @Nullable CancellationSignal cancellationSignal,
             @CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<
                     GetCredentialResponse, CredentialManagerException> callback) {
         requireNonNull(request, "request must not be null");
+        requireNonNull(activity, "activity must not be null");
         requireNonNull(executor, "executor must not be null");
         requireNonNull(callback, "callback must not be null");
 
@@ -84,8 +90,10 @@
 
         ICancellationSignal cancelRemote = null;
         try {
-            cancelRemote = mService.executeGetCredential(request,
-                    new GetCredentialTransport(executor, callback), mContext.getOpPackageName());
+            cancelRemote = mService.executeGetCredential(
+                    request,
+                    new GetCredentialTransport(activity, executor, callback),
+                    mContext.getOpPackageName());
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
@@ -101,18 +109,21 @@
      * <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 activity the activity used to launch any UI needed
+     * @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,
+            @NonNull Activity activity,
             @Nullable CancellationSignal cancellationSignal,
             @CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<
                     CreateCredentialResponse, CredentialManagerException> callback) {
         requireNonNull(request, "request must not be null");
+        requireNonNull(activity, "activity must not be null");
         requireNonNull(executor, "executor must not be null");
         requireNonNull(callback, "callback must not be null");
 
@@ -124,7 +135,51 @@
         ICancellationSignal cancelRemote = null;
         try {
             cancelRemote = mService.executeCreateCredential(request,
-                    new CreateCredentialTransport(executor, callback),
+                    new CreateCredentialTransport(activity, executor, callback),
+                    mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        if (cancellationSignal != null && cancelRemote != null) {
+            cancellationSignal.setRemote(cancelRemote);
+        }
+    }
+
+    /**
+     * Clears the current user credential state from all credential providers.
+     *
+     * You should invoked this api after your user signs out of your app to notify all credential
+     * providers that any stored credential session for the given app should be cleared.
+     *
+     * A credential provider may have stored an active credential session and use it to limit
+     * sign-in options for future get-credential calls. For example, it may prioritize the active
+     * credential over any other available credential. When your user explicitly signs out of your
+     * app and in order to get the holistic sign-in options the next time, you should call this API
+     * to let the provider clear any stored credential session.
+     *
+     * @param request the request data
+     * @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 clearCredentialState(
+            @NonNull ClearCredentialStateRequest request,
+            @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.clearCredentialState(request,
+                    new ClearCredentialStateTransport(executor, callback),
                     mContext.getOpPackageName());
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
@@ -138,17 +193,30 @@
     private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
         // TODO: listen for cancellation to release callback.
 
+        private final Activity mActivity;
         private final Executor mExecutor;
         private final OutcomeReceiver<
                 GetCredentialResponse, CredentialManagerException> mCallback;
 
-        private GetCredentialTransport(Executor executor,
+        private GetCredentialTransport(Activity activity, Executor executor,
                 OutcomeReceiver<GetCredentialResponse, CredentialManagerException> callback) {
+            mActivity = activity;
             mExecutor = executor;
             mCallback = callback;
         }
 
         @Override
+        public void onPendingIntent(PendingIntent pendingIntent) {
+            try {
+                mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+            } catch (IntentSender.SendIntentException e) {
+                Log.e(TAG, "startIntentSender() failed for intent:"
+                        + pendingIntent.getIntentSender(), e);
+                // TODO: propagate the error.
+            }
+        }
+
+        @Override
         public void onResponse(GetCredentialResponse response) {
             mExecutor.execute(() -> mCallback.onResult(response));
         }
@@ -163,17 +231,30 @@
     private static class CreateCredentialTransport extends ICreateCredentialCallback.Stub {
         // TODO: listen for cancellation to release callback.
 
+        private final Activity mActivity;
         private final Executor mExecutor;
         private final OutcomeReceiver<
                 CreateCredentialResponse, CredentialManagerException> mCallback;
 
-        private CreateCredentialTransport(Executor executor,
+        private CreateCredentialTransport(Activity activity, Executor executor,
                 OutcomeReceiver<CreateCredentialResponse, CredentialManagerException> callback) {
+            mActivity = activity;
             mExecutor = executor;
             mCallback = callback;
         }
 
         @Override
+        public void onPendingIntent(PendingIntent pendingIntent) {
+            try {
+                mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+            } catch (IntentSender.SendIntentException e) {
+                Log.e(TAG, "startIntentSender() failed for intent:"
+                        + pendingIntent.getIntentSender(), e);
+                // TODO: propagate the error.
+            }
+        }
+
+        @Override
         public void onResponse(CreateCredentialResponse response) {
             mExecutor.execute(() -> mCallback.onResult(response));
         }
@@ -184,4 +265,29 @@
                     () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
         }
     }
+
+    private static class ClearCredentialStateTransport
+            extends IClearCredentialStateCallback.Stub {
+        // TODO: listen for cancellation to release callback.
+
+        private final Executor mExecutor;
+        private final OutcomeReceiver<Void, CredentialManagerException> mCallback;
+
+        private ClearCredentialStateTransport(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/core/java/android/credentials/GetCredentialOption.java b/core/java/android/credentials/GetCredentialOption.java
index a0d3c0b..ed93dae 100644
--- a/core/java/android/credentials/GetCredentialOption.java
+++ b/core/java/android/credentials/GetCredentialOption.java
@@ -67,8 +67,6 @@
     /**
      * Returns true if the request must only be fulfilled by a system provider, and false
      * otherwise.
-     *
-     * @hide
      */
     public boolean requireSystemProvider() {
         return mRequireSystemProvider;
@@ -100,24 +98,10 @@
      *
      * @param type the requested credential type
      * @param data the request data
-     *
-     * @throws IllegalArgumentException If type is empty
-     */
-    public GetCredentialOption(@NonNull String type, @NonNull Bundle data) {
-        this(type, data, /*requireSystemProvider=*/ false);
-    }
-
-    /**
-     * Constructs a {@link GetCredentialOption}.
-     *
-     * @param type the requested credential type
-     * @param data the request data
      * @param requireSystemProvider whether or not the request must only be fulfilled by a system
      *                              provider
      *
      * @throws IllegalArgumentException If type is empty.
-     *
-     * @hide
      */
     public GetCredentialOption(
             @NonNull String type,
diff --git a/core/java/android/credentials/IClearCredentialStateCallback.aidl b/core/java/android/credentials/IClearCredentialStateCallback.aidl
new file mode 100644
index 0000000..f8b7ae44
--- /dev/null
+++ b/core/java/android/credentials/IClearCredentialStateCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * Listener for clearCredentialState request.
+ *
+ * @hide
+ */
+interface IClearCredentialStateCallback {
+    oneway void onSuccess();
+    oneway void onError(int errorCode, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/credentials/ICreateCredentialCallback.aidl b/core/java/android/credentials/ICreateCredentialCallback.aidl
index 75620fa..87fd36f 100644
--- a/core/java/android/credentials/ICreateCredentialCallback.aidl
+++ b/core/java/android/credentials/ICreateCredentialCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.credentials;
 
+import android.app.PendingIntent;
 import android.credentials.CreateCredentialResponse;
 
 /**
@@ -24,6 +25,7 @@
  * @hide
  */
 interface ICreateCredentialCallback {
+    oneway void onPendingIntent(in PendingIntent pendingIntent);
     oneway void onResponse(in CreateCredentialResponse response);
     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 b0f27f9..c5497bd 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -16,8 +16,10 @@
 
 package android.credentials;
 
+import android.credentials.ClearCredentialStateRequest;
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCredentialRequest;
+import android.credentials.IClearCredentialStateCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.IGetCredentialCallback;
 import android.os.ICancellationSignal;
@@ -32,4 +34,6 @@
     @nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
 
     @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
+
+    @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
 }
diff --git a/core/java/android/credentials/IGetCredentialCallback.aidl b/core/java/android/credentials/IGetCredentialCallback.aidl
index 92e5851..da152ba 100644
--- a/core/java/android/credentials/IGetCredentialCallback.aidl
+++ b/core/java/android/credentials/IGetCredentialCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.credentials;
 
+import android.app.PendingIntent;
 import android.credentials.GetCredentialResponse;
 
 /**
@@ -24,6 +25,7 @@
  * @hide
  */
 interface IGetCredentialCallback {
+    oneway void onPendingIntent(in PendingIntent pendingIntent);
     oneway void onResponse(in GetCredentialResponse response);
     oneway void onError(int errorCode, String message);
 }
\ No newline at end of file
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java
new file mode 100644
index 0000000..0444278
--- /dev/null
+++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java
@@ -0,0 +1,148 @@
+/*
+ * 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;
+        }
+
+        /** Sets the remote entry of the provider. */
+        @NonNull
+        public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
+            mRemoteEntry = remoteEntry;
+            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/Entry.java b/core/java/android/credentials/ui/Entry.java
index 33427d3..5f947bb 100644
--- a/core/java/android/credentials/ui/Entry.java
+++ b/core/java/android/credentials/ui/Entry.java
@@ -17,7 +17,10 @@
 package android.credentials.ui;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
 import android.app.slice.Slice;
+import android.content.Intent;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -53,7 +56,8 @@
     /** Below are only available for get flows. */
     public static final String HINT_NOTE = "HINT_NOTE";
     public static final String HINT_USER_NAME = "HINT_USER_NAME";
-    public static final String HINT_CREDENTIAL_TYPE = "HINT_CREDENTIAL_TYPE";
+    public static final String HINT_CREDENTIAL_TYPE_DISPLAY_NAME =
+            "HINT_CREDENTIAL_TYPE_DISPLAY_NAME";
     public static final String HINT_PASSKEY_USER_DISPLAY_NAME = "HINT_PASSKEY_USER_DISPLAY_NAME";
     public static final String HINT_PASSWORD_VALUE = "HINT_PASSWORD_VALUE";
 
@@ -84,6 +88,8 @@
 
     @NonNull private final String mKey;
     @NonNull private final String mSubkey;
+    @Nullable private PendingIntent mPendingIntent;
+    @Nullable private Intent mFrameworkExtrasIntent;
 
     @NonNull
     private final Slice mSlice;
@@ -91,7 +97,7 @@
     protected Entry(@NonNull Parcel in) {
         String key = in.readString8();
         String subkey = in.readString8();
-        Slice slice = Slice.CREATOR.createFromParcel(in);
+        Slice slice = in.readTypedObject(Slice.CREATOR);
 
         mKey = key;
         AnnotationValidations.validate(NonNull.class, null, mKey);
@@ -99,14 +105,29 @@
         AnnotationValidations.validate(NonNull.class, null, mSubkey);
         mSlice = slice;
         AnnotationValidations.validate(NonNull.class, null, mSlice);
+        mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+        mFrameworkExtrasIntent = in.readTypedObject(Intent.CREATOR);
     }
 
+    /** Constructor to be used for an entry that does not require further activities
+     * to be invoked when selected.
+     */
     public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice) {
         mKey = key;
         mSubkey = subkey;
         mSlice = slice;
     }
 
+    /** Constructor to be used for an entry that requires a pending intent to be invoked
+     * when clicked.
+     */
+    public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
+            @NonNull PendingIntent pendingIntent, @Nullable Intent intent) {
+        this(key, subkey, slice);
+        mPendingIntent = pendingIntent;
+        mFrameworkExtrasIntent = intent;
+    }
+
     /**
     * Returns the identifier of this entry that's unique within the context of the CredentialManager
     * request.
@@ -132,11 +153,23 @@
         return mSlice;
     }
 
+    @Nullable
+    public PendingIntent getPendingIntent() {
+        return mPendingIntent;
+    }
+
+    @Nullable
+    public Intent getFrameworkExtrasIntent() {
+        return mFrameworkExtrasIntent;
+    }
+
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString8(mKey);
         dest.writeString8(mSubkey);
-        mSlice.writeToParcel(dest, flags);
+        dest.writeTypedObject(mSlice, flags);
+        dest.writeTypedObject(mPendingIntent, flags);
+        dest.writeTypedObject(mFrameworkExtrasIntent, flags);
     }
 
     @Override
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..b608e65 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.os.Parcel;
 import android.os.ResultReceiver;
 
@@ -30,15 +31,21 @@
  */
 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));
+        ComponentName componentName = ComponentName.unflattenFromString(
+                Resources.getSystem().getString(
+                        com.android.internal.R.string.config_credentialManagerDialogComponent));
+        intent.setComponent(componentName);
 
         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 3728469..eeaeb46 100644
--- a/core/java/android/credentials/ui/ProviderData.java
+++ b/core/java/android/credentials/ui/ProviderData.java
@@ -16,232 +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 mProviderFlattenedComponentName;
-    @NonNull
-    private final String mProviderDisplayName;
-    @Nullable
-    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;
 
     public ProviderData(
-            @NonNull String providerFlattenedComponentName, @NonNull String providerDisplayName,
-            @Nullable Icon icon, @NonNull List<Entry> credentialEntries,
-            @NonNull List<Entry> actionChips, @Nullable Entry authenticationEntry,
-            @CurrentTimeMillisLong long lastUsedTimeMillis) {
+            @NonNull String providerFlattenedComponentName) {
         mProviderFlattenedComponentName = providerFlattenedComponentName;
-        mProviderDisplayName = providerDisplayName;
-        mIcon = icon;
-        mCredentialEntries = credentialEntries;
-        mActionChips = actionChips;
-        mAuthenticationEntry = authenticationEntry;
-        mLastUsedTimeMillis = lastUsedTimeMillis;
     }
 
-    /** Returns the unique provider id. */
+    /**
+     * Returns provider component name.
+     * It also serves as the unique identifier for this provider.
+     */
     @NonNull
     public String getProviderFlattenedComponentName() {
         return mProviderFlattenedComponentName;
     }
 
-    @NonNull
-    public String getProviderDisplayName() {
-        return mProviderDisplayName;
-    }
-
-    @Nullable
-    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;
-    }
-
     protected ProviderData(@NonNull Parcel in) {
         String providerFlattenedComponentName = in.readString8();
         mProviderFlattenedComponentName = providerFlattenedComponentName;
         AnnotationValidations.validate(NonNull.class, null, mProviderFlattenedComponentName);
-
-        String providerDisplayName = in.readString8();
-        mProviderDisplayName = providerDisplayName;
-        AnnotationValidations.validate(NonNull.class, null, mProviderDisplayName);
-
-        Icon icon = in.readTypedObject(Icon.CREATOR);
-        mIcon = icon;
-
-        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;
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString8(mProviderFlattenedComponentName);
-        dest.writeString8(mProviderDisplayName);
-        dest.writeTypedObject(mIcon, flags);
-        dest.writeTypedList(mCredentialEntries);
-        dest.writeTypedList(mActionChips);
-        dest.writeTypedObject(mAuthenticationEntry, flags);
-        dest.writeLong(mLastUsedTimeMillis);
     }
 
     @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 mProviderFlattenedComponentName;
-        private @NonNull String mProviderDisplayName;
-        private @Nullable 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 providerFlattenedComponentName,
-                @NonNull String providerDisplayName,
-                @Nullable Icon icon) {
-            mProviderFlattenedComponentName = providerFlattenedComponentName;
-            mProviderDisplayName = providerDisplayName;
-            mIcon = icon;
-        }
-
-        /** Sets the unique provider id. */
-        @NonNull
-        public Builder setProviderFlattenedComponentName(@NonNull String providerFlattenedComponentName) {
-            mProviderFlattenedComponentName = providerFlattenedComponentName;
-            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(mProviderFlattenedComponentName, mProviderDisplayName,
-                    mIcon, mCredentialEntries,
-                mActionChips, mAuthenticationEntry, mLastUsedTimeMillis);
-        }
-    }
 }
diff --git a/core/java/android/credentials/ui/ProviderPendingIntentResponse.java b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
new file mode 100644
index 0000000..47936c4
--- /dev/null
+++ b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
@@ -0,0 +1,79 @@
+/*
+ * 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.credentials.ui;
+
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Response from a provider's pending intent
+ *
+ * @hide
+ */
+public final class ProviderPendingIntentResponse implements Parcelable {
+    private final int mResultCode;
+    @Nullable
+    private final Intent mResultData;
+
+    public ProviderPendingIntentResponse(int resultCode, @Nullable Intent resultData) {
+        mResultCode = resultCode;
+        mResultData = resultData;
+    }
+
+    protected ProviderPendingIntentResponse(Parcel in) {
+        mResultCode = in.readInt();
+        mResultData = in.readTypedObject(Intent.CREATOR);
+    }
+
+    public static final Creator<ProviderPendingIntentResponse> CREATOR =
+            new Creator<ProviderPendingIntentResponse>() {
+                @Override
+                public ProviderPendingIntentResponse createFromParcel(Parcel in) {
+                    return new ProviderPendingIntentResponse(in);
+                }
+
+                @Override
+                public ProviderPendingIntentResponse[] newArray(int size) {
+                    return new ProviderPendingIntentResponse[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mResultCode);
+        dest.writeTypedObject(mResultData, flags);
+    }
+
+    /** Returns the result code associated with this pending intent activity result. */
+    public int getResultCode() {
+        return mResultCode;
+    }
+
+    /** Returns the result data associated with this pending intent activity result. */
+    @NonNull public Intent getResultData() {
+        return mResultData;
+    }
+}
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/credentials/ui/UserSelectionDialogResult.java b/core/java/android/credentials/ui/UserSelectionDialogResult.java
index 6025d78..0e8e7b6 100644
--- a/core/java/android/credentials/ui/UserSelectionDialogResult.java
+++ b/core/java/android/credentials/ui/UserSelectionDialogResult.java
@@ -57,6 +57,7 @@
     @NonNull private final String mProviderId;
     @NonNull private final String mEntryKey;
     @NonNull private final String mEntrySubkey;
+    @Nullable private ProviderPendingIntentResponse mProviderPendingIntentResponse;
 
     public UserSelectionDialogResult(
             @NonNull IBinder requestToken, @NonNull String providerId,
@@ -67,6 +68,17 @@
         mEntrySubkey = entrySubkey;
     }
 
+    public UserSelectionDialogResult(
+            @NonNull IBinder requestToken, @NonNull String providerId,
+            @NonNull String entryKey, @NonNull String entrySubkey,
+            @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
+        super(requestToken);
+        mProviderId = providerId;
+        mEntryKey = entryKey;
+        mEntrySubkey = entrySubkey;
+        mProviderPendingIntentResponse = providerPendingIntentResponse;
+    }
+
     /** Returns provider package name whose entry was selected by the user. */
     @NonNull
     public String getProviderId() {
@@ -85,6 +97,12 @@
         return mEntrySubkey;
     }
 
+    /** Returns the pending intent response from the provider. */
+    @Nullable
+    public ProviderPendingIntentResponse getPendingIntentProviderResponse() {
+        return mProviderPendingIntentResponse;
+    }
+
     protected UserSelectionDialogResult(@NonNull Parcel in) {
         super(in);
         String providerId = in.readString8();
@@ -97,6 +115,7 @@
         AnnotationValidations.validate(NonNull.class, null, mEntryKey);
         mEntrySubkey = entrySubkey;
         AnnotationValidations.validate(NonNull.class, null, mEntrySubkey);
+        mProviderPendingIntentResponse = in.readTypedObject(ProviderPendingIntentResponse.CREATOR);
     }
 
     @Override
@@ -105,6 +124,7 @@
         dest.writeString8(mProviderId);
         dest.writeString8(mEntryKey);
         dest.writeString8(mEntrySubkey);
+        dest.writeTypedObject(mProviderPendingIntentResponse, flags);
     }
 
     @Override
diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java
index 24480e9..beb7f36 100644
--- a/core/java/android/graphics/fonts/FontManager.java
+++ b/core/java/android/graphics/fonts/FontManager.java
@@ -198,6 +198,15 @@
      */
     public static final int RESULT_ERROR_INVALID_XML = -10007;
 
+    /**
+     * Indicates a failure due to invalid debug certificate file.
+     *
+     * This error code is only used with the shell command interaction.
+     *
+     * @hide
+     */
+    public static final int RESULT_ERROR_INVALID_DEBUG_CERTIFICATE = -10008;
+
     private FontManager(@NonNull IFontManager iFontManager) {
         mIFontManager = iFontManager;
     }
diff --git a/core/java/android/hardware/CameraInfo.java b/core/java/android/hardware/CameraInfo.java
index 072be50..41ef6aa 100644
--- a/core/java/android/hardware/CameraInfo.java
+++ b/core/java/android/hardware/CameraInfo.java
@@ -60,4 +60,4 @@
             return new CameraInfo[size];
         }
     };
-};
+}
diff --git a/core/java/android/hardware/CameraStatus.java b/core/java/android/hardware/CameraStatus.java
index 874af29..fa35efb 100644
--- a/core/java/android/hardware/CameraStatus.java
+++ b/core/java/android/hardware/CameraStatus.java
@@ -68,4 +68,4 @@
             return new CameraStatus[size];
         }
     };
-};
+}
diff --git a/core/java/android/hardware/OverlayProperties.aidl b/core/java/android/hardware/OverlayProperties.aidl
new file mode 100644
index 0000000..4f66312
--- /dev/null
+++ b/core/java/android/hardware/OverlayProperties.aidl
@@ -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.
+ */
+
+package android.hardware;
+
+parcelable OverlayProperties;
\ No newline at end of file
diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 2a0956b..1ce1361 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -16,32 +16,96 @@
 
 package android.hardware;
 
-import java.util.List;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import libcore.util.NativeAllocationRegistry;
 
 /**
- * // TODO(b/242588489): Continue work, the class needs a jni-specific constructor and DisplayInfo
- * //                    side constructs the object.
+ * The class provides overlay properties of the device. OverlayProperties
+ * exposes some capabilities from HWC e.g. if fp16 can be supported for HWUI.
+ *
+ * In the future, more capabilities can be added, e.g., whether or not
+ * per-layer colorspaces are supported.
  *
  * @hide
  */
-public final class OverlayProperties {
-    private final SupportedBufferCombinations[] mCombinations = null;
-    private final boolean mSupportFp16ForHdr = false;
+public final class OverlayProperties implements Parcelable {
 
-    static class SupportedBufferCombinations {
-        @HardwareBuffer.Format List<Integer> mHardwareBufferFormats;
-        @DataSpace.NamedDataSpace List<Integer> mDataSpaces;
-        SupportedBufferCombinations(@HardwareBuffer.Format List<Integer> hardwareBufferFormats,
-                @DataSpace.NamedDataSpace List<Integer> dataSpaces) {
-            mHardwareBufferFormats = hardwareBufferFormats;
-            mDataSpaces = dataSpaces;
+    private static final NativeAllocationRegistry sRegistry =
+            NativeAllocationRegistry.createMalloced(OverlayProperties.class.getClassLoader(),
+            nGetDestructor());
+
+    private long mNativeObject;
+    // Invoked on destruction
+    private Runnable mCloser;
+
+    public OverlayProperties(long nativeObject) {
+        if (nativeObject != 0) {
+            mCloser = sRegistry.registerNativeAllocation(this, nativeObject);
+        }
+        mNativeObject = nativeObject;
+    }
+
+    /**
+     * @return True if the device can support fp16, false otherwise.
+     */
+    public boolean supportFp16ForHdr() {
+        if (mNativeObject == 0) {
+            return false;
+        }
+        return nSupportFp16ForHdr(mNativeObject);
+    }
+
+    /**
+     * Release the local reference.
+     */
+    public void release() {
+        if (mNativeObject != 0) {
+            mCloser.run();
+            mNativeObject = 0;
         }
     }
 
-    /***
-     * @return if the device can support fp16.
-     */
-    public boolean supportFp16ForHdr() {
-        return mSupportFp16ForHdr;
+    @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) {
+        if (mNativeObject == 0) {
+            dest.writeInt(0);
+            return;
+        }
+        dest.writeInt(1);
+        nWriteOverlayPropertiesToParcel(mNativeObject, dest);
+    }
+
+    public static final @NonNull Parcelable.Creator<OverlayProperties> CREATOR =
+            new Parcelable.Creator<OverlayProperties>() {
+        public OverlayProperties createFromParcel(Parcel in) {
+            if (in.readInt() != 0) {
+                return new OverlayProperties(nReadOverlayPropertiesFromParcel(in));
+            }
+            return null;
+        }
+
+        public OverlayProperties[] newArray(int size) {
+            return new OverlayProperties[size];
+        }
+    };
+
+    private static native long nGetDestructor();
+    private static native boolean nSupportFp16ForHdr(long nativeObject);
+    private static native void nWriteOverlayPropertiesToParcel(long nativeObject, Parcel dest);
+    private static native long nReadOverlayPropertiesFromParcel(Parcel in);
 }
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/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 1c4898a..18118f5 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -16,8 +16,14 @@
 
 package android.hardware;
 
+import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
+import static android.companion.virtual.VirtualDeviceManager.DEFAULT_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
+import android.companion.virtual.VirtualDeviceManager;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
@@ -45,6 +51,7 @@
 import java.io.UncheckedIOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -80,6 +87,8 @@
     private static native boolean nativeGetSensorAtIndex(long nativeInstance,
             Sensor sensor, int index);
     private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
+    private static native void nativeGetRuntimeSensors(
+            long nativeInstance, int deviceId, List<Sensor> list);
     private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
 
     private static native int nativeCreateDirectChannel(
@@ -100,6 +109,10 @@
 
     private final ArrayList<Sensor> mFullSensorsList = new ArrayList<>();
     private List<Sensor> mFullDynamicSensorsList = new ArrayList<>();
+    private final SparseArray<List<Sensor>> mFullRuntimeSensorListByDevice = new SparseArray<>();
+    private final SparseArray<SparseArray<List<Sensor>>> mRuntimeSensorListByDeviceByType =
+            new SparseArray<>();
+
     private boolean mDynamicSensorListDirty = true;
 
     private final HashMap<Integer, Sensor> mHandleToSensor = new HashMap<>();
@@ -114,6 +127,7 @@
     private HashMap<DynamicSensorCallback, Handler>
             mDynamicSensorCallbacks = new HashMap<>();
     private BroadcastReceiver mDynamicSensorBroadcastReceiver;
+    private BroadcastReceiver mRuntimeSensorBroadcastReceiver;
 
     // Looper associated with the context in which this instance was created.
     private final Looper mMainLooper;
@@ -121,6 +135,7 @@
     private final boolean mIsPackageDebuggable;
     private final Context mContext;
     private final long mNativeInstance;
+    private final VirtualDeviceManager mVdm;
 
     private Optional<Boolean> mHasHighSamplingRateSensorsPermission = Optional.empty();
 
@@ -139,6 +154,7 @@
         mContext = context;
         mNativeInstance = nativeCreate(context.getOpPackageName());
         mIsPackageDebuggable = (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
+        mVdm = mContext.getSystemService(VirtualDeviceManager.class);
 
         // initialize the sensor list
         for (int index = 0;; ++index) {
@@ -147,12 +163,63 @@
             mFullSensorsList.add(sensor);
             mHandleToSensor.put(sensor.getHandle(), sensor);
         }
+
+    }
+
+    /** @hide */
+    @Override
+    public List<Sensor> getSensorList(int type) {
+        final int deviceId = mContext.getDeviceId();
+        if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
+                || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
+            return super.getSensorList(type);
+        }
+
+        // Cache the per-device lists on demand.
+        List<Sensor> list;
+        synchronized (mFullRuntimeSensorListByDevice) {
+            List<Sensor> fullList = mFullRuntimeSensorListByDevice.get(deviceId);
+            if (fullList == null) {
+                fullList = createRuntimeSensorListLocked(deviceId);
+            }
+            SparseArray<List<Sensor>> deviceSensorListByType =
+                    mRuntimeSensorListByDeviceByType.get(deviceId);
+            list = deviceSensorListByType.get(type);
+            if (list == null) {
+                if (type == Sensor.TYPE_ALL) {
+                    list = fullList;
+                } else {
+                    list = new ArrayList<>();
+                    for (Sensor i : fullList) {
+                        if (i.getType() == type) {
+                            list.add(i);
+                        }
+                    }
+                }
+                list = Collections.unmodifiableList(list);
+                deviceSensorListByType.append(type, list);
+            }
+        }
+        return list;
     }
 
     /** @hide */
     @Override
     protected List<Sensor> getFullSensorList() {
-        return mFullSensorsList;
+        final int deviceId = mContext.getDeviceId();
+        if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
+                || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
+            return mFullSensorsList;
+        }
+
+        List<Sensor> fullList;
+        synchronized (mFullRuntimeSensorListByDevice) {
+            fullList = mFullRuntimeSensorListByDevice.get(deviceId);
+            if (fullList == null) {
+                fullList = createRuntimeSensorListLocked(deviceId);
+            }
+        }
+        return fullList;
     }
 
     /** @hide */
@@ -446,12 +513,53 @@
         }
     }
 
+    private List<Sensor> createRuntimeSensorListLocked(int deviceId) {
+        setupRuntimeSensorBroadcastReceiver();
+        List<Sensor> list = new ArrayList<>();
+        nativeGetRuntimeSensors(mNativeInstance, deviceId, list);
+        mFullRuntimeSensorListByDevice.put(deviceId, list);
+        mRuntimeSensorListByDeviceByType.put(deviceId, new SparseArray<>());
+        for (Sensor s : list) {
+            mHandleToSensor.put(s.getHandle(), s);
+        }
+        return list;
+    }
+
+    private void setupRuntimeSensorBroadcastReceiver() {
+        if (mRuntimeSensorBroadcastReceiver == null) {
+            mRuntimeSensorBroadcastReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (intent.getAction().equals(ACTION_VIRTUAL_DEVICE_REMOVED)) {
+                        synchronized (mFullRuntimeSensorListByDevice) {
+                            final int deviceId = intent.getIntExtra(
+                                    EXTRA_VIRTUAL_DEVICE_ID, DEFAULT_DEVICE_ID);
+                            List<Sensor> removedSensors =
+                                    mFullRuntimeSensorListByDevice.removeReturnOld(deviceId);
+                            if (removedSensors != null) {
+                                for (Sensor s : removedSensors) {
+                                    cleanupSensorConnection(s);
+                                }
+                            }
+                            mRuntimeSensorListByDeviceByType.remove(deviceId);
+                        }
+                    }
+                }
+            };
+
+            IntentFilter filter = new IntentFilter("virtual_device_removed");
+            filter.addAction(ACTION_VIRTUAL_DEVICE_REMOVED);
+            mContext.registerReceiver(mRuntimeSensorBroadcastReceiver, filter,
+                    Context.RECEIVER_NOT_EXPORTED);
+        }
+    }
+
     private void setupDynamicSensorBroadcastReceiver() {
         if (mDynamicSensorBroadcastReceiver == null) {
             mDynamicSensorBroadcastReceiver = new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
-                    if (intent.getAction() == Intent.ACTION_DYNAMIC_SENSOR_CHANGED) {
+                    if (intent.getAction().equals(Intent.ACTION_DYNAMIC_SENSOR_CHANGED)) {
                         if (DEBUG_DYNAMIC_SENSOR) {
                             Log.i(TAG, "DYNS received DYNAMIC_SENSOR_CHANED broadcast");
                         }
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 2eb8cb9..6b044fc 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -639,5 +639,24 @@
             }
         }
     }
+
+    /**
+     * Notifies AuthService that keyguard has been dismissed for the given userId.
+     *
+     * @param userId
+     * @param hardwareAuthToken
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void resetLockout(int userId, byte[] hardwareAuthToken) {
+        if (mService != null) {
+            try {
+                mService.resetLockout(userId, hardwareAuthToken);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+    }
 }
 
diff --git a/core/java/android/hardware/biometrics/BiometricStateListener.java b/core/java/android/hardware/biometrics/BiometricStateListener.java
index b167cc6..71b7850 100644
--- a/core/java/android/hardware/biometrics/BiometricStateListener.java
+++ b/core/java/android/hardware/biometrics/BiometricStateListener.java
@@ -73,4 +73,5 @@
      */
     public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
     }
+
 }
diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index d415706..267ef36 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -118,4 +118,4 @@
         }
         return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto);
     }
-};
+}
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 7c3cc10b..c2e5c0b 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -79,6 +79,9 @@
     void resetLockoutTimeBound(IBinder token, String opPackageName, int fromSensorId, int userId,
             in byte[] hardwareAuthToken);
 
+    // See documentation in BiometricManager.
+    void resetLockout(int userId, in byte[] hardwareAuthToken);
+
     // Provides a localized string that may be used as the label for a button that invokes
     // BiometricPrompt.
     CharSequence getButtonLabel(int userId, String opPackageName, int authenticators);
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 08f9ed6..c88af5a 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -91,6 +91,10 @@
     void resetLockoutTimeBound(IBinder token, String opPackageName, int fromSensorId, int userId,
             in byte[] hardwareAuthToken);
 
+    // See documentation in BiometricManager.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void resetLockout(int userId, in byte[] hardwareAuthToken);
+
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     int getCurrentStrength(int sensorId);
 
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index d3cb59d..1ee2423 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1310,6 +1310,22 @@
             new Key<int[]>("android.control.availableSettingsOverrides", int[].class);
 
     /**
+     * <p>Whether the camera device supports {@link CaptureRequest#CONTROL_AUTOFRAMING android.control.autoframing}.</p>
+     * <p>Will be <code>false</code> if auto-framing is not available.</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p><b>Limited capability</b> -
+     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+     *
+     * @see CaptureRequest#CONTROL_AUTOFRAMING
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+     */
+    @PublicKey
+    @NonNull
+    public static final Key<Boolean> CONTROL_AUTOFRAMING_AVAILABLE =
+            new Key<Boolean>("android.control.autoframingAvailable", boolean.class);
+
+    /**
      * <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera
      * device.</p>
      * <p>Full-capability camera devices must always support OFF; camera devices that support
@@ -3067,17 +3083,67 @@
      * check if it limits the maximum size for image data.</p>
      * <p>For applications targeting SDK version older than 31, the following table
      * describes the minimum required output stream configurations based on the
-     * hardware level ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel}):
-     * Format                                             | Size                                         | Hardware Level | Notes
-     * :-------------------------------------------------:|:--------------------------------------------:|:--------------:|:--------------:
-     * {@link android.graphics.ImageFormat#JPEG }          | {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} (*1)     | Any            |
-     * {@link android.graphics.ImageFormat#JPEG }          | 1920x1080 (1080p)                            | Any            | if 1080p &lt;= activeArraySize
-     * {@link android.graphics.ImageFormat#JPEG }          | 1280x720 (720p)                               | Any            | if 720p &lt;= activeArraySize
-     * {@link android.graphics.ImageFormat#JPEG }          | 640x480 (480p)                               | Any            | if 480p &lt;= activeArraySize
-     * {@link android.graphics.ImageFormat#JPEG }          | 320x240 (240p)                               | Any            | if 240p &lt;= activeArraySize
-     * {@link android.graphics.ImageFormat#YUV_420_888 }   | all output sizes available for JPEG          | FULL           |
-     * {@link android.graphics.ImageFormat#YUV_420_888 }   | all output sizes available for JPEG, up to the maximum video size | LIMITED        |
-     * {@link android.graphics.ImageFormat#PRIVATE }       | same as YUV_420_888                          | Any            |</p>
+     * hardware level ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel}):</p>
+     * <table>
+     * <thead>
+     * <tr>
+     * <th style="text-align: center;">Format</th>
+     * <th style="text-align: center;">Size</th>
+     * <th style="text-align: center;">Hardware Level</th>
+     * <th style="text-align: center;">Notes</th>
+     * </tr>
+     * </thead>
+     * <tbody>
+     * <tr>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+     * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} (*1)</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;"></td>
+     * </tr>
+     * <tr>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+     * <td style="text-align: center;">1920x1080 (1080p)</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;">if 1080p &lt;= activeArraySize</td>
+     * </tr>
+     * <tr>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+     * <td style="text-align: center;">1280x720 (720p)</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;">if 720p &lt;= activeArraySize</td>
+     * </tr>
+     * <tr>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+     * <td style="text-align: center;">640x480 (480p)</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;">if 480p &lt;= activeArraySize</td>
+     * </tr>
+     * <tr>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+     * <td style="text-align: center;">320x240 (240p)</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;">if 240p &lt;= activeArraySize</td>
+     * </tr>
+     * <tr>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+     * <td style="text-align: center;">all output sizes available for JPEG</td>
+     * <td style="text-align: center;">FULL</td>
+     * <td style="text-align: center;"></td>
+     * </tr>
+     * <tr>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+     * <td style="text-align: center;">all output sizes available for JPEG, up to the maximum video size</td>
+     * <td style="text-align: center;">LIMITED</td>
+     * <td style="text-align: center;"></td>
+     * </tr>
+     * <tr>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#PRIVATE }</td>
+     * <td style="text-align: center;">same as YUV_420_888</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;"></td>
+     * </tr>
+     * </tbody>
+     * </table>
      * <p>For applications targeting SDK version 31 or newer, if the mobile device declares to be
      * media performance class 12 or higher by setting
      * {@link android.os.Build.VERSION#MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 2a47851..bbdb626 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -802,6 +802,46 @@
     }
 
     /**
+     * Retrieve support for capture progress callbacks via
+     *  {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureProcessProgressed}.
+     *
+     * @param extension         the extension type
+     * @return {@code true} in case progress callbacks are supported, {@code false} otherwise
+     *
+     * @throws IllegalArgumentException in case of an unsupported extension.
+     */
+    public boolean isCaptureProcessProgressAvailable(@Extension int extension) {
+        long clientId = registerClient(mContext);
+        if (clientId < 0) {
+            throw new IllegalArgumentException("Unsupported extensions");
+        }
+
+        try {
+            if (!isExtensionSupported(mCameraId, extension, mChars)) {
+                throw new IllegalArgumentException("Unsupported extension");
+            }
+
+            if (areAdvancedExtensionsSupported()) {
+                IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
+                extender.init(mCameraId);
+                return extender.isCaptureProcessProgressAvailable();
+            } else {
+                Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
+                        initializeExtension(extension);
+                extenders.second.init(mCameraId, mChars.getNativeMetadata());
+                return extenders.second.isCaptureProcessProgressAvailable();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does"
+                    + " not respond!");
+        } finally {
+            unregisterClient(clientId);
+        }
+
+        return false;
+    }
+
+    /**
      * Returns the set of keys supported by a {@link CaptureRequest} submitted in a
      * {@link CameraExtensionSession} with a given extension type.
      *
diff --git a/core/java/android/hardware/camera2/CameraExtensionSession.java b/core/java/android/hardware/camera2/CameraExtensionSession.java
index 6ddaddf..6f895d5 100644
--- a/core/java/android/hardware/camera2/CameraExtensionSession.java
+++ b/core/java/android/hardware/camera2/CameraExtensionSession.java
@@ -16,6 +16,7 @@
 
 package android.hardware.camera2;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 
 import java.util.concurrent.Executor;
@@ -198,6 +199,41 @@
                 @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
             // default empty implementation
         }
+
+        /**
+         * This method is called when image capture processing is ongoing between
+         * {@link #onCaptureProcessStarted} and the processed still capture frame returning
+         * to the client surface.
+         *
+         * <p>The value included in the arguments provides clients with an estimate
+         * of the post-processing progress which could take significantly more time
+         * relative to the rest of the {@link #capture} sequence.</p>
+         *
+         * <p>The callback will be triggered only by extensions that return {@code true}
+         * from calls
+         * {@link CameraExtensionCharacteristics#isCaptureProcessProgressAvailable}.</p>
+         *
+         * <p>If support for this callback is present, then clients will be notified at least once
+         * with progress value 100.</p>
+         *
+         * <p>The callback will be triggered only for still capture requests {@link #capture} and
+         * is not supported for repeating requests {@link #setRepeatingRequest}.</p>
+         *
+         * <p>The default implementation of this method does nothing.</p>
+         *
+         * @param session The session received during
+         *                {@link StateCallback#onConfigured(CameraExtensionSession)}
+         * @param request The request that was given to the CameraDevice
+         * @param progress Value between 0 and 100 (inclusive) indicating the current
+         *                post-processing progress
+         *
+         * @see CameraExtensionCharacteristics#isCaptureProcessProgressAvailable
+         *
+         */
+        public void onCaptureProcessProgressed(@NonNull CameraExtensionSession session,
+                @NonNull CaptureRequest request, @IntRange(from = 0, to = 100) int progress) {
+            // default empty implementation
+        }
     }
 
     /**
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 50551fee..f858227 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -245,11 +245,13 @@
      * applications is not guaranteed to be supported, however.</p>
      *
      * <p>For concurrent operation, in chronological order :
-     * - Applications must first close any open cameras that have sessions configured, using
-     *   {@link CameraDevice#close}.
-     * - All camera devices intended to be operated concurrently, must be opened using
-     *   {@link #openCamera}, before configuring sessions on any of the camera devices.</p>
-     *
+     * <ul>
+     * <li> Applications must first close any open cameras that have sessions configured, using
+     *   {@link CameraDevice#close}. </li>
+     * <li> All camera devices intended to be operated concurrently, must be opened using
+     *   {@link #openCamera}, before configuring sessions on any of the camera devices.</li>
+     *</ul>
+     *</p>
      * <p>Each device in a combination, is guaranteed to support stream combinations which may be
      * obtained by querying {@link #getCameraCharacteristics} for the key
      * {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS}.</p>
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 545aa8f..44f8b1b 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3244,6 +3244,28 @@
     public static final int CONTROL_SETTINGS_OVERRIDE_VENDOR_START = 0x4000;
 
     //
+    // Enumeration values for CaptureRequest#CONTROL_AUTOFRAMING
+    //
+
+    /**
+     * <p>Disable autoframing.</p>
+     * @see CaptureRequest#CONTROL_AUTOFRAMING
+     */
+    public static final int CONTROL_AUTOFRAMING_OFF = 0;
+
+    /**
+     * <p>Enable autoframing to keep people in the frame's field of view.</p>
+     * @see CaptureRequest#CONTROL_AUTOFRAMING
+     */
+    public static final int CONTROL_AUTOFRAMING_ON = 1;
+
+    /**
+     * <p>Automatically select ON or OFF based on the system level preferences.</p>
+     * @see CaptureRequest#CONTROL_AUTOFRAMING
+     */
+    public static final int CONTROL_AUTOFRAMING_AUTO = 2;
+
+    //
     // Enumeration values for CaptureRequest#EDGE_MODE
     //
 
@@ -3997,6 +4019,29 @@
     public static final int CONTROL_AF_SCENE_CHANGE_DETECTED = 1;
 
     //
+    // Enumeration values for CaptureResult#CONTROL_AUTOFRAMING_STATE
+    //
+
+    /**
+     * <p>Auto-framing is inactive.</p>
+     * @see CaptureResult#CONTROL_AUTOFRAMING_STATE
+     */
+    public static final int CONTROL_AUTOFRAMING_STATE_INACTIVE = 0;
+
+    /**
+     * <p>Auto-framing is in process - either zooming in, zooming out or pan is taking place.</p>
+     * @see CaptureResult#CONTROL_AUTOFRAMING_STATE
+     */
+    public static final int CONTROL_AUTOFRAMING_STATE_FRAMING = 1;
+
+    /**
+     * <p>Auto-framing has reached a stable state (frame/fov is not being adjusted). The state
+     * may transition back to FRAMING if the scene changes.</p>
+     * @see CaptureResult#CONTROL_AUTOFRAMING_STATE
+     */
+    public static final int CONTROL_AUTOFRAMING_STATE_CONVERGED = 2;
+
+    //
     // Enumeration values for CaptureResult#FLASH_STATE
     //
 
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 407ea07..ea3e4a8 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2498,12 +2498,8 @@
      * <p><b>Available values for this device:</b><br>
      * {@link CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES android.control.availableSettingsOverrides}</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
-     * <p><b>Limited capability</b> -
-     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
-     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
      *
      * @see CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES
-     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
      * @see #CONTROL_SETTINGS_OVERRIDE_OFF
      * @see #CONTROL_SETTINGS_OVERRIDE_ZOOM
      */
@@ -2513,6 +2509,42 @@
             new Key<Integer>("android.control.settingsOverride", int.class);
 
     /**
+     * <p>Automatic crop, pan and zoom to keep objects in the center of the frame.</p>
+     * <p>Auto-framing is a special mode provided by the camera device to dynamically crop, zoom
+     * or pan the camera feed to try to ensure that the people in a scene occupy a reasonable
+     * portion of the viewport. It is primarily designed to support video calling in
+     * situations where the user isn't directly in front of the device, especially for
+     * wide-angle cameras.
+     * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} and {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} in CaptureResult will be used
+     * to denote the coordinates of the auto-framed region.
+     * Zoom and video stabilization controls are disabled when auto-framing is enabled. The 3A
+     * regions must map the screen coordinates into the scaler crop returned from the capture
+     * result instead of using the active array sensor.</p>
+     * <p><b>Possible values:</b></p>
+     * <ul>
+     *   <li>{@link #CONTROL_AUTOFRAMING_OFF OFF}</li>
+     *   <li>{@link #CONTROL_AUTOFRAMING_ON ON}</li>
+     *   <li>{@link #CONTROL_AUTOFRAMING_AUTO AUTO}</li>
+     * </ul>
+     *
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p><b>Limited capability</b> -
+     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+     *
+     * @see CaptureRequest#CONTROL_ZOOM_RATIO
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+     * @see CaptureRequest#SCALER_CROP_REGION
+     * @see #CONTROL_AUTOFRAMING_OFF
+     * @see #CONTROL_AUTOFRAMING_ON
+     * @see #CONTROL_AUTOFRAMING_AUTO
+     */
+    @PublicKey
+    @NonNull
+    public static final Key<Integer> CONTROL_AUTOFRAMING =
+            new Key<Integer>("android.control.autoframing", int.class);
+
+    /**
      * <p>Operation mode for edge
      * enhancement.</p>
      * <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index c4f0cab..285c933 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2702,12 +2702,8 @@
      * <p><b>Available values for this device:</b><br>
      * {@link CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES android.control.availableSettingsOverrides}</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
-     * <p><b>Limited capability</b> -
-     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
-     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
      *
      * @see CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES
-     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
      * @see #CONTROL_SETTINGS_OVERRIDE_OFF
      * @see #CONTROL_SETTINGS_OVERRIDE_ZOOM
      */
@@ -2717,6 +2713,79 @@
             new Key<Integer>("android.control.settingsOverride", int.class);
 
     /**
+     * <p>Automatic crop, pan and zoom to keep objects in the center of the frame.</p>
+     * <p>Auto-framing is a special mode provided by the camera device to dynamically crop, zoom
+     * or pan the camera feed to try to ensure that the people in a scene occupy a reasonable
+     * portion of the viewport. It is primarily designed to support video calling in
+     * situations where the user isn't directly in front of the device, especially for
+     * wide-angle cameras.
+     * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} and {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} in CaptureResult will be used
+     * to denote the coordinates of the auto-framed region.
+     * Zoom and video stabilization controls are disabled when auto-framing is enabled. The 3A
+     * regions must map the screen coordinates into the scaler crop returned from the capture
+     * result instead of using the active array sensor.</p>
+     * <p><b>Possible values:</b></p>
+     * <ul>
+     *   <li>{@link #CONTROL_AUTOFRAMING_OFF OFF}</li>
+     *   <li>{@link #CONTROL_AUTOFRAMING_ON ON}</li>
+     *   <li>{@link #CONTROL_AUTOFRAMING_AUTO AUTO}</li>
+     * </ul>
+     *
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p><b>Limited capability</b> -
+     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+     *
+     * @see CaptureRequest#CONTROL_ZOOM_RATIO
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+     * @see CaptureRequest#SCALER_CROP_REGION
+     * @see #CONTROL_AUTOFRAMING_OFF
+     * @see #CONTROL_AUTOFRAMING_ON
+     * @see #CONTROL_AUTOFRAMING_AUTO
+     */
+    @PublicKey
+    @NonNull
+    public static final Key<Integer> CONTROL_AUTOFRAMING =
+            new Key<Integer>("android.control.autoframing", int.class);
+
+    /**
+     * <p>Current state of auto-framing.</p>
+     * <p>When the camera doesn't have auto-framing available (i.e
+     * <code>{@link CameraCharacteristics#CONTROL_AUTOFRAMING_AVAILABLE android.control.autoframingAvailable}</code> == false) or it is not enabled (i.e
+     * <code>{@link CaptureRequest#CONTROL_AUTOFRAMING android.control.autoframing}</code> == OFF), the state will always be INACTIVE.
+     * Other states indicate the current auto-framing state:</p>
+     * <ul>
+     * <li>When <code>{@link CaptureRequest#CONTROL_AUTOFRAMING android.control.autoframing}</code> is set to ON, auto-framing will take
+     * place. While the frame is aligning itself to center the object (doing things like
+     * zooming in, zooming out or pan), the state will be FRAMING.</li>
+     * <li>When field of view is not being adjusted anymore and has reached a stable state, the
+     * state will be CONVERGED.</li>
+     * </ul>
+     * <p><b>Possible values:</b></p>
+     * <ul>
+     *   <li>{@link #CONTROL_AUTOFRAMING_STATE_INACTIVE INACTIVE}</li>
+     *   <li>{@link #CONTROL_AUTOFRAMING_STATE_FRAMING FRAMING}</li>
+     *   <li>{@link #CONTROL_AUTOFRAMING_STATE_CONVERGED CONVERGED}</li>
+     * </ul>
+     *
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p><b>Limited capability</b> -
+     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+     *
+     * @see CaptureRequest#CONTROL_AUTOFRAMING
+     * @see CameraCharacteristics#CONTROL_AUTOFRAMING_AVAILABLE
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+     * @see #CONTROL_AUTOFRAMING_STATE_INACTIVE
+     * @see #CONTROL_AUTOFRAMING_STATE_FRAMING
+     * @see #CONTROL_AUTOFRAMING_STATE_CONVERGED
+     */
+    @PublicKey
+    @NonNull
+    public static final Key<Integer> CONTROL_AUTOFRAMING_STATE =
+            new Key<Integer>("android.control.autoframingState", int.class);
+
+    /**
      * <p>Operation mode for edge
      * enhancement.</p>
      * <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
index 935a542..fa2cbe7 100644
--- a/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
@@ -33,4 +33,5 @@
     ISessionProcessorImpl getSessionProcessor();
     CameraMetadataNative getAvailableCaptureRequestKeys(in String cameraId);
     CameraMetadataNative getAvailableCaptureResultKeys(in String cameraId);
+    boolean isCaptureProcessProgressAvailable();
 }
diff --git a/core/java/android/hardware/camera2/extension/ICaptureCallback.aidl b/core/java/android/hardware/camera2/extension/ICaptureCallback.aidl
index f3062ad..02a4690 100644
--- a/core/java/android/hardware/camera2/extension/ICaptureCallback.aidl
+++ b/core/java/android/hardware/camera2/extension/ICaptureCallback.aidl
@@ -27,4 +27,5 @@
     void onCaptureSequenceCompleted(int captureSequenceId);
     void onCaptureSequenceAborted(int captureSequenceId);
     void onCaptureCompleted(long shutterTimestamp, int requestId, in CameraMetadataNative results);
+    void onCaptureProcessProgressed(int progress);
 }
diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
index 3a0c3a5..615536b 100644
--- a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
@@ -42,4 +42,5 @@
     LatencyRange getEstimatedCaptureLatencyRange(in Size outputSize);
     CameraMetadataNative getAvailableCaptureRequestKeys();
     CameraMetadataNative getAvailableCaptureResultKeys();
+    boolean isCaptureProcessProgressAvailable();
 }
diff --git a/core/java/android/hardware/camera2/extension/IOutputSurfaceConfiguration.aidl b/core/java/android/hardware/camera2/extension/IOutputSurfaceConfiguration.aidl
new file mode 100644
index 0000000..70b096f
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/IOutputSurfaceConfiguration.aidl
@@ -0,0 +1,26 @@
+/**
+ * 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.camera2.extension;
+
+import android.hardware.camera2.extension.OutputSurface;
+
+/** @hide */
+interface IOutputSurfaceConfiguration
+{
+    OutputSurface getPreviewOutputSurface();
+    OutputSurface getImageCaptureOutputSurface();
+    OutputSurface getImageAnalysisOutputSurface();
+}
diff --git a/core/java/android/hardware/camera2/extension/IProcessResultImpl.aidl b/core/java/android/hardware/camera2/extension/IProcessResultImpl.aidl
index 4114edb..57f94c0 100644
--- a/core/java/android/hardware/camera2/extension/IProcessResultImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IProcessResultImpl.aidl
@@ -21,4 +21,5 @@
 interface IProcessResultImpl
 {
     void onCaptureCompleted(long shutterTimestamp, in CameraMetadataNative results);
+    void onCaptureProcessProgressed(int progress);
 }
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 01c1ef4..3a8dc03 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -663,6 +663,19 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
+
+        @Override
+        public void onCaptureProcessProgressed(int progress) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(
+                        () -> mClientCallbacks.onCaptureProcessProgressed(
+                                CameraAdvancedExtensionSessionImpl.this, mClientRequest,
+                                progress));
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
     }
 
     private final class CaptureCallbackHandler extends CameraCaptureSession.CaptureCallback {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 1f9f3b8..389c214 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -1387,6 +1387,18 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
+
+        @Override
+        public void onCaptureProcessProgressed(int progress) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(
+                        () -> mCallbacks.onCaptureProcessProgressed(CameraExtensionSessionImpl.this,
+                                mClientRequest, progress));
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
     }
 
     // This handler can operate in three modes:
diff --git a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
index 7b6a457..8304796 100644
--- a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
+++ b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
@@ -26,6 +26,7 @@
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.TreeMap;
 
 /**
@@ -63,11 +64,11 @@
     }
 
     private void update() {
-        Iterator iter = mFutureErrorMap.entrySet().iterator();
+        Iterator<Map.Entry<Long, Integer>> iter = mFutureErrorMap.entrySet().iterator();
         while (iter.hasNext()) {
-            TreeMap.Entry pair = (TreeMap.Entry)iter.next();
-            Long errorFrameNumber = (Long)pair.getKey();
-            int requestType = (int) pair.getValue();
+            Map.Entry<Long, Integer> pair = iter.next();
+            long errorFrameNumber = pair.getKey();
+            int requestType = pair.getValue();
             Boolean removeError = false;
             if (errorFrameNumber == mCompletedFrameNumber[requestType] + 1) {
                 removeError = true;
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java
index 621a418..92a2fb6 100644
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java
@@ -103,6 +103,7 @@
         return new MarshalerEnum(managedType, nativeType);
     }
 
+    @SuppressWarnings("ReturnValueIgnored")
     @Override
     public boolean isTypeMappingSupported(TypeReference<T> managedType, int nativeType) {
         if (nativeType == TYPE_INT32 || nativeType == TYPE_BYTE) {
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/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index a0d8f8d..f4b87b9 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -1344,7 +1344,8 @@
                 return false;
             }
             for (int j = 0; j < mSensorPixelModesUsed.size(); j++) {
-                if (mSensorPixelModesUsed.get(j) != other.mSensorPixelModesUsed.get(j)) {
+                if (!Objects.equals(
+                        mSensorPixelModesUsed.get(j), other.mSensorPixelModesUsed.get(j))) {
                     return false;
                 }
             }
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index bdd45e6..dba1a5e 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -52,6 +52,22 @@
     /** The maximum allowed device state identifier. */
     public static final int MAXIMUM_DEVICE_STATE = 255;
 
+    /**
+     * Intent needed to launch the rear display overlay activity from SysUI
+     *
+     * @hide
+     */
+    public static final String ACTION_SHOW_REAR_DISPLAY_OVERLAY =
+            "com.android.intent.action.SHOW_REAR_DISPLAY_OVERLAY";
+
+    /**
+     * Intent extra sent to the rear display overlay activity of the current base state
+     *
+     * @hide
+     */
+    public static final String EXTRA_ORIGINAL_DEVICE_BASE_STATE =
+            "original_device_base_state";
+
     private final DeviceStateManagerGlobal mGlobal;
 
     /** @hide */
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index 738045d..7756b9c 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -51,7 +51,7 @@
      * connection with the device state service couldn't be established.
      */
     @Nullable
-    static DeviceStateManagerGlobal getInstance() {
+    public static DeviceStateManagerGlobal getInstance() {
         synchronized (DeviceStateManagerGlobal.class) {
             if (sInstance == null) {
                 IBinder b = ServiceManager.getService(Context.DEVICE_STATE_SERVICE);
@@ -259,6 +259,22 @@
         }
     }
 
+    /**
+     * Provides notification to the system server that a device state feature overlay
+     * was dismissed. This should only be called from the {@link android.app.Activity} that
+     * was showing the overlay corresponding to the feature.
+     *
+     * Validation of there being an overlay visible and pending state request is handled on the
+     * system server.
+     */
+    public void onStateRequestOverlayDismissed(boolean shouldCancelRequest) {
+        try {
+            mDeviceStateManager.onStateRequestOverlayDismissed(shouldCancelRequest);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
     private void registerCallbackIfNeededLocked() {
         if (mCallback == null) {
             mCallback = new DeviceStateManagerCallback();
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
index 7175eae..0993160 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -103,4 +103,15 @@
     @JavaPassthrough(annotation=
         "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
     void cancelBaseStateOverride();
+
+    /**
+    * Notifies the system service that the educational overlay that was launched
+    * before entering a requested state was dismissed or closed, and provides
+    * the system information on if the pairing mode should be canceled or not.
+    *
+    * This should only be called from the overlay itself.
+    */
+    @JavaPassthrough(annotation=
+        "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
+    void onStateRequestOverlayDismissed(boolean shouldCancelRequest);
 }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 8311190..23d108f 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -137,7 +137,8 @@
             VIRTUAL_DISPLAY_FLAG_TRUSTED,
             VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
             VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED,
-            VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED
+            VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED,
+            VIRTUAL_DISPLAY_FLAG_OWN_FOCUS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface VirtualDisplayFlag {}
@@ -403,6 +404,31 @@
      */
     public static final int VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 13;
 
+    /**
+     * Virtual display flags: Indicates that the display maintains its own focus and touch mode.
+     *
+     * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+     * behavior, but only applies to the specific display instead of system-wide to all displays.
+     *
+     * Note: The display must be trusted in order to have its own focus.
+     *
+     * @see #createVirtualDisplay
+     * @see #VIRTUAL_DISPLAY_FLAG_TRUSTED
+     * @hide
+     */
+    @TestApi
+    public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 1 << 14;
+
+    /**
+     * Virtual display flags: Indicates that the display should not be a part of the default
+     * DisplayGroup and instead be part of a DisplayGroup associated with its virtual device.
+     *
+     * @see #createVirtualDisplay
+     * @hide
+     */
+    public static final int VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP = 1 << 15;
+
+
     /** @hide */
     @IntDef(prefix = {"MATCH_CONTENT_FRAMERATE_"}, value = {
             MATCH_CONTENT_FRAMERATE_UNKNOWN,
@@ -439,12 +465,13 @@
             SWITCHING_TYPE_NONE,
             SWITCHING_TYPE_WITHIN_GROUPS,
             SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS,
+            SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SwitchingType {}
 
     /**
-     * No mode switching will happen.
+     * No display mode switching will happen.
      * @hide
      */
     @TestApi
@@ -467,6 +494,13 @@
     public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2;
 
     /**
+     * Allow render frame rate switches, but not physical modes.
+     * @hide
+     */
+    @TestApi
+    public static final int SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY = 3;
+
+    /**
      * @hide
      */
     @LongDef(flag = true, prefix = {"EVENT_FLAG_"}, value = {
@@ -563,18 +597,20 @@
      * @see #DISPLAY_CATEGORY_PRESENTATION
      */
     public Display[] getDisplays(String category) {
-        final int[] displayIds = mGlobal.getDisplayIds();
+        boolean includeDisabled = (category != null
+                && category.equals(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED));
+        final int[] displayIds = mGlobal.getDisplayIds(includeDisabled);
         synchronized (mLock) {
             try {
-                if (category == null
-                        || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) {
-                    addAllDisplaysLocked(mTempDisplays, displayIds);
-                } else if (category.equals(DISPLAY_CATEGORY_PRESENTATION)) {
+                if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) {
                     addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI);
                     addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_EXTERNAL);
                     addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY);
                     addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL);
                     addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_INTERNAL);
+                } else if (category == null
+                        || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) {
+                    addAllDisplaysLocked(mTempDisplays, displayIds);
                 }
                 return mTempDisplays.toArray(new Display[mTempDisplays.size()]);
             } finally {
@@ -1308,6 +1344,7 @@
         switch (switchingType) {
             case SWITCHING_TYPE_NONE:
                 return MATCH_CONTENT_FRAMERATE_NEVER;
+            case SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY:
             case SWITCHING_TYPE_WITHIN_GROUPS:
                 return MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY;
             case SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS:
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 79223f5..f038c66 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -113,7 +113,7 @@
 
     private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<>();
     private final ColorSpace mWideColorSpace;
-    private final OverlayProperties mOverlayProperties = new OverlayProperties();
+    private final OverlayProperties mOverlayProperties;
     private int[] mDisplayIdCache;
 
     private int mWifiDisplayScanNestCount;
@@ -125,6 +125,7 @@
             mWideColorSpace =
                     ColorSpace.get(
                             ColorSpace.Named.values()[mDm.getPreferredWideGamutColorSpaceId()]);
+            mOverlayProperties = mDm.getOverlaySupport();
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -211,6 +212,16 @@
      */
     @UnsupportedAppUsage
     public int[] getDisplayIds() {
+        return getDisplayIds(/* includeDisabled= */ false);
+    }
+
+    /**
+     * Gets all currently valid logical display ids.
+     *
+     * @param includeDisabled True if the returned list of displays includes disabled displays.
+     * @return An array containing all display ids.
+     */
+    public int[] getDisplayIds(boolean includeDisabled) {
         try {
             synchronized (mLock) {
                 if (USE_CACHE) {
@@ -219,7 +230,7 @@
                     }
                 }
 
-                int[] displayIds = mDm.getDisplayIds();
+                int[] displayIds = mDm.getDisplayIds(includeDisabled);
                 if (USE_CACHE) {
                     mDisplayIdCache = displayIds;
                 }
@@ -728,7 +739,11 @@
         return mWideColorSpace;
     }
 
-    /** @hide */
+    /**
+     * Gets the overlay properties for all displays.
+     *
+     * @hide
+     */
     public OverlayProperties getOverlaySupport() {
         return mOverlayProperties;
     }
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 6b3e673..829908f 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -412,7 +412,7 @@
      * the PowerManagerService to focus on the global power state and not
      * have to micro-manage screen off animations, auto-brightness and other effects.
      */
-    public static final class DisplayPowerRequest {
+    public static class DisplayPowerRequest {
         // Policy: Turn screen off as if the user pressed the power button
         // including playing a screen off animation if applicable.
         public static final int POLICY_OFF = 0;
@@ -423,8 +423,6 @@
         public static final int POLICY_DIM = 2;
         // Policy: Make the screen bright as usual.
         public static final int POLICY_BRIGHT = 3;
-        // Policy: Keep the screen and display optimized for VR mode.
-        public static final int POLICY_VR = 4;
 
         // The basic overall policy to apply: off, doze, dim or bright.
         public int policy;
@@ -489,10 +487,6 @@
             return policy == POLICY_BRIGHT || policy == POLICY_DIM;
         }
 
-        public boolean isVr() {
-            return policy == POLICY_VR;
-        }
-
         public void copyFrom(DisplayPowerRequest other) {
             policy = other.policy;
             useProximitySensor = other.useProximitySensor;
@@ -566,8 +560,6 @@
                     return "DIM";
                 case POLICY_BRIGHT:
                     return "BRIGHT";
-                case POLICY_VR:
-                    return "VR";
                 default:
                     return Integer.toString(policy);
             }
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index b166e21..28bb35f 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -18,6 +18,7 @@
 
 import android.content.pm.ParceledListSlice;
 import android.graphics.Point;
+import android.hardware.OverlayProperties;
 import android.hardware.display.BrightnessConfiguration;
 import android.hardware.display.BrightnessInfo;
 import android.hardware.display.Curve;
@@ -36,7 +37,7 @@
 interface IDisplayManager {
     @UnsupportedAppUsage
     DisplayInfo getDisplayInfo(int displayId);
-    int[] getDisplayIds();
+    int[] getDisplayIds(boolean includeDisabled);
 
     boolean isUidPresentOnDisplay(int uid, int displayId);
 
@@ -192,4 +193,7 @@
     // to set the layerStack after the display was created, which is not something we support in
     // DMS. This should be deleted in V release.
     void setDisplayIdToMirror(in IBinder token, int displayId);
+
+    // Query overlay properties of the device
+    OverlayProperties getOverlaySupport();
 }
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index b76b98d..891ba36 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -30,6 +30,9 @@
 
 import com.android.internal.util.DataClass;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Holds configuration used to create {@link VirtualDisplay} instances. See
  * {@link MediaProjection#createVirtualDisplay(VirtualDisplayConfig, VirtualDisplay.Callback, Handler)}.
@@ -99,6 +102,13 @@
      */
     private boolean mWindowManagerMirroring = false;
 
+    /**
+     * The display categories. If set, only corresponding activities from the same category can be
+     * shown on the display.
+     */
+    @DataClass.PluralOf("displayCategory")
+    @NonNull private List<String> mDisplayCategories = new ArrayList<>();
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -124,7 +134,8 @@
             @Nullable Surface surface,
             @Nullable String uniqueId,
             int displayIdToMirror,
-            boolean windowManagerMirroring) {
+            boolean windowManagerMirroring,
+            @NonNull List<String> displayCategories) {
         this.mName = name;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mName);
@@ -147,6 +158,9 @@
         this.mUniqueId = uniqueId;
         this.mDisplayIdToMirror = displayIdToMirror;
         this.mWindowManagerMirroring = windowManagerMirroring;
+        this.mDisplayCategories = displayCategories;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDisplayCategories);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -233,6 +247,15 @@
         return mWindowManagerMirroring;
     }
 
+    /**
+     * The display categories. If set, only corresponding activities from the same category can be
+     * shown on the display.
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<String> getDisplayCategories() {
+        return mDisplayCategories;
+    }
+
     @Override
     @DataClass.Generated.Member
     public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -252,6 +275,7 @@
         if (mSurface != null) dest.writeTypedObject(mSurface, flags);
         if (mUniqueId != null) dest.writeString(mUniqueId);
         dest.writeInt(mDisplayIdToMirror);
+        dest.writeStringList(mDisplayCategories);
     }
 
     @Override
@@ -275,6 +299,8 @@
         Surface surface = (flg & 0x20) == 0 ? null : (Surface) in.readTypedObject(Surface.CREATOR);
         String uniqueId = (flg & 0x40) == 0 ? null : in.readString();
         int displayIdToMirror = in.readInt();
+        List<String> displayCategories = new ArrayList<>();
+        in.readStringList(displayCategories);
 
         this.mName = name;
         com.android.internal.util.AnnotationValidations.validate(
@@ -298,6 +324,9 @@
         this.mUniqueId = uniqueId;
         this.mDisplayIdToMirror = displayIdToMirror;
         this.mWindowManagerMirroring = windowManagerMirroring;
+        this.mDisplayCategories = displayCategories;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDisplayCategories);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -332,6 +361,7 @@
         private @Nullable String mUniqueId;
         private int mDisplayIdToMirror;
         private boolean mWindowManagerMirroring;
+        private @NonNull List<String> mDisplayCategories;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -478,10 +508,30 @@
             return this;
         }
 
+        /**
+         * The display categories. If set, only corresponding activities from the same category can be
+         * shown on the display.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setDisplayCategories(@NonNull List<String> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x200;
+            mDisplayCategories = value;
+            return this;
+        }
+
+        /** @see #setDisplayCategories */
+        @DataClass.Generated.Member
+        public @NonNull Builder addDisplayCategory(@NonNull String value) {
+            if (mDisplayCategories == null) setDisplayCategories(new ArrayList<>());
+            mDisplayCategories.add(value);
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull VirtualDisplayConfig build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x200; // Mark builder used
+            mBuilderFieldsSet |= 0x400; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x10) == 0) {
                 mFlags = 0;
@@ -498,6 +548,9 @@
             if ((mBuilderFieldsSet & 0x100) == 0) {
                 mWindowManagerMirroring = false;
             }
+            if ((mBuilderFieldsSet & 0x200) == 0) {
+                mDisplayCategories = new ArrayList<>();
+            }
             VirtualDisplayConfig o = new VirtualDisplayConfig(
                     mName,
                     mWidth,
@@ -507,12 +560,13 @@
                     mSurface,
                     mUniqueId,
                     mDisplayIdToMirror,
-                    mWindowManagerMirroring);
+                    mWindowManagerMirroring,
+                    mDisplayCategories);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x200) != 0) {
+            if ((mBuilderFieldsSet & 0x400) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -520,10 +574,10 @@
     }
 
     @DataClass.Generated(
-            time = 1646227247934L,
+            time = 1668534501320L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java",
-            inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate  int mDisplayIdToMirror\nprivate  boolean mWindowManagerMirroring\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
+            inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate  int mDisplayIdToMirror\nprivate  boolean mWindowManagerMirroring\nprivate @com.android.internal.util.DataClass.PluralOf(\"displayCategory\") @android.annotation.NonNull java.util.List<java.lang.String> mDisplayCategories\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/hardware/fingerprint/Fingerprint.java b/core/java/android/hardware/fingerprint/Fingerprint.java
index 9ce834ca..c01c94c 100644
--- a/core/java/android/hardware/fingerprint/Fingerprint.java
+++ b/core/java/android/hardware/fingerprint/Fingerprint.java
@@ -69,4 +69,4 @@
             return new Fingerprint[size];
         }
     };
-};
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 3c73eb6..a748b60 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -49,6 +49,7 @@
 import android.hardware.biometrics.BiometricTestSession;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.SensorProperties;
+import android.hardware.biometrics.fingerprint.PointerContext;
 import android.os.Binder;
 import android.os.Build;
 import android.os.CancellationSignal;
@@ -955,12 +956,18 @@
     public void onPointerDown(long requestId, int sensorId, int x, int y,
             float minor, float major) {
         if (mService == null) {
-            Slog.w(TAG, "onFingerDown: no fingerprint service");
+            Slog.w(TAG, "onPointerDown: no fingerprint service");
             return;
         }
 
+        final PointerContext pc = new PointerContext();
+        pc.x = (int) x;
+        pc.y = (int) y;
+        pc.minor = minor;
+        pc.major = major;
+
         try {
-            mService.onPointerDown(requestId, sensorId, x, y, minor, major);
+            mService.onPointerDown(requestId, sensorId, pc);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -972,12 +979,94 @@
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
     public void onPointerUp(long requestId, int sensorId) {
         if (mService == null) {
-            Slog.w(TAG, "onFingerDown: no fingerprint service");
+            Slog.w(TAG, "onPointerUp: no fingerprint service");
             return;
         }
 
+        final PointerContext pc = new PointerContext();
+
         try {
-            mService.onPointerUp(requestId, sensorId);
+            mService.onPointerUp(requestId, sensorId, pc);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * TODO(b/218388821): The parameter list should be replaced with PointerContext.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void onPointerDown(
+            long requestId,
+            int sensorId,
+            int pointerId,
+            float x,
+            float y,
+            float minor,
+            float major,
+            float orientation,
+            long time,
+            long gestureStart,
+            boolean isAod) {
+        if (mService == null) {
+            Slog.w(TAG, "onPointerDown: no fingerprint service");
+            return;
+        }
+
+        final PointerContext pc = new PointerContext();
+        pc.pointerId = pointerId;
+        pc.x = x;
+        pc.y = y;
+        pc.minor = minor;
+        pc.major = major;
+        pc.orientation = orientation;
+        pc.time = time;
+        pc.gestureStart = gestureStart;
+        pc.isAod = isAod;
+
+        try {
+            mService.onPointerDown(requestId, sensorId, pc);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * TODO(b/218388821): The parameter list should be replaced with PointerContext.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void onPointerUp(
+            long requestId,
+            int sensorId,
+            int pointerId,
+            float x,
+            float y,
+            float minor,
+            float major,
+            float orientation,
+            long time,
+            long gestureStart,
+            boolean isAod) {
+        if (mService == null) {
+            Slog.w(TAG, "onPointerUp: no fingerprint service");
+            return;
+        }
+
+        final PointerContext pc = new PointerContext();
+        pc.pointerId = pointerId;
+        pc.x = x;
+        pc.y = y;
+        pc.minor = minor;
+        pc.major = major;
+        pc.orientation = orientation;
+        pc.time = time;
+        pc.gestureStart = gestureStart;
+        pc.isAod = isAod;
+
+        try {
+            mService.onPointerUp(requestId, sensorId, pc);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 365a6b3..6f35713 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.IFingerprintClientActiveCallback;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -184,11 +185,11 @@
 
     // Notifies about a finger touching the sensor area.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major);
+    void onPointerDown(long requestId, int sensorId, in PointerContext pc);
 
     // Notifies about a finger leaving the sensor area.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void onPointerUp(long requestId, int sensorId);
+    void onPointerUp(long requestId, int sensorId, in PointerContext pc);
 
     // Notifies about the fingerprint UI being ready (e.g. HBM illumination is enabled).
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
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/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/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 3e509e4..b0b7a41 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -50,7 +50,7 @@
 
 /**
  * The {@link HdmiControlManager} class is used to send HDMI control messages
- * to attached CEC devices.
+ * to attached CEC devices. It also allows to control the eARC feature.
  *
  * <p>Provides various HDMI client instances that represent HDMI-CEC logical devices
  * hosted in the system. {@link #getTvClient()}, for instance will return an
@@ -398,6 +398,30 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface RoutingControl {}
 
+    // -- Whether the Soundbar mode feature is enabled or disabled.
+    /**
+     * Soundbar mode feature enabled.
+     *
+     * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+     */
+    public static final int SOUNDBAR_MODE_ENABLED = 1;
+    /**
+     * Soundbar mode feature disabled.
+     *
+     * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+     */
+    public static final int SOUNDBAR_MODE_DISABLED = 0;
+    /**
+     * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+     * @hide
+     */
+    @IntDef(prefix = { "SOUNDBAR_MODE" }, value = {
+            SOUNDBAR_MODE_ENABLED,
+            SOUNDBAR_MODE_DISABLED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SoundbarMode {}
+
     // -- Scope of CEC power control messages sent by a playback device.
     /**
      * Send CEC power control messages to TV only:
@@ -775,6 +799,31 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface SadPresenceInQuery {}
 
+    // -- Whether eARC is enabled or disabled.
+    /**
+     * eARC enabled.
+     *
+     * @see HdmiControlManager#SETTING_NAME_EARC_ENABLED
+     */
+    public static final int EARC_FEATURE_ENABLED = 1;
+    /**
+     * eARC disabled.
+     *
+     * @see HdmiControlManager#SETTING_NAME_EARC_ENABLED
+     */
+    public static final int EARC_FEATURE_DISABLED = 0;
+    /**
+     * @hide
+     *
+     * @see HdmiControlManager#SETTING_NAME_EARC_ENABLED
+     */
+    @IntDef(prefix = { "EARC_FEATURE" }, value = {
+            EARC_FEATURE_ENABLED,
+            EARC_FEATURE_DISABLED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EarcFeature {}
+
     // -- Settings available in the CEC Configuration.
     /**
      * Name of a setting deciding whether the CEC is enabled.
@@ -795,6 +844,14 @@
      */
     public static final String CEC_SETTING_NAME_ROUTING_CONTROL = "routing_control";
     /**
+     * Name of a setting deciding whether the Soundbar mode feature is enabled.
+     * Before exposing this setting make sure the hardware supports it, otherwise, you may
+     * experience multiple issues.
+     *
+     * @see HdmiControlManager#setSoundbarMode(int)
+     */
+    public static final String CEC_SETTING_NAME_SOUNDBAR_MODE = "soundbar_mode";
+    /**
      * Name of a setting deciding on the power control mode.
      *
      * @see HdmiControlManager#setPowerControlMode(String)
@@ -1032,11 +1089,21 @@
      */
     public static final String CEC_SETTING_NAME_QUERY_SAD_MAX = "query_sad_max";
     /**
+     * Name of a setting representing whether eARC is enabled or not.
+     *
+     * @see HdmiControlManager#setEarcEnabled(int)
+     */
+    public static final String SETTING_NAME_EARC_ENABLED = "earc_enabled";
+    /**
      * @hide
      */
-    @StringDef(prefix = { "CEC_SETTING_NAME_" }, value = {
+    // TODO(b/240379115): change names of CEC settings so that their prefix matches with the other
+    // HDMI control settings.
+    @StringDef(value = {
         CEC_SETTING_NAME_HDMI_CEC_ENABLED,
         CEC_SETTING_NAME_HDMI_CEC_VERSION,
+        CEC_SETTING_NAME_ROUTING_CONTROL,
+        CEC_SETTING_NAME_SOUNDBAR_MODE,
         CEC_SETTING_NAME_POWER_CONTROL_MODE,
         CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
         CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
@@ -1066,8 +1133,9 @@
         CEC_SETTING_NAME_QUERY_SAD_DST,
         CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
         CEC_SETTING_NAME_QUERY_SAD_MAX,
+        SETTING_NAME_EARC_ENABLED,
     })
-    public @interface CecSettingName {}
+    public @interface SettingName {}
 
     /**
      * @hide
@@ -1188,7 +1256,16 @@
             case HdmiDeviceInfo.DEVICE_PLAYBACK:
                 return mHasPlaybackDevice ? new HdmiPlaybackClient(mService) : null;
             case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM:
-                return mHasAudioSystemDevice ? new HdmiAudioSystemClient(mService) : null;
+                try {
+                    if ((mService.getCecSettingIntValue(CEC_SETTING_NAME_SOUNDBAR_MODE)
+                            == SOUNDBAR_MODE_ENABLED && mHasPlaybackDevice)
+                            || mHasAudioSystemDevice) {
+                        return new HdmiAudioSystemClient(mService);
+                    }
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                return null;
             case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH:
                 return (mHasSwitchDevice || mIsSwitchDevice)
                     ? new HdmiSwitchClient(mService) : null;
@@ -1291,6 +1368,22 @@
     }
 
     /**
+     * Get the list of the HDMI ports configuration.
+     *
+     * <p>This returns an empty list when the current device does not have HDMI ports.
+     *
+     * @return a list of {@link HdmiPortInfo}
+     */
+    @NonNull
+    public List<HdmiPortInfo> getPortInfo() {
+        try {
+            return mService.getPortInfo();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Power off the target device by sending CEC commands. Note that this device can't be the
      * current device itself.
      *
@@ -1685,7 +1778,7 @@
     public void addHotplugEventListener(@NonNull @CallbackExecutor Executor executor,
             @NonNull HotplugEventListener listener) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "addHotplugEventListener: HdmiControlService is not available");
             return;
         }
         if (mHotplugEventListeners.containsKey(listener)) {
@@ -1710,7 +1803,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public void removeHotplugEventListener(HotplugEventListener listener) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "removeHotplugEventListener: HdmiControlService is not available");
             return;
         }
         IHdmiHotplugEventListener wrappedListener = mHotplugEventListeners.remove(listener);
@@ -1778,7 +1871,7 @@
     public void addHdmiControlStatusChangeListener(@NonNull @CallbackExecutor Executor executor,
             @NonNull HdmiControlStatusChangeListener listener) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "addHdmiControlStatusChangeListener: HdmiControlService is not available");
             return;
         }
         if (mHdmiControlStatusChangeListeners.containsKey(listener)) {
@@ -1805,7 +1898,8 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public void removeHdmiControlStatusChangeListener(HdmiControlStatusChangeListener listener) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG,
+                    "removeHdmiControlStatusChangeListener: HdmiControlService is not available");
             return;
         }
         IHdmiControlStatusChangeListener wrappedListener =
@@ -1854,7 +1948,8 @@
     public void addHdmiCecVolumeControlFeatureListener(@NonNull @CallbackExecutor Executor executor,
             @NonNull HdmiCecVolumeControlFeatureListener listener) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG,
+                    "addHdmiCecVolumeControlFeatureListener: HdmiControlService is not available");
             return;
         }
         if (mHdmiCecVolumeControlFeatureListeners.containsKey(listener)) {
@@ -1882,7 +1977,9 @@
     public void removeHdmiCecVolumeControlFeatureListener(
             HdmiCecVolumeControlFeatureListener listener) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG,
+                    "removeHdmiCecVolumeControlFeatureListener: HdmiControlService is not "
+                            + "available");
             return;
         }
         IHdmiCecVolumeControlFeatureListener wrappedListener =
@@ -1922,7 +2019,7 @@
          *
          * @param setting name of a CEC setting that changed
          */
-        void onChange(@NonNull @CecSettingName String setting);
+        void onChange(@NonNull @SettingName String setting);
     }
 
     private final ArrayMap<String,
@@ -1930,11 +2027,11 @@
                     mCecSettingChangeListeners = new ArrayMap<>();
 
     private void addCecSettingChangeListener(
-            @NonNull @CecSettingName String setting,
+            @NonNull @SettingName String setting,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull CecSettingChangeListener listener) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "addCecSettingChangeListener: HdmiControlService is not available");
             return;
         }
         if (mCecSettingChangeListeners.containsKey(setting)
@@ -1956,10 +2053,10 @@
     }
 
     private void removeCecSettingChangeListener(
-            @NonNull @CecSettingName String setting,
+            @NonNull @SettingName String setting,
             @NonNull CecSettingChangeListener listener) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "removeCecSettingChangeListener: HdmiControlService is not available");
             return;
         }
         IHdmiCecSettingChangeListener wrappedListener =
@@ -1992,17 +2089,20 @@
     }
 
     /**
-     * Get a set of user-modifiable settings.
+     * Get a set of user-modifiable HDMI control settings.
+     * This applies to CEC settings and eARC settings.
      *
      * @return a set of user-modifiable settings.
      * @throws RuntimeException when the HdmiControlService is not available.
      */
+    // TODO(b/240379115): rename this API to represent that this applies to all HDMI control
+    // settings and not just CEC settings.
     @NonNull
-    @CecSettingName
+    @SettingName
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public List<String> getUserCecSettings() {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "getUserCecSettings: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2013,7 +2113,9 @@
     }
 
     /**
-     * Get a set of allowed values for a setting (string value-type).
+     * Get a set of allowed values for an HDMI control setting (string value-type).
+     * This applies to CEC settings and eARC settings.
+     *
      *
      * @param name name of the setting
      * @return a set of allowed values for a settings. {@code null} on failure.
@@ -2021,11 +2123,13 @@
      * @throws IllegalArgumentException when setting {@code name} value type is invalid.
      * @throws RuntimeException when the HdmiControlService is not available.
      */
+    // TODO(b/240379115): rename this API to represent that this applies to all HDMI control
+    // settings and not just CEC settings.
     @NonNull
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
-    public List<String> getAllowedCecSettingStringValues(@NonNull @CecSettingName String name) {
+    public List<String> getAllowedCecSettingStringValues(@NonNull @SettingName String name) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "getAllowedCecSettingStringValues: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2036,7 +2140,8 @@
     }
 
     /**
-     * Get a set of allowed values for a setting (int value-type).
+     * Get a set of allowed values for an HDMI control setting (int value-type).
+     * This applies to CEC settings and eARC settings.
      *
      * @param name name of the setting
      * @return a set of allowed values for a settings. {@code null} on failure.
@@ -2044,11 +2149,13 @@
      * @throws IllegalArgumentException when setting {@code name} value type is invalid.
      * @throws RuntimeException when the HdmiControlService is not available.
      */
+    // TODO(b/240379115): rename this API to represent that this applies to all HDMI control
+    // settings and not just CEC settings.
     @NonNull
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
-    public List<Integer> getAllowedCecSettingIntValues(@NonNull @CecSettingName String name) {
+    public List<Integer> getAllowedCecSettingIntValues(@NonNull @SettingName String name) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "getAllowedCecSettingIntValues: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2067,7 +2174,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public void setHdmiCecEnabled(@NonNull @HdmiCecControl int value) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "setHdmiCecEnabled: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2087,7 +2194,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public int getHdmiCecEnabled() {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "getHdmiCecEnabled: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2145,7 +2252,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public void setHdmiCecVersion(@NonNull @HdmiCecVersion int value) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "setHdmiCecVersion: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2167,7 +2274,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public int getHdmiCecVersion() {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "getHdmiCecVersion: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2190,7 +2297,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public void setRoutingControl(@NonNull @RoutingControl int value) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "setRoutingControl: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2215,7 +2322,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public int getRoutingControl() {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "getRoutingControl: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2226,6 +2333,54 @@
     }
 
     /**
+     * Set the status of Soundbar mode feature.
+     *
+     * <p>This allows to enable/disable Soundbar mode on the playback device.
+     * The setting's effect will be available on devices where the hardware supports this feature.
+     * If enabled, an audio system local device will be allocated and try to establish an ARC
+     * connection with the TV. If disabled, the ARC connection will be terminated and the audio
+     * system local device will be removed from the network.
+     *
+     * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+     */
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public void setSoundbarMode(@SoundbarMode int value) {
+        if (mService == null) {
+            Log.e(TAG, "setSoundbarMode: HdmiControlService is not available");
+            throw new RuntimeException("HdmiControlService is not available");
+        }
+        try {
+            mService.setCecSettingIntValue(CEC_SETTING_NAME_SOUNDBAR_MODE, value);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the current status of Soundbar mode feature.
+     *
+     * <p>Reflects whether Soundbar mode is currently enabled on the playback device.
+     * If enabled, an audio system local device will be allocated and try to establish an ARC
+     * connection with the TV. If disabled, the ARC connection will be terminated and the audio
+     * system local device will be removed from the network.
+     *
+     * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+     */
+    @SoundbarMode
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public int getSoundbarMode() {
+        if (mService == null) {
+            Log.e(TAG, "getSoundbarMode: HdmiControlService is not available");
+            throw new RuntimeException("HdmiControlService is not available");
+        }
+        try {
+            return mService.getCecSettingIntValue(CEC_SETTING_NAME_SOUNDBAR_MODE);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Set the status of Power Control.
      *
      * <p>Specifies to which devices Power Control messages should be sent:
@@ -2236,7 +2391,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public void setPowerControlMode(@NonNull @PowerControlMode String value) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "setPowerControlMode: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2259,7 +2414,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public String getPowerControlMode() {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "getPowerControlMode: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2280,7 +2435,8 @@
     public void setPowerStateChangeOnActiveSourceLost(
             @NonNull @ActiveSourceLostBehavior String value) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG,
+                    "setPowerStateChangeOnActiveSourceLost: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2303,7 +2459,8 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public String getPowerStateChangeOnActiveSourceLost() {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG,
+                    "getPowerStateChangeOnActiveSourceLost: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2328,7 +2485,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public void setSystemAudioControl(@NonNull @SystemAudioControl int value) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "setSystemAudioControl: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2354,7 +2511,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public int getSystemAudioControl() {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "getSystemAudioControl: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2374,7 +2531,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public void setSystemAudioModeMuting(@NonNull @SystemAudioModeMuting int value) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "setSystemAudioModeMuting: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2396,7 +2553,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public int getSystemAudioModeMuting() {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "getSystemAudioModeMuting: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2417,7 +2574,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public void setTvWakeOnOneTouchPlay(@NonNull @TvWakeOnOneTouchPlay int value) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "setTvWakeOnOneTouchPlay: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2440,7 +2597,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public int getTvWakeOnOneTouchPlay() {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "getTvWakeOnOneTouchPlay: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2461,7 +2618,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public void setTvSendStandbyOnSleep(@NonNull @TvSendStandbyOnSleep int value) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "setTvSendStandbyOnSleep: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2484,7 +2641,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public int getTvSendStandbyOnSleep() {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "getTvSendStandbyOnSleep: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2516,7 +2673,7 @@
     public void setSadPresenceInQuery(@NonNull @CecSettingSad String setting,
             @SadPresenceInQuery int value) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "setSadPresenceInQuery: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2550,7 +2707,7 @@
     public void setSadsPresenceInQuery(@NonNull @CecSettingSad List<String> settings,
             @SadPresenceInQuery int value) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "setSadsPresenceInQuery: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2582,7 +2739,7 @@
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
     public int getSadPresenceInQuery(@NonNull @CecSettingSad String setting) {
         if (mService == null) {
-            Log.e(TAG, "HdmiControlService is not available");
+            Log.e(TAG, "getSadPresenceInQuery: HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
@@ -2591,4 +2748,44 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Set the global status of eARC.
+     *
+     * <p>This allows to enable/disable the eARC feature on the device. If the feature is enabled
+     * and the hardware supports eARC as well, the device can attempt to establish an eARC
+     * connection.
+     */
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public void setEarcEnabled(@NonNull @EarcFeature int value) {
+        if (mService == null) {
+            Log.e(TAG, "setEarcEnabled: HdmiControlService is not available");
+            throw new RuntimeException("HdmiControlService is not available");
+        }
+        try {
+            mService.setCecSettingIntValue(SETTING_NAME_EARC_ENABLED, value);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the current global status of eARC.
+     *
+     * <p>Reflects whether the eARC feature is currently enabled on the device.
+     */
+    @NonNull
+    @EarcFeature
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public int getEarcEnabled() {
+        if (mService == null) {
+            Log.e(TAG, "getEarcEnabled: HdmiControlService is not available");
+            throw new RuntimeException("HdmiControlService is not available");
+        }
+        try {
+            return mService.getCecSettingIntValue(SETTING_NAME_EARC_ENABLED);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
index 0fb5894..8aaeec3 100644
--- a/core/java/android/hardware/hdmi/HdmiSwitchClient.java
+++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
@@ -184,7 +184,10 @@
      * <p>This returns an empty list when the current device does not have HDMI input.
      *
      * @return a list of {@link HdmiPortInfo}
+     *
+     * @deprecated Please use {@link HdmiControlManager#getPortInfo()} instead.
      */
+    @Deprecated
     @NonNull
     public List<HdmiPortInfo> getPortInfo() {
         try {
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 49c0f92..b26c0a2 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -32,6 +32,8 @@
 import android.os.IBinder;
 import android.os.IVibratorStateListener;
 import android.os.VibrationEffect;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.InputMonitor;
@@ -80,17 +82,47 @@
 
     // Keyboard layouts configuration.
     KeyboardLayout[] getKeyboardLayouts();
+
     KeyboardLayout[] getKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
+
     KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
+
     String getCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier);
+
+    @EnforcePermission("SET_KEYBOARD_LAYOUT")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
     void setCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor);
+
     String[] getEnabledKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
+
+    @EnforcePermission("SET_KEYBOARD_LAYOUT")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
     void addKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor);
+
+    @EnforcePermission("SET_KEYBOARD_LAYOUT")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
     void removeKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor);
 
+    // New Keyboard layout config APIs
+    String getKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, int userId,
+            in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+
+    @EnforcePermission("SET_KEYBOARD_LAYOUT")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
+    void setKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, int userId,
+            in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype,
+            String keyboardLayoutDescriptor);
+
+    String[] getKeyboardLayoutListForInputDevice(in InputDeviceIdentifier identifier, int userId,
+            in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+
     // Registers an input devices changed listener.
     void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
 
@@ -168,4 +200,9 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.BLUETOOTH)")
     String getInputDeviceBluetoothAddress(int deviceId);
+
+    @EnforcePermission("MONITOR_INPUT")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.MONITOR_INPUT)")
+    void pilferPointers(IBinder inputChannelToken);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 0cf15f7..cea3fa1 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -26,10 +26,12 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UserIdInt;
 import android.app.ActivityThread;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.BatteryState;
 import android.hardware.SensorManager;
 import android.hardware.lights.Light;
@@ -65,6 +67,8 @@
 import android.view.PointerIcon;
 import android.view.VerifiedInputEvent;
 import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -888,6 +892,91 @@
     }
 
     /**
+     * Gets the keyboard layout descriptor for the specified input device, userId, imeInfo and
+     * imeSubtype.
+     *
+     * @param identifier Identifier for the input device
+     * @param userId user profile ID
+     * @param imeInfo contains IME information like imeId, etc.
+     * @param imeSubtype contains IME subtype information like input languageTag, layoutType, etc.
+     * @return The keyboard layout descriptor, or null if no keyboard layout has been set.
+     *
+     * @hide
+     */
+    @Nullable
+    public String getKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @NonNull InputMethodSubtype imeSubtype) {
+        try {
+            return mIm.getKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the keyboard layout descriptor for the specified input device, userId, imeInfo and
+     * imeSubtype.
+     *
+     * <p>
+     * This method may have the side-effect of causing the input device in question to be
+     * reconfigured.
+     * </p>
+     *
+     * @param identifier The identifier for the input device.
+     * @param userId user profile ID
+     * @param imeInfo contains IME information like imeId, etc.
+     * @param imeSubtype contains IME subtype information like input languageTag, layoutType, etc.
+     * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
+    public void setKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @NonNull InputMethodSubtype imeSubtype, @NonNull String keyboardLayoutDescriptor) {
+        if (identifier == null) {
+            throw new IllegalArgumentException("identifier must not be null");
+        }
+        if (keyboardLayoutDescriptor == null) {
+            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+        }
+
+        try {
+            mIm.setKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype,
+                    keyboardLayoutDescriptor);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets all keyboard layout descriptors that are enabled for the specified input device, userId,
+     * imeInfo and imeSubtype.
+     *
+     * @param identifier The identifier for the input device.
+     * @param userId user profile ID
+     * @param imeInfo contains IME information like imeId, etc.
+     * @param imeSubtype contains IME subtype information like input languageTag, layoutType, etc.
+     * @return The keyboard layout descriptors.
+     *
+     * @hide
+     */
+    public String[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @NonNull InputMethodSubtype imeSubtype) {
+        if (identifier == null) {
+            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+        }
+
+        try {
+            return mIm.getKeyboardLayoutListForInputDevice(identifier, userId, imeInfo, imeSubtype);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets the mouse pointer speed.
      * <p>
      * Only returns the permanent mouse pointer speed.  Ignores any temporary pointer
@@ -1720,6 +1809,34 @@
     }
 
     /**
+     * Pilfer pointers from an input channel.
+     *
+     * Takes all the current pointer event streams that are currently being sent to the given
+     * input channel and generates appropriate cancellations for all other windows that are
+     * receiving these pointers.
+     *
+     * This API is intended to be used in conjunction with spy windows. When a spy window pilfers
+     * pointers, the foreground windows and all other spy windows that are receiving any of the
+     * pointers that are currently being dispatched to the pilfering window will have those pointers
+     * canceled. Only the pilfering window will continue to receive events for the affected pointers
+     * until the pointer is lifted.
+     *
+     * This method should be used with caution as unexpected pilfering can break fundamental user
+     * interactions.
+     *
+     * @see android.os.InputConfig#SPY
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_INPUT)
+    public void pilferPointers(IBinder inputChannelToken) {
+        try {
+            mIm.pilferPointers(inputChannelToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Adds a battery listener to be notified about {@link BatteryState} changes for an input
      * device. The same listener can be registered for multiple input devices.
      * The listener will be notified of the initial battery state of the device after it is
@@ -1822,6 +1939,31 @@
     }
 
     /**
+     * Whether stylus has ever been used on device (false by default).
+     * @hide
+     */
+    public boolean isStylusEverUsed(@NonNull Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+                        Settings.Global.STYLUS_EVER_USED, 0) == 1;
+    }
+
+    /**
+     * Set whether stylus has ever been used on device.
+     * Should only ever be set to true once after stylus first usage.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+    public void setStylusEverUsed(@NonNull Context context, boolean stylusEverUsed) {
+        if (context.checkCallingPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("You need WRITE_SECURE_SETTINGS permission "
+                + "to set stylus ever used.");
+        }
+        Settings.Global.putInt(context.getContentResolver(),
+                Settings.Global.STYLUS_EVER_USED, stylusEverUsed ? 1 : 0);
+    }
+
+    /**
      * A callback used to be notified about battery state changes for an input device. The
      * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
      * listener is successfully registered to provide the initial battery state of the device.
diff --git a/core/java/android/hardware/input/VirtualDpad.java b/core/java/android/hardware/input/VirtualDpad.java
index 4d61553..8133472 100644
--- a/core/java/android/hardware/input/VirtualDpad.java
+++ b/core/java/android/hardware/input/VirtualDpad.java
@@ -32,8 +32,8 @@
 /**
  * A virtual dpad representing a key input mechanism on a remote device.
  *
- * This registers an InputDevice that is interpreted like a physically-connected device and
- * dispatches received events to it.
+ * <p>This registers an InputDevice that is interpreted like a physically-connected device and
+ * dispatches received events to it.</p>
  *
  * @hide
  */
@@ -44,6 +44,7 @@
             Collections.unmodifiableSet(
                     new HashSet<>(
                             Arrays.asList(
+                                    KeyEvent.KEYCODE_BACK,
                                     KeyEvent.KEYCODE_DPAD_UP,
                                     KeyEvent.KEYCODE_DPAD_DOWN,
                                     KeyEvent.KEYCODE_DPAD_LEFT,
@@ -58,8 +59,15 @@
     /**
      * Sends a key event to the system.
      *
-     * Supported key codes are KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT,
-     * KEYCODE_DPAD_RIGHT and KEYCODE_DPAD_CENTER,
+     * <p>Supported key codes are:
+     * <ul>
+     *     <li>{@link KeyEvent.KEYCODE_DPAD_UP}</li>
+     *     <li>{@link KeyEvent.KEYCODE_DPAD_DOWN}</li>
+     *     <li>{@link KeyEvent.KEYCODE_DPAD_LEFT}</li>
+     *     <li>{@link KeyEvent.KEYCODE_DPAD_RIGHT}</li>
+     *     <li>{@link KeyEvent.KEYCODE_DPAD_CENTER}</li>
+     *     <li>{@link KeyEvent.KEYCODE_BACK}</li>
+     * </ul>
      *
      * @param event the event to send
      */
diff --git a/core/java/android/hardware/input/VirtualDpadConfig.aidl b/core/java/android/hardware/input/VirtualDpadConfig.aidl
new file mode 100644
index 0000000..fac90f4
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualDpadConfig.aidl
@@ -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.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualDpadConfig;
diff --git a/core/java/android/hardware/input/VirtualDpadConfig.java b/core/java/android/hardware/input/VirtualDpadConfig.java
new file mode 100644
index 0000000..d888dc0
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualDpadConfig.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 android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Configurations to create virtual Dpad.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualDpadConfig extends VirtualInputDeviceConfig implements Parcelable {
+    @NonNull
+    public static final Creator<VirtualDpadConfig> CREATOR = new Creator<VirtualDpadConfig>() {
+        @Override
+        public VirtualDpadConfig createFromParcel(Parcel in) {
+            return new VirtualDpadConfig(in);
+        }
+
+        @Override
+        public VirtualDpadConfig[] newArray(int size) {
+            return new VirtualDpadConfig[size];
+        }
+    };
+
+    private VirtualDpadConfig(@NonNull VirtualDpadConfig.Builder builder) {
+        super(builder);
+    }
+
+    private VirtualDpadConfig(@NonNull Parcel in) {
+        super(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+    }
+
+    /**
+     * Builder for creating a {@link VirtualDpadConfig}.
+     */
+    public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
+
+        /**
+         * Builds the {@link VirtualDpadConfig} instance.
+         */
+        @NonNull
+        public VirtualDpadConfig build() {
+            return new VirtualDpadConfig(this);
+        }
+    }
+}
diff --git a/core/java/android/hardware/input/VirtualInputDeviceConfig.java b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
new file mode 100644
index 0000000..d3dacc9
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
@@ -0,0 +1,144 @@
+/*
+ * 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.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+
+/**
+ * Common configurations to create virtual input devices.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class VirtualInputDeviceConfig {
+    /** The vendor id uniquely identifies the company who manufactured the device. */
+    private final int mVendorId;
+    /**
+     * The product id uniquely identifies which product within the address space of a given vendor,
+     * identified by the device's vendor id.
+     */
+    private final int mProductId;
+    /** The associated display ID of the virtual input device. */
+    private final int mAssociatedDisplayId;
+    /** The name of the virtual input device. */
+    @NonNull
+    private final String mInputDeviceName;
+
+    protected VirtualInputDeviceConfig(@NonNull Builder<? extends Builder<?>> builder) {
+        mVendorId = builder.mVendorId;
+        mProductId = builder.mProductId;
+        mAssociatedDisplayId = builder.mAssociatedDisplayId;
+        mInputDeviceName = builder.mInputDeviceName;
+    }
+
+    protected VirtualInputDeviceConfig(@NonNull Parcel in) {
+        mVendorId = in.readInt();
+        mProductId = in.readInt();
+        mAssociatedDisplayId = in.readInt();
+        mInputDeviceName = in.readString8();
+    }
+
+    /**
+     * The vendor id uniquely identifies the company who manufactured the device.
+     */
+    public int getVendorId() {
+        return mVendorId;
+    }
+
+    /**
+     * The product id uniquely identifies which product within the address space of a given vendor,
+     * identified by the device's vendor id.
+     */
+    public int getProductId() {
+        return mProductId;
+    }
+
+    /**
+     * The associated display ID of the virtual input device.
+     */
+    public int getAssociatedDisplayId() {
+        return mAssociatedDisplayId;
+    }
+
+    /**
+     * The name of the virtual input device.
+     */
+    @NonNull
+    public String getInputDeviceName() {
+        return mInputDeviceName;
+    }
+
+    void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mVendorId);
+        dest.writeInt(mProductId);
+        dest.writeInt(mAssociatedDisplayId);
+        dest.writeString8(mInputDeviceName);
+    }
+
+    /**
+     * A builder for {@link VirtualInputDeviceConfig}
+     *
+     * @param <T> The subclass to be built.
+     */
+    @SuppressWarnings({"StaticFinalBuilder", "MissingBuildMethod"})
+    public abstract static class Builder<T extends Builder<T>> {
+
+        private int mVendorId;
+        private int mProductId;
+        private int mAssociatedDisplayId;
+        @NonNull
+        private String mInputDeviceName;
+
+        /** @see VirtualInputDeviceConfig#getVendorId(). */
+        @NonNull
+        public T setVendorId(int vendorId) {
+            mVendorId = vendorId;
+            return self();
+        }
+
+
+        /** @see VirtualInputDeviceConfig#getProductId(). */
+        @NonNull
+        public T setProductId(int productId) {
+            mProductId = productId;
+            return self();
+        }
+
+        /** @see VirtualInputDeviceConfig#getAssociatedDisplayId(). */
+        @NonNull
+        public T setAssociatedDisplayId(int displayId) {
+            mAssociatedDisplayId = displayId;
+            return self();
+        }
+
+        /** @see VirtualInputDeviceConfig#getInputDeviceName(). */
+        @NonNull
+        public T setInputDeviceName(@NonNull String deviceName) {
+            mInputDeviceName = deviceName;
+            return self();
+        }
+
+        /**
+         * Each subclass should return itself to allow the builder to chain properly
+         */
+        T self() {
+            return (T) this;
+        }
+    }
+}
diff --git a/core/java/android/hardware/input/VirtualKeyboardConfig.aidl b/core/java/android/hardware/input/VirtualKeyboardConfig.aidl
new file mode 100644
index 0000000..8772e23
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualKeyboardConfig.aidl
@@ -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.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualKeyboardConfig;
diff --git a/core/java/android/hardware/input/VirtualKeyboardConfig.java b/core/java/android/hardware/input/VirtualKeyboardConfig.java
new file mode 100644
index 0000000..9463857
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualKeyboardConfig.java
@@ -0,0 +1,77 @@
+/*
+ * 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.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+/**
+ * Configurations to create virtual keyboard.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualKeyboardConfig extends VirtualInputDeviceConfig implements Parcelable {
+
+    @NonNull
+    public static final Creator<VirtualKeyboardConfig> CREATOR =
+            new Creator<VirtualKeyboardConfig>() {
+                @Override
+                public VirtualKeyboardConfig createFromParcel(Parcel in) {
+                    return new VirtualKeyboardConfig(in);
+                }
+
+                @Override
+                public VirtualKeyboardConfig[] newArray(int size) {
+                    return new VirtualKeyboardConfig[size];
+                }
+            };
+
+    private VirtualKeyboardConfig(@NonNull Builder builder) {
+        super(builder);
+    }
+
+    private VirtualKeyboardConfig(@NonNull Parcel in) {
+        super(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+    }
+
+    /**
+     * Builder for creating a {@link VirtualKeyboardConfig}.
+     */
+    public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
+        /**
+         * Builds the {@link VirtualKeyboardConfig} instance.
+         */
+        @NonNull
+        public VirtualKeyboardConfig build() {
+            return new VirtualKeyboardConfig(this);
+        }
+    }
+}
diff --git a/core/java/android/hardware/input/VirtualMouseConfig.aidl b/core/java/android/hardware/input/VirtualMouseConfig.aidl
new file mode 100644
index 0000000..a0d5fb5
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouseConfig.aidl
@@ -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.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualMouseConfig;
diff --git a/core/java/android/hardware/input/VirtualMouseConfig.java b/core/java/android/hardware/input/VirtualMouseConfig.java
new file mode 100644
index 0000000..7ad5d04
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouseConfig.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 android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Configurations to create virtual mouse.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualMouseConfig extends VirtualInputDeviceConfig implements Parcelable {
+    @NonNull
+    public static final Creator<VirtualMouseConfig> CREATOR = new Creator<VirtualMouseConfig>() {
+        @Override
+        public VirtualMouseConfig createFromParcel(Parcel in) {
+            return new VirtualMouseConfig(in);
+        }
+
+        @Override
+        public VirtualMouseConfig[] newArray(int size) {
+            return new VirtualMouseConfig[size];
+        }
+    };
+
+    private VirtualMouseConfig(@NonNull VirtualMouseConfig.Builder builder) {
+        super(builder);
+    }
+
+    private VirtualMouseConfig(@NonNull Parcel in) {
+        super(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+    }
+
+    /**
+     * Builder for creating a {@link VirtualMouseConfig}.
+     */
+    public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
+
+        /**
+         * Builds the {@link VirtualMouseConfig} instance.
+         */
+        @NonNull
+        public VirtualMouseConfig build() {
+            return new VirtualMouseConfig(this);
+        }
+    }
+}
diff --git a/core/java/android/hardware/input/VirtualTouchEvent.java b/core/java/android/hardware/input/VirtualTouchEvent.java
index c7450d8..9b1e6e8 100644
--- a/core/java/android/hardware/input/VirtualTouchEvent.java
+++ b/core/java/android/hardware/input/VirtualTouchEvent.java
@@ -33,6 +33,11 @@
  * The pointer id, tool type, action, and location are required; pressure and main axis size are
  * optional.
  *
+ * Note: A VirtualTouchEvent with ACTION_CANCEL can only be created with TOOL_TYPE_PALM (and vice
+ * versa). Events are injected into the uinput kernel module, which has no concept of cancelling
+ * an action. The only way to state the intention that a pointer should not be handled as a pointer
+ * is to change its tool type to TOOL_TYPE_PALM.
+ *
  * @hide
  */
 @SystemApi
@@ -186,6 +191,10 @@
 
         /**
          * Creates a {@link VirtualTouchEvent} object with the current builder configuration.
+         *
+         * @throws IllegalArgumentException if one of the required arguments is missing or if
+         * ACTION_CANCEL is not set in combination with TOOL_TYPE_PALM. See
+         * {@link VirtualTouchEvent} for a detailed explanation.
          */
         public @NonNull VirtualTouchEvent build() {
             if (mToolType == TOOL_TYPE_UNKNOWN || mPointerId == MotionEvent.INVALID_POINTER_ID
@@ -262,6 +271,16 @@
         /**
          * Sets the pressure of the event. This field is optional and can be omitted.
          *
+         * @param pressure The pressure of the touch.
+         *                 Note: The VirtualTouchscreen, consuming VirtualTouchEvents, is
+         *                 configured with a pressure axis range from 0.0 to 255.0. Only the
+         *                 lower end of the range is enforced. You can pass values larger than
+         *                 255.0. With physical input devices this could happen if the
+         *                 calibration is off. Values larger than 255.0 will not be trimmed and
+         *                 passed on as is.
+         *
+         * @throws IllegalArgumentException if the pressure is smaller than 0.
+         *
          * @return this builder, to allow for chaining of calls
          */
         public @NonNull Builder setPressure(@FloatRange(from = 0f) float pressure) {
diff --git a/core/java/android/hardware/input/VirtualTouchscreenConfig.aidl b/core/java/android/hardware/input/VirtualTouchscreenConfig.aidl
new file mode 100644
index 0000000..e4b0edb
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualTouchscreenConfig.aidl
@@ -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.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualTouchscreenConfig;
diff --git a/core/java/android/hardware/input/VirtualTouchscreenConfig.java b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
new file mode 100644
index 0000000..e358619
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualTouchscreenConfig.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 android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Configurations to create virtual touchscreen.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualTouchscreenConfig extends VirtualInputDeviceConfig implements Parcelable {
+
+    /** The touchscreen width in pixels. */
+    private final int mWidthInPixels;
+    /** The touchscreen height in pixels. */
+    private final int mHeightInPixels;
+
+    private VirtualTouchscreenConfig(@NonNull Builder builder) {
+        super(builder);
+        mWidthInPixels = builder.mWidthInPixels;
+        mHeightInPixels = builder.mHeightInPixels;
+    }
+
+    private VirtualTouchscreenConfig(@NonNull Parcel in) {
+        super(in);
+        mWidthInPixels = in.readInt();
+        mHeightInPixels = in.readInt();
+    }
+
+    /** Returns the touchscreen width in pixels. */
+    public int getWidthInPixels() {
+        return mWidthInPixels;
+    }
+
+    /** Returns the touchscreen height in pixels. */
+    public int getHeightInPixels() {
+        return mHeightInPixels;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mWidthInPixels);
+        dest.writeInt(mHeightInPixels);
+    }
+
+    @NonNull
+    public static final Creator<VirtualTouchscreenConfig> CREATOR =
+            new Creator<VirtualTouchscreenConfig>() {
+                @Override
+                public VirtualTouchscreenConfig createFromParcel(Parcel in) {
+                    return new VirtualTouchscreenConfig(in);
+                }
+
+                @Override
+                public VirtualTouchscreenConfig[] newArray(int size) {
+                    return new VirtualTouchscreenConfig[size];
+                }
+            };
+
+    /**
+     * Builder for creating a {@link VirtualTouchscreenConfig}.
+     */
+    public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
+        private int mWidthInPixels;
+        private int mHeightInPixels;
+
+        /**
+         * @see VirtualTouchscreenConfig#getWidthInPixels().
+         */
+        @NonNull
+        public Builder setWidthInPixels(int widthInPixels) {
+            mWidthInPixels = widthInPixels;
+            return this;
+        }
+
+        /**
+         * @see VirtualTouchscreenConfig#getHeightInPixels().
+         */
+        @NonNull
+        public Builder setHeightInPixels(int heightInPixels) {
+            mHeightInPixels = heightInPixels;
+            return this;
+        }
+
+        /**
+         * Builds the {@link VirtualTouchscreenConfig} instance.
+         */
+        @NonNull
+        public VirtualTouchscreenConfig build() {
+            return new VirtualTouchscreenConfig(this);
+        }
+    }
+}
diff --git a/core/java/android/hardware/location/ActivityRecognitionHardware.java b/core/java/android/hardware/location/ActivityRecognitionHardware.java
index 20d6338..2754096 100644
--- a/core/java/android/hardware/location/ActivityRecognitionHardware.java
+++ b/core/java/android/hardware/location/ActivityRecognitionHardware.java
@@ -91,12 +91,16 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public String[] getSupportedActivities() {
+        super.getSupportedActivities_enforcePermission();
+
         return mSupportedActivities;
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public boolean isActivitySupported(String activity) {
+        super.isActivitySupported_enforcePermission();
+
         int activityType = getActivityType(activity);
         return activityType != INVALID_ACTIVITY_TYPE;
     }
@@ -104,12 +108,16 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public boolean registerSink(IActivityRecognitionHardwareSink sink) {
+        super.registerSink_enforcePermission();
+
         return mSinks.register(sink);
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public boolean unregisterSink(IActivityRecognitionHardwareSink sink) {
+        super.unregisterSink_enforcePermission();
+
         return mSinks.unregister(sink);
     }
 
@@ -117,6 +125,8 @@
     @Override
     public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs) {
 
+        super.enableActivityEvent_enforcePermission();
+
         int activityType = getActivityType(activity);
         if (activityType == INVALID_ACTIVITY_TYPE) {
             return false;
@@ -134,6 +144,8 @@
     @Override
     public boolean disableActivityEvent(String activity, int eventType) {
 
+        super.disableActivityEvent_enforcePermission();
+
         int activityType = getActivityType(activity);
         if (activityType == INVALID_ACTIVITY_TYPE) {
             return false;
@@ -150,6 +162,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public boolean flush() {
+        super.flush_enforcePermission();
+
         int result = nativeFlush();
         return result == NATIVE_SUCCESS_RESULT;
     }
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index b54da6c..ac23af4 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -24,6 +24,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -966,6 +967,34 @@
     }
 
     /**
+     * Queries for the list of preloaded nanoapp IDs on the system.
+     *
+     * @param hubInfo The Context Hub to query a list of nanoapp IDs from.
+     *
+     * @return The list of 64-bit IDs of the preloaded nanoapps.
+     *
+     * @throws NullPointerException if hubInfo is null
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @NonNull public long[] getPreloadedNanoAppIds(@NonNull ContextHubInfo hubInfo) {
+        Objects.requireNonNull(hubInfo, "hubInfo cannot be null");
+
+        long[] nanoappIds = null;
+        try {
+            nanoappIds = mService.getPreloadedNanoAppIds(hubInfo);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        if (nanoappIds == null) {
+            nanoappIds = new long[0];
+        }
+        return nanoappIds;
+    }
+
+    /**
      * Unregister a callback for receive messages from the context hub.
      *
      * @see Callback
diff --git a/core/java/android/hardware/location/GeofenceHardwareService.java b/core/java/android/hardware/location/GeofenceHardwareService.java
index 106bfd5..99c1e16 100644
--- a/core/java/android/hardware/location/GeofenceHardwareService.java
+++ b/core/java/android/hardware/location/GeofenceHardwareService.java
@@ -79,6 +79,8 @@
         @Override
         public int[] getMonitoringTypes() {
 
+            super.getMonitoringTypes_enforcePermission();
+
             return mGeofenceHardwareImpl.getMonitoringTypes();
         }
 
@@ -86,6 +88,8 @@
         @Override
         public int getStatusOfMonitoringType(int monitoringType) {
 
+            super.getStatusOfMonitoringType_enforcePermission();
+
             return mGeofenceHardwareImpl.getStatusOfMonitoringType(monitoringType);
         }
 
@@ -95,6 +99,8 @@
                 int monitoringType,
                 GeofenceHardwareRequestParcelable request,
                 IGeofenceHardwareCallback callback) {
+            super.addCircularFence_enforcePermission();
+
             checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
             return mGeofenceHardwareImpl.addCircularFence(monitoringType, request, callback);
         }
@@ -103,6 +109,8 @@
         @Override
         public boolean removeGeofence(int id, int monitoringType) {
 
+            super.removeGeofence_enforcePermission();
+
             checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
             return mGeofenceHardwareImpl.removeGeofence(id, monitoringType);
         }
@@ -111,6 +119,8 @@
         @Override
         public boolean pauseGeofence(int id, int monitoringType) {
 
+            super.pauseGeofence_enforcePermission();
+
             checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
             return mGeofenceHardwareImpl.pauseGeofence(id, monitoringType);
         }
@@ -119,6 +129,8 @@
         @Override
         public boolean resumeGeofence(int id, int monitoringType, int monitorTransitions) {
 
+            super.resumeGeofence_enforcePermission();
+
             checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
             return mGeofenceHardwareImpl.resumeGeofence(id, monitoringType, monitorTransitions);
         }
@@ -128,6 +140,8 @@
         public boolean registerForMonitorStateChangeCallback(int monitoringType,
                 IGeofenceHardwareMonitorCallback callback) {
 
+            super.registerForMonitorStateChangeCallback_enforcePermission();
+
             checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
             return mGeofenceHardwareImpl.registerForMonitorStateChangeCallback(monitoringType,
                     callback);
@@ -138,6 +152,8 @@
         public boolean unregisterForMonitorStateChangeCallback(int monitoringType,
                 IGeofenceHardwareMonitorCallback callback) {
 
+            super.unregisterForMonitorStateChangeCallback_enforcePermission();
+
             checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
             return mGeofenceHardwareImpl.unregisterForMonitorStateChangeCallback(monitoringType,
                     callback);
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index ced75c4..490267f 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -109,4 +109,8 @@
     // Queries for a list of nanoapps
     @EnforcePermission("ACCESS_CONTEXT_HUB")
     void queryNanoApps(int contextHubId, in IContextHubTransactionCallback transactionCallback);
+
+    // Queries for a list of preloaded nanoapps
+    @EnforcePermission("ACCESS_CONTEXT_HUB")
+    long[] getPreloadedNanoAppIds(in ContextHubInfo hubInfo);
 }
diff --git a/core/java/android/hardware/radio/OWNERS b/core/java/android/hardware/radio/OWNERS
index d2bdd64..302fdd7 100644
--- a/core/java/android/hardware/radio/OWNERS
+++ b/core/java/android/hardware/radio/OWNERS
@@ -1,3 +1,4 @@
 xuweilin@google.com
 oscarazu@google.com
+ericjeong@google.com
 keunyoung@google.com
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index ade9fd6..b2dfd85 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -24,6 +24,8 @@
 import android.os.Parcelable;
 import android.util.ArrayMap;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -41,14 +43,25 @@
 public final class ProgramList implements AutoCloseable {
 
     private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
     private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms =
             new ArrayMap<>();
 
+    @GuardedBy("mLock")
     private final List<ListCallback> mListCallbacks = new ArrayList<>();
+
+    @GuardedBy("mLock")
     private final List<OnCompleteListener> mOnCompleteListeners = new ArrayList<>();
+
+    @GuardedBy("mLock")
     private OnCloseListener mOnCloseListener;
-    private boolean mIsClosed = false;
-    private boolean mIsComplete = false;
+
+    @GuardedBy("mLock")
+    private boolean mIsClosed;
+
+    @GuardedBy("mLock")
+    private boolean mIsComplete;
 
     ProgramList() {}
 
@@ -227,6 +240,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void putLocked(RadioManager.ProgramInfo value,
             List<ProgramSelector.Identifier> changedIdentifierList) {
         ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
@@ -235,6 +249,7 @@
         changedIdentifierList.add(sel);
     }
 
+    @GuardedBy("mLock")
     private void removeLocked(ProgramSelector.Identifier key,
             List<ProgramSelector.Identifier> removedIdentifierList) {
         RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
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/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 8b71092..9a217f9 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -251,7 +251,8 @@
                     Objects.requireNonNull(entry.getValue());
                 }
             }
-            mDabFrequencyTable = dabFrequencyTable;
+            mDabFrequencyTable = (dabFrequencyTable == null || dabFrequencyTable.isEmpty())
+                    ? null : dabFrequencyTable;
             mVendorInfo = (vendorInfo == null) ? new HashMap<>() : vendorInfo;
         }
 
@@ -446,7 +447,8 @@
             mIsBgScanSupported = in.readInt() == 1;
             mSupportedProgramTypes = arrayToSet(in.createIntArray());
             mSupportedIdentifierTypes = arrayToSet(in.createIntArray());
-            mDabFrequencyTable = Utils.readStringIntMap(in);
+            Map<String, Integer> dabFrequencyTableIn = Utils.readStringIntMap(in);
+            mDabFrequencyTable = (dabFrequencyTableIn.isEmpty()) ? null : dabFrequencyTableIn;
             mVendorInfo = Utils.readStringMap(in);
         }
 
@@ -919,7 +921,10 @@
         @NonNull final BandDescriptor mDescriptor;
 
         BandConfig(BandDescriptor descriptor) {
-            mDescriptor = Objects.requireNonNull(descriptor);
+            Objects.requireNonNull(descriptor, "Descriptor cannot be null");
+            mDescriptor = new BandDescriptor(descriptor.getRegion(), descriptor.getType(),
+                    descriptor.getLowerLimit(), descriptor.getUpperLimit(),
+                    descriptor.getSpacing());
         }
 
         BandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing) {
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 5a44244..248b5d0 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -79,9 +79,20 @@
     /* Returns true if the caller has permission to access the device. */
     boolean hasDevicePermission(in UsbDevice device, String packageName);
 
+    /* Returns true if the given package/pid/uid has permission to access the device. */
+    @JavaPassthrough(annotation=
+            "@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_USB)")
+    boolean hasDevicePermissionWithIdentity(in UsbDevice device, String packageName,
+            int pid, int uid);
+
     /* Returns true if the caller has permission to access the accessory. */
     boolean hasAccessoryPermission(in UsbAccessory accessory);
 
+    /* Returns true if the given pid/uid has permission to access the accessory. */
+    @JavaPassthrough(annotation=
+            "@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_USB)")
+    boolean hasAccessoryPermissionWithIdentity(in UsbAccessory accessory, int pid, int uid);
+
     /* Requests permission for the given package to access the device.
      * Will display a system dialog to query the user if permission
      * had not already been given.
@@ -111,10 +122,10 @@
     boolean isFunctionEnabled(String function);
 
     /* Sets the current USB function. */
-    void setCurrentFunctions(long functions);
+    void setCurrentFunctions(long functions, int operationId);
 
     /* Compatibility version of setCurrentFunctions(long). */
-    void setCurrentFunction(String function, boolean usbDataUnlocked);
+    void setCurrentFunction(String function, boolean usbDataUnlocked, int operationId);
 
     /* Gets the current USB functions. */
     long getCurrentFunctions();
diff --git a/core/java/android/hardware/usb/ParcelableUsbPort.java b/core/java/android/hardware/usb/ParcelableUsbPort.java
index 19655ed..7fc282c 100644
--- a/core/java/android/hardware/usb/ParcelableUsbPort.java
+++ b/core/java/android/hardware/usb/ParcelableUsbPort.java
@@ -34,11 +34,13 @@
     private final int mSupportedContaminantProtectionModes;
     private final boolean mSupportsEnableContaminantPresenceProtection;
     private final boolean mSupportsEnableContaminantPresenceDetection;
+    private final boolean mSupportsComplianceWarnings;
 
     private ParcelableUsbPort(@NonNull String id, int supportedModes,
             int supportedContaminantProtectionModes,
             boolean supportsEnableContaminantPresenceProtection,
-            boolean supportsEnableContaminantPresenceDetection) {
+            boolean supportsEnableContaminantPresenceDetection,
+            boolean supportsComplianceWarnings) {
         mId = id;
         mSupportedModes = supportedModes;
         mSupportedContaminantProtectionModes = supportedContaminantProtectionModes;
@@ -46,6 +48,8 @@
                 supportsEnableContaminantPresenceProtection;
         mSupportsEnableContaminantPresenceDetection =
                 supportsEnableContaminantPresenceDetection;
+        mSupportsComplianceWarnings =
+                supportsComplianceWarnings;
     }
 
     /**
@@ -59,7 +63,8 @@
         return new ParcelableUsbPort(port.getId(), port.getSupportedModes(),
                 port.getSupportedContaminantProtectionModes(),
                 port.supportsEnableContaminantPresenceProtection(),
-                port.supportsEnableContaminantPresenceDetection());
+                port.supportsEnableContaminantPresenceDetection(),
+                port.supportsComplianceWarnings());
     }
 
     /**
@@ -72,7 +77,8 @@
     public @NonNull UsbPort getUsbPort(@NonNull UsbManager usbManager) {
         return new UsbPort(usbManager, mId, mSupportedModes, mSupportedContaminantProtectionModes,
                 mSupportsEnableContaminantPresenceProtection,
-                mSupportsEnableContaminantPresenceDetection);
+                mSupportsEnableContaminantPresenceDetection,
+                mSupportsComplianceWarnings);
     }
 
     @Override
@@ -87,6 +93,7 @@
         dest.writeInt(mSupportedContaminantProtectionModes);
         dest.writeBoolean(mSupportsEnableContaminantPresenceProtection);
         dest.writeBoolean(mSupportsEnableContaminantPresenceDetection);
+        dest.writeBoolean(mSupportsComplianceWarnings);
     }
 
     public static final @android.annotation.NonNull Creator<ParcelableUsbPort> CREATOR =
@@ -98,11 +105,13 @@
                     int supportedContaminantProtectionModes = in.readInt();
                     boolean supportsEnableContaminantPresenceProtection = in.readBoolean();
                     boolean supportsEnableContaminantPresenceDetection = in.readBoolean();
+                    boolean supportsComplianceWarnings = in.readBoolean();
 
                     return new ParcelableUsbPort(id, supportedModes,
                             supportedContaminantProtectionModes,
                             supportsEnableContaminantPresenceProtection,
-                            supportsEnableContaminantPresenceDetection);
+                            supportsEnableContaminantPresenceDetection,
+                            supportsComplianceWarnings);
                 }
 
                 @Override
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 2c38f70..7a8117c 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -38,6 +38,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.hardware.usb.gadget.V1_0.GadgetFunction;
 import android.hardware.usb.gadget.V1_2.UsbSpeed;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
@@ -52,6 +53,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.StringJoiner;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This class allows you to access the state of USB and communicate with USB devices.
@@ -95,7 +97,7 @@
      * If the sticky intent has not been found, that indicates USB is disconnected,
      * USB is not configued, MTP function is enabled, and all the other functions are disabled.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String ACTION_USB_STATE =
@@ -113,6 +115,19 @@
     public static final String ACTION_USB_PORT_CHANGED =
             "android.hardware.usb.action.USB_PORT_CHANGED";
 
+     /**
+     * Broadcast Action: A broadcast for USB compliance warning changes.
+     *
+     * This intent is sent when a port partner's
+     * (USB power source/cable/accessory) compliance warnings change state.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    public static final String ACTION_USB_PORT_COMPLIANCE_CHANGED =
+            "android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED";
+
    /**
      * Activity intent sent when user attaches a USB device.
      *
@@ -172,7 +187,7 @@
      * <p>For more information about communicating with USB accessory handshake, refer to
      * <a href="https://source.android.com/devices/accessories/aoa">AOA</a> developer guide.</p>
      *
-     * {@hide}
+     * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     @SystemApi
@@ -184,7 +199,7 @@
      * Boolean extra indicating whether USB is connected or disconnected.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String USB_CONNECTED = "connected";
@@ -193,7 +208,7 @@
      * Boolean extra indicating whether USB is connected or disconnected as host.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_HOST_CONNECTED = "host_connected";
 
@@ -201,7 +216,7 @@
      * Boolean extra indicating whether USB is configured.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String USB_CONFIGURED = "configured";
@@ -212,7 +227,7 @@
      * has explicitly asked for this data to be unlocked.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
      *
-     * {@hide}
+     * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static final String USB_DATA_UNLOCKED = "unlocked";
@@ -221,7 +236,7 @@
      * A placeholder indicating that no USB function is being specified.
      * Used for compatibility with old init scripts to indicate no functions vs. charging function.
      *
-     * {@hide}
+     * @hide
      */
     @UnsupportedAppUsage
     public static final String USB_FUNCTION_NONE = "none";
@@ -230,7 +245,7 @@
      * Name of the adb USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_ADB = "adb";
 
@@ -238,7 +253,7 @@
      * Name of the RNDIS ethernet USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String USB_FUNCTION_RNDIS = "rndis";
@@ -247,7 +262,7 @@
      * Name of the MTP USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_MTP = "mtp";
 
@@ -255,7 +270,7 @@
      * Name of the PTP USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_PTP = "ptp";
 
@@ -263,7 +278,7 @@
      * Name of the audio source USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_AUDIO_SOURCE = "audio_source";
 
@@ -271,7 +286,7 @@
      * Name of the MIDI USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_MIDI = "midi";
 
@@ -279,7 +294,7 @@
      * Name of the Accessory USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     public static final String USB_FUNCTION_ACCESSORY = "accessory";
 
@@ -287,7 +302,7 @@
      * Name of the NCM USB function.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String USB_FUNCTION_NCM = "ncm";
@@ -295,32 +310,39 @@
     /**
      * Name of Gadget Hal Not Present;
      *
-     * {@hide}
+     * @hide
      */
     public static final String GADGET_HAL_UNKNOWN = "unknown";
 
     /**
      * Name of the USB Gadget Hal Version v1.0;
      *
-     * {@hide}
+     * @hide
      */
     public static final String GADGET_HAL_VERSION_1_0 = "V1_0";
 
     /**
      * Name of the USB Gadget Hal Version v1.1;
      *
-     * {@hide}
+     * @hide
      */
     public static final String GADGET_HAL_VERSION_1_1 = "V1_1";
 
     /**
      * Name of the USB Gadget Hal Version v1.2;
      *
-     * {@hide}
+     * @hide
      */
     public static final String GADGET_HAL_VERSION_1_2 = "V1_2";
 
     /**
+     * Name of the USB Gadget Hal Version v2.0;
+     *
+     * @hide
+     */
+    public static final String GADGET_HAL_VERSION_2_0 = "V2_0";
+
+    /**
      * Name of extra for {@link #ACTION_USB_PORT_CHANGED}
      * containing the {@link UsbPort} object for the port.
      *
@@ -356,7 +378,7 @@
      * This is obtained with SystemClock.elapsedRealtime()
      * Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String EXTRA_ACCESSORY_UEVENT_TIME =
@@ -370,7 +392,7 @@
      * between communicating with USB accessory handshake, refer to
      * <a href="https://source.android.com/devices/accessories/aoa">AOA</a> developer guide.</p>
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String EXTRA_ACCESSORY_STRING_COUNT =
@@ -380,7 +402,7 @@
      * Boolean extra indicating whether got start accessory or not
      * Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String EXTRA_ACCESSORY_START =
@@ -392,7 +414,7 @@
      * sending {@link #ACTION_USB_ACCESSORY_HANDSHAKE}.
      * Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final String EXTRA_ACCESSORY_HANDSHAKE_END =
@@ -426,7 +448,7 @@
     /**
      * The Value for USB gadget hal is not presented.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int GADGET_HAL_NOT_SUPPORTED = -1;
@@ -434,7 +456,7 @@
     /**
      * Value for Gadget Hal Version v1.0.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int GADGET_HAL_V1_0 = 10;
@@ -442,7 +464,7 @@
     /**
      * Value for Gadget Hal Version v1.1.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int GADGET_HAL_V1_1 = 11;
@@ -450,15 +472,23 @@
     /**
      * Value for Gadget Hal Version v1.2.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int GADGET_HAL_V1_2 = 12;
 
     /**
+     * Value for Gadget Hal Version v2.0.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int GADGET_HAL_V2_0 = 20;
+
+    /**
      * Value for USB_STATE is not configured.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_UNKNOWN = -1;
@@ -466,7 +496,7 @@
     /**
      * Value for USB Transfer Rate of Low Speed in Mbps (real value is 1.5Mbps).
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_LOW_SPEED = 2;
@@ -474,7 +504,7 @@
     /**
      * Value for USB Transfer Rate of Full Speed in Mbps.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_FULL_SPEED = 12;
@@ -482,7 +512,7 @@
     /**
      * Value for USB Transfer Rate of High Speed in Mbps.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_HIGH_SPEED = 480;
@@ -490,7 +520,7 @@
     /**
      * Value for USB Transfer Rate of Super Speed in Mbps.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_5G = 5 * 1024;
@@ -498,7 +528,7 @@
     /**
      * Value for USB Transfer Rate of 10G.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_10G = 10 * 1024;
@@ -506,7 +536,7 @@
     /**
      * Value for USB Transfer Rate of 20G.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_20G = 20 * 1024;
@@ -514,7 +544,7 @@
     /**
      * Value for USB Transfer Rate of 40G.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_DATA_TRANSFER_RATE_40G = 40 * 1024;
@@ -530,7 +560,7 @@
     /**
      * The Value for USB hal is not presented.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_NOT_SUPPORTED = -1;
@@ -538,7 +568,7 @@
     /**
      * Value for USB Hal Version v1.0.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_V1_0 = 10;
@@ -546,7 +576,7 @@
     /**
      * Value for USB Hal Version v1.1.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_V1_1 = 11;
@@ -554,7 +584,7 @@
     /**
      * Value for USB Hal Version v1.2.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_V1_2 = 12;
@@ -562,7 +592,7 @@
     /**
      * Value for USB Hal Version v1.3.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int USB_HAL_V1_3 = 13;
@@ -577,63 +607,63 @@
 
     /**
      * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_NONE = 0;
 
     /**
      * Code for the mtp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_MTP = GadgetFunction.MTP;
 
     /**
      * Code for the ptp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_PTP = GadgetFunction.PTP;
 
     /**
      * Code for the rndis usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS;
 
     /**
      * Code for the midi usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_MIDI = GadgetFunction.MIDI;
 
     /**
      * Code for the accessory usb function.
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_ACCESSORY = GadgetFunction.ACCESSORY;
 
     /**
      * Code for the audio source usb function.
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE;
 
     /**
      * Code for the adb usb function.
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_ADB = GadgetFunction.ADB;
 
     /**
      * Code for the ncm source usb function.
-     * {@hide}
+     * @hide
      */
     @SystemApi
     public static final long FUNCTION_NCM = 1 << 10;
@@ -643,6 +673,11 @@
 
     private static final Map<String, Long> FUNCTION_NAME_TO_CODE = new HashMap<>();
 
+    /**
+     * Counter for tracking UsbOperation operations.
+     */
+    private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
     static {
         FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_MTP, FUNCTION_MTP);
         FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_PTP, FUNCTION_PTP);
@@ -674,6 +709,7 @@
             GADGET_HAL_V1_0,
             GADGET_HAL_V1_1,
             GADGET_HAL_V1_2,
+            GADGET_HAL_V2_0,
     })
     public @interface UsbGadgetHalVersion {}
 
@@ -692,7 +728,7 @@
     private final IUsbManager mService;
 
     /**
-     * {@hide}
+     * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public UsbManager(Context context, IUsbManager service) {
@@ -803,7 +839,7 @@
      * {@link #FUNCTION_PTP} are supported.
      * @return A ParcelFileDescriptor holding the valid fd, or null if the fd was not found.
      *
-     * {@hide}
+     * @hide
      */
     public ParcelFileDescriptor getControlFd(long function) {
         try {
@@ -838,6 +874,28 @@
     }
 
     /**
+     * Returns true if the caller has permission to access the device. It's similar to the
+     * {@link #hasPermission(UsbDevice)} but allows to specify a different package/uid/pid.
+     *
+     * <p>Not for third-party apps.</p>
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    @RequiresFeature(PackageManager.FEATURE_USB_HOST)
+    public boolean hasPermission(@NonNull UsbDevice device, @NonNull String packageName,
+            int pid, int uid) {
+        if (mService == null) {
+            return false;
+        }
+        try {
+            return mService.hasDevicePermissionWithIdentity(device, packageName, pid, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns true if the caller has permission to access the accessory.
      * Permission might have been granted temporarily via
      * {@link #requestPermission(UsbAccessory, PendingIntent)} or
@@ -859,6 +917,27 @@
     }
 
     /**
+     * Returns true if the caller has permission to access the accessory. It's similar to the
+     * {@link #hasPermission(UsbAccessory)} but allows to specify a different uid/pid.
+     *
+     * <p>Not for third-party apps.</p>
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY)
+    public boolean hasPermission(@NonNull UsbAccessory accessory, int pid, int uid) {
+        if (mService == null) {
+            return false;
+        }
+        try {
+            return mService.hasAccessoryPermissionWithIdentity(accessory, pid, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+   /**
      * Requests temporary permission for the given package to access the device.
      * This may result in a system dialog being displayed to the user
      * if permission had not already been granted.
@@ -921,7 +1000,7 @@
      * Only system components can call this function.
      * @param device to request permissions for
      *
-     * {@hide}
+     * @hide
      */
     public void grantPermission(UsbDevice device) {
         grantPermission(device, Process.myUid());
@@ -933,7 +1012,7 @@
      * @param device to request permissions for
      * @uid uid to give permission
      *
-     * {@hide}
+     * @hide
      */
     public void grantPermission(UsbDevice device, int uid) {
         try {
@@ -949,7 +1028,7 @@
      * @param device to request permissions for
      * @param packageName of package to grant permissions
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -974,7 +1053,7 @@
      * @param function name of the USB function
      * @return true if the USB function is enabled
      *
-     * {@hide}
+     * @hide
      */
     @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1006,14 +1085,17 @@
      * @param functions the USB function(s) to set, as a bitwise mask.
      *                  Must satisfy {@link UsbManager#areSettableFunctions}
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_USB)
     public void setCurrentFunctions(@UsbFunctionMode long functions) {
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
         try {
-            mService.setCurrentFunctions(functions);
+            mService.setCurrentFunctions(functions, operationId);
         } catch (RemoteException e) {
+            Log.e(TAG, "setCurrentFunctions: failed to call setCurrentFunctions. functions:"
+                        + functions + ", opId:" + operationId, e);
             throw e.rethrowFromSystemServer();
         }
     }
@@ -1025,14 +1107,17 @@
      * @param functions the USB function(s) to set.
      * @param usbDataUnlocked unused
 
-     * {@hide}
+     * @hide
      */
     @Deprecated
     @UnsupportedAppUsage
     public void setCurrentFunction(String functions, boolean usbDataUnlocked) {
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
         try {
-            mService.setCurrentFunction(functions, usbDataUnlocked);
+            mService.setCurrentFunction(functions, usbDataUnlocked, operationId);
         } catch (RemoteException e) {
+            Log.e(TAG, "setCurrentFunction: failed to call setCurrentFunction. functions:"
+                        + functions + ", opId:" + operationId, e);
             throw e.rethrowFromSystemServer();
         }
     }
@@ -1047,7 +1132,7 @@
      * @return The currently enabled functions, in a bitwise mask.
      * A zero mask indicates that the current function is the charging function.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1073,7 +1158,7 @@
      * @param functions functions to set, in a bitwise mask.
      *                  Must satisfy {@link UsbManager#areSettableFunctions}
      *
-     * {@hide}
+     * @hide
      */
     public void setScreenUnlockedFunctions(long functions) {
         try {
@@ -1089,7 +1174,7 @@
      * @return The currently set screen enabled functions.
      * A zero mask indicates that the screen unlocked functions feature is not enabled.
      *
-     * {@hide}
+     * @hide
      */
     public long getScreenUnlockedFunctions() {
         try {
@@ -1111,19 +1196,17 @@
      *
      * @return The value of currently USB Bandwidth.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(Manifest.permission.MANAGE_USB)
     public int getUsbBandwidthMbps() {
         int usbSpeed;
-
         try {
             usbSpeed = mService.getCurrentUsbSpeed();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-
         return usbSpeedToBandwidth(usbSpeed);
     }
 
@@ -1135,7 +1218,7 @@
      *
      * @return a integer {@code GADGET_HAL_*} represent hal version.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1155,7 +1238,7 @@
      *
      * @return a integer {@code USB_HAL_*} represent hal version.
      *
-     * {@hide}
+     * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1451,7 +1534,7 @@
      * @param usbDeviceConnectionHandler The component to handle usb connections,
      * {@code null} to unset.
      *
-     * {@hide}
+     * @hide
      */
     public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
         try {
@@ -1470,7 +1553,7 @@
      *
      * @return Whether the mask is settable.
      *
-     * {@hide}
+     * @hide
      */
     public static boolean areSettableFunctions(long functions) {
         return functions == FUNCTION_NONE
@@ -1484,7 +1567,7 @@
      *
      * @return String representation of given mask
      *
-     * {@hide}
+     * @hide
      */
     public static String usbFunctionsToString(long functions) {
         StringJoiner joiner = new StringJoiner(",");
@@ -1520,7 +1603,7 @@
      *
      * @return A mask of all valid functions in the string
      *
-     * {@hide}
+     * @hide
      */
     public static long usbFunctionsFromString(String functions) {
         if (functions == null || functions.equals(USB_FUNCTION_NONE)) {
@@ -1542,7 +1625,7 @@
      *
      * @return a value of USB bandwidth
      *
-     * {@hide}
+     * @hide
      */
     public static int usbSpeedToBandwidth(int speed) {
         switch (speed) {
@@ -1576,12 +1659,14 @@
      *
      * @return String representation of Usb Gadget Hal Version
      *
-     * {@hide}
+     * @hide
      */
     public static @NonNull String usbGadgetHalVersionToString(int version) {
         String halVersion;
 
-        if (version == GADGET_HAL_V1_2) {
+        if (version == GADGET_HAL_V2_0) {
+            halVersion = GADGET_HAL_VERSION_2_0;
+        } else if (version == GADGET_HAL_V1_2) {
             halVersion = GADGET_HAL_VERSION_1_2;
         } else if (version == GADGET_HAL_V1_1) {
             halVersion = GADGET_HAL_VERSION_1_1;
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index 7c5a4c6..e0f9cad 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -46,6 +46,10 @@
 import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DOCK;
 import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_FORCE;
 import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DEBUG;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_BC_1_2;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_OTHER;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
@@ -83,6 +87,7 @@
     private final int mSupportedContaminantProtectionModes;
     private final boolean mSupportsEnableContaminantPresenceProtection;
     private final boolean mSupportsEnableContaminantPresenceDetection;
+    private final boolean mSupportsComplianceWarnings;
 
     private static final int NUM_DATA_ROLES = Constants.PortDataRole.NUM_DATA_ROLES;
     /**
@@ -250,6 +255,18 @@
             int supportedContaminantProtectionModes,
             boolean supportsEnableContaminantPresenceProtection,
             boolean supportsEnableContaminantPresenceDetection) {
+        this(usbManager, id, supportedModes, supportedContaminantProtectionModes,
+                supportsEnableContaminantPresenceProtection,
+                supportsEnableContaminantPresenceDetection,
+                false);
+    }
+
+    /** @hide */
+    public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes,
+            int supportedContaminantProtectionModes,
+            boolean supportsEnableContaminantPresenceProtection,
+            boolean supportsEnableContaminantPresenceDetection,
+            boolean supportsComplianceWarnings) {
         Objects.requireNonNull(id);
         Preconditions.checkFlagsArgument(supportedModes,
                 MODE_DFP | MODE_UFP | MODE_AUDIO_ACCESSORY | MODE_DEBUG_ACCESSORY);
@@ -262,6 +279,7 @@
                 supportsEnableContaminantPresenceProtection;
         mSupportsEnableContaminantPresenceDetection =
                 supportsEnableContaminantPresenceDetection;
+        mSupportsComplianceWarnings = supportsComplianceWarnings;
     }
 
     /**
@@ -331,6 +349,21 @@
     }
 
     /**
+     * Queries USB Port to see if the port is capable of identifying
+     * non compliant USB power source/cable/accessory.
+     *
+     * @return true when the UsbPort is capable of identifying
+     *             non compliant USB power
+     *             source/cable/accessory.
+     * @return false otherwise.
+     */
+    @CheckResult
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    public boolean supportsComplianceWarnings() {
+        return mSupportsComplianceWarnings;
+    }
+
+    /**
      * Sets the desired role combination of the port.
      * <p>
      * The supported role combinations depend on what is connected to the port and may be
@@ -686,6 +719,37 @@
     }
 
     /** @hide */
+    public static String complianceWarningsToString(@NonNull int[] complianceWarnings) {
+        StringBuilder complianceWarningString = new StringBuilder();
+        complianceWarningString.append("[");
+
+        if (complianceWarnings != null) {
+            for (int warning : complianceWarnings) {
+                switch (warning) {
+                    case UsbPortStatus.COMPLIANCE_WARNING_OTHER:
+                        complianceWarningString.append("other, ");
+                        break;
+                    case UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY:
+                        complianceWarningString.append("debug accessory, ");
+                        break;
+                    case UsbPortStatus.COMPLIANCE_WARNING_BC_1_2:
+                        complianceWarningString.append("bc12, ");
+                        break;
+                    case UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP:
+                        complianceWarningString.append("missing rp, ");
+                        break;
+                    default:
+                        complianceWarningString.append(String.format("Unknown(%d), ", warning));
+                        break;
+                }
+            }
+        }
+
+        complianceWarningString.append("]");
+        return complianceWarningString.toString().replaceAll(", ]$", "]");
+    }
+
+    /** @hide */
     public static void checkMode(int powerRole) {
         Preconditions.checkArgumentInRange(powerRole, Constants.PortMode.NONE,
                 Constants.PortMode.NUM_MODES - 1, "portMode");
@@ -720,10 +784,12 @@
     @Override
     public String toString() {
         return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes)
-                + "supportedContaminantProtectionModes=" + mSupportedContaminantProtectionModes
-                + "supportsEnableContaminantPresenceProtection="
+                + ", supportedContaminantProtectionModes=" + mSupportedContaminantProtectionModes
+                + ", supportsEnableContaminantPresenceProtection="
                 + mSupportsEnableContaminantPresenceProtection
-                + "supportsEnableContaminantPresenceDetection="
-                + mSupportsEnableContaminantPresenceDetection;
+                + ", supportsEnableContaminantPresenceDetection="
+                + mSupportsEnableContaminantPresenceDetection
+                + ", supportsComplianceWarnings="
+                + mSupportsComplianceWarnings;
     }
 }
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index 3221ec8..ed3e40d 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -16,9 +16,11 @@
 
 package android.hardware.usb;
 
+import android.Manifest;
+import android.annotation.CheckResult;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -46,6 +48,7 @@
     private final boolean mPowerTransferLimited;
     private final @UsbDataStatus int mUsbDataStatus;
     private final @PowerBrickConnectionStatus int mPowerBrickConnectionStatus;
+    private final @NonNull @ComplianceWarning int[] mComplianceWarnings;
 
     /**
      * Power role: This USB port does not have a power role.
@@ -246,6 +249,41 @@
      */
     public static final int POWER_BRICK_STATUS_DISCONNECTED = 2;
 
+    /**
+     * Used to indicate attached sources/cables/accessories/ports
+     * that do not match the other warnings below and do not meet the
+     * requirements of specifications including but not limited to
+     * USB Type-C Cable and Connector, Universal Serial Bus
+     * Power Delivery, and Universal Serial Bus 1.x/2.0/3.x/4.0.
+     * In addition, constants introduced after the target sdk will be
+     * remapped into COMPLIANCE_WARNING_OTHER.
+     */
+    public static final int COMPLIANCE_WARNING_OTHER = 1;
+
+    /**
+     * Used to indicate Type-C port partner
+     * (cable/accessory/source) that identifies itself as debug
+     * accessory source as defined in USB Type-C Cable and
+     * Connector Specification. However, the specification states
+     * that this is meant for debug only and shall not be used for
+     * with commercial products.
+     */
+    public static final int COMPLIANCE_WARNING_DEBUG_ACCESSORY = 2;
+
+    /**
+     * Used to indicate USB ports that does not
+     * identify itself as one of the charging port types (SDP/CDP
+     * DCP etc) as defined by Battery Charging v1.2 Specification.
+     */
+    public static final int COMPLIANCE_WARNING_BC_1_2 = 3;
+
+    /**
+     * Used to indicate Type-C sources/cables that are missing pull
+     * up resistors on the CC pins as required by USB Type-C Cable
+     * and Connector Specification.
+     */
+    public static final int COMPLIANCE_WARNING_MISSING_RP = 4;
+
     @IntDef(prefix = { "CONTAMINANT_DETECTION_" }, value = {
             CONTAMINANT_DETECTION_NOT_SUPPORTED,
             CONTAMINANT_DETECTION_DISABLED,
@@ -275,6 +313,15 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface UsbPortMode{}
 
+    @IntDef(prefix = { "COMPLIANCE_WARNING_" }, value = {
+            COMPLIANCE_WARNING_OTHER,
+            COMPLIANCE_WARNING_DEBUG_ACCESSORY,
+            COMPLIANCE_WARNING_BC_1_2,
+            COMPLIANCE_WARNING_MISSING_RP,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ComplianceWarning{}
+
     /** @hide */
     @IntDef(prefix = { "DATA_STATUS_" }, flag = true, value = {
             DATA_STATUS_UNKNOWN,
@@ -302,7 +349,8 @@
             int supportedRoleCombinations, int contaminantProtectionStatus,
             int contaminantDetectionStatus, @UsbDataStatus int usbDataStatus,
             boolean powerTransferLimited,
-            @PowerBrickConnectionStatus int powerBrickConnectionStatus) {
+            @PowerBrickConnectionStatus int powerBrickConnectionStatus,
+            @NonNull @ComplianceWarning int[] complianceWarnings) {
         mCurrentMode = currentMode;
         mCurrentPowerRole = currentPowerRole;
         mCurrentDataRole = currentDataRole;
@@ -312,21 +360,29 @@
         mUsbDataStatus = usbDataStatus;
         mPowerTransferLimited = powerTransferLimited;
         mPowerBrickConnectionStatus = powerBrickConnectionStatus;
+        mComplianceWarnings = complianceWarnings;
+    }
+
+    /** @hide */
+    public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole,
+            int supportedRoleCombinations, int contaminantProtectionStatus,
+            int contaminantDetectionStatus, @UsbDataStatus int usbDataStatus,
+            boolean powerTransferLimited,
+            @PowerBrickConnectionStatus int powerBrickConnectionStatus) {
+        this(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations,
+                contaminantProtectionStatus, contaminantDetectionStatus,
+                usbDataStatus, powerTransferLimited, powerBrickConnectionStatus,
+                new int[] {});
     }
 
     /** @hide */
     public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole,
             int supportedRoleCombinations, int contaminantProtectionStatus,
             int contaminantDetectionStatus) {
-        mCurrentMode = currentMode;
-        mCurrentPowerRole = currentPowerRole;
-        mCurrentDataRole = currentDataRole;
-        mSupportedRoleCombinations = supportedRoleCombinations;
-        mContaminantProtectionStatus = contaminantProtectionStatus;
-        mContaminantDetectionStatus = contaminantDetectionStatus;
-        mUsbDataStatus = DATA_STATUS_UNKNOWN;
-        mPowerBrickConnectionStatus = POWER_BRICK_STATUS_UNKNOWN;
-        mPowerTransferLimited = false;
+        this(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations,
+                contaminantProtectionStatus, contaminantDetectionStatus,
+                DATA_STATUS_UNKNOWN, false, POWER_BRICK_STATUS_UNKNOWN,
+                new int[] {});
     }
 
     /**
@@ -443,6 +499,21 @@
         return mPowerBrickConnectionStatus;
     }
 
+    /**
+     * Returns non compliant reasons, if any, for the connected
+     * charger/cable/accessory/USB port.
+     *
+     * @return array including {@link #NON_COMPLIANT_REASON_DEBUG_ACCESSORY},
+     *         {@link #NON_COMPLIANT_REASON_BC12},
+     *         {@link #NON_COMPLIANT_REASON_MISSING_RP},
+     *         or {@link #NON_COMPLIANT_REASON_TYPEC}
+     */
+    @CheckResult
+    @NonNull
+    public @ComplianceWarning int[] getComplianceWarnings() {
+        return mComplianceWarnings;
+    }
+
     @NonNull
     @Override
     public String toString() {
@@ -460,9 +531,11 @@
                         + UsbPort.usbDataStatusToString(getUsbDataStatus())
                 + ", isPowerTransferLimited="
                         + isPowerTransferLimited()
-                +", powerBrickConnectionStatus="
+                + ", powerBrickConnectionStatus="
                         + UsbPort
                             .powerBrickConnectionStatusToString(getPowerBrickConnectionStatus())
+                + ", complianceWarnings="
+                        + UsbPort.complianceWarningsToString(getComplianceWarnings())
                 + "}";
     }
 
@@ -482,6 +555,7 @@
         dest.writeInt(mUsbDataStatus);
         dest.writeBoolean(mPowerTransferLimited);
         dest.writeInt(mPowerBrickConnectionStatus);
+        dest.writeIntArray(mComplianceWarnings);
     }
 
     public static final @NonNull Parcelable.Creator<UsbPortStatus> CREATOR =
@@ -497,10 +571,12 @@
             int usbDataStatus = in.readInt();
             boolean powerTransferLimited = in.readBoolean();
             int powerBrickConnectionStatus = in.readInt();
+            @ComplianceWarning int[] complianceWarnings = in.createIntArray();
             return new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
                     supportedRoleCombinations, contaminantProtectionStatus,
                     contaminantDetectionStatus, usbDataStatus, powerTransferLimited,
-                    powerBrickConnectionStatus);
+                    powerBrickConnectionStatus,
+                    complianceWarnings);
         }
 
         @Override
@@ -524,6 +600,7 @@
         private boolean mPowerTransferLimited;
         private @UsbDataStatus int mUsbDataStatus;
         private @PowerBrickConnectionStatus int mPowerBrickConnectionStatus;
+        private @ComplianceWarning int[] mComplianceWarnings;
 
         public Builder() {
             mCurrentMode = MODE_NONE;
@@ -533,6 +610,7 @@
             mContaminantDetectionStatus = CONTAMINANT_DETECTION_NOT_SUPPORTED;
             mUsbDataStatus = DATA_STATUS_UNKNOWN;
             mPowerBrickConnectionStatus = POWER_BRICK_STATUS_UNKNOWN;
+            mComplianceWarnings = new int[] {};
         }
 
         /**
@@ -619,6 +697,20 @@
         }
 
         /**
+         * Sets the non-compliant charger reasons of {@link UsbPortStatus}
+         *
+         * @return Instance of {@link Builder}
+         */
+        @NonNull
+        public Builder setComplianceWarnings(
+                @NonNull int[] complianceWarnings) {
+            mComplianceWarnings = complianceWarnings == null ? new int[] {} :
+                    complianceWarnings;
+            return this;
+        }
+
+
+        /**
          * Creates the {@link UsbPortStatus} object.
          */
         @NonNull
@@ -626,7 +718,7 @@
             UsbPortStatus status = new UsbPortStatus(mCurrentMode, mCurrentPowerRole,
                     mCurrentDataRole, mSupportedRoleCombinations, mContaminantProtectionStatus,
                     mContaminantDetectionStatus, mUsbDataStatus, mPowerTransferLimited,
-                    mPowerBrickConnectionStatus);
+                    mPowerBrickConnectionStatus, mComplianceWarnings);
             return status;
         }
     };
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index d23fb36..d55367f 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -32,6 +32,7 @@
 import android.util.Log;
 import android.view.InputChannel;
 import android.view.MotionEvent;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethod;
@@ -93,9 +94,10 @@
     final int mTargetSdkVersion;
 
     /**
-     * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()}
-     * so that {@link RemoteInputConnection} can query if {@link #unbindInput()} has already been
-     * called or not, mainly to avoid unnecessary blocking operations.
+     * This is not {@code null} only between {@link #bindInput(InputBinding)} and
+     * {@link #unbindInput()} so that {@link RemoteInputConnection} can query if
+     * {@link #unbindInput()} has already been called or not, mainly to avoid unnecessary
+     * blocking operations.
      *
      * <p>This field must be set and cleared only from the binder thread(s), where the system
      * guarantees that {@link #bindInput(InputBinding)},
@@ -219,18 +221,26 @@
                 return;
             case DO_SHOW_SOFT_INPUT: {
                 final SomeArgs args = (SomeArgs) msg.obj;
+                final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
                 if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) {
+                    ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                     inputMethod.showSoftInputWithToken(
-                            msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
+                            msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1, statsToken);
+                } else {
+                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                 }
                 args.recycle();
                 return;
             }
             case DO_HIDE_SOFT_INPUT: {
                 final SomeArgs args = (SomeArgs) msg.obj;
+                final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
                 if (isValid(inputMethod, target, "DO_HIDE_SOFT_INPUT")) {
+                    ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                     inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
-                            (IBinder) args.arg1);
+                            (IBinder) args.arg1, statsToken);
+                } else {
+                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                 }
                 args.recycle();
                 return;
@@ -416,16 +426,20 @@
 
     @BinderThread
     @Override
-    public void showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
-        mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_SHOW_SOFT_INPUT,
-                flags, showInputToken, resultReceiver));
+    public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
+            int flags, ResultReceiver resultReceiver) {
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+        mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT,
+                flags, showInputToken, resultReceiver, statsToken));
     }
 
     @BinderThread
     @Override
-    public void hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
-        mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_HIDE_SOFT_INPUT,
-                flags, hideInputToken, resultReceiver));
+    public void hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
+            int flags, ResultReceiver resultReceiver) {
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+        mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_HIDE_SOFT_INPUT,
+                flags, hideInputToken, resultReceiver, statsToken));
     }
 
     @BinderThread
diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
index 4f09bee..8759a6a 100644
--- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
+++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
@@ -16,30 +16,30 @@
 
 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.CancellationSignal;
 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 +47,7 @@
 
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
 /**
@@ -98,6 +99,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 +676,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(
@@ -690,6 +698,27 @@
     }
 
     /**
+     * Invokes one of {@link IRemoteInputConnection#previewHandwritingGesture(
+     * InputConnectionCommandHeader, ParcelableHandwritingGesture, CancellationSignal)}
+     */
+    @AnyThread
+    public boolean previewHandwritingGesture(
+            @NonNull ParcelableHandwritingGesture gesture,
+            @Nullable CancellationSignal cancellationSignal) {
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            return false; // cancelled.
+        }
+
+        // TODO(b/254727073): Implement CancellationSignal
+        try {
+            mConnection.previewHandwritingGesture(createHeader(), gesture, null);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
      * Invokes {@link IRemoteInputConnection#requestCursorUpdates(InputConnectionCommandHeader, int,
      * int, AndroidFuture)}.
      *
@@ -736,6 +765,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)}.
      *
@@ -777,8 +828,7 @@
     }
 
     /**
-     * Invokes {@code IRemoteInputConnection#replaceText(InputConnectionCommandHeader, int, int,
-     * CharSequence, TextAttribute)}.
+     * Replaces the specific range in the current input field with suggested text.
      *
      * @param start the character index where the replacement should start.
      * @param end the character index where the replacement should end.
@@ -788,8 +838,9 @@
      *     that this means you can't position the cursor within the text.
      * @param text the text to replace. This may include styles.
      * @param textAttribute The extra information about the text. This value may be null.
-     * @return {@code true} if the invocation is completed without {@link RemoteException}, {@code
-     *     false} otherwise.
+     * @return {@code true} if the specific range is replaced successfully, {@code false} otherwise.
+     * @see android.view.inputmethod.InputConnection#replaceText(int, int, CharSequence, int,
+     *     TextAttribute)
      */
     @AnyThread
     public boolean replaceText(
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 39d362b..872414a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -70,11 +70,14 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -98,6 +101,7 @@
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
+import android.util.Xml;
 import android.util.proto.ProtoOutputStream;
 import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver;
 import android.view.Choreographer;
@@ -124,6 +128,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InlineSuggestionsResponse;
 import android.view.inputmethod.InputBinding;
@@ -157,6 +162,8 @@
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.util.RingBuffer;
 
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -669,6 +676,10 @@
      */
     private IBinder mCurHideInputToken;
 
+    /** The token tracking the current IME request or {@code null} otherwise. */
+    @Nullable
+    private ImeTracker.Token mCurStatsToken;
+
     final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
         onComputeInsets(mTmpInsets);
         if (!mViewsCreated) {
@@ -725,7 +736,6 @@
         @Override
         public final void initializeInternal(@NonNull IInputMethod.InitParams params) {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal");
-            mConfigTracker.onInitialize(params.configChanges);
             mPrivOps.set(params.privilegedOperations);
             InputMethodPrivilegedOperationsRegistry.put(params.token, mPrivOps);
             mNavigationBarController.onNavButtonFlagsChanged(params.navigationBarFlags);
@@ -870,10 +880,12 @@
         @MainThread
         @Override
         public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
-                IBinder hideInputToken) {
+                IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) {
             mSystemCallingHideSoftInput = true;
             mCurHideInputToken = hideInputToken;
+            mCurStatsToken = statsToken;
             hideSoftInput(flags, resultReceiver);
+            mCurStatsToken = null;
             mCurHideInputToken = null;
             mSystemCallingHideSoftInput = false;
         }
@@ -884,6 +896,7 @@
         @MainThread
         @Override
         public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
+            ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
             if (DEBUG) Log.v(TAG, "hideSoftInput()");
             if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
                     && !mSystemCallingHideSoftInput) {
@@ -918,12 +931,17 @@
         @MainThread
         @Override
         public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
-                IBinder showInputToken) {
+                IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
             mSystemCallingShowSoftInput = true;
             mCurShowInputToken = showInputToken;
-            showSoftInput(flags, resultReceiver);
-            mCurShowInputToken = null;
-            mSystemCallingShowSoftInput = false;
+            mCurStatsToken = statsToken;
+            try {
+                showSoftInput(flags, resultReceiver);
+            } finally {
+                mCurStatsToken = null;
+                mCurShowInputToken = null;
+                mSystemCallingShowSoftInput = false;
+            }
         }
 
         /**
@@ -932,6 +950,7 @@
         @MainThread
         @Override
         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
+            ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
             if (DEBUG) Log.v(TAG, "showSoftInput()");
             // TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods.
             if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
@@ -947,7 +966,12 @@
                     null /* icProto */);
             final boolean wasVisible = isInputViewShown();
             if (dispatchOnShowInputRequested(flags, false)) {
+                ImeTracker.get().onProgress(mCurStatsToken,
+                        ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
                 showWindow(true);
+            } else {
+                ImeTracker.get().onFailed(mCurStatsToken,
+                        ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
             }
             setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
 
@@ -1582,6 +1606,8 @@
         mHideNavBarForKeyboard = getApplicationContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_hideNavBarForKeyboard);
 
+        initConfigurationTracker();
+
         // TODO(b/111364446) Need to address context lifecycle issue if need to re-create
         // for update resources & configuration correctly when show soft input
         // in non-default display.
@@ -1637,6 +1663,36 @@
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
+    private void initConfigurationTracker() {
+        final int flags = PackageManager.GET_META_DATA
+                | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+        final ComponentName imeComponent = new ComponentName(
+                getPackageName(), getClass().getName());
+        final String imeId = imeComponent.flattenToShortString();
+        final ServiceInfo si;
+        try {
+            si = getPackageManager().getServiceInfo(imeComponent,
+                    PackageManager.ComponentInfoFlags.of(flags));
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.wtf(TAG, "Unable to find input method " + imeId, e);
+            return;
+        }
+        try (XmlResourceParser parser = si.loadXmlMetaData(getPackageManager(),
+                InputMethod.SERVICE_META_DATA);
+             TypedArray sa = getResources().obtainAttributes(Xml.asAttributeSet(parser),
+                     com.android.internal.R.styleable.InputMethod)) {
+            if (parser == null) {
+                throw new XmlPullParserException(
+                        "No " + InputMethod.SERVICE_META_DATA + " meta-data");
+            }
+            final int handledConfigChanges = sa.getInt(
+                    com.android.internal.R.styleable.InputMethod_configChanges, 0);
+            mConfigTracker.onInitialize(handledConfigChanges);
+        } catch (Exception e) {
+            Log.wtf(TAG, "Unable to load input method " + imeId, e);
+        }
+    }
+
     /**
      * This is a hook that subclasses can use to perform initialization of
      * their interface.  It is called for you prior to any of your UI objects
@@ -1774,16 +1830,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 +1845,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
@@ -2272,6 +2324,8 @@
      * current input field.
      * 
      * @param id Unique identifier of the new input method to start.
+     * @throws IllegalArgumentException if the input method is unknown or filtered
+     * by the rules of <a href="/training/basics/intents/package-visibility">package visibility</a>.
      */
     public void switchInputMethod(String id) {
         mPrivOps.setInputMethod(id);
@@ -2284,6 +2338,8 @@
      *
      * @param id Unique identifier of the new input method to start.
      * @param subtype The new subtype of the new input method to be switched to.
+     * @throws IllegalArgumentException if the input method is unknown or filtered
+     * by the rules of <a href="/training/basics/intents/package-visibility">package visibility</a>.
      */
     public final void switchInputMethod(String id, InputMethodSubtype subtype) {
         mPrivOps.setInputMethodAndSubtype(id, subtype);
@@ -2747,12 +2803,12 @@
             return false;
         }
         if ((flags&InputMethod.SHOW_EXPLICIT) == 0) {
-            if (!configChange && onEvaluateFullscreenMode()) {
+            if (!configChange && onEvaluateFullscreenMode() && !isInputViewShown()) {
                 // Don't show if this is not explicitly requested by the user and
-                // the input method is fullscreen.  That would be too disruptive.
-                // However, we skip this change for a config change, since if
-                // the IME is already shown we do want to go into fullscreen
-                // mode at this point.
+                // the input method is fullscreen unless it is already shown. That
+                // would be too disruptive. However, we skip this change for a
+                // config change, since if the IME is already shown we do want to
+                // go into fullscreen mode at this point.
                 return false;
             }
             if (!mSettingsObserver.shouldShowImeWithHardKeyboard() &&
@@ -2923,8 +2979,10 @@
         ImeTracing.getInstance().triggerServiceDump(
                 "InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", mDumper,
                 null /* icProto */);
+        ImeTracker.get().onProgress(mCurStatsToken,
+                ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
         mPrivOps.applyImeVisibilityAsync(setVisible
-                ? mCurShowInputToken : mCurHideInputToken, setVisible);
+                ? mCurShowInputToken : mCurHideInputToken, setVisible, mCurStatsToken);
     }
 
     private void finishViews(boolean finishingInput) {
diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java
index 09e86c4..f93f9ab 100644
--- a/core/java/android/inputmethodservice/RemoteInputConnection.java
+++ b/core/java/android/inputmethodservice/RemoteInputConnection.java
@@ -21,7 +21,9 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.RectF;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -32,8 +34,11 @@
 import android.view.inputmethod.HandwritingGesture;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.ParcelableHandwritingGesture;
+import android.view.inputmethod.PreviewableHandwritingGesture;
 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 +49,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 +424,16 @@
     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
+    public boolean previewHandwritingGesture(
+            @NonNull PreviewableHandwritingGesture gesture,
+            @Nullable CancellationSignal cancellationSignal) {
+        return mInvoker.previewHandwritingGesture(ParcelableHandwritingGesture.of(gesture),
+                cancellationSignal);
     }
 
     @AnyThread
@@ -460,6 +475,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/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
index f8e2558..d4b76c8 100644
--- a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
+++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
@@ -53,8 +53,8 @@
 import java.net.ProtocolException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -89,12 +89,10 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface Prefix {}
 
-    private static final HashMap<String, String> sPrefixLegacyFileNameMap =
-            new HashMap<String, String>() {{
-                put(PREFIX_XT, "netstats_xt.bin");
-                put(PREFIX_UID, "netstats_uid.bin");
-                put(PREFIX_UID_TAG, "netstats_uid.bin");
-            }};
+    private static final Map<String, String> sPrefixLegacyFileNameMap = Map.of(
+            PREFIX_XT, "netstats_xt.bin",
+            PREFIX_UID, "netstats_uid.bin",
+            PREFIX_UID_TAG, "netstats_uid.bin");
 
     // These version constants are copied from NetworkStatsCollection/History, which is okay for
     // OEMs to modify to adapt their own logic.
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 40e4083..3a7aea5 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -104,12 +104,23 @@
 
     // TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz
 
+    /**
+     * Key for transports that need to be marked as restricted by the VCN
+     *
+     * <p>Defaults to TRANSPORT_WIFI if the config does not exist
+     *
+     * @hide
+     */
+    public static final String VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY =
+            "vcn_restricted_transports";
+
     /** List of Carrier Config options to extract from Carrier Config bundles. @hide */
     @NonNull
     public static final String[] VCN_RELATED_CARRIER_CONFIG_KEYS =
             new String[] {
                 VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
-                VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY
+                VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
+                VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY
             };
 
     private static final Map<
diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
index 4bc5b49..0427742 100644
--- a/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
+++ b/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
@@ -53,7 +53,7 @@
         if (in.keySet().size() != EXPECTED_BUNDLE_KEY_CNT) {
             throw new IllegalArgumentException(
                     String.format(
-                            "Expect PersistableBundle to have %d element but found: %d",
+                            "Expect PersistableBundle to have %d element but found: %s",
                             EXPECTED_BUNDLE_KEY_CNT, in.keySet()));
         }
 
diff --git a/core/java/android/nfc/AvailableNfcAntenna.aidl b/core/java/android/nfc/AvailableNfcAntenna.aidl
new file mode 100644
index 0000000..9d06e2d
--- /dev/null
+++ b/core/java/android/nfc/AvailableNfcAntenna.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.nfc;
+
+parcelable AvailableNfcAntenna;
diff --git a/core/java/android/nfc/AvailableNfcAntenna.java b/core/java/android/nfc/AvailableNfcAntenna.java
new file mode 100644
index 0000000..6e6512a
--- /dev/null
+++ b/core/java/android/nfc/AvailableNfcAntenna.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.nfc;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a single available Nfc antenna
+ * on an Android device.
+ */
+public final class AvailableNfcAntenna implements Parcelable {
+    /**
+     * Location of the antenna on the Y axis in millimeters.
+     * 0 is the bottom-left when the user is facing the screen
+     * and the device orientation is Portrait.
+     */
+    private final int mLocationX;
+    /**
+     * Location of the antenna on the Y axis in millimeters.
+     * 0 is the bottom-left when the user is facing the screen
+     * and the device orientation is Portrait.
+     */
+    private final int mLocationY;
+
+    public AvailableNfcAntenna(int locationX, int locationY) {
+        this.mLocationX = locationX;
+        this.mLocationY = locationY;
+    }
+
+    /**
+     * Location of the antenna on the X axis in millimeters.
+     * 0 is the bottom-left when the user is facing the screen
+     * and the device orientation is Portrait.
+     */
+    public int getLocationX() {
+        return mLocationX;
+    }
+
+    /**
+     * Location of the antenna on the Y axis in millimeters.
+     * 0 is the bottom-left when the user is facing the screen
+     * and the device orientation is Portrait.
+     */
+    public int getLocationY() {
+        return mLocationY;
+    }
+
+    private AvailableNfcAntenna(Parcel in) {
+        this.mLocationX = in.readInt();
+        this.mLocationY = in.readInt();
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<AvailableNfcAntenna>
+            CREATOR = new Parcelable.Creator<AvailableNfcAntenna>() {
+                @Override
+                public AvailableNfcAntenna createFromParcel(Parcel in) {
+                    return new AvailableNfcAntenna(in);
+                }
+
+                @Override
+                public AvailableNfcAntenna[] newArray(int size) {
+                    return new AvailableNfcAntenna[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mLocationX);
+        dest.writeInt(mLocationY);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + mLocationX;
+        result = prime * result + mLocationY;
+        return result;
+    }
+
+    /**
+     * Returns true if the specified AvailableNfcAntenna contains
+     * identical specifications.
+     */
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        AvailableNfcAntenna other = (AvailableNfcAntenna) obj;
+        if (this.mLocationX != other.mLocationX) return false;
+        return this.mLocationY == other.mLocationY;
+    }
+
+    @Override
+    public String toString() {
+        return "AvailableNfcAntenna " + "x: " + mLocationX + " y: " + mLocationY;
+    }
+}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index cb9a3e4..de107a2 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -31,6 +31,7 @@
 import android.nfc.INfcUnlockHandler;
 import android.nfc.ITagRemovedCallback;
 import android.nfc.INfcDta;
+import android.nfc.NfcAntennaInfo;
 import android.os.Bundle;
 
 /**
@@ -72,6 +73,7 @@
     boolean isNfcSecureEnabled();
     boolean deviceSupportsNfcSecure();
     boolean setNfcSecure(boolean enable);
+    NfcAntennaInfo getNfcAntennaInfo();
 
     boolean setControllerAlwaysOn(boolean value);
     boolean isControllerAlwaysOn();
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 64c1211..f545c30 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -18,6 +18,7 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -29,7 +30,6 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.IntentFilter;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.nfc.tech.MifareClassic;
@@ -525,66 +525,6 @@
     }
 
     /**
-     * Helper to check if this device has FEATURE_NFC_BEAM, but without using
-     * a context.
-     * Equivalent to
-     * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_BEAM)
-     */
-    private static boolean hasBeamFeature() {
-        IPackageManager pm = ActivityThread.getPackageManager();
-        if (pm == null) {
-            Log.e(TAG, "Cannot get package manager, assuming no Android Beam feature");
-            return false;
-        }
-        try {
-            return pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM, 0);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Package manager query failed, assuming no Android Beam feature", e);
-            return false;
-        }
-    }
-
-    /**
-     * Helper to check if this device has FEATURE_NFC, but without using
-     * a context.
-     * Equivalent to
-     * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)
-     */
-    private static boolean hasNfcFeature() {
-        IPackageManager pm = ActivityThread.getPackageManager();
-        if (pm == null) {
-            Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
-            return false;
-        }
-        try {
-            return pm.hasSystemFeature(PackageManager.FEATURE_NFC, 0);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
-            return false;
-        }
-    }
-
-    /**
-     * Helper to check if this device is NFC HCE capable, by checking for
-     * FEATURE_NFC_HOST_CARD_EMULATION and/or FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
-     * but without using a context.
-     */
-    private static boolean hasNfcHceFeature() {
-        IPackageManager pm = ActivityThread.getPackageManager();
-        if (pm == null) {
-            Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
-            return false;
-        }
-        try {
-            return pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)
-                || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
-            return false;
-        }
-    }
-
-    /**
      * Return list of Secure Elements which support off host card emulation.
      *
      * @return List<String> containing secure elements on the device which supports
@@ -593,23 +533,21 @@
      * @hide
      */
     public @NonNull List<String> getSupportedOffHostSecureElements() {
+        if (mContext == null) {
+            throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+                    + " getSupportedOffHostSecureElements APIs");
+        }
         List<String> offHostSE = new ArrayList<String>();
-        IPackageManager pm = ActivityThread.getPackageManager();
+        PackageManager pm = mContext.getPackageManager();
         if (pm == null) {
             Log.e(TAG, "Cannot get package manager, assuming no off-host CE feature");
             return offHostSE;
         }
-        try {
-            if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC, 0)) {
-                offHostSE.add("SIM");
-            }
-            if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE, 0)) {
-                offHostSE.add("eSE");
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Package manager query failed, assuming no off-host CE feature", e);
-            offHostSE.clear();
-            return offHostSE;
+        if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)) {
+            offHostSE.add("SIM");
+        }
+        if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE)) {
+            offHostSE.add("eSE");
         }
         return offHostSE;
     }
@@ -621,10 +559,19 @@
      */
     @UnsupportedAppUsage
     public static synchronized NfcAdapter getNfcAdapter(Context context) {
+        if (context == null) {
+            if (sNullContextNfcAdapter == null) {
+                sNullContextNfcAdapter = new NfcAdapter(null);
+            }
+            return sNullContextNfcAdapter;
+        }
         if (!sIsInitialized) {
-            sHasNfcFeature = hasNfcFeature();
-            sHasBeamFeature = hasBeamFeature();
-            boolean hasHceFeature = hasNfcHceFeature();
+            PackageManager pm = context.getPackageManager();
+            sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
+            sHasBeamFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM);
+            boolean hasHceFeature =
+                    pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
+                    || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF);
             /* is this device meant to have NFC */
             if (!sHasNfcFeature && !hasHceFeature) {
                 Log.v(TAG, "this device does not have NFC support");
@@ -660,12 +607,6 @@
 
             sIsInitialized = true;
         }
-        if (context == null) {
-            if (sNullContextNfcAdapter == null) {
-                sNullContextNfcAdapter = new NfcAdapter(null);
-            }
-            return sNullContextNfcAdapter;
-        }
         NfcAdapter adapter = sNfcAdapters.get(context);
         if (adapter == null) {
             adapter = new NfcAdapter(context);
@@ -676,8 +617,12 @@
 
     /** get handle to NFC service interface */
     private static INfcAdapter getServiceInterface() {
+        if (!sHasNfcFeature) {
+            /* NFC is not supported */
+            return null;
+        }
         /* get a handle to NFC service */
-        IBinder b = ServiceManager.getService("nfc");
+        IBinder b = ServiceManager.waitForService("nfc");
         if (b == null) {
             return null;
         }
@@ -707,6 +652,15 @@
                     "context not associated with any application (using a mock context?)");
         }
 
+        synchronized (NfcAdapter.class) {
+            if (!sIsInitialized) {
+                PackageManager pm = context.getPackageManager();
+                sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
+            }
+            if (!sHasNfcFeature) {
+                return null;
+            }
+        }
         if (getServiceInterface() == null) {
             // NFC is not available
             return null;
@@ -1901,6 +1855,36 @@
     }
 
     /**
+     * Returns information regarding Nfc antennas on the device
+     * such as their relative positioning on the device.
+     *
+     * @return Information on the nfc antenna(s) on the device.
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     */
+    @Nullable
+    public NfcAntennaInfo getNfcAntennaInfo() {
+        if (!sHasNfcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.getNfcAntennaInfo();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return null;
+            }
+            try {
+                return sService.getNfcAntennaInfo();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return null;
+        }
+    }
+
+    /**
      * Checks Secure NFC feature is enabled.
      *
      * @return True if Secure NFC is enabled, false otherwise
diff --git a/core/java/android/nfc/NfcAntennaInfo.aidl b/core/java/android/nfc/NfcAntennaInfo.aidl
new file mode 100644
index 0000000..d5e79fc
--- /dev/null
+++ b/core/java/android/nfc/NfcAntennaInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.nfc;
+
+parcelable NfcAntennaInfo;
diff --git a/core/java/android/nfc/NfcAntennaInfo.java b/core/java/android/nfc/NfcAntennaInfo.java
new file mode 100644
index 0000000..d54fcd2
--- /dev/null
+++ b/core/java/android/nfc/NfcAntennaInfo.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.nfc;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Contains information on all available Nfc
+ * antennas on an Android device as well as information
+ * on the device itself in relation positioning of the
+ * antennas.
+ */
+public final class NfcAntennaInfo implements Parcelable {
+    // Width of the device in millimeters.
+    private final int mDeviceWidth;
+    // Height of the device in millimeters.
+    private final int mDeviceHeight;
+    // Whether the device is foldable.
+    private final boolean mDeviceFoldable;
+    // All available Nfc Antennas on the device.
+    private final List<AvailableNfcAntenna> mAvailableNfcAntennas;
+
+    public NfcAntennaInfo(int deviceWidth, int deviceHeight, boolean deviceFoldable,
+            @NonNull List<AvailableNfcAntenna> availableNfcAntennas) {
+        this.mDeviceWidth = deviceWidth;
+        this.mDeviceHeight = deviceHeight;
+        this.mDeviceFoldable = deviceFoldable;
+        this.mAvailableNfcAntennas = availableNfcAntennas;
+    }
+
+    /**
+     * Width of the device in millimeters.
+     */
+    public int getDeviceWidth() {
+        return mDeviceWidth;
+    }
+
+    /**
+     * Height of the device in millimeters.
+     */
+    public int getDeviceHeight() {
+        return mDeviceHeight;
+    }
+
+    /**
+     * Whether the device is foldable. When the device is foldable,
+     * the 0, 0 is considered to be bottom-left when the device is unfolded and
+     * the screens are facing the user. For non-foldable devices 0, 0
+     * is bottom-left when the user is facing the screen.
+     */
+    public boolean isDeviceFoldable() {
+        return mDeviceFoldable;
+    }
+
+    /**
+     * Get all NFC antennas that exist on the device.
+     */
+    @NonNull
+    public List<AvailableNfcAntenna> getAvailableNfcAntennas() {
+        return mAvailableNfcAntennas;
+    }
+
+    private NfcAntennaInfo(Parcel in) {
+        this.mDeviceWidth = in.readInt();
+        this.mDeviceHeight = in.readInt();
+        this.mDeviceFoldable = in.readByte() != 0;
+        this.mAvailableNfcAntennas = new ArrayList<>();
+        in.readParcelableList(this.mAvailableNfcAntennas,
+                AvailableNfcAntenna.class.getClassLoader());
+    }
+
+    public static final @NonNull Parcelable.Creator<NfcAntennaInfo> CREATOR =
+            new Parcelable.Creator<NfcAntennaInfo>() {
+        @Override
+        public NfcAntennaInfo createFromParcel(Parcel in) {
+            return new NfcAntennaInfo(in);
+        }
+
+        @Override
+        public NfcAntennaInfo[] newArray(int size) {
+            return new NfcAntennaInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mDeviceWidth);
+        dest.writeInt(mDeviceHeight);
+        dest.writeByte((byte) (mDeviceFoldable ? 1 : 0));
+        dest.writeTypedList(mAvailableNfcAntennas, 0);
+    }
+}
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 0b56d19..6a42091 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -22,11 +22,9 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.app.Activity;
-import android.app.ActivityThread;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.nfc.INfcCardEmulation;
 import android.nfc.NfcAdapter;
@@ -158,18 +156,13 @@
             throw new UnsupportedOperationException();
         }
         if (!sIsInitialized) {
-            IPackageManager pm = ActivityThread.getPackageManager();
+            PackageManager pm = context.getPackageManager();
             if (pm == null) {
                 Log.e(TAG, "Cannot get PackageManager");
                 throw new UnsupportedOperationException();
             }
-            try {
-                if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)) {
-                    Log.e(TAG, "This device does not support card emulation");
-                    throw new UnsupportedOperationException();
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "PackageManager query failed.");
+            if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
+                Log.e(TAG, "This device does not support card emulation");
                 throw new UnsupportedOperationException();
             }
             sIsInitialized = true;
diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
index 3c92455..48bbf5b6 100644
--- a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
+++ b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
@@ -17,10 +17,8 @@
 package android.nfc.cardemulation;
 
 import android.app.Activity;
-import android.app.ActivityThread;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.nfc.INfcFCardEmulation;
 import android.nfc.NfcAdapter;
@@ -70,18 +68,13 @@
             throw new UnsupportedOperationException();
         }
         if (!sIsInitialized) {
-            IPackageManager pm = ActivityThread.getPackageManager();
+            PackageManager pm = context.getPackageManager();
             if (pm == null) {
                 Log.e(TAG, "Cannot get PackageManager");
                 throw new UnsupportedOperationException();
             }
-            try {
-                if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0)) {
-                    Log.e(TAG, "This device does not support NFC-F card emulation");
-                    throw new UnsupportedOperationException();
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "PackageManager query failed.");
+            if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)) {
+                Log.e(TAG, "This device does not support NFC-F card emulation");
                 throw new UnsupportedOperationException();
             }
             sIsInitialized = true;
diff --git a/core/java/android/os/ArtModuleServiceManager.java b/core/java/android/os/ArtModuleServiceManager.java
new file mode 100644
index 0000000..0009e61
--- /dev/null
+++ b/core/java/android/os/ArtModuleServiceManager.java
@@ -0,0 +1,63 @@
+/*
+ * 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.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the ART
+ * mainline module.
+ *
+ * Only the ART mainline module will be able to access an instance of this class.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class ArtModuleServiceManager {
+    /** @hide */
+    public ArtModuleServiceManager() {}
+
+    /** A class that exposes the method to obtain each system service. */
+    public static final class ServiceRegisterer {
+        @NonNull private final String mServiceName;
+
+        /** @hide */
+        public ServiceRegisterer(@NonNull String serviceName) {
+            mServiceName = serviceName;
+        }
+
+        /**
+         * Returns the service from the service manager.
+         *
+         * If the service is not running, servicemanager will attempt to start it, and this function
+         * will wait for it to be ready.
+         *
+         * @return {@code null} only if there are permission problems or fatal errors.
+         */
+        @Nullable
+        public IBinder waitForService() {
+            return ServiceManager.waitForService(mServiceName);
+        }
+    }
+
+    /** Returns {@link ServiceRegisterer} for the "artd" service. */
+    @NonNull
+    public ServiceRegisterer getArtdServiceRegisterer() {
+        return new ServiceRegisterer("artd");
+    }
+}
diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java
index 97ec594..9bad0de 100644
--- a/core/java/android/os/BatteryManagerInternal.java
+++ b/core/java/android/os/BatteryManagerInternal.java
@@ -47,6 +47,14 @@
     public abstract int getBatteryLevel();
 
     /**
+     * Returns battery health status as an integer representing the current battery health constant.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
+     */
+    public abstract int getBatteryHealth();
+
+    /**
      * Instantaneous battery capacity in uA-h, as defined in the HealthInfo HAL struct.
      * Please note apparently it could be bigger than {@link #getBatteryFullCharge}.
      *
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 3c4abab..def0cbd 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -328,16 +328,48 @@
     public static final native boolean isDirectlyHandlingTransaction();
 
     /**
+    * Returns {@code true} if the current thread has had its identity
+    * set explicitly via {@link #clearCallingIdentity()}
+    *
+    * @hide
+    */
+    @CriticalNative
+    private static native boolean hasExplicitIdentity();
+
+    /**
      * Return the Linux UID assigned to the process that sent the transaction
      * currently being processed.
      *
      * @throws IllegalStateException if the current thread is not currently
-     * executing an incoming transaction.
+     * executing an incoming transaction and the calling identity has not been
+     * explicitly set with {@link #clearCallingIdentity()}
      */
     public static final int getCallingUidOrThrow() {
-        if (!isDirectlyHandlingTransaction()) {
+        if (!isDirectlyHandlingTransaction() && !hasExplicitIdentity()) {
             throw new IllegalStateException(
-                  "Thread is not in a binder transcation");
+                  "Thread is not in a binder transaction, "
+                  + "and the calling identity has not been "
+                  + "explicitly set with clearCallingIdentity");
+        }
+        return getCallingUid();
+    }
+
+    /**
+     * Return the Linux UID assigned to the process that sent the transaction
+     * currently being processed.
+     *
+     * Logs WTF if the current thread is not currently
+     * executing an incoming transaction and the calling identity has not been
+     * explicitly set with {@link #clearCallingIdentity()}
+     *
+     * @hide
+     */
+    public static final int getCallingUidOrWtf() {
+        if (!isDirectlyHandlingTransaction() && !hasExplicitIdentity()) {
+            Log.wtfStack(TAG,
+                    "Thread is not in a binder transaction, "
+                            + "and the calling identity has not been "
+                            + "explicitly set with clearCallingIdentity");
         }
         return getCallingUid();
     }
@@ -911,11 +943,15 @@
             final String transactionName = getTransactionName(transactionCode);
             final StringBuffer buf = new StringBuffer();
 
+            // Keep trace name consistent with cpp trace name in:
+            // system/tools/aidl/generate_cpp.cpp
+            buf.append("AIDL::java::");
             if (transactionName != null) {
-                buf.append(mSimpleDescriptor).append(":").append(transactionName);
+                buf.append(mSimpleDescriptor).append("::").append(transactionName);
             } else {
-                buf.append(mSimpleDescriptor).append("#").append(transactionCode);
+                buf.append(mSimpleDescriptor).append("::#").append(transactionCode);
             }
+            buf.append("::server");
 
             transactionTraceName = buf.toString();
             mTransactionTraceNames.setRelease(index, transactionTraceName);
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 6330661..1929a4d 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -536,8 +536,8 @@
             mWarnOnBlocking = false;
             warnOnBlocking = false;
 
-            if (Build.IS_USERDEBUG) {
-                // Log this as a WTF on userdebug builds.
+            if (Build.IS_USERDEBUG || Build.IS_ENG) {
+                // Log this as a WTF on userdebug and eng builds.
                 Log.wtf(Binder.TAG,
                         "Outgoing transactions from this process must be FLAG_ONEWAY",
                         new Throwable());
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index d5c3de1..b478a379 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -84,7 +84,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
-import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -1314,31 +1313,31 @@
     private static long toBytes(long value, String unit) {
         unit = unit.toUpperCase();
 
-        if (List.of("B").contains(unit)) {
+        if ("B".equals(unit)) {
             return value;
         }
 
-        if (List.of("K", "KB").contains(unit)) {
+        if ("K".equals(unit) || "KB".equals(unit)) {
             return DataUnit.KILOBYTES.toBytes(value);
         }
 
-        if (List.of("M", "MB").contains(unit)) {
+        if ("M".equals(unit) || "MB".equals(unit)) {
             return DataUnit.MEGABYTES.toBytes(value);
         }
 
-        if (List.of("G", "GB").contains(unit)) {
+        if ("G".equals(unit) || "GB".equals(unit)) {
             return DataUnit.GIGABYTES.toBytes(value);
         }
 
-        if (List.of("KI", "KIB").contains(unit)) {
+        if ("KI".equals(unit) || "KIB".equals(unit)) {
             return DataUnit.KIBIBYTES.toBytes(value);
         }
 
-        if (List.of("MI", "MIB").contains(unit)) {
+        if ("MI".equals(unit) || "MIB".equals(unit)) {
             return DataUnit.MEBIBYTES.toBytes(value);
         }
 
-        if (List.of("GI", "GIB").contains(unit)) {
+        if ("GI".equals(unit) || "GIB".equals(unit)) {
             return DataUnit.GIBIBYTES.toBytes(value);
         }
 
@@ -1370,7 +1369,7 @@
                 sign = -1;
             }
 
-            fmtSize = fmtSize.replace(first + "", "");
+            fmtSize = fmtSize.substring(1);
         }
 
         int index = 0;
diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl
index 09bc4cc..0d1dde1 100644
--- a/core/java/android/os/IHintSession.aidl
+++ b/core/java/android/os/IHintSession.aidl
@@ -22,4 +22,5 @@
     void updateTargetWorkDuration(long targetDurationNanos);
     void reportActualWorkDuration(in long[] actualDurationNanos, in long[] timeStampNanos);
     void close();
+    void sendHint(int hint);
 }
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 933769a..d31540a 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -58,6 +58,8 @@
     void setUserIcon(int userId, in Bitmap icon);
     ParcelFileDescriptor getUserIcon(int userId);
     UserInfo getPrimaryUser();
+    int getMainUserId();
+    int getPreviousFullUserToEnterForeground();
     List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated);
     List<UserInfo> getProfiles(int userId, boolean enabledOnly);
     int[] getProfileIds(int userId, boolean enabledOnly);
@@ -69,6 +71,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);
@@ -129,7 +132,7 @@
     boolean isUserRunning(int userId);
     boolean isUserForeground(int userId);
     boolean isUserVisible(int userId);
-    List<UserHandle> getVisibleUsers();
+    int[] getVisibleUsers();
     boolean isUserNameSet(int userId);
     boolean hasRestrictedProfiles(int userId);
     boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags);
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index d84037f..5c5af2a 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -70,3 +70,9 @@
 
 # Tracing
 per-file Trace.java = file:/TRACE_OWNERS
+
+# PermissionEnforcer
+per-file PermissionEnforcer.java = tweek@google.com, brufino@google.com
+
+# ART
+per-file ArtModuleServiceManager.java = file:platform/art:/OWNERS
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 2afa879..1673ade 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -561,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;
 
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/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index a75b5ef..86135bc 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
@@ -24,6 +25,10 @@
 import com.android.internal.util.Preconditions;
 
 import java.io.Closeable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.Reference;
+
 
 /** The PerformanceHintManager allows apps to send performance hint to system. */
 @SystemService(Context.PERFORMANCE_HINT_SERVICE)
@@ -104,6 +109,40 @@
             mNativeSessionPtr = nativeSessionPtr;
         }
 
+        /**
+        * This hint indicates a sudden increase in CPU workload intensity. It means
+        * that this hint session needs extra CPU resources immediately to meet the
+        * target duration for the current work cycle.
+        */
+        public static final int CPU_LOAD_UP = 0;
+        /**
+        * This hint indicates a decrease in CPU workload intensity. It means that
+        * this hint session can reduce CPU resources and still meet the target duration.
+        */
+        public static final int CPU_LOAD_DOWN = 1;
+        /*
+        * This hint indicates an upcoming CPU workload that is completely changed and
+        * unknown. It means that the hint session should reset CPU resources to a known
+        * baseline to prepare for an arbitrary load, and must wake up if inactive.
+        */
+        public static final int CPU_LOAD_RESET = 2;
+        /*
+        * This hint indicates that the most recent CPU workload is resuming after a
+        * period of inactivity. It means that the hint session should allocate similar
+        * CPU resources to what was used previously, and must wake up if inactive.
+        */
+        public static final int CPU_LOAD_RESUME = 3;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = {"CPU_LOAD_"}, value = {
+            CPU_LOAD_UP,
+            CPU_LOAD_DOWN,
+            CPU_LOAD_RESET,
+            CPU_LOAD_RESUME
+        })
+        public @interface Hint {}
+
         /** @hide */
         @Override
         protected void finalize() throws Throwable {
@@ -152,6 +191,21 @@
                 mNativeSessionPtr = 0;
             }
         }
+
+        /**
+         * Sends performance hints to inform the hint session of changes in the workload.
+         *
+         * @param hint The hint to send to the session.
+         */
+        public void sendHint(@Hint int hint) {
+            Preconditions.checkArgumentNonNegative(hint, "the hint ID should be at least"
+                    + " zero.");
+            try {
+                nativeSendHint(mNativeSessionPtr, hint);
+            } finally {
+                Reference.reachabilityFence(this);
+            }
+        }
     }
 
     private static native long nativeAcquireManager();
@@ -163,4 +217,5 @@
     private static native void nativeReportActualWorkDuration(long nativeSessionPtr,
             long actualDurationNanos);
     private static native void nativeCloseSession(long nativeSessionPtr);
+    private static native void nativeSendHint(long nativeSessionPtr, int hint);
 }
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index e483328..ac1583a 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -895,9 +895,21 @@
         return isIsolated(myUid());
     }
 
-    /** {@hide} */
-    @UnsupportedAppUsage
+    /**
+     * @deprecated Use {@link #isIsolatedUid(int)} instead.
+     * {@hide}
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+            publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.")
     public static final boolean isIsolated(int uid) {
+        return isIsolatedUid(uid);
+    }
+
+    /**
+     * Returns whether the process with the given {@code uid} is an isolated sandbox.
+     */
+    public static final boolean isIsolatedUid(int uid) {
         uid = UserHandle.getAppId(uid);
         return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
                 || (uid >= FIRST_APP_ZYGOTE_ISOLATED_UID && uid <= LAST_APP_ZYGOTE_ISOLATED_UID);
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index e321a66..394927e 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -252,18 +252,22 @@
     }
 
     /**
-     * Returns the list of declared instances for an interface.
+     * Returns an array of all declared instances for a particular interface.
      *
-     * @return true if the service is declared somewhere (eg. VINTF manifest) and
-     * waitForService should always be able to return the service.
+     * For instance, if 'android.foo.IFoo/foo' is declared (e.g. in VINTF
+     * manifest), and 'android.foo.IFoo' is passed here, then ["foo"] would be
+     * returned.
+     *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @NonNull
     public static String[] getDeclaredInstances(@NonNull String iface) {
         try {
             return getIServiceManager().getDeclaredInstances(iface);
         } catch (RemoteException e) {
             Log.e(TAG, "error in getDeclaredInstances", e);
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -276,8 +280,6 @@
      * @return {@code null} only if there are permission problems or fatal errors.
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    @Nullable
     public static IBinder waitForService(@NonNull String name) {
         return Binder.allowBlocking(waitForServiceNative(name));
     }
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 6091bf9..bf72b1d 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.hardware.vibrator.IVibrator;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Range;
@@ -313,8 +314,14 @@
         private static final float EPSILON = 1e-5f;
 
         public MultiVibratorInfo(VibratorInfo[] vibrators) {
+            // Need to use an extra constructor to share the computation in super initialization.
+            this(vibrators, frequencyProfileIntersection(vibrators));
+        }
+
+        private MultiVibratorInfo(VibratorInfo[] vibrators,
+                VibratorInfo.FrequencyProfile mergedProfile) {
             super(/* id= */ -1,
-                    capabilitiesIntersection(vibrators),
+                    capabilitiesIntersection(vibrators, mergedProfile.isEmpty()),
                     supportedEffectsIntersection(vibrators),
                     supportedBrakingIntersection(vibrators),
                     supportedPrimitivesAndDurationsIntersection(vibrators),
@@ -323,14 +330,19 @@
                     integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
                     integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
                     floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
-                    frequencyProfileIntersection(vibrators));
+                    mergedProfile);
         }
 
-        private static int capabilitiesIntersection(VibratorInfo[] infos) {
+        private static int capabilitiesIntersection(VibratorInfo[] infos,
+                boolean frequencyProfileIsEmpty) {
             int intersection = ~0;
             for (VibratorInfo info : infos) {
                 intersection &= info.getCapabilities();
             }
+            if (frequencyProfileIsEmpty) {
+                // Revoke frequency control if the merged frequency profile ended up empty.
+                intersection &= ~IVibrator.CAP_FREQUENCY_CONTROL;
+            }
             return intersection;
         }
 
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 8bfa0e9..cdde18a 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -100,6 +100,7 @@
     /** @hide */
     public static final long TRACE_TAG_VIBRATOR = 1L << 23;
     /** @hide */
+    @SystemApi
     public static final long TRACE_TAG_AIDL = 1L << 24;
     /** @hide */
     public static final long TRACE_TAG_NNAPI = 1L << 25;
@@ -240,9 +241,16 @@
     /**
      * Writes a trace message to indicate that a given section of code has
      * begun. Must be followed by a call to {@link #asyncTraceEnd} using the same
-     * tag. Unlike {@link #traceBegin(long, String)} and {@link #traceEnd(long)},
-     * asynchronous events do not need to be nested. The name and cookie used to
-     * begin an event must be used to end it.
+     * tag, name and cookie.
+     *
+     * If two events with the same methodName overlap in time then they *must* have
+     * different cookie values. If they do not, the trace can become corrupted
+     * in unpredictable ways.
+     *
+     * Unlike {@link #traceBegin(long, String)} and {@link #traceEnd(long)},
+     * asynchronous events cannot be not nested. Consider using
+     * {@link #asyncTraceForTrackBegin(long, String, String, int)}
+     * if nested asynchronous events are needed.
      *
      * @param traceTag The trace tag.
      * @param methodName The method name to appear in the trace.
@@ -263,6 +271,9 @@
      * Must be called exactly once for each call to {@link #asyncTraceBegin(long, String, int)}
      * using the same tag, name and cookie.
      *
+     * See the documentation for {@link #asyncTraceBegin(long, String, int)}.
+     * for inteded usage of this method.
+     *
      * @param traceTag The trace tag.
      * @param methodName The method name to appear in the trace.
      * @param cookie Unique identifier for distinguishing simultaneous events
@@ -282,14 +293,73 @@
      * Writes a trace message to indicate that a given section of code has
      * begun. Must be followed by a call to {@link #asyncTraceForTrackEnd} using the same
      * track name and cookie.
-     * This function operates exactly like {@link #asyncTraceBegin(long, String, int)},
-     * except with the inclusion of a track name argument for where this method should appear.
-     * The cookie must be unique on the trackName level, not the methodName level
+     *
+     * Events with the same trackName and cookie nest inside each other in the
+     * same way as calls to {@link #traceBegin(long, String)} and
+     * {@link #traceEnd(long)}.
+     *
+     * If two events with the same trackName overlap in time but do not nest
+     * correctly, then they *must* have different cookie values. If they do not,
+     * the trace can become corrupted in unpredictable ways.
+     *
+     * Good Example:
+     *
+     * public void parent() {
+     *   asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "Track", "parent", mId);
+     *   child()
+     *   asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "Track", mId);
+     * }
+     *
+     * public void child() {
+     *   asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "Track", "child", mId);
+     *   // Some code here.
+     *   asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "Track", mId);
+     * }
+     *
+     * This would be visualized as so:
+     *   [   Parent   ]
+     *     [ Child ]
+     *
+     * Bad Example:
+     *
+     * public static void processData(String dataToProcess) {
+     *   asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "processDataInParallel", "processData", 0);
+     *   // Some code here.
+     *   asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "processDataInParallel", 0);
+     * }
+     *
+     * public static void processDataInParallel({@code List<String>} data) {
+     *   ExecutorService executor = Executors.newCachedThreadPool();
+     *   for (String s : data) {
+     *     pool.execute(() -> processData(s));
+     *   }
+     * }
+     *
+     * This is invalid because it's possible for processData to be run many times
+     * in parallel (i.e. processData events overlap) but the same cookie is
+     * provided each time.
+     *
+     * To fix this, specify a different id in each invocation of processData:
+     *
+     * public static void processData(String dataToProcess, int id) {
+     *   asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "processDataInParallel", "processData", id);
+     *   // Some code here.
+     *   asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "processDataInParallel", id);
+     * }
+     *
+     * public static void processDataInParallel({@code List<String>} data) {
+     *   ExecutorService executor = Executors.newCachedThreadPool();
+     *   for (int i = 0; i < data.size(); ++i) {
+     *     pool.execute(() -> processData(data.get(i), i));
+     *   }
+     * }
      *
      * @param traceTag The trace tag.
      * @param trackName The track where the event should appear in the trace.
      * @param methodName The method name to appear in the trace.
-     * @param cookie Unique identifier for distinguishing simultaneous events
+     * @param cookie Unique identifier used for nesting events on a single
+     *               track. Events which overlap without nesting on the same
+     *               track must have different values for cookie.
      *
      * @hide
      */
@@ -306,9 +376,14 @@
      * {@link #asyncTraceForTrackBegin(long, String, String, int)}
      * using the same tag, track name, and cookie.
      *
+     * See the documentation for {@link #asyncTraceForTrackBegin(long, String, String, int)}.
+     * for inteded usage of this method.
+     *
      * @param traceTag The trace tag.
      * @param trackName The track where the event should appear in the trace.
-     * @param cookie Unique identifier for distinguishing simultaneous events
+     * @param cookie Unique identifier used for nesting events on a single
+     *               track. Events which overlap without nesting on the same
+     *               track must have different values for cookie.
      *
      * @hide
      */
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 51dc643..07c4b44 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -59,7 +59,7 @@
 import android.graphics.drawable.Drawable;
 import android.location.LocationManager;
 import android.provider.Settings;
-import android.telephony.TelephonyManager;
+import android.telecom.TelecomManager;
 import android.util.AndroidException;
 import android.util.ArraySet;
 import android.util.Log;
@@ -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,46 @@
     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";
+
+    /**
+     * This user restriction specifies if Ultra-wideband is disallowed on the device. If
+     * Ultra-wideband is disallowed it cannot be turned on via Settings.
+     *
+     * <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 both cases, the restriction applies globally on the device and will turn off the
+     * ultra-wideband radio if it's currently on and prevent the radio from being turned on in
+     * the future.
+     *
+     * <p>
+     * Ultra-wideband (UWB) is a radio technology that can use a very low energy level
+     * for short-range, high-bandwidth communications over a large portion of the radio spectrum.
+     *
+     * <p>Default is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio";
+
+    /**
      * 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 +1610,8 @@
             DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
             DISALLOW_WIFI_DIRECT,
             DISALLOW_ADD_WIFI_CONFIG,
+            DISALLOW_CELLULAR_2G,
+            DISALLOW_ULTRA_WIDEBAND_RADIO,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserRestrictionKey {}
@@ -1593,6 +1637,16 @@
     /** @hide */
     public static final String SYSTEM_USER_MODE_EMULATION_HEADLESS = "headless";
 
+    /**
+     * System Property used to override whether users can be created even if their type is disabled
+     * or their limit is reached. Set value to 1 to enable.
+     *
+     * <p>Only used on non-user builds.
+     *
+     * @hide
+     */
+    public static final String DEV_CREATE_OVERRIDE_PROPERTY = "debug.user.creation_override";
+
     private static final String ACTION_CREATE_USER = "android.os.action.CREATE_USER";
 
     /**
@@ -2051,28 +2105,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;
     }
 
     /**
@@ -2093,13 +2139,8 @@
                 mContext.getContentResolver(),
                 Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
         boolean isSystemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
-        boolean inCall = false;
-        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
-        if (telephonyManager != null) {
-            inCall = telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
-        }
         boolean isUserSwitchDisallowed = hasUserRestrictionForUser(DISALLOW_USER_SWITCH, mUserId);
-        return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall
+        return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall()
                 && !isUserSwitchDisallowed;
     }
 
@@ -2138,11 +2179,8 @@
             android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public @UserSwitchabilityResult int getUserSwitchability(UserHandle userHandle) {
-        final TelephonyManager tm =
-                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-
         int flags = SWITCHABILITY_STATUS_OK;
-        if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
+        if (inCall()) {
             flags |= SWITCHABILITY_STATUS_USER_IN_CALL;
         }
         if (hasUserRestrictionForUser(DISALLOW_USER_SWITCH, userHandle)) {
@@ -2295,12 +2333,18 @@
     }
 
     /**
-     * Used to check if the context user is the primary user. The primary user
-     * is the first human user on a device. This is not supported in headless system user mode.
+     * Used to check if the context user is the primary user. The primary user is the first human
+     * user on a device. This is not supported in headless system user mode.
      *
      * @return whether the context user is the primary user.
+     *
+     * @deprecated This method always returns true for the system user, who may not be a full user
+     * if {@link #isHeadlessSystemUserMode} is true. Use {@link #isSystemUser}, {@link #isAdminUser}
+     * or {@link #isMainUser} instead.
+     *
      * @hide
      */
+    @Deprecated
     @SystemApi
     @RequiresPermission(anyOf = {
             Manifest.permission.MANAGE_USERS,
@@ -2325,6 +2369,54 @@
     }
 
     /**
+     * Returns {@code true} if the context user is the designated "main user" of the device. This
+     * user may have access to certain features which are limited to at most one user. There will
+     * never be more than one main user on a device.
+     *
+     * <p>Currently, on most form factors the first human user on the device will be the main user;
+     * in the future, the concept may be transferable, so a different user (or even no user at all)
+     * may be designated the main user instead. On other form factors there might not be a main
+     * user.
+     *
+     * <p>Note that this will not be the system user on devices for which
+     * {@link #isHeadlessSystemUserMode()} returns true.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
+    @UserHandleAware
+    public boolean isMainUser() {
+        final UserInfo user = getUserInfo(mUserId);
+        return user != null && user.isMain();
+    }
+
+    /**
+     * Returns the designated "main user" of the device, or {@code null} if there is no main user.
+     *
+     * @see #isMainUser()
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
+    public @Nullable UserHandle getMainUser() {
+        try {
+            final int mainUserId = mService.getMainUserId();
+            if (mainUserId == UserHandle.USER_NULL) {
+                return null;
+            }
+            return UserHandle.of(mainUserId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Used to check if the context user is an admin user. An admin user is allowed to
      * modify or configure certain settings that aren't available to non-admin users,
      * create and delete additional users, etc. There can be more than one admin users.
@@ -2876,8 +2968,15 @@
      * </ol>
      *
      * @return whether the user is visible at the moment, as defined above.
+     *
+     * @hide
      */
+    @SystemApi
     @UserHandleAware
+    @RequiresPermission(anyOf = {
+            "android.permission.INTERACT_ACROSS_USERS",
+            "android.permission.MANAGE_USERS"
+    })
     public boolean isUserVisible() {
         try {
             return mService.isUserVisible(mUserId);
@@ -2890,15 +2989,27 @@
      * Gets the visible users (as defined by {@link #isUserVisible()}.
      *
      * @return visible users at the moment.
+     *
+     * @hide
      */
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.INTERACT_ACROSS_USERS})
-    public @NonNull List<UserHandle> getVisibleUsers() {
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            "android.permission.INTERACT_ACROSS_USERS",
+            "android.permission.MANAGE_USERS"
+    })
+    public @NonNull Set<UserHandle> getVisibleUsers() {
+        ArraySet<UserHandle> result = new ArraySet<>();
         try {
-            return mService.getVisibleUsers();
+            int[] visibleUserIds = mService.getVisibleUsers();
+            if (visibleUserIds != null) {
+                for (int userId : visibleUserIds) {
+                    result.add(UserHandle.of(userId));
+                }
+            }
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
+        return result;
     }
 
     /**
@@ -4193,6 +4304,43 @@
     }
 
     /**
+     * Returns the user who was last in the foreground, not including the current user and not
+     * including profiles.
+     *
+     * <p>Returns {@code null} if there is no previous user, for example if there
+     * is only one full user (i.e. only one user which is not a profile) on the device.
+     *
+     * <p>This method may be used for example to find the user to switch back to if the
+     * current user is removed, or if creating a new user is aborted.
+     *
+     * <p>Note that reboots do not interrupt this calculation; the previous user need not have
+     * used the device since it rebooted.
+     *
+     * <p>Note also that on devices that support multiple users on multiple displays, it is possible
+     * that the returned user will be visible on a secondary display, as the foreground user is the
+     * one associated with the main display.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.CREATE_USERS,
+            android.Manifest.permission.QUERY_USERS
+    })
+    public @Nullable UserHandle getPreviousForegroundUser() {
+        try {
+            final int previousUser = mService.getPreviousFullUserToEnterForeground();
+            if (previousUser == UserHandle.USER_NULL) {
+                return null;
+            }
+            return UserHandle.of(previousUser);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Checks whether it's possible to add more users.
      *
      * @return true if more users can be added, false if limit has been reached.
@@ -4339,6 +4487,7 @@
      * @return true if the creation of users of the given user type is enabled on this device.
      * @hide
      */
+    @TestApi
     @RequiresPermission(anyOf = {
             android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.CREATE_USERS
@@ -5465,6 +5614,11 @@
         }
     }
 
+    private boolean inCall() {
+        final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
+        return telecomManager != null && telecomManager.isInCall();
+    }
+
     /* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */
     private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props";
 
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 71bc4b3..3448a9e 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -227,6 +227,31 @@
     }
 
     /**
+     * Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on"
+     * vibration components) that is equivalent to this VibrationEffect.
+     *
+     * <p>All non-repeating effects created with {@link #createWaveform(int[], int)} are convertible
+     * into an equivalent vibration pattern with this method. It is not guaranteed that an effect
+     * created with other means becomes converted into an equivalent legacy vibration pattern, even
+     * if it has an equivalent vibration pattern. If this method is unable to create an equivalent
+     * vibration pattern for such effects, it will return {@code null}.
+     *
+     * <p>Note that a valid equivalent long[] pattern cannot be created for an effect that has any
+     * form of repeating behavior, regardless of how the effect was created. For repeating effects,
+     * the method will always return {@code null}.
+     *
+     * @return a long array representing a vibration pattern equivalent to the VibrationEffect, if
+     *               the method successfully derived a vibration pattern equivalent to the effect
+     *               (this will always be the case if the effect was created via
+     *               {@link #createWaveform(int[], int)} and is non-repeating). Otherwise, returns
+     *               {@code null}.
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    public abstract long[] computeCreateWaveformOffOnTimingsOrNull();
+
+    /**
      * Create a waveform vibration.
      *
      * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs,
@@ -641,6 +666,51 @@
             return mRepeatIndex;
         }
 
+         /** @hide */
+        @Override
+        @Nullable
+        public long[] computeCreateWaveformOffOnTimingsOrNull() {
+            if (getRepeatIndex() >= 0) {
+                // Repeating effects cannot be fully represented as a long[] legacy pattern.
+                return null;
+            }
+
+            List<VibrationEffectSegment> segments = getSegments();
+
+            // The maximum possible size of the final pattern is 1 plus the number of segments in
+            // the original effect. This is because we will add an empty "off" segment at the
+            // start of the pattern if the first segment of the original effect is an "on" segment.
+            // (because the legacy patterns start with an "off" pattern). Other than this one case,
+            // we will add the durations of back-to-back segments of similar amplitudes (amplitudes
+            // that are all "on" or "off") and create a pattern entry for the total duration, which
+            // will not take more number pattern entries than the number of segments processed.
+            long[] patternBuffer = new long[segments.size() + 1];
+            int patternIndex = 0;
+
+            for (int i = 0; i < segments.size(); i++) {
+                StepSegment stepSegment =
+                        castToValidStepSegmentForOffOnTimingsOrNull(segments.get(i));
+                if (stepSegment == null) {
+                    // This means that there is 1 or more segments of this effect that is/are not a
+                    // possible component of a legacy vibration pattern. Thus, the VibrationEffect
+                    // does not have any equivalent legacy vibration pattern.
+                    return null;
+                }
+
+                boolean isSegmentOff = stepSegment.getAmplitude() == 0;
+                // Even pattern indices are "off", and odd pattern indices are "on"
+                boolean isCurrentPatternIndexOff = (patternIndex % 2) == 0;
+                if (isSegmentOff != isCurrentPatternIndexOff) {
+                    // Move the pattern index one step ahead, so that the current segment's
+                    // "off"/"on" property matches that of the index's
+                    ++patternIndex;
+                }
+                patternBuffer[patternIndex] += stepSegment.getDuration();
+            }
+
+            return Arrays.copyOf(patternBuffer, patternIndex + 1);
+        }
+
         /** @hide */
         @Override
         public void validate() {
@@ -806,6 +876,31 @@
                         return new Composed[size];
                     }
                 };
+
+        /**
+         * Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it,
+         * only if it can possibly be a segment for an effect created via
+         * {@link #createWaveform(int[], int)}. Otherwise, returns {@code null}.
+         */
+        @Nullable
+        private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull(
+                VibrationEffectSegment segment) {
+            if (!(segment instanceof StepSegment)) {
+                return null;
+            }
+
+            StepSegment stepSegment = (StepSegment) segment;
+            if (stepSegment.getFrequencyHz() != 0) {
+                return null;
+            }
+
+            float amplitude = stepSegment.getAmplitude();
+            if (amplitude != 0 && amplitude != DEFAULT_AMPLITUDE) {
+                return null;
+            }
+
+            return stepSegment;
+        }
     }
 
     /**
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 38ac984..7899420 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2540,9 +2540,11 @@
      * called on first creation of a new file on external storage, and whenever the
      * media type of the file is updated later.
      *
-     * This API requires MANAGE_EXTERNAL_STORAGE permission and typical implementations
+     * This API doesn't require any special permissions, though typical implementations
      * will require being called from an SELinux domain that allows setting file attributes
      * related to quota (eg the GID or project ID).
+     * If the calling user has MANAGE_EXTERNAL_STORAGE permissions, quota for shared profile's
+     * volumes is also updated.
      *
      * The default platform user of this API is the MediaProvider process, which is
      * responsible for managing all of external storage.
@@ -2559,15 +2561,17 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
     public void updateExternalStorageFileQuotaType(@NonNull File path,
             @QuotaType int quotaType) throws IOException {
         long projectId;
         final String filePath = path.getCanonicalPath();
-        // MANAGE_EXTERNAL_STORAGE permission is required as FLAG_INCLUDE_SHARED_PROFILE is being
-        // set while querying getVolumeList.
-        final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(),
-                FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_SHARED_PROFILE);
+        int volFlags = FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE;
+        // If caller has MANAGE_EXTERNAL_STORAGE permission, results from User Profile(s) are also
+        // returned by enabling FLAG_INCLUDE_SHARED_PROFILE.
+        if (mContext.checkSelfPermission(MANAGE_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
+            volFlags |= FLAG_INCLUDE_SHARED_PROFILE;
+        }
+        final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(), volFlags);
         final StorageVolume volume = getStorageVolume(availableVolumes, path);
         if (volume == null) {
             Log.w(TAG, "Failed to update quota type for " + filePath);
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 8534c66..16ae3bc 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -77,8 +77,7 @@
     List<SplitPermissionInfoParcelable> getSplitPermissions();
 
     void startOneTimePermissionSession(String packageName, int userId, long timeout,
-            long revokeAfterKilledDelay, int importanceToResetTimer,
-            int importanceToKeepSessionAlive);
+            long revokeAfterKilledDelay);
 
     @EnforcePermission("MANAGE_ONE_TIME_PERMISSION_SESSIONS")
     void stopOneTimePermissionSession(String packageName, int userId);
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 6b540d7..6769954 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1371,8 +1371,7 @@
             @ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) {
         try {
             mPermissionManager.startOneTimePermissionSession(packageName, mContext.getUserId(),
-                    timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer,
-                    importanceToKeepSessionAlive);
+                    timeoutMillis, revokeAfterKilledDelayMillis);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
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/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 7095d1b..7df9290 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -23,12 +23,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
-import android.annotation.TestApi;
-import android.app.ActivityThread;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.provider.Settings.Config.SyncDisabledMode;
@@ -58,13 +54,6 @@
 @SystemApi
 public final class DeviceConfig {
     /**
-     * The content:// style URL for the config table.
-     *
-     * @hide
-     */
-    public static final Uri CONTENT_URI = Uri.parse("content://" + Settings.AUTHORITY + "/config");
-
-    /**
      * Namespace for activity manager related features. These features will be applied
      * immediately upon change.
      *
@@ -78,6 +67,7 @@
      * different namespace in order to avoid phonetype from resetting it.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS = "activity_manager_ca";
 
     /**
@@ -96,7 +86,6 @@
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    @TestApi
     public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
 
     /**
@@ -131,6 +120,13 @@
     public static final String NAMESPACE_APP_STANDBY = "app_standby";
 
     /**
+     * Namespace for all App Cloning related features.
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final String NAMESPACE_APP_CLONING = "app_cloning";
+
+    /**
      * Namespace for AttentionManagerService related features.
      *
      * @hide
@@ -227,7 +223,6 @@
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    @TestApi
     public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
 
     /**
@@ -258,6 +253,14 @@
     public static final String NAMESPACE_GAME_DRIVER = "game_driver";
 
     /**
+     * Namespace for all HDMI Control features.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_HDMI_CONTROL = "hdmi_control";
+
+    /**
      * Namespace for all input-related features that are used at the native level.
      * These features are applied at reboot.
      *
@@ -279,6 +282,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS =
             "intelligence_content_suggestions";
 
@@ -286,7 +290,7 @@
      * Namespace for JobScheduler configurations.
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
 
     /**
@@ -326,6 +330,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_MGLRU_NATIVE = "mglru_native";
 
     /**
@@ -381,6 +386,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_REMOTE_KEY_PROVISIONING_NATIVE =
             "remote_key_provisioning_native";
 
@@ -405,6 +411,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_ROTATION_RESOLVER = "rotation_resolver";
 
     /**
@@ -456,6 +463,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_SETTINGS_STATS = "settings_stats";
 
     /**
@@ -555,6 +563,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_TARE = "tare";
 
     /**
@@ -579,6 +588,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_CONTACTS_PROVIDER = "contacts_provider";
 
     /**
@@ -586,6 +596,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_SETTINGS_UI = "settings_ui";
 
     /**
@@ -596,7 +607,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final String NAMESPACE_ANDROID = "android";
 
     /**
@@ -604,6 +615,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_WINDOW_MANAGER = "window_manager";
 
     /**
@@ -620,7 +632,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
 
     /**
@@ -628,6 +640,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_VOICE_INTERACTION = "voice_interaction";
 
     /**
@@ -635,6 +648,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_DEVICE_POLICY_MANAGER =
             "device_policy_manager";
 
@@ -685,6 +699,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_WIDGET = "widget";
 
     /**
@@ -692,6 +707,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_CONNECTIVITY_THERMAL_POWER_MANAGER =
             "connectivity_thermal_power_manager";
 
@@ -700,6 +716,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_CONFIGURATION = "configuration";
 
     /**
@@ -707,6 +724,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_LATENCY_TRACKER = "latency_tracker";
 
     /**
@@ -714,6 +732,8 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @SuppressLint("IntentName")
     public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor";
 
     /**
@@ -721,6 +741,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_GAME_OVERLAY = "game_overlay";
 
     /**
@@ -728,6 +749,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE =
             "virtualization_framework_native";
 
@@ -736,7 +758,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
 
     /**
@@ -744,7 +766,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
 
     /**
@@ -765,10 +787,20 @@
             "ambient_context_manager_service";
 
     /**
+     * Namespace for WearableSensingManagerService related features.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_WEARABLE_SENSING =
+            "wearable_sensing";
+
+    /**
      * Namespace for Vendor System Native related features.
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
 
     /**
@@ -776,6 +808,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot";
 
     /**
@@ -783,6 +816,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_MEMORY_SAFETY_NATIVE = "memory_safety_native";
 
     /**
@@ -790,6 +824,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_WEAR = "wear";
 
     /**
@@ -805,7 +840,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager";
 
     /**
@@ -821,6 +856,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String NAMESPACE_ARC_APP_COMPAT = "arc_app_compat";
 
     private static final Object sLock = new Object();
@@ -875,9 +911,8 @@
     @NonNull
     @RequiresPermission(READ_DEVICE_CONFIG)
     public static Properties getProperties(@NonNull String namespace, @NonNull String ... names) {
-        ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
         return new Properties(namespace,
-                Settings.Config.getStrings(contentResolver, namespace, Arrays.asList(names)));
+                Settings.Config.getStrings(namespace, Arrays.asList(names)));
     }
 
     /**
@@ -1016,8 +1051,7 @@
     @RequiresPermission(WRITE_DEVICE_CONFIG)
     public static boolean setProperty(@NonNull String namespace, @NonNull String name,
             @Nullable String value, boolean makeDefault) {
-        ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
-        return Settings.Config.putString(contentResolver, namespace, name, value, makeDefault);
+        return Settings.Config.putString(namespace, name, value, makeDefault);
     }
 
     /**
@@ -1038,8 +1072,7 @@
     @SystemApi
     @RequiresPermission(WRITE_DEVICE_CONFIG)
     public static boolean setProperties(@NonNull Properties properties) throws BadConfigException {
-        ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
-        return Settings.Config.setStrings(contentResolver, properties.getNamespace(),
+        return Settings.Config.setStrings(properties.getNamespace(),
                 properties.mMap);
     }
 
@@ -1055,8 +1088,7 @@
     @SystemApi
     @RequiresPermission(WRITE_DEVICE_CONFIG)
     public static boolean deleteProperty(@NonNull String namespace, @NonNull String name) {
-        ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
-        return Settings.Config.deleteString(contentResolver, namespace, name);
+        return Settings.Config.deleteString(namespace, name);
     }
 
     /**
@@ -1087,8 +1119,7 @@
     @SystemApi
     @RequiresPermission(WRITE_DEVICE_CONFIG)
     public static void resetToDefaults(@ResetMode int resetMode, @Nullable String namespace) {
-        ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
-        Settings.Config.resetToDefaults(contentResolver, resetMode, namespace);
+        Settings.Config.resetToDefaults(resetMode, namespace);
     }
 
     /**
@@ -1103,10 +1134,10 @@
      * @see #getSyncDisabledMode()
      * @hide
      */
+    @SystemApi
     @RequiresPermission(WRITE_DEVICE_CONFIG)
     public static void setSyncDisabledMode(@SyncDisabledMode int syncDisabledMode) {
-        ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
-        Settings.Config.setSyncDisabledMode(contentResolver, syncDisabledMode);
+        Settings.Config.setSyncDisabledMode(syncDisabledMode);
     }
 
     /**
@@ -1115,10 +1146,10 @@
      * @see #setSyncDisabledMode(int)
      * @hide
      */
+    @SystemApi
     @RequiresPermission(WRITE_DEVICE_CONFIG)
     public static @SyncDisabledMode int getSyncDisabledMode() {
-        ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
-        return Settings.Config.getSyncDisabledMode(contentResolver);
+        return Settings.Config.getSyncDisabledMode();
     }
 
     /**
@@ -1136,13 +1167,10 @@
      * @see #removeOnPropertiesChangedListener(OnPropertiesChangedListener)
      */
     @SystemApi
-    @RequiresPermission(READ_DEVICE_CONFIG)
     public static void addOnPropertiesChangedListener(
             @NonNull String namespace,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OnPropertiesChangedListener onPropertiesChangedListener) {
-        enforceReadPermission(ActivityThread.currentApplication().getApplicationContext(),
-                namespace);
         synchronized (sLock) {
             Pair<String, Executor> oldNamespace = sListeners.get(onPropertiesChangedListener);
             if (oldNamespace == null) {
@@ -1181,11 +1209,6 @@
         }
     }
 
-    private static Uri createNamespaceUri(@NonNull String namespace) {
-        Preconditions.checkNotNull(namespace);
-        return CONTENT_URI.buildUpon().appendPath(namespace).build();
-    }
-
     /**
      * Increment the count used to represent the number of listeners subscribed to the given
      * namespace. If this is the first (i.e. incrementing from 0 to 1) for the given namespace, a
@@ -1209,8 +1232,8 @@
                     }
                 }
             };
-            ActivityThread.currentApplication().getContentResolver()
-                    .registerContentObserver(createNamespaceUri(namespace), true, contentObserver);
+            Settings.Config
+                    .registerContentObserver(namespace, true, contentObserver);
             sNamespaces.put(namespace, new Pair<>(contentObserver, 1));
         }
     }
@@ -1233,8 +1256,7 @@
             sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second - 1));
         } else {
             // Decrementing a namespace to zero means we no longer need its ContentObserver.
-            ActivityThread.currentApplication().getContentResolver()
-                    .unregisterContentObserver(namespaceCount.first);
+            Settings.Config.unregisterContentObserver(namespaceCount.first);
             sNamespaces.remove(namespace);
         }
     }
@@ -1271,23 +1293,10 @@
     }
 
     /**
-     * Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces.
-     * @hide
-     */
-    public static void enforceReadPermission(Context context, String namespace) {
-        if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
-                != PackageManager.PERMISSION_GRANTED) {
-            if (!PUBLIC_NAMESPACES.contains(namespace)) {
-                throw new SecurityException("Permission denial: reading from settings requires:"
-                        + READ_DEVICE_CONFIG);
-            }
-        }
-    }
-
-    /**
      * Returns list of namespaces that can be read without READ_DEVICE_CONFIG_PERMISSION;
      * @hide
      */
+    @SystemApi
     public static @NonNull List<String> getPublicNamespaces() {
         return PUBLIC_NAMESPACES;
     }
@@ -1344,6 +1353,7 @@
          * @param keyValueMap A map between property names and property values.
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         public Properties(@NonNull String namespace, @Nullable Map<String, String> keyValueMap) {
             Preconditions.checkNotNull(namespace);
             mNamespace = namespace;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 897b7c3..eeac00b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -47,9 +47,11 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PermissionName;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.database.Cursor;
 import android.database.SQLException;
 import android.location.ILocationManager;
@@ -192,6 +194,21 @@
             "android.settings.LOCATION_SCANNING_SETTINGS";
 
     /**
+     * Activity Action: Show settings to manage creation/deletion of cloned apps.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_CLONED_APPS_SETTINGS =
+            "android.settings.MANAGE_CLONED_APPS_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow configuration of users.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -570,6 +587,22 @@
             "android.settings.REQUEST_MANAGE_MEDIA";
 
     /**
+     * Activity Action: Show settings to allow configuration of
+     * {@link Manifest.permission#RUN_LONG_JOBS} permission
+     *
+     * Input: Optionally, the Intent's data URI can specify the application package name to
+     * directly invoke the management GUI specific to the package name. For example
+     * "package:com.my.app".
+     * <p>
+     * Output: When a package data uri is passed as input, the activity result is set to
+     * {@link android.app.Activity#RESULT_OK} if the permission was granted to the app. Otherwise,
+     * the result is set to {@link android.app.Activity#RESULT_CANCELED}.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_APP_LONG_RUNNING_JOBS =
+            "android.settings.MANAGE_APP_LONG_RUNNING_JOBS";
+
+    /**
      * Activity Action: Show settings to allow configuration of cross-profile access for apps
      *
      * Input: Optionally, the Intent's data URI can specify the application package name to
@@ -675,6 +708,22 @@
             "android.settings.WIFI_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of Advanced memory protection.
+     * <p>
+     * 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.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_ADVANCED_MEMORY_PROTECTION_SETTINGS =
+            "android.settings.ADVANCED_MEMORY_PROTECTION_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow configuration of a static IP
      * address for Wi-Fi.
      * <p>
@@ -2770,6 +2819,7 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @TestApi
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int RESET_MODE_PACKAGE_DEFAULTS = 1;
 
     /**
@@ -2779,6 +2829,7 @@
      * the setting will be deleted. This mode is only available to the system.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int RESET_MODE_UNTRUSTED_DEFAULTS = 2;
 
     /**
@@ -2789,6 +2840,7 @@
      * This mode is only available to the system.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int RESET_MODE_UNTRUSTED_CHANGES = 3;
 
     /**
@@ -2800,6 +2852,7 @@
      * to the system.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int RESET_MODE_TRUSTED_DEFAULTS = 4;
 
     /** @hide */
@@ -3313,7 +3366,7 @@
         public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
                 List<String> names) {
             String namespace = prefix.substring(0, prefix.length() - 1);
-            DeviceConfig.enforceReadPermission(ActivityThread.currentApplication(), namespace);
+            Config.enforceReadPermission(namespace);
             ArrayMap<String, String> keyValues = new ArrayMap<>();
             int currentGeneration = -1;
 
@@ -7175,6 +7228,15 @@
         public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard";
 
         /**
+         * Whether stylus button presses are disabled. This is a boolean that
+         * determines if stylus buttons are ignored.
+         *
+         * @hide
+         */
+        @SuppressLint("NoSettingsProvider")
+        public static final String STYLUS_BUTTONS_DISABLED = "stylus_buttons_disabled";
+
+        /**
          * Host name and port for global http proxy. Uses ':' seperator for
          * between host and port.
          *
@@ -7849,6 +7911,13 @@
                 "high_text_contrast_enabled";
 
         /**
+         * The color contrast, float in [-1, 1], 1 being the highest contrast.
+         *
+         * @hide
+         */
+        public static final String CONTRAST_LEVEL = "contrast_level";
+
+        /**
          * Setting that specifies whether the display magnification is enabled via a system-wide
          * triple tap gesture. Display magnifications allows the user to zoom in the display content
          * and is targeted to low vision users. The current magnification scale is controlled by
@@ -9301,6 +9370,14 @@
         public static final int DOCK_SETUP_PAUSED = 2;
 
         /**
+         * Indicates that the user has been prompted to start dock setup.
+         * One of the possible states for {@link #DOCK_SETUP_STATE}.
+         *
+         * @hide
+         */
+        public static final int DOCK_SETUP_PROMPTED = 3;
+
+        /**
          * Indicates that the user has completed dock setup.
          * One of the possible states for {@link #DOCK_SETUP_STATE}.
          *
@@ -9314,6 +9391,7 @@
                 DOCK_SETUP_NOT_STARTED,
                 DOCK_SETUP_STARTED,
                 DOCK_SETUP_PAUSED,
+                DOCK_SETUP_PROMPTED,
                 DOCK_SETUP_COMPLETED
         })
         public @interface DockSetupState {
@@ -9876,6 +9954,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
          */
@@ -10393,11 +10478,11 @@
         public static final String QS_AUTO_ADDED_TILES = "qs_auto_tiles";
 
         /**
-         * The duration of timeout, in milliseconds, to switch from a non-primary user to the
-         * primary user when the device is docked.
+         * The duration of timeout, in milliseconds, to switch from a non-Dock User to the
+         * Dock User when the device is docked.
          * @hide
          */
-        public static final String TIMEOUT_TO_USER_ZERO = "timeout_to_user_zero";
+        public static final String TIMEOUT_TO_DOCK_USER = "timeout_to_dock_user";
 
         /**
          * Backup manager behavioral parameters.
@@ -15943,6 +16028,15 @@
         public static final String STYLUS_HANDWRITING_ENABLED = "stylus_handwriting_enabled";
 
         /**
+         * Indicates whether a stylus has ever been used on the device.
+         *
+         * @hide
+         */
+        @Readable
+        @SuppressLint("NoSettingsProvider")
+        public static final String STYLUS_EVER_USED = "stylus_ever_used";
+
+        /**
          * Exemptions to the hidden API blacklist.
          *
          * @hide
@@ -17863,6 +17957,12 @@
             public static final String WET_MODE_ON = "wet_mode_on";
 
             /*
+             * Whether the RSB wake feature is enabled.
+             * @hide
+             */
+            public static final String RSB_WAKE_ENABLED = "rsb_wake_enabled";
+
+            /*
              * Whether the screen-unlock (keyguard) sound is enabled.
              * @hide
              */
@@ -17914,6 +18014,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class Config extends NameValueTable {
 
         /**
@@ -17948,12 +18049,19 @@
          */
         public static final int SYNC_DISABLED_MODE_UNTIL_REBOOT = 2;
 
+        /**
+         * The content:// style URL for the config table.
+         *
+         * @hide
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/config");
+
         private static final ContentProviderHolder sProviderHolder =
-                new ContentProviderHolder(DeviceConfig.CONTENT_URI);
+                new ContentProviderHolder(CONTENT_URI);
 
         // Populated lazily, guarded by class object:
         private static final NameValueCache sNameValueCache = new NameValueCache(
-                DeviceConfig.CONTENT_URI,
+                CONTENT_URI,
                 CALL_METHOD_GET_CONFIG,
                 CALL_METHOD_PUT_CONFIG,
                 CALL_METHOD_DELETE_CONFIG,
@@ -17962,22 +18070,46 @@
                 sProviderHolder,
                 Config.class);
 
+        // Should never be invoked
+        private Config() {
+        }
+
         /**
          * Look up a name in the database.
-         * @param resolver to access the database with
          * @param name to look up in the table
          * @return the corresponding value, or null if not present
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @Nullable
         @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
-        static String getString(ContentResolver resolver, String name) {
+        public static String getString(@NonNull String name) {
+            ContentResolver resolver = getContentResolver();
             return sNameValueCache.getStringForUser(resolver, name, resolver.getUserId());
         }
 
         /**
          * Look up a list of names in the database, within the specified namespace.
          *
+         * @param namespace to which the names belong
+         * @param names to look up in the table
+         * @return a non null, but possibly empty, map from name to value for any of the names that
+         *         were found during lookup.
+         *
+         * @hide
+         */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @NonNull
+        @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+        public static Map<String, String> getStrings(@NonNull String namespace,
+                @NonNull List<String> names) {
+            return getStrings(getContentResolver(), namespace, names);
+        }
+
+        /**
+         * Look up a list of names in the database, within the specified namespace.
+         *
          * @param resolver to access the database with
          * @param namespace to which the names belong
          * @param names to look up in the table
@@ -18015,7 +18147,6 @@
          * <strong>not</strong> be set as the default.
          * </p>
          *
-         * @param resolver to access the database with.
          * @param namespace to store the name/value pair in.
          * @param name to store.
          * @param value to associate with the name.
@@ -18026,9 +18157,11 @@
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
-        static boolean putString(@NonNull ContentResolver resolver, @NonNull String namespace,
+        public static boolean putString(@NonNull String namespace,
                 @NonNull String name, @Nullable String value, boolean makeDefault) {
+            ContentResolver resolver = getContentResolver();
             return sNameValueCache.putStringForUser(resolver, createCompositeName(namespace, name),
                     value, null, makeDefault, resolver.getUserId(),
                     DEFAULT_OVERRIDEABLE_BY_RESTORE);
@@ -18038,6 +18171,24 @@
          * Clear all name/value pairs for the provided namespace and save new name/value pairs in
          * their place.
          *
+         * @param namespace to which the names should be set.
+         * @param keyValues map of key names (without the prefix) to values.
+         * @return true if the name/value pairs were set, false if setting was blocked
+         *
+         * @hide
+         */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
+        public static boolean setStrings(@NonNull String namespace,
+                @NonNull Map<String, String> keyValues)
+                throws DeviceConfig.BadConfigException {
+            return setStrings(getContentResolver(), namespace, keyValues);
+        }
+
+        /**
+         * Clear all name/value pairs for the provided namespace and save new name/value pairs in
+         * their place.
+         *
          * @param resolver to access the database with.
          * @param namespace to which the names should be set.
          * @param keyValues map of key names (without the prefix) to values.
@@ -18068,7 +18219,6 @@
         /**
          * Delete a name/value pair from the database for the specified namespace.
          *
-         * @param resolver to access the database with.
          * @param namespace to delete the name/value pair from.
          * @param name to delete.
          * @return true if the value was deleted, false on database errors. If the name/value pair
@@ -18078,9 +18228,11 @@
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
-        static boolean deleteString(@NonNull ContentResolver resolver, @NonNull String namespace,
+        public static boolean deleteString(@NonNull String namespace,
                 @NonNull String name) {
+            ContentResolver resolver = getContentResolver();
             return sNameValueCache.deleteStringForUser(resolver,
                     createCompositeName(namespace, name), resolver.getUserId());
         }
@@ -18091,7 +18243,6 @@
          * The method accepts an optional prefix parameter. If provided, only pairs with a name that
          * starts with the exact prefix will be reset. Otherwise all will be reset.
          *
-         * @param resolver Handle to the content resolver.
          * @param resetMode The reset mode to use.
          * @param namespace Optionally, to limit which which namespace is reset.
          *
@@ -18099,10 +18250,12 @@
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
-        static void resetToDefaults(@NonNull ContentResolver resolver, @ResetMode int resetMode,
+        public static void resetToDefaults(@ResetMode int resetMode,
                 @Nullable String namespace) {
             try {
+                ContentResolver resolver = getContentResolver();
                 Bundle arg = new Bundle();
                 arg.putInt(CALL_METHOD_USER_KEY, resolver.getUserId());
                 arg.putInt(CALL_METHOD_RESET_MODE_KEY, resetMode);
@@ -18113,7 +18266,7 @@
                 cp.call(resolver.getAttributionSource(),
                         sProviderHolder.mUri.getAuthority(), CALL_METHOD_RESET_CONFIG, null, arg);
             } catch (RemoteException e) {
-                Log.w(TAG, "Can't reset to defaults for " + DeviceConfig.CONTENT_URI, e);
+                Log.w(TAG, "Can't reset to defaults for " + CONTENT_URI, e);
             }
         }
 
@@ -18123,18 +18276,19 @@
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         @SuppressLint("AndroidFrameworkRequiresPermission")
         @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
-        static void setSyncDisabledMode(
-                @NonNull ContentResolver resolver, @SyncDisabledMode int disableSyncMode) {
+        public static void setSyncDisabledMode(@SyncDisabledMode int disableSyncMode) {
             try {
+                ContentResolver resolver = getContentResolver();
                 Bundle args = new Bundle();
                 args.putInt(CALL_METHOD_SYNC_DISABLED_MODE_KEY, disableSyncMode);
                 IContentProvider cp = sProviderHolder.getProvider(resolver);
                 cp.call(resolver.getAttributionSource(), sProviderHolder.mUri.getAuthority(),
                         CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG, null, args);
             } catch (RemoteException e) {
-                Log.w(TAG, "Can't set sync disabled mode " + DeviceConfig.CONTENT_URI, e);
+                Log.w(TAG, "Can't set sync disabled mode " + CONTENT_URI, e);
             }
         }
 
@@ -18144,10 +18298,12 @@
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         @SuppressLint("AndroidFrameworkRequiresPermission")
         @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
-        static int getSyncDisabledMode(@NonNull ContentResolver resolver) {
+        public static int getSyncDisabledMode() {
             try {
+                ContentResolver resolver = getContentResolver();
                 Bundle args = Bundle.EMPTY;
                 IContentProvider cp = sProviderHolder.getProvider(resolver);
                 Bundle bundle = cp.call(resolver.getAttributionSource(),
@@ -18156,7 +18312,7 @@
                         null, args);
                 return bundle.getInt(KEY_CONFIG_GET_SYNC_DISABLED_MODE_RETURN);
             } catch (RemoteException e) {
-                Log.w(TAG, "Can't query sync disabled mode " + DeviceConfig.CONTENT_URI, e);
+                Log.w(TAG, "Can't query sync disabled mode " + CONTENT_URI, e);
             }
             return -1;
         }
@@ -18164,7 +18320,6 @@
         /**
          * Register callback for monitoring Config table.
          *
-         * @param resolver Handle to the content resolver.
          * @param callback callback to register
          *
          * @hide
@@ -18175,6 +18330,70 @@
             registerMonitorCallbackAsUser(resolver, resolver.getUserId(), callback);
         }
 
+
+        /**
+         * Register a content observer.
+         *
+         * @hide
+         */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        public static void registerContentObserver(@Nullable String namespace,
+                boolean notifyForDescendants, @NonNull ContentObserver observer) {
+            ActivityThread.currentApplication().getContentResolver()
+                    .registerContentObserver(createNamespaceUri(namespace),
+                         notifyForDescendants, observer);
+        }
+
+        /**
+         * Unregister a content observer.
+         * this may only be used with content observers registered through
+         * {@link Config#registerContentObserver}
+         *
+         * @hide
+         */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        public static void unregisterContentObserver(@NonNull ContentObserver observer) {
+            ActivityThread.currentApplication().getContentResolver()
+              .unregisterContentObserver(observer);
+        }
+
+        /**
+         * Determine whether the calling process of an IPC <em>or you</em> have been
+         * granted a particular permission.  This is the same as
+         * {@link #checkCallingPermission}, except it grants your own permissions
+         * if you are not currently processing an IPC.  Use with care!
+         *
+         * @param permission The name of the permission being checked.
+         *
+         * @return {@link PackageManager#PERMISSION_GRANTED} if the calling
+         * pid/uid is allowed that permission, or
+         * {@link PackageManager#PERMISSION_DENIED} if it is not.
+         *
+         * @see PackageManager#checkPermission(String, String)
+         * @see #checkPermission
+         * @see #checkCallingPermission
+         * @hide
+         */
+        public static int checkCallingOrSelfPermission(@NonNull @PermissionName String permission) {
+            return ActivityThread.currentApplication()
+               .getApplicationContext().checkCallingOrSelfPermission(permission);
+        }
+
+        /**
+         * Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces.
+         * @hide
+         */
+        public static void enforceReadPermission(String namespace) {
+            if (ActivityThread.currentApplication().getApplicationContext()
+                    .checkCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG)
+                    != PackageManager.PERMISSION_GRANTED) {
+                if (!DeviceConfig.getPublicNamespaces().contains(namespace)) {
+                    throw new SecurityException("Permission denial: reading from settings requires:"
+                        + Manifest.permission.READ_DEVICE_CONFIG);
+                }
+            }
+        }
+
         private static void registerMonitorCallbackAsUser(
                 @NonNull ContentResolver resolver, @UserIdInt int userHandle,
                 @NonNull RemoteCallback callback) {
@@ -18207,6 +18426,15 @@
             Preconditions.checkNotNull(namespace);
             return namespace + "/";
         }
+
+        private static Uri createNamespaceUri(@NonNull String namespace) {
+            Preconditions.checkNotNull(namespace);
+            return CONTENT_URI.buildUpon().appendPath(namespace).build();
+        }
+
+        private static ContentResolver getContentResolver() {
+            return ActivityThread.currentApplication().getContentResolver();
+        }
     }
 
     /**
@@ -18636,6 +18864,9 @@
     /**
      * Activity Action: For system or preinstalled apps to show their {@link Activity} embedded
      * in Settings app on large screen devices.
+     *
+     * Developers should resolve the Intent action before using it.
+     *
      * <p>
      *     Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI} must be included to
      * specify the intent for the activity which will be embedded in Settings app.
diff --git a/core/java/android/security/keymaster/ExportResult.java b/core/java/android/security/keymaster/ExportResult.java
index 2c382ef..c78fb1a 100644
--- a/core/java/android/security/keymaster/ExportResult.java
+++ b/core/java/android/security/keymaster/ExportResult.java
@@ -61,4 +61,4 @@
         out.writeInt(resultCode);
         out.writeByteArray(exportData);
     }
-};
+}
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index e720f1a..4d6422c 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -126,6 +126,8 @@
             Tag.BOOT_PATCHLEVEL; // KM_UINT | 719;
     public static final int KM_TAG_DEVICE_UNIQUE_ATTESTATION =
             Tag.DEVICE_UNIQUE_ATTESTATION; // KM_BOOL | 720;
+    public static final int KM_TAG_ATTESTATION_ID_SECOND_IMEI =
+            Tag.ATTESTATION_ID_SECOND_IMEI; // KM_BYTES | 723;
 
     public static final int KM_TAG_NONCE = Tag.NONCE; // KM_BYTES | 1001;
     public static final int KM_TAG_MAC_LENGTH = Tag.MAC_LENGTH; // KM_UINT | 1003;
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/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index d9a310f..745f36d 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -332,7 +332,6 @@
     }
 
     @Override
-    /** @hide */
     protected final void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.print("Service component: "); pw.println(
                 ComponentName.flattenToShortString(mServiceComponentName));
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index 47b16a3..9396a88 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -55,6 +55,32 @@
             "android.service.controls.ControlsProviderService";
 
     /**
+     * Manifest metadata to show a custom embedded activity as part of device controls.
+     *
+     * The value of this metadata must be the {@link ComponentName} as a string of an activity in
+     * the same package that will be launched as part of a TaskView.
+     *
+     * The activity must be exported, enabled and protected by
+     * {@link Manifest.permission.BIND_CONTROLS}.
+     *
+     * @hide
+     */
+    public static final String META_DATA_PANEL_ACTIVITY =
+            "android.service.controls.META_DATA_PANEL_ACTIVITY";
+
+    /**
+     * Boolean extra containing the value of
+     * {@link android.provider.Settings.Secure#LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS}.
+     *
+     * This is passed with the intent when the panel specified by {@link #META_DATA_PANEL_ACTIVITY}
+     * is launched.
+     *
+     * @hide
+     */
+    public static final String EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS =
+            "android.service.controls.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
+
+    /**
      * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/core/java/android/service/credentials/Action.java b/core/java/android/service/credentials/Action.java
index 553a324..7757081 100644
--- a/core/java/android/service/credentials/Action.java
+++ b/core/java/android/service/credentials/Action.java
@@ -27,8 +27,6 @@
 /**
  * An action defined by the provider that intents into the provider's app for specific
  * user actions.
- *
- * @hide
  */
 public final class Action implements Parcelable {
     /** Slice object containing display content to be displayed with this action on the UI. */
@@ -39,6 +37,13 @@
     /**
      * Constructs an action to be displayed on the UI.
      *
+     * <p> Actions must be used for any provider related operations, such as opening the provider
+     * app, intenting straight into certain app activities like 'manage credentials', top
+     * level authentication before displaying any content etc.
+     *
+     * <p> See details on usage of {@code Action} for various actionable entries in
+     * {@link BeginCreateCredentialResponse} and {@link GetCredentialsResponse}.
+     *
      * @param slice the display content to be displayed on the UI, along with this action
      * @param pendingIntent the intent to be invoked when the user selects this action
      */
diff --git a/core/java/android/service/credentials/BeginCreateCredentialRequest.aidl b/core/java/android/service/credentials/BeginCreateCredentialRequest.aidl
new file mode 100644
index 0000000..30cab8d
--- /dev/null
+++ b/core/java/android/service/credentials/BeginCreateCredentialRequest.aidl
@@ -0,0 +1,3 @@
+package android.service.credentials;
+
+parcelable BeginCreateCredentialRequest;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/BeginCreateCredentialRequest.java b/core/java/android/service/credentials/BeginCreateCredentialRequest.java
new file mode 100644
index 0000000..1918d8c
--- /dev/null
+++ b/core/java/android/service/credentials/BeginCreateCredentialRequest.java
@@ -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 android.service.credentials;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Request for beginning a create credential request.
+ *
+ * See {@link BeginCreateCredentialResponse} for the counterpart response
+ */
+public final class BeginCreateCredentialRequest implements Parcelable {
+    private final @NonNull String mCallingPackage;
+    private final @NonNull String mType;
+    private final @NonNull Bundle mData;
+
+    /**
+     * Constructs a new instance.
+     *
+     * @throws IllegalArgumentException If {@code callingPackage}, or {@code type} string is
+     * null or empty.
+     * @throws NullPointerException If {@code data} is null.
+     */
+    public BeginCreateCredentialRequest(@NonNull String callingPackage,
+            @NonNull String type, @NonNull Bundle data) {
+        mCallingPackage = Preconditions.checkStringNotEmpty(callingPackage,
+                "callingPackage must not be null or empty");
+        mType = Preconditions.checkStringNotEmpty(type,
+                "type must not be null or empty");
+        mData = Objects.requireNonNull(data, "data must not be null");
+    }
+
+    private BeginCreateCredentialRequest(@NonNull Parcel in) {
+        mCallingPackage = in.readString8();
+        mType = in.readString8();
+        mData = in.readBundle(Bundle.class.getClassLoader());
+    }
+
+    public static final @NonNull Creator<BeginCreateCredentialRequest> CREATOR =
+            new Creator<BeginCreateCredentialRequest>() {
+                @Override
+                public BeginCreateCredentialRequest createFromParcel(@NonNull Parcel in) {
+                    return new BeginCreateCredentialRequest(in);
+                }
+
+                @Override
+                public BeginCreateCredentialRequest[] newArray(int size) {
+                    return new BeginCreateCredentialRequest[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mCallingPackage);
+        dest.writeString8(mType);
+        dest.writeBundle(mData);
+    }
+
+    /** Returns the calling package of the calling app. */
+    @NonNull
+    public String getCallingPackage() {
+        return mCallingPackage;
+    }
+
+    /** Returns the type of the credential to be created. */
+    @NonNull
+    public String getType() {
+        return mType;
+    }
+
+    /** Returns the data to be used while resolving the credential to create. */
+    @NonNull
+    public Bundle getData() {
+        return mData;
+    }
+}
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.aidl b/core/java/android/service/credentials/BeginCreateCredentialResponse.aidl
new file mode 100644
index 0000000..d2a1408
--- /dev/null
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.aidl
@@ -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.
+ */
+
+package android.service.credentials;
+
+parcelable BeginCreateCredentialResponse;
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
new file mode 100644
index 0000000..022678e
--- /dev/null
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -0,0 +1,150 @@
+/*
+ * 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.service.credentials;
+
+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.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Response to a {@link BeginCreateCredentialRequest}.
+ */
+public final class BeginCreateCredentialResponse implements Parcelable {
+    private final @NonNull List<CreateEntry> mCreateEntries;
+    private final @Nullable CreateEntry mRemoteCreateEntry;
+
+    private BeginCreateCredentialResponse(@NonNull Parcel in) {
+        List<CreateEntry> createEntries = new ArrayList<>();
+        in.readTypedList(createEntries, CreateEntry.CREATOR);
+        mCreateEntries = createEntries;
+        mRemoteCreateEntry = in.readTypedObject(CreateEntry.CREATOR);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeTypedList(mCreateEntries);
+        dest.writeTypedObject(mRemoteCreateEntry, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<BeginCreateCredentialResponse> CREATOR =
+            new Creator<BeginCreateCredentialResponse>() {
+                @Override
+                public BeginCreateCredentialResponse createFromParcel(@NonNull Parcel in) {
+                    return new BeginCreateCredentialResponse(in);
+                }
+
+                @Override
+                public BeginCreateCredentialResponse[] newArray(int size) {
+                    return new BeginCreateCredentialResponse[size];
+                }
+            };
+
+    /* package-private */ BeginCreateCredentialResponse(
+            @NonNull List<CreateEntry> createEntries,
+            @Nullable CreateEntry remoteCreateEntry) {
+        this.mCreateEntries = createEntries;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mCreateEntries);
+        this.mRemoteCreateEntry = remoteCreateEntry;
+    }
+
+    /** Returns the list of create entries to be displayed on the UI. */
+    public @NonNull List<CreateEntry> getCreateEntries() {
+        return mCreateEntries;
+    }
+
+    /** Returns the remote create entry to be displayed on the UI. */
+    public @Nullable CreateEntry getRemoteCreateEntry() {
+        return mRemoteCreateEntry;
+    }
+
+    /**
+     * A builder for {@link BeginCreateCredentialResponse}
+     */
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    public static final class Builder {
+        private @NonNull List<CreateEntry> mCreateEntries = new ArrayList<>();
+        private @Nullable CreateEntry mRemoteCreateEntry;
+
+        /**
+         * Sets the list of create entries to be shown on the UI.
+         *
+         * @throws IllegalArgumentException If {@code createEntries} is empty.
+         * @throws NullPointerException If {@code createEntries} is null, or any of its elements
+         * are null.
+         */
+        public @NonNull Builder setCreateEntries(@NonNull List<CreateEntry> createEntries) {
+            Preconditions.checkCollectionNotEmpty(createEntries, "createEntries");
+            mCreateEntries = Preconditions.checkCollectionElementsNotNull(
+                    createEntries, "createEntries");
+            return this;
+        }
+
+        /**
+         * Adds an entry to the list of create entries to be shown on the UI.
+         *
+         * @throws NullPointerException If {@code createEntry} is null.
+         */
+        public @NonNull Builder addCreateEntry(@NonNull CreateEntry createEntry) {
+            mCreateEntries.add(Objects.requireNonNull(createEntry));
+            return this;
+        }
+
+        /**
+         * Sets a remote create entry to be shown on the UI. Provider must set this entry if they
+         * wish to create the credential on a different device.
+         *
+         * <p> When constructing the {@link CreateEntry} object, the {@code pendingIntent} must be
+         * set such that it leads to an activity that can provide UI to fulfill the request on
+         * a remote device. When user selects this {@code remoteCreateEntry}, the system will
+         * invoke the {@code pendingIntent} set on the {@link CreateEntry}.
+         *
+         * <p> Once the remote credential flow is complete, the {@link android.app.Activity}
+         * result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the
+         * {@link CredentialProviderService#EXTRA_CREATE_CREDENTIAL_RESULT} key should be populated
+         * with a {@link android.credentials.CreateCredentialResponse} object.
+         */
+        public @NonNull Builder setRemoteCreateEntry(@Nullable CreateEntry remoteCreateEntry) {
+            mRemoteCreateEntry = remoteCreateEntry;
+            return this;
+        }
+
+        /**
+         * Builds a new instance of {@link BeginCreateCredentialResponse}.
+         *
+         * @throws NullPointerException If {@code createEntries} is null.
+         * @throws IllegalArgumentException If {@code createEntries} is empty.
+         */
+        public @NonNull BeginCreateCredentialResponse build() {
+            Preconditions.checkCollectionNotEmpty(mCreateEntries, "createEntries must "
+                    + "not be null, or empty");
+            return new BeginCreateCredentialResponse(mCreateEntries, mRemoteCreateEntry);
+        }
+    }
+}
diff --git a/core/java/android/service/credentials/CreateCredentialRequest.aidl b/core/java/android/service/credentials/CreateCredentialRequest.aidl
deleted file mode 100644
index eb7fba9..0000000
--- a/core/java/android/service/credentials/CreateCredentialRequest.aidl
+++ /dev/null
@@ -1,3 +0,0 @@
-package android.service.credentials;
-
-parcelable CreateCredentialRequest;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/CreateCredentialRequest.java b/core/java/android/service/credentials/CreateCredentialRequest.java
index e6da349..aee85ab 100644
--- a/core/java/android/service/credentials/CreateCredentialRequest.java
+++ b/core/java/android/service/credentials/CreateCredentialRequest.java
@@ -27,8 +27,6 @@
 
 /**
  * Request for creating a credential.
- *
- * @hide
  */
 public final class CreateCredentialRequest implements Parcelable {
     private final @NonNull String mCallingPackage;
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.aidl b/core/java/android/service/credentials/CreateCredentialResponse.aidl
deleted file mode 100644
index 73c9147..0000000
--- a/core/java/android/service/credentials/CreateCredentialResponse.aidl
+++ /dev/null
@@ -1,19 +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.service.credentials;
-
-parcelable CreateCredentialResponse;
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
deleted file mode 100644
index 559b1ca..0000000
--- a/core/java/android/service/credentials/CreateCredentialResponse.java
+++ /dev/null
@@ -1,141 +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.service.credentials;
-
-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.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Response to a {@link CreateCredentialRequest}.
- *
- * @hide
- */
-public final class CreateCredentialResponse implements Parcelable {
-    private final @Nullable CharSequence mHeader;
-    private final @NonNull List<SaveEntry> mSaveEntries;
-
-    private CreateCredentialResponse(@NonNull Parcel in) {
-        mHeader = in.readCharSequence();
-        List<SaveEntry> saveEntries = new ArrayList<>();
-        in.readTypedList(saveEntries, SaveEntry.CREATOR);
-        mSaveEntries = saveEntries;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeCharSequence(mHeader);
-        dest.writeTypedList(mSaveEntries);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final @NonNull Creator<CreateCredentialResponse> CREATOR =
-            new Creator<CreateCredentialResponse>() {
-                @Override
-                public CreateCredentialResponse createFromParcel(@NonNull Parcel in) {
-                    return new CreateCredentialResponse(in);
-                }
-
-                @Override
-                public CreateCredentialResponse[] newArray(int size) {
-                    return new CreateCredentialResponse[size];
-                }
-            };
-
-    /* package-private */ CreateCredentialResponse(
-            @Nullable CharSequence header,
-            @NonNull List<SaveEntry> saveEntries) {
-        this.mHeader = header;
-        this.mSaveEntries = saveEntries;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mSaveEntries);
-    }
-
-    /** Returns the header to be displayed on the UI. */
-    public @Nullable CharSequence getHeader() {
-        return mHeader;
-    }
-
-    /** Returns the list of save entries to be displayed on the UI. */
-    public @NonNull List<SaveEntry> getSaveEntries() {
-        return mSaveEntries;
-    }
-
-    /**
-     * A builder for {@link CreateCredentialResponse}
-     */
-    @SuppressWarnings("WeakerAccess")
-    public static final class Builder {
-
-        private @Nullable CharSequence mHeader;
-        private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
-
-        /** Sets the header to be displayed on the UI. */
-        public @NonNull Builder setHeader(@Nullable CharSequence header) {
-            mHeader = header;
-            return this;
-        }
-
-        /**
-         * Sets the list of save entries to be shown on the UI.
-         *
-         * @throws IllegalArgumentException If {@code saveEntries} is empty.
-         * @throws NullPointerException If {@code saveEntries} is null, or any of its elements
-         * are null.
-         */
-        public @NonNull Builder setSaveEntries(@NonNull List<SaveEntry> saveEntries) {
-            Preconditions.checkCollectionNotEmpty(saveEntries, "saveEntries");
-            mSaveEntries = Preconditions.checkCollectionElementsNotNull(
-                    saveEntries, "saveEntries");
-            return this;
-        }
-
-        /**
-         * Adds an entry to the list of save entries to be shown on the UI.
-         *
-         * @throws NullPointerException If {@code saveEntry} is null.
-         */
-        public @NonNull Builder addSaveEntry(@NonNull SaveEntry saveEntry) {
-            mSaveEntries.add(Objects.requireNonNull(saveEntry));
-            return this;
-        }
-
-        /**
-         * Builds the instance.
-         *
-         * @throws IllegalArgumentException If {@code saveEntries} is empty.
-         */
-        public @NonNull CreateCredentialResponse build() {
-            Preconditions.checkCollectionNotEmpty(mSaveEntries, "saveEntries must "
-                    + "not be empty");
-            return new CreateCredentialResponse(
-                    mHeader,
-                    mSaveEntries);
-        }
-    }
-}
diff --git a/core/java/android/service/credentials/CreateEntry.java b/core/java/android/service/credentials/CreateEntry.java
new file mode 100644
index 0000000..eb25e25
--- /dev/null
+++ b/core/java/android/service/credentials/CreateEntry.java
@@ -0,0 +1,87 @@
+/*
+ * 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.service.credentials;
+
+import android.annotation.NonNull;
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * 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.
+ */
+public final class CreateEntry implements Parcelable {
+    private final @NonNull Slice mSlice;
+    private final @NonNull PendingIntent mPendingIntent;
+
+    private CreateEntry(@NonNull Parcel in) {
+        mSlice = in.readTypedObject(Slice.CREATOR);
+        mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+    }
+
+    public static final @NonNull Creator<CreateEntry> CREATOR = new Creator<CreateEntry>() {
+        @Override
+        public CreateEntry createFromParcel(@NonNull Parcel in) {
+            return new CreateEntry(in);
+        }
+
+        @Override
+        public CreateEntry[] newArray(int size) {
+            return new CreateEntry[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeTypedObject(mSlice, flags);
+        dest.writeTypedObject(mPendingIntent, flags);
+    }
+
+    /**
+     * Constructs a CreateEntry 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 CreateEntry(
+            @NonNull Slice slice,
+            @NonNull PendingIntent pendingIntent) {
+        this.mSlice = slice;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSlice);
+        this.mPendingIntent = pendingIntent;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPendingIntent);
+    }
+
+    /** Returns the content to be displayed with this create entry on the UI. */
+    public @NonNull Slice getSlice() {
+        return mSlice;
+    }
+
+    /** Returns the pendingIntent to be invoked when this create entry on the UI is selectcd. */
+    public @NonNull PendingIntent getPendingIntent() {
+        return mPendingIntent;
+    }
+}
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index a3fa979..941db02b 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -31,8 +31,6 @@
 /**
  * A credential entry that is displayed on the account selector UI. Each entry corresponds to
  * something that the user can select.
- *
- * @hide
  */
 public final class CredentialEntry implements Parcelable {
     /** The type of the credential entry to be shown on the UI. */
@@ -145,57 +143,67 @@
         private boolean mAutoSelectAllowed = false;
 
         /**
-         * Builds the instance.
+         * Creates a builder for a {@link CredentialEntry} that should invoke a
+         * {@link PendingIntent} when selected by the user.
+         *
+         * <p>The {@code pendingIntent} can be used to launch activities that require some user
+         * engagement before getting the credential corresponding to this entry,
+         * e.g. authentication, confirmation etc.
+         * Once the activity fulfills the required user engagement, the
+         * {@link android.app.Activity} result should be set to
+         * {@link android.app.Activity#RESULT_OK}, and the
+         * {@link CredentialProviderService#EXTRA_CREDENTIAL_RESULT} must be set with a
+         * {@link Credential} object.
+         *
          * @param type the type of credential underlying this credential entry
          * @param slice the content to be displayed with this entry on the UI
+         * @param pendingIntent the pendingIntent to be invoked when this entry is selected by the
+         *                      user
          *
-         * @throws IllegalArgumentException If {@code type} is null or empty.
-         * @throws NullPointerException If {@code slice} is null.
+         * @throws NullPointerException If {@code slice}, or {@code pendingIntent} is null.
+         * @throws IllegalArgumentException If {@code type} is null or empty, or if
+         * {@code pendingIntent} was not created with {@link PendingIntent#getActivity}
+         * or {@link PendingIntent#getActivities}.
          */
-        public Builder(@NonNull String type, @NonNull Slice slice) {
+        public Builder(@NonNull String type, @NonNull Slice slice,
+                @NonNull PendingIntent pendingIntent) {
             mType = Preconditions.checkStringNotEmpty(type, "type must not be "
                     + "null, or empty");
             mSlice = Objects.requireNonNull(slice,
                     "slice must not be null");
+            mPendingIntent = Objects.requireNonNull(pendingIntent,
+                    "pendingIntent must not be null");
+            if (!mPendingIntent.isActivity()) {
+                throw new IllegalStateException("Pending intent must start an activity");
+            }
         }
 
         /**
-         * Sets the pendingIntent to be invoked if the user selects this entry.
+         * Creates a builder for a {@link CredentialEntry} that contains a {@link Credential},
+         * and does not require further action.
+         * @param type the type of credential underlying this credential entry
+         * @param slice the content to be displayed with this entry on the UI
+         * @param credential the credential to be returned to the client app, when this entry is
+         *                   selected by the user
          *
-         * The pending intent can be used to launch activities that require some user engagement
-         * before getting the credential corresponding to this entry, e.g. authentication,
-         * confirmation etc.
-         * Once the activity fulfills the required user engagement, a {@link Credential} object
-         * must be returned as an extra on activity finish.
-         *
-         * @throws IllegalStateException If {@code credential} is already set. Must either set the
-         * {@code credential}, or the {@code pendingIntent}.
+         * @throws IllegalArgumentException If {@code type} is null or empty.
+         * @throws NullPointerException If {@code slice}, or {@code credential} is null.
          */
-        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");
-            mPendingIntent = pendingIntent;
-            return this;
-        }
-
-        /**
-         * Sets the credential to be used, if the user selects this entry.
-         *
-         * @throws IllegalStateException If {@code pendingIntent} is already set. Must either set
-         * 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");
-            mCredential = credential;
-            return this;
+        public Builder(@NonNull String type, @NonNull Slice slice, @NonNull Credential credential) {
+            mType = Preconditions.checkStringNotEmpty(type, "type must not be "
+                    + "null, or empty");
+            mSlice = Objects.requireNonNull(slice,
+                    "slice must not be null");
+            mCredential = Objects.requireNonNull(credential,
+                    "credential must not be null");
         }
 
         /**
          * Sets whether the entry is allowed to be auto selected by the framework.
          * The default value is set to false.
+         *
+         * <p> The entry is only auto selected if it is the only entry on the user selector,
+         * AND the developer has also enabled auto select, while building the request.
          */
         public @NonNull Builder setAutoSelectAllowed(@NonNull boolean autoSelectAllowed) {
             mAutoSelectAllowed = autoSelectAllowed;
@@ -211,10 +219,10 @@
          * is set, or if both are set.
          */
         public @NonNull CredentialEntry build() {
-            Preconditions.checkState(mPendingIntent == null && mCredential == null,
-                    "Either pendingIntent or credential must be set");
-            Preconditions.checkState(mPendingIntent != null && mCredential != null,
-                    "Cannot set both the pendingIntent and credential");
+            Preconditions.checkState(((mPendingIntent != null && mCredential == null)
+                            || (mPendingIntent == null && mCredential != null)),
+                    "Either pendingIntent or credential must be set, and both cannot"
+                            + "be set at the same time");
             return new CredentialEntry(mType, mSlice, mPendingIntent,
                     mCredential, mAutoSelectAllowed);
         }
diff --git a/core/java/android/service/credentials/CredentialProviderException.java b/core/java/android/service/credentials/CredentialProviderException.java
index 06f0052..02b7443 100644
--- a/core/java/android/service/credentials/CredentialProviderException.java
+++ b/core/java/android/service/credentials/CredentialProviderException.java
@@ -24,8 +24,6 @@
 
 /**
  * Contains custom exceptions to be used by credential providers on failure.
- *
- * @hide
  */
 public class CredentialProviderException extends Exception {
     public static final int ERROR_UNKNOWN = 0;
@@ -59,6 +57,11 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface CredentialProviderError { }
 
+    public CredentialProviderException(@CredentialProviderError int errorCode,
+            @NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+        mErrorCode = errorCode;
+    }
 
     public CredentialProviderException(@CredentialProviderError int errorCode,
             @NonNull String message) {
diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfo.java
index 2c7a983..f89ad8e 100644
--- a/core/java/android/service/credentials/CredentialProviderInfo.java
+++ b/core/java/android/service/credentials/CredentialProviderInfo.java
@@ -24,7 +24,6 @@
 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;
@@ -96,6 +95,8 @@
         mLabel = mServiceInfo.loadSafeLabel(
                 mContext.getPackageManager(), 0 /* do not ellipsize */,
                 TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
+        Log.i(TAG, "mLabel is : " + mLabel + ", for: " + mServiceInfo.getComponentName()
+                .flattenToString());
         populateProviderCapabilities(context, serviceInfo);
     }
 
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index b1b08f4..32646e6 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -37,10 +37,64 @@
 /**
  * Service to be extended by credential providers, in order to return user credentials
  * to the framework.
- *
- * @hide
  */
 public abstract class CredentialProviderService extends Service {
+    /**
+     * Intent extra: The {@link android.credentials.CreateCredentialRequest} attached with
+     * the {@code pendingIntent} that is invoked when the user selects a {@link CreateEntry}
+     * returned as part of the {@link BeginCreateCredentialResponse}
+     *
+     * <p>
+     * Type: {@link android.credentials.CreateCredentialRequest}
+     */
+    public static final String EXTRA_CREATE_CREDENTIAL_REQUEST =
+            "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST";
+
+    /**
+     * Intent extra: The result of a create flow operation, to be set on finish of the
+     * {@link android.app.Activity} invoked through the {@code pendingIntent} set on
+     * a {@link CreateEntry}.
+     *
+     * <p>
+     * Type: {@link android.credentials.CreateCredentialResponse}
+     */
+    public static final String EXTRA_CREATE_CREDENTIAL_RESULT =
+            "android.service.credentials.extra.CREATE_CREDENTIAL_RESULT";
+
+    /**
+     * Intent extra: The result of a get credential flow operation, to be set on finish of the
+     * {@link android.app.Activity} invoked through the {@code pendingIntent} set on
+     * a {@link CredentialEntry}.
+     *
+     * <p>
+     * Type: {@link android.credentials.Credential}
+     */
+    public static final String EXTRA_CREDENTIAL_RESULT =
+            "android.service.credentials.extra.CREDENTIAL_RESULT";
+
+    /**
+     * Intent extra: The result of an authentication flow, to be set on finish of the
+     * {@link android.app.Activity} invoked through the {@link android.app.PendingIntent} set on
+     * a {@link GetCredentialsResponse}. This result should contain the actual content, including
+     * credential entries and action entries, to be shown on the selector.
+     *
+     * <p>
+     * Type: {@link CredentialsResponseContent}
+     */
+    public static final String EXTRA_GET_CREDENTIALS_CONTENT_RESULT =
+            "android.service.credentials.extra.GET_CREDENTIALS_CONTENT_RESULT";
+
+    /**
+     * Intent extra: The error result of any {@link android.app.PendingIntent} flow, to be set
+     * on finish of the corresponding {@link android.app.Activity}. This result should contain an
+     * error code, representing the error encountered by the provider.
+     *
+     * <p>
+     * Type: {@link String}
+     */
+    public static final String EXTRA_ERROR =
+            "android.service.credentials.extra.ERROR";
+
     private static final String TAG = "CredProviderService";
 
     public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
@@ -64,7 +118,7 @@
     }
 
     @Override
-    public final @NonNull IBinder onBind(@NonNull Intent intent) {
+    @NonNull public final IBinder onBind(@NonNull Intent intent) {
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
             return mInterface.asBinder();
         }
@@ -108,20 +162,21 @@
         }
 
         @Override
-        public ICancellationSignal onCreateCredential(CreateCredentialRequest request,
-                ICreateCredentialCallback callback) {
+        public ICancellationSignal onBeginCreateCredential(BeginCreateCredentialRequest request,
+                IBeginCreateCredentialCallback callback) {
             Objects.requireNonNull(request);
             Objects.requireNonNull(callback);
 
             ICancellationSignal transport = CancellationSignal.createTransport();
 
             mHandler.sendMessage(obtainMessage(
-                    CredentialProviderService::onCreateCredential,
+                    CredentialProviderService::onBeginCreateCredential,
                     CredentialProviderService.this, request,
                     CancellationSignal.fromTransport(transport),
-                    new OutcomeReceiver<CreateCredentialResponse, CredentialProviderException>() {
+                    new OutcomeReceiver<
+                            BeginCreateCredentialResponse, CredentialProviderException>() {
                         @Override
-                        public void onResult(CreateCredentialResponse result) {
+                        public void onResult(BeginCreateCredentialResponse result) {
                             try {
                                 callback.onSuccess(result);
                             } catch (RemoteException e) {
@@ -161,8 +216,8 @@
      *                           the android system.
      * @param callback Object used to relay the response of the credential creation request.
      */
-    public abstract void onCreateCredential(@NonNull CreateCredentialRequest request,
+    public abstract void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request,
             @NonNull CancellationSignal cancellationSignal,
-            @NonNull OutcomeReceiver<CreateCredentialResponse,
+            @NonNull OutcomeReceiver<BeginCreateCredentialResponse,
                     CredentialProviderException> callback);
 }
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java
deleted file mode 100644
index 2cce169..0000000
--- a/core/java/android/service/credentials/CredentialsDisplayContent.java
+++ /dev/null
@@ -1,191 +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.service.credentials;
-
-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.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Content to be displayed on the account selector UI, including credential entries,
- * actions etc.
- *
- * @hide
- */
-public final class CredentialsDisplayContent implements Parcelable {
-    /** Header to be displayed on the UI. */
-    private final @Nullable CharSequence mHeader;
-
-    /** List of credential entries to be displayed on the UI. */
-    private final @NonNull List<CredentialEntry> mCredentialEntries;
-
-    /** List of provider actions to be displayed on the UI. */
-    private final @NonNull List<Action> mActions;
-
-    private CredentialsDisplayContent(@Nullable CharSequence header,
-            @NonNull List<CredentialEntry> credentialEntries,
-            @NonNull List<Action> actions) {
-        mHeader = header;
-        mCredentialEntries = credentialEntries;
-        mActions = actions;
-    }
-
-    private CredentialsDisplayContent(@NonNull Parcel in) {
-        mHeader = in.readCharSequence();
-        List<CredentialEntry> credentialEntries = new ArrayList<>();
-        in.readTypedList(credentialEntries, CredentialEntry.CREATOR);
-        mCredentialEntries = credentialEntries;
-        List<Action> actions = new ArrayList<>();
-        in.readTypedList(actions, Action.CREATOR);
-        mActions = actions;
-    }
-
-    public static final @NonNull Creator<CredentialsDisplayContent> CREATOR =
-            new Creator<CredentialsDisplayContent>() {
-                @Override
-                public CredentialsDisplayContent createFromParcel(@NonNull Parcel in) {
-                    return new CredentialsDisplayContent(in);
-                }
-
-                @Override
-                public CredentialsDisplayContent[] newArray(int size) {
-                    return new CredentialsDisplayContent[size];
-                }
-            };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeCharSequence(mHeader);
-        dest.writeTypedList(mCredentialEntries, flags);
-        dest.writeTypedList(mActions, flags);
-    }
-
-    /**
-     * Returns the header to be displayed on the UI.
-     */
-    public @Nullable CharSequence getHeader() {
-        return mHeader;
-    }
-
-    /**
-     * Returns the list of credential entries to be displayed on the UI.
-     */
-    public @NonNull List<CredentialEntry> getCredentialEntries() {
-        return mCredentialEntries;
-    }
-
-    /**
-     * Returns the list of actions to be displayed on the UI.
-     */
-    public @NonNull List<Action> getActions() {
-        return mActions;
-    }
-
-    /**
-     * Builds an instance of {@link CredentialsDisplayContent}.
-     */
-    public static final class Builder {
-        private CharSequence mHeader = null;
-        private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
-        private List<Action> mActions = new ArrayList<>();
-
-        /**
-         * Sets the header to be displayed on the UI.
-         */
-        public @NonNull Builder setHeader(@Nullable CharSequence header) {
-            mHeader = header;
-            return this;
-        }
-
-        /**
-         * Adds a {@link CredentialEntry} to the list of entries to be displayed on
-         * the UI.
-         *
-         * @throws NullPointerException If the {@code credentialEntry} is null.
-         */
-        public @NonNull Builder addCredentialEntry(@NonNull CredentialEntry credentialEntry) {
-            mCredentialEntries.add(Objects.requireNonNull(credentialEntry));
-            return this;
-        }
-
-        /**
-         * Adds an {@link Action} to the list of actions to be displayed on
-         * the UI.
-         *
-         * @throws NullPointerException If {@code action} is null.
-         */
-        public @NonNull Builder addAction(@NonNull Action action) {
-            mActions.add(Objects.requireNonNull(action, "action must not be null"));
-            return this;
-        }
-
-        /**
-         * Sets the list of actions to be displayed on the UI.
-         *
-         * @throws NullPointerException If {@code actions} is null, or any of its elements
-         * is null.
-         */
-        public @NonNull Builder setActions(@NonNull List<Action> actions) {
-            mActions = Preconditions.checkCollectionElementsNotNull(actions,
-                    "actions");
-            return this;
-        }
-
-        /**
-         * Sets the list of credential entries to be displayed on the
-         * account selector UI.
-         *
-         * @throws NullPointerException If {@code credentialEntries} is null, or any of its
-         * elements is null.
-         */
-        public @NonNull Builder setCredentialEntries(
-                @NonNull List<CredentialEntry> credentialEntries) {
-            mCredentialEntries = Preconditions.checkCollectionElementsNotNull(
-                    credentialEntries,
-                    "credentialEntries");
-            return this;
-        }
-
-        /**
-         * Builds a {@link GetCredentialsResponse} instance.
-         *
-         * @throws NullPointerException If {@code credentialEntries} is null.
-         * @throws IllegalStateException if both {@code credentialEntries} and
-         * {@code actions} are empty.
-         */
-        public @NonNull CredentialsDisplayContent build() {
-            if (mCredentialEntries != null && mCredentialEntries.isEmpty()
-                    && mActions != null && mActions.isEmpty()) {
-                throw new IllegalStateException("credentialEntries and actions must not both "
-                        + "be empty");
-            }
-            return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions);
-        }
-    }
-}
diff --git a/core/java/android/service/credentials/CredentialsResponseContent.java b/core/java/android/service/credentials/CredentialsResponseContent.java
new file mode 100644
index 0000000..32cab50
--- /dev/null
+++ b/core/java/android/service/credentials/CredentialsResponseContent.java
@@ -0,0 +1,206 @@
+/*
+ * 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.service.credentials;
+
+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.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The content to be displayed on the account selector UI, including credential entries,
+ * actions etc. Returned as part of {@link GetCredentialsResponse}
+ */
+public final class CredentialsResponseContent implements Parcelable {
+    /** List of credential entries to be displayed on the UI. */
+    private final @NonNull List<CredentialEntry> mCredentialEntries;
+
+    /** 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 CredentialEntry mRemoteCredentialEntry;
+
+    private CredentialsResponseContent(@NonNull List<CredentialEntry> credentialEntries,
+            @NonNull List<Action> actions,
+            @Nullable CredentialEntry remoteCredentialEntry) {
+        mCredentialEntries = credentialEntries;
+        mActions = actions;
+        mRemoteCredentialEntry = remoteCredentialEntry;
+    }
+
+    private CredentialsResponseContent(@NonNull Parcel in) {
+        List<CredentialEntry> credentialEntries = new ArrayList<>();
+        in.readTypedList(credentialEntries, CredentialEntry.CREATOR);
+        mCredentialEntries = credentialEntries;
+        List<Action> actions = new ArrayList<>();
+        in.readTypedList(actions, Action.CREATOR);
+        mActions = actions;
+        mRemoteCredentialEntry = in.readTypedObject(CredentialEntry.CREATOR);
+    }
+
+    public static final @NonNull Creator<CredentialsResponseContent> CREATOR =
+            new Creator<CredentialsResponseContent>() {
+                @Override
+                public CredentialsResponseContent createFromParcel(@NonNull Parcel in) {
+                    return new CredentialsResponseContent(in);
+                }
+
+                @Override
+                public CredentialsResponseContent[] newArray(int size) {
+                    return new CredentialsResponseContent[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeTypedList(mCredentialEntries, flags);
+        dest.writeTypedList(mActions, flags);
+        dest.writeTypedObject(mRemoteCredentialEntry, flags);
+    }
+
+    /**
+     * Returns the list of credential entries to be displayed on the UI.
+     */
+    public @NonNull List<CredentialEntry> getCredentialEntries() {
+        return mCredentialEntries;
+    }
+
+    /**
+     * Returns the list of actions to be displayed on the UI.
+     */
+    public @NonNull List<Action> getActions() {
+        return mActions;
+    }
+
+    /**
+     * Returns the remote credential entry to be displayed on the UI.
+     */
+    public @Nullable CredentialEntry getRemoteCredentialEntry() {
+        return mRemoteCredentialEntry;
+    }
+
+    /**
+     * Builds an instance of {@link CredentialsResponseContent}.
+     */
+    public static final class Builder {
+        private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
+        private List<Action> mActions = new ArrayList<>();
+        private CredentialEntry mRemoteCredentialEntry;
+
+        /**
+         * Sets a remote credential entry to be shown on the UI. Provider must set this if they
+         * wish to get the credential from a different device.
+         *
+         * <p> When constructing the {@link CredentialEntry} object, the {@code pendingIntent}
+         * must be set such that it leads to an activity that can provide UI to fulfill the request
+         * on a remote device. When user selects this {@code remoteCredentialEntry}, the system will
+         * invoke the {@code pendingIntent} set on the {@link CredentialEntry}.
+         *
+         * <p> Once the remote credential flow is complete, the {@link android.app.Activity}
+         * result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the
+         * {@link CredentialProviderService#EXTRA_CREDENTIAL_RESULT} key should be populated
+         * with a {@link android.credentials.Credential} object.
+         */
+        public @NonNull Builder setRemoteCredentialEntry(@Nullable CredentialEntry
+                remoteCredentialEntry) {
+            mRemoteCredentialEntry = remoteCredentialEntry;
+            return this;
+        }
+
+        /**
+         * Adds a {@link CredentialEntry} to the list of entries to be displayed on
+         * the UI.
+         *
+         * @throws NullPointerException If the {@code credentialEntry} is null.
+         */
+        public @NonNull Builder addCredentialEntry(@NonNull CredentialEntry credentialEntry) {
+            mCredentialEntries.add(Objects.requireNonNull(credentialEntry));
+            return this;
+        }
+
+        /**
+         * Adds an {@link Action} to the list of actions to be displayed on
+         * the UI.
+         *
+         * <p> An {@code action} must be used for independent user actions,
+         * such as opening the app, intenting directly into a certain app activity etc. The
+         * {@code pendingIntent} set with the {@code action} must invoke the corresponding
+         * activity.
+         *
+         * @throws NullPointerException If {@code action} is null.
+         */
+        public @NonNull Builder addAction(@NonNull Action action) {
+            mActions.add(Objects.requireNonNull(action, "action must not be null"));
+            return this;
+        }
+
+        /**
+         * Sets the list of actions to be displayed on the UI.
+         *
+         * @throws NullPointerException If {@code actions} is null, or any of its elements
+         * is null.
+         */
+        public @NonNull Builder setActions(@NonNull List<Action> actions) {
+            mActions = Preconditions.checkCollectionElementsNotNull(actions,
+                    "actions");
+            return this;
+        }
+
+        /**
+         * Sets the list of credential entries to be displayed on the
+         * account selector UI.
+         *
+         * @throws NullPointerException If {@code credentialEntries} is null, or any of its
+         * elements is null.
+         */
+        public @NonNull Builder setCredentialEntries(
+                @NonNull List<CredentialEntry> credentialEntries) {
+            mCredentialEntries = Preconditions.checkCollectionElementsNotNull(
+                    credentialEntries,
+                    "credentialEntries");
+            return this;
+        }
+
+        /**
+         * Builds a {@link GetCredentialsResponse} instance.
+         *
+         * @throws IllegalStateException if {@code credentialEntries}, {@code actions}
+         * and {@code remoteCredentialEntry} are all null or empty.
+         */
+        public @NonNull CredentialsResponseContent build() {
+            if (mCredentialEntries != null && mCredentialEntries.isEmpty()
+                    && mActions != null && mActions.isEmpty() && mRemoteCredentialEntry == null) {
+                throw new IllegalStateException("credentialEntries and actions must not both "
+                        + "be empty");
+            }
+            return new CredentialsResponseContent(mCredentialEntries, mActions,
+                    mRemoteCredentialEntry);
+        }
+    }
+}
diff --git a/core/java/android/service/credentials/GetCredentialsRequest.java b/core/java/android/service/credentials/GetCredentialsRequest.java
index e06be44..9052b54 100644
--- a/core/java/android/service/credentials/GetCredentialsRequest.java
+++ b/core/java/android/service/credentials/GetCredentialsRequest.java
@@ -29,8 +29,6 @@
 
 /**
  * Request for getting user's credentials from a given credential provider.
- *
- * @hide
  */
 public final class GetCredentialsRequest implements Parcelable {
     /** Calling package of the app requesting for credentials. */
@@ -119,9 +117,9 @@
          */
         public @NonNull Builder setGetCredentialOptions(
                 @NonNull List<GetCredentialOption> getCredentialOptions) {
-            Preconditions.checkCollectionNotEmpty(mGetCredentialOptions,
+            Preconditions.checkCollectionNotEmpty(getCredentialOptions,
                     "getCredentialOptions");
-            Preconditions.checkCollectionElementsNotNull(mGetCredentialOptions,
+            Preconditions.checkCollectionElementsNotNull(getCredentialOptions,
                     "getCredentialOptions");
             mGetCredentialOptions = getCredentialOptions;
             return this;
diff --git a/core/java/android/service/credentials/GetCredentialsResponse.java b/core/java/android/service/credentials/GetCredentialsResponse.java
index 979a699..5263141 100644
--- a/core/java/android/service/credentials/GetCredentialsResponse.java
+++ b/core/java/android/service/credentials/GetCredentialsResponse.java
@@ -26,12 +26,10 @@
 /**
  * Response from a credential provider, containing credential entries and other associated
  * data to be shown on the account selector UI.
- *
- * @hide
  */
 public final class GetCredentialsResponse implements Parcelable {
     /** Content to be used for the UI. */
-    private final @Nullable CredentialsDisplayContent mCredentialsDisplayContent;
+    private final @Nullable CredentialsResponseContent mCredentialsResponseContent;
 
     /**
      * Authentication action that must be launched and completed before showing any content
@@ -40,11 +38,17 @@
     private final @Nullable Action mAuthenticationAction;
 
     /**
-     * Creates a {@link GetCredentialsRequest} instance with an authentication action set.
+     * Creates a {@link GetCredentialsResponse} instance with an authentication {@link Action} set.
      * Providers must use this method when no content can be shown before authentication.
      *
-     * Once the authentication action activity is launched, and the user is authenticated, providers
-     * should create another response with {@link CredentialsDisplayContent} using
+     * <p> When the user selects this {@code authenticationAction}, the system invokes the
+     * corresponding {@code pendingIntent}. Once the authentication flow is complete,
+     * the {@link android.app.Activity} result should be set
+     * to {@link android.app.Activity#RESULT_OK} and the
+     * {@link CredentialProviderService#EXTRA_GET_CREDENTIALS_CONTENT_RESULT} extra should be set
+     * with a fully populated {@link CredentialsResponseContent} object.
+     * the authentication action activity is launched, and the user is authenticated, providers
+     * should create another response with {@link CredentialsResponseContent} using
      * {@code createWithDisplayContent}, and add that response to the result of the authentication
      * activity.
      *
@@ -58,27 +62,27 @@
     }
 
     /**
-     * Creates a {@link GetCredentialsRequest} instance with display content to be shown on the UI.
+     * Creates a {@link GetCredentialsRequest} instance with content to be shown on the UI.
      * Providers must use this method when there is content to be shown without top level
-     * authentication required.
+     * authentication required, including credential entries, action entries or a remote entry,
      *
-     * @throws NullPointerException If {@code credentialsDisplayContent} is null.
+     * @throws NullPointerException If {@code credentialsResponseContent} is null.
      */
-    public static @NonNull GetCredentialsResponse createWithDisplayContent(
-            @NonNull CredentialsDisplayContent credentialsDisplayContent) {
-        Objects.requireNonNull(credentialsDisplayContent,
-                "credentialsDisplayContent must not be null");
-        return new GetCredentialsResponse(credentialsDisplayContent, null);
+    public static @NonNull GetCredentialsResponse createWithResponseContent(
+            @NonNull CredentialsResponseContent credentialsResponseContent) {
+        Objects.requireNonNull(credentialsResponseContent,
+                "credentialsResponseContent must not be null");
+        return new GetCredentialsResponse(credentialsResponseContent, null);
     }
 
-    private GetCredentialsResponse(@Nullable CredentialsDisplayContent credentialsDisplayContent,
+    private GetCredentialsResponse(@Nullable CredentialsResponseContent credentialsResponseContent,
             @Nullable Action authenticationAction) {
-        mCredentialsDisplayContent = credentialsDisplayContent;
+        mCredentialsResponseContent = credentialsResponseContent;
         mAuthenticationAction = authenticationAction;
     }
 
     private GetCredentialsResponse(@NonNull Parcel in) {
-        mCredentialsDisplayContent = in.readTypedObject(CredentialsDisplayContent.CREATOR);
+        mCredentialsResponseContent = in.readTypedObject(CredentialsResponseContent.CREATOR);
         mAuthenticationAction = in.readTypedObject(Action.CREATOR);
     }
 
@@ -102,23 +106,23 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeTypedObject(mCredentialsDisplayContent, flags);
+        dest.writeTypedObject(mCredentialsResponseContent, flags);
         dest.writeTypedObject(mAuthenticationAction, flags);
     }
 
     /**
-     * Returns the authentication action to be invoked before any other content
-     * can be shown to the user.
+     * If this response represents a top level authentication action, returns the authentication
+     * action to be invoked before any other content can be shown to the user.
      */
     public @Nullable Action getAuthenticationAction() {
         return mAuthenticationAction;
     }
 
     /**
-     * Returns the credentialDisplayContent that does not require authentication, and
-     * can be shown to the user on the account selector UI.
+     * Returns the actual content to be displayed on the selector, if this response does not
+     * require any top level authentication.
      */
-    public @Nullable CredentialsDisplayContent getCredentialsDisplayContent() {
-        return mCredentialsDisplayContent;
+    public @Nullable CredentialsResponseContent getCredentialsResponseContent() {
+        return mCredentialsResponseContent;
     }
 }
diff --git a/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl b/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl
new file mode 100644
index 0000000..ec0bc36
--- /dev/null
+++ b/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl
@@ -0,0 +1,13 @@
+package android.service.credentials;
+
+import android.service.credentials.BeginCreateCredentialResponse;
+
+/**
+ * Interface from the system to a credential provider service.
+ *
+ * @hide
+ */
+oneway interface IBeginCreateCredentialCallback {
+    void onSuccess(in BeginCreateCredentialResponse request);
+    void onFailure(int errorCode, in CharSequence message);
+}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ICreateCredentialCallback.aidl b/core/java/android/service/credentials/ICreateCredentialCallback.aidl
deleted file mode 100644
index 4cc76a4..0000000
--- a/core/java/android/service/credentials/ICreateCredentialCallback.aidl
+++ /dev/null
@@ -1,13 +0,0 @@
-package android.service.credentials;
-
-import android.service.credentials.CreateCredentialResponse;
-
-/**
- * Interface from the system to a credential provider service.
- *
- * @hide
- */
-oneway interface ICreateCredentialCallback {
-    void onSuccess(in CreateCredentialResponse request);
-    void onFailure(int errorCode, in CharSequence message);
-}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl
index c21cefa..b9eb3ed 100644
--- a/core/java/android/service/credentials/ICredentialProviderService.aidl
+++ b/core/java/android/service/credentials/ICredentialProviderService.aidl
@@ -18,9 +18,9 @@
 
 import android.os.ICancellationSignal;
 import android.service.credentials.GetCredentialsRequest;
-import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.BeginCreateCredentialRequest;
 import android.service.credentials.IGetCredentialsCallback;
-import android.service.credentials.ICreateCredentialCallback;
+import android.service.credentials.IBeginCreateCredentialCallback;
 import android.os.ICancellationSignal;
 
 /**
@@ -30,5 +30,5 @@
  */
 interface ICredentialProviderService {
     ICancellationSignal onGetCredentials(in GetCredentialsRequest request, in IGetCredentialsCallback callback);
-    ICancellationSignal onCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback);
+    ICancellationSignal onBeginCreateCredential(in BeginCreateCredentialRequest request, in IBeginCreateCredentialCallback callback);
 }
diff --git a/core/java/android/service/credentials/SaveEntry.java b/core/java/android/service/credentials/SaveEntry.java
deleted file mode 100644
index abe51d4..0000000
--- a/core/java/android/service/credentials/SaveEntry.java
+++ /dev/null
@@ -1,161 +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.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.
- *
- * @hide
- */
-public final class SaveEntry implements Parcelable {
-    private final @NonNull Slice mSlice;
-    private final @Nullable PendingIntent mPendingIntent;
-    private final @Nullable Credential mCredential;
-
-    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>() {
-        @Override
-        public SaveEntry createFromParcel(@NonNull Parcel in) {
-            return new SaveEntry(in);
-        }
-
-        @Override
-        public SaveEntry[] newArray(int size) {
-            return new SaveEntry[size];
-        }
-    };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeTypedObject(mSlice, flags);
-        dest.writeTypedObject(mPendingIntent, flags);
-        dest.writeTypedObject(mCredential, flags);
-    }
-
-    /* package-private */ SaveEntry(
-            @NonNull Slice slice,
-            @Nullable PendingIntent pendingIntent,
-            @Nullable Credential credential) {
-        this.mSlice = slice;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mSlice);
-        this.mPendingIntent = pendingIntent;
-        this.mCredential = credential;
-    }
-
-    /** Returns the content to be displayed with this save entry on the UI. */
-    public @NonNull Slice getSlice() {
-        return mSlice;
-    }
-
-    /** Returns the pendingIntent to be invoked when this save entry on the UI is selectcd. */
-    public @Nullable 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 5f30ad0..dd5373f 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -16,7 +16,6 @@
 
 package android.service.dreams;
 
-import android.content.ComponentName;
 
 /**
  * Dream manager local system service interface.
@@ -61,17 +60,30 @@
     public abstract boolean canStartDreaming(boolean isScreenOn);
 
     /**
-     * Called by the ActivityTaskManagerService to verify that the startDreamActivity
-     * request comes from the current active dream component.
+     * Register a {@link DreamManagerStateListener}, which will be called when there are changes to
+     * dream state.
      *
-     * This function and its call path should not acquire the DreamManagerService lock
-     * to avoid deadlock with the ActivityTaskManager lock.
-     *
-     * TODO: Make this interaction push-based - the DreamManager should inform the
-     * ActivityTaskManager whenever the active dream component changes.
-     *
-     * @param doze If true returns the current active doze component. Otherwise, returns the
-     *             active dream component.
+     * @param listener The listener to register.
      */
-    public abstract ComponentName getActiveDreamComponent(boolean doze);
+    public abstract void registerDreamManagerStateListener(DreamManagerStateListener listener);
+
+    /**
+     * Unregister a {@link DreamManagerStateListener}, which will be called when there are changes
+     * to dream state.
+     *
+     * @param listener The listener to unregister.
+     */
+    public abstract void unregisterDreamManagerStateListener(DreamManagerStateListener listener);
+
+    /**
+     * Called when there are changes to dream state.
+     */
+    public interface DreamManagerStateListener {
+        /**
+         * Called when keep dreaming when undocked has changed.
+         *
+         * @param keepDreaming True if the current dream should continue when undocking.
+         */
+        void onKeepDreamingWhenUndockedChanged(boolean keepDreaming);
+    }
 }
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index aa45c20..6e8198b 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -49,6 +49,17 @@
             mShowComplications = shouldShowComplications;
             onStartDream(layoutParams);
         }
+
+        @Override
+        public void wakeUp() {
+            onWakeUp(() -> {
+                try {
+                    mDreamOverlayCallback.onWakeUpComplete();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Could not notify dream of wakeUp:" + e);
+                }
+            });
+        }
     };
 
     IDreamOverlayCallback mDreamOverlayCallback;
@@ -71,6 +82,17 @@
     public abstract void onStartDream(@NonNull WindowManager.LayoutParams layoutParams);
 
     /**
+     * This method is overridden by implementations to handle when the dream has been requested
+     * to wakeup. This allows any overlay animations to run.
+     *
+     * @param onCompleteCallback The callback to trigger to notify the dream service that the
+     *                           overlay has completed waking up.
+     * @hide
+     */
+    public void onWakeUp(@NonNull Runnable onCompleteCallback) {
+    }
+
+    /**
      * This method is invoked to request the dream exit.
      */
     public final void requestExit() {
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 32bdf79..8b9852a 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -312,7 +312,14 @@
         @Override
         public void onExitRequested() {
             // Simply finish dream when exit is requested.
-            finish();
+            mHandler.post(() -> finish());
+        }
+
+        @Override
+        public void onWakeUpComplete() {
+            // Finish the dream once overlay animations are complete. Execute on handler since
+            // this is coming in on the overlay binder.
+            mHandler.post(() -> finish());
         }
     };
 
@@ -975,7 +982,18 @@
      * </p>
      */
     public void onWakeUp() {
-        finish();
+        if (mOverlayConnection != null) {
+            mOverlayConnection.addConsumer(overlay -> {
+                try {
+                    overlay.wakeUp();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error waking the overlay service", e);
+                    finish();
+                }
+            });
+        } else {
+            finish();
+        }
     }
 
     /** {@inheritDoc} */
@@ -1294,7 +1312,7 @@
         if (!mWindowless) {
             Intent i = new Intent(this, DreamActivity.class);
             i.setPackage(getApplicationContext().getPackageName());
-            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
             i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallbacks(mDreamToken));
             final ServiceInfo serviceInfo = fetchServiceInfo(this,
                     new ComponentName(this, getClass()));
diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl
index 05ebbfe..7aeceb2c 100644
--- a/core/java/android/service/dreams/IDreamOverlay.aidl
+++ b/core/java/android/service/dreams/IDreamOverlay.aidl
@@ -38,4 +38,7 @@
     */
     void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
         in String dreamComponent, in boolean shouldShowComplications);
+
+    /** Called when the dream is waking, to do any exit animations */
+    void wakeUp();
 }
diff --git a/core/java/android/service/dreams/IDreamOverlayCallback.aidl b/core/java/android/service/dreams/IDreamOverlayCallback.aidl
index ec76a33..4ad63f1 100644
--- a/core/java/android/service/dreams/IDreamOverlayCallback.aidl
+++ b/core/java/android/service/dreams/IDreamOverlayCallback.aidl
@@ -28,4 +28,7 @@
     * Invoked to request the dream exit.
     */
     void onExitRequested();
+
+    /** Invoked when the dream overlay wakeUp animation is complete. */
+    void onWakeUpComplete();
 }
\ No newline at end of file
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index cfc79e4..e821af1 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -2365,6 +2365,7 @@
                     UserHandle user= (UserHandle) args.arg2;
                     NotificationChannel channel = (NotificationChannel) args.arg3;
                     int modificationType = (int) args.arg4;
+                    args.recycle();
                     onNotificationChannelModified(pkgName, user, channel, modificationType);
                 } break;
 
@@ -2374,6 +2375,7 @@
                     UserHandle user = (UserHandle) args.arg2;
                     NotificationChannelGroup group = (NotificationChannelGroup) args.arg3;
                     int modificationType = (int) args.arg4;
+                    args.recycle();
                     onNotificationChannelGroupModified(pkgName, user, group, modificationType);
                 } break;
 
diff --git a/core/java/android/service/timezone/TimeZoneProviderEvent.java b/core/java/android/service/timezone/TimeZoneProviderEvent.java
index 714afee..e64bdd6 100644
--- a/core/java/android/service/timezone/TimeZoneProviderEvent.java
+++ b/core/java/android/service/timezone/TimeZoneProviderEvent.java
@@ -74,39 +74,53 @@
     @Nullable
     private final String mFailureCause;
 
-    // Populated when mType == EVENT_TYPE_SUGGESTION or EVENT_TYPE_UNCERTAIN
+    // May be populated when EVENT_TYPE_SUGGESTION or EVENT_TYPE_UNCERTAIN
     @Nullable
     private final TimeZoneProviderStatus mTimeZoneProviderStatus;
 
-    private TimeZoneProviderEvent(int type,
+    private TimeZoneProviderEvent(@EventType int type,
             @ElapsedRealtimeLong long creationElapsedMillis,
             @Nullable TimeZoneProviderSuggestion suggestion,
             @Nullable String failureCause,
             @Nullable TimeZoneProviderStatus timeZoneProviderStatus) {
-        mType = type;
+        mType = validateEventType(type);
         mCreationElapsedMillis = creationElapsedMillis;
         mSuggestion = suggestion;
         mFailureCause = failureCause;
         mTimeZoneProviderStatus = timeZoneProviderStatus;
+
+        // Confirm the type and the provider status agree.
+        if (mType == EVENT_TYPE_PERMANENT_FAILURE && mTimeZoneProviderStatus != null) {
+            throw new IllegalArgumentException(
+                    "Unexpected status: mType=" + mType
+                            + ", mTimeZoneProviderStatus=" + mTimeZoneProviderStatus);
+        }
+    }
+
+    private static @EventType int validateEventType(@EventType int eventType) {
+        if (eventType < EVENT_TYPE_PERMANENT_FAILURE || eventType > EVENT_TYPE_UNCERTAIN) {
+            throw new IllegalArgumentException(Integer.toString(eventType));
+        }
+        return eventType;
     }
 
     /** Returns an event of type {@link #EVENT_TYPE_SUGGESTION}. */
     public static TimeZoneProviderEvent createSuggestionEvent(
             @ElapsedRealtimeLong long creationElapsedMillis,
             @NonNull TimeZoneProviderSuggestion suggestion,
-            @NonNull TimeZoneProviderStatus providerStatus) {
+            @Nullable TimeZoneProviderStatus providerStatus) {
         return new TimeZoneProviderEvent(EVENT_TYPE_SUGGESTION, creationElapsedMillis,
-                Objects.requireNonNull(suggestion), null, Objects.requireNonNull(providerStatus));
+                Objects.requireNonNull(suggestion), null, providerStatus);
     }
 
     /** Returns an event of type {@link #EVENT_TYPE_UNCERTAIN}. */
     public static TimeZoneProviderEvent createUncertainEvent(
             @ElapsedRealtimeLong long creationElapsedMillis,
-            @NonNull TimeZoneProviderStatus timeZoneProviderStatus) {
+            @Nullable TimeZoneProviderStatus timeZoneProviderStatus) {
 
         return new TimeZoneProviderEvent(
                 EVENT_TYPE_UNCERTAIN, creationElapsedMillis, null, null,
-                Objects.requireNonNull(timeZoneProviderStatus));
+                timeZoneProviderStatus);
     }
 
     /** Returns an event of type {@link #EVENT_TYPE_PERMANENT_FAILURE}. */
@@ -148,8 +162,8 @@
     }
 
     /**
-     * Returns the status of the time zone provider. Populated when {@link #getType()} is {@link
-     * #EVENT_TYPE_UNCERTAIN} or {@link #EVENT_TYPE_SUGGESTION}.
+     * Returns the status of the time zone provider.  May be populated when {@link #getType()} is
+     * {@link #EVENT_TYPE_UNCERTAIN} or {@link #EVENT_TYPE_SUGGESTION}, otherwise {@code null}.
      */
     @Nullable
     public TimeZoneProviderStatus getTimeZoneProviderStatus() {
diff --git a/core/java/android/service/timezone/TimeZoneProviderService.java b/core/java/android/service/timezone/TimeZoneProviderService.java
index cd4a305..41ca94b 100644
--- a/core/java/android/service/timezone/TimeZoneProviderService.java
+++ b/core/java/android/service/timezone/TimeZoneProviderService.java
@@ -44,8 +44,8 @@
  *
  * <p>Once started, providers are expected to detect the time zone if possible, and report the
  * result via {@link #reportSuggestion(TimeZoneProviderSuggestion)} or {@link
- * #reportUncertain()}. Providers may also report that they have permanently failed
- * by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each
+ * #reportUncertain(TimeZoneProviderStatus)}. Providers may also report that they have permanently
+ * failed by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each
  * method for details.
  *
  * <p>After starting, providers are expected to issue their first callback within the timeout
@@ -203,7 +203,8 @@
      * details.
      */
     public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion) {
-        reportSuggestion(suggestion, TimeZoneProviderStatus.UNKNOWN);
+        TimeZoneProviderStatus providerStatus = null;
+        reportSuggestionInternal(suggestion, providerStatus);
     }
 
     /**
@@ -212,11 +213,15 @@
      *
      * @param providerStatus provider status information that can influence detector service
      *   behavior and/or be reported via the device UI
-     *
-     * @hide
      */
     public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion,
             @NonNull TimeZoneProviderStatus providerStatus) {
+        Objects.requireNonNull(providerStatus);
+        reportSuggestionInternal(suggestion, providerStatus);
+    }
+
+    private void reportSuggestionInternal(@NonNull TimeZoneProviderSuggestion suggestion,
+            @Nullable TimeZoneProviderStatus providerStatus) {
         Objects.requireNonNull(suggestion);
 
         mHandler.post(() -> {
@@ -241,11 +246,13 @@
 
     /**
      * Indicates the time zone is not known because of an expected runtime state or error, e.g. when
-     * the provider is unable to detect location, or there was a problem when resolving the location
-     * to a time zone.
+     * the provider is unable to detect location, or there was connectivity issue.
+     *
+     * <p>See {@link #reportUncertain(TimeZoneProviderStatus)} for a more expressive version
      */
     public final void reportUncertain() {
-        reportUncertain(TimeZoneProviderStatus.UNKNOWN);
+        TimeZoneProviderStatus providerStatus = null;
+        reportUncertainInternal(providerStatus);
     }
 
     /**
@@ -256,10 +263,13 @@
      *
      * @param providerStatus provider status information that can influence detector service
      *   behavior and/or be reported via the device UI
-     *
-     * @hide
      */
     public final void reportUncertain(@NonNull TimeZoneProviderStatus providerStatus) {
+        Objects.requireNonNull(providerStatus);
+        reportUncertainInternal(providerStatus);
+    }
+
+    private void reportUncertainInternal(@Nullable TimeZoneProviderStatus providerStatus) {
         mHandler.post(() -> {
             synchronized (mLock) {
                 ITimeZoneProviderManager manager = mManager;
@@ -349,8 +359,8 @@
      * <p>Between {@link #onStartUpdates(long)} and {@link #onStopUpdates()} calls, the Android
      * system server holds the latest report from the provider in memory. After an initial report,
      * provider implementations are only required to send a report via {@link
-     * #reportSuggestion(TimeZoneProviderSuggestion)} or via {@link #reportUncertain()} when it
-     * differs from the previous report.
+     * #reportSuggestion(TimeZoneProviderSuggestion, TimeZoneProviderStatus)} or via {@link
+     * #reportUncertain(TimeZoneProviderStatus)} when it differs from the previous report.
      *
      * <p>{@link #reportPermanentFailure(Throwable)} can also be called by provider implementations
      * in rare cases, after which the provider should consider itself stopped and not make any
@@ -362,7 +372,8 @@
      * Android system server may move on to use other providers or detection methods. Providers
      * should therefore make best efforts during this time to generate a report, which could involve
      * increased power usage. Providers should preferably report an explicit {@link
-     * #reportUncertain()} if the time zone(s) cannot be detected within the initialization timeout.
+     * #reportUncertain(TimeZoneProviderStatus)} if the time zone(s) cannot be detected within the
+     * initialization timeout.
      *
      * @see #onStopUpdates() for the signal from the system server to stop sending reports
      */
diff --git a/core/java/android/service/timezone/TimeZoneProviderStatus.java b/core/java/android/service/timezone/TimeZoneProviderStatus.java
index 87d7843..e0b78e9 100644
--- a/core/java/android/service/timezone/TimeZoneProviderStatus.java
+++ b/core/java/android/service/timezone/TimeZoneProviderStatus.java
@@ -18,14 +18,19 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Information about the status of a {@link TimeZoneProviderService}.
@@ -61,6 +66,7 @@
  *
  * @hide
  */
+@SystemApi
 public final class TimeZoneProviderStatus implements Parcelable {
 
     /**
@@ -71,7 +77,7 @@
     @IntDef(prefix = "DEPENDENCY_STATUS_", value = {
             DEPENDENCY_STATUS_UNKNOWN,
             DEPENDENCY_STATUS_NOT_APPLICABLE,
-            DEPENDENCY_STATUS_WORKING,
+            DEPENDENCY_STATUS_OK,
             DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE,
             DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT,
             DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS,
@@ -81,14 +87,18 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface DependencyStatus {}
 
-    /** The dependency's status is unknown. */
+    /**
+     * The dependency's status is unknown.
+     *
+     * @hide
+     */
     public static final @DependencyStatus int DEPENDENCY_STATUS_UNKNOWN = 0;
 
     /** The dependency is not used by the provider's implementation. */
     public static final @DependencyStatus int DEPENDENCY_STATUS_NOT_APPLICABLE = 1;
 
-    /** The dependency is applicable and working well. */
-    public static final @DependencyStatus int DEPENDENCY_STATUS_WORKING = 2;
+    /** The dependency is applicable and there are no known problems. */
+    public static final @DependencyStatus int DEPENDENCY_STATUS_OK = 2;
 
     /**
      * The dependency is used but is temporarily unavailable, e.g. connectivity has been lost for an
@@ -136,76 +146,105 @@
     @IntDef(prefix = "OPERATION_STATUS_", value = {
             OPERATION_STATUS_UNKNOWN,
             OPERATION_STATUS_NOT_APPLICABLE,
-            OPERATION_STATUS_WORKING,
+            OPERATION_STATUS_OK,
             OPERATION_STATUS_FAILED,
     })
     @Target(ElementType.TYPE_USE)
     @Retention(RetentionPolicy.SOURCE)
     public @interface OperationStatus {}
 
-    /** The operation's status is unknown. */
+    /**
+     * The operation's status is unknown.
+     *
+     * @hide
+     */
     public static final @OperationStatus int OPERATION_STATUS_UNKNOWN = 0;
 
     /** The operation is not used by the provider's implementation. */
     public static final @OperationStatus int OPERATION_STATUS_NOT_APPLICABLE = 1;
 
-    /** The operation is applicable and working well. */
-    public static final @OperationStatus int OPERATION_STATUS_WORKING = 2;
+    /** The operation is applicable and there are no known problems. */
+    public static final @OperationStatus int OPERATION_STATUS_OK = 2;
 
-    /** The operation is applicable and failed. */
+    /** The operation is applicable and it recently failed. */
     public static final @OperationStatus int OPERATION_STATUS_FAILED = 3;
 
-    /**
-     * An instance that provides no information about status. Effectively a "null" status.
-     */
-    @NonNull
-    public static final TimeZoneProviderStatus UNKNOWN = new TimeZoneProviderStatus(
-            DEPENDENCY_STATUS_UNKNOWN, DEPENDENCY_STATUS_UNKNOWN, OPERATION_STATUS_UNKNOWN);
-
-    private final @DependencyStatus int mLocationDetectionStatus;
-    private final @DependencyStatus int mConnectivityStatus;
-    private final @OperationStatus int mTimeZoneResolutionStatus;
+    private final @DependencyStatus int mLocationDetectionDependencyStatus;
+    private final @DependencyStatus int mConnectivityDependencyStatus;
+    private final @OperationStatus int mTimeZoneResolutionOperationStatus;
 
     private TimeZoneProviderStatus(
             @DependencyStatus int locationDetectionStatus,
             @DependencyStatus int connectivityStatus,
             @OperationStatus int timeZoneResolutionStatus) {
-        mLocationDetectionStatus = requireValidDependencyStatus(locationDetectionStatus);
-        mConnectivityStatus = requireValidDependencyStatus(connectivityStatus);
-        mTimeZoneResolutionStatus = requireValidOperationStatus(timeZoneResolutionStatus);
+        mLocationDetectionDependencyStatus = locationDetectionStatus;
+        mConnectivityDependencyStatus = connectivityStatus;
+        mTimeZoneResolutionOperationStatus = timeZoneResolutionStatus;
     }
 
     /**
      * Returns the status of the location detection dependencies used by the provider (where
      * applicable).
      */
-    public @DependencyStatus int getLocationDetectionStatus() {
-        return mLocationDetectionStatus;
+    public @DependencyStatus int getLocationDetectionDependencyStatus() {
+        return mLocationDetectionDependencyStatus;
     }
 
     /**
      * Returns the status of the connectivity dependencies used by the provider (where applicable).
      */
-    public @DependencyStatus int getConnectivityStatus() {
-        return mConnectivityStatus;
+    public @DependencyStatus int getConnectivityDependencyStatus() {
+        return mConnectivityDependencyStatus;
     }
 
     /**
      * Returns the status of the time zone resolution operation used by the provider.
      */
-    public @OperationStatus int getTimeZoneResolutionStatus() {
-        return mTimeZoneResolutionStatus;
+    public @OperationStatus int getTimeZoneResolutionOperationStatus() {
+        return mTimeZoneResolutionOperationStatus;
     }
 
     @Override
     public String toString() {
         return "TimeZoneProviderStatus{"
-                + "mLocationDetectionStatus=" + mLocationDetectionStatus
-                + ", mConnectivityStatus=" + mConnectivityStatus
-                + ", mTimeZoneResolutionStatus=" + mTimeZoneResolutionStatus
+                + "mLocationDetectionDependencyStatus="
+                + dependencyStatusToString(mLocationDetectionDependencyStatus)
+                + ", mConnectivityDependencyStatus="
+                + dependencyStatusToString(mConnectivityDependencyStatus)
+                + ", mTimeZoneResolutionOperationStatus="
+                + operationStatusToString(mTimeZoneResolutionOperationStatus)
                 + '}';
     }
 
+    /**
+     * Parses a {@link TimeZoneProviderStatus} from a toString() string for manual command-line
+     * testing.
+     *
+     * @hide
+     */
+    @NonNull
+    public static TimeZoneProviderStatus parseProviderStatus(@NonNull String arg) {
+        // Note: "}" has to be escaped on Android with "\\}" because the regexp library is not based
+        // on OpenJDK code.
+        Pattern pattern = Pattern.compile("TimeZoneProviderStatus\\{"
+                + "mLocationDetectionDependencyStatus=([^,]+)"
+                + ", mConnectivityDependencyStatus=([^,]+)"
+                + ", mTimeZoneResolutionOperationStatus=([^\\}]+)"
+                + "\\}");
+        Matcher matcher = pattern.matcher(arg);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("Unable to parse provider status: " + arg);
+        }
+        @DependencyStatus int locationDependencyStatus =
+                dependencyStatusFromString(matcher.group(1));
+        @DependencyStatus int connectivityDependencyStatus =
+                dependencyStatusFromString(matcher.group(2));
+        @OperationStatus int timeZoneResolutionOperationStatus =
+                operationStatusFromString(matcher.group(3));
+        return new TimeZoneProviderStatus(locationDependencyStatus, connectivityDependencyStatus,
+                timeZoneResolutionOperationStatus);
+    }
+
     public static final @NonNull Creator<TimeZoneProviderStatus> CREATOR = new Creator<>() {
         @Override
         public TimeZoneProviderStatus createFromParcel(Parcel in) {
@@ -229,9 +268,9 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel parcel, int flags) {
-        parcel.writeInt(mLocationDetectionStatus);
-        parcel.writeInt(mConnectivityStatus);
-        parcel.writeInt(mTimeZoneResolutionStatus);
+        parcel.writeInt(mLocationDetectionDependencyStatus);
+        parcel.writeInt(mConnectivityDependencyStatus);
+        parcel.writeInt(mTimeZoneResolutionOperationStatus);
     }
 
     @Override
@@ -243,23 +282,33 @@
             return false;
         }
         TimeZoneProviderStatus that = (TimeZoneProviderStatus) o;
-        return mLocationDetectionStatus == that.mLocationDetectionStatus
-                && mConnectivityStatus == that.mConnectivityStatus
-                && mTimeZoneResolutionStatus == that.mTimeZoneResolutionStatus;
+        return mLocationDetectionDependencyStatus == that.mLocationDetectionDependencyStatus
+                && mConnectivityDependencyStatus == that.mConnectivityDependencyStatus
+                && mTimeZoneResolutionOperationStatus == that.mTimeZoneResolutionOperationStatus;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(
-                mLocationDetectionStatus, mConnectivityStatus, mTimeZoneResolutionStatus);
+                mLocationDetectionDependencyStatus, mConnectivityDependencyStatus,
+                mTimeZoneResolutionOperationStatus);
+    }
+
+    /** @hide */
+    public boolean couldEnableTelephonyFallback() {
+        return mLocationDetectionDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT
+                || mLocationDetectionDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS
+                || mConnectivityDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT
+                || mConnectivityDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
     }
 
     /** A builder for {@link TimeZoneProviderStatus}. */
     public static final class Builder {
 
-        private @DependencyStatus int mLocationDetectionStatus = DEPENDENCY_STATUS_UNKNOWN;
-        private @DependencyStatus int mConnectivityStatus = DEPENDENCY_STATUS_UNKNOWN;
-        private @OperationStatus int mTimeZoneResolutionStatus = OPERATION_STATUS_UNKNOWN;
+        private @DependencyStatus int mLocationDetectionDependencyStatus =
+                DEPENDENCY_STATUS_UNKNOWN;
+        private @DependencyStatus int mConnectivityDependencyStatus = DEPENDENCY_STATUS_UNKNOWN;
+        private @OperationStatus int mTimeZoneResolutionOperationStatus = OPERATION_STATUS_UNKNOWN;
 
         /**
          * Creates a new builder instance. At creation time all status properties are set to
@@ -272,9 +321,9 @@
          * @hide
          */
         public Builder(TimeZoneProviderStatus toCopy) {
-            mLocationDetectionStatus = toCopy.mLocationDetectionStatus;
-            mConnectivityStatus = toCopy.mConnectivityStatus;
-            mTimeZoneResolutionStatus = toCopy.mTimeZoneResolutionStatus;
+            mLocationDetectionDependencyStatus = toCopy.mLocationDetectionDependencyStatus;
+            mConnectivityDependencyStatus = toCopy.mConnectivityDependencyStatus;
+            mTimeZoneResolutionOperationStatus = toCopy.mTimeZoneResolutionOperationStatus;
         }
 
         /**
@@ -282,8 +331,9 @@
          * See the {@code DEPENDENCY_STATUS_} constants for more information.
          */
         @NonNull
-        public Builder setLocationDetectionStatus(@DependencyStatus int locationDetectionStatus) {
-            mLocationDetectionStatus = locationDetectionStatus;
+        public Builder setLocationDetectionDependencyStatus(
+                @DependencyStatus int locationDetectionStatus) {
+            mLocationDetectionDependencyStatus = locationDetectionStatus;
             return this;
         }
 
@@ -292,8 +342,8 @@
          * See the {@code DEPENDENCY_STATUS_} constants for more information.
          */
         @NonNull
-        public Builder setConnectivityStatus(@DependencyStatus int connectivityStatus) {
-            mConnectivityStatus = connectivityStatus;
+        public Builder setConnectivityDependencyStatus(@DependencyStatus int connectivityStatus) {
+            mConnectivityDependencyStatus = connectivityStatus;
             return this;
         }
 
@@ -302,8 +352,9 @@
          * See the {@code OPERATION_STATUS_} constants for more information.
          */
         @NonNull
-        public Builder setTimeZoneResolutionStatus(@OperationStatus int timeZoneResolutionStatus) {
-            mTimeZoneResolutionStatus = timeZoneResolutionStatus;
+        public Builder setTimeZoneResolutionOperationStatus(
+                @OperationStatus int timeZoneResolutionStatus) {
+            mTimeZoneResolutionOperationStatus = timeZoneResolutionStatus;
             return this;
         }
 
@@ -313,11 +364,14 @@
         @NonNull
         public TimeZoneProviderStatus build() {
             return new TimeZoneProviderStatus(
-                    mLocationDetectionStatus, mConnectivityStatus, mTimeZoneResolutionStatus);
+                    requireValidDependencyStatus(mLocationDetectionDependencyStatus),
+                    requireValidDependencyStatus(mConnectivityDependencyStatus),
+                    requireValidOperationStatus(mTimeZoneResolutionOperationStatus));
         }
     }
 
-    private @OperationStatus int requireValidOperationStatus(@OperationStatus int operationStatus) {
+    private static @OperationStatus int requireValidOperationStatus(
+            @OperationStatus int operationStatus) {
         if (operationStatus < OPERATION_STATUS_UNKNOWN
                 || operationStatus > OPERATION_STATUS_FAILED) {
             throw new IllegalArgumentException(Integer.toString(operationStatus));
@@ -325,6 +379,45 @@
         return operationStatus;
     }
 
+    /** @hide */
+    @NonNull
+    public static String operationStatusToString(@OperationStatus int operationStatus) {
+        switch (operationStatus) {
+            case OPERATION_STATUS_UNKNOWN:
+                return "UNKNOWN";
+            case OPERATION_STATUS_NOT_APPLICABLE:
+                return "NOT_APPLICABLE";
+            case OPERATION_STATUS_OK:
+                return "OK";
+            case OPERATION_STATUS_FAILED:
+                return "FAILED";
+            default:
+                throw new IllegalArgumentException("Unknown status: " + operationStatus);
+        }
+    }
+
+    /** @hide */
+    public static @OperationStatus int operationStatusFromString(
+            @Nullable String operationStatusString) {
+
+        if (TextUtils.isEmpty(operationStatusString)) {
+            throw new IllegalArgumentException("Empty status: " + operationStatusString);
+        }
+
+        switch (operationStatusString) {
+            case "UNKNOWN":
+                return OPERATION_STATUS_UNKNOWN;
+            case "NOT_APPLICABLE":
+                return OPERATION_STATUS_NOT_APPLICABLE;
+            case "OK":
+                return OPERATION_STATUS_OK;
+            case "FAILED":
+                return OPERATION_STATUS_FAILED;
+            default:
+                throw new IllegalArgumentException("Unknown status: " + operationStatusString);
+        }
+    }
+
     private static @DependencyStatus int requireValidDependencyStatus(
             @DependencyStatus int dependencyStatus) {
         if (dependencyStatus < DEPENDENCY_STATUS_UNKNOWN
@@ -333,4 +426,56 @@
         }
         return dependencyStatus;
     }
+
+    /** @hide */
+    @NonNull
+    public static String dependencyStatusToString(@DependencyStatus int dependencyStatus) {
+        switch (dependencyStatus) {
+            case DEPENDENCY_STATUS_UNKNOWN:
+                return "UNKNOWN";
+            case DEPENDENCY_STATUS_NOT_APPLICABLE:
+                return "NOT_APPLICABLE";
+            case DEPENDENCY_STATUS_OK:
+                return "OK";
+            case DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE:
+                return "TEMPORARILY_UNAVAILABLE";
+            case DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT:
+                return "BLOCKED_BY_ENVIRONMENT";
+            case DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS:
+                return "DEGRADED_BY_SETTINGS";
+            case DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS:
+                return "BLOCKED_BY_SETTINGS";
+            default:
+                throw new IllegalArgumentException("Unknown status: " + dependencyStatus);
+        }
+    }
+
+    /** @hide */
+    public static @DependencyStatus int dependencyStatusFromString(
+            @Nullable String dependencyStatusString) {
+
+        if (TextUtils.isEmpty(dependencyStatusString)) {
+            throw new IllegalArgumentException("Empty status: " + dependencyStatusString);
+        }
+
+        switch (dependencyStatusString) {
+            case "UNKNOWN":
+                return DEPENDENCY_STATUS_UNKNOWN;
+            case "NOT_APPLICABLE":
+                return DEPENDENCY_STATUS_NOT_APPLICABLE;
+            case "OK":
+                return DEPENDENCY_STATUS_OK;
+            case "TEMPORARILY_UNAVAILABLE":
+                return DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE;
+            case "BLOCKED_BY_ENVIRONMENT":
+                return DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
+            case "DEGRADED_BY_SETTINGS":
+                return DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS;
+            case "BLOCKED_BY_SETTINGS":
+                return DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown status: " + dependencyStatusString);
+        }
+    }
 }
diff --git a/core/java/android/service/voice/HotwordAudioStream.java b/core/java/android/service/voice/HotwordAudioStream.java
index 1c4d14c91..6ae952c 100644
--- a/core/java/android/service/voice/HotwordAudioStream.java
+++ b/core/java/android/service/voice/HotwordAudioStream.java
@@ -47,6 +47,21 @@
 public final class HotwordAudioStream implements Parcelable {
 
     /**
+     * Key for int value to be read from {@link #getMetadata()}. The value is read by the system and
+     * is the length (in bytes) of the byte buffers created to copy bytes in the
+     * {@link #getAudioStreamParcelFileDescriptor()} written by the {@link HotwordDetectionService}.
+     * The buffer length should be chosen such that no additional latency is introduced. Typically,
+     * this should be <em>at least</em> the size of byte chunks written by the
+     * {@link HotwordDetectionService}.
+     *
+     * <p>If no value specified in the metadata for the buffer length, or if the value is less than
+     * 1, or if it is greater than 65,536, or if it is not an int, the default value of 2,560 will
+     * be used.</p>
+     */
+    public static final String KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES =
+            "android.service.voice.key.AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES";
+
+    /**
      * The {@link AudioFormat} of the audio stream.
      */
     @NonNull
@@ -92,39 +107,14 @@
         return new PersistableBundle();
     }
 
-    private String timestampToString() {
-        if (mTimestamp == null) {
-            return "";
-        }
-        return "TimeStamp:"
-                + " framePos=" + mTimestamp.framePosition
-                + " nanoTime=" + mTimestamp.nanoTime;
-    }
-
-    private void parcelTimestamp(Parcel dest, int flags) {
-        if (mTimestamp != null) {
-            // mTimestamp is not null, we write it to the parcel, set true.
-            dest.writeBoolean(true);
-            dest.writeLong(mTimestamp.framePosition);
-            dest.writeLong(mTimestamp.nanoTime);
-        } else {
-            // mTimestamp is null, we don't write any value out, set false.
-            dest.writeBoolean(false);
-        }
-    }
-
-    @Nullable
-    private static AudioTimestamp unparcelTimestamp(Parcel in) {
-        // If it is true, it means we wrote the value to the parcel before, parse it.
-        // Otherwise, return null.
-        if (in.readBoolean()) {
-            final AudioTimestamp timeStamp = new AudioTimestamp();
-            timeStamp.framePosition = in.readLong();
-            timeStamp.nanoTime = in.readLong();
-            return timeStamp;
-        } else {
-            return null;
-        }
+    /**
+     * Provides an instance of {@link Builder} with state corresponding to this instance.
+     * @hide
+     */
+    public Builder buildUpon() {
+        return new Builder(mAudioFormat, mAudioStreamParcelFileDescriptor)
+            .setTimestamp(mTimestamp)
+            .setMetadata(mMetadata);
     }
 
 
@@ -219,7 +209,7 @@
         return "HotwordAudioStream { " +
                 "audioFormat = " + mAudioFormat + ", " +
                 "audioStreamParcelFileDescriptor = " + mAudioStreamParcelFileDescriptor + ", " +
-                "timestamp = " + timestampToString() + ", " +
+                "timestamp = " + mTimestamp + ", " +
                 "metadata = " + mMetadata +
         " }";
     }
@@ -268,7 +258,7 @@
         dest.writeByte(flg);
         dest.writeTypedObject(mAudioFormat, flags);
         dest.writeTypedObject(mAudioStreamParcelFileDescriptor, flags);
-        parcelTimestamp(dest, flags);
+        if (mTimestamp != null) dest.writeTypedObject(mTimestamp, flags);
         dest.writeTypedObject(mMetadata, flags);
     }
 
@@ -286,7 +276,7 @@
         byte flg = in.readByte();
         AudioFormat audioFormat = (AudioFormat) in.readTypedObject(AudioFormat.CREATOR);
         ParcelFileDescriptor audioStreamParcelFileDescriptor = (ParcelFileDescriptor) in.readTypedObject(ParcelFileDescriptor.CREATOR);
-        AudioTimestamp timestamp = unparcelTimestamp(in);
+        AudioTimestamp timestamp = (flg & 0x4) == 0 ? null : (AudioTimestamp) in.readTypedObject(AudioTimestamp.CREATOR);
         PersistableBundle metadata = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
 
         this.mAudioFormat = audioFormat;
@@ -439,10 +429,10 @@
     }
 
     @DataClass.Generated(
-            time = 1665976240224L,
+            time = 1669916341034L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordAudioStream.java",
-            inputSignatures = "private final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStreamParcelFileDescriptor\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static  android.media.AudioTimestamp defaultTimestamp()\nprivate static  android.os.PersistableBundle defaultMetadata()\nprivate  java.lang.String timestampToString()\nprivate  void parcelTimestamp(android.os.Parcel,int)\nprivate static @android.annotation.Nullable android.media.AudioTimestamp unparcelTimestamp(android.os.Parcel)\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)")
+            inputSignatures = "public static final  java.lang.String KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES\nprivate final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStreamParcelFileDescriptor\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static  android.media.AudioTimestamp defaultTimestamp()\nprivate static  android.os.PersistableBundle defaultMetadata()\npublic  android.service.voice.HotwordAudioStream.Builder buildUpon()\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index e22bbd8..dee560b 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -31,6 +31,8 @@
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -99,14 +101,30 @@
     private static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE = 63;
 
     /**
-     * The bundle key for proximity value
+     * The bundle key for proximity
      *
      * TODO(b/238896013): Move the proximity logic out of bundle to proper API.
-     *
-     * @hide
      */
-    public static final String EXTRA_PROXIMITY_METERS =
-            "android.service.voice.extra.PROXIMITY_METERS";
+    private static final String EXTRA_PROXIMITY =
+            "android.service.voice.extra.PROXIMITY";
+
+    /** Users’ proximity is unknown (proximity sensing was inconclusive and is unsupported). */
+    public static final int PROXIMITY_UNKNOWN = -1;
+
+    /** Proximity value that represents that the object is near. */
+    public static final int PROXIMITY_NEAR = 1;
+
+    /** Proximity value that represents that the object is far. */
+    public static final int PROXIMITY_FAR = 2;
+
+    /** @hide */
+    @IntDef(prefix = {"PROXIMITY"}, value = {
+            PROXIMITY_UNKNOWN,
+            PROXIMITY_NEAR,
+            PROXIMITY_FAR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ProximityValue {}
 
     /** Confidence level in the trigger outcome. */
     @HotwordConfidenceLevelValue
@@ -220,12 +238,14 @@
      * versions of Android.
      *
      * <p>After the trigger happens, a special case of proximity-related extra, with the key of
-     * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
-     * will be stored to enable proximity logic. The proximity meters is provided by the system,
-     * on devices that support detecting proximity of nearby users, to help disambiguate which
-     * nearby device should respond. When the proximity is unknown, the proximity value will not
-     * be stored. This mapping will be excluded from the max bundle size calculation because this
-     * mapping is included after the result is returned from the hotword detector service.
+     * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+     * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+     * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+     * proximity. The proximity value is provided by the system, on devices that support detecting
+     * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+     * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+     * from the max bundle size calculation because this mapping is included after the result is
+     * returned from the hotword detector service.
      *
      * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
      * that can be used to communicate with other processes.
@@ -348,16 +368,16 @@
             // Remove the proximity key from the bundle before checking the bundle size. The
             // proximity value is added after the privileged module and can avoid the
             // maxBundleSize limitation.
-            if (mExtras.containsKey(EXTRA_PROXIMITY_METERS)) {
-                double proximityMeters = mExtras.getDouble(EXTRA_PROXIMITY_METERS);
-                mExtras.remove(EXTRA_PROXIMITY_METERS);
+            if (mExtras.containsKey(EXTRA_PROXIMITY)) {
+                int proximityValue = mExtras.getInt(EXTRA_PROXIMITY);
+                mExtras.remove(EXTRA_PROXIMITY);
                 // Skip checking parcelable size if the new bundle size is 0. Newly empty bundle
                 // has parcelable size of 4, but the default bundle has parcelable size of 0.
                 if (mExtras.size() > 0) {
                     Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
                             getMaxBundleSize(), "extras");
                 }
-                mExtras.putDouble(EXTRA_PROXIMITY_METERS, proximityMeters);
+                mExtras.putInt(EXTRA_PROXIMITY, proximityValue);
             } else {
                 Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
                         getMaxBundleSize(), "extras");
@@ -372,6 +392,52 @@
         return List.copyOf(mAudioStreams);
     }
 
+    /**
+     * Adds proximity level, either near or far, that is mapped for the given distance into
+     * the bundle. The proximity value is provided by the system, on devices that support detecting
+     * proximity of nearby users, to help disambiguate which nearby device should respond.
+     * This mapping will be excluded from the max bundle size calculation because this mapping is
+     * included after the result is returned from the hotword detector service. The value will not
+     * be included if the proximity was unknown.
+     *
+     * @hide
+     */
+    public void setProximity(double distance) {
+        int proximityLevel = convertToProximityLevel(distance);
+        if (proximityLevel != PROXIMITY_UNKNOWN) {
+            mExtras.putInt(EXTRA_PROXIMITY, proximityLevel);
+        }
+    }
+
+    /**
+     * Returns proximity level, which can be either of {@link HotwordDetectedResult#PROXIMITY_NEAR}
+     * or {@link HotwordDetectedResult#PROXIMITY_FAR}. If the proximity is unknown, it will
+     * return {@link HotwordDetectedResult#PROXIMITY_UNKNOWN}.
+     */
+    @ProximityValue
+    public int getProximity() {
+        return mExtras.getInt(EXTRA_PROXIMITY, PROXIMITY_UNKNOWN);
+    }
+
+    /**
+     * Mapping of the proximity distance (meters) to proximity values, unknown, near, and far.
+     * Currently, this mapping is handled by HotwordDetectedResult because it handles just
+     * HotwordDetectionConnection which we know the mapping of. However, the mapping will need to
+     * move to a more centralized place once there are more clients.
+     *
+     * TODO(b/258531144): Move the proximity mapping to a central location
+     */
+    @ProximityValue
+    private int convertToProximityLevel(double distance) {
+        if (distance < 0) {
+            return PROXIMITY_UNKNOWN;
+        } else if (distance <= 3) {
+            return PROXIMITY_NEAR;
+        } else {
+            return PROXIMITY_FAR;
+        }
+    }
+
     @DataClass.Suppress("addAudioStreams")
     abstract static class BaseBuilder {
         /**
@@ -388,6 +454,25 @@
         }
     }
 
+    /**
+     * Provides an instance of {@link Builder} with state corresponding to this instance.
+     * @hide
+     */
+    public Builder buildUpon() {
+        return new Builder()
+            .setConfidenceLevel(mConfidenceLevel)
+            .setMediaSyncEvent(mMediaSyncEvent)
+            .setHotwordOffsetMillis(mHotwordOffsetMillis)
+            .setHotwordDurationMillis(mHotwordDurationMillis)
+            .setAudioChannel(mAudioChannel)
+            .setHotwordDetectionPersonalized(mHotwordDetectionPersonalized)
+            .setScore(mScore)
+            .setPersonalizedScore(mPersonalizedScore)
+            .setHotwordPhraseId(mHotwordPhraseId)
+            .setAudioStreams(mAudioStreams)
+            .setExtras(mExtras);
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -413,7 +498,7 @@
         CONFIDENCE_LEVEL_HIGH,
         CONFIDENCE_LEVEL_VERY_HIGH
     })
-    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @Retention(RetentionPolicy.SOURCE)
     @DataClass.Generated.Member
     public @interface ConfidenceLevel {}
 
@@ -444,7 +529,7 @@
         LIMIT_HOTWORD_OFFSET_MAX_VALUE,
         LIMIT_AUDIO_CHANNEL_MAX_VALUE
     })
-    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @Retention(RetentionPolicy.SOURCE)
     @DataClass.Generated.Member
     /* package-private */ @interface Limit {}
 
@@ -460,6 +545,30 @@
         }
     }
 
+    /** @hide */
+    @IntDef(prefix = "PROXIMITY_", value = {
+        PROXIMITY_UNKNOWN,
+        PROXIMITY_NEAR,
+        PROXIMITY_FAR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface Proximity {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String proximityToString(@Proximity int value) {
+        switch (value) {
+            case PROXIMITY_UNKNOWN:
+                    return "PROXIMITY_UNKNOWN";
+            case PROXIMITY_NEAR:
+                    return "PROXIMITY_NEAR";
+            case PROXIMITY_FAR:
+                    return "PROXIMITY_FAR";
+            default: return Integer.toHexString(value);
+        }
+    }
+
     @DataClass.Generated.Member
     /* package-private */ HotwordDetectedResult(
             @HotwordConfidenceLevelValue int confidenceLevel,
@@ -586,12 +695,14 @@
      * versions of Android.
      *
      * <p>After the trigger happens, a special case of proximity-related extra, with the key of
-     * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
-     * will be stored to enable proximity logic. The proximity meters is provided by the system,
-     * on devices that support detecting proximity of nearby users, to help disambiguate which
-     * nearby device should respond. When the proximity is unknown, the proximity value will not
-     * be stored. This mapping will be excluded from the max bundle size calculation because this
-     * mapping is included after the result is returned from the hotword detector service.
+     * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+     * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+     * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+     * proximity. The proximity value is provided by the system, on devices that support detecting
+     * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+     * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+     * from the max bundle size calculation because this mapping is included after the result is
+     * returned from the hotword detector service.
      *
      * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
      * that can be used to communicate with other processes.
@@ -904,12 +1015,14 @@
          * versions of Android.
          *
          * <p>After the trigger happens, a special case of proximity-related extra, with the key of
-         * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
-         * will be stored to enable proximity logic. The proximity meters is provided by the system,
-         * on devices that support detecting proximity of nearby users, to help disambiguate which
-         * nearby device should respond. When the proximity is unknown, the proximity value will not
-         * be stored. This mapping will be excluded from the max bundle size calculation because this
-         * mapping is included after the result is returned from the hotword detector service.
+         * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+         * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+         * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+         * proximity. The proximity value is provided by the system, on devices that support detecting
+         * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+         * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+         * from the max bundle size calculation because this mapping is included after the result is
+         * returned from the hotword detector service.
          *
          * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
          * that can be used to communicate with other processes.
@@ -984,10 +1097,10 @@
     }
 
     @DataClass.Generated(
-            time = 1665995595979L,
+            time = 1668385264834L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
-            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final  java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final  java.lang.String EXTRA_PROXIMITY\npublic static final  int PROXIMITY_UNKNOWN\npublic static final  int PROXIMITY_NEAR\npublic static final  int PROXIMITY_FAR\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic  void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic  android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index df69cc0..552a793 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -25,6 +25,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.Service;
 import android.content.ContentCaptureOptions;
 import android.content.Context;
@@ -90,10 +91,10 @@
     /**
      * Feature flag for Attention Service.
      *
-     * TODO(b/247920386): Add TestApi annotation
      * @hide
      */
-    public static final boolean ENABLE_PROXIMITY_RESULT = false;
+    @TestApi
+    public static final boolean ENABLE_PROXIMITY_RESULT = true;
 
     /**
      * Indicates that the updated status is successful.
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 1285d1e..7c125c7 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -349,7 +349,7 @@
      * {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
      * AlwaysOnHotwordDetector.Callback)} or {@link #createHotwordDetector(PersistableBundle,
      * SharedMemory, HotwordDetector.Callback)}, call this will throw an
-     * {@link IllegalArgumentException}.
+     * {@link IllegalStateException}.
      *
      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
      * @param locale The locale for which the enrollment needs to be performed.
@@ -385,7 +385,7 @@
      *
      * <p>Note: If there are any active detectors that are created by using
      * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)},
-     * call this will throw an {@link IllegalArgumentException}.
+     * call this will throw an {@link IllegalStateException}.
      *
      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
      * @param locale The locale for which the enrollment needs to be performed.
@@ -428,13 +428,18 @@
             if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) {
                 // Allow only one concurrent recognition via the APIs.
                 safelyShutdownAllHotwordDetectors();
-            }
-
-            for (HotwordDetector detector : mActiveHotwordDetectors) {
-                if (detector.isUsingHotwordDetectionService() != supportHotwordDetectionService) {
-                    throw new IllegalArgumentException(
-                            "It disallows to create trusted and non-trusted detectors "
-                                    + "at the same time.");
+            } else {
+                for (HotwordDetector detector : mActiveHotwordDetectors) {
+                    if (detector.isUsingHotwordDetectionService()
+                            != supportHotwordDetectionService) {
+                        throw new IllegalStateException(
+                                "It disallows to create trusted and non-trusted detectors "
+                                        + "at the same time.");
+                    } else if (detector instanceof AlwaysOnHotwordDetector) {
+                        throw new IllegalStateException(
+                                "There is already an active AlwaysOnHotwordDetector. "
+                                        + "It must be destroyed to create a new one.");
+                    }
                 }
             }
 
@@ -442,11 +447,7 @@
                     callback, mKeyphraseEnrollmentInfo, mSystemService,
                     getApplicationContext().getApplicationInfo().targetSdkVersion,
                     supportHotwordDetectionService);
-            if (!mActiveHotwordDetectors.add(dspDetector)) {
-                throw new IllegalArgumentException(
-                        "the keyphrase=" + keyphrase + " and locale=" + locale
-                                + " are already used by another always-on detector");
-            }
+            mActiveHotwordDetectors.add(dspDetector);
 
             try {
                 dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
@@ -480,7 +481,7 @@
      *
      * <p>Note: If there are any active detectors that are created by using
      * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)},
-     * call this will throw an {@link IllegalArgumentException}.
+     * call this will throw an {@link IllegalStateException}.
      *
      * @param options Application configuration data to be provided to the
      * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
@@ -513,11 +514,11 @@
             } else {
                 for (HotwordDetector detector : mActiveHotwordDetectors) {
                     if (!detector.isUsingHotwordDetectionService()) {
-                        throw new IllegalArgumentException(
+                        throw new IllegalStateException(
                                 "It disallows to create trusted and non-trusted detectors "
                                         + "at the same time.");
                     } else if (detector instanceof SoftwareHotwordDetector) {
-                        throw new IllegalArgumentException(
+                        throw new IllegalStateException(
                                 "There is already an active SoftwareHotwordDetector. "
                                         + "It must be destroyed to create a new one.");
                     }
@@ -527,6 +528,7 @@
             SoftwareHotwordDetector softwareHotwordDetector =
                     new SoftwareHotwordDetector(
                             mSystemService, null, callback);
+            mActiveHotwordDetectors.add(softwareHotwordDetector);
 
             try {
                 softwareHotwordDetector.registerOnDestroyListener(
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index 93d4def..a41401b 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -47,4 +47,5 @@
     oneway void addLocalColorsAreas(in List<RectF> regions);
     SurfaceControl mirrorSurfaceControl();
     oneway void applyDimming(float dimAmount);
+    oneway void setWallpaperFlags(int which);
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index c6cd708..84a233f 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -31,6 +31,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.FloatRange;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
@@ -165,6 +166,7 @@
     private static final int MSG_SCALE_PREVIEW = 10110;
     private static final int MSG_REPORT_SHOWN = 10150;
     private static final int MSG_UPDATE_DIMMING = 10200;
+    private static final int MSG_WALLPAPER_FLAGS_CHANGED = 10210;
     private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
             Float.NEGATIVE_INFINITY);
 
@@ -485,6 +487,13 @@
         }
 
         /**
+         * Returns the current wallpaper flags indicating which screen this Engine is rendering to.
+         */
+        @SetWallpaperFlags public int getWallpaperFlags() {
+            return mIWallpaperEngine.mWhich;
+        }
+
+        /**
          * Convenience for {@link WallpaperManager#getDesiredMinimumWidth()
          * WallpaperManager.getDesiredMinimumWidth()}, returning the width
          * that the system would like this wallpaper to run in.
@@ -575,6 +584,8 @@
          */
         public void reportEngineShown(boolean waitForEngineShown) {
             if (mIWallpaperEngine.mShownReported) return;
+            Trace.beginSection("WPMS.reportEngineShown-" + waitForEngineShown);
+            Log.d(TAG, "reportEngineShown: shouldWait=" + waitForEngineShown);
             if (!waitForEngineShown) {
                 Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN);
                 mCaller.removeMessages(MSG_REPORT_SHOWN);
@@ -586,6 +597,7 @@
                     mCaller.sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(5));
                 }
             }
+            Trace.endSection();
         }
 
         /**
@@ -654,6 +666,7 @@
          * Called once to initialize the engine.  After returning, the
          * engine's surface will be created by the framework.
          */
+        @MainThread
         public void onCreate(SurfaceHolder surfaceHolder) {
         }
 
@@ -662,6 +675,7 @@
          * surface will be destroyed and this Engine object is no longer
          * valid.
          */
+        @MainThread
         public void onDestroy() {
         }
 
@@ -670,6 +684,7 @@
          * hidden.  <em>It is very important that a wallpaper only use
          * CPU while it is visible.</em>.
          */
+        @MainThread
         public void onVisibilityChanged(boolean visible) {
         }
 
@@ -680,6 +695,7 @@
          *
          * @param insets Insets to apply.
          */
+        @MainThread
         public void onApplyWindowInsets(WindowInsets insets) {
         }
 
@@ -690,6 +706,7 @@
          * user is interacting with, so if it is slow you will get fewer
          * move events.
          */
+        @MainThread
         public void onTouchEvent(MotionEvent event) {
         }
 
@@ -699,6 +716,7 @@
          * call to {@link WallpaperManager#setWallpaperOffsets(IBinder, float, float)
          * WallpaperManager.setWallpaperOffsets()}.
          */
+        @MainThread
         public void onOffsetsChanged(float xOffset, float yOffset,
                 float xOffsetStep, float yOffsetStep,
                 int xPixelOffset, int yPixelOffset) {
@@ -721,6 +739,7 @@
          * @return If returning a result, create a Bundle and place the
          * result data in to it.  Otherwise return null.
          */
+        @MainThread
         public Bundle onCommand(String action, int x, int y, int z,
                 Bundle extras, boolean resultRequested) {
             return null;
@@ -739,6 +758,7 @@
          * @hide
          */
         @SystemApi
+        @MainThread
         public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
         }
 
@@ -746,6 +766,7 @@
          * Called when an application has changed the desired virtual size of
          * the wallpaper.
          */
+        @MainThread
         public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) {
         }
 
@@ -753,6 +774,7 @@
          * Convenience for {@link SurfaceHolder.Callback#surfaceChanged
          * SurfaceHolder.Callback.surfaceChanged()}.
          */
+        @MainThread
         public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
         }
 
@@ -760,6 +782,7 @@
          * Convenience for {@link SurfaceHolder.Callback2#surfaceRedrawNeeded
          * SurfaceHolder.Callback.surfaceRedrawNeeded()}.
          */
+        @MainThread
         public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
         }
 
@@ -767,6 +790,7 @@
          * Convenience for {@link SurfaceHolder.Callback#surfaceCreated
          * SurfaceHolder.Callback.surfaceCreated()}.
          */
+        @MainThread
         public void onSurfaceCreated(SurfaceHolder holder) {
         }
 
@@ -774,16 +798,28 @@
          * Convenience for {@link SurfaceHolder.Callback#surfaceDestroyed
          * SurfaceHolder.Callback.surfaceDestroyed()}.
          */
+        @MainThread
         public void onSurfaceDestroyed(SurfaceHolder holder) {
         }
 
         /**
+         * Called when the current wallpaper flags change.
+         *
+         * @param which The new flag value
+         * @see #getWallpaperFlags()
+         */
+        @MainThread
+        public void onWallpaperFlagsChanged(@SetWallpaperFlags int which) {
+        }
+
+        /**
          * Called when the zoom level of the wallpaper changed.
          * This method will be called with the initial zoom level when the surface is created.
          *
          * @param zoom the zoom level, between 0 indicating fully zoomed in and 1 indicating fully
          *             zoomed out.
          */
+        @MainThread
         public void onZoomChanged(@FloatRange(from = 0f, to = 1f) float zoom) {
         }
 
@@ -833,6 +869,7 @@
          *
          * @return Wallpaper colors.
          */
+        @MainThread
         public @Nullable WallpaperColors onComputeColors() {
             return null;
         }
@@ -1258,7 +1295,9 @@
                             didSurface = true;
                             if (DEBUG) Log.v(TAG, "onSurfaceCreated("
                                     + mSurfaceHolder + "): " + this);
+                            Trace.beginSection("WPMS.Engine.onSurfaceCreated");
                             onSurfaceCreated(mSurfaceHolder);
+                            Trace.endSection();
                             SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                             if (callbacks != null) {
                                 for (SurfaceHolder.Callback c : callbacks) {
@@ -1284,8 +1323,10 @@
                                     + ", " + mCurWidth + ", " + mCurHeight
                                     + "): " + this);
                             didSurface = true;
+                            Trace.beginSection("WPMS.Engine.onSurfaceChanged");
                             onSurfaceChanged(mSurfaceHolder, mFormat,
                                     mCurWidth, mCurHeight);
+                            Trace.endSection();
                             SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                             if (callbacks != null) {
                                 for (SurfaceHolder.Callback c : callbacks) {
@@ -1302,11 +1343,15 @@
                             if (DEBUG) {
                                 Log.v(TAG, "dispatching insets=" + windowInsets);
                             }
+                            Trace.beginSection("WPMS.Engine.onApplyWindowInsets");
                             onApplyWindowInsets(windowInsets);
+                            Trace.endSection();
                         }
 
                         if (redrawNeeded) {
+                            Trace.beginSection("WPMS.Engine.onSurfaceRedrawNeeded");
                             onSurfaceRedrawNeeded(mSurfaceHolder);
+                            Trace.endSection();
                             SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                             if (callbacks != null) {
                                 for (SurfaceHolder.Callback c : callbacks) {
@@ -1331,11 +1376,15 @@
                                 // the state to get them to notice.
                                 if (DEBUG) Log.v(TAG, "onVisibilityChanged(true) at surface: "
                                         + this);
+                                Trace.beginSection("WPMS.Engine.onVisibilityChanged-true");
                                 onVisibilityChanged(true);
+                                Trace.endSection();
                             }
                             if (DEBUG) Log.v(TAG, "onVisibilityChanged(false) at surface: "
                                         + this);
+                            Trace.beginSection("WPMS.Engine.onVisibilityChanged-false");
                             onVisibilityChanged(false);
+                            Trace.endSection();
                         }
                     } finally {
                         mIsCreating = false;
@@ -1420,12 +1469,16 @@
             mDisplayState = mDisplay.getState();
 
             if (DEBUG) Log.v(TAG, "onCreate(): " + this);
+            Trace.beginSection("WPMS.Engine.onCreate");
             onCreate(mSurfaceHolder);
+            Trace.endSection();
 
             mInitializing = false;
 
             mReportedVisible = false;
+            Trace.beginSection("WPMS.Engine.updateSurface");
             updateSurface(false, false, false);
+            Trace.endSection();
         }
 
         /**
@@ -2130,6 +2183,17 @@
         }
     }
 
+    /**
+     * Returns a Looper which messages such as {@link WallpaperService#DO_ATTACH},
+     * {@link WallpaperService#DO_DETACH} etc. are sent to.
+     * By default, returns the process's main looper.
+     * @hide
+     */
+    @NonNull
+    public Looper onProvideEngineLooper() {
+        return super.getMainLooper();
+    }
+
     private boolean isValid(RectF area) {
         if (area == null) return false;
         boolean valid = area.bottom > area.top && area.left < area.right
@@ -2160,13 +2224,14 @@
         private final AtomicBoolean mDetached = new AtomicBoolean();
 
         Engine mEngine;
+        @SetWallpaperFlags int mWhich;
 
-        IWallpaperEngineWrapper(WallpaperService context,
+        IWallpaperEngineWrapper(WallpaperService service,
                 IWallpaperConnection conn, IBinder windowToken,
                 int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
-                int displayId) {
+                int displayId, @SetWallpaperFlags int which) {
             mWallpaperManager = getSystemService(WallpaperManager.class);
-            mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);
+            mCaller = new HandlerCaller(service, service.onProvideEngineLooper(), this, true);
             mConnection = conn;
             mWindowToken = windowToken;
             mWindowType = windowType;
@@ -2175,6 +2240,7 @@
             mReqHeight = reqHeight;
             mDisplayPadding.set(padding);
             mDisplayId = displayId;
+            mWhich = which;
 
             // Create a display context before onCreateEngine.
             mDisplayManager = getSystemService(DisplayManager.class);
@@ -2205,6 +2271,16 @@
         }
 
         @Override
+        public void setWallpaperFlags(@SetWallpaperFlags int which) {
+            if (which == mWhich) {
+                return;
+            }
+            mWhich = which;
+            Message msg = mCaller.obtainMessageI(MSG_WALLPAPER_FLAGS_CHANGED, which);
+            mCaller.sendMessage(msg);
+        }
+
+        @Override
         public void setInAmbientMode(boolean inAmbientDisplay, long animationDuration)
                 throws RemoteException {
             Message msg = mCaller.obtainMessageIO(DO_IN_AMBIENT_MODE, inAmbientDisplay ? 1 : 0,
@@ -2235,14 +2311,15 @@
         public void reportShown() {
             if (!mShownReported) {
                 mShownReported = true;
+                Trace.beginSection("WPMS.mConnection.engineShown");
                 try {
                     mConnection.engineShown(this);
                     Log.d(TAG, "Wallpaper has updated the surface:"
                             + mWallpaperManager.getWallpaperInfo());
                 } catch (RemoteException e) {
                     Log.w(TAG, "Wallpaper host disappeared", e);
-                    return;
                 }
+                Trace.endSection();
             }
         }
 
@@ -2284,6 +2361,27 @@
             return mEngine == null ? null : SurfaceControl.mirrorSurface(mEngine.mSurfaceControl);
         }
 
+        private void doAttachEngine() {
+            Trace.beginSection("WPMS.onCreateEngine");
+            Engine engine = onCreateEngine();
+            Trace.endSection();
+            mEngine = engine;
+            Trace.beginSection("WPMS.mConnection.attachEngine-" + mDisplayId);
+            try {
+                mConnection.attachEngine(this, mDisplayId);
+            } catch (RemoteException e) {
+                engine.detach();
+                Log.w(TAG, "Wallpaper host disappeared", e);
+                return;
+            } finally {
+                Trace.endSection();
+            }
+            mActiveEngines.add(engine);
+            Trace.beginSection("WPMS.engine.attach");
+            engine.attach(this);
+            Trace.endSection();
+        }
+
         private void doDetachEngine() {
             mActiveEngines.remove(mEngine);
             mEngine.detach();
@@ -2309,21 +2407,15 @@
             }
             switch (message.what) {
                 case DO_ATTACH: {
-                    Engine engine = onCreateEngine();
-                    mEngine = engine;
-                    try {
-                        mConnection.attachEngine(this, mDisplayId);
-                    } catch (RemoteException e) {
-                        engine.detach();
-                        Log.w(TAG, "Wallpaper host disappeared", e);
-                        return;
-                    }
-                    mActiveEngines.add(engine);
-                    engine.attach(this);
+                    Trace.beginSection("WPMS.DO_ATTACH");
+                    doAttachEngine();
+                    Trace.endSection();
                     return;
                 }
                 case DO_DETACH: {
+                    Trace.beginSection("WPMS.DO_DETACH");
                     doDetachEngine();
+                    Trace.endSection();
                     return;
                 }
                 case DO_SET_DESIRED_SIZE: {
@@ -2404,7 +2496,12 @@
                     }
                 } break;
                 case MSG_REPORT_SHOWN: {
+                    Trace.beginSection("WPMS.MSG_REPORT_SHOWN");
                     reportShown();
+                    Trace.endSection();
+                } break;
+                case MSG_WALLPAPER_FLAGS_CHANGED: {
+                    mEngine.onWallpaperFlagsChanged(message.arg1);
                 } break;
                 default :
                     Log.w(TAG, "Unknown message type " + message.what);
@@ -2428,8 +2525,10 @@
         public void attach(IWallpaperConnection conn, IBinder windowToken,
                 int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
                 int displayId, @SetWallpaperFlags int which) {
+            Trace.beginSection("WPMS.ServiceWrapper.attach");
             mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken,
-                    windowType, isPreview, reqWidth, reqHeight, padding, displayId);
+                    windowType, isPreview, reqWidth, reqHeight, padding, displayId, which);
+            Trace.endSection();
         }
 
         @Override
@@ -2440,16 +2539,20 @@
 
     @Override
     public void onCreate() {
+        Trace.beginSection("WPMS.onCreate");
         super.onCreate();
+        Trace.endSection();
     }
 
     @Override
     public void onDestroy() {
+        Trace.beginSection("WPMS.onDestroy");
         super.onDestroy();
         for (int i=0; i<mActiveEngines.size(); i++) {
             mActiveEngines.get(i).detach();
         }
         mActiveEngines.clear();
+        Trace.endSection();
     }
 
     /**
@@ -2467,6 +2570,7 @@
      * when the wallpaper is currently set as the active wallpaper and the user
      * is in the wallpaper picker viewing a preview of it as well.
      */
+    @MainThread
     public abstract Engine onCreateEngine();
 
     @Override
diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl
new file mode 100644
index 0000000..ba71174
--- /dev/null
+++ b/core/java/android/service/wearable/IWearableSensingService.aidl
@@ -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 android.service.wearable;
+
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.SharedMemory;
+
+/**
+ * Interface for a concrete implementation to provide AmbientContextEvents to the framework for
+ * wearables.
+ *
+ * @hide
+ */
+oneway interface IWearableSensingService {
+    void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
+    void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
+}
\ No newline at end of file
diff --git a/core/java/android/service/wearable/OWNERS b/core/java/android/service/wearable/OWNERS
new file mode 100644
index 0000000..073e2d7
--- /dev/null
+++ b/core/java/android/service/wearable/OWNERS
@@ -0,0 +1,3 @@
+charliewang@google.com
+oni@google.com
+volnov@google.com
\ No newline at end of file
diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java
new file mode 100644
index 0000000..a1c7658
--- /dev/null
+++ b/core/java/android/service/wearable/WearableSensingService.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 android.service.wearable;
+
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.app.wearable.WearableSensingManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.SharedMemory;
+import android.util.Slog;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+
+/**
+ * Abstract base class for sensing with wearable devices. An example of this is {@link
+ *AmbientContextEvent} detection.
+ *
+ * <p> A service that provides requested sensing events to the system, such as a {@link
+ *AmbientContextEvent}. The system's default WearableSensingService implementation is configured in
+ * {@code config_defaultWearableSensingService}. If this config has no value, a stub is
+ * returned.
+ *
+ * <p> An implementation of a WearableSensingService should be an isolated service. Using the
+ * "isolatedProcess=true" attribute in the service's configurations. </p>
+ **
+ * <pre>
+ * {@literal
+ * <service android:name=".YourWearableSensingService"
+ *          android:permission="android.permission.BIND_WEARABLE_SENSING_SERVICE"
+ *          android:isolatedProcess="true">
+ * </service>}
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class WearableSensingService extends Service {
+    private static final String TAG = WearableSensingService.class.getSimpleName();
+
+    /**
+     * The bundle key for this class of object, used in {@code RemoteCallback#sendResult}.
+     *
+     * @hide
+     */
+    public static final String STATUS_RESPONSE_BUNDLE_KEY =
+            "android.app.wearable.WearableSensingStatusBundleKey";
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service. To be supported, the
+     * service must also require the
+     * {@link android.Manifest.permission#BIND_WEARABLE_SENSING_SERVICE}
+     * permission so that other applications can not abuse it.
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.service.wearable.WearableSensingService";
+
+    @Nullable
+    @Override
+    public final IBinder onBind(@NonNull Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return new IWearableSensingService.Stub() {
+                /** {@inheritDoc} */
+                @Override
+                public void provideDataStream(
+                        ParcelFileDescriptor parcelFileDescriptor,
+                        RemoteCallback callback) {
+                    Objects.requireNonNull(parcelFileDescriptor);
+                    Consumer<Integer> consumer = response -> {
+                        Bundle bundle = new Bundle();
+                        bundle.putInt(
+                                STATUS_RESPONSE_BUNDLE_KEY,
+                                response);
+                        callback.sendResult(bundle);
+                    };
+                    WearableSensingService.this.onDataStreamProvided(
+                            parcelFileDescriptor, consumer);
+                }
+
+                /** {@inheritDoc} */
+                @Override
+                public void provideData(
+                        PersistableBundle data,
+                        SharedMemory sharedMemory,
+                        RemoteCallback callback) {
+                    Objects.requireNonNull(data);
+                    Consumer<Integer> consumer = response -> {
+                        Bundle bundle = new Bundle();
+                        bundle.putInt(
+                                STATUS_RESPONSE_BUNDLE_KEY,
+                                response);
+                        callback.sendResult(bundle);
+                    };
+                    WearableSensingService.this.onDataProvided(data, sharedMemory, consumer);
+                }
+            };
+        }
+        Slog.w(TAG, "Incorrect service interface, returning null.");
+        return null;
+    }
+
+    /**
+     * Called when a data stream to the wearable is provided. This data stream can be used to obtain
+     * data from a wearable device. It is up to the implementation to maintain the data stream and
+     * close the data stream when it is finished.
+     *
+     * @param parcelFileDescriptor The data stream to the wearable
+     * @param statusConsumer the consumer for the service status.
+     */
+    @BinderThread
+    public abstract void onDataStreamProvided(@NonNull ParcelFileDescriptor parcelFileDescriptor,
+            @NonNull Consumer<Integer> statusConsumer);
+
+    /**
+     * Called when configurations and read-only data in a {@link PersistableBundle}
+     * can be used by the WearableSensingService and sends the result to the {@link Consumer}
+     * right after the call. It is dependent on the application to define the type of data to
+     * provide. This is used by applications that will also provide an implementation of an isolated
+     * WearableSensingService. If the data was provided successfully
+     * {@link WearableSensingManager#STATUS_SUCCESS} will be provided.
+     *
+     * @param data Application configuration data to provide to the {@link WearableSensingService}.
+     *             PersistableBundle does not allow any remotable objects or other contents
+     *             that can be used to communicate with other processes.
+     * @param sharedMemory The unrestricted data blob to
+     *                     provide to the {@link WearableSensingService}. Use this to provide the
+     *                     sensing models data or other such data to the trusted process.
+     * @param statusConsumer the consumer for the service status.
+     */
+    @BinderThread
+    public abstract void onDataProvided(
+            @NonNull PersistableBundle data,
+            @Nullable SharedMemory sharedMemory,
+            @NonNull Consumer<Integer> statusConsumer);
+}
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index e5c9adb..dded76c 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -26,7 +26,6 @@
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.Looper;
-import android.telephony.Annotation.CallState;
 import android.telephony.Annotation.DisconnectCauses;
 import android.telephony.Annotation.PreciseDisconnectCauses;
 import android.telephony.Annotation.RadioPowerState;
@@ -726,7 +725,7 @@
      */
     @Deprecated
     @RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
-    public void onCallStateChanged(@CallState int state, String phoneNumber) {
+    public void onCallStateChanged(@Annotation.CallState int state, String phoneNumber) {
         // default implementation empty
     }
 
@@ -1569,12 +1568,48 @@
                     () -> mExecutor.execute(() -> psl.onRadioPowerStateChanged(state)));
         }
 
-        public void onCallAttributesChanged(CallAttributes callAttributes) {
+        public void onCallStatesChanged(List<CallState> callStateList) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
 
+            if (callStateList == null) return;
+            CallAttributes ca;
+            if (callStateList.isEmpty()) {
+                ca = new CallAttributes(
+                        new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                                PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                                PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                                DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+                        TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality());
+            } else {
+                int foregroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+                int backgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+                int ringingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+                for (CallState cs : callStateList) {
+                    switch (cs.getCallClassification()) {
+                        case CallState.CALL_CLASSIFICATION_FOREGROUND:
+                            foregroundCallState = cs.getCallState();
+                            break;
+                        case CallState.CALL_CLASSIFICATION_BACKGROUND:
+                            backgroundCallState = cs.getCallState();
+                            break;
+                        case CallState.CALL_CLASSIFICATION_RINGING:
+                            ringingCallState = cs.getCallState();
+                            break;
+                        default:
+                            break;
+                    }
+                }
+                ca = new CallAttributes(
+                        new PreciseCallState(
+                                ringingCallState, foregroundCallState, backgroundCallState,
+                                DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+                        callStateList.get(0).getNetworkType(),
+                        callStateList.get(0).getCallQuality());
+            }
             Binder.withCleanCallingIdentity(
-                    () -> mExecutor.execute(() -> psl.onCallAttributesChanged(callAttributes)));
+                    () -> mExecutor.execute(
+                            () -> psl.onCallAttributesChanged(ca)));
         }
 
         public void onActiveDataSubIdChanged(int subId) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index e8960b8..257f3b7 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -27,6 +27,7 @@
 import android.os.Build;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IPhoneStateListener;
@@ -62,7 +63,7 @@
  * appropriate sub-interfaces.
  */
 public class TelephonyCallback {
-
+    private static final String LOG_TAG = "TelephonyCallback";
     /**
      * Experiment flag to set the per-pid registration limit for TelephonyCallback
      *
@@ -1332,7 +1333,9 @@
     @SystemApi
     public interface CallAttributesListener {
         /**
-         * Callback invoked when the call attributes changes on the registered subscription.
+         * Callback invoked when the call attributes changes on the active call on the registered
+         * subscription. If the user swaps between a foreground and background call the call
+         * attributes will be reported for the active call only.
          * Note, the registration subscription ID comes from {@link TelephonyManager} object
          * which registers TelephonyCallback by
          * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
@@ -1346,9 +1349,77 @@
          * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
          *
          * @param callAttributes the call attributes
+         * @deprecated Use onCallStatesChanged({@link List<CallState>}) to get each of call
+         *          state for all ongoing calls on the subscription.
          */
         @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
-        void onCallAttributesChanged(@NonNull CallAttributes callAttributes);
+        @Deprecated
+        default void onCallAttributesChanged(@NonNull CallAttributes callAttributes) {
+            Log.w(LOG_TAG, "onCallAttributesChanged(List<CallState>) should be "
+                    + "overridden.");
+        }
+
+        /**
+         * Callback invoked when the call attributes changes on the ongoing calls on the registered
+         * subscription. If there are 1 foreground and 1 background call, Two {@link CallState}
+         * will be passed.
+         * Note, the registration subscription ID comes from {@link TelephonyManager} object
+         * which registers TelephonyCallback by
+         * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
+         * If this TelephonyManager object was created with
+         * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+         * subscription ID. Otherwise, this callback applies to
+         * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+         * In the event that there are no active(state is not
+         * {@link PreciseCallState#PRECISE_CALL_STATE_IDLE}) calls, this API will report empty list.
+         *
+         * The calling app should have carrier privileges
+         * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+         * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
+         *
+         * @param callStateList the list of call states for each ongoing call. If there are
+         *                           a active call and a holding call, 1 call attributes for
+         *                           {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE}  and another
+         *                           for {@link PreciseCallState#PRECISE_CALL_STATE_HOLDING}
+         *                           will be in this list.
+         */
+        // Added as default for backward compatibility
+        @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+        default void onCallStatesChanged(@NonNull List<CallState> callStateList) {
+            if (callStateList.size() > 0) {
+                int foregroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+                int backgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+                int ringingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+                for (CallState cs : callStateList) {
+                    switch (cs.getCallClassification()) {
+                        case CallState.CALL_CLASSIFICATION_FOREGROUND:
+                            foregroundCallState = cs.getCallState();
+                            break;
+                        case CallState.CALL_CLASSIFICATION_BACKGROUND:
+                            backgroundCallState = cs.getCallState();
+                            break;
+                        case CallState.CALL_CLASSIFICATION_RINGING:
+                            ringingCallState = cs.getCallState();
+                            break;
+                        default:
+                            break;
+                    }
+                }
+                onCallAttributesChanged(new CallAttributes(
+                        new PreciseCallState(
+                                ringingCallState, foregroundCallState, backgroundCallState,
+                                DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+                        callStateList.get(0).getNetworkType(),
+                        callStateList.get(0).getCallQuality()));
+            } else {
+                onCallAttributesChanged(new CallAttributes(
+                        new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                                PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                                PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                                DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+                        TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality()));
+            }
+        }
     }
 
     /**
@@ -1702,14 +1773,13 @@
                     () -> mExecutor.execute(() -> listener.onRadioPowerStateChanged(state)));
         }
 
-        public void onCallAttributesChanged(CallAttributes callAttributes) {
+        public void onCallStatesChanged(List<CallState> callStateList) {
             CallAttributesListener listener =
                     (CallAttributesListener) mTelephonyCallbackWeakRef.get();
             if (listener == null) return;
 
             Binder.withCleanCallingIdentity(
-                    () -> mExecutor.execute(() -> listener.onCallAttributesChanged(
-                            callAttributes)));
+                    () -> mExecutor.execute(() -> listener.onCallStatesChanged(callStateList)));
         }
 
         public void onActiveDataSubIdChanged(int subId) {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index a3696e3..0a1538de 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -32,13 +32,13 @@
 import android.telephony.Annotation.DataActivityType;
 import android.telephony.Annotation.DisconnectCauses;
 import android.telephony.Annotation.NetworkType;
-import android.telephony.Annotation.PreciseCallStates;
 import android.telephony.Annotation.PreciseDisconnectCauses;
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SimActivationState;
 import android.telephony.Annotation.SrvccState;
 import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
 import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
 import android.util.ArraySet;
 import android.util.Log;
@@ -741,17 +741,20 @@
      * @param slotIndex for which precise call state changed. Can be derived from subId except when
      * subId is invalid.
      * @param subId for which precise call state changed.
-     * @param ringCallPreciseState ringCall state.
-     * @param foregroundCallPreciseState foreground call state.
-     * @param backgroundCallPreciseState background call state.
+     * @param callStates Array of PreciseCallState of foreground, background & ringing calls.
+     * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId} for
+     *                   ringing, foreground & background calls.
+     * @param imsServiceTypes Array of IMS call service type for ringing, foreground &
+     *                        background calls.
+     * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
      */
     public void notifyPreciseCallState(int slotIndex, int subId,
-            @PreciseCallStates int ringCallPreciseState,
-            @PreciseCallStates int foregroundCallPreciseState,
-            @PreciseCallStates int backgroundCallPreciseState) {
+            @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
+            @Annotation.ImsCallServiceType int[] imsServiceTypes,
+            @Annotation.ImsCallType int[] imsCallTypes) {
         try {
-            sRegistry.notifyPreciseCallState(slotIndex, subId, ringCallPreciseState,
-                foregroundCallPreciseState, backgroundCallPreciseState);
+            sRegistry.notifyPreciseCallState(slotIndex, subId, callStates,
+                    imsCallIds, imsServiceTypes, imsCallTypes);
         } catch (RemoteException ex) {
             // system process is dead
             throw ex.rethrowFromSystemServer();
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/Highlights.java b/core/java/android/text/Highlights.java
new file mode 100644
index 0000000..356dfca
--- /dev/null
+++ b/core/java/android/text/Highlights.java
@@ -0,0 +1,148 @@
+/*
+ * 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.text;
+
+import android.graphics.Paint;
+import android.util.Pair;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A class that represents of the highlight of the text.
+ */
+public class Highlights {
+    private final List<Pair<Paint, int[]>> mHighlights;
+
+    private Highlights(List<Pair<Paint, int[]>> highlights) {
+        mHighlights = highlights;
+    }
+
+    /**
+     * Returns a number of highlight.
+     *
+     * @return a number of highlight.
+     *
+     * @see Builder#addRange(Paint, int, int)
+     * @see Builder#addRanges(Paint, int...)
+     */
+    public @IntRange(from = 0) int getSize() {
+        return mHighlights.size();
+    }
+
+    /**
+     * Returns a paint used for the i-th highlight.
+     *
+     * @param index an index of the highlight. Must be between 0 and {@link #getSize()}
+     * @return a paint object
+     *
+     * @see Builder#addRange(Paint, int, int)
+     * @see Builder#addRanges(Paint, int...)
+     */
+    public @NonNull Paint getPaint(@IntRange(from = 0) int index) {
+        return mHighlights.get(index).first;
+    }
+
+    /**
+     * Returns ranges of the i-th highlight.
+     *
+     * Ranges are represented of flattened inclusive start and exclusive end integers array. The
+     * inclusive start offset of the {@code i}-th range is stored in {@code 2 * i}-th of the array.
+     * The exclusive end offset of the {@code i}-th range is stored in {@code 2* i + 1}-th of the
+     * array. For example, the two ranges: (1, 2) and (3, 4) are flattened into single int array
+     * [1, 2, 3, 4].
+     *
+     * @param index an index of the highlight. Must be between 0 and {@link #getSize()}
+     * @return a paint object
+     *
+     * @see Builder#addRange(Paint, int, int)
+     * @see Builder#addRanges(Paint, int...)
+     */
+    public @NonNull int[] getRanges(int index) {
+        return mHighlights.get(index).second;
+    }
+
+    /**
+     * A builder for the Highlights.
+     */
+    public static final class Builder {
+        private final List<Pair<Paint, int[]>> mHighlights = new ArrayList<>();
+
+        /**
+         * Add single range highlight.
+         *
+         * @param paint a paint object used for drawing highlight path.
+         * @param start an inclusive offset of the text.
+         * @param end an exclusive offset of the text.
+         * @return this builder instance.
+         */
+        public @NonNull Builder addRange(@NonNull Paint paint, @IntRange(from = 0) int start,
+                @IntRange(from = 0) int end) {
+            if (start > end) {
+                throw new IllegalArgumentException("start must not be larger than end: "
+                        + start + ", " + end);
+            }
+            Objects.requireNonNull(paint);
+
+            int[] range = new int[] {start, end};
+            mHighlights.add(new Pair<>(paint, range));
+            return this;
+        }
+
+        /**
+         * Add multiple ranges highlight.
+         *
+         * @param paint a paint object used for drawing highlight path.
+         * @param ranges a flatten ranges. The {@code 2 * i}-th element is an inclusive start offset
+         *              of the {@code i}-th character. The {@code 2 * i + 1}-th element is an
+         *              exclusive end offset of the {@code i}-th character.
+         * @return this builder instance.
+         */
+        public @NonNull Builder addRanges(@NonNull Paint paint, @NonNull int... ranges) {
+            if (ranges.length % 2 == 1) {
+                throw new IllegalArgumentException(
+                        "Flatten ranges must have even numbered elements");
+            }
+            for (int j = 0; j < ranges.length / 2; ++j) {
+                int start = ranges[j * 2];
+                int end = ranges[j * 2 + 1];
+                if (start > end) {
+                    throw new IllegalArgumentException(
+                            "Reverse range found in the flatten range: " + Arrays.toString(
+                                    ranges));
+                }
+            }
+            Objects.requireNonNull(paint);
+            mHighlights.add(new Pair<>(paint, ranges));
+            return this;
+        }
+
+        /**
+         * Build a new Highlights instance.
+         *
+         * @return a new Highlights instance.
+         */
+        public @NonNull Highlights build() {
+            return new Highlights(mHighlights);
+        }
+    }
+}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 1337d6a..64dc16d 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -44,6 +44,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * A base class that manages text layout in visual elements on
@@ -347,9 +348,13 @@
 
     /**
      * Draw this Layout on the specified Canvas.
+     *
+     * This API draws background first, then draws text on top of it.
+     *
+     * @see #draw(Canvas, List, List, Path, Paint, int)
      */
     public void draw(Canvas c) {
-        draw(c, null, null, 0);
+        draw(c, (Path) null, (Paint) null, 0);
     }
 
     /**
@@ -357,23 +362,142 @@
      * between the background and the text.
      *
      * @param canvas the canvas
-     * @param highlight the path of the highlight or cursor; can be null
-     * @param highlightPaint the paint for the highlight
+     * @param selectionHighlight the path of the selection highlight or cursor; can be null
+     * @param selectionHighlightPaint the paint for the selection highlight
      * @param cursorOffsetVertical the amount to temporarily translate the
      *        canvas while rendering the highlight
+     *
+     * @see #draw(Canvas, List, List, Path, Paint, int)
      */
-    public void draw(Canvas canvas, Path highlight, Paint highlightPaint,
+    public void draw(
+            Canvas canvas, Path selectionHighlight,
+            Paint selectionHighlightPaint, int cursorOffsetVertical) {
+        draw(canvas, null, null, selectionHighlight, selectionHighlightPaint, cursorOffsetVertical);
+    }
+
+    /**
+     * Draw this layout on the specified canvas.
+     *
+     * This API draws background first, then draws highlight paths on top of it, then draws
+     * selection or cursor, then finally draws text on top of it.
+     *
+     * @see #drawBackground(Canvas)
+     * @see #drawText(Canvas)
+     *
+     * @param canvas the canvas
+     * @param highlightPaths the path of the highlights. The highlightPaths and highlightPaints must
+     *                      have the same length and aligned in the same order. For example, the
+     *                      paint of the n-th of the highlightPaths should be stored at the n-th of
+     *                      highlightPaints.
+     * @param highlightPaints the paints for the highlights. The highlightPaths and highlightPaints
+     *                        must have the same length and aligned in the same order. For example,
+     *                        the paint of the n-th of the highlightPaths should be stored at the
+     *                        n-th of highlightPaints.
+     * @param selectionPath the selection or cursor path
+     * @param selectionPaint the paint for the selection or cursor.
+     * @param cursorOffsetVertical the amount to temporarily translate the canvas while rendering
+     *                            the highlight
+     */
+    public void draw(@NonNull Canvas canvas,
+            @Nullable List<Path> highlightPaths,
+            @Nullable List<Paint> highlightPaints,
+            @Nullable Path selectionPath,
+            @Nullable Paint selectionPaint,
             int cursorOffsetVertical) {
         final long lineRange = getLineRangeForDraw(canvas);
         int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
         int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
         if (lastLine < 0) return;
 
-        drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,
-                firstLine, lastLine);
+        drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
+                cursorOffsetVertical, firstLine, lastLine);
         drawText(canvas, firstLine, lastLine);
     }
 
+    /**
+     * Draw text part of this layout.
+     *
+     * Different from {@link #draw(Canvas, List, List, Path, Paint, int)} API, this API only draws
+     * text part, not drawing highlights, selections, or backgrounds.
+     *
+     * @see #draw(Canvas, List, List, Path, Paint, int)
+     * @see #drawBackground(Canvas)
+     *
+     * @param canvas the canvas
+     */
+    public void drawText(@NonNull Canvas canvas) {
+        final long lineRange = getLineRangeForDraw(canvas);
+        int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
+        int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
+        if (lastLine < 0) return;
+        drawText(canvas, firstLine, lastLine);
+    }
+
+    /**
+     * Draw background of this layout.
+     *
+     * Different from {@link #draw(Canvas, List, List, Path, Paint, int)} API, this API only draws
+     * background, not drawing text, highlights or selections. The background here is drawn by
+     * {@link LineBackgroundSpan} attached to the text.
+     *
+     * @see #draw(Canvas, List, List, Path, Paint, int)
+     * @see #drawText(Canvas)
+     *
+     * @param canvas the canvas
+     */
+    public void drawBackground(@NonNull Canvas canvas) {
+        final long lineRange = getLineRangeForDraw(canvas);
+        int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
+        int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
+        if (lastLine < 0) return;
+        drawBackground(canvas, firstLine, lastLine);
+    }
+
+    /**
+     * @hide public for Editor.java
+     */
+    public void drawWithoutText(
+            @NonNull Canvas canvas,
+            @Nullable List<Path> highlightPaths,
+            @Nullable List<Paint> highlightPaints,
+            @Nullable Path selectionPath,
+            @Nullable Paint selectionPaint,
+            int cursorOffsetVertical,
+            int firstLine,
+            int lastLine) {
+        drawBackground(canvas, firstLine, lastLine);
+        if (highlightPaths == null && highlightPaints == null) {
+            return;
+        }
+        if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical);
+        try {
+            if (highlightPaths != null) {
+                if (highlightPaints == null) {
+                    throw new IllegalArgumentException(
+                            "if highlight is specified, highlightPaint must be specified.");
+                }
+                if (highlightPaints.size() != highlightPaths.size()) {
+                    throw new IllegalArgumentException(
+                            "The highlight path size is different from the size of highlight"
+                                    + " paints");
+                }
+                for (int i = 0; i < highlightPaths.size(); ++i) {
+                    final Path highlight = highlightPaths.get(i);
+                    final Paint highlightPaint = highlightPaints.get(i);
+                    if (highlight != null) {
+                        canvas.drawPath(highlight, highlightPaint);
+                    }
+                }
+            }
+
+            if (selectionPath != null) {
+                canvas.drawPath(selectionPath, selectionPaint);
+            }
+        } finally {
+            if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical);
+        }
+    }
+
     private boolean isJustificationRequired(int lineNum) {
         if (mJustificationMode == JUSTIFICATION_MODE_NONE) return false;
         final int lineEnd = getLineEnd(lineNum);
@@ -635,8 +759,9 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void drawBackground(Canvas canvas, Path highlight, Paint highlightPaint,
-            int cursorOffsetVertical, int firstLine, int lastLine) {
+    public void drawBackground(
+            @NonNull Canvas canvas,
+            int firstLine, int lastLine) {
         // First, draw LineBackgroundSpans.
         // LineBackgroundSpans know nothing about the alignment, margins, or
         // direction of the layout or line.  XXX: Should they?
@@ -700,14 +825,6 @@
             }
             mLineBackgroundSpans.recycle();
         }
-
-        // There can be a highlight even without spans if we are drawing
-        // a non-spanned transformation of a spanned editing buffer.
-        if (highlight != null) {
-            if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical);
-            canvas.drawPath(highlight, highlightPaint);
-            if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical);
-        }
     }
 
     /**
@@ -3000,6 +3117,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/style/AccessibilityURLSpan.java b/core/java/android/text/style/AccessibilityURLSpan.java
index bd81623..e280bdf 100644
--- a/core/java/android/text/style/AccessibilityURLSpan.java
+++ b/core/java/android/text/style/AccessibilityURLSpan.java
@@ -26,6 +26,7 @@
  * It is used to replace URLSpans in {@link AccessibilityNodeInfo#setText(CharSequence)}
  * @hide
  */
+@SuppressWarnings("ParcelableCreator")
 public class AccessibilityURLSpan extends URLSpan implements Parcelable {
     final AccessibilityClickableSpan mAccessibilityClickableSpan;
 
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index 85b7ae9..d61228b 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -149,7 +149,7 @@
         }
 
         mTextFontWeight = a.getInt(com.android.internal.R.styleable
-                .TextAppearance_textFontWeight, -1);
+                .TextAppearance_textFontWeight, /*defValue*/ FontStyle.FONT_WEIGHT_UNSPECIFIED);
 
         final String localeString = a.getString(com.android.internal.R.styleable
                 .TextAppearance_textLocale);
@@ -215,7 +215,7 @@
         mTextColorLink = linkColor;
         mTypeface = null;
 
-        mTextFontWeight = -1;
+        mTextFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
         mTextLocales = null;
 
         mShadowRadius = 0.0f;
@@ -359,8 +359,8 @@
     }
 
     /**
-     * Returns the text font weight specified by this span, or <code>-1</code>
-     * if it does not specify one.
+     * Returns the text font weight specified by this span, or
+     * <code>FontStyle.FONT_WEIGHT_UNSPECIFIED</code> if it does not specify one.
      */
     public int getTextFontWeight() {
         return mTextFontWeight;
diff --git a/core/java/android/transparency/BinaryTransparencyManager.java b/core/java/android/transparency/BinaryTransparencyManager.java
index 18783f5..f6d7c61 100644
--- a/core/java/android/transparency/BinaryTransparencyManager.java
+++ b/core/java/android/transparency/BinaryTransparencyManager.java
@@ -24,7 +24,7 @@
 
 import com.android.internal.os.IBinaryTransparencyService;
 
-import java.util.Map;
+import java.util.List;
 
 /**
  * BinaryTransparencyManager defines a number of system interfaces that other system apps or
@@ -66,12 +66,15 @@
     }
 
     /**
-     * Returns a map of all installed APEXs consisting of package name to SHA256 hash of the
-     * package.
-     * @return A Map with the following entries: {apex package name : sha256 digest of package}
+     * Gets binary measurements of all installed APEXs, each packed in a Bundle.
+     * @return A List of {@link android.os.Bundle}s with the following keys:
+     *         {@link com.android.server.BinaryTransparencyService#BUNDLE_PACKAGE_INFO}
+     *         {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST_ALGORITHM}
+     *         {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST}
      */
+    // TODO(b/259422958): Fix static constants referenced here - should be defined here
     @NonNull
-    public Map getApexInfo() {
+    public List getApexInfo() {
         try {
             Slog.d(TAG, "Calling backend's getApexInfo()");
             return mService.getApexInfo();
diff --git a/core/java/android/util/AndroidException.java b/core/java/android/util/AndroidException.java
index 1345ddf..d1b9d9f 100644
--- a/core/java/android/util/AndroidException.java
+++ b/core/java/android/util/AndroidException.java
@@ -40,5 +40,5 @@
             boolean writableStackTrace) {
         super(message, cause, enableSuppression, writableStackTrace);
     }
-};
+}
 
diff --git a/core/java/android/util/AndroidRuntimeException.java b/core/java/android/util/AndroidRuntimeException.java
index 2b824bf..72c34d8b 100644
--- a/core/java/android/util/AndroidRuntimeException.java
+++ b/core/java/android/util/AndroidRuntimeException.java
@@ -34,5 +34,4 @@
     public AndroidRuntimeException(Exception cause) {
         super(cause);
     }
-};
-
+}
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 517d982..959295b 100755
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.FontScaleConverter;
 import android.os.SystemProperties;
 
 /**
@@ -273,6 +274,15 @@
      * increments at runtime based on a user preference for the font size.
      */
     public float scaledDensity;
+
+    /**
+     * If non-null, this will be used to calculate font sizes instead of {@link #scaledDensity}.
+     *
+     * @hide
+     */
+    @Nullable
+    public FontScaleConverter fontScaleConverter;
+
     /**
      * The exact physical pixels per inch of the screen in the X dimension.
      */
@@ -350,6 +360,7 @@
         noncompatScaledDensity = o.noncompatScaledDensity;
         noncompatXdpi = o.noncompatXdpi;
         noncompatYdpi = o.noncompatYdpi;
+        fontScaleConverter = o.fontScaleConverter;
     }
     
     public void setToDefaults() {
@@ -367,6 +378,7 @@
         noncompatScaledDensity = scaledDensity;
         noncompatXdpi = xdpi;
         noncompatYdpi = ydpi;
+        fontScaleConverter = null;
     }
 
     @Override
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index d1f05ec..4277d01 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -103,6 +103,32 @@
     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 trackpad gesture settings UI
+     * @hide
+     */
+    public static final String SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE =
+            "settings_new_keyboard_trackpad_gesture";
+
+    /**
      * Enable the new pages which is implemented with SPA.
      * @hide
      */
@@ -113,6 +139,19 @@
      */
     public static final String SETTINGS_ADB_METRICS_WRITER = "settings_adb_metrics_writer";
 
+    /**
+     * Flag to show stylus-specific preferences in Connected Devices
+     * @hide
+     */
+    public static final String SETTINGS_SHOW_STYLUS_PREFERENCES =
+            "settings_show_stylus_preferences";
+
+    /**
+     * Flag to enable/disable biometrics enrollment v2
+     * @hide
+     */
+    public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment";
+
     private static final Map<String, String> DEFAULT_FLAGS;
 
     static {
@@ -143,11 +182,18 @@
         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_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
         DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
+        DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "false");
+        DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
+
     static {
         PERSISTENT_FLAGS = new HashSet<>();
         PERSISTENT_FLAGS.add(SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE);
@@ -158,6 +204,10 @@
         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);
+        PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE);
     }
 
     /**
diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java
index 7b28b8a..bc0e35d 100644
--- a/core/java/android/util/IntArray.java
+++ b/core/java/android/util/IntArray.java
@@ -234,4 +234,23 @@
     public int[] toArray() {
         return Arrays.copyOf(mValues, mSize);
     }
+
+    @Override
+    public String toString() {
+        // Code below is copied from Arrays.toString(), but uses mSize in the lopp (it cannot call
+        // Arrays.toString() directly as it would return the unused elements as well)
+        int iMax = mSize - 1;
+        if (iMax == -1) {
+            return "[]";
+        }
+        StringBuilder b = new StringBuilder();
+        b.append('[');
+        for (int i = 0;; i++) {
+            b.append(mValues[i]);
+            if (i == iMax) {
+                return b.append(']').toString();
+            }
+            b.append(", ");
+        }
+    }
 }
diff --git a/core/java/android/util/OWNERS b/core/java/android/util/OWNERS
index d4cf6e6..3772006 100644
--- a/core/java/android/util/OWNERS
+++ b/core/java/android/util/OWNERS
@@ -1,6 +1,6 @@
 per-file Dump* = file:/core/java/com/android/internal/util/dump/OWNERS
 per-file FeatureFlagUtils.java = sbasi@google.com
-per-file FeatureFlagUtils.java = tmfang@google.com
+per-file FeatureFlagUtils.java = edgarwang@google.com
 
 per-file AttributeSet.java = file:/core/java/android/content/res/OWNERS
 per-file TypedValue.java = file:/core/java/android/content/res/OWNERS
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/util/Range.java b/core/java/android/util/Range.java
index 9fd0ab9..41c171a 100644
--- a/core/java/android/util/Range.java
+++ b/core/java/android/util/Range.java
@@ -356,4 +356,4 @@
 
     private final T mLower;
     private final T mUpper;
-};
+}
diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java
index c54d9b6..3e7c67e 100644
--- a/core/java/android/util/RotationUtils.java
+++ b/core/java/android/util/RotationUtils.java
@@ -25,6 +25,7 @@
 import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.view.Surface.Rotation;
 import android.view.SurfaceControl;
@@ -193,6 +194,29 @@
     }
 
     /**
+     * Same as {@link #rotatePoint}, but for float coordinates.
+     */
+    public static void rotatePointF(PointF inOutPoint, @Rotation int rotation,
+            float parentW, float parentH) {
+        float origX = inOutPoint.x;
+        switch (rotation) {
+            case ROTATION_0:
+                return;
+            case ROTATION_90:
+                inOutPoint.x = inOutPoint.y;
+                inOutPoint.y = parentW - origX;
+                return;
+            case ROTATION_180:
+                inOutPoint.x = parentW - inOutPoint.x;
+                inOutPoint.y = parentH - inOutPoint.y;
+                return;
+            case ROTATION_270:
+                inOutPoint.x = parentH - inOutPoint.y;
+                inOutPoint.y = origX;
+        }
+    }
+
+    /**
      * Sets a matrix such that given a rotation, it transforms physical display
      * coordinates to that rotation's logical coordinates.
      *
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index 05c86172..cc83dec 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -525,9 +525,10 @@
             return false;
         }
 
+        // size() calls above took care about gc() compaction.
         for (int index = 0; index < size; index++) {
-            int key = keyAt(index);
-            if (!Objects.equals(valueAt(index), other.get(key))) {
+            if (mKeys[index] != other.mKeys[index]
+                    || !Objects.equals(mValues[index], other.mValues[index])) {
                 return false;
             }
         }
@@ -545,10 +546,11 @@
     public int contentHashCode() {
         int hash = 0;
         int size = size();
+        // size() call above took care about gc() compaction.
         for (int index = 0; index < size; index++) {
-            int key = keyAt(index);
-            E value = valueAt(index);
-            hash = 31 * hash + Objects.hashCode(key);
+            int key = mKeys[index];
+            E value = (E) mValues[index];
+            hash = 31 * hash + key;
             hash = 31 * hash + Objects.hashCode(value);
         }
         return hash;
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index 19de396..7e054fc 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -408,7 +408,14 @@
         case COMPLEX_UNIT_DIP:
             return value * metrics.density;
         case COMPLEX_UNIT_SP:
-            return value * metrics.scaledDensity;
+                if (metrics.fontScaleConverter != null) {
+                    return applyDimension(
+                            COMPLEX_UNIT_DIP,
+                            metrics.fontScaleConverter.convertSpToDp(value),
+                            metrics);
+                } else {
+                    return value * metrics.scaledDensity;
+                }
         case COMPLEX_UNIT_PT:
             return value * metrics.xdpi * (1.0f/72);
         case COMPLEX_UNIT_IN:
@@ -696,5 +703,5 @@
         sb.append("}");
         return sb.toString();
     }
-};
+}
 
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index c8c1fd4..eb467e0 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -93,8 +93,9 @@
      * associated with each signer.
      *
      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
-     * @throws SecurityException if a APK Signature Scheme v2 signature of this APK does not verify.
-     * @throws IOException if an I/O error occurs while reading the APK file.
+     * @throws SecurityException          if an APK Signature Scheme v2 signature of this APK does
+     *                                    not verify.
+     * @throws IOException                if an I/O error occurs while reading the APK file.
      */
     public static X509Certificate[][] verify(String apkFile)
             throws SignatureNotFoundException, SecurityException, IOException {
@@ -386,7 +387,6 @@
                     break;
             }
         }
-        return;
     }
 
     static byte[] getVerityRootHash(String apkPath)
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 510bde1..44168ca 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -21,6 +21,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY;
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
 
+import android.accessibilityservice.AccessibilityService;
 import android.annotation.NonNull;
 import android.graphics.Matrix;
 import android.graphics.Rect;
@@ -46,11 +47,13 @@
 import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.accessibility.AccessibilityRequestPreparer;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -588,6 +591,43 @@
         }
     }
 
+    /**
+     * Take a screenshot using {@link ScreenCapture} of this {@link ViewRootImpl}'s {@link
+     * SurfaceControl}.
+     */
+    public void takeScreenshotOfWindowClientThread(int interactionId,
+            ScreenCapture.ScreenCaptureListener listener,
+            IAccessibilityInteractionConnectionCallback callback) {
+        Message message = PooledLambda.obtainMessage(
+                AccessibilityInteractionController::takeScreenshotOfWindowUiThread,
+                this, interactionId, listener, callback);
+
+        // Screenshot results are returned to the service asynchronously, so the same-thread
+        // message wait logic from #scheduleMessage() is not needed.
+        mHandler.sendMessage(message);
+    }
+
+    private void takeScreenshotOfWindowUiThread(int interactionId,
+            ScreenCapture.ScreenCaptureListener listener,
+            IAccessibilityInteractionConnectionCallback callback) {
+        try {
+            if ((mViewRootImpl.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+                callback.sendTakeScreenshotOfWindowError(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_SECURE_WINDOW, interactionId);
+                return;
+            }
+            final ScreenCapture.LayerCaptureArgs captureArgs =
+                    new ScreenCapture.LayerCaptureArgs.Builder(mViewRootImpl.getSurfaceControl())
+                            .setChildrenOnly(false).setUid(Process.myUid()).build();
+            if (ScreenCapture.captureLayers(captureArgs, listener) != 0) {
+                callback.sendTakeScreenshotOfWindowError(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+            }
+        } catch (RemoteException re) {
+            /* ignore - the other side will time out */
+        }
+    }
+
     public void findFocusClientThread(long accessibilityNodeId, int focusType,
             Region interactiveRegion, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
diff --git a/core/java/android/view/CutoutSpecification.java b/core/java/android/view/CutoutSpecification.java
index f8aa934..3fc3b6a 100644
--- a/core/java/android/view/CutoutSpecification.java
+++ b/core/java/android/view/CutoutSpecification.java
@@ -394,7 +394,6 @@
                 Log.e(TAG, "According to SVG definition, it shouldn't happen");
                 return;
             }
-            spec.trim();
             translateMatrix();
 
             final Path newPath = PathParser.createPathFromPathData(spec);
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 7199e57..a42d3eb 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -319,6 +319,19 @@
     public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 10;
 
     /**
+     * Flag: Indicates that the display maintains its own focus and touch mode.
+     *
+     * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+     * behavior, but only applies to the specific display instead of system-wide to all displays.
+     *
+     * Note: The display must be trusted in order to have its own focus.
+     *
+     * @see #FLAG_TRUSTED
+     * @hide
+     */
+    public static final int FLAG_OWN_FOCUS = 1 << 11;
+
+    /**
      * Display flag: Indicates that the contents of the display should not be scaled
      * to fit the physical screen dimensions.  Used for development only to emulate
      * devices with smaller physicals screens while preserving density.
@@ -996,6 +1009,28 @@
     }
 
     /**
+     * Returns the {@link DisplayShape} which is based on display coordinates.
+     *
+     * To get the {@link DisplayShape} based on the window frame, use
+     * {@link WindowInsets#getDisplayShape()} instead.
+     *
+     * @see DisplayShape
+     */
+    @SuppressLint("VisiblySynchronized")
+    @NonNull
+    public DisplayShape getShape() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            final DisplayShape displayShape = mDisplayInfo.displayShape;
+            final @Surface.Rotation int rotation = getLocalRotation();
+            if (displayShape != null && rotation != mDisplayInfo.rotation) {
+                return displayShape.setRotation(rotation);
+            }
+            return displayShape;
+        }
+    }
+
+    /**
      * Gets the pixel format of the display.
      * @return One of the constants defined in {@link android.graphics.PixelFormat}.
      *
@@ -1291,7 +1326,10 @@
         }
     }
 
-    /** @hide */
+    /**
+     * Returns null if it's virtual display.
+     * @hide
+     */
     @Nullable
     public OverlayProperties getOverlaySupport() {
         synchronized (mLock) {
@@ -1299,7 +1337,7 @@
             if (mDisplayInfo.type != TYPE_VIRTUAL) {
                 return mGlobal.getOverlaySupport();
             }
-            return new OverlayProperties();
+            return null;
         }
     }
 
@@ -1904,13 +1942,16 @@
         private final float mRefreshRate;
         @NonNull
         private final float[] mAlternativeRefreshRates;
+        @NonNull
+        @HdrCapabilities.HdrType
+        private final int[] mSupportedHdrTypes;
 
         /**
          * @hide
          */
         @TestApi
         public Mode(int width, int height, float refreshRate) {
-            this(INVALID_MODE_ID, width, height, refreshRate, new float[0]);
+            this(INVALID_MODE_ID, width, height, refreshRate, new float[0], new int[0]);
         }
 
         /**
@@ -1918,14 +1959,14 @@
          */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public Mode(int modeId, int width, int height, float refreshRate) {
-            this(modeId, width, height, refreshRate, new float[0]);
+            this(modeId, width, height, refreshRate, new float[0], new int[0]);
         }
 
         /**
          * @hide
          */
         public Mode(int modeId, int width, int height, float refreshRate,
-                float[] alternativeRefreshRates) {
+                float[] alternativeRefreshRates, @HdrCapabilities.HdrType int[] supportedHdrTypes) {
             mModeId = modeId;
             mWidth = width;
             mHeight = height;
@@ -1933,6 +1974,8 @@
             mAlternativeRefreshRates =
                     Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length);
             Arrays.sort(mAlternativeRefreshRates);
+            mSupportedHdrTypes = Arrays.copyOf(supportedHdrTypes, supportedHdrTypes.length);
+            Arrays.sort(mSupportedHdrTypes);
         }
 
         /**
@@ -2007,6 +2050,15 @@
         }
 
         /**
+         * Returns the supported {@link HdrCapabilities} HDR_TYPE_* for this specific mode
+         */
+        @NonNull
+        @HdrCapabilities.HdrType
+        public int[] getSupportedHdrTypes() {
+            return mSupportedHdrTypes;
+        }
+
+        /**
          * Returns {@code true} if this mode matches the given parameters.
          *
          * @hide
@@ -2080,7 +2132,8 @@
             }
             Mode that = (Mode) other;
             return mModeId == that.mModeId && matches(that.mWidth, that.mHeight, that.mRefreshRate)
-                    && Arrays.equals(mAlternativeRefreshRates, that.mAlternativeRefreshRates);
+                    && Arrays.equals(mAlternativeRefreshRates, that.mAlternativeRefreshRates)
+                    && Arrays.equals(mSupportedHdrTypes, that.mSupportedHdrTypes);
         }
 
         @Override
@@ -2091,6 +2144,7 @@
             hash = hash * 17 + mHeight;
             hash = hash * 17 + Float.floatToIntBits(mRefreshRate);
             hash = hash * 17 + Arrays.hashCode(mAlternativeRefreshRates);
+            hash = hash * 17 + Arrays.hashCode(mSupportedHdrTypes);
             return hash;
         }
 
@@ -2103,6 +2157,8 @@
                     .append(", fps=").append(mRefreshRate)
                     .append(", alternativeRefreshRates=")
                     .append(Arrays.toString(mAlternativeRefreshRates))
+                    .append(", supportedHdrTypes=")
+                    .append(Arrays.toString(mSupportedHdrTypes))
                     .append("}")
                     .toString();
         }
@@ -2113,7 +2169,8 @@
         }
 
         private Mode(Parcel in) {
-            this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.createFloatArray());
+            this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.createFloatArray(),
+                    in.createIntArray());
         }
 
         @Override
@@ -2123,6 +2180,7 @@
             out.writeInt(mHeight);
             out.writeFloat(mRefreshRate);
             out.writeFloatArray(mAlternativeRefreshRates);
+            out.writeIntArray(mSupportedHdrTypes);
         }
 
         @SuppressWarnings("hiding")
@@ -2288,6 +2346,9 @@
         /**
          * Gets the supported HDR types of this display.
          * Returns empty array if HDR is not supported by the display.
+         *
+         * @deprecated use {@link Display#getMode()}
+         * and {@link Mode#getSupportedHdrTypes()} instead
          */
         public @HdrType int[] getSupportedHdrTypes() {
             return mSupportedHdrTypes;
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 169c8c5..ce7606a0 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -227,8 +227,10 @@
      * timebase.
      * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
      * @param modeId The new mode Id
+     * @param renderPeriod The render frame period, which is a multiple of the mode's vsync period
      */
-    public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
+    public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+            long renderPeriod) {
     }
 
     /**
@@ -303,8 +305,9 @@
 
     // Called from native code.
     @SuppressWarnings("unused")
-    private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
-        onModeChanged(timestampNanos, physicalDisplayId, modeId);
+    private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+            long renderPeriod) {
+        onModeChanged(timestampNanos, physicalDisplayId, modeId, renderPeriod);
     }
 
     // Called from native code.
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 0ba3072..85cb517 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -180,6 +180,16 @@
     public int modeId;
 
     /**
+     * The render frame rate this display is scheduled at, which is a divisor of the active mode
+     * refresh rate. This is the rate SurfaceFlinger would consume frames and would be observable
+     * by applications via the cadence of {@link android.view.Choreographer} callbacks and
+     * by backpressure when submitting buffers as fast as possible.
+     * Apps can call {@link android.view.Display#getRefreshRate} to query this value.
+     *
+     */
+    public float renderFrameRate;
+
+    /**
      * The default display mode.
      */
     public int defaultModeId;
@@ -323,6 +333,9 @@
     @Surface.Rotation
     public int installOrientation;
 
+    @Nullable
+    public DisplayShape displayShape;
+
     public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
         @Override
         public DisplayInfo createFromParcel(Parcel source) {
@@ -373,6 +386,7 @@
                 && Objects.equals(displayCutout, other.displayCutout)
                 && rotation == other.rotation
                 && modeId == other.modeId
+                && renderFrameRate == other.renderFrameRate
                 && defaultModeId == other.defaultModeId
                 && Arrays.equals(supportedModes, other.supportedModes)
                 && colorMode == other.colorMode
@@ -395,7 +409,8 @@
                 && brightnessMaximum == other.brightnessMaximum
                 && brightnessDefault == other.brightnessDefault
                 && Objects.equals(roundedCorners, other.roundedCorners)
-                && installOrientation == other.installOrientation;
+                && installOrientation == other.installOrientation
+                && Objects.equals(displayShape, other.displayShape);
     }
 
     @Override
@@ -424,6 +439,7 @@
         displayCutout = other.displayCutout;
         rotation = other.rotation;
         modeId = other.modeId;
+        renderFrameRate = other.renderFrameRate;
         defaultModeId = other.defaultModeId;
         supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length);
         colorMode = other.colorMode;
@@ -448,6 +464,7 @@
         brightnessDefault = other.brightnessDefault;
         roundedCorners = other.roundedCorners;
         installOrientation = other.installOrientation;
+        displayShape = other.displayShape;
     }
 
     public void readFromParcel(Parcel source) {
@@ -470,6 +487,7 @@
         displayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(source);
         rotation = source.readInt();
         modeId = source.readInt();
+        renderFrameRate = source.readFloat();
         defaultModeId = source.readInt();
         int nModes = source.readInt();
         supportedModes = new Display.Mode[nModes];
@@ -506,6 +524,7 @@
             userDisabledHdrTypes[i] = source.readInt();
         }
         installOrientation = source.readInt();
+        displayShape = source.readTypedObject(DisplayShape.CREATOR);
     }
 
     @Override
@@ -529,6 +548,7 @@
         DisplayCutout.ParcelableWrapper.writeCutoutToParcel(displayCutout, dest, flags);
         dest.writeInt(rotation);
         dest.writeInt(modeId);
+        dest.writeFloat(renderFrameRate);
         dest.writeInt(defaultModeId);
         dest.writeInt(supportedModes.length);
         for (int i = 0; i < supportedModes.length; i++) {
@@ -562,6 +582,7 @@
             dest.writeInt(userDisabledHdrTypes[i]);
         }
         dest.writeInt(installOrientation);
+        dest.writeTypedObject(displayShape, flags);
     }
 
     @Override
@@ -757,6 +778,7 @@
         sb.append(presentationDeadlineNanos);
         sb.append(", mode ");
         sb.append(modeId);
+        sb.append(renderFrameRate);
         sb.append(", defaultMode ");
         sb.append(defaultModeId);
         sb.append(", modes ");
diff --git a/core/java/android/view/DisplayShape.aidl b/core/java/android/view/DisplayShape.aidl
new file mode 100644
index 0000000..af8b417
--- /dev/null
+++ b/core/java/android/view/DisplayShape.aidl
@@ -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.
+ */
+
+package android.view;
+
+parcelable DisplayShape;
diff --git a/core/java/android/view/DisplayShape.java b/core/java/android/view/DisplayShape.java
new file mode 100644
index 0000000..43bd773
--- /dev/null
+++ b/core/java/android/view/DisplayShape.java
@@ -0,0 +1,357 @@
+/*
+ * 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 static android.view.Surface.ROTATION_0;
+
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.DisplayUtils;
+import android.util.PathParser;
+import android.util.RotationUtils;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * A class representing the shape of a display. It provides a {@link Path} of the display shape of
+ * the display shape.
+ *
+ * {@link DisplayShape} is immutable.
+ */
+public final class DisplayShape implements Parcelable {
+
+    /** @hide */
+    public static final DisplayShape NONE = new DisplayShape("" /* displayShapeSpec */,
+            0 /* displayWidth */, 0 /* displayHeight */, 0 /* physicalPixelDisplaySizeRatio */,
+            0 /* rotation */);
+
+    /** @hide */
+    @VisibleForTesting
+    public final String mDisplayShapeSpec;
+    private final float mPhysicalPixelDisplaySizeRatio;
+    private final int mDisplayWidth;
+    private final int mDisplayHeight;
+    private final int mRotation;
+    private final int mOffsetX;
+    private final int mOffsetY;
+    private final float mScale;
+
+    private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight,
+            float physicalPixelDisplaySizeRatio, int rotation) {
+        this(displayShapeSpec, displayWidth, displayHeight, physicalPixelDisplaySizeRatio,
+                rotation, 0, 0, 1f);
+    }
+
+    private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight,
+            float physicalPixelDisplaySizeRatio, int rotation, int offsetX, int offsetY,
+            float scale) {
+        mDisplayShapeSpec = displayShapeSpec;
+        mDisplayWidth = displayWidth;
+        mDisplayHeight = displayHeight;
+        mPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio;
+        mRotation = rotation;
+        mOffsetX = offsetX;
+        mOffsetY = offsetY;
+        mScale = scale;
+    }
+
+    /**
+     * @hide
+     */
+    @NonNull
+    public static DisplayShape fromResources(
+            @NonNull Resources res, @NonNull String displayUniqueId, int physicalDisplayWidth,
+            int physicalDisplayHeight, int displayWidth, int displayHeight) {
+        final boolean isScreenRound = RoundedCorners.getBuiltInDisplayIsRound(res, displayUniqueId);
+        final String spec = getSpecString(res, displayUniqueId);
+        if (spec == null || spec.isEmpty()) {
+            return createDefaultDisplayShape(displayWidth, displayHeight, isScreenRound);
+        }
+        final float physicalPixelDisplaySizeRatio = DisplayUtils.getPhysicalPixelDisplaySizeRatio(
+                physicalDisplayWidth, physicalDisplayHeight, displayWidth, displayHeight);
+        return fromSpecString(spec, physicalPixelDisplaySizeRatio, displayWidth, displayHeight);
+    }
+
+    /**
+     * @hide
+     */
+    @NonNull
+    public static DisplayShape createDefaultDisplayShape(
+            int displayWidth, int displayHeight, boolean isScreenRound) {
+        return fromSpecString(createDefaultSpecString(displayWidth, displayHeight, isScreenRound),
+                1f, displayWidth, displayHeight);
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public static DisplayShape fromSpecString(@NonNull String spec,
+            float physicalPixelDisplaySizeRatio, int displayWidth, int displayHeight) {
+        return Cache.getDisplayShape(spec, physicalPixelDisplaySizeRatio, displayWidth,
+                    displayHeight);
+    }
+
+    private static String createDefaultSpecString(int displayWidth, int displayHeight,
+            boolean isCircular) {
+        final String spec;
+        if (isCircular) {
+            final float xRadius = displayWidth / 2f;
+            final float yRadius = displayHeight / 2f;
+            // Draw a circular display shape.
+            spec = "M0," + yRadius
+                    // Draw upper half circle with arcTo command.
+                    + " A" + xRadius + "," + yRadius + " 0 1,1 " + displayWidth + "," + yRadius
+                    // Draw lower half circle with arcTo command.
+                    + " A" + xRadius + "," + yRadius + " 0 1,1 0," + yRadius + " Z";
+        } else {
+            // Draw a rectangular display shape.
+            spec = "M0,0"
+                    // Draw top edge.
+                    + " L" + displayWidth + ",0"
+                    // Draw right edge.
+                    + " L" + displayWidth + "," + displayHeight
+                    // Draw bottom edge.
+                    + " L0," + displayHeight
+                    // Draw left edge by close command which draws a line from current position to
+                    // the initial points (0,0).
+                    + " Z";
+        }
+        return spec;
+    }
+
+    /**
+     * Gets the display shape svg spec string of a display which is determined by the given display
+     * unique id.
+     *
+     * Loads the default config {@link R.string#config_mainDisplayShape} if
+     * {@link R.array#config_displayUniqueIdArray} is not set.
+     *
+     * @hide
+     */
+    public static String getSpecString(Resources res, String displayUniqueId) {
+        final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+        final TypedArray array = res.obtainTypedArray(R.array.config_displayShapeArray);
+        final String spec;
+        if (index >= 0 && index < array.length()) {
+            spec = array.getString(index);
+        } else {
+            spec = res.getString(R.string.config_mainDisplayShape);
+        }
+        array.recycle();
+        return spec;
+    }
+
+    /**
+     * @hide
+     */
+    public DisplayShape setRotation(int rotation) {
+        return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+                mPhysicalPixelDisplaySizeRatio, rotation, mOffsetX, mOffsetY, mScale);
+    }
+
+    /**
+     * @hide
+     */
+    public DisplayShape setOffset(int offsetX, int offsetY) {
+        return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+                mPhysicalPixelDisplaySizeRatio, mRotation, offsetX, offsetY, mScale);
+    }
+
+    /**
+     * @hide
+     */
+    public DisplayShape setScale(float scale) {
+        return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+                mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, scale);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+                mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, mScale);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof DisplayShape) {
+            DisplayShape ds = (DisplayShape) o;
+            return Objects.equals(mDisplayShapeSpec, ds.mDisplayShapeSpec)
+                    && mDisplayWidth == ds.mDisplayWidth && mDisplayHeight == ds.mDisplayHeight
+                    && mPhysicalPixelDisplaySizeRatio == ds.mPhysicalPixelDisplaySizeRatio
+                    && mRotation == ds.mRotation && mOffsetX == ds.mOffsetX
+                    && mOffsetY == ds.mOffsetY && mScale == ds.mScale;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "DisplayShape{"
+                + " spec=" + mDisplayShapeSpec
+                + " displayWidth=" + mDisplayWidth
+                + " displayHeight=" + mDisplayHeight
+                + " physicalPixelDisplaySizeRatio=" + mPhysicalPixelDisplaySizeRatio
+                + " rotation=" + mRotation
+                + " offsetX=" + mOffsetX
+                + " offsetY=" + mOffsetY
+                + " scale=" + mScale + "}";
+    }
+
+    /**
+     * Returns a {@link Path} of the display shape.
+     *
+     * @return a {@link Path} of the display shape.
+     */
+    @NonNull
+    public Path getPath() {
+        return Cache.getPath(this);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mDisplayShapeSpec);
+        dest.writeInt(mDisplayWidth);
+        dest.writeInt(mDisplayHeight);
+        dest.writeFloat(mPhysicalPixelDisplaySizeRatio);
+        dest.writeInt(mRotation);
+        dest.writeInt(mOffsetX);
+        dest.writeInt(mOffsetY);
+        dest.writeFloat(mScale);
+    }
+
+    public static final @NonNull Creator<DisplayShape> CREATOR = new Creator<DisplayShape>() {
+        @Override
+        public DisplayShape createFromParcel(Parcel in) {
+            final String spec = in.readString8();
+            final int displayWidth = in.readInt();
+            final int displayHeight = in.readInt();
+            final float ratio = in.readFloat();
+            final int rotation = in.readInt();
+            final int offsetX = in.readInt();
+            final int offsetY = in.readInt();
+            final float scale = in.readFloat();
+            return new DisplayShape(spec, displayWidth, displayHeight, ratio, rotation, offsetX,
+                    offsetY, scale);
+        }
+
+        @Override
+        public DisplayShape[] newArray(int size) {
+            return new DisplayShape[size];
+        }
+    };
+
+    private static final class Cache {
+        private static final Object CACHE_LOCK = new Object();
+
+        @GuardedBy("CACHE_LOCK")
+        private static String sCachedSpec;
+        @GuardedBy("CACHE_LOCK")
+        private static int sCachedDisplayWidth;
+        @GuardedBy("CACHE_LOCK")
+        private static int sCachedDisplayHeight;
+        @GuardedBy("CACHE_LOCK")
+        private static float sCachedPhysicalPixelDisplaySizeRatio;
+        @GuardedBy("CACHE_LOCK")
+        private static DisplayShape sCachedDisplayShape;
+
+        @GuardedBy("CACHE_LOCK")
+        private static DisplayShape sCacheForPath;
+        @GuardedBy("CACHE_LOCK")
+        private static Path sCachedPath;
+
+        static DisplayShape getDisplayShape(String spec, float physicalPixelDisplaySizeRatio,
+                int displayWidth, int displayHeight) {
+            synchronized (CACHE_LOCK) {
+                if (spec.equals(sCachedSpec)
+                        && sCachedDisplayWidth == displayWidth
+                        && sCachedDisplayHeight == displayHeight
+                        && sCachedPhysicalPixelDisplaySizeRatio == physicalPixelDisplaySizeRatio) {
+                    return sCachedDisplayShape;
+                }
+            }
+
+            final DisplayShape shape = new DisplayShape(spec, displayWidth, displayHeight,
+                    physicalPixelDisplaySizeRatio, ROTATION_0);
+
+            synchronized (CACHE_LOCK) {
+                sCachedSpec = spec;
+                sCachedDisplayWidth = displayWidth;
+                sCachedDisplayHeight = displayHeight;
+                sCachedPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio;
+                sCachedDisplayShape = shape;
+            }
+            return shape;
+        }
+
+        static Path getPath(@NonNull DisplayShape shape) {
+            synchronized (CACHE_LOCK) {
+                if (shape.equals(sCacheForPath)) {
+                    return sCachedPath;
+                }
+            }
+
+            final Path path = PathParser.createPathFromPathData(shape.mDisplayShapeSpec);
+
+            if (!path.isEmpty()) {
+                final Matrix matrix = new Matrix();
+                if (shape.mRotation != ROTATION_0) {
+                    RotationUtils.transformPhysicalToLogicalCoordinates(
+                            shape.mRotation, shape.mDisplayWidth, shape.mDisplayHeight, matrix);
+                }
+                if (shape.mPhysicalPixelDisplaySizeRatio != 1f) {
+                    matrix.preScale(shape.mPhysicalPixelDisplaySizeRatio,
+                            shape.mPhysicalPixelDisplaySizeRatio);
+                }
+                if (shape.mOffsetX != 0 || shape.mOffsetY != 0) {
+                    matrix.postTranslate(shape.mOffsetX, shape.mOffsetY);
+                }
+                if (shape.mScale != 1f) {
+                    matrix.postScale(shape.mScale, shape.mScale);
+                }
+                path.transform(matrix);
+            }
+
+            synchronized (CACHE_LOCK) {
+                sCacheForPath = shape;
+                sCachedPath = path;
+            }
+            return path;
+        }
+    }
+}
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/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
index 0769f12..91270d4 100644
--- a/core/java/android/view/IDisplayWindowInsetsController.aidl
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -19,6 +19,7 @@
 import android.content.ComponentName;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.inputmethod.ImeTracker;
 
 /**
  * Singular controller of insets to use when there isn't another obvious controller available.
@@ -48,10 +49,10 @@
     /**
      * @see IWindow#showInsets
      */
-    void showInsets(int types, boolean fromIme);
+    void showInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
 
     /**
      * @see IWindow#hideInsets
      */
-    void hideInsets(int types, boolean fromIme);
+    void hideInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
 }
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index a856474..8e16f24 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -29,6 +29,7 @@
 import android.view.IScrollCaptureResponseListener;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.inputmethod.ImeTracker;
 import android.window.ClientWindowFrames;
 
 import com.android.internal.os.IResultReceiver;
@@ -68,16 +69,18 @@
      *
      * @param types internal insets types (WindowInsets.Type.InsetsType) to show
      * @param fromIme true if this request originated from IME (InputMethodService).
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      */
-    void showInsets(int types, boolean fromIme);
+    void showInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
 
     /**
      * Called when a set of insets source window should be hidden by policy.
      *
      * @param types internal insets types (WindowInsets.Type.InsetsType) to hide
      * @param fromIme true if this request originated from IME (InputMethodService).
+     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
      */
-    void hideInsets(int types, boolean fromIme);
+    void hideInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
 
     void moved(int newX, int newY);
     void dispatchAppVisibility(boolean visible);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index e2bc566..6d9f99f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -53,7 +53,6 @@
 import android.view.KeyEvent;
 import android.view.InputEvent;
 import android.view.InsetsState;
-import android.view.InsetsVisibilities;
 import android.view.MagnificationSpec;
 import android.view.MotionEvent;
 import android.view.InputChannel;
@@ -738,9 +737,8 @@
      * If invoked through a package other than a launcher app, returns an empty list.
      *
      * @param displayId the id of the logical display
-     * @param packageName the name of the calling package
      */
-    List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName);
+    List<DisplayInfo> getPossibleDisplayInfo(int displayId);
 
     /**
      * Called to show global actions.
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 332e97c..02e0fcc 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -65,7 +65,7 @@
     public void onWindowFocusGained(boolean hasViewFocus) {
         super.onWindowFocusGained(hasViewFocus);
         getImm().registerImeConsumer(this);
-        if (isRequestedVisible() && getControl() == null) {
+        if ((mController.getRequestedVisibleTypes() & getType()) != 0 && getControl() == null) {
             mIsRequestedVisibleAwaitingControl = true;
         }
     }
@@ -125,7 +125,7 @@
         // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching
         // this code here means that we now got control, so we can start the animation immediately.
         // If client window is trying to control IME and IME is already visible, it is immediate.
-        if (fromIme || mState.getSource(getType()).isVisible() && getControl() != null) {
+        if (fromIme || (mState.getSource(getInternalType()).isVisible() && getControl() != null)) {
             return ShowResult.SHOW_IMMEDIATELY;
         }
 
@@ -169,7 +169,7 @@
 
     @Override
     protected boolean isRequestedVisibleAwaitingControl() {
-        return mIsRequestedVisibleAwaitingControl || isRequestedVisible();
+        return super.isRequestedVisibleAwaitingControl() || mIsRequestedVisibleAwaitingControl;
     }
 
     @Override
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 799955b..3d7843c 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -219,8 +219,9 @@
 
     /**
      * The input source is a mouse pointing device.
-     * This code is also used for other mouse-like pointing devices such as trackpads
-     * and trackpoints.
+     * This value is also used for other mouse-like pointing devices such as touchpads and pointing
+     * sticks. When used in combination with {@link #SOURCE_STYLUS}, it denotes an external drawing
+     * tablet.
      *
      * @see #SOURCE_CLASS_POINTER
      */
@@ -291,8 +292,8 @@
     public static final int SOURCE_MOUSE_RELATIVE = 0x00020000 | SOURCE_CLASS_TRACKBALL;
 
     /**
-     * The input source is a touch pad or digitizer tablet that is not
-     * associated with a display (unlike {@link #SOURCE_TOUCHSCREEN}).
+     * The input source is a touchpad (also known as a trackpad). Touchpads that are used to move
+     * the mouse cursor will also have {@link #SOURCE_MOUSE}.
      *
      * @see #SOURCE_CLASS_POSITION
      */
@@ -779,7 +780,7 @@
      * same input device descriptor.  This might happen in situations where a single
      * human input device registers multiple {@link InputDevice} instances (HID collections)
      * that describe separate features of the device, such as a keyboard that also
-     * has a trackpad.  Alternately, it may be that the input devices are simply
+     * has a touchpad.  Alternately, it may be that the input devices are simply
      * indistinguishable, such as two keyboards made by the same manufacturer.
      * </p><p>
      * The input device descriptor returned by {@link #getDescriptor} should only be
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 805727c..e775969 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -58,6 +58,7 @@
 import android.view.WindowInsetsAnimation.Bounds;
 import android.view.WindowManager.LayoutParams;
 import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -68,7 +69,7 @@
  * Implements {@link WindowInsetsAnimationController}
  * @hide
  */
-@VisibleForTesting
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class InsetsAnimationControlImpl implements InternalInsetsAnimationController,
         InsetsAnimationControlRunner {
 
@@ -96,6 +97,8 @@
     /** @see WindowInsetsAnimationController#hasZeroInsetsIme */
     private final boolean mHasZeroInsetsIme;
     private final CompatibilityInfo.Translator mTranslator;
+    @Nullable
+    private final ImeTracker.Token mStatsToken;
     private Insets mCurrentInsets;
     private Insets mPendingInsets;
     private float mPendingFraction;
@@ -114,7 +117,7 @@
             @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
             Interpolator interpolator, @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
-            CompatibilityInfo.Translator translator) {
+            CompatibilityInfo.Translator translator, @Nullable ImeTracker.Token statsToken) {
         mControls = controls;
         mListener = listener;
         mTypes = types;
@@ -128,7 +131,7 @@
                     null /* typeSideMap */);
             mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */,
                     typeSideMap);
-            mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME);
+            mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsType(WindowInsets.Type.ime());
             if (mHasZeroInsetsIme) {
                 // IME has shownInsets of ZERO, and can't map to a side by default.
                 // Map zero insets IME to bottom, making it a special case of bottom insets.
@@ -141,7 +144,7 @@
             mCurrentInsets = calculateInsets(mInitialInsetsState, controls, true /* shown */);
             mHiddenInsets = calculateInsets(null, controls, false /* shown */);
             mShownInsets = calculateInsets(null, controls, true /* shown */);
-            mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME);
+            mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsType(WindowInsets.Type.ime());
             buildSideControlsMap(mSideControlsMap, controls);
         }
         mPendingInsets = mCurrentInsets;
@@ -152,6 +155,7 @@
         mAnimationType = animationType;
         mLayoutInsetsDuringAnimation = layoutInsetsDuringAnimation;
         mTranslator = translator;
+        mStatsToken = statsToken;
         mController.startAnimation(this, listener, types, mAnimation,
                 new Bounds(mHiddenInsets, mShownInsets));
     }
@@ -228,6 +232,11 @@
     }
 
     @Override
+    public ImeTracker.Token getStatsToken() {
+        return mStatsToken;
+    }
+
+    @Override
     public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
         setInsetsAndAlpha(insets, alpha, fraction, false /* allowWhenFinished */);
     }
@@ -253,10 +262,10 @@
         }
     }
 
-    @VisibleForTesting
     /**
      * @return Whether the finish callback of this animation should be invoked.
      */
+    @VisibleForTesting
     public boolean applyChangeInsets(@Nullable InsetsState outState) {
         if (mCancelled) {
             if (DEBUG) Log.d(TAG, "applyChangeInsets canceled");
diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java
index 1cb00e3..cf40e7e 100644
--- a/core/java/android/view/InsetsAnimationControlRunner.java
+++ b/core/java/android/view/InsetsAnimationControlRunner.java
@@ -16,11 +16,12 @@
 
 package android.view;
 
+import android.annotation.Nullable;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsController.AnimationType;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
 
 /**
  * Interface representing a runner for an insets animation.
@@ -63,10 +64,10 @@
     WindowInsetsAnimation getAnimation();
 
     /**
-     * @return Whether {@link #getTypes()} maps to a specific {@link InternalInsetsType}.
+     * @return Whether {@link #getTypes()} contains a specific {@link InsetsType}.
      */
-    default boolean controlsInternalType(@InternalInsetsType int type) {
-        return InsetsState.toInternalType(getTypes()).contains(type);
+    default boolean controlsType(@InsetsType int type) {
+        return (getTypes() & type) != 0;
     }
 
     /**
@@ -75,6 +76,12 @@
     @AnimationType int getAnimationType();
 
     /**
+     * @return The token tracking the current IME request or {@code null} otherwise.
+     */
+    @Nullable
+    ImeTracker.Token getStatsToken();
+
+    /**
      *
      * Export the state of classes that implement this interface into a protocol buffer
      * output stream.
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index fc97541..f7b9aa2 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -34,6 +34,7 @@
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation.Bounds;
 import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
 
 /**
  * Insets animation runner that uses {@link InsetsAnimationThread} to run the animation off from the
@@ -112,12 +113,13 @@
             @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
             Interpolator interpolator, @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
-            CompatibilityInfo.Translator translator, Handler mainThreadHandler) {
+            CompatibilityInfo.Translator translator, Handler mainThreadHandler,
+            @Nullable ImeTracker.Token statsToken) {
         mMainThreadHandler = mainThreadHandler;
         mOuterCallbacks = controller;
         mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types,
                 mCallbacks, durationMs, interpolator, animationType, layoutInsetsDuringAnimation,
-                translator);
+                translator, statsToken);
         InsetsAnimationThread.getHandler().post(() -> {
             if (mControl.isCancelled()) {
                 return;
@@ -141,6 +143,11 @@
     }
 
     @Override
+    public ImeTracker.Token getStatsToken() {
+        return mControl.getStatsToken();
+    }
+
+    @Override
     @UiThread
     public int getTypes() {
         return mControl.getTypes();
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8b38e9e..fbd8226 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -24,6 +24,8 @@
 import static android.view.InsetsState.toInternalType;
 import static android.view.InsetsState.toPublicType;
 import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
+import static android.view.WindowInsets.Type.FIRST;
+import static android.view.WindowInsets.Type.LAST;
 import static android.view.WindowInsets.Type.all;
 import static android.view.WindowInsets.Type.ime;
 
@@ -42,6 +44,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Trace;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
@@ -58,6 +61,7 @@
 import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
 import android.view.animation.PathInterpolator;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -744,8 +748,8 @@
         for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
             InsetsSource source = newState.peekSource(type);
             if (source == null) continue;
-            @AnimationType int animationType = getAnimationType(type);
             @InsetsType int insetsType = toPublicType(type);
+            @AnimationType int animationType = getAnimationType(insetsType);
             if (!source.isUserControllable()) {
                 // The user animation is not allowed when visible frame is empty.
                 disabledUserAnimationTypes |= insetsType;
@@ -788,8 +792,7 @@
         if (diff != 0) {
             for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
                 InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
-                if (consumer.getControl() != null
-                        && (toPublicType(consumer.getType()) & diff) != 0) {
+                if (consumer.getControl() != null && (consumer.getType() & diff) != 0) {
                     mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners);
                     mHandler.post(mInvokeControllableInsetsChangedListeners);
                     break;
@@ -897,7 +900,7 @@
         // Ensure to update all existing source consumers
         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
             final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
-            final InsetsSourceControl control = mTmpControlArray.get(consumer.getType());
+            final InsetsSourceControl control = mTmpControlArray.get(consumer.getInternalType());
 
             // control may be null, but we still need to update the control to null if it got
             // revoked.
@@ -928,10 +931,12 @@
         hideTypes[0] &= ~animatingTypes;
 
         if (showTypes[0] != 0) {
-            applyAnimation(showTypes[0], true /* show */, false /* fromIme */);
+            applyAnimation(showTypes[0], true /* show */, false /* fromIme */,
+                    null /* statsToken */);
         }
         if (hideTypes[0] != 0) {
-            applyAnimation(hideTypes[0], false /* show */, false /* fromIme */);
+            applyAnimation(hideTypes[0], false /* show */, false /* fromIme */,
+                    null /* statsToken */);
         }
 
         if (mControllableTypes != controllableTypes) {
@@ -947,11 +952,12 @@
 
     @Override
     public void show(@InsetsType int types) {
-        show(types, false /* fromIme */);
+        show(types, false /* fromIme */, null /* statsToken */);
     }
 
-    @VisibleForTesting
-    public void show(@InsetsType int types, boolean fromIme) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void show(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
         if ((types & ime()) != 0) {
             Log.d(TAG, "show(ime(), fromIme=" + fromIme + ")");
         }
@@ -978,44 +984,57 @@
                     true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator,
                     pendingRequest.animationType,
                     pendingRequest.layoutInsetsDuringAnimation,
-                    pendingRequest.useInsetsAnimationThread);
+                    pendingRequest.useInsetsAnimationThread, statsToken);
             return;
         }
 
         // TODO: Support a ResultReceiver for IME.
         // TODO(b/123718661): Make show() work for multi-session IME.
         int typesReady = 0;
-        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
-        for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            @InternalInsetsType int internalType = internalTypes.valueAt(i);
-            @AnimationType int animationType = getAnimationType(internalType);
-            InsetsSourceConsumer consumer = getSourceConsumer(internalType);
-            if (consumer.isRequestedVisible() && animationType == ANIMATION_TYPE_NONE
+        for (int type = FIRST; type <= LAST; type = type << 1) {
+            if ((types & type) == 0) {
+                continue;
+            }
+            @AnimationType final int animationType = getAnimationType(type);
+            final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
+            final boolean isImeAnimation = type == ime();
+            if (requestedVisible && animationType == ANIMATION_TYPE_NONE
                     || animationType == ANIMATION_TYPE_SHOW) {
                 // no-op: already shown or animating in (because window visibility is
                 // applied before starting animation).
                 if (DEBUG) Log.d(TAG, String.format(
                         "show ignored for type: %d animType: %d requestedVisible: %s",
-                        consumer.getType(), animationType, consumer.isRequestedVisible()));
+                        type, animationType, requestedVisible));
+                if (isImeAnimation) {
+                    ImeTracker.get().onCancelled(statsToken,
+                            ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+                }
                 continue;
             }
             if (fromIme && animationType == ANIMATION_TYPE_USER) {
                 // App is already controlling the IME, don't cancel it.
+                if (isImeAnimation) {
+                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+                }
                 continue;
             }
-            typesReady |= InsetsState.toPublicType(consumer.getType());
+            if (isImeAnimation) {
+                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+            }
+            typesReady |= type;
         }
         if (DEBUG) Log.d(TAG, "show typesReady: " + typesReady);
-        applyAnimation(typesReady, true /* show */, fromIme);
+        applyAnimation(typesReady, true /* show */, fromIme, statsToken);
     }
 
     @Override
     public void hide(@InsetsType int types) {
-        hide(types, false /* fromIme */);
+        hide(types, false /* fromIme */, null /* statsToken */);
     }
 
     @VisibleForTesting
-    public void hide(@InsetsType int types, boolean fromIme) {
+    public void hide(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
         if (fromIme) {
             ImeTracing.getInstance().triggerClientDump("InsetsController#hide",
                     mHost.getInputMethodManager(), null /* icProto */);
@@ -1024,19 +1043,29 @@
             Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
         }
         int typesReady = 0;
-        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
-        for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            @InternalInsetsType int internalType = internalTypes.valueAt(i);
-            @AnimationType int animationType = getAnimationType(internalType);
-            InsetsSourceConsumer consumer = getSourceConsumer(internalType);
-            if (!consumer.isRequestedVisible() && animationType == ANIMATION_TYPE_NONE
-                    || animationType == ANIMATION_TYPE_HIDE) {
-                // no-op: already hidden or animating out.
+        for (int type = FIRST; type <= LAST; type = type << 1) {
+            if ((types & type) == 0) {
                 continue;
             }
-            typesReady |= InsetsState.toPublicType(consumer.getType());
+            @AnimationType final int animationType = getAnimationType(type);
+            final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
+            final boolean isImeAnimation = type == ime();
+            if (!requestedVisible && animationType == ANIMATION_TYPE_NONE
+                    || animationType == ANIMATION_TYPE_HIDE) {
+                // no-op: already hidden or animating out (because window visibility is
+                // applied before starting animation).
+                if (isImeAnimation) {
+                    ImeTracker.get().onCancelled(statsToken,
+                            ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+                }
+                continue;
+            }
+            if (isImeAnimation) {
+                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+            }
+            typesReady |= type;
         }
-        applyAnimation(typesReady, false /* show */, fromIme /* fromIme */);
+        applyAnimation(typesReady, false /* show */, fromIme, statsToken);
     }
 
     @Override
@@ -1065,7 +1094,7 @@
 
         controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
                 interpolator, animationType, getLayoutInsetsDuringAnimationMode(types),
-                false /* useInsetsAnimationThread */);
+                false /* useInsetsAnimationThread */, null /* statsToken */);
     }
 
     private void controlAnimationUnchecked(@InsetsType int types,
@@ -1074,7 +1103,8 @@
             long durationMs, Interpolator interpolator,
             @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
-            boolean useInsetsAnimationThread) {
+            boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
         if ((types & mTypesBeingCancelled) != 0) {
             throw new IllegalStateException("Cannot start a new insets animation of "
                     + Type.toString(types)
@@ -1149,14 +1179,16 @@
                 ? new InsetsAnimationThreadControlRunner(controls,
                         frame, mState, listener, typesReady, this, durationMs, interpolator,
                         animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
-                        mHost.getHandler())
+                        mHost.getHandler(), statsToken)
                 : new InsetsAnimationControlImpl(controls,
                         frame, mState, listener, typesReady, this, durationMs, interpolator,
-                        animationType, layoutInsetsDuringAnimation, mHost.getTranslator());
+                        animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
+                        statsToken);
         if ((typesReady & WindowInsets.Type.ime()) != 0) {
             ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl",
                     mHost.getInputMethodManager(), null /* icProto */);
         }
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING);
         mRunningAnimations.add(new RunningAnimation(runner, animationType));
         if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: "
                 + useInsetsAnimationThread);
@@ -1228,13 +1260,13 @@
             if (!canRun) {
                 if (WARN) Log.w(TAG, String.format(
                         "collectSourceControls can't continue show for type: %s fromIme: %b",
-                        InsetsState.typeToString(consumer.getType()), fromIme));
+                        InsetsState.typeToString(consumer.getInternalType()), fromIme));
                 continue;
             }
             final InsetsSourceControl control = consumer.getControl();
             if (control != null && control.getLeash() != null) {
-                controls.put(consumer.getType(), new InsetsSourceControl(control));
-                typesReady |= toPublicType(consumer.getType());
+                controls.put(control.getType(), new InsetsSourceControl(control));
+                typesReady |= consumer.getType();
             } else if (animationType == ANIMATION_TYPE_SHOW) {
                 if (DEBUG) Log.d(TAG, "collectSourceControls no control for show(). fromIme: "
                         + fromIme);
@@ -1260,25 +1292,15 @@
 
     private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode(
             @InsetsType int types) {
-
-        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
-
         // Generally, we want to layout the opposite of the current state. This is to make animation
         // callbacks easy to use: The can capture the layout values and then treat that as end-state
         // during the animation.
         //
         // However, if controlling multiple sources, we want to treat it as shown if any of the
         // types is currently hidden.
-        for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            InsetsSourceConsumer consumer = mSourceConsumers.get(internalTypes.valueAt(i));
-            if (consumer == null) {
-                continue;
-            }
-            if (!consumer.isRequestedVisible()) {
-                return LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
-            }
-        }
-        return LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+        return (mRequestedVisibleTypes & types) != types
+                ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
+                : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
     }
 
     private void cancelExistingControllers(@InsetsType int types) {
@@ -1318,11 +1340,18 @@
             // requested visibility.
             return;
         }
+        final ImeTracker.Token statsToken = runner.getStatsToken();
         if (shown) {
+            ImeTracker.get().onProgress(statsToken,
+                    ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW);
             showDirectly(runner.getTypes(), true /* fromIme */);
+            ImeTracker.get().onShown(statsToken);
         } else {
+            ImeTracker.get().onProgress(statsToken,
+                    ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_HIDE);
             hideDirectly(runner.getTypes(), true /* animationFinished */,
                     runner.getAnimationType(), true /* fromIme */);
+            ImeTracker.get().onHidden(statsToken);
         }
     }
 
@@ -1332,24 +1361,33 @@
     }
 
     void notifyControlRevoked(InsetsSourceConsumer consumer) {
-        final @InsetsType int types = toPublicType(consumer.getType());
+        final @InsetsType int type = consumer.getType();
         for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
             InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
-            control.notifyControlRevoked(types);
+            control.notifyControlRevoked(type);
             if (control.getControllingTypes() == 0) {
                 cancelAnimation(control, true /* invokeCallback */);
             }
         }
-        if (consumer.getType() == ITYPE_IME) {
+        if (type == ime()) {
             abortPendingImeControlRequest();
         }
     }
 
     private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) {
-        if (DEBUG) Log.d(TAG, String.format("cancelAnimation of types: %d, animType: %d, host: %s",
-                control.getTypes(), control.getAnimationType(), mHost.getRootViewTitle()));
         if (invokeCallback) {
+            ImeTracker.get().onCancelled(control.getStatsToken(),
+                    ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
             control.cancel();
+        } else {
+            // Succeeds if invokeCallback is false (i.e. when called from notifyFinished).
+            ImeTracker.get().onProgress(control.getStatsToken(),
+                    ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
+        }
+        if (DEBUG) {
+            Log.d(TAG, TextUtils.formatSimple(
+                    "cancelAnimation of types: %d, animType: %d, host: %s",
+                    control.getTypes(), control.getAnimationType(), mHost.getRootViewTitle()));
         }
         boolean stateChanged = false;
         for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
@@ -1425,27 +1463,25 @@
     }
 
     @VisibleForTesting
-    public @AnimationType int getAnimationType(@InternalInsetsType int type) {
+    public @AnimationType int getAnimationType(@InsetsType int type) {
         for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
             InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
-            if (control.controlsInternalType(type)) {
+            if (control.controlsType(type)) {
                 return mRunningAnimations.get(i).type;
             }
         }
         return ANIMATION_TYPE_NONE;
     }
 
-    @VisibleForTesting
-    public void onRequestedVisibilityChanged(InsetsSourceConsumer consumer) {
-        final @InsetsType int type = InsetsState.toPublicType(consumer.getType());
-        final int requestedVisibleTypes = consumer.isRequestedVisible()
-                ? mRequestedVisibleTypes | type
-                : mRequestedVisibleTypes & ~type;
+    void setRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
+        final @InsetsType int requestedVisibleTypes =
+                (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
         if (mRequestedVisibleTypes != requestedVisibleTypes) {
-            mRequestedVisibleTypes = requestedVisibleTypes;
-            if (WindowInsets.Type.hasCompatSystemBars(type)) {
+            if (WindowInsets.Type.hasCompatSystemBars(
+                    mRequestedVisibleTypes ^ requestedVisibleTypes)) {
                 mCompatSysUiVisibilityStaled = true;
             }
+            mRequestedVisibleTypes = requestedVisibleTypes;
         }
     }
 
@@ -1461,7 +1497,8 @@
     }
 
     @VisibleForTesting
-    public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme) {
+    public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
         // TODO(b/166736352): We should only skip the animation of specific types, not all types.
         boolean skipAnim = false;
         if ((types & ime()) != 0) {
@@ -1474,12 +1511,12 @@
                         && consumer.hasViewFocusWhenWindowFocusGain();
             }
         }
-        applyAnimation(types, show, fromIme, skipAnim);
+        applyAnimation(types, show, fromIme, skipAnim, statsToken);
     }
 
     @VisibleForTesting
     public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme,
-            boolean skipAnim) {
+            boolean skipAnim, @Nullable ImeTracker.Token statsToken) {
         if (types == 0) {
             // nothing to animate.
             if (DEBUG) Log.d(TAG, "applyAnimation, nothing to animate");
@@ -1499,12 +1536,11 @@
                 listener.getDurationMs(), listener.getInsetsInterpolator(),
                 show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
                 show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
-                !hasAnimationCallbacks /* useInsetsAnimationThread */);
+                !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken);
     }
 
-    private void hideDirectly(
-            @InsetsType int types, boolean animationFinished, @AnimationType int animationType,
-            boolean fromIme) {
+    private void hideDirectly(@InsetsType int types, boolean animationFinished,
+            @AnimationType int animationType, boolean fromIme) {
         if ((types & ime()) != 0) {
             ImeTracing.getInstance().triggerClientDump("InsetsController#hideDirectly",
                     mHost.getInputMethodManager(), null /* icProto */);
@@ -1664,9 +1700,9 @@
         @InsetsType int result = 0;
         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
             InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
-            InsetsSource source = mState.peekSource(consumer.mType);
+            InsetsSource source = mState.peekSource(consumer.getInternalType());
             if (consumer.getControl() != null && source != null && source.isUserControllable()) {
-                result |= toPublicType(consumer.mType);
+                result |= consumer.getType();
             }
         }
         return result & ~mState.calculateUncontrollableInsetsFromFrame(mFrame);
@@ -1707,12 +1743,11 @@
     }
 
     @Override
-    public void reportPerceptible(int types, boolean perceptible) {
-        final ArraySet<Integer> internalTypes = toInternalType(types);
+    public void reportPerceptible(@InsetsType int types, boolean perceptible) {
         final int size = mSourceConsumers.size();
         for (int i = 0; i < size; i++) {
             final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
-            if (internalTypes.contains(consumer.getType())) {
+            if ((consumer.getType() & types) != 0) {
                 consumer.onPerceptible(perceptible);
             }
         }
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index da54da16..58ee59d 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -31,6 +31,10 @@
  *
  * The insets frame will by default as the window frame size. If the providers are set, the
  * calculation result based on the source size will be used as the insets frame.
+ *
+ * The InsetsFrameProvider should be self-contained. Nothing describing the window itself, such as
+ * contentInsets, visibleInsets, etc. won't affect the insets providing to other windows when this
+ * is set.
  * @hide
  */
 public class InsetsFrameProvider implements Parcelable {
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index edcfc95..778c677 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -37,6 +37,7 @@
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation.Bounds;
 import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
 
 /**
  * Runs a fake animation of resizing insets to produce insets animation callbacks.
@@ -92,6 +93,12 @@
     }
 
     @Override
+    public ImeTracker.Token getStatsToken() {
+        // Return null as resizing the IME view is not explicitly tracked.
+        return null;
+    }
+
+    @Override
     public void cancel() {
         if (mCancelled || mFinished) {
             return;
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 7a498ad..21c0395 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -27,7 +27,6 @@
 import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.getDefaultVisibility;
-import static android.view.InsetsState.toPublicType;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
@@ -74,9 +73,9 @@
     }
 
     protected final InsetsController mController;
-    protected boolean mRequestedVisible;
     protected final InsetsState mState;
-    protected final @InternalInsetsType int mType;
+    private final @InternalInsetsType int mInternalType;
+    private final @InsetsType int mType;
 
     private static final String TAG = "InsetsSourceConsumer";
     private final Supplier<Transaction> mTransactionSupplier;
@@ -99,11 +98,11 @@
      */
     public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state,
             Supplier<Transaction> transactionSupplier, InsetsController controller) {
-        mType = type;
+        mType = InsetsState.toPublicType(type);
+        mInternalType = type;
         mState = state;
         mTransactionSupplier = transactionSupplier;
         mController = controller;
-        mRequestedVisible = getDefaultVisibility(type);
     }
 
     /**
@@ -117,7 +116,7 @@
      */
     public boolean setControl(@Nullable InsetsSourceControl control,
             @InsetsType int[] showTypes, @InsetsType int[] hideTypes) {
-        if (mType == ITYPE_IME) {
+        if (mInternalType == ITYPE_IME) {
             ImeTracing.getInstance().triggerClientDump("InsetsSourceConsumer#setControl",
                     mController.getHost().getInputMethodManager(), null /* icProto */);
         }
@@ -141,9 +140,10 @@
             mController.notifyControlRevoked(this);
 
             // Check if we need to restore server visibility.
-            final InsetsSource source = mState.getSource(mType);
+            final InsetsSource source = mState.getSource(mInternalType);
             final boolean serverVisibility =
-                    mController.getLastDispatchedState().getSourceOrDefaultVisibility(mType);
+                    mController.getLastDispatchedState().getSourceOrDefaultVisibility(
+                            mInternalType);
             if (source.isVisible() != serverVisibility) {
                 source.setVisible(serverVisibility);
                 mController.notifyVisibilityChanged();
@@ -159,9 +159,9 @@
                 if (DEBUG) Log.d(TAG, String.format("Gaining leash in %s, requestedVisible: %b",
                         mController.getHost().getRootViewTitle(), requestedVisible));
                 if (requestedVisible) {
-                    showTypes[0] |= toPublicType(getType());
+                    showTypes[0] |= mType;
                 } else {
-                    hideTypes[0] |= toPublicType(getType());
+                    hideTypes[0] |= mType;
                 }
             } else {
                 // We are gaining control, but don't need to run an animation.
@@ -172,7 +172,7 @@
 
                 // If we have a new leash, make sure visibility is up-to-date, even though we
                 // didn't want to run an animation above.
-                if (mController.getAnimationType(control.getType()) == ANIMATION_TYPE_NONE) {
+                if (mController.getAnimationType(mType) == ANIMATION_TYPE_NONE) {
                     applyRequestedVisibilityToControl();
                 }
 
@@ -195,29 +195,32 @@
 
     /**
      * Determines if the consumer will be shown after control is available.
-     * Note: for system bars this method is same as {@link #isRequestedVisible()}.
      *
      * @return {@code true} if consumer has a pending show.
      */
     protected boolean isRequestedVisibleAwaitingControl() {
-        return isRequestedVisible();
+        return (mController.getRequestedVisibleTypes() & mType) != 0;
     }
 
-    int getType() {
+    @InsetsType int getType() {
         return mType;
     }
 
+    @InternalInsetsType int getInternalType() {
+        return mInternalType;
+    }
+
     @VisibleForTesting
     public void show(boolean fromIme) {
         if (DEBUG) Log.d(TAG, String.format("Call show() for type: %s fromIme: %b ",
-                InsetsState.typeToString(mType), fromIme));
+                InsetsState.typeToString(mInternalType), fromIme));
         setRequestedVisible(true);
     }
 
     @VisibleForTesting
     public void hide() {
         if (DEBUG) Log.d(TAG, String.format("Call hide for %s on %s",
-                InsetsState.typeToString(mType), mController.getHost().getRootViewTitle()));
+                InsetsState.typeToString(mInternalType), mController.getHost().getRootViewTitle()));
         setRequestedVisible(false);
     }
 
@@ -245,11 +248,13 @@
     }
 
     boolean applyLocalVisibilityOverride() {
-        final InsetsSource source = mState.peekSource(mType);
-        final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(mType);
+        final InsetsSource source = mState.peekSource(mInternalType);
+        final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(
+                mInternalType);
         final boolean hasControl = mSourceControl != null;
+        final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
 
-        if (mType == ITYPE_IME) {
+        if (mInternalType == ITYPE_IME) {
             ImeTracing.getInstance().triggerClientDump(
                     "InsetsSourceConsumer#applyLocalVisibilityOverride",
                     mController.getHost().getInputMethodManager(), null /* icProto */);
@@ -259,23 +264,18 @@
         if (!hasControl) {
             if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in "
                     + mController.getHost().getRootViewTitle()
-                    + " requestedVisible " + mRequestedVisible);
+                    + " requestedVisible=" + requestedVisible);
             return false;
         }
-        if (isVisible == mRequestedVisible) {
+        if (isVisible == requestedVisible) {
             return false;
         }
         if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b",
-                mController.getHost().getRootViewTitle(), mRequestedVisible));
-        mState.getSource(mType).setVisible(mRequestedVisible);
+                mController.getHost().getRootViewTitle(), requestedVisible));
+        mState.getSource(mInternalType).setVisible(requestedVisible);
         return true;
     }
 
-    @VisibleForTesting
-    public boolean isRequestedVisible() {
-        return mRequestedVisible;
-    }
-
     /**
      * Request to show current window type.
      *
@@ -314,7 +314,7 @@
 
     @VisibleForTesting(visibility = PACKAGE)
     public void updateSource(InsetsSource newSource, @AnimationType int animationType) {
-        InsetsSource source = mState.peekSource(mType);
+        InsetsSource source = mState.peekSource(mInternalType);
         if (source == null || animationType == ANIMATION_TYPE_NONE
                 || source.getFrame().equals(newSource.getFrame())) {
             mPendingFrame = null;
@@ -339,7 +339,7 @@
     @VisibleForTesting(visibility = PACKAGE)
     public boolean notifyAnimationFinished() {
         if (mPendingFrame != null) {
-            InsetsSource source = mState.getSource(mType);
+            InsetsSource source = mState.getSource(mInternalType);
             source.setFrame(mPendingFrame);
             source.setVisibleFrame(mPendingVisibleFrame);
             mPendingFrame = null;
@@ -354,11 +354,8 @@
      * the moment.
      */
     protected void setRequestedVisible(boolean requestedVisible) {
-        if (mRequestedVisible != requestedVisible) {
-            mRequestedVisible = requestedVisible;
-            mController.onRequestedVisibilityChanged(this);
-            if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible);
-        }
+        mController.setRequestedVisibleTypes(requestedVisible ? mType : 0, mType);
+        if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible);
         if (applyLocalVisibilityOverride()) {
             mController.notifyVisibilityChanged();
         }
@@ -369,25 +366,26 @@
             return;
         }
 
+        final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
         try (Transaction t = mTransactionSupplier.get()) {
-            if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + mRequestedVisible);
-            if (mRequestedVisible) {
+            if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + requestedVisible);
+            if (requestedVisible) {
                 t.show(mSourceControl.getLeash());
             } else {
                 t.hide(mSourceControl.getLeash());
             }
             // Ensure the alpha value is aligned with the actual requested visibility.
-            t.setAlpha(mSourceControl.getLeash(), mRequestedVisible ? 1 : 0);
+            t.setAlpha(mSourceControl.getLeash(), requestedVisible ? 1 : 0);
             t.apply();
         }
-        onPerceptible(mRequestedVisible);
+        onPerceptible(requestedVisible);
     }
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mType));
+        proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mInternalType));
         proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus);
-        proto.write(IS_REQUESTED_VISIBLE, mRequestedVisible);
+        proto.write(IS_REQUESTED_VISIBLE, (mController.getRequestedVisibleTypes() & mType) != 0);
         if (mSourceControl != null) {
             mSourceControl.dumpDebug(proto, SOURCE_CONTROL);
         }
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index e91839b..c56d618 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -197,6 +197,9 @@
     private PrivacyIndicatorBounds mPrivacyIndicatorBounds =
             new PrivacyIndicatorBounds();
 
+    /** The display shape */
+    private DisplayShape mDisplayShape = DisplayShape.NONE;
+
     public InsetsState() {
     }
 
@@ -271,6 +274,7 @@
                 alwaysConsumeSystemBars, calculateRelativeCutout(frame),
                 calculateRelativeRoundedCorners(frame),
                 calculateRelativePrivacyIndicatorBounds(frame),
+                calculateRelativeDisplayShape(frame),
                 compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
     }
 
@@ -335,6 +339,16 @@
         return mPrivacyIndicatorBounds.inset(insetLeft, insetTop, insetRight, insetBottom);
     }
 
+    private DisplayShape calculateRelativeDisplayShape(Rect frame) {
+        if (mDisplayFrame.equals(frame)) {
+            return mDisplayShape;
+        }
+        if (frame == null) {
+            return DisplayShape.NONE;
+        }
+        return mDisplayShape.setOffset(-frame.left, -frame.top);
+    }
+
     public Insets calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) {
         Insets insets = Insets.NONE;
         for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
@@ -589,6 +603,14 @@
         return mPrivacyIndicatorBounds;
     }
 
+    public void setDisplayShape(DisplayShape displayShape) {
+        mDisplayShape = displayShape;
+    }
+
+    public DisplayShape getDisplayShape() {
+        return mDisplayShape;
+    }
+
     /**
      * Modifies the state of this class to exclude a certain type to make it ready for dispatching
      * to the client.
@@ -628,6 +650,7 @@
         mRoundedCorners = mRoundedCorners.scale(scale);
         mRoundedCornerFrame.scale(scale);
         mPrivacyIndicatorBounds = mPrivacyIndicatorBounds.scale(scale);
+        mDisplayShape = mDisplayShape.setScale(scale);
         for (int i = 0; i < SIZE; i++) {
             final InsetsSource source = mSources[i];
             if (source != null) {
@@ -650,6 +673,7 @@
         mRoundedCorners = other.getRoundedCorners();
         mRoundedCornerFrame.set(other.mRoundedCornerFrame);
         mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
+        mDisplayShape = other.getDisplayShape();
         if (copySources) {
             for (int i = 0; i < SIZE; i++) {
                 InsetsSource source = other.mSources[i];
@@ -675,6 +699,7 @@
         mRoundedCorners = other.getRoundedCorners();
         mRoundedCornerFrame.set(other.mRoundedCornerFrame);
         mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
+        mDisplayShape = other.getDisplayShape();
         final ArraySet<Integer> t = toInternalType(types);
         for (int i = t.size() - 1; i >= 0; i--) {
             final int type = t.valueAt(i);
@@ -702,7 +727,7 @@
             result.add(ITYPE_NAVIGATION_BAR);
             result.add(ITYPE_EXTRA_NAVIGATION_BAR);
         }
-        if ((types & Type.GENERIC_OVERLAYS) != 0) {
+        if ((types & Type.SYSTEM_OVERLAYS) != 0) {
             result.add(ITYPE_LEFT_GENERIC_OVERLAY);
             result.add(ITYPE_TOP_GENERIC_OVERLAY);
             result.add(ITYPE_RIGHT_GENERIC_OVERLAY);
@@ -752,7 +777,7 @@
             case ITYPE_TOP_GENERIC_OVERLAY:
             case ITYPE_RIGHT_GENERIC_OVERLAY:
             case ITYPE_BOTTOM_GENERIC_OVERLAY:
-                return Type.GENERIC_OVERLAYS;
+                return Type.SYSTEM_OVERLAYS;
             case ITYPE_CAPTION_BAR:
                 return Type.CAPTION_BAR;
             case ITYPE_IME:
@@ -807,6 +832,7 @@
         pw.println(newPrefix + "mRoundedCorners=" + mRoundedCorners);
         pw.println(newPrefix + "mRoundedCornerFrame=" + mRoundedCornerFrame);
         pw.println(newPrefix + "mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds);
+        pw.println(newPrefix + "mDisplayShape=" + mDisplayShape);
         for (int i = 0; i < SIZE; i++) {
             InsetsSource source = mSources[i];
             if (source == null) continue;
@@ -911,7 +937,8 @@
                 || !mDisplayCutout.equals(state.mDisplayCutout)
                 || !mRoundedCorners.equals(state.mRoundedCorners)
                 || !mRoundedCornerFrame.equals(state.mRoundedCornerFrame)
-                || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)) {
+                || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)
+                || !mDisplayShape.equals(state.mDisplayShape)) {
             return false;
         }
         for (int i = 0; i < SIZE; i++) {
@@ -941,7 +968,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources),
-                mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame);
+                mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame, mDisplayShape);
     }
 
     public InsetsState(Parcel in) {
@@ -961,6 +988,7 @@
         dest.writeTypedObject(mRoundedCorners, flags);
         mRoundedCornerFrame.writeToParcel(dest, flags);
         dest.writeTypedObject(mPrivacyIndicatorBounds, flags);
+        dest.writeTypedObject(mDisplayShape, flags);
     }
 
     public static final @NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
@@ -981,6 +1009,7 @@
         mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR);
         mRoundedCornerFrame.readFromParcel(in);
         mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR);
+        mDisplayShape = in.readTypedObject(DisplayShape.CREATOR);
     }
 
     @Override
@@ -998,6 +1027,7 @@
                 + ", mRoundedCorners=" + mRoundedCorners
                 + "  mRoundedCornerFrame=" + mRoundedCornerFrame
                 + ", mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds
+                + ", mDisplayShape=" + mDisplayShape
                 + ", mSources= { " + joiner
                 + " }";
     }
diff --git a/core/java/android/view/InsetsVisibilities.aidl b/core/java/android/view/InsetsVisibilities.aidl
deleted file mode 100644
index bd573ea..0000000
--- a/core/java/android/view/InsetsVisibilities.aidl
+++ /dev/null
@@ -1,19 +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.view;
-
-parcelable InsetsVisibilities;
diff --git a/core/java/android/view/InsetsVisibilities.java b/core/java/android/view/InsetsVisibilities.java
deleted file mode 100644
index 7d259fb..0000000
--- a/core/java/android/view/InsetsVisibilities.java
+++ /dev/null
@@ -1,134 +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.view;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Arrays;
-import java.util.StringJoiner;
-
-/**
- * A collection of visibilities of insets. This is used for carrying the requested visibilities.
- * @hide
- */
-public class InsetsVisibilities implements Parcelable {
-
-    private static final int UNSPECIFIED = 0;
-    private static final int VISIBLE = 1;
-    private static final int INVISIBLE = -1;
-
-    private final int[] mVisibilities = new int[InsetsState.SIZE];
-
-    public InsetsVisibilities() {
-    }
-
-    public InsetsVisibilities(InsetsVisibilities other) {
-        set(other);
-    }
-
-    public InsetsVisibilities(Parcel in) {
-        in.readIntArray(mVisibilities);
-    }
-
-    /**
-     * Copies from another {@link InsetsVisibilities}.
-     *
-     * @param other an instance of {@link InsetsVisibilities}.
-     */
-    public void set(InsetsVisibilities other) {
-        System.arraycopy(other.mVisibilities, InsetsState.FIRST_TYPE, mVisibilities,
-                InsetsState.FIRST_TYPE, InsetsState.SIZE);
-    }
-
-    /**
-     * Sets a visibility to a type.
-     *
-     * @param type The {@link @InsetsState.InternalInsetsType}.
-     * @param visible {@code true} represents visible; {@code false} represents invisible.
-     */
-    public void setVisibility(@InsetsState.InternalInsetsType int type, boolean visible) {
-        mVisibilities[type] = visible ? VISIBLE : INVISIBLE;
-    }
-
-    /**
-     * Returns the specified insets visibility of the type. If it has never been specified,
-     * this returns the default visibility.
-     *
-     * @param type The {@link @InsetsState.InternalInsetsType}.
-     * @return The specified visibility or the default one if it is not specified.
-     */
-    public boolean getVisibility(@InsetsState.InternalInsetsType int type) {
-        final int visibility = mVisibilities[type];
-        return visibility == UNSPECIFIED
-                ? InsetsState.getDefaultVisibility(type)
-                : visibility == VISIBLE;
-    }
-
-    @Override
-    public String toString() {
-        StringJoiner joiner = new StringJoiner(", ");
-        for (int type = InsetsState.FIRST_TYPE; type <= InsetsState.LAST_TYPE; type++) {
-            final int visibility = mVisibilities[type];
-            if (visibility != UNSPECIFIED) {
-                joiner.add(InsetsState.typeToString(type) + ": "
-                        + (visibility == VISIBLE ? "visible" : "invisible"));
-            }
-        }
-        return joiner.toString();
-    }
-
-    @Override
-    public int hashCode() {
-        return Arrays.hashCode(mVisibilities);
-    }
-
-    @Override
-    public boolean equals(Object other) {
-        if (!(other instanceof InsetsVisibilities)) {
-            return false;
-        }
-        return Arrays.equals(mVisibilities, ((InsetsVisibilities) other).mVisibilities);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeIntArray(mVisibilities);
-    }
-
-    public void readFromParcel(@NonNull Parcel in) {
-        in.readIntArray(mVisibilities);
-    }
-
-    public static final @NonNull Creator<InsetsVisibilities> CREATOR =
-            new Creator<InsetsVisibilities>() {
-
-        public InsetsVisibilities createFromParcel(Parcel in) {
-            return new InsetsVisibilities(in);
-        }
-
-        public InsetsVisibilities[] newArray(int size) {
-            return new InsetsVisibilities[size];
-        }
-    };
-}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index a08a5a8..c8a5d8d 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -22,7 +22,9 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Matrix;
@@ -1757,7 +1759,7 @@
      *
      * @param downTime The time (in ms) when the user originally pressed down to start
      * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
-     * @param eventTime The the time (in ms) when this specific event was generated.  This
+     * @param eventTime The time (in ms) when this specific event was generated.  This
      * must be obtained from {@link SystemClock#uptimeMillis()}.
      * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
      * @param pointerCount The number of pointers that will be in this event.
@@ -1771,7 +1773,7 @@
      * @param buttonState The state of buttons that are pressed.
      * @param xPrecision The precision of the X coordinate being reported.
      * @param yPrecision The precision of the Y coordinate being reported.
-     * @param deviceId The id for the device that this event came from.  An id of
+     * @param deviceId The ID for the device that this event came from.  An ID of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
      * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
@@ -1779,18 +1781,19 @@
      * @param source The source of this event.
      * @param displayId The display ID associated with this event.
      * @param flags The motion event flags.
-     * @hide
+     * @param classification The classification to give this event.
      */
-    static public MotionEvent obtain(long downTime, long eventTime,
-            int action, int pointerCount, PointerProperties[] pointerProperties,
-            PointerCoords[] pointerCoords, int metaState, int buttonState,
-            float xPrecision, float yPrecision, int deviceId,
-            int edgeFlags, int source, int displayId, int flags) {
+    public static @Nullable MotionEvent obtain(long downTime, long eventTime, int action,
+            int pointerCount,
+            @SuppressLint("ArrayReturn") @NonNull PointerProperties[] pointerProperties,
+            @SuppressLint("ArrayReturn") @NonNull PointerCoords[] pointerCoords, int metaState,
+            int buttonState, float xPrecision, float yPrecision, int deviceId, int edgeFlags,
+            int source, int displayId, int flags, @Classification int classification) {
         MotionEvent ev = obtain();
         final boolean success = ev.initialize(deviceId, source, displayId, action, flags, edgeFlags,
-                metaState, buttonState, CLASSIFICATION_NONE, 0, 0, xPrecision, yPrecision,
-                downTime * NS_PER_MS, eventTime * NS_PER_MS,
-                pointerCount, pointerProperties, pointerCoords);
+                metaState, buttonState, classification, 0, 0, xPrecision, yPrecision,
+                downTime * NS_PER_MS, eventTime * NS_PER_MS, pointerCount, pointerProperties,
+                pointerCoords);
         if (!success) {
             Log.e(TAG, "Could not initialize MotionEvent");
             ev.recycle();
@@ -1805,7 +1808,7 @@
      *
      * @param downTime The time (in ms) when the user originally pressed down to start
      * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
-     * @param eventTime The the time (in ms) when this specific event was generated.  This
+     * @param eventTime The time (in ms) when this specific event was generated.  This
      * must be obtained from {@link SystemClock#uptimeMillis()}.
      * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
      * @param pointerCount The number of pointers that will be in this event.
@@ -1819,7 +1822,47 @@
      * @param buttonState The state of buttons that are pressed.
      * @param xPrecision The precision of the X coordinate being reported.
      * @param yPrecision The precision of the Y coordinate being reported.
-     * @param deviceId The id for the device that this event came from.  An id of
+     * @param deviceId The ID for the device that this event came from.  An ID of
+     * zero indicates that the event didn't come from a physical device; other
+     * numbers are arbitrary and you shouldn't depend on the values.
+     * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
+     * MotionEvent.
+     * @param source The source of this event.
+     * @param displayId The display ID associated with this event.
+     * @param flags The motion event flags.
+     * @hide
+     */
+    public static MotionEvent obtain(long downTime, long eventTime,
+            int action, int pointerCount, PointerProperties[] pointerProperties,
+            PointerCoords[] pointerCoords, int metaState, int buttonState,
+            float xPrecision, float yPrecision, int deviceId,
+            int edgeFlags, int source, int displayId, int flags) {
+        return obtain(downTime, eventTime, action, pointerCount, pointerProperties, pointerCoords,
+                metaState, buttonState, xPrecision, yPrecision, deviceId, edgeFlags, source,
+                displayId, flags, CLASSIFICATION_NONE);
+    }
+
+    /**
+     * Create a new MotionEvent, filling in all of the basic values that
+     * define the motion.
+     *
+     * @param downTime The time (in ms) when the user originally pressed down to start
+     * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param eventTime The time (in ms) when this specific event was generated.  This
+     * must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+     * @param pointerCount The number of pointers that will be in this event.
+     * @param pointerProperties An array of <em>pointerCount</em> values providing
+     * a {@link PointerProperties} property object for each pointer, which must
+     * include the pointer identifier.
+     * @param pointerCoords An array of <em>pointerCount</em> values providing
+     * a {@link PointerCoords} coordinate object for each pointer.
+     * @param metaState The state of any meta / modifier keys that were in effect when
+     * the event was generated.
+     * @param buttonState The state of buttons that are pressed.
+     * @param xPrecision The precision of the X coordinate being reported.
+     * @param yPrecision The precision of the Y coordinate being reported.
+     * @param deviceId The ID for the device that this event came from.  An ID of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
      * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
@@ -1843,7 +1886,7 @@
      *
      * @param downTime The time (in ms) when the user originally pressed down to start
      * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
-     * @param eventTime The the time (in ms) when this specific event was generated.  This
+     * @param eventTime The time (in ms) when this specific event was generated.  This
      * must be obtained from {@link SystemClock#uptimeMillis()}.
      * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
      * @param pointerCount The number of pointers that will be in this event.
@@ -1855,7 +1898,7 @@
      * the event was generated.
      * @param xPrecision The precision of the X coordinate being reported.
      * @param yPrecision The precision of the Y coordinate being reported.
-     * @param deviceId The id for the device that this event came from.  An id of
+     * @param deviceId The ID for the device that this event came from.  An ID of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
      * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
@@ -1890,7 +1933,7 @@
      *
      * @param downTime The time (in ms) when the user originally pressed down to start
      * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
-     * @param eventTime  The the time (in ms) when this specific event was generated.  This
+     * @param eventTime The time (in ms) when this specific event was generated.  This
      * must be obtained from {@link SystemClock#uptimeMillis()}.
      * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
      * @param x The X coordinate of this event.
@@ -1907,7 +1950,7 @@
      * the event was generated.
      * @param xPrecision The precision of the X coordinate being reported.
      * @param yPrecision The precision of the Y coordinate being reported.
-     * @param deviceId The id for the device that this event came from.  An id of
+     * @param deviceId The ID for the device that this event came from.  An ID of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
      * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
@@ -1927,7 +1970,7 @@
      *
      * @param downTime The time (in ms) when the user originally pressed down to start
      * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
-     * @param eventTime  The the time (in ms) when this specific event was generated.  This
+     * @param eventTime The time (in ms) when this specific event was generated.  This
      * must be obtained from {@link SystemClock#uptimeMillis()}.
      * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
      * @param x The X coordinate of this event.
@@ -1944,7 +1987,7 @@
      * the event was generated.
      * @param xPrecision The precision of the X coordinate being reported.
      * @param yPrecision The precision of the Y coordinate being reported.
-     * @param deviceId The id for the device that this event came from.  An id of
+     * @param deviceId The ID for the device that this event came from.  An ID of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
      * @param source The source of this event.
@@ -1986,7 +2029,7 @@
      *
      * @param downTime The time (in ms) when the user originally pressed down to start
      * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
-     * @param eventTime  The the time (in ms) when this specific event was generated.  This
+     * @param eventTime The time (in ms) when this specific event was generated.  This
      * must be obtained from {@link SystemClock#uptimeMillis()}.
      * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
      * @param pointerCount The number of pointers that are active in this event.
@@ -2004,7 +2047,7 @@
      * the event was generated.
      * @param xPrecision The precision of the X coordinate being reported.
      * @param yPrecision The precision of the Y coordinate being reported.
-     * @param deviceId The id for the device that this event came from.  An id of
+     * @param deviceId The ID for the device that this event came from.  An ID of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
      * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
@@ -2028,7 +2071,7 @@
      *
      * @param downTime The time (in ms) when the user originally pressed down to start
      * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
-     * @param eventTime  The the time (in ms) when this specific event was generated.  This
+     * @param eventTime The time (in ms) when this specific event was generated.  This
      * must be obtained from {@link SystemClock#uptimeMillis()}.
      * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
      * @param x The X coordinate of this event.
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 2a8e7e4..080c0d8 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -533,6 +533,13 @@
         mHotSpotY = hotSpotY;
     }
 
+    @Override
+    public String toString() {
+        return "PointerIcon{type=" + typeToString(mType)
+                + ", hotspotX=" + mHotSpotX + ", hotspotY=" + mHotSpotY
+                + ", systemIconResourceId=" + mSystemIconResourceId + "}";
+    }
+
     private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY) {
         if (hotSpotX < 0 || hotSpotX >= bitmap.getWidth()) {
             throw new IllegalArgumentException("x hotspot lies outside of the bitmap area");
@@ -624,4 +631,40 @@
         displayManager.registerDisplayListener(sDisplayListener, null /* handler */);
     }
 
+    /**
+     * Convert type constant to string.
+     * @hide
+     */
+    public static String typeToString(int type) {
+        switch (type) {
+            case TYPE_CUSTOM: return "CUSTOM";
+            case TYPE_NULL: return "NULL";
+            case TYPE_NOT_SPECIFIED: return "NOT_SPECIFIED";
+            case TYPE_ARROW: return "ARROW";
+            case TYPE_SPOT_HOVER: return "SPOT_HOVER";
+            case TYPE_SPOT_TOUCH: return "SPOT_TOUCH";
+            case TYPE_SPOT_ANCHOR: return "SPOT_ANCHOR";
+            case TYPE_CONTEXT_MENU: return "CONTEXT_MENU";
+            case TYPE_HAND: return "HAND";
+            case TYPE_HELP: return "HELP";
+            case TYPE_WAIT: return "WAIT";
+            case TYPE_CELL: return "CELL";
+            case TYPE_CROSSHAIR: return "CROSSHAIR";
+            case TYPE_TEXT: return "TEXT";
+            case TYPE_VERTICAL_TEXT: return "VERTICAL_TEXT";
+            case TYPE_ALIAS: return "ALIAS";
+            case TYPE_COPY: return "COPY";
+            case TYPE_NO_DROP: return "NO_DROP";
+            case TYPE_ALL_SCROLL: return "ALL_SCROLL";
+            case TYPE_HORIZONTAL_DOUBLE_ARROW: return "HORIZONTAL_DOUBLE_ARROW";
+            case TYPE_VERTICAL_DOUBLE_ARROW: return "VERTICAL_DOUBLE_ARROW";
+            case TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW: return "TOP_RIGHT_DIAGONAL_DOUBLE_ARROW";
+            case TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW: return "TOP_LEFT_DIAGONAL_DOUBLE_ARROW";
+            case TYPE_ZOOM_IN: return "ZOOM_IN";
+            case TYPE_ZOOM_OUT: return "ZOOM_OUT";
+            case TYPE_GRAB: return "GRAB";
+            case TYPE_GRABBING: return "GRABBING";
+            default: return Integer.toString(type);
+        }
+    }
 }
diff --git a/core/java/android/view/RemoteAnimationDefinition.java b/core/java/android/view/RemoteAnimationDefinition.java
index ea97995..ff282ba 100644
--- a/core/java/android/view/RemoteAnimationDefinition.java
+++ b/core/java/android/view/RemoteAnimationDefinition.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 
 import android.annotation.Nullable;
+import android.annotation.NonNull;
 import android.app.WindowConfiguration.ActivityType;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.IBinder;
@@ -157,7 +158,7 @@
         }
     }
 
-    public static final @android.annotation.NonNull Creator<RemoteAnimationDefinition> CREATOR =
+    public static final @NonNull Creator<RemoteAnimationDefinition> CREATOR =
             new Creator<RemoteAnimationDefinition>() {
         public RemoteAnimationDefinition createFromParcel(Parcel in) {
             return new RemoteAnimationDefinition(in);
@@ -199,18 +200,17 @@
             return 0;
         }
 
-        private static final @android.annotation.NonNull Creator<RemoteAnimationAdapterEntry> CREATOR
-                = new Creator<RemoteAnimationAdapterEntry>() {
+        public static final @NonNull Parcelable.Creator<RemoteAnimationAdapterEntry> CREATOR =
+                new Parcelable.Creator<RemoteAnimationAdapterEntry>() {
+                    @Override
+                    public RemoteAnimationAdapterEntry createFromParcel(Parcel in) {
+                        return new RemoteAnimationAdapterEntry(in);
+                    }
 
-            @Override
-            public RemoteAnimationAdapterEntry createFromParcel(Parcel in) {
-                return new RemoteAnimationAdapterEntry(in);
-            }
-
-            @Override
-            public RemoteAnimationAdapterEntry[] newArray(int size) {
-                return new RemoteAnimationAdapterEntry[size];
-            }
-        };
+                    @Override
+                    public RemoteAnimationAdapterEntry[] newArray(int size) {
+                        return new RemoteAnimationAdapterEntry[size];
+                    }
+                };
     }
 }
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index 5e17551..8d8ddb9 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -35,6 +35,7 @@
 
 import android.annotation.ColorInt;
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.TaskInfo;
 import android.app.WindowConfiguration;
@@ -175,10 +176,16 @@
     public final Rect screenSpaceBounds;
 
     /**
-     * The starting bounds of the source container in screen space coordinates. This is {@code null}
-     * if the animation target isn't MODE_CHANGING. Since this is the starting bounds, it's size
-     * should be equivalent to the size of the starting thumbnail. Note that sourceContainerBounds
-     * is the end bounds of a change transition.
+     * The starting bounds of the source container in screen space coordinates.
+     * For {@link #MODE_OPENING}, this will be equivalent to {@link #screenSpaceBounds}.
+     * For {@link #MODE_CLOSING}, this will be equivalent to {@link #screenSpaceBounds} unless the
+     * closing container is also resizing. For example, when ActivityEmbedding split pair becomes
+     * stacked, the container on the back will be resized to fullscreen, but will also be covered
+     * (closing) by the container in the front.
+     * For {@link #MODE_CHANGING}, since this is the starting bounds, its size should be equivalent
+     * to the bounds of the starting thumbnail.
+     *
+     * Note that {@link #screenSpaceBounds} is the end bounds of a transition.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public final Rect startBounds;
@@ -247,7 +254,8 @@
             Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
             Rect localBounds, Rect screenSpaceBounds,
             WindowConfiguration windowConfig, boolean isNotInRecents,
-            SurfaceControl startLeash, Rect startBounds, ActivityManager.RunningTaskInfo taskInfo,
+            SurfaceControl startLeash, @Nullable Rect startBounds,
+            ActivityManager.RunningTaskInfo taskInfo,
             boolean allowEnterPip) {
         this(taskId, mode, leash, isTranslucent, clipRect, contentInsets, prefixOrderIndex,
                 position, localBounds, screenSpaceBounds, windowConfig, isNotInRecents, startLeash,
@@ -258,7 +266,7 @@
             Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
             Rect localBounds, Rect screenSpaceBounds,
             WindowConfiguration windowConfig, boolean isNotInRecents,
-            SurfaceControl startLeash, Rect startBounds,
+            SurfaceControl startLeash, @Nullable Rect startBounds,
             ActivityManager.RunningTaskInfo taskInfo, boolean allowEnterPip,
             @WindowManager.LayoutParams.WindowType int windowType) {
         this.mode = mode;
@@ -275,10 +283,13 @@
         this.windowConfiguration = windowConfig;
         this.isNotInRecents = isNotInRecents;
         this.startLeash = startLeash;
-        this.startBounds = startBounds == null ? null : new Rect(startBounds);
         this.taskInfo = taskInfo;
         this.allowEnterPip = allowEnterPip;
         this.windowType = windowType;
+        // Same as screenSpaceBounds if the window is not resizing.
+        this.startBounds = startBounds == null
+                ? new Rect(screenSpaceBounds)
+                : new Rect(startBounds);
     }
 
     public RemoteAnimationTarget(Parcel in) {
@@ -399,9 +410,7 @@
         if (startLeash != null) {
             startLeash.dumpDebug(proto, START_LEASH);
         }
-        if (startBounds != null) {
-            startBounds.dumpDebug(proto, START_BOUNDS);
-        }
+        startBounds.dumpDebug(proto, START_BOUNDS);
         proto.end(token);
     }
 
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 06851de..89a0c05 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -47,6 +47,7 @@
 import android.gui.DropInputMode;
 import android.hardware.DataSpace;
 import android.hardware.HardwareBuffer;
+import android.hardware.OverlayProperties;
 import android.hardware.SyncFence;
 import android.hardware.display.DeviceProductInfo;
 import android.hardware.display.DisplayManager;
@@ -201,6 +202,7 @@
     private static native DisplayPrimaries nativeGetDisplayNativePrimaries(
             IBinder displayToken);
     private static native int[] nativeGetCompositionDataspaces();
+    private static native OverlayProperties nativeGetOverlaySupport();
     private static native boolean nativeSetActiveColorMode(IBinder displayToken,
             int colorMode);
     private static native boolean nativeGetBootDisplayModeSupport();
@@ -1502,6 +1504,7 @@
     public static final class DynamicDisplayInfo {
         public DisplayMode[] supportedDisplayModes;
         public int activeDisplayModeId;
+        public float renderFrameRate;
 
         public int[] supportedColorModes;
         public int activeColorMode;
@@ -1518,6 +1521,7 @@
             return "DynamicDisplayInfo{"
                     + "supportedDisplayModes=" + Arrays.toString(supportedDisplayModes)
                     + ", activeDisplayModeId=" + activeDisplayModeId
+                    + ", renderFrameRate=" + renderFrameRate
                     + ", supportedColorModes=" + Arrays.toString(supportedColorModes)
                     + ", activeColorMode=" + activeColorMode
                     + ", hdrCapabilities=" + hdrCapabilities
@@ -1533,6 +1537,7 @@
             DynamicDisplayInfo that = (DynamicDisplayInfo) o;
             return Arrays.equals(supportedDisplayModes, that.supportedDisplayModes)
                 && activeDisplayModeId == that.activeDisplayModeId
+                && renderFrameRate == that.renderFrameRate
                 && Arrays.equals(supportedColorModes, that.supportedColorModes)
                 && activeColorMode == that.activeColorMode
                 && Objects.equals(hdrCapabilities, that.hdrCapabilities)
@@ -1542,7 +1547,7 @@
         @Override
         public int hashCode() {
             return Objects.hash(Arrays.hashCode(supportedDisplayModes), activeDisplayModeId,
-                    activeColorMode, hdrCapabilities);
+                    renderFrameRate, activeColorMode, hdrCapabilities);
         }
     }
 
@@ -1561,6 +1566,7 @@
         public float refreshRate;
         public long appVsyncOffsetNanos;
         public long presentationDeadlineNanos;
+        public int[] supportedHdrTypes;
 
         /**
          * The config group ID this config is associated to.
@@ -1580,6 +1586,7 @@
                     + ", refreshRate=" + refreshRate
                     + ", appVsyncOffsetNanos=" + appVsyncOffsetNanos
                     + ", presentationDeadlineNanos=" + presentationDeadlineNanos
+                    + ", supportedHdrTypes=" + Arrays.toString(supportedHdrTypes)
                     + ", group=" + group + "}";
         }
 
@@ -1596,13 +1603,14 @@
                     && Float.compare(that.refreshRate, refreshRate) == 0
                     && appVsyncOffsetNanos == that.appVsyncOffsetNanos
                     && presentationDeadlineNanos == that.presentationDeadlineNanos
+                    && Arrays.equals(supportedHdrTypes, that.supportedHdrTypes)
                     && group == that.group;
         }
 
         @Override
         public int hashCode() {
             return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, appVsyncOffsetNanos,
-                    presentationDeadlineNanos, group);
+                    presentationDeadlineNanos, group, Arrays.hashCode(supportedHdrTypes));
         }
     }
 
@@ -2044,6 +2052,14 @@
     }
 
     /**
+     * @return the overlay properties of the device
+     * @hide
+     */
+    public static OverlayProperties getOverlaySupport() {
+        return nativeGetOverlaySupport();
+    }
+
+    /**
      * @hide
      */
     public static boolean getBootDisplayModeSupport() {
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 3acb053..1889772 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -159,9 +159,11 @@
         }
 
         /**
-         * Use {@link SurfaceView#setChildSurfacePackage} or manually fix
-         * accessibility (see SurfaceView implementation).
-         * @hide
+         * Returns the {@link android.view.SurfaceControl} associated with this SurfacePackage for
+         * cases where more control is required.
+         *
+         * @return the SurfaceControl associated with this SurfacePackage and its containing
+         *     SurfaceControlViewHost
          */
         public @NonNull SurfaceControl getSurfaceControl() {
             return mSurfaceControl;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 198ac9d..33ea92d 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;
@@ -706,8 +728,8 @@
 
     private void releaseSurfaces(boolean releaseSurfacePackage) {
         mAlpha = 1f;
+        mSurface.destroy();
         synchronized (mSurfaceControlLock) {
-            mSurface.destroy();
             if (mBlastBufferQueue != null) {
                 mBlastBufferQueue.destroy();
                 mBlastBufferQueue = null;
@@ -1051,7 +1073,7 @@
     private void handleSyncBufferCallback(SurfaceHolder.Callback[] callbacks,
             SyncBufferTransactionCallback syncBufferTransactionCallback) {
 
-        getViewRootImpl().addToSync(syncBufferCallback ->
+        getViewRootImpl().addToSync((parentSyncGroup, syncBufferCallback) ->
                 redrawNeededAsync(callbacks, () -> {
                     Transaction t = null;
                     if (mBlastBufferQueue != null) {
@@ -1059,7 +1081,7 @@
                         t = syncBufferTransactionCallback.waitForTransaction();
                     }
 
-                    syncBufferCallback.onBufferReady(t);
+                    syncBufferCallback.onTransactionReady(t);
                     onDrawFinished();
                 }));
     }
@@ -1070,9 +1092,9 @@
             mSyncGroups.add(syncGroup);
         }
 
-        syncGroup.addToSync(syncBufferCallback -> redrawNeededAsync(callbacks,
-                () -> {
-                    syncBufferCallback.onBufferReady(null);
+        syncGroup.addToSync((parentSyncGroup, syncBufferCallback) ->
+                redrawNeededAsync(callbacks, () -> {
+                    syncBufferCallback.onTransactionReady(null);
                     onDrawFinished();
                     synchronized (mSyncGroups) {
                         mSyncGroups.remove(syncGroup);
@@ -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/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index d77e882..164a494 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -587,6 +587,13 @@
         updateWebViewOverlayCallbacks();
     }
 
+    @Override
+    public void notifyCallbackPending() {
+        if (isEnabled()) {
+            super.notifyCallbackPending();
+        }
+    }
+
     /**
      * Updates the light position based on the position of the window.
      *
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 6d25523..00170cb 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -28,7 +28,7 @@
 import java.util.Map;
 
 /**
- * Helper for tracking the velocity of touch events, for implementing
+ * Helper for tracking the velocity of motion events, for implementing
  * flinging and other such gestures.
  *
  * Use {@link #obtain} to retrieve a new instance of the class when you are going
@@ -43,6 +43,15 @@
 
     private static final int ACTIVE_POINTER_ID = -1;
 
+    /** @hide */
+    @IntDef(value = {
+            MotionEvent.AXIS_X,
+            MotionEvent.AXIS_Y,
+            MotionEvent.AXIS_SCROLL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VelocityTrackableMotionEventAxis {}
+
     /**
      * Velocity Tracker Strategy: Invalid.
      *
@@ -306,10 +315,12 @@
     }
 
     /**
-     * Checks whether a given motion axis is supported for velocity tracking.
+     * Checks whether a given velocity-trackable {@link MotionEvent} axis is supported for velocity
+     * tracking by this {@link VelocityTracker} instance (refer to
+     * {@link #getAxisVelocity(int, int)} for a list of potentially velocity-trackable axes).
      *
-     * <p>The axis values that would make sense to use for this method are the ones defined in the
-     * {@link MotionEvent} class.
+     * <p>Note that the value returned from this method will stay the same for a given instance, so
+     * a single check for axis support is enough per a {@link VelocityTracker} instance.
      *
      * @param axis The axis to check for velocity support.
      * @return {@code true} if {@code axis} is supported for velocity tracking, or {@code false}
@@ -317,7 +328,7 @@
      * @see #getAxisVelocity(int, int)
      * @see #getAxisVelocity(int)
      */
-    public boolean isAxisSupported(int axis) {
+    public boolean isAxisSupported(@VelocityTrackableMotionEventAxis int axis) {
         return nativeIsAxisSupported(axis);
     }
 
@@ -421,13 +432,16 @@
      * calling this function.
      *
      * <p>In addition to {@link MotionEvent#AXIS_X} and {@link MotionEvent#AXIS_Y} which have been
-     * supported since the introduction of this class, the following axes are supported for this
+     * supported since the introduction of this class, the following axes can be candidates for this
      * method:
      * <ul>
      *   <li> {@link MotionEvent#AXIS_SCROLL}: supported starting
      *        {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
      * </ul>
      *
+     * <p>Before accessing velocities of an axis using this method, check that your
+     * {@link VelocityTracker} instance supports the axis by using {@link #isAxisSupported(int)}.
+     *
      * @param axis Which axis' velocity to return.
      * @param id Which pointer's velocity to return.
      * @return The previously computed velocity for {@code axis} for pointer ID of {@code id} if
@@ -435,7 +449,7 @@
      * for the axis.
      * @see #isAxisSupported(int)
      */
-    public float getAxisVelocity(int axis, int id) {
+    public float getAxisVelocity(@VelocityTrackableMotionEventAxis int axis, int id) {
         return nativeGetVelocity(mPtr, axis, id);
     }
 
@@ -450,7 +464,7 @@
      * @see #isAxisSupported(int)
      * @see #getAxisVelocity(int, int)
      */
-    public float getAxisVelocity(int axis) {
+    public float getAxisVelocity(@VelocityTrackableMotionEventAxis int axis) {
         return nativeGetVelocity(mPtr, axis, ACTIVE_POINTER_ID);
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7b6ebf7..d709840 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.
      */
@@ -8450,6 +8457,18 @@
      * accurately supplying the semantics of their UI.
      * They should not need to specify what exactly is announced to users.
      *
+     * <p>
+     * In general, only announce transitions and don’t generate a confirmation message for simple
+     * actions like a button press. Label your controls concisely and precisely instead, and for
+     * significant UI changes like window changes, use
+     * {@link android.app.Activity#setTitle(CharSequence)} and
+     * {@link View#setAccessibilityPaneTitle(CharSequence)}.
+     *
+     * <p>
+     * Use {@link View#setAccessibilityLiveRegion(int)} to inform the user of changes to critical
+     * views within the user interface. These should still be used sparingly as they may generate
+     * announcements every time a View is updated.
+     *
      * @param text The announcement text.
      */
     public void announceForAccessibility(CharSequence text) {
@@ -12255,6 +12274,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 +17446,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 +20731,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 +20915,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 +21027,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 +21047,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 +21058,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 +23173,7 @@
      *
      * @hide
      */
-    protected final boolean drawsWithRenderNode(Canvas canvas) {
+    protected final boolean drawsWithRenderNode(@NonNull Canvas canvas) {
         return mAttachInfo != null
                 && mAttachInfo.mHardwareAccelerated
                 && canvas.isHardwareAccelerated();
@@ -23142,7 +23185,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 +23473,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 +23508,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 +23743,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 +24248,7 @@
             }
         }
         rebuildOutline();
-        if (onCheckIsTextEditor()) {
+        if (onCheckIsTextEditor() || mHandwritingDelegateConfiguration != null) {
             setHandwritingArea(new Rect(0, 0, newWidth, newHeight));
         }
     }
@@ -24600,7 +24643,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 +25441,7 @@
      *
      * @param canvas canvas to draw into
      */
-    public void onDrawForeground(Canvas canvas) {
+    public void onDrawForeground(@NonNull Canvas canvas) {
         onDrawScrollIndicators(canvas);
         onDrawScrollBars(canvas);
 
@@ -27456,7 +27499,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..727011c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -77,6 +77,7 @@
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -88,6 +89,7 @@
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
 
 import android.Manifest;
+import android.accessibilityservice.AccessibilityService;
 import android.animation.AnimationHandler;
 import android.animation.LayoutTransition;
 import android.annotation.AnyThread;
@@ -192,12 +194,14 @@
 import android.view.contentcapture.ContentCaptureManager;
 import android.view.contentcapture.ContentCaptureSession;
 import android.view.contentcapture.MainContentCaptureSession;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Scroller;
 import android.window.ClientWindowFrames;
 import android.window.CompatOnBackInvokedCallback;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
+import android.window.ScreenCapture;
 import android.window.SurfaceSyncGroup;
 import android.window.WindowOnBackInvokedDispatcher;
 
@@ -228,6 +232,7 @@
 import java.util.Objects;
 import java.util.Queue;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 
 /**
  * The top of a view hierarchy, implementing the needed protocol between View
@@ -466,16 +471,6 @@
     private boolean mAppVisibilityChanged;
     int mOrigWindowType = -1;
 
-    /** Whether the window had focus during the most recent traversal. */
-    boolean mHadWindowFocus;
-
-    /**
-     * Whether the window lost focus during a previous traversal and has not
-     * yet gained it back. Used to determine whether a WINDOW_STATE_CHANGE
-     * accessibility events should be sent during traversal.
-     */
-    boolean mLostWindowFocus;
-
     // Set to true if the owner of this window is in the stopped state,
     // so the window should no longer be active.
     @UnsupportedAppUsage
@@ -501,6 +496,13 @@
     Region mTouchableRegion;
     Region mPreviousTouchableRegion;
 
+    private int mMeasuredWidth;
+    private int mMeasuredHeight;
+
+    // This indicates that we've already known the window size but without measuring the views.
+    // If this is true, we must measure the views before laying out them.
+    private boolean mViewMeasureDeferred;
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     int mWidth;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -711,7 +713,7 @@
     private final InsetsState mTempInsets = new InsetsState();
     private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[SIZE];
     private final WindowConfiguration mTempWinConfig = new WindowConfiguration();
-    private float mInvSizeCompatScale = 1f;
+    private float mInvCompatScale = 1f;
     final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
             = new ViewTreeObserver.InternalInsetsInfo();
 
@@ -848,8 +850,13 @@
     }
 
     private SurfaceSyncGroup mSyncGroup;
-    private SurfaceSyncGroup.SyncBufferCallback mSyncBufferCallback;
-    private int mNumSyncsInProgress = 0;
+    private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
+
+    private static final Object sSyncProgressLock = new Object();
+    // The count needs to be static since it's used to enable or disable RT animations which is
+    // done at a global level per process. If any VRI syncs are in progress, we can't enable RT
+    // animations until all are done.
+    private static int sNumSyncsInProgress = 0;
 
     private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks;
 
@@ -1105,11 +1112,11 @@
 
     private WindowConfiguration getCompatWindowConfiguration() {
         final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
-        if (mInvSizeCompatScale == 1f) {
+        if (mInvCompatScale == 1f) {
             return winConfig;
         }
         mTempWinConfig.setTo(winConfig);
-        mTempWinConfig.scale(mInvSizeCompatScale);
+        mTempWinConfig.scale(mInvCompatScale);
         return mTempWinConfig;
     }
 
@@ -1241,11 +1248,11 @@
                     controlInsetsForCompatibility(mWindowAttributes);
 
                     Rect attachedFrame = new Rect();
-                    final float[] sizeCompatScale = { 1f };
+                    final float[] compatScale = { 1f };
                     res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                             getHostVisibility(), mDisplay.getDisplayId(), userId,
                             mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
-                            mTempControls, attachedFrame, sizeCompatScale);
+                            mTempControls, attachedFrame, compatScale);
                     if (!attachedFrame.isValid()) {
                         attachedFrame = null;
                     }
@@ -1255,9 +1262,9 @@
                         mTranslator.translateRectInScreenToAppWindow(attachedFrame);
                     }
                     mTmpFrames.attachedFrame = attachedFrame;
-                    mTmpFrames.sizeCompatScale = sizeCompatScale[0];
-                    mInvSizeCompatScale = 1f / sizeCompatScale[0];
-                } catch (RemoteException e) {
+                    mTmpFrames.compatScale = compatScale[0];
+                    mInvCompatScale = 1f / compatScale[0];
+                } catch (RemoteException | RuntimeException e) {
                     mAdded = false;
                     mView = null;
                     mAttachInfo.mRootView = null;
@@ -1405,7 +1412,11 @@
                 mFirstPostImeInputStage = earlyPostImeStage;
                 mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
 
-                AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+                if (!mRemoved || !mAppVisible) {
+                    AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+                } else if (LOCAL_LOGV) {
+                    Log.v(mTag, "setView() enabling visibility when removed");
+                }
             }
         }
     }
@@ -1756,7 +1767,12 @@
                 mAppVisibilityChanged = true;
                 scheduleTraversals();
             }
-            AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+            // Only enable if the window is not already removed (via earlier call to doDie())
+            if (!mRemoved || !mAppVisible) {
+                AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+            } else if (LOCAL_LOGV) {
+                Log.v(mTag, "handleAppVisibility() enabling visibility when removed");
+            }
         }
     }
 
@@ -1787,24 +1803,24 @@
             mTranslator.translateRectInScreenToAppWindow(displayFrame);
             mTranslator.translateRectInScreenToAppWindow(attachedFrame);
         }
-        final float sizeCompatScale = frames.sizeCompatScale;
+        final float compatScale = frames.compatScale;
         final boolean frameChanged = !mWinFrame.equals(frame);
         final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration);
         final boolean attachedFrameChanged = LOCAL_LAYOUT
                 && !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
         final boolean displayChanged = mDisplay.getDisplayId() != displayId;
         final boolean resizeModeChanged = mResizeMode != resizeMode;
-        final boolean sizeCompatScaleChanged = mTmpFrames.sizeCompatScale != sizeCompatScale;
+        final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale;
         if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
                 && !displayChanged && !resizeModeChanged && !forceNextWindowRelayout
-                && !sizeCompatScaleChanged) {
+                && !compatScaleChanged) {
             return;
         }
 
         mPendingDragResizing = resizeMode != RESIZE_MODE_INVALID;
         mResizeMode = resizeMode;
-        mTmpFrames.sizeCompatScale = sizeCompatScale;
-        mInvSizeCompatScale = 1f / sizeCompatScale;
+        mTmpFrames.compatScale = compatScale;
+        mInvCompatScale = 1f / compatScale;
 
         if (configChanged) {
             // If configuration changed - notify about that and, maybe, about move to display.
@@ -2305,6 +2321,7 @@
      */
     void notifyRendererOfFramePending() {
         if (mAttachInfo.mThreadedRenderer != null) {
+            mAttachInfo.mThreadedRenderer.notifyCallbackPending();
             mAttachInfo.mThreadedRenderer.notifyFramePending();
         }
     }
@@ -2574,7 +2591,8 @@
     }
 
     private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
-            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
+            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight,
+            boolean forRootSizeOnly) {
         int childWidthMeasureSpec;
         int childHeightMeasureSpec;
         boolean windowSizeMayChange = false;
@@ -2630,7 +2648,15 @@
                     lp.privateFlags);
             childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
                     lp.privateFlags);
-            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+            if (!forRootSizeOnly || !setMeasuredRootSizeFromSpec(
+                    childWidthMeasureSpec, childHeightMeasureSpec)) {
+                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+            } else {
+                // We already know how big the window should be before measuring the views.
+                // We can measure the views before laying out them. This is to avoid unnecessary
+                // measure.
+                mViewMeasureDeferred = true;
+            }
             if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                 windowSizeMayChange = true;
             }
@@ -2646,6 +2672,25 @@
     }
 
     /**
+     * Sets the measured root size for requesting the window frame.
+     *
+     * @param widthMeasureSpec contains the size and the mode of the width.
+     * @param heightMeasureSpec contains the size and the mode of the height.
+     * @return {@code true} if we actually set the measured size; {@code false} otherwise.
+     */
+    private boolean setMeasuredRootSizeFromSpec(int widthMeasureSpec, int heightMeasureSpec) {
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
+            // We don't know the exact size. We need to measure the hierarchy to know that.
+            return false;
+        }
+        mMeasuredWidth = MeasureSpec.getSize(widthMeasureSpec);
+        mMeasuredHeight = MeasureSpec.getSize(heightMeasureSpec);
+        return true;
+    }
+
+    /**
      * Modifies the input matrix such that it maps view-local coordinates to
      * on-screen coordinates.
      *
@@ -2732,6 +2777,14 @@
                 || lp.type == TYPE_VOLUME_OVERLAY;
     }
 
+    /**
+     * @return {@code true} if we should reduce unnecessary measure for the window.
+     * TODO(b/260382739): Apply this to all windows.
+     */
+    private static boolean shouldOptimizeMeasure(final WindowManager.LayoutParams lp) {
+        return lp.type == TYPE_NOTIFICATION_SHADE;
+    }
+
     private Rect getWindowBoundsInsetSystemBars() {
         final Rect bounds = new Rect(
                 mContext.getResources().getConfiguration().windowConfiguration.getBounds());
@@ -2782,6 +2835,7 @@
         mAppVisibilityChanged = false;
         final boolean viewUserVisibilityChanged = !mFirst &&
                 ((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
+        final boolean shouldOptimizeMeasure = shouldOptimizeMeasure(lp);
 
         WindowManager.LayoutParams params = null;
         CompatibilityInfo compatibilityInfo =
@@ -2903,7 +2957,7 @@
 
             // Ask host how big it wants to be
             windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(),
-                    desiredWindowWidth, desiredWindowHeight);
+                    desiredWindowWidth, desiredWindowHeight, shouldOptimizeMeasure);
         }
 
         if (collectViewAttributes()) {
@@ -2943,8 +2997,8 @@
                 // we don't need to go through two layout passes when things
                 // change due to fitting system windows, which can happen a lot.
                 windowSizeMayChange |= measureHierarchy(host, lp,
-                        mView.getContext().getResources(),
-                        desiredWindowWidth, desiredWindowHeight);
+                        mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight,
+                        shouldOptimizeMeasure);
             }
         }
 
@@ -3364,6 +3418,13 @@
             maybeHandleWindowMove(frame);
         }
 
+        if (mViewMeasureDeferred) {
+            // It's time to measure the views since we are going to layout them.
+            performMeasure(
+                    MeasureSpec.makeMeasureSpec(frame.width(), MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(frame.height(), MeasureSpec.EXACTLY));
+        }
+
         if (!mRelayoutRequested && mCheckIfCanDraw) {
             // We had a sync previously, but we didn't call IWindowSession#relayout in this
             // traversal. So we don't know if the sync is complete that we can continue to draw.
@@ -3559,20 +3620,8 @@
         }
 
         final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
-        final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
-        final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
-        if (regainedFocus) {
-            mLostWindowFocus = false;
-        } else if (!hasWindowFocus && mHadWindowFocus) {
-            mLostWindowFocus = true;
-        }
-
-        if (changedVisibility || regainedFocus) {
-            // Toasts are presented as notifications - don't present them as windows as well
-            boolean isToast = mWindowAttributes.type == TYPE_TOAST;
-            if (!isToast) {
-                host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-            }
+        if (changedVisibility) {
+            maybeFireAccessibilityWindowStateChangedEvent();
         }
 
         mFirst = false;
@@ -3580,8 +3629,8 @@
         mNewSurfaceNeeded = false;
         mActivityRelaunched = false;
         mViewVisibility = viewVisibility;
-        mHadWindowFocus = hasWindowFocus;
 
+        final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
         mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);
 
         if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
@@ -3607,8 +3656,8 @@
                 mPendingTransitions.clear();
             }
 
-            if (mSyncBufferCallback != null) {
-                mSyncBufferCallback.onBufferReady(null);
+            if (mTransactionReadyCallback != null) {
+                mTransactionReadyCallback.onTransactionReady(null);
             }
         } else if (cancelAndRedraw) {
             mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
@@ -3623,8 +3672,8 @@
                 }
                 mPendingTransitions.clear();
             }
-            if (!performDraw() && mSyncBufferCallback != null) {
-                mSyncBufferCallback.onBufferReady(null);
+            if (!performDraw() && mTransactionReadyCallback != null) {
+                mTransactionReadyCallback.onTransactionReady(null);
             }
         }
 
@@ -3638,7 +3687,7 @@
         if (!cancelAndRedraw) {
             mReportNextDraw = false;
             mLastReportNextDrawReason = null;
-            mSyncBufferCallback = null;
+            mTransactionReadyCallback = null;
             mSyncBuffer = false;
             if (isInLocalSync()) {
                 mSyncGroup.markSyncReady();
@@ -3824,6 +3873,8 @@
                         ~WindowManager.LayoutParams
                                 .SOFT_INPUT_IS_FORWARD_NAVIGATION;
 
+                maybeFireAccessibilityWindowStateChangedEvent();
+
                 // Refocusing a window that has a focused view should fire a
                 // focus event for the view since the global focused view changed.
                 fireAccessibilityFocusEventIfHasFocusedNode();
@@ -3851,6 +3902,14 @@
         ensureTouchModeLocally(inTouchMode);
     }
 
+    private void maybeFireAccessibilityWindowStateChangedEvent() {
+        // Toasts are presented as notifications - don't present them as windows as well.
+        boolean isToast = mWindowAttributes != null && (mWindowAttributes.type == TYPE_TOAST);
+        if (!isToast && mView != null) {
+            mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        }
+    }
+
     private void fireAccessibilityFocusEventIfHasFocusedNode() {
         if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
             return;
@@ -3954,6 +4013,9 @@
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
         }
+        mMeasuredWidth = mView.getMeasuredWidth();
+        mMeasuredHeight = mView.getMeasuredHeight();
+        mViewMeasureDeferred = false;
     }
 
     /**
@@ -4049,7 +4111,7 @@
                         view.requestLayout();
                     }
                     measureHierarchy(host, lp, mView.getContext().getResources(),
-                            desiredWindowWidth, desiredWindowHeight);
+                            desiredWindowWidth, desiredWindowHeight, false /* forRootSizeOnly */);
                     mInLayout = true;
                     host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
 
@@ -4385,7 +4447,7 @@
             return false;
         }
 
-        final boolean fullRedrawNeeded = mFullRedrawNeeded || mSyncBufferCallback != null;
+        final boolean fullRedrawNeeded = mFullRedrawNeeded || mTransactionReadyCallback != null;
         mFullRedrawNeeded = false;
 
         mIsDrawing = true;
@@ -4393,9 +4455,9 @@
 
         addFrameCommitCallbackIfNeeded();
 
-        boolean usingAsyncReport = isHardwareEnabled() && mSyncBufferCallback != null;
+        boolean usingAsyncReport = isHardwareEnabled() && mTransactionReadyCallback != null;
         if (usingAsyncReport) {
-            registerCallbacksForSync(mSyncBuffer, mSyncBufferCallback);
+            registerCallbacksForSync(mSyncBuffer, mTransactionReadyCallback);
         } else if (mHasPendingTransactions) {
             // These callbacks are only needed if there's no sync involved and there were calls to
             // applyTransactionOnDraw. These callbacks check if the draw failed for any reason and
@@ -4446,10 +4508,11 @@
             }
 
             if (mSurfaceHolder != null && mSurface.isValid()) {
-                final SurfaceSyncGroup.SyncBufferCallback syncBufferCallback = mSyncBufferCallback;
+                final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback =
+                        mTransactionReadyCallback;
                 SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
-                        mHandler.post(() -> syncBufferCallback.onBufferReady(null)));
-                mSyncBufferCallback = null;
+                        mHandler.post(() -> transactionReadyCallback.onTransactionReady(null)));
+                mTransactionReadyCallback = null;
 
                 SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
 
@@ -4460,8 +4523,8 @@
                 }
             }
         }
-        if (mSyncBufferCallback != null && !usingAsyncReport) {
-            mSyncBufferCallback.onBufferReady(null);
+        if (mTransactionReadyCallback != null && !usingAsyncReport) {
+            mTransactionReadyCallback.onTransactionReady(null);
         }
         if (mPerformContentCapture) {
             performContentCaptureInitialReport();
@@ -5649,17 +5712,23 @@
                     break;
                 }
                 case MSG_SHOW_INSETS: {
+                    final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
+                    ImeTracker.get().onProgress(statsToken,
+                            ImeTracker.PHASE_CLIENT_HANDLE_SHOW_INSETS);
                     if (mView == null) {
                         Log.e(TAG,
                                 String.format("Calling showInsets(%d,%b) on window that no longer"
                                         + " has views.", msg.arg1, msg.arg2 == 1));
                     }
                     clearLowProfileModeIfNeeded(msg.arg1, msg.arg2 == 1);
-                    mInsetsController.show(msg.arg1, msg.arg2 == 1);
+                    mInsetsController.show(msg.arg1, msg.arg2 == 1, statsToken);
                     break;
                 }
                 case MSG_HIDE_INSETS: {
-                    mInsetsController.hide(msg.arg1, msg.arg2 == 1);
+                    final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
+                    ImeTracker.get().onProgress(statsToken,
+                            ImeTracker.PHASE_CLIENT_HANDLE_HIDE_INSETS);
+                    mInsetsController.hide(msg.arg1, msg.arg2 == 1, statsToken);
                     break;
                 }
                 case MSG_WINDOW_MOVED:
@@ -8141,8 +8210,8 @@
         final WindowConfiguration winConfigFromWm =
                 mLastReportedMergedConfiguration.getGlobalConfiguration().windowConfiguration;
         final WindowConfiguration winConfig = getCompatWindowConfiguration();
-        final int measuredWidth = mView.getMeasuredWidth();
-        final int measuredHeight = mView.getMeasuredHeight();
+        final int measuredWidth = mMeasuredWidth;
+        final int measuredHeight = mMeasuredHeight;
         final boolean relayoutAsync;
         if (LOCAL_LAYOUT
                 && (mViewFrameInfo.flags & FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED) == 0
@@ -8225,7 +8294,7 @@
                 mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
                 mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
             }
-            mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
+            mInvCompatScale = 1f / mTmpFrames.compatScale;
             CompatibilityInfo.applyOverrideScaleIfNeeded(mPendingMergedConfiguration);
             mInsetsController.onStateChanged(mTempInsets);
             mInsetsController.onControlsChanged(mTempControls);
@@ -8811,12 +8880,14 @@
         mHandler.obtainMessage(MSG_INSETS_CONTROL_CHANGED, args).sendToTarget();
     }
 
-    private void showInsets(@InsetsType int types, boolean fromIme) {
-        mHandler.obtainMessage(MSG_SHOW_INSETS, types, fromIme ? 1 : 0).sendToTarget();
+    private void showInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
+        mHandler.obtainMessage(MSG_SHOW_INSETS, types, fromIme ? 1 : 0, statsToken).sendToTarget();
     }
 
-    private void hideInsets(@InsetsType int types, boolean fromIme) {
-        mHandler.obtainMessage(MSG_HIDE_INSETS, types, fromIme ? 1 : 0).sendToTarget();
+    private void hideInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
+        mHandler.obtainMessage(MSG_HIDE_INSETS, types, fromIme ? 1 : 0, statsToken).sendToTarget();
     }
 
     public void dispatchMoved(int newX, int newY) {
@@ -9114,6 +9185,9 @@
             mConsumeBatchedInputScheduled = true;
             mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
                     mConsumedBatchedInputRunnable, null);
+            if (mAttachInfo.mThreadedRenderer != null) {
+                mAttachInfo.mThreadedRenderer.notifyCallbackPending();
+            }
         }
     }
 
@@ -9307,6 +9381,9 @@
                 mViews.add(view);
                 postIfNeededLocked();
             }
+            if (mAttachInfo.mThreadedRenderer != null) {
+                mAttachInfo.mThreadedRenderer.notifyCallbackPending();
+            }
         }
 
         public void addViewRect(AttachInfo.InvalidateInfo info) {
@@ -9314,6 +9391,9 @@
                 mViewRects.add(info);
                 postIfNeededLocked();
             }
+            if (mAttachInfo.mThreadedRenderer != null) {
+                mAttachInfo.mThreadedRenderer.notifyCallbackPending();
+            }
         }
 
         public void removeView(View view) {
@@ -10180,7 +10260,8 @@
         }
 
         @Override
-        public void showInsets(@InsetsType int types, boolean fromIme) {
+        public void showInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (fromIme) {
                 ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#showInsets",
@@ -10188,13 +10269,16 @@
                         null /* icProto */);
             }
             if (viewAncestor != null) {
-                viewAncestor.showInsets(types, fromIme);
+                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
+                viewAncestor.showInsets(types, fromIme, statsToken);
+            } else {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
             }
         }
 
         @Override
-        public void hideInsets(@InsetsType int types, boolean fromIme) {
-
+        public void hideInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (fromIme) {
                 ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#hideInsets",
@@ -10202,7 +10286,10 @@
                         null /* icProto */);
             }
             if (viewAncestor != null) {
-                viewAncestor.hideInsets(types, fromIme);
+                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
+                viewAncestor.hideInsets(types, fromIme, statsToken);
+            } else {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
             }
         }
 
@@ -10691,6 +10778,25 @@
                         .notifyOutsideTouchClientThread();
             }
         }
+
+        @Override
+        public void takeScreenshotOfWindow(int interactionId,
+                ScreenCapture.ScreenCaptureListener listener,
+                IAccessibilityInteractionConnectionCallback callback) {
+            ViewRootImpl viewRootImpl = mViewRootImpl.get();
+            if (viewRootImpl != null && viewRootImpl.mView != null) {
+                viewRootImpl.getAccessibilityInteractionController()
+                        .takeScreenshotOfWindowClientThread(interactionId, listener, callback);
+            } else {
+                try {
+                    callback.sendTakeScreenshotOfWindowError(
+                            AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+                            interactionId);
+                } catch (RemoteException re) {
+                    /* best effort - ignore */
+                }
+            }
+        }
     }
 
     /**
@@ -11097,7 +11203,7 @@
     }
 
     private void registerCallbacksForSync(boolean syncBuffer,
-            final SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
+            final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
         if (!isHardwareEnabled()) {
             return;
         }
@@ -11124,7 +11230,7 @@
                 // pendingDrawFinished.
                 if ((syncResult
                         & (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
-                    syncBufferCallback.onBufferReady(
+                    transactionReadyCallback.onTransactionReady(
                             mBlastBufferQueue.gatherPendingTransactions(frame));
                     return null;
                 }
@@ -11134,7 +11240,8 @@
                 }
 
                 if (syncBuffer) {
-                    mBlastBufferQueue.syncNextTransaction(syncBufferCallback::onBufferReady);
+                    mBlastBufferQueue.syncNextTransaction(
+                            transactionReadyCallback::onTransactionReady);
                 }
 
                 return didProduceBuffer -> {
@@ -11154,7 +11261,7 @@
                         // since the frame didn't draw on this vsync. It's possible the frame will
                         // draw later, but it's better to not be sync than to block on a frame that
                         // may never come.
-                        syncBufferCallback.onBufferReady(
+                        transactionReadyCallback.onTransactionReady(
                                 mBlastBufferQueue.gatherPendingTransactions(frame));
                         return;
                     }
@@ -11163,7 +11270,7 @@
                     // syncNextTransaction callback. Instead, just report back to the Syncer so it
                     // knows that this sync request is complete.
                     if (!syncBuffer) {
-                        syncBufferCallback.onBufferReady(null);
+                        transactionReadyCallback.onTransactionReady(null);
                     }
                 };
             }
@@ -11172,48 +11279,55 @@
 
     public final SurfaceSyncGroup.SyncTarget mSyncTarget = new SurfaceSyncGroup.SyncTarget() {
         @Override
-        public void onReadyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
-            readyToSync(syncBufferCallback);
-        }
+        public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+                SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
+            updateSyncInProgressCount(parentSyncGroup);
+            if (!isInLocalSync()) {
+                // Always sync the buffer if the sync request did not come from VRI.
+                mSyncBuffer = true;
+            }
 
-        @Override
-        public void onSyncComplete() {
-            mHandler.postAtFrontOfQueue(() -> {
-                if (--mNumSyncsInProgress == 0 && mAttachInfo.mThreadedRenderer != null) {
-                    HardwareRenderer.setRtAnimationsEnabled(true);
-                }
-            });
+            if (mTransactionReadyCallback != null) {
+                Log.d(mTag, "Already set sync for the next draw.");
+                mTransactionReadyCallback.onTransactionReady(null);
+            }
+            if (DEBUG_BLAST) {
+                Log.d(mTag, "Setting syncFrameCallback");
+            }
+            mTransactionReadyCallback = transactionReadyCallback;
+            if (!mIsInTraversal && !mTraversalScheduled) {
+                scheduleTraversals();
+            }
         }
     };
 
+    private final Executor mSimpleExecutor = Runnable::run;
+
+    private void updateSyncInProgressCount(SurfaceSyncGroup syncGroup) {
+        if (mAttachInfo.mThreadedRenderer == null) {
+            return;
+        }
+
+        synchronized (sSyncProgressLock) {
+            if (sNumSyncsInProgress++ == 0) {
+                HardwareRenderer.setRtAnimationsEnabled(false);
+            }
+        }
+
+        syncGroup.addSyncCompleteCallback(mSimpleExecutor, () -> {
+            synchronized (sSyncProgressLock) {
+                if (--sNumSyncsInProgress == 0) {
+                    HardwareRenderer.setRtAnimationsEnabled(true);
+                }
+            }
+        });
+    }
+
     @Override
     public SurfaceSyncGroup.SyncTarget getSyncTarget() {
         return mSyncTarget;
     }
 
-    private void readyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
-        mNumSyncsInProgress++;
-        if (!isInLocalSync()) {
-            // Always sync the buffer if the sync request did not come from VRI.
-            mSyncBuffer = true;
-        }
-        if (mAttachInfo.mThreadedRenderer != null) {
-            HardwareRenderer.setRtAnimationsEnabled(false);
-        }
-
-        if (mSyncBufferCallback != null) {
-            Log.d(mTag, "Already set sync for the next draw.");
-            mSyncBufferCallback.onBufferReady(null);
-        }
-        if (DEBUG_BLAST) {
-            Log.d(mTag, "Setting syncFrameCallback");
-        }
-        mSyncBufferCallback = syncBufferCallback;
-        if (!mIsInTraversal && !mTraversalScheduled) {
-            scheduleTraversals();
-        }
-    }
-
     void mergeSync(SurfaceSyncGroup otherSyncGroup) {
         if (!isInLocalSync()) {
             return;
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 2a76c4e..fd55d8d 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -29,6 +29,7 @@
 import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.TAPPABLE_ELEMENT;
 import static android.view.WindowInsets.Type.all;
+import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.indexOf;
 import static android.view.WindowInsets.Type.systemBars;
@@ -41,7 +42,6 @@
 import android.content.Intent;
 import android.graphics.Insets;
 import android.graphics.Rect;
-import android.util.SparseArray;
 import android.view.View.OnApplyWindowInsetsListener;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.inputmethod.EditorInfo;
@@ -82,6 +82,7 @@
     @Nullable private final DisplayCutout mDisplayCutout;
     @Nullable private final RoundedCorners mRoundedCorners;
     @Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds;
+    @Nullable private final DisplayShape mDisplayShape;
 
     /**
      * In multi-window we force show the navigation bar. Because we don't want that the surface size
@@ -114,24 +115,9 @@
     public static final @NonNull WindowInsets CONSUMED;
 
     static {
-        CONSUMED = new WindowInsets((Rect) null, null, false, false, null);
-    }
-
-    /**
-     * Construct a new WindowInsets from individual insets.
-     *
-     * A {@code null} inset indicates that the respective inset is consumed.
-     *
-     * @hide
-     * @deprecated Use {@link WindowInsets(SparseArray, SparseArray, boolean, boolean, DisplayCutout)}
-     */
-    @Deprecated
-    public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect, boolean isRound,
-            boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) {
-        this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect),
-                createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)),
-                isRound, alwaysConsumeSystemBars, displayCutout, null, null,
-                systemBars(), false /* compatIgnoreVisibility */);
+        CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null),
+                createCompatVisibilityMap(createCompatTypeMap(null)), false, false, null, null,
+                null, null, systemBars(), false);
     }
 
     /**
@@ -153,6 +139,7 @@
             boolean alwaysConsumeSystemBars, DisplayCutout displayCutout,
             RoundedCorners roundedCorners,
             PrivacyIndicatorBounds privacyIndicatorBounds,
+            DisplayShape displayShape,
             @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) {
         mSystemWindowInsetsConsumed = typeInsetsMap == null;
         mTypeInsetsMap = mSystemWindowInsetsConsumed
@@ -176,6 +163,7 @@
 
         mRoundedCorners = roundedCorners;
         mPrivacyIndicatorBounds = privacyIndicatorBounds;
+        mDisplayShape = displayShape;
     }
 
     /**
@@ -190,6 +178,7 @@
                 src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src),
                 src.mRoundedCorners,
                 src.mPrivacyIndicatorBounds,
+                src.mDisplayShape,
                 src.mCompatInsetsTypes,
                 src.mCompatIgnoreVisibility);
     }
@@ -243,15 +232,18 @@
     @UnsupportedAppUsage
     public WindowInsets(Rect systemWindowInsets) {
         this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null,
-                null, null, systemBars(), false /* compatIgnoreVisibility */);
+                null, null, null, systemBars(), false /* compatIgnoreVisibility */);
     }
 
     /**
      * Creates a indexOf(type) -> inset map for which the {@code insets} is just mapped to
      * {@link Type#statusBars()} and {@link Type#navigationBars()}, depending on the
      * location of the inset.
+     *
+     * @hide
      */
-    private static Insets[] createCompatTypeMap(@Nullable Rect insets) {
+    @VisibleForTesting
+    public static Insets[] createCompatTypeMap(@Nullable Rect insets) {
         if (insets == null) {
             return null;
         }
@@ -270,6 +262,10 @@
                 Insets.of(insets.left, 0, insets.right, insets.bottom);
     }
 
+    /**
+     * @hide
+     */
+    @VisibleForTesting
     private static boolean[] createCompatVisibilityMap(@Nullable Insets[] typeInsetsMap) {
         boolean[] typeVisibilityMap = new boolean[SIZE];
         if (typeInsetsMap == null) {
@@ -532,6 +528,17 @@
     }
 
     /**
+     * Returns the display shape in the coordinate space of the window.
+     *
+     * @return the display shape
+     * @see DisplayShape
+     */
+    @Nullable
+    public DisplayShape getDisplayShape() {
+        return mDisplayShape;
+    }
+
+    /**
      * Returns a copy of this WindowInsets with the cutout fully consumed.
      *
      * @return A modified copy of this WindowInsets
@@ -546,7 +553,7 @@
                 mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
                 mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeSystemBars,
-                null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds,
+                null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
                 mCompatInsetsTypes, mCompatIgnoreVisibility);
     }
 
@@ -597,8 +604,11 @@
         return new WindowInsets(null, null,
                 mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeSystemBars,
-                displayCutoutCopyConstructorArgument(this),
-                mRoundedCorners, mPrivacyIndicatorBounds, mCompatInsetsTypes,
+                // If the system window insets types contain displayCutout, we should also consume
+                // it.
+                (mCompatInsetsTypes & displayCutout()) != 0
+                        ? null : displayCutoutCopyConstructorArgument(this),
+                mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes,
                 mCompatIgnoreVisibility);
     }
 
@@ -907,6 +917,8 @@
         result.append(mPrivacyIndicatorBounds != null ? "privacyIndicatorBounds="
                 + mPrivacyIndicatorBounds : "");
         result.append("\n    ");
+        result.append(mDisplayShape != null ? "displayShape=" + mDisplayShape : "");
+        result.append("\n    ");
         result.append("compatInsetsTypes=" + mCompatInsetsTypes);
         result.append("\n    ");
         result.append("compatIgnoreVisibility=" + mCompatIgnoreVisibility);
@@ -1014,6 +1026,7 @@
                 mPrivacyIndicatorBounds == null
                         ? null
                         : mPrivacyIndicatorBounds.inset(left, top, right, bottom),
+                mDisplayShape,
                 mCompatInsetsTypes, mCompatIgnoreVisibility);
     }
 
@@ -1033,7 +1046,8 @@
                 && Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap)
                 && Objects.equals(mDisplayCutout, that.mDisplayCutout)
                 && Objects.equals(mRoundedCorners, that.mRoundedCorners)
-                && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds);
+                && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds)
+                && Objects.equals(mDisplayShape, that.mDisplayShape);
     }
 
     @Override
@@ -1041,7 +1055,7 @@
         return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
                 Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
                 mAlwaysConsumeSystemBars, mSystemWindowInsetsConsumed, mStableInsetsConsumed,
-                mDisplayCutoutConsumed, mPrivacyIndicatorBounds);
+                mDisplayCutoutConsumed, mPrivacyIndicatorBounds, mDisplayShape);
     }
 
 
@@ -1102,6 +1116,7 @@
 
         private DisplayCutout mDisplayCutout;
         private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS;
+        private DisplayShape mDisplayShape = DisplayShape.NONE;
 
         private boolean mIsRound;
         private boolean mAlwaysConsumeSystemBars;
@@ -1133,6 +1148,7 @@
             mIsRound = insets.mIsRound;
             mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars;
             mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
+            mDisplayShape = insets.mDisplayShape;
         }
 
         /**
@@ -1377,6 +1393,19 @@
             return this;
         }
 
+        /**
+         * Sets the display shape.
+         *
+         * @see #getDisplayShape().
+         * @param displayShape the display shape.
+         * @return itself.
+         */
+        @NonNull
+        public Builder setDisplayShape(@NonNull DisplayShape displayShape) {
+            mDisplayShape = displayShape;
+            return this;
+        }
+
         /** @hide */
         @NonNull
         public Builder setRound(boolean round) {
@@ -1401,7 +1430,8 @@
             return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
                     mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
                     mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout, mRoundedCorners,
-                    mPrivacyIndicatorBounds, systemBars(), false /* compatIgnoreVisibility */);
+                    mPrivacyIndicatorBounds, mDisplayShape, systemBars(),
+                    false /* compatIgnoreVisibility */);
         }
     }
 
@@ -1425,8 +1455,8 @@
 
         static final int WINDOW_DECOR = 1 << 8;
 
-        static final int GENERIC_OVERLAYS = 1 << 9;
-        static final int LAST = GENERIC_OVERLAYS;
+        static final int SYSTEM_OVERLAYS = 1 << 9;
+        static final int LAST = SYSTEM_OVERLAYS;
         static final int SIZE = 10;
 
         static final int DEFAULT_VISIBLE = ~IME;
@@ -1451,7 +1481,7 @@
                     return 7;
                 case WINDOW_DECOR:
                     return 8;
-                case GENERIC_OVERLAYS:
+                case SYSTEM_OVERLAYS:
                     return 9;
                 default:
                     throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST,"
@@ -1463,37 +1493,37 @@
         public static String toString(@InsetsType int types) {
             StringBuilder result = new StringBuilder();
             if ((types & STATUS_BARS) != 0) {
-                result.append("statusBars |");
+                result.append("statusBars ");
             }
             if ((types & NAVIGATION_BARS) != 0) {
-                result.append("navigationBars |");
+                result.append("navigationBars ");
             }
             if ((types & CAPTION_BAR) != 0) {
-                result.append("captionBar |");
+                result.append("captionBar ");
             }
             if ((types & IME) != 0) {
-                result.append("ime |");
+                result.append("ime ");
             }
             if ((types & SYSTEM_GESTURES) != 0) {
-                result.append("systemGestures |");
+                result.append("systemGestures ");
             }
             if ((types & MANDATORY_SYSTEM_GESTURES) != 0) {
-                result.append("mandatorySystemGestures |");
+                result.append("mandatorySystemGestures ");
             }
             if ((types & TAPPABLE_ELEMENT) != 0) {
-                result.append("tappableElement |");
+                result.append("tappableElement ");
             }
             if ((types & DISPLAY_CUTOUT) != 0) {
-                result.append("displayCutout |");
+                result.append("displayCutout ");
             }
             if ((types & WINDOW_DECOR) != 0) {
-                result.append("windowDecor |");
+                result.append("windowDecor ");
             }
-            if ((types & GENERIC_OVERLAYS) != 0) {
-                result.append("genericOverlays |");
+            if ((types & SYSTEM_OVERLAYS) != 0) {
+                result.append("systemOverlays ");
             }
             if (result.length() > 0) {
-                result.delete(result.length() - 2, result.length());
+                result.delete(result.length() - 1, result.length());
             }
             return result.toString();
         }
@@ -1505,7 +1535,7 @@
         @Retention(RetentionPolicy.SOURCE)
         @IntDef(flag = true, value = {STATUS_BARS, NAVIGATION_BARS, CAPTION_BAR, IME, WINDOW_DECOR,
                 SYSTEM_GESTURES, MANDATORY_SYSTEM_GESTURES, TAPPABLE_ELEMENT, DISPLAY_CUTOUT,
-                GENERIC_OVERLAYS})
+                SYSTEM_OVERLAYS})
         public @interface InsetsType {
         }
 
@@ -1593,11 +1623,27 @@
         }
 
         /**
+         * System overlays represent the insets caused by the system visible elements. Unlike
+         * {@link #navigationBars()} or {@link #statusBars()}, system overlays might not be
+         * hidden by the client.
+         *
+         * For compatibility reasons, this type is included in {@link #systemBars()}. In this
+         * way, views which fit {@link #systemBars()} fit {@link #systemOverlays()}.
+         *
+         * Examples include climate controls, multi-tasking affordances, etc.
+         *
+         * @return An insets type representing the system overlays.
+         */
+        public static @InsetsType int systemOverlays() {
+            return SYSTEM_OVERLAYS;
+        }
+
+        /**
          * @return All system bars. Includes {@link #statusBars()}, {@link #captionBar()} as well as
-         *         {@link #navigationBars()}, but not {@link #ime()}.
+         *         {@link #navigationBars()}, {@link #systemOverlays()}, but not {@link #ime()}.
          */
         public static @InsetsType int systemBars() {
-            return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR | GENERIC_OVERLAYS;
+            return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR | SYSTEM_OVERLAYS;
         }
 
         /**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index cc85181..67a6e89 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -819,25 +819,36 @@
     public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
 
     /**
-     * Application level {@link android.content.pm.PackageManager.Property} tag for developers to
-     * provide consent for their app to allow OEMs to manually provide ActivityEmbedding split
-     * rule configuration on behalf of the app.
+     * Application-level
+     * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+     * tag that specifies whether OEMs are permitted to provide activity
+     * embedding split-rule configurations on behalf of the app.
      *
-     * <p>If {@code true}, the system can override the windowing behaviors for the app, such as
-     * showing some activities side-by-side. In this case, it will report that ActivityEmbedding
-     * APIs are disabled for the app to avoid conflict.
+     * <p>If {@code true}, the system is permitted to override the app's
+     * windowing behavior and implement activity embedding split rules, such as
+     * displaying activities side by side. A system override informs the app
+     * that the activity embedding APIs are disabled so the app will not provide
+     * its own activity embedding rules, which would conflict with the system's
+     * rules.
      *
-     * <p>If {@code false}, the system can't override the window behavior for the app. It should
-     * be used if the app wants to provide their own ActivityEmbedding split rules, or if the app
-     * wants to opt-out of system overrides for any other reason.
+     * <p>If {@code false}, the system is not permitted to override the
+     * windowing behavior of the app. Set the property to {@code false} if the
+     * app provides its own activity embedding split rules, or if you want to
+     * prevent the system override for any other reason.
      *
-     * <p>Default is {@code false}.
+     * <p>The default value is {@code false}.
      *
-     * <p>The system enforcement is added in Android 14, but some devices may start following the
-     * requirement before that. The best practice for apps is to always explicitly set this
-     * property in AndroidManifest instead of relying on the default value.
+     * <p class="note"><b>Note:</b> Refusal to permit the system override is not
+     * enforceable. OEMs can override the app's activity embedding
+     * implementation whether or not this property is specified and set to
+     * <code>false</code>. The property is, in effect, a hint to OEMs.
      *
-     * <p>Example usage:
+     * <p>OEMs can implement activity embedding on any API level. The best
+     * practice for apps is to always explicitly set this property in the app
+     * manifest file regardless of targeted API level rather than rely on the
+     * default value.
+     *
+     * <p><b>Syntax:</b>
      * <pre>
      * &lt;application&gt;
      *   &lt;property
@@ -3408,6 +3419,11 @@
          * alt="Screenshot of an activity on a display with a cutout on the long edge in portrait,
          *         letterbox is applied."/>
          *
+         * <p>
+         * Note: Android might not allow the content view to overlap the system bars in view level.
+         * To override this behavior and allow content to be able to extend into the cutout area,
+         * call {@link Window#setDecorFitsSystemWindows(boolean)} with {@code false}.
+         *
          * @see DisplayCutout
          * @see WindowInsets#getDisplayCutout()
          * @see #layoutInDisplayCutoutMode
@@ -3443,6 +3459,11 @@
          * In this mode, the window extends under cutouts on the all edges of the display in both
          * portrait and landscape, regardless of whether the window is hiding the system bars.
          *
+         * <p>
+         * Note: Android might not allow the content view to overlap the system bars in view level.
+         * To override this behavior and allow content to be able to extend into the cutout area,
+         * call {@link Window#setDecorFitsSystemWindows(boolean)} with {@code false}.
+         *
          * @see DisplayCutout
          * @see WindowInsets#getDisplayCutout()
          * @see #layoutInDisplayCutoutMode
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 6dc9011..5c4305c 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -366,7 +366,7 @@
         List<DisplayInfo> possibleDisplayInfos;
         try {
             possibleDisplayInfos = WindowManagerGlobal.getWindowManagerService()
-                    .getPossibleDisplayInfo(displayId, mContext.getPackageName());
+                    .getPossibleDisplayInfo(displayId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
new file mode 100644
index 0000000..44b6deb
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
@@ -0,0 +1,330 @@
+/*
+ * 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.IAccessibilityServiceConnection;
+import android.accessibilityservice.MagnificationConfig;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+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.Collections;
+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 by accessibility, such as private displays, 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.
+ *
+ * <p>
+ * Avoid using the app's main thread. Proxy methods such as {@link #getWindows} and node methods
+ * like {@link AccessibilityNodeInfo#getChild(int)} will happen frequently. Node methods may also
+ * wait on the displayed app's UI thread to obtain accurate screen data.
+ *
+ * <p>
+ * To get a list of {@link AccessibilityServiceInfo}s that have populated {@link ComponentName}s and
+ * {@link ResolveInfo}s, retrieve the list using {@link #getInstalledAndEnabledServices()} after
+ * {@link #onProxyConnected()} has been called.
+ *
+ * @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;
+    }
+
+    /**
+     * Handles {@link android.view.accessibility.AccessibilityEvent}s.
+     * <p>
+     * AccessibilityEvents represent changes to the UI, or what parts of the node tree have changed.
+     * AccessibilityDisplayProxy should use these to query new UI and send appropriate feedback
+     * to their users.
+     * <p>
+     * For example, a {@link AccessibilityEvent#TYPE_WINDOWS_CHANGED} indicates a change in windows,
+     * so a proxy may query {@link #getWindows} to obtain updated UI and potentially inform of a new
+     * window title. Or a proxy may emit an earcon on a
+     * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
+     */
+    public void onAccessibilityEvent(@NonNull AccessibilityEvent event) {
+        // Default no-op
+    }
+
+    /**
+     * Handles a successful system connection after
+     * {@link AccessibilityManager#registerDisplayProxy(AccessibilityDisplayProxy)} is called.
+     *
+     * <p>
+     * At this point, querying for UI is available and {@link AccessibilityEvent}s will begin being
+     * sent. An AccessibilityDisplayProxy may instantiate core infrastructure components here.
+     */
+    public void onProxyConnected() {
+        // Default no-op
+    }
+
+    /**
+     * Handles a request to interrupt the accessibility feedback.
+     * <p>
+     * AccessibilityDisplayProxy should interrupt the accessibility activity occurring on its
+     * display. For example, a screen reader may interrupt speech.
+     *
+     * @see AccessibilityManager#interrupt()
+     * @see AccessibilityService#onInterrupt()
+     */
+    public void interrupt() {
+        // Default no-op
+    }
+
+    /**
+     * Gets the focus of the window specified by {@code windowInfo}.
+     *
+     * @param windowInfo the window to search
+     * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
+     * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
+     * @return The node info of the focused view or null.
+     * @hide
+     * TODO(254545943): Do not expose until support for accessibility focus and/or input is in place
+     */
+    @Nullable
+    public AccessibilityNodeInfo findFocus(@NonNull AccessibilityWindowInfo windowInfo, int focus) {
+        AccessibilityNodeInfo windowRoot = windowInfo.getRoot();
+        return windowRoot != null ? windowRoot.findFocus(focus) : null;
+    }
+
+    /**
+     * Gets the windows of the tracked display.
+     *
+     * @see AccessibilityService#getWindows()
+     */
+    @NonNull
+    public List<AccessibilityWindowInfo> getWindows() {
+        return AccessibilityInteractionClient.getInstance().getWindowsOnDisplay(mConnectionId,
+                mDisplayId);
+    }
+
+    /**
+     * Sets the list of {@link AccessibilityServiceInfo}s describing the services interested in the
+     * {@link AccessibilityDisplayProxy}'s display.
+     *
+     * <p>These represent a11y features and services that are installed and running. These should
+     * not include {@link AccessibilityService}s installed on the phone.
+     *
+     * @param installedAndEnabledServices the list of installed and running a11y services.
+     */
+    public void setInstalledAndEnabledServices(
+            @NonNull List<AccessibilityServiceInfo> installedAndEnabledServices) {
+        mInstalledAndEnabledServices = installedAndEnabledServices;
+        sendServiceInfos();
+    }
+
+    /**
+     * Sets the {@link AccessibilityServiceInfo} for this service if the latter is
+     * properly set and there is an {@link IAccessibilityServiceConnection} to the
+     * AccessibilityManagerService.
+     */
+    private void sendServiceInfos() {
+        IAccessibilityServiceConnection connection =
+                AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+        if (mInstalledAndEnabledServices != null && mInstalledAndEnabledServices.size() > 0
+                && connection != null) {
+            try {
+                connection.setInstalledAndEnabledServices(mInstalledAndEnabledServices);
+                AccessibilityInteractionClient.getInstance().clearCache(mConnectionId);
+            } catch (RemoteException re) {
+                Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfos", re);
+                re.rethrowFromSystemServer();
+            }
+        }
+        mInstalledAndEnabledServices = null;
+    }
+
+    /**
+     * Gets the list of {@link AccessibilityServiceInfo}s describing the services interested in the
+     * {@link AccessibilityDisplayProxy}'s display.
+     *
+     * @return The {@link AccessibilityServiceInfo}s of interested services.
+     * @see AccessibilityServiceInfo
+     */
+    @NonNull
+    public final List<AccessibilityServiceInfo> getInstalledAndEnabledServices() {
+        IAccessibilityServiceConnection connection =
+                AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+        if (connection != null) {
+            try {
+                return connection.getInstalledAndEnabledServices();
+            } catch (RemoteException re) {
+                Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
+                re.rethrowFromSystemServer();
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * An IAccessibilityServiceClient that handles interrupts, accessibility events, and system
+     * connection.
+     */
+    private class IAccessibilityServiceClientImpl extends
+            AccessibilityService.IAccessibilityServiceClientWrapper {
+
+        IAccessibilityServiceClientImpl(Context context, Executor executor) {
+            super(context, executor, new AccessibilityService.Callbacks() {
+                @Override
+                public void onAccessibilityEvent(AccessibilityEvent event) {
+                    // TODO(254545943): Remove check when event processing is done more upstream in
+                    // AccessibilityManagerService.
+                    if (event.getDisplayId() == mDisplayId) {
+                        AccessibilityDisplayProxy.this.onAccessibilityEvent(event);
+                    }
+                }
+
+                @Override
+                public void onInterrupt() {
+                    AccessibilityDisplayProxy.this.interrupt();
+                }
+
+                @Override
+                public void onServiceConnected() {
+                    AccessibilityDisplayProxy.this.sendServiceInfos();
+                    AccessibilityDisplayProxy.this.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/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index b0cf504..a52a99b 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -24,6 +24,7 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.View;
 
 import com.android.internal.util.BitUtils;
 
@@ -519,6 +520,9 @@
 
     /**
      * Represents the event of an application making an announcement.
+     * <p>
+     * In general, follow the practices described in
+     * {@link View#announceForAccessibility(CharSequence)}.
      */
     public static final int TYPE_ANNOUNCEMENT = 0x00004000;
 
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index e3ffc9d..06a6de9 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -22,7 +22,9 @@
 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_MASK;
 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
 
+import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -31,17 +33,21 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.LongSparseArray;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.util.SparseLongArray;
 import android.view.Display;
 import android.view.ViewConfiguration;
+import android.window.ScreenCapture;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -53,6 +59,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Queue;
+import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -146,6 +153,10 @@
 
     private boolean mPerformAccessibilityActionResult;
 
+    // SparseArray of interaction ID -> screenshot executor+callback.
+    private final SparseArray<Pair<Executor, AccessibilityService.TakeScreenshotCallback>>
+            mTakeScreenshotOfWindowCallbacks = new SparseArray<>();
+
     private Message mSameThreadMessage;
 
     private int mInteractionIdWaitingForPrefetchResult = -1;
@@ -447,15 +458,21 @@
      * @return The {@link AccessibilityWindowInfo} list.
      */
     public List<AccessibilityWindowInfo> getWindows(int connectionId) {
-        final SparseArray<List<AccessibilityWindowInfo>> windows =
-                getWindowsOnAllDisplays(connectionId);
-        if (windows.size() > 0) {
-            return windows.valueAt(Display.DEFAULT_DISPLAY);
-        }
-        return Collections.emptyList();
+        return getWindowsOnDisplay(connectionId, Display.DEFAULT_DISPLAY);
     }
 
     /**
+     * Gets the info for all windows of the specified display.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @return The {@link AccessibilityWindowInfo} list belonging to {@code displayId}.
+     */
+    public List<AccessibilityWindowInfo> getWindowsOnDisplay(int connectionId, int displayId) {
+        final SparseArray<List<AccessibilityWindowInfo>> windows =
+                getWindowsOnAllDisplays(connectionId);
+        return windows.get(displayId, Collections.emptyList());
+    }
+    /**
      * Gets the info for all windows of all displays.
      *
      * @param connectionId The id of a connection for interacting with the system.
@@ -779,6 +796,59 @@
     }
 
     /**
+     * Takes a screenshot of the window with the provided {@code accessibilityWindowId} and
+     * returns the answer asynchronously. This async behavior is similar to {@link
+     * AccessibilityService#takeScreenshot} but unlike other methods in this class which perform
+     * synchronous waiting in the AccessibilityService client.
+     *
+     * @see AccessibilityService#takeScreenshotOfWindow
+     */
+    public void takeScreenshotOfWindow(int connectionId, int accessibilityWindowId,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AccessibilityService.TakeScreenshotCallback callback) {
+        synchronized (mInstanceLock) {
+            try {
+                IAccessibilityServiceConnection connection = getConnection(connectionId);
+                if (connection == null) {
+                    executor.execute(() -> callback.onFailure(
+                            AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR));
+                    return;
+                }
+                final long identityToken = Binder.clearCallingIdentity();
+                try {
+                    final int interactionId = mInteractionIdCounter.getAndIncrement();
+                    mTakeScreenshotOfWindowCallbacks.put(interactionId,
+                            Pair.create(executor, callback));
+                    // Create a ScreenCaptureListener to receive the screenshot directly from
+                    // SurfaceFlinger instead of requiring an extra IPC from the app:
+                    //   A11yService -> App -> SurfaceFlinger -> A11yService
+                    ScreenCapture.ScreenCaptureListener listener =
+                            new ScreenCapture.ScreenCaptureListener(
+                                    screenshot -> sendWindowScreenshotSuccess(screenshot,
+                                            interactionId));
+                    connection.takeScreenshotOfWindow(accessibilityWindowId, interactionId,
+                            listener, this);
+                    new Handler(Looper.getMainLooper()).postDelayed(() -> {
+                        synchronized (mInstanceLock) {
+                            // Notify failure if we still haven't sent a response after timeout.
+                            if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+                                sendTakeScreenshotOfWindowError(
+                                        AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+                                        interactionId);
+                            }
+                        }
+                    }, TIMEOUT_INTERACTION_MILLIS);
+                } finally {
+                    Binder.restoreCallingIdentity(identityToken);
+                }
+            } catch (RemoteException re) {
+                executor.execute(() -> callback.onFailure(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR));
+            }
+        }
+    }
+
+    /**
      * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
      * insensitive containment. The search is performed in the window whose
      * id is specified and starts from the node whose accessibility id is
@@ -1254,6 +1324,55 @@
     }
 
     /**
+     * Sends the result of a window screenshot request to the requesting client.
+     *
+     * {@link #takeScreenshotOfWindow} does not perform synchronous waiting, so this method
+     * does not notify any wait lock.
+     */
+    private void sendWindowScreenshotSuccess(ScreenCapture.ScreenshotHardwareBuffer screenshot,
+            int interactionId) {
+        if (screenshot == null) {
+            sendTakeScreenshotOfWindowError(
+                    AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+            return;
+        }
+        synchronized (mInstanceLock) {
+            if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+                final AccessibilityService.ScreenshotResult result =
+                        new AccessibilityService.ScreenshotResult(screenshot.getHardwareBuffer(),
+                                screenshot.getColorSpace(), SystemClock.uptimeMillis());
+                final Pair<Executor, AccessibilityService.TakeScreenshotCallback> pair =
+                        mTakeScreenshotOfWindowCallbacks.get(interactionId);
+                final Executor executor = pair.first;
+                final AccessibilityService.TakeScreenshotCallback callback = pair.second;
+                executor.execute(() -> callback.onSuccess(result));
+                mTakeScreenshotOfWindowCallbacks.remove(interactionId);
+            }
+        }
+    }
+
+    /**
+     * Sends an error code for a window screenshot request to the requesting client.
+     *
+     * @param errorCode The error code from {@link AccessibilityService.ScreenshotErrorCode}.
+     * @param interactionId The interaction id of the request.
+     */
+    @Override
+    public void sendTakeScreenshotOfWindowError(
+            @AccessibilityService.ScreenshotErrorCode int errorCode, int interactionId) {
+        synchronized (mInstanceLock) {
+            if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+                final Pair<Executor, AccessibilityService.TakeScreenshotCallback> pair =
+                        mTakeScreenshotOfWindowCallbacks.get(interactionId);
+                final Executor executor = pair.first;
+                final AccessibilityService.TakeScreenshotCallback callback = pair.second;
+                executor.execute(() -> callback.onFailure(errorCode));
+                mTakeScreenshotOfWindowCallbacks.remove(interactionId);
+            }
+        }
+    }
+
+    /**
      * Clears the result state.
      */
     private void clearResultLocked() {
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/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index d07a797..b91199d 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -67,6 +67,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -810,6 +811,8 @@
 
     private static final int BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE = 0x0800000;
 
+    private static final int BOOLEAN_PROPERTY_REQUEST_INITIAL_ACCESSIBILITY_FOCUS = 1 << 24;
+
     /**
      * Bits that provide the id of a virtual descendant of a view.
      */
@@ -879,6 +882,8 @@
     private long mTraversalBefore = UNDEFINED_NODE_ID;
     private long mTraversalAfter = UNDEFINED_NODE_ID;
 
+    private long mMinDurationBetweenContentChanges = 0;
+
     private int mBooleanProperties;
     private final Rect mBoundsInParent = new Rect();
     private final Rect mBoundsInScreen = new Rect();
@@ -897,6 +902,7 @@
     private CharSequence mTooltipText;
     private String mViewIdResourceName;
     private String mUniqueId;
+    private CharSequence mContainerTitle;
     private ArrayList<String> mExtraDataKeys;
 
     @UnsupportedAppUsage
@@ -1781,6 +1787,35 @@
     }
 
     /**
+     * Sets the minimum time duration between two content change events, which is used in throttling
+     * content change events in accessibility services.
+     *
+     * <p>
+     * Example: An app can set MinMillisBetweenContentChanges as 1 min for a view which sends
+     * content change events to accessibility services one event per second.
+     * Accessibility service will throttle those content change events and only handle one event
+     * per minute for that view.
+     * </p>
+     *
+     * @see AccessibilityEvent#getContentChangeTypes for all content change types.
+     * @param minDurationBetweenContentChanges the minimum duration between content change events.
+     *                                         Negative duration would be treated as zero.
+     */
+    public void setMinDurationBetweenContentChanges(
+            @NonNull Duration minDurationBetweenContentChanges) {
+        enforceNotSealed();
+        mMinDurationBetweenContentChanges = minDurationBetweenContentChanges.toMillis();
+    }
+
+    /**
+     * Gets the minimum time duration between two content change events.
+     */
+    @NonNull
+    public Duration getMinDurationBetweenContentChanges() {
+        return Duration.ofMillis(mMinDurationBetweenContentChanges);
+    }
+
+    /**
      * Performs an action on the node.
      * <p>
      *   <strong>Note:</strong> An action can be performed only if the request is made
@@ -2441,6 +2476,38 @@
     }
 
     /**
+     * Gets whether the node has {@link #setRequestInitialAccessibilityFocus}.
+     *
+     * @return True if the node has requested initial accessibility focus.
+     */
+    public boolean hasRequestInitialAccessibilityFocus() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_REQUEST_INITIAL_ACCESSIBILITY_FOCUS);
+    }
+
+    /**
+     * Sets whether the node has requested initial accessibility focus.
+     *
+     * <p>
+     * If the node {@link #hasRequestInitialAccessibilityFocus}, this node would be one of
+     * candidates to be accessibility focused when the window appears.
+     * </p>
+     *
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param requestInitialAccessibilityFocus True if the node requests to receive initial
+     *                                         accessibility focus.
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setRequestInitialAccessibilityFocus(boolean requestInitialAccessibilityFocus) {
+        setBooleanProperty(BOOLEAN_PROPERTY_REQUEST_INITIAL_ACCESSIBILITY_FOCUS,
+                requestInitialAccessibilityFocus);
+    }
+
+    /**
      * Gets if the node is editable.
      *
      * @return True if the node is editable, false otherwise.
@@ -3631,6 +3698,47 @@
     }
 
     /**
+     * Sets the container title for app-developer-defined container which can be any type of
+     * ViewGroup or layout.
+     * Container title will be used to group together related controls, similar to HTML fieldset.
+     * Or container title may identify a large piece of the UI that is visibly grouped together,
+     * such as a toolbar or a card, etc.
+     * <p>
+     * Container title helps to assist in navigation across containers and other groups.
+     * For example, a screen reader may use this to determine where to put accessibility focus.
+     * </p>
+     * <p>
+     * Container title is different from pane title{@link #setPaneTitle} which indicates that the
+     * node represents a window or activity.
+     * </p>
+     *
+     * <p>
+     *  Example: An app can set container titles on several non-modal menus, containing TextViews
+     *  or ImageButtons that have content descriptions, text, etc. Screen readers can quickly
+     *  switch accessibility focus among menus instead of child views.  Other accessibility-services
+     *  can easily find the menu.
+     * </p>
+     *
+     * @param containerTitle The container title that is associated with a ViewGroup/Layout on the
+     *                       screen.
+     */
+    public void setContainerTitle(@Nullable CharSequence containerTitle) {
+        enforceNotSealed();
+        mContainerTitle = (containerTitle == null) ? null
+                : containerTitle.subSequence(0, containerTitle.length());
+    }
+
+    /**
+     * Returns the container title.
+     *
+     * @see #setContainerTitle for details.
+     */
+    @Nullable
+    public CharSequence getContainerTitle() {
+        return mContainerTitle;
+    }
+
+    /**
      * Sets the token and node id of the leashed parent.
      *
      * @param token The token.
@@ -3909,6 +4017,11 @@
         fieldIndex++;
         if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
         fieldIndex++;
+        if (mMinDurationBetweenContentChanges
+                != DEFAULT.mMinDurationBetweenContentChanges) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
         if (mConnectionId != DEFAULT.mConnectionId) nonDefaultFields |= bitAt(fieldIndex);
         fieldIndex++;
         if (!LongArray.elementsEqual(mChildNodeIds, DEFAULT.mChildNodeIds)) {
@@ -3963,6 +4076,10 @@
             nonDefaultFields |= bitAt(fieldIndex);
         }
         fieldIndex++;
+        if (!Objects.equals(mContainerTitle, DEFAULT.mContainerTitle)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
         if (!Objects.equals(mViewIdResourceName, DEFAULT.mViewIdResourceName)) {
             nonDefaultFields |= bitAt(fieldIndex);
         }
@@ -4034,6 +4151,9 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeLong(mMinDurationBetweenContentChanges);
+        }
 
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mConnectionId);
 
@@ -4108,10 +4228,10 @@
         }
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPaneTitle);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mTooltipText);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mContainerTitle);
 
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mViewIdResourceName);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mUniqueId);
-
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionStart);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionEnd);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mInputType);
@@ -4189,6 +4309,7 @@
         mLabeledById = other.mLabeledById;
         mTraversalBefore = other.mTraversalBefore;
         mTraversalAfter = other.mTraversalAfter;
+        mMinDurationBetweenContentChanges = other.mMinDurationBetweenContentChanges;
         mWindowId = other.mWindowId;
         mConnectionId = other.mConnectionId;
         mUniqueId = other.mUniqueId;
@@ -4204,6 +4325,7 @@
         mContentDescription = other.mContentDescription;
         mPaneTitle = other.mPaneTitle;
         mTooltipText = other.mTooltipText;
+        mContainerTitle = other.mContainerTitle;
         mViewIdResourceName = other.mViewIdResourceName;
 
         if (mActions != null) mActions.clear();
@@ -4291,6 +4413,9 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mMinDurationBetweenContentChanges = parcel.readLong();
+        }
 
         if (isBitSet(nonDefaultFields, fieldIndex++)) mConnectionId = parcel.readInt();
 
@@ -4347,6 +4472,7 @@
         }
         if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readCharSequence();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = parcel.readCharSequence();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mContainerTitle = parcel.readCharSequence();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mUniqueId = parcel.readString();
 
@@ -4638,6 +4764,8 @@
             builder.append("; mParentNodeId: 0x").append(Long.toHexString(mParentNodeId));
             builder.append("; traversalBefore: 0x").append(Long.toHexString(mTraversalBefore));
             builder.append("; traversalAfter: 0x").append(Long.toHexString(mTraversalAfter));
+            builder.append("; minDurationBetweenContentChanges: ")
+                    .append(mMinDurationBetweenContentChanges);
 
             int granularities = mMovementGranularities;
             builder.append("; MovementGranularities: [");
@@ -4675,6 +4803,7 @@
         builder.append("; stateDescription: ").append(mStateDescription);
         builder.append("; contentDescription: ").append(mContentDescription);
         builder.append("; tooltipText: ").append(mTooltipText);
+        builder.append("; containerTitle: ").append(mContainerTitle);
         builder.append("; viewIdResName: ").append(mViewIdResourceName);
         builder.append("; uniqueId: ").append(mUniqueId);
 
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index 472a363..fb01921 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -22,6 +22,7 @@
 import android.view.MagnificationSpec;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
 
 /**
  * Interface for interaction between the AccessibilityManagerService
@@ -60,4 +61,8 @@
     void clearAccessibilityFocus();
 
     void notifyOutsideTouch();
+
+    void takeScreenshotOfWindow(int interactionId,
+        in ScreenCapture.ScreenCaptureListener listener,
+        IAccessibilityInteractionConnectionCallback callback);
 }
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
index 231e75a..456bf58 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -63,4 +63,9 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
+
+    /**
+    * Sends an error code for a window screenshot request to the requesting client.
+    */
+    void sendTakeScreenshotOfWindowError(int errorCode, int interactionId);
 }
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 36fdcce4..364c7c8 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -27,6 +27,7 @@
 import android.view.accessibility.IAccessibilityManagerClient;
 import android.view.accessibility.AccessibilityWindowAttributes;
 import android.view.accessibility.IWindowMagnificationConnection;
+import android.view.InputEvent;
 import android.view.IWindow;
 
 /**
@@ -109,9 +110,12 @@
 
     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);
+
+    // Used by UiAutomation for tests on the InputFilter
+    void injectInputEventToInputFilter(in InputEvent event);
 }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 70cfc3e..a92bc94 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,19 @@
      * @hide
      */
     public void notifyViewEnteredForFillDialog(View v) {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        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;
@@ -1203,13 +1221,18 @@
                 Log.d(TAG, "Trigger fill request at view entered");
             }
 
-            // Note: No need for atomic getAndSet as this method is called on the UI thread.
-            mIsFillRequested.set(true);
-
             int flags = FLAG_SUPPORTS_FILL_DIALOG;
             flags |= FLAG_VIEW_NOT_FOCUSED;
-            // use root view, so autofill UI does not trigger immediately.
-            notifyViewEntered(v.getRootView(), flags);
+
+            synchronized (mLock) {
+                // To match the id of the IME served view, used AutofillId.NO_AUTOFILL_ID on prefill
+                // request, because IME will reset the id of IME served view to 0 when activity
+                // start and does not focus on any view. If the id of the prefill request is
+                // not match to the IME served view's, Autofill will be blocking to wait inline
+                // request from the IME.
+                notifyViewEnteredLocked(/* view= */ null, AutofillId.NO_AUTOFILL_ID,
+                        /* bounds= */ null,  /* value= */ null, flags);
+            }
         }
     }
 
@@ -1218,6 +1241,8 @@
     }
 
     private int getImeStateFlag(View v) {
+        if (v == null) return 0;
+
         final WindowInsets rootWindowInsets = v.getRootWindowInsets();
         if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsets.Type.ime())) {
             return FLAG_IME_SHOWING;
@@ -1265,8 +1290,8 @@
         }
         AutofillCallback callback;
         synchronized (mLock) {
-            mIsFillRequested.set(true);
-            callback = notifyViewEnteredLocked(view, flags);
+            callback = notifyViewEnteredLocked(
+                    view, view.getAutofillId(), /* bounds= */ null, view.getAutofillValue(), flags);
         }
 
         if (callback != null) {
@@ -1274,58 +1299,6 @@
         }
     }
 
-    /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */
-    @GuardedBy("mLock")
-    private AutofillCallback notifyViewEnteredLocked(@NonNull View view, int flags) {
-        final AutofillId id = view.getAutofillId();
-        if (shouldIgnoreViewEnteredLocked(id, flags)) return null;
-
-        AutofillCallback callback = null;
-
-        final boolean clientAdded = tryAddServiceClientIfNeededLocked();
-
-        if (!clientAdded) {
-            if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): no service client");
-            return callback;
-        }
-
-        if (!mEnabled && !mEnabledForAugmentedAutofillOnly) {
-            if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled");
-
-            if (mCallback != null) {
-                callback = mCallback;
-            }
-        } else {
-            // don't notify entered when Activity is already in background
-            if (!isClientDisablingEnterExitEvent()) {
-                final AutofillValue value = view.getAutofillValue();
-
-                if (view instanceof TextView && ((TextView) view).isAnyPasswordInputType()) {
-                    flags |= FLAG_PASSWORD_INPUT_TYPE;
-                }
-
-                flags |= getImeStateFlag(view);
-
-                if (!isActiveLocked()) {
-                    // Starts new session.
-                    startSessionLocked(id, null, value, flags);
-                } else {
-                    // Update focus on existing session.
-                    if (mForAugmentedAutofillOnly && (flags & FLAG_MANUAL_REQUEST) != 0) {
-                        if (sDebug) {
-                            Log.d(TAG, "notifyViewEntered(" + id + "): resetting "
-                                    + "mForAugmentedAutofillOnly on manual request");
-                        }
-                        mForAugmentedAutofillOnly = false;
-                    }
-                    updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags);
-                }
-                addEnteredIdLocked(id);
-            }
-        }
-        return callback;
-    }
-
     /**
      * Called when a {@link View} that supports autofill is exited.
      *
@@ -1442,9 +1415,11 @@
         if (!hasAutofillFeature()) {
             return;
         }
+
         AutofillCallback callback;
         synchronized (mLock) {
-            callback = notifyViewEnteredLocked(view, virtualId, bounds, flags);
+            callback = notifyViewEnteredLocked(
+                    view, getAutofillId(view, virtualId), bounds, /* value= */ null, flags);
         }
 
         if (callback != null) {
@@ -1455,53 +1430,55 @@
 
     /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */
     @GuardedBy("mLock")
-    private AutofillCallback notifyViewEnteredLocked(View view, int virtualId, Rect bounds,
-                                                     int flags) {
-        final AutofillId id = getAutofillId(view, virtualId);
-        AutofillCallback callback = null;
-        if (shouldIgnoreViewEnteredLocked(id, flags)) return callback;
+    private AutofillCallback notifyViewEnteredLocked(@Nullable View view, AutofillId id,
+            Rect bounds, AutofillValue value, int flags) {
+        if (shouldIgnoreViewEnteredLocked(id, flags)) return null;
 
         final boolean clientAdded = tryAddServiceClientIfNeededLocked();
-
         if (!clientAdded) {
             if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): no service client");
-            return callback;
+            return null;
         }
 
         if (!mEnabled && !mEnabledForAugmentedAutofillOnly) {
             if (sVerbose) {
                 Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled");
             }
-            if (mCallback != null) {
-                callback = mCallback;
-            }
-        } else {
-            // don't notify entered when Activity is already in background
-            if (!isClientDisablingEnterExitEvent()) {
-                if (view instanceof TextView && ((TextView) view).isAnyPasswordInputType()) {
-                    flags |= FLAG_PASSWORD_INPUT_TYPE;
-                }
-
-                flags |= getImeStateFlag(view);
-
-                if (!isActiveLocked()) {
-                    // Starts new session.
-                    startSessionLocked(id, bounds, null, flags);
-                } else {
-                    // Update focus on existing session.
-                    if (mForAugmentedAutofillOnly && (flags & FLAG_MANUAL_REQUEST) != 0) {
-                        if (sDebug) {
-                            Log.d(TAG, "notifyViewEntered(" + id + "): resetting "
-                                    + "mForAugmentedAutofillOnly on manual request");
-                        }
-                        mForAugmentedAutofillOnly = false;
-                    }
-                    updateSessionLocked(id, bounds, null, ACTION_VIEW_ENTERED, flags);
-                }
-                addEnteredIdLocked(id);
-            }
+            return mCallback;
         }
-        return callback;
+
+        mIsFillRequested.set(true);
+
+        // don't notify entered when Activity is already in background
+        if (!isClientDisablingEnterExitEvent()) {
+            if (view instanceof TextView && ((TextView) view).isAnyPasswordInputType()) {
+                flags |= FLAG_PASSWORD_INPUT_TYPE;
+            }
+
+            flags |= getImeStateFlag(view);
+
+            if (!isActiveLocked()) {
+                // Starts new session.
+                startSessionLocked(id, bounds, value, flags);
+            } else {
+                // Update focus on existing session.
+                if (mForAugmentedAutofillOnly && (flags & FLAG_MANUAL_REQUEST) != 0) {
+                    if (sDebug) {
+                        Log.d(TAG, "notifyViewEntered(" + id + "): resetting "
+                                + "mForAugmentedAutofillOnly on manual request");
+                    }
+                    mForAugmentedAutofillOnly = false;
+                }
+
+                if ((flags & FLAG_SUPPORTS_FILL_DIALOG) != 0) {
+                    flags |= FLAG_RESET_FILL_DIALOG_STATE;
+                }
+
+                updateSessionLocked(id, bounds, value, ACTION_VIEW_ENTERED, flags);
+            }
+            addEnteredIdLocked(id);
+        }
+        return null;
     }
 
     @GuardedBy("mLock")
@@ -2006,7 +1983,8 @@
             if (!mOnInvisibleCalled && focusView != null
                     && focusView.canNotifyAutofillEnterExitEvent()) {
                 notifyViewExitedLocked(focusView);
-                notifyViewEnteredLocked(focusView, 0);
+                notifyViewEnteredLocked(focusView, focusView.getAutofillId(),
+                        /* bounds= */ null, focusView.getAutofillValue(), /* flags= */ 0);
             }
             if (data == null) {
                 // data is set to null when result is not RESULT_OK
@@ -2217,6 +2195,7 @@
         mIsFillRequested.set(false);
         mShowAutofillDialogCalled = false;
         mFillDialogTriggerIds = null;
+        mAllTrackedViews.clear();
         if (resetEnteredIds) {
             mEnteredIds = null;
         }
@@ -2776,14 +2755,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 +2779,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 +3571,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 +3649,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 +3724,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 +3768,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/DeleteGesture.java b/core/java/android/view/inputmethod/DeleteGesture.java
index c88158f..b843594 100644
--- a/core/java/android/view/inputmethod/DeleteGesture.java
+++ b/core/java/android/view/inputmethod/DeleteGesture.java
@@ -33,7 +33,7 @@
  * <p>Note: This deletes all text <em>within</em> the given area. To delete a range <em>between</em>
  * two areas, use {@link DeleteRangeGesture}.</p>
  */
-public final class DeleteGesture extends HandwritingGesture implements Parcelable {
+public final class DeleteGesture extends PreviewableHandwritingGesture implements Parcelable {
 
     private @Granularity int mGranularity;
     private RectF mArea;
diff --git a/core/java/android/view/inputmethod/DeleteRangeGesture.java b/core/java/android/view/inputmethod/DeleteRangeGesture.java
index 53b4209..0bce15e 100644
--- a/core/java/android/view/inputmethod/DeleteRangeGesture.java
+++ b/core/java/android/view/inputmethod/DeleteRangeGesture.java
@@ -34,7 +34,7 @@
  * <p>Note: this deletes text within a range <em>between</em> two given areas. To delete all text
  * <em>within</em> a single area, use {@link DeleteGesture}.</p>
  */
-public final class DeleteRangeGesture extends HandwritingGesture implements Parcelable {
+public final class DeleteRangeGesture extends PreviewableHandwritingGesture implements Parcelable {
 
     private @Granularity int mGranularity;
     private RectF mStartArea;
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index febdac2..9ebaa67 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -55,8 +55,10 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * An EditorInfo describes several attributes of a text editing object
@@ -534,6 +536,8 @@
 
     private @HandwritingGesture.GestureTypeFlags int mSupportedHandwritingGestureTypes;
 
+    private @HandwritingGesture.GestureTypeFlags int mSupportedHandwritingGesturePreviewTypes;
+
     /**
      * Set the Handwriting gestures supported by the current {@code Editor}.
      * For an editor that supports Stylus Handwriting
@@ -541,8 +545,11 @@
      * supported gestures.
      * <p> If editor doesn't support one of the declared types, IME will not send those Gestures
      *  to the editor. Instead they will fallback to using normal text input. </p>
+     * <p>Note: A supported gesture may not have preview supported
+     * {@link #getSupportedHandwritingGesturePreviews()}.</p>
      * @param gestures List of supported gesture classes including any of {@link SelectGesture},
      * {@link InsertGesture}, {@link DeleteGesture}.
+     * @see #setSupportedHandwritingGesturePreviews(Set)
      */
     public void setSupportedHandwritingGestures(
             @NonNull List<Class<? extends HandwritingGesture>> gestures) {
@@ -584,6 +591,7 @@
      * {@link InputMethodManager#startStylusHandwriting}, it also declares supported gestures.
      * @return List of supported gesture classes including any of {@link SelectGesture},
      * {@link InsertGesture}, {@link DeleteGesture}.
+     * @see #getSupportedHandwritingGesturePreviews()
      */
     @NonNull
     public List<Class<? extends HandwritingGesture>> getSupportedHandwritingGestures() {
@@ -623,6 +631,86 @@
     }
 
     /**
+     * Set the Handwriting gesture previews supported by the current {@code Editor}.
+     * For an editor that supports Stylus Handwriting
+     * {@link InputMethodManager#startStylusHandwriting}, it is also recommended that it declares
+     * supported gesture previews.
+     * <p>Note: A supported gesture {@link EditorInfo#getSupportedHandwritingGestures()} may not
+     * have preview supported {@link EditorInfo#getSupportedHandwritingGesturePreviews()}.</p>
+     * <p> If editor doesn't support one of the declared types, gesture preview will be ignored.</p>
+     * @param gestures Set of supported gesture classes. One of {@link SelectGesture},
+     * {@link SelectRangeGesture}, {@link DeleteGesture}, {@link DeleteRangeGesture}.
+     * @see #setSupportedHandwritingGestures(List)
+     */
+    public void setSupportedHandwritingGesturePreviews(
+            @NonNull Set<Class<? extends PreviewableHandwritingGesture>> gestures) {
+        Objects.requireNonNull(gestures);
+        if (gestures.isEmpty()) {
+            mSupportedHandwritingGesturePreviewTypes = 0;
+            return;
+        }
+
+        int supportedTypes = 0;
+        for (Class<? extends PreviewableHandwritingGesture> gesture : gestures) {
+            Objects.requireNonNull(gesture);
+            if (gesture.equals(SelectGesture.class)) {
+                supportedTypes |= HandwritingGesture.GESTURE_TYPE_SELECT;
+            } else if (gesture.equals(SelectRangeGesture.class)) {
+                supportedTypes |= HandwritingGesture.GESTURE_TYPE_SELECT_RANGE;
+            } else if (gesture.equals(DeleteGesture.class)) {
+                supportedTypes |= HandwritingGesture.GESTURE_TYPE_DELETE;
+            } else if (gesture.equals(DeleteRangeGesture.class)) {
+                supportedTypes |= HandwritingGesture.GESTURE_TYPE_DELETE_RANGE;
+            } else {
+                throw new IllegalArgumentException(
+                        "Unsupported gesture type for preview: " + gesture);
+            }
+        }
+
+        mSupportedHandwritingGesturePreviewTypes = supportedTypes;
+    }
+
+    /**
+     * Returns the combination of Stylus handwriting gesture preview types
+     * supported by the current {@code Editor}.
+     * For an editor that supports Stylus Handwriting.
+     * {@link InputMethodManager#startStylusHandwriting}, it also declares supported gesture
+     * previews.
+     * <p>Note: A supported gesture {@link EditorInfo#getSupportedHandwritingGestures()} may not
+     * have preview supported {@link EditorInfo#getSupportedHandwritingGesturePreviews()}.</p>
+     * @return Set of supported gesture preview classes. One of {@link SelectGesture},
+     * {@link SelectRangeGesture}, {@link DeleteGesture}, {@link DeleteRangeGesture}.
+     * @see #getSupportedHandwritingGestures()
+     */
+    @NonNull
+    public Set<Class<? extends PreviewableHandwritingGesture>>
+            getSupportedHandwritingGesturePreviews() {
+        Set<Class<? extends PreviewableHandwritingGesture>> set  = new HashSet<>();
+        if (mSupportedHandwritingGesturePreviewTypes == 0) {
+            return set;
+        }
+        if ((mSupportedHandwritingGesturePreviewTypes & HandwritingGesture.GESTURE_TYPE_SELECT)
+                == HandwritingGesture.GESTURE_TYPE_SELECT) {
+            set.add(SelectGesture.class);
+        }
+        if ((mSupportedHandwritingGesturePreviewTypes
+                & HandwritingGesture.GESTURE_TYPE_SELECT_RANGE)
+                        == HandwritingGesture.GESTURE_TYPE_SELECT_RANGE) {
+            set.add(SelectRangeGesture.class);
+        }
+        if ((mSupportedHandwritingGesturePreviewTypes & HandwritingGesture.GESTURE_TYPE_DELETE)
+                == HandwritingGesture.GESTURE_TYPE_DELETE) {
+            set.add(DeleteGesture.class);
+        }
+        if ((mSupportedHandwritingGesturePreviewTypes
+                & HandwritingGesture.GESTURE_TYPE_DELETE_RANGE)
+                        == HandwritingGesture.GESTURE_TYPE_DELETE_RANGE) {
+            set.add(DeleteRangeGesture.class);
+        }
+        return set;
+    }
+
+    /**
      * If not {@code null}, this editor needs to talk to IMEs that run for the specified user, no
      * matter what user ID the calling process has.
      *
@@ -1114,6 +1202,9 @@
         pw.println(prefix + "supportedHandwritingGestureTypes="
                 + InputMethodDebug.handwritingGestureTypeFlagsToString(
                         mSupportedHandwritingGestureTypes));
+        pw.println(prefix + "supportedHandwritingGesturePreviewTypes="
+                + InputMethodDebug.handwritingGestureTypeFlagsToString(
+                        mSupportedHandwritingGesturePreviewTypes));
         pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes));
         if (targetInputMethodUser != null) {
             pw.println(prefix + "targetInputMethodUserId=" + targetInputMethodUser.getIdentifier());
@@ -1149,6 +1240,8 @@
         newEditorInfo.contentMimeTypes = ArrayUtils.cloneOrNull(contentMimeTypes);
         newEditorInfo.targetInputMethodUser = targetInputMethodUser;
         newEditorInfo.mSupportedHandwritingGestureTypes = mSupportedHandwritingGestureTypes;
+        newEditorInfo.mSupportedHandwritingGesturePreviewTypes =
+                mSupportedHandwritingGesturePreviewTypes;
         return newEditorInfo;
     }
 
@@ -1177,6 +1270,7 @@
         dest.writeString(fieldName);
         dest.writeBundle(extras);
         dest.writeInt(mSupportedHandwritingGestureTypes);
+        dest.writeInt(mSupportedHandwritingGesturePreviewTypes);
         dest.writeBoolean(mInitialSurroundingText != null);
         if (mInitialSurroundingText != null) {
             mInitialSurroundingText.writeToParcel(dest, flags);
@@ -1215,6 +1309,7 @@
                     res.fieldName = source.readString();
                     res.extras = source.readBundle();
                     res.mSupportedHandwritingGestureTypes = source.readInt();
+                    res.mSupportedHandwritingGesturePreviewTypes = source.readInt();
                     boolean hasInitialSurroundingText = source.readBoolean();
                     if (hasInitialSurroundingText) {
                         res.mInitialSurroundingText =
@@ -1258,6 +1353,8 @@
                 && initialCapsMode == that.initialCapsMode
                 && fieldId == that.fieldId
                 && mSupportedHandwritingGestureTypes == that.mSupportedHandwritingGestureTypes
+                && mSupportedHandwritingGesturePreviewTypes
+                        == that.mSupportedHandwritingGesturePreviewTypes
                 && Objects.equals(autofillId, that.autofillId)
                 && Objects.equals(privateImeOptions, that.privateImeOptions)
                 && Objects.equals(packageName, that.packageName)
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index aeff37c..6eae63a 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -16,6 +16,7 @@
 
 package android.view.inputmethod;
 
+import android.Manifest;
 import android.annotation.AnyThread;
 import android.annotation.DurationMillisLong;
 import android.annotation.NonNull;
@@ -106,8 +107,8 @@
      * @param where where the information is coming from.
      * @param exceptionHandler an optional {@link RemoteException} handler.
      */
-    @RequiresNoPermission
     @AnyThread
+    @RequiresNoPermission
     static void startProtoDump(byte[] protoDump, int source, String where,
             @Nullable Consumer<RemoteException> exceptionHandler) {
         final IInputMethodManager service = getService();
@@ -126,8 +127,8 @@
      *
      * @param exceptionHandler an optional {@link RemoteException} handler.
      */
-    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
     @AnyThread
+    @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
     static void startImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -145,8 +146,8 @@
      *
      * @param exceptionHandler an optional {@link RemoteException} handler.
      */
-    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
     @AnyThread
+    @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
     static void stopImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -164,8 +165,8 @@
      *
      * @return The return value of {@link IInputMethodManager#isImeTraceEnabled()}.
      */
-    @RequiresNoPermission
     @AnyThread
+    @RequiresNoPermission
     static boolean isImeTraceEnabled() {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -181,8 +182,8 @@
     /**
      * Invokes {@link IInputMethodManager#removeImeSurface()}
      */
-    @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
     @AnyThread
+    @RequiresPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
     static void removeImeSurface(@Nullable Consumer<RemoteException> exceptionHandler) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -211,6 +212,7 @@
 
     @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();
@@ -226,6 +228,7 @@
 
     @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) {
@@ -240,6 +243,7 @@
 
     @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();
@@ -256,6 +260,7 @@
 
     @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) {
@@ -270,15 +275,16 @@
 
     @AnyThread
     static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            int flags, int lastClickToolType, @Nullable ResultReceiver resultReceiver,
+            @Nullable ImeTracker.Token statsToken, 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);
+            return service.showSoftInput(client, windowToken, statsToken, flags, lastClickToolType,
+                    resultReceiver, reason);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -286,14 +292,15 @@
 
     @AnyThread
     static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            int flags, @Nullable ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
+            @Nullable ImeTracker.Token statsToken, 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);
+            return service.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver,
+                    reason);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -301,6 +308,7 @@
 
     @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,
@@ -339,20 +347,21 @@
     }
 
     @AnyThread
-    static void showInputMethodPickerFromSystem(@NonNull IInputMethodClient client,
-            int auxiliarySubtypeMode, int displayId) {
+    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+    static void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
         final IInputMethodManager service = getService();
         if (service == null) {
             return;
         }
         try {
-            service.showInputMethodPickerFromSystem(client, auxiliarySubtypeMode, displayId);
+            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) {
@@ -367,6 +376,7 @@
 
     @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) {
@@ -380,6 +390,7 @@
     }
 
     @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();
@@ -394,6 +405,7 @@
     }
 
     @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();
@@ -474,6 +486,7 @@
     }
 
     @AnyThread
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     static boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -487,6 +500,7 @@
     }
 
     @AnyThread
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
     static void addVirtualStylusIdForTestSession(IInputMethodClient client) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -500,6 +514,7 @@
     }
 
     @AnyThread
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
     static void setStylusWindowIdleTimeoutForTest(
             IInputMethodClient client, @DurationMillisLong long timeout) {
         final IInputMethodManager service = getService();
diff --git a/core/java/android/view/inputmethod/ImeTracker.aidl b/core/java/android/view/inputmethod/ImeTracker.aidl
new file mode 100644
index 0000000..1988f48
--- /dev/null
+++ b/core/java/android/view/inputmethod/ImeTracker.aidl
@@ -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.
+ */
+
+package android.view.inputmethod;
+
+parcelable ImeTracker.Token;
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
new file mode 100644
index 0000000..f4ecdff
--- /dev/null
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -0,0 +1,488 @@
+/*
+ * 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 android.view.inputmethod.ImeTracker.Debug.originToString;
+import static android.view.inputmethod.ImeTracker.Debug.phaseToString;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.Collectors;
+
+/** @hide */
+public interface ImeTracker {
+
+    String TAG = "ImeTracker";
+
+    /**
+     * The origin of the IME request
+     *
+     * The name follows the format {@code PHASE_x_...} where {@code x} denotes
+     * where the origin is (i.e. {@code PHASE_SERVER_...} occurs in the server).
+     */
+    @IntDef(prefix = { "ORIGIN_" }, value = {
+            ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+            ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+            ORIGIN_SERVER_START_INPUT,
+            ORIGIN_SERVER_HIDE_INPUT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface Origin {}
+
+    /**
+     * The IME show request originated in the client.
+     */
+    int ORIGIN_CLIENT_SHOW_SOFT_INPUT = 0;
+
+    /**
+     * The IME hide request originated in the client.
+     */
+    int ORIGIN_CLIENT_HIDE_SOFT_INPUT = 1;
+
+    /**
+     * The IME show request originated in the server.
+     */
+    int ORIGIN_SERVER_START_INPUT = 2;
+
+    /**
+     * The IME hide request originated in the server.
+     */
+    int ORIGIN_SERVER_HIDE_INPUT = 3;
+
+    /**
+     * The current phase of the IME request.
+     *
+     * The name follows the format {@code PHASE_x_...} where {@code x} denotes
+     * where the phase is (i.e. {@code PHASE_SERVER_...} occurs in the server).
+     */
+    @IntDef(prefix = { "PHASE_" }, value = {
+            PHASE_CLIENT_VIEW_SERVED,
+            PHASE_SERVER_CLIENT_KNOWN,
+            PHASE_SERVER_CLIENT_FOCUSED,
+            PHASE_SERVER_ACCESSIBILITY,
+            PHASE_SERVER_SYSTEM_READY,
+            PHASE_SERVER_HIDE_IMPLICIT,
+            PHASE_SERVER_HIDE_NOT_ALWAYS,
+            PHASE_SERVER_WAIT_IME,
+            PHASE_SERVER_HAS_IME,
+            PHASE_SERVER_SHOULD_HIDE,
+            PHASE_IME_WRAPPER,
+            PHASE_IME_WRAPPER_DISPATCH,
+            PHASE_IME_SHOW_SOFT_INPUT,
+            PHASE_IME_HIDE_SOFT_INPUT,
+            PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE,
+            PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER,
+            PHASE_SERVER_APPLY_IME_VISIBILITY,
+            PHASE_WM_SHOW_IME_RUNNER,
+            PHASE_WM_SHOW_IME_READY,
+            PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET,
+            PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS,
+            PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS,
+            PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS,
+            PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS,
+            PHASE_WM_REMOTE_INSETS_CONTROLLER,
+            PHASE_WM_ANIMATION_CREATE,
+            PHASE_WM_ANIMATION_RUNNING,
+            PHASE_CLIENT_SHOW_INSETS,
+            PHASE_CLIENT_HIDE_INSETS,
+            PHASE_CLIENT_HANDLE_SHOW_INSETS,
+            PHASE_CLIENT_HANDLE_HIDE_INSETS,
+            PHASE_CLIENT_APPLY_ANIMATION,
+            PHASE_CLIENT_CONTROL_ANIMATION,
+            PHASE_CLIENT_ANIMATION_RUNNING,
+            PHASE_CLIENT_ANIMATION_CANCEL,
+            PHASE_CLIENT_ANIMATION_FINISHED_SHOW,
+            PHASE_CLIENT_ANIMATION_FINISHED_HIDE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface Phase {}
+
+    /** The view that requested the IME has been served by the IMM. */
+    int PHASE_CLIENT_VIEW_SERVED = 0;
+
+    /** The IME client that requested the IME has window manager focus. */
+    int PHASE_SERVER_CLIENT_KNOWN = 1;
+
+    /** The IME client that requested the IME has IME focus. */
+    int PHASE_SERVER_CLIENT_FOCUSED = 2;
+
+    /** The IME request complies with the current accessibility settings. */
+    int PHASE_SERVER_ACCESSIBILITY = 3;
+
+    /** The server is ready to run third party code. */
+    int PHASE_SERVER_SYSTEM_READY = 4;
+
+    /** Checked the implicit hide request against any explicit show requests. */
+    int PHASE_SERVER_HIDE_IMPLICIT = 5;
+
+    /** Checked the not-always hide request against any forced show requests. */
+    int PHASE_SERVER_HIDE_NOT_ALWAYS = 6;
+
+    /** The server is waiting for a connection to the IME. */
+    int PHASE_SERVER_WAIT_IME = 7;
+
+    /** The server has a connection to the IME. */
+    int PHASE_SERVER_HAS_IME = 8;
+
+    /** The server decided the IME should be hidden. */
+    int PHASE_SERVER_SHOULD_HIDE = 9;
+
+    /** Reached the IME wrapper. */
+    int PHASE_IME_WRAPPER = 10;
+
+    /** Dispatched from the IME wrapper to the IME. */
+    int PHASE_IME_WRAPPER_DISPATCH = 11;
+
+    /** Reached the IME' showSoftInput method. */
+    int PHASE_IME_SHOW_SOFT_INPUT = 12;
+
+    /** Reached the IME' hideSoftInput method. */
+    int PHASE_IME_HIDE_SOFT_INPUT = 13;
+
+    /** The server decided the IME should be shown. */
+    int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = 14;
+
+    /** Requested applying the IME visibility in the insets source consumer. */
+    int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER = 15;
+
+    /** Applied the IME visibility. */
+    int PHASE_SERVER_APPLY_IME_VISIBILITY = 16;
+
+    /** Created the show IME runner. */
+    int PHASE_WM_SHOW_IME_RUNNER = 17;
+
+    /** Ready to show IME. */
+    int PHASE_WM_SHOW_IME_READY = 18;
+
+    /** The Window Manager has a connection to the IME insets control target. */
+    int PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET = 19;
+
+    /** Reached the window insets control target's show insets method. */
+    int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS = 20;
+
+    /** Reached the window insets control target's hide insets method. */
+    int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS = 21;
+
+    /** Reached the remote insets control target's show insets method. */
+    int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS = 22;
+
+    /** Reached the remote insets control target's hide insets method. */
+    int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS = 23;
+
+    /** Reached the remote insets controller. */
+    int PHASE_WM_REMOTE_INSETS_CONTROLLER = 24;
+
+    /** Created the IME window insets show animation. */
+    int PHASE_WM_ANIMATION_CREATE = 25;
+
+    /** Started the IME window insets show animation. */
+    int PHASE_WM_ANIMATION_RUNNING = 26;
+
+    /** Reached the client's show insets method. */
+    int PHASE_CLIENT_SHOW_INSETS = 27;
+
+    /** Reached the client's hide insets method. */
+    int PHASE_CLIENT_HIDE_INSETS = 28;
+
+    /** Handling the IME window insets show request. */
+    int PHASE_CLIENT_HANDLE_SHOW_INSETS = 29;
+
+    /** Handling the IME window insets hide request. */
+    int PHASE_CLIENT_HANDLE_HIDE_INSETS = 30;
+
+    /** Applied the IME window insets show animation. */
+    int PHASE_CLIENT_APPLY_ANIMATION = 31;
+
+    /** Started the IME window insets show animation. */
+    int PHASE_CLIENT_CONTROL_ANIMATION = 32;
+
+    /** Queued the IME window insets show animation. */
+    int PHASE_CLIENT_ANIMATION_RUNNING = 33;
+
+    /** Cancelled the IME window insets show animation. */
+    int PHASE_CLIENT_ANIMATION_CANCEL = 34;
+
+    /** Finished the IME window insets show animation. */
+    int PHASE_CLIENT_ANIMATION_FINISHED_SHOW = 35;
+
+    /** Finished the IME window insets hide animation. */
+    int PHASE_CLIENT_ANIMATION_FINISHED_HIDE = 36;
+
+    /**
+     * Called when an IME show request is created.
+     *
+     * @param token the token tracking the current IME show request or {@code null} otherwise.
+     * @param origin the origin of the IME show request.
+     * @param reason the reason why the IME show request was created.
+     */
+    void onRequestShow(@Nullable Token token, @Origin int origin,
+            @SoftInputShowHideReason int reason);
+
+    /**
+     * Called when an IME hide request is created.
+     *
+     * @param token the token tracking the current IME hide request or {@code null} otherwise.
+     * @param origin the origin of the IME hide request.
+     * @param reason the reason why the IME hide request was created.
+     */
+    void onRequestHide(@Nullable Token token, @Origin int origin,
+            @SoftInputShowHideReason int reason);
+
+    /**
+     * Called when an IME request progresses to a further phase.
+     *
+     * @param token the token tracking the current IME request or {@code null} otherwise.
+     * @param phase the new phase the IME request reached.
+     */
+    void onProgress(@Nullable Token token, @Phase int phase);
+
+    /**
+     * Called when an IME request fails.
+     *
+     * @param token the token tracking the current IME request or {@code null} otherwise.
+     * @param phase the phase the IME request failed at.
+     */
+    void onFailed(@Nullable Token token, @Phase int phase);
+
+    /**
+     * Called when an IME request reached a flow that is not yet implemented.
+     *
+     * @param token the token tracking the current IME request or {@code null} otherwise.
+     * @param phase the phase the IME request was currently at.
+     */
+    void onTodo(@Nullable Token token, @Phase int phase);
+
+    /**
+     * Called when an IME request is cancelled.
+     *
+     * @param token the token tracking the current IME request or {@code null} otherwise.
+     * @param phase the phase the IME request was cancelled at.
+     */
+    void onCancelled(@Nullable Token token, @Phase int phase);
+
+    /**
+     * Called when the IME show request is successful.
+     *
+     * @param token the token tracking the current IME show request or {@code null} otherwise.
+     */
+    void onShown(@Nullable Token token);
+
+    /**
+     * Called when the IME hide request is successful.
+     *
+     * @param token the token tracking the current IME hide request or {@code null} otherwise.
+     */
+    void onHidden(@Nullable Token token);
+
+    /**
+     * Get the singleton instance of this class.
+     *
+     * @return the singleton instance of this class
+     */
+    @NonNull
+    static ImeTracker get() {
+        return SystemProperties.getBoolean("persist.debug.imetracker", false)
+                ? LOGGER
+                : NOOP_LOGGER;
+    }
+
+    /** The singleton IME tracker instance. */
+    ImeTracker LOGGER = new ImeTracker() {
+
+        @Override
+        public void onRequestShow(@Nullable Token token, int origin,
+                @SoftInputShowHideReason int reason) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onRequestShow at " + originToString(origin)
+                    + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+        }
+
+        @Override
+        public void onRequestHide(@Nullable Token token, int origin,
+                @SoftInputShowHideReason int reason) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onRequestHide at " + originToString(origin)
+                    + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+        }
+
+        @Override
+        public void onProgress(@Nullable Token token, int phase) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onProgress at " + phaseToString(phase));
+        }
+
+        @Override
+        public void onFailed(@Nullable Token token, int phase) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onFailed at " + phaseToString(phase));
+        }
+
+        @Override
+        public void onTodo(@Nullable Token token, int phase) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onTodo at " + phaseToString(phase));
+        }
+
+        @Override
+        public void onCancelled(@Nullable Token token, int phase) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onCancelled at " + phaseToString(phase));
+        }
+
+        @Override
+        public void onShown(@Nullable Token token) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onShown");
+        }
+
+        @Override
+        public void onHidden(@Nullable Token token) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onHidden");
+        }
+    };
+
+    /** The singleton no-op IME tracker instance. */
+    ImeTracker NOOP_LOGGER = new ImeTracker() {
+
+        @Override
+        public void onRequestShow(@Nullable Token token, int origin,
+                @SoftInputShowHideReason int reason) {}
+
+        @Override
+        public void onRequestHide(@Nullable Token token, int origin,
+                @SoftInputShowHideReason int reason) {}
+
+        @Override
+        public void onProgress(@Nullable Token token, int phase) {}
+
+        @Override
+        public void onFailed(@Nullable Token token, int phase) {}
+
+        @Override
+        public void onTodo(@Nullable Token token, int phase) {}
+
+        @Override
+        public void onCancelled(@Nullable Token token, int phase) {}
+
+        @Override
+        public void onShown(@Nullable Token token) {}
+
+        @Override
+        public void onHidden(@Nullable Token token) {}
+    };
+
+    /** A token that tracks the progress of an IME request. */
+    class Token implements Parcelable {
+
+        private final IBinder mBinder;
+        private final String mTag;
+
+        public Token() {
+            this(ActivityThread.currentProcessName());
+        }
+
+        public Token(String component) {
+            this(new Binder(), component + ":" + Integer.toHexString((new Random().nextInt())));
+        }
+
+        private Token(IBinder binder, String tag) {
+            mBinder = binder;
+            mTag = tag;
+        }
+
+        /** For Parcelable, no special marshalled objects. */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeStrongBinder(mBinder);
+            dest.writeString8(mTag);
+        }
+
+        @NonNull
+        public static final Creator<Token> CREATOR = new Creator<>() {
+            @Override
+            public Token createFromParcel(Parcel source) {
+                IBinder binder = source.readStrongBinder();
+                String tag = source.readString8();
+                return new Token(binder, tag);
+            }
+
+            @Override
+            public Token[] newArray(int size) {
+                return new Token[size];
+            }
+        };
+    }
+
+    /**
+     * Utilities for mapping phases and origins IntDef values to their names.
+     *
+     * Note: This is held in a separate class so that it only gets initialized when actually needed.
+     */
+    class Debug {
+
+        private static final Map<Integer, String> sOrigins =
+                getFieldMapping(ImeTracker.class, "ORIGIN_");
+        private static final Map<Integer, String> sPhases =
+                getFieldMapping(ImeTracker.class, "PHASE_");
+
+        public static String originToString(int origin) {
+            return sOrigins.getOrDefault(origin, "ORIGIN_" + origin);
+        }
+
+        public static String phaseToString(int phase) {
+            return sPhases.getOrDefault(phase, "PHASE_" + phase);
+        }
+
+        private static Map<Integer, String> getFieldMapping(Class<?> cls, String fieldPrefix) {
+            return Arrays.stream(cls.getDeclaredFields())
+                    .filter(field -> field.getName().startsWith(fieldPrefix))
+                    .collect(Collectors.toMap(Debug::getFieldValue, Field::getName));
+        }
+
+        private static int getFieldValue(Field field) {
+            try {
+                return field.getInt(null);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 7d268a9..9b519c3 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -16,13 +16,17 @@
 
 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.CancellationSignal;
 import android.os.Handler;
 import android.text.TextUtils;
 import android.view.KeyCharacterMap;
@@ -32,7 +36,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;
 
 /**
@@ -1026,6 +1032,8 @@
     /**
      * Perform a handwriting gesture on text.
      *
+     * <p>Note: A supported gesture {@link EditorInfo#getSupportedHandwritingGestures()} may not
+     * have preview supported {@link EditorInfo#getSupportedHandwritingGesturePreviews()}.</p>
      * @param gesture the gesture to perform
      * @param executor The executor to run the callback on.
      * @param consumer if the caller passes a non-null consumer, the editor must invoke this
@@ -1036,6 +1044,7 @@
      * completed. Will be invoked on the given {@link Executor}.
      * Default implementation provides a callback to {@link IntConsumer} with
      * {@link #HANDWRITING_GESTURE_RESULT_UNSUPPORTED}.
+     * @see #previewHandwritingGesture(PreviewableHandwritingGesture, CancellationSignal)
      */
     default void performHandwritingGesture(
             @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
@@ -1046,14 +1055,41 @@
     }
 
     /**
+     * Preview a handwriting gesture on text.
+     * Provides a real-time preview for a gesture to user for an ongoing gesture. e.g. as user
+     * begins to draw a circle around text, resulting selection {@link SelectGesture} is previewed
+     * while stylus is moving over applicable text.
+     *
+     * <p>Note: A supported gesture {@link EditorInfo#getSupportedHandwritingGestures()} might not
+     * have preview supported {@link EditorInfo#getSupportedHandwritingGesturePreviews()}.</p>
+     * @param gesture the gesture to preview. Preview support for a gesture (regardless of whether
+     *  implemented by editor) can be determined if gesture subclasses
+     *  {@link PreviewableHandwritingGesture}. Supported previewable gestures include
+     *  {@link SelectGesture}, {@link SelectRangeGesture}, {@link DeleteGesture} and
+     *  {@link DeleteRangeGesture}.
+     * @param cancellationSignal signal to cancel an ongoing preview.
+     * @return true on successfully sending command to Editor, false if not implemented by editor or
+     * the input connection is no longer valid or preview was cancelled with
+     * {@link CancellationSignal}.
+     * @see #performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)
+     */
+    default boolean previewHandwritingGesture(
+            @NonNull PreviewableHandwritingGesture gesture,
+            @Nullable CancellationSignal cancellationSignal) {
+        return false;
+    }
+
+    /**
      * The editor is requested to call
      * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} at
      * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be
      * 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 +1107,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 +1124,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 +1140,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 +1156,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 +1171,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 +1206,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 +1259,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.
@@ -1352,6 +1440,8 @@
      *     that this means you can't position the cursor within the text.
      * @param text the text to replace. This may include styles.
      * @param textAttribute The extra information about the text. This value may be null.
+     * @return {@code true} if the replace command was sent to the associated editor (regardless of
+     *     whether the replacement is success or not), {@code false} otherwise.
      */
     default boolean replaceText(
             @IntRange(from = 0) int start,
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 56beddf..4befd6f 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -20,13 +20,16 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.RectF;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.view.KeyEvent;
 
 import com.android.internal.util.Preconditions;
 
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
 /**
@@ -338,6 +341,17 @@
      * @throws NullPointerException if the target is {@code null}.
      */
     @Override
+    public boolean previewHandwritingGesture(
+            @NonNull PreviewableHandwritingGesture gesture,
+            @Nullable CancellationSignal cancellationSignal) {
+        return mTarget.previewHandwritingGesture(gesture, cancellationSignal);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
     public boolean requestCursorUpdates(int cursorUpdateMode) {
         return mTarget.requestCursorUpdates(cursorUpdateMode);
     }
@@ -347,6 +361,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/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 4d5a17d..92380ed 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -300,11 +300,12 @@
      * @param showInputToken an opaque {@link android.os.Binder} token to identify which API call
      *        of {@link InputMethodManager#showSoftInput(View, int)} is associated with
      *        this callback.
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      * @hide
      */
     @MainThread
-    default public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
-            IBinder showInputToken) {
+    public default void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+            IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
         showSoftInput(flags, resultReceiver);
     }
 
@@ -338,11 +339,14 @@
      * @param hideInputToken an opaque {@link android.os.Binder} token to identify which API call
      *         of {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}} is associated
      *         with this callback.
+     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
      * @hide
      */
     @MainThread
-    public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
-            IBinder hideInputToken);
+    public default void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+            IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) {
+        hideSoftInput(flags, resultReceiver);
+    }
 
     /**
      * Request that any soft input part of the input method be hidden from the user.
@@ -369,7 +373,7 @@
 
     /**
      * Checks if IME is ready to start stylus handwriting session.
-     * If yes, {@link #startStylusHandwriting(InputChannel, List)} is called.
+     * If yes, {@link #startStylusHandwriting(int, InputChannel, List)} is called.
      * @param requestId
      * @hide
      */
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 53d77e1..ee31fd5 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;
@@ -128,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;
 
 /**
@@ -633,6 +632,8 @@
 
     private final DelegateImpl mDelegate = new DelegateImpl();
 
+    private static boolean sPreventImeStartupUnlessTextEditor;
+
     // -----------------------------------------------------------
 
     private static final int MSG_DUMP = 1;
@@ -1436,6 +1437,10 @@
         // display case.
         final Looper looper = displayId == Display.DEFAULT_DISPLAY
                 ? Looper.getMainLooper() : context.getMainLooper();
+        // Keep track of whether to expect the IME to be unavailable so as to avoid log spam in
+        // sendInputEventOnMainLooperLocked() by not logging a verbose message on every DPAD event
+        sPreventImeStartupUnlessTextEditor = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
         return forContextInternal(displayId, looper);
     }
 
@@ -1525,12 +1530,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) {
@@ -1549,13 +1560,16 @@
     /**
      * 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 IInputMethodManagerGlobalInvoker.getInputMethodList(userId,
                 DirectBootAwareness.AUTO);
@@ -1564,14 +1578,17 @@
     /**
      * 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 IInputMethodManagerGlobalInvoker.getInputMethodList(userId, directBootAwareness);
@@ -1595,11 +1612,14 @@
     /**
      * 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 IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(userId);
     }
@@ -1902,8 +1922,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.
      */
@@ -1965,8 +1988,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
@@ -1981,6 +2007,10 @@
 
     private boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
+        final ImeTracker.Token statsToken = new ImeTracker.Token();
+        ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+                reason);
+
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
                 null /* icProto */);
         // Re-dispatch if there is a context mismatch.
@@ -1992,10 +2022,13 @@
         checkFocus();
         synchronized (mH) {
             if (!hasServedByInputMethodLocked(view)) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                 Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served.");
                 return false;
             }
 
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
             // 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));
@@ -2004,6 +2037,7 @@
             return IInputMethodManagerGlobalInvoker.showSoftInput(
                     mClient,
                     view.getWindowToken(),
+                    statsToken,
                     flags,
                     mCurRootView.getLastClickToolType(),
                     resultReceiver,
@@ -2023,19 +2057,28 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
     public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
         synchronized (mH) {
+            final ImeTracker.Token statsToken = new ImeTracker.Token();
+            ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+                    SoftInputShowHideReason.SHOW_SOFT_INPUT);
+
             Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
                     + " removed soon. If you are using androidx.appcompat.widget.SearchView,"
                     + " please update to version 26.0 or newer version.");
             if (mCurRootView == null || mCurRootView.getView() == null) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                 Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
                 return;
             }
+
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
             // 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));
             IInputMethodManagerGlobalInvoker.showSoftInput(
                     mClient,
                     mCurRootView.getView().getWindowToken(),
+                    statsToken,
                     flags,
                     mCurRootView.getLastClickToolType(),
                     resultReceiver,
@@ -2105,17 +2148,24 @@
 
     private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        final ImeTracker.Token statsToken = new ImeTracker.Token();
+        ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+                reason);
+
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow",
                 this, null /* icProto */);
         checkFocus();
         synchronized (mH) {
             final View servedView = getServedViewLocked();
             if (servedView == null || servedView.getWindowToken() != windowToken) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                 return false;
             }
 
-            return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, flags,
-                    resultReceiver, reason);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
+            return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
+                    flags, resultReceiver, reason);
         }
     }
 
@@ -2346,7 +2396,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) {
@@ -2590,6 +2644,7 @@
      * @hide
      */
     @TestApi
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
     public void addVirtualStylusIdForTestSession() {
         synchronized (mH) {
             IInputMethodManagerGlobalInvoker.addVirtualStylusIdForTestSession(mClient);
@@ -2602,8 +2657,8 @@
      * @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) {
             IInputMethodManagerGlobalInvoker.setStylusWindowIdleTimeoutForTest(mClient, timeout);
@@ -2738,14 +2793,23 @@
 
     @UnsupportedAppUsage
     void closeCurrentInput() {
+        final ImeTracker.Token statsToken = new ImeTracker.Token();
+        ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+                SoftInputShowHideReason.HIDE_SOFT_INPUT);
+
         synchronized (mH) {
             if (mCurRootView == null || mCurRootView.getView() == null) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                 Log.w(TAG, "No current root view, ignoring closeCurrentInput()");
                 return;
             }
+
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
             IInputMethodManagerGlobalInvoker.hideSoftInput(
                     mClient,
                     mCurRootView.getView().getWindowToken(),
+                    statsToken,
                     HIDE_NOT_ALWAYS,
                     null,
                     SoftInputShowHideReason.HIDE_SOFT_INPUT);
@@ -2814,15 +2878,24 @@
      * @hide
      */
     public void notifyImeHidden(IBinder windowToken) {
+        final ImeTracker.Token statsToken = new ImeTracker.Token();
+        ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+                SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
                 null /* icProto */);
         synchronized (mH) {
-            if (isImeSessionAvailableLocked() && mCurRootView != null
-                    && mCurRootView.getWindowToken() == windowToken) {
-                IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, 0 /* flags */,
-                        null /* resultReceiver */,
-                        SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+            if (!isImeSessionAvailableLocked() || mCurRootView == null
+                    || mCurRootView.getWindowToken() != windowToken) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+                return;
             }
+
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
+            IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
+                    0 /* flags */, null /* resultReceiver */,
+                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
         }
     }
 
@@ -3082,7 +3155,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>
      *
@@ -3090,6 +3163,8 @@
      * when it was started, which allows it to perform this operation on
      * itself.
      * @param id The unique identifier for the new input method to be switched to.
+     * @throws IllegalArgumentException if the input method is unknown or filtered by the rules of
+     * <a href="/training/basics/intents/package-visibility">package visibility</a>.
      * @deprecated Use {@link InputMethodService#switchInputMethod(String)}
      * instead. This method was intended for IME developers who should be accessing APIs through
      * the service. APIs in this class are intended for app developers interacting with the IME.
@@ -3114,7 +3189,7 @@
             if (fallbackContext == null) {
                 return;
             }
-            if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS)
+            if (fallbackContext.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
                     != PackageManager.PERMISSION_GRANTED) {
                 return;
             }
@@ -3151,7 +3226,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>
      *
@@ -3160,6 +3235,8 @@
      * itself.
      * @param id The unique identifier for the new input method to be switched to.
      * @param subtype The new subtype of the new input method to be switched to.
+     * @throws IllegalArgumentException if the input method is unknown or filtered by the rules of
+     * <a href="/training/basics/intents/package-visibility">package visibility</a>.
      * @deprecated Use
      * {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}
      * instead. This method was intended for IME developers who should be accessing APIs through
@@ -3335,8 +3412,12 @@
                 return DISPATCH_IN_PROGRESS;
             }
 
-            Log.w(TAG, "Unable to send input event to IME: " + getImeIdLocked()
-                    + " dropping: " + event);
+            if (sPreventImeStartupUnlessTextEditor) {
+                Log.d(TAG, "Dropping event because IME is evicted: " + event);
+            } else {
+                Log.w(TAG, "Unable to send input event to IME: " + getImeIdLocked()
+                        + " dropping: " + event);
+            }
         }
         return DISPATCH_NOT_HANDLED;
     }
@@ -3433,12 +3514,12 @@
      * @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;
-        IInputMethodManagerGlobalInvoker.showInputMethodPickerFromSystem(mClient, mode, displayId);
+        IInputMethodManagerGlobalInvoker.showInputMethodPickerFromSystem(mode, displayId);
     }
 
     @GuardedBy("mH")
@@ -3459,6 +3540,7 @@
      * @hide
      */
     @TestApi
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
     public boolean isInputMethodPickerShown() {
         return IInputMethodManagerGlobalInvoker.isInputMethodPickerShownForTest();
     }
@@ -3511,11 +3593,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 "
@@ -3532,7 +3614,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;
         }
@@ -3614,6 +3696,32 @@
     }
 
     /**
+     * {@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);
+    }
+
+    /**
      * An internal API for {@link android.hardware.display.VirtualDisplay} to report where its
      * embedded virtual display is placed.
      *
@@ -3911,7 +4019,7 @@
 
         /**
          * As reported by {@link InputBindResult}. This value is determined by
-         * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecking}.
+         * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecker}.
          */
         final boolean mIsInputMethodSuppressingSpellChecker;
 
diff --git a/core/java/android/view/inputmethod/InputMethodManagerGlobal.java b/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
index 63d9167..5df9fd1 100644
--- a/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
+++ b/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
@@ -16,6 +16,7 @@
 
 package android.view.inputmethod;
 
+import android.Manifest;
 import android.annotation.AnyThread;
 import android.annotation.Nullable;
 import android.annotation.RequiresNoPermission;
@@ -51,8 +52,8 @@
      * @param where where the information is coming from.
      * @param exceptionHandler an optional {@link RemoteException} handler.
      */
-    @RequiresNoPermission
     @AnyThread
+    @RequiresNoPermission
     public static void startProtoDump(byte[] protoDump, int source, String where,
             @Nullable Consumer<RemoteException> exceptionHandler) {
         IInputMethodManagerGlobalInvoker.startProtoDump(protoDump, source, where, exceptionHandler);
@@ -63,8 +64,8 @@
      *
      * @param exceptionHandler an optional {@link RemoteException} handler.
      */
-    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
     @AnyThread
+    @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
     public static void startImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
         IInputMethodManagerGlobalInvoker.startImeTrace(exceptionHandler);
     }
@@ -74,8 +75,8 @@
      *
      * @param exceptionHandler an optional {@link RemoteException} handler.
      */
-    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
     @AnyThread
+    @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
     public static void stopImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
         IInputMethodManagerGlobalInvoker.stopImeTrace(exceptionHandler);
     }
@@ -85,8 +86,8 @@
      *
      * @return The return value of {@link IInputMethodManager#isImeTraceEnabled()}.
      */
-    @RequiresNoPermission
     @AnyThread
+    @RequiresNoPermission
     public static boolean isImeTraceEnabled() {
         return IInputMethodManagerGlobalInvoker.isImeTraceEnabled();
     }
@@ -96,8 +97,8 @@
      *
      * @param exceptionHandler an optional {@link RemoteException} handler.
      */
-    @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
     @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/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index bdfcb03..6a02198 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -86,6 +86,7 @@
     private final int mSubtypeHashCode;
     private final int mSubtypeIconResId;
     private final int mSubtypeNameResId;
+    private final CharSequence mSubtypeNameOverride;
     private final int mSubtypeId;
     private final String mSubtypeLocale;
     private final String mSubtypeLanguageTag;
@@ -174,6 +175,21 @@
         private int mSubtypeNameResId = 0;
 
         /**
+         * Sets the untranslatable name of the subtype.
+         *
+         * This string is used as the subtype's display name if subtype's name res Id is 0.
+         *
+         * @param nameOverride is the name to set.
+         */
+        @NonNull
+        public InputMethodSubtypeBuilder setSubtypeNameOverride(
+                @NonNull CharSequence nameOverride) {
+            mSubtypeNameOverride = nameOverride;
+            return this;
+        }
+        private CharSequence mSubtypeNameOverride = "";
+
+        /**
          * @param subtypeId is the unique ID for this subtype. The input method framework keeps
          * track of enabled subtypes by ID. When the IME package gets upgraded, enabled IDs will
          * stay enabled even if other attributes are different. If the ID is unspecified or 0,
@@ -229,23 +245,23 @@
         public InputMethodSubtype build() {
             return new InputMethodSubtype(this);
         }
-     }
+    }
 
-     private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId, String locale,
-             String mode, String extraValue, boolean isAuxiliary,
-             boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) {
-         final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
-         builder.mSubtypeNameResId = nameId;
-         builder.mSubtypeIconResId = iconId;
-         builder.mSubtypeLocale = locale;
-         builder.mSubtypeMode = mode;
-         builder.mSubtypeExtraValue = extraValue;
-         builder.mIsAuxiliary = isAuxiliary;
-         builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
-         builder.mSubtypeId = id;
-         builder.mIsAsciiCapable = isAsciiCapable;
-         return builder;
-     }
+    private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId,
+            String locale, String mode, String extraValue, boolean isAuxiliary,
+            boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) {
+        final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
+        builder.mSubtypeNameResId = nameId;
+        builder.mSubtypeIconResId = iconId;
+        builder.mSubtypeLocale = locale;
+        builder.mSubtypeMode = mode;
+        builder.mSubtypeExtraValue = extraValue;
+        builder.mIsAuxiliary = isAuxiliary;
+        builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
+        builder.mSubtypeId = id;
+        builder.mIsAsciiCapable = isAsciiCapable;
+        return builder;
+    }
 
     /**
      * Constructor with no subtype ID specified.
@@ -305,6 +321,7 @@
      */
     private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
         mSubtypeNameResId = builder.mSubtypeNameResId;
+        mSubtypeNameOverride = builder.mSubtypeNameOverride;
         mSubtypeIconResId = builder.mSubtypeIconResId;
         mSubtypeLocale = builder.mSubtypeLocale;
         mSubtypeLanguageTag = builder.mSubtypeLanguageTag;
@@ -327,6 +344,8 @@
     InputMethodSubtype(Parcel source) {
         String s;
         mSubtypeNameResId = source.readInt();
+        CharSequence cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        mSubtypeNameOverride = cs != null ? cs : "";
         mSubtypeIconResId = source.readInt();
         s = source.readString();
         mSubtypeLocale = s != null ? s : "";
@@ -351,6 +370,14 @@
     }
 
     /**
+     * @return The subtype's untranslatable name string.
+     */
+    @NonNull
+    public CharSequence getNameOverride() {
+        return mSubtypeNameOverride;
+    }
+
+    /**
      * @return Resource ID of the subtype icon drawable.
      */
     public int getIconResId() {
@@ -532,8 +559,11 @@
     public CharSequence getDisplayName(
             Context context, String packageName, ApplicationInfo appInfo) {
         if (mSubtypeNameResId == 0) {
-            return getLocaleDisplayName(getLocaleFromContext(context), getLocaleObject(),
-                    DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU);
+            return TextUtils.isEmpty(mSubtypeNameOverride)
+                    ? getLocaleDisplayName(
+                            getLocaleFromContext(context), getLocaleObject(),
+                            DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU)
+                    : mSubtypeNameOverride;
         }
 
         final CharSequence subtypeName = context.getPackageManager().getText(
@@ -698,6 +728,7 @@
     @Override
     public void writeToParcel(Parcel dest, int parcelableFlags) {
         dest.writeInt(mSubtypeNameResId);
+        TextUtils.writeToParcel(mSubtypeNameOverride, dest, parcelableFlags);
         dest.writeInt(mSubtypeIconResId);
         dest.writeString(mSubtypeLocale);
         dest.writeString(mSubtypeLanguageTag);
@@ -765,4 +796,4 @@
         }
         return sortedList;
     }
-}
\ No newline at end of file
+}
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/core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl
new file mode 100644
index 0000000..ffadf82
--- /dev/null
+++ b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl
@@ -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.
+ */
+
+package android.view.inputmethod;
+
+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/PreviewableHandwritingGesture.java b/core/java/android/view/inputmethod/PreviewableHandwritingGesture.java
new file mode 100644
index 0000000..7683ece
--- /dev/null
+++ b/core/java/android/view/inputmethod/PreviewableHandwritingGesture.java
@@ -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 android.view.inputmethod;
+
+import android.os.CancellationSignal;
+
+import java.util.Set;
+
+/**
+ * A {@link HandwritingGesture} that can be
+ * {@link InputConnection#previewHandwritingGesture(
+ *  PreviewableHandwritingGesture, CancellationSignal) previewed}.
+ *
+ *  Note: An editor might only implement a subset of gesture previews and declares the supported
+ *  ones via {@link EditorInfo#getSupportedHandwritingGesturePreviews}.
+ *
+ * @see EditorInfo#setSupportedHandwritingGesturePreviews(Set)
+ * @see EditorInfo#getSupportedHandwritingGesturePreviews()
+ */
+public abstract class PreviewableHandwritingGesture extends HandwritingGesture {
+    PreviewableHandwritingGesture() {
+        // intentionally empty.
+    }
+}
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index f2b7099..7525d72 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -28,8 +28,11 @@
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.RectF;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
+import android.os.ICancellationSignal;
 import android.os.Looper;
 import android.os.ResultReceiver;
 import android.os.Trace;
@@ -982,62 +985,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 +1009,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,24 +1017,31 @@
         });
     }
 
-    /**
-     * 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()) {
+    @Override
+    public void previewHandwritingGesture(
+            InputConnectionCommandHeader header, ParcelableHandwritingGesture gestureContainer,
+            ICancellationSignal transport) {
+
+        // TODO(b/254727073): Implement CancellationSignal receiver
+        final CancellationSignal cancellationSignal = CancellationSignal.fromTransport(transport);
+        // Previews always use PreviewableHandwritingGesture but if incorrectly wrong class is
+        // passed, ClassCastException will be sent back to caller.
+        final PreviewableHandwritingGesture gesture =
+                (PreviewableHandwritingGesture) gestureContainer.get();
+
+        dispatchWithTracing("previewHandwritingGesture", () -> {
+            if (header.mSessionId != mCurrentSessionId.get()
+                    || (cancellationSignal != null && cancellationSignal.isCanceled())) {
                 return;  // cancelled
             }
-            requestCursorUpdatesInternal(cursorUpdateMode, cursorUpdateFilter, imeDisplayId);
+            InputConnection ic = getInputConnection();
+            if (ic == null || !isActive()) {
+                Log.w(TAG, "previewHandwritingGesture on inactive InputConnection");
+                return; // cancelled
+            }
+
+            ic.previewHandwritingGesture(gesture, cancellationSignal);
         });
     }
 
@@ -1123,7 +1080,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 +1105,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/SelectGesture.java b/core/java/android/view/inputmethod/SelectGesture.java
index 6dc4ed2..ba600df 100644
--- a/core/java/android/view/inputmethod/SelectGesture.java
+++ b/core/java/android/view/inputmethod/SelectGesture.java
@@ -33,7 +33,7 @@
  * <p>Note: This selects all text <em>within</em> the given area. To select a range <em>between</em>
  * two areas, use {@link SelectRangeGesture}.</p>
  */
-public final class SelectGesture extends HandwritingGesture implements Parcelable {
+public final class SelectGesture extends PreviewableHandwritingGesture implements Parcelable {
 
     private @Granularity int mGranularity;
     private RectF mArea;
@@ -72,7 +72,6 @@
         return mArea;
     }
 
-
     /**
      * Builder for {@link SelectGesture}. This class is not designed to be thread-safe.
      */
diff --git a/core/java/android/view/inputmethod/SelectRangeGesture.java b/core/java/android/view/inputmethod/SelectRangeGesture.java
index 7cb6002..c31bc27 100644
--- a/core/java/android/view/inputmethod/SelectRangeGesture.java
+++ b/core/java/android/view/inputmethod/SelectRangeGesture.java
@@ -34,7 +34,7 @@
  * <p>Note: this selects text within a range <em>between</em> two given areas. To select all text
  * <em>within</em> a single area, use {@link SelectGesture}</p>
  */
-public final class SelectRangeGesture extends HandwritingGesture implements Parcelable {
+public final class SelectRangeGesture extends PreviewableHandwritingGesture implements Parcelable {
 
     private @Granularity int mGranularity;
     private RectF mStartArea;
@@ -87,7 +87,6 @@
         return mEndArea;
     }
 
-
     /**
      * Builder for {@link SelectRangeGesture}. This class is not designed to be thread-safe.
      */
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
new file mode 100644
index 0000000..500c41c
--- /dev/null
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -0,0 +1,798 @@
+/*
+ * 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 android.graphics.Typeface.NORMAL;
+
+import android.annotation.ColorInt;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Px;
+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.method.TransformationMethod;
+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 editor.
+     */
+    private final @Px float mTextSize;
+
+    /**
+     * The {@link 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.
+     */
+    @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+    private final 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 editor 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 shadow color of the text shadow.
+     */
+    private final @ColorInt int mShadowColor;
+
+    /**
+     * 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 mHighlightTextColor;
+
+    /**
+     * The current text color of the editor.
+     */
+    private final @ColorInt int mTextColor;
+
+    /**
+     *  The current color of the hint text.
+     */
+    private final @ColorInt int mHintTextColor;
+
+    /**
+     * The text color used to paint the links in the editor.
+     */
+    private final @ColorInt int mLinkTextColor;
+
+    private TextAppearanceInfo(@NonNull final TextAppearanceInfo.Builder builder) {
+        mTextSize = builder.mTextSize;
+        mTextLocales = builder.mTextLocales;
+        mSystemFontFamilyName = builder.mSystemFontFamilyName;
+        mTextFontWeight = builder.mTextFontWeight;
+        mTextStyle = builder.mTextStyle;
+        mAllCaps = builder.mAllCaps;
+        mShadowDx = builder.mShadowDx;
+        mShadowDy = builder.mShadowDy;
+        mShadowRadius = builder.mShadowRadius;
+        mShadowColor = builder.mShadowColor;
+        mElegantTextHeight = builder.mElegantTextHeight;
+        mFallbackLineSpacing = builder.mFallbackLineSpacing;
+        mLetterSpacing = builder.mLetterSpacing;
+        mFontFeatureSettings = builder.mFontFeatureSettings;
+        mFontVariationSettings = builder.mFontVariationSettings;
+        mLineBreakStyle = builder.mLineBreakStyle;
+        mLineBreakWordStyle = builder.mLineBreakWordStyle;
+        mTextScaleX = builder.mTextScaleX;
+        mHighlightTextColor = builder.mHighlightTextColor;
+        mTextColor = builder.mTextColor;
+        mHintTextColor = builder.mHintTextColor;
+        mLinkTextColor = builder.mLinkTextColor;
+    }
+
+    @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.writeInt(mShadowColor);
+        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(mHighlightTextColor);
+        dest.writeInt(mTextColor);
+        dest.writeInt(mHintTextColor);
+        dest.writeInt(mLinkTextColor);
+    }
+
+    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();
+        mShadowColor = in.readInt();
+        mElegantTextHeight = in.readBoolean();
+        mFallbackLineSpacing = in.readBoolean();
+        mLetterSpacing = in.readFloat();
+        mFontFeatureSettings = in.readString8();
+        mFontVariationSettings = in.readString8();
+        mLineBreakStyle = in.readInt();
+        mLineBreakWordStyle = in.readInt();
+        mTextScaleX = in.readFloat();
+        mHighlightTextColor = in.readInt();
+        mTextColor = in.readInt();
+        mHintTextColor = in.readInt();
+        mLinkTextColor = 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 editor.
+     */
+    public @Px float getTextSize() {
+        return mTextSize;
+    }
+
+    /**
+     * Returns the {@link 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.
+     *
+     * @see Typeface#getSystemFontFamilyName()
+     */
+    @Nullable
+    public String getSystemFontFamilyName() {
+        return mSystemFontFamilyName;
+    }
+
+    /**
+     * Returns the weight of the text, or {@code FontStyle#FONT_WEIGHT_UNSPECIFIED}
+     * when no {@link Typeface} is specified.
+     */
+    @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+    public 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 Typeface
+     */
+    public @Typeface.Style int getTextStyle() {
+        return mTextStyle;
+    }
+
+    /**
+     * Returns whether the transformation method applied to the current editor is set to all caps.
+     *
+     * @see TextView#setAllCaps(boolean)
+     * @see TextView#setTransformationMethod(TransformationMethod)
+     */
+    public boolean isAllCaps() {
+        return mAllCaps;
+    }
+
+    /**
+     * Returns the horizontal offset (in pixels) of the text shadow.
+     *
+     * @see Paint#setShadowLayer(float, float, float, int)
+     */
+    public @Px float getShadowDx() {
+        return mShadowDx;
+    }
+
+    /**
+     * Returns the vertical offset (in pixels) of the text shadow.
+     *
+     * @see Paint#setShadowLayer(float, float, float, int)
+     */
+    public @Px float getShadowDy() {
+        return mShadowDy;
+    }
+
+    /**
+     * Returns the blur radius (in pixels) of the text shadow.
+     *
+     * @see Paint#setShadowLayer(float, float, float, int)
+     */
+    public @Px float getShadowRadius() {
+        return mShadowRadius;
+    }
+
+    /**
+     * Returns the color of the text shadow.
+     *
+     * @see Paint#setShadowLayer(float, float, float, int)
+     */
+    public @ColorInt int getShadowColor() {
+        return mShadowColor;
+    }
+
+    /**
+     * 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.
+     *
+     * @see Paint#isElegantTextHeight()
+     */
+    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.
+     *
+     * @see TextView#getHighlightColor()
+     */
+    public @ColorInt int getHighlightTextColor() {
+        return mHighlightTextColor;
+    }
+
+    /**
+     * Returns the current text color of the editor.
+     *
+     * @see TextView#getCurrentTextColor()
+     */
+    public @ColorInt int getTextColor() {
+        return mTextColor;
+    }
+
+    /**
+     * Returns the current color of the hint text.
+     *
+     * @see TextView#getCurrentHintTextColor()
+     */
+    public @ColorInt int getHintTextColor() {
+        return mHintTextColor;
+    }
+
+    /**
+     * Returns the text color used to paint the links in the editor.
+     *
+     * @see TextView#getLinkTextColors()
+     */
+    public @ColorInt int getLinkTextColor() {
+        return mLinkTextColor;
+    }
+
+
+    @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 && that.mShadowColor == mShadowColor
+                && mElegantTextHeight == that.mElegantTextHeight
+                && mFallbackLineSpacing == that.mFallbackLineSpacing && Float.compare(
+                that.mLetterSpacing, mLetterSpacing) == 0 && mLineBreakStyle == that.mLineBreakStyle
+                && mLineBreakWordStyle == that.mLineBreakWordStyle
+                && mHighlightTextColor == that.mHighlightTextColor
+                && mTextColor == that.mTextColor
+                && mLinkTextColor == that.mLinkTextColor
+                && mHintTextColor == that.mHintTextColor
+                && 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, mShadowColor,
+                mElegantTextHeight, mFallbackLineSpacing, mLetterSpacing, mFontFeatureSettings,
+                mFontVariationSettings, mLineBreakStyle, mLineBreakWordStyle, mTextScaleX,
+                mHighlightTextColor, mTextColor, mHintTextColor, mLinkTextColor);
+    }
+
+    @Override
+    public String toString() {
+        return "TextAppearanceInfo{"
+                + "mTextSize=" + mTextSize
+                + ", mTextLocales=" + mTextLocales
+                + ", mSystemFontFamilyName='" + mSystemFontFamilyName + '\''
+                + ", mTextFontWeight=" + mTextFontWeight
+                + ", mTextStyle=" + mTextStyle
+                + ", mAllCaps=" + mAllCaps
+                + ", mShadowDx=" + mShadowDx
+                + ", mShadowDy=" + mShadowDy
+                + ", mShadowRadius=" + mShadowRadius
+                + ", mShadowColor=" + mShadowColor
+                + ", mElegantTextHeight=" + mElegantTextHeight
+                + ", mFallbackLineSpacing=" + mFallbackLineSpacing
+                + ", mLetterSpacing=" + mLetterSpacing
+                + ", mFontFeatureSettings='" + mFontFeatureSettings + '\''
+                + ", mFontVariationSettings='" + mFontVariationSettings + '\''
+                + ", mLineBreakStyle=" + mLineBreakStyle
+                + ", mLineBreakWordStyle=" + mLineBreakWordStyle
+                + ", mTextScaleX=" + mTextScaleX
+                + ", mHighlightTextColor=" + mHighlightTextColor
+                + ", mTextColor=" + mTextColor
+                + ", mHintTextColor=" + mHintTextColor
+                + ", mLinkTextColor=" + mLinkTextColor
+                + '}';
+    }
+
+    /**
+     * Builder for {@link TextAppearanceInfo}.
+     */
+    public static final class Builder {
+        private @Px float mTextSize = -1;
+        private @NonNull LocaleList mTextLocales = LocaleList.getAdjustedDefault();
+        @Nullable private String mSystemFontFamilyName = null;
+        @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+        private int mTextFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
+        private @Typeface.Style int mTextStyle = NORMAL;
+        private boolean mAllCaps = false;
+        private @Px float mShadowDx = 0;
+        private @Px float mShadowDy = 0;
+        private @Px float mShadowRadius = 0;
+        private @ColorInt int mShadowColor = 0;
+        private boolean mElegantTextHeight = false;
+        private boolean mFallbackLineSpacing = false;
+        private float mLetterSpacing = 0;
+        @Nullable private String mFontFeatureSettings = null;
+        @Nullable private String mFontVariationSettings = null;
+        @LineBreakConfig.LineBreakStyle
+        private int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE;
+        @LineBreakConfig.LineBreakWordStyle
+        private int mLineBreakWordStyle = LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+        private float mTextScaleX = 1;
+        private @ColorInt int mHighlightTextColor = 0;
+        private @ColorInt int mTextColor = 0;
+        private @ColorInt int mHintTextColor = 0;
+        private @ColorInt int mLinkTextColor = 0;
+
+        /**
+         * Set the text size (in pixels) obtained from the current editor.
+         */
+        @NonNull
+        public Builder setTextSize(@Px float textSize) {
+            mTextSize = textSize;
+            return this;
+        }
+
+        /**
+         * Set the {@link LocaleList} of the text.
+         */
+        @NonNull
+        public Builder setTextLocales(@NonNull LocaleList textLocales) {
+            mTextLocales = textLocales;
+            return this;
+        }
+
+        /**
+         * Set the system font family name if the {@link Typeface} of the text is created from a
+         * system font family.
+         *
+         * @see Typeface#getSystemFontFamilyName()
+         */
+        @NonNull
+        public Builder setSystemFontFamilyName(@Nullable String systemFontFamilyName) {
+            mSystemFontFamilyName = systemFontFamilyName;
+            return this;
+        }
+
+        /**
+         * Set the weight of the text.
+         */
+        @NonNull
+        public Builder setTextFontWeight(
+                @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED,
+                        to = FontStyle.FONT_WEIGHT_MAX) int textFontWeight) {
+            mTextFontWeight = textFontWeight;
+            return this;
+        }
+
+        /**
+         * Set the style (normal, bold, italic, bold|italic) of the text.
+         *
+         * @see Typeface
+         */
+        @NonNull
+        public Builder setTextStyle(@Typeface.Style int textStyle) {
+            mTextStyle = textStyle;
+            return this;
+        }
+
+        /**
+         * Set whether the transformation method applied to the current editor  is set to all caps.
+         *
+         * @see TextView#setAllCaps(boolean)
+         * @see TextView#setTransformationMethod(TransformationMethod)
+         */
+        @NonNull
+        public Builder setAllCaps(boolean allCaps) {
+            mAllCaps = allCaps;
+            return this;
+        }
+
+        /**
+         * Set the horizontal offset (in pixels) of the text shadow.
+         *
+         * @see Paint#setShadowLayer(float, float, float, int)
+         */
+        @NonNull
+        public Builder setShadowDx(@Px float shadowDx) {
+            mShadowDx = shadowDx;
+            return this;
+        }
+
+        /**
+         * Set the vertical offset (in pixels) of the text shadow.
+         *
+         * @see Paint#setShadowLayer(float, float, float, int)
+         */
+        @NonNull
+        public Builder setShadowDy(@Px float shadowDy) {
+            mShadowDy = shadowDy;
+            return this;
+        }
+
+        /**
+         * Set the blur radius (in pixels) of the text shadow.
+         *
+         * @see Paint#setShadowLayer(float, float, float, int)
+         */
+        @NonNull
+        public Builder setShadowRadius(@Px float shadowRadius) {
+            mShadowRadius = shadowRadius;
+            return this;
+        }
+
+        /**
+         * Set the color of the text shadow.
+         *
+         * @see Paint#setShadowLayer(float, float, float, int)
+         */
+        @NonNull
+        public Builder setShadowColor(@ColorInt int shadowColor) {
+            mShadowColor = shadowColor;
+            return this;
+        }
+
+        /**
+         * Set the elegant height metrics flag. 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.
+         *
+         * @see Paint#isElegantTextHeight()
+         */
+        @NonNull
+        public Builder setElegantTextHeight(boolean elegantTextHeight) {
+            mElegantTextHeight = elegantTextHeight;
+            return this;
+        }
+
+        /**
+         * Set whether to expand linespacing based on fallback fonts.
+         *
+         * @see TextView#setFallbackLineSpacing(boolean)
+         */
+        @NonNull
+        public Builder setFallbackLineSpacing(boolean fallbackLineSpacing) {
+            mFallbackLineSpacing = fallbackLineSpacing;
+            return this;
+        }
+
+        /**
+         * Set the text letter-spacing, which determines the spacing between characters.
+         * The value is in 'EM' units. Normally, this value is 0.0.
+         */
+        @NonNull
+        public Builder setLetterSpacing(float letterSpacing) {
+            mLetterSpacing = letterSpacing;
+            return this;
+        }
+
+        /**
+         * Set the font feature settings.
+         *
+         * @see Paint#getFontFeatureSettings()
+         */
+        @NonNull
+        public Builder setFontFeatureSettings(@Nullable String fontFeatureSettings) {
+            mFontFeatureSettings = fontFeatureSettings;
+            return this;
+        }
+
+        /**
+         * Set the font variation settings. Returns null if no variation is specified.
+         *
+         * @see Paint#getFontVariationSettings()
+         */
+        @NonNull
+        public Builder setFontVariationSettings(@Nullable String fontVariationSettings) {
+            mFontVariationSettings = fontVariationSettings;
+            return this;
+        }
+
+        /**
+         * Set the line-break strategies for text wrapping.
+         *
+         * @see TextView#setLineBreakStyle(int)
+         */
+        @NonNull
+        public Builder setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
+            mLineBreakStyle = lineBreakStyle;
+            return this;
+        }
+
+        /**
+         * Set the line-break word strategies for text wrapping.
+         *
+         * @see TextView#setLineBreakWordStyle(int)
+         */
+        @NonNull
+        public Builder setLineBreakWordStyle(
+                @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
+            mLineBreakWordStyle = lineBreakWordStyle;
+            return this;
+        }
+
+        /**
+         * Set the extent by which text should be stretched horizontally.
+         */
+        @NonNull
+        public Builder setTextScaleX(float textScaleX) {
+            mTextScaleX = textScaleX;
+            return this;
+        }
+
+        /**
+         * Set the color of the text selection highlight.
+         *
+         * @see TextView#getHighlightColor()
+         */
+        @NonNull
+        public Builder setHighlightTextColor(@ColorInt int highlightTextColor) {
+            mHighlightTextColor = highlightTextColor;
+            return this;
+        }
+
+        /**
+         * Set the current text color of the editor.
+         *
+         * @see TextView#getCurrentTextColor()
+         */
+        @NonNull
+        public Builder setTextColor(@ColorInt int textColor) {
+            mTextColor = textColor;
+            return this;
+        }
+
+        /**
+         * Set the current color of the hint text.
+         *
+         * @see TextView#getCurrentHintTextColor()
+         */
+        @NonNull
+        public Builder setHintTextColor(@ColorInt int hintTextColor) {
+            mHintTextColor = hintTextColor;
+            return this;
+        }
+
+        /**
+         * Set the text color used to paint the links in the editor.
+         *
+         * @see TextView#getLinkTextColors()
+         */
+        @NonNull
+        public Builder setLinkTextColor(@ColorInt int linkTextColor) {
+            mLinkTextColor = linkTextColor;
+            return this;
+        }
+
+        /**
+         * Returns {@link TextAppearanceInfo} using parameters in this
+         * {@link TextAppearanceInfo.Builder}.
+         */
+        @NonNull
+        public TextAppearanceInfo build() {
+            return new TextAppearanceInfo(this);
+        }
+    }
+}
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/webkit/ConsoleMessage.java b/core/java/android/webkit/ConsoleMessage.java
index 5474557..89cb6b2 100644
--- a/core/java/android/webkit/ConsoleMessage.java
+++ b/core/java/android/webkit/ConsoleMessage.java
@@ -68,4 +68,4 @@
     public int lineNumber() {
         return mLineNumber;
     }
-};
+}
diff --git a/core/java/android/webkit/ValueCallback.java b/core/java/android/webkit/ValueCallback.java
index 5c7d97f..3d5bb49 100644
--- a/core/java/android/webkit/ValueCallback.java
+++ b/core/java/android/webkit/ValueCallback.java
@@ -25,4 +25,4 @@
      * @param value The value.
      */
     public void onReceiveValue(T value);
-};
+}
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index 5265840..e6379cf 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -968,7 +968,8 @@
         final int position = getSelectedItemPosition();
         if (position >= 0) {
             // we fire selection events here not in View
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+            // posting the event should delay it long enough for UI changes to take effect.
+            post(() -> sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED));
         }
     }
 
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 8f590f8..0a3ea8a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -21,6 +21,7 @@
 
 import android.R;
 import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -37,6 +38,7 @@
 import android.content.UndoOwner;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -49,8 +51,10 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.RenderNode;
+import android.graphics.Typeface;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.fonts.FontStyle;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.LocaleList;
@@ -125,6 +129,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;
@@ -2049,7 +2054,10 @@
         }
     }
 
-    void onDraw(Canvas canvas, Layout layout, Path highlight, Paint highlightPaint,
+    void onDraw(Canvas canvas, Layout layout,
+            List<Path> highlightPaths,
+            List<Paint> highlightPaints,
+            Path selectionHighlight, Paint selectionHighlightPaint,
             int cursorOffsetVertical) {
         final int selectionStart = mTextView.getSelectionStart();
         final int selectionEnd = mTextView.getSelectionEnd();
@@ -2073,37 +2081,41 @@
             mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
         }
 
-        if (highlight != null && selectionStart == selectionEnd && mDrawableForCursor != null) {
+        if (selectionHighlight != null && selectionStart == selectionEnd
+                && mDrawableForCursor != null
+                && !mTextView.hasGesturePreviewHighlight()) {
             drawCursor(canvas, cursorOffsetVertical);
             // Rely on the drawable entirely, do not draw the cursor line.
             // Has to be done after the IMM related code above which relies on the highlight.
-            highlight = null;
+            selectionHighlight = null;
         }
 
         if (mSelectionActionModeHelper != null) {
             mSelectionActionModeHelper.onDraw(canvas);
             if (mSelectionActionModeHelper.isDrawingHighlight()) {
-                highlight = null;
+                selectionHighlight = null;
             }
         }
 
         if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) {
-            drawHardwareAccelerated(canvas, layout, highlight, highlightPaint,
-                    cursorOffsetVertical);
+            drawHardwareAccelerated(canvas, layout, highlightPaths, highlightPaints,
+                    selectionHighlight, selectionHighlightPaint, cursorOffsetVertical);
         } else {
-            layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);
+            layout.draw(canvas, highlightPaths, highlightPaints, selectionHighlight,
+                    selectionHighlightPaint, cursorOffsetVertical);
         }
     }
 
-    private void drawHardwareAccelerated(Canvas canvas, Layout layout, Path highlight,
-            Paint highlightPaint, int cursorOffsetVertical) {
+    private void drawHardwareAccelerated(Canvas canvas, Layout layout,
+            List<Path> highlightPaths, List<Paint> highlightPaints,
+            Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical) {
         final long lineRange = layout.getLineRangeForDraw(canvas);
         int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
         int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
         if (lastLine < 0) return;
 
-        layout.drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,
-                firstLine, lastLine);
+        layout.drawWithoutText(canvas, highlightPaths, highlightPaints, selectionHighlight,
+                selectionHighlightPaint, cursorOffsetVertical, firstLine, lastLine);
 
         if (layout instanceof DynamicLayout) {
             if (mTextRenderNodes == null) {
@@ -2149,8 +2161,9 @@
                     continue;
                 }
                 startIndexToFindAvailableRenderNode = drawHardwareAcceleratedInner(canvas, layout,
-                        highlight, highlightPaint, cursorOffsetVertical, blockEndLines,
-                        blockIndices, i, numberOfBlocks, startIndexToFindAvailableRenderNode);
+                        selectionHighlight, selectionHighlightPaint, cursorOffsetVertical,
+                        blockEndLines, blockIndices, i, numberOfBlocks,
+                        startIndexToFindAvailableRenderNode);
                 if (blockEndLines[i] >= lastLine) {
                     lastIndex = Math.max(indexFirstChangedBlock, i + 1);
                     break;
@@ -2164,9 +2177,9 @@
                             || mTextRenderNodes[blockIndex] == null
                             || mTextRenderNodes[blockIndex].needsToBeShifted) {
                         startIndexToFindAvailableRenderNode = drawHardwareAcceleratedInner(canvas,
-                                layout, highlight, highlightPaint, cursorOffsetVertical,
-                                blockEndLines, blockIndices, block, numberOfBlocks,
-                                startIndexToFindAvailableRenderNode);
+                                layout, selectionHighlight, selectionHighlightPaint,
+                                cursorOffsetVertical, blockEndLines, blockIndices, block,
+                                numberOfBlocks, startIndexToFindAvailableRenderNode);
                     }
                 }
             }
@@ -2702,7 +2715,7 @@
         unregisterOnBackInvokedCallback();
     }
 
-    private void stopTextActionModeWithPreservingSelection() {
+    void stopTextActionModeWithPreservingSelection() {
         if (mTextActionMode != null) {
             mRestartActionModeOnNextRefresh = true;
         }
@@ -4630,14 +4643,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 +4773,43 @@
                 }
             }
 
+            if (includeTextAppearance) {
+                Typeface typeface = mTextView.getPaint().getTypeface();
+                String systemFontFamilyName = null;
+                int textFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
+                if (typeface != null) {
+                    systemFontFamilyName = typeface.getSystemFontFamilyName();
+                    textFontWeight = typeface.getWeight();
+                }
+                ColorStateList linkTextColors = mTextView.getLinkTextColors();
+                @ColorInt int linkTextColor = linkTextColors != null
+                        ? linkTextColors.getDefaultColor() : 0;
+
+                TextAppearanceInfo.Builder appearanceBuilder = new TextAppearanceInfo.Builder();
+                appearanceBuilder.setTextSize(mTextView.getTextSize())
+                        .setTextLocales(mTextView.getTextLocales())
+                        .setSystemFontFamilyName(systemFontFamilyName)
+                        .setTextFontWeight(textFontWeight)
+                        .setTextStyle(mTextView.getTypefaceStyle())
+                        .setAllCaps(mTextView.isAllCaps())
+                        .setShadowDx(mTextView.getShadowDx())
+                        .setShadowDy(mTextView.getShadowDy())
+                        .setShadowRadius(mTextView.getShadowRadius())
+                        .setShadowColor(mTextView.getShadowColor())
+                        .setElegantTextHeight(mTextView.isElegantTextHeight())
+                        .setFallbackLineSpacing(mTextView.isFallbackLineSpacing())
+                        .setLetterSpacing(mTextView.getLetterSpacing())
+                        .setFontFeatureSettings(mTextView.getFontFeatureSettings())
+                        .setFontVariationSettings(mTextView.getFontVariationSettings())
+                        .setLineBreakStyle(mTextView.getLineBreakStyle())
+                        .setLineBreakWordStyle(mTextView.getLineBreakWordStyle())
+                        .setTextScaleX(mTextView.getTextScaleX())
+                        .setHighlightTextColor(mTextView.getHighlightColor())
+                        .setTextColor(mTextView.getCurrentTextColor())
+                        .setHintTextColor(mTextView.getCurrentHintTextColor())
+                        .setLinkTextColor(linkTextColor);
+                builder.setTextAppearanceInfo(appearanceBuilder.build());
+            }
             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/Spinner.java b/core/java/android/widget/Spinner.java
index ba6fa19..ad431ef 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -802,6 +802,21 @@
         dialog.dismiss();
     }
 
+    /**
+     * Sets selection and dismisses the spinner's popup if it can be dismissed.
+     * For ease of use in tests, where publicly obtaining the spinner's popup is difficult.
+     *
+     * @param which index of the item to be selected.
+     * @hide
+     */
+    @TestApi
+    public void onClick(int which) {
+        setSelection(which);
+        if (mPopup != null && mPopup.isShowing()) {
+            mPopup.dismiss();
+        }
+    }
+
     @Override
     public CharSequence getAccessibilityClassName() {
         return Spinner.class.getName();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index b5c58fb..b9b928e 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -68,7 +68,9 @@
 import android.graphics.BaseCanvas;
 import android.graphics.BlendMode;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Insets;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
 import android.graphics.Path;
@@ -86,6 +88,7 @@
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.LocaleList;
 import android.os.Parcel;
@@ -101,6 +104,7 @@
 import android.text.GetChars;
 import android.text.GraphemeClusterSegmentFinder;
 import android.text.GraphicsOperations;
+import android.text.Highlights;
 import android.text.InputFilter;
 import android.text.InputType;
 import android.text.Layout;
@@ -146,6 +150,7 @@
 import android.text.style.URLSpan;
 import android.text.style.UpdateAppearance;
 import android.text.util.Linkify;
+import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.FeatureFlagUtils;
@@ -197,9 +202,11 @@
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InsertGesture;
 import android.view.inputmethod.JoinOrSplitGesture;
+import android.view.inputmethod.PreviewableHandwritingGesture;
 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;
@@ -219,6 +226,7 @@
 
 import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
 import com.android.internal.inputmethod.EditableInputConnection;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -236,8 +244,10 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
@@ -924,6 +934,15 @@
     @UnsupportedAppUsage
     private boolean mHighlightPathBogus = true;
 
+    private List<Path> mHighlightPaths;
+    private List<Paint> mHighlightPaints;
+    private Highlights mHighlights;
+    private int mGesturePreviewHighlightStart = -1;
+    private int mGesturePreviewHighlightEnd = -1;
+    private Paint mGesturePreviewHighlightPaint;
+    private final List<Path> mPathRecyclePool = new ArrayList<>();
+    private boolean mHighlightPathsBogus = true;
+
     // Although these fields are specific to editable text, they are not added to Editor because
     // they are defined by the TextView's style and are theme-dependent.
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -980,8 +999,11 @@
 
     /**
      * The last input source on this TextView.
+     *
+     * Use the SOURCE_TOUCHSCREEN as the default value for backward compatibility. There could be a
+     * non UI event originated ActionMode initiation, e.g. API call, a11y events, etc.
      */
-    private int mLastInputSource = InputDevice.SOURCE_UNKNOWN;
+    private int mLastInputSource = InputDevice.SOURCE_TOUCHSCREEN;
 
     /**
      * The TextView does not auto-size text (default).
@@ -2284,11 +2306,13 @@
      * @param familyName family name string, e.g. "serif"
      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
      * @param style a typeface style
-     * @param weight a weight value for the Typeface or -1 if not specified.
+     * @param weight a weight value for the Typeface or {@code FontStyle.FONT_WEIGHT_UNSPECIFIED}
+     *               if not specified.
      */
     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
-            @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
+            @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+                    int weight) {
         if (typeface == null && familyName != null) {
             // Lookup normal Typeface from system font map.
             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
@@ -2315,7 +2339,8 @@
     }
 
     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
-            @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
+            @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+                    int weight) {
         if (weight >= 0) {
             weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
             final boolean italic = (style & Typeface.ITALIC) != 0;
@@ -4016,7 +4041,7 @@
         boolean mFontFamilyExplicit = false;
         int mTypefaceIndex = -1;
         int mTextStyle = 0;
-        int mFontWeight = -1;
+        int mFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
         boolean mAllCaps = false;
         int mShadowColor = 0;
         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
@@ -6123,6 +6148,87 @@
     }
 
     /**
+     * Set Highlights
+     *
+     * @param highlights A highlight object. Call with null for reset.
+     *
+     * @see #getHighlights()
+     * @see Highlights
+     */
+    public void setHighlights(@Nullable Highlights highlights) {
+        mHighlights = highlights;
+        mHighlightPathsBogus = true;
+        invalidate();
+    }
+
+    /**
+     * Returns highlights
+     *
+     * @return a highlight to be drawn. null if no highlight was set.
+     *
+     * @see #setHighlights(Highlights)
+     * @see Highlights
+     *
+     */
+    @Nullable
+    public Highlights getHighlights() {
+        return mHighlights;
+    }
+
+    /**
+     * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
+     * will be selected by the ongoing select handwriting gesture. While the gesture preview
+     * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
+     * the gesture preview highlight will be cleared.
+     */
+    private void setSelectGesturePreviewHighlight(int start, int end) {
+        // Selection preview highlight color is the same as selection highlight color.
+        setGesturePreviewHighlight(start, end, mHighlightColor);
+    }
+
+    /**
+     * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
+     * will be deleted by the ongoing delete handwriting gesture. While the gesture preview
+     * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
+     * the gesture preview highlight will be cleared.
+     */
+    private void setDeleteGesturePreviewHighlight(int start, int end) {
+        // Deletion preview highlight color is 20% opacity of the default text color.
+        int color = mTextColor.getDefaultColor();
+        color = ColorUtils.setAlphaComponent(color, (int) (0.2f * Color.alpha(color)));
+        setGesturePreviewHighlight(start, end, color);
+    }
+
+    private void setGesturePreviewHighlight(int start, int end, int color) {
+        mGesturePreviewHighlightStart = start;
+        mGesturePreviewHighlightEnd = end;
+        if (mGesturePreviewHighlightPaint == null) {
+            mGesturePreviewHighlightPaint = new Paint();
+            mGesturePreviewHighlightPaint.setStyle(Paint.Style.FILL);
+        }
+        mGesturePreviewHighlightPaint.setColor(color);
+
+        if (mEditor != null) {
+            mEditor.hideCursorAndSpanControllers();
+            mEditor.stopTextActionModeWithPreservingSelection();
+        }
+
+        mHighlightPathsBogus = true;
+        invalidate();
+    }
+
+    private void clearGesturePreviewHighlight() {
+        mGesturePreviewHighlightStart = -1;
+        mGesturePreviewHighlightEnd = -1;
+        mHighlightPathsBogus = true;
+        invalidate();
+    }
+
+    boolean hasGesturePreviewHighlight() {
+        return mGesturePreviewHighlightStart > 0;
+    }
+
+    /**
      * Convenience method to append the specified text to the TextView's
      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
      * if it was not already editable.
@@ -6941,18 +7047,18 @@
         if (isPassword) {
             setTransformationMethod(PasswordTransformationMethod.getInstance());
             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
-                    Typeface.NORMAL, -1 /* weight, not specifeid */);
+                    Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
         } else if (isVisiblePassword) {
             if (mTransformation == PasswordTransformationMethod.getInstance()) {
                 forceUpdate = true;
             }
             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
-                    Typeface.NORMAL, -1 /* weight, not specified */);
+                    Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
         } else if (wasPassword || wasVisiblePassword) {
             // not in password mode, clean up typeface and transformation
             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
-                    -1 /* weight, not specified */);
+                    FontStyle.FONT_WEIGHT_UNSPECIFIED);
             if (mTransformation == PasswordTransformationMethod.getInstance()) {
                 forceUpdate = true;
             }
@@ -8211,6 +8317,70 @@
         return drawableState;
     }
 
+    private void maybeUpdateHighlightPaths() {
+        if (!mHighlightPathsBogus) {
+            return;
+        }
+
+        if (mHighlightPaths != null) {
+            mPathRecyclePool.addAll(mHighlightPaths);
+            mHighlightPaths.clear();
+            mHighlightPaints.clear();
+        } else {
+            mHighlightPaths = new ArrayList<>();
+            mHighlightPaints = new ArrayList<>();
+        }
+
+        if (mHighlights != null) {
+            for (int i = 0; i < mHighlights.getSize(); ++i) {
+                final int[] ranges = mHighlights.getRanges(i);
+                final Paint paint = mHighlights.getPaint(i);
+
+                final Path path;
+                if (mPathRecyclePool.isEmpty()) {
+                    path = new Path();
+                } else {
+                    path = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
+                    mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
+                    path.reset();
+                }
+
+                boolean atLeastOnePathAdded = false;
+                for (int j = 0; j < ranges.length / 2; ++j) {
+                    final int start = ranges[2 * j];
+                    final int end = ranges[2 * j + 1];
+                    if (start < end) {
+                        mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
+                                path.addRect(left, top, right, bottom, Path.Direction.CW)
+                        );
+                        atLeastOnePathAdded = true;
+                    }
+                }
+                if (atLeastOnePathAdded) {
+                    mHighlightPaths.add(path);
+                    mHighlightPaints.add(paint);
+                }
+            }
+        }
+
+        if (hasGesturePreviewHighlight()) {
+            final Path path;
+            if (mPathRecyclePool.isEmpty()) {
+                path = new Path();
+            } else {
+                path = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
+                mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
+                path.reset();
+            }
+            mLayout.getSelectionPath(
+                    mGesturePreviewHighlightStart, mGesturePreviewHighlightEnd, path);
+            mHighlightPaths.add(path);
+            mHighlightPaints.add(mGesturePreviewHighlightPaint);
+        }
+
+        mHighlightPathsBogus = false;
+    }
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private Path getUpdatedHighlightPath() {
         Path highlight = null;
@@ -8410,17 +8580,22 @@
 
         final int cursorOffsetVertical = voffsetCursor - voffsetText;
 
-        Path highlight = getUpdatedHighlightPath();
+        maybeUpdateHighlightPaths();
+        // If there is a gesture preview highlight, then the selection or cursor is not drawn.
+        Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
         if (mEditor != null) {
-            mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
+            mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
+                    mHighlightPaint, cursorOffsetVertical);
         } else {
-            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
+            layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
+                    cursorOffsetVertical);
         }
 
         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
             final float dx = mMarquee.getGhostOffset();
             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
-            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
+            layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
+                    cursorOffsetVertical);
         }
 
         canvas.restore();
@@ -9104,6 +9279,14 @@
                 gestures.add(RemoveSpaceGesture.class);
                 gestures.add(JoinOrSplitGesture.class);
                 outAttrs.setSupportedHandwritingGestures(gestures);
+
+                Set<Class<? extends PreviewableHandwritingGesture>> previews = new ArraySet<>();
+                previews.add(SelectGesture.class);
+                previews.add(SelectRangeGesture.class);
+                previews.add(DeleteGesture.class);
+                previews.add(DeleteRangeGesture.class);
+                outAttrs.setSupportedHandwritingGesturePreviews(previews);
+
                 return ic;
             }
         }
@@ -9315,83 +9498,130 @@
     }
 
     /** @hide */
+    public boolean previewHandwritingGesture(
+            @NonNull PreviewableHandwritingGesture gesture,
+            @Nullable CancellationSignal cancellationSignal) {
+        if (gesture instanceof SelectGesture) {
+            performHandwritingSelectGesture((SelectGesture) gesture, /* isPreview= */ true);
+        } else if (gesture instanceof SelectRangeGesture) {
+            performHandwritingSelectRangeGesture(
+                    (SelectRangeGesture) gesture, /* isPreview= */ true);
+        } else if (gesture instanceof DeleteGesture) {
+            performHandwritingDeleteGesture((DeleteGesture) gesture, /* isPreview= */ true);
+        } else if (gesture instanceof DeleteRangeGesture) {
+            performHandwritingDeleteRangeGesture(
+                    (DeleteRangeGesture) gesture, /* isPreview= */ true);
+        } else {
+            return false;
+        }
+        if (cancellationSignal != null) {
+            cancellationSignal.setOnCancelListener(this::clearGesturePreviewHighlight);
+        }
+        return true;
+    }
+
+    /** @hide */
     public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) {
+        return performHandwritingSelectGesture(gesture, /* isPreview= */ false);
+    }
+
+    private int performHandwritingSelectGesture(@NonNull SelectGesture gesture, boolean isPreview) {
         int[] range = getRangeForRect(
                 convertFromScreenToContentCoordinates(gesture.getSelectionArea()),
                 gesture.getGranularity());
         if (range == null) {
-            return handleGestureFailure(gesture);
+            return handleGestureFailure(gesture, isPreview);
         }
-        Selection.setSelection(getEditableText(), range[0], range[1]);
-        mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
+        return performHandwritingSelectGesture(range, isPreview);
+    }
+
+    private int performHandwritingSelectGesture(int[] range, boolean isPreview) {
+        if (isPreview) {
+            setSelectGesturePreviewHighlight(range[0], range[1]);
+        } else {
+            Selection.setSelection(getEditableText(), range[0], range[1]);
+            mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
+        }
         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
     }
 
     /** @hide */
     public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) {
+        return performHandwritingSelectRangeGesture(gesture, /* isPreview= */ false);
+    }
+
+    private int performHandwritingSelectRangeGesture(
+            @NonNull SelectRangeGesture gesture, boolean isPreview) {
         int[] startRange = getRangeForRect(
                 convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()),
                 gesture.getGranularity());
         if (startRange == null) {
-            return handleGestureFailure(gesture);
+            return handleGestureFailure(gesture, isPreview);
         }
         int[] endRange = getRangeForRect(
                 convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()),
                 gesture.getGranularity());
         if (endRange == null) {
-            return handleGestureFailure(gesture);
+            return handleGestureFailure(gesture, isPreview);
         }
         int[] range = new int[] {
                 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
         };
-        Selection.setSelection(getEditableText(), range[0], range[1]);
-        mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
-        return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
+        return performHandwritingSelectGesture(range, isPreview);
     }
 
     /** @hide */
     public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) {
+        return performHandwritingDeleteGesture(gesture, /* isPreview= */ false);
+    }
+
+    private int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture, boolean isPreview) {
         int[] range = getRangeForRect(
                 convertFromScreenToContentCoordinates(gesture.getDeletionArea()),
                 gesture.getGranularity());
         if (range == null) {
-            return handleGestureFailure(gesture);
+            return handleGestureFailure(gesture, isPreview);
         }
+        return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview);
+    }
 
-        if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) {
-            range = adjustHandwritingDeleteGestureRange(range);
+    private int performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview) {
+        if (isPreview) {
+            setDeleteGesturePreviewHighlight(range[0], range[1]);
+        } else {
+            if (granularity == HandwritingGesture.GRANULARITY_WORD) {
+                range = adjustHandwritingDeleteGestureRange(range);
+            }
+
+            getEditableText().delete(range[0], range[1]);
+            Selection.setSelection(getEditableText(), range[0]);
         }
-
-        getEditableText().delete(range[0], range[1]);
-        Selection.setSelection(getEditableText(), range[0]);
         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
     }
 
     /** @hide */
     public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) {
+        return performHandwritingDeleteRangeGesture(gesture, /* isPreview= */ false);
+    }
+
+    private int performHandwritingDeleteRangeGesture(
+            @NonNull DeleteRangeGesture gesture, boolean isPreview) {
         int[] startRange = getRangeForRect(
                 convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()),
                 gesture.getGranularity());
         if (startRange == null) {
-            return handleGestureFailure(gesture);
+            return handleGestureFailure(gesture, isPreview);
         }
         int[] endRange = getRangeForRect(
                 convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()),
                 gesture.getGranularity());
         if (endRange == null) {
-            return handleGestureFailure(gesture);
+            return handleGestureFailure(gesture, isPreview);
         }
         int[] range = new int[] {
                 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
         };
-
-        if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) {
-            range = adjustHandwritingDeleteGestureRange(range);
-        }
-
-        getEditableText().delete(range[0], range[1]);
-        Selection.setSelection(getEditableText(), range[0]);
-        return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
+        return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview);
     }
 
     private int[] adjustHandwritingDeleteGestureRange(int[] range) {
@@ -9569,7 +9799,12 @@
     }
 
     private int handleGestureFailure(HandwritingGesture gesture) {
-        if (!TextUtils.isEmpty(gesture.getFallbackText())) {
+        return handleGestureFailure(gesture, /* isPreview= */ false);
+    }
+
+    private int handleGestureFailure(HandwritingGesture gesture, boolean isPreview) {
+        clearGesturePreviewHighlight();
+        if (!isPreview && !TextUtils.isEmpty(gesture.getFallbackText())) {
             getEditableText()
                     .replace(getSelectionStart(), getSelectionEnd(), gesture.getFallbackText());
             return InputConnection.HANDWRITING_GESTURE_RESULT_FALLBACK;
@@ -9742,6 +9977,7 @@
         mOldMaxMode = mMaxMode;
 
         mHighlightPathBogus = true;
+        mHighlightPathsBogus = true;
 
         if (wantWidth < 0) {
             wantWidth = 0;
@@ -11602,6 +11838,8 @@
         resetErrorChangedFlag();
         sendOnTextChanged(buffer, start, before, after);
         onTextChanged(buffer, start, before, after);
+
+        clearGesturePreviewHighlight();
     }
 
     /**
@@ -11640,6 +11878,7 @@
         }
 
         if (selChanged) {
+            clearGesturePreviewHighlight();
             mHighlightPathBogus = true;
             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
 
@@ -12953,18 +13192,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 +13221,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/BackEvent.aidl b/core/java/android/window/BackEvent.aidl
deleted file mode 100644
index 821f1fa..0000000
--- a/core/java/android/window/BackEvent.aidl
+++ /dev/null
@@ -1,22 +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.window;
-
-/**
- * @hide
- */
-parcelable BackEvent;
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index 85b2881..40c0fee 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -16,29 +16,24 @@
 
 package android.window;
 
+import android.annotation.FloatRange;
 import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.RemoteAnimationTarget;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Represents an event that is sent out by the system during back navigation gesture.
- * Holds information about the touch event, swipe direction and overall progress of the gesture
- * interaction.
- *
- * @hide
+ * Object used to report back gesture progress.
+ * Holds information about the touch event, swipe direction and the animation progress that
+ * predictive back animations should seek to.
  */
-public class BackEvent implements Parcelable {
+public final class BackEvent {
     /** Indicates that the edge swipe starts from the left edge of the screen */
     public static final int EDGE_LEFT = 0;
     /** Indicates that the edge swipe starts from the right edge of the screen */
     public static final int EDGE_RIGHT = 1;
 
+    /** @hide */
     @IntDef({
             EDGE_LEFT,
             EDGE_RIGHT,
@@ -52,78 +47,52 @@
 
     @SwipeEdge
     private final int mSwipeEdge;
-    @Nullable
-    private final RemoteAnimationTarget mDepartingAnimationTarget;
 
     /**
-     * Creates a new {@link BackEvent} instance.
+     * Creates a new {@link BackMotionEvent} instance.
      *
      * @param touchX Absolute X location of the touch point of this event.
      * @param touchY Absolute Y location of the touch point of this event.
      * @param progress Value between 0 and 1 on how far along the back gesture is.
      * @param swipeEdge Indicates which edge the swipe starts from.
-     * @param departingAnimationTarget The remote animation target of the departing
-     *                                 application window.
      */
-    public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge,
-            @Nullable RemoteAnimationTarget departingAnimationTarget) {
+    public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge) {
         mTouchX = touchX;
         mTouchY = touchY;
         mProgress = progress;
         mSwipeEdge = swipeEdge;
-        mDepartingAnimationTarget = departingAnimationTarget;
-    }
-
-    private BackEvent(@NonNull Parcel in) {
-        mTouchX = in.readFloat();
-        mTouchY = in.readFloat();
-        mProgress = in.readFloat();
-        mSwipeEdge = in.readInt();
-        mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
-    }
-
-    public static final Creator<BackEvent> CREATOR = new Creator<BackEvent>() {
-        @Override
-        public BackEvent createFromParcel(Parcel in) {
-            return new BackEvent(in);
-        }
-
-        @Override
-        public BackEvent[] newArray(int size) {
-            return new BackEvent[size];
-        }
-    };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeFloat(mTouchX);
-        dest.writeFloat(mTouchY);
-        dest.writeFloat(mProgress);
-        dest.writeInt(mSwipeEdge);
-        dest.writeTypedObject(mDepartingAnimationTarget, flags);
     }
 
     /**
-     * Returns a value between 0 and 1 on how far along the back gesture is.
+     * Returns a value between 0 and 1 on how far along the back gesture is. This value is
+     * driven by the horizontal location of the touch point, and should be used as the fraction to
+     * seek the predictive back animation with. Specifically,
+     * <ol>
+     * <li>The progress is 0 when the touch is at the starting edge of the screen (left or right),
+     * and animation should seek to its start state.
+     * <li>The progress is approximately 1 when the touch is at the opposite side of the screen,
+     * and animation should seek to its end state. Exact end value may vary depending on
+     * screen size.
+     * </ol>
+     * In-between locations are linearly interpolated based on horizontal distance from the starting
+     * edge and smooth clamped to 1 when the distance exceeds a system-wide threshold.
      */
+    @FloatRange(from = 0, to = 1)
     public float getProgress() {
         return mProgress;
     }
 
     /**
-     * Returns the absolute X location of the touch point.
+     * Returns the absolute X location of the touch point, or NaN if the event is from
+     * a button press.
      */
     public float getTouchX() {
         return mTouchX;
     }
 
     /**
-     * Returns the absolute Y location of the touch point.
+     * Returns the absolute Y location of the touch point, or NaN if the event is from
+     * a button press.
      */
     public float getTouchY() {
         return mTouchY;
@@ -132,20 +101,11 @@
     /**
      * Returns the screen edge that the swipe starts from.
      */
+    @SwipeEdge
     public int getSwipeEdge() {
         return mSwipeEdge;
     }
 
-    /**
-     * Returns the {@link RemoteAnimationTarget} of the top departing application window,
-     * or {@code null} if the top window should not be moved for the current type of back
-     * destination.
-     */
-    @Nullable
-    public RemoteAnimationTarget getDepartingAnimationTarget() {
-        return mDepartingAnimationTarget;
-    }
-
     @Override
     public String toString() {
         return "BackEvent{"
diff --git a/core/java/android/window/BackMotionEvent.aidl b/core/java/android/window/BackMotionEvent.aidl
new file mode 100644
index 0000000..7c675c3
--- /dev/null
+++ b/core/java/android/window/BackMotionEvent.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.window;
+
+/**
+ * @hide
+ */
+parcelable BackMotionEvent;
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
new file mode 100644
index 0000000..8012a1c
--- /dev/null
+++ b/core/java/android/window/BackMotionEvent.java
@@ -0,0 +1,150 @@
+/*
+ * 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.window;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.RemoteAnimationTarget;
+
+/**
+ * Object used to report back gesture progress. Holds information about a {@link BackEvent} plus
+ * any {@link RemoteAnimationTarget} the gesture manipulates.
+ *
+ * @see BackEvent
+ * @hide
+ */
+public final class BackMotionEvent implements Parcelable {
+    private final float mTouchX;
+    private final float mTouchY;
+    private final float mProgress;
+
+    @BackEvent.SwipeEdge
+    private final int mSwipeEdge;
+    @Nullable
+    private final RemoteAnimationTarget mDepartingAnimationTarget;
+
+    /**
+     * Creates a new {@link BackMotionEvent} instance.
+     *
+     * @param touchX Absolute X location of the touch point of this event.
+     * @param touchY Absolute Y location of the touch point of this event.
+     * @param progress Value between 0 and 1 on how far along the back gesture is.
+     * @param swipeEdge Indicates which edge the swipe starts from.
+     * @param departingAnimationTarget The remote animation target of the departing
+     *                                 application window.
+     */
+    public BackMotionEvent(float touchX, float touchY, float progress,
+            @BackEvent.SwipeEdge int swipeEdge,
+            @Nullable RemoteAnimationTarget departingAnimationTarget) {
+        mTouchX = touchX;
+        mTouchY = touchY;
+        mProgress = progress;
+        mSwipeEdge = swipeEdge;
+        mDepartingAnimationTarget = departingAnimationTarget;
+    }
+
+    private BackMotionEvent(@NonNull Parcel in) {
+        mTouchX = in.readFloat();
+        mTouchY = in.readFloat();
+        mProgress = in.readFloat();
+        mSwipeEdge = in.readInt();
+        mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
+    }
+
+    @NonNull
+    public static final Creator<BackMotionEvent> CREATOR = new Creator<BackMotionEvent>() {
+        @Override
+        public BackMotionEvent createFromParcel(Parcel in) {
+            return new BackMotionEvent(in);
+        }
+
+        @Override
+        public BackMotionEvent[] newArray(int size) {
+            return new BackMotionEvent[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeFloat(mTouchX);
+        dest.writeFloat(mTouchY);
+        dest.writeFloat(mProgress);
+        dest.writeInt(mSwipeEdge);
+        dest.writeTypedObject(mDepartingAnimationTarget, flags);
+    }
+
+    /**
+     * Returns the progress of a {@link BackEvent}.
+     *
+     * @see BackEvent#getProgress()
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getProgress() {
+        return mProgress;
+    }
+
+    /**
+     * Returns the absolute X location of the touch point.
+     */
+    public float getTouchX() {
+        return mTouchX;
+    }
+
+    /**
+     * Returns the absolute Y location of the touch point.
+     */
+    public float getTouchY() {
+        return mTouchY;
+    }
+
+    /**
+     * Returns the screen edge that the swipe starts from.
+     */
+    @BackEvent.SwipeEdge
+    public int getSwipeEdge() {
+        return mSwipeEdge;
+    }
+
+    /**
+     * Returns the {@link RemoteAnimationTarget} of the top departing application window,
+     * or {@code null} if the top window should not be moved for the current type of back
+     * destination.
+     */
+    @Nullable
+    public RemoteAnimationTarget getDepartingAnimationTarget() {
+        return mDepartingAnimationTarget;
+    }
+
+    @Override
+    public String toString() {
+        return "BackMotionEvent{"
+                + "mTouchX=" + mTouchX
+                + ", mTouchY=" + mTouchY
+                + ", mProgress=" + mProgress
+                + ", mSwipeEdge" + mSwipeEdge
+                + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+                + "}";
+    }
+}
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index 9b91cf2..d7bca30 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -29,6 +30,7 @@
  *
  * @hide
  */
+@TestApi
 public final class BackNavigationInfo implements Parcelable {
 
     /**
@@ -71,6 +73,7 @@
     /**
      * Defines the type of back destinations a back even can lead to. This is used to define the
      * type of animation that need to be run on SystemUI.
+     * @hide
      */
     @IntDef(prefix = "TYPE_", value = {
             TYPE_UNDEFINED,
@@ -89,8 +92,6 @@
     @Nullable
     private final IOnBackInvokedCallback mOnBackInvokedCallback;
     private final boolean mPrepareRemoteAnimation;
-    @Nullable
-    private WindowContainerToken mDepartingWindowContainerToken;
 
     /**
      * Create a new {@link BackNavigationInfo} instance.
@@ -99,21 +100,15 @@
      * @param onBackNavigationDone    The callback to be called once the client is done with the
      *                                back preview.
      * @param onBackInvokedCallback   The back callback registered by the current top level window.
-     * @param departingWindowContainerToken The {@link WindowContainerToken} of departing window.
-     * @param isPrepareRemoteAnimation  Return whether the core is preparing a back gesture
-     *                                  animation, if true, the caller of startBackNavigation should
-     *                                  be expected to receive an animation start callback.
      */
     private BackNavigationInfo(@BackTargetType int type,
             @Nullable RemoteCallback onBackNavigationDone,
             @Nullable IOnBackInvokedCallback onBackInvokedCallback,
-            boolean isPrepareRemoteAnimation,
-            @Nullable WindowContainerToken departingWindowContainerToken) {
+            boolean isPrepareRemoteAnimation) {
         mType = type;
         mOnBackNavigationDone = onBackNavigationDone;
         mOnBackInvokedCallback = onBackInvokedCallback;
         mPrepareRemoteAnimation = isPrepareRemoteAnimation;
-        mDepartingWindowContainerToken = departingWindowContainerToken;
     }
 
     private BackNavigationInfo(@NonNull Parcel in) {
@@ -121,21 +116,20 @@
         mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR);
         mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
         mPrepareRemoteAnimation = in.readBoolean();
-        mDepartingWindowContainerToken = in.readTypedObject(WindowContainerToken.CREATOR);
     }
 
+    /** @hide */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mType);
         dest.writeTypedObject(mOnBackNavigationDone, flags);
         dest.writeStrongInterface(mOnBackInvokedCallback);
         dest.writeBoolean(mPrepareRemoteAnimation);
-        dest.writeTypedObject(mDepartingWindowContainerToken, flags);
     }
 
     /**
      * Returns the type of back navigation that is about to happen.
-     *
+     * @hide
      * @see BackTargetType
      */
     public @BackTargetType int getType() {
@@ -147,7 +141,7 @@
      * the client didn't register a callback.
      * <p>
      * This is never null when {@link #getType} returns {@link #TYPE_CALLBACK}.
-     *
+     * @hide
      * @see OnBackInvokedCallback
      * @see OnBackInvokedDispatcher
      */
@@ -158,27 +152,16 @@
 
     /**
      * Return true if the core is preparing a back gesture nimation.
+     * @hide
      */
     public boolean isPrepareRemoteAnimation() {
         return mPrepareRemoteAnimation;
     }
 
     /**
-     * Returns the {@link WindowContainerToken} of the highest container in the hierarchy being
-     * removed.
-     * <p>
-     * For example, if an Activity is the last one of its Task, the Task's token will be given.
-     * Otherwise, it will be the Activity's token.
-     */
-    @Nullable
-    public WindowContainerToken getDepartingWindowContainerToken() {
-        return mDepartingWindowContainerToken;
-    }
-
-    /**
      * Callback to be called when the back preview is finished in order to notify the server that
      * it can clean up the resources created for the animation.
-     *
+     * @hide
      * @param triggerBack Boolean indicating if back navigation has been triggered.
      */
     public void onBackNavigationFinished(boolean triggerBack) {
@@ -189,11 +172,13 @@
         }
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
     }
 
+    @NonNull
     public static final Creator<BackNavigationInfo> CREATOR = new Creator<BackNavigationInfo>() {
         @Override
         public BackNavigationInfo createFromParcel(Parcel in) {
@@ -212,13 +197,13 @@
                 + "mType=" + typeToString(mType) + " (" + mType + ")"
                 + ", mOnBackNavigationDone=" + mOnBackNavigationDone
                 + ", mOnBackInvokedCallback=" + mOnBackInvokedCallback
-                + ", mWindowContainerToken=" + mDepartingWindowContainerToken
                 + '}';
     }
 
     /**
      * Translates the {@link BackNavigationInfo} integer type to its String representation
      */
+    @NonNull
     public static String typeToString(@BackTargetType int type) {
         switch (type) {
             case TYPE_UNDEFINED:
@@ -248,8 +233,6 @@
         @Nullable
         private IOnBackInvokedCallback mOnBackInvokedCallback = null;
         private boolean mPrepareRemoteAnimation;
-        @Nullable
-        private WindowContainerToken mDepartingWindowContainerToken = null;
 
         /**
          * @see BackNavigationInfo#getType()
@@ -285,20 +268,12 @@
         }
 
         /**
-         * @see BackNavigationInfo#getDepartingWindowContainerToken()
-         */
-        public void setDepartingWCT(@NonNull WindowContainerToken windowContainerToken) {
-            mDepartingWindowContainerToken = windowContainerToken;
-        }
-
-        /**
          * Builds and returns an instance of {@link BackNavigationInfo}
          */
         public BackNavigationInfo build() {
             return new BackNavigationInfo(mType, mOnBackNavigationDone,
                     mOnBackInvokedCallback,
-                    mPrepareRemoteAnimation,
-                    mDepartingWindowContainerToken);
+                    mPrepareRemoteAnimation);
         }
     }
 }
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index 2e3afde..14a57e0 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -40,7 +40,7 @@
     private final SpringAnimation mSpring;
     private ProgressCallback mCallback;
     private float mProgress = 0;
-    private BackEvent mLastBackEvent;
+    private BackMotionEvent mLastBackEvent;
     private boolean mStarted = false;
 
     private void setProgress(float progress) {
@@ -82,9 +82,9 @@
     /**
      * Sets a new target position for the back progress.
      *
-     * @param event the {@link BackEvent} containing the latest target progress.
+     * @param event the {@link BackMotionEvent} containing the latest target progress.
      */
-    public void onBackProgressed(BackEvent event) {
+    public void onBackProgressed(BackMotionEvent event) {
         if (!mStarted) {
             return;
         }
@@ -98,11 +98,11 @@
     /**
      * Starts the back progress animation.
      *
-     * @param event the {@link BackEvent} that started the gesture.
+     * @param event the {@link BackMotionEvent} that started the gesture.
      * @param callback the back callback to invoke for the gesture. It will receive back progress
      *                 dispatches as the progress animation updates.
      */
-    public void onBackStarted(BackEvent event, ProgressCallback callback) {
+    public void onBackStarted(BackMotionEvent event, ProgressCallback callback) {
         reset();
         mLastBackEvent = event;
         mCallback = callback;
@@ -132,8 +132,7 @@
         }
         mCallback.onProgressUpdate(
                 new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(),
-                        progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge(),
-                        mLastBackEvent.getDepartingAnimationTarget()));
+                        progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge()));
     }
 
 }
diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java
index f274d1a..0ce076b6 100644
--- a/core/java/android/window/ClientWindowFrames.java
+++ b/core/java/android/window/ClientWindowFrames.java
@@ -49,7 +49,7 @@
 
     public boolean isParentFrameClippedByDisplayCutout;
 
-    public float sizeCompatScale = 1f;
+    public float compatScale = 1f;
 
     public ClientWindowFrames() {
     }
@@ -62,7 +62,7 @@
             attachedFrame = new Rect(other.attachedFrame);
         }
         isParentFrameClippedByDisplayCutout = other.isParentFrameClippedByDisplayCutout;
-        sizeCompatScale = other.sizeCompatScale;
+        compatScale = other.compatScale;
     }
 
     private ClientWindowFrames(Parcel in) {
@@ -76,7 +76,7 @@
         parentFrame.readFromParcel(in);
         attachedFrame = in.readTypedObject(Rect.CREATOR);
         isParentFrameClippedByDisplayCutout = in.readBoolean();
-        sizeCompatScale = in.readFloat();
+        compatScale = in.readFloat();
     }
 
     @Override
@@ -86,7 +86,7 @@
         parentFrame.writeToParcel(dest, flags);
         dest.writeTypedObject(attachedFrame, flags);
         dest.writeBoolean(isParentFrameClippedByDisplayCutout);
-        dest.writeFloat(sizeCompatScale);
+        dest.writeFloat(compatScale);
     }
 
     @Override
@@ -97,7 +97,7 @@
                 + " parentFrame=" + parentFrame.toShortString(sb)
                 + (attachedFrame != null ? " attachedFrame=" + attachedFrame.toShortString() : "")
                 + (isParentFrameClippedByDisplayCutout ? " parentClippedByDisplayCutout" : "")
-                + (sizeCompatScale != 1f ? " sizeCompatScale=" + sizeCompatScale : "") +  "}";
+                + (compatScale != 1f ? " sizeCompatScale=" + compatScale : "") +  "}";
     }
 
     @Override
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
index a5aefd5..e027934 100644
--- a/core/java/android/window/DisplayWindowPolicyController.java
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -16,6 +16,8 @@
 
 package android.window;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
 import android.annotation.NonNull;
 import android.app.WindowConfiguration;
 import android.content.ComponentName;
@@ -126,9 +128,10 @@
             ActivityInfo activityInfo, int windowFlags, int systemWindowFlags);
 
     /**
-     * Returns {@code true} if the tasks which is on this virtual display can be showed on Recents.
+     * Returns {@code true} if the tasks which is on this virtual display can be showed in the
+     * host device of the recently launched activities list.
      */
-    public abstract boolean canShowTasksInRecents();
+    public abstract boolean canShowTasksInHostDeviceRecents();
 
     /**
      * This is called when the top activity of the display is changed.
@@ -142,6 +145,14 @@
      */
     public void onRunningAppsChanged(ArraySet<Integer> runningUids) {}
 
+    /**
+     * This is called when an Activity is entering PIP.
+     * Returns {@code true} if the Activity is allowed to enter PIP.
+     */
+    public boolean isEnteringPipAllowed(int uid) {
+        return isWindowingModeSupported(WINDOWING_MODE_PINNED);
+    }
+
     /** Dump debug data */
     public void dump(String prefix, final PrintWriter pw) {
         pw.println(prefix + "DisplayWindowPolicyController{" + super.toString() + "}");
diff --git a/core/java/android/window/IBackAnimationFinishedCallback.aidl b/core/java/android/window/IBackAnimationFinishedCallback.aidl
index 8afc003..f034339 100644
--- a/core/java/android/window/IBackAnimationFinishedCallback.aidl
+++ b/core/java/android/window/IBackAnimationFinishedCallback.aidl
@@ -22,6 +22,6 @@
  * @param trigger Whether the back gesture has passed the triggering threshold.
  * {@hide}
  */
-oneway interface IBackAnimationFinishedCallback {
+interface IBackAnimationFinishedCallback {
     void onAnimationFinished(in boolean triggerBack);
 }
\ No newline at end of file
diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl
index 6af8ddd..159c0e8 100644
--- a/core/java/android/window/IOnBackInvokedCallback.aidl
+++ b/core/java/android/window/IOnBackInvokedCallback.aidl
@@ -17,7 +17,7 @@
 
 package android.window;
 
-import android.window.BackEvent;
+import android.window.BackMotionEvent;
 
 /**
  * Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager
@@ -30,18 +30,19 @@
     * Called when a back gesture has been started, or back button has been pressed down.
     * Wraps {@link OnBackInvokedCallback#onBackStarted(BackEvent)}.
     *
-    * @param backEvent The {@link BackEvent} containing information about the touch or button press.
+    * @param backMotionEvent The {@link BackMotionEvent} containing information about the touch
+    *        or button press.
     */
-    void onBackStarted(in BackEvent backEvent);
+    void onBackStarted(in BackMotionEvent backMotionEvent);
 
     /**
      * Called on back gesture progress.
      * Wraps {@link OnBackInvokedCallback#onBackProgressed(BackEvent)}.
      *
-     * @param backEvent The {@link BackEvent} containing information about the latest touch point
-     *                  and the progress that the back animation should seek to.
+     * @param backMotionEvent The {@link BackMotionEvent} containing information about the latest
+     *                        touch point and the progress that the back animation should seek to.
      */
-    void onBackProgressed(in BackEvent backEvent);
+    void onBackProgressed(in BackMotionEvent backMotionEvent);
 
     /**
      * Called when a back gesture or back button press has been cancelled.
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 3250dd8..d25c8a8 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -39,13 +39,13 @@
      * animations if the transition only contains windows that belong to the organized
      * TaskFragments in the given Task.
      */
-    void registerRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId,
+    void registerRemoteAnimations(in ITaskFragmentOrganizer organizer,
         in RemoteAnimationDefinition definition);
 
     /**
      * Unregisters remote animations per transition type for the organizer.
      */
-    void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId);
+    void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
 
     /**
      * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
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/OnBackAnimationCallback.java b/core/java/android/window/OnBackAnimationCallback.java
index c05809b..9119e71 100644
--- a/core/java/android/window/OnBackAnimationCallback.java
+++ b/core/java/android/window/OnBackAnimationCallback.java
@@ -18,6 +18,8 @@
 import android.app.Activity;
 import android.app.Dialog;
 import android.view.View;
+import android.view.Window;
+
 /**
  * Interface for applications to register back animation callbacks along their custom back
  * handling.
@@ -25,24 +27,29 @@
  * This allows the client to customize various back behaviors by overriding the corresponding
  * callback methods.
  * <p>
- * Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, held
- * by classes that implement {@link OnBackInvokedDispatcherOwner} (such as {@link Activity},
- * {@link Dialog} and {@link View}).
+ * Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, which
+ * is held at window level and accessible through {@link Activity#getOnBackInvokedDispatcher()},
+ * {@link Dialog#getOnBackInvokedDispatcher()}, {@link Window#getOnBackInvokedDispatcher()}
+ * and {@link View#findOnBackInvokedDispatcher()}.
  * <p>
  * When back is triggered, callbacks on the in-focus window are invoked in reverse order in which
  * they are added within the same priority. Between different priorities, callbacks with higher
  * priority are invoked first.
  * <p>
  * @see OnBackInvokedCallback
- * @hide
  */
 public interface OnBackAnimationCallback extends OnBackInvokedCallback {
     /**
      * Called when a back gesture has been started, or back button has been pressed down.
+     *
+     * @param backEvent The {@link BackEvent} containing information about the touch or
+     *                  button press.
+     * @see BackEvent
      */
-    default void onBackStarted() { }
+    default void onBackStarted(@NonNull BackEvent backEvent) {}
+
     /**
-     * Called on back gesture progress.
+     * Called when a back gesture progresses.
      *
      * @param backEvent An {@link BackEvent} object describing the progress event.
      *
diff --git a/core/java/android/window/OnBackInvokedCallback.java b/core/java/android/window/OnBackInvokedCallback.java
index 62c41bf..6beaad3 100644
--- a/core/java/android/window/OnBackInvokedCallback.java
+++ b/core/java/android/window/OnBackInvokedCallback.java
@@ -16,9 +16,9 @@
 
 package android.window;
 
-import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.Dialog;
+import android.view.View;
 import android.view.Window;
 
 /**
@@ -26,7 +26,8 @@
  * <p>
  * Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, which
  * is held at window level and accessible through {@link Activity#getOnBackInvokedDispatcher()},
- * {@link Dialog#getOnBackInvokedDispatcher()} and {@link Window#getOnBackInvokedDispatcher()}.
+ * {@link Dialog#getOnBackInvokedDispatcher()}, {@link Window#getOnBackInvokedDispatcher()}
+ * and {@link View#findOnBackInvokedDispatcher()}.
  * <p>
  * When back is triggered, callbacks on the in-focus window are invoked in reverse order in which
  * they are added within the same priority. Between different priorities, callbacks with higher
@@ -35,6 +36,9 @@
  * This replaces {@link Activity#onBackPressed()}, {@link Dialog#onBackPressed()} and
  * {@link android.view.KeyEvent#KEYCODE_BACK}
  * <p>
+ * If you want to customize back animation behaviors, in addition to handling back invocations,
+ * register its subclass instances {@link OnBackAnimationCallback} instead.
+ * <p>
  * @see OnBackInvokedDispatcher#registerOnBackInvokedCallback(int, OnBackInvokedCallback)
  * registerOnBackInvokedCallback(priority, OnBackInvokedCallback)
  * to specify callback priority.
@@ -42,35 +46,8 @@
 @SuppressWarnings("deprecation")
 public interface OnBackInvokedCallback {
     /**
-     * Called when a back gesture has been started, or back button has been pressed down.
-     *
-     * @param backEvent The {@link BackEvent} containing information about the touch or
-     *                  button press.
-     *
-     * @hide
-     */
-    default void onBackStarted(@NonNull BackEvent backEvent) {}
-
-    /**
-     * Called when a back gesture has been progressed.
-     *
-     * @param backEvent The {@link BackEvent} containing information about the latest touch point
-     *                  and the progress that the back animation should seek to.
-     *
-     * @hide
-     */
-    default void onBackProgressed(@NonNull BackEvent backEvent) {}
-
-    /**
      * Called when a back gesture has been completed and committed, or back button pressed
      * has been released and committed.
      */
     void onBackInvoked();
-
-    /**
-     * Called when a back gesture or button press has been cancelled.
-     *
-     * @hide
-     */
-    default void onBackCancelled() {}
 }
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
new file mode 100644
index 0000000..1a58fd5
--- /dev/null
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -0,0 +1,508 @@
+/*
+ * 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.window;
+
+import static android.graphics.Color.WHITE;
+import static android.graphics.Color.alpha;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
+import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
+
+import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
+import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
+import static com.android.internal.policy.DecorView.getNavigationBarRect;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.GraphicBuffer;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.hardware.HardwareBuffer;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.DecorView;
+
+/**
+ * Utils class to help draw a snapshot on a surface.
+ * @hide
+ */
+public class SnapshotDrawerUtils {
+    private static final String TAG = "SnapshotDrawerUtils";
+
+    /**
+     * When creating the starting window, we use the exact same layout flags such that we end up
+     * with a window with the exact same dimensions etc. However, these flags are not used in layout
+     * and might cause other side effects so we exclude them.
+     */
+    static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE
+            | FLAG_NOT_TOUCHABLE
+            | FLAG_NOT_TOUCH_MODAL
+            | FLAG_ALT_FOCUSABLE_IM
+            | FLAG_NOT_FOCUSABLE
+            | FLAG_HARDWARE_ACCELERATED
+            | FLAG_IGNORE_CHEEK_PRESSES
+            | FLAG_LOCAL_FOCUS_MODE
+            | FLAG_SLIPPERY
+            | FLAG_WATCH_OUTSIDE_TOUCH
+            | FLAG_SPLIT_TOUCH
+            | FLAG_SCALED
+            | FLAG_SECURE;
+
+    private static final RectF sTmpSnapshotSize = new RectF();
+    private static final RectF sTmpDstFrame = new RectF();
+
+    private static final Matrix sSnapshotMatrix = new Matrix();
+    private static final float[] sTmpFloat9 = new float[9];
+    private static final Paint sBackgroundPaint = new Paint();
+
+    /**
+     * The internal object to hold the surface and drawing on it.
+     */
+    @VisibleForTesting
+    public static class SnapshotSurface {
+        private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+        private final SurfaceControl mRootSurface;
+        private final TaskSnapshot mSnapshot;
+        private final CharSequence mTitle;
+
+        private SystemBarBackgroundPainter mSystemBarBackgroundPainter;
+        private final Rect mTaskBounds;
+        private final Rect mFrame = new Rect();
+        private final Rect mSystemBarInsets = new Rect();
+        private boolean mSizeMismatch;
+
+        public SnapshotSurface(SurfaceControl rootSurface, TaskSnapshot snapshot,
+                CharSequence title,
+                Rect taskBounds) {
+            mRootSurface = rootSurface;
+            mSnapshot = snapshot;
+            mTitle = title;
+            mTaskBounds = taskBounds;
+        }
+
+        /**
+         * Initiate system bar painter to draw the system bar background.
+         */
+        void initiateSystemBarPainter(int windowFlags, int windowPrivateFlags,
+                int appearance, ActivityManager.TaskDescription taskDescription,
+                @WindowInsets.Type.InsetsType int requestedVisibleTypes) {
+            mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
+                    windowPrivateFlags, appearance, taskDescription, 1f, requestedVisibleTypes);
+            int backgroundColor = taskDescription.getBackgroundColor();
+            sBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
+        }
+
+        /**
+         * Set frame size.
+         */
+        void setFrames(Rect frame, Rect systemBarInsets) {
+            mFrame.set(frame);
+            mSystemBarInsets.set(systemBarInsets);
+            final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
+            mSizeMismatch = (mFrame.width() != snapshot.getWidth()
+                    || mFrame.height() != snapshot.getHeight());
+            mSystemBarBackgroundPainter.setInsets(systemBarInsets);
+        }
+
+        private void drawSnapshot(boolean releaseAfterDraw) {
+            Log.v(TAG, "Drawing snapshot surface sizeMismatch=" + mSizeMismatch);
+            if (mSizeMismatch) {
+                // The dimensions of the buffer and the window don't match, so attaching the buffer
+                // will fail. Better create a child window with the exact dimensions and fill the
+                // parent window with the background color!
+                drawSizeMismatchSnapshot();
+            } else {
+                drawSizeMatchSnapshot();
+            }
+
+            // In case window manager leaks us, make sure we don't retain the snapshot.
+            if (mSnapshot.getHardwareBuffer() != null) {
+                mSnapshot.getHardwareBuffer().close();
+            }
+            if (releaseAfterDraw) {
+                mRootSurface.release();
+            }
+        }
+
+        private void drawSizeMatchSnapshot() {
+            mTransaction.setBuffer(mRootSurface, mSnapshot.getHardwareBuffer())
+                    .setColorSpace(mRootSurface, mSnapshot.getColorSpace())
+                    .apply();
+        }
+
+        private void drawSizeMismatchSnapshot() {
+            final HardwareBuffer buffer = mSnapshot.getHardwareBuffer();
+            final SurfaceSession session = new SurfaceSession();
+
+            // We consider nearly matched dimensions as there can be rounding errors and the user
+            // won't notice very minute differences from scaling one dimension more than the other
+            final boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshot);
+
+            // Keep a reference to it such that it doesn't get destroyed when finalized.
+            SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session)
+                    .setName(mTitle + " - task-snapshot-surface")
+                    .setBLASTLayer()
+                    .setFormat(buffer.getFormat())
+                    .setParent(mRootSurface)
+                    .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot")
+                    .build();
+
+            final Rect frame;
+            // We can just show the surface here as it will still be hidden as the parent is
+            // still hidden.
+            mTransaction.show(childSurfaceControl);
+            if (aspectRatioMismatch) {
+                // Clip off ugly navigation bar.
+                final Rect crop = calculateSnapshotCrop();
+                frame = calculateSnapshotFrame(crop);
+                mTransaction.setWindowCrop(childSurfaceControl, crop);
+                mTransaction.setPosition(childSurfaceControl, frame.left, frame.top);
+                sTmpSnapshotSize.set(crop);
+                sTmpDstFrame.set(frame);
+            } else {
+                frame = null;
+                sTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight());
+                sTmpDstFrame.set(mFrame);
+                sTmpDstFrame.offsetTo(0, 0);
+            }
+
+            // Scale the mismatch dimensions to fill the task bounds
+            sSnapshotMatrix.setRectToRect(sTmpSnapshotSize, sTmpDstFrame, Matrix.ScaleToFit.FILL);
+            mTransaction.setMatrix(childSurfaceControl, sSnapshotMatrix, sTmpFloat9);
+            mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace());
+            mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer());
+
+            if (aspectRatioMismatch) {
+                GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(),
+                        PixelFormat.RGBA_8888,
+                        GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
+                                | GraphicBuffer.USAGE_SW_WRITE_RARELY);
+                // TODO: Support this on HardwareBuffer
+                final Canvas c = background.lockCanvas();
+                drawBackgroundAndBars(c, frame);
+                background.unlockCanvasAndPost(c);
+                mTransaction.setBuffer(mRootSurface,
+                        HardwareBuffer.createFromGraphicBuffer(background));
+            }
+            mTransaction.apply();
+            childSurfaceControl.release();
+        }
+
+        /**
+         * Calculates the snapshot crop in snapshot coordinate space.
+         *
+         * @return crop rect in snapshot coordinate space.
+         */
+        Rect calculateSnapshotCrop() {
+            final Rect rect = new Rect();
+            final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
+            rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight());
+            final Rect insets = mSnapshot.getContentInsets();
+
+            final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
+            final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
+
+            // Let's remove all system decorations except the status bar, but only if the task is at
+            // the very top of the screen.
+            final boolean isTop = mTaskBounds.top == 0 && mFrame.top == 0;
+            rect.inset((int) (insets.left * scaleX),
+                    isTop ? 0 : (int) (insets.top * scaleY),
+                    (int) (insets.right * scaleX),
+                    (int) (insets.bottom * scaleY));
+            return rect;
+        }
+
+        /**
+         * Calculates the snapshot frame in window coordinate space from crop.
+         *
+         * @param crop rect that is in snapshot coordinate space.
+         */
+        Rect calculateSnapshotFrame(Rect crop) {
+            final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
+            final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
+            final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
+
+            // Rescale the frame from snapshot to window coordinate space
+            final Rect frame = new Rect(0, 0,
+                    (int) (crop.width() / scaleX + 0.5f),
+                    (int) (crop.height() / scaleY + 0.5f)
+            );
+
+            // However, we also need to make space for the navigation bar on the left side.
+            frame.offset(mSystemBarInsets.left, 0);
+            return frame;
+        }
+
+        /**
+         * Draw status bar and navigation bar background.
+         */
+        void drawBackgroundAndBars(Canvas c, Rect frame) {
+            final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight();
+            final boolean fillHorizontally = c.getWidth() > frame.right;
+            final boolean fillVertically = c.getHeight() > frame.bottom;
+            if (fillHorizontally) {
+                c.drawRect(frame.right, alpha(mSystemBarBackgroundPainter.mStatusBarColor) == 0xFF
+                        ? statusBarHeight : 0, c.getWidth(), fillVertically
+                        ? frame.bottom : c.getHeight(), sBackgroundPaint);
+            }
+            if (fillVertically) {
+                c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), sBackgroundPaint);
+            }
+            mSystemBarBackgroundPainter.drawDecors(c, frame);
+        }
+
+        /**
+         * Ask system bar background painter to draw status bar background.
+         *
+         */
+        void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) {
+            mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame,
+                    mSystemBarBackgroundPainter.getStatusBarColorViewHeight());
+        }
+
+        /**
+         * Ask system bar background painter to draw navigation bar background.
+         *
+         */
+        void drawNavigationBarBackground(Canvas c) {
+            mSystemBarBackgroundPainter.drawNavigationBarBackground(c);
+        }
+    }
+
+    /**
+     * @return true if the aspect ratio match between a frame and a snapshot buffer.
+     */
+    public static boolean isAspectRatioMatch(Rect frame, TaskSnapshot snapshot) {
+        if (frame.isEmpty()) {
+            return false;
+        }
+        final HardwareBuffer buffer = snapshot.getHardwareBuffer();
+        return Math.abs(
+                ((float) buffer.getWidth() / buffer.getHeight())
+                        - ((float) frame.width() / frame.height())) <= 0.01f;
+    }
+
+    /**
+     * Help method to draw the snapshot on a surface.
+     */
+    public static void drawSnapshotOnSurface(StartingWindowInfo info, WindowManager.LayoutParams lp,
+            SurfaceControl rootSurface, TaskSnapshot snapshot,
+            Rect configBounds, Rect windowBounds, InsetsState topWindowInsetsState,
+            boolean releaseAfterDraw) {
+        if (windowBounds.isEmpty()) {
+            Log.e(TAG, "Unable to draw snapshot on an empty windowBounds");
+            return;
+        }
+        final SnapshotSurface drawSurface = new SnapshotSurface(
+                rootSurface, snapshot, lp.getTitle(), configBounds);
+
+        final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams;
+        final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
+        final ActivityManager.TaskDescription taskDescription;
+        if (runningTaskInfo.taskDescription != null) {
+            taskDescription = runningTaskInfo.taskDescription;
+        } else {
+            taskDescription = new ActivityManager.TaskDescription();
+            taskDescription.setBackgroundColor(WHITE);
+        }
+        drawSurface.initiateSystemBarPainter(lp.flags, lp.privateFlags,
+                attrs.insetsFlags.appearance, taskDescription, info.requestedVisibleTypes);
+        final Rect systemBarInsets = getSystemBarInsets(windowBounds, topWindowInsetsState);
+        drawSurface.setFrames(windowBounds, systemBarInsets);
+        drawSurface.drawSnapshot(releaseAfterDraw);
+    }
+
+    /**
+     * Help method to create a layout parameters for a window.
+     */
+    public static WindowManager.LayoutParams createLayoutParameters(StartingWindowInfo info,
+            CharSequence title, @WindowManager.LayoutParams.WindowType int windowType,
+            int pixelFormat, IBinder token) {
+        final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams;
+        final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams;
+        final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
+        if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) {
+            Log.w(TAG, "unable to create taskSnapshot surface ");
+            return null;
+        }
+        final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+
+        final int appearance = attrs.insetsFlags.appearance;
+        final int windowFlags = attrs.flags;
+        final int windowPrivateFlags = attrs.privateFlags;
+
+        layoutParams.packageName = mainWindowParams.packageName;
+        layoutParams.windowAnimations = mainWindowParams.windowAnimations;
+        layoutParams.dimAmount = mainWindowParams.dimAmount;
+        layoutParams.type = windowType;
+        layoutParams.format = pixelFormat;
+        layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
+                | FLAG_NOT_FOCUSABLE
+                | FLAG_NOT_TOUCHABLE;
+        // Setting as trusted overlay to let touches pass through. This is safe because this
+        // window is controlled by the system.
+        layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS)
+                | PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST;
+        layoutParams.token = token;
+        layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+        layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+        layoutParams.insetsFlags.appearance = appearance;
+        layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior;
+        layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode;
+        layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes());
+        layoutParams.setFitInsetsSides(attrs.getFitInsetsSides());
+        layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
+
+        layoutParams.setTitle(title);
+        return layoutParams;
+    }
+
+    static Rect getSystemBarInsets(Rect frame, InsetsState state) {
+        return state.calculateInsets(frame, WindowInsets.Type.systemBars(),
+                false /* ignoreVisibility */).toRect();
+    }
+
+    /**
+     * Helper class to draw the background of the system bars in regions the task snapshot isn't
+     * filling the window.
+     */
+    public static class SystemBarBackgroundPainter {
+        private final Paint mStatusBarPaint = new Paint();
+        private final Paint mNavigationBarPaint = new Paint();
+        private final int mStatusBarColor;
+        private final int mNavigationBarColor;
+        private final int mWindowFlags;
+        private final int mWindowPrivateFlags;
+        private final float mScale;
+        private final @WindowInsets.Type.InsetsType int mRequestedVisibleTypes;
+        private final Rect mSystemBarInsets = new Rect();
+
+        public SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
+                ActivityManager.TaskDescription taskDescription, float scale,
+                @WindowInsets.Type.InsetsType int requestedVisibleTypes) {
+            mWindowFlags = windowFlags;
+            mWindowPrivateFlags = windowPrivateFlags;
+            mScale = scale;
+            final Context context = ActivityThread.currentActivityThread().getSystemUiContext();
+            final int semiTransparent = context.getColor(
+                    R.color.system_bar_background_semi_transparent);
+            mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS,
+                    semiTransparent, taskDescription.getStatusBarColor(), appearance,
+                    APPEARANCE_LIGHT_STATUS_BARS,
+                    taskDescription.getEnsureStatusBarContrastWhenTransparent());
+            mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
+                    FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
+                    taskDescription.getNavigationBarColor(), appearance,
+                    APPEARANCE_LIGHT_NAVIGATION_BARS,
+                    taskDescription.getEnsureNavigationBarContrastWhenTransparent()
+                            && context.getResources().getBoolean(
+                            R.bool.config_navBarNeedsScrim));
+            mStatusBarPaint.setColor(mStatusBarColor);
+            mNavigationBarPaint.setColor(mNavigationBarColor);
+            mRequestedVisibleTypes = requestedVisibleTypes;
+        }
+
+        /**
+         * Set system bar insets.
+         */
+        public void setInsets(Rect systemBarInsets) {
+            mSystemBarInsets.set(systemBarInsets);
+        }
+
+        int getStatusBarColorViewHeight() {
+            final boolean forceBarBackground =
+                    (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
+            if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
+                    mRequestedVisibleTypes, mStatusBarColor, mWindowFlags,
+                    forceBarBackground)) {
+                return (int) (mSystemBarInsets.top * mScale);
+            } else {
+                return 0;
+            }
+        }
+
+        private boolean isNavigationBarColorViewVisible() {
+            final boolean forceBarBackground =
+                    (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
+            return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
+                    mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags,
+                    forceBarBackground);
+        }
+
+        /**
+         * Draw bar colors to a canvas.
+         */
+        public void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
+            drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight());
+            drawNavigationBarBackground(c);
+        }
+
+        void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame,
+                int statusBarHeight) {
+            if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0
+                    && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) {
+                final int rightInset = (int) (mSystemBarInsets.right * mScale);
+                final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0;
+                c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight,
+                        mStatusBarPaint);
+            }
+        }
+
+        void drawNavigationBarBackground(Canvas c) {
+            final Rect navigationBarRect = new Rect();
+            getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect,
+                    mScale);
+            final boolean visible = isNavigationBarColorViewVisible();
+            if (visible && Color.alpha(mNavigationBarColor) != 0
+                    && !navigationBarRect.isEmpty()) {
+                c.drawRect(navigationBarRect, mNavigationBarPaint);
+            }
+        }
+    }
+}
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 573db0d..384dacf 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -61,6 +61,12 @@
      */
     public boolean deferRemoveForIme;
 
+    /**
+     * The rounded corner radius
+     * @hide
+     */
+    public float roundedCornerRadius;
+
     public StartingWindowRemovalInfo() {
 
     }
@@ -80,6 +86,7 @@
         mainFrame = source.readTypedObject(Rect.CREATOR);
         playRevealAnimation = source.readBoolean();
         deferRemoveForIme = source.readBoolean();
+        roundedCornerRadius = source.readFloat();
     }
 
     @Override
@@ -89,6 +96,7 @@
         dest.writeTypedObject(mainFrame, flags);
         dest.writeBoolean(playRevealAnimation);
         dest.writeBoolean(deferRemoveForIme);
+        dest.writeFloat(roundedCornerRadius);
     }
 
     @Override
@@ -96,6 +104,7 @@
         return "StartingWindowRemovalInfo{taskId=" + taskId
                 + " frame=" + mainFrame
                 + " playRevealAnimation=" + playRevealAnimation
+                + " roundedCornerRadius=" + roundedCornerRadius
                 + " deferRemoveForIme=" + deferRemoveForIme + "}";
     }
 
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 4248096..3950739 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -57,12 +57,13 @@
  * option is provided.
  *
  * The following is what happens within the {@link SurfaceSyncGroup}
- * 1. Each SyncTarget will get a {@link SyncTarget#onReadyToSync} callback that contains a
- * {@link SyncBufferCallback}.
- * 2. Each {@link SyncTarget} needs to invoke {@link SyncBufferCallback#onBufferReady(Transaction)}.
- * This makes sure the SurfaceSyncGroup knows when the SyncTarget is complete, allowing the
- * SurfaceSyncGroup to get the Transaction that contains the buffer.
- * 3. When the final SyncBufferCallback finishes for the SurfaceSyncGroup, in most cases the
+ * 1. Each SyncTarget will get a {@link SyncTarget#onAddedToSyncGroup} callback that contains a
+ * {@link TransactionReadyCallback}.
+ * 2. Each {@link SyncTarget} needs to invoke
+ * {@link TransactionReadyCallback#onTransactionReady(Transaction)}. This makes sure the
+ * SurfaceSyncGroup knows when the SyncTarget is complete, allowing the SurfaceSyncGroup to get the
+ * Transaction that contains the buffer.
+ * 3. When the final TransactionReadyCallback finishes for the SurfaceSyncGroup, in most cases the
  * transaction is applied and then the sync complete callbacks are invoked, letting the callers know
  * the sync is now complete.
  *
@@ -86,8 +87,6 @@
     private final Transaction mTransaction = sTransactionFactory.get();
     @GuardedBy("mLock")
     private boolean mSyncReady;
-    @GuardedBy("mLock")
-    private final Set<SyncTarget> mSyncTargets = new ArraySet<>();
 
     @GuardedBy("mLock")
     private Consumer<Transaction> mSyncRequestCompleteCallback;
@@ -197,14 +196,13 @@
      * Add a {@link SyncTarget} to a sync set. The sync set will wait for all
      * SyncableSurfaces to complete before notifying.
      *
-     * @param syncTarget A SyncableSurface that implements how to handle syncing
-     *                   buffers.
+     * @param syncTarget A SyncTarget that implements how to handle syncing transactions.
      * @return true if the SyncTarget was successfully added to the SyncGroup, false otherwise.
      */
     public boolean addToSync(SyncTarget syncTarget) {
-        SyncBufferCallback syncBufferCallback = new SyncBufferCallback() {
+        TransactionReadyCallback transactionReadyCallback = new TransactionReadyCallback() {
             @Override
-            public void onBufferReady(Transaction t) {
+            public void onTransactionReady(Transaction t) {
                 synchronized (mLock) {
                     if (t != null) {
                         mTransaction.merge(t);
@@ -221,10 +219,9 @@
                         + "SyncTargets can be added.");
                 return false;
             }
-            mPendingSyncs.add(syncBufferCallback.hashCode());
-            mSyncTargets.add(syncTarget);
+            mPendingSyncs.add(transactionReadyCallback.hashCode());
         }
-        syncTarget.onReadyToSync(syncBufferCallback);
+        syncTarget.onAddedToSyncGroup(this, transactionReadyCallback);
         return true;
     }
 
@@ -256,17 +253,13 @@
             Log.d(TAG, "Successfully finished sync id=" + this);
         }
 
-        for (SyncTarget syncTarget : mSyncTargets) {
-            syncTarget.onSyncComplete();
-        }
-        mSyncTargets.clear();
         mSyncRequestCompleteCallback.accept(mTransaction);
         mFinished = true;
     }
 
     /**
      * Add a Transaction to this sync set. This allows the caller to provide other info that
-     * should be synced with the buffers.
+     * should be synced with the transactions.
      */
     public void addTransactionToSync(Transaction t) {
         synchronized (mLock) {
@@ -334,9 +327,10 @@
         }
 
         @Override
-        public void onReadyToSync(SyncBufferCallback syncBufferCallback) {
+        public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+                TransactionReadyCallback transactionReadyCallback) {
             mFrameCallbackConsumer.accept(
-                    () -> mSurfaceView.syncNextFrame(syncBufferCallback::onBufferReady));
+                    () -> mSurfaceView.syncNextFrame(transactionReadyCallback::onTransactionReady));
         }
     }
 
@@ -345,22 +339,19 @@
      */
     public interface SyncTarget {
         /**
-         * Called when the Syncable is ready to begin handing a sync request. When invoked, the
-         * implementor is required to call {@link SyncBufferCallback#onBufferReady(Transaction)}
-         * and {@link SyncBufferCallback#onBufferReady(Transaction)} in order for this Syncable
-         * to be marked as complete.
+         * Called when the SyncTarget has been added to a SyncGroup as is ready to begin handing a
+         * sync request. When invoked, the implementor is required to call
+         * {@link TransactionReadyCallback#onTransactionReady(Transaction)} in order for this
+         * SurfaceSyncGroup to fully complete.
          *
          * Always invoked on the thread that initiated the call to {@link #addToSync(SyncTarget)}
          *
-         * @param syncBufferCallback A SyncBufferCallback that the caller must invoke onBufferReady
+         * @param parentSyncGroup The sync group this target has been added to.
+         * @param transactionReadyCallback A TransactionReadyCallback that the caller must invoke
+         *                                 onTransactionReady
          */
-        void onReadyToSync(SyncBufferCallback syncBufferCallback);
-
-        /**
-         * There's no guarantee about the thread this callback is invoked on.
-         */
-        default void onSyncComplete() {
-        }
+        void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+                TransactionReadyCallback transactionReadyCallback);
     }
 
     /**
@@ -368,14 +359,14 @@
      * completed. The caller should invoke the calls when the rendering has started and finished a
      * frame.
      */
-    public interface SyncBufferCallback {
+    public interface TransactionReadyCallback {
         /**
-         * Invoked when the transaction contains the buffer and is ready to sync.
+         * Invoked when the transaction is ready to sync.
          *
-         * @param t The transaction that contains the buffer to be synced. This can be null if
-         *          there's nothing to sync
+         * @param t The transaction that contains the anything to be included in the synced. This
+         *          can be null if there's nothing to sync
          */
-        void onBufferReady(@Nullable Transaction t);
+        void onTransactionReady(@Nullable Transaction t);
     }
 
     /**
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
index e2c8a31..dc60edd 100644
--- a/core/java/android/window/TaskFragmentInfo.java
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -83,6 +83,12 @@
     private final boolean mIsTaskFragmentClearedForPip;
 
     /**
+     * Whether the last running activity of the TaskFragment was removed because it was reordered to
+     * front of the Task.
+     */
+    private final boolean mIsClearedForReorderActivityToFront;
+
+    /**
      * The maximum {@link ActivityInfo.WindowLayout#minWidth} and
      * {@link ActivityInfo.WindowLayout#minHeight} aggregated from the TaskFragment's child
      * activities.
@@ -96,7 +102,7 @@
             @NonNull Configuration configuration, int runningActivityCount,
             boolean isVisible, @NonNull List<IBinder> activities, @NonNull Point positionInParent,
             boolean isTaskClearedForReuse, boolean isTaskFragmentClearedForPip,
-            @NonNull Point minimumDimensions) {
+            boolean isClearedForReorderActivityToFront, @NonNull Point minimumDimensions) {
         mFragmentToken = requireNonNull(fragmentToken);
         mToken = requireNonNull(token);
         mConfiguration.setTo(configuration);
@@ -106,6 +112,7 @@
         mPositionInParent.set(positionInParent);
         mIsTaskClearedForReuse = isTaskClearedForReuse;
         mIsTaskFragmentClearedForPip = isTaskFragmentClearedForPip;
+        mIsClearedForReorderActivityToFront = isClearedForReorderActivityToFront;
         mMinimumDimensions.set(minimumDimensions);
     }
 
@@ -160,6 +167,11 @@
         return mIsTaskFragmentClearedForPip;
     }
 
+    /** @hide */
+    public boolean isClearedForReorderActivityToFront() {
+        return mIsClearedForReorderActivityToFront;
+    }
+
     @WindowingMode
     public int getWindowingMode() {
         return mConfiguration.windowConfiguration.getWindowingMode();
@@ -207,6 +219,7 @@
                 && mPositionInParent.equals(that.mPositionInParent)
                 && mIsTaskClearedForReuse == that.mIsTaskClearedForReuse
                 && mIsTaskFragmentClearedForPip == that.mIsTaskFragmentClearedForPip
+                && mIsClearedForReorderActivityToFront == that.mIsClearedForReorderActivityToFront
                 && mMinimumDimensions.equals(that.mMinimumDimensions);
     }
 
@@ -220,6 +233,7 @@
         mPositionInParent.readFromParcel(in);
         mIsTaskClearedForReuse = in.readBoolean();
         mIsTaskFragmentClearedForPip = in.readBoolean();
+        mIsClearedForReorderActivityToFront = in.readBoolean();
         mMinimumDimensions.readFromParcel(in);
     }
 
@@ -235,6 +249,7 @@
         mPositionInParent.writeToParcel(dest, flags);
         dest.writeBoolean(mIsTaskClearedForReuse);
         dest.writeBoolean(mIsTaskFragmentClearedForPip);
+        dest.writeBoolean(mIsClearedForReorderActivityToFront);
         mMinimumDimensions.writeToParcel(dest, flags);
     }
 
@@ -262,8 +277,9 @@
                 + " activities=" + mActivities
                 + " positionInParent=" + mPositionInParent
                 + " isTaskClearedForReuse=" + mIsTaskClearedForReuse
-                + " isTaskFragmentClearedForPip" + mIsTaskFragmentClearedForPip
-                + " minimumDimensions" + mMinimumDimensions
+                + " isTaskFragmentClearedForPip=" + mIsTaskFragmentClearedForPip
+                + " mIsClearedForReorderActivityToFront=" + mIsClearedForReorderActivityToFront
+                + " minimumDimensions=" + mMinimumDimensions
                 + "}";
     }
 
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 8df6541..ef19c55 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -122,16 +122,13 @@
     /**
      * Registers remote animations per transition type for the organizer. It will override the
      * animations if the transition only contains windows that belong to the organized
-     * TaskFragments in the given Task.
-     *
-     * @param taskId overrides if the transition only contains windows belonging to this Task.
+     * TaskFragments, and at least one of the transition window is embedded (not filling the Task).
      * @hide
      */
     @CallSuper
-    public void registerRemoteAnimations(int taskId,
-            @NonNull RemoteAnimationDefinition definition) {
+    public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) {
         try {
-            getController().registerRemoteAnimations(mInterface, taskId, definition);
+            getController().registerRemoteAnimations(mInterface, definition);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -142,9 +139,9 @@
      * @hide
      */
     @CallSuper
-    public void unregisterRemoteAnimations(int taskId) {
+    public void unregisterRemoteAnimations() {
         try {
-            getController().unregisterRemoteAnimations(mInterface, taskId);
+            getController().unregisterRemoteAnimations(mInterface);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index db15145..e62d5c9 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -296,7 +296,7 @@
                     out.append((i == 0 ? "" : ",") + TransitionInfo.modeToString(mModes[i]));
                 }
             }
-            out.append("]").toString();
+            out.append("]");
             out.append(" flags=" + TransitionInfo.flagsToString(mFlags));
             out.append(" mustBeTask=" + mMustBeTask);
             out.append(" order=" + containerOrderToString(mOrder));
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 8815ab3..a35e13e 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -138,8 +138,15 @@
     /** The container is a system window, excluding wallpaper and input-method. */
     public static final int FLAG_IS_SYSTEM_WINDOW = 1 << 16;
 
+    /** The window was animated by back gesture. */
+    public static final int FLAG_BACK_GESTURE_ANIMATED = 1 << 17;
+
     /** 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;
+    public static final int FLAG_FIRST_CUSTOM = 1 << 18;
+
+    /** 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 = {
@@ -161,6 +168,7 @@
             FLAG_IS_BEHIND_STARTING_WINDOW,
             FLAG_IS_OCCLUDED,
             FLAG_IS_SYSTEM_WINDOW,
+            FLAG_BACK_GESTURE_ANIMATED,
             FLAG_FIRST_CUSTOM
     })
     public @interface ChangeFlags {}
@@ -376,6 +384,9 @@
         if ((flags & FLAG_IS_SYSTEM_WINDOW) != 0) {
             sb.append(sb.length() == 0 ? "" : "|").append("FLAG_IS_SYSTEM_WINDOW");
         }
+        if ((flags & FLAG_BACK_GESTURE_ANIMATED) != 0) {
+            sb.append(sb.length() == 0 ? "" : "|").append("FLAG_BACK_GESTURE_ANIMATED");
+        }
         if ((flags & FLAG_FIRST_CUSTOM) != 0) {
             sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
         }
@@ -410,6 +421,53 @@
         return false;
     }
 
+    /**
+     * Releases temporary-for-animation surfaces referenced by this to potentially free up memory.
+     * This includes root-leash and snapshots.
+     */
+    public void releaseAnimSurfaces() {
+        for (int i = mChanges.size() - 1; i >= 0; --i) {
+            final Change c = mChanges.get(i);
+            if (c.mSnapshot != null) {
+                c.mSnapshot.release();
+                c.mSnapshot = null;
+            }
+        }
+        if (mRootLeash != null) {
+            mRootLeash.release();
+        }
+    }
+
+    /**
+     * Releases ALL the surfaces referenced by this to potentially free up memory. Do NOT use this
+     * if the surface-controls get stored and used elsewhere in the process. To just release
+     * temporary-for-animation surfaces, use {@link #releaseAnimSurfaces}.
+     */
+    public void releaseAllSurfaces() {
+        releaseAnimSurfaces();
+        for (int i = mChanges.size() - 1; i >= 0; --i) {
+            mChanges.get(i).getLeash().release();
+        }
+    }
+
+    /**
+     * Makes a copy of this as if it were parcel'd and unparcel'd. This implies that surfacecontrol
+     * refcounts are incremented which allows the "remote" receiver to release them without breaking
+     * the caller's references. Use this only if you need to "send" this to a local function which
+     * assumes it is being called from a remote caller.
+     */
+    public TransitionInfo localRemoteCopy() {
+        final TransitionInfo out = new TransitionInfo(mType, mFlags);
+        for (int i = 0; i < mChanges.size(); ++i) {
+            out.mChanges.add(mChanges.get(i).localRemoteCopy());
+        }
+        out.mRootLeash = mRootLeash != null ? new SurfaceControl(mRootLeash, "localRemote") : null;
+        // Doesn't have any native stuff, so no need for actual copy
+        out.mOptions = mOptions;
+        out.mRootOffset.set(mRootOffset);
+        return out;
+    }
+
     /** Represents the change a WindowContainer undergoes during a transition */
     public static final class Change implements Parcelable {
         private final WindowContainerToken mContainer;
@@ -462,6 +520,27 @@
             mSnapshotLuma = in.readFloat();
         }
 
+        private Change localRemoteCopy() {
+            final Change out = new Change(mContainer, new SurfaceControl(mLeash, "localRemote"));
+            out.mParent = mParent;
+            out.mLastParent = mLastParent;
+            out.mMode = mMode;
+            out.mFlags = mFlags;
+            out.mStartAbsBounds.set(mStartAbsBounds);
+            out.mEndAbsBounds.set(mEndAbsBounds);
+            out.mEndRelOffset.set(mEndRelOffset);
+            out.mTaskInfo = mTaskInfo;
+            out.mAllowEnterPip = mAllowEnterPip;
+            out.mStartRotation = mStartRotation;
+            out.mEndRotation = mEndRotation;
+            out.mEndFixedRotation = mEndFixedRotation;
+            out.mRotationAnimation = mRotationAnimation;
+            out.mBackgroundColor = mBackgroundColor;
+            out.mSnapshot = mSnapshot != null ? new SurfaceControl(mSnapshot, "localRemote") : null;
+            out.mSnapshotLuma = mSnapshotLuma;
+            return out;
+        }
+
         /** Sets the parent of this change's container. The parent must be a participant or null. */
         public void setParent(@Nullable WindowContainerToken parent) {
             mParent = parent;
@@ -579,11 +658,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/WindowContainerToken.java b/core/java/android/window/WindowContainerToken.java
index 22b90b2..b914cb0 100644
--- a/core/java/android/window/WindowContainerToken.java
+++ b/core/java/android/window/WindowContainerToken.java
@@ -48,7 +48,6 @@
     }
 
     @Override
-    /** @hide */
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeStrongBinder(mRealToken.asBinder());
     }
@@ -68,7 +67,6 @@
             };
 
     @Override
-    /** @hide */
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 2d29c59..5793674 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -454,6 +454,23 @@
     }
 
     /**
+     * Sets whether a container is being drag-resized.
+     * When {@code true}, the client will reuse a single (larger) surface size to avoid
+     * continuous allocations on every size change.
+     *
+     * @param container WindowContainerToken of the task that changed its drag resizing state
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction setDragResizing(@NonNull WindowContainerToken container,
+            boolean dragResizing) {
+        final Change change = getOrCreateChange(container.asBinder());
+        change.mChangeMask |= Change.CHANGE_DRAG_RESIZING;
+        change.mDragResizing = dragResizing;
+        return this;
+    }
+
+    /**
      * Sends a pending intent in sync.
      * @param sender The PendingIntent sender.
      * @param intent The fillIn intent to patch over the sender's base intent.
@@ -711,6 +728,29 @@
     }
 
     /**
+     * Sets the TaskFragment {@code container} to have a companion TaskFragment {@code companion}.
+     * This indicates that the organizer will remove the TaskFragment when the companion
+     * TaskFragment is removed.
+     *
+     * @param container the TaskFragment container
+     * @param companion the companion TaskFragment. If it is {@code null}, the transaction will
+     *                  reset the companion TaskFragment.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction setCompanionTaskFragment(@NonNull IBinder container,
+            @Nullable IBinder companion) {
+        final HierarchyOp hierarchyOp =
+                new HierarchyOp.Builder(
+                        HierarchyOp.HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT)
+                        .setContainer(container)
+                        .setReparentContainer(companion)
+                        .build();
+        mHierarchyOps.add(hierarchyOp);
+        return this;
+    }
+
+    /**
      * Sets/removes the always on top flag for this {@code windowContainer}. See
      * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
      * Please note that this method is only intended to be used for a
@@ -775,6 +815,38 @@
     }
 
     /**
+     * Clears container adjacent.
+     * @param root the root container to clear the adjacent roots for.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction clearAdjacentRoots(
+            @NonNull WindowContainerToken root) {
+        mHierarchyOps.add(HierarchyOp.createForClearAdjacentRoots(root.asBinder()));
+        return this;
+    }
+
+    /**
+     * Sets/removes the reparent leaf task flag for this {@code windowContainer}.
+     * When this is set, the server side will try to reparent the leaf task to task display area
+     * if there is an existing activity in history during the activity launch. This operation only
+     * support on the organized root task.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction setReparentLeafTaskIfRelaunch(
+            @NonNull WindowContainerToken windowContainer, boolean reparentLeafTaskIfRelaunch) {
+        final HierarchyOp hierarchyOp =
+                new HierarchyOp.Builder(
+                        HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH)
+                        .setContainer(windowContainer.asBinder())
+                        .setReparentLeafTaskIfRelaunch(reparentLeafTaskIfRelaunch)
+                        .build();
+        mHierarchyOps.add(hierarchyOp);
+        return this;
+    }
+
+    /**
      * Merges another WCT into this one.
      * @param transfer When true, this will transfer everything from other potentially leaving
      *                 other in an unusable state. When false, other is left alone, but
@@ -853,7 +925,6 @@
     }
 
     @Override
-    /** @hide */
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeMap(mChanges);
         dest.writeTypedList(mHierarchyOps);
@@ -862,7 +933,6 @@
     }
 
     @Override
-    /** @hide */
     public int describeContents() {
         return 0;
     }
@@ -894,12 +964,14 @@
         public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 5;
         public static final int CHANGE_FORCE_NO_PIP = 1 << 6;
         public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 7;
+        public static final int CHANGE_DRAG_RESIZING = 1 << 8;
 
         private final Configuration mConfiguration = new Configuration();
         private boolean mFocusable = true;
         private boolean mHidden = false;
         private boolean mIgnoreOrientationRequest = false;
         private boolean mForceTranslucent = false;
+        private boolean mDragResizing = false;
 
         private int mChangeMask = 0;
         private @ActivityInfo.Config int mConfigSetMask = 0;
@@ -920,6 +992,7 @@
             mHidden = in.readBoolean();
             mIgnoreOrientationRequest = in.readBoolean();
             mForceTranslucent = in.readBoolean();
+            mDragResizing = in.readBoolean();
             mChangeMask = in.readInt();
             mConfigSetMask = in.readInt();
             mWindowSetMask = in.readInt();
@@ -968,6 +1041,9 @@
             if ((other.mChangeMask & CHANGE_FORCE_TRANSLUCENT) != 0) {
                 mForceTranslucent = other.mForceTranslucent;
             }
+            if ((other.mChangeMask & CHANGE_DRAG_RESIZING) != 0) {
+                mDragResizing = other.mDragResizing;
+            }
             mChangeMask |= other.mChangeMask;
             if (other.mActivityWindowingMode >= 0) {
                 mActivityWindowingMode = other.mActivityWindowingMode;
@@ -1027,6 +1103,15 @@
             return mForceTranslucent;
         }
 
+        /** Gets the requested drag resizing state. */
+        public boolean getDragResizing() {
+            if ((mChangeMask & CHANGE_DRAG_RESIZING) == 0) {
+                throw new RuntimeException("Drag resizing not set. "
+                        + "Check CHANGE_DRAG_RESIZING first");
+            }
+            return mDragResizing;
+        }
+
         public int getChangeMask() {
             return mChangeMask;
         }
@@ -1088,6 +1173,9 @@
             if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
                 sb.append("focusable:" + mFocusable + ",");
             }
+            if ((mChangeMask & CHANGE_DRAG_RESIZING) != 0) {
+                sb.append("dragResizing:" + mDragResizing + ",");
+            }
             if (mBoundsChangeTransaction != null) {
                 sb.append("hasBoundsTransaction,");
             }
@@ -1105,6 +1193,7 @@
             dest.writeBoolean(mHidden);
             dest.writeBoolean(mIgnoreOrientationRequest);
             dest.writeBoolean(mForceTranslucent);
+            dest.writeBoolean(mDragResizing);
             dest.writeInt(mChangeMask);
             dest.writeInt(mConfigSetMask);
             dest.writeInt(mWindowSetMask);
@@ -1169,6 +1258,9 @@
         public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 19;
         public static final int HIERARCHY_OP_TYPE_REMOVE_TASK = 20;
         public static final int HIERARCHY_OP_TYPE_FINISH_ACTIVITY = 21;
+        public static final int HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT = 22;
+        public static final int HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS = 23;
+        public static final int HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH = 24;
 
         // The following key(s) are for use with mLaunchOptions:
         // When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1221,6 +1313,8 @@
 
         private boolean mAlwaysOnTop;
 
+        private boolean mReparentLeafTaskIfRelaunch;
+
         public static HierarchyOp createForReparent(
                 @NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
             return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
@@ -1305,6 +1399,13 @@
                     .build();
         }
 
+        /** Create a hierarchy op for clearing adjacent root tasks. */
+        public static HierarchyOp createForClearAdjacentRoots(@NonNull IBinder root) {
+            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS)
+                    .setContainer(root)
+                    .build();
+        }
+
         /** Only creates through {@link Builder}. */
         private HierarchyOp(int type) {
             mType = type;
@@ -1326,6 +1427,7 @@
             mPendingIntent = copy.mPendingIntent;
             mShortcutInfo = copy.mShortcutInfo;
             mAlwaysOnTop = copy.mAlwaysOnTop;
+            mReparentLeafTaskIfRelaunch = copy.mReparentLeafTaskIfRelaunch;
         }
 
         protected HierarchyOp(Parcel in) {
@@ -1348,6 +1450,7 @@
             mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
             mShortcutInfo = in.readTypedObject(ShortcutInfo.CREATOR);
             mAlwaysOnTop = in.readBoolean();
+            mReparentLeafTaskIfRelaunch = in.readBoolean();
         }
 
         public int getType() {
@@ -1383,6 +1486,11 @@
         }
 
         @NonNull
+        public IBinder getCompanionContainer() {
+            return mReparent;
+        }
+
+        @NonNull
         public IBinder getCallingActivity() {
             return mReparent;
         }
@@ -1417,6 +1525,10 @@
             return mAlwaysOnTop;
         }
 
+        public boolean isReparentLeafTaskIfRelaunch() {
+            return mReparentLeafTaskIfRelaunch;
+        }
+
         @Nullable
         public TaskFragmentCreationParams getTaskFragmentCreationOptions() {
             return mTaskFragmentCreationOptions;
@@ -1492,6 +1604,14 @@
                     return "{RemoveTask: task=" + mContainer + "}";
                 case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
                     return "{finishActivity: activity=" + mContainer + "}";
+                case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT:
+                    return "{setCompanionTaskFragment: container = " + mContainer + " companion = "
+                            + mReparent + "}";
+                case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS:
+                    return "{ClearAdjacentRoot: container=" + mContainer + "}";
+                case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
+                    return "{setReparentLeafTaskIfRelaunch: container= " + mContainer
+                            + " reparentLeafTaskIfRelaunch= " + mReparentLeafTaskIfRelaunch + "}";
                 default:
                     return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
                             + " mToTop=" + mToTop
@@ -1522,6 +1642,7 @@
             dest.writeTypedObject(mPendingIntent, flags);
             dest.writeTypedObject(mShortcutInfo, flags);
             dest.writeBoolean(mAlwaysOnTop);
+            dest.writeBoolean(mReparentLeafTaskIfRelaunch);
         }
 
         @Override
@@ -1582,6 +1703,8 @@
 
             private boolean mAlwaysOnTop;
 
+            private boolean mReparentLeafTaskIfRelaunch;
+
             Builder(int type) {
                 mType = type;
             }
@@ -1652,6 +1775,11 @@
                 return this;
             }
 
+            Builder setReparentLeafTaskIfRelaunch(boolean reparentLeafTaskIfRelaunch) {
+                mReparentLeafTaskIfRelaunch = reparentLeafTaskIfRelaunch;
+                return this;
+            }
+
             Builder setShortcutInfo(@Nullable ShortcutInfo shortcutInfo) {
                 mShortcutInfo = shortcutInfo;
                 return this;
@@ -1677,6 +1805,7 @@
                 hierarchyOp.mAlwaysOnTop = mAlwaysOnTop;
                 hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions;
                 hierarchyOp.mShortcutInfo = mShortcutInfo;
+                hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch;
 
                 return hierarchyOp;
             }
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index fda39c1..dd9483a 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -229,19 +229,21 @@
         }
 
         @Override
-        public void onBackStarted(BackEvent backEvent) {
+        public void onBackStarted(BackMotionEvent backEvent) {
             Handler.getMain().post(() -> {
                 final OnBackAnimationCallback callback = getBackAnimationCallback();
                 if (callback != null) {
                     mProgressAnimator.onBackStarted(backEvent, event ->
                             callback.onBackProgressed(event));
-                    callback.onBackStarted(backEvent);
+                    callback.onBackStarted(new BackEvent(
+                            backEvent.getTouchX(), backEvent.getTouchY(),
+                            backEvent.getProgress(), backEvent.getSwipeEdge()));
                 }
             });
         }
 
         @Override
-        public void onBackProgressed(BackEvent backEvent) {
+        public void onBackProgressed(BackMotionEvent backEvent) {
             Handler.getMain().post(() -> {
                 final OnBackAnimationCallback callback = getBackAnimationCallback();
                 if (callback != null) {
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/android/window/WindowProvider.java b/core/java/android/window/WindowProvider.java
index b078b93..dbdc68f 100644
--- a/core/java/android/window/WindowProvider.java
+++ b/core/java/android/window/WindowProvider.java
@@ -15,8 +15,10 @@
  */
 package android.window;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.view.WindowManager.LayoutParams.WindowType;
 
 /**
@@ -36,4 +38,11 @@
     /** Gets the launch options of this provider */
     @Nullable
     Bundle getWindowContextOptions();
+
+    /**
+     * Gets the WindowContextToken of this provider.
+     * @see android.content.Context#getWindowContextToken
+     */
+    @NonNull
+    IBinder getWindowContextToken();
 }
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index 2d2c8de..f2ae973 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -27,7 +27,10 @@
 import android.app.ActivityThread;
 import android.app.LoadedApk;
 import android.app.Service;
+import android.content.ComponentCallbacks;
+import android.content.ComponentCallbacksController;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -54,6 +57,8 @@
     private final WindowContextController mController = new WindowContextController(mWindowToken);
     private WindowManager mWindowManager;
     private boolean mInitialized;
+    private final ComponentCallbacksController mCallbacksController =
+            new ComponentCallbacksController();
 
     /**
      * Returns {@code true} if the {@code windowContextOptions} declares that it is a
@@ -118,6 +123,48 @@
         return mOptions;
     }
 
+    @SuppressLint({"OnNameExpected", "ExecutorRegistration"})
+    // Suppress lint because this is a legacy named function and doesn't have an optional param
+    // for executor.
+    // TODO(b/259347943): Update documentation for U.
+    /**
+     * Here we override to prevent WindowProviderService from invoking
+     * {@link Application.registerComponentCallback}, which will result in callback registered
+     * for process-level Configuration change updates.
+     */
+    @Override
+    public void registerComponentCallbacks(@NonNull ComponentCallbacks callback) {
+        // For broadcasting Configuration Changes.
+        mCallbacksController.registerCallbacks(callback);
+    }
+
+    @SuppressLint("OnNameExpected")
+    @Override
+    public void unregisterComponentCallbacks(@NonNull ComponentCallbacks callback) {
+        mCallbacksController.unregisterCallbacks(callback);
+    }
+
+    @SuppressLint("OnNameExpected")
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration configuration) {
+        // This is only called from WindowTokenClient.
+        mCallbacksController.dispatchConfigurationChanged(configuration);
+    }
+
+    /**
+     * Override {@link Service}'s empty implementation and listen to {@link ActivityThread} for
+     * low memory and trim memory events.
+     */
+    @Override
+    public void onLowMemory() {
+        mCallbacksController.dispatchLowMemory();
+    }
+
+    @Override
+    public void onTrimMemory(int level) {
+        mCallbacksController.dispatchTrimMemory(level);
+    }
+
     /**
      * Returns the display ID to launch this {@link WindowProviderService}.
      *
@@ -181,5 +228,6 @@
     public void onDestroy() {
         super.onDestroy();
         mController.detachIfNeeded();
+        mCallbacksController.clearCallbacks();
     }
 }
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index fc52620..2316738 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -17,18 +17,15 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.app.AppGlobals;
-import android.app.admin.DevicePolicyEventLogger;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.IPackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.AsyncTask;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.UserManager;
-import android.stats.devicepolicy.DevicePolicyEnums;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
@@ -60,73 +57,31 @@
     private final Context mContext;
     private int mCurrentPage;
     private OnProfileSelectedListener mOnProfileSelectedListener;
-    private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
     private Set<Integer> mLoadedPages;
-    private final UserHandle mPersonalProfileUserHandle;
+    private final EmptyStateProvider mEmptyStateProvider;
     private final UserHandle mWorkProfileUserHandle;
-    private Injector mInjector;
-    private boolean mIsWaitingToEnableWorkProfile;
+    private final QuietModeManager mQuietModeManager;
 
     AbstractMultiProfilePagerAdapter(Context context, int currentPage,
-            UserHandle personalProfileUserHandle,
+            EmptyStateProvider emptyStateProvider,
+            QuietModeManager quietModeManager,
             UserHandle workProfileUserHandle) {
         mContext = Objects.requireNonNull(context);
         mCurrentPage = currentPage;
         mLoadedPages = new HashSet<>();
-        mPersonalProfileUserHandle = personalProfileUserHandle;
         mWorkProfileUserHandle = workProfileUserHandle;
-        UserManager userManager = context.getSystemService(UserManager.class);
-        mInjector = new Injector() {
-            @Override
-            public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
-                    int targetUserId) {
-                return AbstractMultiProfilePagerAdapter.this
-                        .hasCrossProfileIntents(intents, sourceUserId, targetUserId);
-            }
-
-            @Override
-            public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
-                return userManager.isQuietModeEnabled(workProfileUserHandle);
-            }
-
-            @Override
-            public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) {
-                AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
-                    userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
-                });
-                mIsWaitingToEnableWorkProfile = true;
-            }
-        };
+        mEmptyStateProvider = emptyStateProvider;
+        mQuietModeManager = quietModeManager;
     }
 
-    protected void markWorkProfileEnabledBroadcastReceived() {
-        mIsWaitingToEnableWorkProfile = false;
-    }
-
-    protected boolean isWaitingToEnableWorkProfile() {
-        return mIsWaitingToEnableWorkProfile;
-    }
-
-    /**
-     * Overrides the default {@link Injector} for testing purposes.
-     */
-    @VisibleForTesting
-    public void setInjector(Injector injector) {
-        mInjector = injector;
-    }
-
-    protected boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
-        return mInjector.isQuietModeEnabled(workProfileUserHandle);
+    private boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
+        return mQuietModeManager.isQuietModeEnabled(workProfileUserHandle);
     }
 
     void setOnProfileSelectedListener(OnProfileSelectedListener listener) {
         mOnProfileSelectedListener = listener;
     }
 
-    void setOnSwitchOnWorkSelectedListener(OnSwitchOnWorkSelectedListener listener) {
-        mOnSwitchOnWorkSelectedListener = listener;
-    }
-
     Context getContext() {
         return mContext;
     }
@@ -280,8 +235,6 @@
 
     abstract @Nullable ViewGroup getInactiveAdapterView();
 
-    abstract String getMetricsCategory();
-
     /**
      * Rebuilds the tab that is currently visible to the user.
      * <p>Returns {@code true} if rebuild has completed.
@@ -317,41 +270,18 @@
     }
 
     private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) {
-        if (shouldShowNoCrossProfileIntentsEmptyState(activeListAdapter)) {
+        if (shouldSkipRebuild(activeListAdapter)) {
             activeListAdapter.postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ true);
             return false;
         }
         return activeListAdapter.rebuildList(doPostProcessing);
     }
 
-    private boolean shouldShowNoCrossProfileIntentsEmptyState(
-            ResolverListAdapter activeListAdapter) {
-        UserHandle listUserHandle = activeListAdapter.getUserHandle();
-        return UserHandle.myUserId() != listUserHandle.getIdentifier()
-                && allowShowNoCrossProfileIntentsEmptyState()
-                && !mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(),
-                        UserHandle.myUserId(), listUserHandle.getIdentifier());
+    private boolean shouldSkipRebuild(ResolverListAdapter activeListAdapter) {
+        EmptyState emptyState = mEmptyStateProvider.getEmptyState(activeListAdapter);
+        return emptyState != null && emptyState.shouldSkipDataRebuild();
     }
 
-    boolean allowShowNoCrossProfileIntentsEmptyState() {
-        return true;
-    }
-
-    protected abstract void showWorkProfileOffEmptyState(
-            ResolverListAdapter activeListAdapter, View.OnClickListener listener);
-
-    protected abstract void showNoPersonalToWorkIntentsEmptyState(
-            ResolverListAdapter activeListAdapter);
-
-    protected abstract void showNoPersonalAppsAvailableEmptyState(
-            ResolverListAdapter activeListAdapter);
-
-    protected abstract void showNoWorkAppsAvailableEmptyState(
-            ResolverListAdapter activeListAdapter);
-
-    protected abstract void showNoWorkToPersonalIntentsEmptyState(
-            ResolverListAdapter activeListAdapter);
-
     /**
      * The empty state screens are shown according to their priority:
      * <ol>
@@ -366,103 +296,88 @@
      * anyway.
      */
     void showEmptyResolverListEmptyState(ResolverListAdapter listAdapter) {
-        if (maybeShowNoCrossProfileIntentsEmptyState(listAdapter)) {
-            return;
-        }
-        if (maybeShowWorkProfileOffEmptyState(listAdapter)) {
-            return;
-        }
-        maybeShowNoAppsAvailableEmptyState(listAdapter);
-    }
+        final EmptyState emptyState = mEmptyStateProvider.getEmptyState(listAdapter);
 
-    private boolean maybeShowNoCrossProfileIntentsEmptyState(ResolverListAdapter listAdapter) {
-        if (!shouldShowNoCrossProfileIntentsEmptyState(listAdapter)) {
-            return false;
+        if (emptyState == null) {
+            return;
         }
-        if (listAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) {
-            DevicePolicyEventLogger.createEvent(
-                    DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL)
-                    .setStrings(getMetricsCategory())
-                    .write();
-            showNoWorkToPersonalIntentsEmptyState(listAdapter);
-        } else {
-            DevicePolicyEventLogger.createEvent(
-                    DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK)
-                    .setStrings(getMetricsCategory())
-                    .write();
-            showNoPersonalToWorkIntentsEmptyState(listAdapter);
+
+        emptyState.onEmptyStateShown();
+
+        View.OnClickListener clickListener = null;
+
+        if (emptyState.getButtonClickListener() != null) {
+            clickListener = v -> emptyState.getButtonClickListener().onClick(() -> {
+                ProfileDescriptor descriptor = getItem(
+                        userHandleToPageIndex(listAdapter.getUserHandle()));
+                AbstractMultiProfilePagerAdapter.this.showSpinner(descriptor.getEmptyStateView());
+            });
         }
-        return true;
+
+        showEmptyState(listAdapter, emptyState, clickListener);
     }
 
     /**
-     * Returns {@code true} if the work profile off empty state screen is shown.
+     * Class to get user id of the current process
      */
-    private boolean maybeShowWorkProfileOffEmptyState(ResolverListAdapter listAdapter) {
-        UserHandle listUserHandle = listAdapter.getUserHandle();
-        if (!listUserHandle.equals(mWorkProfileUserHandle)
-                || !mInjector.isQuietModeEnabled(mWorkProfileUserHandle)
-                || listAdapter.getCount() == 0) {
-            return false;
-        }
-        DevicePolicyEventLogger
-                .createEvent(DevicePolicyEnums.RESOLVER_EMPTY_STATE_WORK_APPS_DISABLED)
-                .setStrings(getMetricsCategory())
-                .write();
-        showWorkProfileOffEmptyState(listAdapter,
-                v -> {
-                    ProfileDescriptor descriptor = getItem(
-                            userHandleToPageIndex(listAdapter.getUserHandle()));
-                    showSpinner(descriptor.getEmptyStateView());
-                    if (mOnSwitchOnWorkSelectedListener != null) {
-                        mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
-                    }
-                    mInjector.requestQuietModeEnabled(false, mWorkProfileUserHandle);
-                });
-        return true;
-    }
-
-    private void maybeShowNoAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
-        UserHandle listUserHandle = listAdapter.getUserHandle();
-        if (mWorkProfileUserHandle != null
-                && (UserHandle.myUserId() == listUserHandle.getIdentifier()
-                        || !hasAppsInOtherProfile(listAdapter))) {
-            DevicePolicyEventLogger.createEvent(
-                    DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_APPS_RESOLVED)
-                    .setStrings(getMetricsCategory())
-                    .setBoolean(/*isPersonalProfile*/ listUserHandle == mPersonalProfileUserHandle)
-                    .write();
-            if (listUserHandle == mPersonalProfileUserHandle) {
-                showNoPersonalAppsAvailableEmptyState(listAdapter);
-            } else {
-                showNoWorkAppsAvailableEmptyState(listAdapter);
-            }
-        } else if (mWorkProfileUserHandle == null) {
-            showConsumerUserNoAppsAvailableEmptyState(listAdapter);
+    public static class MyUserIdProvider {
+        /**
+         * @return user id of the current process
+         */
+        public int getMyUserId() {
+            return UserHandle.myUserId();
         }
     }
 
-    protected void showEmptyState(ResolverListAdapter activeListAdapter, String title,
-            String subtitle) {
-        showEmptyState(activeListAdapter, title, subtitle, /* buttonOnClick */ null);
+    /**
+     * Utility class to check if there are cross profile intents, it is in a separate class so
+     * it could be mocked in tests
+     */
+    public static class CrossProfileIntentsChecker {
+
+        private final ContentResolver mContentResolver;
+
+        public CrossProfileIntentsChecker(@NonNull ContentResolver contentResolver) {
+            mContentResolver = contentResolver;
+        }
+
+        /**
+         * Returns {@code true} if at least one of the provided {@code intents} can be forwarded
+         * from {@code source} (user id) to {@code target} (user id).
+         */
+        public boolean hasCrossProfileIntents(List<Intent> intents, @UserIdInt int source,
+                @UserIdInt int target) {
+            IPackageManager packageManager = AppGlobals.getPackageManager();
+
+            return intents.stream().anyMatch(intent ->
+                    null != IntentForwarderActivity.canForward(intent, source, target,
+                            packageManager, mContentResolver));
+        }
     }
 
-    protected void showEmptyState(ResolverListAdapter activeListAdapter,
-            String title, String subtitle, View.OnClickListener buttonOnClick) {
+    protected void showEmptyState(ResolverListAdapter activeListAdapter, EmptyState emptyState,
+            View.OnClickListener buttonOnClick) {
         ProfileDescriptor descriptor = getItem(
                 userHandleToPageIndex(activeListAdapter.getUserHandle()));
         descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
         ViewGroup emptyStateView = descriptor.getEmptyStateView();
-        resetViewVisibilitiesForWorkProfileEmptyState(emptyStateView);
+        resetViewVisibilitiesForEmptyState(emptyStateView);
         emptyStateView.setVisibility(View.VISIBLE);
 
         View container = emptyStateView.findViewById(R.id.resolver_empty_state_container);
         setupContainerPadding(container);
 
         TextView titleView = emptyStateView.findViewById(R.id.resolver_empty_state_title);
-        titleView.setText(title);
+        String title = emptyState.getTitle();
+        if (title != null) {
+            titleView.setVisibility(View.VISIBLE);
+            titleView.setText(title);
+        } else {
+            titleView.setVisibility(View.GONE);
+        }
 
         TextView subtitleView = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle);
+        String subtitle = emptyState.getSubtitle();
         if (subtitle != null) {
             subtitleView.setVisibility(View.VISIBLE);
             subtitleView.setText(subtitle);
@@ -470,6 +385,9 @@
             subtitleView.setVisibility(View.GONE);
         }
 
+        View defaultEmptyText = emptyStateView.findViewById(R.id.empty);
+        defaultEmptyText.setVisibility(emptyState.useDefaultEmptyView() ? View.VISIBLE : View.GONE);
+
         Button button = emptyStateView.findViewById(R.id.resolver_empty_state_button);
         button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE);
         button.setOnClickListener(buttonOnClick);
@@ -483,22 +401,6 @@
      */
     protected void setupContainerPadding(View container) {}
 
-    private void showConsumerUserNoAppsAvailableEmptyState(ResolverListAdapter activeListAdapter) {
-        ProfileDescriptor descriptor = getItem(
-                userHandleToPageIndex(activeListAdapter.getUserHandle()));
-        descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
-        View emptyStateView = descriptor.getEmptyStateView();
-        resetViewVisibilitiesForConsumerUserEmptyState(emptyStateView);
-        emptyStateView.setVisibility(View.VISIBLE);
-
-        activeListAdapter.markTabLoaded();
-    }
-
-    private boolean isSpinnerShowing(View emptyStateView) {
-        return emptyStateView.findViewById(R.id.resolver_empty_state_progress).getVisibility()
-                == View.VISIBLE;
-    }
-
     private void showSpinner(View emptyStateView) {
         emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.INVISIBLE);
         emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
@@ -506,7 +408,7 @@
         emptyStateView.findViewById(R.id.empty).setVisibility(View.GONE);
     }
 
-    private void resetViewVisibilitiesForWorkProfileEmptyState(View emptyStateView) {
+    private void resetViewVisibilitiesForEmptyState(View emptyStateView) {
         emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.VISIBLE);
         emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.VISIBLE);
         emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
@@ -514,14 +416,6 @@
         emptyStateView.findViewById(R.id.empty).setVisibility(View.GONE);
     }
 
-    private void resetViewVisibilitiesForConsumerUserEmptyState(View emptyStateView) {
-        emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.GONE);
-        emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.GONE);
-        emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.GONE);
-        emptyStateView.findViewById(R.id.resolver_empty_state_progress).setVisibility(View.GONE);
-        emptyStateView.findViewById(R.id.empty).setVisibility(View.VISIBLE);
-    }
-
     protected void showListView(ResolverListAdapter activeListAdapter) {
         ProfileDescriptor descriptor = getItem(
                 userHandleToPageIndex(activeListAdapter.getUserHandle()));
@@ -530,33 +424,6 @@
         emptyStateView.setVisibility(View.GONE);
     }
 
-    private boolean hasCrossProfileIntents(List<Intent> intents, int source, int target) {
-        IPackageManager packageManager = AppGlobals.getPackageManager();
-        ContentResolver contentResolver = mContext.getContentResolver();
-        for (Intent intent : intents) {
-            if (IntentForwarderActivity.canForward(intent, source, target, packageManager,
-                    contentResolver) != null) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean hasAppsInOtherProfile(ResolverListAdapter adapter) {
-        if (mWorkProfileUserHandle == null) {
-            return false;
-        }
-        List<ResolverActivity.ResolvedComponentInfo> resolversForIntent =
-                adapter.getResolversForUser(UserHandle.of(UserHandle.myUserId()));
-        for (ResolverActivity.ResolvedComponentInfo info : resolversForIntent) {
-            ResolveInfo resolveInfo = info.getResolveInfoAt(0);
-            if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     boolean shouldShowEmptyStateScreen(ResolverListAdapter listAdapter) {
         int count = listAdapter.getUnfilteredCount();
         return (count == 0 && listAdapter.getPlaceholderCount() == 0)
@@ -600,6 +467,98 @@
     }
 
     /**
+     * Returns an empty state to show for the current profile page (tab) if necessary.
+     * This could be used e.g. to show a blocker on a tab if device management policy doesn't
+     * allow to use it or there are no apps available.
+     */
+    public interface EmptyStateProvider {
+        /**
+         * When a non-null empty state is returned the corresponding profile page will show
+         * this empty state
+         * @param resolverListAdapter the current adapter
+         */
+        @Nullable
+        default EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+            return null;
+        }
+    }
+
+    /**
+     * Empty state provider that combines multiple providers. Providers earlier in the list have
+     * priority, that is if there is a provider that returns non-null empty state then all further
+     * providers will be ignored.
+     */
+    public static class CompositeEmptyStateProvider implements EmptyStateProvider {
+
+        private final EmptyStateProvider[] mProviders;
+
+        public CompositeEmptyStateProvider(EmptyStateProvider... providers) {
+            mProviders = providers;
+        }
+
+        @Nullable
+        @Override
+        public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+            for (EmptyStateProvider provider : mProviders) {
+                EmptyState emptyState = provider.getEmptyState(resolverListAdapter);
+                if (emptyState != null) {
+                    return emptyState;
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Describes how the blocked empty state should look like for a profile tab
+     */
+    public interface EmptyState {
+        /**
+         * Title that will be shown on the empty state
+         */
+        @Nullable
+        default String getTitle() { return null; }
+
+        /**
+         * Subtitle that will be shown underneath the title on the empty state
+         */
+        @Nullable
+        default String getSubtitle()  { return null; }
+
+        /**
+         * If non-null then a button will be shown and this listener will be called
+         * when the button is clicked
+         */
+        @Nullable
+        default ClickListener getButtonClickListener()  { return null; }
+
+        /**
+         * If true then default text ('No apps can perform this action') and style for the empty
+         * state will be applied, title and subtitle will be ignored.
+         */
+        default boolean useDefaultEmptyView() { return false; }
+
+        /**
+         * Returns true if for this empty state we should skip rebuilding of the apps list
+         * for this tab.
+         */
+        default boolean shouldSkipDataRebuild() { return false; }
+
+        /**
+         * Called when empty state is shown, could be used e.g. to track analytics events
+         */
+        default void onEmptyStateShown() {}
+
+        interface ClickListener {
+            void onClick(TabControl currentTab);
+        }
+
+        interface TabControl {
+            void showSpinner();
+        }
+    }
+
+    /**
      * Listener for when the user switches on the work profile from the work tab.
      */
     interface OnSwitchOnWorkSelectedListener {
@@ -612,14 +571,7 @@
     /**
      * Describes an injector to be used for cross profile functionality. Overridable for testing.
      */
-    @VisibleForTesting
-    public interface Injector {
-        /**
-         * Returns {@code true} if at least one of the provided {@code intents} can be forwarded
-         * from {@code sourceUserId} to {@code targetUserId}.
-         */
-        boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId, int targetUserId);
-
+    public interface QuietModeManager {
         /**
          * Returns whether the given profile is in quiet mode or not.
          */
@@ -629,5 +581,15 @@
          * Enables or disables quiet mode for a managed profile.
          */
         void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle);
+
+        /**
+         * Should be called when the work profile enabled broadcast received
+         */
+        void markWorkProfileEnabledBroadcastReceived();
+
+        /**
+         * Returns true if enabling of work profile is in progress
+         */
+        boolean isWaitingToEnableWorkProfile();
     }
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index c8eec7d..02cbd41 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -16,6 +16,14 @@
 
 package com.android.internal.app;
 
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
+import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
+import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
+
 import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -114,6 +122,9 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
+import com.android.internal.app.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
 import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
 import com.android.internal.app.ResolverListAdapter.ViewHolder;
 import com.android.internal.app.chooser.ChooserTargetInfo;
@@ -830,6 +841,41 @@
         return mChooserMultiProfilePagerAdapter;
     }
 
+    @Override
+    protected EmptyStateProvider createBlockerEmptyStateProvider() {
+        final boolean isSendAction = isSendAction(getTargetIntent());
+
+        final EmptyState noWorkToPersonalEmptyState =
+                new DevicePolicyBlockerEmptyState(
+                /* context= */ this,
+                /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+                /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+                /* devicePolicyStringSubtitleId= */
+                isSendAction ? RESOLVER_CANT_SHARE_WITH_PERSONAL : RESOLVER_CANT_ACCESS_PERSONAL,
+                /* defaultSubtitleResource= */
+                isSendAction ? R.string.resolver_cant_share_with_personal_apps_explanation
+                        : R.string.resolver_cant_access_personal_apps_explanation,
+                /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
+                /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
+
+        final EmptyState noPersonalToWorkEmptyState =
+                new DevicePolicyBlockerEmptyState(
+                /* context= */ this,
+                /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+                /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+                /* devicePolicyStringSubtitleId= */
+                isSendAction ? RESOLVER_CANT_SHARE_WITH_WORK : RESOLVER_CANT_ACCESS_WORK,
+                /* defaultSubtitleResource= */
+                isSendAction ? R.string.resolver_cant_share_with_work_apps_explanation
+                        : R.string.resolver_cant_access_work_apps_explanation,
+                /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
+                /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
+
+        return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
+                noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
+                createCrossProfileIntentsChecker(), createMyUserIdProvider());
+    }
+
     private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
             Intent[] initialIntents,
             List<ResolveInfo> rList,
@@ -844,9 +890,10 @@
         return new ChooserMultiProfilePagerAdapter(
                 /* context */ this,
                 adapter,
-                getPersonalProfileUserHandle(),
+                createEmptyStateProvider(/* workProfileUserHandle= */ null),
+                mQuietModeManager,
                 /* workProfileUserHandle= */ null,
-                isSendAction(getTargetIntent()), mMaxTargetsPerRow);
+                mMaxTargetsPerRow);
     }
 
     private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
@@ -872,10 +919,11 @@
                 /* context */ this,
                 personalAdapter,
                 workAdapter,
+                createEmptyStateProvider(/* workProfileUserHandle= */ getWorkProfileUserHandle()),
+                mQuietModeManager,
                 selectedProfile,
-                getPersonalProfileUserHandle(),
                 getWorkProfileUserHandle(),
-                isSendAction(getTargetIntent()), mMaxTargetsPerRow);
+                mMaxTargetsPerRow);
     }
 
     private int findSelectedProfile() {
@@ -2096,7 +2144,8 @@
         return matchingShortcuts;
     }
 
-    private void sendShortcutManagerShareTargetResults(
+    @VisibleForTesting
+    protected void sendShortcutManagerShareTargetResults(
             int shortcutType, ServiceResultInfo[] results) {
         final Message msg = Message.obtain();
         msg.what = ChooserHandler.SHORTCUT_MANAGER_ALL_SHARE_TARGET_RESULTS;
@@ -3873,7 +3922,11 @@
         }
     }
 
-    static class ServiceResultInfo {
+    /**
+     * Shortcuts grouped by application.
+     */
+    @VisibleForTesting
+    public static class ServiceResultInfo {
         public final DisplayResolveInfo originalTarget;
         public final List<ChooserTarget> resultTargets;
         public final UserHandle userHandle;
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index df1130b..0509b67 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -16,17 +16,7 @@
 
 package com.android.internal.app;
 
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
-
 import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.os.UserHandle;
 import android.view.LayoutInflater;
@@ -47,37 +37,37 @@
     private static final int SINGLE_CELL_SPAN_SIZE = 1;
 
     private final ChooserProfileDescriptor[] mItems;
-    private final boolean mIsSendAction;
     private int mBottomOffset;
     private int mMaxTargetsPerRow;
 
     ChooserMultiProfilePagerAdapter(Context context,
             ChooserActivity.ChooserGridAdapter adapter,
-            UserHandle personalProfileUserHandle,
+            EmptyStateProvider emptyStateProvider,
+            QuietModeManager quietModeManager,
             UserHandle workProfileUserHandle,
-            boolean isSendAction, int maxTargetsPerRow) {
-        super(context, /* currentPage */ 0, personalProfileUserHandle, workProfileUserHandle);
+            int maxTargetsPerRow) {
+        super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
+                workProfileUserHandle);
         mItems = new ChooserProfileDescriptor[] {
                 createProfileDescriptor(adapter)
         };
-        mIsSendAction = isSendAction;
         mMaxTargetsPerRow = maxTargetsPerRow;
     }
 
     ChooserMultiProfilePagerAdapter(Context context,
             ChooserActivity.ChooserGridAdapter personalAdapter,
             ChooserActivity.ChooserGridAdapter workAdapter,
+            EmptyStateProvider emptyStateProvider,
+            QuietModeManager quietModeManager,
             @Profile int defaultProfile,
-            UserHandle personalProfileUserHandle,
             UserHandle workProfileUserHandle,
-            boolean isSendAction, int maxTargetsPerRow) {
-        super(context, /* currentPage */ defaultProfile, personalProfileUserHandle,
-                workProfileUserHandle);
+            int maxTargetsPerRow) {
+        super(context, /* currentPage */ defaultProfile, emptyStateProvider,
+                quietModeManager, workProfileUserHandle);
         mItems = new ChooserProfileDescriptor[] {
                 createProfileDescriptor(personalAdapter),
                 createProfileDescriptor(workAdapter)
         };
-        mIsSendAction = isSendAction;
         mMaxTargetsPerRow = maxTargetsPerRow;
     }
 
@@ -192,112 +182,6 @@
         return getListViewForIndex(1 - getCurrentPage());
     }
 
-    @Override
-    String getMetricsCategory() {
-        return ResolverActivity.METRICS_CATEGORY_CHOOSER;
-    }
-
-    @Override
-    protected void showWorkProfileOffEmptyState(ResolverListAdapter activeListAdapter,
-            View.OnClickListener listener) {
-        showEmptyState(activeListAdapter,
-                getWorkAppPausedTitle(),
-                /* subtitle = */ null,
-                listener);
-    }
-
-    @Override
-    protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) {
-        if (mIsSendAction) {
-            showEmptyState(activeListAdapter,
-                    getCrossProfileBlockedTitle(),
-                    getCantShareWithWorkMessage());
-        } else {
-            showEmptyState(activeListAdapter,
-                    getCrossProfileBlockedTitle(),
-                    getCantAccessWorkMessage());
-        }
-    }
-
-    @Override
-    protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) {
-        if (mIsSendAction) {
-            showEmptyState(activeListAdapter,
-                    getCrossProfileBlockedTitle(),
-                    getCantShareWithPersonalMessage());
-        } else {
-            showEmptyState(activeListAdapter,
-                    getCrossProfileBlockedTitle(),
-                    getCantAccessPersonalMessage());
-        }
-    }
-
-    @Override
-    protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
-        showEmptyState(listAdapter, getNoPersonalAppsAvailableMessage(), /* subtitle= */ null);
-
-    }
-
-    @Override
-    protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
-        showEmptyState(listAdapter, getNoWorkAppsAvailableMessage(), /* subtitle = */ null);
-    }
-
-    private String getWorkAppPausedTitle() {
-        return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
-                RESOLVER_WORK_PAUSED_TITLE,
-                () -> getContext().getString(R.string.resolver_turn_on_work_apps));
-    }
-
-    private String getCrossProfileBlockedTitle() {
-        return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
-                RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
-                () -> getContext().getString(R.string.resolver_cross_profile_blocked));
-    }
-
-    private String getCantShareWithWorkMessage() {
-        return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
-                RESOLVER_CANT_SHARE_WITH_WORK,
-                () -> getContext().getString(
-                        R.string.resolver_cant_share_with_work_apps_explanation));
-    }
-
-    private String getCantShareWithPersonalMessage() {
-        return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
-                RESOLVER_CANT_SHARE_WITH_PERSONAL,
-                () -> getContext().getString(
-                        R.string.resolver_cant_share_with_personal_apps_explanation));
-    }
-
-    private String getCantAccessWorkMessage() {
-        return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
-                RESOLVER_CANT_ACCESS_WORK,
-                () -> getContext().getString(
-                        R.string.resolver_cant_access_work_apps_explanation));
-    }
-
-    private String getCantAccessPersonalMessage() {
-        return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
-                RESOLVER_CANT_ACCESS_PERSONAL,
-                () -> getContext().getString(
-                        R.string.resolver_cant_access_personal_apps_explanation));
-    }
-
-    private String getNoWorkAppsAvailableMessage() {
-        return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
-                RESOLVER_NO_WORK_APPS,
-                () -> getContext().getString(
-                        R.string.resolver_no_work_apps_available));
-    }
-
-    private String getNoPersonalAppsAvailableMessage() {
-        return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
-                RESOLVER_NO_PERSONAL_APPS,
-                () -> getContext().getString(
-                        R.string.resolver_no_personal_apps_available));
-    }
-
-
     void setEmptyStateBottomOffset(int bottomOffset) {
         mBottomOffset = bottomOffset;
     }
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 30da4b4..88447da 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -58,11 +58,12 @@
     SyncNotedAppOp noteProxyOperation(int code, in AttributionSource attributionSource,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
             boolean skipProxyOperation);
-    SyncNotedAppOp startProxyOperation(int code, in AttributionSource attributionSource,
-            boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
-            boolean shouldCollectMessage, boolean skipProxyOperation, int proxyAttributionFlags,
-            int proxiedAttributionFlags, int attributionChainId);
-    void finishProxyOperation(int code, in AttributionSource attributionSource,
+    SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
+            in AttributionSource attributionSource, boolean startIfModeDefault,
+            boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+            boolean skipProxyOperation, int proxyAttributionFlags, int proxiedAttributionFlags,
+            int attributionChainId);
+    void finishProxyOperation(IBinder clientId, int code, in AttributionSource attributionSource,
             boolean skipProxyOperation);
 
     // Remaining methods are only used in Java.
diff --git a/core/java/com/android/internal/app/LogAccessDialogActivity.java b/core/java/com/android/internal/app/LogAccessDialogActivity.java
deleted file mode 100644
index 4adb867..0000000
--- a/core/java/com/android/internal/app/LogAccessDialogActivity.java
+++ /dev/null
@@ -1,260 +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.app;
-
-import android.annotation.StyleRes;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Configuration;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.text.Html;
-import android.text.Spannable;
-import android.text.TextUtils;
-import android.text.method.LinkMovementMethod;
-import android.text.style.TypefaceSpan;
-import android.text.style.URLSpan;
-import android.util.Slog;
-import android.view.ContextThemeWrapper;
-import android.view.InflateException;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Button;
-import android.widget.TextView;
-
-import com.android.internal.R;
-
-/**
- * Dialog responsible for obtaining user consent per-use log access
- */
-public class LogAccessDialogActivity extends Activity implements
-        View.OnClickListener {
-    private static final String TAG = LogAccessDialogActivity.class.getSimpleName();
-    public static final String EXTRA_CALLBACK = "EXTRA_CALLBACK";
-
-
-    private static final int DIALOG_TIME_OUT = Build.IS_DEBUGGABLE ? 60000 : 300000;
-    private static final int MSG_DISMISS_DIALOG = 0;
-
-    private String mPackageName;
-    private int mUid;
-    private ILogAccessDialogCallback mCallback;
-
-    private String mAlertTitle;
-    private String mAlertBody;
-    private String mAlertLearnMore;
-    private AlertDialog.Builder mAlertDialog;
-    private AlertDialog mAlert;
-    private View mAlertView;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        // retrieve Intent extra information
-        if (!readIntentInfo(getIntent())) {
-            Slog.e(TAG, "Invalid Intent extras, finishing");
-            finish();
-            return;
-        }
-
-        // retrieve the title string from passed intent extra
-        try {
-            mAlertTitle = getTitleString(this, mPackageName, mUid);
-        } catch (NameNotFoundException e) {
-            Slog.e(TAG, "Unable to fetch label of package " + mPackageName, e);
-            declineLogAccess();
-            finish();
-            return;
-        }
-
-        mAlertBody = getResources().getString(R.string.log_access_confirmation_body);
-        mAlertLearnMore = getResources().getString(R.string.log_access_confirmation_learn_more);
-
-        // create View
-        boolean isDarkTheme = (getResources().getConfiguration().uiMode
-                & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
-        int themeId = isDarkTheme ? android.R.style.Theme_DeviceDefault_Dialog_Alert :
-                android.R.style.Theme_DeviceDefault_Light_Dialog_Alert;
-        mAlertView = createView(themeId);
-
-        // create AlertDialog
-        mAlertDialog = new AlertDialog.Builder(this, themeId);
-        mAlertDialog.setView(mAlertView);
-        mAlertDialog.setOnCancelListener(dialog -> declineLogAccess());
-        mAlertDialog.setOnDismissListener(dialog -> finish());
-
-        // show Alert
-        mAlert = mAlertDialog.create();
-        mAlert.getWindow().setHideOverlayWindows(true);
-        mAlert.show();
-
-        // set Alert Timeout
-        mHandler.sendEmptyMessageDelayed(MSG_DISMISS_DIALOG, DIALOG_TIME_OUT);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        if (!isChangingConfigurations() && mAlert != null && mAlert.isShowing()) {
-            mAlert.dismiss();
-        }
-        mAlert = null;
-    }
-
-    private boolean readIntentInfo(Intent intent) {
-        if (intent == null) {
-            Slog.e(TAG, "Intent is null");
-            return false;
-        }
-
-        mCallback = ILogAccessDialogCallback.Stub.asInterface(
-                intent.getExtras().getBinder(EXTRA_CALLBACK));
-        if (mCallback == null) {
-            Slog.e(TAG, "Missing callback");
-            return false;
-        }
-
-        mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-        if (mPackageName == null || mPackageName.length() == 0) {
-            Slog.e(TAG, "Missing package name extra");
-            return false;
-        }
-
-        if (!intent.hasExtra(Intent.EXTRA_UID)) {
-            Slog.e(TAG, "Missing EXTRA_UID");
-            return false;
-        }
-
-        mUid = intent.getIntExtra(Intent.EXTRA_UID, 0);
-
-        return true;
-    }
-
-    private Handler mHandler = new Handler() {
-        public void handleMessage(android.os.Message msg) {
-            switch (msg.what) {
-                case MSG_DISMISS_DIALOG:
-                    if (mAlert != null) {
-                        mAlert.dismiss();
-                        mAlert = null;
-                        declineLogAccess();
-                    }
-                    break;
-
-                default:
-                    break;
-            }
-        }
-    };
-
-    private String getTitleString(Context context, String callingPackage, int uid)
-            throws NameNotFoundException {
-        PackageManager pm = context.getPackageManager();
-
-        CharSequence appLabel = pm.getApplicationInfoAsUser(callingPackage,
-                PackageManager.MATCH_DIRECT_BOOT_AUTO,
-                UserHandle.getUserId(uid)).loadLabel(pm);
-
-        String titleString = context.getString(
-                com.android.internal.R.string.log_access_confirmation_title, appLabel);
-
-        return titleString;
-    }
-
-    private Spannable styleFont(String text) {
-        Spannable s = (Spannable) Html.fromHtml(text);
-        for (URLSpan span : s.getSpans(0, s.length(), URLSpan.class)) {
-            TypefaceSpan typefaceSpan = new TypefaceSpan("google-sans");
-            s.setSpan(typefaceSpan, s.getSpanStart(span), s.getSpanEnd(span), 0);
-        }
-        return s;
-    }
-
-    /**
-     * Returns the dialog view.
-     * If we cannot retrieve the package name, it returns null and we decline the full device log
-     * access
-     */
-    private View createView(@StyleRes int themeId) {
-        Context themedContext = new ContextThemeWrapper(this, themeId);
-        final View view = LayoutInflater.from(themedContext).inflate(
-                R.layout.log_access_user_consent_dialog_permission, null /*root*/);
-
-        if (view == null) {
-            throw new InflateException();
-        }
-
-        ((TextView) view.findViewById(R.id.log_access_dialog_title))
-            .setText(mAlertTitle);
-
-        if (!TextUtils.isEmpty(mAlertLearnMore)) {
-            Spannable mSpannableLearnMore = styleFont(mAlertLearnMore);
-
-            ((TextView) view.findViewById(R.id.log_access_dialog_body))
-                    .setText(TextUtils.concat(mAlertBody, "\n\n", mSpannableLearnMore));
-
-            ((TextView) view.findViewById(R.id.log_access_dialog_body))
-                    .setMovementMethod(LinkMovementMethod.getInstance());
-        } else {
-            ((TextView) view.findViewById(R.id.log_access_dialog_body))
-                    .setText(mAlertBody);
-        }
-
-        Button button_allow = (Button) view.findViewById(R.id.log_access_dialog_allow_button);
-        button_allow.setOnClickListener(this);
-
-        Button button_deny = (Button) view.findViewById(R.id.log_access_dialog_deny_button);
-        button_deny.setOnClickListener(this);
-
-        return view;
-
-    }
-
-    @Override
-    public void onClick(View view) {
-        try {
-            switch (view.getId()) {
-                case R.id.log_access_dialog_allow_button:
-                    mCallback.approveAccessForClient(mUid, mPackageName);
-                    finish();
-                    break;
-                case R.id.log_access_dialog_deny_button:
-                    declineLogAccess();
-                    finish();
-                    break;
-            }
-        } catch (RemoteException e) {
-            finish();
-        }
-    }
-
-    private void declineLogAccess() {
-        try {
-            mCallback.declineAccessForClient(mUid, mPackageName);
-        } catch (RemoteException e) {
-            finish();
-        }
-    }
-}
diff --git a/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java b/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java
new file mode 100644
index 0000000..34249f2
--- /dev/null
+++ b/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java
@@ -0,0 +1,154 @@
+/*
+ * 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.app;
+
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
+import android.stats.devicepolicy.nano.DevicePolicyEnums;
+
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.internal.R;
+
+import java.util.List;
+
+/**
+ * Chooser/ResolverActivity empty state provider that returns empty state which is shown when
+ * there are no apps available.
+ */
+public class NoAppsAvailableEmptyStateProvider implements EmptyStateProvider {
+
+    @NonNull
+    private final Context mContext;
+    @Nullable
+    private final UserHandle mWorkProfileUserHandle;
+    @Nullable
+    private final UserHandle mPersonalProfileUserHandle;
+    @NonNull
+    private final String mMetricsCategory;
+    @NonNull
+    private final MyUserIdProvider mMyUserIdProvider;
+
+    public NoAppsAvailableEmptyStateProvider(Context context, UserHandle workProfileUserHandle,
+            UserHandle personalProfileUserHandle, String metricsCategory,
+            MyUserIdProvider myUserIdProvider) {
+        mContext = context;
+        mWorkProfileUserHandle = workProfileUserHandle;
+        mPersonalProfileUserHandle = personalProfileUserHandle;
+        mMetricsCategory = metricsCategory;
+        mMyUserIdProvider = myUserIdProvider;
+    }
+
+    @Nullable
+    @Override
+    @SuppressWarnings("ReferenceEquality")
+    public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+        UserHandle listUserHandle = resolverListAdapter.getUserHandle();
+
+        if (mWorkProfileUserHandle != null
+                && (mMyUserIdProvider.getMyUserId() == listUserHandle.getIdentifier()
+                || !hasAppsInOtherProfile(resolverListAdapter))) {
+
+            String title;
+            if (listUserHandle == mPersonalProfileUserHandle) {
+                title = mContext.getSystemService(
+                        DevicePolicyManager.class).getResources().getString(
+                        RESOLVER_NO_PERSONAL_APPS,
+                        () -> mContext.getString(R.string.resolver_no_personal_apps_available));
+            } else {
+                title = mContext.getSystemService(
+                        DevicePolicyManager.class).getResources().getString(
+                        RESOLVER_NO_WORK_APPS,
+                        () -> mContext.getString(R.string.resolver_no_work_apps_available));
+            }
+
+            return new NoAppsAvailableEmptyState(
+                    title, mMetricsCategory,
+                    /* isPersonalProfile= */ listUserHandle == mPersonalProfileUserHandle
+            );
+        } else if (mWorkProfileUserHandle == null) {
+            // Return default empty state without tracking
+            return new DefaultEmptyState();
+        }
+
+        return null;
+    }
+
+    private boolean hasAppsInOtherProfile(ResolverListAdapter adapter) {
+        if (mWorkProfileUserHandle == null) {
+            return false;
+        }
+        List<ResolverActivity.ResolvedComponentInfo> resolversForIntent =
+                adapter.getResolversForUser(UserHandle.of(mMyUserIdProvider.getMyUserId()));
+        for (ResolverActivity.ResolvedComponentInfo info : resolversForIntent) {
+            ResolveInfo resolveInfo = info.getResolveInfoAt(0);
+            if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static class DefaultEmptyState implements EmptyState {
+        @Override
+        public boolean useDefaultEmptyView() {
+            return true;
+        }
+    }
+
+    public static class NoAppsAvailableEmptyState implements EmptyState {
+
+        @NonNull
+        private String mTitle;
+
+        @NonNull
+        private String mMetricsCategory;
+
+        private boolean mIsPersonalProfile;
+
+        public NoAppsAvailableEmptyState(String title, String metricsCategory,
+                boolean isPersonalProfile) {
+            mTitle = title;
+            mMetricsCategory = metricsCategory;
+            mIsPersonalProfile = isPersonalProfile;
+        }
+
+        @Nullable
+        @Override
+        public String getTitle() {
+            return mTitle;
+        }
+
+        @Override
+        public void onEmptyStateShown() {
+            DevicePolicyEventLogger.createEvent(
+                            DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_APPS_RESOLVED)
+                    .setStrings(mMetricsCategory)
+                    .setBoolean(/*isPersonalProfile*/ mIsPersonalProfile)
+                    .write();
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java b/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java
new file mode 100644
index 0000000..2e7d5bf
--- /dev/null
+++ b/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.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 com.android.internal.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserHandle;
+
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+
+/**
+ * Empty state provider that does not allow cross profile sharing, it will return a blocker
+ * in case if the profile of the current tab is not the same as the profile of the calling app.
+ */
+public class NoCrossProfileEmptyStateProvider implements EmptyStateProvider {
+
+    private final UserHandle mPersonalProfileUserHandle;
+    private final EmptyState mNoWorkToPersonalEmptyState;
+    private final EmptyState mNoPersonalToWorkEmptyState;
+    private final CrossProfileIntentsChecker mCrossProfileIntentsChecker;
+    private final MyUserIdProvider mUserIdProvider;
+
+    public NoCrossProfileEmptyStateProvider(UserHandle personalUserHandle,
+            EmptyState noWorkToPersonalEmptyState,
+            EmptyState noPersonalToWorkEmptyState,
+            CrossProfileIntentsChecker crossProfileIntentsChecker,
+            MyUserIdProvider myUserIdProvider) {
+        mPersonalProfileUserHandle = personalUserHandle;
+        mNoWorkToPersonalEmptyState = noWorkToPersonalEmptyState;
+        mNoPersonalToWorkEmptyState = noPersonalToWorkEmptyState;
+        mCrossProfileIntentsChecker = crossProfileIntentsChecker;
+        mUserIdProvider = myUserIdProvider;
+    }
+
+    @Nullable
+    @Override
+    public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+        boolean shouldShowBlocker =
+                mUserIdProvider.getMyUserId() != resolverListAdapter.getUserHandle().getIdentifier()
+                && !mCrossProfileIntentsChecker
+                        .hasCrossProfileIntents(resolverListAdapter.getIntents(),
+                                mUserIdProvider.getMyUserId(),
+                                resolverListAdapter.getUserHandle().getIdentifier());
+
+        if (!shouldShowBlocker) {
+            return null;
+        }
+
+        if (resolverListAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) {
+            return mNoWorkToPersonalEmptyState;
+        } else {
+            return mNoPersonalToWorkEmptyState;
+        }
+    }
+
+
+    /**
+     * Empty state that gets strings from the device policy manager and tracks events into
+     * event logger of the device policy events.
+     */
+    public static class DevicePolicyBlockerEmptyState implements EmptyState {
+
+        @NonNull
+        private final Context mContext;
+        private final String mDevicePolicyStringTitleId;
+        @StringRes
+        private final int mDefaultTitleResource;
+        private final String mDevicePolicyStringSubtitleId;
+        @StringRes
+        private final int mDefaultSubtitleResource;
+        private final int mEventId;
+        @NonNull
+        private final String mEventCategory;
+
+        public DevicePolicyBlockerEmptyState(Context context, String devicePolicyStringTitleId,
+                @StringRes int defaultTitleResource, String devicePolicyStringSubtitleId,
+                @StringRes int defaultSubtitleResource,
+                int devicePolicyEventId, String devicePolicyEventCategory) {
+            mContext = context;
+            mDevicePolicyStringTitleId = devicePolicyStringTitleId;
+            mDefaultTitleResource = defaultTitleResource;
+            mDevicePolicyStringSubtitleId = devicePolicyStringSubtitleId;
+            mDefaultSubtitleResource = defaultSubtitleResource;
+            mEventId = devicePolicyEventId;
+            mEventCategory = devicePolicyEventCategory;
+        }
+
+        @Nullable
+        @Override
+        public String getTitle() {
+            return mContext.getSystemService(DevicePolicyManager.class).getResources().getString(
+                    mDevicePolicyStringTitleId,
+                    () -> mContext.getString(mDefaultTitleResource));
+        }
+
+        @Nullable
+        @Override
+        public String getSubtitle() {
+            return mContext.getSystemService(DevicePolicyManager.class).getResources().getString(
+                    mDevicePolicyStringSubtitleId,
+                    () -> mContext.getString(mDefaultSubtitleResource));
+        }
+
+        @Override
+        public void onEmptyStateShown() {
+            DevicePolicyEventLogger.createEvent(mEventId)
+                    .setStrings(mEventCategory)
+                    .write();
+        }
+
+        @Override
+        public boolean shouldSkipDataRebuild() {
+            return true;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 822393f..0706ee5 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -19,6 +19,9 @@
 import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
 import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL;
 import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB;
 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY;
 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED;
@@ -26,6 +29,8 @@
 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.PermissionChecker.PID_UNKNOWN;
+import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
+import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import android.annotation.Nullable;
@@ -57,6 +62,7 @@
 import android.graphics.Insets;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PatternMatcher;
@@ -93,7 +99,14 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.CompositeEmptyStateProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.Profile;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
+import com.android.internal.app.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
 import com.android.internal.app.chooser.ChooserTargetInfo;
 import com.android.internal.app.chooser.DisplayResolveInfo;
 import com.android.internal.app.chooser.TargetInfo;
@@ -186,6 +199,8 @@
     @VisibleForTesting
     protected AbstractMultiProfilePagerAdapter mMultiProfilePagerAdapter;
 
+    protected QuietModeManager mQuietModeManager;
+
     // Intent extra for connected audio devices
     public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device";
 
@@ -217,6 +232,9 @@
 
     private UserHandle mWorkProfileUserHandle;
 
+    @Nullable
+    private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
+
     protected final LatencyTracker mLatencyTracker = getLatencyTracker();
 
     private LatencyTracker getLatencyTracker() {
@@ -375,6 +393,8 @@
         setTheme(appliedThemeResId());
         super.onCreate(savedInstanceState);
 
+        mQuietModeManager = createQuietModeManager();
+
         // Determine whether we should show that intent is forwarded
         // from managed profile to owner or other way around.
         setProfileSwitchMessage(intent.getContentUserHint());
@@ -475,6 +495,111 @@
         return resolverMultiProfilePagerAdapter;
     }
 
+    @VisibleForTesting
+    protected MyUserIdProvider createMyUserIdProvider() {
+        return new MyUserIdProvider();
+    }
+
+    @VisibleForTesting
+    protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+        return new CrossProfileIntentsChecker(getContentResolver());
+    }
+
+    @VisibleForTesting
+    protected QuietModeManager createQuietModeManager() {
+        UserManager userManager = getSystemService(UserManager.class);
+        return new QuietModeManager() {
+
+            private boolean mIsWaitingToEnableWorkProfile = false;
+
+            @Override
+            public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
+                return userManager.isQuietModeEnabled(workProfileUserHandle);
+            }
+
+            @Override
+            public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) {
+                AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
+                    userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
+                });
+                mIsWaitingToEnableWorkProfile = true;
+            }
+
+            @Override
+            public void markWorkProfileEnabledBroadcastReceived() {
+                mIsWaitingToEnableWorkProfile = false;
+            }
+
+            @Override
+            public boolean isWaitingToEnableWorkProfile() {
+                return mIsWaitingToEnableWorkProfile;
+            }
+        };
+    }
+
+    protected EmptyStateProvider createBlockerEmptyStateProvider() {
+        final boolean shouldShowNoCrossProfileIntentsEmptyState = getUser().equals(getIntentUser());
+
+        if (!shouldShowNoCrossProfileIntentsEmptyState) {
+            // Implementation that doesn't show any blockers
+            return new EmptyStateProvider() {};
+        }
+
+        final AbstractMultiProfilePagerAdapter.EmptyState
+                noWorkToPersonalEmptyState =
+                new DevicePolicyBlockerEmptyState(/* context= */ this,
+                /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+                /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+                /* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_PERSONAL,
+                /* defaultSubtitleResource= */
+                R.string.resolver_cant_access_personal_apps_explanation,
+                /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
+                /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_RESOLVER);
+
+        final AbstractMultiProfilePagerAdapter.EmptyState noPersonalToWorkEmptyState =
+                new DevicePolicyBlockerEmptyState(/* context= */ this,
+                /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+                /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+                /* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_WORK,
+                /* defaultSubtitleResource= */
+                R.string.resolver_cant_access_work_apps_explanation,
+                /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
+                /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_RESOLVER);
+
+        return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
+                noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
+                createCrossProfileIntentsChecker(), createMyUserIdProvider());
+    }
+
+    protected EmptyStateProvider createEmptyStateProvider(
+            @Nullable UserHandle workProfileUserHandle) {
+        final EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider();
+
+        final EmptyStateProvider workProfileOffEmptyStateProvider =
+                new WorkProfilePausedEmptyStateProvider(this, workProfileUserHandle,
+                        mQuietModeManager,
+                        /* onSwitchOnWorkSelectedListener= */
+                        () -> { if (mOnSwitchOnWorkSelectedListener != null) {
+                            mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
+                        }},
+                        getMetricsCategory());
+
+        final EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider(
+                this,
+                workProfileUserHandle,
+                getPersonalProfileUserHandle(),
+                getMetricsCategory(),
+                createMyUserIdProvider()
+        );
+
+        // Return composite provider, the order matters (the higher, the more priority)
+        return new CompositeEmptyStateProvider(
+                blockerEmptyStateProvider,
+                workProfileOffEmptyStateProvider,
+                noAppsEmptyStateProvider
+        );
+    }
+
     private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForOneProfile(
             Intent[] initialIntents,
             List<ResolveInfo> rList, boolean filterLastUsed) {
@@ -485,13 +610,21 @@
                 rList,
                 filterLastUsed,
                 /* userHandle */ UserHandle.of(UserHandle.myUserId()));
+        QuietModeManager quietModeManager = createQuietModeManager();
         return new ResolverMultiProfilePagerAdapter(
                 /* context */ this,
                 adapter,
-                getPersonalProfileUserHandle(),
+                createEmptyStateProvider(/* workProfileUserHandle= */ null),
+                quietModeManager,
                 /* workProfileUserHandle= */ null);
     }
 
+    private UserHandle getIntentUser() {
+        return getIntent().hasExtra(EXTRA_CALLING_USER)
+                ? getIntent().getParcelableExtra(EXTRA_CALLING_USER, android.os.UserHandle.class)
+                : getUser();
+    }
+
     private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles(
             Intent[] initialIntents,
             List<ResolveInfo> rList,
@@ -500,9 +633,7 @@
         // the intent resolver is started in the other profile. Since this is the only case when
         // this happens, we check for it here and set the current profile's tab.
         int selectedProfile = getCurrentProfile();
-        UserHandle intentUser = getIntent().hasExtra(EXTRA_CALLING_USER)
-                ? getIntent().getParcelableExtra(EXTRA_CALLING_USER, android.os.UserHandle.class)
-                : getUser();
+        UserHandle intentUser = getIntentUser();
         if (!getUser().equals(intentUser)) {
             if (getPersonalProfileUserHandle().equals(intentUser)) {
                 selectedProfile = PROFILE_PERSONAL;
@@ -535,14 +666,15 @@
                 (filterLastUsed && UserHandle.myUserId()
                         == workProfileUserHandle.getIdentifier()),
                 /* userHandle */ workProfileUserHandle);
+        QuietModeManager quietModeManager = createQuietModeManager();
         return new ResolverMultiProfilePagerAdapter(
                 /* context */ this,
                 personalAdapter,
                 workAdapter,
+                createEmptyStateProvider(getWorkProfileUserHandle()),
+                quietModeManager,
                 selectedProfile,
-                getPersonalProfileUserHandle(),
-                getWorkProfileUserHandle(),
-                /* shouldShowNoCrossProfileIntentsEmptyState= */ getUser().equals(intentUser));
+                getWorkProfileUserHandle());
     }
 
     protected int appliedThemeResId() {
@@ -849,9 +981,9 @@
             }
             mRegistered = true;
         }
-        if (shouldShowTabs() && mMultiProfilePagerAdapter.isWaitingToEnableWorkProfile()) {
-            if (mMultiProfilePagerAdapter.isQuietModeEnabled(getWorkProfileUserHandle())) {
-                mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived();
+        if (shouldShowTabs() && mQuietModeManager.isWaitingToEnableWorkProfile()) {
+            if (mQuietModeManager.isQuietModeEnabled(getWorkProfileUserHandle())) {
+                mQuietModeManager.markWorkProfileEnabledBroadcastReceived();
             }
         }
         mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
@@ -1098,6 +1230,9 @@
     @Override // ResolverListCommunicator
     public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing,
             boolean rebuildCompleted) {
+        if (isDestroyed()) {
+            return;
+        }
         if (isAutolaunching()) {
             return;
         }
@@ -1808,13 +1943,12 @@
                         onHorizontalSwipeStateChanged(state);
                     }
                 });
-        mMultiProfilePagerAdapter.setOnSwitchOnWorkSelectedListener(
-                () -> {
-                    final View workTab = tabHost.getTabWidget().getChildAt(1);
-                    workTab.setFocusable(true);
-                    workTab.setFocusableInTouchMode(true);
-                    workTab.requestFocus();
-                });
+        mOnSwitchOnWorkSelectedListener = () -> {
+            final View workTab = tabHost.getTabWidget().getChildAt(1);
+            workTab.setFocusable(true);
+            workTab.setFocusableInTouchMode(true);
+            workTab.requestFocus();
+        };
     }
 
     private String getPersonalTabLabel() {
@@ -2075,7 +2209,7 @@
     public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
         if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) {
             if (listAdapter.getUserHandle().equals(getWorkProfileUserHandle())
-                    && mMultiProfilePagerAdapter.isWaitingToEnableWorkProfile()) {
+                    && mQuietModeManager.isWaitingToEnableWorkProfile()) {
                 // We have just turned on the work profile and entered the pass code to start it,
                 // now we are waiting to receive the ACTION_USER_UNLOCKED broadcast. There is no
                 // point in reloading the list now, since the work profile user is still
@@ -2127,7 +2261,7 @@
                     }
 
                     mWorkProfileHasBeenEnabled = true;
-                    mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived();
+                    mQuietModeManager.markWorkProfileEnabledBroadcastReceived();
                 } else {
                     // Must be an UNAVAILABLE broadcast, so we watch for the next availability
                     mWorkProfileHasBeenEnabled = false;
@@ -2143,7 +2277,6 @@
         };
     }
 
-    @VisibleForTesting
     public static final class ResolvedComponentInfo {
         public final ComponentName name;
         private final List<Intent> mIntents = new ArrayList<>();
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index 0b33501..9922051 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -16,15 +16,7 @@
 
 package com.android.internal.app;
 
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
-
 import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.os.UserHandle;
 import android.view.LayoutInflater;
@@ -43,34 +35,33 @@
 public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerAdapter {
 
     private final ResolverProfileDescriptor[] mItems;
-    private final boolean mShouldShowNoCrossProfileIntentsEmptyState;
     private boolean mUseLayoutWithDefault;
 
     ResolverMultiProfilePagerAdapter(Context context,
             ResolverListAdapter adapter,
-            UserHandle personalProfileUserHandle,
+            EmptyStateProvider emptyStateProvider,
+            QuietModeManager quietModeManager,
             UserHandle workProfileUserHandle) {
-        super(context, /* currentPage */ 0, personalProfileUserHandle, workProfileUserHandle);
+        super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
+                workProfileUserHandle);
         mItems = new ResolverProfileDescriptor[] {
                 createProfileDescriptor(adapter)
         };
-        mShouldShowNoCrossProfileIntentsEmptyState = true;
     }
 
     ResolverMultiProfilePagerAdapter(Context context,
             ResolverListAdapter personalAdapter,
             ResolverListAdapter workAdapter,
+            EmptyStateProvider emptyStateProvider,
+            QuietModeManager quietModeManager,
             @Profile int defaultProfile,
-            UserHandle personalProfileUserHandle,
-            UserHandle workProfileUserHandle,
-            boolean shouldShowNoCrossProfileIntentsEmptyState) {
-        super(context, /* currentPage */ defaultProfile, personalProfileUserHandle,
+            UserHandle workProfileUserHandle) {
+        super(context, /* currentPage */ defaultProfile, emptyStateProvider, quietModeManager,
                 workProfileUserHandle);
         mItems = new ResolverProfileDescriptor[] {
                 createProfileDescriptor(personalAdapter),
                 createProfileDescriptor(workAdapter)
         };
-        mShouldShowNoCrossProfileIntentsEmptyState = shouldShowNoCrossProfileIntentsEmptyState;
     }
 
     private ResolverProfileDescriptor createProfileDescriptor(
@@ -170,93 +161,6 @@
         return getListViewForIndex(1 - getCurrentPage());
     }
 
-    @Override
-    String getMetricsCategory() {
-        return ResolverActivity.METRICS_CATEGORY_RESOLVER;
-    }
-
-    @Override
-    boolean allowShowNoCrossProfileIntentsEmptyState() {
-        return mShouldShowNoCrossProfileIntentsEmptyState;
-    }
-
-    @Override
-    protected void showWorkProfileOffEmptyState(ResolverListAdapter activeListAdapter,
-            View.OnClickListener listener) {
-        showEmptyState(activeListAdapter,
-                getWorkAppPausedTitle(),
-                /* subtitle = */ null,
-                listener);
-    }
-
-    @Override
-    protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) {
-        showEmptyState(activeListAdapter,
-                getCrossProfileBlockedTitle(),
-                getCantAccessWorkMessage());
-    }
-
-    @Override
-    protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) {
-        showEmptyState(activeListAdapter,
-                getCrossProfileBlockedTitle(),
-                getCantAccessPersonalMessage());
-    }
-
-    @Override
-    protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
-        showEmptyState(listAdapter,
-                getNoPersonalAppsAvailableMessage(),
-                /* subtitle = */ null);
-    }
-
-    @Override
-    protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
-        showEmptyState(listAdapter,
-                getNoWorkAppsAvailableMessage(),
-                /* subtitle= */ null);
-    }
-
-    private String getWorkAppPausedTitle() {
-        return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
-                RESOLVER_WORK_PAUSED_TITLE,
-                () -> getContext().getString(R.string.resolver_turn_on_work_apps));
-    }
-
-    private String getCrossProfileBlockedTitle() {
-        return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
-                RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
-                () -> getContext().getString(R.string.resolver_cross_profile_blocked));
-    }
-
-    private String getCantAccessWorkMessage() {
-        return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
-                RESOLVER_CANT_ACCESS_WORK,
-                () -> getContext().getString(
-                        R.string.resolver_cant_access_work_apps_explanation));
-    }
-
-    private String getCantAccessPersonalMessage() {
-        return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
-                RESOLVER_CANT_ACCESS_PERSONAL,
-                () -> getContext().getString(
-                        R.string.resolver_cant_access_personal_apps_explanation));
-    }
-
-    private String getNoWorkAppsAvailableMessage() {
-        return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
-                RESOLVER_NO_WORK_APPS,
-                () -> getContext().getString(
-                        R.string.resolver_no_work_apps_available));
-    }
-
-    private String getNoPersonalAppsAvailableMessage() {
-        return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
-                RESOLVER_NO_PERSONAL_APPS,
-                () -> getContext().getString(
-                        R.string.resolver_no_personal_apps_available));
-    }
-
     void setUseLayoutWithDefault(boolean useLayoutWithDefault) {
         mUseLayoutWithDefault = useLayoutWithDefault;
     }
diff --git a/core/java/com/android/internal/app/WorkProfilePausedEmptyStateProvider.java b/core/java/com/android/internal/app/WorkProfilePausedEmptyStateProvider.java
new file mode 100644
index 0000000..6a88557
--- /dev/null
+++ b/core/java/com/android/internal/app/WorkProfilePausedEmptyStateProvider.java
@@ -0,0 +1,114 @@
+/*
+ * 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.app;
+
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserHandle;
+import android.stats.devicepolicy.nano.DevicePolicyEnums;
+
+import com.android.internal.R;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
+
+/**
+ * Chooser/ResolverActivity empty state provider that returns empty state which is shown when
+ * work profile is paused and we need to show a button to enable it.
+ */
+public class WorkProfilePausedEmptyStateProvider implements EmptyStateProvider {
+
+    private final UserHandle mWorkProfileUserHandle;
+    private final QuietModeManager mQuietModeManager;
+    private final String mMetricsCategory;
+    private final OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
+    private final Context mContext;
+
+    public WorkProfilePausedEmptyStateProvider(@NonNull Context context,
+            @Nullable UserHandle workProfileUserHandle,
+            @NonNull QuietModeManager quietModeManager,
+            @Nullable OnSwitchOnWorkSelectedListener onSwitchOnWorkSelectedListener,
+            @NonNull String metricsCategory) {
+        mContext = context;
+        mWorkProfileUserHandle = workProfileUserHandle;
+        mQuietModeManager = quietModeManager;
+        mMetricsCategory = metricsCategory;
+        mOnSwitchOnWorkSelectedListener = onSwitchOnWorkSelectedListener;
+    }
+
+    @Nullable
+    @Override
+    public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+        if (!resolverListAdapter.getUserHandle().equals(mWorkProfileUserHandle)
+                || !mQuietModeManager.isQuietModeEnabled(mWorkProfileUserHandle)
+                || resolverListAdapter.getCount() == 0) {
+            return null;
+        }
+
+        final String title = mContext.getSystemService(DevicePolicyManager.class)
+                .getResources().getString(RESOLVER_WORK_PAUSED_TITLE,
+                () -> mContext.getString(R.string.resolver_turn_on_work_apps));
+
+        return new WorkProfileOffEmptyState(title, (tab) -> {
+            tab.showSpinner();
+            if (mOnSwitchOnWorkSelectedListener != null) {
+                mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
+            }
+            mQuietModeManager.requestQuietModeEnabled(false, mWorkProfileUserHandle);
+        }, mMetricsCategory);
+    }
+
+    public static class WorkProfileOffEmptyState implements EmptyState {
+
+        private final String mTitle;
+        private final ClickListener mOnClick;
+        private final String mMetricsCategory;
+
+        public WorkProfileOffEmptyState(String title, @NonNull ClickListener onClick,
+                @NonNull String metricsCategory) {
+            mTitle = title;
+            mOnClick = onClick;
+            mMetricsCategory = metricsCategory;
+        }
+
+        @Nullable
+        @Override
+        public String getTitle() {
+            return mTitle;
+        }
+
+        @Nullable
+        @Override
+        public ClickListener getButtonClickListener() {
+            return mOnClick;
+        }
+
+        @Override
+        public void onEmptyStateShown() {
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.RESOLVER_EMPTY_STATE_WORK_APPS_DISABLED)
+                    .setStrings(mMetricsCategory)
+                    .write();
+        }
+    }
+}
diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java
index 97f4b0f..a21a842 100644
--- a/core/java/com/android/internal/app/procstats/AssociationState.java
+++ b/core/java/com/android/internal/app/procstats/AssociationState.java
@@ -59,6 +59,7 @@
     /**
      * The state of the source process of an association.
      */
+    @SuppressWarnings("ParcelableCreator")
     public static final class SourceState implements Parcelable {
         private @NonNull final ProcessStats mProcessStats;
         private @Nullable final AssociationState mAssociationState;
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 44997b4..4f7f8ba 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -556,6 +556,17 @@
             "show_stop_button_for_user_allowlisted_apps";
 
     /**
+     * (boolean) Whether the task manager should show apps running user-visible jobs.
+     */
+    public static final String TASK_MANAGER_SHOW_USER_VISIBLE_JOBS =
+            "task_manager_show_user_visible_jobs";
+
+    /**
+     * (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";
@@ -579,6 +590,13 @@
     public static final String CLIPBOARD_OVERLAY_SHOW_ACTIONS = "clipboard_overlay_show_actions";
 
     /**
+     * (boolean) Whether to ignore the source package for determining whether to use remote copy
+     * behavior in the clipboard UI.
+     */
+    public static final String CLIPBOARD_IGNORE_REMOTE_COPY_SOURCE =
+            "clipboard_ignore_remote_copy_source";
+
+    /**
      * (boolean) Whether to combine the broadcasts APPWIDGET_ENABLED and APPWIDGET_UPDATE
      */
     public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled";
diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java
index 828e5cf..2d04bdb 100644
--- a/core/java/com/android/internal/content/om/OverlayConfig.java
+++ b/core/java/com/android/internal/content/om/OverlayConfig.java
@@ -140,7 +140,6 @@
 
         ArrayMap<Integer, List<String>> activeApexesPerPartition = getActiveApexes(partitions);
 
-        boolean foundConfigFile = false;
         final Map<String, ParsedOverlayInfo> packageManagerOverlayInfos =
                 packageProvider == null ? null : getOverlayPackageInfos(packageProvider);
 
@@ -154,7 +153,6 @@
                             activeApexesPerPartition.getOrDefault(partition.type,
                                     Collections.emptyList()));
             if (partitionOverlays != null) {
-                foundConfigFile = true;
                 overlays.addAll(partitionOverlays);
                 continue;
             }
@@ -191,12 +189,6 @@
             overlays.addAll(partitionConfigs);
         }
 
-        if (!foundConfigFile) {
-            // If no overlay configuration files exist, disregard partition precedence and allow
-            // android:priority to reorder overlays across partition boundaries.
-            overlays.sort(sStaticOverlayComparator);
-        }
-
         for (int i = 0, n = overlays.size(); i < n; i++) {
             // Add the configurations to a map so definitions of an overlay in an earlier
             // partition can be replaced by an overlay with the same package name in a later
diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
new file mode 100644
index 0000000..260d1a2
--- /dev/null
+++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
@@ -0,0 +1,403 @@
+/*
+ * 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.content.om;
+
+import static android.content.Context.MODE_PRIVATE;
+import static android.content.om.OverlayManagerTransaction.Request.BUNDLE_FABRICATED_OVERLAY;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.internal.content.om.OverlayConfig.DEFAULT_PRIORITY;
+
+import android.annotation.NonNull;
+import android.annotation.NonUiContext;
+import android.content.Context;
+import android.content.om.OverlayIdentifier;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManagerTransaction;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.parsing.FrameworkParsingPackageUtils;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
+import android.os.FabricatedOverlayInternalEntry;
+import android.os.FileUtils;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * This class provides the functionalities of registering an overlay, unregistering an overlay, and
+ * getting the list of overlays information.
+ */
+public class OverlayManagerImpl {
+    private static final String TAG = "OverlayManagerImpl";
+    private static final boolean DEBUG = false;
+
+    private static final String FRRO_EXTENSION = ".frro";
+
+    private static final String IDMAP_EXTENSION = ".idmap";
+
+    @VisibleForTesting(visibility = PRIVATE)
+    public static final String SELF_TARGET = ".self_target";
+
+    @NonNull
+    private final Context mContext;
+    private Path mBasePath;
+
+    /**
+     * Init a OverlayManagerImpl by the context.
+     *
+     * @param context the context to create overlay environment
+     */
+    @VisibleForTesting(visibility = PACKAGE)
+    public OverlayManagerImpl(@NonNull Context context) {
+        mContext = Objects.requireNonNull(context);
+
+        if (!Process.myUserHandle().equals(context.getUser())) {
+            throw new SecurityException("Self-Targeting doesn't support multiple user now!");
+        }
+    }
+
+    private static void cleanExpiredOverlays(Path selfTargetingBasePath,
+            Path folderForCurrentBaseApk) {
+        try {
+            final String currentBaseFolder = folderForCurrentBaseApk.toString();
+            final String selfTargetingDir = selfTargetingBasePath.getFileName().toString();
+            Files.walkFileTree(
+                    selfTargetingBasePath,
+                    new SimpleFileVisitor<>() {
+                        @Override
+                        public FileVisitResult preVisitDirectory(Path dir,
+                                                                 BasicFileAttributes attrs)
+                                throws IOException {
+                            final String fileName = dir.getFileName().toString();
+                            return fileName.equals(currentBaseFolder)
+                                    ? FileVisitResult.SKIP_SUBTREE
+                                    : super.preVisitDirectory(dir, attrs);
+                        }
+
+                        @Override
+                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+                                throws IOException {
+                            if (!file.toFile().delete()) {
+                                Log.w(TAG, "Failed to delete file " + file);
+                            }
+                            return super.visitFile(file, attrs);
+                        }
+
+                        @Override
+                        public FileVisitResult postVisitDirectory(Path dir, IOException exc)
+                                throws IOException {
+                            final String fileName = dir.getFileName().toString();
+                            if (!fileName.equals(currentBaseFolder)
+                                    && !fileName.equals(selfTargetingDir)) {
+                                if (!dir.toFile().delete()) {
+                                    Log.w(TAG, "Failed to delete dir " + dir);
+                                }
+                            }
+                            return super.postVisitDirectory(dir, exc);
+                        }
+                    });
+        } catch (IOException e) {
+            Log.w(TAG, "Unknown fail " + e);
+        }
+    }
+
+    /** Ensure the base dir for self-targeting is valid. */
+    @VisibleForTesting
+    @NonUiContext
+    public void ensureBaseDir() {
+        final String baseApkPath = mContext.getApplicationInfo().getBaseCodePath();
+        final Path baseApkFolderName = Path.of(baseApkPath).getParent().getFileName();
+        final File selfTargetingBaseFile = mContext.getDir(SELF_TARGET, MODE_PRIVATE);
+        Preconditions.checkArgument(
+                selfTargetingBaseFile.isDirectory()
+                        && selfTargetingBaseFile.exists()
+                        && selfTargetingBaseFile.canWrite()
+                        && selfTargetingBaseFile.canRead()
+                        && selfTargetingBaseFile.canExecute(),
+                "Can't work for this context");
+        cleanExpiredOverlays(selfTargetingBaseFile.toPath(), baseApkFolderName);
+
+        final File baseFile = new File(selfTargetingBaseFile, baseApkFolderName.toString());
+        if (!baseFile.exists()) {
+            if (!baseFile.mkdirs()) {
+                Log.w(TAG, "Failed to create directory " + baseFile);
+            }
+
+            // It fails to create frro and idmap files without this setting.
+            FileUtils.setPermissions(
+                    baseFile,
+                    FileUtils.S_IRWXU,
+                    -1 /* uid unchanged */,
+                    -1 /* gid unchanged */);
+        }
+        Preconditions.checkArgument(
+                baseFile.isDirectory()
+                        && baseFile.exists()
+                        && baseFile.canWrite()
+                        && baseFile.canRead()
+                        && baseFile.canExecute(), // 'list' capability
+                "Can't create a workspace for this context");
+
+        mBasePath = baseFile.toPath();
+    }
+
+    private boolean isSameWithTargetSignature(final String targetPackage) {
+        final PackageManager packageManager = mContext.getPackageManager();
+        final String packageName = mContext.getPackageName();
+        if (TextUtils.equals(packageName, targetPackage)) {
+            return true;
+        }
+        return packageManager.checkSignatures(packageName, targetPackage)
+                == PackageManager.SIGNATURE_MATCH;
+    }
+
+    /**
+     * Check if the overlay name is valid or not.
+     *
+     * @param name the non-check overlay name
+     * @return the valid overlay name
+     */
+    public static String checkOverlayNameValid(@NonNull String name) {
+        final String overlayName =
+                Preconditions.checkStringNotEmpty(
+                        name, "overlayName should be neither empty nor null string");
+        final String checkOverlayNameResult =
+                FrameworkParsingPackageUtils.validateName(
+                        overlayName, false /* requireSeparator */, true /* requireFilename */);
+        Preconditions.checkArgument(
+                checkOverlayNameResult == null,
+                TextUtils.formatSimple(
+                        "Invalid overlayName \"%s\". The check result is %s.",
+                        overlayName, checkOverlayNameResult));
+        return overlayName;
+    }
+
+    private void checkPackageName(@NonNull String packageName) {
+        Preconditions.checkStringNotEmpty(packageName);
+        Preconditions.checkArgument(
+                TextUtils.equals(mContext.getPackageName(), packageName),
+                TextUtils.formatSimple(
+                        "UID %d doesn't own the package %s", Process.myUid(), packageName));
+    }
+
+    /**
+     * Save FabricatedOverlay instance as frro and idmap files.
+     *
+     * <p>In order to fill the overlayable policy, it's necessary to collect the information from
+     * app. And then, the information is passed to native layer to fill the overlayable policy
+     *
+     * @param overlayInternal the FabricatedOverlayInternal to be saved.
+     */
+    @NonUiContext
+    public void registerFabricatedOverlay(@NonNull FabricatedOverlayInternal overlayInternal)
+            throws IOException, PackageManager.NameNotFoundException {
+        ensureBaseDir();
+        Objects.requireNonNull(overlayInternal);
+        final List<FabricatedOverlayInternalEntry> entryList =
+                Objects.requireNonNull(overlayInternal.entries);
+        Preconditions.checkArgument(!entryList.isEmpty(), "overlay entries shouldn't be empty");
+        final String overlayName = checkOverlayNameValid(overlayInternal.overlayName);
+        checkPackageName(overlayInternal.packageName);
+        checkPackageName(overlayInternal.targetPackageName);
+        Preconditions.checkStringNotEmpty(
+                overlayInternal.targetOverlayable,
+                "Target overlayable should be neither null nor empty string.");
+
+        final ApplicationInfo applicationInfo = mContext.getApplicationInfo();
+        final String targetPackage = Preconditions.checkStringNotEmpty(
+                applicationInfo.getBaseCodePath());
+        final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION);
+        final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION);
+
+        createFrroFile(frroPath.toString(), overlayInternal);
+        try {
+            createIdmapFile(
+                    targetPackage,
+                    frroPath.toString(),
+                    idmapPath.toString(),
+                    overlayName,
+                    applicationInfo.isSystemApp() || applicationInfo.isSystemExt() /* isSystem */,
+                    applicationInfo.isVendor(),
+                    applicationInfo.isProduct(),
+                    isSameWithTargetSignature(overlayInternal.targetPackageName),
+                    applicationInfo.isOdm(),
+                    applicationInfo.isOem());
+        } catch (IOException e) {
+            if (!frroPath.toFile().delete()) {
+                Log.w(TAG, "Failed to delete file " + frroPath);
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * Remove the overlay with the specific name
+     *
+     * @param overlayName the specific name
+     */
+    @NonUiContext
+    public void unregisterFabricatedOverlay(@NonNull String overlayName) {
+        ensureBaseDir();
+        checkOverlayNameValid(overlayName);
+        final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION);
+        final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION);
+
+        if (!frroPath.toFile().delete()) {
+            Log.w(TAG, "Failed to delete file " + frroPath);
+        }
+        if (!idmapPath.toFile().delete()) {
+            Log.w(TAG, "Failed to delete file " + idmapPath);
+        }
+    }
+
+    /**
+     * Commit the overlay manager transaction
+     *
+     * @param transaction the overlay manager transaction
+     */
+    @NonUiContext
+    public void commit(@NonNull OverlayManagerTransaction transaction)
+            throws PackageManager.NameNotFoundException, IOException {
+        Objects.requireNonNull(transaction);
+
+        for (Iterator<OverlayManagerTransaction.Request> it = transaction.iterator();
+                it.hasNext(); ) {
+            final OverlayManagerTransaction.Request request = it.next();
+            if (request.type == TYPE_REGISTER_FABRICATED) {
+                final FabricatedOverlayInternal fabricatedOverlayInternal =
+                        Objects.requireNonNull(
+                                request.extras.getParcelable(
+                                        BUNDLE_FABRICATED_OVERLAY,
+                                        FabricatedOverlayInternal.class));
+
+                // populate the mandatory data
+                if (TextUtils.isEmpty(fabricatedOverlayInternal.packageName)) {
+                    fabricatedOverlayInternal.packageName = mContext.getPackageName();
+                } else {
+                    if (!TextUtils.equals(
+                            fabricatedOverlayInternal.packageName, mContext.getPackageName())) {
+                        throw new IllegalArgumentException("Unknown package name in transaction");
+                    }
+                }
+
+                registerFabricatedOverlay(fabricatedOverlayInternal);
+            } else if (request.type == TYPE_UNREGISTER_FABRICATED) {
+                final OverlayIdentifier overlayIdentifier = Objects.requireNonNull(request.overlay);
+                unregisterFabricatedOverlay(overlayIdentifier.getOverlayName());
+            } else {
+                throw new IllegalArgumentException("Unknown request in transaction " + request);
+            }
+        }
+    }
+
+    /**
+     * Get the list of overlays information for the target package name.
+     *
+     * @param targetPackage the target package name
+     * @return the list of overlays information.
+     */
+    @NonNull
+    public List<OverlayInfo> getOverlayInfosForTarget(@NonNull String targetPackage) {
+        ensureBaseDir();
+
+        final File base = mBasePath.toFile();
+        final File[] frroFiles = base.listFiles((dir, name) -> {
+            if (!name.endsWith(FRRO_EXTENSION)) {
+                return false;
+            }
+
+            final String idmapFileName = name.substring(0, name.length() - FRRO_EXTENSION.length())
+                    + IDMAP_EXTENSION;
+            final File idmapFile = new File(dir, idmapFileName);
+            return idmapFile.exists();
+        });
+
+        final ArrayList<OverlayInfo> overlayInfos = new ArrayList<>();
+        for (File file : frroFiles) {
+            final FabricatedOverlayInfo fabricatedOverlayInfo;
+            try {
+                fabricatedOverlayInfo = getFabricatedOverlayInfo(file.getAbsolutePath());
+            } catch (IOException e) {
+                Log.w(TAG, "can't load " + file);
+                continue;
+            }
+            if (!TextUtils.equals(targetPackage, fabricatedOverlayInfo.targetPackageName)) {
+                continue;
+            }
+            if (DEBUG) {
+                Log.i(TAG, "load " + file);
+            }
+
+            final OverlayInfo overlayInfo =
+                    new OverlayInfo(
+                            fabricatedOverlayInfo.packageName,
+                            fabricatedOverlayInfo.overlayName,
+                            fabricatedOverlayInfo.targetPackageName,
+                            fabricatedOverlayInfo.targetOverlayable,
+                            null,
+                            file.getAbsolutePath(),
+                            OverlayInfo.STATE_ENABLED,
+                            UserHandle.myUserId(),
+                            DEFAULT_PRIORITY,
+                            true /* isMutable */,
+                            true /* isFabricated */);
+            overlayInfos.add(overlayInfo);
+        }
+        return overlayInfos;
+    }
+
+    private static native void createFrroFile(
+            @NonNull String frroFile, @NonNull FabricatedOverlayInternal fabricatedOverlayInternal)
+            throws IOException;
+
+    private static native void createIdmapFile(
+            @NonNull String targetPath,
+            @NonNull String overlayPath,
+            @NonNull String idmapPath,
+            @NonNull String overlayName,
+            boolean isSystem,
+            boolean isVendor,
+            boolean isProduct,
+            boolean isSameWithTargetSignature,
+            boolean isOdm,
+            boolean isOem)
+            throws IOException;
+
+    private static native FabricatedOverlayInfo getFabricatedOverlayInfo(
+            @NonNull String overlayPath) throws IOException;
+}
diff --git a/core/java/com/android/internal/content/om/TEST_MAPPING b/core/java/com/android/internal/content/om/TEST_MAPPING
index 4cb595b..98dadce7 100644
--- a/core/java/com/android/internal/content/om/TEST_MAPPING
+++ b/core/java/com/android/internal/content/om/TEST_MAPPING
@@ -7,6 +7,28 @@
           "include-filter": "com.android.internal.content."
         }
       ]
+    },
+    {
+      "name": "SelfTargetingOverlayDeviceTests"
+    }
+  ],
+  "presubmit-large": [
+    {
+      "name": "CtsContentTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "include-filter": "android.content.om.cts"
+        },
+        {
+          "include-filter": "android.content.res.loader.cts"
+        }
+      ]
     }
   ]
-}
\ No newline at end of file
+}
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/expresslog/OWNERS b/core/java/com/android/internal/expresslog/OWNERS
new file mode 100644
index 0000000..ee865b1
--- /dev/null
+++ b/core/java/com/android/internal/expresslog/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/stats/OWNERS
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index f600c36..52e7471 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -25,7 +25,9 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.RectF;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.text.Editable;
 import android.text.Selection;
 import android.text.method.KeyListener;
@@ -43,12 +45,16 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InsertGesture;
 import android.view.inputmethod.JoinOrSplitGesture;
+import android.view.inputmethod.PreviewableHandwritingGesture;
 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 +239,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 +270,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);
@@ -298,6 +323,13 @@
     }
 
     @Override
+    public boolean previewHandwritingGesture(
+            @NonNull PreviewableHandwritingGesture gesture,
+            @Nullable CancellationSignal cancellationSignal) {
+        return mTextView.previewHandwritingGesture(gesture, cancellationSignal);
+    }
+
+    @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         CharSequence editableText = mTextView.getText();
diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
index c62fba9..8cb568d 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
@@ -21,6 +21,7 @@
 import android.view.InputChannel;
 import android.view.MotionEvent;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputMethodSubtype;
 import android.window.ImeOnBackInvokedDispatcher;
@@ -39,7 +40,6 @@
     parcelable InitParams {
         IBinder token;
         IInputMethodPrivilegedOperations privilegedOperations;
-        int configChanges;
         int navigationBarFlags;
     }
 
@@ -69,9 +69,11 @@
 
     void setSessionEnabled(IInputMethodSession session, boolean enabled);
 
-    void showSoftInput(in IBinder showInputToken, int flags, in ResultReceiver resultReceiver);
+    void showSoftInput(in IBinder showInputToken, in @nullable ImeTracker.Token statsToken,
+            int flags, in ResultReceiver resultReceiver);
 
-    void hideSoftInput(in IBinder hideInputToken, int flags, in ResultReceiver resultReceiver);
+    void hideSoftInput(in IBinder hideInputToken, in @nullable ImeTracker.Token statsToken,
+            int flags, in ResultReceiver resultReceiver);
 
     void updateEditorToolType(int toolType);
 
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index 4babb70..f77e962 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.inputmethod;
 
 import android.net.Uri;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.infra.AndroidFuture;
@@ -41,7 +42,8 @@
     void switchToNextInputMethod(boolean onlyCurrentIme, in AndroidFuture future /* T=Boolean */);
     void shouldOfferSwitchingToNextInputMethod(in AndroidFuture future /* T=Boolean */);
     void notifyUserActionAsync();
-    void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible);
+    void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible,
+            in @nullable ImeTracker.Token statsToken);
     void onStylusHandwritingReady(int requestId, int pid);
     void resetStylusHandwriting(int requestId);
 }
diff --git a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
index ea5c9a3..65016c2 100644
--- a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
+++ b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
@@ -16,20 +16,16 @@
 
 package com.android.internal.inputmethod;
 
+import android.graphics.RectF;
 import android.os.Bundle;
+import android.os.ICancellationSignal;
 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 +90,11 @@
     void performPrivateCommand(in InputConnectionCommandHeader header, String action,
             in Bundle data);
 
-    void performHandwritingSelectGesture(in InputConnectionCommandHeader header,
-            in SelectGesture gesture, in ResultReceiver resultReceiver);
+    void performHandwritingGesture(in InputConnectionCommandHeader header,
+            in ParcelableHandwritingGesture 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 previewHandwritingGesture(in InputConnectionCommandHeader header,
+            in ParcelableHandwritingGesture gesture, in ICancellationSignal transport);
 
     void setComposingRegion(in InputConnectionCommandHeader header, int start, int end);
 
@@ -130,6 +111,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/ImeTracingServerImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
index 2a242a5..edd74f6 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
@@ -138,7 +138,7 @@
     private void writeTracesToFilesLocked() {
         try {
             long timeOffsetNs =
-                    TimeUnit.NANOSECONDS.convert(System.currentTimeMillis(), TimeUnit.NANOSECONDS)
+                    TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
                     - SystemClock.elapsedRealtimeNanos();
 
             ProtoOutputStream clientsProto = new ProtoOutputStream();
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 67c2103..66e3333 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -25,6 +25,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.View;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.GuardedBy;
@@ -100,7 +101,7 @@
     }
 
     /**
-     * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatusAsync(int, int}.
+     * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatusAsync(int, int)}.
      *
      * @param vis visibility flags
      * @param backDisposition disposition flags
@@ -250,7 +251,7 @@
     }
 
     /**
-     * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, IVoidResultCallback)}
+     * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, int, AndroidFuture)}
      *
      * @param flags additional operating flags
      * @param reason the reason to hide soft input
@@ -316,7 +317,7 @@
 
     /**
      * Calls {@link IInputMethodPrivilegedOperations#switchToNextInputMethod(boolean,
-     * IBooleanResultCallback)}
+     * AndroidFuture)}
      *
      * @param onlyCurrentIme {@code true} to switch to a {@link InputMethodSubtype} within the same
      *                       IME
@@ -375,22 +376,25 @@
     }
 
     /**
-     * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibilityAsync(IBinder, boolean)}.
+     * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibilityAsync(IBinder, boolean,
+     * ImeTracker.Token)}.
      *
      * @param showOrHideInputToken placeholder token that maps to window requesting
      *        {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} or
-     *        {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow
-     *        (IBinder, int)}
+     *        {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow(IBinder,
+     *        int)}
      * @param setVisible {@code true} to set IME visible, else hidden.
+     * @param statsToken the token tracking the current IME request or {@code null} otherwise.
      */
     @AnyThread
-    public void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible) {
+    public void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible,
+            @Nullable ImeTracker.Token statsToken) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             return;
         }
         try {
-            ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible);
+            ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible, statsToken);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
new file mode 100644
index 0000000..18bd6e5
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
@@ -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.
+ */
+
+package com.android.internal.inputmethod;
+
+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/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index b0d5922..75f0bf5 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -27,6 +27,7 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
@@ -90,13 +91,13 @@
 import android.annotation.NonNull;
 import android.annotation.UiThread;
 import android.annotation.WorkerThread;
-import android.app.ActivityThread;
 import android.content.Context;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
@@ -226,6 +227,7 @@
     public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63;
     public static final int CUJ_LOCKSCREEN_OCCLUSION = 64;
     public static final int CUJ_RECENTS_SCROLLING = 65;
+    public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
 
     private static final int NO_STATSD_LOGGING = -1;
 
@@ -300,6 +302,7 @@
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS,
     };
 
     private static class InstanceHolder {
@@ -389,7 +392,8 @@
             CUJ_SHADE_CLEAR_ALL,
             CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
             CUJ_LOCKSCREEN_OCCLUSION,
-            CUJ_RECENTS_SCROLLING
+            CUJ_RECENTS_SCROLLING,
+            CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -412,8 +416,7 @@
     @VisibleForTesting
     public InteractionJankMonitor(@NonNull HandlerThread worker) {
         // Check permission early.
-        DeviceConfig.enforceReadPermission(
-            ActivityThread.currentApplication().getApplicationContext(),
+        Settings.Config.enforceReadPermission(
             DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR);
 
         mRunningTrackers = new SparseArray<>();
@@ -906,6 +909,8 @@
                 return "LOCKSCREEN_OCCLUSION";
             case CUJ_RECENTS_SCROLLING:
                 return "RECENTS_SCROLLING";
+            case CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS:
+                return "LAUNCHER_APP_SWIPE_TO_RECENTS";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 696f0ff..556e146 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -403,7 +403,7 @@
      * Returns true if this instance only supports reading history.
      */
     public boolean isReadOnly() {
-        return mActiveFile == null;
+        return mActiveFile == null || mHistoryDir == null;
     }
 
     /**
@@ -1292,7 +1292,9 @@
                 && mHistoryLastWritten.batteryHealth == cur.batteryHealth
                 && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
                 && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
-                && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) {
+                && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage
+                && mHistoryLastWritten.measuredEnergyDetails == null
+                && mHistoryLastWritten.cpuUsageDetails == null) {
             // We can merge this new change in with the last one.  Merging is
             // allowed as long as only the states have changed, and within those states
             // as long as no bit has changed both between now and the last entry, as
@@ -1761,8 +1763,8 @@
      * Saves the accumulated history buffer in the active file, see {@link #getActiveFile()} .
      */
     public void writeHistory() {
-        if (mActiveFile == null) {
-            Slog.w(TAG, "writeHistory: no history file associated with this instance");
+        if (isReadOnly()) {
+            Slog.w(TAG, "writeHistory: this instance instance is read-only");
             return;
         }
 
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 0a29fc52..eb62cb0 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -735,7 +735,7 @@
     }
 
     protected boolean shouldRecordDetailedData() {
-        return mRandom.nextInt() % mPeriodicSamplingInterval == 0;
+        return mRandom.nextInt(mPeriodicSamplingInterval) == 0;
     }
 
     /**
diff --git a/core/java/com/android/internal/os/BinderLatencyObserver.java b/core/java/com/android/internal/os/BinderLatencyObserver.java
index e9d55db..1276fb9 100644
--- a/core/java/com/android/internal/os/BinderLatencyObserver.java
+++ b/core/java/com/android/internal/os/BinderLatencyObserver.java
@@ -236,7 +236,7 @@
     }
 
     protected boolean shouldKeepSample() {
-        return mRandom.nextInt() % mPeriodicSamplingInterval == 0;
+        return mRandom.nextInt(mPeriodicSamplingInterval) == 0;
     }
 
     /** Updates the sampling interval. */
diff --git a/core/java/com/android/internal/os/IBinaryTransparencyService.aidl b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
index 9be686a..a1ad5d5 100644
--- a/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
+++ b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
@@ -26,5 +26,7 @@
 interface IBinaryTransparencyService {
     String getSignedImageInfo();
 
-    Map getApexInfo();
+    List getApexInfo();
+
+    List getMeasurementsForAllPackages();
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java
index 2805dcc..0645eb7 100644
--- a/core/java/com/android/internal/os/LooperStats.java
+++ b/core/java/com/android/internal/os/LooperStats.java
@@ -290,7 +290,7 @@
     }
 
     protected boolean shouldCollectDetailedData() {
-        return ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+        return ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0;
     }
 
     private static class DispatchSession {
diff --git a/core/java/com/android/internal/os/ProcLocksReader.java b/core/java/com/android/internal/os/ProcLocksReader.java
index 2143bc1..9ddb8c7 100644
--- a/core/java/com/android/internal/os/ProcLocksReader.java
+++ b/core/java/com/android/internal/os/ProcLocksReader.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.os;
 
+import android.util.IntArray;
+
 import com.android.internal.util.ProcFileReader;
 
 import java.io.FileInputStream;
@@ -35,6 +37,7 @@
 public class ProcLocksReader {
     private final String mPath;
     private ProcFileReader mReader = null;
+    private IntArray mPids = new IntArray();
 
     public ProcLocksReader() {
         mPath = "/proc/locks";
@@ -51,9 +54,13 @@
     public interface ProcLocksReaderCallback {
         /**
          * Call the callback function of handleBlockingFileLocks().
-         * @param pid Each process that hold file locks blocking other processes.
+         * @param pids Each process that hold file locks blocking other processes.
+         *             pids[0] is the process blocking others
+         *             pids[1..n-1] are the processes being blocked
+         * NOTE: pids are cleared immediately after onBlockingFileLock() returns. If the caller
+         * needs to cache it, please make a copy, e.g. by calling pids.toArray().
          */
-        void onBlockingFileLock(int pid);
+        void onBlockingFileLock(IntArray pids);
     }
 
     /**
@@ -64,8 +71,7 @@
     public void handleBlockingFileLocks(ProcLocksReaderCallback callback) throws IOException {
         long last = -1;
         long id; // ordinal position of the lock in the list
-        int owner = -1; // the PID of the process that owns the lock
-        int pid = -1; // the PID of the process blocking others
+        int pid = -1; // the PID of the process being blocked
 
         if (mReader == null) {
             mReader = new ProcFileReader(new FileInputStream(mPath));
@@ -73,26 +79,49 @@
             mReader.rewind();
         }
 
+        mPids.clear();
         while (mReader.hasMoreData()) {
             id = mReader.nextLong(true); // lock id
             if (id == last) {
-                mReader.finishLine(); // blocked lock
-                if (pid < 0) {
-                    pid = owner; // get pid from the previous line
-                    callback.onBlockingFileLock(pid);
+                // blocked lock found
+                mReader.nextIgnored(); // ->
+                mReader.nextIgnored(); // lock type: POSIX?
+                mReader.nextIgnored(); // lock type: MANDATORY?
+                mReader.nextIgnored(); // lock type: RW?
+
+                pid = mReader.nextInt(); // pid
+                if (pid > 0) {
+                    mPids.add(pid);
                 }
-                continue;
+
+                mReader.finishLine();
             } else {
-                pid = -1; // a new lock
+                // process blocking lock and move on to a new lock
+                if (mPids.size() > 1) {
+                    callback.onBlockingFileLock(mPids);
+                    mPids.clear();
+                }
+
+                // new lock found
+                mReader.nextIgnored(); // lock type: POSIX?
+                mReader.nextIgnored(); // lock type: MANDATORY?
+                mReader.nextIgnored(); // lock type: RW?
+
+                pid = mReader.nextInt(); // pid
+                if (pid > 0) {
+                    if (mPids.size() == 0) {
+                        mPids.add(pid);
+                    } else {
+                        mPids.set(0, pid);
+                    }
+                }
+                mReader.finishLine();
+                last = id;
             }
-
-            mReader.nextIgnored(); // lock type: POSIX?
-            mReader.nextIgnored(); // lock type: MANDATORY?
-            mReader.nextIgnored(); // lock type: RW?
-
-            owner = mReader.nextInt(); // pid
-            mReader.finishLine();
-            last = id;
+        }
+        // The last unprocessed blocking lock immediately before EOF
+        if (mPids.size() > 1) {
+            callback.onBlockingFileLock(mPids);
         }
     }
 }
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/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 28b98d6..8a9445d 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -16,10 +16,14 @@
 
 package com.android.internal.os;
 
+import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
+
+import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.ApplicationErrorReport;
 import android.app.IActivityManager;
+import android.app.compat.CompatChanges;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.type.DefaultMimeMapFactory;
 import android.net.TrafficStats;
@@ -36,6 +40,7 @@
 
 import dalvik.system.RuntimeHooks;
 import dalvik.system.VMRuntime;
+import dalvik.system.ZipPathValidator;
 
 import libcore.content.type.MimeMap;
 
@@ -260,10 +265,31 @@
          */
         TrafficStats.attachSocketTagger();
 
+        /*
+         * Initialize the zip path validator callback depending on the targetSdk.
+         */
+        initZipPathValidatorCallback();
+
         initialized = true;
     }
 
     /**
+     * If targetSDK >= U: set the safe zip path validator callback which disallows dangerous zip
+     * entry names.
+     * Otherwise: clear the callback to the default validation.
+     *
+     * @hide
+     */
+    @TestApi
+    public static void initZipPathValidatorCallback() {
+        if (CompatChanges.isChangeEnabled(VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL)) {
+            ZipPathValidator.setCallback(new SafeZipPathValidatorCallback());
+        } else {
+            ZipPathValidator.clearCallback();
+        }
+    }
+
+    /**
      * Returns an HTTP user agent of the form
      * "Dalvik/1.1.0 (Linux; U; Android Eclair Build/MAIN)".
      */
diff --git a/core/java/com/android/internal/os/SafeZipPathValidatorCallback.java b/core/java/com/android/internal/os/SafeZipPathValidatorCallback.java
new file mode 100644
index 0000000..a6ee108
--- /dev/null
+++ b/core/java/com/android/internal/os/SafeZipPathValidatorCallback.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.internal.os;
+
+import android.annotation.NonNull;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.os.Build;
+
+import dalvik.system.ZipPathValidator;
+
+import java.io.File;
+import java.util.zip.ZipException;
+
+/**
+ * A child implementation of the {@link dalvik.system.ZipPathValidator.Callback} that removes the
+ * risk of zip path traversal vulnerabilities.
+ *
+ * @hide
+ */
+public class SafeZipPathValidatorCallback implements ZipPathValidator.Callback {
+    /**
+     * This change targets zip path traversal vulnerabilities by throwing
+     * {@link java.util.zip.ZipException} if zip path entries contain ".." or start with "/".
+     * <p>
+     * The exception will be thrown in {@link java.util.zip.ZipInputStream#getNextEntry} or
+     * {@link java.util.zip.ZipFile#ZipFile(String)}.
+     * <p>
+     * This validation is enabled for apps with targetSDK >= U.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final long VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL = 242716250L;
+
+    @Override
+    public void onZipEntryAccess(@NonNull String path) throws ZipException {
+        if (path.startsWith("/")) {
+            throw new ZipException("Invalid zip entry path: " + path);
+        }
+        if (path.contains("..")) {
+            // If the string does contain "..", break it down into its actual name elements to
+            // ensure it actually contains ".." as a name, not just a name like "foo..bar" or even
+            // "foo..", which should be fine.
+            File file = new File(path);
+            while (file != null) {
+                if (file.getName().equals("..")) {
+                    throw new ZipException("Invalid zip entry path: " + path);
+                }
+                file = file.getParentFile();
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index be3f172..a587834 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.content.Intent;
 import android.os.SystemClock;
 
 import com.android.internal.os.anr.AnrLatencyTracker;
@@ -39,7 +40,8 @@
             TimeoutKind.SERVICE_START,
             TimeoutKind.SERVICE_EXEC,
             TimeoutKind.CONTENT_PROVIDER,
-            TimeoutKind.APP_REGISTERED})
+            TimeoutKind.APP_REGISTERED,
+            TimeoutKind.SHORT_FGS_TIMEOUT})
 
     @Retention(RetentionPolicy.SOURCE)
     public @interface TimeoutKind {
@@ -50,6 +52,7 @@
         int SERVICE_EXEC = 5;
         int CONTENT_PROVIDER = 6;
         int APP_REGISTERED = 7;
+        int SHORT_FGS_TIMEOUT = 8;
     }
 
     /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
@@ -92,7 +95,17 @@
 
     /** Record for a broadcast receiver timeout. */
     @NonNull
-    public static TimeoutRecord forBroadcastReceiver(@NonNull String reason) {
+    public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent) {
+        String reason = "Broadcast of " + intent.toString();
+        return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
+    }
+
+    /** Record for a broadcast receiver timeout. */
+    @NonNull
+    public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent,
+            long timeoutDurationMs) {
+        String reason = "Broadcast of " + intent.toString() + ", waited " + timeoutDurationMs
+                + "ms";
         return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
     }
 
@@ -133,4 +146,10 @@
     public static TimeoutRecord forApp(@NonNull String reason) {
         return TimeoutRecord.endingApproximatelyNow(TimeoutKind.APP_REGISTERED, reason);
     }
+
+    /** Record for a "short foreground service" timeout. */
+    @NonNull
+    public static TimeoutRecord forShortFgsTimeout(String reason) {
+        return TimeoutRecord.endingNow(TimeoutKind.SHORT_FGS_TIMEOUT, reason);
+    }
 }
diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
index a182920..2748ded 100644
--- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
+++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -24,6 +24,7 @@
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT_NO_FOCUSED_WINDOW;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__START_FOREGROUND_SERVICE;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
 
@@ -400,6 +401,8 @@
                 return ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE;
             case TimeoutKind.CONTENT_PROVIDER:
                 return ANRLATENCY_REPORTED__ANR_TYPE__CONTENT_PROVIDER_NOT_RESPONDING;
+            case TimeoutKind.SHORT_FGS_TIMEOUT:
+                return ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
             default:
                 return ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
         }
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 3e988e6..145aeaf 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -2402,7 +2402,7 @@
             return;
         }
         final ThreadedRenderer renderer = getThreadedRenderer();
-        if (renderer != null) {
+        if (renderer != null && !CAPTION_ON_SHELL) {
             loadBackgroundDrawablesIfNeeded();
             WindowInsets rootInsets = getRootWindowInsets();
             mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer,
diff --git a/core/java/com/android/internal/power/ModemPowerProfile.java b/core/java/com/android/internal/power/ModemPowerProfile.java
index afea69a..a555ae3 100644
--- a/core/java/com/android/internal/power/ModemPowerProfile.java
+++ b/core/java/com/android/internal/power/ModemPowerProfile.java
@@ -158,11 +158,11 @@
 
     private static final SparseArray<String> MODEM_TX_LEVEL_NAMES = new SparseArray<>(5);
     static {
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_0, "0");
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_1, "1");
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_2, "2");
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_3, "3");
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_4, "4");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_0, "0");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_1, "1");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_2, "2");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_3, "3");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_4, "4");
     }
 
     private static final int[] MODEM_TX_LEVEL_MAP = new int[]{
@@ -418,7 +418,10 @@
         return Double.NaN;
     }
 
-    private static String keyToString(int key) {
+    /**
+     * Returns a human readable version of a key.
+     */
+    public static String keyToString(int key) {
         StringBuilder sb = new StringBuilder();
         final int drainType = key & MODEM_DRAIN_TYPE_MASK;
         appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES, drainType);
@@ -427,6 +430,7 @@
         if (drainType == MODEM_DRAIN_TYPE_TX) {
             final int txLevel = key & MODEM_TX_LEVEL_MASK;
             appendFieldToString(sb, "level", MODEM_TX_LEVEL_NAMES, txLevel);
+            sb.append(",");
         }
 
         final int ratType = key & MODEM_RAT_TYPE_MASK;
diff --git a/core/java/com/android/internal/protolog/BaseProtoLogImpl.java b/core/java/com/android/internal/protolog/BaseProtoLogImpl.java
index 83309fc..cd55d32 100644
--- a/core/java/com/android/internal/protolog/BaseProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/BaseProtoLogImpl.java
@@ -32,6 +32,7 @@
 import android.annotation.Nullable;
 import android.os.ShellCommand;
 import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
@@ -44,7 +45,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.IllegalFormatConversionException;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
 
@@ -108,10 +108,14 @@
             messageString = mViewerConfig.getViewerString(messageHash);
         }
         if (messageString != null) {
-            try {
-                message = String.format(messageString, args);
-            } catch (IllegalFormatConversionException ex) {
-                Slog.w(TAG, "Invalid ProtoLog format string.", ex);
+            if (args != null) {
+                try {
+                    message = TextUtils.formatSimple(messageString, args);
+                } catch (Exception ex) {
+                    Slog.w(TAG, "Invalid ProtoLog format string.", ex);
+                }
+            } else {
+                message = messageString;
             }
         }
         if (message == null) {
diff --git a/core/java/com/android/internal/protolog/common/LogDataType.java b/core/java/com/android/internal/protolog/common/LogDataType.java
index 651932a..c05824a 100644
--- a/core/java/com/android/internal/protolog/common/LogDataType.java
+++ b/core/java/com/android/internal/protolog/common/LogDataType.java
@@ -74,13 +74,10 @@
                         types.add(LogDataType.BOOLEAN);
                         break;
                     case 'd':
-                    case 'o':
                     case 'x':
                         types.add(LogDataType.LONG);
                         break;
                     case 'f':
-                    case 'e':
-                    case 'g':
                         types.add(LogDataType.DOUBLE);
                         break;
                     case 's':
diff --git a/core/java/com/android/internal/protolog/common/ProtoLog.java b/core/java/com/android/internal/protolog/common/ProtoLog.java
index 01cc1ed..93765cd 100644
--- a/core/java/com/android/internal/protolog/common/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLog.java
@@ -22,11 +22,12 @@
  * a messageString, which is a format string for the log message (has to be a string literal or
  * a concatenation of string literals) and a vararg array of parameters for the formatter.
  *
- * The syntax for the message string is a subset of {@code java.util.Formatter} syntax.
+ * The syntax for the message string depends on
+ * {@link android.text.TextUtils#formatSimple(String, Object...)}}.
  * Supported conversions:
  * %b - boolean
- * %d, %o and %x - integral type (Short, Integer or Long)
- * %f, %e and %g - floating point type (Float or Double)
+ * %d %x - integral type (Short, Integer or Long)
+ * %f - floating point type (Float or Double)
  * %s - string
  * %% - a literal percent character
  * The width and precision modifiers are supported, argument_index and flags are not.
diff --git a/core/java/com/android/internal/security/VerityUtils.java b/core/java/com/android/internal/security/VerityUtils.java
index 7f45c09..3ab11a8 100644
--- a/core/java/com/android/internal/security/VerityUtils.java
+++ b/core/java/com/android/internal/security/VerityUtils.java
@@ -17,6 +17,7 @@
 package com.android.internal.security;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Build;
 import android.os.SystemProperties;
 import android.system.Os;
@@ -41,6 +42,7 @@
 import java.nio.ByteOrder;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
@@ -77,17 +79,23 @@
         return filePath + FSVERITY_SIGNATURE_FILE_EXTENSION;
     }
 
-    /** Enables fs-verity for the file with a PKCS#7 detached signature file. */
-    public static void setUpFsverity(@NonNull String filePath, @NonNull String signaturePath)
+    /** Enables fs-verity for the file with an optional PKCS#7 detached signature file. */
+    public static void setUpFsverity(@NonNull String filePath, @Nullable String signaturePath)
             throws IOException {
-        if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) {
-            throw new SecurityException("Signature file is unexpectedly large: " + signaturePath);
+        byte[] rawSignature = null;
+        if (signaturePath != null) {
+            Path path = Paths.get(signaturePath);
+            if (Files.size(path) > MAX_SIGNATURE_FILE_SIZE_BYTES) {
+                throw new SecurityException("Signature file is unexpectedly large: "
+                        + signaturePath);
+            }
+            rawSignature = Files.readAllBytes(path);
         }
-        setUpFsverity(filePath, Files.readAllBytes(Paths.get(signaturePath)));
+        setUpFsverity(filePath, rawSignature);
     }
 
-    /** Enables fs-verity for the file with a PKCS#7 detached signature bytes. */
-    public static void setUpFsverity(@NonNull String filePath, @NonNull byte[] pkcs7Signature)
+    /** Enables fs-verity for the file with an optional PKCS#7 detached signature bytes. */
+    public static void setUpFsverity(@NonNull String filePath, @Nullable byte[] pkcs7Signature)
             throws IOException {
         // This will fail if the public key is not already in .fs-verity kernel keyring.
         int errno = enableFsverityNative(filePath, pkcs7Signature);
@@ -227,7 +235,7 @@
     }
 
     private static native int enableFsverityNative(@NonNull String filePath,
-            @NonNull byte[] pkcs7Signature);
+            @Nullable byte[] pkcs7Signature);
     private static native int measureFsverityNative(@NonNull String filePath,
             @NonNull byte[] digest);
     private static native int statxForFsverityNative(@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..db288c0 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);
 
     /**
@@ -325,4 +324,17 @@
 
     /** Dump protos from SystemUI. The proto definition is defined there */
     void dumpProto(in String[] args, in ParcelFileDescriptor pfd);
+
+    /** Shows rear display educational dialog */
+    void showRearDisplayDialog(int currentBaseState);
+
+    /** Called when requested to go to fullscreen from the active split app. */
+    void goToFullscreenFromSplit();
+
+    /**
+     * Enters stage split from a current running app.
+     *
+     * @param leftOrTop indicates where the stage split is.
+     */
+    void enterStageSplitFromRunningApp(boolean leftOrTop);
 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index ef8f2db..8f04cda 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.
@@ -226,4 +226,7 @@
 
     /** Unregisters a nearby media devices provider. */
     void unregisterNearbyMediaDevicesProvider(in INearbyMediaDevicesProvider provider);
+
+    /** Shows rear display educational dialog */
+    void showRearDisplayDialog(int currentBaseState);
 }
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/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 4b1753a..9cb2e68 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -17,7 +17,7 @@
 package com.android.internal.telephony;
 
 import android.telephony.BarringInfo;
-import android.telephony.CallAttributes;
+import android.telephony.CallState;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
 import android.telephony.DataConnectionRealTimeInfo;
@@ -62,7 +62,7 @@
     void onPhoneCapabilityChanged(in PhoneCapability capability);
     void onActiveDataSubIdChanged(in int subId);
     void onRadioPowerStateChanged(in int state);
-    void onCallAttributesChanged(in CallAttributes callAttributes);
+    void onCallStatesChanged(in List<CallState> callStateList);
     @SuppressWarnings(value={"untyped-collection"})
     void onEmergencyNumberListChanged(in Map emergencyNumberList);
     void onOutgoingEmergencyCall(in EmergencyNumber placedEmergencyNumber, int subscriptionId);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index c7fa757..7ba2686 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -66,8 +66,8 @@
     void notifyCellLocationForSubscriber(in int subId, in CellIdentity cellLocation);
     @UnsupportedAppUsage
     void notifyCellInfo(in List<CellInfo> cellInfo);
-    void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
-            int foregroundCallState, int backgroundCallState);
+    void notifyPreciseCallState(int phoneId, int subId, in int[] callStates, in String[] imsCallIds,
+            in int[] imsCallServiceTypes, in int[] imsCallTypes);
     void notifyDisconnectCause(int phoneId, int subId, int disconnectCause,
             int preciseDisconnectCause);
     void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo);
diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java
index 1eb446e..f974d9d 100644
--- a/core/java/com/android/internal/usb/DumpUtils.java
+++ b/core/java/com/android/internal/usb/DumpUtils.java
@@ -174,6 +174,9 @@
         } else {
             dump.write("supported_modes", UsbPortProto.SUPPORTED_MODES, UsbPort.modeToString(mode));
         }
+        dump.write("supports_compliance_warnings",
+                UsbPortProto.SUPPORTS_COMPLIANCE_WARNINGS,
+                port.supportsComplianceWarnings());
 
         dump.end(token);
     }
@@ -250,6 +253,8 @@
                 status.isPowerTransferLimited());
         dump.write("usb_power_brick_status", UsbPortStatusProto.USB_POWER_BRICK_STATUS,
                 UsbPort.powerBrickConnectionStatusToString(status.getPowerBrickConnectionStatus()));
+        dump.write("compliance_warning_status", UsbPortStatusProto.COMPLIANCE_WARNINGS_STRING,
+                UsbPort.complianceWarningsToString(status.getComplianceWarnings()));
         dump.end(token);
     }
 }
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 8fcb6d5..4b7b91c 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -468,7 +468,7 @@
         boolean shouldSample;
         int traceThreshold;
         synchronized (mLock) {
-            shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+            shouldSample = ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0;
             traceThreshold = mTraceThresholdPerAction[action];
         }
 
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index fe77236..2ac4309 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.view;
 
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.input.InputManager;
 import android.os.Bundle;
@@ -31,6 +32,7 @@
 import android.view.PointerIcon;
 import android.view.ScrollCaptureResponse;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
 import android.window.ClientWindowFrames;
 
 import com.android.internal.os.IResultReceiver;
@@ -66,11 +68,13 @@
     }
 
     @Override
-    public void showInsets(@InsetsType int types, boolean fromIme) {
+    public void showInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
     }
 
     @Override
-    public void hideInsets(@InsetsType int types, boolean fromIme) {
+    public void hideInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
     }
 
     @Override
diff --git a/core/java/com/android/internal/view/BaseSurfaceHolder.java b/core/java/com/android/internal/view/BaseSurfaceHolder.java
index 32ce0fe..1ae1307 100644
--- a/core/java/com/android/internal/view/BaseSurfaceHolder.java
+++ b/core/java/com/android/internal/view/BaseSurfaceHolder.java
@@ -241,4 +241,4 @@
         mSurfaceFrame.right = width;
         mSurfaceFrame.bottom = height;
     }
-};
+}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 423642a..00bc3f2 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.view;
 
 import android.os.ResultReceiver;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 import android.view.inputmethod.EditorInfo;
@@ -54,11 +55,12 @@
             + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
     InputMethodSubtype getLastInputMethodSubtype(int userId);
 
-    boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, int flags,
-            int lastClickToolType, in @nullable ResultReceiver resultReceiver, int reason);
-    boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, int flags,
+    boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
+            in @nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
             in @nullable ResultReceiver resultReceiver, int reason);
-
+    boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
+            in @nullable ImeTracker.Token statsToken, int flags,
+            in @nullable ResultReceiver resultReceiver, int reason);
     // If windowToken is null, this just does startInput().  Otherwise this reports that a window
     // has gained focus, and if 'editorInfo' is non-null then also does startInput.
     // @NonNull
@@ -81,8 +83,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 +148,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/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 5b08bb1..6063c90 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -54,6 +54,12 @@
     // TODO(b/183140900) split store escrow key errors into detailed ones.
 
     /**
+     * This is called when Weaver is guaranteed to be available (if the device supports Weaver).
+     * It does any synthetic password related work that was delayed from earlier in the boot.
+     */
+    public abstract void onThirdPartyAppsStarted();
+
+    /**
      * Unlocks the credential-encrypted storage for the given user if the user is not secured, i.e.
      * doesn't have an LSKF.
      * <p>
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index c50abb3..a43f0b3 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -34,10 +34,14 @@
         "-Wno-error=deprecated-declarations",
         "-Wunused",
         "-Wunreachable-code",
+
+        "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
     ],
 
     cppflags: ["-Wno-conversion-null"],
 
+    cpp_std: "gnu++20",
+
     srcs: [
         "android_animation_PropertyValuesHolder.cpp",
         "android_os_SystemClock.cpp",
@@ -190,6 +194,7 @@
                 "android_hardware_display_DisplayManagerGlobal.cpp",
                 "android_hardware_display_DisplayViewport.cpp",
                 "android_hardware_HardwareBuffer.cpp",
+                "android_hardware_OverlayProperties.cpp",
                 "android_hardware_SensorManager.cpp",
                 "android_hardware_SerialPort.cpp",
                 "android_hardware_SyncFence.cpp",
@@ -212,6 +217,8 @@
                 "android_content_res_ResourceTimer.cpp",
                 "android_security_Scrypt.cpp",
                 "com_android_internal_content_om_OverlayConfig.cpp",
+                "com_android_internal_content_om_OverlayManagerImpl.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 +254,7 @@
                 "libscrypt_static",
                 "libstatssocket_lazy",
                 "libskia",
+                "libtextclassifier_hash_static",
             ],
 
             shared_libs: [
@@ -329,6 +337,10 @@
             header_libs: [
                 "bionic_libc_platform_headers",
                 "dnsproxyd_protocol_headers",
+                "libtextclassifier_hash_headers",
+            ],
+            runtime_libs: [
+                "libidmap2",
             ],
         },
         host: {
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 9da28a3..6ceffde 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -19,6 +19,7 @@
 #define LOG_NDEBUG 1
 
 #include <android-base/macros.h>
+#include <android-base/parsebool.h>
 #include <android-base/properties.h>
 #include <android/graphics/jni_runtime.h>
 #include <android_runtime/AndroidRuntime.h>
@@ -52,6 +53,8 @@
 using namespace android;
 using android::base::GetBoolProperty;
 using android::base::GetProperty;
+using android::base::ParseBool;
+using android::base::ParseBoolResult;
 
 extern int register_android_os_Binder(JNIEnv* env);
 extern int register_android_os_Process(JNIEnv* env);
@@ -79,6 +82,7 @@
 extern int register_android_hardware_camera2_utils_SurfaceUtils(JNIEnv* env);
 extern int register_android_hardware_display_DisplayManagerGlobal(JNIEnv* env);
 extern int register_android_hardware_HardwareBuffer(JNIEnv *env);
+extern int register_android_hardware_OverlayProperties(JNIEnv* env);
 extern int register_android_hardware_SensorManager(JNIEnv *env);
 extern int register_android_hardware_SerialPort(JNIEnv *env);
 extern int register_android_hardware_SyncFence(JNIEnv* env);
@@ -194,6 +198,8 @@
 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_content_om_OverlayManagerImpl(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);
@@ -639,7 +645,6 @@
     char jitmaxsizeOptsBuf[sizeof("-Xjitmaxsize:")-1 + PROPERTY_VALUE_MAX];
     char jitinitialsizeOptsBuf[sizeof("-Xjitinitialsize:")-1 + PROPERTY_VALUE_MAX];
     char jitthresholdOptsBuf[sizeof("-Xjitthreshold:")-1 + PROPERTY_VALUE_MAX];
-    char useJitProfilesOptsBuf[sizeof("-Xjitsaveprofilinginfo:")-1 + PROPERTY_VALUE_MAX];
     char jitprithreadweightOptBuf[sizeof("-Xjitprithreadweight:")-1 + PROPERTY_VALUE_MAX];
     char jittransitionweightOptBuf[sizeof("-Xjittransitionweight:")-1 + PROPERTY_VALUE_MAX];
     char hotstartupsamplesOptsBuf[sizeof("-Xps-hot-startup-method-samples:")-1 + PROPERTY_VALUE_MAX];
@@ -702,17 +707,24 @@
 
     // Read if we are using the profile configuration, do this at the start since the last ART args
     // take precedence.
-    property_get("dalvik.vm.profilebootclasspath", propBuf, "");
-    std::string profile_boot_class_path_flag = propBuf;
-    // Empty means the property is unset and we should default to the phenotype property.
-    // The possible values are {"true", "false", ""}
-    if (profile_boot_class_path_flag.empty()) {
-        profile_boot_class_path_flag = server_configurable_flags::GetServerConfigurableFlag(
-                RUNTIME_NATIVE_BOOT_NAMESPACE,
-                PROFILE_BOOT_CLASS_PATH,
-                /*default_value=*/ "");
+    std::string profile_boot_class_path_flag =
+            server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE,
+                                                                 PROFILE_BOOT_CLASS_PATH,
+                                                                 /*default_value=*/"");
+    bool profile_boot_class_path;
+    switch (ParseBool(profile_boot_class_path_flag)) {
+        case ParseBoolResult::kError:
+            // Default to the system property.
+            profile_boot_class_path =
+                    GetBoolProperty("dalvik.vm.profilebootclasspath", /*default_value=*/false);
+            break;
+        case ParseBoolResult::kTrue:
+            profile_boot_class_path = true;
+            break;
+        case ParseBoolResult::kFalse:
+            profile_boot_class_path = false;
+            break;
     }
-    const bool profile_boot_class_path = (profile_boot_class_path_flag == "true");
     if (profile_boot_class_path) {
         addOption("-Xcompiler-option");
         addOption("--count-hotness-in-compiled-code");
@@ -847,10 +859,7 @@
     parseRuntimeOption("dalvik.vm.jitpthreadpriority",
                        jitpthreadpriorityOptsBuf,
                        "-Xjitpthreadpriority:");
-    property_get("dalvik.vm.usejitprofiles", useJitProfilesOptsBuf, "");
-    if (strcmp(useJitProfilesOptsBuf, "true") == 0) {
-        addOption("-Xjitsaveprofilinginfo");
-    }
+    addOption("-Xjitsaveprofilinginfo");
 
     parseRuntimeOption("dalvik.vm.jitprithreadweight",
                        jitprithreadweightOptBuf,
@@ -1575,6 +1584,8 @@
         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_content_om_OverlayManagerImpl),
+        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),
@@ -1591,6 +1602,7 @@
         REG_JNI(register_android_hardware_camera2_utils_SurfaceUtils),
         REG_JNI(register_android_hardware_display_DisplayManagerGlobal),
         REG_JNI(register_android_hardware_HardwareBuffer),
+        REG_JNI(register_android_hardware_OverlayProperties),
         REG_JNI(register_android_hardware_SensorManager),
         REG_JNI(register_android_hardware_SerialPort),
         REG_JNI(register_android_hardware_SyncFence),
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index a068008..53594e1 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -29,7 +29,7 @@
 per-file android_util_AssetManager* = file:/core/java/android/content/res/OWNERS
 per-file android_util_StringBlock* = file:/core/java/android/content/res/OWNERS
 per-file android_util_XmlBlock* = file:/core/java/android/content/res/OWNERS
-per-file com_android_internal_content_om_OverlayConfig* = file:/core/java/android/content/res/OWNERS
+per-file com_android_internal_content_om_Overlay* = file:/core/java/android/content/res/OWNERS
 
 # Binder related things
 per-file android_os_Parcel* = file:platform/frameworks/native:/libs/binder/OWNERS
@@ -99,3 +99,5 @@
 # PM
 per-file com_android_internal_content_* = file:/PACKAGE_MANAGER_OWNERS
 
+# Stats/expresslog
+per-file *expresslog* = file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/jni/TEST_MAPPING b/core/jni/TEST_MAPPING
index 004c30e..2844856 100644
--- a/core/jni/TEST_MAPPING
+++ b/core/jni/TEST_MAPPING
@@ -11,6 +11,29 @@
         }
       ],
       "file_patterns": ["CharsetUtils|FastData"]
+    },
+    {
+      "name": "SelfTargetingOverlayDeviceTests",
+      "file_patterns": ["Overlay"]
+    }
+  ],
+  "presubmit-large": [
+    {
+      "name": "CtsContentTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "include-filter": "android.content.om.cts"
+        },
+        {
+          "include-filter": "android.content.res.loader.cts"
+        }
+      ]
     }
   ]
 }
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index a8d7231..e9ada23 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -96,7 +96,7 @@
   }
 
   bool ForEachFile(const std::string& /* root_path */,
-                   const std::function<void(const StringPiece&, FileType)>& /* f */) const {
+                   android::base::function_ref<void(StringPiece, FileType)> /* f */) const {
     return true;
   }
 
@@ -402,7 +402,7 @@
     return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
 }
 
-static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+static jboolean NativeIsUpToDate(jlong ptr) {
     auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
     auto apk_assets = scoped_apk_assets->get();
     return apk_assets->IsUpToDate() ? JNI_TRUE : JNI_FALSE;
@@ -500,24 +500,28 @@
 
 // JNI registration.
 static const JNINativeMethod gApkAssetsMethods[] = {
-    {"nativeLoad", "(ILjava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
-     (void*)NativeLoad},
-    {"nativeLoadEmpty", "(ILandroid/content/res/loader/AssetsProvider;)J", (void*)NativeLoadEmpty},
-    {"nativeLoadFd",
-     "(ILjava/io/FileDescriptor;Ljava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
-     (void*)NativeLoadFromFd},
-    {"nativeLoadFdOffsets",
-     "(ILjava/io/FileDescriptor;Ljava/lang/String;JJILandroid/content/res/loader/AssetsProvider;)J",
-     (void*)NativeLoadFromFdOffset},
-    {"nativeDestroy", "(J)V", (void*)NativeDestroy},
-    {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
-    {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName},
-    {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
-    {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
-    {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
-    {"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
-     (void*)NativeGetOverlayableInfo},
-    {"nativeDefinesOverlayable", "(J)Z", (void*)NativeDefinesOverlayable},
+        {"nativeLoad", "(ILjava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
+         (void*)NativeLoad},
+        {"nativeLoadEmpty", "(ILandroid/content/res/loader/AssetsProvider;)J",
+         (void*)NativeLoadEmpty},
+        {"nativeLoadFd",
+         "(ILjava/io/FileDescriptor;Ljava/lang/String;ILandroid/content/res/loader/"
+         "AssetsProvider;)J",
+         (void*)NativeLoadFromFd},
+        {"nativeLoadFdOffsets",
+         "(ILjava/io/FileDescriptor;Ljava/lang/String;JJILandroid/content/res/loader/"
+         "AssetsProvider;)J",
+         (void*)NativeLoadFromFdOffset},
+        {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+        {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
+        {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName},
+        {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
+        // @CriticalNative
+        {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
+        {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
+        {"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
+         (void*)NativeGetOverlayableInfo},
+        {"nativeDefinesOverlayable", "(J)Z", (void*)NativeDefinesOverlayable},
 };
 
 int register_android_content_res_ApkAssets(JNIEnv* env) {
diff --git a/core/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp
new file mode 100644
index 0000000..a96af86
--- /dev/null
+++ b/core/jni/android_hardware_OverlayProperties.cpp
@@ -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.
+ */
+
+#define LOG_TAG "OverlayProperties"
+// #define LOG_NDEBUG 0
+
+#include <android/gui/OverlayProperties.h>
+#include <binder/Parcel.h>
+#include <gui/SurfaceComposerClient.h>
+#include <nativehelper/JNIHelp.h>
+
+#include "android_os_Parcel.h"
+#include "core_jni_helpers.h"
+#include "jni.h"
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+// Types
+// ----------------------------------------------------------------------------
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+} gOverlayPropertiesClassInfo;
+
+// ----------------------------------------------------------------------------
+// OverlayProperties lifecycle
+// ----------------------------------------------------------------------------
+
+static void destroyOverlayProperties(gui::OverlayProperties* overlayProperties) {
+    delete overlayProperties;
+}
+
+static jlong android_hardware_OverlayProperties_getDestructor(JNIEnv*, jclass) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyOverlayProperties));
+}
+
+//----------------------------------------------------------------------------
+// Accessors
+// ----------------------------------------------------------------------------
+
+static jboolean android_hardware_OverlayProperties_supportFp16ForHdr(JNIEnv* env, jobject thiz,
+                                                                     jlong nativeObject) {
+    gui::OverlayProperties* properties = reinterpret_cast<gui::OverlayProperties*>(nativeObject);
+    if (properties != nullptr) {
+        for (const auto& i : properties->combinations) {
+            if (std::find(i.pixelFormats.begin(), i.pixelFormats.end(),
+                          static_cast<int32_t>(HAL_PIXEL_FORMAT_RGBA_FP16)) !=
+                        i.pixelFormats.end() &&
+                std::find(i.dataspaces.begin(), i.dataspaces.end(),
+                          static_cast<int32_t>(HAL_DATASPACE_BT2020_PQ)) != i.dataspaces.end()) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+// ----------------------------------------------------------------------------
+// Serialization
+// ----------------------------------------------------------------------------
+
+static void android_hardware_OverlayProperties_write(JNIEnv* env, jclass, jlong nativeObject,
+                                                     jobject dest) {
+    Parcel* parcel = parcelForJavaObject(env, dest);
+    if (parcel == nullptr) {
+        jniThrowNullPointerException(env, nullptr);
+        return;
+    }
+    gui::OverlayProperties* overlayProperties =
+            reinterpret_cast<gui::OverlayProperties*>(nativeObject);
+    if (overlayProperties != nullptr) {
+        overlayProperties->writeToParcel(parcel);
+    }
+}
+
+static long android_hardware_OverlayProperties_read(JNIEnv* env, jclass, jobject in) {
+    Parcel* parcel = parcelForJavaObject(env, in);
+    if (parcel == nullptr) {
+        jniThrowNullPointerException(env, nullptr);
+        return 0;
+    }
+    gui::OverlayProperties* overlayProperties = new gui::OverlayProperties;
+    if (overlayProperties->readFromParcel(parcel) != NO_ERROR) {
+        delete overlayProperties;
+        return 0;
+    }
+    return reinterpret_cast<jlong>(overlayProperties);
+}
+
+// ----------------------------------------------------------------------------
+// Public functions
+// ----------------------------------------------------------------------------
+
+namespace android {
+
+jobject android_hardware_OverlayProperties_convertToJavaObject(
+        JNIEnv* env, gui::OverlayProperties* overlayProperties) {
+    jobject overlayPropertiesObj =
+            env->NewObject(gOverlayPropertiesClassInfo.clazz, gOverlayPropertiesClassInfo.ctor,
+                           reinterpret_cast<jlong>(overlayProperties));
+    return overlayPropertiesObj;
+}
+
+}; // namespace android
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/hardware/OverlayProperties";
+
+// clang-format off
+static const JNINativeMethod gMethods[] = {
+    { "nGetDestructor", "()J", (void*) android_hardware_OverlayProperties_getDestructor },
+    { "nSupportFp16ForHdr",  "(J)Z",
+            (void*)  android_hardware_OverlayProperties_supportFp16ForHdr },
+    { "nWriteOverlayPropertiesToParcel", "(JLandroid/os/Parcel;)V",
+            (void*) android_hardware_OverlayProperties_write },
+    { "nReadOverlayPropertiesFromParcel", "(Landroid/os/Parcel;)J",
+            (void*) android_hardware_OverlayProperties_read },
+};
+// clang-format on
+
+int register_android_hardware_OverlayProperties(JNIEnv* env) {
+    int err = RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
+
+    jclass clazz = FindClassOrDie(env, "android/hardware/OverlayProperties");
+    gOverlayPropertiesClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gOverlayPropertiesClassInfo.ctor =
+            GetMethodIDOrDie(env, gOverlayPropertiesClassInfo.clazz, "<init>", "(J)V");
+
+    return err;
+}
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index cb97698..939a0e4 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -243,6 +243,23 @@
     }
 }
 
+static void nativeGetRuntimeSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jint deviceId,
+                                    jobject sensorList) {
+    SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+    const ListOffsets &listOffsets(gListOffsets);
+
+    Vector<Sensor> nativeList;
+
+    mgr->getRuntimeSensorList(deviceId, nativeList);
+
+    ALOGI("DYNS native SensorManager.getRuntimeSensorList return %zu sensors", nativeList.size());
+    for (size_t i = 0; i < nativeList.size(); ++i) {
+        jobject sensor = translateNativeSensorToJavaSensor(env, NULL, nativeList[i]);
+        // add to list
+        env->CallBooleanMethod(sensorList, listOffsets.add, sensor);
+    }
+}
+
 static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong sensorManager) {
     SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
     return mgr->isDataInjectionEnabled();
@@ -503,40 +520,26 @@
 //----------------------------------------------------------------------------
 
 static const JNINativeMethod gSystemSensorManagerMethods[] = {
-    {"nativeClassInit",
-            "()V",
-            (void*)nativeClassInit },
-    {"nativeCreate",
-             "(Ljava/lang/String;)J",
-             (void*)nativeCreate },
+        {"nativeClassInit", "()V", (void *)nativeClassInit},
+        {"nativeCreate", "(Ljava/lang/String;)J", (void *)nativeCreate},
 
-    {"nativeGetSensorAtIndex",
-            "(JLandroid/hardware/Sensor;I)Z",
-            (void*)nativeGetSensorAtIndex },
+        {"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
+         (void *)nativeGetSensorAtIndex},
 
-    {"nativeGetDynamicSensors",
-            "(JLjava/util/List;)V",
-            (void*)nativeGetDynamicSensors },
+        {"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors},
 
-    {"nativeIsDataInjectionEnabled",
-            "(J)Z",
-            (void*)nativeIsDataInjectionEnabled },
+        {"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors},
 
-    {"nativeCreateDirectChannel",
-            "(JJIILandroid/hardware/HardwareBuffer;)I",
-            (void*)nativeCreateDirectChannel },
+        {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled},
 
-    {"nativeDestroyDirectChannel",
-            "(JI)V",
-            (void*)nativeDestroyDirectChannel },
+        {"nativeCreateDirectChannel", "(JJIILandroid/hardware/HardwareBuffer;)I",
+         (void *)nativeCreateDirectChannel},
 
-    {"nativeConfigDirectChannel",
-            "(JIII)I",
-            (void*)nativeConfigDirectChannel },
+        {"nativeDestroyDirectChannel", "(JI)V", (void *)nativeDestroyDirectChannel},
 
-    {"nativeSetOperationParameter",
-            "(JII[F[I)I",
-            (void*)nativeSetOperationParameter },
+        {"nativeConfigDirectChannel", "(JIII)I", (void *)nativeConfigDirectChannel},
+
+        {"nativeSetOperationParameter", "(JII[F[I)I", (void *)nativeSetOperationParameter},
 };
 
 static const JNINativeMethod gBaseEventQueueMethods[] = {
diff --git a/core/jni/android_hardware_display_DisplayViewport.cpp b/core/jni/android_hardware_display_DisplayViewport.cpp
index 03432e9..7f630cb 100644
--- a/core/jni/android_hardware_display_DisplayViewport.cpp
+++ b/core/jni/android_hardware_display_DisplayViewport.cpp
@@ -61,7 +61,8 @@
 
     viewport->displayId = env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId);
     viewport->isActive = env->GetBooleanField(viewportObj, gDisplayViewportClassInfo.isActive);
-    viewport->orientation = env->GetIntField(viewportObj, gDisplayViewportClassInfo.orientation);
+    jint orientation = env->GetIntField(viewportObj, gDisplayViewportClassInfo.orientation);
+    viewport->orientation = static_cast<ui::Rotation>(orientation);
     viewport->deviceWidth = env->GetIntField(viewportObj, gDisplayViewportClassInfo.deviceWidth);
     viewport->deviceHeight = env->GetIntField(viewportObj, gDisplayViewportClassInfo.deviceHeight);
 
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index 0e6b587..962b501 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -45,8 +45,10 @@
 #define ENCODING_MPEGH_BL_L4 24
 #define ENCODING_MPEGH_LC_L3 25
 #define ENCODING_MPEGH_LC_L4 26
-#define ENCODING_DTS_UHD 27
+#define ENCODING_DTS_UHD_P1 27
 #define ENCODING_DRA 28
+#define ENCODING_DTS_HD_MA 29
+#define ENCODING_DTS_UHD_P2 30
 
 #define ENCODING_INVALID    0
 #define ENCODING_DEFAULT    1
@@ -112,10 +114,14 @@
         return AUDIO_FORMAT_MPEGH_LC_L3;
     case ENCODING_MPEGH_LC_L4:
         return AUDIO_FORMAT_MPEGH_LC_L4;
-    case ENCODING_DTS_UHD:
+    case ENCODING_DTS_UHD_P1:
         return AUDIO_FORMAT_DTS_UHD;
     case ENCODING_DRA:
         return AUDIO_FORMAT_DRA;
+    case ENCODING_DTS_HD_MA:
+        return AUDIO_FORMAT_DTS_HD_MA;
+    case ENCODING_DTS_UHD_P2:
+        return AUDIO_FORMAT_DTS_UHD_P2;
     default:
         return AUDIO_FORMAT_INVALID;
     }
@@ -186,9 +192,13 @@
     case AUDIO_FORMAT_MPEGH_LC_L4:
         return ENCODING_MPEGH_LC_L4;
     case AUDIO_FORMAT_DTS_UHD:
-        return ENCODING_DTS_UHD;
+        return ENCODING_DTS_UHD_P1;
     case AUDIO_FORMAT_DRA:
         return ENCODING_DRA;
+    case AUDIO_FORMAT_DTS_HD_MA:
+        return ENCODING_DTS_HD_MA;
+    case AUDIO_FORMAT_DTS_UHD_P2:
+        return ENCODING_DTS_UHD_P2;
     case AUDIO_FORMAT_DEFAULT:
         return ENCODING_DEFAULT;
     default:
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
index d05a24f..ac1401d 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -40,6 +40,7 @@
 typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
 typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
 typedef void (*APH_closeSession)(APerformanceHintSession* session);
+typedef void (*APH_sendHint)(APerformanceHintSession*, int32_t);
 
 bool gAPerformanceHintBindingInitialized = false;
 APH_getManager gAPH_getManagerFn = nullptr;
@@ -48,6 +49,7 @@
 APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
 APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
 APH_closeSession gAPH_closeSessionFn = nullptr;
+APH_sendHint gAPH_sendHintFn = nullptr;
 
 void ensureAPerformanceHintBindingInitialized() {
     if (gAPerformanceHintBindingInitialized) return;
@@ -88,6 +90,11 @@
     LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
                         "Failed to find required symbol APerformanceHint_closeSession!");
 
+    gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint");
+    LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr,
+                        "Failed to find required symbol "
+                        "APerformanceHint_sendHint!");
+
     gAPerformanceHintBindingInitialized = true;
 }
 
@@ -138,6 +145,11 @@
     gAPH_closeSessionFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr));
 }
 
+static void nativeSendHint(JNIEnv* env, jclass clazz, jlong nativeSessionPtr, jint hint) {
+    ensureAPerformanceHintBindingInitialized();
+    gAPH_sendHintFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr), hint);
+}
+
 static const JNINativeMethod gPerformanceHintMethods[] = {
         {"nativeAcquireManager", "()J", (void*)nativeAcquireManager},
         {"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos},
@@ -145,6 +157,7 @@
         {"nativeUpdateTargetWorkDuration", "(JJ)V", (void*)nativeUpdateTargetWorkDuration},
         {"nativeReportActualWorkDuration", "(JJ)V", (void*)nativeReportActualWorkDuration},
         {"nativeCloseSession", "(J)V", (void*)nativeCloseSession},
+        {"nativeSendHint", "(JI)V", (void*)nativeSendHint},
 };
 
 int register_android_os_PerformanceHintManager(JNIEnv* env) {
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index 571a8e2..b6bf617 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -126,6 +126,7 @@
     addHyphenator("nn", 2, 2);  // Norwegian Nynorsk
     addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Oriya
     addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Punjabi
+    addHyphenator("pl", 2, 2);  // Polish
     addHyphenator("pt", 2, 3);  // Portuguese
     addHyphenator("ru", 2, 2);  // Russian
     addHyphenator("sk", 2, 2);  // Slovak
@@ -141,7 +142,6 @@
     // Following two hyphenators do not have pattern files but there is some special logic based on
     // language.
     addHyphenatorWithoutPatternFile("ca", 2, 2);  // Catalan
-    addHyphenatorWithoutPatternFile("pl", 2, 2);  // Polish
 
     // English locales that fall back to en-US. The data is from CLDR. It's all English locales,
     // minus the locales whose parent is en-001 (from supplementalData.xml, under <parentLocales>).
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 9f88f33..a7c7d0b 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -25,6 +25,7 @@
 #include <inttypes.h>
 #include <mutex>
 #include <stdio.h>
+#include <string>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -880,7 +881,7 @@
         case FAILED_TRANSACTION: {
             ALOGE("!!! FAILED BINDER TRANSACTION !!!  (parcel size = %d)", parcelSize);
             const char* exceptionToThrow;
-            char msg[128];
+            std::string msg;
             // TransactionTooLargeException is a checked exception, only throw from certain methods.
             // TODO(b/28321379): Transaction size is the most common cause for FAILED_TRANSACTION
             //        but it is not the only one.  The Binder driver can return BR_FAILED_REPLY
@@ -890,7 +891,7 @@
             if (canThrowRemoteException && parcelSize > 200*1024) {
                 // bona fide large payload
                 exceptionToThrow = "android/os/TransactionTooLargeException";
-                snprintf(msg, sizeof(msg)-1, "data parcel size %d bytes", parcelSize);
+                msg = base::StringPrintf("data parcel size %d bytes", parcelSize);
             } else {
                 // Heuristic: a payload smaller than this threshold "shouldn't" be too
                 // big, so it's probably some other, more subtle problem.  In practice
@@ -899,11 +900,10 @@
                 exceptionToThrow = (canThrowRemoteException)
                         ? "android/os/DeadObjectException"
                         : "java/lang/RuntimeException";
-                snprintf(msg, sizeof(msg) - 1,
-                         "Transaction failed on small parcel; remote process probably died, but "
-                         "this could also be caused by running out of binder buffer space");
+                msg = "Transaction failed on small parcel; remote process probably died, but "
+                      "this could also be caused by running out of binder buffer space";
             }
-            jniThrowException(env, exceptionToThrow, msg);
+            jniThrowException(env, exceptionToThrow, msg.c_str());
         } break;
         case FDS_NOT_ALLOWED:
             jniThrowException(env, "java/lang/RuntimeException",
@@ -983,6 +983,10 @@
     IPCThreadState::self()->restoreCallingIdentity(token);
 }
 
+static jboolean android_os_Binder_hasExplicitIdentity() {
+    return IPCThreadState::self()->hasExplicitIdentity();
+}
+
 static void android_os_Binder_setThreadStrictModePolicy(jint policyMask)
 {
     IPCThreadState::self()->setStrictModePolicy(policyMask);
@@ -1079,6 +1083,8 @@
     // @CriticalNative
     { "restoreCallingIdentity", "(J)V", (void*)android_os_Binder_restoreCallingIdentity },
     // @CriticalNative
+    { "hasExplicitIdentity", "()Z", (void*)android_os_Binder_hasExplicitIdentity },
+    // @CriticalNative
     { "setThreadStrictModePolicy", "(I)V", (void*)android_os_Binder_setThreadStrictModePolicy },
     // @CriticalNative
     { "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy },
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 019e3bd..a8d8a43 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -80,7 +80,7 @@
                        VsyncEventData vsyncEventData) override;
     void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
     void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
-                             nsecs_t vsyncPeriod) override;
+                             nsecs_t renderPeriod) override;
     void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
                                     std::vector<FrameRateOverride> overrides) override;
     void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
@@ -168,14 +168,14 @@
 }
 
 void NativeDisplayEventReceiver::dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
-                                                     int32_t modeId, nsecs_t) {
+                                                     int32_t modeId, nsecs_t renderPeriod) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
 
     ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
     if (receiverObj.get()) {
         ALOGV("receiver %p ~ Invoking mode changed handler.", this);
         env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchModeChanged,
-                            timestamp, displayId.value, modeId);
+                            timestamp, displayId.value, modeId, renderPeriod);
         ALOGV("receiver %p ~ Returned from mode changed handler.", this);
     }
 
@@ -290,7 +290,7 @@
             gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
     gDisplayEventReceiverClassInfo.dispatchModeChanged =
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchModeChanged",
-                             "(JJI)V");
+                             "(JJIJ)V");
     gDisplayEventReceiverClassInfo.dispatchFrameRateOverrides =
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz,
                              "dispatchFrameRateOverrides",
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 2b932cb..98814bf 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -380,8 +380,8 @@
                 if (kDebugDispatchCycle) {
                     ALOGD("channel '%s' ~ Received motion event.", getInputChannelName().c_str());
                 }
-                MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
-                if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
+                const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*inputEvent);
+                if ((motionEvent.getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
                     *outConsumedBatch = true;
                 }
                 inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index a30935b..403c583 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -82,7 +82,7 @@
             reinterpret_cast<jlong>(event));
 }
 
-jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* event) {
+jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent& event) {
     jobject eventObj = env->CallStaticObjectMethod(gMotionEventClassInfo.clazz,
             gMotionEventClassInfo.obtain);
     if (env->ExceptionCheck() || !eventObj) {
@@ -98,7 +98,7 @@
         android_view_MotionEvent_setNativePtr(env, eventObj, destEvent);
     }
 
-    destEvent->copyFrom(event, true);
+    destEvent->copyFrom(&event, true);
     return eventObj;
 }
 
@@ -763,7 +763,12 @@
 
 static jint android_view_MotionEvent_nativeGetSurfaceRotation(jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
-    return jint(event->getSurfaceRotation());
+    auto rotation = event->getSurfaceRotation();
+    if (rotation) {
+        return static_cast<jint>(rotation.value());
+    } else {
+        return -1;
+    }
 }
 
 // ----------------------------------------------------------------------------
diff --git a/core/jni/android_view_MotionEvent.h b/core/jni/android_view_MotionEvent.h
index 9ce4bf3..32a280e 100644
--- a/core/jni/android_view_MotionEvent.h
+++ b/core/jni/android_view_MotionEvent.h
@@ -26,7 +26,7 @@
 
 /* Obtains an instance of a DVM MotionEvent object as a copy of a native MotionEvent instance.
  * Returns NULL on error. */
-extern jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* event);
+extern jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent& event);
 
 /* Gets the underlying native MotionEvent instance within a DVM MotionEvent object.
  * Returns NULL if the event is NULL or if it is uninitialized. */
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 8fd0401..1ed3555 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -27,6 +27,7 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_graphics_GraphicBuffer.h>
 #include <android_runtime/android_hardware_HardwareBuffer.h>
+#include <android_runtime/android_hardware_OverlayProperties.h>
 #include <android_runtime/android_view_Surface.h>
 #include <android_runtime/android_view_SurfaceControl.h>
 #include <android_runtime/android_view_SurfaceSession.h>
@@ -108,6 +109,7 @@
     jmethodID ctor;
     jfieldID supportedDisplayModes;
     jfieldID activeDisplayModeId;
+    jfieldID renderFrameRate;
     jfieldID supportedColorModes;
     jfieldID activeColorMode;
     jfieldID hdrCapabilities;
@@ -128,6 +130,7 @@
     jfieldID appVsyncOffsetNanos;
     jfieldID presentationDeadlineNanos;
     jfieldID group;
+    jfieldID supportedHdrTypes;
 } gDisplayModeClassInfo;
 
 // Implements SkMallocPixelRef::ReleaseProc, to delete the screenshot on unref.
@@ -1129,6 +1132,16 @@
     env->SetLongField(object, gDisplayModeClassInfo.presentationDeadlineNanos,
                       config.presentationDeadline);
     env->SetIntField(object, gDisplayModeClassInfo.group, config.group);
+
+    const auto& types = config.supportedHdrTypes;
+    std::vector<jint> intTypes;
+    for (auto type : types) {
+        intTypes.push_back(static_cast<jint>(type));
+    }
+    auto typesArray = env->NewIntArray(types.size());
+    env->SetIntArrayRegion(typesArray, 0, intTypes.size(), intTypes.data());
+    env->SetObjectField(object, gDisplayModeClassInfo.supportedHdrTypes, typesArray);
+
     return object;
 }
 
@@ -1172,6 +1185,7 @@
     env->SetObjectField(object, gDynamicDisplayInfoClassInfo.supportedDisplayModes, modesArray);
     env->SetIntField(object, gDynamicDisplayInfoClassInfo.activeDisplayModeId,
                      info.activeDisplayModeId);
+    env->SetFloatField(object, gDynamicDisplayInfoClassInfo.renderFrameRate, info.renderFrameRate);
 
     jintArray colorModesArray = env->NewIntArray(info.supportedColorModes.size());
     if (colorModesArray == NULL) {
@@ -1202,66 +1216,40 @@
     return object;
 }
 
-struct RefreshRateRange {
-    const float min;
-    const float max;
-
-    RefreshRateRange(float min, float max) : min(min), max(max) {}
-
-    RefreshRateRange(JNIEnv* env, jobject obj)
-          : min(env->GetFloatField(obj, gRefreshRateRangeClassInfo.min)),
-            max(env->GetFloatField(obj, gRefreshRateRangeClassInfo.max)) {}
-
-    jobject toJava(JNIEnv* env) const {
-        return env->NewObject(gRefreshRateRangeClassInfo.clazz, gRefreshRateRangeClassInfo.ctor,
-                              min, max);
-    }
-};
-
-struct RefreshRateRanges {
-    const RefreshRateRange physical;
-    const RefreshRateRange render;
-
-    RefreshRateRanges(RefreshRateRange physical, RefreshRateRange render)
-          : physical(physical), render(render) {}
-
-    RefreshRateRanges(JNIEnv* env, jobject obj)
-          : physical(env, env->GetObjectField(obj, gRefreshRateRangesClassInfo.physical)),
-            render(env, env->GetObjectField(obj, gRefreshRateRangesClassInfo.render)) {}
-
-    jobject toJava(JNIEnv* env) const {
-        return env->NewObject(gRefreshRateRangesClassInfo.clazz, gRefreshRateRangesClassInfo.ctor,
-                              physical.toJava(env), render.toJava(env));
-    }
-};
-
 static jboolean nativeSetDesiredDisplayModeSpecs(JNIEnv* env, jclass clazz, jobject tokenObj,
                                                  jobject DesiredDisplayModeSpecs) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == nullptr) return JNI_FALSE;
 
-    ui::DisplayModeId defaultMode = env->GetIntField(DesiredDisplayModeSpecs,
-                                                     gDesiredDisplayModeSpecsClassInfo.defaultMode);
-    jboolean allowGroupSwitching =
+    const auto makeRanges = [env](jobject obj) {
+        const auto makeRange = [env](jobject obj) {
+            gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange range;
+            range.min = env->GetFloatField(obj, gRefreshRateRangeClassInfo.min);
+            range.max = env->GetFloatField(obj, gRefreshRateRangeClassInfo.max);
+            return range;
+        };
+
+        gui::DisplayModeSpecs::RefreshRateRanges ranges;
+        ranges.physical = makeRange(env->GetObjectField(obj, gRefreshRateRangesClassInfo.physical));
+        ranges.render = makeRange(env->GetObjectField(obj, gRefreshRateRangesClassInfo.render));
+        return ranges;
+    };
+
+    gui::DisplayModeSpecs specs;
+    specs.defaultMode = env->GetIntField(DesiredDisplayModeSpecs,
+                                         gDesiredDisplayModeSpecsClassInfo.defaultMode);
+    specs.allowGroupSwitching =
             env->GetBooleanField(DesiredDisplayModeSpecs,
                                  gDesiredDisplayModeSpecsClassInfo.allowGroupSwitching);
 
-    const jobject primaryRangesObject =
-            env->GetObjectField(DesiredDisplayModeSpecs,
-                                gDesiredDisplayModeSpecsClassInfo.primaryRanges);
-    const jobject appRequestRangesObject =
-            env->GetObjectField(DesiredDisplayModeSpecs,
-                                gDesiredDisplayModeSpecsClassInfo.appRequestRanges);
-    const RefreshRateRanges primaryRanges(env, primaryRangesObject);
-    const RefreshRateRanges appRequestRanges(env, appRequestRangesObject);
+    specs.primaryRanges =
+            makeRanges(env->GetObjectField(DesiredDisplayModeSpecs,
+                                           gDesiredDisplayModeSpecsClassInfo.primaryRanges));
+    specs.appRequestRanges =
+            makeRanges(env->GetObjectField(DesiredDisplayModeSpecs,
+                                           gDesiredDisplayModeSpecsClassInfo.appRequestRanges));
 
-    size_t result =
-            SurfaceComposerClient::setDesiredDisplayModeSpecs(token, defaultMode,
-                                                              allowGroupSwitching,
-                                                              primaryRanges.physical.min,
-                                                              primaryRanges.physical.max,
-                                                              appRequestRanges.physical.min,
-                                                              appRequestRanges.physical.max);
+    size_t result = SurfaceComposerClient::setDesiredDisplayModeSpecs(token, specs);
     return result == NO_ERROR ? JNI_TRUE : JNI_FALSE;
 }
 
@@ -1269,33 +1257,26 @@
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == nullptr) return nullptr;
 
-    ui::DisplayModeId defaultMode;
-    bool allowGroupSwitching;
-    float primaryPhysicalRefreshRateMin;
-    float primaryPhysicalRefreshRateMax;
-    float appRequestPhysicalRefreshRateMin;
-    float appRequestPhysicalRefreshRateMax;
-    if (SurfaceComposerClient::getDesiredDisplayModeSpecs(token, &defaultMode, &allowGroupSwitching,
-                                                          &primaryPhysicalRefreshRateMin,
-                                                          &primaryPhysicalRefreshRateMax,
-                                                          &appRequestPhysicalRefreshRateMin,
-                                                          &appRequestPhysicalRefreshRateMax) !=
-        NO_ERROR) {
+    const auto rangesToJava = [env](const gui::DisplayModeSpecs::RefreshRateRanges& ranges) {
+        const auto rangeToJava =
+                [env](const gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange& range) {
+                    return env->NewObject(gRefreshRateRangeClassInfo.clazz,
+                                          gRefreshRateRangeClassInfo.ctor, range.min, range.max);
+                };
+
+        return env->NewObject(gRefreshRateRangesClassInfo.clazz, gRefreshRateRangesClassInfo.ctor,
+                              rangeToJava(ranges.physical), rangeToJava(ranges.render));
+    };
+
+    gui::DisplayModeSpecs specs;
+    if (SurfaceComposerClient::getDesiredDisplayModeSpecs(token, &specs) != NO_ERROR) {
         return nullptr;
     }
 
-    const RefreshRateRange primaryPhysicalRange(primaryPhysicalRefreshRateMin,
-                                                primaryPhysicalRefreshRateMax);
-    const RefreshRateRange appRequestPhysicalRange(appRequestPhysicalRefreshRateMin,
-                                                   appRequestPhysicalRefreshRateMax);
-
-    // TODO(b/241460058): populate the render ranges
-    const RefreshRateRanges primaryRanges(primaryPhysicalRange, primaryPhysicalRange);
-    const RefreshRateRanges appRequestRanges(appRequestPhysicalRange, appRequestPhysicalRange);
-
     return env->NewObject(gDesiredDisplayModeSpecsClassInfo.clazz,
-                          gDesiredDisplayModeSpecsClassInfo.ctor, defaultMode, allowGroupSwitching,
-                          primaryRanges.toJava(env), appRequestRanges.toJava(env));
+                          gDesiredDisplayModeSpecsClassInfo.ctor, specs.defaultMode,
+                          specs.allowGroupSwitching, rangesToJava(specs.primaryRanges),
+                          rangesToJava(specs.appRequestRanges));
 }
 
 static jobject nativeGetDisplayNativePrimaries(JNIEnv* env, jclass, jobject tokenObj) {
@@ -1379,6 +1360,15 @@
     return array;
 }
 
+static jobject nativeGetOverlaySupport(JNIEnv* env, jclass) {
+    gui::OverlayProperties* overlayProperties = new gui::OverlayProperties;
+    if (SurfaceComposerClient::getOverlaySupport(overlayProperties) != NO_ERROR) {
+        delete overlayProperties;
+        return nullptr;
+    }
+    return android_hardware_OverlayProperties_convertToJavaObject(env, overlayProperties);
+}
+
 static jboolean nativeSetActiveColorMode(JNIEnv* env, jclass,
         jobject tokenObj, jint colorMode) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
@@ -2058,6 +2048,8 @@
             (void*)nativeSetGameContentType },
     {"nativeGetCompositionDataspaces", "()[I",
             (void*)nativeGetCompositionDataspaces},
+    {"nativeGetOverlaySupport", "()Landroid/hardware/OverlayProperties;",
+            (void*) nativeGetOverlaySupport},
     {"nativeClearContentFrameStats", "(J)Z",
             (void*)nativeClearContentFrameStats },
     {"nativeGetContentFrameStats", "(JLandroid/view/WindowContentFrameStats;)Z",
@@ -2184,6 +2176,8 @@
                             "[Landroid/view/SurfaceControl$DisplayMode;");
     gDynamicDisplayInfoClassInfo.activeDisplayModeId =
             GetFieldIDOrDie(env, dynamicInfoClazz, "activeDisplayModeId", "I");
+    gDynamicDisplayInfoClassInfo.renderFrameRate =
+            GetFieldIDOrDie(env, dynamicInfoClazz, "renderFrameRate", "F");
     gDynamicDisplayInfoClassInfo.supportedColorModes =
             GetFieldIDOrDie(env, dynamicInfoClazz, "supportedColorModes", "[I");
     gDynamicDisplayInfoClassInfo.activeColorMode =
@@ -2212,6 +2206,8 @@
     gDisplayModeClassInfo.presentationDeadlineNanos =
             GetFieldIDOrDie(env, modeClazz, "presentationDeadlineNanos", "J");
     gDisplayModeClassInfo.group = GetFieldIDOrDie(env, modeClazz, "group", "I");
+    gDisplayModeClassInfo.supportedHdrTypes =
+            GetFieldIDOrDie(env, modeClazz, "supportedHdrTypes", "[I");
 
     jclass frameStatsClazz = FindClassOrDie(env, "android/view/FrameStats");
     jfieldID undefined_time_nano_field = GetStaticFieldIDOrDie(env,
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_content_om_OverlayManagerImpl.cpp b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
new file mode 100644
index 0000000..bba1760
--- /dev/null
+++ b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
@@ -0,0 +1,474 @@
+/*
+ * 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 <dlfcn.h>
+
+#include <optional>
+
+#define LOG_TAG "OverlayManagerImpl"
+
+#include "android-base/no_destructor.h"
+#include "androidfw/ResourceTypes.h"
+#include "core_jni_helpers.h"
+#include "jni.h"
+
+namespace android {
+
+static struct fabricated_overlay_internal_offsets_t {
+    jclass classObject;
+    jfieldID packageName;
+    jfieldID overlayName;
+    jfieldID targetPackageName;
+    jfieldID targetOverlayable;
+    jfieldID entries;
+} gFabricatedOverlayInternalOffsets;
+
+static struct fabricated_overlay_internal_entry_offsets_t {
+    jclass classObject;
+    jfieldID resourceName;
+    jfieldID dataType;
+    jfieldID data;
+    jfieldID stringData;
+    jfieldID binaryData;
+    jfieldID configuration;
+} gFabricatedOverlayInternalEntryOffsets;
+
+static struct parcel_file_descriptor_offsets_t {
+    jclass classObject;
+    jmethodID getFd;
+} gParcelFileDescriptorOffsets;
+
+static struct List_offsets_t {
+    jclass classObject;
+    jmethodID size;
+    jmethodID get;
+} gListOffsets;
+
+static struct fabricated_overlay_info_offsets_t {
+    jclass classObject;
+    jmethodID constructor;
+    jfieldID packageName;
+    jfieldID overlayName;
+    jfieldID targetPackageName;
+    jfieldID targetOverlayable;
+    jfieldID path;
+} gFabricatedOverlayInfoOffsets;
+
+namespace self_targeting {
+
+constexpr const char kIOException[] = "java/io/IOException";
+constexpr const char IllegalArgumentException[] = "java/lang/IllegalArgumentException";
+
+class DynamicLibraryLoader {
+public:
+    explicit DynamicLibraryLoader(JNIEnv* env) {
+        /* For SelfTargeting, there are 2 types of files to be handled. One is frro and the other is
+         * idmap. For creating frro/idmap files and reading frro files, it needs libandroid_runtime
+         * to do a shared link to libidmap2. However, libidmap2 contains the codes generated from
+         * google protocol buffer. When libandroid_runtime does a shared link to libidmap2, it will
+         * impact the memory for system_server and zygote(a.k.a. all applications).
+         *
+         * Not all applications need to either create/read frro files or create idmap files all the
+         * time. When the apps apply the SelfTargeting overlay effect, it only needs libandroifw
+         * that is loaded. To use dlopen(libidmap2.so) is to make sure that applications don't
+         * impact themselves' memory by loading libidmap2 until they need to create/read frro files
+         * or create idmap files.
+         */
+        handle_ = dlopen("libidmap2.so", RTLD_NOW);
+        if (handle_ == nullptr) {
+            jniThrowNullPointerException(env);
+            return;
+        }
+
+        createIdmapFileFuncPtr_ =
+                reinterpret_cast<CreateIdmapFileFunc>(dlsym(handle_, "CreateIdmapFile"));
+        if (createIdmapFileFuncPtr_ == nullptr) {
+            jniThrowNullPointerException(env, "The symbol CreateIdmapFile is not found.");
+            return;
+        }
+        getFabricatedOverlayInfoFuncPtr_ = reinterpret_cast<GetFabricatedOverlayInfoFunc>(
+                dlsym(handle_, "GetFabricatedOverlayInfo"));
+        if (getFabricatedOverlayInfoFuncPtr_ == nullptr) {
+            jniThrowNullPointerException(env, "The symbol GetFabricatedOverlayInfo is not found.");
+            return;
+        }
+        createFrroFile_ = reinterpret_cast<CreateFrroFileFunc>(dlsym(handle_, "CreateFrroFile"));
+        if (createFrroFile_ == nullptr) {
+            jniThrowNullPointerException(env, "The symbol CreateFrroFile is not found.");
+            return;
+        }
+    }
+
+    bool callCreateFrroFile(std::string& out_error, const std::string& packageName,
+                            const std::string& overlayName, const std::string& targetPackageName,
+                            const std::optional<std::string>& targetOverlayable,
+                            const std::vector<FabricatedOverlayEntryParameters>& entries_params,
+                            const std::string& frro_file_path) {
+        return createFrroFile_(out_error, packageName, overlayName, targetPackageName,
+                               targetOverlayable, entries_params, frro_file_path);
+    }
+
+    bool callCreateIdmapFile(std::string& out_error, const std::string& targetPath,
+                             const std::string& overlayPath, const std::string& idmapPath,
+                             const std::string& overlayName, const bool isSystem,
+                             const bool isVendor, const bool isProduct,
+                             const bool isTargetSignature, const bool isOdm, const bool isOem) {
+        return createIdmapFileFuncPtr_(out_error, targetPath, overlayPath, idmapPath, overlayName,
+                                       isSystem, isVendor, isProduct, isTargetSignature, isOdm,
+                                       isOem);
+    }
+
+    bool callGetFabricatedOverlayInfo(std::string& out_error, const std::string& overlay_path,
+                                      OverlayManifestInfo& out_overlay_manifest_info) {
+        return getFabricatedOverlayInfoFuncPtr_(out_error, overlay_path, out_overlay_manifest_info);
+    }
+
+    explicit operator bool() const {
+        return handle_ != nullptr && createFrroFile_ != nullptr &&
+                createIdmapFileFuncPtr_ != nullptr && getFabricatedOverlayInfoFuncPtr_ != nullptr;
+    }
+
+    DynamicLibraryLoader(const DynamicLibraryLoader&) = delete;
+
+    DynamicLibraryLoader& operator=(const DynamicLibraryLoader&) = delete;
+
+    ~DynamicLibraryLoader() {
+        if (handle_ != nullptr) {
+            dlclose(handle_);
+        }
+    }
+
+private:
+    typedef bool (*CreateFrroFileFunc)(
+            std::string& out_error, const std::string& packageName, const std::string& overlayName,
+            const std::string& targetPackageName,
+            const std::optional<std::string>& targetOverlayable,
+            const std::vector<FabricatedOverlayEntryParameters>& entries_params,
+            const std::string& frro_file_path);
+
+    typedef bool (*CreateIdmapFileFunc)(std::string& out_error, const std::string& targetPath,
+                                        const std::string& overlayPath,
+                                        const std::string& idmapPath,
+                                        const std::string& overlayName, const jboolean isSystem,
+                                        const jboolean isVendor, const jboolean isProduct,
+                                        const jboolean isSameWithTargetSignature,
+                                        const jboolean isOdm, const jboolean isOem);
+
+    typedef bool (*GetFabricatedOverlayInfoFunc)(std::string& out_error,
+                                                 const std::string& overlay_path,
+                                                 OverlayManifestInfo& out_overlay_manifest_info);
+
+    void* handle_;
+    CreateFrroFileFunc createFrroFile_;
+    CreateIdmapFileFunc createIdmapFileFuncPtr_;
+    GetFabricatedOverlayInfoFunc getFabricatedOverlayInfoFuncPtr_;
+};
+
+static DynamicLibraryLoader& EnsureDynamicLibraryLoader(JNIEnv* env) {
+    static android::base::NoDestructor<DynamicLibraryLoader> loader(env);
+    return *loader;
+}
+
+static std::optional<std::string> getNullableString(JNIEnv* env, jobject object, jfieldID field) {
+    auto javaString = reinterpret_cast<jstring>(env->GetObjectField(object, field));
+    if (javaString == nullptr) {
+        return std::nullopt;
+    }
+
+    const ScopedUtfChars result(env, javaString);
+    if (result.c_str() == nullptr) {
+        return std::nullopt;
+    }
+
+    return std::optional<std::string>{result.c_str()};
+}
+
+static std::optional<android::base::borrowed_fd> getNullableFileDescriptor(JNIEnv* env,
+                                                                           jobject object,
+                                                                           jfieldID field) {
+    auto binaryData = env->GetObjectField(object, field);
+    if (binaryData == nullptr) {
+        return std::nullopt;
+    }
+
+    return env->CallIntMethod(binaryData, gParcelFileDescriptorOffsets.getFd);
+}
+
+static void CreateFrroFile(JNIEnv* env, jclass /*clazz*/, jstring jsFrroFilePath, jobject overlay) {
+    DynamicLibraryLoader& dlLoader = EnsureDynamicLibraryLoader(env);
+    if (!dlLoader) {
+        jniThrowNullPointerException(env, "libidmap2 is not loaded");
+        return;
+    }
+
+    if (overlay == nullptr) {
+        jniThrowNullPointerException(env, "overlay is null");
+        return;
+    }
+    auto jsPackageName =
+            (jstring)env->GetObjectField(overlay, gFabricatedOverlayInternalOffsets.packageName);
+    const ScopedUtfChars packageName(env, jsPackageName);
+    if (packageName.c_str() == nullptr) {
+        jniThrowNullPointerException(env, "packageName is null");
+        return;
+    }
+    auto jsOverlayName =
+            (jstring)env->GetObjectField(overlay, gFabricatedOverlayInternalOffsets.overlayName);
+    const ScopedUtfChars overlayName(env, jsOverlayName);
+    if (overlayName.c_str() == nullptr) {
+        jniThrowNullPointerException(env, "overlayName is null");
+        return;
+    }
+    auto jsTargetPackageName =
+            (jstring)env->GetObjectField(overlay,
+                                         gFabricatedOverlayInternalOffsets.targetPackageName);
+    const ScopedUtfChars targetPackageName(env, jsTargetPackageName);
+    if (targetPackageName.c_str() == nullptr) {
+        jniThrowNullPointerException(env, "targetPackageName is null");
+        return;
+    }
+    auto overlayable =
+            getNullableString(env, overlay, gFabricatedOverlayInternalOffsets.targetOverlayable);
+    const ScopedUtfChars frroFilePath(env, jsFrroFilePath);
+    if (frroFilePath.c_str() == nullptr) {
+        jniThrowNullPointerException(env, "frroFilePath is null");
+        return;
+    }
+    jobject entries = env->GetObjectField(overlay, gFabricatedOverlayInternalOffsets.entries);
+    if (entries == nullptr) {
+        jniThrowNullPointerException(env, "overlay entries is null");
+        return;
+    }
+    const jint size = env->CallIntMethod(entries, gListOffsets.size);
+    ALOGV("frroFilePath = %s, packageName = %s, overlayName = %s, targetPackageName = %s,"
+          " targetOverlayable = %s, size = %d",
+          frroFilePath.c_str(), packageName.c_str(), overlayName.c_str(), targetPackageName.c_str(),
+          overlayable.value_or(std::string()).c_str(), size);
+
+    std::vector<FabricatedOverlayEntryParameters> entries_params;
+    for (jint i = 0; i < size; i++) {
+        jobject entry = env->CallObjectMethod(entries, gListOffsets.get, i);
+        auto jsResourceName = reinterpret_cast<jstring>(
+                env->GetObjectField(entry, gFabricatedOverlayInternalEntryOffsets.resourceName));
+        const ScopedUtfChars resourceName(env, jsResourceName);
+        const auto dataType =
+                env->GetIntField(entry, gFabricatedOverlayInternalEntryOffsets.dataType);
+
+        // In Java, the data type is int but the maximum value of data Type is less than 0xff.
+        if (dataType >= UCHAR_MAX) {
+            jniThrowException(env, IllegalArgumentException, "Unsupported data type");
+            return;
+        }
+
+        const auto data = env->GetIntField(entry, gFabricatedOverlayInternalEntryOffsets.data);
+        auto configuration =
+                getNullableString(env, entry, gFabricatedOverlayInternalEntryOffsets.configuration);
+        auto string_data =
+                getNullableString(env, entry, gFabricatedOverlayInternalEntryOffsets.stringData);
+        auto binary_data =
+                getNullableFileDescriptor(env, entry,
+                                          gFabricatedOverlayInternalEntryOffsets.binaryData);
+        entries_params.push_back(
+                FabricatedOverlayEntryParameters{resourceName.c_str(), (DataType)dataType,
+                                                 (DataValue)data,
+                                                 string_data.value_or(std::string()), binary_data,
+                                                 configuration.value_or(std::string())});
+        ALOGV("resourceName = %s, dataType = 0x%08x, data = 0x%08x, dataString = %s,"
+              " binaryData = %d, configuration = %s",
+              resourceName.c_str(), dataType, data, string_data.value_or(std::string()).c_str(),
+              binary_data.has_value(), configuration.value_or(std::string()).c_str());
+    }
+
+    std::string err_result;
+    if (!dlLoader.callCreateFrroFile(err_result, packageName.c_str(), overlayName.c_str(),
+                                     targetPackageName.c_str(), overlayable, entries_params,
+                                     frroFilePath.c_str())) {
+        jniThrowException(env, IllegalArgumentException, err_result.c_str());
+        return;
+    }
+}
+
+static void CreateIdmapFile(JNIEnv* env, jclass /* clazz */, jstring jsTargetPath,
+                            jstring jsOverlayPath, jstring jsIdmapPath, jstring jsOverlayName,
+                            jboolean isSystem, jboolean isVendor, jboolean isProduct,
+                            jboolean isTargetSignature, jboolean isOdm, jboolean isOem) {
+    DynamicLibraryLoader& dlLoader = EnsureDynamicLibraryLoader(env);
+    if (!dlLoader) {
+        jniThrowNullPointerException(env, "libidmap2 is not loaded");
+        return;
+    }
+
+    const ScopedUtfChars targetPath(env, jsTargetPath);
+    if (targetPath.c_str() == nullptr) {
+        jniThrowNullPointerException(env, "targetPath is null");
+        return;
+    }
+    const ScopedUtfChars overlayPath(env, jsOverlayPath);
+    if (overlayPath.c_str() == nullptr) {
+        jniThrowNullPointerException(env, "overlayPath is null");
+        return;
+    }
+    const ScopedUtfChars idmapPath(env, jsIdmapPath);
+    if (idmapPath.c_str() == nullptr) {
+        jniThrowNullPointerException(env, "idmapPath is null");
+        return;
+    }
+    const ScopedUtfChars overlayName(env, jsOverlayName);
+    if (overlayName.c_str() == nullptr) {
+        jniThrowNullPointerException(env, "overlayName is null");
+        return;
+    }
+    ALOGV("target_path = %s, overlay_path = %s, idmap_path = %s, overlay_name = %s",
+          targetPath.c_str(), overlayPath.c_str(), idmapPath.c_str(), overlayName.c_str());
+
+    std::string err_result;
+    if (!dlLoader.callCreateIdmapFile(err_result, targetPath.c_str(), overlayPath.c_str(),
+                                      idmapPath.c_str(), overlayName.c_str(),
+                                      (isSystem == JNI_TRUE), (isVendor == JNI_TRUE),
+                                      (isProduct == JNI_TRUE), (isTargetSignature == JNI_TRUE),
+                                      (isOdm == JNI_TRUE), (isOem == JNI_TRUE))) {
+        jniThrowException(env, kIOException, err_result.c_str());
+        return;
+    }
+}
+
+static jobject GetFabricatedOverlayInfo(JNIEnv* env, jclass /* clazz */, jstring jsOverlayPath) {
+    const ScopedUtfChars overlay_path(env, jsOverlayPath);
+    if (overlay_path.c_str() == nullptr) {
+        jniThrowNullPointerException(env, "overlay_path is null");
+        return nullptr;
+    }
+    ALOGV("overlay_path = %s", overlay_path.c_str());
+
+    DynamicLibraryLoader& dlLoader = EnsureDynamicLibraryLoader(env);
+    if (!dlLoader) {
+        return nullptr;
+    }
+
+    std::string err_result;
+    OverlayManifestInfo overlay_manifest_info;
+    if (!dlLoader.callGetFabricatedOverlayInfo(err_result, overlay_path.c_str(),
+                                               overlay_manifest_info) != 0) {
+        jniThrowException(env, kIOException, err_result.c_str());
+        return nullptr;
+    }
+    jobject info = env->NewObject(gFabricatedOverlayInfoOffsets.classObject,
+                                  gFabricatedOverlayInfoOffsets.constructor);
+    jstring jsOverName = env->NewStringUTF(overlay_manifest_info.name.c_str());
+    jstring jsPackageName = env->NewStringUTF(overlay_manifest_info.package_name.c_str());
+    jstring jsTargetPackage = env->NewStringUTF(overlay_manifest_info.target_package.c_str());
+    jstring jsTargetOverlayable = env->NewStringUTF(overlay_manifest_info.target_name.c_str());
+    env->SetObjectField(info, gFabricatedOverlayInfoOffsets.overlayName, jsOverName);
+    env->SetObjectField(info, gFabricatedOverlayInfoOffsets.packageName, jsPackageName);
+    env->SetObjectField(info, gFabricatedOverlayInfoOffsets.targetPackageName, jsTargetPackage);
+    env->SetObjectField(info, gFabricatedOverlayInfoOffsets.targetOverlayable, jsTargetOverlayable);
+    env->SetObjectField(info, gFabricatedOverlayInfoOffsets.path, jsOverlayPath);
+    return info;
+}
+
+} // namespace self_targeting
+
+// JNI registration.
+static const JNINativeMethod gOverlayManagerMethods[] = {
+        {"createFrroFile", "(Ljava/lang/String;Landroid/os/FabricatedOverlayInternal;)V",
+         reinterpret_cast<void*>(self_targeting::CreateFrroFile)},
+        {"createIdmapFile",
+         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZZZZ)V",
+         reinterpret_cast<void*>(self_targeting::CreateIdmapFile)},
+        {"getFabricatedOverlayInfo", "(Ljava/lang/String;)Landroid/os/FabricatedOverlayInfo;",
+         reinterpret_cast<void*>(self_targeting::GetFabricatedOverlayInfo)},
+};
+
+int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env) {
+    jclass ListClass = FindClassOrDie(env, "java/util/List");
+    gListOffsets.classObject = MakeGlobalRefOrDie(env, ListClass);
+    gListOffsets.size = GetMethodIDOrDie(env, gListOffsets.classObject, "size", "()I");
+    gListOffsets.get =
+            GetMethodIDOrDie(env, gListOffsets.classObject, "get", "(I)Ljava/lang/Object;");
+
+    jclass fabricatedOverlayInternalClass =
+            FindClassOrDie(env, "android/os/FabricatedOverlayInternal");
+    gFabricatedOverlayInternalOffsets.classObject =
+            MakeGlobalRefOrDie(env, fabricatedOverlayInternalClass);
+    gFabricatedOverlayInternalOffsets.packageName =
+            GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "packageName",
+                            "Ljava/lang/String;");
+    gFabricatedOverlayInternalOffsets.overlayName =
+            GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "overlayName",
+                            "Ljava/lang/String;");
+    gFabricatedOverlayInternalOffsets.targetPackageName =
+            GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "targetPackageName",
+                            "Ljava/lang/String;");
+    gFabricatedOverlayInternalOffsets.targetOverlayable =
+            GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "targetOverlayable",
+                            "Ljava/lang/String;");
+    gFabricatedOverlayInternalOffsets.entries =
+            GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "entries",
+                            "Ljava/util/List;");
+
+    jclass fabricatedOverlayInternalEntryClass =
+            FindClassOrDie(env, "android/os/FabricatedOverlayInternalEntry");
+    gFabricatedOverlayInternalEntryOffsets.classObject =
+            MakeGlobalRefOrDie(env, fabricatedOverlayInternalEntryClass);
+    gFabricatedOverlayInternalEntryOffsets.resourceName =
+            GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "resourceName",
+                            "Ljava/lang/String;");
+    gFabricatedOverlayInternalEntryOffsets.dataType =
+            GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "dataType",
+                            "I");
+    gFabricatedOverlayInternalEntryOffsets.data =
+            GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "data", "I");
+    gFabricatedOverlayInternalEntryOffsets.stringData =
+            GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "stringData",
+                            "Ljava/lang/String;");
+    gFabricatedOverlayInternalEntryOffsets.binaryData =
+            GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "binaryData",
+                            "Landroid/os/ParcelFileDescriptor;");
+    gFabricatedOverlayInternalEntryOffsets.configuration =
+            GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject,
+                            "configuration", "Ljava/lang/String;");
+
+    jclass parcelFileDescriptorClass =
+            android::FindClassOrDie(env, "android/os/ParcelFileDescriptor");
+    gParcelFileDescriptorOffsets.classObject = MakeGlobalRefOrDie(env, parcelFileDescriptorClass);
+    gParcelFileDescriptorOffsets.getFd =
+            GetMethodIDOrDie(env, gParcelFileDescriptorOffsets.classObject, "getFd", "()I");
+
+    jclass fabricatedOverlayInfoClass = FindClassOrDie(env, "android/os/FabricatedOverlayInfo");
+    gFabricatedOverlayInfoOffsets.classObject = MakeGlobalRefOrDie(env, fabricatedOverlayInfoClass);
+    gFabricatedOverlayInfoOffsets.constructor =
+            GetMethodIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "<init>", "()V");
+    gFabricatedOverlayInfoOffsets.packageName =
+            GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "packageName",
+                            "Ljava/lang/String;");
+    gFabricatedOverlayInfoOffsets.overlayName =
+            GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "overlayName",
+                            "Ljava/lang/String;");
+    gFabricatedOverlayInfoOffsets.targetPackageName =
+            GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "targetPackageName",
+                            "Ljava/lang/String;");
+    gFabricatedOverlayInfoOffsets.targetOverlayable =
+            GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "targetOverlayable",
+                            "Ljava/lang/String;");
+    gFabricatedOverlayInfoOffsets.path =
+            GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "path",
+                            "Ljava/lang/String;");
+
+    return RegisterMethodsOrDie(env, "com/android/internal/content/om/OverlayManagerImpl",
+                                gOverlayManagerMethods, NELEM(gOverlayManagerMethods));
+}
+
+} // namespace android
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/jni/com_android_internal_security_VerityUtils.cpp b/core/jni/com_android_internal_security_VerityUtils.cpp
index 8305bd0..dabee69 100644
--- a/core/jni/com_android_internal_security_VerityUtils.cpp
+++ b/core/jni/com_android_internal_security_VerityUtils.cpp
@@ -48,10 +48,6 @@
     if (rfd.get() < 0) {
         return errno;
     }
-    ScopedByteArrayRO signature_bytes(env, signature);
-    if (signature_bytes.get() == nullptr) {
-        return EINVAL;
-    }
 
     fsverity_enable_arg arg = {};
     arg.version = 1;
@@ -59,8 +55,18 @@
     arg.block_size = 4096;
     arg.salt_size = 0;
     arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr);
-    arg.sig_size = signature_bytes.size();
-    arg.sig_ptr = reinterpret_cast<uintptr_t>(signature_bytes.get());
+
+    if (signature != nullptr) {
+        ScopedByteArrayRO signature_bytes(env, signature);
+        if (signature_bytes.get() == nullptr) {
+            return EINVAL;
+        }
+        arg.sig_size = signature_bytes.size();
+        arg.sig_ptr = reinterpret_cast<uintptr_t>(signature_bytes.get());
+    } else {
+        arg.sig_size = 0;
+        arg.sig_ptr = reinterpret_cast<uintptr_t>(nullptr);
+    }
 
     if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) {
         return errno;
diff --git a/core/jni/include/android_runtime/android_hardware_OverlayProperties.h b/core/jni/include/android_runtime/android_hardware_OverlayProperties.h
new file mode 100644
index 0000000..372cca9
--- /dev/null
+++ b/core/jni/include/android_runtime/android_hardware_OverlayProperties.h
@@ -0,0 +1,31 @@
+/**
+ * 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.
+ */
+
+#ifndef _ANDROID_HARDWARE_OVERLAYPROPERTIES_H
+#define _ANDROID_HARDWARE_OVERLAYPROPERTIES_H
+
+#include <android/gui/OverlayProperties.h>
+
+#include "jni.h"
+
+namespace android {
+
+extern jobject android_hardware_OverlayProperties_convertToJavaObject(
+        JNIEnv* env, gui::OverlayProperties* overlayProperties);
+
+}; // namespace android
+
+#endif // _ANDROID_HARDWARE_OVERLAYPROPERTIES_H
diff --git a/core/proto/android/app/location_time_zone_manager.proto b/core/proto/android/app/location_time_zone_manager.proto
index 5fdcfdf..7037a6c 100644
--- a/core/proto/android/app/location_time_zone_manager.proto
+++ b/core/proto/android/app/location_time_zone_manager.proto
@@ -40,7 +40,7 @@
 message LocationTimeZoneManagerServiceStateProto {
   option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
-  optional GeolocationTimeZoneSuggestionProto last_suggestion = 1;
+  optional LocationTimeZoneProviderEventProto last_event = 1;
   repeated TimeZoneProviderStateProto primary_provider_states = 2;
   repeated TimeZoneProviderStateProto secondary_provider_states = 3;
   repeated ControllerStateEnum controller_states = 4;
diff --git a/core/proto/android/app/time_zone_detector.proto b/core/proto/android/app/time_zone_detector.proto
index b52aa82..cd4a36f 100644
--- a/core/proto/android/app/time_zone_detector.proto
+++ b/core/proto/android/app/time_zone_detector.proto
@@ -22,13 +22,38 @@
 option java_multiple_files = true;
 option java_outer_classname = "TimeZoneDetectorProto";
 
-// Represents a GeolocationTimeZoneSuggestion that can be / has been passed to the time zone
+// Represents a LocationTimeZoneProviderEvent that can be / has been passed to the time zone
 // detector.
+message LocationTimeZoneProviderEventProto {
+  option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+  optional GeolocationTimeZoneSuggestionProto suggestion = 1;
+  repeated string debug_info = 2;
+  optional LocationTimeZoneAlgorithmStatusProto algorithm_status = 3;
+}
+
+// Represents a LocationTimeZoneAlgorithmStatus that can be / has been passed to the time zone
+// detector.
+message LocationTimeZoneAlgorithmStatusProto {
+  option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+  optional DetectionAlgorithmStatusEnum status = 1;
+}
+
+// The state enum for detection algorithms.
+enum DetectionAlgorithmStatusEnum {
+    DETECTION_ALGORITHM_STATUS_UNKNOWN = 0;
+    DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED = 1;
+    DETECTION_ALGORITHM_STATUS_NOT_RUNNING = 2;
+    DETECTION_ALGORITHM_STATUS_RUNNING = 3;
+}
+
+// Represents a GeolocationTimeZoneSuggestion that can be contained in a
+// LocationTimeZoneProviderEvent.
 message GeolocationTimeZoneSuggestionProto {
   option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
   repeated string zone_ids = 1;
-  repeated string debug_info = 2;
 }
 
 /*
diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto
index 26e7dbb..75e2908 100644
--- a/core/proto/android/content/intent.proto
+++ b/core/proto/android/content/intent.proto
@@ -21,6 +21,7 @@
 
 import "frameworks/base/core/proto/android/content/component_name.proto";
 import "frameworks/base/core/proto/android/os/patternmatcher.proto";
+import "frameworks/base/core/proto/android/os/persistablebundle.proto";
 import "frameworks/base/core/proto/android/privacy.proto";
 
 // Next Tag: 14
@@ -87,6 +88,7 @@
     optional bool has_partial_types = 9;
     optional bool get_auto_verify = 10;
     repeated string mime_groups = 11;
+    optional android.os.PersistableBundleProto extras = 12;
 }
 
 message AuthorityEntryProto {
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 285258a..556636dd 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -88,6 +88,7 @@
         optional SettingProto odi_captions_volume_ui_enabled = 42 [ (android.privacy).dest = DEST_AUTOMATIC ];
         // Setting for accessibility magnification for following typing.
         optional SettingProto accessibility_magnification_follow_typing_enabled = 43 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto contrast_level = 44 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional Accessibility accessibility = 2;
 
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 4650000..18d84d5 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -457,6 +457,7 @@
         optional bool stop_if_killed = 3;
         optional bool call_start = 4;
         optional int32 last_start_id = 5;
+        optional int32 start_command_result = 6;
     }
     optional Start start = 19;
 
@@ -499,7 +500,21 @@
     repeated ConnectionRecordProto connections = 26;
 
     optional bool allow_while_in_use_permission_in_fgs = 27;
-    // Next Tag: 28
+
+    message ShortFgsInfo {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional int64 start_time = 1;
+        optional int32 start_foreground_count = 2;
+        optional int32 start_id = 3;
+        optional int64 timeout_time = 4;
+        optional int64 proc_state_demote_time = 5;
+        optional int64 anr_time = 6;
+    }
+
+    optional ShortFgsInfo short_fgs_info = 28;
+
+    // Next Tag: 29
 }
 
 message ConnectionRecordProto {
@@ -977,12 +992,11 @@
         optional int32 profile = 2;
     }
     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;
+    optional int32 current_user = 5;
+    repeated int32 current_profiles = 6;
 }
 
 // sync with com.android.server.am.AppTimeTracker.java
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index bd4f990..004d5d6 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -189,6 +189,8 @@
     optional bool is_enhanced_discharge_prediction_personalized = 54;
     optional bool is_low_power_standby_active = 55;
     optional LowPowerStandbyControllerDumpProto low_power_standby_controller = 56;
+    // The battery level drained by the dream.
+    optional int32 battery_level_drained_while_dreaming = 57;
 }
 
 // A com.android.server.power.PowerManagerService.SuspendBlockerImpl object.
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 537efc0..82e1777 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -54,6 +54,7 @@
     optional int32 focused_display_id = 9;
     optional bool hard_keyboard_available = 10;
     optional bool window_frames_valid = 11;
+    optional BackNavigationProto back_navigation = 12;
 }
 
 /* represents RootWindowContainer object */
@@ -595,3 +596,11 @@
     optional WindowStateProto ime_target_from_ime = 2;
     optional bool is_ime_layout_drawn = 3;
 }
+
+message BackNavigationProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional bool animation_in_progress = 1;
+    optional int32 last_back_type = 2;
+    optional bool show_wallpaper = 3;
+}
\ No newline at end of file
diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto
index df5e0a9..607fd10 100644
--- a/core/proto/android/service/usb.proto
+++ b/core/proto/android/service/usb.proto
@@ -240,6 +240,7 @@
     // ID of the port. A device (eg: Chromebooks) might have multiple ports.
     optional string id = 1;
     repeated Mode supported_modes = 2;
+    optional bool supports_compliance_warnings = 3;
 }
 
 message UsbPortStatusProto {
@@ -268,6 +269,7 @@
     optional string usb_data_status = 7;
     optional bool is_power_transfer_limited = 8;
     optional string usb_power_brick_status = 9;
+    optional string compliance_warnings_string = 10;
 }
 
 message UsbPortStatusRoleCombinationProto {
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 7e17840..3c4bac8 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -107,7 +107,9 @@
     sdk_version: "core_platform",
     certificate: "platform",
 
-    srcs: [":remote-color-resources-arsc"],
+    srcs: [
+        ":remote-color-resources-arsc",
+    ],
 
     // Disable dexpreopt and verify_uses_libraries check as the app
     // contains no Java code to be dexpreopted.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 62c5848..71a02dd 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -293,6 +293,7 @@
 
     <protected-broadcast android:name="android.hardware.usb.action.USB_STATE" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_CHANGED" />
+    <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_DETACHED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE" />
@@ -669,10 +670,6 @@
     <protected-broadcast android:name="android.media.tv.action.WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED" />
     <protected-broadcast android:name="android.media.tv.action.CHANNEL_BROWSABLE_REQUESTED" />
 
-    <!-- Time zone rules update intents fired by the system server -->
-    <protected-broadcast android:name="com.android.intent.action.timezone.RULES_UPDATE_OPERATION" />
-    <protected-broadcast android:name="com.android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK" />
-
     <!-- Made protected in P (was introduced in JB-MR2) -->
     <protected-broadcast android:name="android.intent.action.GET_RESTRICTION_ENTRIES" />
     <protected-broadcast android:name="android.telephony.euicc.action.OTA_STATUS_CHANGED" />
@@ -1150,6 +1147,26 @@
                 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 {@link #READ_MEDIA_VIDEO}. It does not prevent apps from
+      accessing the standard photo picker manually. This permission should be requested alongside
+      {@link #READ_MEDIA_IMAGES} and/or {@link #READ_MEDIA_VIDEO}, depending on which type of media
+      is desired.
+      <p> This permission will be automatically added to an app's manifest if the app requests
+      {@link #READ_MEDIA_IMAGES}, {@link #READ_MEDIA_VIDEO}, or {@link #ACCESS_MEDIA_LOCATION}
+      regardless of target SDK. If an app does not request this permission, then the grant dialog
+      will return `PERMISSION_GRANTED` for {@link #READ_MEDIA_IMAGES} and/or
+      {@link #READ_MEDIA_VIDEO}, but the app will only have access to the media selected by the
+      user. This false grant state will persist until the app goes into the background.
+   <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.
@@ -2521,6 +2538,19 @@
         android:description="@string/permdesc_modifyAudioSettings"
         android:protectionLevel="normal" />
 
+    <!-- ==================================================== -->
+    <!-- Permissions related to screen capture   -->
+    <!-- ==================================================== -->
+    <eat-comment />
+
+    <!-- Allows an application to get notified when a screen capture of its windows is attempted.
+         <p>Protection level: normal
+    -->
+    <permission android:name="android.permission.DETECT_SCREEN_CAPTURE"
+                android:label="@string/permlab_detectScreenCapture"
+                android:description="@string/permdesc_detectScreenCapture"
+                android:protectionLevel="normal" />
+
     <!-- ======================================== -->
     <!-- Permissions for factory reset protection -->
     <!-- ======================================== -->
@@ -3152,6 +3182,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 component lifecycle operation such as sending
+         a broadcast is associated with an "interactive" usage scenario.
+         @hide -->
+    <permission android:name="android.permission.COMPONENT_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.
@@ -3291,7 +3327,9 @@
          <p>Protection level: normal
          -->
     <permission android:name="android.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND"
-        android:protectionLevel="normal"/>
+                android:label="@string/permlab_startForegroundServicesFromBackground"
+                android:description="@string/permdesc_startForegroundServicesFromBackground"
+                android:protectionLevel="normal"/>
 
     <!-- Allows a companion app to use data in the background.
          <p>Protection level: normal
@@ -3307,6 +3345,8 @@
          <p>Protection level: normal
      -->
     <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH"
+                android:label="@string/permlab_companionProfileWatch"
+                android:description="@string/permdesc_companionProfileWatch"
                 android:protectionLevel="normal" />
 
     <!-- Allows application to request to be associated with a virtual display capable of streaming
@@ -3742,7 +3782,8 @@
 
     <!-- Allows an application to query the current time zone rules state
          on device.
-         @SystemApi @hide -->
+         @SystemApi @hide
+         @deprecated Vestigial permission declaration. No longer used. -->
     <permission android:name="android.permission.QUERY_TIME_ZONE_RULES"
                 android:protectionLevel="signature|privileged" />
 
@@ -3751,17 +3792,11 @@
          <p>An application requesting this permission is responsible for
          verifying the source and integrity of the update before passing
          it off to the installer components.
-         @SystemApi @hide -->
+         @SystemApi @hide
+         @deprecated Vestigial permission declaration. No longer used. -->
     <permission android:name="android.permission.UPDATE_TIME_ZONE_RULES"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Must be required by a time zone rule updater application,
-         to ensure that only the system can trigger it.
-         @hide -->
-    <permission android:name="android.permission.TRIGGER_TIME_ZONE_RULES_CHECK"
-        android:protectionLevel="signature" />
-    <uses-permission android:name="android.permission.TRIGGER_TIME_ZONE_RULES_CHECK"/>
-
     <!-- Allows the system to reset throttling in shortcut manager.
          @hide -->
     <permission android:name="android.permission.RESET_SHORTCUT_MANAGER_THROTTLING"
@@ -4127,7 +4162,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" />
 
@@ -4593,6 +4629,8 @@
          <p>Protection level: appop
      -->
     <permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+        android:label="@string/permlab_schedule_exact_alarm"
+        android:description="@string/permdesc_schedule_exact_alarm"
         android:protectionLevel="normal|appop"/>
 
     <!-- Allows apps to use exact alarms just like with {@link
@@ -4618,6 +4656,8 @@
          lower standby bucket.
     -->
     <permission android:name="android.permission.USE_EXACT_ALARM"
+                android:label="@string/permlab_use_exact_alarm"
+                android:description="@string/permdesc_use_exact_alarm"
                 android:protectionLevel="normal"/>
 
     <!-- Allows an application to query tablet mode state and monitor changes
@@ -4882,11 +4922,15 @@
          of their associated companion device
          -->
     <permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"
+                android:label="@string/permlab_observeCompanionDevicePresence"
+                android:description="@string/permdesc_observeCompanionDevicePresence"
                 android:protectionLevel="normal" />
 
     <!-- Allows an application to deliver companion messages to system
          -->
     <permission android:name="android.permission.DELIVER_COMPANION_MESSAGES"
+                android:label="@string/permlab_deliverCompanionMessages"
+                android:description="@string/permdesc_deliverCompanionMessages"
                 android:protectionLevel="normal" />
 
     <!-- Allows an application to create new companion device associations.
@@ -6152,6 +6196,125 @@
         android:label="@string/permlab_foregroundService"
         android:protectionLevel="normal|instant" />
 
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "camera".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"
+        android:description="@string/permdesc_foregroundServiceCamera"
+        android:label="@string/permlab_foregroundServiceCamera"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "connectedDevice".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"
+        android:description="@string/permdesc_foregroundServiceConnectedDevice"
+        android:label="@string/permlab_foregroundServiceConnectedDevice"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "dataSync".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"
+        android:description="@string/permdesc_foregroundServiceDataSync"
+        android:label="@string/permlab_foregroundServiceDataSync"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "location".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"
+        android:description="@string/permdesc_foregroundServiceLocation"
+        android:label="@string/permlab_foregroundServiceLocation"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "mediaPlayback".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"
+        android:description="@string/permdesc_foregroundServiceMediaPlayback"
+        android:label="@string/permlab_foregroundServiceMediaPlayback"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "mediaProjection".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"
+        android:description="@string/permdesc_foregroundServiceMediaProjection"
+        android:label="@string/permlab_foregroundServiceMediaProjection"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "microphone".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"
+        android:description="@string/permdesc_foregroundServiceMicrophone"
+        android:label="@string/permlab_foregroundServiceMicrophone"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "phoneCall".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"
+        android:description="@string/permdesc_foregroundServicePhoneCall"
+        android:label="@string/permlab_foregroundServicePhoneCall"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "health".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH"
+        android:description="@string/permdesc_foregroundServiceHealth"
+        android:label="@string/permlab_foregroundServiceHealth"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "remoteMessaging".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING"
+        android:description="@string/permdesc_foregroundServiceRemoteMessaging"
+        android:label="@string/permlab_foregroundServiceRemoteMessaging"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "systemExempted".
+         Apps are allowed to use this type only in the use cases listed in
+         {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}.
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED"
+        android:description="@string/permdesc_foregroundServiceSystemExempted"
+        android:label="@string/permlab_foregroundServiceSystemExempted"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "fileManagement".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT"
+        android:description="@string/permdesc_foregroundServiceFileManagement"
+        android:label="@string/permlab_foregroundServiceFileManagement"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "specialUse".
+         <p>Protection level: normal|appop|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
+        android:description="@string/permdesc_foregroundServiceSpecialUse"
+        android:label="@string/permlab_foregroundServiceSpecialUse"
+        android:protectionLevel="normal|appop|instant" />
+
     <!-- @SystemApi Allows to access all app shortcuts.
          @hide -->
     <permission android:name="android.permission.ACCESS_SHORTCUTS"
@@ -6622,6 +6785,21 @@
     <permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE"
                 android:protectionLevel="internal|role" />
 
+    <!-- @SystemApi Required by a WearableSensingService to
+          ensure that only the caller with this permission can bind to it.
+          <p> Protection level: signature
+     @hide
+    -->
+    <permission android:name="android.permission.BIND_WEARABLE_SENSING_SERVICE"
+                android:protectionLevel="signature" />
+
+    <!-- @SystemApi Allows an app to manage the wearable sensing service.
+             <p>Protection level: signature|privileged
+             @hide
+        -->
+    <permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"
+                android:protectionLevel="signature|privileged" />
+
     <!-- 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
@@ -6889,13 +7067,6 @@
                   android:exported="false">
         </activity>
 
-        <activity android:name="com.android.internal.app.LogAccessDialogActivity"
-                  android:theme="@style/Theme.Translucent.NoTitleBar"
-                  android:process=":ui"
-                  android:excludeFromRecents="true"
-                  android:exported="false">
-        </activity>
-
         <activity android:name="com.android.server.notification.NASLearnMoreActivity"
                   android:theme="@style/Theme.Dialog.Confirmation"
                   android:excludeFromRecents="true"
@@ -7141,6 +7312,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
+        <service android:name="com.android.server.pm.GentleUpdateHelper$Service"
+            android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
         <service
                 android:name="com.android.server.autofill.AutofillCompatAccessibilityService"
                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
@@ -7192,6 +7367,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/OWNERS b/core/res/OWNERS
index 22f40a1..a2ef400 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -1,5 +1,4 @@
 adamp@google.com
-alanv@google.com
 asc@google.com
 cinek@google.com
 dsandler@android.com
@@ -9,7 +8,6 @@
 hackbod@google.com
 ilyamaty@google.com
 jaggies@google.com
-jdemeulenaere@google.com
 jsharkey@android.com
 jsharkey@google.com
 juliacr@google.com
@@ -20,9 +18,6 @@
 ogunwale@google.com
 patb@google.com
 shanh@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
-toddke@google.com
 tsuji@google.com
 yamasani@google.com
 
diff --git a/core/res/assets/geoid_height_map/README.md b/core/res/assets/geoid_height_map/README.md
new file mode 100644
index 0000000..849d32e
--- /dev/null
+++ b/core/res/assets/geoid_height_map/README.md
@@ -0,0 +1,2 @@
+These binary protos are generated at runtime from the text protos in ../../geoid_height_map_assets
+and using aprotoc.
\ No newline at end of file
diff --git a/core/res/assets/geoid_height_map/map-params.pb b/core/res/assets/geoid_height_map/map-params.pb
new file mode 100644
index 0000000..6fd4022
--- /dev/null
+++ b/core/res/assets/geoid_height_map/map-params.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-1.pb b/core/res/assets/geoid_height_map/tile-1.pb
new file mode 100644
index 0000000..546dd0d
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-1.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-3.pb b/core/res/assets/geoid_height_map/tile-3.pb
new file mode 100644
index 0000000..eb3fe46
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-3.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-5.pb b/core/res/assets/geoid_height_map/tile-5.pb
new file mode 100644
index 0000000..0243d6d0
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-5.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-7.pb b/core/res/assets/geoid_height_map/tile-7.pb
new file mode 100644
index 0000000..3c2f777
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-7.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-9.pb b/core/res/assets/geoid_height_map/tile-9.pb
new file mode 100644
index 0000000..5e9a480
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-9.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-b.pb b/core/res/assets/geoid_height_map/tile-b.pb
new file mode 100644
index 0000000..c57e873
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-b.pb
Binary files differ
diff --git a/core/res/geoid_height_map_assets/README.md b/core/res/geoid_height_map_assets/README.md
new file mode 100644
index 0000000..800b3e5
--- /dev/null
+++ b/core/res/geoid_height_map_assets/README.md
@@ -0,0 +1,8 @@
+These text protos contain composite JPEG/PNG images representing the EGM2008 Earth Gravitational
+Model[^1] published by the National Geospatial-Intelligence Agency.[^2]
+
+[^1]: Pavlis, Nikolaos K., et al. "The development and evaluation of the Earth Gravitational Model
+2008 (EGM2008)." Journal of geophysical research: solid earth 117.B4 (2012).
+
+[^2]: National Geospatial-Intelligence Agency. “Office of Geomatics.” 2022.
+URL: https://earth-info.nga.mil.
\ No newline at end of file
diff --git a/core/res/geoid_height_map_assets/map-params.textpb b/core/res/geoid_height_map_assets/map-params.textpb
new file mode 100644
index 0000000..3f504d4
--- /dev/null
+++ b/core/res/geoid_height_map_assets/map-params.textpb
@@ -0,0 +1,6 @@
+map_s2_level: 9
+cache_tile_s2_level: 5
+disk_tile_s2_level: 0
+model_a_meters: 255.0
+model_b_meters: -128.0
+model_rmse_meters: 0.36
diff --git a/core/res/geoid_height_map_assets/tile-1.textpb b/core/res/geoid_height_map_assets/tile-1.textpb
new file mode 100644
index 0000000..7fac234
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-1.textpb
@@ -0,0 +1,3 @@
+tile_key: "1"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\244i(\242\224R\216\264\361OZ\221je\251V\246Z\225jE\251V\245Z\225jE\247\216\324\361N\317\024f\214\322\023I\2323I\232L\322f\220\232LdqM\316:\321E\024\354\344sJ)\364\340i\340S\200\315(\030\243\255:\224\032p4\264\340i\300\324\213\315:\232EFz\320\0174\244\323I\246\223M-F\3527R\026\244\rR+S\353\314\r%\024QN\247\212z\324\253R\255J\2652\324\253R\255H*U\251\024\324\213O\006\244\024\264QM4RRRt\244$\342\220{\322a\201\312\363M,I\344P\017\255;\214\360h\245\006\234\r<5(4\365cO\006\226\227\024\264\nPiisNSS)\245&\232M0\232J3Fj65\031j7Rn\244\335F\352\225\033\212\224\036+\314\215!8\242\212)\303\245<S\326\245Z\225jU\251\226\245Z\221jU5\"\324\202\244\006\236)\342\235\2323HNh\2434\204\322SI\244\3154\322\t\n\364\241e\301\346\234]X\360(\371{Ss\315(4\340i\340\323\305<S\251A\365\247\216zQE.i3J\017\255(8\251U\251\305\251\204\323wRg4f\226\243sQ\023M\335I\272\223u\033\252x\232\236\322W\232\346\201\315)\351H\r-8S\305<\n\221jU\251V\246Z\225MJ\265\"\232\221jQO\024\360i\340\323\263I\232;\321\232Bi3IHO\024\302i3M4\334R\216)E-:\214\323\301\251\024\323\301\247\203K\326\201\221\305(4\273\251C\003KE&j@iwSKSKR\006\247f\226\230\325\003\034Te\251\245\250\335K\272\244G\300\241\236\274\374\212J\t\245\024\264\341O\002\244\025 \251TT\252*Q\305H\246\245SR\255<T\200\342\236\r<\032x4\271\2434f\214\322g\336\214\322n\3074\326bi\224\322i3I\232PiE:\235J\0058S\205<\032x4\340psN\316\356\264\224\240f\214R\200{sN\036\364\204zP\033\024\026\246\346\220\265\000\323\324\323\211\246\267J\253!\346\241-M\335I\276\215\364\345\227\212S%p\331\244\315!4\240\342\2349\251\000\251\022\244\002\236\005H\016*E\004\367\251\000>\265\"\232\225MJ\246\244SR\003N\006\236\r?4f\215\324\002)3Fh\315!>\264\204\323M7\223M9\024f\226\224S\201\247\212p\024\340)\324\242\234)\302\235\232\0058\014\323\302\346\244\000\001H\334\216*\022\r\' \323\261\232i\024\332L\322\356\3058=\014\374UYZ\2533sM-M-I\276\220\311\203K\346W\036\335i\271\245\315-<T\212jd\000\324\300\nv\332]\264\365\251\024\323\305H\246\244V\251\025\252P\324\360\324\340\324\354\322\346\214\322n\244\315\031\2434\231\367\2439\242\233\315&\t\240\nZu(\024\341\322\236)\340\323\207ZZu--(\251\024T\200S\361M \322\021\212\215\215\013\232v3\326\243u\305Bi\271\243v)\013UyZ\253\026\246\026\244\335HZ\243g\305\n\371\025\313\223\223E\'J\003T\201\263N\rS\306\370\251\321\352]\364\006\247\n\221i\300\323\301\251\026\244\025 4\360i\340\323\263J\r)4\334\321\232L\321E(\243\036\224}h\3074c\326\224\016)\300R\342\226\234:R\212p\247\212u:\212P*t\\u\251\024sO\307\2454\203M*M\"\305\334\323\304`\016\224\205)\214\271\025VE\301\250I\346\232M4\265V\225\252\271j\214\265&\352\013Tl\324\212Nk\235u\301\244\006\226\232G4\345\034S\226\246SR\251\251T\324\200\323\301\251\001\342\224\032p5 5\"\265H\032\236\032\236\032\234\r(4\354\322\032i4QE8R\343\024\244n?-(\030\034\322\355\357J)\301sF)qK\212p\024\242\235N\002\236\005(\024\365^jQ\351R\016*D\030\0314\270\024\234f\220\221J\016)O\"\242u\300\252\317\212\251/\025\\\2651\232\240\220\346\2533TE\2517PZ\230Z\244OZ\304u\315W*E%\031\346\2274\340jE5*\232\221MJ\032\244\016)CS\203S\203T\212\325 jxjxjxjx4\340i\331\2434Rd\032>\224\242\224\032p\247t\373\264\240f\234\247\035E.=*D\311\030\244\307<\322\342\214S\200\245\3058\np\247\201N\002\244Q\3058\nxS\326\235\272\223u!jM\334\346\202\371\251#\177Z\212y;\n\250\354j\274\247+UKb\242f\250]\262\rUc\223Q\261\246\346\220\2657v[\025aH\305d\221Q\262\203Q\264~\225\021\0304\242\234\005<T\200\323\301\247\203O\rN\rO\rO\006\236\rH\r<\032\220\032x4\360i\300\323\301\245&\222\212)E(\247\212p4\341N\034\365\247\004=\251\300`\323\331r3M\242\234\0058\nu\002\244Z\221Fi\346\225E<\275FZ\224P\302\231\2323NV\3051\2335VV\305V\221\370\252\214\334\324.\325\013>\005BMFM4\232ajj\037\232\246\316*\211\031\246\021M<\036j)S\270\250\207\006\236)\300\323\201\247\003O\rN\rN\rO\rR+T\201\252@i\340\323\301\247\203R\003O\006\234\r;4f\226\212QN\035i\302\234)\302\234*@i\300R\266q\326\220R\322\212Zu(\251\026\245N\224\340(\346\2028\250\363\315.\361H\322SKSKR\027\244.1Tg\227\223U\336O\226\253\226\250\335\270\252\356\325\021zc50\2651\232\225\033\034\323\214\225X7\024\3074\314\322\365\025\t\034\320)E:\235J\r(4\340i\341\251\341\252Uj\2205<\032\2205<\032\220\032x5 4\354\321J)iE8R\216\264\361\326\234)\300\323\205<\032x\346\215\270\240\nZ)A\245\025*\324\240b\244QI\'\025\031~\324\322j6ni\244\323wTm.)\215!=*3)\031\315S\222M\315QH\374T%\351\217\'\025Y\236\241g\244-\221Q\231)\245\350\3631F\372\256\257\212\035\371\247+\002)H\300\372\324\r\301\240\002FiA\247\003N\024\341KE(4\360\325\"\265H\255R+T\200\324\200\324\212j@j@i\324\240\322\322\203J\r8S\2058S\251\300\323\201\247\203OS\212\220`\365\247l\006\243<\032(\240\032\225\rN\247\232\225M6S\232\256i\245\2526j\214\265F\317P\273\032h|S\036@{\325f\340\325y_\232\204\265E$\225]\244\250\313\324m\'\2753}&\374\232\031\251w\361\212\210\032\010\315 8\342\245\r\221H\311\270\344S\261\260u\241B\261\347\212xU\'\031\240\250\354h\002\212(\245\006\234\rH\246\244SR\253T\212\325*\265H\246\244SO\3158\034\321\232Pis\212p4\340i\300\323\301\247\003N\024\340i\340\324\213R\251\300\250\336\243\315\031\247\n\231*PjE4\217U\337\203Q\263b\242g\250\331\252\"\325\0335B\315Q3TM \025\003\2605\003\266*\273\266j\274\204\212\207\314$\323Y\351\273\351CsAl\232pj\211Z\244\315;\002\224qR)\024\222\214\257\035\2521N\025 \247\216:R\032\000\024\231\242\224\032x5\"\232\221MJ\246\244SR\251\251T\324\200\322\212u\024\240\322\203N\006\234)\324\341N\006\236\r<\032\221Z\244\335Lza\3068\244\024\3455:\036*@i\340\342\225\216EWaP\311\305Vf\346\232\315Q3Te\251\214j\007\252\316\rC\236\271\250]\372\324\005\361Lr\030Uv\030\250\230\323\013b\205|\323\225\275i\302Nh\024\240\346\236\265 \024\003@\2239\030\315\"\343w\315\300\247\034\003\3058\032x4\264\204SM&i\300\323\201\251\026\244Z\225jU\251\026\245SR)\247\322\322\321J)E<\032p4\271\247\003N\006\236\r<\032xj]\324\323I\364\247\n\224\034\npj\2205!|To \305V\221\352\2635F\317\305D\315Q3\324fJa|\323\030\344Ug\030\315Tr9\252\356\324\315\370\250\344q\332\243$\032\206F\3051_\013I\347`\322\371\340\016\265p\232\007Z\221H\247\223@\246\276\027\225<\323U\311\353R\016i\302\234\016)\300\321\232Bi(\024\365\251V\245QR-J\242\244Z\221jU\247\322\212\\\321Fi\324\341N\024\np4\271\245\rO\rO\rK\272\234\034R\202\r9y\247\223\232Pi\013\342\232d\250\231\252\027j\254[\232\215\232\230Z\240v#\245@\322Sw\344\3224\230\025RisU\267TNs\322\253\273\021Q\026\246\371\225\034\255\221\232\214\036)\214\334\324l\325\262M74\240\234\324\212r9\251\007\000\324\017\311\241x\251T\323\301\3158R\321\232)3J*E\251EH\265*\324\242\244\024\361R\003N\006\235N\242\2123N\006\236\r.h\315\024\271\245\rN\rN\335I\272\2245N\217\305H\264\342x\250\\\324d\323\031\252\031\033\212\256Z\230MD\307\024\322\303\034\325Iz\234TFP\243\336\253IrI\300\250\231\311\034\232\207q&\220\266\006*\0275\013TD\323$o\227\024\302p*&jh\311<V\306\354\323\t\251\243\373\274\323\324\323\363L#\212h\340\323\301\251\026\236)h\242\212QR\n\220T\213R\251\251EH*@i\302\234)\331\245\006\235E\024\242\234\r-\024f\2274f\2245.\3523OSS\257\"\236\246\225\215B\315L&\242f\250\035\252\026j\210\2654\265B\315P\263UIO\245W\315F\315@\340d\324,\331\311\250\213Td\323\r@\347\006\241g\315\0107\034S\300\330s\236\225\177\'4\365\251T\366\247\322\364\024v\244\305(\251\026\236)sFiiE8u\247\n\220T\212*U\251V\244\025 \247\001N\024\264\341J)\331\030\351IJ)h\242\2123K\2323@9\245\247\255N\215\305H\0174\3622*\026\025\021\025\013T/U\331\252&5\0335D\315P;TM\316j\244\200\255F\0334\331\037\013P\253dT.\374\361Q\226\244\335QHj#\3159\030(>\265\033=l\200\r4\360i\350y\251A\241\216\005,|\2574\273M \353O\006\244\007\024f\200iE:\234)\342\244Z\225jU\025*\212\221E<S\205(\247S\201\315(\245\243\351E\024Q\2323Fh\245\006\224\034S\251EJ\246\246\034\214\323\201\241\205B\343\025]\352\007<UF=j&j\214\265D\355U\334\323\013T/\363US\36256^V\241\034-B\375j3Gj\216R\002\324!\276Z\214\2754\265n\253\323\261\232p\030\251\001\247\021\305:1\201O\307\345L<\032)\331\245\006\224S\251E<T\213R\250\251TT\252*@*@*AKE(\247\np4\354\203\332\222\214\372\322\023I\2323Fh\315\024\271\247\003N\024\361O\034T\250\335\251\343\031\247\023U\345`*\243\275Ww\252\356j\0064\306\346\242cPHs\322\241-\353M&\253\3102i$\000\'\275@zsQ=ELv\300\252\222K\232a~*2\324\205\253l5L\215S)\315H\026\236\005=W\025 \034SY8\250\250\247\nu:\234)\342\244QS(\251\224T\252*@*E\024\360)\333h\305&)\302\224\nZ)\246\212L\014u\244\242\214\321\232Pi\300\323\203S\203S\301\251\024\323\303\363C>\005S\226L\232\256\355P;T,\325ZG\346\243\3631Q\273\347\2450\236*\273\2674\200\346\220\214\232c&[\223\300\250g\340qU\230\361P\261\305C/CU\030b\243&\233\232ij\334\006\244SV#j\264\234\212x\342\244Z\220\nR\274Ug\034\361M\245\025 \024\243\255<S\300\251\026\246AS\255L\242\244\002\236\005H\242\237\212LQ\266\227m\030\243\024b\223m\030\246\220;SN1M\242\214\321\232]\324\340\324\340\324\360\365\"\267\024\026\346\221\334m\252r?5\0135@\355P\263\324\016rj\007\342\242\3630y\241\233\212\205\201\355LRKb\207s\273\002\230M5\210\306\rVu\306qP7Z\205\370\252\322T\004\323\t\246\223[\240\324\212j\304f\255!\251\001\251Tb\246ZRj\263\016i1@\024\360)\300S\200\251\000\251\024T\312*e\0252\324\242\244\025\"\212~(\305&\3321K\212LQ\2121AZiZa\\\347\024\302)\244RQF}\350\335N\rN\004\324\212\330\034\323\032L\232Fo\226\252\273TL\334Uy\036\253\264\225\023I\3151\233\212\254\347\232M\365 \341y\250\207\031\367\246\232k\032\201\330\223M \221PH*\263\236*\263\234\325v<\323\t\246\223[\252je5<ue\017J\235je\247\203J\307\212\212\214R\201J\0058\nx\025\"\255H\242\245QS-L\242\245\002\244\002\236\253O\002\235\2121HE!\036\224b\226\214Q\212\n\324l\246\242a\212a\024\332C\326\233\2323N\006\246Zk\266*\271~i\314\374T\016\325\004\217U]\352\274\215U\313\234\322o4\3269\246/Z\224\266\0056\243s\212\215\232\243=i\340qPH9\252N9\301\252\257P7Z\210\232a5\274\225:\212\236:\262\274b\254!\315J)CzP\\\343\030\244\034\322\201OQN\333N\002\236\005<\n\221EJ\242\245QS(\251\224S\300\251\000\247\201N\305\030\244+M\305\030\244\245\006\226\202)\244TN9\310\250\230S\010\246\232i\024\224\245\200\245\022\340To&\352\217<\3223\361U\336LUg\222\240y*\006z\205\233&\233\272\215\331\247(\346\234\313\315\025\024\234\232\204\365\245\305/j\205\272\223T\346\0309\252\255\311\252\362pj\003Q\265t\010*\302\234T\250j\312\363V\022\236\030\032\025\261\320\323\363\221\3159O\030\247v\247(\247\201J\005H\005<\n\221EH\242\245Z\231jU\251@\247\201R\001K\212)\r%&(\305\030\243\024\021HEF\313Q\262\324ei\204S\010\246\261\305G\313R1\300\342\233\232kTL\330\252\262\2775Y\332\240f\250\331\261Q\026\346\233\236i\353SD2j}\234\234\324.1P=DFi@8\305(\004\214\nc\256\336\265NnsU\010\353U$\250MF\325\321\"\032\231S5*\200*\302\234\nw\231\330S\303\032r\232\225NjAO\024\361O\024\360*@)\341i\340T\212*E\025*\212\231jU\025 \024\3608\245\"\223\024Q\212m\024QK\232Ji\024\302\276\225\031J\214\2550\255DS&\221\206\321Q\221\232\002b\230\342\252\313T\344\252\256j\022\365\013\276M34\n\231*\314?xU\2228&\253\311\336\25352\227\034qD|\212\216j\243%@zU\031\206\r@M1\253\250\351J[\002\205j\231Z\245S\353S)\030\251\027\232\221V\244U\251\025\rJ\261\232\221b&\236#5 B)\352)\340T\200\n\221EJ\253R*\324\200\021\326\236\265 \024QHE%!\024\224QKIE4\212i\024\302\275\351\205i\206<sP\310\274\323U;\320\303\002\240\220\3259\0175NS\212\246\3475]\215DM%*\324\351V\242\030\"\255\0221Ue\252\315M\357OQH\027nj\031\252\224\225]\370\252\322\256j\243\014\032\214\327LOzhl\232x52\232\225MJ\2652\232\235MJ\246\246Z\225EL\2652\212xZxZ\\{P\024}*@\247\352)\340\343\255L\2305(\024\270\245\315\031\245\242\220\212Ji\030\242\212L\321\2323IHi1\232k\014Tl{T/M\350)\215Ue5Q\352\224\334\232\252\374T\014)\204S)\351S\'QV\242\346\245|\214\021P?5\023-DF\rH\2074\3622*\244\243\004\212\251(\346\252\311P\236j\244\313\203U\315tE\363J\265*\212\231jU\034\324\312*eZ\231T\324\310\246\247U5:!\251UjP)\342\236>\224\341N\306h\000\216\224\340\304u\251\025\324\366\305J\030v4\241\251w\212p#\265-&)(\244\'4\224\021\232m\024\231\245\315&x\244\007\0249/\315DEDW&\220\214TRUIj\243\203T\344\035j\263\212\205\2051\2050\255*\212\235\005X\214\342\236_\327\221Q1\007\245\'Z\214\216x\247\001\265M&x\252\322\034\346\251\310j\273\212\200\214T2.\341U$\\V\332\324\253S(\251\224T\313S\245N\265:T\350*t\025:\212x\031\247\201N\002\236)qJ)\334\322\322\201N\024\365\315H\006i\333\005.\334t\244\311\024\231\245\315\024\322)\264QHi3I\232:\322\206\300#\035j3M\353LqU\336\253H3U\334dUI\022\252\310\265\\\212\217\0314\2453@J\231F\005<u\247\3435\013\251C\354h\246\363\221J[\203M\317\006\253Jj\253\014\324.*\"\264\306Z\253*f\264\326\245Z\231jU5:\324\351V\022\247AS\240\253\010*e\247\212x\247\016\224\372p\024\340)\300R\205\247\205\245\013O\013O\002\235\212Z\nR\025\244\331\355M \212NM!\024\332)\247\336\232i@&\244T\241\222\233\345\226\031\364\2462\355\250\330T\016\271\250\031*\273\307U\244N*\244\211U\236:\214G\315?e\033iqKO\007\212d\274\212\214PG\024\303L\317\006\240~j&\025\003\212\205\2054\212\201\305\\SR\251\251\222\246J\235*t\253\tV\022\247J\260\225*\324\213N\024\360)\340S\300\247\201N\305(\024\340)\340S\261O\002\234\026\245H\367\032\227\311\002\232\321q\3050\307\216\325\033% \212\220\307\352*6OJn\332c\n`\0252-HN\0054\363H\006\r1\223\'\232\215\223\025\031AQ2\n\255\"\325YW\345\252\016*\026\031\244)\307\024\230\246\342\232x\240\034\322\366\246\236\234\322\001JG\025\021\025\031\351L+Q:b\253\270\252\357L\353Q\270\251\301\251\226\246SS-N\225a*\302U\204\253\tV\026\244Z\221i\340S\305H*@)\352)\370\243\024\340)\300S\300\247\252\324\251\036MXU\300\247\342\232E4\212\214\2504m\024\233i\245\001\246\030\207j\201\3415\037\226\300\323\324\021\326\220\344\232r\255;\024\240T.\274\324\0141P\260\252\356\265^Q\305Q\2219\250\n\321\212iZ\215\306*#J\253KH\302\231F\354\212k\n\214\n1\326\242q\315V\225j\233\216i\240b\243z\225MJ\246\247J\231*\302T\351VR\254%YJ\231jU\251\001\251\026\236\242\236:T\252*@;\323\326\227\024\340)\301jEBzU\210\340<f\247\010\007\002\234\022\224\2454\212a\024\314R\n\r7\255%\004\003M()\245)\246:M\270\246\232BqL$w\353Q8\025\013\255V\222\2538\315U\221sP4t\2051L+Q8\3153e.\321MaM9\"\230E ZR\271\024\2018\244+\324T2.*\254\235\rTu\346\242#\025\023sOJ\231jt5:T\350*\312\n\260\234U\2048\251\325\205J\257R\253T\200\324\212jE5*\232\221MJ\265 \366\024\242\236\242\245U\315[\202<\014\232\225\260\243\212\022\246\002\224\364\250\030\363Q\223LcM\3154\232L\323wS\201\342\214\321\232ajN\325\031\036\224\326\250\315F\325\013\232\257%VcQ5Bi\244f\230\313Q\224\246\221L\"\230E0\214\322\021H\005=W=\2516\342\232G4\307\213p\342\251I\0363\232\253 \252\354)\205i\026\245SS\245X@j\302\n\262\203\0252\324\352jU5*\232\225MJ\246\245SR\255J\265\"\324\252}j@\376\224\345\253\021\256j\312 \0258\245a\220)@\305.\354P[\212\211\215Fi\246\232i\246\232M0\365\247\003JM74\003\203\315)!\272P1QH3\322\240\"\230\302\241aP8\250\031j\007J\205\201\024\332\017Ja\031\250\330TdTl9\240-!\024\3209\251T`R\021L+\221M?v\252\3123\232\246\351\315B\321\346\242d\250\026\246A\232\261\032\325\244\025:T\313S-J\246\245SR\251\251\226\245Z\231jU\251TT\252)\364\340*E\025n\016\225d\032z\232\225H&\236j2)\246\2434\001M4\302)\270\342\230E4\212i\310\240\236)\205\216i\300\322\346\214\323\032\242jcT-\315B\3035\023\n\211\226\241d\250\312R\025\246\025\2462\346\243\"\231\262\224\255F\303\024*\323\310\307Zn3F*\'\\\016*\273\256E@\361\324-\035D\351Y\351VPU\210\352\302\324\252jUj\221Z\246SR\251\251\224\324\313S\240\251\324T\312\2652\212x\251\000\247\205\251\000\253p\217\226\246\013O\002\234\016\r.\352\\\323MFi\r\'ji\246PE0\323M0\323\010\244\316)CS\263\3055\215Fj&\250\332\243j\215\206j\"*2)\245)\245)\214\225\023%0\245\0331Me\250\331h\013M#\'\232\\\036\324\214\204\014\323\010\312\234\325r1Q\260\317Z\211\226\240u\315e\240\251\320U\204\310\251\224\324\213R\255L\246\245SS\245N\225:\n\235\005XAS\250\251Ui\341jEZ\220\n\225S5f5\300\346\246\006\2349\245a\3050\032\\\322\023L4\207\245(\2468\364\250\311\246\026\246\226\2439\246\236)\206\232E\002\234\r\006\230\334T/Q\036\264\323Q\232k\014\324eh\tF\332c\257\245B\313L+M+\212a\025\031\034\323H\246\323\227\255\0142)\254>^\225Y\205D\302\241qU\333\203Y\310\2652\216\225*\324\253R-L\265*\324\310*\302\n\260\202\254 \253\010\265a\026\254\"\325\210\342-\322\245\3733c4\2331OU\251Pb\254\257LR\201\315H\242\211\016\005C\272\227u4\232J)E-0\250\250\331*2\224\241i\214)\230\240\212LR\205\240\212\215\252&\025\031\024\302)\204SqM+J\005!\024\3223P\262\323\n\342\232EF\313Q\221L\"\232V\2000i\3148\342\230\303+U\330Tl*\t\005Wu\254\325\034T\253R-L\265*\212\231EL\202\247AV\021j\302-YE\253(\265a\026\254*\325\210\320\366\253pr\n\277J\212E\033\270\024\005\247\201R\3069\251v\363OQQK\311\250H9\245\353IFh\315\000\323\201\243\024\230\315!Za\025\033-7m&(\305=S\"\221\226\242e\250\331j&Z\211\205F\302\233K\214\212\000\241\205FE4\212\215\226\243\"\232ED\313L+M\"\220\214P\t9\305\005p\rVa\315F\303\025\013\014\324N\225\222\005=EJ\265*\212\231EL\202\254\"\325\204Z\263\032\325\224J\262\211V\021jtZ\2361\310\315\\\014\252\240/Z\025\210<Q\214\320F)sO\214\363V\200\340\032x\030\025\013/4\322\242\230V\232E!\244\242\200i\342\236\006h\333\315!L\324n\225\036\312iZLsR\240\244aP\260\250\210\250\310\250\312S\031*\"\224\230\305\000\320Ni\244SJ\323\010\250\330S\010\250\310\2462\323\010\244\333\232ENi_\345\004Ub*\026\246c\232\032<\212\300\006\244Q\232\225EL\202\247AS\240\253(*\302\n\263\032\325\250\326\254\242\324\352*U\0252T\242\244\024\374\322\023\232J\2325\357V\024\361O\3154\256i\245i\204S\010\246R\021\2121\305 \034\324\2128\247S\363\221F)\031A\250\331*2\224\302\264\345\024\244TL*\026\024\322(\331\232C\021#\245E$[G5Y\206)\270\315\030\244#4b\243aQ\232a\024\322)\2148\246\025\246\221GN\225\013\222MD\302\243+M\013\3159\206\005sj*U\0252\212\231EL\202\247AVPU\230\305Z\214U\250\326\254\242\324\312\264\361R\251\247\203O\006\235\272\2234\344<\325\264\344\014T\341x\245\333N\333Me\250\210\2460\250\332\243&\223u\000\346\236\r<\032}8R\342\223m#%DR\200\230\246\260\250\331j&\024\300\265,i\223S\004\030\306*\033\230\376Z\314t\346\243\306(\245\333I\214Tl*6Zf)\244S\010\246\221M)\221Q\343\002\243aQ\260\250\312\321\216i\262g\025\316\250\251Uje\0252\212\231V\247AVPU\230\305[\214U\250\305YQR\n3O\006\236\r<S\351qOU\253p\203\212\264\2434\340\224\3420*6\025\021\025\023\n\214\212c%0\255\030\305(4\365\251\001\247\346\234\r8\n\220 \"\243h\361L)Q2\324n1P\232@*P=)\335\2529\016\340A\2522\2475]\3053\024\365\031\244e\250\210\250\310\246\221I\267\"\230\313L\305\0140*\006\246\021Q\260\246\204\'\240\245\331\216\265\004\247\002\260UjUZ\225V\246U\251\325jdZ\262\213VcZ\265\030\253IS)\247R\212z\212\221EH\005<\n\221V\246T\251\223\345\251C`\325\205pE\014)\245i\214\265\013-D\302\233\212B\264\322\264\3221FiC\322\371\225*6je5 jBsHF*\'\"\240a\232\211\226\243\350jT4\346\2467J\255(\252\222\n\212\244Z\030TL\265\031\024\303I\315!\024\322\264\306\034T\004S\010\246\225\245\215y\311\244\223\212\245)\306k\031V\246U\251UjeZ\231\026\247E\253\010\265e\026\254\245N\265*\323\300\247\205\247\201R-H\242\244QS\242\324\341qMn\r9[5*f\247\316E(\244a\221Q2f\241d\3057m\033i\245j\027Z\214\323y\240\023R+\021R\254\206\245\022R\031h2f\230Ni\206\230\302\230V\234\274S\310\342\242j\201\352\273\214\324%piV\202i\206\230\302\243\"\223\024b\232\303\212\215\272T$R\005\243\313\356hn\007\025VJ\25175\230\253R\252\324\312\2652-N\213S\"\324\350*\302\n\235EL\202\247QR\205\251\002\323\302S\202\323\300\251TT\351\305L9\244d&\205\201\210\310\025*#\016\2650\024\243\203A\2445\023.i6Q\345\323\031*\026CP\262Rm\246\221\3158-/AFx\244\0074\271\245\245\353\326\202\264\322\264\233i\370\342\241a\315B\300TEj\007^j3M&\233\232\t\246\342\232E&)\2568\250H\246\021@\024\343\322\243qU\234UIEg\252\324\252\2652\255N\253S*\324\352\2252%N\253S\242\324\312\265:-L\253R\252\323\302\322\343\332\200*U\025*\212\261\027$U\305\010\247\347\0314\327\230.Dc\203O\211\303\2140\0242m84\321\212\221c\315+[\236\324\337 \322\371\036\324\206,Tm\035@\311P\264u\013Fi\233M\024\322i\t\244\315(jx4\341J)i\244\ni<TOQ0\250\332\243e\252\3561Q\323H\240f\214\342\221\2104\001L\177AQc4\322\224m\305\014*&\025\003\214\325i\022\263\324f\246U\251\225*tJ\235\022\247T\251\325*eJ\231\022\254\"T\312\225(JxZp\024\355\264\241jEZ\224-H\274T\200\223R\004\006\244H\361Sc\214\036}\352&C\236*xP\236\247\025q\023\324\212\220\306;\n\215\242\250Z:\211\343\250Z:\211\243\250\031=\252&\2175\023\307\212\205\226\230E&)@\247\250\251\002\323\200\3055\251\271\244\250\332\243aQ\221\3155\207\025\004\211\232\204\256)\207\2321M\"\224-+\014\n\204\212n\332wjk\nc\n\211\206j&\025^E\254\364J\235\026\247D\251\321*tZ\235\022\247D\251\321*uJ\235\022\247T\251\002g\245)\214\257ZP\264\270\245\002\245QR*\324\201i\301i\342\245V\305<\267\245\033\260*H\334\032\2345H\256A\342\247\014\030r)\214\202\241h\352&J\205\343\252\354\224\302\225\023\307U\335*\022\264\335\264\340\265\"\2558\322c4\322\270\250\317\024\335\324\204\346\232V\242e\3050\323\030f\242t\364\250J\320E7\024\360\000\246\260\334*=\264\355\235\351\254)\245j&\025\023\212\205\215B\346\251*T\350\265a\022\247T\251\321*\302%N\211V\021*tJ\231R\245\tR \301\342\234\371f\344Rb\223m(Z\225EL\213\232\235c\315N\220\257\361\032kE\203\305FW\024\240\361Mf8\250\304\205\032\255G85:\316\007z\221nTT\242ezL\212i\025\033.j&J\215\243\025\013GU\244J\252\353\212\217\024\341\232\224\003F(4\306\250\332\243\"\201\326\244\n\rG$uY\306*2\336\264\322\300\212\205\210\246f\216\364\356\270\024\366A\267\212lj6\234\323\037\212\214\323I\246\032\205\305V\220Uv8\250\343Z\260\211V\021*\302%N\211V\021*\302GS\242T\312\2252\245J\022\236\251\203\234T\205\001\034\365\246,T\246:M\264\340\2652\014T\352jU\004\364\247b\232S4y&\230\321b\243h\201\246\204\333FqNQ\223\315Y\215\200\351S\357\243}\005\263M&\230\303=\252\026\025\004\213\236\325\t\267\334y\240Z\001A\267\307Ji\217\024\335\224\205*2\264\306J\205\2050\361M/\216E\006\347\214\032\247<\343\265Q{\203\353Q\371\344\3654\276a=\352T9\251vq@\0304\374\346\225z\232c\255B\303\025\031\250\311\250\335\252\274\225U\372\323\343Z\262\213\232\260\211V\021*\312%XD\253\010\2252%N\251R\252T\312\224\361\035/\227JW\035)\273sF\312P\230\247\001\212\220T\310\330\251G\315\322\230\310A\245\010\330\344\322\265\273c \346\243\020\261\353R\01029\024\306\266\3054B\001\346\244X\300\350jQ\037\024\306LS1\351F\342(\363=\251\013\2554\225\365\244\033OJB\0054\201Q\262f\231\262\232\313Q\354\315C7\313\300\252\304\346\243~*\244\262b\251\311>*\263;9\342\230\310\330\252\316\305\r\t6MZ\216J\271\034\231\024\245\250\006\244\003\220hqP0\250\230T\rQ1\250\034\325g\025b1V\343Z\262\211VQ*\312%XD\253\010\225:%L\251S*T\213\036j@\224\375\224\236^iD4\030\351\276]\'\227J#5\"\241\025*pjl\202*\t\t\3155e`q\232\23599\315XQK\260\032cB\rG\345c\245(CC\naQ\351Q\262S\n\232aZaZiZL\021J3\336\227\024\230\246\262g\2655W\373\302\251\\\217\234\342\252\234\324o\310\254\371\301\346\251\030\231\317\025n\336\323\035EZ6c\035+.\356\307\004\220+5\241dj\221\033\025f)*\312\266i\300\346\254 \312\212V\217#5\013&*)\026\252H*\273T\rP\275[\214U\270\305[\215j\324kV\221*\302%XD\251\321*eJ\231R\244\tR\004\247\204\247\010\363N1\2208\024\303\037\255\001\005#(\355H\006(\357J)\313\326\207L\324b0O&\254\306\212\243\203\223R\201N4\337\251\240\342\231\232Ni\254*\")3\232FL\364\250\331qL\"\233\212\220 \3051\227\024\231\2434\202\241\232\000\340\234sY\322&\302EU\220\342\241\333\270T\220\300\255\332\256\307\000\003\201S\030A\035*\254\366\201\201\342\261/l\366\202@\252?g;rE5\020\206\255(-L\213\220y\241\340h\31755\271\347\006\247t#\351P2\324\022-T\221j\254\202\253?Z\201\315_\214U\270\326\255\306*\344B\255F=*\324kV\021j\302%N\251R\252T\201)\352\231\251U)\341y\241\200\355Le\246\021\212n)6f\202\270\246\322n\"\233\274\232o9\251\341<\363V\326\224\212L\n\215\200=)\002\232\017\024\322\302\230j2)\271\"\232Nz\323)\010\346\246P1Mq\223Q\342\223m.\312\n\341O\025\233p\233\311\300\252o\006:\324\"\"3V \214\203W\221x\247\221Q\262\346\252Ml$\353U\215\222\200F*\224\226![\345\025b\010\031\007\024\351>a\206\034\325`\233_\"\254\261$sP=@\342\253H\265NQU$\030\315U\220\326\254kV\343\025j1V\343\025j1V\243\253Q\212\262\202\254\"\346\246U\251U*UJ\220 \240\2554\2554\2550\2550\256\r7\034\321\203M\"\230V\223e(\2175,i\203V@\247m\3155\206*<sA\351\311\250\330\366\024\334f\215\206\215\206\230\313P\225\246\221I\212\221\006N)XsM\333N\tK\267\035i\030\006\025VH\324sT\245\000\232j\302\032\245\020\340\323\300\305.(\3050\256j3\0350\302\t\311\024\024\000t\252\362\301\225$U=\234\363O\"\242a\305WqP8\252\262\255P\230U9\005l\306*\324b\255F*\334b\255F*\312\n\264\225e*\302\n\260\202\247QR\005\247\001A\024\204Sv\323v\323YsL)I\262\220\245FiUsR\025\013J\204T\200\212]\300R3\203Q4\212:TM \365\246\207\006\234$\002\227\315\364\244\336M!\031\246\225\024\323\036j2\244R\253m4\343\311\315(\245\310\025\013K\223@\220c\025^c\232\250\312I\253\020\307\305LS\212a\\\016)\204RSM\024\322)\244TdsPK\010\373\302\241)\305@\353P2\324.\265ZE\310\252\023%R\221+^1V\243\025n1V\243\025f1V\220U\204\253\021\212\264\202\254\240\251\324T\240R\342\214R\021I\266\220\2554\214Q\2674\322\224\306\035\251\236^i\215\307J\211\331\2155d\"\236\327\001G\0315\013]\023\320SE\303w\244iKv\241\010\357R\0021F\352PsOZ\220\014\323YiB\346\225\320\001\223U\037\031\371i\013\343\251\246\231\207\255Ff\31579\247\n\n\347\265\036Ni\352\233zS\261H@\250\230\n\215\2522Nx\244\335FsHi\206\232j7@j\264\221\343\245Wt\250\035}j\264\211T\345\216\251\274y\257\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\004\255IDATx^\355\335\321\226\2338\014\000\320\036\372\377\237\2749{\232m\272\035H\006BlcI\367>2\t\030ld\311$\231\037?xfYo\000\000\000`\210\217\353\261\345\343=\300\264n\353\rl\010\001\000\300\347E\005\000\000\000\031<\312Ck\353\000\000\000\323\363\204\017\020\t\3302&h\303H\342\210\233g\t\000\000\300d\324\263W\370\2738\324\003\000\000\000@0\341\2273\302\237\000\000\000\000\260\021\355\307\342\2075\3277\030\000\000\200\234\326eU\344\352g}.\ru\334\365\305\362\236\031\000\000\000\007(\013y)\362\"\021p\206)\201\277\231\005\240\004\241\237\223\226\237\353-\320U\343h\325xw\034\"\273\004\000\000`\237\352\221.\014,\200\227\254\227\003\000@1\212\000\000\000\n\363\330\020\000\200\272,\016\003p\221\321\0132O\247\274\345K;n\267\321\215\002.\36642\000\000P\213J\020\000\306\2122\367Fi\'=X6\204h\304l\310\313\375\235\314\3414\353\360\013\001`\2079\005\340MY\002\347\222\345D  \367\037\000\000@2\236\333G3\2602\367\273\014\274\305x\201\310\334\301\000\014a\302Ii`\225JSz\016\000\200\246$\230\305\031\000\000\025\025X\360Mv\212M\347\353\246;\003\372s\323\206\240\233\000\n\032Tt\014:\014\2632\000\000\000\000\240\252\343\217\036\254\037\000\000@\025\235\263\377\316\273\347s\307+\305b:]\230N\273\2459=\005\000@8\007+p\271nZ;\377;\360\373\277\002\344\'\016\262og2\005 \253I\342\377\301\262~>a\033\036\322m\226\361\n\220B\2429\314\364\000@\017\346\027\222J\224\005\002\000\345\311l\000\302\020\262y\305\032\034\000\000\364%\347&\224\027\013\010}\307\361\355\353Q_\264\001\250\304\267l+\373\325\371\006\000\000\344\243\324\203\000$\342\000\360\006k\230\000\033\277\213\177\0012\000\235\004\225\211\000\000\000\344\341\203\010\331\254z\364Q\276\334\326\3376\340\177\313\372\252Q\315\310\001`I\001\000\270\222\\\204\211\030\216\343\214,y\016\233\262Q\326N\200\234\3046\000\210\310\014\376\233\013\221\307\234KAs\266\n\262\021\313\001\000\230\205\334t\270 \227<H3\001\000\000\346\263}\340\252\304\002\250\314\017\214\027\267\374\\o\001\000\312\220\010\002\000\000q\251h\342+\327\207\345Nx\207\353\001@j&:\200Wr\177b9\367\331\3255\240_\007\034\3423\235s\233\315\3567\033\022\230\276\223G\312\330\301\234g<\000\000$%\321#\261k\213\374k\217~R\310FS\324}\3762\211\001\300\365\244\220\314l|\2768\376\210@\024\342\003\000\000\314\302z\026\000\000\000@\024Vr\000\000\000\000\000\000\000\340n\211\366]\345h\355\005\000>g\376\207\366\334WD\341s\337\245\351\376\016\"\305\177\003\240\270\267\007@\244\321\3153\341\026\252\001\242y{n\005\030\357v}\254\272\276\005\000\000\000\000\000@X>\376\002P\234\'\316\347\334\\:\000h\313\314\n\000\205\230\370\001\000\200\231\004\371\350\214R\252\270\t\276\316\016\000\000\000\300\033,\347\0009\210f$\026\344!!\000\320\310\005\037\274\270\340\220\034!\017\344\356\327\r\372\317z#\005\374\t\315\213E\017(h\221\007@I\2339\177\263!\006\021\254\021\027\222\347\202F\006\336%\004\024g\000\000\000\360\214<\021\000\200\324<\002I*Q\307\216/\312\336:\342[/&\212D7\020\224&DCY\207\277\010\262\010\024I\035\034\000\000u\325\230\001\217\235\345\261W]\250m\276b\216\004&\3264\336\001\334\265M\245\276!\313\232\323\260\001\300\\\222\335\220\311N\347\205\0067\353%\027\252\347\017\0076\270&\000\325\010\235\000\274\3201q\'\262O\006\306\355\243w\037\361\310ld8g=z\250wO}\357K\377\035\353\314c\257b\353\332\256\006h\313l\000@\006r\364}\3679\337\205:)\370\205\013\336|`\000q\002\000\370\236\222\362S\255\027\342?\351\214\263\357=\373>\272h:\244\364mu{\303\311\010\231\220N\001\372\370oJ\330\233\030\200\224\"\244\027O\303S\204\206\267\223\366l\307\235\330\270#\r\326\363\267\007\000\000\000\370c\306\352\353\374\217^.\347\337\032\315\214\035\007\227+\023\001\000\000\330\220\013\326\266\314\264 0OKz\362<\037\000\340b\023\344c\0234\001\362\333/\276j\024\241;\016\\\204\307K\376\005s\343\23463\025f\327\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-3.textpb b/core/res/geoid_height_map_assets/tile-3.textpb
new file mode 100644
index 0000000..486adf4
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-3.textpb
@@ -0,0 +1,3 @@
+tile_key: "3"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\364/\245.\352pz\225d\247\356\3151\316j65\0314\302i\245\251\244\323sFh\315&h\315\004\323I\246\223M\335HZ\232M4\232ija4\302i\204\324d\324d\323\t\250\311\246\023Q\223Q\263TL\325\0235D\315Q3T,\325\0135D\315Q\263T,\325\0235D\315Q\263Te\251\205\251\244\323I\244\315&h\315\031\242\2274\240\323\251\300\323\201\247\003R\242\223Vb\212\264!\213\025\353E\251\245\250\017O\017R\007\247\026\030\037Zk\032\214\232\215\215FM74\233\2513K\232L\321\2323M&\232Z\230Z\233\272\220\2654\232ijijaj\215\215FZ\230Z\243&\243-L-Q\263TL\325\0235D\315Q3T,\325\0235B\315Q3TL\325\0235D\315Q\263TE\251\205\251\204\323I\244\315&i3Fh\315.isJ\r;4\340i\302\245E\315[\215*\364H\000\253I\355^\231\276\223}\033\351\341\351\341\351\301\363O\334)\215Q1\250\211\244\315&h\315\031\2434f\220\232ijajaji4\335\324\026\246\026\246\026\246\026\246\026\250\331\2522\324\302\325\0335F\315Q\263TL\325\0235D\315P\263TL\325\0135D\315P\263TL\325\0335D\315Q\263Td\323\t\246\223M&\2234\204\322f\214\322\356\2434\240\322\203O\024\361R\242\022j\334q\364\253\221\307\264d\325\204\253q-z\000z7\322\356\245\017N\017OW\251\003\346\235\2735\033TML\315\031\2434f\214\323sHZ\230M0\2654\2650\265!j7S\031\2522\325\031jaj\214\265F\315L/Q\227\250\331\252&z\215\236\242g\250Y\352&j\211\232\241f\250Y\252&j\211\232\243f\250\213S\013S\t\246\223M&\232M7u!jM\324\233\251A4\360\rH\020\323\304u\"\305S\254F\254\305\016:\325\250\323\025co\002\245\215*\344K\212\355\003\346\215\324o\305(\222\234\036\234\036\244Y*P\371\240\267\025\0214\302i7R\346\214\322\023M&\232Z\230Z\230Z\230Z\232Z\223u\000\323\034\324L\325\0335FZ\243/Lf\250\231\2526z\215\236\242g\250\331\252&z\211\236\242g\250Y\252&j\205\232\242f\250\231\2526j\214\2654\2650\2650\2754\2653u\031\247\005&\234#8\351J#5\"\307\355R\244C\275N\261\217J\231b\036\225*\305R\004\002\244QSF9\253\210\231\253\021\307V\022:\350\326Jxz\013R\007\247\007\247o\251\025\352U\222\244\017\221\3150\236)\204\323wR\206\245\335HZ\232Z\230M0\2650\232ajijM\324\233\261HZ\240s\212\211\232\243-Q\263Te\2526j\211\232\243g\250\231\3526z\211\236\242g\250\231\352\026z\211\232\242f\250\331\252&j\214\265F_\024\302\365\031~i\013f\232h\000\232\235\"\365\253\t\037\240\251<\272M\230\355O\013OU\315J\253\212\235G\025(\034RT\252*\304K\315^\215j\312%YE\2550\324\360\324\355\324\205\2517\323\303\324\212\365 zx\222\234\030\021McL-\315\033\250\335HZ\220\2650\2654\2650\2650\2654\2654\265!jaj\215\232\240f\305F\315Q\226\250\331\252&z\215\232\242f\250\331\252&j\215\232\242f\250Y\252&j\211\232\243f\250\231\2526lT,\377\000\205F\\w\246\347=)*EJR\242\204Q\232\262\007\313V\020p)\370\243m(Jz\256\rJ\027=*@\265 \024\273y\251\221j\314C\025v<U\250\326\254*\325\260\330\245\337K\276\221\244\246\357\247\253\324\241\351\341\351\302Jz\311N\337\236\264\306<\323wQ\272\215\324\322\324\322\324\322\324\302\324\322\324\302\324\322\324\322\324\302\365\0335B\315Q3\324l\325\031j\211\232\243f\250\231\2526z\211\236\242g\250\231\252&j\211\232\243-Q\263TL\330\250X\236\246\242f\246S\200\245\035j`@\024\335\324/Z\234\036\225j#\225\251@\247\201O\013N\333O\214`\324\333)\300S\202\232\231\026\254\"\036\325b4j\271\0225[D\251G4\326\342\243/Az@\364\340\365\"\311O\017K\346S\326J\225d\343\024\026\244&\233\272\215\324\205\251\013S\013SKS\013S\013SKS\013Tl\365\033=D\317Q3\324e\3522\324\306j\205\232\242f\250\231\3526z\211\232\242f\250\231\2526j\214\2651\232\241rs\234\324li\206\223\034R\321N\240T\2129\253\n\234f\255B\277(\251\302\323\302\323\202\324\252\225(\216\236\026\244T\251\026*\263\025\271n\325r8\002\216je\n;T\311\364\253Q\256j0})\254j\00684\335\324n\245\335N\rR\007\247n\245\337OY)\341\363\212v\352Bi7Rn\246\226\246\226\246\026\246\226\246\026\246\226\250\331\352&z\211\236\243g\250\331\352&z\214\276)\245\352\'j\205\232\242f\250\231\2526j\215\232\230A=x\250\233m0\342\230Xv\024\302F*&\344\323H\243\030\242\212QR(\251Q7\036*\340N\000\253\010\230\002\246U\251\002\324\212\225*\245J\022\236#\251\243\2135j8=j\342\"\250\342\2022i\311\035Y\215*\324Q\346\250\253qC6*\t\rE\272\200\324\340iA\247\206\247\006\243u(|S\325\352@\371\247o\365\246\356\315!8\246\226\246\026\246\226\246\026\246\026\246\027\250\331\352&z\205\236\243g\250\231\3526z\215\236\233\277\024\326z\205\232\241f\367\250\213{\323I\035\3114\302\303\265F\315Q\026\246\223Q\261\246\3474\206\220\2121I\2121N\002\245E\315[\202*\274\261`\n\225R\244\013\212z\256juJ\220-J\251R\252U\204\001G\275N\274\324\240b\225FMX\2162{U\350\255\275j\354p\001\332\271\250\344\342\234NEA#qP\226\240=<58\032pjvh\335F\352]\330\247\253\323\374\312R\340\323Kb\232Z\232Z\230Z\230Z\230^\242g\250\231\352&z\211\236\2432TL\365\031zc=3}1\244\250Z^j6\222\241g\246\357\365\246\226\246\026\246\026\240\364\250\311\244\315-\006\233E\030\247\250\253Q%^\211qV\224t\251\200\245\013\223\305N\211\212\231S5 \216\244T\253\010\234T\211\036MXX\352d\2075a QV\243\214/AV\343Bj\322%p\361\276j\300<T,x5Y\233\006\2205<=H\032\227u;u\033\251CR\356\245\335K\272\215\324y\224\205\275)\205\251\245\3526z\215\236\241g\250\231\352&z\210\275F\317Q\227\250\331\3522\364\322\331\250\331\261P\227\250\231\371\246\026\246\227\246\027\246\227\244\335Jd\342\233\232)\331\242\214R\221@\031\251\243J\273\nU\245\\T\350*`8\247\242\022j\334QU\205JxJ\221c\251\3250*x\343\346\254\254u:ES\244&\255G\020\035j\302(\035*\312-y\324G\275YS\221Ls\214\325)\033\006\232\032\234\032\236\257R\256[\245\004\225\353F\374\322\206\245\rK\272\227u\033\251\013\323K\323K\323\031\352&z\215\244\250\231\352\026z\211\244\250\331\3522\365\031z\215\236\243/F\372\211\3335\013=F\315\3150\2650\2654\2654\265&\352]\324\341\203E8\032x\245\245\0035 LT\211\326\257CVUsV#J\262\261\324\311\035N\243\0252.j\312G\232\220%H\251V#\216\255\"U\204CV\0251R*\223V#\216\254\252W\230F\370\253\010\364\216sT\346\353\232\213u(jpz\265o2\251\371\205\027\022\2536W\212\204?\255H\036\227w\245\001\350\337AzizB\364\306z\215\244\250\232J\215\244\250ZJ\211\244\250\232J\214\311Q\263\324e\3526\222\230^\200\364\307z\201\236\230Z\230Z\232Z\232Z\2234\231\2405<58\032x\247\216i\336\302\246ARb\205\034\325\270Z\257C\315\\\215j\312\255<\014T\321\256j\334q\364\253J\234S\325*t\216\254$uj8\352\312GR\210\352DJ\260\211V\021+\307\204\236\2254sv\251w\346\240\224\344Um\324n\247\006\245\337H\\\232\262>h2G#\275F\262T\233\3517\373\322\356\244\337H^\230^\243g\250\232J\211\236\242i*&\222\242i*#%F\322TfJa\222\243/M/F\372c\265D\317Q\226\246\226\244\335F\352L\323I\240\032x5 5\"\342\247T\317\265.0jt\034S\261OU\251\343\\U\330[\025~#\320\325\201\315J\213\232\271\014|U\330\243\342\247\tR\244y\253\t\035X\216*\260\221\340\325\224J\224%H\211S\204\251\343Z\360\245\233wCS$\2305i\037\"\232\346\2539\301\246\346\227u;u&\354\032\235g%v\346\231\300\357K\276\215\343\265;x\355I\276\232^\2432Tm%B\322TM%B\322TM%D\322Tm%F\322Tm%Fd\2442SK\320\036\232\357\305D^\233\232ijM\324f\2279\244&\220\032z\232\225ML\206\254\253qJ\0075*\n\231V\245T\253\010\265e\022\255\302z\003WQ3\322\255D\225r$\253h*tL\325\204\216\254\244uj8\352c\037\024\344\030\251\325jdZ\231R\247H\353\347tl\032\260\222f\254\306\346\245c\300\250d\342\242\335\2127S\203PZ\233\272\245^A\244\335I\276\223\314\305\036e4\311Q\264\225\023IP\264\225\013KQ4\225\023IQ\231*&\222\243i*6\222\230d\246\231i<\332x|\323Y\252-\324o\2434\204\321\232\\\321\326\201\326\236*U\251\226\254%J\242\246QS\240\253\n\2654iV\321x\251\222>j\334y\025v\023\232\277\032f\255G\035\\\216.\225e#\305Y\216<\325\225\217\024\375\264\252\225:\245N\211V\022:\235c\257\232\310*y\247\253`\325\330OCS\261\252\362\265@Z\215\324\340\364n\246\227\251c\220r3H\347\322\2432SL\224\236e4\311Q\264\225\013KP\264\225\013KP\264\264\303%F\322TfJ\215\244\250\332J\214\311I\346f\232_\024\364\222\236\355\306j\035\364n\245\017N\316i3\212p4\361N\003\232\221EH\242\246AS\240\251\320T\350\265a\022\254\306\265a\022\255\242\000\265:\307\306sR\242\342\255@\270<\326\244\003\201Z\021%ZD\253\010\225f5\253\n\264\375\224\365\216\254,ub8\252\312GR\004\257\230\244}\346\233\320\212\271\031\332\005M\346qU\344\223&\242-I\276\224=.\372ij\217~\rX\337\362\324\014\364\303%!\222\232d\250\232J\201\344\250^Z\201\245\250\232Oza\226\232d\367\250\332J\215\244\250\332J\214\311M2R\371\231\247#\324\305\362\265\016\356iCR\251\247\251\301\247\236FE*sR\250\346\244QR\252\361OU\315N\213S\242\324\350\265f4\253H\225:%Z\215*\312\002F\000\251\322?Z\260\221{U\250\343\253\360\'J\321\204c\025q\026\254\"\324\352\2652\212\231W&\246\t\322\254G\035YD\251\325i\341y\257\224\267R\253d\365\251\326\340\214\017J\223\317\310\250\231\362i\273\251\013R\207\243}4\2751\236\244Ir\234\324L\365\031\222\232d\246\264\225\013IP\274\225\003\311U\332J\214\311L2SL\264\303%4\311L/Q\226\246\356\247\003\232\221x\247\227\342\242\r\363S\263\203OV\251\001\315J\207<\032z\256\rN\026\244QS*\324\310\225:\307S\"U\250\343\315Z\216:\266\221q\232\231#\253QGV\322!\216\225:GV\022*\263\034uz\010\375\252\342GW#\\\212\265\032T\252\2652GV\021*UNj\332\'\0252-L\027\212r\212\371\'u\001\261N\rO\017\212\013qF\352M\336\264n\244-M/Q\263\346\204~\324\326|TfJa\222\230\322T-%B\362Uw\222\240i*&\226\243ii\276m\'\231M2S|\312\013Te\371\247\254\270\251<\352x|\212f~j\223w\024\34452\032\225\0175eFj\302\n\221V\246E\253q&j\312\307\355R\244Uj8\261\212\267\034Uv8\270\305J\260\373U\230\341\366\253+\037\250\253\021\307VV:\2364\253\221\014U\224>\225n,U\225\"\247J\262\213S\252T\250\234\325\224\025:\212\230\nP\265\361\376\374u\245-\273\221\320Q\346d\323\267r*U`x\244\'\232i4\233\251\013S\031\2522\324\253\300\3156F\342\2533\342\2432\323\032J\201\344\250ZJ\205\344\252\355%D\322TM%0\311I\346\322\371\224\236e(zij\025\351\333\261S\304\324\245\271\251\024\344S\324\342\246CV\"\003<\325\330\370\351S\252\324\312\275*dNj\364)\322\255\"U\244\2078\342\247X\261V\243N\225n5\2531\240\253J\243\002\235\220\rH\216;U\204b{T\350O\245Y\2103\032\273\032b\244\335\264\342\244B\331\310\346\255G&:\365\253qL*\332J*eqS\243\n\260\246\246S\221R(\257\215\213z\232\2267\302\034TA\260\334\324\241\263\315(~})\305\275\r7y?J\013SKTl\324\300\334\323\214\230\353PI(#\212\254\362T-%1\244\315B\362T\r%D\362Ui$\250ZZ\215\244\246\031)<\312Q%\033\351D\224\375\331\024\014\346\236\017\025,MR1\311\342\244\211\275je\301\251\220b\255F*\334~\365m{T\350\275*\334q\216*\324k\212\271\032\325\350\0235d\303\200\r*\340T\310\330\251\321\316zU\224\311\251\304\031\031<\324\251\t\354*\322[\271\253p\3321#5\261ib\253\202ji\342\000|\242\250\264M\273\245^\266Q\2140\253&\327w+Q\230Y)\213#\206\305\\\216V\342\255\307!\305[\212L\342\256\306r*\302-|`i\276a^\224\007,j`\330\024\273\263C\034\n\013`b\233\272\220\267\025\03357uG#\324\016\365]\336\241i*&\222\241y*\026\222\242g\252\362IU\332J\214\311L2Ry\224\242Jv\372pzz\275J\0374\360jD\251~\265,uaz\325\204\031\2531\n\270\203\000U\270\327\"\255\"t\253\221-\\H\363V\342LU\310\260\265)\223#\002\234\210I\346\255\305\026x\002\257Ed\314>\355\\\207On7V\214V\034c\025n\033\000:\212\273\035\242\000\t\351S\013d\316EXED\357R3G\214\001\232A\0227U\251R$\037\303R\205\031\371F\005+\303\221\3275\017\331\207a\315*\246\323\310\247\026\njX\344\346\264\255\344\r\212\320N\225\361a4\323@\342\236\0374\340\3243d\nB\334\234\323Kb\233\273\212a99\246;\342\253\273\324,\365\003\275@\317Q3\324N\374Uf\223\232a|\212\255#\325v\222\2432S\013\323|\312z\311O\022S\203\324\213%J\257R\253T\361\265MR\306j\312u\2531\212\267\020\315[J\273\n\360*\364i\234U\270\322\256D\265eH\025&\342G\025=\274e\316\024d\326\315\256\230\317\202\303\025\255\005\212G\216+A!T\034\212\231B\2020*\30279\002\246POJ\225b=\352e\2074\357\263\234\325\224\266P2y\251\004c\037(\247*T\241)|\2726b\221\220\021\315f]\023\033c\265Ij\306LV\244A\243\301\355Z\2606\345\025\361ni\271\2434\241\251CR\223J\314\n\202z\324d\344\322c\202)\214\330\252\356\365\003\265@\317P;\324\014\365\013=F\317\305U\221\351\202L\212\206V\305Tg\250\214\224\205\351\273\351\301\352Epj@\342\236\257S\253T\310j\314|\325\220*D\0305f>\265r1\305Z\214U\310\3278\253\361/\025\241\n\360*\352\'\002\246^*tN\346\255End<V\355\205\232FA\3075\265\033\0000\005J\2715aA=jdJ\260\211VcJ\260\251S\242T\253\036\343Rm\344\212\220F})\351\027s\326\237\260R\204\240\306)\214\225\233{\016\3420)\326p\354\353Z\341r\200b\255Z\251\003\006\276+\315&h\316h\315(4\273\251r\017\007\2458\000\0055\333\002\252\310\371\252\356\325\003\265@\355U\335\252\006j\205\236\242g\252\322=B$\301\250\345\220\232\246\317\311\250\313\322o\246\027\245\022T\210\365.\342*Dj\265\033U\2045f#V\321\252d\253q\n\271\030\351\212\265\030\253\360\247\002\257\302\274U\370S\201V\301\300\251\341M\307&\256,E\210\013[6V\273PdV\254H\007J\264\213VQj\312-XE\253\010\225j4\251\325*A\307\025n\025\030\247\010\306\356*`\224\316\344\nz\212~\007\343HV\223h\006\240xC\222H\2428\300l\n\323\212\037\224f\247\216,\034\327\304\006\2234\231\245\315.h&\223u(\227\002\242\222M\325\0035@\355P;Ugj\201\332\241f\250\035\252\026j\255#Ugza\223\212\255#|\306\242-H^\232^\227p\251cz\234\020E=MY\211\252\322U\250\252\352\016*t\025n!\322\257B=j\354KW\241Z\320\201j\364`\001R\250\334\303\025\243\004Y\300\025\265kl\240\002\302\264c\003\265ZAVc\025f1VPU\230\326\255F\265j5\315YU\240\307\316j\334\021\222\265<Q\374\307=\251\345qP\221\226\342\244\002\245*\252\276\264\305 \212B\200\362M5\306@\002\237\005\271$\034V\200\033p*@k\341\243L&\2234f\224\032Bi7SKTL\325\0235B\355U\235\252\273\265@\355P1\250Y\252\027j\253#Ugj\210\265A+r*\"\324\302\364\205\351C\324\310j\322\221\266\234\244\346\254\304\325r3V\342\355Wc5j1V\342\034\212\277\020\253\321\014\325\370V\257D0EX\317LU\313u\307&\266,\"\334A5\260\270\030\002\254\305V\322\254\245Z\214U\230\326\255\306*\314b\254\3060EZAO8\034U\373|2b\235\267nsQHK\034\nhV\317J\231P\234qJ\352FEV(\301\370\351V\022\022\335j\302\333\201\326\254$x\245e4\252\276\265\360\333\n\204\365\246\223FisHM&i\244\324Lj\0265\013\232\254\346\253\271\252\356\325\013\265B\306\240\221\252\244\215U\235\252=\325\024\255\305BZ\243&\232Z\205j\261\033\325\205z\231\0335j#V\3428\253\321\034\342\256F*\324uv*\320\206\257\302:U\370G\"\255\216*\335\272n\344\325\330\027{\214t\255\353e\021\240\035\352\342\032\267\035[\216\254\307W\"\253q\212\267\032\325\230\326\254\306\274\325\2001A^sWm\270\305Y\312\263`\323\035\0009\035*\274\227+\031\250N\247\351\201B^\031\\\016\265\251\034a\324\020*\302\305\216\225*\246jU\2175(\204w\250\335\0247\025\360\223\032\205\2523Gj3A4\334\323I\250\232\241cP\271\252\356j\263\232\201\315@\346\241cU\244j\252\355U\2445\036j)\017\025\016x\246\023M&\224\032\225\rYZ\2363V\343\253\221\325\350j\364F\255\307\315\\\213\265_\207\265h\302:U\370\370\305N\2373\001Zp!\332\005hZ\307\206\025\250\215V\3429\253\221U\270\315Z\216\256EWb\355V\3435n>j\302\2561S\016i\315\320T\320\266*u9<\365\245\221\266\216\265\227:\231\033\212\317\2226F\255]&\r\356\t\256\215#\n8\247\242\234\373T\333x\244I6\2674\351&\335\302\324Y\311\346\276\024\"\241j\211\250\006\202qHM!4\302i\214x\250\036\253\271\250\036\240z\256\365]\352\007<UY\rU\220\325w4\300y\346\242\220\365\250\r0\232J*D5j6\342\246F\346\256D\325z#\232\277\r]\205sWc\\U\310\205_\204c\025\241\025]V\033j\335\252\344\346\265`\255\030\260\243\212\267\031\315\\\214\325\310\232\256Fj\334f\256Dj\344f\256EWb\346\255\240\315J\027\024\377\000+&\225>Y\000\253\312\200\214\212I-\367P\266H{U;\273\020\275\005K\246\257\226\370\255\325PE;\201F\340i\n\212B*&\310\351_\016:b\2538\305@\324\212y\247\036\264\323M\3150\232\215\215D\365]\352\026\252\357U\336\253\275@\346\252Jj\244\206\253\261\246u\250\330\324,j2iz\nAOZ\231\032\255E\315^\210U\330x\255\010{V\204#\030\253\2503V\343\030\025r#\216\265r)*\354J\315\327\245_\205\266\340V\255\250\316\rh!\305Z\210\325\310\215]\214\325\250\315\\\215\252\344MW#j\271\023U\330\232\257Ds\212\2621\201Rn\003\245B\315\363\325\250\346!1K\346\2615<R\034\202j[\225\014\231\252\021\270\216Q[1\311\205\006\223y,EH\264\356\364\206\230y\257\210\235A\025RT\252\216\274\3231\203JO4\323M\2465D\306\243sP=B\365]\352\007\025]\352\254\206\252Jj\253\234\325w\246\257z\211\272\324\rL\240\232L\324\212jx\352\324uv#W\242\347\025\241\017j\320\207\265hF8\2531\324\350{\n\275\016\027\031\353Z\020\313\221\201W\355\343.sZ\220\260A\216\365r3\232\271\021\253\221\232\265\033U\270\332\256F\325n6\253q\265\\\215\372U\370[5z\'\253(sO$\366\246\204\346\246E5:%Y\215*r\273\220\212\307\234\025\227\217Z\320\266\237\200\255VP\202\306\247\024\242\202j2k\342f\025\013\256ER\225pj\022\265\023\036\271\246\253v4\036\264\323Q\260\250\232\241aP\270\250\034Uy*\244\206\252\310j\244\206\252\271\305@\335i\005B\335MB\324\312c\036h\024\365\2531\325\310\205\\\217\222*\3645\241\rhC\332\264\"\346\254\216\005K\t\3475ad\313`V\275\234D\201\232\327\215\202.\005O\023sW\242j\273\023qV\321\252tj\265\033\325\350\236\255\306\325r&\253\221\265]\205\361W\241|\325\304l\324\3523R\005\305J\242\254 \251\327\212\232.s\364\254\353\210\201r})\321\240\0035j\023V\001\247)\244\315FO5\361ST&\253\310*\253u\250$\353P\323\263\221\357HzS\032\243aP\260\250XT\022\n\247%T\220\325Y\rT\220\325g\250Z\232;\324-\336\230i\204TX\311\251\222\002\325!\210\255=\005Z\216\256\304:U\370E_\207\265h\300j\364b\245-\212zK\205\253\226\274\260&\266\241\220\200\000\253\220\271\'\232\275\021\253\261\265\\\215\360*\312IV\021\352\334MWaj\271\033U\310\232\256D\371\253qI\316*\374M\214\032\277\t\357V\343~qVP\206\251\002\355\251\026\245\335\201K\024\303\232\245qq\202\331\357L\212\1770\205\025\243\027\002\246\315*\236iI\353Q\232\370\271\226\240aP\270\315T\221H\317\245U\177Z\214\212fpi\364\204TdTL*\027\025VJ\247-S\222\252IU^\253\275B\302\233\332\2414\334Pc$Tay\253\260\201\306jFPA\305F\253V#\025r!W\241\253\320\326\2045v6\300\247;f\233\031%\200\255{a\2001Z\220\267\025v\023W\242j\267\033U\224z\263\033\325\250\332\256\304\365n\'\253\261\275[\211\352\344MV\324\364\305^\267\2238\006\264\340|qVP\374\325a%\305^\211\204\213N+\264\373S\325\014\247\013J\366\376R\234\034\361X\272\211*F*]92\0015\256\235)\333\250\r\3158\2650\265|j\353P\262\324,\265\004\211\220j\204\313\216\225\\\323\030R\253v4\374qL\"\243aU\344\252rU9j\234\225VAU\\Uw\025\t\024\323\322\242\"\205\\\323\312\361L\331\315L\203\024\346n\324%X\214U\250\352\364=\005^\203\255^\216\254\253b\236\032\226&\303\214\326\265\273qZ\020?j\277\023U\310\332\255\243\342\247G\2531\275\\\215\352\334oW#j\273\023qW\"j\275\023t\253\2617J\264\234\034\212\275o!$V\202\265L\206\254E!C\221Z(D\210\rIn\n9\367\251\'\031J\306\324\340$\006\247\331\r\250+@\032\t\244\006\234O\025\021s\234\036\265\362\003-FR\243h\352\007\217\212\316\236>MTe\301\246\025\246\355\3475&8\246\225\342\242qUd\252rUI\005T\220UY\005V\220Uw\025\013-4\255D\313\3159\006\005;nW4\230\246\223\212h952\n\263\030\2531\365\253\261U\350{U\350\315M\232x<RFI\220V\275\273`\n\275\023\362+B\027\253\221\275XG\253(\365j7\253Q\275\\\215\252\344OW\242|\325\270\236\257\302\365v\'\253\261\266j\334M\202+F3\275x5<G<\036\265>p*{{\222\204/j\322I\200 \372\324\3228#\216\365\235\250\260\362\300\250mxQW\003Rn\245\315.\352%Q\267x\355_#\262TE1Q\225\250]*\224\361g5\237\"`\363P\225\246\225\247\017J\030qU\344\252\222UI\005U\220Ui\005U\221j\263\255Wu\250\212S\031j=\2314\360\231\340R\274ei\241)\222\257\034Th\265e\026\254 \251\343\034\325\330\252\3545r:\237\265*\032\222 \004\2315\247\t\343\212\267\033t\253\3617\025n6\253(\325b6\253q5ZF\253q5^\210\346\256D\330\253q\275]\211\352\364Rr+B\'\253\221\232\275n\3705qFy\035j\300\345pjkU\035\372\325\254g\247j\236\',pz\n\316\324\244\314\241i\320\034(\251\367Q\272\200\364\355\324\273\262\214\t\342\276Rd\250\214t\302\225\004\213UeL\326t\361\363U\331)\205=)v\2201Q\222T\340\364\250%\252\222\n\255 \252\262-U\221j\263\255Wu\252\356\265\036\332\215\222\221S\034\324\210\203\322\234c\315F\321\342\242e\317Zb\245N\213S\250\002\246\214d\325\270\305\\\212\255\307V\0074\252>j\220\374\244\032\277n\331\025n3W\342<U\230\332\255+T\361\265[\215\261Vcz\271\023U\370\037\025y_ T\350\374\325\310^\257B\375*\374RV\204\022\014\001V\243\223\232\320\212L\212\264\255V\"85e[\232\231[\0035\215;\371\227\'\332\255\306x\2517PZ\223u8=9[\203_-\236j29\2460\025\021Pz\325yS#\212\243,95]\355\375*/+\035i\254\265\004\251\306j\254\202\252\270\315Wu\252\316\265Y\326\253\272UgZ\201\326\243+Q\224\311\241\223\260\247\252b\234x\250\\\346\243)H\027\025*\n\223mX\211qV\220U\250\352\334uaE?\035\351G\315\305]\267\\\001Wc\253q\232\265\031\251\321\252\324f\254#{\325\230\332\256\304\365v\026\253\250\374T\350\374\325\330Z\257D\365z\'\253\2615]\215\352\344S\205<\236*\364R\206\344\032\262\222b\254$\231\247M8X\2175\231\021\313\226>\265y\033\212v\352M\324n\2405H\255_1\025\250\312\324l\265\023\n\211\327\212\256\311Q\025\333\332\240t\311\250Y*\t\027\212\245\"\365\025U\306\r@\353U\244Z\256\353U\335j\264\213P2Te)\004tyy4\273*6\\\323<\274\363OX\367R\233zO/m=V\246AVPU\224\025f1VR\244\305 \030j\277\017J\264\206\254\304j\334f\246CV\024\342\245G\2531\275\\\211\353B\007\343\236\225ie\315X\215\363W\242z\273\023\325\370_\245^\211\352\344oR\003\271\261\232\323\200\225P\005[V\251\267\0208\252SL\354v\223SC\300\025d5.\3727Q\272\22459Z\276me\250\212\324l*&Z\211\205B\313\326\242d\250Yj\027Z\253(\252R\202\017\025M\371j\215\226\253\272\325wJ\257\"UvL\232\211\322\242\331\223Jc\300\244\tA\216\233\345d\322\230\251V,\032\231\"\315#Z\363Q\030\n\366\247F\234\325\225Z\235\026\254GV\024T\242\223\034\325\2503\212\271\0375f>*\312t\251\220\363V\024\324\250jt`*\334-WRLU\230\3375j\'\301\253\360\276j\364.\001\031\253\310\300`\203Waz\275\033\361Vm\206\351FkMxc\355J\267J\247\031\251\305\300\333\234\325r\333\3375e\016\005I\272\227u.\352]\324\241\251\352k\347\246\207\212\201\243\250\314u\013\2475\013\'\265B\353P\260\250XT\022\n\251 \353U\312d\034\326s\256\030\212iZ\211\322\240d\252\322&N*#\026\005B\351\236(\020\200=\351\217\035\'\227\212<\2726zR\371t,y52E\203S\371{\227\336\230\326\371\355Q\233r\247\212x\216\244T\251\025pjt\025 \004\324\210\207\251\2531qVc\253h8\0252\364\251\022\247S\300\251W&\247\214z\325\244|t\251\343z\271\023zU\3049\372\325\270d\305]\211\352\364RU\350^\257\305%^\265\227ksZ\261\220Ww\255S\272\217k\006\007\203R\303\222\006MXQ\212\2206)\341\251CS\203R\346\234\032\236\246\274\r\333\002\253\261\250\331\261P3TMP\262\346\240u\367\250\035N*\006\004\216\225ZD89\252\344qT%O\336\032n\312\215\226\241t\315Bb\357PH\230\246,<d\3222\034\323\032>:Q\345\361Hb\243\312\366\243\3134\242<\032\220-9x\251@\004PR\233\260f\245H\201\024\361\020\251\025\000\251UG\245J\0234\021\264\200*\314c\246j\332\216)\352jU\251\324\360*t5:5J\246\245F\253\260I\212\273\033\346\255\306E[\214\325\310\236\257C%^\211\352\3442a\205lE\'\356\361Q\334I\362\250\245\205\361V\203\346\2245?u(l\323\203S\203S\303S\201\257\004qP\271\250\035\352\273IQ4\265\023\313\305Wi*&\222\231\2734\307\\\212\252\311\212\253:\014g\275C\214\212\215\226\240n)\215\310\250\032=\306\244\021|\265\031\206\230c\244\362\275\250\362\250\021\212_&\223\312\244)\212P\225\"\255?o\024\322\234\323\324b\236)\300T\213\301\251\327\326\243\335\271\352\304g\221V\225\252E5*\232\225ML\206\246S\306jEl\325\2045b3W\242n\225r6\253\221=\\\214\325\330\217J\271\023\325\244~\365~\332\344\343i54\222n\024\350\332\254+\324\241\251\341\251\301\251\301\251\301\251\301\252Ej\360\211\rU\221\252\244\215U]\352\026z\211\236\242f\246\023\232r\212y\\\212\202H\370\252\222G\236\265\003\304T\373S\032<\212\254\361\020i\2062h\021\340R\355\342\243d\250\331(\tHc\244\362\361I\202)z\323M\"\216j`\005;\024c4\230\346\235\212x\024\341R\216\225]\270\223\332\255\305\332\255)\004{\324\253R\255J\2652T\240\372\324\253S#U\210\315[\215\252\344MW#j\273\023\325\350\232\255\306\325e_\212\232\027\303\n\272\357R#T\352\365*\265J\032\234\032\234\032\234\032\236\032\236\255\315x\\\246\251\311U$5Y\315@\306\242&\232E&)V\246Q\305+G\362\232\241\"\340\325yNF)\241\306\323\270T$\206\351HPb\232R\233\345\346\220\307M0\346\232\321\342\243+\212i\250\230\3233M\335\212i\220\n<\352p\236\244YjU9\247\250\251DD\364\247\213f\245\362\266\3655\033\305\236\2254Gh\346\246C\310\253IR\250\251TT\312)\343\212z\265J\246\254Fj\334g\245[\215\252\344mW\"j\275\023U\330\216j\322\216*x\370 \324\373\363R\306\334U\204j\231Z\244\rO\rO\rN\006\236\r<5xt\225RZ\247%Vz\201\2522(\305.\332\002\363VR?Z\216\346@\243j\325\006\250\035\t\250\231\030\016\224\301\031\317\"\237\266\232\302\232\0074\2458\246\221M`\030Ui\001\006\242\'\025\003\232\205\237\025\033IQ\027&\22015*\006c\300\253Q\333\310q\301\253q\333H\007\3355e-\237\031\332i\342&\035\2158\254\200t5ZF#9\250\222|\036j\310;\271\025*d\n\271\031\340T\340T\242\244\006\236)V\246Z\231*\324f\255\306j\334f\256D\335*\364M\322\256\302\325v6\315\\\214\003\031>\225\t\233i\251b\270\025n93V\025\252e4\340i\340\323\301\247\003R\003^+\"\3259\227\255Q\220u\252\316*\002)\204Q\212Z\232\030\363\311\245w?\303U\232&s\223J-\375E\006\334SL\000\366\250\244\200\016\325Y\223\031\250Xg\2450\214\032p4\326\3053\245G\"\203U\\b\252\310*\273)4%\273Hx\025m4\247n\306\255E\242\271?t\342\265\255tE\030$\001W\377\000\263\242U\343\255=mbP8\247\030\223\267\024\326\265N\242\201j\230\346\250]X\214\235\2039\254\271,\212\234\343\024\350\240}\330\305h-\266V\244HJ\365\253\n\274T\241x\244<P$\002\244\017R\253f\254Fj\312\036*\304mV\342j\271\023U\330[\245]\210\325\310\332\256B\340\002\017z\204\304\314\307\236*E\201\207\275X\2100\351V\243\223\326\255#T\231\247\003O\006\236\r<\032\361\347J\2472U\tV\252:\324\014)\204R\021M5n\022\014|S\322\035\334\323\314@v\246\230\352&\\TG\255\014\241\205U\232.\rS\333\202j\0318\250w\363K\234\322\212cT\016\231\250\014\005\317\002\245\217N,\303ul\331\351\n0H\255d\260\215\000\342\236#U\340\nF8\246\022MF\331\246\362)7\237Z\004\236\364\355\343\275C2#\203QG\030\317J\262\024\001\236\364\207\031\245Z\220t\250\035\300\004US!\315M\034\276\265n6\310\253Q\265ZC\232\235\rY\211\252\344mW\"j\275\023U\330\333\245Z\215\252t\346\254%K\264u\035i\244\367\035EK\024\231\253hr*AN\024\361O\006\274\226E\252\223/\025\237*\3259\026\253\260\346\243\"\232E4\2415,D\250\305_\200eO\024\346\004\236)\276Y\357P\312\270\252\376]!\\SfO\220\326pB[\030\246\334A\204\310\025\232F\032\244QN#\002\230FjH\355Z^\202\264\355\264\262{V\202YG\037Q\315M\200\275)\215%1\237\322\243$\236\264\224\322i\204\323MFN)7R\026\240>:S\274\312PjE5 <Uk\210\333\250\351U9\035i\312\325j\027\307Z\275\035\\\214\234sS+U\230\332\256Fj\344G\245]\211\272U\330\333\212\265\033U\250\332\255F\3258\366\244x\311\345z\324`\0259\253p\311\221V\223\232v1N\006\234\255^W%S\230U\031\207\025FQU\210\246m\315K\0349\353S}\234c\245:;M\307\245\\Kr\203\030\2462\200x\245+\201U\234\014\363\315D\330\003\201P2\346\227fF\rD`U\344\n\255r\000\214\326)L\261\251\025qC\220)\221\251v\300\025\277\247\331\034\002\303\025\247\263`\300\250Y\252&&\243\3154\232i4\322i\214i\231\244-M&\230i\271\244-NSR\003R\003R\006\300\247\3440\301\2523\306\003eEB\005O\0305~\002E^C\305=j\314f\256Fj\344F\257Dj\334mV\320\325\250\332\255F\325j3\232\231i$L\214\325p\333\032\256\305!8\305M\311\247\000GZp\257.~ES\226\250\313T\345\025\\\216i\361C\270\364\253\211\016;U\210\355\363\324U\330\241U\035\0056\343\n\274U\0227\034\323%<qU\230f\242aM\305(\024\307\\\212\317\272S\264\326[\020\246\2432zS\222&\224\360+Z\306\304\002\t\034\326\332\3425\300\250\244\2235]\230\323\r4\221L&\243-L-L-L-I\273\024\322\364\335\324f\2239\247-H\r<5;4\345jl\204\032\210(\31752\001Vc|U\244\222\245\r\232\263\033U\270\216j\354g\025r#W#5n3Vc5i\rZ\215\252\302\232y9\025VU\301\247\301!\007\025u\0375(j\007^+\313\244<U9MT\222\252\310*!\036Mh[\301\201\234U\225\213=\252dL\nq<{UY\316\343\201\332\242\333\305C \250\030Svf\230S\024c\212\215\306*\205\3361Y\023\250\'\345\346\231\024\005\210\310\255kx6\340\001\212\325\210\010\320b\202sQ\265FsMcP\263Tl\325\031j\214\2654\2650\265!ji4\204\321\272\2245(jpjw\231\212p|\323\303R\026\024\200\363R\253\001\324T\361\310\t\025e*\302\n\260\234U\250\232\256\304sWb\253q\232\267\031\253iVP\325\2045e\rH\r5\306G5]xz\275\031\310\340\324\240\323\324\327\226\311U$\252\262T\004f\247\267\203q\316+M\"\332\240S\266zP\303h\250\334\361\216\365Y\2074\323\300\252\322\236qQ\3434`\ncS0*\0311\315P\226#)#<T_b\000\363V\342\265D\000\342\234\355\260\361\214\323\321\313\032\224\360*\031%\n\t5J[\325\\\363U\216\242\t\347\245\037o\214\322}\241\033\241\243x=)\206ALi*3(\246\371\264\307\237m@\327\200t\250\215\343\036\224\365\272jx\270cS\244\247\034\323\213\023OV u\2517\232P\371\251\343\367\247\367\251U*\314d\216\265m=j\302U\210\352\354]\252\354Un3V\3435j3VP\325\2045f3S\nq\031\025VE(\331\02542U\260r*E\025\345\222\032\251!\252\315\315\010\233\230V\234\020\355\025c\024\207\201\357Q1\342\240s\324\324-Q\310\330\252\262\034sQ\027\003\2554\315\237jkJ=j\007\271\003\245R\271\272\003\241\346\243\206\350\036\365ed\337R\006=*\'\0074,\273?\n\251q\251\225$\003Y\263_\273\367\252\215#\267SM.GZa\227\322\201pGzQz\313\336\246[\364?z\246[\204q\301\246\271\003\221P<\307\265U\222V5\017&\246\215\t5m#\030\251V1R\252\323\302\212\177\035\351\014\253\234R\207\031\251\221\352\302\266MXJ\260\230$U\310\207j\260\023\214\212\2321\203W\"\355W\"\253\221\325\250\352\324f\254\245XJ\260\206\247S\232\220sL\221r*$\0305n3S\003^U)\252\222\034\324[rj\345\254\005\233\245i\210\266\212B\270\344\324NE@\354*\273\032\211\217<TL3\326\253L@\2522IP<\270\357U\236s\330\325y&n\325RB\317\234\322\300\010`+^.\000\251\2529\016;\325\033\213\225@@\353Y2\271s\232`J\016\005W\222AU\332J\211\244\250\332Jn\372U\235\220\360jU\275q\324\346\236/3\326\227\317S\332\236\222\'z\225$L\361VU\301\357R\006\003\275)\235W\2750\334g\356\320$8\347\232\003sS!\253\t\323\212\261\031\253Q\232\265\027QW\243\343\007\265[A\221NN\265n*\275\025ZJ\265\031\2531\232\265\031\253)S\255N\225*\322\260\310\252\303\206\305N\207\025aMyT\2035\027\226X\361SEhI\344V\224\020\204\035*f\340sU\235\262MV\223$\324,\246\242~*&5\014\207\000\346\263.%\3118\252N\365]\3335\021\353N\n\034`\323M\267>\324\242\r\274\212\235\034\255;\317\342\232\356XqY\362\332\263\234\365\250~\312GQQ\310\205\007\002\263\347v\006\252\263\032\211\230\324Li1\232pBi\336Q\243\3114\322\230\245\tN\013\216\364\360\017cR\2430\357Roj\006M<\034T\212\325:T\3501S\243m\342\254\241\315Z\214U\310\305^\217\221VS\245=z\325\230\352\344F\256Dj\334ua*\314f\254\2475a*e\251\224\324\202\241h\362\331\025 N=\351\352ppk\314\266n5f;lu\034\325\250\341\305M\267\002\241\223?\205C\345g\223\322\241p\240\361U\337\212\254\3475\013\266\001\315f\334\317\270\220\247\212\240\355P75\033\na\024\200\342\232\327\005j#r\344qP5\313\203O\216g|U\264}\243\236\264\3573\'\245)\301\034\325y\242\3348\252\022\330\263d\325\tm\031{Usl\304\364\246\233F\364\2446\305\006H\244\001\207j\031\210\246y\236\246\227p4\322@\243u=ML\206\244\342\214zR\212\221jt8\251\225\252\302|\334T\311\225<\325\350Nj\344g\326\257BF*\324~\2250J\2321\212\263\035[\214\325\310\315YCVR\254!\305XCV\026\244\007\025\"\232\\s\232\224\014\212G_J\363\310b\311\253\313\026{T\2420)\257\201P\2200Y\272U\013\233\241\321zV{\316{\032\256\323\222y4\323.{\325;\211\267p\275*\213\363P\260\250\210\250\332\231\365\250\335\2608\252\315\315>%\365\251\232\335[\266)R\035\2751N8\007\223L/\375\334\032\215\213\372\322y\244pM5\3468\342\253I.z\212\256d^\324(\311\245p\270\347\025VIQx\025Q\2432\234\203M\373&:\265\006\334\216\206\220@\304\324\211o\357R\224T\036\364\300rx\251\000\247\201NU\317J\225P\324\252\2652-L\203\025j2\017Z\267\020\002\256\304GCV\343\307j\266\225a\rL\242\254F*\312\n\267\021\253IVP\325\204\251\320\325\2045.x\245W\251\224\346\245^:S\310\334+\202\206,b\256\252`R0\252\362\270S\317J\314\274\273\317\312\275+-\344\311\252\362IPn\315#\023\212\256\365\003TMQ0\250\332\242j\256\346\241=jH\363\326\244iO\255F\323\036\334S\001$\362i\031\310\350j=\355\330\322\035\346\242e\220\372\324N\204\014\271\250|\345Rr*\031/\030gh\305T\222\351\333\275W23\032\2269\034p\rN\245\217Zx\311\247g\024\231n\302\201\0339\346\245\020m\353N\tO\tOT\"\244\037J\225@\251\227\035\252U\251S\212\265\031\253Q\232\271\023U\310\332\255Fj\312U\250\371\253\010*x\3705r3V\022\254!\253\tS\245H[\212@jTj\262\255\305H=\253\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\010\026IDATx^\355\335\333\266\243(\020\000\320^\231\377\377\344\311\232\311\255\317\211A\243\010J\301\336O\335&\022\254*\020r\351\376\363\207q]\247\007\000\000\000\350\323ez\000\000`\216w\216\001\000\000\000\000\240y\276\007\320;\037\330\000\000\000\014\3046\037\000\000\000\002\261\221\357\231\354\002\000\000\000\000\000\000\020\224\237\247\237O\016\000\2003\370\352#\000C\370\262\351\256}?\254\335>\000\000\000la\237\272\326\2277\024\2003\031\240T\241\260\000\200\026\014\277&\271\016\037\001\250\301\300\002\000\200q\244\277\022p\271\244\217\263\235\035\026\000\000\020\200\255K\232\270\220\2440\350\225\332\006\000\000\000\000\000\000\200\306\370y\017\000\300D\344\005\222oj\026\220]\000\201\242\237}\215\335)\023\211@\251\007\240Weni\014E\321@P%\266\037\327\177\247G\3063\356$x\235)\242q#RV2\270-\222pT\001\037\224\004\014\305\220\007\310Sl\323\227?\021\347\237\311\016\037a/V\nG\t\327a\000\240\214\217e\014\000\000\0004\315Nv\007\237\005@\017\214d\000 \213\275\024@\030\246l8\235a8\250\265\211_\373<\000\000\200\265|\017\000*1\270\000 &o\304\303VF\r\0000 K \000*q\2139\316\330\261\366I\346\237\177\246\007\016u\271\304*@\005\223ic\340bU\305(nY\t\233\231\260\035o\207\020\016@\222a\025Ce\233\237E\340\306\345`\253\024\300\031\352\025\317\346\226\317.\200g\2077\367\033\316r/V\025\013\035+6\300\2135\304\261v\254\215\2469\23745}\030\350\336\216\t\205\346\310\346\300\336\223\177\335vCO=;u\254\022\205;c)0\357\371\271?\363\357\323\227\316\333\250`S\354\224\316E\372\350 \016\234\2456(\220\222\307{z\253.\257\300\253\235*z\377w\233\317\362\374#w\303G\256o\337\322\373\355\361\275\276T\037a\234\236\311\332\245\332\261\323s\027Q\007AK\r\231\313\333\205\335\277\352\237zZ@M\\F\023\235xIv\246\334\257;\236\205\264k\240\354:y<\305r\307\010\272\036]?\027WnJ\353\315\257\002\350\272\026\000xi\366\226X\362>\324\354E\236huLJ&\342L\253/x \367\334\326\nLf\273\275\324\333\350.\271\005\020\226\312\205\035F\2330\000\206\222\275Jrw\250Fh\271d\017\3142\376\326\340\311\375\240\0029\005\240\tV\274p\234\357\343\355\3733b\n\264\364m\246\253\315t\204|\257\001\375\1772o?/~\3765\177\230\'\213\"\2779\340T\311\021\r\000\014\315\372`@_\222\036{\303\367\345\342z\365\223\264\330\351\353\234\344\214\315\017\3449\314\255\326\006\275\035\002\360#\275\364x\334 \334&\016\220N@}9\257\233s\016\007\311\034\255\263\247\315>\300(\214\367C\031q\000\000\235K\254\257\217Z\003\336^:\361\362ws\307\227\034\325\357~\345D\035\000\212r;?E;ao\247\'\024$\255\300K\353\363\201MqE\333\223/\035\345Mc\272=+@H\323\301\177\214\274)&\357,\032\2646\225k\237G\001\347\314\005\234\312\010\033\314$\341\341\006\275\202\355LN\005*\202\200$\215\202r\346\r\326\020Y\240M\226\021T\244\274\356\002\205\241\314r\245L+\r+\221\321\022m\324\327}*\363\304H\036\000\000\300\273\373^\346\366\357V\333\353m\323\326&pO\366\236W\262\247\t\222\202\204\364\270R\016\022\020\000\030\213\033t1\307\255\252\226Ii\023Z)\207\251\352\375R\177\215\223\240\261]\025\300\030\344\231e\325\027\003\023\275Wd\357\327GS\224\333fG\317xU\225\310\177W\001\001\322\322\003\3752\367\000\235\221f\036T\302\340v\177\003\354\262\343\\\032\223\223\312\234s\310\'\336T\245\300:\363\270\307_[^\353\265\334\267\320\226\003[r\250\337~I\002\214hy\236!\214\334D\346\236G\'\024\000@\024\211\031\273\3517\t\250\344\261s\177O\275B\030\234\002(\372\366\0304\250B\211Wh\222\352L\367\274k\254\"L+\301\224I\330O+e\332[\322X\305\237\353\025\214\374\240\024M\235w&\026\255\213N\201<\254t\334+\361\260\256\002\310\"\270023\000c\353\177Ig\214\337\t\303\340f\n\340\232\271\007\337=q\354n`^\305\246\343\232I\263X\3018f\246\001F\241\000\000\370\253\231]@3\035\031\324\001?\370\336\370\n\326+\017\033\303\026@\177W\004\001\031\210\320\260R\003t\266\235\331\007\0060\304\002s\335E\256{\026\364c\300\251\3170\347I)\3600\340<\310/\362\017\034\312\244\003P\313\3103\254\315\355J\267\"y\025J\253A\363\177R\325\326j\346\001\000\0006zno\326\356r\326>\017\000\200#X\235\215\300\'>\207z\206\273\335\261\365\3363\325Q2S%\333\002 $\267\002\000\240+\3365H\213\037\227\330\313\326\370\361\247/\261\307\023@\014\346Z\232\024bY\372\326\311\020=\216\342o0MP+\tT\033^\225k:\330+^\004\357\377\213E\274n\003\005\271\027\227 \212P\2261\005{X\3363u\275\225E\370\302\010\177\001\'\022;\240W\366\r#X\270\213)\000\200w\013S\346>U\032N\314\342\211C\213\266>\277\204\353\031/\332\233IA\025\ti\221FV\2512\034\006\261\030\273\305\007\233\361\253\320bt\270\005\223\321\371\036\270\2310~\214\350\327\201\217\007hV\361\\\335\277\330\004\204\264yB\230\216\367{\003\323\2034\346\232\221\351:\224\312I\236\201\377)\203\234L\334\316y\235\367j)\247\035\372qidf\341`\006>\360\303\2140\n\367\374\215\014\r\030Q##\377\327\367\320\357\223\267\217oZ\223*\224\337\331I=\276\305[\332\211D\326F\267\267\002\366N\036@4\257Qo\364\277\023\217\256\364\234\316\2757\376hF\273\336\257\366\006\344q\376\3077\345\247\177/-{L\326\356\330\313\257\016\036\365\222\247h\372\342\026\252\244\351~\227\262p\375\364m\210\372\246\032\365\003\301\275\177&\367\366\207\rr\316iC\334\236\307b\235\231\253v\205^sr\263\241S\031\2553\006\245\001\000\000p\246*\273\262*\215\022\301\206\367\212\032sV\321\306\215X\302\245\257\313\201\263\2345\037m\027\247\247\241\010+\320,\377\034khU\263W\265\361z\202v\273\025\327\217\355\354\256o\232U\2605\301[\237\017\254\031\354FV\347$x\237\250\361{\374\3703j\357\331l\315l\017\324\267b\3325\\w[\021\345\272\016\313\341\351W\312@\016+kj*\232F3\320\340\024\300\331d`\352m\206\353?<>\351\345\215z\340TE\227\230\305\233\343\253\3523\310\363\277\240\370\370\247(\327\252\336A\362\345&\265\260 \353\242\030\275<Y#%\005\361\230a\000\330\343\324E\330\334V\261\263\233\333\221\2273FDYo\246\"\200.|\033\341\337\036\007\000j\330\260\375r\263\036\333\206R)h\356\235\030 \251\346@\255\3316p\276\364\030O\037=\312\271\257>\246I\314\367\245 \347\354\337\337\251\3139\177L3\221Z\273\216^\216y\342\320\321\326^\010o.{\343\266\367\374%\305\276=[\252\235\n\372\330\311\026\313\324/\345\002\363\210q\271\366\272\262=sa\003\031\266\343Um/\200C|&\253\321\216\306St\t\377\'\225\252]\n7G4\351\002\330]\246p\223./\226\030|\203Z\231\370Rc*\361r\245\232^\345\320\027\013\344\371c\371\300\266\365\277\306\033\032\215\032\347J\001\236\252M|\026\021P\201\201\005g:{\004~\274\376\307\201\031\033o\366\033\237\036\337\177\034\3615X\n\320m\016\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-5.textpb b/core/res/geoid_height_map_assets/tile-5.textpb
new file mode 100644
index 0000000..0cb5489
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-5.textpb
@@ -0,0 +1,3 @@
+tile_key: "5"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\325\210r*\326\332c\257\245S\272\203zq\326\271\353\244*Nj\203>*&;\272S\243OZq\217\212\253\"Ug\025\021\025\033.*\"\265\023\212\254\313M\021\344\323\230m\340TMQ\363HN\005D\304\323w\221NI\260y\251~\320\244t\250\245UpNj\250\201[5\024\226\313\216*\224\220`\324~MH\251\212\260\210jeLv\247\010\363\332\236\"\245(\007|R\205\007\2758*\216\324\274P>\224\360=\251\340T\210\2652\n\235\026\247E\253q\255Z\215j\344kW\"J\267\032U\224\\U\204\025:-[\215j\312-Y\215*p\000\247n\354)\342\244\002\245U\251A\342\245^+\224H\360j\310N)\205EB\351\3075\215\250\332\203\222+\233\271\214\2515\004g\346\346\256*\361JEV\225j\233\255DF*6\025\003T.*&Zh\030\2465D\334\232i\024\302)\214\265\023-3i\245\333A\004\212@\215\332\227\313\343\232i\266R3P\274\000S\004C5:E\236\3258\207\003\232]\241z\322\023L\306\343NU\003\245<&z\322\371b\236\020R\355\245\013R\252\324\252\265:\n\260\213V\243Z\271\022U\310\222\256\304\265i\005N\202\254\"\325\230\326\256D\265e\026\254\242`PF)V\244Z\221y\251\207\025\"\325\210\327&\271\245^j\302\307\305F\361\324.\244\203\212\315\271\214\220w\n\347\357m\216N\005d\264e\036\255\306r\005+\n\255\'5]\222\240u\250\030TL*&Z\211\205FE7mFS\232F\\TdSJ\323\nR\010\251|\272M\270\355Ml\343\212\210\356\244\332\306\224D\307\255H\260\016\365(@:\n\010\250H$\346\223\031\245\013O\tO\000S\250>\324S\224T\2503S\252\324\350\265b5\253q-\\\211j\344b\255\304*\312\212\235\005Y\215j\324kW#\025e\026\254\252\361\315F\364-J\24352\214\np\346\254D\274f\254E\367\253\237)\212\221\007\024\025\315@\311\203Un\242\014\206\262\036\000\344\202+\026\356\334+\236*\262\246\332q\034T\016\265\023/\025]\326\253\270\250XS\n\324L\265\031ZB\224\302\224\306J\217m!ZM\224\340\224\326\300\250Z\233\326\215\236\224\345^\330\251\004g\256)\3338\351M)L\332O\322\232\313\212hQ\353K\201J\0058\n\\R\201O\013J\026\246E\253\010\265:-X\215j\324kV\343\025n!W\"Z\262\213V\021j\314kVcZ\266\202\254\247\025)l-E\324\323\305M\030\251\324qNQS\257\240\251\342Z\306)\236\324\340\230\024\205j\027L\325k\205\371Mf\204\371\3533P\207\014H\025\224\313\212cTn8\250\034Ug\025\003\255FR\230R\243d\250\212\321\266\223m1\222\230c\246\371t\322\230\246\232\211\206j\"(\013R*\342\237\200)~\224\264b\243lTE3\336\223\313\367\247\005\024\360=\005;fz\321\263\024\273i\333qJ\00752-XAV\021j\302-Y\214U\250\305\\\211j\354kVQju\025b1V\243\025j5\342\247QO\3054\256\r9EJ\234T\353\315H\242\254 \253\n0++m.\332aZ\211\205W\225A\0305O\311\352k#P\307#\275bH\274\232\211\2050\256j\'Z\205\324T\014\242\233\260\036\264\326\213\025\023GP4t\315\264\025\244+L+M\"\243e\250\231i\205i\205i1\212N\224\233\21581\247\002i\371\310\246\220\r0\250\244\331\232p@)\352\276\325 L\212]\224\334{Rn\\\342\236\005L\202\254\242\324\350*\302\n\263\032\325\270\326\256\302\265u\027\212\235\026\247E\253\010*\312\n\262\203\212\231jA\3158\256G4\320*U\251V\247\214U\250\327\035idp\200\346\251\021M\"\241\225\266\203T^\340\346\2432\231\010\024L\010\210\342\260n\306\342k6H\352\006\216\243d\305@\365]\306j\026\024\312z\034\360i\255\035@\321\324L\224\320\264\205i\245i\205j6J\211\226\241aL\"\223\024m\244+F\332p\247\204\310\250\330\020x\244\301\247\001OT\024\360\230\245\342\223u79\2441\344{\323\343\007\241\253(*\304b\254\"\325\210\326\255\306\265n5\253\221-\\AV\021jtZ\235\026\254*\324\353R\255J\264\343\300\246\255J\005L\213\221V\243\000\n{\316\261\257^k*\352\3739\301\253\246\230\307\025\237r\3475E\315\020\374\315\305Y\237\0011X\2271\362k>D\250\031*\274\242\251\3109\250H\246m\3155\242\250\310 \324\313\363\255F\361\324\r\035G\267\024\025\2462\324dTl\265\013\n\211\226\230V\223e.\312M\224\205iU1Noja\244\300\245\340R\206\024\027\250\313\023N^jUZ~\336iv\340\203S\3063V\221j\302-Y\215j\324kW#Z\271\032\342\255\"\325\224Z\235\005N\202\245\025*\232\221j\314c\212k\036iTT\261\256ML\\ \252\322\337\204\350j\224\267\245\272\232\253\270\312}\253\245<TN+:\340u\252R\016\rE\014\233d\253\023J\010\254\371\271\252R/5ZU\252r\257\006\2522\234\324e)\230\307Zi\344Te\t\247\306\2705#G\232\205\343\250\032:\214\246*2\264\302\265\033-D\313Q\025\246\225\246\355\242\220\212JF8\351Q\223\2323HO\245!\367\244\335\216\224\231&\224\n\225EL\2434\374`S\212\344\212\261\032\020\325m\026\247AV#Z\267\022\325\270\305\\\214U\230\305X^\005J\225:T\303\255H\2652\216jq\302\324}MJ\253S)\300\250g\014\340\366\025\223pB\344f\252\002]\272\325\350\227\000WHG\025\024\230\000\326e\311\347\212\244\307\255V\301\335H\354sP\2775\013-W\2213T\344CU\335@\250\231sQ2R\010\271\247\210}i\255\026\332P)\032<\212\201\243\250Y*\026LTl*&\025\013\naZc-0\256)\244SH\244\305!Zn\332n(\013\232k!\246\343\024\n\225EH\005<S\327\222*\310\2178#\265N\203\326\254F\265a\026\254\242\325\230\305Z\217\265Z\216\254\245N\265:\n\262\202\237\212\221j\302\017ZWn\302\221j`jU\246J\245\201\305a\336.\3065\005\270\346\257)\342\272\031$\n*\224\323\0228\2522\261&\252\266i\270\3438\250[$\323J\323\031*\007J\257$UY\340\315@\320\221M\020\372\323\274\254R\371u\033\246j\002\204\032Q\356)\0320j\254\221\324\014\276\265\013-D\313Q\025\250\330S\010\246\021L+M+I\262\215\264\302(\t\232x\216\202\242\243(\r7e=V\245\013\305\033i\350\2705r>EL\253V#OJ\267\032T\352\270\251\343\025j5\253(\270\253\010*\302\n\262\203\212\235E>\236\202\246\007\002\230[\232z\232\2206)\301\352@r+\013Q\220\0075\005\261\310\315\\\007\212\336\237\322\2522\325y\022\230\2109\334)%A\216*\261\213\322\233\345S\035p*\253\014\232c&j\026J\211\343\250\374\272B\224\335\264\326Z\201\326\230\026\215\265\014\261\367\252\256\225\013%B\311Q\262\324,\264\302\264\302\264\302\264\233h\333F\312iNiBb\224\214t\250\230\032m.)B\324\200R\201O\013\212\2263\203W\023\006\254 \003\025e\0179\253\t\311\253Q\245YE\253\n*t\031\353VQj\302\216*@qN\0075\"\323\213qH\274\365\251\001\002\223\314\317JUl\232\260[j\023\355\\\276\2416\351\210\007\275X\265\030QVI\256\226X\362*\233\251\007\245!\213\"\242h\366\324.3Q\354\246\262\325y\026\2520\301\246\221Q2\323\n\361L\333M+M)Q\262\324.\225\036\314Q\266\221\223\"\251\310\2305\003\255B\313P\262\346\242e\246\025\310\250\312`\322\024\246\354\240%.\332M\200R\025\244+M+\232aJM\264\340\264\354R\201O\307\024\016\r[\205\262*\302\236j\324F\256\306*\334hx\315[\215*\302GV\021*T\03056q@\346\245Z~\354R\016z\322\226\240\232E5*rE\027\223yp\266=+\224i\014\267\034\372\326\274<(\251\031\253\257a\232\205\343\250\031j\027\031\250\nS\n\324l*\254\203\255Ve\346\230V\243e\246\021L#\024\230\244+M)Q\262Te)\214\265\031\340\3242\307\273\232\252\311\216\265\013GP\230\352&J\214\2450\255&\312B\224\233x\246\355\305&\332]\224\326\025\031\\\323H\244\305(\024\270\247*\324\230\243fj\304K\267\255Y\215sV\342N\225v1W\"<\n\270\235\252\302\324\302\235N\025\"\324\231\240ry\247\026\002\214\323I\315*\361R\306~a\212\253\253I\266\023\315s\226\243t\244\326\322\034\n\031\253\271+\201P\275Ts\203Q\036i\254\264\302\271\025]\324\203P:\346\2532b\242e\305F\302\243\333C-G\266\223h&\224\255FV\230\313Q2\324\0169\246{\032\202T\364\252\344Tl\242\243d\250\231*\026Z6\323J\321\266\232V\233\212R8\246l\3157m\006<\212h\212\235\345P\"\346\236#\245\331\212U\0375H\300\361\212\265\000\343\232\271\030\253iVc\253q\0360j\322\232\225i\364\340i\341\251\331\244-\212\001\247\nu(\251c\342\262\365w\314x\254\333(\361\315h\203\201Lf\257Aq\305Wq\305A\345n&\230\320\343\221Q\225\246\025\250\035*\006^j\007LT\014\242\242d\315&\312aZ\215\226\233\266\220\212a\024\3223Q8\305@W&\230\313Le\014*\007\212\240d\305D\313Q:\324%h\333HV\200\264\2333L1\322l\243e\'\227HR\224%8&i\342:_.\232\313I\262\236\007\255X\217\002\254F\325j3\232\267\035Z\217\203VS\232\235)\304\320*A\357J[\322\223\2558\nz\323\361\305\000T\313\302\223X\232\223y\222\005\024\220\307\261EHMB\355^\222\303 \324\016\264\213\036\325\317sQ\225\354j\273\256\323Q\232\211\305Us\203P\310\300\212\254\335i\204Rb\230\304S\010\244\333M S\010\024\302\265\033\246EWd\305F\302\243#m5\230b\242!Z\242x\275*\273\307\212\204\2574\233h\333M\305.)\n\342\232\027=\251\3333I\262\223\313\346\202\224\233qO\002\237\266\230R\220\2554\014\032\231*e\340\325\310NqWc\251\322\255F*\302\234\n\\\346\234\242\237\264\321\212p\024\340)\302\236:S\221w\032m\324\302\030\311?\205c\'\357d.js\300\250\231\252\006j\364\362)\247\2574\322sQ8\035\252\274\303\"\253g\326\230\334\325i\227\212\250T\346\230V\230V\232EF\313I\212c\036\324\303HE4\212i\034T,\274\324\016\274\324L\271\025\023-DS\024\231\301\250\244\346\253\224\346\223m!Zi\024\230\247\000\017ZpP)\017\024\302=)\240\034\323\266f\217.\224/\265=V\227m1\226\232#\334x\253P\301\201\310\251\032\014\364\247F\214\235E[\211\263Z\021/\002\254(\305<t\247\n\225EH)qF)@\247\201O\305=\230D\204\232\302\274\2717\022\355\007\212\2225\n\242\2075]\315B\306\275T\216*2)\206\243j\211\207\025M\324\202qQ1\250\233\232\201\222\243e\250\230TD\021M\357JG\025\004\213H\007\255!\244#\212\211\2526\250\335r3P\225\305D\302\242a\305D\303\025\023S\010\244\305\005j2\271\246\225\244\305\030\315\000{\323\200\035\350\300\317\025\"\256i\305@\246\021@\024\255\300\250\031\262j\325\274|d\325\221\307J\221\005X\010\033\265H\220\000sV\221p*e\247\201N\002\244SR\212Z\\S\200\251\000\247\242\3675\233\251\334\355R\252k2\3352w\032\267\234\n\215\332\253\273T,\325\353#\2450\212i\024\302\264\307N*\253\2575ZT\364\250\010\2460\250\234T,*&\025\t\353G4\204f\233\214Rb\2028\250\312\372\324r(=*,qP\270\250\030TL*\031\005E\212M\264\233qA\034SqMaM\305\0053\322\233\267i\245#\212j\360j`h\316iqN\013Mu\250\322\034\265]Q\265p)\300T\350*d\030\251\324\324\252ju5 \247\001N\3058\034S\201\247\2575*\255H\005\023H\"\214\375+\234\271\223\316\227\216\225$ch\247\026\250]\252\006j\205\215z\332\236)M6\232N)\255\315W\221j\254\225Y\226\243aP\275Dx\353Q\265@\303\006\233\232v)\204sJ\005\014\274S\nqQ0\305D\303\212\256\342\241aL)P\262sQ\262S6Rm\244\"\233\212B\264\335\224m\244+\232n)\n\322\212u:\236\2640\317JX\306*QR*\324\350\206\254(\002\244\003=*E\025*\324\313R\001K\216)1\353R \251\2213S\204\307Z\216i\204JI\254K\313\343&@<UhT\236OZ\261\322\243f\250\035\252\026j\211\232\275q\rJ\005.\332\215\226\231\266\241\220u\252R\214\032\200\232\211\352\023P\311Q\324L)\201y\2511\3054\255 \247\036E4\257\034T2-@\313P\262\324,\264\312C\036\352\211\243\250\312\323\n\324dSh\244\315\035i\n\321\262\224GA\217\035\250\331F\332P(\247(\251TT\3121Rg\0254{Oz\235F:sR\nz\232\225\016jQ\322\2274\001\223S \253\000\205\0243\340Vm\365\332\200T\236k\025A\221\363\3335mF\000\241\215B\306\240cQ1\250\311\257\\\214\325\210\316MH\303\002\243\316i\255PH*\234\313UY9\250\331j\006\025\003\365\250\311\301\244\"\223\024\206\222\216\264\230\245\003\212\212E\250\035j\026\025\023\n\211\222\223\030\240`\365\2441\203Q4 \364\250\0319\3057\312\246\262\342\233\2126\322\355\247\010\351\341qHE7m&\332M\264\005\247\205\247\201R\n\220sO\013\351R)+R+\023S)\251R\246\335\201M\315H\206\247CN.;\232\257qp\241\t\006\271\371\244i\245\340\325\230c\332*S\305F\306\240sP\261\250\231\2522\325\353\360\363\305]\2120\0074\222\361\322\242Z\030qP8\252\322\214\325r\265\033-@\351\212\256\351U\331i\240Rb\233E(\024m4\355\230\025\033\255B\313\232\211\222\242d\250\312S\nSq\212i\024\303P\260\346\220-!\2174\323\035\001)\302:v\312B\270\246\342\215\264m\244+@ZP\264\340)\300T\212*QO\247\255H\225:\320O4\242\246Q\305H[\002\253\317\'\312H5\213$\256\354FN*h#\000s\326\254\343\003\212\215\315@\355U\335\352\027\222\241/\232aj\366H\276Y1ZJ\006*)y\250\361KP\270\252\356\271\250\031j2*\'L\324\016\225\003G\232\214\307Ld\244X\211\243\312\346\234#\247yt\245*\'N\265\\\2550\250\2462T,\225\031ZaJiJc%D\311H\026\224\241\246\355\366\245\tK\262\223\030\2460\3153m\006\234\007\024\005\311\245+F(\305(\024\361N\025 \247\255J\275jaJ94\354\201N\rMy1\221U\244\311\3435\003\332\356\031\217\357TA\214M\265\3705:\311\221H\304T.F9\250X\002\274TE\021\201\301\250\377\000v\271\365\246*&I\'5\354\261\304K\364\253d\020\265\016\t4\270\244\"\243~\225]\373\324,)\204sLe\250\035j\"\224yY\246\230E\'\226\000\243\313\024\276]\036]F\313\351Q2u\315@\311L\331M)Q2TL\224\302\224\323\0354\2450\305\232o\225K\345\323Lt\335\224\036\005BFi1\212i\031\246\260\245\035)\300`PM&(\240u\247S\305H\265\"\324\240T\203\245&H4\240\344\323\267b\240\335\311\240\234\322+\355<Uk\245\017\363\n\254\222\02585`>i\216\001\006\240u5\001R3Q\0255\031\316+\337\002*\364\246Hs\322\242\034Rri1Q\275Uzn3Me\246\021\232\214\246i\246*\004t\326J\217fivb\233\232v3\322\243(ED\310MD\361b\230c\366\2464u\023%FR\231\345\321\345\346\232c\246\024\305\n\240\236i\216\203\265DEFE0\256i\245qM+\353Q\2650\323\227\2458\n1HE6\212p\247\212\221jE\251\226\226\202h\034\323&|\016*\020\334R\202i\245\251\205\263PN\230]\303\2556)2*\\\344TmP\265B\302\230\302\275\325\230\223M\'\327\2553u&i\t\357P\271\315Wzh\353JFi\204S\225)JRy^\324\306\212\231\345b\230c4\301nX\346\245\021\000\005\006,\324f\034v\250Z,\236\225\033G\212\211\222\242x\352\023\035/\226)\245)\214\270\250\034c\245F\006)\255Q\225\3154\256)\207\330S\010\250\332\242aM\"\225y\247\n\\R\021Q\232QJ:\323\305J\274\324\202\236\016)wQ\234\323\272\n\212R\270\371\215D\030R\027\250\313f\231\234\032\224\'\232\244\032\240\352`\223\025:\234\214\3225D\303\232\211\252&\036\225\356\254\270\250\3150\232@i\030\346\2425\023\nh\034\323\261I\212\231Tb\236\020\032R\243\025\013\255Bh\t\232]\270\024\320\271\346\235\266\215\240\324m\030\025\033C\270dUW\214\203\322\242)\236\325\031\216\233\266\243a\315E%@\313Q\225\246\354\246\025\364\2462\201Q\265D\334\324dS\010\246\021M\007\006\244\352(\315\004\323\0174\200S\200\245\251\024\342\245\rN\315&\352p4\342\340\n\241p\344\26501\247\346\223v)\240\345\252h\337k\324\027\250X\356\250\341l\255Hj&\250\230TF\275\325\352\006\250\330\322f\2239\246\236\365\031\244\305;\024b\236)\340\322\265F\336\365\036\334\322\343\003\212M\271\247\005\243m\001)\031j\"\010\250]7v\2506\020\324\311#\347\212\205\223h5Y\205FV\243+M)L`1\357P\267\035*\027\250\2150\212i\\Tl2j2\264\303\301\245V\305;9\351Hi(\006\2274\264\345\247\212v\352\\\322\346\243w\305T\221\262\324\253N\3155\215,JpX\323\315%\313~\344\343\232\243l\374\220j\331\351Q\265D\325\021\025\356%\270\250\332\2424\224\235(\3051\205 \024\360(\333K\266\234\0058\014\324n\231\245\013\305\0333K\263\002\215\264m\315;g\024\306\002\242+Q\262\324,*6\030\252\262\014\324\016\270\250\312S\010\002\243`*\027\305@\325\013\323\n\036\246\223\201Q\261\346\2439\246\032\214\212f9\247)\245\315!\244\024\242\224\361NZ\226\212L\320\0335\023\344\344\325|d\323\205:\232\3252\377\000\253\000Si]wDEe\257\311.*\3709ZcTmQ5{fsHi\270\246\221\212JP)\n\322m\247\001N\002\214S\202\323\266\342\223ni\3018\244\333\212B(\3058-\0140*\007\025\031\366\250\230\361Q1\250\217\275B\342\253\260\346\243aQ7\025\003\232\205\215Dj&\034\324m\222i\245x\246b\230E1\205FE4\255&1A\244\315\002\234\264\032U\342\245\007\212\t\246\232\024\363N\342\253\310\230<S@\247\001H\302\244\204\344m4\2450i[\345^k\036f\377\000H8\365\253\310~QCTL*&\025\355C\255:\214SXS@\247\205\245\331K\262\223m.\3326\323\302\320iB\323\261HE4\255\001i\341x\246\270\252\356*\006\030\250\232\242aQ\270\342\240l\324D\376\025\023\232\201\315@\365\023\n\214\217Jcq\365\250\210\3151\251\270\342\232\325\013\032a4\334\321M<Sz\236(\247\n\\\361H\r<\032\\\321J:\322\323Xn\250\312\021H)q\3054\214\034\212x\224\367\243%\301\315d\314\000\270\343\326\264#\037(\241\205D\325\033W\264\201N\305(\024\025\315\001)\352\224\375\264\273i6sMd\247\204\244aH\251K\214R\342\220\255&(\003\232\220\014\212\215\305WaQ0\315W\224\343\201U\036LTfBi\205\261\326\243b\010\340\324\'\223P\311\317J\200\323\010\3151\205D\302\230ED\302\232x\250\336\241j\214\322\037j94\323B\014\320\303\024\320i\335\251)\300\323\201\247\nu)\034Rc\024\244pj\035\264\264\021M#\024\341\367X\326C\374\323\3765\245\030\371E\rQ5D\302\275\245\016E<\014\322\201N\0034\360\264\365\030\024\354f\200\264b\215\242\215\264\2052i\312\264\215\035.\312M\224\233(\330\007Zq\034qP\270\250\030T\022p*\204\362c\201T]\2114)\315+.F* \204\032G\025]\315@y8\244n\005Bi\246\243aQ\265D\306\241sP\261\250\311\246\203\223Rf\232}\350\r\203C\220E0S\373Sh\025\"\212x\247\001N\002\227\031\243\030\250X\037JA\326\235\212i\024\262|\2201\365\254t\033\246\255$\351CTL*&\257dS\201S!\315J\0059E<\nv3N\002\227\024\241h\333H\306\221Fj@\264\355\264yt\205qH\026\215\264\204Uw\025\021\\u\252s\347\234Vl\334\036j\253\002\335(D \324\342>3Lu\364\252\262\034\032\204\340\217z\201\206\323Mc\221P\2650\232c\034T\016\325\0135B\315Q1\250\3157q\355N\317\255\004\323sN&\201KE\003\255J)\302\244\002\224S\300\245\307\265\0057\014Ur\273X\212\\R\005\311\250\357\316\310p+2\325r\371\255 8\246\260\250\232\241j\366E\351O^\rZ^E<\n~)@\247\001N\240\361I\270\na\303\037JT\0252\212\220\npZ\ng\265FS\024\230\2467\025\013\016\365\003\325g@A&\251=\276\366\366\250\336\334/j\213\313\013A \n\205\330b\263\356\034\202qU\274\323Am\335j6\250X\323\013TL\365\0035D\306\242cQ1\246\026\246\347\232]\324\205\361M\337OBZ\244\351M\007\232x\024\240sR\250\315;\030\247\212p\024\360)\330\245\002\253\310\270z1J\253\315S\324\217\312\005U\264\\\n\275\216)\214*&\025\013\n\366E\034S\300\253\021\2361S\250\251\002\322\355\245\305\007\000f\243-H9\316i1R\240\251\007\024\340j\302-+\n\214\212\215\252&\300\250\034\325g\3115\003\324y\002\241\221\252\254\215P\263T\016\325J~k>F\330jDp@\2476*\007aP\267\265Ws\212\205\232\242g\250\232J\210\276i\205\251\241\251\333\2513M&\237\033T\244\346\205\034\325\2208\243mH\243\212v)TT\200S\300\245\305(\246H\2319\246\355\247\204\302\223X\367\255\276B\007J\222\336<(\253\030\250\336\241j\205\253\331\224qN^\265\"\037\232\256 \310\251\000\245\305(\025\034\265\0363O\2152y\247\024\000\346\234\007\034R\323\324U\244\340R\265B\325\003\236i\2147\003UO\007\223Q;dUv\340UWl\032\205\3375Y\316MD\306\240sUf=k.v\353Kn\341\207\275H\357\212\201\244\006\242g\250Y\263Le\315@\353\212\254\324\302p*2\324\335\324\340\324\271\246\026\247\304jqR\240\346\247\307Jx\024\360\264\355\271\247\205\245\003\024\361N\305.)6\344s@L\221Q]\270\2110:\326:\251\222L\232\272\213\201Jj&\250Z\241j\366\304L\212f9\251PU\250\316\005;94\345\346\236\005G\'&\221W\234T\244`qM\344\322\343\002\224\n\224\n\231\017\024\023Q9\250\017\255\034\021UYpOz\202CU\344\351Tf<\361P1\250\034\324D\343\255F\304\032\2550\3105\225t\207\223T\321\312\036*W\230\260\346\241\337\223C\270\307Z\256\317\317\024\3174\212C.j\0269\246\021\232\211\226\242<P\032\227u%I\030\346\254\245Y\215sVB\212P1N\024\341N\247\001O\002\235\2121J\0274\343\210\324\223X\367sy\256@\242\030\260*|b\230\325\013T-P\265{x_\226\205\2175 \\S\306E(52\260\002\227w\2457\2559\0079\247\023\305 \024u4\341\326\245\002\236:R\023P\261\315D\306\243g\307\322\242f\316qUdj\253#\325Y\rWcP9\305W\221\352\271\222\243w8\252S8#\004U1\215\324\222\201\216*\253\270CP4\271\351Q\2310i\215&i\236`\246\2313H_\024\201\367\036h\221x\315@z\323\205(\025i\024\005\367\253\021DO5r8\3609\025&1J\0058\014\323\200\245\002\244\024\352SH9\251\020`\363U/\246\302\355\006\263\242\217sd\325\300\270\024\326\250\332\241j\205\252&\257s\000m\241F\r<\n\\R\342\212QR\216\224\341\305\035M\006\222\236\242\244\024\244\340TL\325\016\356i\214j23PH\330\315T\221\352\254\215P9\250d8\252r\311T\244z\256\322s\305!\220\221\315V\227-U\330\005>\365\013\270\317&\252\315\317CU\217\006\242rsQ\226\3050\265\001\270\246;\322)=ju\223\214\032M\231\351J\027\024\340\274\325\210\306p+B \025j]\330\245\0074\372x\036\224\360\236\264\273pi@\245\244\357\305H\243\271\246\314\373W5\225!2\275O\034{E<\361Q\265D\306\241j\205\252&\257vQ\223O\013\315(\024\340\264\273h\333F\332p\024u\247 \240\365\240S\324S\307\002\230\315P;\324\005\271\241\337\025\031\224b\252\310\371\252\222>*\263\275WiqP\311(#\025FBI8\252\333Y\316)\031\002\216j=\303\322\242\221\205P\231\262p*\264\2101\235\334\325f8\353P\273T,\325\003\311Q\031i<\332\024\356<\324\243\212w=\251\351&\323\203S\207SN,;T\22075y\rH\005J\202\245U\311\346\247\003h\340Uw\224\253\363OY\225\273\323\203\002i\304\201M\363\000\245\r\232\212y21U\343\217\234\232\233\2651\215D\306\242cQ5B\325\023\032\367\244\\S\300\247\005\247\001K\212\\{Q\212gCG&\2348\315%8S\305)<T\016\325]\332\242\335\315A4\265\030|\212kt\252S63T&\227\025L\313\270\343\232^\325\023\020*\274\222\355\351\305Uy3\336\243/\357U\246\233\035\rg\315+)\252\257p\335\352\006\231\215F\\\232\211\336\240\221\352\003%&\363\232\225\036\246\017N\363\005*\266ML\246\245U\315X\210m5q*\302\363S%M\030\313T\300\214\32471\003\326\241\020\205\357FpqR/4\2059\241\334(\250F\\\344\323\372R\023Q\261\250\230\324Lj65\013\032\211\215{\341\247\255I\326\224\nu\024S\010\364\243\030\244\242\234)\340\342\243v\252\3625Wf\250\367`\325y~f\300\245\013\264TR=Q\235\305f\316s\234Ud\030j{6\005V\226N\0175FY@\357U^\340\n\256\367G\267\025ZI\262\0175I\3459\344\346\240\222L\324-!\002\231\347v\246<\234\324\022=W\337\315.\372\2266\315I\273\024\34595f4\316*`1S%L\255V\342l\212\267\030\251\2623R\306y\025ch\340\367\250\2475\016Gz\000\035i\305\325\005@\323\0268Z6\347\2559@\024\214j2\324\3065\023\032\211\215F\315Q1\250\230\327\320\000d\323\302\323\261N\024\242\227\024\224\323I\236)(\245\024\244\340T\016\365Y\336\241f\2463\0002i\261\214\363D\216\000\252\023\313\214\325\007\223uU\221\211\310\2507\001\326\241\232a\214\n\245$\243\234\232\314\232}\316pj\007\220\001\315U\226\340\016\365N[\237z\254\3679\357P\264\376\365\023OL\363rhy\006*\006\2235\021|\032O2\245\212Nj\312\275M\031\031\311\253j\343\034S\303T\252\365*\022\306\264aP\024f\254\007\300\342\234\246\254Fy\025h\0163\351U&\223\3465Y\244\'\245 v\247\034\221\315:5\301\251\030\342\231\272\232\315Q\026\250\331\252&j\215\232\243f\250\330\324lk\350UZ\220\014S\261N\013F\3321HE4\212\214\360h4\235)sLv\252\316\365]\332\242\3150\234\237j\014\201G\025Zy\2532yI\357U\267\032\206I\007j\252\357\305R\232oJ\245+\2229\252\022\270^\365Fk\214w\2522\334\023\336\252<\331\357Q\0313\336\230\362c\275E\277\336\2173\025\033L}i\206Zi\2234\201\351\351&\rYI\211\253Q\271\"\247V5\"\311S\243\325\313c\226\346\264Q\270\247\006\346\245SV\021\352g\230\210\316*\233\266T\223L\006\236)I\245V\346\225\332\230[\212\215\232\243-Q\263Tl\325\031j\215\232\243&\243&\276\214Q\305<{\323\300\247Rb\220\323I\246\226\250\330\212nsE5\233\025\004\217U\235\352\006z\215\237\002\253\313)\003\212\254\327\030\030&\253\3119#\255Ty\200\252\317?\275Wyrj\255\304\373W\255Pi\300\0075F\342\357\260\254\331\256I\357T%\236\252I-Wi)\003\323\035\351\201\263J[\212\201\232\230Z\233\270\323\303f\244S\315Z\216\255FqSn\251\020\346\254\306j\334\'\006\257\243qO\rR\253\324\310\365&\340\303\031\252\316\330R)\241\251\341\250\337J\036\202\364\322\374Tl\325\031jc5F\315Q\226\246\026\250\313S\t\257\243\326\236)\340\323\251\245\2050\2650\275F^\232Z\231\277\024\031*&\222\240w\252\356\365\003\275A$\234UYd\343\255Uy=\352\264\217U\244z\253$\225\001\223\255P\272\233<\n\241#\026\035j\224\307\025Bf\252R5Vv\250\031\351\276e4\276h\337\212i\222\230\315Q\026\2405=ML\255\212\261\033\325\204z\224=L\215Vcj\265\024\234\325\324\223\212\224=H\257\232\225_\024\342\374\361QJ\334\373SCS\203\321\276\224=\033\351\245\3522\364\302\324\302\325\031jajajaji5\364\222\323\3058PZ\243-Q\263Tl\365\031zizc5Fd\250\332J\205\344\250\035\352\274\222Ug\222\252\311-Uy*\254\222\325i%\367\252\317&j\031$\n+:\342A\234\3257\237\216*\234\262\016rk>iG5M\345\252\362IU\313\022h\372\232ajM\324\233\251\214\365\031z@\365*=N\255\221S#b\246V\251C\324\321\275[\216LU\204oJ\265\034\270\340\325\205|\364\251\221\252`\324\245\275)\254r\rB\037\236i\333\3517\322\357\243}4\2750\2750\2750\2750\2654\2650\2654\232Bk\351Q\322\234\r\005\261L-Q\263\324L\365\023=0\2754\275F\317Q3\324FJ\205\244\250\036J\255$\225VI*\254\222UY%\252\222IU\236Z\201\3445VY}\3536\346\343\255gIpMWyI\252\322d\365\252\316\r@\355P\263Te\375i\246Ja\222\220\313M2S\013P\rJ\206\254#T\352\325\"\265H\032\246\215\215[\210\346\255#b\247V\315X\205\271\253j\325 z\013\322o\252\356\330\177\255.\372B\364o\243}!zizazizajijijM\324n\257\245\305\005\261L/Q\263\324L\365\033=F\317Q\263\324fJ\211\244\250ZJ\205\244\347\255D\362T\017-V\222Z\251,\265U\345\252\262KUd\222\253\274\200\014\223Ud\234\023\305S\232oz\316\232\\\346\2523\n\211\210\250\235\352\254\222Uv|\324\016\325\0135FZ\230\315Q\226\305&\352P\325*\232\225jT\253\010jU\251\026\247\216\256F8\251U\252d&\254\306pj\312\275?}\033\351\013\324r\034\256Gj`~)\013\321\276\215\364\322\364\322\364\205\351\273\251\013SKSwQ\272\215\325\364\301jc=F\317Q\263\324L\365\033=B\322Tl\365\031z\211\236\241i*\273\311P\264\265]\345\252\322IU]\362j\274\257\201\326\251\264\234\362j\264\327\n\265\237,\345\317Z\200\275T\232J\243,\225X\311\232cIP\274\225RW\252\306L\032k>j&j\2179\240\324mM\247\255L\242\245Z\225\005N\242\245Z\225jd\253(ML\265e\rJ\255S+\361N\337N\r\232B\324\335\334\021P\206\244-I\276\202\364\322\364\205\351\273\350\335I\272\232Z\223u\031\245\335_K\026\250\231\3522\364\306z\211\236\241g\250\231\352&\222\243/\232\215\232\253\311&*\264\222\372T,\304\324.\330\352j\264\222UY$\252S\314{U\t\'#\275S\222]\307\255B[\025\023\311T\245\222\251H\3715\0136*\026z\201\344\252\362>j\271=i\205\251\245\251\231\2434\207\232LS\324\032\231EN\253S\"\342\246Q\232\225V\245U\251PU\2055*\361R\251\251CT\240\361K\272\234\255C5F[\025\036\356i\013SKSKSKRn\244\335K\272\215\324\233\2517Q\272\224\032\372I\244\250Y\351\205\3526z\215\236\241g\250Y\3522\371\244\006\230\355\201T\245~\265X\266M#\266\005S\226J\254\362U9&\252\222\310\rg\316\370\'\025I\236\230d\250\235\370\252R\234\232\256\347\025]\332\253\273T\016\325\0136j75\0214\204\322P\005.)\300T\252\265*\255L\213S\250\251UjU\024\354T\211S\251\251\224\324\253R(\346\245\307JN\224\3654\255P\267z\2046sHZ\232Z\230Z\232Z\220\265&\352]\324\273\2517Q\272\215\324\273\253\350\306\222\243/L/Q\264\225\013IQ\0311Q4\231\250\313\322\254\237-C$\231=j\274\244\036I\250I\013\316j\264\323g\201T\235\352\254\322\340U\007\233\223U\244\2275VV\3105U\372T\r\221P\263T\016\325ZCU\334\325w5Y\330\212\2179\2460\3154\203M\305\030\240\n\220-=TT\252\242\245U\251\225jUZ\224\nx\025 \025\"\214T\200T\212je9\251\343\\\323\363\203M&\234\247\326\206jc\237\2275S8&\202i\244\323\030\323\t\246\356\243u.\3527Q\272\215\324n\240\267j\372)\236\242g\250\332J\211\244\250ZJ\215\244\250\332J\214\2754\311\201PI)\035*\264\223\037Z\257$\307\326\253\264\231\315@\317U&~\rf\313\'5Y\346\250\232L\324L\325\023\236*\253\266*\027j\256\346\2539\252\356j\026\250\310\240\323M7\024\240S\202\324\212\265 ZxZ\221EL\202\245\013R\001R\240\251\002\324\212\264\360\264\340*D\253qc\024\215L\3174f\232\317H\355\362UV<\320M4\232a4\302i\231\2434f\227u\033\250\335F\352@\325\364+IQ\264\225\023IP\264\225\013IQ\264\225\031\222\232e\250\314\231\250\244z\251$\225Y\244\311\346\241y*\273\313U.%\342\263e\222\2533Te\361M\3633M/U\344\3475Y\332\241v\252\362\032\256\325\013u\246\363IF)6\323\302\323\302\324\201i\341j@\265 Zz\214T\312je\\\364\251Uj@)\340b\236)\330\245Z\261\031\342\203M4\225\024\234S\031\262\225\001jM\324\231\246\223Q\261\246\223M\315\031\243u\033\250\3154\265(j\372\t\336\241i*\026\222\241i*\026\222\2432S\014\231\246\263SK\340T\022IU\036Nj\273\311\212\255$\265U\346\367\252\223M\232\252\3075\013SJdu\250_\345\342\230Z\243v\342\252\310j\026\250Z\240j\214\2126\322m\245\013F\312P\265\"\255H\253R\005\251\024S\361J\005H\253S\3061S\201R\001K\212z\2558-(\034\324\212\010\247S\t\246\026\244?0\250\266\225\353\322\241\221q\310\250\263\212\t\244&\243c\232i4\322i3Fh\335Mg\244\006\234\r{\353\311U\236J\205\244\250ZJ\211\244\250\232Za\232\217<t\250\344\233\336\252I8\035\352\253\334\201P4\331\025ZIj\2735B\347\212\200\232\214\232ij\206S\232\203&\241\221\252\271l\324lj&5\031\024l\365\245\331M+@\024\354R\205\251\002\323\302\201O\013R\005\247\205\247\005\251\225*U\025*\212\220\npZp\030\247\212r\217Zp\024\204`\323\030TMQ\346\235\234\216i\204\216\207\245W\2210x\250\263HM4\232a\246\223HN)\245\251\013SsK\234S\201\257vy*\273\311U\336J\205\244\250\332J\205\244\250\232\\T^v\016MC-\317\275Sy\211\250\231\352?7\007\025\024\217P4\224\302\331\250\211\2461\246\026\250\\\324]MG\"\212\200\212\214\255D\302\230E\030\245\240\212LR\342\224\nz\323\305=EH\005H\005<\n\225je\247\250\251qJ)\300S\200\251\002\344\322\355\305\014*6\024\302\265\023G\3157n)\244S\010\310\301\252\262\251\006\230\016i\r0\323I\246\023L-M\315.isFk\334\032J\256\362Uw\222\241i*\026\222\242ij\027\226\253<\276\365\003I\357Q3\324O.\005W2\363N\336\n\324\016y\246\027\3057~i\214\325\0314\306\351Q7\265D\315Q\261\250\311\24674\314R\355\243\030\240\322\036\264b\236\0058.*e\214\021\301\247l\3059EH8\247T\213R-J\246\245^i\340R\212x\251T\323\210\310\244\333M+L\"\243ja\250\315F\302\242q\221UXm4\302i\244\324l\330\250\213f\233K\2323FiA\257gy*\007\222\240y*\006z\211\244\250\036J\201\344\250\032J\201\344\250^Z\201\344\250\014\234\322\211\251\305\363Q\26574\023Q\223L\3150\324,9\246\021\212\214\323\0174\336\224\271\244\315\035iqF\332z\247\275L\027\326\2342:T\241r9\243m8\nZx\251\026\244\025*\032\230\036)qN\035j@02ju\301^(8\250\310\024\306\025\003\212c\n\214\323\032\2435Ve\346\253\223\212\215\237\322\2429&\222\2234\231\245\315.h\025\353\355%@\357P<\225\013IP\264\225\013\311U\336J\201\344\252\357%B\317Q3\324e\251\233\271\247\2074\355\371\024\231\346\220\2650\232a<\322\036\224\316\325\013\232\214\232ni\t\246\021F)\312)I Rn\346\236\257R+\212\220\032z\276)\371\356)\300\323\251E<qO\006\244V\251\320\346\245\307\024\243\255K!\004\014T\221\034\nV\250\233\212\215\215Fy\2465D\324\303Q\266{Uv;\270=j\264\203\025\001\246\023\212i4\334\322f\212\\\323\201\257\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\006\nIDATx^\355\334\331\222\343*\014\000\320\251\374\377/_\327\255^&\023\307\361\0166H\347<ug\263A\002\204\235\356?\177\026=\336\037\000\310\3439\005\016\257\217\002?\014\214\340z\t\260j\265\222^\022\200Z\376\016\255.\206\330\\\272NO~\372\010|\231\313!\200l\254\224@\010&\263\334\304\037v\351u?|\346\274M\023\000\000O)J\2433\265#\001H\200W\237\306\374\260\322E\237\336C\034\263\361\235}\242\tki\013\034\021w`\265=\2435#n\002$#\337\001\350\325\236b\344\343k-\202\313\336\373\347c\'\002\000\000\320\250\367]\035\000deM\204-\334\003\200\027\351\006D\272\006\357\224\246\226(\222\010\355\366\326\243\335S\273\330\277\216\360\325z\010\312\330f\003i\002!\030\312\225\005\336B\310\035r\3330\0026\274\204\261X]v[kn;0\000\000\000\255^\r\264U\00463a@h\215\226*\244e\321\201>\030\253\237<\327\324x\335s\331\337\005\014\001;\017\000\310\300\325\025\000\2567\267\203\236{\374\220\242\037\226\200\222\000\340\020\313Mr\022\000\240\030[\022\216YX\214\027\236\242\031C\307\203\277\3373\377\344\266\377Bz\327q\271\314\362\\,\001\000\000\202[.\007\211gs\304\177_\270\371\365\304\"\360\331u\232\001\235\2366\274\221\311\300-\032\276\027\320\360\251\305\262m\001\332\366*\256P\374\336q\351\317\003\000\000\240_\007.\000\034x\013\037T\337\236\377=@\365\003\001\000\274R-\002\375\010\271]*9\r\207\354 \000\000\000\366\262=\034i\240;~O\341\3305\200c\357\2421g\322\360\314{i\310\321\261,\001\202\220\000\000\300\026u\326\376\311_;\037-M\306\312|JD\337=3\354\350\237\367\000\365\244\347s\207jv\214\177\000 \026e@^\027\307\376\342\303\261b\020\221\344\\\034\001\312+=\263X\251j\321\263\000\364\241tm\261\327E+\346E\207\341V\367E\371\276#\323\004\t\200$\310N\002\000\020\304\335\027\010\370L\251\321$\303\005\000\000 \205\347\366\317>\020\000\000\346\204\272\231\025\2521\020\302\337QYit>\367\373\225>\237Rf\003t\356\222\315\373\273g\017CtB\017\274/\t\2045\236\362-\000@\313n\232\243n:\354uj-\372\265>\227NH\200N\010\024@\026f|\000\322\010\177%\347\234\331\356Q,\360\211\274\210hv\032\000 \021\253A^\207b?*\n\037J\304\276\274\306k\030~~_K\203b1^;PGv5e\327\213+(\026@\000 55\005p\237\331\031h\366\211\266\335v\332+\007^yz\352\356\355n5a\033\006\020\307\356E\013\000\350\2225\377\220\323\273\332\323\037\320\233(\211V*pM\365\307\025\'s\3051\340v\022=9\t\000\220\315\317\314\177f\376/\265\275\340\026gBO \3061@\034\026w\000:\365\334\226\254\355O\326\326\272\265\347I\351\353\337#\214eK\224I\007\000\220E\266%o\302\032\010o\014\212\344$@.\3432 }QpP\244Q\023\251-i\t\"\000\000k\324\214\'\331=\323\244\303#\373\360\033\001\240\013V:\000R\033\206\351\327\343\263K\333!i\033\236\335k\340%\001\000\000\267R\220f%\362\367\320\357\000\000@L\253\273\035_\364\017\332\003\2555k5\023/\326\332\371p1\t\000\000@o\036\207vy\t+\337\355M>\324\243tO\334\001j3\323\002\r\331\276= $\t@\033\232\250\216\374\337\204x\232H\254\375d\"\000\000\334\257\323\355\004u\004\332\246\311l\370\307x \227@\213\031@%j\203\276X\3316x\351\244x\371]\240E\005>\242q\001[\370lR\300\266\0010\343\247\242Q\374%7\310\201\254*\307]Q\331\262\237\340\307\215Q\334\226\225Ub\0228\362\247\001}\306\247\317\263\256\356H\002\320\200\207\214\246\016\211\005\345Xbc\020\307\344ZK\200\342\347\023|\345\037\312\367X\020o\201\017\330M\337\033\335\200\355\002\200*\202\327\204\300\275\224\345e\350\307\234\252.\321U?\2746#\"\210b\201,\366A\271\350\266\250D\026\000xuam\320\3656\023\000\000B\272pC\000e5\277\305l}t5}~\315G\267\007Y:\261\351L\006\000`\037\305\335\001Y*\177\026\254\3747\260\346s\344\367\364\233?\317\336|\367\353\320x\307\216On\364\333$\255\233n\0104\313\310IN\002\320\271I9@O\204\357rmM\372\022\340IW\314\3201k\226.\366,<\365k\375\025\177.\2306>\035\340\323c_\346\036\347\3338\240\233\302\013\264i\347\000\336\371r\000\200\306\274U3\212\233#\036\017;f\000\216\262\366\002@\233\336\327\350\367\337\273\366\322\230\274\373\331\363-\017\225\023@\n\347g>\"\350~\375\352\276\0017\323\177\000O\243)Q\241\224\211h\247\246\030b\201\364\000\200\313(\312\001\340(\233W\200:\314\257\000\204aQ\333a\347u\312\235/\007\000\332c9\007\2022\275M\244\332\034\213\377~\361\372,^\213\000\000\000\226\330\005\001\000\344\223\352\346\017\000@V\212>\026I\020\000\210\305\332\016\231Mf\200\305/\202,>\t\000\000\000\320\024W2\000\200\350&\367y\200\210\346\206\372\353\343\377\275\374Lvs\031C\022\022\000\000\000 \231\177\267\305\335 \007\000\000\200\256\270\273[N\207}\331\341)\003\000\000\264\302\226*\013\221\006\0023\305\001\300Yi\2777=\214\352\210\264\335\220\272\351\000\000\000t\306=\201\354\356\276\212!\003\201UwOT\000\000+\354k\000\016\262\337\343\213<\000\000\272\246\230\001\000\200\350T\375\200/\005\364\345w\336n1j\377\003\020\243\377\206\213\235b\021\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-7.textpb b/core/res/geoid_height_map_assets/tile-7.textpb
new file mode 100644
index 0000000..83f1fcb
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-7.textpb
@@ -0,0 +1,3 @@
+tile_key: "7"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\364\241\311\247\272\014UR\270j\221E;\024\240R\342\227\024\355\271\244\3074\341\326\234)\340S\302\324\2121O\025\"\324\202\236\265*\324\213R\016\265 \247\221\300\247!\035;\324\312)\342\235\232p4\352\rFx\346\254B\341\206\r<\257j\257  \321\022\0265<\361\205N:\325#\2321IM84\204\n\215\224TD\n\211\361U\334Uw\025\003\212\201\305VqU\334T-P\270\252\322\016*\234\253Ud\025Y\305FV\242\"\241a\232\214\245D\351Q\024\250\235k\327\2323\236)\256\016\334\032\213m8\np\024\340)@\245\3058\nP\264\273i\341i\301i\340S\200\247\201O\024\360*E\251\026\244Z\221jE\251\224\016\365\030 \310qV\024\323\301\247\003N\024\340ii\254)\250v\265[S\221\232c.\346\346\254\300\2358\242\362<(\"\263\366\322m\244+LaL5\033TdT,*\027\250\\Ug\250\030Uy\005We\250XT\016*\264\202\252\312*\234\202\240qP\221Q\262\324L\265\031\024\306\025\013\n\211\205{\010\036\265\024\200T;y\245\013N\333K\266\234\005.)\300S\200\247\005\247\001N\002\224\np\024\360)\300S\305<T\202\236)\336b\257SR\t\227\037/&\201\276C\311\300\251\321v\212\224\032p4\341N\024\341N\024\244f\243#\006\254Dr\274\320\300\203V\255\337\212}\340-\030=\2534\214RQ\212c-F\302\243+Q\270\305@\365]\352\026\250XT\014*\007\025\013-@\353U\344\030\315U\220U9\005VqU\330Tei\2053Q\262\032\210\245D\313\216\265\013\212\205\305z\363q\322\240y0phV\rR\252\322\342\212)\300R\343\322\224\nx\247\001N\305-(\247\np\247\np\247f\202r1\232b\214I\216\306\257\244`\n\225i\340\323\201\247\003O\006\234\r8\032x\247\001HW4\014\257J\221X\265O\037\006\255I\363\304@\254\366\217\232aZLSH\250\312\324o\305Wz\201\352\273\212\210\212c\n\205\326\241+Q2Uy\027\025VAU$\030\315R\222\253=@\302\230E5\216:Tg\232\215\270\252\356j\007\250\034W\255<\200\n\243<\302\2515\367\224\371\315\\\203RW\003\232\224\335\016\306\234\267 \367\245\373H\315J\223\206\251D\202\234\034z\324\313\315;\024\264\003N\247R\212p\245\315;\265F\354W\232\204\334\342A\3175\247\004\333\30758\251\001\245\024\361O\035i\302\236;S\3058S\351\030qO\203\275J\006\rJ\200\223\365\250\245]\246\2414\322)\244Tn*\026\250\034T,*\007\025\t\025\033T-Q3TLj\0075RSTe9\252\216*\273\255DV\232\313Q2Tl0*\273\232\200\324l\265\023\255z4\267\003\007\232\313\270\270\311<\325\tK9\342\230\211*\037\224\232\260\262\314\007SR\255\304\242\247K\306\376!Z6wq\271\001\216+ac\211\324\020\334\3242\304\253\321\251\022M\275\352q>;\346\246W\rN\306zR\342\224\np\247\nQN\002\231*dV;\356[\234\023[V\317\200*\3720\"\244\007\024\361O\024\361\3058\032p4\360i\342\236)q\272\236\213\264\324\246\245\214\322N\205\206@\252\244\021I\214\323J\323\031j\026J\201\326\241e\250\035j\026Z\205\226\241u\252\3561P\265W\220\3259Nj\254\225Y\226\242e\250\310\250\330TNqU\2449\250\030TdS\010\250\334q]K\\\356\025\\\202\346\246H\272T\313\030\251\004c\322\235\345\n_!Oj>\317\267\225\342\246I$A\200\306\247Id\'\3469\0251\215\230d\032\201\244\222>\271\305Y\266\273$\326\224r\206\02584\340i\302\236\0058\nv*)\030(\254\231\030I?\313ZP\214\001V\343r\rZV\315H\0168\247\003N\006\234\r8\032z\232\224\032vjd\0250ZF\351\212lNw\n\270pW\212\253\"\342\241\351IM8\250\236\241l\032\211\222\241d\250\035qP\262\324n\234U9V\2538\252\262UI\005Wu\250Yj\026Z\215\226\253\310j\273\344\324%j6\025\031\024\322*\027\256\221b\247\252\000jA\307Jp85 l\323\301\247n\247\251\315<b\246A\307\322\247\004\355\310\374i\031C\212\245*\030\216S\212\236\326\364\202\003\032\325\216`\303\203S\t*EqR\253\n\220\032R@\352qY\367\327\001\020\205\357Tl\327s\3565\265\020\030\251\024s\305YQ\3005 j~iA\247\003N\006\244SO\006\236\234\232\270\215R\001L~\225[y\215\262zU\350\246WN\265\034\247\232\200\3223\201Q\026\315B\3715\001\342\234\016W\232cTL\265\021Z\205\305T\225j\243\255U\221j\254\213U\331j\026Z\205\226\241qU\235j\026Z\205\226\243aQ\221Q\267\002\253\270\256\246\202h\335F\352p|S\303\323\267\323\325\352Uz\231_<T\253&:R\207\301\241\300qT\336\022\255\225\251\340\270)\303U\305\272\351\315J\267$\036\265f;\241\336\246\373j\201\305V\236\367\336\263\036G\270\177j\321\266\214\240\025\240\215\212\231\033\326\254#v5&y\247\003J\r<\032p4\360i\340\324\261\034\346\255G\324U\2208\246\260\252w*q\305$\014Ur;U\304o1zTrD\303\'\025M\362\247\232\001\310\250\334\324M\355L$\212\214\271\357HZ\243cP\265W\220f\253\272\325Y\022\252\310\225Y\322\253\270\250\030TL\265\003\256*\006Z\205\205D\313L\331P\313\201U\034\327M\272\220\265&\3527\321\276\234\037\232xcN\017R+\324\311%J$\251\267d\0029\247+z\323\260\032\232a\006\221\240*F\r&]i\004\222zT\352]\307Jp\267.~cVa\267\013V\200\333\214S\325\252\302\002}\252\324})\344\323\201\247\212p4\340\324\360\302\235RBy\253\221\236EZV\315;\031\250\244\2175Q\334[\003\221\326\241\267\324B\313\216\325\260\223,\313\322\253\\@95A\270\250\330\324D\340\322\036j6\025\013\002)3Q\2675\013\324\017P8\315U\224b\251\311U\330T,*\026\025\023-B\313P\272f\2421\324R\035\242\250\310rj\006\025\321f\232Z\230^\215\364\273\250\335OY=i\373\307jz\275J\257\212\231d\310\305J\222\021R\254\204\234\324\350\371\025&jD\3062FE&\325\'\216\224\341\020\364\251Q@\343\025&=iA\247\356\243uH\2221 f\257\305#*\362*A \'$\322\203\371R\371\200w\247\007\317\265<\020\335\351\341G\367\215/\314:05$nC|\325z3\337\265ZF\251\224\322\221Y\372\224d\247J\310H\210`En[\202\261\256MYl2\237Z\314\230`\232\254MF\306\224\036)\254*\027\034\032\216\230\325\023{\324\016*\273\325Yj\243\216\265\003\212\205\224\324ej2\265\023\255@\303\025\014\214*\224\315Y\357\'\315Q\264\265\320\026\246\226\250\313P\032\215\364\340\364\340\324\360\324\365z\225Z\245G\305L\255S#\35550q\236*P\334T\261\313\216\243\212\220\225\352*h\361\267=\350i\001n)w\322\356\24058\032r6\r]\023\226@\270\351J\244\324\241\370\245\r\315;u85H\256j@\364\354\325\313yr\274\232\275\033\202*ej\225Z\251j\023\2026\016MS\215G\031\255 \000E\307J3\317\025R\344s\232\244z\324mB\232SP\310j#\322\243j\205\315D\306\242|Ui\024UWJ\256\313Le\250\\b\253\261\305W\222LU)\256\000\351T\244\270&\252\311)j\254\325\023WB\315L-L-I\272\215\324\007\247\207\247\206\247\206\251U\352D|\032\235\037\336\247V\315J\215\371\324\252\365 j\230>\341\322\236\257\371\324\204\250\307\257zqe*1\326\223u8\032pj\221\r^\205\224!\365\251RA\214b\220\236y\2434\240\323\305H\264\360i\340\324\3219C\317J\277\034\230\344T\253p\007=\252\031\365 \200\205\252P\271\231\2131\315]\333\307\025f\026\334\230=\250\'\006\240\270?-S&\243jh84\273\252\'94\302x\250\232\241~*\0065\023\032\211\252\273\340\346\240b\001\250]\200\252\322\275Q\232`\277Z\317\226Fj\254\365\003\212\205\205B\302\241j\333-L-L-M\335F\352]\324\340\325\"\265<5H\034c\336\236\216*Tz\260\262qS\307&\322\rK\346\2069\003\025 \223 T\310\343\036\364\360\370\346\237\36374\241\251\301\251\301\251\352j@jdsVT\232\221NMHq\332\200*@)\343\232P)\375*Tn0E=da\307QJ]\210;\016\rUh\345rwQ\004\255\013\355qZ\221I\270U\210\376\\\232Rj)\233(j\236)\255Q\221\315!\351Q=B\315\212\214\277\255F\346\252\274\233MFe\035\352\026\225}j\027\220\016\365VI@\351U$\230\325Ief\252\257\317Z\205\305@\342\240qP\260\250\036\241j\325-L-L-M-@jp4\006\251U\251\341\251\301\251\352\3252\265J\257S\253\344T\350\330\025*H\000 \363OW\251\225\263O\363\010\030\007\212\221Yvd\236i\003\023\323\245=^\244V\251T\324\311\326\256G\265\207&\226\236\rJ\244S\201\251\001\247\216\264\341\315=jE\247m\311\310\353OV*p\303\"\244\222\3329Wp\024\220\257\226p{U\235\324\205\252\263\271v\300\351JS\002\242aQ54\216*7\351U\332\241`s\315B\344\363\203U_\336\240r\rB@\'\236*\tT\212\254\365]\352\007\252\357P\260\250\034T\017P=B\325\003\n\320-L-M-L-@4\340\324\340i\352j@iwS\203T\252\365\"\265X\215\252\300$u\251\021\206EX\334:c\232Uz\2205.MM\023\200\016ic \223\272\245FS\327\212\224\020:\034\324\310julT\310sR\212z\340S\367\np>\224\360\364\360\324\365j\2205L\2074\346 \nXe\031\332MJ\024~4\355\330\246\034\277\002\200\241hcU\330\344\323\010\250\311\250\230\324-\315D\375*\273Uy\006j\263\214T\017\232\201\311\250O\0078\252\362rs\212\205\233\003\201U\236\241z\201\333\214Uw\250\032\241j\205\252\321jijaji4\252i\304\322\253T\252\324\355\324n\247\007\251\025\252Uj\231\032\254,\234T\210\334\324\301\360\335jM\340\236*Ej\2206i\301\260jEl\234\232x8\251\221\252ej\235\033\246j\326\365\000c\255;u(l\323\201\251\003S\203\323\203\324\201\351\352\3652\2759\233\"\230\215\207\253Fb0iL\231\031\024\365\223\tM\017\232k\034\323\010\342\243cQ6OJ\214\241=\351\214\204Uy3U\3335\003\325w\250^\240aP=@\340\212\254\365]\352\0075\003\232\201\315@\365\013T\017S\223L-M&\232Z\200\324\355\364\241\271\247\207\247\007\247\206\245\006\244V\305J\255R\243\325\200\371\002\246\215\307B)\340\374\330\0257\335\305J\215\305H\247&\244\347\322\234\032\244\014*U\315H\255\216\265:=L\036\244W\355R\003N\337N\337J\032\236\036\234$\251U\352tz\227vEF\371\007\"\246\211\303\'5*\256\345 \036\224\210\340\214\032yB9SI\311\353Mj\211\215D\336\324\336E1\213\n\205\333\332\253\276*&\013\216z\325f\343<Uw\250[\245B\302\241\224n\252\262%Uu\305WqU\334T/P5B\325\013\323\213S\t\246\223I\2323I\232pj]\364\340\364\360\365\"\265I\232z\265N\246\246\rS\241\356:\324\321\311\216\243\232\220\023\336\244V\251\003T\313\'@zT\203\004R\240\311\353S\253\355\340S\367\344\212\2205J\257\212\2208\251\026J\223viwR\207\247\007\247)\251\225\252Uj\234=;9\246\357h\217\003\"\246I\207U\342\225\324\246\037\261\251\221\362:\322\223\336\242f5\033\034\324g\"\232}\350b\010\252\354*&Z\201\205@\342\253\270\250XT\rP=W\220Ui\005V\220Ug\025\003\212\205\205@\342\241aL&\232M4\232L\321\272\2234\233\250\335NV\251CT\212\325(jz\265N\255S+\342\245V\251\327\234{\324\341\370\332{T\212}jQ\202x8\247\364\034\034\323\324\364\317J\221\037i\310\251\003\356l\232~\374\267\024\360\334\324\201\252da\216M<5<585;u\000\324\212\325*5J\257R\253\324\252\365/\017\326\223\312\301\371N)e\220\354\t\327\232\2263\201R\026\342\230G\031\246\026\035\206h\013\270g\275B\352W\255G\273\025\033\021Q7\025\023T\017U\336\240z\201\352\026\250\034Ui\005VqU\234T\014\265\003\212\205\205B\302\253\023M&\232M4\265&\3523HM\000\323\201\251\024\324\252\325\"\265H\032\244V\305L\255S\243T\350\370\251\223,x\251\225\261\326\244\rR\003\351R\253\345pi\350\t\351O\r\212pjr\265J\032\236\036\244W\251\003S\303S\303Q\272\236\032\236\032\246V\251\025\371\251\203T\361\276\0075(j\205\2372\014\366\251\321\352V8\034SKn\030\246\001\212_\247\024\016xj\2574E\016GJ\256y\250\330\324,j\007j\201\315WcP=D\331\250Z\241u\252\356\265^E\252\356\270\025]\305@\302\240qT\311\246\223L&\233\232J\\\322\023E(4\360\325\"\265J\246\244SR\003R\251\251\321\252tl\324\350\307\265L\030\2202jEj\225H\357Rdg\212\221\033\007\"\244$\261\317Jr\343<\363K\236x\247\003R+T\212\325 j\2205;u.\352pjxj\221Z\245V\251\321\252Uo\230\n\262\274\324\027\n\335S\255@\223\310\207\346SV\222\3600\301\340\324\261\271bI\351R\003\232:Q\327\353J\300H\244\036\265I\320\2515\013\212\201\205W\221j\006Z\205\326\240e5\013q\326\241qQ\267J\256\342\253\275Vz\256\302\241u\252\356+<\232i5\0314\334\321\232Pi\017Zu\024\340i\352jE5 5*\232\225ML\206\247F\305Y\211\271\346\246SOB;\323\267b\254\"\202\231\3174\261\261\006\254;p1K\021\014piH\305\001\251\342\244V\247\206\251CS\203S\303R\203O\006\244\rR+T\310\325:\034\221V\221\252P\271\243\310\004\321\366A\235\330\253\n\243n\010\305A(1\237Z\204\315\203\2021NI\206jRy\014\275)\222\000\334\325WZ\201\220T\016\225\013F{T\016\225\t\030\355P\270\007\255V\221qP0\250\331sP:qU\335*\273\255VqU%\006\262w\323K\323KSwQ\272\234\032\235\232\001\247f\224S\305<\034S\301\251\024\324\312jd5:\032\231Z\247V\251\024\323\301\311\251s\267\200sR!\365\342\237\270\376\025&\010\000\323\321\371\245lg\345\247\002@\346\236\017\24585H\036\236\036\234\032\236\255R\003O\006\244V\251\025\252\3025Z\211\363V\220\324\353R\nx\024\025\r\324T\022[+d\342\263\246\267x\330\225\351K\024\344|\257\305M\272\242j\201\205B\342\242\'\025]\352\273\324\016*\027\\\212\256\302\243\"\242qU\244\025ZN\365Y\305D#\311\311\2546U\r\301\310\246\230\201\357BC\273\275G2\252q\236j\024\345\261R\310\2331\212h4\274\216\264\3455 4\341N\3158\032\221ML\246\246SS\241\251\224\324\313\310\310\251\024\324\200\323\324\324\310A\353S#\214`\324\252\343\'#\212L\363\300\342\227w9\2517\226\306iA\247\251\247S\207\024\345lS\303T\201\352Ezxj\221Z\247\215\352\3227q\326\256D\371\025eMJ\246\244\006\226\203\315D\350\017Z\245s\007\004\257Z\253\034\204pz\212y9\250\332\241qQ0\250\034T\016*\006\034T\r\301\250Xu\250\033\203P\271\252\317U\334f\241)\232F\\q\\\256\r4\222)\003\262\3645\033e\2174\344m\224\346\224\2767S\323n\340j\304\221\243.\340qQ\004\\q\326\215\244S\226\235J\rH\246\246CR\255N\206\246\\\232\263\033\014R\347\232\2205=MJ\246\246SN\r\203R\371\271\024\356\n\217Z\010\333@jxjpz~\372pjx4\340jEj\221Z\244V\251\221\252\324OV\342c\273\025q\rL\246\244\006\236\r\031\246\232\212^\225\224\343\023\034S\373S\032\242j\211\205B\342\241qQ\025\315W\2250j\263\212\256\375*\007\346\240aQ\025\246\021\212\255!\346\271\242)\244S\010\244\333M+I\266\224\np\334x\315=r\rNHd\344\363LZ~(\035i\353R\245L\2650\251TT\311\301\342\245\335\223O\006\236\rJ\206\246SO\006\234\r<7\024\273\210\024\375\300\217zvW\034u\247.\336\364\270\317\335\244\r\203R+\324\200\323\201\251T\324\212je5b6\301\253\221\276q\216\265r6\316*u5\"\232x4\354\322\023PJx5\234>g&\236j3Q5F\302\242lTOP\236\rG\"\344UI\027\232\252\342\253\313\305V2`\323K\203U\335\362MW\222\271\242qHZ\232M&h4\001KN\002\237\212Jr\324\202\223\2758T\253S\251\251\243\035\315H*d4\360zT\200\323\301\251T\324\212\325 4\340\324\355\324\271\247\003N\006\237\237JP\304\016)\353\2029\353I\312\232z\275H\247\232\224\032\231\rL\246\245V\253\220\2660j\344g\025aZ\244V\251\003R\356\244-PN~CU\020b\221\2150\232\215\252&5\023TMQ\232i\344Uy\026\252H\265B\343\200k8\276Z\230\362\021ML\221\232d\225\3160\315DA\024\334\321E:\227\212QO\035)*@0\0058t\245\305-=*d\367\253\010r8\251\000\251\024\323\305J*E\305;#\265=MJ\r85874\270=\216i\300\221\326\234\0334\340y\247n\315(<\324\231\365\353H\016\rJ\017\245J\247\212\221\032\247SS+U\270\030\021W\021\252uj\225Z\236\032\227u\005\252\t\233<T$\342\230MFM0\232\215\252&\250\310\250\310\250\311\305B\346\253Hk>\353\2258\254\365\213\004\223PL2\334S\320aj\031k\234ja\250\310\301\246\232ZQN\353J\005<t\244\247\216i\353N\245\3059je5b/Z\2274\240\324\212jE5 4\340i\340\324\200\323\272\032:\322\362:S\203\032xl\323\201\247\003N\006\235\270\223N\316i\352qR\241\'\245H\207&\247SR\253U\250[\006\257#T\352\325*\265<r(-Lg\307\326\242-\334\323\t\246\023L&\230i\206\243ja\250\333\245@\365VW\305Vy*\214\244\263{T\017\307J\252\303-O\306\005V\224\327:\324\303L4\303IN\006\236\r8S\207JJr\324\203\212x\245\245\025*\325\210\317\002\236)\342\236\016\rH\r85<\032\220\032x8\247f\224\032v\352Pi\340\322\346\236\r8\036)A\247\203O\006\236\255R\247&\247\004v\251\024\325\3301\216z\325\244j\260\255R)\251C`\363Li\007\'\265G\273\'&\232[&\232M4\232a\246\323M0\323\010\250\330UyN\005e\\\312rqU\203\356\024\326\305V~j2\270\244n\225Rc\326\271\363Q\232a\246\232i\245\024\341O\006\237\221\212JU\342\245\316qNZu8\nz\365\251\326\244\006\236)\342\234\r8qRn\315<\032xjp4\354\322\346\234\r<\032\\\323\324\323\263J\r<\032vi\353R\243b\246F\343\025<c$\003V\224c\245N\222c\212\264\215\305J\207\232s?5\031$\375(\315%\024\204SH\246\232n3HV\2435\004\215\212\316\270\230\034\200k6f\004\324C\332\220\212aZ\211\352&\351T\346\357XMQ\032\214\232CGZAN\035i\302\226\224\032vx\251\242\0314\3420i\302\236\0059x5*\324\253O\024\341N\025 \247\np4\340i\340\323\301\245\245\025 \245\247\016)\331\245\31585(jxjz\232\231\032\254\304\325r6\350z\325\205\000\266EXSR+S\272\321\212P)qF)\n\323J\323N\0050\232\215\232\240\222P\240\344\326]\335\340\350\reKpOJ\215r\375jP\270\244j\215\215WsQ=T\232\260\232\243j\210\322Rt4\235)\342\234)M(\247\212\222#\206\253.\001\031\024\300*E\024\270\251\005H\225 \245\035i\342\244\024\354R\323\205<S\326\235@\251\001\247\nPih\245\006\224\032x5\"\232\231MXF\253P\276:\325\224|c5eZ\244\006\244\rN\006\234)\331\244\2445\031\2463\001U\345\235W\275g\334_\355\007\006\262\246\277f\316\rSfy\r\002/Z\225W\024\343\322\242cQ3T\016j&5Z^\225\204\325\033Tt\332i\344QJ*U\240\214\032QO\024\364\034\212\262y\300\245e\332i\313N\002\234\005H\240\212\224\014\323\261\306)G\025 \247\203E(4\340jU4\354\321\212u<\032v)(\024\352\0058\032z\232\235\032\247CS\251\351VCg\034\346\255\306\334\n\2305H\2475*\323\300\247b\230N*&\220Uy.@\3175Rk\265\037\305Y\267\027\231\350k9\313\312}\251V\337\035jA\020\024\245E0\323\030\324\016\325\0135D\306\231\236*\274\335+\021\205D\325\031\246\032m(\245\024\354\372R\203\332\235\214S\305H\275j\310q\263\035\350\352i\340T\202\224\n\221jT\031\2470\244\357O\024\340i\324\231\305(<\324\212\324\360j@iisK\232\013\021N\355\223A\342\200iA\251\024\324\252jt5:\266*\302\2779\035j\304.s\212\266\255R\251\251\224\324\242\231$\252\200\344\342\262\256\265UN\024\325\003~\362\0363P\310\362\037Z\200\243\267SJ \035\352A\030\035\250*\0054\212\215\215@\315\212\211\232\240f\250\031\271\2461\250\313qPL\334\032\310j\205\205Fi\246\232E%\024\242\234\016jE\367\251\027\322\245\013\307Zp\253p\306\031\t\364\244\3075 \031\247\005\247\201S(\305!\240S\251\302\235Hi)\342\236)\352i\340\322\346\224\032\0174\354\2221J)wQ\332\234\2652\032\260\225\"\236jp\340~U,r\200\3035z7\315XV\247\371\252\275N*95\024@y\254{\273\366\231\260\235*\232\304\\\345\3715ec\000T\235\260E!@G\035j\"0i\013b\230Z\242f\250\235\252\007j\201\332\240v\250\213S\031\252&n*\274\255Y\306\243aQ\232\214\322\036\224\332(\035i\342\236\265*\212\231E?mX\200\237\272;\323\366\025<\324\200S\200\251\024S\263IE8S\305-6\227\024\242\236:\323\205<\032\\\322\206\245\006\235\232Z^\264\202\236\t\251\227\246jdj\2234\370\316[\025$\352c\034u\244\217P\300\303pEL50\007\025^[\311%\345sP\215\357\367\215J\251\212\225x\247\206\240\2657v)\215%F\315\273\247Z\205\233\025\031z\215\232\241sP9\250\030\324,\324\315\325\023\265W\220\3253L5\023S\r!\246\232J)\342\236\2652\324\350*]\265f\331rH\003\232V\316\356i\352)\370\247\201\232\\`\321@\024\016)\353K\364\245\307\024\224\242\234)\302\226\224\032Zu.i\374\020(\317\006\200i\342\236\246\245\rR\006\247+\340\361VZA*\017QU\236\020\306\225b\003\265I\214\014P8\245\335F\372<\312<\312B\324\306j\210\276*3\'\2551\210\307\025\0215\023\034\324Lj\0075\003\032a5\023\232\255#T\rQ\032\214\323M4\323M7\275-<S\326\247J\260\202\245\002\254\333\022\255\305:C\227\310\247\250\247\342\227\265\004~\224\240\0222(#\201K\214\nQNQ\326\227\024\230\243\024\264\340i\324S\251A\245\245\351K\316y\242\234\032\236\r<\032xjxj\261\033|\206\231\346sN\337\232pl\365\246\027\246\357\244/I\276\215\364o\246\027\246\026\246\023Q\261\246\357\3051\271\372T.\010\252\356j\0265\0315\013\032\256\346\243j\211\2523M4\323M4\332Zx\251\026\246J\263\035N\242\254\333\256\343\216\346\245\222\023\030\311\246-IJ(\31794\240q\301\243\2674\270\247\001J(\245\024\237J\\R\201\232u(\240zR\347\024\341\322\227\255-\024S\201\247\006\247n\245\rOY\010\342\215\374\322\211)\376g\035i\205\350\337H^\223}&\3727\322n\246\226\246\026\246\226\246\023LcL-\236*\031\024\212\200\367\250\\\324,j\007\250\330\324f\2434\303\326\232M#SE8S\205H\265:\n\263\035N\2654Rl>\225l\376\372\"T\222E@\243\006\245\024\224R\212p\346\224S\251\324b\2121E(\245\242\234(\306iz\032ZQK\212(\245\006\215\324n\243u.\357J7R\357\240\271\305\001\350/M\335F\372]\324\233\251\013S\013SKSKSKTmM2z\363PI\355P1\250^\240z\215\215FM0\323\r4\323Z\212QO\025\"\324\311V#\253)O#5j\331\202\251\365\246\377\000\025?<SM&i\300\323\251\300\376\024\341O\300\365\245*G=i:\322\322\342\223\030\245\0034\001\353KI\322\226\234\264\352BqJ\r!\244\315\031\030\246\226\"\215\324n\243u.\3527Rn\243u\033\250\335I\272\227u4\2654\2657u\031\246\223LcQ1\246\026\343\006\241\221p2:T\rP\275D\306\243&\243&\232i)\244\346\212QR-H\005J\265a*\302\032\222\254[\270V\344f\234\347,H\242\220\322QN\006\234\r8\034T\213\3158t\243\003\034R\212\\Q\266\212)GJCE(4\374\322\023M\315\004\346\214R\036)\244\322d\322f\223u;<P\r\004\322n\243u&\3527Q\272\215\324\322i\244\323sHM4\232a5\023S7v=*\031\007\247J\201\252\273\032\215\2150\323I\244&\233\232\005<T\212jAR\255N\225a*A\322\246\204e\205J\343\r\315&i\t\246\320\r;4\340i\300\324\200\342\237\232\\\343\2458\014\232p\030\245\002\220\212L\322\322\036\264\224R\346\202sM&\214\322\203E!\024\302i\204\322\203\232\\\361\212L\321\2323M4\322h\335F\3527Rn\244&\232M!4\322i\204\323\t\250\332\243\'\326\242qU\030\324f\230M4\232i4\231\245\245\025*\324\200\324\250j\302\032\235jQRF\373H\305Lr\347\216i;\322\036\224\323I\232\\\323\201\247\251\365\247\203R\016\224\361\357R\014R\321\320\321\326\223\034\321Hi\010\315\035)3Fi\244\321\273\336\233\236iwzP^\230\315L\245\315\031\2434\240\320M74\322i\244\321\272\2234n\243u4\232nh\3154\323\032\243j\215\2522}j\2214\302j3M4\322h\2434\361OSR\003R\251\253\010j\302\032\224S\207Z\274\233V\023\317&\253\347\232Ri\246\231K\232p4\360i\352x\367\251\007J\220\032\220S\261K\214\321\212)1Hi;SM%!4\322i\245\2517Rf\2234\322h\315\031\367\244\335N\316G\024\252q\326\220\232ajBi\244\322\026\244\315\033\250\315\004\323I\244\315\031\246\236j6\250\215F\325@\232i4\302i\204\322f\214\322\323\201\247\003R)\251T\324\350j\312\032\235iI\305[\265\304\200\2065\034\213\261\310\240\021McL4\200\323\201\251\024\323\324\324\240\361O\006\244SO\006\235J\r.8\246\237jCHi\264\323L=)\244\323OZ\030\00085\036h\315!4\231\2434f\2246)X\372SwqL&\2234\322\324\233\2513Fh\315\031\244&\233\2327PM0\324mQ\232\316&\230M4\232a4\231\245\240S\307Zp\251\005H\246\247CV\020\325\2054\254x\251l\345\304\240\032\275=\271\220\3461\237\245Tu1\360\334T{\263M&\233\232p5 5\"\232\221MH\246\244\006\236\r8\032\\\323\267qIHi\246\222\232i\206\232i\215L&\2234\231\244\2434Rf\220\2327SI\244&\220\232i4\334\321\272\214\322f\2274\231\244&\2234f\220\323\rF\325\226M0\232i4\322i3KN\024\361N\024\361R-J\246\247F\253(\324\342j5b\216\010\255\233=McC\270U\033\253\257>B\325\016\352B\324\231\247\203R\003O\006\244SR\003R\003R\003K\232\\\322\346\2274f\220\323M4\323I\346\230M0\323\r4\323sFh\242\220\360i\271\2434\231\246\223I\232i4\334\321\2323KFi3M&\2234f\214\346\232MF\325\222Z\230Z\230Z\223u\031\245\006\236\rH)\300\323\205H*E5*\265L\257O\337HM cN\006\235\272\220\265\001\252E5 5\"\232\220\032\221MH\r<\032p4\264\240\322\321Fi\246\233Mja\246\232a\246\023L&\2234\240\322\346\220\236)\204\323sM-Fi\t\244\315!8\244\315\031\245\315\024\204\323I\244&\2234f\202i\215X\244\323\013SsIFi\300\323\324\324\200\323\301\247)\247\203O\006\244\rO\rO\017N\rK\232]\324n\2434\240\324\212jE5*\232\220\032x5\"\232x4\340i\300\323\250\315\024f\214\323MFi\244\323s\351L&\230\306\230i\271\2434\240\323\331~\\\212\210\234S\017\037Ji4\231=\251\271\2434QE&h\315\004\323I\2444\231\244\315\033\275\351\t\254&ja4\334\321\232\\\322\203R)\251\001\247\203N\006\236\r8\032x4\340i\301\251\341\251wS\203R\346\226\234\r<S\301\251T\324\212j@i\300\323\301\247\203N\006\236\r.i\t\244\315\031\244&\230M0\232a84\322\336\264\322i\215Q\232\006M(4\340\347\030\244q\305DM0\232M\330\246\226\240\032v}(\315\035h\351Hi3IHM0\322f\2234\026\254\026ni\244\322f\214\321\232p4\360jE4\361\322\236\r8\032p4\360i\300\323\201\247\003N\006\224\032p4\340i\340\323\201\247\203R)\251\024\324\200\323\301\247\203N\3158\032p4\354\321I\2323HM0\232\214\232a4\322i\244\323I\250\311\244\007\035i\336\342\223uI\346\002\270\"\241c\315DM4\232@\330\315(\351\305(4f\2274\271\244&\222\220\323I\246\223M4\334\322f\260Kd\322\023I\272\214\322\346\224\032z\232\221O5(4\340i\300\322\203N\006\234\032\234\032\234\r8\032vi\300\323\201\251\001\247\003O\006\244\006\236\r<\032\22058\032x4\360i\300\323\263Fi\t\244&\214\323\030\324li\204\323\t\246\223M&\230M4\236jEn9\2466G=\251\003qL-M&\230M&i\331\030\343\255.sFOz(\315\033\250\316h\246\032i8\244\246\236\264\206\271\342\334SKR\203FiA\245\006\236\rJ\206\244\006\234\r;4\340iwR\206\247\006\247\006\247\206\247\003N\006\234\247\232\220\032p5 4\360i\340\323\301\247\203O\006\236\246\234\r8\032v\352\\\321\232ni3HNj6\246\023L&\230M4\232i4\302iA\342\244\335\270TG\214\342\230M4\232ni\t\240\032x<\361O\335\232C\3056\2234f\214\322\023M\'4\204\322f\232k\233-I\272\215\324\240\322\346\234\r9NML\r<\032x4\240\322\346\215\324\240\323\203S\303S\203S\203S\301\251\024\323\301\247\203O\006\236\r<\032x4\360\324\360i\340\323\201\247f\234\r\031\2434\204\322f\220\232i4\303Q\232a4\322i\204\322f\214\322\206\301\346\2069\025\0214\334\342\233HzQ\236)CS\325\251\375E34\323II\2323Fi)\264\231\256_4f\224\032\\\323\201\247f\236\265 4\360i\300\323\201\245\315&i\300\322\203O\006\234\032\234\032\236\016jPi\340\323\301\247\203N\006\236\032\236\r<5=MH\032\22458\032vh\315.\352Bi3\357I\232L\323I\246\032\214\323\r4\323s\326\2234\023K\234\214S\032\230M%%&h\3158\032z\2659\271\346\230i\206\220\2323Fi\r!4\225\312f\2274\240\323\201\247\003N\006\244\006\234\r8\032x4\240\322\346\214\373\322\203N\rJ\032\234\032\244SR!\251\001\247\206\247\206\247\003N\rO\rOV\247\206\251\001\247\203J\032\234\032\235\2323J\032\202i3Fi3HM0\363L&\230i\206\233\330\323s\315-\004\323I\374i\207\332\232M&h\2434\003N\rO\rIM4\323\326\212(\244=i\246\271,\322\203N\006\234\r8\034\323\301\247\003N\006\234\r<\032]\324n\2434\271\245\006\234\032\234\rH\rL\r<\032p4\360\324\340\324\340\324\360\324\360\325 4\360i\341\251\331\245\rN\rK\232\001\245\315&h\315&i\t\247.0sM+\270dT\0140y\246\023M\355M\'\2323A4\323M&\233I\301\240\373RQ@4\340\324\340h\246\236i\017ZJPh4\206\270\372p\247\003N\006\234\r8\032Pi\331\245\006\235\232\\\322\356\240585.iA\247\203R\241\251\001\247\006\247\206\247\006\247\206\247\003O\rO\rR+S\301\247\206\247\006\245\31585.\352\\\321\2323Fh\315\006\223v(\017\214\212B\233\206sP\260\3052\230zSM\031\240\234\323M74\032LdqI\232J)sN\rK\234\321M4QE\025\306\212p4\340i\300\323\201\245\315(4\271\247\003K\272\227u.\352\003S\267R\203N\006\236\rJ\247\024\360i\301\251\301\251\340\323\203S\301\247\206\247\203O\006\244\rO\rN\rK\232pjPisF\352\\\373\322f\227>\364f\212m\001\261Ms\232\214\323\r0\232nisHM6\212L\322Q\232L\321\232\\\322\203KE\024\235\351h\2560\032\\\323\201\247f\224\032viA\245\315(4\003K\232]\324\003N\rN\rN\006\234\246\245\rO\rN\335N\rN\rR\003O\006\234\r<\032xj\220585<5(4\340iCR\356\245\315.i3K\2323Fi\t\244&\232M4\232\214\323M4\232L\321\232)\t\240s\305%4\234\321E\031\2434\340h\315.i(\242\270\300iA\247\003N\006\2274\240\322\346\214\322\356\245\315\033\250\315(jv\352Pi\341\251\340\324\201\251\301\251\300\323\303S\203S\303T\201\251\301\251\340\323\301\251\003S\201\247\003N\006\235\232\\\323\201\245\315\031\2434f\227>\364g\336\2234\231\244\3154\232a\246\323M!\244\315.i\r\024\037j@3IHi)s@4\354\322\321E\025\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\010\244IDATx^\355\335\211r\343\250\026\000\320\224\373\377?9\256\031\333\261\343\310Z@\002\304rN\275z5-K\010.\227EJ\267\363\365\325\266\353\364@\264\313\364\000\000\334\214\263@\034_L\001\240I{\227\300q6\tc\333\233\037\000\000\264\304\356\276\021:\352\240\016\236o.\222\000\000\000\006\322\301C\014\201<\354\301AM\017\242\313y\023\376Y\367\345\227>\030\234\004\340N\"\000E5\275u\206\303\256\327\nV\336\3470<\241&\'\334\362t\225\266\271T\265\336\356S\352\226T\306\302\017\000\3000\376<\365D>\377\337O\216\272\002\242\310\256l\016=\366\036\272\230:\351Tj\362\234\374\345\345\240\254\376\000\371\305\316\265?\347/^\266\370\301\016\347n\000R\266\244s\347v\024\345\\7G\205\\\200\256\254\217\371\365O\2419\326\260\235z\t\\/\355\330i\360\346\177\215\033\201\200w\357K\217\000+\227\354\267p\257_\233\'\354\223\251\330.=bu\375\372\367\236\001\007\003\370H\262\333\377gI\2537\007+\372!w}a0\251\207\350_\006lv\331B\23473\340E\252\001,Z\233\"#\267\0001\247Oo\033s-\211=\202\177\371\372\236\034\237\232v\322\264\023\243\035. \334\322\033\260\321M\373t\206\310\301\235\241\000\260\311TIE\244\343\246\303!\nx\226\370\343\360\r#\224\274W\007\256\361\275\031 C\221\201.2 Z\300\357\364l%\246\263-\271\037\014h$\351\265\2228\225\0216\240\214\214Kc\306\242\367\252\260J\220G\360Fbrb\372A\022\\\223dnw\374mG\371\3737\345\021\250G\220\362\206\352\377\322\237?\244\377\351\235\340d\313[\261:\004\007\2431s}7w\214\332\365\232\241\0041h\233\224\270\333\022\027G\313\316X\021\316\270\'U\221\002\305]oQ\177\306\375\300\032p+\342~\371L\031\227\231c\207L\0373S\227\237Yc\325\335V\313\317b#\177\267\306I*\t\026t\302\210:*\305\274\231\242\014\330 \3152\022\\Ng9/\357}\340\357\216\177\231\331c\366m\305\373\037\312T\243c\367\010\177<\324O\377\274\350-\376i\272\"M)l\272D\364\362\231jI\210&\202u\323LE\003\325\222\000\024\360\231\274\237G\252\325PU[!\244e\375|?\364\364\360\026\335D\016\323o\004\223g\235\233}\340g\034\021#<\342\324t\256\263wm\345a\272\025^\361\035\324S6N\022\240\227|\310\334E\231\213\317\245\321j\327b\272a>\333\376\356\334\177%pH\225\203\257\262\251\255\177\227\023b\036z\303\327y\211S5\364\376\275I\034\306Wy\251\313}\312U\356K\366\033\264`\324\321\000\037\014\006\306r0\343\017^\276\310\322\014\220T\360\264\032|bn\327\327\032SM\225\032\262\024\263\245\343i|\374[\2034\362\224\032\346v\357{\036\236Y\211\302\006j\352\036\231\262\234\212\215\326\345\271\036\3578\323%\274cC\317\243k\227\361\246>\350\313\357\243\364\333\221m5\014\374\245:\334\366\340A\215\240\037:\374\220\370\360-\215\276\342\342\253\016\300\274\364S{\372\022\311\350\325]\326V\302\370\301G_\214\374\341\214\333\345\346.\346\311\214<\262\3155:\214td\023c\371;1\313\377%\003E\246\305\246^\037\265\316\266\315\030\312F\002\314~|\213\374\354\007\231=\357\371\375\347\350\303\341l8\243Ag\t\014VHH\002\213\312b.\0178EH\252P\205T]u\346\310\'\241\310\216L\225?T\241\325\356\214\314Z\226\034H\200\003\227&TG-h\200Ic\215\350\214mi\"\275\347\305\334\267\020\317\035\203\362n\251\273\224\276\344UM\3347+b\276\n\323o\230\372m\031\214\252\245Q]w]\227\227\320\345O\316WwL\233Rs7/\370\255\262\177\250RLOC\256\247\266\000\000df\303\315\017\273h\316\"\367r\272O\362!!\356\352\347k=\265\005\010`\320\307\020\255A,\375`E\002\014h)\031*\325Vm\213\022\0326t\230\"\317&u\330\264\014\032\233\355C\255\356\\\356M\356\263\335\303\3534\237\353\266:\332h\307\336\216\334{\035\365\212\232H%\000\014-j\276(\305\274\224\334RH\257\313\0371\222*\277\267\257\253\277\273\000\000\241r\274\030]+\362\343\263\217\003\347\261\027H$GR\001\355iy*\270\327\335\252\020\353\362w\001h5\200\337\267\212\377\274 \270\264\333\214\363\265\033\271[\036\267[\373\032\264<\375\1775_}\356\222\366b\322\302\022\253\264n}M\241\247\006y3\224\247\326\256{uDw3\t\250\333\301\016\\\272|.;\347\216\245\260T\207x\351JjG\3126\317<k/\024\237+\027\206\260\020\323\363}V\354\363H]\374| \277\231\020\317\034\242\177\277\263A\235\tP\373l\005\344V\347\334D1\022\240\2544\361n\357/P?*\334h\265\017\230\351\357\303e>\335\313NV\0327\257p\212+\000\220X\242\355\305\314\366\222`\211:\201rtYaf\230\206u\327y\377\372k\0221.\022\340T\242\017\230\010`xf\001\350\310\216\367k\346\200\236D\'@\364\005\324\315x\236\032,\"\2035\027\210`~\200\\\306\332O\233K\022\020\304\303\204\020R0\222\000\000\310\256\350\246\263\350\315\200\342\306z\013\013\345\031c\000D\032\355\021l\264\366\206\312\025\227\327\326$\327\r\240\022R\034>\030\026\000\000\205\005\377x \370\304E\037\277O\341{\362g\212y\337w?\376\273\334N|\232\006P\251r\203\002\000\200\363\331\375u\306\223\347\311&\035\240?\006\327P\002X\014``\r\315U\000\225\351b\013\265s\031\350\242\355\275\320\031\033v&\371\207{93\205=;@G0\034IO;d+\274\271\030\021\207<\267\2033\333B\250A@j\0161\t<\246\272\350\246\006\204\217&\374\364d\262\016\215N%\316\225\252\347u|\363R\245B_\306J\354\261Z\233\226\3301\022\353EeL@{\255\246\362xa]\r\307\233\311y\327\351\001\232\264\222\360\037_\361\360i\373\014*\267\222\000\237\364\367P\002\272;\340\024\240I\177Fw\324JA\027\226\246w\271\360\264\024\241\023\235\3319\025\206\343\200\276Z3k%Yv\265~\327E\355\352\247\271+\211\020\255\237\250\214c\265\317V?\234J\231I-k>\016\3157\340d\342\007\264+j\335\357\226y\234\036\254\216\346\225\017\223\344\377\277\351\201v$i\177Y\025V\371Y\245\n\253\306\'\3354\234\253N\007\312ha\262Y\331\025\267\255\205\340\367C\264\233\367\234\t\032~\216\335\257\333i0?\241\013\362\372\327\006\325O\225\325W\020\000\000v\261\323]t\211y\254\21397\271\2147\317V\364\275\340l\245\0374\251\327V5\347>7\260\2327\327\255S!\347\320\255\327(7\334G\'\003\000\200B<\200\320\213\2507nP\234\207\274A\351x\300L02}?\274\200\257L\337\"\213\032\027\233\002:\034:b@\303\270b7\000\r1\265\225\2623\211v^\006\320\201z\226\250\357\235\223\361l\013f\017R\267\235\t\300nUE\274\252\3120(+\307\340$\000@\303\354%\001\000\000F\343u\036\264\307\270\005\362\363\256x0\226\226f\350*r(0\347\027\270\005\014\342{z\240vV\256\204\022|Q\004\0041p\273w\321\3074A\242\002\0000\252\337w@v\3050,/\203i\225\245+\0053\300\227T\202\202\016\016\267\304SV\342\342\032v\260_Z4`\223a\312\034\0100\260?\213\200\025a<\372|`\257\316\257<\013<\261\345u\275\377O\224a8\225\317\375\024\"\017\250\217MI1u\206\272\316ZAG\254\375\300@.\266\026\000\000\000\003\272.<\013z1\006\000\320\232\205\215\035\314\221.0,\303\037I\000\000\000}\332\263\325\337s\r\320\000\203\033\000\000\330\340wb\017L\337\003\000\000@\223<\322\017N\002\000\000\000\000\000\020\311w\n\003\214\3516\377\373\361b\333\364\037\000\000\300P\274\312\005\000\000\000\000\000\200\221\370\213\242\000\000\000\000\000\000\000\000\000\000\000\000\320\013\337\'\010\000\000\243\363T\000$\364\037\213Ab\036\317\"\"\006\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-9.textpb b/core/res/geoid_height_map_assets/tile-9.textpb
new file mode 100644
index 0000000..5397cb37
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-9.textpb
@@ -0,0 +1,3 @@
+tile_key: "9"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\363<\322\206\247f\234\r(4\271\2434\271\367\243u.h\rK\272\214\322\356\247\006\247\006\346\244\rO\rN\rO\rN\rO\rO\rO\rO\rO\rR\006\247\206\247\203N\rN\006\234\r8\032\\\321\232]\324\271\2434f\214\322f\214\322f\232M4\323I\244\2444\224QE\024\200\3434\235i\r%\024S\250\242\212up\364f\234\r(4\354\320\r.\3527Q\272\2274\003K\2327R\346\224585<5<5<5<585<5<5<5H\032\236\246\244\006\236\r<\032x4\340i\300\323\201\245\006\2274f\2274f\214\342\214\373\322f\214\320M%6\220\365\244\244=i(\244&\200sKM\246\232(\242\212QKE(\024\265\303\323sJ\r;4f\2274n\245\315\033\250\315(j\\\321\2323F\352pjxn)\301\251\301\252@\324\360\324\360\324\360\324\360\325\"\265J\255O\006\236\r<\032x4\360i\300\323\201\245\315;4f\214\322\347\336\212L\321\232\\\321HM%!\024\224\206\222\212i\245\024\264\332i\242\212(\245\024\264\270\245\351Hk\2074\224\231\346\234\r\031\2434f\214\321\2327R\356\243u.\3523F\352]\324\340\324\360\324\340\324\360\325\"\265<5<5H\rH\246\245V\251\003S\201\247\203R\003N\006\236\r8\032]\324\354\322\346\214\322\321I\232\\\321Fh\242\214\323M!\244\242\223\275-\024\230\346\222\233E\024S\251@\245\242\232k\2104\224\206\214\322\203HM&i3Fh\315(4\271\2434f\215\324\340iA\247\006\247\006\247\206\251\003S\325\252@\325 j\221Z\245SR\003O\006\236\r<\032x4\340i\300\323\263N\006\2274f\235E\031\242\224\032Z(\242\220\322\032LqII\336\226\212)\264\204RQJ\005-:\212CI\\I\024\332CIE\024\206\220\322f\223>\364\271\2434\271\2434n\245\006\224\032p4\340i\341\251\341\251\341\252@\325\"\265J\246\245V\251\024\323\301\247\203O\006\236\r<\032p4\340\324\340i\300\320\016)\300\321E(9\245\247QE\024Sh\2444\224b\227\336\222\220\322R\001\212Z)@\245\242\232h\256,\212a\024\323IE\024\332i\246\346\212L\322\346\214\322\346\214\322\203J\r(4\340i\341\251\301\252Ej\221Z\244\rR\251\251T\324\200\324\201\251\340\323\303S\301\247\003N\006\234\r<\032p4\354\320\r.h\3158S\251\324QE\024\204Rb\212LR\320zSh\246\236(\245\305.(\242\212CI\\{\n\214\212a\024\322))0i)\246\233Hz\322Q\2323Fi3N\006\2274\240\323\201\247\006\247\203OSR\003R+T\252\325*\232\221MH\r<\032x4\360i\340\323\201\247\203N\006\234\r<\032\\\322\346\226\234;S\251\302\212)qF)(\"\223\024b\223\024R\032J)\000\305-\024QHM%\025\313\2749\252\355\031\025\031\030\353M\"\230E%4\323H\246\323i\244\346\214\322f\214\321\232\\\322\346\234\r(4\240\323\301\247\203R)\251T\324\212jU5(5 4\360i\340\323\301\247\203N\006\236\r<\032x4\340ii\302\224S\351\324\3521N\242\227\024\224Rb\223\024PFi\264\204RQE\024\202\226\232z\n(\256}\201\035EB\342\241x\3628\250\031\010\250\310\246\221M\"\232i\246\230i\264\207\255%\024QJ\r(4\352Pi\340\323\301\251\024\324\212jE52\232\220\032\220\032x4\360i\340\323\301\247\203O\006\236\r<S\305.iE<S\305<R\323\251qKE\024b\223\024\224SH\2444\224\206\222\212CHh\315\024\231\2435\222\312\r@\360\372UwB\265\031\301\250Y*\026LS\010\3054\212a\024\322)\206\232Fi(\2444\264R\212\\\323\251\342\234*AR)\251V\245SR\n\220\032x4\360i\340\323\301\247\203O\006\244Zx4\360ii\324\341\326\244Zx\247\np\024\264\354QF(\244\"\222\220\212B)\246\233Hi(\2444\224R\032J+0\212a4\326P\303\232\255$\004Ur\204\036j9W\035*\"3Q\025\246\021M\"\230E4\212i\244\242\212)E(\024\352p\247\212x\355R-J\265*\324\202\236)\342\244\006\236)\340\323\305<S\305<S\305>\234;S\207Z\221i\342\236)\330\247\001K\212Z(\244\305%!\024\323M=i\207\255\024\332)\r%\024\332(\252\014\265\021Zk\014Te\210\250\\\346\240e\3109\250\031\010\250\310\250\312\324dSH\246\021M\"\223\024\230\243\024\264\240R\201K\212p\247\nx\025\"\324\253R-H)\342\244\024\361O\035\251\342\236\264\361R\nx\355O\024\361N\024\361OZ\220\nx\024\360)\300R\342\226\2121M#\024\204R\032a\246\236\264\323IHi)\017ZJ):\320\005-Se\250\312\324N8\250XT,\246\242<S\030\217Ja@\325\023\305\351P2z\324M\221\332\223\031\244\3054\212LQ\266\227\024b\235\212P)@\247\001O\002\236\005J\242\244Zx\251\005<S\305<S\305<v\247\212\220S\326\236)\342\236)\353R(\251\000\247\201O\002\235K\212Z)\010\244\246\322\032a\246\032CM\2444\224\206\222\212(\243\025]\205D\302\243e\250Yj\026\250XTL*\"1I\274\212C\206\250\312\343\202*\027\217o#\2450\212M\264\233h\333K\266\215\264\273i\301iB\322\201O\002\236\005H\005<\nx\024\360*AR\001N\002\236\005<S\305<T\202\236\005<S\300\251\024T\200T\200S\305<R\201K\212\\RQM\244=i\246\230i\246\233M4Sh\240\212LP\005\030\245\252\3548\250XS\010\250\331sQ\262rsQ2T\016\230\250J\324l\264\322(\353\301\246\225\364\346\2431\203\315FT\2126\321\266\227m\033iv\323\202\322\205\245\333N\013O\002\234\005H\0058\n\220\nx\024\360)\340S\300\247\201O\002\236)\340S\305<T\212*E\025 \024\361O\024\361J)\324R\021IHi\247\2554\323M0\323i\r%!\024\224QE\024\240T.*\"\274\323\nSJ\212\201\316\r0\340\365\250\23528\252\345i\245i\205j2\264\336E\033A\351HW\324S\nc\245&\332]\264\273iv\322\355\245\333J\026\234\026\234\026\236\026\234\026\234\005<\nx\024\360)\340S\300\247\201N\002\236\005<S\300\251\000\251\000\251\000\247\212x\247\212p\351N\024\264PzSi\246\220\322\032a\351M4\323IM=h\242\212LQ\212\\QQ\311\307QQ\2023\315D\347\346\"\241c\316\r0\25794\205A\250\210\347\035\252\'J\214\2554\255FV\230R\230W\024\017zw\226\010\342\231\266\227m.\332]\264m\245\333K\266\234\026\236\026\224-8-<-8\np\024\360)\300S\300\247\001O\002\236\005<S\300\247\212\220T\213O\035\251\342\234:S\307JQKE!\244\246\322\032Jm0\364\244\246\322b\222\212(\242\212*2\373\206\rDN)\215\202sLe\3151\224\324y\307\024\322\271\2462\344TEqM+\232C\0354\305Lh\361L)M\332iv\322\355\243m.\332P\264\241iv\323\202\323\202\340\323\266\322\205\247\001J\0058\np\024\360)\300S\200\247\001O\002\236\264\361O\025 \247\212x\355O\024\341N\035)GZu\031\246\223\232)\264\206\222\232i\246\233M\242\212m\024QE\030\250\260\001\246\355\004\320c\301\246\225\246\221\305Wd\301\246\036)\206\243jn)@\247b\220\307\232\215\242\305Fc\246\354\366\243\241\301\245\0034\340\224\241)BS\266R\354\245\333N\333F\332\\S\261J\0058\np\024\340)\300S\200\247\nx\247\212x\247\212x\353N\024\360i\342\234)h\242\212)\264R\021M4\303\326\220\322R\021IHE\030\244\305.(\305-T\rRdu\240\270\357L\334\017CLn{\323\0175\023\000)\207\024\306Q\330\3231F(\247\250\247c=E#F\010\250\214x\355Lhw\366\346\232ad\367\251\025r\005<%.\312]\224\273)v\320\026\227m\033iv\322\201K\212v)qJ\0058\np\247\n}<S\205<S\201\247\203N\024\340i\331\245\242\212)1A\024\206\230i\246\220\323h\244\305%\024QE\025L\214\032c=0\266i\003\342\232_\232\003f\230\355Qg4\034\201MV\311\346\236V\224\n\\S\305<-)L\320\023\006\234\321\002:Ub\2066\351\305<.E8-.\3126R\354\243m\033h\333F\3326\322\342\227\024\270\245\002\226\235N\024\352p\247\003N\006\234\r<\032x4\340i\300\361K\232Z(\242\212CL4\323M\242\223\024\230\244#4b\214PE\000R\342\263\013\346\232M0\234S\013S\013Q\277\024\302\364\201\251\373\251\274f\234\r<\nx\024\340\265*\212xZpJv\323\216)\222\306\032>\225]\007j\227m.\332]\264m\244\333F\3326\322m\243\024b\214Q\212ZP)i\324\240\322\203N\006\234\r8S\305<\032x\247\nZPih\243\024\270\246\232i\246\232i\024\224Q\212n1E\024QF+\017\1774\360\334SI\250\330\323sL&\231\234\232\t\305;w\024\3459\247\014\323\303S\325\215L\207&\246\002\244\002\236\005</\024\320\241\216\017z\202X\274\267\247\005\315;m\033h\333I\266\215\264\233h\333I\212M\264b\214QE(\024\264S\251E8S\205H:\323\205<S\251A\245\035i\324S\250\246\221L4\323\322\222\212)\010\346\222\212)1@\353K\\\343\032r\266E)5\0314\332k\032fy\245oZi5\"\032\234t\246\223J\257\212\235d\350EYI\001\353R\214v\251S\223S\252\323Ja\270\244\236=\313\357P \342\244\333F\332B)1F(\305&)\010\246\342\214Rb\212(\242\212u\024\341O\024\361\332\236)\342\224S\207ZQ\326\235J\0058RR\032a\246\232CIE\030\244\"\223\024Q\336\200=)Es&\2054\342i\204\323sMcH)\343\221\212\215\226\225\016*p\374S\013P\032\244Rj\312?\0305:\260\307Z\2327\301\253\321\020\302\234\313\363S\2312\275*\243.\327\305<\n1I\212LQ\212\010\246\342\220\212LSH\244\305\024\230\244\242\212u\002\234)\342\236;S\305<t\240u\247S\307ZQN\247R\021M\"\232i\246\232i1K\212\\sI\212LQ\214\322\025\346\224\016\324\240W.i\005\004\323O\025\0337\245&iA\247\251\024\244dR\005\243\245!\247-N\270\247n\002\234\262U\210\336\257[\311\203VD\300\232\262\010e\025Z\3456\2604\320(\"\223\024b\214R\021M\"\223\024\334R\021HE%6\220\322QJ:R\212x\353N\024\361O\024\361@\353O\035i\302\234)\302\224QM4\323M\"\233E\024\242\214Q\2121HG4Q\326\271L\323Kb\233\273&\232\317\315FM\000\323\363H_\025*6Fi\331\246\223@\245c\201M\363qHe\315H\222U\210\234\325\270\345\"\255\246H\334*\314R\236\206\211\244/\317\245*\034\212~(\305\030\244\"\223\024\322)\010\244\3054\212f)\010\244\246\322\021IJ\0058S\205<S\307Zp\247\322\212p\247\201N\024\340)\330\244#\232CL4\322)\244Q\212Z1K\2121F)\244P\005-q\346\233\324\320\307h\367\250I\246\323\226\245\003\212c\016jU\030\024\356\324\334\323\226\221\307\025\003)\246\200sS\306*\324b\255\306\271\305[\214\225\\T\360\214\346\247\n\010 \365\250\323\344}\265`\014\322\342\214R\021M\"\232E4\2121M\"\230E4\212i\024\322)(\242\224S\3058\nx\247\np\247\nx\247\212p\247\nZ)\010\246\221M\"\233\212JP)h\242\227\024\322)1K\212\343\013f\232[oJc1=i\271\244\247%L(\306M<\nZi\247-\0148\250\217\"\220/52\361V#Ry\253\221\n\266\243\002\254\300\234dT\354\234dT2/\000\216\325,|\201Rm\243\036\324\230\246\221M\"\233\2121M\"\230E0\212i\024\322)\244Rb\224\nZp\247\212p\351O\245\024\360)\342\236)\302\235N\244\305%!\024\322)\244RQE/jZ)\010\244\242\270|\342\202A\2461\246\347\232Zz\361R\nx\247QM<\323\322\207\351Q\nv)\311\311\2558\020\005\025aTT\3123Va;j\354 8\'\265C,xb;S\"\033X\255O\2121HE4\212i\024\334PE4\212aZi\214\216j2)\244SH\244\242\224\np\247\n\220R\342\235O\024\341O\024\361N\024\242\212)1M\"\232i\270\244\242\235J\005\006\222\212+\200-\315\001\251i;\323\205H\007\024\341O\035){P)\017Z\221(z\217\024\247\245I\002\026`kN1\200*\302\324\312*e\025j\014\203O\230g\004Uc\303\203V\227\221F)1M\"\232E7\024\021M\"\232T\0321\306*\006\\\032i\024\302)1E.)@\247\212p\247\322\201O\024\360)\302\236)\324\270\353F3@\024\224\323M4\332\r \024\264\341E\024\230\243\025\347D\363NZx\351KJ*U\247R\203JM\000\322\023\315J\235(\316M&)\247\322\257Z\307\205\031\253\212*d\025aEL\203&\254\'\007\212\224\214\212\254\313\301\366\251\3429QO\305!\024\322)\244Rb\233\212i\024\322)*6\03750\212a\024\230\243\024b\234\0058\np\024\341J\005<\nx\247\212p\247\nQE\024\206\232i\204R\021IE(\024\264\270\342\200(\305\030\2577\034\323\201\247\203N\245\035i\340\323\301\247\nF\342\2054w\247\226\302\323Q\262jZD\\\2775\243\021\342\254\245N\202\245\310\002\244G\253P\363\315X\306EVu\301>\364\260\235\247\006\254\342\202\264\322\264\322\264\334SH\246\021M\"\223\025\031\034\323H\246\021I\266\223\024b\235\212p\024\340)i\300S\200\247\212x\024\340)\324\275\250\305\030\246\232i\024\206\233I\2121KN\002\212P(\305\030\2575\002\236\0058Q\232\\\323\225\252@i\371\305!9\245QK\212\033\221L\037+U\205\344S\200\301\253q\036\225q\010\035iL\276\224\345rjt\255\033q\2003V\2052H\362*\020\207>\365:\036\306\244\3054\255&\332i\\Tl)\204SH\246\221M+M+M+M\333I\2121J\005.)\300P\0058\nx\024\340)\342\234)\300R\322\342\222\232z\322\032i\024\204Rb\214P\005-(\034S\200\245\244\305y\270Z\\b\212B9\2434\341\326\245Zu\024\364\024\204\363N\034\212FL\362)\312\330\353Rn\315O\023\0003\336\245\016O\322\246\217\236\265aMX\213\226\025\245\017J\262\265&2)\0259\351Oh\3062)\000\310\240\2554\214Tl)\204SJ\323\010\244\333M\333HV\230V\220\2554\255&\332\\R\342\214R\342\234\0058\nx\024\340)\340S\200\245\305\024\021M4\322)\270\244\305\030\240\n1K@\024\360)h\305y\2304\354\346\232N)A\315\004S\220z\324\271\300\245\003\024\243\232\223\240\246\232QS\001\305\030\024\030\373\212|C\'\025d\214b\245F\253\0108\311\253p\0163W\2438\002\254#T\312\325\"\363S\371{\223\"\242\t\212R\265\033\naZaZiZaZ6PR\230V\230V\232V\223m&\3326\321\2121@\024\340)\340S\200\247\201N\002\235\212\\z\320G4\204SqHE&)1F\3321F(\305(\024\340)qF+\313\305:\233\324\323\207\024\340sN\024\341\326\234[<S\201\30585\004\322\216\265(<S\224S\311\342\235o\313\223V$\351Dun3\221V\340\340U\3055:\032\225M<?5j\t{\036\206\245e\364\250\272\366\246\225\246\225\246\2244\302\264\233)|\272k-F\303\025\021\300\246\344QF(\333F\332M\264\273iB\323\302\323\200\247\001O\013F=)q\221JG4\322)1I\212L{Rb\227m&(\305\030\243\024\360)qF+\312\305-8R\232\000\251\000\300\240\236\324\243\2123J\032\236\r(5\"5H\r9\217\024\350\016\01754\215O\214\325\250\210\365\253\221\034U\244j\231Z\245V\245\316\rK\024\234\214V\222r\242\230\340\006\351H\303\212M\224\322\264\322\264\334\000ipOALh\237\322\253:\234\340\320\266\254\3434\326\262n\306\241h\335\017\"\220>:\361O\0074\360)vQ\266\235\262\224-8-8-;\003\246h\372R\201\216\224b\220\212LRm\244\305\033h\305\030\244\305\030\245\3058\n1K\212\362\214\322\212~8\245\002\236\006(&\221FNM8\2650\265&\352xzQ\'\255=^\245\022\nz\276jd \032{\216\364\364n*x\233\201W\242|u\251\325\252tz\225[4\254\330\2536\213\274\346\264\320\342\221\316\347\030\243\024\264\306 S0Z\244[|\363R\254[y\247\272\251N:\325#lY\262EZHF\334T2\302\312x\025]\243\335\324Tmh\030t\252\222@\321\036(G\365\253\n3N\tN\331@J]\264\270\243m\024\270\245\000\032B\264\233i\010\244\305*\216\264\230\240\212LQ\212]\264\340(\305\030\257%=jTZy\342\223v(\r\232w\035\351\245\361\322\243/L\336(\337\336\223\314\315H\032\246\215\207J\221\030d\324\350A\251\224`\324\200\3664\231\332\246\247\203$f\256+p*tj\2305L\217O-\232\275b\3406=kQ\024\036\225\033|\214sK\221Mg\364\241\020\267&\254EnX\360*\372[\205\030\"\234b@9\305FD=\360*\264\262 8Z\256\323m<S\326F\223\036\225(\205Xt\346\206\266\300\371j\264\266\233\263\221Td\262 \344\n\215T\241\346\247A\232\227e.\312M\264\322\264\230\243\024\270\245\333AZM\264\205i6\322\250\000\234\322b\215\264\230\243\024b\235\2121F+\3111\315H\274R\232h\031\247\022\0050\275F\315L\'4\302i\246OJE\220w\247\254\303\326\244YT09\247\254\336c\341x\253h\330\034\032\262\262|\2714\261\311\274\373\324\2238\001GsVm[\367d\032\235\0375*\271\035*d\223&\254+S\303\324\360\273\0021[6\363\344\014\365\253C\347\3523A\210t\002\221m\035\317\3355v\033=\277z\254\210\302\014\360\005C-\317e\252\31730\250\031\232\252\276\342i\2715<R\221S\213\254\034T\3510r0qW\002\006Q\300\346\230\326\201\272U9\3541\310\025I\255\3323\2208\247\'5!^)\205i\245i\245h\013K\266\224-\005i1I\266\215\264\230\306i\002\322\355\244\305&)qKE\030\257&\331\212pZk\373Sz\n\214\234\323\t4\302Oz\211\346\003\201P\371\244\236i\371\310\250\217ZQOZz\261C\221W\"\23389\347\275X3\340\343\265Ii \363y5=\311\314\253\212\263\013`}ju|T\252\364\365|\032\230MV#9\346\256F\330\034\325\353yFEn[H\205GL\325\245e\'\370sR\371\321\240\371\216\343\350)>\322\010\302.\rC$\245\270&\253\021\232M\264\322\225\023\307P\024\240)\024\354q\232\221\t\035+F\336^0\335*\364d\036\225ab\0140\302\240\237O\005IQX\3676\215\021\334\005B\215\232v\332B\264\322\264m\243m.\334Rm\244\333AZM\264\322\274\322\343\024\323\3054\232M\324n\245\315\'>\224\273I\257+lRg\216)\204z\324nj\026`*#%D\362Uwj\214\266)\313%?viA\247\006\305(z\221\037oJ\262\254\n\362y\244Y\2126Gj\320\211\314\307y\364\253L\333UO\255>9sS\253\322\371\230\245\216B^\267\355\255\013@\035O\024\004!\261V\243!:\346\256\3013\266\002\212\324\2066#.j\177\335G\313\234\232O47B\007\2654\234\367\243\024b\227\024\322\200\323\014T\337*\236\261\002)D\035\305H\250E_\266\343\025}\010\317<T\340c\336\253\\[\t\201\342\271\313\270\r\274\247\035(\214\206\034S\212\322\025\244\333N\333HG4\025\246\342\220\2121M\300\250\335\200\250\211,x\247\010\031\251\337g#\255 \217\232\221a8\247yT\206<W\224\025\036\224\323\201P\271\250\035\260*\263\022MF\306\241cQ1\250\215 5\"\276)w\322\356\247\006\247\207\251\222BF\321Ly\n\235\244V\345\236\014JE>v\373\253N\210\360*\33251\337\232\265n\200\200Ml\333]\210\343\nNG\245)\273\313p1Wm\246\014>`\010\255{f\207\031R\006:\324\263_\240\033R\253\t\313\036Nj\302>ju\346\237J\r\007\353J\r\024\340\231\247*S\300\301\251\000\366\251\020\340\325\373e\336G5\243\345qQ:\n\310\324\255<\300N+\017i\205\275\252\302:\260\340\322\221I\200)\t\024\231\024\322\342\2432\016\325\031v=\005&\347\364\246\222\347\265\013\003\261\311\253Q[\205\353S\355\013Ma\236\324\301\037=)\341\016)\014f\217.\274\204\212\211\205B\374UW\3115\013qQ\265D\325\013\na\024\323I\232\\\322\356\245\rO\rOI6\266jI\037p\007\336\266l\234yC\236\224\347\270\005\261\217\306\244\216L\324\302LSD\233\237\025~96\000\005Z\215\213\n\265o\t\221\376n\007z\272\322*\r\211NIXp\rL\256OSVcj\265\033\342\254\244\2250z]\364\273\251U\252@i\353RS\200\006\236\242\237\264\324\360\314b\"\265\355\356\026T\301\373\325#GU\346\2040<V%\345\230\311\300\254\326\264pr\271\024\236T\302\233\344\316{R\213Y\217\\\323\276\303.y\3158i\316z\223S\307`\243\250\315Y\032x#!x\2456 \034\025\250\332\311Gjg\223\216\202\220\300~\224\242\020:\322\024\002\220\250\024\224\323M\257\0365\023\036*\274\207\203U\232\241j\214\364\250\332\243\"\243\"\230E&(\305%\024\340iI\251\003.\337z\277gq\225\353\322\234\322\345\252\344-\305L[\212\215$\303\326\234\r\270\014\326\214X\002\255\tx\302\360)\312jej\231\036\254F\365e\036\247G\251\203\232xzxzP\365\"\266jd5.}\351\312\325(5\"\234\365\245#\232\275g(\215\206k\\:\272|\247\232\211\205A%\272\277QP\265\232\366\000\325w\266\307E\240D\000\345)\2050\331\002\246\t\274r)D\002\203\010ZM\341F\007J\202I\262x\246\371\343\0375D\323F\017\024\217.zTe\252\026j\214\275(9\2434\332\362\006\025\013\212\251)\346\253\275DE0\212a\024\302\264\322\264\302\264\322\264\205i\244SH\305(\245\305C<\276Z\222\016)\332d\345\263\223\326\264\201%\271\253\321>\005J_\212\213v\0335\255k\'\356\324\326\2042f\255!\251\224\323\267\372T\250MXF\305YI\005L\263\001R\254\242\236$\247\t(\022T\311%N\222T\242Z\221^\246G\251\224\346\245Q\2322T\3475b+\355\204\014\326\2147K(\031\353S\343\214\212\214\322\034S\n\003Q\030\2114\360\240S\361\212\2574\300dt\252NI\031\rU\244W\316s\232\202\\\2163LRE;y\0244\231\025\02350\265(zv\354\320My\023Uy\033\322\252?Z\205\2050\2554\2550\2554\2550\2554\212a\024\323L\"\232i\264\352\315\324$\302\220*\326\212\204\214\236\225\253\374uj3S\003\305D\347\006\264\355\030\030\226\264\242\224\001\201V#z\260\255R\241\031\353S\254\201{f\227\314$\344S\326CS\243\023S+\021\326\244\022S\303\323\203T\210\325:\275H\262T\213%X\216J\265\033U\225aMs\236\225]\301\007\"\254\332\335la\232\337\212ex\301\0074\034\032a\024\200\322\346\220\220*3(9\025J\347\'\221T\335\231V\242\016O\336\244`\r7g\025\033\014Td\323\r4\232@h\017\212R\365\344\2621\252\316j\026\031\246\225\246\025\246\221M+NX\201\004\261\305Wu\347\212\210\212a\250\3150\323\t\244\243\265f]\246\351\000\255{\030\374\250F:\325\241\367\252t5:\322\262f\255@\031\000\364\253)!\0075r)\263\336\256F\371\251\343 \036j~\033\356\324\253\220=\351y\0075<s`sN\363A\247\007\247\211)\302Z\221f\307z\224M\232\221d\251U\352x\344\305]\212^*e\222\246S\221Lz\205\362\274\325\375:\364\203\265\217\025\246\263\202z\323\314\312\007Z\210\334(\353Lk\324\025]\356\331\363\216\225\037\236Oz\212IK\036\264\306pG5\0162j@\243\034\322\021\201P\265B\324\323\315Fi\271\244&\232My[\324\014)\273x4\302*2)\244Rb\232\335*&\025\023\n\205\252\0264\302i\244\321JzV|\347\367\2035\253f\013F\t\253\241EH\270\251\227\024\375\352*\304rdd\364\246Ip\027\201V-\244\316+J90\005YG\3175b9MXY)\333\363F\352xjxz]\364y\224\tjT\224\325\210\336\255\306\331\251\324\212\263\033U\205z\231d\305!|\232q\033\326\230\243\3139\025g\355g\030\357A\271\'\2754\316i\245\311\245\016i\333\251\214i\264n\300\342\223\314\315!zal\324Li\264\323\315Fx\246\223M&\274\265\2522\264\230\3050\2550\2550\2554\212c\n\205\252\027\250\036\241ja\244\245\002\234G\025F\346>sW4\351\2066\261\300\025\246\010<\002)y\024\354\232P\244\375*O7h\300\252\345\213\311Zv\274\001Z\010\365z\006\335\305Y\300\035)\003\324\210\331\251\001\247\006\247n\244\337@|\323\3075\"\266*\302I\212\263\034\265a$\253\t-X\216J\225_4\375\325\"59\2153\275<c\024\022(\016)\300\323\267SI\246\223M&\230M4\265!4\231\2444\303Lja\024\323^`E \000R0\310\342\243+\212a\025\033\na\250\232\241j\205\352\006\250\310\246\221M\3058\np\\\324\0271\344b\250\020\361\234\212\261m~\361\261\3632kF\rAX\363W\005\302\205\310\002\241{\242N)V\\\212\236\021\223Z1\014\n\262\215\212\265\004\2705o\315\342\220?5\"\311R\211)\302J]\364\273\350\rS#f\235\272\244F\2531\275XW\251VLT\361KV\222Q\353R\t\005J\262S\367\344Rf\205\223\024\374\203M\350i\333\351w\346\215\324\322i\244\323\t\246\346\2234\271\244\242\242q\212`>\264W\231\225\246\221M\"\230\302\243\"\243e\250\332\242aP\260\250\\T,)\204SH\244\333J\026\244\013\212\216E\315Wh\001\250M\267=(0\024<U\310\203\005\000\232q\214\346\247\211=j\324\177)\253\321\276EN\246\246F\305N$\247\207\315<=J\257\305/\231N\363)C\323\203\324\321\2658\2775,oVQ\252Q%<IR\307.OZ\264\254}jt~9\247\2111R\ti\342L\364\245\316i\341\360(\363)\013\322\206\247n\244\335M&\220\232J(\244\006\202qMnj\"0h\2578+M+L+Q\260\250\330sLaP0\250\330TL*\026\025\021ZaZn\3326\323\325i\032\243\306z\323X`PW\214\323H\311\025\"\234S\267\323\225\352\302\034\325\250\016*\342\232\231N)\341\205<0\247\253T\201\2517S\267\323\325\362je\347\255I\274(\244W\311\251\325\361R\254\2250~)C\346\244W*\302\256\307>@\251\204\331\2453zS\322\\\324\352\365 z]\364\240\320[\024\t1N\017\232]\324\205\251\273\251A\247f\226\233Fr)\244\342\230Ni3^zV\232V\243+Q\262\324L*6\025\023-D\302\243aP\262\324ei\214\264\335\264m\243\024\322\271\244\333\212\215\305+\034\001L\003\234\324\200R\021J\2654mW\"j\266\207\"\234d\305\002\\\324\252\365a\017\024\354\321\232P\325\"6*p\331\034SY\215*\275J$\251VJ\235d\342\236\262`\322\274\26543\347\203S\211H\247\2113S#\342\246\022T\202Zw\235@\233=\351|\312O2\234\262{\323\304\224o\245\017OSO\006\2279\244&\243\'\232B\324\302i7W\020R\243+Le\250\231j&Z\211\226\243e\250\231j&Z\211\226\230V\230R\233\262\223m&\3326\323J\324,\274\323\030f\200\265 \024\021J\0059\001\'\212\267\020\351V\323\245)\024\321\301\251\220\324\352\324\360\324\354\321\232p\353\305N\206\225\31538\247\253T\252\325*\275J\0374\355\331\251\"85d7\024\252\325:\311\305<IN\022\342\227\315\245\363h\023R\211sR\007\247\t)\341\251\352jEj\2245.\3523Q\263S\t\315F\317\212a\222\271r\225\033%D\313Q2\324L\265\023-D\313Q2\324l\265\031Jc-FV\232V\233\266\232E&)\010\250XS6\322\355\247SM\024\36485n6\351V\220\346\224\232eH\271\251T\324\200\323\301\247\212p5 <SK\346\220\032x5*\324\200\324\210jPjDlT\241\251\301\252P\334S\303S\267SK\342\215\364\241\251\352\3252\275<75\"\236j`i\331\251\025\251wQ\277\212\214\266i\204\342\242w\250KV;-DW\332\243t\301\250\231*\026Z\211\226\242d\250\331*&JaJ\215\222\230R\232\313L)L+M+L\"\230V\233\266\223\024\230\246\221F)EH\214E[\211\315N\334\212h\251T\324\213\315H\005H\005;\024S\351\273y\247\001O\002\245Zx\251\024\342\244\006\2245J\255N\335\315L\215\232x4\271\246\226\244\335N\rR+T\212j\302sS(\342\234\033\265\005\251U\351\333\3517R\347\271\250\335\352\026j\210\265Qd\250\331j6N9\353P\262TL\225\023%F\311Q2Tl\225\031J\215\222\230R\243e\246\262\324ei\205i\245i\205i\214\2704\322)\204b\214Rb\235\212U\025:\034T\313\'cO\034\324\212*U\251T\324\200\322\356\245\3158\0323NZx\247\347\024\241\251\301\351|\312<\312\225$\251\224\346\246V\305H\032\224\2654\323i\300\323\301\251\243oZ\266\203\214\324\201\261N\006\224\234\324d\340\323\203f\234\010\035i\035\270\342\240g\250\331\251\205\251\205j\"\231\2462\324,\225\033%D\311Q\262TL\225\033%F\311Q2sL)Q\262S\nTei\205i\245i\205*7^j2\264\322)1F(\305(\025\"\324\212\271\2531\245K\267\024\n\220\032p4\341N\006\227u\004\320\254jA%.\362i\273\311\247\253\023R\014\232xZ\225\026\246\034S\267b\234\257\315)j\004\225 `i\331\245\006\244C\212\266\217\305K\270S|\316iZN8\244\031\357J[h\250\314\246\2173\212c5FZ\2235;&j6J\215\222\242d\250\312Tl\225\023%F\311Q\224\250\331*\"\225\031J\215\222\230\313Q\262S\nS\nSJ\324L\265\033%1\226\223m&\332\n\320\026\236\005J\202\254\245I\232\000\3158\nv)\302\235M4\240\323\205\004\322\203N\006\236\rH\246\245SN\335\212Q%;vi\312i\371\315\000T\200T\213\315?\024\341R\253\340T\201\263N\343\326\234\010\245/\212k8\"\240\335\315.\352Bi\231\2435\242R\243)Q\262TL\224\306J\210\245F\311Q\262Tl\225\033%BR\243e\250\331*2\224\302\225\031ZiZ\214\2550\245F\311Q\272\323v\321\266\215\264\233)\301jE\030\251\024\342\237\232@\3305:\020\325&(\002\226\212a8\243u(4\340i\324\3655\"\324\253N\306h\332i\301H\247\250\247\347\024\241\252e\251\007\255H\2434\360\224\275\005*\265\005\350\3631A\223\"\233\272\233\236i\331\244\3154\320\rm\024\250\312Tl\225\031J\215\222\243d\250\331*6J\211\226\242aQ2Tl\265\033-D\313Q0\246\021L\"\233\266\232R\243)Q\262S6Rm\245\333F\332\002\323\202\323\302\323\266\322\025\247\247\006\254\216E8\n\030T\014H4\204\346\201\326\234*E\024\354S\324T\310*P8\243<\324\213Rb\214SM*\324\240\342\244\00752\n\224P\335\351\203\212k5F\033&\235\232M\324f\234\0334\271\240\322W\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\006\"IDATx^\355\335\333\266\342(\020\000\320\263\234\377\377\344q\315x;G1\367\020\002\324\336Om4\tP\005\001\264\273\177~h\307%=\000\000\000\000\004`G\000\000\000\310\315:\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\032\345/\013\003\000\000\000\000\324\315>.\000\000\000DdG\000\000\000\000\000\000\000\000\240]~\373\001\000\000\254b\021\001\000\000\000q\\\323\003\204\364\177\036\\\345\002\313\330=\214N\006\000\000t\3114\017\000\242\2623\314\274\213\311bl\022\000b2C\210I\334\001\000\000bJ\326\203v\004[\267r\201\177\013\370\312S\250\232h\002\000\000\000@\\\266\370\001\000X\301\3641\256{\354%\000\000\000\324+\347\277\362\221\363Z\344\347\267\337\214\320s\203\223\000\020\227\376\017\000\221\331%\000\350\226!\036\200\233\322\317\203\322\367#\221\006 }\235\231\315e\000\000\000\200\340\016\336\177\002\000\000\340 \326s\237\242\374\000\"S\3343]\006\000\000\000h\202\235\000\000\000\200p\236\337\241G\371*\2357\266\001\240\t\272*\000\333\230\341C([\273\374\326\363h\335\353?\233\277Zo\004\'\001\242\223\001\301I\200,\314\246\000h\215g\027\000\000\000@\313\354\356\000\000\000S\374(,\2364\346\351k\000\000B0\r\004\010\353\342+\344\310D?8\t\000\000\020\203\255\277\350~3\300\022\000\000\272c\246\307:2\006 \212\333\026\200Q\237\317\034\2601\004\000\315\362\030\007V\353vE\330m\305 \247\367\216\242\323\304!\326\335\261\n\000\000`-s\310\270\304\036\"3\002\000\000DgF\030\214\357\205\203\223\000\020\217\'=\000\360\306\324\240m\342\007\000\360db\024\234\004\240i\333\276\255\333v\326\235\036\323\236#b\266#\205\340|Gt\n\000\200\354rNZ\314\340\033\2645\001\266\2367\355\230\253RJ\215C\200\234\002hC\215\317\020\312\362\314\216N\006\004\367L\000y\020L\032\360\3645\264\3004\026\200\332\205\231c}U\364\353\000\347\021\014\010n|\335\264kx\030\277,\237\216j\251\r\341\033:\345\250\342\0015\032\032\005&\031\"j\366\027\316j\343\264:\343\350\313\241\tp\350\305Y\244\332\241\007z\226\253\343\355\271\316eh\004\23696\3646m\022\313S\355\351\2720+C\202e\270D.F\253\315\036Q\254(\226\034+\355+[B\177;g\313yT\350-\220b\332\274\264{\007P.k\017j\334W\005\376*\222\365F\213/\366\371\301\301-\200\345\336\316.\027\240\266\034\325.k\003\267\366\363\017\327G\361/\267?\354\314\025\010\355\264\356\263q\010z\225\367\275\334\317\361\000\210\313(P7\361\251J\301\'\377\\\3443\025e\3566a\2156L\246\206_\351\25589\013\220\363Zg\030\215\322N\023\355r9\352\236s&\312DnU5\366\2430\037\313\267\267?\347UU\305\3330\030\214\301\203\313\315\237\2763P;O\347\333|\314\026\273G\347~\275#{\375d\016\330!Z-\177\213\215E(\377\235\330\357\370\250<\362\341\370\373\260\322XG\205\355^Yu\371\353\363\217I\301?\333\307\200\3475G\022\366\357pz\203\221\023\342\032i\220\221\303\204\223\366\240\212\205N\332\232\3434X\266\301\203\031\035}\375\316u\333|\335V\354t#-;r\370\347\347\237\364@)\243%\"\261\252\245^_*\255:\351h\203\205\031<\310\200\365-\025z\026\326\201\201\345\372*kN\272\337+G\302\244\327x\177\235\276\307\264{\000s5Z\235?\323/V\250\341\316P\354\366\333\014\027z\205\327\267n\225\327\223#}\007\377\324/c/i\201\316,\014P\232\036\177\256\357\'B\020\022o\204\206i\317\242\230]\236+\277\267\017?W\202\'\016\002\023\267\236x\213E\021\037uo\332\312\332w\2668\373\252\314\237\340-9\233i\255Z\\\261SW\375;,,\366X;,<=\214z\333cc\311\346N\273\275?\367\231V\215%}O\216\036\267\016\276|niq\353\374b\'\217\216\253\266]\232\000[i\3346=\306\303g\364\326\006\361\231<\327\333E\326\236{\214\301ei\372z\314\327\227I\237n\225\374\374\300\324\253Pj\257\372\\\371\026\217\202\237\027\372=m\361\371\375\231k\332>\254\255\345\332\317\323\210\300=}Xo\r\322[}8\214T\201\272\035:\021\233\272\370\324{\034cn@\236{\177\316i\273\243\005\357[\360V\345$\201\337\233\007\257m\263\304\3201j$Rls\037\036\273\034#\013i\275\353\365\036\373\326\343\303N\317\004\350)\017~\353\322S\245\250\305\340b \210\337\252\367\376\\<\303\231y\2253\251\037\227\272\234[\241\r\032+.p4\203B\007\0041<)\000\254d\235\373-c\233\344\\v\316\333R\360\353\343\311\361*g\321\362\226\266\265r[\317k\307\226\314\351[\3771gJ\323\361o\272\360\203\014P\253\364\227\000\265Jfx\265\344iw\t\320]\205*QK\302Nk\243\2244\301P\302\"i\242\224\032\2052\335\'\323ez\244i\356\322\004\207\030\226\016\000\317\317\351(\301I\000\000ZP\366\207\007wK\247T\243N+\362X\311G\217\217\275\001\264\245\374\250CU$\000\363<\362\001\272t\375\367\371C\370e\032~\032\254\250e0\227[\323\274\375\017\022\327\206\243\014l\363\335\355\215\231\000\224\366\3754\002\330\302L\026\240\010\303-\000\205y\364\000\000\204\347\357-\001\000,c\332\004\020\234\357T\000\000\370f\273\200A\022\003\000\200\305\354=\003\000\000\000\000\361\330\031\355\204@\002\000\000\300\371\254\317\001\000\200I\345\376\322\263\345IY\325\265wu\005\nF\373\003\000PX\271\325f\353\252\233\254WW \000\000\000\000\000\000:\344\013E\000\000\000\000\000\250\312\177]O\370\366;\244\233\330\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-b.textpb b/core/res/geoid_height_map_assets/tile-b.textpb
new file mode 100644
index 0000000..b04a194
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-b.textpb
@@ -0,0 +1,3 @@
+tile_key: "b"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\310\315-\004\346\212(\245\315-\024\003O\006\234\r;4\273\251sFh\335F\352v\374\014S7SKRn\243}(jP\364\340\364\341%<I\357N\022R\371\264\276m;\315\367\245\022S\204\224\3572\227\314\243\314\243\314\251c\2235)l\212Bx\254\373\221\345\266}j5|\323\213SKS\013Rn\240\265\033\2517Q\272\233\272\224\034\325\210\370\024\354\322n\244-\201Q4\300TfqQ\231\3523=!\270\250\332\350Uv\271$\325\243I\232\\\321\2323Fih\315\024S\201\247\003K\232PisK\232Bizu\246\226\246\226\244-I\272\223u.\3527\322\357\245\017K\346R\371\264\242JQ%H\254M<\023N\334iw\032]\364n\243}H\222b\247\022\344u\240\311\216\365\005\313\007_\245T\007\322\235\272\230Z\232Z\233\272\227u\033\2517Rn\240\232\222>\265eM\014@\250ZP;\325y.3\300\250\014\204\323K\323\013\323\013\324l\365\0235D\317\212\326\242\212(\242\2123K\232L\322\203N\315\031\245\315.isK\232\032L\214S7SKRf\223u!jM\324n\243}\033\350\337F\372pzp~j\302?\025&\372M\364o\245\337K\272\223u(z\221d\247\027\310\252\357)\246+f\224\265F[\024\205\2517Q\272\214\322n\245\rAl\232\2323\201Ry\230\250%\237\035\rViI\357Q\226\315&i\t\2463Tl\325\031j\215\232\242c[tRf\214\321\232J(\315.h\315-\031\245\315\031\245\317\275(4n\3054\2654\2654\265&\3527SKRn\244\335I\272\215\324\233\350\337OS\232v\354S\326lT\202l\322\371\236\364\242J\220IK\276\215\324\233\251\301\351\346L\n\202Bv\223H\207\212R\325\03357u\033\250\335F\352M\324\273\250\335R\tp)\217)\250K\023IHM4\232i4\302\325\033\032\214\232\214\232a5\273A\246\321E&h\315%\024\271\2434\264f\2274f\214\321\232ijajn\352M\324\233\251\013R\026\246\226\243u!jizM\364\365\233\024\246\\\322y\224\242Zp\232\244YI\357R\254\224\361%8=.\352pj\035\270\244V\334\244Ss\216)\245\251\205\251\273\250\335J\032\215\324u\245\346\212Bi\271\244\315!4\204\323I\246\023Q\223Q\261\250\311\246\023L&\272\n\r6\212L\322QHh\315\031\244\315(4\271\2434\243\212L\346\202i\205\251\205\251\245\2517f\220\2650\265&\352izM\364\205\351\205\351\273\350\337K\346R\371\224\273\351C\323\226J\235e\315J\036\236\036\234\036\234$\244y:sJ\222\000iX\367\024\302\324\302i\271\245\315.iG4\361J[\212\215\232\2234\204\322f\222\220\232a5\0314\302j64\302j2i\244\327EHi(\246\321E!4\334\321\232L\322\346\214\322\346\202\324\200\322\026\2463S\t\246\026\243<SKS\013SKSKSK\323\013\323w\322\027\244\337F\372<\312]\364\340\364\242J\231%\305L\262\324\213%?\314\364\240=#>M9^\244W\310\347\2654\2674\322h\315-.}i\333\370\342\223u\033\250\244\315!4R\023L&\230MFM0\232a5\0314\323L&\272J\017Jm\006\233E!\246\232JL\321\2323Fh\315\004\320N)\244\324d\323I\3053=\350-Q\226\246\026\246\026\246\226\246\026\246\226\246\026\246\227\244\337I\276\200\364\355\364\273\351|\312Q-H\263\221\336\247I\263R\254\224\340\364o\346\244W\247\253\340\020)7R\356\245\315.h\006\2274f\224Q\232L\321\326\202i\244\323\t\250\311\246\023L&\230M0\232a\246\232\351h\2444\224\323E!\244\246\322\032J)3@4w\244&\232M0\232c\036\324\204\323\t\246\023Q\263Te\251\205\251\205\251\205\351\245\351\205\351\273\350\337I\276\227}.\372<\312]\364\241\352T\223\336\254$\324\363/\245*\311\236jUz\2326\371I\245\315(4\340is\315.is\3158R\346\220\232\000\240\232i4\302i\204\323\t\246\023L&\230i\246\230M0\327OE\024\204b\222\220\212i\244\244\"\230h\244\244<P\017\024\236\364\204\323I\246\023L\246\223L&\243cQ\226\250\331\2526j\214\2650\2650\2750\265&\372iz7\322o\245\337F\372]\364\340\365\"\275H\257R\253f\246CS\003R\253\343\212x4\360isN\006\2279\247\201KFi:\322\346\2234\322i\204\323I\246\032a4\303M4\323Q\232i\353]0\245\242\212LRR\021\212i\246\232a\246\232)\t\243\266(4\323LcQ\223\232Bj64\302j65\023\032\214\232\211\232\243-L-Q\223M&\220\232ijM\324\233\250\335F\372]\324\360\325\"\265J\246\246SS\306\325:\234\014\232\"$\2615`\032p4\360iA\315H\264\354\322\023Fh\315\031\244&\232M4\323\t\246\232a\246\232i\246\032a\246\036\365\324QK\2121IHFi\r4\212i\024\303L4\206\232i\302\203L&\243<\323I\250\330\323\t\250\330\324Lj&5\0335D\306\243&\243&\232M0\232B\324\322\324\334\322f\214\322\346\224\032\221MH\246\245Z\231jelu5 \334\374t\025:|\243\025(4\360qN\0074\360i\333\251wQ\272\2234\271\2434f\232M4\232i\246\232i\246\236\264\303\326\232i\206\232k\247\245\002\226\212)1IM\"\230E0\212a\246\232h\031j\220\323\032\243cQ\261\246\026\250\313Tl\325\033\032\211\215F\306\242f\250\311\250\311\3150\232c\032a4\322i\244\322\023I\232)sN\006\244PML\213\212\235EJ\242\246LT\240\323\303T\212qO\rN\rN\rJ\032\215\364\273\251sJ\r.h\315%6\233M4\206\232i\246\230i\244S\ru\000R\321E\024Rb\220\212a\024\302)\204S\010\305\"\214sJM0\232\211\215BO4\306j\214\265F\315Q3Tl\325\0335DZ\243-L-L-L-L-L&\220\232Bh\315\002\236\005H\270\025 j\221Z\245V\251U\252Uj\2205H\255\212v\372P\364\340\364\340\324\355\364\241\251\300\323\201\245\006\235\2323HM%%!\246\232CM4\303M4\303]E\024\nv)\010\243\024\224\206\232i\204S\010\250\330Rt\246\223Q\261\250]\252\"j2\325\0235F\315Q3Tl\325\0235F\317Q3\324e\351\205\351\245\351\245\251\273\2517Q\270R\027\002\200\364\242J\220=H\255R\253T\252\325*\275<IR\007\305;\314\247\007\247\207\247\007\247\207\247\006\247\203O\006\234\r8\032vh\315\031\243\024b\222\232E!\024\322)\204S\r4\212\3521F)h\242\212)\246\232i\244S\010\246\021\315Fj65\023\032\205\2175\023\032\205\232\242f\250\231\2526j\211\232\242g\250Y\3526z\214\2750\2754\2754\2757}4\312\0057\315\317J\003\323\203S\203T\201\252Uj\221Z\244\017R\007\247\207\247\211)\301\351\341\351\341\351\341\252@\324\365j\221MH\r<\032p4\340ii@\245\243\024b\232E!\024\302)\244S\010\246\221]=\024R\342\222\212(\246\221L4\302*6\3435\0215\033T.x\250\030\324.j\0265\023\032\211\232\242f\250Y\252\026j\211\232\243f\250\313S\013Te\351\205\351\215%G\270\232z\265(l\323\303S\324\324\201\275\352Ea\232x\223\322\236\257R+\032P\3478\024\375\304S\225\352A%H\032\234\034\346\247V\251\024\324\252j@jAO\024\361N\002\234\005(\024\270\244\"\220\212i\024\322)\204SH\246\021]-\024\242\226\212CA4\224\207\2554\323\010\250$<\324MQ1\250\\\324.j\0075\003\032\205\215D\306\242cP\261\250\035\252&lTL\325\031jaz\215\232\243g\246\023M\3159I\251\001\247\255H\r.\352p$\324\203#\255J\204S\214\224\345\222\245\022\003\326\234\007\245<T\252i\370\364\251\025\252u5*\232\225jQR\001O\002\236\0058\n\\R\342\214R\021M\"\232V\232E0\2554\255t4\240R\321E!4\224\231\244\246\223McU\334\345\252&\250X\324\016y\250\234\325w5\003\032\205\215D\306\242f\250]\252\273\265B\306\242cQ\026\246\223Q\226\246\023M\335\353H95*\2169\247\n\220t\251\025sO\333\266\233\346`\361O\014M.\354Q\27352\014\324\350\265a\022\244\333\353J\023\322\237\202)\313\326\247Z\235*u\251\024T\240S\300\247\201N\002\234\005.(\333F\332iZiZiZa\024\322\265\277E\024\334\321Hh&\222\220\232i\2461\252\354y\250\330\324,j\274\206\241sP5B\365\003\324Lj\0275\003\232\201\215D\306\241cQ\023M&\230MFM&iG\006\246S\232pZ\225H\305L\224\366\\\216*\002\2304\340\330\244\335\232\221Fj\304B\256\304\231\251\200\305<.iB\363R\252\344sM)\203R\240\342\246Z\235*e\025*\212\220\nx\024\340)\300S\202\322\355\244\333HV\232V\232V\230V\230V\266\350\240\323h\246\232)3Hi\244\324NqP1\344\324lj&\252\362T-P\265D\302\241aP\270\250\034Uw\025\003T\017Q1\250\311\246\032a\024\302(\301\245\305J\275*E\346\236\006\rJ\247\025(a\212\211\332\241\316M=x\251\343\346\254\307\305\\\215\300\025 l\232\260\203\212SOC\232\221\223\214\322(\305L\242\246AV\024T\252*@\264\360\264\360\264\360\264\340\264\273i\n\322m\246\225\246\225\246\025\246\025\255|RQHz\322\032m!\244\2444\302j\'<T\016pj65\023\032\205\352&\250Z\243aQ0\250\\Uw\025\003\212\256\342\253\270\250\231j\"\264\335\264\205i\002d\323\304x\245\362\351v\323\325qO\333K\332\230X\212o&\234\006)GZ\265\020\251~\225b%&\255\"\342\246\007\002\234\243uXH\352m\234Sv\324\212\265*\n\260\202\246U\251B\323\302\323\302\323\302\323\266\321\266\215\264\233i\245i\245i\205i\205kO\024\204RR\032Jm!\244\246\232a\250^\241z\214\324MQ\265B\325\033TMQ\232\205\205@\342\240qU\335*&J\215\2435\023Di\236Y\357Hc4\2011N\000\367\245\002\224\nz\2558\256ivf\223\312\2441\342\230i\312\271\2531-YT\315Z\215@\251@\251V2j\314Q\342\255\004\000P\027\232\177\223\306E*\307O\013\212\231\026\254*\324\241i\341i\341i\341iv\322\355\243m&\332B\264\322\265\031ZiZ\277M\2444\230\246\221Hi\246\222\230i\215Q=B\342\242j\214\324mQ5F\325\023\n\215\205D\374\032\205\306j\022\264\206,\216j3\020\035\005D\321f\243x\261P\025\305&\332B\224\233iB\322\204\247\205\247\205\245\002\206\342\241v\250\361\223S\306\265:\220*d5e\rX\214d\325\330\323\212\231V\245U\342\227o5<c\326\234W\024\201jdZ\260\253R\205\247\205\247\205\247\205\247m\243m\033i6\322m\246\225\246\025\246\025\253dRSM6\220\322SH\246\323Z\243j\215\273\324M\322\242aQ\021Q\260\250\330TL)\214*&\025\013\324{\t\240\246)\205i\276]4\245E\"qUY2i\2451M\305&\332]\264\241i\301i\333i\002\320W\212\201\322\232\253S\242\324\351\0215f8qRc\025<\0035\241\030\300\251\325sS\250\247\204\315H\251N\307\024\212\2715aW\0252\255J\253O\013O\013O\013N\013F\3326\322m\244\333HV\230V\243e\251\373\320E0\212B)\010\246\221M\"\232E4\212c\n\215\205B\325\021\250\330TdS\010\250\330TMQ0\250\266n5(@\242\241q\223L\331K\266\242q\212\201\305W\"\243aL\331K\266\227m(Z~\332\002\323\212TL1Q\260\315\013\0375f8\352\322\000*a\216\324\205sV-\327\006\264\020T\350*U\025:\255<\n\220G\271iV<S\302\346\246E\251@\247\252\323\302\323\302\323\266\321\266\215\264\233i6\322\025\246\025\250\312\324\204z\322\032i\244\246\342\220\212n)\244SM1\252&\025\013\n\215\205FEFE0\212\215\252&\025\031\024*c\232k\nn\314\320S\024\302*\027\025\003\255B\313Q\225\244\333I\266\234\022\227e<&i\342*y\217\212\201\342\246\010\252E\212\246T\247\2044\365CR\355\251#\030\253h\3252\275Z\213\232\265\032\346\246\020\232\231#\300\240\2474\241qR\001R*\346\245\013R\005\247\005\247b\227m&\3326\322m\246\225\246\025\2462\320E4\212a\024\224Si\244b\220\361Q\232cTl*6\025\023\n\214\212\214\212c\n\210\212\215\205\"\246\346\251\0311\232\204\2574b\230\302\243e\250Yj&Z\211\226\243)I\345\322\354\245\tR\2549\251\026\034S\366`S\010\244\362\363M1\342\224%H\221\022j\312C\221R\255\267\265I\366ojQm\216\324\361\021\251\022#\232\275\024\\\n\273\004\\\325\321\030\305/\227Ha$dTe1NU\251\225jP\264\365ZxZv\337Z]\264\233h\333HV\232V\232V\243+Qb\220\323H\244\246\323i\r4\323M0\212\215\205F\302\242aQ\260\246\021Q\260\250\310\246\025\247F\270\247\025\250\231)\230\2468\250\215F\302\243+\232\215\222\233\266\223m(\214\232zE\315ZH\270\240\250\024\322*\"\274\323\325i\nd\324\211\025N\221b\254\307\035YH\200\251\2260jO \021\322\233\366nzT\211m\203\322\255$X\025*\361S\246MJ\005H\203\"\242t\346\220%J\253R\252\324\201i\341iB\321\266\215\264m\244+M+L+L+U\351\010\246\221M\"\232i\246\232i\206\233F)\214*&\025\031\024\306\025\031Z\215\226\243\"\2435\"/\002\244\333Q\262\324%i\214\265\036\312\215\222\231\262\220\2453\313\245\021T\213\020\251\004x\247\355\2462\324dSv\344\324\251\036jA\016jd\207\0252\303S\244X\251B\032\22649\253h\234S\304U:CNh\261Dp\222j\332\333\340t\250\331pjh\327\212\215\226\220/5\"\255L\253R\005\245\013K\266\227m\033h\333M+M+M+L+T\361F)\244SH\246\021M\"\223\024\326Zn)\010\2460\250\310\2460\250\312\323Yx\315D\302\243e\250\314D\236\265(B)\300R2\361P\025\250\331j<Rl\315!JB\224\337.\234#\245\013\212~))\215Q\021\232UJ\263\032U\224\2175*\305Vc\213\332\246\021S\2045*E\212\235R\245H\352\312G\201N\362\362jx\240\002\244u\300\252\254\274\323\324`R\025\246\355\346\244U\251\225j@\264\241iv\321\266\227m&\332B\264\322\264\302\264\302\265G\024b\220\212i\024\302)\244SqHE0\322c\212a\024\302*2)\245i\2733Q\025\244\t\223La\264\342\244\333\3057o42\361P\225\246\024\315\'\223I\263\024\306Z\214\212P)M%!\246Q\214\322\210\352E\212\254G\035ZH\352\314qf\255$5*\303O\021R\210\361R\254u2&*`\265\"\'5aW\002\230\353\232\204\245.\332M\264l\247\252\324\212\265 Zv\3326\322\355\243m!ZB\264\322\264\302)\205k?\024b\220\323H\246\021M\"\233\212\010\250\310\244\3054\212\214\2557\024\322)\204SJ\323v\323\014yjy\030\030\246\201JW\212\211\226\230\026\203\214TLj6\250\361F1H\0015 N)\254\265\021\030\245Q\232\260\221\346\254$5:\307\212\235#\315[\212:\270\221\324\242:]\224\361\030\247*T\241)\352\2252GRc\024\326Zf\312M\224l\243m8-=V\236\005;\024\273h\305\030\244\333HV\232V\230E0\255f\342\222\220\212i\031\244\"\230E7\024\204S\010\243\024\302)\245j2)1HV\233\266\233\266\225S\223H\313\3157m\005j6Zc-@\347\025\021\250\230\322\016i\304S\220\n\221\230\001P3f\230FjX\243\315\\\216<U\224Z\231S5f8\252\334q\212\262\211S\254|S\035v\322)\251\000\251TT\250\234\325\205N)\n\342\220\255&\332n\332\002\363K\262\227e(Z\220\n]\264\270\243m.\332B\264\322\264\322\264\302)\214\265\225\2121I\212LSH\246\221M\"\223\024\322\264\335\264\204S\010\246\025\244\333I\266\223m7o5 L\niJn\312\nT.\2705\033t\252\3169\250XTei1K\214\323\225M)\214\232A\001\251\004\0252E\212\262\211R\205\305K\020\346\256 \342\254GVR\246\007\002\243\220f\242PA\253(3S\252\324\310\265:\216(\"\231\266\214S@\245T\251\004y\247\210\263M1b\232\026\234\0058-.\3326\322\025\244+L+L\"\243aYX\346\223\024\230\244\"\223\024\322)\245i6\322b\232V\232V\232V\233\266\215\264l\246\224\244\331\315H\023\"\232\311HR\233\266\232\321f\252J\230\252\314*\026Zi\025\0369\251\2213R\252\n\235c\024\024\024\004\024\270\305I\035ZT\310\247*`\325\210\305[\215ju\247\n~\320EF\311\203O\214\342\254\241\251T\324\300\321\232\\f\232\374p)\025x\247\252\340\324\312\265(Z\0313P\262`\322\005\311\245\000\347\024\360\264m\244\333M+L+L+Q\262\326F(\305&)1HE!\024\233i\245i\245i\010\246\225\246\225\243m\033)Lt\335\224\276VjA\036\0055\243\246\224\250\366sJW\002\251N:\3257Z\201\205&\334\322yx4\365\030\251\024f\246Piv\223K\214R\355\315*\2575z\001\220*\307\227OH\361V\024b\236)\342\2363K\264\232@\270\251V\244S\315YS\221F)\340`SB\344\344\324\212\264\355\265*\212\224\n]\264\307L\212\213f)\3120y\247\355\346\215\264\205i\205i\205i\204Tl+\037\024b\223\024\230\366\244\305\033i6\322\025\246\225\246\225\244+I\262\200\224\355\224\273)\273*E\217\212pJC\036j6\212\231\345\342\243\227\201Y\362\214\232\254\353P\224\247*P\311L\3075*T\312E8\221\212i\346\225EJ\211\223W\240\217\025mR\244\013\212x\024\241jEL\324\311\0259\223\024\300\2315\"\305S,5*\304E=c\251V>\016j=\2704\365\024\375\264\345Z\231V\237\267\212M\264\315\234\323\n\342\244\333\362\203AZaZiZaZ\215\205F\302\261\261F)\270\243\024\230\245\305!\024\230\244\"\232V\223m\033iBS\202\322\354\245X\362jA\035;\313\243\313\250\335@\252\322\034UII5U\320\232\201\2434\317.\232W\024\021\221L\331\315<Fi\353\031\247\2244\251\021&\254,<T\311\026\rZE\000T\302\237R*\323\302T\350\2252\256)\031sO\216,\324\342*\262\221\002)\306,Rm\305!<`S6f\234\027\024\340)\300T\250*R\0061M\333J\0274yt\246<-0\212iZaZ\215\205F\302\243aX\300Q\212B)\247\212i4\224s\351Hr(\006\227\031\245\331J\026\234\022\227e8%H\261\324\201)\257\305B\317P;f\240u\315B\321\324m\025B\321\324f:\212H\361L\013\232<\263\232z\256*E\025(\214R\205\000\324\212EJ\274\324\312*E\025*\214\325\204Z\224-J\242\236\0058.jdZ\235W5b4\342\225\226\230\313P\343$\342\236\005.)B\322\343\024\365\342\2369\247\342\225EH\026\224\247\025\013/&\230E1\205F\302\242aQ\262\3268Z1Q\265\'\226Z\244X\t\352)\342\000)Lb\2436\340\3645\004\260\2249\024\211\351S*\346\237\262\224%8%8GS,t\245p*\274\2435]\226\241\220\005\025_\314\346\224\020z\320\312*\026Ni\236^j)b\342\253\252`\324\342,\212C\r\013\021\025(\030\024\335\244\323\225\rX\215\rN\026\244QS\"\325\205Z\225V\245T\251\002S\325*eJ\231R\254\"\361C\n\215\207\025\010\0304\354Q\212p\024\340\264\354S\220sR\020\0059EH\203&\236W\025\023\256~\265\t\025\033\n\211\205F\302\243aX\324c4\242.y\251\200P0\005\033I\351A\030\035j2i\t\305!\001\3075\031\267\364\247F9\301\353S\005\247\205\245\331\212@~lU\205Zd\202\2532\346\242\223\n*\224\274\325r\244\032L\322\026\"\243g4$\274\363S\262\007Z\254a\301\247\205\300\247*\344\324\242.)\246,R\010\375\252d\216\246\010\005;mH\261\346\247H\352\302\307\305=W\025:-H\022\235\214T\211S\245Y@1Lu\246\021\305BW\232\\R\355\245\003\024\360)H\342\235\030\317J~9\245\305H\215\203R1\365\2460\250\030sQ\260\250\330TL*6\025\214\027\361\251Q=i\342<\363I\267\007\245\014\307\030\250Y\252\"rh\353R\'\002\244\002\232@\317J\225E<\n\\S#\\\271\253J\274Tn*\007\030\025J^M@V\242t\250Yi\204TL\264\314\02552\271\3059>c\203S\371y\024\320\2305f5\004S\214Y\240AO\021b\236#\245\021\324\250\230\251\221j\302\247\024\204`\324\321\221R\323s\223S\306\001\024\376\2254oRu\246?J\213\024\001K\212\\P)\325,k\201Hz\323\2513\203S!\336>\224\036\265\033\016j&\025\023\n\215\205B\302\263\025*M\240\n:\212a5\033\232\205\2174\312z\322\223\216i\301\370\342\234\274\324\252\265(ZR\274S!\341\315Y\003\212\216A\200MRv\'5\013.j\"\265\023\255DV\243d\250\212\323\nf\200\270\247\257\006\256Dw\n$\\sI\023`\342\257F\273\205K\260\n<\274\323\204F\234\"5\"\305O\n\027\255J\035@\250$q\232X\344\251\374\314\212n\376j\304r\020)\373\362jd\247\356\244\334qL\357O\024\242\234\005.)@\251\001\3055\251i\010\247+\025\351S*\356\346\221\222\242d\342\240qP\260\250\330U\000\270\240\340Td\322\032\205\315B{\323i\340P\307\212E<U\204\025:\212~8\245\347\024\221\240\337\232\230\374\242\243s\221U\0359\246\024\250\331*&Z\210\255F\313P\262\3236\322\354\243mM\017\006\255\024\334\265\017\225\264\325\230\344\332*_754L;\325\215\353@\221hi@\351P<\271\351M\014\306\234\020\236\265*\307\212\220%<GR\252\201\326\234\000\365\251\024\343\245J\017\024\204\322\nx\247S\200\247b\227\024\264\032@9\305-8\n\236#\306)\314*\'\025Y\305B\302\242aT\rF\306\243cLg\342\241&\232i@\247\342\232\343\"\210\200<U\225\\qR\255<\032ZU\340\346\234[ \324f\230\302\242e\250\231j&Z\210\212c-D\311L+I\217j6\323\227\203\305Z\211\370\301\251\366\006\246\371T\341\035H\024\216\224\034\322\000\324\340\204\323\304t\274\npjQ!\025\"K\353R\356\310\310\245\334M9sS(\342\236)\373sJ\006)@\247\201N\002\234\005;\024b\227\024\322)@\247/\314i\377\000t\361O\316E1\371\025Y\352\026\250\232\263\230\324Lj\026j\214\234\323M&9\247\216\00586i\257\351P\243lz\274\247#5 jpjZp4\200\201\301\247\210\375i\256\230\250H\250\330Tl*&\025\021\024\302)\204Sv\321\266\227\024\345\342\246Y\rI\346R\211i\302Zp\2234\360\364\340\374Q\346\032n\354\321\232PsOZ\221IS\355S\251\317J\225EL\265 \024\340)\330\245\002\234\0058\nx\024\340(\305\024\204Q\212T8jRrh\017\212Fj\201\315@\324\306\254\206j\211\232\242c\232BqM\242\236)qM5\024\243\034\323\240\270\354j\322I\232\2247\255;9\247\nF\007\250\247\307q\3742\017\306\236\314\276\265\001\031\246\260\250\231j&Z\215\226\243+M+L\"\214Q\2121N\024\360)v\323\200\247\201R\001O\035(\002\214S\266\322\205\247\201R\001OPGJ\235:T\353R\250\247\201N\002\234\026\234\026\234\026\234\026\234\005.\3326SJ\321\212n(5\031\342\230Z\230y\250\3150\326\031j\215\2156\220\232J3J\r8\032By\250\2449\025\014C\347\253\253\362\324\352jA\355N\006\235\232f\321\232v\320p{\322\201\305#.FEF\313Q\262\324L\265\031Za\024\302\264\334R\342\214R\201O\002\234\005=P\323\302\323\200\247\250\247\205\030\240\256)@\247\001N\002\244\002\236\005H\243\0252\324\313R-<\nx\024\340\264\340\264\354T\2423\212i\\Q\212n)\n\323J\323H\250\315F\302\243\246\032i\256|\232a4\332BqH[\024\302\364\241\351\333\250\r\232k\232\205N\032\255+\344T\361\265J\017\024\360iA\247u\245\035)\312)\330\3151\226\243e\364\250\231j&Z\214\2550\212n)1K\212P)\300S\224T\352)\373h\333J\005<\n~(\331J\026\236\0058\n\221EH\005H\242\245Z\221jP*@)\340T\201iv\340\212pbh#\024\230\244\"\232E4\212k\n\211\205D\302\243\"\230i\206\271\322i\244\322TO%D_4\205\351\003\342\235\346P$\346\234[5\033t\342\235n\371m\254j\370\030\251\025\251\340\323\3058S\324S\205<R5V\226P\247\024\335\331\355HT\032\215\226\242+M\333M\333K\266\227\024\001N\025\"\234T\201\251\302\234\005H\253O\013N\333K\266\224\nx\025\"\212x\024\345\251\205H\242\246QR(\251Uj@\270\245\003\232VQ\214\212i\311\034\322Rb\223\024\204S\010\250\331j\026Z\211\205FEFk\234\244<T.\335\205Ws\3150\2750\275&\372\013R\253\363\315J\257RpED\312U\262\265b+\223\300z\270\2370\342\245\034S\325\252AO\034\323\305>\232\325\237u\301\247\301\312\214\324\333\007jk!\250\231)\205)6\322m\244\333F\3321N\247\212\221MH*E\251\005-8R\201N\003\322\234*AR(\251\005H\2652\324\350*eZ~\332\220\240\t\232\217\024\302)1F)\r!\024\322)\214*\027\025\013-D\302\242a\\\331\353Lv\300\252\254\325\0136j3M&\233\2327S\263R+T\310sO\333L+\203\232\225.\031:U\313k\201\'\r\326\257\252\251\035\205\007`\007\232\024\203\322\244\006\224\265C$\241G\275R\226O9\300\035\005Y\215p\005J)OJaZaZaZiZM\264m\244\305.)@\247\212x5\"\232\22058\032vi\302\236:S\2058T\253R\255J\242\245Z\235\rN\206\245\003=($\3644\303L4\224Q\214\323\274\2763Q\021Lj\211\205B\302\242aP\260\256d\232\2573UGja4\302i\204\323I\244\31585H\246\246\215\271\253jF)\255\3157fjH\243 \344U\261+\001\201Mb\344pi\251<\221q\326\245K\341\2347\024\367\277\215W\206\346\251\231\344\270l(\300\253pC\264s\326\254\201\212Z\0014\204\2323\352(\3004\322\264\233(\331I\345\321\262\215\224\270\245\002\226\236\016)\352i\342\245QR\001N\013\351O\002\236\0075\"\324\212je5*\232\235MH\255\212R\324\302i\231\346\226\212T\344\325\223\235\234UV\0305\031\025\023\n\205\205D\325\013\n\345\\\340U)\033&\240&\230M4\234\323M4\322P*E\251\220\324\353%.\374\232\225Njej\225\006j\\\niAPI\000jbY\202y\253\321@\2508\025>1K\364\240\nZ1\232]\264\340\202\227`\240\306)\273\005&\3126\322l\3154\255\030\305\030\315.)\352*U\025*\212\225V\244QO\013\232v\302)\3123O\003\024\365\251T\324\252\325 j\\\322SM(4\3602)\007\312r*_;#\232\205\216M0\323\030T\016*\026\025\013\n\343fz\250\315\232\214\232a4\224\206\232i)@\247\003OSR\203J\t\006\247F\251\224\214\325\204z\225H\247\022(\306i\350\2650\030\034R\321\214\322\205\247b\214R\201N\002\234\005.)\010\244\305&\3326\322\024\246\354>\224m=\305.\332r\255H\253R\250\251TT\252\265*\257\2558\014PW\373\264\003\353O\003\322\234)\340\323\324\323\203R\203A\244\006\244CO#5\031\244=i\244S\032\242a\232\205\205D\302\270)\0375\t4\302i\264R\023M4\224\023@4\3655\"\265J\246\244Z\231ML\206\246\006\236\274\324\300qO\035)\324\352v)@\247b\224\np\024\270\247\001K\212\000\245\332)\245Gj\002\203K\264\nLz\322\021\352(\013N\tO\013R*\324\252\265\"\373S\305;u\031\346\203\203\365\241\01685(\347\2458S\300\245\242\235\326\234\200g\2321\203\305;4\322i3Hi\206\243j\211\205B\302\274\355\215FM4\232m\031\244\315!4\334\321@\247\212z\232\231\rL\265(\036\225\"\261\251\326\246Z\220\032x\247\212p\024\361N\024\340)\300S\261@\024\354R\342\212Bh\244\2434\023FE\002\244\002\236\277J\220})\340\372\323\263F\352\\\322\346\224\032p9\353N\\\212\231H>\306\236\005?\024\252\007~izg\024\001A\024\224\206\222\220\232i\246\032\215\207\245D\302\274\321\2150\232i4\231\367\244\315!ji4\231\245\006\234)\342\236\265*\n\235je\251\224T\252*E\251\224T\200S\200\247\201O\002\236\005.)pi\324\240R\321Hi)3F\352JL\322\322\203N\r\212\221\\T\201\251wR\203N\315(4\271\245\315(jz\265J\0105*\277@\177:\224\032x\366\245\002\220\320y\024\322\010\246\322u\244\244\"\230E4\212k.k\313\t\246\223M&\232M!4\231\244\242\234\0058T\200T\212*e\0252\n\231EL\242\246QR\001R(\251\005<\nx\024\360)\340R\212ZZ3Fi3Fi\t\246\223I\2328\307\275\007\2123\212\001\247\003\232^\247\212\220\034S\303S\203S\201\247Q\232\\\321\232p8\247\253\324\253&jU\223\037J\225^\244\017\232RE&\352BsM4\235\250\372\323M\030\246\342\232Ey94\204\323i\264\332\r\024\240S\300\247\201R(\251\024T\310\265:-L\242\245QR\250\251\024T\252*@)\340S\300\247\001O\002\227\024b\203HM4\232L\322\023I\232L\321\272\214\323\272\320\t\034v\241\207qJ\275)\303\245<\021J\r<S\201\247R\216\264\264R\346\200i\301\252E\222\245W\251\003\323\304\236\264\273\2517Q\272\214\363\305\005\251\013Rn\244$SI\025\344\331\244\244&\232M%\024\240S\300\247\001R\001R(\251\225jeZ\225EL\242\245QR(\251TT\212*@*@)\340S\261K\214t\245\006\203M&\232i\244\322f\233\232\t\244\2434\231\247\003N\004\366\247\014\343\236iW\234\346\227\214P)\300\323\305<S\2058R\321E\024S\263N\r\212\221^\236\036\224=.\3727R\357\245\335I\272\232M!jaj\362\274\322\023M&\222\212P)\300S\300\247\201R(\251\024T\352*U\025*\212\225EJ\242\245QR\250\251\000\247\201O\002\236)sE\031\246\2264\026\3154\232L\323sIE!4\206\200sJ\r=i\331\305\024\2434\341N\247\003O\006\236\r8\032Z)GJR(\244\"\200is\212P\364\355\364\273\350\337J\036\227u\033\350\335A4\302k\313(\'\024\332)E-<\nx\024\360*E\0252\255L\242\245QR\250\251\024T\312*U\025\"\324\202\236\005>\2274\231\245\315&i3IHE4\212L\322f\215\302\223\"\223\255\030\305/\326\224\023N\025 \245\247R\212u8\032p4\340isN\024\242\224\n\\PE2\227&\214PM&qF\352\003R\357\240\275 zx|\322\023_\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\003,IDATx^\355\335\335r\263 \020\000\320\214\357\377\314\231\357\247^\264:QQ(\260{\316]\2155\215\254\013,\306\276^\324\360\336n\000\000\000\000\242R\006\000\000\200r\306\321P`\331n\210@\026\270+d8\000\000\000\000\300\240\324\343\000\000fb\035\032\000\000\000 \253\307\353zJK\220\316\343\274\001\000\000\000\000@\037\n\274@\t9#\261\326\215\357F\003\000\000\000\"2\337\375\311\371\000\230C\353z0\000\320\231\311\331=\261\007I\261?\035\000\000\000\337\230\002\002\000\000@\036ow\010\000@J\326\002 )\343\377\334\264?\000\000\000\300\324\324\366\001\000\000 0\367u\000\000\000\000\000\000\000\300)\313\353y\371N\00506Y*=!\000\000\211x\252?\000\000\000\307L\034\271\302\322\002\000\000\000\000\000\000\000\000\000\000\000\304\347\373\003\000\000\000\000\000\323Q\332\005\000\200\346\026\343n\000\000\000\000\000\000\000\306gu\033\000\000\000\000\000\000\000\000\350\316\215L\000\000$b\370\233\331{\273\001\000\000\000\000\000\022\361\317|\000V\026\0169\262\215\217\355\317\3718\003\000\000\000\000\000\000\201Y\014\312\300MC\214\357N\224\272\037\016\000\000\000Z0\341\006\000\000\000\202Q\356\000\000\000\000\210F\305\007\000\000(\342\271\032\344 \322\001\000\000B3\355\003\370D\206\004\000\200\021\271\343\233j\004\023\3004\244l\000\000\332\263>\014\000\000\220K\257\325\207\306\357kz\013<\3208C\001\000\300\2573\306\005\000\000\340\216O\363\311\375r\354\317-\333\337\333\357?\276\345\257\355\266i\254\'\374\376\007(j\261\363\2679\337\003\310K\206\000\350\257h\360\307\320\264e \006I1]\276Hk\004@\215cp\317\345\206\006\000\000\000xB\021\002\200\036\364?\311\t\000\340\036\353\327\305$\\\000f\243\273\007\000\010\242h`W\2643\000\014M\257\266\223k\251B\000\000_r\345\276\007\244\315\272v\201\267\333P\231\006\344\221\377\001\324:J\031\334[\004\260\022\n\311\t\000\200tL\'!%\227>\000[\372\006 \275\2654\232\257B\252\007\000\000\000\000\000RQ\024\005HNG\000\337\344[ \377]\303\236\337\257?\314\327I\233\321\325\000\000\00000\005\201\030\224\037\000\370Hg\237\233\366\207C\023_\"\2139\000\'&\016o\036\221\034r8\270\302\017^b<\325\233\253\372\001\211L\2171\213\253\027\266\026\315\355j\234\000\323\223\356os\352\206\246#\003\000\200r\2469\311M\033\000\255g\200N\0140\245i\223\027Uh\177`X\022TX\0361GWM\002\260\3051y\350\270\0279~\025\200n\364\251\000\3008L\035\353i5\312ku\\xI\001\2759\377\364!\362\206\241\217ON\000\000\000P\235\t\037+\217\320\316N\000d\367/\002N\352\016\313\351\036\244 \n !\027>@R:\000\000\200F\024\3449d$\016\000\361U\350\357+\034\002\200N\344p\000\000\000\000\000\000\000\000\000\000\000\006\362\007Wv]\t\272\351Z\n\000\000\000\000IEND\256B`\202"
diff --git a/core/res/res/anim/dock_bottom_enter.xml b/core/res/res/anim/dock_bottom_enter.xml
deleted file mode 100644
index bfb97b6..0000000
--- a/core/res/res/anim/dock_bottom_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the bottom of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/decelerate_cubic">
-    <translate android:fromYDelta="100%" android:toYDelta="0"
-        android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/anim/dock_bottom_exit.xml b/core/res/res/anim/dock_bottom_exit.xml
deleted file mode 100644
index 4e15448..0000000
--- a/core/res/res/anim/dock_bottom_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the bottom of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/accelerate_cubic">
-    <translate android:fromYDelta="0" android:toYDelta="100%"
-        android:startOffset="100" android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/anim/dock_bottom_exit_keyguard.xml b/core/res/res/anim/dock_bottom_exit_keyguard.xml
deleted file mode 100644
index 4de3ce5..0000000
--- a/core/res/res/anim/dock_bottom_exit_keyguard.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
-  ~ Copyright (C) 2016 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<!-- Animation for when a dock window at the bottom of the screen is exiting while on Keyguard -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:interpolator="@android:interpolator/fast_out_linear_in">
-    <translate android:fromYDelta="0" android:toYDelta="100%"
-        android:duration="200"/>
-</set>
\ No newline at end of file
diff --git a/core/res/res/anim/dock_left_enter.xml b/core/res/res/anim/dock_left_enter.xml
deleted file mode 100644
index 7f5dfd5..0000000
--- a/core/res/res/anim/dock_left_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the left of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/decelerate_cubic">
-    <translate android:fromXDelta="-100%" android:toXDelta="0"
-        android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_left_exit.xml b/core/res/res/anim/dock_left_exit.xml
deleted file mode 100644
index 11cbc0b3..0000000
--- a/core/res/res/anim/dock_left_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the right of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/accelerate_cubic">
-    <translate android:fromXDelta="0" android:toXDelta="-100%"
-        android:startOffset="100" android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_right_enter.xml b/core/res/res/anim/dock_right_enter.xml
deleted file mode 100644
index a92c7d2..0000000
--- a/core/res/res/anim/dock_right_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the right of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/decelerate_cubic">
-    <translate android:fromXDelta="100%" android:toXDelta="0"
-        android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_right_exit.xml b/core/res/res/anim/dock_right_exit.xml
deleted file mode 100644
index 80e4dc3..0000000
--- a/core/res/res/anim/dock_right_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the right of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/accelerate_cubic">
-    <translate android:fromXDelta="0" android:toXDelta="100%"
-        android:startOffset="100" android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_top_enter.xml b/core/res/res/anim/dock_top_enter.xml
deleted file mode 100644
index f763fb5..0000000
--- a/core/res/res/anim/dock_top_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the top of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/decelerate_cubic">
-    <translate android:fromYDelta="-100%" android:toYDelta="0"
-        android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/anim/dock_top_exit.xml b/core/res/res/anim/dock_top_exit.xml
deleted file mode 100644
index 995b7d0..0000000
--- a/core/res/res/anim/dock_top_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the top of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/accelerate_cubic">
-    <translate android:fromYDelta="0" android:toYDelta="-100%"
-        android:startOffset="100" android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/layout-watch/alert_dialog_progress_material.xml b/core/res/res/layout-watch/alert_dialog_progress_material.xml
new file mode 100644
index 0000000..686156c
--- /dev/null
+++ b/core/res/res/layout-watch/alert_dialog_progress_material.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:paddingStart="?attr/dialogPreferredPadding"
+    android:paddingTop="@dimen/dialog_padding_top_material"
+    android:paddingEnd="?attr/dialogPreferredPadding"
+    android:paddingBottom="@dimen/dialog_padding_top_material">
+    <TextView
+        android:id="@+id/progress_dialog_message"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        style="@style/ProgressDialogMessage" />
+    <ProgressBar
+        android:id="@+id/progress"
+        style="?attr/progressBarStyleHorizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true" />
+    <TextView
+        android:id="@+id/progress_percent"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="true"
+        android:layout_below="@id/progress" />
+    <TextView
+        android:id="@+id/progress_number"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_below="@id/progress" />
+</RelativeLayout>
diff --git a/core/res/res/layout/log_access_user_consent_dialog_permission.xml b/core/res/res/layout/log_access_user_consent_dialog_permission.xml
deleted file mode 100644
index 3da14c8..0000000
--- a/core/res/res/layout/log_access_user_consent_dialog_permission.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** 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.
-*/
--->
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:tools="http://schemas.android.com/tools"
-        android:layout_width="380dp"
-        android:layout_height="match_parent"
-        android:clipToPadding="false">
-    <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="vertical"
-            android:gravity="center"
-            android:paddingLeft="24dp"
-            android:paddingRight="24dp"
-            android:paddingTop="24dp"
-            android:paddingBottom="24dp">
-
-        <ImageView
-                android:id="@+id/log_access_image_view"
-                android:layout_width="32dp"
-                android:layout_height="32dp"
-                android:layout_marginBottom="16dp"
-                android:src="@drawable/ic_doc_document"
-                tools:layout_editor_absoluteX="148dp"
-                tools:layout_editor_absoluteY="35dp"
-                android:gravity="center" />
-
-        <TextView
-                android:id="@+id/log_access_dialog_title"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:layout_marginBottom="32dp"
-                android:text="@string/log_access_confirmation_title"
-                android:textAppearance="@style/AllowLogAccess"
-                android:textColor="?android:attr/textColorPrimary"
-                android:gravity="center" />
-
-        <TextView
-                android:id="@+id/log_access_dialog_body"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:layout_marginBottom="40dp"
-                android:text="@string/log_access_confirmation_body"
-                android:textAppearance="@style/PrimaryAllowLogAccess"
-                android:textColor="?android:attr/textColorPrimary"
-                android:gravity="center" />
-
-        <Space
-                android:layout_width="match_parent"
-                android:layout_height="0dp"
-                android:layout_weight="1" />
-
-        <LinearLayout
-                android:orientation="vertical"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-            <Button
-                    android:id="@+id/log_access_dialog_allow_button"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:text="@string/log_access_confirmation_allow"
-                    style="@style/PermissionGrantButtonTop"
-                    android:textAppearance="@style/PermissionGrantButtonTextAppearance"
-                    android:layout_marginBottom="5dp"
-                    android:layout_centerHorizontal="true"
-                    android:layout_alignParentTop="true"
-                    android:layout_alignParentBottom="true"
-                    android:clipToOutline="true"
-                    android:gravity="center" />
-
-            <Button
-                    android:id="@+id/log_access_dialog_deny_button"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:text="@string/log_access_confirmation_deny"
-                    style="@style/PermissionGrantButtonBottom"
-                    android:textAppearance="@style/PermissionGrantButtonTextAppearance"
-                    android:layout_centerHorizontal="true"
-                    android:layout_alignParentTop="true"
-                    android:layout_alignParentBottom="true"
-                    android:clipToOutline="true"
-                    android:gravity="center" />
-        </LinearLayout>
-    </LinearLayout>
-</ScrollView>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 0ebce40..f5e3009 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Stemboodskap"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Verbindingsprobleem of ongeldige MMI-kode."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Kenmerk word nie gesteun nie."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Bewerking is beperk tot belbeperking-nommers."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Kan oproep-aanstuurinstellings nie van jou foon af verander tewyl jy swerf nie."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Diens is geaktiveer."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Het data oorgeskakel na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Jy kan dit enige tyd in instellings verander"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Laat die program toe om dele van ditself deurdringend in die geheue te hou. Dit kan geheue wat aan ander programme beskikbaar is, beperk, en die foon stadiger maak."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"laat loop voorgronddiens"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Laat die program toe om van voorgronddienste gebruik te maak."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"gebruik voorgronddienstipe “kamera”"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Laat die app toe om die voorgronddienstipe “kamera” te gebruik"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"gebruik voorgronddienstipe “gekoppelde toestel”"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Laat die app toe om die voorgronddienstipe “gekoppelde toestel” te gebruik"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"gebruik voorgronddienstipe “datasinkronisering”"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Laat die app toe om die voorgronddienstipe “datasinkronisering” te gebruik"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"gebruik voorgronddienstipe “ligging”"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Laat die app toe om die voorgronddienstipe “ligging” te gebruik"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"gebruik voorgronddienstipe “mediaterugspeel”"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Laat die app toe om die voorgronddienstipe “mediaterugspeel” te gebruik"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"gebruik voorgronddienstipe “mediaprojeksie”"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Laat die app toe om die voorgronddienstipe “mediaprojeksie” te gebruik"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"gebruik voorgronddienstipe “mikrofoon”"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Laat die app toe om die voorgronddienstipe “mikrofoon” te gebruik"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"gebruik voorgronddienstipe “foonoproep”"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Laat die app toe om die voorgronddienstipe “foonoproep” te gebruik"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"gebruik voorgronddienstipe “gesondheid”"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Laat die app toe om die voorgronddienstipe “gesondheid” te gebruik"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"gebruik voorgronddienstipe “afstandboodskappe”"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Laat die app toe om die voorgronddienstipe “afstandboodskappe” te gebruik"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"gebruik voorgronddienstipe “stelselvrystelling”"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Laat die app toe om die voorgronddienstipe “stelselvrystelling” te gebruik"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"gebruik voorgronddienstipe “spesiale gebruik”"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Laat die app toe om die voorgronddienstipe “spesiale gebruik” te gebruik"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"meet programberging-ruimte"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Laat die program toe om sy kode, data en kasgroottes op te haal"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"verander stelsel-instellings"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Hierdie die program kan oudio met die mikrofoon opneem terwyl die program gebruik word."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"neem oudio op die agtergrond op"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Hierdie program kan enige tyd oudio met die mikrofoon opneem."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"bespeur skermskote van appvensters"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Hierdie app sal ingelig word as ’n skermskoot geneem word terwyl die app gebruik word."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"stuur bevele na die SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Laat die program toe om bevele na die SIM te stuur. Dit is baie gevaarlik."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"herken fisieke aktiwiteit"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Laat die program toe om videolêers in jou gedeelde berging te lees."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"lees prentlêers in gedeelde berging"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Laat die program toe om prentlêers in jou gedeelde berging te lees."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lees prent- en videolêers wat gebruiker in gedeelde berging kies"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Laat die app toe om prent- en videolêers te lees wat jy in jou gedeelde berging kies."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"verander of vee jou gedeelde berging se inhoud uit"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Laat die program toe om jou gedeelde berging se inhoud te skryf."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"maak en/of ontvang SIP-oproepe"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEΪNSTALLEER"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"MAAK TOG OOP"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Skadelike program is bespeur"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Gee <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> toegang tot alle toestelloglêers?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Gee eenmalige toegang"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Moenie toelaat nie"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Toestelloglêers teken aan wat op jou toestel gebeur. Programme kan hierdie loglêers gebruik om kwessies op te spoor en reg te stel.\n\nSommige loglêers bevat dalk sensitiewe inligting en daarom moet jy toegang tot alle toestelloglêers net gee aan programme wat jy vertrou. \n\nAs jy nie vir hierdie program toegang tot alle toestelloglêers gee nie, het die program steeds toegang tot eie loglêers. Jou toestelvervaardiger het dalk steeds toegang tot sommige loglêers of inligting op jou toestel."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Toestelloglêers teken aan wat op jou toestel gebeur. Programme kan hierdie loglêers gebruik om kwessies op te spoor en reg te stel.\n\nSommige loglêers bevat dalk sensitiewe inligting, en daarom moet jy toegang tot alle toestelloglêers net gee aan programme wat jy vertrou. \n\nHierdie program het steeds toegang tot eie loglêers as jy nie vir hierdie program toegang tot alle toestelloglêers gee nie. Jou toestelvervaardiger het dalk steeds toegang tot sommige loglêers of inligting op jou toestel.\n\nKom meer te wete by g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Moenie weer wys nie"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wil <xliff:g id="APP_2">%2$s</xliff:g>-skyfies wys"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Wysig"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Oproepe en kennisgewings sal vibreer"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Kan nie toegang tot die foon se kamera op jou <xliff:g id="DEVICE">%1$s</xliff:g> kry nie"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Kan nie toegang tot die tablet se kamera op jou <xliff:g id="DEVICE">%1$s</xliff:g> kry nie"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Jy kan nie toegang hiertoe kry terwyl daar gestroom word nie. Probeer eerder op jou foon."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Kan nie prent-in-prent sien terwyl jy stroom nie"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Stelselverstek"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KAART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index a861f3c..305cefa 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -28,6 +28,8 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"የድምፅ መልዕክት"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"የተያያዥ ችግር ወይም  ትክከል ያልሆነየMMI ኮድ ባህሪ።"</string>
+    <!-- no translation found for mmiErrorNotSupported (5001803469335286099) -->
+    <skip />
     <string name="mmiFdnError" msgid="3975490266767565852">"ክዋኔ ለቋሚ መደወያ ቁጥሮች ብቻ ተገድቧል።"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Can not change call forwarding settings from your phone while you are roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"አገልግሎት ነቅቶ ነበር።"</string>
@@ -72,6 +74,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"የደዋይ ID ነባሪዎች ወደአልተከለከለም። ቀጥሎ ጥሪ፡አልተከለከለም"</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>
@@ -393,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"መተግበሪያው የራሱን ክፍሎች በማህደረ ትውስታ ውስጥ በቋሚነት የሚቀጥሉ እንዲያደርግ ይፈቅድለታል። ይህ ለሌላ መተግበሪያዎች ያለውን ማህደረ ትውስታ በመገደብ ስልኩን ያንቀራፍፈዋል።"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"የፊት አገልግሎትን ማሄድ"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"መተግበሪያው ፊት ላይ ያሉ አገልግሎቶችን እንዲጠቀም ያስችለዋል።"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"የፊት አገልግሎትን በ«ካሜራ» ዓይነት ማስሄድ"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"መተግበሪያው የፊት አገልግሎትን በ«ካሜራ» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"የፊት አገልግሎትን በ«connectedDevice» ዓይነት ማስሄድ"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"መተግበሪያው የፊት አገልግሎትን በ«connectedDevice» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"የፊት አገልግሎትን በ«dataSync» ዓይነት ማስሄድ"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"መተግበሪያው የፊት አገልግሎትን በ«dataSync» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"የፊት አገልግሎትን በ«አካባቢ» ዓይነት ማስሄድ"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"መተግበሪያው የፊት አገልግሎትን በ«አካባቢ» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"የፊት አገልግሎትን በ«mediaPlayback» ዓይነት ማስሄድ"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"መተግበሪያው የፊት አገልግሎትን በ«mediaPlayback» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"የፊት አገልግሎትን በ«mediaProjection» ዓይነት ማስሄድ"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"መተግበሪያው የፊት አገልግሎትን በ«mediaProjection» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"የፊት አገልግሎትን በ«ማይክሮፎን» ዓይነት ማስሄድ"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"መተግበሪያው የፊት አገልግሎትን በ«ማይክሮፎን» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"የፊት አገልግሎትን በ«phoneCall» ዓይነት ማስሄድ"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"መተግበሪያው የፊት አገልግሎትን በ«phoneCall» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"የፊት አገልግሎትን በ«ጤና» ዓይነት ማስሄድ"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"መተግበሪያው የፊት አገልግሎትን በ«ጤና» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"የፊት አገልግሎትን በ«remoteMessaging» ዓይነት ማስሄድ"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"መተግበሪያው የፊት አገልግሎትን በ«remoteMessaging» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"የፊት አገልግሎትን በ«systemExempted» ዓይነት ማስሄድ"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"መተግበሪያው የፊት አገልግሎትን በ«systemExempted» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"የፊት አገልግሎትን በ«specialUse» ዓይነት ማስሄድ"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"መተግበሪያው የፊት አገልግሎትን በ«specialUse» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"የመተግበሪያ ማከማቻ ቦታ ለካ"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"የራሱን ኮድ ፣ውሂብ እና መሸጎጫ መጠኖች ሰርስሮ ለማውጣት ለመተግበሪያው ይፈቅዳሉ።"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"የስርዓት ቅንብሮችን አስተካክል"</string>
@@ -445,6 +473,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"ይህ መተግበሪያ መተግበሪያው ስራ ላይ ሳለ ማይክሮፎኑን በመጠቀም ኦዲዮን መቅዳት ይችላል።"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"በበስተጀርባ ኦዲዮን ይቅዱ"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ይህ መተግበሪያ በማናቸውም ጊዜ ማይክራፎኑን በመጠቀም ኦዲዮን መቅዳት ይችላል።"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"የመተግበሪያ መስኮቶች የማያ ገጽ ቀረጻዎችን ማወቅ"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"መተግበሪያው በጥቅም ላይ ሳለ ቅጽበታዊ ገጽ እይታ ሲነሳ ይህ መተግበሪያ ማሳወቂያ ይደርሰዋል።"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"ወደ ሲሙ ትዕዛዞችን መላክ"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"መተግበሪያው ትዕዛዞችን ወደ ሲሙ እንዲልክ ያስችለዋል። ይሄ በጣማ አደገኛ ነው።"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"አካላዊ እንቅስቃሴን ለይቶ ማወቅ"</string>
@@ -696,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"መተግበሪያው ከእርስዎ የተጋራ ማከማቻ የቪዲዮ ፋይሎችን እንዲያነብ ይፈቅድለታል።"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"ከጋራ ማከማቻ የምስል ፋይሎችን አንብብ"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"መተግበሪያው ከእርስዎ የተጋራ ማከማቻ የምስል ፋይሎችን እንዲያነብ ይፈቅድለታል።"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ከተጋራ ማከማቻው ውስጥ በተጠቃሚ የተመረጡ የምስል እና የቪድዮ ፋይሎችን አንብብ"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ከእርስዎ የተጋራ ማከማቻ እርስዎ የሚመርጧቸው የምስል እና የቪድዮ ፋይሎችን መተግበሪያው እንዲያነብ ይፈቅድለታል።"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"የተጋራ ማከማቻዎን ይዘቶች ይቀይሩ ወይም ይሰርዙ"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"መተግበሪያው የእርስዎን የተጋራ ማከማቻ ይዘቶችን እንዲጽፍ ያስችለዋል።"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"የSIP ጥሪዎችን ያድርጉ/ይቀበሉ"</string>
@@ -2046,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"አራግፍ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ለማንኛውም ክፈት"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"ጎጂ መተግበሪያ ተገኝቷል"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርስ ይፈቀድለት?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"የአንድ ጊዜ መዳረሻን ፍቀድ"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"አትፍቀድ"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"የመሣሪያ ምዝግብ ማስታወሻዎች በመሣሪያዎ ላይ ምን እንደሚከሰት ይመዘግባሉ። መተግበሪያዎች ችግሮችን ለማግኘት እና ለማስተካከል እነዚህን ምዝግብ ማስታወሻዎች መጠቀም ይችላሉ።\n\nአንዳንድ ምዝግብ ማስታወሻዎች ሚስጥራዊነት ያለው መረጃ ሊይዙ ይችላሉ፣ ስለዚህ የሚያምኗቸውን መተግበሪያዎች ብቻ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርሱ ይፍቀዱላቸው። \n\nይህ መተግበሪያ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርስ ካልፈቀዱለት አሁንም የራሱን ምዝግብ ማስታወሻዎች መድረስ ይችላል። የእርስዎ መሣሪያ አምራች አሁንም አንዳንድ ምዝግብ ማስታወሻዎችን ወይም መረጃዎችን በመሣሪያዎ ላይ ሊደርስ ይችላል።"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"የመሣሪያ ምዝግብ ማስታወሻዎች በመሣሪያዎ ላይ ምን እንደሚከሰት ይመዘግባሉ። መተግበሪያዎች ችግሮችን ለማግኘት እና ለማስተካከል እነዚህን ምዝግብ ማስታወሻዎች መጠቀም ይችላሉ።\n\nአንዳንድ ምዝግብ ማስታወሻዎች ሚስጥራዊነት ያለው መረጃ ሊይዙ ይችላሉ፣ ስለዚህ የሚያምኗቸው መተግበሪያዎች ብቻ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርሱ ይፍቀዱላቸው። \n\nይህ መተግበሪያ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርስ ካልፈቀዱለት አሁንም የራሱን ምዝግብ ማስታወሻዎች መድረስ ይችላል። የመሣሪያዎ አምራች አሁንም አንዳንድ ምዝግብ ማስታወሻዎችን ወይም መረጃዎችን በመሣሪያዎ ላይ ሊደርስ ይችላል።\n\ng.co/android/devicelogs ላይ የበለጠ ይወቁ።"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ዳግም አታሳይ"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> የ<xliff:g id="APP_2">%2$s</xliff:g> ቁራጮችን ማሳየት ይፈልጋል"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"አርትዕ"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ጥሪዎች እና ማሳወቂያዎች ይነዝራሉ"</string>
@@ -2292,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"የስልኩን ካሜራ ከእርስዎ <xliff:g id="DEVICE">%1$s</xliff:g> መድረስ አይቻልም"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ጡባዊውን ካሜራ ከእርስዎ <xliff:g id="DEVICE">%1$s</xliff:g> መድረስ አይቻልም"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"ዥረት በመልቀቅ ላይ ሳለ ይህ ሊደረስበት አይችልም። በምትኩ በስልክዎ ላይ ይሞክሩ።"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"በዥረት በመልቀቅ ወቅት በስዕል-ላይ-ስዕል ማየት አይችሉም"</string>
     <string name="system_locale_title" msgid="711882686834677268">"የሥርዓት ነባሪ"</string>
     <string name="default_card_name" msgid="9198284935962911468">"ካርድ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 7623fb1..dc8ffda 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"البريد الصوتي"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"‏حدثت مشكلة في الاتصال أو أن رمز MMI غير صحيح."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"الميزة غير متاحة."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"تم تقييد التشغيل لأرقام الاتصال الثابت فقط."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"يتعذر تغيير إعدادات إعادة توجيه المكالمات من هاتفك أثناء التجوال."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"تم تفعيل الخدمة."</string>
@@ -76,6 +77,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>
@@ -397,6 +400,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"للسماح للتطبيق بجعل أجزاء منه ثابتة في الذاكرة. وقد يؤدي هذا إلى تقييد الذاكرة المتاحة للتطبيقات الأخرى مما يؤدي إلى حدوث بطء في الهاتف."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"تشغيل الخدمة في المقدمة"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"للسماح للتطبيق بالاستفادة من الخدمات التي تعمل في المقدمة."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"camera\"."</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"connectedDevice\"."</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"dataSync\"."</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"location\"."</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"mediaPlayback\"."</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"mediaProjection\"."</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"microphone\"."</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"phoneCall\"."</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"health\"."</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"remoteMessaging\"."</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"systemExempted\"."</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"specialUse\"."</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"قياس مساحة تخزين التطبيق"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"للسماح للتطبيق باسترداد شفرته وبياناته وأحجام ذاكرات التخزين المؤقت"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"تعديل إعدادات النظام"</string>
@@ -449,6 +476,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"يمكن لهذا التطبيق تسجيل الصوت باستخدام الميكروفون عندما يكون التطبيق قيد الاستخدام."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"تسجيل الصوت في الخلفية"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"يمكن لهذا التطبيق تسجيل الصوت باستخدام الميكروفون في أي وقت."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"رصد لقطات الشاشة لنوافذ التطبيق"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"سيتم إشعار هذا التطبيق عند التقاط لقطة شاشة أثناء استخدام التطبيق."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"‏إرسال أوامر إلى شريحة SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"‏السماح للتطبيق بإرسال أوامر إلى شريحة SIM. وهذا أمر بالغ الخطورة."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"التعرّف على النشاط البدني"</string>
@@ -700,6 +729,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"يسمح للتطبيق بقراءة ملفات الفيديو من مساحة التخزين المشتركة."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"قراءة ملفات الصور من مساحة التخزين المشتركة"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"يسمح هذا الإذن للتطبيق بقراءة ملفات الصور من مساحة التخزين المشتركة."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"قراءة ملفات الفيديو والصور من مساحة التخزين المشتركة"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"يسمح هذا الإذن للتطبيق بقراءة ملفات الفيديو والصور التي تختارها من مساحة التخزين المشتركة."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"تعديل محتوى مساحة التخزين المشتركة أو حذفه"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"للسماح للتطبيق بالكتابة إلى محتوى مساحة التخزين المشتركة."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"‏إجراء/تلقي مكالمات SIP"</string>
@@ -2050,12 +2081,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"إلغاء التثبيت"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"الفتح على أي حال"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"تم العثور على تطبيق ضار"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"هل تريد السماح لتطبيق <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> بالوصول إلى جميع سجلّات الجهاز؟"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"السماح بالوصول إلى السجلّ لمرة واحدة"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"عدم السماح"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ترصد سجلّات الجهاز ما يحدث على جهازك. يمكن أن تستخدم التطبيقات هذه السجلّات لتحديد المشاكل وحلها.\n\nقد تحتوي بعض السجلّات على معلومات حساسة، ولذلك يجب عدم السماح بالوصول إلى جميع سجلّات الجهاز إلا للتطبيقات التي تثق بها. \n\nإذا لم تسمح بوصول هذا التطبيق إلى جميع سجلّات الجهاز، يظل بإمكان التطبيق الوصول إلى سجلّاته. ويظل بإمكان الشركة المصنِّعة لجهازك الوصول إلى بعض السجلّات أو المعلومات المتوفّرة على جهازك."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"‏ترصد سجلّات الجهاز ما يحدث على جهازك. يمكن أن تستخدم التطبيقات هذه السجلّات لتحديد المشاكل وحلّها.\n\nقد تحتوي بعض السجلّات على معلومات حساسة، ولذلك يجب عدم السماح بالوصول إلى جميع سجلّات الجهاز إلا للتطبيقات التي تثق بها. \n\nإذا لم تسمح بوصول هذا التطبيق إلى جميع سجلّات الجهاز، يظل بإمكان التطبيق الوصول إلى سجلّاته. وقد يظل بإمكان الشركة المصنِّعة لجهازك الوصول إلى بعض السجلّات أو المعلومات المتوفّرة على جهازك.\n\nتعرَّف على مزيد من المعلومات على الرابط g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"عدم الإظهار مرة أخرى"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"يريد تطبيق <xliff:g id="APP_0">%1$s</xliff:g> عرض شرائح تطبيق <xliff:g id="APP_2">%2$s</xliff:g>."</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"تعديل"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"سيهتز الهاتف عند تلقّي المكالمات والإشعارات."</string>
@@ -2296,6 +2321,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"يتعذّر الوصول إلى كاميرا الهاتف من على جهاز <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"يتعذّر الوصول إلى كاميرا الجهاز اللوحي من على جهاز <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"لا يمكن الوصول إلى هذا المحتوى أثناء البث. بدلاً من ذلك، جرِّب استخدام هاتفك."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"لا يمكن عرض نافذة ضمن النافذة أثناء البث."</string>
     <string name="system_locale_title" msgid="711882686834677268">"الإعداد التلقائي للنظام"</string>
     <string name="default_card_name" msgid="9198284935962911468">"‏رقم البطاقة ‎<xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 3a97baf..8f2e3f2 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ভইচমেইল"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"সংযোগৰ সমস্যা বা MMI ক\'ড মান্য নহয়।"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"সুবিধাটো সমৰ্থিত নহয়।"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"কেৱল ফিক্সড ডায়েলিং নম্বৰৰ বাবে কার্য সীমাবদ্ধ কৰা হৈছে।"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"আপুনি ৰ\'মিঙত থকাৰ সময়ত কল ফৰৱাৰ্ডিঙৰ ছেটিং সলনি কৰিব নোৱাৰি।"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"সেৱা সক্ষম কৰা হ’ল।"</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"মেম\'ৰিত নিজৰ বাবে প্ৰয়োজনীয় ঠাই পৃথক কৰিবলৈ এপক অনুমতি দিয়ে। এই কার্যই ফ\'নৰ কার্যক লেহেমীয়া কৰি অন্য এপবোৰৰ বাবে উপলব্ধ মেম\'ৰিক সীমাবদ্ধ কৰে।"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"অগ্ৰভূমিৰ সেৱা চলাব পাৰে"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"এপ্‌টোক অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে।"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"কেমেৰা\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"এপ্‌টোক \"কেমেৰা\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"এপ্‌টোক \"connectedDevice\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"এপ্‌টোক \"dataSync\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"অৱস্থান\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"এপ্‌টোক \"অৱস্থান\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"এপ্‌টোক \"mediaPlayback\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"এপ্‌টোক \"mediaProjection\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"মাইক্ৰ’ফ’ন\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"এপ্‌টোক \"মাইক্ৰ’ফ’ন\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"এপ্‌টোক \"phoneCall\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"স্বাস্থ্য\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"এপ্‌টোক \"স্বাস্থ্য\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"এপ্‌টোক \"remoteMessaging\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"কেমেৰা\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"এপ্‌টোক \"systemExempted\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"এপ্‌টোক \"specialUse\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"এপৰ ষ্ট’ৰেজৰ খালী ঠাই হিচাপ কৰক"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"এপ্‌টোক ইয়াৰ ক\'ড, ডেটা আৰু কেশ্বৰ আকাৰ বিচাৰি উলিয়াবলৈ অনুমতি দিয়ে"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"ছিষ্টেম ছেটিংহ সংশোধন কৰক"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"এই এপ্‌টোৱে ইয়াক ব্যৱহাৰ কৰি থাকোঁতে মাইক্ৰ’ফ’ন ব্যৱহাৰ কৰি অডিঅ’ ৰেকর্ড কৰিব পাৰে।"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"নেপথ্যত অডিঅ’ ৰেকৰ্ড কৰক"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"এই এপ্‌টোৱে যিকোনো সময়তে মাইক্ৰ’ফ’ন ব্যৱহাৰ কৰি অডিঅ’ ৰেকৰ্ড কৰিব পাৰে।"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"এপৰ ৱিণ্ড’সমূহৰ স্ক্ৰীনৰ ফট’ তোলাটো চিনাক্ত কৰক"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"এই এপ্‌টো ব্যৱহাৰ হৈ থকাৰ সময়ত কোনো স্ক্ৰীনশ্বট ল’লে, ইয়াক জনোৱা হয়।"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"ছিমলৈ নিৰ্দেশ পঠিয়াব পাৰে"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"ছিমলৈ নিৰ্দেশসমূহ প্ৰেৰণ কৰিবলৈ এপক অনুমতি দিয়ে। ই অতি ক্ষতিকাৰক।"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"শাৰীৰিক কাৰ্যকলাপ চিনাক্ত কৰক"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ পৰা ভিডিঅ’ ফাইল পঢ়িবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ পৰা প্ৰতিচ্ছবিৰ ফাইল পঢ়ক"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ পৰা প্ৰতিচ্ছবিৰ ফাইল পঢ়িবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ব্যৱহাৰকাৰীয়ে শ্বেয়াৰ কৰা ষ্ট’ৰেজৰ পৰা বাছনি কৰা প্ৰতিচ্ছবি আৰু ভিডিঅ’ ফাইলসমূহ পঢ়া"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"আপুনি নিজৰ শ্বেয়াৰ কৰা ষ্ট’ৰেজৰ পৰা বাছনি কৰা প্ৰতিচ্ছবি আৰু ভিডিঅ’ ফাইলসমূহ পঢ়িবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ সমল সংশোধন কৰিব বা মচিব পাৰে"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ সমল লিখিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP কল কৰা/পোৱা"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"আনইনষ্টল কৰক"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"যিহ\'লেও খোলক"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"ক্ষতিকাৰক এপ্‌ চিনাক্ত কৰা হৈছে"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>ক আটাইবোৰ ডিভাইচৰ লগ এক্সেছ কৰাৰ অনুমতি প্ৰদান কৰিবনে?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"কেৱল এবাৰ এক্সেছ কৰাৰ অনুমতি দিয়ক"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"অনুমতি নিদিব"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"আপোনাৰ ডিভাইচত কি কি ঘটে সেয়া ডিভাইচ লগে ৰেকৰ্ড কৰে। এপ্‌সমূহে সমস্যা বিচাৰিবলৈ আৰু সমাধান কৰিবলৈ এই লগসমূহ ব্যৱহাৰ কৰিব পাৰে।\n\nকিছুমান লগত সংবেদনশীল তথ্য থাকিব পাৰে, গতিকে কেৱল আপুনি বিশ্বাস কৰা এপকহে আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি দিয়ক। \n\nআপুনি যদি এই এপ্‌টোক আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি নিদিয়ে, তথাপিও ই নিজৰ লগসমূহ এক্সেছ কৰিব পাৰিব। আপোনাৰ ডিভাইচৰ নিৰ্মাতাই তথাপিও হয়তো আপোনাৰ ডিভাইচটোত থকা কিছু লগ অথবা তথ্য এক্সেছ কৰিব পাৰিব।"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"আপোনাৰ ডিভাইচত কি কি ঘটে সেয়া ডিভাইচ লগে ৰেকৰ্ড কৰে। এপ্‌সমূহে সমস্যা বিচাৰিবলৈ আৰু সমাধান কৰিবলৈ এই লগসমূহ ব্যৱহাৰ কৰিব পাৰে।\n\nকিছুমান লগত সংবেদনশীল তথ্য থাকিব পাৰে, গতিকে কেৱল আপুনি বিশ্বাস কৰা এপকহে আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি দিয়ক। \n\nআপুনি যদি এই এপ্‌টোক আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি নিদিয়ে, তথাপিও ই নিজৰ লগসমূহ এক্সেছ কৰিব পাৰিব। আপোনাৰ ডিভাইচৰ নিৰ্মাতাই তথাপিও হয়তো আপোনাৰ ডিভাইচটোত থকা কিছু লগ অথবা তথ্য এক্সেছ কৰিব পাৰিব।\n\ng.co/android/devicelogsত অধিক জানক।"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"পুনৰ নেদেখুৱাব"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>এ <xliff:g id="APP_2">%2$s</xliff:g>ৰ অংশ দেখুওৱাব খুজিছে"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"সম্পাদনা কৰক"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"কল আৰু জাননীসমূহে কম্পন কৰিব"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ৰ পৰা ফ’নটোৰ কেমেৰা এক্সেছ কৰিব নোৱাৰি"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ৰ পৰা টেবলেটটোৰ কেমেৰা এক্সেছ কৰিব নোৱাৰি"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"ষ্ট্ৰীম কৰি থকাৰ সময়ত এইটো এক্সেছ কৰিব নোৱাৰি। তাৰ পৰিৱৰ্তে আপোনাৰ ফ’নত চেষ্টা কৰি চাওক।"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"ষ্ট্ৰীম কৰি থকাৰ সময়ত picture-in-picture চাব নোৱাৰি"</string>
     <string name="system_locale_title" msgid="711882686834677268">"ছিষ্টেম ডিফ’ল্ট"</string>
     <string name="default_card_name" msgid="9198284935962911468">"কাৰ্ড <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 0b361ac..292f51b3 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Səsli poçt"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Bağlantı problemi və ya yalnış MM kodu."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funksiya dəstəklənmir."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Əməliyyat yalnız sabit nömrələrə yığımla məhdudlaşıb."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Roaminqdə olarkən zəng yönləndirmə ayarlarını telefonunuzdan dəyişə bilməz."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Servis işə salındı."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Data <xliff:g id="CARRIERDISPLAY">%s</xliff:g> operatoruna keçirilib"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Bunu istənilən zaman Ayarlarda dəyişə bilərsiniz"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Tətbiqə öz komponentlərini yaddaşda saxlama icazəsi verir. Bu digər tətbiqlər üçün mövcud olan yaddaşı limitləyə bilər."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"ön fon xidmətindən istifadə edin"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Tətbiqə ön fon xidmətlərini işlətmək icazəsi verin."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"kamera\" növü olan ön fon xidmətləri işlətmək"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Tətbiqə \"kamera\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" növü olan ön fon xidmətləri işlətmək"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Tətbiqə \"connectedDevice\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" növü olan ön fon xidmətləri işlətmək"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Tətbiqə \"dataSync\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"məkan\" növü olan ön fon xidmətləri işlətmək"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Tətbiqə \"məkan\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" növü olan ön fon xidmətləri işlətmək"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Tətbiqə \"mediaPlayback\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" növü olan ön fon xidmətləri işlətmək"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Tətbiqə \"mediaProjection\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"mikrofon\" növü olan ön fon xidmətləri işlətmək"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Tətbiqə \"mikrofon\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" növü olan ön fon xidmətləri işlətmək"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Tətbiqə \"phoneCall\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"sağlamlıq\" növü olan ön fon xidmətləri işlətmək"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Tətbiqə \"sağlamlıq\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" növü olan ön fon xidmətləri işlətmək"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Tətbiqə \"remoteMessaging\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" növü olan ön fon xidmətləri işlətmək"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Tətbiqə \"systemExempted\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" növü olan ön fon xidmətləri işlətmək"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Tətbiqə \"specialUse\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"tətbiq saxlama yaddaşını ölçmək"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Tətbiqə özünün kodunu, məlumatını və keş ölçüsünü alma icazəsi verir."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"Sistem ayarlarının dəyişdirilməsi"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Bu tətbiq istifadə edilən zaman mikrofondan istifadə edərək audio yaza bilər."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"arxa fonda audio yazmaq"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Bu tətbiq istənilən zaman mikrofondan istifadə edərək audio yaza bilər."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"tətbiq pəncərələrinin ekran şəkillərini aşkar edin"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Tətbiq istifadə edilərkən skrinşot çəkildikdə bu tətbiqə bildiriş göndəriləcək."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"əmrləri SIM\'ə göndərin"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Tətbiqə SIM-ə əmrlər göndərməyə imkan verir. Bu, çox təhlükəlidir."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"fiziki fəaliyyəti tanıyın"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Tətbiqə paylaşılan yaddaşdakı video fayllarını oxumaq imkanı verir."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"paylaşılan yaddaşdakı şəkil fayllarını oxumaq"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Tətbiqə paylaşılan yaddaşdakı şəkil fayllarını oxumaq imkanı verir."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"paylaşılan yaddaşdan istifadəçinin seçdiyi şəkil və video fayllarını oxumaq"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Tətbiqə paylaşılan yaddaşdan seşdiyiniz şəkil və video fayllarını oxumaq imkanı verir."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"paylaşılan yaddaşdakı kontenti dəyişmək və ya silmək"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Tətbiqə paylaşılan yaddaşdakı kontenti yazmaq imkanı verir."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP çağrıları göndərin/qəbul edin"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"SİSTEMDƏN SİLİN"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"İSTƏNİLƏN HALDA AÇIN"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Zərərli tətbiq aşkarlandı"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> tətbiqinin bütün cihaz qeydlərinə girişinə icazə verilsin?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Birdəfəlik girişə icazə verin"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"İcazə verməyin"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Cihaz qeydləri cihazınızda baş verənləri qeyd edir. Tətbiqlər problemləri tapmaq və həll etmək üçün bu qeydlərdən istifadə edə bilər.\n\nBəzi qeydlərdə həssas məlumatlar ola bilər, ona görə də yalnız etibar etdiyiniz tətbiqlərin bütün cihaz qeydlərinə giriş etməsinə icazə verin. \n\nBu tətbiqin bütün cihaz qeydlərinə girişinə icazə verməsəniz, o, hələ də öz qeydlərinə giriş edə bilər. Cihaz istehsalçınız hələ də cihazınızda bəzi qeydlərə və ya məlumatlara giriş edə bilər."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Cihaz qeydləri cihazınızda baş verənləri qeyd edir. Tətbiqlər problemləri tapmaq və həll etmək üçün bu qeydlərdən istifadə edə bilər.\n\nBəzi qeydlərdə həssas məlumatlar ola bilər, ona görə də yalnız etibar etdiyiniz tətbiqlərin bütün cihaz qeydlərinə giriş etməsinə icazə verin. \n\nBu tətbiqin bütün cihaz qeydlərinə girişinə icazə verməsəniz, o, hələ də öz qeydlərinə giriş edə bilər. Cihaz istehsalçınız hələ də cihazınızda bəzi qeydlərə və ya məlumatlara giriş edə bilər.\n\nƏtraflı məlumat: g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Daha göstərməyin"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> <xliff:g id="APP_2">%2$s</xliff:g> tətbiqindən bölmələr göstərmək istəyir"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Redaktə edin"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Zəng və bildirişlər vibrasiya verəcək"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan telefonun kamerasına giriş etmək olmur"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan planşetin kamerasına giriş etmək olmur"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Yayım zamanı buna giriş mümkün deyil. Telefonunuzda sınayın."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Yayım zamanı şəkildə şəkilə baxmaq mümkün deyil"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Sistem defoltu"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 078c098..b1ccdf9 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Glasovna pošta"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problemi sa vezom ili nevažeći MMI kôd."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcija nije podržana."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Rad je ograničen samo na brojeve fiksnog biranja."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Ne možete da promenite podešavanja preusmeravanja poziva sa telefona dok ste u romingu."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Usluga je omogućena."</string>
@@ -73,6 +74,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Mobilni podaci su prebačeni na operatera <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Ovo možete u svakom trenutku da promenite u Podešavanjima"</string>
     <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>
@@ -394,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Dozvoljava aplikaciji da učini sopstvene komponente trajnim u memoriji. Ovo može da ograniči memoriju dostupnu drugim aplikacijama i uspori telefon."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"pokreni uslugu u prvom planu"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Dozvoljava aplikaciji da koristi usluge u prvom planu."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"pokretanje usluge u prvom planu koja pripada tipu „camera“"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „camera“"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"pokretanje usluge u prvom planu koja pripada tipu „connectedDevice“"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „connectedDevice“"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"pokretanje usluge u prvom planu koja pripada tipu „dataSync“"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „dataSync“"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"pokretanje usluge u prvom planu koja pripada tipu „location“"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „location“"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"pokretanje usluge u prvom planu koja pripada tipu „mediaPlayback“"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „mediaPlayback“"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"pokretanje usluge u prvom planu koja pripada tipu „mediaProjection“"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „mediaProjection“"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"pokretanje usluge u prvom planu koja pripada tipu „microphone“"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „microphone“"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"pokretanje usluge u prvom planu koja pripada tipu „phoneCall“"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „phoneCall“"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"pokretanje usluge u prvom planu koja pripada tipu „health“"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „health“"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"pokretanje usluge u prvom planu koja pripada tipu „remoteMessaging“"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „remoteMessaging“"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"pokretanje usluge u prvom planu koja pripada tipu „systemExempted“"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „systemExempted“"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"pokretanje usluge u prvom planu koja pripada tipu „specialUse“"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „specialUse“"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"merenje memorijskog prostora u aplikaciji"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Dozvoljava aplikaciji da preuzme veličine kôda, podataka i keša."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"izmena podešavanja sistema"</string>
@@ -446,6 +473,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ova aplikacija može da snima zvuk pomoću mikrofona dok se aplikacija koristi."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"da snima zvuk u pozadini"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ova aplikacija može da snima zvuk pomoću mikrofona u bilo kom trenutku."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"otkrivanje snimanja ekrana u prozorima aplikacija"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ako se tokom korišćenja ove aplikacije napravi snimak ekrana, aplikacija će dobiti obaveštenje."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"slanje komandi na SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Omogućava aplikaciji da šalje komande SIM kartici. To je veoma opasno."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznavanje fizičkih aktivnosti"</string>
@@ -697,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Omogućava aplikaciji da čita video fajlove iz deljenog memorijskog prostora."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"čitanje fajlova slika iz deljenog memorijskog prostora"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Omogućava aplikaciji da čita fajlove slika iz deljenog memorijskog prostora."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"čitanje fajlova slika i video snimaka koje korisnik bira iz deljenog memorijskog prostora"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Omogućava aplikaciji da čita fajlove slika i video snimaka koje izaberete iz deljenog memorijskog prostora."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"menjanje ili brisanje sadržaja deljenog memorijskog prostora"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Dozvoljava aplikaciji da upisuje sadržaj deljenog memorijskog prostora."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"upućivanje/prijem SIP poziva"</string>
@@ -2047,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEINSTALIRAJ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"IPAK OTVORI"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Otkrivena je štetna aplikacija"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Želite da dozvolite aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> da pristupa svim evidencijama uređaja?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Dozvoli jednokratan pristup"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ne dozvoli"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Evidencije uređaja registruju šta se dešava na uređaju. Aplikacije mogu da koriste te evidencije da bi pronašle i rešile probleme.\n\nNeke evidencije mogu da sadrže osetljive informacije, pa pristup svim evidencijama uređaja treba da dozvoljavate samo aplikacijama u koje imate poverenja. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim evidencijama uređaja, ona i dalje može da pristupa sopstvenim evidencijama. Proizvođač uređaja će možda i dalje moći da pristupa nekim evidencijama ili informacijama na uređaju."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Evidencije uređaja registruju šta se dešava na uređaju. Aplikacije mogu da koriste te evidencije da bi pronašle i rešile probleme.\n\nNeke evidencije mogu da sadrže osetljive informacije, pa pristup svim evidencijama uređaja treba da dozvoljavate samo aplikacijama u koje imate poverenja. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim evidencijama uređaja, ona i dalje može da pristupa sopstvenim evidencijama. Proizvođač uređaja će možda i dalje moći da pristupa nekim evidencijama ili informacijama na uređaju.\n\nSaznajte više na g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne prikazuj ponovo"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Aplikacija <xliff:g id="APP_0">%1$s</xliff:g> želi da prikazuje isečke iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Izmeni"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Vibracija za pozive i obaveštenja je uključena"</string>
@@ -2293,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ne može da se pristupi kameri telefona sa <xliff:g id="DEVICE">%1$s</xliff:g> uređaja"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ne može da se pristupi kameri tableta sa <xliff:g id="DEVICE">%1$s</xliff:g> uređaja"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Ovom ne možete da pristupate tokom strimovanja. Probajte na telefonu."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Ne možete da gledate sliku u slici pri strimovanju"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Podrazumevani sistemski"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KARTICA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 023e82c..4cd558d 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Галасавая пошта"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Праблема падлучэння ці няправільны код MMI."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функцыя не падтрымліваецца."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Выкарыстанне абмежаванае толькі дазволенымі нумарамі."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Немагчыма змяніць налады пераадрасацыі выклікаў з тэлефона, пакуль вы знаходзіцеся ў роўмінгу."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Служба была ўключана."</string>
@@ -74,6 +75,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Налады ідэнтыфікатару АВН па змаўчанні: не абмяжавана. Наступны выклік: не абмежавана"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Служба не прадастаўляецца."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Вы не можаце змяніць налады ідэнтыфікатара абанента, якi тэлефануе."</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>
@@ -395,6 +398,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Дазваляе прыкладанню захоўваць некаторыя пастаянныя часткi ў памяцi. Гэта можа абмежаваць аб\'ём памяці, даступнай для іншых прыкладанняў, i запаволiць працу тэлефона."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"запусціць актыўныя сэрвісы"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Дазваляе праграме выкарыстоўваць асноўныя сэрвісы."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"запуск актыўнага сэрвісу тыпу \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"запуск актыўнага сэрвісу тыпу \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"запуск актыўнага сэрвісу тыпу \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"запуск актыўнага сэрвісу тыпу \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"запуск актыўнага сэрвісу тыпу \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"запуск актыўнага сэрвісу тыпу \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"запуск актыўнага сэрвісу тыпу \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"запуск актыўнага сэрвісу тыпу \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"запуск актыўнага сэрвісу тыпу \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"запуск актыўнага сэрвісу тыпу \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"запуск актыўнага сэрвісу тыпу \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"запуск актыўнага сэрвісу тыпу \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"вымерыць прастору для захоўвання прыкладання"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Дазваляе прыкладанням атрымліваць яго код, дадзеныя і аб\'ём кэш-памяці"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"змена сістэмных налад"</string>
@@ -447,6 +474,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Гэта праграма падчас яе выкарыстання можа запісваць аўдыя з дапамогай мікрафона."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"запісваць аўдыя ў фонавым рэжыме"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Гэта праграма можа ў любы час запісваць аўдыя з дапамогай мікрафона."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"выяўляць здыманне экрана з вокнамі праграмы"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Калі ў час выкарыстання гэтай праграмы будзе зроблены здымак экрана, паявіцца апавяшчэнне."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"адпраўляць каманды на SIM-карту"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Дазваляе праграме адпраўляць каманды SIM-карце. Гэта вельмі небяспечна."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"распазнаваць фізічную актыўнасць"</string>
@@ -698,6 +727,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Праграма зможа счытваць відэафайлы з абагуленага сховішча."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"счытваць файлы відарысаў з абагуленага сховішча"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Праграма зможа счытваць файлы відарысаў з абагуленага сховішча."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"счытваць з абагуленага сховішча выбраныя карыстальнікам файлы відарысаў і відэа"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Праграма зможа счытваць з абагуленага сховішча выбраныя вамі файлы відарысаў і відэа."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"змяненне або выдаленне змесціва абагуленага сховішча"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Дазваляе праграме запісваць змесціва абагуленага сховішча."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"ажыццяўленне/прыманне выклікаў SIP"</string>
@@ -2048,12 +2079,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ВЫДАЛІЦЬ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"УСЁ РОЎНА АДКРЫЦЬ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Выяўлена шкодная праграма"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Дазволіць праграме \"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>\" мець доступ да ўсіх журналаў прылады?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Дазволіць аднаразовы доступ"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Не дазваляць"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Журналы прылад запісваюць усё, што адбываецца на вашай прыладзе. Праграмы выкарыстоўваюць гэтыя журналы для пошуку і выпраўлення памылак.\n\nУ некаторых журналах можа ўтрымлівацца канфідэнцыяльная інфармацыя, таму давайце доступ да ўсіх журналаў прылады толькі тым праграмам, якім вы давяраеце. \n\nКалі вы не дасце гэтай праграме доступу да ўсіх журналаў прылад, у яе ўсё роўна застанецца доступ да ўласных журналаў. Для вытворцы вашай прылады будуць даступнымі некаторыя журналы і інфармацыя на вашай прыладзе."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Журналы прылад запісваюць усё, што адбываецца на вашай прыладзе. Праграмы выкарыстоўваюць гэтыя журналы для пошуку і выпраўлення памылак.\n\nУ некаторых журналах можа ўтрымлівацца канфідэнцыяльная інфармацыя, таму давайце доступ да ўсіх журналаў прылады толькі тым праграмам, якім вы давяраеце. \n\nКалі вы не дасце гэтай праграме доступу да ўсіх журналаў прылад, у яе ўсё роўна застанецца доступ да ўласных журналаў. Для вытворцы вашай прылады будуць даступнымі некаторыя журналы і інфармацыя на вашай прыладзе.\n\nДаведайцеся больш на старонцы g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Больш не паказваць"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Праграма <xliff:g id="APP_0">%1$s</xliff:g> запытвае дазвол на паказ зрэзаў праграмы <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Рэдагаваць"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Для выклікаў і апавяшчэнняў уключаны вібрасігнал"</string>
@@ -2294,6 +2319,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не ўдалося атрымаць доступ да камеры тэлефона з прылады \"<xliff:g id="DEVICE">%1$s</xliff:g>\""</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Не ўдалося атрымаць доступ да камеры планшэта з прылады \"<xliff:g id="DEVICE">%1$s</xliff:g>\""</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Не ўдаецца атрымаць доступ у час перадачы плынню. Паспрабуйце скарыстаць тэлефон."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Падчас перадачы плынню прагляд у рэжыме \"Відарыс у відарысе\" немагчымы"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Стандартная сістэмная налада"</string>
     <string name="default_card_name" msgid="9198284935962911468">"КАРТА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index aaa080a..d39425b 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Гласова поща"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Има проблем с връзката или MMI кодът е невалиден."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функцията не се поддържа."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Операцията е ограничена само до фиксираните номера за набиране."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Докато сте в режим на роуминг, настройките за пренасочване на обажданията не могат да се променят от телефона ви."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Услугата бе активирана."</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Разрешава на приложението да прави части от себе си постоянни в паметта. Това може да ограничи наличната за другите приложения, забавяйки телефона."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"изпълнение на услуги на преден план"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Разрешава на приложението да се възползва от услуги на преден план."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"изпълнение на услуги на преден план от тип camera"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Разрешава на приложението да се възползва от услуги на преден план от тип camera"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"изпълнение на услуги на преден план от тип connectedDevice"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Разрешава на приложението да се възползва от услуги на преден план от тип connectedDevice"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"изпълнение на услуги на преден план от тип dataSync"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Разрешава на приложението да се възползва от услуги на преден план от тип dataSync"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"изпълнение на услуги на преден план от тип location"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Разрешава на приложението да се възползва от услуги на преден план от тип location"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"изпълнение на услуги на преден план от тип mediaPlayback"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Разрешава на приложението да се възползва от услуги на преден план от тип mediaPlayback"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"изпълнение на услуги на преден план от тип mediaProjection"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Разрешава на приложението да се възползва от услуги на преден план от тип mediaProjection"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"изпълнение на услуги на преден план от тип microphone"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Разрешава на приложението да се възползва от услуги на преден план от тип microphone"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"изпълнение на услуги на преден план от тип phoneCall"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Разрешава на приложението да се възползва от услуги на преден план от тип phoneCall"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"изпълнение на услуги на преден план от тип health"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Разрешава на приложението да се възползва от услуги на преден план от тип health"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"изпълнение на услуги на преден план от тип remoteMessaging"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Разрешава на приложението да се възползва от услуги на преден план от тип remoteMessaging"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"изпълнение на услуги на преден план от тип systemExempted"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Разрешава на приложението да се възползва от услуги на преден план от тип systemExempted"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"изпълнение на услуги на преден план от тип specialUse"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Разрешава на приложението да се възползва от услуги на преден план от тип specialUse"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"измерване на ползваното от приложението място в хранилището"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Разрешава на приложението да извлича размера на своя код, данни и кеш"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"промяна на системните настройки"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Когато се използва, това приложение може да записва аудио посредством микрофона."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"записва аудио на заден план"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Това приложение може по всяко време да записва аудио посредством микрофона."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"установяване на заснемания на екрана на прозорците на приложението"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Това приложение ще получава известия, когато по време на използването му бъде направена екранна снимка."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"изпращане на команди до SIM картата"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Разрешава на приложението да изпраща команди до SIM картата. Това е много опасно."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"разпознаване на физическата активност"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Разрешава на приложението да чете видеофайлове от споделеното ви хранилище."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"да чете графични файлове от споделеното хранилище"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Разрешава на приложението да чете графични файлове от споделеното ви хранилище."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"да чете избраните от потребителя графични и видеофайлове от споделеното хранилище"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Разрешава на приложението да чете графичните и видеофайловете, които сте избрали от споделеното си хранилище."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"промяна или изтрив. на съдърж. от сподел. ви хранил."</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Разрешава на прил. да записва съдърж. от сподел. ви хранил."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"извършване/получаване на обаждания чрез SIP"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ДЕИНСТАЛИРАНЕ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ОТВАРЯНЕ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Открито е опасно приложение"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Да се разреши ли на <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> достъп до всички регистрационни файлове за устройството?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Разрешаване на еднократен достъп"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Забраняване"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"В регистрационните файлове за устройството се записва какво се извършва на него. Приложенията могат да използват тези регистрационни файлове, за да откриват и отстраняват проблеми.\n\nНякои регистрационни файлове за устройството може да съдържат поверителна информация, затова разрешавайте достъп до всички тях само на приложения, на които имате доверие. \n\nАко не разрешите на това приложение достъп до всички регистрационни файлове за устройството, то пак може да осъществява достъп до собствените си регистрационни файлове. Производителят на устройството пак може да има достъп до някои регистрационни файлове или информация на устройството ви."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"В регистрационните файлове за устройството се записва какво се извършва на него. Приложенията могат да използват тези регистрационни файлове, за да откриват и отстраняват проблеми.\n\nНякои регистрационни файлове за устройството може да съдържат поверителна информация, затова разрешавайте достъп до всички тях само на приложения, на които имате доверие. \n\nАко не разрешите на това приложение достъп до всички регистрационни файлове за устройството, то пак може да осъществява достъп до собствените си регистрационни файлове. Производителят на устройството може да има достъп до някои регистрационни файлове или информация на устройството ви.\n\nНаучете повече на адрес g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Да не се показва пак"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> иска да показва части от <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Редактиране"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"При обаждания и известия устройството ще вибрира"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Няма достъп до камерата на телефона от вашия <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Няма достъп до камерата на таблета от вашия <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"До това съдържание не може да се осъществи достъп при поточно предаване. Вместо това опитайте от телефона си."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Функцията „Картина в картината“ не е налице при поточно предаване"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Стандартно за системата"</string>
     <string name="default_card_name" msgid="9198284935962911468">"КАРТА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index ee1db8e..2836580 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ভয়েসমেল"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"সংযোগ সমস্যা বা অবৈধ MMI কোড৷"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ফিচার কাজ করে না।"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"নির্দিষ্ট নম্বরে ডায়ালযোগ্য হিসেবে প্রক্রিয়াটি সীমিত করা হয়েছে৷"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"আপনি রোমিংয়ে থাকাকালীন আপনার ফোন থেকে \'কল ফরওয়ার্ড করার সেটিংস\' পরিবর্তন করা যাবে না৷"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"পরিষেবা সক্ষম করা ছিল৷"</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"মেমরিতে নিজের জন্য প্রয়োজনীয় জায়গা আলাদা করে রাখতে অ্যাপ্লিকেশানটিকে মঞ্জুর করে৷ এর ফলে অন্যান্য অ্যাপ্লিকেশানগুলির জায়গা সীমিত হয়ে পড়তে পারে ও ফোনটি অপেক্ষাকৃত ধীরগতির হয়ে পড়তে পারে৷"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"ফোরগ্রাউন্ডে পরিষেবা চালানো"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"অ্যাপটিকে ফোরগ্রাউন্ডের পরিষেবা ব্যবহার করতে দেয়।"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"camera\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করান"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"অ্যাপকে \"camera\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করান"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"অ্যাপকে \"connectedDevice\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করান"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"অ্যাপকে \"dataSync\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"location\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করান"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"অ্যাপকে \"location\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করান"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"অ্যাপকে \"mediaPlayback\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করান"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"অ্যাপকে \"mediaProjection\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"microphone\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করান"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"অ্যাপকে \"microphone\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করান"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"অ্যাপকে \"phoneCall\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"health\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করান"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"অ্যাপকে \"health\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করান"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"অ্যাপকে \"remoteMessaging\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করান"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"অ্যাপকে \"systemExempted\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করান"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"অ্যাপকে \"specialUse\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"অ্যাপ্লিকেশন সঞ্চয়স্থানের জায়গা পরিমাপ করে"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"অ্যাপ্লিকেশানকে এটির কোড, ডেটা, এবং ক্যাশে মাপ উদ্ধার করার অনুমতি দেয়"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"সিস্টেম সেটিংস পরিবর্তন করুন"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"এই অ্যাপটি যখন ব্যবহার করা হচ্ছে, তখন মাইক্রোফোন ব্যবহার করে অডিও রেকর্ড করতে পারবে।"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ব্যাকগ্রাউন্ডে অডিও রেকর্ড করতে পারবে"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"এই অ্যাপটি মাইক্রোফোন ব্যবহার করে যেকোনও সময় অডিও রেকর্ড করতে পারবে।"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"অ্যাপ উইন্ডোর স্ক্রিন ক্যাপচার করা হলে তা শনাক্ত করা"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"অ্যাপ ব্যবহার করার সময় স্ক্রিনশট নেওয়া হলে এই অ্যাপে বিজ্ঞপ্তি পাঠানো হবে।"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"সিম এ আদেশগুলি পাঠান"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"অ্যাপ্লিকেশানটিকে সিম কার্ডে কমান্ডগুলি পাঠানোর অনুমতি দেয়৷ এটি খুবই বিপজ্জনক৷"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"শারীরিক অ্যাক্টিভিটি শনাক্ত করুন"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"আপনার শেয়ার করা স্টোরেজ থেকে ভিডিও ফাইল রিড করতে অ্যাপকে অনুমতি দিন।"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"শেয়ার করা স্টোরেজ থেকে ছবির ফাইল রিড করা"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"আপনার শেয়ার করা স্টোরেজ থেকে ছবির ফাইল রিড করার জন্য অ্যাপকে অনুমতি দেয়।"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"শেয়ার করা স্টোরেজ থেকে ব্যবহারকারীর বেছে নেওয়া ছবি ও ভিডিওর ফাইল রিড করুন"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"আপনার শেয়ার করা স্টোরেজ থেকে ছবি ও ভিডিওর ফাইল রিড করার জন্য অ্যাপকে অনুমতি দেয়।"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"শেয়ার করা স্টোরেজের কন্টেন্ট মুছে ফেলুন বা পরিবর্তন করুন"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"শেয়ার করা স্টোরেজের কন্টেন্ট লেখার জন্য অ্যাপটিকে অনুমতি দিন।"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP কল করুন/গ্রহণ করুন"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"আন-ইনস্টল করুন"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"যাই হোক, খুলতে চাই"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"ক্ষতিকর অ্যাপ শনাক্ত করা হয়েছে"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> অ্যাপকে ডিভাইসের সব লগ অ্যাক্সেসের অনুমতি দিতে চান?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"এককালীন অ্যাক্সেসের অনুমতি দিন"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"অনুমতি দেবেন না"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ডিভাইস লগে আপনার ডিভাইসে করা অ্যাক্টিভিটি রেকর্ড করা হয়। অ্যাপ সমস্যা খুঁজে তা সমাধান করতে এইসব লগ ব্যবহার করতে পারে।\n\nকিছু লগে সংবেদনশীল তথ্য থাকতে পারে, তাই বিশ্বাস করেন শুধুমাত্র এমন অ্যাপকেই সব ডিভাইসের লগ অ্যাক্সেসের অনুমতি দিন। \n\nআপনি এই অ্যাপকে ডিভাইসের সব লগ অ্যাক্সেস করার অনুমতি না দিলেও, এটি নিজের লগ অ্যাক্সেস করতে পারবে। ডিভাইস প্রস্তুতকারকও আপনার ডিভাইসের কিছু লগ বা তথ্য হয়ত অ্যাক্সেস করতে পারবে।"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ডিভাইস লগে আপনার ডিভাইসে করা অ্যাক্টিভিটি রেকর্ড করা হয়। অ্যাপ, সমস্যা খুঁজে তা সমাধান করতে এইসব লগ ব্যবহার করতে পারে।\n\nকিছু লগে সংবেদনশীল তথ্য থাকতে পারে, তাই বিশ্বাস করেন শুধুমাত্র এমন অ্যাপকেই ডিভাইসের সব লগ অ্যাক্সেসের অনুমতি দিন। \n\nআপনি এই অ্যাপকে ডিভাইসের সব লগ অ্যাক্সেস করার অনুমতি না দিলেও, এটি নিজের লগ অ্যাক্সেস করতে পারবে। ডিভাইস প্রস্তুতকারক এখনও আপনার ডিভাইসের কিছু লগ বা তথ্য হয়ত অ্যাক্সেস করতে পারবে।\n\ng.co/android/devicelogs লিঙ্ক থেকে আরও জানুন।"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"আর দেখতে চাই না"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> অ্যাপটি <xliff:g id="APP_2">%2$s</xliff:g> এর অংশ দেখাতে চায়"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"এডিট করুন"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"কল এবং বিজ্ঞপ্তি আসলে ভাইব্রেট হবে"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"আপনার <xliff:g id="DEVICE">%1$s</xliff:g> থেকে ফোনের ক্যামেরা অ্যাক্সেস করা যাচ্ছে না"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"আপনার <xliff:g id="DEVICE">%1$s</xliff:g> থেকে ট্যাবলেটের ক্যামেরা অ্যাক্সেস করা যাচ্ছে না"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"স্ট্রিমিংয়ের সময় এটি অ্যাক্সেস করা যাবে না। পরিবর্তে আপনার ফোনে ব্যবহার করে দেখুন।"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"স্ট্রিম করার সময় \'ছবির-মধ্যে-ছবি\' দেখা যাবে না"</string>
     <string name="system_locale_title" msgid="711882686834677268">"সিস্টেম ডিফল্ট"</string>
     <string name="default_card_name" msgid="9198284935962911468">"কার্ড <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 20f6bc1..0486932 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Govorna pošta"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problem sa povezivanjem ili nevažeći MMI kôd."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcija nije podržana."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Operacija je ograničena samo na brojeve fiksnog biranja."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nije moguće promijeniti postavke prosljeđivanja poziva s vašeg telefona dok ste u romingu."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Usluga je omogućena."</string>
@@ -73,6 +74,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">"Prijenos podataka usmjeravanjem na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Ove postavke možete uvijek 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>
@@ -230,7 +233,7 @@
     <string name="shutdown_confirm" product="default" msgid="136816458966692315">"Telefon će se isključiti."</string>
     <string name="shutdown_confirm_question" msgid="796151167261608447">"Želite li ugasiti telefon?"</string>
     <string name="reboot_safemode_title" msgid="5853949122655346734">"Ponovo pokreni uređaj u sigurnom načinu rada"</string>
-    <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Želite li pokrenuti uređaj u sigurnom načinu rada? To će onemogućiti sve aplikacije trećih strana koje ste instalirali. One će biti obnovljene kada ponovo pokrenete uređaj."</string>
+    <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Želite li ponovo pokrenuti uređaj u sigurnom načinu rada? To će onemogućiti sve aplikacije trećih strana koje ste instalirali. Obnovit će se kada još jednom ponovo pokrenete uređaj."</string>
     <string name="recent_tasks_title" msgid="8183172372995396653">"Nedavni zadaci"</string>
     <string name="no_recent_tasks" msgid="9063946524312275906">"Nema nedavno pokrenutih aplikacija."</string>
     <string name="global_actions" product="tablet" msgid="4412132498517933867">"Opcije tableta"</string>
@@ -394,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Omogućava aplikaciji da neke svoje dijelove pohrani trajno u memoriji. Ovo može ograničiti veličinu raspoložive memorije za druge aplikacije i tako usporiti telefon."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"pokretanje usluge u prvom planu"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Dopušta aplikaciji korištenje usluga u prvom planu."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"pokreni uslugu u prvom planu s vrstom \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"pokreni uslugu u prvom planu s vrstom \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"pokreni uslugu u prvom planu s vrstom \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"pokreni uslugu u prvom planu s vrstom \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"pokreni uslugu u prvom planu s vrstom \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"pokreni uslugu u prvom planu s vrstom \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"pokreni uslugu u prvom planu s vrstom \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"pokreni uslugu u prvom planu s vrstom \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"pokreni uslugu u prvom planu s vrstom \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"pokreni uslugu u prvom planu s vrstom \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"pokreni uslugu u prvom planu s vrstom \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"pokreni uslugu u prvom planu s vrstom \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mjerenje prostora kojeg aplikacije zauzimaju u pohrani"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Dozvoljava aplikaciji preuzimanje svog koda, podataka i veličine keš memorije"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"izmjena postavki sistema"</string>
@@ -446,6 +473,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Za vrijeme korištenja, ova aplikacija može snimati zvuk koristeći mikrofon."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"snimanje zvuka u pozadini"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ova aplikacija može u svakom trenutku snimati zvuk koristeći mikrofon."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"otkrivanje snimanja ekrana u prozorima aplikacije"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Aplikacija će dobiti obavještenje kada se kreira snimak ekrana dok se aplikacija koristi."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"slanje komandi SIM kartici"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Omogućava aplikaciji slanje naredbi na SIM. Ovo je vrlo opasno."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznavanje fizičke aktivnosti"</string>
@@ -697,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Omogućava aplikaciji da čita fajlove videozapisa iz vaše dijeljene pohrane."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"čitanje fajlova slika iz dijeljene pohrane"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Omogućava aplikaciji da čita fajlove slika iz vaše dijeljene pohrane."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"čitanje fajlova slika i videozapisa iz dijeljene pohrane koje je odabrao korisnik"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Omogućava aplikaciji da čita fajlove slika i videozapisa koje odaberete iz vaše dijeljene pohrane."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"mijenja ili briše sadržaj vaše dijeljene pohrane"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Omogućava aplikaciji da piše sadržaj vaše dijeljene pohrane."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"Uputi/primi SIP pozive"</string>
@@ -1011,7 +1042,7 @@
     <string name="factorytest_failed" msgid="3190979160945298006">"Fabrički test nije uspio"</string>
     <string name="factorytest_not_system" msgid="5658160199925519869">"Akcija FACTORY_TEST podržana je samo za pakete instalirane u facsikli /system/app."</string>
     <string name="factorytest_no_action" msgid="339252838115675515">"Nije pronađen paket koji omogućava akciju FACTORY_TEST."</string>
-    <string name="factorytest_reboot" msgid="2050147445567257365">"Ponovno pokretanje"</string>
+    <string name="factorytest_reboot" msgid="2050147445567257365">"Ponovo pokreni"</string>
     <string name="js_dialog_title" msgid="7464775045615023241">"Stranica na \"<xliff:g id="TITLE">%s</xliff:g>\" kaže:"</string>
     <string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</string>
     <string name="js_dialog_before_unload_title" msgid="7012587995876771246">"Potvrdite navigaciju"</string>
@@ -1372,7 +1403,7 @@
     <string name="console_running_notification_title" msgid="6087888939261635904">"Serijska konzola omogućena"</string>
     <string name="console_running_notification_message" msgid="7892751888125174039">"Performanse su smanjene. Da onemogućite, provjerite program za učitavanje operativnog sistema."</string>
     <string name="mte_override_notification_title" msgid="4731115381962792944">"Eksperimentalni MTE je omogućen"</string>
-    <string name="mte_override_notification_message" msgid="2441170442725738942">"Moguće da će to uticati na performanse i stabilnost. Ponovo pokrenite da onemogućite. Ako je omogućeno pomoću arm64.memtag.bootctl, unaprijed ga postavite na \"Ništa\"."</string>
+    <string name="mte_override_notification_message" msgid="2441170442725738942">"To može uticati na performanse i stabilnost. Ponovo pokrenite da onemogućite. Ako je omogućeno pomoću arm64.memtag.bootctl, prvo postavite na \"Ništa\"."</string>
     <string name="usb_contaminant_detected_title" msgid="4359048603069159678">"Tečnost ili nečistoće u USB priključku"</string>
     <string name="usb_contaminant_detected_message" msgid="7346100585390795743">"USB priključak je automatski onemogućen. Dodirnite da saznate više."</string>
     <string name="usb_contaminant_not_detected_title" msgid="2651167729563264053">"USB priključak je sada sigurno koristiti"</string>
@@ -2047,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEINSTALIRAJ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"IPAK OTVORI"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Otkrivena je štetna aplikacija"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Dozvoliti aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> da pristupa svim zapisnicima uređaja?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Dozvoli jednokratan pristup"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nemoj dozvoliti"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Zapisnici uređaja bilježe šta se dešava na uređaju. Aplikacije mogu koristiti te zapisnike da pronađu i isprave probleme.\n\nNeki zapisnici mogu sadržavati osjetljive podatke, zato pristup svim zapisnicima uređaja dozvolite samo aplikacijama kojima vjerujete. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač uređaja će možda i dalje biti u stanju pristupiti nekim zapisnicima ili podacima na uređaju."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Zapisnici uređaja bilježe šta se dešava na uređaju. Aplikacije mogu koristiti te zapisnike da pronađu i riješe probleme.\n\nNeki zapisnici mogu sadržavati osjetljive podatke. Zbog toga pristup svim zapisnicima uređaja dozvolite samo aplikacijama koje smatrate pouzdanima. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač uređaja će možda i dalje moći pristupiti nekim zapisnicima ili podacima na uređaju.\n\nSaznajte više na g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne prikazuj ponovo"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Aplikacija <xliff:g id="APP_0">%1$s</xliff:g> želi prikazati isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Uredi"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Pozivi i obavještenja će vibrirati"</string>
@@ -2293,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nije moguće pristupiti kameri telefona s uređaja <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nije moguće pristupiti kameri tableta s uređaja <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Ovom ne možete pristupiti tokom prijenosa. Umjesto toga pokušajte na telefonu."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Tokom prijenosa nije moguće gledati sliku u slici"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Sistemski zadano"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KARTICA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 2142b60..8936e4f 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Bústia de veu"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problema de connexió o codi MMI no vàlid."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"La funció no s\'admet."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"L\'operació està restringida a números de marcatge fixos."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"No es pot canviar la configuració de desviació de trucades del telèfon quan estàs en itinerància."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"El servei s\'ha activat."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Les dades s\'han canviat a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Pots canviar aquesta opció en qualsevol moment a Configuració"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permet que l\'aplicació faci que parts de la seva memòria siguin persistents. Aquesta acció pot limitar la memòria disponible per a altres aplicacions i alentir el telèfon."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"executar serveis en primer pla"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Permet que l\'aplicació utilitzi serveis en primer pla."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"executa serveis en primer pla amb el tipus \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"executa serveis en primer pla amb el tipus \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"executa serveis en primer pla amb el tipus \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"executa serveis en primer pla amb el tipus \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"executa serveis en primer pla amb el tipus \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"executa serveis en primer pla amb el tipus \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"executa serveis en primer pla amb el tipus \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"executa serveis en primer pla amb el tipus \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"executa serveis en primer pla amb el tipus \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"executa serveis en primer pla amb el tipus \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"executa serveis en primer pla amb el tipus \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executa serveis en primer pla amb el tipus \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mesura l\'espai d\'emmagatzematge d\'aplicacions"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Permet que l\'aplicació recuperi les mides del codi, de les dades i de la memòria cau"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modificar la configuració del sistema"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Aquesta aplicació pot gravar àudio amb el micròfon mentre s\'està utilitzant."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar àudio en segon pla"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Aquesta aplicació pot gravar àudio amb el micròfon en qualsevol moment."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detecta les captures de pantalla de les finestres d\'aplicacions"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Aquesta aplicació rebrà una notificació quan es faci una captura de pantalla mentre s\'utilitza l\'aplicació."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"enviar ordres a la SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permet que l\'aplicació enviï ordres a la SIM. Això és molt perillós."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconèixer l\'activitat física"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permet que l\'aplicació llegeixi fitxers de vídeo de l\'emmagatzematge compartit."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"llegir fitxers d\'imatge de l\'emmagatzematge compartit"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permet que l\'aplicació llegeixi fitxers d\'imatge de l\'emmagatzematge compartit."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"llegir els fitxers d\'imatge i de vídeo que l\'usuari seleccioni de l\'emmagatzematge compartit"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permet que l\'aplicació llegeixi els fitxers d\'imatge i de vídeo que seleccionis de l\'emmagatzematge compartit."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"editar o suprimir cont. d\'emmagatzematge compartit"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"L\'app pot editar contingut de l\'emmagatzematge compartit."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"Fer i rebre trucades de SIP"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTAL·LA"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OBRE IGUALMENT"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"S\'ha detectat una aplicació perjudicial"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vols permetre que <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> accedeixi a tots els registres del dispositiu?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permet l\'accés únic"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"No permetis"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Els registres del dispositiu inclouen informació sobre tot allò que passa al teu dispositiu. Les aplicacions poden utilitzar aquests registres per detectar i corregir problemes.\n\nÉs possible que alguns registres continguin informació sensible; per això només has de donar-hi accés a les aplicacions de confiança. \n\nEncara que no permetis que aquesta aplicació pugui accedir a tots els registres del dispositiu, podrà accedir als seus propis registres. És possible que el fabricant del dispositiu també tingui accés a alguns registres o a informació del teu dispositiu."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Els registres del dispositiu inclouen informació sobre tot allò que passa al teu dispositiu. Les aplicacions poden utilitzar aquests registres per detectar i corregir problemes.\n\nÉs possible que alguns registres continguin informació sensible; per això només has de donar-hi accés a les aplicacions de confiança. \n\nEncara que no permetis que aquesta aplicació accedeixi a tots els registres del dispositiu, podrà accedir als seus propis registres. És possible que el fabricant del dispositiu també tingui accés a alguns registres o a informació del teu dispositiu.\n\nObtén més informació a g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"No tornis a mostrar"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vol mostrar porcions de l\'aplicació <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edita"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Les trucades i les notificacions vibraran"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"No es pot accedir a la càmera del telèfon des del teu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"No es pot accedir a la càmera de la tauleta des del teu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"No s\'hi pot accedir mentre s\'està reproduint en continu. Prova-ho al telèfon."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"No es pot veure el mode de pantalla en pantalla durant la reproducció en continu"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Valor predeterminat del sistema"</string>
     <string name="default_card_name" msgid="9198284935962911468">"TARGETA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 7720d08..91daf78 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Hlasová schránka"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problém s připojením nebo neplatný kód MMI."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkce není podporována."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Operace je omezena pouze na povolená telefonní čísla."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Když je aktivní roaming, nastavení přesměrování hovorů z telefonu nelze změnit."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Služba byla zapnuta."</string>
@@ -74,6 +75,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Datové připojení bylo přepnuto na operátora <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Volbu můžete kdykoli změnit v nastavení"</string>
     <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>
@@ -395,6 +398,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Umožňuje aplikaci uložit některé své části trvale do paměti. Může to omezit paměť dostupnou pro ostatní aplikace a zpomalit tak telefon."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"spouštění služeb na popředí"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Povolte aplikaci využívání služeb na popředí."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"používat službu v popředí typu „camera“"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Umožňuje aplikaci používat služby v popředí typu „camera“"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"používat službu v popředí typu „connectedDevice“"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Umožňuje aplikaci používat služby v popředí typu „connectedDevice“"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"používat službu v popředí typu „dataSync“"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Umožňuje aplikaci používat služby v popředí typu „dataSync“"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"používat službu v popředí typu „location“"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Umožňuje aplikaci používat služby v popředí typu „location“"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"používat službu v popředí typu „mediaPlayback“"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Umožňuje aplikaci používat služby v popředí typu „mediaPlayback“"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"používat službu v popředí typu „mediaProjection“"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Umožňuje aplikaci používat služby v popředí typu „mediaProjection“"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"používat službu v popředí typu „microphone“"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Umožňuje aplikaci používat služby v popředí typu „microphone“"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"používat službu v popředí typu „phoneCall“"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Umožňuje aplikaci používat služby v popředí typu „phoneCall“"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"používat službu v popředí typu „health“"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Umožňuje aplikaci používat služby v popředí typu „health“"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"používat službu v popředí typu „remoteMessaging“"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Umožňuje aplikaci používat služby v popředí typu „remoteMessaging“"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"používat službu v popředí typu „systemExempted“"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Umožňuje aplikaci používat služby v popředí typu „systemExempted“"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"používat službu v popředí typu „specialUse“"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Umožňuje aplikaci používat služby v popředí typu „specialUse“"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"výpočet místa pro ukládání aplikací"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Umožňuje aplikaci načtení svého kódu, dat a velikostí mezipaměti."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"změna nastavení systému"</string>
@@ -447,6 +474,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Tato aplikace může pomocí mikrofonu během svého používání zaznamenat zvuk."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"zaznamenávat zvuk na pozadí"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Tato aplikace může pomocí mikrofonu kdykoli zaznamenat zvuk."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"zjišťování záznamu obrazovky s okny aplikace"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Tato aplikace dostane oznámení v případě pořízení snímku obrazovky během používání aplikace."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"odesílání příkazů do SIM karty"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Umožňuje aplikaci odesílat příkazy na kartu SIM. Toto oprávnění je velmi nebezpečné."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"rozpoznávání fyzické aktivity"</string>
@@ -698,6 +727,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Umožňuje aplikaci čtení videosouborů ze sdíleného úložiště."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"čtení obrázkových souborů ze sdíleného úložiště"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Umožňuje aplikaci číst obrázkové soubory ze sdíleného úložiště."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"čtení vybraných obrázkových souborů a souborů videa ze sdíleného úložiště"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Umožňuje aplikaci číst vybrané obrázkové soubory a soubory videa ze sdíleného úložiště."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"úprava nebo mazání obsahu sdíleného úložiště"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Umožňuje aplikaci zápis obsahu do sdíleného úložiště."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"uskutečňování/příjem volání SIP"</string>
@@ -2048,12 +2079,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ODINSTALOVAT"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"PŘESTO OTEVŘÍT"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Byla zjištěna škodlivá aplikace"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Povolit aplikaci <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> přístup ke všem protokolům zařízení?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Povolit jednorázový přístup"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nepovolovat"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Do protokolů zařízení se zaznamenává, co se na zařízení děje. Aplikace tyto protokoly mohou používat k vyhledání a odstranění problémů.\n\nNěkteré protokoly mohou zahrnovat citlivé údaje. Přístup k protokolům zařízení proto povolte pouze aplikacím, kterým důvěřujete. \n\nPokud této aplikaci nepovolíte přístup ke všem protokolům zařízení, bude mít stále přístup ke svým vlastním protokolům. Výrobce zařízení může mít stále přístup k některým protokolům nebo informacím na vašem zařízení."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Do protokolů zařízení se zaznamenává, co se na zařízení děje. Aplikace tyto protokoly mohou používat k vyhledání a odstranění problémů.\n\nNěkteré protokoly mohou zahrnovat citlivé údaje. Přístup k protokolům zařízení proto povolte pouze aplikacím, kterým důvěřujete. \n\nPokud této aplikaci nepovolíte přístup ke všem protokolům zařízení, bude mít stále přístup ke svým vlastním protokolům. Výrobce zařízení může mít stále přístup k některým protokolům nebo informacím na vašem zařízení.\n\nDalší informace najdete na stránce g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Příště nezobrazovat"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Aplikace <xliff:g id="APP_0">%1$s</xliff:g> chce zobrazovat ukázky z aplikace <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Upravit"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Volání a oznámení budou vibrovat"</string>
@@ -2294,6 +2319,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ze zařízení <xliff:g id="DEVICE">%1$s</xliff:g> nelze získat přístup k fotoaparátu telefonu"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ze zařízení <xliff:g id="DEVICE">%1$s</xliff:g> nelze získat přístup k fotoaparátu tabletu"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Tento obsah při streamování nelze zobrazit. Zkuste to na telefonu."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Během streamování nelze zobrazit obraz v obraze"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Výchozí nastavení systému"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index ecd6407..b1f5e2a 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Telefonsvarer"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Forbindelsesproblemer eller ugyldigt MMI-nummer."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funktionen understøttes ikke."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Du kan kun foretage handlinger med dine numre til begrænset opkald."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Det er ikke muligt at ændre indstillingerne for viderestilling af opkald fra din telefon, mens du bruger roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Tjenesten blev aktiveret."</string>
@@ -72,6 +73,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>
@@ -332,7 +335,7 @@
     <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Udføre bevægelser"</string>
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Kan trykke, stryge, knibe sammen og udføre andre bevægelser."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingeraftryksbevægelser"</string>
-    <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Kan registrere bevægelser, der foretages på enhedens fingeraftrykslæser."</string>
+    <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Kan registrere bevægelser, der foretages på enhedens fingeraftrykssensor."</string>
     <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Tag screenshot"</string>
     <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Kan tage et screenshot af skærmen."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"deaktivere eller redigere statuslinje"</string>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Tillader, at appen gør dele af sig selv vedholdende i hukommelsen. Dette kan begrænse den tilgængelige hukommelse for andre apps, hvilket gør telefonen langsommere."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"kør tjeneste i forgrunden"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Tillad, at appen anvender tjenester i forgrunden."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"kør tjenesten af typen \"camera\" i forgrunden"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Tillader, at appen benytter tjenester af typen \"camera\" i forgrunden"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"kør tjenesten af typen \"connectedDevice\" i forgrunden"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Tillader, at appen benytter tjenester af typen \"connectedDevice\" i forgrunden"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"kør tjenesten af typen \"dataSync\" i forgrunden"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Tillader, at appen benytter tjenester af typen \"dataSync\" i forgrunden"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"kør tjenesten af typen \"location\" i forgrunden"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Tillader, at appen benytter tjenester af typen \"location\" i forgrunden"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"kør tjenesten af typen \"mediaPlayback\" i forgrunden"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Tillader, at appen benytter tjenester af typen \"mediaPlayback\" i forgrunden"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"kør tjenesten af typen \"mediaProjection\" i forgrunden"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Tillader, at appen benytter tjenester af typen \"mediaProjection\" i forgrunden"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"kør tjenesten af typen \"microphone\" i forgrunden"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Tillader, at appen benytter tjenester af typen \"microphone\" i forgrunden"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"kør tjenesten af typen \"phoneCall\" i forgrunden"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Tillader, at appen benytter tjenester af typen \"phoneCall\" i forgrunden"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"kør tjenesten af typen \"health\" i forgrunden"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Tillader, at appen benytter tjenester af typen \"health\" i forgrunden"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"kør tjenesten af typen \"remoteMessaging\" i forgrunden"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Tillader, at appen benytter tjenester af typen \"remoteMessaging\" i forgrunden"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"kør tjenesten af typen \"systemExempted\" i forgrunden"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Tillader, at appen benytter tjenester af typen \"systemExempted\" i forgrunden"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"kør tjenesten af typen \"specialUse\" i forgrunden"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Tillader, at appen benytter tjenester af typen \"specialUse\" i forgrunden"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"måle appens lagerplads"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Tillader, at en app kan hente sin kode, data og cachestørrelser"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"ændre systemindstillinger"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Denne app kan optage lyd med mikrofonen, mens appen er i brug."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"optag lyd i baggrunden"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Denne app kan optage lyd med mikrofonen når som helst."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"registrer screenshots af appvindue"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Denne app får en notifikation, når der tages et screenshot, mens appen er i brug."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"send kommandoer til SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Tillader, at appen sender kommandoer til SIM-kortet. Dette er meget farligt."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"genkend fysisk aktivitet"</string>
@@ -581,11 +610,11 @@
     <string name="biometric_error_generic" msgid="6784371929985434439">"Der opstod fejl i forbindelse med godkendelse"</string>
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Brug skærmlås"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Angiv din skærmlås for at fortsætte"</string>
-    <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Hold fingeren nede på læseren"</string>
+    <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Hold fingeren nede på sensoren"</string>
     <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Fingeraftrykket kan ikke genkendes. Prøv igen."</string>
-    <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Rengør fingeraftrykslæseren, og prøv igen"</string>
-    <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Rengør læseren, og prøv igen"</string>
-    <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Hold fingeren nede på læseren"</string>
+    <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Rengør fingeraftrykssensoren, og prøv igen"</string>
+    <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Rengør sensoren, og prøv igen"</string>
+    <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Hold fingeren nede på sensoren"</string>
     <string name="fingerprint_acquired_too_slow" msgid="6683510291554497580">"Du bevægede fingeren for langsomt. Prøv igen."</string>
     <string name="fingerprint_acquired_already_enrolled" msgid="2285166003936206785">"Prøv med et andet fingeraftryk"</string>
     <string name="fingerprint_acquired_too_bright" msgid="3863560181670915607">"Der er for lyst"</string>
@@ -608,9 +637,9 @@
     <string name="fingerprint_error_lockout_permanent" msgid="9060651300306264843">"Du har brugt for mange forsøg. Brug skærmlåsen i stedet."</string>
     <string name="fingerprint_error_unable_to_process" msgid="2446280592818621224">"Fingeraftrykket kan ikke behandles. Prøv igen."</string>
     <string name="fingerprint_error_no_fingerprints" msgid="8671811719699072411">"Der er ikke registreret nogen fingeraftryk."</string>
-    <string name="fingerprint_error_hw_not_present" msgid="578914350967423382">"Denne enhed har ingen fingeraftrykslæser."</string>
+    <string name="fingerprint_error_hw_not_present" msgid="578914350967423382">"Denne enhed har ingen fingeraftrykssensor."</string>
     <string name="fingerprint_error_security_update_required" msgid="7750187320640856433">"Sensoren er midlertidigt deaktiveret."</string>
-    <string name="fingerprint_error_bad_calibration" msgid="4385512597740168120">"Fingeraftrykslæseren kan ikke bruges. Få den repareret"</string>
+    <string name="fingerprint_error_bad_calibration" msgid="4385512597740168120">"Fingeraftrykssensoren kan ikke bruges. Få den repareret"</string>
     <string name="fingerprint_error_power_pressed" msgid="5479524500542129414">"Der blev trykket på afbryderknappen"</string>
     <string name="fingerprint_name_template" msgid="8941662088160289778">"Fingeraftryk <xliff:g id="FINGERID">%d</xliff:g>"</string>
     <string name="fingerprint_app_setting_name" msgid="4253767877095495844">"Brug fingeraftryk"</string>
@@ -630,7 +659,7 @@
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Konfigurer flere måder at låse op på"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Tryk for at tilføje et fingeraftryk"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Oplåsning med fingeraftryk"</string>
-    <string name="fingerprint_recalibrate_notification_title" msgid="2406561052064558497">"Fingeraftrykslæseren kan ikke bruges"</string>
+    <string name="fingerprint_recalibrate_notification_title" msgid="2406561052064558497">"Fingeraftrykssensoren kan ikke bruges"</string>
     <string name="fingerprint_recalibrate_notification_content" msgid="8519935717822194943">"Få den repareret."</string>
     <string name="face_acquired_insufficient" msgid="6889245852748492218">"Din ansigtsmodel kan ikke oprettes. Prøv igen."</string>
     <string name="face_acquired_too_bright" msgid="8070756048978079164">"Der er for lyst. Prøv en mere dæmpet belysning."</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Tillader, at appen læser videofiler fra din fælles lagerplads."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"Læse billedfiler fra den delte lagerplads"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Tillader, at appen læser billedfiler fra din delte lagerplads."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"læse brugervalgte billed- og videofiler fra delt lagerplads"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Tillader, at appen læser billed- og videofiler, som du vælger fra din delte lagerplads."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"ændre eller slette indholdet af din delte lagerplads"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Tillader, at appen kan skrive indholdet af din delte lagerplads."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"foretage/modtage SIP-opkald"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"AFINSTALLER"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ÅBN ALLIGEVEL"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Der er registreret en skadelig app"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vil du give <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> adgang til alle enhedslogs?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Tillad engangsadgang"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Tillad ikke"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Enhedslogs registrerer, hvad der sker på din enhed. Apps kan bruge disse logs til at finde og løse problemer.\n\nNogle logs kan indeholde følsomme oplysninger, så giv kun apps, du har tillid til, adgang til alle enhedslogs. \n\nSelvom du ikke giver denne app adgang til alle enhedslogs, kan den stadig tilgå sine egne logs. Producenten af din enhed kan muligvis fortsat tilgå visse logs eller oplysninger på din enhed."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Enhedslogs registrerer, hvad der sker på din enhed. Apps kan bruge disse logs til at finde og løse problemer.\n\nNogle logs kan indeholde følsomme oplysninger, så giv kun apps, du har tillid til, adgang til alle enhedslogs. \n\nSelvom du ikke giver denne app adgang til alle enhedslogs, kan den stadig tilgå sine egne logs. Producenten af din enhed kan muligvis fortsat tilgå visse logs eller oplysninger på din enhed.\n\nFå flere oplysninger på g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Vis ikke igen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> anmoder om tilladelse til at vise eksempler fra <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Rediger"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Telefonen vibrerer ved opkald og notifikationer"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Kameraet på din telefon kan ikke tilgås via din <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Kameraet på din tablet kan ikke tilgås via din <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Der er ikke adgang til dette indhold under streaming. Prøv på din telefon i stedet."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Funktionen Integreret billede er ikke tilgængelig, når der streames"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Systemstandard"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KORT <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 3d5985c9..9e89501 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Mailbox"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Verbindungsproblem oder ungültiger MMI-Code."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funktion nicht unterstützt."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Der Vorgang ist nur für deine zugelassenen Rufnummern möglich."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Die Einstellungen für die Anrufweiterleitung von deinem Smartphone können während des Roamings nicht geändert werden."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Dienst wurde aktiviert."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Mobile Daten wurden auf <xliff:g id="CARRIERDISPLAY">%s</xliff:g> umgestellt"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Du kannst dies jederzeit in den Einstellungen ändern"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Ermöglicht der App, Teile der eigenen App dauerhaft im Speicher abzulegen. Dies kann dazu führen, dass anderen Apps weniger Arbeitsspeicher zur Verfügung steht und das Telefon langsamer wird."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"Vordergrunddienst ausführen"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Ermöglicht der App, die Vordergrunddienste zu verwenden."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"Vordergrunddienste mit dem Typ „camera“ ausführen"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Ermöglicht der App, Vordergrunddienste mit dem Typ „camera“ zu verwenden"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"Vordergrunddienste mit dem Typ „connectedDevice“ ausführen"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Ermöglicht der App, Vordergrunddienste mit dem Typ „connectedDevice“ zu verwenden"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"Vordergrunddienste mit dem Typ „dataSync“ ausführen"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Ermöglicht der App, Vordergrunddienste mit dem Typ „dataSync“ zu verwenden"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"Vordergrunddienste mit dem Typ „location“ ausführen"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Ermöglicht der App, Vordergrunddienste mit dem Typ „location“ zu verwenden"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"Vordergrunddienste mit dem Typ „mediaPlayback“ ausführen"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Ermöglicht der App, Vordergrunddienste mit dem Typ „mediaPlayback“ zu verwenden"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"Vordergrunddienste mit dem Typ „mediaProjection“ ausführen"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Ermöglicht der App, Vordergrunddienste mit dem Typ „mediaProjection“ zu verwenden"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"Vordergrunddienste mit dem Typ „microphone“ ausführen"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Ermöglicht der App, Vordergrunddienste mit dem Typ „microphone“ zu verwenden"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"Vordergrunddienste mit dem Typ „phoneCall“ ausführen"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Ermöglicht der App, Vordergrunddienste mit dem Typ „phoneCall“ zu verwenden"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"Vordergrunddienste mit dem Typ „health“ ausführen"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Ermöglicht der App, Vordergrunddienste mit dem Typ „health“ zu verwenden"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"Vordergrunddienste mit dem Typ „remoteMessaging“ ausführen"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Ermöglicht der App, Vordergrunddienste mit dem Typ „remoteMessaging“ zu verwenden"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"Vordergrunddienste mit dem Typ „systemExempted“ ausführen"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Ermöglicht der App, Vordergrunddienste mit dem Typ „systemExempted“ zu verwenden"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"Vordergrunddienste mit dem Typ „specialUse“ ausführen"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Ermöglicht der App, Vordergrunddienste mit dem Typ „specialUse“ zu verwenden"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"Speicherplatz der App ermitteln"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Ermöglicht der App, ihre Code-, Daten- und Cache-Größe abzurufen"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"Systemeinstellungen ändern"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Diese App darf mit dem Mikrofon Audioaufnahmen machen, solange sie verwendet wird."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Audioaufnahmen im Hintergrund machen"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Diese App darf mit dem Mikrofon jederzeit Audioaufnahmen machen."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"Bildschirmaufnahmen von App-Fenstern erkennen"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Diese App wird benachrichtigt, wenn ein Screenshot aufgenommen wird, während du sie verwendest."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"Befehle an die SIM senden"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Ermöglicht der App das Senden von Befehlen an die SIM-Karte. Dies ist äußerst risikoreich."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"Körperliche Aktivitäten erkennen"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Gewährt der App Lesezugriff auf Videodateien in deinem freigegebenen Speicher."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"Lesezugriff auf Bilddateien im freigegebenen Speicher"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Gewährt der App Lesezugriff auf Bilddateien in deinem freigegebenen Speicher."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"Vom Nutzer ausgewählte Bild- und Videodateien aus dem freigegebenen Speicher lesen"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Die App darf Bild- und Videodateien lesen, die du aus dem freigegebenen Speicher auswählst."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"Inhalte deines freigegebenen Speichers ändern oder löschen"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"So kann die App Inhalte deines freigegebenen Speichers erstellen."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP-Anrufe tätigen/empfangen"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEINSTALLIEREN"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"TROTZDEM ÖFFNEN"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Schädliche App erkannt"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> den Zugriff auf alle Geräteprotokolle erlauben?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Einmaligen Zugriff zulassen"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nicht zulassen"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"In Geräteprotokollen wird aufgezeichnet, welche Aktionen auf deinem Gerät ausgeführt werden. Apps können diese Protokolle verwenden, um Probleme zu finden und zu beheben.\n\nEinige Protokolle enthalten unter Umständen vertrauliche Informationen, daher solltest du nur vertrauenswürdigen Apps den Zugriff auf alle Geräteprotokolle erlauben. \n\nWenn du dieser App keinen Zugriff auf alle Geräteprotokolle gewährst, kann sie trotzdem auf ihre eigenen Protokolle zugreifen. Dein Gerätehersteller hat möglicherweise auch Zugriff auf einige Protokolle oder Informationen auf deinem Gerät."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"In Geräteprotokollen wird aufgezeichnet, welche Aktionen auf deinem Gerät ausgeführt werden. Apps können diese Protokolle verwenden, um Probleme zu finden und zu beheben.\n\nEinige Protokolle enthalten unter Umständen vertrauliche Informationen. Daher solltest du nur vertrauenswürdigen Apps den Zugriff auf alle Geräteprotokolle erlauben. \n\nWenn du dieser App keinen Zugriff auf alle Geräteprotokolle gewährst, kann sie trotzdem auf ihre eigenen Protokolle zugreifen. Dein Gerätehersteller hat möglicherweise auch Zugang zu einigen Protokollen oder Informationen auf deinem Gerät.\n\nWeitere Informationen findest du unter g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Nicht mehr anzeigen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> möchte Teile von <xliff:g id="APP_2">%2$s</xliff:g> anzeigen"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Bearbeiten"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Gerät vibriert bei Anrufen und Benachrichtigungen"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Zugriff auf die Kamera des Smartphones über dein Gerät (<xliff:g id="DEVICE">%1$s</xliff:g>) nicht möglich"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Zugriff auf die Kamera des Tablets über dein Gerät (<xliff:g id="DEVICE">%1$s</xliff:g>) nicht möglich"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Während des Streamings ist kein Zugriff möglich. Versuch es stattdessen auf deinem Smartphone."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Funktion „Bild im Bild“ kann beim Streamen nicht verwendet werden"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Standardeinstellung des Systems"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KARTE <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index f84a9fb..8f55589 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Αυτ/τος τηλεφωνητής"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Πρόβλημα σύνδεσης ή μη έγκυρος κώδικας MMI."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Η λειτουργία δεν υποστηρίζεται."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Η λειτουργία περιορίζεται μόνο σε προκαθορισμένους αριθμούς κλήσης."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Δεν είναι δυνατή η αλλαγή των ρυθμίσεων προώθησης κλήσεων από το τηλέφωνό σας κατά τη διάρκεια της περιαγωγής."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Η υπηρεσία ενεργοποιήθηκε."</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Επιτρέπει στην εφαρμογή να δημιουργεί μόνιμα τμήματα του εαυτού της στη μνήμη. Αυτό μπορεί να περιορίζει τη μνήμη που διατίθεται σε άλλες εφαρμογές, καθυστερώντας τη λειτουργία του τηλεφώνου."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"εκτέλεση υπηρεσίας προσκηνίου"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί υπηρεσίες προσκηνίου."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"υπολογίζει τον αποθηκευτικό χώρο εφαρμογής"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Επιτρέπει στην εφαρμογή να ανακτήσει τα μεγέθη κώδικα, δεδομένων και προσωρινής μνήμης"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"τροποποίηση ρυθμίσεων συστήματος"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Αυτή η εφαρμογή μπορεί να εγγράφει ήχο μέσω του μικροφώνου, όταν τη χρησιμοποιείτε."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"εγγραφή ήχου στο παρασκήνιο"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Αυτή η εφαρμογή μπορεί να εγγράφει ήχο μέσω του μικροφώνου, ανά πάσα στιγμή."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ανίχνευση καταγραφών οθόνης που περιέχουν τα παράθυρα της εφαρμογής"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Η εφαρμογή θα λάβει ειδοποίηση όταν ληφθεί ένα στιγμιότυπο οθόνης ενώ βρίσκεται σε χρήση."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"στέλνει εντολές στην κάρτα SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Επιτρέπει στην εφαρμογή την αποστολή εντολών στην κάρτα SIM. Αυτό είναι εξαιρετικά επικίνδυνο."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"αναγνώριση σωματικής δραστηριότητας"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Επιτρέπει στην εφαρμογή την ανάγνωση αρχείων βίντεο από τον κοινόχρηστο αποθηκευτικό σας χώρο."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"ανάγνωση αρχείων εικόνας από τον κοινόχρηστο αποθηκευτικό χώρο"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Επιτρέπει στην εφαρμογή την ανάγνωση αρχείων εικόνας από τον κοινόχρηστο αποθηκευτικό σας χώρο."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ανάγνωση αρχείων εικόνας και βίντεο που έχει επιλέξει ο χρήστης από τον κοινόχρηστο αποθηκευτικό χώρο"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Επιτρέπει στην εφαρμογή την ανάγνωση των αρχείων εικόνας και βίντεο που θα επιλέξετε από τον κοινόχρηστο αποθηκευτικό χώρο."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"τροποποιεί ή διαγράφει το περιεχόμενο του κοινόχρηστου αποθηκευτικού χώρου σας"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Επιτρέπει στην εφαρμογή την εγγραφή του περιεχομένου του κοινόχρηστου αποθηκευτικού χώρου σας."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"πραγματοποιεί/λαμβάνει κλήσεις SIP"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ΑΠΕΓΚΑΤΑΣΤΑΣΗ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ΑΝΟΙΓΜΑ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Εντοπίστηκε επιβλαβής εφαρμογή"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Να επιτρέπεται στο <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> η πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής;"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Να επιτρέπεται η πρόσβαση για μία φορά"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Να μην επιτραπεί"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Τα αρχεία καταγραφής συσκευής καταγράφουν ό,τι συμβαίνει στη συσκευή σας. Οι εφαρμογές μπορούν να χρησιμοποιούν αυτά τα αρχεία καταγραφής για να εντοπίζουν και να διορθώνουν ζητήματα.\n\nΟρισμένα αρχεία καταγραφής ενδέχεται να περιέχουν ευαίσθητες πληροφορίες. Ως εκ τούτου, επιτρέψτε την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής μόνο στις εφαρμογές που εμπιστεύεστε. \n\nΕάν δεν επιτρέψετε σε αυτήν την εφαρμογή την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής, η εφαρμογή εξακολουθεί να έχει πρόσβαση στα δικά της αρχεία καταγραφής. Ο κατασκευαστής της συσκευής σας ενδέχεται να εξακολουθεί να έχει πρόσβαση σε ορισμένα αρχεία καταγραφής ή ορισμένες πληροφορίες στη συσκευή σας."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Τα αρχεία καταγραφής συσκευής καταγράφουν ό,τι συμβαίνει στη συσκευή σας. Οι εφαρμογές μπορούν να χρησιμοποιούν αυτά τα αρχεία καταγραφής για να εντοπίζουν και να διορθώνουν ζητήματα.\n\nΟρισμένα αρχεία καταγραφής ενδέχεται να περιέχουν ευαίσθητες πληροφορίες. Ως εκ τούτου, επιτρέψτε την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής μόνο στις εφαρμογές που εμπιστεύεστε. \n\nΕάν δεν επιτρέψετε σε αυτήν την εφαρμογή την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής, η εφαρμογή εξακολουθεί να έχει πρόσβαση στα δικά της αρχεία καταγραφής. Ο κατασκευαστής της συσκευής σας ενδέχεται να εξακολουθεί να έχει πρόσβαση σε ορισμένα αρχεία καταγραφής ή σε πληροφορίες στη συσκευή σας.\n\nΜάθετε περισσότερα στη διεύθυνση g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Να μην εμφανισ. ξανά"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Η εφαρμογή <xliff:g id="APP_0">%1$s</xliff:g> θέλει να εμφανίζει τμήματα της εφαρμογής <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Επεξεργασία"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Θα υπάρχει δόνηση για κλήσεις και ειδοποιήσεις"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Δεν είναι δυνατή η πρόσβαση στην κάμερα τηλεφώνου από το <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Δεν είναι δυνατή η πρόσβαση στην κάμερα του tablet από τη συσκευή <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Δεν είναι δυνατή η πρόσβαση σε αυτό το στοιχείο κατά τη ροή. Δοκιμάστε στο τηλέφωνό σας."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Δεν είναι δυνατή η προβολή picture-in-picture κατά τη ροή"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Προεπιλογή συστήματος"</string>
     <string name="default_card_name" msgid="9198284935962911468">"ΚΑΡΤΑ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 4c0510b..003b3f0 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Voicemail"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Connection problem or invalid MMI code."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Feature not supported."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Operation is restricted to fixed dialling numbers only."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Cannot change call forwarding settings from your phone while you are roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Service was enabled."</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps, slowing down the phone."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"run foreground service"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Allows the app to make use of foreground services."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"run foreground service with the type \'camera\'"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Allows the app to make use of foreground services with the type \'camera\'"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"run foreground service with the type \'connectedDevice\'"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Allows the app to make use of foreground services with the type \'connectedDevice\'"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"run foreground service with the type \'dataSync\'"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Allows the app to make use of foreground services with the type \'dataSync\'"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"run foreground service with the type \'location\'"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Allows the app to make use of foreground services with the type \'location\'"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"run foreground service with the type \'mediaPlayback\'"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Allows the app to make use of foreground services with the type \'mediaPlayback\'"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"run foreground service with the type \'mediaProjection\'"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Allows the app to make use of foreground services with the type \'mediaProjection\'"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"run foreground service with the type \'microphone\'"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Allows the app to make use of foreground services with the type \'microphone\'"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"run foreground service with the type \'phoneCall\'"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Allows the app to make use of foreground services with the type \'phoneCall\'"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"run foreground service with the type \'health\'"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Allows the app to make use of foreground services with the type \'health\'"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"run foreground service with the type \'remoteMessaging\'"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Allows the app to make use of foreground services with the type \'remoteMessaging\'"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"run foreground service with the type \'systemExempted\'"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Allows the app to make use of foreground services with the type \'systemExempted\'"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"run foreground service with the type \'specialUse\'"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Allows the app to make use of foreground services with the type \'specialUse\'"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Allows the app to retrieve its code, data and cache sizes"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modify system settings"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"This app can record audio using the microphone while the app is in use."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"record audio in the background"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"This app can record audio using the microphone at any time."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detect screen captures of app windows"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"This app will get notified when a screenshot is taken while the app is in use."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Allows the app to read video files from your shared storage."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"read image files from shared storage"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Allows the app to read image files from your shared storage."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"read user selected image and video files from shared storage"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Allows the app to read image and video files that you select from your shared storage."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"modify or delete the contents of your shared storage"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Allows the app to write the contents of your shared storage."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"make/receive SIP calls"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"UNINSTALL"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OPEN ANYWAY"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Harmful app detected"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Allow one-time access"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Don’t allow"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Don’t show again"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wants to show <xliff:g id="APP_2">%2$s</xliff:g> slices"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Calls and notifications will vibrate"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Can’t access the phone’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Can’t access the tablet’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"This can’t be accessed while streaming. Try on your phone instead."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Can’t view picture-in-picture while streaming"</string>
     <string name="system_locale_title" msgid="711882686834677268">"System default"</string>
     <string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 875ddf9..e1cfd83 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -28,18 +28,19 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Voicemail"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Connection problem or invalid MMI code."</string>
-    <string name="mmiFdnError" msgid="3975490266767565852">"Operation is restricted to fixed dialling numbers only."</string>
-    <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Cannot change call forwarding settings from your phone while you are roaming."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Feature not supported."</string>
+    <string name="mmiFdnError" msgid="3975490266767565852">"Operation is restricted to fixed dialing numbers only."</string>
+    <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Can not change call forwarding settings from your phone while you are roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Service was enabled."</string>
     <string name="serviceEnabledFor" msgid="1463104778656711613">"Service was enabled for:"</string>
     <string name="serviceDisabled" msgid="641878791205871379">"Service has been disabled."</string>
     <string name="serviceRegistered" msgid="3856192211729577482">"Registration was successful."</string>
-    <string name="serviceErased" msgid="997354043770513494">"Erase successful."</string>
+    <string name="serviceErased" msgid="997354043770513494">"Erasure was successful."</string>
     <string name="passwordIncorrect" msgid="917087532676155877">"Incorrect password."</string>
     <string name="mmiComplete" msgid="6341884570892520140">"MMI complete."</string>
-    <string name="badPin" msgid="888372071306274355">"The old PIN that you typed is incorrect."</string>
-    <string name="badPuk" msgid="4232069163733147376">"The PUK that you typed isn\'t correct."</string>
-    <string name="mismatchPin" msgid="2929611853228707473">"The PINs that you typed don\'t match."</string>
+    <string name="badPin" msgid="888372071306274355">"The old PIN you typed isn\'t correct."</string>
+    <string name="badPuk" msgid="4232069163733147376">"The PUK you typed isn\'t correct."</string>
+    <string name="mismatchPin" msgid="2929611853228707473">"The PINs you typed don\'t match."</string>
     <string name="invalidPin" msgid="7542498253319440408">"Type a PIN that is 4 to 8 numbers."</string>
     <string name="invalidPuk" msgid="8831151490931907083">"Type a PUK that is 8 numbers or longer."</string>
     <string name="needPuk" msgid="7321876090152422918">"Your SIM card is PUK-locked. Type the PUK code to unlock it."</string>
@@ -52,7 +53,7 @@
     <string name="imei" msgid="2157082351232630390">"IMEI"</string>
     <string name="meid" msgid="3291227361605924674">"MEID"</string>
     <string name="ClipMmi" msgid="4110549342447630629">"Incoming Caller ID"</string>
-    <string name="ClirMmi" msgid="6752346475055446417">"Hide outgoing caller ID"</string>
+    <string name="ClirMmi" msgid="6752346475055446417">"Hide Outgoing Caller ID"</string>
     <string name="ColpMmi" msgid="4736462893284419302">"Connected Line ID"</string>
     <string name="ColrMmi" msgid="5889782479745764278">"Connected Line ID Restriction"</string>
     <string name="CfMmi" msgid="8390012691099787178">"Call forwarding"</string>
@@ -62,7 +63,7 @@
     <string name="PinMmi" msgid="7133542099618330959">"PIN change"</string>
     <string name="CnipMmi" msgid="4897531155968151160">"Calling number present"</string>
     <string name="CnirMmi" msgid="885292039284503036">"Calling number restricted"</string>
-    <string name="ThreeWCMmi" msgid="2436550866139999411">"Three-way calling"</string>
+    <string name="ThreeWCMmi" msgid="2436550866139999411">"Three way calling"</string>
     <string name="RuacMmi" msgid="1876047385848991110">"Rejection of undesired annoying calls"</string>
     <string name="CndMmi" msgid="185136449405618437">"Calling number delivery"</string>
     <string name="DndMmi" msgid="8797375819689129800">"Do not disturb"</string>
@@ -72,6 +73,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>
@@ -88,7 +91,7 @@
     <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Mobile data status"</string>
     <string name="notification_channel_sms" msgid="1243384981025535724">"SMS messages"</string>
     <string name="notification_channel_voice_mail" msgid="8457433203106654172">"Voicemail messages"</string>
-    <string name="notification_channel_wfc" msgid="9048240466765169038">"Wi-Fi Calling"</string>
+    <string name="notification_channel_wfc" msgid="9048240466765169038">"Wi-Fi calling"</string>
     <string name="notification_channel_sim" msgid="5098802350325677490">"SIM status"</string>
     <string name="notification_channel_sim_high_prio" msgid="642361929452850928">"High priority SIM status"</string>
     <string name="peerTtyModeFull" msgid="337553730440832160">"Peer requested TTY Mode FULL"</string>
@@ -106,7 +109,7 @@
     <string name="roamingText0" msgid="7793257871609854208">"Roaming Indicator On"</string>
     <string name="roamingText1" msgid="5073028598334616445">"Roaming Indicator Off"</string>
     <string name="roamingText2" msgid="2834048284153110598">"Roaming Indicator Flashing"</string>
-    <string name="roamingText3" msgid="831690234035748988">"Out of local area"</string>
+    <string name="roamingText3" msgid="831690234035748988">"Out of Neighborhood"</string>
     <string name="roamingText4" msgid="2171252529065590728">"Out of Building"</string>
     <string name="roamingText5" msgid="4294671587635796641">"Roaming - Preferred System"</string>
     <string name="roamingText6" msgid="5536156746637992029">"Roaming - Available System"</string>
@@ -127,15 +130,15 @@
     <!-- no translation found for wfcSpnFormat_spn (2982505428519096311) -->
     <skip />
     <string name="wfcSpnFormat_spn_wifi_calling" msgid="3165949348000906194">"<xliff:g id="SPN">%s</xliff:g> Wi-Fi Calling"</string>
-    <string name="wfcSpnFormat_spn_wifi_calling_vo_hyphen" msgid="3836827895369365298">"<xliff:g id="SPN">%s</xliff:g> Wi-Fi calling"</string>
-    <string name="wfcSpnFormat_wlan_call" msgid="4895315549916165700">"WLAN call"</string>
+    <string name="wfcSpnFormat_spn_wifi_calling_vo_hyphen" msgid="3836827895369365298">"<xliff:g id="SPN">%s</xliff:g> WiFi Calling"</string>
+    <string name="wfcSpnFormat_wlan_call" msgid="4895315549916165700">"WLAN Call"</string>
     <string name="wfcSpnFormat_spn_wlan_call" msgid="255919245825481510">"<xliff:g id="SPN">%s</xliff:g> WLAN Call"</string>
     <string name="wfcSpnFormat_spn_wifi" msgid="7232899594327126970">"<xliff:g id="SPN">%s</xliff:g> Wi-Fi"</string>
-    <string name="wfcSpnFormat_wifi_calling_bar_spn" msgid="8383917598312067365">"Wi-Fi Calling | <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="wfcSpnFormat_wifi_calling_bar_spn" msgid="8383917598312067365">"WiFi Calling | <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="wfcSpnFormat_spn_vowifi" msgid="6865214948822061486">"<xliff:g id="SPN">%s</xliff:g> VoWifi"</string>
     <string name="wfcSpnFormat_wifi_calling" msgid="6178935388378661755">"Wi-Fi Calling"</string>
     <string name="wfcSpnFormat_wifi" msgid="1376356951297043426">"Wi-Fi"</string>
-    <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"Wi-Fi Calling"</string>
+    <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"WiFi Calling"</string>
     <string name="wfcSpnFormat_vowifi" msgid="8371335230890725606">"VoWifi"</string>
     <string name="wifi_calling_off_summary" msgid="5626710010766902560">"Off"</string>
     <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Call over Wi-Fi"</string>
@@ -143,7 +146,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi only"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Backup calling"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Backup Calling"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Not forwarded"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> after <xliff:g id="TIME_DELAY">{2}</xliff:g> seconds"</string>
@@ -167,13 +170,13 @@
     <string name="httpErrorFile" msgid="3400658466057744084">"Couldn\'t access the file."</string>
     <string name="httpErrorFileNotFound" msgid="5191433324871147386">"Couldn\'t find the requested file."</string>
     <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"Too many requests are being processed. Try again later."</string>
-    <string name="notification_title" msgid="5783748077084481121">"Sign-in error for <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
+    <string name="notification_title" msgid="5783748077084481121">"Signin error for <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
     <string name="contentServiceSync" msgid="2341041749565687871">"Sync"</string>
     <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"Can\'t sync"</string>
     <string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"Attempted to delete too many <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
     <string name="low_memory" product="tablet" msgid="5557552311566179924">"Tablet storage is full. Delete some files to free space."</string>
-    <string name="low_memory" product="watch" msgid="3479447988234030194">"Watch storage is full. Delete some files to free up space."</string>
-    <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV device storage is full. Delete some files to free up space."</string>
+    <string name="low_memory" product="watch" msgid="3479447988234030194">"Watch storage is full. Delete some files to free space."</string>
+    <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV device storage is full. Delete some files to free space."</string>
     <string name="low_memory" product="default" msgid="2539532364144025569">"Phone storage is full. Delete some files to free space."</string>
     <string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{Certificate authority installed}other{Certificate authorities installed}}"</string>
     <string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"By an unknown third party"</string>
@@ -187,16 +190,16 @@
     <string name="network_logging_notification_title" msgid="554983187553845004">"Device is managed"</string>
     <string name="network_logging_notification_text" msgid="1327373071132562512">"Your organization manages this device and may monitor network traffic. Tap for details."</string>
     <string name="location_changed_notification_title" msgid="3620158742816699316">"Apps can access your location"</string>
-    <string name="location_changed_notification_text" msgid="7158423339982706912">"Contact your IT admin to find out more"</string>
-    <string name="geofencing_service" msgid="3826902410740315456">"Geofencing service"</string>
+    <string name="location_changed_notification_text" msgid="7158423339982706912">"Contact your IT admin to learn more"</string>
+    <string name="geofencing_service" msgid="3826902410740315456">"Geofencing Service"</string>
     <string name="country_detector" msgid="7023275114706088854">"Country Detector"</string>
     <string name="location_service" msgid="2439187616018455546">"Location Service"</string>
-    <string name="gnss_service" msgid="8907781262179951385">"GNSS service"</string>
+    <string name="gnss_service" msgid="8907781262179951385">"GNSS Service"</string>
     <string name="sensor_notification_service" msgid="7474531979178682676">"Sensor Notification Service"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight Service"</string>
-    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS time update service"</string>
-    <string name="device_policy_manager_service" msgid="5085762851388850332">"Device Policy manager service"</string>
-    <string name="music_recognition_manager_service" msgid="7481956037950276359">"Music recognition manager service"</string>
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS Time Update Service"</string>
+    <string name="device_policy_manager_service" msgid="5085762851388850332">"Device Policy Manager Service"</string>
+    <string name="music_recognition_manager_service" msgid="7481956037950276359">"Music Recognition Manager Service"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Your device will be erased"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"The admin app can\'t be used. Your device will now be erased.\n\nIf you have questions, contact your organization\'s admin."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Printing disabled by <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -229,9 +232,9 @@
     <string name="shutdown_confirm" product="default" msgid="136816458966692315">"Your phone will shut down."</string>
     <string name="shutdown_confirm_question" msgid="796151167261608447">"Do you want to shut down?"</string>
     <string name="reboot_safemode_title" msgid="5853949122655346734">"Reboot to safe mode"</string>
-    <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Do you want to reboot into safe mode? This will disable all third-party applications that you have installed. They will be restored when you reboot again."</string>
+    <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Do you want to reboot into safe mode? This will disable all third party applications you have installed. They will be restored when you reboot again."</string>
     <string name="recent_tasks_title" msgid="8183172372995396653">"Recent"</string>
-    <string name="no_recent_tasks" msgid="9063946524312275906">"No recent apps"</string>
+    <string name="no_recent_tasks" msgid="9063946524312275906">"No recent apps."</string>
     <string name="global_actions" product="tablet" msgid="4412132498517933867">"Tablet options"</string>
     <string name="global_actions" product="tv" msgid="3871763739487450369">"Android TV options"</string>
     <string name="global_actions" product="default" msgid="6410072189971495460">"Phone options"</string>
@@ -244,9 +247,9 @@
     <string name="global_action_logout" msgid="6093581310002476511">"End session"</string>
     <string name="global_action_screenshot" msgid="2610053466156478564">"Screenshot"</string>
     <string name="bugreport_title" msgid="8549990811777373050">"Bug report"</string>
-    <string name="bugreport_message" msgid="5212529146119624326">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
+    <string name="bugreport_message" msgid="5212529146119624326">"This will collect information about your current device state, to send as an e-mail message. It will take a little time from starting the bug report until it is ready to be sent; please be patient."</string>
     <string name="bugreport_option_interactive_title" msgid="7968287837902871289">"Interactive report"</string>
-    <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Use this under most circumstances. It allows you to track progress of the report, enter more details about the problem and take screenshots. It might omit some less-used sections that take a long time to report."</string>
+    <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Use this under most circumstances. It allows you to track progress of the report, enter more details about the problem, and take screenshots. It might omit some less-used sections that take a long time to report."</string>
     <string name="bugreport_option_full_title" msgid="7681035745950045690">"Full report"</string>
     <string name="bugreport_option_full_summary" msgid="1975130009258435885">"Use this option for minimal system interference when your device is unresponsive or too slow, or when you need all report sections. Does not allow you to enter more details or take additional screenshots."</string>
     <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Taking screenshot for bug report in # second.}other{Taking screenshot for bug report in # seconds.}}"</string>
@@ -314,7 +317,7 @@
     <string name="permgrouplab_nearby_devices" msgid="5529147543651181991">"Nearby devices"</string>
     <string name="permgroupdesc_nearby_devices" msgid="3213561597116913508">"discover and connect to nearby devices"</string>
     <string name="permgrouplab_calllog" msgid="7926834372073550288">"Call logs"</string>
-    <string name="permgroupdesc_calllog" msgid="2026996642917801803">"read and write phone call logs"</string>
+    <string name="permgroupdesc_calllog" msgid="2026996642917801803">"read and write phone call log"</string>
     <string name="permgrouplab_phone" msgid="570318944091926620">"Phone"</string>
     <string name="permgroupdesc_phone" msgid="270048070781478204">"make and manage phone calls"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"Body sensors"</string>
@@ -330,7 +333,7 @@
     <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Control display magnification"</string>
     <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Control the display\'s zoom level and positioning."</string>
     <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Perform gestures"</string>
-    <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Can tap, swipe, pinch and perform other gestures."</string>
+    <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Can tap, swipe, pinch, and perform other gestures."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingerprint gestures"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Can capture gestures performed on the device\'s fingerprint sensor."</string>
     <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Take screenshot"</string>
@@ -343,24 +346,24 @@
     <string name="permdesc_expandStatusBar" msgid="7180756900448498536">"Allows the app to expand or collapse the status bar."</string>
     <string name="permlab_fullScreenIntent" msgid="4310888199502509104">"display notifications as full screen activities on a locked device"</string>
     <string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"Allows the app to display notifications as full screen activities on a locked device"</string>
-    <string name="permlab_install_shortcut" msgid="7451554307502256221">"Install shortcuts"</string>
-    <string name="permdesc_install_shortcut" msgid="4476328467240212503">"Allows an application to add Home screen shortcuts without user intervention."</string>
+    <string name="permlab_install_shortcut" msgid="7451554307502256221">"install shortcuts"</string>
+    <string name="permdesc_install_shortcut" msgid="4476328467240212503">"Allows an application to add Homescreen shortcuts without user intervention."</string>
     <string name="permlab_uninstall_shortcut" msgid="295263654781900390">"uninstall shortcuts"</string>
-    <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Allows the application to remove Home screen shortcuts without user intervention."</string>
+    <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Allows the application to remove Homescreen shortcuts without user intervention."</string>
     <string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"reroute outgoing calls"</string>
-    <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"Allows the app to see the number being dialled during an outgoing call with the option to redirect the call to a different number or abort the call altogether."</string>
+    <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"Allows the app to see the number being dialed during an outgoing call with the option to redirect the call to a different number or abort the call altogether."</string>
     <string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"answer phone calls"</string>
     <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"Allows the app to answer an incoming phone call."</string>
     <string name="permlab_receiveSms" msgid="505961632050451881">"receive text messages (SMS)"</string>
-    <string name="permdesc_receiveSms" msgid="1797345626687832285">"Allows the app to receive and process SMS messages. This means that the app could monitor or delete messages sent to your device without showing them to you."</string>
+    <string name="permdesc_receiveSms" msgid="1797345626687832285">"Allows the app to receive and process SMS messages. This means the app could monitor or delete messages sent to your device without showing them to you."</string>
     <string name="permlab_receiveMms" msgid="4000650116674380275">"receive text messages (MMS)"</string>
-    <string name="permdesc_receiveMms" msgid="958102423732219710">"Allows the app to receive and process MMS messages. This means that the app could monitor or delete messages sent to your device without showing them to you."</string>
+    <string name="permdesc_receiveMms" msgid="958102423732219710">"Allows the app to receive and process MMS messages. This means the app could monitor or delete messages sent to your device without showing them to you."</string>
     <string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"Forward cell broadcast messages"</string>
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Allows the app to bind to the cell broadcast module in order to forward cell broadcast messages as they are received. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency cell broadcast is received."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Manage ongoing calls"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Allows an app to see details about ongoing calls on your device and to control these calls."</string>
-    <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"read mobile broadcast messages"</string>
-    <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Allows the app to read mobile broadcast messages received by your device. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency mobile broadcast is received."</string>
+    <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"read cell broadcast messages"</string>
+    <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Allows the app to read cell broadcast messages received by your device. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency cell broadcast is received."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"read subscribed feeds"</string>
     <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"Allows the app to get details about the currently synced feeds."</string>
     <string name="permlab_sendSms" msgid="7757368721742014252">"send and view SMS messages"</string>
@@ -375,7 +378,7 @@
     <string name="permdesc_getTasks" msgid="7388138607018233726">"Allows the app to retrieve information about currently and recently running tasks. This may allow the app to discover information about which applications are used on the device."</string>
     <string name="permlab_manageProfileAndDeviceOwners" msgid="639849495253987493">"manage profile and device owners"</string>
     <string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"Allows apps to set the profile owners and the device owner."</string>
-    <string name="permlab_reorderTasks" msgid="7598562301992923804">"re-order running apps"</string>
+    <string name="permlab_reorderTasks" msgid="7598562301992923804">"reorder running apps"</string>
     <string name="permdesc_reorderTasks" msgid="8796089937352344183">"Allows the app to move tasks to the foreground and background. The app may do this without your input."</string>
     <string name="permlab_enableCarMode" msgid="893019409519325311">"enable car mode"</string>
     <string name="permdesc_enableCarMode" msgid="56419168820473508">"Allows the app to enable the car mode."</string>
@@ -388,13 +391,37 @@
     <string name="permlab_useDataInBackground" msgid="783415807623038947">"use data in the background"</string>
     <string name="permdesc_useDataInBackground" msgid="1230753883865891987">"This app can use data in the background. This may increase data usage."</string>
     <string name="permlab_persistentActivity" msgid="464970041740567970">"make app always run"</string>
-    <string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps, slowing down the tablet."</string>
-    <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps slowing down your Android TV device."</string>
-    <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps, slowing down the phone."</string>
+    <string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the tablet."</string>
+    <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down your Android TV device."</string>
+    <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the phone."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"run foreground service"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Allows the app to make use of foreground services."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"run foreground service with the type \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Allows the app to make use of foreground services with the type \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"run foreground service with the type \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Allows the app to make use of foreground services with the type \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"run foreground service with the type \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Allows the app to make use of foreground services with the type \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"run foreground service with the type \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Allows the app to make use of foreground services with the type \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"run foreground service with the type \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Allows the app to make use of foreground services with the type \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"run foreground service with the type \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Allows the app to make use of foreground services with the type \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"run foreground service with the type \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Allows the app to make use of foreground services with the type \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"run foreground service with the type \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Allows the app to make use of foreground services with the type \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"run foreground service with the type \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Allows the app to make use of foreground services with the type \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"run foreground service with the type \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Allows the app to make use of foreground services with the type \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"run foreground service with the type \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Allows the app to make use of foreground services with the type \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"run foreground service with the type \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Allows the app to make use of foreground services with the type \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
-    <string name="permdesc_getPackageSize" msgid="742743530909966782">"Allows the app to retrieve its code, data and cache sizes"</string>
+    <string name="permdesc_getPackageSize" msgid="742743530909966782">"Allows the app to retrieve its code, data, and cache sizes"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modify system settings"</string>
     <string name="permdesc_writeSettings" msgid="8293047411196067188">"Allows the app to modify the system\'s settings data. Malicious apps may corrupt your system\'s configuration."</string>
     <string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"run at startup"</string>
@@ -406,9 +433,9 @@
     <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make your Android TV device slow or unstable by causing it to use too much memory."</string>
     <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make the phone slow or unstable by causing it to use too much memory."</string>
     <string name="permlab_readContacts" msgid="8776395111787429099">"read your contacts"</string>
-    <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Allows the app to read data about your contacts stored on your tablet. Apps will also have access to the accounts on your tablet that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
-    <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Allows the app to read data about your contacts stored on your Android TV device. Apps will also have access to the accounts on your Android TV device that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
-    <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Allows the app to read data about your contacts stored on your phone. Apps will also have access to the accounts on your phone that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+    <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Allows the app to read data about your contacts stored on your tablet. Apps will also have access to the accounts on your tablet that have created contacts. This may include accounts created by apps you have installed. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+    <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Allows the app to read data about your contacts stored on your Android TV device. Apps will also have access to the accounts on your Android TV device that have created contacts. This may include accounts created by apps you have installed. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+    <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Allows the app to read data about your contacts stored on your phone. Apps will also have access to the accounts on your phone that have created contacts. This may include accounts created by apps you have installed. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
     <string name="permlab_writeContacts" msgid="8919430536404830430">"modify your contacts"</string>
     <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Allows the app to modify the data about your contacts stored on your tablet. This permission allows apps to delete contact data."</string>
     <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Allows the app to modify the data about your contacts stored on your Android TV device. This permission allows apps to delete contact data."</string>
@@ -417,26 +444,26 @@
     <string name="permdesc_readCallLog" msgid="8964770895425873433">"This app can read your call history."</string>
     <string name="permlab_writeCallLog" msgid="670292975137658895">"write call log"</string>
     <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Allows the app to modify your tablet\'s call log, including data about incoming and outgoing calls. Malicious apps may use this to erase or modify your call log."</string>
-    <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Allows the app to modify your Android TV device\'s call log, including data about incoming and outgoing calls. Malicious apps may use this to delete or modify your call log."</string>
+    <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Allows the app to modify your Android TV device\'s call log, including data about incoming and outgoing calls. Malicious apps may use this to erase or modify your call log."</string>
     <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Allows the app to modify your phone\'s call log, including data about incoming and outgoing calls. Malicious apps may use this to erase or modify your call log."</string>
     <string name="permlab_bodySensors" msgid="662918578601619569">"Access body sensor data, like heart rate, while in use"</string>
-    <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Allows the app to access body sensor data, such as heart rate, temperature and blood oxygen percentage, while the app is in use."</string>
+    <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in use."</string>
     <string name="permlab_bodySensors_background" msgid="4912560779957760446">"Access body sensor data, like heart rate, while in the background"</string>
-    <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Allows the app to access body sensor data, such as heart rate, temperature and blood oxygen percentage, while the app is in the background."</string>
+    <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in the background."</string>
     <string name="permlab_readCalendar" msgid="6408654259475396200">"Read calendar events and details"</string>
     <string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"This app can read all calendar events stored on your tablet and share or save your calendar data."</string>
     <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"This app can read all calendar events stored on your Android TV device and share or save your calendar data."</string>
     <string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"This app can read all calendar events stored on your phone and share or save your calendar data."</string>
-    <string name="permlab_writeCalendar" msgid="6422137308329578076">"add or modify calendar events and send emails to guests without owners\' knowledge"</string>
-    <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"This app can add, remove or change calendar events on your tablet. This app can send messages that may appear to come from calendar owners or change events without notifying their owners."</string>
-    <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"This app can add, remove or change calendar events on your Android TV device. This app can send messages that may appear to come from calendar owners or change events without notifying their owners."</string>
-    <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"This app can add, remove or change calendar events on your phone. This app can send messages that may appear to come from calendar owners or change events without notifying their owners."</string>
+    <string name="permlab_writeCalendar" msgid="6422137308329578076">"add or modify calendar events and send email to guests without owners\' knowledge"</string>
+    <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"This app can add, remove, or change calendar events on your tablet. This app can send messages that may appear to come from calendar owners, or change events without notifying their owners."</string>
+    <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"This app can add, remove, or change calendar events on your Android TV device. This app can send messages that may appear to come from calendar owners, or change events without notifying their owners."</string>
+    <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"This app can add, remove, or change calendar events on your phone. This app can send messages that may appear to come from calendar owners, or change events without notifying their owners."</string>
     <string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"access extra location provider commands"</string>
     <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Allows the app to access extra location provider commands. This may allow the app to interfere with the operation of the GPS or other location sources."</string>
     <string name="permlab_accessFineLocation" msgid="6426318438195622966">"access precise location only in the foreground"</string>
-    <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"This app can get your precise location from Location Services while the app is in use. Location Services for your device must be turned on for the app to get location. This may increase battery usage."</string>
+    <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"This app can get your precise location from location services while the app is in use. Location services for your device must be turned on for the app to get location. This may increase battery usage."</string>
     <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"access approximate location only in the foreground"</string>
-    <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"This app can get your approximate location from Location Services while the app is in use. Location Services for your device must be turned on for the app to get location."</string>
+    <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"This app can get your approximate location from location services while the app is in use. Location services for your device must be turned on for the app to get location."</string>
     <string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"access location in the background"</string>
     <string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"This app can access location at any time, even while the app is not in use."</string>
     <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string>
@@ -445,15 +472,17 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"This app can record audio using the microphone while the app is in use."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"record audio in the background"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"This app can record audio using the microphone at any time."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detect screen captures of app windows"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"This app will get notified when a screenshot is taken while the app is in use."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string>
-    <string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string>
-    <string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognise your physical activity."</string>
+    <string name="permlab_activityRecognition" msgid="1782303296053990884">"recognize physical activity"</string>
+    <string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognize your physical activity."</string>
     <string name="permlab_camera" msgid="6320282492904119413">"take pictures and videos"</string>
     <string name="permdesc_camera" msgid="5240801376168647151">"This app can take pictures and record videos using the camera while the app is in use."</string>
     <string name="permlab_backgroundCamera" msgid="7549917926079731681">"take pictures and videos in the background"</string>
     <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"This app can take pictures and record videos using the camera at any time."</string>
-    <string name="permlab_systemCamera" msgid="3642917457796210580">"Grant an application or service access to system cameras to take pictures and videos"</string>
+    <string name="permlab_systemCamera" msgid="3642917457796210580">"Allow an application or service access to system cameras to take pictures and videos"</string>
     <string name="permdesc_systemCamera" msgid="5938360914419175986">"This privileged or system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well"</string>
     <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Allow an application or service to receive callbacks about camera devices being opened or closed."</string>
     <string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"This app can receive callbacks when any camera device is being opened (by what application) or closed."</string>
@@ -465,14 +494,14 @@
     <string name="permlab_accessImsCallService" msgid="442192920714863782">"access IMS call service"</string>
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Allows the app to use the IMS service to make calls without your intervention."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"read phone status and identity"</string>
-    <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Allows the app to access the phone features of the device. This permission allows the app to determine the phone number and device IDs, whether a call is active and the remote number connected by a call."</string>
+    <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Allows the app to access the phone features of the device. This permission allows the app to determine the phone number and device IDs, whether a call is active, and the remote number connected by a call."</string>
     <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"read basic telephony status and identity"</string>
     <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Allows the app to access the basic telephony features of the device."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"route calls through the system"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Allows the app to route its calls through the system in order to improve the calling experience."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"see and control calls through the system."</string>
     <string name="permdesc_callCompanionApp" msgid="8474168926184156261">"Allows the app to see and control ongoing calls on the device. This includes information such as call numbers for calls and the state of the calls."</string>
-    <string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"exempt from audio recording restrictions"</string>
+    <string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"exempt from audio record restrictions"</string>
     <string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"Exempt the app from restrictions to record audio."</string>
     <string name="permlab_acceptHandover" msgid="2925523073573116523">"continue a call from another app"</string>
     <string name="permdesc_acceptHandovers" msgid="7129026180128626870">"Allows the app to continue a call which was started in another app."</string>
@@ -499,57 +528,57 @@
     <string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"Allows the app to change your Android TV device\'s time zone."</string>
     <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"Allows the app to change the phone\'s time zone."</string>
     <string name="permlab_getAccounts" msgid="5304317160463582791">"find accounts on the device"</string>
-    <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Allows the app to get the list of accounts known by the tablet. This may include any accounts created by applications that you have installed."</string>
-    <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Allows the app to get the list of accounts known by your Android TV device. This may include any accounts created by applications that you have installed."</string>
-    <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Allows the app to get the list of accounts known by the phone. This may include any accounts created by applications that you have installed."</string>
+    <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Allows the app to get the list of accounts known by the tablet. This may include any accounts created by applications you have installed."</string>
+    <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Allows the app to get the list of accounts known by your Android TV device. This may include any accounts created by applications you have installed."</string>
+    <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Allows the app to get the list of accounts known by the phone. This may include any accounts created by applications you have installed."</string>
     <string name="permlab_accessNetworkState" msgid="2349126720783633918">"view network connections"</string>
     <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"Allows the app to view information about network connections such as which networks exist and are connected."</string>
     <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"have full network access"</string>
-    <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"Allows the app to create network sockets and use customised network protocols. The browser and other applications provide means to send data to the Internet, so this permission is not required to send data to the Internet."</string>
+    <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"Allows the app to create network sockets and use custom network protocols. The browser and other applications provide means to send data to the internet, so this permission is not required to send data to the internet."</string>
     <string name="permlab_changeNetworkState" msgid="8945711637530425586">"change network connectivity"</string>
     <string name="permdesc_changeNetworkState" msgid="649341947816898736">"Allows the app to change the state of network connectivity."</string>
     <string name="permlab_changeTetherState" msgid="9079611809931863861">"change tethered connectivity"</string>
     <string name="permdesc_changeTetherState" msgid="3025129606422533085">"Allows the app to change the state of tethered network connectivity."</string>
     <string name="permlab_accessWifiState" msgid="5552488500317911052">"view Wi-Fi connections"</string>
     <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Allows the app to view information about Wi-Fi networking, such as whether Wi-Fi is enabled and name of connected Wi-Fi devices."</string>
-    <string name="permlab_changeWifiState" msgid="7947824109713181554">"Connect and disconnect from Wi-Fi"</string>
+    <string name="permlab_changeWifiState" msgid="7947824109713181554">"connect and disconnect from Wi-Fi"</string>
     <string name="permdesc_changeWifiState" msgid="7170350070554505384">"Allows the app to connect to and disconnect from Wi-Fi access points and to make changes to device configuration for Wi-Fi networks."</string>
     <string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"allow Wi-Fi Multicast reception"</string>
     <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Allows the app to receive packets sent to all devices on a Wi-Fi network using multicast addresses, not just your tablet. It uses more power than the non-multicast mode."</string>
     <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Allows the app to receive packets sent to all devices on a Wi-Fi network using multicast addresses, not just your Android TV device. It uses more power than the non-multicast mode."</string>
     <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Allows the app to receive packets sent to all devices on a Wi-Fi network using multicast addresses, not just your phone. It uses more power than the non-multicast mode."</string>
-    <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"Access Bluetooth settings"</string>
-    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Allows the app to configure the local Bluetooth tablet and to discover and pair with remote devices."</string>
-    <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Allows the app to configure Bluetooth on your Android TV device and to discover and pair with remote devices."</string>
-    <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Allows the app to configure the local Bluetooth phone and to discover and pair with remote devices."</string>
+    <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"access Bluetooth settings"</string>
+    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Allows the app to configure the local Bluetooth tablet, and to discover and pair with remote devices."</string>
+    <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Allows the app to configure Bluetooth on your Android TV device, and to discover and pair with remote devices."</string>
+    <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Allows the app to configure the local Bluetooth phone, and to discover and pair with remote devices."</string>
     <string name="permlab_accessWimaxState" msgid="7029563339012437434">"connect and disconnect from WiMAX"</string>
     <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"Allows the app to determine whether WiMAX is enabled and information about any WiMAX networks that are connected."</string>
     <string name="permlab_changeWimaxState" msgid="6223305780806267462">"change WiMAX state"</string>
     <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"Allows the app to connect the tablet to and disconnect the tablet from WiMAX networks."</string>
     <string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"Allows the app to connect your Android TV device to and disconnect your Android TV device from WiMAX networks."</string>
     <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"Allows the app to connect the phone to and disconnect the phone from WiMAX networks."</string>
-    <string name="permlab_bluetooth" msgid="586333280736937209">"Pair with Bluetooth devices"</string>
-    <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Allows the app to view the configuration of Bluetooth on the tablet and to make and accept connections with paired devices."</string>
-    <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Allows the app to view the configuration of Bluetooth on your Android TV device and to make and accept connections with paired devices."</string>
-    <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Allows the app to view the configuration of the Bluetooth on the phone and to make and accept connections with paired devices."</string>
+    <string name="permlab_bluetooth" msgid="586333280736937209">"pair with Bluetooth devices"</string>
+    <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Allows the app to view the configuration of Bluetooth on the tablet, and to make and accept connections with paired devices."</string>
+    <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Allows the app to view the configuration of Bluetooth on your Android TV device, and to make and accept connections with paired devices."</string>
+    <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Allows the app to view the configuration of the Bluetooth on the phone, and to make and accept connections with paired devices."</string>
     <string name="permlab_bluetooth_scan" msgid="5402587142833124594">"discover and pair nearby Bluetooth devices"</string>
     <string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"Allows the app to discover and pair nearby Bluetooth devices"</string>
     <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"connect to paired Bluetooth devices"</string>
     <string name="permdesc_bluetooth_connect" product="default" msgid="4546016548795544617">"Allows the app to connect to paired Bluetooth devices"</string>
     <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"advertise to nearby Bluetooth devices"</string>
     <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Allows the app to advertise to nearby Bluetooth devices"</string>
-    <string name="permlab_uwb_ranging" msgid="8141915781475770665">"determine relative position between nearby ultra-wideband devices"</string>
-    <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Allow the app to determine relative position between nearby ultra-wideband devices"</string>
+    <string name="permlab_uwb_ranging" msgid="8141915781475770665">"determine relative position between nearby Ultra-Wideband devices"</string>
+    <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Allow the app to determine relative position between nearby Ultra-Wideband devices"</string>
     <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"interact with nearby Wi‑Fi devices"</string>
-    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Allows the app to advertise, connect and determine the relative position of nearby Wi‑Fi devices"</string>
-    <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Preferred NFC payment service information"</string>
-    <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Allows the app to get preferred NFC payment service information, such as registered aids and route destination."</string>
-    <string name="permlab_nfc" msgid="1904455246837674977">"control Near-Field Communication"</string>
-    <string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards and readers."</string>
+    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Allows the app to advertise, connect, and determine the relative position of nearby Wi‑Fi devices"</string>
+    <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Preferred NFC Payment Service Information"</string>
+    <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Allows the app to get preferred nfc payment service information like registered aids and route destination."</string>
+    <string name="permlab_nfc" msgid="1904455246837674977">"control Near Field Communication"</string>
+    <string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards, and readers."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"disable your screen lock"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Allows the app to disable the keylock and any associated password security. For example, the phone disables the keylock when receiving an incoming phone call, then re-enables the keylock when the call is finished."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"request screen lock complexity"</string>
-    <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Allows the app to learn the screen lock complexity level (high, medium, low or none), which indicates the possible range of length and type of the screen lock. The app can also suggest to users that they update the screen lock to a certain level but users can freely ignore and navigate away. Note that the screen lock is not stored in plain text so the app does not know the exact password."</string>
+    <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Allows the app to learn the screen lock complexity level (high, medium, low or none), which indicates the possible range of length and type of the screen lock. The app can also suggest to users that they update the screen lock to a certain level but users can freely ignore and navigate away. Note that the screen lock is not stored in plaintext so the app does not know the exact password."</string>
     <string name="permlab_postNotification" msgid="4875401198597803658">"show notifications"</string>
     <string name="permdesc_postNotification" msgid="5974977162462877075">"Allows the app to show notifications"</string>
     <string name="permlab_turnScreenOn" msgid="219344053664171492">"turn on the screen"</string>
@@ -558,7 +587,7 @@
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"Allows the app to use biometric hardware for authentication"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"manage fingerprint hardware"</string>
     <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Allows the app to invoke methods to add and delete fingerprint templates for use."</string>
-    <string name="permlab_useFingerprint" msgid="1001421069766751922">"Use fingerprint hardware"</string>
+    <string name="permlab_useFingerprint" msgid="1001421069766751922">"use fingerprint hardware"</string>
     <string name="permdesc_useFingerprint" msgid="412463055059323742">"Allows the app to use fingerprint hardware for authentication"</string>
     <string name="permlab_audioWrite" msgid="8501705294265669405">"modify your music collection"</string>
     <string name="permdesc_audioWrite" msgid="8057399517013412431">"Allows the app to modify your music collection."</string>
@@ -570,19 +599,19 @@
     <string name="permdesc_mediaLocation" msgid="597912899423578138">"Allows the app to read locations from your media collection."</string>
     <string name="biometric_app_setting_name" msgid="3339209978734534457">"Use biometrics"</string>
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Use biometrics or screen lock"</string>
-    <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verify that it’s you"</string>
+    <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verify it’s you"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Use your biometric to continue"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Use your biometric or screen lock to continue"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
-    <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication cancelled"</string>
-    <string name="biometric_not_recognized" msgid="5106687642694635888">"Not recognised"</string>
-    <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentication cancelled"</string>
-    <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No pin, pattern or password set"</string>
-    <string name="biometric_error_generic" msgid="6784371929985434439">"Error while authenticating"</string>
+    <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication canceled"</string>
+    <string name="biometric_not_recognized" msgid="5106687642694635888">"Not recognized"</string>
+    <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentication canceled"</string>
+    <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No pin, pattern, or password set"</string>
+    <string name="biometric_error_generic" msgid="6784371929985434439">"Error authenticating"</string>
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Use screen lock"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Enter your screen lock to continue"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Press firmly on the sensor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognise fingerprint. Try again."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognize fingerprint. Try again."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Clean fingerprint sensor and try again"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Clean sensor and try again"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Press firmly on the sensor"</string>
@@ -594,16 +623,16 @@
     <string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"Change the position of your finger slightly each time"</string>
   <string-array name="fingerprint_acquired_vendor">
   </string-array>
-    <string name="fingerprint_error_not_match" msgid="4599441812893438961">"Fingerprint not recognised"</string>
-    <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"Fingerprint not recognised"</string>
+    <string name="fingerprint_error_not_match" msgid="4599441812893438961">"Fingerprint not recognized"</string>
+    <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"Fingerprint not recognized"</string>
     <string name="fingerprint_authenticated" msgid="2024862866860283100">"Fingerprint authenticated"</string>
     <string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"Face authenticated"</string>
-    <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Face authenticated. Please press confirm"</string>
+    <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Face authenticated, please press confirm"</string>
     <string name="fingerprint_error_hw_not_available" msgid="4571700896929561202">"Fingerprint hardware not available."</string>
     <string name="fingerprint_error_no_space" msgid="7285481581905967580">"Can’t set up fingerprint"</string>
     <string name="fingerprint_error_timeout" msgid="7361192266621252164">"Fingerprint setup timed out. Try again."</string>
-    <string name="fingerprint_error_canceled" msgid="540026881380070750">"Fingerprint operation cancelled."</string>
-    <string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"Fingerprint operation cancelled by user."</string>
+    <string name="fingerprint_error_canceled" msgid="540026881380070750">"Fingerprint operation canceled."</string>
+    <string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"Fingerprint operation canceled by user."</string>
     <string name="fingerprint_error_lockout" msgid="6626753679019351368">"Too many attempts. Use screen lock instead."</string>
     <string name="fingerprint_error_lockout_permanent" msgid="9060651300306264843">"Too many attempts. Use screen lock instead."</string>
     <string name="fingerprint_error_unable_to_process" msgid="2446280592818621224">"Can’t process fingerprint. Try again."</string>
@@ -635,7 +664,7 @@
     <string name="face_acquired_insufficient" msgid="6889245852748492218">"Can’t create your face model. Try again."</string>
     <string name="face_acquired_too_bright" msgid="8070756048978079164">"Too bright. Try gentler lighting."</string>
     <string name="face_acquired_too_dark" msgid="8539853432479385326">"Not enough light"</string>
-    <string name="face_acquired_too_close" msgid="4453646176196302462">"Move phone further away"</string>
+    <string name="face_acquired_too_close" msgid="4453646176196302462">"Move phone farther away"</string>
     <string name="face_acquired_too_far" msgid="2922278214231064859">"Move phone closer"</string>
     <string name="face_acquired_too_high" msgid="8278815780046368576">"Move phone higher"</string>
     <string name="face_acquired_too_low" msgid="4075391872960840081">"Move phone lower"</string>
@@ -645,7 +674,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Can’t see your face. Hold your phone at eye level."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Too much motion. Hold phone steady."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Please re-enroll your face."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognise face. Try again."</string>
+    <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognize face. Try again."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Change the position of your head slightly"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Look more directly at your phone"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Look more directly at your phone"</string>
@@ -664,8 +693,8 @@
     <string name="face_error_hw_not_available" msgid="5085202213036026288">"Can’t verify face. Hardware not available."</string>
     <string name="face_error_timeout" msgid="2598544068593889762">"Try Face Unlock again"</string>
     <string name="face_error_no_space" msgid="5649264057026021723">"Can’t store new face data. Delete an old one first."</string>
-    <string name="face_error_canceled" msgid="2164434737103802131">"Face operation cancelled."</string>
-    <string name="face_error_user_canceled" msgid="5766472033202928373">"Face Unlock cancelled by user"</string>
+    <string name="face_error_canceled" msgid="2164434737103802131">"Face operation canceled."</string>
+    <string name="face_error_user_canceled" msgid="5766472033202928373">"Face Unlock canceled by user"</string>
     <string name="face_error_lockout" msgid="7864408714994529437">"Too many attempts. Try again later."</string>
     <string name="face_error_lockout_permanent" msgid="3277134834042995260">"Too many attempts. Face Unlock disabled."</string>
     <string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"Too many attempts. Enter screen lock instead."</string>
@@ -685,7 +714,7 @@
     <string name="permlab_readSyncSettings" msgid="6250532864893156277">"read sync settings"</string>
     <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Allows the app to read the sync settings for an account. For example, this can determine whether the People app is synced with an account."</string>
     <string name="permlab_writeSyncSettings" msgid="6583154300780427399">"toggle sync on and off"</string>
-    <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Allows an app to modify the sync settings for an account. For example, this can be used to enable syncing of the People app with an account."</string>
+    <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Allows an app to modify the sync settings for an account. For example, this can be used to enable sync of the People app with an account."</string>
     <string name="permlab_readSyncStats" msgid="3747407238320105332">"read sync statistics"</string>
     <string name="permdesc_readSyncStats" msgid="3867809926567379434">"Allows an app to read the sync stats for an account, including the history of sync events and how much data is synced."</string>
     <string name="permlab_sdcardRead" msgid="5791467020950064920">"read the contents of your shared storage"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Allows the app to read video files from your shared storage."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"read image files from shared storage"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Allows the app to read image files from your shared storage."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"read user selected image and video files from shared storage"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Allows the app to read image and video files that you select from your shared storage."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"modify or delete the contents of your shared storage"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Allows the app to write the contents of your shared storage."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"make/receive SIP calls"</string>
@@ -755,12 +786,12 @@
     <string name="policylab_limitPassword" msgid="4851829918814422199">"Set password rules"</string>
     <string name="policydesc_limitPassword" msgid="4105491021115793793">"Control the length and the characters allowed in screen lock passwords and PINs."</string>
     <string name="policylab_watchLogin" msgid="7599669460083719504">"Monitor screen unlock attempts"</string>
-    <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"Monitor the number of incorrect passwords typed when unlocking the screen and lock the tablet or erase all the tablet\'s data if too many incorrect passwords are typed."</string>
-    <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock your Android TV device or delete all of your Android TV device\'s data if too many incorrect passwords are typed."</string>
-    <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the infotainment system or erase all the infotainment system\'s data if too many incorrect passwords are typed."</string>
-    <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Monitor the number of incorrect passwords typed when unlocking the screen and lock the phone or erase all the phone\'s data if too many incorrect passwords are typed."</string>
+    <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the tablet or erase all the tablet\'s data if too many incorrect passwords are typed."</string>
+    <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock your Android TV device or erase all your Android TV device\'s data if too many incorrect passwords are typed."</string>
+    <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Monitor the number of incorrect passwords typed. when unlocking the screen, and lock the infotainment system or erase all the infotainment system\'s data if too many incorrect passwords are typed."</string>
+    <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Monitor the number of incorrect passwords typed. when unlocking the screen, and lock the phone or erase all the phone\'s data if too many incorrect passwords are typed."</string>
     <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the tablet or erase all this user\'s data if too many incorrect passwords are typed."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock your Android TV device or delete all of this user\'s data if too many incorrect passwords are typed."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock your Android TV device or erase all this user\'s data if too many incorrect passwords are typed."</string>
     <string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the infotainment system or erase all this profile\'s data if too many incorrect passwords are typed."</string>
     <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the phone or erase all this user\'s data if too many incorrect passwords are typed."</string>
     <string name="policylab_resetPassword" msgid="214556238645096520">"Change the screen lock"</string>
@@ -769,19 +800,19 @@
     <string name="policydesc_forceLock" msgid="1008844760853899693">"Control how and when the screen locks."</string>
     <string name="policylab_wipeData" msgid="1359485247727537311">"Erase all data"</string>
     <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"Erase the tablet\'s data without warning by performing a factory data reset."</string>
-    <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"Delete your Android TV device\'s data without warning by performing a factory data reset."</string>
+    <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"Erase your Android TV device\'s data without warning by performing a factory data reset."</string>
     <string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"Erase the infotainment system\'s data without warning by performing a factory data reset."</string>
     <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"Erase the phone\'s data without warning by performing a factory data reset."</string>
     <string name="policylab_wipeData_secondaryUser" product="automotive" msgid="115034358520328373">"Erase profile data"</string>
     <string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Erase user data"</string>
     <string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"Erase this user\'s data on this tablet without warning."</string>
-    <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Delete this user\'s data on this Android TV device without warning."</string>
+    <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Erase this user\'s data on this Android TV device without warning."</string>
     <string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"Erase this profile\'s data on this infotainment system without warning."</string>
     <string name="policydesc_wipeData_secondaryUser" product="default" msgid="2788325512167208654">"Erase this user\'s data on this phone without warning."</string>
     <string name="policylab_setGlobalProxy" msgid="215332221188670221">"Set the device global proxy"</string>
     <string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"Set the device global proxy to be used while policy is enabled. Only the device owner can set the global proxy."</string>
-    <string name="policylab_expirePassword" msgid="6015404400532459169">"Set screen lock password expiry"</string>
-    <string name="policydesc_expirePassword" msgid="9136524319325960675">"Change how frequently the screen lock password, PIN or pattern must be changed."</string>
+    <string name="policylab_expirePassword" msgid="6015404400532459169">"Set screen lock password expiration"</string>
+    <string name="policydesc_expirePassword" msgid="9136524319325960675">"Change how frequently the screen lock password, PIN, or pattern must be changed."</string>
     <string name="policylab_encryptedStorage" msgid="9012936958126670110">"Set storage encryption"</string>
     <string name="policydesc_encryptedStorage" msgid="1102516950740375617">"Require that stored app data be encrypted."</string>
     <string name="policylab_disableCamera" msgid="5749486347810162018">"Disable cameras"</string>
@@ -792,8 +823,8 @@
     <item msgid="8996339953292723951">"Home"</item>
     <item msgid="7740243458912727194">"Mobile"</item>
     <item msgid="8526146065496663766">"Work"</item>
-    <item msgid="8150904584178569699">"Work fax"</item>
-    <item msgid="4537253139152229577">"Home fax"</item>
+    <item msgid="8150904584178569699">"Work Fax"</item>
+    <item msgid="4537253139152229577">"Home Fax"</item>
     <item msgid="6751245029698664340">"Pager"</item>
     <item msgid="1692790665884224905">"Other"</item>
     <item msgid="6216981255272016212">"Custom"</item>
@@ -835,8 +866,8 @@
     <string name="phoneTypeHome" msgid="3880132427643623588">"Home"</string>
     <string name="phoneTypeMobile" msgid="1178852541462086735">"Mobile"</string>
     <string name="phoneTypeWork" msgid="6604967163358864607">"Work"</string>
-    <string name="phoneTypeFaxWork" msgid="6757519896109439123">"Work fax"</string>
-    <string name="phoneTypeFaxHome" msgid="6678559953115904345">"Home fax"</string>
+    <string name="phoneTypeFaxWork" msgid="6757519896109439123">"Work Fax"</string>
+    <string name="phoneTypeFaxHome" msgid="6678559953115904345">"Home Fax"</string>
     <string name="phoneTypePager" msgid="576402072263522767">"Pager"</string>
     <string name="phoneTypeOther" msgid="6918196243648754715">"Other"</string>
     <string name="phoneTypeCallback" msgid="3455781500844157767">"Callback"</string>
@@ -847,7 +878,7 @@
     <string name="phoneTypeOtherFax" msgid="3037145630364770357">"Other Fax"</string>
     <string name="phoneTypeRadio" msgid="2637819130239264771">"Radio"</string>
     <string name="phoneTypeTelex" msgid="2558783611711876562">"Telex"</string>
-    <string name="phoneTypeTtyTdd" msgid="532038552105328779">"TTY/TDD"</string>
+    <string name="phoneTypeTtyTdd" msgid="532038552105328779">"TTY TDD"</string>
     <string name="phoneTypeWorkMobile" msgid="7522314392003565121">"Work Mobile"</string>
     <string name="phoneTypeWorkPager" msgid="3748332310638505234">"Work Pager"</string>
     <string name="phoneTypeAssistant" msgid="757550783842231039">"Assistant"</string>
@@ -878,7 +909,7 @@
     <string name="imProtocolGoogleTalk" msgid="9194016024343166782">"Hangouts"</string>
     <string name="imProtocolIcq" msgid="2410325380427389521">"ICQ"</string>
     <string name="imProtocolJabber" msgid="7919269388889582015">"Jabber"</string>
-    <string name="imProtocolNetMeeting" msgid="4985002408136148256">"Net Meeting"</string>
+    <string name="imProtocolNetMeeting" msgid="4985002408136148256">"NetMeeting"</string>
     <string name="orgTypeWork" msgid="8684458700669564172">"Work"</string>
     <string name="orgTypeOther" msgid="5450675258408005553">"Other"</string>
     <string name="orgTypeCustom" msgid="1126322047677329218">"Custom"</string>
@@ -905,12 +936,12 @@
     <string name="keyguard_password_enter_pin_code" msgid="6401406801060956153">"Type PIN code"</string>
     <string name="keyguard_password_enter_puk_code" msgid="3112256684547584093">"Type PUK and new PIN code"</string>
     <string name="keyguard_password_enter_puk_prompt" msgid="2825313071899938305">"PUK code"</string>
-    <string name="keyguard_password_enter_pin_prompt" msgid="5505434724229581207">"New PIN Code"</string>
+    <string name="keyguard_password_enter_pin_prompt" msgid="5505434724229581207">"New PIN code"</string>
     <string name="keyguard_password_entry_touch_hint" msgid="4032288032993261520"><font size="17">"Tap to type password"</font></string>
     <string name="keyguard_password_enter_password_code" msgid="2751130557661643482">"Type password to unlock"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"Type PIN to unlock"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"Incorrect PIN code."</string>
-    <string name="keyguard_label_text" msgid="3841953694564168384">"To unlock, press Menu, then 0."</string>
+    <string name="keyguard_label_text" msgid="3841953694564168384">"To unlock, press Menu then 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Emergency number"</string>
     <string name="lockscreen_carrier_default" msgid="6192313772955399160">"No service"</string>
     <string name="lockscreen_screen_locked" msgid="7364905540516041817">"Screen locked."</string>
@@ -938,7 +969,7 @@
     <string name="lockscreen_transport_play_description" msgid="106868788691652733">"Play"</string>
     <string name="lockscreen_transport_stop_description" msgid="1449552232598355348">"Stop"</string>
     <string name="lockscreen_transport_rew_description" msgid="7680106856221622779">"Rewind"</string>
-    <string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Fast-forward"</string>
+    <string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Fast forward"</string>
     <string name="emergency_calls_only" msgid="3057351206678279851">"Emergency calls only"</string>
     <string name="lockscreen_network_locked_message" msgid="2814046965899249635">"Network locked"</string>
     <string name="lockscreen_sim_puk_locked_message" msgid="6618356415831082174">"SIM card is PUK-locked."</string>
@@ -948,9 +979,9 @@
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
     <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"You have incorrectly typed your password <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
     <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"You have incorrectly typed your PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
-    <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your tablet using your Google sign-in.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
-    <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"You have drawn your unlock pattern incorrectly <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your Android TV device using your Google sign-in.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
-    <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"You have drawn your unlock pattern incorrectly <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using your Google sign-in.\n\n Please try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your tablet using your Google signin.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your Android TV device using your Google signin.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using your Google signin.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
     <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"You have incorrectly attempted to unlock the tablet <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, the tablet will be reset to factory default and all user data will be lost."</string>
     <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"You have incorrectly attempted to unlock your Android TV device <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, your Android TV device will be reset to factory default and all user data will be lost."</string>
     <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"You have incorrectly attempted to unlock the phone <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, the phone will be reset to factory default and all user data will be lost."</string>
@@ -958,15 +989,15 @@
     <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"You have incorrectly attempted to unlock your Android TV device <xliff:g id="NUMBER">%d</xliff:g> times. Your Android TV device will now be reset to factory default."</string>
     <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"You have incorrectly attempted to unlock the phone <xliff:g id="NUMBER">%d</xliff:g> times. The phone will now be reset to factory default."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"Try again in <xliff:g id="NUMBER">%d</xliff:g> seconds."</string>
-    <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Forgotten pattern?"</string>
+    <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Forgot pattern?"</string>
     <string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"Account unlock"</string>
     <string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"Too many pattern attempts"</string>
-    <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"To unlock, sign in with your Google Account."</string>
+    <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"To unlock, sign in with your Google account."</string>
     <string name="lockscreen_glogin_username_hint" msgid="6916101478673157045">"Username (email)"</string>
     <string name="lockscreen_glogin_password_hint" msgid="3031027901286812848">"Password"</string>
     <string name="lockscreen_glogin_submit_button" msgid="3590556636347843733">"Sign in"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="4369219936865697679">"Invalid username or password."</string>
-    <string name="lockscreen_glogin_account_recovery_hint" msgid="1683405808525090649">"Forgot your username or password?\nVisit "<b>"google.co.uk/accounts/recovery"</b>"."</string>
+    <string name="lockscreen_glogin_account_recovery_hint" msgid="1683405808525090649">"Forgot your username or password?\nVisit "<b>"google.com/accounts/recovery"</b>"."</string>
     <string name="lockscreen_glogin_checking_password" msgid="2607271802803381645">"Checking…"</string>
     <string name="lockscreen_unlock_label" msgid="4648257878373307582">"Unlock"</string>
     <string name="lockscreen_sound_on_label" msgid="1660281470535492430">"Sound on"</string>
@@ -978,7 +1009,7 @@
     <string name="lockscreen_access_pattern_detected" msgid="3931150554035194012">"Pattern completed"</string>
     <string name="lockscreen_access_pattern_area" msgid="1288780416685002841">"Pattern area."</string>
     <string name="keyguard_accessibility_widget_changed" msgid="7298011259508200234">"%1$s. Widget %2$d of %3$d."</string>
-    <string name="keyguard_accessibility_add_widget" msgid="8245795023551343672">"Add widget"</string>
+    <string name="keyguard_accessibility_add_widget" msgid="8245795023551343672">"Add widget."</string>
     <string name="keyguard_accessibility_widget_empty_slot" msgid="544239307077644480">"Empty"</string>
     <string name="keyguard_accessibility_unlock_area_expanded" msgid="7768634718706488951">"Unlock area expanded."</string>
     <string name="keyguard_accessibility_unlock_area_collapsed" msgid="4729922043778400434">"Unlock area collapsed."</string>
@@ -995,8 +1026,8 @@
     <string name="keyguard_accessibility_pattern_unlock" msgid="8669128146589233293">"Pattern unlock."</string>
     <string name="keyguard_accessibility_face_unlock" msgid="4533832120787386728">"Face Unlock."</string>
     <string name="keyguard_accessibility_pin_unlock" msgid="4020864007967340068">"Pin unlock."</string>
-    <string name="keyguard_accessibility_sim_pin_unlock" msgid="4895939120871890557">"SIM PIN unlock."</string>
-    <string name="keyguard_accessibility_sim_puk_unlock" msgid="3459003464041899101">"SIM PUK unlock."</string>
+    <string name="keyguard_accessibility_sim_pin_unlock" msgid="4895939120871890557">"Sim Pin unlock."</string>
+    <string name="keyguard_accessibility_sim_puk_unlock" msgid="3459003464041899101">"Sim Puk unlock."</string>
     <string name="keyguard_accessibility_password_unlock" msgid="6130186108581153265">"Password unlock."</string>
     <string name="keyguard_accessibility_pattern_area" msgid="1419570880512350689">"Pattern area."</string>
     <string name="keyguard_accessibility_slide_area" msgid="4331399051142520176">"Slide area."</string>
@@ -1018,18 +1049,18 @@
     <string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"Stay on this Page"</string>
     <string name="js_dialog_before_unload" msgid="7213364985774778744">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nAre you sure you want to navigate away from this page?"</string>
     <string name="save_password_label" msgid="9161712335355510035">"Confirm"</string>
-    <string name="double_tap_toast" msgid="7065519579174882778">"Tip: double-tap to zoom in and out."</string>
-    <string name="autofill_this_form" msgid="3187132440451621492">"Auto-fill"</string>
-    <string name="setup_autofill" msgid="5431369130866618567">"Set up Auto-fill"</string>
+    <string name="double_tap_toast" msgid="7065519579174882778">"Tip: Double-tap to zoom in and out."</string>
+    <string name="autofill_this_form" msgid="3187132440451621492">"Autofill"</string>
+    <string name="setup_autofill" msgid="5431369130866618567">"Set up Autofill"</string>
     <string name="autofill_window_title" msgid="4379134104008111961">"Autofill with <xliff:g id="SERVICENAME">%1$s</xliff:g>"</string>
     <string name="autofill_address_name_separator" msgid="8190155636149596125">" "</string>
     <string name="autofill_address_summary_name_format" msgid="3402882515222673691">"$1$2$3"</string>
     <string name="autofill_address_summary_separator" msgid="760522655085707045">", "</string>
     <string name="autofill_address_summary_format" msgid="8417010069362125194">"$1$2$3"</string>
     <string name="autofill_province" msgid="3676846437741893159">"Province"</string>
-    <string name="autofill_postal_code" msgid="7034789388968295591">"Postcode"</string>
+    <string name="autofill_postal_code" msgid="7034789388968295591">"Postal code"</string>
     <string name="autofill_state" msgid="3341725337190434069">"State"</string>
-    <string name="autofill_zip_code" msgid="1315503730274962450">"Zip code"</string>
+    <string name="autofill_zip_code" msgid="1315503730274962450">"ZIP code"</string>
     <string name="autofill_county" msgid="7781382735643492173">"County"</string>
     <string name="autofill_island" msgid="5367139008536593734">"Island"</string>
     <string name="autofill_district" msgid="6428712062213557327">"District"</string>
@@ -1041,15 +1072,15 @@
     <string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"read your Web bookmarks and history"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"Allows the app to read the history of all URLs that the Browser has visited, and all of the Browser\'s bookmarks. Note: this permission may not be enforced by third-party browsers or other applications with web browsing capabilities."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"write web bookmarks and history"</string>
-    <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Allows the app to modify the Browser\'s history or bookmarks stored on your tablet. This may allow the app to delete or modify Browser data. Note: this permission may not be enforced by third-party browsers or other applications with web browsing capabilities."</string>
-    <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Allows the app to modify the browser\'s history or bookmarks stored on your Android TV device. This may allow the app to delete or modify browser data. Note: This permission may not be enforced by third-party browsers or other applications with web browsing capabilities."</string>
-    <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Allows the app to modify the Browser\'s history or bookmarks stored on your phone. This may allow the app to delete or modify Browser data. Note: this permission may not be enforced by third-party browsers or other applications with web browsing capabilities."</string>
+    <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Allows the app to modify the Browser\'s history or bookmarks stored on your tablet. This may allow the app to erase or modify Browser data. Note: this permission may note be enforced by third-party browsers or other applications with web browsing capabilities."</string>
+    <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Allows the app to modify the Browser\'s history or bookmarks stored on your Android TV device. This may allow the app to erase or modify Browser data. Note: this permission may note be enforced by third-party browsers or other applications with web browsing capabilities."</string>
+    <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Allows the app to modify the Browser\'s history or bookmarks stored on your phone. This may allow the app to erase or modify Browser data. Note: this permission may note be enforced by third-party browsers or other applications with web browsing capabilities."</string>
     <string name="permlab_setAlarm" msgid="1158001610254173567">"set an alarm"</string>
     <string name="permdesc_setAlarm" msgid="2185033720060109640">"Allows the app to set an alarm in an installed alarm clock app. Some alarm clock apps may not implement this feature."</string>
     <string name="permlab_addVoicemail" msgid="4770245808840814471">"add voicemail"</string>
     <string name="permdesc_addVoicemail" msgid="5470312139820074324">"Allows the app to add messages to your voicemail inbox."</string>
-    <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"Modify Browser geo-location permissions"</string>
-    <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Allows the app to modify the Browser\'s geo-location permissions. Malicious apps may use this to allow sending location information to arbitrary websites."</string>
+    <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"modify Browser geolocation permissions"</string>
+    <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Allows the app to modify the Browser\'s geolocation permissions. Malicious apps may use this to allow sending location information to arbitrary web sites."</string>
     <string name="save_password_message" msgid="2146409467245462965">"Do you want the browser to remember this password?"</string>
     <string name="save_password_notnow" msgid="2878327088951240061">"Not now"</string>
     <string name="save_password_remember" msgid="6490888932657708341">"Remember"</string>
@@ -1058,9 +1089,9 @@
     <string name="text_copied" msgid="2531420577879738860">"Text copied to clipboard."</string>
     <string name="pasted_from_app" msgid="5627698450808256545">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted from <xliff:g id="SOURCE_APP_NAME">%2$s</xliff:g>"</string>
     <string name="pasted_from_clipboard" msgid="7355790625710831847">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted from your clipboard"</string>
-    <string name="pasted_text" msgid="4298871641549173733">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted text that you copied"</string>
-    <string name="pasted_image" msgid="4729097394781491022">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted an image that you copied"</string>
-    <string name="pasted_content" msgid="646276353060777131">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted content that you copied"</string>
+    <string name="pasted_text" msgid="4298871641549173733">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted text you copied"</string>
+    <string name="pasted_image" msgid="4729097394781491022">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted an image you copied"</string>
+    <string name="pasted_content" msgid="646276353060777131">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted content you copied"</string>
     <string name="more_item_label" msgid="7419249600215749115">"More"</string>
     <string name="prepend_shortcut_label" msgid="1743716737502867951">"Menu+"</string>
     <string name="menu_meta_shortcut_label" msgid="1623390163674762478">"Meta+"</string>
@@ -1089,7 +1120,7 @@
     <string name="older" msgid="1645159827884647400">"Older"</string>
     <string name="preposition_for_date" msgid="2780767868832729599">"on <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="preposition_for_time" msgid="4336835286453822053">"at <xliff:g id="TIME">%s</xliff:g>"</string>
-    <string name="preposition_for_year" msgid="3149809685340130039">"in<xliff:g id="YEAR">%s</xliff:g>"</string>
+    <string name="preposition_for_year" msgid="3149809685340130039">"in <xliff:g id="YEAR">%s</xliff:g>"</string>
     <string name="day" msgid="8394717255950176156">"day"</string>
     <string name="days" msgid="4570879797423034973">"days"</string>
     <string name="hour" msgid="7796325297097314653">"hour"</string>
@@ -1110,7 +1141,7 @@
     <string name="duration_minutes_shortest_future" msgid="5260857299282734759">"in <xliff:g id="COUNT">%d</xliff:g>m"</string>
     <string name="duration_hours_shortest_future" msgid="2979276794547981674">"in <xliff:g id="COUNT">%d</xliff:g>h"</string>
     <string name="duration_days_shortest_future" msgid="3392722163935571543">"in <xliff:g id="COUNT">%d</xliff:g>d"</string>
-    <string name="duration_years_shortest_future" msgid="5537464088352970388">"in <xliff:g id="COUNT">%d</xliff:g> y"</string>
+    <string name="duration_years_shortest_future" msgid="5537464088352970388">"in <xliff:g id="COUNT">%d</xliff:g>y"</string>
     <string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minute ago}other{# minutes ago}}"</string>
     <string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# hour ago}other{# hours ago}}"</string>
     <string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# day ago}other{# days ago}}"</string>
@@ -1136,13 +1167,13 @@
     <string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"Failed to copy to clipboard"</string>
     <string name="paste" msgid="461843306215520225">"Paste"</string>
     <string name="paste_as_plain_text" msgid="7664800665823182587">"Paste as plain text"</string>
-    <string name="replace" msgid="7842675434546657444">"Replace..."</string>
+    <string name="replace" msgid="7842675434546657444">"Replace…"</string>
     <string name="delete" msgid="1514113991712129054">"Delete"</string>
     <string name="copyUrl" msgid="6229645005987260230">"Copy URL"</string>
     <string name="selectTextMode" msgid="3225108910999318778">"Select text"</string>
     <string name="undo" msgid="3175318090002654673">"Undo"</string>
     <string name="redo" msgid="7231448494008532233">"Redo"</string>
-    <string name="autofill" msgid="511224882647795296">"Auto-fill"</string>
+    <string name="autofill" msgid="511224882647795296">"Autofill"</string>
     <string name="textSelectionCABTitle" msgid="5151441579532476940">"Text selection"</string>
     <string name="addToDictionary" msgid="8041821113480950096">"Add to dictionary"</string>
     <string name="deleteText" msgid="4200807474529938112">"Delete"</string>
@@ -1152,7 +1183,7 @@
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Switch input method"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Storage space running out"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Some system functions may not work"</string>
-    <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure that you have 250 MB of free space and restart."</string>
+    <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure you have 250MB of free space and restart."</string>
     <string name="app_running_notification_title" msgid="8985999749231486569">"<xliff:g id="APP_NAME">%1$s</xliff:g> is running"</string>
     <string name="app_running_notification_text" msgid="5120815883400228566">"Tap for more information or to stop the app."</string>
     <string name="ok" msgid="2646370155170753815">"OK"</string>
@@ -1168,7 +1199,7 @@
     <string name="selected" msgid="6614607926197755875">"selected"</string>
     <string name="not_selected" msgid="410652016565864475">"not selected"</string>
     <string name="rating_label" msgid="1837085249662154601">"{rating,plural, =1{One star out of {max}}other{# stars out of {max}}}"</string>
-    <string name="in_progress" msgid="2149208189184319441">"In progress"</string>
+    <string name="in_progress" msgid="2149208189184319441">"in progress"</string>
     <string name="whichApplication" msgid="5432266899591255759">"Complete action using"</string>
     <string name="whichApplicationNamed" msgid="6969946041713975681">"Complete action using %1$s"</string>
     <string name="whichApplicationLabel" msgid="7852182961472531728">"Complete action"</string>
@@ -1231,8 +1262,8 @@
     <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g> was built for an incompatible version of the Android OS and may behave unexpectedly. An updated version of the app may be available."</string>
     <string name="unsupported_compile_sdk_show" msgid="1601210057960312248">"Always show"</string>
     <string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"Check for update"</string>
-    <string name="smv_application" msgid="3775183542777792638">"The app <xliff:g id="APPLICATION">%1$s</xliff:g> (process <xliff:g id="PROCESS">%2$s</xliff:g>) has violated its self-enforced Strict Mode policy."</string>
-    <string name="smv_process" msgid="1398801497130695446">"The process <xliff:g id="PROCESS">%1$s</xliff:g> has violated its self-enforced StrictMode policy."</string>
+    <string name="smv_application" msgid="3775183542777792638">"The app <xliff:g id="APPLICATION">%1$s</xliff:g> (process <xliff:g id="PROCESS">%2$s</xliff:g>) has violated its self-enforced StrictMode policy."</string>
+    <string name="smv_process" msgid="1398801497130695446">"The process <xliff:g id="PROCESS">%1$s</xliff:g> has has violated its self-enforced StrictMode policy."</string>
     <string name="android_upgrading_title" product="default" msgid="7279077384220829683">"Phone is updating…"</string>
     <string name="android_upgrading_title" product="tablet" msgid="4268417249079938805">"Tablet is updating…"</string>
     <string name="android_upgrading_title" product="device" msgid="6774767702998149762">"Device is updating…"</string>
@@ -1240,18 +1271,18 @@
     <string name="android_start_title" product="automotive" msgid="7917984412828168079">"Android is starting…"</string>
     <string name="android_start_title" product="tablet" msgid="4429767260263190344">"Tablet is starting…"</string>
     <string name="android_start_title" product="device" msgid="6967413819673299309">"Device is starting…"</string>
-    <string name="android_upgrading_fstrim" msgid="3259087575528515329">"Optimising storage."</string>
+    <string name="android_upgrading_fstrim" msgid="3259087575528515329">"Optimizing storage."</string>
     <string name="android_upgrading_notification_title" product="default" msgid="3509927005342279257">"Finishing system update…"</string>
     <string name="app_upgrading_toast" msgid="1016267296049455585">"<xliff:g id="APPLICATION">%1$s</xliff:g> is upgrading…"</string>
-    <string name="android_upgrading_apk" msgid="1339564803894466737">"Optimising app <xliff:g id="NUMBER_0">%1$d</xliff:g> of <xliff:g id="NUMBER_1">%2$d</xliff:g>."</string>
+    <string name="android_upgrading_apk" msgid="1339564803894466737">"Optimizing app <xliff:g id="NUMBER_0">%1$d</xliff:g> of <xliff:g id="NUMBER_1">%2$d</xliff:g>."</string>
     <string name="android_preparing_apk" msgid="589736917792300956">"Preparing <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Starting apps."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Finishing boot."</string>
-    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"You pressed the power button – this usually turns off the screen.\n\nTry tapping lightly while setting up your fingerprint."</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"You pressed the power button — this usually turns off the screen.\n\nTry tapping lightly while setting up your fingerprint."</string>
     <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"To end setup, turn off screen"</string>
     <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Turn off"</string>
     <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Continue verifying your fingerprint?"</string>
-    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"You pressed the power button – this usually turns off the screen.\n\nTry tapping lightly to verify your fingerprint."</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"You pressed the power button — this usually turns off the screen.\n\nTry tapping lightly to verify your fingerprint."</string>
     <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Turn off screen"</string>
     <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continue"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> running"</string>
@@ -1266,8 +1297,8 @@
     <string name="dump_heap_notification_detail" msgid="8431586843001054050">"Heap dump collected. Tap to share."</string>
     <string name="dump_heap_title" msgid="4367128917229233901">"Share heap dump?"</string>
     <string name="dump_heap_text" msgid="1692649033835719336">"The <xliff:g id="PROC">%1$s</xliff:g> process has exceeded its memory limit of <xliff:g id="SIZE">%2$s</xliff:g>. A heap dump is available for you to share with its developer. Be careful: this heap dump can contain any of your personal information that the application has access to."</string>
-    <string name="dump_heap_system_text" msgid="6805155514925350849">"The <xliff:g id="PROC">%1$s</xliff:g> process has exceeded its memory limit of <xliff:g id="SIZE">%2$s</xliff:g>. A heap dump is available for you to share. Be careful: this heap dump can contain any sensitive personal information that the process has access to, which may include things that you’ve typed."</string>
-    <string name="dump_heap_ready_text" msgid="5849618132123045516">"A heap dump of <xliff:g id="PROC">%1$s</xliff:g>’s process is available for you to share. Be careful: this heap dump may contain any sensitive personal information that the process has access to, which may include things that you’ve typed."</string>
+    <string name="dump_heap_system_text" msgid="6805155514925350849">"The <xliff:g id="PROC">%1$s</xliff:g> process has exceeded its memory limit of <xliff:g id="SIZE">%2$s</xliff:g>. A heap dump is available for you to share. Be careful: this heap dump can contain any sensitive personal information that the process has access to, which may include things you’ve typed."</string>
+    <string name="dump_heap_ready_text" msgid="5849618132123045516">"A heap dump of <xliff:g id="PROC">%1$s</xliff:g>’s process is available for you to share. Be careful: this heap dump may contain any sensitive personal information that the process has access to, which may include things you’ve typed."</string>
     <string name="sendText" msgid="493003724401350724">"Choose an action for text"</string>
     <string name="volume_ringtone" msgid="134784084629229029">"Ringer volume"</string>
     <string name="volume_music" msgid="7727274216734955095">"Media volume"</string>
@@ -1287,22 +1318,22 @@
     <string name="ringtone_default_with_actual" msgid="2709686194556159773">"Default (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
     <string name="ringtone_silent" msgid="397111123930141876">"None"</string>
     <string name="ringtone_picker_title" msgid="667342618626068253">"Ringtones"</string>
-    <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Alarm Sounds"</string>
-    <string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Notification Sounds"</string>
+    <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Alarm sounds"</string>
+    <string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Notification sounds"</string>
     <string name="ringtone_unknown" msgid="5059495249862816475">"Unknown"</string>
-    <string name="wifi_available_sign_in" msgid="381054692557675237">"Sign in to a Wi-Fi network"</string>
+    <string name="wifi_available_sign_in" msgid="381054692557675237">"Sign in to Wi-Fi network"</string>
     <string name="network_available_sign_in" msgid="1520342291829283114">"Sign in to network"</string>
     <!-- no translation found for network_available_sign_in_detailed (7520423801613396556) -->
     <skip />
-    <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
+    <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no internet access"</string>
     <string name="wifi_no_internet_detailed" msgid="634938444133558942">"Tap for options"</string>
-    <string name="mobile_no_internet" msgid="4014455157529909781">"Mobile network has no Internet access"</string>
-    <string name="other_networks_no_internet" msgid="6698711684200067033">"Network has no Internet access"</string>
+    <string name="mobile_no_internet" msgid="4014455157529909781">"Mobile network has no internet access"</string>
+    <string name="other_networks_no_internet" msgid="6698711684200067033">"Network has no internet access"</string>
     <string name="private_dns_broken_detailed" msgid="3709388271074611847">"Private DNS server cannot be accessed"</string>
     <string name="network_partial_connectivity" msgid="4791024923851432291">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has limited connectivity"</string>
     <string name="network_partial_connectivity_detailed" msgid="5741329444564575840">"Tap to connect anyway"</string>
     <string name="network_switch_metered" msgid="1531869544142283384">"Switched to <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
-    <string name="network_switch_metered_detail" msgid="1358296010128405906">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no Internet access. Charges may apply."</string>
+    <string name="network_switch_metered_detail" msgid="1358296010128405906">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no internet access. Charges may apply."</string>
     <string name="network_switch_metered_toast" msgid="501662047275723743">"Switched from <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> to <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
   <string-array name="network_switch_type_name">
     <item msgid="2255670471736226365">"mobile data"</item>
@@ -1346,7 +1377,7 @@
     <string name="date_time_done" msgid="8363155889402873463">"Done"</string>
     <string name="perms_new_perm_prefix" msgid="6984556020395757087"><font size="12" fgcolor="#ff33b5e5">"NEW: "</font></string>
     <string name="perms_description_app" msgid="2747752389870161996">"Provided by <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
-    <string name="no_permissions" msgid="5729199278862516390">"No permission required"</string>
+    <string name="no_permissions" msgid="5729199278862516390">"No permissions required"</string>
     <string name="perm_costs_money" msgid="749054595022779685">"this may cost you money"</string>
     <string name="dlg_ok" msgid="5103447663504839312">"OK"</string>
     <string name="usb_charging_notification_title" msgid="1674124518282666955">"Charging this device via USB"</string>
@@ -1358,7 +1389,7 @@
     <string name="usb_accessory_notification_title" msgid="1385394660861956980">"USB accessory connected"</string>
     <string name="usb_notification_message" msgid="4715163067192110676">"Tap for more options."</string>
     <string name="usb_power_notification_message" msgid="7284765627437897702">"Charging connected device. Tap for more options."</string>
-    <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Analogue audio accessory detected"</string>
+    <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Analog audio accessory detected"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"The attached device is not compatible with this phone. Tap to learn more."</string>
     <string name="adb_active_notification_title" msgid="408390247354560331">"USB debugging connected"</string>
     <string name="adb_active_notification_message" msgid="5617264033476778211">"Tap to turn off USB debugging"</string>
@@ -1396,7 +1427,7 @@
     <string name="alert_windows_notification_turn_off_action" msgid="7805857234839123780">"Turn off"</string>
     <string name="ext_media_checking_notification_title" msgid="8299199995416510094">"Checking <xliff:g id="NAME">%s</xliff:g>…"</string>
     <string name="ext_media_checking_notification_message" msgid="2231566971425375542">"Reviewing current content"</string>
-    <string name="ext_media_checking_notification_message" product="tv" msgid="7986154434946021415">"Analysing media storage"</string>
+    <string name="ext_media_checking_notification_message" product="tv" msgid="7986154434946021415">"Analyzing media storage"</string>
     <string name="ext_media_new_notification_title" msgid="3517407571407687677">"New <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_new_notification_title" product="automotive" msgid="9085349544984742727">"<xliff:g id="NAME">%s</xliff:g> isn’t working"</string>
     <string name="ext_media_new_notification_message" msgid="6095403121990786986">"Tap to set up"</string>
@@ -1411,7 +1442,7 @@
     <string name="ext_media_unmountable_notification_message" product="automotive" msgid="2274596120715020680">"You may need to reformat the device. Tap to eject."</string>
     <string name="ext_media_unsupported_notification_title" msgid="3487534182861251401">"<xliff:g id="NAME">%s</xliff:g> detected"</string>
     <string name="ext_media_unsupported_notification_title" product="automotive" msgid="6004193172658722381">"<xliff:g id="NAME">%s</xliff:g> isn’t working"</string>
-    <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Tap to set up."</string>
+    <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Tap to set up ."</string>
     <string name="ext_media_unsupported_notification_message" product="tv" msgid="1595482802187036532">"Select to set up <xliff:g id="NAME">%s</xliff:g> in a supported format."</string>
     <string name="ext_media_unsupported_notification_message" product="automotive" msgid="3412494732736336330">"You may need to reformat the device"</string>
     <string name="ext_media_badremoval_notification_title" msgid="4114625551266196872">"<xliff:g id="NAME">%s</xliff:g> unexpectedly removed"</string>
@@ -1420,7 +1451,7 @@
     <string name="ext_media_nomedia_notification_message" msgid="2832724384636625852">"Some functionality may not work properly. Insert new storage."</string>
     <string name="ext_media_unmounting_notification_title" msgid="4147986383917892162">"Ejecting <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_unmounting_notification_message" msgid="5717036261538754203">"Don’t remove"</string>
-    <string name="ext_media_init_action" msgid="2312974060585056709">"Set-up"</string>
+    <string name="ext_media_init_action" msgid="2312974060585056709">"Set up"</string>
     <string name="ext_media_unmount_action" msgid="966992232088442745">"Eject"</string>
     <string name="ext_media_browse_action" msgid="344865351947079139">"Explore"</string>
     <string name="ext_media_seamless_action" msgid="8837030226009268080">"Switch output"</string>
@@ -1428,7 +1459,7 @@
     <string name="ext_media_missing_message" msgid="4408988706227922909">"Insert device again"</string>
     <string name="ext_media_move_specific_title" msgid="8492118544775964250">"Moving <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_move_title" msgid="2682741525619033637">"Moving data"</string>
-    <string name="ext_media_move_success_title" msgid="4901763082647316767">"Content transfer is finished"</string>
+    <string name="ext_media_move_success_title" msgid="4901763082647316767">"Content transfer is done"</string>
     <string name="ext_media_move_success_message" msgid="9159542002276982979">"Content moved to <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_move_failure_title" msgid="3184577479181333665">"Couldn’t move content"</string>
     <string name="ext_media_move_failure_message" msgid="4197306718121869335">"Try moving content again"</string>
@@ -1452,8 +1483,8 @@
     <string name="permdesc_requestInstallPackages" msgid="3969369278325313067">"Allows an application to request installation of packages."</string>
     <string name="permlab_requestDeletePackages" msgid="2541172829260106795">"request delete packages"</string>
     <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"Allows an application to request deletion of packages."</string>
-    <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"ask to ignore battery optimisations"</string>
-    <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Allows an app to ask for permission to ignore battery optimisations for that app."</string>
+    <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"ask to ignore battery optimizations"</string>
+    <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Allows an app to ask for permission to ignore battery optimizations for that app."</string>
     <string name="permlab_queryAllPackages" msgid="2928450604653281650">"query all packages"</string>
     <string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Allows an app to see all installed packages."</string>
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Tap twice for zoom control"</string>
@@ -1465,9 +1496,9 @@
     <string name="ime_action_done" msgid="6299921014822891569">"Done"</string>
     <string name="ime_action_previous" msgid="6548799326860401611">"Prev"</string>
     <string name="ime_action_default" msgid="8265027027659800121">"Execute"</string>
-    <string name="dial_number_using" msgid="6060769078933953531">"Dial number\n using <xliff:g id="NUMBER">%s</xliff:g>"</string>
-    <string name="create_contact_using" msgid="6200708808003692594">"Create contact\n using <xliff:g id="NUMBER">%s</xliff:g>"</string>
-    <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"The following one or more applications request permission to access your account, now and in the future."</string>
+    <string name="dial_number_using" msgid="6060769078933953531">"Dial number\nusing <xliff:g id="NUMBER">%s</xliff:g>"</string>
+    <string name="create_contact_using" msgid="6200708808003692594">"Create contact\nusing <xliff:g id="NUMBER">%s</xliff:g>"</string>
+    <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"The following one or more apps request permission to access your account, now and in the future."</string>
     <string name="grant_credentials_permission_message_footer" msgid="1886710210516246461">"Do you want to allow this request?"</string>
     <string name="grant_permissions_header_text" msgid="3420736827804657201">"Access request"</string>
     <string name="allow" msgid="6195617008611933762">"Allow"</string>
@@ -1477,7 +1508,7 @@
     <string name="permission_request_notification_for_app_with_subtitle" msgid="1298704005732851350">"Permission requested by <xliff:g id="APP">%1$s</xliff:g>\nfor account <xliff:g id="ACCOUNT">%2$s</xliff:g>."</string>
     <string name="forward_intent_to_owner" msgid="4620359037192871015">"You\'re using this app outside of your work profile"</string>
     <string name="forward_intent_to_work" msgid="3620262405636021151">"You\'re using this app in your work profile"</string>
-    <string name="input_method_binding_label" msgid="1166731601721983656">"Input Method"</string>
+    <string name="input_method_binding_label" msgid="1166731601721983656">"Input method"</string>
     <string name="sync_binding_label" msgid="469249309424662147">"Sync"</string>
     <string name="accessibility_binding_label" msgid="1974602776545801715">"Accessibility"</string>
     <string name="wallpaper_binding_label" msgid="1197440498000786738">"Wallpaper"</string>
@@ -1519,7 +1550,7 @@
     <string name="gpsNotifMessage" msgid="7346649122793758032">"Requested by <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="SERVICE">%2$s</xliff:g>)"</string>
     <string name="gpsVerifYes" msgid="3719843080744112940">"Yes"</string>
     <string name="gpsVerifNo" msgid="1671201856091564741">"No"</string>
-    <string name="sync_too_many_deletes" msgid="6999440774578705300">"Deletion limit exceeded"</string>
+    <string name="sync_too_many_deletes" msgid="6999440774578705300">"Delete limit exceeded"</string>
     <string name="sync_too_many_deletes_desc" msgid="7409327940303504440">"There are <xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</xliff:g> deleted items for <xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>, account <xliff:g id="ACCOUNT_NAME">%3$s</xliff:g>. What do you want to do?"</string>
     <string name="sync_really_delete" msgid="5657871730315579051">"Delete the items"</string>
     <string name="sync_undo_deletes" msgid="5786033331266418896">"Undo the deletes"</string>
@@ -1535,8 +1566,8 @@
     <string name="time_picker_decrement_minute_button" msgid="230925389943411490">"Decrease minute"</string>
     <string name="time_picker_increment_hour_button" msgid="3063572723197178242">"Increase hour"</string>
     <string name="time_picker_decrement_hour_button" msgid="584101766855054412">"Decrease hour"</string>
-    <string name="time_picker_increment_set_pm_button" msgid="5889149366900376419">"Set p.m."</string>
-    <string name="time_picker_decrement_set_am_button" msgid="1422608001541064087">"Set a.m."</string>
+    <string name="time_picker_increment_set_pm_button" msgid="5889149366900376419">"Set PM"</string>
+    <string name="time_picker_decrement_set_am_button" msgid="1422608001541064087">"Set AM"</string>
     <string name="date_picker_increment_month_button" msgid="3447263316096060309">"Increase month"</string>
     <string name="date_picker_decrement_month_button" msgid="6531888937036883014">"Decrease month"</string>
     <string name="date_picker_increment_day_button" msgid="4349336637188534259">"Increase day"</string>
@@ -1588,15 +1619,15 @@
     <string name="issued_to" msgid="5975877665505297662">"Issued to:"</string>
     <string name="common_name" msgid="1486334593631798443">"Common name:"</string>
     <string name="org_name" msgid="7526331696464255245">"Organization:"</string>
-    <string name="org_unit" msgid="995934486977223076">"Organisational unit:"</string>
+    <string name="org_unit" msgid="995934486977223076">"Organizational unit:"</string>
     <string name="issued_by" msgid="7872459822431585684">"Issued by:"</string>
     <string name="validity_period" msgid="1717724283033175968">"Validity:"</string>
     <string name="issued_on" msgid="5855489688152497307">"Issued on:"</string>
     <string name="expires_on" msgid="1623640879705103121">"Expires on:"</string>
     <string name="serial_number" msgid="3479576915806623429">"Serial number:"</string>
     <string name="fingerprints" msgid="148690767172613723">"Fingerprints:"</string>
-    <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 fingerprint"</string>
-    <string name="sha1_fingerprint" msgid="2339915142825390774">"SHA-1 fingerprint"</string>
+    <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 fingerprint:"</string>
+    <string name="sha1_fingerprint" msgid="2339915142825390774">"SHA-1 fingerprint:"</string>
     <string name="activity_chooser_view_see_all" msgid="3917045206812726099">"See all"</string>
     <string name="activity_chooser_view_dialog_title_default" msgid="8880731437191978314">"Choose activity"</string>
     <string name="share_action_provider_share_with" msgid="1904096863622941880">"Share with"</string>
@@ -1610,7 +1641,7 @@
     <string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="9213546147739983977">"Phone"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="1551166029093995289">"Dock speakers"</string>
-    <string name="default_audio_route_name_external_device" msgid="8124229858618975">"External device"</string>
+    <string name="default_audio_route_name_external_device" msgid="8124229858618975">"External Device"</string>
     <string name="default_audio_route_name_headphones" msgid="6954070994792640762">"Headphones"</string>
     <string name="default_audio_route_name_usb" msgid="895668743163316932">"USB"</string>
     <string name="default_audio_route_category_name" msgid="5241740395748134483">"System"</string>
@@ -1648,9 +1679,9 @@
     <string name="kg_invalid_sim_pin_hint" msgid="4821601451222564077">"Type a PIN that is 4 to 8 numbers."</string>
     <string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"PUK code should be 8 numbers."</string>
     <string name="kg_invalid_puk" msgid="4809502818518963344">"Re-enter the correct PUK code. Repeated attempts will permanently disable the SIM."</string>
-    <string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"PIN codes do not match"</string>
+    <string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"PIN codes does not match"</string>
     <string name="kg_login_too_many_attempts" msgid="699292728290654121">"Too many pattern attempts"</string>
-    <string name="kg_login_instructions" msgid="3619844310339066827">"To unlock, sign in with your Google Account."</string>
+    <string name="kg_login_instructions" msgid="3619844310339066827">"To unlock, sign in with your Google account."</string>
     <string name="kg_login_username_hint" msgid="1765453775467133251">"Username (email)"</string>
     <string name="kg_login_password_hint" msgid="3330530727273164402">"Password"</string>
     <string name="kg_login_submit_button" msgid="893611277617096870">"Sign in"</string>
@@ -1667,13 +1698,13 @@
     <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"You have incorrectly attempted to unlock your Android TV device <xliff:g id="NUMBER">%d</xliff:g> times. Your Android TV device will now be reset to factory default."</string>
     <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"You have incorrectly attempted to unlock the phone <xliff:g id="NUMBER">%d</xliff:g> times. The phone will now be reset to factory default."</string>
     <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your tablet using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
-    <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"You have drawn your unlock pattern incorrectly <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your Android TV device using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
+    <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your Android TV device using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
     <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
     <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
     <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Remove"</string>
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string>
-    <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string>
+    <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature."</string>
     <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string>
     <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings &gt; Accessibility."</string>
     <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
@@ -1692,7 +1723,7 @@
     <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Allow"</string>
     <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Deny"</string>
     <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tap a feature to start using it:"</string>
-    <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the Accessibility button"</string>
+    <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the accessibility button"</string>
     <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choose features to use with the volume key shortcut"</string>
     <string name="accessibility_uncheck_legacy_item_warning" msgid="8047830891064817447">"<xliff:g id="SERVICE_NAME">%s</xliff:g> has been turned off"</string>
     <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Edit shortcuts"</string>
@@ -1701,15 +1732,15 @@
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Use Shortcut"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Colour inversion"</string>
     <string name="color_correction_feature_name" msgid="3655077237805422597">"Colour correction"</string>
-    <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-handed mode"</string>
+    <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-Handed mode"</string>
     <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string>
     <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Held volume keys. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> turned on."</string>
     <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Held volume keys. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> turned off."</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"Press and hold both volume keys for three seconds to use <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
-    <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Choose a feature to use when you tap the Accessibility button:"</string>
+    <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Choose a feature to use when you tap the accessibility button:"</string>
     <string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"Choose a feature to use with the accessibility gesture (swipe up from the bottom of the screen with two fingers):"</string>
     <string name="accessibility_gesture_3finger_prompt_text" msgid="5211827854510660203">"Choose a feature to use with the accessibility gesture (swipe up from the bottom of the screen with three fingers):"</string>
-    <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"To switch between features, touch and hold the Accessibility button."</string>
+    <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"To switch between features, touch &amp; hold the accessibility button."</string>
     <string name="accessibility_gesture_instructional_text" msgid="9196230728837090497">"To switch between features, swipe up with two fingers and hold."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"To switch between features, swipe up with three fingers and hold."</string>
     <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Magnification"</string>
@@ -1761,9 +1792,9 @@
     <string name="mediasize_na_junior_legal" msgid="3398084874757748531">"Junior Legal"</string>
     <string name="mediasize_na_ledger" msgid="1819497882853940248">"Ledger"</string>
     <string name="mediasize_na_tabloid" msgid="6792611672983574375">"Tabloid"</string>
-    <string name="mediasize_na_index_3x5" msgid="990821038991491710">"Index Card 3 x 5"</string>
-    <string name="mediasize_na_index_4x6" msgid="4414381976602032401">"Index Card 4 x 6"</string>
-    <string name="mediasize_na_index_5x8" msgid="4499341583361946948">"Index Card 5 x 8"</string>
+    <string name="mediasize_na_index_3x5" msgid="990821038991491710">"Index Card 3x5"</string>
+    <string name="mediasize_na_index_4x6" msgid="4414381976602032401">"Index Card 4x6"</string>
+    <string name="mediasize_na_index_5x8" msgid="4499341583361946948">"Index Card 5x8"</string>
     <string name="mediasize_na_monarch" msgid="4396943937986136896">"Monarch"</string>
     <string name="mediasize_na_quarto" msgid="2119101847712239885">"Quarto"</string>
     <string name="mediasize_na_foolscap" msgid="5011612828564394648">"Foolscap"</string>
@@ -1826,12 +1857,12 @@
     <string name="restr_pin_enter_admin_pin" msgid="1199419462726962697">"Enter admin PIN"</string>
     <string name="restr_pin_enter_pin" msgid="373139384161304555">"Enter PIN"</string>
     <string name="restr_pin_incorrect" msgid="3861383632940852496">"Incorrect"</string>
-    <string name="restr_pin_enter_old_pin" msgid="7537079094090650967">"Current PIN:"</string>
+    <string name="restr_pin_enter_old_pin" msgid="7537079094090650967">"Current PIN"</string>
     <string name="restr_pin_enter_new_pin" msgid="3267614461844565431">"New PIN"</string>
     <string name="restr_pin_confirm_pin" msgid="7143161971614944989">"Confirm new PIN"</string>
     <string name="restr_pin_create_pin" msgid="917067613896366033">"Create a PIN for modifying restrictions"</string>
     <string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"PINs don\'t match. Try again."</string>
-    <string name="restr_pin_error_too_short" msgid="1547007808237941065">"PIN is too short. Must be at least four digits."</string>
+    <string name="restr_pin_error_too_short" msgid="1547007808237941065">"PIN is too short. Must be at least 4 digits."</string>
     <string name="restr_pin_try_later" msgid="5897719962541636727">"Try again later"</string>
     <string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string>
@@ -1854,8 +1885,8 @@
     <string name="package_updated_device_owner" msgid="7560272363805506941">"Updated by your admin"</string>
     <string name="package_deleted_device_owner" msgid="2292335928930293023">"Deleted by your admin"</string>
     <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
-    <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features and some network connections."</string>
-    <string name="battery_saver_description" msgid="8518809702138617167">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features and some network connections."</string>
+    <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features, and some network connections."</string>
+    <string name="battery_saver_description" msgid="8518809702138617167">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features, and some network connections."</string>
     <string name="data_saver_description" msgid="4995164271550590517">"To help reduce data usage, Data Saver prevents some apps from sending or receiving data in the background. An app you\'re currently using can access data, but may do so less frequently. This may mean, for example, that images don\'t display until you tap them."</string>
     <string name="data_saver_enable_title" msgid="7080620065745260137">"Turn on Data Saver?"</string>
     <string name="data_saver_enable_button" msgid="4399405762586419726">"Turn on"</string>
@@ -1871,7 +1902,7 @@
     <string name="zen_mode_until" msgid="2250286190237669079">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
     <string name="zen_mode_alarm" msgid="7046911727540499275">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (next alarm)"</string>
     <string name="zen_mode_forever" msgid="740585666364912448">"Until you turn off"</string>
-    <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"Until you turn off Do not disturb"</string>
+    <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"Until you turn off Do Not Disturb"</string>
     <string name="zen_mode_rule_name_combination" msgid="7174598364351313725">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
     <string name="toolbar_collapse_description" msgid="8009920446193610996">"Collapse"</string>
     <string name="zen_mode_feature_name" msgid="3785547207263754500">"Do not disturb"</string>
@@ -1903,22 +1934,22 @@
     <string name="usb_midi_peripheral_product_name" msgid="2836276258480904434">"USB Peripheral Port"</string>
     <string name="floating_toolbar_open_overflow_description" msgid="2260297653578167367">"More options"</string>
     <string name="floating_toolbar_close_overflow_description" msgid="3949818077708138098">"Close overflow"</string>
-    <string name="maximize_button_text" msgid="4258922519914732645">"Maximise"</string>
+    <string name="maximize_button_text" msgid="4258922519914732645">"Maximize"</string>
     <string name="close_button_text" msgid="10603510034455258">"Close"</string>
     <string name="notification_messaging_title_template" msgid="772857526770251989">"<xliff:g id="CONVERSATION_TITLE">%1$s</xliff:g>: <xliff:g id="SENDER_NAME">%2$s</xliff:g>"</string>
     <string name="call_notification_answer_action" msgid="5999246836247132937">"Answer"</string>
     <string name="call_notification_answer_video_action" msgid="2086030940195382249">"Video"</string>
     <string name="call_notification_decline_action" msgid="3700345945214000726">"Decline"</string>
-    <string name="call_notification_hang_up_action" msgid="9130720590159188131">"Hang up"</string>
+    <string name="call_notification_hang_up_action" msgid="9130720590159188131">"Hang Up"</string>
     <string name="call_notification_incoming_text" msgid="6143109825406638201">"Incoming call"</string>
-    <string name="call_notification_ongoing_text" msgid="3880832933933020875">"On-going call"</string>
+    <string name="call_notification_ongoing_text" msgid="3880832933933020875">"Ongoing call"</string>
     <string name="call_notification_screening_text" msgid="8396931408268940208">"Screening an incoming call"</string>
-    <string name="default_notification_channel_label" msgid="3697928973567217330">"Uncategorised"</string>
+    <string name="default_notification_channel_label" msgid="3697928973567217330">"Uncategorized"</string>
     <string name="importance_from_user" msgid="2782756722448800447">"You set the importance of these notifications."</string>
     <string name="importance_from_person" msgid="4235804979664465383">"This is important because of the people involved."</string>
     <string name="notification_history_title_placeholder" msgid="7748630986182249599">"Custom app notification"</string>
-    <string name="user_creation_account_exists" msgid="2239146360099708035">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> (a User with this account already exists)?"</string>
-    <string name="user_creation_adding" msgid="7305185499667958364">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
+    <string name="user_creation_account_exists" msgid="2239146360099708035">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> (a User with this account already exists) ?"</string>
+    <string name="user_creation_adding" msgid="7305185499667958364">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
     <string name="supervised_user_creation_label" msgid="6884904353827427515">"Add supervised user"</string>
     <string name="language_selection_title" msgid="52674936078683285">"Add a language"</string>
     <string name="country_selection_title" msgid="5221495687299014379">"Region preference"</string>
@@ -1931,7 +1962,7 @@
     <string name="region_picker_section_all" msgid="756441309928774155">"All regions"</string>
     <string name="locale_search_menu" msgid="6258090710176422934">"Search"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"App isn’t available"</string>
-    <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> isn’t available at the moment. This is managed by <xliff:g id="APP_NAME_1">%2$s</xliff:g>."</string>
+    <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> isn’t available right now. This is managed by <xliff:g id="APP_NAME_1">%2$s</xliff:g>."</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"Learn more"</string>
     <string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"Unpause app"</string>
     <string name="work_mode_off_title" msgid="961171256005852058">"Turn on work apps?"</string>
@@ -1957,7 +1988,7 @@
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g>. Try on your Android TV device instead."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g>. Try on your tablet instead."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g>. Try on your phone instead."</string>
-    <string name="deprecated_target_sdk_message" msgid="5246906284426844596">"This app was built for an older version of Android. It might not work properly and doesn\'t include the latest security and privacy protections. Check for an update or contact the app\'s developer."</string>
+    <string name="deprecated_target_sdk_message" msgid="5246906284426844596">"This app was built for an older version of Android. It might not work properly and doesn\'t include the latest security and privacy protections. Check for an update, or contact the app\'s developer."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string>
     <string name="new_sms_notification_content" msgid="3197949934153460639">"Open SMS app to view"</string>
@@ -1995,21 +2026,21 @@
     <string name="time_picker_prompt_label" msgid="303588544656363889">"Type in time"</string>
     <string name="time_picker_text_input_mode_description" msgid="4761160667516611576">"Switch to text input mode for the time input."</string>
     <string name="time_picker_radial_mode_description" msgid="1222342577115016953">"Switch to clock mode for the time input."</string>
-    <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"Auto-fill options"</string>
-    <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Save for AutoFill"</string>
-    <string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"Contents can’t be auto-filled"</string>
-    <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"No auto-fill suggestions"</string>
-    <string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{One auto-fill suggestion}other{# auto-fill suggestions}}"</string>
+    <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"Autofill options"</string>
+    <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Save for Autofill"</string>
+    <string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"Contents can’t be autofilled"</string>
+    <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"No autofill suggestions"</string>
+    <string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{One autofill suggestion}other{# autofill suggestions}}"</string>
     <string name="autofill_save_title" msgid="7719802414283739775">"Save to "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
     <string name="autofill_save_title_with_type" msgid="3002460014579799605">"Save <xliff:g id="TYPE">%1$s</xliff:g> to "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
     <string name="autofill_save_title_with_2types" msgid="3783270967447869241">"Save <xliff:g id="TYPE_0">%1$s</xliff:g> and <xliff:g id="TYPE_1">%2$s</xliff:g> to "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>"?"</string>
-    <string name="autofill_save_title_with_3types" msgid="6598228952100102578">"Save <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> and <xliff:g id="TYPE_2">%3$s</xliff:g> to "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"?"</string>
+    <string name="autofill_save_title_with_3types" msgid="6598228952100102578">"Save <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, and <xliff:g id="TYPE_2">%3$s</xliff:g> to "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"?"</string>
     <string name="autofill_update_title" msgid="3630695947047069136">"Update in "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
     <string name="autofill_update_title_with_type" msgid="5264152633488495704">"Update <xliff:g id="TYPE">%1$s</xliff:g> in "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
     <string name="autofill_update_title_with_2types" msgid="1797514386321086273">"Update <xliff:g id="TYPE_0">%1$s</xliff:g> and <xliff:g id="TYPE_1">%2$s</xliff:g> in "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>"?"</string>
-    <string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Update these items in "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> and <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
+    <string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Update these items in "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, and <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
     <string name="autofill_save_yes" msgid="8035743017382012850">"Save"</string>
-    <string name="autofill_save_no" msgid="9212826374207023544">"No, thanks"</string>
+    <string name="autofill_save_no" msgid="9212826374207023544">"No thanks"</string>
     <string name="autofill_save_notnow" msgid="2853932672029024195">"Not now"</string>
     <string name="autofill_save_never" msgid="6821841919831402526">"Never"</string>
     <string name="autofill_update_yes" msgid="4608662968996874445">"Update"</string>
@@ -2036,9 +2067,9 @@
     <string name="mmcc_imsi_unknown_in_hlr_msim_template" msgid="3688508325248599657">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> not provisioned"</string>
     <string name="mmcc_illegal_ms_msim_template" msgid="832644375774599327">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> not allowed"</string>
     <string name="mmcc_illegal_me_msim_template" msgid="4802735138861422802">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> not allowed"</string>
-    <string name="popup_window_default_title" msgid="6907717596694826919">"Pop-Up Window"</string>
+    <string name="popup_window_default_title" msgid="6907717596694826919">"Popup Window"</string>
     <string name="slice_more_content" msgid="3377367737876888459">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
-    <string name="shortcut_restored_on_lower_version" msgid="9206301954024286063">"App version downgraded or isn’t compatible with this shortcut"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="9206301954024286063">"App version downgraded, or isn’t compatible with this shortcut"</string>
     <string name="shortcut_restore_not_supported" msgid="4763198938588468400">"Couldn’t restore shortcut because app doesn’t support backup and restore"</string>
     <string name="shortcut_restore_signature_mismatch" msgid="579345304221605479">"Couldn’t restore shortcut because of app signature mismatch"</string>
     <string name="shortcut_restore_unknown_issue" msgid="2478146134395982154">"Couldn’t restore shortcut"</string>
@@ -2046,20 +2077,14 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"UNINSTALL"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OPEN ANYWAY"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Harmful app detected"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Allow one-time access"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Don’t allow"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Don’t show again"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wants to show <xliff:g id="APP_2">%2$s</xliff:g> slices"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Calls and notifications will vibrate"</string>
     <string name="volume_dialog_ringer_guidance_silent" msgid="1011246774949993783">"Calls and notifications will be muted"</string>
     <string name="notification_channel_system_changes" msgid="2462010596920209678">"System changes"</string>
-    <string name="notification_channel_do_not_disturb" msgid="7832584281883687653">"Do not disturb"</string>
+    <string name="notification_channel_do_not_disturb" msgid="7832584281883687653">"Do Not Disturb"</string>
     <string name="zen_upgrade_notification_visd_title" msgid="2001148984371968620">"New: Do Not Disturb is hiding notifications"</string>
-    <string name="zen_upgrade_notification_visd_content" msgid="3683314609114134946">"Tap to find out more and change."</string>
+    <string name="zen_upgrade_notification_visd_content" msgid="3683314609114134946">"Tap to learn more and change."</string>
     <string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Do Not Disturb has changed"</string>
     <string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Tap to check what\'s blocked."</string>
     <string name="review_notification_settings_title" msgid="5102557424459810820">"Review notification settings"</string>
@@ -2071,17 +2096,17 @@
     <string name="notification_appops_camera_active" msgid="8177643089272352083">"Camera"</string>
     <string name="notification_appops_microphone_active" msgid="581333393214739332">"Microphone"</string>
     <string name="notification_appops_overlay_active" msgid="5571732753262836481">"displaying over other apps on your screen"</string>
-    <string name="notification_feedback_indicator" msgid="663476517711323016">"Provide feedback"</string>
-    <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"This notification was promoted to default. Tap to provide feedback."</string>
-    <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"This notification was demoted to silent. Tap to provide feedback."</string>
+    <string name="notification_feedback_indicator" msgid="663476517711323016">"Provide Feedback"</string>
+    <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"This notification was promoted to Default. Tap to provide feedback."</string>
+    <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"This notification was demoted to Silent. Tap to provide feedback."</string>
     <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"This notification was ranked higher. Tap to provide feedback."</string>
     <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"This notification was ranked lower. Tap to provide feedback."</string>
     <string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Enhanced notifications"</string>
-    <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"Suggested actions and replies are now provided by enhanced notifications. Android adaptive notifications are no longer supported."</string>
+    <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"Suggested actions and replies are now provided by enhanced notifications. Android Adaptive Notifications are no longer supported."</string>
     <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</string>
     <string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Turn off"</string>
     <string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Learn more"</string>
-    <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Enhanced notifications replaced Android adaptive notifications in Android 12. This feature shows suggested actions and replies, and organises your notifications.\n\nEnhanced notifications can access notification content, including personal information like contact names and messages. This feature can also dismiss or respond to notifications, such as answering phone calls, and control Do Not Disturb."</string>
+    <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Enhanced notifications replaced Android Adaptive Notifications in Android 12. This feature shows suggested actions and replies, and organizes your notifications.\n\nEnhanced notifications can access notification content, including personal information like contact names and messages. This feature can also dismiss or respond to notifications, such as answering phone calls, and control Do Not Disturb."</string>
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Routine Mode info notification"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Battery Saver turned on"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reducing battery usage to extend battery life"</string>
@@ -2119,25 +2144,25 @@
     <string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Recent Apps"</string>
     <string name="accessibility_system_action_notifications_label" msgid="6083767351772162010">"Notifications"</string>
     <string name="accessibility_system_action_quick_settings_label" msgid="4583900123506773783">"Quick Settings"</string>
-    <string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Power Dialogue"</string>
+    <string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Power Dialog"</string>
     <string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Lock Screen"</string>
     <string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Screenshot"</string>
-    <string name="accessibility_system_action_headset_hook_label" msgid="8524691721287425468">"Headset hook"</string>
-    <string name="accessibility_system_action_on_screen_a11y_shortcut_label" msgid="8488701469459210309">"On-screen accessibility shortcut"</string>
-    <string name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" msgid="1057878690209817886">"On-screen accessibility shortcut chooser"</string>
-    <string name="accessibility_system_action_hardware_a11y_shortcut_label" msgid="5764644187715255107">"Accessibility shortcut"</string>
-    <string name="accessibility_system_action_dismiss_notification_shade" msgid="8931637495533770352">"Dismiss notification shade"</string>
-    <string name="accessibility_system_action_dpad_up_label" msgid="1029042950229333782">"Dpad up"</string>
-    <string name="accessibility_system_action_dpad_down_label" msgid="3441918448624921461">"Dpad down"</string>
-    <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad left"</string>
-    <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad right"</string>
-    <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad centre"</string>
+    <string name="accessibility_system_action_headset_hook_label" msgid="8524691721287425468">"Headset Hook"</string>
+    <string name="accessibility_system_action_on_screen_a11y_shortcut_label" msgid="8488701469459210309">"On-screen Accessibility Shortcut"</string>
+    <string name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" msgid="1057878690209817886">"On-screen Accessibility Shortcut Chooser"</string>
+    <string name="accessibility_system_action_hardware_a11y_shortcut_label" msgid="5764644187715255107">"Accessibility Shortcut"</string>
+    <string name="accessibility_system_action_dismiss_notification_shade" msgid="8931637495533770352">"Dismiss Notification Shade"</string>
+    <string name="accessibility_system_action_dpad_up_label" msgid="1029042950229333782">"Dpad Up"</string>
+    <string name="accessibility_system_action_dpad_down_label" msgid="3441918448624921461">"Dpad Down"</string>
+    <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad Left"</string>
+    <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad Right"</string>
+    <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad Center"</string>
     <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar of <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> has been put into the RESTRICTED bucket"</string>
     <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
     <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sent an image"</string>
     <string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"Conversation"</string>
-    <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Group conversation"</string>
+    <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Group Conversation"</string>
     <string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string>
     <string name="resolver_personal_tab" msgid="2051260504014442073">"Personal"</string>
     <string name="resolver_work_tab" msgid="2690019516263167035">"Work"</string>
@@ -2168,7 +2193,7 @@
     <string name="PERSOSUBSTATE_SIM_SIM_PUK_ENTRY" msgid="3013902515773728996">"Enter PUK"</string>
     <string name="PERSOSUBSTATE_RUIM_NETWORK1_ENTRY" msgid="2974411408893410289">"RUIM network1 unlock PIN"</string>
     <string name="PERSOSUBSTATE_RUIM_NETWORK2_ENTRY" msgid="687618528751880721">"RUIM network2 unlock PIN"</string>
-    <string name="PERSOSUBSTATE_RUIM_HRPD_ENTRY" msgid="6810596579655575381">"RUIM HRPD unlock PIN"</string>
+    <string name="PERSOSUBSTATE_RUIM_HRPD_ENTRY" msgid="6810596579655575381">"RUIM hrpd unlock PIN"</string>
     <string name="PERSOSUBSTATE_RUIM_CORPORATE_ENTRY" msgid="2715929642540980259">"RUIM corporate unlock PIN"</string>
     <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ENTRY" msgid="8557791623303951590">"RUIM service provider unlock PIN"</string>
     <string name="PERSOSUBSTATE_RUIM_RUIM_ENTRY" msgid="7382468767274580323">"RUIM unlock PIN"</string>
@@ -2184,8 +2209,8 @@
     <string name="PERSOSUBSTATE_SIM_IMPI_ENTRY" msgid="7043865376145617024">"IMPI unlock PIN"</string>
     <string name="PERSOSUBSTATE_SIM_NS_SP_ENTRY" msgid="6144227308185112176">"Network subset service provider unlock PIN"</string>
     <string name="PERSOSUBSTATE_SIM_NETWORK_IN_PROGRESS" msgid="4233355366318061180">"Requesting SIM network unlock…"</string>
-    <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_IN_PROGRESS" msgid="6742563947637715645">"Requesting SIM network subset unlock…"</string>
-    <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_IN_PROGRESS" msgid="2033399698172403560">"Requesting SIM service provider unlock…"</string>
+    <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_IN_PROGRESS" msgid="6742563947637715645">"Requesting SIM network subset unlock …"</string>
+    <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_IN_PROGRESS" msgid="2033399698172403560">"Requesting SIM service provider un lock…"</string>
     <string name="PERSOSUBSTATE_SIM_CORPORATE_IN_PROGRESS" msgid="4795977251920732254">"Requesting SIM corporate unlock…"</string>
     <string name="PERSOSUBSTATE_SIM_NETWORK_PUK_IN_PROGRESS" msgid="1090425878157254446">"Requesting PUK unlock…"</string>
     <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_IN_PROGRESS" msgid="6476898876518094438">"Requesting PUK unlock…"</string>
@@ -2195,14 +2220,14 @@
     <string name="PERSOSUBSTATE_SIM_SIM_IN_PROGRESS" msgid="6709169861932992750">"Requesting SIM unlock…"</string>
     <string name="PERSOSUBSTATE_RUIM_NETWORK1_IN_PROGRESS" msgid="4013870911606478520">"Requesting RUIM network1 unlock…"</string>
     <string name="PERSOSUBSTATE_RUIM_NETWORK2_IN_PROGRESS" msgid="9032651188219523434">"Requesting RUIM network2 unlock…"</string>
-    <string name="PERSOSUBSTATE_RUIM_HRPD_IN_PROGRESS" msgid="6584576506344491207">"Requesting RUIM HRPD unlock…"</string>
+    <string name="PERSOSUBSTATE_RUIM_HRPD_IN_PROGRESS" msgid="6584576506344491207">"Requesting RUIM hrpd unlock…"</string>
     <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_IN_PROGRESS" msgid="830981927724888114">"Requesting RUIM service provider unlock…"</string>
     <string name="PERSOSUBSTATE_RUIM_CORPORATE_IN_PROGRESS" msgid="7851790973098894802">"Requesting RUIM corporate unlock…"</string>
     <string name="PERSOSUBSTATE_SIM_SPN_IN_PROGRESS" msgid="1149560739586960121">"Requesting SPN unlock…"</string>
     <string name="PERSOSUBSTATE_SIM_SP_EHPLMN_IN_PROGRESS" msgid="5708964693522116025">"Requesting SP Equivalent Home PLMN unlock…"</string>
     <string name="PERSOSUBSTATE_SIM_ICCID_IN_PROGRESS" msgid="7288103122966483455">"Requesting ICCID unlock…"</string>
     <string name="PERSOSUBSTATE_SIM_IMPI_IN_PROGRESS" msgid="4036752174056147753">"Requesting IMPI unlock…"</string>
-    <string name="PERSOSUBSTATE_SIM_NS_SP_IN_PROGRESS" msgid="5089536274515338566">"Requesting network subset service provider unlock…"</string>
+    <string name="PERSOSUBSTATE_SIM_NS_SP_IN_PROGRESS" msgid="5089536274515338566">"Requesting Network subset service provider unlock…"</string>
     <string name="PERSOSUBSTATE_RUIM_RUIM_IN_PROGRESS" msgid="6737197986936251958">"Requesting RUIM unlock…"</string>
     <string name="PERSOSUBSTATE_RUIM_NETWORK1_PUK_IN_PROGRESS" msgid="5658767775619998623">"Requesting PUK unlock…"</string>
     <string name="PERSOSUBSTATE_RUIM_NETWORK2_PUK_IN_PROGRESS" msgid="665978313257653727">"Requesting PUK unlock…"</string>
@@ -2210,16 +2235,16 @@
     <string name="PERSOSUBSTATE_RUIM_CORPORATE_PUK_IN_PROGRESS" msgid="2695664012344346788">"Requesting PUK unlock…"</string>
     <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_IN_PROGRESS" msgid="2695678959963807782">"Requesting PUK unlock…"</string>
     <string name="PERSOSUBSTATE_RUIM_RUIM_PUK_IN_PROGRESS" msgid="1230605365926493599">"Requesting PUK unlock…"</string>
-    <string name="PERSOSUBSTATE_SIM_NETWORK_ERROR" msgid="1924844017037151535">"SIM network unlock request unsuccessful."</string>
-    <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_ERROR" msgid="3372797822292089708">"SIM network subset unlock request unsuccessful."</string>
-    <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_ERROR" msgid="1878443146720411381">"SIM service provider unlock request unsuccessful."</string>
-    <string name="PERSOSUBSTATE_SIM_CORPORATE_ERROR" msgid="7664778312218023192">"SIM corporate unlock request unsuccessful."</string>
+    <string name="PERSOSUBSTATE_SIM_NETWORK_ERROR" msgid="1924844017037151535">"SIM Network unlock request unsuccessful."</string>
+    <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_ERROR" msgid="3372797822292089708">"SIM Network Subset unlock request unsucces sful."</string>
+    <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_ERROR" msgid="1878443146720411381">"SIM Service Provider unlock request unsu ccessful."</string>
+    <string name="PERSOSUBSTATE_SIM_CORPORATE_ERROR" msgid="7664778312218023192">"SIM Corporate unlock request unsuccessful."</string>
     <string name="PERSOSUBSTATE_SIM_SIM_ERROR" msgid="2472944311643350302">"SIM unlock request unsuccessful."</string>
-    <string name="PERSOSUBSTATE_RUIM_NETWORK1_ERROR" msgid="828089694480999120">"RUIM network1 unlock request unsuccessful."</string>
-    <string name="PERSOSUBSTATE_RUIM_NETWORK2_ERROR" msgid="17619001007092511">"RUIM network2 unlock request unsuccessful."</string>
-    <string name="PERSOSUBSTATE_RUIM_HRPD_ERROR" msgid="807214229604353614">"RUIM HRPD unlock request unsuccessful."</string>
-    <string name="PERSOSUBSTATE_RUIM_CORPORATE_ERROR" msgid="8644184447744175747">"RUIM corporate unlock request unsuccessful."</string>
-    <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ERROR" msgid="3801002648649640407">"RUIM service provider unlock request unsuccessful."</string>
+    <string name="PERSOSUBSTATE_RUIM_NETWORK1_ERROR" msgid="828089694480999120">"RUIM Network1 unlock request unsuccessful."</string>
+    <string name="PERSOSUBSTATE_RUIM_NETWORK2_ERROR" msgid="17619001007092511">"RUIM Network2 unlock request unsuccessful."</string>
+    <string name="PERSOSUBSTATE_RUIM_HRPD_ERROR" msgid="807214229604353614">"RUIM Hrpd unlock request unsuccessful."</string>
+    <string name="PERSOSUBSTATE_RUIM_CORPORATE_ERROR" msgid="8644184447744175747">"RUIM Corporate unlock request unsuccessful."</string>
+    <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ERROR" msgid="3801002648649640407">"RUIM Service Provider unlock request un successful."</string>
     <string name="PERSOSUBSTATE_RUIM_RUIM_ERROR" msgid="707397021218680753">"RUIM unlock request unsuccessful."</string>
     <string name="PERSOSUBSTATE_SIM_NETWORK_PUK_ERROR" msgid="894358680773257820">"PUK unlock unsuccessful."</string>
     <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_ERROR" msgid="352466878146726991">"PUK unlock unsuccessful."</string>
@@ -2237,16 +2262,16 @@
     <string name="PERSOSUBSTATE_SIM_ICCID_ERROR" msgid="7559167306794441462">"ICCID unlock request unsuccessful."</string>
     <string name="PERSOSUBSTATE_SIM_IMPI_ERROR" msgid="2782926139511136588">"IMPI unlock request unsuccessful."</string>
     <string name="PERSOSUBSTATE_SIM_NS_SP_ERROR" msgid="1890493954453456758">"Network subset service provider unlock request unsuccessful."</string>
-    <string name="PERSOSUBSTATE_SIM_NETWORK_SUCCESS" msgid="4886243367747126325">"SIM network unlock successful."</string>
-    <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_SUCCESS" msgid="4053809277733513987">"SIM network subset unlock successful."</string>
-    <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_SUCCESS" msgid="8249342930499801740">"SIM service provider unlock successful ."</string>
-    <string name="PERSOSUBSTATE_SIM_CORPORATE_SUCCESS" msgid="2339794542560381270">"SIM corporate unlock successful."</string>
+    <string name="PERSOSUBSTATE_SIM_NETWORK_SUCCESS" msgid="4886243367747126325">"SIM Network unlock successful."</string>
+    <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_SUCCESS" msgid="4053809277733513987">"SIM Network Subset unlock successful."</string>
+    <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_SUCCESS" msgid="8249342930499801740">"SIM Service Provider unlock successful ."</string>
+    <string name="PERSOSUBSTATE_SIM_CORPORATE_SUCCESS" msgid="2339794542560381270">"SIM Corporate unlock successful."</string>
     <string name="PERSOSUBSTATE_SIM_SIM_SUCCESS" msgid="6975608174152828954">"SIM unlock successful."</string>
-    <string name="PERSOSUBSTATE_RUIM_NETWORK1_SUCCESS" msgid="2846699261330463192">"RUIM network1 unlock successful."</string>
-    <string name="PERSOSUBSTATE_RUIM_NETWORK2_SUCCESS" msgid="5335414726057102801">"RUIM network2 unlock successful."</string>
-    <string name="PERSOSUBSTATE_RUIM_HRPD_SUCCESS" msgid="8868100318474971969">"RUIM HRPD unlock successful."</string>
-    <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_SUCCESS" msgid="6020936629725666932">"RUIM service provider unlock successful."</string>
-    <string name="PERSOSUBSTATE_RUIM_CORPORATE_SUCCESS" msgid="6944873647584595489">"RUIM corporate unlock successful."</string>
+    <string name="PERSOSUBSTATE_RUIM_NETWORK1_SUCCESS" msgid="2846699261330463192">"RUIM Network1 unlock successful."</string>
+    <string name="PERSOSUBSTATE_RUIM_NETWORK2_SUCCESS" msgid="5335414726057102801">"RUIM Network2 unlock successful."</string>
+    <string name="PERSOSUBSTATE_RUIM_HRPD_SUCCESS" msgid="8868100318474971969">"RUIM Hrpd unlock successful."</string>
+    <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_SUCCESS" msgid="6020936629725666932">"RUIM Service Provider unlock successf ul."</string>
+    <string name="PERSOSUBSTATE_RUIM_CORPORATE_SUCCESS" msgid="6944873647584595489">"RUIM Corporate unlock successful."</string>
     <string name="PERSOSUBSTATE_RUIM_RUIM_SUCCESS" msgid="2526483514124121988">"RUIM unlock successful."</string>
     <string name="PERSOSUBSTATE_SIM_NETWORK_PUK_SUCCESS" msgid="7662200333621664621">"PUK unlock successful."</string>
     <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_SUCCESS" msgid="2861223407953766632">"PUK unlock successful."</string>
@@ -2276,14 +2301,14 @@
     <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="7287720213963466672">"Unblock device camera"</string>
     <string name="sensor_privacy_start_use_notification_content_text" msgid="7595608891015777346">"For &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; and all apps and services"</string>
     <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"Unblock"</string>
-    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sensor privacy"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sensor Privacy"</string>
     <string name="splash_screen_view_icon_description" msgid="180638751260598187">"Application icon"</string>
     <string name="splash_screen_view_branding_description" msgid="7911129347402728216">"Application branding image"</string>
     <string name="view_and_control_notification_title" msgid="4300765399209912240">"Check access settings"</string>
     <string name="view_and_control_notification_content" msgid="8003766498562604034">"<xliff:g id="SERVICE_NAME">%s</xliff:g> can view and control your screen. Tap to review."</string>
-    <string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> translated."</string>
+    <string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> Translated."</string>
     <string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Message translated from <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> to <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
-    <string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Background activity"</string>
+    <string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Background Activity"</string>
     <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"An app is draining battery"</string>
     <string name="notification_title_long_running_fgs" msgid="8170284286477131587">"An app is still active"</string>
     <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> is running in the background. Tap to manage battery usage."</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Can’t access the phone’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Can’t access the tablet’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"This can’t be accessed while streaming. Try on your phone instead."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Can’t view picture-in-picture while streaming"</string>
     <string name="system_locale_title" msgid="711882686834677268">"System default"</string>
     <string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 6e034b7..4c0a7aa 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Voicemail"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Connection problem or invalid MMI code."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Feature not supported."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Operation is restricted to fixed dialling numbers only."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Cannot change call forwarding settings from your phone while you are roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Service was enabled."</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps, slowing down the phone."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"run foreground service"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Allows the app to make use of foreground services."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"run foreground service with the type \'camera\'"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Allows the app to make use of foreground services with the type \'camera\'"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"run foreground service with the type \'connectedDevice\'"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Allows the app to make use of foreground services with the type \'connectedDevice\'"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"run foreground service with the type \'dataSync\'"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Allows the app to make use of foreground services with the type \'dataSync\'"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"run foreground service with the type \'location\'"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Allows the app to make use of foreground services with the type \'location\'"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"run foreground service with the type \'mediaPlayback\'"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Allows the app to make use of foreground services with the type \'mediaPlayback\'"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"run foreground service with the type \'mediaProjection\'"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Allows the app to make use of foreground services with the type \'mediaProjection\'"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"run foreground service with the type \'microphone\'"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Allows the app to make use of foreground services with the type \'microphone\'"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"run foreground service with the type \'phoneCall\'"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Allows the app to make use of foreground services with the type \'phoneCall\'"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"run foreground service with the type \'health\'"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Allows the app to make use of foreground services with the type \'health\'"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"run foreground service with the type \'remoteMessaging\'"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Allows the app to make use of foreground services with the type \'remoteMessaging\'"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"run foreground service with the type \'systemExempted\'"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Allows the app to make use of foreground services with the type \'systemExempted\'"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"run foreground service with the type \'specialUse\'"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Allows the app to make use of foreground services with the type \'specialUse\'"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Allows the app to retrieve its code, data and cache sizes"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modify system settings"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"This app can record audio using the microphone while the app is in use."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"record audio in the background"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"This app can record audio using the microphone at any time."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detect screen captures of app windows"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"This app will get notified when a screenshot is taken while the app is in use."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Allows the app to read video files from your shared storage."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"read image files from shared storage"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Allows the app to read image files from your shared storage."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"read user selected image and video files from shared storage"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Allows the app to read image and video files that you select from your shared storage."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"modify or delete the contents of your shared storage"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Allows the app to write the contents of your shared storage."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"make/receive SIP calls"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"UNINSTALL"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OPEN ANYWAY"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Harmful app detected"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Allow one-time access"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Don’t allow"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Don’t show again"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wants to show <xliff:g id="APP_2">%2$s</xliff:g> slices"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Calls and notifications will vibrate"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Can’t access the phone’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Can’t access the tablet’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"This can’t be accessed while streaming. Try on your phone instead."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Can’t view picture-in-picture while streaming"</string>
     <string name="system_locale_title" msgid="711882686834677268">"System default"</string>
     <string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 643f27f..7e3ce2d 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Voicemail"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Connection problem or invalid MMI code."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Feature not supported."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Operation is restricted to fixed dialling numbers only."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Cannot change call forwarding settings from your phone while you are roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Service was enabled."</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps, slowing down the phone."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"run foreground service"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Allows the app to make use of foreground services."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"run foreground service with the type \'camera\'"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Allows the app to make use of foreground services with the type \'camera\'"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"run foreground service with the type \'connectedDevice\'"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Allows the app to make use of foreground services with the type \'connectedDevice\'"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"run foreground service with the type \'dataSync\'"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Allows the app to make use of foreground services with the type \'dataSync\'"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"run foreground service with the type \'location\'"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Allows the app to make use of foreground services with the type \'location\'"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"run foreground service with the type \'mediaPlayback\'"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Allows the app to make use of foreground services with the type \'mediaPlayback\'"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"run foreground service with the type \'mediaProjection\'"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Allows the app to make use of foreground services with the type \'mediaProjection\'"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"run foreground service with the type \'microphone\'"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Allows the app to make use of foreground services with the type \'microphone\'"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"run foreground service with the type \'phoneCall\'"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Allows the app to make use of foreground services with the type \'phoneCall\'"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"run foreground service with the type \'health\'"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Allows the app to make use of foreground services with the type \'health\'"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"run foreground service with the type \'remoteMessaging\'"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Allows the app to make use of foreground services with the type \'remoteMessaging\'"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"run foreground service with the type \'systemExempted\'"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Allows the app to make use of foreground services with the type \'systemExempted\'"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"run foreground service with the type \'specialUse\'"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Allows the app to make use of foreground services with the type \'specialUse\'"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Allows the app to retrieve its code, data and cache sizes"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modify system settings"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"This app can record audio using the microphone while the app is in use."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"record audio in the background"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"This app can record audio using the microphone at any time."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detect screen captures of app windows"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"This app will get notified when a screenshot is taken while the app is in use."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Allows the app to read video files from your shared storage."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"read image files from shared storage"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Allows the app to read image files from your shared storage."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"read user selected image and video files from shared storage"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Allows the app to read image and video files that you select from your shared storage."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"modify or delete the contents of your shared storage"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Allows the app to write the contents of your shared storage."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"make/receive SIP calls"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"UNINSTALL"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OPEN ANYWAY"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Harmful app detected"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Allow one-time access"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Don’t allow"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Don’t show again"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wants to show <xliff:g id="APP_2">%2$s</xliff:g> slices"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Calls and notifications will vibrate"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Can’t access the phone’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Can’t access the tablet’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"This can’t be accessed while streaming. Try on your phone instead."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Can’t view picture-in-picture while streaming"</string>
     <string name="system_locale_title" msgid="711882686834677268">"System default"</string>
     <string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 91e99ff..9cc06d1 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‏‎‎‏‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎Voicemail‎‏‎‎‏‎"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‎MSISDN1‎‏‎‎‏‎"</string>
     <string name="mmiError" msgid="2862759606579822246">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎Connection problem or invalid MMI code.‎‏‎‎‏‎"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‏‎Feature not supported.‎‏‎‎‏‎"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‏‏‏‎‎‎‏‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‏‎‏‏‎‏‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‎Operation is restricted to fixed dialing numbers only.‎‏‎‎‏‎"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‏‏‎‎‎‎‏‎‏‎‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‎Can not change call forwarding settings from your phone while you are roaming.‎‏‎‎‏‎"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‎‎‎‏‏‏‎Service was enabled.‎‏‎‎‏‎"</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‏‎‏‏‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‎Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the phone.‎‏‎‎‏‎"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‎‎‎‏‎‏‎‏‎‎‎‏‏‎run foreground service‎‏‎‎‏‎"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‏‏‏‏‎‏‎‏‎‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‏‎‏‎‏‏‎Allows the app to make use of foreground services.‎‏‎‎‏‎"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‎‎‏‏‏‎‎‏‎‎‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‏‏‏‏‎‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎run foreground service with the type \"camera\"‎‏‎‎‏‎"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‏‎‏‏‎‏‏‏‏‎‎‎‏‏‏‏‎Allows the app to make use of foreground services with the type \"camera\"‎‏‎‎‏‎"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‎run foreground service with the type \"connectedDevice\"‎‏‎‎‏‎"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‏‎‏‎‎‏‏‏‏‏‎‎‎‏‎‎‎‎‎‎‎‎‏‏‎Allows the app to make use of foreground services with the type \"connectedDevice\"‎‏‎‎‏‎"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎‏‎‏‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‎‏‏‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎run foreground service with the type \"dataSync\"‎‏‎‎‏‎"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‎‎‎‎‎‎‎‎‎‎‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‏‏‏‎‏‎‎Allows the app to make use of foreground services with the type \"dataSync\"‎‏‎‎‏‎"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‎‎‏‏‎‎‏‎‎‏‏‎‎‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‏‎‏‏‎‎‎‎‏‎‏‎‎run foreground service with the type \"location\"‎‏‎‎‏‎"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‎‎‏‏‎‎‏‎‏‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‏‏‏‏‎Allows the app to make use of foreground services with the type \"location\"‎‏‎‎‏‎"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‏‎‎‎‏‏‎‎‏‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‎‏‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‎‎run foreground service with the type \"mediaPlayback\"‎‏‎‎‏‎"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎Allows the app to make use of foreground services with the type \"mediaPlayback\"‎‏‎‎‏‎"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‎‎‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‏‏‎‎‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‏‏‏‎run foreground service with the type \"mediaProjection\"‎‏‎‎‏‎"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‎‏‏‎‎‎‏‏‏‏‎‎‎‏‎‏‏‏‎‏‏‎‎‏‎‎‎‏‏‎‎‎‎‎‏‎‎‎‏‎‏‏‏‏‎‏‎‎Allows the app to make use of foreground services with the type \"mediaProjection\"‎‏‎‎‏‎"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‏‎‏‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‏‎run foreground service with the type \"microphone\"‎‏‎‎‏‎"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‏‏‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎‏‏‎‎‎‎‎‏‎Allows the app to make use of foreground services with the type \"microphone\"‎‏‎‎‏‎"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎‎‏‎‏‏‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‎‎run foreground service with the type \"phoneCall\"‎‏‎‎‏‎"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‎Allows the app to make use of foreground services with the type \"phoneCall\"‎‏‎‎‏‎"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‎‏‏‏‎‎‎‎run foreground service with the type \"health\"‎‏‎‎‏‎"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‏‏‎‎‎‏‏‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‎‏‎‏‏‎‏‎‏‏‎‎‎‏‎Allows the app to make use of foreground services with the type \"health\"‎‏‎‎‏‎"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‏‎‏‎‏‎‏‎‎‎‏‏‏‎‏‎‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‏‏‎‎‎‎‏‎‎‎‏‎‏‏‎‎run foreground service with the type \"remoteMessaging\"‎‏‎‎‏‎"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‏‎‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‎‏‎‎‏‏‎‎‏‏‏‎‎‏‎‏‎‏‎‏‎Allows the app to make use of foreground services with the type \"remoteMessaging\"‎‏‎‎‏‎"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎‏‏‎‎‎‎‎‎‏‎‏‎‏‎‏‎‏‏‏‎‏‏‎‎‎‏‏‏‎‎‎‏‏‎‎‏‏‎‎‏‎‎‏‎‏‏‎‎‏‏‎‏‎run foreground service with the type \"systemExempted\"‎‏‎‎‏‎"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‎‏‏‎‏‎‏‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‏‎‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎Allows the app to make use of foreground services with the type \"systemExempted\"‎‏‎‎‏‎"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‏‏‎‎‎‏‎‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‏‎‏‎‎run foreground service with the type \"specialUse\"‎‏‎‎‏‎"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‏‏‏‎Allows the app to make use of foreground services with the type \"specialUse\"‎‏‎‎‏‎"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‎‏‏‎‎‎‏‎‏‏‎‏‎‎‏‎‏‏‏‎‎‎‏‎measure app storage space‎‏‎‎‏‎"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‏‎‎‎‎‏‏‎‏‏‏‏‏‎‎Allows the app to retrieve its code, data, and cache sizes‎‏‎‎‏‎"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‏‎‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‎‏‎‏‎‎‏‏‎‏‎‏‎‎‏‎‎modify system settings‎‏‎‎‏‎"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‏‏‎‎This app can record audio using the microphone while the app is in use.‎‏‎‎‏‎"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‏‎‎‏‎‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‏‏‎‏‎‏‏‏‎‎record audio in the background‎‏‎‎‏‎"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‎‎‎‏‏‎‏‏‎‏‎‎‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎This app can record audio using the microphone at any time.‎‏‎‎‏‎"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‎‏‏‎‎‎‏‏‏‏‎‏‏‏‎‎‏‎‎‏‎detect screen captures of app windows‎‏‎‎‏‎"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎This app will get notified when a screenshot is taken while the app is in use.‎‏‎‎‏‎"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‏‏‎‎‏‎‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎send commands to the SIM‎‏‎‎‏‎"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‎‎‎‎‏‏‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎‏‎‎‎‎‏‏‏‎‏‎‎‏‏‎‏‎‎‎‎‎‎‏‏‏‏‎‎‏‎‎‎‎Allows the app to send commands to the SIM. This is very dangerous.‎‏‎‎‏‎"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‏‎‎‎‎‎‎‎‎‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‏‏‏‎‎‏‎‏‎‏‎‏‏‏‏‎‎‏‎‎‎recognize physical activity‎‏‎‎‏‎"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‎‎‎‏‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‎‏‏‏‏‏‎‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‎Allows the app to read video files from your shared storage.‎‏‎‎‏‎"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‎‏‏‏‎‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‎‏‎‎‏‎‏‎read image files from shared storage‎‏‎‎‏‎"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‎‎‏‏‎‎‏‎‎‏‎‎‏‏‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‏‎Allows the app to read image files from your shared storage.‎‏‎‎‏‎"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‏‏‎‏‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‎‎‏‏‎‎‏‎‎‏‎‎‎‎‏‎‎read user selected image and video files from shared storage‎‏‎‎‏‎"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‎‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‏‏‏‎‏‎‎‏‏‎‎‏‎Allows the app to read image and video files that you select from your shared storage.‎‏‎‎‏‎"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‎‎‏‏‎‎‎‎‎‎‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‎‎‎modify or delete the contents of your shared storage‎‏‎‎‏‎"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‎‏‎‎‎‎‏‎‎‎‏‏‎‏‏‎‏‏‏‏‎‎Allows the app to write the contents of your shared storage.‎‏‎‎‏‎"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‎‎‏‎‏‎‎‎‏‏‏‏‎‎‎‎‎‎‏‎‏‎‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‎‎‎‎‏‎‏‎make/receive SIP calls‎‏‎‎‏‎"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‏‎‎‎‏‏‎‏‎‎‎‎‎‎‎‏‏‏‎‎‏‏‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‎‎‎‏‏‏‎‎‎UNINSTALL‎‏‎‎‏‎"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‏‎‏‎‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‎‏‎‎‏‎‏‏‏‏‏‎OPEN ANYWAY‎‏‎‎‏‎"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‎‎‏‎‎‎‎‎‏‎‏‎‏‎‎‎‏‎‏‏‎‎‎‎‎‎‎‎‎Harmful app detected‎‏‎‎‏‎"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‎‎‏‏‎‏‎‏‏‏‎‎‎‏‎‎‎Allow ‎‏‎‎‏‏‎<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>‎‏‎‎‏‏‏‎ to access all device logs?‎‏‎‎‏‎"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‎‏‎‏‏‎‎‏‎‏‎‎‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‏‏‎Allow one-time access‎‏‎‎‏‎"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‎‏‏‏‎‏‎‎‎‎‎‏‎‏‎‏‎‎‎‏‏‏‏‏‎‎‎‏‎‏‎Don’t allow‎‏‎‎‏‎"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‎Device logs record what happens on your device. Apps can use these logs to find and fix issues.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Some logs may contain sensitive info, so only allow apps you trust to access all device logs. ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎If you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.‎‏‎‎‏‎"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‏‏‎‎‎‎‏‎‏‏‎‎‎‎‏‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‎‎Device logs record what happens on your device. Apps can use these logs to find and fix issues.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Some logs may contain sensitive info, so only allow apps you trust to access all device logs. ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎If you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Learn more at g.co/android/devicelogs.‎‏‎‎‏‎"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‎‏‏‏‎‎‏‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎‎‏‎‏‎‎‏‏‎‎‏‏‎‎‎‎‎‎Don’t show again‎‏‎‎‏‎"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‏‎‎‎‎‏‏‎‏‎‎‏‎‏‏‏‎‏‎‎‏‏‏‏‎‏‎‎‏‎‎‎‏‏‏‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="APP_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎ wants to show ‎‏‎‎‏‏‎<xliff:g id="APP_2">%2$s</xliff:g>‎‏‎‎‏‏‏‎ slices‎‏‎‎‏‎"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‏‏‏‎‏‎‎‏‏‎‎‎‎‎‏‏‎‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‏‎‏‏‏‏‎‏‏‏‎Edit‎‏‎‎‏‎"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎‎‎‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‏‏‎Calls and notifications will vibrate‎‏‎‎‏‎"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‎‎‎‎‎‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‏‏‏‎‎‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‎‏‎Can’t access the phone’s camera from your ‎‏‎‎‏‏‎<xliff:g id="DEVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‎‏‏‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‏‎‏‏‏‎‏‎‏‏‎‏‎‎‎‎‏‏‎‏‏‎‏‎‎‎Can’t access the tablet’s camera from your ‎‏‎‎‏‏‎<xliff:g id="DEVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‏‏‎‏‎‎This can’t be accessed while streaming. Try on your phone instead.‎‏‎‎‏‎"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‎‏‏‎‎‏‎‎‎‏‎‎‏‏‎‏‎‎‎‎‏‎‏‎‎‎‏‏‎‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎Can’t view picture-in-picture while streaming‎‏‎‎‏‎"</string>
     <string name="system_locale_title" msgid="711882686834677268">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‎‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‎‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‏‎‏‏‎‎‎‎‏‎‏‎‎‎System default‎‏‎‎‏‎"</string>
     <string name="default_card_name" msgid="9198284935962911468">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎‏‏‎‎‎CARD ‎‏‎‎‏‏‎<xliff:g id="CARDNUMBER">%d</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
 </resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 6a45205..69ce57e 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Buzón de voz"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problema de conexión o código incorrecto de MMI."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Función no compatible."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"La operación está limitada a números de marcación fija."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"No se puede cambiar la configuración de desvío de llamadas de tu teléfono mientras usas el servicio de roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Se ha activado el servicio."</string>
@@ -73,6 +74,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Se cambiaron los datos a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Puedes cambiar esto cuando quieras en Configuración"</string>
     <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>
@@ -394,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite que la aplicación haga que algunas de sus partes se mantengan persistentes en la memoria. Esto puede limitar la memoria disponible para otras aplicaciones y ralentizar el dispositivo."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"ejecutar servicio en primer plano"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite que la app use servicios en primer plano."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"Ejecuta un servicio en primer plano con el tipo \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Permite que la app use servicios en primer plano con el tipo \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"Ejecuta un servicio en primer plano con el tipo \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Permite que la app use servicios en primer plano con el tipo \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"Ejecuta un servicio en primer plano con el tipo \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Permite que la app use servicios en primer plano con el tipo \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"Ejecuta un servicio en primer plano con el tipo \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Permite que la app use servicios en primer plano con el tipo \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"Ejecuta un servicio en primer plano con el tipo \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Permite que la app use servicios en primer plano con el tipo \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"Ejecuta un servicio en primer plano con el tipo \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Permite que la app use servicios en primer plano con el tipo \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"Ejecuta un servicio en primer plano con el tipo \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Permite que la app use servicios en primer plano con el tipo \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"Ejecuta un servicio en primer plano con el tipo \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Permite que la app use servicios en primer plano con el tipo \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"Ejecuta un servicio en primer plano con el tipo \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Permite que la app use servicios en primer plano con el tipo \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"Ejecuta un servicio en primer plano con el tipo \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Permite que la app use servicios en primer plano con el tipo \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"Ejecuta un servicio en primer plano con el tipo \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que la app use servicios en primer plano con el tipo \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"Ejecuta un servicio en primer plano con el tipo \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que la app use servicios en primer plano con el tipo \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"medir el espacio de almacenamiento de la aplicación"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite que la aplicación recupere su código, sus datos y los tamaños de la memoria caché."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modificar la configuración del sistema"</string>
@@ -441,11 +468,13 @@
     <string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"acceder a la ubicación en segundo plano"</string>
     <string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"Esta aplicación puede acceder a la ubicación en cualquier momento, aunque no la estés usando."</string>
     <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"cambiar tu configuración de audio"</string>
-    <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que la aplicación modifique la configuración de audio global, por ejemplo, el volumen y el altavoz de salida."</string>
+    <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que la aplicación modifique la configuración de audio global, por ejemplo, el volumen y la bocina de salida."</string>
     <string name="permlab_recordAudio" msgid="1208457423054219147">"grabar audio"</string>
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Esta app puede grabar audio con el micrófono mientras está en uso."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"grabar audio en segundo plano"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Esta app puede grabar audio con el micrófono en cualquier momento."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"Detección de capturas de pantalla de las ventanas de la app"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Esta app recibirá una notificación cuando se tome una captura de pantalla mientras esté en uso."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos a la tarjeta SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que la aplicación envíe comandos a la tarjeta SIM. Usar este permiso es peligroso."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconocer actividad física"</string>
@@ -697,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permite que la app lea los archivos de video del almacenamiento compartido."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"leer los archivos de imagen del almacenamiento compartido"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permite que la app lea los archivos de imagen del almacenamiento compartido."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"leer los archivos de imagen y video que el usuario haya seleccionado del almacenamiento compartido"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permite que la app lea los archivos de imagen y video que selecciones del almacenamiento compartido."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"cambiar o borrar contenido de almacenamiento compartido"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Editar almacen. compartido"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"realizar/recibir llamadas SIP"</string>
@@ -2047,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALAR"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ABRIR DE TODOS MODOS"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Se detectó una app dañina"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"¿Quieres permitir que <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acceda a todos los registros del dispositivo?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir acceso por única vez"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"No permitir"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Los registros del dispositivo permiten documentar lo que sucede en él. Las apps pueden usar estos registros para encontrar y solucionar problemas.\n\nEs posible que algunos registros del dispositivo contengan información sensible, por lo que solo debes permitir que accedan a todos ellos las apps que sean de tu confianza. \n\nSi no permites que esta app acceda a todos los registros del dispositivo, aún puede acceder a sus propios registros. Además, es posible que el fabricante del dispositivo acceda a algunos registros o información en tu dispositivo."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Los registros del dispositivo permiten documentar lo que sucede en él. Las apps pueden usar estos registros para encontrar y solucionar problemas.\n\nEs posible que algunos registros del dispositivo contengan información sensible, por lo que solo debes permitir que accedan a todos los registros las apps que sean de tu confianza. \n\nTen en cuenta que la app puede acceder a sus propios registros incluso si no permites que acceda a todos los registros del dispositivo. También es posible que el fabricante del dispositivo acceda a algunos registros o información en tu dispositivo.\n\nObtén más información en g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"No volver a mostrar"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quiere mostrar fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Vibrarán las llamadas y notificaciones"</string>
@@ -2293,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"No se puede acceder a la cámara del dispositivo desde tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"No se puede acceder a la cámara de la tablet desde tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"No se puede acceder a este contenido durante una transmisión. Inténtalo en tu teléfono."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"No puedes ver pantalla en pantalla mientras transmites"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Predeterminado del sistema"</string>
     <string name="default_card_name" msgid="9198284935962911468">"TARJETA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 66f67b3..91c12c9 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Buzón de voz"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Se ha producido un problema de conexión o el código MMI no es válido."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Función no disponible."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"La operación solo es válida para números de marcación fija."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"No se puede cambiar la configuración de desvío de llamadas desde tu teléfono mientras estás en roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"El servicio se ha habilitado."</string>
@@ -73,6 +74,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Datos cambiados a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Puedes cambiar esta opción en cualquier momento en Ajustes"</string>
     <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>
@@ -86,7 +89,7 @@
     <string name="notification_channel_network_alert" msgid="4788053066033851841">"Alertas"</string>
     <string name="notification_channel_call_forward" msgid="8230490317314272406">"Desvío de llamadas"</string>
     <string name="notification_channel_emergency_callback" msgid="54074839059123159">"Modo de devolución de llamada de emergencia"</string>
-    <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Estado de los datos móviles"</string>
+    <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Estado de datos móviles"</string>
     <string name="notification_channel_sms" msgid="1243384981025535724">"Mensajes SMS"</string>
     <string name="notification_channel_voice_mail" msgid="8457433203106654172">"Mensajes de voz"</string>
     <string name="notification_channel_wfc" msgid="9048240466765169038">"Llamada por Wi-Fi"</string>
@@ -394,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite que la aplicación haga que algunas de sus partes se mantengan en la memoria. Esto puede limitar la cantidad de memoria disponible para otras aplicaciones y ralentizar el teléfono."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"ejecutar servicio en primer plano"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite que la aplicación use servicios en primer plano."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"ejecutar un servicio en primer plano con el tipo \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Permite que la aplicación use servicios en primer plano con el tipo \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"ejecutar un servicio en primer plano con el tipo \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Permite que la aplicación use servicios en primer plano con el tipo \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"ejecutar un servicio en primer plano con el tipo \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Permite que la aplicación use servicios en primer plano con el tipo \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"ejecutar un servicio en primer plano con el tipo \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Permite que la aplicación use servicios en primer plano con el tipo \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"ejecutar un servicio en primer plano con el tipo \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Permite que la aplicación use servicios en primer plano con el tipo \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"ejecutar un servicio en primer plano con el tipo \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Permite que la aplicación use servicios en primer plano con el tipo \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"ejecutar un servicio en primer plano con el tipo \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Permite que la aplicación use servicios en primer plano con el tipo \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"ejecutar un servicio en primer plano con el tipo \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Permite que la aplicación use servicios en primer plano con el tipo \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"ejecutar un servicio en primer plano con el tipo \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Permite que la aplicación use servicios en primer plano con el tipo \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"ejecutar un servicio en primer plano con el tipo \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Permite que la aplicación use servicios en primer plano con el tipo \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"ejecutar un servicio en primer plano con el tipo \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que la aplicación use servicios en primer plano con el tipo \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"ejecutar un servicio en primer plano con el tipo \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que la aplicación use servicios en primer plano con el tipo \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"medir el espacio de almacenamiento de la aplicación"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite que la aplicación recupere su código, sus datos y los tamaños de caché."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modificar los ajustes del sistema"</string>
@@ -446,6 +473,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Esta aplicación puede grabar audio con el micrófono mientras la estés usando."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Grabar audio en segundo plano"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Esta aplicación puede grabar audio con el micrófono en cualquier momento."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detectar capturas de pantalla de ventanas de la aplicación"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Esta aplicación recibirá un aviso cuando se haga una captura de pantalla mientras está en uso."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos a la tarjeta SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que la aplicación envíe comandos a la tarjeta SIM. Este permiso es muy peligroso."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconocer actividad física"</string>
@@ -697,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permite que la aplicación lea archivos de vídeo desde tu almacenamiento compartido."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"leer archivos de imagen desde el almacenamiento compartido"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permite que la aplicación lea archivos de imagen desde tu almacenamiento compartido."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"leer los archivos de imagen y de vídeo que el usuario seleccione del almacenamiento compartido"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permite que la aplicación lea los archivos de imagen y de vídeo que selecciones del almacenamiento compartido."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"editar/eliminar contenido de almacenamiento compartido"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Permite que app edite contenido de almacenamiento compartido."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"hacer/recibir llamadas SIP"</string>
@@ -2047,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALAR"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ABRIR IGUALMENTE"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Se ha detectado una aplicación dañina"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"¿Permitir que <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acceda a todos los registros del dispositivo?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir el acceso una vez"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"No permitir"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Los registros del dispositivo documentan lo que sucede en tu dispositivo. Las aplicaciones pueden usar estos registros para encontrar y solucionar problemas.\n\nComo algunos registros pueden contener información sensible, es mejor que solo permitas que accedan a ellos las aplicaciones en las que confíes. \n\nAunque no permitas que esta aplicación acceda a todos los registros del dispositivo, aún podrá acceder a sus propios registros. El fabricante de tu dispositivo aún puede acceder a algunos registros o información de tu dispositivo."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Los registros del dispositivo documentan lo que sucede en tu dispositivo. Las aplicaciones pueden usar estos registros para encontrar y solucionar problemas.\n\nComo algunos registros pueden contener información sensible, es mejor que solo permitas que accedan a ellos las aplicaciones en las que confíes. \n\nAunque no permitas que esta aplicación acceda a todos los registros del dispositivo, podrá seguir accediendo a sus propios registros. El fabricante de tu dispositivo aún puede acceder a algunos registros o información de tu dispositivo.\n\nObtén más información en g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"No volver a mostrar"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quiere mostrar fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Las llamadas y las notificaciones vibrarán"</string>
@@ -2293,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"No se puede acceder a la cámara del teléfono desde tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"No se puede acceder a la cámara del tablet desde tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"No se puede acceder a este contenido durante una emisión. Prueba en tu teléfono."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"No se puede usar imagen en imagen mientras se emite contenido"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Predeterminado del sistema"</string>
     <string name="default_card_name" msgid="9198284935962911468">"TARJETA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 349a6b2..42a383c 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Kõnepost"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Ühendusprobleem või kehtetu MMI-kood."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funktsiooni ei toetata."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Toiming on ainult fikseeritud valimisnumbritele."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Kõne suunamise seadeid ei saa rändluse ajal teie telefonis muuta."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Teenus on lubatud."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Mobiilne andmeside lülitati operaatorile <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Saate seda igal ajal seadetes muuta"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Võimaldab rakendusel muuta oma osi mälus püsivaks. See võib piirata teistele (telefoni aeglasemaks muutvatele) rakendustele saadaolevat mälu."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"käita esiplaanil olevat teenust"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „camera“"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „camera“."</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „connectedDevice“"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „connectedDevice“."</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „dataSync“"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „dataSync“."</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „location“"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „location“."</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „mediaPlayback“"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „mediaPlayback“."</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „mediaProjection“"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „mediaProjection“."</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „microphone“"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „microphone“."</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „phoneCall“"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „phoneCall“."</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „health“"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „health“."</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „remoteMessaging“"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „remoteMessaging“."</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „systemExempted“"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „systemExempted“."</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „specialUse“"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „specialUse“."</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"Rakenduse mäluruumi mõõtmine"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Võimaldab rakendusel tuua oma koodi, andmed ja vahemälu suurused"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"muutke süsteemi seadeid"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"See rakendus saab mikrofoniga heli salvestada siis, kui rakendus on kasutusel."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Taustal heli salvestamine."</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"See rakendus saab mikrofoniga heli salvestada mis tahes ajal."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"rakenduste akende puhul ekraanikuva jäädvustamise tuvastamine"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Rakendust teavitatakse, kui selle kasutamise ajal jäädvustatakse ekraanipilt."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIM-kaardile käskluste saatmine"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Lubab rakendusel saata käske SIM-kaardile. See on väga ohtlik."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"füüsiliste tegevuste tuvastamine"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Võimaldab rakendusel lugeda teie jagatud salvestusruumis olevaid videofaile."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"lugeda teie jagatud salvestusruumis olevaid pildifaile"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Võimaldab rakendusel lugeda teie jagatud salvestusruumis olevaid pildifaile."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lugeda jagatud salvestusruumis olevaid kasutaja valitud pildi- ja videofaile"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Võimaldab rakendusel lugeda teie jagatud salvestusruumis olevaid valitud pildi- ja videofaile."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"Jagatud salvestusruumi sisu muutmine või kustutamine"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Lubab rakendusel kirjutada jagatud salvestusruumi sisu."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP-kõnede tegemine/vastuvõtmine"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALLI"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"AVA IKKA"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Tuvastati kahjulik rakendus"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Kas anda rakendusele <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> juurdepääs kõigile seadmelogidele?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Luba ühekordne juurdepääs"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ära luba"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Seadmelogid jäädvustavad, mis teie seadmes toimub. Rakendused saavad neid logisid kasutada probleemide tuvastamiseks ja lahendamiseks.\n\nMõned logid võivad sisaldada tundlikku teavet, seega lubage juurdepääs kõigile seadmelogidele ainult rakendustele, mida usaldate. \n\nKui te ei luba sellel rakendusel kõigile seadmelogidele juurde pääseda, pääseb see siiski juurde oma logidele. Teie seadme tootja võib teie seadmes siiski teatud logidele või teabele juurde pääseda."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Seadmelogid jäädvustavad, mis teie seadmes toimub. Rakendused saavad neid logisid kasutada probleemide tuvastamiseks ja lahendamiseks.\n\nMõned logid võivad sisaldada tundlikku teavet, seega lubage juurdepääs kõigile seadmelogidele ainult rakendustele, mida usaldate. \n\nKui te ei luba sellel rakendusel kõigile seadmelogidele juurde pääseda, pääseb see siiski juurde oma logidele. Teie seadme tootja võib teie seadmes siiski teatud logidele või teabele juurde pääseda.\n\nLugege lisateavet aadressil g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ära kuva uuesti"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Rakendus <xliff:g id="APP_0">%1$s</xliff:g> soovib näidata rakenduse <xliff:g id="APP_2">%2$s</xliff:g> lõike"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Muuda"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Kõnede ja märguannete puhul seade vibreerib"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Teie seadmest <xliff:g id="DEVICE">%1$s</xliff:g> ei pääse telefoni kaamerale juurde."</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Teie seadmest <xliff:g id="DEVICE">%1$s</xliff:g> ei pääse tahvelarvuti kaamerale juurde"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Sellele ei pääse voogesituse ajal juurde. Proovige juurde pääseda oma telefonis."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Voogesitamise ajal ei saa pilt pildis funktsiooni kasutada"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Süsteemi vaikeseade"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KAART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index d4759d5..878b57e 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Erantzungailua"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Konexio-arazoren bat gertatu da edo MMI kodea baliogabea da."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Ez da onartzen eginbidea."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Eragiketa markatze finkoko zenbakietara murriztua dago."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Ezin dira aldatu deiak desbideratzeko ezarpenak telefonoa ibiltaritzan dagoenean."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Zerbitzua gaitu da."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> operadorearen datu-konexiora aldatu zara"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Ezarpenak atalean alda dezakezu aukera hori"</string>
     <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>
@@ -175,7 +178,7 @@
     <string name="low_memory" product="watch" msgid="3479447988234030194">"Erlojuaren memoria beteta dago. Tokia egiteko, ezabatu fitxategi batzuk."</string>
     <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV gailuaren memoria beteta dago. Tokia egiteko, ezabatu fitxategi batzuk."</string>
     <string name="low_memory" product="default" msgid="2539532364144025569">"Telefonoaren memoria beteta dago. Tokia egiteko, ezabatu fitxategi batzuk."</string>
-    <string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{Ziurtagiri-emaile bat dago instalatuta}other{Ziurtagiri-emaile bat baino gehiago daude instalatuta}}"</string>
+    <string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{Autoritate ziurtagiri-emaile bat dago instalatuta}other{Autoritate ziurtagiri-emaile bat baino gehiago daude instalatuta}}"</string>
     <string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"Hirugarren alderdi ezezagun baten arabera"</string>
     <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"Laneko profilen administratzaileak"</string>
     <string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"<xliff:g id="MANAGING_DOMAIN">%s</xliff:g> da arduraduna"</string>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Beren zati batzuk memoria modu iraunkorrean ezartzeko baimena ematen die aplikazioei. Horrela, beste aplikazioek erabilgarri duten memoria murritz daiteke eta telefonoa motel daiteke."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"abiarazi zerbitzuak aurreko planoan"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Aurreko planoko zerbitzuak erabiltzea baimentzen dio aplikazioari."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"exekutatu aurreko planoko zerbitzu bat (camera motakoa)"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Aurreko planoko zerbitzuak (camera motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"exekutatu aurreko planoko zerbitzu bat (connectedDevice motakoa)"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Aurreko planoko zerbitzuak (connectedDevice motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"exekutatu aurreko planoko zerbitzu bat (dataSync motakoa)"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Aurreko planoko zerbitzuak (dataSync motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"exekutatu aurreko planoko zerbitzu bat (location motakoa)"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Aurreko planoko zerbitzuak (location motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"exekutatu aurreko planoko zerbitzu bat (mediaPlayback motakoa)"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Aurreko planoko zerbitzuak (mediaPlayback motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"exekutatu aurreko planoko zerbitzu bat (mediaProjection motakoa)"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Aurreko planoko zerbitzuak (mediaProjection motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"exekutatu aurreko planoko zerbitzu bat (microphone motakoa)"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Aurreko planoko zerbitzuak (microphone motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"exekutatu aurreko planoko zerbitzu bat (phoneCall motakoa)"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Aurreko planoko zerbitzuak (phoneCall motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"exekutatu aurreko planoko zerbitzu bat (health motakoa)"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Aurreko planoko zerbitzuak (health motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"exekutatu aurreko planoko zerbitzu bat (remoteMessaging motakoa)"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Aurreko planoko zerbitzuak (remoteMessaging motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"exekutatu aurreko planoko zerbitzu bat (systemExempted motakoa)"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Aurreko planoko zerbitzuak (systemExempted motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"exekutatu aurreko planoko zerbitzu bat (specialUse motakoa)"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Aurreko planoko zerbitzuak (specialUse motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"neurtu aplikazioen biltegiratzeko tokia"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Bere kodea, datuak eta cache-tamainak eskuratzeko baimena ematen die aplikazioei."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"aldatu sistemaren ezarpenak"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Aplikazioak abian den bitartean erabil dezake mikrofonoa audioa grabatzeko."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Audioa grabatu atzeko planoan."</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Aplikazioak edonoiz erabil dezake mikrofonoa audioa grabatzeko."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"hauteman pantaila-argazkiak aplikazioen leihoetan"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Aplikazioa erabili bitartean pantaila-argazki bat ateratzen denean, jakinarazpen bat jasoko duzu aplikazioan."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"bidali aginduak SIM txartelera"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM txartelera aginduak bidaltzeko baimena ematen die aplikazioei. Oso arriskutsua da."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"hauteman jarduera fisikoa"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Biltegi partekatuko bideo-fitxategiak irakurtzeko baimena ematen die aplikazioei."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"irakurri biltegi partekatuko irudi-fitxategiak"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Biltegi partekatuko irudi-fitxategiak irakurtzeko baimena ematen die aplikazioei."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"irakurri biltegi partekatuko irudi- eta bideo-fitxategiak"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Biltegi partekatuko irudi- eta bideo-fitxategiak irakurtzeko baimena ematen die aplikazioei."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"aldatu edo ezabatu biltegi partekatuko edukia"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Biltegi partekatuko edukian idazteko baimena ematen die aplikazioei."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"egin/jaso SIP deiak"</string>
@@ -1454,7 +1485,7 @@
     <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"Paketeak ezabatzeko eskatzea baimentzen die aplikazioei."</string>
     <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"eskatu bateria-optimizazioei ez ikusi egitea"</string>
     <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Bateriaren optimizazioei ez ikusi egiteko baimena eskatzea baimentzen die aplikazioei."</string>
-    <string name="permlab_queryAllPackages" msgid="2928450604653281650">"Kontsultatu pakete guztiak"</string>
+    <string name="permlab_queryAllPackages" msgid="2928450604653281650">"kontsultatu pakete guztiak"</string>
     <string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Instalatutako pakete guztiak ikusteko baimena ematen dio aplikazioari."</string>
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Sakatu birritan zooma kontrolatzeko"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"Ezin izan da widgeta gehitu."</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALATU"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"IREKI, HALA ERE"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Aplikazio kaltegarri bat hauteman da"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Gailuko erregistro guztiak atzitzeko baimena eman nahi diozu <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> aplikazioari?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Eman behin erabiltzeko baimena"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ez eman baimenik"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Gailuko erregistroetan gailuan gertatzen den guztia gordetzen da. Arazoak bilatu eta konpontzeko erabil ditzakete aplikazioek erregistro horiek.\n\nBaliteke erregistro batzuek kontuzko informazioa edukitzea. Beraz, eman gailuko erregistro guztiak atzitzeko baimena fidagarritzat jotzen dituzun aplikazioei bakarrik. \n\nNahiz eta gailuko erregistro guztiak atzitzeko baimena ez eman aplikazio honi, aplikazioak hari dagozkion erregistroak atzitu ahalko ditu. Gainera, baliteke gailuaren fabrikatzaileak gailuko erregistro edo datu batzuk atzitu ahal izatea."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Gailuko erregistroetan gailuan gertatzen den guztia gordetzen da. Arazoak bilatu eta konpontzeko erabil ditzakete aplikazioek erregistro horiek.\n\nBaliteke erregistro batzuek kontuzko informazioa edukitzea. Beraz, eman gailuko erregistro guztiak atzitzeko baimena fidagarritzat jotzen dituzun aplikazioei bakarrik. \n\nNahiz eta gailuko erregistro guztiak atzitzeko baimena ez eman aplikazio honi, aplikazioak hari dagozkion erregistroak atzitu ahalko ditu. Gainera, baliteke gailuaren fabrikatzaileak gailuko erregistro edo datu batzuk atzitu ahal izatea.\n\nLortu informazio gehiago g.co/android/devicelogs helbidean."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ez erakutsi berriro"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> aplikazioak <xliff:g id="APP_2">%2$s</xliff:g> aplikazioaren zatiak erakutsi nahi ditu"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editatu"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Dar-dar egingo du deiak eta jakinarazpenak jasotzean"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ezin da atzitu telefonoaren kamera <xliff:g id="DEVICE">%1$s</xliff:g> gailutik"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ezin da atzitu tabletaren kamera <xliff:g id="DEVICE">%1$s</xliff:g> gailutik"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Ezin da atzitu edukia hura igorri bitartean. Oraingo gailuaren ordez, erabili telefonoa."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Edukia zuzenean erreproduzitu bitartean ezin da pantaila txiki gainjarrian ikusi"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Sistemaren balio lehenetsia"</string>
     <string name="default_card_name" msgid="9198284935962911468">"<xliff:g id="CARDNUMBER">%d</xliff:g> TXARTELA"</string>
 </resources>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 4064353..a65910b 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"پست صوتی"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"‏مشکل در اتصال یا کد MMI نامعتبر."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"از این ویژگی پشتیبانی نمی‌شود."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"عملکرد فقط به شماره‌های شماره‌گیری ثابت محدود است."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"وقتی درحال فراگردی هستید، نمی‌توانید تنظیمات هدایت تماس را از تلفنتان تغییر دهید."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"سرویس فعال شد."</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"به برنامه امکان می‌دهد قسمت‌هایی از خود را در حافظه دائمی کند. این کار حافظه موجود را برای سایر برنامه‌ها محدود کرده و باعث کندی تلفن می‌شود."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"اجرای سرویس پیش‌زمینه"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌زمینه استفاده کند."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"اجرای سرویس پیش‌نما از نوع «دوربین»"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «دوربین» استفاده کند"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"اجرای سرویس پیش‌نما از نوع «دستگاه متصل»"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «دستگاه متصل» استفاده کند"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"اجرای سرویس پیش‌نما از نوع «همگام‌سازی داده»"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «همگام‌سازی داده» استفاده کند"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"اجرای سرویس پیش‌نما از نوع «مکان»"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «مکان» استفاده کند"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"اجرای سرویس پیش‌نما از نوع «بازپخش رسانه»"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «بازپخش رسانه» استفاده کند"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"اجرای سرویس پیش‌نما از نوع «ارسال محتوا»"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «ارسال محتوا» استفاده کند"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"اجرای سرویس پیش‌نما از نوع «میکروفون»"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «میکروفون» استفاده کند"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"اجرای سرویس پیش‌نما از نوع «تماس تلفنی»"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «تماس تلفنی» استفاده کند"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"اجرای سرویس پیش‌نما از نوع «سلامتی»"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «سلامتی» استفاده کند"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"اجرای سرویس پیش‌نما از نوع «پیام‌رسانی ازراه‌دور»"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «پیام‌رسانی ازراه‌دور» استفاده کند"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"اجرای سرویس پیش‌نما از نوع «معافیت سیستم»"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «معافیت سیستم» استفاده کند"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"اجرای سرویس پیش‌نما از نوع «استفاده ویژه»"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «استفاده ویژه» استفاده کند"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"اندازه‌گیری اندازه فضای ذخیره‌سازی برنامه"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"‏به برنامه اجازه می‎دهد تا کدها، داده‎ها و اندازه‎های حافظهٔ پنهان خود را بازیابی کند"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"تغییر تنظیمات سیستم"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"این برنامه وقتی درحال استفاده است، می‌تواند بااستفاده از میکروفون صدا ضبط کند."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ضبط صدا در پس‌زمینه"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"این برنامه می‌تواند در هرزمانی با استفاده از میکروفون صدا ضبط کند."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"تشخیص ضبط صفحه‌نمایش از پنجره برنامه‌ها"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"وقتی نماگرفتی درحین استفاده از برنامه گرفته می‌شود، به این برنامه اطلاع داده می‌شود."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"ارسال فرمان به سیم کارت"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"به برنامه اجازه ارسال دستورات به سیم کارت را می‌دهد. این بسیار خطرناک است."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"تشخیص فعالیت فیزیکی"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"به برنامه اجازه می‌دهد فایل‌های ویدیویی موجود در فضای ذخیره‌سازی هم‌رسانی‌شده را بخواند."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"خواندن فایل‌های تصویری موجود در فضای ذخیره‌سازی مشترک"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"به برنامه اجازه می‌دهد فایل‌های تصویری موجود در فضای ذخیره‌سازی مشترک را بخواند."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"خواندن فایل‌های تصویری و ویدیویی انتخابی کاربر از فضای ذخیره‌سازی مشترک"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"به برنامه جازه می‌دهد فایل‌های تصویری و ویدیویی‌ای را که از فضای ذخیره‌سازی مشترکتان انتخاب می‌کنید بخواند."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"تغییر یا حذف محتوای فضای ذخیره‌سازی مشترک"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"به برنامه اجازه می‌دهد محتوای فضای ذخیره‌سازی مشترکتان را بنویسد."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"‏تماس گرفتن/دریافت تماس از طریق SIP"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"حذف نصب"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"درهرصورت باز شود"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"برنامه مضر شناسایی شد"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"به <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> اجازه می‌دهید به همه گزارش‌های دستگاه دسترسی داشته باشد؟"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"مجاز کردن دسترسی یک‌باره"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"اجازه ندادن"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"گزارش‌های دستگاه آنچه را در دستگاهتان رخ می‌دهد ثبت می‌کند. برنامه‌ها می‌توانند از این گزارش‌ها برای پیدا کردن مشکلات و رفع آن‌ها استفاده کنند.\n\nبرخی‌از گزارش‌ها ممکن است حاوی اطلاعات حساس باشند، بنابراین فقط به برنامه‌های مورداعتمادتان اجازه دسترسی به همه گزارش‌های دستگاه را بدهید. \n\nاگر به این برنامه اجازه ندهید به همه گزارش‌های دستگاه دسترسی داشته باشد، همچنان می‌تواند به گزارش‌های خودش دسترسی داشته باشد. سازنده دستگاه نیز ممکن است همچنان بتواند به برخی‌از گزارش‌ها یا اطلاعات دستگاهتان دسترسی داشته باشد."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"‏گزارش‌های دستگاه آنچه را در دستگاهتان رخ می‌دهد ثبت می‌کند. برنامه‌ها می‌توانند از این گزارش‌ها برای پیدا کردن مشکلات و رفع آن‌ها استفاده کنند.\n\nبرخی‌از گزارش‌ها ممکن است حاوی اطلاعات حساس باشند، بنابراین فقط به برنامه‌های مورداعتمادتان اجازه دسترسی به همه گزارش‌های دستگاه را بدهید. \n\nاگر به این برنامه اجازه ندهید به همه گزارش‌های دستگاه دسترسی داشته باشد، همچنان می‌تواند به گزارش‌های خودش دسترسی داشته باشد. سازنده دستگاه نیز ممکن است همچنان بتواند به برخی‌از گزارش‌ها یا اطلاعات دستگاهتان دسترسی داشته باشد.\n\nاطلاعات بیشتر: g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"دوباره نشان داده نشود"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> می‌خواهد تکه‌های <xliff:g id="APP_2">%2$s</xliff:g> را نشان دهد"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ویرایش"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"دستگاهتان برای تماس‌ها و اعلان‌ها می‌لرزد"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"از <xliff:g id="DEVICE">%1$s</xliff:g> به دوربین تلفن دسترسی ندارید"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"نمی‌توان از <xliff:g id="DEVICE">%1$s</xliff:g> شما به دوربین رایانه لوحی دسترسی داشت"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"درحین جاری‌سازی، نمی‌توانید به آن دسترسی داشته باشید. دسترسی به آن را در تلفنتان امتحان کنید."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"هنگام جاری‌سازی نمی‌توان تصویر در تصویر را مشاهده کرد"</string>
     <string name="system_locale_title" msgid="711882686834677268">"پیش‌فرض سیستم"</string>
     <string name="default_card_name" msgid="9198284935962911468">"کارت <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 8fedfb7..02ae893 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Vastaaja"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Yhteysongelma tai virheellinen MMI-koodi."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Ominaisuutta ei tueta."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Voit suorittaa toiminnon vain sallitut puhelut -numeroihin."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Soitonsiirtoasetuksia ei voi muuttaa puhelimella roaming-tilassa."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Palvelu otettiin käyttöön."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Data vaihdettu: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Voit vaihtaa valintasi milloin tahansa asetuksista."</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Antaa sovelluksen lisätä omia osiaan muistiin pysyvästi. Tämä voi rajoittaa muiden sovellusten käytettävissä olevaa muistia ja hidastaa puhelimen toimintaa."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"suorita etualan palvelu"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Sallii sovelluksen käyttää etualan palveluja"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"käyttää etualan palveluja, joiden tyyppi on \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"käyttää etualan palveluja, joiden tyyppi on \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"käyttää etualan palveluja, joiden tyyppi on \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"käyttää etualan palveluja, joiden tyyppi on \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"käyttää etualan palveluja, joiden tyyppi on \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"käyttää etualan palveluja, joiden tyyppi on \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"käyttää etualan palveluja, joiden tyyppi on \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"käyttää etualan palveluja, joiden tyyppi on \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"käyttää etualan palveluja, joiden tyyppi on \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"käyttää etualan palveluja, joiden tyyppi on \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"käyttää etualan palveluja, joiden tyyppi on \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"käyttää etualan palveluja, joiden tyyppi on \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"sovellusten tallennustilan mittaaminen"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Antaa sovelluksen noutaa sen koodin, tietojen ja välimuistin koot."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"muokkaa järjestelmän asetuksia"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Tämä sovellus voi tallentaa mikrofonilla audiota, kun sovellusta käytetään."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"tallentaa audiota taustalla"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Tämä sovellus voi tallentaa mikrofonilla audiota koska tahansa."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"havaitse sovelluksen ikkunoista otetut kuvakaappaukset"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Sovellukseen tulee ilmoitus, kun kuvakaappaus otetaan käytettäessä sovellusta."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"lähettää komentoja SIM-kortille"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Antaa sovelluksen lähettää komentoja SIM-kortille. Tämä ei ole turvallista."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"tunnistaa liikkumisen"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Sallii sovelluksen lukea jaetun tallennustilan videotiedostoja."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"lue jaetun tallennustilan kuvatiedostoja"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Sallii sovelluksen lukea jaetun tallennustilan kuvatiedostoja."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lukulupa jaetun tallennustilan kuva- ja videotiedostoihin, jotka käyttäjä on valinnut"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Sallii sovelluksen lukea kuva- ja videotiedostoja, joita valitset jaetusta tallennustilasta."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"muokata tai poistaa jaetun tallennustilan sisältöä"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Antaa sovelluksen kirjoittaa jaetun tallennustilan sisällön."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"soita/vastaanota SIP-puheluja"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"POISTA"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"AVAA SILTI"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Haitallinen sovellus havaittu"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Saako <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> pääsyn kaikkiin laitelokeihin?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Salli kertaluonteinen pääsy"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Älä salli"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Laitteen tapahtumat tallentuvat laitelokeihin. Niiden avulla sovellukset voivat löytää ja korjata ongelmia.\n\nJotkin lokit voivat sisältää arkaluontoista tietoa, joten salli pääsy kaikkiin laitelokeihin vain sovelluksille, joihin luotat. \n\nJos et salli tälle sovellukselle pääsyä kaikkiin laitelokeihin, sillä on kuitenkin pääsy sen omiin lokeihin. Laitteen valmistajalla voi olla pääsy joihinkin lokeihin tai tietoihin laitteella."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Laitteen tapahtumat tallentuvat laitelokeihin. Niiden avulla sovellukset voivat löytää ja korjata ongelmia.\n\nJotkin lokit voivat sisältää arkaluontoista tietoa, joten salli pääsy kaikkiin laitelokeihin vain sovelluksille, joihin luotat. \n\nJos et salli tälle sovellukselle pääsyä kaikkiin laitelokeihin, sillä on kuitenkin pääsy sen omiin lokeihin. Laitteen valmistajalla voi olla pääsy joihinkin lokeihin tai tietoihin laitteella.\n\nLue lisää osoitteessa g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Älä näytä uudelleen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> haluaa näyttää osia sovelluksesta <xliff:g id="APP_2">%2$s</xliff:g>."</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Muokkaa"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Puhelut ja ilmoitukset värisevät"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> ei pääse puhelimen kameraan"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> ei pääse tabletin kameraan"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Sisältöön ei saa pääsyä striimauksen aikana. Kokeile striimausta puhelimella."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Pikkuruutua ei voi nähdä striimauksen aikana"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Järjestelmän oletusarvo"</string>
     <string name="default_card_name" msgid="9198284935962911468">"Kortti: <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index e63b734..264cf67 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Messagerie vocale"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problème de connexion ou code IHM incorrect"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Fonctionnalité non prise en charge."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Opération réservée aux numéros autorisés"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Impossible de modifier les paramètres de transfert d\'appel sur votre téléphone lorsque vous êtes en itinérance."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Le service a été activé."</string>
@@ -73,6 +74,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Données changées à <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Vous pouvez modifier cette option en tout temps dans les paramètres"</string>
     <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>
@@ -394,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permet à l\'application de rendre certains de ces composants persistants dans la mémoire. Cette autorisation peut limiter la mémoire disponible pour d\'autres applications et ralentir le téléphone."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"exécuter le service en premier plan"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Permet à l\'application d\'utiliser les services en premier plan."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"exécuter le service d\'avant-plan avec le type « appareil photo »"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « appareil photo »"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"exécuter le service d\'avant-plan avec le type « appareil connecté »"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « appareil connecté »"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"exécuter le service d\'avant-plan avec le type « synchronisation des données »"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « synchronisation des données »"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"exécuter le service d\'avant-plan avec le type « emplacement »"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « emplacement »"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"exécuter le service d\'avant-plan avec le type « lecture multimédia »"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « lecture multimédia »"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"exécuter le service d\'avant-plan avec le type « projection de contenus multimédias »"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « projection de contenus multimédias »"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"exécuter le service d\'avant-plan avec le type « microphone »"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « microphone »"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"exécuter le service d\'avant-plan avec le type « appel téléphonique »"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « appel téléphonique »"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"exécuter le service d\'avant-plan avec le type « santé »"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « santé »"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"exécuter le service d\'avant-plan avec le type « messagerie à distance »"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « messagerie à distance »"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"exécuter le service d\'avant-plan avec le type « système exempté »"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « système exempté »"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"exécuter le service d\'avant-plan avec le type « usage spécial »"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « usage spécial »"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"évaluer l\'espace de stockage de l\'application"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Permet à l\'application de récupérer la taille de son code, de ses données et de sa mémoire cache."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modifier les paramètres du système"</string>
@@ -446,6 +473,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Cette application peut enregistrer de l\'audio à l\'aide du microphone lorsque vous utilisez l\'application."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"enregistrer de l\'audio en arrière-plan"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Cette application peut enregistrer de l\'audio à l\'aide du microphone en tout temps."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"détecter les captures d\'écran des fenêtres d\'application"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Cette application recevra une notification lorsqu\'une capture d\'écran sera prise pendant que l\'application est en cours d\'utilisation."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"envoyer des commandes à la carte SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permet à l\'application d\'envoyer des commandes à la carte SIM. Cette fonctionnalité est très dangereuse."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconnaître les activités physiques"</string>
@@ -697,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permet à l\'application de lire les fichiers vidéo de votre espace de stockage partagé."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"lire des fichiers d\'image à partir de l\'espace de stockage partagé"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permet à l\'application de lire les fichiers d\'image de votre espace de stockage partagé."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lire les fichiers d\'images et de vidéos sélectionnés par l\'utilisateur dans l\'espace de stockage partagé"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permet à l\'application de lire les fichiers d\'images et de vidéos que vous sélectionnez dans votre espace de stockage partagé."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"modifier ou supprimer le contenu de votre espace de stockage partagé"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Autorise l\'application à écrire le contenu de votre espace de stockage partagé."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"faire et recevoir des appels SIP"</string>
@@ -2047,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DÉSINSTALLER"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OUVRIR QUAND MÊME"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Une application nuisible a été détectée"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Autoriser <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> à accéder à l\'ensemble des journaux de l\'appareil?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Autoriser un accès unique"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ne pas autoriser"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Les journaux de l\'appareil enregistrent ce qui se passe sur celui-ci. Les applications peuvent utiliser ces journaux pour trouver et résoudre des problèmes.\n\nCertains journaux peuvent contenir des renseignements confidentiels. N\'autorisez donc que les applications auxquelles vous faites confiance puisque celles-ci pourront accéder à l\'ensemble des journaux de l\'appareil. \n\nMême si vous n\'autorisez pas cette application à accéder à l\'ensemble des journaux de l\'appareil, elle aura toujours accès à ses propres journaux. Le fabricant de votre appareil pourrait toujours être en mesure d\'accéder à certains journaux ou renseignements sur votre appareil."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Les journaux de l\'appareil enregistrent ce qui se passe sur celui-ci. Les applications peuvent utiliser ces journaux pour trouver et résoudre des problèmes.\n\nCertains journaux peuvent contenir des renseignements confidentiels. N\'autorisez donc que les applications auxquelles vous faites confiance puisque celles-ci pourront accéder à l\'ensemble des journaux de l\'appareil. \n\nMême si vous n\'autorisez pas cette application à accéder à l\'ensemble des journaux de l\'appareil, elle aura toujours accès à ses propres journaux. Le fabricant de votre appareil pourrait toujours être en mesure d\'accéder à certains journaux ou renseignements sur votre appareil.\n\nPour en savoir plus, consultez la page g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne plus afficher"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> souhaite afficher <xliff:g id="APP_2">%2$s</xliff:g> tranches"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Modifier"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Les appels et les notifications vibreront"</string>
@@ -2121,7 +2146,7 @@
     <string name="accessibility_system_action_notifications_label" msgid="6083767351772162010">"Notifications"</string>
     <string name="accessibility_system_action_quick_settings_label" msgid="4583900123506773783">"Paramètres rapides"</string>
     <string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Boîte de dialogue sur l\'alimentation"</string>
-    <string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Écran de verrouillage"</string>
+    <string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Verrouiller l\'écran"</string>
     <string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Capture d\'écran"</string>
     <string name="accessibility_system_action_headset_hook_label" msgid="8524691721287425468">"Crochet de casque d\'écoute"</string>
     <string name="accessibility_system_action_on_screen_a11y_shortcut_label" msgid="8488701469459210309">"Raccourci d\'accessibilité à l\'écran"</string>
@@ -2293,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Impossible d\'accéder à l\'appareil photo du téléphone à partir de votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Impossible d\'accéder à l\'appareil photo de la tablette à partir de votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Vous ne pouvez pas y accéder lorsque vous utilisez la diffusion en continu. Essayez sur votre téléphone à la place."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Impossible d\'afficher des incrustations d\'image pendant une diffusion en continu"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Paramètre système par défaut"</string>
     <string name="default_card_name" msgid="9198284935962911468">"CARTE <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 019fdf2..0e4bb4c 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Messagerie vocale"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problème de connexion ou code IHM non valide."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Fonctionnalité non disponible."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Opération réservée aux numéros autorisés"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Impossible de modifier les paramètres de transfert d\'appel depuis votre téléphone lorsque vous êtes en itinérance."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Le service a été activé."</string>
@@ -73,6 +74,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Données transférées vers <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Vous pouvez modifier cette option à tout moment dans les paramètres."</string>
     <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>
@@ -394,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permet à l\'application de rendre certains de ces composants persistants dans la mémoire. Cette autorisation peut limiter la mémoire disponible pour d\'autres applications et ralentir le téléphone."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"exécuter un service de premier plan"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Autorise l\'application à utiliser des services de premier plan."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"exécuter un service de premier plan avec le type \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Autorise l\'appli à utiliser les services de premier plan avec le type \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"exécuter un service de premier plan avec le type \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Autorise l\'appli à utiliser les services de premier plan avec le type \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"exécuter un service de premier plan avec le type \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Autorise l\'appli à utiliser les services de premier plan avec le type \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"exécuter un service de premier plan avec le type \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Autorise l\'appli à utiliser les services de premier plan avec le type \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"exécuter un service de premier plan avec le type \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Autorise l\'appli à utiliser les services de premier plan avec le type \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"exécuter un service de premier plan avec le type \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Autorise l\'appli à utiliser les services de premier plan avec le type \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"exécuter un service de premier plan avec le type \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Autorise l\'appli à utiliser les services de premier plan avec le type \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"exécuter un service de premier plan avec le type \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Autorise l\'appli à utiliser les services de premier plan avec le type \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"exécuter un service de premier plan avec le type \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Autorise l\'appli à utiliser les services de premier plan avec le type \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"exécuter un service de premier plan avec le type \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Autorise l\'appli à utiliser les services de premier plan avec le type \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"exécuter un service de premier plan avec le type \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Autorise l\'appli à utiliser les services de premier plan avec le type \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"exécuter un service de premier plan avec le type \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Autorise l\'appli à utiliser les services de premier plan avec le type \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"évaluer l\'espace de stockage de l\'application"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Permet à l\'application de récupérer son code, ses données et la taille de sa mémoire cache."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modifier les paramètres du système"</string>
@@ -446,6 +473,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Cette application peut utiliser le micro pour réaliser des enregistrements audio quand elle est en cours d\'utilisation."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"réaliser des enregistrements audio en arrière-plan"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Cette application peut utiliser le micro pour réaliser des enregistrements audio à tout moment."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"détecter les captures d\'écran de fenêtres d\'appli"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Cette app sera notifiée lors de la prise d\'une capture d\'écran pendant son utilisation."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"envoyer des commandes à la carte SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Autoriser l\'envoi de commandes à la carte SIM via l\'application. Cette fonctionnalité est très risquée."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconnaître l\'activité physique"</string>
@@ -697,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permettre à l\'application de lire les fichiers vidéo de votre espace de stockage partagé."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"lire les fichiers image de l\'espace de stockage partagé"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permettre à l\'application de lire les fichiers image de votre espace de stockage partagé."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lire les fichiers image et vidéo sélectionnés par l\'utilisateur dans le stockage partagé"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permet à l\'appli de lire les fichiers image et vidéo que vous sélectionnez dans votre stockage partagé."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"modifier/supprimer contenu mémoire stockage partagée"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Permet de modifier le contenu mémoire de stockage partagée."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"effectuer/recevoir des appels SIP"</string>
@@ -2002,7 +2033,7 @@
     <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"Aucune suggestion de saisie automatique"</string>
     <string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{1 suggestion de saisie automatique}one{# suggestion de saisie automatique}many{# suggestions de saisie automatique}other{# suggestions de saisie automatique}}"</string>
     <string name="autofill_save_title" msgid="7719802414283739775">"Enregistrer dans "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>" ?"</string>
-    <string name="autofill_save_title_with_type" msgid="3002460014579799605">"Enregistrer la <xliff:g id="TYPE">%1$s</xliff:g> dans "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>" ?"</string>
+    <string name="autofill_save_title_with_type" msgid="3002460014579799605">"Enregistrer le <xliff:g id="TYPE">%1$s</xliff:g> dans "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>" ?"</string>
     <string name="autofill_save_title_with_2types" msgid="3783270967447869241">"Enregistrer <xliff:g id="TYPE_0">%1$s</xliff:g> et <xliff:g id="TYPE_1">%2$s</xliff:g> dans "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>" ?"</string>
     <string name="autofill_save_title_with_3types" msgid="6598228952100102578">"Enregistrer <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> et <xliff:g id="TYPE_2">%3$s</xliff:g> dans "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" ?"</string>
     <string name="autofill_update_title" msgid="3630695947047069136">"Mettre à jour cet élément dans "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>" ?"</string>
@@ -2047,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DÉSINSTALLER"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OUVRIR QUAND MÊME"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Application dangereuse détectée"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Autoriser <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> à accéder à tous les journaux de l\'appareil ?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Autoriser un accès unique"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ne pas autoriser"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Les journaux enregistrent ce qui se passe sur votre appareil. Les applis peuvent les utiliser pour rechercher et résoudre les problèmes.\n\nCertains journaux pouvant contenir des infos sensibles, autorisez uniquement les applis de confiance à accéder à tous les journaux de l\'appareil. \n\nSi vous refusez à cette appli l\'accès à tous les journaux de l\'appareil, elle a quand même accès aux siens. Le fabricant de l\'appareil peut accéder à certains journaux ou certaines infos sur votre appareil."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Les journaux enregistrent ce qui se passe sur votre appareil. Les applis peuvent les utiliser pour rechercher et résoudre des problèmes.\n\nCertains journaux pouvant contenir des infos sensibles, autorisez uniquement les applis de confiance à accéder à tous les journaux de l\'appareil. \n\nSi vous refusez à cette appli l\'accès à tous les journaux de l\'appareil, elle a quand même accès aux siens. Le fabricant de l\'appareil peut accéder à certains journaux ou certaines infos sur votre appareil.\n\nEn savoir plus sur g.co/android/devicelogs"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne plus afficher"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> souhaite afficher des éléments de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Modifier"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Les appels et les notifications vibreront"</string>
@@ -2293,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Impossible d\'accéder à l\'appareil photo du téléphone depuis votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Impossible d\'accéder à l\'appareil photo de la tablette depuis votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Impossible d\'accéder à cela pendant le streaming. Essayez plutôt sur votre téléphone."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Impossible d\'afficher Picture-in-picture pendant le streaming"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Paramètre système par défaut"</string>
     <string name="default_card_name" msgid="9198284935962911468">"CARTE <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 219299f..31da9fa 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Correo de voz"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problema de conexión ou código MMI non válido."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Función non compatible."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"A operación está restrinxida a números de marcación fixa."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Non se pode cambiar a configuración do desvío de chamadas desde o teléfono mentres estás en itinerancia."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Activouse o servizo."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Cambiouse a conexión de datos a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Podes cambiar esta opción en calquera momento en Configuración"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite á aplicación converter partes súas como persistentes na memoria. Esta acción pode limitar a cantidade memoria dispoñible para outras aplicacións e reducir a velocidade de funcionamento do teléfono."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"executar un servizo en primeiro plano"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite que a aplicación utilice os servizos en primeiro plano."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"executar servizo en primeiro plano co tipo \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"executar servizo en primeiro plano co tipo \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"executar servizo en primeiro plano co tipo \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"executar servizo en primeiro plano co tipo \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"executar servizo en primeiro plano co tipo \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"executar servizo en primeiro plano co tipo \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"executar servizo en primeiro plano co tipo \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"executar servizo en primeiro plano co tipo \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"executar servizo en primeiro plano co tipo \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"executar servizo en primeiro plano co tipo \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"executar servizo en primeiro plano co tipo \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executar servizo en primeiro plano co tipo \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"medir o espazo de almacenamento da aplicación"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite á aplicación recuperar o código, os datos e os tamaños da caché"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modificar a configuración do sistema"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Mentres a empregas, esta aplicación pode utilizar o micrófono para gravar audio."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar audio en segundo plano"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Esta aplicación pode utilizar o micrófono en calquera momento para gravar audio."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detectar capturas de pantalla de ventás da aplicación"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Esta aplicación recibirá unha notificación cando se faga unha captura de pantalla mentres se estea utilizando."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos á SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite á aplicación enviar comandos á SIM. Isto é moi perigoso."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"recoñecer actividade física"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permite que a aplicación acceda a ficheiros de vídeo do almacenamento compartido."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"acceder a ficheiros de imaxe do almacenamento compartido"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permite que a aplicación acceda a ficheiros de imaxe do almacenamento compartido."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ler ficheiros de imaxe e de vídeo do almacenamento compartido seleccionados polo usuario"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permite que a aplicación lea os ficheiros de imaxe e de vídeo que selecciones do almacenamento compartido."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"modificar ou eliminar o almacenamento compartido"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Permite á aplicación escribir no almacenamento compartido."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"facer/recibir chamadas SIP"</string>
@@ -1948,13 +1979,13 @@
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"A configuración de Android TV non está dispoñible"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"A configuración da tableta non está dispoñible"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"A configuración do teléfono non está dispoñible"</string>
-    <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo con Android TV."</string>
+    <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde a tableta."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o teléfono."</string>
-    <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta aplicación solicita seguranza adicional. Proba a facelo desde o dispositivo con Android TV."</string>
+    <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta aplicación solicita seguranza adicional. Proba a facelo desde o dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Esta aplicación solicita seguranza adicional. Proba a facelo desde a tableta."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Esta aplicación solicita seguranza adicional. Proba a facelo desde o teléfono."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo con Android TV."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde a tableta."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o teléfono."</string>
     <string name="deprecated_target_sdk_message" msgid="5246906284426844596">"Esta aplicación deseñouse para unha versión anterior de Android. Quizais non funcione correctamente e non inclúa as últimas medidas de protección de privacidade e seguranza. Comproba se hai actualizacións ou ponte en contacto co programador da aplicación."</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALAR"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ABRIR IGUALMENTE"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Detectouse unha aplicación daniña"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Queres permitir que a aplicación <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acceda a todos os rexistros do dispositivo?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir acceso unha soa vez"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Non permitir"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Os rexistros do dispositivo dan conta do que ocorre neste. As aplicacións poden usalos para buscar problemas e solucionalos.\n\nAlgúns poden conter información confidencial, polo que che recomendamos que só permitas que accedan a todos os rexistros do dispositivo as aplicacións nas que confíes. \n\nEsta aplicación pode acceder aos seus propios rexistros aínda que non lle permitas acceder a todos. É posible que o fabricante do dispositivo teña acceso a algúns rexistros ou á información do teu dispositivo."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Os rexistros do dispositivo dan conta do que ocorre neste. As aplicacións poden usalos para buscar problemas e solucionalos.\n\nAlgúns poden conter información confidencial, polo que che recomendamos que só permitas que accedan a todos os rexistros do dispositivo as aplicacións nas que confíes. \n\nEsta aplicación pode acceder aos seus propios rexistros aínda que non lle permitas acceder a todos. É posible que o fabricante do dispositivo teña acceso a algúns rexistros ou á información do teu dispositivo.\n\nConsulta máis información en g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Non amosar outra vez"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quere mostrar fragmentos de aplicación de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"As chamadas e as notificacións vibrarán"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Non se puido acceder á cámara do teléfono desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>)"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Non se puido acceder á cámara da tableta desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>)"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Non se puido acceder a este contido durante a reprodución en tempo real. Téntao desde o teléfono."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Non se pode ver un vídeo en pantalla superposta mentres se reproduce en tempo real"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Opción predeterminada do sistema"</string>
     <string name="default_card_name" msgid="9198284935962911468">"TARXETA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 90dda1a..8351fbc 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"વૉઇસમેઇલ"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"કનેક્શન સમસ્યા અથવા અમાન્ય MMI કોડ."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"સુવિધાને સપોર્ટ આપવામાં આવતો નથી."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"ઑપરેશન ફક્ત સ્થિર ડાયલિંગ નંબર્સ પર પ્રતિબંધિત છે."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"તમે રોમિંગમાં હો તે વખતે તમારા ફોન પરથી કૉલ ફૉરવર્ડિગ સેટિંગ બદલી શકતાં નથી."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"સેવા સક્ષમ હતી."</string>
@@ -72,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"કૉલર ID પ્રતિબંધિત નહીં પર ડિફોલ્ટ છે. આગલો કૉલ: પ્રતિબંધિત નહીં"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"સેવાની જોગવાઈ કરી નથી."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"તમે કૉલર ID સેટિંગ બદલી શકતાં નથી."</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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"એપ્લિકેશનને મેમરીમાં પોતાના ભાગ સતત બનાવવાની મંજૂરી આપે છે. આ ફોનને ધીમો કરીને અન્ય ઍપ્લિકેશનો પર ઉપલબ્ધ મેમરીને સીમિત કરી શકે છે."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"ઍપને ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"camera\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"ઍપને \"camera\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"ઍપને \"connectedDevice\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"ઍપને \"dataSync\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"location\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"ઍપને \"location\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"ઍપને \"mediaPlayback\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"ઍપને \"mediaProjection\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"microphone\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"ઍપને \"microphone\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"ઍપને \"phoneCall\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"health\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"ઍપને \"health\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"ઍપને \"remoteMessaging\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"ઍપને \"systemExempted\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"ઍપને \"specialUse\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ઍપ્લિકેશન સંગ્રહ સ્થાન માપો"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"એપ્લિકેશનને તેનો કોડ, ડેટા અને કેશ કદ પુનઃપ્રાપ્ત કરવાની મંજૂરી આપે છે."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"સિસ્ટમ સેટિંગમાં ફેરફાર કરો"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"આ ઍપ ઉપયોગમાં હોય ત્યારે તે માઇક્રોફોનનો ઉપયોગ કરીને ઑડિયો રેકોર્ડ કરી શકે છે."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"બૅકગ્રાઉન્ડમાં ઑડિયો રેકોર્ડ કરો"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"આ ઍપ, માઇક્રોફોનનો ઉપયોગ કરીને કોઈપણ સમયે ઑડિયો રેકોર્ડ કરી શકે છે."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ઍપ વિન્ડોના સ્ક્રીન કૅપ્ચરની ભાળ મેળવો"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"ઍપનો ઉપયોગ થઈ રહ્યો હોય ત્યારે સ્ક્રીનશૉટ લેવામાં આવે, તો આ ઍપને નોટિફિકેશન મળશે."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"સિમ ને આદેશો મોકલો"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"એપ્લિકેશનને સિમ પરા આદેશો મોકલવાની મંજૂરી આપે છે. આ ખૂબ જ ખતરનાક છે."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"શારીરિક પ્રવૃત્તિને ઓળખો"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ઍપને તમારા શેર કરાયેલા સ્ટોરેજમાંથી વીડિયો ફાઇલો વાંચવાની મંજૂરી આપે છે."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"શેર કરાયેલા સ્ટોરેજમાંથી છબી ફાઇલો વાંચવા માટે"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"ઍપને તમારા શેર કરાયેલા સ્ટોરેજમાંથી છબી ફાઇલો વાંચવાની મંજૂરી આપે છે."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"શેર કરેલા સ્ટોરેજમાંથી વપરાશકર્તા દ્વારા પસંદ કરેલી છબી અને વીડિયો ફાઇલો વાંચો"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ઍપને તમારા શેર કરેલા સ્ટોરેજમાંથી તમે પસંદ કરેલી છબી અને વીડિયો ફાઇલો વાંચવાની મંજૂરી આપે છે."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"શેર કરેલા સ્ટોરેજ કન્ટેન્ટમાં ફેરફાર કરો/ડિલીટ કરો"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"શેર કરેલા સ્ટોરેજ કન્ટેન્ટમાં લખવાની મંજૂરી આપે છે."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP કૉલ્સ કરો/પ્રાપ્ત કરો"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"અનઇન્સ્ટૉલ કરો"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"કોઈપણ રીતે ખોલો"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"નુકસાનકારક ઍપ મળી આવી છે"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>ને ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી આપવી છે?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"એક-વખતના ઍક્સેસની મંજૂરી આપો"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"મંજૂરી આપશો નહીં"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"તમારા ડિવાઇસ પર થતી કામગીરીને ડિવાઇસ લૉગ રેકોર્ડ કરે છે. ઍપ આ લૉગનો ઉપયોગ સમસ્યાઓ શોધી તેનું નિરાકરણ કરવા માટે કરી શકે છે.\n\nઅમુક લૉગમાં સંવેદનશીલ માહિતી હોઈ શકે, આથી ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી માત્ર તમારી વિશ્વાસપાત્ર ઍપને જ આપો. \n\nજો તમે આ ઍપને ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી ન આપો, તો પણ તે તેના પોતાના લૉગ ઍક્સેસ કરી શકે છે. તમારા ડિવાઇસના નિર્માતા હજુ પણ કદાચ તમારા ડિવાઇસ પર અમુક લૉગ અથવા માહિતી ઍક્સેસ કરી શકે છે."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"તમારા ડિવાઇસ પર થતી કામગીરીને ડિવાઇસ લૉગ રેકોર્ડ કરે છે. ઍપ આ લૉગનો ઉપયોગ સમસ્યાઓ શોધી તેનું નિરાકરણ કરવા માટે કરી શકે છે.\n\nઅમુક લૉગમાં સંવેદનશીલ માહિતી હોઈ શકે, આથી ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી માત્ર તમારી વિશ્વાસપાત્ર ઍપને જ આપો. \n\nજો તમે આ ઍપને ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી ન આપો, તો પણ તે તેના પોતાના લૉગ ઍક્સેસ કરી શકે છે. તમારા ડિવાઇસના નિર્માતા હજુ પણ કદાચ તમારા ડિવાઇસ પર અમુક લૉગ અથવા માહિતી ઍક્સેસ કરી શકે છે.\n\ng.co/android/devicelogs પર વધુ જાણો."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ફરીથી બતાવશો નહીં"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>એ <xliff:g id="APP_2">%2$s</xliff:g> સ્લાઇસ બતાવવા માગે છે"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ફેરફાર કરો"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"કૉલ અને નોટિફિકેશન માટે ઉપકરણ વાઇબ્રેટ થશે"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"તમારા <xliff:g id="DEVICE">%1$s</xliff:g> પરથી ફોનના કૅમેરાનો ઍક્સેસ કરી શકતાં નથી"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"તમારા <xliff:g id="DEVICE">%1$s</xliff:g> પરથી ટૅબ્લેટના કૅમેરાનો ઍક્સેસ કરી શકતાં નથી"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"સ્ટ્રીમ કરતી વખતે આ ઍક્સેસ કરી શકાતું નથી. તેના બદલે તમારા ફોન પર પ્રયાસ કરો."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"સ્ટ્રીમ કરતી વખતે ચિત્ર-માં-ચિત્ર જોઈ શકતા નથી"</string>
     <string name="system_locale_title" msgid="711882686834677268">"સિસ્ટમ ડિફૉલ્ટ"</string>
     <string name="default_card_name" msgid="9198284935962911468">"કાર્ડ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index af5bc1f..5bc7273 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"वॉइसमेल"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"कनेक्‍शन समस्‍या या अमान्‍य MMI कोड."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"यह सुविधा, इस नेटवर्क पर काम नहीं करती है."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"कार्रवाई केवल फ़िक्‍स्‍ड डायलिंग नंबर के लिए प्रतिबंधित है."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"आपके रोमिंग में होने पर, आपके फ़ोन से कॉल को दूसरे नंबर पर भेजने की सेटिंग नहीं बदली जा सकती."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"सेवा अक्षम थी."</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ऐप्स को मेमोरी में स्‍वयं के कुछ हिस्सों को लगातार बनाने देता है. यह अन्‍य ऐप्स  के लिए उपलब्‍ध स्‍मृति को सीमित कर फ़ोन को धीमा कर सकता है."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"स्क्रीन पर दिखने वाली सेवा चालू करें"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"ऐप्लिकेशन को स्क्रीन पर दिखने वाली सेवाएं इस्तेमाल करने दें."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"camera\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"इससे ऐप्लिकेशन, \"camera\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"इससे ऐप्लिकेशन, \"connectedDevice\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"इससे ऐप्लिकेशन, \"dataSync\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"location\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"इससे ऐप्लिकेशन, \"location\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"इससे ऐप्लिकेशन, \"mediaPlayback\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"इससे ऐप्लिकेशन, \"mediaProjection\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"microphone\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"इससे ऐप्लिकेशन, \"microphone\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"इससे ऐप्लिकेशन, \"phoneCall\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"health\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"इससे ऐप्लिकेशन, \"health\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"इससे ऐप्लिकेशन, \"remoteMessaging\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"इससे ऐप्लिकेशन, \"systemExempted\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"इससे ऐप्लिकेशन, \"specialUse\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"पता करें कि ऐप मेमोरी में कितनी जगह है"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"ऐप को उसका कोड, डेटा, और कैश मेमोरी के आकारों को फिर से पाने देता है"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"सिस्‍टम सेटिंग बदलें"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"जब इस ऐप्लिकेशन का इस्तेमाल किया जा रहा हो, तब यह माइक्रोफ़ोन का इस्तेमाल करके ऑडियो रिकॉर्ड कर सकता है."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ऐप्लिकेशन बैकग्राउंड में ऑडियो रिकॉर्ड कर सकता है"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"यह ऐप्लिकेशन जब चाहे, माइक्रोफ़ोन का इस्तेमाल करके ऑडियो रिकॉर्ड कर सकता है."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ऐप्लिकेशन विंडो के स्क्रीन कैप्चर का पता लगाने की अनुमति दें"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"अगर ऐप्लिकेशन का इस्तेमाल करते समय कोई स्क्रीनशॉट लिया जाता है, तो इसकी सूचना इस ऐप्लिकेशन पर दिखेगी."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"सिम पर निर्देश भेजें"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"ऐप को सिम पर निर्देश भेजने देती है. यह बहुत ही खतरनाक है."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"शरीर की गतिविधि को पहचानना"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"अपने डिवाइस के शेयर किए गए स्टोरेज से, ऐप्लिकेशन को वीडियो फ़ाइलें पढ़ने की अनुमति दें."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"डिवाइस के शेयर किए गए स्टोरेज से, इमेज फ़ाइलें देखने की अनुमति"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"अपने डिवाइस के शेयर किए गए स्टोरेज से, ऐप्लिकेशन को इमेज फ़ाइलें देखने की अनुमति दें."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"डिवाइस के शेयर किए गए स्टोरेज से चुनी गई इमेज और वीडियो फ़ाइलों को देखने की अनुमति दें"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"इससे ऐप्लिकेशन को, डिवाइस के शेयर किए गए स्टोरेज से चुनी गई इमेज और वीडियो फ़ाइलों को देखने की अनुमति मिलती है."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"आपकी शेयर की गई मेमोरी की सामग्री में बदलाव करना या उसे मिटाना"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"ऐप्लिकेशन को आपकी शेयर की गई मेमोरी की सामग्री लिखने देती है."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP कॉल करें/पाएं"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"अनइंस्‍टॉल करें"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"फिर भी खोलें"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"नुकसान पहुंचाने वाले ऐप का पता चला"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"क्या <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> को डिवाइस लॉग का ऐक्सेस देना है?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"एक बार ऐक्सेस करने की अनुमति दें"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"अनुमति न दें"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"डिवाइस लॉग में, आपके डिवाइस पर की गई कार्रवाइयां रिकॉर्ड होती हैं. ऐप्लिकेशन, इन लॉग का इस्तेमाल गड़बड़ियां ढूंढने और उन्हें ठीक करने के लिए करते हैं.\n\nकुछ लॉग में संवेदनशील जानकारी हो सकती है. इसलिए, सिर्फ़ भरोसेमंद ऐप्लिकेशन को डिवाइस लॉग का ऐक्सेस दें. \n\nअगर इस ऐप्लिकेशन को डिवाइस के सभी लॉग का ऐक्सेस नहीं दिया जाता है, तब भी यह डिवाइस पर अपने लॉग को ऐक्सेस कर सकता है. डिवाइस को बनाने वाली कंपनी अब भी डिवाइस के कुछ लॉग या जानकारी को ऐक्सेस कर सकती है."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"डिवाइस लॉग में आपके डिवाइस पर की गई कार्रवाइयां रिकॉर्ड होती हैं. ऐप्लिकेशन, इन लॉग का इस्तेमाल गड़बड़ियां ढूंढने और उन्हें सही करने के लिए करता है.\n\nकुछ लॉग में संवेदनशील जानकारी हो सकती है. इसलिए, सिर्फ़ भरोसेमंद ऐप्लिकेशन को डिवाइस के सभी लॉग का ऐक्सेस दें. \n\nअगर किसी ऐप्लिकेशन को डिवाइस के सभी लॉग का ऐक्सेस नहीं दिया जाता है, तब भी वह डिवाइस पर अपने लॉग को ऐक्सेस कर सकता है. डिवाइस को बनाने वाली कंपनी अब भी डिवाइस के कुछ लॉग या जानकारी को ऐक्सेस कर सकती है.\n\nज़्यादा जानने के लिए, g.co/android/devicelogs पर जाएं."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"फिर से न दिखाएं"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>, <xliff:g id="APP_2">%2$s</xliff:g> के हिस्से (स्लाइस) दिखाना चाहता है"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"बदलाव करें"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"कॉल और सूचनाओं आने पर डिवाइस वाइब्रेट हाेगा"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"आपके <xliff:g id="DEVICE">%1$s</xliff:g> से फ़ोन के कैमरे को ऐक्सेस नहीं किया जा सकता"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"आपके <xliff:g id="DEVICE">%1$s</xliff:g> से टैबलेट के कैमरे को ऐक्सेस नहीं किया जा सकता"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"स्ट्रीमिंग के दौरान, इसे ऐक्सेस नहीं किया जा सकता. इसके बजाय, अपने फ़ोन पर ऐक्सेस करके देखें."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"स्ट्रीमिंग करते समय, \'पिक्चर में पिक्चर\' सुविधा इस्तेमाल नहीं की जा सकती"</string>
     <string name="system_locale_title" msgid="711882686834677268">"सिस्टम डिफ़ॉल्ट"</string>
     <string name="default_card_name" msgid="9198284935962911468">"कार्ड <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 87df29a..6d6d5fe 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Govorna pošta"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problem s vezom ili nevažeći MMI kôd."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Značajka nije podržana."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Operacija je ograničena samo na brojeve s fiksnim biranjem."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nije moguće promijeniti postavke preusmjeravanja poziva na telefonu dok ste u roamingu."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Usluga je omogućena."</string>
@@ -73,6 +74,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>
@@ -394,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Aplikaciji omogućuje trajnu prisutnost nekih njenih dijelova u memoriji. To može ograničiti dostupnost memorije drugim aplikacijama i usporiti telefon."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"pokrenuti uslugu u prednjem planu"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Omogućuje aplikaciji da upotrebljava usluge koje su u prednjem planu."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"pokretanje usluge u prednjem planu s vrstom \"kamera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"fotoaparat\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"pokretanje usluge u prednjem planu s vrstom \"povezani uređaj\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"povezani uređaj\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"pokretanje usluge u prednjem planu s vrstom \"sinkroniziranje podataka\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"sinkroniziranje podataka\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"pokretanje usluge u prednjem planu s vrstom \"lokacija\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"lokacija\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"pokretanje usluge u prednjem planu s vrstom \"reprodukcija medijskih sadržaja\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"reprodukcija medijskih sadržaja\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"pokretanje usluge u prednjem planu s vrstom \"projekcija multimedija\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"projekcija multimedija\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"pokretanje usluge u prednjem planu s vrstom \"mikrofon\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"mikrofon\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"pokretanje usluge u prednjem planu s vrstom \"telefonski poziv\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"telefonski poziv\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"pokretanje usluge u prednjem planu s vrstom \"zdravlje\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"zdravlje\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"pokretanje usluge u prednjem planu s vrstom \"udaljena razmjena poruka\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"udaljena razmjena poruka\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"pokretanje usluge u prednjem planu s vrstom \"sustav je izuzeo\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"izuzeo sustav\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"pokretanje usluge u prednjem planu s vrstom \"posebna upotreba\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"posebna upotreba\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mjerenje prostora za pohranu aplikacije"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Aplikaciji omogućuje dohvaćanje koda, podataka i veličine predmemorije"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"izmjena postavki sustava"</string>
@@ -446,6 +473,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Aplikacija može snimati audiozapise pomoću mikrofona dok se upotrebljava."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"snimati audiozapise u pozadini"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Aplikacija može snimati audiozapise pomoću mikrofona u svakom trenutku."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detektirati snimanja zaslona prozora aplikacije"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ako se tijekom upotrebe ove aplikacije izradi snimka zaslona, aplikacija će dobiti obavijest."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"slati naredbe SIM-u"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Omogućuje aplikaciji slanje naredbi SIM-u. To je vrlo opasno."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznati tjelesnu aktivnost"</string>
@@ -697,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Aplikaciji omogućuje čitanje videodatoteka iz dijeljene pohrane."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"čitati slikovne datoteke iz dijeljene pohrane"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Aplikaciji omogućuje čitanje slikovnih datoteka iz dijeljene pohrane."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"čitati slikovne i videodatoteke koje je korisnik odabrao iz dijeljene pohrane"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Aplikaciji omogućuje čitanje slikovnih i videodatoteka koje odaberete iz dijeljene pohrane."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"izmjena ili brisanje sadržaja dijeljene pohrane"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Aplikaciji omogućuje pisanje sadržaja u dijeljenu pohranu."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"upućivanje/primanje SIP poziva"</string>
@@ -2047,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEINSTALIRAJ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"IPAK OTVORI"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Otkrivena je štetna aplikacija"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Želite li dopustiti aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> da pristupa svim zapisnicima uređaja?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Omogući jednokratni pristup"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nemoj dopustiti"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"U zapisnicima uređaja bilježi se što se događa na uređaju. Aplikacije mogu koristiti te zapisnike kako bi pronašle i riješile poteškoće.\n\nNeki zapisnici mogu sadržavati osjetljive podatke, pa pristup svim zapisnicima uređaja odobrite samo pouzdanim aplikacijama. \n\nAko ne dopustite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač vašeg uređaja i dalje može pristupati nekim zapisnicima ili podacima na vašem uređaju."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"U zapisnicima uređaja bilježi se što se događa na uređaju. Aplikacije mogu koristiti te zapisnike kako bi pronašle i riješile poteškoće.\n\nNeki zapisnici mogu sadržavati osjetljive podatke, pa pristup svim zapisnicima uređaja odobrite samo pouzdanim aplikacijama. \n\nAko ne dopustite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač vašeg uređaja i dalje može pristupati nekim zapisnicima ili podacima na vašem uređaju.\n\nSaznajte više na g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne prikazuj ponovo"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> želi prikazivati isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Uredi"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Uređaj će vibrirati za pozive i obavijesti"</string>
@@ -2293,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"S vašeg uređaja <xliff:g id="DEVICE">%1$s</xliff:g> nije moguće pristupiti fotoaparatu telefona"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"S vašeg uređaja <xliff:g id="DEVICE">%1$s</xliff:g> nije moguće pristupiti fotoaparatu tableta"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Sadržaju nije moguće pristupiti tijekom streaminga. Pokušajte mu pristupiti na telefonu."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Slika u slici ne može se prikazivati tijekom streaminga"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Zadane postavke sustava"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KARTICA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 3762fde..05953f0 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Hangposta"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Kapcsolódási probléma vagy érvénytelen MMI-kód."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"A funkció nem támogatott."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"A művelet fix hívószámokra van korlátozva."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"A hívásátirányítási beállításokat roaming közben telefonról nem lehet módosítani."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"A szolgáltatás engedélyezésre került."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Adatforgalom átváltva a következőre: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"A Beállításokban ezt bármikor módosíthatja"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Lehetővé teszi az alkalmazás számára, hogy egyes részeit állandó jelleggel eltárolja a memóriában. Ez korlátozhatja a többi alkalmazás számára rendelkezésre álló memóriát, és lelassíthatja a telefont."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"előtérben lévő szolgáltatás futtatása"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"előtérben lévő szolgáltatás futtatása a következő típussal: „camera”"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „camera”"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"előtérben lévő szolgáltatás futtatása a következő típussal: „connectedDevice”"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „connectedDevice”"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"előtérben lévő szolgáltatás futtatása a következő típussal: „dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „dataSync”"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"előtérben lévő szolgáltatás futtatása a következő típussal: „location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „location”"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"előtérben lévő szolgáltatás futtatása a következő típussal: „mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „mediaPlayback”"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"előtérben lévő szolgáltatás futtatása a következő típussal: „mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „mediaProjection”"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"előtérben lévő szolgáltatás futtatása a következő típussal: „microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „microphone”"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"előtérben lévő szolgáltatás futtatása a következő típussal: „phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „phoneCall”"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"előtérben lévő szolgáltatás futtatása a következő típussal: „health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „health”"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"előtérben lévő szolgáltatás futtatása a következő típussal: „remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „remoteMessaging”"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"előtérben lévő szolgáltatás futtatása a következő típussal: „systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „systemExempted”"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"előtérben lévő szolgáltatás futtatása a következő típussal: „specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „specialUse”"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"alkalmazás-tárhely felmérése"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Lehetővé teszi az alkalmazás számára a kód, az adatok és a gyorsítótár-méret lekérését"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"rendszerbeállítások módosítása"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Az alkalmazás a mikrofon használatával akkor készíthet hangfelvételt, amikor az alkalmazás használatban van."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"hangfelvétel készítése a háttérben"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Az alkalmazás a mikrofon használatával bármikor készíthet hangfelvételt."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"alkalmazásablakokról készült képernyőfelvétel észlelése"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ez az alkalmazás értesítést kap majd, amikor képernyőkép készül az alkalmazás használata közben."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"parancsok küldése a SIM-kártyára"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Engedélyezi, hogy az alkalmazás parancsokat küldjön a SIM kártyára. Ez rendkívül veszélyes lehet."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"testmozgás felismerése"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Engedélyezi az alkalmazásnak a megosztott tárhelyen található videófájlok olvasását."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"a megosztott tárhelyen található képfájlok olvasása"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Engedélyezi az alkalmazásnak a megosztott tárhelyen található képfájlok olvasását."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"a felhasználó által kiválasztott, megosztott tárhelyen található kép- és videófájlok olvasása"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Engedélyezi az alkalmazásnak a megosztott tárhelyen található, Ön által kiválasztott kép- és videófájlok olvasását."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"a közös tárhely tartalmainak törlése és módosítása"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Engedélyezi az alkalmazás számára a közös tárhely tartalmainak felülírását."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP-hívások indítása/fogadása"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ELTÁVOLÍTÁS"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"MEGNYITÁS MÉGIS"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"A rendszer kártékony alkalmazást észlelt"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Engedélyezi a(z) <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> számára, hogy hozzáférjen az összes eszköznaplóhoz?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Egyszeri hozzáférés engedélyezése"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Tiltás"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Az eszköznaplók rögzítik, hogy mi történik az eszközén. Az alkalmazások ezeket a naplókat használhatják a problémák megkeresésére és kijavítására.\n\nBizonyos naplók bizalmas adatokat is tartalmazhatnak, ezért csak olyan alkalmazások számára engedélyezze az összes eszköznaplóhoz való hozzáférést, amelyekben megbízik. \n\nHa nem engedélyezi ennek az alkalmazásnak, hogy hozzáférjen az összes eszköznaplójához, az app továbbra is hozzáférhet a saját naplóihoz. Előfordulhat, hogy az eszköz gyártója továbbra is hozzáfér az eszközön található bizonyos naplókhoz és adatokhoz."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Az eszköznaplók rögzítik, hogy mi történik az eszközén. Az alkalmazások ezeket a naplókat használhatják a problémák megkeresésére és kijavítására.\n\nBizonyos naplók bizalmas adatokat is tartalmazhatnak, ezért csak olyan alkalmazások számára engedélyezze az összes eszköznaplóhoz való hozzáférést, amelyekben megbízik. \n\nHa nem engedélyezi ennek az alkalmazásnak, hogy hozzáférjen az összes eszköznaplójához, az app továbbra is hozzáférhet a saját naplóihoz. Előfordulhat, hogy az eszköz gyártója továbbra is hozzáfér az eszközön található bizonyos naplókhoz és adatokhoz.\n\nTovábbi információ: g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne jelenjen meg újra"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"A(z) <xliff:g id="APP_0">%1$s</xliff:g> alkalmazás részleteket szeretne megjeleníteni a(z) <xliff:g id="APP_2">%2$s</xliff:g> alkalmazásból"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Szerkesztés"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"A hívások és az értesítések rezegnek"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nem lehet hozzáférni a telefon kamerájához a következő eszközön: <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nem lehet hozzáférni a táblagép kamerájához a következő eszközön: <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Ehhez a tartalomhoz nem lehet hozzáférni streamelés közben. Próbálja újra a telefonján."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Streamelés közben nem lehetséges a kép a képben módban való lejátszás"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Rendszerbeállítás"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KÁRTYA: <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index a11e24b..7f8898e 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Ձայնային փոստ"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Միացման խնդիր կամ անվավեր MMI ծածակագիր:"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Գործառույթը չի աջակցվում։"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Գործողությունը սահմանափակված է միայն ամրակայված հեռախոսահամարների համար:"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Ռոումինգում չեք կարող փոխել զանգի վերահասցեավորման կարգավորումները ձեր հեռախոսից։"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Ծառայությունը միացված է:"</string>
@@ -72,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Զանգողի ID-ն լռելյայն չսահմանափակված է: Հաջորդ զանգը` չսահմանափակված"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Ծառայությունը չի տրամադրվում:"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Դուք չեք կարող փոխել զանգողի ID-ի կարգավորումները:"</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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Թույլ է տալիս հավելվածին մնայուն դարձնել իր մասերը հիշողության մեջ: Սա կարող է սահմանափակել այլ հավելվածներին հասանելի հիշողությունը` դանդաղեցնելով հեռախոսի աշխատանքը:"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"աշխատեցնել ակտիվ ծառայությունները"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Թույլ է տալիս հավելվածին օգտագործել ակտիվ ծառայությունները:"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"գործարկել camera տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Հավելվածին թույլ է տալիս օգտագործել camera տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"գործարկել connectedDevice տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Հավելվածին թույլ է տալիս օգտագործել connectedDevice տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"գործարկել dataSync տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Հավելվածին թույլ է տալիս օգտագործել dataSync տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"գործարկել location տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Հավելվածին թույլ է տալիս օգտագործել location տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"գործարկել mediaPlayback տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Հավելվածին թույլ է տալիս օգտագործել mediaPlayback տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"գործարկել mediaProjection տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Հավելվածին թույլ է տալիս օգտագործել mediaProjection տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"գործարկել microphone տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Հավելվածին թույլ է տալիս օգտագործել microphone տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"գործարկել phoneCall տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Հավելվածին թույլ է տալիս օգտագործել phoneCall տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"գործարկել health տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Հավելվածին թույլ է տալիս օգտագործել health տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"գործարկել remoteMessaging տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Հավելվածին թույլ է տալիս օգտագործել remoteMessaging տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"գործարկել systemExempted տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Հավելվածին թույլ է տալիս օգտագործել systemExempted տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"գործարկել specialUse տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Հավելվածին թույլ է տալիս օգտագործել specialUse տեսակով ակտիվ ծառայությունները"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"չափել հավելվածի պահոցի տարածքը"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Թույլ է տալիս հավելվածին առբերել իր կոդը, տվյալները և քեշի չափերը"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"փոփոխել համակարգի կարգավորումները"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Այս հավելվածը կարող է օգտագործել խոսափողը՝ ձայնագրություններ անելու համար, միայն երբ հավելվածն ակտիվ է։"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ձայնագրել ֆոնային ռեժիմում"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Այս հավելվածը կարող է ցանկացած ժամանակ օգտագործել խոսափողը՝ ձայնագրություններ անելու համար։"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"հայտնաբերել հավելվածի պատուհանների էկրանի տեսագրումները"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Եթե հավելվածի օգտագործման ժամանակ սքրինշոթ արվի, հավելվածը ծանուցում կստանա։"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"ուղարկել հրամաններ SIM քարտին"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Թույլ է տալիս հավելվածին հրամաններ ուղարկել SIM-ին: Սա շատ վտանգավոր է:"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"ֆիզիկական ակտիվության ճանաչում"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Հավելվածին թույլ է տալիս կարդալ ձեր սարքի ընդհանուր հիշողության մեջ պահված վիդեո ֆայլերը։"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"կարդալ ձեր սարքի ընդհանուր հիշողության մեջ պահված գրաֆիկական ֆայլերը"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Հավելվածին թույլ է տալիս կարդալ ձեր սարքի ընդհանուր հիշողության մեջ պահված գրաֆիկական ֆայլերը։"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"կարդալ օգտատիրոջ ընտրած գրաֆիկական և վիդեո ֆայլերն ընդհանուր պահոցից"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Հավելվածին թույլ է տալիս կարդալ գրաֆիկական և վիդեո ֆայլերը, որոնք ընտրել եք ընդհանուր պահոցից։"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"փոփոխել կամ ջնջել ձեր ընդհանուր հիշողության բովանդակությունը"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Հավելվածին թույլ է տալիս փոփոխել ձեր ընդհանուր հիշողության պարունակությունը:"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"կատարել կամ ստանալ SIP զանգեր"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ԱՊԱՏԵՂԱԴՐԵԼ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ԲԱՑԵԼ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Հայտնաբերվել է վնասաբեր հավելված"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Հասանելի դարձնե՞լ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> հավելվածին սարքի բոլոր մատյանները"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Թույլատրել մեկանգամյա մուտքը"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Չթույլատրել"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Այն, ինչ տեղի է ունենում ձեր սարքում, գրանցվում է սարքի մատյաններում։ Հավելվածները կարող են դրանք օգտագործել անսարքությունները հայտնաբերելու և վերացնելու նպատակով։\n\nՔանի որ որոշ մատյաններ անձնական տեղեկություններ են պարունակում, խորհուրդ ենք տալիս հասանելի դարձնել ձեր սարքի բոլոր մատյանները միայն այն հավելվածներին, որոնց վստահում եք։ \n\nԵթե այս հավելվածին նման թույլտվություն չեք տվել, դրան նախկինի պես հասանելի կլինեն իր մատյանները։ Հնարավոր է՝ ձեր սարքի արտադրողին ևս հասանելի լինեն սարքի որոշ մատյաններ և տեղեկություններ։"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Այն, ինչ տեղի է ունենում ձեր սարքում, գրանցվում է սարքի մատյաններում։ Հավելվածները կարող են դրանք օգտագործել անսարքությունները հայտնաբերելու և վերացնելու նպատակով։\n\nՔանի որ որոշ մատյաններ անձնական տեղեկություններ են պարունակում, խորհուրդ ենք տալիս հասանելի դարձնել ձեր սարքի բոլոր մատյանները միայն այն հավելվածներին, որոնց վստահում եք։ \n\nԵթե այս հավելվածին նման թույլտվություն չեք տվել, դրան նախկինի պես հասանելի կլինեն իր մատյանները։ Հնարավոր է՝ ձեր սարքի արտադրողին ևս հասանելի լինեն սարքի որոշ մատյաններ և տեղեկություններ։\n\nՄանրամասների համար այցելեք g.co/android/devicelogs էջ։"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Այլևս ցույց չտալ"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> հավելվածն ուզում է ցուցադրել հատվածներ <xliff:g id="APP_2">%2$s</xliff:g> հավելվածից"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Փոփոխել"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Զանգերի և ծանուցումների համար թրթռոցը միացված է"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Հնարավոր չէ օգտագործել հեռախոսի տեսախցիկը ձեր <xliff:g id="DEVICE">%1$s</xliff:g> սարքից"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Հնարավոր չէ օգտագործել պլանշետի տեսախցիկը ձեր <xliff:g id="DEVICE">%1$s</xliff:g> սարքից"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Այս բովանդակությունը հասանելի չէ հեռարձակման ընթացքում։ Օգտագործեք ձեր հեռախոսը։"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Հեռարձակման ժամանակ հնարավոր չէ դիտել նկարը նկարի մեջ"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Կանխադրված"</string>
     <string name="default_card_name" msgid="9198284935962911468">"ՔԱՐՏ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index dbccee9..79ee0e8 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Pesan suara"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Masalah sambungan atau kode MMI tidak valid."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Fitur tidak didukung."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Operasi dibatasi untuk nomor panggilan tetap saja."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Tidak dapat mengubah setelan penerusan panggilan dari ponsel saat roaming"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Layanan telah diaktifkan."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Mengalihkan data seluler ke <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Anda dapat mengubahnya kapan saja di Setelan"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Memungkinkan aplikasi membuat bagian dari dirinya sendiri terus-menerus berada dalam memori. Izin ini dapat membatasi memori yang tersedia untuk aplikasi lain sehingga menjadikan ponsel lambat."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"jalankan layanan di latar depan"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Mengizinkan aplikasi menggunakan layanan di latar depan."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"menjalankan layanan latar depan dengan jenis \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"menjalankan layanan latar depan dengan jenis \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"menjalankan layanan latar depan dengan jenis \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"menjalankan layanan latar depan dengan jenis \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"menjalankan layanan latar depan dengan jenis \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"menjalankan layanan latar depan dengan jenis \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"menjalankan layanan latar depan dengan jenis \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"menjalankan layanan latar depan dengan jenis \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"menjalankan layanan latar depan dengan jenis \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"menjalankan layanan latar depan dengan jenis \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"menjalankan layanan latar depan dengan jenis \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"menjalankan layanan latar depan dengan jenis \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mengukur ruang penyimpanan aplikasi"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Mengizinkan apl mengambil kode, data, dan ukuran temboloknya"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"ubah setelan sistem"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Aplikasi ini dapat merekam audio menggunakan mikrofon saat aplikasi sedang digunakan."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"merekam audio di latar belakang"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Aplikasi ini dapat merekam audio menggunakan mikrofon kapan saja."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"mendeteksi screenshot jendela aplikasi"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Aplikasi ini akan diberi tahu saat screenshot diambil dan aplikasi sedang digunakan."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"kirimkan perintah ke SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Mengizinkan aplikasi mengirim perintah ke SIM. Ini sangat berbahaya."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"kenali aktivitas fisik"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Mengizinkan aplikasi membaca file video dari penyimpanan bersama."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"membaca file gambar dari penyimpanan bersama"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Mengizinkan aplikasi membaca file gambar dari penyimpanan bersama."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"membaca file gambar dan video yang dipilih dari penyimpanan bersama"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Mengizinkan aplikasi membaca file gambar dan video yang Anda pilih dari penyimpanan bersama."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"memodifikasi atau menghapus konten penyimpanan bersama Anda"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Mengizinkan aplikasi menulis konten penyimpanan bersama Anda."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"lakukan/terima panggilan SIP"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"UNINSTAL"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"TETAP BUKA"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Aplikasi berbahaya terdeteksi"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Izinkan <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> mengakses semua log perangkat?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Izinkan akses satu kali"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Jangan izinkan"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Log perangkat merekam hal-hal yang terjadi di perangkat Anda. Aplikasi dapat menggunakan log ini untuk menemukan dan memperbaiki masalah.\n\nBeberapa log mungkin berisi info sensitif, jadi hanya izinkan aplikasi yang Anda percayai untuk mengakses semua log perangkat. \n\nJika Anda tidak mengizinkan aplikasi ini mengakses semua log perangkat, aplikasi masih dapat mengakses log-nya sendiri. Produsen perangkat masih dapat mengakses beberapa log atau info di perangkat Anda."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Log perangkat merekam hal-hal yang terjadi di perangkat Anda. Aplikasi dapat menggunakan log ini untuk menemukan dan memperbaiki masalah.\n\nBeberapa log mungkin berisi info sensitif, jadi hanya izinkan aplikasi yang Anda percayai untuk mengakses semua log perangkat. \n\nJika Anda tidak mengizinkan aplikasi ini mengakses semua log perangkat, aplikasi masih dapat mengakses log-nya sendiri. Produsen perangkat masih dapat mengakses beberapa log atau info di perangkat Anda.\n\nPelajari lebih lanjut di g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Jangan tampilkan lagi"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ingin menampilkan potongan <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Panggilan dan notifikasi akan bergetar"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Tidak dapat mengakses kamera ponsel dari <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Tidak dapat mengakses kamera tablet dari <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Konten ini tidak dapat diakses saat melakukan streaming. Coba di ponsel."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Tidak dapat menampilkan picture-in-picture saat streaming"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Default sistem"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KARTU <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index cfefc03..5343d23 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Talhólf"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Vandamál með tengingu eða ógild MMI-kóðaskipun."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Eiginleiki ekki studdur."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Aðgerð takmarkast við fast númeraval."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Ekki er hægt að breyta stillingum fyrir framsendingu símtala úr símanum á meðan þú ert í reiki."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Þjónustan var virkjuð."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Skipt yfir í farsímagögn hjá <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Þú getur breytt þessu hvenær sem er í stillingum"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Leyfir forriti að gera hluta sjálfs sín varanlega í minni. Þetta getur takmarkað það minni sem býðst öðrum forritum og þannig hægt á símanum."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"keyra þjónustu í forgrunni"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Leyfir forritinu að nota þjónustu sem er í forgrunni."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"keyra forgrunnsþjónustu af gerðinni „camera“"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „camera“"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"keyra forgrunnsþjónustu af gerðinni „connectedDevice“"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „connectedDevice“"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"keyra forgrunnsþjónustu af gerðinni „dataSync“"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „dataSync“"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"keyra forgrunnsþjónustu af gerðinni „location“"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „location“"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"keyra forgrunnsþjónustu af gerðinni „mediaPlayback“"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „mediaPlayback“"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"keyra forgrunnsþjónustu af gerðinni „mediaProjection“"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „mediaProjection“"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"keyra forgrunnsþjónustu af gerðinni „microphone“"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „microphone“"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"keyra forgrunnsþjónustu af gerðinni „phoneCall“"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „phoneCall“"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"keyra forgrunnsþjónustu af gerðinni „health“"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „health“"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"keyra forgrunnsþjónustu af gerðinni „remoteMessaging“"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „remoteMessaging“"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"keyra forgrunnsþjónustu af gerðinni „systemExempted“"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „systemExempted“"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"keyra forgrunnsþjónustu af gerðinni „specialUse“"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „specialUse“"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mæla geymslurými forrits"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Leyfir forriti að sækja stærðir kóða, gagna og skyndiminnis síns."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"breyta kerfisstillingum"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Þetta forrit getur tekið upp hljóð með hljóðnemanum meðan forritið er í notkun."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"taka upp hljóð í bakgrunni"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Þetta forrit getur tekið upp hljóð með hljóðnemanum hvenær sem er."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"greina skjáupptöku í forritagluggum"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Tilkynning berst til forritsins þegar skjámynd er tekin á meðan forritið er í notkun."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"senda skipanir til SIM-kortsins"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Leyfir forriti að senda SIM-kortinu skipanir. Þetta er mjög hættulegt."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"greina hreyfingu"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Leyfir forritinu að lesa myndskeiðaskrár úr samnýtta geymslurýminu þínu."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"lesa myndskrár úr samnýttu geymslurými"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Leyfir forritinu að lesa myndskrár úr samnýtta geymslurýminu þínu."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lesa mynd- og myndskeiðaskrár sem notendur velja úr samnýttu geymslurými"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Leyfir forritinu að lesa mynd- og myndskeiðaskrár sem þú velur úr samnýtta geymslurýminu þínu."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"breyta eða eyða innihaldi samnýtta geymslurýmisins"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Leyfir forriti að skrifa í innihald samnýtta geymslurýmisins."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"hringja/svara SIP-símtölum"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"FJARLÆGJA"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OPNA SAMT"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Skaðlegt forrit fannst"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Veita <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> aðgang að öllum annálum í tækinu?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Leyfa aðgang í eitt skipti"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ekki leyfa"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Annálar tækisins skrá það sem gerist í tækinu. Forrit geta notað þessa annála til að finna og lagfæra vandamál.\n\nTilteknir annálar innihalda viðkvæmar upplýsingar og því skaltu einungis veita forritum sem þú treystir aðgang að öllum annálum tækisins. \n\nEf þú veitir þessu forriti ekki aðgang að öllum annálum tækisins hefur það áfram aðgang að eigin annálum. Framleiðandi tækisins getur þó hugsanlega opnað tiltekna annála eða upplýsingar í tækinu."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Annálar tækisins skrá það sem gerist í tækinu. Forrit geta notað þessa annála til að finna og lagfæra vandamál.\n\nTilteknir annálar innihalda viðkvæmar upplýsingar og því skaltu einungis veita forritum sem þú treystir aðgang að öllum annálum tækisins. \n\nEf þú veitir þessu forriti ekki aðgang að öllum annálum tækisins hefur það áfram aðgang að eigin annálum. Framleiðandi tækisins getur þó hugsanlega opnað tiltekna annála eða upplýsingar í tækinu.\n\nNánar á g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ekki sýna aftur"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vill sýna sneiðar úr <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Breyta"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Titringur er virkur fyrir símtöl og tilkynningar"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ekki er hægt að opna myndavél símans úr <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ekki er hægt að opna myndavél spjaldtölvunnar úr <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Ekki er hægt að opna þetta á meðan streymi stendur yfir. Prófaðu það í símanum í staðinn."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Ekki er hægt að horfa á mynd í mynd á meðan streymi er í gangi"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Sjálfgildi kerfis"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KORT <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index b05bf79..64c4fd5 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Segreteria"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problema di connessione o codice MMI non valido."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funzionalità non supportata."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Operazione limitata solo ai numeri di selezione fissa."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Impossibile modificare le impostazioni di deviazione chiamate dal telefono durante il roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Il servizio è stato attivato."</string>
@@ -73,6 +74,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"I dati sono stati trasferiti a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Puoi modificare questa opzione in qualsiasi momento in Impostazioni"</string>
     <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>
@@ -394,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Consente all\'applicazione di rendere persistenti in memoria alcune sue parti. Ciò può limitare la memoria disponibile per altre applicazioni, rallentando il telefono."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"esecuzione servizio in primo piano"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Consente all\'app di utilizzare i servizi in primo piano."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"Esecuzione di servizi in primo piano con il tipo \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Consente all\'app di usare i servizi in primo piano con il tipo \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"Esecuzione di servizi in primo piano con il tipo \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Consente all\'app di usare i servizi in primo piano con il tipo \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"Esecuzione di servizi in primo piano con il tipo \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Consente all\'app di usare i servizi in primo piano con il tipo \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"Esecuzione di servizi in primo piano con il tipo \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Consente all\'app di usare i servizi in primo piano con il tipo \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"Esecuzione di servizi in primo piano con il tipo \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Consente all\'app di usare i servizi in primo piano con il tipo \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"Esecuzione di servizi in primo piano con il tipo \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Consente all\'app di usare i servizi in primo piano con il tipo \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"Esecuzione di servizi in primo piano con il tipo \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Consente all\'app di usare i servizi in primo piano con il tipo \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"Esecuzione di servizi in primo piano con il tipo \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Consente all\'app di usare i servizi in primo piano con il tipo \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"Esecuzione di servizi in primo piano con il tipo \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Consente all\'app di usare i servizi in primo piano con il tipo \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"Esecuzione di servizi in primo piano con il tipo \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Consente all\'app di usare i servizi in primo piano con il tipo \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"Esecuzione di servizi in primo piano con il tipo \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Consente all\'app di usare i servizi in primo piano con il tipo \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"Esecuzione di servizi in primo piano con il tipo \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Consente all\'app di usare i servizi in primo piano con il tipo \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"calcolo spazio di archiviazione applicazioni"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Consente all\'applicazione di recuperare il suo codice, i suoi dati e le dimensioni della cache"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modifica delle impostazioni di sistema"</string>
@@ -446,6 +473,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Questa app può registrare audio tramite il microfono mentre l\'app è in uso."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Registrazione di audio in background"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Questa app può registrare audio tramite il microfono in qualsiasi momento."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"rileva le acquisizioni schermo delle finestre dell\'app"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Questa app riceverà una notifica quando viene acquisito uno screenshot mentre è in uso."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"invio di comandi alla SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Consente all\'app di inviare comandi alla SIM. Questo è molto pericoloso."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"riconoscimento dell\'attività fisica"</string>
@@ -697,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Consente all\'app di leggere i file video dal tuo spazio di archiviazione condiviso."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"Lettura dei file immagine dallo spazio di archiviazione condiviso"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Consente all\'app di leggere i file immagine dal tuo spazio di archiviazione condiviso."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"Lettura dei file immagine e video selezionati dall\'utente dallo spazio di archiviazione condiviso"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Consente all\'app di leggere i file immagine e video che selezioni dal tuo spazio di archiviazione condiviso."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"Modifica/eliminazione dei contenuti dell\'archivio condiviso"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Consente all\'app di modificare i contenuti del tuo archivio condiviso."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"invio/ricezione di chiamate SIP"</string>
@@ -1957,7 +1988,7 @@
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Questa app richiede maggiore sicurezza. Prova a usare il telefono."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Non è possibile accedere a questa impostazione su <xliff:g id="DEVICE">%1$s</xliff:g>. Prova a usare il dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Non è possibile accedere a questa impostazione su <xliff:g id="DEVICE">%1$s</xliff:g>. Prova a usare il tablet."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Non è possibile accedere a questa impostazione su <xliff:g id="DEVICE">%1$s</xliff:g>. Prova a usare il telefono."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Non è possibile accedere su <xliff:g id="DEVICE">%1$s</xliff:g>. Prova a usare il telefono."</string>
     <string name="deprecated_target_sdk_message" msgid="5246906284426844596">"Questa app è stata progettata per una versione precedente di Android. Potrebbe non funzionare correttamente e non include le protezioni della sicurezza e della privacy più recenti. Verifica la presenza di un aggiornamento o contatta lo sviluppatore dell\'app."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Cerca aggiornamenti"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Hai nuovi messaggi"</string>
@@ -2047,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DISINSTALLA"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"APRI COMUNQUE"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"App dannosa rilevata"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Consentire all\'app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> di accedere a tutti i log del dispositivo?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Consenti accesso una tantum"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Non consentire"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"I log del dispositivo registrano tutto ciò che succede sul tuo dispositivo. Le app possono usare questi log per individuare problemi e correggerli.\n\nAlcuni log potrebbero contenere informazioni sensibili, quindi concedi l\'accesso a tutti i log del dispositivo soltanto alle app attendibili. \n\nSe le neghi l\'accesso a tutti i log del dispositivo, questa app può comunque accedere ai propri log. Il produttore del tuo dispositivo potrebbe essere comunque in grado di accedere ad alcuni log o informazioni sul dispositivo."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"I log del dispositivo registrano tutto ciò che succede sul tuo dispositivo. Le app possono usare questi log per individuare problemi e correggerli.\n\nAlcuni log potrebbero contenere informazioni sensibili, quindi concedi l\'accesso a tutti i log del dispositivo soltanto alle app attendibili. \n\nSe le neghi l\'accesso a tutti i log del dispositivo, questa app può comunque accedere ai propri log. Il produttore del tuo dispositivo potrebbe essere comunque in grado di accedere ad alcuni log o informazioni sul dispositivo.\n\nScopri di più all\'indirizzo g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Non mostrare più"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"L\'app <xliff:g id="APP_0">%1$s</xliff:g> vuole mostrare porzioni dell\'app <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Modifica"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"La vibrazione sarà attiva per chiamate e notifiche"</string>
@@ -2293,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Impossibile accedere alla fotocamera del telefono dal tuo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Impossibile accedere alla fotocamera del tablet dal tuo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Impossibile accedere a questi contenuti durante lo streaming. Prova a usare il telefono."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Impossibile visualizzare Picture in picture durante lo streaming"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Predefinita di sistema"</string>
     <string name="default_card_name" msgid="9198284935962911468">"SCHEDA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 8656fce..a6c0383 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"דואר קולי"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"‏בעיה בחיבור או קוד MMI לא חוקי."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"התכונה לא נתמכת."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"הפעולה מוגבלת למספרי חיוג קבועים בלבד."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"לא ניתן לשנות את הגדרות העברת השיחות מהטלפון במצב נדידה."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"השירות הופעל."</string>
@@ -74,6 +75,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>
@@ -395,6 +398,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"מאפשרת לאפליקציה להפוך חלקים ממנה לקבועים בזיכרון. הפעולה הזו עשויה להגביל את הזיכרון הזמין לאפליקציות אחרות ולהאט את פעולת הטלפון."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"הפעלת שירות בחזית"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"‏הפעלת שירות שפועל בחזית מסוג \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"‏הפעלת שירות שפועל בחזית מסוג \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"‏הפעלת שירות שפועל בחזית מסוג \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"‏הפעלת שירות שפועל בחזית מסוג \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"‏הפעלת שירות שפועל בחזית מסוג \'mediaPlayback\'"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"‏הפעלת שירות שפועל בחזית מסוג \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"‏הפעלת שירות שפועל בחזית מסוג \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"‏הפעלת שירות שפועל בחזית מסוג \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"‏הפעלת שירות שפועל בחזית מסוג \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"‏הפעלת שירות שפועל בחזית מסוג \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"‏הפעלת שירות שפועל בחזית מסוג \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"‏הפעלת שירות שפועל בחזית מסוג \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"מדידת נפח האחסון של אפליקציות"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"מאפשרת לאפליקציה לאחזר את הקוד, הנתונים, ואת גודל קובצי המטמון שלה"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"שינוי הגדרות מערכת"</string>
@@ -447,6 +474,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"האפליקציה הזו יכולה להשתמש במיקרופון כדי להקליט אודיו כאשר היא בשימוש."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"הקלטת אודיו ברקע"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"האפליקציה הזו יכולה להשתמש במיקרופון כדי להקליט אודיו בכל זמן."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"זיהוי צילומי מסך של חלונות האפליקציה"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"לאפליקציה הזו תישלח התראה אם יצולם צילום מסך בזמן שהיא בשימוש."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"‏שליחת פקודות אל ה-SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"‏מאפשרת לאפליקציה לשלוח פקודות ל-SIM. זוהי הרשאה מסוכנת מאוד."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"זיהוי של פעילות גופנית"</string>
@@ -698,6 +727,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"מאפשרת לאפליקציה לקרוא קובצי וידאו מתוך האחסון המשותף."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"קריאה של קובצי תמונה מתוך האחסון המשותף"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"מאפשרת לאפליקציה לקרוא קובצי תמונה מתוך האחסון המשותף."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"קריאה של קובצי הווידאו והתמונה שנבחרו על ידי המשתמש מתוך נפח האחסון המשותף"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ההרשאה מאפשרת לאפליקציה לקרוא קובצי וידאו ותמונה שבחרת מתוך נפח האחסון המשותף."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"שינוי או מחיקה של תוכן האחסון המשותף שלך"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"מאפשרת לאפליקציה לכתוב את התוכן של האחסון המשותף."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"‏ביצוע/קבלה של שיחות SIP"</string>
@@ -1250,7 +1281,7 @@
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"מתבצעת הפעלה של אפליקציות."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"תהליך האתחול בשלבי סיום."</string>
     <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"לחצת על לחצן ההפעלה – בדרך כלל הפעולה הזו מכבה את המסך.\n\nעליך לנסות להקיש בעדינות במהלך ההגדרה של טביעת האצבע שלך."</string>
-    <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"כדי לסיים את ההגדרה צריך לכבות את המסך"</string>
+    <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"לסיום ההגדרה, יש לכבות את המסך"</string>
     <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"השבתה"</string>
     <string name="fp_power_button_bp_title" msgid="5585506104526820067">"להמשיך לאמת את טביעת האצבע שלך?"</string>
     <string name="fp_power_button_bp_message" msgid="2983163038168903393">"לחצת על לחצן ההפעלה – בדרך כלל הפעולה הזו מכבה את המסך.\n\nעליך לנסות להקיש בעדינות כדי לאמת את טביעת האצבע שלך."</string>
@@ -2048,12 +2079,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"הסרת התקנה"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"לפתוח בכל זאת"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"אותרה אפליקציה מזיקה"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"לתת לאפליקציה <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> הרשאת גישה לכל יומני המכשיר?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"הרשאת גישה חד-פעמית"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"אין אישור"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ביומני המכשיר מתועדת הפעילות במכשיר. האפליקציות יכולות להשתמש ביומנים האלה כדי למצוא בעיות ולפתור אותן.\n\nהמידע בחלק מהיומנים יכול להיות רגיש, לכן יש לתת הרשאת גישה לכל יומני המכשיר רק לאפליקציות מהימנות. \n\nגם אם האפליקציה הזו לא תקבל הרשאת גישה לכל יומני המכשיר, היא תוכל לגשת ליומנים שלה. יכול להיות שליצרן המכשיר עדיין תהיה גישה לחלק מהיומנים או למידע במכשיר שלך."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"‏ביומני המכשיר מתועדת הפעילות במכשיר. האפליקציות יכולות להשתמש ביומנים האלה כדי למצוא בעיות ולפתור אותן.\n\nהמידע בחלק מהיומנים יכול להיות רגיש, לכן יש לתת הרשאת גישה לכל היומנים של המכשיר רק לאפליקציות שסומכים עליהן. \n\nגם אם האפליקציה הזו לא תקבל הרשאת גישה לכל יומני המכשיר, היא תוכל לגשת ליומנים שלה. יכול להיות שליצרן המכשיר עדיין תהיה גישה לחלק מהיומנים או למידע במכשיר שלך.\n\nמידע נוסף זמין בכתובת g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"אין להציג שוב"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> רוצה להציג חלקים מ-<xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"עריכה"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"שיחות והודעות ירטטו"</string>
@@ -2294,6 +2319,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"לא ניתן לגשת למצלמה של הטלפון מה‑<xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"לא ניתן לגשת למצלמה של הטאבלט מה‑<xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"אי אפשר לגשת לתוכן המאובטח הזה בזמן סטרימינג. במקום זאת, אפשר לנסות בטלפון."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"אי אפשר להציג תמונה בתוך תמונה בזמן סטרימינג"</string>
     <string name="system_locale_title" msgid="711882686834677268">"ברירת המחדל של המערכת"</string>
     <string name="default_card_name" msgid="9198284935962911468">"כרטיס <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 69d0b9d..9bcde38 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"留守番電話"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"接続に問題があるか、MMIコードが正しくありません。"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"サポートされていません。"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"発信番号制限で指定された番号に対してのみ操作できます。"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ローミング中はスマートフォンから着信転送設定の変更はできません。"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"サービスが有効になりました。"</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"アプリにその一部をメモリに常駐させることを許可します。これにより他のアプリが使用できるメモリが制限されるため、モバイル デバイスの動作が遅くなることがあります。"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"フォアグラウンド サービスの実行"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"フォアグラウンド サービスの使用をアプリに許可します。"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"タイプが「camera」のフォアグラウンド サービスの実行"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"タイプが「camera」のフォアグラウンド サービスの使用をアプリに許可します"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"タイプが「connectedDevice」のフォアグラウンド サービスの実行"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"タイプが「connectedDevice」のフォアグラウンド サービスの使用をアプリに許可します"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"タイプが「dataSync」のフォアグラウンド サービスの実行"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"タイプが「dataSync」のフォアグラウンド サービスの使用をアプリに許可します"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"タイプが「location」のフォアグラウンド サービスの実行"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"タイプが「location」のフォアグラウンド サービスの使用をアプリに許可します"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"タイプが「mediaPlayback」のフォアグラウンド サービスの実行"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"タイプが「mediaPlayback」のフォアグラウンド サービスの使用をアプリに許可します"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"タイプが「mediaProjection」のフォアグラウンド サービスの実行"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"タイプが「mediaProjection」のフォアグラウンド サービスの使用をアプリに許可します"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"タイプが「microphone」のフォアグラウンド サービスの実行"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"タイプが「microphone」のフォアグラウンド サービスの使用をアプリに許可します"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"タイプが「phoneCall」のフォアグラウンド サービスの実行"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"タイプが「phoneCall」のフォアグラウンド サービスの使用をアプリに許可します"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"タイプが「health」のフォアグラウンド サービスの実行"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"タイプが「health」のフォアグラウンド サービスの使用をアプリに許可します"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"タイプが「remoteMessaging」のフォアグラウンド サービスの実行"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"タイプが「remoteMessaging」のフォアグラウンド サービスの使用をアプリに許可します"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"タイプが「systemExempted」のフォアグラウンド サービスの実行"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"タイプが「systemExempted」のフォアグラウンド サービスの使用をアプリに許可します"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"タイプが「specialUse」のフォアグラウンド サービスの実行"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"タイプが「specialUse」のフォアグラウンド サービスの使用をアプリに許可します"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"アプリのストレージ容量の計測"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"アプリのコード、データ、キャッシュサイズを取得することをアプリに許可します"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"システム設定の変更"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"このアプリは、ユーザーがアプリを使用している場合にマイクを使用して音声を録音できます。"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"バックグラウンドでの音声の録音"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"このアプリは、いつでもマイクを使用して音声を録音できます。"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"アプリ ウィンドウのスクリーン キャプチャの検出"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"アプリの使用中にスクリーンショットが撮影されると、このアプリに通知が届きます。"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIMへのコマンド送信"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"SIMにコマンドを送信することをアプリに許可します。この許可は非常に危険です。"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"身体活動の認識"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"共有ストレージからの動画ファイルの読み取りをアプリに許可します。"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"共有ストレージからの画像ファイルの読み取り"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"共有ストレージからの画像ファイルの読み取りをアプリに許可します。"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ユーザーが選択した画像と動画ファイルの共有ストレージからの読み取り"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"選択した画像と動画ファイルを共有ストレージから読み取ることをアプリに許可します。"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"共有ストレージのコンテンツの変更または削除"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"共有ストレージのコンテンツの書き込みをアプリに許可します。"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP通話の発着信"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"アンインストール"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"開く"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"有害なアプリが検出されました"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> にすべてのデバイスログへのアクセスを許可しますか?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"1 回限りのアクセスを許可"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"許可しない"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"デバイスのログに、このデバイスで発生したことが記録されます。アプリは問題を検出、修正するためにこれらのログを使用することができます。\n\nログによっては機密性の高い情報が含まれている可能性があるため、すべてのデバイスログへのアクセスは信頼できるアプリにのみ許可してください。\n\nすべてのデバイスログへのアクセスを許可しなかった場合も、このアプリはアプリ独自のログにアクセスできます。また、デバイスのメーカーもデバイスの一部のログや情報にアクセスできる可能性があります。"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"デバイスのログに、このデバイスで発生したことが記録されます。アプリは問題を検出、修正するためにこれらのログを使用することができます。\n\nログによっては機密性の高い情報が含まれている可能性があるため、すべてのデバイスログへのアクセスは信頼できるアプリにのみ許可してください。\n\nすべてのデバイスログへのアクセスを許可しなかった場合でも、このアプリはアプリ独自のログにアクセスできます。また、デバイスの製造メーカーもデバイスの一部のログや情報にアクセスできる可能性があります。\n\n詳しくは、g.co/android/devicelogs をご覧ください。"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"次回から表示しない"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"「<xliff:g id="APP_0">%1$s</xliff:g>」が「<xliff:g id="APP_2">%2$s</xliff:g>」のスライスの表示をリクエストしています"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"編集"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"着信や通知をバイブレーションで知らせます"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> からスマートフォンのカメラにアクセスできません"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> からタブレットのカメラにアクセスできません"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"ストリーミング中はアクセスできません。スマートフォンでのアクセスをお試しください。"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"ストリーミング中はピクチャー イン ピクチャーを表示できません"</string>
     <string name="system_locale_title" msgid="711882686834677268">"システムのデフォルト"</string>
     <string name="default_card_name" msgid="9198284935962911468">"カード <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 6d32f25..92a37ce 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ხმოვანი ფოსტა"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"კავშირის პრობლემა ან არასწორი MMI კოდი."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ფუნქცია მხარდაუჭერელია."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"ოპერაცია შეზღუდულია მხოლოდ დაშვებულ ნომრებზე."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ზარის გადამისამართების პარამეტრების თქვენი ტელეფონიდან შეცვლა როუმინგისას ვერ მოხერხდება."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"სერვისი ჩართულია."</string>
@@ -72,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ნაგულისხმებად დაყენებულია ნომრის დაფარვის გამორთვა. შემდეგი ზარი: არ არის დაფარული."</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"სერვისი არ არის მიწოდებული."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"არ შეგიძლიათ აბონენტის ID პარამეტრების შეცვლა."</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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"აპს შეეძლება, საკუთარი ნაწილები მუდმივად ჩაწეროს მეხსიერებაში. ეს შეზღუდავს მეხსიერების ხელმისაწვდომობას სხვა აპებისთვის და შეანელებს ტელეფონს."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"წინა პლანის სერვისის გაშვება"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"აპს შეეძლება, გამოიყენოს წინა პლანის სერვისები."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"პრიორიტეტული სერვისის გაშვება ტიპის „კამერა“ შემთხვევაში"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „კამერა“ შემთხვევაში"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"პრიორიტეტული სერვისის გაშვება ტიპის „დაკავშირებულიმოწყობილობა“ შემთხვევაში"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „დაკავშირებულიმოწყობილობა“ შემთხვევაში"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"პრიორიტეტული სერვისის გაშვება ტიპის „მონაცემებისსინქრონიზაცია“ შემთხვევაში"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „მონაცემებისსინქრონიზაცია“ შემთხვევაში"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"პრიორიტეტული სერვისის გაშვება ტიპის „მდებარეობა“ შემთხვევაში"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „მდებარეობა“ შემთხვევაში"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"პრიორიტეტული სერვისის გაშვება ტიპის „მედია-ფაილებისდაკვრა“ შემთხვევაში"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „მედია-ფაილებისდაკვრა“ შემთხვევაში"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"პრიორიტეტული სერვისის გაშვება ტიპის „მედიაპროეცირება“ შემთხვევაში"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „მედიაპროეცირება“ შემთხვევაში"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"პრიორიტეტული სერვისის გაშვება ტიპის „მიკროფონი“ შემთხვევაში"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „მიკროფონი“ შემთხვევაში"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"პრიორიტეტული სერვისის გაშვება ტიპის „სატელეფონოზარი“ შემთხვევაში"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „სატელეფონოზარი“ შემთხვევაში"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"პრიორიტეტული სერვისის გაშვება ტიპის „ჯანმრთელობა“ შემთხვევაში"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „ჯანმრთელობა“ შემთხვევაში"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"პრიორიტეტული სერვისის გაშვება ტიპის „დისტანციურიშეტყობინებები“ შემთხვევაში"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „დისტანციურიშეტყობინებები“ შემთხვევაში"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"პრიორიტეტული სერვისის გაშვება ტიპის \"გათავისუფლებულისისტემა\" შემთხვევაში"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „გათავისუფლებულისისტემა“ შემთხვევაში"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"პრიორიტეტული სერვისის გაშვება ტიპის „სპეციალურიგამოყენება“ შემთხვევაში"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „სპეციალურიგამოყენება“ შემთხვევაში"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"აპის მეხსიერების სივრცის გაზომვა"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"აპს შეეძლება, მოიპოვოს თავისი კოდი, მონაცემები და ქეშის ზომები."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"სისტემის პარამეტრების შეცვლა"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"ამ აპს გამოყენების დროს შეუძლია მიკროფონით აუდიოს ჩაწერა."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ფონურად ჩაწერს აუდიოს"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ამ აპს ნებისმიერ დროს შეუძლია მიკროფონით აუდიოს ჩაწერა."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"აპის ფანჯრების ეკრანის აღბეჭდვის გამოვლენა"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"ეს აპი მიიღებს შეტყობინებას, როდესაც ეკრანის ანაბეჭდის გადაღება აპის გამოყენების პროცესში მოხდება."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"ბრძანებების SIM-ზე გაგზავნა"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"აპისთვის ნების დართვა გაუგზავნოს ბრძანებები SIM-ბარათს. ეს ძალიან საშიშია."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"ფიზიკური აქტივობის ამოცნობა"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"საშუალებას აძლევს აპს, წაიკითხოს ვიდეო ფაილები თქვენი ზიარი მეხსიერებიდან."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"სურათების ფაილების წაკითხვა ზიარი მეხსიერებიდან"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"საშუალებას აძლევს აპს, წაიკითხოს სურათის ფაილები თქვენი ზიარი მეხსიერებიდან."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"წაიკითხეთ მომხმარებლის მიერ არჩეული სურათი და ვიდეო ფაილები საზიარო მეხსიერებიდან"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"აპს შეეძლება წაიკითხოს სურათი და ვიდეო ფაილები, რომლებიც არჩეულია თქვენი საზიარო მეხსიერებიდან."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"თქვენი ზიარი მეხსიერების შიგთავსის შეცვლა ან წაშლა"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"საშუალებას აძლევს აპს, ჩაწეროს თქვენი ზიარი მეხსიერების შიგთავსი."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP ზარების წამოწყება/მიღება"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"დეინსტალაცია"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"მაინც გახსნა"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"აღმოჩენილია საზიანო აპი"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"გსურთ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>-ს მიანიჭოთ მოწყობილობის ყველა ჟურნალზე წვდომა?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ერთჯერადი წვდომის დაშვება"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"არ დაიშვას"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"მოწყობილობის ჟურნალში იწერება, რა ხდება ამ მოწყობილობაზე. აპებს შეუძლია ამ ჟურნალების გამოყენება პრობლემების აღმოსაჩენად და მოსაგვარებლად.\n\nზოგი ჟურნალი შეიძლება სენსიტიური ინფორმაციის მატარებელი იყოს, ამიტომაც მოწყობილობის ყველა ჟურნალზე წვდომა მხოლოდ სანდო აპებს მიანიჭეთ. \n\nთუ ამ აპს მოწყობილობის ყველა ჟურნალზე წვდომას არ მიანიჭებთ, მას მაინც ექნება წვდომა თქვენს ჟურნალებზე. თქვენი მოწყობილობის მწარმოებელს მაინც შეეძლება თქვენი მოწყობილობის ზოგიერთ ჟურნალსა თუ ინფორმაციაზე წვდომა."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"მოწყობილობის ჟურნალში იწერება, რა ხდება ამ მოწყობილობაზე. აპებს შეუძლია ამ ჟურნალების გამოყენება პრობლემების აღმოსაჩენად და მოსაგვარებლად.\n\nზოგი ჟურნალი შეიძლება სენსიტიური ინფორმაციის მატარებელი იყოს, ამიტომაც მოწყობილობის ყველა ჟურნალზე წვდომა მხოლოდ სანდო აპებს მიანიჭეთ. \n\nთუ ამ აპს მოწყობილობის ყველა ჟურნალზე წვდომას არ მიანიჭებთ, მას მაინც ექნება წვდომა საკუთარ ჟურნალებზე. თქვენი მოწყობილობის მწარმოებელს მაინც შეეძლება თქვენი მოწყობილობის ზოგიერთ ჟურნალსა თუ ინფორმაციაზე წვდომა.\n\n შეიტყვეთ მეტი g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"აღარ გამოჩნდეს"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>-ს სურს, გაჩვენოთ <xliff:g id="APP_2">%2$s</xliff:g>-ის ფრაგმენტები"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"რედაქტირება"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ზარების და შეტყობინებების მიღებისას ვიბრაცია ჩაირთვება"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ტელეფონის კამერაზე წვდომა ვერ მოხერხდა თქვენი <xliff:g id="DEVICE">%1$s</xliff:g>-დან"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ტაბლეტის კამერაზე წვდომა ვერ მოხერხდა თქვენი <xliff:g id="DEVICE">%1$s</xliff:g>-დან"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"მასზე წვდომის მიᲦება შეუძლებელია სტრიმინგის დროს. ცადეთ ტელეფონიდან."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"სტრიმინგის დროს ეკრანის ეკრანში ნახვა შეუძლებელია"</string>
     <string name="system_locale_title" msgid="711882686834677268">"სისტემის ნაგულისხმევი"</string>
     <string name="default_card_name" msgid="9198284935962911468">"ბარათი <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 0d9fd3f..04f0445 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Дауыстық пошта"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Байланыс мәселесі немесе MMИ коды жарамсыз."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функция қолданылмайды."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Әрекет анықталған сандарды теруге шектелген."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Роуминг кезінде телефоннан қоңырауды басқа нөмірге бағыттау параметрлері өзгертілмейді."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Қызмет қосылған."</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Қолданбаға өзінің бөліктерін жадта бекіндіру мүмкіндігін береді. Бұл басқа қолданбалардың жадқа қол жетімділігін шектеп, телефонды баяулатуы мүмкін."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"басымдылығы жоғары қызметті іске қосу"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Қолданбаға басымдылығы жоғары қызметтерді пайдалануға рұқсат береді."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"camera\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Қолданбаға \"camera\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"health\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Қолданбаға \"connectedDevice\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Қолданбаға \"dataSync\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"location\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Қолданбаға \"location\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Қолданбаға \"mediaPlayback\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Қолданбаға \"mediaProjection\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"microphone\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Қолданбаға \"microphone\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Қолданбаға \"phoneCall\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"health\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Қолданбаға \"health\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Қолданбаға \"remoteMessaging\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Қолданбаға \"systemExempted\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Қолданбаға \"specialUse\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"қолданба жадындағы бос орынды өлшеу"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Қолданбаға оның кодын, деректерін және кэш өлшемдерін шығарып алуға рұқсат береді"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"жүйе параметрлерін өзгерту"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Бұл қолданба жұмыс барысында микрофон арқылы аудиомазмұн жаза алады."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Фондық режимде аудиомазмұн жазу"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Бұл қолданба кез келген уақытта микрофон арқылы аудиомазмұн жаза алады."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"қолданба терезелерінің скриншоттарын анықтау"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Қолданбаны пайдаланып скриншот жасаған кезде, ескерту шығады."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIM картасына пәрмендер жіберу"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Қолданбаға SIM картасына пәрмен жіберу мүмкіндігін береді. Бұл өте қауіпті."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"физикалық әрекетті тану"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Қолданбаға ортақ жадтың бейнефайлдарын оқуға мүмкіндік береді."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"ортақ жадтың кескін файлдарын оқу"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Қолданбаға ортақ жадтың кескін файлдарын оқуға мүмкіндік береді."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"Пайдаланушының ортақ жадтағы сурет файлдарын және бейнефайлдарын оқу"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Қолданбаға ортақ жадтың сурет файлдарын және бейнефайлдарын оқуға мүмкіндік береді."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"ортақ жадтың мазмұнын өзгерту немесе жою"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Қолданбаға ортақ жадтың мазмұнын жазуға мүмкіндік береді."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP қоңырауларын шалу/қабылдау"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ЖОЮ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"БӘРІБІР АШУ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Зиянды қолданба анықталды"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> қолданбасына барлық құрылғының журналын пайдалануға рұқсат берілсін бе?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Бір реттік пайдалану рұқсатын беру"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Рұқсат бермеу"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Журналдарға құрылғыда не болып жатқаны жазылады. Қолданбалар осы журналдарды қате тауып, түзету үшін пайдаланады.\n\nКейбір журналдарда құпия ақпарат болуы мүмкін. Сондықтан барлық құрылғының журналын пайдалану рұқсаты тек сенімді қолданбаларға берілуі керек. \n\nБұл қолданбаға барлық құрылғының журналын пайдалануға рұқсат бермесеңіз де, ол өзінің журналдарын пайдалана береді. Құрылғы өндірушісі де құрылғыдағы кейбір журналдарды немесе ақпаратты пайдалануы мүмкін."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Журналдарға құрылғыда не болып жатқаны жазылады. Қолданбалар осы журналдарды қате тауып, түзету үшін пайдаланады.\n\nКейбір журналдарда құпия ақпарат болуы мүмкін. Сондықтан барлық құрылғының журналын пайдалану рұқсаты тек сенімді қолданбаларға берілуі керек. \n\nБұл қолданбаға барлық құрылғының журналын пайдалануға рұқсат бермесеңіз де, ол өзінің журналдарын пайдалана береді. Құрылғы өндірушісі де құрылғыдағы кейбір журналдарды немесе ақпаратты пайдалануы мүмкін.\n\nТолық ақпарат: g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Қайта көрсетілмесін"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> қолданбасы <xliff:g id="APP_2">%2$s</xliff:g> қолданбасының үзінділерін көрсеткісі келеді"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Өзгерту"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Қоңыраулар мен хабарландырулардың вибрациясы болады"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан телефон камерасын пайдалану мүмкін емес."</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан планшет камерасын пайдалану мүмкін емес."</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Трансляция кезінде мазмұнды көру мүмкін емес. Оның орнына телефоннан көріңіз."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Трансляция кезінде суреттегі суретті көру мүмкін емес."</string>
     <string name="system_locale_title" msgid="711882686834677268">"Жүйенің әдепкі параметрі"</string>
     <string name="default_card_name" msgid="9198284935962911468">"<xliff:g id="CARDNUMBER">%d</xliff:g>-КАРТА"</string>
 </resources>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 0c82b66..1477713 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"សារ​ជា​សំឡេង"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"បញ្ហា​ក្នុង​ការ​តភ្ជាប់​ ឬ​កូដ MMI មិន​ត្រឹមត្រូវ។"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"មិនអាចប្រើមុខងារនេះបានទេ។"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"ប្រតិបត្តិការ​ត្រូវ​បាន​ដាក់​កម្រិត​​​ចំពោះ​លេខ​ហៅ​ថេរ​តែ​ប៉ុណ្ណោះ។"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"មិន​អាច​ប្តូរ​ការ​កំណត់​នៃ​ការ​បញ្ជូន​ការ​ហៅ​បន្ត​ពី​ទូរសព្ទ​របស់​អ្នក​បាន​ទេ​ ខណៈ​ពេល​ដែល​អ្នក​កំពុង​ប្រើ​សេវា​រ៉ូមីង។"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"បាន​បើក​សេវាកម្ម។"</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ឲ្យ​កម្មវិធី ធ្វើជា​ផ្នែក​អចិន្ត្រៃយ៍​នៃ​ខ្លួន​ក្នុង​អង្គ​ចងចាំ។ វា​អាច​កម្រិត​អង្គ​ចងចាំ​អាច​ប្រើ​បាន​ ដើម្បី​ធ្វើ​ឲ្យ​កម្មវិធី​ផ្សេង​ធ្វើ​ឲ្យ​ទូរស័ព្ទ​របស់​អ្នក​យឺត។​"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"ដំណើរ​ការ​សេវាកម្ម​ផ្ទៃ​ខាង​មុខ"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"អនុញ្ញាត​ឱ្យ​កម្មវិធី​ប្រើ​ប្រាស់​សេវាកម្ម​ផ្ទៃខាង​មុខ។"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"វាស់​ទំហំ​ការ​ផ្ទុក​​កម្មវិធី"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"ឲ្យ​កម្មវិធី​ទៅ​យក​កូដ ទិន្នន័យ និង​ទំហំ​ឃ្លាំង​សម្ងាត់​របស់​វា"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"កែ​​ការ​កំណត់​ប្រព័ន្ធ"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"កម្មវិធី​នេះ​អាច​ថតសំឡេងដោយប្រើមីក្រូហ្វូន នៅពេលកំពុងប្រើប្រាស់កម្មវិធី។"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ថតសំឡេងនៅផ្ទៃខាងក្រោយ"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"កម្មវិធី​នេះ​អាច​ថត​សំឡេង​ដោយ​ប្រើ​មីក្រូហ្វូន​បាន​គ្រប់​ពេល។"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"រកឃើញការថត​អេក្រង់វិនដូកម្មវិធី"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"កម្មវិធី​នេះ​នឹង​ទទួល​បាន​ការ​ជូន​ដំណឹង​នៅ​ពេល​រូបថត​អេក្រង់ត្រូវបានថត ​ខណៈ​ពេល​​​កំពុង​ប្រើកម្មវិធី​នេះ។"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"ផ្ញើពាក្យបញ្ជាទៅស៊ីមកាត"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"ឲ្យ​កម្មវិធី​ផ្ញើ​ពាក្យ​បញ្ជា​ទៅ​ស៊ីម​កាត។ វា​គ្រោះ​ថ្នាក់​ណាស់។"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"ស្គាល់​សកម្មភាព​រាងកាយ"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"អនុញ្ញាតឱ្យ​កម្មវិធី​អានឯកសារវីដេអូពីទំហំផ្ទុករួមរបស់អ្នក។"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"អានឯកសាររូបភាពពីទំហំ​ផ្ទុករួម"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"អនុញ្ញាតឱ្យ​កម្មវិធី​អានឯកសាររូបភាពពីទំហំផ្ទុករួមរបស់អ្នក។"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"អានឯកសាររូបភាព និងវីដេអូដែលអ្នកប្រើប្រាស់ជ្រើសរើសពីទំហំផ្ទុករួម"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"អនុញ្ញាតឱ្យកម្មវិធីអានឯកសាររូបភាព និងវីដេអូដែលអ្នកជ្រើសរើសពីទំហំផ្ទុករួមរបស់អ្នក។"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"កែប្រែ ឬលុប​ខ្លឹមសារនៃ​ទំហំផ្ទុករួម​របស់អ្នក"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"អនុញ្ញាតឱ្យ​កម្មវិធី​សរសេរខ្លឹមសារនៃ​ទំហំផ្ទុករួម​របស់អ្នក។"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"បង្កើត/ទទួល ការ​ហៅ SIP"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"លុប"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"មិន​អីទេ បើក​ចុះ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"បាន​រកឃើញ​កម្មវិធី​ដែលបង្ក​គ្រោះថ្នាក់"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"អនុញ្ញាតឱ្យ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់ឬ?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"អនុញ្ញាតឱ្យចូលប្រើ​ម្ដង"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"មិនអនុញ្ញាត"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"កំណត់ហេតុឧបករណ៍កត់ត្រាអ្វីដែលកើតឡើងនៅលើឧបករណ៍របស់អ្នក។ កម្មវិធីអាចប្រើកំណត់ហេតុទាំងនេះដើម្បីស្វែងរក និងដោះស្រាយបញ្ហាបាន។\n\nកំណត់ហេតុមួយចំនួនអាចមានព័ត៌មានរសើប ដូច្នេះគួរអនុញ្ញាតឱ្យចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់សម្រាប់តែកម្មវិធីដែលអ្នកទុកចិត្តប៉ុណ្ណោះ។ \n\nប្រសិនបើអ្នកមិនអនុញ្ញាតឱ្យកម្មវិធីនេះចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់ទេ វានៅតែអាចចូលប្រើកំណត់ហេតុរបស់វាផ្ទាល់បាន។ ក្រុមហ៊ុន​ផលិត​ឧបករណ៍របស់អ្នក​ប្រហែលជា​នៅតែអាចចូលប្រើ​កំណត់ហេតុ ឬព័ត៌មានមួយចំនួន​នៅលើឧបករណ៍​របស់អ្នក​បានដដែល។"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"កំណត់ហេតុឧបករណ៍កត់ត្រាអ្វីដែលកើតឡើងនៅលើឧបករណ៍របស់អ្នក។ កម្មវិធីអាចប្រើកំណត់ហេតុទាំងនេះដើម្បីស្វែងរក និងដោះស្រាយបញ្ហាបាន។\n\nកំណត់ហេតុមួយចំនួនអាចមានព័ត៌មានរសើប ដូច្នេះគួរអនុញ្ញាតឱ្យចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់សម្រាប់តែកម្មវិធីដែលអ្នកទុកចិត្តប៉ុណ្ណោះ។ \n\nប្រសិនបើអ្នកមិនអនុញ្ញាតឱ្យកម្មវិធីនេះចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់ទេ វានៅតែអាចចូលប្រើកំណត់ហេតុរបស់វាផ្ទាល់បាន។ ក្រុមហ៊ុន​ផលិត​ឧបករណ៍របស់អ្នក​ប្រហែលជា​នៅតែអាចចូលប្រើ​កំណត់ហេតុ ឬព័ត៌មានមួយចំនួន​នៅលើឧបករណ៍​របស់អ្នក​បានដដែល។\n\nស្វែងយល់បន្ថែមតាមរយៈ g.co/android/devicelogs។"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"កុំ​បង្ហាញ​ម្ដង​ទៀត"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ចង់​បង្ហាញ​ស្ថិតិ​ប្រើប្រាស់​របស់ <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"កែ"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ការហៅ​ទូរសព្ទ និងការជូន​ដំណឹងនឹងញ័រ"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"មិនអាច​ចូលប្រើ​កាមេរ៉ាទូរសព្ទ​ពី <xliff:g id="DEVICE">%1$s</xliff:g> របស់អ្នក​បានទេ"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"មិនអាច​ចូលប្រើ​កាមេរ៉ា​ថេប្លេតពី <xliff:g id="DEVICE">%1$s</xliff:g> របស់អ្នក​បានទេ"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"មិន​អាច​ចូល​ប្រើប្រាស់​ខ្លឹមសារ​នេះ​បាន​ទេ ពេល​ផ្សាយ។ សូមសាកល្បងប្រើ​នៅលើ​ទូរសព្ទរបស់អ្នក​ជំនួសវិញ។"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"មិនអាចមើលរូបក្នុងរូបខណៈពេលកំពុងផ្សាយបានទេ"</string>
     <string name="system_locale_title" msgid="711882686834677268">"លំនាំ​ដើម​ប្រព័ន្ធ"</string>
     <string name="default_card_name" msgid="9198284935962911468">"កាត <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index e27527f..b5535c9 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ಧ್ವನಿಮೇಲ್"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"ಸಂಪರ್ಕ ಸಮಸ್ಯೆ ಇಲ್ಲವೇ ಅಮಾನ್ಯ MMI ಕೋಡ್."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ಫೀಚರ್ ಬಂಬಲಿಸುತ್ತಿಲ್ಲ."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"ಕಾರ್ಯಾಚರಣೆಯನ್ನು ಸ್ಥಿರ ದೂರವಾಣಿ ಸಂಖ್ಯೆಗಳಿಗೆ ಮಾತ್ರ ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ನೀವು ರೋಮಿಂಗ್‌ನಲ್ಲಿರುವಾಗ ನಿಮ್ಮ ಫೋನ್‌ನಿಂದ ಕರೆ ಫಾರ್ವರ್ಡ್ ಮಾಡುವಿಕೆಯ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"ಸೇವೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
@@ -72,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ಕರೆಮಾಡುವವರ ID ಅನ್ನು ನಿರ್ಬಂಧಿಸದಿರುವಂತೆ ಡಿಫಾಲ್ಟ್ ಮಾಡಲಾಗಿದೆ. ಮುಂದಿನ ಕರೆ: ನಿರ್ಬಂಧಿಸಲಾಗಿಲ್ಲ"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ಸೇವೆಯನ್ನು ಪೂರೈಸಲಾಗಿಲ್ಲ."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"ನೀವು ಕಾಲರ್‌ ID ಸೆಟ್ಟಿಂಗ್‌ ಬದಲಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ಸ್ಮರಣೆಯಲ್ಲಿ ನಿರಂತರವಾಗಿ ತನ್ನದೇ ಭಾಗಗಳನ್ನು ಮಾಡಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಇದು ಫೋನ್ ಕಾರ್ಯವನ್ನು ನಿಧಾನಗೊಳಿಸುವುದರ ಮೂಲಕ ಇತರ ಅಪ್ಲಿಕೇಶನ್‍‍ಗಳಿಗೆ ಲಭ್ಯವಿರುವ ಸ್ಮರಣೆಯನ್ನು ಮಿತಿಗೊಳಿಸುತ್ತದೆ."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್‌ ಮಾಡಿ"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡಿ."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"ಕ್ಯಾಮರಾ\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"\"ಕ್ಯಾಮರಾ\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"\"connectedDevice\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"\"dataSync\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"ಸ್ಥಳ\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"\"ಸ್ಥಳ\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"\"mediaPlayback\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"\"mediaProjection\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"ಮೈಕ್ರೊಫೋನ್‌\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"\"ಮೈಕ್ರೊಫೋನ್‌\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"\"phoneCall\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"ಅರೋಗ್ಯ\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"\"ಅರೋಗ್ಯ\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"\"remoteMessaging\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ಅಪ್ಲಿಕೇಶನ್‌ ಸಂಗ್ರಹ ಸ್ಥಳವನ್ನು ಅಳೆಯಿರಿ"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"ಅದರ ಕೋಡ್‌‌, ಡೇಟಾ, ಮತ್ತು ಕ್ಯಾಷ್‌ ಗಾತ್ರಗಳನ್ನು ಹಿಂಪಡೆಯಲು ಅಪ್ಲಿಕೇಶನ್‌‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"ಸಿಸ್ಟಂ ಸೆಟ್ಟಿಂಗ್‍ಗಳನ್ನು ಮಾರ್ಪಡಿಸಿ"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"ಆ್ಯಪ್ ಬಳಕೆಯಲ್ಲಿರುವಾಗ ಈ ಆ್ಯಪ್ ಮೈಕ್ರೊಫೋನ್ ಬಳಸಿ ಆಡಿಯೊವನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ಹಿನ್ನೆಲೆಯಲ್ಲಿ ಆಡಿಯೊವನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ಈ ಆ್ಯಪ್ ಮೈಕ್ರೋಫೋನ್ ಬಳಸುವ ಮೂಲಕ ಯಾವುದೇ ಸಮಯದಲ್ಲಾದರೂ ಆಡಿಯೋ ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ಆ್ಯಪ್ ವಿಂಡೋಗಳ ಸ್ಕ್ರೀನ್ ಕ್ಯಾಪ್ಚರ್‌ಗಳನ್ನು ಪತ್ತೆ ಮಾಡಿ"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"ಆ್ಯಪ್ ಬಳಕೆಯಲ್ಲಿರುವಾಗ ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಒಂದನ್ನು ತೆಗೆದುಕೊಂಡಾಗ ಈ ಆ್ಯಪ್ ಸೂಚನೆಯನ್ನು ಪಡೆಯುತ್ತದೆ."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"ಸಿಮ್‌ಗೆ ಆಜ್ಞೆಗಳನ್ನು ಕಳುಹಿಸಿ"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"ಸಿಮ್‌ ಗೆ ಆದೇಶಗಳನ್ನು ಕಳುಹಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಇದು ತುಂಬಾ ಅಪಾಯಕಾರಿ."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"ದೈಹಿಕ ಚಟುವಟಿಕೆಯನ್ನು ಗುರುತಿಸಿ"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ನಿಮ್ಮ ಹಂಚಿಕೊಂಡ ಸಂಗ್ರಹಣೆಯಿಂದ ವೀಡಿಯೊ ಫೈಲ್‌ಗಳನ್ನು ಓದಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"ಹಂಚಿಕೊಂಡ ಸಂಗ್ರಹಣೆಯಿಂದ ಚಿತ್ರದ ಫೈಲ್‌ಗಳನ್ನು ಓದಿ"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"ನಿಮ್ಮ ಹಂಚಿಕೊಂಡ ಸಂಗ್ರಹಣೆಯಿಂದ ಚಿತ್ರದ ಫೈಲ್‌ಗಳನ್ನು ಓದಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ಹಂಚಿಕೊಂಡ ಸಂಗ್ರಹಣೆಯಿಂದ ಬಳಕೆದಾರರು ಆಯ್ಕೆಮಾಡಿದ ಚಿತ್ರ ಮತ್ತು ವೀಡಿಯೊ ಫೈಲ್‌ಗಳನ್ನು ಓದಿರಿ"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ನಿಮ್ಮ ಹಂಚಿಕೊಂಡ ಸಂಗ್ರಹಣೆಯಿಂದ ನೀವು ಆಯ್ಕೆಮಾಡಿದ ಚಿತ್ರ ಮತ್ತು ವೀಡಿಯೊ ಫೈಲ್‌ಗಳನ್ನು ಓದಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"ನಿಮ್ಮ ಹಂಚಿಕೊಂಡ ಸಂಗ್ರಹಣೆಯ ವಿಷಯಗಳನ್ನು ಮಾರ್ಪಡಿಸಿ ಅಥವಾ ಅಳಿಸಿ"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"ನಿಮ್ಮ ಹಂಚಿಕೊಂಡ ಸಂಗ್ರಹಣೆಯ ವಿಷಯಗಳನ್ನು ಬರೆಯಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"ಎಸ್‌ಐಪಿ ಕರೆಗಳನ್ನು ಮಾಡಿ/ಸ್ವೀಕರಿಸಿ"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ಹೇಗಿದ್ದರೂ ತೆರೆಯಿರಿ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"ಅಪಾಯಕಾರಿ ಅಪ್ಲಿಕೇಶನ್ ಕಂಡುಬಂದಿದೆ"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"ಎಲ್ಲಾ ಸಾಧನದ ಲಾಗ್‌ಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ಗೆ ಅನುಮತಿಸುವುದೇ?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ಒಂದು ಬಾರಿಯ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಿ"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ಅನುಮತಿಸಬೇಡಿ"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕಾರ್ಯಾಚರಣೆಗಳನ್ನು ಸಾಧನದ ಲಾಗ್‌ಗಳು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತವೆ. ಸಮಸ್ಯೆಗಳನ್ನು ಪತ್ತೆಹಚ್ಚಲು ಮತ್ತು ಪರಿಹರಿಸಲು ಆ್ಯಪ್‌ಗಳು ಈ ಲಾಗ್ ಅನ್ನು ಬಳಸಬಹುದು.\n\nಕೆಲವು ಲಾಗ್‌ಗಳು ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಿರಬಹುದು, ಆದ್ದರಿಂದ ನಿಮ್ಮ ವಿಶ್ವಾಸಾರ್ಹ ಆ್ಯಪ್‌ಗಳಿಗೆ ಮಾತ್ರ ಸಾಧನದ ಎಲ್ಲಾ ಲಾಗ್‌ಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಿ. \n\nಎಲ್ಲಾ ಸಾಧನ ಲಾಗ್‌ಗಳನ್ನು ಪ್ರವೇಶಿಸಲು ನೀವು ಈ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸದಿದ್ದರೆ, ಅದು ಆಗಲೂ ತನ್ನದೇ ಆದ ಲಾಗ್‌ಗಳನ್ನು ಪ್ರವೇಶಿಸಬಹುದು. ನಿಮ್ಮ ಸಾಧನ ತಯಾರಕರಿಗೆ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕೆಲವು ಲಾಗ್‌ಗಳು ಅಥವಾ ಮಾಹಿತಿಯನ್ನು ಪ್ರವೇಶಿಸಲು ಈಗಲೂ ಸಾಧ್ಯವಾಗುತ್ತದೆ."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕಾರ್ಯಾಚರಣೆಗಳನ್ನು ಸಾಧನದ ಲಾಗ್‌ಗಳು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತವೆ. ಸಮಸ್ಯೆಗಳನ್ನು ಪತ್ತೆಹಚ್ಚಲು ಮತ್ತು ಪರಿಹರಿಸಲು ಆ್ಯಪ್‌ಗಳು ಈ ಲಾಗ್ ಅನ್ನು ಬಳಸಬಹುದು.\n\nಕೆಲವು ಲಾಗ್‌ಗಳು ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಿರಬಹುದು, ಆದ್ದರಿಂದ ನಿಮ್ಮ ವಿಶ್ವಾಸಾರ್ಹ ಆ್ಯಪ್‌ಗಳಿಗೆ ಮಾತ್ರ ಸಾಧನದ ಎಲ್ಲಾ ಲಾಗ್‌ಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಿ. \n\nಎಲ್ಲಾ ಸಾಧನ ಲಾಗ್‌ಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ನೀವು ಈ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸದಿದ್ದರೆ, ಅದು ಆಗಲೂ ತನ್ನದೇ ಆದ ಲಾಗ್‌ಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಬಹುದು. ಹಾಗಿದ್ದರೂ, ನಿಮ್ಮ ಸಾಧನ ತಯಾರಕರಿಗೆ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕೆಲವು ಲಾಗ್‌ಗಳು ಅಥವಾ ಮಾಹಿತಿಯನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಬಹುದು.\n\ng.co/android/devicelogs ನಲ್ಲಿ ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ಮತ್ತೊಮ್ಮೆ ತೋರಿಸಬೇಡಿ"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_2">%2$s</xliff:g> ಸ್ಲೈಸ್‌ಗಳನ್ನು <xliff:g id="APP_0">%1$s</xliff:g> ತೋರಿಸಲು ಬಯಸಿದೆ"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ಎಡಿಟ್"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ಕರೆಗಳು ಮತ್ತು ಅಧಿಸೂಚನೆಗಳು ವೈಬ್ರೇಟ್‌ ಆಗುತ್ತವೆ"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ನಿಮ್ಮ <xliff:g id="DEVICE">%1$s</xliff:g> ಮೂಲಕ ಫೋನ್‌ನ ಕ್ಯಾಮರಾವನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ನಿಮ್ಮ <xliff:g id="DEVICE">%1$s</xliff:g> ಮೂಲಕ ಟ್ಯಾಬ್ಲೆಟ್‌ನ ಕ್ಯಾಮರಾವನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"ಸ್ಟ್ರೀಮ್ ಮಾಡುವಾಗ ಇದನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಅದರ ಬದಲು ನಿಮ್ಮ ಫೋನ್‌ನಲ್ಲಿ ಪ್ರಯತ್ನಿಸಿ."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"ಸ್ಟ್ರೀಮ್ ಮಾಡುವಾಗ ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರವನ್ನು ವೀಕ್ಷಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
     <string name="system_locale_title" msgid="711882686834677268">"ಸಿಸ್ಟಂ ಡೀಫಾಲ್ಟ್"</string>
     <string name="default_card_name" msgid="9198284935962911468">"ಕಾರ್ಡ್ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index c953a39..aa6feae 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"음성사서함"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"연결에 문제가 있거나 MMI 코드가 잘못되었습니다."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"기능이 지원되지 않습니다."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"발신 허용 번호에서만 수행할 수 있는 작업입니다."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"로밍 중에는 착신 전환 설정을 변경할 수 없습니다."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"서비스를 사용하도록 설정했습니다."</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"앱이 그 일부분을 영구적인 메모리로 만들 수 있도록 허용합니다. 이렇게 하면 다른 앱이 사용할 수 있는 메모리를 제한하여 휴대전화의 속도를 저하시킬 수 있습니다."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"포그라운드 서비스 실행"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"앱에서 포그라운드 서비스를 사용하도록 허용합니다."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\'camera\' 유형의 포그라운드 서비스 실행"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"앱에서 \'camera\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\'connectedDevice\' 유형의 포그라운드 서비스 실행"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"앱에서 \'connectedDevice\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\'dataSync\' 유형의 포그라운드 서비스 실행"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"앱에서 \'dataSync\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\'location\' 유형의 포그라운드 서비스 실행"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"앱에서 \'location\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\'mediaPlayback\' 유형의 포그라운드 서비스 실행"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"앱에서 \'mediaPlayback\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\'mediaProjection\' 유형의 포그라운드 서비스 실행"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"앱에서 \'mediaProjection\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\'microphone\' 유형의 포그라운드 서비스 실행"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"앱에서 \'microphone\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\'phoneCall\' 유형의 포그라운드 서비스 실행"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"앱에서 \'phoneCall\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\'health\' 유형의 포그라운드 서비스 실행"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"앱에서 \'health\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\'remoteMessaging\' 유형의 포그라운드 서비스 실행"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"앱에서 \'remoteMessaging\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\'systemExempted\' 유형의 포그라운드 서비스 실행"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"앱에서 \'systemExempted\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\'specialUse\' 유형의 포그라운드 서비스 실행"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"앱에서 \'specialUse\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"앱 저장공간 계산"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"앱이 해당 코드, 데이터 및 캐시 크기를 검색할 수 있도록 허용합니다."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"시스템 설정 수정"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"앱을 사용하는 동안 앱에서 마이크를 사용하여 오디오를 녹음할 수 있습니다."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"백그라운드에서 오디오 녹음"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"언제든지 앱에서 마이크를 사용하여 오디오를 녹음할 수 있습니다."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"앱 창 화면 캡처 감지"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"앱이 사용 중일 때 스크린샷을 찍으면 알림이 전송됩니다."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIM 카드로 명령 전송"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"앱이 SIM에 명령어를 전송할 수 있도록 허용합니다. 이 기능은 매우 신중히 허용해야 합니다."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"신체 활동 확인"</string>
@@ -594,7 +623,7 @@
     <string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"지문을 등록할 때마다 손가락을 조금씩 이동하세요"</string>
   <string-array name="fingerprint_acquired_vendor">
   </string-array>
-    <string name="fingerprint_error_not_match" msgid="4599441812893438961">"지문이 인식되지 않습니다."</string>
+    <string name="fingerprint_error_not_match" msgid="4599441812893438961">"지문이 인식되지 않았습니다."</string>
     <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"지문을 인식할 수 없습니다."</string>
     <string name="fingerprint_authenticated" msgid="2024862866860283100">"지문이 인증됨"</string>
     <string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"얼굴이 인증되었습니다"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"앱이 공유 저장소에서 동영상 파일을 읽도록 허용합니다."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"공유 저장소에서 이미지 파일 읽기"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"앱이 공유 저장소에서 이미지 파일을 읽도록 허용합니다."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"공유 저장소에서 사용자가 선택한 이미지 및 동영상 파일 읽기"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"앱이 공유 저장소에서 내가 선택한 이미지와 동영상 파일을 읽을 수 있도록 허용합니다."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"공유 저장공간의 콘텐츠 수정 또는 삭제"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"앱이 공유 저장공간의 콘텐츠에 쓰도록 허용합니다."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP 통화 발신/수신"</string>
@@ -1249,7 +1280,7 @@
     <string name="android_upgrading_complete" msgid="409800058018374746">"부팅 완료"</string>
     <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"전원 버튼을 눌렀습니다. 이러면 보통 화면이 꺼집니다.\n\n지문 설정 중에 가볍게 탭하세요."</string>
     <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"설정을 완료하려면 화면을 끄세요"</string>
-    <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"사용 중지"</string>
+    <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"화면 끄기"</string>
     <string name="fp_power_button_bp_title" msgid="5585506104526820067">"지문 인증을 계속할까요?"</string>
     <string name="fp_power_button_bp_message" msgid="2983163038168903393">"전원 버튼을 눌렀습니다. 이러면 보통 화면이 꺼집니다.\n\n지문을 인식하려면 화면을 가볍게 탭하세요."</string>
     <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"화면 끄기"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"제거"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"열기"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"유해한 앱 감지됨"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>에서 모든 기기 로그에 액세스하도록 허용하시겠습니까?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"일회성 액세스 허용"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"허용 안함"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"기기 로그에 기기에서 발생한 상황이 기록됩니다. 앱은 문제를 찾고 해결하는 데 이 로그를 사용할 수 있습니다.\n\n일부 로그는 민감한 정보를 포함할 수 있으므로 신뢰할 수 있는 앱만 모든 기기 로그에 액세스하도록 허용하세요. \n\n앱에 전체 기기 로그에 대한 액세스 권한을 부여하지 않아도 앱이 자체 로그에는 액세스할 수 있습니다. 기기 제조업체에서 일부 로그 또는 기기 내 정보에 액세스할 수도 있습니다."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"기기 로그에는 기기에서 발생한 상황이 기록됩니다. 앱은 문제를 찾고 해결하는 데 이 로그를 사용할 수 있습니다.\n\n일부 로그에 민감한 정보가 포함될 수 있으므로 신뢰할 수 있는 앱만 모든 기기 로그에 액세스하도록 허용하세요. \n\n앱에 전체 기기 로그에 대한 액세스 권한을 부여하지 않아도, 앱이 자체 로그에는 액세스할 수 있습니다. 기기 제조업체에서 기기 내 일부 로그 또는 정보에 액세스할 수도 있습니다.\n\ng.co/android/devicelogs에서 자세히 알아보세요."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"다시 표시 안함"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>에서 <xliff:g id="APP_2">%2$s</xliff:g>의 슬라이스를 표시하려고 합니다"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"수정"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"전화 및 알림이 오면 진동이 사용됩니다."</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"사용자의 <xliff:g id="DEVICE">%1$s</xliff:g>에서 휴대전화 카메라에 액세스할 수 없습니다."</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"사용자의 <xliff:g id="DEVICE">%1$s</xliff:g>에서 태블릿 카메라에 액세스할 수 없습니다."</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"스트리밍 중에는 액세스할 수 없습니다. 대신 휴대전화에서 시도해 보세요."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"스트리밍 중에는 PIP 모드를 볼 수 없습니다."</string>
     <string name="system_locale_title" msgid="711882686834677268">"시스템 기본값"</string>
     <string name="default_card_name" msgid="9198284935962911468">"<xliff:g id="CARDNUMBER">%d</xliff:g> 카드"</string>
 </resources>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index dccc4a6..8201c50 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Үн почтасы"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Туташууда көйгөй чыкты же MMI коду жараксыз."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функция колдоого алынбайт."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Иш-аракет туруктуу терүү номерлери менен гана чектелет."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Роуминг учурунда чалууну башка номерге багыттоонун жөндөөлөрүн телефонуңуздан өзгөртүү мүмкүн эмес."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Кызмат иштетилди."</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Колдонмого  өзүнүн бөлүктөрүн эстутумда туруктуу кармоого уруксат берет. Бул эстутумдун башка колдонмолорго жетиштүүлүгүн чектеши жана телефондун иштешин жайлатышы мүмкүн."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"активдүү кызматты иштетүү"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Колдонмолорго алдынкы пландагы кызматтарды колдонууга уруксат берет."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"алдынкы пландагы \"camera\" түрүндөгү кызматты аткаруу"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Колдонмолорго алдынкы пландагы \"camera\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"алдынкы пландагы \"connectedDevice\" түрүндөгү кызматты аткаруу"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Колдонмолорго алдынкы пландагы \"connectedDevice\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"алдынкы пландагы \"dataSync\" түрүндөгү кызматты аткаруу"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Колдонмолорго алдынкы пландагы \"dataSync\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"алдынкы пландагы \"location\" түрүндөгү кызматты аткаруу"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Колдонмолорго алдынкы пландагы \"location\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"алдынкы пландагы \"mediaPlayback\" түрүндөгү кызматты аткаруу"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Колдонмолорго алдынкы пландагы \"mediaPlayback\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"алдынкы пландагы \"mediaProjection\" түрүндөгү кызматты аткаруу"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Колдонмолорго алдынкы пландагы \"mediaProjection\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"алдынкы пландагы \"microphone\" түрүндөгү кызматты аткаруу"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Колдонмолорго алдынкы пландагы \"microphone\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"алдынкы пландагы \"phoneCall\" түрүндөгү кызматты аткаруу"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Колдонмолорго алдынкы пландагы \"phoneCall\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"алдынкы пландагы \"health\" түрүндөгү кызматты аткаруу"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Колдонмолорго алдынкы пландагы \"health\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"алдынкы пландагы \"remoteMessaging\" түрүндөгү кызматты аткаруу"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Колдонмолорго алдынкы пландагы \"remoteMessaging\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"алдынкы пландагы \"systemExempted\" түрүндөгү кызматты аткаруу"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Колдонмолорго алдынкы пландагы \"systemExempted\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"алдынкы пландагы \"specialUse\" түрүндөгү кызматты аткаруу"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Колдонмолорго алдынкы пландагы \"specialUse\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"колдонмо сактагычынын мейкиндигин өлчөө"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Колдонмого өз кодун, дайындарын жана кэш өлчөмдөрүн түшүрүп алуу мүмкүнчүлүгүн берет"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"система тууралоолорун өзгөртүү"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Бул колдонмо иштеп жатканда микрофон менен аудио файлдарды жаздыра алат."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Фондо аудио жаздыруу"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Бул колдонмо каалаган убакта микрофон менен аудио файлдарды жаздыра алат."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"колдонмонун терезелериндегилер сүрөткө тартылганын аныктоо"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Колдонмо иштеп жатканда скриншот тартылса, колдонмого кабарланат."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIM-картага буйруктарды жөнөтүү"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Колдонмого SIM-картага буйруктарды жөнөтүү мүмкүнчүлүгүн берет. Бул абдан кооптуу."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"Кыймыл-аракетти аныктоо"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Колдонмого жалпы сактагычыңыздагы видеолорду окуу мүмкүнчүлүгүн берет."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"жалпы сактагычтагы сүрөт файлдарды окуу"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Колдонмого жалпы сактагычыңыздагы сүрөт файлдарды окуу мүмкүнчүлүгүн берет."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"жалпы сактагычтагы колдонуучу тандаган сүрөт жана видео файлдарын окуу"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Колдонмого жалпы сактагычыңыздагы өзүңүз тандаган сүрөт жана видео файлдарын окуу мүмкүнчүлүгүн берет."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"жалпы сактагычыңыздын мазмунун өзгөртүү же жок кылуу"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Колдонмого жалпы сактагычыңыздын мазмунун жазуу мүмкүнчүлүгүн берет."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP чалуу/чалууну кабыл алуу"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ЧЫГАРЫП САЛУУ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"БААРЫ БИР АЧЫЛСЫН"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Зыянкеч колдонмо аныкталды"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> колдонмосуна түзмөктөгү бардык таржымалдарды жеткиликтүү кыласызбы?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Бир жолу жеткиликтүү кылуу"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Жок"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Түзмөктө аткарылган бардык аракеттер түзмөктүн таржымалдарында сакталып калат. Колдонмолор бул таржымалдарды колдонуп, маселелерди оңдошот.\n\nАйрым таржымалдарда купуя маалымат болушу мүмкүн, андыктан түзмөктөгү бардык таржымалдарды ишенимдүү колдонмолорго гана пайдаланууга уруксат бериңиз. \n\nЭгер бул колдонмого түзмөктөгү айрым таржымалдарга кирүүгө тыюу салсаңыз, ал өзүнүн таржымалдарын пайдалана берет. Түзмөктү өндүрүүчү түзмөгүңүздөгү айрым таржымалдарды же маалыматты көрө берет."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Түзмөктө жасалган нерселердин баары таржымалга сактала берет. Колдонмолор анын жардамы менен көйгөйлөрдү аныктап, оңдоп турушат.\n\nАйрым таржымалдарда купуя маалымат болушу мүмкүн, андыктан ишенимдүү колдонмолорго гана түзмөктөгү бардык таржымалдарды пайдаланууга уруксат бериңиз. \n\nЭгер бул колдонмого түзмөктөгү айрым таржымалдарга кирүүгө тыюу салсаңыз, ал өзүнүн таржымалдарын пайдалана берет. Түзмөктү өндүрүүчү түзмөгүңүздөгү айрым таржымалдарды же маалыматты көрө берет.\n\nКеңири маалымат: g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Экинчи көрүнбөсүн"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> колдонмосу <xliff:g id="APP_2">%2$s</xliff:g> үлгүлөрүн көрсөткөнү жатат"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Түзөтүү"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Чалуулар менен билдирмелер дирилдөө режиминде иштейт"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> түзмөгүңүздөн телефондун камерасына мүмкүнчүлүк жок"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> түзмөгүңүздөн планшетиңиздин камерасына мүмкүнчүлүк жок"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Муну алып ойнотуу учурунда көрүүгө болбойт. Анын ордуна телефондон кирип көрүңүз."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Алып ойнотуп жатканда сүрөттөгү сүрөт көрүнбөйт"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Системанын демейки параметрлери"</string>
     <string name="default_card_name" msgid="9198284935962911468">"КАРТА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index c6524de..d7b77bfc 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ຂໍ້ຄວາມສຽງ"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"ມີບັນຫາໃນການເຊື່ອມຕໍ່ ຫຼືລະຫັດ MMI ບໍ່ຖືກຕ້ອງ."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ບໍ່ຮອງຮັບຄຸນສົມບັດ."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"ການດຳເນີນການຖືກຈຳກັດເປັນ ຈຳກັດໝາຍເລກໂທອອກເທົ່ານັ້ນ."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Can not change call forwarding settings from your phone while you are roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"ບໍລິການຖືກເປີດໄວ້ແລ້ວ."</string>
@@ -72,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ໝາຍເລກຜູ່ໂທ ໄດ້ຮັບການຕັ້ງຄ່າເລີ່ມຕົ້ນເປັນ ບໍ່ຖືກຈຳກັດ. ການໂທຄັ້ງຕໍ່ໄປ: ບໍ່ຖືກຈຳກັດ."</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ບໍ່ໄດ້ເປີດໃຊ້ບໍລິການ."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"ທ່ານບໍ່ສາມາດປ່ຽນແປງການຕັ້ງຄ່າ Caller ID"</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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ອະນຸຍາດໃຫ້ແອັບຯເຮັດໃຫ້ສ່ວນນຶ່ງຂອງຕົນເອງ ຄົງຢູ່ຖາວອນໃນໜ່ວຍຄວາມຈຳ ເຊິ່ງອາດສາມາດ ເຮັດໃຫ້ການນຳໃຊ້ໜ່ວຍຄວາມຈຳຂອງແອັບຯ ອື່ນຖືກຈຳກັດ ສົ່ງຜົນເຮັດໃຫ້ມືຖືຂອງທ່ານເຮັດວຽກຊ້າລົງໄດ້."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"ໃຊ້ບໍລິການພື້ນໜ້າ"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ບໍລິການພື້ນໜ້າ."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ກ້ອງຖ່າຍຮູບ\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ກ້ອງຖ່າຍຮູບ\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ອຸປະກອນທີ່ເຊື່ອມຕໍ່\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ອຸປະກອນທີ່ເຊື່ອມຕໍ່\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການຊິ້ງຂໍ້ມູນ\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການຊິ້ງຂໍ້ມູນ\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ສະຖານທີ່\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ສະຖານທີ່\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການຫຼິ້ນມີເດຍ\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການຫຼິ້ນມີເດຍ\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການສາຍພາບມີເດຍ\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການສາຍພາບມີເດຍ\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ໄມໂຄຣໂຟນ\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ໄມໂຄຣໂຟນ\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ສາຍໂທ\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ສາຍໂທ\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ສຸຂະພາບ\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ສຸຂະພາບ\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການຮັບສົ່ງຂໍ້ຄວາມຈາກໄລຍະໄກ\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການຮັບສົ່ງຂໍ້ຄວາມຈາກໄລຍະໄກ\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ໄດ້ຮັບການຍົກເວັ້ນຈາກລະບົບ\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ໄດ້ຮັບການຍົກເວັ້ນຈາກລະບົບ\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການນຳໃຊ້ພິເສດ\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການນຳໃຊ້ພິເສດ\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ກວດສອບພື້ນທີ່ຈັດເກັບຂໍ້ມູນແອັບຯ"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"ອະນຸຍາດໃຫ້ແອັບຯດຶງໂຄດ, ຂໍ້ມູນ ແລະຂະໜາດ cache ຂອງມັນໄດ້."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"ແກ້ໄຂການຕັ້ງຄ່າລະບົບ"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"ແອັບນີ້ສາມາດບັນທຶກສຽງດ້ວຍໄມໂຄຣໂຟນໃນຂະນະທີ່ກຳລັງໃຊ້ແອັບຢູ່ໄດ້."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ບັນທຶກສຽງໃນພື້ນຫຼັງ"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ແອັບນີ້ສາມາດບັນທຶກສຽງດ້ວຍໄມໂຄຣໂຟນຕອນໃດກໍໄດ້."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ກວດຫາການຖ່າຍຮູບໜ້າຈໍຂອງໜ້າຈໍແອັບ"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"ແອັບນີ້ຈະໄດ້ຮັບການແຈ້ງເຕືອນເມື່ອມີການຖ່າຍຮູບໜ້າຈໍໃນຂະນະທີ່ກຳລັງໃຊ້ແອັບຢູ່."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"ສົ່ງ​ຄຳ​ສັ່ງ​ຫາ SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"ອະນຸຍາດໃຫ້ແອັບຯສົ່ງຄຳສັ່ງຫາ SIM. ສິ່ງນີ້ອັນຕະລາຍຫຼາຍ."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"ຈຳແນກກິດຈະກຳທາງກາຍ"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ອະນຸຍາດໃຫ້ແອັບອ່ານໄຟລ໌ວິດີໂອຈາກບ່ອນຈັດເກັບຂໍ້ມູນທີ່ແບ່ງປັນຂອງທ່ານ."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"ອ່ານໄຟລ໌ຮູບຈາກບ່ອນຈັດເກັບຂໍ້ມູນທີ່ແບ່ງປັນ"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"ອະນຸຍາດໃຫ້ແອັບອ່ານໄຟລ໌ຮູບຈາກບ່ອນຈັດເກັບຂໍ້ມູນທີ່ແບ່ງປັນຂອງທ່ານ."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ອ່ານໄຟລ໌ຮູບ ແລະ ວິດີໂອທີ່ຜູ້ໃຊ້ເລືອກຈາກບ່ອນຈັດເກັບຂໍ້ມູນທີ່ແບ່ງປັນ"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ອະນຸຍາດໃຫ້ແອັບອ່ານໄຟລ໌ຮູບ ແລະ ວິດີໂອທີ່ທ່ານເລືອກຈາກບ່ອນຈັດເກັບຂໍ້ມູນທີ່ແບ່ງປັນຂອງທ່ານ."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"ແກ້ໄຂ ຫຼືລຶບເນື້ອຫາໃນບ່ອນຈັດເກັບຂໍ້ມູນທີ່ແບ່ງປັນຂອງທ່ານ"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"ອະນຸຍາດໃຫ້ແອັບຂຽນເນື້ອຫາຕ່າງໆຂອງບ່ອນຈັດເກັບຂໍ້ມູນທີ່ແບ່ງປັນຂອງທ່ານ."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"ຮັບສາຍ/ໂທອອກ ຜ່ານ SIP"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ຖອນການຕິດຕັ້ງ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ຢືນຢັນການເປີດ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"ກວດສອບແອັບທີ່ເປັນອັນຕະລາຍ"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"ອະນຸຍາດໃຫ້ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດບໍ?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ອະນຸຍາດການເຂົ້າເຖິງແບບເທື່ອດຽວ"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ບໍ່ອະນຸຍາດ"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ບັນທຶກອຸປະກອນຈະບັນທຶກສິ່ງທີ່ເກີດຂຶ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ແອັບສາມາດໃຊ້ບັນທຶກເຫຼົ່ານີ້ເພື່ອຊອກຫາ ແລະ ແກ້ໄຂບັນຫາໄດ້.\n\nບັນທຶກບາງຢ່າງອາດມີຂໍ້ມູນລະອຽດອ່ອນ, ດັ່ງນັ້ນໃຫ້ອະນຸຍາດສະເພາະແອັບທີ່ທ່ານເຊື່ອຖືໃຫ້ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດເທົ່ານັ້ນ. \n\nຫາກທ່ານບໍ່ອະນຸຍາດແອັບນີ້ໃຫ້ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດ, ມັນຈະຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກຂອງຕົວມັນເອງໄດ້ຢູ່. ຜູ້ຜະລິດອຸປະກອນຂອງທ່ານອາດຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກ ຫຼື ຂໍ້ມູນບາງຢ່າງຢູ່ອຸປະກອນຂອງທ່ານໄດ້."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ບັນທຶກອຸປະກອນຈະບັນທຶກສິ່ງທີ່ເກີດຂຶ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ແອັບສາມາດໃຊ້ບັນທຶກເຫຼົ່ານີ້ເພື່ອຊອກຫາ ແລະ ແກ້ໄຂບັນຫາໄດ້.\n\nບັນທຶກບາງຢ່າງອາດມີຂໍ້ມູນລະອຽດອ່ອນ, ດັ່ງນັ້ນໃຫ້ອະນຸຍາດສະເພາະແອັບທີ່ທ່ານເຊື່ອຖືໃຫ້ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດເທົ່ານັ້ນ. \n\nຫາກທ່ານບໍ່ອະນຸຍາດແອັບນີ້ໃຫ້ເຂົ້າເຖິງບັນທຶກອຸປະກອນທັງໝົດ, ມັນຈະຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກຂອງຕົວມັນເອງໄດ້ຢູ່. ຜູ້ຜະລິດອຸປະກອນຂອງທ່ານອາດຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກ ຫຼື ຂໍ້ມູນບາງຢ່າງຢູ່ອຸປະກອນຂອງທ່ານໄດ້.\n\nສຶກສາເພີ່ມເຕີມໄດ້ຢູ່ g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ບໍ່ຕ້ອງສະແດງອີກ"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ຕ້ອງການສະແດງ <xliff:g id="APP_2">%2$s</xliff:g> ສະໄລ້"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ແກ້ໄຂ"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ການໂທ ແລະ ການແຈ້ງເຕືອນຈະສັ່ນ"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ບໍ່ສາມາດເຂົ້າເຖິງກ້ອງຖ່າຍຮູບຂອງໂທລະສັບຈາກ <xliff:g id="DEVICE">%1$s</xliff:g> ຂອງທ່ານໄດ້"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ບໍ່ສາມາດເຂົ້າເຖິງກ້ອງຖ່າຍຮູບຂອງແທັບເລັດຈາກ <xliff:g id="DEVICE">%1$s</xliff:g> ຂອງທ່ານໄດ້"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"ບໍ່ສາມາດເຂົ້າເຖິງເນື້ອຫານີ້ໄດ້ໃນຂະນະທີ່ຍັງສະຕຣີມຢູ່. ກະລຸນາລອງຢູ່ໂທລະສັບຂອງທ່ານແທນ."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"ບໍ່ສາມາດເບິ່ງການສະແດງຜົນຊ້ອນກັນໃນຂະນະທີ່ສະຕຣີມໄດ້"</string>
     <string name="system_locale_title" msgid="711882686834677268">"ຄ່າເລີ່ມຕົ້ນຂອງລະບົບ"</string>
     <string name="default_card_name" msgid="9198284935962911468">"ບັດ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index adf30e8..eb74970 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Balso paštas"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Ryšio problema arba neteisingas MMI kodas."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcija nepalaikoma."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Operacija ribojama tik naudojant fiksuoto rinkimo numerius."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Negalima pakeisti telefono skambučio peradresavimo nustatymų, kai naudojate tarptinklinį ryšį."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Paslauga įgalinta."</string>
@@ -74,6 +75,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Duomenys perjungti į „<xliff:g id="CARRIERDISPLAY">%s</xliff:g>“"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Galite tai bet kada pakeisti nustatymuose"</string>
     <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>
@@ -395,6 +398,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Leidžiama programai savo dalis įrašyti į atmintį. Dėl to gali būti apribota kitomis programomis pasiekiama atmintis ir sulėtėti telefono veikimas."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"vykdyti priekiniame plane veikiančią paslaugą"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Programai leidžiama naudoti priekiniame plane veikiančias paslaugas."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"paleisti priekinio plano paslaugą, kurios tipas „camera“"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „camera“"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"paleisti priekinio plano paslaugą, kurios tipas „connectedDevice“"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „connectedDevice“"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"paleisti priekinio plano paslaugą, kurios tipas „dataSync“"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „dataSync“"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"paleisti priekinio plano paslaugą, kurios tipas „location“"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „location“"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"paleisti priekinio plano paslaugą, kurios tipas „mediaPlayback“"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „mediaPlayback“"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"paleisti priekinio plano paslaugą, kurios tipas „mediaProjection“"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „mediaProjection“"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"paleisti priekinio plano paslaugą, kurios tipas „microphone“"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „microphone“"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"paleisti priekinio plano paslaugą, kurios tipas „phoneCall“"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „phoneCall“"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"paleisti priekinio plano paslaugą, kurios tipas „health“"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „health“"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"paleisti priekinio plano paslaugą, kurios tipas „remoteMessaging“"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „remoteMessaging“"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"paleisti priekinio plano paslaugą, kurios tipas „systemExempted“"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „systemExempted“"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"paleisti priekinio plano paslaugą, kurios tipas „specialUse“"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „specialUse“"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"matuoti programos atmintinės vietą"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Leidžiama programai nuskaityti kodą, duomenis ir talpykloje saugoti dydžius"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"keisti sistemos nustatymus"</string>
@@ -447,6 +474,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ši programa gali įrašyti garsą naudodama mikrofoną, kol programa naudojama."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"įrašyti garsą fone"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ši programa gali bet kada įrašyti garsą naudodama mikrofoną."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"Programos langų ekrano fiksavimo aptikimas"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Šiai programai bus pranešta, kai bus sukurta ekrano kopija, kol programa naudojama."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"siųsti komandas į SIM kortelę"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Programai leidžiama siųsti komandas į SIM kortelę. Tai labai pavojinga."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"atpažinti fizinę veiklą"</string>
@@ -698,6 +727,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Leidžiama programai nuskaityti vaizdo įrašo failus iš bendrinamos saugyklos."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"nuskaityti vaizdo failus iš bendrinamos saugyklos"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Leidžiama programai nuskaityti vaizdo failus iš bendrinamos saugyklos."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"nuskaityti naudotojo pasirinktus vaizdo ir vaizdo įrašo failus iš bendrinamos saugyklos"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Leidžiama programai nuskaityti jūsų pasirinktus vaizdo ir vaizdo įrašo failus iš bendrinamos saugyklos."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"keisti / trinti bendr. atm. t."</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Pr. leidž. raš. bendr. atm. t."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"skambinti / priimti SIP skambučius"</string>
@@ -2048,12 +2079,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"PAŠALINTI"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"VIS TIEK ATIDARYTI"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Aptikta žalinga programa"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Leisti „<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>“ pasiekti visus įrenginio žurnalus?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Leisti vienkartinę prieigą"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Neleisti"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Įrenginyje įrašoma, kas įvyksta jūsų įrenginyje. Programos gali naudoti šiuos žurnalus, kad surastų ir išspręstų problemas.\n\nKai kuriuose žurnaluose gali būti neskelbtinos informacijos, todėl visus įrenginio žurnalus leiskite pasiekti tik programoms, kuriomis pasitikite. \n\nJei neleisite šiai programai pasiekti visų įrenginio žurnalų, ji vis tiek galės pasiekti savo žurnalus. Įrenginio gamintojui vis tiek gali būti leidžiama pasiekti tam tikrus žurnalus ar informaciją jūsų įrenginyje."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Įrenginyje įrašoma, kas jame įvyksta. Programos gali naudoti šiuos žurnalus, kai reikia surasti ir išspręsti problemas.\n\nKai kuriuose žurnaluose gali būti neskelbtinos informacijos, todėl visus įrenginio žurnalus leiskite pasiekti tik programoms, kuriomis pasitikite. \n\nJei neleisite šiai programai pasiekti visų įrenginio žurnalų, ji vis tiek galės pasiekti savo žurnalus. Įrenginio gamintojui vis tiek gali būti leidžiama pasiekti tam tikrus žurnalus ar informaciją jūsų įrenginyje.\n\nSužinokite daugiau adresu g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Daugiau neberodyti"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"„<xliff:g id="APP_0">%1$s</xliff:g>“ nori rodyti „<xliff:g id="APP_2">%2$s</xliff:g>“ fragmentus"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Redaguoti"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Skambučiai ir pranešimai vibruos"</string>
@@ -2294,6 +2319,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nepavyko pasiekti telefono fotoaparato iš „<xliff:g id="DEVICE">%1$s</xliff:g>“"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nepavyko pasiekti planšetinio kompiuterio fotoaparato iš „<xliff:g id="DEVICE">%1$s</xliff:g>“"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Nepavyksta pasiekti perduodant srautu. Pabandykite naudoti telefoną."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Negalima peržiūrėti vaizdo vaizde perduodant srautu"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Numatytoji sistemos vertė"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KORTELĖ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 5631521..792ce6a 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Balss pasts"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Savienojuma problēma vai nederīgs MMI kods."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcija netiek atbalstīta."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Darbība ir atļauta tikai fiksēto numuru sastādīšanai."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nevar mainīt zvanu pāradresēšanas iestatījumus tālrunī, kamēr izmantojat viesabonēšanu."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Pakalpojums tika iespējots."</string>
@@ -73,6 +74,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Tiek izmantots operatora <xliff:g id="CARRIERDISPLAY">%s</xliff:g> datu savienojums"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Šo opciju jebkurā brīdī var mainīt iestatījumos"</string>
     <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>
@@ -394,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Ļauj lietotnei nodrošināt atsevišķu tās daļu nepārtrauktu atrašanos atmiņā. Tas var ierobežot pieejamo atmiņas daudzumu citām lietotnēm, tādējādi palēninot tālruņa darbību."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"Aktivizēt priekšplāna pakalpojumu"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Ļauj lietotnei izmantot priekšplāna pakalpojumus."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"izpildīt šāda veida priekšplāna pakalpojumu: camera"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: camera"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"izpildīt šāda veida priekšplāna pakalpojumu: connectedDevice"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: connectedDevice"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"izpildīt šāda veida priekšplāna pakalpojumu: dataSync"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: dataSync"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"izpildīt šāda veida priekšplāna pakalpojumu: location"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: location"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"izpildīt šāda veida priekšplāna pakalpojumu: mediaPlayback"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: mediaPlayback"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"izpildīt šāda veida priekšplāna pakalpojumu: mediaProjection"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: mediaProjection"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"izpildīt šāda veida priekšplāna pakalpojumu: microphone"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: microphone"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"izpildīt šāda veida priekšplāna pakalpojumu: phoneCall"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: phoneCall"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"izpildīt šāda veida priekšplāna pakalpojumu: health"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: health"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"izpildīt šāda veida priekšplāna pakalpojumu: remoteMessaging"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: remoteMessaging"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"izpildīt šāda veida priekšplāna pakalpojumu: systemExempted"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: systemExempted"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"izpildīt šāda veida priekšplāna pakalpojumu: specialUse"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: specialUse"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"noteikt vietas apjomu lietotnes atmiņā"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Ļauj lietotnei izgūt tās koda datus un kešatmiņas izmēru."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"mainīt sistēmas iestatījumus"</string>
@@ -446,6 +473,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Šī lietotne var ierakstīt audio, izmantojot mikrofonu, kamēr lietotne tiek izmantota."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ierakstīt audio fonā"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Šī lietotne var jebkurā brīdī ierakstīt audio, izmantojot mikrofonu."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"noteikt lietotnes logu ekrānuzņēmumu izveidi"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ja lietotnes izmantošanas laikā tiks izveidots ekrānuzņēmums, lietotnei tiks nosūtīts paziņojums."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"Sūtīt komandas SIM kartei"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Ļauj lietotnei sūtīt komandas uz SIM karti. Tas ir ļoti bīstami!"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"noteikt fiziskās aktivitātes"</string>
@@ -697,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Ļauj lietotnei lasīt video failus jūsu koplietotajā krātuvē."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"lasīt attēlu failus koplietotajā krātuvē"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Ļauj lietotnei lasīt attēlu failus jūsu koplietotajā krātuvē."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lasīt lietotāja atlasītos attēlu un video failus no kopīgās krātuves"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Ļauj lietotnei lasīt attēlu un video failus, ko atlasāt no savas kopīgās krātuves."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"Jūsu kopīgotās krātuves satura pārveidošana vai dzēšana"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Ļauj lietotnei rakstīt jūsu kopīgotās krātuves saturu."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP zvanu veikšana/saņemšana"</string>
@@ -2047,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ATINSTALĒT"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"TIK UN TĀ ATVĒRT"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Konstatēta kaitīga lietotne"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vai atļaujat lietotnei <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> piekļūt visiem ierīces žurnāliem?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Atļaut vienreizēju piekļuvi"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Neatļaut"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Ierīces žurnālos tiek reģistrēti ierīces procesi un notikumi. Lietotņu izstrādātāji var izmantot šos žurnālus, lai atrastu un izlabotu problēmas savās lietotnēs.\n\nDažos žurnālos var būt ietverta sensitīva informācija, tāpēc atļaujiet tikai uzticamām lietotnēm piekļūt visiem ierīces žurnāliem. \n\nJa neatļausiet šai lietotnei piekļūt visiem ierīces žurnāliem, lietotnes izstrādātājs joprojām varēs piekļūt pašas lietotnes žurnāliem. Iespējams, ierīces ražotājs joprojām varēs piekļūt noteiktiem žurnāliem vai informācijai jūsu ierīcē."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Ierīces žurnālos tiek reģistrēti ierīces procesi un notikumi. Lietotņu izstrādātāji var izmantot šos žurnālus, lai atrastu un izlabotu problēmas savās lietotnēs.\n\nDažos žurnālos var būt ietverta sensitīva informācija, tāpēc atļaujiet tikai uzticamām lietotnēm piekļūt visiem ierīces žurnāliem. \n\nJa neatļausiet šai lietotnei piekļūt visiem ierīces žurnāliem, lietotnes izstrādātājs joprojām varēs piekļūt pašas lietotnes žurnāliem. Iespējams, ierīces ražotājs joprojām varēs piekļūt noteiktiem žurnāliem vai informācijai jūsu ierīcē.\n\nŠeit varat uzzināt vairāk: g.co/android/devicelogs"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Vairs nerādīt"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Lietotne <xliff:g id="APP_0">%1$s</xliff:g> vēlas rādīt lietotnes <xliff:g id="APP_2">%2$s</xliff:g> sadaļas"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Rediģēt"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Zvaniem un paziņojumiem tiks aktivizēta vibrācija."</string>
@@ -2293,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nevar piekļūt tālruņa kamerai no jūsu ierīces (<xliff:g id="DEVICE">%1$s</xliff:g>)."</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nevar piekļūt planšetdatora kamerai no jūsu ierīces (<xliff:g id="DEVICE">%1$s</xliff:g>)."</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Straumēšanas laikā nevar piekļūt šim saturam. Mēģiniet tam piekļūt savā tālrunī."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Straumēšanas laikā nevar skatīt attēlu attēlā"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Sistēmas noklusējums"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KARTE <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index a45d0a7..e5710cd 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Говорна пошта"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Проблем со поврзување или неважечки MMI код."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функцијата не е поддржана."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Операцијата е ограничена на бирање само фиксни броеви."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Не може да се сменат поставките за проследување повик од телефонот додека сте во роаминг."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Услугата беше овозможена."</string>
@@ -72,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Стандардно, ID на повикувач не е скриен. Следен повик: не е скриен"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Услугата не е предвидена."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Не може да го промените поставувањето за ID на повикувач."</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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Овозможува апликацијата да прави трајни делови од себеси во меморијата. Ова може да ја ограничи расположливата меморија на други апликации што го забавува телефонот."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"извршување услуга во преден план"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Дозволува апликацијата да ги користи услугите во преден план."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"да извршува во преден план услуга со типот „camera“"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Дозволува апликацијата да ги користи во преден план услугите со типот „camera“"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"да извршува во преден план услуга со типот „connectedDevice“"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Дозволува апликацијата да ги користи во преден план услугите со типот „connectedDevice“"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"да извршува во преден план услуга со типот „dataSync“"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Дозволува апликацијата да ги користи во преден план услугите со типот „dataSync“"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"да извршува во преден план услуга со типот „location“"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Дозволува апликацијата да ги користи во преден план услугите со типот „location“"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"да извршува во преден план услуга со типот „mediaPlayback“"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Дозволува апликацијата да ги користи во преден план услугите со типот „mediaPlayback“"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"да извршува во преден план услуга со типот „mediaProjection“"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Дозволува апликацијата да ги користи во преден план услугите со типот „mediaProjection“"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"да извршува во преден план услуга со типот „microphone“"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Дозволува апликацијата да ги користи во преден план услугите со типот „microphone“"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"да извршува во преден план услуга со типот „phoneCall“"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Дозволува апликацијата да ги користи во преден план услугите со типот „phoneCall“"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"да извршува во преден план услуга со типот „health“"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Дозволува апликацијата да ги користи во преден план услугите со типот „health“"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"да извршува во преден план услуга со типот „remoteMessaging“"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Дозволува апликацијата да ги користи во преден план услугите со типот „remoteMessaging“"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"да извршува во преден план услуга со типот „systemExempted“"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Дозволува апликацијата да ги користи во преден план услугите со типот „systemExempted“"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"да извршува во преден план услуга со типот „specialUse“"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Дозволува апликацијата да ги користи во преден план услугите со типот „specialUse“"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"измери простор за складирање на апликацијата"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Дозволува апликацијата да ги обнови кодот, податоците и величините на кеш."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"менува системски поставки"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Апликацијава може да снима аудио со микрофонот додека се користи апликацијата."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"снима аудио во заднината"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Апликацијава може да снима аудио со микрофонот во секое време."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"откривај снимања на екранот од прозорци на апликацијата"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Апликацијава ќе биде известена кога ќе се направи слика од екранот додека се користи апликацијата."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"испраќање наредби до SIM-картичката"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Овозможува апликацијата да испраќа наредби до SIM картичката. Ова е многу опасно."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"препознавајте ја физичката активност"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Дозволува апликацијата да ги чита видеодатотеките од споделениот капацитет."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"да чита датотеки со слики од споделениот капацитет"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Дозволува апликацијата да ги чита датотеките со слики од споделениот капацитет."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"да чита датотеки со слики и видеа избрани од корисникот од споделениот простор."</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Дозволува апликацијата да чита датотеки со слики и видеа што ќе ги изберете од вашиот споделен простор."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"ги менува или брише содржините на заедничкото место за складирање"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Дозволува апликацијата да ги пишува содржините на заедничкото место за складирање."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"остварува/прима повици преку SIP"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ДЕИНСТАЛИРАЈ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"СЕПАК ОТВОРИ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Откриена е штетна апликација"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Да се дозволи <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> да пристапува до целата евиденција на уредот?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Дозволи еднократен пристап"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Не дозволувај"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Дневниците за евиденција на уредот снимаат што се случува на вашиот уред. Апликациите може да ги користат овие дневници за евиденција за да наоѓаат и поправаат проблеми.\n\nНекои дневници за евиденција може да содржат чувствителни податоци, па затоа дозволете им пристап до сите дневници за евиденција на уредот само на апликациите во кои имате доверба. \n\nАко не ѝ дозволите на апликацијава да пристапува до сите дневници за евиденција на уредот, таа сепак ќе може да пристапува до сопствените дневници за евиденција. Производителот на вашиот уред можеби сепак ќе може да пристапува до некои дневници за евиденција или податоци на уредот."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Дневниците за евиденција на уредот снимаат што се случува на вашиот уред. Апликациите може да ги користат овие дневници за евиденција за да наоѓаат и поправаат проблеми.\n\nНекои дневници за евиденција може да содржат чувствителни податоци, па затоа дозволете им пристап до сите дневници за евиденција на уредот само на апликациите во кои имате доверба. \n\nАко не ѝ дозволите на апликацијава да пристапува до сите дневници за евиденција на уредот, таа сепак ќе може да пристапува до сопствените дневници за евиденција. Производителот на вашиот уред можеби сепак ќе може да пристапува до некои дневници за евиденција или податоци на уредот.\n\nДознајте повеќе на g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Не прикажувај повторно"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> сака да прикажува делови од <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Измени"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Повиците и известувањата ќе вибрираат"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не може да се пристапи до камерата на вашиот телефон од <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Не може да се пристапи до камерата на вашиот таблет од <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"До ова не може да се пристапи при стриминг. Наместо тоа, пробајте на вашиот телефон."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Не може да се прикажува слика во слика при стримување"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Стандардно за системот"</string>
     <string name="default_card_name" msgid="9198284935962911468">"КАРТИЧКА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 492cd54..65b2c5d 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"വോയ്സ് മെയില്‍"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"കണക്ഷൻ പ്രശ്‌നം അല്ലെങ്കിൽ MMI കോഡ് അസാധുവാണ്."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ഫീച്ചർ പിന്തുണയ്‌ക്കുന്നില്ല."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"നിശ്ചയിച്ചുറപ്പിച്ച ഡയൽ ചെയ്യൽ നമ്പറുകൾക്ക് മാത്രമായി പ്രവർത്തനം പരിമിതപ്പെടുത്തിയിരിക്കുന്നു."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"റോമിംഗിൽ ആയിരിക്കുമ്പോൾ നിങ്ങളുടെ ഫോണിൽ നിന്ന് കോൾ കൈമാറ്റ ക്രമീകരണം സാധിക്കില്ല."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"സേവനം പ്രവർത്തനക്ഷമമാക്കി."</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"മെമ്മറിയിൽ അപ്ലിക്കേഷനുകളുടെ ഭാഗങ്ങൾ നിലനിർത്താൻ സ്വയം അനുവദിക്കുന്നു. ഇത് ഫോണിനെ മന്ദഗതിയിലാക്കുന്ന വിധത്തിൽ മറ്റ് അപ്ലിക്കേഷനുകൾക്ക് ലഭ്യമായ മെമ്മറി പരിമിതപ്പെടുത്താനിടയുണ്ട്."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"മുൻവശത്തുള്ള സേവനം റൺ ചെയ്യുക"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"മുൻവശത്തുള്ള സേവനങ്ങൾ ഉപയോഗിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"camera\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"\"camera\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"\"connectedDevice\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"\"dataSync\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"location\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"\"location\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"\"mediaPlayback\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"\"mediaProjection\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"microphone\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"\"microphone\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"\"phoneCall\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"health\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"\"health\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"\"remoteMessaging\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"അപ്ലിക്കേഷൻ സംഭരണയിടം അളക്കുക"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"അപ്ലിക്കേഷന്റെ കോഡ്, ഡാറ്റ, കാഷെ വലുപ്പങ്ങൾ എന്നിവ വീണ്ടെടുക്കുന്നതിന് അതിനെ അനുവദിക്കുക"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"സിസ്റ്റം ക്രമീകരണങ്ങൾ പരിഷ്‌ക്കരിക്കുക"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"ആപ്പ് ഉപയോഗത്തിലായിരിക്കുമ്പോൾ മൈക്രോഫോൺ ഉപയോഗിച്ച് ഓഡിയോ റെക്കോർഡ് ചെയ്യാൻ ഈ ആപ്പിന് കഴിയും."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"പശ്ചാത്തലത്തിൽ ഓഡിയോ റെക്കോർഡ് ചെയ്യുക"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ഈ ആപ്പിന് ഏത് സമയത്തും മൈക്രോഫോൺ ഉപയോഗിച്ച് ഓഡിയോ റെക്കോർഡ് ചെയ്യാൻ കഴിയും."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ആപ്പ് വിൻഡോകളുടെ സ്‌ക്രീൻ ക്യാപ്‌ചർ ചെയ്യലുകൾ കണ്ടെത്തുക"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"ആപ്പ് ഉപയോഗിച്ചുകൊണ്ടിരിക്കുമ്പോൾ സ്ക്രീൻഷോട്ട് എടുത്താൽ ആപ്പിന് അറിയിപ്പ് ലഭിക്കും."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIM-ലേക്ക് കമാൻഡുകൾ അയയ്ക്കുക"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"സിമ്മിലേക്ക് കമാൻഡുകൾ അയയ്‌ക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു. ഇത് വളരെ അപകടകരമാണ്."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"ശാരീരിക പ്രവർത്തനം തിരിച്ചറിയുക"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"നിങ്ങളുടെ പങ്കിട്ട സ്‌റ്റോറേജിൽ നിന്നുള്ള വീഡിയോ ഫയലുകൾ വായിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"പങ്കിട്ട സ്റ്റോറേജിൽ നിന്നുള്ള ചിത്ര ഫയലുകൾ വായിക്കുക"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"നിങ്ങളുടെ പങ്കിട്ട സ്‌റ്റോറേജിൽ നിന്നുള്ള ചിത്ര ഫയലുകൾ വായിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"പങ്കിട്ട സ്റ്റോറേജിൽ നിന്ന് ഉപയോക്താവ് തിരഞ്ഞെടുത്ത ചിത്രത്തിന്റെയും വീഡിയോയുടെയും ഫയലുകൾ വായിക്കുക"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"പങ്കിട്ട സ്‌റ്റോറേജിൽ നിന്ന് നിങ്ങൾ തിരഞ്ഞെടുത്ത ചിത്രത്തിന്റെയും വീഡിയോയുടെയും ഫയലുകൾ വായിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"നിങ്ങൾ പങ്കിടുന്ന സ്‌റ്റോറേജിലെ ഉള്ളടക്കങ്ങൾ പരിഷ്‌ക്കരിക്കുക അല്ലെങ്കിൽ ഇല്ലാതാക്കുക"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"നിങ്ങൾ പങ്കിടുന്ന സ്‌റ്റോറേജിലെ ഉള്ളടക്കങ്ങൾ എഴുതാൻ ആപ്പിനെ അനുവദിക്കുന്നു."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP കോളുകൾ വിളിക്കുക/സ്വീകരിക്കുക"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"അൺഇൻസ്‌റ്റാള്‍ ചെയ്യുക"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"എന്തായാലും തുറക്കുക"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"ദോഷകരമായ ആപ്പ് കണ്ടെത്തി"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"എല്ലാ ഉപകരണ ലോഗുകളും ആക്‌സസ് ചെയ്യാൻ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> എന്നതിനെ അനുവദിക്കണോ?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ഒറ്റത്തവണ ആക്‌സസ് അനുവദിക്കുക"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"അനുവദിക്കരുത്"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ഉപകരണ ലോഗുകൾ നിങ്ങളുടെ ഉപകരണത്തിൽ എന്തൊക്കെയാണ് സംഭവിക്കുന്നതെന്ന് റെക്കോർഡ് ചെയ്യുന്നു. പ്രശ്‌നങ്ങൾ കണ്ടെത്തി പരിഹരിക്കുന്നതിന് ആപ്പുകൾക്ക് ഈ ലോഗുകൾ ഉപയോഗിക്കാൻ കഴിയും.\n\nചില ലോഗുകളിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട വിവരങ്ങൾ അടങ്ങിയിരിക്കാൻ സാധ്യതയുള്ളതിനാൽ, എല്ലാ ഉപകരണ ലോഗുകളും ആക്സസ് ചെയ്യാനുള്ള അനുമതി നിങ്ങൾക്ക് വിശ്വാസമുള്ള ആപ്പുകൾക്ക് മാത്രം നൽകുക. \n\nഎല്ലാ ഉപകരണ ലോഗുകളും ആക്‌സസ് ചെയ്യാനുള്ള അനുവാദം നൽകിയില്ലെങ്കിലും, ഈ ആപ്പിന് അതിന്റെ സ്വന്തം ലോഗുകൾ ആക്‌സസ് ചെയ്യാനാകും. നിങ്ങളുടെ ഉപകരണ നിർമ്മാതാവിന് തുടർന്നും നിങ്ങളുടെ ഉപകരണത്തിലെ ചില ലോഗുകളോ വിവരങ്ങളോ ആക്‌സസ് ചെയ്യാനായേക്കും."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ഉപകരണ ലോഗുകൾ നിങ്ങളുടെ ഉപകരണത്തിൽ എന്തൊക്കെയാണ് സംഭവിക്കുന്നതെന്ന് റെക്കോർഡ് ചെയ്യുന്നു. പ്രശ്‌നങ്ങൾ കണ്ടെത്തി പരിഹരിക്കുന്നതിന് ആപ്പുകൾക്ക് ഈ ലോഗുകൾ ഉപയോഗിക്കാൻ കഴിയും.\n\nചില ലോഗുകളിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട വിവരങ്ങൾ അടങ്ങിയിരിക്കാൻ സാധ്യതയുള്ളതിനാൽ, എല്ലാ ഉപകരണ ലോഗുകളും ആക്സസ് ചെയ്യാനുള്ള അനുമതി നിങ്ങൾക്ക് വിശ്വാസമുള്ള ആപ്പുകൾക്ക് മാത്രം നൽകുക. \n\nഎല്ലാ ഉപകരണ ലോഗുകളും ആക്‌സസ് ചെയ്യാനുള്ള അനുവാദം നൽകിയില്ലെങ്കിലും, ഈ ആപ്പിന് അതിന്റെ സ്വന്തം ലോഗുകൾ ആക്‌സസ് ചെയ്യാനാകും. നിങ്ങളുടെ ഉപകരണ നിർമ്മാതാവിന് തുടർന്നും നിങ്ങളുടെ ഉപകരണത്തിലെ ചില ലോഗുകളോ വിവരങ്ങളോ ആക്‌സസ് ചെയ്യാനായേക്കും.\n\ng.co/android/devicelogs എന്നതിൽ കൂടുതലറിയുക."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"വീണ്ടും കാണിക്കരുത്"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_2">%2$s</xliff:g> സ്ലൈസുകൾ കാണിക്കാൻ <xliff:g id="APP_0">%1$s</xliff:g> താൽപ്പര്യപ്പെടുന്നു"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"എഡിറ്റ് ചെയ്യുക"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"കോളുകളും അറിയിപ്പുകളും വൈബ്രേറ്റ് ചെയ്യും"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"നിങ്ങളുടെ <xliff:g id="DEVICE">%1$s</xliff:g> എന്നതിൽ നിന്ന് ഫോണിന്റെ ക്യാമറ ആക്‌സസ് ചെയ്യാനാകില്ല"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"നിങ്ങളുടെ <xliff:g id="DEVICE">%1$s</xliff:g> എന്നതിൽ നിന്ന് ടാബ്‌ലെറ്റിന്റെ ക്യാമറ ആക്‌സസ് ചെയ്യാനാകില്ല"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"സ്ട്രീം ചെയ്യുമ്പോൾ ഇത് ആക്സസ് ചെയ്യാനാകില്ല. പകരം നിങ്ങളുടെ ഫോണിൽ ശ്രമിച്ച് നോക്കൂ."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"സ്ട്രീമിംഗിനിടെ ചിത്രത്തിനുള്ളിൽ ചിത്രം കാണാനാകില്ല"</string>
     <string name="system_locale_title" msgid="711882686834677268">"സിസ്‌റ്റം ഡിഫോൾട്ട്"</string>
     <string name="default_card_name" msgid="9198284935962911468">"കാർഡ് <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 2c8aaae..6d48c97 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"дуут шуудан"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Холболтын асуудал эсвэл буруу MMI код."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Онцлогийг дэмжээгүй."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Ажиллагаа зөвөх тогтсон дугаараар хязгаарлагдсан."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Таныг роуминг үйлчилгээг идэвхжүүлсэн үед таны утаснаас дуудлага дамжуулах тохиргоог өөрчлөх боломжгүй."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Үйлчилгээ идэвхжсэн."</string>
@@ -72,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Дуудлага хийгчийн ID хязгаарлагдсан. Дараагийн дуудлага: Хязгаарлагдсан"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Үйлчилгээ провишн хийгдээгүй ."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Та дуудлага хийгчийн ID тохиргоог солиж чадахгүй."</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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Апп нь өөрийн хэсгийг санах ойд байнга байлгах боломжтой. Энэ нь бусад апп-уудын ашиглах санах ойг хязгаарлан утсыг удаашруулах болно."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"интерактив (foreground) үйлчилгээг ажиллуулах"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Аппад интерактив (foreground) үйлчилгээг ашиглахыг зөвшөөрнө үү."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"Камер\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Аппад \"камер\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"ConnectedDevice\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Аппад \"connectedDevice\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"DataSync\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Аппад \"dataSync\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"Байршил\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Аппад \"байршил\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"MediaPlayback\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Аппад \"mediaPlayback\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"MediaProjection\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Аппад \"mediaProjection\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"Микрофон\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Аппад \"микрофон\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"PhoneCall\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Аппад \"phoneCall\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"Эрүүл мэнд\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Аппад \"эрүүл мэнд\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"RemoteMessaging\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Аппад \"remoteMessaging\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"SystemExempted\"-н төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Аппад \"systemExempted\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"SpecialUse\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Аппад \"specialUse\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"апп сангийн хэмжээг хэмжих"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Апп нь өөрийн код, дата болон кеш хэмжээг унших боломжтой"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"систем тохиргоог өөрчлөх"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Энэ аппыг ашиглаж байх үед энэ нь микрофон ашиглан аудио бичих боломжтой."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ард видео бичих"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Энэ апп ямар ч үед микрофон ашиглан аудио бичих боломжтой."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"аппын цонхны дэлгэцийн зургийг илрүүлэх"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Энэ аппыг ашиглаж байх үеэр дэлгэцийн агшин авбал мэдэгдэл хүлээн авах болно."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIM картад тушаал илгээх"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Апп-д SIM рүү комманд илгээхийг зөвшөөрнө. Энэ маш аюултай."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"биеийн дасгал хөдөлгөөн таних"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Аппад таны дундын хадгалах сангаас видео файлыг унших боломжийг олгодог."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"дундын хадгалах сангаас зургийн файл унших"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Аппад таны дундын хадгалах сангаас зургийн файлыг унших зөвшөөрөл олгодог."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"дундын хадгалах сангаас хэрэглэгчийн сонгосон зураг болон видео файлуудыг унших"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Аппад таны дундын хадгалах сангаас сонгосон зураг болон видео файлуудыг унших боломж олгодог."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"дундын хадгалах сангийнхаа контентыг өөрчлөх эсвэл устгах"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Аппад таны дундын хадгалах сангийн контентыг бичихийг зөвшөөрдөг."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP дуудлага хийх/хүлээн авах"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"УСТГАХ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ЯМАР Ч ТОХИОЛДОЛД НЭЭХ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Аюултай апп олдсон"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>-д төхөөрөмжийн бүх логт хандахыг зөвшөөрөх үү?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Нэг удаагийн хандалтыг зөвшөөрнө үү"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Бүү зөвшөөр"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Төхөөрөмжийн лог нь таны төхөөрөмж дээр юу болж байгааг бичдэг. Аппууд эдгээр логийг асуудлыг олох болон засахад ашиглах боломжтой.\n\nЗарим лог эмзэг мэдээлэл агуулж байж магадгүй тул та зөвхөн итгэдэг аппууддаа төхөөрөмжийн бүх логт хандахыг зөвшөөрнө үү. \n\nХэрэв та энэ аппад төхөөрөмжийн бүх логт хандахыг зөвшөөрөхгүй бол энэ нь өөрийн логт хандах боломжтой хэвээр байх болно. Tаны төхөөрөмж үйлдвэрлэгч таны төхөөрөмж дээрх зарим лог эсвэл мэдээлэлд хандах боломжтой хэвээр байж магадгүй."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Төхөөрөмжийн лог нь таны төхөөрөмж дээр юу болж байгааг бичдэг. Аппууд эдгээр логийг асуудлыг олох болон засахад ашиглах боломжтой.\n\nЗарим лог эмзэг мэдээлэл агуулж байж магадгүй тул та зөвхөн итгэдэг аппууддаа төхөөрөмжийн бүх логт хандахыг зөвшөөрнө үү. \n\nХэрэв та энэ аппад төхөөрөмжийн бүх логт хандахыг зөвшөөрөхгүй бол энэ нь өөрийн логт хандах боломжтой хэвээр байх болно. Таны төхөөрөмжийн үйлдвэрлэгч таны төхөөрөмж дээрх зарим лог эсвэл мэдээлэлд хандах боломжтой хэвээр байж магадгүй.\n\ng.co/android/devicelogs -с нэмэлт мэдээлэл аваарай."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Дахиж бүү харуул"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> <xliff:g id="APP_2">%2$s</xliff:g>-н хэсгүүдийг (slices) харуулах хүсэлтэй байна"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Засах"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Дуудлага болон мэдэгдэл чичирнэ"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Таны <xliff:g id="DEVICE">%1$s</xliff:g>-с утасны камерт хандах боломжгүй"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Таны <xliff:g id="DEVICE">%1$s</xliff:g>-с таблетын камерт хандах боломжгүй"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Стримингийн үед үүнд хандах боломжгүй. Оронд нь утас дээрээ туршиж үзнэ үү."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Дамжуулах явцад дэлгэц доторх дэлгэцийг үзэх боломжгүй"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Системийн өгөгдмөл"</string>
     <string name="default_card_name" msgid="9198284935962911468">"КАРТ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index d47fea35..b33fb3fb 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"व्हॉइसमेल"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"कनेक्शन समस्या किंवा अवैध MMI कोड."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"वैशिष्ट्याला सपोर्ट नाही."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"कार्य फक्त निश्चित डायलिंग नंबरसाठी प्रतिबंधित आहे."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"तुम्ही रोमिंगमध्ये असताना आपल्या फोनवरील कॉल फॉरवर्डिंग सेटिंंग्ज बदलू शकत नाही."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"सेवा सक्षम केली."</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"अ‍ॅप ला मेमरीमध्ये कायम असलेले त्याचे स्वतःचे भाग बनविण्यास अनुमती देते. हे फोन धीमा करून अन्य अ‍ॅप्सवर उपलब्ध असलेल्या मेमरीवर मर्यादा घालू शकते."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"पृष्‍ठभाग सेवा रन करा"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"अ‍ॅपला पृष्‍ठभाग सेवा वापरण्याची अनुमती देते."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"कॅमेरा\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"\"कॅमेरा\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"\"connectedDevice\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"\"dataSync\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"स्थान\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"\"स्थान\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"\"mediaPlayback\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"\"mediaProjection\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"मायक्रोफोन\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"\"मायक्रोफोन\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"\"phoneCall\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"आरोग्य\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"\"आरोग्य\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"\"remoteMessaging\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"अ‍ॅप संचयन स्थान मोजा"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"अ‍ॅप ला त्याचा कोड, डेटा आणि कॅशे    आकार पुनर्प्राप्त करण्यासाठी अनुमती देते"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"सिस्टीम सेटिंग्ज सुधारित करा"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"ॲप वापरात असताना, हे ॲप मायक्रोफोन वापरून ऑडिओ रेकॉर्ड करू शकते."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"बॅकग्राउंडमध्ये ऑडिओ रेकॉर्ड करा"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"हे ॲप मायक्रोफोन वापरून ऑडिओ कधीही रेकॉर्ड करू शकते."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"अ‍ॅप विंडोचे स्‍क्रीन कॅप्‍चर डिटेक्ट करा"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"ॲप वापरात असताना स्क्रीनशॉट घेतल्यावर या ॲपला सूचित केले जाईल."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"सिम वर कमांड पाठवा"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"अ‍ॅप ला सिम वर कमांड पाठविण्‍याची अनुमती देते. हे खूप धोकादायक असते."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"शारीरिक ॲक्टिव्हिटी ओळखा"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ॲपला तुमच्या शेअर केलेल्या स्टोरेजमधून व्हिडिओ फाइल वाचण्याची अनुमती देते."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"शेअर केलेल्या स्टोरेजमधून इमेज फाइल वाचा"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"ॲपला तुमच्या शेअर केलेल्या स्टोरेजमधून इमेज फाइल वाचण्याची अनुमती देते."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"शेअर केलेल्या स्टोरेजमधून वापरकर्त्याने निवडलेल्या इमेज आणि व्हिडिओ फाइल वाचा"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"तुमच्या शेअर केलेले स्टोरेजमधून तुम्ही निवडलेल्या इमेज आणि व्हिडिओ फाइल वाचण्याची अ‍ॅपना अनुमती देते."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"तुमच्या शेअर केलेल्या स्टोरेजच्या आशयांमध्ये सुधारणा करा किंवा हटवा"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"ॲपला तुमच्या शेअर केलेल्या स्टोरेजचे आशय लिहिण्याची अनमती देते."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP कॉल करा/मिळवा"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"अनइंस्टॉल करा"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"तरीही उघडा"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"हानिकारक अ‍ॅप आढळला"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ला सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती द्यायची आहे का?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"एक वेळ अ‍ॅक्सेसची अनुमती द्या"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"अनुमती देऊ नका"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"तुमच्या डिव्हाइसवर काय होते ते डिव्हाइस लॉग रेकॉर्ड करते. समस्या शोधण्यासाठी आणि त्यांचे निराकरण करण्याकरिता ॲप्स हे लॉग वापरू शकतात.\n\nकाही लॉगमध्ये संवेदनशील माहिती असू शकते, त्यामुळे फक्त तुमचा विश्वास असलेल्या ॲप्सना सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती द्या. \n\nतुम्ही या ॲपला सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती न दिल्यास, ते तरीही त्याचा स्वतःचा लॉग अ‍ॅक्सेस करू शकते. तुमच्या डिव्हाइसचा उत्पादक तरीही काही लॉग किंवा तुमच्या डिव्हाइसवरील माहिती अ‍ॅक्सेस करू शकतो."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"तुमच्या डिव्हाइसवर काय होते ते डिव्हाइस लॉग रेकॉर्ड करते. समस्या शोधण्यासाठी आणि त्यांचे निराकरण करण्याकरिता ॲप्स हे लॉग वापरू शकतात.\n\nकाही लॉगमध्ये संवेदनशील माहिती असू शकते, त्यामुळे फक्त तुमचा विश्वास असलेल्या ॲप्सना सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती द्या. \n\nतुम्ही या ॲपला सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती न दिल्यास, ते तरीही त्याचा स्वतःचा लॉग अ‍ॅक्सेस करू शकते. तुमच्या डिव्हाइसचा उत्पादक तरीही काही लॉग किंवा तुमच्या डिव्हाइसवरील माहिती अ‍ॅक्सेस करू शकतो.\n\ng.co/android/devicelogs येथे अधिक जाणून घ्या."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"पुन्हा दाखवू नका"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ला <xliff:g id="APP_2">%2$s</xliff:g> चे तुकडे दाखवायचे आहेत"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"संपादित करा"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"कॉल आणि सूचनांवर व्हायब्रेट होईल"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"तुमच्या <xliff:g id="DEVICE">%1$s</xliff:g> वरून फोनचा कॅमेरा अ‍ॅक्सेस करू शकत नाही"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"तुमच्या <xliff:g id="DEVICE">%1$s</xliff:g> वरून टॅबलेटचा कॅमेरा अ‍ॅक्सेस करू शकत नाही"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"स्ट्रीम करताना हे अ‍ॅक्सेस केले जाऊ शकत नाही. त्याऐवजी तुमच्या फोनवर अ‍ॅक्सेस करून पहा."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"स्ट्रीम होत असताना चित्रात-चित्र पाहू शकत नाही"</string>
     <string name="system_locale_title" msgid="711882686834677268">"सिस्टीम डीफॉल्ट"</string>
     <string name="default_card_name" msgid="9198284935962911468">"कार्ड <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index deb343d..c6065c6 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Mel suara"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Masalah sambungan atau kod MMI tidak sah"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Ciri tidak disokong."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Pengendalian dihadkan kepada nombor dailan tetap sahaja."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Tidak dapat mengubah tetapan pemajuan panggilan daripad telefon anda semasa dalam perayauan."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Perkhidmatan telah didayakan."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Data ditukar kepada <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Anda boleh menukar pilihan ini pada bila-bila masa dalam Tetapan"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Membenarkan apl untuk membuat sebahagian dari dirinya berterusan dalam memori. Ini boleh mengehadkan memori yang tersedia kepada apl lain dan menjadikan telefon perlahan."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"jalankan perkhidmatan latar depan"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Membenarkan apl menggunakan perkhidmatan latar depan."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"jalankan perkhidmatan latar depan dengan jenis \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"jalankan perkhidmatan latar depan dengan jenis \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"jalankan perkhidmatan latar depan dengan jenis \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"jalankan perkhidmatan latar depan dengan jenis \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"jalankan perkhidmatan latar depan dengan jenis \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"jalankan perkhidmatan latar depan dengan jenis \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"jalankan perkhidmatan latar depan dengan jenis \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"jalankan perkhidmatan latar depan dengan jenis \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"jalankan perkhidmatan latar depan dengan jenis \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"jalankan perkhidmatan latar depan dengan jenis \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"jalankan perkhidmatan latar depan dengan jenis \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"jalankan perkhidmatan latar depan dengan jenis \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ukur ruang storan apl"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Membenarkan apl mendapatkan semula kodnya, datanya dan saiz cachenya"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"ubah suai tetapan sistem"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Aplikasi ini boleh merakam audio menggunakan mikrofon semasa apl sedang digunakan."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"rakam audio di latar"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Apl ini boleh merakam audio menggunakan mikrofon pada bila-bila masa."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"mengesan tangkapan skrin tetingkap apl"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Apl ini akan dimaklumkan apabila tangkapan skrin diambil semasa apl sedang digunakan."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"hantar perintah ke SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Membenarkan apl menghantar arahan kepada SIM. Ini amat berbahaya."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"camkan aktiviti fizikal"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Membenarkan apl membaca fail video daripada storan kongsi anda."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"baca fail imej daripada storan kongsi"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Membenarkan apl membaca fail imej daripada storan kongsi anda."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"baca fail imej dan video yang dipilih oleh pengguna daripada storan kongsi"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Membenarkan apl membaca fail imej dan video yang anda pilih daripada storan kongsi anda."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"mengubah suai atau memadamkan kandungan storan kongsi anda"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Membenarkan apl menulis kandungan storan kongsi anda."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"buat/terima panggilan SIP"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"NYAHPASANG"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"BUKA JUGA"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Apl berbahaya dikesan"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Benarkan <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> mengakses semua log peranti?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Benarkan akses satu kali"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Jangan benarkan"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Log peranti merekodkan perkara yang berlaku pada peranti anda. Apl dapat menggunakan log ini untuk menemukan dan membetulkan isu.\n\nSesetengah log mungkin mengandungi maklumat sensitif, jadi benarkan apl yang anda percaya sahaja untuk mengakses semua log peranti. \n\nJika anda tidak membenarkan apl ini mengakses semua log peranti, apl masih boleh mengakses log sendiri. Pengilang peranti anda mungkin masih dapat mengakses sesetengah log atau maklumat pada peranti anda."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Log peranti merekodkan perkara yang berlaku pada peranti anda. Apl boleh menggunakan log ini untuk menemukan dan membetulkan masalah.\n\nSesetengah log mungkin mengandungi maklumat sensitif, jadi hanya benarkan apl yang anda percaya untuk mengakses semua log peranti. \n\nJika anda tidak membenarkan apl ini mengakses semua log peranti, apl ini masih boleh mengakses log sendiri. Pengilang peranti anda mungkin masih dapat mengakses sesetengah log atau maklumat pada peranti anda.\n\nKetahui lebih lanjut di g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Jangan tunjuk lagi"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> mahu menunjukkan <xliff:g id="APP_2">%2$s</xliff:g> hirisan"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Panggilan dan pemberitahuan akan bergetar"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Tidak dapat mengakses kamera telefon daripada <xliff:g id="DEVICE">%1$s</xliff:g> anda"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Tidak dapat mengakses kamera tablet daripada <xliff:g id="DEVICE">%1$s</xliff:g> anda"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Kandungan ini tidak boleh diakses semasa penstriman. Cuba pada telefon anda."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Tidak dapat melihat gambar dalam gambar semasa penstriman"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Lalai sistem"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KAD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 5f41672..0e92d8a 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"အသံမေးလ်"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"ဆက်သွယ်မှုဆိုင်ရာပြသနာ သို့မဟုတ် မမှန်ကန်သောMMIကုတ်"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ဝန်ဆောင်မှုကို မပံ့ပိုးပါ။"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"သတ်မှတ်ခေါ်ဆိုနိုင်သောနံပါတ်များထံသာ ကန့်သတ်ထားသည်"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ကွန်ရက်ပြင်ပဒေတာအသုံးပြုခြင်းကို ဖွင့်ထားသည့်အခါ သင့်ဖုန်းမှနေ၍ ခေါ်ဆိုမှုထပ်ဆင့်ပို့ခြင်းဆက်တင်အား ပြောင်း၍မရပါ။"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"ဝန်ဆောင်မှု လုပ်ဆောင်နိုင်မည်"</string>
@@ -72,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ပုံသေအားဖြင့် ခေါ်ဆိုသူအိုင်ဒီ(Caller ID)အား ကန့်သတ်မထားပါ။ နောက်ထပ်အဝင်ခေါ်ဆိုမှု-ကန့်သတ်မထားပါ။"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ဝန်ဆောင်မှုအား ကန့်သတ်မထားပါ"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"သင်သည် ခေါ်ဆိုသူ ID ဆက်တင်ကို မပြောင်းလဲနိုင်ပါ။"</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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"အပလီကေးရှင်းအား မှတ်ဉာဏ်ထဲတွင် ရေရှည်သိမ်းဆည်ထားရန် ခွင့်ပြုပါ။ ဒီခွင့်ပြုချက်ကြောင့် တခြားအပလီကေးရှင်းအများအတွက် မှတ်ဉာဏ်ရရှိမှု နည်းသွားနိုင်ပြီး ဖုန်းလည်း နှေးသွားနိုင်ပါသည်။"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"မျက်နှာစာ ဝန်ဆောင်မှုကို ဖွင့်ခြင်း"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"မျက်နှာစာဝန်ဆောင်မှုများကို အက်ပ်အား အသုံးပြုခွင့်ပေးသည်။"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"camera\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"\"camera\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"\"connectedDevice\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"\"dataSync\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"location\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"\"location\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"\"mediaPlayback\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"\"mediaProjection\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"microphone\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"\"microphone\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"\"phoneCall\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"health\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"\"health\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"\"remoteMessaging\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"အက်ပ်သိုလ​ှောင်မှု နေရာကို တိုင်းထွာခြင်း"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"အက်ပ်အား ၎င်း၏ ကုဒ်၊ ဒေတာ၊ နှင့် ကက်ရှ ဆိုက်များကို ရယူခွင့် ပြုသည်။"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"စနစ်အပြင်အဆင်အား မွမ်းမံခြင်း"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"ဤအက်ပ်ကို အသုံးပြုနေစဉ် ၎င်းက မိုက်ခရိုဖုန်းကို အသုံးပြု၍ အသံဖမ်းနိုင်သည်။"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"နောက်ခံတွင် အသံဖမ်းပါ"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ဤအက်ပ်သည် မိုက်ခရိုဖုန်းကို အသုံးပြု၍ အချိန်မရွေး အသံဖမ်းနိုင်သည်။"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"အက်ပ်ဝင်းဒိုး၏ ဖန်သားပြင်ပုံဖမ်းမှုကို သိရှိခြင်း"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"အက်ပ်သုံးနေစဉ် ဖန်သားပြင်ဓာတ်ပုံရိုက်သည့်အခါ ဤအက်ပ်က အကြောင်းကြားချက်ရရှိမည်။"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIM ထံသို့ ညွှန်ကြားချက်များကို ပို့ပါ"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"အက်ပ်အား ဆင်းမ်ကဒ်ဆီသို့ အမိန့်များ ပေးပို့ခွင့် ပြုခြင်း။ ဤခွင့်ပြုမှုမှာ အန္တရာယ်အလွန် ရှိပါသည်။"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"ကိုယ်ခန္ဓာလှုပ်ရှားမှုကို မှတ်သားပါ"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"သင်၏မျှဝေထားသော သိုလှောင်ခန်းမှ ဗီဒီယိုဖိုင်များဖတ်ရန် အက်ပ်ကိုခွင့်ပြုသည်။"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"မျှဝေထားသည့် သိုလှောင်ခန်းမှ ပုံပါဝင်သောဖိုင်များဖတ်ရန်"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"သင်၏မျှဝေထားသည့် သိုလှောင်ခန်းမှ ပုံပါဝင်သောဖိုင်များဖတ်ရန် အက်ပ်ကို ခွင့်ပြုသည်။"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"မျှသုံးသိုလှောင်ခန်းမှ အသုံးပြုသူ ရွေးချယ်ထားသော ပုံနှင့် ဗီဒီယိုဖိုင်များ ဖတ်ခြင်း"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"မျှသုံးသိုလှောင်ခန်းမှ သင်ရွေးချယ်သည့် ပုံနှင့် ဗီဒီယိုဖိုင်များအား အက်ပ်ကို ဖတ်ခွင့်ပြုသည်။"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"မျှဝေသိုလှောင်ခန်းမှ အရာများ ပြုပြင်/ဖျက်ခြင်း"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"မျှဝေသိုလှောင်ခန်းမှ အရာများ ရေးခွင့်ပြုသည်။"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP ခေါ်ဆိုမှုများ ခေါ်ရန်/လက်ခံရန်"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ဖြုတ်ရန်"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ဘာဖြစ်ဖြစ် ဖွင့်ရန်"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"အန္တရာယ်ရှိသော အက်ပ်ကို တွေ့ရှိထားသည်"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ကို စက်မှတ်တမ်းအားလုံး သုံးခွင့်ပြုမလား။"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"တစ်ခါသုံး ဝင်ခွင့်ပေးရန်"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ခွင့်မပြုပါ"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"သင့်စက်ရှိ အဖြစ်အပျက်များကို စက်မှတ်တမ်းများက မှတ်တမ်းတင်သည်။ အက်ပ်များက ပြဿနာများ ရှာဖွေပြီးဖြေရှင်းရန် ဤမှတ်တမ်းများကို သုံးနိုင်သည်။\n\nအချို့မှတ်တမ်းများတွင် သတိထားရမည့်အချက်အလက်များ ပါဝင်နိုင်သဖြင့် စက်မှတ်တမ်းအားလုံးကို ယုံကြည်ရသည့် အက်ပ်များကိုသာ သုံးခွင့်ပြုပါ။ \n\nဤအက်ပ်ကို စက်မှတ်တမ်းအားလုံး သုံးခွင့်မပြုသော်လည်း ၎င်းက ၎င်း၏ကိုယ်ပိုင်မှတ်တမ်းကို သုံးနိုင်ဆဲဖြစ်သည်။ သင့်စက်ရှိ အချို့မှတ်တမ်းများ (သို့) အချက်အလက်များကို သင့်စက်ထုတ်လုပ်သူက သုံးနိုင်ပါသေးသည်။"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"သင့်စက်ရှိ အဖြစ်အပျက်များကို စက်မှတ်တမ်းများက မှတ်တမ်းတင်သည်။ အက်ပ်များက ပြဿနာများ ရှာဖွေပြီးဖြေရှင်းရန် ဤမှတ်တမ်းများကို သုံးနိုင်သည်။\n\nအချို့မှတ်တမ်းများတွင် သတိထားရမည့်အချက်အလက်များ ပါဝင်နိုင်သဖြင့် ယုံကြည်ရသည့် အက်ပ်များကိုသာ စက်မှတ်တမ်းအားလုံး သုံးခွင့်ပြုပါ။ \n\nဤအက်ပ်ကို စက်မှတ်တမ်းအားလုံး သုံးခွင့်မပြုသော်လည်း ၎င်းက ကိုယ်ပိုင်မှတ်တမ်းများ သုံးနိုင်သေးသည်။ သင့်စက်ရှိ မှတ်တမ်း (သို့) အချက်အလက်အချို့ကို စက်ထုတ်လုပ်သူက သုံးနိုင်သေးသည်။\n\ng.co/android/devicelogs တွင် ပိုမိုလေ့လာပါ။"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"နောက်ထပ်မပြပါနှင့်"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> သည် <xliff:g id="APP_2">%2$s</xliff:g> ၏အချပ်များကို ပြသလိုသည်"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"တည်းဖြတ်ရန်"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ခေါ်ဆိုမှုများနှင့် အကြောင်းကြားချက်များ တုန်ခါပါမည်"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"သင်၏ <xliff:g id="DEVICE">%1$s</xliff:g> မှ ဖုန်းကင်မရာကို သုံး၍မရပါ"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"သင်၏ <xliff:g id="DEVICE">%1$s</xliff:g> မှ တက်ဘလက်ကင်မရာကို သုံး၍မရပါ"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"တိုက်ရိုက်လွှင့်နေစဉ် ၎င်းကို မသုံးနိုင်ပါ။ ၎င်းအစား ဖုန်းတွင် စမ်းကြည့်ပါ။"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"တိုက်ရိုက်လွှင့်စဉ် နှစ်ခုထပ်၍ မကြည့်နိုင်ပါ"</string>
     <string name="system_locale_title" msgid="711882686834677268">"စနစ်မူရင်း"</string>
     <string name="default_card_name" msgid="9198284935962911468">"ကတ် <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 0c9a983..8b796dd 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Telefonsvarer"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Tilkoblingsproblem eller ugyldig MMI-kode."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funksjonen støttes ikke."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Handlingen kan kun utføres på numre med anropsbegrensning."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Får ikke endret innstillinger for viderekobling fra telefonen din når du bruker roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Tjenesten ble aktivert."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Byttet data til <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Du kan endre dette når som helst i innstillingene"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Lar appen gjøre deler av seg selv vedvarende i minnet. Dette kan begrense minnet for andre apper og gjøre telefonen treg."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"kjøre tjenesten i forgrunnen"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Lar appen bruke tjenester i forgrunnen."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"kjøre forgrunnstjeneste med typen «camera»"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Lar appen bruke forgrunnstjenester med typen «camera»"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"kjøre forgrunnstjeneste med typen «connectedDevice»"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Lar appen bruke forgrunnstjenester med typen «connectedDevice»"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"kjøre forgrunnstjeneste med typen «dataSync»"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Lar appen bruke forgrunnstjenester med typen «dataSync»"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"kjøre forgrunnstjeneste med typen «location»"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Lar appen bruke forgrunnstjenester med typen «location»"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"kjøre forgrunnstjeneste med typen «mediaPlayback»"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Lar appen bruke forgrunnstjenester med typen «mediaPlayback»"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"kjøre forgrunnstjeneste med typen «mediaProjection»"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Lar appen bruke forgrunnstjenester med typen «mediaProjection»"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"kjøre forgrunnstjeneste med typen «microphone»"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Lar appen bruke forgrunnstjenester med typen «microphone»"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"kjøre forgrunnstjeneste med typen «phoneCall»"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Lar appen bruke forgrunnstjenester med typen «phoneCall»"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"kjøre forgrunnstjeneste med typen «health»"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Lar appen bruke forgrunnstjenester med typen «health»"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"kjøre forgrunnstjeneste med typen «remoteMessaging»"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Lar appen bruke forgrunnstjenester med typen «remoteMessaging»"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"kjøre forgrunnstjeneste med typen «systemExempted»"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Lar appen bruke forgrunnstjenester med typen «systemExempted»"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"kjøre forgrunnstjeneste med typen «specialUse»"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Lar appen bruke forgrunnstjenester med typen «specialUse»"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"måle lagringsplass for apper"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Lar appen hente ut koden, dataene og bufferstørrelsene til appen"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"endre systeminnstillingene"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Denne appen kan ta opp lyd med mikrofonen mens den er i bruk."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ta opp lyd i bakgrunnen"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Denne appen kan når som helst ta opp lyd med mikrofonen."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"registrere skjermdumper av appvinduer"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Denne appen varsles hvis det tas skjermdumper mens appen er i bruk."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"sende kommandoer til SIM-kortet"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Lar appen sende kommandoer til SIM-kortet. Dette er veldig farlig."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"gjenkjenn fysisk aktivitet"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Lar appen lese videofiler fra den delte lagringsplassen din."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"lese bildefiler fra delt lagringsplass"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Lar appen lese bildefiler fra den delte lagringsplassen din."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"lese brukervalgte bilde- og videofiler fra den delte lagringen"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"lar appen lese bilde- og videofiler du velger fra den delte lagringen din"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"endre eller slette innholdet i den delte lagringen din"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Lar appen skrive innholdet i den delte lagringen din."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"foreta/motta SIP-anrop"</string>
@@ -1152,7 +1183,7 @@
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Bytt inndatametode"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Lite ledig lagringsplass"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Enkelte systemfunksjoner fungerer muligens ikke slik de skal"</string>
-    <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Det er ikke nok lagringsplass for systemet. Kontrollér at du har 250 MB ledig plass, og start på nytt."</string>
+    <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Det er ikke nok lagringsplass for systemet. Kontroller at du har 250 MB ledig plass, og start på nytt."</string>
     <string name="app_running_notification_title" msgid="8985999749231486569">"<xliff:g id="APP_NAME">%1$s</xliff:g> kjører"</string>
     <string name="app_running_notification_text" msgid="5120815883400228566">"Trykk for å få mer informasjon eller for å stoppe appen."</string>
     <string name="ok" msgid="2646370155170753815">"OK"</string>
@@ -1685,7 +1716,7 @@
     <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"AV"</string>
     <string name="accessibility_enable_service_title" msgid="3931558336268541484">"Vil du gi <xliff:g id="SERVICE">%1$s</xliff:g> full kontroll over enheten din?"</string>
     <string name="accessibility_service_warning_description" msgid="291674995220940133">"Full kontroll er passende for apper som hjelper deg med tilgjengelighetsbehov, men ikke for de fleste apper."</string>
-    <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Se og kontrollér skjermen"</string>
+    <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Se og kontroller skjermen"</string>
     <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Den kan lese alt innhold på skjermen og vise innhold over andre apper."</string>
     <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Se og utfør handlinger"</string>
     <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Den kan spore kommunikasjonen din med en app eller maskinvaresensor og kommunisere med apper på dine vegne."</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"AVINSTALLER"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ÅPNE LIKEVEL"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"En skadelig app ble oppdaget"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vil du gi <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> tilgang til alle enhetslogger?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Gi éngangstilgang"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ikke tillat"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Enhetslogger registrerer det som skjer på enheten din. Apper kan bruke disse loggene til å finne og løse problemer.\n\nNoen logger kan inneholde sensitiv informasjon, så du bør bare gi tilgang til alle enhetslogger til apper du stoler på. \n\nHvis du ikke gir denne appen tilgang til alle enhetslogger, har den fortsatt tilgang til sine egne logger. Enhetsprodusenten kan fortsatt ha tilgang til visse logger eller noe informasjon på enheten din."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Enhetslogger registrerer det som skjer på enheten. Apper kan bruke disse loggene til å finne og løse problemer.\n\nNoen logger kan inneholde sensitiv informasjon, så du bør bare gi tilgang til alle enhetslogger til apper du stoler på. \n\nHvis du ikke gir denne appen tilgang til alle enhetslogger, har den fortsatt tilgang til sine egne logger. Enhetsprodusenten kan fortsatt ha tilgang til visse logger eller noe informasjon på enheten.\n\nFinn ut mer på g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ikke vis igjen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vil vise <xliff:g id="APP_2">%2$s</xliff:g>-utsnitt"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Endre"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Anrop og varsler vibrerer"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Det er ikke mulig å få tilgang til telefonkameraet fra <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Det er ikke mulig å få tilgang til kameraet på nettbrettet fra <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Dette er ikke tilgjengelig under strømming. Prøv på telefonen i stedet."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Kan ikke se bilde-i-bilde under strømming"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Systemstandard"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KORT <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index e95d48b..54875ac 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"भ्वाइस मेल"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN१"</string>
     <string name="mmiError" msgid="2862759606579822246">"जडान समस्या वा अमान्य MMI कोड।"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"यो सुविधा प्रयोग गर्न मिल्दैन।"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"अपरेशन निश्चित डायल नम्बरहरूको लागि मात्र प्रतिबन्धित छ।"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"तपाईं रोमिङमा हुनुहुँदा तपाईंको फोनबाट कल फर्वार्ड गर्ने सम्बन्धी सेटिङहरू परिवर्तन गर्न सकिँदैन।"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"सेवा सक्षम पारियो।"</string>
@@ -72,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कलर ID पूर्वनिर्धारितको लागि रोकावट छैन। अर्को कल: रोकावट छैन"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवाको व्यवस्था छैन।"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"तपाईं कलर ID सेटिङ परिवर्तन गर्न सक्नुहुन्न।"</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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"एपलाई मेमोरीमा आफैंको निरन्तरको अंश बनाउन अनुमति दिन्छ। यसले फोनलाई ढिला बनाएर अन्य एपहरूमा मेमोरी SIMित गर्न सक्दछन्।"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"अग्रभूमिको सेवा सञ्चालन गर्नुहोस्"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"एपलाई अग्रभूमिका सेवाहरू प्रयोग गर्ने अनुमति दिन्छ।"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"camera\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"यसले एपलाई \"camera\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"यसले एपलाई \"connectedDevice\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"यसले एपलाई \"dataSync\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"location\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"यसले एपलाई \"location\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"यसले एपलाई \"mediaPlayback\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"यसले एपलाई \"mediaProjection\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"microphone\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"यसले एपलाई \"microphone\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"यसले एपलाई \"phoneCall\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"health\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"यसले एपलाई \"health\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"यसले एपलाई \"remoteMessaging\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"यसले एपलाई \"systemExempted\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"यसले एपलाई \"specialUse\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"एप भण्डारण ठाउँको मापन गर्नुहोस्"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"एपलाई यसको कोड, डेटा, र क्यास आकारहरू पुनःप्राप्त गर्न अनुमति दिन्छ।"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"प्रणाली सेटिङहरू परिमार्जन गर्नुहोस्"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"यो एप प्रयोग भइरहेका बेला यसले माइक्रोफोन प्रयोग गरेर अडियो रेकर्ड गर्न सक्छ।"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ब्याकग्राउन्डमा अडियो रेकर्ड गर्नुहोस्"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"यो एपले जुनसुकै बेला माइक्रोफोन प्रयोग गरी अडियो रेकर्ड गर्न सक्छ।"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"एपको विन्डोको स्क्रिन क्याप्चर गरेको कुरा पत्ता लगाउनुहोस्"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"एप प्रयोग भइरहेको बेला स्क्रिनसट लिइयो भने यो एपलाई सूचना दिइने छ।"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIM मा आदेशहरू पठाउन दिनुहोस्"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM लाई आदेश पठाउन एपलाई अनुमति दिन्छ। यो निकै खतरनाक हुन्छ।"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"शारीरिक गतिविधि पहिचान गर्नुहोस्‌"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"एपलाई तपाईंको साझा भण्डारणमा भएका भिडियो फाइलहरू पढ्ने अनुमति दिन्छ।"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"साझा भण्डारणमा भएका फोटो फाइलहरू पढ्ने"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"एपलाई तपाईंको साझा भण्डारणमा भएका फोटो फाइलहरू पढ्ने अनुमति दिन्छ।"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"प्रयोगकर्ताले साझा भण्डारणबाट चयन गरेका फोटो र भिडियो फाइलहरू पढ्ने"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"एपलाई तपाईंले आफ्नो साझा भण्डारणबाट चयन गर्नुभएका फोटो र भिडियो फाइलहरू पढ्ने अनुमति दिन्छ।"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"तपाईंको आदान प्रदान गरिएको भण्डारणको विषयवस्तुहरूलाई परिमार्जन गर्नहोस् वा मेटाउनुहोस्"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"एपलाई तपाईंको आदान प्रदान गरिएको भण्डारणको सामग्री लेख्न अनुमति दिन्छ।"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP कलहरू प्राप्त/बनाउन"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"स्थापना रद्द गर्नु…"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"जे भए पनि खोल्नुहोस्"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"हानिकारक एप भेटियो"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> लाई डिभाइसका सबै लग हेर्ने अनुमति दिने हो?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"एक पटक प्रयोग गर्ने अनुमति दिनुहोस्"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"अनुमति नदिनुहोस्"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"डिभाइसका लगले तपाईंको डिभाइसमा भएका विभिन्न गतिविधिको अभिलेख राख्छ। एपहरू यी लगका आधारमा समस्या पत्ता लगाउन र तिनको समाधान गर्न सक्छन्।\n\nकेही लगहरूमा संवेदनशील जानकारी समावेश हुन सक्ने भएकाले आफूले भरोसा गर्ने एपलाई मात्र डिभाइसका सबै लग हेर्ने अनुमति दिनुहोस्। \n\nतपाईंले यो एपलाई डिभाइसका सबै लग हेर्ने अनुमति दिनुभएन भने पनि यसले आफ्नै लग भने हेर्न सक्छ। तपाईंको डिभाइसको उत्पादकले पनि तपाईंको डिभाइसमा भएका केही लग वा जानकारी हेर्न सक्ने सम्भावना हुन्छ।"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"डिभाइसका लगले तपाईंको डिभाइसमा भएका विभिन्न गतिविधिको अभिलेख राख्छ। एपहरू यी लगका आधारमा समस्या पत्ता लगाउन र तिनको समाधान गर्न सक्छन्।\n\nकेही लगहरूमा संवेदनशील जानकारी समावेश हुन सक्ने भएकाले आफूले भरोसा गर्ने एपलाई मात्र डिभाइसका सबै लग हेर्ने अनुमति दिनुहोस्। \n\nतपाईंले यो एपलाई डिभाइसका सबै लग हेर्ने अनुमति दिनुभएन भने पनि यसले आफ्नै लग भने हेर्न सक्छ। तपाईंको डिभाइसको उत्पादकले पनि तपाईंको डिभाइसमा भएका केही लग वा जानकारी हेर्न सक्ने सम्भावना हुन्छ।\n\nतपाईं यस सम्बन्धमा थप जान्न चाहनुहुन्छ भने g.co/android/devicelogs मा जानुहोस्।"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"फेरि नदेखाइयोस्"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ले <xliff:g id="APP_2">%2$s</xliff:g> का स्लाइसहरू देखाउन चाहन्छ"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"सम्पादन गर्नुहोस्"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"कल तथा सूचनाहरू आउँदा कम्पन हुने छ"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"तपाईंको <xliff:g id="DEVICE">%1$s</xliff:g> मार्फत फोनको क्यामेरा प्रयोग गर्न मिल्दैन"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"तपाईंको <xliff:g id="DEVICE">%1$s</xliff:g> मार्फत ट्याब्लेटको क्यामेरा प्रयोग गर्न मिल्दैन"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"स्ट्रिम गरिरहेका बेला यो सामग्री हेर्न तथा प्रयोग गर्न मिल्दैन। बरु आफ्नो फोनमार्फत सो सामग्री हेर्ने तथा प्रयोग गर्ने प्रयास गर्नुहोस्।"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"स्ट्रिम गरिरहेका बेला picture-in-picture मोड प्रयोग गर्न मिल्दैन"</string>
     <string name="system_locale_title" msgid="711882686834677268">"सिस्टम डिफल्ट"</string>
     <string name="default_card_name" msgid="9198284935962911468">"कार्ड <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 5723510..e40a087 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Voicemail"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Verbindingsprobleem of ongeldige MMI-code."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Functie niet ondersteund."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Bewerking is beperkt tot vaste nummers."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Kan instellingen voor doorschakelen van gesprekken niet wijzigen vanaf je telefoon tijdens roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Service staat aan."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Mobiele data overgeschakeld naar <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Je kunt dit altijd wijzigen via Instellingen"</string>
     <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>
@@ -255,7 +258,7 @@
     <string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Stille modus"</string>
     <string name="global_action_silent_mode_on_status" msgid="2371892537738632013">"Geluid is UIT"</string>
     <string name="global_action_silent_mode_off_status" msgid="6608006545950920042">"Geluid is AAN"</string>
-    <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"Vliegtuigmodus"</string>
+    <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"Vliegtuig­modus"</string>
     <string name="global_actions_airplane_mode_on_status" msgid="5508025516695361936">"Vliegtuigmodus is AAN"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="8522219771500505475">"Vliegtuigmodus is UIT"</string>
     <string name="global_action_settings" msgid="4671878836947494217">"Instellingen"</string>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Hiermee kan de app gedeelten van zichzelf persistent maken in het geheugen. Dit kan de hoeveelheid geheugen beperken die beschikbaar is voor andere apps, waardoor de telefoon trager kan worden."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"service op de voorgrond uitvoeren"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Hiermee kan de app gebruikmaken van services op de voorgrond."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"service op de voorgrond van het type \'camera\' uitvoeren"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'camera\'"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"service op de voorgrond van het type \'connectedDevice\' uitvoeren"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'connectedDevice\'"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"service op de voorgrond van het type \'dataSync\' uitvoeren"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'dataSync\'"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"service op de voorgrond van het type \'location\' uitvoeren"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'location\'"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"service op de voorgrond van het type \'mediaPlayback\' uitvoeren"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'mediaPlayback\'"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"service op de voorgrond van het type \'mediaProjection\' uitvoeren"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'mediaProjection\'"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"service op de voorgrond van het type \'microphone\' uitvoeren"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'microphone\'"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"service op de voorgrond van het type \'phoneCall\' uitvoeren"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'phoneCall\'"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"service op de voorgrond van het type \'health\' uitvoeren"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'health\'"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"service op de voorgrond van het type \'remoteMessaging\' uitvoeren"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'remoteMessaging\'"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"service op de voorgrond van het type \'systemExempted\' uitvoeren"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'systemExempted\'"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"service op de voorgrond van het type \'specialUse\' uitvoeren"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'specialUse\'"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"opslagruimte van app meten"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Hiermee kan de app de bijbehorende code, gegevens en cachegrootten ophalen."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"systeeminstellingen aanpassen"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Deze app kan audio opnemen met de microfoon als de app wordt gebruikt."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"audio opnemen op de achtergrond"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Deze app kan altijd audio opnemen met de microfoon."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"schermopnamen van de app vastleggen"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Deze app krijgt een melding als een screenshot wordt gemaakt terwijl de app in gebruik is."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"opdrachten verzenden naar de simkaart"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Hiermee kan de app opdrachten verzenden naar de simkaart. Dit is erg gevaarlijk."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"fysieke activiteit herkennen"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Hiermee kan de app videobestanden in je gedeelde opslag lezen."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"afbeeldingsbestanden in gedeelde opslag lezen"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Hiermee kan de app afbeeldingsbestanden in je gedeelde opslag lezen."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"door de gebruiker geselecteerde afbeeldings- en videobestanden in de gedeelde opslag lezen"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Hiermee kan de app afbeeldings- en videobestanden lezen die je selecteert in je gedeelde opslag."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"de content van je gedeelde opslag aanpassen of verwijderen"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Hiermee kan de app de content van je gedeelde opslag schrijven."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"Bellen of gebeld worden via SIP"</string>
@@ -986,7 +1017,7 @@
     <string name="keyguard_accessibility_user_selector" msgid="1466067610235696600">"Gebruikersselectie"</string>
     <string name="keyguard_accessibility_status" msgid="6792745049712397237">"Status"</string>
     <string name="keyguard_accessibility_camera" msgid="7862557559464986528">"Camera"</string>
-    <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Mediabediening"</string>
+    <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Media­bediening"</string>
     <string name="keyguard_accessibility_widget_reorder_start" msgid="7066213328912939191">"Opnieuw indelen van widget gestart."</string>
     <string name="keyguard_accessibility_widget_reorder_end" msgid="1083806817600593490">"Opnieuw indelen van widget beëindigd."</string>
     <string name="keyguard_accessibility_widget_deleted" msgid="1509738950119878705">"Widget <xliff:g id="WIDGET_INDEX">%1$s</xliff:g> verwijderd."</string>
@@ -1479,7 +1510,7 @@
     <string name="forward_intent_to_work" msgid="3620262405636021151">"U gebruikt deze app in je werkprofiel"</string>
     <string name="input_method_binding_label" msgid="1166731601721983656">"Invoermethode"</string>
     <string name="sync_binding_label" msgid="469249309424662147">"Synchroniseren"</string>
-    <string name="accessibility_binding_label" msgid="1974602776545801715">"Toegankelijkheid"</string>
+    <string name="accessibility_binding_label" msgid="1974602776545801715">"Toe­gankelijk­heid"</string>
     <string name="wallpaper_binding_label" msgid="1197440498000786738">"Achtergrond"</string>
     <string name="chooser_wallpaper" msgid="3082405680079923708">"Achtergrond wijzigen"</string>
     <string name="notification_listener_binding_label" msgid="2702165274471499713">"Listener voor meldingen"</string>
@@ -1985,7 +2016,7 @@
     <string name="app_category_news" msgid="1172762719574964544">"Nieuws en tijdschriften"</string>
     <string name="app_category_maps" msgid="6395725487922533156">"Maps en navigatie"</string>
     <string name="app_category_productivity" msgid="1844422703029557883">"Productiviteit"</string>
-    <string name="app_category_accessibility" msgid="6643521607848547683">"Toegankelijkheid"</string>
+    <string name="app_category_accessibility" msgid="6643521607848547683">"Toe­gankelijk­heid"</string>
     <string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"Apparaatopslag"</string>
     <string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"USB-foutopsporing"</string>
     <string name="time_picker_hour_label" msgid="4208590187662336864">"uur"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"VERWIJDEREN"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"TOCH OPENEN"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Schadelijke app gevonden"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> toegang geven tot alle apparaatlogboeken?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Eenmalige toegang toestaan"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Niet toestaan"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Apparaatlogboeken leggen vast wat er op je apparaat gebeurt. Apps kunnen deze logboeken gebruiken om problemen op te sporen en te verhelpen.\n\nSommige logboeken kunnen gevoelige informatie bevatten, dus geef alleen apps die je vertrouwt toegang tot alle apparaatlogboeken. \n\nAls je deze app geen toegang tot alle apparaatlogboeken geeft, heeft de app nog wel toegang tot de eigen logboeken. De fabrikant van je apparaat heeft misschien nog steeds toegang tot bepaalde logboeken of informatie op je apparaat."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Apparaatlogboeken leggen vast wat er op je apparaat gebeurt. Apps kunnen deze logboeken gebruiken om problemen op te sporen en te verhelpen.\n\nSommige logboeken kunnen gevoelige informatie bevatten, dus geef alleen apps die je vertrouwt toegang tot alle apparaatlogboeken. \n\nAls je deze app geen toegang tot alle apparaatlogboeken geeft, heeft de app nog wel toegang tot de eigen logboeken. De fabrikant van je apparaat heeft misschien nog steeds toegang tot bepaalde logboeken of informatie op je apparaat.\n\nGa voor meer informatie naar g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Niet opnieuw tonen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wil segmenten van <xliff:g id="APP_2">%2$s</xliff:g> tonen"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Bewerken"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Trillen bij gesprekken en meldingen"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Kan geen toegang tot de camera van de telefoon krijgen vanaf je <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Kan geen toegang tot de camera van de tablet krijgen vanaf je <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Je hebt hier geen toegang toe tijdens streaming. Probeer het in plaats daarvan op je telefoon."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Kan scherm-in-scherm niet bekijken tijdens het streamen"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Systeemstandaard"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KAART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index c4150dc..412409a 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ଭଏସ୍‌ ମେଲ୍"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"ସଂଯୋଗରେ ସମସ୍ୟା ଅଛି କିମ୍ବା ଅମାନ୍ୟ MMI କୋଡ୍।"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ଫିଚର ସମର୍ଥିତ ନୁହେଁ।"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"କେବଳ ସ୍ଥାୟୀ ଡାୟଲିଙ୍ଗ ନମ୍ବର୍‌ ପାଇଁ କାର୍ଯ୍ୟ ସୀମିତ ଅଟେ।"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ଆପଣ ରୋମିଙ୍ଗରେ ଥିବାବେଳେ କଲ୍‍ ଫର୍‌ୱର୍ଡିଙ୍ଗ ସେଟିଙ୍ଗ ବଦଳାଇପାରିବେ ନାହିଁ।"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"ସେବା ସକ୍ଷମ କରାଯାଇଥିଲା।"</string>
@@ -72,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"କଲର୍ ଆଇଡି ଡିଫଲ୍ଟ ଭାବରେ ପ୍ରତିବନ୍ଧିତ ନୁହେଁ। ପରବର୍ତ୍ତୀ କଲ୍: ପ୍ରତିବନ୍ଧିତ ନୁହେଁ"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ସେବାର ସୁବିଧା ନାହିଁ।"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"ଆପଣ କଲର୍‍ ID ସେଟିଙ୍ଗ ବଦଳାଇପାରିବେ ନାହିଁ।"</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>
@@ -85,7 +88,7 @@
     <string name="notification_channel_network_alert" msgid="4788053066033851841">"ଆଲର୍ଟ"</string>
     <string name="notification_channel_call_forward" msgid="8230490317314272406">"କଲ୍‌ ଫରୱାର୍ଡିଂ"</string>
     <string name="notification_channel_emergency_callback" msgid="54074839059123159">"ଜରୁରୀକାଳୀନ କଲବ୍ୟାକ୍‍ ମୋଡ୍‍"</string>
-    <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"ମୋବାଇଲ୍‍ ଡାଟା ଷ୍ଟାଟସ୍‌"</string>
+    <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"ମୋବାଇଲ ଡାଟା ଷ୍ଟାଟସ"</string>
     <string name="notification_channel_sms" msgid="1243384981025535724">"SMS ମେସେଜ୍‌"</string>
     <string name="notification_channel_voice_mail" msgid="8457433203106654172">"ଭଏସମେଲ୍‍ ମେସେଜ୍‍"</string>
     <string name="notification_channel_wfc" msgid="9048240466765169038">"ୱାଇ-ଫାଇ କଲିଙ୍ଗ"</string>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ଆପ୍‍ଟି ନିଜକୁ ମେମୋରୀରେ ଭାଗ କରିବାକୁ ଦେଇଥାଏ। ଏହାଦ୍ୱାରା ଅନ୍ୟ ଆପ୍‍ଗୁଡ଼ିକ ପାଇଁ ମେମୋରୀ ଉପଲବ୍ଧକୁ କମ୍‌ କରିବା ସହ ଫୋନ୍‍ଟିକୁ ମନ୍ଥର କରିବ।"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"ଫୋର୍‌ଗ୍ରାଉଣ୍ଡ ସେବାକୁ ଚଲାନ୍ତୁ"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"ଫୋର୍‌ଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପ୍‌କୁ ଅନୁମତି ଦିଅନ୍ତୁ।"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"camera\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"\"camera\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"\"connectedDevice\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"\"dataSync\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"location\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"\"location\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"\"mediaPlayback\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"\"mediaProjection\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"microphone\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"\"microphone\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"\"phoneCall\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"health\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"\"health\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"\"remoteMessaging\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ଆପ୍‍ ଷ୍ଟୋରେଜ୍‍ ସ୍ଥାନର ମାପ କରନ୍ତୁ"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"ଆପ୍‍ର କୋଡ୍‍, ଡାଟା ଓ କ୍ୟାଶ୍‌ ଆକାର ହାସଲ କରିବା ପାଇଁ ଏହାକୁ ଅନୁମତି ଦେଇଥାଏ।"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"ସିଷ୍ଟମ୍‍ ସେଟିଂସ ବଦଳାନ୍ତୁ"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"ଆପକୁ ବ୍ୟବହାର କରାଯାଉଥିବା ସମୟରେ ଏହା ମାଇକ୍ରୋଫୋନକୁ ବ୍ୟବହାର କରି ଅଡିଓ ରେକର୍ଡ କରିପାରିବ।"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ପୃଷ୍ଠପଟରେ ଅଡିଓ ରେକର୍ଡ କରନ୍ତୁ"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ଏହି ଆପ୍ ଯେ କୌଣସି ସମୟରେ ମାଇକ୍ରୋଫୋନକୁ ବ୍ୟବହାର କରି ଅଡିଓ ରେକର୍ଡ କରିପାରିବ।"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ଆପ ୱିଣ୍ଡୋର ସ୍କ୍ରିନ କେପଚରଗୁଡ଼ିକୁ ଚିହ୍ନଟ କରନ୍ତୁ"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"ଆପଟି ବ୍ୟବହାରରେ ଥିବା ସମୟରେ ଏକ ସ୍କ୍ରିନସଟ ନିଆଗଲେ ଏହି ଆପକୁ ସୂଚିତ କରାଯିବ।"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIMକୁ କମାଣ୍ଡ ପଠାନ୍ତୁ"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"SIMକୁ କମାଣ୍ଡ ପଠାଇବା ପାଇଁ ଆପ୍‍କୁ ଅନୁମତି ଦେଇଥାଏ। ଏହା ବହୁତ ବିପଦପୂର୍ଣ୍ଣ।"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"ଶାରୀରିକ ଗତିବିଧି ଚିହ୍ନଟକରେ"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ଆପଣଙ୍କ ସେୟାର କରାଯାଇଥିବା ଷ୍ଟୋରେଜରୁ ଭିଡିଓ ଫାଇଲଗୁଡ଼ିକୁ ପଢ଼ିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"ସେୟାର କରାଯାଇଥିବା ଷ୍ଟୋରେଜରୁ ଇମେଜ ଫାଇଲଗୁଡ଼ିକୁ ପଢ଼ନ୍ତୁ"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"ଆପଣଙ୍କ ସେୟାର କରାଯାଇଥିବା ଷ୍ଟୋରେଜରୁ ଇମେଜ ଫାଇଲଗୁଡ଼ିକୁ ପଢ଼ିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ସେୟାର କରାଯାଇଥିବା ଷ୍ଟୋରେଜରୁ ୟୁଜରଙ୍କ ଦ୍ୱାରା ଚୟନିତ ଇମେଜ ଏବଂ ଭିଡିଓ ଫାଇଲଗୁଡ଼ିକୁ ପଢ଼ିବା"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ଆପଣ ଆପଣଙ୍କ ସେୟାର କରାଯାଇଥିବା ଷ୍ଟୋରେଜରୁ ଚୟନ କରିଥିବା ଇମେଜ ଏବଂ ଭିଡିଓ ଫାଇଲଗୁଡ଼ିକୁ ପଢ଼ିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"ଆପଣଙ୍କତ ସେୟାର୍‍ ହୋଇଥିବା ଷ୍ଟୋରେଜ୍‍ର ବିଷୟବସ୍ତୁ ସଂଶୋଧନ କିମ୍ବା ଡିଲିଟ୍‍ କରନ୍ତୁ"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"ଆପଣଙ୍କର ସେୟାର୍‍ ହୋଇଥିବା ଷ୍ଟୋରେଜ୍‍ର ବିଷୟବସ୍ତୁ ଲେଖିବାକୁ ଅନୁମତି କରିଥାଏ।"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP କଲ୍‌ କରନ୍ତୁ ଏବଂ ଗ୍ରହଣ କରନ୍ତୁ"</string>
@@ -795,30 +826,30 @@
     <item msgid="8150904584178569699">"ୱାର୍କ ଫ୍ୟାକ୍ସ"</item>
     <item msgid="4537253139152229577">"ହୋମ ଫାକ୍ସ"</item>
     <item msgid="6751245029698664340">"ପେଜର୍"</item>
-    <item msgid="1692790665884224905">"ଅନ୍ୟାନ୍ୟ"</item>
+    <item msgid="1692790665884224905">"ଅନ୍ୟ"</item>
     <item msgid="6216981255272016212">"କଷ୍ଟମ୍‌"</item>
   </string-array>
   <string-array name="emailAddressTypes">
     <item msgid="7786349763648997741">"ହୋମ"</item>
     <item msgid="435564470865989199">"ୱାର୍କ"</item>
-    <item msgid="4199433197875490373">"ଅନ୍ୟାନ୍ୟ"</item>
+    <item msgid="4199433197875490373">"ଅନ୍ୟ"</item>
     <item msgid="3233938986670468328">"କଷ୍ଟମ୍‌"</item>
   </string-array>
   <string-array name="postalAddressTypes">
     <item msgid="3861463339764243038">"ହୋମ"</item>
     <item msgid="5472578890164979109">"ୱାର୍କ"</item>
-    <item msgid="5718921296646594739">"ଅନ୍ୟାନ୍ୟ"</item>
+    <item msgid="5718921296646594739">"ଅନ୍ୟ"</item>
     <item msgid="5523122236731783179">"କଷ୍ଟମ୍‌"</item>
   </string-array>
   <string-array name="imAddressTypes">
     <item msgid="588088543406993772">"ହୋମ"</item>
     <item msgid="5503060422020476757">"ୱାର୍କ"</item>
-    <item msgid="2530391194653760297">"ଅନ୍ୟାନ୍ୟ"</item>
+    <item msgid="2530391194653760297">"ଅନ୍ୟ"</item>
     <item msgid="7640927178025203330">"କଷ୍ଟମ୍‌"</item>
   </string-array>
   <string-array name="organizationTypes">
     <item msgid="6144047813304847762">"ୱାର୍କ"</item>
-    <item msgid="7402720230065674193">"ଅନ୍ୟାନ୍ୟ"</item>
+    <item msgid="7402720230065674193">"ଅନ୍ୟ"</item>
     <item msgid="808230403067569648">"କଷ୍ଟମ୍‌"</item>
   </string-array>
   <string-array name="imProtocols">
@@ -838,7 +869,7 @@
     <string name="phoneTypeFaxWork" msgid="6757519896109439123">"ୱାର୍କ ଫାକ୍ସ"</string>
     <string name="phoneTypeFaxHome" msgid="6678559953115904345">"ହୋମ ଫାକ୍ସ"</string>
     <string name="phoneTypePager" msgid="576402072263522767">"ପେଜର୍"</string>
-    <string name="phoneTypeOther" msgid="6918196243648754715">"ଅନ୍ୟାନ୍ୟ"</string>
+    <string name="phoneTypeOther" msgid="6918196243648754715">"ଅନ୍ୟ"</string>
     <string name="phoneTypeCallback" msgid="3455781500844157767">"କଲବ୍ୟାକ୍"</string>
     <string name="phoneTypeCar" msgid="4604775148963129195">"କାର୍"</string>
     <string name="phoneTypeCompanyMain" msgid="4482773154536455441">"କମ୍ପାନୀର ମୁଖ୍ୟ"</string>
@@ -859,16 +890,16 @@
     <string name="emailTypeCustom" msgid="1809435350482181786">"କଷ୍ଟମ୍‌"</string>
     <string name="emailTypeHome" msgid="1597116303154775999">"ହୋମ"</string>
     <string name="emailTypeWork" msgid="2020095414401882111">"ୱାର୍କ"</string>
-    <string name="emailTypeOther" msgid="5131130857030897465">"ଅନ୍ୟାନ୍ୟ"</string>
+    <string name="emailTypeOther" msgid="5131130857030897465">"ଅନ୍ୟ"</string>
     <string name="emailTypeMobile" msgid="787155077375364230">"ମୋବାଇଲ୍‍"</string>
     <string name="postalTypeCustom" msgid="5645590470242939129">"କଷ୍ଟମ୍‌"</string>
     <string name="postalTypeHome" msgid="7562272480949727912">"ହୋମ"</string>
     <string name="postalTypeWork" msgid="8553425424652012826">"ୱାର୍କ"</string>
-    <string name="postalTypeOther" msgid="7094245413678857420">"ଅନ୍ୟାନ୍ୟ"</string>
+    <string name="postalTypeOther" msgid="7094245413678857420">"ଅନ୍ୟ"</string>
     <string name="imTypeCustom" msgid="5653384545085765570">"କଷ୍ଟମ୍‌"</string>
     <string name="imTypeHome" msgid="6996507981044278216">"ହୋମ"</string>
     <string name="imTypeWork" msgid="2099668940169903123">"ୱାର୍କ"</string>
-    <string name="imTypeOther" msgid="8068447383276219810">"ଅନ୍ୟାନ୍ୟ"</string>
+    <string name="imTypeOther" msgid="8068447383276219810">"ଅନ୍ୟ"</string>
     <string name="imProtocolCustom" msgid="4437878287653764692">"କଷ୍ଟମ୍‌"</string>
     <string name="imProtocolAim" msgid="4050198236506604378">"AIM"</string>
     <string name="imProtocolMsn" msgid="2257148557766499232">"Windows Live"</string>
@@ -880,7 +911,7 @@
     <string name="imProtocolJabber" msgid="7919269388889582015">"Jabber"</string>
     <string name="imProtocolNetMeeting" msgid="4985002408136148256">"NetMeeting"</string>
     <string name="orgTypeWork" msgid="8684458700669564172">"ୱାର୍କ"</string>
-    <string name="orgTypeOther" msgid="5450675258408005553">"ଅନ୍ୟାନ୍ୟ"</string>
+    <string name="orgTypeOther" msgid="5450675258408005553">"ଅନ୍ୟ"</string>
     <string name="orgTypeCustom" msgid="1126322047677329218">"କଷ୍ଟମ୍‌"</string>
     <string name="relationTypeCustom" msgid="282938315217441351">"କଷ୍ଟମ୍‌"</string>
     <string name="relationTypeAssistant" msgid="4057605157116589315">"Assistant"</string>
@@ -900,7 +931,7 @@
     <string name="sipAddressTypeCustom" msgid="6283889809842649336">"କଷ୍ଟମ୍‌"</string>
     <string name="sipAddressTypeHome" msgid="5918441930656878367">"ହୋମ"</string>
     <string name="sipAddressTypeWork" msgid="7873967986701216770">"ୱାର୍କ"</string>
-    <string name="sipAddressTypeOther" msgid="6317012577345187275">"ଅନ୍ୟାନ୍ୟ"</string>
+    <string name="sipAddressTypeOther" msgid="6317012577345187275">"ଅନ୍ୟ"</string>
     <string name="quick_contacts_not_available" msgid="1262709196045052223">"ଏହି କଣ୍ଟାକ୍ଟ ଦେଖିବାକୁ କୌଣସି ଆପ୍ଲିକେସନ ମିଳିଲା ନାହିଁ।"</string>
     <string name="keyguard_password_enter_pin_code" msgid="6401406801060956153">"PIN କୋଡ୍‍ ଟାଇପ୍‍ କରନ୍ତୁ"</string>
     <string name="keyguard_password_enter_puk_code" msgid="3112256684547584093">"PUK ଓ ନୂଆ PIN କୋଡ୍‍ ଟାଇପ୍‍ କରନ୍ତୁ"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ଅନଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"କୌଣସିମତେ ଖୋଲନ୍ତୁ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"ହାନିକାରକ ଆପ୍‌ ଚିହ୍ନଟ ହୋଇଛି"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ଗୋଟିଏ-ଥର ଆକ୍ସେସ ପାଇଁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ଅନୁମତି ଦିଅନ୍ତୁ ନାହିଁ"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ଆପଣଙ୍କ ଡିଭାଇସରେ ଯାହା ହୁଏ ତାହା ଡିଭାଇସ ଲଗଗୁଡ଼ିକ ରେକର୍ଡ କରେ। ସମସ୍ୟାଗୁଡ଼ିକୁ ଖୋଜି ସମାଧାନ କରିବାକୁ ଆପ୍ସ ଏହି ଲଗଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିପାରିବ।\n\nକିଛି ଲଗରେ ସମ୍ବେଦନଶୀଳ ସୂଚନା ଥାଇପାରେ, ତେଣୁ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଆପଣ ବିଶ୍ୱାସ କରୁଥିବା ଆପ୍ସକୁ ହିଁ ଅନୁମତି ଦିଅନ୍ତୁ। \n\nଯଦି ଆପଣ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦିଅନ୍ତି ନାହିଁ, ତେବେ ବି ଏହା ନିଜର ଡିଭାଇସ ଲଗଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିପାରିବ। ଆପଣଙ୍କ ଡିଭାଇସର ନିର୍ମାତା ଏବେ ବି ଆପଣଙ୍କର ଡିଭାଇସରେ କିଛି ଲଗ କିମ୍ବା ସୂଚନାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ସକ୍ଷମ ହୋଇପାରନ୍ତି।"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ଆପଣଙ୍କ ଡିଭାଇସରେ ଯାହା ହୁଏ ତାହା ଡିଭାଇସ ଲଗଗୁଡ଼ିକ ରେକର୍ଡ କରେ। ସମସ୍ୟାଗୁଡ଼ିକୁ ଖୋଜି ସମାଧାନ କରିବାକୁ ଆପ୍ସ ଏହି ଲଗଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିପାରିବ।\n\nକିଛି ଲଗରେ ସମ୍ବେଦନଶୀଳ ସୂଚନା ଥାଇପାରେ, ତେଣୁ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଆପଣ ବିଶ୍ୱାସ କରୁଥିବା ଆପ୍ସକୁ ହିଁ ଅନୁମତି ଦିଅନ୍ତୁ। \n\nଯଦି ଆପଣ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦିଅନ୍ତି ନାହିଁ, ତେବେ ବି ଏହା ନିଜର ଡିଭାଇସ ଲଗଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିପାରିବ। ଆପଣଙ୍କ ଡିଭାଇସର ନିର୍ମାତା ଏବେ ବି ଆପଣଙ୍କର ଡିଭାଇସରେ କିଛି ଲଗ କିମ୍ବା ସୂଚନାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ସକ୍ଷମ ହୋଇପାରନ୍ତି।\n\ng.co/android/devicelogsରେ ଅଧିକ ଜାଣନ୍ତୁ।"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ପୁଣି ଦେଖାନ୍ତୁ ନାହିଁ"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>, <xliff:g id="APP_2">%2$s</xliff:g> ସ୍ଲାଇସ୍‌କୁ ଦେଖାଇବା ପାଇଁ ଚାହେଁ"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ଏଡିଟ କରନ୍ତୁ"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"କଲ୍ ଓ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ଭାଇବ୍ରେଟ୍ ହେବ"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ଆପଣଙ୍କ <xliff:g id="DEVICE">%1$s</xliff:g>ରୁ ଫୋନର କ୍ୟାମେରାକୁ ଆକ୍ସେସ କରାଯାଇପାରିବ ନାହିଁ"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ଆପଣଙ୍କ <xliff:g id="DEVICE">%1$s</xliff:g>ରୁ ଟାବଲେଟର କ୍ୟାମେରାକୁ ଆକ୍ସେସ କରାଯାଇପାରିବ ନାହିଁ"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"ଷ୍ଟ୍ରିମ କରିବା ସମୟରେ ଏହାକୁ ଆକ୍ସେସ କରାଯାଇପାରିବ ନାହିଁ। ଏହା ପରିବର୍ତ୍ତେ ଆପଣଙ୍କ ଫୋନରେ ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"ଷ୍ଟ୍ରିମ କରିବା ସମୟରେ ପିକଚର-ଇନ-ପିକଚର ଦେଖାଯାଇପାରିବ ନାହିଁ"</string>
     <string name="system_locale_title" msgid="711882686834677268">"ସିଷ୍ଟମ ଡିଫଲ୍ଟ"</string>
     <string name="default_card_name" msgid="9198284935962911468">"କାର୍ଡ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 96917c0..cb2886e 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ਵੌਇਸਮੇਲ"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"ਕਨੈਕਸ਼ਨ ਸਮੱਸਿਆ ਜਾਂ ਅਵੈਧ MMI ਕੋਡ।"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ਵਿਸ਼ੇਸ਼ਤਾ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ।"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"ਓਪਰੇਸ਼ਨ ਕੇਵਲ ਫਿਕਸਡ ਡਾਇਲਿੰਗ ਨੰਬਰਾਂ ਤੱਕ ਸੀਮਿਤ ਹੈ।"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ਤੁਹਾਡੇ ਰੋਮਿੰਗ ਵਿੱਚ ਹੋਣ ਦੌਰਾਨ ਤੁਹਾਡੇ ਫ਼ੋਨ ਤੋਂ ਕਾਲ ਫਾਰਵਰਡਿੰਗ ਸੈਟਿੰਗਾਂ ਨੂੰ ਬਦਲਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ।"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"ਸੇਵਾ ਅਸਮਰੱਥ ਬਣਾਈ ਗਈ ਸੀ।"</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ਐਪ ਨੂੰ ਮੈਮਰੀ ਵਿੱਚ ਖੁਦ ਦੇ ਭਾਗਾਂ ਨੂੰ ਸਥਾਈ ਬਣਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਫ਼ੋਨ ਨੂੰ ਹੌਲੀ ਕਰਦੇ ਹੋਏ ਹੋਰਾਂ ਐਪਾਂ ਤੇ ਉਪਲਬਧ ਮੈਮਰੀ ਨੂੰ ਸੀਮਿਤ ਕਰ ਸਕਦਾ ਹੈ।"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"ਫੋਰਗ੍ਰਾਉਂਡ ਸੇਵਾਵਾਂ ਚਲਾਓ"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"ਐਪ ਨੂੰ ਫੋਰਗ੍ਰਾਉਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦਿਓ।"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"camera\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"ਐਪ ਨੂੰ \"camera\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"ਐਪ ਨੂੰ \"connectedDevice\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"ਐਪ ਨੂੰ \"dataSync\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"location\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"ਐਪ ਨੂੰ \"location\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"ਐਪ ਨੂੰ \"mediaPlayback\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"ਐਪ ਨੂੰ \"mediaProjection\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"microphone\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"ਐਪ ਨੂੰ \"microphone\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"ਐਪ ਨੂੰ \"phoneCall\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"health\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"ਐਪ ਨੂੰ \"health\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"ਐਪ ਨੂੰ \"remoteMessaging\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"ਐਪ ਨੂੰ \"systemExempted\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"ਐਪ ਨੂੰ \"specialUse\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ਐਪ ਸਟੋਰੇਜ ਜਗ੍ਹਾ ਮਾਪੋ"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"ਐਪ ਨੂੰ ਇਸਦਾ ਕੋਡ, ਡਾਟਾ ਅਤੇ ਕੈਸ਼ੇ ਆਕਾਰ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"ਸਿਸਟਮ ਸੈਟਿੰਗਾਂ  ਸੰਸ਼ੋਧਿਤ ਕਰੋ"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"ਇਹ ਐਪ ਵਰਤੋਂ ਵਿੱਚ ਹੋਣ ਵੇਲੇ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਨੂੰ ਵਰਤ ਕੇ ਆਡੀਓ ਰਿਕਾਰਡ ਕਰ ਸਕਦੀ ਹੈ।"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਆਡੀਓ ਰਿਕਾਰਡ ਕਰੋ"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ਇਹ ਐਪ ਕਿਸੇ ਵੇਲੇ ਵੀ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਨੂੰ ਵਰਤ ਕੇ ਆਡੀਓ ਰਿਕਾਰਡ ਕਰ ਸਕਦੀ ਹੈ।"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ਐਪ ਵਿੰਡੋਆਂ ਦੇ ਸਕ੍ਰੀਨ ਕੈਪਚਰਾਂ ਦਾ ਪਤਾ ਲਗਾਓ"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"ਐਪ ਦੇ ਵਰਤੋਂ ਵਿੱਚ ਹੋਣ ਦੌਰਾਨ ਸਕ੍ਰੀਨਸ਼ਾਟ ਲੈਣ \'ਤੇ ਇਸ ਐਪ ਨੂੰ ਸੂਚਨਾ ਪ੍ਰਾਪਤ ਹੋਵੇਗੀ।"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIM ਨੂੰ ਕਮਾਂਡਾਂ ਭੇਜੋ"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"ਐਪ ਨੂੰ SIM ਨੂੰ ਕਮਾਂਡਾਂ ਭੇਜਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਬਹੁਤ ਘਾਤਕ ਹੈ।"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"ਸਰੀਰਕ ਸਰਗਰਮੀ ਨੂੰ ਪਛਾਣਨਾ"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ਐਪ ਨੂੰ ਤੁਹਾਡੀ ਸਾਂਝੀ ਕੀਤੀ ਸਟੋਰੇਜ ਤੋਂ ਵੀਡੀਓ ਫ਼ਾਈਲਾਂ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"ਸਾਂਝੀ ਕੀਤੀ ਸਟੋਰੇਜ ਤੋਂ ਚਿੱਤਰ ਫ਼ਾਈਲਾਂ ਪੜ੍ਹੋ"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"ਐਪ ਨੂੰ ਤੁਹਾਡੀ ਸਾਂਝੀ ਕੀਤੀ ਸਟੋਰੇਜ ਤੋਂ ਚਿੱਤਰ ਫ਼ਾਈਲਾਂ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ਸਾਂਝੀ ਕੀਤੀ ਸਟੋਰੇਜ ਤੋਂ ਵਰਤੋਂਕਾਰ ਵੱਲੋਂ ਚੁਣੀਆਂ ਚਿੱਤਰ ਅਤੇ ਵੀਡੀਓ ਫ਼ਾਈਲਾਂ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ਇਹ ਐਪ ਨੂੰ ਉਨ੍ਹਾਂ ਚਿੱਤਰ ਅਤੇ ਵੀਡੀਓ ਫ਼ਾਈਲਾਂ ਨੂੰ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ ਜਿਨ੍ਹਾਂ ਨੂੰ ਤੁਸੀਂ ਆਪਣੀ ਸਾਂਝੀ ਸਟੋਰੇਜ ਤੋਂ ਚੁਣਦੇ ਹੋ।"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"ਸਮੱਗਰੀਆਂ ਦਾ ਸੰਸ਼ੋਧਨ ਕਰੋ ਜਾਂ ਮਿਟਾਓ"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"ਐਪ ਨੂੰ ਸਮੱਗਰੀਆਂ ਲਿਖਣ ਦਿੰਦੀ ਹੈ।"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP ਕਾਲਾਂ ਕਰੋ/ਪ੍ਰਾਪਤ ਕਰੋ"</string>
@@ -1284,7 +1315,7 @@
     <string name="volume_icon_description_media" msgid="4997633254078171233">"ਮੀਡੀਆ ਦੀ ਅਵਾਜ਼"</string>
     <string name="volume_icon_description_notification" msgid="579091344110747279">"ਸੂਚਨਾ ਵੌਲਿਊਮ"</string>
     <string name="ringtone_default" msgid="9118299121288174597">"ਪੂਰਵ-ਨਿਰਧਾਰਤ ਰਿੰਗਟੋਨ"</string>
-    <string name="ringtone_default_with_actual" msgid="2709686194556159773">"ਪੂਰਵ-ਨਿਰਧਾਰਤ (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+    <string name="ringtone_default_with_actual" msgid="2709686194556159773">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
     <string name="ringtone_silent" msgid="397111123930141876">"ਕੋਈ ਨਹੀਂ"</string>
     <string name="ringtone_picker_title" msgid="667342618626068253">"ਰਿੰਗਟੋਨਾਂ"</string>
     <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"ਅਲਾਰਮ ਧੁਨੀਆਂ"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ਅਣਸਥਾਪਤ ਕਰੋ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ਫਿਰ ਵੀ ਖੋਲ੍ਹੋ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"ਹਾਨੀਕਾਰਕ ਐਪ ਦਾ ਪਤਾ ਲੱਗਿਆ"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"ਕੀ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ਨੂੰ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ਇੱਕ-ਵਾਰ ਲਈ ਪਹੁੰਚ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ਆਗਿਆ ਨਾ ਦਿਓ"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ਡੀਵਾਈਸ ਲੌਗਾਂ ਵਿੱਚ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀਆਂ ਕਾਰਵਾਈਆਂ ਰਿਕਾਰਡ ਹੁੰਦੀਆਂ ਹਨ। ਐਪਾਂ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਲੱਭਣ ਅਤੇ ਉਨ੍ਹਾਂ ਦਾ ਹੱਲ ਕਰਨ ਲਈ ਇਨ੍ਹਾਂ ਲੌਗਾਂ ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੀਆਂ ਹਨ।\n\nਕੁਝ ਲੌਗਾਂ ਵਿੱਚ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੋ ਸਕਦੀ ਹੈ, ਇਸ ਲਈ ਸਿਰਫ਼ ਆਪਣੀਆਂ ਭਰੋਸੇਯੋਗ ਐਪਾਂ ਨੂੰ ਹੀ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ। \n\nਜੇ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਦਿੰਦੇ ਹੋ, ਤਾਂ ਇਹ ਹਾਲੇ ਵੀ ਆਪਣੇ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ। ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਨਿਰਮਾਤਾ ਹਾਲੇ ਵੀ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਮੌਜੂਦ ਕੁਝ ਲੌਗਾਂ ਜਾਂ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦਾ ਹੈ।"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"ਡੀਵਾਈਸ ਲੌਗਾਂ ਵਿੱਚ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀਆਂ ਕਾਰਵਾਈਆਂ ਰਿਕਾਰਡ ਹੁੰਦੀਆਂ ਹਨ। ਐਪਾਂ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਲੱਭਣ ਅਤੇ ਉਨ੍ਹਾਂ ਦਾ ਹੱਲ ਕਰਨ ਲਈ ਇਨ੍ਹਾਂ ਲੌਗਾਂ ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੀਆਂ ਹਨ।\n\nਕੁਝ ਲੌਗਾਂ ਵਿੱਚ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੋ ਸਕਦੀ ਹੈ, ਇਸ ਲਈ ਸਿਰਫ਼ ਆਪਣੀਆਂ ਭਰੋਸੇਯੋਗ ਐਪਾਂ ਨੂੰ ਹੀ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ। \n\nਜੇ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਦਿੰਦੇ ਹੋ, ਤਾਂ ਇਹ ਹਾਲੇ ਵੀ ਆਪਣੇ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ। ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਨਿਰਮਾਤਾ ਹਾਲੇ ਵੀ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਮੌਜੂਦ ਕੁਝ ਲੌਗਾਂ ਜਾਂ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦਾ ਹੈ।\n\ng.co/android/devicelogs \'ਤੇ ਹੋਰ ਜਾਣੋ।"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ਦੁਬਾਰਾ ਨਾ ਦਿਖਾਓ"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ਦੀ <xliff:g id="APP_2">%2$s</xliff:g> ਦੇ ਹਿੱਸੇ ਦਿਖਾਉਣ ਦੀ ਇੱਛਾ ਹੈ"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ਸੰਪਾਦਨ ਕਰੋ"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ਕਾਲਾਂ ਅਤੇ ਸੂਚਨਾਵਾਂ ਦੀ ਥਰਥਰਾਹਟ ਹੋਵੇਗੀ"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ਤੁਹਾਡੇ <xliff:g id="DEVICE">%1$s</xliff:g> ਤੋਂ ਫ਼ੋਨ ਦੇ ਕੈਮਰੇ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ਤੁਹਾਡੇ <xliff:g id="DEVICE">%1$s</xliff:g> ਤੋਂ ਟੈਬਲੈੱਟ ਦੇ ਕੈਮਰੇ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"ਸਟ੍ਰੀਮਿੰਗ ਦੌਰਾਨ ਇਸ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ। ਇਸਦੀ ਬਜਾਏ ਆਪਣੇ ਫ਼ੋਨ \'ਤੇ ਵਰਤ ਕੇ ਦੇਖੋ।"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"ਸਟ੍ਰੀਮਿੰਗ ਦੌਰਾਨ ਤਸਵੀਰ-ਵਿੱਚ-ਤਸਵੀਰ ਨਹੀਂ ਦੇਖੀ ਜਾ ਸਕਦੀ"</string>
     <string name="system_locale_title" msgid="711882686834677268">"ਸਿਸਟਮ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ"</string>
     <string name="default_card_name" msgid="9198284935962911468">"ਕਾਰਡ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index be9d322..70c9e32 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Poczta głosowa"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problem z połączeniem lub błędny kod MMI."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcja nie jest obsługiwana."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Operacja jest ograniczona wyłącznie do numerów ustalonych."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Podczas roamingu nie można zmienić ustawień przekazywania połączeń z telefonu."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Usługa została włączona."</string>
@@ -74,6 +75,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Przełączono mobilną transmisję danych na: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Możesz to zmienić w dowolnym momencie w Ustawieniach"</string>
     <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>
@@ -395,6 +398,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Pozwala aplikacji na trwałe zapisywanie swoich fragmentów w pamięci. Może to zmniejszyć ilość pamięci dostępnej dla innych aplikacji i spowolnić działanie telefonu."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"uruchom usługę na pierwszym planie"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Zezwala na korzystanie przez aplikację z usług na pierwszym planie."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"uruchamianie usług działających na pierwszym planie typu „camera”"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „camera”"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"uruchamianie usług działających na pierwszym planie typu „connectedDevice”"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „connectedDevice”"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"uruchamianie usług działających na pierwszym planie typu „dataSync”"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „dataSync”"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"uruchamianie usług działających na pierwszym planie typu „location”"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „location”"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"uruchamianie usług działających na pierwszym planie typu „mediaPlayback”"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „mediaPlayback”"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"uruchamianie usług działających na pierwszym planie typu „mediaProjection”"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „mediaProjection”"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"uruchamianie usług działających na pierwszym planie typu „microphone”"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „microphone”"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"uruchamianie usług działających na pierwszym planie typu „phoneCall”"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „phoneCall”"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"uruchamianie usług działających na pierwszym planie typu „health”"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „health”"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"uruchamianie usług działających na pierwszym planie typu „remoteMessaging”"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „remoteMessaging”"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"uruchamianie usług działających na pierwszym planie typu „systemExempted”"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „systemExempted”"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"uruchamianie usług działających na pierwszym planie typu „specialUse”"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „specialUse”"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mierzenie rozmiaru pamięci aplikacji"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Pozwala aplikacji na pobieranie własnego kodu, danych oraz rozmiarów pamięci podręcznej."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modyfikowanie ustawień systemu"</string>
@@ -447,6 +474,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ta aplikacja może nagrywać dźwięk przy użyciu mikrofonu, gdy jest używana."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"nagrywanie dźwięku w tle"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ta aplikacja może w dowolnym momencie nagrywać dźwięk przy użyciu mikrofonu."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"wykrywanie zrzutów ekranu z oknami aplikacji"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ta aplikacja będzie powiadamiana o zrzutach ekranu robionych po jej uruchomieniu."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"wysyłanie poleceń do karty SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Pozwala aplikacji na wysyłanie poleceń do karty SIM. To bardzo niebezpieczne."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"rozpoznawanie aktywności fizycznej"</string>
@@ -698,6 +727,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Zezwala na odczyt przez aplikację plików wideo w pamięci współdzielonej."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"odczyt plików graficznych z pamięci współdzielonej"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Zezwala na odczyt przez aplikację plików graficznych w pamięci współdzielonej."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"odczyt plików obrazów i filmów wybranych przez użytkownika w pamięci współdzielonej"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Zezwala na odczyt przez aplikację plików obrazów i filmów wybranych w pamięci współdzielonej."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"modyfikowanie i usuwanie zawartości pamięci współdzielonej"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Zezwala aplikacji na zapis zawartości pamięci współdzielonej."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"wykonywanie/odbieranie połączeń SIP"</string>
@@ -2048,12 +2079,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ODINSTALUJ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OTWÓRZ MIMO TO"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Wykryto szkodliwą aplikację"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Zezwolić aplikacji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> na dostęp do wszystkich dzienników urządzenia?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Zezwól na jednorazowy dostęp"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nie zezwalaj"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Dzienniki urządzenia zapisują, co dzieje się na urządzeniu. Aplikacje mogą ich używać do wykrywania i rozwiązywania problemów.\n\nNiektóre dzienniki mogą zawierać poufne dane, dlatego na dostęp do wszystkich dzienników zezwalaj tylko aplikacjom, którym ufasz. \n\nNawet jeśli nie zezwolisz tej aplikacji na dostęp do wszystkich dzienników na urządzeniu, będzie mogła korzystać z własnych. Producent urządzenia nadal będzie mógł używać niektórych dzienników na urządzeniu."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Dzienniki urządzenia zapisują, co dzieje się na urządzeniu. Aplikacje mogą ich używać do wykrywania i rozwiązywania problemów.\n\nNiektóre dzienniki mogą zawierać poufne dane, dlatego na dostęp do wszystkich dzienników zezwalaj tylko aplikacjom, którym ufasz. \n\nNawet jeśli nie zezwolisz tej aplikacji na dostęp do wszystkich dzienników na urządzeniu, będzie mogła korzystać z własnych. Producent urządzenia nadal będzie mógł używać niektórych dzienników na urządzeniu.\n\nWięcej informacji znajdziesz na stronie g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Nie pokazuj ponownie"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Aplikacja <xliff:g id="APP_0">%1$s</xliff:g> chce pokazywać wycinki z aplikacji <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edytuj"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Wibracje przy połączeniach i powiadomieniach"</string>
@@ -2294,6 +2319,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nie można korzystać z aparatu telefonu na urządzeniu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nie można korzystać z aparatu tabletu na urządzeniu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Nie można z tego skorzystać podczas strumieniowania. Użyj telefonu."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Podczas strumieniowania nie można wyświetlać obrazu w obrazie"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Ustawienie domyślne systemu"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index ff352b1..255ed46 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Correio de voz"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problema de conexão ou código MMI inválido."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Recurso indisponível."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"A operação é limitada somente a números de discagem fixa."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Não é possível alterar as configurações de encaminhamento de chamada do seu smartphone em roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"O serviço foi ativado."</string>
@@ -73,6 +74,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Dados da <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ativados"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"É possível mudar essa opção a qualquer momento nas Configurações"</string>
     <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>
@@ -394,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite que o app torne partes de si mesmo persistentes na memória. Pode limitar a memória disponível para outros apps, deixando o telefone mais lento."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"executar serviço em primeiro plano"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite que o app use serviços em primeiro plano."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"executar serviços em primeiro plano com o tipo \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Permite que o app use serviços em primeiro plano com o tipo \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"executar serviços em primeiro plano com o tipo \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Permite que o app use serviços em primeiro plano com o tipo \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"executar serviços em primeiro plano com o tipo \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Permite que o app use serviços em primeiro plano com o tipo \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"executar serviços em primeiro plano com o tipo \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Permite que o app use serviços em primeiro plano com o tipo \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"executar serviços em primeiro plano com o tipo \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Permite que o app use serviços em primeiro plano com o tipo \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"executar serviços em primeiro plano com o tipo \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Permite que o app use serviços em primeiro plano com o tipo \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"executar serviços em primeiro plano com o tipo \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Permite que o app use serviços em primeiro plano com o tipo \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"executar serviços em primeiro plano com o tipo \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Permite que o app use serviços em primeiro plano com o tipo \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"executar serviços em primeiro plano com o tipo \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Permite que o app use serviços em primeiro plano com o tipo \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"executar serviços em primeiro plano com o tipo \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Permite que o app use serviços em primeiro plano com o tipo \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"executar serviços em primeiro plano com o tipo \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que o app use serviços em primeiro plano com o tipo \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executar serviços em primeiro plano com o tipo \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que o app use serviços em primeiro plano com o tipo \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"medir o espaço de armazenamento do app"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite que o app recupere o código, os dados e os tamanhos de cache"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modificar configurações do sistema"</string>
@@ -446,6 +473,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Enquanto está sendo usado, este app pode gravar áudio usando o microfone."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar áudio em segundo plano"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Este app pode gravar áudio usando o microfone a qualquer momento."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detectar capturas de tela de janelas do app"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"O app vai ser notificado quando uma captura de tela for tirada enquanto ele estiver em uso."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos para o chip"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que o app envie comandos ao chip. Muito perigoso."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconhecer atividade física"</string>
@@ -697,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permite que o app leia arquivos de vídeo do armazenamento compartilhado."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"ler arquivos de imagem do armazenamento compartilhado"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permite que o app leia arquivos de imagem do armazenamento compartilhado."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ler arquivos de imagem e vídeo selecionados pelo usuário no armazenamento compartilhado"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permite que o app leia arquivos de imagem e vídeo que você selecionar no armazenamento compartilhado."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"alterar ou excluir conteúdo do armaz. compartilhado"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Permite que o app grave o conteúdo do armaz. compartilhado."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"fazer/receber chamadas SIP"</string>
@@ -2047,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALAR"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ABRIR MESMO ASSIM"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"App nocivo detectado"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Permitir que o app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acesse todos os registros do dispositivo?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir o acesso único"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Não permitir"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações.\n\nSaiba mais em g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Não mostrar novamente"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quer mostrar partes do app <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Chamadas e notificações farão o dispositivo vibrar"</string>
@@ -2293,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Não é possível acessar a câmera do smartphone pelo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Não é possível acessar a câmera do tablet pelo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Não é possível acessar esse conteúdo durante o streaming. Tente pelo smartphone."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Não é possível usar o modo picture-in-picture durante o streaming"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Padrão do sistema"</string>
     <string name="default_card_name" msgid="9198284935962911468">"CHIP <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index d343af6..480de87 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Correio de voz"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problema de ligação ou código MMI inválido."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funcionalidade não suportada."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"A operação está restringida a números fixos autorizados."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Não é possível alterar as definições do encaminhamento de chamadas no telemóvel quando está em roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"O serviço foi ativado."</string>
@@ -73,6 +74,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>
@@ -394,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite que a app torne partes de si mesma persistentes na memória. Isto pode limitar a disponibilidade da memória para outras aplicações, tornando o telemóvel mais lento."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"executar serviço em primeiro plano"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite que a app utilize serviços em primeiro plano."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"executar o serviço em primeiro plano com o tipo \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Permite que a app use serviços em primeiro plano com o tipo \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"executar o serviço em primeiro plano com o tipo \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Permite que a app use serviços em primeiro plano com o tipo \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"executar o serviço em primeiro plano com o tipo \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Permite que a app use serviços em primeiro plano com o tipo \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"executar o serviço em primeiro plano com o tipo \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Permite que a app use serviços em primeiro plano com o tipo \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"executar o serviço em primeiro plano com o tipo \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Permite que a app use serviços em primeiro plano com o tipo \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"executar o serviço em primeiro plano com o tipo \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Permite que a app use serviços em primeiro plano com o tipo \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"executar o serviço em primeiro plano com o tipo \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Permite que a app use serviços em primeiro plano com o tipo \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"executar o serviço em primeiro plano com o tipo \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Permite que a app use serviços em primeiro plano com o tipo \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"executar o serviço em primeiro plano com o tipo \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Permite que a app use serviços em primeiro plano com o tipo \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"executar o serviço em primeiro plano com o tipo \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Permite que a app use serviços em primeiro plano com o tipo \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"executar o serviço em primeiro plano com o tipo \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que a app use serviços em primeiro plano com o tipo \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executar o serviço em primeiro plano com o tipo \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que a app use serviços em primeiro plano com o tipo \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"medir espaço de armazenamento da app"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite à app obter o código, os dados e o tamanhos de cache da mesma"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modificar as definições do sistema"</string>
@@ -446,6 +473,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Esta app pode gravar áudio através do microfone enquanto estiver a ser utilizada."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar áudio em segundo plano"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Esta app pode gravar áudio através do microfone em qualquer altura."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detetar capturas de ecrã de janelas da app"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Esta app vai receber uma notificação quando for tirada uma captura de ecrã enquanto a app estiver a ser usada."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos para o SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que a app envie comandos para o SIM. Esta ação é muito perigosa."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconhecer a atividade física"</string>
@@ -697,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permite que a app leia ficheiros de vídeo do armazenamento partilhado."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"ler ficheiros de imagem do armazenamento partilhado"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permite que a app leia ficheiros de imagem do armazenamento partilhado."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ler ficheiros de imagem e vídeo do armazenamento partilhado selecionados pelo utilizador"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permite que a app leia ficheiros de imagem e vídeo que selecionar no seu armazenamento partilhado."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"modif./elim. os conteúdos do armazenam. partilhado"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Permite que a apl. escreva conteúd. do armazen. partilhado."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"efetuar/receber chamadas SIP"</string>
@@ -2047,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALAR"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ABRIR MESMO ASSIM"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Aplicação prejudicial detetada"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Permitir que a app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> aceda a todos os registos do dispositivo?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir acesso único"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Não permitir"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Os registos do dispositivo documentam o que ocorre no seu dispositivo. As apps podem usar esses registos para detetar e corrigir problemas.\n\nAlguns registos podem conter informações confidenciais e, por isso, o acesso a todos os registos do dispositivo deve apenas ser permitido às apps nas quais confia. \n\nSe não permitir o acesso desta app a todos os registos do dispositivo, esta pode ainda assim aceder aos próprios registos. O fabricante do dispositivo pode continuar a aceder a alguns registos ou informações no seu dispositivo."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Os registos do dispositivo documentam o que ocorre no seu dispositivo. As apps podem usar esses registos para detetar e corrigir problemas.\n\nAlguns registos podem conter informações confidenciais e, por isso, o acesso a todos os registos do dispositivo só deve ser permitido às apps nas quais confia. \n\nSe não permitir o acesso desta app a todos os registos do dispositivo, esta pode ainda assim aceder aos próprios registos. O fabricante do dispositivo pode continuar a aceder a alguns registos ou informações no seu dispositivo.\n\nSaiba mais em g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Não mostrar de novo"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"A app <xliff:g id="APP_0">%1$s</xliff:g> pretende mostrar partes da app <xliff:g id="APP_2">%2$s</xliff:g>."</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"As chamadas e as notificações vibram."</string>
@@ -2293,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Não é possível aceder à câmara do telemóvel a partir do dispositivo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Não é possível aceder à câmara do tablet a partir do dispositivo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Não é possível aceder a isto durante o streaming. Em alternativa, experimente no telemóvel."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Não é possível ver o ecrã no ecrã durante o streaming"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Predefinição do sistema"</string>
     <string name="default_card_name" msgid="9198284935962911468">"CARTÃO <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index ff352b1..255ed46 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Correio de voz"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problema de conexão ou código MMI inválido."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Recurso indisponível."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"A operação é limitada somente a números de discagem fixa."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Não é possível alterar as configurações de encaminhamento de chamada do seu smartphone em roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"O serviço foi ativado."</string>
@@ -73,6 +74,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Dados da <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ativados"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"É possível mudar essa opção a qualquer momento nas Configurações"</string>
     <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>
@@ -394,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite que o app torne partes de si mesmo persistentes na memória. Pode limitar a memória disponível para outros apps, deixando o telefone mais lento."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"executar serviço em primeiro plano"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite que o app use serviços em primeiro plano."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"executar serviços em primeiro plano com o tipo \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Permite que o app use serviços em primeiro plano com o tipo \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"executar serviços em primeiro plano com o tipo \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Permite que o app use serviços em primeiro plano com o tipo \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"executar serviços em primeiro plano com o tipo \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Permite que o app use serviços em primeiro plano com o tipo \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"executar serviços em primeiro plano com o tipo \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Permite que o app use serviços em primeiro plano com o tipo \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"executar serviços em primeiro plano com o tipo \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Permite que o app use serviços em primeiro plano com o tipo \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"executar serviços em primeiro plano com o tipo \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Permite que o app use serviços em primeiro plano com o tipo \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"executar serviços em primeiro plano com o tipo \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Permite que o app use serviços em primeiro plano com o tipo \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"executar serviços em primeiro plano com o tipo \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Permite que o app use serviços em primeiro plano com o tipo \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"executar serviços em primeiro plano com o tipo \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Permite que o app use serviços em primeiro plano com o tipo \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"executar serviços em primeiro plano com o tipo \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Permite que o app use serviços em primeiro plano com o tipo \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"executar serviços em primeiro plano com o tipo \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que o app use serviços em primeiro plano com o tipo \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executar serviços em primeiro plano com o tipo \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que o app use serviços em primeiro plano com o tipo \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"medir o espaço de armazenamento do app"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite que o app recupere o código, os dados e os tamanhos de cache"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modificar configurações do sistema"</string>
@@ -446,6 +473,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Enquanto está sendo usado, este app pode gravar áudio usando o microfone."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar áudio em segundo plano"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Este app pode gravar áudio usando o microfone a qualquer momento."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detectar capturas de tela de janelas do app"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"O app vai ser notificado quando uma captura de tela for tirada enquanto ele estiver em uso."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos para o chip"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que o app envie comandos ao chip. Muito perigoso."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"reconhecer atividade física"</string>
@@ -697,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permite que o app leia arquivos de vídeo do armazenamento compartilhado."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"ler arquivos de imagem do armazenamento compartilhado"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permite que o app leia arquivos de imagem do armazenamento compartilhado."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"ler arquivos de imagem e vídeo selecionados pelo usuário no armazenamento compartilhado"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permite que o app leia arquivos de imagem e vídeo que você selecionar no armazenamento compartilhado."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"alterar ou excluir conteúdo do armaz. compartilhado"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Permite que o app grave o conteúdo do armaz. compartilhado."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"fazer/receber chamadas SIP"</string>
@@ -2047,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALAR"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ABRIR MESMO ASSIM"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"App nocivo detectado"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Permitir que o app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acesse todos os registros do dispositivo?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir o acesso único"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Não permitir"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações.\n\nSaiba mais em g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Não mostrar novamente"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quer mostrar partes do app <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Chamadas e notificações farão o dispositivo vibrar"</string>
@@ -2293,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Não é possível acessar a câmera do smartphone pelo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Não é possível acessar a câmera do tablet pelo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Não é possível acessar esse conteúdo durante o streaming. Tente pelo smartphone."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Não é possível usar o modo picture-in-picture durante o streaming"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Padrão do sistema"</string>
     <string name="default_card_name" msgid="9198284935962911468">"CHIP <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index b560b07..4d8d96b4 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Mesagerie vocală"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problemă de conexiune sau cod MMI nevalid."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funcția nu este acceptată."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Operația este limitată la numerele cu apelări restricționate."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nu poți schimba setările de redirecționare a apelurilor de pe telefon când ești în roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Serviciul a fost activat."</string>
@@ -73,6 +74,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"S-a trecut la datele mobile <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Poți modifica oricând opțiunea din Setări"</string>
     <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>
@@ -394,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Permite aplicației să declare persistente în memorie anumite părți ale sale. Acest lucru poate limita memoria disponibilă pentru alte aplicații și poate încetini funcționarea telefonului."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"să ruleze serviciul în prim plan"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Permite aplicației să utilizeze serviciile din prim-plan."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"să folosească serviciile în prim-plan cu tipul „camera”"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Permite aplicației să folosească serviciile în prim-plan cu tipul „camera”"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"să folosească serviciile în prim-plan cu tipul „connectedDevice”"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Permite aplicației să folosească serviciile în prim-plan cu tipul „connectedDevice”"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"să folosească serviciile în prim-plan cu tipul „dataSync”"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Permite aplicației să folosească serviciile în prim-plan cu tipul „dataSync”"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"să folosească serviciile în prim-plan cu tipul „location”"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Permite aplicației să folosească serviciile în prim-plan cu tipul „location”"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"să folosească serviciile în prim-plan cu tipul „mediaPlayback”"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Permite aplicației să folosească serviciile în prim-plan cu tipul „mediaPlayback”"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"să folosească serviciile în prim-plan cu tipul „mediaProjection”"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Permite aplicației să folosească serviciile în prim-plan cu tipul „mediaProjection”"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"să folosească serviciile în prim-plan cu tipul „microphone”"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Permite aplicației să folosească serviciile în prim-plan cu tipul „microphone”"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"să folosească serviciile în prim-plan cu tipul „phoneCall”"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Permite aplicației să folosească serviciile în prim-plan cu tipul „phoneCall”"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"să folosească serviciile în prim-plan cu tipul „health”"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Permite aplicației să folosească serviciile în prim-plan cu tipul „health”"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"să folosească serviciile în prim-plan cu tipul „remoteMessaging”"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Permite aplicației să folosească serviciile în prim-plan cu tipul „remoteMessaging”"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"să folosească serviciile în prim-plan cu tipul „systemExempted”"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite aplicației să folosească serviciile în prim-plan cu tipul „systemExempted”"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"să folosească serviciile în prim-plan cu tipul „specialUse”"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite aplicației să folosească serviciile în prim-plan cu tipul „specialUse”"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"măsurare spațiu de stocare al aplicației"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Permite aplicației să preia dimensiunile codului, ale datelor și ale memoriei cache"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modifică setări de sistem"</string>
@@ -446,6 +473,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Această aplicație poate să înregistreze conținut audio folosind microfonul când este în uz."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"să înregistreze conținut audio în fundal"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Această aplicație poate înregistra conținut audio folosind microfonul oricând."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"detectează capturile de ecran din ferestrele aplicației"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Aplicația va fi notificată când se realizează o captură de ecran din folosirea aplicației."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"să trimită comenzi către SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite aplicației să trimită comenzi pe cardul SIM. Această permisiune este foarte periculoasă."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"recunoașterea activității fizice"</string>
@@ -697,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Permite aplicației să citească fișiere video din spațiul de stocare comun."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"să citească fișiere imagine din spațiul de stocare comun"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Permite aplicației să citească fișiere imagine din spațiul de stocare comun."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"să citească fișierele imagine și video selectate de utilizator din spațiul de stocare comun"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Permite aplicației să citească fișierele imagine și video pe care le selectezi din spațiul de stocare comun."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"să modifice sau să șteargă conținutul spațiului de stocare comun"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Permite aplicației scrierea conținutul spațiului de stocare comun."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"efectuarea/primirea apelurilor SIP"</string>
@@ -2047,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEZINSTALEAZĂ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"Deschide oricum"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Aplicație dăunătoare detectată"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Permiți ca <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> să acceseze toate jurnalele dispozitivului?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permite accesul o dată"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nu permite"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Jurnalele dispozitivului înregistrează activitatea de pe dispozitivul tău. Aplicațiile pot folosi aceste jurnale pentru a identifica și a remedia probleme.\n\nUnele jurnale pot să conțină informații sensibile, prin urmare permite accesul la toate jurnalele dispozitivului doar aplicațiilor în care ai încredere. \n\nDacă nu permiți accesul aplicației la toate jurnalele dispozitivului, aceasta poate în continuare să acceseze propriile jurnale. Este posibil ca producătorul dispozitivului să acceseze în continuare unele jurnale sau informații de pe dispozitiv."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Jurnalele dispozitivului înregistrează activitatea de pe acesta. Aplicațiile pot folosi aceste jurnale pentru a identifica și a remedia probleme.\n\nUnele jurnale pot să conțină informații sensibile, prin urmare permite accesul la toate jurnalele dispozitivului doar aplicațiilor în care ai încredere. \n\nDacă nu permiți accesul aplicației la toate jurnalele dispozitivului, aceasta poate în continuare să acceseze propriile jurnale. E posibil ca producătorul dispozitivului să acceseze în continuare unele jurnale sau informații de pe dispozitiv.\n\nAflă mai multe la g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Nu mai afișa"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vrea să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editează"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Apelurile și notificările vor vibra"</string>
@@ -2293,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nu se poate accesa camera foto a telefonului de pe <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nu se poate accesa camera foto a tabletei de pe <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Nu se poate accesa în timpul streamingului. Încearcă pe telefon."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Nu se poate viziona picture-in-picture în timpul streamingului"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Prestabilit de sistem"</string>
     <string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index fbe67e2..353131e 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Голосовая почта"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Неполадки подключения или неверный код MMI."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функция не поддерживается."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Операция возможна только для разрешенных номеров."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Вы не можете изменить настройки переадресации вызовов, поскольку находитесь в роуминге."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Служба включена."</string>
@@ -74,6 +75,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>
@@ -87,7 +90,7 @@
     <string name="notification_channel_network_alert" msgid="4788053066033851841">"Оповещения"</string>
     <string name="notification_channel_call_forward" msgid="8230490317314272406">"Переадресация вызовов"</string>
     <string name="notification_channel_emergency_callback" msgid="54074839059123159">"Режим экстренных обратных вызовов"</string>
-    <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Состояние мобильного Интернета"</string>
+    <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Состояние мобильного интернета"</string>
     <string name="notification_channel_sms" msgid="1243384981025535724">"SMS"</string>
     <string name="notification_channel_voice_mail" msgid="8457433203106654172">"Голосовые сообщения"</string>
     <string name="notification_channel_wfc" msgid="9048240466765169038">"Звонки по Wi-Fi"</string>
@@ -395,6 +398,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Приложение сможет постоянно хранить свои компоненты в памяти. Это может уменьшить объем памяти, доступный другим приложениям, и замедлить работу устройства."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"Запуск активных сервисов"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Разрешить приложению использовать активные сервисы."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"запускать активные службы с типом camera"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Разрешить приложению использовать активные службы с типом camera"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"запускать активные службы с типом connectedDevice"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Разрешить приложению использовать активные службы с типом connectedDevice"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"запускать активные службы с типом dataSync"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Разрешить приложению использовать активные службы с типом dataSync"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"запускать активные службы с типом location"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Разрешить приложению использовать активные службы с типом location"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"запускать активные службы с типом mediaPlayback"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Разрешить приложению использовать активные службы с типом mediaPlayback"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"запускать активные службы с типом mediaProjection"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Разрешить приложению использовать активные службы с типом mediaProjection"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"запускать активные службы с типом microphone"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Разрешить приложению использовать активные службы с типом microphone"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"запускать активные службы с типом phoneCall"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Разрешить приложению использовать активные службы с типом phoneCall"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"запускать активные службы с типом health"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Разрешить приложению использовать активные службы с типом health"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"запускать активные службы с типом remoteMessaging"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Разрешить приложению использовать активные службы с типом remoteMessaging"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"запускать активные службы с типом systemExempted"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Разрешить приложению использовать активные службы с типом systemExempted"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"запускать активные службы с типом specialUse"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Разрешить приложению использовать активные службы с типом specialUse"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"Вычисление объема памяти приложений"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Приложение сможет получать сведения о размере кода, данных и кеша."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"Изменение настроек системы"</string>
@@ -447,6 +474,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Когда приложение используется, оно может записывать аудио с помощью микрофона."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Записывать аудио в фоновом режиме"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Приложение может в любое время записывать аудио с помощью микрофона."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"Обнаруживать запись экрана, когда открыто окно приложения"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Если во время использования приложения будет сделан скриншот, оно получит уведомление."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"Отправка команд SIM-карте"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Приложение сможет отправлять команды SIM-карте (данное разрешение представляет большую угрозу)."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"Распознавать физическую активность"</string>
@@ -698,6 +727,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Приложение сможет считывать видеофайлы из общего хранилища."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"считывание изображений из общего хранилища"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Приложение сможет считывать изображения из общего хранилища."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"считывание указанных пользователем изображений и видеофайлов из общего хранилища"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Приложение сможет считывать указанные вами изображения и видеофайлы из общего хранилища."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"Изменение или удаление данных на общем накопителе"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Приложение сможет записывать данные на общий накопитель."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"Входящие и исходящие вызовы SIP"</string>
@@ -2048,12 +2079,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"УДАЛИТЬ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ОТКРЫТЬ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Обнаружено вредоносное приложение"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Разрешить приложению \"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>\" доступ ко всем журналам устройства?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Разрешить разовый доступ"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Запретить"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"В журналы записывается информация о том, что происходит на устройстве. Приложения могут использовать их, чтобы находить и устранять неполадки.\n\nТак как некоторые журналы могут содержать конфиденциальную информацию, доступ ко всем журналам следует предоставлять только тем приложениям, которым вы доверяете. \n\nЕсли вы не предоставите такой доступ этому приложению, оно по-прежнему сможет просматривать свои журналы. Не исключено, что некоторые журналы или сведения на вашем устройстве будут по-прежнему доступны его производителю."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"В журналы записывается информация о том, что происходит на устройстве. Приложения могут использовать их, чтобы находить и устранять неполадки.\n\nТак как некоторые журналы могут содержать конфиденциальную информацию, доступ ко всем журналам следует предоставлять только тем приложениям, которым вы доверяете. \n\nЕсли вы не предоставите такой доступ этому приложению, оно по-прежнему сможет просматривать свои журналы. Также некоторые журналы или сведения на вашем устройстве могут быть доступны его производителю.\n\nПодробнее: g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Больше не показывать"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Приложение \"<xliff:g id="APP_0">%1$s</xliff:g>\" запрашивает разрешение на показ фрагментов приложения \"<xliff:g id="APP_2">%2$s</xliff:g>\"."</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Изменить"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Для звонков и уведомлений включен вибросигнал."</string>
@@ -2294,6 +2319,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"У устройства <xliff:g id="DEVICE">%1$s</xliff:g> нет доступа к камере телефона."</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"У устройства \"<xliff:g id="DEVICE">%1$s</xliff:g>\" нет доступа к камере планшета."</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Этот контент недоступен во время трансляции. Используйте телефон."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Нельзя запустить режим \"Картинка в картинке\" во время потоковой передачи"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Системные настройки по умолчанию"</string>
     <string name="default_card_name" msgid="9198284935962911468">"КАРТА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 4cec877..7bee4de 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"කටහඬ තැපෑල"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"සම්බන්ධතා ගැටළුවක් හෝ අවලංගු MMI කේතයකි."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"විශේෂාංගය සහාය නොදක්වයි."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"ස්ථාවර ඇමතීම් අංක වලට පමණක් මෙහෙයුම සීමාකර ඇත."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ඔබ රෝමිං තුළ සිටින අතරතුර ඔබේ දුරකථනයෙන් ඇමතුම් ප්‍රතියොමු සැකසීම් වෙනස් කළ නොහැකිය."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"සේවාව සබල කරන ලදි."</string>
@@ -72,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"අමතන්නාගේ ID සුපුරුදු අනුව සීමා වී නැත. මීළඟ ඇමතුම: සීමා කර ඇත"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"සේවාවන් සපයා නැත."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"අමතන්නාගේ ID සැකසීම ඔබට වෙනස්කල නොහැක."</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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"යෙදුමට තම කොටස් මතකය තුල නොබිඳීව රඳා පවත්වාගෙන යාමට අවසර දෙන්න. මෙය දුරකථනය මන්දගාමී කරමින් අනෙකුත් උපාංගයන් සඳහා ඉතිරි මතකය සීමා කිරීමට හැක."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"පෙරබිම් සේවාව ධාවනය කරන්න"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"පෙරබිම් සේවා භාවිත කිරීමට යෙදුමට ඉඩ දෙයි."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"කැමරාව\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"\"කැමරාව\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"\"connectedDevice\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"\"dataSync\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"ස්ථානය\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"\"ස්ථානය\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"\"mediaPlayback\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"\"mediaProjection\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"මයික්‍රොෆෝනය\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"\"මයික්‍රොෆෝනය\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"\"phoneCall\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"සෞඛ්‍යය\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"\"සෞඛ්‍යය\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"\"remoteMessaging\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"යෙදුම් ආචයනයේ ඉඩ ප්‍රමාණය මැනීම"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"යෙදුමකට එහි කේතය, දත්ත සහ හැඹිලි ප්‍රමාණ ලබාගැනීමට අවසර දෙන්න."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"පද්ධති සැකසීම් වෙනස් කිරීම"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"මෙම යෙදුමට එය භාවිතයෙහි ඇති අතරතුර මයික්‍රෆෝනය භාවිත කර ඕඩියෝ පටිගත කිරීමට හැකිය."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"පසුබිමෙහි ඕඩියෝ පටිගත කරන්න"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"මෙම යෙදුමට ඕනෑම වේලාවක මයික්‍රෆෝනය භාවිත කර ඕඩියෝ පටිගත කිරීමට හැකිය."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"යෙදුම් කවුළුවල තිර ග්‍රහණ අනාවරණය කර ගන්න"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"යෙදුම භාවිතා කරන අතරේ තිර රුවක් ගත් විට මෙම යෙදුමට දැනුම් දෙනු ලැබේ."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIM වෙත විධාන යැවීම"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM වෙත විධාන ගෙන යාමට යෙදුමට අවසර දෙයි. මෙය ඉතා භයානක වේ."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"ශාරීරික ක්‍රියාකාරකම හඳුනා ගන්න"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ඔබගේ බෙදා ගත් ගබඩාවෙන් වීඩියෝ ගොනු කියවීමට යෙදුමට ඉඩ දෙයි."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"බෙදා ගත් ගබඩාවෙන් රූප ගොනු කියවන්න"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"ඔබගේ බෙදා ගත් ගබඩාවෙන් රූප ගොනු කියවීමට යෙදුමට ඉඩ දෙයි."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"බෙදා ගත් ආචයනයෙන් පරිශීලක තෝරන ලද රූප සහ වීඩියෝ ගොනු කියවන්න"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ඔබේ බෙදා ගත් ආචයනයෙන් ඔබ තෝරන රූප සහ වීඩියෝ ගොනු කියවීමට යෙදුමට ඉඩ දෙයි."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"ඔබේ බෙදා ගත් ගබඩාවේ අන්තර්ගත වෙනස් කරන්න නැතහොත් මකන්න"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"යෙදුමට ඔබේ බෙදා ගත් ගබඩාවේ අන්තර්ගත කියවීමට ඉඩ දෙයි."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP ඇමතුම් සිදුකිරීමට/ලබාගැනීමට"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"අස්ථාපනය කරන්න"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"කෙසේ වුවත් විවෘත කරන්න"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"හානිකර යෙදුමක් අනාවරණය කර ගන්නා ලදී"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> හට සියලු උපාංග ලොග ප්‍රවේශ වීමට ඉඩ දෙන්නද?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"එක් වරක් ප්‍රවේශය ඉඩ දෙන්න"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ඉඩ නොදෙන්න"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"උපාංග ලොග ඔබේ උපාංගයෙහි සිදු වන දේ වාර්තා කරයි. ගැටලු සොයා ගැනීමට සහ නිරාකරණයට යෙදුම්වලට මෙම ලොග භාවිතා කළ හැක.\n\nසමහර ලොගවල සංවේදී තතු අඩංගු විය හැකි බැවින්, ඔබ විශ්වාස කරන යෙදුම්වලට පමණක් සියලු උපාංග ලොග වෙත ප්‍රවේශ වීමට ඉඩ දෙන්න. \n\nඔබ මෙම යෙදුමට සියලු උපාංග ලොග වෙත ප්‍රවේශ වීමට ඉඩ නොදෙන්නේ නම්, එයට තවමත් එහිම ලොග වෙත ප්‍රවේශ විය හැක. ඔබේ උපාංග නිෂ්පාදකයාට තවමත් ඔබේ උපාංගයෙහි සමහර ලොග හෝ තතු වෙත ප්‍රවේශ විය හැක."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"උපාංග ලොග ඔබේ උපාංගයෙහි සිදු වන දේ වාර්තා කරයි. ගැටලු සොයා ගැනීමට සහ නිරාකරණයට යෙදුම්වලට මෙම ලොග භාවිතා කළ හැක.\n\nසමහර ලොගවල සංවේදී තතු අඩංගු විය හැකි බැවින්, ඔබ විශ්වාස කරන යෙදුම්වලට පමණක් සියලු උපාංග ලොග වෙත ප්‍රවේශ වීමට ඉඩ දෙන්න. \n\nඔබ මෙම යෙදුමට සියලු උපාංග ලොග වෙත ප්‍රවේශ වීමට ඉඩ නොදෙන්නේ නම්, එයට තවමත් එහිම ලොග වෙත ප්‍රවේශ විය හැක. ඔබේ උපාංග නිෂ්පාදකයාට තවමත් ඔබේ උපාංගයෙහි සමහර ලොග හෝ තතු වෙත ප්‍රවේශ විය හැක.\n\ng.co/android/devicelogs හි දී තව දැන ගන්න."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"නැවත නොපෙන්වන්න"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> හට කොටස් <xliff:g id="APP_2">%2$s</xliff:g>ක් පෙන්වීමට අවශ්‍යයි"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"සංස්කරණය"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ඇමතුම් සහ දැනුම්දීම් කම්පනය වනු ඇත"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ඔබගේ <xliff:g id="DEVICE">%1$s</xliff:g> වෙතින් දුරකථනයේ කැමරාවට ප්‍රවේශ විය නොහැකිය"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ඔබගේ <xliff:g id="DEVICE">%1$s</xliff:g> වෙතින් ටැබ්ලටයේ කැමරාවට ප්‍රවේශ විය නොහැකිය"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"ප්‍රවාහය කරන අතරේ මෙයට ප්‍රවේශ විය නොහැක. ඒ වෙනුවට ඔබේ දුරකථනයෙහි උත්සාහ කරන්න."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"ප්‍රවාහය අතරේ පින්තූරයේ-පින්තූරය බැලිය නොහැක"</string>
     <string name="system_locale_title" msgid="711882686834677268">"පද්ධති පෙරනිමිය"</string>
     <string name="default_card_name" msgid="9198284935962911468">"කාඩ්පත <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index b98364a..7fa87e4 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Hlasová schránka"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problém s pripojením alebo neplatný kód MMI."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcia nie je podporovaná."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Operácia je obmedzená len na povolené čísla."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nastavenia presmerovania hovorov nie je možné zmeniť z telefónu počas roamingu."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Služba bola povolená."</string>
@@ -74,6 +75,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Dátové pripojenie bolo prepnuté na operátora <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Toto môžete kedykoľvek zmeniť v Nastaveniach"</string>
     <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>
@@ -395,6 +398,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Umožňuje aplikácii uložiť niektoré svoje časti natrvalo do pamäte. Môže to obmedziť pamäť dostupnú pre ostatné aplikácie a spomaliť tak telefón."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"spustiť službu v popredí"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Umožňuje aplikácii používať služby v popredí"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"spustiť službu na popredí s typom camera"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Umožňuje aplikácii využívať služby na popredí s typom camera"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"spustiť službu na popredí s typom connectedDevice"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Umožňuje aplikácii využívať služby na popredí s typom connectedDevice"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"spustiť službu na popredí s typom dataSync"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Umožňuje aplikácii využívať služby na popredí s typom dataSync"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"spustiť službu na popredí s typom location"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Umožňuje aplikácii využívať služby na popredí s typom location"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"spustiť službu na popredí s typom mediaPlayback"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Umožňuje aplikácii využívať služby na popredí s typom mediaPlayback"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"spustiť službu na popredí s typom mediaProjection"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Umožňuje aplikácii využívať služby na popredí s typom mediaProjection"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"spustiť službu na popredí s typom microphone"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Umožňuje aplikácii využívať služby na popredí s typom microphone"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"spustiť službu na popredí s typom phoneCall"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Umožňuje aplikácii využívať služby na popredí s typom phoneCall"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"spustiť službu na popredí s typom health"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Umožňuje aplikácii využívať služby na popredí s typom health"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"spustiť službu na popredí s typom remoteMessaging"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Umožňuje aplikácii využívať služby na popredí s typom remoteMessaging"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"spustiť službu na popredí s typom systemExempted"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Umožňuje aplikácii využívať služby na popredí s typom dataSync systemExempted"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"spustiť službu na popredí s typom specialUse"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Umožňuje aplikácii využívať služby na popredí s typom specialUse"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"zistiť veľkosť ukladacieho priestoru aplikácie"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Umožňuje aplikácii načítať svoj kód, údaje a veľkosti vyrovnávacej pamäte"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"upraviť nastavenia systému"</string>
@@ -447,6 +474,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Táto aplikácia môže nahrávať zvuk pomocou mikrofónu, keď ju používate."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"nahrávanie zvuku na pozadí"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Táto aplikácia môže kedykoľvek nahrávať zvuk pomocou mikrofónu."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"rozpoznávanie snímaní obrazovky zahŕňajúcich okná aplikácie"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Táto aplikácia dostane upozornenie, keď bude počas jej používania vytvorená snímka obrazovky."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"posielanie príkazov do SIM karty"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Umožňuje aplikácii odosielať príkazy na SIM kartu. Toto je veľmi nebezpečné povolenie."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"rozpoznávanie fyzickej aktivity"</string>
@@ -698,6 +727,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Umožňuje aplikácii čítať videosúbory z vášho zdieľaného priestoru."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"čítať súbory obrázka zo zdieľaného priestoru"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Umožňuje aplikácii čítať súbory obrázka z vášho zdieľaného priestoru."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"čítanie obrázkových súborov a videosúborov vybraných používateľom v zdieľanom ukladacom priestore"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Umožňuje aplikácii čítať obrázkové súbory a videosúbory, ktoré vyberiete vo svojom zdieľanom ukladacom priestore."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"upravovanie alebo odstraňovanie obsahu zdieľaného úložiska"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Umožňuje aplikácii zapisovať obsah zdieľaného úložiska."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"uskutočňovanie/príjem hovorov SIP"</string>
@@ -2048,12 +2079,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ODINŠTALOVAŤ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"OTVORIŤ AJ TAK"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Bola zistená škodlivá aplikácia"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Chcete povoliť aplikácii <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> prístup k všetkým denníkom zariadenia?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Povoliť jednorazový prístup"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nepovoliť"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Denníky zariadenia zaznamenávajú, čo sa deje vo vašom zariadení. Aplikácie môžu pomocou týchto denníkov vyhľadávať a riešiť problémy.\n\nNiektoré denníky môžu obsahovať citlivé údaje, preto povoľte prístup k všetkým denníkom zariadenia iba dôveryhodným aplikáciám. \n\nAk tejto aplikácii nepovolíte prístup k všetkým denníkom zariadenia, stále bude mať prístup k vlastným denníkom. Výrobca vášho zariadenia bude mať naďalej prístup k niektorým denníkom alebo informáciám vo vašom zariadení."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Denníky zariadenia zaznamenávajú, čo sa deje vo vašom zariadení. Aplikácie môžu pomocou týchto denníkov vyhľadávať a riešiť problémy.\n\nNiektoré denníky môžu obsahovať citlivé údaje, preto povoľte prístup k všetkým denníkom zariadenia iba dôveryhodným aplikáciám. \n\nAk tejto aplikácii nepovolíte prístup k všetkým denníkom zariadenia, stále bude mať prístup k vlastným denníkom. Výrobca vášho zariadenia bude mať naďalej prístup k niektorým denníkom alebo informáciám vo vašom zariadení.\n\nViac sa dozviete na g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Už nezobrazovať"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> chce zobrazovať rezy z aplikácie <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Upraviť"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Hovory a upozornenia budú vibrovať"</string>
@@ -2294,6 +2319,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> nemáte prístup ku kamere telefónu"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> nemáte prístup ku kamere tabletu"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"K tomuto obsahu nie je počas streamovania prístup. Skúste použiť telefón."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Počas streamingu sa obraz v obraze nedá zobraziť"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Predvolené systémom"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 6972abb..d98a130 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Glasovna pošta"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Težava s povezavo ali neveljavna koda MMI."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcija ni podprta."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Operacija je omejena na dovoljene telefonske številke, za katere ne velja zapora odhodnega klica."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Nastavitev preusmerjanja klicev ni mogoče spremeniti v telefonu med gostovanjem v tujem omrežju."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Storitev je omogočena."</string>
@@ -74,6 +75,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Prenos podatkov je preklopljen na operaterja <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"To lahko kadar koli spremenite v nastavitvah."</string>
     <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>
@@ -395,6 +398,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Aplikaciji omogoča, da nekatere svoje dele naredi trajne v pomnilniku. S tem je lahko pomnilnik omejen za druge aplikacije, zaradi česar je delovanje telefona upočasnjeno."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"Izvajanje storitve v ospredju"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Aplikaciji dovoljuje uporabo storitev v ospredju."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"izvajanje storitve v ospredju vrste »camera«"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »camera«."</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"izvajanje storitve v ospredju vrste »connectedDevice«"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »connectedDevice«."</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"izvajanje storitve v ospredju vrste »dataSync«"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »dataSync«."</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"izvajanje storitve v ospredju vrste »location«"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »location«."</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"izvajanje storitve v ospredju vrste »mediaPlayback«"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »mediaPlayback«."</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"izvajanje storitve v ospredju vrste »mediaProjection«"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »mediaProjection«."</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"izvajanje storitve v ospredju vrste »microphone«"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »microphone«."</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"izvajanje storitve v ospredju vrste »phoneCall«"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »phoneCall«."</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"izvajanje storitve v ospredju vrste »health«"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »health«."</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"izvajanje storitve v ospredju vrste »remoteMessaging«"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »remoteMessaging«."</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"izvajanje storitve v ospredju vrste »systemExempted«"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »systemExempted«."</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"izvajanje storitve v ospredju vrste »specialUse«"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »specialUse«."</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"izračunavanje prostora za shranjevanje aplikacije"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Aplikaciji omogoča, da pridobi njeno kodo, podatke in velikosti predpomnilnika."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"spreminjanje sistemskih nastavitev"</string>
@@ -447,6 +474,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ta aplikacija lahko uporablja mikrofon za snemanje zvoka med uporabo aplikacije."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"snemanje zvoka v ozadju"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ta aplikacija lahko poljubno uporablja mikrofon za snemanje zvoka."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"zaznavanje zajemov zaslonskih slik v oknih aplikacij"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ta aplikacija bo obveščena o vsakem posnetku zaslona, ustvarjenem med njeno uporabo."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"pošiljanje ukazov na kartico SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Aplikaciji dovoli pošiljanje ukazov kartici SIM. To je lahko zelo nevarno."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznavanje telesne dejavnosti"</string>
@@ -698,6 +727,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Aplikaciji omogoča branje videodatotek v deljeni shrambi."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"branje slikovnih datotek v deljeni shrambi"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Aplikaciji omogoča branje slikovnih datotek v deljeni shrambi."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"branje uporabniško izbranih slikovnih datotek in videodatotek v deljeni shrambi"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Aplikaciji omogoča branje slikovnih datotek in videodatotek, ki jih izberete v deljeni shrambi."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"spreminjanje ali brisanje vsebine skupne shrambe"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Aplikaciji omogoča zapisovanje vsebine skupne shrambe."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"opravljanje/sprejemanje klicev SIP"</string>
@@ -1250,7 +1281,7 @@
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Zagon aplikacij."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Dokončevanje zagona."</string>
     <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Pritisnili ste gumb za vklop, s čimer običajno izklopite zaslon.\n\nPoskusite se narahlo dotakniti med nastavljanjem prstnega odtisa."</string>
-    <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Za končanje nastavitve izklopite zaslon"</string>
+    <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Za končanje nastavitve izklopite zaslon."</string>
     <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Izklopi"</string>
     <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Želite nadaljevati preverjanje prstnega odtisa?"</string>
     <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Pritisnili ste gumb za vklop, s čimer običajno izklopite zaslon.\n\nZa preverjanje prstnega odtisa se poskusite narahlo dotakniti."</string>
@@ -2048,12 +2079,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ODMESTI"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"VSEENO ODPRI"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Zaznana je bila škodljiva aplikacija"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Ali aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> dovolite dostop do vseh dnevnikov naprave?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Dovoli enkratni dostop"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ne dovoli"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"V dnevnikih naprave se beleži dogajanje v napravi. Aplikacije lahko te dnevnike uporabijo za iskanje in odpravljanje težav.\n\nNekateri dnevniki morda vsebujejo občutljive podatke, zato dostop do vseh dnevnikov naprave omogočite le aplikacijam, ki jim zaupate. \n\nČe tej aplikaciji ne dovolite dostopa do vseh dnevnikov naprave, bo aplikacija kljub temu lahko dostopala do svojih dnevnikov. Proizvajalec naprave bo morda lahko kljub temu dostopal do nekaterih dnevnikov ali podatkov v napravi."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"V dnevnikih naprave se beleži dogajanje v napravi. Aplikacije lahko te dnevnike uporabijo za iskanje in odpravljanje težav.\n\nNekateri dnevniki morda vsebujejo občutljive podatke, zato dostop do vseh dnevnikov naprave omogočite le aplikacijam, ki jim zaupate. \n\nČe tej aplikaciji ne dovolite dostopa do vseh dnevnikov naprave, bo aplikacija kljub temu lahko dostopala do svojih dnevnikov. Proizvajalec naprave bo morda lahko kljub temu dostopal do nekaterih dnevnikov ali podatkov v napravi.\n\nPreberite več o tem na g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne prikaži več"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Aplikacija <xliff:g id="APP_0">%1$s</xliff:g> želi prikazati izreze aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Uredi"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Vibriranje bo vklopljeno za klice in obvestila"</string>
@@ -2294,6 +2319,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ni mogoče dostopati do fotoaparata telefona prek naprave <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ni mogoče dostopati do fotoaparata tabličnega računalnika prek naprave <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Do te vsebine ni mogoče dostopati med pretočnim predvajanjem. Poskusite s telefonom."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Slike v sliki ni mogoče prikazati med pretočnim predvajanjem."</string>
     <string name="system_locale_title" msgid="711882686834677268">"Sistemsko privzeto"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KARTICA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index cbac0f0..17c1f63a 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Posta zanore"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problem në lidhje ose kod i pavlefshëm MMI-je."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Veçoria nuk mbështetet."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Veprimi është i kufizuar vetëm kundrejt numrave me telefonim të përzgjedhur"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Cilësimet e transferimit të telefonatave nuk mund të ndryshohen nga telefoni yt kur je në roaming."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Shërbimi u aktivizua."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Të dhënat u kaluan te <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Mund ta ndryshosh këtë në çdo kohë te \"Cilësimet\""</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Lejon aplikacionin të zaptojë një pjesë të qëndrueshme në kujtesë. Kjo mund të kufizojë kujtesën e disponueshme për aplikacionet e tjera duke e ngadalësuar telefonin."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"ekzekuto shërbimin në plan të parë"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Lejon aplikacionin të përdorë shërbimet në plan të parë."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"të ekzekutojë shërbimin në plan të parë me llojin \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"të ekzekutojë shërbimin në plan të parë me llojin \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"të ekzekutojë shërbimin në plan të parë me llojin \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"të ekzekutojë shërbimin në plan të parë me llojin \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"të ekzekutojë shërbimin në plan të parë me llojin \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"të ekzekutojë shërbimin në plan të parë me llojin \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"të ekzekutojë shërbimin në plan të parë me llojin \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"të ekzekutojë shërbimin në plan të parë me llojin \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"të ekzekutojë shërbimin në plan të parë me llojin \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"të ekzekutojë shërbimin në plan të parë me llojin \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"të ekzekutojë shërbimin në plan të parë me llojin \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"të ekzekutojë shërbimin në plan të parë me llojin \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mat hapësirën ruajtëse të aplikacionit"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Lejon aplikacionin të gjejë kodin e tij, të dhënat dhe madhësitë e memorieve të përkohshme."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"modifiko cilësimet e sistemit"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ky aplikacion mund të regjistrojë audion duke përdorur mikrofonin kur aplikacioni është në përdorim."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"të regjistrojë audion në sfond"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ky aplikacion mund të regjistrojë audion me mikrofonin në çdo kohë."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"të zbulojë regjistrimet e ekranit të dritareve të aplikacionit"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ky aplikacion do të njoftohet kur nxirret një pamje ekrani ndërkohë që aplikacioni është në përdorim."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"dërgo komanda te karta SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Lejon aplikacionin t\'i dërgojë komanda kartës SIM. Kjo është shumë e rrezikshme."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"njih aktivitetin fizik"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Lejon që aplikacioni të lexojë skedarët e videove nga hapësira ruajtëse e ndarë."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"të lexojë skedarët e imazheve nga hapësira ruajtëse e ndarë"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Lejon që aplikacioni të lexojë skedarët e imazheve nga hapësira ruajtëse e ndarë."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"të lexojë imazhin e zgjedhur nga përdoruesi dhe skedarët e videove nga hapësira ruajtëse e ndarë"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Lejon aplikacionin të lexojë imazhin dhe skedarët e videove që ti zgjedh nga hapësira jote ruajtëse e ndarë."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"modifiko ose fshi përmbajtjet e hapësirës ruajtëse të ndarë"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Lejon që aplikacioni të shkruajë përmbajtjet e hapësirës ruajtëse të ndarë."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"bëj/merr telefonata SIP"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ÇINSTALO"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"HAPE GJITHSESI"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"U gjet aplikacion i dëmshëm"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Të lejohet që <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> të ketë qasje te të gjitha evidencat e pajisjes?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Lejo qasjen vetëm për një herë"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Mos lejo"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Evidencat e pajisjes regjistrojnë çfarë ndodh në pajisjen tënde. Aplikacionet mund t\'i përdorin këto evidenca për të gjetur dhe rregulluar problemet.\n\nDisa evidenca mund të përmbajnë informacione delikate, ndaj lejo vetëm aplikacionet që u beson të kenë qasje te të gjitha evidencat e pajisjes. \n\nNëse nuk e lejon këtë aplikacion që të ketë qasje te të gjitha evidencat e pajisjes, ai mund të vazhdojë të ketë qasje tek evidencat e tij. Prodhuesi i pajisjes sate mund të jetë ende në gjendje që të ketë qasje te disa evidenca ose informacione në pajisjen tënde."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Evidencat e pajisjes regjistrojnë çfarë ndodh në pajisjen tënde. Aplikacionet mund t\'i përdorin këto evidenca për të gjetur dhe rregulluar problemet.\n\nDisa evidenca mund të përmbajnë informacione delikate, ndaj lejo vetëm aplikacionet që u beson të kenë qasje te të gjitha evidencat e pajisjes. \n\nNëse nuk e lejon këtë aplikacion që të ketë qasje te të gjitha evidencat e pajisjes, ai mund të vazhdojë të ketë qasje tek evidencat e tij. Prodhuesi i pajisjes sate mund të jetë ende në gjendje që të ketë qasje te disa evidenca ose informacione në pajisjen tënde.\n\nMëso më shumë në g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Mos e shfaq më"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> dëshiron të shfaqë pjesë të <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Modifiko"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Do të lëshojë dridhje për telefonatat dhe njoftimet"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nuk mund të qasesh në kamerën e telefonit tënd nga <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nuk mund të qasesh në kamerën e tabletit tënd nga <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Nuk mund të kesh qasje në të gjatë transmetimit. Provoje në telefon më mirë."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Figura brenda figurës nuk mund të shikohet gjatë transmetimit"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Parazgjedhja e sistemit"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index d5549e7..e7b1b12 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Гласовна пошта"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Проблеми са везом или неважећи MMI кôд."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функција није подржана."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Рад је ограничен само на бројеве фиксног бирања."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Не можете да промените подешавања преусмеравања позива са телефона док сте у ромингу."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Услуга је омогућена."</string>
@@ -73,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>
@@ -394,6 +397,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Дозвољава апликацији да учини сопствене компоненте трајним у меморији. Ово може да ограничи меморију доступну другим апликацијама и успори телефон."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"покрени услугу у првом плану"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Дозвољава апликацији да користи услуге у првом плану."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"покретање услуге у првом плану која припада типу „camera“"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „camera“"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"покретање услуге у првом плану која припада типу „connectedDevice“"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „connectedDevice“"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"покретање услуге у првом плану која припада типу „dataSync“"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „dataSync“"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"покретање услуге у првом плану која припада типу „location“"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „location“"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"покретање услуге у првом плану која припада типу „mediaPlayback“"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „mediaPlayback“"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"покретање услуге у првом плану која припада типу „mediaProjection“"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „mediaProjection“"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"покретање услуге у првом плану која припада типу „microphone“"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „microphone“"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"покретање услуге у првом плану која припада типу „phoneCall“"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „phoneCall“"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"покретање услуге у првом плану која припада типу „health“"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „health“"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"покретање услуге у првом плану која припада типу „remoteMessaging“"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „remoteMessaging“"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"покретање услуге у првом плану која припада типу „systemExempted“"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „systemExempted“"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"покретање услуге у првом плану која припада типу „specialUse“"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „specialUse“"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"мерење меморијског простора у апликацији"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Дозвољава апликацији да преузме величине кôда, података и кеша."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"измена подешавања система"</string>
@@ -446,6 +473,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ова апликација може да снима звук помоћу микрофона док се апликација користи."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"да снима звук у позадини"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ова апликација може да снима звук помоћу микрофона у било ком тренутку."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"откривање снимања екрана у прозорима апликација"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ако се током коришћења ове апликације направи снимак екрана, апликација ће добити обавештење."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"слање команди на SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Омогућава апликацији да шаље команде SIM картици. То је веома опасно."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"препознавање физичких активности"</string>
@@ -697,6 +726,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Омогућава апликацији да чита видео фајлове из дељеног меморијског простора."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"читање фајлова слика из дељеног меморијског простора"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Омогућава апликацији да чита фајлове слика из дељеног меморијског простора."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"читање фајлова слика и видео снимака које корисник бира из дељеног меморијског простора"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Омогућава апликацији да чита фајлове слика и видео снимака које изаберете из дељеног меморијског простора."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"мењање или брисање садржаја дељеног меморијског простора"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Дозвољава апликацији да уписује садржај дељеног меморијског простора."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"упућивање/пријем SIP позива"</string>
@@ -2047,12 +2078,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ДЕИНСТАЛИРАЈ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ИПАК ОТВОРИ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Откривена је штетна апликација"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Желите да дозволите апликацији <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> да приступа свим евиденцијама уређаја?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Дозволи једнократан приступ"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Не дозволи"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Евиденције уређаја региструју шта се дешава на уређају. Апликације могу да користе те евиденције да би пронашле и решиле проблеме.\n\nНеке евиденције могу да садрже осетљиве информације, па приступ свим евиденцијама уређаја треба да дозвољавате само апликацијама у које имате поверења. \n\nАко не дозволите овој апликацији да приступа свим евиденцијама уређаја, она и даље може да приступа сопственим евиденцијама. Произвођач уређаја ће можда и даље моћи да приступа неким евиденцијама или информацијама на уређају."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Евиденције уређаја региструју шта се дешава на уређају. Апликације могу да користе те евиденције да би пронашле и решиле проблеме.\n\nНеке евиденције могу да садрже осетљиве информације, па приступ свим евиденцијама уређаја треба да дозвољавате само апликацијама у које имате поверења. \n\nАко не дозволите овој апликацији да приступа свим евиденцијама уређаја, она и даље може да приступа сопственим евиденцијама. Произвођач уређаја ће можда и даље моћи да приступа неким евиденцијама или информацијама на уређају.\n\nСазнајте више на g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Не приказуј поново"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Апликација <xliff:g id="APP_0">%1$s</xliff:g> жели да приказује исечке из апликације <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Измени"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Вибрација за позиве и обавештења је укључена"</string>
@@ -2293,6 +2318,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не може да се приступи камери телефона са <xliff:g id="DEVICE">%1$s</xliff:g> уређаја"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Не може да се приступи камери таблета са <xliff:g id="DEVICE">%1$s</xliff:g> уређаја"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Овом не можете да приступате током стримовања. Пробајте на телефону."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Не можете да гледате слику у слици при стримовању"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Подразумевани системски"</string>
     <string name="default_card_name" msgid="9198284935962911468">"КАРТИЦА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index d1c579d..7d4ba74 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Röstbrevlåda"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Anslutningsproblem eller ogiltig MMI-kod."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funktionen stöds inte."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Endast fasta nummer kan användas."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Det går inte att ändra inställningarna för vidarebefordran av samtal medan mobilen är i roaming-läge."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Tjänsten har aktiverats."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Du har bytt mobildata till <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Du kan ändra det här när som helst i inställningarna"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Tillåter att delar av appen läggs beständigt i minnet. Detta kan innebära att det tillgängliga minnet för andra appar begränsas, vilket gör mobilen långsam."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"kör tjänst i förgrunden"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Tillåter att appen använder tjänster i förgrunden."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"kör förgrundstjänst av typen camera"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Tillåter att appen använder förgrundstjänster av typen camera"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"kör förgrundstjänst av typen connectedDevice"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Tillåter att appen använder förgrundstjänster av typen connectedDevice"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"kör förgrundstjänst av typen dataSync"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Tillåter att appen använder förgrundstjänster av typen dataSync"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"kör förgrundstjänst av typen location"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Tillåter att appen använder förgrundstjänster av typen location"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"kör förgrundstjänst av typen mediaPlayback"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Tillåter att appen använder förgrundstjänster av typen mediaPlayback"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"kör förgrundstjänst av typen mediaProjection"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Tillåter att appen använder förgrundstjänster av typen mediaProjection"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"kör förgrundstjänst av typen microphone"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Tillåter att appen använder förgrundstjänster av typen microphone"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"kör förgrundstjänst av typen phoneCall"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Tillåter att appen använder förgrundstjänster av typen phoneCall"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"kör förgrundstjänst av typen health"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Tillåter att appen använder förgrundstjänster av typen health"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"kör förgrundstjänst av typen remoteMessaging"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Tillåter att appen använder förgrundstjänster av typen remoteMessaging"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"kör förgrundstjänst av typen systemExempted"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Tillåter att appen använder förgrundstjänster av typen systemExempted"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"kör förgrundstjänst av typen specialUse"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Tillåter att appen använder förgrundstjänster av typen specialUse"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mäta appens lagringsplats"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Tillåter att appen hämtar kod, data och cachestorlekar"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"ändra systeminställningar"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Appen kan ta spela in ljud med mikrofonen när appen används."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"spela in ljud i bakgrunden"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Appen kan spela in ljud med mikrofonen när som helst."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"upptäck skärmbilder/skärminspelningar av appfönster"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Den här appen informeras om en skärmbild tas när appen används."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"skicka kommandon till SIM-kortet"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Tillåter att appen skickar kommandon till SIM-kortet. Detta är mycket farligt."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"känn igen fysisk aktivitet"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Tillåter att appen läser videofiler från delad lagring."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"läsa bildfiler från delad lagring"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Tillåter att appen läser bildfiler från delad lagring."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"läsa bild- och videofiler som användaren valt från delat lagringsutrymme"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Tillåter att appen läser bild- och videofiler som du väljer från ditt delade lagringsutrymme."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"ändra eller ta bort innehåll på delat lagringsutrymme"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Tillåter att appen skriver innehåll på ditt delade lagringsutrymme."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"gör/ta emot SIP-anrop"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"AVINSTALLERA"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ÖPPNA ÄNDÅ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"En skadlig app har upptäckts"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vill du tillåta att <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> får åtkomst till alla enhetsloggar?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Tillåt engångsåtkomst"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Tillåt inte"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"I enhetsloggar registreras vad som händer på enheten. Appar kan använda dessa loggar för att hitta och åtgärda problem.\n\nVissa loggar kan innehålla känsliga uppgifter, så du ska bara bevilja appar du litar på åtkomst till alla enhetsloggar. \n\nEn app har åtkomst till sina egna loggar även om du inte ger den åtkomst till alla enhetsloggar. Enhetens tillverkare kan fortfarande ha åtkomst till vissa loggar eller viss information på enheten."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Enhetsloggar registrerar vad som händer på enheten. Appar kan använda dessa loggar för att hitta och åtgärda problem.\n\nVissa loggar kan innehålla känsliga uppgifter, så du ska bara bevilja appar du litar på åtkomst till alla enhetsloggar. \n\nEn app har åtkomst till sina egna loggar även om du inte ger den åtkomst till alla enhetsloggar. Enhetens tillverkare kan fortfarande ha åtkomst till vissa loggar eller viss information på enheten.\n\nLäs mer på g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Visa inte igen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vill kunna visa bitar av <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Redigera"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Vibrerar vid samtal och aviseringar"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Telefonens kamera kan inte användas från <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Surfplattans kamera kan inte användas från <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Det går inte att komma åt innehållet när du streamar. Testa med telefonen i stället."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Det går inte att visa bild-i-bild när du streamar"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Systemets standardinställning"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KORT <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 2bc3f57..702e97f 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Ujumbe wa sauti"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Tatizo la muunganisho au msimbo batili MMI."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Kipengele hakitumiki."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Ni matumizi yanayohusisha nambari za simu zilizobainishwa pekee yatakayowezekana."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Haiwezi kubadilisha mipangilio ya kusambaza simu kutoka kwenye simu yako ukiwa unatumia mitandao mingine."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Huduma iliwezeshwa"</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Sasa unatumia data ya mtandao wa <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Unaweza kubadilisha hali hii wakati wowote kwenye Mipangilio"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Inaruhusu programu kuendelesha vijisehemu vyake kwenye kumbukumbu. Hii inaweza kupunguza kumbukumbu inayopatikana katika programu nyingine ikipunguza kasi ya simu."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"tumia huduma zinazoonekana kwenye skrini"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Huruhusu programu kutumia huduma zinazoonekana kwenye skrini."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"health\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"Pima nafasi ya hifadhi ya programu"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Huruhusu Programu kupata tena msimbo, data na ukubwa wa akiba yake"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"rekebisha mipangilio ya mfumo"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Programu hii inaweza kurekodi sauti kwa kutumia maikrofoni wakati programu inatumika."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"rekodi sauti chinichini"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Programu hii inaweza kurekodi sauti kwa kutumia maikrofoni wakati wowote."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"tambua picha za skrini za madirisha ya programu"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Programu hii itaarifiwa picha ya skrini itakapopigwa wakati programu inatumika."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"tuma amri kwenye SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Huruhusu programu kutuma amri kwa SIM. Hii ni hatari sana."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"itambue shughuli unazofanya"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Huruhusu programu kusoma faili za video kutoka kwenye hifadhi unayoshiriki."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"soma faili za picha kutoka kwenye hifadhi iliyoshirikiwa"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Huruhusu programu kusoma faili za picha kutoka kwenye hifadhi yako iliyoshirikiwa."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"soma faili za picha na video alizochagua mtumiaji kutoka kwenye hifadhi inayoshirikiwa"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Huruhusu programu kusoma faili za picha na video unazochagua kutoka kwenye hifadhi yako inayoshirikiwa."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"irekebishe au ifute maudhui ya hifadhi unayoshiriki"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Huruhusu programu iandike maudhui ya hifadhi unayoshiriki."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"piga/pokea simu za SIP"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ONDOA"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"FUNGUA TU"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Imetambua programu hatari"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Ungependa kuruhusu <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ifikie kumbukumbu zote za kifaa?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Ruhusu ufikiaji wa mara moja"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Usiruhusu"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Kumbukumbu za kifaa zinarekodi kinachofanyika kwenye kifaa chako. Programu zinaweza kutumia kumbukumbu hizi ili kutambua na kurekebisha hitilafu.\n\nBaadhi ya kumbukumbu huenda zikawa na taarifa nyeti, hivyo ruhusu tu programu unazoziamini kufikia kumbukumbu zote za kifaa. \n\nIwapo hutaruhusu programu hii ifikie kumbukumbu zote za kifaa, bado inaweza kufikia kumbukumbu zake yenyewe. Huenda mtengenezaji wa kifaa chako bado akaweza kufikia baadhi ya kumbukumbu au taarifa zilizopo kwenye kifaa chako."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Kumbukumbu za kifaa hurekodi kinachofanyika kwenye kifaa chako. Programu zinaweza kutumia kumbukumbu hizi ili kutambua na kurekebisha hitilafu.\n\nBaadhi ya kumbukumbu huenda zikawa na taarifa nyeti, hivyo ruhusu tu programu unazoziamini kufikia kumbukumbu zote za kifaa. \n\nIwapo hutaruhusu programu hii ifikie kumbukumbu zote za kifaa, bado inaweza kufikia kumbukumbu zake yenyewe. Huenda mtengenezaji wa kifaa chako bado akaweza kufikia baadhi ya kumbukumbu au taarifa zilizopo kwenye kifaa chako.\n\nPata maelezo zaidi kwenye g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Usionyeshe tena"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> inataka kuonyesha vipengee <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Badilisha"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Itatetema arifa ikitumwa au simu ikipigwa"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Huwezi kufikia kamera ya simu kutoka kwenye <xliff:g id="DEVICE">%1$s</xliff:g> yako"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Haiwezi kufikia kamera ya kompyuta kibao kutoka kwenye <xliff:g id="DEVICE">%1$s</xliff:g> yako"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Huwezi kufikia maudhui haya unapotiririsha. Badala yake jaribu kwenye simu yako."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Huwezi kuona picha iliyopachikwa ndani ya picha nyingine unapotiririsha"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Chaguomsingi la mfumo"</string>
     <string name="default_card_name" msgid="9198284935962911468">"SIM KADI <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 9e48a47..7e1ad9e 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"குரலஞ்சல்"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"இணைப்பு சிக்கல் அல்லது தவறான MMI குறியீடு."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"அம்சம் ஆதரிக்கப்படவில்லை."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"நிலையான அழைப்பு எண்களுக்கு மட்டுமே எனச் செயல்பாடு வரையறுக்கப்பட்டுள்ளது."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ரோமிங்கில் இருக்கும் போது, உங்கள் மொபைலிலிருந்து அழைப்புப் பகிர்வு அமைப்புகளை மாற்ற முடியாது."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"சேவை இயக்கப்பட்டுள்ளது."</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"நினைவகத்தில் நிலையாக இருக்கும் தன்னுடைய பகுதிகளை உருவாக்கப் ஆப்ஸை அனுமதிக்கிறது. இதனால பிற பயன்பாடுகளுக்குக் கிடைக்கும் நினைவகம் வரையறுக்கப்பட்டு, மொபைலின் வேகத்தைக் குறைக்கலாம்"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"முன்புலத்தில் இயங்கும் சேவையை இயக்குதல்"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"முன்புலத்தில் இயங்கும் சேவைகளை உபயோகிக்க, ஆப்ஸை அனுமதிக்கிறது."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"camera\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"\"camera\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"\"connectedDevice\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"\"dataSync\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"location\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"\"location\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"\"mediaPlayback\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"\"mediaProjection\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"microphone\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"\"microphone\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"\"phoneCall\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"health\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"\"health\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"\"remoteMessaging\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ஆப்ஸ் சேமிப்பு இடத்தை அளவிடல்"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"ஆப்ஸ், அதன் குறியீடு, தரவு, மற்றும் தற்காலிகச் சேமிப்பு அளவுகளை மீட்டெடுக்க அனுமதிக்கிறது"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"சாதன அமைப்புகளை மாற்றுதல்"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"இந்த ஆப்ஸ் உபயோகத்தில் இருக்கும்போதே இதனால் மைக்ரோஃபோனைப் பயன்படுத்தி ஆடியோவை ரெக்கார்டு செய்ய முடியும்."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"பின்புலத்தில் ஆடியோ ரெக்கார்டு செய்தல்"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"இந்த ஆப்ஸால் எப்போது வேண்டுமானாலும் மைக்ரோஃபோனைப் பயன்படுத்தி ஆடியோவை ரெக்கார்டு செய்ய முடியும்."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ஆப்ஸ் சாளரங்களில் திரையைப் படமெடுத்தலைக் கண்டறிதல்"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"இந்த ஆப்ஸ் பயன்பாட்டில் இருக்கும்போது ஸ்கிரீன்ஷாட் எடுக்கப்பட்டால் ஆப்ஸுக்கு அறிவிக்கப்படும்."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"கட்டளைகளை சிம்மிற்கு அனுப்புதல்"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"சிம் க்குக் கட்டளைகளை அனுப்ப ஆப்ஸை அனுமதிக்கிறது. இது மிகவும் ஆபத்தானதாகும்."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"உடல் செயல்பாட்டைக் கண்டறிதல்"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"உங்கள் பகிர்ந்த சேமிப்பகத்திலுள்ள வீடியோ ஃபைல்களைப் படிக்க ஆப்ஸை அனுமதிக்கும்."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"பகிர்ந்த சேமிப்பகத்திலுள்ள பட ஃபைல்களைப் படித்தல்"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"உங்கள் பகிர்ந்த சேமிப்பகத்திலுள்ள பட ஃபைல்களைப் படிக்க ஆப்ஸை அனுமதிக்கும்."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"பகிர்ந்த சேமிப்பகத்தில் இருந்து பயனர் தேர்ந்தெடுக்கும் பட/வீடியோ ஃபைல்களைப் படித்தல்"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"உங்கள் பகிர்ந்த சேமிப்பகத்தில் இருந்து நீங்கள் தேர்ந்தெடுக்கும் பட/வீடியோ ஃபைல்களைப் படிக்க ஆப்ஸை அனுமதிக்கும்."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"பகிர்ந்த சேமிப்பகத்தின் உள்ளடக்கங்களை மாற்றும் அல்லது நீக்கும்"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"பகிர்ந்த சேமிப்பகத்தின் உள்ளடக்கத்தில் மாற்றங்களைச் செய்ய அனுமதிக்கும்."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP அழைப்புகளைச் செய்தல்/பெறுதல்"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"நிறுவல் நீக்கு"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"பரவாயில்லை, திற"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"தீங்கிழைக்கும் ஆப்ஸ் உள்ளது"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"சாதனப் பதிவுகள் அனைத்தையும் <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> அணுக அனுமதிக்கவா?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"ஒருமுறை அணுகலை அனுமதி"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"அனுமதிக்க வேண்டாம்"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"உங்கள் சாதனத்தில் நடப்பவற்றைச் சாதனப் பதிவுகள் ரெக்கார்டு செய்யும். சிக்கல்களைக் கண்டறிந்து சரிசெய்ய ஆப்ஸ் இந்தப் பதிவுகளைப் பயன்படுத்தலாம்.\n\nபாதுகாக்கப்பட வேண்டிய தகவல்கள் சில பதிவுகளில் இருக்கக்கூடும் என்பதால் சாதனப் பதிவுகள் அனைத்தையும் அணுக நீங்கள் நம்பும் ஆப்ஸை மட்டும் அனுமதிக்கவும். \n\nசாதனப் பதிவுகள் அனைத்தையும் அணுக இந்த ஆப்ஸை அனுமதிக்கவில்லை என்றாலும் அதற்குச் சொந்தமான பதிவுகளை அதனால் அணுக முடியும். உங்கள் சாதனத்திலுள்ள சில பதிவுகளையோ தகவல்களையோ சாதன உற்பத்தியாளரால் தொடர்ந்து அணுக முடியும்."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"உங்கள் சாதனத்தில் நடப்பவற்றைச் சாதனப் பதிவுகள் ரெக்கார்டு செய்யும். சிக்கல்களைக் கண்டறிந்து சரிசெய்ய ஆப்ஸால் இந்தப் பதிவுகளைப் பயன்படுத்த முடியும்.\n\nபாதுகாக்கப்பட வேண்டிய தகவல்கள், சில பதிவுகளில் இருக்கக்கூடும் என்பதால் சாதனப் பதிவுகள் அனைத்தையும் அணுக உங்களுக்கு நம்பகமான ஆப்ஸை மட்டும் அனுமதிக்கவும். \n\nசாதனப் பதிவுகள் அனைத்தையும் அணுக இந்த ஆப்ஸை நீங்கள் அனுமதிக்கவில்லை என்றாலும் அதற்குச் சொந்தமான பதிவுகளை அதனால் அணுக முடியும். உங்கள் சாதனத்திலுள்ள சில பதிவுகளையோ தகவல்களையோ சாதன உற்பத்தியாளரால் தொடர்ந்து அணுக முடியும்.\n\n மேலும் அறிந்துகொள்ள g.co/android/devicelogs இணைப்பிற்குச் செல்லுங்கள்."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"மீண்டும் காட்டாதே"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_2">%2$s</xliff:g> ஆப்ஸின் விழிப்பூட்டல்களைக் காண்பிக்க, <xliff:g id="APP_0">%1$s</xliff:g> அனுமதி கேட்கிறது"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"திருத்து"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"அழைப்புகள் மற்றும் அறிவிப்புகளுக்கு அதிரும்"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"உங்கள் <xliff:g id="DEVICE">%1$s</xliff:g> சாதனத்திலிருந்து மொபைலின் கேமராவை அணுக முடியாது"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"உங்கள் <xliff:g id="DEVICE">%1$s</xliff:g> சாதனத்திலிருந்து டேப்லெட்டின் கேமராவை அணுக முடியாது"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"ஸ்ட்ரீமின்போது இதை அணுக முடியாது. அதற்குப் பதிலாக உங்கள் மொபைலில் பயன்படுத்திப் பார்க்கவும்."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"ஸ்ட்ரீம் செய்யும்போது பிக்ச்சர்-இன்-பிக்ச்சர் அம்சத்தைப் பயன்படுத்த முடியாது"</string>
     <string name="system_locale_title" msgid="711882686834677268">"சிஸ்டத்தின் இயல்பு"</string>
     <string name="default_card_name" msgid="9198284935962911468">"கார்டு <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index eff08bc..5355f6f 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"వాయిస్ మెయిల్"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"కనెక్షన్ సమస్య లేదా చెల్లని MMI కోడ్."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ప్రస్తుత మొబైల్ నెట్‌వర్క్‌లో ఫీచర్ సపోర్ట్ చేయడం లేదు."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"చర్య స్థిరమైన డయలింగ్ నంబర్‌లకు మాత్రమే పరిమితం చేయబడింది."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"మీరు రోమింగ్‌లో ఉన్నప్పుడు మీ ఫోన్‌ నుండి కాల్ ఫార్వార్డింగ్ సెట్టింగ్‌లను మార్చలేరు."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"సేవ ప్రారంభించబడింది."</string>
@@ -72,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"కాలర్ ID ఆటోమేటిక్‌లపై పరిమితి లేదు. తర్వాత కాల్: పరిమితి లేదు"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"సేవ కేటాయించబడలేదు."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"మీరు కాలర్ ID సెట్టింగ్‌ను మార్చలేరు."</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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"యాప్‌, దాని భాగాలు మెమరీలో ఉండేలా చేయడానికి దానిని అనుమతిస్తుంది. ఇది ఇతర యాప్‌లకు అందుబాటులో ఉన్న మెమరీని ఆక్రమిస్తుంది, ఫోన్ నెమ్మదిగా పని చేస్తుంది."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"సేవని ముందు భాగంలో అమలు చేయడం"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"ముందు భాగంలో సేవలను ఉపయోగించడానికి యాప్‌ని అనుమతిస్తుంది."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"camera\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"\"camera\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించడానికి యాప్‌ను అనుమతిస్తుంది"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"\"connectedDevice\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించుకోవడానికి యాప్‌ను అనుమతిస్తుంది"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"\"dataSync\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించుకోవడానికి యాప్‌ను అనుమతిస్తుంది"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"location\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"\"location\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించుకోవడానికి యాప్‌ను అనుమతిస్తుంది"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"\"mediaPlayback\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించుకోవడానికి యాప్‌ను అనుమతిస్తుంది"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"\"mediaProjection\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించుకోవడానికి యాప్‌ను అనుమతిస్తుంది"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"microphone\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"\"microphone\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించుకోవడానికి యాప్‌ను అనుమతిస్తుంది"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"\"phoneCall\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించుకోవడానికి యాప్‌ను అనుమతిస్తుంది"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"health\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"\"health\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించుకోవడానికి యాప్‌ను అనుమతిస్తుంది"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"\"remoteMessaging\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించడానికి యాప్‌ను అనుమతిస్తుంది"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించడానికి యాప్‌ను అనుమతిస్తుంది"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించడానికి యాప్‌ను అనుమతిస్తుంది"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"యాప్ నిల్వ స్థలాన్ని అంచనా వేయడం"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"యాప్‌ కోడ్, డేటా మరియు కాష్ పరిమాణాలను తిరిగి పొందడానికి దాన్ని అనుమతిస్తుంది"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"సిస్టమ్ సెట్టింగ్‌లను మార్చడం"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"యాప్ ఉపయోగంలో ఉన్నపుడు మైక్రోఫోన్‌ను ఉపయోగించి ఈ యాప్, ఆడియోను రికార్డ్ చేయగలదు."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"బ్యాక్‌గ్రౌండ్‌లో ఆడియోను రికార్డ్ చేయగలదు"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"మైక్రోఫోన్‌ను ఉపయోగించి ఈ యాప్ ఎప్పుడైనా ఆడియోను రికార్డ్ చేయగలదు."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"యాప్ విండోలకు సంబంధించిన స్క్రీన్ క్యాప్చర్‌లను గుర్తించండి"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"యాప్ ఉపయోగంలో ఉన్నప్పుడు స్క్రీన్‌షాట్ తీయబడినప్పుడు ఈ యాప్‌కు తెలియజేయబడుతుంది."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIMకి ఆదేశాలను పంపడం"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"సిమ్‌కు ఆదేశాలను పంపడానికి యాప్‌ను అనుమతిస్తుంది. ఇది చాలా ప్రమాదకరం."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"భౌతిక కార్యాకలాపాన్ని గుర్తించండి"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"మీ షేర్ చేయబడిన స్టోరేజ్ నుండి వీడియో ఫైల్‌లను చదవడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"షేర్ చేయబడిన స్టోరేజ్ నుండి ఇమేజ్ ఫైల్‌లను చదవండి"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"మీ షేర్ చేయబడిన స్టోరేజ్ నుండి ఇమేజ్ ఫైల్‌లను చదవడానికి యాప్‌ను అనుమతిస్తుంది."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"షేర్ చేయబడిన స్టోరేజ్ నుండి యూజర్ ఎంచుకున్న ఇమేజ్ ఫైల్స్‌ను, వీడియో ఫైల్స్‌ను చదువుతుంది"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"మీరు మీ షేర్ చేయబడిన స్టోరేజ్ నుండి ఎంచుకున్న ఇమేజ్, వీడియో ఫైల్స్‌ను చదవడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"మీ షేర్ చేసిన నిల్వ యొక్క కంటెంట్‌లను ఎడిట్ చేయండి లేదా తొలగించండి"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"మీ షేర్ చేసిన నిల్వ యొక్క కంటెంట్‌లను రాయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP కాల్స్‌ను చేయడానికి/స్వీకరించడానికి"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"అన్ఇన్‌స్టాల్ చేయండి"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ఏదేమైనా తెరువు"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"హానికరమైన యాప్ గుర్తించబడింది"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"అన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>‌ను అనుమతించాలా?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"వన్-టైమ్ యాక్సెస్‌ను అనుమతించండి"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"అనుమతించవద్దు"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"మీ పరికరంలో జరిగే దాన్ని పరికర లాగ్‌లు రికార్డ్ చేస్తాయి. సమస్యలను కనుగొని, పరిష్కరించడానికి యాప్‌లు ఈ లాగ్‌లను ఉపయోగిస్తాయి.\n\nకొన్ని లాగ్‌లలో గోప్యమైన సమాచారం ఉండవచ్చు, కాబట్టి మీరు విశ్వసించే యాప్‌లను మాత్రమే అన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి అనుమతించండి. \n\nఅన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి మీరు ఈ యాప్‌ను అనుమతించకపోతే, అది తన స్వంత లాగ్‌లను ఇప్పటికి యాక్సెస్ చేయగలదు. మీ పరికర తయారీదారు ఇప్పటికీ మీ పరికరంలో కొన్ని లాగ్‌లు లేదా సమాచారాన్ని యాక్సెస్ చేయగలరు."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"మీ పరికరంలో జరిగే దాన్ని పరికర లాగ్‌లు రికార్డ్ చేస్తాయి. సమస్యలను కనుగొని, పరిష్కరించడానికి యాప్‌లు ఈ లాగ్‌లను ఉపయోగిస్తాయి.\n\nకొన్ని లాగ్‌లలో గోప్యమైన సమాచారం ఉండవచ్చు, కాబట్టి మీరు విశ్వసించే యాప్‌లను మాత్రమే అన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి అనుమతించండి. \n\nఅన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి మీరు ఈ యాప్‌ను అనుమతించకపోతే, అది తన స్వంత లాగ్‌లను ఇప్పటికి యాక్సెస్ చేయగలదు. మీ పరికర తయారీదారు ఇప్పటికీ మీ పరికరంలో కొన్ని లాగ్‌లు లేదా సమాచారాన్ని యాక్సెస్ చేయగలరు.\n\ng.co/android/devicelogsలో దీని గురించి మరింత తెలుసుకోండి."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"మళ్లీ చూపవద్దు"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> <xliff:g id="APP_2">%2$s</xliff:g> స్లైస్‌లను చూపించాలనుకుంటోంది"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ఎడిట్ చేయండి"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"కాల్స్‌ మరియు నోటిఫికేషన్‌లు వైబ్రేట్ అవుతాయి"</string>
@@ -2071,7 +2096,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>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"మీ <xliff:g id="DEVICE">%1$s</xliff:g> నుండి ఫోన్ కెమెరాను యాక్సెస్ చేయడం సాధ్యపడదు"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"మీ <xliff:g id="DEVICE">%1$s</xliff:g> నుండి టాబ్లెట్ కెమెరాను యాక్సెస్ చేయడం సాధ్యపడదు"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"స్ట్రీమింగ్ చేస్తున్నప్పుడు దీన్ని యాక్సెస్ చేయడం సాధ్యపడదు. బదులుగా మీ ఫోన్‌లో ట్రై చేయండి."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"స్ట్రీమింగ్ చేస్తున్నప్పుడు పిక్చర్-ఇన్-పిక్చర్ చూడలేరు"</string>
     <string name="system_locale_title" msgid="711882686834677268">"సిస్టమ్ ఆటోమేటిక్ సెట్టింగ్"</string>
     <string name="default_card_name" msgid="9198284935962911468">"కార్డ్ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
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-television/styles.xml b/core/res/res/values-television/styles.xml
deleted file mode 100644
index ad9140c..0000000
--- a/core/res/res/values-television/styles.xml
+++ /dev/null
@@ -1,21 +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.
-  -->
-<resources>
-    <style name="PermissionGrantButtonTop"
-        parent="@style/Widget.Leanback.Button.ButtonBarGravityStart" />
-    <style name="PermissionGrantButtonBottom"
-        parent="@style/Widget.Leanback.Button.ButtonBarGravityStart" />
-</resources>
\ No newline at end of file
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 72cbc36..1bdaa50 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"ข้อความเสียง"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"ปัญหาการเชื่อมต่อหรือรหัส MMI ไม่ถูกต้อง"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"ไม่รองรับฟีเจอร์"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"การดำเนินการถูกจำกัดไว้ที่การจำกัดหมายเลขโทรออกเท่านั้น"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"ไม่สามารถเปลี่ยนการตั้งค่าการโอนสายจากโทรศัพท์ในขณะที่โรมมิ่ง"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"เปิดใช้งานบริการแล้ว"</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"อนุญาตให้แอปพลิเคชันทำให้ส่วนหนึ่งของตัวเองคงอยู่ถาวรในหน่วยความจำ ซึ่งจะจำกัดพื้นที่หน่วยความจำที่ใช้งานได้ของแอปพลิเคชันอื่นๆ และทำให้โทรศัพท์ทำงานช้าลง"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"เรียกใช้บริการที่ใช้งานอยู่"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ใช้งานอยู่"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"กล้อง\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"กล้อง\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"อุปกรณ์ที่เชื่อมต่อ\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"อุปกรณ์ที่เชื่อมต่อ\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การซิงค์ข้อมูล\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การซิงค์ข้อมูล\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"ตำแหน่ง\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"ตำแหน่ง\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การเล่นสื่อ\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การเล่นสื่อ\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การฉายภาพสื่อ\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การฉายภาพสื่อ\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"ไมโครโฟน\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"ไมโครโฟน\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การโทร\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การโทร\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"สุขภาพ\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"สุขภาพ\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การรับส่งข้อความระยะไกล\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การรับส่งข้อความระยะไกล\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"ได้รับการยกเว้นจากระบบ\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"ได้รับการยกเว้นจากระบบ\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การใช้งานพิเศษ\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การใช้งานพิเศษ\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"วัดพื้นที่เก็บข้อมูลของแอปพลิเคชัน"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"อนุญาตให้แอปพลิเคชันเรียกดูรหัส ข้อมูล และขนาดแคชของตน"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"แก้ไขการตั้งค่าระบบ"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"แอปนี้บันทึกเสียงด้วยไมโครโฟนขณะที่มีการใช้แอปได้"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"บันทึกเสียงในเบื้องหลัง"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"แอปนี้บันทึกเสียงด้วยไมโครโฟนได้ทุกเมื่อ"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ตรวจจับการจับภาพหน้าจอของหน้าต่างแอป"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"แอปนี้จะได้รับการแจ้งเตือนเมื่อมีการจับภาพหน้าจอขณะใช้งานแอป"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"ส่งคำสั่งไปยังซิม"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"อนุญาตให้แอปส่งคำสั่งไปยัง SIM ซึ่งอันตรายมาก"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"จดจำกิจกรรมการเคลื่อนไหวร่างกาย"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"อนุญาตให้แอปอ่านไฟล์วิดีโอจากพื้นที่เก็บข้อมูลที่แชร์"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"อ่านไฟล์ภาพจากพื้นที่เก็บข้อมูลที่แชร์"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"อนุญาตให้แอปอ่านไฟล์ภาพจากพื้นที่เก็บข้อมูลที่แชร์"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"อ่านไฟล์รูปภาพและวิดีโอที่ผู้ใช้เลือกจากพื้นที่เก็บข้อมูลที่ใช้ร่วมกัน"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"อนุญาตให้แอปอ่านไฟล์รูปภาพและวิดีโอที่คุณเลือกจากพื้นที่เก็บข้อมูลที่ใช้ร่วมกัน"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"แก้ไขหรือลบเนื้อหาในพื้นที่จัดเก็บข้อมูลที่ใช้ร่วมกัน"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"อนุญาตให้แอปเขียนเนื้อหาในพื้นที่จัดเก็บข้อมูลที่ใช้ร่วมกัน"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"โทร/รับสาย SIP"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ถอนการติดตั้ง"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"เปิดต่อไป"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"ตรวจพบแอปที่เป็นอันตราย"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"อนุญาตให้ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> เข้าถึงบันทึกทั้งหมดของอุปกรณ์ใช่ไหม"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"อนุญาตสิทธิ์เข้าถึงแบบครั้งเดียว"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ไม่อนุญาต"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"บันทึกของอุปกรณ์เก็บข้อมูลสิ่งที่เกิดขึ้นในอุปกรณ์ แอปสามารถใช้บันทึกเหล่านี้เพื่อค้นหาและแก้ไขปัญหา\n\nบันทึกบางรายการอาจมีข้อมูลที่ละเอียดอ่อน คุณจึงควรอนุญาตเฉพาะแอปที่เชื่อถือได้ให้เข้าถึงบันทึกทั้งหมดของอุปกรณ์ \n\nหากคุณไม่อนุญาตให้แอปนี้เข้าถึงบันทึกทั้งหมดของอุปกรณ์ แอปจะยังเข้าถึงบันทึกของตัวเองได้อยู่ ผู้ผลิตอุปกรณ์อาจยังเข้าถึงบันทึกหรือข้อมูลบางรายการในอุปกรณ์ของคุณได้"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"บันทึกของอุปกรณ์เก็บข้อมูลสิ่งที่เกิดขึ้นในอุปกรณ์ แอปสามารถใช้บันทึกเหล่านี้เพื่อค้นหาและแก้ไขปัญหา\n\nบันทึกบางรายการอาจมีข้อมูลที่ละเอียดอ่อน คุณจึงควรอนุญาตเฉพาะแอปที่เชื่อถือได้ให้เข้าถึงบันทึกทั้งหมดของอุปกรณ์\n\nหากคุณไม่อนุญาตให้แอปนี้เข้าถึงบันทึกทั้งหมดของอุปกรณ์ แอปจะยังเข้าถึงบันทึกของตัวเองได้อยู่ ผู้ผลิตอุปกรณ์อาจยังเข้าถึงบันทึกหรือข้อมูลบางรายการในอุปกรณ์ได้\n\nดูข้อมูลเพิ่มเติมที่ g.co/android/devicelogs"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ไม่ต้องแสดงอีก"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ต้องการแสดงส่วนต่างๆ ของ <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"แก้ไข"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"สายเรียกเข้าและการแจ้งเตือนจะสั่น"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"เข้าถึงกล้องของโทรศัพท์จาก <xliff:g id="DEVICE">%1$s</xliff:g> ไม่ได้"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"เข้าถึงกล้องของแท็บเล็ตจาก <xliff:g id="DEVICE">%1$s</xliff:g> ไม่ได้"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"เข้าถึงเนื้อหานี้ไม่ได้ขณะที่สตรีมมิง โปรดลองเข้าถึงในโทรศัพท์แทน"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"ดูการแสดงภาพซ้อนภาพขณะสตรีมไม่ได้"</string>
     <string name="system_locale_title" msgid="711882686834677268">"ค่าเริ่มต้นของระบบ"</string>
     <string name="default_card_name" msgid="9198284935962911468">"ซิมการ์ด <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index e66999d..bb17eb9 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Voicemail"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Problema sa koneksyon o di-wastong MMI code."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Hindi sinusuportahan ang feature."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Pinaghihigpitan ang pagpapatakbo sa mga fixed dialing number lang."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Hindi maaaring baguhin ang mga setting ng pagpapasa ng tawag mula sa iyong telepono habang naka-roaming ka."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Pinagana ang serbisyo."</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Pinapayagan ang app na panatilihin ang ilang bahagi nito sa memory. Maaari nitong limitahan ang memory na available sa iba pang apps na nagpapabagal sa telepono."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"paganahin ang foreground na serbisyo"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Payagan ang app na gamitin ang mga foreground na serbisyo."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"magpagana ng serbisyo sa foreground na may uring \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"magpagana ng serbisyo sa foreground na may uring \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"magpagana ng serbisyo sa foreground na may uring \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"magpagana ng serbisyo sa foreground na may uring \"lokasyon\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"lokasyon\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"magpagana ng serbisyo sa foreground na may uring \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"magpagana ng serbisyo sa foreground na may uring \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Nagbibigay-daan sa na gamitin ang mga serbisyo sa foreground na may uring \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"magpagana ng serbisyo sa foreground na may uring \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"magpagana ng serbisyo sa foreground gamit na may uring \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"magpagana ng serbisyo sa foreground na may uring \"kalusugan\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"kalusugan\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"magpagana ng serbisyo sa foreground na may uring \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"magpagana ng serbisyo sa foreground na may uring \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"magpagana ng serbisyo sa foreground na may uring \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"sukatin ang espasyo ng storage ng app"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Pinapayagan ang app na bawiin ang code, data, at mga laki ng cache nito"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"baguhin ang mga setting ng system"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Makakapag-record ng audio ang app na ito gamit ang mikropono habang ginagamit ang app."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"mag-record ng audio sa background"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Makakapag-record ng audio ang app na ito gamit ang mikropono anumang oras."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"i-detect ang mga pag-screen capture ng mga window ng app"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Maaabisuhan ang app na ito kapag may kinuhang screenshot habang ginagamit ang app."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"magpadala ng mga command sa SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Pinapahintulutang magpadala ang app ng mga command sa SIM. Napakapanganib nito."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"tukuyin ang pisikal na aktibidad"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Nagbibigay-daan sa app na magbasa ng mga video file mula sa iyong nakabahaging storage."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"magbasa ng mga image file mula sa nakabahaging storage"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Nagbibigay-daan sa app na magbasa ng mga image file mula sa iyong nakabahaging storage."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"magbasa ng mga image at video file na pinili ng user mula sa nakabahaging storage"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Nagbibigay-daan sa app na magbasa ng mga image at video file na pipiliin mo mula sa iyong nakabahaging storage."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"baguhin o i-delete ang content ng nakabahagi mong storage"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Pinapayagan ang app na mag-write sa content ng nakabahagi mong storage."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"magsagawa/tumanggap ng mga tawag sa SIP"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"I-UNINSTALL"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"BUKSAN PA RIN"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"May na-detect na mapaminsalang app"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Payagan ang <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> na i-access ang lahat ng log ng device?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Payagan ang isang beses na pag-access"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Huwag payagan"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Nire-record ng mga log ng device kung ano ang nangyayari sa iyong device. Magagamit ng mga app ang mga log na ito para maghanap at mag-ayos ng mga isyu.\n\nPosibleng maglaman ang ilang log ng sensitibong impormasyon, kaya ang mga app lang na pinagkakatiwalaan mo ang payagang maka-access sa lahat ng log ng device. \n\nKung hindi mo papayagan ang app na ito na i-access ang lahat ng log ng device, maa-access pa rin nito ang mga sarili nitong log. Posible pa ring ma-access ng manufacturer ng iyong device ang ilang log o impormasyon sa device mo."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Nire-record ng mga log ng device kung ano ang nangyayari sa iyong device. Magagamit ng mga app ang mga log na ito para maghanap at mag-ayos ng mga isyu.\n\nPosibleng maglaman ang ilang log ng sensitibong impormasyon, kaya ang mga app lang na pinagkakatiwalaan mo ang payagang maka-access sa lahat ng log ng device. \n\nKung hindi mo papayagan ang app na ito na i-access ang lahat ng log ng device, maa-access pa rin nito ang mga sarili nitong log. Posible pa ring ma-access ng manufacturer ng iyong device ang ilang log o impormasyon sa device mo.\n\nMatuto pa sa g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Huwag ipakita ulit"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Gustong ipakita ng <xliff:g id="APP_0">%1$s</xliff:g> ang mga slice ng <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"I-edit"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Magva-vibrate ang mga tawag at notification"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Hindi ma-access ang camera ng telepono mula sa iyong <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Hindi ma-access ang camera ng tablet mula sa iyong <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Hindi ito puwedeng i-access habang nagsi-stream. Subukan na lang sa iyong telepono."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Hindi matingnan nang picture-in-picture habang nagsi-stream"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Default ng system"</string>
     <string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index b6c4b4a..3e974e3 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Sesli Mesaj"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Bağlantı sorunu veya geçersiz MMI kodu."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Özellik desteklenmiyor."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"İşlem sadece sabit arama numaralarıyla sınırlandırılmıştır."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Dolaşımdayken telefonunuzdan çağrı yönlendirme ayarları değiştirilemiyor."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Hizmet etkindi."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Veriler şuraya aktarıldı: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Bunu istediğiniz zaman Ayarlar\'da değiştirebilirsiniz"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Uygulamaya kendisinin bir bölümünü bellekte kalıcı yapma izni verir. Bu izin, diğer uygulamaların kullanabileceği belleği sınırlandırarak telefonun yavaş çalışmasına neden olabilir."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"ön plan hizmetini çalıştırma"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Uygulamanın ön plan hizmetlerinden faydalanmasına izin verir."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"camera\" türüyle ön plan hizmetini çalıştırma"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Uygulamanın \"camera\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"\"connectedDevice\" türüyle ön plan hizmetini çalıştırma"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Uygulamanın \"connectedDevice\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"\"dataSync\" türüyle ön plan hizmetini çalıştırma"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Uygulamanın \"dataSync\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"location\" türüyle ön plan hizmetini çalıştırma"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Uygulamanın \"location\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"\"mediaPlayback\" türüyle ön plan hizmetini çalıştırma"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Uygulamanın \"mediaPlayback\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"\"mediaProjection\" türüyle ön plan hizmetini çalıştırma"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Uygulamanın \"mediaProjection\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"microphone\" türüyle ön plan hizmetini çalıştırma"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Uygulamanın \"microphone\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"\"phoneCall\" türüyle ön plan hizmetini çalıştırma"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Uygulamanın \"phoneCall\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"health\" türüyle ön plan hizmetini çalıştırma"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Uygulamanın \"health\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"\"remoteMessaging\" türüyle ön plan hizmetini çalıştırma"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Uygulamanın \"remoteMessaging\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"\"systemExempted\" türüyle ön plan hizmetini çalıştırma"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Uygulamanın \"systemExempted\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" türüyle ön plan hizmetini çalıştırma"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Uygulamanın \"specialUse\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"uygulama depolama alanını ölç"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Uygulamaya kodunu, verilerini ve önbellek boyutlarını alma izni verir"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"sistem ayarlarını değiştirme"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Bu uygulama, kullanıldığı sırada mikrofonu kullanarak ses kaydedebilir."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"arka planda ses kaydeder"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Bu uygulama, herhangi bir zaman mikrofonu kullanarak ses kaydedebilir."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"uygulama pencerelerindeki ekran görüntülerini algılama"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Uygulama kullanılırken ekran görüntüsü alındığında bu uygulama bilgilendirilir."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIM karta komut gönderme"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Uygulamanın SIM karta komut göndermesine izin verir. Bu izin çok tehlikelidir."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"fiziksel aktiviteyi algıla"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Uygulamaya, paylaşılan depolama alanınızdaki video dosyalarını okuma izni verir."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"paylaşılan depolama alanınızdaki resim dosyalarını okuma"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Uygulamaya, paylaşılan depolama alanınızdaki resim dosyalarını okuma izni verir."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"kullanıcının, paylaşılan depolama alanından seçtiği resim ve video dosyalarını okuma"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Uygulamaya, paylaşılan depolama alanınızdan seçtiğiniz resim ve video dosyalarını okuma izni verir."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"paylaşılan depolama alanımın içeriğini değiştir veya sil"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Uygulamanın paylaşılan depolama alanınıza içerik yazmasına izin verir."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP aramaları yapma/alma"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"YÜKLEMEYİ KALDIR"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"YİNE DE AÇ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Zararlı uygulama tespit edildi"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> uygulamasının tüm cihaz günlüklerine erişmesine izin verilsin mi?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Tek seferlik erişim izni ver"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"İzin verme"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Cihaz günlükleri, cihazınızda olanları kaydeder. Uygulamalar, sorunları bulup düzeltmek için bu günlükleri kullanabilir.\n\nBazı günlükler hassas bilgiler içerebileceği için yalnızca güvendiğiniz uygulamaların tüm cihaz günlüklerine erişmesine izin verin. \n\nBu uygulamanın tüm cihaz günlüklerine erişmesine izin vermeseniz de kendi günlüklerine erişmeye devam edebilir. Ayrıca, cihaz üreticiniz de cihazınızdaki bazı günlüklere veya bilgilere erişmeye devam edebilir."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Cihaz günlükleri, cihazınızda olanları kaydeder. Uygulamalar, sorunları bulup düzeltmek için bu günlükleri kullanabilir.\n\nBazı günlükler hassas bilgiler içerebileceği için yalnızca güvendiğiniz uygulamaların tüm cihaz günlüklerine erişmesine izin verin. \n\nBu uygulamanın tüm cihaz günlüklerine erişmesine izin vermeseniz de kendi günlüklerine erişmeye devam edebilir. Ayrıca, cihaz üreticiniz de cihazınızdaki bazı günlüklere veya bilgilere erişmeye devam edebilir.\n\nDaha fazla bilgiyi g.co/android/devicelogs sayfasında bulabilirsiniz."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Bir daha gösterme"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> uygulaması, <xliff:g id="APP_2">%2$s</xliff:g> dilimlerini göstermek istiyor"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Düzenle"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Aramalar ve bildirimler titreşim yapacak"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan telefonun kamerasına erişilemiyor"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan tabletin kamerasına erişilemiyor"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Canlı oynatılırken bu içeriğe erişilemez. Bunun yerine telefonunuzu kullanmayı deneyin."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Yayın sırasında pencere içinde pencere görüntülenemez"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Sistem varsayılanı"</string>
     <string name="default_card_name" msgid="9198284935962911468">"KART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index c408c51..528575b 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Голосова пошта"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Пробл. підключення чи недійсний код MMI."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функція не підтримується."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Операція лише для номерів фіксованого набору."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"У роумінгу на телефоні не можна змінити налаштування переадресації викликів."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Послугу ввімкнено."</string>
@@ -74,6 +75,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>
@@ -395,6 +398,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Дозволяє програмі робити свої частини сталими в пам’яті. Це може зменшувати обсяг пам’яті, доступної для інших програм, і сповільнювати роботу телефону."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"запускати пріоритетну службу"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Додаток може використовувати пріоритетні служби."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"запускати сервіс типу camera в активному режимі"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Дозволяє додатку використовувати активні сервіси типу camera"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"запускати сервіс типу connectedDevice в активному режимі"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Дозволяє додатку використовувати активні сервіси типу connectedDevice"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"запускати сервіс типу dataSync в активному режимі"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Дозволяє додатку використовувати активні сервіси типу dataSync"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"запускати сервіс типу location в активному режимі"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Дозволяє додатку використовувати активні сервіси типу location"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"запускати сервіс типу mediaPlayback в активному режимі"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Дозволяє додатку використовувати активні сервіси типу mediaPlayback"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"запускати сервіс типу mediaProjection в активному режимі"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Дозволяє додатку використовувати активні сервіси типу mediaProjection"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"запускати сервіс типу microphone в активному режимі"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Дозволяє додатку використовувати активні сервіси типу microphone"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"запускати сервіс типу phoneCall в активному режимі"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Дозволяє додатку використовувати активні сервіси типу phoneCall"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"запускати сервіс типу health в активному режимі"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Дозволяє додатку використовувати активні сервіси типу health"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"запускати сервіс типу remoteMessaging в активному режимі"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Дозволяє додатку використовувати активні сервіси типу remoteMessaging"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"запускати сервіс типу systemExempted в активному режимі"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Дозволяє додатку використовувати активні сервіси типу systemExempted"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"запускати сервіс типу specialUse в активному режимі"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Дозволяє додатку використовувати активні сервіси типу specialUse"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"визначати об’єм пам’яті програми"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Дозволяє програмі отримувати її код, дані та розміри кеш-пам’яті"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"змінювати налаштування системи"</string>
@@ -447,6 +474,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Цей додаток може записувати звук за допомогою мікрофона, коли ви використовуєте його."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"записувати звук у фоновому режимі"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Цей додаток може будь-коли записувати звук за допомогою мікрофона."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"виявляти знімки екрана вікон додатка"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Цей додаток отримуватиме сповіщення, коли під час його роботи створюватимуться знімки екрана."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"надсилати команди на SIM-карту"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Дозволяє програмі надсилати команди на SIM-карту. Це дуже небезпечно."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"розпізнавати фізичну активність"</string>
@@ -698,6 +727,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Дозволяє додатку зчитувати відеофайли з вашого спільного сховища."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"зчитувати файли зображень зі спільного сховища"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Дозволяє додатку зчитувати файли зображень із вашого спільного сховища."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"зчитувати вибрані користувачем файли зображень і відео зі спільного сховища"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Дозволяє додатку зчитувати вибрані вами файли зображень і відео зі спільного сховища."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"змінювати чи видаляти вміст у спільній пам’яті"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Додаток може писати вміст у спільній пам’яті."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"здійснювати й отримувати дзвінки через протокол SIP"</string>
@@ -1163,8 +1194,8 @@
     <string name="no" msgid="5122037903299899715">"Скасувати"</string>
     <string name="dialog_alert_title" msgid="651856561974090712">"Увага"</string>
     <string name="loading" msgid="3138021523725055037">"Завантаження..."</string>
-    <string name="capital_on" msgid="2770685323900821829">"УВІМК"</string>
-    <string name="capital_off" msgid="7443704171014626777">"ВИМК"</string>
+    <string name="capital_on" msgid="2770685323900821829">"УВІМКНЕНО"</string>
+    <string name="capital_off" msgid="7443704171014626777">"ВИМКНЕНО"</string>
     <string name="checked" msgid="9179896827054513119">"вибрано"</string>
     <string name="not_checked" msgid="7972320087569023342">"не вибрано"</string>
     <string name="selected" msgid="6614607926197755875">"вибрано"</string>
@@ -2048,12 +2079,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ВИДАЛИТИ"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"УСЕ ОДНО ВІДКРИТИ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Виявлено шкідливий додаток"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Надати додатку <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> доступ до всіх журналів пристрою?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Надати доступ лише цього разу"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Не дозволяти"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"У журналах пристрою реєструється все, що відбувається на ньому. За допомогою цих журналів додатки можуть виявляти й усувати проблеми.\n\nДеякі журнали можуть містити конфіденційні дані, тому надавати доступ до всіх журналів пристрою слід лише надійним додаткам. \n\nЯкщо додаток не має доступу до всіх журналів пристрою, він усе одно може використовувати власні журнали. Виробник вашого пристрою все одно може використовувати деякі журнали чи інформацію на ньому."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"У журналах пристрою реєструється все, що відбувається на ньому. За допомогою цих журналів додатки можуть виявляти й усувати проблеми.\n\nДеякі журнали можуть містити конфіденційні дані, тому надавати доступ до всіх журналів пристрою слід лише надійним додаткам. \n\nЯкщо додаток не має доступу до всіх журналів пристрою, він усе одно може використовувати власні журнали. Виробник вашого пристрою все одно може використовувати деякі журнали чи інформацію на ньому.\n\nДокладніше: g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Більше не показувати"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> хоче показати фрагменти додатка <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Редагувати"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Вібросигнал для викликів і сповіщень увімкнено"</string>
@@ -2294,6 +2319,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не вдається отримати доступ до камери телефона з пристрою <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Не вдається отримати доступ до камери планшета з пристрою <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Цей контент недоступний під час потокового передавання. Спробуйте натомість скористатися телефоном."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Ви не можете переглядати картинку в картинці під час трансляції"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Налаштування системи за умовчанням"</string>
     <string name="default_card_name" msgid="9198284935962911468">"КАРТКА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 7784431..9d15d64 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"صوتی میل"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"‏کنکشن مسئلہ یا غلط MMI کوڈ۔"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"خصوصیت تعاون یافتہ نہیں ہے۔"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"آپریشن صرف متعین ڈائلنگ نمبرز تک محدود ہے۔"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"جب آپ رومنگ پر ہوں تو اپنے فون سے کال فارورڈنگ کی ترتیبات تبدیل نہیں کی جا سکتیں۔"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"سروس فعال کی گئی۔"</string>
@@ -72,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"‏کالر ID کی ڈیفالٹ ترتیب غیر محدود کردہ ہے۔ اگلی کال: غیر محدود کردہ"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"سروس فراہم نہیں کی گئی۔"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"‏آپ کالر ID کی ترتیبات تبدیل نہیں کر سکتے ہیں۔"</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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ایپ کو خود اپنے ہی حصوں کو میموری میں استقلال پذیر بنانے کی اجازت دیتا ہے۔ یہ فون کو سست بناکر دوسری ایپس کیلئے دستیاب میموری کو محدود کرسکتا ہے۔"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"پیش منظر سروس چلائیں"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"ایپ کو پیش منظر سروسز کے استعمال کی اجازت دیتا ہے۔"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"\"کیمرا\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"ایپ کو \"کیمرا\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"‏\"connectedDevice\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"‏ایپ کو \"connectedDevice\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"‏\"dataSync\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"‏ایپ کو \"dataSync\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"\"مقام\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"ایپ کو \"مقام\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"‏\"mediaPlayback\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"‏ایپ کو \"mediaPlayback\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"‏\"mediaProjection\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"‏ایپ کو \"mediaProjection\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"\"مائیکروفون\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"ایپ کو \"مائیکروفون\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"‏\"phoneCall\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"‏ایپ کو \"phoneCall\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"\"صحت\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"ایپ کو \"صحت\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"‏\"remoteMessaging\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"‏ایپ کو \"remoteMessaging\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"‏\"systemExempted\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"‏ایپ کو \"systemExempted\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"‏\"specialUse\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"‏ایپ کو \"specialUse\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ایپ اسٹوریج کی جگہ کی پیمائش کریں"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"ایپ کو اپنے کوڈ، ڈیٹا اور کیش کے سائزوں کی بازیافت کرنے دیتا ہے"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"سسٹم کی ترتیبات میں ترمیم کریں"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"ایپ کے استعمال ہونے کے دوران یہ ایپ مائیکروفون استعمال کرتے ہوئے آڈیو ریکارڈ کر سکتی ہے۔"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"پس منظر میں آڈیو ریکارڈ کریں"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"یہ ایپ کسی بھی وقت مائیکروفون استعمال کرتے ہوئے آڈیو ریکارڈ کر سکتی ہے۔"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ایپ کی ونڈوز کے اسکرین کیپچرز کا پتہ لگائیں"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"اس ایپ کے استعمال کے دوران اسکرین شاٹ لینے پر اس ایپ کو مطلع کیا جائے گا۔"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"‏SIM کو ہدایات بھیجیں"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"‏ایپ کو SIM کو کمانڈز بھیجنے کی اجازت دیتا ہے۔ یہ بہت خطرناک ہے۔"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"جسمانی سرگرمی کی شناخت کریں"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"ایپ کو آپ کی اشتراک کردہ اسٹوریج سے ویڈیو فائلز کو پڑھنے کی اجازت دیتا ہے۔"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"اشتراک کردہ اسٹوریج سے تصویری فائلز کو پڑھیں"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"ایپ کو آپ کی اشتراک کردہ اسٹوریج سے تصویری فائلز کو پڑھنے کی اجازت دیتا ہے۔"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"مشترکہ اسٹوریج سے صارف کی منتخب کردہ تصویر اور ویڈیو فائلز کو پڑھیں"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"ایپ کو ان تصویر اور ویڈیو فائلز کو پڑھنے کی اجازت دیتی ہے جنہیں آپ اپنے مشترکہ اسٹوریج سے منتخب کرتے ہیں۔"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"اپنے اشتراک کردہ اسٹوریج کے مواد میں ترمیم کریں یا اسے حذف کریں"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"ایپ کو آپ کے اشتراک کردہ اسٹوریج کے مواد کو لکھنے کی اجازت دیتا ہے۔"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"‏SIP کالز کریں/موصول کریں"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"اَن انسٹال کریں"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"بہر صورت کھولیں"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"ضرر رساں ایپ کا پتہ چلا"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> کو آلے کے تمام لاگز تک رسائی کی اجازت دیں؟"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"یک وقتی رسائی کی اجازت دیں"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"اجازت نہ دیں"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"آپ کے آلے پر جو ہوتا ہے آلے کے لاگز اسے ریکارڈ کر لیتے ہیں۔ ایپس ان لاگز کا استعمال مسائل کو تلاش کرنے اور ان کو حل کرنے کے لیے کر سکتی ہیں۔\n\nکچھ لاگز میں حساس معلومات شامل ہو سکتی ہیں، اس لیے صرف اپنے بھروسے مند ایپس کو ہی آلے کے تمام لاگز تک رسائی کی اجازت دیں۔ \n\nاگر آپ اس ایپ کو آلے کے تمام لاگز تک رسائی کی اجازت نہیں دیتے ہیں تب بھی یہ اپنے لاگز تک رسائی حاصل کر سکتی ہے۔ آپ کے آلے کا مینوفیکچرر اب بھی آپ کے آلے پر کچھ لاگز یا معلومات تک رسائی حاصل کر سکتا ہے۔"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"‏آپ کے آلے پر جو ہوتا ہے آلے کے لاگز اسے ریکارڈ کر لیتے ہیں۔ ایپس ان لاگز کا استعمال مسائل کو تلاش کرنے اور ان کو حل کرنے کے لیے کر سکتی ہیں۔\n\nکچھ لاگز میں حساس معلومات شامل ہو سکتی ہیں، اس لیے صرف اپنی بھروسے مند ایپس کو ہی آلے کے تمام لاگز تک رسائی کی اجازت دیں۔ \n\nاگر آپ اس ایپ کو آلے کے تمام لاگز تک رسائی کی اجازت نہیں دیتے ہیں تب بھی یہ اپنے لاگز تک رسائی حاصل کر سکتی ہے۔ آپ کے آلے کا مینوفیکچرر اب بھی آپ کے آلے پر کچھ لاگز یا معلومات تک رسائی حاصل کر سکتا ہے۔\n\ng.co/android/devicelogs پر مزید جانیں۔"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"دوبارہ نہ دکھائیں"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> <xliff:g id="APP_2">%2$s</xliff:g> کے سلائسز دکھانا چاہتی ہے"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ترمیم کریں"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"کالز اور اطلاعات پر وائبریٹ کرے گا"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"آپ کے <xliff:g id="DEVICE">%1$s</xliff:g> سے فون کے کیمرا تک رسائی حاصل نہیں کی جا سکتی"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"آپ کے <xliff:g id="DEVICE">%1$s</xliff:g> سے ٹیبلیٹ کے کیمرا تک رسائی حاصل نہیں کی جا سکتی"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"سلسلہ بندی کے دوران اس تک رسائی حاصل نہیں کی جا سکتی۔ اس کے بجائے اپنے فون پر کوشش کریں۔"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"سلسلہ بندی کے دوران تصویر میں تصویر نہیں دیکھ سکتے"</string>
     <string name="system_locale_title" msgid="711882686834677268">"سسٹم ڈیفالٹ"</string>
     <string name="default_card_name" msgid="9198284935962911468">"کارڈ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 5ac689d1..f987eae 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Ovozli pochta"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Tarmoqda xato yoki MMI kod noto‘g‘ri."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Ishlamaydigan funksiya."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Bu amal faqat ruxsat etilgan raqamlar uchun mavjud."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Rouming vaqtida telefondagi chaqiruvni boshqa raqamga uzatish sozlamalarini o‘zgartirib bo‘lmadi."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Xizmat yoqildi."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Internet <xliff:g id="CARRIERDISPLAY">%s</xliff:g> operatoriga almashtirildi"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Buni istalgan vaqtda sozlamalardan o‘zgartirish mumkin"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Ilovaga o‘zining komponentlarini xotirada doimiy saqlashga ruxsat beradi. Bu mavjud xotirani cheklashi va telefonni sekin ishlashiga sabab bo‘lishi mumkin."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"faol xizmatlarni ishga tushirish"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Ilovaga faol xizmatlardan foydalanishga ruxsat beradi."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"“camera” turidagi faol xizmatni ishga tushirish"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Ilovaga “camera” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"“connectedDevice” turidagi faol xizmatni ishga tushirish"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Ilovaga “connectedDevice” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"“dataSync” turidagi faol xizmatni ishga tushirish"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Ilovaga “dataSync” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"“location” turidagi faol xizmatni ishga tushirish"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Ilovaga “location” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"“mediaPlayback” turidagi faol xizmatni ishga tushirish"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Ilovaga “mediaPlayback” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"“mediaProjection” turidagi faol xizmatni ishga tushirish"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Ilovaga “mediaProjection” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"“microphone” turidagi faol xizmatni ishga tushirish"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Ilovaga “microphone” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"“phoneCall” turidagi faol xizmatni ishga tushirish"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Ilovaga “phoneCall” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"“health” turidagi faol xizmatni ishga tushirish"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Ilovaga “health” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"“remoteMessaging” turidagi faol xizmatni ishga tushirish"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Ilovaga “remoteMessaging” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"“systemExempted” turidagi faol xizmatni ishga tushirish"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Ilovaga “systemExempted” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"“specialUse” turidagi faol xizmatni ishga tushirish"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Ilovaga “specialUse” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ilovalar egallagan xotira joyini hisoblash"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Ilova o‘zining kodi, ma’lumotlari va kesh o‘lchami to‘g‘risidagi ma’lumotlarni olishi mumkin"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"tizim sozlamalarini o‘zgartirish"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Bu ilova ishlayotganida u mikrofon orqali audio yozib olishi mumkin."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"orqa fonda ovoz yozib olish"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Bu ilova xohlagan vaqtda mikrofon yordami audio yozib olishi mumkin."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"ilova oynalarining skrinshotga olinishini aniqlash"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ilova ishlatilayotgan vaqtda skrinshot olinganda bu ilova ogohlantiriladi."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"SIM kartaga buyruqlar yuborish"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Dasturga SIM kartaga buyruqlar jo‘natishga ruxsat beradi. Bu juda ham xavfli."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"jismoniy harakatni aniqlash"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Ilovaga video fayllarni umumiy xotiradan oʻqish imkonini beradi."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"umumiy xotiradan rasmli fayllarni oʻqish"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Ilovaga rasm fayllarini umumiy xotiradan oʻqish imkonini beradi."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"umumiy xotiradan siz tanlagan rasm va video fayllarni oʻqish"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Ilovaga siz tanlagan rasm va video fayllarni umumiy xotiradan oʻqish imkonini beradi."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"umumiy xotiradagi kontentlarni tahrirlash yoki oʻchirib tashlash"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Ilovaga umumiy xotiradagi kontentlarga yozish imkonini beradi."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"SIP qo‘ng‘iroqlarini amalga oshirish/qabul qilish"</string>
@@ -1592,7 +1623,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>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"O‘CHIRIB TASHLASH"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"BARIBIR OCHILSIN"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Zararli ilova aniqlandi"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ilovasining qurilmadagi barcha jurnallarga kirishiga ruxsat berilsinmi?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Bir matalik foydalanishga ruxsat berish"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Rad etish"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Qurilma jurnaliga qurilma bilan yuz bergan hodisalar qaydlari yoziladi. Ilovalar bu jurnal qaydlari yordamida muammolarni topishi va bartaraf qilishi mumkin.\n\nAyrim jurnal qaydlarida maxfiy axborotlar yozilishi mumkin, shu sababli qurilmadagi barcha jurnal qaydlariga ruxsatni faqat ishonchli ilovalarga bering. \n\nBu ilovaga qurilmadagi barcha jurnal qaydlariga ruxsat berilmasa ham, u oʻzining jurnalini ocha oladi. Qurilma ishlab chiqaruvchisi ham ayrim jurnallar yoki qurilma haqidagi axborotlarni ocha oladi."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Qurilma jurnaliga qurilma bilan yuz bergan hodisalar qaydlari yoziladi. Ilovalar bu jurnal qaydlari yordamida muammolarni topishi va bartaraf qilishi mumkin.\n\nAyrim jurnal qaydlarida maxfiy axborotlar yozilishi mumkin, shu sababli qurilmadagi barcha jurnal qaydlariga ruxsatni faqat ishonchli ilovalarga bering. \n\nBu ilovaga qurilmadagi barcha jurnal qaydlariga ruxsat berilmasa ham, u oʻzining jurnalini ocha oladi. Qurilma ishlab chiqaruvchisi ham ayrim jurnallar yoki qurilma haqidagi axborotlarni ocha oladi.\n\nBatafsil: g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Boshqa chiqmasin"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ilovasi <xliff:g id="APP_2">%2$s</xliff:g> ilovasidan fragmentlar ko‘rsatish uchun ruxsat so‘ramoqda"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Tahrirlash"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Chaqiruvlar va bildirishnomalar tebranadi"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> qurilmasidan telefonning kamerasiga kirish imkonsiz"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> qurilmasidan planshetning kamerasiga kirish imkonsiz"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Bu kontent striming vaqtida ochilmaydi. Telefon orqali urininb koʻring."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Striming vaqtida tasvir ustida tasvir rejimida koʻrib boʻlmaydi"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Tizim standarti"</string>
     <string name="default_card_name" msgid="9198284935962911468">"SIM KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index e1b479c..af1ae05 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Thư thoại"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Sự cố kết nối hoặc mã MMI không hợp lệ."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Tính năng không được hỗ trợ."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Chỉ hạn chế thao tác đối với số quay số định sẵn."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Không thể thay đổi cài đặt chuyển tiếp cuộc gọi từ điện thoại của bạn khi bạn đang chuyển vùng."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Dịch vụ đã được bật."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Đã chuyển dữ liệu sang <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Bạn có thể thay đổi chế độ này bất cứ lúc nào trong phần Cài đặt"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Cho phép ứng dụng tạo sự đồng nhất cho các phần của mình trong bộ nhớ. Việc này có thể hạn chế bộ nhớ đối với các ứng dụng khác đang làm chậm điện thoại."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"chạy dịch vụ trên nền trước"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Cho phép ứng dụng sử dụng các dịch vụ trên nền trước."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"chạy dịch vụ trên nền trước thuộc loại \"camera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"camera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"chạy dịch vụ trên nền trước thuộc loại \"connectedDevice\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"chạy dịch vụ trên nền trước thuộc loại \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"chạy dịch vụ trên nền trước thuộc loại \"location\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"location\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"chạy dịch vụ trên nền trước thuộc loại \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"chạy dịch vụ trên nền trước thuộc loại \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"chạy dịch vụ trên nền trước thuộc loại \"microphone\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"microphone\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"chạy dịch vụ trên nền trước thuộc loại \"phoneCall\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"chạy dịch vụ trên nền trước thuộc loại \"health\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"health\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"chạy dịch vụ trên nền trước thuộc loại \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"chạy dịch vụ trên nền trước thuộc loại \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"chạy dịch vụ trên nền trước thuộc loại \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"đo dung lượng lưu trữ ứng dụng"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Cho phép ứng dụng truy xuất mã, dữ liệu và kích thước bộ nhớ đệm của chính ứng dụng"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"sửa đổi các chế độ cài đặt hệ thống"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ứng dụng này có thể ghi âm bằng micrô khi bạn đang dùng ứng dụng."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ghi âm trong nền"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ứng dụng này có thể ghi âm bằng micrô bất kỳ lúc nào."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"phát hiện ảnh chụp màn hình các cửa sổ ứng dụng"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ứng dụng này sẽ nhận được thông báo nếu người dùng chụp màn hình trong khi đang dùng ứng dụng."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"gửi lệnh đến SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Cho phép ứng dụng gửi lệnh đến SIM. Việc này rất nguy hiểm."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"nhận dạng hoạt động thể chất"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Cho phép ứng dụng đọc tệp video trong bộ nhớ dùng chung."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"đọc tệp hình ảnh trong bộ nhớ dùng chung"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Cho phép ứng dụng đọc tệp hình ảnh trong bộ nhớ dùng chung."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"đọc các tệp hình ảnh và video mà người dùng đã chọn trong bộ nhớ dùng chung"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Cho phép ứng dụng đọc các tệp hình ảnh và video mà bạn chọn trong bộ nhớ dùng chung."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"sửa đổi hoặc xóa nội dung của bộ nhớ dùng chung"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Cho phép ứng dụng ghi nội dung của bộ nhớ dùng chung."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"thực hiện/nhận các cuộc gọi qua SIP"</string>
@@ -1206,7 +1237,7 @@
     <string name="aerr_application_repeated" msgid="7804378743218496566">"<xliff:g id="APPLICATION">%1$s</xliff:g> tiếp tục dừng"</string>
     <string name="aerr_process_repeated" msgid="1153152413537954974">"<xliff:g id="PROCESS">%1$s</xliff:g> tiếp tục dừng"</string>
     <string name="aerr_restart" msgid="2789618625210505419">"Mở lại ứng dụng"</string>
-    <string name="aerr_report" msgid="3095644466849299308">"Gửi phản hồi"</string>
+    <string name="aerr_report" msgid="3095644466849299308">"Gửi ý kiến phản hồi"</string>
     <string name="aerr_close" msgid="3398336821267021852">"Đóng"</string>
     <string name="aerr_mute" msgid="2304972923480211376">"Tắt tiếng cho đến khi thiết bị khởi động lại"</string>
     <string name="aerr_wait" msgid="3198677780474548217">"Đợi"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"GỠ CÀI ĐẶT"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"VẪN MỞ"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Đã phát hiện ứng dụng độc hại"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Cho phép <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> truy cập vào tất cả các nhật ký thiết bị?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Cho phép truy cập một lần"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Không cho phép"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Nhật ký thiết bị ghi lại những hoạt động diễn ra trên thiết bị. Các ứng dụng có thể dùng nhật ký này để tìm và khắc phục sự cố.\n\nMột số nhật ký có thể chứa thông tin nhạy cảm, vì vậy, bạn chỉ nên cấp quyền truy cập vào toàn bộ nhật ký thiết bị cho những ứng dụng mà mình tin cậy. \n\nNếu bạn không cho phép ứng dụng này truy cập vào toàn bộ nhật ký thiết bị, thì ứng dụng vẫn có thể truy cập vào nhật ký của chính nó. Nhà sản xuất thiết bị vẫn có thể truy cập vào một số nhật ký hoặc thông tin trên thiết bị của bạn."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Nhật ký thiết bị ghi lại những hoạt động diễn ra trên thiết bị. Các ứng dụng có thể dùng các nhật ký này để tìm và khắc phục sự cố.\n\nMột số nhật ký có thể chứa thông tin nhạy cảm, vì vậy, bạn chỉ nên cấp quyền truy cập vào mọi nhật ký thiết bị cho những ứng dụng mà mình tin cậy. \n\nNếu bạn không cho phép ứng dụng này truy cập vào toàn bộ nhật ký thiết bị, thì ứng dụng vẫn có thể truy cập vào nhật ký của chính nó. Nhà sản xuất thiết bị vẫn có thể truy cập vào một số nhật ký hoặc thông tin trên thiết bị của bạn.\n\nTìm hiểu thêm tại g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Không hiện lại"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> muốn hiển thị các lát của <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Chỉnh sửa"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Cuộc gọi và thông báo sẽ rung"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Không truy cập được vào máy ảnh trên điện thoại từ <xliff:g id="DEVICE">%1$s</xliff:g> của bạn"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Không truy cập được vào máy ảnh trên máy tính bảng từ <xliff:g id="DEVICE">%1$s</xliff:g> của bạn"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Bạn không thể truy cập vào nội dung này trong khi phát trực tuyến. Hãy thử trên điện thoại."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Không thể xem video ở chế độ hình trong hình khi đang truyền trực tuyến"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Theo chế độ mặc định của hệ thống"</string>
     <string name="default_card_name" msgid="9198284935962911468">"THẺ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-watch/dimens_material.xml b/core/res/res/values-watch/dimens_material.xml
index 82c088b..51d4018 100644
--- a/core/res/res/values-watch/dimens_material.xml
+++ b/core/res/res/values-watch/dimens_material.xml
@@ -45,6 +45,13 @@
     <dimen name="progress_bar_size_medium">32dip</dimen>
     <dimen name="progress_bar_size_large">64dip</dimen>
 
+    <!-- Progress bar message dimens -->
+    <dimen name="message_progress_dialog_text_size">18sp</dimen>
+    <dimen name="message_progress_dialog_bottom_padding">80px</dimen>
+    <dimen name="message_progress_dialog_top_padding">0dp</dimen>
+    <dimen name="message_progress_dialog_start_padding">0dp</dimen>
+    <dimen name="message_progress_dialog_end_padding">0dp</dimen>
+
     <!-- fallback for screen percentage widths -->
     <dimen name="screen_percentage_05">0dp</dimen>
     <dimen name="screen_percentage_10">0dp</dimen>
diff --git a/core/res/res/values-watch/styles_material.xml b/core/res/res/values-watch/styles_material.xml
index 5093aa8..f34d9f9 100644
--- a/core/res/res/values-watch/styles_material.xml
+++ b/core/res/res/values-watch/styles_material.xml
@@ -97,4 +97,14 @@
         <item name="gravity">center_horizontal|top</item>
         <item name="ellipsize">end</item>
     </style>
+
+    <style name="ProgressDialogMessage">
+        <item name="android:textAlignment">center</item>
+        <item name="textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
+        <item name="textSize">@dimen/message_progress_dialog_text_size</item>
+        <item name="paddingBottom">@dimen/message_progress_dialog_bottom_padding</item>
+        <item name="paddingEnd">@dimen/message_progress_dialog_end_padding</item>
+        <item name="paddingStart">@dimen/message_progress_dialog_start_padding</item>
+        <item name="paddingTop">@dimen/message_progress_dialog_top_padding</item>
+    </style>
 </resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 5fce25e..73f5b034 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"语音信箱"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"出现连接问题或 MMI 码无效。"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"不支持此功能。"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"只能对固定拨号号码执行此类操作。"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"漫游时无法通过您的手机来更改来电转接设置。"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"已启用服务。"</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"允许该应用在内存中持续保留其自身的某些组件。这会限制其他应用可用的内存,从而减缓手机运行速度。"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"运行前台服务"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"允许该应用使用前台服务。"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"运行“camera”类型的前台服务"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"允许该应用使用“camera”类型的前台服务"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"运行“connectedDevice”类型的前台服务"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"允许该应用使用“connectedDevice”类型的前台服务"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"运行“dataSync”类型的前台服务"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"允许该应用使用“dataSync”类型的前台服务"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"运行“location”类型的前台服务"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"允许该应用使用“location”类型的前台服务"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"运行“mediaPlayback”类型的前台服务"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"允许该应用使用“mediaPlayback”类型的前台服务"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"运行“mediaProjection”类型的前台服务"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"允许该应用使用“mediaProjection”类型的前台服务"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"运行“microphone”类型的前台服务"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"允许该应用使用“microphone”类型的前台服务"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"运行“phoneCall”类型的前台服务"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"允许该应用使用“phoneCall”类型的前台服务"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"运行“health”类型的前台服务"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"允许该应用使用“health”类型的前台服务"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"运行“remoteMessaging”类型的前台服务"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"允许该应用使用“remoteMessaging”类型的前台服务"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"运行“systemExempted”类型的前台服务"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"允许该应用使用“systemExempted”类型的前台服务"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"运行“specialUse”类型的前台服务"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"允许该应用使用“specialUse”类型的前台服务"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"计算应用存储空间"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"允许应用检索其代码、数据和缓存大小"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"修改系统设置"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"当您使用此应用时,它可以使用麦克风录音。"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"在后台录音"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"此应用可以随时使用麦克风录音。"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"检测应用窗口的屏幕截图"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"在应用使用过程中截取屏幕截图时,此应用将收到通知。"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"向 SIM 卡发送命令"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"允许应用向SIM卡发送命令(此权限具有很高的危险性)。"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"识别身体活动"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"允许应用读取您共享存储空间中的视频文件。"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"读取共享存储空间中的图片文件"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"允许应用读取您共享存储空间中的图片文件。"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"从共享存储空间读取用户选择的图片和视频文件"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"使应用能够从共享存储空间读取您所选的图片和视频文件。"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"修改或删除您共享存储空间中的内容"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"允许该应用写入您共享存储空间中的内容。"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"拨打/接听SIP电话"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"卸载"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"仍然打开"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"检测到有害应用"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"允许“<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>”访问所有设备日志吗?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"允许访问一次"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"不允许"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"设备日志会记录设备上发生的活动。应用可以使用这些日志查找和修复问题。\n\n部分日志可能包含敏感信息,因此请仅允许您信任的应用访问所有设备日志。\n\n如果您不授予此应用访问所有设备日志的权限,它仍然可以访问自己的日志。您的设备制造商可能仍然能够访问设备上的部分日志或信息。"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"设备日志会记录设备上发生的活动。应用可以使用这些日志查找和修复问题。\n\n部分日志可能包含敏感信息,因此请仅允许您信任的应用访问所有设备日志。\n\n如果您不授予此应用访问所有设备日志的权限,它仍然可以访问自己的日志。您的设备制造商可能仍然能够访问设备上的部分日志或信息。\n\n如需了解详情,请访问 g.co/android/devicelogs。"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"不再显示"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"“<xliff:g id="APP_0">%1$s</xliff:g>”想要显示“<xliff:g id="APP_2">%2$s</xliff:g>”图块"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"编辑"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"有来电和通知时会振动"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"无法从<xliff:g id="DEVICE">%1$s</xliff:g>上访问手机的摄像头"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"无法从<xliff:g id="DEVICE">%1$s</xliff:g>上访问平板电脑的摄像头"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"流式传输时无法访问此内容。您可以尝试在手机上访问。"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"在线播放时无法查看画中画"</string>
     <string name="system_locale_title" msgid="711882686834677268">"系统默认设置"</string>
     <string name="default_card_name" msgid="9198284935962911468">"SIM 卡 <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 61a3f20..4993c89 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"留言信箱"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"連線發生問題或 MMI 碼無效。"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"不支援的功能。"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"僅限對固定撥號號碼執行這項運作。"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"使用漫遊服務時,不可從手機變更來電轉駁設定。"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"服務已啟用。"</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"允許應用程式設定本身的某些部分持續佔用記憶體。這樣可能會限制其他應用程式可用的記憶體,並拖慢手機的運作速度。"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"執行前景服務"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"允許應用程式使用前景服務。"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"配搭「camera」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"允許應用程式配搭「camera」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"配搭「connectedDevice」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"允許應用程式配搭「connectedDevice」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"配搭「dataSync」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"允許應用程式配搭「dataSync」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"配搭「location」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"允許應用程式配搭「location」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"配搭「mediaPlayback」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"允許應用程式配搭「mediaPlayback」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"配搭「mediaProjection」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"允許應用程式配搭「mediaProjection」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"配搭「microphone」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"允許應用程式配搭「microphone」類型使用前景服務"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"配搭「phoneCall」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"允許應用程式配搭「phoneCall」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"配搭「health」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"允許應用程式配搭「health」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"配搭「remoteMessaging」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"允許應用程式配搭「remoteMessaging」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"配搭「systemExempted」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"允許應用程式配搭「systemExempted」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"配搭「specialUse」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"允許應用程式配搭「specialUse」類型使用前景服務"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"測量應用程式儲存空間"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"允許應用程式擷取本身的程式碼、資料和快取大小"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"修改系統設定"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"此應用程式在使用期間可使用麥克風錄音。"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"在背景錄音"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"此應用程式可隨時使用麥克風錄音。"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"偵測應用程式視窗是否擷取螢幕畫面"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"如有人在應用程式使用期間擷取螢幕截圖,此應用程式將會收到通知"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"發送指令至 SIM 卡"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"允許應用程式傳送指令到 SIM 卡。這項操作具有高危險性。"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"識別體能活動"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"允許應用程式讀取共用儲存空間中的影片檔案。"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"讀取共用儲存空間中的圖片檔案"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"允許應用程式讀取共用儲存空間中的圖片檔案。"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"讀取使用者在共用儲存空間中選取的圖片和影片檔案"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"允許應用程式讀取您在共用儲存空間中選取的圖片和影片檔案。"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"修改或刪除您共用儲存空間的內容"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"允許應用程式寫入您共用儲存空間的內容。"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"撥打/接聽 SIP 電話"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"解除安裝"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"仍要開啟"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"偵測到有害的應用程式"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"要允許「<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>」存取所有裝置記錄嗎?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"允許存取一次"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"不允許"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"裝置記錄會記下裝置上的活動。應用程式可透過這些記錄找出並修正問題。\n\n部分記錄可能包含敏感資料,因此請只允許信任的應用程式存取所有裝置記錄。\n\n如果不允許此應用程式存取所有裝置記錄,此應用程式仍能存取自己的記錄,且裝置製造商可能仍可存取裝置上的部分記錄或資料。"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"裝置記錄會記下裝置上的活動。應用程式可使用這些記錄找出並修正問題。\n\n有些記錄可能包含敏感資料,因此建議只允許信任的應用程式存取所有裝置記錄。\n\n如果不允許此應用程式存取所有裝置記錄,此應用程式仍能存取自己的記錄。您的裝置製造商可能仍可存取裝置上的一些記錄或資料。\n\n詳情請瀏覽 g.co/android/devicelogs。"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"不要再顯示"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"「<xliff:g id="APP_0">%1$s</xliff:g>」想顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的快訊"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"編輯"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"有來電和通知時會震動"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"無法從 <xliff:g id="DEVICE">%1$s</xliff:g> 存取手機的相機"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"無法從 <xliff:g id="DEVICE">%1$s</xliff:g> 存取平板電腦的相機"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"串流播放時無法使用,請改用手機。"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"串流期間無法查看畫中畫"</string>
     <string name="system_locale_title" msgid="711882686834677268">"系統預設"</string>
     <string name="default_card_name" msgid="9198284935962911468">"SIM 卡 <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 10dc699..7dde343 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"語音留言"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"連線發生問題或錯誤的 MMI 碼。"</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"不支援的功能。"</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"僅限對固定撥號號碼執行此作業。"</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"漫遊時無法透過你的手機變更來電轉接設定。"</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"服務已啟用。"</string>
@@ -72,6 +73,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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"允許應用程式讓部分內容佔用記憶體,持續執行。這項設定可能會限制其他應用程式可用的記憶體,並拖慢手機運作速度。"</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"執行前景服務"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"允許應用程式使用前景服務。"</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"搭配「camera」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"允許應用程式搭配「camera」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"搭配「connectedDevice」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"允許應用程式搭配「connectedDevice」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"搭配「dataSync」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"允許應用程式搭配「dataSync」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"搭配「location」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"允許應用程式搭配「location」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"搭配「mediaPlayback」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"允許應用程式搭配「mediaPlayback」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"搭配「mediaProjection」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"允許應用程式搭配「mediaProjection」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"搭配「microphone」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"允許應用程式搭配「microphone」類型使用前景服務"</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"搭配「phoneCall」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"允許應用程式搭配「phoneCall」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"搭配「health」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"允許應用程式搭配「health」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"搭配「remoteMessaging」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"允許應用程式搭配「remoteMessaging」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"搭配「systemExempted」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"允許應用程式搭配「systemExempted」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"搭配「specialUse」類型執行前景服務"</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"允許應用程式搭配「specialUse」類型使用前景服務"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"測量應用程式儲存空間"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"允許應用程式擷取本身的程式碼、資料及快取大小"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"修改系統設定"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"這個應用程式在使用期間可以使用麥克風錄音。"</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"在背景錄音"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"這個應用程式隨時可以使用麥克風錄音。"</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"偵測應用程式視窗是否擷取螢幕畫面"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"如果在使用應用程式過程中拍攝了螢幕截圖,這個應用程式將會收到通知。"</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"傳送指令到 SIM 卡"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"允許應用程式傳送指令到 SIM 卡。這麼做非常危險。"</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"辨識體能活動"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"允許應用程式讀取共用儲存空間中的影片檔案。"</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"讀取共用儲存空間中的圖片檔案"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"允許應用程式讀取共用儲存空間中的圖片檔案。"</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"讀取使用者在共用儲存空間中選取的圖片和影片檔案"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"允許應用程式讀取你在共用儲存空間中選取的圖片和影片檔案。"</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"修改或刪除共用儲存空間中的內容"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"允許這個應用程式寫入共用儲存空間中的內容。"</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"撥打/接聽 SIP 通話"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"解除安裝"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"仍要開啟"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"偵測到有害應用程式"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"要允許「<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>」存取所有裝置記錄嗎?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"允許一次性存取"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"不允許"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"系統會透過裝置記錄記下裝置上的活動。應用程式可以根據這些記錄找出問題並進行修正。\n\n某些記錄可能含有機密資訊,因此請勿讓不信任的應用程式存取所有裝置記錄。\n\n即使你不允許這個應用程式存取所有裝置記錄,這個應用程式仍能存取自己的記錄,而且裝置製造商或許仍可存取裝置的某些記錄或資訊。"</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"裝置記錄會記下裝置上的活動。應用程式可以根據這些記錄找出問題並進行修正。\n\n由於某些記錄可能含有機密資訊,建議只讓信任的應用程式存取所有裝置記錄。\n\n如果你不允許這個應用程式存取所有裝置記錄,這個應用程式仍可存取屬於自己的記錄,而裝置製造商也或許還是可以存取裝置的某些記錄或資訊。\n\n請參閱以下網址瞭解詳情:g.co/android/devicelogs。"</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"不要再顯示"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"「<xliff:g id="APP_0">%1$s</xliff:g>」想要顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的區塊"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"編輯"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"有來電和通知時會震動"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"無法從 <xliff:g id="DEVICE">%1$s</xliff:g> 存取手機的相機"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"無法從 <xliff:g id="DEVICE">%1$s</xliff:g> 存取平板電腦的相機"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"串流播放時無法存取這項內容,請改用手機。"</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"串流播放時無法查看子母畫面"</string>
     <string name="system_locale_title" msgid="711882686834677268">"系統預設"</string>
     <string name="default_card_name" msgid="9198284935962911468">"SIM 卡 <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 66d639e..86f869d 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -28,6 +28,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Ivoyisimeyili"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
     <string name="mmiError" msgid="2862759606579822246">"Inkinga yoxhumano noma ikhadi ye-MMI engalungile."</string>
+    <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Isakhi asisekelwa."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Umsebenzi uvinjelwe ekudayeleni izinombolo ezingaguquki kuphela."</string>
     <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Ayikwazi ukushintsha izilungiselelo zokudluliselwa kwekholi kusuka efonini yakho ngenkathi uzula."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Isevisi ivaliwe."</string>
@@ -72,6 +73,8 @@
     <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>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Ushintshele idatha ku-<xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Ungashintsha lokhu noma nini kumasethingi"</string>
     <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>
@@ -393,6 +396,30 @@
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Ivumela uhlelo kusebenza ukwenza izingxenye yazo ezicindezelayo kumemori. Lokhu kungakhawulela imemori ekhona kwezinye izinhlelo zokusebenza ukwenza ukuthi ifoni ingasheshi."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"qalisa amasevisi waphambili"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Vumela uhlelo lokusebenza ukusebenzisa amasevisi wangaphambili."</string>
+    <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"qalisa isevisi ephambili ngohlobo lokuthi \"ikhamera\""</string>
+    <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"ikhamera\""</string>
+    <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"qalisa isevisi ephambili ngohlobo lokuthi \"Idivayisi exhunyiwe\""</string>
+    <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"connectedDevice\""</string>
+    <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"qalisa isevisi ephambili ngohlobo lokuthi \"dataSync\""</string>
+    <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"dataSync\""</string>
+    <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"qalisa isevisi ephambili ngohlobo lokuthi \"indawo\""</string>
+    <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Vumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"indawo\""</string>
+    <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"qalisa isevisi ephambili ngohlobo lokuthi \"mediaPlayback\""</string>
+    <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"mediaPlayback\""</string>
+    <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"qalisa isevisi ephambili ngohlobo lokuthi \"mediaProjection\""</string>
+    <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"mediaProjection\""</string>
+    <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"qalisa isevisi ephambili ngohlobo lokuthi \"imakrofoni\""</string>
+    <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"imakrofoni\""</string>
+    <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"qalisa isevisi ephambili ngohlobo lokuthi \"Ikholi yefoni\""</string>
+    <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"phoneCall\""</string>
+    <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"qalisa isevisi ephambili ngohlobo lokuthi \"impilo\""</string>
+    <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"impilo\""</string>
+    <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"qalisa isevisi ephambili ngohlobo lokuthi \"remoteMessaging\""</string>
+    <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"remoteMessaging\""</string>
+    <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"qalisa isevisi ephambili ngohlobo lokuthi \"systemExempted\""</string>
+    <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"systemExempted\""</string>
+    <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"qalisa isevisi ephambili ngohlobo lokuthi \"specialUse\""</string>
+    <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"linganisa isikhala sokugcina uhlelo lokusebenza"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Ivuela uhlelo lokusebenza ukuthi ithole kabusha ikhodi yayo, i-dat kanye nosayizi abagcinwe okwesikhashana."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"shintsha amasethingi esistimu"</string>
@@ -445,6 +472,8 @@
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Lolu hlelo lokusebenza lungarekhoda umsindo lisebenzisa imakrofoni kuyilapho uhlelo lokusebenza lusetshenziswa."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"rekhoda umsindo ngemuva"</string>
     <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Lolu hlelo lokusebenza lungafunda umsindo lisebenzisa imakrofoni noma kunini."</string>
+    <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"thola izithombe zesikrini zamawindi e-app"</string>
+    <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Loe-app izokwaziswa uma isithombe-skrini sithathwa ngenkathi i-app isetshenziswa."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"thumela imilayezo ku-SIM"</string>
     <string name="permdesc_sim_communication" msgid="4179799296415957960">"Ivumela uhlelo lokusebenza ukuthumela imiyalo ku-SIM. Lokhu kuyingozi kakhulu."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"bona umsebenzi"</string>
@@ -696,6 +725,8 @@
     <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Ivumela i-app ukuthi ifunde amafayela amavidiyo kwisitoreji sakho owabelane ngaso."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"funda amafayela womfanekiso wesitoreji okwabelenwe ngaso"</string>
     <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Ivumela i-app ukuthi ifunde amafayela ezithombe kwisitoreji sakho owabelane ngaso."</string>
+    <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"funda amafayela akhethiwe wesithombe namavidiyo akhethiwe kusitoreji esabiwe"</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Ivumela i-app ukuthi ifunde amafayela wesithombe namavidiyo owakhethayo kusitoreji esabiwe."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"guqula noma susa okuqukethwe kwesitoreji sakho esabiwe"</string>
     <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Ivumela uhlelo lokusebenza ukuthi lubhale okuqukethwe kwesitoreji sakho esabiwe."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"yenza/thola amakholi we-SIP"</string>
@@ -2046,12 +2077,6 @@
     <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"KHIPHA"</string>
     <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"VULA NOMA KUNJALO"</string>
     <string name="harmful_app_warning_title" msgid="8794823880881113856">"Uhlelo lokusebenza oluyingozi lutholakele"</string>
-    <string name="log_access_confirmation_title" msgid="2343578467290592708">"Vumela i-<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ukuba ifinyelele wonke amalogu edivayisi?"</string>
-    <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Vumela ukufinyelela kwesikhathi esisodwa"</string>
-    <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ungavumeli"</string>
-    <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Amalogu edivayisi arekhoda okwenzekayo kudivayisi yakho. Ama-app angasebenzisa lawa malogu ukuze athole futhi alungise izinkinga.\n\nAmanye amalogu angase aqukathe ulwazi olubucayi, ngakho vumela ama-app owathembayo kuphela ukuthi afinyelele wonke amalogu edivayisi. \n\nUma ungayivumeli le app ukuthi ifinyelele wonke amalogu wedivayisi, isengakwazi ukufinyelela amalogu wayo. Umkhiqizi wedivayisi yakho usengakwazi ukufinyelela amanye amalogu noma ulwazi kudivayisi yakho."</string>
-    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Amalogu edivayisi arekhoda okwenzekayo kudivayisi yakho. Ama-app angasebenzisa lawa malogu ukuze athole futhi alungise izinkinga.\n\nAmanye amalogu angase aqukathe ulwazi olubucayi, ngakho vumela ama-app owathembayo kuphela ukuthi afinyelele wonke amalogu edivayisi. \n\nUma ungayivumeli le app ukuthi ifinyelele wonke amalogu wedivayisi, isengakwazi ukufinyelela amalogu wayo. Umkhiqizi wedivayisi yakho usengakwazi ukufinyelela amanye amalogu noma ulwazi kudivayisi yakho.\n\nFunda kabanzi ku-g.co/android/devicelogs."</string>
-    <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ungabonisi futhi"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"I-<xliff:g id="APP_0">%1$s</xliff:g> ifuna ukubonisa izingcezu ze-<xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Hlela"</string>
     <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Amakholi nezaziso zizodlidliza"</string>
@@ -2292,6 +2317,7 @@
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ayikwazi ukufinyelela ikhamera yefoni kusuka ku-<xliff:g id="DEVICE">%1$s</xliff:g> yakho"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ayikwazi ukufinyelela ikhamera yethebulethi kusuka ku-<xliff:g id="DEVICE">%1$s</xliff:g> yakho"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Lokhu akukwazi ukufinyelelwa ngenkathi usakaza. Zama efonini yakho kunalokho."</string>
+    <string name="vdm_pip_blocked" msgid="4036107522497281397">"Ayikwazi ukubuka isithombe esiphakathi kwesithombe ngenkathi isakaza"</string>
     <string name="system_locale_title" msgid="711882686834677268">"Okuzenzakalelayo kwesistimu"</string>
     <string name="default_card_name" msgid="9198284935962911468">"IKHADI <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index dff7751..9e735d0 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -166,12 +166,12 @@
     </string-array>
 
     <array name="sim_colors">
-        <item>@color/Teal_700</item>
-        <item>@color/Blue_700</item>
-        <item>@color/Indigo_700</item>
-        <item>@color/Purple_700</item>
-        <item>@color/Pink_700</item>
-        <item>@color/Red_700</item>
+        <item>@color/SIM_color_cyan</item>
+        <item>@color/SIM_color_blue</item>
+        <item>@color/SIM_color_green</item>
+        <item>@color/SIM_color_purple</item>
+        <item>@color/SIM_color_pink</item>
+        <item>@color/SIM_color_orange</item>
     </array>
 
     <!-- Used in ResolverTargetActionsDialogFragment -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2d832bc..7946493 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8955,6 +8955,9 @@
         <!-- Flag indicating whether a recognition service can be selected as default. The default
              value of this flag is true. -->
         <attr name="selectableAsDefault" format="boolean" />
+        <!-- The maximal number of recognition sessions ongoing at the same time.
+             The default value is 1, meaning no concurrency. -->
+        <attr name="maxConcurrentSessionsCount" format="integer" />
     </declare-styleable>
 
     <!-- Use <code>voice-interaction-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index eac2b94..d4644c5 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -294,6 +294,9 @@
         <!-- Additional flag from base permission type: this permission can be automatically
             granted to the system app predictor -->
         <flag name="appPredictor" value="0x200000" />
+        <!-- Additional flag from base permission type: this permission can also be granted if the
+             requesting application is included in the mainline module}. -->
+        <flag name="module" value="0x400000" />
         <!-- Additional flag from base permission type: this permission can be automatically
             granted to the system companion device manager service -->
         <flag name="companion" value="0x800000" />
@@ -1586,19 +1589,71 @@
          together. -->
     <attr name="foregroundServiceType">
         <!-- Data (photo, file, account) upload/download, backup/restore, import/export, fetch,
-        transfer over network between device and cloud.  -->
+            transfer over network between device and cloud.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, this type should NOT
+            be used: calling
+            {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
+            this type on devices running {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+            is still allowed, but calling it with this type on devices running future platform
+            releases may get a {@link android.app.InvalidForegroundServiceTypeException}.
+        -->
         <flag name="dataSync" value="0x01" />
-        <!-- Music, video, news or other media play. -->
+        <!-- Music, video, news or other media play.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+            service with this type will require permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PLAYBACK}.
+        -->
         <flag name="mediaPlayback" value="0x02" />
         <!-- Ongoing operations related to phone calls, video conferencing,
-             or similar interactive communication. -->
+            or similar interactive communication.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+            service with this type will require permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_PHONE_CALL} and
+            {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
+        -->
         <flag name="phoneCall" value="0x04" />
-        <!-- GPS, map, navigation location update. -->
+        <!-- GPS, map, navigation location update.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+            service with this type will require permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_LOCATION} and one of the
+            following permissions:
+            {@link android.Manifest.permission#ACCESS_COARSE_LOCATION},
+            {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
+        -->
         <flag name="location" value="0x08" />
-        <!-- Auto, bluetooth, TV or other devices connection, monitoring and interaction. -->
+        <!-- Auto, bluetooth, TV or other devices connection, monitoring and interaction.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+            service with this type will require permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_CONNECTED_DEVICE} and one of the
+            following permissions:
+            {@link android.Manifest.permission#BLUETOOTH_CONNECT},
+            {@link android.Manifest.permission#CHANGE_NETWORK_STATE},
+            {@link android.Manifest.permission#CHANGE_WIFI_STATE},
+            {@link android.Manifest.permission#CHANGE_WIFI_MULTICAST_STATE},
+            {@link android.Manifest.permission#NFC},
+            {@link android.Manifest.permission#TRANSMIT_IR},
+            or has been granted the access to one of the attached USB devices/accessories.
+        -->
         <flag name="connectedDevice" value="0x10" />
         <!-- Managing a media projection session, e.g, for screen recording or taking
-             screenshots.-->
+            screenshots.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+            service with this type will require permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PROJECTION}, and the user
+            must have allowed the screen capture request from this app.
+        -->
         <flag name="mediaProjection" value="0x20" />
         <!-- Use the camera device or record video.
 
@@ -1606,6 +1661,12 @@
             and above, a foreground service will not be able to access the camera if this type is
             not specified in the manifest and in
             {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+            service with this type will require permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_CAMERA} and
+            {@link android.Manifest.permission#CAMERA}.
             -->
         <flag name="camera" value="0x40" />
         <!--Use the microphone device or record audio.
@@ -1614,8 +1675,60 @@
             and above, a foreground service will not be able to access the microphone if this type
             is not specified in the manifest and in
             {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+            service with this type will require permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_MICROPHONE} and one of the
+            following permissions:
+            {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT},
+            {@link android.Manifest.permission#RECORD_AUDIO}.
             -->
         <flag name="microphone" value="0x80" />
+        <!--Health, wellness and fitness.
+            <p>Requires the app to hold the permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_HEALTH} and one of the following
+            permissions
+            {@link android.Manifest.permission#ACTIVITY_RECOGNITION},
+            {@link android.Manifest.permission#BODY_SENSORS},
+            {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS}.
+        -->
+        <flag name="health" value="0x100" />
+        <!-- Messaging use cases which host local server to relay messages across devices.
+            <p>Requires the app to hold the permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_REMOTE_MESSAGING} in order to use
+            this type.
+        -->
+        <flag name="remoteMessaging" value="0x200" />
+        <!-- The system exmpted foreground service use cases.
+            <p>Requires the app to hold the permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_SYSTEM_EXEMPTED} in order to use
+            this type. Apps are allowed to use this type only in the use cases listed in
+            {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}.
+        -->
+        <flag name="systemExempted" value="0x400" />
+        <!-- "Short service" foreground service type. See
+           {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
+           for more details.
+        -->
+        <flag name="shortService" value="0x800" />
+        <!-- The file management use case which manages files/directories, often involving file I/O
+            across the file system.
+            <p>Requires the app to hold the permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_FILE_MANAGEMENT} in order to use
+            this type.
+        -->
+        <flag name="fileManagement" value="0x1000" />
+        <!-- Use cases that can't be categorized into any other foreground service types, but also
+            can't use @link android.app.job.JobInfo.Builder} APIs.
+            See {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE} for the
+            best practice of the use of this type.
+
+            <p>Requires the app to hold the permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_SPECIAL_USE} in order to use
+            this type.
+        -->
+        <flag name="specialUse" value="0x40000000" />
     </attr>
 
     <!-- Enable sampled memory bug detection in this process.
@@ -3062,20 +3175,20 @@
         <attr name="canDisplayOnRemoteDevices" format="boolean"/>
         <attr name="allowUntrustedActivityEmbedding" />
         <attr name="knownActivityEmbeddingCerts" />
-        <!-- Specifies the category of the target display the activity is expected to run on. Upon
-             creation, a virtual display can specify which display categories it supports and one of
-             the category must be present in the activity's manifest to allow this activity to run.
-             The default value is {@code null}, which indicates the activity does not belong to a
-             restricted display category and thus can only run on a display that didn't specify any
-             display categories. Each activity can only specify one category it targets to but a
-             virtual display can accommodate multiple restricted categories.
+        <!-- Specifies the required display category of the activity. Upon creation, a display can
+             specify which display categories it supports and one of the categories must be present
+             in the {@code <activity>} element to allow this activity to run. The default value is
+             {@code null}, which indicates the activity does not have a required display category
+             and thus can only run on a display that didn't specify any display categories. Each
+             activity can only specify one required category but a display can accommodate multiple
+             display categories.
 
              <p> This field should be formatted as a Java-language-style free form string(for
              example, com.google.automotive_entertainment), which may contain uppercase or lowercase
              letters ('A' through 'Z'), numbers, and underscores ('_') but may only start with
              letters.
          -->
-        <attr name="targetDisplayCategory" format="string"/>
+        <attr name="requiredDisplayCategory" format="string"/>
     </declare-styleable>
 
     <!-- The <code>activity-alias</code> tag declares a new
diff --git a/core/res/res/values/cloneable_apps.xml b/core/res/res/values/cloneable_apps.xml
new file mode 100644
index 0000000..b852c3c
--- /dev/null
+++ b/core/res/res/values/cloneable_apps.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<resources>
+    <!-- A list of apps that are allowed to have another instance on a device
+    in the clone profile. -->
+    <string-array translatable="false" name="cloneable_apps">
+    </string-array>
+</resources>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 77d7c43..8c356b4 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -198,18 +198,18 @@
     <color name="instant_app_badge">#ff757575</color><!-- Grey -->
 
     <!-- Multi-sim sim colors -->
-    <color name="Teal_700">#ff00796b</color>
-    <color name="Teal_800">#ff00695c</color>
-    <color name="Blue_700">#ff3367d6</color>
-    <color name="Blue_800">#ff2a56c6</color>
-    <color name="Indigo_700">#ff303f9f</color>
-    <color name="Indigo_800">#ff283593</color>
-    <color name="Purple_700">#ff7b1fa2</color>
-    <color name="Purple_800">#ff6a1b9a</color>
-    <color name="Pink_700">#ffc2185b</color>
-    <color name="Pink_800">#ffad1457</color>
-    <color name="Red_700">#ffc53929</color>
-    <color name="Red_800">#ffb93221</color>
+    <color name="SIM_color_cyan">#ff006D74</color><!-- Material Custom Cyan -->
+    <color name="SIM_dark_mode_color_cyan">#ff4DD0E1</color><!-- Material Cyan 300 -->
+    <color name="SIM_color_blue">#ff185ABC</color><!-- Material Blue 800 -->
+    <color name="SIM_dark_mode_color_blue">#ff8AB4F8</color><!-- Material Blue 300 -->
+    <color name="SIM_color_green">#ff137333</color><!-- Material Green 800 -->
+    <color name="SIM_dark_mode_color_green">#ff81C995</color><!-- Material Green 300 -->
+    <color name="SIM_color_purple">#ff7627bb</color><!-- Material Purple 800 -->
+    <color name="SIM_dark_mode_color_purple">#ffC58AF9</color><!-- Material Purple 300 -->
+    <color name="SIM_color_pink">#ffb80672</color><!-- Material Pink 800 -->
+    <color name="SIM_dark_mode_color_pink">#ffff8bcb</color><!-- Material Pink 300 -->
+    <color name="SIM_color_orange">#ff995400</color><!-- Material Custom Orange -->
+    <color name="SIM_dark_mode_color_orange">#fffcad70</color><!-- Material Orange 300 -->
 
     <color name="resize_shadow_start_color">#2a000000</color>
     <color name="resize_shadow_end_color">#00000000</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 23dd1b4..9a585a1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -342,22 +342,6 @@
     <!-- Mask to use when checking skb mark defined in config_networkWakeupPacketMark above. -->
     <integer name="config_networkWakeupPacketMask">0</integer>
 
-    <!-- Whether the APF Filter in the device should filter out IEEE 802.3 Frames
-         Those frames are identified by the field Eth-type having values
-         less than 0x600 -->
-    <bool translatable="false" name="config_apfDrop802_3Frames">true</bool>
-
-    <!-- An array of Denylisted EtherType, packets with EtherTypes within this array
-         will be dropped
-         TODO: need to put proper values, these are for testing purposes only -->
-    <integer-array translatable="false" name="config_apfEthTypeBlackList">
-        <item>0x88A2</item>
-        <item>0x88A4</item>
-        <item>0x88B8</item>
-        <item>0x88CD</item>
-        <item>0x88E3</item>
-    </integer-array>
-
     <!-- Default value for ConnectivityManager.getMultipathPreference() on metered networks. Actual
          device behaviour is controlled by Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE.
          This is the default value of that setting. -->
@@ -1844,6 +1828,14 @@
          config_enableFusedLocationOverlay is false. -->
     <string name="config_fusedLocationProviderPackageName" translatable="false">com.android.location.fused</string>
 
+    <!-- If true will use the GNSS hardware implementation to service the GPS_PROVIDER. If false
+         will allow the GPS_PROVIDER to be replaced by an app at run-time (restricted to the package
+         specified by config_gnssLocationProviderPackageName). -->
+    <bool name="config_useGnssHardwareProvider" translatable="false">true</bool>
+    <!-- Package name providing GNSS location support. Used only when
+         config_useGnssHardwareProvider is false. -->
+    <string name="config_gnssLocationProviderPackageName" translatable="false">@null</string>
+
     <!-- Default value for the ADAS GNSS Location Enabled setting if this setting has never been
          set before. -->
     <bool name="config_defaultAdasGnssLocationEnabled" translatable="false">false</bool>
@@ -1871,6 +1863,10 @@
     -->
     <string name="config_defaultCaptivePortalLoginPackageName" translatable="false">com.android.captiveportallogin</string>
 
+    <!-- The package name of the dock manager app. Must be granted the
+         POST_NOTIFICATIONS permission. -->
+    <string name="config_defaultDockManagerPackageName" translatable="false"></string>
+
     <!-- Whether to enable geocoder overlay which allows geocoder to be replaced
          by an app at run-time. When disabled, only the
          config_geocoderProviderPackageName package will be searched for
@@ -2027,10 +2023,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>
 
@@ -2086,6 +2078,13 @@
     <!-- Flag indicating whether the current device supports "Ask every time" for sms-->
     <bool name="config_sms_ask_every_time_support">true</bool>
 
+    <!-- Flag indicating whether the current device allows acknowledgement of SIM operation like
+         SM-PP or saving SMS to SIM can be done via the IMS interfaces.
+         If true,this means that the device supports sending of sim operation response via the
+         IMS interface APIs. This can be overridden to false for devices which can't send
+         sim operation acknowledgements via IMS interface APIs. -->
+    <bool name="config_smppsim_response_via_ims">false</bool>
+
     <!-- Flag indicating whether the current device allows data.
          If true, this means that the device supports data connectivity through
          the telephony network.
@@ -2442,22 +2441,24 @@
     <integer name="config_dreamsBatteryLevelDrainCutoff">5</integer>
     <!-- Limit of how long the device can remain unlocked due to attention checking.  -->
     <integer name="config_attentionMaximumExtension">900000</integer> <!-- 15 minutes.  -->
-    <!-- Is the system user the only user allowed to dream. -->
-    <bool name="config_dreamsOnlyEnabledForSystemUser">false</bool>
+    <!-- Whether there is to be a chosen Dock User who is the only user allowed to dream. -->
+    <bool name="config_dreamsOnlyEnabledForDockUser">false</bool>
     <!-- Whether dreams are disabled when ambient mode is suppressed. -->
     <bool name="config_dreamsDisabledByAmbientModeSuppressionConfig">false</bool>
 
     <!-- The duration in milliseconds of the dream opening animation.  -->
     <integer name="config_dreamOpenAnimationDuration">250</integer>
     <!-- The duration in milliseconds of the dream closing animation.  -->
-    <integer name="config_dreamCloseAnimationDuration">100</integer>
+    <integer name="config_dreamCloseAnimationDuration">300</integer>
 
     <!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to
          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
@@ -2690,9 +2691,9 @@
          Should be false for most devices, except automotive vehicle with passenger displays. -->
     <bool name="config_multiuserUsersOnSecondaryDisplays">false</bool>
 
-    <!-- Whether to automatically switch a non-primary user back to the primary user after a
-         timeout when the device is docked.  -->
-    <bool name="config_enableTimeoutToUserZeroWhenDocked">false</bool>
+    <!-- Whether to automatically switch to the designated Dock User (the user chosen for
+         displaying dreams, etc.) after a timeout when the device is docked.  -->
+    <bool name="config_enableTimeoutToDockUserWhenDocked">false</bool>
 
     <!-- Whether to only install system packages on a user if they're allowlisted for that user
          type. These are flags and can be freely combined.
@@ -2903,7 +2904,7 @@
             >com.android.systemui/com.android.systemui.usb.UsbDebuggingActivity</string>
 
     <!-- Name of the activity that prompts the secondary user to acknowledge they need to
-         switch to the primary user to enable USB debugging.
+         switch to an admin user to enable USB debugging.
          Can be customized for other product types -->
     <string name="config_customAdbPublicKeyConfirmationSecondaryUserComponent"
             >com.android.systemui/com.android.systemui.usb.UsbDebuggingSecondaryUserActivity</string>
@@ -2915,7 +2916,7 @@
             >com.android.systemui/com.android.systemui.wifi.WifiDebuggingActivity</string>
 
     <!-- Name of the activity that prompts the secondary user to acknowledge they need to
-         switch to the primary user to enable wireless debugging.
+         switch to an admin user to enable wireless debugging.
          Can be customized for other product types -->
     <string name="config_customAdbWifiNetworkConfirmationSecondaryUserComponent"
             >com.android.systemui/com.android.systemui.wifi.WifiDebuggingSecondaryUserActivity</string>
@@ -2942,6 +2943,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"
@@ -2973,6 +2977,10 @@
     <string name="config_carrierAppInstallDialogComponent" translatable="false"
             >com.android.simappdialog/com.android.simappdialog.InstallCarrierAppActivity</string>
 
+    <!-- Name of the dialog that is used to get or save an app credential -->
+    <string name="config_credentialManagerDialogComponent" translatable="false"
+            >com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string>
+
     <!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
     <string name="config_appsAuthorizedForSharedAccounts" translatable="false">;com.android.settings;</string>
 
@@ -3695,6 +3703,12 @@
          experience while the device is non-interactive. -->
     <bool name="config_emergencyGestureEnabled">true</bool>
 
+    <!-- Default value for Use Emergency SOS in Settings false = disabled, true = enabled -->
+    <bool name="config_defaultEmergencyGestureEnabled">true</bool>
+
+    <!-- Default value for Use Play countdown alarm in Settings false = disabled, true = enabled -->
+    <bool name="config_defaultEmergencyGestureSoundEnabled">false</bool>
+
     <!-- Allow the gesture power + volume up to change the ringer mode while the device
          is interactive. -->
     <bool name="config_volumeHushGestureEnabled">true</bool>
@@ -3979,7 +3993,7 @@
     <!-- Colon separated list of package names that should be granted DND access -->
     <string name="config_defaultDndAccessPackages" translatable="false">com.android.camera2</string>
 
-    <!-- User restrictions set when the first user is created.
+    <!-- User restrictions set on the SYSTEM user when it is first created.
          Note: Also update appropriate overlay files. -->
     <string-array translatable="false" name="config_defaultFirstUserRestrictions">
     </string-array>
@@ -4150,6 +4164,12 @@
    -->
     <string name="config_defaultAmbientContextDetectionService" translatable="false"></string>
 
+    <!-- The component name for the default system wearable sensing service.
+        This service must be trusted, as it can be activated without explicit consent of the user.
+        See android.service.wearable.WearableSensingService.
+   -->
+    <string name="config_defaultWearableSensingService" translatable="false"></string>
+
     <!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent. -->
     <string translatable="false" name="config_defaultAmbientContextConsentComponent"></string>
 
@@ -4421,17 +4441,21 @@
         or empty if the default should be used. -->
     <string translatable="false" name="config_deviceSpecificDeviceStatePolicyProvider"></string>
 
+    <!-- Class name of the device specific implementation of InputMethodManagerService
+        or empty if the default should be used. -->
+    <string translatable="false" name="config_deviceSpecificInputMethodManagerService"></string>
+
     <!-- Component name of media projection permission dialog -->
     <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 +4969,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. -->
@@ -5340,6 +5369,12 @@
     <bool name="config_cecRoutingControlDisabled_allowed">true</bool>
     <bool name="config_cecRoutingControlDisabled_default">true</bool>
 
+    <bool name="config_cecSoundbarMode_userConfigurable">true</bool>
+    <bool name="config_cecSoundbarModeEnabled_allowed">true</bool>
+    <bool name="config_cecSoundbarModeEnabled_default">false</bool>
+    <bool name="config_cecSoundbarModeDisabled_allowed">true</bool>
+    <bool name="config_cecSoundbarModeDisabled_default">true</bool>
+
     <bool name="config_cecPowerControlMode_userConfigurable">true</bool>
     <bool name="config_cecPowerControlModeTv_allowed">true</bool>
     <bool name="config_cecPowerControlModeTv_default">false</bool>
@@ -5525,6 +5560,13 @@
     <bool name="config_cecQuerySadMaxDisabled_allowed">true</bool>
     <bool name="config_cecQuerySadMaxDisabled_default">false</bool>
 
+    <!-- eARC Configuration -->
+    <bool name="config_earcEnabled_userConfigurable">true</bool>
+    <bool name="config_earcFeatureEnabled_allowed">true</bool>
+    <bool name="config_earcFeatureEnabled_default">true</bool>
+    <bool name="config_earcFeatureDisabled_allowed">true</bool>
+    <bool name="config_earcFeatureDisabled_default">false</bool>
+
     <!-- Whether app hibernation deletes OAT artifact files as part of global hibernation. -->
     <bool name="config_hibernationDeletesOatArtifactsEnabled">true</bool>
 
@@ -5945,4 +5987,40 @@
     <!-- Whether the lock screen is allowed to run its own live wallpaper,
          different from the home screen wallpaper. -->
     <bool name="config_independentLockscreenLiveWallpaper">false</bool>
+
+    <!-- List of certificate to be used for font fs-verity integrity verification -->
+    <string-array translatable="false" name="config_fontManagerServiceCerts">
+    </string-array>
+
+    <!-- A string config in svg path format for the main display shape.
+         (@see https://www.w3.org/TR/SVG/paths.html#PathData).
+
+         This config must be set unless:
+         1. {@link Configuration#isScreenRound} is true which means the display shape is circular
+            and the system will auto-generate a circular shape.
+         2. The display has no rounded corner and the system will auto-generate a rectangular shape.
+         (@see DisplayShape#createDefaultDisplayShape)
+
+         Note: If the display supports multiple resolutions, please define the path config based on
+         the highest resolution so that it can be scaled correctly in each resolution. -->
+    <string name="config_mainDisplayShape" translatable="false"></string>
+
+    <!-- A string config in svg path format for the secondary display shape.
+         (@see https://www.w3.org/TR/SVG/paths.html#PathData).
+
+         This config must be set unless:
+         1. {@link Configuration#isScreenRound} is true which means the display shape is circular
+            and the system will auto-generate a circular shape.
+         2. The display has no rounded corner and the system will auto-generate a rectangular shape.
+         (@see DisplayShape#createDefaultDisplayShape)
+
+         Note: If the display supports multiple resolutions, please define the path config based on
+         the highest resolution so that it can be scaled correctly in each resolution. -->
+    <string name="config_secondaryDisplayShape" translatable="false"></string>
+
+    <!-- The display shape config for each display in a multi-display device. -->
+    <string-array name="config_displayShapeArray" translatable="false">
+        <item>@string/config_mainDisplayShape</item>
+        <item>@string/config_secondaryDisplayShape</item>
+    </string-array>
 </resources>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index f2a16d3..d40adf5 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -118,6 +118,11 @@
     <bool name="config_using_subscription_manager_service">false</bool>
     <java-symbol type="bool" name="config_using_subscription_manager_service" />
 
+    <!-- Whether asynchronously update the subscription database or not. Async mode increases
+         the performance, but sync mode reduces the chance of database/cache out-of-sync. -->
+    <bool name="config_subscription_database_async_update">true</bool>
+    <java-symbol type="bool" name="config_subscription_database_async_update" />
+
     <!-- Boolean indicating whether the emergency numbers for a country, sourced from modem/config,
          should be ignored if that country is 'locked' (i.e. ignore_modem_config set to true) in
          Android Emergency DB. If this value is true, emergency numbers for a country, sourced from
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/public-staging.xml b/core/res/res/values/public-staging.xml
index 61229cb..a9bec7a9 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -116,7 +116,8 @@
     <public name="handwritingBoundsOffsetBottom" />
     <public name="accessibilityDataPrivate" />
     <public name="enableTextStylingShortcuts" />
-    <public name="targetDisplayCategory"/>
+    <public name="requiredDisplayCategory"/>
+    <public name="maxConcurrentSessionsCount" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01cd0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 509de33..18a5c72 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -46,6 +46,9 @@
     <!-- Displayed when the user dialed an MMI code whose function
          could not be performed. This will be displayed in a toast. -->
     <string name="mmiError">Connection problem or invalid MMI code.</string>
+    <!-- Displayed when the user dialed an MMI code whose function could not be performed because
+         the feature is not supported on the current mobile network. -->
+    <string name="mmiErrorNotSupported">Feature not supported.</string>
     <!-- Displayed when the user dialed an MMI code whose function
          could not be performed because FDN is enabled. This will be displayed in a toast. -->
     <string name="mmiFdnError">Operation is restricted to fixed dialing numbers only.</string>
@@ -1130,6 +1133,16 @@
     <string name="permdesc_useDataInBackground">This app can use data in the background. This may increase data usage.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_schedule_exact_alarm">Schedule precisely timed actions</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_schedule_exact_alarm">This app can schedule work to happen at a desired time in the future. This also means that the app can run when you\u2019re not actively using the device.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_use_exact_alarm">Schedule alarms or event reminders</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_use_exact_alarm">This app can schedule actions like alarms and reminders to notify you at a desired time in the future.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_persistentActivity">make app always run</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_persistentActivity" product="tablet">Allows the app to make parts of itself persistent in memory.  This can limit memory available to other apps slowing down the tablet.</string>
@@ -1143,6 +1156,71 @@
     <string name="permdesc_foregroundService">Allows the app to make use of foreground services.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceCamera">run foreground service with the type \"camera\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceCamera">Allows the app to make use of foreground services with the type \"camera\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceConnectedDevice">run foreground service with the type \"connectedDevice\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceConnectedDevice">Allows the app to make use of foreground services with the type \"connectedDevice\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceDataSync">run foreground service with the type \"dataSync\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceDataSync">Allows the app to make use of foreground services with the type \"dataSync\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceLocation">run foreground service with the type \"location\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceLocation">Allows the app to make use of foreground services with the type \"location\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceMediaPlayback">run foreground service with the type \"mediaPlayback\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceMediaPlayback">Allows the app to make use of foreground services with the type \"mediaPlayback\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceMediaProjection">run foreground service with the type \"mediaProjection\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceMediaProjection">Allows the app to make use of foreground services with the type \"mediaProjection\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceMicrophone">run foreground service with the type \"microphone\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceMicrophone">Allows the app to make use of foreground services with the type \"microphone\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServicePhoneCall">run foreground service with the type \"phoneCall\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServicePhoneCall">Allows the app to make use of foreground services with the type \"phoneCall\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceHealth">run foreground service with the type \"health\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceHealth">Allows the app to make use of foreground services with the type \"health\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceRemoteMessaging">run foreground service with the type \"remoteMessaging\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceRemoteMessaging">Allows the app to make use of foreground services with the type \"remoteMessaging\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceSystemExempted">run foreground service with the type \"systemExempted\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceSystemExempted">Allows the app to make use of foreground services with the type \"systemExempted\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceFileManagement">run foreground service with the type \"fileManagement\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceFileManagement">Allows the app to make use of foreground services with the type \"fileManagement\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceSpecialUse">run foreground service with the type \"specialUse\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceSpecialUse">Allows the app to make use of foreground services with the type \"specialUse\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_getPackageSize">measure app storage space</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_getPackageSize">Allows the app to retrieve its code, data, and cache sizes</string>
@@ -1302,6 +1380,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
     <string name="permdesc_recordBackgroundAudio">This app can record audio using the microphone at any time.</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+    <string name="permlab_detectScreenCapture">detect screen captures of app windows</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
+    <string name="permdesc_detectScreenCapture">This app will get notified when a screenshot is taken while the app is in use.</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_sim_communication">send commands to the SIM</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1933,6 +2016,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] -->
@@ -5754,32 +5842,6 @@
     <!-- Title for the harmful app warning dialog. [CHAR LIMIT=40] -->
     <string name="harmful_app_warning_title">Harmful app detected</string>
 
-    <!-- Title for the log access confirmation dialog. [CHAR LIMIT=NONE] -->
-    <string name="log_access_confirmation_title">Allow <xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> to access all device logs?</string>
-    <!-- Label for the allow button on the log access confirmation dialog. [CHAR LIMIT=40] -->
-    <string name="log_access_confirmation_allow">Allow one-time access</string>
-    <!-- Label for the deny button on the log access confirmation dialog. [CHAR LIMIT=20] -->
-    <string name="log_access_confirmation_deny">Don\u2019t allow</string>
-
-    <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
-    <string name="log_access_confirmation_body" product="default">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
-        \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.
-    </string>
-
-    <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
-    <string name="log_access_confirmation_body" product="tv">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
-        \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs.
-    </string>
-
-    <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
-    <string name="log_access_confirmation_learn_more" product="default" translatable="false">&lt;a href="https://support.google.com/android?p=system_logs#topic=7313011"&gt;Learn more&lt;/a&gt;</string>
-
-    <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
-    <string name="log_access_confirmation_learn_more" product="tv" translatable="false"></string>
-
-    <!-- Privacy notice do not show [CHAR LIMIT=20] -->
-    <string name="log_access_do_not_show_again">Don\u2019t show again</string>
-
     <!-- Text describing a permission request for one app to show another app's
          slices [CHAR LIMIT=NONE] -->
     <string name="slices_permission_request"><xliff:g id="app" example="Example App">%1$s</xliff:g> wants to show <xliff:g id="app_2" example="Other Example App">%2$s</xliff:g> slices</string>
@@ -6347,10 +6409,30 @@
     <string name="vdm_camera_access_denied" product="tablet">Can’t access the tablet’s camera from your <xliff:g id="device" example="Chromebook">%1$s</xliff:g></string>
     <!-- Error message indicating the user cannot access secure content when running on a virtual device. [CHAR LIMIT=NONE] -->
     <string name="vdm_secure_window">This can’t be accessed while streaming. Try on your phone instead.</string>
+    <!-- Error message indicating the user cannot view picture-in-picture when running on a virtual device. [CHAR LIMIT=NONE] -->
+    <string name="vdm_pip_blocked">Can’t view picture-in-picture while streaming</string>
 
     <!-- Title for preference of the system default locale. [CHAR LIMIT=50]-->
     <string name="system_locale_title">System default</string>
 
     <!-- Display content to tell the user the sim card name and number-->
     <string name="default_card_name">CARD <xliff:g id="cardNumber" example="1">%d</xliff:g></string>
+
+    <!-- Strings for CompanionDeviceManager -->
+    <!-- Title of Watch profile permission, which allows a companion app to manage watches. [CHAR LIMIT=NONE] -->
+    <string name="permlab_companionProfileWatch">Companion Watch profile permission to manage watches</string>
+    <!-- Description of Watch profile permission, which allows a companion app to manage watches. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_companionProfileWatch">Allows a companion app to manage watches.</string>
+    <!-- Title of observing device presence permission, which allows a companion app to observe the presence of the associated devices. [CHAR LIMIT=NONE] -->
+    <string name="permlab_observeCompanionDevicePresence">Observe companion device presence</string>
+    <!-- Description of observing device presence permission, which allows a companion app to observe the presence of the associated devices. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_observeCompanionDevicePresence">Allows a companion app to observe companion device presence when the devices are nearby or far-away.</string>
+    <!-- Title of delivering companion messages permission, which allows a companion app to deliver messages to other devices. [CHAR LIMIT=NONE] -->
+    <string name="permlab_deliverCompanionMessages">Deliver companion messages</string>
+    <!-- Description of delivering companion messages permission, which allows a companion app to deliver messages to other devices. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_deliverCompanionMessages">Allows a companion app to deliver companion messages to other devices.</string>
+    <!-- Title of start foreground services from background permission [CHAR LIMIT=NONE] -->
+    <string name="permlab_startForegroundServicesFromBackground">Start foreground services from background</string>
+    <!-- Description of start foreground services from background permission [CHAR LIMIT=NONE] -->
+    <string name="permdesc_startForegroundServicesFromBackground">Allows a companion app to start foreground services from background.</string>
 </resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 2dd563d..476c18e 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1541,40 +1541,4 @@
     <style name="NotificationTombstoneAction" parent="NotificationAction">
       <item name="textColor">#555555</item>
     </style>
-
-    <!-- The style for log access consent text -->
-    <style name="AllowLogAccess">
-        <item name="android:textSize">24sp</item>
-        <item name="android:fontFamily">google-sans</item>
-    </style>
-
-    <style name="PrimaryAllowLogAccess">
-        <item name="android:textSize">14sp</item>
-        <item name="android:fontFamily">google-sans-text</item>
-    </style>
-
-    <style name="PermissionGrantButtonTextAppearance">
-        <item name="android:fontFamily">google-sans-medium</item>
-        <item name="android:textSize">14sp</item>
-        <item name="android:textColor">@android:color/system_neutral1_900</item>
-    </style>
-
-    <style name="PermissionGrantButtonTop"
-           parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored">
-        <item name="android:layout_width">332dp</item>
-        <item name="android:layout_height">56dp</item>
-        <item name="android:layout_marginTop">2dp</item>
-        <item name="android:layout_marginBottom">2dp</item>
-        <item name="android:background">@drawable/grant_permissions_buttons_top</item>
-    </style>
-
-    <style name="PermissionGrantButtonBottom"
-           parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored">
-        <item name="android:layout_width">332dp</item>
-        <item name="android:layout_height">56dp</item>
-        <item name="android:layout_marginTop">2dp</item>
-        <item name="android:layout_marginBottom">2dp</item>
-        <item name="android:background">@drawable/grant_permissions_buttons_bottom</item>
-    </style>
-
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 476d36d..ace7e4c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -153,6 +153,7 @@
   <java-symbol type="id" name="progress_horizontal" />
   <java-symbol type="id" name="progress_number" />
   <java-symbol type="id" name="progress_percent" />
+  <java-symbol type="id" name="progress_dialog_message" />
   <java-symbol type="id" name="progressContainer" />
   <java-symbol type="id" name="rew" />
   <java-symbol type="id" name="rightSpacer" />
@@ -282,7 +283,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" />
@@ -315,6 +315,7 @@
   <java-symbol type="bool" name="config_sms_ask_every_time_support" />
   <java-symbol type="bool" name="config_sms_capable" />
   <java-symbol type="bool" name="config_sms_utf8_support" />
+  <java-symbol type="bool" name="config_smppsim_response_via_ims" />
   <java-symbol type="bool" name="config_mobile_data_capable" />
   <java-symbol type="bool" name="config_suspendWhenScreenOffDueToProximity" />
   <java-symbol type="bool" name="config_swipeDisambiguation" />
@@ -374,6 +375,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" />
@@ -466,7 +468,7 @@
   <java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
   <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
   <java-symbol type="bool" name="config_multiuserUsersOnSecondaryDisplays" />
-  <java-symbol type="bool" name="config_enableTimeoutToUserZeroWhenDocked" />
+  <java-symbol type="bool" name="config_enableTimeoutToDockUserWhenDocked" />
   <java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
   <java-symbol type="xml" name="config_user_types" />
   <java-symbol type="integer" name="config_safe_media_volume_index" />
@@ -483,6 +485,7 @@
   <java-symbol type="array" name="config_deviceSpecificSystemServices" />
   <java-symbol type="string" name="config_deviceSpecificDevicePolicyManagerService" />
   <java-symbol type="string" name="config_deviceSpecificAudioService" />
+  <java-symbol type="string" name="config_deviceSpecificInputMethodManagerService" />
   <java-symbol type="integer" name="config_num_physical_slots" />
   <java-symbol type="integer" name="config_default_cellular_usage_setting" />
   <java-symbol type="array" name="config_supported_cellular_usage_settings" />
@@ -837,6 +840,7 @@
   <java-symbol type="string" name="mismatchPin" />
   <java-symbol type="string" name="mmiComplete" />
   <java-symbol type="string" name="mmiError" />
+  <java-symbol type="string" name="mmiErrorNotSupported" />
   <java-symbol type="string" name="mmiFdnError" />
   <java-symbol type="string" name="mmiErrorWhileRoaming" />
   <java-symbol type="string" name="month_day_year" />
@@ -1296,6 +1300,7 @@
   <java-symbol type="array" name="vendor_cross_profile_apps" />
   <java-symbol type="array" name="policy_exempt_apps" />
   <java-symbol type="array" name="vendor_policy_exempt_apps" />
+  <java-symbol type="array" name="cloneable_apps" />
 
   <java-symbol type="drawable" name="default_wallpaper" />
   <java-symbol type="drawable" name="default_lock_wallpaper" />
@@ -1690,15 +1695,6 @@
 
   <!-- From android.policy -->
   <java-symbol type="anim" name="app_starting_exit" />
-  <java-symbol type="anim" name="dock_top_enter" />
-  <java-symbol type="anim" name="dock_top_exit" />
-  <java-symbol type="anim" name="dock_bottom_enter" />
-  <java-symbol type="anim" name="dock_bottom_exit" />
-  <java-symbol type="anim" name="dock_bottom_exit_keyguard" />
-  <java-symbol type="anim" name="dock_left_enter" />
-  <java-symbol type="anim" name="dock_left_exit" />
-  <java-symbol type="anim" name="dock_right_enter" />
-  <java-symbol type="anim" name="dock_right_exit" />
   <java-symbol type="anim" name="fade_in" />
   <java-symbol type="anim" name="fade_out" />
   <java-symbol type="anim" name="voice_activity_close_exit" />
@@ -1968,6 +1964,7 @@
   <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
   <java-symbol type="bool" name="config_defaultAdasGnssLocationEnabled" />
   <java-symbol type="bool" name="config_enableFusedLocationOverlay" />
+  <java-symbol type="bool" name="config_useGnssHardwareProvider" />
   <java-symbol type="bool" name="config_enableGeocoderOverlay" />
   <java-symbol type="bool" name="config_enableGeofenceOverlay" />
   <java-symbol type="bool" name="config_enableNetworkLocationOverlay" />
@@ -2045,8 +2042,6 @@
   <java-symbol type="integer" name="config_networkAvoidBadWifi" />
   <java-symbol type="integer" name="config_networkWakeupPacketMark" />
   <java-symbol type="integer" name="config_networkWakeupPacketMask" />
-  <java-symbol type="bool" name="config_apfDrop802_3Frames" />
-  <java-symbol type="array" name="config_apfEthTypeBlackList" />
   <java-symbol type="integer" name="config_networkDefaultDailyMultipathQuotaBytes" />
   <java-symbol type="integer" name="config_networkMeteredMultipathPreference" />
   <java-symbol type="array" name="config_networkSupportedKeepaliveCount" />
@@ -2132,6 +2127,7 @@
   <java-symbol type="string" name="config_datause_iface" />
   <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" />
   <java-symbol type="string" name="config_fusedLocationProviderPackageName" />
+  <java-symbol type="string" name="config_gnssLocationProviderPackageName" />
   <java-symbol type="string" name="config_geocoderProviderPackageName" />
   <java-symbol type="string" name="config_geofenceProviderPackageName" />
   <java-symbol type="string" name="config_networkLocationProviderPackageName" />
@@ -2237,7 +2233,7 @@
   <java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" />
   <java-symbol type="string" name="config_dreamsDefaultComponent" />
   <java-symbol type="bool" name="config_dreamsDisabledByAmbientModeSuppressionConfig" />
-  <java-symbol type="bool" name="config_dreamsOnlyEnabledForSystemUser" />
+  <java-symbol type="bool" name="config_dreamsOnlyEnabledForDockUser" />
   <java-symbol type="integer" name="config_dreamOpenAnimationDuration" />
   <java-symbol type="integer" name="config_dreamCloseAnimationDuration" />
   <java-symbol type="array" name="config_supportedDreamComplications" />
@@ -2246,7 +2242,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" />
@@ -2265,6 +2261,7 @@
   <java-symbol type="string" name="config_customVpnAlwaysOnDisconnectedDialogComponent" />
   <java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
   <java-symbol type="string" name="config_carrierAppInstallDialogComponent" />
+  <java-symbol type="string" name="config_credentialManagerDialogComponent" />
   <java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
   <java-symbol type="string" name="config_persistentDataPackageName" />
   <java-symbol type="string" name="config_deviceConfiguratorPackageName" />
@@ -2309,6 +2306,7 @@
   <java-symbol type="id" name="media_actions" />
 
   <java-symbol type="dimen" name="config_mediaMetadataBitmapMaxSize" />
+  <java-symbol type="array" name="config_fontManagerServiceCerts" />
 
     <!-- From SystemUI -->
   <java-symbol type="anim" name="push_down_in" />
@@ -2725,6 +2723,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" />
@@ -3022,6 +3021,8 @@
   <java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
   <java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
   <java-symbol type="bool" name="config_emergencyGestureEnabled" />
+  <java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" />
+  <java-symbol type="bool" name="config_defaultEmergencyGestureSoundEnabled" />
   <java-symbol type="bool" name="config_volumeHushGestureEnabled" />
 
   <java-symbol type="drawable" name="platlogo_m" />
@@ -3445,7 +3446,7 @@
   <java-symbol type="array" name="config_displayWhiteBalanceDisplayNominalWhite" />
   <java-symbol type="bool" name="config_displayWhiteBalanceLightModeAllowed" />
 
-  <!-- Default first user restrictions -->
+  <!-- Default user restrictions for the SYSTEM user -->
   <java-symbol type="array" name="config_defaultFirstUserRestrictions" />
 
   <java-symbol type="bool" name="config_permissionsIndividuallyControlled" />
@@ -3478,6 +3479,9 @@
   <!-- Captive Portal Login -->
   <java-symbol type="string" name="config_defaultCaptivePortalLoginPackageName" />
 
+  <!-- Dock Manager -->
+  <java-symbol type="string" name="config_defaultDockManagerPackageName" />
+
   <!-- Optional IPsec algorithms -->
   <java-symbol type="array" name="config_optionalIpSecAlgorithms" />
 
@@ -3768,6 +3772,7 @@
   <java-symbol type="string" name="config_defaultAmbientContextConsentComponent" />
   <java-symbol type="string" name="config_ambientContextPackageNameExtraKey" />
   <java-symbol type="string" name="config_ambientContextEventArrayExtraKey" />
+  <java-symbol type="string" name="config_defaultWearableSensingService" />
   <java-symbol type="string" name="config_retailDemoPackage" />
   <java-symbol type="string" name="config_retailDemoPackageSignature" />
 
@@ -3938,17 +3943,6 @@
   <java-symbol type="string" name="harmful_app_warning_title" />
   <java-symbol type="layout" name="harmful_app_warning_dialog" />
 
-  <java-symbol type="string" name="log_access_confirmation_allow" />
-  <java-symbol type="string" name="log_access_confirmation_deny" />
-  <java-symbol type="string" name="log_access_confirmation_title" />
-  <java-symbol type="string" name="log_access_confirmation_body" />
-  <java-symbol type="string" name="log_access_confirmation_learn_more" />
-  <java-symbol type="layout" name="log_access_user_consent_dialog_permission" />
-  <java-symbol type="id" name="log_access_dialog_title" />
-  <java-symbol type="id" name="log_access_dialog_body" />
-  <java-symbol type="id" name="log_access_dialog_allow_button" />
-  <java-symbol type="id" name="log_access_dialog_deny_button" />
-
   <java-symbol type="string" name="config_defaultAssistantAccessComponent" />
 
   <java-symbol type="string" name="slices_permission_request" />
@@ -4565,6 +4559,12 @@
   <java-symbol type="bool" name="config_cecRoutingControlDisabled_allowed" />
   <java-symbol type="bool" name="config_cecRoutingControlDisabled_default" />
 
+  <java-symbol type="bool" name="config_cecSoundbarMode_userConfigurable" />
+  <java-symbol type="bool" name="config_cecSoundbarModeEnabled_allowed" />
+  <java-symbol type="bool" name="config_cecSoundbarModeEnabled_default" />
+  <java-symbol type="bool" name="config_cecSoundbarModeDisabled_allowed" />
+  <java-symbol type="bool" name="config_cecSoundbarModeDisabled_default" />
+
   <java-symbol type="bool" name="config_cecPowerControlMode_userConfigurable" />
   <java-symbol type="bool" name="config_cecPowerControlModeTv_allowed" />
   <java-symbol type="bool" name="config_cecPowerControlModeTv_default" />
@@ -4750,6 +4750,13 @@
   <java-symbol type="bool" name="config_cecQuerySadMaxDisabled_allowed" />
   <java-symbol type="bool" name="config_cecQuerySadMaxDisabled_default" />
 
+  <!-- eARC Configuration -->
+  <java-symbol type="bool" name="config_earcEnabled_userConfigurable" />
+  <java-symbol type="bool" name="config_earcFeatureEnabled_allowed" />
+  <java-symbol type="bool" name="config_earcFeatureEnabled_default" />
+  <java-symbol type="bool" name="config_earcFeatureDisabled_allowed" />
+  <java-symbol type="bool" name="config_earcFeatureDisabled_default" />
+
   <!-- Ids for RemoteViews -->
   <java-symbol type="id" name="remote_views_next_child" />
   <java-symbol type="id" name="remote_views_stable_id" />
@@ -4867,6 +4874,7 @@
   <!-- For VirtualDeviceManager -->
   <java-symbol type="string" name="vdm_camera_access_denied" />
   <java-symbol type="string" name="vdm_secure_window" />
+  <java-symbol type="string" name="vdm_pip_blocked" />
 
   <java-symbol type="color" name="camera_privacy_light_day"/>
   <java-symbol type="color" name="camera_privacy_light_night"/>
@@ -4908,4 +4916,8 @@
   <java-symbol type="dimen" name="status_bar_height_default" />
 
   <java-symbol type="string" name="default_card_name"/>
+
+  <java-symbol type="string" name="config_mainDisplayShape"/>
+  <java-symbol type="string" name="config_secondaryDisplayShape"/>
+  <java-symbol type="array" name="config_displayShapeArray" />
 </resources>
diff --git a/core/res/res/xml/config_user_types.xml b/core/res/res/xml/config_user_types.xml
index 7663150..df6b7b2 100644
--- a/core/res/res/xml/config_user_types.xml
+++ b/core/res/res/xml/config_user_types.xml
@@ -83,6 +83,8 @@
 For profile and full users:
     default-restrictions (with values defined in UserRestrictionUtils.USER_RESTRICTIONS)
     enabled
+    user-properties
+    max-allowed
 For profile users only:
     max-allowed-per-parent
     icon-badge
diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp
index 7cb64c8..436f058 100644
--- a/core/tests/BroadcastRadioTests/Android.bp
+++ b/core/tests/BroadcastRadioTests/Android.bp
@@ -48,7 +48,7 @@
     ],
     // mockito-target-inline dependency
     jni_libs: [
-        "libcarservicejni",
         "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
     ],
 }
diff --git a/core/tests/BroadcastRadioTests/AndroidTest.xml b/core/tests/BroadcastRadioTests/AndroidTest.xml
new file mode 100644
index 0000000..ed88537
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?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 Broadcast Radio 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="test-file-name" value="BroadcastRadioTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="BroadcastRadioTests" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.hardware.radio.tests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
new file mode 100644
index 0000000..2fa3f876
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.radio.tests.unittests;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+
+import android.graphics.Bitmap;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public final class DefaultRadioTunerTest {
+
+    private static final RadioTuner DEFAULT_RADIO_TUNER = new RadioTuner() {
+        @Override
+        public void close() {}
+
+        @Override
+        public int setConfiguration(RadioManager.BandConfig config) {
+            return 0;
+        }
+
+        @Override
+        public int getConfiguration(RadioManager.BandConfig[] config) {
+            return 0;
+        }
+
+        @Override
+        public int setMute(boolean mute) {
+            return 0;
+        }
+
+        @Override
+        public boolean getMute() {
+            return false;
+        }
+
+        @Override
+        public int step(int direction, boolean skipSubChannel) {
+            return 0;
+        }
+
+        @Override
+        public int scan(int direction, boolean skipSubChannel) {
+            return 0;
+        }
+
+        @Override
+        public int tune(int channel, int subChannel) {
+            return 0;
+        }
+
+        @Override
+        public void tune(@NonNull ProgramSelector selector) {}
+
+        @Override
+        public int cancel() {
+            return 0;
+        }
+
+        @Override
+        public void cancelAnnouncement() {}
+
+        @Override
+        public int getProgramInformation(RadioManager.ProgramInfo[] info) {
+            return 0;
+        }
+
+        @Nullable
+        @Override
+        public Bitmap getMetadataImage(int id) {
+            return null;
+        }
+
+        @Override
+        public boolean startBackgroundScan() {
+            return false;
+        }
+
+        @NonNull
+        @Override
+        public List<RadioManager.ProgramInfo> getProgramList(
+                @Nullable Map<String, String> vendorFilter) {
+            return new ArrayList<>();
+        }
+
+        @Override
+        public boolean isAnalogForced() {
+            return false;
+        }
+
+        @Override
+        public void setAnalogForced(boolean isForced) {}
+
+        @Override
+        public boolean isAntennaConnected() {
+            return false;
+        }
+
+        @Override
+        public boolean hasControl() {
+            return false;
+        }
+    };
+
+    @Test
+    public void getDynamicProgramList_forRadioTuner_returnsNull() {
+        assertWithMessage("Dynamic program list obtained from default radio tuner")
+                .that(DEFAULT_RADIO_TUNER.getDynamicProgramList(new ProgramList.Filter())).isNull();
+    }
+
+    @Test
+    public void isConfigFlagSupported_forRadioTuner_throwsException() {
+        assertWithMessage("Dynamic program list obtained from default radio tuner")
+                .that(DEFAULT_RADIO_TUNER.isConfigFlagSupported(/* flag= */ 1)).isFalse();
+    }
+
+    @Test
+    public void isConfigFlagSet_forRadioTuner_throwsException() {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            DEFAULT_RADIO_TUNER.isConfigFlagSet(/* flag= */ 1);
+        });
+    }
+
+    @Test
+    public void setConfigFlag_forRadioTuner_throwsException() {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            DEFAULT_RADIO_TUNER.setConfigFlag(/* flag= */ 1, /* value= */ false);
+        });
+    }
+
+    @Test
+    public void setParameters_forRadioTuner_throwsException() {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            DEFAULT_RADIO_TUNER.setParameters(Map.of("testKey", "testValue"));
+        });
+    }
+
+    @Test
+    public void getParameters_forRadioTuner_throwsException() {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            DEFAULT_RADIO_TUNER.getParameters(List.of("testKey"));
+        });
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
new file mode 100644
index 0000000..2b9de18
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
@@ -0,0 +1,511 @@
+/*
+ * 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.radio.tests.unittests;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.hardware.radio.IRadioService;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+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.ArraySet;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.verification.VerificationWithTimeout;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class ProgramListTest {
+
+    private static final int CREATOR_ARRAY_SIZE = 3;
+    private static final VerificationWithTimeout CALLBACK_TIMEOUT = timeout(/* millis= */ 500);
+
+    private static final boolean IS_PURGE = false;
+    private static final boolean IS_COMPLETE = true;
+
+    private static final boolean INCLUDE_CATEGORIES = true;
+    private static final boolean EXCLUDE_MODIFICATIONS = false;
+
+    private static final ProgramSelector.Identifier FM_IDENTIFIER =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+                    /* value= */ 94300);
+    private static final ProgramSelector.Identifier RDS_IDENTIFIER =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI, 15019);
+    private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
+                    /* value= */ 0x10000111);
+    private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+                    /* value= */ 0x1013);
+    private static final RadioManager.ProgramInfo FM_PROGRAM_INFO = createFmProgramInfo(
+            createProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER));
+    private static final RadioManager.ProgramInfo RDS_PROGRAM_INFO = createFmProgramInfo(
+            createProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, RDS_IDENTIFIER));
+
+    private static final Set<Integer> FILTER_IDENTIFIER_TYPES = Set.of(
+            ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, ProgramSelector.IDENTIFIER_TYPE_RDS_PI);
+    private static final Set<ProgramSelector.Identifier> FILTER_IDENTIFIERS = Set.of(FM_IDENTIFIER);
+
+    private static final ProgramList.Chunk FM_RDS_ADD_CHUNK = new ProgramList.Chunk(IS_PURGE,
+            IS_COMPLETE, Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+            Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+    private static final ProgramList.Chunk FM_ADD_INCOMPLETE_CHUNK = new ProgramList.Chunk(IS_PURGE,
+            /* complete= */ false, Set.of(FM_PROGRAM_INFO), new ArraySet<>());
+    private static final ProgramList.Filter TEST_FILTER = new ProgramList.Filter(
+            FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+    private static final Map<String, String> VENDOR_FILTER = Map.of("testVendorKey1",
+            "testVendorValue1", "testVendorKey2", "testVendorValue2");
+
+    private final Executor mExecutor = new Executor() {
+        @Override
+        public void execute(Runnable command) {
+            command.run();
+        }
+    };
+
+    private RadioTuner mRadioTuner;
+    private ITunerCallback mTunerCallback;
+    private ProgramList mProgramList;
+
+    private ProgramList.ListCallback[] mListCallbackMocks;
+    private ProgramList.OnCompleteListener[] mOnCompleteListenerMocks;
+    @Mock
+    private IRadioService mRadioServiceMock;
+    @Mock
+    private Context mContextMock;
+    @Mock
+    private ITuner mTunerMock;
+    @Mock
+    private RadioTuner.Callback mTunerCallbackMock;
+
+    @Test
+    public void getIdentifierTypes_forFilter() {
+        ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+                FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+        assertWithMessage("Filtered identifier types").that(filter.getIdentifierTypes())
+                .containsExactlyElementsIn(FILTER_IDENTIFIER_TYPES);
+    }
+
+    @Test
+    public void getIdentifiers_forFilter() {
+        ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+                FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+        assertWithMessage("Filtered identifiers").that(filter.getIdentifiers())
+                .containsExactlyElementsIn(FILTER_IDENTIFIERS);
+    }
+
+    @Test
+    public void areCategoriesIncluded_forFilter() {
+        ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+                FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+        assertWithMessage("Filter including categories")
+                .that(filter.areCategoriesIncluded()).isEqualTo(INCLUDE_CATEGORIES);
+    }
+
+    @Test
+    public void areModificationsExcluded_forFilter() {
+        ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+                FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+        assertWithMessage("Filter excluding modifications")
+                .that(filter.areModificationsExcluded()).isEqualTo(EXCLUDE_MODIFICATIONS);
+    }
+
+    @Test
+    public void getVendorFilter_forFilterWithoutVendorFilter_returnsNull() {
+        ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+                FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+        assertWithMessage("Filter vendor obtained from filter without vendor filter")
+                .that(filter.getVendorFilter()).isNull();
+    }
+
+    @Test
+    public void getVendorFilter_forFilterWithVendorFilter() {
+        ProgramList.Filter vendorFilter = new ProgramList.Filter(VENDOR_FILTER);
+
+        assertWithMessage("Filter vendor obtained from filter with vendor filter")
+                .that(vendorFilter.getVendorFilter()).isEqualTo(VENDOR_FILTER);
+    }
+
+    @Test
+    public void describeContents_forFilter() {
+        assertWithMessage("Filter contents").that(TEST_FILTER.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void hashCode_withTheSameFilters_equals() {
+        ProgramList.Filter filterCompared = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+                FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+        assertWithMessage("Hash code of the same filter")
+                .that(filterCompared.hashCode()).isEqualTo(TEST_FILTER.hashCode());
+    }
+
+    @Test
+    public void hashCode_withDifferentFilters_notEquals() {
+        ProgramList.Filter filterCompared = new ProgramList.Filter();
+
+        assertWithMessage("Hash code of the different filter")
+                .that(filterCompared.hashCode()).isNotEqualTo(TEST_FILTER.hashCode());
+    }
+
+    @Test
+    public void writeToParcel_forFilter() {
+        Parcel parcel = Parcel.obtain();
+
+        TEST_FILTER.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        ProgramList.Filter filterFromParcel =
+                ProgramList.Filter.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Filter created from parcel")
+                .that(filterFromParcel).isEqualTo(TEST_FILTER);
+    }
+
+    @Test
+    public void newArray_forFilterCreator() {
+        ProgramList.Filter[] filters = ProgramList.Filter.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("Program filters").that(filters).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
+    public void isPurge_forChunk() {
+        ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
+                Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+
+        assertWithMessage("Puring chunk").that(chunk.isPurge()).isEqualTo(IS_PURGE);
+    }
+
+    @Test
+    public void isComplete_forChunk() {
+        ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
+                Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+
+        assertWithMessage("Complete chunk").that(chunk.isComplete()).isEqualTo(IS_COMPLETE);
+    }
+
+    @Test
+    public void getModified_forChunk() {
+        ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
+                Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+
+        assertWithMessage("Modified program info in chunk")
+                .that(chunk.getModified()).containsExactly(FM_PROGRAM_INFO, RDS_PROGRAM_INFO);
+    }
+
+    @Test
+    public void getRemoved_forChunk() {
+        ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
+                Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+
+        assertWithMessage("Removed program identifiers in chunk").that(chunk.getRemoved())
+                .containsExactly(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER);
+    }
+
+    @Test
+    public void describeContents_forChunk() {
+        assertWithMessage("Chunk contents").that(FM_RDS_ADD_CHUNK.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void writeToParcel_forChunk() {
+        Parcel parcel = Parcel.obtain();
+
+        FM_RDS_ADD_CHUNK.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        ProgramList.Chunk chunkFromParcel =
+                ProgramList.Chunk.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Chunk created from parcel")
+                .that(chunkFromParcel).isEqualTo(FM_RDS_ADD_CHUNK);
+    }
+
+    @Test
+    public void newArray_forChunkCreator() {
+        ProgramList.Chunk[] chunks = ProgramList.Chunk.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("Chunks").that(chunks).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
+    public void getDynamicProgramList_forTunerAdapter() throws Exception {
+        createRadioTuner();
+
+        mRadioTuner.getDynamicProgramList(TEST_FILTER);
+
+        verify(mTunerMock).startProgramListUpdates(TEST_FILTER);
+    }
+
+    @Test
+    public void getDynamicProgramList_forTunerAdapterWithServiceDied_throwsException()
+            throws Exception {
+        createRadioTuner();
+        doThrow(new RemoteException()).when(mTunerMock).startProgramListUpdates(any());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+            mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        });
+
+        assertWithMessage("Exception for radio HAL client service died")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
+    public void onProgramListUpdated_withNewIdsAdded_invokesMockedCallbacks() throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(/* numCallbacks= */ 1);
+        addOnCompleteListeners(/* numListeners= */ 1);
+
+        mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(RDS_IDENTIFIER);
+        verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT).onComplete();
+        assertWithMessage("Program info in program list after adding FM and RDS info")
+                .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, RDS_PROGRAM_INFO);
+    }
+
+    @Test
+    public void onProgramListUpdated_withIdsRemoved_invokesMockedCallbacks() throws Exception {
+        ProgramList.Chunk fmRemovedChunk = new ProgramList.Chunk(/* purge= */ false,
+                /* complete= */ false, new ArraySet<>(), Set.of(FM_IDENTIFIER));
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(/* numCallbacks= */ 1);
+        mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+        mTunerCallback.onProgramListUpdated(fmRemovedChunk);
+
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
+        assertWithMessage("Program info in program list after removing FM id")
+                .that(mProgramList.toList()).containsExactly(RDS_PROGRAM_INFO);
+        assertWithMessage("Program info FM identifier")
+                .that(mProgramList.get(RDS_IDENTIFIER)).isEqualTo(RDS_PROGRAM_INFO);
+    }
+
+    @Test
+    public void onProgramListUpdated_withIncompleteChunk_notInvokesOnCompleteListener()
+            throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        addOnCompleteListeners(/* numListeners= */ 1);
+
+        mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+
+        verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT.times(0)).onComplete();
+    }
+
+    @Test
+    public void onProgramListUpdated_withPurgeChunk() throws Exception {
+        ProgramList.Chunk purgeChunk = new ProgramList.Chunk(/* purge= */ true,
+                /* complete= */ true, new ArraySet<>(), new ArraySet<>());
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(/* numCallbacks= */ 1);
+        mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+        mTunerCallback.onProgramListUpdated(purgeChunk);
+
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(RDS_IDENTIFIER);
+        assertWithMessage("Program list after purge chunk applied")
+                .that(mProgramList.toList()).isEmpty();
+    }
+
+    @Test
+    public void onItemChanged_forListCallbackRegisteredWithExecutor_invokesWhenIdAdded()
+            throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        ProgramList.ListCallback listCallbackMock = mock(ProgramList.ListCallback.class);
+        mProgramList.registerListCallback(mExecutor, listCallbackMock);
+
+        mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+
+        verify(listCallbackMock, CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+    }
+
+    @Test
+    public void onItemRemoved_forListCallbackRegisteredWithExecutor_invokesWhenIdRemoved()
+            throws Exception {
+        ProgramList.Chunk purgeChunk = new ProgramList.Chunk(/* purge= */ true,
+                /* complete= */ true, new ArraySet<>(), new ArraySet<>());
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        ProgramList.ListCallback listCallbackMock = mock(ProgramList.ListCallback.class);
+        mProgramList.registerListCallback(mExecutor, listCallbackMock);
+        mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+
+        mTunerCallback.onProgramListUpdated(purgeChunk);
+
+        verify(listCallbackMock, CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
+    }
+
+    @Test
+    public void onProgramListUpdated_withMultipleListCallBacks() throws Exception {
+        int numCallbacks = 3;
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(numCallbacks);
+
+        mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+
+        for (int index = 0; index < numCallbacks; index++) {
+            verify(mListCallbackMocks[index], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+        }
+    }
+
+    @Test
+    public void unregisterListCallback_withProgramUpdated_notInvokesCallback() throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(/* numCallbacks= */ 1);
+
+        mProgramList.unregisterListCallback(mListCallbackMocks[0]);
+        mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT.times(0)).onItemChanged(any());
+    }
+
+    @Test
+    public void addOnCompleteListener_withExecutor() throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        ProgramList.OnCompleteListener onCompleteListenerMock =
+                mock(ProgramList.OnCompleteListener.class);
+
+        mProgramList.addOnCompleteListener(mExecutor, onCompleteListenerMock);
+        mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+        verify(onCompleteListenerMock, CALLBACK_TIMEOUT).onComplete();
+    }
+
+    @Test
+    public void onProgramListUpdated_withMultipleOnCompleteListeners() throws Exception {
+        int numListeners = 3;
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        addOnCompleteListeners(numListeners);
+
+        mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+        for (int index = 0; index < numListeners; index++) {
+            verify(mOnCompleteListenerMocks[index], CALLBACK_TIMEOUT).onComplete();
+        }
+    }
+
+    @Test
+    public void removeOnCompleteListener_withProgramUpdated_notInvokesListener() throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        addOnCompleteListeners(/* numListeners= */ 1);
+
+        mProgramList.removeOnCompleteListener(mOnCompleteListenerMocks[0]);
+        mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+        verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT.times(0)).onComplete();
+    }
+
+    @Test
+    public void close_forProgramList_invokesStopProgramListUpdates() throws Exception {
+        createRadioTuner();
+        ProgramList programList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+
+        programList.close();
+
+        verify(mTunerMock, CALLBACK_TIMEOUT).stopProgramListUpdates();
+    }
+
+    private static ProgramSelector createProgramSelector(int programType,
+            ProgramSelector.Identifier identifier) {
+        return new ProgramSelector(programType, identifier, /* secondaryIds= */ null,
+                /* vendorIds= */ null);
+    }
+
+    private static RadioManager.ProgramInfo createFmProgramInfo(ProgramSelector selector) {
+        return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(),
+                selector.getPrimaryId(), /* relatedContents= */ null, /* infoFlags= */ 0,
+                /* signalQuality= */ 1, new RadioMetadata.Builder().build(),
+                /* vendorInfo= */ null);
+    }
+
+    private void createRadioTuner() throws Exception {
+        RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock);
+        RadioManager.BandConfig band = new RadioManager.FmBandConfig(
+                new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
+                        /* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 200,
+                        /* stereo= */ true, /* rds= */ false, /* ta= */ false, /* af= */ false,
+                        /* es= */ false));
+
+        doAnswer(invocation -> {
+            mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
+            return mTunerMock;
+        }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
+
+        mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, band,
+                /* withAudio= */ true, mTunerCallbackMock, /* handler= */ null);
+    }
+
+    private void registerListCallbacks(int numCallbacks) {
+        mListCallbackMocks = new ProgramList.ListCallback[numCallbacks];
+        for (int index = 0; index < numCallbacks; index++) {
+            mListCallbackMocks[index] = mock(ProgramList.ListCallback.class);
+            mProgramList.registerListCallback(mListCallbackMocks[index]);
+        }
+    }
+
+    private void addOnCompleteListeners(int numListeners) {
+        mOnCompleteListenerMocks = new ProgramList.OnCompleteListener[numListeners];
+        for (int index = 0; index < numListeners; index++) {
+            mOnCompleteListenerMocks[index] = mock(ProgramList.OnCompleteListener.class);
+            mProgramList.addOnCompleteListener(mOnCompleteListenerMocks[index]);
+        }
+    }
+}
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..44aa6d1 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
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.radio.Announcement;
 import android.hardware.radio.IAnnouncementListener;
@@ -33,6 +34,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;
 
@@ -45,6 +47,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -80,13 +83,16 @@
     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 =
             createAmBandDescriptor();
     private static final RadioManager.FmBandConfig FM_BAND_CONFIG = createFmBandConfig();
     private static final RadioManager.AmBandConfig AM_BAND_CONFIG = createAmBandConfig();
-    private static final RadioManager.ModuleProperties AMFM_PROPERTIES = createAmFmProperties();
+    private static final RadioManager.ModuleProperties AMFM_PROPERTIES =
+            createAmFmProperties(/* dabFrequencyTable= */ null);
 
     /**
      * Info flags with live, tuned and stereo enabled
@@ -173,6 +179,36 @@
     }
 
     @Test
+    public void describeContents_forBandDescriptor() {
+        RadioManager.BandDescriptor bandDescriptor = createFmBandDescriptor();
+
+        assertWithMessage("Band Descriptor contents")
+                .that(bandDescriptor.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void writeToParcel_forBandDescriptor() {
+        Parcel parcel = Parcel.obtain();
+        RadioManager.BandDescriptor bandDescriptor = createFmBandDescriptor();
+
+        bandDescriptor.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioManager.BandDescriptor bandDescriptorFromParcel =
+                RadioManager.BandDescriptor.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Band Descriptor created from parcel")
+                .that(bandDescriptorFromParcel).isEqualTo(bandDescriptor);
+    }
+
+    @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 +255,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 +349,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 +427,38 @@
     }
 
     @Test
+    public void describeContents_forBandConfig() {
+        RadioManager.BandConfig bandConfig = createFmBandConfig();
+
+        assertWithMessage("FM Band Config contents")
+                .that(bandConfig.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void writeToParcel_forBandConfig() {
+        Parcel parcel = Parcel.obtain();
+        RadioManager.BandConfig bandConfig = createAmBandConfig();
+
+        bandConfig.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioManager.BandConfig bandConfigFromParcel =
+                RadioManager.BandConfig.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Band Config created from parcel")
+                .that(bandConfigFromParcel).isEqualTo(bandConfig);
+    }
+
+    @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,14 +487,70 @@
     }
 
     @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();
+        RadioManager.FmBandConfig.Builder builder =
+                new RadioManager.FmBandConfig.Builder(FM_BAND_CONFIG);
+        RadioManager.FmBandConfig fmBandConfigCompared = builder.build();
 
         assertWithMessage("The same FM Band Config")
                 .that(FM_BAND_CONFIG).isEqualTo(fmBandConfigCompared);
@@ -360,7 +575,9 @@
 
     @Test
     public void equals_withSameAmBandConfigs_returnsTrue() {
-        RadioManager.AmBandConfig amBandConfigCompared = createAmBandConfig();
+        RadioManager.AmBandConfig.Builder builder =
+                new RadioManager.AmBandConfig.Builder(AM_BAND_CONFIG);
+        RadioManager.AmBandConfig amBandConfigCompared = builder.build();
 
         assertWithMessage("The same AM Band Config")
                 .that(AM_BAND_CONFIG).isEqualTo(amBandConfigCompared);
@@ -387,6 +604,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);
@@ -490,12 +744,20 @@
     }
 
     @Test
-    public void getDabFrequencyTable_forModuleProperties() {
+    public void getDabFrequencyTable_forModulePropertiesInitializedWithNullTable() {
         assertWithMessage("Properties DAB frequency table")
                 .that(AMFM_PROPERTIES.getDabFrequencyTable()).isNull();
     }
 
     @Test
+    public void getDabFrequencyTable_forModulePropertiesInitializedWithEmptyTable() {
+        RadioManager.ModuleProperties properties = createAmFmProperties(new ArrayMap<>());
+
+        assertWithMessage("Properties DAB frequency table")
+                .that(properties.getDabFrequencyTable()).isNull();
+    }
+
+    @Test
     public void getVendorInfo_forModuleProperties() {
         assertWithMessage("Properties vendor info")
                 .that(AMFM_PROPERTIES.getVendorInfo()).isEmpty();
@@ -509,8 +771,43 @@
     }
 
     @Test
+    public void describeContents_forModuleProperties() {
+        assertWithMessage("Module properties contents")
+                .that(AMFM_PROPERTIES.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void writeToParcel_forModulePropertiesWithNullDabFrequencyTable() {
+        Parcel parcel = Parcel.obtain();
+
+        AMFM_PROPERTIES.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioManager.ModuleProperties modulePropertiesFromParcel =
+                RadioManager.ModuleProperties.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Module properties created from parcel")
+                .that(modulePropertiesFromParcel).isEqualTo(AMFM_PROPERTIES);
+    }
+
+    @Test
+    public void writeToParcel_forModulePropertiesWithNonnullDabFrequencyTable() {
+        Parcel parcel = Parcel.obtain();
+        RadioManager.ModuleProperties propertiesToParcel = createAmFmProperties(
+                Map.of("5A", 174928, "12D", 229072));
+
+        propertiesToParcel.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioManager.ModuleProperties modulePropertiesFromParcel =
+                RadioManager.ModuleProperties.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Module properties created from parcel")
+                .that(modulePropertiesFromParcel).isEqualTo(propertiesToParcel);
+    }
+
+    @Test
     public void equals_withSameProperties_returnsTrue() {
-        RadioManager.ModuleProperties propertiesCompared = createAmFmProperties();
+        RadioManager.ModuleProperties propertiesCompared =
+                createAmFmProperties(/* dabFrequencyTable= */ null);
 
         assertWithMessage("The same module properties")
                 .that(AMFM_PROPERTIES).isEqualTo(propertiesCompared);
@@ -522,7 +819,7 @@
                 PROPERTIES_ID + 1, SERVICE_NAME, CLASS_ID, IMPLEMENTOR, PRODUCT, VERSION,
                 SERIAL, NUM_TUNERS, NUM_AUDIO_SOURCES, IS_INITIALIZATION_REQUIRED,
                 IS_CAPTURE_SUPPORTED, /* bands= */ null, IS_BG_SCAN_SUPPORTED,
-                SUPPORTED_PROGRAM_TYPES, SUPPORTED_IDENTIFIERS_TYPES, /* dabFrequencyTable= */ null,
+                SUPPORTED_PROGRAM_TYPES, SUPPORTED_IDENTIFIERS_TYPES, Map.of("5A", 174928),
                 /* vendorInfo= */ null);
 
         assertWithMessage("Module properties of different id")
@@ -530,6 +827,24 @@
     }
 
     @Test
+    public void hashCode_withSameModuleProperties_equals() {
+        RadioManager.ModuleProperties propertiesCompared =
+                createAmFmProperties(/* dabFrequencyTable= */ null);
+
+        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 +864,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 +942,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);
 
@@ -720,13 +1062,14 @@
         verify(mCloseHandleMock).close();
     }
 
-    private static RadioManager.ModuleProperties createAmFmProperties() {
+    private static RadioManager.ModuleProperties createAmFmProperties(
+            @Nullable Map<String, Integer>  dabFrequencyTable) {
         return new RadioManager.ModuleProperties(PROPERTIES_ID, SERVICE_NAME, CLASS_ID,
                 IMPLEMENTOR, PRODUCT, VERSION, SERIAL, NUM_TUNERS, NUM_AUDIO_SOURCES,
                 IS_INITIALIZATION_REQUIRED, IS_CAPTURE_SUPPORTED,
                 new RadioManager.BandDescriptor[]{AM_BAND_DESCRIPTOR, FM_BAND_DESCRIPTOR},
                 IS_BG_SCAN_SUPPORTED, SUPPORTED_PROGRAM_TYPES, SUPPORTED_IDENTIFIERS_TYPES,
-                /* dabFrequencyTable= */ null, /* vendorInfo= */ null);
+                dabFrequencyTable, /* vendorInfo= */ null);
     }
 
     private static RadioManager.FmBandDescriptor createFmBandDescriptor() {
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/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
index fe3ab62..bdba6a1 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
@@ -280,6 +280,16 @@
     }
 
     @Test
+    public void startBackgroundScan_forTunerAdapter() throws Exception {
+        when(mTunerMock.startBackgroundScan()).thenReturn(false);
+
+        boolean scanStatus = mRadioTuner.startBackgroundScan();
+
+        verify(mTunerMock).startBackgroundScan();
+        assertWithMessage("Status for starting background scan").that(scanStatus).isFalse();
+    }
+
+    @Test
     public void isAnalogForced_forTunerAdapter() throws Exception {
         when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true);
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/ExtendedRadioMockitoTestCase.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/ExtendedRadioMockitoTestCase.java
new file mode 100644
index 0000000..c6021ec
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/ExtendedRadioMockitoTestCase.java
@@ -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.
+ */
+package com.android.server.broadcastradio;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import android.util.Log;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+/**
+ * Base class to make it easier to write tests that uses {@code ExtendedMockito} for radio.
+ *
+ */
+public abstract class ExtendedRadioMockitoTestCase {
+
+    private static final String TAG = "RadioMockitoTestCase";
+
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private MockitoSession mSession;
+
+    @Before
+    public void startSession() {
+        StaticMockitoSessionBuilder builder = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT);
+        initializeSession(builder);
+        mSession = builder.startMocking();
+    }
+
+    /**
+     * Initializes the mockito session for radio test.
+     *
+     * <p>Typically used to define which classes should have static methods mocked or spied.
+     */
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        if (DEBUG) {
+            Log.d(TAG, "initializeSession()");
+        }
+    }
+
+    @After
+    public final void finishSession() {
+        if (mSession == null) {
+            Log.w(TAG, "finishSession(): no session");
+            return;
+        }
+        try {
+            if (DEBUG) {
+                Log.d(TAG, "finishSession()");
+            }
+        } finally {
+            // mSession.finishMocking() must ALWAYS be called (hence the over-protective try/finally
+            // statements), otherwise it would cause failures on future tests as mockito
+            // cannot start a session when a previous one is not finished
+            mSession.finishMocking();
+        }
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
index 7f4ea11..a2df426 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
@@ -16,11 +16,13 @@
 
 package com.android.server.broadcastradio;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -31,30 +33,36 @@
 import android.hardware.radio.ITuner;
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
+import android.os.IBinder;
+import android.os.ServiceManager;
 
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
 import com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl;
 
 import org.junit.Before;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
 
-import java.util.Arrays;
+import java.util.List;
 
 /**
  * Tests for {@link android.hardware.radio.IRadioService} with AIDL HAL implementation
  */
-@RunWith(MockitoJUnitRunner.class)
-public final class IRadioServiceAidlImplTest {
+public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCase {
 
     private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
+    private static final String AM_FM_SERVICE_NAME =
+            "android.hardware.broadcastradio.IBroadcastRadio/amfm";
+    private static final String DAB_SERVICE_NAME =
+            "android.hardware.broadcastradio.IBroadcastRadio/dab";
 
     private IRadioServiceAidlImpl mAidlImpl;
 
     @Mock
     private BroadcastRadioService mServiceMock;
     @Mock
+    private IBinder mServiceBinderMock;
+    @Mock
     private BroadcastRadioServiceImpl mHalMock;
     @Mock
     private RadioManager.ModuleProperties mModuleMock;
@@ -73,7 +81,7 @@
     public void setUp() throws Exception {
         doNothing().when(mServiceMock).enforcePolicyAccess();
 
-        when(mHalMock.listModules()).thenReturn(Arrays.asList(mModuleMock));
+        when(mHalMock.listModules()).thenReturn(List.of(mModuleMock));
         when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any()))
                 .thenReturn(mTunerMock);
         when(mHalMock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle);
@@ -81,11 +89,26 @@
         mAidlImpl = new IRadioServiceAidlImpl(mServiceMock, mHalMock);
     }
 
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder.spyStatic(ServiceManager.class);
+    }
+
+    @Test
+    public void getServicesNames_forAidlImpl() {
+        doReturn(null).when(() -> ServiceManager.waitForDeclaredService(
+                AM_FM_SERVICE_NAME));
+        doReturn(mServiceBinderMock).when(() -> ServiceManager.waitForDeclaredService(
+                DAB_SERVICE_NAME));
+
+        assertWithMessage("Names of services available")
+                .that(IRadioServiceAidlImpl.getServicesNames()).containsExactly(DAB_SERVICE_NAME);
+    }
+
     @Test
     public void loadModules_forAidlImpl() {
         assertWithMessage("Modules loaded in AIDL HAL")
-                .that(mAidlImpl.listModules())
-                .containsExactly(mModuleMock);
+                .that(mAidlImpl.listModules()).containsExactly(mModuleMock);
     }
 
     @Test
@@ -105,5 +128,4 @@
         assertWithMessage("Close handle of announcement listener for HAL 2")
                 .that(closeHandle).isEqualTo(mICloseHandle);
     }
-
 }
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
index f28e27d..5ab9435 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
@@ -18,9 +18,9 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.verify;
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java
new file mode 100644
index 0000000..a2d8467
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java
@@ -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.server.broadcastradio;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.os.Binder;
+import android.os.UserHandle;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+/**
+ * Tests for {@link com.android.server.broadcastradio.RadioServiceUserController}
+ */
+public final class RadioServiceUserControllerTest extends ExtendedRadioMockitoTestCase {
+
+    private static final int USER_ID_1 = 11;
+    private static final int USER_ID_2 = 12;
+
+    @Mock
+    private UserHandle mUserHandleMock;
+
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder.spyStatic(ActivityManager.class)
+                .spyStatic(Binder.class);
+    }
+
+    @Before
+    public void setUp() {
+        doReturn(mUserHandleMock).when(() -> Binder.getCallingUserHandle());
+        doReturn(USER_ID_1).when(() -> ActivityManager.getCurrentUser());
+    }
+
+    @Test
+    public void isCurrentUser_forCurrentUser_returnsFalse() {
+        when(mUserHandleMock.getIdentifier()).thenReturn(USER_ID_1);
+
+        assertWithMessage("Current user")
+                .that(RadioServiceUserController.isCurrentOrSystemUser()).isTrue();
+    }
+
+    @Test
+    public void isCurrentUser_forNonCurrentUser_returnsFalse() {
+        when(mUserHandleMock.getIdentifier()).thenReturn(USER_ID_2);
+
+        assertWithMessage("Non-current user")
+                .that(RadioServiceUserController.isCurrentOrSystemUser()).isFalse();
+    }
+
+    @Test
+    public void isCurrentUser_forSystemUser_returnsTrue() {
+        when(mUserHandleMock.getIdentifier()).thenReturn(UserHandle.USER_SYSTEM);
+
+        assertWithMessage("System user")
+                .that(RadioServiceUserController.isCurrentOrSystemUser()).isTrue();
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
index 2cb058b..a421218 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
@@ -31,6 +31,16 @@
         throw new UnsupportedOperationException("AidlTestUtils class is noninstantiable");
     }
 
+    static RadioManager.ModuleProperties makeDefaultModuleProperties() {
+        return new RadioManager.ModuleProperties(
+                /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
+                /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
+                /* numAudioSources= */ 0, /* isInitializationRequired= */ false,
+                /* isCaptureSupported= */ false, /* bands= */ null,
+                /* isBgScanSupported= */ false, new int[] {}, new int[] {},
+                /* dabFrequencyTable= */ null, /* vendorInfo= */ null);
+    }
+
     static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
         return new RadioManager.ProgramInfo(selector,
                 selector.getPrimaryId(), selector.getPrimaryId(), /* relatedContents= */ null,
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java
index 699212a..9a1af19 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -57,11 +56,8 @@
     private IAnnouncementListener mListenerMock;
     @Mock
     private IBinder mBinderMock;
-    // Array of mocked radio modules
     private RadioModule[] mRadioModuleMocks;
-    // Array of mocked close handles
     private ICloseHandle[] mCloseHandleMocks;
-    // Array of mocked announcements
     private Announcement[] mAnnouncementMocks;
 
     @Before
@@ -72,7 +68,7 @@
 
         mAnnouncementAggregator = new AnnouncementAggregator(mListenerMock, mLock);
 
-        verify(mBinderMock).linkToDeath(deathRecipientCaptor.capture(), anyInt());
+        verify(mBinderMock).linkToDeath(deathRecipientCaptor.capture(), eq(0));
         mDeathRecipient = deathRecipientCaptor.getValue();
     }
 
@@ -105,7 +101,8 @@
             moduleWatcherCaptor.getValue().onListUpdated(Arrays.asList(mAnnouncementMocks[index]));
 
             verify(mListenerMock, times(index + 1)).onListUpdated(announcementsCaptor.capture());
-            assertWithMessage("Number of announcements %s", announcementsCaptor.getValue())
+            assertWithMessage("Number of announcements %s after %s announcements were updated",
+                    announcementsCaptor.getValue(), index + 1)
                     .that(announcementsCaptor.getValue().size()).isEqualTo(index + 1);
         }
     }
@@ -117,7 +114,7 @@
         mAnnouncementAggregator.close();
 
         verify(mCloseHandleMocks[0]).close();
-        verify(mBinderMock).unlinkToDeath(eq(mDeathRecipient), anyInt());
+        verify(mBinderMock).unlinkToDeath(mDeathRecipient, 0);
     }
 
     @Test
@@ -140,7 +137,7 @@
         mAnnouncementAggregator.close();
 
         verify(mCloseHandleMocks[0]).close();
-        verify(mBinderMock).unlinkToDeath(eq(mDeathRecipient), anyInt());
+        verify(mBinderMock).unlinkToDeath(mDeathRecipient, 0);
     }
 
     @Test
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
new file mode 100644
index 0000000..1cc0a985
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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 static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+import android.os.IBinder;
+import android.os.IServiceCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
+import com.android.server.broadcastradio.RadioServiceUserController;
+
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTestCase {
+
+    private static final int FM_RADIO_MODULE_ID = 0;
+    private static final int DAB_RADIO_MODULE_ID = 1;
+    private static final ArrayList<String> SERVICE_LIST =
+            new ArrayList<>(Arrays.asList("FmService", "DabService"));
+    private static final int[] TEST_ENABLED_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
+
+    private BroadcastRadioServiceImpl mBroadcastRadioService;
+    private IBinder.DeathRecipient mFmDeathRecipient;
+
+    @Mock
+    private RadioManager.ModuleProperties mFmModuleMock;
+    @Mock
+    private RadioManager.ModuleProperties mDabModuleMock;
+    @Mock
+    private RadioModule mFmRadioModuleMock;
+    @Mock
+    private RadioModule mDabRadioModuleMock;
+    @Mock
+    private IBroadcastRadio mFmHalServiceMock;
+    @Mock
+    private IBroadcastRadio mDabHalServiceMock;
+    @Mock
+    private IBinder mFmBinderMock;
+    @Mock
+    private IBinder mDabBinderMock;
+    @Mock
+    private TunerSession mFmTunerSessionMock;
+    @Mock
+    private ITunerCallback mTunerCallbackMock;
+    @Mock
+    private ICloseHandle mFmCloseHandleMock;
+    @Mock
+    private ICloseHandle mDabCloseHandleMock;
+    @Mock
+    private IAnnouncementListener mAnnouncementListenerMock;
+    @Mock
+    private IBinder mListenerBinderMock;
+
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder.spyStatic(ServiceManager.class)
+                .spyStatic(RadioModule.class)
+                .spyStatic(RadioServiceUserController.class);
+    }
+
+    @Test
+    public void listModules_withMultipleServiceNames() throws Exception {
+        createBroadcastRadioService();
+
+        assertWithMessage("Radio modules in AIDL broadcast radio HAL client")
+                .that(mBroadcastRadioService.listModules())
+                .containsExactly(mFmModuleMock, mDabModuleMock);
+    }
+
+    @Test
+    public void hasModules_withIdFoundInModules() throws Exception {
+        createBroadcastRadioService();
+
+        assertWithMessage("DAB radio module in AIDL broadcast radio HAL client")
+                .that(mBroadcastRadioService.hasModule(DAB_RADIO_MODULE_ID)).isTrue();
+    }
+
+    @Test
+    public void hasModules_withIdNotFoundInModules() throws Exception {
+        createBroadcastRadioService();
+
+        assertWithMessage("Radio module of id not found in AIDL broadcast radio HAL client")
+                .that(mBroadcastRadioService.hasModule(DAB_RADIO_MODULE_ID + 1)).isFalse();
+    }
+
+    @Test
+    public void hasAnyModules_withModulesExist() throws Exception {
+        createBroadcastRadioService();
+
+        assertWithMessage("Any radio module in AIDL broadcast radio HAL client")
+                .that(mBroadcastRadioService.hasAnyModules()).isTrue();
+    }
+
+    @Test
+    public void openSession_withIdFound() throws Exception {
+        createBroadcastRadioService();
+
+        ITuner session = mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
+                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
+
+        assertWithMessage("Session opened in FM radio module")
+                .that(session).isEqualTo(mFmTunerSessionMock);
+    }
+
+    @Test
+    public void openSession_withIdNotFound() throws Exception {
+        createBroadcastRadioService();
+
+        ITuner session = mBroadcastRadioService.openSession(DAB_RADIO_MODULE_ID + 1,
+                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
+
+        assertWithMessage("Session opened with id not found").that(session).isNull();
+    }
+
+    @Test
+    public void openSession_forNonCurrentUser_throwsException() throws Exception {
+        createBroadcastRadioService();
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        IllegalStateException thrown = assertThrows(IllegalStateException.class,
+                () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
+                        /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock));
+
+        assertWithMessage("Exception for opening session by non-current user")
+                .that(thrown).hasMessageThat().contains("Cannot open session for non-current user");
+    }
+
+    @Test
+    public void binderDied_forDeathRecipient() throws Exception {
+        createBroadcastRadioService();
+
+        mFmDeathRecipient.binderDied();
+
+        verify(mFmRadioModuleMock).closeSessions(eq(RadioTuner.ERROR_HARDWARE_FAILURE));
+        assertWithMessage("FM radio module after FM broadcast radio HAL service died")
+                .that(mBroadcastRadioService.hasModule(FM_RADIO_MODULE_ID)).isFalse();
+    }
+
+    @Test
+    public void addAnnouncementListener_addsOnAllRadioModules() throws Exception {
+        createBroadcastRadioService();
+        when(mAnnouncementListenerMock.asBinder()).thenReturn(mListenerBinderMock);
+        when(mFmRadioModuleMock.addAnnouncementListener(any(), any()))
+                .thenReturn(mFmCloseHandleMock);
+        when(mDabRadioModuleMock.addAnnouncementListener(any(), any()))
+                .thenReturn(mDabCloseHandleMock);
+
+        mBroadcastRadioService.addAnnouncementListener(TEST_ENABLED_TYPES,
+                mAnnouncementListenerMock);
+
+        verify(mFmRadioModuleMock).addAnnouncementListener(any(), any());
+        verify(mDabRadioModuleMock).addAnnouncementListener(any(), any());
+    }
+
+    private void createBroadcastRadioService() throws RemoteException {
+        doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        mockServiceManager();
+        mBroadcastRadioService = new BroadcastRadioServiceImpl(SERVICE_LIST);
+    }
+
+    private void mockServiceManager() throws RemoteException {
+        doAnswer((Answer<Void>) invocation -> {
+            String serviceName = (String) invocation.getArguments()[0];
+            IServiceCallback serviceCallback = (IServiceCallback) invocation.getArguments()[1];
+            IBinder mockBinder = serviceName.equals("FmService") ? mFmBinderMock : mDabBinderMock;
+            serviceCallback.onRegistration(serviceName, mockBinder);
+            return null;
+        }).when(() -> ServiceManager.registerForNotifications(anyString(),
+                any(IServiceCallback.class)));
+
+        doReturn(mFmRadioModuleMock).when(() -> RadioModule.tryLoadingModule(
+                eq(FM_RADIO_MODULE_ID), anyString(), any(IBinder.class)));
+        doReturn(mDabRadioModuleMock).when(() -> RadioModule.tryLoadingModule(
+                eq(DAB_RADIO_MODULE_ID), anyString(), any(IBinder.class)));
+
+        when(mFmRadioModuleMock.getProperties()).thenReturn(mFmModuleMock);
+        when(mDabRadioModuleMock.getProperties()).thenReturn(mDabModuleMock);
+
+        when(mFmRadioModuleMock.getService()).thenReturn(mFmHalServiceMock);
+        when(mDabRadioModuleMock.getService()).thenReturn(mDabHalServiceMock);
+
+        when(mFmHalServiceMock.asBinder()).thenReturn(mFmBinderMock);
+        when(mDabHalServiceMock.asBinder()).thenReturn(mDabBinderMock);
+
+        doAnswer(invocation -> {
+            mFmDeathRecipient = (IBinder.DeathRecipient) invocation.getArguments()[0];
+            return null;
+        }).when(mFmBinderMock).linkToDeath(any(), anyInt());
+
+        when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock)))
+                .thenReturn(mFmTunerSessionMock);
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
index cd1cd7e..a034653 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
@@ -19,9 +19,9 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -47,6 +47,8 @@
 public final class RadioModuleTest {
 
     private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EVENT;
+    private static final RadioManager.ModuleProperties TEST_MODULE_PROPERTIES =
+            AidlTestUtils.makeDefaultModuleProperties();
 
     // Mocks
     @Mock
@@ -56,21 +58,13 @@
     @Mock
     private android.hardware.broadcastradio.ICloseHandle mHalCloseHandleMock;
 
-    private final Object mLock = new Object();
     // RadioModule under test
     private RadioModule mRadioModule;
     private android.hardware.broadcastradio.IAnnouncementListener mHalListener;
 
     @Before
     public void setup() throws RemoteException {
-        mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(
-                /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
-                /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
-                /* numAudioSources= */ 0, /* isInitializationRequired= */ false,
-                /* isCaptureSupported= */ false, /* bands= */ null, /* isBgScanSupported= */ false,
-                /* supportedProgramTypes= */ new int[]{},
-                /* supportedIdentifierTypes */ new int[]{},
-                /* dabFrequencyTable= */ null, /* vendorInfo= */ null), mLock);
+        mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES);
 
         // TODO(b/241118988): test non-null image for getImage method
         when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(null);
@@ -88,6 +82,12 @@
     }
 
     @Test
+    public void getProperties() {
+        assertWithMessage("Module properties of radio module")
+                .that(mRadioModule.getProperties()).isEqualTo(TEST_MODULE_PROPERTIES);
+    }
+
+    @Test
     public void setInternalHalCallback_callbackSetInHal() throws Exception {
         mRadioModule.setInternalHalCallback();
 
@@ -100,7 +100,7 @@
 
         Bitmap imageTest = mRadioModule.getImage(imageId);
 
-        assertWithMessage("Image got from radio module").that(imageTest).isNull();
+        assertWithMessage("Image from radio module").that(imageTest).isNull();
     }
 
     @Test
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 06d7cdd..993ca77 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -16,13 +16,15 @@
 
 package com.android.server.broadcastradio.aidl;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.timeout;
@@ -44,12 +46,14 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
+import com.android.server.broadcastradio.RadioServiceUserController;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.verification.VerificationWithTimeout;
 
 import java.util.ArrayList;
@@ -59,8 +63,7 @@
 /**
  * Tests for AIDL HAL TunerSession.
  */
-@RunWith(MockitoJUnitRunner.class)
-public final class TunerSessionTest {
+public final class TunerSessionTest extends ExtendedRadioMockitoTestCase {
 
     private static final VerificationWithTimeout CALLBACK_TIMEOUT =
             timeout(/* millis= */ 200);
@@ -80,7 +83,6 @@
     @Mock private IBroadcastRadio mBroadcastRadioMock;
     private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks;
 
-    private final Object mLock = new Object();
     // RadioModule under test
     private RadioModule mRadioModule;
 
@@ -91,15 +93,17 @@
 
     private TunerSession[] mTunerSessions;
 
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder.spyStatic(RadioServiceUserController.class);
+    }
+
     @Before
     public void setup() throws Exception {
-        mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(
-                /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
-                /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
-                /* numAudioSources= */ 0, /* isInitializationRequired= */ false,
-                /* isCaptureSupported= */ false, /* bands= */ null, /* isBgScanSupported= */ false,
-                new int[] {}, new int[] {},
-                /* dabFrequencyTable= */ null, /* vendorInfo= */ null), mLock);
+        doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mRadioModule = new RadioModule(mBroadcastRadioMock,
+                AidlTestUtils.makeDefaultModuleProperties());
 
         doAnswer(invocation -> {
             mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
@@ -210,7 +214,7 @@
 
         mTunerSessions[0].setMuted(/* mute= */ false);
 
-        assertWithMessage("Session mute state after setting muted %s", false)
+        assertWithMessage("Session mute state after setting unmuted")
                 .that(mTunerSessions[0].isMuted()).isFalse();
     }
 
@@ -220,7 +224,7 @@
 
         mTunerSessions[0].setMuted(/* mute= */ true);
 
-        assertWithMessage("Session mute state after setting muted %s", true)
+        assertWithMessage("Session mute state after setting muted")
                 .that(mTunerSessions[0].isMuted()).isTrue();
     }
 
@@ -325,6 +329,20 @@
     }
 
     @Test
+    public void tune_forCurrentUser_doesNotTune() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+        RadioManager.ProgramInfo tuneInfo =
+                AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
+
+        mTunerSessions[0].tune(initialSel);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
+                .onCurrentProgramInfoChanged(tuneInfo);
+    }
+
+    @Test
     public void step_withDirectionUp() throws Exception {
         long initFreq = AM_FM_FREQUENCY_LIST[1];
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
@@ -424,7 +442,7 @@
             mTunerSessions[0].getImage(imageId);
         });
 
-        assertWithMessage("Exception for getting image with invalid ID")
+        assertWithMessage("Get image exception")
                 .that(thrown).hasMessageThat().contains("Image ID is missing");
     }
 
@@ -467,7 +485,7 @@
         boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag);
 
         verify(mBroadcastRadioMock).isConfigFlagSet(flag);
-        assertWithMessage("Config  flag %s is supported", flag).that(isSupported).isFalse();
+        assertWithMessage("Config flag %s is supported", flag).that(isSupported).isFalse();
     }
 
     @Test
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/AnnouncementAggregatorHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/AnnouncementAggregatorHidlTest.java
new file mode 100644
index 0000000..b68e65f
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/AnnouncementAggregatorHidlTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.hal2;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for HIDL 2.0 HAL AnnouncementAggregator.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public final class AnnouncementAggregatorHidlTest {
+
+    private static final int[] TEST_ENABLED_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
+
+    private final Object mLock = new Object();
+    private AnnouncementAggregator mAnnouncementAggregator;
+    private IBinder.DeathRecipient mDeathRecipient;
+
+    @Mock
+    private IAnnouncementListener mListenerMock;
+    @Mock
+    private IBinder mBinderMock;
+    private RadioModule[] mRadioModuleMocks;
+    private ICloseHandle[] mCloseHandleMocks;
+    private Announcement[] mAnnouncementMocks;
+
+    @Before
+    public void setUp() throws Exception {
+        ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor =
+                ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
+        when(mListenerMock.asBinder()).thenReturn(mBinderMock);
+
+        mAnnouncementAggregator = new AnnouncementAggregator(mListenerMock, mLock);
+
+        verify(mBinderMock).linkToDeath(deathRecipientCaptor.capture(), eq(0));
+        mDeathRecipient = deathRecipientCaptor.getValue();
+    }
+
+    @Test
+    public void onListUpdated_withOneModuleWatcher() throws Exception {
+        ArgumentCaptor<IAnnouncementListener> moduleWatcherCaptor =
+                ArgumentCaptor.forClass(IAnnouncementListener.class);
+        watchModules(/* moduleNumber= */ 1);
+
+        verify(mRadioModuleMocks[0]).addAnnouncementListener(any(), moduleWatcherCaptor.capture());
+
+        moduleWatcherCaptor.getValue().onListUpdated(Arrays.asList(mAnnouncementMocks[0]));
+
+        verify(mListenerMock).onListUpdated(any());
+    }
+
+    @Test
+    public void onListUpdated_withMultipleModuleWatchers() throws Exception {
+        int moduleNumber = 3;
+        watchModules(moduleNumber);
+
+        for (int index = 0; index < moduleNumber; index++) {
+            ArgumentCaptor<IAnnouncementListener> moduleWatcherCaptor =
+                    ArgumentCaptor.forClass(IAnnouncementListener.class);
+            ArgumentCaptor<List<Announcement>> announcementsCaptor =
+                    ArgumentCaptor.forClass(List.class);
+            verify(mRadioModuleMocks[index])
+                    .addAnnouncementListener(any(), moduleWatcherCaptor.capture());
+
+            moduleWatcherCaptor.getValue().onListUpdated(Arrays.asList(mAnnouncementMocks[index]));
+
+            verify(mListenerMock, times(index + 1)).onListUpdated(announcementsCaptor.capture());
+            assertWithMessage("Number of announcements %s after %s announcements were updated",
+                    announcementsCaptor.getValue(), index + 1)
+                    .that(announcementsCaptor.getValue().size()).isEqualTo(index + 1);
+        }
+    }
+
+    @Test
+    public void close_withOneModuleWatcher_invokesCloseHandle() throws Exception {
+        watchModules(/* moduleNumber= */ 1);
+
+        mAnnouncementAggregator.close();
+
+        verify(mCloseHandleMocks[0]).close();
+        verify(mBinderMock).unlinkToDeath(mDeathRecipient, 0);
+    }
+
+    @Test
+    public void close_withMultipleModuleWatcher_invokesCloseHandles() throws Exception {
+        int moduleNumber = 3;
+        watchModules(moduleNumber);
+
+        mAnnouncementAggregator.close();
+
+        for (int index = 0; index < moduleNumber; index++) {
+            verify(mCloseHandleMocks[index]).close();
+        }
+    }
+
+    @Test
+    public void close_twice_invokesCloseHandleOnce() throws Exception {
+        watchModules(/* moduleNumber= */ 1);
+
+        mAnnouncementAggregator.close();
+        mAnnouncementAggregator.close();
+
+        verify(mCloseHandleMocks[0]).close();
+        verify(mBinderMock).unlinkToDeath(mDeathRecipient, 0);
+    }
+
+    @Test
+    public void binderDied_forDeathRecipient_invokesCloseHandle() throws Exception {
+        watchModules(/* moduleNumber= */ 1);
+
+        mDeathRecipient.binderDied();
+
+        verify(mCloseHandleMocks[0]).close();
+
+    }
+
+    private void watchModules(int moduleNumber) throws RemoteException {
+        mRadioModuleMocks = new RadioModule[moduleNumber];
+        mCloseHandleMocks = new ICloseHandle[moduleNumber];
+        mAnnouncementMocks = new Announcement[moduleNumber];
+
+        for (int index = 0; index < moduleNumber; index++) {
+            mRadioModuleMocks[index] = mock(RadioModule.class);
+            mCloseHandleMocks[index] = mock(ICloseHandle.class);
+            mAnnouncementMocks[index] = mock(Announcement.class);
+
+            when(mRadioModuleMocks[index].addAnnouncementListener(any(), any()))
+                    .thenReturn(mCloseHandleMocks[index]);
+            mAnnouncementAggregator.watchModule(mRadioModuleMocks[index], TEST_ENABLED_TYPES);
+        }
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java
new file mode 100644
index 0000000..c9224bf
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.hal2;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.IBinder;
+import android.os.IHwBinder.DeathRecipient;
+import android.os.RemoteException;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
+import com.android.server.broadcastradio.RadioServiceUserController;
+
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public final class BroadcastRadioServiceHidlTest extends ExtendedRadioMockitoTestCase {
+
+    private static final int FM_RADIO_MODULE_ID = 0;
+    private static final int DAB_RADIO_MODULE_ID = 1;
+    private static final ArrayList<String> SERVICE_LIST =
+            new ArrayList<>(Arrays.asList("FmService", "DabService"));
+    private static final int[] TEST_ENABLED_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
+
+    private BroadcastRadioService mBroadcastRadioService;
+    private DeathRecipient mFmDeathRecipient;
+
+    @Mock
+    private IServiceManager mServiceManagerMock;
+    @Mock
+    private RadioManager.ModuleProperties mFmModuleMock;
+    @Mock
+    private RadioManager.ModuleProperties mDabModuleMock;
+    @Mock
+    private RadioModule mFmRadioModuleMock;
+    @Mock
+    private RadioModule mDabRadioModuleMock;
+    @Mock
+    private IBroadcastRadio mFmHalServiceMock;
+    @Mock
+    private IBroadcastRadio mDabHalServiceMock;
+    @Mock
+    private TunerSession mFmTunerSessionMock;
+    @Mock
+    private ITunerCallback mTunerCallbackMock;
+    @Mock
+    private ICloseHandle mFmCloseHandleMock;
+    @Mock
+    private ICloseHandle mDabCloseHandleMock;
+    @Mock
+    private IAnnouncementListener mAnnouncementListenerMock;
+    @Mock
+    private IBinder mBinderMock;
+
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder.spyStatic(RadioModule.class)
+                .spyStatic(RadioServiceUserController.class);
+    }
+
+    @Test
+    public void listModules_withMultipleServiceNames() throws Exception {
+        createBroadcastRadioService();
+
+        assertWithMessage("Radio modules in HIDL broadcast radio HAL client")
+                .that(mBroadcastRadioService.listModules())
+                .containsExactly(mFmModuleMock, mDabModuleMock);
+    }
+
+    @Test
+    public void hasModules_withIdFoundInModules() throws Exception {
+        createBroadcastRadioService();
+
+        assertWithMessage("DAB radio module in HIDL broadcast radio HAL client")
+                .that(mBroadcastRadioService.hasModule(FM_RADIO_MODULE_ID)).isTrue();
+    }
+
+    @Test
+    public void hasModules_withIdNotFoundInModules() throws Exception {
+        createBroadcastRadioService();
+
+        assertWithMessage("Radio module of id not found in HIDL broadcast radio HAL client")
+                .that(mBroadcastRadioService.hasModule(DAB_RADIO_MODULE_ID + 1)).isFalse();
+    }
+
+    @Test
+    public void hasAnyModules_withModulesExist() throws Exception {
+        createBroadcastRadioService();
+
+        assertWithMessage("Any radio module in HIDL broadcast radio HAL client")
+                .that(mBroadcastRadioService.hasAnyModules()).isTrue();
+    }
+
+    @Test
+    public void openSession_withIdFound() throws Exception {
+        createBroadcastRadioService();
+
+        ITuner session = mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
+                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
+
+        assertWithMessage("Session opened in FM radio module")
+                .that(session).isEqualTo(mFmTunerSessionMock);
+    }
+
+    @Test
+    public void openSession_withIdNotFound() throws Exception {
+        createBroadcastRadioService();
+        int moduleIdInvalid = DAB_RADIO_MODULE_ID + 1;
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            mBroadcastRadioService.openSession(moduleIdInvalid, /* legacyConfig= */ null,
+                    /* withAudio= */ true, mTunerCallbackMock);
+        });
+
+        assertWithMessage("Exception for opening session with module id %s", moduleIdInvalid)
+                .that(thrown).hasMessageThat().contains("Invalid module ID");
+    }
+
+    @Test
+    public void openSession_forNonCurrentUser_throwsException() throws Exception {
+        createBroadcastRadioService();
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        IllegalStateException thrown = assertThrows(IllegalStateException.class,
+                () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
+                        /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock));
+
+        assertWithMessage("Exception for opening session by non-current user")
+                .that(thrown).hasMessageThat().contains("Cannot open session for non-current user");
+    }
+
+    @Test
+    public void addAnnouncementListener_addsOnAllRadioModules() throws Exception {
+        createBroadcastRadioService();
+        when(mAnnouncementListenerMock.asBinder()).thenReturn(mBinderMock);
+        when(mFmRadioModuleMock.addAnnouncementListener(any(), any()))
+                .thenReturn(mFmCloseHandleMock);
+        when(mDabRadioModuleMock.addAnnouncementListener(any(), any()))
+                .thenReturn(mDabCloseHandleMock);
+
+        mBroadcastRadioService.addAnnouncementListener(TEST_ENABLED_TYPES,
+                mAnnouncementListenerMock);
+
+        verify(mFmRadioModuleMock).addAnnouncementListener(any(), any());
+        verify(mDabRadioModuleMock).addAnnouncementListener(any(), any());
+    }
+
+    @Test
+    public void binderDied_forDeathRecipient() throws Exception {
+        createBroadcastRadioService();
+
+        mFmDeathRecipient.serviceDied(FM_RADIO_MODULE_ID);
+
+        verify(mFmRadioModuleMock).closeSessions(eq(RadioTuner.ERROR_HARDWARE_FAILURE));
+        assertWithMessage("FM radio module after FM broadcast radio HAL service died")
+                .that(mBroadcastRadioService.hasModule(FM_RADIO_MODULE_ID)).isFalse();
+    }
+
+    private void createBroadcastRadioService() throws RemoteException {
+        doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mockServiceManager();
+        mBroadcastRadioService = new BroadcastRadioService(/* nextModuleId= */ FM_RADIO_MODULE_ID,
+                mServiceManagerMock);
+    }
+
+    private void mockServiceManager() throws RemoteException {
+        doAnswer(invocation -> {
+            mFmDeathRecipient = (DeathRecipient) invocation.getArguments()[0];
+            return null;
+        }).when(mFmHalServiceMock).linkToDeath(any(), eq((long) FM_RADIO_MODULE_ID));
+
+        when(mServiceManagerMock.registerForNotifications(anyString(), anyString(),
+                any(IServiceNotification.class))).thenAnswer(invocation -> {
+                    IServiceNotification serviceCallback =
+                            (IServiceNotification) invocation.getArguments()[2];
+                    for (int index = 0; index < SERVICE_LIST.size(); index++) {
+                        serviceCallback.onRegistration(IBroadcastRadio.kInterfaceName,
+                                SERVICE_LIST.get(index), /* b= */ false);
+                    }
+                    return true;
+                }).thenReturn(true);
+
+        doReturn(mFmRadioModuleMock).when(() -> RadioModule.tryLoadingModule(
+                eq(FM_RADIO_MODULE_ID), anyString()));
+        doReturn(mDabRadioModuleMock).when(() -> RadioModule.tryLoadingModule(
+                eq(DAB_RADIO_MODULE_ID), anyString()));
+
+        when(mFmRadioModuleMock.getProperties()).thenReturn(mFmModuleMock);
+        when(mDabRadioModuleMock.getProperties()).thenReturn(mDabModuleMock);
+
+        when(mFmRadioModuleMock.getService()).thenReturn(mFmHalServiceMock);
+        when(mDabRadioModuleMock.getService()).thenReturn(mDabHalServiceMock);
+
+        when(mFmRadioModuleMock.openSession(mTunerCallbackMock))
+                .thenReturn(mFmTunerSessionMock);
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertTest.java
new file mode 100644
index 0000000..3de4f5d
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.hal2;
+
+import android.hardware.broadcastradio.V2_0.AmFmBandRange;
+import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
+import android.hardware.broadcastradio.V2_0.DabTableEntry;
+import android.hardware.broadcastradio.V2_0.IdentifierType;
+import android.hardware.broadcastradio.V2_0.Properties;
+import android.hardware.broadcastradio.V2_0.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.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+public final class ConvertTest {
+
+    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 =
+            Convert.announcementFromHal(
+                    TestUtils.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();
+        List<DabTableEntry> dabTableEntries = Arrays.asList(
+                createDabTableEntry(DAB_ENTRY_LABEL_1, DAB_ENTRY_FREQUENCY_1),
+                createDabTableEntry(DAB_ENTRY_LABEL_2, DAB_ENTRY_FREQUENCY_2));
+        Properties properties = createHalProperties();
+
+        return Convert.propertiesFromHal(TEST_ID, TEST_SERVICE_NAME, properties,
+                amFmConfig, dabTableEntries);
+    }
+
+    private static AmFmRegionConfig createAmFmRegionConfig() {
+        AmFmRegionConfig amFmRegionConfig = new AmFmRegionConfig();
+        amFmRegionConfig.ranges = new ArrayList<AmFmBandRange>(Arrays.asList(
+                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.scanSpacing = bandRange.spacing;
+        return bandRange;
+    }
+
+    private static DabTableEntry createDabTableEntry(String label, int value) {
+        DabTableEntry dabTableEntry = new DabTableEntry();
+        dabTableEntry.label = label;
+        dabTableEntry.frequency = value;
+        return dabTableEntry;
+    }
+
+    private static Properties createHalProperties() {
+        Properties halProperties = new Properties();
+        halProperties.supportedIdentifierTypes = new ArrayList<Integer>(Arrays.asList(
+                IdentifierType.AMFM_FREQUENCY, 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 ArrayList<VendorKeyValue>(Arrays.asList(
+                TestUtils.makeVendorKeyValue(VENDOR_INFO_KEY_1, VENDOR_INFO_VALUE_1),
+                TestUtils.makeVendorKeyValue(VENDOR_INFO_KEY_2, VENDOR_INFO_VALUE_2)));
+        return halProperties;
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ProgramInfoCacheTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ProgramInfoCacheTest.java
index eadf226..ec55ddb 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ProgramInfoCacheTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ProgramInfoCacheTest.java
@@ -25,6 +25,8 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -37,7 +39,7 @@
  */
 @RunWith(AndroidJUnit4.class)
 @MediumTest
-public class ProgramInfoCacheTest {
+public class ProgramInfoCacheTest extends ExtendedRadioMockitoTestCase {
     private static final String TAG = "BroadcastRadioTests.ProgramInfoCache";
 
     private final ProgramSelector.Identifier mAmFmIdentifier =
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java
new file mode 100644
index 0000000..1f5e770
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.hal2;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.hardware.broadcastradio.V2_0.Constants;
+import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
+import android.hardware.broadcastradio.V2_0.Result;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.hardware.radio.RadioManager;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Tests for HIDL HAL RadioModule.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public final class RadioModuleHidlTest {
+
+    private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EVENT;
+    private static final RadioManager.ModuleProperties TEST_MODULE_PROPERTIES =
+            TestUtils.makeDefaultModuleProperties();
+
+    @Mock
+    private IBroadcastRadio mBroadcastRadioMock;
+    @Mock
+    private IAnnouncementListener mListenerMock;
+    @Mock
+    private android.hardware.broadcastradio.V2_0.ICloseHandle mHalCloseHandleMock;
+
+    private RadioModule mRadioModule;
+    private android.hardware.broadcastradio.V2_0.IAnnouncementListener mHalListener;
+
+    @Before
+    public void setup() throws RemoteException {
+        mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES);
+
+        when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(new ArrayList<Byte>(0));
+
+        doAnswer(invocation -> {
+            mHalListener = (android.hardware.broadcastradio.V2_0.IAnnouncementListener) invocation
+                    .getArguments()[1];
+            IBroadcastRadio.registerAnnouncementListenerCallback cb =
+                    (IBroadcastRadio.registerAnnouncementListenerCallback)
+                            invocation.getArguments()[2];
+            cb.onValues(Result.OK, mHalCloseHandleMock);
+            return null;
+        }).when(mBroadcastRadioMock).registerAnnouncementListener(any(), any(), any());
+    }
+
+    @Test
+    public void getService() {
+        assertWithMessage("Service of radio module")
+                .that(mRadioModule.getService()).isEqualTo(mBroadcastRadioMock);
+    }
+
+    @Test
+    public void getProperties() {
+        assertWithMessage("Module properties of radio module")
+                .that(mRadioModule.getProperties()).isEqualTo(TEST_MODULE_PROPERTIES);
+    }
+
+    @Test
+    public void getImage_withValidIdFromRadioModule() {
+        int imageId = 1;
+
+        Bitmap imageTest = mRadioModule.getImage(imageId);
+
+        assertWithMessage("Image from radio module").that(imageTest).isNull();
+    }
+
+    @Test
+    public void getImage_withInvalidIdFromRadioModule_throwsIllegalArgumentException() {
+        int invalidImageId = Constants.INVALID_IMAGE;
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            mRadioModule.getImage(invalidImageId);
+        });
+
+        assertWithMessage("Exception for getting image with invalid ID")
+                .that(thrown).hasMessageThat().contains("Image ID is missing");
+    }
+
+    @Test
+    public void addAnnouncementListener_listenerRegistered() throws Exception {
+        ArrayList<Byte> enabledListExpected = new ArrayList<Byte>(Arrays.asList(
+                (byte) TEST_ENABLED_TYPE));
+        mRadioModule.addAnnouncementListener(new int[]{TEST_ENABLED_TYPE}, mListenerMock);
+
+        verify(mBroadcastRadioMock)
+                .registerAnnouncementListener(eq(enabledListExpected), any(), any());
+    }
+
+    @Test
+    public void onListUpdate_forAnnouncementListener() throws Exception {
+        android.hardware.broadcastradio.V2_0.Announcement halAnnouncement =
+                TestUtils.makeAnnouncement(TEST_ENABLED_TYPE, /* selectorFreq= */ 96300);
+        mRadioModule.addAnnouncementListener(new int[]{TEST_ENABLED_TYPE}, mListenerMock);
+
+        mHalListener.onListUpdated(
+                new ArrayList<android.hardware.broadcastradio.V2_0.Announcement>(
+                        Arrays.asList(halAnnouncement)));
+
+        verify(mListenerMock).onListUpdated(any());
+    }
+
+    @Test
+    public void close_forCloseHandle() throws Exception {
+        ICloseHandle closeHandle =
+                mRadioModule.addAnnouncementListener(new int[]{TEST_ENABLED_TYPE}, mListenerMock);
+
+        closeHandle.close();
+
+        verify(mHalCloseHandleMock).close();
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
index 25bf93f..7d604d4 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.broadcastradio.hal2;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
 import static org.junit.Assert.*;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doAnswer;
@@ -34,15 +36,14 @@
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.os.RemoteException;
-import android.test.suitebuilder.annotation.MediumTest;
 
-import androidx.test.runner.AndroidJUnit4;
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
+import com.android.server.broadcastradio.RadioServiceUserController;
 
 import org.junit.Before;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
 import org.mockito.verification.VerificationWithTimeout;
 
@@ -53,9 +54,7 @@
 /**
  * Tests for v2 HAL RadioModule.
  */
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class StartProgramListUpdatesFanoutTest {
+public class StartProgramListUpdatesFanoutTest extends ExtendedRadioMockitoTestCase {
     private static final String TAG = "BroadcastRadioTests.hal2.StartProgramListUpdatesFanout";
 
     private static final VerificationWithTimeout CB_TIMEOUT = timeout(500);
@@ -65,7 +64,6 @@
     @Mock ITunerSession mHalTunerSessionMock;
     private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks;
 
-    private final Object mLock = new Object();
     // RadioModule under test
     private RadioModule mRadioModule;
 
@@ -91,13 +89,17 @@
     private final RadioManager.ProgramInfo mDabEnsembleInfo = TestUtils.makeProgramInfo(
             ProgramSelector.PROGRAM_TYPE_DAB, mDabEnsembleIdentifier, 0);
 
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder.spyStatic(RadioServiceUserController.class);
+    }
+
     @Before
     public void setup() throws RemoteException {
-        MockitoAnnotations.initMocks(this);
+        doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
 
-        mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(0, "",
-                  0, "", "", "", "", 0, 0, false, false, null, false, new int[] {}, new int[] {},
-                  null, null), mLock);
+        mRadioModule = new RadioModule(mBroadcastRadioMock,
+                TestUtils.makeDefaultModuleProperties());
 
         doAnswer((Answer) invocation -> {
             mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java
index 4944803..4eedd2f 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java
@@ -15,22 +15,61 @@
  */
 package com.android.server.broadcastradio.hal2;
 
+import android.hardware.broadcastradio.V2_0.IdentifierType;
+import android.hardware.broadcastradio.V2_0.ProgramIdentifier;
 import android.hardware.broadcastradio.V2_0.ProgramInfo;
+import android.hardware.broadcastradio.V2_0.VendorKeyValue;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioMetadata;
+import android.util.ArrayMap;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 
 final class TestUtils {
+
+    private TestUtils() {
+        throw new UnsupportedOperationException("TestUtils class is noninstantiable");
+    }
+
+    static RadioManager.ModuleProperties makeDefaultModuleProperties() {
+        return new RadioManager.ModuleProperties(
+                /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
+                /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
+                /* numAudioSources= */ 0, /* isInitializationRequired= */ false,
+                /* isCaptureSupported= */ false, /* bands= */ null,
+                /* isBgScanSupported= */ false, new int[] {}, new int[] {},
+                /* dabFrequencyTable= */ null, /* vendorInfo= */ null);
+    }
+
+    static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
+        return new RadioManager.ProgramInfo(selector,
+                selector.getPrimaryId(), selector.getPrimaryId(), /* relatedContents= */ null,
+                /* infoFlags= */ 0, signalQuality,
+                new RadioMetadata.Builder().build(), new ArrayMap<>());
+    }
+
     static RadioManager.ProgramInfo makeProgramInfo(int programType,
             ProgramSelector.Identifier identifier, int signalQuality) {
         // Note: If you set new fields, check if programInfoToHal() needs to be updated as well.
-        return new RadioManager.ProgramInfo(new ProgramSelector(programType, identifier, null,
-                null), null, null, null, 0, signalQuality, new RadioMetadata.Builder().build(),
+        return new RadioManager.ProgramInfo(makeProgramSelector(programType, identifier), null,
+                null, null, 0, signalQuality, new RadioMetadata.Builder().build(),
                 new HashMap<String, String>());
     }
 
+    static ProgramSelector makeFmSelector(long freq) {
+        return makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM,
+                new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+                        freq));
+    }
+
+    static ProgramSelector makeProgramSelector(int programType,
+            ProgramSelector.Identifier identifier) {
+        return new ProgramSelector(programType, identifier, /* secondaryIds= */ null,
+                /* vendorIds= */ null);
+    }
+
     static ProgramInfo programInfoToHal(RadioManager.ProgramInfo info) {
         // Note that because Convert does not by design provide functions for all conversions, this
         // function only copies fields that are set by makeProgramInfo().
@@ -39,4 +78,45 @@
         hwInfo.signalQuality = info.getSignalStrength();
         return hwInfo;
     }
+
+    static android.hardware.broadcastradio.V2_0.ProgramSelector makeHalFmSelector(int freq) {
+        ProgramIdentifier halId = new ProgramIdentifier();
+        halId.type = IdentifierType.AMFM_FREQUENCY;
+        halId.value = freq;
+
+        android.hardware.broadcastradio.V2_0.ProgramSelector halSelector =
+                new android.hardware.broadcastradio.V2_0.ProgramSelector();
+        halSelector.primaryId = halId;
+        halSelector.secondaryIds = new ArrayList<ProgramIdentifier>();
+        return halSelector;
+    }
+
+    static ProgramInfo makeHalProgramInfo(
+            android.hardware.broadcastradio.V2_0.ProgramSelector hwSel, int hwSignalQuality) {
+        ProgramInfo hwInfo = new ProgramInfo();
+        hwInfo.selector = hwSel;
+        hwInfo.logicallyTunedTo = hwSel.primaryId;
+        hwInfo.physicallyTunedTo = hwSel.primaryId;
+        hwInfo.signalQuality = hwSignalQuality;
+        hwInfo.relatedContent = new ArrayList<>();
+        hwInfo.metadata = new ArrayList<>();
+        return hwInfo;
+    }
+
+    static VendorKeyValue makeVendorKeyValue(String vendorKey, String vendorValue) {
+        VendorKeyValue vendorKeyValue = new VendorKeyValue();
+        vendorKeyValue.key = vendorKey;
+        vendorKeyValue.value = vendorValue;
+        return vendorKeyValue;
+    }
+
+    static android.hardware.broadcastradio.V2_0.Announcement makeAnnouncement(int type,
+            int selectorFreq) {
+        android.hardware.broadcastradio.V2_0.Announcement halAnnouncement =
+                new android.hardware.broadcastradio.V2_0.Announcement();
+        halAnnouncement.type = (byte) type;
+        halAnnouncement.selector = makeHalFmSelector(selectorFreq);
+        halAnnouncement.vendorInfo = new ArrayList<>();
+        return halAnnouncement;
+    }
 }
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
new file mode 100644
index 0000000..ff988a2
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -0,0 +1,661 @@
+/*
+ * 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.hal2;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.hardware.broadcastradio.V2_0.Constants;
+import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
+import android.hardware.broadcastradio.V2_0.ITunerCallback;
+import android.hardware.broadcastradio.V2_0.ITunerSession;
+import android.hardware.broadcastradio.V2_0.IdentifierType;
+import android.hardware.broadcastradio.V2_0.ProgramInfo;
+import android.hardware.broadcastradio.V2_0.Result;
+import android.hardware.broadcastradio.V2_0.VendorKeyValue;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
+import com.android.server.broadcastradio.RadioServiceUserController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.verification.VerificationWithTimeout;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Tests for HIDL HAL TunerSession.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase {
+
+    private static final VerificationWithTimeout CALLBACK_TIMEOUT =
+            timeout(/* millis= */ 200);
+    private static final int SIGNAL_QUALITY = 1;
+    private static final long AM_FM_FREQUENCY_SPACING = 500;
+    private static final long[] AM_FM_FREQUENCY_LIST = {97500, 98100, 99100};
+    private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR =
+            new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
+                    /* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 100,
+                    /* stereo= */ false, /* rds= */ false, /* ta= */ false, /* af= */ false,
+                    /* ea= */ false);
+    private static final RadioManager.BandConfig FM_BAND_CONFIG =
+            new RadioManager.FmBandConfig(FM_BAND_DESCRIPTOR);
+    private static final int UNSUPPORTED_CONFIG_FLAG = 0;
+
+    private final ArrayMap<Integer, Boolean> mHalConfigMap = new ArrayMap<>();
+    private RadioModule mRadioModule;
+    private ITunerCallback mHalTunerCallback;
+    private ProgramInfo mHalCurrentInfo;
+    private TunerSession[] mTunerSessions;
+
+    @Mock private IBroadcastRadio mBroadcastRadioMock;
+    @Mock ITunerSession mHalTunerSessionMock;
+    private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks;
+
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder.spyStatic(RadioServiceUserController.class);
+    }
+
+    @Before
+    public void setup() throws Exception {
+        doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mRadioModule = new RadioModule(mBroadcastRadioMock,
+                TestUtils.makeDefaultModuleProperties());
+
+        doAnswer(invocation -> {
+            mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
+            IBroadcastRadio.openSessionCallback cb = (IBroadcastRadio.openSessionCallback)
+                    invocation.getArguments()[1];
+            cb.onValues(Result.OK, mHalTunerSessionMock);
+            return null;
+        }).when(mBroadcastRadioMock).openSession(any(), any());
+
+        doAnswer(invocation -> {
+            android.hardware.broadcastradio.V2_0.ProgramSelector halSel =
+                    (android.hardware.broadcastradio.V2_0.ProgramSelector)
+                            invocation.getArguments()[0];
+            mHalCurrentInfo = TestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
+            if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY) {
+                return Result.NOT_SUPPORTED;
+            }
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return Result.OK;
+        }).when(mHalTunerSessionMock).tune(any());
+
+        doAnswer(invocation -> {
+            if ((boolean) invocation.getArguments()[0]) {
+                mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
+            } else {
+                mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
+            }
+            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return Result.OK;
+        }).when(mHalTunerSessionMock).step(anyBoolean());
+
+        doAnswer(invocation -> {
+            if (mHalCurrentInfo == null) {
+                android.hardware.broadcastradio.V2_0.ProgramSelector placeHolderSelector =
+                        TestUtils.makeHalFmSelector(/* freq= */ 97300);
+
+                mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
+                return Result.OK;
+            }
+            mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
+                    mHalCurrentInfo.selector.primaryId.value,
+                    !(boolean) invocation.getArguments()[0]);
+            mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+            mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+            return Result.OK;
+        }).when(mHalTunerSessionMock).scan(anyBoolean(), anyBoolean());
+
+        when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(new ArrayList<Byte>(0));
+
+        doAnswer(invocation -> {
+            int configFlag = (int) invocation.getArguments()[0];
+            ITunerSession.isConfigFlagSetCallback cb = (ITunerSession.isConfigFlagSetCallback)
+                    invocation.getArguments()[1];
+            if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
+                cb.onValues(Result.NOT_SUPPORTED, false);
+                return null;
+            }
+            cb.onValues(Result.OK, mHalConfigMap.getOrDefault(configFlag, false));
+            return null;
+        }).when(mHalTunerSessionMock).isConfigFlagSet(anyInt(), any());
+
+        doAnswer(invocation -> {
+            int configFlag = (int) invocation.getArguments()[0];
+            if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
+                return Result.NOT_SUPPORTED;
+            }
+            mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
+            return Result.OK;
+        }).when(mHalTunerSessionMock).setConfigFlag(anyInt(), anyBoolean());
+    }
+
+    @After
+    public void cleanUp() {
+        mHalConfigMap.clear();
+    }
+
+    @Test
+    public void openSession_withMultipleSessions() throws Exception {
+        int numSessions = 3;
+
+        openAidlClients(numSessions);
+
+        for (int index = 0; index < numSessions; index++) {
+            assertWithMessage("Session of index %s close state", index)
+                    .that(mTunerSessions[index].isClosed()).isFalse();
+        }
+    }
+
+    @Test
+    public void setConfiguration() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+
+        mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onConfigurationChanged(FM_BAND_CONFIG);
+    }
+
+    @Test
+    public void getConfiguration() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
+
+        RadioManager.BandConfig config = mTunerSessions[0].getConfiguration();
+
+        assertWithMessage("Session configuration").that(config)
+                .isEqualTo(FM_BAND_CONFIG);
+    }
+
+    @Test
+    public void setMuted_withUnmuted() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+
+        mTunerSessions[0].setMuted(/* mute= */ false);
+
+        assertWithMessage("Session mute state after setting unmuted")
+                .that(mTunerSessions[0].isMuted()).isFalse();
+    }
+
+    @Test
+    public void setMuted_withMuted() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+
+        mTunerSessions[0].setMuted(/* mute= */ true);
+
+        assertWithMessage("Session mute state after setting muted")
+                .that(mTunerSessions[0].isMuted()).isTrue();
+    }
+
+    @Test
+    public void close_withOneSession() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+
+        mTunerSessions[0].close();
+
+        assertWithMessage("Close state of broadcast radio service session")
+                .that(mTunerSessions[0].isClosed()).isTrue();
+    }
+
+    @Test
+    public void close_withOnlyOneSession_withMultipleSessions() throws Exception {
+        int numSessions = 3;
+        openAidlClients(numSessions);
+        int closeIdx = 0;
+
+        mTunerSessions[closeIdx].close();
+
+        for (int index = 0; index < numSessions; index++) {
+            if (index == closeIdx) {
+                assertWithMessage(
+                        "Close state of broadcast radio service session of index %s", index)
+                        .that(mTunerSessions[index].isClosed()).isTrue();
+            } else {
+                assertWithMessage(
+                        "Close state of broadcast radio service session of index %s", index)
+                        .that(mTunerSessions[index].isClosed()).isFalse();
+            }
+        }
+    }
+
+    @Test
+    public void close_withOneSession_withError() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        int errorCode = RadioTuner.ERROR_SERVER_DIED;
+
+        mTunerSessions[0].close(errorCode);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onError(errorCode);
+        assertWithMessage("Close state of broadcast radio service session")
+                .that(mTunerSessions[0].isClosed()).isTrue();
+    }
+
+    @Test
+    public void closeSessions_withMultipleSessions_withError() throws Exception {
+        int numSessions = 3;
+        openAidlClients(numSessions);
+
+        int errorCode = RadioTuner.ERROR_SERVER_DIED;
+        mRadioModule.closeSessions(errorCode);
+
+        for (int index = 0; index < numSessions; index++) {
+            verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT).onError(errorCode);
+            assertWithMessage("Close state of broadcast radio service session of index %s", index)
+                    .that(mTunerSessions[index].isClosed()).isTrue();
+        }
+    }
+
+    @Test
+    public void tune_withOneSession() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+        RadioManager.ProgramInfo tuneInfo =
+                TestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
+
+        mTunerSessions[0].tune(initialSel);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo);
+    }
+
+    @Test
+    public void tune_withMultipleSessions() throws Exception {
+        int numSessions = 3;
+        openAidlClients(numSessions);
+        ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+        RadioManager.ProgramInfo tuneInfo =
+                TestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
+
+        mTunerSessions[0].tune(initialSel);
+
+        for (int index = 0; index < numSessions; index++) {
+            verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+                    .onCurrentProgramInfoChanged(tuneInfo);
+        }
+    }
+
+    @Test
+    public void tune_withUnsupportedSelector_throwsException() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        ProgramSelector unsupportedSelector = TestUtils.makeProgramSelector(
+                ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, new ProgramSelector.Identifier(
+                        ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, /* value= */ 300));
+
+        UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+                () -> mTunerSessions[0].tune(unsupportedSelector));
+
+        assertWithMessage("Exception for tuning on unsupported program selector")
+                .that(thrown).hasMessageThat().contains("tune: NOT_SUPPORTED");
+    }
+
+    @Test
+    public void tune_forCurrentUser_doesNotTune() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+        ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+        RadioManager.ProgramInfo tuneInfo =
+                TestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
+
+        mTunerSessions[0].tune(initialSel);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
+                .onCurrentProgramInfoChanged(tuneInfo);
+    }
+
+    @Test
+    public void step_withDirectionUp() throws Exception {
+        long initFreq = AM_FM_FREQUENCY_LIST[1];
+        ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
+        RadioManager.ProgramInfo stepUpInfo = TestUtils.makeProgramInfo(
+                TestUtils.makeFmSelector(initFreq + AM_FM_FREQUENCY_SPACING), SIGNAL_QUALITY);
+        openAidlClients(/* numClients= */ 1);
+        mHalCurrentInfo = TestUtils.makeHalProgramInfo(
+                Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
+
+        mTunerSessions[0].step(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+                .onCurrentProgramInfoChanged(stepUpInfo);
+    }
+
+    @Test
+    public void step_withDirectionDown() throws Exception {
+        long initFreq = AM_FM_FREQUENCY_LIST[1];
+        ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
+        RadioManager.ProgramInfo stepDownInfo = TestUtils.makeProgramInfo(
+                TestUtils.makeFmSelector(initFreq - AM_FM_FREQUENCY_SPACING),
+                SIGNAL_QUALITY);
+        openAidlClients(/* numClients= */ 1);
+        mHalCurrentInfo = TestUtils.makeHalProgramInfo(
+                Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
+
+        mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+                .onCurrentProgramInfoChanged(stepDownInfo);
+    }
+
+    @Test
+    public void scan_withDirectionUp() throws Exception {
+        long initFreq = AM_FM_FREQUENCY_LIST[2];
+        ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
+        RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo(
+                TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
+                SIGNAL_QUALITY);
+        openAidlClients(/* numClients= */ 1);
+        mHalCurrentInfo = TestUtils.makeHalProgramInfo(
+                Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
+
+        mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+                .onCurrentProgramInfoChanged(scanUpInfo);
+    }
+
+    @Test
+    public void scan_callsOnTuneFailedWhenTimeout() throws Exception {
+        int numSessions = 2;
+        openAidlClients(numSessions);
+
+        mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+        for (int index = 0; index < numSessions; index++) {
+            verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+                    .onTuneFailed(eq(Result.TIMEOUT), any());
+        }
+    }
+
+    @Test
+    public void scan_withDirectionDown() throws Exception {
+        long initFreq = AM_FM_FREQUENCY_LIST[2];
+        ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
+        RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo(
+                TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
+                SIGNAL_QUALITY);
+        openAidlClients(/* numClients= */ 1);
+        mHalCurrentInfo = TestUtils.makeHalProgramInfo(
+                Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
+
+        mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+                .onCurrentProgramInfoChanged(scanUpInfo);
+    }
+
+    @Test
+    public void cancel() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+        mTunerSessions[0].tune(initialSel);
+
+        mTunerSessions[0].cancel();
+
+        verify(mHalTunerSessionMock).cancel();
+    }
+
+    @Test
+    public void cancel_forNonCurrentUser() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+        mTunerSessions[0].tune(initialSel);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].cancel();
+
+        verify(mHalTunerSessionMock, never()).cancel();
+    }
+
+    @Test
+    public void getImage_withInvalidId_throwsIllegalArgumentException() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        int imageId = Constants.INVALID_IMAGE;
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            mTunerSessions[0].getImage(imageId);
+        });
+
+        assertWithMessage("Get image exception")
+                .that(thrown).hasMessageThat().contains("Image ID is missing");
+    }
+
+    @Test
+    public void getImage_withValidId() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        int imageId = 1;
+
+        Bitmap imageTest = mTunerSessions[0].getImage(imageId);
+
+        assertWithMessage("Null image").that(imageTest).isEqualTo(null);
+    }
+
+    @Test
+    public void startBackgroundScan() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+
+        mTunerSessions[0].startBackgroundScan();
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onBackgroundScanComplete();
+    }
+
+    @Test
+    public void stopProgramListUpdates() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+                /* includeCategories= */ true, /* excludeModifications= */ false);
+        mTunerSessions[0].startProgramListUpdates(aidlFilter);
+
+        mTunerSessions[0].stopProgramListUpdates();
+
+        verify(mHalTunerSessionMock).stopProgramListUpdates();
+    }
+
+    @Test
+    public void isConfigFlagSupported_withUnsupportedFlag_returnsFalse() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        int flag = UNSUPPORTED_CONFIG_FLAG;
+
+        boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag);
+
+        verify(mHalTunerSessionMock).isConfigFlagSet(eq(flag), any());
+        assertWithMessage("Config flag %s is supported", flag).that(isSupported).isFalse();
+    }
+
+    @Test
+    public void isConfigFlagSupported_withSupportedFlag_returnsTrue() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+
+        boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag);
+
+        verify(mHalTunerSessionMock).isConfigFlagSet(eq(flag), any());
+        assertWithMessage("Config flag %s is supported", flag).that(isSupported).isTrue();
+    }
+
+    @Test
+    public void setConfigFlag_withUnsupportedFlag_throwsRuntimeException() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        int flag = UNSUPPORTED_CONFIG_FLAG;
+
+        RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+            mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
+        });
+
+        assertWithMessage("Exception for setting unsupported flag %s", flag)
+                .that(thrown).hasMessageThat().contains("setConfigFlag: NOT_SUPPORTED");
+    }
+
+    @Test
+    public void setConfigFlag_withFlagSetToTrue() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+
+        mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
+
+        verify(mHalTunerSessionMock).setConfigFlag(flag, /* value= */ true);
+    }
+
+    @Test
+    public void setConfigFlag_withFlagSetToFalse() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+
+        mTunerSessions[0].setConfigFlag(flag, /* value= */ false);
+
+        verify(mHalTunerSessionMock).setConfigFlag(flag, /* value= */ false);
+    }
+
+    @Test
+    public void isConfigFlagSet_withUnsupportedFlag_throwsRuntimeException()
+            throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        int flag = UNSUPPORTED_CONFIG_FLAG;
+
+        RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+            mTunerSessions[0].isConfigFlagSet(flag);
+        });
+
+        assertWithMessage("Exception for check if unsupported flag %s is set", flag)
+                .that(thrown).hasMessageThat().contains("isConfigFlagSet: NOT_SUPPORTED");
+    }
+
+    @Test
+    public void isConfigFlagSet_withSupportedFlag() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+        boolean expectedConfigFlagValue = true;
+        mTunerSessions[0].setConfigFlag(flag, /* value= */ expectedConfigFlagValue);
+
+        boolean isSet = mTunerSessions[0].isConfigFlagSet(flag);
+
+        assertWithMessage("Config flag %s is set", flag)
+                .that(isSet).isEqualTo(expectedConfigFlagValue);
+    }
+
+    @Test
+    public void setParameters_withMockParameters() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
+                "mockParam2", "mockValue2");
+
+        mTunerSessions[0].setParameters(parametersSet);
+
+        verify(mHalTunerSessionMock).setParameters(Convert.vendorInfoToHal(parametersSet));
+    }
+
+    @Test
+    public void getParameters_withMockKeys() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        ArrayList<String> parameterKeys = new ArrayList<>(Arrays.asList("mockKey1", "mockKey2"));
+
+        mTunerSessions[0].getParameters(parameterKeys);
+
+        verify(mHalTunerSessionMock).getParameters(parameterKeys);
+    }
+
+    @Test
+    public void onConfigFlagUpdated_forTunerCallback() throws Exception {
+        int numSessions = 3;
+        openAidlClients(numSessions);
+
+        mHalTunerCallback.onAntennaStateChange(/* connected= */ false);
+
+        for (int index = 0; index < numSessions; index++) {
+            verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+                    .onAntennaState(/* connected= */ false);
+        }
+    }
+
+    @Test
+    public void onParametersUpdated_forTunerCallback() throws Exception {
+        int numSessions = 3;
+        openAidlClients(numSessions);
+        ArrayList<VendorKeyValue> parametersUpdates = new ArrayList<VendorKeyValue>(Arrays.asList(
+                TestUtils.makeVendorKeyValue("com.vendor.parameter1", "value1")));
+        Map<String, String> parametersExpected = Map.of("com.vendor.parameter1", "value1");
+
+        mHalTunerCallback.onParametersUpdated(parametersUpdates);
+
+        for (int index = 0; index < numSessions; index++) {
+            verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+                    .onParametersUpdated(parametersExpected);
+        }
+    }
+
+    private void openAidlClients(int numClients) throws Exception {
+        mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients];
+        mTunerSessions = new TunerSession[numClients];
+        for (int index = 0; index < numClients; index++) {
+            mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
+            mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
+        }
+    }
+
+    private long getSeekFrequency(long currentFrequency, boolean seekDown) {
+        long seekFrequency;
+        if (seekDown) {
+            seekFrequency = AM_FM_FREQUENCY_LIST[AM_FM_FREQUENCY_LIST.length - 1];
+            for (int i = AM_FM_FREQUENCY_LIST.length - 1; i >= 0; i--) {
+                if (AM_FM_FREQUENCY_LIST[i] < currentFrequency) {
+                    seekFrequency = AM_FM_FREQUENCY_LIST[i];
+                    break;
+                }
+            }
+        } else {
+            seekFrequency = AM_FM_FREQUENCY_LIST[0];
+            for (int index = 0; index < AM_FM_FREQUENCY_LIST.length; index++) {
+                if (AM_FM_FREQUENCY_LIST[index] > currentFrequency) {
+                    seekFrequency = AM_FM_FREQUENCY_LIST[index];
+                    break;
+                }
+            }
+        }
+        return seekFrequency;
+    }
+}
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
index 100eb99..b3af895 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
@@ -145,7 +145,8 @@
         mIntentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
         mIntentFilter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
         mIntentFilter.addAction(TetheringManager.ACTION_TETHER_STATE_CHANGED);
-        mContext.registerReceiver(mWifiReceiver, mIntentFilter);
+        mContext.registerReceiver(mWifiReceiver, mIntentFilter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
 
         logv("Clear Wifi before we start the test.");
         removeConfiguredNetworksAndDisableWifi();
diff --git a/core/tests/GameManagerTests/AndroidManifest.xml b/core/tests/GameManagerTests/AndroidManifest.xml
index 6a01abe..f1ab696 100644
--- a/core/tests/GameManagerTests/AndroidManifest.xml
+++ b/core/tests/GameManagerTests/AndroidManifest.xml
@@ -17,7 +17,9 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.app.gamemanagertests"
-          android:sharedUserId="android.uid.system" >
+          android:sharedUserId="com.android.uid.test" >
+
+    <uses-permission android:name="android.permission.MANAGE_GAME_MODE" />
 
     <application android:appCategory="game">
         <uses-library android:name="android.test.runner" />
diff --git a/core/tests/GameManagerTests/OWNERS b/core/tests/GameManagerTests/OWNERS
new file mode 100644
index 0000000..0992440
--- /dev/null
+++ b/core/tests/GameManagerTests/OWNERS
@@ -0,0 +1 @@
+include /GAME_MANAGER_OWNERS
\ No newline at end of file
diff --git a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
index baecc8c..fac3a0e 100644
--- a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
+++ b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
@@ -19,6 +19,8 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
 
 import android.content.Context;
 import android.platform.test.annotations.Presubmit;
@@ -48,34 +50,60 @@
         mPackageName = mContext.getPackageName();
 
         // Reset the Game Mode for the test app, since it persists across invocations.
-        mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_UNSUPPORTED);
+        mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD);
     }
 
     @Test
     public void testPublicApiGameModeGetterSetter() {
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
-                mGameManager.getGameMode());
-
-        mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD);
         assertEquals(GameManager.GAME_MODE_STANDARD,
                 mGameManager.getGameMode());
 
         mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE);
         assertEquals(GameManager.GAME_MODE_PERFORMANCE,
                 mGameManager.getGameMode());
+
+        mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_CUSTOM);
+        assertEquals(GameManager.GAME_MODE_CUSTOM,
+                mGameManager.getGameMode());
     }
 
     @Test
     public void testPrivilegedGameModeGetterSetter() {
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
-                mGameManager.getGameMode(mPackageName));
-
-        mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD);
         assertEquals(GameManager.GAME_MODE_STANDARD,
                 mGameManager.getGameMode(mPackageName));
 
         mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE);
         assertEquals(GameManager.GAME_MODE_PERFORMANCE,
                 mGameManager.getGameMode(mPackageName));
+
+        mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_CUSTOM);
+        assertEquals(GameManager.GAME_MODE_CUSTOM,
+                mGameManager.getGameMode(mPackageName));
+    }
+
+    @Test
+    public void testUpdateCustomGameModeConfiguration() {
+        GameModeInfo gameModeInfo = mGameManager.getGameModeInfo(mPackageName);
+        assertNotNull(gameModeInfo);
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM));
+        GameModeConfiguration unsupportedFpsConfig =
+                new GameModeConfiguration.Builder().setFpsOverride(
+                        70).setScalingFactor(0.5f).build();
+        mGameManager.updateCustomGameModeConfiguration(mPackageName, unsupportedFpsConfig);
+        gameModeInfo = mGameManager.getGameModeInfo(mPackageName);
+        assertNotNull(gameModeInfo);
+        // TODO(b/243448953): update to non-zero FPS when matching is implemented
+        assertEquals(new GameModeConfiguration.Builder().setFpsOverride(
+                        GameModeConfiguration.FPS_OVERRIDE_NONE).setScalingFactor(0.5f).build(),
+                gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM));
+
+        GameModeConfiguration supportedFpsConfig =
+                new GameModeConfiguration.Builder().setFpsOverride(
+                        60).setScalingFactor(0.5f).build();
+        mGameManager.updateCustomGameModeConfiguration(mPackageName, supportedFpsConfig);
+        gameModeInfo = mGameManager.getGameModeInfo(mPackageName);
+        assertNotNull(gameModeInfo);
+        assertEquals(supportedFpsConfig,
+                gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM));
     }
 }
diff --git a/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java b/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java
new file mode 100644
index 0000000..b3e74d3
--- /dev/null
+++ b/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class GameModeConfigurationTest {
+    @Test
+    public void testEqualsAndHashCode() {
+        GameModeConfiguration config = new GameModeConfiguration.Builder()
+                .setScalingFactor(0.5f).setFpsOverride(10).build();
+        assertTrue(config.equals(config));
+
+        GameModeConfiguration config1 = new GameModeConfiguration.Builder()
+                .setScalingFactor(0.5f).setFpsOverride(10).build();
+        assertTrue(config.equals(config1));
+        assertEquals(config.hashCode(), config1.hashCode());
+
+        GameModeConfiguration config2 = new GameModeConfiguration.Builder()
+                .setScalingFactor(0.5f).build();
+        assertFalse(config.equals(config2));
+        assertNotEquals(config.hashCode(), config2.hashCode());
+
+        GameModeConfiguration config3 = new GameModeConfiguration.Builder()
+                .setFpsOverride(10).build();
+        assertFalse(config.equals(config3));
+        assertNotEquals(config.hashCode(), config3.hashCode());
+        assertFalse(config2.equals(config3));
+        assertNotEquals(config2.hashCode(), config3.hashCode());
+
+        GameModeConfiguration config4 = new GameModeConfiguration.Builder()
+                .setScalingFactor(0.2f).setFpsOverride(10).build();
+        assertFalse(config.equals(config4));
+        assertNotEquals(config.hashCode(), config4.hashCode());
+
+        GameModeConfiguration config5 = new GameModeConfiguration.Builder()
+                .setScalingFactor(0.5f).setFpsOverride(30).build();
+        assertFalse(config.equals(config5));
+        assertNotEquals(config.hashCode(), config5.hashCode());
+
+        GameModeConfiguration config6 = new GameModeConfiguration.Builder()
+                .build();
+        assertFalse(config.equals(config6));
+        assertNotEquals(config.hashCode(), config6.hashCode());
+        assertFalse(config2.equals(config6));
+        assertNotEquals(config2.hashCode(), config6.hashCode());
+        assertFalse(config3.equals(config6));
+        assertNotEquals(config3.hashCode(), config6.hashCode());
+    }
+
+    @Test
+    public void testBuilderConstructor() {
+        GameModeConfiguration config = new GameModeConfiguration
+                .Builder().setFpsOverride(40).setScalingFactor(0.5f).build();
+        GameModeConfiguration newConfig = new GameModeConfiguration.Builder(config).build();
+        assertEquals(config, newConfig);
+    }
+
+    @Test
+    public void testGetters() {
+        GameModeConfiguration config = new GameModeConfiguration.Builder()
+                .setScalingFactor(0.5f).setFpsOverride(10).build();
+        assertEquals(0.5f, config.getScalingFactor(), 0.01f);
+        assertEquals(10, config.getFpsOverride());
+    }
+}
diff --git a/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java b/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java
new file mode 100644
index 0000000..5fa6084
--- /dev/null
+++ b/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link android.app.GameModeInfo}.
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class GameModeInfoTest {
+    @Test
+    public void testParcelable() {
+        int activeGameMode = GameManager.GAME_MODE_PERFORMANCE;
+        int[] availableGameModes =
+                new int[]{GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_PERFORMANCE,
+                        GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_CUSTOM};
+        int[] overriddenGameModes = new int[]{GameManager.GAME_MODE_PERFORMANCE};
+        GameModeConfiguration batteryConfig = new GameModeConfiguration
+                .Builder().setFpsOverride(40).setScalingFactor(0.5f).build();
+        GameModeConfiguration performanceConfig = new GameModeConfiguration
+                .Builder().setFpsOverride(90).setScalingFactor(0.9f).build();
+        GameModeInfo gameModeInfo = new GameModeInfo.Builder()
+                .setActiveGameMode(activeGameMode)
+                .setAvailableGameModes(availableGameModes)
+                .setOverriddenGameModes(overriddenGameModes)
+                .setDownscalingAllowed(true)
+                .setFpsOverrideAllowed(false)
+                .setGameModeConfiguration(GameManager.GAME_MODE_BATTERY, batteryConfig)
+                .setGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE, performanceConfig)
+                .build();
+
+        assertArrayEquals(availableGameModes, gameModeInfo.getAvailableGameModes());
+        assertArrayEquals(overriddenGameModes, gameModeInfo.getOverriddenGameModes());
+        assertEquals(activeGameMode, gameModeInfo.getActiveGameMode());
+        assertTrue(gameModeInfo.isDownscalingAllowed());
+        assertFalse(gameModeInfo.isFpsOverrideAllowed());
+        assertEquals(performanceConfig,
+                gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+        assertEquals(batteryConfig,
+                gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+
+        Parcel parcel = Parcel.obtain();
+        gameModeInfo.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GameModeInfo newGameModeInfo = new GameModeInfo(parcel);
+        assertEquals(gameModeInfo.getActiveGameMode(), newGameModeInfo.getActiveGameMode());
+        assertArrayEquals(gameModeInfo.getAvailableGameModes(),
+                newGameModeInfo.getAvailableGameModes());
+        assertArrayEquals(gameModeInfo.getOverriddenGameModes(),
+                newGameModeInfo.getOverriddenGameModes());
+        assertTrue(newGameModeInfo.isDownscalingAllowed());
+        assertFalse(newGameModeInfo.isFpsOverrideAllowed());
+        assertEquals(performanceConfig,
+                newGameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+        assertEquals(batteryConfig,
+                newGameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+    }
+}
diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java
index 1a63660..191756a 100644
--- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java
+++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java
@@ -99,7 +99,8 @@
         // Register a download receiver for ACTION_DOWNLOAD_COMPLETE
         mDownloadReceiver = new DownloadReceiver();
         mContext.registerReceiver(mDownloadReceiver,
-                new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+                new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
+                Context.RECEIVER_EXPORTED_UNAUDITED);
 
         // Register a wifi receiver
         mWifiReceiver = new WifiReceiver();
diff --git a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
index 1cf4302..372bca4 100644
--- a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
+++ b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
@@ -88,6 +88,7 @@
         }
     }
 
+    @SuppressWarnings("ParcelableCreator")
     @SuppressLint("ParcelCreator")
     private static class PointArray implements Parcelable {
         Rect mBounds = new Rect();
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 48cfc87..e811bb6 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -43,10 +43,12 @@
         "mockwebserver",
         "guava",
         "androidx.core_core",
+        "androidx.core_core-ktx",
         "androidx.test.espresso.core",
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "androidx.test.rules",
+        "junit-params",
         "kotlin-test",
         "mockito-target-minus-junit4",
         "ub-uiautomator",
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 04952bd..e2cdbf3 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -25,6 +25,11 @@
         <option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" />
     </target_preparer>
 
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <!-- TODO(b/254155965): Design a mechanism to finally remove this command. -->
+        <option name="run-command" value="settings put global device_config_sync_disabled 0" />
+    </target_preparer>
+
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DeviceInteractionHelperInstaller" />
 
     <option name="test-tag" value="FrameworksCoreTests" />
diff --git a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
index f4709ff..cb66fc8 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
@@ -471,7 +471,7 @@
     protected MultipleDownloadsCompletedReceiver registerNewMultipleDownloadsReceiver() {
         MultipleDownloadsCompletedReceiver receiver = new MultipleDownloadsCompletedReceiver();
         mContext.registerReceiver(receiver, new IntentFilter(
-                DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+                DownloadManager.ACTION_DOWNLOAD_COMPLETE), Context.RECEIVER_EXPORTED_UNAUDITED);
         return receiver;
     }
 
diff --git a/core/tests/coretests/src/android/app/activity/BroadcastTest.java b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
index 0f81896..10452fd 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.setInteractive(true);
+        final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);
+
+        try {
+            getContext().sendBroadcast(intent, null, options.toBundle());
+            fail("No exception thrown with BroadcastOptions.setInteractive(true)");
+        } catch (SecurityException se) {
+            // Expected, correct behavior - this case intentionally empty
+        } catch (Exception e) {
+            fail("Unexpected exception " + e.getMessage()
+                    + " thrown with BroadcastOptions.setInteractive(true)");
+        }
+    }
+
+    public void testBroadcastOption_interactive_PendingIntent() throws Exception {
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setInteractive(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.setInteractive(true)");
+        } catch (SecurityException se) {
+            // Expected, correct behavior - this case intentionally empty
+        } catch (Exception e) {
+            fail("Unexpected exception " + e.getMessage()
+                    + " thrown with BroadcastOptions.setInteractive(true)");
+        } finally {
+            brPending.cancel();
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/app/activity/LocalReceiver.java b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
index 7f81339..5ac84f8 100644
--- a/core/tests/coretests/src/android/app/activity/LocalReceiver.java
+++ b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
@@ -36,7 +36,8 @@
         if (BroadcastTest.BROADCAST_FAIL_REGISTER.equals(intent.getAction())) {
             resultString = "Successfully registered, but expected it to fail";
             try {
-                context.registerReceiver(this, new IntentFilter("foo.bar"));
+                context.registerReceiver(this, new IntentFilter("foo.bar"),
+                        Context.RECEIVER_EXPORTED_UNAUDITED);
                 context.unregisterReceiver(this);
             } catch (ReceiverCallNotAllowedException e) {
                 //resultString = "This is the correct behavior but not yet implemented";
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/activity/ServiceTest.java b/core/tests/coretests/src/android/app/activity/ServiceTest.java
index c89f37d..3f3d6a3 100644
--- a/core/tests/coretests/src/android/app/activity/ServiceTest.java
+++ b/core/tests/coretests/src/android/app/activity/ServiceTest.java
@@ -172,7 +172,7 @@
                 pidResult.complete(intent.getIntExtra(EXTRA_PID, NOT_STARTED));
                 mContext.unregisterReceiver(this);
             }
-        }, new IntentFilter(ACTION_SERVICE_STARTED));
+        }, new IntentFilter(ACTION_SERVICE_STARTED), Context.RECEIVER_EXPORTED_UNAUDITED);
 
         serviceTrigger.run();
         try {
diff --git a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
index 37cf470..561c10ba 100644
--- a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
@@ -21,7 +21,8 @@
 import static org.mockito.Mockito.when;
 
 import android.app.backup.BackupAgent.IncludeExcludeRules;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
 import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
@@ -46,6 +47,7 @@
 public class BackupAgentTest {
     // An arbitrary user.
     private static final UserHandle USER_HANDLE = new UserHandle(15);
+    private static final String DATA_TYPE_BACKED_UP = "test data type";
 
     @Mock FullBackup.BackupScheme mBackupScheme;
 
@@ -65,7 +67,7 @@
         excludePaths.add(path);
         IncludeExcludeRules expectedRules = new IncludeExcludeRules(includePaths, excludePaths);
 
-        mBackupAgent = getAgentForOperationType(OperationType.BACKUP);
+        mBackupAgent = getAgentForBackupDestination(BackupDestination.CLOUD);
         when(mBackupScheme.maybeParseAndGetCanonicalExcludePaths()).thenReturn(excludePaths);
         when(mBackupScheme.maybeParseAndGetCanonicalIncludePaths()).thenReturn(includePaths);
 
@@ -73,9 +75,47 @@
         assertThat(rules).isEqualTo(expectedRules);
     }
 
-    private BackupAgent getAgentForOperationType(@OperationType int operationType) {
+    @Test
+    public void getBackupRestoreEventLogger_beforeOnCreate_isNull() {
         BackupAgent agent = new TestFullBackupAgent();
-        agent.onCreate(USER_HANDLE, operationType);
+
+        assertThat(agent.getBackupRestoreEventLogger()).isNull();
+    }
+
+    @Test
+    public void getBackupRestoreEventLogger_afterOnCreateForBackup_initializedForBackup() {
+        BackupAgent agent = new TestFullBackupAgent();
+        agent.onCreate(USER_HANDLE, BackupDestination.CLOUD, OperationType.BACKUP);
+
+        assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(
+                OperationType.BACKUP);
+    }
+
+    @Test
+    public void getBackupRestoreEventLogger_afterOnCreateForRestore_initializedForRestore() {
+        BackupAgent agent = new TestFullBackupAgent();
+        agent.onCreate(USER_HANDLE, BackupDestination.CLOUD, OperationType.RESTORE);
+
+        assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(
+                OperationType.RESTORE);
+    }
+
+    @Test
+    public void getBackupRestoreEventLogger_afterBackup_containsLogsLoggedByAgent()
+            throws Exception {
+        BackupAgent agent = new TestFullBackupAgent();
+        agent.onCreate(USER_HANDLE, BackupDestination.CLOUD, OperationType.BACKUP);
+
+        // TestFullBackupAgent logs DATA_TYPE_BACKED_UP when onFullBackup is called.
+        agent.onFullBackup(new FullBackupDataOutput(/* quota = */ 0));
+
+        assertThat(agent.getBackupRestoreEventLogger().getLoggingResults().get(0).getDataType())
+                .isEqualTo(DATA_TYPE_BACKED_UP);
+    }
+
+    private BackupAgent getAgentForBackupDestination(@BackupDestination int backupDestination) {
+        BackupAgent agent = new TestFullBackupAgent();
+        agent.onCreate(USER_HANDLE, backupDestination);
         return agent;
     }
 
@@ -88,6 +128,11 @@
         }
 
         @Override
+        public void onFullBackup(FullBackupDataOutput data) {
+            getBackupRestoreEventLogger().logItemsBackedUp(DATA_TYPE_BACKED_UP, 1);
+        }
+
+        @Override
         public void onRestore(BackupDataInput data, int appVersionCode,
                 ParcelFileDescriptor newState) throws IOException {
             // Left empty as this is a full backup agent.
diff --git a/core/tests/coretests/src/android/app/backup/BackupManagerTest.java b/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
new file mode 100644
index 0000000..cbf167c
--- /dev/null
+++ b/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
@@ -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 android.app.backup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupManagerTest {
+    private BackupManager mBackupManager;
+
+    @Mock
+    Context mContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mBackupManager = new BackupManager(mContext);
+    }
+
+    @Test
+    public void testGetBackupRestoreEventLogger_returnsBackupLoggerForBackup() {
+        BackupAgent agent = getTestAgent();
+        agent.onCreate(UserHandle.SYSTEM, BackupDestination.CLOUD,
+                OperationType.BACKUP);
+
+        BackupRestoreEventLogger logger = mBackupManager.getBackupRestoreEventLogger(agent);
+
+        assertThat(logger.getOperationType()).isEqualTo(OperationType.BACKUP);
+    }
+
+    @Test
+    public void testGetBackupRestoreEventLogger_returnsRestoreLoggerForRestore() {
+        BackupAgent agent = getTestAgent();
+        agent.onCreate(UserHandle.SYSTEM, BackupDestination.CLOUD,
+                OperationType.RESTORE);
+
+        BackupRestoreEventLogger logger = mBackupManager.getBackupRestoreEventLogger(agent);
+
+        assertThat(logger.getOperationType()).isEqualTo(OperationType.RESTORE);
+    }
+
+    @Test
+    public void testGetBackupRestoreEventLogger_uninitialisedAgent_throwsException() {
+        BackupAgent agent = getTestAgent();
+
+        assertThrows(IllegalStateException.class,
+                () -> mBackupManager.getBackupRestoreEventLogger(agent));
+    }
+
+    private static BackupAgent getTestAgent() {
+        return new BackupAgent() {
+            @Override
+            public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+                    ParcelFileDescriptor newState) throws IOException {
+
+            }
+
+            @Override
+            public void onRestore(BackupDataInput data, int appVersionCode,
+                    ParcelFileDescriptor newState) throws IOException {
+
+            }
+        };
+    }
+
+}
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index 67b24ec..112d394 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -16,8 +16,8 @@
 
 package android.app.backup;
 
-import static android.app.backup.BackupRestoreEventLogger.OperationType.BACKUP;
-import static android.app.backup.BackupRestoreEventLogger.OperationType.RESTORE;
+import static android.app.backup.BackupAnnotations.OperationType.BACKUP;
+import static android.app.backup.BackupAnnotations.OperationType.RESTORE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -25,6 +25,7 @@
 
 import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
 import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
+import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -35,6 +36,7 @@
 
 import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
@@ -63,18 +65,22 @@
     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();
+        mLogger.logItemsRestored(DATA_TYPE_1, /* count */ 5);
+        mLogger.logItemsRestoreFailed(DATA_TYPE_1, /* count */ 5, ERROR_1);
+        mLogger.logRestoreMetadata(DATA_TYPE_1, /* metadata */ "metadata");
+
+        assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_1)).isEqualTo(Optional.empty());
     }
 
     @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();
+        mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5);
+        mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1);
+        mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata");
+
+        assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_1)).isEqualTo(Optional.empty());
     }
 
     @Test
@@ -83,16 +89,17 @@
 
         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();
+            mLogger.logItemsBackedUp(dataType, /* count */ 5);
+            mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
+            mLogger.logBackupMetaData(dataType, METADATA_1);
+
+            assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+                    Optional.empty());
         }
 
-        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();
+        mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5);
+        mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+        mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
         assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
     }
 
@@ -102,16 +109,17 @@
 
         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();
+            mLogger.logItemsRestored(dataType, /* count */ 5);
+            mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null);
+            mLogger.logRestoreMetadata(dataType, METADATA_1);
+
+            assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+                    Optional.empty());
         }
 
-        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();
+        mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5);
+        mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+        mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
         assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
     }
 
@@ -230,10 +238,10 @@
         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);
+        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);
     }
@@ -247,16 +255,54 @@
         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);
+        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) {
+    @Test
+    public void testGetLoggingResults_resultsParceledAndUnparceled_recreatedCorrectly() {
+        mLogger = new BackupRestoreEventLogger(RESTORE);
+        int firstTypeSuccessCount = 1;
+        int firstTypeErrorOneCount = 2;
+        int firstTypeErrorTwoCount = 3;
+        mLogger.logItemsRestored(DATA_TYPE_1, firstTypeSuccessCount);
+        mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstTypeErrorOneCount, ERROR_1);
+        mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstTypeErrorTwoCount, ERROR_2);
+        mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_1);
+        int secondTypeSuccessCount = 4;
+        int secondTypeErrorOneCount = 5;
+        mLogger.logItemsRestored(DATA_TYPE_2, secondTypeSuccessCount);
+        mLogger.logItemsRestoreFailed(DATA_TYPE_2, secondTypeErrorOneCount, ERROR_1);
+
+        List<DataTypeResult> resultsList = mLogger.getLoggingResults();
+        Parcel parcel = Parcel.obtain();
+
+        parcel.writeParcelableList(resultsList, /* flags= */ 0);
+
+        parcel.setDataPosition(0);
+        List<DataTypeResult> recreatedList = new ArrayList<>();
+        parcel.readParcelableList(
+                recreatedList, DataTypeResult.class.getClassLoader(), DataTypeResult.class);
+
+        assertThat(recreatedList.get(0).getDataType()).isEqualTo(DATA_TYPE_1);
+        assertThat(recreatedList.get(0).getSuccessCount()).isEqualTo(firstTypeSuccessCount);
+        assertThat(recreatedList.get(0).getFailCount())
+                .isEqualTo(firstTypeErrorOneCount + firstTypeErrorTwoCount);
+        assertThat(recreatedList.get(0).getErrors().get(ERROR_1)).isEqualTo(firstTypeErrorOneCount);
+        assertThat(recreatedList.get(0).getErrors().get(ERROR_2)).isEqualTo(firstTypeErrorTwoCount);
+        assertThat(recreatedList.get(1).getDataType()).isEqualTo(DATA_TYPE_2);
+        assertThat(recreatedList.get(1).getSuccessCount()).isEqualTo(secondTypeSuccessCount);
+        assertThat(recreatedList.get(1).getFailCount()).isEqualTo(secondTypeErrorOneCount);
+        assertThat(recreatedList.get(1).getErrors().get(ERROR_1))
+                .isEqualTo(secondTypeErrorOneCount);
+    }
+
+    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);
@@ -267,8 +313,9 @@
     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();
+        return resultList.stream()
+                .filter(dataTypeResult -> dataTypeResult.getDataType().equals(dataType))
+                .findAny();
     }
 
     private byte[] getMetaDataHash(String metaData) {
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index b292d7d..a0ed026 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -294,10 +294,9 @@
 
         StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */);
 
-        IApplicationThread appThread = new StubAppThread();
         Binder activityToken = new Binder();
 
-        ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken);
+        ClientTransaction transaction = ClientTransaction.obtain(null, activityToken);
         transaction.addCallback(callback1);
         transaction.addCallback(callback2);
         transaction.setLifecycleStateRequest(lifecycleRequest);
@@ -318,10 +317,9 @@
         ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
                 config());
 
-        IApplicationThread appThread = new StubAppThread();
         Binder activityToken = new Binder();
 
-        ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken);
+        ClientTransaction transaction = ClientTransaction.obtain(null, activityToken);
         transaction.addCallback(callback1);
         transaction.addCallback(callback2);
 
@@ -339,10 +337,9 @@
         // Write to parcel
         StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */);
 
-        IApplicationThread appThread = new StubAppThread();
         Binder activityToken = new Binder();
 
-        ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken);
+        ClientTransaction transaction = ClientTransaction.obtain(null, activityToken);
         transaction.setLifecycleStateRequest(lifecycleRequest);
 
         writeAndPrepareForReading(transaction);
@@ -400,286 +397,4 @@
             }
         };
     }
-
-    /** Stub implementation of IApplicationThread that can be presented as {@link Binder}. */
-    class StubAppThread extends android.app.IApplicationThread.Stub  {
-
-        @Override
-        public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
-        }
-
-        @Override
-        public void scheduleReceiver(Intent intent, ActivityInfo activityInfo,
-                CompatibilityInfo compatibilityInfo, int i, String s, Bundle bundle, boolean b,
-                int i1, int i2) throws RemoteException {
-        }
-
-        @Override
-        public void scheduleCreateService(IBinder iBinder, ServiceInfo serviceInfo,
-                CompatibilityInfo compatibilityInfo, int i) throws RemoteException {
-        }
-
-        @Override
-        public void scheduleStopService(IBinder iBinder) throws RemoteException {
-        }
-
-        @Override
-        public void bindApplication(String s, ApplicationInfo applicationInfo,
-                String sdkSandboxClientAppVolumeUuid, String sdkSandboxClientAppPackage,
-                ProviderInfoList list, ComponentName componentName, ProfilerInfo profilerInfo,
-                Bundle bundle, IInstrumentationWatcher iInstrumentationWatcher,
-                IUiAutomationConnection iUiAutomationConnection, int i, boolean b, boolean b1,
-                boolean b2, boolean b3, Configuration configuration,
-                CompatibilityInfo compatibilityInfo, Map map, Bundle bundle1, String s1,
-                AutofillOptions ao, ContentCaptureOptions co, long[] disableCompatChanges,
-                SharedMemory serializedSystemFontMap,
-                long startRequestedElapsedTime, long startRequestedUptime)
-                throws RemoteException {
-        }
-
-        @Override
-        public void scheduleExit() throws RemoteException {
-        }
-
-        @Override
-        public void scheduleServiceArgs(IBinder iBinder, ParceledListSlice parceledListSlice)
-                throws RemoteException {
-        }
-
-        @Override
-        public void updateTimeZone() throws RemoteException {
-        }
-
-        @Override
-        public void processInBackground() throws RemoteException {
-        }
-
-        @Override
-        public void scheduleBindService(IBinder iBinder, Intent intent, boolean b, int i)
-                throws RemoteException {
-        }
-
-        @Override
-        public void scheduleUnbindService(IBinder iBinder, Intent intent) throws RemoteException {
-        }
-
-        @Override
-        public void dumpService(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
-                String[] strings) throws RemoteException {
-        }
-
-        @Override
-        public void scheduleRegisteredReceiver(IIntentReceiver iIntentReceiver, Intent intent,
-                int i, String s, Bundle bundle, boolean b, boolean b1, int i1, int i2)
-                throws RemoteException {
-        }
-
-        @Override
-        public void scheduleLowMemory() throws RemoteException {
-        }
-
-        @Override
-        public void profilerControl(boolean b, ProfilerInfo profilerInfo, int i)
-                throws RemoteException {
-        }
-
-        @Override
-        public void setSchedulingGroup(int i) throws RemoteException {
-        }
-
-        @Override
-        public void scheduleCreateBackupAgent(ApplicationInfo applicationInfo,
-                int i, int userId, int operatioType)
-                throws RemoteException {
-        }
-
-        @Override
-        public void scheduleDestroyBackupAgent(ApplicationInfo applicationInfo,
-                int userId) throws RemoteException {
-        }
-
-        @Override
-        public void scheduleOnNewActivityOptions(IBinder iBinder, Bundle bundle)
-                throws RemoteException {
-        }
-
-        @Override
-        public void scheduleSuicide() throws RemoteException {
-        }
-
-        @Override
-        public void dispatchPackageBroadcast(int i, String[] strings) throws RemoteException {
-        }
-
-        @Override
-        public void scheduleCrash(String s, int i, Bundle extras) throws RemoteException {
-        }
-
-        @Override
-        public void dumpActivity(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
-                String s, String[] strings) throws RemoteException {
-        }
-
-        @Override
-        public void clearDnsCache() throws RemoteException {
-        }
-
-        @Override
-        public void updateHttpProxy() throws RemoteException {
-        }
-
-        @Override
-        public void setCoreSettings(Bundle bundle) throws RemoteException {
-        }
-
-        @Override
-        public void updatePackageCompatibilityInfo(String s, CompatibilityInfo compatibilityInfo)
-                throws RemoteException {
-        }
-
-        @Override
-        public void scheduleTrimMemory(int i) throws RemoteException {
-        }
-
-        @Override
-        public void dumpMemInfo(ParcelFileDescriptor parcelFileDescriptor,
-                Debug.MemoryInfo memoryInfo, boolean b, boolean b1, boolean b2, boolean b3,
-                boolean b4, String[] strings) throws RemoteException {
-        }
-
-        @Override
-        public void dumpMemInfoProto(ParcelFileDescriptor parcelFileDescriptor,
-                Debug.MemoryInfo memoryInfo, boolean b, boolean b1, boolean b2,
-                boolean b3, String[] strings) throws RemoteException {
-        }
-
-        @Override
-        public void dumpGfxInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
-                throws RemoteException {
-        }
-
-        @Override
-        public void dumpCacheInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
-                throws RemoteException {
-        }
-
-        @Override
-        public void dumpProvider(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
-                String[] strings) throws RemoteException {
-        }
-
-        @Override
-        public void dumpDbInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
-                throws RemoteException {
-        }
-
-        @Override
-        public void unstableProviderDied(IBinder iBinder) throws RemoteException {
-        }
-
-        @Override
-        public void requestAssistContextExtras(IBinder iBinder, IBinder iBinder1, int i, int i1,
-                int i2) throws RemoteException {
-        }
-
-        @Override
-        public void scheduleTranslucentConversionComplete(IBinder iBinder, boolean b)
-                throws RemoteException {
-        }
-
-        @Override
-        public void setProcessState(int i) throws RemoteException {
-        }
-
-        @Override
-        public void scheduleInstallProvider(ProviderInfo providerInfo) throws RemoteException {
-        }
-
-        @Override
-        public void updateTimePrefs(int i) throws RemoteException {
-        }
-
-        @Override
-        public void scheduleEnterAnimationComplete(IBinder iBinder) throws RemoteException {
-        }
-
-        @Override
-        public void notifyCleartextNetwork(byte[] bytes) throws RemoteException {
-        }
-
-        @Override
-        public void startBinderTracking() throws RemoteException {
-        }
-
-        @Override
-        public void stopBinderTrackingAndDump(ParcelFileDescriptor parcelFileDescriptor)
-                throws RemoteException {
-        }
-
-        @Override
-        public void scheduleLocalVoiceInteractionStarted(IBinder iBinder,
-                IVoiceInteractor iVoiceInteractor) throws RemoteException {
-        }
-
-        @Override
-        public void handleTrustStorageUpdate() throws RemoteException {
-        }
-
-        @Override
-        public void attachAgent(String s) throws RemoteException {
-        }
-
-        @Override
-        public void attachStartupAgents(String s) throws RemoteException {
-        }
-
-        @Override
-        public void scheduleApplicationInfoChanged(ApplicationInfo applicationInfo)
-                throws RemoteException {
-        }
-
-        @Override
-        public void setNetworkBlockSeq(long l) throws RemoteException {
-        }
-
-        @Override
-        public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String path,
-                ParcelFileDescriptor fd, RemoteCallback finishCallback) {
-        }
-
-        @Override
-        public void dumpResources(ParcelFileDescriptor fd, RemoteCallback finishCallback) {
-        }
-
-        @Override
-        public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
-        }
-
-        @Override
-        public void requestDirectActions(IBinder activityToken, IVoiceInteractor interactor,
-                RemoteCallback cancellationCallback, RemoteCallback resultCallback) {
-        }
-
-        @Override
-        public void performDirectAction(IBinder activityToken, String actionId, Bundle arguments,
-                RemoteCallback cancellationCallback, RemoteCallback resultCallback) {
-        }
-
-        @Override
-        public void notifyContentProviderPublishStatus(ContentProviderHolder holder, String auth,
-                int userId, boolean published) {
-        }
-
-        @Override
-        public void instrumentWithoutRestart(ComponentName instrumentationName,
-                Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
-                IUiAutomationConnection instrumentationUiConnection, ApplicationInfo targetInfo) {
-        }
-
-        @Override
-        public void updateUiTranslationState(IBinder activityToken, int state,
-                TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
-                UiTranslationSpec uiTranslationSpec) {
-        }
-    }
 }
diff --git a/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java b/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java
new file mode 100644
index 0000000..f57ee43
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_UNKNOWN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.app.time.DetectorStatusTypes.DetectorStatus;
+
+import org.junit.Test;
+
+public class DetectorStatusTypesTest {
+
+    @Test
+    public void testRequireValidDetectionAlgorithmStatus() {
+        for (@DetectionAlgorithmStatus int status = DETECTION_ALGORITHM_STATUS_UNKNOWN;
+                status <= DETECTION_ALGORITHM_STATUS_RUNNING; status++) {
+            assertEquals(status, DetectorStatusTypes.requireValidDetectionAlgorithmStatus(status));
+        }
+
+        assertThrows(IllegalArgumentException.class,
+                () -> DetectorStatusTypes.requireValidDetectionAlgorithmStatus(
+                        DETECTION_ALGORITHM_STATUS_UNKNOWN - 1));
+        assertThrows(IllegalArgumentException.class,
+                () -> DetectorStatusTypes.requireValidDetectionAlgorithmStatus(
+                        DETECTION_ALGORITHM_STATUS_RUNNING + 1));
+    }
+
+    @Test
+    public void testFormatAndParseDetectionAlgorithmStatus() {
+        for (@DetectionAlgorithmStatus int status = DETECTION_ALGORITHM_STATUS_UNKNOWN;
+                status <= DETECTION_ALGORITHM_STATUS_RUNNING; status++) {
+            assertEquals(status, DetectorStatusTypes.detectionAlgorithmStatusFromString(
+                    DetectorStatusTypes.detectionAlgorithmStatusToString(status)));
+        }
+
+        assertThrows(IllegalArgumentException.class,
+                () -> DetectorStatusTypes.detectorStatusToString(
+                        DETECTION_ALGORITHM_STATUS_UNKNOWN - 1));
+        assertThrows(IllegalArgumentException.class,
+                () -> DetectorStatusTypes.detectorStatusToString(
+                        DETECTION_ALGORITHM_STATUS_RUNNING + 1));
+        assertThrows(IllegalArgumentException.class,
+                () -> DetectorStatusTypes.detectorStatusFromString(null));
+        assertThrows(IllegalArgumentException.class,
+                () -> DetectorStatusTypes.detectorStatusFromString(""));
+        assertThrows(IllegalArgumentException.class,
+                () -> DetectorStatusTypes.detectorStatusFromString("FOO"));
+    }
+
+    @Test
+    public void testRequireValidDetectorStatus() {
+        for (@DetectorStatus int status = DETECTOR_STATUS_UNKNOWN;
+                status <= DETECTOR_STATUS_RUNNING; status++) {
+            assertEquals(status, DetectorStatusTypes.requireValidDetectorStatus(status));
+        }
+
+        assertThrows(IllegalArgumentException.class,
+                () -> DetectorStatusTypes.requireValidDetectorStatus(DETECTOR_STATUS_UNKNOWN - 1));
+        assertThrows(IllegalArgumentException.class,
+                () -> DetectorStatusTypes.requireValidDetectorStatus(DETECTOR_STATUS_RUNNING + 1));
+    }
+
+    @Test
+    public void testFormatAndParseDetectorStatus() {
+        for (@DetectorStatus int status = DETECTOR_STATUS_UNKNOWN;
+                status <= DETECTOR_STATUS_RUNNING; status++) {
+            assertEquals(status, DetectorStatusTypes.detectorStatusFromString(
+                    DetectorStatusTypes.detectorStatusToString(status)));
+        }
+
+        assertThrows(IllegalArgumentException.class,
+                () -> DetectorStatusTypes.detectorStatusToString(DETECTOR_STATUS_UNKNOWN - 1));
+        assertThrows(IllegalArgumentException.class,
+                () -> DetectorStatusTypes.detectorStatusToString(DETECTOR_STATUS_RUNNING + 1));
+        assertThrows(IllegalArgumentException.class,
+                () -> DetectorStatusTypes.detectorStatusFromString(null));
+        assertThrows(IllegalArgumentException.class,
+                () -> DetectorStatusTypes.detectorStatusFromString(""));
+        assertThrows(IllegalArgumentException.class,
+                () -> DetectorStatusTypes.detectorStatusFromString("FOO"));
+    }
+}
diff --git a/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java
new file mode 100644
index 0000000..fc69f69
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java
@@ -0,0 +1,327 @@
+/*
+ * 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.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_NOT_APPLICABLE;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus;
+import android.service.timezone.TimeZoneProviderStatus;
+
+import org.junit.Test;
+
+public class LocationTimeZoneAlgorithmStatusTest {
+
+    private static final TimeZoneProviderStatus ARBITRARY_PROVIDER_RUNNING_STATUS =
+            new TimeZoneProviderStatus.Builder()
+                    .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+                    .build();
+
+    @Test
+    public void testConstructorValidation() {
+        // Sample some invalid cases
+
+        // There can't be a reported provider status if the algorithm isn't running.
+        new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+                PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+        assertThrows(IllegalArgumentException.class,
+                () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+                        PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+                        PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS));
+
+        new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+                PROVIDER_STATUS_NOT_PRESENT, null);
+        assertThrows(IllegalArgumentException.class,
+                () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+                        PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+                        PROVIDER_STATUS_NOT_PRESENT, null));
+
+        new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                PROVIDER_STATUS_NOT_PRESENT, null,
+                PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+        assertThrows(IllegalArgumentException.class,
+                () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+                        PROVIDER_STATUS_NOT_PRESENT, null,
+                        PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS));
+
+        // No reported provider status expected if the associated provider isn't ready / present.
+        new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                PROVIDER_STATUS_NOT_PRESENT, null,
+                PROVIDER_STATUS_NOT_PRESENT, null);
+        assertThrows(IllegalArgumentException.class,
+                () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                        PROVIDER_STATUS_NOT_PRESENT, ARBITRARY_PROVIDER_RUNNING_STATUS,
+                        PROVIDER_STATUS_NOT_PRESENT, null));
+        new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                PROVIDER_STATUS_NOT_READY, null,
+                PROVIDER_STATUS_NOT_PRESENT, null);
+        assertThrows(IllegalArgumentException.class,
+                () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                        PROVIDER_STATUS_NOT_READY, null,
+                        PROVIDER_STATUS_NOT_PRESENT, ARBITRARY_PROVIDER_RUNNING_STATUS));
+    }
+
+    @Test
+    public void testEquals() {
+        LocationTimeZoneAlgorithmStatus one = new LocationTimeZoneAlgorithmStatus(
+                DETECTION_ALGORITHM_STATUS_RUNNING,
+                PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+                PROVIDER_STATUS_NOT_PRESENT, null);
+        assertEqualsAndHashCode(one, one);
+
+        {
+            LocationTimeZoneAlgorithmStatus two = new LocationTimeZoneAlgorithmStatus(
+                    DETECTION_ALGORITHM_STATUS_RUNNING,
+                    PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+                    PROVIDER_STATUS_NOT_PRESENT, null);
+            assertEqualsAndHashCode(one, two);
+        }
+
+        {
+            LocationTimeZoneAlgorithmStatus three = new LocationTimeZoneAlgorithmStatus(
+                    DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+                    PROVIDER_STATUS_NOT_READY, null,
+                    PROVIDER_STATUS_NOT_PRESENT, null);
+            assertNotEquals(one, three);
+            assertNotEquals(three, one);
+        }
+    }
+
+    @Test
+    public void testParcelable() {
+        // Primary provider only.
+        {
+            LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+                    new LocationTimeZoneAlgorithmStatus(
+                            DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+                            PROVIDER_STATUS_NOT_PRESENT, null);
+            assertRoundTripParcelable(locationAlgorithmStatus);
+        }
+
+        // Secondary provider only
+        {
+            LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+                    new LocationTimeZoneAlgorithmStatus(
+                            DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_NOT_PRESENT, null,
+                            PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+            assertRoundTripParcelable(locationAlgorithmStatus);
+        }
+
+        // Algorithm not running.
+        {
+            LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+                    new LocationTimeZoneAlgorithmStatus(
+                            DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+                            PROVIDER_STATUS_NOT_PRESENT, null,
+                            PROVIDER_STATUS_NOT_PRESENT, null);
+            assertRoundTripParcelable(locationAlgorithmStatus);
+        }
+    }
+
+    @Test
+    public void testRequireValidProviderStatus() {
+        for (@ProviderStatus int status = PROVIDER_STATUS_NOT_PRESENT;
+                status <= PROVIDER_STATUS_IS_UNCERTAIN; status++) {
+            assertEquals(status,
+                    LocationTimeZoneAlgorithmStatus.requireValidProviderStatus(status));
+        }
+
+        assertThrows(IllegalArgumentException.class,
+                () -> LocationTimeZoneAlgorithmStatus.requireValidProviderStatus(
+                        PROVIDER_STATUS_NOT_PRESENT - 1));
+        assertThrows(IllegalArgumentException.class,
+                () -> LocationTimeZoneAlgorithmStatus.requireValidProviderStatus(
+                        PROVIDER_STATUS_IS_UNCERTAIN + 1));
+    }
+
+    @Test
+    public void testFormatAndParseProviderStatus() {
+        for (@ProviderStatus int status = PROVIDER_STATUS_NOT_PRESENT;
+                status <= PROVIDER_STATUS_IS_UNCERTAIN; status++) {
+            assertEquals(status, LocationTimeZoneAlgorithmStatus.providerStatusFromString(
+                    LocationTimeZoneAlgorithmStatus.providerStatusToString(status)));
+        }
+
+        assertThrows(IllegalArgumentException.class,
+                () -> LocationTimeZoneAlgorithmStatus.providerStatusToString(
+                        PROVIDER_STATUS_NOT_PRESENT - 1));
+        assertThrows(IllegalArgumentException.class,
+                () -> LocationTimeZoneAlgorithmStatus.providerStatusToString(
+                        PROVIDER_STATUS_IS_UNCERTAIN + 1));
+        assertThrows(IllegalArgumentException.class,
+                () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString(null));
+        assertThrows(IllegalArgumentException.class,
+                () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString(""));
+        assertThrows(IllegalArgumentException.class,
+                () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString("FOO"));
+    }
+
+    @Test
+    public void testParseCommandlineArg_noNullReportedStatuses() {
+        LocationTimeZoneAlgorithmStatus status = new LocationTimeZoneAlgorithmStatus(
+                DETECTION_ALGORITHM_STATUS_RUNNING,
+                PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+                PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+        assertEquals(status,
+                LocationTimeZoneAlgorithmStatus.parseCommandlineArg(status.toString()));
+    }
+
+    @Test
+    public void testParseCommandlineArg_withNullReportedStatuses() {
+        LocationTimeZoneAlgorithmStatus status = new LocationTimeZoneAlgorithmStatus(
+                DETECTION_ALGORITHM_STATUS_RUNNING,
+                PROVIDER_STATUS_IS_CERTAIN, null,
+                PROVIDER_STATUS_IS_UNCERTAIN, null);
+        assertEquals(status,
+                LocationTimeZoneAlgorithmStatus.parseCommandlineArg(status.toString()));
+    }
+
+    @Test
+    public void testCouldEnableTelephonyFallback_notRunning() {
+        LocationTimeZoneAlgorithmStatus notRunning =
+                new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+                        PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+        assertFalse(notRunning.couldEnableTelephonyFallback());
+    }
+
+    @Test
+    public void testCouldEnableTelephonyFallback_unknown() {
+        // DETECTION_ALGORITHM_STATUS_UNKNOWN must never allow fallback
+        LocationTimeZoneAlgorithmStatus unknown =
+                new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_UNKNOWN,
+                        PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+        assertFalse(unknown.couldEnableTelephonyFallback());
+    }
+
+    @Test
+    public void testCouldEnableTelephonyFallback_notSupported() {
+        // DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED must never allow fallback
+        LocationTimeZoneAlgorithmStatus notSupported =
+                new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED,
+                        PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+        assertFalse(notSupported.couldEnableTelephonyFallback());
+    }
+
+    @Test
+    public void testCouldEnableTelephonyFallback_running() {
+        // DETECTION_ALGORITHM_STATUS_RUNNING may allow fallback
+
+        // Sample provider-reported statuses that do / do not enable fallback.
+        TimeZoneProviderStatus enableTelephonyFallbackProviderStatus =
+                new TimeZoneProviderStatus.Builder()
+                        .setLocationDetectionDependencyStatus(
+                                DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
+                        .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+                        .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_NOT_APPLICABLE)
+                        .build();
+        assertTrue(enableTelephonyFallbackProviderStatus.couldEnableTelephonyFallback());
+
+        TimeZoneProviderStatus notEnableTelephonyFallbackProviderStatus =
+                new TimeZoneProviderStatus.Builder()
+                        .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+                        .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+                        .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_NOT_APPLICABLE)
+                        .build();
+        assertFalse(notEnableTelephonyFallbackProviderStatus.couldEnableTelephonyFallback());
+
+        // Provider not ready: Never enable fallback
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+            assertFalse(status.couldEnableTelephonyFallback());
+        }
+
+        // Provider uncertain without reported status: Never enable fallback
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_IS_UNCERTAIN, null, PROVIDER_STATUS_NOT_READY, null);
+            assertFalse(status.couldEnableTelephonyFallback());
+        }
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_IS_UNCERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
+            assertFalse(status.couldEnableTelephonyFallback());
+        }
+
+        // Provider uncertain with reported status: Fallback is based on the status for present
+        // providers that report their status. All present providers must have reported status and
+        // agree that fallback is a good idea.
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus,
+                            PROVIDER_STATUS_NOT_READY, null);
+            assertFalse(status.couldEnableTelephonyFallback());
+        }
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus,
+                            PROVIDER_STATUS_NOT_PRESENT, null);
+            assertTrue(status.couldEnableTelephonyFallback());
+        }
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus,
+                            PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus);
+            assertTrue(status.couldEnableTelephonyFallback());
+        }
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus,
+                            PROVIDER_STATUS_IS_UNCERTAIN, notEnableTelephonyFallbackProviderStatus);
+            assertFalse(status.couldEnableTelephonyFallback());
+        }
+        {
+            LocationTimeZoneAlgorithmStatus status =
+                    new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                            PROVIDER_STATUS_NOT_PRESENT, null,
+                            PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus);
+            assertTrue(status.couldEnableTelephonyFallback());
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/app/time/ParcelableTestSupport.java b/core/tests/coretests/src/android/app/time/ParcelableTestSupport.java
new file mode 100644
index 0000000..13e5e14
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/ParcelableTestSupport.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.reflect.Field;
+
+/** Utility methods related to {@link Parcelable} objects used in several tests. */
+public final class ParcelableTestSupport {
+
+    private ParcelableTestSupport() {}
+
+    /** Returns the result of parceling and unparceling the argument. */
+    @SuppressWarnings("unchecked")
+    public static <T extends Parcelable> T roundTripParcelable(T parcelable) {
+        Parcel parcel = Parcel.obtain();
+        parcel.writeTypedObject(parcelable, 0);
+        parcel.setDataPosition(0);
+
+        Parcelable.Creator<T> creator;
+        try {
+            Field creatorField = parcelable.getClass().getField("CREATOR");
+            creator = (Parcelable.Creator<T>) creatorField.get(null);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new AssertionError(e);
+        }
+        T toReturn = parcel.readTypedObject(creator);
+        parcel.recycle();
+        return toReturn;
+    }
+
+    public static <T extends Parcelable> void assertRoundTripParcelable(T instance) {
+        assertEqualsAndHashCode(instance, roundTripParcelable(instance));
+    }
+
+    /** Asserts that the objects are equal and return identical hash codes. */
+    public static void assertEqualsAndHashCode(Object one, Object two) {
+        assertEquals(one, two);
+        assertEquals(two, one);
+        assertEquals(one.hashCode(), two.hashCode());
+    }
+}
diff --git a/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java b/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java
new file mode 100644
index 0000000..b90c485
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+
+import static org.junit.Assert.assertNotEquals;
+
+import org.junit.Test;
+
+public class TelephonyTimeZoneAlgorithmStatusTest {
+
+    @Test
+    public void testEquals() {
+        TelephonyTimeZoneAlgorithmStatus one = new TelephonyTimeZoneAlgorithmStatus(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
+        assertEqualsAndHashCode(one, one);
+
+        {
+            TelephonyTimeZoneAlgorithmStatus two = new TelephonyTimeZoneAlgorithmStatus(
+                    DETECTION_ALGORITHM_STATUS_RUNNING);
+            assertEqualsAndHashCode(one, two);
+        }
+
+        {
+            TelephonyTimeZoneAlgorithmStatus three = new TelephonyTimeZoneAlgorithmStatus(
+                    DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
+            assertNotEquals(one, three);
+            assertNotEquals(three, one);
+        }
+    }
+
+    @Test
+    public void testParcelable() {
+        // Algorithm running.
+        {
+            TelephonyTimeZoneAlgorithmStatus locationAlgorithmStatus =
+                    new TelephonyTimeZoneAlgorithmStatus(
+                            DETECTION_ALGORITHM_STATUS_RUNNING);
+            assertRoundTripParcelable(locationAlgorithmStatus);
+        }
+
+        // Algorithm not running.
+        {
+            TelephonyTimeZoneAlgorithmStatus locationAlgorithmStatus =
+                    new TelephonyTimeZoneAlgorithmStatus(
+                            DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
+            assertRoundTripParcelable(locationAlgorithmStatus);
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
index c9b96c6..1a276ad 100644
--- a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
@@ -21,7 +21,8 @@
 import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE;
 import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED;
 import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -55,7 +56,7 @@
         {
             TimeCapabilities one = builder1.build();
             TimeCapabilities two = builder2.build();
-            assertEquals(one, two);
+            assertEqualsAndHashCode(one, two);
         }
 
         builder2.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
@@ -69,7 +70,7 @@
         {
             TimeCapabilities one = builder1.build();
             TimeCapabilities two = builder2.build();
-            assertEquals(one, two);
+            assertEqualsAndHashCode(one, two);
         }
 
         builder2.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED);
@@ -83,7 +84,7 @@
         {
             TimeCapabilities one = builder1.build();
             TimeCapabilities two = builder2.build();
-            assertEquals(one, two);
+            assertEqualsAndHashCode(one, two);
         }
     }
 
diff --git a/core/tests/coretests/src/android/app/time/TimeStateTest.java b/core/tests/coretests/src/android/app/time/TimeStateTest.java
index bce0909..25e6e2b 100644
--- a/core/tests/coretests/src/android/app/time/TimeStateTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeStateTest.java
@@ -16,7 +16,8 @@
 
 package android.app.time;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
@@ -52,11 +53,6 @@
         assertNotEquals(time1False_1, time2False);
     }
 
-    private static void assertEqualsAndHashCode(Object one, Object two) {
-        assertEquals(one, two);
-        assertEquals(one.hashCode(), two.hashCode());
-    }
-
     @Test
     public void testParceling() {
         UnixEpochTime time = new UnixEpochTime(1, 2);
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
index 3f7da8a..8bed31f 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -18,7 +18,7 @@
 
 import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED;
 import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java b/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java
new file mode 100644
index 0000000..dfff7ec
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+
+import static org.junit.Assert.assertNotEquals;
+
+import org.junit.Test;
+
+public class TimeZoneDetectorStatusTest {
+
+    private static final TelephonyTimeZoneAlgorithmStatus ARBITRARY_TELEPHONY_ALGORITHM_STATUS =
+            new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING);
+
+    private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_ALGORITHM_STATUS =
+            new LocationTimeZoneAlgorithmStatus(
+                    DETECTION_ALGORITHM_STATUS_RUNNING,
+                    LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null,
+                    LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT, null);
+
+    @Test
+    public void testEquals() {
+        TimeZoneDetectorStatus one = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING,
+                ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+        assertEqualsAndHashCode(one, one);
+
+        {
+            TimeZoneDetectorStatus two = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING,
+                    ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+            assertEqualsAndHashCode(one, two);
+        }
+
+        {
+            TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+                    ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+            assertNotEquals(one, three);
+            assertNotEquals(three, one);
+        }
+
+        {
+            TelephonyTimeZoneAlgorithmStatus telephonyAlgorithmStatus =
+                    new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
+            assertNotEquals(telephonyAlgorithmStatus, ARBITRARY_TELEPHONY_ALGORITHM_STATUS);
+
+            TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+                    telephonyAlgorithmStatus, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+            assertNotEquals(one, three);
+            assertNotEquals(three, one);
+        }
+
+        {
+            LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+                    new LocationTimeZoneAlgorithmStatus(
+                            DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+                            LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null,
+                            LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null);
+            assertNotEquals(locationAlgorithmStatus, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+
+            TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+                    ARBITRARY_TELEPHONY_ALGORITHM_STATUS, locationAlgorithmStatus);
+            assertNotEquals(one, three);
+            assertNotEquals(three, one);
+        }
+    }
+
+    @Test
+    public void testParcelable() {
+        // Detector running.
+        {
+            TimeZoneDetectorStatus locationAlgorithmStatus = new TimeZoneDetectorStatus(
+                    DETECTOR_STATUS_RUNNING, ARBITRARY_TELEPHONY_ALGORITHM_STATUS,
+                    ARBITRARY_LOCATION_ALGORITHM_STATUS);
+            assertRoundTripParcelable(locationAlgorithmStatus);
+        }
+
+        // Detector not running.
+        {
+            TimeZoneDetectorStatus locationAlgorithmStatus =
+                    new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+                            ARBITRARY_TELEPHONY_ALGORITHM_STATUS,
+                            ARBITRARY_LOCATION_ALGORITHM_STATUS);
+            assertRoundTripParcelable(locationAlgorithmStatus);
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
index 35a9dbc..595b700 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
@@ -16,7 +16,8 @@
 
 package android.app.time;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
@@ -52,11 +53,6 @@
         assertNotEquals(zone1False_1, zone2False);
     }
 
-    private static void assertEqualsAndHashCode(Object one, Object two) {
-        assertEquals(one, two);
-        assertEquals(one.hashCode(), two.hashCode());
-    }
-
     @Test
     public void testParceling() {
         assertRoundTripParcelable(new TimeZoneState("Europe/London", true));
diff --git a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
index 0c7c8c1..28da164 100644
--- a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
@@ -16,8 +16,8 @@
 
 package android.app.timedetector;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
index 26cb902..e9ca069 100644
--- a/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
@@ -16,8 +16,8 @@
 
 package android.app.timedetector;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
deleted file mode 100644
index 0efc0ab..0000000
--- a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
+++ /dev/null
@@ -1,85 +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 android.app.timezone;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.os.Parcel;
-
-import androidx.test.filters.LargeTest;
-
-import org.junit.Test;
-
-/**
- * Tests for {@link DistroFormatVersion}.
- */
-@LargeTest
-public class DistroFormatVersionTest {
-
-    @Test
-    public void equalsAndHashCode() {
-        DistroFormatVersion one = new DistroFormatVersion(1, 2);
-        assertEqualsContract(one, one);
-
-        DistroFormatVersion two = new DistroFormatVersion(1, 2);
-        assertEqualsContract(one, two);
-
-        DistroFormatVersion three = new DistroFormatVersion(2, 1);
-        assertFalse(one.equals(three));
-    }
-
-    @Test
-    public void parcelable() {
-        DistroFormatVersion version = new DistroFormatVersion(2, 3);
-
-        Parcel parcel = Parcel.obtain();
-        version.writeToParcel(parcel, 0 /* flags */);
-        parcel.setDataPosition(0);
-
-        DistroFormatVersion newVersion = DistroFormatVersion.CREATOR.createFromParcel(parcel);
-
-        assertEquals(version, newVersion);
-    }
-
-    @Test
-    public void supportsVersion() {
-        DistroFormatVersion deviceVersion = new DistroFormatVersion(2, 2);
-        assertTrue(deviceVersion.supports(deviceVersion));
-
-        DistroFormatVersion sameVersion = new DistroFormatVersion(2, 2);
-        assertTrue(deviceVersion.supports(sameVersion));
-
-        // Minor versions are backwards compatible.
-        DistroFormatVersion sameMajorNewerMinor = new DistroFormatVersion(2, 3);
-        assertTrue(deviceVersion.supports(sameMajorNewerMinor));
-        DistroFormatVersion sameMajorOlderMinor = new DistroFormatVersion(2, 1);
-        assertFalse(deviceVersion.supports(sameMajorOlderMinor));
-
-        // Major versions are not backwards compatible.
-        DistroFormatVersion newerMajor = new DistroFormatVersion(1, 2);
-        assertFalse(deviceVersion.supports(newerMajor));
-        DistroFormatVersion olderMajor = new DistroFormatVersion(3, 2);
-        assertFalse(deviceVersion.supports(olderMajor));
-    }
-
-    private static void assertEqualsContract(DistroFormatVersion one, DistroFormatVersion two) {
-        assertEquals(one, two);
-        assertEquals(one.hashCode(), two.hashCode());
-    }
-}
diff --git a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
deleted file mode 100644
index b519bf8..0000000
--- a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
+++ /dev/null
@@ -1,85 +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 android.app.timezone;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.os.Parcel;
-
-import androidx.test.filters.LargeTest;
-
-import org.junit.Test;
-
-/**
- * Tests for {@link DistroRulesVersion}.
- */
-@LargeTest
-public class DistroRulesVersionTest {
-
-    @Test
-    public void equalsAndHashCode() {
-        DistroRulesVersion one = new DistroRulesVersion("2016a", 2);
-        assertEqualsContract(one, one);
-
-        DistroRulesVersion two = new DistroRulesVersion("2016a", 2);
-        assertEqualsContract(one, two);
-
-        DistroRulesVersion three = new DistroRulesVersion("2016b", 1);
-        assertFalse(one.equals(three));
-    }
-
-    @Test
-    public void parcelable() {
-        DistroRulesVersion version = new DistroRulesVersion("2016a", 2);
-
-        Parcel parcel = Parcel.obtain();
-        version.writeToParcel(parcel, 0 /* flags */);
-        parcel.setDataPosition(0);
-
-        DistroRulesVersion newVersion = DistroRulesVersion.CREATOR.createFromParcel(parcel);
-
-        assertEquals(version, newVersion);
-    }
-
-    @Test
-    public void isOlderThan() {
-        DistroRulesVersion deviceVersion = new DistroRulesVersion("2016b", 2);
-        assertFalse(deviceVersion.isOlderThan(deviceVersion));
-
-        DistroRulesVersion sameVersion = new DistroRulesVersion("2016b", 2);
-        assertFalse(deviceVersion.isOlderThan(sameVersion));
-
-        DistroRulesVersion sameRulesNewerRevision = new DistroRulesVersion("2016b", 3);
-        assertTrue(deviceVersion.isOlderThan(sameRulesNewerRevision));
-
-        DistroRulesVersion sameRulesOlderRevision = new DistroRulesVersion("2016b", 1);
-        assertFalse(deviceVersion.isOlderThan(sameRulesOlderRevision));
-
-        DistroRulesVersion newerRules = new DistroRulesVersion("2016c", 2);
-        assertTrue(deviceVersion.isOlderThan(newerRules));
-
-        DistroRulesVersion olderRules = new DistroRulesVersion("2016a", 2);
-        assertFalse(deviceVersion.isOlderThan(olderRules));
-    }
-
-    private static void assertEqualsContract(DistroRulesVersion one, DistroRulesVersion two) {
-        assertEquals(one, two);
-        assertEquals(one.hashCode(), two.hashCode());
-    }
-}
diff --git a/core/tests/coretests/src/android/app/timezone/OWNERS b/core/tests/coretests/src/android/app/timezone/OWNERS
deleted file mode 100644
index 381ecf1..0000000
--- a/core/tests/coretests/src/android/app/timezone/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 24949
-include /core/java/android/app/timezone/OWNERS
diff --git a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
deleted file mode 100644
index 30cc7ff..0000000
--- a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
+++ /dev/null
@@ -1,146 +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 android.app.timezone;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.os.Parcel;
-
-import androidx.test.filters.LargeTest;
-
-import org.junit.Test;
-
-/**
- * Tests for {@link RulesState}.
- */
-@LargeTest
-public class RulesStateTest {
-
-    @Test
-    public void equalsAndHashCode() {
-        RulesState one = new RulesState(
-                "2016a", formatVersion(1, 2), false /* operationInProgress */,
-                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
-                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
-        assertEqualsContract(one, one);
-
-        RulesState two = new RulesState(
-                "2016a", formatVersion(1, 2), false /* operationInProgress */,
-                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
-                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
-        assertEqualsContract(one, two);
-
-        RulesState differentBaseRules = new RulesState(
-                "2016b", formatVersion(1, 2), false /* operationInProgress */,
-                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
-                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
-        assertFalse(one.equals(differentBaseRules));
-
-        RulesState differentFormatVersion = new RulesState(
-                "2016a", formatVersion(1, 1), false /* operationInProgress */,
-                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
-                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
-        assertFalse(one.equals(differentFormatVersion));
-
-        RulesState differentOperationInProgress = new RulesState(
-                "2016a", formatVersion(1, 1), true /* operationInProgress */,
-                RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
-                RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
-        assertFalse(one.equals(differentOperationInProgress));
-
-        RulesState differentStagedOperation = new RulesState(
-                "2016a", formatVersion(1, 1), false /* operationInProgress */,
-                RulesState.STAGED_OPERATION_UNINSTALL, null /* stagedDistroRulesVersion */,
-                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
-        assertFalse(one.equals(differentStagedOperation));
-
-        RulesState differentStagedInstallVersion = new RulesState(
-                "2016a", formatVersion(1, 1), false /* operationInProgress */,
-                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 4),
-                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
-        assertFalse(one.equals(differentStagedInstallVersion));
-
-        RulesState differentInstalled = new RulesState(
-                "2016a", formatVersion(1, 1), false /* operationInProgress */,
-                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
-                RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
-        assertFalse(one.equals(differentInstalled));
-
-        RulesState differentInstalledVersion = new RulesState(
-                "2016a", formatVersion(1, 1), false /* operationInProgress */,
-                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
-                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
-        assertFalse(one.equals(differentInstalledVersion));
-    }
-
-    @Test
-    public void parcelable() {
-        RulesState rulesState1 = new RulesState(
-                "2016a", formatVersion(1, 1), false /* operationInProgress */,
-                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016b", 2),
-                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
-        checkParcelableRoundTrip(rulesState1);
-
-        RulesState rulesStateWithNulls = new RulesState(
-                "2016a", formatVersion(1, 1), false /* operationInProgress */,
-                RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
-                RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
-        checkParcelableRoundTrip(rulesStateWithNulls);
-
-        RulesState rulesStateWithUnknowns = new RulesState(
-                "2016a", formatVersion(1, 1), true /* operationInProgress */,
-                RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
-                RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
-        checkParcelableRoundTrip(rulesStateWithUnknowns);
-    }
-
-    private static void checkParcelableRoundTrip(RulesState rulesState) {
-        Parcel parcel = Parcel.obtain();
-        rulesState.writeToParcel(parcel, 0 /* flags */);
-        parcel.setDataPosition(0);
-
-        RulesState newVersion = RulesState.CREATOR.createFromParcel(parcel);
-
-        assertEquals(rulesState, newVersion);
-    }
-
-    @Test
-    public void isBaseVersionNewerThan() {
-        RulesState rulesState = new RulesState(
-                "2016b", formatVersion(1, 1), false /* operationInProgress */,
-                RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
-                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
-        assertTrue(rulesState.isBaseVersionNewerThan(rulesVersion("2016a", 1)));
-        assertFalse(rulesState.isBaseVersionNewerThan(rulesVersion("2016b", 1)));
-        assertFalse(rulesState.isBaseVersionNewerThan(rulesVersion("2016c", 1)));
-    }
-
-    private static void assertEqualsContract(RulesState one, RulesState two) {
-        assertEquals(one, two);
-        assertEquals(one.hashCode(), two.hashCode());
-    }
-
-    private static DistroRulesVersion rulesVersion(String rulesVersion, int revision) {
-        return new DistroRulesVersion(rulesVersion, revision);
-    }
-
-    private static DistroFormatVersion formatVersion(int majorVersion, int minorVersion) {
-        return new DistroFormatVersion(majorVersion, minorVersion);
-    }
-}
diff --git a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
deleted file mode 100644
index df9ddea..0000000
--- a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
+++ /dev/null
@@ -1,87 +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 android.app.timezone;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.hamcrest.MockitoHamcrest.argThat;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.UserHandle;
-
-import androidx.test.filters.LargeTest;
-
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.junit.Test;
-
-/**
- * Tests for {@link RulesUpdaterContract}.
- */
-@LargeTest
-public class RulesUpdaterContractTest {
-
-    @Test
-    public void createUpdaterIntent() throws Exception {
-        String packageName = "foobar";
-        Intent intent = RulesUpdaterContract.createUpdaterIntent(packageName);
-
-        assertEquals(RulesUpdaterContract.ACTION_TRIGGER_RULES_UPDATE_CHECK, intent.getAction());
-        assertEquals(packageName, intent.getPackage());
-        assertEquals(Intent.FLAG_INCLUDE_STOPPED_PACKAGES, intent.getFlags());
-    }
-
-    @Test
-    public void sendBroadcast() throws Exception {
-        String packageName = "foobar";
-        byte[] tokenBytes = new byte[] { 1, 2, 3, 4, 5 };
-
-        Intent expectedIntent = RulesUpdaterContract.createUpdaterIntent(packageName);
-        expectedIntent.putExtra(RulesUpdaterContract.EXTRA_CHECK_TOKEN, tokenBytes);
-
-        Context mockContext = mock(Context.class);
-
-        RulesUpdaterContract.sendBroadcast(mockContext, packageName, tokenBytes);
-
-        verify(mockContext).sendBroadcastAsUser(
-                filterEquals(expectedIntent),
-                eq(UserHandle.SYSTEM),
-                eq(RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION));
-    }
-
-    /**
-     * Registers a mockito parameter matcher that uses {@link Intent#filterEquals(Intent)}. to
-     * check the parameter against the intent supplied.
-     */
-    private static Intent filterEquals(final Intent expected) {
-        final Matcher<Intent> m = new BaseMatcher<Intent>() {
-            @Override
-            public boolean matches(Object actual) {
-                return actual != null && expected.filterEquals((Intent) actual);
-            }
-            @Override
-            public void describeTo(Description description) {
-                description.appendText(expected.toString());
-            }
-        };
-        return argThat(m);
-    }
-}
diff --git a/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
index 17838bb1..b5bdea7 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
@@ -16,8 +16,8 @@
 
 package android.app.timezonedetector;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java b/core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java
deleted file mode 100644
index 0073d86..0000000
--- a/core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.timezonedetector;
-
-import static org.junit.Assert.assertEquals;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.lang.reflect.Field;
-
-/** Utility methods related to {@link Parcelable} objects used in several tests. */
-public final class ParcelableTestSupport {
-
-    private ParcelableTestSupport() {}
-
-    /** Returns the result of parceling and unparceling the argument. */
-    @SuppressWarnings("unchecked")
-    public static <T extends Parcelable> T roundTripParcelable(T parcelable) {
-        Parcel parcel = Parcel.obtain();
-        parcel.writeTypedObject(parcelable, 0);
-        parcel.setDataPosition(0);
-
-        Parcelable.Creator<T> creator;
-        try {
-            Field creatorField = parcelable.getClass().getField("CREATOR");
-            creator = (Parcelable.Creator<T>) creatorField.get(null);
-        } catch (NoSuchFieldException | IllegalAccessException e) {
-            throw new AssertionError(e);
-        }
-        T toReturn = parcel.readTypedObject(creator);
-        parcel.recycle();
-        return toReturn;
-    }
-
-    public static <T extends Parcelable> void assertRoundTripParcelable(T instance) {
-        assertEquals(instance, roundTripParcelable(instance));
-    }
-}
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
index 28009d4..d5dcac2 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
@@ -16,8 +16,8 @@
 
 package android.app.timezonedetector;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
new file mode 100644
index 0000000..11afd04
--- /dev/null
+++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
@@ -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 android.companion.virtual.sensor;
+
+import static android.hardware.Sensor.TYPE_ACCELEROMETER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.os.Parcel;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Duration;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualSensorConfigTest {
+
+    private static final String SENSOR_NAME = "VirtualSensorName";
+    private static final String SENSOR_VENDOR = "VirtualSensorVendor";
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private VirtualSensor.SensorStateChangeCallback mSensorCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void parcelAndUnparcel_matches() {
+        final VirtualSensorConfig originalConfig =
+                new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+                        .setVendor(SENSOR_VENDOR)
+                        .setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback)
+                        .build();
+        final Parcel parcel = Parcel.obtain();
+        originalConfig.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+        final VirtualSensorConfig recreatedConfig =
+                VirtualSensorConfig.CREATOR.createFromParcel(parcel);
+        assertThat(recreatedConfig.getType()).isEqualTo(originalConfig.getType());
+        assertThat(recreatedConfig.getName()).isEqualTo(originalConfig.getName());
+        assertThat(recreatedConfig.getVendor()).isEqualTo(originalConfig.getVendor());
+        assertThat(recreatedConfig.getStateChangeCallback()).isNotNull();
+    }
+
+    @Test
+    public void sensorConfig_onlyRequiredFields() {
+        final VirtualSensorConfig config =
+                new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME).build();
+        assertThat(config.getVendor()).isNull();
+        assertThat(config.getStateChangeCallback()).isNull();
+    }
+
+    @Test
+    public void sensorConfig_sensorCallbackInvocation() throws Exception {
+        final VirtualSensorConfig config =
+                new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+                        .setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback)
+                        .build();
+
+        final Duration samplingPeriod = Duration.ofMillis(123);
+        final Duration batchLatency = Duration.ofMillis(456);
+
+        config.getStateChangeCallback().onStateChanged(true,
+                (int) MILLISECONDS.toMicros(samplingPeriod.toMillis()),
+                (int) MILLISECONDS.toMicros(batchLatency.toMillis()));
+
+        verify(mSensorCallback, timeout(1000)).onStateChanged(true, samplingPeriod, batchLatency);
+    }
+}
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
new file mode 100644
index 0000000..a9583fd
--- /dev/null
+++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.companion.virtual.sensor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.SystemClock;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualSensorEventTest {
+
+    private static final long TIMESTAMP_NANOS = SystemClock.elapsedRealtimeNanos();
+    private static final float[] SENSOR_VALUES = new float[] {1.2f, 3.4f, 5.6f};
+
+    @Test
+    public void parcelAndUnparcel_matches() {
+        final VirtualSensorEvent originalEvent = new VirtualSensorEvent.Builder(SENSOR_VALUES)
+                .setTimestampNanos(TIMESTAMP_NANOS)
+                .build();
+        final Parcel parcel = Parcel.obtain();
+        originalEvent.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+        final VirtualSensorEvent recreatedEvent =
+                VirtualSensorEvent.CREATOR.createFromParcel(parcel);
+        assertThat(recreatedEvent.getValues()).isEqualTo(originalEvent.getValues());
+        assertThat(recreatedEvent.getTimestampNanos()).isEqualTo(originalEvent.getTimestampNanos());
+    }
+
+    @Test
+    public void sensorEvent_nullValues() {
+        assertThrows(
+                IllegalArgumentException.class, () -> new VirtualSensorEvent.Builder(null).build());
+    }
+
+    @Test
+    public void sensorEvent_noValues() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new VirtualSensorEvent.Builder(new float[0]).build());
+    }
+
+    @Test
+    public void sensorEvent_noTimestamp_usesCurrentTime() {
+        final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES).build();
+        assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
+        assertThat(TIMESTAMP_NANOS).isLessThan(event.getTimestampNanos());
+        assertThat(event.getTimestampNanos()).isLessThan(SystemClock.elapsedRealtimeNanos());
+    }
+
+    @Test
+    public void sensorEvent_created() {
+        final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES)
+                .setTimestampNanos(TIMESTAMP_NANOS)
+                .build();
+        assertThat(event.getTimestampNanos()).isEqualTo(TIMESTAMP_NANOS);
+        assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
+    }
+}
diff --git a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
index 1509ff9..5dbeac2 100644
--- a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
+++ b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
@@ -49,7 +49,8 @@
         final IntentFilter mockFilter = new IntentFilter("android.content.tests.TestAction");
         try {
             for (int i = 0; i < RECEIVER_LIMIT_PER_APP + 1; i++) {
-                mContext.registerReceiver(new EmptyReceiver(), mockFilter);
+                mContext.registerReceiver(new EmptyReceiver(), mockFilter,
+                        Context.RECEIVER_EXPORTED_UNAUDITED);
             }
             fail("No exception thrown when registering "
                     + (RECEIVER_LIMIT_PER_APP + 1) + " receivers");
diff --git a/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java b/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
index 98485c0..ee73f00 100644
--- a/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
+++ b/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
@@ -29,10 +29,11 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
- * Test class for {@link ConstrainDisplayApisConfig}.
+ * Test for {@link ConstrainDisplayApisConfig}.
  *
  * Build/Install/Run:
  * atest FrameworksCoreTests:ConstrainDisplayApisConfigTest
@@ -72,6 +73,7 @@
         testNeverConstrainDisplayApis("com.android.test", /* version= */ 1, /* expected= */ false);
     }
 
+    @Ignore("b/257375674")
     @Test
     public void neverConstrainDisplayApis_flagsHasSingleEntry_returnsTrueForPackageWithinRange() {
         setNeverConstrainDisplayApisFlag("com.android.test:1:1");
@@ -107,6 +109,7 @@
         testNeverConstrainDisplayApis("com.android.test4", /* version= */ 9, /* expected= */ false);
     }
 
+    @Ignore("b/257375674")
     @Test
     public void neverConstrainDisplayApis_flagHasInvalidEntries_ignoresInvalidEntries() {
         // We add a valid entry before and after the invalid ones to make sure they are applied.
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
index d505492..86e95832 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
 
 import android.content.pm.PackageManager.Property;
@@ -162,40 +163,30 @@
 
     @Test
     public void testProperty_invalidName() throws Exception {
-        try {
+        assertThrows(NullPointerException.class, () -> {
             final Property p = new Property(null, 1, "android", null);
-            fail("expected assertion error");
-        } catch (AssertionError expected) {
-        }
+        });
     }
 
     @Test
     public void testProperty_invalidType() throws Exception {
-        try {
+        assertThrows(IllegalArgumentException.class, () -> {
             final Property p = new Property("invalidTypeProperty", 0, "android", null);
-            fail("expected assertion error");
-        } catch (AssertionError expected) {
-        }
+        });
 
-        try {
+        assertThrows(IllegalArgumentException.class, () -> {
             final Property p = new Property("invalidTypeProperty", 6, "android", null);
-            fail("expected assertion error");
-        } catch (AssertionError expected) {
-        }
+        });
 
-        try {
+        assertThrows(IllegalArgumentException.class, () -> {
             final Property p = new Property("invalidTypeProperty", -1, "android", null);
-            fail("expected assertion error");
-        } catch (AssertionError expected) {
-        }
+        });
     }
 
     @Test
     public void testProperty_noPackageName() throws Exception {
-        try {
+        assertThrows(NullPointerException.class, () -> {
             final Property p = new Property(null, 1, null, null);
-            fail("expected assertion error");
-        } catch (AssertionError expected) {
-        }
+        });
     }
 }
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
new file mode 100644
index 0000000..625c318
--- /dev/null
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.res
+
+import androidx.core.util.forEach
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlin.math.ceil
+import kotlin.math.floor
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FontScaleConverterFactoryTest {
+
+    @Test
+    fun scale200IsTwiceAtSmallSizes() {
+        val table = FontScaleConverterFactory.forScale(2F)!!
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+    }
+
+    @SmallTest
+    fun missingLookupTableReturnsNull() {
+        assertThat(FontScaleConverterFactory.forScale(3F)).isNull()
+    }
+
+    @SmallTest
+    fun missingLookupTable105ReturnsNull() {
+        assertThat(FontScaleConverterFactory.forScale(1.05F)).isNull()
+    }
+
+    @SmallTest
+    fun missingLookupTableNegativeReturnsNull() {
+        assertThat(FontScaleConverterFactory.forScale(-1F)).isNull()
+    }
+
+    @SmallTest
+    fun unnecessaryFontScalesReturnsNull() {
+        assertThat(FontScaleConverterFactory.forScale(0F)).isNull()
+        assertThat(FontScaleConverterFactory.forScale(1F)).isNull()
+        assertThat(FontScaleConverterFactory.forScale(0.85F)).isNull()
+    }
+
+    @SmallTest
+    fun tablesMatchAndAreMonotonicallyIncreasing() {
+        FontScaleConverterFactory.LOOKUP_TABLES.forEach { _, lookupTable ->
+            assertThat(lookupTable.mToDpValues).hasLength(lookupTable.mFromSpValues.size)
+            assertThat(lookupTable.mToDpValues).isNotEmpty()
+
+            assertThat(lookupTable.mFromSpValues.asList()).isInStrictOrder()
+            assertThat(lookupTable.mToDpValues.asList()).isInStrictOrder()
+
+            assertThat(lookupTable.mFromSpValues.asList()).containsNoDuplicates()
+            assertThat(lookupTable.mToDpValues.asList()).containsNoDuplicates()
+        }
+    }
+
+    @LargeTest
+    @Test
+    fun allFeasibleScalesAndConversionsDoNotCrash() {
+        generateSequenceOfFractions(-10000f..10000f, step = 0.01f)
+            .mapNotNull{ FontScaleConverterFactory.forScale(it) }
+            .flatMap{ table ->
+                generateSequenceOfFractions(-10000f..10000f, step = 0.01f)
+                    .map{ Pair(table, it) }
+            }
+            .forEach { (table, sp) ->
+                try {
+                    assertWithMessage(
+                        "convertSpToDp(%s) on table: %s",
+                        sp.toString(),
+                        table.toString()
+                    )
+                        .that(table.convertSpToDp(sp))
+                        .isFinite()
+                } catch (e: Exception) {
+                    throw AssertionError("Exception during convertSpToDp($sp) on table: $table", e)
+                }
+            }
+    }
+
+    @Test
+    fun testGenerateSequenceOfFractions() {
+        val fractions = generateSequenceOfFractions(-1000f..1000f, step = 0.1f)
+            .toList()
+        fractions.forEach {
+            assertThat(it).isAtLeast(-1000f)
+            assertThat(it).isAtMost(1000f)
+        }
+
+        assertThat(fractions).isInStrictOrder()
+        assertThat(fractions).hasSize(1000 * 2 * 10 + 1) // Don't forget the 0 in the middle!
+
+        assertThat(fractions).contains(100f)
+        assertThat(fractions).contains(500.1f)
+        assertThat(fractions).contains(500.2f)
+        assertThat(fractions).contains(0.2f)
+        assertThat(fractions).contains(0f)
+        assertThat(fractions).contains(-10f)
+        assertThat(fractions).contains(-10f)
+        assertThat(fractions).contains(-10.3f)
+
+        assertThat(fractions).doesNotContain(-10.31f)
+        assertThat(fractions).doesNotContain(0.35f)
+        assertThat(fractions).doesNotContain(0.31f)
+        assertThat(fractions).doesNotContain(-.35f)
+    }
+
+    companion object {
+        private const val CONVERSION_TOLERANCE = 0.05f
+    }
+}
+
+fun generateSequenceOfFractions(
+    range: ClosedFloatingPointRange<Float>,
+    step: Float
+): Sequence<Float> {
+    val multiplier = 1f / step
+    val start = floor(range.start * multiplier).toInt()
+    val endInclusive = ceil(range.endInclusive * multiplier).toInt()
+    return generateSequence(start) { it + 1 }
+        .takeWhile { it <= endInclusive }
+        .map{ it.toFloat() / multiplier }
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
new file mode 100644
index 0000000..e405c55
--- /dev/null
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.res
+
+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 FontScaleConverterTest {
+
+    @Test
+    fun straightInterpolation() {
+        val table = createTable(8f to 8f, 10f to 10f, 20f to 20f)
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+        assertThat(table.convertSpToDp(30F)).isWithin(CONVERSION_TOLERANCE).of(30f)
+        assertThat(table.convertSpToDp(20F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+    }
+
+    @Test
+    fun interpolate200Percent() {
+        val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+        assertThat(table.convertSpToDp(30F)).isWithin(CONVERSION_TOLERANCE).of(60f)
+        assertThat(table.convertSpToDp(20F)).isWithin(CONVERSION_TOLERANCE).of(40f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+    }
+
+    @Test
+    fun interpolate150Percent() {
+        val table = createTable(2f to 3f, 10f to 15f, 20f to 30f, 100f to 150f)
+        assertThat(table.convertSpToDp(2F)).isWithin(CONVERSION_TOLERANCE).of(3f)
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1.5f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(12f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(15f)
+        assertThat(table.convertSpToDp(20F)).isWithin(CONVERSION_TOLERANCE).of(30f)
+        assertThat(table.convertSpToDp(50F)).isWithin(CONVERSION_TOLERANCE).of(75f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(7.5f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+    }
+
+    @Test
+    fun pastEndsUsesLastScalingFactor() {
+        val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+        assertThat(table.convertSpToDp(100F)).isWithin(CONVERSION_TOLERANCE).of(200f)
+        assertThat(table.convertSpToDp(31F)).isWithin(CONVERSION_TOLERANCE).of(62f)
+        assertThat(table.convertSpToDp(1000F)).isWithin(CONVERSION_TOLERANCE).of(2000f)
+        assertThat(table.convertSpToDp(2000F)).isWithin(CONVERSION_TOLERANCE).of(4000f)
+        assertThat(table.convertSpToDp(10000F)).isWithin(CONVERSION_TOLERANCE).of(20000f)
+    }
+
+    @Test
+    fun negativeSpIsNegativeDp() {
+        val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+        assertThat(table.convertSpToDp(-1F)).isWithin(CONVERSION_TOLERANCE).of(-2f)
+        assertThat(table.convertSpToDp(-8F)).isWithin(CONVERSION_TOLERANCE).of(-16f)
+        assertThat(table.convertSpToDp(-10F)).isWithin(CONVERSION_TOLERANCE).of(-20f)
+        assertThat(table.convertSpToDp(-30F)).isWithin(CONVERSION_TOLERANCE).of(-60f)
+        assertThat(table.convertSpToDp(-20F)).isWithin(CONVERSION_TOLERANCE).of(-40f)
+        assertThat(table.convertSpToDp(-5F)).isWithin(CONVERSION_TOLERANCE).of(-10f)
+        assertThat(table.convertSpToDp(-0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+    }
+
+    private fun createTable(vararg pairs: Pair<Float, Float>) =
+        FontScaleConverter(
+            pairs.map { it.first }.toFloatArray(),
+            pairs.map { it.second }.toFloatArray()
+        )
+
+    companion object {
+        private const val CONVERSION_TOLERANCE = 0.05f
+    }
+}
diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java
index a528c19..6bf8f56 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java
@@ -203,16 +203,22 @@
                 fallbackMap);
         SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap);
         Map<String, Typeface> copiedFontMap = new ArrayMap<>();
-        Typeface.deserializeFontMap(sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN),
-                copiedFontMap);
-        assertEquals(systemFontMap.size(), copiedFontMap.size());
-        for (String key : systemFontMap.keySet()) {
-            assertTrue(copiedFontMap.containsKey(key));
-            Typeface original = systemFontMap.get(key);
-            Typeface copied = copiedFontMap.get(key);
-            assertEquals(original.getStyle(), copied.getStyle());
-            assertEquals(original.getWeight(), copied.getWeight());
-            assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6);
+        try {
+            Typeface.deserializeFontMap(sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN),
+                    copiedFontMap);
+            assertEquals(systemFontMap.size(), copiedFontMap.size());
+            for (String key : systemFontMap.keySet()) {
+                assertTrue(copiedFontMap.containsKey(key));
+                Typeface original = systemFontMap.get(key);
+                Typeface copied = copiedFontMap.get(key);
+                assertEquals(original.getStyle(), copied.getStyle());
+                assertEquals(original.getWeight(), copied.getWeight());
+                assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6);
+            }
+        } finally {
+            for (Typeface typeface : copiedFontMap.values()) {
+                typeface.releaseNativeObjectForTest();
+            }
         }
     }
 
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java
index 3f504a0..100aba5 100644
--- a/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java
+++ b/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java
@@ -136,6 +136,12 @@
                 .build());
     }
 
+    /**
+     * The combination of TOOL_TYPE_PALM with anything else than ACTION_CANCEL should throw an
+     * exception. This is due to an underlying implementation detail. See documentation of {@link
+     * VirtualTouchEvent}
+     * for details.
+     */
     @Test
     public void touchEvent_palmUsedImproperly() {
         assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
diff --git a/core/tests/coretests/src/android/os/BinderTest.java b/core/tests/coretests/src/android/os/BinderTest.java
index 99dbe64..02f8790 100644
--- a/core/tests/coretests/src/android/os/BinderTest.java
+++ b/core/tests/coretests/src/android/os/BinderTest.java
@@ -20,6 +20,8 @@
 
 import junit.framework.TestCase;
 
+import static org.testng.Assert.assertThrows;
+
 public class BinderTest extends TestCase {
     private static final int UID = 100;
 
@@ -45,12 +47,8 @@
     }
 
     @SmallTest
-    public void testGetCallingUidOrThrow() throws Exception {
-        try {
-            Binder.getCallingUidOrThrow();
-            throw new AssertionError("IllegalStateException expected");
-        } catch (IllegalStateException expected) {
-        }
+    public void testGetCallingUidOrThrow_throws() throws Exception {
+        assertThrows(IllegalStateException.class, () -> Binder.getCallingUidOrThrow());
     }
 
     @SmallTest
diff --git a/core/tests/coretests/src/android/os/EnvironmentTest.java b/core/tests/coretests/src/android/os/EnvironmentTest.java
index c0325ca..8e63a0f 100644
--- a/core/tests/coretests/src/android/os/EnvironmentTest.java
+++ b/core/tests/coretests/src/android/os/EnvironmentTest.java
@@ -47,29 +47,6 @@
         return InstrumentationRegistry.getContext();
     }
 
-    /**
-     * Sets {@code mode} for the given {@code ops} and the given {@code uid}.
-     *
-     * <p>This method drops shell permission identity.
-     */
-    private static void setAppOpsModeForUid(int uid, int mode, String... ops) {
-        if (ops == null) {
-            return;
-        }
-        InstrumentationRegistry.getInstrumentation()
-                .getUiAutomation()
-                .adoptShellPermissionIdentity();
-        try {
-            for (String op : ops) {
-                getContext().getSystemService(AppOpsManager.class).setUidMode(op, uid, mode);
-            }
-        } finally {
-            InstrumentationRegistry.getInstrumentation()
-                    .getUiAutomation()
-                    .dropShellPermissionIdentity();
-        }
-    }
-
     @Before
     public void setUp() throws Exception {
         dir = getContext().getDir("testing", Context.MODE_PRIVATE);
@@ -127,17 +104,4 @@
         Environment.buildPath(dir, "Taxes.pdf").createNewFile();
         assertEquals(HAS_OTHER, classifyExternalStorageDirectory(dir));
     }
-
-    @Test
-    public void testIsExternalStorageManager() throws Exception {
-        assertFalse(Environment.isExternalStorageManager());
-        try {
-            setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ALLOWED,
-                    AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE);
-            assertTrue(Environment.isExternalStorageManager());
-        } finally {
-            setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_DEFAULT,
-                    AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE);
-        }
-    }
 }
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 69eb13f..44923b6 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -114,6 +114,24 @@
     }
 
     @Test
+    public void testSendHint() {
+        Session s = createSession();
+        assumeNotNull(s);
+        s.sendHint(Session.CPU_LOAD_RESET);
+        // ensure we can also send within the rate limit without exception
+        s.sendHint(Session.CPU_LOAD_RESET);
+    }
+
+    @Test
+    public void testSendHintWithNegativeHint() {
+        Session s = createSession();
+        assumeNotNull(s);
+        assertThrows(IllegalArgumentException.class, () -> {
+            s.sendHint(-1);
+        });
+    }
+
+    @Test
     public void testCloseHintSession() {
         Session s = createSession();
         assumeNotNull(s);
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index f7ca822..0c7ff4a 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import static android.os.VibrationEffect.DEFAULT_AMPLITUDE;
 import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
 import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
 
@@ -48,6 +49,7 @@
 import org.mockito.junit.MockitoJUnitRunner;
 
 import java.time.Duration;
+import java.util.Arrays;
 
 @Presubmit
 @RunWith(MockitoJUnitRunner.class)
@@ -62,16 +64,363 @@
     private static final int TEST_AMPLITUDE = 100;
     private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 };
     private static final int[] TEST_AMPLITUDES =
-            new int[] { 255, 0, VibrationEffect.DEFAULT_AMPLITUDE };
+            new int[] { 255, 0, DEFAULT_AMPLITUDE };
 
     private static final VibrationEffect TEST_ONE_SHOT =
             VibrationEffect.createOneShot(TEST_TIMING, TEST_AMPLITUDE);
     private static final VibrationEffect DEFAULT_ONE_SHOT =
-            VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE);
+            VibrationEffect.createOneShot(TEST_TIMING, DEFAULT_AMPLITUDE);
     private static final VibrationEffect TEST_WAVEFORM =
             VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
 
     @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnEvenIndices() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 3, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnOddIndices() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {
+                        DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1, 2, 3, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheStart() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {0, 0, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {3, 3};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheEnd() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, 0, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_allDefaultAmplitudes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {
+                        DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 6};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_allZeroAmplitudes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* amplitudes= */ new int[] {0, 0, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {6};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_sparsedZeroAmplitudes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5, 6, 7},
+                /* amplitudes= */ new int[] {
+                        0, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {3, 3, 4, 11, 7};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithDefaultAmplitude() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1},
+                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithZeroAmplitude() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1},
+                /* amplitudes= */ new int[] {0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_repeating() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ 0);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4, 5},
+                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+                /* repeatIndex= */ 3);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2},
+                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                /* repeatIndex= */ 1);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsAndAmplitudes_badAmplitude() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1},
+                /* amplitudes= */ new int[] {200},
+                /* repeatIndex= */ -1);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_nonZeroTimings() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 3};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_oneValue() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {5},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_zeroesAtTheEnd() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 0, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 3, 0, 0};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_zeroesAtTheStart() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {0, 0, 1, 2, 3},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 0, 1, 2, 3};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_zeroesAtTheMiddle() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 0, 0, 3, 4, 5},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {1, 2, 0, 0, 3, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_sparsedZeroes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0},
+                /* repeatIndex= */ -1);
+        long[] expectedPattern = new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_timingsOnly_repeating() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0},
+                /* repeatIndex= */ 0);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[] {1, 2, 3, 4},
+                /* repeatIndex= */ 2);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_notPatternPased() {
+        VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_oneShot_defaultAmplitude() {
+        VibrationEffect effect = VibrationEffect.createOneShot(
+                /* milliseconds= */ 5, /* ampliutde= */ DEFAULT_AMPLITUDE);
+        long[] expectedPattern = new long[] {0, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_oneShot_badAmplitude() {
+        VibrationEffect effect = VibrationEffect.createOneShot(
+                /* milliseconds= */ 5, /* ampliutde= */ 50);
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_noOffDuration() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {5},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {2, 3},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {10, 20},
+                                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {4, 5},
+                                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .compose();
+        long[] expectedPattern = new long[] {7, 33, 4, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_withOffDuration() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addOffDuration(Duration.ofMillis(20))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {10, 20},
+                                /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {30, 40},
+                                /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+                                /* repeatIndex= */ -1))
+                .addOffDuration(Duration.ofMillis(10))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {4, 5},
+                                /* repeatIndex= */ -1))
+                .addOffDuration(Duration.ofMillis(5))
+                .compose();
+        long[] expectedPattern = new long[] {30, 90, 14, 5, 5};
+
+        assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_withPrimitives() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                .addOffDuration(Duration.ofMillis(20))
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {5},
+                                /* repeatIndex= */ -1))
+                .compose();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_composition_repeating() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addEffect(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {5},
+                                /* repeatIndex= */ -1))
+                .repeatEffectIndefinitely(
+                        VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {2, 3},
+                                /* repeatIndex= */ -1))
+                .compose();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
+    public void computeLegacyPattern_effectsViaStartWaveform() {
+        // Effects created via startWaveform are not expected to be converted to long[] patterns, as
+        // they are not configured to always play with the default amplitude.
+        VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
+                .addSustain(Duration.ofMillis(200))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
+                .build();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.startWaveform(targetFrequency(60))
+                .addTransition(Duration.ofMillis(80), targetAmplitude(1))
+                .addSustain(Duration.ofMillis(200))
+                .addTransition(Duration.ofMillis(100), targetAmplitude(0))
+                .build();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.startWaveform(targetFrequency(60))
+                .addTransition(Duration.ofMillis(100), targetFrequency(50))
+                .addSustain(Duration.ofMillis(50))
+                .addTransition(Duration.ofMillis(20), targetFrequency(75))
+                .build();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+    }
+
+    @Test
     public void getRingtones_noPrebakedRingtones() {
         Resources r = mockRingtoneResources(new String[0]);
         Context context = mockContext(r);
@@ -100,7 +449,7 @@
     @Test
     public void testValidateOneShot() {
         VibrationEffect.createOneShot(1, 255).validate();
-        VibrationEffect.createOneShot(1, VibrationEffect.DEFAULT_AMPLITUDE).validate();
+        VibrationEffect.createOneShot(1, DEFAULT_AMPLITUDE).validate();
 
         assertThrows(IllegalArgumentException.class,
                 () -> VibrationEffect.createOneShot(-1, 255).validate());
@@ -501,6 +850,13 @@
         assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_TICK).isHapticFeedbackCandidate());
     }
 
+    private void assertArrayEq(long[] expected, long[] actual) {
+        assertTrue(
+                String.format("Expected pattern %s, but was %s",
+                        Arrays.toString(expected), Arrays.toString(actual)),
+                Arrays.equals(expected, actual));
+    }
+
     private Resources mockRingtoneResources() {
         return mockRingtoneResources(new String[]{
                 RINGTONE_URI_1,
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
index 7ebebc9..c59a3f5 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -246,10 +246,12 @@
     @Test
     public void getQFactorAndResonantFrequency_differentValues_returnsNaN() {
         VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setQFactor(1f)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
                 .build();
         VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setQFactor(2f)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 2, 2, null))
                 .build();
@@ -258,6 +260,7 @@
 
         assertTrue(Float.isNaN(info.getQFactor()));
         assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+        assertEmptyFrequencyProfileAndControl(info);
 
         // One vibrator with values undefined.
         VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3).build();
@@ -266,16 +269,19 @@
 
         assertTrue(Float.isNaN(info.getQFactor()));
         assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+        assertEmptyFrequencyProfileAndControl(info);
     }
 
     @Test
     public void getQFactorAndResonantFrequency_sameValues_returnsValue() {
         VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setQFactor(10f)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
                         /* resonantFrequencyHz= */ 11, 10, 0.5f, null))
                 .build();
         VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setQFactor(10f)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
                         /* resonantFrequencyHz= */ 11, 5, 1, null))
@@ -285,113 +291,131 @@
 
         assertEquals(10f, info.getQFactor(), TEST_TOLERANCE);
         assertEquals(11f, info.getResonantFrequencyHz(), TEST_TOLERANCE);
+
+        // No frequency range defined.
+        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
     }
 
     @Test
     public void getFrequencyProfile_noVibrator_returnsEmpty() {
         VibratorInfo info = new SystemVibrator.NoVibratorInfo();
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
     }
 
     @Test
     public void getFrequencyProfile_differentResonantFrequencyOrResolutionValues_returnsEmpty() {
         VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
                         new float[] { 0, 1 }))
                 .build();
         VibratorInfo differentResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 1, 1,
                         new float[] { 0, 1 }))
                 .build();
         VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, differentResonantFrequency});
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
 
         VibratorInfo differentFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 2,
                         new float[] { 0, 1 }))
                 .build();
         info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, differentFrequencyResolution});
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
     }
 
     @Test
     public void getFrequencyProfile_missingValues_returnsEmpty() {
         VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
                         new float[] { 0, 1 }))
                 .build();
         VibratorInfo missingResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(Float.NaN, 1, 1,
                         new float[] { 0, 1 }))
                 .build();
         VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, missingResonantFrequency});
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
 
         VibratorInfo missingMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, Float.NaN, 1,
                         new float[] { 0, 1 }))
                 .build();
         info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, missingMinFrequency});
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
 
         VibratorInfo missingFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, Float.NaN,
                         new float[] { 0, 1 }))
                 .build();
         info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, missingFrequencyResolution});
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
 
         VibratorInfo missingMaxAmplitudes = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
                 .build();
         info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, missingMaxAmplitudes});
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
     }
 
     @Test
     public void getFrequencyProfile_unalignedMaxAmplitudes_returnsEmpty() {
         VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
                         new float[] { 0, 1, 1, 0 }))
                 .build();
         VibratorInfo unalignedMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.1f, 0.5f,
                         new float[] { 0, 1, 1, 0 }))
                 .build();
         VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
                         new float[] { 0, 1, 1, 0 }))
                 .build();
         VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, unalignedMinFrequency, thirdVibrator});
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
     }
 
     @Test
     public void getFrequencyProfile_alignedProfiles_returnsIntersection() {
         VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
                         new float[] { 0.5f, 1, 1, 0.5f }))
                 .build();
         VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
                         new float[] { 1, 1, 1 }))
                 .build();
         VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
                         new float[] { 0.8f, 1, 0.8f, 0.5f }))
                 .build();
@@ -401,6 +425,20 @@
         assertEquals(
                 new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
                 info.getFrequencyProfile());
+        assertEquals(true, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+
+        // Third vibrator without frequency control capability.
+        thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+                        new float[] { 0.8f, 1, 0.8f, 0.5f }))
+                .build();
+        info = new SystemVibrator.MultiVibratorInfo(
+                new VibratorInfo[]{firstVibrator, secondVibrator, thirdVibrator});
+
+        assertEquals(
+                new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
+                info.getFrequencyProfile());
+        assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
     }
 
     @Test
@@ -547,4 +585,12 @@
         VibrationAttributes vibrationAttributes = captor.getValue();
         assertEquals(new VibrationAttributes.Builder().build(), vibrationAttributes);
     }
+
+    /**
+     * Asserts that the frequency profile is empty, and therefore frequency control isn't supported.
+     */
+    void assertEmptyFrequencyProfileAndControl(VibratorInfo info) {
+        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+    }
 }
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 352c6a7..aa1853f 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -857,7 +857,10 @@
         ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
         String compositeName = namespace + "/" + key;
         Bundle result = resolver.call(
-                DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
+                Settings.Config.CONTENT_URI,
+                Settings.CALL_METHOD_DELETE_CONFIG,
+                compositeName,
+                null);
         assertThat(result).isNotNull();
         return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
     }
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index ee0b127..2e31bb5 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -76,7 +76,7 @@
         when(mMockContentProvider.getIContentProvider()).thenReturn(mMockIContentProvider);
         mMockContentResolver = new MockContentResolver(InstrumentationRegistry
                 .getInstrumentation().getContext());
-        mMockContentResolver.addProvider(DeviceConfig.CONTENT_URI.getAuthority(),
+        mMockContentResolver.addProvider(Settings.Config.CONTENT_URI.getAuthority(),
                 mMockContentProvider);
         mCacheGenerationStore = new MemoryIntArray(1);
         mStorage = new HashMap<>();
@@ -84,7 +84,7 @@
         // Stores keyValues for a given prefix and increments the generation. (Note that this
         // increments the generation no matter what, it doesn't pay attention to if anything
         // actually changed).
-        when(mMockIContentProvider.call(any(), eq(DeviceConfig.CONTENT_URI.getAuthority()),
+        when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
                 eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
                 any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
                     Bundle incomingBundle = invocationOnMock.getArgument(4);
@@ -104,7 +104,7 @@
         // Returns the keyValues corresponding to a namespace, or an empty map if the namespace
         // doesn't have anything stored for it. Returns the generation key if the caller asked
         // for one.
-        when(mMockIContentProvider.call(any(), eq(DeviceConfig.CONTENT_URI.getAuthority()),
+        when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
                 eq(Settings.CALL_METHOD_LIST_CONFIG),
                 any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
                     Bundle incomingBundle = invocationOnMock.getArgument(4);
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 4adbc91..1331779 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -345,27 +345,33 @@
         try {
             // value is empty
             Bundle results =
-                    r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+                    r.call(Settings.Config.CONTENT_URI,
+                           Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertNull(results.get(Settings.NameValueTable.VALUE));
 
             // save value
-            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+            results = r.call(Settings.Config.CONTENT_URI,
+                             Settings.CALL_METHOD_PUT_CONFIG, name, args);
             assertNull(results);
 
             // value is no longer empty
-            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+            results = r.call(Settings.Config.CONTENT_URI,
+                             Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertEquals(value, results.get(Settings.NameValueTable.VALUE));
 
             // save new value
             args.putString(Settings.NameValueTable.VALUE, newValue);
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+            r.call(Settings.Config.CONTENT_URI,
+                    Settings.CALL_METHOD_PUT_CONFIG, name, args);
 
             // new value is returned
-            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+            results = r.call(Settings.Config.CONTENT_URI,
+                             Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertEquals(newValue, results.get(Settings.NameValueTable.VALUE));
         } finally {
             // clean up
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+            r.call(Settings.Config.CONTENT_URI,
+                    Settings.CALL_METHOD_DELETE_CONFIG, name, null);
         }
     }
 
@@ -379,23 +385,25 @@
 
         try {
             // save value
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+            r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
 
             // get value
             Bundle results =
-                    r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+                    r.call(Settings.Config.CONTENT_URI,
+                            Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertEquals(value, results.get(Settings.NameValueTable.VALUE));
 
             // delete value
-            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name,
+            results = r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name,
                     null);
 
             // value is empty now
-            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+            results = r.call(Settings.Config.CONTENT_URI,
+                            Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertNull(results.get(Settings.NameValueTable.VALUE));
         } finally {
             // clean up
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+            r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
         }
     }
 
@@ -413,12 +421,12 @@
 
         try {
             // save both values
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+            r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
             args.putString(Settings.NameValueTable.VALUE, newValue);
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
+            r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
 
             // list all values
-            Bundle result = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
+            Bundle result = r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
                     null, null);
             Map<String, String> keyValueMap =
                     (HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
@@ -428,14 +436,15 @@
 
             // list values for prefix
             args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
-            result = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG, null, args);
+            result = r.call(Settings.Config.CONTENT_URI,
+                            Settings.CALL_METHOD_LIST_CONFIG, null, args);
             keyValueMap = (HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
             assertThat(keyValueMap, aMapWithSize(1));
             assertEquals(value, keyValueMap.get(name));
         } finally {
             // clean up
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
+            r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+            r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
         }
     }
 }
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
index ab63f14..7f772dd 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
@@ -16,14 +16,19 @@
 
 package android.service.timezone;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
 import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -34,12 +39,92 @@
 
 public class TimeZoneProviderEventTest {
 
+    public static final TimeZoneProviderStatus ARBITRARY_TIME_ZONE_PROVIDER_STATUS =
+            new TimeZoneProviderStatus.Builder()
+                    .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+                    .build();
+
+    @Test
+    public void createPermanentFailure() {
+        long creationElapsedMillis = 1111L;
+        String cause = "Cause";
+        TimeZoneProviderEvent event = TimeZoneProviderEvent.createPermanentFailureEvent(
+                creationElapsedMillis, cause);
+
+        assertEquals(EVENT_TYPE_PERMANENT_FAILURE, event.getType());
+        assertEquals(cause, event.getFailureCause());
+        assertEquals(creationElapsedMillis, event.getCreationElapsedMillis());
+        assertNull(event.getSuggestion());
+        assertNull(event.getTimeZoneProviderStatus());
+    }
+
+    @Test
+    public void createSuggestion() {
+        long creationElapsedMillis = 1111L;
+        TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+                .setElapsedRealtimeMillis(2222L)
+                .setTimeZoneIds(Collections.singletonList("Europe/London"))
+                .build();
+
+        TimeZoneProviderStatus reportedStatus = new TimeZoneProviderStatus.Builder()
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+                .build();
+
+        assertThrows(NullPointerException.class, () -> TimeZoneProviderEvent.createSuggestionEvent(
+                creationElapsedMillis, /*suggestion=*/null, reportedStatus));
+
+        // Only TimeZoneProvider can report itself certain.
+        {
+            TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
+                    creationElapsedMillis, suggestion, reportedStatus);
+            assertEquals(EVENT_TYPE_SUGGESTION, event.getType());
+            assertEquals(creationElapsedMillis, event.getCreationElapsedMillis());
+            assertNull(event.getFailureCause());
+            assertEquals(suggestion, event.getSuggestion());
+        }
+
+        // Legacy API events can be created where the TimeZoneProviderStatus is omitted.
+        {
+            TimeZoneProviderStatus legacyStatus = null;
+            TimeZoneProviderEvent legacyEvent = TimeZoneProviderEvent.createSuggestionEvent(
+                    creationElapsedMillis, suggestion, legacyStatus);
+            assertEquals(legacyStatus, legacyEvent.getTimeZoneProviderStatus());
+        }
+    }
+
+    @Test
+    public void createUncertain() {
+        long creationElapsedMillis = 1111L;
+
+        // The TimeZoneProvider can report itself uncertain.
+        {
+            TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(
+                    creationElapsedMillis, ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
+            assertEquals(EVENT_TYPE_UNCERTAIN, event.getType());
+            assertEquals(creationElapsedMillis, event.getCreationElapsedMillis());
+            assertNull(event.getFailureCause());
+            assertNull(event.getSuggestion());
+        }
+
+        // Legacy API events can be created where the TimeZoneProviderStatus is omitted.
+        {
+            TimeZoneProviderStatus legacyStatus = null;
+            TimeZoneProviderEvent legacyEvent = TimeZoneProviderEvent.createUncertainEvent(
+                    creationElapsedMillis, legacyStatus);
+            assertEquals(legacyStatus, legacyEvent.getTimeZoneProviderStatus());
+        }
+    }
+
     @Test
     public void isEquivalentToAndEquals() {
         long creationElapsedMillis = 1111L;
         TimeZoneProviderEvent failEvent =
                 TimeZoneProviderEvent.createPermanentFailureEvent(creationElapsedMillis, "one");
-        TimeZoneProviderStatus providerStatus = TimeZoneProviderStatus.UNKNOWN;
+        TimeZoneProviderStatus providerStatus = ARBITRARY_TIME_ZONE_PROVIDER_STATUS;
 
         TimeZoneProviderEvent uncertainEvent =
                 TimeZoneProviderEvent.createUncertainEvent(creationElapsedMillis, providerStatus);
@@ -85,14 +170,14 @@
     @Test
     public void isEquivalentToAndEquals_uncertain() {
         TimeZoneProviderStatus status1 = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
                 .build();
         TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
                 .build();
 
         TimeZoneProviderEvent uncertain1v1 =
@@ -123,14 +208,14 @@
     @Test
     public void isEquivalentToAndEquals_suggestion() {
         TimeZoneProviderStatus status1 = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
                 .build();
         TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
                 .build();
         TimeZoneProviderSuggestion suggestion1 = new TimeZoneProviderSuggestion.Builder()
                 .setElapsedRealtimeMillis(1111L)
@@ -194,7 +279,13 @@
     @Test
     public void testParcelable_uncertain() {
         TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(
-                1111L, TimeZoneProviderStatus.UNKNOWN);
+                1111L, ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
+        assertRoundTripParcelable(event);
+    }
+
+    @Test
+    public void testParcelable_uncertain_legacy() {
+        TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(1111L, null);
         assertRoundTripParcelable(event);
     }
 
@@ -204,7 +295,17 @@
                 .setTimeZoneIds(Arrays.asList("Europe/London", "Europe/Paris"))
                 .build();
         TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
-                1111L, suggestion, TimeZoneProviderStatus.UNKNOWN);
+                1111L, suggestion, ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
+        assertRoundTripParcelable(event);
+    }
+
+    @Test
+    public void testParcelable_suggestion_legacy() {
+        TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+                .setTimeZoneIds(Arrays.asList("Europe/London", "Europe/Paris"))
+                .build();
+        TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
+                1111L, suggestion, null);
         assertRoundTripParcelable(event);
     }
 
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
index d61c33c..0c1630e 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
@@ -16,95 +16,88 @@
 
 package android.service.timezone;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_UNKNOWN;
 import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertThrows;
+
+import android.service.timezone.TimeZoneProviderStatus.DependencyStatus;
+import android.service.timezone.TimeZoneProviderStatus.OperationStatus;
 
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.IntStream;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+/** Non-SDK tests. See CTS for SDK API tests. */
+@RunWith(JUnitParamsRunner.class)
 public class TimeZoneProviderStatusTest {
 
     @Test
-    public void testStatusValidation() {
+    public void parseProviderStatus() {
         TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                .setTimeZoneResolutionStatus(DEPENDENCY_STATUS_WORKING)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
                 .build();
 
-        assertThrows(IllegalArgumentException.class,
-                () -> new TimeZoneProviderStatus.Builder(status)
-                        .setLocationDetectionStatus(-1)
-                        .build());
-        assertThrows(IllegalArgumentException.class,
-                () -> new TimeZoneProviderStatus.Builder(status)
-                        .setConnectivityStatus(-1)
-                        .build());
-        assertThrows(IllegalArgumentException.class,
-                () -> new TimeZoneProviderStatus.Builder(status)
-                        .setTimeZoneResolutionStatus(-1)
-                        .build());
+        assertEquals(status, TimeZoneProviderStatus.parseProviderStatus(status.toString()));
     }
 
     @Test
-    public void testEqualsAndHashcode() {
-        TimeZoneProviderStatus status1_1 = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
-                .build();
-        assertEqualsAndHashcode(status1_1, status1_1);
-        assertNotEquals(status1_1, null);
+    @Parameters(method = "couldEnableTelephonyFallbackParams")
+    public void couldEnableTelephonyFallback(@DependencyStatus int locationDetectionStatus,
+            @DependencyStatus int connectivityStatus, @OperationStatus int tzResolutionStatus) {
+        TimeZoneProviderStatus providerStatus =
+                new TimeZoneProviderStatus.Builder()
+                        .setLocationDetectionDependencyStatus(locationDetectionStatus)
+                        .setConnectivityDependencyStatus(connectivityStatus)
+                        .setTimeZoneResolutionOperationStatus(tzResolutionStatus)
+                        .build();
+        boolean locationDetectionStatusCouldEnableFallback =
+                (locationDetectionStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT
+                        || locationDetectionStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS);
+        boolean connectivityStatusCouldEnableFallback =
+                (connectivityStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT
+                        || connectivityStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS);
+        boolean tzResolutionStatusCouldEnableFallback = false;
 
-        {
-            TimeZoneProviderStatus status1_2 =
-                    new TimeZoneProviderStatus.Builder(status1_1).build();
-            assertEqualsAndHashcode(status1_1, status1_2);
-            assertNotSame(status1_1, status1_2);
-        }
-
-        {
-            TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setLocationDetectionStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
-                    .build();
-            assertNotEquals(status1_1, status2);
-        }
-
-        {
-            TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setConnectivityStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
-                    .build();
-            assertNotEquals(status1_1, status2);
-        }
-
-        {
-            TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
-                    .build();
-            assertNotEquals(status1_1, status2);
-        }
+        assertEquals(locationDetectionStatusCouldEnableFallback
+                        || connectivityStatusCouldEnableFallback
+                        || tzResolutionStatusCouldEnableFallback,
+                providerStatus.couldEnableTelephonyFallback());
     }
 
-    private static void assertEqualsAndHashcode(Object one, Object two) {
-        assertEquals(one, two);
-        assertEquals(two, one);
-        assertEquals(one.hashCode(), two.hashCode());
-    }
+    public static Integer[][] couldEnableTelephonyFallbackParams() {
+        List<Integer[]> params = new ArrayList<>();
+        @DependencyStatus int[] dependencyStatuses =
+                IntStream.rangeClosed(
+                        DEPENDENCY_STATUS_UNKNOWN, DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS).toArray();
+        @OperationStatus int[] operationStatuses =
+                IntStream.rangeClosed(OPERATION_STATUS_UNKNOWN, OPERATION_STATUS_FAILED).toArray();
 
-    @Test
-    public void testParcelable() {
-        TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
-                .build();
-        assertRoundTripParcelable(status);
+        // Cartesian product: dependencyStatus x dependencyStatus x operationStatus
+        for (@DependencyStatus int locationDetectionStatus : dependencyStatuses) {
+            for (@DependencyStatus int connectivityStatus : dependencyStatuses) {
+                for (@OperationStatus int tzResolutionStatus : operationStatuses) {
+                    params.add(new Integer[] {
+                            locationDetectionStatus,
+                            connectivityStatus,
+                            tzResolutionStatus
+                    });
+                }
+            }
+        }
+        return params.toArray(new Integer[0][0]);
     }
 }
diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java
index 212cc44..8459330 100644
--- a/core/tests/coretests/src/android/text/format/DateFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java
@@ -156,8 +156,8 @@
     @DisableCompatChanges({DateFormat.DISALLOW_DUPLICATE_FIELD_IN_SKELETON})
     public void testGetBestDateTimePattern_enableDuplicateField() {
         // en-US uses 12-hour format by default.
-        assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
-        assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
+        assertEquals("h:mm\u202fa", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
+        assertEquals("h:mm\u202fa", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
     }
 
     private static void assertIllegalArgumentException(Locale l, String skeleton) {
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index 9c06395..de7244d 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -93,7 +93,8 @@
         assertEquals("January 19",
                 formatDateRange(en_US, tz, timeWithCurrentYear, timeWithCurrentYear + HOUR,
                         FORMAT_SHOW_DATE));
-        assertEquals("3:30 AM", formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_SHOW_TIME));
+        assertEquals("3:30\u202fAM", formatDateRange(en_US, tz, fixedTime, fixedTime,
+                FORMAT_SHOW_TIME));
         assertEquals("January 19, 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + HOUR, FORMAT_SHOW_YEAR));
         assertEquals("January 19",
@@ -101,27 +102,27 @@
         assertEquals("January",
                 formatDateRange(en_US, tz, timeWithCurrentYear, timeWithCurrentYear + HOUR,
                         FORMAT_NO_MONTH_DAY));
-        assertEquals("3:30 AM",
+        assertEquals("3:30\u202fAM",
                 formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_12HOUR | FORMAT_SHOW_TIME));
         assertEquals("03:30",
                 formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_24HOUR | FORMAT_SHOW_TIME));
-        assertEquals("3:30 AM", formatDateRange(en_US, tz, fixedTime, fixedTime,
+        assertEquals("3:30\u202fAM", formatDateRange(en_US, tz, fixedTime, fixedTime,
                 FORMAT_12HOUR /*| FORMAT_CAP_AMPM*/ | FORMAT_SHOW_TIME));
-        assertEquals("12:00 PM",
+        assertEquals("12:00\u202fPM",
                 formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
                         FORMAT_12HOUR | FORMAT_SHOW_TIME));
-        assertEquals("12:00 PM",
+        assertEquals("12:00\u202fPM",
                 formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
                         FORMAT_12HOUR | FORMAT_SHOW_TIME /*| FORMAT_CAP_NOON*/));
-        assertEquals("12:00 PM",
+        assertEquals("12:00\u202fPM",
                 formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
                         FORMAT_12HOUR /*| FORMAT_NO_NOON*/ | FORMAT_SHOW_TIME));
-        assertEquals("12:00 AM", formatDateRange(en_US, tz, fixedTime - midnightDuration,
+        assertEquals("12:00\u202fAM", formatDateRange(en_US, tz, fixedTime - midnightDuration,
                 fixedTime - midnightDuration,
                 FORMAT_12HOUR | FORMAT_SHOW_TIME /*| FORMAT_NO_MIDNIGHT*/));
-        assertEquals("3:30 AM",
+        assertEquals("3:30\u202fAM",
                 formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_SHOW_TIME | FORMAT_UTC));
-        assertEquals("3 AM", formatDateRange(en_US, tz, onTheHour, onTheHour,
+        assertEquals("3\u202fAM", formatDateRange(en_US, tz, onTheHour, onTheHour,
                 FORMAT_SHOW_TIME | FORMAT_ABBREV_TIME));
         assertEquals("Mon", formatDateRange(en_US, tz, fixedTime, fixedTime + HOUR,
                 FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_WEEKDAY));
@@ -134,13 +135,13 @@
 
         assertEquals("1/19/2009", formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * HOUR,
                 FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("1/19/2009 – 1/22/2009",
+        assertEquals("1/19/2009\u2009\u2013\u20091/22/2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("1/19/2009 – 4/22/2009",
+        assertEquals("1/19/2009\u2009\u2013\u20094/22/2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("1/19/2009 – 2/9/2012",
+        assertEquals("1/19/2009\u2009\u2013\u20092/9/2012",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
 
@@ -151,7 +152,7 @@
         assertEquals("19.01. – 22.04.2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("19.01.2009 – 09.02.2012",
+        assertEquals("19.01.2009\u2009\u2013\u200909.02.2012",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
 
@@ -169,48 +170,48 @@
 
         assertEquals("19/1/2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + HOUR,
                 FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("19/1/2009 – 22/1/2009",
+        assertEquals("19/1/2009\u2009\u2013\u200922/1/2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("19/1/2009 – 22/4/2009",
+        assertEquals("19/1/2009\u2009\u2013\u200922/4/2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("19/1/2009 – 9/2/2012",
+        assertEquals("19/1/2009\u2009\u2013\u20099/2/2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
 
         // These are some random other test cases I came up with.
 
-        assertEquals("January 19 – 22, 2009",
+        assertEquals("January 19\u2009\u2013\u200922, 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY, 0));
-        assertEquals("Jan 19 – 22, 2009", formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
-                FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("Mon, Jan 19 – Thu, Jan 22, 2009",
+        assertEquals("Jan 19\u2009\u2013\u200922, 2009", formatDateRange(en_US, tz, fixedTime,
+                fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
+        assertEquals("Mon, Jan 19\u2009\u2013\u2009Thu, Jan 22, 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
-        assertEquals("Monday, January 19 – Thursday, January 22, 2009",
+        assertEquals("Monday, January 19\u2009\u2013\u2009Thursday, January 22, 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
 
-        assertEquals("January 19 – April 22, 2009",
+        assertEquals("January 19\u2009\u2013\u2009April 22, 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH, 0));
-        assertEquals("Jan 19 – Apr 22, 2009",
+        assertEquals("Jan 19\u2009\u2013\u2009Apr 22, 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("Mon, Jan 19 – Wed, Apr 22, 2009",
+        assertEquals("Mon, Jan 19\u2009\u2013\u2009Wed, Apr 22, 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
-        assertEquals("January – April 2009",
+        assertEquals("January\u2009\u2013\u2009April 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
 
-        assertEquals("Jan 19, 2009 – Feb 9, 2012",
+        assertEquals("Jan 19, 2009\u2009\u2013\u2009Feb 9, 2012",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("Jan 2009 – Feb 2012",
+        assertEquals("Jan 2009\u2009\u2013\u2009Feb 2012",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
-        assertEquals("January 19, 2009 – February 9, 2012",
+        assertEquals("January 19, 2009\u2009\u2013\u2009February 9, 2012",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR, 0));
-        assertEquals("Monday, January 19, 2009 – Thursday, February 9, 2012",
+        assertEquals("Monday, January 19, 2009\u2009\u2013\u2009Thursday, February 9, 2012",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
 
         // The same tests but for de_DE.
@@ -225,26 +226,26 @@
         assertEquals("Montag, 19. – Donnerstag, 22. Januar 2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
 
-        assertEquals("19. Januar – 22. April 2009",
+        assertEquals("19. Januar\u2009\u2013\u200922. April 2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, 0));
-        assertEquals("19. Jan. – 22. Apr. 2009",
+        assertEquals("19. Jan.\u2009\u2013\u200922. Apr. 2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("Mo., 19. Jan. – Mi., 22. Apr. 2009",
+        assertEquals("Mo., 19. Jan.\u2009\u2013\u2009Mi., 22. Apr. 2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
         assertEquals("Januar–April 2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
 
-        assertEquals("19. Jan. 2009 – 9. Feb. 2012",
+        assertEquals("19. Jan. 2009\u2009\u2013\u20099. Feb. 2012",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("Jan. 2009 – Feb. 2012",
+        assertEquals("Jan. 2009\u2009\u2013\u2009Feb. 2012",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
-        assertEquals("19. Januar 2009 – 9. Februar 2012",
+        assertEquals("19. Januar 2009\u2009\u2013\u20099. Februar 2012",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, 0));
-        assertEquals("Montag, 19. Januar 2009 – Donnerstag, 9. Februar 2012",
+        assertEquals("Montag, 19. Januar 2009\u2009\u2013\u2009Donnerstag, 9. Februar 2012",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
 
         // The same tests but for es_US.
@@ -254,32 +255,32 @@
         assertEquals("19–22 de ene de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("lun, 19 de ene – jue, 22 de ene de 2009",
+        assertEquals("lun, 19 de ene\u2009\u2013\u2009jue, 22 de ene de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
-        assertEquals("lunes, 19 de enero – jueves, 22 de enero de 2009",
+        assertEquals("lunes, 19 de enero\u2009\u2013\u2009jueves, 22 de enero de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
 
-        assertEquals("19 de enero – 22 de abril de 2009",
+        assertEquals("19 de enero\u2009\u2013\u200922 de abril de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, 0));
-        assertEquals("19 de ene – 22 de abr 2009",
+        assertEquals("19 de ene\u2009\u2013\u200922 de abr 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("lun, 19 de ene – mié, 22 de abr de 2009",
+        assertEquals("lun, 19 de ene\u2009\u2013\u2009mié, 22 de abr de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
         assertEquals("enero–abril de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
 
-        assertEquals("19 de ene de 2009 – 9 de feb de 2012",
+        assertEquals("19 de ene de 2009\u2009\u2013\u20099 de feb de 2012",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("ene de 2009 – feb de 2012",
+        assertEquals("ene de 2009\u2009\u2013\u2009feb de 2012",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
-        assertEquals("19 de enero de 2009 – 9 de febrero de 2012",
+        assertEquals("19 de enero de 2009\u2009\u2013\u20099 de febrero de 2012",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, 0));
-        assertEquals("lunes, 19 de enero de 2009 – jueves, 9 de febrero de 2012",
+        assertEquals("lunes, 19 de enero de 2009\u2009\u2013\u2009jueves, 9 de febrero de 2012",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
 
         // The same tests but for es_ES.
@@ -288,32 +289,32 @@
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, 0));
         assertEquals("19–22 ene 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
                 FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("lun, 19 ene – jue, 22 ene 2009",
+        assertEquals("lun, 19 ene\u2009\u2013\u2009jue, 22 ene 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
-        assertEquals("lunes, 19 de enero – jueves, 22 de enero de 2009",
+        assertEquals("lunes, 19 de enero\u2009\u2013\u2009jueves, 22 de enero de 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
 
-        assertEquals("19 de enero – 22 de abril de 2009",
+        assertEquals("19 de enero\u2009\u2013\u200922 de abril de 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, 0));
-        assertEquals("19 ene – 22 abr 2009",
+        assertEquals("19 ene\u2009\u2013\u200922 abr 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("lun, 19 ene – mié, 22 abr 2009",
+        assertEquals("lun, 19 ene\u2009\u2013\u2009mié, 22 abr 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
         assertEquals("enero–abril de 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
 
-        assertEquals("19 ene 2009 – 9 feb 2012",
+        assertEquals("19 ene 2009\u2009\u2013\u20099 feb 2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("ene 2009 – feb 2012",
+        assertEquals("ene 2009\u2009\u2013\u2009feb 2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
-        assertEquals("19 de enero de 2009 – 9 de febrero de 2012",
+        assertEquals("19 de enero de 2009\u2009\u2013\u20099 de febrero de 2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, 0));
-        assertEquals("lunes, 19 de enero de 2009 – jueves, 9 de febrero de 2012",
+        assertEquals("lunes, 19 de enero de 2009\u2009\u2013\u2009jueves, 9 de febrero de 2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
     }
 
@@ -330,7 +331,7 @@
         c.set(2046, Calendar.OCTOBER, 4, 3, 30);
         long oct_4_2046 = c.getTimeInMillis();
         int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL;
-        assertEquals("Jan 19, 2042 – Oct 4, 2046",
+        assertEquals("Jan 19, 2042\u2009\u2013\u2009Oct 4, 2046",
                 formatDateRange(l, tz, jan_19_2042, oct_4_2046, flags));
     }
 
@@ -343,15 +344,15 @@
         int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL | FORMAT_SHOW_TIME | FORMAT_24HOUR;
 
         // The Unix epoch is UTC, so 0 is 1970-01-01T00:00Z...
-        assertEquals("Jan 1, 1970, 00:00 – Jan 2, 1970, 00:00",
+        assertEquals("Jan 1, 1970, 00:00\u2009\u2013\u2009Jan 2, 1970, 00:00",
                 formatDateRange(l, utc, 0, DAY + 1, flags));
         // But MTV is hours behind, so 0 was still the afternoon of the previous day...
-        assertEquals("Dec 31, 1969, 16:00 – Jan 1, 1970, 16:00",
+        assertEquals("Dec 31, 1969, 16:00\u2009\u2013\u2009Jan 1, 1970, 16:00",
                 formatDateRange(l, pacific, 0, DAY, flags));
     }
 
     // http://b/10318326 - we can drop the minutes in a 12-hour time if they're zero,
-    // but not if we're using the 24-hour clock. That is: "4 PM" is reasonable, "16" is not.
+    // but not if we're using the 24-hour clock. That is: "4\u202fPM" is reasonable, "16" is not.
     @Test
     public void test10318326() throws Exception {
         long midnight = 0;
@@ -367,23 +368,26 @@
 
         // Full length on-the-hour times.
         assertEquals("00:00", formatDateRange(l, utc, midnight, midnight, time24));
-        assertEquals("12:00 AM", formatDateRange(l, utc, midnight, midnight, time12));
+        assertEquals("12:00\u202fAM", formatDateRange(l, utc, midnight, midnight, time12));
         assertEquals("16:00", formatDateRange(l, utc, teaTime, teaTime, time24));
-        assertEquals("4:00 PM", formatDateRange(l, utc, teaTime, teaTime, time12));
+        assertEquals("4:00\u202fPM", formatDateRange(l, utc, teaTime, teaTime, time12));
 
         // Abbreviated on-the-hour times.
         assertEquals("00:00", formatDateRange(l, utc, midnight, midnight, abbr24));
-        assertEquals("12 AM", formatDateRange(l, utc, midnight, midnight, abbr12));
+        assertEquals("12\u202fAM", formatDateRange(l, utc, midnight, midnight, abbr12));
         assertEquals("16:00", formatDateRange(l, utc, teaTime, teaTime, abbr24));
-        assertEquals("4 PM", formatDateRange(l, utc, teaTime, teaTime, abbr12));
+        assertEquals("4\u202fPM", formatDateRange(l, utc, teaTime, teaTime, abbr12));
 
         // Abbreviated on-the-hour ranges.
-        assertEquals("00:00 – 16:00", formatDateRange(l, utc, midnight, teaTime, abbr24));
-        assertEquals("12 AM – 4 PM", formatDateRange(l, utc, midnight, teaTime, abbr12));
+        assertEquals("00:00\u2009\u2013\u200916:00", formatDateRange(l, utc, midnight, teaTime,
+                abbr24));
+        assertEquals("12\u202fAM\u2009\u2013\u20094\u202fPM", formatDateRange(l, utc, midnight,
+                teaTime, abbr12));
 
         // Abbreviated mixed ranges.
-        assertEquals("00:00 – 16:01", formatDateRange(l, utc, midnight, teaTime + MINUTE, abbr24));
-        assertEquals("12:00 AM – 4:01 PM",
+        assertEquals("00:00\u2009\u2013\u200916:01", formatDateRange(l, utc, midnight,
+                teaTime + MINUTE, abbr24));
+        assertEquals("12:00\u202fAM\u2009\u2013\u20094:01\u202fPM",
                 formatDateRange(l, utc, midnight, teaTime + MINUTE, abbr12));
     }
 
@@ -406,12 +410,12 @@
 
         // Run one millisecond over, though, and you're into the next day.
         long nextMorning = 1 * DAY + 1;
-        assertEquals("Thursday, January 1 – Friday, January 2, 1970",
+        assertEquals("Thursday, January 1\u2009\u2013\u2009Friday, January 2, 1970",
                 formatDateRange(l, utc, midnight, nextMorning, flags));
 
         // But the same reasoning applies for that day.
         long nextMidnight = 2 * DAY;
-        assertEquals("Thursday, January 1 – Friday, January 2, 1970",
+        assertEquals("Thursday, January 1\u2009\u2013\u2009Friday, January 2, 1970",
                 formatDateRange(l, utc, midnight, nextMidnight, flags));
     }
 
@@ -424,9 +428,9 @@
 
         int flags = FORMAT_SHOW_TIME | FORMAT_24HOUR | FORMAT_SHOW_DATE;
 
-        assertEquals("January 1, 1970, 22:00 – 00:00",
+        assertEquals("January 1, 1970, 22:00\u2009\u2013\u200900:00",
                 formatDateRange(l, utc, 22 * HOUR, 24 * HOUR, flags));
-        assertEquals("January 1, 1970 at 22:00 – January 2, 1970 at 00:30",
+        assertEquals("January 1, 1970 at 22:00\u2009\u2013\u2009January 2, 1970 at 00:30",
                 formatDateRange(l, utc, 22 * HOUR, 24 * HOUR + 30 * MINUTE, flags));
     }
 
@@ -443,9 +447,9 @@
         c.clear();
         c.set(1980, Calendar.JANUARY, 1, 0, 0);
         long jan_1_1980 = c.getTimeInMillis();
-        assertEquals("January 1, 1980, 22:00 – 00:00",
+        assertEquals("January 1, 1980, 22:00\u2009\u2013\u200900:00",
                 formatDateRange(l, utc, jan_1_1980 + 22 * HOUR, jan_1_1980 + 24 * HOUR, flags));
-        assertEquals("January 1, 1980 at 22:00 – January 2, 1980 at 00:30",
+        assertEquals("January 1, 1980 at 22:00\u2009\u2013\u2009January 2, 1980 at 00:30",
                 formatDateRange(l, utc, jan_1_1980 + 22 * HOUR,
                         jan_1_1980 + 24 * HOUR + 30 * MINUTE, flags));
     }
@@ -463,12 +467,12 @@
         c.clear();
         c.set(1980, Calendar.JANUARY, 1, 0, 0);
         long jan_1_1980 = c.getTimeInMillis();
-        assertEquals("January 1, 1980, 22:00 – 00:00",
+        assertEquals("January 1, 1980, 22:00\u2009\u2013\u200900:00",
                 formatDateRange(l, pacific, jan_1_1980 + 22 * HOUR, jan_1_1980 + 24 * HOUR, flags));
 
         c.set(1980, Calendar.JULY, 1, 0, 0);
         long jul_1_1980 = c.getTimeInMillis();
-        assertEquals("July 1, 1980, 22:00 – 00:00",
+        assertEquals("July 1, 1980, 22:00\u2009\u2013\u200900:00",
                 formatDateRange(l, pacific, jul_1_1980 + 22 * HOUR, jul_1_1980 + 24 * HOUR, flags));
     }
 
@@ -531,11 +535,13 @@
                 formatDateRange(l, utc, oldYear, oldYear, FORMAT_SHOW_DATE | FORMAT_NO_YEAR));
 
         // ...or the start and end years aren't the same...
-        assertEquals(String.format("February 10, 1980 – February 10, %d", c.get(Calendar.YEAR)),
+        assertEquals(String.format("February 10, 1980\u2009\u2013\u2009February 10, %d",
+                        c.get(Calendar.YEAR)),
                 formatDateRange(l, utc, oldYear, thisYear, FORMAT_SHOW_DATE));
 
         // (And you can't avoid that --- icu4c steps in and overrides you.)
-        assertEquals(String.format("February 10, 1980 – February 10, %d", c.get(Calendar.YEAR)),
+        assertEquals(String.format("February 10, 1980\u2009\u2013\u2009February 10, %d",
+                        c.get(Calendar.YEAR)),
                 formatDateRange(l, utc, oldYear, thisYear, FORMAT_SHOW_DATE | FORMAT_NO_YEAR));
     }
 
@@ -595,7 +601,7 @@
                 formatDateRange(new ULocale("fa"), utc, thisYear, thisYear, flags));
         assertEquals("يونۍ د ۱۹۸۰ د فبروري ۱۰",
                 formatDateRange(new ULocale("ps"), utc, thisYear, thisYear, flags));
-        assertEquals("วันอาทิตย์ที่ 10 กุมภาพันธ์ ค.ศ. 1980",
+        assertEquals("วันอาทิตย์ที่ 10 กุมภาพันธ์ 1980",
                 formatDateRange(new ULocale("th"), utc, thisYear, thisYear, flags));
     }
 
@@ -607,9 +613,12 @@
 
         int flags = FORMAT_SHOW_TIME | FORMAT_ABBREV_ALL | FORMAT_12HOUR;
 
-        assertEquals("10 – 11 AM", formatDateRange(l, utc, 10 * HOUR, 11 * HOUR, flags));
-        assertEquals("11 AM – 1 PM", formatDateRange(l, utc, 11 * HOUR, 13 * HOUR, flags));
-        assertEquals("2 – 3 PM", formatDateRange(l, utc, 14 * HOUR, 15 * HOUR, flags));
+        assertEquals("10\u2009\u2013\u200911\u202fAM", formatDateRange(l, utc,
+                10 * HOUR, 11 * HOUR, flags));
+        assertEquals("11\u202fAM\u2009\u2013\u20091\u202fPM", formatDateRange(l, utc,
+                11 * HOUR, 13 * HOUR, flags));
+        assertEquals("2\u2009\u2013\u20093\u202fPM", formatDateRange(l, utc,
+                14 * HOUR, 15 * HOUR, flags));
     }
 
     // http://b/20708022
@@ -618,8 +627,8 @@
         final ULocale locale = new ULocale("en");
         final TimeZone timeZone = TimeZone.getTimeZone("UTC");
 
-        assertEquals("11:00 PM – 12:00 AM", formatDateRange(locale, timeZone,
-                1430434800000L, 1430438400000L, FORMAT_SHOW_TIME));
+        assertEquals("11:00\u202fPM\u2009\u2013\u200912:00\u202fAM", formatDateRange(locale,
+                timeZone, 1430434800000L, 1430438400000L, FORMAT_SHOW_TIME));
     }
 
     // http://b/68847519
@@ -629,23 +638,25 @@
                 ENGLISH, GMT_ZONE, from, to, FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_24HOUR);
         // If we're showing times and the end-point is midnight the following day, we want the
         // behaviour of suppressing the date for the end...
-        assertEquals("February 27, 2007, 04:00 – 00:00", fmt.apply(1172548800000L, 1172620800000L));
+        assertEquals("February 27, 2007, 04:00\u2009\u2013\u200900:00", fmt.apply(1172548800000L,
+                1172620800000L));
         // ...unless the start-point is also midnight, in which case we need dates to disambiguate.
-        assertEquals("February 27, 2007 at 00:00 – February 28, 2007 at 00:00",
+        assertEquals("February 27, 2007 at 00:00\u2009\u2013\u2009February 28, 2007 at 00:00",
                 fmt.apply(1172534400000L, 1172620800000L));
         // We want to show the date if the end-point is a millisecond after midnight the following
         // day, or if it is exactly midnight the day after that.
-        assertEquals("February 27, 2007 at 04:00 – February 28, 2007 at 00:00",
+        assertEquals("February 27, 2007 at 04:00\u2009\u2013\u2009February 28, 2007 at 00:00",
                 fmt.apply(1172548800000L, 1172620800001L));
-        assertEquals("February 27, 2007 at 04:00 – March 1, 2007 at 00:00",
+        assertEquals("February 27, 2007 at 04:00\u2009\u2013\u2009March 1, 2007 at 00:00",
                 fmt.apply(1172548800000L, 1172707200000L));
         // We want to show the date if the start-point is anything less than a minute after
       // midnight,
         // since that gets displayed as midnight...
-        assertEquals("February 27, 2007 at 00:00 – February 28, 2007 at 00:00",
+        assertEquals("February 27, 2007 at 00:00\u2009\u2013\u2009February 28, 2007 at 00:00",
                 fmt.apply(1172534459999L, 1172620800000L));
         // ...but not if it is exactly one minute after midnight.
-        assertEquals("February 27, 2007, 00:01 – 00:00", fmt.apply(1172534460000L, 1172620800000L));
+        assertEquals("February 27, 2007, 00:01\u2009\u2013\u200900:00", fmt.apply(1172534460000L,
+                1172620800000L));
     }
 
     // http://b/68847519
@@ -656,16 +667,20 @@
         // If we're only showing dates and the end-point is midnight of any day, we want the
         // behaviour of showing an end date one earlier. So if the end-point is March 2, 2007 00:00,
         // show March 1, 2007 instead (whether the start-point is midnight or not).
-        assertEquals("February 27 – March 1, 2007", fmt.apply(1172534400000L, 1172793600000L));
-        assertEquals("February 27 – March 1, 2007", fmt.apply(1172548800000L, 1172793600000L));
+        assertEquals("February 27\u2009\u2013\u2009March 1, 2007",
+                fmt.apply(1172534400000L, 1172793600000L));
+        assertEquals("February 27\u2009\u2013\u2009March 1, 2007",
+                fmt.apply(1172548800000L, 1172793600000L));
         // We want to show the true date if the end-point is a millisecond after midnight.
-        assertEquals("February 27 – March 2, 2007", fmt.apply(1172534400000L, 1172793600001L));
+        assertEquals("February 27\u2009\u2013\u2009March 2, 2007",
+                fmt.apply(1172534400000L, 1172793600001L));
 
         // 2006-02-27 00:00:00.000 GMT - 2007-03-02 00:00:00.000 GMT
-        assertEquals("February 27, 2006 – March 1, 2007",
+        assertEquals("February 27, 2006\u2009\u2013\u2009March 1, 2007",
                 fmt.apply(1140998400000L, 1172793600000L));
 
         // Spans a leap year's Feb 29th.
-        assertEquals("February 27 – March 1, 2004", fmt.apply(1077840000000L, 1078185600000L));
+        assertEquals("February 27\u2009\u2013\u2009March 1, 2004",
+                fmt.apply(1077840000000L, 1078185600000L));
     }
 }
diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
index 381c051..39ed82ef 100644
--- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
@@ -139,16 +139,16 @@
                 fixedTime, java.text.DateFormat.SHORT, java.text.DateFormat.FULL));
 
         final long hourDuration = 2 * 60 * 60 * 1000;
-        assertEquals("5:30:15 AM Greenwich Mean Time", DateUtils.formatSameDayTime(
+        assertEquals("5:30:15\u202fAM Greenwich Mean Time", DateUtils.formatSameDayTime(
                 fixedTime + hourDuration, fixedTime, java.text.DateFormat.FULL,
                 java.text.DateFormat.FULL));
-        assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+        assertEquals("5:30:15\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
                 fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.DEFAULT));
-        assertEquals("5:30:15 AM GMT", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+        assertEquals("5:30:15\u202fAM GMT", DateUtils.formatSameDayTime(fixedTime + hourDuration,
                 fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.LONG));
-        assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+        assertEquals("5:30:15\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
                 fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.MEDIUM));
-        assertEquals("5:30 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+        assertEquals("5:30\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
                 fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.SHORT));
     }
 
diff --git a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
index b342516..2337802 100644
--- a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
@@ -468,37 +468,37 @@
         cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0);
         final long base = cal.getTimeInMillis();
 
-        assertEquals("5 seconds ago, 10:49 AM",
+        assertEquals("5 seconds ago, 10:49\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 5 * SECOND_IN_MILLIS, base, 0,
                         MINUTE_IN_MILLIS, 0));
-        assertEquals("5 min. ago, 10:45 AM",
+        assertEquals("5 min. ago, 10:45\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base, 0,
                         HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
-        assertEquals("0 hr. ago, 10:45 AM",
+        assertEquals("0 hr. ago, 10:45\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base,
                         HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
-        assertEquals("5 hours ago, 5:50 AM",
+        assertEquals("5 hours ago, 5:50\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base,
                         HOUR_IN_MILLIS, DAY_IN_MILLIS, 0));
-        assertEquals("Yesterday, 7:50 PM",
+        assertEquals("Yesterday, 7:50\u202fPM",
                 getRelativeDateTimeString(en_US, tz, base - 15 * HOUR_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
-        assertEquals("5 days ago, 10:50 AM",
+        assertEquals("5 days ago, 10:50\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 5 * DAY_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, 0));
-        assertEquals("Jan 29, 10:50 AM",
+        assertEquals("Jan 29, 10:50\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 7 * DAY_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, 0));
-        assertEquals("11/27/2014, 10:50 AM",
+        assertEquals("11/27/2014, 10:50\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, 0));
-        assertEquals("11/27/2014, 10:50 AM",
+        assertEquals("11/27/2014, 10:50\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
                         YEAR_IN_MILLIS, 0));
 
         // User-supplied flags should be ignored when formatting the date clause.
         final int FORMAT_SHOW_WEEKDAY = 0x00002;
-        assertEquals("11/27/2014, 10:50 AM",
+        assertEquals("11/27/2014, 10:50\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS,
                         FORMAT_ABBREV_ALL | FORMAT_SHOW_WEEKDAY));
@@ -514,14 +514,14 @@
         // So 5 hours before 3:15 AM should be formatted as 'Yesterday, 9:15 PM'.
         cal.set(2014, Calendar.MARCH, 9, 3, 15, 0);
         long base = cal.getTimeInMillis();
-        assertEquals("Yesterday, 9:15 PM",
+        assertEquals("Yesterday, 9:15\u202fPM",
                 getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, 0));
 
         // 1 hour after 2:00 AM should be formatted as 'In 1 hour, 4:00 AM'.
         cal.set(2014, Calendar.MARCH, 9, 2, 0, 0);
         base = cal.getTimeInMillis();
-        assertEquals("In 1 hour, 4:00 AM",
+        assertEquals("In 1 hour, 4:00\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base + 1 * HOUR_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, 0));
 
@@ -529,22 +529,22 @@
         // 1:00 AM. 8 hours before 5:20 AM should be 'Yesterday, 10:20 PM'.
         cal.set(2014, Calendar.NOVEMBER, 2, 5, 20, 0);
         base = cal.getTimeInMillis();
-        assertEquals("Yesterday, 10:20 PM",
+        assertEquals("Yesterday, 10:20\u202fPM",
                 getRelativeDateTimeString(en_US, tz, base - 8 * HOUR_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, 0));
 
         cal.set(2014, Calendar.NOVEMBER, 2, 0, 45, 0);
         base = cal.getTimeInMillis();
         // 45 minutes after 0:45 AM should be 'In 45 minutes, 1:30 AM'.
-        assertEquals("In 45 minutes, 1:30 AM",
+        assertEquals("In 45 minutes, 1:30\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base + 45 * MINUTE_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, 0));
         // 45 minutes later, it should be 'In 45 minutes, 1:15 AM'.
-        assertEquals("In 45 minutes, 1:15 AM",
+        assertEquals("In 45 minutes, 1:15\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base + 90 * MINUTE_IN_MILLIS,
                         base + 45 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0));
         // Another 45 minutes later, it should be 'In 45 minutes, 2:00 AM'.
-        assertEquals("In 45 minutes, 2:00 AM",
+        assertEquals("In 45 minutes, 2:00\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base + 135 * MINUTE_IN_MILLIS,
                         base + 90 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0));
     }
@@ -593,7 +593,7 @@
         Calendar yesterdayCalendar1 = Calendar.getInstance(tz, en_US);
         yesterdayCalendar1.set(2011, Calendar.SEPTEMBER, 1, 10, 24, 0);
         long yesterday1 = yesterdayCalendar1.getTimeInMillis();
-        assertEquals("Yesterday, 10:24 AM",
+        assertEquals("Yesterday, 10:24\u202fAM",
                 getRelativeDateTimeString(en_US, tz, yesterday1, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
 
@@ -601,7 +601,7 @@
         Calendar yesterdayCalendar2 = Calendar.getInstance(tz, en_US);
         yesterdayCalendar2.set(2011, Calendar.SEPTEMBER, 1, 10, 22, 0);
         long yesterday2 = yesterdayCalendar2.getTimeInMillis();
-        assertEquals("Yesterday, 10:22 AM",
+        assertEquals("Yesterday, 10:22\u202fAM",
                 getRelativeDateTimeString(en_US, tz, yesterday2, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
 
@@ -609,7 +609,7 @@
         Calendar twoDaysAgoCalendar1 = Calendar.getInstance(tz, en_US);
         twoDaysAgoCalendar1.set(2011, Calendar.AUGUST, 31, 10, 24, 0);
         long twoDaysAgo1 = twoDaysAgoCalendar1.getTimeInMillis();
-        assertEquals("2 days ago, 10:24 AM",
+        assertEquals("2 days ago, 10:24\u202fAM",
                 getRelativeDateTimeString(en_US, tz, twoDaysAgo1, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
 
@@ -617,7 +617,7 @@
         Calendar twoDaysAgoCalendar2 = Calendar.getInstance(tz, en_US);
         twoDaysAgoCalendar2.set(2011, Calendar.AUGUST, 31, 10, 22, 0);
         long twoDaysAgo2 = twoDaysAgoCalendar2.getTimeInMillis();
-        assertEquals("2 days ago, 10:22 AM",
+        assertEquals("2 days ago, 10:22\u202fAM",
                 getRelativeDateTimeString(en_US, tz, twoDaysAgo2, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
 
@@ -625,7 +625,7 @@
         Calendar tomorrowCalendar1 = Calendar.getInstance(tz, en_US);
         tomorrowCalendar1.set(2011, Calendar.SEPTEMBER, 3, 10, 22, 0);
         long tomorrow1 = tomorrowCalendar1.getTimeInMillis();
-        assertEquals("Tomorrow, 10:22 AM",
+        assertEquals("Tomorrow, 10:22\u202fAM",
                 getRelativeDateTimeString(en_US, tz, tomorrow1, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
 
@@ -633,7 +633,7 @@
         Calendar tomorrowCalendar2 = Calendar.getInstance(tz, en_US);
         tomorrowCalendar2.set(2011, Calendar.SEPTEMBER, 3, 10, 24, 0);
         long tomorrow2 = tomorrowCalendar2.getTimeInMillis();
-        assertEquals("Tomorrow, 10:24 AM",
+        assertEquals("Tomorrow, 10:24\u202fAM",
                 getRelativeDateTimeString(en_US, tz, tomorrow2, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
 
@@ -641,7 +641,7 @@
         Calendar twoDaysLaterCalendar1 = Calendar.getInstance(tz, en_US);
         twoDaysLaterCalendar1.set(2011, Calendar.SEPTEMBER, 4, 10, 22, 0);
         long twoDaysLater1 = twoDaysLaterCalendar1.getTimeInMillis();
-        assertEquals("In 2 days, 10:22 AM",
+        assertEquals("In 2 days, 10:22\u202fAM",
                 getRelativeDateTimeString(en_US, tz, twoDaysLater1, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
 
@@ -649,7 +649,7 @@
         Calendar twoDaysLaterCalendar2 = Calendar.getInstance(tz, en_US);
         twoDaysLaterCalendar2.set(2011, Calendar.SEPTEMBER, 4, 10, 24, 0);
         long twoDaysLater2 = twoDaysLaterCalendar2.getTimeInMillis();
-        assertEquals("In 2 days, 10:24 AM",
+        assertEquals("In 2 days, 10:24\u202fAM",
                 getRelativeDateTimeString(en_US, tz, twoDaysLater2, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
     }
@@ -664,11 +664,11 @@
         cal.set(2012, Calendar.FEBRUARY, 5, 10, 50, 0);
         long base = cal.getTimeInMillis();
 
-        assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz,
+        assertEquals("Feb 5, 5:50\u202fAM", getRelativeDateTimeString(en_US, tz,
                 base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0));
-        assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+        assertEquals("Jan 29, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
                 base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
-        assertEquals("11/27/2011, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+        assertEquals("11/27/2011, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
                 base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
 
         assertEquals("January 6", getRelativeTimeSpanString(en_US, tz,
@@ -687,11 +687,11 @@
         // Feb 5, 2018 at 10:50 PST
         cal.set(2018, Calendar.FEBRUARY, 5, 10, 50, 0);
         base = cal.getTimeInMillis();
-        assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz,
+        assertEquals("Feb 5, 5:50\u202fAM", getRelativeDateTimeString(en_US, tz,
                 base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0));
-        assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+        assertEquals("Jan 29, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
                 base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
-        assertEquals("11/27/2017, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+        assertEquals("11/27/2017, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
                 base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
 
         assertEquals("January 6", getRelativeTimeSpanString(en_US, tz,
diff --git a/core/tests/coretests/src/android/util/RotationUtilsTest.java b/core/tests/coretests/src/android/util/RotationUtilsTest.java
index 826eb30..1b1ee4f 100644
--- a/core/tests/coretests/src/android/util/RotationUtilsTest.java
+++ b/core/tests/coretests/src/android/util/RotationUtilsTest.java
@@ -18,6 +18,7 @@
 
 import static android.util.RotationUtils.rotateBounds;
 import static android.util.RotationUtils.rotatePoint;
+import static android.util.RotationUtils.rotatePointF;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
@@ -25,6 +26,7 @@
 import static org.junit.Assert.assertEquals;
 
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -79,4 +81,26 @@
         rotatePoint(testResult, ROTATION_270, parentW, parentH);
         assertEquals(new Point(560, 60), testResult);
     }
+
+    @Test
+    public void testRotatePointF() {
+        float parentW = 1000f;
+        float parentH = 600f;
+        PointF testPt = new PointF(60f, 40f);
+
+        PointF testResult = new PointF(testPt);
+        rotatePointF(testResult, ROTATION_90, parentW, parentH);
+        assertEquals(40f, testResult.x, .1f);
+        assertEquals(940f, testResult.y, .1f);
+
+        testResult.set(testPt.x, testPt.y);
+        rotatePointF(testResult, ROTATION_180, parentW, parentH);
+        assertEquals(940f, testResult.x, .1f);
+        assertEquals(560f, testResult.y, .1f);
+
+        testResult.set(testPt.x, testPt.y);
+        rotatePointF(testResult, ROTATION_270, parentW, parentH);
+        assertEquals(560f, testResult.x, .1f);
+        assertEquals(60f, testResult.y, .1f);
+    }
 }
diff --git a/core/tests/coretests/src/android/util/TypedValueTest.kt b/core/tests/coretests/src/android/util/TypedValueTest.kt
index 7a05d97..7d98a7d 100644
--- a/core/tests/coretests/src/android/util/TypedValueTest.kt
+++ b/core/tests/coretests/src/android/util/TypedValueTest.kt
@@ -16,16 +16,17 @@
 
 package android.util
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlin.math.abs
+import kotlin.math.min
+import kotlin.math.roundToInt
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
-import kotlin.math.abs
-import kotlin.math.min
-import kotlin.math.roundToInt
 
 @RunWith(AndroidJUnit4::class)
 class TypedValueTest {
@@ -152,4 +153,19 @@
         val widthPx = TypedValue.complexToDimensionPixelSize(widthDimen, metrics)
         assertEquals(widthFloat.roundToInt(), widthPx)
     }
-}
\ No newline at end of file
+
+    @SmallTest
+    @Test
+    fun testNonLinearFontScaling_nullLookupFallsBackToScaledDensity() {
+        val metrics: DisplayMetrics = mock(DisplayMetrics::class.java)
+        val fontScale = 2f
+        metrics.density = 1f
+        metrics.scaledDensity = fontScale * metrics.density
+        metrics.fontScaleConverter = null
+
+        assertThat(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10f, metrics))
+                .isEqualTo(20f)
+        assertThat(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 50f, metrics))
+                .isEqualTo(100f)
+    }
+}
diff --git a/core/tests/coretests/src/android/view/DisplayShapeTest.java b/core/tests/coretests/src/android/view/DisplayShapeTest.java
new file mode 100644
index 0000000..77dd8bd
--- /dev/null
+++ b/core/tests/coretests/src/android/view/DisplayShapeTest.java
@@ -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 android.view;
+
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link DisplayShape}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class DisplayShapeTest {
+    // Rectangle with w=100, height=200
+    private static final String SPEC_RECTANGULAR_SHAPE = "M0,0 L100,0 L100,200 L0,200 Z";
+
+    @Test
+    public void testGetPath() {
+        final DisplayShape displayShape = DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+        final Path path = displayShape.getPath();
+        final RectF actualRect = new RectF();
+        path.computeBounds(actualRect, false);
+
+        final RectF expectRect = new RectF(0f, 0f, 100f, 200f);
+        assertEquals(actualRect, expectRect);
+    }
+
+    @Test
+    public void testDefaultShape_screenIsRound() {
+        final DisplayShape displayShape = DisplayShape.createDefaultDisplayShape(100, 100, true);
+
+        // A circle with radius = 50.
+        final String expect = "M0,50.0 A50.0,50.0 0 1,1 100,50.0 A50.0,50.0 0 1,1 0,50.0 Z";
+        assertEquals(displayShape.mDisplayShapeSpec, expect);
+    }
+
+    @Test
+    public void testDefaultShape_screenIsNotRound() {
+        final DisplayShape displayShape = DisplayShape.createDefaultDisplayShape(100, 200, false);
+
+        // A rectangle with width/height = 100/200.
+        final String expect = "M0,0 L100,0 L100,200 L0,200 Z";
+        assertEquals(displayShape.mDisplayShapeSpec, expect);
+    }
+
+    @Test
+    public void testFromSpecString_cache() {
+        final DisplayShape cached = DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+        assertThat(DisplayShape.fromSpecString(SPEC_RECTANGULAR_SHAPE, 1f, 100, 200),
+                sameInstance(cached));
+    }
+
+    @Test
+    public void testGetPath_cache() {
+        final Path cached = DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 1f, 100, 200).getPath();
+        assertThat(DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 1f, 100, 200).getPath(),
+                sameInstance(cached));
+    }
+
+    @Test
+    public void testRotate_90() {
+        DisplayShape displayShape = DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+        displayShape = displayShape.setRotation(ROTATION_90);
+        final Path path = displayShape.getPath();
+        final RectF actualRect = new RectF();
+        path.computeBounds(actualRect, false);
+
+        final RectF expectRect = new RectF(0f, 0f, 200f, 100f);
+        assertEquals(actualRect, expectRect);
+    }
+
+    @Test
+    public void testRotate_270() {
+        DisplayShape displayShape = DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+        displayShape = displayShape.setRotation(ROTATION_270);
+        final Path path = displayShape.getPath();
+        final RectF actualRect = new RectF();
+        path.computeBounds(actualRect, false);
+
+        final RectF expectRect = new RectF(0f, 0f, 200f, 100f);
+        assertEquals(actualRect, expectRect);
+    }
+
+    @Test
+    public void testOffset() {
+        DisplayShape displayShape = DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+        displayShape = displayShape.setOffset(-10, -20);
+        final Path path = displayShape.getPath();
+        final RectF actualRect = new RectF();
+        path.computeBounds(actualRect, false);
+
+        final RectF expectRect = new RectF(-10f, -20f, 90f, 180f);
+        assertEquals(actualRect, expectRect);
+    }
+
+    @Test
+    public void testPhysicalPixelDisplaySizeRatio() {
+        final DisplayShape displayShape = DisplayShape.fromSpecString(
+                SPEC_RECTANGULAR_SHAPE, 0.5f, 100, 200);
+        final Path path = displayShape.getPath();
+        final RectF actualRect = new RectF();
+        path.computeBounds(actualRect, false);
+
+        final RectF expectRect = new RectF(0f, 0f, 50f, 100f);
+        assertEquals(actualRect, expectRect);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 44bb062..0bf133f 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -24,6 +24,7 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -98,14 +99,14 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // test if setVisibility can show IME
             mImeConsumer.onWindowFocusGained(true);
-            mController.show(WindowInsets.Type.ime(), true /* fromIme */);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertTrue((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
 
             // test if setVisibility can hide IME
-            mController.hide(WindowInsets.Type.ime(), true /* fromIme */);
+            mController.hide(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertFalse((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
         });
     }
 
@@ -117,7 +118,7 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // Request IME visible before control is available.
             mImeConsumer.onWindowFocusGained(true);
-            mController.show(WindowInsets.Type.ime(), true /* fromIme */);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
 
             // set control and verify visibility is applied.
             InsetsSourceControl control =
@@ -125,9 +126,11 @@
             mController.onControlsChanged(new InsetsSourceControl[] { control });
             // IME show animation should be triggered when control becomes available.
             verify(mController).applyAnimation(
-                    eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */);
+                    eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */,
+                    any() /* statsToken */);
             verify(mController, never()).applyAnimation(
-                    eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */);
+                    eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */,
+                    any() /* statsToken */);
         });
     }
 
@@ -153,7 +156,8 @@
             mImeConsumer.onWindowFocusGained(hasWindowFocus);
             final boolean imeVisible = hasWindowFocus && hasViewFocus;
             if (imeVisible) {
-                mController.show(WindowInsets.Type.ime(), true /* fromIme */);
+                mController.show(WindowInsets.Type.ime(), true /* fromIme */,
+                        null /* statsToken */);
             }
 
             // set control and verify visibility is applied.
@@ -169,20 +173,21 @@
                 verify(control).getAndClearSkipAnimationOnce();
                 verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
                         eq(true) /* show */, eq(false) /* fromIme */,
-                        eq(expectSkipAnim) /* skipAnim */);
+                        eq(expectSkipAnim) /* skipAnim */, null /* statsToken */);
             }
 
             // If previously hasViewFocus is false, verify when requesting the IME visible next
             // time will not skip animation.
             if (!hasViewFocus) {
-                mController.show(WindowInsets.Type.ime(), true);
+                mController.show(WindowInsets.Type.ime(), true /* fromIme */,
+                        null /* statsToken */);
                 mController.onControlsChanged(new InsetsSourceControl[]{ control });
                 // Verify IME show animation should be triggered when control becomes available and
                 // the animation will be skipped by getAndClearSkipAnimationOnce invoked.
                 verify(control).getAndClearSkipAnimationOnce();
                 verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
                         eq(true) /* show */, eq(true) /* fromIme */,
-                        eq(false) /* skipAnim */);
+                        eq(false) /* skipAnim */, null /* statsToken */);
             }
         });
     }
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index d0f7fe04..c88255e 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -27,7 +27,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -90,7 +89,6 @@
         mInsetsState = new InsetsState();
         mInsetsState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 500, 100));
         mInsetsState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(400, 0, 500, 500));
-        doNothing().when(mMockController).onRequestedVisibilityChanged(any());
         InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mInsetsState,
                 () -> mMockTransaction, mMockController);
         topConsumer.setControl(
@@ -111,7 +109,8 @@
         mController = new InsetsAnimationControlImpl(controls,
                 new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
                 mMockController, 10 /* durationMs */, new LinearInterpolator(),
-                0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */);
+                0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */,
+                null /* statsToken */);
         mController.setReadyDispatched(true);
     }
 
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 5e12313..c6fa778 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -248,35 +248,29 @@
             mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
-            mController.show(ime(), true /* fromIme */);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             verify(loggingListener).onReady(notNull(), anyInt());
         });
     }
 
     @Test
     public void testAnimationEndState() {
-        InsetsSourceControl[] controls = prepareControls();
-        InsetsSourceControl navBar = controls[0];
-        InsetsSourceControl statusBar = controls[1];
-        InsetsSourceControl ime = controls[2];
+        prepareControls();
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
-            mController.show(ime(), true /* fromIme */);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             mController.show(all());
             // quickly jump to final state by cancelling it.
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            final @InsetsType int types = navigationBars() | statusBars() | ime();
+            assertEquals(types, mController.getRequestedVisibleTypes() & types);
 
-            mController.hide(ime(), true /* fromIme */);
+            mController.hide(ime(), true /* fromIme */, null /* statsToken */);
             mController.hide(all());
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(0, mController.getRequestedVisibleTypes() & types);
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -288,12 +282,12 @@
         mController.onControlsChanged(new InsetsSourceControl[] { ime });
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
-            mController.show(ime(), true /* fromIme */);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
-            mController.hide(ime(), true /* fromIme */);
+            assertTrue(isRequestedVisible(mController, ime()));
+            mController.hide(ime(), true /* fromIme */, null /* statsToken */);
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertFalse(isRequestedVisible(mController, ime()));
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -307,26 +301,22 @@
         InsetsSourceControl ime = controls[2];
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            int types = navigationBars() | systemBars();
+            int types = navigationBars() | statusBars();
             // test hide select types.
             mController.hide(types);
-            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
-            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(navigationBars()));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
-            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
-            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(navigationBars()));
+            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(statusBars()));
+            assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
 
-            // test hide all
+            // test show all
             mController.show(types);
-            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
-            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(navigationBars()));
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(types, mController.getRequestedVisibleTypes() & (types | ime()));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -339,33 +329,27 @@
         InsetsSourceControl ime = controls[2];
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            int types = navigationBars() | systemBars();
+            int types = navigationBars() | statusBars();
             // test show select types.
             mController.show(types);
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(types, mController.getRequestedVisibleTypes() & types);
+            assertEquals(0, mController.getRequestedVisibleTypes() & ime());
 
             // test hide all
             mController.hide(all());
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
 
             // test single show
             mController.show(navigationBars());
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(navigationBars(),
+                    mController.getRequestedVisibleTypes() & (types | ime()));
 
             // test single hide
             mController.hide(navigationBars());
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
 
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -373,49 +357,38 @@
 
     @Test
     public void testShowHideMultiple() {
-        InsetsSourceControl[] controls = prepareControls();
-        InsetsSourceControl navBar = controls[0];
-        InsetsSourceControl statusBar = controls[1];
-        InsetsSourceControl ime = controls[2];
+        prepareControls();
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // start two animations and see if previous is cancelled and final state is reached.
             mController.hide(navigationBars());
             mController.hide(systemBars());
-            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
-            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
+            int types = navigationBars() | statusBars();
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(navigationBars()));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
 
             mController.show(navigationBars());
             mController.show(systemBars());
-            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
-            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(navigationBars()));
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(types, mController.getRequestedVisibleTypes() & (types | ime()));
 
-            int types = navigationBars() | systemBars();
             // show two at a time and hide one by one.
             mController.show(types);
             mController.hide(navigationBars());
-            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
-            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(navigationBars()));
+            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(statusBars(), mController.getRequestedVisibleTypes() & (types | ime()));
 
             mController.hide(systemBars());
-            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
-            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
+            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(navigationBars()));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -428,20 +401,16 @@
         InsetsSourceControl ime = controls[2];
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            int types = navigationBars() | systemBars();
+            int types = navigationBars() | statusBars();
             // show two at a time and hide one by one.
             mController.show(types);
             mController.hide(navigationBars());
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(statusBars(), mController.getRequestedVisibleTypes() & (types | ime()));
 
             mController.hide(systemBars());
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -453,7 +422,7 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.hide(statusBars());
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+            assertFalse(isRequestedVisible(mController, statusBars()));
             assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
 
             // Loosing control
@@ -461,14 +430,14 @@
             state.setSourceVisible(ITYPE_STATUS_BAR, true);
             mController.onStateChanged(state);
             mController.onControlsChanged(new InsetsSourceControl[0]);
-            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+            assertFalse(isRequestedVisible(mController, statusBars()));
             assertTrue(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
 
             // Gaining control
             mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
-            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+            assertFalse(isRequestedVisible(mController, statusBars()));
             assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -483,14 +452,14 @@
             assertFalse(mController.getState().getSource(ITYPE_IME).isVisible());
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */);
+            mController.show(ime(), true /* fromIme */, null /* statsToken */);
 
             // Gaining control shortly after
             mController.onControlsChanged(createSingletonControl(ITYPE_IME));
 
-            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_IME));
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(ITYPE_IME).isRequestedVisible());
+            assertTrue(isRequestedVisible(mController, ime()));
             assertTrue(mController.getState().getSource(ITYPE_IME).isVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -507,11 +476,11 @@
             mController.onControlsChanged(createSingletonControl(ITYPE_IME));
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */);
+            mController.show(ime(), true /* fromIme */, null /* statsToken */);
 
-            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_IME));
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(ITYPE_IME).isRequestedVisible());
+            assertTrue(isRequestedVisible(mController, ime()));
             assertTrue(mController.getState().getSource(ITYPE_IME).isVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -538,7 +507,7 @@
         });
         waitUntilNextFrame();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+            assertFalse(isRequestedVisible(mController, statusBars()));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -565,7 +534,7 @@
         });
         waitUntilNextFrame();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+            assertFalse(isRequestedVisible(mController, statusBars()));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -585,7 +554,7 @@
             verify(listener, never()).onReady(any(), anyInt());
 
             // Pretend that IME is calling.
-            mController.show(ime(), true);
+            mController.show(ime(), true /* fromIme */, null /* statsToken */);
 
             // Ready gets deferred until next predraw
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -669,7 +638,7 @@
             mController.onControlsChanged(createSingletonControl(ITYPE_IME));
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */);
+            mController.show(ime(), true /* fromIme */, null /* statsToken */);
 
             InsetsState copy = new InsetsState(mController.getState(), true /* copySources */);
             copy.getSource(ITYPE_IME).setFrame(0, 1, 2, 3);
@@ -701,6 +670,7 @@
 
     private void doTestResizeAnimation_insetsTypes(@InternalInsetsType int type,
             @AnimationType int expectedAnimationType) {
+        final @InsetsType int publicType = InsetsState.toPublicType(type);
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             final InsetsState state1 = new InsetsState();
             state1.getSource(type).setVisible(true);
@@ -711,15 +681,15 @@
 
             // New insets source won't cause the resize animation.
             mController.onStateChanged(state1);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
 
             // Changing frame might cause the resize animation. This depends on the insets type.
             mController.onStateChanged(state2);
-            assertEquals(message, expectedAnimationType, mController.getAnimationType(type));
+            assertEquals(message, expectedAnimationType, mController.getAnimationType(publicType));
 
             // Cancel the existing animations for the next iteration.
             mController.cancelExistingAnimations();
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -728,6 +698,7 @@
     public void testResizeAnimation_displayFrame() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             final @InternalInsetsType int type = ITYPE_STATUS_BAR;
+            final @InsetsType int publicType = statusBars();
             final InsetsState state1 = new InsetsState();
             state1.setDisplayFrame(new Rect(0, 0, 500, 1000));
             state1.getSource(type).setFrame(0, 0, 500, 50);
@@ -738,11 +709,11 @@
 
             // New insets source won't cause the resize animation.
             mController.onStateChanged(state1);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
 
             // Changing frame won't cause the resize animation if the display frame is also changed.
             mController.onStateChanged(state2);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -751,6 +722,7 @@
     public void testResizeAnimation_visibility() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             final @InternalInsetsType int type = ITYPE_STATUS_BAR;
+            final @InsetsType int publicType = statusBars();
             final InsetsState state1 = new InsetsState();
             state1.getSource(type).setVisible(true);
             state1.getSource(type).setFrame(0, 0, 500, 50);
@@ -764,17 +736,17 @@
 
             // New insets source won't cause the resize animation.
             mController.onStateChanged(state1);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
 
             // Changing source visibility (visible --> invisible) won't cause the resize animation.
             // The previous source and the current one must be both visible.
             mController.onStateChanged(state2);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
 
             // Changing source visibility (invisible --> visible) won't cause the resize animation.
             // The previous source and the current one must be both visible.
             mController.onStateChanged(state3);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -873,7 +845,7 @@
 
             // Showing invisible ime should only causes insets change once.
             clearInvocations(mTestHost);
-            mController.show(ime(), true /* fromIme */);
+            mController.show(ime(), true /* fromIme */, null /* statsToken */);
             verify(mTestHost, times(1)).notifyInsetsChanged();
 
             // Sending the same insets state should not cause insets change.
@@ -940,10 +912,10 @@
 
             // Verify IME requested visibility should be updated to IME consumer from controller.
             mController.show(ime());
-            assertTrue(imeInsetsConsumer.isRequestedVisible());
+            assertTrue(isRequestedVisible(mController, ime()));
 
             mController.hide(ime());
-            assertFalse(imeInsetsConsumer.isRequestedVisible());
+            assertFalse(isRequestedVisible(mController, ime()));
         });
     }
 
@@ -980,6 +952,10 @@
         return controls;
     }
 
+    private static boolean isRequestedVisible(InsetsController controller, @InsetsType int type) {
+        return (controller.getRequestedVisibleTypes() & type) != 0;
+    }
+
     public static class TestHost extends ViewRootInsetsControllerHost {
 
         private @InsetsType int mRequestedVisibleTypes = defaultVisible();
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 8cf118c..1253278 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -122,7 +122,6 @@
     public void testHide() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mConsumer.hide();
-            assertFalse("Consumer should not be visible", mConsumer.isRequestedVisible());
             verify(mSpyInsetsSource).setVisible(eq(false));
         });
 
@@ -134,7 +133,6 @@
             // Insets source starts out visible
             mConsumer.hide();
             mConsumer.show(false /* fromIme */);
-            assertTrue("Consumer should be visible", mConsumer.isRequestedVisible());
             verify(mSpyInsetsSource).setVisible(eq(false));
             verify(mSpyInsetsSource).setVisible(eq(true));
         });
@@ -240,7 +238,7 @@
             // visibility won't be updated when the consumer received the same leash in setControl.
             insetsController.controlWindowInsetsAnimation(ime(), 0L,
                     null /* interpolator */, null /* cancellationSignal */, null /* listener */);
-            assertTrue(insetsController.getAnimationType(ITYPE_IME) == ANIMATION_TYPE_USER);
+            assertEquals(ANIMATION_TYPE_USER, insetsController.getAnimationType(ime()));
             imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
                     true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
             verify(mMockTransaction, never()).show(mLeash);
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index be9da11..6a96f28 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -517,6 +517,19 @@
                 windowInsets.getRoundedCorner(POSITION_BOTTOM_LEFT));
     }
 
+    @Test
+    public void testCalculateRelativeDisplayShape() {
+        mState.setDisplayFrame(new Rect(0, 0, 200, 400));
+        mState.setDisplayShape(DisplayShape.createDefaultDisplayShape(200, 400, false));
+        WindowInsets windowInsets = mState.calculateInsets(new Rect(10, 20, 200, 400), null, false,
+                false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
+                WINDOWING_MODE_UNDEFINED, new SparseIntArray());
+
+        final DisplayShape expect =
+                DisplayShape.createDefaultDisplayShape(200, 400, false).setOffset(-10, -20);
+        assertEquals(expect, windowInsets.getDisplayShape());
+    }
+
     private void assertEqualsAndHashCode() {
         assertEquals(mState, mState2);
         assertEquals(mState.hashCode(), mState2.hashCode());
diff --git a/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java b/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java
deleted file mode 100644
index 5664e0b..0000000
--- a/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java
+++ /dev/null
@@ -1,121 +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.view;
-
-import static android.view.InsetsState.FIRST_TYPE;
-import static android.view.InsetsState.LAST_TYPE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.view.InsetsState.InternalInsetsType;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for {@link InsetsVisibilities}.
- *
- * <p>Build/Install/Run:
- *  atest FrameworksCoreTests:InsetsVisibilities
- *
- * <p>This test class is a part of Window Manager Service tests and specified in
- * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class InsetsVisibilitiesTest {
-
-    @Test
-    public void testEquals() {
-        final InsetsVisibilities v1 = new InsetsVisibilities();
-        final InsetsVisibilities v2 = new InsetsVisibilities();
-        final InsetsVisibilities v3 = new InsetsVisibilities();
-        assertEquals(v1, v2);
-        assertEquals(v1, v3);
-
-        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            v1.setVisibility(type, false);
-            v2.setVisibility(type, false);
-        }
-        assertEquals(v1, v2);
-        assertNotEquals(v1, v3);
-
-        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            v1.setVisibility(type, true);
-            v2.setVisibility(type, true);
-        }
-        assertEquals(v1, v2);
-        assertNotEquals(v1, v3);
-    }
-
-    @Test
-    public void testSet() {
-        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            final InsetsVisibilities v1 = new InsetsVisibilities();
-            final InsetsVisibilities v2 = new InsetsVisibilities();
-
-            v1.setVisibility(type, true);
-            assertNotEquals(v1, v2);
-
-            v2.set(v1);
-            assertEquals(v1, v2);
-
-            v2.setVisibility(type, false);
-            assertNotEquals(v1, v2);
-
-            v1.set(v2);
-            assertEquals(v1, v2);
-        }
-    }
-
-    @Test
-    public void testCopyConstructor() {
-        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            final InsetsVisibilities v1 = new InsetsVisibilities();
-            v1.setVisibility(type, true);
-            final InsetsVisibilities v2 = new InsetsVisibilities(v1);
-            assertEquals(v1, v2);
-
-            v2.setVisibility(type, false);
-            assertNotEquals(v1, v2);
-        }
-    }
-
-    @Test
-    public void testGetterAndSetter() {
-        final InsetsVisibilities v1 = new InsetsVisibilities();
-        final InsetsVisibilities v2 = new InsetsVisibilities();
-
-        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            assertEquals(InsetsState.getDefaultVisibility(type), v1.getVisibility(type));
-        }
-
-        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            v1.setVisibility(type, true);
-            assertTrue(v1.getVisibility(type));
-
-            v2.setVisibility(type, false);
-            assertFalse(v2.getVisibility(type));
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index dd9634b..4fed396 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -39,13 +39,16 @@
 
     @Test
     public void systemWindowInsets_afterConsuming_isConsumed() {
-        assertTrue(new WindowInsets(new Rect(1, 2, 3, 4), null, false, false, null)
+        assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null,
+                null, false, false, null, null, null, null,
+                WindowInsets.Type.systemBars(), false)
                 .consumeSystemWindowInsets().isConsumed());
     }
 
     @Test
     public void multiNullConstructor_isConsumed() {
-        assertTrue(new WindowInsets((Rect) null, null, false, false, null).isConsumed());
+        assertTrue(new WindowInsets(null, null, null, false, false, null, null, null, null,
+                WindowInsets.Type.systemBars(), false).isConsumed());
     }
 
     @Test
@@ -61,7 +64,8 @@
         WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0));
         WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
         WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false, null,
-                null, null, systemBars(), true /* compatIgnoreVisibility */);
+                null, null, DisplayShape.NONE, systemBars(),
+                true /* compatIgnoreVisibility */);
         assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
     }
 }
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index bb1a3b18..7cbf3ffa 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,59 @@
         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 {
+        MyAccessibilityProxy(int displayId,
+                @NonNull List<AccessibilityServiceInfo> serviceInfos) {
+            super(displayId, Executors.newSingleThreadExecutor(), serviceInfos);
+        }
+
+        @Override
+        public void onAccessibilityEvent(@NonNull AccessibilityEvent event) {
+
+        }
+
+        @Override
+        public void onProxyConnected() {
+
+        }
+
+        @Override
+        public void interrupt() {
+
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 99670d9..248420f 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
     // The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
     // See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
     // and assertAccessibilityNodeInfoCleared in that class.
-    private static final int NUM_MARSHALLED_PROPERTIES = 40;
+    private static final int NUM_MARSHALLED_PROPERTIES = 42;
 
     /**
      * The number of properties that are purposely not marshalled
@@ -58,7 +58,7 @@
 
     // The number of flags held in boolean properties. Their values should also be double-checked
     // in the methods above.
-    private static final int NUM_BOOLEAN_PROPERTIES = 24;
+    private static final int NUM_BOOLEAN_PROPERTIES = 25;
 
     @Test
     public void testStandardActions_serializationFlagIsValid() {
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 35d5948..6443ac18 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -27,6 +27,8 @@
 import android.os.IBinder;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.view.SurfaceControl;
+import android.window.ScreenCapture;
 
 import java.util.Collections;
 import java.util.List;
@@ -180,6 +182,10 @@
 
     public void takeScreenshot(int displayId, RemoteCallback callback) {}
 
+    public void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+            ScreenCapture.ScreenCaptureListener listener,
+            IAccessibilityInteractionConnectionCallback callback) {}
+
     public void setFocusAppearance(int strokeWidth, int color) {}
 
     public void setCacheEnabled(boolean enabled) {}
@@ -217,4 +223,7 @@
     public List<AccessibilityServiceInfo> getInstalledAndEnabledServices() throws RemoteException {
         return null;
     }
+
+    @Override
+    public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {}
 }
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
index 599cf97..6e73b9f 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
@@ -52,6 +52,8 @@
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Supplemental tests that cannot be covered by CTS (e.g. due to hidden API dependencies).
@@ -500,6 +502,7 @@
                 + "prefix: extras=null\n"
                 + "prefix: hintLocales=null\n"
                 + "prefix: supportedHandwritingGestureTypes=(none)\n"
+                + "prefix: supportedHandwritingGesturePreviewTypes=(none)\n"
                 + "prefix: contentMimeTypes=null\n");
     }
 
@@ -516,6 +519,8 @@
         info.label = "testLabel";
         info.setInitialToolType(MotionEvent.TOOL_TYPE_STYLUS);
         info.setSupportedHandwritingGestures(Arrays.asList(SelectGesture.class));
+        info.setSupportedHandwritingGesturePreviews(
+                Stream.of(SelectGesture.class).collect(Collectors.toSet()));
         info.packageName = "android.view.inputmethod";
         info.autofillId = new AutofillId(123);
         info.fieldId = 456;
@@ -538,6 +543,7 @@
                         + "prefix2: extras=Bundle[{testKey=testValue}]\n"
                         + "prefix2: hintLocales=[en,es,zh]\n"
                         + "prefix2: supportedHandwritingGestureTypes=SELECT\n"
+                        + "prefix2: supportedHandwritingGesturePreviewTypes=SELECT\n"
                         + "prefix2: contentMimeTypes=[image/png]\n"
                         + "prefix2: targetInputMethodUserId=10\n");
     }
@@ -558,6 +564,7 @@
                         + "prefix: packageName=null autofillId=null fieldId=0 fieldName=null\n"
                         + "prefix: hintLocales=null\n"
                         + "prefix: supportedHandwritingGestureTypes=(none)\n"
+                        + "prefix: supportedHandwritingGesturePreviewTypes=(none)\n"
                         + "prefix: contentMimeTypes=null\n");
     }
 
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/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 00b3693..bbf9f3c 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -128,6 +128,7 @@
         RemoteViews clone = child.clone();
     }
 
+    @SuppressWarnings("ReturnValueIgnored")
     @Test
     public void clone_repeatedly() {
         RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
@@ -485,6 +486,7 @@
         }
     }
 
+    @SuppressWarnings("ReturnValueIgnored")
     @Test
     public void nestedAddViews() {
         RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
@@ -509,6 +511,7 @@
         parcelAndRecreate(views);
     }
 
+    @SuppressWarnings("ReturnValueIgnored")
     @Test
     public void nestedLandscapeViews() {
         RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
diff --git a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
new file mode 100644
index 0000000..281d677
--- /dev/null
+++ b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
@@ -0,0 +1,289 @@
+/*
+ * 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.window;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager.TaskDescription;
+import android.content.ComponentName;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorSpace;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.WindowInsets;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link SnapshotDrawerUtils}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SnapshotDrawerUtilsTest {
+
+    private SnapshotDrawerUtils.SnapshotSurface mSnapshotSurface;
+
+    private void setupSurface(int width, int height) {
+        setupSurface(width, height, new Rect(), FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                new Rect(0, 0, width, height));
+    }
+
+    private void setupSurface(int width, int height, Rect contentInsets,
+            int windowFlags, Rect taskBounds) {
+        // Previously when constructing TaskSnapshots for this test, scale was 1.0f, so to mimic
+        // this behavior set the taskSize to be the same as the taskBounds width and height. The
+        // taskBounds passed here are assumed to be the same task bounds as when the snapshot was
+        // taken. We assume there is no aspect ratio mismatch between the screenshot and the
+        // taskBounds
+        assertEquals(width, taskBounds.width());
+        assertEquals(height, taskBounds.height());
+        Point taskSize = new Point(taskBounds.width(), taskBounds.height());
+
+        final TaskSnapshot snapshot = createTaskSnapshot(width, height, taskSize, contentInsets);
+        TaskDescription taskDescription = createTaskDescription(Color.WHITE,
+                Color.RED, Color.BLUE);
+
+        mSnapshotSurface = new SnapshotDrawerUtils.SnapshotSurface(
+                new SurfaceControl(), snapshot, "Test", taskBounds);
+        mSnapshotSurface.initiateSystemBarPainter(windowFlags, 0, 0,
+                taskDescription, WindowInsets.Type.defaultVisible());
+    }
+
+    private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize,
+            Rect contentInsets) {
+        final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888,
+                1, HardwareBuffer.USAGE_CPU_READ_RARELY);
+        return new TaskSnapshot(
+                System.currentTimeMillis(),
+                new ComponentName("", ""), buffer,
+                ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
+                Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets */,
+                false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
+                0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */);
+    }
+
+    private static TaskDescription createTaskDescription(int background,
+            int statusBar, int navigationBar) {
+        final TaskDescription td = new TaskDescription();
+        td.setBackgroundColor(background);
+        td.setStatusBarColor(statusBar);
+        td.setNavigationBarColor(navigationBar);
+        return td;
+    }
+
+    @Test
+    public void fillEmptyBackground_fillHorizontally() {
+        setupSurface(200, 100);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(200);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200));
+        verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
+    }
+
+    @Test
+    public void fillEmptyBackground_fillVertically() {
+        setupSurface(100, 200);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(200);
+        mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100));
+        verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any());
+    }
+
+    @Test
+    public void fillEmptyBackground_fillBoth() {
+        setupSurface(200, 200);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(200);
+        when(mockCanvas.getHeight()).thenReturn(200);
+        mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
+        verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
+        verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any());
+    }
+
+    @Test
+    public void fillEmptyBackground_dontFill_sameSize() {
+        setupSurface(100, 100);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
+        verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
+    }
+
+    @Test
+    public void fillEmptyBackground_dontFill_bitmapLarger() {
+        setupSurface(100, 100);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200));
+        verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
+    }
+
+    @Test
+    public void testCalculateSnapshotCrop() {
+        setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, new Rect(0, 0, 100, 100));
+        assertEquals(new Rect(0, 0, 100, 90), mSnapshotSurface.calculateSnapshotCrop());
+    }
+
+    @Test
+    public void testCalculateSnapshotCrop_taskNotOnTop() {
+        setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, new Rect(0, 50, 100, 150));
+        assertEquals(new Rect(0, 10, 100, 90), mSnapshotSurface.calculateSnapshotCrop());
+    }
+
+    @Test
+    public void testCalculateSnapshotCrop_navBarLeft() {
+        setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, new Rect(0, 0, 100, 100));
+        assertEquals(new Rect(10, 0, 100, 100), mSnapshotSurface.calculateSnapshotCrop());
+    }
+
+    @Test
+    public void testCalculateSnapshotCrop_navBarRight() {
+        setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, new Rect(0, 0, 100, 100));
+        assertEquals(new Rect(0, 0, 90, 100), mSnapshotSurface.calculateSnapshotCrop());
+    }
+
+    @Test
+    public void testCalculateSnapshotCrop_waterfall() {
+        setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, new Rect(0, 0, 100, 100));
+        assertEquals(new Rect(5, 0, 95, 90), mSnapshotSurface.calculateSnapshotCrop());
+    }
+
+    @Test
+    public void testCalculateSnapshotFrame() {
+        setupSurface(100, 100);
+        final Rect insets = new Rect(0, 10, 0, 10);
+        mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
+        assertEquals(new Rect(0, 0, 100, 80),
+                mSnapshotSurface.calculateSnapshotFrame(new Rect(0, 10, 100, 90)));
+    }
+
+    @Test
+    public void testCalculateSnapshotFrame_navBarLeft() {
+        setupSurface(100, 100);
+        final Rect insets = new Rect(10, 10, 0, 0);
+        mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
+        assertEquals(new Rect(10, 0, 100, 90),
+                mSnapshotSurface.calculateSnapshotFrame(new Rect(10, 10, 100, 100)));
+    }
+
+    @Test
+    public void testCalculateSnapshotFrame_waterfall() {
+        setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, new Rect(0, 0, 100, 100));
+        final Rect insets = new Rect(0, 10, 0, 10);
+        mSnapshotSurface.setFrames(new Rect(5, 0, 95, 100), insets);
+        assertEquals(new Rect(0, 0, 90, 90),
+                mSnapshotSurface.calculateSnapshotFrame(new Rect(5, 0, 95, 90)));
+    }
+
+    @Test
+    public void testDrawStatusBarBackground() {
+        setupSurface(100, 100);
+        final Rect insets = new Rect(0, 10, 10, 0);
+        mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSnapshotSurface.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 50, 100));
+        verify(mockCanvas).drawRect(eq(50.0f), eq(0.0f), eq(90.0f), eq(10.0f), any());
+    }
+
+    @Test
+    public void testDrawStatusBarBackground_nullFrame() {
+        setupSurface(100, 100);
+        final Rect insets = new Rect(0, 10, 10, 0);
+        mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSnapshotSurface.drawStatusBarBackground(mockCanvas, null);
+        verify(mockCanvas).drawRect(eq(0.0f), eq(0.0f), eq(90.0f), eq(10.0f), any());
+    }
+
+    @Test
+    public void testDrawStatusBarBackground_nope() {
+        setupSurface(100, 100);
+        final Rect insets = new Rect(0, 10, 10, 0);
+        mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSnapshotSurface.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 100, 100));
+        verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
+    }
+
+    @Test
+    public void testDrawNavigationBarBackground() {
+        final Rect insets = new Rect(0, 10, 0, 10);
+        setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                new Rect(0, 0, 100, 100));
+        mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSnapshotSurface.drawNavigationBarBackground(mockCanvas);
+        verify(mockCanvas).drawRect(eq(new Rect(0, 90, 100, 100)), any());
+    }
+
+    @Test
+    public void testDrawNavigationBarBackground_left() {
+        final Rect insets = new Rect(10, 10, 0, 0);
+        setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                new Rect(0, 0, 100, 100));
+        mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSnapshotSurface.drawNavigationBarBackground(mockCanvas);
+        verify(mockCanvas).drawRect(eq(new Rect(0, 0, 10, 100)), any());
+    }
+
+    @Test
+    public void testDrawNavigationBarBackground_right() {
+        final Rect insets = new Rect(0, 10, 10, 0);
+        setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                new Rect(0, 0, 100, 100));
+        mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        mSnapshotSurface.drawNavigationBarBackground(mockCanvas);
+        verify(mockCanvas).drawRect(eq(new Rect(90, 0, 100, 100)), any());
+    }
+}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index f370ebd..9d6b29e 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -17,6 +17,7 @@
 package android.window;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
@@ -60,8 +61,8 @@
     private OnBackAnimationCallback mCallback1;
     @Mock
     private OnBackAnimationCallback mCallback2;
-    @Mock
-    private BackEvent mBackEvent;
+    private final BackMotionEvent mBackEvent = new BackMotionEvent(
+            0, 0, 0, BackEvent.EDGE_LEFT, null);
 
     @Before
     public void setUp() throws Exception {
@@ -89,12 +90,12 @@
                 captor.capture());
         captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent);
         waitForIdle();
-        verify(mCallback1).onBackStarted(mBackEvent);
+        verify(mCallback1).onBackStarted(any(BackEvent.class));
         verifyZeroInteractions(mCallback2);
 
         captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent);
         waitForIdle();
-        verify(mCallback2).onBackStarted(mBackEvent);
+        verify(mCallback2).onBackStarted(any(BackEvent.class));
         verifyNoMoreInteractions(mCallback1);
     }
 
@@ -114,7 +115,7 @@
         assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY);
         captor.getValue().getCallback().onBackStarted(mBackEvent);
         waitForIdle();
-        verify(mCallback1).onBackStarted(mBackEvent);
+        verify(mCallback1).onBackStarted(any(BackEvent.class));
     }
 
     @Test
@@ -152,6 +153,6 @@
         verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
         captor.getValue().getCallback().onBackStarted(mBackEvent);
         waitForIdle();
-        verify(mCallback2).onBackStarted(mBackEvent);
+        verify(mCallback2).onBackStarted(any(BackEvent.class));
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
index 499f7a5..eead4ed 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
@@ -16,19 +16,25 @@
 
 package com.android.internal.app;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.os.UserHandle;
+import android.util.Pair;
 
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
 import com.android.internal.app.chooser.TargetInfo;
 import com.android.internal.logging.MetricsLogger;
 
-import java.util.List;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 
 /**
@@ -50,6 +56,9 @@
     public Function<PackageManager, PackageManager> createPackageManager;
     public Function<TargetInfo, Boolean> onSafelyStartCallback;
     public Function<ChooserListAdapter, Void> onQueryDirectShareTargets;
+    public BiFunction<
+            IChooserWrapper, ChooserListAdapter, Pair<Integer, ChooserActivity.ServiceResultInfo[]>>
+            directShareTargets;
     public ResolverListController resolverListController;
     public ResolverListController workResolverListController;
     public Boolean isVoiceInteraction;
@@ -66,12 +75,16 @@
     public boolean isQuietModeEnabled;
     public boolean isWorkProfileUserRunning;
     public boolean isWorkProfileUserUnlocked;
-    public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
+    public Integer myUserId;
+    public QuietModeManager mQuietModeManager;
+    public MyUserIdProvider mMyUserIdProvider;
+    public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
     public PackageManager packageManager;
 
     public void reset() {
         onSafelyStartCallback = null;
         onQueryDirectShareTargets = null;
+        directShareTargets = null;
         isVoiceInteraction = null;
         createPackageManager = null;
         previewThumbnail = null;
@@ -89,14 +102,9 @@
         isQuietModeEnabled = false;
         isWorkProfileUserRunning = true;
         isWorkProfileUserUnlocked = true;
+        myUserId = null;
         packageManager = null;
-        multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
-            @Override
-            public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
-                    int targetUserId) {
-                return hasCrossProfileIntents;
-            }
-
+        mQuietModeManager = new QuietModeManager() {
             @Override
             public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
                 return isQuietModeEnabled;
@@ -107,9 +115,28 @@
                     UserHandle workProfileUserHandle) {
                 isQuietModeEnabled = enabled;
             }
+
+            @Override
+            public void markWorkProfileEnabledBroadcastReceived() {
+            }
+
+            @Override
+            public boolean isWaitingToEnableWorkProfile() {
+                return false;
+            }
         };
+
+        mMyUserIdProvider = new MyUserIdProvider() {
+            @Override
+            public int getMyUserId() {
+                return myUserId != null ? myUserId : UserHandle.myUserId();
+            }
+        };
+
+        mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
+        when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
+                .thenAnswer(invocation -> hasCrossProfileIntents);
     }
 
     private ChooserActivityOverrideData() {}
 }
-
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index e8c7ce0..d656678 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -81,6 +81,7 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.service.chooser.ChooserTarget;
+import android.util.Pair;
 import android.view.View;
 
 import androidx.annotation.CallSuper;
@@ -89,6 +90,7 @@
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.internal.R;
+import com.android.internal.app.ChooserActivity.ServiceResultInfo;
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
 import com.android.internal.app.chooser.DisplayResolveInfo;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -2166,8 +2168,8 @@
         assertThat(logger.numCalls(), is(6));
     }
 
-    @Test @Ignore
-    public void testDirectTargetLogging() throws InterruptedException {
+    @Test
+    public void testDirectTargetLogging() {
         Intent sendIntent = createSendTextIntent();
         // We need app targets for direct targets to get displayed
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -2187,30 +2189,35 @@
                 resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
         ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
 
+        ChooserActivityOverrideData
+                .getInstance()
+                .directShareTargets = (activity, adapter) -> {
+                    DisplayResolveInfo displayInfo = activity.createTestDisplayResolveInfo(
+                            sendIntent,
+                            ri,
+                             "testLabel",
+                             "testInfo",
+                            sendIntent,
+                            /* resolveInfoPresentationGetter */ null);
+                    ServiceResultInfo[] results = {
+                            new ServiceResultInfo(
+                                    displayInfo,
+                                    serviceTargets,
+                                    adapter.getUserHandle())};
+                    // TODO: consider covering the other type.
+                    //  Only 2 types are expected out of the shortcut loading logic:
+                    //  - TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER, if shortcuts were loaded from
+                    //    the ShortcutManager, and;
+                    //  - TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE, if shortcuts were loaded
+                    //    from AppPredictor.
+                    //  Ideally, our tests should cover all of them.
+                    return new Pair<>(TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER, results);
+                };
+
         // Start activity
         final IChooserWrapper activity = (IChooserWrapper)
                 mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
 
-        // Insert the direct share target
-        Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
-        directShareToShortcutInfos.put(serviceTargets.get(0), null);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                () -> activity.getAdapter().addServiceResults(
-                        activity.createTestDisplayResolveInfo(sendIntent,
-                                ri,
-                                "testLabel",
-                                "testInfo",
-                                sendIntent,
-                                /* resolveInfoPresentationGetter */ null),
-                        serviceTargets,
-                        TARGET_TYPE_CHOOSER_TARGET,
-                        directShareToShortcutInfos)
-        );
-        // Thread.sleep shouldn't be a thing in an integration test but it's
-        // necessary here because of the way the code is structured
-        // TODO: restructure the tests b/129870719
-        Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
-
         assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
                 activity.getAdapter().getCount(), is(3));
         assertThat("Chooser should have exactly one selectable direct target",
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
new file mode 100644
index 0000000..c6537c0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
@@ -0,0 +1,450 @@
+/*
+ * 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.app;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER;
+import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_ACCESS_BLOCKER;
+import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_SHARE_BLOCKER;
+import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_ACCESS_BLOCKER;
+import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_SHARE_BLOCKER;
+import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.Tab.PERSONAL;
+import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.Tab.WORK;
+import static com.android.internal.app.ChooserWrapperActivity.sOverrides;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.companion.DeviceFilter;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.internal.R;
+import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.Tab;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+@DeviceFilter.MediumType
+@RunWith(Parameterized.class)
+public class ChooserActivityWorkProfileTest {
+
+    private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+            .getInstrumentation().getTargetContext().getUser();
+    private static final UserHandle WORK_USER_HANDLE = UserHandle.of(10);
+
+    @Rule
+    public ActivityTestRule<ChooserWrapperActivity> mActivityRule =
+            new ActivityTestRule<>(ChooserWrapperActivity.class, false,
+                    false);
+    private final TestCase mTestCase;
+
+    public ChooserActivityWorkProfileTest(TestCase testCase) {
+        mTestCase = testCase;
+    }
+
+    @Before
+    public void cleanOverrideData() {
+        sOverrides.reset();
+    }
+
+    @Test
+    public void testBlocker() {
+        setUpPersonalAndWorkComponentInfos();
+        sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
+        sOverrides.myUserId = mTestCase.getMyUserHandle().getIdentifier();
+
+        launchActivity(mTestCase.getIsSendAction());
+        switchToTab(mTestCase.getTab());
+
+        switch (mTestCase.getExpectedBlocker()) {
+            case NO_BLOCKER:
+                assertNoBlockerDisplayed();
+                break;
+            case PERSONAL_PROFILE_SHARE_BLOCKER:
+                assertCantSharePersonalAppsBlockerDisplayed();
+                break;
+            case WORK_PROFILE_SHARE_BLOCKER:
+                assertCantShareWorkAppsBlockerDisplayed();
+                break;
+            case PERSONAL_PROFILE_ACCESS_BLOCKER:
+                assertCantAccessPersonalAppsBlockerDisplayed();
+                break;
+            case WORK_PROFILE_ACCESS_BLOCKER:
+                assertCantAccessWorkAppsBlockerDisplayed();
+                break;
+        }
+    }
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Collection tests() {
+        return Arrays.asList(
+                new TestCase(
+                        /* isSendAction= */ true,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+//                TODO(b/256869196) ChooserActivity goes into requestLayout loop
+//                new TestCase(
+//                        /* isSendAction= */ true,
+//                        /* hasCrossProfileIntents= */ false,
+//                        /* myUserHandle= */ WORK_USER_HANDLE,
+//                        /* tab= */ WORK,
+//                        /* expectedBlocker= */ NO_BLOCKER
+//                ),
+                new TestCase(
+                        /* isSendAction= */ true,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ true,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ WORK_PROFILE_SHARE_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ true,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+//                TODO(b/256869196) ChooserActivity goes into requestLayout loop
+//                new TestCase(
+//                        /* isSendAction= */ true,
+//                        /* hasCrossProfileIntents= */ false,
+//                        /* myUserHandle= */ WORK_USER_HANDLE,
+//                        /* tab= */ PERSONAL,
+//                        /* expectedBlocker= */ PERSONAL_PROFILE_SHARE_BLOCKER
+//                ),
+                new TestCase(
+                        /* isSendAction= */ true,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ true,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ WORK_PROFILE_ACCESS_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ PERSONAL_PROFILE_ACCESS_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                )
+        );
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+            int numberOfResults, int userId) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            infoList.add(
+                    ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+        }
+        return infoList;
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+        }
+        return infoList;
+    }
+
+    private void setUpPersonalAndWorkComponentInfos() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3,
+                        /* userId */ WORK_USER_HANDLE.getIdentifier());
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+    }
+
+    private void setupResolverControllers(
+            List<ResolvedComponentInfo> personalResolvedComponentInfos,
+            List<ResolvedComponentInfo> workResolvedComponentInfos) {
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(UserHandle.SYSTEM)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+    }
+
+    private void waitForIdle() {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    private void markWorkProfileUserAvailable() {
+        ChooserWrapperActivity.sOverrides.workProfileUserHandle = WORK_USER_HANDLE;
+    }
+
+    private void assertCantAccessWorkAppsBlockerDisplayed() {
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.resolver_cant_access_work_apps_explanation))
+                .check(matches(isDisplayed()));
+    }
+
+    private void assertCantAccessPersonalAppsBlockerDisplayed() {
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.resolver_cant_access_personal_apps_explanation))
+                .check(matches(isDisplayed()));
+    }
+
+    private void assertCantShareWorkAppsBlockerDisplayed() {
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.resolver_cant_share_with_work_apps_explanation))
+                .check(matches(isDisplayed()));
+    }
+
+    private void assertCantSharePersonalAppsBlockerDisplayed() {
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.resolver_cant_share_with_personal_apps_explanation))
+                .check(matches(isDisplayed()));
+    }
+
+    private void assertNoBlockerDisplayed() {
+        try {
+            onView(withText(R.string.resolver_cross_profile_blocked))
+                    .check(matches(not(isDisplayed())));
+        } catch (NoMatchingViewException ignored) {
+        }
+    }
+
+    private void switchToTab(Tab tab) {
+        final int stringId = tab == Tab.WORK ? R.string.resolver_work_tab
+                : R.string.resolver_personal_tab;
+
+        onView(withText(stringId)).perform(click());
+        waitForIdle();
+
+        onView(withId(R.id.contentPanel))
+                .perform(swipeUp());
+        waitForIdle();
+    }
+
+    private Intent createTextIntent(boolean isSendAction) {
+        Intent sendIntent = new Intent();
+        if (isSendAction) {
+            sendIntent.setAction(Intent.ACTION_SEND);
+        }
+        sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
+        sendIntent.setType("text/plain");
+        return sendIntent;
+    }
+
+    private void launchActivity(boolean isSendAction) {
+        Intent sendIntent = createTextIntent(isSendAction);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test"));
+        waitForIdle();
+    }
+
+    public static class TestCase {
+        private final boolean mIsSendAction;
+        private final boolean mHasCrossProfileIntents;
+        private final UserHandle mMyUserHandle;
+        private final Tab mTab;
+        private final ExpectedBlocker mExpectedBlocker;
+
+        public enum ExpectedBlocker {
+            NO_BLOCKER,
+            PERSONAL_PROFILE_SHARE_BLOCKER,
+            WORK_PROFILE_SHARE_BLOCKER,
+            PERSONAL_PROFILE_ACCESS_BLOCKER,
+            WORK_PROFILE_ACCESS_BLOCKER
+        }
+
+        public enum Tab {
+            WORK,
+            PERSONAL
+        }
+
+        public TestCase(boolean isSendAction, boolean hasCrossProfileIntents,
+                UserHandle myUserHandle, Tab tab, ExpectedBlocker expectedBlocker) {
+            mIsSendAction = isSendAction;
+            mHasCrossProfileIntents = hasCrossProfileIntents;
+            mMyUserHandle = myUserHandle;
+            mTab = tab;
+            mExpectedBlocker = expectedBlocker;
+        }
+
+        public boolean getIsSendAction() {
+            return mIsSendAction;
+        }
+
+        public boolean hasCrossProfileIntents() {
+            return mHasCrossProfileIntents;
+        }
+
+        public UserHandle getMyUserHandle() {
+            return mMyUserHandle;
+        }
+
+        public Tab getTab() {
+            return mTab;
+        }
+
+        public ExpectedBlocker getExpectedBlocker() {
+            return mExpectedBlocker;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder result = new StringBuilder("test");
+
+            if (mTab == WORK) {
+                result.append("WorkTab_");
+            } else {
+                result.append("PersonalTab_");
+            }
+
+            if (mIsSendAction) {
+                result.append("sendAction_");
+            } else {
+                result.append("notSendAction_");
+            }
+
+            if (mHasCrossProfileIntents) {
+                result.append("hasCrossProfileIntents_");
+            } else {
+                result.append("doesNotHaveCrossProfileIntents_");
+            }
+
+            if (mMyUserHandle.equals(PERSONAL_USER_HANDLE)) {
+                result.append("myUserIsPersonal_");
+            } else {
+                result.append("myUserIsWork_");
+            }
+
+            if (mExpectedBlocker == ExpectedBlocker.NO_BLOCKER) {
+                result.append("thenNoBlocker");
+            } else if (mExpectedBlocker == PERSONAL_PROFILE_ACCESS_BLOCKER) {
+                result.append("thenAccessBlockerOnPersonalProfile");
+            } else if (mExpectedBlocker == PERSONAL_PROFILE_SHARE_BLOCKER) {
+                result.append("thenShareBlockerOnPersonalProfile");
+            } else if (mExpectedBlocker == WORK_PROFILE_ACCESS_BLOCKER) {
+                result.append("thenAccessBlockerOnWorkProfile");
+            } else if (mExpectedBlocker == WORK_PROFILE_SHARE_BLOCKER) {
+                result.append("thenShareBlockerOnWorkProfile");
+            }
+
+            return result.toString();
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 7f85982..5dc0c8b 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -16,6 +16,10 @@
 
 package com.android.internal.app;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
@@ -31,8 +35,11 @@
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.UserHandle;
+import android.util.Pair;
 import android.util.Size;
 
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
 import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
 import com.android.internal.app.chooser.DisplayResolveInfo;
 import com.android.internal.app.chooser.TargetInfo;
@@ -59,15 +66,6 @@
     }
 
     @Override
-    protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
-            Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) {
-        AbstractMultiProfilePagerAdapter multiProfilePagerAdapter =
-                super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
-        multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector);
-        return multiProfilePagerAdapter;
-    }
-
-    @Override
     public ChooserListAdapter createChooserListAdapter(Context context, List<Intent> payloadIntents,
             Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed,
             ResolverListController resolverListController) {
@@ -134,6 +132,30 @@
     }
 
     @Override
+    protected MyUserIdProvider createMyUserIdProvider() {
+        if (sOverrides.mMyUserIdProvider != null) {
+            return sOverrides.mMyUserIdProvider;
+        }
+        return super.createMyUserIdProvider();
+    }
+
+    @Override
+    protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+        if (sOverrides.mCrossProfileIntentsChecker != null) {
+            return sOverrides.mCrossProfileIntentsChecker;
+        }
+        return super.createCrossProfileIntentsChecker();
+    }
+
+    @Override
+    protected AbstractMultiProfilePagerAdapter.QuietModeManager createQuietModeManager() {
+        if (sOverrides.mQuietModeManager != null) {
+            return sOverrides.mQuietModeManager;
+        }
+        return super.createQuietModeManager();
+    }
+
+    @Override
     public void safelyStartActivity(com.android.internal.app.chooser.TargetInfo cti) {
         if (sOverrides.onSafelyStartCallback != null &&
                 sOverrides.onSafelyStartCallback.apply(cti)) {
@@ -239,6 +261,12 @@
     @Override
     protected void queryDirectShareTargets(ChooserListAdapter adapter,
             boolean skipAppPredictionService) {
+        if (sOverrides.directShareTargets != null) {
+            Pair<Integer, ServiceResultInfo[]> result =
+                    sOverrides.directShareTargets.apply(this, adapter);
+            sendShortcutManagerShareTargetResults(result.first, result.second);
+            return;
+        }
         if (sOverrides.onQueryDirectShareTargets != null) {
             sOverrides.onQueryDirectShareTargets.apply(adapter);
         }
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java
new file mode 100644
index 0000000..ce68906
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java
@@ -0,0 +1,429 @@
+/*
+ * 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.app;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER;
+import static com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_BLOCKER;
+import static com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_BLOCKER;
+import static com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.Tab.PERSONAL;
+import static com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.Tab.WORK;
+import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.companion.DeviceFilter;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.internal.R;
+import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.Tab;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+@DeviceFilter.MediumType
+@RunWith(Parameterized.class)
+public class ResolverActivityWorkProfileTest {
+
+    private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+            .getInstrumentation().getTargetContext().getUser();
+    private static final UserHandle WORK_USER_HANDLE = UserHandle.of(10);
+
+    @Rule
+    public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
+            new ActivityTestRule<>(ResolverWrapperActivity.class, false,
+                    false);
+    private final TestCase mTestCase;
+
+    public ResolverActivityWorkProfileTest(TestCase testCase) {
+        mTestCase = testCase;
+    }
+
+    @Before
+    public void cleanOverrideData() {
+        sOverrides.reset();
+    }
+
+    @Test
+    public void testBlocker() {
+        setUpPersonalAndWorkComponentInfos();
+        sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
+        sOverrides.myUserId = mTestCase.getMyUserHandle().getIdentifier();
+
+        launchActivity(/* callingUser= */ mTestCase.getExtraCallingUser());
+        switchToTab(mTestCase.getTab());
+
+        switch (mTestCase.getExpectedBlocker()) {
+            case NO_BLOCKER:
+                assertNoBlockerDisplayed();
+                break;
+            case PERSONAL_PROFILE_BLOCKER:
+                assertCantAccessPersonalAppsBlockerDisplayed();
+                break;
+            case WORK_PROFILE_BLOCKER:
+                assertCantAccessWorkAppsBlockerDisplayed();
+                break;
+        }
+    }
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Collection tests() {
+        return Arrays.asList(
+                new TestCase(
+                        /* extraCallingUser= */ null,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* extraCallingUser= */ null,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* extraCallingUser= */ null,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* extraCallingUser= */ null,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ WORK_PROFILE_BLOCKER
+                ),
+                new TestCase(
+                        /* extraCallingUser= */ null,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* extraCallingUser= */ null,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ PERSONAL_PROFILE_BLOCKER
+                ),
+                new TestCase(
+                        /* extraCallingUser= */ null,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* extraCallingUser= */ null,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+
+                new TestCase(
+                        /* extraCallingUser= */ WORK_USER_HANDLE,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* extraCallingUser= */ WORK_USER_HANDLE,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* extraCallingUser= */ WORK_USER_HANDLE,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* extraCallingUser= */ WORK_USER_HANDLE,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* extraCallingUser= */ WORK_USER_HANDLE,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* extraCallingUser= */ WORK_USER_HANDLE,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* extraCallingUser= */ WORK_USER_HANDLE,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* extraCallingUser= */ WORK_USER_HANDLE,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                )
+        );
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+            int numberOfResults, int userId) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            infoList.add(
+                    ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+        }
+        return infoList;
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+        }
+        return infoList;
+    }
+
+    private void setUpPersonalAndWorkComponentInfos() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3,
+                        /* userId */ WORK_USER_HANDLE.getIdentifier());
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+    }
+
+    private void setupResolverControllers(
+            List<ResolvedComponentInfo> personalResolvedComponentInfos,
+            List<ResolvedComponentInfo> workResolvedComponentInfos) {
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(UserHandle.SYSTEM)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+    }
+
+    private void waitForIdle() {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    private void markWorkProfileUserAvailable() {
+        ResolverWrapperActivity.sOverrides.workProfileUserHandle = WORK_USER_HANDLE;
+    }
+
+    private void assertCantAccessWorkAppsBlockerDisplayed() {
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.resolver_cant_access_work_apps_explanation))
+                .check(matches(isDisplayed()));
+    }
+
+    private void assertCantAccessPersonalAppsBlockerDisplayed() {
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.resolver_cant_access_personal_apps_explanation))
+                .check(matches(isDisplayed()));
+    }
+
+    private void assertNoBlockerDisplayed() {
+        try {
+            onView(withText(R.string.resolver_cross_profile_blocked))
+                    .check(matches(not(isDisplayed())));
+        } catch (NoMatchingViewException ignored) {
+        }
+    }
+
+    private void switchToTab(Tab tab) {
+        final int stringId = tab == Tab.WORK ? R.string.resolver_work_tab
+                : R.string.resolver_personal_tab;
+
+        onView(withText(stringId)).perform(click());
+        waitForIdle();
+
+        onView(withId(R.id.contentPanel))
+                .perform(swipeUp());
+        waitForIdle();
+    }
+
+    private Intent createSendImageIntent() {
+        Intent sendIntent = new Intent();
+        sendIntent.setAction(Intent.ACTION_SEND);
+        sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
+        sendIntent.setType("image/jpeg");
+        return sendIntent;
+    }
+
+    private void launchActivity(UserHandle callingUser) {
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+
+        if (callingUser != null) {
+            sendIntent.putExtra(ResolverActivity.EXTRA_CALLING_USER, callingUser);
+        }
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+    }
+
+    public static class TestCase {
+        @Nullable
+        private final UserHandle mExtraCallingUser;
+        private final boolean mHasCrossProfileIntents;
+        private final UserHandle mMyUserHandle;
+        private final Tab mTab;
+        private final ExpectedBlocker mExpectedBlocker;
+
+        public enum ExpectedBlocker {
+            NO_BLOCKER,
+            PERSONAL_PROFILE_BLOCKER,
+            WORK_PROFILE_BLOCKER
+        }
+
+        public enum Tab {
+            WORK,
+            PERSONAL
+        }
+
+        public TestCase(@Nullable UserHandle extraCallingUser, boolean hasCrossProfileIntents,
+                UserHandle myUserHandle, Tab tab, ExpectedBlocker expectedBlocker) {
+            mExtraCallingUser = extraCallingUser;
+            mHasCrossProfileIntents = hasCrossProfileIntents;
+            mMyUserHandle = myUserHandle;
+            mTab = tab;
+            mExpectedBlocker = expectedBlocker;
+        }
+
+        @Nullable
+        public UserHandle getExtraCallingUser() {
+            return mExtraCallingUser;
+        }
+
+        public boolean hasCrossProfileIntents() {
+            return mHasCrossProfileIntents;
+        }
+
+        public UserHandle getMyUserHandle() {
+            return mMyUserHandle;
+        }
+
+        public Tab getTab() {
+            return mTab;
+        }
+
+        public ExpectedBlocker getExpectedBlocker() {
+            return mExpectedBlocker;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder result = new StringBuilder("test");
+
+            if (mTab == WORK) {
+                result.append("WorkTab_");
+            } else {
+                result.append("PersonalTab_");
+            }
+
+            if (mExtraCallingUser != null
+                    && !mExtraCallingUser.equals(PERSONAL_USER_HANDLE)) {
+                result.append("callingUserIsNonPersonal_");
+            } else {
+                result.append("callingUserIsPersonal_");
+            }
+
+            if (mHasCrossProfileIntents) {
+                result.append("hasCrossProfileIntents_");
+            } else {
+                result.append("doesNotHaveCrossProfileIntents_");
+            }
+
+            if (mMyUserHandle.equals(PERSONAL_USER_HANDLE)) {
+                result.append("myUserIsPersonal_");
+            } else {
+                result.append("myUserIsWork_");
+            }
+
+            if (mExpectedBlocker == ExpectedBlocker.NO_BLOCKER) {
+                result.append("thenNoBlocker");
+            } else if (mExpectedBlocker == ExpectedBlocker.PERSONAL_PROFILE_BLOCKER) {
+                result.append("thenBlockerOnPersonalProfile");
+            } else if (mExpectedBlocker == WORK_PROFILE_BLOCKER) {
+                result.append("thenBlockerOnWorkProfile");
+            }
+
+            return result.toString();
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index 4cf9c3f..c778dfe 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.app;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -27,6 +29,9 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
 import com.android.internal.app.chooser.TargetInfo;
 
 import java.util.List;
@@ -52,12 +57,27 @@
     }
 
     @Override
-    protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
-            Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) {
-        AbstractMultiProfilePagerAdapter multiProfilePagerAdapter =
-                super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
-        multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector);
-        return multiProfilePagerAdapter;
+    protected MyUserIdProvider createMyUserIdProvider() {
+        if (sOverrides.mMyUserIdProvider != null) {
+            return sOverrides.mMyUserIdProvider;
+        }
+        return super.createMyUserIdProvider();
+    }
+
+    @Override
+    protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+        if (sOverrides.mCrossProfileIntentsChecker != null) {
+            return sOverrides.mCrossProfileIntentsChecker;
+        }
+        return super.createCrossProfileIntentsChecker();
+    }
+
+    @Override
+    protected QuietModeManager createQuietModeManager() {
+        if (sOverrides.mQuietModeManager != null) {
+            return sOverrides.mQuietModeManager;
+        }
+        return super.createQuietModeManager();
     }
 
     ResolverWrapperAdapter getAdapter() {
@@ -137,9 +157,12 @@
         public ResolverListController workResolverListController;
         public Boolean isVoiceInteraction;
         public UserHandle workProfileUserHandle;
+        public Integer myUserId;
         public boolean hasCrossProfileIntents;
         public boolean isQuietModeEnabled;
-        public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
+        public QuietModeManager mQuietModeManager;
+        public MyUserIdProvider mMyUserIdProvider;
+        public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
 
         public void reset() {
             onSafelyStartCallback = null;
@@ -148,15 +171,11 @@
             resolverListController = mock(ResolverListController.class);
             workResolverListController = mock(ResolverListController.class);
             workProfileUserHandle = null;
+            myUserId = null;
             hasCrossProfileIntents = true;
             isQuietModeEnabled = false;
-            multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
-                @Override
-                public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
-                        int targetUserId) {
-                    return hasCrossProfileIntents;
-                }
 
+            mQuietModeManager = new QuietModeManager() {
                 @Override
                 public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
                     return isQuietModeEnabled;
@@ -167,7 +186,27 @@
                         UserHandle workProfileUserHandle) {
                     isQuietModeEnabled = enabled;
                 }
+
+                @Override
+                public void markWorkProfileEnabledBroadcastReceived() {
+                }
+
+                @Override
+                public boolean isWaitingToEnableWorkProfile() {
+                    return false;
+                }
             };
+
+            mMyUserIdProvider = new MyUserIdProvider() {
+                @Override
+                public int getMyUserId() {
+                    return myUserId != null ? myUserId : UserHandle.myUserId();
+                }
+            };
+
+            mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
+            when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
+                    .thenAnswer(invocation -> hasCrossProfileIntents);
         }
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
index caec365..0f30cfe 100644
--- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
@@ -286,6 +286,39 @@
     }
 
     @Test
+    public void testPartialConfigPartitionPrecedence() throws IOException {
+        createFile("/odm/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"two\" enabled=\"true\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
+                1);
+        mScannerRule.addOverlay(createFile("/odm/overlay/two.apk"), "two");
+        mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three", "android", 0,
+                true, 0);
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertConfig(overlayConfig, "one", false, true, 0);
+        assertConfig(overlayConfig, "two", true, true, 1);
+        assertConfig(overlayConfig, "three", false, true, 2);
+    }
+
+    @Test
+    public void testNoConfigPartitionPrecedence() throws IOException {
+        mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
+                1);
+        mScannerRule.addOverlay(createFile("/odm/overlay/two.apk"), "two", "android", 0, true, 2);
+        mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three", "android", 0,
+                true, 0);
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertConfig(overlayConfig, "one", false, true, 0);
+        assertConfig(overlayConfig, "two", false, true, 1);
+        assertConfig(overlayConfig, "three", false, true, 2);
+    }
+
+    @Test
     public void testImmutable() throws IOException {
         createFile("/product/overlay/config/config.xml",
                 "<config>"
@@ -507,37 +540,6 @@
     }
 
     @Test
-    public void testNoConfigsAllowPartitionReordering() throws IOException {
-        mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
-                1);
-        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true,
-                0);
-
-        final OverlayConfig overlayConfig = createConfigImpl();
-        assertConfig(overlayConfig, "one", false, true, 1);
-        assertConfig(overlayConfig, "two", false, true, 0);
-    }
-
-    @Test
-    public void testConfigDisablesPartitionReordering() throws IOException {
-        createFile("/odm/overlay/config/config.xml",
-                "<config>"
-                        + "  <overlay package=\"two\" enabled=\"true\" />"
-                        + "</config>");
-
-        mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
-                1);
-        mScannerRule.addOverlay(createFile("/odm/overlay/two.apk"), "two");
-        mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three", "android", 0,
-                true, 0);
-
-        final OverlayConfig overlayConfig = createConfigImpl();
-        assertConfig(overlayConfig, "one", false, true, 0);
-        assertConfig(overlayConfig, "two", true, true, 1);
-        assertConfig(overlayConfig, "three", false, true, 2);
-    }
-
-    @Test
     public void testStaticOverlayOutsideOverlayDir() throws IOException {
         mScannerRule.addOverlay(createFile("/product/app/one.apk"), "one", "android", 0, true, 0);
 
@@ -550,7 +552,7 @@
     @Test
     public void testSortStaticOverlaysDifferentTargets() throws IOException {
         mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "other", 0, true, 0);
-        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true,
+        mScannerRule.addOverlay(createFile("/vendor/overlay/two.apk"), "two", "android", 0, true,
                 0);
 
         final OverlayConfig overlayConfig = createConfigImpl();
@@ -559,15 +561,33 @@
     }
 
     @Test
+    public void testSortStaticOverlaysDifferentPartitions() throws IOException {
+        mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
+                2);
+        mScannerRule.addOverlay(createFile("/vendor/overlay/two.apk"), "two", "android", 0, true,
+                3);
+        mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three", "android", 0,
+                true, 0);
+        mScannerRule.addOverlay(createFile("/product/overlay/four.apk"), "four", "android", 0,
+                true, 1);
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertConfig(overlayConfig, "one", false, true, 0);
+        assertConfig(overlayConfig, "two", false, true, 1);
+        assertConfig(overlayConfig, "three", false, true, 2);
+        assertConfig(overlayConfig, "four", false, true, 3);
+    }
+
+    @Test
     public void testSortStaticOverlaysSamePriority() throws IOException {
         mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
                 0);
-        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true,
+        mScannerRule.addOverlay(createFile("/vendor/overlay/two.apk"), "two", "android", 0, true,
                 0);
 
         final OverlayConfig overlayConfig = createConfigImpl();
-        assertConfig(overlayConfig, "one", false, true, 1);
-        assertConfig(overlayConfig, "two", false, true, 0);
+        assertConfig(overlayConfig, "one", false, true, 0);
+        assertConfig(overlayConfig, "two", false, true, 1);
     }
 
     @Test
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/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 82b2bf4..8207c9e 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -1054,10 +1054,23 @@
             super(new Injector() {
                 public Random getRandomGenerator() {
                     return new Random() {
-                        int mCallCount = 0;
+                        int mCallCount = -1;
 
                         public int nextInt() {
-                            return mCallCount++;
+                            throw new IllegalStateException("Should not use nextInt()");
+                        }
+
+                        public int nextInt(int x) {
+                            if (mCallCount == -1) {
+                                // The tests are written such that they expect
+                                // the first call to nextInt() to be on the first
+                                // callEnded(). However, the BinderCallsStats
+                                // constructor also calls nextInt(). Fake 0 being
+                                // rolled twice.
+                                mCallCount++;
+                                return 0;
+                            }
+                            return (mCallCount++) % x;
                         }
                     };
                 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
index 5af7376..7bd53b9 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
@@ -98,7 +98,7 @@
         assertEquals(1, latencyHistograms.size());
         LatencyDims dims = latencyHistograms.keySet().iterator().next();
         assertEquals(binder.getClass(), dims.getBinderClass());
-        assertEquals(1, dims.getTransactionCode());
+        assertEquals(2, dims.getTransactionCode()); // the first nextInt() is in the constructor
         assertThat(latencyHistograms.get(dims)).asList().containsExactly(1, 0, 0, 0, 0).inOrder();
     }
 
@@ -313,11 +313,11 @@
                                 int mCallCount = 0;
 
                                 public int nextInt() {
-                                    return mCallCount++;
+                                    throw new IllegalStateException("Should not use nextInt()");
                                 }
 
                                 public int nextInt(int x) {
-                                    return 1;
+                                    return (mCallCount++) % x;
                                 }
                             };
                         }
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
index b34554c..c3d40eb 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.os.FileUtils;
+import android.util.IntArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -33,13 +34,15 @@
 import java.io.File;
 import java.nio.file.Files;
 import java.util.ArrayList;
+import java.util.Arrays;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ProcLocksReaderTest implements
         ProcLocksReader.ProcLocksReaderCallback {
     private File mProcDirectory;
-    private ArrayList<Integer> mPids = new ArrayList<>();
+
+    private ArrayList<int[]> mPids = new ArrayList<>();
 
     @Before
     public void setUp() {
@@ -54,41 +57,51 @@
 
     @Test
     public void testRunSimpleLocks() throws Exception {
-        String simpleLocks =
-                "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n" +
-                "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n";
+        String simpleLocks = "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n"
+                           + "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n";
         runHandleBlockingFileLocks(simpleLocks);
         assertTrue(mPids.isEmpty());
     }
 
     @Test
     public void testRunBlockingLocks() throws Exception {
-        String blockedLocks =
-                "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n" +
-                "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n" +
-                "2: -> POSIX  ADVISORY  WRITE 18291 fd:09:34062 0 EOF\n" +
-                "2: -> POSIX  ADVISORY  WRITE 18293 fd:09:34062 0 EOF\n" +
-                "3: POSIX  ADVISORY  READ  3888 fd:09:13992 128 128\n" +
-                "4: POSIX  ADVISORY  READ  3888 fd:09:14230 1073741826 1073742335\n";
+        String blockedLocks = "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n"
+                            + "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n"
+                            + "2: -> POSIX  ADVISORY  WRITE 18291 fd:09:34062 0 EOF\n"
+                            + "2: -> POSIX  ADVISORY  WRITE 18293 fd:09:34062 0 EOF\n"
+                            + "3: POSIX  ADVISORY  READ  3888 fd:09:13992 128 128\n"
+                            + "4: POSIX  ADVISORY  READ  3888 fd:09:14230 1073741826 1073742335\n";
         runHandleBlockingFileLocks(blockedLocks);
-        assertTrue(mPids.remove(0).equals(18292));
+        assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
+        assertTrue(mPids.isEmpty());
+    }
+
+    @Test
+    public void testRunLastBlockingLocks() throws Exception {
+        String blockedLocks = "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n"
+                            + "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n"
+                            + "2: -> POSIX  ADVISORY  WRITE 18291 fd:09:34062 0 EOF\n"
+                            + "2: -> POSIX  ADVISORY  WRITE 18293 fd:09:34062 0 EOF\n";
+        runHandleBlockingFileLocks(blockedLocks);
+        assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
         assertTrue(mPids.isEmpty());
     }
 
     @Test
     public void testRunMultipleBlockingLocks() throws Exception {
-        String blockedLocks =
-                "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n" +
-                "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n" +
-                "2: -> POSIX  ADVISORY  WRITE 18291 fd:09:34062 0 EOF\n" +
-                "2: -> POSIX  ADVISORY  WRITE 18293 fd:09:34062 0 EOF\n" +
-                "3: POSIX  ADVISORY  READ  3888 fd:09:13992 128 128\n" +
-                "4: FLOCK  ADVISORY  WRITE 3840 fe:01:5111809 0 EOF\n" +
-                "4: -> FLOCK  ADVISORY  WRITE 3841 fe:01:5111809 0 EOF\n" +
-                "5: POSIX  ADVISORY  READ  3888 fd:09:14230 1073741826 1073742335\n";
+        String blockedLocks = "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n"
+                            + "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n"
+                            + "2: -> POSIX  ADVISORY  WRITE 18291 fd:09:34062 0 EOF\n"
+                            + "2: -> POSIX  ADVISORY  WRITE 18293 fd:09:34062 0 EOF\n"
+                            + "3: POSIX  ADVISORY  READ  3888 fd:09:13992 128 128\n"
+                            + "4: FLOCK  ADVISORY  WRITE 3840 fe:01:5111809 0 EOF\n"
+                            + "4: -> FLOCK  ADVISORY  WRITE 3841 fe:01:5111809 0 EOF\n"
+                            + "5: FLOCK  ADVISORY  READ  3888 fd:09:14230 0 EOF\n"
+                            + "5: -> FLOCK  ADVISORY  READ  3887 fd:09:14230 0 EOF\n";
         runHandleBlockingFileLocks(blockedLocks);
-        assertTrue(mPids.remove(0).equals(18292));
-        assertTrue(mPids.remove(0).equals(3840));
+        assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
+        assertTrue(Arrays.equals(mPids.remove(0), new int[]{3840, 3841}));
+        assertTrue(Arrays.equals(mPids.remove(0), new int[]{3888, 3887}));
         assertTrue(mPids.isEmpty());
     }
 
@@ -102,11 +115,12 @@
 
     /**
      * Call the callback function of handleBlockingFileLocks().
-     *
-     * @param pid Each process that hold file locks blocking other processes.
+     * @param pids Each process that hold file locks blocking other processes.
+     *             pids[0] is the process blocking others
+     *             pids[1..n-1] are the processes being blocked
      */
     @Override
-    public void onBlockingFileLock(int pid) {
-        mPids.add(pid);
+    public void onBlockingFileLock(IntArray pids) {
+        mPids.add(pids.toArray());
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java b/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java
new file mode 100644
index 0000000..c540a15
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java
@@ -0,0 +1,244 @@
+/*
+ * 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.os;
+
+import static org.junit.Assert.assertThrows;
+
+import android.compat.testing.PlatformCompatChangeRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Test SafeZipPathCallback.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SafeZipPathValidatorCallbackTest {
+    @Rule
+    public TestRule mCompatChangeRule = new PlatformCompatChangeRule();
+
+    @Before
+    public void setUp() {
+        RuntimeInit.initZipPathValidatorCallback();
+    }
+
+    @Test
+    @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+    public void testNewZipFile_whenZipFileHasDangerousEntriesAndChangeEnabled_throws()
+            throws Exception {
+        final String[] dangerousEntryNames = {
+            "../foo.bar",
+            "foo/../bar.baz",
+            "foo/../../bar.baz",
+            "foo.bar/..",
+            "foo.bar/../",
+            "..",
+            "../",
+            "/foo",
+        };
+        for (String entryName : dangerousEntryNames) {
+            final File tempFile = File.createTempFile("smdc", "zip");
+            try {
+                writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+
+                assertThrows(
+                        "ZipException expected for entry: " + entryName,
+                        ZipException.class,
+                        () -> {
+                            new ZipFile(tempFile);
+                        });
+            } finally {
+                tempFile.delete();
+            }
+        }
+    }
+
+    @Test
+    @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+    public void
+            testZipInputStreamGetNextEntry_whenZipFileHasDangerousEntriesAndChangeEnabled_throws()
+                    throws Exception {
+        final String[] dangerousEntryNames = {
+            "../foo.bar",
+            "foo/../bar.baz",
+            "foo/../../bar.baz",
+            "foo.bar/..",
+            "foo.bar/../",
+            "..",
+            "../",
+            "/foo",
+        };
+        for (String entryName : dangerousEntryNames) {
+            byte[] badZipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+            try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(badZipBytes))) {
+                assertThrows(
+                        "ZipException expected for entry: " + entryName,
+                        ZipException.class,
+                        () -> {
+                            zis.getNextEntry();
+                        });
+            }
+        }
+    }
+
+    @Test
+    @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+    public void testNewZipFile_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow()
+            throws Exception {
+        final String[] normalEntryNames = {
+            "foo", "foo.bar", "foo..bar",
+        };
+        for (String entryName : normalEntryNames) {
+            final File tempFile = File.createTempFile("smdc", "zip");
+            try {
+                writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+                try {
+                    new ZipFile((tempFile));
+                } catch (ZipException e) {
+                    throw new AssertionError("ZipException not expected for entry: " + entryName);
+                }
+            } finally {
+                tempFile.delete();
+            }
+        }
+    }
+
+    @Test
+    @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+    public void
+            testZipInputStreamGetNextEntry_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow()
+                    throws Exception {
+        final String[] normalEntryNames = {
+            "foo", "foo.bar", "foo..bar",
+        };
+        for (String entryName : normalEntryNames) {
+            byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+            try {
+                ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes));
+                zis.getNextEntry();
+            } catch (ZipException e) {
+                throw new AssertionError("ZipException not expected for entry: " + entryName);
+            }
+        }
+    }
+
+    @Test
+    @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+    public void
+            testNewZipFile_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow()
+                    throws Exception {
+        final String[] entryNames = {
+            "../foo.bar",
+            "foo/../bar.baz",
+            "foo/../../bar.baz",
+            "foo.bar/..",
+            "foo.bar/../",
+            "..",
+            "../",
+            "/foo",
+            "foo",
+            "foo.bar",
+            "foo..bar",
+        };
+        for (String entryName : entryNames) {
+            final File tempFile = File.createTempFile("smdc", "zip");
+            try {
+                writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+                try {
+                    new ZipFile((tempFile));
+                } catch (ZipException e) {
+                    throw new AssertionError("ZipException not expected for entry: " + entryName);
+                }
+            } finally {
+                tempFile.delete();
+            }
+        }
+    }
+
+    @Test
+    @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+    public void
+            testZipInputStreamGetNextEntry_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow()
+                    throws Exception {
+        final String[] entryNames = {
+            "../foo.bar",
+            "foo/../bar.baz",
+            "foo/../../bar.baz",
+            "foo.bar/..",
+            "foo.bar/../",
+            "..",
+            "../",
+            "/foo",
+            "foo",
+            "foo.bar",
+            "foo..bar",
+        };
+        for (String entryName : entryNames) {
+            byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+            try {
+                ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes));
+                zis.getNextEntry();
+            } catch (ZipException e) {
+                throw new AssertionError("ZipException not expected for entry: " + entryName);
+            }
+        }
+    }
+
+    private void writeZipFileOutputStreamWithEmptyEntry(File tempFile, String entryName)
+            throws IOException {
+        FileOutputStream tempFileStream = new FileOutputStream(tempFile);
+        writeZipOutputStreamWithEmptyEntry(tempFileStream, entryName);
+        tempFileStream.close();
+    }
+
+    private byte[] getZipBytesFromZipOutputStreamWithEmptyEntry(String entryName)
+            throws IOException {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        writeZipOutputStreamWithEmptyEntry(bos, entryName);
+        return bos.toByteArray();
+    }
+
+    private void writeZipOutputStreamWithEmptyEntry(OutputStream os, String entryName)
+            throws IOException {
+        ZipOutputStream zos = new ZipOutputStream(os);
+        ZipEntry entry = new ZipEntry(entryName);
+        zos.putNextEntry(entry);
+        zos.write(new byte[2]);
+        zos.closeEntry();
+        zos.close();
+    }
+}
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/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/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java b/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
index 6c50bce..8b30828 100644
--- a/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.util;
 
+import static org.junit.Assert.assertThrows;
+
 import android.os.SystemClock;
 import android.text.format.DateUtils;
 
@@ -170,10 +172,9 @@
     }
 
     void assertThrow(Fn fn) {
-        try {
+        assertThrows(Throwable.class, () -> {
             fn.call();
-            fail("expected n exception to be thrown.");
-        } catch (Throwable t) { }
+        });
     }
 
     interface Fn { void call(); }
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index d10f173..4d4ec35 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -168,7 +168,8 @@
     }
 
     private WindowInsets insetsWith(Insets content, DisplayCutout cutout) {
-        return new WindowInsets(content.toRect(), null, false, false, cutout);
+        return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null,
+                false, false, cutout, null, null, null, WindowInsets.Type.systemBars(), false);
     }
 
     private ViewGroup createViewGroupWithId(int id) {
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index 9e39e13..3e3c77b 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -377,6 +377,11 @@
             notifyDeviceStateInfoChanged();
         }
 
+        // No-op in the test since DeviceStateManagerGlobal just calls into the system server with
+        // no business logic around it.
+        @Override
+        public void onStateRequestOverlayDismissed(boolean shouldCancelMode) {}
+
         public void setSupportedStates(int[] states) {
             mSupportedStates = states;
             notifyDeviceStateInfoChanged();
diff --git a/core/tests/fuzzers/FuzzService/Android.bp b/core/tests/fuzzers/FuzzService/Android.bp
new file mode 100644
index 0000000..5093185
--- /dev/null
+++ b/core/tests/fuzzers/FuzzService/Android.bp
@@ -0,0 +1,28 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library {
+    name: "random_parcel_lib",
+    srcs: ["FuzzBinder.java"],
+}
+
+cc_library_shared {
+    name: "librandom_parcel_jni",
+    defaults: ["service_fuzzer_defaults"],
+    srcs: [
+        "random_parcel_jni.cpp",
+    ],
+    shared_libs: [
+        "libandroid_runtime",
+        "libbase",
+        "liblog",
+    ],
+    static_libs: [
+        "libnativehelper_lazy",
+        "libbinder_random_parcel",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+}
diff --git a/core/tests/fuzzers/FuzzService/FuzzBinder.java b/core/tests/fuzzers/FuzzService/FuzzBinder.java
new file mode 100644
index 0000000..7fd199a
--- /dev/null
+++ b/core/tests/fuzzers/FuzzService/FuzzBinder.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 randomparcel;
+import android.os.IBinder;
+import android.os.Parcel;
+
+public class FuzzBinder {
+    static {
+        System.loadLibrary("random_parcel_jni");
+    }
+
+    // DO NOT REUSE: This API should be called from fuzzer to setup JNI dependencies from
+    // libandroid_runtime. THIS IS WORKAROUND. Please file a bug if you need to use this.
+    public static void init() {
+        System.loadLibrary("android_runtime");
+        registerNatives();
+    }
+
+    // This API automatically fuzzes provided service
+    public static void fuzzService(IBinder binder, byte[] data) {
+        fuzzServiceInternal(binder, data);
+    }
+
+    // This API fills parcel object
+    public static void fillRandomParcel(Parcel parcel, byte[] data) {
+        fillParcelInternal(parcel, data);
+    }
+
+    private static native void fuzzServiceInternal(IBinder binder, byte[] data);
+    private static native void fillParcelInternal(Parcel parcel, byte[] data);
+    private static native int registerNatives();
+}
diff --git a/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp b/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp
new file mode 100644
index 0000000..264aa5f
--- /dev/null
+++ b/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp
@@ -0,0 +1,51 @@
+/*
+ * 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 "random_parcel_jni.h"
+#include <android_util_Binder.h>
+#include <android_os_Parcel.h>
+#include <fuzzbinder/libbinder_driver.h>
+#include <fuzzbinder/random_parcel.h>
+#include <fuzzer/FuzzedDataProvider.h>
+using namespace android;
+
+// JNI interface for fuzzService
+JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fuzzServiceInternal(JNIEnv *env, jobject thiz, jobject javaBinder, jbyteArray fuzzData) {
+    size_t len = static_cast<size_t>(env->GetArrayLength(fuzzData));
+    uint8_t data[len];
+    env->GetByteArrayRegion(fuzzData, 0, len, reinterpret_cast<jbyte*>(data));
+
+    FuzzedDataProvider provider(data, len);
+    sp<IBinder> binder = android::ibinderForJavaObject(env, javaBinder);
+    fuzzService(binder, std::move(provider));
+}
+
+// API used by AIDL fuzzers to access JNI functions from libandroid_runtime.
+JNIEXPORT jint JNICALL Java_randomparcel_FuzzBinder_registerNatives(JNIEnv* env) {
+    return registerFrameworkNatives(env);
+}
+
+JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fillParcelInternal(JNIEnv *env, jobject thiz, jobject jparcel, jbyteArray fuzzData) {
+    size_t len = static_cast<size_t>(env->GetArrayLength(fuzzData));
+    uint8_t data[len];
+    env->GetByteArrayRegion(fuzzData, 0, len, reinterpret_cast<jbyte*>(data));
+
+    FuzzedDataProvider provider(data, len);
+    RandomParcelOptions options;
+
+    Parcel* parcel = parcelForJavaObject(env, jparcel);
+    fillRandomParcel(parcel, std::move(provider), &options);
+}
diff --git a/core/tests/fuzzers/FuzzService/random_parcel_jni.h b/core/tests/fuzzers/FuzzService/random_parcel_jni.h
new file mode 100644
index 0000000..c96354a
--- /dev/null
+++ b/core/tests/fuzzers/FuzzService/random_parcel_jni.h
@@ -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.
+ */
+#include <jni.h>
+
+extern "C" {
+    JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fuzzServiceInternal(JNIEnv *env, jobject thiz, jobject javaBinder, jbyteArray fuzzData);
+
+    // Function to register libandroid_runtime JNI functions with java env.
+    JNIEXPORT jint JNICALL Java_randomparcel_FuzzBinder_registerNatives(JNIEnv* env);
+
+    // Function from AndroidRuntime
+    jint registerFrameworkNatives(JNIEnv* env);
+
+    JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fillParcelInternal(JNIEnv *env, jobject thiz, jobject parcel, jbyteArray fuzzData);
+}
diff --git a/core/tests/fuzzers/OWNERS b/core/tests/fuzzers/OWNERS
new file mode 100644
index 0000000..b972ac0
--- /dev/null
+++ b/core/tests/fuzzers/OWNERS
@@ -0,0 +1,2 @@
+smoreland@google.com
+waghpawan@google.com
diff --git a/core/tests/fuzzers/ParcelFuzzer/Android.bp b/core/tests/fuzzers/ParcelFuzzer/Android.bp
new file mode 100644
index 0000000..b71a06e
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/Android.bp
@@ -0,0 +1,40 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_fuzz {
+    name: "java_binder_parcel_fuzzer",
+    srcs: [
+        "ParcelFuzzer.java",
+        "ReadUtils.java",
+        "FuzzUtils.java",
+        "FuzzOperation.java",
+        "ReadOperation.java",
+        ":framework-core-sources-for-fuzzers",
+    ],
+    static_libs: [
+        "jazzer",
+        "random_parcel_lib",
+        "binderReadParcelIface-java",
+    ],
+    jni_libs: [
+        "librandom_parcel_jni",
+        "libc++",
+        "libandroid_runtime",
+    ],
+    libs: [
+        "framework",
+        "unsupportedappusage",
+        "ext",
+        "framework-res",
+    ],
+    native_bridge_supported: true,
+    fuzz_config: {
+        cc: [
+            "smoreland@google.com",
+            "waghpawan@google.com",
+        ],
+        // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
+        hotlists: ["4637097"],
+    },
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java b/core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java
new file mode 100644
index 0000000..033231d
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java
@@ -0,0 +1,22 @@
+/*
+ * 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 parcelfuzzer;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+public interface FuzzOperation {
+    void doFuzz(FuzzedDataProvider data);
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java b/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java
new file mode 100644
index 0000000..5e9e5ec
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java
@@ -0,0 +1,87 @@
+/*
+ * 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 parcelfuzzer;
+
+import android.os.Parcel;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+import randomparcel.FuzzBinder;
+
+public class FuzzUtils {
+    public static FuzzOperation[] FUZZ_OPERATIONS =
+            new FuzzOperation[] {
+                new FuzzOperation() {
+                    @java.lang.Override
+                    public void doFuzz(FuzzedDataProvider provider) {
+                        // Fuzz Append
+                        int start = provider.consumeInt();
+                        int len = provider.consumeInt();
+                        Parcel p1 = null;
+                        Parcel p2 = null;
+
+                        try {
+                            p1 = Parcel.obtain();
+                            p2 = Parcel.obtain();
+
+                            byte[] data =
+                                    provider.consumeBytes(
+                                            provider.consumeInt(0, provider.remainingBytes()));
+                            FuzzBinder.fillRandomParcel(p1, data);
+                            FuzzBinder.fillRandomParcel(p2, provider.consumeRemainingAsBytes());
+
+                            p1.appendFrom(p2, start, len);
+
+                        } catch (Exception e) {
+                            // Rethrow exception as runtime exceptions are catched
+                            // at highest level.
+                            throw e;
+                        } finally {
+                            p1.recycle();
+                            p2.recycle();
+                        }
+                    }
+                },
+                new FuzzOperation() {
+                    @java.lang.Override
+                    public void doFuzz(FuzzedDataProvider provider) {
+                        // Fuzz Read
+                        // Use maximum bytes to generate read instructions and remaining for parcel
+                        // creation
+                        int maxParcelBytes = provider.remainingBytes() / 3;
+                        byte[] data = provider.consumeBytes(maxParcelBytes);
+                        Parcel randomParcel = null;
+
+                        try {
+                            randomParcel = Parcel.obtain();
+                            FuzzBinder.fillRandomParcel(randomParcel, data);
+
+                            while (provider.remainingBytes() > 0) {
+                                provider.pickValue(ReadUtils.READ_OPERATIONS)
+                                        .readParcel(randomParcel, provider);
+                            }
+
+                        } catch (Exception e) {
+                            // Rethrow exception as runtime exceptions are catched
+                            // at highest level.
+                            throw e;
+                        } finally {
+                            randomParcel.recycle();
+                        }
+                    }
+                },
+            };
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java b/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java
new file mode 100644
index 0000000..688c812
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java
@@ -0,0 +1,42 @@
+/*
+ * 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 parcelfuzzer;
+
+import android.util.Log;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+import randomparcel.FuzzBinder;
+
+public class ParcelFuzzer {
+
+    static {
+        // Initialize JNI dependencies
+        FuzzBinder.init();
+    }
+
+    public static void fuzzerTestOneInput(FuzzedDataProvider provider) {
+        // Default behavior for Java APIs is to throw RuntimeException.
+        // We need to fuzz to detect other problems which are not handled explicitly.
+        // TODO(b/150808347): Change known API exceptions to subclass of
+        // RuntimeExceptions and catch those only.
+        try {
+            provider.pickValue(FuzzUtils.FUZZ_OPERATIONS).doFuzz(provider);
+        } catch (RuntimeException e) {
+            Log.e("ParcelFuzzer", "Exception occurred while fuzzing ", e);
+        }
+    }
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/ReadOperation.java b/core/tests/fuzzers/ParcelFuzzer/ReadOperation.java
new file mode 100644
index 0000000..5c227e3
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/ReadOperation.java
@@ -0,0 +1,24 @@
+/*
+ * 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 parcelfuzzer;
+
+import android.os.Parcel;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+public interface ReadOperation {
+    void readParcel(Parcel parcel, FuzzedDataProvider provider);
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java b/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java
new file mode 100644
index 0000000..0eff5f2
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java
@@ -0,0 +1,435 @@
+/*
+ * 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 parcelfuzzer;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import parcelables.EmptyParcelable;
+import parcelables.GenericDataParcelable;
+import parcelables.SingleDataParcelable;
+
+public class ReadUtils {
+    public static int MAX_LEN = 1000000;
+    public static int MIN_LEN = 0;
+
+    private static class SomeParcelable implements Parcelable {
+        private final int mValue;
+
+        private SomeParcelable(Parcel in) {
+            this.mValue = in.readInt();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(mValue);
+        }
+
+        public static Parcelable.Creator<SomeParcelable> CREATOR =
+                new Parcelable.Creator<SomeParcelable>() {
+
+                    @Override
+                    public SomeParcelable createFromParcel(Parcel source) {
+                        return new SomeParcelable(source);
+                    }
+
+                    @Override
+                    public SomeParcelable[] newArray(int size) {
+                        return new SomeParcelable[size];
+                    }
+                };
+    }
+
+    private static class TestClassLoader extends ClassLoader {
+        TestClassLoader() {
+            super();
+        }
+    }
+
+    private static class TestInterface implements IInterface {
+        public Binder binder;
+        private static final String DESCRIPTOR = "TestInterface";
+
+        TestInterface() {
+            binder = new Binder();
+            binder.attachInterface(this, DESCRIPTOR);
+        }
+
+        public IBinder asBinder() {
+            return binder;
+        }
+
+        public static TestInterface asInterface(IBinder binder) {
+            if (binder != null) {
+                IInterface iface = binder.queryLocalInterface(DESCRIPTOR);
+                if (iface != null && iface instanceof TestInterface) {
+                    return (TestInterface) iface;
+                }
+            }
+            return null;
+        }
+    }
+
+    public static ReadOperation[] READ_OPERATIONS =
+            new ReadOperation[] {
+                    (parcel, provider) -> {
+                        parcel.setDataPosition(provider.consumeInt());
+                    },
+                    (parcel, provider) -> {
+                        parcel.setDataCapacity(provider.consumeInt());
+                    },
+                    (parcel, provider) -> {
+                        parcel.setDataSize(provider.consumeInt());
+                    },
+                    (parcel, provider) -> {
+                        parcel.dataSize();
+                    },
+                    (parcel, provider) -> {
+                        parcel.dataPosition();
+                    },
+                    (parcel, provider) -> {
+                        parcel.dataCapacity();
+                    },
+
+                    // read basic types
+                    (parcel, provider) -> {
+                        parcel.readByte();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readBoolean();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readInt();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readLong();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readFloat();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readDouble();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readString();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readString8();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readString16();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readBlob();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readStrongBinder();
+                    },
+
+                    // read arrays of random length
+                    (parcel, provider) -> {
+                        byte[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new byte[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new byte[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readByteArray(array);
+                    },
+                    (parcel, provider) -> {
+                        char[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new char[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new char[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readCharArray(array);
+                    },
+                    (parcel, provider) -> {
+                        int[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new int[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new int[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readIntArray(array);
+                    },
+                    (parcel, provider) -> {
+                        double[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new double[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new double[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readDoubleArray(array);
+                    },
+                    (parcel, provider) -> {
+                        float[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new float[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new float[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readFloatArray(array);
+                    },
+                    (parcel, provider) -> {
+                        boolean[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new boolean[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new boolean[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readBooleanArray(array);
+                    },
+                    (parcel, provider) -> {
+                        long[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new long[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new long[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readLongArray(array);
+                    },
+                    (parcel, provider) -> {
+                        IBinder[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new IBinder[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new IBinder[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readBinderArray(array);
+                    },
+                    (parcel, provider) -> {
+                        ArrayList<IBinder> arrayList = new ArrayList<IBinder>();
+                        parcel.readBinderList(arrayList);
+                    },
+
+                    // unmarshall from random parcel data and random bytes
+                    (parcel, provider) -> {
+                        byte[] data = parcel.marshall();
+                        Parcel p = Parcel.obtain();
+                        p.unmarshall(data, provider.consumeInt(), provider.consumeInt());
+                        p.recycle();
+                    },
+                    (parcel, provider) -> {
+                        byte[] data = provider.consumeRemainingAsBytes();
+                        Parcel p = Parcel.obtain();
+                        p.unmarshall(data, provider.consumeInt(), provider.consumeInt());
+                        p.recycle();
+                    },
+                    (parcel, provider) -> {
+                        parcel.hasFileDescriptors(provider.consumeInt(), provider.consumeInt());
+                    },
+
+                    // read AIDL generated parcelables
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelable(loader, SingleDataParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader, SingleDataParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        SingleDataParcelable[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new SingleDataParcelable[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new SingleDataParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readTypedArray(array, SingleDataParcelable.CREATOR);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelable(loader, EmptyParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader, EmptyParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        EmptyParcelable[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new EmptyParcelable[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new EmptyParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readTypedArray(array, EmptyParcelable.CREATOR);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelable(loader, GenericDataParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader, GenericDataParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        GenericDataParcelable[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new GenericDataParcelable[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            int len = provider.consumeInt(MIN_LEN, MAX_LEN);
+                            array = new GenericDataParcelable[len];
+                        }
+                        parcel.readTypedArray(array, GenericDataParcelable.CREATOR);
+                    },
+
+                    // read parcelables
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelable(loader, SomeParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader, SomeParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        SomeParcelable[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new SomeParcelable[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new SomeParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readTypedArray(array, SomeParcelable.CREATOR);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader);
+                    },
+                    (parcel, provider) -> {
+                        parcel.hasFileDescriptors(provider.consumeInt(), provider.consumeInt());
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader);
+                    },
+
+                    // read lists
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readArrayList(loader);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readArrayList(loader, Object.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readArrayList(loader, SomeParcelable.class);
+                    },
+
+                    // read sparse arrays
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readSparseArray(loader);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readSparseArray(loader, Object.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readSparseArray(loader, SomeParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readSerializable(loader, Object.class);
+                    },
+
+                    // read interface
+                    (parcel, provider) -> {
+                        TestInterface[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new TestInterface[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new TestInterface[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readInterfaceArray(array, TestInterface::asInterface);
+                    },
+                    (parcel, provider) -> {
+                        int w = provider.consumeInt(MIN_LEN, MAX_LEN);
+                        int h = provider.consumeInt(MIN_LEN, MAX_LEN);
+                        TestInterface[][] array = new TestInterface[w][h];
+                        parcel.readFixedArray(array, TestInterface::asInterface);
+                    },
+                    (parcel, provider) -> {
+                        ArrayList<TestInterface> array = new ArrayList<TestInterface>();
+                        parcel.readInterfaceList(array, TestInterface::asInterface);
+                    },
+
+                    // read bundle
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readBundle(loader);
+                    },
+                    (parcel, provider) -> {
+                        parcel.readBundle();
+                    },
+
+                    // read HashMap
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readHashMap(loader);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readHashMap(loader, String.class, String.class);
+                    },
+                    (parcel, provider) -> {
+                        HashMap<String, String> hashMap = new HashMap<>();
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readMap(hashMap, loader, String.class, String.class);
+                    },
+            };
+}
diff --git a/core/tests/fuzzers/java_service_fuzzer/Android.bp b/core/tests/fuzzers/java_service_fuzzer/Android.bp
new file mode 100644
index 0000000..6acb198
--- /dev/null
+++ b/core/tests/fuzzers/java_service_fuzzer/Android.bp
@@ -0,0 +1,48 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+aidl_interface {
+    name: "fuzzTestInterface",
+    srcs: ["fuzztest/ITestService.aidl"],
+    unstable: true,
+    backend: {
+        java: {
+            enabled: true,
+        },
+    },
+}
+
+java_fuzz {
+    name: "java_binder_service_fuzzer",
+    srcs: [
+        "ServiceFuzzer.java",
+        "TestService.java",
+        ":framework-core-sources-for-fuzzers",
+    ],
+    static_libs: [
+        "jazzer",
+        "fuzzTestInterface-java",
+        "random_parcel_lib",
+    ],
+    jni_libs: [
+        "librandom_parcel_jni",
+        "libc++",
+        "libandroid_runtime",
+    ],
+    libs: [
+        "framework",
+        "unsupportedappusage",
+        "ext",
+        "framework-res",
+    ],
+    native_bridge_supported: true,
+    fuzz_config: {
+        cc: [
+            "smoreland@google.com",
+            "waghpawan@google.com",
+        ],
+        // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
+        hotlists: ["4637097"],
+    },
+}
diff --git a/core/tests/fuzzers/java_service_fuzzer/ServiceFuzzer.java b/core/tests/fuzzers/java_service_fuzzer/ServiceFuzzer.java
new file mode 100644
index 0000000..a6e0986
--- /dev/null
+++ b/core/tests/fuzzers/java_service_fuzzer/ServiceFuzzer.java
@@ -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.
+ */
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+import randomparcel.FuzzBinder;
+
+public class ServiceFuzzer {
+
+    static {
+        // Initialize fuzzService and JNI dependencies
+        FuzzBinder.init();
+    }
+
+    public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+        TestService service = new TestService();
+        FuzzBinder.fuzzService(service, data.consumeRemainingAsBytes());
+    }
+}
diff --git a/core/tests/fuzzers/java_service_fuzzer/TestService.java b/core/tests/fuzzers/java_service_fuzzer/TestService.java
new file mode 100644
index 0000000..4404386
--- /dev/null
+++ b/core/tests/fuzzers/java_service_fuzzer/TestService.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+import fuzztest.ITestService;
+
+public class TestService extends ITestService.Stub {
+
+    @Override
+    public boolean repeatData(boolean token) {
+        return token;
+    }
+}
diff --git a/core/tests/fuzzers/java_service_fuzzer/fuzztest/ITestService.aidl b/core/tests/fuzzers/java_service_fuzzer/fuzztest/ITestService.aidl
new file mode 100644
index 0000000..b766c9f
--- /dev/null
+++ b/core/tests/fuzzers/java_service_fuzzer/fuzztest/ITestService.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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 fuzztest;
+
+interface ITestService {
+    boolean repeatData(boolean token);
+}
\ No newline at end of file
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java
index 41b8956f..a226325 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java
@@ -31,6 +31,7 @@
         assertEquals(3366, getActivity().getValue());
     }
 
+    @SuppressWarnings("ReturnValueIgnored")
     public void testAnnotation() throws Exception {
         assertEquals(ReferencedByAnnotation.B,
                 ((AnnotationWithEnum) TestApplication.annotation).value());
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
index 0c939ec..9ccf3b3 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -27,6 +27,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertArrayEquals;
+
 import android.app.WindowConfiguration;
 import android.content.Context;
 import android.content.res.Resources;
@@ -399,6 +401,15 @@
         verifyRealMetricsMatchesBounds(display, sDeviceBoundsLandscape);
     }
 
+    @Test
+    public void testSupportedHdrTypesForDisplayModeAreSorted() {
+        int[] nonSortedHdrTypes = new int[]{3, 2, 1};
+        Display.Mode displayMode = new Display.Mode(0, 0, 0, 0, new float[0], nonSortedHdrTypes);
+
+        int[] sortedHdrTypes = new int[]{1, 2, 3};
+        assertArrayEquals(sortedHdrTypes, displayMode.getSupportedHdrTypes());
+    }
+
     // Given rotated display dimensions, calculate the letterboxed app bounds.
     private static Rect buildAppBounds(int displayWidth, int displayHeight) {
         final int midWidth = displayWidth / 2;
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
index 3465989..2da9a2e 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertThrows;
@@ -45,7 +44,6 @@
 import org.junit.runners.JUnit4;
 
 import java.util.Collections;
-import java.util.List;
 import java.util.concurrent.TimeoutException;
 
 @RunWith(JUnit4.class)
@@ -221,11 +219,56 @@
     }
 
     @Test
+    public void setResourceValue_withNullResourceName() throws Exception {
+        final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+        assertThrows(NullPointerException.class,
+                () -> builder.setResourceValue(null, TypedValue.TYPE_INT_DEC, 1));
+    }
+
+    @Test
+    public void setResourceValue_withEmptyResourceName() throws Exception {
+        final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.setResourceValue("", TypedValue.TYPE_INT_DEC, 1));
+    }
+
+    @Test
+    public void setResourceValue_withEmptyPackageName() throws Exception {
+        final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.setResourceValue(":color/mycolor", TypedValue.TYPE_INT_DEC, 1));
+    }
+
+    @Test
+    public void setResourceValue_withInvalidTypeName() throws Exception {
+        final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.setResourceValue("c/mycolor", TypedValue.TYPE_INT_DEC, 1));
+    }
+
+    @Test
+    public void setResourceValue_withEmptyTypeName() throws Exception {
+        final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.setResourceValue("/mycolor", TypedValue.TYPE_INT_DEC, 1));
+    }
+
+    @Test
     public void testInvalidResourceValues() throws Exception {
         final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
                 "android", TEST_OVERLAY_NAME, mContext.getPackageName())
                 .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
-                .setResourceValue("something", TypedValue.TYPE_INT_DEC, 1)
+                .setResourceValue("color/something", TypedValue.TYPE_INT_DEC, 1)
                 .build();
 
         waitForResourceValue(0);
diff --git a/core/tests/overlaytests/device_self_targeting/Android.bp b/core/tests/overlaytests/device_self_targeting/Android.bp
new file mode 100644
index 0000000..063c569
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/Android.bp
@@ -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 {
+    // 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: "SelfTargetingOverlayDeviceTests",
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.runner",
+        "androidx.test.ext.junit",
+        "mockito-target-minus-junit4",
+        "truth-prebuilt",
+    ],
+
+    optimize: {
+        enabled: false,
+    },
+    test_suites: ["device-tests"],
+}
diff --git a/core/tests/overlaytests/device_self_targeting/AndroidManifest.xml b/core/tests/overlaytests/device_self_targeting/AndroidManifest.xml
new file mode 100644
index 0000000..c121bf2
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?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.overlaytest.self_targeting">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.overlaytest.self_targeting"
+        android:label="Self-Targeting resource overlay tests" />
+</manifest>
diff --git a/core/tests/overlaytests/device_self_targeting/res/drawable/mydrawable.webp b/core/tests/overlaytests/device_self_targeting/res/drawable/mydrawable.webp
new file mode 100644
index 0000000..aa7d642
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/res/drawable/mydrawable.webp
Binary files differ
diff --git a/core/tests/overlaytests/device_self_targeting/res/raw/overlay_drawable.webp b/core/tests/overlaytests/device_self_targeting/res/raw/overlay_drawable.webp
new file mode 100644
index 0000000..9126ae3
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/res/raw/overlay_drawable.webp
Binary files differ
diff --git a/core/tests/overlaytests/device_self_targeting/res/values/overlayable.xml b/core/tests/overlaytests/device_self_targeting/res/values/overlayable.xml
new file mode 100644
index 0000000..5cc214d
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/res/values/overlayable.xml
@@ -0,0 +1,84 @@
+<?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.
+  -->
+
+<resources>
+    <overlayable name="PublicOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="public">
+            <item type="color" name="public_overlayable_color" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="SignatureOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="signature">
+            <item type="color" name="mycolor" />
+            <item type="color" name="signature_overlayable_color" />
+            <item type="string" name="mystring" />
+            <item type="drawable" name="mydrawable" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="SystemAppOverlayable" actor="overlay://theme">
+        <!-- The app in system partition can overlay the below resources -->
+        <policy type="system">
+            <item type="color" name="system_app_overlayable_color" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="OdmOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="odm">
+            <item type="color" name="odm_overlayable_color" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="OemOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="oem">
+            <item type="color" name="oem_overlayable_color" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="VendorOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="vendor">
+            <item type="color" name="vendor_overlayable_color" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="ProductOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="product">
+            <item type="color" name="product_overlayable_color" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="ActorOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="actor">
+            <item type="color" name="actor_overlayable_color" />
+        </policy>
+    </overlayable>
+
+    <overlayable name="ConfigOverlayable" actor="overlay://theme">
+        <!-- The app with the same signature can overlay the below resources -->
+        <policy type="config_signature">
+            <item type="color" name="config_overlayable_color" />
+        </policy>
+    </overlayable>
+
+</resources>
diff --git a/core/tests/overlaytests/device_self_targeting/res/values/values.xml b/core/tests/overlaytests/device_self_targeting/res/values/values.xml
new file mode 100644
index 0000000..d82de97
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/res/values/values.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.
+  -->
+
+<resources>
+    <color name="mycolor">#ff112233</color>
+    <string name="mystring">hello</string>
+
+    <color name="public_overlayable_color">#000</color>
+    <color name="signature_overlayable_color">#000</color>
+    <color name="system_app_overlayable_color">#000</color>
+    <color name="odm_overlayable_color">#000</color>
+    <color name="oem_overlayable_color">#000</color>
+    <color name="vendor_overlayable_color">#000</color>
+    <color name="product_overlayable_color">#000</color>
+    <color name="actor_overlayable_color">#000</color>
+    <color name="config_overlayable_color">#000</color>
+</resources>
diff --git a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
new file mode 100644
index 0000000..40d0bef
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
@@ -0,0 +1,679 @@
+/*
+ * 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.overlaytest;
+
+import static android.content.Context.MODE_PRIVATE;
+import static android.content.pm.PackageManager.SIGNATURE_NO_MATCH;
+
+import static com.android.internal.content.om.OverlayManagerImpl.SELF_TARGET;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManagerTransaction;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.os.FabricatedOverlayInternal;
+import android.os.FabricatedOverlayInternalEntry;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Pair;
+import android.util.TypedValue;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.content.om.OverlayManagerImpl;
+import com.android.overlaytest.self_targeting.R;
+
+import com.google.common.truth.Expect;
+import com.google.common.truth.Truth;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This test class verify the interfaces of {@link
+ * com.android.internal.content.om.OverlayManagerImpl}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class OverlayManagerImplTest {
+    private static final String TAG = "OverlayManagerImplTest";
+
+    private static final String TARGET_COLOR_RES = "color/mycolor";
+    private static final String TARGET_STRING_RES = "string/mystring";
+    private static final String TARGET_DRAWABLE_RES = "drawable/mydrawable";
+    private static final String PUBLIC_OVERLAYABLE = "PublicOverlayable";
+    private static final String SIGNATURE_OVERLAYABLE = "SignatureOverlayable";
+    private static final String SYSTEM_APP_OVERLAYABLE = "SystemAppOverlayable";
+    private static final String ODM_OVERLAYABLE = "OdmOverlayable";
+    private static final String OEM_OVERLAYABLE = "OemOverlayable";
+    private static final String VENDOR_OVERLAYABLE = "VendorOverlayable";
+    private static final String PRODUCT_OVERLAYABLE = "ProductOverlayable";
+    private static final String ACTOR_OVERLAYABLE = "ActorOverlayable";
+    private static final String CONFIG_OVERLAYABLE = "ConfigOverlayable";
+
+    private Context mContext;
+    private OverlayManagerImpl mOverlayManagerImpl;
+    private String mOverlayName;
+
+    private PackageManager mMockPackageManager;
+    private ApplicationInfo mMockApplicationInfo;
+
+    @Rule public TestName mTestName = new TestName();
+
+    @Rule public Expect expect = Expect.create();
+
+    private void clearDir() throws IOException {
+        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        final Path basePath = context.getDir(SELF_TARGET, MODE_PRIVATE).toPath();
+        Files.walkFileTree(
+                basePath,
+                new SimpleFileVisitor<>() {
+                    @Override
+                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+                            throws IOException {
+                        if (!file.toFile().delete()) {
+                            Log.w(TAG, "Failed to delete file " + file);
+                        }
+                        return super.visitFile(file, attrs);
+                    }
+
+                    @Override
+                    public FileVisitResult postVisitDirectory(Path dir, IOException exc)
+                            throws IOException {
+                        if (!dir.toFile().delete()) {
+                            Log.w(TAG, "Failed to delete dir " + dir);
+                        }
+                        return super.postVisitDirectory(dir, exc);
+                    }
+                });
+    }
+
+    @Before
+    public void setUp() throws IOException {
+        clearDir();
+        mOverlayName = mTestName.getMethodName();
+        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        mMockApplicationInfo = mock(ApplicationInfo.class);
+        when(mMockApplicationInfo.isSystemApp()).thenReturn(false);
+        when(mMockApplicationInfo.isSystemExt()).thenReturn(false);
+        when(mMockApplicationInfo.isOdm()).thenReturn(false);
+        when(mMockApplicationInfo.isOem()).thenReturn(false);
+        when(mMockApplicationInfo.isVendor()).thenReturn(false);
+        when(mMockApplicationInfo.isProduct()).thenReturn(false);
+        when(mMockApplicationInfo.getBaseCodePath()).thenReturn(
+                context.getApplicationInfo().getBaseCodePath());
+        mMockApplicationInfo.sourceDir = context.getApplicationInfo().sourceDir;
+
+        mMockPackageManager = mock(PackageManager.class);
+        when(mMockPackageManager.checkSignatures(anyString(), anyString()))
+                .thenReturn(SIGNATURE_NO_MATCH);
+
+        mContext =
+                new ContextWrapper(context) {
+                    @Override
+                    public ApplicationInfo getApplicationInfo() {
+                        return mMockApplicationInfo;
+                    }
+
+                    @Override
+                    public PackageManager getPackageManager() {
+                        return mMockPackageManager;
+                    }
+                };
+
+        mOverlayManagerImpl = new OverlayManagerImpl(mContext);
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        clearDir();
+    }
+
+    private <T> void addOverlayEntry(
+            FabricatedOverlayInternal overlayInternal,
+            @NonNull List<Pair<String, Pair<String, T>>> entryDefinitions) {
+        List<FabricatedOverlayInternalEntry> entries = new ArrayList<>();
+        for (Pair<String, Pair<String, T>> entryDefinition : entryDefinitions) {
+            FabricatedOverlayInternalEntry internalEntry = new FabricatedOverlayInternalEntry();
+            internalEntry.resourceName = entryDefinition.first;
+            internalEntry.configuration = entryDefinition.second.first;
+            if (entryDefinition.second.second instanceof ParcelFileDescriptor) {
+                internalEntry.binaryData = (ParcelFileDescriptor) entryDefinition.second.second;
+            } else if (entryDefinition.second.second instanceof String) {
+                internalEntry.stringData = (String) entryDefinition.second.second;
+                internalEntry.dataType = TypedValue.TYPE_STRING;
+            } else {
+                internalEntry.data = (int) entryDefinition.second.second;
+                internalEntry.dataType = TypedValue.TYPE_INT_COLOR_ARGB8;
+            }
+            entries.add(internalEntry);
+            overlayInternal.entries = entries;
+        }
+    }
+
+    private <T> FabricatedOverlayInternal createOverlayWithName(
+            @NonNull String overlayName,
+            @NonNull String targetOverlayable,
+            @NonNull String targetPackageName,
+            @NonNull List<Pair<String, Pair<String, T>>> entryDefinitions) {
+        final String packageName = mContext.getPackageName();
+        FabricatedOverlayInternal overlayInternal = new FabricatedOverlayInternal();
+        overlayInternal.overlayName = overlayName;
+        overlayInternal.targetPackageName = targetPackageName;
+        overlayInternal.targetOverlayable = targetOverlayable;
+        overlayInternal.packageName = packageName;
+
+        addOverlayEntry(overlayInternal, entryDefinitions);
+
+        return overlayInternal;
+    }
+
+    @Test
+    public void registerOverlay_forAndroidPackage_shouldFail() {
+        FabricatedOverlayInternal overlayInternal =
+                createOverlayWithName(
+                        mOverlayName,
+                        SYSTEM_APP_OVERLAYABLE,
+                        "android",
+                        List.of(Pair.create("color/white", Pair.create(null, Color.BLACK))));
+
+        assertThrows(
+                "Wrong target package name",
+                IllegalArgumentException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void getOverlayInfosForTarget_defaultShouldBeZero() {
+        List<OverlayInfo> overlayInfos =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
+
+        Truth.assertThat(overlayInfos.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void unregisterNonExistingOverlay_shouldBeOk() {
+        mOverlayManagerImpl.unregisterFabricatedOverlay("NotExisting");
+    }
+
+    @Test
+    public void registerOverlay_createColorOverlay_shouldBeSavedInAndLoadFromFile()
+            throws IOException, PackageManager.NameNotFoundException {
+        FabricatedOverlayInternal overlayInternal =
+                createOverlayWithName(
+                        mOverlayName,
+                        SIGNATURE_OVERLAYABLE,
+                        mContext.getPackageName(),
+                        List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+        final List<OverlayInfo> overlayInfos =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
+
+        final int firstNumberOfOverlays = overlayInfos.size();
+        expect.that(firstNumberOfOverlays).isEqualTo(1);
+        final OverlayInfo overlayInfo = overlayInfos.get(0);
+        expect.that(overlayInfo).isNotNull();
+        Truth.assertThat(expect.hasFailures()).isFalse();
+        expect.that(overlayInfo.isFabricated()).isTrue();
+        expect.that(overlayInfo.getOverlayName()).isEqualTo(mOverlayName);
+        expect.that(overlayInfo.getPackageName()).isEqualTo(mContext.getPackageName());
+        expect.that(overlayInfo.getTargetPackageName()).isEqualTo(mContext.getPackageName());
+        expect.that(overlayInfo.getUserId()).isEqualTo(mContext.getUserId());
+    }
+
+    @Test
+    public void registerOverlay_createStringOverlay_shouldBeSavedInAndLoadFromFile()
+            throws IOException, PackageManager.NameNotFoundException {
+        FabricatedOverlayInternal overlayInternal =
+                createOverlayWithName(
+                        mOverlayName,
+                        SIGNATURE_OVERLAYABLE,
+                        mContext.getPackageName(),
+                        List.of(Pair.create(TARGET_STRING_RES, Pair.create(null, "HELLO"))));
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+        final List<OverlayInfo> overlayInfos =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
+
+        final int firstNumberOfOverlays = overlayInfos.size();
+        expect.that(firstNumberOfOverlays).isEqualTo(1);
+        final OverlayInfo overlayInfo = overlayInfos.get(0);
+        expect.that(overlayInfo).isNotNull();
+        Truth.assertThat(expect.hasFailures()).isFalse();
+        expect.that(overlayInfo.isFabricated()).isTrue();
+        expect.that(overlayInfo.getOverlayName()).isEqualTo(mOverlayName);
+        expect.that(overlayInfo.getPackageName()).isEqualTo(mContext.getPackageName());
+        expect.that(overlayInfo.getTargetPackageName()).isEqualTo(mContext.getPackageName());
+        expect.that(overlayInfo.getUserId()).isEqualTo(mContext.getUserId());
+    }
+
+    @Test
+    public void registerOverlay_createFileOverlay_shouldBeSavedInAndLoadFromFile()
+            throws IOException, PackageManager.NameNotFoundException {
+        ParcelFileDescriptor parcelFileDescriptor = mContext.getResources()
+                .openRawResourceFd(R.raw.overlay_drawable).getParcelFileDescriptor();
+        FabricatedOverlayInternal overlayInternal =
+                createOverlayWithName(
+                        mOverlayName,
+                        SIGNATURE_OVERLAYABLE,
+                        mContext.getPackageName(),
+                        List.of(Pair.create(TARGET_DRAWABLE_RES,
+                                            Pair.create(null, parcelFileDescriptor))));
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+        final List<OverlayInfo> overlayInfos =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
+
+        final int firstNumberOfOverlays = overlayInfos.size();
+        expect.that(firstNumberOfOverlays).isEqualTo(1);
+        final OverlayInfo overlayInfo = overlayInfos.get(0);
+        expect.that(overlayInfo).isNotNull();
+        Truth.assertThat(expect.hasFailures()).isFalse();
+        expect.that(overlayInfo.isFabricated()).isTrue();
+        expect.that(overlayInfo.getOverlayName()).isEqualTo(mOverlayName);
+        expect.that(overlayInfo.getPackageName()).isEqualTo(mContext.getPackageName());
+        expect.that(overlayInfo.getTargetPackageName()).isEqualTo(mContext.getPackageName());
+        expect.that(overlayInfo.getUserId()).isEqualTo(mContext.getUserId());
+    }
+
+    @Test
+    public void registerOverlay_notExistedResource_shouldFailWithoutSavingAnyFile()
+            throws IOException {
+        FabricatedOverlayInternal overlayInternal =
+                createOverlayWithName(
+                        mOverlayName,
+                        SIGNATURE_OVERLAYABLE,
+                        mContext.getPackageName(),
+                        List.of(Pair.create("color/not_existed", Pair.create(null, "HELLO"))));
+
+        assertThrows(IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+        final List<OverlayInfo> overlayInfos =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
+        final int firstNumberOfOverlays = overlayInfos.size();
+        expect.that(firstNumberOfOverlays).isEqualTo(0);
+        final int[] fileCounts = new int[1];
+        Files.walkFileTree(
+                mContext.getDir(SELF_TARGET, MODE_PRIVATE).toPath(),
+                new SimpleFileVisitor<>() {
+                    @Override
+                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+                            throws IOException {
+                        fileCounts[0]++;
+                        return super.visitFile(file, attrs);
+                    }
+                });
+        expect.that(fileCounts[0]).isEqualTo(0);
+    }
+
+    @Test
+    public void registerMultipleOverlays_shouldMatchTheNumberOfOverlays()
+            throws IOException, PackageManager.NameNotFoundException {
+        final String secondOverlayName = mOverlayName + "2nd";
+        final int initNumberOfOverlays =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
+
+        FabricatedOverlayInternal overlayInternal =
+                createOverlayWithName(
+                        mOverlayName,
+                        SIGNATURE_OVERLAYABLE,
+                        mContext.getPackageName(),
+                        List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+        final int firstNumberOfOverlays =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
+        overlayInternal =
+                createOverlayWithName(
+                        secondOverlayName,
+                        SIGNATURE_OVERLAYABLE,
+                        mContext.getPackageName(),
+                        List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+        final int secondNumberOfOverlays =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
+        mOverlayManagerImpl.unregisterFabricatedOverlay(mOverlayName);
+        mOverlayManagerImpl.unregisterFabricatedOverlay(secondOverlayName);
+        final int finalNumberOfOverlays =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
+
+        expect.that(initNumberOfOverlays).isEqualTo(0);
+        expect.that(firstNumberOfOverlays).isEqualTo(1);
+        expect.that(secondNumberOfOverlays).isEqualTo(2);
+        expect.that(finalNumberOfOverlays).isEqualTo(0);
+    }
+
+    @Test
+    public void unregisterOverlay_withIllegalOverlayName_shouldFail() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mOverlayManagerImpl.unregisterFabricatedOverlay("../../etc/password"));
+    }
+
+    @Test
+    public void registerTheSameOverlay_shouldNotIncreaseTheNumberOfOverlays()
+            throws IOException, PackageManager.NameNotFoundException {
+        final int initNumberOfOverlays =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
+
+        FabricatedOverlayInternal overlayInternal =
+                createOverlayWithName(
+                        mOverlayName,
+                        SIGNATURE_OVERLAYABLE,
+                        mContext.getPackageName(),
+                        List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+        final int firstNumberOfOverlays =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
+        overlayInternal =
+                createOverlayWithName(
+                        mOverlayName,
+                        SIGNATURE_OVERLAYABLE,
+                        mContext.getPackageName(),
+                        List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+        final int secondNumberOfOverlays =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
+        mOverlayManagerImpl.unregisterFabricatedOverlay(mOverlayName);
+        final int finalNumberOfOverlays =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
+
+        expect.that(initNumberOfOverlays).isEqualTo(0);
+        expect.that(firstNumberOfOverlays).isEqualTo(1);
+        expect.that(secondNumberOfOverlays).isEqualTo(1);
+        expect.that(finalNumberOfOverlays).isEqualTo(0);
+    }
+
+    @Test
+    public void registerOverlay_packageNotOwnedBySelf_shouldFail() {
+        FabricatedOverlayInternal overlayInternal = new FabricatedOverlayInternal();
+        overlayInternal.packageName = "com.android.systemui";
+        overlayInternal.overlayName = mOverlayName;
+        overlayInternal.targetOverlayable = "non-existed-target-overlayable";
+        overlayInternal.targetPackageName = mContext.getPackageName();
+        addOverlayEntry(
+                overlayInternal,
+                List.of(Pair.create("color/white", Pair.create(null, Color.BLACK))));
+
+        assertThrows(
+                "The context doesn't own the package",
+                IllegalArgumentException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void ensureBaseDir_forOtherPackage_shouldFail()
+            throws PackageManager.NameNotFoundException {
+        final Context fakeContext =
+                mContext.createPackageContext("com.android.systemui", 0 /* flags */);
+        final OverlayManagerImpl overlayManagerImpl = new OverlayManagerImpl(fakeContext);
+
+        assertThrows(IllegalArgumentException.class, overlayManagerImpl::ensureBaseDir);
+    }
+
+    @Test
+    public void commit_withNullTransaction_shouldFail() {
+        assertThrows(NullPointerException.class, () -> mOverlayManagerImpl.commit(null));
+    }
+
+    @Test
+    public void commitRegisterOverlay_fromOtherBuilder_shouldWork()
+            throws PackageManager.NameNotFoundException, IOException {
+        FabricatedOverlay overlay =
+                new FabricatedOverlay.Builder(
+                                mContext.getPackageName(), mOverlayName, mContext.getPackageName())
+                        .setTargetOverlayable(SIGNATURE_OVERLAYABLE)
+                        .setResourceValue(
+                                TARGET_COLOR_RES, TypedValue.TYPE_INT_COLOR_ARGB8, Color.WHITE)
+                        .build();
+        OverlayManagerTransaction transaction =
+                new OverlayManagerTransaction.Builder().registerFabricatedOverlay(overlay).build();
+
+        mOverlayManagerImpl.commit(transaction);
+
+        final List<OverlayInfo> overlayInfos =
+                mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
+        final int firstNumberOfOverlays = overlayInfos.size();
+        expect.that(firstNumberOfOverlays).isEqualTo(1);
+        final OverlayInfo overlayInfo = overlayInfos.get(0);
+        expect.that(overlayInfo).isNotNull();
+        Truth.assertThat(expect.hasFailures()).isFalse();
+        expect.that(overlayInfo.isFabricated()).isTrue();
+        expect.that(overlayInfo.getOverlayName()).isEqualTo(mOverlayName);
+        expect.that(overlayInfo.getPackageName()).isEqualTo(mContext.getPackageName());
+        expect.that(overlayInfo.getTargetPackageName()).isEqualTo(mContext.getPackageName());
+        expect.that(overlayInfo.getUserId()).isEqualTo(mContext.getUserId());
+    }
+
+    @Test
+    public void newOverlayManagerImpl_forOtherUser_shouldFail() {
+        Context fakeContext =
+                new ContextWrapper(mContext) {
+                    @Override
+                    public UserHandle getUser() {
+                        return UserHandle.of(100);
+                    }
+
+                    @Override
+                    public int getUserId() {
+                        return 100;
+                    }
+                };
+
+        assertThrows(SecurityException.class, () -> new OverlayManagerImpl(fakeContext));
+    }
+
+    FabricatedOverlayInternal prepareFabricatedOverlayInternal(
+            String targetOverlayableName, String targetEntryName) {
+        return createOverlayWithName(
+                mOverlayName,
+                targetOverlayableName,
+                mContext.getPackageName(),
+                List.of(
+                        Pair.create(
+                                targetEntryName,
+                                Pair.create(null, Color.WHITE))));
+    }
+
+    @Test
+    public void registerOverlayOnSystemOverlayable_selfIsNotSystemApp_shouldFail() {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                SYSTEM_APP_OVERLAYABLE,
+                "color/system_app_overlayable_color");
+
+        assertThrows(
+                IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void registerOverlayOnOdmOverlayable_selfIsNotOdm_shouldFail() {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                ODM_OVERLAYABLE,
+                "color/odm_overlayable_color");
+
+        assertThrows(
+                IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void registerOverlayOnOemOverlayable_selfIsNotOem_shouldFail() {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                OEM_OVERLAYABLE,
+                "color/oem_overlayable_color");
+
+        assertThrows(
+                IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void registerOverlayOnVendorOverlayable_selfIsNotVendor_shouldFail() {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                VENDOR_OVERLAYABLE,
+                "color/vendor_overlayable_color");
+
+        assertThrows(
+                IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void registerOverlayOnProductOverlayable_selfIsNotProduct_shouldFail() {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                PRODUCT_OVERLAYABLE,
+                "color/product_overlayable_color");
+
+        assertThrows(
+                IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void registerOverlayOnActorOverlayable_notSupport_shouldFail() {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                ACTOR_OVERLAYABLE,
+                "color/actor_overlayable_color");
+
+        assertThrows(
+                IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void registerOverlayOnConfigOverlayable_notSupport_shouldFail() {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                CONFIG_OVERLAYABLE,
+                "color/config_overlayable_color");
+
+        assertThrows(
+                IOException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
+    public void registerOverlayOnPublicOverlayable_shouldAlwaysSucceed()
+            throws PackageManager.NameNotFoundException, IOException {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                PUBLIC_OVERLAYABLE,
+                "color/public_overlayable_color");
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+        assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+                .isEqualTo(1);
+    }
+
+    @Test
+    public void registerOverlayOnSystemOverlayable_selfIsSystemApp_shouldSucceed()
+            throws PackageManager.NameNotFoundException, IOException {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                SYSTEM_APP_OVERLAYABLE,
+                "color/system_app_overlayable_color");
+        when(mMockApplicationInfo.isSystemApp()).thenReturn(true);
+        when(mMockApplicationInfo.isSystemExt()).thenReturn(true);
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+        assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+                .isEqualTo(1);
+    }
+
+    @Test
+    public void registerOverlayOnOdmOverlayable_selfIsOdm_shouldSucceed()
+            throws PackageManager.NameNotFoundException, IOException {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                ODM_OVERLAYABLE,
+                "color/odm_overlayable_color");
+        when(mMockApplicationInfo.isOdm()).thenReturn(true);
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+        assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+                .isEqualTo(1);
+    }
+
+    @Test
+    public void registerOverlayOnOemOverlayable_selfIsOem_shouldSucceed()
+            throws PackageManager.NameNotFoundException, IOException {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                OEM_OVERLAYABLE,
+                "color/oem_overlayable_color");
+        when(mMockApplicationInfo.isOem()).thenReturn(true);
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+        assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+                .isEqualTo(1);
+    }
+
+    @Test
+    public void registerOverlayOnVendorOverlayable_selfIsVendor_shouldSucceed()
+            throws PackageManager.NameNotFoundException, IOException {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                VENDOR_OVERLAYABLE,
+                "color/vendor_overlayable_color");
+        when(mMockApplicationInfo.isVendor()).thenReturn(true);
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+        assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+                .isEqualTo(1);
+    }
+
+    @Test
+    public void registerOverlayOnProductOverlayable_selfIsProduct_shouldSucceed()
+            throws PackageManager.NameNotFoundException, IOException {
+        final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+                PRODUCT_OVERLAYABLE,
+                "color/product_overlayable_color");
+        when(mMockApplicationInfo.isProduct()).thenReturn(true);
+
+        mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+        assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+                .isEqualTo(1);
+    }
+}
diff --git a/core/tests/utiltests/src/android/util/IntArrayTest.java b/core/tests/utiltests/src/android/util/IntArrayTest.java
index a76c640..caa7312 100644
--- a/core/tests/utiltests/src/android/util/IntArrayTest.java
+++ b/core/tests/utiltests/src/android/util/IntArrayTest.java
@@ -16,8 +16,8 @@
 
 package android.util;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -25,6 +25,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class IntArrayTest {
@@ -35,51 +37,65 @@
         a.add(1);
         a.add(2);
         a.add(3);
-        verify(new int[]{1, 2, 3}, a);
+        verify(a, 1, 2, 3);
 
         IntArray b = IntArray.fromArray(new int[]{4, 5, 6, 7, 8}, 3);
         a.addAll(b);
-        verify(new int[]{1, 2, 3, 4, 5, 6}, a);
+        verify(a, 1, 2, 3, 4, 5, 6);
 
         a.resize(2);
-        verify(new int[]{1, 2}, a);
+        verify(a, 1, 2);
 
         a.resize(8);
-        verify(new int[]{1, 2, 0, 0, 0, 0, 0, 0}, a);
+        verify(a, 1, 2, 0, 0, 0, 0, 0, 0);
 
         a.set(5, 10);
-        verify(new int[]{1, 2, 0, 0, 0, 10, 0, 0}, a);
+        verify(a, 1, 2, 0, 0, 0, 10, 0, 0);
 
         a.add(5, 20);
-        assertEquals(20, a.get(5));
-        assertEquals(5, a.indexOf(20));
-        verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0}, a);
+        assertThat(a.get(5)).isEqualTo(20);
+        assertThat(a.indexOf(20)).isEqualTo(5);
+        verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0);
 
-        assertEquals(-1, a.indexOf(99));
+        assertThat(a.indexOf(99)).isEqualTo(-1);
 
         a.resize(15);
         a.set(14, 30);
-        verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30}, a);
+        verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30);
 
         int[] backingArray = new int[]{1, 2, 3, 4};
         a = IntArray.wrap(backingArray);
         a.set(0, 10);
-        assertEquals(10, backingArray[0]);
+        assertThat(backingArray[0]).isEqualTo(10);
         backingArray[1] = 20;
         backingArray[2] = 30;
-        verify(backingArray, a);
-        assertEquals(2, a.indexOf(30));
+        verify(a, backingArray);
+        assertThat(a.indexOf(30)).isEqualTo(2);
 
         a.resize(2);
-        assertEquals(0, backingArray[2]);
-        assertEquals(0, backingArray[3]);
+        assertThat(backingArray[2]).isEqualTo(0);
+        assertThat(backingArray[3]).isEqualTo(0);
 
         a.add(50);
-        verify(new int[]{10, 20, 50}, a);
+        verify(a, 10, 20, 50);
     }
 
-    public void verify(int[] expected, IntArray intArray) {
-        assertEquals(expected.length, intArray.size());
-        assertArrayEquals(expected, intArray.toArray());
+    @Test
+    public void testToString() {
+        IntArray a = new IntArray(10);
+        a.add(4);
+        a.add(8);
+        a.add(15);
+        a.add(16);
+        a.add(23);
+        a.add(42);
+
+        assertWithMessage("toString()").that(a.toString()).contains("4, 8, 15, 16, 23, 42");
+        assertWithMessage("toString()").that(a.toString()).doesNotContain("0");
+    }
+
+    public void verify(IntArray intArray, int... expected) {
+        assertWithMessage("contents of %s", intArray).that(intArray.toArray()).asList()
+                .containsExactlyElementsIn(Arrays.stream(expected).boxed().toList());
     }
 }
diff --git a/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java b/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java
index c53f4cc..1581abb 100644
--- a/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java
@@ -20,6 +20,7 @@
 import org.junit.Test;
 
 import java.util.ArrayList;
+import java.util.Objects;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -39,11 +40,11 @@
     Integer argValue;
 
     private void addNotifyCount(Integer callback) {
-        if (callback == callback1) {
+        if (Objects.equals(callback, callback1)) {
             notify1++;
-        } else if (callback == callback2) {
+        } else if (Objects.equals(callback, callback2)) {
             notify2++;
-        } else if (callback == callback3) {
+        } else if (Objects.equals(callback, callback3)) {
             notify3++;
         }
         deepNotifyCount[callback]++;
@@ -114,7 +115,7 @@
                     public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
                             int arg1, Integer arg) {
                         addNotifyCount(callback);
-                        if (callback == callback1) {
+                        if (Objects.equals(callback, callback1)) {
                             registry.remove(callback1);
                             registry.remove(callback2);
                         }
@@ -166,9 +167,9 @@
                     public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
                             int arg1, Integer arg) {
                         addNotifyCount(callback);
-                        if (callback == callback1) {
+                        if (Objects.equals(callback, callback1)) {
                             registry.remove(callback2);
-                        } else if (callback == callback3) {
+                        } else if (Objects.equals(callback, callback3)) {
                             registry.add(callback2);
                         }
                     }
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index df51871..d0c3e5f 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -171,12 +171,6 @@
     filename_from_src: true,
 }
 
-prebuilt_etc {
-    name: "com.android.timezone.updater.xml",
-    sub_dir: "permissions",
-    src: "com.android.timezone.updater.xml",
-}
-
 filegroup {
     name: "services.core.protolog.json",
     srcs: ["services.core.protolog.json"],
diff --git a/data/etc/com.android.timezone.updater.xml b/data/etc/com.android.timezone.updater.xml
deleted file mode 100644
index 60a66e2..0000000
--- a/data/etc/com.android.timezone.updater.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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
-  -->
-<permissions>
-    <privapp-permissions package="com.android.timezone.updater">
-        <permission name="android.permission.QUERY_TIME_ZONE_RULES" />
-        <permission name="android.permission.UPDATE_TIME_ZONE_RULES" />
-    </privapp-permissions>
-</permissions>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 9a1b8a9..6328b02 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -288,6 +288,15 @@
                       targetSdk="33">
         <new-permission name="android.permission.READ_MEDIA_IMAGES" />
     </split-permission>
+    <split-permission name="android.permission.READ_MEDIA_IMAGES">
+        <new-permission name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
+    </split-permission>
+    <split-permission name="android.permission.READ_MEDIA_VIDEO">
+        <new-permission name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
+    </split-permission>
+    <split-permission name="android.permission.ACCESS_MEDIA_LOCATION">
+        <new-permission name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
+    </split-permission>
 
     <!-- This is a list of all the libraries available for application
          code to link against. -->
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index decfb9f..3e2b71f 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -495,6 +495,10 @@
         <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
         <!-- Permission required for CTS test - CtsTelephonyTestCases -->
         <permission name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
+        <!-- Permission required for CTS test - CtsAppTestCases -->
+        <permission name="android.permission.CAPTURE_MEDIA_OUTPUT" />
+        <permission name="android.permission.CAPTURE_TUNER_AUDIO_INPUT" />
+        <permission name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index ca543f4..3346740 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1357,6 +1357,12 @@
       "group": "WM_DEBUG_ANIM",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-787664727": {
+      "message": "Cannot launch dream activity due to invalid state. dream component: %s packageName: %s",
+      "level": "ERROR",
+      "group": "WM_DEBUG_DREAM",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-784959154": {
       "message": "Attempted to add private presentation window to a non-private display.  Aborting.",
       "level": "WARN",
@@ -1951,6 +1957,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
+    "-240296576": {
+      "message": "handleAppTransitionReady: displayId=%d appTransition={%s} openingApps=[%s] closingApps=[%s] transit=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
     "-237664290": {
       "message": "Pause the recording session on display %s",
       "level": "VERBOSE",
@@ -2053,12 +2065,6 @@
       "group": "WM_DEBUG_CONTENT_RECORDING",
       "at": "com\/android\/server\/wm\/ContentRecorder.java"
     },
-    "-134793542": {
-      "message": "handleAppTransitionReady: displayId=%d appTransition={%s} excludeLauncherFromAnimation=%b openingApps=[%s] closingApps=[%s] transit=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_APP_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/AppTransitionController.java"
-    },
     "-134091882": {
       "message": "Screenshotting Activity %s",
       "level": "VERBOSE",
@@ -2173,12 +2179,6 @@
       "group": "WM_DEBUG_ANIM",
       "at": "com\/android\/server\/wm\/WindowContainer.java"
     },
-    "-23020844": {
-      "message": "Back: Reset surfaces",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_BACK_PREVIEW",
-      "at": "com\/android\/server\/wm\/BackNavigationController.java"
-    },
     "-21399771": {
       "message": "activity %s already destroying, skipping request with reason:%s",
       "level": "VERBOSE",
@@ -2599,6 +2599,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "323235828": {
+      "message": "Delaying app transition for recents animation to finish",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
     "327461496": {
       "message": "Complete pause: %s",
       "level": "VERBOSE",
@@ -2887,6 +2893,12 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "601283564": {
+      "message": "Dream packageName does not match active dream. Package %s does not match %s",
+      "level": "ERROR",
+      "group": "WM_DEBUG_DREAM",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "608694300": {
       "message": "  NEW SURFACE SESSION %s",
       "level": "INFO",
@@ -3127,12 +3139,6 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
     },
-    "829869827": {
-      "message": "Cannot launch dream activity due to invalid state. dreaming: %b packageName: %s",
-      "level": "ERROR",
-      "group": "WM_DEBUG_DREAM",
-      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
-    },
     "835814848": {
       "message": "%s",
       "level": "INFO",
@@ -3811,12 +3817,6 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "1544805551": {
-      "message": "Skipping app transition animation. task=%s",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_BACK_PREVIEW",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "1557732761": {
       "message": "For Intent %s bringing to top: %s",
       "level": "DEBUG",
@@ -4153,12 +4153,6 @@
       "group": "WM_DEBUG_WINDOW_ORGANIZER",
       "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
     },
-    "1918771553": {
-      "message": "Dream packageName does not match active dream. Package %s does not match %s or %s",
-      "level": "ERROR",
-      "group": "WM_DEBUG_DREAM",
-      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
-    },
     "1921821199": {
       "message": "Preserving %s until the new one is added",
       "level": "VERBOSE",
@@ -4177,6 +4171,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "1945495497": {
+      "message": "Focused window didn't have a valid surface drawn.",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_BACK_PREVIEW",
+      "at": "com\/android\/server\/wm\/BackNavigationController.java"
+    },
     "1947239194": {
       "message": "Deferring rotation, still finishing previous rotation",
       "level": "VERBOSE",
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index af96c74..a43e225 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -371,6 +371,11 @@
 # key 413 "KEY_DIGITS"
 # key 414 "KEY_TEEN"
 # key 415 "KEY_TWEN"
+# key 418 "KEY_ZOOM_IN"
+key 418   ZOOM_IN
+# key 419 "KEY_ZOOM_OUT"
+key 419   ZOOM_OUT
+key 528 FOCUS
 
 key 429   CONTACTS
 
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java
index 8706a68..42e3046 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java
@@ -168,6 +168,7 @@
      */
     private static final Matcher<ExpressionTree> CONVERT_PRIMITIVE_TO_STRING =
             new Matcher<ExpressionTree>() {
+        @SuppressWarnings("TreeToString") //TODO: Fix me
         @Override
         public boolean matches(ExpressionTree tree, VisitorState state) {
             if (PRIMITIVE_TO_STRING.matches(tree, state)) {
@@ -205,6 +206,7 @@
      */
     private static final Matcher<ExpressionTree> CONVERT_STRING_TO_PRIMITIVE =
             new Matcher<ExpressionTree>() {
+        @SuppressWarnings("TreeToString") //TODO: Fix me
         @Override
         public boolean matches(ExpressionTree tree, VisitorState state) {
             if (PRIMITIVE_PARSE.matches(tree, state)) {
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 54d6428..e62ac46 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -96,7 +96,7 @@
     // These are also implemented in RecordingCanvas so that we can
     // selectively apply on them
     // Everything below here is copy/pasted from Canvas.java
-    // The JNI registration is handled by android_view_Canvas.cpp
+    // The JNI registration is handled by android_graphics_Canvas.cpp
     // ---------------------------------------------------------------------------
 
     public void drawArc(float left, float top, float right, float bottom, float startAngle,
@@ -670,6 +670,17 @@
     /**
      * @hide
      */
+    public void drawMesh(Mesh mesh, BlendMode blendMode, Paint paint) {
+        if (!isHardwareAccelerated() && onHwFeatureInSwMode()) {
+            throw new RuntimeException("software rendering doesn't support meshes");
+        }
+        nDrawMesh(this.mNativeCanvasWrapper, mesh.getNativeWrapperInstance(),
+                blendMode.getXfermode().porterDuffMode, paint.getNativeInstance());
+    }
+
+    /**
+     * @hide
+     */
     public void punchHole(float left, float top, float right, float bottom, float rx, float ry,
             float alpha) {
         nPunchHole(mNativeCanvasWrapper, left, top, right, bottom, rx, ry, alpha);
@@ -801,6 +812,9 @@
             int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset,
             short[] indices, int indexOffset, int indexCount, long nativePaint);
 
+    private static native void nDrawMesh(
+            long nativeCanvas, long nativeMesh, int mode, long nativePaint);
+
     private static native void nDrawGlyphs(long nativeCanvas, int[] glyphIds, float[] positions,
             int glyphIdStart, int positionStart, int glyphCount, long nativeFont, long nativePaint);
 
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index 1ba79b8..eeff694 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -606,6 +606,12 @@
                 indices, indexOffset, indexCount, paint.getNativeInstance());
     }
 
+    @Override
+    public final void drawMesh(Mesh mesh, BlendMode blendMode, Paint paint) {
+        nDrawMesh(mNativeCanvasWrapper, mesh.getNativeWrapperInstance(),
+                blendMode.getXfermode().porterDuffMode, paint.getNativeInstance());
+    }
+
     /**
      * @hide
      */
@@ -708,6 +714,10 @@
             long nativePaint);
 
     @FastNative
+    private static native void nDrawMesh(
+            long canvasHandle, long nativeMesh, int mode, long nativePaint);
+
+    @FastNative
     private static native void nDrawVertices(long nativeCanvas, int mode, int n, float[] verts,
             int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset,
             short[] indices, int indexOffset, int indexCount, long nativePaint);
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index abf7e99..42c892a 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1667,6 +1667,9 @@
      * effectively treating them as zeros. In API level {@value Build.VERSION_CODES#P} and above
      * these parameters will be respected.
      *
+     * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+     * ignored.</p>
+     *
      * @param bitmap The bitmap to draw using the mesh
      * @param meshWidth The number of columns in the mesh. Nothing is drawn if this is 0
      * @param meshHeight The number of rows in the mesh. Nothing is drawn if this is 0
@@ -1678,7 +1681,7 @@
      *            null, there must be at least (meshWidth+1) * (meshHeight+1) + colorOffset values
      *            in the array.
      * @param colorOffset Number of color elements to skip before drawing
-     * @param paint May be null. The paint used to draw the bitmap
+     * @param paint May be null. The paint used to draw the bitmap. Antialiasing is not supported.
      */
     public void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
             @NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
@@ -1832,9 +1835,12 @@
     /**
      * Draws the specified bitmap as an N-patch (most often, a 9-patch.)
      *
+     * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+     * ignored.</p>
+     *
      * @param patch The ninepatch object to render
      * @param dst The destination rectangle.
-     * @param paint The paint to draw the bitmap with. may be null
+     * @param paint The paint to draw the bitmap with. May be null. Antialiasing is not supported.
      */
     public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) {
         super.drawPatch(patch, dst, paint);
@@ -1843,9 +1849,12 @@
     /**
      * Draws the specified bitmap as an N-patch (most often, a 9-patch.)
      *
+     * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+     * ignored.</p>
+     *
      * @param patch The ninepatch object to render
      * @param dst The destination rectangle.
-     * @param paint The paint to draw the bitmap with. may be null
+     * @param paint The paint to draw the bitmap with. May be null. Antialiasing is not supported.
      */
     public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) {
         super.drawPatch(patch, dst, paint);
@@ -2278,6 +2287,9 @@
      * array is optional, but if it is present, then it is used to specify the index of each
      * triangle, rather than just walking through the arrays in order.
      *
+     * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+     * ignored.</p>
+     *
      * @param mode How to interpret the array of vertices
      * @param vertexCount The number of values in the vertices array (and corresponding texs and
      *            colors arrays if non-null). Each logical vertex is two values (x, y), vertexCount
@@ -2292,8 +2304,9 @@
      * @param colorOffset Number of values in colors to skip before drawing.
      * @param indices If not null, array of indices to reference into the vertex (texs, colors)
      *            array.
-     * @param indexCount number of entries in the indices array (if not null).
-     * @param paint Specifies the shader to use if the texs array is non-null.
+     * @param indexCount Number of entries in the indices array (if not null).
+     * @param paint Specifies the shader to use if the texs array is non-null. Antialiasing is not
+     *            supported.
      */
     public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts,
             int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors,
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 48dd3e6..c7c97e0 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.hardware.OverlayProperties;
 import android.hardware.display.DisplayManager;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
@@ -982,6 +983,15 @@
     }
 
     /**
+     * Notifies the hardware renderer about pending choreographer callbacks.
+     *
+     * @hide
+     */
+    public void notifyCallbackPending() {
+        nNotifyCallbackPending(mNativeProxy);
+    }
+
+    /**
      * b/68769804, b/66945974: For low FPS experiments.
      *
      * @hide
@@ -1321,6 +1331,9 @@
             // memory policy in play will interpret these values differently.
             int largestWidth = activeMode.getPhysicalWidth();
             int largestHeight = activeMode.getPhysicalHeight();
+            final OverlayProperties overlayProperties = defaultDisplay.getOverlaySupport();
+            boolean supportFp16ForHdr = overlayProperties != null
+                    ? overlayProperties.supportFp16ForHdr() : false;
 
             for (int i = 0; i < allDisplays.length; i++) {
                 final Display display = allDisplays[i];
@@ -1348,7 +1361,7 @@
             nInitDisplayInfo(largestWidth, largestHeight, defaultDisplay.getRefreshRate(),
                     wideColorDataspace, defaultDisplay.getAppVsyncOffsetNanos(),
                     defaultDisplay.getPresentationDeadlineNanos(),
-                    defaultDisplay.getOverlaySupport().supportFp16ForHdr());
+                    supportFp16ForHdr);
 
             mDisplayInitialized = true;
         }
@@ -1536,4 +1549,6 @@
     private static native boolean nIsDrawingEnabled();
 
     private static native void nSetRtAnimationsEnabled(boolean rtAnimationsEnabled);
+
+    private static native void nNotifyCallbackPending(long nativeProxy);
 }
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index c93b733..68f2927 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -26,7 +26,7 @@
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(value = {
              UNKNOWN,
-             /**
+             /*
               * Since some APIs accept either ImageFormat or PixelFormat (and the two
               * enums do not overlap since they're both partial versions of the
               * internal format enum), add PixelFormat values here so linting
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
new file mode 100644
index 0000000..f32e0ee
--- /dev/null
+++ b/graphics/java/android/graphics/Mesh.java
@@ -0,0 +1,321 @@
+/*
+ * 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.graphics;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.nio.Buffer;
+import java.nio.ShortBuffer;
+
+/**
+ * Class representing a mesh object.
+ *
+ * This class generates Mesh objects via the
+ * {@link #make(MeshSpecification, Mode, Buffer, int, Rect)} and
+ * {@link #makeIndexed(MeshSpecification, Mode, Buffer, int, ShortBuffer, Rect)} methods,
+ * where a {@link MeshSpecification} is required along with various attributes for
+ * detailing the mesh object, including a mode, vertex buffer, optional index buffer, and bounds
+ * for the mesh.
+ *
+ * @hide
+ */
+public class Mesh {
+    private long mNativeMeshWrapper;
+    private boolean mIsIndexed;
+
+    /**
+     * Enum to determine how the mesh is represented.
+     */
+    public enum Mode {Triangles, TriangleStrip}
+
+    private static class MeshHolder {
+        public static final NativeAllocationRegistry MESH_SPECIFICATION_REGISTRY =
+                NativeAllocationRegistry.createMalloced(
+                        MeshSpecification.class.getClassLoader(), nativeGetFinalizer());
+    }
+
+    /**
+     * Generates a {@link Mesh} object.
+     *
+     * @param meshSpec     {@link MeshSpecification} used when generating the mesh.
+     * @param mode         {@link Mode} enum
+     * @param vertexBuffer vertex buffer representing through {@link Buffer}.
+     * @param vertexCount  the number of vertices represented in the vertexBuffer.
+     * @param bounds       bounds of the mesh object.
+     * @return a new Mesh object.
+     */
+    public static Mesh make(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer,
+            int vertexCount, Rect bounds) {
+        long nativeMesh = nativeMake(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer,
+                vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), bounds.left,
+                bounds.top, bounds.right, bounds.bottom);
+        if (nativeMesh == 0) {
+            throw new IllegalArgumentException("Mesh construction failed.");
+        }
+        return new Mesh(nativeMesh, false);
+    }
+
+    /**
+     * Generates an indexed {@link Mesh} object.
+     *
+     * @param meshSpec     {@link MeshSpecification} used when generating the mesh.
+     * @param mode         {@link Mode} enum
+     * @param vertexBuffer vertex buffer representing through {@link Buffer}.
+     * @param vertexCount  the number of vertices represented in the vertexBuffer.
+     * @param indexBuffer  index buffer representing through {@link ShortBuffer}.
+     * @param bounds       bounds of the mesh object.
+     * @return a new Mesh object.
+     */
+    public static Mesh makeIndexed(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer,
+            int vertexCount, ShortBuffer indexBuffer, Rect bounds) {
+        long nativeMesh = nativeMakeIndexed(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer,
+                vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), indexBuffer,
+                indexBuffer.isDirect(), indexBuffer.capacity(), indexBuffer.position(), bounds.left,
+                bounds.top, bounds.right, bounds.bottom);
+        if (nativeMesh == 0) {
+            throw new IllegalArgumentException("Mesh construction failed.");
+        }
+        return new Mesh(nativeMesh, true);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the color uniform declared in the shader program.
+     * @param color       the provided sRGB color will be converted into the shader program's output
+     *                    colorspace and be available as a vec4 uniform in the program.
+     */
+    public void setColorUniform(String uniformName, int color) {
+        setUniform(uniformName, Color.valueOf(color).getComponents(), true);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the color uniform declared in the shader program.
+     * @param color       the provided sRGB color will be converted into the shader program's output
+     *                    colorspace and be available as a vec4 uniform in the program.
+     */
+    public void setColorUniform(String uniformName, long color) {
+        Color exSRGB = Color.valueOf(color).convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
+        setUniform(uniformName, exSRGB.getComponents(), true);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the color uniform declared in the shader program.
+     * @param color       the provided sRGB color will be converted into the shader program's output
+     *                    colorspace and will be made available as a vec4 uniform in the program.
+     */
+    public void setColorUniform(String uniformName, Color color) {
+        if (color == null) {
+            throw new NullPointerException("The color parameter must not be null");
+        }
+
+        Color exSRGB = color.convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
+        setUniform(uniformName, exSRGB.getComponents(), true);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the float uniform declared in the shader program.
+     * @param value       float value corresponding to the float uniform with the given name.
+     */
+    public void setFloatUniform(String uniformName, float value) {
+        setFloatUniform(uniformName, value, 0.0f, 0.0f, 0.0f, 1);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the float uniform declared in the shader program.
+     * @param value1      first float value corresponding to the float uniform with the given name.
+     * @param value2      second float value corresponding to the float uniform with the given name.
+     */
+    public void setFloatUniform(String uniformName, float value1, float value2) {
+        setFloatUniform(uniformName, value1, value2, 0.0f, 0.0f, 2);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the float uniform declared in the shader program.
+     * @param value1      first float value corresponding to the float uniform with the given name.
+     * @param value2      second float value corresponding to the float uniform with the given name.
+     * @param value3      third float value corresponding to the float unifiform with the given
+     *                    name.
+     */
+    public void setFloatUniform(String uniformName, float value1, float value2, float value3) {
+        setFloatUniform(uniformName, value1, value2, value3, 0.0f, 3);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the float uniform declared in the shader program.
+     * @param value1      first float value corresponding to the float uniform with the given name.
+     * @param value2      second float value corresponding to the float uniform with the given name.
+     * @param value3      third float value corresponding to the float uniform with the given name.
+     * @param value4      fourth float value corresponding to the float uniform with the given name.
+     */
+    public void setFloatUniform(
+            String uniformName, float value1, float value2, float value3, float value4) {
+        setFloatUniform(uniformName, value1, value2, value3, value4, 4);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the float uniform declared in the shader program.
+     * @param values      float value corresponding to the vec4 float uniform with the given name.
+     */
+    public void setFloatUniform(String uniformName, float[] values) {
+        setUniform(uniformName, values, false);
+    }
+
+    private void setFloatUniform(
+            String uniformName, float value1, float value2, float value3, float value4, int count) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+        nativeUpdateUniforms(
+                mNativeMeshWrapper, uniformName, value1, value2, value3, value4, count);
+    }
+
+    private void setUniform(String uniformName, float[] values, boolean isColor) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+        if (values == null) {
+            throw new NullPointerException("The uniform values parameter must not be null");
+        }
+
+        nativeUpdateUniforms(mNativeMeshWrapper, uniformName, values, isColor);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the int uniform delcared in the shader program.
+     * @param value       value corresponding to the int uniform with the given name.
+     */
+    public void setIntUniform(String uniformName, int value) {
+        setIntUniform(uniformName, value, 0, 0, 0, 1);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the int uniform delcared in the shader program.
+     * @param value1      first value corresponding to the int uniform with the given name.
+     * @param value2      second value corresponding to the int uniform with the given name.
+     */
+    public void setIntUniform(String uniformName, int value1, int value2) {
+        setIntUniform(uniformName, value1, value2, 0, 0, 2);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the int uniform delcared in the shader program.
+     * @param value1      first value corresponding to the int uniform with the given name.
+     * @param value2      second value corresponding to the int uniform with the given name.
+     * @param value3      third value corresponding to the int uniform with the given name.
+     */
+    public void setIntUniform(String uniformName, int value1, int value2, int value3) {
+        setIntUniform(uniformName, value1, value2, value3, 0, 3);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the int uniform delcared in the shader program.
+     * @param value1      first value corresponding to the int uniform with the given name.
+     * @param value2      second value corresponding to the int uniform with the given name.
+     * @param value3      third value corresponding to the int uniform with the given name.
+     * @param value4      fourth value corresponding to the int uniform with the given name.
+     */
+    public void setIntUniform(String uniformName, int value1, int value2, int value3, int value4) {
+        setIntUniform(uniformName, value1, value2, value3, value4, 4);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     *
+     * @param uniformName name matching the int uniform delcared in the shader program.
+     * @param values      int values corresponding to the vec4 int uniform with the given name.
+     */
+    public void setIntUniform(String uniformName, int[] values) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+        if (values == null) {
+            throw new NullPointerException("The uniform values parameter must not be null");
+        }
+        nativeUpdateUniforms(mNativeMeshWrapper, uniformName, values);
+    }
+
+    /**
+     * @hide so only calls from module can utilize it
+     */
+    long getNativeWrapperInstance() {
+        nativeUpdateMesh(mNativeMeshWrapper, mIsIndexed);
+        return mNativeMeshWrapper;
+    }
+
+    private void setIntUniform(
+            String uniformName, int value1, int value2, int value3, int value4, int count) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+
+        nativeUpdateUniforms(
+                mNativeMeshWrapper, uniformName, value1, value2, value3, value4, count);
+    }
+
+    private Mesh(long nativeMeshWrapper, boolean isIndexed) {
+        mNativeMeshWrapper = nativeMeshWrapper;
+        this.mIsIndexed = isIndexed;
+        MeshHolder.MESH_SPECIFICATION_REGISTRY.registerNativeAllocation(this, mNativeMeshWrapper);
+    }
+
+    private static native long nativeGetFinalizer();
+
+    private static native long nativeMake(long meshSpec, int mode, Buffer vertexBuffer,
+            boolean isDirect, int vertexCount, int vertexOffset, int left, int top, int right,
+            int bottom);
+
+    private static native long nativeMakeIndexed(long meshSpec, int mode, Buffer vertexBuffer,
+            boolean isVertexDirect, int vertexCount, int vertexOffset, ShortBuffer indexBuffer,
+            boolean isIndexDirect, int indexCount, int indexOffset, int left, int top, int right,
+            int bottom);
+
+    private static native void nativeUpdateUniforms(long builder, String uniformName, float value1,
+            float value2, float value3, float value4, int count);
+
+    private static native void nativeUpdateUniforms(
+            long builder, String uniformName, float[] values, boolean isColor);
+
+    private static native void nativeUpdateUniforms(long builder, String uniformName, int value1,
+            int value2, int value3, int value4, int count);
+
+    private static native void nativeUpdateUniforms(long builder, String uniformName, int[] values);
+
+    private static native void nativeUpdateMesh(long nativeMeshWrapper, boolean mIsIndexed);
+}
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
new file mode 100644
index 0000000..45c13af
--- /dev/null
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -0,0 +1,194 @@
+/*
+ * 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.graphics;
+
+import android.annotation.IntDef;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * Class responsible for holding specifications for {@link Mesh} creations. This class
+ * generates a {@link MeshSpecification} via the Make method, where multiple parameters to set up
+ * the mesh are supplied, including attributes, vertex stride, varyings, and
+ * vertex/fragment shaders. There are also additional methods to provide an optional
+ * {@link ColorSpace} as well as an alpha type.
+ *
+ * Note that there are several limitations on various mesh specifications:
+ * 1. The max amount of attributes allowed is 8.
+ * 2. The offset alignment length is 4 bytes.
+ * 2. The max stride length is 1024.
+ * 3. The max amount of varyings is 6.
+ *
+ * These should be kept in mind when generating a mesh specification, as exceeding them will
+ * lead to errors.
+ *
+ * @hide
+ */
+public class MeshSpecification {
+    long mNativeMeshSpec;
+
+    /**
+     * Constants for {@link #make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
+     * to determine alpha type
+     */
+    @IntDef({UNKNOWN, OPAQUE, PREMUL, UNPREMULT})
+    public @interface AlphaType {
+    }
+
+    public static final int UNKNOWN = 0;
+    public static final int OPAQUE = 1;
+    public static final int PREMUL = 2;
+    public static final int UNPREMULT = 3;
+
+    /**
+     * Constants for {@link Attribute} and {@link Varying} for determining the data type.
+     */
+    @IntDef({FLOAT, FLOAT2, FLOAT3, FLOAT4, UBYTE4})
+    public @interface Type {
+    }
+
+    public static final int FLOAT = 0;
+    public static final int FLOAT2 = 1;
+    public static final int FLOAT3 = 2;
+    public static final int FLOAT4 = 3;
+    public static final int UBYTE4 = 4;
+
+    /**
+     * Data class to represent a single attribute in a shader. Note that type parameter must be
+     * one of {@link #FLOAT}, {@link #FLOAT2}, {@link #FLOAT3}, {@link #FLOAT4}, or {@link #UBYTE4}.
+     */
+    public static class Attribute {
+        @Type
+        private int mType;
+        private int mOffset;
+        private String mName;
+
+        public Attribute(@Type int type, int offset, String name) {
+            mType = type;
+            mOffset = offset;
+            mName = name;
+        }
+    }
+
+    /**
+     * Data class to represent a single varying variable. Note that type parameter must be
+     * one of {@link #FLOAT}, {@link #FLOAT2}, {@link #FLOAT3}, {@link #FLOAT4}, or {@link #UBYTE4}.
+     */
+    public static class Varying {
+        @Type
+        private int mType;
+        private String mName;
+
+        public Varying(@Type int type, String name) {
+            mType = type;
+            mName = name;
+        }
+    }
+
+    private static class MeshSpecificationHolder {
+        public static final NativeAllocationRegistry MESH_SPECIFICATION_REGISTRY =
+                NativeAllocationRegistry.createMalloced(
+                        MeshSpecification.class.getClassLoader(), nativeGetFinalizer());
+    }
+
+    /**
+     * Creates a {@link MeshSpecification} object.
+     *
+     * @param attributes     list of attributes represented by {@link Attribute}. Can hold a max of
+     *                       8.
+     * @param vertexStride   length of vertex stride. Max of 1024 is accepted.
+     * @param varyings       List of varyings represented by {@link Varying}. Can hold a max of 6.
+     * @param vertexShader   vertex shader to be supplied to the mesh.
+     * @param fragmentShader fragment shader to be suppied to the mesh.
+     * @return {@link MeshSpecification} object for use when creating {@link Mesh}
+     */
+    public static MeshSpecification make(Attribute[] attributes, int vertexStride,
+            Varying[] varyings, String vertexShader, String fragmentShader) {
+        long nativeMeshSpec =
+                nativeMake(attributes, vertexStride, varyings, vertexShader, fragmentShader);
+        if (nativeMeshSpec == 0) {
+            throw new IllegalArgumentException("MeshSpecification construction failed");
+        }
+        return new MeshSpecification(nativeMeshSpec);
+    }
+
+    /**
+     * Creates a {@link MeshSpecification} object.
+     *
+     * @param attributes     list of attributes represented by {@link Attribute}. Can hold a max of
+     *                       8.
+     * @param vertexStride   length of vertex stride. Max of 1024 is accepted.
+     * @param varyings       List of varyings represented by {@link Varying}. Can hold a max of
+     *                       6.
+     * @param vertexShader   vertex shader to be supplied to the mesh.
+     * @param fragmentShader fragment shader to be supplied to the mesh.
+     * @param colorSpace     {@link ColorSpace} to tell what color space to work in.
+     * @return {@link MeshSpecification} object for use when creating {@link Mesh}
+     */
+    public static MeshSpecification make(Attribute[] attributes, int vertexStride,
+            Varying[] varyings, String vertexShader, String fragmentShader, ColorSpace colorSpace) {
+        long nativeMeshSpec = nativeMakeWithCS(attributes, vertexStride, varyings, vertexShader,
+                fragmentShader, colorSpace.getNativeInstance());
+        if (nativeMeshSpec == 0) {
+            throw new IllegalArgumentException("MeshSpecification construction failed");
+        }
+        return new MeshSpecification(nativeMeshSpec);
+    }
+
+    /**
+     * Creates a {@link MeshSpecification} object.
+     *
+     * @param attributes     list of attributes represented by {@link Attribute}. Can hold a max of
+     *                       8.
+     * @param vertexStride   length of vertex stride. Max of 1024 is accepted.
+     * @param varyings       List of varyings represented by {@link Varying}. Can hold a max of 6.
+     * @param vertexShader   vertex shader code to be supplied to the mesh.
+     * @param fragmentShader fragment shader code to be suppied to the mesh.
+     * @param colorSpace     {@link ColorSpace} to tell what color space to work in.
+     * @param alphaType      Describes how to interpret the alpha component for a pixel. Must be
+     *                       one of {@link AlphaType} values.
+     * @return {@link MeshSpecification} object for use when creating {@link Mesh}
+     */
+    public static MeshSpecification make(Attribute[] attributes, int vertexStride,
+            Varying[] varyings, String vertexShader, String fragmentShader, ColorSpace colorSpace,
+            @AlphaType int alphaType) {
+        long nativeMeshSpec = nativeMakeWithAlpha(attributes, vertexStride, varyings, vertexShader,
+                fragmentShader, colorSpace.getNativeInstance(), alphaType);
+        if (nativeMeshSpec == 0) {
+            throw new IllegalArgumentException("MeshSpecification construction failed");
+        }
+        return new MeshSpecification(nativeMeshSpec);
+    }
+
+    private MeshSpecification(long meshSpec) {
+        mNativeMeshSpec = meshSpec;
+        MeshSpecificationHolder.MESH_SPECIFICATION_REGISTRY.registerNativeAllocation(
+                this, meshSpec);
+    }
+
+    private static native long nativeGetFinalizer();
+
+    private static native long nativeMake(Attribute[] attributes, int vertexStride,
+            Varying[] varyings, String vertexShader, String fragmentShader);
+
+    private static native long nativeMakeWithCS(Attribute[] attributes, int vertexStride,
+            Varying[] varyings, String vertexShader, String fragmentShader, long colorSpace);
+
+    private static native long nativeMakeWithAlpha(Attribute[] attributes, int vertexStride,
+            Varying[] varyings, String vertexShader, String fragmentShader, long colorSpace,
+            int alphaType);
+}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 1a80ab3..f0e496f 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -137,6 +137,13 @@
      * <p>Enabling this flag will cause all draw operations that support
      * antialiasing to use it.</p>
      *
+     * <p>Notable draw operations that do <b>not</b> support antialiasing include:</p>
+     * <ul>
+     *      <li>{@link android.graphics.Canvas#drawBitmapMesh}</li>
+     *      <li>{@link android.graphics.Canvas#drawPatch}</li>
+     *      <li>{@link android.graphics.Canvas#drawVertices}</li>
+     * </ul>
+     *
      * @see #Paint(int)
      * @see #setFlags(int)
      */
diff --git a/graphics/java/android/graphics/PathIterator.java b/graphics/java/android/graphics/PathIterator.java
index 33b9a47..bfda690 100644
--- a/graphics/java/android/graphics/PathIterator.java
+++ b/graphics/java/android/graphics/PathIterator.java
@@ -281,7 +281,7 @@
             return mConicWeight;
         }
 
-        public Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) {
+        Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) {
             mVerb = verb;
             mPoints = points;
             mConicWeight = conicWeight;
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 3b7d0e1..9fb627f 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1207,6 +1207,7 @@
      * It is safe to call this method twice or more on the same instance.
      * @hide
      */
+    @TestApi
     public void releaseNativeObjectForTest() {
         mCleaner.run();
     }
@@ -1294,6 +1295,13 @@
     /**
      * Deserialize the font mapping from the serialized byte buffer.
      *
+     * <p>Warning: the given {@code buffer} must outlive generated Typeface
+     * objects in {@code out}. In production code, this is guaranteed by
+     * storing the buffer in {@link #sSystemFontMapBuffer}.
+     * If you call this method in a test, please make sure to destroy the
+     * generated Typeface objects by calling
+     * {@link #releaseNativeObjectForTest()}.
+     *
      * @hide
      */
     @TestApi
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 54c9f62..1a878df 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -766,7 +766,7 @@
         if (mBackground != null) {
             mBackground.onHotspotBoundsChanged();
         }
-        float newRadius = Math.round(getComputedRadius());
+        float newRadius = getComputedRadius();
         for (int i = 0; i < mRunningAnimations.size(); i++) {
             RippleAnimationSession s = mRunningAnimations.get(i);
             s.setRadius(newRadius);
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 4065bd1..e25ee90 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -60,7 +60,7 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Map;
 import java.util.Stack;
 
 /**
@@ -1171,18 +1171,14 @@
 
         private static final int NATIVE_ALLOCATION_SIZE = 100;
 
-        private static final HashMap<String, Integer> sPropertyIndexMap =
-                new HashMap<String, Integer>() {
-                    {
-                        put("translateX", TRANSLATE_X_INDEX);
-                        put("translateY", TRANSLATE_Y_INDEX);
-                        put("scaleX", SCALE_X_INDEX);
-                        put("scaleY", SCALE_Y_INDEX);
-                        put("pivotX", PIVOT_X_INDEX);
-                        put("pivotY", PIVOT_Y_INDEX);
-                        put("rotation", ROTATION_INDEX);
-                    }
-                };
+        private static final Map<String, Integer> sPropertyIndexMap = Map.of(
+                "translateX", TRANSLATE_X_INDEX,
+                "translateY", TRANSLATE_Y_INDEX,
+                "scaleX", SCALE_X_INDEX,
+                "scaleY", SCALE_Y_INDEX,
+                "pivotX", PIVOT_X_INDEX,
+                "pivotY", PIVOT_Y_INDEX,
+                "rotation", ROTATION_INDEX);
 
         static int getPropertyIndex(String propertyName) {
             if (sPropertyIndexMap.containsKey(propertyName)) {
@@ -1285,18 +1281,15 @@
                     }
                 };
 
-        private static final HashMap<String, Property> sPropertyMap =
-                new HashMap<String, Property>() {
-                    {
-                        put("translateX", TRANSLATE_X);
-                        put("translateY", TRANSLATE_Y);
-                        put("scaleX", SCALE_X);
-                        put("scaleY", SCALE_Y);
-                        put("pivotX", PIVOT_X);
-                        put("pivotY", PIVOT_Y);
-                        put("rotation", ROTATION);
-                    }
-                };
+        private static final Map<String, Property> sPropertyMap = Map.of(
+                "translateX", TRANSLATE_X,
+                "translateY", TRANSLATE_Y,
+                "scaleX", SCALE_X,
+                "scaleY", SCALE_Y,
+                "pivotX", PIVOT_X,
+                "pivotY", PIVOT_Y,
+                "rotation", ROTATION);
+
         // Temp array to store transform values obtained from native.
         private float[] mTransform;
         /////////////////////////////////////////////////////
@@ -1762,19 +1755,15 @@
 
         private static final int NATIVE_ALLOCATION_SIZE = 264;
         // Property map for animatable attributes.
-        private final static HashMap<String, Integer> sPropertyIndexMap
-                = new HashMap<String, Integer> () {
-            {
-                put("strokeWidth", STROKE_WIDTH_INDEX);
-                put("strokeColor", STROKE_COLOR_INDEX);
-                put("strokeAlpha", STROKE_ALPHA_INDEX);
-                put("fillColor", FILL_COLOR_INDEX);
-                put("fillAlpha", FILL_ALPHA_INDEX);
-                put("trimPathStart", TRIM_PATH_START_INDEX);
-                put("trimPathEnd", TRIM_PATH_END_INDEX);
-                put("trimPathOffset", TRIM_PATH_OFFSET_INDEX);
-            }
-        };
+        private static final Map<String, Integer> sPropertyIndexMap = Map.of(
+                "strokeWidth", STROKE_WIDTH_INDEX,
+                "strokeColor", STROKE_COLOR_INDEX,
+                "strokeAlpha", STROKE_ALPHA_INDEX,
+                "fillColor", FILL_COLOR_INDEX,
+                "fillAlpha", FILL_ALPHA_INDEX,
+                "trimPathStart", TRIM_PATH_START_INDEX,
+                "trimPathEnd", TRIM_PATH_END_INDEX,
+                "trimPathOffset", TRIM_PATH_OFFSET_INDEX);
 
         // Below are the Properties that wrap the setters to avoid reflection overhead in animations
         private static final Property<VFullPath, Float> STROKE_WIDTH =
@@ -1881,19 +1870,15 @@
                     }
                 };
 
-        private final static HashMap<String, Property> sPropertyMap
-                = new HashMap<String, Property> () {
-            {
-                put("strokeWidth", STROKE_WIDTH);
-                put("strokeColor", STROKE_COLOR);
-                put("strokeAlpha", STROKE_ALPHA);
-                put("fillColor", FILL_COLOR);
-                put("fillAlpha", FILL_ALPHA);
-                put("trimPathStart", TRIM_PATH_START);
-                put("trimPathEnd", TRIM_PATH_END);
-                put("trimPathOffset", TRIM_PATH_OFFSET);
-            }
-        };
+        private static final Map<String, Property> sPropertyMap = Map.of(
+                "strokeWidth", STROKE_WIDTH,
+                "strokeColor", STROKE_COLOR,
+                "strokeAlpha", STROKE_ALPHA,
+                "fillColor", FILL_COLOR,
+                "fillAlpha", FILL_ALPHA,
+                "trimPathStart", TRIM_PATH_START,
+                "trimPathEnd", TRIM_PATH_END,
+                "trimPathOffset", TRIM_PATH_OFFSET);
 
         // Temp array to store property data obtained from native getter.
         private byte[] mPropertyData;
diff --git a/graphics/java/android/graphics/fonts/FontStyle.java b/graphics/java/android/graphics/fonts/FontStyle.java
index 09799fd..48969aa 100644
--- a/graphics/java/android/graphics/fonts/FontStyle.java
+++ b/graphics/java/android/graphics/fonts/FontStyle.java
@@ -48,6 +48,10 @@
     private static final String TAG = "FontStyle";
 
     /**
+     * A default value when font weight is unspecified
+     */
+    public static final int FONT_WEIGHT_UNSPECIFIED = -1;
+    /**
      * A minimum weight value for the font
      */
     public static final int FONT_WEIGHT_MIN = 1;
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 0f82c8f..0e198d5 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.graphics.Bitmap;
 import android.graphics.HardwareRenderer;
 import android.graphics.Rect;
@@ -311,11 +312,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 +336,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,160 +350,207 @@
     }
 
     /**
-     * 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, Executor, Consumer)}
      */
     public static final class Request {
-        private Request(Surface source, Rect sourceInsets, Executor listenerThread,
-                        Consumer<CopyResult> 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
-         */
-        public @NonNull Request setSourceRect(@Nullable Rect srcRect) {
-            this.mSrcRect = srcRect;
-            return this;
+        private Request(Surface source, Rect sourceInsets) {
+            this.mSource = source;
+            this.mSourceInsets = sourceInsets;
         }
 
         /**
-         * 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
+         * A builder to create the complete PixelCopy request, which is then executed by calling
+         * {@link #request(Request, Executor, Consumer)} with the built request returned from
+         * {@link #build()}
          */
-        public @NonNull Request setDestinationBitmap(@Nullable Bitmap destination) {
-            if (destination != null) {
-                validateBitmapDest(destination);
+        public static final class Builder {
+            private Request mRequest;
+
+            private Builder(Request request) {
+                mRequest = request;
             }
-            this.mDest = destination;
-            return this;
+
+            /**
+             * Creates a PixelCopy Builder for the given {@link Window}
+             * @param source The Window to copy from
+             * @return A {@link Builder} builder to set the optional params & build the request
+             */
+            @SuppressLint("BuilderSetStyle")
+            public static @NonNull Builder ofWindow(@NonNull Window source) {
+                final Rect insets = new Rect();
+                final Surface surface = sourceForWindow(source, insets);
+                return new Builder(new Request(surface, insets));
+            }
+
+            /**
+             * Creates a PixelCopy Builder 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.
+             * @return A {@link Builder} builder to set the optional params & build the request
+             */
+            @SuppressLint("BuilderSetStyle")
+            public static @NonNull Builder ofWindow(@NonNull View source) {
+                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));
+            }
+
+            /**
+             * Creates a PixelCopy Builder for the given {@link Surface}
+             *
+             * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
+             * @return A {@link Builder} builder to set the optional params & build the request
+             */
+            @SuppressLint("BuilderSetStyle")
+            public static @NonNull Builder ofSurface(@NonNull Surface source) {
+                if (source == null || !source.isValid()) {
+                    throw new IllegalArgumentException("Source must not be null & must be valid");
+                }
+                return new Builder(new Request(source, null));
+            }
+
+            /**
+             * Creates a PixelCopy Builder 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}
+             * @return A {@link Builder} builder to set the optional params & build the request
+             */
+            @SuppressLint("BuilderSetStyle")
+            public static @NonNull Builder ofSurface(@NonNull SurfaceView source) {
+                return ofSurface(source.getHolder().getSurface());
+            }
+
+            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.
+         * @return The destination bitmap as set by {@link Builder#setDestinationBitmap(Bitmap)}
          */
-        public void request() {
+        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(@NonNull Executor callbackExecutor,
+                            @NonNull Consumer<Result> listener) {
             if (!mSource.isValid()) {
-                mListenerThread.execute(() -> mListener.accept(
-                        new CopyResult(ERROR_SOURCE_INVALID, null)));
+                callbackExecutor.execute(() -> listener.accept(
+                        new Result(ERROR_SOURCE_INVALID, null)));
                 return;
             }
             HardwareRenderer.copySurfaceInto(mSource, new HardwareRenderer.CopyRequest(
                     adjustSourceRectForInsets(mSourceInsets, mSrcRect), mDest) {
                 @Override
                 public void onCopyFinished(int result) {
-                    mListenerThread.execute(() -> mListener.accept(
-                            new CopyResult(result, mDestinationBitmap)));
+                    callbackExecutor.execute(() -> listener.accept(
+                            new Result(result, mDestinationBitmap)));
                 }
             });
         }
     }
 
     /**
-     * Creates a PixelCopy request for the given {@link Window}
-     * @param source The Window to copy from
+     * Executes the pixel copy request
+     * @param request The request to execute
      * @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 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, @NonNull Executor callbackExecutor,
+                               @NonNull Consumer<Result> listener) {
+        request.request(callbackExecutor, listener);
     }
 
     private PixelCopy() {}
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 1a81dda..6536e43 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -138,6 +138,16 @@
      * provisioning server refuses key issuance, this is a permanent error.</p>
      */
     public static final int ERROR_ATTESTATION_KEYS_UNAVAILABLE = 16;
+    /**
+     * This device requires a software upgrade to use the key provisioning server. The software
+     * is outdated and this error is returned only on devices that rely solely on
+     * remotely-provisioned keys (see <a href=
+     * "https://android-developers.googleblog.com/2022/03/upgrading-android-attestation-remote.html"
+     * >Remote Key Provisioning</a>).
+     *
+     * @hide
+     */
+    public static final int ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION = 17;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -157,7 +167,8 @@
             ERROR_INCORRECT_USAGE,
             ERROR_KEY_NOT_TEMPORALLY_VALID,
             ERROR_KEY_OPERATION_EXPIRED,
-            ERROR_ATTESTATION_KEYS_UNAVAILABLE
+            ERROR_ATTESTATION_KEYS_UNAVAILABLE,
+            ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION,
     })
     public @interface PublicErrorCode {
     }
@@ -184,6 +195,16 @@
      * This value is returned when {@link #isTransientFailure()} is {@code true}.
      */
     public static final int RETRY_WHEN_CONNECTIVITY_AVAILABLE = 3;
+    /**
+     * Re-try the operation that led to this error when the device has a software update
+     * downloaded and on the next reboot. The Remote provisioning server recognizes
+     * the device, but refuses issuance of attestation keys because it contains a software
+     * version that could potentially be vulnerable and needs an update. Re-trying after the
+     * device has upgraded and rebooted may alleviate the problem.
+     *
+     * <p>This value is returned when {@link #isTransientFailure()} is {@code true}.
+     */
+    public static final int RETRY_AFTER_NEXT_REBOOT = 4;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -191,6 +212,7 @@
             RETRY_NEVER,
             RETRY_WITH_EXPONENTIAL_BACKOFF,
             RETRY_WHEN_CONNECTIVITY_AVAILABLE,
+            RETRY_AFTER_NEXT_REBOOT,
     })
     public @interface RetryPolicy {
     }
@@ -217,6 +239,13 @@
      * when the device has connectivity again.
      * @hide */
     public static final int RKP_FETCHING_PENDING_CONNECTIVITY = 3;
+    /**
+     * The RKP server recognizes the device, but the device may be running vulnerable software,
+     * and thus refusing issuance of RKP keys to it.
+     *
+     * @hide
+     */
+    public static final int RKP_FETCHING_PENDING_SOFTWARE_REBOOT = 4;
 
     // Constants for encoding information about the error encountered:
     // Whether the error relates to the system state/implementation as a whole, or a specific key.
@@ -236,7 +265,7 @@
     private static int initializeRkpStatusForRegularErrors(int errorCode) {
         // Check if the system code mistakenly called a constructor of KeyStoreException with
         // the OUT_OF_KEYS error code but without RKP status.
-        if (errorCode == ResponseCode.OUT_OF_KEYS) {
+        if (isRkpRelatedError(errorCode)) {
             Log.e(TAG, "RKP error code without RKP status");
             // Set RKP status to RKP_SERVER_REFUSED_ISSUANCE so that the caller never retries.
             return RKP_SERVER_REFUSED_ISSUANCE;
@@ -272,7 +301,7 @@
         super(message);
         mErrorCode = errorCode;
         mRkpStatus = rkpStatus;
-        if (mErrorCode != ResponseCode.OUT_OF_KEYS) {
+        if (!isRkpRelatedError(mErrorCode)) {
             Log.e(TAG, "Providing RKP status for error code " + errorCode + " has no effect.");
         }
     }
@@ -309,10 +338,11 @@
     public boolean isTransientFailure() {
         PublicErrorInformation failureInfo = getErrorInformation(mErrorCode);
         // Special-case handling for RKP failures:
-        if (mRkpStatus != RKP_SUCCESS && mErrorCode == ResponseCode.OUT_OF_KEYS) {
+        if (mRkpStatus != RKP_SUCCESS && isRkpRelatedError(mErrorCode)) {
             switch (mRkpStatus) {
                 case RKP_TEMPORARILY_UNAVAILABLE:
                 case RKP_FETCHING_PENDING_CONNECTIVITY:
+                case RKP_FETCHING_PENDING_SOFTWARE_REBOOT:
                     return true;
                 case RKP_SERVER_REFUSED_ISSUANCE:
                 default:
@@ -346,6 +376,11 @@
         return (failureInfo.indicators & IS_SYSTEM_ERROR) != 0;
     }
 
+    private static boolean isRkpRelatedError(int errorCode) {
+        return errorCode == ResponseCode.OUT_OF_KEYS
+                  || errorCode == ResponseCode.OUT_OF_KEYS_REQUIRES_UPGRADE;
+    }
+
     /**
      * Returns the re-try policy for transient failures. Valid only if
      * {@link #isTransientFailure()} returns {@code True}.
@@ -362,6 +397,8 @@
                     return RETRY_WHEN_CONNECTIVITY_AVAILABLE;
                 case RKP_SERVER_REFUSED_ISSUANCE:
                     return RETRY_NEVER;
+                case RKP_FETCHING_PENDING_SOFTWARE_REBOOT:
+                    return RETRY_AFTER_NEXT_REBOOT;
                 default:
                     return (failureInfo.indicators & IS_TRANSIENT_ERROR) != 0
                             ? RETRY_WITH_EXPONENTIAL_BACKOFF : RETRY_NEVER;
@@ -620,5 +657,8 @@
                 new PublicErrorInformation(0, ERROR_KEY_DOES_NOT_EXIST));
         sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS,
                 new PublicErrorInformation(IS_SYSTEM_ERROR, ERROR_ATTESTATION_KEYS_UNAVAILABLE));
+        sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS_REQUIRES_UPGRADE,
+                new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
+                        ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION));
     }
 }
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index dbd918e..8c42547 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -30,6 +30,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECParameterSpec;
 import java.security.spec.MGF1ParameterSpec;
 import java.util.Collection;
 import java.util.Locale;
@@ -195,6 +196,7 @@
     @StringDef(prefix = { "KEY_" }, value = {
         KEY_ALGORITHM_RSA,
         KEY_ALGORITHM_EC,
+        KEY_ALGORITHM_XDH,
         KEY_ALGORITHM_AES,
         KEY_ALGORITHM_HMAC_SHA1,
         KEY_ALGORITHM_HMAC_SHA224,
@@ -210,6 +212,11 @@
     /** Elliptic Curve (EC) Cryptography key. */
     public static final String KEY_ALGORITHM_EC = "EC";
 
+    /** Curve 25519 based Agreement key.
+     * @hide
+     */
+    public static final String KEY_ALGORITHM_XDH = "XDH";
+
     /** Advanced Encryption Standard (AES) key. */
     public static final String KEY_ALGORITHM_AES = "AES";
 
@@ -245,7 +252,8 @@
 
         public static int toKeymasterAsymmetricKeyAlgorithm(
                 @NonNull @KeyAlgorithmEnum String algorithm) {
-            if (KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) {
+            if (KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)
+                    || KEY_ALGORITHM_XDH.equalsIgnoreCase(algorithm)) {
                 return KeymasterDefs.KM_ALGORITHM_EC;
             } else if (KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) {
                 return KeymasterDefs.KM_ALGORITHM_RSA;
@@ -914,6 +922,51 @@
     }
 
     /**
+     * @hide
+     */
+    public abstract static class EcCurve {
+        private EcCurve() {}
+
+        /**
+         * @hide
+         */
+        public static int toKeymasterCurve(ECParameterSpec spec) {
+            int keySize = spec.getCurve().getField().getFieldSize();
+            switch (keySize) {
+                case 224:
+                    return android.hardware.security.keymint.EcCurve.P_224;
+                case 256:
+                    return android.hardware.security.keymint.EcCurve.P_256;
+                case 384:
+                    return android.hardware.security.keymint.EcCurve.P_384;
+                case 521:
+                    return android.hardware.security.keymint.EcCurve.P_521;
+                default:
+                    return -1;
+            }
+        }
+
+        /**
+         * @hide
+         */
+        public static int fromKeymasterCurve(int ecCurve) {
+            switch (ecCurve) {
+                case android.hardware.security.keymint.EcCurve.P_224:
+                    return 224;
+                case android.hardware.security.keymint.EcCurve.P_256:
+                case android.hardware.security.keymint.EcCurve.CURVE_25519:
+                    return 256;
+                case android.hardware.security.keymint.EcCurve.P_384:
+                    return 384;
+                case android.hardware.security.keymint.EcCurve.P_521:
+                    return 521;
+                default:
+                    return -1;
+            }
+        }
+    }
+
+    /**
      * Namespaces provide system developers and vendors with a way to use keystore without
      * requiring an applications uid. Namespaces can be configured using SEPolicy.
      * See <a href="https://source.android.com/security/keystore#access-control">
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
index 5216a90..ace2053 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
@@ -203,6 +203,11 @@
         for (Authorization a : key.getAuthorizations()) {
             if (a.keyParameter.tag == KeymasterDefs.KM_TAG_KEY_SIZE) {
                 keySizeBits = KeyStore2ParameterUtils.getUnsignedInt(a);
+                break;
+            } else if (a.keyParameter.tag == KeymasterDefs.KM_TAG_EC_CURVE) {
+                keySizeBits = KeyProperties.EcCurve.fromKeymasterCurve(
+                        a.keyParameter.value.getEcCurve());
+                break;
             }
         }
 
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java
index 4e73bd9..4505eaf 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java
@@ -24,13 +24,9 @@
 import android.system.keystore2.KeyDescriptor;
 import android.system.keystore2.KeyMetadata;
 
-import java.security.AlgorithmParameters;
-import java.security.NoSuchAlgorithmException;
 import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECGenParameterSpec;
 import java.security.spec.ECParameterSpec;
 import java.security.spec.ECPoint;
-import java.security.spec.InvalidParameterSpecException;
 
 /**
  * {@link ECPublicKey} backed by keystore.
@@ -62,34 +58,13 @@
         }
     }
 
-    private static String getEcCurveFromKeymaster(int ecCurve) {
-        switch (ecCurve) {
-            case android.hardware.security.keymint.EcCurve.P_224:
-                return "secp224r1";
-            case android.hardware.security.keymint.EcCurve.P_256:
-                return "secp256r1";
-            case android.hardware.security.keymint.EcCurve.P_384:
-                return "secp384r1";
-            case android.hardware.security.keymint.EcCurve.P_521:
-                return "secp521r1";
-        }
-        return "";
-    }
-
-    private ECParameterSpec getCurveSpec(String name)
-            throws NoSuchAlgorithmException, InvalidParameterSpecException {
-        AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
-        parameters.init(new ECGenParameterSpec(name));
-        return parameters.getParameterSpec(ECParameterSpec.class);
-    }
-
     @Override
     public AndroidKeyStorePrivateKey getPrivateKey() {
         ECParameterSpec params = mParams;
         for (Authorization a : getAuthorizations()) {
             try {
                 if (a.keyParameter.tag == KeymasterDefs.KM_TAG_EC_CURVE) {
-                    params = getCurveSpec(getEcCurveFromKeymaster(
+                    params = KeymasterUtils.getCurveSpec(KeymasterUtils.getEcCurveFromKeymaster(
                             a.keyParameter.value.getEcCurve()));
                     break;
                 }
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
index 4caa47f..7292cd3 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
@@ -32,7 +32,6 @@
 import java.security.PublicKey;
 import java.security.SecureRandom;
 import java.security.interfaces.ECKey;
-import java.security.interfaces.XECKey;
 import java.security.spec.AlgorithmParameterSpec;
 import java.util.ArrayList;
 import java.util.List;
@@ -134,10 +133,15 @@
             throw new InvalidKeyException("key == null");
         } else if (!(key instanceof PublicKey)) {
             throw new InvalidKeyException("Only public keys supported. Key: " + key);
-        } else if (!(mKey instanceof ECKey && key instanceof ECKey)
-                && !(mKey instanceof XECKey && key instanceof XECKey)) {
+        } else if (mKey instanceof ECKey && !(key instanceof ECKey)
+                /*&& !(mKey instanceof XECKey && key instanceof XECKey)*/) {
+        /** TODO This condition is temporary modified, because OpenSSL implementation does not
+         * implement OpenSSLX25519PublicKey from XECKey interface (b/214203951).
+         * This change has to revert once conscrypt implements OpenSSLX25519PublicKey from
+         * XECKey interface.
+         */
             throw new InvalidKeyException(
-                    "Public and Private key should be of the same type:");
+                    "Public and Private key should be of the same type.");
         } else if (mKey instanceof ECKey
                 && !((ECKey) key).getParams().getCurve()
                 .equals(((ECKey) mKey).getParams().getCurve())) {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index acc0005..2830d7e 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -52,6 +52,7 @@
 import android.system.keystore2.KeyMetadata;
 import android.system.keystore2.ResponseCode;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -647,6 +648,7 @@
         // {@link android.security.KeyStoreException#RKP_TEMPORARILY_UNAVAILABLE},
         // {@link android.security.KeyStoreException#RKP_SERVER_REFUSED_ISSUANCE},
         // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_CONNECTIVITY}
+        // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_SOFTWARE_REBOOT}
         public final int rkpStatus;
         @Nullable
         public final KeyPair keyPair;
@@ -856,6 +858,13 @@
                                 KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
                                 imei.getBytes(StandardCharsets.UTF_8)
                         ));
+                        final String secondImei = telephonyService.getImei(1);
+                        if (!TextUtils.isEmpty(secondImei)) {
+                            params.add(KeyStore2ParameterUtils.makeBytes(
+                                    KeymasterDefs.KM_TAG_ATTESTATION_ID_SECOND_IMEI,
+                                    secondImei.getBytes(StandardCharsets.UTF_8)
+                            ));
+                        }
                         break;
                     }
                     case AttestationUtils.ID_TYPE_MEID: {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 94bf122..2d609e8 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.hardware.biometrics.BiometricManager;
+import android.hardware.security.keymint.EcCurve;
 import android.hardware.security.keymint.HardwareAuthenticatorType;
 import android.hardware.security.keymint.KeyParameter;
 import android.hardware.security.keymint.SecurityLevel;
@@ -66,6 +67,15 @@
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.EdECKey;
+import java.security.interfaces.EdECPrivateKey;
+import java.security.interfaces.XECKey;
+import java.security.interfaces.XECPrivateKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.NamedParameterSpec;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -566,6 +576,14 @@
                         spec.getMaxUsageCount()
                 ));
             }
+            if (KeymasterDefs.KM_ALGORITHM_EC
+                    == KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
+                            key.getAlgorithm())) {
+                importArgs.add(KeyStore2ParameterUtils.makeEnum(
+                        KeymasterDefs.KM_TAG_EC_CURVE,
+                        getKeymasterEcCurve(key)
+                ));
+            }
         } catch (IllegalArgumentException | IllegalStateException e) {
             throw new KeyStoreException(e);
         }
@@ -591,6 +609,31 @@
         }
     }
 
+    private int getKeymasterEcCurve(PrivateKey key) {
+        if (key instanceof ECKey) {
+            ECParameterSpec param = ((ECPrivateKey) key).getParams();
+            int kmECCurve = KeymasterUtils.getKeymasterEcCurve(KeymasterUtils.getCurveName(param));
+            if (kmECCurve >= 0) {
+                return kmECCurve;
+            }
+        } else if (key instanceof XECKey) {
+            AlgorithmParameterSpec param = ((XECPrivateKey) key).getParams();
+            if (param.equals(NamedParameterSpec.X25519)) {
+                return EcCurve.CURVE_25519;
+            }
+        } else if (key.getAlgorithm().equals("XDH")) {
+            // TODO com.android.org.conscrypt.OpenSSLX25519PrivateKey does not implement XECKey,
+            //  this case is not required once it implements XECKey interface(b/214203951).
+            return EcCurve.CURVE_25519;
+        } else if (key instanceof EdECKey) {
+            AlgorithmParameterSpec param = ((EdECPrivateKey) key).getParams();
+            if (param.equals(NamedParameterSpec.ED25519)) {
+                return EcCurve.CURVE_25519;
+            }
+        }
+        throw new IllegalArgumentException("Unexpected Key " + key.getClass().getName());
+    }
+
     private static void assertCanReplace(String alias, @Domain int targetDomain,
             int targetNamespace, KeyDescriptor descriptor)
             throws KeyStoreException {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreXDHPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreXDHPublicKey.java
index 9f3df3d..6913834 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreXDHPublicKey.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreXDHPublicKey.java
@@ -88,7 +88,7 @@
                 getUserKeyDescriptor(),
                 getKeyIdDescriptor().nspace,
                 getAuthorizations(),
-                "x25519",
+                "XDH",
                 getSecurityLevel());
     }
 
diff --git a/keystore/java/android/security/keystore2/KeymasterUtils.java b/keystore/java/android/security/keystore2/KeymasterUtils.java
index de4696c..614e368 100644
--- a/keystore/java/android/security/keystore2/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore2/KeymasterUtils.java
@@ -20,7 +20,12 @@
 import android.security.keymaster.KeymasterDefs;
 import android.security.keystore.KeyProperties;
 
+import java.security.AlgorithmParameters;
+import java.security.NoSuchAlgorithmException;
 import java.security.ProviderException;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
 
 /**
  * @hide
@@ -121,4 +126,65 @@
                 break;
         }
     }
+
+    static String getEcCurveFromKeymaster(int ecCurve) {
+        switch (ecCurve) {
+            case android.hardware.security.keymint.EcCurve.P_224:
+                return "secp224r1";
+            case android.hardware.security.keymint.EcCurve.P_256:
+                return "secp256r1";
+            case android.hardware.security.keymint.EcCurve.P_384:
+                return "secp384r1";
+            case android.hardware.security.keymint.EcCurve.P_521:
+                return "secp521r1";
+        }
+        return "";
+    }
+
+    static int getKeymasterEcCurve(String ecCurveName) {
+        if (ecCurveName.equals("secp224r1")) {
+            return android.hardware.security.keymint.EcCurve.P_224;
+        } else if (ecCurveName.equals("secp256r1")) {
+            return android.hardware.security.keymint.EcCurve.P_256;
+        } else if (ecCurveName.equals("secp384r1")) {
+            return android.hardware.security.keymint.EcCurve.P_384;
+        } else if (ecCurveName.equals("secp521r1")) {
+            return android.hardware.security.keymint.EcCurve.P_521;
+        }
+        return -1;
+    }
+
+    static ECParameterSpec getCurveSpec(String name)
+            throws NoSuchAlgorithmException, InvalidParameterSpecException {
+        AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
+        parameters.init(new ECGenParameterSpec(name));
+        return parameters.getParameterSpec(ECParameterSpec.class);
+    }
+
+    static String getCurveName(ECParameterSpec spec) {
+        if (KeymasterUtils.isECParameterSpecOfCurve(spec, "secp224r1")) {
+            return "secp224r1";
+        } else if (KeymasterUtils.isECParameterSpecOfCurve(spec, "secp256r1")) {
+            return "secp256r1";
+        } else if (KeymasterUtils.isECParameterSpecOfCurve(spec, "secp384r1")) {
+            return "secp384r1";
+        } else if (KeymasterUtils.isECParameterSpecOfCurve(spec, "secp521r1")) {
+            return "secp521r1";
+        }
+        return null;
+    }
+
+    private static boolean isECParameterSpecOfCurve(ECParameterSpec spec, String curveName) {
+        try {
+            ECParameterSpec curveSpec = KeymasterUtils.getCurveSpec(curveName);
+            if (curveSpec.getCurve().equals(spec.getCurve())
+                    && curveSpec.getOrder().equals(spec.getOrder())
+                    && curveSpec.getGenerator().equals(spec.getGenerator())) {
+                return true;
+            }
+        } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) {
+            return false;
+        }
+        return false;
+    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 74303e2..d7d43aa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -18,6 +18,12 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
+import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
+import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishPrimaryWithSecondary;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishSecondaryWithPrimary;
+
 import android.app.Activity;
 import android.app.WindowConfiguration.WindowingMode;
 import android.content.Intent;
@@ -31,7 +37,6 @@
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
 
-import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -79,26 +84,20 @@
     @Override
     public void unregisterOrganizer() {
         if (mAnimationController != null) {
-            mAnimationController.unregisterAllRemoteAnimations();
+            mAnimationController.unregisterRemoteAnimations();
             mAnimationController = null;
         }
         super.unregisterOrganizer();
     }
 
-    /** Overrides the animation if the transition is on the given Task. */
-    void startOverrideSplitAnimation(int taskId) {
+    /**
+     * Overrides the animation for transitions of embedded activities organized by this organizer.
+     */
+    void overrideSplitAnimation() {
         if (mAnimationController == null) {
             mAnimationController = new TaskFragmentAnimationController(this);
         }
-        mAnimationController.registerRemoteAnimations(taskId);
-    }
-
-    /** No longer overrides the animation if the transition is on the given Task. */
-    @GuardedBy("mLock")
-    void stopOverrideSplitAnimation(int taskId) {
-        if (mAnimationController != null) {
-            mAnimationController.unregisterRemoteAnimations(taskId);
-        }
+        mAnimationController.registerRemoteAnimations();
     }
 
     /**
@@ -140,6 +139,8 @@
 
         // Set adjacent to each other so that the containers below will be invisible.
         setAdjacentTaskFragments(wct, launchingFragmentToken, secondaryFragmentToken, rule);
+        setCompanionTaskFragment(wct, launchingFragmentToken, secondaryFragmentToken, rule,
+                false /* isStacked */);
     }
 
     /**
@@ -215,6 +216,28 @@
         wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
     }
 
+    void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule,
+            boolean isStacked) {
+        final boolean finishPrimaryWithSecondary;
+        if (isStacked) {
+            finishPrimaryWithSecondary = shouldFinishAssociatedContainerWhenStacked(
+                    getFinishPrimaryWithSecondaryBehavior(splitRule));
+        } else {
+            finishPrimaryWithSecondary = shouldFinishPrimaryWithSecondary(splitRule);
+        }
+        wct.setCompanionTaskFragment(primary, finishPrimaryWithSecondary ? secondary : null);
+
+        final boolean finishSecondaryWithPrimary;
+        if (isStacked) {
+            finishSecondaryWithPrimary = shouldFinishAssociatedContainerWhenStacked(
+                    getFinishSecondaryWithPrimaryBehavior(splitRule));
+        } else {
+            finishSecondaryWithPrimary = shouldFinishSecondaryWithPrimary(splitRule);
+        }
+        wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null);
+    }
+
     TaskFragmentCreationParams createFragmentOptions(@NonNull IBinder fragmentToken,
             @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
         if (mFragmentInfos.containsKey(fragmentToken)) {
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..6f9a4ff8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -41,7 +41,6 @@
 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
 import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
-import static androidx.window.extensions.embedding.SplitPresenter.getNonEmbeddedActivityBounds;
 import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
 
 import android.app.Activity;
@@ -193,7 +192,6 @@
                         continue;
                     }
                     updateContainersInTask(wct, taskContainer);
-                    updateAnimationOverride(taskContainer);
                 }
                 // The WCT should be applied and merged to the device state change transition if
                 // there is one.
@@ -208,9 +206,6 @@
         synchronized (mLock) {
             mSplitRules.clear();
             mSplitRules.addAll(rules);
-            for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-                updateAnimationOverride(mTaskContainers.valueAt(i));
-            }
         }
     }
 
@@ -389,6 +384,10 @@
                 // launching activity in the Task.
                 mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
                 mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
+            } else if (taskFragmentInfo.isClearedForReorderActivityToFront()) {
+                // Do not finish the dependents if this TaskFragment was cleared to reorder
+                // the launching Activity to front of the Task.
+                mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
             } else if (!container.isWaitingActivityAppear()) {
                 // Do not finish the container before the expected activity appear until
                 // timeout.
@@ -464,7 +463,6 @@
             // parentInfo#isVisibleRequested is true.
             return;
         }
-        onTaskContainerInfoChanged(taskContainer, parentInfo.getConfiguration());
         if (isInPictureInPicture(parentInfo.getConfiguration())) {
             // No need to update presentation in PIP until the Task exit PIP.
             return;
@@ -608,55 +606,12 @@
             }
             if (taskContainer.isEmpty()) {
                 // Cleanup the TaskContainer if it becomes empty.
-                mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
                 mTaskContainers.remove(taskContainer.getTaskId());
             }
             return;
         }
     }
 
-    @GuardedBy("mLock")
-    private void onTaskContainerInfoChanged(@NonNull TaskContainer taskContainer,
-            @NonNull Configuration config) {
-        final boolean wasInPip = taskContainer.isInPictureInPicture();
-        final boolean isInPIp = isInPictureInPicture(config);
-
-        // We need to check the animation override when enter/exit PIP or has bounds changed.
-        boolean shouldUpdateAnimationOverride = wasInPip != isInPIp;
-        if (taskContainer.setTaskBounds(config.windowConfiguration.getBounds())
-                && !isInPIp) {
-            // We don't care the bounds change when it has already entered PIP.
-            shouldUpdateAnimationOverride = true;
-        }
-        if (shouldUpdateAnimationOverride) {
-            updateAnimationOverride(taskContainer);
-        }
-    }
-
-    /**
-     * Updates if we should override transition animation. We only want to override if the Task
-     * bounds is large enough for at least one split rule.
-     */
-    @GuardedBy("mLock")
-    private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
-        if (ENABLE_SHELL_TRANSITIONS) {
-            // TODO(b/207070762): cleanup with legacy app transition
-            // Animation will be handled by WM Shell with Shell transition enabled.
-            return;
-        }
-        if (!taskContainer.isTaskBoundsInitialized()) {
-            // We don't know about the Task bounds/windowingMode yet.
-            return;
-        }
-
-        // We only want to override if the TaskContainer may show split.
-        if (mayShowSplit(taskContainer)) {
-            mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId());
-        } else {
-            mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
-        }
-    }
-
     /** Returns whether the given {@link TaskContainer} may show in split. */
     // Suppress GuardedBy warning because lint asks to mark this method as
     // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
@@ -1010,10 +965,16 @@
     @VisibleForTesting
     @GuardedBy("mLock")
     void onActivityDestroyed(@NonNull Activity activity) {
+        if (!activity.isFinishing()) {
+            // onDestroyed is triggered without finishing. This happens when the activity is
+            // relaunched. In this case, we don't want to cleanup the record.
+            return;
+        }
         // Remove any pending appeared activity, as the server won't send finished activity to the
         // organizer.
+        final IBinder activityToken = activity.getActivityToken();
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            mTaskContainers.valueAt(i).onActivityDestroyed(activity);
+            mTaskContainers.valueAt(i).onActivityDestroyed(activityToken);
         }
         // We didn't trigger the callback if there were any pending appeared activities, so check
         // again after the pending is removed.
@@ -1215,16 +1176,33 @@
      * Returns a container that this activity is registered with. An activity can only belong to one
      * container, or no container at all.
      */
+    @GuardedBy("mLock")
     @Nullable
     TaskFragmentContainer getContainerWithActivity(@NonNull Activity activity) {
-        final IBinder activityToken = activity.getActivityToken();
+        return getContainerWithActivity(activity.getActivityToken());
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
+        // Check pending appeared activity first because there can be a delay for the server
+        // update.
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
             final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
-            // Traverse from top to bottom in case an activity is added to top pending, and hasn't
-            // received update from server yet.
             for (int j = containers.size() - 1; j >= 0; j--) {
                 final TaskFragmentContainer container = containers.get(j);
-                if (container.hasActivity(activityToken)) {
+                if (container.hasPendingAppearedActivity(activityToken)) {
+                    return container;
+                }
+            }
+        }
+
+        // Check appeared activity if there is no such pending appeared activity.
+        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+            for (int j = containers.size() - 1; j >= 0; j--) {
+                final TaskFragmentContainer container = containers.get(j);
+                if (container.hasAppearedActivity(activityToken)) {
                     return container;
                 }
             }
@@ -1272,14 +1250,6 @@
         final TaskContainer taskContainer = mTaskContainers.get(taskId);
         final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
                 pendingAppearedIntent, taskContainer, this);
-        if (!taskContainer.isTaskBoundsInitialized()) {
-            // Get the initial bounds before the TaskFragment has appeared.
-            final Rect taskBounds = getNonEmbeddedActivityBounds(activityInTask);
-            if (!taskContainer.setTaskBounds(taskBounds)) {
-                Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
-            }
-        }
-        updateAnimationOverride(taskContainer);
         return container;
     }
 
@@ -1873,6 +1843,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 +1879,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 +1901,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 +1919,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 +1959,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..5395fb2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -139,6 +139,11 @@
         super(executor, controller);
         mController = controller;
         registerOrganizer();
+        if (!SplitController.ENABLE_SHELL_TRANSITIONS) {
+            // TODO(b/207070762): cleanup with legacy app transition
+            // Animation will be handled by WM Shell when Shell transition is enabled.
+            overrideSplitAnimation();
+        }
     }
 
     /**
@@ -227,7 +232,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.
@@ -371,13 +376,16 @@
             @NonNull SplitAttributes splitAttributes) {
         // Clear adjacent TaskFragments if the container is shown in fullscreen, or the
         // secondaryContainer could not be finished.
-        if (!shouldShowSplit(splitAttributes)) {
+        boolean isStacked = !shouldShowSplit(splitAttributes);
+        if (isStacked) {
             setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
                     null /* secondary */, null /* splitRule */);
         } else {
             setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
                     secondaryContainer.getTaskFragmentToken(), splitRule);
         }
+        setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(),
+                secondaryContainer.getTaskFragmentToken(), splitRule, isStacked);
     }
 
     /**
@@ -489,8 +497,15 @@
                     || splitContainer.getSecondaryContainer().getInfo() == null) {
                 return RESULT_EXPAND_FAILED_NO_TF_INFO;
             }
-            expandTaskFragment(wct, splitContainer.getPrimaryContainer().getTaskFragmentToken());
-            expandTaskFragment(wct, splitContainer.getSecondaryContainer().getTaskFragmentToken());
+            final IBinder primaryToken =
+                    splitContainer.getPrimaryContainer().getTaskFragmentToken();
+            final IBinder secondaryToken =
+                    splitContainer.getSecondaryContainer().getTaskFragmentToken();
+            expandTaskFragment(wct, primaryToken);
+            expandTaskFragment(wct, secondaryToken);
+            // Set the companion TaskFragment when the two containers stacked.
+            setCompanionTaskFragment(wct, primaryToken, secondaryToken,
+                    splitContainer.getSplitRule(), true /* isStacked */);
             return RESULT_EXPANDED;
         }
         return RESULT_NOT_EXPANDED;
@@ -917,11 +932,7 @@
         if (taskContainer != null) {
             return taskContainer.getTaskProperties();
         }
-        // Use a copy of configuration because activity's configuration may be updated later,
-        // or we may get unexpected TaskContainer's configuration if Activity's configuration is
-        // updated. An example is Activity is going to be in split.
-        return new TaskProperties(activity.getDisplayId(),
-                new Configuration(activity.getResources().getConfiguration()));
+        return TaskProperties.getTaskPropertiesFromActivity(activity);
     }
 
     @NonNull
@@ -935,16 +946,4 @@
         // TODO(b/190433398): Supply correct insets.
         return new WindowMetrics(taskBounds, WindowInsets.CONSUMED);
     }
-
-    /** Obtains the bounds from a non-embedded Activity. */
-    @NonNull
-    static Rect getNonEmbeddedActivityBounds(@NonNull Activity activity) {
-        final WindowConfiguration windowConfiguration =
-                activity.getResources().getConfiguration().windowConfiguration;
-        if (!activity.isInMultiWindowMode()) {
-            // In fullscreen mode the max bounds should correspond to the task bounds.
-            return windowConfiguration.getMaxBounds();
-        }
-        return windowConfiguration.getBounds();
-    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 231da05..03f4dc9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -20,14 +20,17 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.inMultiWindowMode;
 
 import android.app.Activity;
+import android.app.ActivityClient;
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.util.ArraySet;
+import android.util.Log;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentParentInfo;
 import android.window.WindowContainerTransaction;
@@ -41,14 +44,11 @@
 
 /** Represents TaskFragments and split pairs below a Task. */
 class TaskContainer {
+    private static final String TAG = TaskContainer.class.getSimpleName();
 
     /** The unique task id. */
     private final int mTaskId;
 
-    // TODO(b/240219484): consolidate to mConfiguration
-    /** Available window bounds of this Task. */
-    private final Rect mTaskBounds = new Rect();
-
     /** Active TaskFragments in this Task. */
     @NonNull
     final List<TaskFragmentContainer> mContainers = new ArrayList<>();
@@ -86,10 +86,10 @@
             throw new IllegalArgumentException("Invalid Task id");
         }
         mTaskId = taskId;
-        // Make a copy in case the activity's config is updated, and updates the TaskContainer's
-        // config unexpectedly.
-        mConfiguration = new Configuration(activityInTask.getResources().getConfiguration());
-        mDisplayId = activityInTask.getDisplayId();
+        final TaskProperties taskProperties = TaskProperties
+                .getTaskPropertiesFromActivity(activityInTask);
+        mConfiguration = taskProperties.getConfiguration();
+        mDisplayId = taskProperties.getDisplayId();
         // Note that it is always called when there's a new Activity is started, which implies
         // the host task is visible.
         mIsVisible = true;
@@ -108,25 +108,6 @@
     }
 
     @NonNull
-    Rect getTaskBounds() {
-        return mTaskBounds;
-    }
-
-    /** Returns {@code true} if the bounds is changed. */
-    boolean setTaskBounds(@NonNull Rect taskBounds) {
-        if (!taskBounds.isEmpty() && !mTaskBounds.equals(taskBounds)) {
-            mTaskBounds.set(taskBounds);
-            return true;
-        }
-        return false;
-    }
-
-    /** Whether the Task bounds has been initialized. */
-    boolean isTaskBoundsInitialized() {
-        return !mTaskBounds.isEmpty();
-    }
-
-    @NonNull
     Configuration getConfiguration() {
         // Make a copy in case the config is updated unexpectedly.
         return new Configuration(mConfiguration);
@@ -185,16 +166,16 @@
     }
 
     /** Called when the activity is destroyed. */
-    void onActivityDestroyed(@NonNull Activity activity) {
+    void onActivityDestroyed(@NonNull IBinder activityToken) {
         for (TaskFragmentContainer container : mContainers) {
-            container.onActivityDestroyed(activity);
+            container.onActivityDestroyed(activityToken);
         }
     }
 
     /** Removes the pending appeared activity from all TaskFragments in this Task. */
-    void cleanupPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
+    void cleanupPendingAppearedActivity(@NonNull IBinder activityToken) {
         for (TaskFragmentContainer container : mContainers) {
-            container.removePendingAppearedActivity(pendingAppearedActivity);
+            container.removePendingAppearedActivity(activityToken);
         }
     }
 
@@ -261,5 +242,45 @@
         Configuration getConfiguration() {
             return mConfiguration;
         }
+
+        /**
+         * Obtains the {@link TaskProperties} for the task that the provided {@link Activity} is
+         * associated with.
+         * <p>
+         * Note that for most case, caller should use
+         * {@link SplitPresenter#getTaskProperties(Activity)} instead. This method is used before
+         * the {@code activity} goes into split.
+         * </p><p>
+         * If the {@link Activity} is in fullscreen, override
+         * {@link WindowConfiguration#getBounds()} with {@link WindowConfiguration#getMaxBounds()}
+         * in case the {@link Activity} is letterboxed. Otherwise, get the Task
+         * {@link Configuration} from the server side or use {@link Activity}'s
+         * {@link Configuration} as a fallback if the Task {@link Configuration} cannot be obtained.
+         */
+        @NonNull
+        static TaskProperties getTaskPropertiesFromActivity(@NonNull Activity activity) {
+            final int displayId = activity.getDisplayId();
+            // Use a copy of configuration because activity's configuration may be updated later,
+            // or we may get unexpected TaskContainer's configuration if Activity's configuration is
+            // updated. An example is Activity is going to be in split.
+            final Configuration activityConfig = new Configuration(
+                    activity.getResources().getConfiguration());
+            final WindowConfiguration windowConfiguration = activityConfig.windowConfiguration;
+            final int windowingMode = windowConfiguration.getWindowingMode();
+            if (!inMultiWindowMode(windowingMode)) {
+                // Use the max bounds in fullscreen in case the Activity is letterboxed.
+                windowConfiguration.setBounds(windowConfiguration.getMaxBounds());
+                return new TaskProperties(displayId, activityConfig);
+            }
+            final Configuration taskConfig = ActivityClient.getInstance()
+                    .getTaskConfiguration(activity.getActivityToken());
+            if (taskConfig == null) {
+                Log.w(TAG, "Could not obtain task configuration for activity:" + activity);
+                // Still report activity config if task config cannot be obtained from the server
+                // side.
+                return new TaskProperties(displayId, activityConfig);
+            }
+            return new TaskProperties(displayId, taskConfig);
+        }
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
index af5d8c5..33220c4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
@@ -18,7 +18,9 @@
 
 import static android.graphics.Matrix.MTRANS_X;
 import static android.graphics.Matrix.MTRANS_Y;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.view.Choreographer;
 import android.view.RemoteAnimationTarget;
@@ -49,6 +51,16 @@
     /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
     @NonNull
     private final Rect mWholeAnimationBounds = new Rect();
+    /**
+     * Area in absolute coordinate that should represent all the content to show for this window.
+     * This should be the end bounds for opening window, and start bounds for closing window in case
+     * the window is resizing during the open/close transition.
+     */
+    @NonNull
+    private final Rect mContentBounds = new Rect();
+    /** Offset relative to the window parent surface for {@link #mContentBounds}. */
+    @NonNull
+    private final Point mContentRelOffset = new Point();
 
     @NonNull
     final Transformation mTransformation = new Transformation();
@@ -78,6 +90,21 @@
         mTarget = target;
         mLeash = leash;
         mWholeAnimationBounds.set(wholeAnimationBounds);
+        if (target.mode == MODE_CLOSING) {
+            // When it is closing, we want to show the content at the start position in case the
+            // window is resizing as well. For example, when the activities is changing from split
+            // to stack, the bottom TaskFragment will be resized to fullscreen when hiding.
+            final Rect startBounds = target.startBounds;
+            final Rect endBounds = target.screenSpaceBounds;
+            mContentBounds.set(startBounds);
+            mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
+            mContentRelOffset.offset(
+                    startBounds.left - endBounds.left,
+                    startBounds.top - endBounds.top);
+        } else {
+            mContentBounds.set(target.screenSpaceBounds);
+            mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
+        }
     }
 
     /**
@@ -108,8 +135,7 @@
     /** To be overridden by subclasses to adjust the animation surface change. */
     void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
         // Update the surface position and alpha.
-        mTransformation.getMatrix().postTranslate(
-                mTarget.localBounds.left, mTarget.localBounds.top);
+        mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
         t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
         t.setAlpha(mLeash, mTransformation.getAlpha());
 
@@ -117,9 +143,8 @@
         // positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
         final int positionX = Math.round(mMatrix[MTRANS_X]);
         final int positionY = Math.round(mMatrix[MTRANS_Y]);
-        final Rect cropRect = new Rect(mTarget.screenSpaceBounds);
-        final Rect localBounds = mTarget.localBounds;
-        cropRect.offset(positionX - localBounds.left, positionY - localBounds.top);
+        final Rect cropRect = new Rect(mContentBounds);
+        cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y);
 
         // Store the current offset of the surface top left from (0,0) in absolute coordinate.
         final int offsetX = cropRect.left;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
index ee2e139..d7eb9a0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -18,13 +18,10 @@
 
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
 
-import android.util.ArraySet;
 import android.util.Log;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
@@ -44,8 +41,7 @@
     private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
     @VisibleForTesting
     final RemoteAnimationDefinition mDefinition;
-    /** Task Ids that we have registered for remote animation. */
-    private final ArraySet<Integer> mRegisterTasks = new ArraySet<>();
+    private boolean mIsRegistered;
 
     TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
         mOrganizer = organizer;
@@ -54,39 +50,30 @@
                 new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
         mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter);
         mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
-        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_OPEN, animationAdapter);
         mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter);
         mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
-        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_CLOSE, animationAdapter);
         mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
     }
 
-    void registerRemoteAnimations(int taskId) {
+    void registerRemoteAnimations() {
         if (DEBUG) {
             Log.v(TAG, "registerRemoteAnimations");
         }
-        if (mRegisterTasks.contains(taskId)) {
+        if (mIsRegistered) {
             return;
         }
-        mOrganizer.registerRemoteAnimations(taskId, mDefinition);
-        mRegisterTasks.add(taskId);
+        mOrganizer.registerRemoteAnimations(mDefinition);
+        mIsRegistered = true;
     }
 
-    void unregisterRemoteAnimations(int taskId) {
+    void unregisterRemoteAnimations() {
         if (DEBUG) {
             Log.v(TAG, "unregisterRemoteAnimations");
         }
-        if (!mRegisterTasks.contains(taskId)) {
+        if (!mIsRegistered) {
             return;
         }
-        mOrganizer.unregisterRemoteAnimations(taskId);
-        mRegisterTasks.remove(taskId);
-    }
-
-    void unregisterAllRemoteAnimations() {
-        final ArraySet<Integer> tasks = new ArraySet<>(mRegisterTasks);
-        for (int taskId : tasks) {
-            unregisterRemoteAnimations(taskId);
-        }
+        mOrganizer.unregisterRemoteAnimations();
+        mIsRegistered = false;
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index 8c416e8..322f854 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -17,14 +17,13 @@
 package androidx.window.extensions.embedding;
 
 import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+import static android.view.RemoteAnimationTarget.MODE_CHANGING;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
 
 import android.animation.Animator;
@@ -169,11 +168,9 @@
         switch (transit) {
             case TRANSIT_OLD_ACTIVITY_OPEN:
             case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
-            case TRANSIT_OLD_TASK_OPEN:
                 return createOpenAnimationAdapters(targets);
             case TRANSIT_OLD_ACTIVITY_CLOSE:
             case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
-            case TRANSIT_OLD_TASK_CLOSE:
                 return createCloseAnimationAdapters(targets);
             case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
                 return createChangeAnimationAdapters(targets);
@@ -258,7 +255,7 @@
             @NonNull RemoteAnimationTarget[] targets) {
         final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
         for (RemoteAnimationTarget target : targets) {
-            if (target.startBounds != null) {
+            if (target.mode == MODE_CHANGING) {
                 // This is the target with bounds change.
                 final Animation[] animations =
                         mAnimationSpec.createChangeBoundsChangeAnimations(target);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index a7d47ef..1f866c3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -86,13 +86,23 @@
     /** Animation for target that is opening in a change transition. */
     @NonNull
     Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
-        final Rect bounds = target.localBounds;
-        // The target will be animated in from left or right depends on its position.
-        final int startLeft = bounds.left == 0 ? -bounds.width() : bounds.width();
+        final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+        final Rect bounds = target.screenSpaceBounds;
+        final int startLeft;
+        final int startTop;
+        if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+            // The window will be animated in from left or right depending on its position.
+            startTop = 0;
+            startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+        } else {
+            // The window will be animated in from top or bottom depending on its position.
+            startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+            startLeft = 0;
+        }
 
         // The position should be 0-based as we will post translate in
         // TaskFragmentAnimationAdapter#onAnimationUpdate
-        final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0);
+        final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0);
         animation.setInterpolator(mFastOutExtraSlowInInterpolator);
         animation.setDuration(CHANGE_ANIMATION_DURATION);
         animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
@@ -103,13 +113,24 @@
     /** Animation for target that is closing in a change transition. */
     @NonNull
     Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
-        final Rect bounds = target.localBounds;
-        // The target will be animated out to left or right depends on its position.
-        final int endLeft = bounds.left == 0 ? -bounds.width() : bounds.width();
+        final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+        // Use startBounds if the window is closing in case it may also resize.
+        final Rect bounds = target.startBounds;
+        final int endTop;
+        final int endLeft;
+        if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+            // The window will be animated out to left or right depending on its position.
+            endTop = 0;
+            endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+        } else {
+            // The window will be animated out to top or bottom depending on its position.
+            endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+            endLeft = 0;
+        }
 
         // The position should be 0-based as we will post translate in
         // TaskFragmentAnimationAdapter#onAnimationUpdate
-        final Animation animation = new TranslateAnimation(0, endLeft, 0, 0);
+        final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop);
         animation.setInterpolator(mFastOutExtraSlowInInterpolator);
         animation.setDuration(CHANGE_ANIMATION_DURATION);
         animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 71b8840..e31792a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -43,6 +43,9 @@
  * Client-side container for a stack of activities. Corresponds to an instance of TaskFragment
  * on the server side.
  */
+// Suppress GuardedBy warning because all the TaskFragmentContainers are stored in
+// SplitController.mTaskContainers which is guarded.
+@SuppressWarnings("GuardedBy")
 class TaskFragmentContainer {
     private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000;
 
@@ -66,11 +69,11 @@
     TaskFragmentInfo mInfo;
 
     /**
-     * Activities that are being reparented or being started to this container, but haven't been
-     * added to {@link #mInfo} yet.
+     * Activity tokens that are being reparented or being started to this container, but haven't
+     * been added to {@link #mInfo} yet.
      */
     @VisibleForTesting
-    final ArrayList<Activity> mPendingAppearedActivities = new ArrayList<>();
+    final ArrayList<IBinder> mPendingAppearedActivities = new ArrayList<>();
 
     /**
      * When this container is created for an {@link Intent} to start within, we store that Intent
@@ -84,8 +87,11 @@
     private final List<TaskFragmentContainer> mContainersToFinishOnExit =
             new ArrayList<>();
 
-    /** Individual associated activities in different containers that should be finished on exit. */
-    private final List<Activity> mActivitiesToFinishOnExit = new ArrayList<>();
+    /**
+     * Individual associated activity tokens in different containers that should be finished on
+     * exit.
+     */
+    private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
 
     /** Indicates whether the container was cleaned up after the last activity was removed. */
     private boolean mIsFinished;
@@ -158,8 +164,9 @@
         // in this intermediate state.
         // Place those on top of the list since they will be on the top after reported from the
         // server.
-        for (Activity activity : mPendingAppearedActivities) {
-            if (!activity.isFinishing()) {
+        for (IBinder token : mPendingAppearedActivities) {
+            final Activity activity = mController.getActivity(token);
+            if (activity != null && !activity.isFinishing()) {
                 allActivities.add(activity);
             }
         }
@@ -203,55 +210,58 @@
 
     /** Adds the activity that will be reparented to this container. */
     void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
-        if (hasActivity(pendingAppearedActivity.getActivityToken())) {
+        final IBinder activityToken = pendingAppearedActivity.getActivityToken();
+        if (hasActivity(activityToken)) {
             return;
         }
-        // Remove the pending activity from other TaskFragments.
-        mTaskContainer.cleanupPendingAppearedActivity(pendingAppearedActivity);
-        mPendingAppearedActivities.add(pendingAppearedActivity);
-        updateActivityClientRecordTaskFragmentToken(pendingAppearedActivity);
+        // Remove the pending activity from other TaskFragments in case the activity is reparented
+        // again before the server update.
+        mTaskContainer.cleanupPendingAppearedActivity(activityToken);
+        mPendingAppearedActivities.add(activityToken);
+        updateActivityClientRecordTaskFragmentToken(activityToken);
     }
 
     /**
      * Updates the {@link ActivityThread.ActivityClientRecord#mTaskFragmentToken} for the
      * activity. This makes sure the token is up-to-date if the activity is relaunched later.
      */
-    private void updateActivityClientRecordTaskFragmentToken(@NonNull Activity activity) {
+    private void updateActivityClientRecordTaskFragmentToken(@NonNull IBinder activityToken) {
         final ActivityThread.ActivityClientRecord record = ActivityThread
-                .currentActivityThread().getActivityClient(activity.getActivityToken());
+                .currentActivityThread().getActivityClient(activityToken);
         if (record != null) {
             record.mTaskFragmentToken = mToken;
         }
     }
 
-    void removePendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
-        mPendingAppearedActivities.remove(pendingAppearedActivity);
+    void removePendingAppearedActivity(@NonNull IBinder activityToken) {
+        mPendingAppearedActivities.remove(activityToken);
     }
 
     void clearPendingAppearedActivities() {
-        final List<Activity> cleanupActivities = new ArrayList<>(mPendingAppearedActivities);
+        final List<IBinder> cleanupActivities = new ArrayList<>(mPendingAppearedActivities);
         // Clear mPendingAppearedActivities so that #getContainerWithActivity won't return the
         // current TaskFragment.
         mPendingAppearedActivities.clear();
         mPendingAppearedIntent = null;
 
         // For removed pending activities, we need to update the them to their previous containers.
-        for (Activity activity : cleanupActivities) {
+        for (IBinder activityToken : cleanupActivities) {
             final TaskFragmentContainer curContainer = mController.getContainerWithActivity(
-                    activity);
+                    activityToken);
             if (curContainer != null) {
-                curContainer.updateActivityClientRecordTaskFragmentToken(activity);
+                curContainer.updateActivityClientRecordTaskFragmentToken(activityToken);
             }
         }
     }
 
     /** Called when the activity is destroyed. */
-    void onActivityDestroyed(@NonNull Activity activity) {
-        removePendingAppearedActivity(activity);
+    void onActivityDestroyed(@NonNull IBinder activityToken) {
+        removePendingAppearedActivity(activityToken);
         if (mInfo != null) {
             // Remove the activity now because there can be a delay before the server callback.
-            mInfo.getActivities().remove(activity.getActivityToken());
+            mInfo.getActivities().remove(activityToken);
         }
+        mActivitiesToFinishOnExit.remove(activityToken);
     }
 
     @Nullable
@@ -275,16 +285,24 @@
         mPendingAppearedIntent = null;
     }
 
-    boolean hasActivity(@NonNull IBinder token) {
-        if (mInfo != null && mInfo.getActivities().contains(token)) {
-            return true;
-        }
-        for (Activity activity : mPendingAppearedActivities) {
-            if (activity.getActivityToken().equals(token)) {
-                return true;
-            }
-        }
-        return false;
+    boolean hasActivity(@NonNull IBinder activityToken) {
+        // Instead of using (hasAppearedActivity() || hasPendingAppearedActivity), we want to make
+        // sure the controller considers this container as the one containing the activity.
+        // This is needed when the activity is added as pending appeared activity to one
+        // TaskFragment while it is also an appeared activity in another.
+        return mController.getContainerWithActivity(activityToken) == this;
+    }
+
+    /** Whether this activity has appeared in the TaskFragment on the server side. */
+    boolean hasAppearedActivity(@NonNull IBinder activityToken) {
+        return mInfo != null && mInfo.getActivities().contains(activityToken);
+    }
+
+    /**
+     * Whether we are waiting for this activity to appear in the TaskFragment on the server side.
+     */
+    boolean hasPendingAppearedActivity(@NonNull IBinder activityToken) {
+        return mPendingAppearedActivities.contains(activityToken);
     }
 
     int getRunningActivityCount() {
@@ -342,8 +360,8 @@
         // Cleanup activities that were being re-parented
         List<IBinder> infoActivities = mInfo.getActivities();
         for (int i = mPendingAppearedActivities.size() - 1; i >= 0; --i) {
-            final Activity activity = mPendingAppearedActivities.get(i);
-            if (infoActivities.contains(activity.getActivityToken())) {
+            final IBinder activityToken = mPendingAppearedActivities.get(i);
+            if (infoActivities.contains(activityToken)) {
                 mPendingAppearedActivities.remove(i);
             }
         }
@@ -392,7 +410,7 @@
         if (mIsFinished) {
             return;
         }
-        mActivitiesToFinishOnExit.add(activityToFinish);
+        mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken());
     }
 
     /**
@@ -402,7 +420,7 @@
         if (mIsFinished) {
             return;
         }
-        mActivitiesToFinishOnExit.remove(activityToRemove);
+        mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken());
     }
 
     /** Removes all dependencies that should be finished when this container is finished. */
@@ -470,8 +488,9 @@
         mContainersToFinishOnExit.clear();
 
         // Finish associated activities
-        for (Activity activity : mActivitiesToFinishOnExit) {
-            if (activity.isFinishing()
+        for (IBinder activityToken : mActivitiesToFinishOnExit) {
+            final Activity activity = mController.getActivity(activityToken);
+            if (activity == null || activity.isFinishing()
                     || controller.shouldRetainAssociatedActivity(this, activity)) {
                 continue;
             }
@@ -540,7 +559,8 @@
         }
         int maxMinWidth = mInfo.getMinimumWidth();
         int maxMinHeight = mInfo.getMinimumHeight();
-        for (Activity activity : mPendingAppearedActivities) {
+        for (IBinder activityToken : mPendingAppearedActivities) {
+            final Activity activity = mController.getActivity(activityToken);
             final Size minDimensions = SplitPresenter.getMinDimensions(activity);
             if (minDimensions == null) {
                 continue;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index b516e140..b70b320 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -35,7 +35,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.util.ArrayMap;
-import android.window.WindowContext;
+import android.window.WindowProvider;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -71,7 +71,7 @@
 
     private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>();
 
-    private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners =
+    private final Map<IBinder, ConfigurationChangeListener> mConfigurationChangeListeners =
             new ArrayMap<>();
 
     public WindowLayoutComponentImpl(@NonNull Context context) {
@@ -121,21 +121,21 @@
         }
         if (!context.isUiContext()) {
             throw new IllegalArgumentException("Context must be a UI Context, which should be"
-                    + " an Activity or a WindowContext");
+                    + " an Activity, WindowContext or InputMethodService");
         }
         mFoldingFeatureProducer.getData((features) -> {
-            // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
             WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features);
             consumer.accept(newWindowLayout);
         });
         mWindowLayoutChangeListeners.put(context, consumer);
 
-        if (context instanceof WindowContext) {
+        // TODO(b/258065175) Further extend this to ContextWrappers.
+        if (context instanceof WindowProvider) {
             final IBinder windowContextToken = context.getWindowContextToken();
-            final WindowContextConfigListener listener =
-                    new WindowContextConfigListener(windowContextToken);
+            final ConfigurationChangeListener listener =
+                    new ConfigurationChangeListener(windowContextToken);
             context.registerComponentCallbacks(listener);
-            mWindowContextConfigListeners.put(windowContextToken, listener);
+            mConfigurationChangeListeners.put(windowContextToken, listener);
         }
     }
 
@@ -150,10 +150,10 @@
             if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) {
                 continue;
             }
-            if (context instanceof WindowContext) {
+            if (context instanceof WindowProvider) {
                 final IBinder token = context.getWindowContextToken();
-                context.unregisterComponentCallbacks(mWindowContextConfigListeners.get(token));
-                mWindowContextConfigListeners.remove(token);
+                context.unregisterComponentCallbacks(mConfigurationChangeListeners.get(token));
+                mConfigurationChangeListeners.remove(token);
             }
             break;
         }
@@ -309,20 +309,21 @@
         }
         final int windowingMode;
         if (context instanceof Activity) {
-            windowingMode = ActivityClient.getInstance().getTaskWindowingMode(
+            final Configuration taskConfig = ActivityClient.getInstance().getTaskConfiguration(
                     context.getActivityToken());
+            if (taskConfig == null) {
+                // If we cannot determine the task configuration for any reason, it is likely that
+                // we won't be able to determine its position correctly as well. DisplayFeatures'
+                // bounds in this case can't be computed correctly, so we should skip.
+                return false;
+            }
+            windowingMode = taskConfig.windowConfiguration.getWindowingMode();
         } else {
             // TODO(b/242674941): use task windowing mode for window context that associates with
             //  activity.
             windowingMode = context.getResources().getConfiguration().windowConfiguration
                     .getWindowingMode();
         }
-        if (windowingMode == -1) {
-            // If we cannot determine the task windowing mode for any reason, it is likely that we
-            // won't be able to determine its position correctly as well. DisplayFeatures' bounds
-            // in this case can't be computed correctly, so we should skip.
-            return false;
-        }
         // It is recommended not to report any display features in multi-window mode, since it
         // won't be possible to synchronize the display feature positions with window movement.
         return !WindowConfiguration.inMultiWindowMode(windowingMode);
@@ -349,10 +350,10 @@
         }
     }
 
-    private final class WindowContextConfigListener implements ComponentCallbacks {
+    private final class ConfigurationChangeListener implements ComponentCallbacks {
         final IBinder mToken;
 
-        WindowContextConfigListener(IBinder token) {
+        ConfigurationChangeListener(IBinder token) {
             mToken = token;
         }
 
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index 40f7a27..bc03e4e 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -21,6 +21,7 @@
 import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS;
 import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER;
 
+import static org.junit.Assert.assertFalse;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
@@ -45,6 +46,8 @@
 import java.util.Collections;
 import java.util.List;
 
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
 public class EmbeddingTestUtils {
     static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
     static final int TASK_ID = 10;
@@ -169,6 +172,7 @@
                 new Point(),
                 false /* isTaskClearedForReuse */,
                 false /* isTaskFragmentClearedForPip */,
+                false /* isClearedForReorderActivityToFront */,
                 new Point());
     }
 
@@ -190,6 +194,15 @@
         return new TaskContainer(TASK_ID, activity);
     }
 
+    static TaskContainer createTestTaskContainer(@NonNull SplitController controller) {
+        final TaskContainer taskContainer = createTestTaskContainer();
+        final int taskId = taskContainer.getTaskId();
+        // Should not call to create TaskContainer with the same task id twice.
+        assertFalse(controller.mTaskContainers.contains(taskId));
+        controller.mTaskContainers.put(taskId, taskContainer);
+        return taskContainer;
+    }
+
     static WindowLayoutInfo createWindowLayoutInfo() {
         final FoldingFeature foldingFeature = new FoldingFeature(
                 new Rect(
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 957a248..31aa09c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -18,7 +18,6 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -26,10 +25,8 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -85,35 +82,20 @@
 
     @Test
     public void testUnregisterOrganizer() {
-        mOrganizer.startOverrideSplitAnimation(TASK_ID);
-        mOrganizer.startOverrideSplitAnimation(TASK_ID + 1);
+        mOrganizer.overrideSplitAnimation();
         mOrganizer.unregisterOrganizer();
 
-        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
-        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
+        verify(mOrganizer).unregisterRemoteAnimations();
     }
 
     @Test
-    public void testStartOverrideSplitAnimation() {
+    public void testOverrideSplitAnimation() {
         assertNull(mOrganizer.mAnimationController);
 
-        mOrganizer.startOverrideSplitAnimation(TASK_ID);
+        mOrganizer.overrideSplitAnimation();
 
         assertNotNull(mOrganizer.mAnimationController);
-        verify(mOrganizer).registerRemoteAnimations(TASK_ID,
-                mOrganizer.mAnimationController.mDefinition);
-    }
-
-    @Test
-    public void testStopOverrideSplitAnimation() {
-        mOrganizer.stopOverrideSplitAnimation(TASK_ID);
-
-        verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
-
-        mOrganizer.startOverrideSplitAnimation(TASK_ID);
-        mOrganizer.stopOverrideSplitAnimation(TASK_ID);
-
-        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+        verify(mOrganizer).registerRemoteAnimations(mOrganizer.mAnimationController.mDefinition);
     }
 
     @Test
@@ -144,6 +126,6 @@
                 mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */,
                 false /* isVisible */, new ArrayList<>(), new Point(),
                 false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */,
-                new Point());
+                false /* isClearedForReorderActivityToFront */, new Point());
     }
 }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 87d0278..221c764 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -242,6 +242,14 @@
 
         assertTrue(tf.hasActivity(mActivity.getActivityToken()));
 
+        // When the activity is not finishing, do not clear the record.
+        doReturn(false).when(mActivity).isFinishing();
+        mSplitController.onActivityDestroyed(mActivity);
+
+        assertTrue(tf.hasActivity(mActivity.getActivityToken()));
+
+        // Clear the record when the activity is finishing and destroyed.
+        doReturn(true).when(mActivity).isFinishing();
         mSplitController.onActivityDestroyed(mActivity);
 
         assertFalse(tf.hasActivity(mActivity.getActivityToken()));
@@ -261,7 +269,7 @@
 
         assertNotNull(tf);
         assertNotNull(taskContainer);
-        assertEquals(TASK_BOUNDS, taskContainer.getTaskBounds());
+        assertEquals(TASK_BOUNDS, taskContainer.getConfiguration().windowConfiguration.getBounds());
     }
 
     @Test
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index af9c6ba..95328ce 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -23,7 +23,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
 
 import static org.junit.Assert.assertEquals;
@@ -68,28 +67,6 @@
     }
 
     @Test
-    public void testIsTaskBoundsInitialized() {
-        final TaskContainer taskContainer = createTestTaskContainer();
-
-        assertFalse(taskContainer.isTaskBoundsInitialized());
-
-        taskContainer.setTaskBounds(TASK_BOUNDS);
-
-        assertTrue(taskContainer.isTaskBoundsInitialized());
-    }
-
-    @Test
-    public void testSetTaskBounds() {
-        final TaskContainer taskContainer = createTestTaskContainer();
-
-        assertFalse(taskContainer.setTaskBounds(new Rect()));
-
-        assertTrue(taskContainer.setTaskBounds(TASK_BOUNDS));
-
-        assertFalse(taskContainer.setTaskBounds(TASK_BOUNDS));
-    }
-
-    @Test
     public void testGetWindowingModeForSplitTaskFragment() {
         final TaskContainer taskContainer = createTestTaskContainer();
         final Rect splitBounds = new Rect(0, 0, 500, 1000);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
index d31342b..379ea0c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
@@ -16,11 +16,8 @@
 
 package androidx.window.extensions.embedding;
 
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
-
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.never;
 
 import android.platform.test.annotations.Presubmit;
@@ -57,41 +54,31 @@
 
     @Test
     public void testRegisterRemoteAnimations() {
-        mAnimationController.registerRemoteAnimations(TASK_ID);
+        mAnimationController.registerRemoteAnimations();
 
-        verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
+        verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
 
-        mAnimationController.registerRemoteAnimations(TASK_ID);
+        mAnimationController.registerRemoteAnimations();
 
         // No extra call if it has been registered.
-        verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
+        verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
     }
 
     @Test
     public void testUnregisterRemoteAnimations() {
-        mAnimationController.unregisterRemoteAnimations(TASK_ID);
+        mAnimationController.unregisterRemoteAnimations();
 
         // No call if it is not registered.
-        verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
+        verify(mOrganizer, never()).unregisterRemoteAnimations();
 
-        mAnimationController.registerRemoteAnimations(TASK_ID);
-        mAnimationController.unregisterRemoteAnimations(TASK_ID);
+        mAnimationController.registerRemoteAnimations();
+        mAnimationController.unregisterRemoteAnimations();
 
-        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+        verify(mOrganizer).unregisterRemoteAnimations();
 
-        mAnimationController.unregisterRemoteAnimations(TASK_ID);
+        mAnimationController.unregisterRemoteAnimations();
 
         // No extra call if it has been unregistered.
-        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
-    }
-
-    @Test
-    public void testUnregisterAllRemoteAnimations() {
-        mAnimationController.registerRemoteAnimations(TASK_ID);
-        mAnimationController.registerRemoteAnimations(TASK_ID + 1);
-        mAnimationController.unregisterAllRemoteAnimations();
-
-        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
-        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
+        verify(mOrganizer).unregisterRemoteAnimations();
     }
 }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index d43c471..99f56b4 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -161,7 +161,8 @@
         final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity,
                 null /* pendingAppearedIntent */, taskContainer, mController);
 
-        assertTrue(pendingActivityContainer.mPendingAppearedActivities.contains(mActivity));
+        assertTrue(pendingActivityContainer.mPendingAppearedActivities.contains(
+                mActivity.getActivityToken()));
 
         final TaskFragmentInfo info0 = createMockTaskFragmentInfo(pendingActivityContainer,
                 mActivity);
@@ -317,7 +318,7 @@
 
     @Test
     public void testOnActivityDestroyed() {
-        final TaskContainer taskContainer = createTestTaskContainer();
+        final TaskContainer taskContainer = createTestTaskContainer(mController);
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
         container.addPendingAppearedActivity(mActivity);
@@ -328,7 +329,7 @@
 
         assertTrue(container.hasActivity(mActivity.getActivityToken()));
 
-        taskContainer.onActivityDestroyed(mActivity);
+        taskContainer.onActivityDestroyed(mActivity.getActivityToken());
 
         // It should not contain the destroyed Activity.
         assertFalse(container.hasActivity(mActivity.getActivityToken()));
@@ -398,6 +399,79 @@
         assertFalse(taskContainer.isInIntermediateState());
     }
 
+    @Test
+    public void testHasAppearedActivity() {
+        final TaskContainer taskContainer = createTestTaskContainer();
+        final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+                mIntent, taskContainer, mController);
+        container.addPendingAppearedActivity(mActivity);
+
+        assertFalse(container.hasAppearedActivity(mActivity.getActivityToken()));
+
+        final List<IBinder> activities = new ArrayList<>();
+        activities.add(mActivity.getActivityToken());
+        doReturn(activities).when(mInfo).getActivities();
+        container.setInfo(mTransaction, mInfo);
+
+        assertTrue(container.hasAppearedActivity(mActivity.getActivityToken()));
+    }
+
+    @Test
+    public void testHasPendingAppearedActivity() {
+        final TaskContainer taskContainer = createTestTaskContainer();
+        final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+                mIntent, taskContainer, mController);
+        container.addPendingAppearedActivity(mActivity);
+
+        assertTrue(container.hasPendingAppearedActivity(mActivity.getActivityToken()));
+
+        final List<IBinder> activities = new ArrayList<>();
+        activities.add(mActivity.getActivityToken());
+        doReturn(activities).when(mInfo).getActivities();
+        container.setInfo(mTransaction, mInfo);
+
+        assertFalse(container.hasPendingAppearedActivity(mActivity.getActivityToken()));
+    }
+
+    @Test
+    public void testHasActivity() {
+        final TaskContainer taskContainer = createTestTaskContainer(mController);
+        final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
+                mIntent, taskContainer, mController);
+        final TaskFragmentContainer container2 = new TaskFragmentContainer(null /* activity */,
+                mIntent, taskContainer, mController);
+
+        // Activity is pending appeared on container2.
+        container2.addPendingAppearedActivity(mActivity);
+
+        assertFalse(container1.hasActivity(mActivity.getActivityToken()));
+        assertTrue(container2.hasActivity(mActivity.getActivityToken()));
+
+        // Activity is pending appeared on container1 (removed from container2).
+        container1.addPendingAppearedActivity(mActivity);
+
+        assertTrue(container1.hasActivity(mActivity.getActivityToken()));
+        assertFalse(container2.hasActivity(mActivity.getActivityToken()));
+
+        final List<IBinder> activities = new ArrayList<>();
+        activities.add(mActivity.getActivityToken());
+        doReturn(activities).when(mInfo).getActivities();
+
+        // Although Activity is appeared on container2, we prioritize pending appeared record on
+        // container1.
+        container2.setInfo(mTransaction, mInfo);
+
+        assertTrue(container1.hasActivity(mActivity.getActivityToken()));
+        assertFalse(container2.hasActivity(mActivity.getActivityToken()));
+
+        // When the pending appeared record is removed from container1, we respect the appeared
+        // record in container2.
+        container1.removePendingAppearedActivity(mActivity.getActivityToken());
+
+        assertFalse(container1.hasActivity(mActivity.getActivityToken()));
+        assertTrue(container2.hasActivity(mActivity.getActivityToken()));
+    }
+
     /** Creates a mock activity in the organizer process. */
     private Activity createMockActivity() {
         final Activity activity = mock(Activity.class);
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/decor_caption_menu_background.xml b/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml
new file mode 100644
index 0000000..416287d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<shape android:shape="rectangle"
+       xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@android:color/white" />
+    <corners android:radius="20dp" />
+</shape>
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/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
index e6ae282..2994593 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -1,19 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-     Copyright (C) 2019 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
+  ~ 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="48dp"
         android:height="48dp"
@@ -25,13 +25,12 @@
         android:fillAlpha="0.8"
         android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
     <group
-        android:translateX="12"
-        android:translateY="12">
+        android:scaleX="0.8"
+        android:scaleY="0.8"
+        android:translateX="10"
+        android:translateY="10">
         <path
-            android:fillColor="@color/compat_controls_text"
-            android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/>
-        <path
-            android:fillColor="@color/compat_controls_text"
-            android:pathData="M20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01v0l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z"/>
+            android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z"
+            android:fillColor="@color/compat_controls_text"/>
     </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..582a11c
--- /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_menu_background">
+    <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-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index fc0c20e..904ae86 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Laat los"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Program sal dalk nie met verdeelde skerm werk nie."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Program steun nie verdeelde skerm nie."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Hierdie app kan net in 1 venster oopgemaak word."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Program sal dalk nie op \'n sekondêre skerm werk nie."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Program steun nie begin op sekondêre skerms nie."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Skermverdeler"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Maak toe"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handvatsel"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Volskerm"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Rekenaarmodus"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Verdeelde skerm"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Meer"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Sweef"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 405cb06..51de2e5 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"መተግበሪያ ከተከፈለ ማያ ገጽ ጋር ላይሠራ ይችላል"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም።"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ይህ መተግበሪያ መከፈት የሚችለው በ1 መስኮት ብቻ ነው።"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"መተግበሪያ በሁለተኛ ማሳያ ላይ ላይሠራ ይችላል።"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"መተግበሪያ በሁለተኛ ማሳያዎች ላይ ማስጀመርን አይደግፍም።"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"የተከፈለ የማያ ገጽ ከፋይ"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ተመለስ"</string>
     <string name="handle_text" msgid="1766582106752184456">"መያዣ"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"ሙሉ ማያ"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"የዴስክቶፕ ሁነታ"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"የተከፈለ ማያ ገጽ"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"ተጨማሪ"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"ተንሳፋፊ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 9321d8d..635334d 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"إظهار"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"قد لا يعمل التطبيق بشكل سليم في وضع \"تقسيم الشاشة\"."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"التطبيق لا يتيح تقسيم الشاشة."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"لا يمكن فتح هذا التطبيق إلا في نافذة واحدة."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"قد لا يعمل التطبيق على شاشة عرض ثانوية."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"لا يمكن تشغيل التطبيق على شاشات عرض ثانوية."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"أداة تقسيم الشاشة"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"إغلاق"</string>
     <string name="back_button_text" msgid="1469718707134137085">"رجوع"</string>
     <string name="handle_text" msgid="1766582106752184456">"مقبض"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"ملء الشاشة"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"وضع سطح المكتب"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"تقسيم الشاشة"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"المزيد"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"نافذة عائمة"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 268827c..788fd5c 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"দেখুৱাওক"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"এপ্‌টোৱে বিভাজিত স্ক্ৰীনৰ সৈতে কাম নকৰিব পাৰে।"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"এপ্‌টোৱে বিভাজিত স্ক্ৰীন সমৰ্থন নকৰে।"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই এপ্‌টো কেৱল ১ খন ৱিণ্ড’ত খুলিব পাৰি।"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"গৌণ ডিছপ্লেত এপে সঠিকভাৱে কাম নকৰিব পাৰে।"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"গৌণ ডিছপ্লেত এপ্ লঞ্চ কৰিব নোৱাৰি।"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"বন্ধ কৰক"</string>
     <string name="back_button_text" msgid="1469718707134137085">"উভতি যাওক"</string>
     <string name="handle_text" msgid="1766582106752184456">"হেণ্ডেল"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"সম্পূৰ্ণ স্ক্ৰীন"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"ডেস্কটপ ম’ড"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"বিভাজিত স্ক্ৰীন"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"অধিক"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"ওপঙা"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 779bfb6..a56918d 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Güvənli məkandan çıxarın"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Tətbiq bölünmüş ekran ilə işləməyə bilər."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Tətbiq ekran bölünməsini dəstəkləmir."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu tətbiq yalnız 1 pəncərədə açıla bilər."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Tətbiq ikinci ekranda işləməyə bilər."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Tətbiq ikinci ekranda başlamağı dəstəkləmir."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Bölünmüş ekran ayırıcısı"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Bağlayın"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Geriyə"</string>
     <string name="handle_text" msgid="1766582106752184456">"Hər kəsə açıq istifadəçi adı"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Tam Ekran"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Masaüstü Rejimi"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Bölünmüş Ekran"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Ardı"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Üzən pəncərə"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index 1e78b3c..dcb03aa 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Uklonite iz tajne memorije"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi sa podeljenim ekranom."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podeljeni ekran."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija može da se otvori samo u jednom prozoru."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionisati na sekundarnom ekranu."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Razdelnik podeljenog ekrana"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Zatvorite"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
     <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Preko celog ekrana"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Režim za računare"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Podeljeni ekran"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Još"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Plutajuće"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 5524c19..f6b285a 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Паказаць"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Праграма можа не працаваць у рэжыме падзеленага экрана."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Праграма не падтрымлівае функцыю дзялення экрана."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Гэту праграму можна адкрыць толькі ў адным акне."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Праграма можа не працаваць на дадатковых экранах."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Праграма не падтрымлівае запуск на дадатковых экранах."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Раздзяляльнік падзеленага экрана"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
     <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"На ўвесь экран"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Рэжым працоўнага стала"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Падзяліць экран"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Яшчэ"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Зрабіць рухомым акном"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 42dbe3e..044f2a7 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Отмяна на съхраняването"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Приложението може да не работи в режим на разделен екран."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Приложението не поддържа разделен екран."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Това приложение може да се отвори само в 1 прозорец."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Възможно е приложението да не работи на алтернативни дисплеи."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложението не поддържа използването на алтернативни дисплеи."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Разделител в режима за разделен екран"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Затваряне"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
     <string name="handle_text" msgid="1766582106752184456">"Манипулатор"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Цял екран"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Режим за настолни компютри"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Разделяне на екрана"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Още"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Плаващо"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 31a11cd..1fb0292 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"আনস্ট্যাস করুন"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"অ্যাপটি স্প্লিট স্ক্রিনে কাজ নাও করতে পারে।"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"অ্যাপ্লিকেশান বিভক্ত-স্ক্রিন সমর্থন করে না৷"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই অ্যাপটি শুধু ১টি উইন্ডোয় খোলা যেতে পারে।"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"সেকেন্ডারি ডিসপ্লেতে অ্যাপটি কাজ নাও করতে পারে।"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"সেকেন্ডারি ডিসপ্লেতে অ্যাপ লঞ্চ করা যাবে না।"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"বিভক্ত-স্ক্রিন বিভাজক"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ফিরে যান"</string>
     <string name="handle_text" msgid="1766582106752184456">"হাতল"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"ফুলস্ক্রিন"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"ডেস্কটপ মোড"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"স্প্লিট স্ক্রিন"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"আরও"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"ফ্লোট"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 1d674a5..8e52d78 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vađenje iz stasha"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi na podijeljenom ekranu."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava dijeljenje ekrana."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija se može otvoriti samo u 1 prozoru."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće raditi na sekundarnom ekranu."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog ekrana"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Zatvaranje"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
     <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Cijeli ekran"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Način rada radne površine"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Podijeljeni ekran"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Više"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Lebdeći"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 3346938..6bc4f99 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Deixa d\'amagar"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"És possible que l\'aplicació no funcioni amb la pantalla dividida."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'aplicació no admet la pantalla dividida."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aquesta aplicació només pot obrir-se en 1 finestra."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"És possible que l\'aplicació no funcioni en una pantalla secundària."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'aplicació no es pot obrir en pantalles secundàries."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalles"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Tanca"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Enrere"</string>
     <string name="handle_text" msgid="1766582106752184456">"Ansa"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Mode d\'escriptori"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Més"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Flotant"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 555c252..b638b0e 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušit uložení"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikace v režimu rozdělené obrazovky nemusí fungovat."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikace nepodporuje režim rozdělené obrazovky."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tuto aplikaci lze otevřít jen na jednom okně."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikace na sekundárním displeji nemusí fungovat."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikace nepodporuje spuštění na sekundárních displejích."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Čára rozdělující obrazovku"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Zavřít"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Zpět"</string>
     <string name="handle_text" msgid="1766582106752184456">"Úchyt"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Celá obrazovka"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Režim počítače"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Rozdělená obrazovka"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Více"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Plovoucí"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 829bb91..e0b7a8c 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vis"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Appen fungerer muligvis ikke i opdelt skærm."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen understøtter ikke opdelt skærm."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne app kan kun åbnes i 1 vindue."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer muligvis ikke på sekundære skærme."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke åbnes på sekundære skærme."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Adskiller til opdelt skærm"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Luk"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Tilbage"</string>
     <string name="handle_text" msgid="1766582106752184456">"Håndtag"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Fuld skærm"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Computertilstand"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Opdelt skærm"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Mere"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Svævende"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index b3bd9ea..caca8b4 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Aus Stash entfernen"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Die App funktioniert unter Umständen im Modus für geteilten Bildschirm nicht."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Das Teilen des Bildschirms wird in dieser App nicht unterstützt."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Diese App kann nur in einem einzigen Fenster geöffnet werden."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Die App funktioniert auf einem sekundären Display möglicherweise nicht."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Die App unterstützt den Start auf sekundären Displays nicht."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Bildschirmteiler"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Schließen"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Zurück"</string>
     <string name="handle_text" msgid="1766582106752184456">"Ziehpunkt"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Vollbild"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Desktopmodus"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Geteilter Bildschirm"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Mehr"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Frei schwebend"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 79e2dab..ffb4fb0 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Κατάργηση απόκρυψης"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Η εφαρμογή ενδέχεται να μην λειτουργεί με διαχωρισμό οθόνης."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Η εφαρμογή δεν υποστηρίζει διαχωρισμό οθόνης."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Αυτή η εφαρμογή μπορεί να ανοιχθεί μόνο σε 1 παράθυρο."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Η εφαρμογή ίσως να μην λειτουργήσει σε δευτερεύουσα οθόνη."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Η εφαρμογή δεν υποστηρίζει την εκκίνηση σε δευτερεύουσες οθόνες."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Διαχωριστικό οθόνης"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Κλείσιμο"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Πίσω"</string>
     <string name="handle_text" msgid="1766582106752184456">"Λαβή"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Πλήρης οθόνη"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Λειτουργία επιφάνειας εργασίας"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Διαχωρισμός οθόνης"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Περισσότερα"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Κινούμενο"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 6db010a..c71011d 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"More"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 6db010a..05091fb 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -22,7 +22,7 @@
     <string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string>
     <string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string>
     <string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
-    <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-picture menu"</string>
+    <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-Picture Menu"</string>
     <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string>
     <string name="pip_notification_message" msgid="8854051911700302620">"If you don\'t want <xliff:g id="NAME">%s</xliff:g> to use this feature, tap to open settings and turn it off."</string>
     <string name="pip_play" msgid="3496151081459417097">"Play"</string>
@@ -34,10 +34,11 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in 1 window."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
-    <string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Split screen divider"</string>
+    <string name="accessibility_divider" msgid="703810061635792791">"Split-screen divider"</string>
+    <string name="divider_title" msgid="5482989479865361192">"Split-screen divider"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -66,9 +67,9 @@
     <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
     <string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
     <string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
-    <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Control bubbles at any time"</string>
+    <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Control bubbles anytime"</string>
     <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Tap Manage to turn off bubbles from this app"</string>
-    <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
+    <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Got it"</string>
     <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No recent bubbles"</string>
     <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recent bubbles and dismissed bubbles will appear here"</string>
     <string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
@@ -83,9 +84,14 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
-    <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
-    <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
+    <string name="maximize_button_text" msgid="1650859196290301963">"Maximize"</string>
+    <string name="minimize_button_text" msgid="271592547935841753">"Minimize"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Desktop Mode"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"More"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
index 71d02271..09def6b 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
@@ -17,15 +17,15 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string>
+    <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-Picture"</string>
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
     <string name="pip_close" msgid="2955969519031223530">"Close"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
     <string name="pip_move" msgid="158770205886688553">"Move"</string>
     <string name="pip_expand" msgid="1051966011679297308">"Expand"</string>
     <string name="pip_collapse" msgid="3903295106641385962">"Collapse"</string>
-    <string name="pip_edu_text" msgid="7930546669915337998">"Double-press "<annotation icon="home_icon">"HOME"</annotation>" for controls"</string>
-    <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-picture menu"</string>
+    <string name="pip_edu_text" msgid="7930546669915337998">"Double press "<annotation icon="home_icon">"HOME"</annotation>" for controls"</string>
+    <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-Picture menu."</string>
     <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Move left"</string>
     <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Move right"</string>
     <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Move up"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 6db010a..c71011d 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"More"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 6db010a..c71011d 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"More"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index 37b4fc7..2993fe7 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‏‏‎‎Unstash‎‏‎‎‏‎"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎App may not work with split-screen.‎‏‎‎‏‎"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‏‏‏‎‏‎App does not support split-screen.‎‏‎‎‏‎"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‎This app can only be opened in 1 window.‎‏‎‎‏‎"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎App may not work on a secondary display.‎‏‎‎‏‎"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‎App does not support launch on secondary displays.‎‏‎‎‏‎"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‏‏‏‎Split-screen divider‎‏‎‎‏‎"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎Close‎‏‎‎‏‎"</string>
     <string name="back_button_text" msgid="1469718707134137085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‏‎‏‎Back‎‏‎‎‏‎"</string>
     <string name="handle_text" msgid="1766582106752184456">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‎‎‎Handle‎‏‎‎‏‎"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‏‎‏‏‎‎‎‎‎‏‏‎‏‏‏‎‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‏‎Fullscreen‎‏‎‎‏‎"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‏‎‎Desktop Mode‎‏‎‎‏‎"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‏‎‎Split Screen‎‏‎‎‏‎"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‎‏‎‎More‎‏‎‎‏‎"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‎‏‏‎‎‎‎‏‏‎‏‎‎‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎Float‎‏‎‎‏‎"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 1e7174d..0eaca8b 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Dejar de almacenar de manera segura"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la app no funcione en el modo de pantalla dividida."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La app no es compatible con la función de pantalla dividida."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app solo puede estar abierta en 1 ventana."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la app no funcione en una pantalla secundaria."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La app no puede iniciarse en pantallas secundarias."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalla dividida"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
     <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Modo de escritorio"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Más"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 87cdca4..9c8fed1 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"No esconder"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la aplicación no funcione con la pantalla dividida."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La aplicación no admite la pantalla dividida."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación solo puede abrirse en una ventana."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la aplicación no funcione en una pantalla secundaria."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La aplicación no se puede abrir en pantallas secundarias."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Dividir la pantalla"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
     <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Modo Escritorio"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Más"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index f392560..e8cbe53 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Eemalda hoidlast"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Rakendus ei pruugi poolitatud ekraaniga töötada."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Rakendus ei toeta jagatud ekraani."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Selle rakenduse saab avada ainult ühes aknas."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Rakendus ei pruugi teisesel ekraanil töötada."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Rakendus ei toeta teisestel ekraanidel käivitamist."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Ekraanijagaja"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Sule"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Tagasi"</string>
     <string name="handle_text" msgid="1766582106752184456">"Käepide"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Täisekraan"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Lauaarvuti režiim"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Jagatud ekraanikuva"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Rohkem"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Hõljuv"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index 6bc1d91..4417668 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ez gorde"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Baliteke aplikazioak ez funtzionatzea pantaila zatituan."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikazioak ez du onartzen pantaila zatitua"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Leiho bakar batean ireki daiteke aplikazioa."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Baliteke aplikazioak ez funtzionatzea bigarren mailako pantailetan."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikazioa ezin da abiarazi bigarren mailako pantailatan."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Pantaila-zatitzailea"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Itxi"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Atzera"</string>
     <string name="handle_text" msgid="1766582106752184456">"Kontu-izena"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Pantaila osoa"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Ordenagailuetarako modua"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Pantaila zatitua"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Gehiago"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Leiho gainerakorra"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 9949dd2..7375faf 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"لغو مخفی‌سازی"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن است برنامه با «صفحهٔ دونیمه» کار نکند."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"برنامه از تقسیم صفحه پشتیبانی نمی‌کند."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"این برنامه فقط در ۱ پنجره می‌تواند باز شود."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن است برنامه در نمایشگر ثانویه کار نکند."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"برنامه از راه‌اندازی در نمایشگرهای ثانویه پشتیبانی نمی‌کند."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"تقسیم‌کننده صفحه"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"بستن"</string>
     <string name="back_button_text" msgid="1469718707134137085">"برگشتن"</string>
     <string name="handle_text" msgid="1766582106752184456">"دستگیره"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"تمام‌صفحه"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"حالت رایانه"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"صفحهٔ دونیمه"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"بیشتر"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"شناور"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index e701452..7729d1c 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poista turvasäilytyksestä"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Sovellus ei ehkä toimi jaetulla näytöllä."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Sovellus ei tue jaetun näytön tilaa."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tämän sovelluksen voi avata vain yhdessä ikkunassa."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Sovellus ei ehkä toimi toissijaisella näytöllä."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Sovellus ei tue käynnistämistä toissijaisilla näytöillä."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Näytön jakaja"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Sulje"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Takaisin"</string>
     <string name="handle_text" msgid="1766582106752184456">"Kahva"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Koko näyttö"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Työpöytätila"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Jaettu näyttö"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Lisää"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Kelluva ikkuna"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index ff8417b..6348800 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Retirer de la réserve"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'application n\'est pas compatible avec l\'écran partagé."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette application ne peut être ouverte que dans une fenêtre."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Séparateur d\'écran partagé"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
     <string name="handle_text" msgid="1766582106752184456">"Identifiant"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Mode Bureau"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Plus"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Flottant"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 8ceeec0..1842213 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Application incompatible avec l\'écran partagé."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette appli ne peut être ouverte que dans 1 fenêtre."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Séparateur d\'écran partagé"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
     <string name="handle_text" msgid="1766582106752184456">"Poignée"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Mode ordinateur"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Plus"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Flottante"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 999921a..2e05d4c 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Non esconder"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Pode que a aplicación non funcione coa pantalla dividida."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A aplicación non é compatible coa función de pantalla dividida."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación só se pode abrir en 1 ventá."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É posible que a aplicación non funcione nunha pantalla secundaria."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A aplicación non se pode iniciar en pantallas secundarias."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalla dividida"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Pechar"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
     <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Modo de escritorio"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Máis"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 5207e19..e680298 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"બતાવો"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"વિભાજિત-સ્ક્રીન સાથે ઍપ કદાચ કામ ન કરે."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ઍપ્લિકેશન સ્ક્રીન-વિભાજનનું સમર્થન કરતી નથી."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"આ ઍપ માત્ર 1 વિન્ડોમાં ખોલી શકાય છે."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર કદાચ કામ ન કરે."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર લૉન્ચનું સમર્થન કરતી નથી."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"સ્પ્લિટ-સ્ક્રીન વિભાજક"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"બંધ કરો"</string>
     <string name="back_button_text" msgid="1469718707134137085">"પાછળ"</string>
     <string name="handle_text" msgid="1766582106752184456">"હૅન્ડલ"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"પૂર્ણસ્ક્રીન"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"ડેસ્કટૉપ મોડ"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"સ્ક્રીનને વિભાજિત કરો"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"વધુ"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"ફ્લોટિંગ વિન્ડો"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 1b9b90b..1a7cf3e 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -34,6 +34,8 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"दिखाएं"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"ऐप्लिकेशन शायद स्प्लिट स्क्रीन मोड में काम न करे."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ऐप विभाजित स्‍क्रीन का समर्थन नहीं करता है."</string>
+    <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+    <skip />
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"हो सकता है कि ऐप प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर काम न करे."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर ऐप लॉन्च नहीं किया जा सकता."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"विभाजित स्क्रीन विभाजक"</string>
@@ -88,4 +90,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"बंद करें"</string>
     <string name="back_button_text" msgid="1469718707134137085">"वापस जाएं"</string>
     <string name="handle_text" msgid="1766582106752184456">"हैंडल"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"फ़ुलस्क्रीन"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"डेस्कटॉप मोड"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रीन मोड"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"ज़्यादा देखें"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"फ़्लोट"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 08aa262..23a5970 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poništite sakrivanje"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće funkcionirati s podijeljenim zaslonom."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podijeljeni zaslon."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova se aplikacija može otvoriti samo u jednom prozoru."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionirati na sekundarnom zaslonu."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim zaslonima."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog zaslona"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Zatvori"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Natrag"</string>
     <string name="handle_text" msgid="1766582106752184456">"Pokazivač"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Puni zaslon"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Stolni način rada"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Razdvojeni zaslon"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Više"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Plutajući"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 8ad0a01..1bbbdb7 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Félretevés megszüntetése"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Lehet, hogy az alkalmazás nem működik osztott képernyős nézetben."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Az alkalmazás nem támogatja az osztott képernyős nézetet."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ez az alkalmazás csak egy ablakban nyitható meg."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Előfordulhat, hogy az alkalmazás nem működik másodlagos kijelzőn."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Az alkalmazást nem lehet másodlagos kijelzőn elindítani."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Elválasztó az osztott nézetben"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Bezárás"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Vissza"</string>
     <string name="handle_text" msgid="1766582106752184456">"Fogópont"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Teljes képernyő"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Asztali üzemmód"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Osztott képernyő"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Továbbiak"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Lebegő"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index b61ea1d..6eef4af 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ցուցադրել"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Հավելվածը չի կարող աշխատել տրոհված էկրանի ռեժիմում։"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Հավելվածը չի աջակցում էկրանի տրոհումը:"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Այս հավելվածը հնարավոր է բացել միայն մեկ պատուհանում։"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Հավելվածը կարող է չաշխատել լրացուցիչ էկրանի վրա"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Հավելվածը չի աջակցում գործարկումը լրացուցիչ էկրանների վրա"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Տրոհված էկրանի բաժանիչ"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Փակել"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Հետ"</string>
     <string name="handle_text" msgid="1766582106752184456">"Նշիչ"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Լիաէկրան"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Համակարգչի ռեժիմ"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Տրոհված էկրան"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Ավելին"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Լողացող պատուհան"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 79e926d..61a9558 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Batalkan stash"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikasi mungkin tidak berfungsi dengan layar terpisah."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App tidak mendukung layar terpisah."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplikasi ini hanya dapat dibuka di 1 jendela."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikasi mungkin tidak berfungsi pada layar sekunder."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikasi tidak mendukung peluncuran pada layar sekunder."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Pembagi layar terpisah"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
     <string name="handle_text" msgid="1766582106752184456">"Tuas"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Layar Penuh"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Mode Desktop"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Layar Terpisah"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Lainnya"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Mengambang"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 0645c41..0b873bc 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Taka úr geymslu"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Hugsanlega virkar forritið ekki með skjáskiptingu."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Forritið styður ekki að skjánum sé skipt."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aðeins er hægt að opna þetta forrit í 1 glugga."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Hugsanlegt er að forritið virki ekki á öðrum skjá."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Forrit styður ekki opnun á öðrum skjá."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Skjáskipting"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Loka"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Til baka"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handfang"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Allur skjárinn"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Skjáborðsstilling"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Skjáskipting"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Meira"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Reikult"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 9a023f5..da4d0bb 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Annulla accantonamento"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"L\'app potrebbe non funzionare con lo schermo diviso."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'app non supporta la modalità Schermo diviso."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Questa app può essere aperta soltanto in 1 finestra."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"L\'app potrebbe non funzionare su un display secondario."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'app non supporta l\'avvio su display secondari."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Strumento per schermo diviso"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Chiudi"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Indietro"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Schermo intero"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Modalità desktop"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Schermo diviso"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Altro"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Mobile"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 57238ea..e9a53dd 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ביטול ההסתרה הזמנית"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"ייתכן שהאפליקציה לא תפעל במסך מפוצל."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"האפליקציה אינה תומכת במסך מפוצל."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ניתן לפתוח את האפליקציה הזו רק בחלון אחד."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ייתכן שהאפליקציה לא תפעל במסך משני."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"האפליקציה אינה תומכת בהפעלה במסכים משניים."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"מחלק מסך מפוצל"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"סגירה"</string>
     <string name="back_button_text" msgid="1469718707134137085">"חזרה"</string>
     <string name="handle_text" msgid="1766582106752184456">"נקודת אחיזה"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"מסך מלא"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"ממשק המחשב"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"מסך מפוצל"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"עוד"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"בלונים"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index d0b5462..2930cc3 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"表示"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"アプリは分割画面では動作しないことがあります。"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"アプリで分割画面がサポートされていません。"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"このアプリはウィンドウが 1 つの場合のみ開くことができます。"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"アプリはセカンダリ ディスプレイでは動作しないことがあります。"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"アプリはセカンダリ ディスプレイでの起動に対応していません。"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"分割画面の分割線"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"閉じる"</string>
     <string name="back_button_text" msgid="1469718707134137085">"戻る"</string>
     <string name="handle_text" msgid="1766582106752184456">"ハンドル"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"全画面表示"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"デスクトップ モード"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"分割画面"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"その他"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"フローティング"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index b0cf539..848be3f 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"გადანახვის გაუქმება"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"აპმა შეიძლება არ იმუშაოს გაყოფილი ეკრანის რეჟიმში."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ეკრანის გაყოფა არ არის მხარდაჭერილი აპის მიერ."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ამ აპის გახსნა შესაძლებელია მხოლოდ 1 ფანჯარაში."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"აპმა შეიძლება არ იმუშაოს მეორეულ ეკრანზე."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"აპს არ გააჩნია მეორეული ეკრანის მხარდაჭერა."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"გაყოფილი ეკრანის რეჟიმის გამყოფი"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string>
     <string name="back_button_text" msgid="1469718707134137085">"უკან"</string>
     <string name="handle_text" msgid="1766582106752184456">"იდენტიფიკატორი"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"სრულ ეკრანზე"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"დესკტოპის რეჟიმი"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"ეკრანის გაყოფა"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"სხვა"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"ფარფატი"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index a9f350e..8d08ccab 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Көрсету"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Қолданба экранды бөлу режимінде жұмыс істемеуі мүмкін."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Қодланба бөлінген экранды қолдамайды."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бұл қолданбаны тек 1 терезеден ашуға болады."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Қолданба қосымша дисплейде жұмыс істемеуі мүмкін."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Қолданба қосымша дисплейлерде іске қосуды қолдамайды."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Бөлінген экран бөлгіші"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Жабу"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Артқа"</string>
     <string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Толық экран"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Компьютер режимі"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Экранды бөлу"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Қосымша"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Қалқыма"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index a1d2691..7c4ea57e 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ឈប់លាក់ជាបណ្ដោះអាសន្ន"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"កម្មវិធី​អាចនឹងមិន​ដំណើរការ​ជាមួយ​មុខងារបំបែកអេក្រង់​ទេ។"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"កម្មវិធីមិនគាំទ្រអេក្រង់បំបែកជាពីរទេ"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"កម្មវិធីនេះអាចបើកនៅក្នុងវិនដូតែ 1 ប៉ុណ្ណោះ។"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"កម្មវិធីនេះ​ប្រហែល​ជាមិនដំណើរការ​នៅលើ​អេក្រង់បន្ទាប់បន្សំទេ។"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"កម្មវិធី​នេះមិន​អាច​ចាប់ផ្តើម​នៅលើ​អេក្រង់បន្ទាប់បន្សំបានទេ។"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"កម្មវិធីចែកអេក្រង់បំបែក"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"បិទ"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ថយក្រោយ"</string>
     <string name="handle_text" msgid="1766582106752184456">"ឈ្មោះអ្នកប្រើប្រាស់"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"អេក្រង់​ពេញ"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"មុខងារកុំព្យូទ័រ"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"មុខងារ​បំបែក​អេក្រង់"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"ច្រើនទៀត"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"អណ្ដែត"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index acad7c1..7290617 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ಅನ್‌ಸ್ಟ್ಯಾಶ್ ಮಾಡಿ"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"ವಿಭಜಿಸಿದ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ಆ್ಯಪ್ ಕೆಲಸ ಮಾಡದೇ ಇರಬಹುದು."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ಅಪ್ಲಿಕೇಶನ್ ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ಈ ಆ್ಯಪ್ ಅನ್ನು 1 ವಿಂಡೋದಲ್ಲಿ ಮಾತ್ರ ತೆರೆಯಬಹುದು."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ಸೆಕೆಂಡರಿ ಡಿಸ್‌ಪ್ಲೇಗಳಲ್ಲಿ ಅಪ್ಲಿಕೇಶನ್‌ ಕಾರ್ಯ ನಿರ್ವಹಿಸದೇ ಇರಬಹುದು."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ಸೆಕೆಂಡರಿ ಡಿಸ್‌ಪ್ಲೇಗಳಲ್ಲಿ ಪ್ರಾರಂಭಿಸುವಿಕೆಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"ಸ್ಪ್ಲಿಟ್-ಪರದೆ ಡಿವೈಡರ್"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"ಮುಚ್ಚಿರಿ"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ಹಿಂದಕ್ಕೆ"</string>
     <string name="handle_text" msgid="1766582106752184456">"ಹ್ಯಾಂಡಲ್"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"ಫುಲ್‌ಸ್ಕ್ರೀನ್"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"ಡೆಸ್ಕ್‌ಟಾಪ್ ಮೋಡ್"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"ಇನ್ನಷ್ಟು"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"ಫ್ಲೋಟ್"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index f6ea6cc..59b405f 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"숨기기 취소"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"앱이 분할 화면에서 작동하지 않을 수 있습니다."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"앱이 화면 분할을 지원하지 않습니다."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"이 앱은 창 1개에서만 열 수 있습니다."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"앱이 보조 디스플레이에서 작동하지 않을 수도 있습니다."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"앱이 보조 디스플레이에서의 실행을 지원하지 않습니다."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"화면 분할기"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"닫기"</string>
     <string name="back_button_text" msgid="1469718707134137085">"뒤로"</string>
     <string name="handle_text" msgid="1766582106752184456">"핸들"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"전체 화면"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"데스크톱 모드"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"화면 분할"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"더보기"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"플로팅"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 9ad82de..69ec8eb 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Сейфтен чыгаруу"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Колдонмодо экран бөлүнбөшү мүмкүн."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Колдонмодо экран бөлүнбөйт."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бул колдонмону 1 терезеде гана ачууга болот."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Колдонмо кошумча экранда иштебей коюшу мүмкүн."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Колдонмону кошумча экрандарда иштетүүгө болбойт."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Экранды бөлгүч"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Жабуу"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Артка"</string>
     <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Толук экран"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Компьютер режими"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Экранды бөлүү"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Дагы"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Калкыма"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 53d4f34..d5ea3cf 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ເອົາອອກຈາກບ່ອນເກັບສ່ວນຕົວ"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"ແອັບອາດໃຊ້ບໍ່ໄດ້ກັບການແບ່ງໜ້າຈໍ."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ແອັບບໍ່ຮອງຮັບໜ້າຈໍແບບແຍກກັນ."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ແອັບນີ້ສາມາດເປີດໄດ້ໃນ 1 ໜ້າຈໍເທົ່ານັ້ນ."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ແອັບອາດບໍ່ສາມາດໃຊ້ໄດ້ໃນໜ້າຈໍທີສອງ."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ແອັບບໍ່ຮອງຮັບການເປີດໃນໜ້າຈໍທີສອງ."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"ຕົວຂັ້ນການແບ່ງໜ້າຈໍ"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"ປິດ"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ກັບຄືນ"</string>
     <string name="handle_text" msgid="1766582106752184456">"ມືບັງຄັບ"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"ເຕັມຈໍ"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"ໂໝດເດັສທັອບ"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"ແບ່ງໜ້າຈໍ"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"ເພີ່ມເຕີມ"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"ລອຍ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 331281a..922f5b5 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Nebeslėpti"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Programa gali neveikti naudojant išskaidyto ekrano režimą."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programoje nepalaikomas skaidytas ekranas."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šią programą galima atidaryti tik viename lange."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Programa gali neveikti antriniame ekrane."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programa nepalaiko paleisties antriniuose ekranuose."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Skaidyto ekrano daliklis"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Uždaryti"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Atgal"</string>
     <string name="handle_text" msgid="1766582106752184456">"Rankenėlė"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Visas ekranas"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Stalinio kompiuterio režimas"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Išskaidyto ekrano režimas"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Daugiau"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Slankusis langas"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index d301721..08ac928 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Rādīt"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Iespējams, lietotne nedarbosies ekrāna sadalīšanas režīmā."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Lietotnē netiek atbalstīta ekrāna sadalīšana."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šo lietotni var atvērt tikai vienā logā."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Lietotne, iespējams, nedarbosies sekundārajā displejā."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Lietotnē netiek atbalstīta palaišana sekundārajos displejos."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Ekrāna sadalītājs"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Aizvērt"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Atpakaļ"</string>
     <string name="handle_text" msgid="1766582106752184456">"Turis"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Pilnekrāna režīms"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Darbvirsmas režīms"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Sadalīt ekrānu"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Vairāk"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Peldošs"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 00f2900..ae71ae9 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Прикажете"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Апликацијата може да не работи со поделен екран."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Апликацијата не поддржува поделен екран."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Апликацијава може да се отвори само во еден прозорец."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликацијата може да не функционира на друг екран."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликацијата не поддржува стартување на други екрани."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Разделник на поделен екран"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Затвори"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
     <string name="handle_text" msgid="1766582106752184456">"Прекар"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Цел екран"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Режим за компјутер"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Поделен екран"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Повеќе"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Лебдечко"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 9722868..1f2ee77 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -34,6 +34,8 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"അൺസ്റ്റാഷ് ചെയ്യൽ"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"സ്‌ക്രീൻ വിഭജന മോഡിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"സ്പ്ലിറ്റ്-സ്ക്രീനിനെ ആപ്പ് പിന്തുണയ്ക്കുന്നില്ല."</string>
+    <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+    <skip />
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"രണ്ടാം ഡിസ്‌പ്ലേയിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"രണ്ടാം ഡിസ്‌പ്ലേകളിൽ സമാരംഭിക്കുന്നതിനെ ആപ്പ് അനുവദിക്കുന്നില്ല."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"സ്പ്ലിറ്റ്-സ്ക്രീൻ ഡിവൈഡർ"</string>
@@ -88,4 +90,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string>
     <string name="back_button_text" msgid="1469718707134137085">"മടങ്ങുക"</string>
     <string name="handle_text" msgid="1766582106752184456">"ഹാൻഡിൽ"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"പൂർണ്ണസ്ക്രീൻ"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"ഡെസ്‌ക്ടോപ്പ് മോഡ്"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"സ്‌ക്രീൻ വിഭജനം"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"കൂടുതൽ"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"ഫ്ലോട്ട്"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 3d598e4..c1950a1 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ил гаргах"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Апп хуваагдсан дэлгэц дээр ажиллахгүй байж болзошгүй."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Энэ апп нь дэлгэц хуваах тохиргоог дэмждэггүй."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Энэ аппыг зөвхөн 1 цонхонд нээх боломжтой."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апп хоёрдогч дэлгэцэд ажиллахгүй."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Аппыг хоёрдогч дэлгэцэд эхлүүлэх боломжгүй."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"\"Дэлгэц хуваах\" хуваагч"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Хаах"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Буцах"</string>
     <string name="handle_text" msgid="1766582106752184456">"Бариул"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Бүтэн дэлгэц"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Дэлгэцийн горим"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Дэлгэцийг хуваах"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Бусад"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Хөвөгч"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 29f57fb..29821f6 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्टॅश करा"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"अ‍ॅप कदाचित स्प्लिट स्क्रीनसह काम करू शकत नाही."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अ‍ॅप स्क्रीन-विभाजनास समर्थन देत नाही."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"हे अ‍ॅप फक्त एका विंडोमध्ये उघडले जाऊ शकते."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"दुसऱ्या डिस्प्लेवर अ‍ॅप कदाचित चालणार नाही."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"दुसऱ्या डिस्प्लेवर अ‍ॅप लाँच होणार नाही."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रीन विभाजक"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string>
     <string name="back_button_text" msgid="1469718707134137085">"मागे जा"</string>
     <string name="handle_text" msgid="1766582106752184456">"हँडल"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"फुलस्‍क्रीन"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"डेस्कटॉप मोड"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रीन"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"आणखी"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"फ्लोट"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 4dc8dca..c3db19d 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Tunjukkan"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Apl mungkin tidak berfungsi dengan skrin pisah."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Apl tidak menyokong skrin pisah."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Apl ini hanya boleh dibuka dalam 1 tetingkap."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Apl mungkin tidak berfungsi pada paparan kedua."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Apl tidak menyokong pelancaran pada paparan kedua."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Pembahagi skrin pisah"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
     <string name="handle_text" msgid="1766582106752184456">"Pemegang"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Skrin penuh"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Mod Desktop"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Skrin Pisah"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Lagi"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Terapung"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 0bb6acf..b2bb37d 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"မသိုဝှက်ရန်"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းဖြင့် အက်ပ်သည် အလုပ်မလုပ်ပါ။"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"အက်ပ်သည် မျက်နှာပြင်ခွဲပြရန် ပံ့ပိုးထားခြင်းမရှိပါ။"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ဤအက်ပ်ကို ဝင်းဒိုး ၁ ခုတွင်သာ ဖွင့်နိုင်သည်။"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ဤအက်ပ်အနေဖြင့် ဒုတိယဖန်သားပြင်ပေါ်တွင် အလုပ်လုပ်မည် မဟုတ်ပါ။"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ဤအက်ပ်အနေဖြင့် ဖွင့်ရန်စနစ်ကို ဒုတိယဖန်သားပြင်မှ အသုံးပြုရန် ပံ့ပိုးမထားပါ။"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"မျက်နှာပြင်ခွဲခြမ်း ပိုင်းခြားပေးသည့်စနစ်"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string>
     <string name="back_button_text" msgid="1469718707134137085">"နောက်သို့"</string>
     <string name="handle_text" msgid="1766582106752184456">"သုံးသူအမည်"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"ဖန်သားပြင်အပြည့်"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"ဒက်စ်တော့မုဒ်"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"မျက်နှာပြင် ခွဲ၍ပြသရန်"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"ပိုပြပါ"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"မျှောရန်"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index f4f3af8..90b9dfc 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Avslutt oppbevaring"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Det kan hende at appen ikke fungerer med delt skjerm."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen støtter ikke delt skjerm."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne appen kan bare åpnes i ett vindu."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer kanskje ikke på en sekundær skjerm."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke kjøres på sekundære skjermer."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Skilleelement for delt skjerm"</string>
@@ -66,7 +67,7 @@
     <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ikke vis samtaler i bobler"</string>
     <string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat med bobler"</string>
     <string name="bubbles_user_education_description" msgid="4215862563054175407">"Nye samtaler vises som flytende ikoner eller bobler. Trykk for å åpne en boble. Dra for å flytte den."</string>
-    <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Kontrollér bobler når som helst"</string>
+    <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Kontroller bobler når som helst"</string>
     <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Trykk på Administrer for å slå av bobler for denne appen"</string>
     <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Greit"</string>
     <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ingen nylige bobler"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Lukk"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Tilbake"</string>
     <string name="handle_text" msgid="1766582106752184456">"Håndtak"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Fullskjerm"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Skrivebordmodus"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Delt skjerm"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Mer"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Svevende"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 4b90e92..0f6b155 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -34,6 +34,8 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्ट्यास गर्नुहोस्"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"एप विभाजित स्क्रिनमा काम नगर्न सक्छ।"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अनुप्रयोगले विभाजित-स्क्रिनलाई समर्थन गर्दैन।"</string>
+    <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+    <skip />
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"यो एपले सहायक प्रदर्शनमा काम नगर्नसक्छ।"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"अनुप्रयोगले सहायक प्रदर्शनहरूमा लञ्च सुविधालाई समर्थन गर्दैन।"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रिन छुट्याउने"</string>
@@ -88,4 +90,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string>
     <string name="back_button_text" msgid="1469718707134137085">"पछाडि"</string>
     <string name="handle_text" msgid="1766582106752184456">"ह्यान्डल"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"फुल स्क्रिन"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"डेस्कटप मोड"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रिन"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"थप"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"फ्लोट"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 18a2021..f9f4ef4 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Niet meer verbergen"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"De app werkt mogelijk niet met gesplitst scherm."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App biedt geen ondersteuning voor gesplitst scherm."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Deze app kan slechts in 1 venster worden geopend."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App werkt mogelijk niet op een secundair scherm."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App kan niet op secundaire displays worden gestart."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Scheiding voor gesplitst scherm"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Sluiten"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
     <string name="handle_text" msgid="1766582106752184456">"Gebruikersnaam"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Volledig scherm"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Desktopmodus"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Gesplitst scherm"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Meer"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Zwevend"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 9e5a96d..5a76a6f 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ଦେଖାନ୍ତୁ"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"ସ୍ପ୍ଲିଟ୍-ସ୍କ୍ରିନରେ ଆପ୍ କାମ କରିନପାରେ।"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ଆପ୍‍ ସ୍ପ୍ଲିଟ୍‍-ସ୍କ୍ରୀନକୁ ସପୋର୍ଟ କରେ ନାହିଁ।"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ଏହି ଆପକୁ କେବଳ 1ଟି ୱିଣ୍ଡୋରେ ଖୋଲାଯାଇପାରିବ।"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍‍ କାମ ନକରିପାରେ।"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍‍ ଲଞ୍ଚ ସପୋର୍ଟ କରେ ନାହିଁ।"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"ସ୍ପ୍ଲିଟ୍‍-ସ୍କ୍ରୀନ ବିଭାଜକ"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ପଛକୁ ଫେରନ୍ତୁ"</string>
     <string name="handle_text" msgid="1766582106752184456">"ହେଣ୍ଡେଲ"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"ପୂର୍ଣ୍ଣସ୍କ୍ରିନ"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"ଡେସ୍କଟପ ମୋଡ"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"ଅଧିକ"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"ଫ୍ଲୋଟ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 5c255d8..617c95e 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ਅਣਸਟੈਸ਼"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ।"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਨੂੰ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ।"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ਇਹ ਐਪ ਸਿਰਫ਼ 1 ਵਿੰਡੋ ਵਿੱਚ ਖੋਲ੍ਹੀ ਜਾ ਸਕਦੀ ਹੈ।"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇ \'ਤੇ ਕੰਮ ਨਾ ਕਰੇ।"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇਆਂ \'ਤੇ ਲਾਂਚ ਕਰਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਡਿਵਾਈਡਰ"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"ਬੰਦ ਕਰੋ"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ਪਿੱਛੇ"</string>
     <string name="handle_text" msgid="1766582106752184456">"ਹੈਂਡਲ"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"ਪੂਰੀ-ਸਕ੍ਰੀਨ"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"ਡੈਸਕਟਾਪ ਮੋਡ"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"ਹੋਰ"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"ਫ਼ਲੋਟ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 086726c..4a17ec7 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zabierz ze schowka"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacja może nie działać przy podzielonym ekranie."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacja nie obsługuje dzielonego ekranu."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ta aplikacja może być otwarta tylko w 1 oknie."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacja może nie działać na dodatkowym ekranie."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacja nie obsługuje uruchamiania na dodatkowych ekranach."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Linia dzielenia ekranu"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Zamknij"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Wstecz"</string>
     <string name="handle_text" msgid="1766582106752184456">"Uchwyt"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Pełny ekran"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Tryb pulpitu"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Podzielony ekran"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Więcej"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Pływające"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 353c02d..69be68e 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string>
     <string name="handle_text" msgid="1766582106752184456">"Alça"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Tela cheia"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Modo área de trabalho"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Tela dividida"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Ponto flutuante"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 97d40b5..13e83ac 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Remover do armazenamento"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"A app pode não funcionar com o ecrã dividido."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A app não é compatível com o ecrã dividido."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app só pode ser aberta em 1 janela."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"A app pode não funcionar num ecrã secundário."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A app não é compatível com o início em ecrãs secundários."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Divisor do ecrã dividido"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Anterior"</string>
     <string name="handle_text" msgid="1766582106752184456">"Indicador"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Ecrã inteiro"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Modo de ambiente de trabalho"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Ecrã dividido"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Flutuar"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 353c02d..69be68e 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string>
     <string name="handle_text" msgid="1766582106752184456">"Alça"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Tela cheia"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Modo área de trabalho"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Tela dividida"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Ponto flutuante"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index a085f02..c112a9d 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulează stocarea"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Este posibil ca aplicația să nu funcționeze cu ecranul împărțit."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplicația nu acceptă ecranul împărțit."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplicația poate fi deschisă într-o singură fereastră."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Este posibil ca aplicația să nu funcționeze pe un ecran secundar."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplicația nu acceptă lansare pe ecrane secundare."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Separator pentru ecranul împărțit"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Închide"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Înapoi"</string>
     <string name="handle_text" msgid="1766582106752184456">"Ghidaj"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Ecran complet"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Modul desktop"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Ecran împărțit"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Mai multe"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Flotantă"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index 97c5a06..489adc0 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показать"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"В режиме разделения экрана приложение может работать нестабильно."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Приложение не поддерживает разделение экрана."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Это приложение можно открыть только в одном окне."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Приложение может не работать на дополнительном экране"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложение не поддерживает запуск на дополнительных экранах"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Разделитель экрана"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
     <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Полноэкранный режим"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Режим компьютера"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Разделить экран"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Ещё"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Плавающее окно"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 60e1222..3237114 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"සඟවා තැබීම ඉවත් කරන්න"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"යෙදුම බෙදුම් තිරය සමග ක්‍රියා නොකළ හැකිය"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"යෙදුම බෙදුණු-තිරය සඳහා සහාය නොදක්වයි."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"මෙම යෙදුම විවෘත කළ හැක්කේ 1 කවුළුවක පමණයි."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"යෙදුම ද්විතියික සංදර්ශකයක ක්‍රියා නොකළ හැකිය."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"යෙදුම ද්විතීයික සංදර්ශක මත දියත් කිරීම සඳහා සහාය නොදක්වයි."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"බෙදුම්-තිර වෙන්කරණය"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"වසන්න"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ආපසු"</string>
     <string name="handle_text" msgid="1766582106752184456">"හැඬලය"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"පූර්ණ තිරය"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"ඩෙස්ක්ටොප් ප්‍රකාරය"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"බෙදුම් තිරය"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"තව"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"පාවෙන"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 4007498..a753021 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušiť skrytie"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikácia nemusí fungovať s rozdelenou obrazovkou."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikácia nepodporuje rozdelenú obrazovku."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Táto aplikácia môže byť otvorená iba v jednom okne."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikácia nemusí fungovať na sekundárnej obrazovke."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikácia nepodporuje spúšťanie na sekundárnych obrazovkách."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Rozdeľovač obrazovky"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Zavrieť"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Späť"</string>
     <string name="handle_text" msgid="1766582106752184456">"Rukoväť"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Celá obrazovka"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Režim počítača"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Rozdelená obrazovka"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Viac"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Plávajúce"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 6dbd883..b5d8733 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Razkrij"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija morda ne deluje v načinu razdeljenega zaslona."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podpira načina razdeljenega zaslona."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"To aplikacijo je mogoče odpreti samo v enem oknu."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija morda ne bo delovala na sekundarnem zaslonu."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podpira zagona na sekundarnih zaslonih."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Razdelilnik zaslonov"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Zapri"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Nazaj"</string>
     <string name="handle_text" msgid="1766582106752184456">"Ročica"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Celozaslonsko"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Namizni način"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Razdeljen zaslon"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Več"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Lebdeče"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 11e5713..ebd644c 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Mos e fshih"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacioni mund të mos funksionojë me ekranin e ndarë."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacioni nuk mbështet ekranin e ndarë."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ky aplikacion mund të hapet vetëm në 1 dritare."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacioni mund të mos funksionojë në një ekran dytësor."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacioni nuk mbështet nisjen në ekrane dytësore."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Ndarësi i ekranit të ndarë"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Mbyll"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Pas"</string>
     <string name="handle_text" msgid="1766582106752184456">"Emërtimi"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Ekrani i plotë"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Modaliteti i desktopit"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Ekrani i ndarë"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Më shumë"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Pluskuese"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 5beb31c..d051ca3 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Уклоните из тајне меморије"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Апликација можда неће радити са подељеним екраном."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Апликација не подржава подељени екран."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ова апликација може да се отвори само у једном прозору."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликација можда неће функционисати на секундарном екрану."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликација не подржава покретање на секундарним екранима."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Разделник подељеног екрана"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Затворите"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
     <string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Преко целог екрана"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Режим за рачунаре"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Подељени екран"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Још"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Плутајуће"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index c175583..cd46039 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Återställ stash"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Appen kanske inte fungerar med delad skärm."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen har inte stöd för delad skärm."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denna app kan bara vara öppen i ett fönster."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen kanske inte fungerar på en sekundär skärm."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan inte köras på en sekundär skärm."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Avdelare för delad skärm"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Stäng"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Tillbaka"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handtag"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Helskärm"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Datorläge"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Delad skärm"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Mer"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Svävande"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index a049364..345fbf8 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Fichua"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Huenda programu isifanye kazi kwenye skrini inayogawanywa."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programu haiwezi kutumia skrini iliyogawanywa."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Programu hii inaweza kufunguliwa katika dirisha 1 pekee."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Huenda programu isifanye kazi kwenye dirisha lingine."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programu hii haiwezi kufunguliwa kwenye madirisha mengine."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Kitenganishi cha skrini inayogawanywa"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Funga"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Rudi nyuma"</string>
     <string name="handle_text" msgid="1766582106752184456">"Ncha"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Skrini nzima"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Hali ya Kompyuta ya mezani"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Gawa Skrini"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Zaidi"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Inayoelea"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 21b7412..2039685 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -34,6 +34,8 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"திரைப் பிரிப்பு அம்சத்தில் ஆப்ஸ் செயல்படாமல் போகக்கூடும்."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"திரையைப் பிரிப்பதைப் ஆப்ஸ் ஆதரிக்கவில்லை."</string>
+    <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+    <skip />
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"இரண்டாம்நிலைத் திரையில் ஆப்ஸ் வேலை செய்யாமல் போகக்கூடும்."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"இரண்டாம்நிலைத் திரைகளில் பயன்பாட்டைத் தொடங்க முடியாது."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"திரையைப் பிரிக்கும் பிரிப்பான்"</string>
@@ -88,4 +90,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string>
     <string name="back_button_text" msgid="1469718707134137085">"பின்செல்லும்"</string>
     <string name="handle_text" msgid="1766582106752184456">"ஹேண்டில்"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"முழுத்திரை"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"டெஸ்க்டாப் பயன்முறை"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"திரையைப் பிரிக்கும்"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"கூடுதல் விருப்பத்தேர்வுகள்"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"மிதக்கும் சாளரம்"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 18c3719..0c0114a 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ఆన్‌స్టాచ్"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"స్క్రీన్ విభజనతో యాప్‌ పని చేయకపోవచ్చు."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"యాప్‌లో స్క్రీన్ విభజనకు మద్దతు లేదు."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ఈ యాప్‌ను 1 విండోలో మాత్రమే తెరవవచ్చు."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్‌ప్లేలో యాప్ పని చేయకపోవచ్చు."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్‌ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"మూసివేయండి"</string>
     <string name="back_button_text" msgid="1469718707134137085">"వెనుకకు"</string>
     <string name="handle_text" msgid="1766582106752184456">"హ్యాండిల్"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"ఫుల్-స్క్రీన్"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"డెస్క్‌టాప్ మోడ్"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"స్ప్లిట్ స్క్రీన్"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"మరిన్ని"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"ఫ్లోట్"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 9e11d66..9f3a146 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"เอาออกจากที่เก็บส่วนตัว"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"แอปอาจใช้ไม่ได้กับโหมดแบ่งหน้าจอ"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"แอปไม่สนับสนุนการแยกหน้าจอ"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"แอปนี้เปิดได้ใน 1 หน้าต่างเท่านั้น"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"แอปอาจไม่ทำงานในจอแสดงผลรอง"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"แอปไม่รองรับการเรียกใช้ในจอแสดงผลรอง"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"เส้นแบ่งหน้าจอ"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"ปิด"</string>
     <string name="back_button_text" msgid="1469718707134137085">"กลับ"</string>
     <string name="handle_text" msgid="1766582106752184456">"แฮนเดิล"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"เต็มหน้าจอ"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"โหมดเดสก์ท็อป"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"แยกหน้าจอ"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"เพิ่มเติม"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"ล่องลอย"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index fbe0347..c20a07f 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"I-unstash"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Posibleng hindi gumana ang app sa split screen."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Hindi sinusuportahan ng app ang split-screen."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Sa 1 window lang puwedeng buksan ang app na ito."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Maaaring hindi gumana ang app sa pangalawang display."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Hindi sinusuportahan ng app ang paglulunsad sa mga pangalawang display."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Divider ng split-screen"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Isara"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Bumalik"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Desktop Mode"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Higit pa"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index dca58b8..aeb86da 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Depolama"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Uygulama bölünmüş ekranda çalışmayabilir."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uygulama bölünmüş ekranı desteklemiyor."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu uygulama yalnızca 1 pencerede açılabilir."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uygulama ikincil ekranda çalışmayabilir."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uygulama ikincil ekranlarda başlatılmayı desteklemiyor."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Bölünmüş ekran ayırıcı"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Kapat"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Geri"</string>
     <string name="handle_text" msgid="1766582106752184456">"Herkese açık kullanıcı adı"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Tam Ekran"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Masaüstü Modu"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Bölünmüş Ekran"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Daha Fazla"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Havada Süzülen"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index f7a59d3..b589ed8 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показати"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Додаток може не працювати в режимі розділеного екрана."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Додаток не підтримує розділення екрана."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Цей додаток можна відкрити лише в одному вікні."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Додаток може не працювати на додатковому екрані."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Додаток не підтримує запуск на додаткових екранах."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Розділювач екрана"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Закрити"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
     <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"На весь екран"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Режим комп’ютера"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Розділити екран"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Більше"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Плаваюче вікно"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 0ff1b6c..81672bf 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن ہے کہ ایپ اسپلٹ اسکرین کے ساتھ کام نہ کرے۔"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ایپ سپلٹ اسکرین کو سپورٹ نہیں کرتی۔"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"یہ ایپ صرف 1 ونڈو میں کھولی جا سکتی ہے۔"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن ہے ایپ ثانوی ڈسپلے پر کام نہ کرے۔"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ایپ ثانوی ڈسپلیز پر شروعات کا تعاون نہیں کرتی۔"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"سپلٹ اسکرین تقسیم کار"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"بند کریں"</string>
     <string name="back_button_text" msgid="1469718707134137085">"پیچھے"</string>
     <string name="handle_text" msgid="1766582106752184456">"ہینڈل"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"مکمل اسکرین"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"ڈیسک ٹاپ موڈ"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"اسپلٹ اسکرین"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"مزید"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"فلوٹ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index ee9b4dc..d0384e9 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Chiqarish"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Bu ilova ekranni ikkiga ajratish rejimini dastaklamaydi."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Bu ilova ekranni bo‘lish xususiyatini qo‘llab-quvvatlamaydi."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu ilovani faqat 1 ta oynada ochish mumkin."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Bu ilova qo‘shimcha ekranda ishlamasligi mumkin."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Bu ilova qo‘shimcha ekranlarda ishga tushmaydi."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Ekranni ikkiga bo‘lish chizig‘i"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Yopish"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Orqaga"</string>
     <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Butun ekran"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Desktop rejimi"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Ekranni ikkiga ajratish"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Yana"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Pufakli"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 67ab2a0..49986b5 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Hiện"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Ứng dụng có thể không hoạt động với tính năng chia đôi màn hình."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Ứng dụng không hỗ trợ chia đôi màn hình."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ứng dụng này chỉ có thể mở 1 cửa sổ."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Ứng dụng có thể không hoạt động trên màn hình phụ."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Ứng dụng không hỗ trợ khởi chạy trên màn hình phụ."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Bộ chia chia đôi màn hình"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Đóng"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Quay lại"</string>
     <string name="handle_text" msgid="1766582106752184456">"Xử lý"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Toàn màn hình"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Chế độ máy tính"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Chia đôi màn hình"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Tuỳ chọn khác"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Nổi"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index 8fdf1d4..acdb252 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消隐藏"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"应用可能无法在分屏模式下正常运行。"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"应用不支持分屏。"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此应用只能在 1 个窗口中打开。"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"应用可能无法在辅显示屏上正常运行。"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"应用不支持在辅显示屏上启动。"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"分屏分隔线"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"关闭"</string>
     <string name="back_button_text" msgid="1469718707134137085">"返回"</string>
     <string name="handle_text" msgid="1766582106752184456">"处理"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"全屏"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"桌面模式"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"分屏"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"悬浮"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index 5dce250..b1a957e5 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消保護"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"應用程式可能無法在分割畫面中運作。"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"應用程式不支援分割畫面。"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此應用程式只可在 1 個視窗中開啟"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示屏上運作。"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示屏上啟動。"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"分割畫面分隔線"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
     <string name="back_button_text" msgid="1469718707134137085">"返去"</string>
     <string name="handle_text" msgid="1766582106752184456">"控點"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"全螢幕"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"桌面模式"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"分割螢幕"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"浮動"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index c449c2e..bb3dba1 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消暫時隱藏"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"應用程式可能無法在分割畫面中運作。"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"這個應用程式不支援分割畫面。"</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"這個應用程式只能在 1 個視窗中開啟。"</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示器上運作。"</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示器上啟動。"</string>
     <string name="accessibility_divider" msgid="703810061635792791">"分割畫面分隔線"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
     <string name="back_button_text" msgid="1469718707134137085">"返回"</string>
     <string name="handle_text" msgid="1766582106752184456">"控點"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"全螢幕"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"電腦模式"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"分割畫面"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"浮動"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index b470e7f..51a23ff 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -34,6 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Susa isiteshi"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"Izinhlelo zokusebenza kungenzeka zingasebenzi ngesikrini esihlukanisiwe."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uhlelo lokusebenza alusekeli isikrini esihlukanisiwe."</string>
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Le-app ingavulwa kuphela ewindini eli-1."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uhlelo lokusebenza kungenzeka lungasebenzi kusibonisi sesibili."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uhlelo lokusebenza alusekeli ukuqalisa kuzibonisi zesibili."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"Isihlukanisi sokuhlukanisa isikrini"</string>
@@ -88,4 +89,9 @@
     <string name="close_button_text" msgid="2913281996024033299">"Vala"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Emuva"</string>
     <string name="handle_text" msgid="1766582106752184456">"Isibambo"</string>
+    <string name="fullscreen_text" msgid="1162316685217676079">"Isikrini esigcwele"</string>
+    <string name="desktop_text" msgid="1077633567027630454">"Imodi Yedeskithophu"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Hlukanisa isikrini"</string>
+    <string name="more_button_text" msgid="3655388105592893530">"Okwengeziwe"</string>
+    <string name="float_button_text" msgid="9221657008391364581">"Iflowuthi"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index df5f921..c6197c8 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -111,4 +111,8 @@
 
     <!-- Whether to dim a split-screen task when the other is the IME target -->
     <bool name="config_dimNonImeAttachedSide">true</bool>
+
+    <!-- Components support to launch multiple instances into split-screen -->
+    <string-array name="config_componentsSupportMultiInstancesSplit">
+    </string-array>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 4807f08..9490ddc 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -69,8 +69,10 @@
     <!-- Multi-Window strings -->
     <!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity to be displayed in split-screen and that things might crash/not work properly [CHAR LIMIT=NONE] -->
     <string name="dock_forced_resizable">App may not work with split-screen.</string>
-    <!-- Warning message when we try to dock a non-resizeable task and launch it in fullscreen instead. -->
+    <!-- Warning message when we try to dock a non-resizeable task and launch it in fullscreen instead  [CHAR LIMIT=NONE] -->
     <string name="dock_non_resizeble_failed_to_dock_text">App does not support split-screen.</string>
+    <!-- Warning message when we try to dock an app not supporting multiple instances split into multiple sides [CHAR LIMIT=NONE] -->
+    <string name="dock_multi_instances_not_supported_text">This app can only be opened in 1 window.</string>
     <!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity to be displayed on a secondary display and that things might crash/not work properly [CHAR LIMIT=NONE] -->
     <string name="forced_resizable_secondary_display">App may not work on a secondary display.</string>
     <!-- Warning message when we try to launch a non-resizeable activity on a secondary display and launch it on the primary instead. -->
@@ -202,4 +204,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/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index dec1e38..065fd95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -16,14 +16,12 @@
 
 package com.android.wm.shell;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 
@@ -49,7 +47,6 @@
 import android.window.StartingWindowRemovalInfo;
 import android.window.TaskAppearedInfo;
 import android.window.TaskOrganizer;
-import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
@@ -568,6 +565,22 @@
         }
     }
 
+    /**
+     * Return list of {@link RunningTaskInfo}s for the given display.
+     *
+     * @return filtered list of tasks or empty list
+     */
+    public ArrayList<RunningTaskInfo> getRunningTasks(int displayId) {
+        ArrayList<RunningTaskInfo> result = new ArrayList<>();
+        for (int i = 0; i < mTasks.size(); i++) {
+            RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+            if (taskInfo.displayId == displayId) {
+                result.add(taskInfo);
+            }
+        }
+        return result;
+    }
+
     /** Gets running task by taskId. Returns {@code null} if no such task observed. */
     @Nullable
     public RunningTaskInfo getRunningTaskInfo(int taskId) {
@@ -694,57 +707,6 @@
         taskListener.reparentChildSurfaceToTask(taskId, sc, t);
     }
 
-    /**
-     * Create a {@link WindowContainerTransaction} to clear task bounds.
-     *
-     * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
-     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
-     *
-     * @param displayId display id for tasks that will have bounds cleared
-     * @return {@link WindowContainerTransaction} with pending operations to clear bounds
-     */
-    public WindowContainerTransaction prepareClearBoundsForStandardTasks(int displayId) {
-        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        for (int i = 0; i < mTasks.size(); i++) {
-            RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
-            if ((taskInfo.displayId == displayId) && (taskInfo.getActivityType()
-                    == WindowConfiguration.ACTIVITY_TYPE_STANDARD)) {
-                ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
-                        taskInfo.token, taskInfo);
-                wct.setBounds(taskInfo.token, null);
-            }
-        }
-        return wct;
-    }
-
-    /**
-     * Create a {@link WindowContainerTransaction} to clear task level freeform setting.
-     *
-     * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
-     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
-     *
-     * @param displayId display id for tasks that will have windowing mode reset to {@link
-     *                  WindowConfiguration#WINDOWING_MODE_UNDEFINED}
-     * @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
-     */
-    public WindowContainerTransaction prepareClearFreeformForStandardTasks(int displayId) {
-        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        for (int i = 0; i < mTasks.size(); i++) {
-            RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
-            if (taskInfo.displayId == displayId
-                    && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
-                    && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
-                ProtoLog.d(WM_SHELL_DESKTOP_MODE,
-                        "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
-                        taskInfo);
-                wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
-            }
-        }
-        return wct;
-    }
-
     private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
             int event) {
         ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 215308d..00b9fced 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -30,6 +30,8 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.wm.shell.transition.Transitions;
+
 /**
  * Wrapper to handle the ActivityEmbedding animation update in one
  * {@link SurfaceControl.Transaction}.
@@ -50,6 +52,16 @@
     /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
     @NonNull
     private final Rect mWholeAnimationBounds = new Rect();
+    /**
+     * Area in absolute coordinate that should represent all the content to show for this window.
+     * This should be the end bounds for opening window, and start bounds for closing window in case
+     * the window is resizing during the open/close transition.
+     */
+    @NonNull
+    private final Rect mContentBounds = new Rect();
+    /** Offset relative to the window parent surface for {@link #mContentBounds}. */
+    @NonNull
+    private final Point mContentRelOffset = new Point();
 
     @NonNull
     final Transformation mTransformation = new Transformation();
@@ -80,6 +92,21 @@
         mChange = change;
         mLeash = leash;
         mWholeAnimationBounds.set(wholeAnimationBounds);
+        if (Transitions.isClosingType(change.getMode())) {
+            // When it is closing, we want to show the content at the start position in case the
+            // window is resizing as well. For example, when the activities is changing from split
+            // to stack, the bottom TaskFragment will be resized to fullscreen when hiding.
+            final Rect startBounds = change.getStartAbsBounds();
+            final Rect endBounds = change.getEndAbsBounds();
+            mContentBounds.set(startBounds);
+            mContentRelOffset.set(change.getEndRelOffset());
+            mContentRelOffset.offset(
+                    startBounds.left - endBounds.left,
+                    startBounds.top - endBounds.top);
+        } else {
+            mContentBounds.set(change.getEndAbsBounds());
+            mContentRelOffset.set(change.getEndRelOffset());
+        }
     }
 
     /**
@@ -110,8 +137,7 @@
     /** To be overridden by subclasses to adjust the animation surface change. */
     void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
         // Update the surface position and alpha.
-        final Point offset = mChange.getEndRelOffset();
-        mTransformation.getMatrix().postTranslate(offset.x, offset.y);
+        mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
         t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
         t.setAlpha(mLeash, mTransformation.getAlpha());
 
@@ -119,8 +145,8 @@
         // positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
         final int positionX = Math.round(mMatrix[MTRANS_X]);
         final int positionY = Math.round(mMatrix[MTRANS_Y]);
-        final Rect cropRect = new Rect(mChange.getEndAbsBounds());
-        cropRect.offset(positionX - offset.x, positionY - offset.y);
+        final Rect cropRect = new Rect(mContentBounds);
+        cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y);
 
         // Store the current offset of the surface top left from (0,0) in absolute coordinate.
         final int offsetX = cropRect.left;
@@ -133,7 +159,7 @@
         } else if (mAnimation.hasExtension()) {
             // Allow the surface to be shown in its original bounds in case we want to use edge
             // extensions.
-            cropRect.union(mChange.getEndAbsBounds());
+            cropRect.union(mContentBounds);
         }
 
         // cropRect is in absolute coordinate, so we need to translate it to surface top left.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 921861a..c0a6456 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -304,6 +304,7 @@
         // This is because the TaskFragment surface/change won't contain the Activity's before its
         // reparent.
         Animation changeAnimation = null;
+        Rect parentBounds = new Rect();
         for (TransitionInfo.Change change : info.getChanges()) {
             if (change.getMode() != TRANSIT_CHANGE
                     || change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
@@ -326,10 +327,15 @@
                 }
             }
 
+            // The TaskFragment may be enter/exit split, so we take the union of both as the parent
+            // size.
+            parentBounds.union(boundsAnimationChange.getStartAbsBounds());
+            parentBounds.union(boundsAnimationChange.getEndAbsBounds());
+
             // There are two animations in the array. The first one is for the start leash
             // (snapshot), and the second one is for the end leash (TaskFragment).
             final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change,
-                    boundsAnimationChange.getEndAbsBounds());
+                    parentBounds);
             // Keep track as we might need to add background color for the animation.
             // Although there may be multiple change animation, record one of them is sufficient
             // because the background color will be added to the root leash for the whole animation.
@@ -352,6 +358,11 @@
                     animations[1], boundsAnimationChange));
         }
 
+        if (parentBounds.isEmpty()) {
+            throw new IllegalStateException(
+                    "There should be at least one changing window to play the change animation");
+        }
+
         // If there is no corresponding open/close window with the change, we should show background
         // color to cover the empty part of the screen.
         boolean shouldShouldBackgroundColor = true;
@@ -368,10 +379,10 @@
                 // No-op if it will be covered by the changing parent window.
                 animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
             } else if (Transitions.isClosingType(change.getMode())) {
-                animation = mAnimationSpec.createChangeBoundsCloseAnimation(change);
+                animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
                 shouldShouldBackgroundColor = false;
             } else {
-                animation = mAnimationSpec.createChangeBoundsOpenAnimation(change);
+                animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds);
                 shouldShouldBackgroundColor = false;
             }
             adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 2bb7369..d10a674 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -21,7 +21,6 @@
 import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
 
 import android.content.Context;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
@@ -80,15 +79,25 @@
 
     /** Animation for window that is opening in a change transition. */
     @NonNull
-    Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change) {
+    Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change,
+            @NonNull Rect parentBounds) {
+        // Use end bounds for opening.
         final Rect bounds = change.getEndAbsBounds();
-        final Point offset = change.getEndRelOffset();
-        // The window will be animated in from left or right depends on its position.
-        final int startLeft = offset.x == 0 ? -bounds.width() : bounds.width();
+        final int startLeft;
+        final int startTop;
+        if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+            // The window will be animated in from left or right depending on its position.
+            startTop = 0;
+            startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+        } else {
+            // The window will be animated in from top or bottom depending on its position.
+            startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+            startLeft = 0;
+        }
 
         // The position should be 0-based as we will post translate in
         // ActivityEmbeddingAnimationAdapter#onAnimationUpdate
-        final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0);
+        final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0);
         animation.setInterpolator(mFastOutExtraSlowInInterpolator);
         animation.setDuration(CHANGE_ANIMATION_DURATION);
         animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
@@ -98,15 +107,25 @@
 
     /** Animation for window that is closing in a change transition. */
     @NonNull
-    Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change) {
-        final Rect bounds = change.getEndAbsBounds();
-        final Point offset = change.getEndRelOffset();
-        // The window will be animated out to left or right depends on its position.
-        final int endLeft = offset.x == 0 ? -bounds.width() : bounds.width();
+    Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change,
+            @NonNull Rect parentBounds) {
+        // Use start bounds for closing.
+        final Rect bounds = change.getStartAbsBounds();
+        final int endTop;
+        final int endLeft;
+        if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+            // The window will be animated out to left or right depending on its position.
+            endTop = 0;
+            endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+        } else {
+            // The window will be animated out to top or bottom depending on its position.
+            endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+            endLeft = 0;
+        }
 
         // The position should be 0-based as we will post translate in
         // ActivityEmbeddingAnimationAdapter#onAnimationUpdate
-        final Animation animation = new TranslateAnimation(0, endLeft, 0, 0);
+        final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop);
         animation.setInterpolator(mFastOutExtraSlowInInterpolator);
         animation.setDuration(CHANGE_ANIMATION_DURATION);
         animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index a0dde6a..2ec9e8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.animation;
 
+import android.graphics.Path;
 import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
 import android.view.animation.PathInterpolator;
@@ -53,6 +54,11 @@
     public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
 
     /**
+     * The default emphasized interpolator. Used for hero / emphasized movement of content.
+     */
+    public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
+
+    /**
      * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
      * is disappearing e.g. when moving off screen.
      */
@@ -81,4 +87,14 @@
 
     public static final PathInterpolator DIM_INTERPOLATOR =
             new PathInterpolator(.23f, .87f, .52f, -0.11f);
+
+    // Create the default emphasized interpolator
+    private static PathInterpolator createEmphasizedInterpolator() {
+        Path path = new Path();
+        // Doing the same as fast_out_extra_slow_in
+        path.moveTo(0f, 0f);
+        path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
+        path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
+        return new PathInterpolator(path);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
new file mode 100644
index 0000000..36cf29a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -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.wm.shell.back;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
+import android.graphics.Color;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+
+/**
+ * Controls background surface for the back animations
+ */
+public class BackAnimationBackground {
+    private static final int BACKGROUND_LAYER = -1;
+    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+    private SurfaceControl mBackgroundSurface;
+
+    public BackAnimationBackground(RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+    }
+
+    void ensureBackground(int color, @NonNull SurfaceControl.Transaction transaction) {
+        if (mBackgroundSurface != null) {
+            return;
+        }
+
+        final float[] colorComponents = new float[] { Color.red(color) / 255.f,
+                Color.green(color) / 255.f, Color.blue(color) / 255.f };
+
+        final SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
+                .setName("back-animation-background")
+                .setCallsite("BackAnimationBackground")
+                .setColorLayer();
+
+        mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder);
+        mBackgroundSurface = colorLayerBuilder.build();
+        transaction.setColor(mBackgroundSurface, colorComponents)
+                .setLayer(mBackgroundSurface, BACKGROUND_LAYER)
+                .show(mBackgroundSurface);
+    }
+
+    void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
+        if (mBackgroundSurface == null) {
+            return;
+        }
+
+        if (mBackgroundSurface.isValid()) {
+            transaction.remove(mBackgroundSurface);
+        }
+        mBackgroundSurface = null;
+    }
+}
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..57a0fd5 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
@@ -47,6 +47,7 @@
 import android.view.RemoteAnimationTarget;
 import android.window.BackAnimationAdapter;
 import android.window.BackEvent;
+import android.window.BackMotionEvent;
 import android.window.BackNavigationInfo;
 import android.window.IBackAnimationFinishedCallback;
 import android.window.IBackAnimationRunner;
@@ -61,7 +62,6 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -69,7 +69,7 @@
  * Controls the window animation run when a user initiates a back gesture.
  */
 public class BackAnimationController implements RemoteCallable<BackAnimationController> {
-    private static final String TAG = "BackAnimationController";
+    private static final String TAG = "ShellBackPreview";
     private static final int SETTING_VALUE_OFF = 0;
     private static final int SETTING_VALUE_ON = 1;
     public static final boolean IS_ENABLED =
@@ -81,19 +81,16 @@
                     SETTING_VALUE_OFF) == SETTING_VALUE_ON;
     /** Predictive back animation developer option */
     private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
-    // TODO (b/241808055) Find a appropriate time to remove during refactor
-    private static final boolean ENABLE_SHELL_TRANSITIONS = Transitions.ENABLE_SHELL_TRANSITIONS;
     /**
-     * Max duration to wait for a transition to finish before accepting another gesture start
-     * request.
+     * Max duration to wait for an animation to finish before triggering the real back.
      */
-    private static final long MAX_TRANSITION_DURATION = 2000;
+    private static final long MAX_ANIMATION_DURATION = 2000;
 
     /** True when a back gesture is ongoing */
     private boolean mBackGestureStarted = false;
 
-    /** Tracks if an uninterruptible transition is in progress */
-    private boolean mTransitionInProgress = false;
+    /** Tracks if an uninterruptible animation is in progress */
+    private boolean mPostCommitAnimationInProgress = false;
     /** Tracks if we should start the back gesture on the next motion move event */
     private boolean mShouldStartOnNextMoveEvent = false;
     /** @see #setTriggerBack(boolean) */
@@ -107,9 +104,9 @@
     private final ShellController mShellController;
     private final ShellExecutor mShellExecutor;
     private final Handler mBgHandler;
-    private final Runnable mResetTransitionRunnable = () -> {
-        ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Transition didn't finish in %d ms. Resetting...",
-                MAX_TRANSITION_DURATION);
+    private final Runnable mAnimationTimeoutRunnable = () -> {
+        ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...",
+                MAX_ANIMATION_DURATION);
         onBackAnimationFinished();
     };
 
@@ -120,8 +117,8 @@
     private final TouchTracker mTouchTracker = new TouchTracker();
 
     private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
-    private final Transitions mTransitions;
-    private BackTransitionHandler mBackTransitionHandler;
+    @Nullable
+    private IOnBackInvokedCallback mActiveCallback;
 
     @VisibleForTesting
     final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
@@ -130,9 +127,9 @@
         @Override
         public void focusLost(IBinder inputToken) {
             mShellExecutor.execute(() -> {
-                if (!mBackGestureStarted || mTransitionInProgress) {
-                    // If an uninterruptible transition is already in progress, we should ignore
-                    // this due to the transition may cause focus lost. (alpha = 0)
+                if (!mBackGestureStarted || mPostCommitAnimationInProgress) {
+                    // If an uninterruptible animation is already in progress, we should ignore
+                    // this due to it may cause focus lost. (alpha = 0)
                     return;
                 }
                 ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Target window lost focus.");
@@ -142,16 +139,18 @@
         }
     };
 
+    private final BackAnimationBackground mAnimationBackground;
+
     public BackAnimationController(
             @NonNull ShellInit shellInit,
             @NonNull ShellController shellController,
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
             @NonNull @ShellBackgroundThread Handler backgroundHandler,
             Context context,
-            Transitions transitions) {
+            @NonNull BackAnimationBackground backAnimationBackground) {
         this(shellInit, shellController, shellExecutor, backgroundHandler,
                 ActivityTaskManager.getService(), context, context.getContentResolver(),
-                transitions);
+                backAnimationBackground);
     }
 
     @VisibleForTesting
@@ -162,7 +161,7 @@
             @NonNull @ShellBackgroundThread Handler bgHandler,
             @NonNull IActivityTaskManager activityTaskManager,
             Context context, ContentResolver contentResolver,
-            Transitions transitions) {
+            @NonNull BackAnimationBackground backAnimationBackground) {
         mShellController = shellController;
         mShellExecutor = shellExecutor;
         mActivityTaskManager = activityTaskManager;
@@ -170,7 +169,7 @@
         mContentResolver = contentResolver;
         mBgHandler = bgHandler;
         shellInit.addInitCallback(this::onInit, this);
-        mTransitions = transitions;
+        mAnimationBackground = backAnimationBackground;
     }
 
     @VisibleForTesting
@@ -181,12 +180,26 @@
     private void onInit() {
         setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
         createAdapter();
-        if (ENABLE_SHELL_TRANSITIONS) {
-            mBackTransitionHandler = new BackTransitionHandler(this);
-            mTransitions.addHandler(mBackTransitionHandler);
-        }
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
                 this::createExternalInterface, this);
+
+        initBackAnimationRunners();
+    }
+
+    private void initBackAnimationRunners() {
+        if (!IS_U_ANIMATION_ENABLED) {
+            return;
+        }
+
+        final CrossTaskBackAnimation crossTaskAnimation =
+                new CrossTaskBackAnimation(mContext, mAnimationBackground);
+        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
+                crossTaskAnimation.mBackAnimationRunner);
+        final CrossActivityAnimation crossActivityAnimation =
+                new CrossActivityAnimation(mContext, mAnimationBackground);
+        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+                crossActivityAnimation.mBackAnimationRunner);
+        // TODO (236760237): register dialog close animation when it's completed.
     }
 
     private void setupAnimationDeveloperSettingsObserver(
@@ -211,8 +224,7 @@
                 Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF);
         boolean isEnabled = settingValue == SETTING_VALUE_ON;
         mEnableAnimations.set(isEnabled);
-        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s",
-                isEnabled);
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
     }
 
     public BackAnimation getBackAnimationImpl() {
@@ -266,13 +278,16 @@
         public void setBackToLauncherCallback(IOnBackInvokedCallback callback,
                 IRemoteAnimationRunner runner) {
             executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
-                    (controller) -> controller.setBackToLauncherCallback(callback, runner));
+                    (controller) -> controller.registerAnimation(
+                            BackNavigationInfo.TYPE_RETURN_TO_HOME,
+                            new BackAnimationRunner(callback, runner)));
         }
 
         @Override
         public void clearBackToLauncherCallback() {
             executeRemoteCallWithTaskPermission(mController, "clearBackToLauncherCallback",
-                    (controller) -> controller.clearBackToLauncherCallback());
+                    (controller) -> controller.unregisterAnimation(
+                            BackNavigationInfo.TYPE_RETURN_TO_HOME));
         }
 
         @Override
@@ -281,45 +296,13 @@
         }
     }
 
-    @VisibleForTesting
-    void setBackToLauncherCallback(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner) {
-        mAnimationDefinition.set(BackNavigationInfo.TYPE_RETURN_TO_HOME,
-                new BackAnimationRunner(callback, runner));
+    void registerAnimation(@BackNavigationInfo.BackTargetType int type,
+            @NonNull BackAnimationRunner runner) {
+        mAnimationDefinition.set(type, runner);
     }
 
-    private void clearBackToLauncherCallback() {
-        mAnimationDefinition.remove(BackNavigationInfo.TYPE_RETURN_TO_HOME);
-    }
-
-    @VisibleForTesting
-    void onBackAnimationFinished() {
-        if (!mTransitionInProgress) {
-            return;
-        }
-
-        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
-
-        // Trigger real back.
-        if (mBackNavigationInfo != null) {
-            IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
-            if (mTriggerBack) {
-                dispatchOnBackInvoked(callback);
-            } else {
-                dispatchOnBackCancelled(callback);
-            }
-        }
-
-        // In legacy transition, it would use `Task.mBackGestureStarted` in core to handle the
-        // following transition when back callback is invoked.
-        // If the back callback is not invoked, we should reset the token and finish the whole back
-        // navigation without waiting the transition.
-        if (!ENABLE_SHELL_TRANSITIONS) {
-            finishBackNavigation();
-        } else if (!mTriggerBack) {
-            // reset the token to prevent it consume next transition.
-            mBackTransitionHandler.setDepartingWindowContainerToken(null);
-            finishBackNavigation();
-        }
+    void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
+        mAnimationDefinition.remove(type);
     }
 
     /**
@@ -328,7 +311,7 @@
      */
     public void onMotionEvent(float touchX, float touchY, int keyAction,
             @BackEvent.SwipeEdge int swipeEdge) {
-        if (mTransitionInProgress) {
+        if (mPostCommitAnimationInProgress) {
             return;
         }
 
@@ -345,7 +328,7 @@
                 onGestureStarted(touchX, touchY, swipeEdge);
                 mShouldStartOnNextMoveEvent = false;
             }
-            onMove(touchX, touchY, swipeEdge);
+            onMove();
         } else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) {
             ProtoLog.d(WM_SHELL_BACK_PREVIEW,
                     "Finishing gesture with event action: %d", keyAction);
@@ -383,30 +366,28 @@
             return;
         }
         final int backType = backNavigationInfo.getType();
-        final IOnBackInvokedCallback targetCallback;
-        final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
+        final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
         if (shouldDispatchToAnimator) {
-            mAnimationDefinition.get(backType).startGesture();
+            if (mAnimationDefinition.contains(backType)) {
+                mActiveCallback = mAnimationDefinition.get(backType).getCallback();
+                mAnimationDefinition.get(backType).startGesture();
+            } else {
+                mActiveCallback = null;
+            }
         } else {
-            targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
-            dispatchOnBackStarted(targetCallback, mTouchTracker.createStartEvent(null));
+            mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
+            dispatchOnBackStarted(mActiveCallback, mTouchTracker.createStartEvent(null));
         }
     }
 
-    private void onMove(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
-        if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()) {
+    private void onMove() {
+        if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()
+                || mActiveCallback == null) {
             return;
         }
-        final BackEvent backEvent = mTouchTracker.createProgressEvent();
 
-        int backType = mBackNavigationInfo.getType();
-        IOnBackInvokedCallback targetCallback;
-        if (shouldDispatchToAnimator(backType)) {
-            targetCallback = mAnimationDefinition.get(backType).getCallback();
-        } else {
-            targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
-        }
-        dispatchOnBackProgressed(targetCallback, backEvent);
+        final BackMotionEvent backEvent = mTouchTracker.createProgressEvent();
+        dispatchOnBackProgressed(mActiveCallback, backEvent);
     }
 
     private void injectBackKey() {
@@ -428,71 +409,19 @@
         }
     }
 
-    private void onGestureFinished(boolean fromTouch) {
-        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
-        if (!mBackGestureStarted) {
-            finishBackNavigation();
-            return;
-        }
-
-        if (fromTouch) {
-            // Let touch reset the flag otherwise it will start a new back navigation and refresh
-            // the info when received a new move event.
-            mBackGestureStarted = false;
-        }
-
-        if (mTransitionInProgress) {
-            return;
-        }
-
-        if (mBackNavigationInfo == null) {
-            // No focus window found or core are running recents animation, inject back key as
-            // legacy behavior.
-            if (mTriggerBack) {
-                injectBackKey();
-            }
-            finishBackNavigation();
-            return;
-        }
-
-        int backType = mBackNavigationInfo.getType();
-        boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
-        final BackAnimationRunner runner = mAnimationDefinition.get(backType);
-        IOnBackInvokedCallback targetCallback = shouldDispatchToAnimator
-                ? runner.getCallback() : mBackNavigationInfo.getOnBackInvokedCallback();
-
-        if (shouldDispatchToAnimator) {
-            if (runner.onGestureFinished(mTriggerBack)) {
-                Log.w(TAG, "Gesture released, but animation didn't ready.");
-                return;
-            }
-            startTransition();
-        }
-        if (mTriggerBack) {
-            dispatchOnBackInvoked(targetCallback);
-        } else {
-            dispatchOnBackCancelled(targetCallback);
-        }
-        if (!shouldDispatchToAnimator) {
-            // Animation callback missing. Simply finish animation.
-            finishBackNavigation();
-        }
-    }
-
-    private boolean shouldDispatchToAnimator(int backType) {
+    private boolean shouldDispatchToAnimator() {
         return mEnableAnimations.get()
                 && mBackNavigationInfo != null
-                && mBackNavigationInfo.isPrepareRemoteAnimation()
-                && mAnimationDefinition.contains(backType);
+                && mBackNavigationInfo.isPrepareRemoteAnimation();
     }
 
     private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
-            BackEvent backEvent) {
+            BackMotionEvent backEvent) {
         if (callback == null) {
             return;
         }
         try {
-            if (shouldDispatchAnimation(callback)) {
+            if (mEnableAnimations.get()) {
                 callback.onBackStarted(backEvent);
             }
         } catch (RemoteException e) {
@@ -516,7 +445,7 @@
             return;
         }
         try {
-            if (shouldDispatchAnimation(callback)) {
+            if (mEnableAnimations.get()) {
                 callback.onBackCancelled();
             }
         } catch (RemoteException e) {
@@ -525,12 +454,12 @@
     }
 
     private void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
-            BackEvent backEvent) {
+            BackMotionEvent backEvent) {
         if (callback == null) {
             return;
         }
         try {
-            if (shouldDispatchAnimation(callback)) {
+            if (mEnableAnimations.get()) {
                 callback.onBackProgressed(backEvent);
             }
         } catch (RemoteException e) {
@@ -539,16 +468,15 @@
     }
 
     private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) {
-        return (IS_U_ANIMATION_ENABLED || callback == mAnimationDefinition.get(
-                BackNavigationInfo.TYPE_RETURN_TO_HOME).getCallback())
-                && mEnableAnimations.get();
+        // TODO(b/258698745): Only dispatch to animation callbacks.
+        return mEnableAnimations.get();
     }
 
     /**
      * Sets to true when the back gesture has passed the triggering threshold, false otherwise.
      */
     public void setTriggerBack(boolean triggerBack) {
-        if (mTransitionInProgress) {
+        if (mPostCommitAnimationInProgress) {
             return;
         }
         mTriggerBack = triggerBack;
@@ -559,58 +487,129 @@
         mTouchTracker.setProgressThreshold(progressThreshold);
     }
 
-    @VisibleForTesting
-    void finishBackNavigation() {
-        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
-        BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
-        boolean triggerBack = mTriggerBack;
-        mBackNavigationInfo = null;
-        mTriggerBack = false;
-        mShouldStartOnNextMoveEvent = false;
-        mTouchTracker.reset();
-        if (backNavigationInfo == null) {
-            return;
-        }
-        stopTransition();
+    private void invokeOrCancelBack() {
+        // Make a synchronized call to core before dispatch back event to client side.
+        // If the close transition happens before the core receives onAnimationFinished, there will
+        // play a second close animation for that transition.
         if (mBackAnimationFinishedCallback != null) {
             try {
-                mBackAnimationFinishedCallback.onAnimationFinished(triggerBack);
+                mBackAnimationFinishedCallback.onAnimationFinished(mTriggerBack);
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e);
             }
             mBackAnimationFinishedCallback = null;
         }
-        backNavigationInfo.onBackNavigationFinished(triggerBack);
-    }
 
-    private void startTransition() {
-        if (mTransitionInProgress) {
-            return;
+        if (mBackNavigationInfo != null) {
+            final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
+            if (mTriggerBack) {
+                dispatchOnBackInvoked(callback);
+            } else {
+                dispatchOnBackCancelled(callback);
+            }
         }
-        mTransitionInProgress = true;
-        if (ENABLE_SHELL_TRANSITIONS) {
-            mBackTransitionHandler.setDepartingWindowContainerToken(
-                    mBackNavigationInfo.getDepartingWindowContainerToken());
-        }
-        mShellExecutor.executeDelayed(mResetTransitionRunnable, MAX_TRANSITION_DURATION);
-    }
-
-    void stopTransition() {
-        mShellExecutor.removeCallbacks(mResetTransitionRunnable);
-        mTransitionInProgress = false;
+        finishBackNavigation();
     }
 
     /**
-     * This should be called from {@link BackTransitionHandler#startAnimation} when the following
-     * transition is triggered by the real back callback in {@link #onBackAnimationFinished}.
-     * Will consume the default transition and finish current back navigation.
+     * Called when the gesture is released, then it could start the post commit animation.
      */
-    void finishTransition(Transitions.TransitionFinishCallback finishCallback) {
-        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishTransition()");
-        mShellExecutor.execute(() -> {
+    private void onGestureFinished(boolean fromTouch) {
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
+        if (!mBackGestureStarted) {
             finishBackNavigation();
-            finishCallback.onTransitionFinished(null, null);
-        });
+            return;
+        }
+
+        if (fromTouch) {
+            // Let touch reset the flag otherwise it will start a new back navigation and refresh
+            // the info when received a new move event.
+            mBackGestureStarted = false;
+        }
+
+        if (mPostCommitAnimationInProgress) {
+            ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation is still running");
+            return;
+        }
+
+        if (mBackNavigationInfo == null) {
+            // No focus window found or core are running recents animation, inject back key as
+            // legacy behavior.
+            if (mTriggerBack) {
+                injectBackKey();
+            }
+            finishBackNavigation();
+            return;
+        }
+
+        final int backType = mBackNavigationInfo.getType();
+        // Simply trigger and finish back navigation when no animator defined.
+        if (!shouldDispatchToAnimator() || mActiveCallback == null) {
+            invokeOrCancelBack();
+            return;
+        }
+
+        final BackAnimationRunner runner = mAnimationDefinition.get(backType);
+        if (runner.isWaitingAnimation()) {
+            ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
+            return;
+        }
+        startPostCommitAnimation();
+    }
+
+    /**
+     * Start the phase 2 animation when gesture is released.
+     * Callback to {@link #onBackAnimationFinished} when it is finished or timeout.
+     */
+    private void startPostCommitAnimation() {
+        if (mPostCommitAnimationInProgress) {
+            return;
+        }
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()");
+        mPostCommitAnimationInProgress = true;
+        mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
+
+        // The next callback should be {@link #onBackAnimationFinished}.
+        if (mTriggerBack) {
+            dispatchOnBackInvoked(mActiveCallback);
+        } else {
+            dispatchOnBackCancelled(mActiveCallback);
+        }
+    }
+
+    /**
+     * Called when the post commit animation is completed or timeout.
+     * This will trigger the real {@link IOnBackInvokedCallback} behavior.
+     */
+    @VisibleForTesting
+    void onBackAnimationFinished() {
+        if (!mPostCommitAnimationInProgress) {
+            return;
+        }
+        // Stop timeout runner.
+        mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
+        mPostCommitAnimationInProgress = false;
+
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
+
+        // Trigger the real back.
+        invokeOrCancelBack();
+    }
+
+    /**
+     * This should be called after the whole back navigation is completed.
+     */
+    @VisibleForTesting
+    void finishBackNavigation() {
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
+        mShouldStartOnNextMoveEvent = false;
+        mTouchTracker.reset();
+        mActiveCallback = null;
+        if (mBackNavigationInfo != null) {
+            mBackNavigationInfo.onBackNavigationFinished(mTriggerBack);
+            mBackNavigationInfo = null;
+        }
+        mTriggerBack = false;
     }
 
     private void createAdapter() {
@@ -636,22 +635,20 @@
                     mBackAnimationFinishedCallback = finishedCallback;
 
                     ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
-                    runner.startAnimation(apps, wallpapers, nonApps,
-                            BackAnimationController.this::onBackAnimationFinished);
+                    runner.startAnimation(apps, wallpapers, nonApps, () -> mShellExecutor.execute(
+                            BackAnimationController.this::onBackAnimationFinished));
+
                     if (apps.length >= 1) {
-                        final int backType = mBackNavigationInfo.getType();
-                        IOnBackInvokedCallback targetCallback = mAnimationDefinition.get(backType)
-                                .getCallback();
                         dispatchOnBackStarted(
-                                targetCallback, mTouchTracker.createStartEvent(apps[0]));
+                                mActiveCallback, mTouchTracker.createStartEvent(apps[0]));
                     }
 
                     if (!mBackGestureStarted) {
                         // if the down -> up gesture happened before animation start, we have to
                         // trigger the uninterruptible transition to finish the back animation.
-                        final BackEvent backFinish = mTouchTracker.createProgressEvent(1);
-                        startTransition();
-                        runner.consumeIfGestureFinished(backFinish);
+                        final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
+                        dispatchOnBackProgressed(mActiveCallback, backFinish);
+                        startPostCommitAnimation();
                     }
                 });
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index c53fcfc..d70b8f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -18,12 +18,12 @@
 
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
 
+import android.annotation.NonNull;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
 import android.view.RemoteAnimationTarget;
-import android.window.BackEvent;
 import android.window.IBackAnimationRunner;
 import android.window.IOnBackInvokedCallback;
 
@@ -38,11 +38,11 @@
     private final IOnBackInvokedCallback mCallback;
     private final IRemoteAnimationRunner mRunner;
 
-    private boolean mTriggerBack;
     // Whether we are waiting to receive onAnimationStart
     private boolean mWaitingAnimation;
 
-    BackAnimationRunner(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner) {
+    BackAnimationRunner(@NonNull IOnBackInvokedCallback callback,
+            @NonNull IRemoteAnimationRunner runner) {
         mCallback = callback;
         mRunner = runner;
     }
@@ -83,25 +83,7 @@
         mWaitingAnimation = true;
     }
 
-    boolean onGestureFinished(boolean triggerBack) {
-        if (mWaitingAnimation) {
-            mTriggerBack = triggerBack;
-            return true;
-        }
-        return false;
-    }
-
-    void consumeIfGestureFinished(final BackEvent backFinish) {
-        Log.d(TAG, "Start transition due to gesture is finished");
-        try {
-            mCallback.onBackProgressed(backFinish);
-            if (mTriggerBack) {
-                mCallback.onBackInvoked();
-            } else {
-                mCallback.onBackCancelled();
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "dispatch error: ", e);
-        }
+    boolean isWaitingAnimation() {
+        return mWaitingAnimation;
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java
deleted file mode 100644
index 6d72d9c..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java
+++ /dev/null
@@ -1,78 +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.wm.shell.back;
-
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.wm.shell.transition.Transitions;
-
-class BackTransitionHandler implements Transitions.TransitionHandler {
-    private BackAnimationController mBackAnimationController;
-    private WindowContainerToken mDepartingWindowContainerToken;
-
-    BackTransitionHandler(@NonNull BackAnimationController backAnimationController) {
-        mBackAnimationController = backAnimationController;
-    }
-
-    @Override
-    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        if (mDepartingWindowContainerToken != null) {
-            final TransitionInfo.Change change = info.getChange(mDepartingWindowContainerToken);
-            if (change == null) {
-                return false;
-            }
-
-            startTransaction.hide(change.getLeash());
-            startTransaction.apply();
-            mDepartingWindowContainerToken = null;
-            mBackAnimationController.finishTransition(finishCallback);
-            return true;
-        }
-
-        return false;
-    }
-
-    @Nullable
-    @Override
-    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
-            @NonNull TransitionRequestInfo request) {
-        return null;
-    }
-
-    @Override
-    public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
-            @NonNull Transitions.TransitionFinishCallback finishCallback) {
-    }
-
-    void setDepartingWindowContainerToken(
-            @Nullable WindowContainerToken departingWindowContainerToken) {
-        mDepartingWindowContainerToken = departingWindowContainerToken;
-    }
-}
-
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
new file mode 100644
index 0000000..e36e16c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -0,0 +1,374 @@
+/*
+ * 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 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.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.window.BackEvent;
+import android.window.BackMotionEvent;
+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.animation.Interpolators;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/** Class that defines cross-activity animation. */
+@ShellMainThread
+class CrossActivityAnimation {
+    /**
+     * Minimum scale of the entering/closing window.
+     */
+    private static final float MIN_WINDOW_SCALE = 0.9f;
+
+    /**
+     * Minimum alpha of the closing/entering window.
+     */
+    private static final float CLOSING_MIN_WINDOW_ALPHA = 0.5f;
+
+    /**
+     * Progress value to fly out closing window and fly in entering window.
+     */
+    private static final float SWITCH_ENTERING_WINDOW_PROGRESS = 0.5f;
+
+    /** Max window translation in the Y axis. */
+    private static final int WINDOW_MAX_DELTA_Y = 160;
+
+    /** Duration of fade in/out entering window. */
+    private static final int FADE_IN_DURATION = 100;
+    /** Duration of post animation after gesture committed. */
+    private static final int POST_ANIMATION_DURATION = 350;
+    private static final Interpolator INTERPOLATOR = Interpolators.EMPHASIZED;
+
+    private final Rect mStartTaskRect = new Rect();
+    private final float mCornerRadius;
+
+    // The closing window properties.
+    private final RectF mClosingRect = new RectF();
+
+    // The entering window properties.
+    private final Rect mEnteringStartRect = new Rect();
+    private final RectF mEnteringRect = new RectF();
+
+    private float mCurrentAlpha = 1.0f;
+
+    private float mEnteringMargin = 0;
+    private ValueAnimator mEnteringAnimator;
+    private boolean mEnteringWindowShow = false;
+
+    private final PointF mInitialTouchPos = new PointF();
+
+    private final Matrix mTransformMatrix = new Matrix();
+
+    private final float[] mTmpFloat9 = new float[9];
+
+    private RemoteAnimationTarget mEnteringTarget;
+    private RemoteAnimationTarget mClosingTarget;
+    private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+    private boolean mBackInProgress = false;
+
+    private PointF mTouchPos = new PointF();
+    private IRemoteAnimationFinishedCallback mFinishCallback;
+
+    private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+    final BackAnimationRunner mBackAnimationRunner;
+
+    private final BackAnimationBackground mBackground;
+
+    CrossActivityAnimation(Context context, BackAnimationBackground background) {
+        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+        mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+        mBackground = background;
+    }
+
+    private static float mapRange(float value, float min, float max) {
+        return min + (value * (max - min));
+    }
+
+    private float getInterpolatedProgress(float backProgress) {
+        return INTERPOLATOR.getInterpolation(backProgress);
+    }
+
+    private void startBackAnimation() {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+            return;
+        }
+        mTransaction.setAnimationTransaction();
+
+        // Offset start rectangle to align task bounds.
+        mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
+        mStartTaskRect.offsetTo(0, 0);
+
+        // Draw background with task background color.
+        mBackground.ensureBackground(
+                mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
+    }
+
+    private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) {
+        final float scale = targetRect.width() / mStartTaskRect.width();
+        mTransformMatrix.reset();
+        mTransformMatrix.setScale(scale, scale);
+        mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
+        mTransaction.setAlpha(leash, targetAlpha)
+                .setMatrix(leash, mTransformMatrix, mTmpFloat9)
+                .setWindowCrop(leash, mStartTaskRect)
+                .setCornerRadius(leash, mCornerRadius);
+    }
+
+    private void finishAnimation() {
+        if (mEnteringTarget != null) {
+            mEnteringTarget.leash.release();
+            mEnteringTarget = null;
+        }
+        if (mClosingTarget != null) {
+            mClosingTarget.leash.release();
+            mClosingTarget = null;
+        }
+        if (mBackground != null) {
+            mBackground.removeBackground(mTransaction);
+        }
+
+        mTransaction.apply();
+        mBackInProgress = false;
+        mTransformMatrix.reset();
+        mInitialTouchPos.set(0, 0);
+        mEnteringWindowShow = false;
+        mEnteringMargin = 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());
+            mBackInProgress = true;
+        }
+        mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            return;
+        }
+
+        final float progress = getInterpolatedProgress(backEvent.getProgress());
+        final float touchY = mTouchPos.y;
+
+        final int width = mStartTaskRect.width();
+        final int height = mStartTaskRect.height();
+
+        final float closingScale = mapRange(progress, 1, MIN_WINDOW_SCALE);
+
+        final float closingWidth = closingScale * width;
+        final float closingHeight = (float) height / width * closingWidth;
+
+        // Move the window along the X axis.
+        final float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2;
+
+        // Move the window along the Y axis.
+        final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+        final float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
+        final float closingTop = (height - closingHeight) * 0.5f + deltaY;
+        mClosingRect.set(
+                closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
+        mEnteringRect.set(mClosingRect);
+
+        // Switch closing/entering targets while reach to the threshold progress.
+        if (showEnteringWindow(progress > SWITCH_ENTERING_WINDOW_PROGRESS)) {
+            return;
+        }
+
+        // Present windows and update the alpha.
+        mCurrentAlpha = Math.max(mapRange(progress, 1.0f, 0), CLOSING_MIN_WINDOW_ALPHA);
+        mClosingRect.offset(mEnteringMargin, 0);
+        mEnteringRect.offset(mEnteringMargin - width, 0);
+
+        applyTransform(
+                mClosingTarget.leash, mClosingRect, mEnteringWindowShow ? 0.01f : mCurrentAlpha);
+        applyTransform(
+                mEnteringTarget.leash, mEnteringRect, mEnteringWindowShow ? mCurrentAlpha : 0.01f);
+        mTransaction.apply();
+    }
+
+    private boolean showEnteringWindow(boolean show) {
+        if (mEnteringAnimator == null) {
+            mEnteringAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(FADE_IN_DURATION);
+            mEnteringAnimator.setInterpolator(new AccelerateInterpolator());
+            mEnteringAnimator.addUpdateListener(animation -> {
+                float progress = animation.getAnimatedFraction();
+                final int width = mStartTaskRect.width();
+                mEnteringMargin = width * progress;
+                // We don't animate to 0 or the surface would become invisible and lose focus.
+                final float alpha = progress >= 0.5f ? 0.01f
+                        : mapRange(progress * 2, mCurrentAlpha, 0.01f);
+                mClosingRect.offset(mEnteringMargin, 0);
+                mEnteringRect.offset(mEnteringMargin - width, 0);
+
+                applyTransform(mClosingTarget.leash, mClosingRect, alpha);
+                applyTransform(mEnteringTarget.leash, mEnteringRect, mCurrentAlpha);
+                mTransaction.apply();
+            });
+        }
+
+        if (mEnteringAnimator.isRunning()) {
+            return true;
+        }
+
+        if (mEnteringWindowShow == show) {
+            return false;
+        }
+
+        mEnteringWindowShow = show;
+        if (show) {
+            mEnteringAnimator.start();
+        } else {
+            mEnteringAnimator.reverse();
+        }
+        return true;
+    }
+
+    private void onGestureCommitted() {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            finishAnimation();
+            return;
+        }
+
+        // End the fade in animation.
+        if (mEnteringAnimator.isRunning()) {
+            mEnteringAnimator.cancel();
+        }
+
+        // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+        // coordinate of the gesture driven phase.
+        mEnteringRect.round(mEnteringStartRect);
+        mTransaction.hide(mClosingTarget.leash);
+
+        ValueAnimator valueAnimator =
+                ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION);
+        valueAnimator.setInterpolator(new DecelerateInterpolator());
+        valueAnimator.addUpdateListener(animation -> {
+            float progress = animation.getAnimatedFraction();
+            updatePostCommitEnteringAnimation(progress);
+            mTransaction.apply();
+        });
+
+        valueAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finishAnimation();
+            }
+        });
+        valueAnimator.start();
+    }
+
+    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());
+        float alpha = mapRange(progress, mCurrentAlpha, 1.0f);
+
+        mEnteringRect.set(left, top, left + width, top + height);
+        applyTransform(mEnteringTarget.leash, mEnteringRect, alpha);
+    }
+
+    private final class Callback extends IOnBackInvokedCallback.Default {
+        @Override
+        public void onBackStarted(BackMotionEvent backEvent) {
+            mProgressAnimator.onBackStarted(backEvent,
+                    CrossActivityAnimation.this::onGestureProgress);
+        }
+
+        @Override
+        public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
+            mProgressAnimator.onBackProgressed(backEvent);
+        }
+
+        @Override
+        public void onBackCancelled() {
+            // End the fade in animation.
+            if (mEnteringAnimator.isRunning()) {
+                mEnteringAnimator.cancel();
+            }
+            // TODO (b259608500): Let BackProgressAnimator could play cancel animation.
+            mProgressAnimator.reset();
+            finishAnimation();
+        }
+
+        @Override
+        public void onBackInvoked() {
+            mProgressAnimator.reset();
+            onGestureCommitted();
+        }
+    }
+
+    private final class Runner extends 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 activity animation.");
+            for (RemoteAnimationTarget a : apps) {
+                if (a.mode == MODE_CLOSING) {
+                    mClosingTarget = a;
+                }
+                if (a.mode == MODE_OPENING) {
+                    mEnteringTarget = a;
+                }
+            }
+
+            startBackAnimation();
+            mFinishCallback = finishedCallback;
+        }
+
+        @Override
+        public void onAnimationCancelled(boolean isKeyguardOccluded) {
+            finishAnimation();
+        }
+    }
+}
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..676e259
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -0,0 +1,362 @@
+/*
+ * 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.BackMotionEvent;
+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 int BACKGROUNDCOLOR = 0x43433A;
+
+    /**
+     * 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.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 BackAnimationRunner mBackAnimationRunner;
+
+    private final BackAnimationBackground mBackground;
+
+    CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+        mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+        mBackground = background;
+    }
+
+    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.
+        mBackground.ensureBackground(BACKGROUNDCOLOR, mTransaction);
+    }
+
+    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 (mBackground != null) {
+            mBackground.removeBackground(mTransaction);
+        }
+
+        mTransaction.apply();
+        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));
+    }
+
+    private final class Callback extends IOnBackInvokedCallback.Default  {
+        @Override
+        public void onBackStarted(BackMotionEvent backEvent) {
+            mProgressAnimator.onBackStarted(backEvent,
+                    CrossTaskBackAnimation.this::onGestureProgress);
+        }
+
+        @Override
+        public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
+            mProgressAnimator.onBackProgressed(backEvent);
+        }
+
+        @Override
+        public void onBackCancelled() {
+            mProgressAnimator.reset();
+            finishAnimation();
+        }
+
+        @Override
+        public void onBackInvoked() {
+            mProgressAnimator.reset();
+            onGestureCommitted();
+        }
+    };
+
+    private final class Runner extends 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;
+        }
+    };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/back/OWNERS
new file mode 100644
index 0000000..1e0f9bc
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/OWNERS
@@ -0,0 +1,5 @@
+# WM shell sub-module back navigation owners
+# Bug component: 1152663
+shanh@google.com
+arthurhung@google.com
+wilsonshih@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index ccfac65..695ef4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -19,6 +19,7 @@
 import android.os.SystemProperties;
 import android.view.RemoteAnimationTarget;
 import android.window.BackEvent;
+import android.window.BackMotionEvent;
 
 /**
  * Helper class to record the touch location for gesture and generate back events.
@@ -82,11 +83,11 @@
         mSwipeEdge = BackEvent.EDGE_LEFT;
     }
 
-    BackEvent createStartEvent(RemoteAnimationTarget target) {
-        return new BackEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
+    BackMotionEvent createStartEvent(RemoteAnimationTarget target) {
+        return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
     }
 
-    BackEvent createProgressEvent() {
+    BackMotionEvent createProgressEvent() {
         float progressThreshold = PROGRESS_THRESHOLD >= 0
                 ? PROGRESS_THRESHOLD : mProgressThreshold;
         progressThreshold = progressThreshold == 0 ? 1 : progressThreshold;
@@ -109,8 +110,8 @@
         return createProgressEvent(progress);
     }
 
-    BackEvent createProgressEvent(float progress) {
-        return new BackEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+    BackMotionEvent createProgressEvent(float progress) {
+        return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
     }
 
     public void setProgressThreshold(float progressThreshold) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 3972b59..9674b69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -24,6 +24,7 @@
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
+import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
@@ -37,7 +38,6 @@
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
-import static com.android.wm.shell.floating.FloatingTasksController.SHOW_FLOATING_TASKS_AS_BUBBLES;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
@@ -150,6 +150,9 @@
 
     private final ShellExecutor mBackgroundExecutor;
 
+    // Whether or not we should show bubbles pinned at the bottom of the screen.
+    private boolean mIsBubbleBarEnabled;
+
     private BubbleLogger mLogger;
     private BubbleData mBubbleData;
     @Nullable private BubbleStackView mStackView;
@@ -210,7 +213,6 @@
     /** Drag and drop controller to register listener for onDragStarted. */
     private DragAndDropController mDragAndDropController;
 
-  
     public BubbleController(Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
@@ -473,8 +475,13 @@
 
     @VisibleForTesting
     public void onStatusBarStateChanged(boolean isShade) {
+        boolean didChange = mIsStatusBarShade != isShade;
+        if (DEBUG_BUBBLE_CONTROLLER) {
+            Log.d(TAG, "onStatusBarStateChanged isShade=" + isShade + " didChange=" + didChange);
+        }
         mIsStatusBarShade = isShade;
-        if (!mIsStatusBarShade) {
+        if (!mIsStatusBarShade && didChange) {
+            // Only collapse stack on change
             collapseStack();
         }
 
@@ -526,6 +533,12 @@
         mDataRepository.removeBubblesForUser(removedUserId, parentUserId);
     }
 
+    // TODO(b/256873975): Should pass this into the constructor once flags are available to shell.
+    /** Sets whether the bubble bar is enabled (i.e. bubbles pinned to bottom on large screens). */
+    public void setBubbleBarEnabled(boolean enabled) {
+        mIsBubbleBarEnabled = enabled;
+    }
+
     /** Whether this userId belongs to the current user. */
     private boolean isCurrentProfile(int userId) {
         return userId == UserHandle.USER_ALL
@@ -591,7 +604,8 @@
             }
             mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
         }
-        if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubblePositioner.isLargeScreen()) {
+
+        if (mIsBubbleBarEnabled && mBubblePositioner.isLargeScreen()) {
             mBubblePositioner.setUsePinnedLocation(true);
         } else {
             mBubblePositioner.setUsePinnedLocation(false);
@@ -700,7 +714,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         filter.addAction(Intent.ACTION_SCREEN_OFF);
-        mContext.registerReceiver(mBroadcastReceiver, filter);
+        mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
     }
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -967,14 +981,18 @@
     }
 
     /**
-     * Adds a bubble for a specific intent. These bubbles are <b>not</b> backed by a notification
-     * and remain until the user dismisses the bubble or bubble stack. Only one intent bubble
-     * is supported at a time.
+     * Adds and expands bubble for a specific intent. These bubbles are <b>not</b> backed by a n
+     * otification and remain until the user dismisses the bubble or bubble stack. Only one intent
+     * bubble is supported at a time.
      *
      * @param intent the intent to display in the bubble expanded view.
      */
-    public void addAppBubble(Intent intent) {
+    public void showAppBubble(Intent intent) {
         if (intent == null || intent.getPackage() == null) return;
+
+        PackageManager packageManager = getPackageManagerForUser(mContext, mCurrentUserId);
+        if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return;
+
         Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
         b.setShouldAutoExpand(true);
         inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
@@ -1497,18 +1515,23 @@
         }
         PackageManager packageManager = getPackageManagerForUser(
                 context, entry.getStatusBarNotification().getUser().getIdentifier());
-        ActivityInfo info =
-                intent.getIntent().resolveActivityInfo(packageManager, 0);
+        return isResizableActivity(intent.getIntent(), packageManager, entry.getKey());
+    }
+
+    static boolean isResizableActivity(Intent intent, PackageManager packageManager, String key) {
+        if (intent == null) {
+            Log.w(TAG, "Unable to send as bubble: " + key + " null intent");
+            return false;
+        }
+        ActivityInfo info = intent.resolveActivityInfo(packageManager, 0);
         if (info == null) {
-            Log.w(TAG, "Unable to send as bubble, "
-                    + entry.getKey() + " couldn't find activity info for intent: "
-                    + intent);
+            Log.w(TAG, "Unable to send as bubble: " + key
+                    + " couldn't find activity info for intent: " + intent);
             return false;
         }
         if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
-            Log.w(TAG, "Unable to send as bubble, "
-                    + entry.getKey() + " activity is not resizable for intent: "
-                    + intent);
+            Log.w(TAG, "Unable to send as bubble: " + key
+                    + " activity is not resizable for intent: " + intent);
             return false;
         }
         return true;
@@ -1682,6 +1705,13 @@
         }
 
         @Override
+        public void showAppBubble(Intent intent) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.showAppBubble(intent);
+            });
+        }
+
+        @Override
         public boolean handleDismissalInterception(BubbleEntry entry,
                 @Nullable List<BubbleEntry> children, IntConsumer removeCallback,
                 Executor callbackExecutor) {
@@ -1792,6 +1822,13 @@
         }
 
         @Override
+        public void setBubbleBarEnabled(boolean enabled) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.setBubbleBarEnabled(enabled);
+            });
+        }
+
+        @Override
         public void onNotificationPanelExpandedChanged(boolean expanded) {
             mMainExecutor.execute(
                     () -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index f31a27d..04d62f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -83,7 +83,6 @@
 import com.android.wm.shell.bubbles.animation.ExpandedAnimationController;
 import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationController;
 import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationControllerImpl;
-import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationControllerStub;
 import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout;
 import com.android.wm.shell.bubbles.animation.StackAnimationController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -105,11 +104,6 @@
  */
 public class BubbleStackView extends FrameLayout
         implements ViewTreeObserver.OnComputeInternalInsetsListener {
-    /**
-     * Set to {@code true} to enable home gesture handling in bubbles
-     */
-    public static final boolean HOME_GESTURE_ENABLED =
-            SystemProperties.getBoolean("persist.wm.debug.bubbles_home_gesture", true);
 
     public static final boolean ENABLE_FLING_TO_DISMISS_BUBBLE =
             SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_bubble", true);
@@ -898,12 +892,8 @@
         mExpandedAnimationController = new ExpandedAnimationController(mPositioner,
                 onBubbleAnimatedOut, this);
 
-        if (HOME_GESTURE_ENABLED) {
-            mExpandedViewAnimationController =
-                    new ExpandedViewAnimationControllerImpl(context, mPositioner);
-        } else {
-            mExpandedViewAnimationController = new ExpandedViewAnimationControllerStub();
-        }
+        mExpandedViewAnimationController =
+                new ExpandedViewAnimationControllerImpl(context, mPositioner);
 
         mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
 
@@ -1964,11 +1954,8 @@
 
         if (wasExpanded) {
             stopMonitoringSwipeUpGesture();
-            if (HOME_GESTURE_ENABLED) {
-                animateCollapse();
-            } else {
-                animateCollapseWithScale();
-            }
+            animateCollapse();
+            showManageMenu(false);
             logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
         } else {
             animateExpansion();
@@ -1976,13 +1963,11 @@
             logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
             logBubbleEvent(mExpandedBubble,
                     FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
-            if (HOME_GESTURE_ENABLED) {
-                mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> {
-                    if (!notifPanelExpanded && mIsExpanded) {
-                        startMonitoringSwipeUpGesture();
-                    }
-                });
-            }
+            mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> {
+                if (!notifPanelExpanded && mIsExpanded) {
+                    startMonitoringSwipeUpGesture();
+                }
+            });
         }
         notifyExpansionChanged(mExpandedBubble, mIsExpanded);
     }
@@ -2298,106 +2283,6 @@
         mMainExecutor.executeDelayed(mDelayedAnimation, startDelay);
     }
 
-    private void animateCollapseWithScale() {
-        cancelDelayedExpandCollapseSwitchAnimations();
-
-        if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
-            mManageEduView.hide();
-        }
-        // Hide the menu if it's visible.
-        showManageMenu(false);
-
-        mIsExpanded = false;
-        mIsExpansionAnimating = true;
-
-        showScrim(false);
-
-        mBubbleContainer.cancelAllAnimations();
-
-        // If we were in the middle of swapping, the animating-out surface would have been scaling
-        // to zero - finish it off.
-        PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
-        mAnimatingOutSurfaceContainer.setScaleX(0f);
-        mAnimatingOutSurfaceContainer.setScaleY(0f);
-
-        // Let the expanded animation controller know that it shouldn't animate child adds/reorders
-        // since we're about to animate collapsed.
-        mExpandedAnimationController.notifyPreparingToCollapse();
-
-        mExpandedAnimationController.collapseBackToStack(
-                mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
-                /* collapseTo */,
-                () -> mBubbleContainer.setActiveController(mStackAnimationController));
-
-        int index;
-        if (mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey())) {
-            index = mBubbleData.getBubbles().size();
-        } else {
-            index = mBubbleData.getBubbles().indexOf(mExpandedBubble);
-        }
-        // Value the bubble is animating from (back into the stack).
-        final PointF p = mPositioner.getExpandedBubbleXY(index, getState());
-        if (mPositioner.showBubblesVertically()) {
-            float pivotX;
-            float pivotY = p.y + mBubbleSize / 2f;
-            if (mStackOnLeftOrWillBe) {
-                pivotX = mPositioner.getAvailableRect().left + mBubbleSize + mExpandedViewPadding;
-            } else {
-                pivotX = mPositioner.getAvailableRect().right - mBubbleSize - mExpandedViewPadding;
-            }
-            mExpandedViewContainerMatrix.setScale(
-                    1f, 1f,
-                    pivotX, pivotY);
-        } else {
-            mExpandedViewContainerMatrix.setScale(
-                    1f, 1f,
-                    p.x + mBubbleSize / 2f,
-                    p.y + mBubbleSize + mExpandedViewPadding);
-        }
-
-        mExpandedViewAlphaAnimator.reverse();
-
-        // When the animation completes, we should no longer be showing the content.
-        if (mExpandedBubble.getExpandedView() != null) {
-            mExpandedBubble.getExpandedView().setContentVisibility(false);
-        }
-
-        PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
-        PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
-                .spring(AnimatableScaleMatrix.SCALE_X,
-                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
-                                1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
-                        mScaleOutSpringConfig)
-                .spring(AnimatableScaleMatrix.SCALE_Y,
-                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
-                                1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
-                        mScaleOutSpringConfig)
-                .addUpdateListener((target, values) -> {
-                    mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
-                })
-                .withEndActions(() -> {
-                    final BubbleViewProvider previouslySelected = mExpandedBubble;
-                    beforeExpandedViewAnimation();
-                    if (mManageEduView != null) {
-                        mManageEduView.hide();
-                    }
-
-                    if (DEBUG_BUBBLE_STACK_VIEW) {
-                        Log.d(TAG, "animateCollapse");
-                        Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
-                                mExpandedBubble));
-                    }
-                    updateOverflowVisibility();
-                    updateZOrder();
-                    updateBadges(true /* setBadgeForCollapsedStack */);
-                    afterExpandedViewAnimation();
-                    if (previouslySelected != null) {
-                        previouslySelected.setTaskViewVisibility(false);
-                    }
-                })
-                .start();
-    }
-
     private void animateCollapse() {
         cancelDelayedExpandCollapseSwitchAnimations();
 
@@ -2579,65 +2464,6 @@
      * and clip the expanded view.
      */
     public void setImeVisible(boolean visible) {
-        if (HOME_GESTURE_ENABLED) {
-            setImeVisibleInternal(visible);
-        } else {
-            setImeVisibleWithoutClipping(visible);
-        }
-    }
-
-    private void setImeVisibleWithoutClipping(boolean visible) {
-        if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
-            // This will update the animation so the bubbles move to position for the IME
-            mExpandedAnimationController.expandFromStack(() -> {
-                updatePointerPosition(false /* forIme */);
-                afterExpandedViewAnimation();
-            } /* after */);
-            return;
-        }
-
-        if (!mIsExpanded && getBubbleCount() > 0) {
-            final float stackDestinationY =
-                    mStackAnimationController.animateForImeVisibility(visible);
-
-            // How far the stack is animating due to IME, we'll just animate the flyout by that
-            // much too.
-            final float stackDy =
-                    stackDestinationY - mStackAnimationController.getStackPosition().y;
-
-            // If the flyout is visible, translate it along with the bubble stack.
-            if (mFlyout.getVisibility() == VISIBLE) {
-                PhysicsAnimator.getInstance(mFlyout)
-                        .spring(DynamicAnimation.TRANSLATION_Y,
-                                mFlyout.getTranslationY() + stackDy,
-                                FLYOUT_IME_ANIMATION_SPRING_CONFIG)
-                        .start();
-            }
-        } else if (mPositioner.showBubblesVertically() && mIsExpanded
-                && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-            float selectedY = mPositioner.getExpandedBubbleXY(getState().selectedIndex,
-                    getState()).y;
-            float newExpandedViewTop = mPositioner.getExpandedViewY(mExpandedBubble, selectedY);
-            mExpandedBubble.getExpandedView().setImeVisible(visible);
-            if (!mExpandedBubble.getExpandedView().isUsingMaxHeight()) {
-                mExpandedViewContainer.animate().translationY(newExpandedViewTop);
-            }
-
-            List<Animator> animList = new ArrayList();
-            for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
-                View child = mBubbleContainer.getChildAt(i);
-                float transY = mPositioner.getExpandedBubbleXY(i, getState()).y;
-                ObjectAnimator anim = ObjectAnimator.ofFloat(child, TRANSLATION_Y, transY);
-                animList.add(anim);
-            }
-            updatePointerPosition(true /* forIme */);
-            AnimatorSet set = new AnimatorSet();
-            set.playTogether(animList);
-            set.start();
-        }
-    }
-
-    private void setImeVisibleInternal(boolean visible) {
         if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
             // This will update the animation so the bubbles move to position for the IME
             mExpandedAnimationController.expandFromStack(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 7f891ec..465d1ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -22,6 +22,7 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.app.NotificationChannel;
+import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
@@ -108,6 +109,15 @@
     void expandStackAndSelectBubble(Bubble bubble);
 
     /**
+     * Adds and expands bubble that is not notification based, but instead based on an intent from
+     * the app. The intent must be explicit (i.e. include a package name or fully qualified
+     * component class name) and the activity for it should be resizable.
+     *
+     * @param intent the intent to populate the bubble.
+     */
+    void showAppBubble(Intent intent);
+
+    /**
      * @return a bubble that matches the provided shortcutId, if one exists.
      */
     @Nullable
@@ -232,6 +242,11 @@
      */
     void onUserRemoved(int removedUserId);
 
+    /**
+     * Sets whether bubble bar should be enabled or not.
+     */
+    void setBubbleBarEnabled(boolean enabled);
+
     /** Listener to find out about stack expansion / collapse events. */
     interface BubbleExpandListener {
         /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index b91062f..33629f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -20,7 +20,6 @@
 
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
 import static com.android.wm.shell.bubbles.BubbleStackView.ENABLE_FLING_TO_DISMISS_BUBBLE;
-import static com.android.wm.shell.bubbles.BubbleStackView.HOME_GESTURE_ENABLED;
 
 import android.content.res.Resources;
 import android.graphics.Path;
@@ -81,11 +80,6 @@
             new PhysicsAnimator.SpringConfig(
                     EXPAND_COLLAPSE_ANIM_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
 
-    private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfigWithoutHomeGesture =
-            new PhysicsAnimator.SpringConfig(
-                    EXPAND_COLLAPSE_ANIM_STIFFNESS_WITHOUT_HOME_GESTURE,
-                    SpringForce.DAMPING_RATIO_NO_BOUNCY);
-
     /** Horizontal offset between bubbles, which we need to know to re-stack them. */
     private float mStackOffsetPx;
     /** Size of each bubble. */
@@ -307,14 +301,8 @@
                     (firstBubbleLeads && index == 0)
                             || (!firstBubbleLeads && index == mLayout.getChildCount() - 1);
 
-            Interpolator interpolator;
-            if (HOME_GESTURE_ENABLED) {
-                // When home gesture is enabled, we use a different animation timing for collapse
-                interpolator = expanding
-                        ? Interpolators.EMPHASIZED_ACCELERATE : Interpolators.EMPHASIZED_DECELERATE;
-            } else {
-                interpolator = Interpolators.LINEAR;
-            }
+            Interpolator interpolator = expanding
+                    ? Interpolators.EMPHASIZED_ACCELERATE : Interpolators.EMPHASIZED_DECELERATE;
 
             animation
                     .followAnimatedTargetAlongPath(
@@ -564,16 +552,10 @@
             finishRemoval.run();
             mOnBubbleAnimatedOutAction.run();
         } else {
-            PhysicsAnimator.SpringConfig springConfig;
-            if (HOME_GESTURE_ENABLED) {
-                springConfig = mAnimateOutSpringConfig;
-            } else {
-                springConfig = mAnimateOutSpringConfigWithoutHomeGesture;
-            }
             PhysicsAnimator.getInstance(child)
                     .spring(DynamicAnimation.ALPHA, 0f)
-                    .spring(DynamicAnimation.SCALE_X, 0f, springConfig)
-                    .spring(DynamicAnimation.SCALE_Y, 0f, springConfig)
+                    .spring(DynamicAnimation.SCALE_X, 0f, mAnimateOutSpringConfig)
+                    .spring(DynamicAnimation.SCALE_Y, 0f, mAnimateOutSpringConfig)
                     .withEndActions(finishRemoval, mOnBubbleAnimatedOutAction)
                     .start();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java
deleted file mode 100644
index bb8a3aa..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java
+++ /dev/null
@@ -1,57 +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.wm.shell.bubbles.animation;
-
-import com.android.wm.shell.bubbles.BubbleExpandedView;
-
-/**
- * Stub implementation {@link ExpandedViewAnimationController} that does not animate the
- * {@link BubbleExpandedView}
- */
-public class ExpandedViewAnimationControllerStub implements ExpandedViewAnimationController {
-    @Override
-    public void setExpandedView(BubbleExpandedView expandedView) {
-    }
-
-    @Override
-    public void updateDrag(float distance) {
-    }
-
-    @Override
-    public void setSwipeVelocity(float velocity) {
-    }
-
-    @Override
-    public boolean shouldCollapse() {
-        return false;
-    }
-
-    @Override
-    public void animateCollapse(Runnable startStackCollapse, Runnable after) {
-    }
-
-    @Override
-    public void animateBackToExpanded() {
-    }
-
-    @Override
-    public void animateForImeVisibilityChange(boolean visible) {
-    }
-
-    @Override
-    public void reset() {
-    }
-}
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 bb7c4134..d9b4f47 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
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.res.Configuration;
 import android.graphics.Point;
@@ -38,6 +39,7 @@
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodManagerGlobal;
 
 import androidx.annotation.VisibleForTesting;
@@ -112,7 +114,7 @@
         }
         if (mDisplayController.getDisplayLayout(displayId).rotation()
                 != pd.mRotation && isImeShowing(displayId)) {
-            pd.startAnimation(true, false /* forceRestart */);
+            pd.startAnimation(true, false /* forceRestart */, null /* statsToken */);
         }
     }
 
@@ -244,7 +246,7 @@
             mInsetsState.set(insetsState, true /* copySources */);
             if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) {
                 if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
-                startAnimation(mImeShowing, true /* forceRestart */);
+                startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
             }
         }
 
@@ -280,7 +282,7 @@
                         !haveSameLeash(mImeSourceControl, imeSourceControl);
                 if (mAnimation != null) {
                     if (positionChanged) {
-                        startAnimation(mImeShowing, true /* forceRestart */);
+                        startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
                     }
                 } else {
                     if (leashChanged) {
@@ -312,21 +314,23 @@
         }
 
         @Override
-        public void showInsets(int types, boolean fromIme) {
+        public void showInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             if ((types & WindowInsets.Type.ime()) == 0) {
                 return;
             }
             if (DEBUG) Slog.d(TAG, "Got showInsets for ime");
-            startAnimation(true /* show */, false /* forceRestart */);
+            startAnimation(true /* show */, false /* forceRestart */, statsToken);
         }
 
         @Override
-        public void hideInsets(int types, boolean fromIme) {
+        public void hideInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             if ((types & WindowInsets.Type.ime()) == 0) {
                 return;
             }
             if (DEBUG) Slog.d(TAG, "Got hideInsets for ime");
-            startAnimation(false /* show */, false /* forceRestart */);
+            startAnimation(false /* show */, false /* forceRestart */, statsToken);
         }
 
         @Override
@@ -367,9 +371,11 @@
                     .navBarFrameHeight();
         }
 
-        private void startAnimation(final boolean show, final boolean forceRestart) {
+        private void startAnimation(final boolean show, final boolean forceRestart,
+                @Nullable ImeTracker.Token statsToken) {
             final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
             if (imeSource == null || mImeSourceControl == null) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
                 return;
             }
             final Rect newFrame = imeSource.getFrame();
@@ -390,8 +396,9 @@
                         + (mAnimationDirection == DIRECTION_SHOW ? "SHOW"
                         : (mAnimationDirection == DIRECTION_HIDE ? "HIDE" : "NONE")));
             }
-            if (!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show)
+            if ((!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show))
                     || (mAnimationDirection == DIRECTION_HIDE && !show)) {
+                ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
                 return;
             }
             boolean seek = false;
@@ -435,8 +442,11 @@
                 mTransactionPool.release(t);
             });
             mAnimation.setInterpolator(INTERPOLATOR);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
             mAnimation.addListener(new AnimatorListenerAdapter() {
                 private boolean mCancelled = false;
+                @Nullable
+                private final ImeTracker.Token mStatsToken = statsToken;
 
                 @Override
                 public void onAnimationStart(Animator animation) {
@@ -455,6 +465,8 @@
                             : 1.f;
                     t.setAlpha(mImeSourceControl.getLeash(), alpha);
                     if (mAnimationDirection == DIRECTION_SHOW) {
+                        ImeTracker.get().onProgress(mStatsToken,
+                                ImeTracker.PHASE_WM_ANIMATION_RUNNING);
                         t.show(mImeSourceControl.getLeash());
                     }
                     t.apply();
@@ -476,8 +488,16 @@
                     }
                     dispatchEndPositioning(mDisplayId, mCancelled, t);
                     if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
+                        ImeTracker.get().onProgress(mStatsToken,
+                                ImeTracker.PHASE_WM_ANIMATION_RUNNING);
                         t.hide(mImeSourceControl.getLeash());
                         removeImeSurface();
+                        ImeTracker.get().onHidden(mStatsToken);
+                    } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) {
+                        ImeTracker.get().onShown(mStatsToken);
+                    } else if (mCancelled) {
+                        ImeTracker.get().onCancelled(mStatsToken,
+                                ImeTracker.PHASE_WM_ANIMATION_RUNNING);
                     }
                     t.apply();
                     mTransactionPool.release(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 8d4a09d..8759301 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.common;
 
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -25,6 +26,7 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
 
 import androidx.annotation.BinderThread;
 
@@ -156,23 +158,29 @@
             }
         }
 
-        private void showInsets(int types, boolean fromIme) {
+        private void showInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
             if (listeners == null) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
                 return;
             }
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
             for (OnInsetsChangedListener listener : listeners) {
-                listener.showInsets(types, fromIme);
+                listener.showInsets(types, fromIme, statsToken);
             }
         }
 
-        private void hideInsets(int types, boolean fromIme) {
+        private void hideInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
             if (listeners == null) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
                 return;
             }
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
             for (OnInsetsChangedListener listener : listeners) {
-                listener.hideInsets(types, fromIme);
+                listener.hideInsets(types, fromIme, statsToken);
             }
         }
 
@@ -214,16 +222,18 @@
             }
 
             @Override
-            public void showInsets(int types, boolean fromIme) throws RemoteException {
+            public void showInsets(@InsetsType int types, boolean fromIme,
+                    @Nullable ImeTracker.Token statsToken) throws RemoteException {
                 mMainExecutor.execute(() -> {
-                    PerDisplay.this.showInsets(types, fromIme);
+                    PerDisplay.this.showInsets(types, fromIme, statsToken);
                 });
             }
 
             @Override
-            public void hideInsets(int types, boolean fromIme) throws RemoteException {
+            public void hideInsets(@InsetsType int types, boolean fromIme,
+                    @Nullable ImeTracker.Token statsToken) throws RemoteException {
                 mMainExecutor.execute(() -> {
-                    PerDisplay.this.hideInsets(types, fromIme);
+                    PerDisplay.this.hideInsets(types, fromIme, statsToken);
                 });
             }
         }
@@ -263,15 +273,21 @@
          *
          * @param types {@link InsetsType} to show
          * @param fromIme true if this request originated from IME (InputMethodService).
+         * @param statsToken the token tracking the current IME show request
+         *                   or {@code null} otherwise.
          */
-        default void showInsets(@InsetsType int types, boolean fromIme) {}
+        default void showInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {}
 
         /**
          * Called when a set of insets source window should be hidden by policy.
          *
          * @param types {@link InsetsType} to hide
          * @param fromIme true if this request originated from IME (InputMethodService).
+         * @param statsToken the token tracking the current IME hide request
+         *                   or {@code null} otherwise.
          */
-        default void hideInsets(@InsetsType int types, boolean fromIme) {}
+        default void hideInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {}
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index e270edb..af13bf5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -19,6 +19,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Region;
@@ -46,6 +47,7 @@
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.WindowlessWindowManager;
+import android.view.inputmethod.ImeTracker;
 import android.window.ClientWindowFrames;
 
 import com.android.internal.os.IResultReceiver;
@@ -351,10 +353,10 @@
                 InsetsSourceControl[] activeControls) {}
 
         @Override
-        public void showInsets(int types, boolean fromIme) {}
+        public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {}
 
         @Override
-        public void hideInsets(int types, boolean fromIme) {}
+        public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {}
 
         @Override
         public void moved(int newX, int newY) {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 8bc16bc..214b304 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -46,8 +46,10 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 /**
  * Divider for multi window splits.
@@ -74,6 +76,7 @@
     private GestureDetector mDoubleTapDetector;
     private boolean mInteractive;
     private boolean mSetTouchRegion = true;
+    private int mLastDraggingPosition;
 
     /**
      * Tracks divider bar visible bounds in screen-based coordination. Used to calculate with
@@ -216,7 +219,11 @@
                 insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
         // Only insets the divider bar with task bar when it's expanded so that the rounded corners
         // will be drawn against task bar.
-        if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+        // But there is no need to do it when IME showing because there are no rounded corners at
+        // the bottom. This also avoids the problem of task bar height not changing when IME
+        // floating.
+        if (!insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME)
+                && taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
             mTempRect.inset(taskBarInsetsSource.calculateVisibleInsets(mTempRect));
         }
 
@@ -296,6 +303,7 @@
                 }
                 if (mMoving) {
                     final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
+                    mLastDraggingPosition = position;
                     mSplitLayout.updateDivideBounds(position);
                 }
                 break;
@@ -364,9 +372,21 @@
         mViewHost.relayout(lp);
     }
 
-    void setInteractive(boolean interactive) {
+    void setInteractive(boolean interactive, String from) {
         if (interactive == mInteractive) return;
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
+                from);
         mInteractive = interactive;
+        if (!mInteractive && mMoving) {
+            final int position = mSplitLayout.getDividePosition();
+            mSplitLayout.flingDividePosition(
+                    mLastDraggingPosition,
+                    position,
+                    mSplitLayout.FLING_RESIZE_DURATION,
+                    () -> mSplitLayout.setDividePosition(position, true /* applyLayoutChange */));
+            mMoving = false;
+        }
         releaseTouching();
         mHandle.setVisibility(mInteractive ? View.VISIBLE : View.INVISIBLE);
     }
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/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 74f8bf9..c836b95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -48,10 +48,9 @@
 
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
+import com.android.wm.shell.common.ScreenshotUtils;
 import com.android.wm.shell.common.SurfaceUtils;
 
-import java.util.function.Consumer;
-
 /**
  * Handles split decor like showing resizing hint for a specific split.
  */
@@ -71,13 +70,18 @@
     private SurfaceControl mIconLeash;
     private SurfaceControl mBackgroundLeash;
     private SurfaceControl mGapBackgroundLeash;
+    private SurfaceControl mScreenshot;
 
     private boolean mShown;
     private boolean mIsResizing;
-    private Rect mBounds = new Rect();
+    private final Rect mBounds = new Rect();
+    private final Rect mTempRect = new Rect();
     private ValueAnimator mFadeAnimator;
 
     private int mIconSize;
+    private int mOffsetX;
+    private int mOffsetY;
+    private int mRunningAnimationCount = 0;
 
     public SplitDecorManager(Configuration configuration, IconProvider iconProvider,
             SurfaceSession surfaceSession) {
@@ -158,7 +162,8 @@
 
     /** Showing resizing hint. */
     public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
-            Rect sideBounds, SurfaceControl.Transaction t) {
+            Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
+            boolean immediately) {
         if (mResizingIconView == null) {
             return;
         }
@@ -167,11 +172,13 @@
             mIsResizing = true;
             mBounds.set(newBounds);
         }
+        mOffsetX = offsetX;
+        mOffsetY = offsetY;
 
         final boolean show =
                 newBounds.width() > mBounds.width() || newBounds.height() > mBounds.height();
-        final boolean animate = show != mShown;
-        if (animate && mFadeAnimator != null && mFadeAnimator.isRunning()) {
+        final boolean update = show != mShown;
+        if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) {
             // If we need to animate and animator still running, cancel it before we ensure both
             // background and icon surfaces are non null for next animation.
             mFadeAnimator.cancel();
@@ -184,7 +191,7 @@
                     .setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
         }
 
-        if (mGapBackgroundLeash == null) {
+        if (mGapBackgroundLeash == null && !immediately) {
             final boolean isLandscape = newBounds.height() == sideBounds.height();
             final int left = isLandscape ? mBounds.width() : 0;
             final int top = isLandscape ? 0 : mBounds.height();
@@ -213,19 +220,58 @@
                 newBounds.width() / 2 - mIconSize / 2,
                 newBounds.height() / 2 - mIconSize / 2);
 
-        if (animate) {
-            startFadeAnimation(show, null /* finishedConsumer */);
+        if (update) {
+            if (immediately) {
+                t.setVisibility(mBackgroundLeash, show);
+                t.setVisibility(mIconLeash, show);
+            } else {
+                startFadeAnimation(show, false, null);
+            }
             mShown = show;
         }
     }
 
     /** Stops showing resizing hint. */
-    public void onResized(SurfaceControl.Transaction t) {
+    public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
+        if (mScreenshot != null) {
+            t.setPosition(mScreenshot, mOffsetX, mOffsetY);
+
+            final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
+            final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
+            va.addUpdateListener(valueAnimator -> {
+                final float progress = (float) valueAnimator.getAnimatedValue();
+                animT.setAlpha(mScreenshot, progress);
+                animT.apply();
+            });
+            va.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mRunningAnimationCount++;
+                }
+
+                @Override
+                public void onAnimationEnd(@androidx.annotation.NonNull Animator animation) {
+                    mRunningAnimationCount--;
+                    animT.remove(mScreenshot);
+                    animT.apply();
+                    animT.close();
+                    mScreenshot = null;
+
+                    if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+                        animFinishedCallback.run();
+                    }
+                }
+            });
+            va.start();
+        }
+
         if (mResizingIconView == null) {
             return;
         }
 
         mIsResizing = false;
+        mOffsetX = 0;
+        mOffsetY = 0;
         if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
             if (!mShown) {
                 // If fade-out animation is running, just add release callback to it.
@@ -245,10 +291,34 @@
             mFadeAnimator.cancel();
         }
         if (mShown) {
-            fadeOutDecor(null /* finishedCallback */);
+            fadeOutDecor(animFinishedCallback);
         } else {
             // Decor surface is hidden so release it directly.
             releaseDecor(t);
+            if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+                animFinishedCallback.run();
+            }
+        }
+    }
+
+    /** Screenshot host leash and attach on it if meet some conditions */
+    public void screenshotIfNeeded(SurfaceControl.Transaction t) {
+        if (!mShown && mIsResizing) {
+            mTempRect.set(mBounds);
+            mTempRect.offsetTo(0, 0);
+            mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
+                    Integer.MAX_VALUE - 1);
+        }
+    }
+
+    /** Set screenshot and attach on host leash it if meet some conditions */
+    public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) {
+        if (screenshot == null || !screenshot.isValid()) return;
+
+        if (!mShown && mIsResizing) {
+            mScreenshot = screenshot;
+            t.reparent(screenshot, mHostLeash);
+            t.setLayer(screenshot, Integer.MAX_VALUE - 1);
         }
     }
 
@@ -256,18 +326,15 @@
      * directly. */
     public void fadeOutDecor(Runnable finishedCallback) {
         if (mShown) {
-            startFadeAnimation(false /* show */, transaction -> {
-                releaseDecor(transaction);
-                if (finishedCallback != null) finishedCallback.run();
-            });
+            startFadeAnimation(false /* show */, true, finishedCallback);
             mShown = false;
         } else {
             if (finishedCallback != null) finishedCallback.run();
         }
     }
 
-    private void startFadeAnimation(boolean show,
-            Consumer<SurfaceControl.Transaction> finishedConsumer) {
+    private void startFadeAnimation(boolean show, boolean releaseSurface,
+            Runnable finishedCallback) {
         final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
         mFadeAnimator = ValueAnimator.ofFloat(0f, 1f);
         mFadeAnimator.setDuration(FADE_DURATION);
@@ -284,15 +351,19 @@
         mFadeAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(@NonNull Animator animation) {
+                mRunningAnimationCount++;
                 if (show) {
-                    animT.show(mBackgroundLeash).show(mIconLeash).show(mGapBackgroundLeash).apply();
-                } else {
-                    animT.hide(mGapBackgroundLeash).apply();
+                    animT.show(mBackgroundLeash).show(mIconLeash);
                 }
+                if (mGapBackgroundLeash != null) {
+                    animT.setVisibility(mGapBackgroundLeash, show);
+                }
+                animT.apply();
             }
 
             @Override
             public void onAnimationEnd(@NonNull Animator animation) {
+                mRunningAnimationCount--;
                 if (!show) {
                     if (mBackgroundLeash != null) {
                         animT.hide(mBackgroundLeash);
@@ -301,11 +372,15 @@
                         animT.hide(mIconLeash);
                     }
                 }
-                if (finishedConsumer != null) {
-                    finishedConsumer.accept(animT);
+                if (releaseSurface) {
+                    releaseDecor(animT);
                 }
                 animT.apply();
                 animT.close();
+
+                if (mRunningAnimationCount == 0 && finishedCallback != null) {
+                    finishedCallback.run();
+                }
             }
         });
         mFadeAnimator.start();
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..45b234a 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
@@ -80,14 +81,14 @@
     public static final int PARALLAX_DISMISSING = 1;
     public static final int PARALLAX_ALIGN_CENTER = 2;
 
-    private static final int FLING_RESIZE_DURATION = 250;
+    public static final int FLING_RESIZE_DURATION = 250;
     private static final int FLING_SWITCH_DURATION = 350;
-    private static final int FLING_ENTER_DURATION = 350;
-    private static final int FLING_EXIT_DURATION = 350;
+    private static final int FLING_ENTER_DURATION = 450;
+    private static final int FLING_EXIT_DURATION = 450;
 
-    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();
@@ -121,6 +122,7 @@
     private int mDensity;
 
     private final boolean mDimNonImeSide;
+    private ValueAnimator mDividerFlingAnimator;
 
     public SplitLayout(String windowName, Context context, Configuration configuration,
             SplitLayoutHandler splitLayoutHandler,
@@ -130,6 +132,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 +141,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 +167,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 +311,7 @@
         mRotation = rotation;
         mDensity = density;
         mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
+        updateDividerConfig(mContext);
         initDividerPosition(mTempRect);
         updateInvisibleRect();
 
@@ -392,6 +396,10 @@
         mSplitWindowManager.release(t);
         mDisplayImeController.removePositionProcessor(mImePositionProcessor);
         mImePositionProcessor.reset();
+        if (mDividerFlingAnimator != null) {
+            mDividerFlingAnimator.cancel();
+        }
+        resetDividerPosition();
     }
 
     public void release() {
@@ -445,7 +453,8 @@
      */
     void updateDivideBounds(int position) {
         updateBounds(position);
-        mSplitLayoutHandler.onLayoutSizeChanging(this);
+        mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x,
+                mSurfaceEffectPolicy.mParallaxOffset.y);
     }
 
     void setDividePosition(int position, boolean applyLayoutChange) {
@@ -573,13 +582,18 @@
                     CUJ_SPLIT_SCREEN_RESIZE);
             return;
         }
-        ValueAnimator animator = ValueAnimator
+
+        if (mDividerFlingAnimator != null) {
+            mDividerFlingAnimator.cancel();
+        }
+
+        mDividerFlingAnimator = ValueAnimator
                 .ofInt(from, to)
                 .setDuration(duration);
-        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        animator.addUpdateListener(
+        mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        mDividerFlingAnimator.addUpdateListener(
                 animation -> updateDivideBounds((int) animation.getAnimatedValue()));
-        animator.addListener(new AnimatorListenerAdapter() {
+        mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 if (flingFinishedCallback != null) {
@@ -587,19 +601,20 @@
                 }
                 InteractionJankMonitorUtils.endTracing(
                         CUJ_SPLIT_SCREEN_RESIZE);
+                mDividerFlingAnimator = null;
             }
 
             @Override
             public void onAnimationCancel(Animator animation) {
-                setDividePosition(to, true /* applyLayoutChange */);
+                mDividerFlingAnimator = null;
             }
         });
-        animator.start();
+        mDividerFlingAnimator.start();
     }
 
-    /** Swich both surface position with animation. */
+    /** Switch 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 +632,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 +648,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 +671,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();
@@ -813,7 +823,7 @@
          * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
          * SurfaceControl, SurfaceControl, boolean)
          */
-        void onLayoutSizeChanging(SplitLayout layout);
+        void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY);
 
         /**
          * Calls when finish resizing the split bounds.
@@ -1094,7 +1104,8 @@
             // ImePositionProcessor#onImeVisibilityChanged directly in DividerView is not enough
             // because DividerView won't receive onImeVisibilityChanged callback after it being
             // re-inflated.
-            mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus);
+            mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus,
+                    "onImeStartPositioning");
 
             return needOffset ? IME_ANIMATION_NO_ALPHA : 0;
         }
@@ -1120,7 +1131,7 @@
             // Restore the split layout when wm-shell is not controlling IME insets anymore.
             if (!controlling && mImeShown) {
                 reset();
-                mSplitWindowManager.setInteractive(true);
+                mSplitWindowManager.setInteractive(true, "onImeControlTargetChanged");
                 mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
                 mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 7fea237..5397552 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -167,9 +167,9 @@
         }
     }
 
-    void setInteractive(boolean interactive) {
+    void setInteractive(boolean interactive, String from) {
         if (mDividerView == null) return;
-        mDividerView.setInteractive(interactive);
+        mDividerView.setInteractive(interactive, from);
     }
 
     View getDividerView() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 8b8e192..dc1634a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -24,7 +24,6 @@
 import android.os.Handler;
 import android.os.SystemProperties;
 import android.view.IWindowManager;
-import android.view.WindowManager;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.launcher3.icons.IconProvider;
@@ -39,6 +38,7 @@
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
 import com.android.wm.shell.back.BackAnimation;
+import com.android.wm.shell.back.BackAnimationBackground;
 import com.android.wm.shell.back.BackAnimationController;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -65,8 +65,6 @@
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.floating.FloatingTasks;
-import com.android.wm.shell.floating.FloatingTasksController;
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.fullscreen.FullscreenTaskListener;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
@@ -96,13 +94,13 @@
 import com.android.wm.shell.unfold.UnfoldTransitionHandler;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
-import java.util.Optional;
-
 import dagger.BindsOptionalOf;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+
 /**
  * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
  * accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -258,22 +256,30 @@
 
     @WMSingleton
     @Provides
+    static BackAnimationBackground provideBackAnimationBackground(
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+        return new BackAnimationBackground(rootTaskDisplayAreaOrganizer);
+    }
+
+    @WMSingleton
+    @Provides
     static Optional<BackAnimationController> provideBackAnimationController(
             Context context,
             ShellInit shellInit,
             ShellController shellController,
             @ShellMainThread ShellExecutor shellExecutor,
             @ShellBackgroundThread Handler backgroundHandler,
-            Transitions transitions
+            BackAnimationBackground backAnimationBackground
     ) {
         if (BackAnimationController.IS_ENABLED) {
             return Optional.of(
                     new BackAnimationController(shellInit, shellController, shellExecutor,
-                            backgroundHandler, context, transitions));
+                            backgroundHandler, context, backAnimationBackground));
         }
         return Optional.empty();
     }
 
+
     //
     // Bubbles (optional feature)
     //
@@ -576,47 +582,6 @@
     }
 
     //
-    // Floating tasks
-    //
-
-    @WMSingleton
-    @Provides
-    static Optional<FloatingTasks> provideFloatingTasks(
-            Optional<FloatingTasksController> floatingTaskController) {
-        return floatingTaskController.map((controller) -> controller.asFloatingTasks());
-    }
-
-    @WMSingleton
-    @Provides
-    static Optional<FloatingTasksController> provideFloatingTasksController(Context context,
-            ShellInit shellInit,
-            ShellController shellController,
-            ShellCommandHandler shellCommandHandler,
-            Optional<BubbleController> bubbleController,
-            WindowManager windowManager,
-            ShellTaskOrganizer organizer,
-            TaskViewTransitions taskViewTransitions,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellBackgroundThread ShellExecutor bgExecutor,
-            SyncTransactionQueue syncQueue) {
-        if (FloatingTasksController.FLOATING_TASKS_ENABLED) {
-            return Optional.of(new FloatingTasksController(context,
-                    shellInit,
-                    shellController,
-                    shellCommandHandler,
-                    bubbleController,
-                    windowManager,
-                    organizer,
-                    taskViewTransitions,
-                    mainExecutor,
-                    bgExecutor,
-                    syncQueue));
-        } else {
-            return Optional.empty();
-        }
-    }
-
-    //
     // Starting window
     //
 
@@ -732,10 +697,13 @@
 
     @WMSingleton
     @Provides
-    static Optional<DesktopModeController> providesDesktopModeController(
-            @DynamicOverride Optional<DesktopModeController> desktopModeController) {
+    static Optional<DesktopModeController> provideDesktopModeController(
+            @DynamicOverride Optional<Lazy<DesktopModeController>> desktopModeController) {
+        // Use optional-of-lazy for the dependency that this provider relies on.
+        // Lazy ensures that this provider will not be the cause the dependency is created
+        // when it will not be returned due to the condition below.
         if (DesktopModeStatus.IS_SUPPORTED) {
-            return desktopModeController;
+            return desktopModeController.map(Lazy::get);
         }
         return Optional.empty();
     }
@@ -746,10 +714,13 @@
 
     @WMSingleton
     @Provides
-    static Optional<DesktopModeTaskRepository> providesDesktopTaskRepository(
-            @DynamicOverride Optional<DesktopModeTaskRepository> desktopModeTaskRepository) {
+    static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository(
+            @DynamicOverride Optional<Lazy<DesktopModeTaskRepository>> desktopModeTaskRepository) {
+        // Use optional-of-lazy for the dependency that this provider relies on.
+        // Lazy ensures that this provider will not be the cause the dependency is created
+        // when it will not be returned due to the condition below.
         if (DesktopModeStatus.IS_SUPPORTED) {
-            return desktopModeTaskRepository;
+            return desktopModeTaskRepository.map(Lazy::get);
         }
         return Optional.empty();
     }
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..6be8305 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
@@ -189,7 +189,7 @@
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             SyncTransactionQueue syncQueue,
-            @DynamicOverride DesktopModeController desktopModeController) {
+            Optional<DesktopModeController> desktopModeController) {
         return new CaptionWindowDecorViewModel(
                     context,
                     mainHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 34ff6d8..5824f51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -16,8 +16,11 @@
 
 package com.android.wm.shell.desktopmode;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -61,6 +64,7 @@
 
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -96,7 +100,9 @@
         mDesktopModeTaskRepository = desktopModeTaskRepository;
         mMainExecutor = mainExecutor;
         mSettingsObserver = new SettingsObserver(mContext, mainHandler);
-        shellInit.addInitCallback(this::onInit, this);
+        if (DesktopModeStatus.isSupported()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
     }
 
     private void onInit() {
@@ -151,21 +157,18 @@
 
         int displayId = mContext.getDisplayId();
 
+        ArrayList<RunningTaskInfo> runningTasks = mShellTaskOrganizer.getRunningTasks(displayId);
+
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        // Reset freeform windowing mode that is set per task level (tasks should inherit
-        // container value)
-        wct.merge(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(displayId),
-                true /* transfer */);
-        int targetWindowingMode;
+        // Reset freeform windowing mode that is set per task level so tasks inherit it
+        clearFreeformForStandardTasks(runningTasks, wct);
         if (active) {
-            targetWindowingMode = WINDOWING_MODE_FREEFORM;
+            moveHomeBehindVisibleTasks(runningTasks, wct);
+            setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FREEFORM, wct);
         } else {
-            targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
-            // Clear any resized bounds
-            wct.merge(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(displayId),
-                    true /* transfer */);
+            clearBoundsForStandardTasks(runningTasks, wct);
+            setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FULLSCREEN, wct);
         }
-        prepareWindowingModeChange(wct, displayId, targetWindowingMode);
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
         } else {
@@ -173,17 +176,69 @@
         }
     }
 
-    private void prepareWindowingModeChange(WindowContainerTransaction wct,
-            int displayId, @WindowConfiguration.WindowingMode int windowingMode) {
-        DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer
-                .getDisplayAreaInfo(displayId);
+    private WindowContainerTransaction clearBoundsForStandardTasks(
+            ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct) {
+        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks");
+        for (RunningTaskInfo taskInfo : runningTasks) {
+            if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
+                ProtoLog.v(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
+                        taskInfo.token, taskInfo);
+                wct.setBounds(taskInfo.token, null);
+            }
+        }
+        return wct;
+    }
+
+    private void clearFreeformForStandardTasks(ArrayList<RunningTaskInfo> runningTasks,
+            WindowContainerTransaction wct) {
+        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks");
+        for (RunningTaskInfo taskInfo : runningTasks) {
+            if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+                    && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
+                ProtoLog.v(WM_SHELL_DESKTOP_MODE,
+                        "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
+                        taskInfo);
+                wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+            }
+        }
+    }
+
+    private void moveHomeBehindVisibleTasks(ArrayList<RunningTaskInfo> runningTasks,
+            WindowContainerTransaction wct) {
+        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks");
+        RunningTaskInfo homeTask = null;
+        ArrayList<RunningTaskInfo> visibleTasks = new ArrayList<>();
+        for (RunningTaskInfo taskInfo : runningTasks) {
+            if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+                homeTask = taskInfo;
+            } else if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
+                    && taskInfo.isVisible()) {
+                visibleTasks.add(taskInfo);
+            }
+        }
+        if (homeTask == null) {
+            ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: home task not found");
+        } else {
+            ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: visible tasks %d",
+                    visibleTasks.size());
+            wct.reorder(homeTask.getToken(), true /* onTop */);
+            for (RunningTaskInfo task : visibleTasks) {
+                wct.reorder(task.getToken(), true /* onTop */);
+            }
+        }
+    }
+
+    private void setDisplayAreaWindowingMode(int displayId,
+            @WindowConfiguration.WindowingMode int windowingMode, WindowContainerTransaction wct) {
+        DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+                displayId);
         if (displayAreaInfo == null) {
             ProtoLog.e(WM_SHELL_DESKTOP_MODE,
                     "unable to update windowing mode for display %d display not found", displayId);
             return;
         }
 
-        ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+        ProtoLog.v(WM_SHELL_DESKTOP_MODE,
                 "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
                 displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
                 windowingMode);
@@ -206,18 +261,36 @@
 
     @NonNull
     private WindowContainerTransaction bringDesktopAppsToFront() {
-        ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
-        ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
+
+        final List<RunningTaskInfo> taskInfos = new ArrayList<>();
         for (Integer taskId : activeTasks) {
             RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
             if (taskInfo != null) {
                 taskInfos.add(taskInfo);
             }
         }
-        // Order by lastActiveTime, descending
-        taskInfos.sort(Comparator.comparingLong(task -> -task.lastActiveTime));
-        WindowContainerTransaction wct = new WindowContainerTransaction();
+
+        if (taskInfos.isEmpty()) {
+            return wct;
+        }
+
+        final boolean allActiveTasksAreVisible = taskInfos.stream()
+                .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId));
+        if (allActiveTasksAreVisible) {
+            ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+                    "bringDesktopAppsToFront: active tasks are already in front, skipping.");
+            return wct;
+        }
+        ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+                "bringDesktopAppsToFront: reordering all active tasks to the front");
+        final List<Integer> allTasksInZOrder =
+                mDesktopModeTaskRepository.getFreeformTasksInZOrder();
+        // Sort by z-order, bottom to top, so that the top-most task is reordered to the top last
+        // in the WCT.
+        taskInfos.sort(Comparator.comparingInt(task -> -allTasksInZOrder.indexOf(task.taskId)));
         for (RunningTaskInfo task : taskInfos) {
             wct.reorder(task.token, true);
         }
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 2fafe67..e3eb2b7 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
@@ -37,6 +37,13 @@
             "persist.wm.debug.desktop_mode", false);
 
     /**
+     * Return {@code true} if desktop mode support is enabled
+     */
+    public static boolean isSupported() {
+        return IS_SUPPORTED;
+    }
+
+    /**
      * Check if desktop mode is active
      *
      * @return {@code true} if active
@@ -54,4 +61,5 @@
             return false;
         }
     }
+
 }
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 b7749fc..600ccc1 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
@@ -33,6 +33,8 @@
      */
     private val activeTasks = ArraySet<Int>()
     private val visibleTasks = ArraySet<Int>()
+    // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
+    private val freeformTasksInZOrder = mutableListOf<Int>()
     private val activeTasksListeners = ArraySet<ActiveTasksListener>()
     // Track visible tasks separately because a task may be part of the desktop but not visible.
     private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
@@ -101,6 +103,13 @@
     }
 
     /**
+     * Whether a task is visible.
+     */
+    fun isVisibleTask(taskId: Int): Boolean {
+        return visibleTasks.contains(taskId)
+    }
+
+    /**
      * Get a set of the active tasks
      */
     fun getActiveTasks(): ArraySet<Int> {
@@ -108,6 +117,13 @@
     }
 
     /**
+     * Get a list of freeform tasks, ordered from top-bottom (top at index 0).
+     */
+    fun getFreeformTasksInZOrder(): List<Int> {
+        return freeformTasksInZOrder
+    }
+
+    /**
      * Updates whether a freeform task with this id is visible or not and notifies listeners.
      */
     fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) {
@@ -127,6 +143,23 @@
     }
 
     /**
+     * Add (or move if it already exists) the task to the top of the ordered list.
+     */
+    fun addOrMoveFreeformTaskToTop(taskId: Int) {
+        if (freeformTasksInZOrder.contains(taskId)) {
+            freeformTasksInZOrder.remove(taskId)
+        }
+        freeformTasksInZOrder.add(0, taskId)
+    }
+
+    /**
+     * Remove the task from the ordered list.
+     */
+    fun removeFreeformTask(taskId: Int) {
+        freeformTasksInZOrder.remove(taskId)
+    }
+
+    /**
      * Defines interface for classes that can listen to changes for active tasks in desktop mode.
      */
     interface ActiveTasksListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
new file mode 100644
index 0000000..926cfb3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module desktop owners
+madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 497a6f6..55378a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -311,7 +311,7 @@
                     animateSplitContainers(true, null /* animCompleteCallback */);
                     animateHighlight(target);
                 }
-            } else {
+            } else if (mCurrentTarget.type != target.type) {
                 // Switching between targets
                 mDropZoneView1.animateSwitch();
                 mDropZoneView2.animateSwitch();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java
deleted file mode 100644
index 83a1734..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java
+++ /dev/null
@@ -1,259 +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.wm.shell.floating;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.view.MotionEvent;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.bubbles.DismissView;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
-import com.android.wm.shell.floating.views.FloatingTaskLayer;
-import com.android.wm.shell.floating.views.FloatingTaskView;
-
-import java.util.Objects;
-
-/**
- * Controls a floating dismiss circle that has a 'magnetic' field around it, causing views moved
- * close to the target to be stuck to it unless moved out again.
- */
-public class FloatingDismissController {
-
-    /** Velocity required to dismiss the view without dragging it into the dismiss target. */
-    private static final float FLING_TO_DISMISS_MIN_VELOCITY = 4000f;
-    /**
-     * Max velocity that the view can be moving through the target with to stick (i.e. if it's
-     * more than this velocity, it will pass through the target.
-     */
-    private static final float STICK_TO_TARGET_MAX_X_VELOCITY = 2000f;
-    /**
-     * Percentage of the target width to use to determine if an object flung towards the target
-     * should dismiss (e.g. if target is 100px and this is set ot 2f, anything flung within a
-     * 200px-wide area around the target will be considered 'near' enough get dismissed).
-     */
-    private static final float FLING_TO_TARGET_WIDTH_PERCENT = 2f;
-    /** Minimum alpha to apply to the view being dismissed when it is in the target. */
-    private static final float DISMISS_VIEW_MIN_ALPHA = 0.6f;
-    /** Amount to scale down the view being dismissed when it is in the target. */
-    private static final float DISMISS_VIEW_SCALE_DOWN_PERCENT = 0.15f;
-
-    private Context mContext;
-    private FloatingTasksController mController;
-    private FloatingTaskLayer mParent;
-
-    private DismissView mDismissView;
-    private ValueAnimator mDismissAnimator;
-    private View mViewBeingDismissed;
-    private float mDismissSizePercent;
-    private float mDismissSize;
-
-    /**
-     * The currently magnetized object, which is being dragged and will be attracted to the magnetic
-     * dismiss target.
-     */
-    private MagnetizedObject<View> mMagnetizedObject;
-    /**
-     * The MagneticTarget instance for our circular dismiss view. This is added to the
-     * MagnetizedObject instances for the view being dragged.
-     */
-    private MagnetizedObject.MagneticTarget mMagneticTarget;
-    /** Magnet listener that handles animating and dismissing the view. */
-    private MagnetizedObject.MagnetListener mFloatingViewMagnetListener;
-
-    public FloatingDismissController(Context context, FloatingTasksController controller,
-            FloatingTaskLayer parent) {
-        mContext = context;
-        mController = controller;
-        mParent = parent;
-        updateSizes();
-        createAndAddDismissView();
-
-        mDismissAnimator = ValueAnimator.ofFloat(1f, 0f);
-        mDismissAnimator.addUpdateListener(animation -> {
-            final float value = (float) animation.getAnimatedValue();
-            if (mDismissView != null) {
-                mDismissView.setPivotX((mDismissView.getRight() - mDismissView.getLeft()) / 2f);
-                mDismissView.setPivotY((mDismissView.getBottom() - mDismissView.getTop()) / 2f);
-                final float scaleValue = Math.max(value, mDismissSizePercent);
-                mDismissView.getCircle().setScaleX(scaleValue);
-                mDismissView.getCircle().setScaleY(scaleValue);
-            }
-            if (mViewBeingDismissed != null) {
-                // TODO: alpha doesn't actually apply to taskView currently.
-                mViewBeingDismissed.setAlpha(Math.max(value, DISMISS_VIEW_MIN_ALPHA));
-                mViewBeingDismissed.setScaleX(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT));
-                mViewBeingDismissed.setScaleY(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT));
-            }
-        });
-
-        mFloatingViewMagnetListener = new MagnetizedObject.MagnetListener() {
-            @Override
-            public void onStuckToTarget(
-                    @NonNull MagnetizedObject.MagneticTarget target) {
-                animateDismissing(/* dismissing= */ true);
-            }
-
-            @Override
-            public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
-                    float velX, float velY, boolean wasFlungOut) {
-                animateDismissing(/* dismissing= */ false);
-                mParent.onUnstuckFromTarget((FloatingTaskView) mViewBeingDismissed, velX, velY,
-                        wasFlungOut);
-            }
-
-            @Override
-            public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
-                doDismiss();
-            }
-        };
-    }
-
-    /** Updates all the sizes used and applies them to the {@link DismissView}. */
-    public void updateSizes() {
-        Resources res = mContext.getResources();
-        mDismissSize = res.getDimensionPixelSize(
-                R.dimen.floating_task_dismiss_circle_size);
-        final float minDismissSize = res.getDimensionPixelSize(
-                R.dimen.floating_dismiss_circle_small);
-        mDismissSizePercent = minDismissSize / mDismissSize;
-
-        if (mDismissView != null) {
-            mDismissView.updateResources();
-        }
-    }
-
-    /** Prepares the view being dragged to be magnetic. */
-    public void setUpMagneticObject(View viewBeingDragged) {
-        mViewBeingDismissed = viewBeingDragged;
-        mMagnetizedObject = getMagnetizedView(viewBeingDragged);
-        mMagnetizedObject.clearAllTargets();
-        mMagnetizedObject.addTarget(mMagneticTarget);
-        mMagnetizedObject.setMagnetListener(mFloatingViewMagnetListener);
-    }
-
-    /** Shows or hides the dismiss target. */
-    public void showDismiss(boolean show) {
-        if (show) {
-            mDismissView.show();
-        } else {
-            mDismissView.hide();
-        }
-    }
-
-    /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */
-    public boolean passEventToMagnetizedObject(MotionEvent event) {
-        return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
-    }
-
-    private void createAndAddDismissView() {
-        if (mDismissView != null) {
-            mParent.removeView(mDismissView);
-        }
-        mDismissView = new DismissView(mContext);
-        mDismissView.setTargetSizeResId(R.dimen.floating_task_dismiss_circle_size);
-        mDismissView.updateResources();
-        mParent.addView(mDismissView);
-
-        final float dismissRadius = mDismissSize;
-        // Save the MagneticTarget instance for the newly set up view - we'll add this to the
-        // MagnetizedObjects when the dismiss view gets shown.
-        mMagneticTarget = new MagnetizedObject.MagneticTarget(
-                mDismissView.getCircle(), (int) dismissRadius);
-    }
-
-    private MagnetizedObject<View> getMagnetizedView(View v) {
-        if (mMagnetizedObject != null
-                && Objects.equals(mMagnetizedObject.getUnderlyingObject(), v)) {
-            // Same view being dragged, we can reuse the magnetic object.
-            return mMagnetizedObject;
-        }
-        MagnetizedObject<View> magnetizedView = new MagnetizedObject<View>(
-                mContext,
-                v,
-                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y
-        ) {
-            @Override
-            public float getWidth(@NonNull View underlyingObject) {
-                return underlyingObject.getWidth();
-            }
-
-            @Override
-            public float getHeight(@NonNull View underlyingObject) {
-                return underlyingObject.getHeight();
-            }
-
-            @Override
-            public void getLocationOnScreen(@NonNull View underlyingObject,
-                    @NonNull int[] loc) {
-                loc[0] = (int) underlyingObject.getTranslationX();
-                loc[1] = (int) underlyingObject.getTranslationY();
-            }
-        };
-        magnetizedView.setHapticsEnabled(true);
-        magnetizedView.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
-        magnetizedView.setStickToTargetMaxXVelocity(STICK_TO_TARGET_MAX_X_VELOCITY);
-        magnetizedView.setFlingToTargetWidthPercent(FLING_TO_TARGET_WIDTH_PERCENT);
-        return magnetizedView;
-    }
-
-    /** Animates the dismiss treatment on the view being dismissed. */
-    private void animateDismissing(boolean shouldDismiss) {
-        if (mViewBeingDismissed == null) {
-            return;
-        }
-        if (shouldDismiss) {
-            mDismissAnimator.removeAllListeners();
-            mDismissAnimator.start();
-        } else {
-            mDismissAnimator.removeAllListeners();
-            mDismissAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
-                    resetDismissAnimator();
-                }
-            });
-            mDismissAnimator.reverse();
-        }
-    }
-
-    /** Actually dismisses the view. */
-    private void doDismiss() {
-        mDismissView.hide();
-        mController.removeTask();
-        resetDismissAnimator();
-        mViewBeingDismissed = null;
-    }
-
-    private void resetDismissAnimator() {
-        mDismissAnimator.removeAllListeners();
-        mDismissAnimator.cancel();
-        if (mDismissView != null) {
-            mDismissView.cancelAnimators();
-            mDismissView.getCircle().setScaleX(1f);
-            mDismissView.getCircle().setScaleY(1f);
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
deleted file mode 100644
index f86d467..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
+++ /dev/null
@@ -1,36 +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.wm.shell.floating;
-
-import android.content.Intent;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to interact with floating tasks.
- */
-@ExternalThread
-public interface FloatingTasks {
-
-    /**
-     * Shows, stashes, or un-stashes the floating task depending on state:
-     * - If there is no floating task for this intent, it shows the task for the provided intent.
-     * - If there is a floating task for this intent, but it's stashed, this un-stashes it.
-     * - If there is a floating task for this intent, and it's not stashed, this stashes it.
-     */
-    void showOrSetStashed(Intent intent);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
deleted file mode 100644
index b3c09d3..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
+++ /dev/null
@@ -1,454 +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.wm.shell.floating;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.os.SystemProperties;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-
-import androidx.annotation.BinderThread;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.bubbles.BubbleController;
-import com.android.wm.shell.common.ExternalInterfaceBinder;
-import com.android.wm.shell.common.RemoteCallable;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.annotations.ShellBackgroundThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.floating.views.FloatingTaskLayer;
-import com.android.wm.shell.floating.views.FloatingTaskView;
-import com.android.wm.shell.sysui.ConfigurationChangeListener;
-import com.android.wm.shell.sysui.ShellCommandHandler;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-import java.util.Optional;
-
-/**
- * Entry point for creating and managing floating tasks.
- *
- * A single window layer is added and the task(s) are displayed using a {@link FloatingTaskView}
- * within that window.
- *
- * Currently optimized for a single task. Multiple tasks are not supported.
- */
-public class FloatingTasksController implements RemoteCallable<FloatingTasksController>,
-        ConfigurationChangeListener {
-
-    private static final String TAG = FloatingTasksController.class.getSimpleName();
-
-    public static final boolean FLOATING_TASKS_ENABLED =
-            SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false);
-    public static final boolean SHOW_FLOATING_TASKS_AS_BUBBLES =
-            SystemProperties.getBoolean("persist.wm.debug.floating_tasks_as_bubbles", false);
-
-    @VisibleForTesting
-    static final int SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET = 600;
-
-    // Only used for testing
-    private Configuration mConfig;
-    private boolean mFloatingTasksEnabledForTests;
-
-    private FloatingTaskImpl mImpl = new FloatingTaskImpl();
-    private Context mContext;
-    private ShellController mShellController;
-    private ShellCommandHandler mShellCommandHandler;
-    private @Nullable BubbleController mBubbleController;
-    private WindowManager mWindowManager;
-    private ShellTaskOrganizer mTaskOrganizer;
-    private TaskViewTransitions mTaskViewTransitions;
-    private @ShellMainThread ShellExecutor mMainExecutor;
-    // TODO: mBackgroundThread is not used but we'll probs need it eventually?
-    private @ShellBackgroundThread ShellExecutor mBackgroundThread;
-    private SyncTransactionQueue mSyncQueue;
-
-    private boolean mIsFloatingLayerAdded;
-    private FloatingTaskLayer mFloatingTaskLayer;
-    private final Point mLastPosition = new Point(-1, -1);
-
-    private Task mTask;
-
-    // Simple class to hold onto info for intent or shortcut based tasks.
-    public static class Task {
-        public int taskId = INVALID_TASK_ID;
-        @Nullable
-        public Intent intent;
-        @Nullable
-        public ShortcutInfo info;
-        @Nullable
-        public FloatingTaskView floatingView;
-    }
-
-    public FloatingTasksController(Context context,
-            ShellInit shellInit,
-            ShellController shellController,
-            ShellCommandHandler shellCommandHandler,
-            Optional<BubbleController> bubbleController,
-            WindowManager windowManager,
-            ShellTaskOrganizer organizer,
-            TaskViewTransitions transitions,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellBackgroundThread ShellExecutor bgExceutor,
-            SyncTransactionQueue syncTransactionQueue) {
-        mContext = context;
-        mShellController = shellController;
-        mShellCommandHandler = shellCommandHandler;
-        mBubbleController = bubbleController.get();
-        mWindowManager = windowManager;
-        mTaskOrganizer = organizer;
-        mTaskViewTransitions = transitions;
-        mMainExecutor = mainExecutor;
-        mBackgroundThread = bgExceutor;
-        mSyncQueue = syncTransactionQueue;
-        if (isFloatingTasksEnabled()) {
-            shellInit.addInitCallback(this::onInit, this);
-        }
-    }
-
-    protected void onInit() {
-        mShellController.addConfigurationChangeListener(this);
-        mShellController.addExternalInterface(KEY_EXTRA_SHELL_FLOATING_TASKS,
-                this::createExternalInterface, this);
-        mShellCommandHandler.addDumpCallback(this::dump, this);
-    }
-
-    /** Only used for testing. */
-    @VisibleForTesting
-    void setConfig(Configuration config) {
-        mConfig = config;
-    }
-
-    /** Only used for testing. */
-    @VisibleForTesting
-    void setFloatingTasksEnabled(boolean enabled) {
-        mFloatingTasksEnabledForTests = enabled;
-    }
-
-    /** Whether the floating layer is available. */
-    boolean isFloatingLayerAvailable() {
-        Configuration config = mConfig == null
-                ? mContext.getResources().getConfiguration()
-                : mConfig;
-        return config.smallestScreenWidthDp >= SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
-    }
-
-    /** Whether floating tasks are enabled.  */
-    boolean isFloatingTasksEnabled() {
-        return FLOATING_TASKS_ENABLED || mFloatingTasksEnabledForTests;
-    }
-
-    private ExternalInterfaceBinder createExternalInterface() {
-        return new IFloatingTasksImpl(this);
-    }
-
-    @Override
-    public void onThemeChanged() {
-        if (mIsFloatingLayerAdded) {
-            mFloatingTaskLayer.updateSizes();
-        }
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        // TODO: probably other stuff here to do (e.g. handle rotation)
-        if (mIsFloatingLayerAdded) {
-            mFloatingTaskLayer.updateSizes();
-        }
-    }
-
-    /** Returns false if the task shouldn't be shown. */
-    private boolean canShowTask(Intent intent) {
-        ProtoLog.d(WM_SHELL_FLOATING_APPS, "canShowTask --  %s", intent);
-        if (!isFloatingTasksEnabled() || !isFloatingLayerAvailable()) return false;
-        if (intent == null) {
-            ProtoLog.e(WM_SHELL_FLOATING_APPS, "canShowTask given null intent, doing nothing");
-            return false;
-        }
-        return true;
-    }
-
-    /** Returns true if the task was or should be shown as a bubble. */
-    private boolean maybeShowTaskAsBubble(Intent intent) {
-        if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubbleController != null) {
-            removeFloatingLayer();
-            if (intent.getPackage() != null) {
-                mBubbleController.addAppBubble(intent);
-                ProtoLog.d(WM_SHELL_FLOATING_APPS, "showing floating task as bubble: %s", intent);
-            } else {
-                ProtoLog.d(WM_SHELL_FLOATING_APPS,
-                        "failed to show floating task as bubble: %s; unknown package", intent);
-            }
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Shows, stashes, or un-stashes the floating task depending on state:
-     * - If there is no floating task for this intent, it shows this the provided task.
-     * - If there is a floating task for this intent, but it's stashed, this un-stashes it.
-     * - If there is a floating task for this intent, and it's not stashed, this stashes it.
-     */
-    public void showOrSetStashed(Intent intent) {
-        if (!canShowTask(intent)) return;
-        if (maybeShowTaskAsBubble(intent)) return;
-
-        addFloatingLayer();
-
-        if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) {
-            // The task is already added, toggle the stash state.
-            mFloatingTaskLayer.setStashed(mTask, !mTask.floatingView.isStashed());
-            return;
-        }
-
-        // If we're here it's either a new or different task
-        showNewTask(intent);
-    }
-
-    /**
-     * Shows a floating task with the provided intent.
-     * If the same task is present it will un-stash it or do nothing if it is already un-stashed.
-     * Removes any other floating tasks that might exist.
-     */
-    public void showTask(Intent intent) {
-        if (!canShowTask(intent)) return;
-        if (maybeShowTaskAsBubble(intent)) return;
-
-        addFloatingLayer();
-
-        if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) {
-            // The task is already added, show it if it's stashed.
-            if (mTask.floatingView.isStashed()) {
-                mFloatingTaskLayer.setStashed(mTask, false);
-            }
-            return;
-        }
-        showNewTask(intent);
-    }
-
-    private void showNewTask(Intent intent) {
-        if (mTask != null && !intent.filterEquals(mTask.intent)) {
-            mFloatingTaskLayer.removeAllTaskViews();
-            mTask.floatingView.cleanUpTaskView();
-            mTask = null;
-        }
-
-        FloatingTaskView ftv = new FloatingTaskView(mContext, this);
-        ftv.createTaskView(mContext, mTaskOrganizer, mTaskViewTransitions, mSyncQueue);
-
-        mTask = new Task();
-        mTask.floatingView = ftv;
-        mTask.intent = intent;
-
-        // Add & start the task.
-        mFloatingTaskLayer.addTask(mTask);
-        ProtoLog.d(WM_SHELL_FLOATING_APPS, "showNewTask, startingIntent: %s", intent);
-        mTask.floatingView.startTask(mMainExecutor, mTask);
-    }
-
-    /**
-     * Removes the task and cleans up the view.
-     */
-    public void removeTask() {
-        if (mTask != null) {
-            ProtoLog.d(WM_SHELL_FLOATING_APPS, "Removing task with id=%d", mTask.taskId);
-
-            if (mTask.floatingView != null) {
-                // TODO: animate it
-                mFloatingTaskLayer.removeView(mTask.floatingView);
-                mTask.floatingView.cleanUpTaskView();
-            }
-            removeFloatingLayer();
-        }
-    }
-
-    /**
-     * Whether there is a floating task and if it is stashed.
-     */
-    public boolean isStashed() {
-        return isTaskAttached(mTask) && mTask.floatingView.isStashed();
-    }
-
-    /**
-     * If a floating task exists, this sets whether it is stashed and animates if needed.
-     */
-    public void setStashed(boolean shouldStash) {
-        if (mTask != null && mTask.floatingView != null && mIsFloatingLayerAdded) {
-            mFloatingTaskLayer.setStashed(mTask, shouldStash);
-        }
-    }
-
-    /**
-     * Saves the last position the floating task was in so that it can be put there again.
-     */
-    public void setLastPosition(int x, int y) {
-        mLastPosition.set(x, y);
-    }
-
-    /**
-     * Returns the last position the floating task was in.
-     */
-    public Point getLastPosition() {
-        return mLastPosition;
-    }
-
-    /**
-     * Whether the provided task has a view that's attached to the floating layer.
-     */
-    private boolean isTaskAttached(Task t) {
-        return t != null && t.floatingView != null
-                && mIsFloatingLayerAdded
-                && mFloatingTaskLayer.getTaskViewCount() > 0
-                && Objects.equals(mFloatingTaskLayer.getFirstTaskView(), t.floatingView);
-    }
-
-    // TODO: when this is added, if there are bubbles, they get hidden? Is only one layer of this
-    //  type allowed? Bubbles & floating tasks should probably be in the same layer to reduce
-    //  # of windows.
-    private void addFloatingLayer() {
-        if (mIsFloatingLayerAdded) {
-            return;
-        }
-
-        mFloatingTaskLayer = new FloatingTaskLayer(mContext, this, mWindowManager);
-
-        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
-                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
-                PixelFormat.TRANSLUCENT
-        );
-        params.setTrustedOverlay();
-        params.setFitInsetsTypes(0);
-        params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-        params.setTitle("FloatingTaskLayer");
-        params.packageName = mContext.getPackageName();
-        params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-
-        try {
-            mIsFloatingLayerAdded = true;
-            mWindowManager.addView(mFloatingTaskLayer, params);
-        } catch (IllegalStateException e) {
-            // This means the floating layer has already been added which shouldn't happen.
-            e.printStackTrace();
-        }
-    }
-
-    private void removeFloatingLayer() {
-        if (!mIsFloatingLayerAdded) {
-            return;
-        }
-        try {
-            mIsFloatingLayerAdded = false;
-            if (mFloatingTaskLayer != null) {
-                mWindowManager.removeView(mFloatingTaskLayer);
-            }
-        } catch (IllegalArgumentException e) {
-            // This means the floating layer has already been removed which shouldn't happen.
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * Description of current floating task state.
-     */
-    private void dump(PrintWriter pw, String prefix) {
-        pw.println("FloatingTaskController state:");
-        pw.print("   isFloatingLayerAvailable= "); pw.println(isFloatingLayerAvailable());
-        pw.print("   isFloatingTasksEnabled= "); pw.println(isFloatingTasksEnabled());
-        pw.print("   mIsFloatingLayerAdded= "); pw.println(mIsFloatingLayerAdded);
-        pw.print("   mLastPosition= "); pw.println(mLastPosition);
-        pw.println();
-    }
-
-    /** Returns the {@link FloatingTasks} implementation. */
-    public FloatingTasks asFloatingTasks() {
-        return mImpl;
-    }
-
-    @Override
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public ShellExecutor getRemoteCallExecutor() {
-        return mMainExecutor;
-    }
-
-    /**
-     * The interface for calls from outside the shell, within the host process.
-     */
-    @ExternalThread
-    private class FloatingTaskImpl implements FloatingTasks {
-        @Override
-        public void showOrSetStashed(Intent intent) {
-            mMainExecutor.execute(() -> FloatingTasksController.this.showOrSetStashed(intent));
-        }
-    }
-
-    /**
-     * The interface for calls from outside the host process.
-     */
-    @BinderThread
-    private static class IFloatingTasksImpl extends IFloatingTasks.Stub
-            implements ExternalInterfaceBinder {
-        private FloatingTasksController mController;
-
-        IFloatingTasksImpl(FloatingTasksController controller) {
-            mController = controller;
-        }
-
-        /**
-         * Invalidates this instance, preventing future calls from updating the controller.
-         */
-        @Override
-        public void invalidate() {
-            mController = null;
-        }
-
-        public void showTask(Intent intent) {
-            executeRemoteCallWithTaskPermission(mController, "showTask",
-                    (controller) ->  controller.showTask(intent));
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
deleted file mode 100644
index f79ca10..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ /dev/null
@@ -1,28 +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.wm.shell.floating;
-
-import android.content.Intent;
-
-/**
- * Interface that is exposed to remote callers to manipulate floating task features.
- */
-interface IFloatingTasks {
-
-    void showTask(in Intent intent) = 1;
-
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java
deleted file mode 100644
index c922109..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java
+++ /dev/null
@@ -1,73 +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.wm.shell.floating.views;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.wm.shell.R;
-
-/**
- * Displays the menu items for a floating task view (e.g. close).
- */
-public class FloatingMenuView extends LinearLayout {
-
-    private int mItemSize;
-    private int mItemMargin;
-
-    public FloatingMenuView(Context context) {
-        super(context);
-        setOrientation(LinearLayout.HORIZONTAL);
-        setGravity(Gravity.CENTER);
-
-        mItemSize = context.getResources().getDimensionPixelSize(
-                R.dimen.floating_task_menu_item_size);
-        mItemMargin = context.getResources().getDimensionPixelSize(
-                R.dimen.floating_task_menu_item_padding);
-    }
-
-    /** Adds a clickable item to the menu bar. Items are ordered as added. */
-    public void addMenuItem(@Nullable Drawable drawable, View.OnClickListener listener) {
-        ImageView itemView = new ImageView(getContext());
-        itemView.setScaleType(ImageView.ScaleType.CENTER);
-        if (drawable != null) {
-            itemView.setImageDrawable(drawable);
-        }
-        LinearLayout.LayoutParams lp = new LayoutParams(mItemSize,
-                ViewGroup.LayoutParams.MATCH_PARENT);
-        lp.setMarginStart(mItemMargin);
-        lp.setMarginEnd(mItemMargin);
-        addView(itemView, lp);
-
-        itemView.setOnClickListener(listener);
-    }
-
-    /**
-     * The menu extends past the top of the TaskView because of the rounded corners. This means
-     * to center content in the menu we must subtract the radius (i.e. the amount of space covered
-     * by TaskView).
-     */
-    public void setCornerRadius(float radius) {
-        setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java
deleted file mode 100644
index 16dab24..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java
+++ /dev/null
@@ -1,687 +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.wm.shell.floating.views;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Insets;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.FlingAnimation;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.R;
-import com.android.wm.shell.floating.FloatingDismissController;
-import com.android.wm.shell.floating.FloatingTasksController;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * This is the layout that {@link FloatingTaskView}s are contained in. It handles input and
- * movement of the task views.
- */
-public class FloatingTaskLayer extends FrameLayout
-        implements ViewTreeObserver.OnComputeInternalInsetsListener {
-
-    private static final String TAG = FloatingTaskLayer.class.getSimpleName();
-
-    /** How big to make the task view based on screen width of the largest size. */
-    private static final float START_SIZE_WIDTH_PERCENT = 0.33f;
-    /** Min fling velocity required to move the view from one side of the screen to the other. */
-    private static final float ESCAPE_VELOCITY = 750f;
-    /** Amount of friction to apply to fling animations. */
-    private static final float FLING_FRICTION = 1.9f;
-
-    private final FloatingTasksController mController;
-    private final FloatingDismissController mDismissController;
-    private final WindowManager mWindowManager;
-    private final TouchHandlerImpl mTouchHandler;
-
-    private final Region mTouchableRegion = new Region();
-    private final Rect mPositionRect = new Rect();
-    private final Point mDefaultStartPosition = new Point();
-    private final Point mTaskViewSize = new Point();
-    private WindowInsets mWindowInsets;
-    private int mVerticalPadding;
-    private int mOverhangWhenStashed;
-
-    private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect());
-    private ViewTreeObserver.OnDrawListener mSystemGestureExclusionListener =
-            this::updateSystemGestureExclusion;
-
-    /** Interface allowing something to handle the touch events going to a task. */
-    interface FloatingTaskTouchHandler {
-        void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
-                float viewInitialX, float viewInitialY);
-
-        void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
-                 float dx, float dy);
-
-        void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
-                float dx, float dy, float velX, float velY);
-
-        void onClick(@NonNull FloatingTaskView v);
-    }
-
-    public FloatingTaskLayer(Context context,
-            FloatingTasksController controller,
-            WindowManager windowManager) {
-        super(context);
-        // TODO: Why is this necessary? Without it FloatingTaskView does not render correctly.
-        setBackgroundColor(Color.argb(0, 0, 0, 0));
-
-        mController = controller;
-        mWindowManager = windowManager;
-        updateSizes();
-
-        // TODO: Might make sense to put dismiss controller in the touch handler since that's the
-        //  main user of dismiss controller.
-        mDismissController = new FloatingDismissController(context, mController, this);
-        mTouchHandler = new TouchHandlerImpl();
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
-        getViewTreeObserver().addOnDrawListener(mSystemGestureExclusionListener);
-        setOnApplyWindowInsetsListener((view, windowInsets) -> {
-            if (!windowInsets.equals(mWindowInsets)) {
-                mWindowInsets = windowInsets;
-                updateSizes();
-            }
-            return windowInsets;
-        });
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
-        getViewTreeObserver().removeOnDrawListener(mSystemGestureExclusionListener);
-    }
-
-    @Override
-    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
-        inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-        mTouchableRegion.setEmpty();
-        getTouchableRegion(mTouchableRegion);
-        inoutInfo.touchableRegion.set(mTouchableRegion);
-    }
-
-    /** Adds a floating task to the layout. */
-    public void addTask(FloatingTasksController.Task task) {
-        if (task.floatingView == null) return;
-
-        task.floatingView.setTouchHandler(mTouchHandler);
-        addView(task.floatingView, new LayoutParams(mTaskViewSize.x, mTaskViewSize.y));
-        updateTaskViewPosition(task.floatingView);
-    }
-
-    /** Animates the stashed state of the provided task, if it's part of the floating layer. */
-    public void setStashed(FloatingTasksController.Task task, boolean shouldStash) {
-        if (task.floatingView != null && task.floatingView.getParent() == this) {
-            mTouchHandler.stashTaskView(task.floatingView, shouldStash);
-        }
-    }
-
-    /** Removes all {@link FloatingTaskView} from the layout. */
-    public void removeAllTaskViews() {
-        int childCount = getChildCount();
-        ArrayList<View> viewsToRemove = new ArrayList<>();
-        for (int i = 0; i < childCount; i++) {
-            if (getChildAt(i) instanceof FloatingTaskView) {
-                viewsToRemove.add(getChildAt(i));
-            }
-        }
-        for (View v : viewsToRemove) {
-            removeView(v);
-        }
-    }
-
-    /** Returns the number of task views in the layout. */
-    public int getTaskViewCount() {
-        int taskViewCount = 0;
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            if (getChildAt(i) instanceof FloatingTaskView) {
-                taskViewCount++;
-            }
-        }
-        return taskViewCount;
-    }
-
-    /**
-     * Called when the task view is un-stuck from the dismiss target.
-     * @param v the task view being moved.
-     * @param velX the x velocity of the motion event.
-     * @param velY the y velocity of the motion event.
-     * @param wasFlungOut true if the user flung the task view out of the dismiss target (i.e. there
-     *                    was an 'up' event), otherwise the user is still dragging.
-     */
-    public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY,
-            boolean wasFlungOut) {
-        mTouchHandler.onUnstuckFromTarget(v, velX, velY, wasFlungOut);
-    }
-
-    /**
-     * Updates dimensions and applies them to any task views.
-     */
-    public void updateSizes() {
-        if (mDismissController != null) {
-            mDismissController.updateSizes();
-        }
-
-        mOverhangWhenStashed = getResources().getDimensionPixelSize(
-                R.dimen.floating_task_stash_offset);
-        mVerticalPadding = getResources().getDimensionPixelSize(
-                R.dimen.floating_task_vertical_padding);
-
-        WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
-        WindowInsets windowInsets = windowMetrics.getWindowInsets();
-        Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
-                | WindowInsets.Type.statusBars()
-                | WindowInsets.Type.displayCutout());
-        Rect bounds = windowMetrics.getBounds();
-        mPositionRect.set(bounds.left + insets.left,
-                bounds.top + insets.top + mVerticalPadding,
-                bounds.right - insets.right,
-                bounds.bottom - insets.bottom - mVerticalPadding);
-
-        int taskViewWidth = Math.max(bounds.height(), bounds.width());
-        int taskViewHeight = Math.min(bounds.height(), bounds.width());
-        taskViewHeight = taskViewHeight - (insets.top + insets.bottom + (mVerticalPadding * 2));
-        mTaskViewSize.set((int) (taskViewWidth * START_SIZE_WIDTH_PERCENT), taskViewHeight);
-        mDefaultStartPosition.set(mPositionRect.left, mPositionRect.top);
-
-        // Update existing views
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            if (getChildAt(i) instanceof FloatingTaskView) {
-                FloatingTaskView child = (FloatingTaskView) getChildAt(i);
-                LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                lp.width = mTaskViewSize.x;
-                lp.height = mTaskViewSize.y;
-                child.setLayoutParams(lp);
-                updateTaskViewPosition(child);
-            }
-        }
-    }
-
-    /** Returns the first floating task view in the layout. (Currently only ever 1 view). */
-    @Nullable
-    public FloatingTaskView getFirstTaskView() {
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = getChildAt(i);
-            if (child instanceof FloatingTaskView) {
-                return (FloatingTaskView) child;
-            }
-        }
-        return null;
-    }
-
-    private void updateTaskViewPosition(FloatingTaskView floatingView) {
-        Point lastPosition = mController.getLastPosition();
-        if (lastPosition.x == -1 && lastPosition.y == -1) {
-            floatingView.setX(mDefaultStartPosition.x);
-            floatingView.setY(mDefaultStartPosition.y);
-        } else {
-            floatingView.setX(lastPosition.x);
-            floatingView.setY(lastPosition.y);
-        }
-        if (mTouchHandler.isStashedPosition(floatingView)) {
-            floatingView.setStashed(true);
-        }
-        floatingView.updateLocation();
-    }
-
-    /**
-     * Updates the area of the screen that shouldn't allow the back gesture due to the placement
-     * of task view (i.e. when task view is stashed on an edge, tapping or swiping that edge would
-     * un-stash the task view instead of performing the back gesture).
-     */
-    private void updateSystemGestureExclusion() {
-        Rect excludeZone = mSystemGestureExclusionRects.get(0);
-        FloatingTaskView floatingTaskView = getFirstTaskView();
-        if (floatingTaskView != null && floatingTaskView.isStashed()) {
-            excludeZone.set(floatingTaskView.getLeft(),
-                    floatingTaskView.getTop(),
-                    floatingTaskView.getRight(),
-                    floatingTaskView.getBottom());
-            excludeZone.offset((int) (floatingTaskView.getTranslationX()),
-                    (int) (floatingTaskView.getTranslationY()));
-            setSystemGestureExclusionRects(mSystemGestureExclusionRects);
-        } else {
-            excludeZone.setEmpty();
-            setSystemGestureExclusionRects(Collections.emptyList());
-        }
-    }
-
-    /**
-     * Fills in the touchable region for floating windows. This is used by WindowManager to
-     * decide which touch events go to the floating windows.
-     */
-    private void getTouchableRegion(Region outRegion) {
-        int childCount = getChildCount();
-        Rect temp = new Rect();
-        for (int i = 0; i < childCount; i++) {
-            View child = getChildAt(i);
-            if (child instanceof FloatingTaskView) {
-                child.getBoundsOnScreen(temp);
-                outRegion.op(temp, Region.Op.UNION);
-            }
-        }
-    }
-
-    /**
-     * Implementation of the touch handler. Animates the task view based on touch events.
-     */
-    private class TouchHandlerImpl implements FloatingTaskTouchHandler {
-        /**
-         * The view can be stashed by swiping it towards the current edge or moving it there. If
-         * the view gets moved in a way that is not one of these gestures, this is flipped to false.
-         */
-        private boolean mCanStash = true;
-        /**
-         * This is used to indicate that the view has been un-stuck from the dismiss target and
-         * needs to spring to the current touch location.
-         */
-        // TODO: implement this behavior
-        private boolean mSpringToTouchOnNextMotionEvent = false;
-
-        private ArrayList<FlingAnimation> mFlingAnimations;
-        private ViewPropertyAnimator mViewPropertyAnimation;
-
-        private float mViewInitialX;
-        private float mViewInitialY;
-
-        private float[] mMinMax = new float[2];
-
-        @Override
-        public void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, float viewInitialX,
-                float viewInitialY) {
-            mCanStash = true;
-            mViewInitialX = viewInitialX;
-            mViewInitialY = viewInitialY;
-            mDismissController.setUpMagneticObject(v);
-            mDismissController.passEventToMagnetizedObject(ev);
-        }
-
-        @Override
-        public void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
-                float dx, float dy) {
-            // Shows the magnetic dismiss target if needed.
-            mDismissController.showDismiss(/* show= */ true);
-
-            // Send it to magnetic target first.
-            if (mDismissController.passEventToMagnetizedObject(ev)) {
-                v.setStashed(false);
-                mCanStash = true;
-
-                return;
-            }
-
-            // If we're here magnetic target didn't want it so move as per normal.
-
-            v.setTranslationX(capX(v, mViewInitialX + dx, /* isMoving= */ true));
-            v.setTranslationY(capY(v, mViewInitialY + dy));
-            if (v.isStashed()) {
-                // Check if we've moved far enough to be not stashed.
-                final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
-                final boolean viewInitiallyOnLeftSide = mViewInitialX < centerX;
-                if (viewInitiallyOnLeftSide) {
-                    if (v.getTranslationX() > mPositionRect.left) {
-                        v.setStashed(false);
-                        mCanStash = true;
-                    }
-                } else if (v.getTranslationX() + v.getWidth() < mPositionRect.right) {
-                    v.setStashed(false);
-                    mCanStash = true;
-                }
-            }
-        }
-
-        // Reference for math / values: StackAnimationController#flingStackThenSpringToEdge.
-        // TODO clean up the code here, pretty hard to comprehend
-        // TODO code here doesn't work the best when in portrait (e.g. can't fling up/down on edges)
-        @Override
-        public void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
-                float dx, float dy, float velX, float velY) {
-
-            // Send it to magnetic target first.
-            if (mDismissController.passEventToMagnetizedObject(ev)) {
-                v.setStashed(false);
-                return;
-            }
-            mDismissController.showDismiss(/* show= */ false);
-
-            // If we're here magnetic target didn't want it so handle up as per normal.
-
-            final float x = capX(v, mViewInitialX + dx, /* isMoving= */ false);
-            final float centerX = mPositionRect.centerX();
-            final boolean viewInitiallyOnLeftSide = mViewInitialX + v.getWidth() < centerX;
-            final boolean viewOnLeftSide = x + v.getWidth() < centerX;
-            final boolean isFling = Math.abs(velX) > ESCAPE_VELOCITY;
-            final boolean isFlingLeft = isFling && velX < ESCAPE_VELOCITY;
-            // TODO: check velX here sometimes it doesn't stash on move when I think it should
-            final boolean shouldStashFromMove =
-                    (velX < 0 && v.getTranslationX() < mPositionRect.left)
-                            || (velX > 0
-                            && v.getTranslationX() + v.getWidth() > mPositionRect.right);
-            final boolean shouldStashFromFling = viewInitiallyOnLeftSide == viewOnLeftSide
-                    && isFling
-                    && ((viewOnLeftSide && velX < ESCAPE_VELOCITY)
-                    || (!viewOnLeftSide && velX > ESCAPE_VELOCITY));
-            final boolean shouldStash = mCanStash && (shouldStashFromFling || shouldStashFromMove);
-
-            ProtoLog.d(WM_SHELL_FLOATING_APPS,
-                    "shouldStash=%s shouldStashFromFling=%s shouldStashFromMove=%s"
-                    + " viewInitiallyOnLeftSide=%s viewOnLeftSide=%s isFling=%s velX=%f"
-                    + " isStashed=%s", shouldStash, shouldStashFromFling, shouldStashFromMove,
-                    viewInitiallyOnLeftSide, viewOnLeftSide, isFling, velX, v.isStashed());
-
-            if (v.isStashed()) {
-                mMinMax[0] = viewOnLeftSide
-                        ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed
-                        : mPositionRect.right - v.getWidth();
-                mMinMax[1] = viewOnLeftSide
-                        ? mPositionRect.left
-                        : mPositionRect.right - mOverhangWhenStashed;
-            } else {
-                populateMinMax(v, viewOnLeftSide, shouldStash, mMinMax);
-            }
-
-            boolean movingLeft = isFling ? isFlingLeft : viewOnLeftSide;
-            float destinationRelativeX = movingLeft
-                    ? mMinMax[0]
-                    : mMinMax[1];
-
-            // TODO: why is this necessary / when does this happen?
-            if (mMinMax[1] < v.getTranslationX()) {
-                mMinMax[1] = v.getTranslationX();
-            }
-            if (v.getTranslationX() < mMinMax[0]) {
-                mMinMax[0] = v.getTranslationX();
-            }
-
-            // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity
-            // so that it'll make it all the way to the side of the screen.
-            final float minimumVelocityToReachEdge =
-                    getMinimumVelocityToReachEdge(v, destinationRelativeX);
-            final float startXVelocity = movingLeft
-                    ? Math.min(minimumVelocityToReachEdge, velX)
-                    : Math.max(minimumVelocityToReachEdge, velX);
-
-            cancelAnyAnimations(v);
-
-            mFlingAnimations = getAnimationForUpEvent(v, shouldStash,
-                    startXVelocity, mMinMax[0], mMinMax[1], destinationRelativeX);
-            for (int i = 0; i < mFlingAnimations.size(); i++) {
-                mFlingAnimations.get(i).start();
-            }
-        }
-
-        @Override
-        public void onClick(@NonNull FloatingTaskView v) {
-            if (v.isStashed()) {
-                final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
-                final boolean viewOnLeftSide = v.getTranslationX() < centerX;
-                final float destinationRelativeX = viewOnLeftSide
-                        ? mPositionRect.left
-                        : mPositionRect.right - v.getWidth();
-                final float minimumVelocityToReachEdge =
-                        getMinimumVelocityToReachEdge(v, destinationRelativeX);
-                populateMinMax(v, viewOnLeftSide, /* stashed= */ true, mMinMax);
-
-                cancelAnyAnimations(v);
-
-                FlingAnimation flingAnimation = new FlingAnimation(v,
-                        DynamicAnimation.TRANSLATION_X);
-                flingAnimation.setFriction(FLING_FRICTION)
-                        .setStartVelocity(minimumVelocityToReachEdge)
-                        .setMinValue(mMinMax[0])
-                        .setMaxValue(mMinMax[1])
-                        .addEndListener((animation, canceled, value, velocity) -> {
-                            if (canceled) return;
-                            mController.setLastPosition((int) v.getTranslationX(),
-                                    (int) v.getTranslationY());
-                            v.setStashed(false);
-                            v.updateLocation();
-                        });
-                mFlingAnimations = new ArrayList<>();
-                mFlingAnimations.add(flingAnimation);
-                flingAnimation.start();
-            }
-        }
-
-        public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY,
-                boolean wasFlungOut) {
-            if (wasFlungOut) {
-                snapTaskViewToEdge(v, velX, /* shouldStash= */ false);
-            } else {
-                // TODO: use this for something / to spring the view to the touch location
-                mSpringToTouchOnNextMotionEvent = true;
-            }
-        }
-
-        public void stashTaskView(FloatingTaskView v, boolean shouldStash) {
-            if (v.isStashed() == shouldStash) {
-                return;
-            }
-            final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
-            final boolean viewOnLeftSide = v.getTranslationX() < centerX;
-            snapTaskViewToEdge(v, viewOnLeftSide ? -ESCAPE_VELOCITY : ESCAPE_VELOCITY, shouldStash);
-        }
-
-        public boolean isStashedPosition(View v) {
-            return v.getTranslationX() < mPositionRect.left
-                    || v.getTranslationX() + v.getWidth() > mPositionRect.right;
-        }
-
-        // TODO: a lot of this is duplicated in onUp -- can it be unified?
-        private void snapTaskViewToEdge(FloatingTaskView v, float velX, boolean shouldStash) {
-            final boolean movingLeft = velX < ESCAPE_VELOCITY;
-            populateMinMax(v, movingLeft, shouldStash, mMinMax);
-            float destinationRelativeX = movingLeft
-                    ? mMinMax[0]
-                    : mMinMax[1];
-
-            // TODO: why is this necessary / when does this happen?
-            if (mMinMax[1] < v.getTranslationX()) {
-                mMinMax[1] = v.getTranslationX();
-            }
-            if (v.getTranslationX() < mMinMax[0]) {
-                mMinMax[0] = v.getTranslationX();
-            }
-
-            // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity
-            // so that it'll make it all the way to the side of the screen.
-            final float minimumVelocityToReachEdge =
-                    getMinimumVelocityToReachEdge(v, destinationRelativeX);
-            final float startXVelocity = movingLeft
-                    ? Math.min(minimumVelocityToReachEdge, velX)
-                    : Math.max(minimumVelocityToReachEdge, velX);
-
-            cancelAnyAnimations(v);
-
-            mFlingAnimations = getAnimationForUpEvent(v,
-                    shouldStash, startXVelocity,  mMinMax[0], mMinMax[1],
-                    destinationRelativeX);
-            for (int i = 0; i < mFlingAnimations.size(); i++) {
-                mFlingAnimations.get(i).start();
-            }
-        }
-
-        private void cancelAnyAnimations(FloatingTaskView v) {
-            if (mFlingAnimations != null) {
-                for (int i = 0; i < mFlingAnimations.size(); i++) {
-                    if (mFlingAnimations.get(i).isRunning()) {
-                        mFlingAnimations.get(i).cancel();
-                    }
-                }
-            }
-            if (mViewPropertyAnimation != null) {
-                mViewPropertyAnimation.cancel();
-                mViewPropertyAnimation = null;
-            }
-        }
-
-        private ArrayList<FlingAnimation> getAnimationForUpEvent(FloatingTaskView v,
-                boolean shouldStash, float startVelX, float minValue, float maxValue,
-                float destinationRelativeX) {
-            final float ty = v.getTranslationY();
-            final ArrayList<FlingAnimation> animations = new ArrayList<>();
-            if (ty != capY(v, ty)) {
-                // The view was being dismissed so the Y is out of bounds, need to animate that.
-                FlingAnimation yFlingAnimation = new FlingAnimation(v,
-                        DynamicAnimation.TRANSLATION_Y);
-                yFlingAnimation.setFriction(FLING_FRICTION)
-                        .setStartVelocity(startVelX)
-                        .setMinValue(mPositionRect.top)
-                        .setMaxValue(mPositionRect.bottom - mTaskViewSize.y);
-                animations.add(yFlingAnimation);
-            }
-            FlingAnimation flingAnimation = new FlingAnimation(v, DynamicAnimation.TRANSLATION_X);
-            flingAnimation.setFriction(FLING_FRICTION)
-                    .setStartVelocity(startVelX)
-                    .setMinValue(minValue)
-                    .setMaxValue(maxValue)
-                    .addEndListener((animation, canceled, value, velocity) -> {
-                        if (canceled) return;
-                        Runnable endAction = () -> {
-                            v.setStashed(shouldStash);
-                            v.updateLocation();
-                            if (!v.isStashed()) {
-                                mController.setLastPosition((int) v.getTranslationX(),
-                                        (int) v.getTranslationY());
-                            }
-                        };
-                        if (!shouldStash) {
-                            final int xTranslation = (int) v.getTranslationX();
-                            if (xTranslation != destinationRelativeX) {
-                                // TODO: this animation doesn't feel great, should figure out
-                                //  a better way to do this or remove the need for it all together.
-                                mViewPropertyAnimation = v.animate()
-                                        .translationX(destinationRelativeX)
-                                        .setListener(getAnimationListener(endAction));
-                                mViewPropertyAnimation.start();
-                            } else {
-                                endAction.run();
-                            }
-                        } else {
-                            endAction.run();
-                        }
-                    });
-            animations.add(flingAnimation);
-            return animations;
-        }
-
-        private AnimatorListenerAdapter getAnimationListener(Runnable endAction) {
-            return new AnimatorListenerAdapter() {
-                boolean translationCanceled = false;
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    super.onAnimationCancel(animation);
-                    translationCanceled = true;
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
-                    if (!translationCanceled) {
-                        endAction.run();
-                    }
-                }
-            };
-        }
-
-        private void populateMinMax(FloatingTaskView v, boolean onLeft, boolean shouldStash,
-                float[] out) {
-            if (shouldStash) {
-                out[0] = onLeft
-                        ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed
-                        : mPositionRect.right - v.getWidth();
-                out[1] = onLeft
-                        ? mPositionRect.left
-                        : mPositionRect.right - mOverhangWhenStashed;
-            } else {
-                out[0] = mPositionRect.left;
-                out[1] = mPositionRect.right - mTaskViewSize.x;
-            }
-        }
-
-        private float getMinimumVelocityToReachEdge(FloatingTaskView v,
-                float destinationRelativeX) {
-            // Minimum velocity required for the view to make it to the targeted side of the screen,
-            // taking friction into account (4.2f is the number that friction scalars are multiplied
-            // by in DynamicAnimation.DragForce). This is an estimate and could be slightly off, the
-            // animation at the end will ensure that it reaches the destination X regardless.
-            return (destinationRelativeX - v.getTranslationX()) * (FLING_FRICTION * 4.2f);
-        }
-
-        private float capX(FloatingTaskView v, float x, boolean isMoving) {
-            final int width = v.getWidth();
-            if (v.isStashed() || isMoving) {
-                if (x < mPositionRect.left - v.getWidth() + mOverhangWhenStashed) {
-                    return mPositionRect.left - v.getWidth() + mOverhangWhenStashed;
-                }
-                if (x > mPositionRect.right - mOverhangWhenStashed) {
-                    return mPositionRect.right - mOverhangWhenStashed;
-                }
-            } else {
-                if (x < mPositionRect.left) {
-                    return mPositionRect.left;
-                }
-                if (x > mPositionRect.right - width) {
-                    return mPositionRect.right - width;
-                }
-            }
-            return x;
-        }
-
-        private float capY(FloatingTaskView v, float y) {
-            final int height = v.getHeight();
-            if (y < mPositionRect.top) {
-                return mPositionRect.top;
-            }
-            if (y > mPositionRect.bottom - height) {
-                return mPositionRect.bottom - height;
-            }
-            return y;
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java
deleted file mode 100644
index 581204a..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java
+++ /dev/null
@@ -1,385 +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.wm.shell.floating.views;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
-
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskView;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.bubbles.RelativeTouchListener;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.floating.FloatingTasksController;
-
-/**
- * A view that holds a floating task using {@link TaskView} along with additional UI to manage
- * the task.
- */
-public class FloatingTaskView extends FrameLayout {
-
-    private static final String TAG = FloatingTaskView.class.getSimpleName();
-
-    private FloatingTasksController mController;
-
-    private FloatingMenuView mMenuView;
-    private int mMenuHeight;
-    private TaskView mTaskView;
-
-    private float mCornerRadius = 0f;
-    private int mBackgroundColor;
-
-    private FloatingTasksController.Task mTask;
-
-    private boolean mIsStashed;
-
-    /**
-     * Creates a floating task view.
-     *
-     * @param context the context to use.
-     * @param controller the controller to notify about changes in the floating task (e.g. removal).
-     */
-    public FloatingTaskView(Context context, FloatingTasksController controller) {
-        super(context);
-        mController = controller;
-        setElevation(getResources().getDimensionPixelSize(R.dimen.floating_task_elevation));
-        mMenuHeight = context.getResources().getDimensionPixelSize(R.dimen.floating_task_menu_size);
-        mMenuView = new FloatingMenuView(context);
-        addView(mMenuView);
-
-        applyThemeAttrs();
-
-        setClipToOutline(true);
-        setOutlineProvider(new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
-            }
-        });
-    }
-
-    // TODO: call this when theme/config changes
-    void applyThemeAttrs() {
-        boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
-                mContext.getResources());
-        final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
-                android.R.attr.dialogCornerRadius,
-                android.R.attr.colorBackgroundFloating});
-        mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0;
-        mCornerRadius = mCornerRadius / 2f;
-        mBackgroundColor = ta.getColor(1, Color.WHITE);
-
-        ta.recycle();
-
-        mMenuView.setCornerRadius(mCornerRadius);
-        mMenuHeight = getResources().getDimensionPixelSize(
-                R.dimen.floating_task_menu_size);
-
-        if (mTaskView != null) {
-            mTaskView.setCornerRadius(mCornerRadius);
-        }
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        int height = MeasureSpec.getSize(heightMeasureSpec);
-
-        // Add corner radius here so that the menu extends behind the rounded corners of TaskView.
-        int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height);
-        measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
-                MeasureSpec.getMode(heightMeasureSpec)));
-
-        if (mTaskView != null) {
-            int taskViewHeight = height - menuViewHeight;
-            measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight,
-                    MeasureSpec.getMode(heightMeasureSpec)));
-        }
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        // Drag handle above
-        final int dragHandleBottom = t + mMenuView.getMeasuredHeight();
-        mMenuView.layout(l, t, r, dragHandleBottom);
-        if (mTaskView != null) {
-            // Subtract radius so that the menu extends behind the rounded corners of TaskView.
-            mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r,
-                    dragHandleBottom + mTaskView.getMeasuredHeight());
-        }
-    }
-
-    /**
-     * Constructs the TaskView to display the task. Must be called for {@link #startTask} to work.
-     */
-    public void createTaskView(Context context, ShellTaskOrganizer organizer,
-            TaskViewTransitions transitions, SyncTransactionQueue syncQueue) {
-        mTaskView = new TaskView(context, organizer, transitions, syncQueue);
-        addView(mTaskView);
-        mTaskView.setEnableSurfaceClipping(true);
-        mTaskView.setCornerRadius(mCornerRadius);
-    }
-
-    /**
-     * Starts the provided task in the TaskView, if the TaskView exists. This should be called after
-     * {@link #createTaskView}.
-     */
-    public void startTask(@ShellMainThread ShellExecutor executor,
-            FloatingTasksController.Task task) {
-        if (mTaskView == null) {
-            Log.e(TAG, "starting task before creating the view!");
-            return;
-        }
-        mTask = task;
-        mTaskView.setListener(executor, mTaskViewListener);
-    }
-
-    /**
-     * Sets the touch handler for the view.
-     *
-     * @param handler the touch handler for the view.
-     */
-    public void setTouchHandler(FloatingTaskLayer.FloatingTaskTouchHandler handler) {
-        setOnTouchListener(new RelativeTouchListener() {
-            @Override
-            public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
-                handler.onDown(FloatingTaskView.this, ev, v.getTranslationX(), v.getTranslationY());
-                return true;
-            }
-
-            @Override
-            public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
-                    float viewInitialY, float dx, float dy) {
-                handler.onMove(FloatingTaskView.this, ev, dx, dy);
-            }
-
-            @Override
-            public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
-                    float viewInitialY, float dx, float dy, float velX, float velY) {
-                handler.onUp(FloatingTaskView.this, ev, dx, dy, velX, velY);
-            }
-        });
-        setOnClickListener(view -> {
-            handler.onClick(FloatingTaskView.this);
-        });
-
-        mMenuView.addMenuItem(null, view -> {
-            if (mIsStashed) {
-                // If we're stashed all clicks un-stash.
-                handler.onClick(FloatingTaskView.this);
-            }
-        });
-    }
-
-    private void setContentVisibility(boolean visible) {
-        if (mTaskView == null) return;
-        mTaskView.setAlpha(visible ? 1f : 0f);
-    }
-
-    /**
-     * Sets the alpha of both this view and the TaskView.
-     */
-    public void setTaskViewAlpha(float alpha) {
-        if (mTaskView != null) {
-            mTaskView.setAlpha(alpha);
-        }
-        setAlpha(alpha);
-    }
-
-    /**
-     * Call when the location or size of the view has changed to update TaskView.
-     */
-    public void updateLocation() {
-        if (mTaskView == null) return;
-        mTaskView.onLocationChanged();
-    }
-
-    private void updateMenuColor() {
-        ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo();
-        int color = info != null ? info.taskDescription.getBackgroundColor() : -1;
-        if (color != -1) {
-            mMenuView.setBackgroundColor(color);
-        } else {
-            mMenuView.setBackgroundColor(mBackgroundColor);
-        }
-    }
-
-    /**
-     * Sets whether the view is stashed or not.
-     *
-     * Also updates the touchable area based on this. If the view is stashed we don't direct taps
-     * on the activity to the activity, instead a tap will un-stash the view.
-     */
-    public void setStashed(boolean isStashed) {
-        if (mIsStashed != isStashed) {
-            mIsStashed = isStashed;
-            if (mTaskView == null) {
-                return;
-            }
-            updateObscuredTouchRect();
-        }
-    }
-
-    /** Whether the view is stashed at the edge of the screen or not. **/
-    public boolean isStashed() {
-        return mIsStashed;
-    }
-
-    private void updateObscuredTouchRect() {
-        if (mIsStashed) {
-            Rect tmpRect = new Rect();
-            getBoundsOnScreen(tmpRect);
-            mTaskView.setObscuredTouchRect(tmpRect);
-        } else {
-            mTaskView.setObscuredTouchRect(null);
-        }
-    }
-
-    /**
-     * Whether the task needs to be restarted, this can happen when {@link #cleanUpTaskView()} has
-     * been called on this view or if
-     * {@link #startTask(ShellExecutor, FloatingTasksController.Task)} was never called.
-     */
-    public boolean needsTaskStarted() {
-        // If the task needs to be restarted then TaskView would have been cleaned up.
-        return mTaskView == null;
-    }
-
-    /** Call this when the floating task activity is no longer in use. */
-    public void cleanUpTaskView() {
-        if (mTask != null && mTask.taskId != INVALID_TASK_ID) {
-            try {
-                ActivityTaskManager.getService().removeTask(mTask.taskId);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.getMessage());
-            }
-        }
-        if (mTaskView != null) {
-            mTaskView.release();
-            removeView(mTaskView);
-            mTaskView = null;
-        }
-    }
-
-    // TODO: use task background colour / how to get the taskInfo ?
-    private static int getDragBarColor(ActivityManager.RunningTaskInfo taskInfo) {
-        final int taskBgColor = taskInfo.taskDescription.getStatusBarColor();
-        return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
-    }
-
-    private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
-        private boolean mInitialized = false;
-        private boolean mDestroyed = false;
-
-        @Override
-        public void onInitialized() {
-            if (mDestroyed || mInitialized) {
-                return;
-            }
-            // Custom options so there is no activity transition animation
-            ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
-                    /* enterResId= */ 0, /* exitResId= */ 0);
-
-            Rect launchBounds = new Rect();
-            mTaskView.getBoundsOnScreen(launchBounds);
-
-            try {
-                options.setTaskAlwaysOnTop(true);
-                if (mTask.intent != null) {
-                    Intent fillInIntent = new Intent();
-                    // Apply flags to make behaviour match documentLaunchMode=always.
-                    fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
-                    fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-
-                    PendingIntent pi = PendingIntent.getActivity(mContext, 0, mTask.intent,
-                            PendingIntent.FLAG_MUTABLE,
-                            null);
-                    mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
-                } else {
-                    ProtoLog.e(WM_SHELL_FLOATING_APPS, "Tried to start a task with null intent");
-                }
-            } catch (RuntimeException e) {
-                ProtoLog.e(WM_SHELL_FLOATING_APPS, "Exception while starting task: %s",
-                        e.getMessage());
-                mController.removeTask();
-            }
-            mInitialized = true;
-        }
-
-        @Override
-        public void onReleased() {
-            mDestroyed = true;
-        }
-
-        @Override
-        public void onTaskCreated(int taskId, ComponentName name) {
-            mTask.taskId = taskId;
-            updateMenuColor();
-            setContentVisibility(true);
-        }
-
-        @Override
-        public void onTaskVisibilityChanged(int taskId, boolean visible) {
-            setContentVisibility(visible);
-        }
-
-        @Override
-        public void onTaskRemovalStarted(int taskId) {
-            // Must post because this is called from a binder thread.
-            post(() -> {
-                mController.removeTask();
-                cleanUpTaskView();
-            });
-        }
-
-        @Override
-        public void onBackPressedOnTaskRoot(int taskId) {
-            if (mTask.taskId == taskId && !mIsStashed) {
-                // TODO: is removing the window the desired behavior?
-                post(() -> mController.removeTask());
-            }
-        }
-    };
-}
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 90b35a5..8a9b74f 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
@@ -38,7 +38,8 @@
  * {@link ShellTaskOrganizer.TaskListener} for {@link
  * ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
  */
-public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
+public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
+        ShellTaskOrganizer.FocusListener {
     private static final String TAG = "FreeformTaskListener";
 
     private final ShellTaskOrganizer mShellTaskOrganizer;
@@ -67,6 +68,9 @@
 
     private void onInit() {
         mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
+        if (DesktopModeStatus.IS_SUPPORTED) {
+            mShellTaskOrganizer.addFocusListener(this);
+        }
     }
 
     @Override
@@ -82,17 +86,20 @@
         mTasks.put(taskInfo.taskId, state);
         if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t);
+            mWindowDecorationViewModel.onTaskOpening(taskInfo, leash, t, t);
             t.apply();
         }
 
-        if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) {
+        if (DesktopModeStatus.IS_SUPPORTED) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
-                if (repository.addActiveTask(taskInfo.taskId)) {
-                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
-                            "Adding active freeform task: #%d", taskInfo.taskId);
+                repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+                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, true);
                 }
-                repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
             });
         }
     }
@@ -105,6 +112,7 @@
 
         if (DesktopModeStatus.IS_SUPPORTED) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
+                repository.removeFreeformTask(taskInfo.taskId);
                 if (repository.removeActiveTask(taskInfo.taskId)) {
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                             "Removing active freeform task: #%d", taskInfo.taskId);
@@ -140,6 +148,18 @@
     }
 
     @Override
+    public void onFocusTaskChanged(RunningTaskInfo taskInfo) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
+                "Freeform Task Focus Changed: #%d focused=%b",
+                taskInfo.taskId, taskInfo.isFocused);
+        if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isFocused) {
+            mDesktopModeTaskRepository.ifPresent(repository -> {
+                repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+            });
+        }
+    }
+
+    @Override
     public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
         b.setParent(findTaskSurface(taskId));
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index f4888fb..60e5ff2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -90,7 +90,7 @@
                 // This logic relies on 2 assumptions: 1 is that child tasks will be visited before
                 // parents (due to how z-order works). 2 is that no non-tasks are interleaved
                 // between tasks (hierarchically).
-                taskParents.add(change.getContainer());
+                taskParents.add(change.getParent());
             }
             if (taskParents.contains(change.getContainer())) {
                 continue;
@@ -98,9 +98,11 @@
 
             switch (change.getMode()) {
                 case WindowManager.TRANSIT_OPEN:
-                case WindowManager.TRANSIT_TO_FRONT:
                     onOpenTransitionReady(change, startT, finishT);
                     break;
+                case WindowManager.TRANSIT_TO_FRONT:
+                    onToFrontTransitionReady(change, startT, finishT);
+                    break;
                 case WindowManager.TRANSIT_CLOSE: {
                     taskInfoList.add(change.getTaskInfo());
                     onCloseTransitionReady(change, startT, finishT);
@@ -118,7 +120,7 @@
             TransitionInfo.Change change,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        mWindowDecorViewModel.createWindowDecoration(
+        mWindowDecorViewModel.onTaskOpening(
                 change.getTaskInfo(), change.getLeash(), startT, finishT);
     }
 
@@ -126,16 +128,23 @@
             TransitionInfo.Change change,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        mWindowDecorViewModel.setupWindowDecorationForTransition(
-                change.getTaskInfo(), startT, finishT);
+        mWindowDecorViewModel.onTaskClosing(change.getTaskInfo(), startT, finishT);
     }
 
     private void onChangeTransitionReady(
             TransitionInfo.Change change,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        mWindowDecorViewModel.setupWindowDecorationForTransition(
-                change.getTaskInfo(), startT, finishT);
+        mWindowDecorViewModel.onTaskChanging(
+                change.getTaskInfo(), change.getLeash(), startT, finishT);
+    }
+
+    private void onToFrontTransitionReady(
+            TransitionInfo.Change change,
+            SurfaceControl.Transaction startT,
+            SurfaceControl.Transaction finishT) {
+        mWindowDecorViewModel.onTaskChanging(
+                change.getTaskInfo(), change.getLeash(), startT, finishT);
     }
 
     @Override
@@ -171,4 +180,4 @@
             mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
         }
     }
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
new file mode 100644
index 0000000..0c2d5c4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module freeform owners
+madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 75a4091..6623f5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -103,7 +103,7 @@
         if (mWindowDecorViewModelOptional.isPresent()) {
             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
             createdWindowDecor = mWindowDecorViewModelOptional.get()
-                    .createWindowDecoration(taskInfo, leash, t, t);
+                    .onTaskOpening(taskInfo, leash, t, t);
             t.apply();
         }
         if (!createdWindowDecor) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 17d7f5d..5376ae3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -97,6 +97,8 @@
     private int mShelfHeight;
     /** Whether the user has resized the PIP manually. */
     private boolean mHasUserResizedPip;
+    /** Whether the user has moved the PIP manually. */
+    private boolean mHasUserMovedPip;
     /**
      * Areas defined by currently visible apps that they prefer to keep clear from overlays such as
      * the PiP. Restricted areas may only move the PiP a limited amount from its anchor position.
@@ -279,6 +281,7 @@
         if (changed) {
             clearReentryState();
             setHasUserResizedPip(false);
+            setHasUserMovedPip(false);
         }
     }
 
@@ -442,6 +445,16 @@
         mHasUserResizedPip = hasUserResizedPip;
     }
 
+    /** Returns whether the user has moved the PIP. */
+    public boolean hasUserMovedPip() {
+        return mHasUserMovedPip;
+    }
+
+    /** Set whether the user has moved the PIP. */
+    public void setHasUserMovedPip(boolean hasUserMovedPip) {
+        mHasUserMovedPip = hasUserMovedPip;
+    }
+
     /**
      * Registers a callback when the minimal size of PIP that is set by the app changes.
      */
@@ -577,6 +590,8 @@
         pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
         pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
         pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
+        pw.println(innerPrefix + "mHasUserMovedPip=" + mHasUserMovedPip);
+        pw.println(innerPrefix + "mHasUserResizedPip=" + mHasUserResizedPip);
         if (mPipReentryState == null) {
             pw.println(innerPrefix + "mPipReentryState=null");
         } else {
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..8ba2583 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
@@ -63,6 +63,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
+import android.view.Choreographer;
 import android.view.Display;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -179,8 +180,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 +199,39 @@
         }
     };
 
+    /**
+     * 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
+     * onPipAnimationUpdate and the finishResize in onPipAnimationEnd. The transaction in
+     * onPipAnimationUpdate is applied directly from WmShell, while onPipAnimationEnd creates a
+     * WindowContainerTransaction in finishResize, which is to be applied by WmCore later. Normally,
+     * the WCT should be the last transaction to finish the animation. However, it  may happen that
+     * it gets applied *before* the transaction created by the last onPipAnimationUpdate. This
+     * happens only 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 animation with the PiP menu surface
+     * animation and redrawing the PiP menu contents. As a result, the PiP surface gets 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.
+     */
+    private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
+        if (!shouldSyncPipTransactionWithMenu()) {
+            finishResizeRunnable.run();
+            return;
+        }
+
+        // Delay the finishResize to the next frame
+        Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
+            mMainExecutor.execute(finishResizeRunnable);
+        }, null);
+    }
+
+    private boolean shouldSyncPipTransactionWithMenu() {
+        return mPipMenuController.isMenuVisible();
+    }
+
     @VisibleForTesting
     final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
             new PipTransitionController.PipTransitionCallback() {
@@ -221,7 +257,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 +1259,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 +1301,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/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
index 84071e0..690505e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.os.SystemProperties;
 import android.util.ArraySet;
 import android.view.Gravity;
 
@@ -34,6 +35,10 @@
  */
 public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithm {
 
+    private boolean mKeepClearAreaGravityEnabled =
+            SystemProperties.getBoolean(
+                    "persist.wm.debug.enable_pip_keep_clear_algorithm_gravity", false);
+
     protected int mKeepClearAreasPadding;
 
     public PhonePipKeepClearAlgorithm(Context context) {
@@ -53,31 +58,36 @@
         Rect startingBounds = pipBoundsState.getBounds().isEmpty()
                 ? pipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas()
                 : pipBoundsState.getBounds();
-        float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds);
-        int verticalGravity = Gravity.BOTTOM;
-        int horizontalGravity;
-        if (snapFraction >= 0.5f && snapFraction < 2.5f) {
-            horizontalGravity = Gravity.RIGHT;
-        } else {
-            horizontalGravity = Gravity.LEFT;
-        }
-        // push the bounds based on the gravity
         Rect insets = new Rect();
         pipBoundsAlgorithm.getInsetBounds(insets);
         if (pipBoundsState.isImeShowing()) {
             insets.bottom -= pipBoundsState.getImeHeight();
         }
-        Rect pushedBounds = new Rect(startingBounds);
-        if (verticalGravity == Gravity.BOTTOM) {
-            pushedBounds.offsetTo(pushedBounds.left,
-                    insets.bottom - pushedBounds.height());
+        Rect pipBounds = new Rect(startingBounds);
+
+        // move PiP towards corner if user hasn't moved it manually or the flag is on
+        if (mKeepClearAreaGravityEnabled
+                || (!pipBoundsState.hasUserMovedPip() && !pipBoundsState.hasUserResizedPip())) {
+            float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds);
+            int verticalGravity = Gravity.BOTTOM;
+            int horizontalGravity;
+            if (snapFraction >= 0.5f && snapFraction < 2.5f) {
+                horizontalGravity = Gravity.RIGHT;
+            } else {
+                horizontalGravity = Gravity.LEFT;
+            }
+            if (verticalGravity == Gravity.BOTTOM) {
+                pipBounds.offsetTo(pipBounds.left,
+                        insets.bottom - pipBounds.height());
+            }
+            if (horizontalGravity == Gravity.RIGHT) {
+                pipBounds.offsetTo(insets.right - pipBounds.width(), pipBounds.top);
+            } else {
+                pipBounds.offsetTo(insets.left, pipBounds.top);
+            }
         }
-        if (horizontalGravity == Gravity.RIGHT) {
-            pushedBounds.offsetTo(insets.right - pushedBounds.width(), pushedBounds.top);
-        } else {
-            pushedBounds.offsetTo(insets.left, pushedBounds.top);
-        }
-        return findUnoccludedPosition(pushedBounds, pipBoundsState.getRestrictedKeepClearAreas(),
+
+        return findUnoccludedPosition(pipBounds, pipBoundsState.getRestrictedKeepClearAreas(),
                 pipBoundsState.getUnrestrictedKeepClearAreas(), insets);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 7365b95..1f7a7fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -29,6 +29,7 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
 
 import androidx.annotation.BinderThread;
 
@@ -362,6 +363,15 @@
         }
 
         @Override
+        public void takeScreenshotOfWindow(int interactionId,
+                ScreenCapture.ScreenCaptureListener listener,
+                IAccessibilityInteractionConnectionCallback callback) throws RemoteException {
+            // AbstractAccessibilityServiceConnection uses the standard
+            // IAccessibilityInteractionConnection for takeScreenshotOfWindow for Pip windows,
+            // so do nothing here.
+        }
+
+        @Override
         public void clearAccessibilityFocus() throws RemoteException {
             // Do nothing
         }
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..efe938f 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,33 @@
                 new DisplayInsetsController.OnInsetsChangedListener() {
                     @Override
                     public void insetsChanged(InsetsState insetsState) {
+                        DisplayLayout pendingLayout =
+                                mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId());
+                        if (mIsInFixedRotation
+                                || pendingLayout.rotation()
+                                != mPipBoundsState.getDisplayLayout().rotation()) {
+                            // bail out if there is a pending rotation or fixed rotation change
+                            return;
+                        }
+                        int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
                         onDisplayChanged(
                                 mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
                                 false /* saveRestoreSnapFraction */);
+                        int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
+                        if (!mEnablePipKeepClearAlgorithm) {
+                            // offset PiP to adjust for bottom inset change
+                            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..83bc7c0 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) {
@@ -875,6 +875,8 @@
             }
 
             if (touchState.isDragging()) {
+                mPipBoundsState.setHasUserMovedPip(true);
+
                 // Move the pinned stack freely
                 final PointF lastDelta = touchState.getLastTouchDelta();
                 float lastX = mStartPosition.x + mDelta.x;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index c52ed24..75f9a4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -42,8 +42,8 @@
             Consts.TAG_WM_SHELL),
     WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
-    WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
-            Consts.TAG_WM_SHELL),
+    WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+            Consts.TAG_WM_SPLIT_SCREEN),
     WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
     WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
@@ -110,6 +110,7 @@
     private static class Consts {
         private static final String TAG_WM_SHELL = "WindowManagerShell";
         private static final String TAG_WM_STARTING_WINDOW = "ShellStartingWindow";
+        private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen";
 
         private static final boolean ENABLE_DEBUG = true;
         private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index f9172ba..db0f0bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -342,6 +342,16 @@
     }
 
     /**
+     * Returns the top running leaf task.
+     */
+    @Nullable
+    public ActivityManager.RunningTaskInfo getTopRunningTask() {
+        List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(1,
+                false /* filterOnlyVisibleRecents */);
+        return tasks.isEmpty() ? null : tasks.get(0);
+    }
+
+    /**
      * Find the background task that match the given component.
      */
     @Nullable
@@ -367,6 +377,8 @@
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
+        pw.println(prefix + " mListener=" + mListener);
+        pw.println(prefix + "Tasks:");
         ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
                 ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
         for (int i = 0; i < recentTasks.size(); i++) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index eb08d0e..56aa742 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -86,8 +86,8 @@
     /**
      * Starts a pair of intent and task in one transition.
      */
-    oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent,
-            in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio,
+    oneway void startIntentAndTask(in PendingIntent pendingIntent, in Bundle options1, int taskId,
+            in Bundle options2, int sidePosition, float splitRatio,
             in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;
 
     /**
@@ -95,7 +95,7 @@
      */
     oneway void startShortcutAndTask(in ShortcutInfo shortcutInfo, in Bundle options1, int taskId,
             in Bundle options2, int splitPosition, float splitRatio,
-             in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
+            in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
 
     /**
      * Version of startTasks using legacy transition system.
@@ -108,9 +108,8 @@
      * Starts a pair of intent and task using legacy transition system.
      */
     oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
-            in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2,
-            int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
-            in InstanceId instanceId) = 12;
+            in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+            in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12;
 
     /**
      * Starts a pair of shortcut and task using legacy transition system.
@@ -120,6 +119,21 @@
             in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
 
     /**
+     * Start a pair of intents using legacy transition system.
+     */
+    oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1,
+            in Bundle options1, in PendingIntent pendingIntent2, in Bundle options2,
+            int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
+            in InstanceId instanceId) = 18;
+
+    /**
+     * Start a pair of intents in one transition.
+     */
+    oneway void startIntents(in PendingIntent pendingIntent1, in Bundle options1,
+            in PendingIntent pendingIntent2, in Bundle options2, int splitPosition,
+            float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
+
+    /**
      * Blocking call that notifies and gets additional split-screen targets when entering
      * recents (for example: the dividerBar).
      * @param appTargets apps that will be re-parented to display area
@@ -133,4 +147,4 @@
      */
     RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
 }
-// Last id = 17
+// Last id = 19
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index d86aadc..2f2bc77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -73,6 +73,9 @@
     /** Called when device waking up finished. */
     void onFinishedWakingUp();
 
+    /** Called when requested to go to fullscreen from the current active split app. */
+    void goToFullscreenFromSplit();
+
     /** Get a string representation of a stage type */
     static String stageTypeToString(@StageType int stage) {
         switch (stage) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index c6a2b83..9329d02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -32,6 +33,8 @@
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
@@ -55,18 +58,18 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.WindowManager;
+import android.widget.Toast;
 import android.window.RemoteTransition;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -120,6 +123,7 @@
     public static final int EXIT_REASON_SCREEN_LOCKED = 7;
     public static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
     public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
+    public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 10;
     @IntDef(value = {
             EXIT_REASON_UNKNOWN,
             EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -131,6 +135,7 @@
             EXIT_REASON_SCREEN_LOCKED,
             EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP,
             EXIT_REASON_CHILD_TASK_ENTER_PIP,
+            EXIT_REASON_FULLSCREEN_SHORTCUT,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface ExitReason{}
@@ -166,8 +171,11 @@
     private final IconProvider mIconProvider;
     private final Optional<RecentTasksController> mRecentTasksOptional;
     private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
+    private final String[] mMultiInstancesComponents;
 
-    private StageCoordinator mStageCoordinator;
+    @VisibleForTesting
+    StageCoordinator mStageCoordinator;
+
     // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
     // outside the bounds of the roots by being reparented into a higher level fullscreen container
     private SurfaceControl mGoingToRecentsTasksLayer;
@@ -210,6 +218,51 @@
         if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
             shellInit.addInitCallback(this::onInit, this);
         }
+
+        // TODO(255224696): Remove the config once having a way for client apps to opt-in
+        //                  multi-instances split.
+        mMultiInstancesComponents = mContext.getResources()
+                .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
+    }
+
+    @VisibleForTesting
+    SplitScreenController(Context context,
+            ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
+            ShellController shellController,
+            ShellTaskOrganizer shellTaskOrganizer,
+            SyncTransactionQueue syncQueue,
+            RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+            DisplayController displayController,
+            DisplayImeController displayImeController,
+            DisplayInsetsController displayInsetsController,
+            DragAndDropController dragAndDropController,
+            Transitions transitions,
+            TransactionPool transactionPool,
+            IconProvider iconProvider,
+            RecentTasksController recentTasks,
+            ShellExecutor mainExecutor,
+            StageCoordinator stageCoordinator) {
+        mShellCommandHandler = shellCommandHandler;
+        mShellController = shellController;
+        mTaskOrganizer = shellTaskOrganizer;
+        mSyncQueue = syncQueue;
+        mContext = context;
+        mRootTDAOrganizer = rootTDAOrganizer;
+        mMainExecutor = mainExecutor;
+        mDisplayController = displayController;
+        mDisplayImeController = displayImeController;
+        mDisplayInsetsController = displayInsetsController;
+        mDragAndDropController = dragAndDropController;
+        mTransitions = transitions;
+        mTransactionPool = transactionPool;
+        mIconProvider = iconProvider;
+        mRecentTasksOptional = Optional.of(recentTasks);
+        mStageCoordinator = stageCoordinator;
+        mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
+        shellInit.addInitCallback(this::onInit, this);
+        mMultiInstancesComponents = mContext.getResources()
+                .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
     }
 
     public SplitScreen asSplitScreen() {
@@ -264,10 +317,6 @@
         return mStageCoordinator;
     }
 
-    public ActivityManager.RunningTaskInfo getFocusingTaskInfo() {
-        return mStageCoordinator.getFocusingTaskInfo();
-    }
-
     public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
         return mStageCoordinator.isValidToEnterSplitScreen(taskInfo);
     }
@@ -371,6 +420,10 @@
         mStageCoordinator.unregisterSplitScreenListener(listener);
     }
 
+    public void goToFullscreenFromSplit() {
+        mStageCoordinator.goToFullscreenFromSplit();
+    }
+
     public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
         final int[] result = new int[1];
         IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -471,72 +524,182 @@
         startIntent(intent, fillInIntent, position, options);
     }
 
+    private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
+            @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+            @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
+        Intent fillInIntent = null;
+        if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)) {
+            if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+                fillInIntent = new Intent();
+                fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+            } else {
+                try {
+                    adapter.getRunner().onAnimationCancelled(false /* isKeyguardOccluded */);
+                    ActivityTaskManager.getService().startActivityFromRecents(taskId, options2);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error starting remote animation", e);
+                }
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Cancel entering split as not supporting multi-instances");
+                Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+                        Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+        mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
+                options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
+    }
+
+    private void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+            int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+            float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        Intent fillInIntent = null;
+        if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)) {
+            if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+                fillInIntent = new Intent();
+                fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+            } else {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Cancel entering split as not supporting multi-instances");
+                Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+                        Toast.LENGTH_SHORT).show();
+            }
+        }
+        mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
+                options2, splitPosition, splitRatio, remoteTransition, instanceId);
+    }
+
+    private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+            @Nullable Bundle options1, PendingIntent pendingIntent2,
+            @Nullable Bundle options2, @SplitPosition int splitPosition,
+            float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+        Intent fillInIntent1 = null;
+        Intent fillInIntent2 = null;
+        if (launchSameComponentAdjacently(pendingIntent1, pendingIntent2)) {
+            if (supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) {
+                fillInIntent1 = new Intent();
+                fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                fillInIntent2 = new Intent();
+                fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+            } else {
+                try {
+                    adapter.getRunner().onAnimationCancelled(false /* isKeyguardOccluded */);
+                    pendingIntent1.send();
+                } catch (RemoteException | PendingIntent.CanceledException e) {
+                    Slog.e(TAG, "Error starting remote animation", e);
+                }
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Cancel entering split as not supporting multi-instances");
+                Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+                        Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+        mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, options1,
+                pendingIntent2, fillInIntent2, options2, splitPosition, splitRatio, adapter,
+                instanceId);
+    }
+
     @Override
     public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
             @SplitPosition int position, @Nullable Bundle options) {
-        if (fillInIntent == null) {
-            fillInIntent = new Intent();
-        }
-        // Flag this as a no-user-action launch to prevent sending user leaving event to the
-        // current top activity since it's going to be put into another side of the split. This
-        // prevents the current top activity from going into pip mode due to user leaving event.
+        // Flag this as a no-user-action launch to prevent sending user leaving event to the current
+        // top activity since it's going to be put into another side of the split. This prevents the
+        // current top activity from going into pip mode due to user leaving event.
+        if (fillInIntent == null) fillInIntent = new Intent();
         fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
 
-        // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
-        // split and there is no reusable background task.
-        if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) {
-            final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent()
-                    ? mRecentTasksOptional.get().findTaskInBackground(
-                            intent.getIntent().getComponent())
-                    : null;
-            if (taskInfo != null) {
-                startTask(taskInfo.taskId, position, options);
+        if (launchSameComponentAdjacently(intent, position, INVALID_TASK_ID)) {
+            final ComponentName launching = intent.getIntent().getComponent();
+            if (supportMultiInstancesSplit(launching)) {
+                // To prevent accumulating large number of instances in the background, reuse task
+                // in the background with priority.
+                final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
+                        .map(recentTasks -> recentTasks.findTaskInBackground(launching))
+                        .orElse(null);
+                if (taskInfo != null) {
+                    startTask(taskInfo.taskId, position, options);
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                            "Start task in background");
+                    return;
+                }
+
+                // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
+                // the split and there is no reusable background task.
+                fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+            } else if (isSplitScreenVisible()) {
+                mStageCoordinator.switchSplitPosition("startIntent");
+                return;
+            } else {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Cancel entering split as not supporting multi-instances");
+                Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+                        Toast.LENGTH_SHORT).show();
                 return;
             }
-            fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
         }
 
-        if (!ENABLE_SHELL_TRANSITIONS) {
-            mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options);
-            return;
-        }
         mStageCoordinator.startIntent(intent, fillInIntent, position, options);
     }
 
     /** Returns {@code true} if it's launching the same component on both sides of the split. */
-    @VisibleForTesting
-    boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) {
-        if (startIntent == null) {
-            return false;
-        }
+    private boolean launchSameComponentAdjacently(@Nullable PendingIntent pendingIntent,
+            @SplitPosition int position, int taskId) {
+        if (pendingIntent == null || pendingIntent.getIntent() == null) return false;
 
-        final ComponentName launchingActivity = startIntent.getComponent();
-        if (launchingActivity == null) {
-            return false;
-        }
+        final ComponentName launchingActivity = pendingIntent.getIntent().getComponent();
+        if (launchingActivity == null) return false;
 
-        if (isSplitScreenVisible()) {
-            // To prevent users from constantly dropping the same app to the same side resulting in
-            // a large number of instances in the background.
-            final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position);
-            final ComponentName targetActivity = targetTaskInfo != null
-                    ? targetTaskInfo.baseIntent.getComponent() : null;
-            if (Objects.equals(launchingActivity, targetActivity)) {
-                return false;
+        if (taskId != INVALID_TASK_ID) {
+            final ActivityManager.RunningTaskInfo taskInfo =
+                    mTaskOrganizer.getRunningTaskInfo(taskId);
+            if (taskInfo != null) {
+                return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
             }
-
-            // Allow users to start a new instance the same to adjacent side.
-            final ActivityManager.RunningTaskInfo pairedTaskInfo =
-                    getTaskInfo(SplitLayout.reversePosition(position));
-            final ComponentName pairedActivity = pairedTaskInfo != null
-                    ? pairedTaskInfo.baseIntent.getComponent() : null;
-            return Objects.equals(launchingActivity, pairedActivity);
+            return false;
         }
 
-        final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
-        if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
-            return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+        if (!isSplitScreenVisible()) {
+            // Split screen is not yet activated, check if the current top running task is valid to
+            // split together.
+            final ActivityManager.RunningTaskInfo topRunningTask = mRecentTasksOptional
+                    .map(recentTasks -> recentTasks.getTopRunningTask()).orElse(null);
+            if (topRunningTask != null && isValidToEnterSplitScreen(topRunningTask)) {
+                return Objects.equals(topRunningTask.baseIntent.getComponent(), launchingActivity);
+            }
+            return false;
+        }
+
+        // Compare to the adjacent side of the split to determine if this is launching the same
+        // component adjacently.
+        final ActivityManager.RunningTaskInfo pairedTaskInfo =
+                getTaskInfo(SplitLayout.reversePosition(position));
+        final ComponentName pairedActivity = pairedTaskInfo != null
+                ? pairedTaskInfo.baseIntent.getComponent() : null;
+        return Objects.equals(launchingActivity, pairedActivity);
+    }
+
+    private boolean launchSameComponentAdjacently(PendingIntent pendingIntent1,
+            PendingIntent pendingIntent2) {
+        return Objects.equals(pendingIntent1.getIntent().getComponent(),
+                pendingIntent2.getIntent().getComponent());
+    }
+
+    @VisibleForTesting
+    /** Returns {@code true} if the component supports multi-instances split. */
+    boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
+        if (launching == null) return false;
+
+        final String componentName = launching.flattenToString();
+        for (int i = 0; i < mMultiInstancesComponents.length; i++) {
+            if (mMultiInstancesComponents[i].equals(componentName)) {
+                return true;
+            }
         }
 
         return false;
@@ -740,9 +903,12 @@
 
         @Override
         public void onFinishedWakingUp() {
-            mMainExecutor.execute(() -> {
-                SplitScreenController.this.onFinishedWakingUp();
-            });
+            mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp);
+        }
+
+        @Override
+        public void goToFullscreenFromSplit() {
+            mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit);
         }
     }
 
@@ -798,33 +964,25 @@
         @Override
         public void exitSplitScreen(int toTopTaskId) {
             executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
-                    (controller) -> {
-                        controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN);
-                    });
+                    (controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN));
         }
 
         @Override
         public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
             executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
-                    (controller) -> {
-                        controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
-                    });
+                    (controller) -> controller.exitSplitScreenOnHide(exitSplitScreenOnHide));
         }
 
         @Override
         public void removeFromSideStage(int taskId) {
             executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
-                    (controller) -> {
-                        controller.removeFromSideStage(taskId);
-                    });
+                    (controller) -> controller.removeFromSideStage(taskId));
         }
 
         @Override
         public void startTask(int taskId, int position, @Nullable Bundle options) {
             executeRemoteCallWithTaskPermission(mController, "startTask",
-                    (controller) -> {
-                        controller.startTask(taskId, position, options);
-                    });
+                    (controller) -> controller.startTask(taskId, position, options));
         }
 
         @Override
@@ -839,14 +997,13 @@
 
         @Override
         public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
-                Intent fillInIntent, Bundle options1, int taskId, Bundle options2,
-                int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
-                InstanceId instanceId) {
+                Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio,
+                RemoteAnimationAdapter adapter, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController,
                     "startIntentAndTaskWithLegacyTransition", (controller) ->
-                            controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
-                                    pendingIntent, fillInIntent, options1, taskId, options2,
-                                    splitPosition, splitRatio, adapter, instanceId));
+                            controller.startIntentAndTaskWithLegacyTransition(pendingIntent,
+                                    options1, taskId, options2, splitPosition, splitRatio, adapter,
+                                    instanceId));
         }
 
         @Override
@@ -872,14 +1029,13 @@
         }
 
         @Override
-        public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
-                @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
-                @SplitPosition int splitPosition, float splitRatio,
-                @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        public void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+                int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+                float splitRatio, @Nullable RemoteTransition remoteTransition,
+                InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
-                    (controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent,
-                            fillInIntent, options1, taskId, options2, splitPosition, splitRatio,
-                            remoteTransition, instanceId));
+                    (controller) -> controller.startIntentAndTask(pendingIntent, options1, taskId,
+                            options2, splitPosition, splitRatio, remoteTransition, instanceId));
         }
 
         @Override
@@ -894,22 +1050,40 @@
         }
 
         @Override
+        public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+                @Nullable Bundle options1, PendingIntent pendingIntent2, @Nullable Bundle options2,
+                @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+                InstanceId instanceId) {
+            executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
+                    (controller) ->
+                        controller.startIntentsWithLegacyTransition(
+                                pendingIntent1, options1, pendingIntent2, options2, splitPosition,
+                                splitRatio, adapter, instanceId)
+                    );
+        }
+
+        @Override
+        public void startIntents(PendingIntent pendingIntent1, @Nullable Bundle options1,
+                PendingIntent pendingIntent2, @Nullable Bundle options2,
+                @SplitPosition int splitPosition, float splitRatio,
+                @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+            // TODO(b/259368992): To be implemented.
+        }
+
+        @Override
         public void startShortcut(String packageName, String shortcutId, int position,
                 @Nullable Bundle options, UserHandle user, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startShortcut",
-                    (controller) -> {
-                        controller.startShortcut(packageName, shortcutId, position, options, user,
-                                instanceId);
-                    });
+                    (controller) -> controller.startShortcut(packageName, shortcutId, position,
+                            options, user, instanceId));
         }
 
         @Override
         public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
                 @Nullable Bundle options, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startIntent",
-                    (controller) -> {
-                        controller.startIntent(intent, fillInIntent, position, options, instanceId);
-                    });
+                    (controller) -> controller.startIntent(intent, fillInIntent, position, options,
+                            instanceId));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 21a1310..1cf3a89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -47,6 +47,7 @@
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.transition.OneShotRemoteHandler;
 import com.android.wm.shell.transition.Transitions;
@@ -64,6 +65,7 @@
     DismissTransition mPendingDismiss = null;
     TransitSession mPendingEnter = null;
     TransitSession mPendingRecent = null;
+    TransitSession mPendingResize = null;
 
     private IBinder mAnimatingTransition = null;
     OneShotRemoteHandler mPendingRemoteHandler = null;
@@ -177,6 +179,43 @@
         onFinish(null /* wct */, null /* wctCB */);
     }
 
+    void applyResizeTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback,
+            @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
+            @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+        mFinishCallback = finishCallback;
+        mAnimatingTransition = transition;
+        mFinishTransaction = finishTransaction;
+
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if (mainRoot.equals(change.getContainer()) || sideRoot.equals(change.getContainer())) {
+                final SurfaceControl leash = change.getLeash();
+                startTransaction.setPosition(leash, change.getEndAbsBounds().left,
+                        change.getEndAbsBounds().top);
+                startTransaction.setWindowCrop(leash, change.getEndAbsBounds().width(),
+                        change.getEndAbsBounds().height());
+
+                SplitDecorManager decor = mainRoot.equals(change.getContainer())
+                        ? mainDecor : sideDecor;
+                ValueAnimator va = new ValueAnimator();
+                mAnimations.add(va);
+                decor.setScreenshotIfNeeded(change.getSnapshot(), startTransaction);
+                decor.onResized(startTransaction, () -> {
+                    mTransitions.getMainExecutor().execute(() -> {
+                        mAnimations.remove(va);
+                        onFinish(null /* wct */, null /* wctCB */);
+                    });
+                });
+            }
+        }
+
+        startTransaction.apply();
+        onFinish(null /* wct */, null /* wctCB */);
+    }
+
     boolean isPendingTransition(IBinder transition) {
         return getPendingTransition(transition) != null;
     }
@@ -193,6 +232,10 @@
         return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
     }
 
+    boolean isPendingResize(IBinder transition) {
+        return mPendingResize != null && mPendingResize.mTransition == transition;
+    }
+
     @Nullable
     private TransitSession getPendingTransition(IBinder transition) {
         if (isPendingEnter(transition)) {
@@ -201,11 +244,14 @@
             return mPendingRecent;
         } else if (isPendingDismiss(transition)) {
             return mPendingDismiss;
+        } else if (isPendingResize(transition)) {
+            return mPendingResize;
         }
 
         return null;
     }
 
+
     /** Starts a transition to enter split with a remote transition animator. */
     IBinder startEnterTransition(
             @WindowManager.TransitionType int transitType,
@@ -258,6 +304,21 @@
                 exitReasonToString(reason), stageTypeToString(dismissTop));
     }
 
+    IBinder startResizeTransition(WindowContainerTransaction wct,
+            Transitions.TransitionHandler handler,
+            @Nullable TransitionFinishedCallback finishCallback) {
+        IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
+        setResizeTransition(transition, finishCallback);
+        return transition;
+    }
+
+    void setResizeTransition(@NonNull IBinder transition,
+            @Nullable TransitionFinishedCallback finishCallback) {
+        mPendingResize = new TransitSession(transition, null /* consumedCb */, finishCallback);
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
+                + " deduced Resize split screen");
+    }
+
     void setRecentTransition(@NonNull IBinder transition,
             @Nullable RemoteTransition remoteTransition,
             @Nullable TransitionFinishedCallback finishCallback) {
@@ -324,6 +385,9 @@
             mPendingRecent.onConsumed(aborted);
             mPendingRecent = null;
             mPendingRemoteHandler = null;
+        } else if (isPendingResize(transition)) {
+            mPendingResize.onConsumed(aborted);
+            mPendingResize = null;
         }
     }
 
@@ -340,6 +404,9 @@
         } else if (isPendingDismiss(mAnimatingTransition)) {
             mPendingDismiss.onFinished(wct, mFinishTransaction);
             mPendingDismiss = null;
+        } else if (isPendingResize(mAnimatingTransition)) {
+            mPendingResize.onFinished(wct, mFinishTransaction);
+            mPendingResize = null;
         }
 
         mPendingRemoteHandler = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 2dc4a04..1016e1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -23,6 +23,7 @@
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
@@ -38,6 +39,7 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ROOT_TASK_VANISHED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED;
@@ -180,6 +182,8 @@
                 return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
             case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
                 return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
+            case EXIT_REASON_FULLSCREEN_SHORTCUT:
+                return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
             case EXIT_REASON_UNKNOWN:
                 // Fall through
             default:
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..da8dc87 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
@@ -24,7 +24,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -50,6 +49,7 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
@@ -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;
@@ -150,7 +151,7 @@
  */
 public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
         DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler,
-        ShellTaskOrganizer.TaskListener, ShellTaskOrganizer.FocusListener {
+        ShellTaskOrganizer.TaskListener {
 
     private static final String TAG = StageCoordinator.class.getSimpleName();
 
@@ -169,6 +170,7 @@
     private ValueAnimator mDividerFadeInAnimator;
     private boolean mDividerVisible;
     private boolean mKeyguardShowing;
+    private boolean mShowDecorImmediately;
     private final SyncTransactionQueue mSyncQueue;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Context mContext;
@@ -185,8 +187,6 @@
     private final Rect mTempRect1 = new Rect();
     private final Rect mTempRect2 = new Rect();
 
-    private ActivityManager.RunningTaskInfo mFocusingTaskInfo;
-
     /**
      * A single-top root task which the split divider attached to.
      */
@@ -303,7 +303,6 @@
         mDisplayController.addDisplayWindowListener(this);
         mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
         transitions.addHandler(this);
-        mTaskOrganizer.addFocusListener(this);
         mSplitUnsupportedToast = Toast.makeText(mContext,
                 R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
     }
@@ -427,6 +426,11 @@
     /** Launches an activity into split. */
     void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
             @Nullable Bundle options) {
+        if (!ENABLE_SHELL_TRANSITIONS) {
+            startIntentLegacy(intent, fillInIntent, position, options);
+            return;
+        }
+
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         final WindowContainerTransaction evictWct = new WindowContainerTransaction();
         prepareEvictChildTasks(position, evictWct);
@@ -440,13 +444,7 @@
         prepareEnterSplitScreen(wct, null /* taskInfo */, position);
 
         mSplitTransitions.startEnterTransition(transitType, wct, null, this,
-                aborted -> {
-                    // Switch the split position if launching as MULTIPLE_TASK failed.
-                    if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
-                        setSideStagePositionAnimated(
-                                SplitLayout.reversePosition(mSideStagePosition));
-                    }
-                } /* consumedCallback */,
+                null /* consumedCallback */,
                 (finishWct, finishT) -> {
                     if (!evictWct.isEmpty()) {
                         finishWct.merge(evictWct, true);
@@ -455,10 +453,9 @@
     }
 
     /** Launches an activity into split by legacy transition. */
-    void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
-            @SplitPosition int position, @androidx.annotation.Nullable Bundle options) {
-        final WindowContainerTransaction evictWct = new WindowContainerTransaction();
-        prepareEvictChildTasks(position, evictWct);
+    void startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
+            @Nullable Bundle options) {
+        final boolean isEnteringSplit = !isSplitActive();
 
         LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
             @Override
@@ -466,28 +463,28 @@
                     RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
                     IRemoteAnimationFinishedCallback finishedCallback,
                     SurfaceControl.Transaction t) {
-                if (apps == null || apps.length == 0) {
-                    if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
-                        mMainExecutor.execute(() ->
-                                exitSplitScreen(mMainStage.getChildCount() == 0
-                                        ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
-                        mSplitUnsupportedToast.show();
-                    } else {
-                        // Switch the split position if launching as MULTIPLE_TASK failed.
-                        if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
-                            setSideStagePosition(SplitLayout.reversePosition(
-                                    getSideStagePosition()), null);
+                boolean openingToSide = false;
+                if (apps != null) {
+                    for (int i = 0; i < apps.length; ++i) {
+                        if (apps[i].mode == MODE_OPENING
+                                && mSideStage.containsTask(apps[i].taskId)) {
+                            openingToSide = true;
+                            break;
                         }
                     }
-
-                    // Do nothing when the animation was cancelled.
-                    t.apply();
-                    return;
                 }
 
-                for (int i = 0; i < apps.length; ++i) {
-                    if (apps[i].mode == MODE_OPENING) {
-                        t.show(apps[i].leash);
+                if (isEnteringSplit && !openingToSide) {
+                    mMainExecutor.execute(() -> exitSplitScreen(
+                            mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
+                            EXIT_REASON_UNKNOWN));
+                }
+
+                if (apps != null) {
+                    for (int i = 0; i < apps.length; ++i) {
+                        if (apps[i].mode == MODE_OPENING) {
+                            t.show(apps[i].leash);
+                        }
                     }
                 }
                 t.apply();
@@ -500,7 +497,12 @@
                     }
                 }
 
-                mSyncQueue.queue(evictWct);
+
+                if (!isEnteringSplit && openingToSide) {
+                    final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+                    prepareEvictNonOpeningChildTasks(position, apps, evictWct);
+                    mSyncQueue.queue(evictWct);
+                }
             }
         };
 
@@ -509,7 +511,7 @@
 
         // If split still not active, apply windows bounds first to avoid surface reset to
         // wrong pos by SurfaceAnimator from wms.
-        if (!mMainStage.isActive() && mLogger.isEnterRequestedByDrag()) {
+        if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) {
             updateWindowBounds(mSplitLayout, wct);
         }
 
@@ -595,8 +597,7 @@
     /** Starts a pair of tasks using legacy transition. */
     void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
             int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
-            float splitRatio, RemoteAnimationAdapter adapter,
-            InstanceId instanceId) {
+            float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (options1 == null) options1 = new Bundle();
         addActivityOptions(options1, mSideStage);
@@ -606,7 +607,20 @@
                 instanceId);
     }
 
-    /** Starts a pair of intent and task using legacy transition. */
+    /** Starts a pair of intents using legacy transition. */
+    void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, Intent fillInIntent1,
+            @Nullable Bundle options1, PendingIntent pendingIntent2, Intent fillInIntent2,
+            @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+            RemoteAnimationAdapter adapter, InstanceId instanceId) {
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        if (options1 == null) options1 = new Bundle();
+        addActivityOptions(options1, mSideStage);
+        wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+
+        startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, options2, splitPosition,
+                splitRatio, adapter, instanceId);
+    }
+
     void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
             @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
             @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
@@ -634,18 +648,41 @@
                 instanceId);
     }
 
+    private void startWithLegacyTransition(WindowContainerTransaction wct,
+            @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
+            @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
+            RemoteAnimationAdapter adapter, InstanceId instanceId) {
+        startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent,
+                mainOptions, sidePosition, splitRatio, adapter, instanceId);
+    }
+
+    private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
+            @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
+            RemoteAnimationAdapter adapter, InstanceId instanceId) {
+        startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */,
+                null /* mainFillInIntent */, mainOptions, sidePosition, splitRatio, adapter,
+                instanceId);
+    }
+
     /**
      * @param wct        transaction to start the first task
      * @param instanceId if {@code null}, will not log. Otherwise it will be used in
      *                   {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
      */
     private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
+            @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
             @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
         // Init divider first to make divider leash for remote animation target.
         mSplitLayout.init();
         mSplitLayout.setDivideRatio(splitRatio);
 
+        // Apply surface bounds before animation start.
+        SurfaceControl.Transaction startT = mTransactionPool.acquire();
+        updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */);
+        startT.apply();
+        mTransactionPool.release(startT);
+
         // Set false to avoid record new bounds with old task still on top;
         mShouldUpdateRecents = false;
         mIsDividerRemoteAnimating = true;
@@ -708,14 +745,17 @@
         if (mainOptions == null) mainOptions = new Bundle();
         addActivityOptions(mainOptions, mMainStage);
         updateWindowBounds(mSplitLayout, wct);
-        wct.startTask(mainTaskId, mainOptions);
+        if (mainTaskId == INVALID_TASK_ID) {
+            wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions);
+        } else {
+            wct.startTask(mainTaskId, mainOptions);
+        }
         wct.reorder(mRootTaskInfo.token, true);
         wct.setForceTranslucent(mRootTaskInfo.token, false);
 
         mSyncQueue.queue(wct);
         mSyncQueue.runInSync(t -> {
             setDividerVisibility(true, t);
-            updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
         });
 
         setEnterInstanceId(instanceId);
@@ -770,9 +810,8 @@
         mSideStage.evictInvisibleChildren(wct);
     }
 
-    Bundle resolveStartStage(@StageType int stage,
-            @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
-            @androidx.annotation.Nullable WindowContainerTransaction wct) {
+    Bundle resolveStartStage(@StageType int stage, @SplitPosition int position,
+            @Nullable Bundle options, @Nullable WindowContainerTransaction wct) {
         switch (stage) {
             case STAGE_TYPE_UNDEFINED: {
                 if (position != SPLIT_POSITION_UNDEFINED) {
@@ -843,19 +882,52 @@
                 : mMainStage.getTopVisibleChildTaskId();
     }
 
-    void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) {
-        if (mSideStagePosition == sideStagePosition) return;
-        SurfaceControl.Transaction t = mTransactionPool.acquire();
+    void switchSplitPosition(String reason) {
+        final 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();
+                    });
                 });
+
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
+        mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+                getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+                mSplitLayout.isLandscape());
     }
 
     void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -976,7 +1048,7 @@
         mIsDividerRemoteAnimating = false;
 
         mSplitLayout.getInvisibleBounds(mTempRect1);
-        if (childrenToTop == null) {
+        if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) {
             mSideStage.removeAllTasks(wct, false /* toTop */);
             mMainStage.deactivate(wct, false /* toTop */);
             wct.reorder(mRootTaskInfo.token, false /* onTop */);
@@ -1046,15 +1118,8 @@
      * Exits the split screen by finishing one of the tasks.
      */
     protected void exitStage(@SplitPosition int stageToClose) {
-        if (ENABLE_SHELL_TRANSITIONS) {
-            StageTaskListener stageToTop = mSideStagePosition == stageToClose
-                    ? mMainStage
-                    : mSideStage;
-            exitSplitScreen(stageToTop, EXIT_REASON_APP_FINISHED);
-        } else {
-            boolean toEnd = stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT;
-            mSplitLayout.flingDividerToDismiss(toEnd, EXIT_REASON_APP_FINISHED);
-        }
+        mSplitLayout.flingDividerToDismiss(stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT,
+                EXIT_REASON_APP_FINISHED);
     }
 
     /**
@@ -1067,7 +1132,7 @@
             activityTaskManagerService.setFocusedTask(getTaskId(stageToFocus));
         } catch (RemoteException | NullPointerException e) {
             ProtoLog.e(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
-                    "%s: Unable to update focus on the chosen stage, %s", TAG, e);
+                    "Unable to update focus on the chosen stage: %s", e.getMessage());
         }
     }
 
@@ -1088,6 +1153,9 @@
             case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
                 // User has unlocked the device after folded
             case EXIT_REASON_DEVICE_FOLDED:
+                // The device is folded
+            case EXIT_REASON_FULLSCREEN_SHORTCUT:
+                // User has used a keyboard shortcut to go back to fullscreen from split
                 return true;
             default:
                 return false;
@@ -1235,13 +1303,6 @@
         }
     }
 
-    private void onStageChildTaskEnterPip() {
-        // When the exit split-screen is caused by one of the task enters auto pip,
-        // we want both tasks to be put to bottom instead of top, otherwise it will end up
-        // a fullscreen plus a pinned task instead of pinned only at the end of the transition.
-        exitSplitScreen(null, EXIT_REASON_CHILD_TASK_ENTER_PIP);
-    }
-
     private void updateRecentTasksSplitPair() {
         if (!mShouldUpdateRecents) {
             return;
@@ -1404,14 +1465,14 @@
         }
 
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
-                "%s: Request to %s divider bar from %s.", TAG,
+                "Request to %s divider bar from %s.",
                 (visible ? "show" : "hide"), Debug.getCaller());
 
         // Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard
         // dismissing animation.
         if (visible && mKeyguardShowing) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
-                    "%s:   Defer showing divider bar due to keyguard showing.", TAG);
+                    "   Defer showing divider bar due to keyguard showing.");
             return;
         }
 
@@ -1420,7 +1481,7 @@
 
         if (mIsDividerRemoteAnimating) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
-                    "%s:   Skip animating divider bar due to it's remote animating.", TAG);
+                    "   Skip animating divider bar due to it's remote animating.");
             return;
         }
 
@@ -1435,12 +1496,12 @@
         final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
         if (dividerLeash == null) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
-                    "%s:   Skip animating divider bar due to divider leash not ready.", TAG);
+                    "   Skip animating divider bar due to divider leash not ready.");
             return;
         }
         if (mIsDividerRemoteAnimating) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
-                    "%s:   Skip animating divider bar due to it's remote animating.", TAG);
+                    "   Skip animating divider bar due to it's remote animating.");
             return;
         }
 
@@ -1531,6 +1592,7 @@
                 if (mLogger.isEnterRequestedByDrag()) {
                     updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
                 } else {
+                    mShowDecorImmediately = true;
                     mSplitLayout.flingDividerToCenter();
                 }
             });
@@ -1557,15 +1619,6 @@
                 && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
     }
 
-    ActivityManager.RunningTaskInfo getFocusingTaskInfo() {
-        return mFocusingTaskInfo;
-    }
-
-    @Override
-    public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
-        mFocusingTaskInfo = taskInfo;
-    }
-
     @Override
     public void onSnappedToDismiss(boolean bottomOrRight, int reason) {
         final boolean mainStageToTop =
@@ -1587,10 +1640,7 @@
 
     @Override
     public void onDoubleTappedDivider() {
-        setSideStagePositionAnimated(SplitLayout.reversePosition(mSideStagePosition));
-        mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
-                getSideStagePosition(), mSideStage.getTopChildTaskUid(),
-                mSplitLayout.isLandscape());
+        switchSplitPosition("double tap");
     }
 
     @Override
@@ -1603,29 +1653,45 @@
     }
 
     @Override
-    public void onLayoutSizeChanging(SplitLayout layout) {
+    public void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY) {
         final SurfaceControl.Transaction t = mTransactionPool.acquire();
         t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
         updateSurfaceBounds(layout, t, true /* applyResizingOffset */);
         getMainStageBounds(mTempRect1);
         getSideStageBounds(mTempRect2);
-        mMainStage.onResizing(mTempRect1, mTempRect2, t);
-        mSideStage.onResizing(mTempRect2, mTempRect1, t);
+        mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
+        mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
         t.apply();
         mTransactionPool.release(t);
     }
 
     @Override
     public void onLayoutSizeChanged(SplitLayout layout) {
+        // Reset this flag every time onLayoutSizeChanged.
+        mShowDecorImmediately = false;
+
+        if (!ENABLE_SHELL_TRANSITIONS) {
+            // Only need screenshot for legacy case because shell transition should screenshot
+            // itself during transition.
+            final SurfaceControl.Transaction startT = mTransactionPool.acquire();
+            mMainStage.screenshotIfNeeded(startT);
+            mSideStage.screenshotIfNeeded(startT);
+            mTransactionPool.release(startT);
+        }
+
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         updateWindowBounds(layout, wct);
         sendOnBoundsChanged();
-        mSyncQueue.queue(wct);
-        mSyncQueue.runInSync(t -> {
-            updateSurfaceBounds(layout, t, false /* applyResizingOffset */);
-            mMainStage.onResized(t);
-            mSideStage.onResized(t);
-        });
+        if (ENABLE_SHELL_TRANSITIONS) {
+            mSplitTransitions.startResizeTransition(wct, this, null /* callback */);
+        } else {
+            mSyncQueue.queue(wct);
+            mSyncQueue.runInSync(t -> {
+                updateSurfaceBounds(layout, t, false /* applyResizingOffset */);
+                mMainStage.onResized(t);
+                mSideStage.onResized(t);
+            });
+        }
         mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
     }
 
@@ -1979,6 +2045,12 @@
         } else if (mSplitTransitions.isPendingDismiss(transition)) {
             shouldAnimate = startPendingDismissAnimation(
                     mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
+        } else if (mSplitTransitions.isPendingResize(transition)) {
+            mSplitTransitions.applyResizeTransition(transition, info, startTransaction,
+                    finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token,
+                    mSideStage.mRootTaskInfo.token, mMainStage.getSplitDecorManager(),
+                    mSideStage.getSplitDecorManager());
+            return true;
         }
         if (!shouldAnimate) return false;
 
@@ -2004,7 +2076,6 @@
             // Update divider state after animation so that it is still around and positioned
             // properly for the animation itself.
             mSplitLayout.release();
-            mSplitLayout.resetDividerPosition();
             mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
         }
     }
@@ -2067,6 +2138,16 @@
         return true;
     }
 
+    public void goToFullscreenFromSplit() {
+        boolean leftOrTop;
+        if (mSideStage.isFocused()) {
+            leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+        } else {
+            leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
+        }
+        mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+    }
+
     /** Synchronize split-screen state with transition and make appropriate preparations. */
     public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@@ -2281,11 +2362,6 @@
         }
 
         @Override
-        public void onChildTaskEnterPip() {
-            StageCoordinator.this.onStageChildTaskEnterPip();
-        }
-
-        @Override
         public void onRootTaskVanished() {
             reset();
             StageCoordinator.this.onRootTaskVanished();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 6b90eab..8a52c87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -18,7 +18,6 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 
@@ -74,8 +73,6 @@
 
         void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
 
-        void onChildTaskEnterPip();
-
         void onRootTaskVanished();
 
         void onNoLongerSupportMultiWindow();
@@ -257,9 +254,6 @@
                 // Status is managed/synchronized by the transition lifecycle.
                 return;
             }
-            if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
-                mCallbacks.onChildTaskEnterPip();
-            }
             sendStatusChanged();
         } else {
             throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
@@ -288,15 +282,23 @@
         }
     }
 
-    void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t) {
+    void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
+            int offsetY, boolean immediately) {
         if (mSplitDecorManager != null && mRootTaskInfo != null) {
-            mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t);
+            mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX,
+                    offsetY, immediately);
         }
     }
 
     void onResized(SurfaceControl.Transaction t) {
         if (mSplitDecorManager != null) {
-            mSplitDecorManager.onResized(t);
+            mSplitDecorManager.onResized(t, null);
+        }
+    }
+
+    void screenshotIfNeeded(SurfaceControl.Transaction t) {
+        if (mSplitDecorManager != null) {
+            mSplitDecorManager.screenshotIfNeeded(t);
         }
     }
 
@@ -308,6 +310,10 @@
         }
     }
 
+    SplitDecorManager getSplitDecorManager() {
+        return mSplitDecorManager;
+    }
+
     void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
         // Clear overridden bounds and windowing mode to make sure the child task can inherit
         // windowing mode and bounds from split root.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 8bba4404..20da877 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -50,13 +50,17 @@
     private final float mIconStartAlpha;
     private final float mBrandingStartAlpha;
     private final TransactionPool mTransactionPool;
+    // TODO(b/261167708): Clean enter animation code after moving Letterbox code to Shell
+    private final float mRoundedCornerRadius;
 
     private Runnable mFinishCallback;
 
     SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
-            Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish) {
+            Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish,
+            float roundedCornerRadius) {
         mSplashScreenView = view;
         mFirstWindowSurface = leash;
+        mRoundedCornerRadius = roundedCornerRadius;
         if (frame != null) {
             mFirstWindowFrame.set(frame);
         }
@@ -97,7 +101,7 @@
         SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
                 mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
                 mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
-                mAppRevealDuration, this);
+                mAppRevealDuration, this, mRoundedCornerRadius);
     }
 
     private void reset() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index 3098e55..a7e4385 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -63,6 +63,24 @@
 
     /**
      * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
+     * window with rounded corner radius.
+     */
+    static void startAnimations(ViewGroup splashScreenView,
+            SurfaceControl firstWindowSurface, int mainWindowShiftLength,
+            TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+            int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+            int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
+            float roundedCornerRadius) {
+        ValueAnimator animator =
+                createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+                        transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+                        iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+                        animatorListener, roundedCornerRadius);
+        animator.start();
+    }
+
+    /**
+     * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
      * window.
      * @hide
      */
@@ -71,12 +89,10 @@
             TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
             int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
             int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
-        ValueAnimator animator =
-                createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
-                        transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
-                        iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
-                        animatorListener);
-        animator.start();
+        startAnimations(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+                transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+                iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+                animatorListener, 0f /* roundedCornerRadius */);
     }
 
     /**
@@ -87,7 +103,8 @@
             SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
             TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
             int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
-            int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+            int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
+            float roundedCornerRadius) {
         // reveal app
         final float transparentRatio = 0.8f;
         final int globalHeight = splashScreenView.getHeight();
@@ -124,7 +141,7 @@
 
             shiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView,
                     firstWindowSurface, splashScreenView, transactionPool, firstWindowFrame,
-                    mMainWindowShiftLength);
+                    mMainWindowShiftLength, roundedCornerRadius);
         }
 
         ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
@@ -289,8 +306,8 @@
         public ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView,
                                 SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
                                 TransactionPool transactionPool, Rect firstWindowFrame,
-                                int mainWindowShiftLength) {
-            mFromYDelta = fromYDelta;
+                                int mainWindowShiftLength, float roundedCornerRadius) {
+            mFromYDelta = fromYDelta - roundedCornerRadius;
             mToYDelta = toYDelta;
             mOccludeHoleView = occludeHoleView;
             mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 6ce981e..ebb957b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -16,8 +16,10 @@
 
 package com.android.wm.shell.startingsurface;
 
+import static android.content.Context.CONTEXT_RESTRICTED;
 import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
@@ -29,6 +31,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -48,9 +51,11 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
+import android.hardware.display.DisplayManager;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -58,7 +63,9 @@
 import android.util.DisplayMetrics;
 import android.util.Slog;
 import android.view.ContextThemeWrapper;
+import android.view.Display;
 import android.view.SurfaceControl;
+import android.view.WindowManager;
 import android.window.SplashScreenView;
 import android.window.StartingWindowInfo;
 import android.window.StartingWindowInfo.StartingWindowType;
@@ -134,6 +141,144 @@
     }
 
     /**
+     * Help method to create a layout parameters for a window.
+     */
+    static Context createContext(Context initContext, StartingWindowInfo windowInfo,
+            int theme, @StartingWindowInfo.StartingWindowType int suggestType,
+            DisplayManager displayManager) {
+        final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+        final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+                ? windowInfo.targetActivityInfo
+                : taskInfo.topActivityInfo;
+        if (activityInfo == null || activityInfo.packageName == null) {
+            return null;
+        }
+
+        final int displayId = taskInfo.displayId;
+        final int taskId = taskInfo.taskId;
+
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+                "addSplashScreen for package: %s with theme: %s for task: %d, suggestType: %d",
+                activityInfo.packageName, Integer.toHexString(theme), taskId, suggestType);
+        final Display display = displayManager.getDisplay(displayId);
+        if (display == null) {
+            // Can't show splash screen on requested display, so skip showing at all.
+            return null;
+        }
+        Context context = displayId == DEFAULT_DISPLAY
+                ? initContext : initContext.createDisplayContext(display);
+        if (context == null) {
+            return null;
+        }
+        if (theme != context.getThemeResId()) {
+            try {
+                context = context.createPackageContextAsUser(activityInfo.packageName,
+                        CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId));
+                context.setTheme(theme);
+            } catch (PackageManager.NameNotFoundException e) {
+                Slog.w(TAG, "Failed creating package context with package name "
+                        + activityInfo.packageName + " for user " + taskInfo.userId, e);
+                return null;
+            }
+        }
+
+        final Configuration taskConfig = taskInfo.getConfiguration();
+        if (taskConfig.diffPublicOnly(context.getResources().getConfiguration()) != 0) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+                    "addSplashScreen: creating context based on task Configuration %s",
+                    taskConfig);
+            final Context overrideContext = context.createConfigurationContext(taskConfig);
+            overrideContext.setTheme(theme);
+            final TypedArray typedArray = overrideContext.obtainStyledAttributes(
+                    com.android.internal.R.styleable.Window);
+            final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
+            try {
+                if (resId != 0 && overrideContext.getDrawable(resId) != null) {
+                    // We want to use the windowBackground for the override context if it is
+                    // available, otherwise we use the default one to make sure a themed starting
+                    // window is displayed for the app.
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+                            "addSplashScreen: apply overrideConfig %s",
+                            taskConfig);
+                    context = overrideContext;
+                }
+            } catch (Resources.NotFoundException e) {
+                Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: "
+                        + taskId, e);
+                return null;
+            }
+            typedArray.recycle();
+        }
+        return context;
+    }
+
+    /**
+     * Creates the window layout parameters for splashscreen window.
+     */
+    static WindowManager.LayoutParams createLayoutParameters(Context context,
+            StartingWindowInfo windowInfo,
+            @StartingWindowInfo.StartingWindowType int suggestType,
+            CharSequence title, int pixelFormat, IBinder appToken) {
+        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
+        params.setFitInsetsSides(0);
+        params.setFitInsetsTypes(0);
+        params.format = pixelFormat;
+        int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+        final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
+        if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
+            windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+        }
+        if (suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
+            if (a.getBoolean(R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) {
+                windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+            }
+        } else {
+            windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+        }
+        params.layoutInDisplayCutoutMode = a.getInt(
+                R.styleable.Window_windowLayoutInDisplayCutoutMode,
+                params.layoutInDisplayCutoutMode);
+        params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
+        a.recycle();
+
+        final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+        final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+                ? windowInfo.targetActivityInfo
+                : taskInfo.topActivityInfo;
+        final int displayId = taskInfo.displayId;
+        // Assumes it's safe to show starting windows of launched apps while
+        // the keyguard is being hidden. This is okay because starting windows never show
+        // secret information.
+        // TODO(b/113840485): Occluded may not only happen on default display
+        if (displayId == DEFAULT_DISPLAY && windowInfo.isKeyguardOccluded) {
+            windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+        }
+
+        // Force the window flags: this is a fake window, so it is not really
+        // touchable or focusable by the user.  We also add in the ALT_FOCUSABLE_IM
+        // flag because we do know that the next window will take input
+        // focus, so we want to get the IME window up on top of us right away.
+        // Touches will only pass through to the host activity window and will be blocked from
+        // passing to any other windows.
+        windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+        params.flags = windowFlags;
+        params.token = appToken;
+        params.packageName = activityInfo.packageName;
+        params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+
+        if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
+            params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+        }
+
+        params.setTitle("Splash Screen " + title);
+        return params;
+    }
+    /**
      * Create a SplashScreenView object.
      *
      * In order to speed up the splash screen view to show on first frame, preparing the
@@ -248,6 +393,26 @@
         return null;
     }
 
+    /**
+     * Creates a SplashScreenView without read animatable icon and branding image.
+     */
+    SplashScreenView makeSimpleSplashScreenContentView(Context context,
+            StartingWindowInfo info, int themeBGColor) {
+        updateDensity();
+        mTmpAttrs.reset();
+        final ActivityInfo ai = info.targetActivityInfo != null
+                ? info.targetActivityInfo
+                : info.taskInfo.topActivityInfo;
+
+        final SplashViewBuilder builder = new SplashViewBuilder(context, ai);
+        final SplashScreenView view = builder
+                .setWindowBGColor(themeBGColor)
+                .chooseStyle(STARTING_WINDOW_TYPE_SPLASH_SCREEN)
+                .build();
+        view.setNotCopyable();
+        return view;
+    }
+
     private SplashScreenView makeSplashScreenContentView(Context context, StartingWindowInfo info,
             @StartingWindowType int suggestType, Consumer<Runnable> uiThreadInitConsumer) {
         updateDensity();
@@ -263,7 +428,8 @@
         final int themeBGColor = legacyDrawable != null
                 ? getBGColorFromCache(ai, () -> estimateWindowBGColor(legacyDrawable))
                 : getBGColorFromCache(ai, () -> peekWindowBGColor(context, mTmpAttrs));
-        return new StartingWindowViewBuilder(context, ai)
+
+        return new SplashViewBuilder(context, ai)
                 .setWindowBGColor(themeBGColor)
                 .overlayDrawable(legacyDrawable)
                 .chooseStyle(suggestType)
@@ -322,6 +488,14 @@
         private Drawable mSplashScreenIcon = null;
         private Drawable mBrandingImage = null;
         private int mIconBgColor = Color.TRANSPARENT;
+
+        void reset() {
+            mWindowBgResId = 0;
+            mWindowBgColor = Color.TRANSPARENT;
+            mSplashScreenIcon = null;
+            mBrandingImage = null;
+            mIconBgColor = Color.TRANSPARENT;
+        }
     }
 
     /**
@@ -351,7 +525,7 @@
         return appReadyDuration;
     }
 
-    private class StartingWindowViewBuilder {
+    private class SplashViewBuilder {
         private final Context mContext;
         private final ActivityInfo mActivityInfo;
 
@@ -364,27 +538,28 @@
         /** @see #setAllowHandleSolidColor(boolean) **/
         private boolean mAllowHandleSolidColor;
 
-        StartingWindowViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
+        SplashViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
             mContext = context;
             mActivityInfo = aInfo;
         }
 
-        StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) {
+        SplashViewBuilder setWindowBGColor(@ColorInt int background) {
             mThemeColor = background;
             return this;
         }
 
-        StartingWindowViewBuilder overlayDrawable(Drawable overlay) {
+        SplashViewBuilder overlayDrawable(Drawable overlay) {
             mOverlayDrawable = overlay;
             return this;
         }
 
-        StartingWindowViewBuilder chooseStyle(int suggestType) {
+        SplashViewBuilder chooseStyle(int suggestType) {
             mSuggestType = suggestType;
             return this;
         }
 
-        StartingWindowViewBuilder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) {
+        // Set up the UI thread for the View.
+        SplashViewBuilder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) {
             mUiThreadInitTask = uiThreadInitTask;
             return this;
         }
@@ -395,7 +570,7 @@
          * android.window.SplashScreen.OnExitAnimationListener#onSplashScreenExit(SplashScreenView)}
          * callback, effectively copying the {@link SplashScreenView} into the client process.
          */
-        StartingWindowViewBuilder setAllowHandleSolidColor(boolean allowHandleSolidColor) {
+        SplashViewBuilder setAllowHandleSolidColor(boolean allowHandleSolidColor) {
             mAllowHandleSolidColor = allowHandleSolidColor;
             return this;
         }
@@ -993,10 +1168,11 @@
      * Create and play the default exit animation for splash screen view.
      */
     void applyExitAnimation(SplashScreenView view, SurfaceControl leash,
-            Rect frame, Runnable finishCallback, long createTime) {
+            Rect frame, Runnable finishCallback, long createTime, float roundedCornerRadius) {
         final Runnable playAnimation = () -> {
             final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext,
-                    view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback);
+                    view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback,
+                    roundedCornerRadius);
             animation.startAnimations();
         };
         if (view.getIconView() == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 7f6bfd2..e419462 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -62,7 +62,7 @@
      */
     static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor,
             @NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize,
-            boolean loadInDetail, Handler splashscreenWorkerHandler) {
+            boolean loadInDetail, Handler preDrawHandler) {
         Drawable foreground;
         Drawable background = null;
         boolean drawBackground =
@@ -74,13 +74,13 @@
             // If the icon is Adaptive, we already use the icon background.
             drawBackground = false;
             foreground = new ImmobileIconDrawable(foregroundDrawable,
-                    srcIconSize, iconSize, loadInDetail, splashscreenWorkerHandler);
+                    srcIconSize, iconSize, loadInDetail, preDrawHandler);
         } else {
             // Adaptive icon don't handle transparency so we draw the background of the adaptive
             // icon with the same color as the window background color instead of using two layers
             foreground = new ImmobileIconDrawable(
                     new AdaptiveForegroundDrawable(foregroundDrawable),
-                    srcIconSize, iconSize, loadInDetail, splashscreenWorkerHandler);
+                    srcIconSize, iconSize, loadInDetail, preDrawHandler);
         }
 
         if (drawBackground) {
@@ -91,9 +91,9 @@
     }
 
     static Drawable[] makeLegacyIconDrawable(@NonNull Drawable iconDrawable, int srcIconSize,
-            int iconSize, boolean loadInDetail, Handler splashscreenWorkerHandler) {
+            int iconSize, boolean loadInDetail, Handler preDrawHandler) {
         return new Drawable[]{new ImmobileIconDrawable(iconDrawable, srcIconSize, iconSize,
-                loadInDetail, splashscreenWorkerHandler)};
+                loadInDetail, preDrawHandler)};
     }
 
     /**
@@ -107,14 +107,14 @@
         private Bitmap mIconBitmap;
 
         ImmobileIconDrawable(Drawable drawable, int srcIconSize, int iconSize, boolean loadInDetail,
-                Handler splashscreenWorkerHandler) {
+                Handler preDrawHandler) {
             // This icon has lower density, don't scale it.
             if (loadInDetail) {
-                splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, iconSize));
+                preDrawHandler.post(() -> preDrawIcon(drawable, iconSize));
             } else {
                 final float scale = (float) iconSize / srcIconSize;
                 mMatrix.setScale(scale, scale);
-                splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, srcIconSize));
+                preDrawHandler.post(() -> preDrawIcon(drawable, srcIconSize));
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index a0e176c..4f07bfe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.startingsurface;
 
-import static android.content.Context.CONTEXT_RESTRICTED;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -32,8 +31,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
@@ -198,118 +195,21 @@
         if (activityInfo == null || activityInfo.packageName == null) {
             return;
         }
-
-        final int displayId = taskInfo.displayId;
-        final int taskId = taskInfo.taskId;
-
         // replace with the default theme if the application didn't set
         final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
-                "addSplashScreen for package: %s with theme: %s for task: %d, suggestType: %d",
-                activityInfo.packageName, Integer.toHexString(theme), taskId, suggestType);
-        final Display display = getDisplay(displayId);
-        if (display == null) {
-            // Can't show splash screen on requested display, so skip showing at all.
-            return;
-        }
-        Context context = displayId == DEFAULT_DISPLAY
-                ? mContext : mContext.createDisplayContext(display);
+        final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme,
+                suggestType, mDisplayManager);
         if (context == null) {
             return;
         }
-        if (theme != context.getThemeResId()) {
-            try {
-                context = context.createPackageContextAsUser(activityInfo.packageName,
-                        CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId));
-                context.setTheme(theme);
-            } catch (PackageManager.NameNotFoundException e) {
-                Slog.w(TAG, "Failed creating package context with package name "
-                        + activityInfo.packageName + " for user " + taskInfo.userId, e);
-                return;
-            }
-        }
+        final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(
+                context, windowInfo, suggestType, activityInfo.packageName,
+                suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+                        ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, appToken);
 
-        final Configuration taskConfig = taskInfo.getConfiguration();
-        if (taskConfig.diffPublicOnly(context.getResources().getConfiguration()) != 0) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
-                    "addSplashScreen: creating context based on task Configuration %s",
-                    taskConfig);
-            final Context overrideContext = context.createConfigurationContext(taskConfig);
-            overrideContext.setTheme(theme);
-            final TypedArray typedArray = overrideContext.obtainStyledAttributes(
-                    com.android.internal.R.styleable.Window);
-            final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
-            try {
-                if (resId != 0 && overrideContext.getDrawable(resId) != null) {
-                    // We want to use the windowBackground for the override context if it is
-                    // available, otherwise we use the default one to make sure a themed starting
-                    // window is displayed for the app.
-                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
-                            "addSplashScreen: apply overrideConfig %s",
-                            taskConfig);
-                    context = overrideContext;
-                }
-            } catch (Resources.NotFoundException e) {
-                Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: "
-                        + taskId, e);
-                return;
-            }
-            typedArray.recycle();
-        }
-
-        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
-                WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
-        params.setFitInsetsSides(0);
-        params.setFitInsetsTypes(0);
-        params.format = suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
-                ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
-        int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
-                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
-        final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
-        if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
-            windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
-        }
-        if (suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
-            if (a.getBoolean(R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) {
-                windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-            }
-        } else {
-            windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-        }
-        params.layoutInDisplayCutoutMode = a.getInt(
-                R.styleable.Window_windowLayoutInDisplayCutoutMode,
-                params.layoutInDisplayCutoutMode);
-        params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
-        a.recycle();
-
-        // Assumes it's safe to show starting windows of launched apps while
-        // the keyguard is being hidden. This is okay because starting windows never show
-        // secret information.
-        // TODO(b/113840485): Occluded may not only happen on default display
-        if (displayId == DEFAULT_DISPLAY && windowInfo.isKeyguardOccluded) {
-            windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
-        }
-
-        // Force the window flags: this is a fake window, so it is not really
-        // touchable or focusable by the user.  We also add in the ALT_FOCUSABLE_IM
-        // flag because we do know that the next window will take input
-        // focus, so we want to get the IME window up on top of us right away.
-        // Touches will only pass through to the host activity window and will be blocked from
-        // passing to any other windows.
-        windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
-                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-        params.flags = windowFlags;
-        params.token = appToken;
-        params.packageName = activityInfo.packageName;
-        params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-
-        if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
-            params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
-        }
-
-        params.setTitle("Splash Screen " + activityInfo.packageName);
+        final int displayId = taskInfo.displayId;
+        final int taskId = taskInfo.taskId;
+        final Display display = getDisplay(displayId);
 
         // TODO(b/173975965) tracking performance
         // Prepare the splash screen content view on splash screen worker thread in parallel, so the
@@ -430,7 +330,8 @@
         }
 
         @Override
-        public @Nullable SplashScreenView get() {
+        @Nullable
+        public SplashScreenView get() {
             synchronized (this) {
                 while (!mIsViewSet) {
                     try {
@@ -645,7 +546,7 @@
                             mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
                                     removalInfo.windowAnimationLeash, removalInfo.mainFrame,
                                     () -> removeWindowInner(record.mDecorView, true),
-                                    record.mCreateTime);
+                                    record.mCreateTime, removalInfo.roundedCornerRadius);
                         } else {
                             // the SplashScreenView has been copied to client, hide the view to skip
                             // default exit animation
@@ -691,7 +592,7 @@
         private final TaskSnapshotWindow mTaskSnapshotWindow;
         private SplashScreenView mContentView;
         private boolean mSetSplashScreen;
-        private @StartingWindowType int mSuggestType;
+        @StartingWindowType private int mSuggestType;
         private int mBGColor;
         private final long mCreateTime;
         private int mSystemBarAppearance;
@@ -732,7 +633,7 @@
 
         // Reset the system bar color which set by splash screen, make it align to the app.
         private void clearSystemBarColor() {
-            if (mDecorView == null) {
+            if (mDecorView == null || !mDecorView.isAttachedToWindow()) {
                 return;
             }
             if (mDecorView.getLayoutParams() instanceof WindowManager.LayoutParams) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 3929e83..9d6711f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -18,50 +18,16 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.graphics.Color.WHITE;
-import static android.graphics.Color.alpha;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
-import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
-import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
-import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 
-import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
-import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
-import static com.android.internal.policy.DecorView.getNavigationBarRect;
-
 import android.annotation.BinderThread;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.TaskDescription;
-import android.app.ActivityThread;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.GraphicBuffer;
-import android.graphics.Matrix;
 import android.graphics.Paint;
-import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.RectF;
-import android.hardware.HardwareBuffer;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -73,20 +39,14 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
-import android.view.SurfaceSession;
 import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.window.ClientWindowFrames;
+import android.window.SnapshotDrawerUtils;
 import android.window.StartingWindowInfo;
 import android.window.TaskSnapshot;
 
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.DecorView;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.view.BaseIWindow;
 import com.android.wm.shell.common.ShellExecutor;
@@ -100,27 +60,8 @@
  * @hide
  */
 public class TaskSnapshotWindow {
-    /**
-     * When creating the starting window, we use the exact same layout flags such that we end up
-     * with a window with the exact same dimensions etc. However, these flags are not used in layout
-     * and might cause other side effects so we exclude them.
-     */
-    static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE
-            | FLAG_NOT_TOUCHABLE
-            | FLAG_NOT_TOUCH_MODAL
-            | FLAG_ALT_FOCUSABLE_IM
-            | FLAG_NOT_FOCUSABLE
-            | FLAG_HARDWARE_ACCELERATED
-            | FLAG_IGNORE_CHEEK_PRESSES
-            | FLAG_LOCAL_FOCUS_MODE
-            | FLAG_SLIPPERY
-            | FLAG_WATCH_OUTSIDE_TOUCH
-            | FLAG_SPLIT_TOUCH
-            | FLAG_SCALED
-            | FLAG_SECURE;
-
     private static final String TAG = StartingWindowController.TAG;
-    private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
+    private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=";
 
     private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
     /**
@@ -133,25 +74,12 @@
     private final Window mWindow;
     private final Runnable mClearWindowHandler;
     private final ShellExecutor mSplashScreenExecutor;
-    private final SurfaceControl mSurfaceControl;
     private final IWindowSession mSession;
-    private final Rect mTaskBounds;
-    private final Rect mFrame = new Rect();
-    private final Rect mSystemBarInsets = new Rect();
-    private TaskSnapshot mSnapshot;
-    private final RectF mTmpSnapshotSize = new RectF();
-    private final RectF mTmpDstFrame = new RectF();
-    private final CharSequence mTitle;
     private boolean mHasDrawn;
-    private boolean mSizeMismatch;
     private final Paint mBackgroundPaint = new Paint();
     private final int mActivityType;
-    private final int mStatusBarColor;
-    private final SystemBarBackgroundPainter mSystemBarBackgroundPainter;
     private final int mOrientationOnCreation;
-    private final SurfaceControl.Transaction mTransaction;
-    private final Matrix mSnapshotMatrix = new Matrix();
-    private final float[] mTmpFloat9 = new float[9];
+
     private final Runnable mScheduledRunnable = this::removeImmediately;
     private final boolean mHasImeSurface;
 
@@ -163,42 +91,15 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                 "create taskSnapshot surface for task: %d", taskId);
 
-        final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams;
-        final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams;
         final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
-        if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) {
-            Slog.w(TAG, "unable to create taskSnapshot surface for task: " + taskId);
+
+        final WindowManager.LayoutParams layoutParams = SnapshotDrawerUtils.createLayoutParameters(
+                info, TITLE_FORMAT + taskId, TYPE_APPLICATION_STARTING,
+                snapshot.getHardwareBuffer().getFormat(), appToken);
+        if (layoutParams == null) {
+            Slog.e(TAG, "TaskSnapshotWindow no layoutParams");
             return null;
         }
-        final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
-
-        final int appearance = attrs.insetsFlags.appearance;
-        final int windowFlags = attrs.flags;
-        final int windowPrivateFlags = attrs.privateFlags;
-
-        layoutParams.packageName = mainWindowParams.packageName;
-        layoutParams.windowAnimations = mainWindowParams.windowAnimations;
-        layoutParams.dimAmount = mainWindowParams.dimAmount;
-        layoutParams.type = TYPE_APPLICATION_STARTING;
-        layoutParams.format = snapshot.getHardwareBuffer().getFormat();
-        layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
-                | FLAG_NOT_FOCUSABLE
-                | FLAG_NOT_TOUCHABLE;
-        // Setting as trusted overlay to let touches pass through. This is safe because this
-        // window is controlled by the system.
-        layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS)
-                | PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST;
-        layoutParams.token = appToken;
-        layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
-        layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
-        layoutParams.insetsFlags.appearance = appearance;
-        layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior;
-        layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode;
-        layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes());
-        layoutParams.setFitInsetsSides(attrs.getFitInsetsSides());
-        layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
-
-        layoutParams.setTitle(String.format(TITLE_FORMAT, taskId));
 
         final Point taskSize = snapshot.getTaskSize();
         final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y);
@@ -222,9 +123,8 @@
         }
 
         final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
-                surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance,
-                windowFlags, windowPrivateFlags, taskBounds, orientation, activityType,
-                info.requestedVisibleTypes, clearWindowHandler, splashScreenExecutor);
+                snapshot, taskDescription, orientation, activityType,
+                clearWindowHandler, splashScreenExecutor);
         final Window window = snapshotSurface.mWindow;
 
         final InsetsState tmpInsetsState = new InsetsState();
@@ -255,33 +155,25 @@
             snapshotSurface.clearWindowSynced();
         }
 
-        final Rect systemBarInsets = getSystemBarInsets(tmpFrames.frame, topWindowInsetsState);
-        snapshotSurface.setFrames(tmpFrames.frame, systemBarInsets);
-        snapshotSurface.drawSnapshot();
+        SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot,
+                taskBounds, tmpFrames.frame, topWindowInsetsState, true /* releaseAfterDraw */);
+        snapshotSurface.mHasDrawn = true;
+        snapshotSurface.reportDrawn();
+
         return snapshotSurface;
     }
 
-    public TaskSnapshotWindow(SurfaceControl surfaceControl,
-            TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription,
-            int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds,
-            int currentOrientation, int activityType, @InsetsType int requestedVisibleTypes,
-            Runnable clearWindowHandler, ShellExecutor splashScreenExecutor) {
+    public TaskSnapshotWindow(TaskSnapshot snapshot, TaskDescription taskDescription,
+            int currentOrientation, int activityType, Runnable clearWindowHandler,
+            ShellExecutor splashScreenExecutor) {
         mSplashScreenExecutor = splashScreenExecutor;
         mSession = WindowManagerGlobal.getWindowSession();
         mWindow = new Window();
         mWindow.setSession(mSession);
-        mSurfaceControl = surfaceControl;
-        mSnapshot = snapshot;
-        mTitle = title;
         int backgroundColor = taskDescription.getBackgroundColor();
         mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
-        mTaskBounds = taskBounds;
-        mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
-                windowPrivateFlags, appearance, taskDescription, 1f, requestedVisibleTypes);
-        mStatusBarColor = taskDescription.getStatusBarColor();
         mOrientationOnCreation = currentOrientation;
         mActivityType = activityType;
-        mTransaction = new SurfaceControl.Transaction();
         mClearWindowHandler = clearWindowHandler;
         mHasImeSurface = snapshot.hasImeSurface();
     }
@@ -294,23 +186,6 @@
 	return mHasImeSurface;
     }
 
-    /**
-     * Ask system bar background painter to draw status bar background.
-     * @hide
-     */
-    public void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) {
-        mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame,
-                mSystemBarBackgroundPainter.getStatusBarColorViewHeight());
-    }
-
-    /**
-     * Ask system bar background painter to draw navigation bar background.
-     * @hide
-     */
-    public void drawNavigationBarBackground(Canvas c) {
-        mSystemBarBackgroundPainter.drawNavigationBarBackground(c);
-    }
-
     void scheduleRemove(boolean deferRemoveForIme) {
         // Show the latest content as soon as possible for unlocking to home.
         if (mActivityType == ACTIVITY_TYPE_HOME) {
@@ -338,178 +213,6 @@
     }
 
     /**
-     * Set frame size.
-     * @hide
-     */
-    public void setFrames(Rect frame, Rect systemBarInsets) {
-        mFrame.set(frame);
-        mSystemBarInsets.set(systemBarInsets);
-        final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
-        mSizeMismatch = (mFrame.width() != snapshot.getWidth()
-                || mFrame.height() != snapshot.getHeight());
-        mSystemBarBackgroundPainter.setInsets(systemBarInsets);
-    }
-
-    static Rect getSystemBarInsets(Rect frame, InsetsState state) {
-        return state.calculateInsets(frame, WindowInsets.Type.systemBars(),
-                false /* ignoreVisibility */).toRect();
-    }
-
-    private void drawSnapshot() {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
-                "Drawing snapshot surface sizeMismatch=%b", mSizeMismatch);
-        if (mSizeMismatch) {
-            // The dimensions of the buffer and the window don't match, so attaching the buffer
-            // will fail. Better create a child window with the exact dimensions and fill the parent
-            // window with the background color!
-            drawSizeMismatchSnapshot();
-        } else {
-            drawSizeMatchSnapshot();
-        }
-        mHasDrawn = true;
-        reportDrawn();
-
-        // In case window manager leaks us, make sure we don't retain the snapshot.
-        if (mSnapshot.getHardwareBuffer() != null) {
-            mSnapshot.getHardwareBuffer().close();
-        }
-        mSnapshot = null;
-        mSurfaceControl.release();
-    }
-
-    private void drawSizeMatchSnapshot() {
-        mTransaction.setBuffer(mSurfaceControl, mSnapshot.getHardwareBuffer())
-                .setColorSpace(mSurfaceControl, mSnapshot.getColorSpace())
-                .apply();
-    }
-
-    private void drawSizeMismatchSnapshot() {
-        final HardwareBuffer buffer = mSnapshot.getHardwareBuffer();
-        final SurfaceSession session = new SurfaceSession();
-
-        // We consider nearly matched dimensions as there can be rounding errors and the user won't
-        // notice very minute differences from scaling one dimension more than the other
-        final boolean aspectRatioMismatch = Math.abs(
-                ((float) buffer.getWidth() / buffer.getHeight())
-                - ((float) mFrame.width() / mFrame.height())) > 0.01f;
-
-        // Keep a reference to it such that it doesn't get destroyed when finalized.
-        SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session)
-                .setName(mTitle + " - task-snapshot-surface")
-                .setBLASTLayer()
-                .setFormat(buffer.getFormat())
-                .setParent(mSurfaceControl)
-                .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot")
-                .build();
-
-        final Rect frame;
-        // We can just show the surface here as it will still be hidden as the parent is
-        // still hidden.
-        mTransaction.show(childSurfaceControl);
-        if (aspectRatioMismatch) {
-            // Clip off ugly navigation bar.
-            final Rect crop = calculateSnapshotCrop();
-            frame = calculateSnapshotFrame(crop);
-            mTransaction.setWindowCrop(childSurfaceControl, crop);
-            mTransaction.setPosition(childSurfaceControl, frame.left, frame.top);
-            mTmpSnapshotSize.set(crop);
-            mTmpDstFrame.set(frame);
-        } else {
-            frame = null;
-            mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight());
-            mTmpDstFrame.set(mFrame);
-            mTmpDstFrame.offsetTo(0, 0);
-        }
-
-        // Scale the mismatch dimensions to fill the task bounds
-        mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL);
-        mTransaction.setMatrix(childSurfaceControl, mSnapshotMatrix, mTmpFloat9);
-        mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace());
-        mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer());
-
-        if (aspectRatioMismatch) {
-            GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(),
-                    PixelFormat.RGBA_8888,
-                    GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
-                            | GraphicBuffer.USAGE_SW_WRITE_RARELY);
-            // TODO: Support this on HardwareBuffer
-            final Canvas c = background.lockCanvas();
-            drawBackgroundAndBars(c, frame);
-            background.unlockCanvasAndPost(c);
-            mTransaction.setBuffer(mSurfaceControl,
-                    HardwareBuffer.createFromGraphicBuffer(background));
-        }
-        mTransaction.apply();
-        childSurfaceControl.release();
-    }
-
-    /**
-     * Calculates the snapshot crop in snapshot coordinate space.
-     *
-     * @return crop rect in snapshot coordinate space.
-     */
-    public Rect calculateSnapshotCrop() {
-        final Rect rect = new Rect();
-        final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
-        rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight());
-        final Rect insets = mSnapshot.getContentInsets();
-
-        final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
-        final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
-
-        // Let's remove all system decorations except the status bar, but only if the task is at the
-        // very top of the screen.
-        final boolean isTop = mTaskBounds.top == 0 && mFrame.top == 0;
-        rect.inset((int) (insets.left * scaleX),
-                isTop ? 0 : (int) (insets.top * scaleY),
-                (int) (insets.right * scaleX),
-                (int) (insets.bottom * scaleY));
-        return rect;
-    }
-
-    /**
-     * Calculates the snapshot frame in window coordinate space from crop.
-     *
-     * @param crop rect that is in snapshot coordinate space.
-     */
-    public Rect calculateSnapshotFrame(Rect crop) {
-        final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
-        final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
-        final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
-
-        // Rescale the frame from snapshot to window coordinate space
-        final Rect frame = new Rect(0, 0,
-                (int) (crop.width() / scaleX + 0.5f),
-                (int) (crop.height() / scaleY + 0.5f)
-        );
-
-        // However, we also need to make space for the navigation bar on the left side.
-        frame.offset(mSystemBarInsets.left, 0);
-        return frame;
-    }
-
-    /**
-     * Draw status bar and navigation bar background.
-     * @hide
-     */
-    public void drawBackgroundAndBars(Canvas c, Rect frame) {
-        final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight();
-        final boolean fillHorizontally = c.getWidth() > frame.right;
-        final boolean fillVertically = c.getHeight() > frame.bottom;
-        if (fillHorizontally) {
-            c.drawRect(frame.right, alpha(mStatusBarColor) == 0xFF ? statusBarHeight : 0,
-                    c.getWidth(), fillVertically
-                            ? frame.bottom
-                            : c.getHeight(),
-                    mBackgroundPaint);
-        }
-        if (fillVertically) {
-            c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), mBackgroundPaint);
-        }
-        mSystemBarBackgroundPainter.drawDecors(c, frame);
-    }
-
-    /**
      * Clear window from drawer, must be post on main executor.
      */
     private void clearWindowSynced() {
@@ -557,92 +260,4 @@
             });
         }
     }
-
-    /**
-     * Helper class to draw the background of the system bars in regions the task snapshot isn't
-     * filling the window.
-     */
-    static class SystemBarBackgroundPainter {
-        private final Paint mStatusBarPaint = new Paint();
-        private final Paint mNavigationBarPaint = new Paint();
-        private final int mStatusBarColor;
-        private final int mNavigationBarColor;
-        private final int mWindowFlags;
-        private final int mWindowPrivateFlags;
-        private final float mScale;
-        private final @InsetsType int mRequestedVisibleTypes;
-        private final Rect mSystemBarInsets = new Rect();
-
-        SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
-                TaskDescription taskDescription, float scale,
-                @InsetsType int requestedVisibleTypes) {
-            mWindowFlags = windowFlags;
-            mWindowPrivateFlags = windowPrivateFlags;
-            mScale = scale;
-            final Context context = ActivityThread.currentActivityThread().getSystemUiContext();
-            final int semiTransparent = context.getColor(
-                    R.color.system_bar_background_semi_transparent);
-            mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS,
-                    semiTransparent, taskDescription.getStatusBarColor(), appearance,
-                    APPEARANCE_LIGHT_STATUS_BARS,
-                    taskDescription.getEnsureStatusBarContrastWhenTransparent());
-            mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
-                    FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
-                    taskDescription.getNavigationBarColor(), appearance,
-                    APPEARANCE_LIGHT_NAVIGATION_BARS,
-                    taskDescription.getEnsureNavigationBarContrastWhenTransparent()
-                            && context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
-            mStatusBarPaint.setColor(mStatusBarColor);
-            mNavigationBarPaint.setColor(mNavigationBarColor);
-            mRequestedVisibleTypes = requestedVisibleTypes;
-        }
-
-        void setInsets(Rect systemBarInsets) {
-            mSystemBarInsets.set(systemBarInsets);
-        }
-
-        int getStatusBarColorViewHeight() {
-            final boolean forceBarBackground =
-                    (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
-            if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
-                    mRequestedVisibleTypes, mStatusBarColor, mWindowFlags, forceBarBackground)) {
-                return (int) (mSystemBarInsets.top * mScale);
-            } else {
-                return 0;
-            }
-        }
-
-        private boolean isNavigationBarColorViewVisible() {
-            final boolean forceBarBackground =
-                    (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
-            return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
-                    mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags, forceBarBackground);
-        }
-
-        void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
-            drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight());
-            drawNavigationBarBackground(c);
-        }
-
-        void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame,
-                int statusBarHeight) {
-            if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0
-                    && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) {
-                final int rightInset = (int) (mSystemBarInsets.right * mScale);
-                final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0;
-                c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint);
-            }
-        }
-
-        @VisibleForTesting
-        void drawNavigationBarBackground(Canvas c) {
-            final Rect navigationBarRect = new Rect();
-            getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect,
-                    mScale);
-            final boolean visible = isNavigationBarColorViewVisible();
-            if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) {
-                c.drawRect(navigationBarRect, mNavigationBarPaint);
-            }
-        }
-    }
 }
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..63d4a6f 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
@@ -41,11 +41,13 @@
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
 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 +321,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;
 
@@ -383,6 +396,11 @@
                 }
             }
 
+            // The back gesture has animated this change before transition happen, so here we don't
+            // play the animation again.
+            if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+                continue;
+            }
             // Don't animate anything that isn't independent.
             if (!TransitionInfo.isIndependent(change, info)) continue;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4e1fa29..485b400 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -77,10 +77,10 @@
                 if (mRemote.asBinder() != null) {
                     mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
                 }
+                if (sct != null) {
+                    finishTransaction.merge(sct);
+                }
                 mMainExecutor.execute(() -> {
-                    if (sct != null) {
-                        finishTransaction.merge(sct);
-                    }
                     finishCallback.onTransitionFinished(wct, null /* wctCB */);
                 });
             }
@@ -90,7 +90,13 @@
             if (mRemote.asBinder() != null) {
                 mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
             }
-            mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteStartT = RemoteTransitionHandler.copyIfLocal(
+                    startTransaction, mRemote.getRemoteTransition());
+            final TransitionInfo remoteInfo =
+                    remoteStartT == startTransaction ? info : info.localRemoteCopy();
+            mRemote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
             // assume that remote will apply the start transaction.
             startTransaction.clear();
         } catch (RemoteException e) {
@@ -124,7 +130,13 @@
             }
         };
         try {
-            mRemote.getRemoteTransition().mergeAnimation(transition, info, t, mergeTarget, cb);
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteT =
+                    RemoteTransitionHandler.copyIfLocal(t, mRemote.getRemoteTransition());
+            final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+            mRemote.getRemoteTransition().mergeAnimation(
+                    transition, remoteInfo, remoteT, mergeTarget, cb);
         } catch (RemoteException e) {
             Log.e(Transitions.TAG, "Error merging remote transition.", e);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 9469529..b4e0584 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -120,10 +121,10 @@
             public void onTransitionFinished(WindowContainerTransaction wct,
                     SurfaceControl.Transaction sct) {
                 unhandleDeath(remote.asBinder(), finishCallback);
+                if (sct != null) {
+                    finishTransaction.merge(sct);
+                }
                 mMainExecutor.execute(() -> {
-                    if (sct != null) {
-                        finishTransaction.merge(sct);
-                    }
                     mRequestedRemotes.remove(transition);
                     finishCallback.onTransitionFinished(wct, null /* wctCB */);
                 });
@@ -131,8 +132,14 @@
         };
         Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
         try {
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteStartT =
+                    copyIfLocal(startTransaction, remote.getRemoteTransition());
+            final TransitionInfo remoteInfo =
+                    remoteStartT == startTransaction ? info : info.localRemoteCopy();
             handleDeath(remote.asBinder(), finishCallback);
-            remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+            remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
             // assume that remote will apply the start transaction.
             startTransaction.clear();
         } catch (RemoteException e) {
@@ -145,6 +152,28 @@
         return true;
     }
 
+    static SurfaceControl.Transaction copyIfLocal(SurfaceControl.Transaction t,
+            IRemoteTransition remote) {
+        // We care more about parceling than local (though they should be the same); so, use
+        // queryLocalInterface since that's what Binder uses to decide if it needs to parcel.
+        if (remote.asBinder().queryLocalInterface(IRemoteTransition.DESCRIPTOR) == null) {
+            // No local interface, so binder itself will parcel and thus we don't need to.
+            return t;
+        }
+        // Binder won't be parceling; however, the remotes assume they have their own native
+        // objects (and don't know if caller is local or not), so we need to make a COPY here so
+        // that the remote can clean it up without clearing the original transaction.
+        // Since there's no direct `copy` for Transaction, we have to parcel/unparcel instead.
+        final Parcel p = Parcel.obtain();
+        try {
+            t.writeToParcel(p, 0);
+            p.setDataPosition(0);
+            return SurfaceControl.Transaction.CREATOR.createFromParcel(p);
+        } finally {
+            p.recycle();
+        }
+    }
+
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -175,7 +204,11 @@
             }
         };
         try {
-            remote.mergeAnimation(transition, info, t, mergeTarget, cb);
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote);
+            final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+            remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb);
         } catch (RemoteException e) {
             Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index e338221..6af81f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -322,6 +322,7 @@
                         .setPixelFormat(PixelFormat.RGBA_8888)
                         .setChildrenOnly(true)
                         .setAllowProtected(true)
+                        .setCaptureSecureLayers(true)
                         .build();
         final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
                 ScreenCapture.captureLayers(captureArgs);
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..c6935c0 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
@@ -24,7 +24,6 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
-import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -175,6 +174,9 @@
     }
 
     private void onInit() {
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mOrganizer.shareTransactionQueue();
+        }
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
                 this::createExternalInterface, this);
 
@@ -333,14 +335,26 @@
         boolean isOpening = isOpeningType(info.getType());
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
-            if ((change.getFlags() & TransitionInfo.FLAG_IS_SYSTEM_WINDOW) != 0) {
+            if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) {
                 // Currently system windows are controlled by WindowState, so don't change their
-                // surfaces. Otherwise their window tokens could be hidden unexpectedly.
+                // surfaces. Otherwise their surfaces could be hidden or cropped unexpectedly.
+                // This includes Wallpaper (always z-ordered at bottom) and IME (associated with
+                // app), because there may not be a transition associated with their visibility
+                // changes, and currently they don't need transition animation.
                 continue;
             }
             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) {
@@ -363,16 +377,7 @@
                     finishT.setAlpha(leash, 1.f);
                 }
             } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
-                // Wallpaper/IME are anomalies: their visibility is tied to other WindowStates.
-                // As a result, we actually can't hide their WindowTokens because there may not be a
-                // transition associated with them becoming visible again. Fortunately, since
-                // wallpapers are always z-ordered to the back, we don't have to worry about it
-                // flickering to the front during reparenting. Similarly, the IME is reparented to
-                // the associated app, so its visibility is coupled. So, an explicit hide is not
-                // needed visually anyways.
-                if ((change.getFlags() & (FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD)) == 0) {
-                    finishT.hide(leash);
-                }
+                finishT.hide(leash);
             }
         }
     }
@@ -498,6 +503,7 @@
             // Treat this as an abort since we are bypassing any merge logic and effectively
             // finishing immediately.
             onAbort(transitionToken);
+            releaseSurfaces(info);
             return;
         }
 
@@ -602,6 +608,15 @@
         onFinish(transition, wct, wctCB, false /* abort */);
     }
 
+    /**
+     * Releases an info's animation-surfaces. These don't need to persist and we need to release
+     * them asap so that SF can free memory sooner.
+     */
+    private void releaseSurfaces(@Nullable TransitionInfo info) {
+        if (info == null) return;
+        info.releaseAnimSurfaces();
+    }
+
     private void onFinish(IBinder transition,
             @Nullable WindowContainerTransaction wct,
             @Nullable WindowContainerTransactionCallback wctCB,
@@ -640,6 +655,11 @@
         }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                 "Transition animation finished (abort=%b), notifying core %s", abort, transition);
+        if (active.mStartT != null) {
+            // Applied by now, so close immediately. Do not set to null yet, though, since nullness
+            // is used later to disambiguate malformed transitions.
+            active.mStartT.close();
+        }
         // Merge all relevant transactions together
         SurfaceControl.Transaction fullFinish = active.mFinishT;
         for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
@@ -659,12 +679,14 @@
             fullFinish.apply();
         }
         // Now perform all the finishes.
+        releaseSurfaces(active.mInfo);
         mActiveTransitions.remove(activeIdx);
         mOrganizer.finishTransition(transition, wct, wctCB);
         while (activeIdx < mActiveTransitions.size()) {
             if (!mActiveTransitions.get(activeIdx).mMerged) break;
             ActiveTransition merged = mActiveTransitions.remove(activeIdx);
             mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
+            releaseSurfaces(merged.mInfo);
         }
         // sift through aborted transitions
         while (mActiveTransitions.size() > activeIdx
@@ -677,8 +699,9 @@
             }
             mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
             for (int i = 0; i < mObservers.size(); ++i) {
-                mObservers.get(i).onTransitionFinished(active.mToken, true);
+                mObservers.get(i).onTransitionFinished(aborted.mToken, true);
             }
+            releaseSurfaces(aborted.mInfo);
         }
         if (mActiveTransitions.size() <= activeIdx) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
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..5655402 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;
@@ -39,6 +44,8 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.Nullable;
+
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -48,6 +55,9 @@
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 import com.android.wm.shell.transition.Transitions;
 
+import java.util.Optional;
+import java.util.function.Supplier;
+
 /**
  * View model for the window decoration with a caption and shadows. Works with
  * {@link CaptionWindowDecoration}.
@@ -55,6 +65,8 @@
 
 public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
     private static final String TAG = "CaptionViewModel";
+    private final CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
+    private final Supplier<InputManager> mInputManagerSupplier;
     private final ActivityTaskManager mActivityTaskManager;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Context mContext;
@@ -63,9 +75,14 @@
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
     private FreeformTaskTransitionStarter mTransitionStarter;
-    private DesktopModeController mDesktopModeController;
+    private Optional<DesktopModeController> mDesktopModeController;
+    private boolean mTransitionDragActive;
+
+    private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
 
     private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
+    private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
+    private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory();
 
     public CaptionWindowDecorViewModel(
             Context context,
@@ -74,7 +91,30 @@
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             SyncTransactionQueue syncQueue,
-            DesktopModeController desktopModeController) {
+            Optional<DesktopModeController> desktopModeController) {
+        this(
+                context,
+                mainHandler,
+                mainChoreographer,
+                taskOrganizer,
+                displayController,
+                syncQueue,
+                desktopModeController,
+                new CaptionWindowDecoration.Factory(),
+                InputManager::getInstance);
+    }
+
+    public CaptionWindowDecorViewModel(
+            Context context,
+            Handler mainHandler,
+            Choreographer mainChoreographer,
+            ShellTaskOrganizer taskOrganizer,
+            DisplayController displayController,
+            SyncTransactionQueue syncQueue,
+            Optional<DesktopModeController> desktopModeController,
+            CaptionWindowDecoration.Factory captionWindowDecorFactory,
+            Supplier<InputManager> inputManagerSupplier) {
+
         mContext = context;
         mMainHandler = mainHandler;
         mMainChoreographer = mainChoreographer;
@@ -83,6 +123,13 @@
         mDisplayController = displayController;
         mSyncQueue = syncQueue;
         mDesktopModeController = desktopModeController;
+
+        mCaptionWindowDecorFactory = captionWindowDecorFactory;
+        mInputManagerSupplier = inputManagerSupplier;
+    }
+
+    void setEventReceiverFactory(EventReceiverFactory eventReceiverFactory) {
+        mEventReceiverFactory = eventReceiverFactory;
     }
 
     @Override
@@ -91,42 +138,55 @@
     }
 
     @Override
-    public boolean createWindowDecoration(
+    public boolean onTaskOpening(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
         if (!shouldShowWindowDecor(taskInfo)) return false;
-        final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
-                mContext,
-                mDisplayController,
-                mTaskOrganizer,
-                taskInfo,
-                taskSurface,
-                mMainHandler,
-                mMainChoreographer,
-                mSyncQueue);
-        mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
-
-        TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
-        CaptionTouchEventListener touchEventListener =
-                new CaptionTouchEventListener(taskInfo, taskPositioner);
-        windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
-        windowDecoration.setDragResizeCallback(taskPositioner);
-        setupWindowDecorationForTransition(taskInfo, startT, finishT);
+        createWindowDecoration(taskInfo, taskSurface, startT, finishT);
         return true;
     }
 
     @Override
     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
         final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+
         if (decoration == null) return;
 
+        int oldDisplayId = decoration.mDisplay.getDisplayId();
+        if (taskInfo.displayId != oldDisplayId) {
+            removeTaskFromEventReceiver(oldDisplayId);
+            incrementEventReceiverTasks(taskInfo.displayId);
+        }
+
         decoration.relayout(taskInfo);
     }
 
     @Override
-    public void setupWindowDecorationForTransition(
+    public void onTaskChanging(
+            RunningTaskInfo taskInfo,
+            SurfaceControl taskSurface,
+            SurfaceControl.Transaction startT,
+            SurfaceControl.Transaction finishT) {
+        final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+
+        if (!shouldShowWindowDecor(taskInfo)) {
+            if (decoration != null) {
+                destroyWindowDecoration(taskInfo);
+            }
+            return;
+        }
+
+        if (decoration == null) {
+            createWindowDecoration(taskInfo, taskSurface, startT, finishT);
+        } else {
+            decoration.relayout(taskInfo, startT, finishT);
+        }
+    }
+
+    @Override
+    public void onTaskClosing(
             RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
@@ -143,6 +203,11 @@
         if (decoration == null) return;
 
         decoration.close();
+        int displayId = taskInfo.displayId;
+        if (mEventReceiversByDisplay.contains(displayId)) {
+            EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+            removeTaskFromEventReceiver(displayId);
+        }
     }
 
     private class CaptionTouchEventListener implements
@@ -151,20 +216,23 @@
         private final int mTaskId;
         private final WindowContainerToken mTaskToken;
         private final DragResizeCallback mDragResizeCallback;
+        private final DragDetector mDragDetector;
 
         private int mDragPointerId = -1;
-        private boolean mDragActive = false;
 
         private CaptionTouchEventListener(
                 RunningTaskInfo taskInfo,
-                DragResizeCallback dragResizeCallback) {
+                DragResizeCallback dragResizeCallback,
+                DragDetector dragDetector) {
             mTaskId = taskInfo.taskId;
             mTaskToken = taskInfo.token;
             mDragResizeCallback = dragResizeCallback;
+            mDragDetector = dragDetector;
         }
 
         @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,8 +244,18 @@
                 }
             } 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.ifPresent(c -> c.setDesktopModeActive(true));
+                decoration.closeHandleMenu();
+            } else if (id == R.id.fullscreen_button) {
+                mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
+                decoration.closeHandleMenu();
+                decoration.setButtonVisibility();
             }
         }
+
         private void injectBackKey() {
             sendBackEvent(KeyEvent.ACTION_DOWN);
             sendBackEvent(KeyEvent.ACTION_UP);
@@ -199,19 +277,21 @@
 
         @Override
         public boolean onTouch(View v, MotionEvent e) {
+            boolean isDrag = false;
             int id = v.getId();
             if (id != R.id.caption_handle && id != R.id.caption) {
                 return false;
             }
-            if (id == R.id.caption_handle || mDragActive) {
+            if (id == R.id.caption_handle) {
+                isDrag = mDragDetector.detectDragEvent(e);
                 handleEventForMove(e);
             }
             if (e.getAction() != MotionEvent.ACTION_DOWN) {
-                return false;
+                return isDrag;
             }
             RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
             if (taskInfo.isFocused) {
-                return false;
+                return isDrag;
             }
             WindowContainerTransaction wct = new WindowContainerTransaction();
             wct.reorder(mTaskToken, true /* onTop */);
@@ -219,20 +299,24 @@
             return true;
         }
 
+        /**
+         * @param e {@link MotionEvent} to process
+         * @return {@code true} if a drag is happening; or {@code false} if it is not
+         */
         private void handleEventForMove(MotionEvent e) {
             RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
-            int windowingMode =  mDesktopModeController
-                    .getDisplayAreaWindowingMode(taskInfo.displayId);
-            if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
+            if (mDesktopModeController.isPresent()
+                    && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
+                    == WINDOWING_MODE_FULLSCREEN) {
                 return;
             }
             switch (e.getActionMasked()) {
-                case MotionEvent.ACTION_DOWN:
-                    mDragActive = true;
-                    mDragPointerId  = e.getPointerId(0);
+                case MotionEvent.ACTION_DOWN: {
+                    mDragPointerId = e.getPointerId(0);
                     mDragResizeCallback.onDragResizeStart(
                             0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
                     break;
+                }
                 case MotionEvent.ACTION_MOVE: {
                     int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     mDragResizeCallback.onDragResizeMove(
@@ -241,15 +325,14 @@
                 }
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
-                    mDragActive = false;
                     int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
                             .stableInsets().top;
                     mDragResizeCallback.onDragResizeEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     if (e.getRawY(dragPointerIdx) <= statusBarHeight
-                            && windowingMode == WINDOWING_MODE_FREEFORM) {
-                        mDesktopModeController.setDesktopModeActive(false);
+                            && DesktopModeStatus.isActive(mContext)) {
+                        mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
                     }
                     break;
                 }
@@ -257,6 +340,181 @@
         }
     }
 
+    // InputEventReceiver to listen for touch input outside of caption bounds
+    class EventReceiver extends InputEventReceiver {
+        private InputMonitor mInputMonitor;
+        private int mTasksOnDisplay;
+        EventReceiver(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
+            super(channel, looper);
+            mInputMonitor = inputMonitor;
+            mTasksOnDisplay = 1;
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event) {
+            boolean handled = false;
+            if (event instanceof MotionEvent) {
+                handled = true;
+                CaptionWindowDecorViewModel.this
+                        .handleReceivedMotionEvent((MotionEvent) event, mInputMonitor);
+            }
+            finishInputEvent(event, handled);
+        }
+
+        @Override
+        public void dispose() {
+            if (mInputMonitor != null) {
+                mInputMonitor.dispose();
+                mInputMonitor = null;
+            }
+            super.dispose();
+        }
+
+        private void incrementTaskNumber() {
+            mTasksOnDisplay++;
+        }
+
+        private void decrementTaskNumber() {
+            mTasksOnDisplay--;
+        }
+
+        private int getTasksOnDisplay() {
+            return mTasksOnDisplay;
+        }
+    }
+
+    /**
+     * Check if an EventReceiver exists on a particular display.
+     * If it does, increment its task count. Otherwise, create one for that display.
+     * @param displayId the display to check against
+     */
+    private void incrementEventReceiverTasks(int displayId) {
+        if (mEventReceiversByDisplay.contains(displayId)) {
+            EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+            eventReceiver.incrementTaskNumber();
+        } else {
+            createInputChannel(displayId);
+        }
+    }
+
+    // If all tasks on this display are gone, we don't need to monitor its input.
+    private void removeTaskFromEventReceiver(int displayId) {
+        if (!mEventReceiversByDisplay.contains(displayId)) return;
+        EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+        if (eventReceiver == null) return;
+        eventReceiver.decrementTaskNumber();
+        if (eventReceiver.getTasksOnDisplay() == 0) {
+            disposeInputChannel(displayId);
+        }
+    }
+
+    class EventReceiverFactory {
+        EventReceiver create(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
+            return new EventReceiver(inputMonitor, channel, looper);
+        }
+    }
+
+    /**
+     * Handle MotionEvents relevant to focused task's caption that don't directly touch it
+     *
+     * @param ev the {@link MotionEvent} received by {@link EventReceiver}
+     */
+    private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
+        if (!DesktopModeStatus.isActive(mContext)) {
+            handleCaptionThroughStatusBar(ev);
+        }
+        handleEventOutsideFocusedCaption(ev);
+        // Prevent status bar from reacting to a caption drag.
+        if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
+            inputMonitor.pilferPointers();
+        }
+    }
+
+    // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
+    private void handleEventOutsideFocusedCaption(MotionEvent ev) {
+        int action = ev.getActionMasked();
+        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            CaptionWindowDecoration focusedDecor = getFocusedDecor();
+            if (focusedDecor == null) {
+                return;
+            }
+
+            if (!mTransitionDragActive) {
+                focusedDecor.closeHandleMenuIfNeeded(ev);
+            }
+        }
+    }
+
+
+    /**
+     * Perform caption actions if not able to through normal means.
+     * Turn on desktop mode if handle is dragged below status bar.
+     */
+    private void handleCaptionThroughStatusBar(MotionEvent ev) {
+        switch (ev.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN: {
+                // Begin drag through status bar if applicable.
+                CaptionWindowDecoration focusedDecor = getFocusedDecor();
+                if (focusedDecor != null && !DesktopModeStatus.isActive(mContext)
+                        && focusedDecor.checkTouchEventInHandle(ev)) {
+                    mTransitionDragActive = true;
+                }
+                break;
+            }
+            case MotionEvent.ACTION_UP: {
+                CaptionWindowDecoration focusedDecor = getFocusedDecor();
+                if (focusedDecor == null) {
+                    mTransitionDragActive = false;
+                    return;
+                }
+                if (mTransitionDragActive) {
+                    mTransitionDragActive = false;
+                    int statusBarHeight = mDisplayController
+                            .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
+                    if (ev.getY() > statusBarHeight) {
+                        mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
+                        return;
+                    }
+                }
+                focusedDecor.checkClickEvent(ev);
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL: {
+                mTransitionDragActive = false;
+            }
+        }
+    }
+
+    @Nullable
+    private CaptionWindowDecoration getFocusedDecor() {
+        int size = mWindowDecorByTaskId.size();
+        CaptionWindowDecoration focusedDecor = null;
+        for (int i = 0; i < size; i++) {
+            CaptionWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+            if (decor != null && decor.isFocused()) {
+                focusedDecor = decor;
+                break;
+            }
+        }
+        return focusedDecor;
+    }
+
+    private void createInputChannel(int displayId) {
+        InputManager inputManager = mInputManagerSupplier.get();
+        InputMonitor inputMonitor =
+                inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
+        EventReceiver eventReceiver = mEventReceiverFactory.create(
+                inputMonitor, inputMonitor.getInputChannel(), Looper.myLooper());
+        mEventReceiversByDisplay.put(displayId, eventReceiver);
+    }
+
+    private void disposeInputChannel(int displayId) {
+        EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId);
+        if (eventReceiver != null) {
+            eventReceiver.dispose();
+        }
+    }
+
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
         return DesktopModeStatus.IS_SUPPORTED
@@ -264,4 +522,44 @@
                 && mDisplayController.getDisplayContext(taskInfo.displayId)
                 .getResources().getConfiguration().smallestScreenWidthDp >= 600;
     }
+
+    private void createWindowDecoration(
+            ActivityManager.RunningTaskInfo taskInfo,
+            SurfaceControl taskSurface,
+            SurfaceControl.Transaction startT,
+            SurfaceControl.Transaction finishT) {
+        CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+        if (oldDecoration != null) {
+            // close the old decoration if it exists to avoid two window decorations being added
+            oldDecoration.close();
+        }
+        final CaptionWindowDecoration windowDecoration =
+                mCaptionWindowDecorFactory.create(
+                        mContext,
+                        mDisplayController,
+                        mTaskOrganizer,
+                        taskInfo,
+                        taskSurface,
+                        mMainHandler,
+                        mMainChoreographer,
+                        mSyncQueue);
+        mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
+
+        TaskPositioner taskPositioner =
+                new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener);
+        CaptionTouchEventListener touchEventListener =
+                new CaptionTouchEventListener(
+                        taskInfo, taskPositioner, windowDecoration.getDragDetector());
+        windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
+        windowDecoration.setDragResizeCallback(taskPositioner);
+        windowDecoration.relayout(taskInfo, startT, finishT);
+        incrementEventReceiverTasks(taskInfo.displayId);
+    }
+
+    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..037ca20 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,15 @@
 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.PointF;
+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;
@@ -37,7 +42,8 @@
 
 /**
  * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
- * {@link CaptionWindowDecorViewModel}. The caption bar contains a handle, back button, and close button.
+ * {@link CaptionWindowDecorViewModel}. The caption bar contains a handle, back button, and close
+ * button.
  *
  * The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
  */
@@ -58,6 +64,10 @@
 
     private boolean mDesktopActive;
 
+    private DragDetector mDragDetector;
+
+    private AdditionalWindow mHandleMenu;
+
     CaptionWindowDecoration(
             Context context,
             DisplayController displayController,
@@ -73,6 +83,7 @@
         mChoreographer = choreographer;
         mSyncQueue = syncQueue;
         mDesktopActive = DesktopModeStatus.isActive(mContext);
+        mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
     }
 
     void setCaptionListeners(
@@ -86,6 +97,10 @@
         mDragResizeCallback = dragResizeCallback;
     }
 
+    DragDetector getDragDetector() {
+        return mDragDetector;
+    }
+
     @Override
     void relayout(ActivityManager.RunningTaskInfo taskInfo) {
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
@@ -123,7 +138,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 +165,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();
@@ -155,15 +182,17 @@
         if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
             closeDragResizeListener();
             mDragResizeListener = new DragResizeInputListener(
-                        mContext,
-                        mHandler,
-                        mChoreographer,
-                        mDisplay.getDisplayId(),
-                        mDecorationContainerSurface,
-                        mDragResizeCallback);
+                    mContext,
+                    mHandler,
+                    mChoreographer,
+                    mDisplay.getDisplayId(),
+                    mDecorationContainerSurface,
+                    mDragResizeCallback);
         }
 
         int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
+        mDragDetector.setTouchSlop(touchSlop);
+
         int resize_handle = mResult.mRootView.getResources()
                 .getDimensionPixelSize(R.dimen.freeform_resize_handle);
         int resize_corner = mResult.mRootView.getResources()
@@ -184,9 +213,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,15 +236,16 @@
      */
     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();
     }
 
     /**
      * Sets the visibility of buttons and color of caption based on desktop mode status
-     *
      */
-    public void setButtonVisibility() {
+    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 +263,10 @@
         caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
     }
 
+    boolean isHandleMenuActive() {
+        return mHandleMenu != null;
+    }
+
     private void closeDragResizeListener() {
         if (mDragResizeListener == null) {
             return;
@@ -228,9 +275,155 @@
         mDragResizeListener = null;
     }
 
+    /**
+     * Create and display handle menu window
+     */
+    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
+     */
+    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
+     */
+    void closeHandleMenuIfNeeded(MotionEvent ev) {
+        if (isHandleMenuActive()) {
+            if (!checkEventInCaptionView(ev, R.id.caption)) {
+                closeHandleMenu();
+            }
+        }
+    }
+
+    boolean isFocused() {
+        return mTaskInfo.isFocused;
+    }
+
+    /**
+     * Offset the coordinates of a {@link MotionEvent} to be in the same coordinate space as caption
+     *
+     * @param ev the {@link MotionEvent} to offset
+     * @return the point of the input in local space
+     */
+    private PointF offsetCaptionLocation(MotionEvent ev) {
+        PointF result = new PointF(ev.getX(), ev.getY());
+        Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
+                .positionInParent;
+        result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
+        result.offset(-positionInParent.x, -positionInParent.y);
+        return result;
+    }
+
+    /**
+     * Determine if a passed MotionEvent is in a view in caption
+     *
+     * @param ev       the {@link MotionEvent} to check
+     * @param layoutId the id of the view
+     * @return {@code true} if event is inside the specified view, {@code false} if not
+     */
+    private boolean checkEventInCaptionView(MotionEvent ev, int layoutId) {
+        if (mResult.mRootView == null) return false;
+        PointF inputPoint = offsetCaptionLocation(ev);
+        View view = mResult.mRootView.findViewById(layoutId);
+        return view != null && view.pointInView(inputPoint.x, inputPoint.y, 0);
+    }
+
+    boolean checkTouchEventInHandle(MotionEvent ev) {
+        if (isHandleMenuActive()) return false;
+        return checkEventInCaptionView(ev, R.id.caption_handle);
+    }
+
+    /**
+     * Check a passed MotionEvent if a click has occurred on any button on this caption
+     * Note this should only be called when a regular onClick is not possible
+     * (i.e. the button was clicked through status bar layer)
+     *
+     * @param ev the MotionEvent to compare
+     */
+    void checkClickEvent(MotionEvent ev) {
+        if (mResult.mRootView == null) return;
+        View caption = mResult.mRootView.findViewById(R.id.caption);
+        PointF inputPoint = offsetCaptionLocation(ev);
+        if (!isHandleMenuActive()) {
+            View handle = caption.findViewById(R.id.caption_handle);
+            clickIfPointInView(inputPoint, handle);
+        } else {
+            View menu = mHandleMenu.mWindowViewHost.getView();
+            View fullscreen = menu.findViewById(R.id.fullscreen_button);
+            if (clickIfPointInView(inputPoint, fullscreen)) return;
+            View desktop = menu.findViewById(R.id.desktop_button);
+            if (clickIfPointInView(inputPoint, desktop)) return;
+            View split = menu.findViewById(R.id.split_screen_button);
+            if (clickIfPointInView(inputPoint, split)) return;
+            View more = menu.findViewById(R.id.more_button);
+            clickIfPointInView(inputPoint, more);
+        }
+    }
+
+    private boolean clickIfPointInView(PointF inputPoint, View v) {
+        if (v.pointInView(inputPoint.x - v.getLeft(), inputPoint.y, 0)) {
+            mOnCaptionButtonClickListener.onClick(v);
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public void close() {
         closeDragResizeListener();
+        closeHandleMenu();
         super.close();
     }
+
+    static class Factory {
+
+        CaptionWindowDecoration create(
+                Context context,
+                DisplayController displayController,
+                ShellTaskOrganizer taskOrganizer,
+                ActivityManager.RunningTaskInfo taskInfo,
+                SurfaceControl taskSurface,
+                Handler handler,
+                Choreographer choreographer,
+                SyncTransactionQueue syncQueue) {
+            return new CaptionWindowDecoration(
+                    context,
+                    displayController,
+                    taskOrganizer,
+                    taskInfo,
+                    taskSurface,
+                    handler,
+                    choreographer,
+                    syncQueue);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
new file mode 100644
index 0000000..0abe8ab
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -0,0 +1,87 @@
+/*
+ * 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.windowdecor;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+/**
+ * A detector for touch inputs that differentiates between drag and click inputs.
+ * All touch events must be passed through this class to track a drag event.
+ */
+public class DragDetector {
+    private int mTouchSlop;
+    private PointF mInputDownPoint;
+    private boolean mIsDragEvent;
+    private int mDragPointerId;
+    public DragDetector(int touchSlop) {
+        mTouchSlop = touchSlop;
+        mInputDownPoint = new PointF();
+        mIsDragEvent = false;
+        mDragPointerId = -1;
+    }
+
+    /**
+     * Determine if {@link MotionEvent} is part of a drag event.
+     * @return {@code true} if this is a drag event, {@code false} if not
+     */
+    public boolean detectDragEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case ACTION_DOWN: {
+                mDragPointerId = ev.getPointerId(0);
+                float rawX = ev.getRawX(0);
+                float rawY = ev.getRawY(0);
+                mInputDownPoint.set(rawX, rawY);
+                return false;
+            }
+            case ACTION_MOVE: {
+                if (!mIsDragEvent) {
+                    int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
+                    float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
+                    float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
+                    if (Math.hypot(dx, dy) > mTouchSlop) {
+                        mIsDragEvent = true;
+                    }
+                }
+                return mIsDragEvent;
+            }
+            case ACTION_UP: {
+                boolean result = mIsDragEvent;
+                mIsDragEvent = false;
+                mInputDownPoint.set(0, 0);
+                mDragPointerId = -1;
+                return result;
+            }
+            case ACTION_CANCEL: {
+                mIsDragEvent = false;
+                mInputDownPoint.set(0, 0);
+                mDragPointerId = -1;
+                return false;
+            }
+        }
+        return mIsDragEvent;
+    }
+
+    public void setTouchSlop(int touchSlop) {
+        mTouchSlop = touchSlop;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index b9f16b6..d3f1332 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -22,7 +22,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import android.content.Context;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.input.InputManager;
@@ -38,6 +37,7 @@
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.SurfaceControl;
+import android.view.ViewConfiguration;
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.view.BaseIWindow;
@@ -76,7 +76,7 @@
     private Rect mRightBottomCornerBounds;
 
     private int mDragPointerId = -1;
-    private int mTouchSlop;
+    private DragDetector mDragDetector;
 
     DragResizeInputListener(
             Context context,
@@ -115,6 +115,7 @@
         mInputEventReceiver = new TaskResizeInputEventReceiver(
                 mInputChannel, mHandler, mChoreographer);
         mCallback = callback;
+        mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
     }
 
     /**
@@ -146,7 +147,7 @@
         mHeight = height;
         mResizeHandleThickness = resizeHandleThickness;
         mCornerSize = cornerSize;
-        mTouchSlop = touchSlop;
+        mDragDetector.setTouchSlop(touchSlop);
 
         Region touchRegion = new Region();
         final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
@@ -228,7 +229,6 @@
         private boolean mConsumeBatchEventScheduled;
         private boolean mShouldHandleEvents;
         private boolean mDragging;
-        private final PointF mActionDownPoint = new PointF();
 
         private TaskResizeInputEventReceiver(
                 InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -276,7 +276,9 @@
             // Check if this is a touch event vs mouse event.
             // Touch events are tracked in four corners. Other events are tracked in resize edges.
             boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
-
+            if (isTouch) {
+                mDragging = mDragDetector.detectDragEvent(e);
+            }
             switch (e.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN: {
                     float x = e.getX(0);
@@ -290,7 +292,6 @@
                         mDragPointerId = e.getPointerId(0);
                         float rawX = e.getRawX(0);
                         float rawY = e.getRawY(0);
-                        mActionDownPoint.set(rawX, rawY);
                         int ctrlType = calculateCtrlType(isTouch, x, y);
                         mCallback.onDragResizeStart(ctrlType, rawX, rawY);
                         result = true;
@@ -304,14 +305,7 @@
                     int dragPointerIndex = e.findPointerIndex(mDragPointerId);
                     float rawX = e.getRawX(dragPointerIndex);
                     float rawY = e.getRawY(dragPointerIndex);
-                    if (isTouch) {
-                        // Check for touch slop for touch events
-                        float dx = rawX - mActionDownPoint.x;
-                        float dy = rawY - mActionDownPoint.y;
-                        if (!mDragging && Math.hypot(dx, dy) > mTouchSlop) {
-                            mDragging = true;
-                        }
-                    } else {
+                    if (!isTouch) {
                         // For all other types allow immediate dragging.
                         mDragging = true;
                     }
@@ -323,14 +317,13 @@
                 }
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
-                    if (mDragging) {
+                    if (mShouldHandleEvents && mDragging) {
                         int dragPointerIndex = e.findPointerIndex(mDragPointerId);
                         mCallback.onDragResizeEnd(
                                 e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
                     }
                     mDragging = false;
                     mShouldHandleEvents = false;
-                    mActionDownPoint.set(0, 0);
                     mDragPointerId = -1;
                     result = true;
                     break;
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..a49a300 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
@@ -40,16 +40,29 @@
     private final Rect mTaskBoundsAtDragStart = new Rect();
     private final PointF mResizeStartPoint = new PointF();
     private final Rect mResizeTaskBounds = new Rect();
+    // Whether the |dragResizing| hint should be sent with the next bounds change WCT.
+    // Used to optimized fluid resizing of freeform tasks.
+    private boolean mPendingDragResizeHint = false;
 
     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) {
+        if (ctrlType != CTRL_TYPE_UNDEFINED) {
+            // The task is being resized, send the |dragResizing| hint to core with the first
+            // bounds-change wct.
+            mPendingDragResizeHint = true;
+        }
+
+        mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
         mCtrlType = ctrlType;
 
         mTaskBoundsAtDragStart.set(
@@ -59,19 +72,31 @@
 
     @Override
     public void onDragResizeMove(float x, float y) {
-        changeBounds(x, y);
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        if (changeBounds(wct, x, y)) {
+            if (mPendingDragResizeHint) {
+                // This is the first bounds change since drag resize operation started.
+                wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
+                mPendingDragResizeHint = false;
+            }
+            mTaskOrganizer.applyTransaction(wct);
+        }
     }
 
     @Override
     public void onDragResizeEnd(float x, float y) {
-        changeBounds(x, y);
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */);
+        changeBounds(wct, x, y);
+        mTaskOrganizer.applyTransaction(wct);
 
         mCtrlType = 0;
         mTaskBoundsAtDragStart.setEmpty();
         mResizeStartPoint.set(0, 0);
+        mPendingDragResizeHint = false;
     }
 
-    private void changeBounds(float x, float y) {
+    private boolean changeBounds(WindowContainerTransaction wct, float x, float y) {
         float deltaX = x - mResizeStartPoint.x;
         mResizeTaskBounds.set(mTaskBoundsAtDragStart);
         if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
@@ -92,9 +117,17 @@
         }
 
         if (!mResizeTaskBounds.isEmpty()) {
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
             wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
-            mTaskOrganizer.applyTransaction(wct);
+            return true;
         }
+        return false;
+    }
+
+    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/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index d7f71c8..907977c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -28,7 +28,6 @@
  * servers.
  */
 public interface WindowDecorViewModel {
-
     /**
      * Sets the transition starter that starts freeform task transitions.
      *
@@ -37,16 +36,16 @@
     void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter);
 
     /**
-     * Creates a window decoration for the given task.
-     * Can be {@code null} for Fullscreen tasks but not Freeform ones.
+     * Creates a window decoration for the given task. Can be {@code null} for Fullscreen tasks but
+     * not Freeform ones.
      *
-     * @param taskInfo the initial task info of the task
+     * @param taskInfo    the initial task info of the task
      * @param taskSurface the surface of the task
-     * @param startT the start transaction to be applied before the transition
-     * @param finishT the finish transaction to restore states after the transition
-     * @return the window decoration object
+     * @param startT      the start transaction to be applied before the transition
+     * @param finishT     the finish transaction to restore states after the transition
+     * @return {@code true} if window decoration was created, {@code false} otherwise
      */
-    boolean createWindowDecoration(
+    boolean onTaskOpening(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
             SurfaceControl.Transaction startT,
@@ -54,7 +53,7 @@
 
     /**
      * Notifies a task info update on the given task, with the window decoration created previously
-     * for this task by {@link #createWindowDecoration}.
+     * for this task by {@link #onTaskOpening}.
      *
      * @param taskInfo the new task info of the task
      */
@@ -62,12 +61,29 @@
 
     /**
      * Notifies a transition is about to start about the given task to give the window decoration a
-     * chance to prepare for this transition.
+     * chance to prepare for this transition. Unlike {@link #onTaskInfoChanged}, this method creates
+     * a window decoration if one does not exist but is required.
      *
-     * @param startT the start transaction to be applied before the transition
-     * @param finishT the finish transaction to restore states after the transition
+     * @param taskInfo    the initial task info of the task
+     * @param taskSurface the surface of the task
+     * @param startT      the start transaction to be applied before the transition
+     * @param finishT     the finish transaction to restore states after the transition
      */
-    void setupWindowDecorationForTransition(
+    void onTaskChanging(
+            ActivityManager.RunningTaskInfo taskInfo,
+            SurfaceControl taskSurface,
+            SurfaceControl.Transaction startT,
+            SurfaceControl.Transaction finishT);
+
+    /**
+     * Notifies that the given task is about to close to give the window decoration a chance to
+     * prepare for this transition.
+     *
+     * @param taskInfo the initial task info of the task
+     * @param startT   the start transaction to be applied before the transition
+     * @param finishT  the finish transaction to restore states after the transition
+     */
+    void onTaskClosing(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT);
@@ -78,4 +94,4 @@
      * @param taskInfo the info of the task
      */
     void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
-}
+}
\ No newline at end of file
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..9215496 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
@@ -250,16 +251,15 @@
         }
 
         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;
+        final int captionWidth = params.mCaptionWidthId == Resources.ID_NULL
+                ? taskBounds.width()
+                : loadDimensionPixelSize(resources, params.mCaptionWidthId);
 
         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 +292,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 +302,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 +326,7 @@
         return true;
     }
 
-    private void releaseViews() {
+    void releaseViews() {
         if (mViewHost != null) {
             mViewHost.release();
             mViewHost = null;
@@ -369,20 +369,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 +435,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 +445,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 +460,9 @@
             mOutsetBottomId = Resources.ID_NULL;
             mOutsetLeftId = Resources.ID_NULL;
             mOutsetRightId = Resources.ID_NULL;
+
+            mCaptionX = 0;
+            mCaptionY = 0;
         }
     }
 
@@ -419,10 +470,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 +487,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/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 2d6e8f5..27fc381a 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -13,10 +13,14 @@
         <option name="run-command" value="cmd window tracing level all" />
         <!-- set WM tracing to frame (avoid incomplete states) -->
         <option name="run-command" value="cmd window tracing frame" />
+        <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+        <option name="run-command" value="pm disable com.google.android.internal.betterbug" />
         <!-- ensure lock screen mode is swipe -->
         <option name="run-command" value="locksettings set-disabled false" />
         <!-- restart launcher to activate TAPL -->
         <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
+        <!-- Ensure output directory is empty at the start -->
+        <option name="run-command" value="rm -rf /sdcard/flicker" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
@@ -34,4 +38,4 @@
         <option name="collect-on-run-ended-only" value="true" />
         <option name="clean-up" value="true" />
     </metrics_collector>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 6370df4..8465678 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -20,10 +20,10 @@
 import android.platform.test.annotations.Presubmit
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.FlickerBuilderProvider
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.junit.FlickerBuilderProvider
 import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
 import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
@@ -45,12 +45,12 @@
 abstract class BaseTest
 @JvmOverloads
 constructor(
-    protected val testSpec: FlickerTestParameter,
+    protected val flicker: FlickerTest,
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
     protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
 ) {
     init {
-        testSpec.setIsTablet(
+        flicker.scenario.setIsTablet(
             WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false)
                 .currentState
                 .wmState
@@ -68,13 +68,13 @@
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            setup { testSpec.setIsTablet(wmHelper.currentState.wmState.isTablet) }
+            setup { flicker.scenario.setIsTablet(wmHelper.currentState.wmState.isTablet) }
             transition()
         }
     }
 
     /** Checks that all parts of the screen are covered during the transition */
-    @Presubmit @Test open fun entireScreenCovered() = testSpec.entireScreenCovered()
+    @Presubmit @Test open fun entireScreenCovered() = flicker.entireScreenCovered()
 
     /**
      * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
@@ -82,8 +82,8 @@
     @Presubmit
     @Test
     open fun navBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.navBarLayerIsVisibleAtStartAndEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerIsVisibleAtStartAndEnd()
     }
 
     /**
@@ -93,8 +93,8 @@
     @Presubmit
     @Test
     open fun navBarLayerPositionAtStartAndEnd() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.navBarLayerPositionAtStartAndEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerPositionAtStartAndEnd()
     }
 
     /**
@@ -105,8 +105,8 @@
     @Presubmit
     @Test
     open fun navBarWindowIsAlwaysVisible() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.navBarWindowIsAlwaysVisible()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarWindowIsAlwaysVisible()
     }
 
     /**
@@ -115,8 +115,8 @@
     @Presubmit
     @Test
     open fun taskBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeTrue(testSpec.isTablet)
-        testSpec.taskBarLayerIsVisibleAtStartAndEnd()
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarLayerIsVisibleAtStartAndEnd()
     }
 
     /**
@@ -127,8 +127,8 @@
     @Presubmit
     @Test
     open fun taskBarWindowIsAlwaysVisible() {
-        Assume.assumeTrue(testSpec.isTablet)
-        testSpec.taskBarWindowIsAlwaysVisible()
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarWindowIsAlwaysVisible()
     }
 
     /**
@@ -137,8 +137,7 @@
      */
     @Presubmit
     @Test
-    open fun statusBarLayerIsVisibleAtStartAndEnd() =
-        testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+    open fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
 
     /**
      * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
@@ -146,7 +145,7 @@
      */
     @Presubmit
     @Test
-    open fun statusBarLayerPositionAtStartAndEnd() = testSpec.statusBarLayerPositionAtStartAndEnd()
+    open fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
 
     /**
      * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
@@ -154,7 +153,7 @@
      */
     @Presubmit
     @Test
-    open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+    open fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
 
     /**
      * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
@@ -163,7 +162,7 @@
     @Presubmit
     @Test
     open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
+        flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
     }
 
     /**
@@ -173,6 +172,6 @@
     @Presubmit
     @Test
     open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
+        flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
     }
 }
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..5186914 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -18,23 +18,23 @@
 
 package com.android.wm.shell.flicker
 
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.traces.layers.LayerTraceEntrySubject
 import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
 import com.android.server.wm.traces.common.IComponentMatcher
 import com.android.server.wm.traces.common.region.Region
+import com.android.server.wm.traces.common.service.PlatformConsts
 
-fun FlickerTestParameter.appPairsDividerIsVisibleAtEnd() {
+fun FlickerTest.appPairsDividerIsVisibleAtEnd() {
     assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTestParameter.appPairsDividerIsInvisibleAtEnd() {
+fun FlickerTest.appPairsDividerIsInvisibleAtEnd() {
     assertLayersEnd { this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTestParameter.appPairsDividerBecomesVisible() {
+fun FlickerTest.appPairsDividerBecomesVisible() {
     assertLayers {
         this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
             .then()
@@ -42,17 +42,26 @@
     }
 }
 
-fun FlickerTestParameter.splitScreenEntered(
+fun FlickerTest.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)
@@ -60,7 +69,7 @@
     splitScreenDividerIsVisibleAtEnd()
 }
 
-fun FlickerTestParameter.splitScreenDismissed(
+fun FlickerTest.splitScreenDismissed(
     component1: IComponentMatcher,
     component2: IComponentMatcher,
     toHome: Boolean
@@ -78,27 +87,27 @@
     splitScreenDividerIsInvisibleAtEnd()
 }
 
-fun FlickerTestParameter.splitScreenDividerIsVisibleAtStart() {
+fun FlickerTest.splitScreenDividerIsVisibleAtStart() {
     assertLayersStart { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTestParameter.splitScreenDividerIsVisibleAtEnd() {
+fun FlickerTest.splitScreenDividerIsVisibleAtEnd() {
     assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTestParameter.splitScreenDividerIsInvisibleAtStart() {
+fun FlickerTest.splitScreenDividerIsInvisibleAtStart() {
     assertLayersStart { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTestParameter.splitScreenDividerIsInvisibleAtEnd() {
+fun FlickerTest.splitScreenDividerIsInvisibleAtEnd() {
     assertLayersEnd { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTestParameter.splitScreenDividerBecomesVisible() {
+fun FlickerTest.splitScreenDividerBecomesVisible() {
     layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 }
 
-fun FlickerTestParameter.splitScreenDividerBecomesInvisible() {
+fun FlickerTest.splitScreenDividerBecomesInvisible() {
     assertLayers {
         this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
             .then()
@@ -106,23 +115,23 @@
     }
 }
 
-fun FlickerTestParameter.layerBecomesVisible(component: IComponentMatcher) {
+fun FlickerTest.layerBecomesVisible(component: IComponentMatcher) {
     assertLayers { this.isInvisible(component).then().isVisible(component) }
 }
 
-fun FlickerTestParameter.layerBecomesInvisible(component: IComponentMatcher) {
+fun FlickerTest.layerBecomesInvisible(component: IComponentMatcher) {
     assertLayers { this.isVisible(component).then().isInvisible(component) }
 }
 
-fun FlickerTestParameter.layerIsVisibleAtEnd(component: IComponentMatcher) {
+fun FlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) {
     assertLayersEnd { this.isVisible(component) }
 }
 
-fun FlickerTestParameter.layerKeepVisible(component: IComponentMatcher) {
+fun FlickerTest.layerKeepVisible(component: IComponentMatcher) {
     assertLayers { this.isVisible(component) }
 }
 
-fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
+fun FlickerTest.splitAppLayerBoundsBecomesVisible(
     component: IComponentMatcher,
     landscapePosLeft: Boolean,
     portraitPosTop: Boolean
@@ -136,12 +145,12 @@
                 component,
                 landscapePosLeft,
                 portraitPosTop,
-                endRotation
+                scenario.endRotation
             )
     }
 }
 
-fun FlickerTestParameter.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) {
+fun FlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) {
     assertLayers {
         this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
             .then()
@@ -152,7 +161,7 @@
     }
 }
 
-fun FlickerTestParameter.splitAppLayerBoundsBecomesInvisible(
+fun FlickerTest.splitAppLayerBoundsBecomesInvisible(
     component: IComponentMatcher,
     landscapePosLeft: Boolean,
     portraitPosTop: Boolean
@@ -162,7 +171,7 @@
                 component,
                 landscapePosLeft,
                 portraitPosTop,
-                endRotation
+                scenario.endRotation
             )
             .then()
             .isVisible(component, true)
@@ -171,27 +180,37 @@
     }
 }
 
-fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd(
+fun FlickerTest.splitAppLayerBoundsIsVisibleAtEnd(
     component: IComponentMatcher,
     landscapePosLeft: Boolean,
     portraitPosTop: Boolean
 ) {
     assertLayersEnd {
-        splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, endRotation)
+        splitAppLayerBoundsSnapToDivider(
+            component,
+            landscapePosLeft,
+            portraitPosTop,
+            scenario.endRotation
+        )
     }
 }
 
-fun FlickerTestParameter.splitAppLayerBoundsKeepVisible(
+fun FlickerTest.splitAppLayerBoundsKeepVisible(
     component: IComponentMatcher,
     landscapePosLeft: Boolean,
     portraitPosTop: Boolean
 ) {
     assertLayers {
-        splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, endRotation)
+        splitAppLayerBoundsSnapToDivider(
+            component,
+            landscapePosLeft,
+            portraitPosTop,
+            scenario.endRotation
+        )
     }
 }
 
-fun FlickerTestParameter.splitAppLayerBoundsChanges(
+fun FlickerTest.splitAppLayerBoundsChanges(
     component: IComponentMatcher,
     landscapePosLeft: Boolean,
     portraitPosTop: Boolean
@@ -202,14 +221,14 @@
                 component,
                 landscapePosLeft,
                 portraitPosTop,
-                endRotation
+                scenario.endRotation
             )
         } else {
             this.splitAppLayerBoundsSnapToDivider(
                     component,
                     landscapePosLeft,
                     portraitPosTop,
-                    endRotation
+                    scenario.endRotation
                 )
                 .then()
                 .isInvisible(component)
@@ -218,7 +237,7 @@
                     component,
                     landscapePosLeft,
                     portraitPosTop,
-                    endRotation
+                    scenario.endRotation
                 )
         }
     }
@@ -228,7 +247,7 @@
     component: IComponentMatcher,
     landscapePosLeft: Boolean,
     portraitPosTop: Boolean,
-    rotation: Int
+    rotation: PlatformConsts.Rotation
 ): LayersTraceSubject {
     return invoke("splitAppLayerBoundsSnapToDivider") {
         it.splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, rotation)
@@ -239,7 +258,7 @@
     component: IComponentMatcher,
     landscapePosLeft: Boolean,
     portraitPosTop: Boolean,
-    rotation: Int
+    rotation: PlatformConsts.Rotation
 ): LayerTraceEntrySubject {
     val displayBounds = WindowUtils.getDisplayBounds(rotation)
     return invoke {
@@ -283,7 +302,7 @@
     }
 }
 
-fun FlickerTestParameter.appWindowBecomesVisible(component: IComponentMatcher) {
+fun FlickerTest.appWindowBecomesVisible(component: IComponentMatcher) {
     assertWm {
         this.isAppWindowInvisible(component)
             .then()
@@ -295,35 +314,39 @@
     }
 }
 
-fun FlickerTestParameter.appWindowBecomesInvisible(component: IComponentMatcher) {
+fun FlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) {
     assertWm { this.isAppWindowVisible(component).then().isAppWindowInvisible(component) }
 }
 
-fun FlickerTestParameter.appWindowIsVisibleAtStart(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) {
     assertWmStart { this.isAppWindowVisible(component) }
 }
 
-fun FlickerTestParameter.appWindowIsVisibleAtEnd(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) {
     assertWmEnd { this.isAppWindowVisible(component) }
 }
 
-fun FlickerTestParameter.appWindowIsInvisibleAtStart(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) {
     assertWmStart { this.isAppWindowInvisible(component) }
 }
 
-fun FlickerTestParameter.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
     assertWmEnd { this.isAppWindowInvisible(component) }
 }
 
-fun FlickerTestParameter.appWindowKeepVisible(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) {
+    assertWmStart { this.notContains(component) }
+}
+
+fun FlickerTest.appWindowKeepVisible(component: IComponentMatcher) {
     assertWm { this.isAppWindowVisible(component) }
 }
 
-fun FlickerTestParameter.dockedStackDividerIsVisibleAtEnd() {
+fun FlickerTest.dockedStackDividerIsVisibleAtEnd() {
     assertLayersEnd { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTestParameter.dockedStackDividerBecomesVisible() {
+fun FlickerTest.dockedStackDividerBecomesVisible() {
     assertLayers {
         this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
             .then()
@@ -331,7 +354,7 @@
     }
 }
 
-fun FlickerTestParameter.dockedStackDividerBecomesInvisible() {
+fun FlickerTest.dockedStackDividerBecomesInvisible() {
     assertLayers {
         this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
             .then()
@@ -339,12 +362,12 @@
     }
 }
 
-fun FlickerTestParameter.dockedStackDividerNotExistsAtEnd() {
+fun FlickerTest.dockedStackDividerNotExistsAtEnd() {
     assertLayersEnd { this.notContains(DOCKED_STACK_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTestParameter.appPairsPrimaryBoundsIsVisibleAtEnd(
-    rotation: Int,
+fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd(
+    rotation: PlatformConsts.Rotation,
     primaryComponent: IComponentMatcher
 ) {
     assertLayersEnd {
@@ -353,8 +376,8 @@
     }
 }
 
-fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisibleAtEnd(
-    rotation: Int,
+fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd(
+    rotation: PlatformConsts.Rotation,
     primaryComponent: IComponentMatcher
 ) {
     assertLayersEnd {
@@ -363,8 +386,8 @@
     }
 }
 
-fun FlickerTestParameter.appPairsSecondaryBoundsIsVisibleAtEnd(
-    rotation: Int,
+fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd(
+    rotation: PlatformConsts.Rotation,
     secondaryComponent: IComponentMatcher
 ) {
     assertLayersEnd {
@@ -373,8 +396,8 @@
     }
 }
 
-fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd(
-    rotation: Int,
+fun FlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd(
+    rotation: PlatformConsts.Rotation,
     secondaryComponent: IComponentMatcher
 ) {
     assertLayersEnd {
@@ -383,38 +406,38 @@
     }
 }
 
-fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region {
+fun getPrimaryRegion(dividerRegion: Region, rotation: PlatformConsts.Rotation): Region {
     val displayBounds = WindowUtils.getDisplayBounds(rotation)
-    return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
-        Region.from(
-            0,
-            0,
-            displayBounds.bounds.right,
-            dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
-        )
-    } else {
+    return if (rotation.isRotated()) {
         Region.from(
             0,
             0,
             dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
             displayBounds.bounds.bottom
         )
+    } else {
+        Region.from(
+            0,
+            0,
+            displayBounds.bounds.right,
+            dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
+        )
     }
 }
 
-fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region {
+fun getSecondaryRegion(dividerRegion: Region, rotation: PlatformConsts.Rotation): Region {
     val displayBounds = WindowUtils.getDisplayBounds(rotation)
-    return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
+    return if (rotation.isRotated()) {
         Region.from(
+            dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset,
             0,
-            dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
             displayBounds.bounds.right,
             displayBounds.bounds.bottom
         )
     } else {
         Region.from(
-            dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset,
             0,
+            dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
             displayBounds.bounds.right,
             displayBounds.bounds.bottom
         )
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/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 0fc2004..996b677 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -21,21 +21,21 @@
 import android.content.Context
 import android.content.pm.PackageManager
 import android.os.ServiceManager
-import android.view.Surface
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiObject2
 import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.Flicker
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.IFlickerTestData
 import com.android.server.wm.flicker.helpers.LaunchBubbleHelper
 import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
+import com.android.server.wm.traces.common.service.PlatformConsts
 import com.android.wm.shell.flicker.BaseTest
 import org.junit.runners.Parameterized
 
 /** Base configurations for Bubble flicker tests */
-abstract class BaseBubbleScreen(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) {
 
     protected val context: Context = instrumentation.context
     protected val testApp = LaunchBubbleHelper(instrumentation)
@@ -79,17 +79,18 @@
         }
     }
 
-    protected fun Flicker.waitAndGetAddBubbleBtn(): UiObject2? =
+    protected fun IFlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? =
         device.wait(Until.findObject(By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
-    protected fun Flicker.waitAndGetCancelAllBtn(): UiObject2? =
+    protected fun IFlickerTestData.waitAndGetCancelAllBtn(): UiObject2? =
         device.wait(Until.findObject(By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
 
         const val FIND_OBJECT_TIMEOUT = 2000L
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
index ab72117..7fc12f0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
@@ -25,9 +25,9 @@
 import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -45,7 +45,7 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class DismissBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
 
     private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
     private val displaySize = DisplayMetrics()
@@ -72,7 +72,7 @@
     @Presubmit
     @Test
     open fun testAppIsAlwaysVisible() {
-        testSpec.assertLayers { this.isVisible(testApp) }
+        flicker.assertLayers { this.isVisible(testApp) }
     }
 
     /** {@inheritDoc} */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
index 226eab8..0cda626 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
@@ -20,9 +20,9 @@
 import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -42,7 +42,7 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class ExpandBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -64,6 +64,6 @@
     @Presubmit
     @Test
     open fun testAppIsAlwaysVisible() {
-        testSpec.assertLayers { this.isVisible(testApp) }
+        flicker.assertLayers { this.isVisible(testApp) }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
index 47167b8..04b1bdd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -23,9 +23,9 @@
 import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -43,7 +43,7 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class LaunchBubbleFromLockScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+class LaunchBubbleFromLockScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -88,7 +88,7 @@
     @FlakyTest(bugId = 242088970)
     @Test
     fun testAppIsVisibleAtEnd() {
-        testSpec.assertLayersEnd { this.isVisible(testApp) }
+        flicker.assertLayersEnd { this.isVisible(testApp) }
     }
 
     /** {@inheritDoc} */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
index b865999..9b4e39c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
@@ -19,9 +19,9 @@
 import android.platform.test.annotations.RequiresDevice
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -40,7 +40,7 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class LaunchBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -59,6 +59,6 @@
 
     @Test
     open fun testAppIsAlwaysVisible() {
-        testSpec.assertLayers { this.isVisible(testApp) }
+        flicker.assertLayers { this.isVisible(testApp) }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
index bf4d7d4..b3a2ad3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
@@ -22,10 +22,10 @@
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiObject2
 import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
@@ -45,7 +45,7 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class MultiBubblesScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
 
     @Before
     open fun before() {
@@ -87,6 +87,6 @@
     @Presubmit
     @Test
     open fun testAppIsAlwaysVisible() {
-        testSpec.assertLayers { this.isVisible(testApp) }
+        flicker.assertLayers { this.isVisible(testApp) }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
index 57adeab..191f4fa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
@@ -18,9 +18,9 @@
 
 import android.platform.test.annotations.FlakyTest
 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.FlickerTest
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.Assume
 import org.junit.Before
 import org.junit.runner.RunWith
@@ -30,8 +30,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FlakyTest(bugId = 217777115)
-class MultiBubblesScreenShellTransit(testSpec: FlickerTestParameter) :
-    MultiBubblesScreen(testSpec) {
+class MultiBubblesScreenShellTransit(flicker: FlickerTest) : MultiBubblesScreen(flicker) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
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..5e898e8 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
@@ -18,15 +18,15 @@
 
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
 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.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -59,7 +59,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 238367575)
-class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
+class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipTest(flicker) {
     /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -73,17 +73,17 @@
                 // close gracefully so that onActivityUnpinned() can be called before force exit
                 pipApp.closePipWindow(wmHelper)
 
-                setRotation(Surface.ROTATION_0)
+                setRotation(PlatformConsts.Rotation.ROTATION_0)
                 RemoveAllTasksButHomeRule.removeAllTasksButHome()
                 pipApp.exit(wmHelper)
             }
             transitions { tapl.goHome() }
         }
 
-    @FlakyTest
+    @FlakyTest(bugId = 256863309)
     @Test
     override fun pipLayerReduces() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
             pipLayerList.zipWithNext { previous, current ->
                 current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
@@ -96,8 +96,8 @@
     @Test
     fun pipLayerMovesTowardsRightBottomCorner() {
         // in gestural nav the swipe makes PiP first go upwards
-        Assume.assumeFalse(testSpec.isGesturalNavigation)
-        testSpec.assertLayers {
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+        flicker.assertLayers {
             val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
             // Pip animates towards the right bottom corner, but because it is being resized at the
             // same time, it is possible it shrinks first quickly below the default position and get
@@ -108,19 +108,11 @@
         }
     }
 
-    @FlakyTest(bugId = 239807171)
-    @Test
-    override fun pipAppLayerAlwaysVisible() = super.pipAppLayerAlwaysVisible()
-
-    @FlakyTest(bugId = 239807171)
-    @Test
-    override fun pipLayerRemainInsideVisibleBounds() = super.pipLayerRemainInsideVisibleBounds()
-
     @Presubmit
     @Test
     override fun focusChanges() {
         // in gestural nav the focus goes to different activity on swipe up
-        Assume.assumeFalse(testSpec.isGesturalNavigation)
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
         super.focusChanges()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index c8aa6d2..79feeaa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -17,14 +17,14 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
 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.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -56,7 +56,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
+class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTest(flicker) {
     /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -68,7 +68,7 @@
                 pipApp.enableEnterPipOnUserLeaveHint()
             }
             teardown {
-                setRotation(Surface.ROTATION_0)
+                setRotation(PlatformConsts.Rotation.ROTATION_0)
                 RemoveAllTasksButHomeRule.removeAllTasksButHome()
                 pipApp.exit(wmHelper)
             }
@@ -78,10 +78,10 @@
     @Presubmit
     @Test
     override fun pipAppLayerAlwaysVisible() {
-        if (!testSpec.isGesturalNavigation) super.pipAppLayerAlwaysVisible()
+        if (!flicker.scenario.isGesturalNavigation) super.pipAppLayerAlwaysVisible()
         else {
             // pip layer in gesture nav will disappear during transition
-            testSpec.assertLayers {
+            flicker.assertLayers {
                 this.isVisible(pipApp).then().isInvisible(pipApp).then().isVisible(pipApp)
             }
         }
@@ -91,7 +91,7 @@
     @Test
     override fun pipLayerReduces() {
         // in gestural nav the pip enters through alpha animation
-        Assume.assumeFalse(testSpec.isGesturalNavigation)
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
         super.pipLayerReduces()
     }
 
@@ -99,7 +99,7 @@
     @Test
     override fun focusChanges() {
         // in gestural nav the focus goes to different activity on swipe up
-        Assume.assumeFalse(testSpec.isGesturalNavigation)
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
         super.focusChanges()
     }
 
@@ -112,11 +112,11 @@
     @Presubmit
     @Test
     override fun pipLayerRemainInsideVisibleBounds() {
-        if (!testSpec.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds()
+        if (!flicker.scenario.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds()
         else {
             // pip layer in gesture nav will disappear during transition
-            testSpec.assertLayersStart { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
-            testSpec.assertLayersEnd { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
+            flicker.assertLayersStart { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
+            flicker.assertLayersEnd { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 2b629e7..1a76142 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -17,16 +17,16 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -57,7 +57,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class EnterPipTest(flicker: FlickerTest) : PipTransition(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -68,7 +68,7 @@
                 pipApp.launchViaIntent(wmHelper)
             }
             teardown {
-                setRotation(Surface.ROTATION_0)
+                setRotation(PlatformConsts.Rotation.ROTATION_0)
                 RemoveAllTasksButHomeRule.removeAllTasksButHome()
                 pipApp.exit(wmHelper)
             }
@@ -79,16 +79,14 @@
     @Presubmit
     @Test
     open fun pipAppWindowAlwaysVisible() {
-        testSpec.assertWm { this.isAppWindowVisible(pipApp) }
+        flicker.assertWm { this.isAppWindowVisible(pipApp) }
     }
 
-    /**
-     * Checks [pipApp] layer remains visible throughout the animation
-     */
+    /** Checks [pipApp] layer remains visible throughout the animation */
     @Presubmit
     @Test
     open fun pipAppLayerAlwaysVisible() {
-        testSpec.assertLayers { this.isVisible(pipApp) }
+        flicker.assertLayers { this.isVisible(pipApp) }
     }
 
     /**
@@ -98,7 +96,7 @@
     @Presubmit
     @Test
     fun pipWindowRemainInsideVisibleBounds() {
-        testSpec.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+        flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
     /**
@@ -108,14 +106,14 @@
     @Presubmit
     @Test
     open fun pipLayerRemainInsideVisibleBounds() {
-        testSpec.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+        flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
     /** Checks that the visible region of [pipApp] always reduces during the animation */
     @Presubmit
     @Test
     open fun pipLayerReduces() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
             pipLayerList.zipWithNext { previous, current ->
                 current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
@@ -127,7 +125,7 @@
     @Presubmit
     @Test
     fun pipWindowBecomesPinned() {
-        testSpec.assertWm {
+        flicker.assertWm {
             invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp) }
                 .then()
                 .invoke("pipWindowIsPinned") { it.isPinned(pipApp) }
@@ -138,7 +136,7 @@
     @Presubmit
     @Test
     fun launcherLayerBecomesVisible() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             isInvisible(ComponentNameMatcher.LAUNCHER)
                 .then()
                 .isVisible(ComponentNameMatcher.LAUNCHER)
@@ -152,21 +150,22 @@
     @Presubmit
     @Test
     open fun focusChanges() {
-        testSpec.assertEventLog { this.focusChanges(pipApp.`package`, "NexusLauncherActivity") }
+        flicker.assertEventLog { this.focusChanges(pipApp.`package`, "NexusLauncherActivity") }
     }
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+         * and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index b4594de..2b90243 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -19,22 +19,22 @@
 import android.app.Activity
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
 import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
 import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
 import org.junit.Assume
@@ -51,32 +51,31 @@
  * To run this test: `atest WMShellFlickerTests:EnterPipToOtherOrientationTest`
  *
  * Actions:
+ * ```
  *     Launch [testApp] on a fixed portrait orientation
  *     Launch [pipApp] on a fixed landscape orientation
  *     Broadcast action [ACTION_ENTER_PIP] to enter pip mode
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited [PipTransition]
  *     2. Part of the test setup occurs automatically via
  *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
  *        including configuring navigation mode, initial orientation and ensuring no
  *        apps are running before setup
+ * ```
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipToOtherOrientationTest(
-    testSpec: FlickerTestParameter
-) : PipTransition(testSpec) {
+class EnterPipToOtherOrientationTest(flicker: FlickerTest) : PipTransition(flicker) {
     private val testApp = FixedOrientationAppHelper(instrumentation)
-    private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
-    private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
+    private val startingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_90)
+    private val endingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_0)
 
-    /**
-     * Defines the transition used to run the test
-     */
+    /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             setup {
@@ -85,19 +84,18 @@
 
                 // Launch a portrait only app on the fullscreen stack
                 testApp.launchViaIntent(
-                    wmHelper, stringExtras = mapOf(
-                        EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()
-                    )
+                    wmHelper,
+                    stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())
                 )
                 // Launch the PiP activity fixed as landscape
                 pipApp.launchViaIntent(
-                    wmHelper, stringExtras = mapOf(
-                        EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()
-                    )
+                    wmHelper,
+                    stringExtras =
+                        mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
                 )
             }
             teardown {
-                setRotation(Surface.ROTATION_0)
+                setRotation(PlatformConsts.Rotation.ROTATION_0)
                 RemoveAllTasksButHomeRule.removeAllTasksButHome()
                 pipApp.exit(wmHelper)
                 testApp.exit(wmHelper)
@@ -107,7 +105,8 @@
                 // in portrait
                 broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
                 // during rotation the status bar becomes invisible and reappears at the end
-                wmHelper.StateSyncBuilder()
+                wmHelper
+                    .StateSyncBuilder()
                     .withPipShown()
                     .withNavOrTaskBarVisible()
                     .withStatusBarVisible()
@@ -116,21 +115,21 @@
         }
 
     /**
-     * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation]
-     * to fix a orientation, Tablets instead keep the same orientation and add letterboxes
+     * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to
+     * fix a orientation, Tablets instead keep the same orientation and add letterboxes
      */
     @Before
     fun setup() {
-        Assume.assumeFalse(testSpec.isTablet)
+        Assume.assumeFalse(flicker.scenario.isTablet)
     }
 
     /**
-     * Checks that the [ComponentNameMatcher.NAV_BAR] has the correct position at
-     * the start and end of the transition
+     * Checks that the [ComponentNameMatcher.NAV_BAR] has the correct position at the start and end
+     * of the transition
      */
     @FlakyTest
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() = testSpec.navBarLayerPositionAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = flicker.navBarLayerPositionAtStartAndEnd()
 
     /**
      * Checks that all parts of the screen are covered at the start and end of the transition
@@ -139,7 +138,7 @@
      */
     @Presubmit
     @Test
-    fun entireScreenCoveredAtStartAndEnd() = testSpec.entireScreenCovered(allStates = false)
+    fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered(allStates = false)
 
     @FlakyTest(bugId = 251219769)
     @Test
@@ -147,89 +146,65 @@
         super.entireScreenCovered()
     }
 
-    /**
-     * Checks [pipApp] window remains visible and on top throughout the transition
-     */
+    /** Checks [pipApp] window remains visible and on top throughout the transition */
     @Presubmit
     @Test
     fun pipAppWindowIsAlwaysOnTop() {
-        testSpec.assertWm {
-            isAppWindowOnTop(pipApp)
-        }
+        flicker.assertWm { isAppWindowOnTop(pipApp) }
     }
 
-    /**
-     * Checks that [testApp] window is not visible at the start
-     */
+    /** Checks that [testApp] window is not visible at the start */
     @Presubmit
     @Test
     fun testAppWindowInvisibleOnStart() {
-        testSpec.assertWmStart {
-            isAppWindowInvisible(testApp)
-        }
+        flicker.assertWmStart { isAppWindowInvisible(testApp) }
     }
 
-    /**
-     * Checks that [testApp] window is visible at the end
-     */
+    /** Checks that [testApp] window is visible at the end */
     @Presubmit
     @Test
     fun testAppWindowVisibleOnEnd() {
-        testSpec.assertWmEnd {
-            isAppWindowVisible(testApp)
-        }
+        flicker.assertWmEnd { isAppWindowVisible(testApp) }
     }
 
-    /**
-     * Checks that [testApp] layer is not visible at the start
-     */
+    /** Checks that [testApp] layer is not visible at the start */
     @Presubmit
     @Test
     fun testAppLayerInvisibleOnStart() {
-        testSpec.assertLayersStart {
-            isInvisible(testApp)
-        }
+        flicker.assertLayersStart { isInvisible(testApp) }
     }
 
-    /**
-     * Checks that [testApp] layer is visible at the end
-     */
+    /** Checks that [testApp] layer is visible at the end */
     @Presubmit
     @Test
     fun testAppLayerVisibleOnEnd() {
-        testSpec.assertLayersEnd {
-            isVisible(testApp)
-        }
+        flicker.assertLayersEnd { isVisible(testApp) }
     }
 
     /**
-     * Checks that the visible region of [pipApp] covers the full display area at the start of
-     * the transition
+     * Checks that the visible region of [pipApp] covers the full display area at the start of the
+     * transition
      */
     @Presubmit
     @Test
     fun pipAppLayerCoversFullScreenOnStart() {
-        testSpec.assertLayersStart {
-            visibleRegion(pipApp).coversExactly(startingBounds)
-        }
+        flicker.assertLayersStart { visibleRegion(pipApp).coversExactly(startingBounds) }
     }
 
     /**
-     * Checks that the visible region of [testApp] plus the visible region of [pipApp]
-     * cover the full display area at the end of the transition
+     * Checks that the visible region of [testApp] plus the visible region of [pipApp] cover the
+     * full display area at the end of the transition
      */
     @Presubmit
     @Test
     fun testAppPlusPipLayerCoversFullScreenOnEnd() {
-        testSpec.assertLayersEnd {
+        flicker.assertLayersEnd {
             val pipRegion = visibleRegion(pipApp).region
-            visibleRegion(testApp)
-                .plus(pipRegion)
-                .coversExactly(endingBounds)
+            visibleRegion(testApp).plus(pipRegion).coversExactly(endingBounds)
         }
     }
 
-    /** {@inheritDoc}  */
+    /** {@inheritDoc} */
     @Presubmit
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
@@ -239,16 +214,15 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0)
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 1dc03b9..7466916 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -17,12 +17,12 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.Presubmit
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import org.junit.Test
 
 /** Base class for pip expand tests */
-abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+abstract class ExitPipToAppTransition(flicker: FlickerTest) : PipTransition(flicker) {
     protected val testApp = SimpleAppHelper(instrumentation)
 
     /**
@@ -32,7 +32,7 @@
     @Presubmit
     @Test
     open fun pipAppWindowRemainInsideVisibleBounds() {
-        testSpec.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+        flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
     /**
@@ -42,7 +42,7 @@
     @Presubmit
     @Test
     open fun pipAppLayerRemainInsideVisibleBounds() {
-        testSpec.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+        flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
     /**
@@ -52,7 +52,7 @@
     @Presubmit
     @Test
     open fun showBothAppWindowsThenHidePip() {
-        testSpec.assertWm {
+        flicker.assertWm {
             // when the activity is STOPPING, sometimes it becomes invisible in an entry before
             // the window, sometimes in the same entry. This occurs because we log 1x per frame
             // thus we ignore activity here
@@ -71,7 +71,7 @@
     @Presubmit
     @Test
     open fun showBothAppLayersThenHidePip() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             isVisible(testApp).isVisible(pipApp).then().isInvisible(testApp).isVisible(pipApp)
         }
     }
@@ -83,7 +83,7 @@
     @Presubmit
     @Test
     open fun testPlusPipAppsCoverFullScreenAtStart() {
-        testSpec.assertLayersStart {
+        flicker.assertLayersStart {
             val pipRegion = visibleRegion(pipApp).region
             visibleRegion(testApp).plus(pipRegion).coversExactly(displayBounds)
         }
@@ -96,14 +96,14 @@
     @Presubmit
     @Test
     open fun pipAppCoversFullScreenAtEnd() {
-        testSpec.assertLayersEnd { visibleRegion(pipApp).coversExactly(displayBounds) }
+        flicker.assertLayersEnd { visibleRegion(pipApp).coversExactly(displayBounds) }
     }
 
     /** Checks that the visible region of [pipApp] always expands during the animation */
     @Presubmit
     @Test
     open fun pipLayerExpands() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
             pipLayerList.zipWithNext { previous, current ->
                 current.visibleRegion.coversAtLeast(previous.visibleRegion.region)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
index 3b8bb90..1b5c227 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
@@ -17,20 +17,20 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.LAUNCHER
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Test
 
 /** Base class for exiting pip (closing pip window) without returning to the app */
-abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+abstract class ExitPipTransition(flicker: FlickerTest) : PipTransition(flicker) {
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
-            setup { this.setRotation(testSpec.startRotation) }
-            teardown { this.setRotation(Surface.ROTATION_0) }
+            setup { this.setRotation(flicker.scenario.startRotation) }
+            teardown { this.setRotation(PlatformConsts.Rotation.ROTATION_0) }
         }
 
     /**
@@ -45,16 +45,16 @@
             // When Shell transition is enabled, we change the windowing mode at start, but
             // update the visibility after the transition is finished, so we can't check isNotPinned
             // and isAppWindowInvisible in the same assertion block.
-            testSpec.assertWm {
+            flicker.assertWm {
                 this.invoke("hasPipWindow") {
                         it.isPinned(pipApp).isAppWindowVisible(pipApp).isAppWindowOnTop(pipApp)
                     }
                     .then()
                     .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowNotOnTop(pipApp) }
             }
-            testSpec.assertWmEnd { isAppWindowInvisible(pipApp) }
+            flicker.assertWmEnd { isAppWindowInvisible(pipApp) }
         } else {
-            testSpec.assertWm {
+            flicker.assertWm {
                 this.invoke("hasPipWindow") { it.isPinned(pipApp).isAppWindowVisible(pipApp) }
                     .then()
                     .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowInvisible(pipApp) }
@@ -69,7 +69,7 @@
     @Presubmit
     @Test
     open fun pipLayerBecomesInvisible() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isVisible(pipApp)
                 .isVisible(LAUNCHER)
                 .then()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index 6bf7e8c..1420f8ce 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -18,12 +18,12 @@
 
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -36,30 +36,29 @@
  * To run this test: `atest WMShellFlickerTests:ExitPipViaExpandButtonClickTest`
  *
  * Actions:
+ * ```
  *     Launch an app in pip mode [pipApp],
  *     Launch another full screen mode [testApp]
  *     Expand [pipApp] app to full screen by clicking on the pip window and
  *     then on the expand button
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited [PipTransition]
  *     2. Part of the test setup occurs automatically via
  *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
  *        including configuring navigation mode, initial orientation and ensuring no
  *        apps are running before setup
+ * ```
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipViaExpandButtonClickTest(
-    testSpec: FlickerTestParameter
-) : ExitPipToAppTransition(testSpec) {
+class ExitPipViaExpandButtonClickTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
 
-    /**
-     * Defines the transition used to run the test
-     */
+    /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
             setup {
@@ -70,34 +69,29 @@
                 // This will bring PipApp to fullscreen
                 pipApp.expandPipWindowToApp(wmHelper)
                 // Wait until the other app is no longer visible
-                wmHelper.StateSyncBuilder()
-                    .withWindowSurfaceDisappeared(testApp)
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
             }
         }
 
-    /** {@inheritDoc}  */
-    @Presubmit
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
+    /** {@inheritDoc} */
+    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
-    /** {@inheritDoc}  */
-    @FlakyTest(bugId = 197726610)
-    @Test
-    override fun pipLayerExpands() = super.pipLayerExpands()
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 197726610) @Test override fun pipLayerExpands() = super.pipLayerExpands()
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0))
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 3356d3e..dffbe7e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -18,13 +18,13 @@
 
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -57,7 +57,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransition(testSpec) {
+class ExitPipViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
 
     /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
@@ -74,10 +74,8 @@
             }
         }
 
-    /** {@inheritDoc}  */
-    @Presubmit
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
+    /** {@inheritDoc} */
+    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Presubmit
@@ -113,14 +111,15 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+         * and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index d195abb..232c025 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -17,12 +17,12 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -54,7 +54,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
+class ExitPipWithDismissButtonTest(flicker: FlickerTest) : ExitPipTransition(flicker) {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -69,21 +69,22 @@
     @Presubmit
     @Test
     fun focusChanges() {
-        testSpec.assertEventLog { this.focusChanges("PipMenuView", "NexusLauncherActivity") }
+        flicker.assertEventLog { this.focusChanges("PipMenuView", "NexusLauncherActivity") }
     }
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+         * and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index f7a2447..dbbfdcc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -17,13 +17,13 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -54,7 +54,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
+class ExitPipWithSwipeDownTest(flicker: FlickerTest) : ExitPipTransition(flicker) {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
@@ -64,7 +64,7 @@
                 val pipCenterY = pipRegion.centerY()
                 val displayCenterX = device.displayWidth / 2
                 val barComponent =
-                    if (testSpec.isTablet) {
+                    if (flicker.scenario.isTablet) {
                         ComponentNameMatcher.TASK_BAR
                     } else {
                         ComponentNameMatcher.NAV_BAR
@@ -92,21 +92,22 @@
     @Presubmit
     @Test
     fun focusDoesNotChange() {
-        testSpec.assertEventLog { this.focusDoesNotChange() }
+        flicker.assertEventLog { this.focusDoesNotChange() }
     }
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+         * and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index fa5ce5b..f213cc9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -17,13 +17,13 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.FlakyTest
-import android.view.Surface
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -36,28 +36,27 @@
  * To run this test: `atest WMShellFlickerTests:ExpandPipOnDoubleClickTest`
  *
  * Actions:
+ * ```
  *     Launch an app in pip mode [pipApp],
  *     Expand [pipApp] app to its maximum pip size by double clicking on it
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited [PipTransition]
  *     2. Part of the test setup occurs automatically via
  *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
  *        including configuring navigation mode, initial orientation and ensuring no
  *        apps are running before setup
+ * ```
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) {
     override val transition: FlickerBuilder.() -> Unit
-        get() = buildTransition {
-            transitions {
-                pipApp.doubleClickPipWindow(wmHelper)
-            }
-        }
+        get() = buildTransition { transitions { pipApp.doubleClickPipWindow(wmHelper) } }
 
     /**
      * Checks that the pip app window remains inside the display bounds throughout the whole
@@ -66,9 +65,7 @@
     @FlakyTest(bugId = 249308003)
     @Test
     fun pipWindowRemainInsideVisibleBounds() {
-        testSpec.assertWmVisibleRegion(pipApp) {
-            coversAtMost(displayBounds)
-        }
+        flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
     /**
@@ -78,40 +75,28 @@
     @FlakyTest(bugId = 249308003)
     @Test
     fun pipLayerRemainInsideVisibleBounds() {
-        testSpec.assertLayersVisibleRegion(pipApp) {
-            coversAtMost(displayBounds)
-        }
+        flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
-    /**
-     * Checks [pipApp] window remains visible throughout the animation
-     */
+    /** Checks [pipApp] window remains visible throughout the animation */
     @FlakyTest(bugId = 249308003)
     @Test
     fun pipWindowIsAlwaysVisible() {
-        testSpec.assertWm {
-            isAppWindowVisible(pipApp)
-        }
+        flicker.assertWm { isAppWindowVisible(pipApp) }
     }
 
-    /**
-     * Checks [pipApp] layer remains visible throughout the animation
-     */
+    /** Checks [pipApp] layer remains visible throughout the animation */
     @FlakyTest(bugId = 249308003)
     @Test
     fun pipLayerIsAlwaysVisible() {
-        testSpec.assertLayers {
-            isVisible(pipApp)
-        }
+        flicker.assertLayers { isVisible(pipApp) }
     }
 
-    /**
-     * Checks that the visible region of [pipApp] always expands during the animation
-     */
+    /** Checks that the visible region of [pipApp] always expands during the animation */
     @FlakyTest(bugId = 249308003)
     @Test
     fun pipLayerExpands() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
             pipLayerList.zipWithNext { previous, current ->
                 current.visibleRegion.coversAtLeast(previous.visibleRegion.region)
@@ -122,7 +107,7 @@
     @FlakyTest(bugId = 249308003)
     @Test
     fun pipSameAspectRatio() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
             pipLayerList.zipWithNext { previous, current ->
                 current.visibleRegion.isSameAspectRatio(previous.visibleRegion)
@@ -130,37 +115,25 @@
         }
     }
 
-    /**
-     * Checks [pipApp] window remains pinned throughout the animation
-     */
+    /** Checks [pipApp] window remains pinned throughout the animation */
     @FlakyTest(bugId = 249308003)
     @Test
     fun windowIsAlwaysPinned() {
-        testSpec.assertWm {
-            this.invoke("hasPipWindow") { it.isPinned(pipApp) }
-        }
+        flicker.assertWm { this.invoke("hasPipWindow") { it.isPinned(pipApp) } }
     }
 
-    /**
-     * Checks [ComponentMatcher.LAUNCHER] layer remains visible throughout the animation
-     */
+    /** Checks [ComponentMatcher.LAUNCHER] layer remains visible throughout the animation */
     @FlakyTest(bugId = 249308003)
     @Test
     fun launcherIsAlwaysVisible() {
-        testSpec.assertLayers {
-            isVisible(ComponentNameMatcher.LAUNCHER)
-        }
+        flicker.assertLayers { isVisible(ComponentNameMatcher.LAUNCHER) }
     }
 
-    /**
-     * Checks that the focus doesn't change between windows during the transition
-     */
+    /** Checks that the focus doesn't change between windows during the transition */
     @FlakyTest(bugId = 216306753)
     @Test
     fun focusDoesNotChange() {
-        testSpec.assertEventLog {
-            this.focusDoesNotChange()
-        }
+        flicker.assertEventLog { this.focusDoesNotChange() }
     }
 
     @FlakyTest(bugId = 216306753)
@@ -233,16 +206,15 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0)
-                )
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index bcd01a4..34f6659 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -17,40 +17,32 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.Postsubmit
-import android.view.Surface
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
 
-/**
- * Test expanding a pip window via pinch out gesture.
- */
+/** Test expanding a pip window via pinch out gesture. */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnPinchOpenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) {
     override val transition: FlickerBuilder.() -> Unit
-        get() = buildTransition {
-            transitions {
-                pipApp.pinchOpenPipWindow(wmHelper, 0.4f, 30)
-            }
-        }
+        get() = buildTransition { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.4f, 30) } }
 
-    /**
-     * Checks that the visible region area of [pipApp] always increases during the animation.
-     */
+    /** Checks that the visible region area of [pipApp] always increases during the animation. */
     @Postsubmit
     @Test
     fun pipLayerAreaIncreases() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
             pipLayerList.zipWithNext { previous, current ->
                 previous.visibleRegion.notBiggerThan(current.visibleRegion.region)
@@ -62,16 +54,15 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0)
-                )
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index 0c0228e..e9847fa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -18,11 +18,11 @@
 
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import com.android.wm.shell.flicker.Direction
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -36,71 +36,56 @@
  * To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest`
  *
  * Actions:
+ * ```
  *     Launch [pipApp] in pip mode
  *     Press home
  *     Launch [testApp]
  *     Check if pip window moves down (visually)
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited [PipTransition]
  *     2. Part of the test setup occurs automatically via
  *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
  *        including configuring navigation mode, initial orientation and ensuring no
  *        apps are running before setup
+ * ```
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MovePipDownShelfHeightChangeTest(
-    testSpec: FlickerTestParameter
-) : MovePipShelfHeightTransition(testSpec) {
-//    @Before
-//    fun before() {
-//        Assume.assumeFalse(isShellTransitionsEnabled)
-//    }
-
-    /**
-     * Defines the transition used to run the test
-     */
+class MovePipDownShelfHeightChangeTest(flicker: FlickerTest) :
+    MovePipShelfHeightTransition(flicker) {
+    /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
             teardown {
                 tapl.pressHome()
                 testApp.exit(wmHelper)
             }
-            transitions {
-                testApp.launchViaIntent(wmHelper)
-            }
+            transitions { testApp.launchViaIntent(wmHelper) }
         }
 
-    /**
-     * Checks that the visible region of [pipApp] window always moves down during the animation.
-     */
-    @Presubmit
-    @Test
-    fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
+    /** Checks that the visible region of [pipApp] window always moves down during the animation. */
+    @Presubmit @Test fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
 
-    /**
-     * Checks that the visible region of [pipApp] layer always moves down during the animation.
-     */
-    @Presubmit
-    @Test
-    fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN)
+    /** Checks that the visible region of [pipApp] layer always moves down during the animation. */
+    @Presubmit @Test fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN)
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                supportedRotations = listOf(Surface.ROTATION_0)
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
             )
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
index b401067..35525cb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
@@ -17,29 +17,28 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.Presubmit
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
 import com.android.server.wm.flicker.traces.region.RegionSubject
 import com.android.wm.shell.flicker.Direction
 import org.junit.Test
 
 /** Base class for pip tests with Launcher shelf height change */
-abstract class MovePipShelfHeightTransition(testSpec: FlickerTestParameter) :
-    PipTransition(testSpec) {
+abstract class MovePipShelfHeightTransition(flicker: FlickerTest) : PipTransition(flicker) {
     protected val testApp = FixedOrientationAppHelper(instrumentation)
 
     /** Checks [pipApp] window remains visible throughout the animation */
     @Presubmit
     @Test
     open fun pipWindowIsAlwaysVisible() {
-        testSpec.assertWm { isAppWindowVisible(pipApp) }
+        flicker.assertWm { isAppWindowVisible(pipApp) }
     }
 
     /** Checks [pipApp] layer remains visible throughout the animation */
     @Presubmit
     @Test
     open fun pipLayerIsAlwaysVisible() {
-        testSpec.assertLayers { isVisible(pipApp) }
+        flicker.assertLayers { isVisible(pipApp) }
     }
 
     /**
@@ -49,7 +48,7 @@
     @Presubmit
     @Test
     open fun pipWindowRemainInsideVisibleBounds() {
-        testSpec.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+        flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
     /**
@@ -59,7 +58,7 @@
     @Presubmit
     @Test
     open fun pipLayerRemainInsideVisibleBounds() {
-        testSpec.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+        flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
     /**
@@ -67,7 +66,7 @@
      * during the animation.
      */
     protected fun pipWindowMoves(direction: Direction) {
-        testSpec.assertWm {
+        flicker.assertWm {
             val pipWindowFrameList =
                 this.windowStates { pipApp.windowMatchesAnyOf(it) && it.isVisible }.map { it.frame }
             when (direction) {
@@ -83,7 +82,7 @@
      * during the animation.
      */
     protected fun pipLayerMoves(direction: Direction) {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             val pipLayerRegionList =
                 this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
                     .map { it.visibleRegion }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
index 7f8ef32..3a12a34 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
@@ -17,12 +17,12 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import com.android.wm.shell.flicker.Direction
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -36,68 +36,55 @@
  * To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest`
  *
  * Actions:
+ * ```
  *     Launch [pipApp] in pip mode
  *     Launch [testApp]
  *     Press home
  *     Check if pip window moves up (visually)
- *
+ * ```
  * Notes:
+ * ```
  *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
  *        are inherited [PipTransition]
  *     2. Part of the test setup occurs automatically via
  *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
  *        including configuring navigation mode, initial orientation and ensuring no
  *        apps are running before setup
+ * ```
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class MovePipUpShelfHeightChangeTest(
-    testSpec: FlickerTestParameter
-) : MovePipShelfHeightTransition(testSpec) {
-    /**
-     * Defines the transition used to run the test
-     */
+open class MovePipUpShelfHeightChangeTest(flicker: FlickerTest) :
+    MovePipShelfHeightTransition(flicker) {
+    /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
-        get() = buildTransition() {
-            setup {
-                testApp.launchViaIntent(wmHelper)
+        get() =
+            buildTransition() {
+                setup { testApp.launchViaIntent(wmHelper) }
+                transitions { tapl.pressHome() }
+                teardown { testApp.exit(wmHelper) }
             }
-            transitions {
-                tapl.pressHome()
-            }
-            teardown {
-                testApp.exit(wmHelper)
-            }
-        }
 
-    /**
-     * Checks that the visible region of [pipApp] window always moves up during the animation.
-     */
-    @Presubmit
-    @Test
-    fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
+    /** Checks that the visible region of [pipApp] window always moves up during the animation. */
+    @Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
 
-    /**
-     * Checks that the visible region of [pipApp] layer always moves up during the animation.
-     */
-    @Presubmit
-    @Test
-    fun pipLayerMovesUp() = pipLayerMoves(Direction.UP)
+    /** Checks that the visible region of [pipApp] layer always moves up during the animation. */
+    @Presubmit @Test fun pipLayerMovesUp() = pipLayerMoves(Direction.UP)
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
-         * repetitions, screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                supportedRotations = listOf(Surface.ROTATION_0)
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
             )
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 3b64d21..12d6362 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -17,17 +17,17 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Assume.assumeFalse
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -41,7 +41,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class PipKeyboardTest(flicker: FlickerTest) : PipTransition(flicker) {
     private val imeApp = ImeAppHelper(instrumentation)
 
     @Before
@@ -54,11 +54,11 @@
         get() = buildTransition {
             setup {
                 imeApp.launchViaIntent(wmHelper)
-                setRotation(testSpec.startRotation)
+                setRotation(flicker.scenario.startRotation)
             }
             teardown {
                 imeApp.exit(wmHelper)
-                setRotation(Surface.ROTATION_0)
+                setRotation(PlatformConsts.Rotation.ROTATION_0)
             }
             transitions {
                 // open the soft keyboard
@@ -74,8 +74,8 @@
     @Presubmit
     @Test
     open fun pipInVisibleBounds() {
-        testSpec.assertWmVisibleRegion(pipApp) {
-            val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+        flicker.assertWmVisibleRegion(pipApp) {
+            val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
             coversAtMost(displayBounds)
         }
     }
@@ -84,7 +84,7 @@
     @Presubmit
     @Test
     open fun pipIsAboveAppWindow() {
-        testSpec.assertWmTag(TAG_IME_VISIBLE) { isAboveWindow(ComponentNameMatcher.IME, pipApp) }
+        flicker.assertWmTag(TAG_IME_VISIBLE) { isAboveWindow(ComponentNameMatcher.IME, pipApp) }
     }
 
     companion object {
@@ -92,9 +92,10 @@
 
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
index 2a82c00..901814e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
@@ -18,9 +18,9 @@
 
 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.FlickerTest
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -33,7 +33,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardTest(testSpec) {
+class PipKeyboardTestShellTransit(flicker: FlickerTest) : PipKeyboardTest(flicker) {
 
     @Before
     override fun before() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 7de5494..eee00bd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -18,16 +18,15 @@
 
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -45,7 +44,7 @@
  * ```
  *     Launch a [pipApp] in pip mode
  *     Launch another app [fixedApp] (appears below pip)
- *     Rotate the screen from [testSpec.startRotation] to [testSpec.endRotation]
+ *     Rotate the screen from [flicker.scenario.startRotation] to [flicker.scenario.endRotation]
  *     (usually, 0->90 and 90->0)
  * ```
  * Notes:
@@ -62,10 +61,10 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class PipRotationTest(flicker: FlickerTest) : PipTransition(flicker) {
     private val testApp = SimpleAppHelper(instrumentation)
-    private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation)
-    private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation)
+    private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
+    private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
 
     @Before
     open fun before() {
@@ -76,9 +75,9 @@
         get() = buildTransition {
             setup {
                 testApp.launchViaIntent(wmHelper)
-                setRotation(testSpec.startRotation)
+                setRotation(flicker.scenario.startRotation)
             }
-            transitions { setRotation(testSpec.endRotation) }
+            transitions { setRotation(flicker.scenario.endRotation) }
         }
 
     /** Checks the position of the navigation bar at the start and end of the transition */
@@ -90,14 +89,14 @@
     @Presubmit
     @Test
     fun fixedAppLayer_StartingBounds() {
-        testSpec.assertLayersStart { visibleRegion(testApp).coversAtMost(screenBoundsStart) }
+        flicker.assertLayersStart { visibleRegion(testApp).coversAtMost(screenBoundsStart) }
     }
 
     /** Checks that [testApp] layer is within [screenBoundsEnd] at the end of the transition */
     @Presubmit
     @Test
     fun fixedAppLayer_EndingBounds() {
-        testSpec.assertLayersEnd { visibleRegion(testApp).coversAtMost(screenBoundsEnd) }
+        flicker.assertLayersEnd { visibleRegion(testApp).coversAtMost(screenBoundsEnd) }
     }
 
     /**
@@ -107,7 +106,7 @@
     @Presubmit
     @Test
     fun appLayers_StartingBounds() {
-        testSpec.assertLayersStart {
+        flicker.assertLayersStart {
             visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsStart)
         }
     }
@@ -119,14 +118,12 @@
     @Presubmit
     @Test
     fun appLayers_EndingBounds() {
-        testSpec.assertLayersEnd {
-            visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsEnd)
-        }
+        flicker.assertLayersEnd { visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsEnd) }
     }
 
     /** Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition */
     private fun pipLayerRotates_StartingBounds_internal() {
-        testSpec.assertLayersStart { visibleRegion(pipApp).coversAtMost(screenBoundsStart) }
+        flicker.assertLayersStart { visibleRegion(pipApp).coversAtMost(screenBoundsStart) }
     }
 
     /** Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition */
@@ -140,7 +137,7 @@
     @Presubmit
     @Test
     fun pipLayerRotates_EndingBounds() {
-        testSpec.assertLayersEnd { visibleRegion(pipApp).coversAtMost(screenBoundsEnd) }
+        flicker.assertLayersEnd { visibleRegion(pipApp).coversAtMost(screenBoundsEnd) }
     }
 
     /**
@@ -149,7 +146,7 @@
     @Presubmit
     @Test
     fun pipIsAboveFixedAppWindow_Start() {
-        testSpec.assertWmStart { isAboveWindow(pipApp, testApp) }
+        flicker.assertWmStart { isAboveWindow(pipApp, testApp) }
     }
 
     /**
@@ -158,7 +155,7 @@
     @Presubmit
     @Test
     fun pipIsAboveFixedAppWindow_End() {
-        testSpec.assertWmEnd { isAboveWindow(pipApp, testApp) }
+        flicker.assertWmEnd { isAboveWindow(pipApp, testApp) }
     }
 
     @Presubmit
@@ -171,16 +168,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+         * and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.rotationTests()
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt
index 983cb1c..d0d9167 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt
@@ -18,9 +18,9 @@
 
 import android.platform.test.annotations.FlakyTest
 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.FlickerTest
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -38,7 +38,7 @@
  * ```
  *     Launch a [pipApp] in pip mode
  *     Launch another app [fixedApp] (appears below pip)
- *     Rotate the screen from [testSpec.startRotation] to [testSpec.endRotation]
+ *     Rotate the screen from [flicker.scenario.startRotation] to [flicker.scenario.endRotation]
  *     (usually, 0->90 and 90->0)
  * ```
  * Notes:
@@ -56,7 +56,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 239575053)
-class PipRotationTest_ShellTransit(testSpec: FlickerTestParameter) : PipRotationTest(testSpec) {
+class PipRotationTest_ShellTransit(flicker: FlickerTest) : PipRotationTest(flicker) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index dfa2510..0e0be79 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -18,19 +18,19 @@
 
 import android.app.Instrumentation
 import android.content.Intent
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
 import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.service.PlatformConsts
 import com.android.wm.shell.flicker.BaseTest
 
-abstract class PipTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) {
     protected val pipApp = PipAppHelper(instrumentation)
-    protected val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+    protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
     protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
 
     // Helper class to process test actions by broadcast.
@@ -67,12 +67,12 @@
     ): FlickerBuilder.() -> Unit {
         return {
             setup {
-                setRotation(Surface.ROTATION_0)
+                setRotation(PlatformConsts.Rotation.ROTATION_0)
                 removeAllTasksButHome()
                 pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
             }
             teardown {
-                setRotation(Surface.ROTATION_0)
+                setRotation(PlatformConsts.Rotation.ROTATION_0)
                 removeAllTasksButHome()
                 pipApp.exit(wmHelper)
             }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index f0093e6..157aa98 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -20,19 +20,19 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
+import com.android.server.wm.traces.common.service.PlatformConsts
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
 import org.junit.Assume
 import org.junit.Before
@@ -43,20 +43,18 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test exiting Pip with orientation changes.
- * To run this test: `atest WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest`
+ * Test exiting Pip with orientation changes. To run this test: `atest
+ * WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SetRequestedOrientationWhilePinnedTest(
-    testSpec: FlickerTestParameter
-) : PipTransition(testSpec) {
-    private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
-    private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
+open class SetRequestedOrientationWhilePinnedTest(flicker: FlickerTest) : PipTransition(flicker) {
+    private val startingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_0)
+    private val endingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_90)
 
-    /** {@inheritDoc}  */
+    /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             setup {
@@ -64,30 +62,35 @@
                 device.wakeUpAndGoToHomeScreen()
 
                 // Launch the PiP activity fixed as landscape.
-                pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
-                    EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
+                pipApp.launchViaIntent(
+                    wmHelper,
+                    stringExtras =
+                        mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+                )
                 // Enter PiP.
                 broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP)
                 // System bar may fade out during fixed rotation.
-                wmHelper.StateSyncBuilder()
+                wmHelper
+                    .StateSyncBuilder()
                     .withPipShown()
-                    .withRotation(Surface.ROTATION_0)
+                    .withRotation(PlatformConsts.Rotation.ROTATION_0)
                     .withNavOrTaskBarVisible()
                     .withStatusBarVisible()
                     .waitForAndVerify()
             }
             teardown {
                 pipApp.exit(wmHelper)
-                setRotation(Surface.ROTATION_0)
+                setRotation(PlatformConsts.Rotation.ROTATION_0)
                 removeAllTasksButHome()
             }
             transitions {
                 // Launch the activity back into fullscreen and ensure that it is now in landscape
                 pipApp.launchViaIntent(wmHelper)
                 // System bar may fade out during fixed rotation.
-                wmHelper.StateSyncBuilder()
+                wmHelper
+                    .StateSyncBuilder()
                     .withFullScreenApp(pipApp)
-                    .withRotation(Surface.ROTATION_90)
+                    .withRotation(PlatformConsts.Rotation.ROTATION_90)
                     .withNavOrTaskBarVisible()
                     .withStatusBarVisible()
                     .waitForAndVerify()
@@ -95,34 +98,32 @@
         }
 
     /**
-     * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation]
-     * to fix a orientation, Tablets instead keep the same orientation and add letterboxes
+     * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to
+     * fix a orientation, Tablets instead keep the same orientation and add letterboxes
      */
     @Before
     fun setup() {
-        Assume.assumeFalse(testSpec.isTablet)
+        Assume.assumeFalse(flicker.scenario.isTablet)
     }
 
     @Presubmit
     @Test
     fun displayEndsAt90Degrees() {
-        testSpec.assertWmEnd {
-            hasRotation(Surface.ROTATION_90)
-        }
+        flicker.assertWmEnd { hasRotation(PlatformConsts.Rotation.ROTATION_90) }
     }
 
-    /** {@inheritDoc}  */
+    /** {@inheritDoc} */
     @Presubmit
     @Test
     override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
-    /** {@inheritDoc}  */
+    /** {@inheritDoc} */
     @Presubmit
     @Test
     override fun statusBarLayerIsVisibleAtStartAndEnd() =
         super.statusBarLayerIsVisibleAtStartAndEnd()
 
-    /** {@inheritDoc}  */
+    /** {@inheritDoc} */
     @FlakyTest
     @Test
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
@@ -130,23 +131,17 @@
     @Presubmit
     @Test
     fun pipWindowInsideDisplay() {
-        testSpec.assertWmStart {
-            visibleRegion(pipApp).coversAtMost(startingBounds)
-        }
+        flicker.assertWmStart { visibleRegion(pipApp).coversAtMost(startingBounds) }
     }
 
     @Presubmit
     @Test
     fun pipAppShowsOnTop() {
-        testSpec.assertWmEnd {
-            isAppWindowOnTop(pipApp)
-        }
+        flicker.assertWmEnd { isAppWindowOnTop(pipApp) }
     }
 
     private fun pipLayerInsideDisplay_internal() {
-        testSpec.assertLayersStart {
-            visibleRegion(pipApp).coversAtMost(startingBounds)
-        }
+        flicker.assertLayersStart { visibleRegion(pipApp).coversAtMost(startingBounds) }
     }
 
     @Presubmit
@@ -166,40 +161,35 @@
     @Presubmit
     @Test
     fun pipAlwaysVisible() {
-        testSpec.assertWm {
-            this.isAppWindowVisible(pipApp)
-        }
+        flicker.assertWm { this.isAppWindowVisible(pipApp) }
     }
 
     @Presubmit
     @Test
     fun pipAppLayerCoversFullScreen() {
-        testSpec.assertLayersEnd {
-            visibleRegion(pipApp).coversExactly(endingBounds)
-        }
+        flicker.assertLayersEnd { visibleRegion(pipApp).coversExactly(endingBounds) }
     }
 
-    /** {@inheritDoc}  */
+    /** {@inheritDoc} */
     @Postsubmit
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
-    /** {@inheritDoc}  */
+    /** {@inheritDoc} */
     @Postsubmit
     @Test
     override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
-    /** {@inheritDoc}  */
-    @Postsubmit
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
+    /** {@inheritDoc} */
+    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
index 2cb18f9..a16f5f6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
@@ -24,7 +24,10 @@
 import org.junit.Before
 import org.junit.runners.Parameterized
 
-abstract class PipTestBase(protected val rotationName: String, protected val rotation: Int) {
+abstract class PipTestBase(
+    protected val rotationName: String,
+    protected val rotation: Int
+) {
     val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     val uiDevice = UiDevice.getInstance(instrumentation)
     val packageManager: PackageManager = instrumentation.context.packageManager
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..65cbea0 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
@@ -20,10 +20,12 @@
 import android.platform.test.annotations.IwTest
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.EdgeExtensionComponentMatcher
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -47,13 +49,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class CopyContentInSplit(flicker: FlickerTest) : SplitScreenBase(flicker) {
     private val textEditApp = SplitScreenUtils.getIme(instrumentation)
+    private val MagnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
+    private val PopupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
 
     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,
@@ -68,29 +72,29 @@
     @Presubmit
     @Test
     fun cujCompleted() {
-        testSpec.appWindowIsVisibleAtStart(primaryApp)
-        testSpec.appWindowIsVisibleAtStart(textEditApp)
-        testSpec.splitScreenDividerIsVisibleAtStart()
+        flicker.appWindowIsVisibleAtStart(primaryApp)
+        flicker.appWindowIsVisibleAtStart(textEditApp)
+        flicker.splitScreenDividerIsVisibleAtStart()
 
-        testSpec.appWindowIsVisibleAtEnd(primaryApp)
-        testSpec.appWindowIsVisibleAtEnd(textEditApp)
-        testSpec.splitScreenDividerIsVisibleAtEnd()
+        flicker.appWindowIsVisibleAtEnd(primaryApp)
+        flicker.appWindowIsVisibleAtEnd(textEditApp)
+        flicker.splitScreenDividerIsVisibleAtEnd()
 
         // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
     }
 
     @Presubmit
     @Test
-    fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+    fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @Presubmit @Test fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
+    @Presubmit @Test fun primaryAppLayerKeepVisible() = flicker.layerKeepVisible(primaryApp)
 
-    @Presubmit @Test fun textEditAppLayerKeepVisible() = testSpec.layerKeepVisible(textEditApp)
+    @Presubmit @Test fun textEditAppLayerKeepVisible() = flicker.layerKeepVisible(textEditApp)
 
     @Presubmit
     @Test
     fun primaryAppBoundsKeepVisible() =
-        testSpec.splitAppLayerBoundsKeepVisible(
+        flicker.splitAppLayerBoundsKeepVisible(
             primaryApp,
             landscapePosLeft = tapl.isTablet,
             portraitPosTop = false
@@ -99,21 +103,18 @@
     @Presubmit
     @Test
     fun textEditAppBoundsKeepVisible() =
-        testSpec.splitAppLayerBoundsKeepVisible(
+        flicker.splitAppLayerBoundsKeepVisible(
             textEditApp,
             landscapePosLeft = !tapl.isTablet,
             portraitPosTop = true
         )
 
-    @Presubmit @Test fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
+    @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
 
-    @Presubmit @Test fun textEditAppWindowKeepVisible() = testSpec.appWindowKeepVisible(textEditApp)
+    @Presubmit @Test fun textEditAppWindowKeepVisible() = flicker.appWindowKeepVisible(textEditApp)
 
     /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun entireScreenCovered() =
-        super.entireScreenCovered()
+    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Presubmit
@@ -159,8 +160,21 @@
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        flicker.assertLayers {
+            this.visibleLayersShownMoreThanOneConsecutiveEntry(
+                ignoreLayers =
+                    listOf(
+                        ComponentNameMatcher.SPLASH_SCREEN,
+                        ComponentNameMatcher.SNAPSHOT,
+                        ComponentNameMatcher.IME_SNAPSHOT,
+                        EdgeExtensionComponentMatcher(),
+                        MagnifierLayer,
+                        PopupWindowLayer
+                    )
+            )
+        }
+    }
 
     /** {@inheritDoc} */
     @Presubmit
@@ -171,9 +185,8 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests()
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
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..d0f02e2 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
@@ -21,11 +21,11 @@
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -49,54 +49,62 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class DismissSplitScreenByDivider(flicker: FlickerTest) : SplitScreenBase(flicker) {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
-            setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
-            }
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
             transitions {
                 if (tapl.isTablet) {
-                    SplitScreenUtils.dragDividerToDismissSplit(device, wmHelper,
-                        dragToRight = false, dragToBottom = true)
+                    SplitScreenUtils.dragDividerToDismissSplit(
+                        device,
+                        wmHelper,
+                        dragToRight = false,
+                        dragToBottom = true
+                    )
                 } else {
-                    SplitScreenUtils.dragDividerToDismissSplit(device, wmHelper,
-                        dragToRight = true, dragToBottom = true)
+                    SplitScreenUtils.dragDividerToDismissSplit(
+                        device,
+                        wmHelper,
+                        dragToRight = true,
+                        dragToBottom = true
+                    )
                 }
-                wmHelper.StateSyncBuilder()
-                    .withFullScreenApp(secondaryApp)
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
             }
         }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
+    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
 
     @Presubmit
     @Test
-    fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
+    fun splitScreenDividerBecomesInvisible() = flicker.splitScreenDividerBecomesInvisible()
 
     @Presubmit
     @Test
-    fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+    fun primaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
+    fun secondaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(secondaryApp)
 
     @Presubmit
     @Test
-    fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
-        primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+    fun primaryAppBoundsBecomesInvisible() =
+        flicker.splitAppLayerBoundsBecomesInvisible(
+            primaryApp,
+            landscapePosLeft = tapl.isTablet,
+            portraitPosTop = false
+        )
 
     @Presubmit
     @Test
     fun secondaryAppBoundsIsFullscreenAtEnd() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isVisible(secondaryApp)
                 .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
                 .then()
@@ -109,7 +117,7 @@
                 .contains(SPLIT_SCREEN_DIVIDER_COMPONENT)
                 .then()
                 .invoke("secondaryAppBoundsIsFullscreenAtEnd") {
-                    val displayBounds = WindowUtils.getDisplayBounds(testSpec.endRotation)
+                    val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
                     it.visibleRegion(secondaryApp).coversExactly(displayBounds)
                 }
         }
@@ -117,35 +125,29 @@
 
     @Presubmit
     @Test
-    fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
+    fun primaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+    fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
+
+    /** {@inheritDoc} */
+    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun entireScreenCovered() =
-        super.entireScreenCovered()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() =
-        super.navBarLayerIsVisibleAtStartAndEnd()
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun navBarWindowIsAlwaysVisible() =
-        super.navBarWindowIsAlwaysVisible()
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -156,26 +158,22 @@
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarWindowIsAlwaysVisible() =
-        super.statusBarWindowIsAlwaysVisible()
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -192,8 +190,8 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
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..b44b681 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
@@ -20,10 +20,10 @@
 import android.platform.test.annotations.IwTest
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
@@ -44,89 +44,81 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByGoHome(
-    testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class DismissSplitScreenByGoHome(flicker: FlickerTest) : SplitScreenBase(flicker) {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
-            setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
-            }
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
             transitions {
                 tapl.goHome()
-                wmHelper.StateSyncBuilder()
-                    .withHomeActivityVisible()
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
             }
         }
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
+    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
 
     @Presubmit
     @Test
-    fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
+    fun splitScreenDividerBecomesInvisible() = flicker.splitScreenDividerBecomesInvisible()
 
     @FlakyTest(bugId = 241525302)
     @Test
-    fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+    fun primaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp)
 
     // TODO(b/245472831): Move back to presubmit after shell transitions landing.
     @FlakyTest(bugId = 245472831)
     @Test
-    fun secondaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+    fun secondaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp)
 
     // TODO(b/245472831): Move back to presubmit after shell transitions landing.
     @FlakyTest(bugId = 245472831)
     @Test
-    fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
-        primaryApp,
-        landscapePosLeft = tapl.isTablet,
-        portraitPosTop = false
-    )
+    fun primaryAppBoundsBecomesInvisible() =
+        flicker.splitAppLayerBoundsBecomesInvisible(
+            primaryApp,
+            landscapePosLeft = tapl.isTablet,
+            portraitPosTop = false
+        )
 
     @FlakyTest(bugId = 250530241)
     @Test
-    fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
-        secondaryApp,
-        landscapePosLeft = !tapl.isTablet,
-        portraitPosTop = true
-    )
+    fun secondaryAppBoundsBecomesInvisible() =
+        flicker.splitAppLayerBoundsBecomesInvisible(
+            secondaryApp,
+            landscapePosLeft = !tapl.isTablet,
+            portraitPosTop = true
+        )
 
     @Presubmit
     @Test
-    fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
+    fun primaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(secondaryApp)
+    fun secondaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(secondaryApp)
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 251268711)
     @Test
-    override fun entireScreenCovered() =
-        super.entireScreenCovered()
+    override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() =
-        super.navBarLayerIsVisibleAtStartAndEnd()
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun navBarWindowIsAlwaysVisible() =
-        super.navBarWindowIsAlwaysVisible()
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Presubmit
@@ -137,26 +129,22 @@
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun statusBarWindowIsAlwaysVisible() =
-        super.statusBarWindowIsAlwaysVisible()
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @FlakyTest
@@ -173,8 +161,8 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
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..5b656b3 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
@@ -21,10 +21,10 @@
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -50,35 +50,31 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DragDividerToResize(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
-            setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
-            }
-            transitions {
-                SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper)
-            }
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
         }
 
     @Before
     fun before() {
-        Assume.assumeTrue(tapl.isTablet || !testSpec.isLandscapeOrSeascapeAtStart)
+        Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
     }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun cujCompleted() {
-        testSpec.appWindowIsVisibleAtStart(primaryApp)
-        testSpec.appWindowIsVisibleAtStart(secondaryApp)
-        testSpec.splitScreenDividerIsVisibleAtStart()
+        flicker.appWindowIsVisibleAtStart(primaryApp)
+        flicker.appWindowIsVisibleAtStart(secondaryApp)
+        flicker.splitScreenDividerIsVisibleAtStart()
 
-        testSpec.appWindowIsVisibleAtEnd(primaryApp)
-        testSpec.appWindowIsVisibleAtEnd(secondaryApp)
-        testSpec.splitScreenDividerIsVisibleAtEnd()
+        flicker.appWindowIsVisibleAtEnd(primaryApp)
+        flicker.appWindowIsVisibleAtEnd(secondaryApp)
+        flicker.splitScreenDividerIsVisibleAtEnd()
 
         // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
         // robust enough to get the correct end state.
@@ -86,16 +82,14 @@
 
     @Presubmit
     @Test
-    fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+    fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @Presubmit
-    @Test
-    fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
+    @Presubmit @Test fun primaryAppLayerKeepVisible() = flicker.layerKeepVisible(primaryApp)
 
     @Presubmit
     @Test
     fun secondaryAppLayerVisibilityChanges() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isVisible(secondaryApp)
                 .then()
                 .isInvisible(secondaryApp)
@@ -104,53 +98,47 @@
         }
     }
 
-    @Presubmit
-    @Test
-    fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
+    @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(secondaryApp)
+    fun secondaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(secondaryApp)
 
     @Presubmit
     @Test
-    fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
-        primaryApp,
-        landscapePosLeft = true,
-        portraitPosTop = false
-    )
+    fun primaryAppBoundsChanges() =
+        flicker.splitAppLayerBoundsChanges(
+            primaryApp,
+            landscapePosLeft = true,
+            portraitPosTop = false
+        )
 
     @FlakyTest(bugId = 250530664)
     @Test
-    fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
-        secondaryApp,
-        landscapePosLeft = false,
-        portraitPosTop = true
-    )
+    fun secondaryAppBoundsChanges() =
+        flicker.splitAppLayerBoundsChanges(
+            secondaryApp,
+            landscapePosLeft = false,
+            portraitPosTop = true
+        )
+
+    /** {@inheritDoc} */
+    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun entireScreenCovered() =
-        super.entireScreenCovered()
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() =
-        super.navBarLayerIsVisibleAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun navBarWindowIsAlwaysVisible() =
-        super.navBarWindowIsAlwaysVisible()
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -161,26 +149,22 @@
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarWindowIsAlwaysVisible() =
-        super.statusBarWindowIsAlwaysVisible()
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -197,8 +181,8 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 7378e21..4e36c36 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -19,13 +19,13 @@
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -44,8 +44,8 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test enter split screen by dragging app icon from all apps.
- * This test is only for large screen devices.
+ * Test enter split screen by dragging app icon from all apps. This test is only for large screen
+ * devices.
  *
  * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromAllApps`
  */
@@ -53,9 +53,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromAllApps(
-    testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class EnterSplitScreenByDragFromAllApps(flicker: FlickerTest) : SplitScreenBase(flicker) {
 
     @Before
     fun before() {
@@ -71,9 +69,9 @@
             }
             transitions {
                 tapl.launchedAppState.taskbar
-                        .openAllApps()
-                        .getAppIcon(secondaryApp.appName)
-                        .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                    .openAllApps()
+                    .getAppIcon(secondaryApp.appName)
+                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
                 SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
@@ -81,13 +79,13 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
 
     @Presubmit
     @Test
     fun splitScreenDividerBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
-        testSpec.splitScreenDividerBecomesVisible()
+        flicker.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@@ -95,60 +93,54 @@
     @Test
     fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
         Assume.assumeTrue(isShellTransitionsEnabled)
-        testSpec.assertLayersEnd {
-            this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
-        }
+        flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
     }
 
-    @Presubmit
-    @Test
-    fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+    @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+    fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
 
     @Presubmit
     @Test
-    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, landscapePosLeft = false, portraitPosTop = false)
+    fun primaryAppBoundsIsVisibleAtEnd() =
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            primaryApp,
+            landscapePosLeft = false,
+            portraitPosTop = false
+        )
 
     @Presubmit
     @Test
-    fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
-        secondaryApp)
+    fun secondaryAppBoundsBecomesVisible() =
+        flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp)
 
     @Presubmit
     @Test
-    fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+    fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+    fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
+
+    /** {@inheritDoc} */
+    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun entireScreenCovered() =
-        super.entireScreenCovered()
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() =
-        super.navBarLayerIsVisibleAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun navBarWindowIsAlwaysVisible() =
-        super.navBarWindowIsAlwaysVisible()
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -159,26 +151,22 @@
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarWindowIsAlwaysVisible() =
-        super.statusBarWindowIsAlwaysVisible()
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -195,11 +183,11 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
-                supportedNavigationModes =
-                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+                supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+            )
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 0c03d31..5d37e85 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -19,13 +19,13 @@
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.layerBecomesVisible
@@ -43,8 +43,8 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test enter split screen by dragging app icon from notification.
- * This test is only for large screen devices.
+ * Test enter split screen by dragging app icon from notification. This test is only for large
+ * screen devices.
  *
  * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromNotification`
  */
@@ -52,9 +52,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromNotification(
-    testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class EnterSplitScreenByDragFromNotification(flicker: FlickerTest) : SplitScreenBase(flicker) {
 
     private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
 
@@ -78,21 +76,19 @@
                 SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
                 SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
             }
-            teardown {
-                sendNotificationApp.exit(wmHelper)
-            }
+            teardown { sendNotificationApp.exit(wmHelper) }
         }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
 
     @Presubmit
     @Test
     fun splitScreenDividerBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
-        testSpec.splitScreenDividerBecomesVisible()
+        flicker.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@@ -100,20 +96,16 @@
     @Test
     fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
         Assume.assumeTrue(isShellTransitionsEnabled)
-        testSpec.assertLayersEnd {
-            this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
-        }
+        flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
     }
 
-    @Presubmit
-    @Test
-    fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+    @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
 
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isInvisible(sendNotificationApp)
                 .then()
                 .isVisible(sendNotificationApp)
@@ -129,50 +121,48 @@
     @Test
     fun secondaryAppLayerBecomesVisible_ShellTransit() {
         Assume.assumeTrue(isShellTransitionsEnabled)
-        testSpec.layerBecomesVisible(sendNotificationApp)
+        flicker.layerBecomesVisible(sendNotificationApp)
     }
 
     @Presubmit
     @Test
-    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, landscapePosLeft = false, portraitPosTop = false)
+    fun primaryAppBoundsIsVisibleAtEnd() =
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            primaryApp,
+            landscapePosLeft = false,
+            portraitPosTop = false
+        )
 
     @Presubmit
     @Test
-    fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
-        sendNotificationApp)
+    fun secondaryAppBoundsBecomesVisible() =
+        flicker.splitAppLayerBoundsBecomesVisibleByDrag(sendNotificationApp)
 
     @Presubmit
     @Test
-    fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+    fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(sendNotificationApp)
+    fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(sendNotificationApp)
+
+    /** {@inheritDoc} */
+    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun entireScreenCovered() =
-        super.entireScreenCovered()
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() =
-        super.navBarLayerIsVisibleAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun navBarWindowIsAlwaysVisible() =
-        super.navBarWindowIsAlwaysVisible()
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -183,26 +173,22 @@
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarWindowIsAlwaysVisible() =
-        super.statusBarWindowIsAlwaysVisible()
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -219,11 +205,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
-                supportedNavigationModes =
-                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+                supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
             )
         }
     }
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..abf9426
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -0,0 +1,194 @@
+/*
+ * 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.IwTest
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
+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(flicker: FlickerTest) : SplitScreenBase(flicker) {
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(flicker.scenario.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)
+            }
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() =
+        flicker.splitScreenEntered(
+            primaryApp,
+            secondaryApp,
+            fromOtherApp = false,
+            appExistAtStart = false
+        )
+
+    @Presubmit
+    @Test
+    fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
+
+    @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
+
+    @Presubmit
+    @Test
+    fun primaryAppBoundsIsVisibleAtEnd() =
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            primaryApp,
+            landscapePosLeft = false,
+            portraitPosTop = false
+        )
+
+    @Presubmit
+    @Test
+    fun secondaryAppBoundsBecomesVisible() =
+        flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp)
+
+    @Presubmit
+    @Test
+    fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppWindowBecomesVisible() {
+        flicker.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<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 496d439..795a2c4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -19,13 +19,13 @@
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -44,8 +44,8 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test enter split screen by dragging app icon from taskbar.
- * This test is only for large screen devices.
+ * Test enter split screen by dragging app icon from taskbar. This test is only for large screen
+ * devices.
  *
  * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromTaskbar`
  */
@@ -53,9 +53,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromTaskbar(
-    testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class EnterSplitScreenByDragFromTaskbar(flicker: FlickerTest) : SplitScreenBase(flicker) {
 
     @Before
     fun before() {
@@ -68,9 +66,7 @@
             super.transition(this)
             setup {
                 tapl.goHome()
-                SplitScreenUtils.createShortcutOnHotseatIfNotExist(
-                    tapl, secondaryApp.appName
-                )
+                SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
                 primaryApp.launchViaIntent(wmHelper)
             }
             transitions {
@@ -84,13 +80,13 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
 
     @Presubmit
     @Test
     fun splitScreenDividerBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
-        testSpec.splitScreenDividerBecomesVisible()
+        flicker.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@@ -98,20 +94,16 @@
     @Test
     fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
         Assume.assumeTrue(isShellTransitionsEnabled)
-        testSpec.assertLayersEnd {
-            this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
-        }
+        flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
     }
 
-    @Presubmit
-    @Test
-    fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+    @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
 
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isInvisible(secondaryApp)
                 .then()
                 .isVisible(secondaryApp)
@@ -127,50 +119,48 @@
     @Test
     fun secondaryAppLayerBecomesVisible_ShellTransit() {
         Assume.assumeTrue(isShellTransitionsEnabled)
-        testSpec.layerBecomesVisible(secondaryApp)
+        flicker.layerBecomesVisible(secondaryApp)
     }
 
     @Presubmit
     @Test
-    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, landscapePosLeft = false, portraitPosTop = false)
+    fun primaryAppBoundsIsVisibleAtEnd() =
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            primaryApp,
+            landscapePosLeft = false,
+            portraitPosTop = false
+        )
 
     @Presubmit
     @Test
-    fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
-        secondaryApp)
+    fun secondaryAppBoundsBecomesVisible() =
+        flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp)
 
     @Presubmit
     @Test
-    fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+    fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+    fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
+
+    /** {@inheritDoc} */
+    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun entireScreenCovered() =
-        super.entireScreenCovered()
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() =
-        super.navBarLayerIsVisibleAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun navBarWindowIsAlwaysVisible() =
-        super.navBarWindowIsAlwaysVisible()
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -181,26 +171,22 @@
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarWindowIsAlwaysVisible() =
-        super.statusBarWindowIsAlwaysVisible()
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -217,10 +203,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
-                supportedNavigationModes =
-                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
             )
         }
     }
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..c09ca91 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
@@ -21,11 +21,11 @@
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
@@ -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
@@ -50,22 +49,22 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class EnterSplitScreenFromOverview(flicker: FlickerTest) : SplitScreenBase(flicker) {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
             setup {
-                tapl.workspace.switchToOverview().dismissAllTasks()
                 primaryApp.launchViaIntent(wmHelper)
                 secondaryApp.launchViaIntent(wmHelper)
                 tapl.goHome()
-                wmHelper.StateSyncBuilder()
+                wmHelper
+                    .StateSyncBuilder()
                     .withAppTransitionIdle()
                     .withHomeActivityVisible()
                     .waitForAndVerify()
             }
             transitions {
-                SplitScreenUtils.splitFromOverview(tapl)
+                SplitScreenUtils.splitFromOverview(tapl, device)
                 SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
@@ -73,72 +72,76 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
 
     @Presubmit
     @Test
-    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+    fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
+
+    @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
 
     @Presubmit
     @Test
-    fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+    fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
-
-    @Presubmit
-    @Test
-    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+    fun primaryAppBoundsIsVisibleAtEnd() =
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            primaryApp,
+            landscapePosLeft = tapl.isTablet,
+            portraitPosTop = false
+        )
 
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
-        testSpec.splitAppLayerBoundsBecomesVisible(
-            secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+        flicker.splitAppLayerBoundsBecomesVisible(
+            secondaryApp,
+            landscapePosLeft = !tapl.isTablet,
+            portraitPosTop = true
+        )
     }
 
     @FlakyTest(bugId = 244407465)
     @Test
     fun secondaryAppBoundsBecomesVisible_shellTransit() {
         Assume.assumeTrue(isShellTransitionsEnabled)
-        testSpec.splitAppLayerBoundsBecomesVisible(
-            secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+        flicker.splitAppLayerBoundsBecomesVisible(
+            secondaryApp,
+            landscapePosLeft = !tapl.isTablet,
+            portraitPosTop = true
+        )
     }
 
     @Presubmit
     @Test
-    fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+    fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+    fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 251269324)
     @Test
-    override fun entireScreenCovered() =
-        super.entireScreenCovered()
+    override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() =
-        super.navBarLayerIsVisibleAtStartAndEnd()
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun navBarWindowIsAlwaysVisible() =
-        super.navBarWindowIsAlwaysVisible()
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Presubmit
@@ -149,26 +152,22 @@
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun statusBarWindowIsAlwaysVisible() =
-        super.statusBarWindowIsAlwaysVisible()
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 252736515)
@@ -185,8 +184,8 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
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..8c0a303 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
@@ -17,12 +17,12 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.content.Context
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.wm.shell.flicker.BaseTest
 
-abstract class SplitScreenBase(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class SplitScreenBase(flicker: FlickerTest) : BaseTest(flicker) {
     protected val context: Context = instrumentation.context
     protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
@@ -32,8 +32,9 @@
         get() = {
             setup {
                 tapl.setEnableRotation(true)
-                setRotation(testSpec.startRotation)
-                tapl.setExpectedRotation(testSpec.startRotation)
+                setRotation(flicker.scenario.startRotation)
+                tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+                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..f3927d4 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,15 +39,19 @@
 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
+import org.junit.Assert.assertNotNull
 
 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 LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L
     private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
 
     private val notificationScrollerSelector: BySelector
@@ -55,6 +60,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 +101,47 @@
     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()
@@ -254,13 +284,6 @@
         }
     }
 
-    fun longPress(instrumentation: Instrumentation, point: Point) {
-        val downTime = SystemClock.uptimeMillis()
-        touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
-        SystemClock.sleep(LONG_PRESS_TIME_MS)
-        touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
-    }
-
     fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) {
         tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
         val allApps = tapl.workspace.switchToAllApps()
@@ -332,9 +355,11 @@
                 Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")),
                 TIMEOUT_MS
             )
-        longPress(instrumentation, textView.visibleCenter)
+        assertNotNull("Unable to find the TextView", textView)
+        textView.click(LONG_PRESS_TIME_MS)
 
         val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+        assertNotNull("Unable to find the copy button", copyBtn)
         copyBtn.click()
 
         // Paste text to destinationApp
@@ -343,9 +368,11 @@
                 Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
                 TIMEOUT_MS
             )
-        longPress(instrumentation, editText.visibleCenter)
+        assertNotNull("Unable to find the EditText", editText)
+        editText.click(LONG_PRESS_TIME_MS)
 
         val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+        assertNotNull("Unable to find the paste button", pasteBtn)
         pasteBtn.click()
 
         // Verify text
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..09568b2 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
@@ -19,13 +19,13 @@
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -50,19 +50,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchAppByDoubleTapDivider(flicker: FlickerTest) : SplitScreenBase(flicker) {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
-            setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
-            }
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
             transitions {
                 SplitScreenUtils.doubleTapDividerToSwitch(device)
-                wmHelper.StateSyncBuilder()
-                    .withAppTransitionIdle()
-                    .waitForAndVerify()
+                wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
 
                 waitForLayersToSwitch(wmHelper)
                 waitForWindowsToSwitch(wmHelper)
@@ -70,61 +66,74 @@
         }
 
     private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
-        wmHelper.StateSyncBuilder().add("appWindowsSwitched") {
-            val primaryAppWindow = it.wmState.visibleWindows.firstOrNull { window ->
-                primaryApp.windowMatchesAnyOf(window)
-            } ?: return@add false
-            val secondaryAppWindow = it.wmState.visibleWindows.firstOrNull { window ->
-                secondaryApp.windowMatchesAnyOf(window)
-            } ?: return@add false
+        wmHelper
+            .StateSyncBuilder()
+            .add("appWindowsSwitched") {
+                val primaryAppWindow =
+                    it.wmState.visibleWindows.firstOrNull { window ->
+                        primaryApp.windowMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+                val secondaryAppWindow =
+                    it.wmState.visibleWindows.firstOrNull { window ->
+                        secondaryApp.windowMatchesAnyOf(window)
+                    }
+                        ?: return@add false
 
-            if (isLandscape(testSpec.endRotation)) {
-                return@add if (testSpec.isTablet) {
-                    secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+                if (isLandscape(flicker.scenario.endRotation)) {
+                    return@add if (flicker.scenario.isTablet) {
+                        secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+                    } else {
+                        primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+                    }
                 } else {
-                    primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
-                }
-            } else {
-                return@add if (testSpec.isTablet) {
-                    primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
-                } else {
-                    primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                    return@add if (flicker.scenario.isTablet) {
+                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                    } else {
+                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                    }
                 }
             }
-        }.waitForAndVerify()
+            .waitForAndVerify()
     }
 
     private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
-        wmHelper.StateSyncBuilder().add("appLayersSwitched") {
-            val primaryAppLayer = it.layerState.visibleLayers.firstOrNull { window ->
-                primaryApp.layerMatchesAnyOf(window)
-            } ?: return@add false
-            val secondaryAppLayer = it.layerState.visibleLayers.firstOrNull { window ->
-                secondaryApp.layerMatchesAnyOf(window)
-            } ?: return@add false
+        wmHelper
+            .StateSyncBuilder()
+            .add("appLayersSwitched") {
+                val primaryAppLayer =
+                    it.layerState.visibleLayers.firstOrNull { window ->
+                        primaryApp.layerMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+                val secondaryAppLayer =
+                    it.layerState.visibleLayers.firstOrNull { window ->
+                        secondaryApp.layerMatchesAnyOf(window)
+                    }
+                        ?: return@add false
 
-            val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds
-                ?: return@add false
-            val secondaryVisibleRegion = secondaryAppLayer.visibleRegion?.bounds
-                ?: return@add false
+                val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
+                val secondaryVisibleRegion =
+                    secondaryAppLayer.visibleRegion?.bounds ?: return@add false
 
-            if (isLandscape(testSpec.endRotation)) {
-                return@add if (testSpec.isTablet) {
-                    secondaryVisibleRegion.right <= primaryVisibleRegion.left
+                if (isLandscape(flicker.scenario.endRotation)) {
+                    return@add if (flicker.scenario.isTablet) {
+                        secondaryVisibleRegion.right <= primaryVisibleRegion.left
+                    } else {
+                        primaryVisibleRegion.right <= secondaryVisibleRegion.left
+                    }
                 } else {
-                    primaryVisibleRegion.right <= secondaryVisibleRegion.left
-                }
-            } else {
-                return@add if (testSpec.isTablet) {
-                    primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
-                } else {
-                    primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                    return@add if (flicker.scenario.isTablet) {
+                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                    } else {
+                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                    }
                 }
             }
-        }.waitForAndVerify()
+            .waitForAndVerify()
     }
 
-    private fun isLandscape(rotation: Int): Boolean {
+    private fun isLandscape(rotation: PlatformConsts.Rotation): Boolean {
         val displayBounds = WindowUtils.getDisplayBounds(rotation)
         return displayBounds.width > displayBounds.height
     }
@@ -133,70 +142,71 @@
     @Presubmit
     @Test
     fun cujCompleted() {
-        testSpec.appWindowIsVisibleAtStart(primaryApp)
-        testSpec.appWindowIsVisibleAtStart(secondaryApp)
-        testSpec.splitScreenDividerIsVisibleAtStart()
+        flicker.appWindowIsVisibleAtStart(primaryApp)
+        flicker.appWindowIsVisibleAtStart(secondaryApp)
+        flicker.splitScreenDividerIsVisibleAtStart()
 
-        testSpec.appWindowIsVisibleAtEnd(primaryApp)
-        testSpec.appWindowIsVisibleAtEnd(secondaryApp)
-        testSpec.splitScreenDividerIsVisibleAtEnd()
+        flicker.appWindowIsVisibleAtEnd(primaryApp)
+        flicker.appWindowIsVisibleAtEnd(secondaryApp)
+        flicker.splitScreenDividerIsVisibleAtEnd()
 
         // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
         // robust enough to get the correct end state.
     }
 
+    @Presubmit
     @Test
-    fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+    fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @Test
-    fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+    @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
 
+    @Presubmit
     @Test
-    fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
+    fun secondaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(secondaryApp)
 
+    @Presubmit
     @Test
-    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp,
-        landscapePosLeft = !tapl.isTablet,
-        portraitPosTop = true
-    )
+    fun primaryAppBoundsIsVisibleAtEnd() =
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            primaryApp,
+            landscapePosLeft = !tapl.isTablet,
+            portraitPosTop = true
+        )
 
+    @Presubmit
     @Test
-    fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        secondaryApp,
-        landscapePosLeft = tapl.isTablet,
-        portraitPosTop = false
-    )
+    fun secondaryAppBoundsIsVisibleAtEnd() =
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            secondaryApp,
+            landscapePosLeft = tapl.isTablet,
+            portraitPosTop = false
+        )
 
+    @Presubmit
     @Test
-    fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+    fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
 
+    @Presubmit
     @Test
-    fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+    fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
+
+    /** {@inheritDoc} */
+    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun entireScreenCovered() =
-        super.entireScreenCovered()
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() =
-        super.navBarLayerIsVisibleAtStartAndEnd()
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun navBarLayerPositionAtStartAndEnd() =
-        super.navBarLayerPositionAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun navBarWindowIsAlwaysVisible() =
-        super.navBarWindowIsAlwaysVisible()
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -207,26 +217,22 @@
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarLayerPositionAtStartAndEnd() =
-        super.statusBarLayerPositionAtStartAndEnd()
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun statusBarWindowIsAlwaysVisible() =
-        super.statusBarWindowIsAlwaysVisible()
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() =
-        super.taskBarLayerIsVisibleAtStartAndEnd()
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
     @Postsubmit
     @Test
-    override fun taskBarWindowIsAlwaysVisible() =
-        super.taskBarWindowIsAlwaysVisible()
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
     @Postsubmit
@@ -243,11 +249,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
-                supportedNavigationModes =
-                listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+                supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
             )
         }
     }
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..940e0e9 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
@@ -19,12 +19,12 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
@@ -45,14 +45,14 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchBackToSplitFromAnotherApp(flicker: FlickerTest) : SplitScreenBase(flicker) {
     val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
 
     override val transition: FlickerBuilder.() -> Unit
         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()
@@ -66,22 +66,22 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
 
     @Presubmit
     @Test
-    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+    fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
 
-    @Presubmit @Test fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+    @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+    fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
 
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() =
-        testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
             primaryApp,
             landscapePosLeft = tapl.isTablet,
             portraitPosTop = false
@@ -90,7 +90,7 @@
     @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() =
-        testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
             secondaryApp,
             landscapePosLeft = !tapl.isTablet,
             portraitPosTop = true
@@ -98,17 +98,14 @@
 
     @Presubmit
     @Test
-    fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+    fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+    fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
 
     /** {@inheritDoc} */
-    @FlakyTest
-    @Test
-    override fun entireScreenCovered() =
-        super.entireScreenCovered()
+    @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Presubmit
@@ -166,13 +163,11 @@
     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)
-                )
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+            )
         }
     }
 }
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..85812c4 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
@@ -19,12 +19,12 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
@@ -45,13 +45,13 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchBackToSplitFromHome(flicker: FlickerTest) : SplitScreenBase(flicker) {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
             setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
 
                 tapl.goHome()
                 wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
@@ -65,22 +65,22 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
 
     @Presubmit
     @Test
-    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+    fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
 
-    @Presubmit @Test fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+    @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+    fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
 
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() =
-        testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
             primaryApp,
             landscapePosLeft = tapl.isTablet,
             portraitPosTop = false
@@ -89,7 +89,7 @@
     @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() =
-        testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
             secondaryApp,
             landscapePosLeft = !tapl.isTablet,
             portraitPosTop = true
@@ -97,17 +97,14 @@
 
     @Presubmit
     @Test
-    fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+    fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+    fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
 
     /** {@inheritDoc} */
-    @FlakyTest
-    @Test
-    override fun entireScreenCovered() =
-        super.entireScreenCovered()
+    @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Presubmit
@@ -165,13 +162,11 @@
     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)
-                )
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+            )
         }
     }
 }
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..7c62433 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
@@ -19,12 +19,12 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
@@ -45,13 +45,13 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchBackToSplitFromRecent(flicker: FlickerTest) : SplitScreenBase(flicker) {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
             setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
 
                 tapl.goHome()
                 wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
@@ -65,22 +65,22 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
 
     @Presubmit
     @Test
-    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+    fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
 
-    @Presubmit @Test fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+    @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+    fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
 
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() =
-        testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
             primaryApp,
             landscapePosLeft = tapl.isTablet,
             portraitPosTop = false
@@ -89,7 +89,7 @@
     @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() =
-        testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
             secondaryApp,
             landscapePosLeft = !tapl.isTablet,
             portraitPosTop = true
@@ -97,17 +97,14 @@
 
     @Presubmit
     @Test
-    fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+    fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
 
     @Presubmit
     @Test
-    fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+    fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
 
     /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun entireScreenCovered() =
-        super.entireScreenCovered()
+    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
     @Presubmit
@@ -165,13 +162,11 @@
     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)
-                )
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+            )
         }
     }
 }
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..193ab98
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -0,0 +1,241 @@
+/*
+ * 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.IwTest
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+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.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
+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.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+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(flicker: FlickerTest) : SplitScreenBase(flicker) {
+    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)
+            }
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() {
+        flicker.appWindowIsVisibleAtStart(thirdApp)
+        flicker.appWindowIsVisibleAtStart(fourthApp)
+        flicker.splitScreenDividerIsVisibleAtStart()
+
+        flicker.appWindowIsVisibleAtEnd(primaryApp)
+        flicker.appWindowIsVisibleAtEnd(secondaryApp)
+        flicker.appWindowIsInvisibleAtEnd(thirdApp)
+        flicker.appWindowIsInvisibleAtEnd(fourthApp)
+        flicker.splitScreenDividerIsVisibleAtEnd()
+    }
+
+    @Presubmit
+    @Test
+    fun splitScreenDividerInvisibleAtMiddle() =
+        flicker.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() = flicker.layerBecomesVisible(primaryApp)
+
+    @FlakyTest(bugId = 247095572)
+    @Test
+    fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
+
+    @FlakyTest(bugId = 247095572)
+    @Test
+    fun thirdAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(thirdApp)
+
+    @FlakyTest(bugId = 247095572)
+    @Test
+    fun fourthAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(fourthApp)
+
+    @Presubmit
+    @Test
+    fun primaryAppBoundsIsVisibleAtEnd() =
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            primaryApp,
+            landscapePosLeft = tapl.isTablet,
+            portraitPosTop = false
+        )
+
+    @Presubmit
+    @Test
+    fun secondaryAppBoundsIsVisibleAtEnd() =
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            secondaryApp,
+            landscapePosLeft = !tapl.isTablet,
+            portraitPosTop = true
+        )
+
+    @Presubmit
+    @Test
+    fun thirdAppBoundsIsVisibleAtBegin() =
+        flicker.assertLayersStart {
+            this.splitAppLayerBoundsSnapToDivider(
+                thirdApp,
+                landscapePosLeft = tapl.isTablet,
+                portraitPosTop = false,
+                flicker.scenario.startRotation
+            )
+        }
+
+    @Presubmit
+    @Test
+    fun fourthAppBoundsIsVisibleAtBegin() =
+        flicker.assertLayersStart {
+            this.splitAppLayerBoundsSnapToDivider(
+                fourthApp,
+                landscapePosLeft = !tapl.isTablet,
+                portraitPosTop = true,
+                flicker.scenario.startRotation
+            )
+        }
+
+    @Presubmit
+    @Test
+    fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
+
+    @Presubmit
+    @Test
+    fun thirdAppWindowBecomesVisible() = flicker.appWindowBecomesInvisible(thirdApp)
+
+    @Presubmit
+    @Test
+    fun fourthAppWindowBecomesVisible() = flicker.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<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 7cbace5..081c8ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,13 +16,9 @@
 
 package com.android.wm.shell;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -34,8 +30,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
@@ -44,11 +38,9 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
-import android.app.WindowConfiguration;
 import android.content.LocusId;
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
@@ -61,8 +53,6 @@
 import android.window.ITaskOrganizerController;
 import android.window.TaskAppearedInfo;
 import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransaction.Change;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -638,130 +628,10 @@
         verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
     }
 
-    @Test
-    public void testPrepareClearBoundsForStandardTasks() {
-        MockToken token1 = new MockToken();
-        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
-        mOrganizer.onTaskAppeared(task1, null);
-
-        MockToken token2 = new MockToken();
-        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
-        mOrganizer.onTaskAppeared(task2, null);
-
-        MockToken otherDisplayToken = new MockToken();
-        RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED,
-                otherDisplayToken);
-        otherDisplayTask.displayId = 2;
-        mOrganizer.onTaskAppeared(otherDisplayTask, null);
-
-        WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
-
-        assertEquals(wct.getChanges().size(), 2);
-        Change boundsChange1 = wct.getChanges().get(token1.binder());
-        assertNotNull(boundsChange1);
-        assertNotEquals(
-                (boundsChange1.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
-        assertTrue(boundsChange1.getConfiguration().windowConfiguration.getBounds().isEmpty());
-
-        Change boundsChange2 = wct.getChanges().get(token2.binder());
-        assertNotNull(boundsChange2);
-        assertNotEquals(
-                (boundsChange2.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
-        assertTrue(boundsChange2.getConfiguration().windowConfiguration.getBounds().isEmpty());
-    }
-
-    @Test
-    public void testPrepareClearBoundsForStandardTasks_onlyClearActivityTypeStandard() {
-        MockToken token1 = new MockToken();
-        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
-        mOrganizer.onTaskAppeared(task1, null);
-
-        MockToken token2 = new MockToken();
-        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
-        task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
-        mOrganizer.onTaskAppeared(task2, null);
-
-        WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
-
-        // Only clear bounds for task1
-        assertEquals(1, wct.getChanges().size());
-        assertNotNull(wct.getChanges().get(token1.binder()));
-    }
-
-    @Test
-    public void testPrepareClearFreeformForStandardTasks() {
-        MockToken token1 = new MockToken();
-        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
-        mOrganizer.onTaskAppeared(task1, null);
-
-        MockToken token2 = new MockToken();
-        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW, token2);
-        mOrganizer.onTaskAppeared(task2, null);
-
-        MockToken otherDisplayToken = new MockToken();
-        RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM,
-                otherDisplayToken);
-        otherDisplayTask.displayId = 2;
-        mOrganizer.onTaskAppeared(otherDisplayTask, null);
-
-        WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
-
-        // Only task with freeform windowing mode and the right display should be updated
-        assertEquals(wct.getChanges().size(), 1);
-        Change wmModeChange1 = wct.getChanges().get(token1.binder());
-        assertNotNull(wmModeChange1);
-        assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED);
-    }
-
-    @Test
-    public void testPrepareClearFreeformForStandardTasks_onlyClearActivityTypeStandard() {
-        MockToken token1 = new MockToken();
-        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
-        mOrganizer.onTaskAppeared(task1, null);
-
-        MockToken token2 = new MockToken();
-        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_FREEFORM, token2);
-        task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
-        mOrganizer.onTaskAppeared(task2, null);
-
-        WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
-
-        // Only clear freeform for task1
-        assertEquals(1, wct.getChanges().size());
-        assertNotNull(wct.getChanges().get(token1.binder()));
-    }
-
     private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
         RunningTaskInfo taskInfo = new RunningTaskInfo();
         taskInfo.taskId = taskId;
         taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
         return taskInfo;
     }
-
-    private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, MockToken token) {
-        RunningTaskInfo taskInfo = createTaskInfo(taskId, windowingMode);
-        taskInfo.displayId = 1;
-        taskInfo.token = token.token();
-        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
-        return taskInfo;
-    }
-
-    private static class MockToken {
-        private final WindowContainerToken mToken;
-        private final IBinder mBinder;
-
-        MockToken() {
-            mToken = mock(WindowContainerToken.class);
-            mBinder = mock(IBinder.class);
-            when(mToken.asBinder()).thenReturn(mBinder);
-        }
-
-        WindowContainerToken token() {
-            return mToken;
-        }
-
-        IBinder binder() {
-            return mBinder;
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 7896247..8a5b490 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -37,6 +38,7 @@
 import android.content.pm.ApplicationInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteCallback;
@@ -51,10 +53,12 @@
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.window.BackEvent;
+import android.window.BackMotionEvent;
 import android.window.BackNavigationInfo;
 import android.window.IBackAnimationFinishedCallback;
 import android.window.IOnBackInvokedCallback;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -64,7 +68,6 @@
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.sysui.ShellSharedConstants;
-import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -94,7 +97,10 @@
     private IActivityTaskManager mActivityTaskManager;
 
     @Mock
-    private IOnBackInvokedCallback mIOnBackInvokedCallback;
+    private IOnBackInvokedCallback mAppCallback;
+
+    @Mock
+    private IOnBackInvokedCallback mAnimatorCallback;
 
     @Mock
     private IBackAnimationFinishedCallback mBackAnimationFinishedCallback;
@@ -103,14 +109,12 @@
     private IRemoteAnimationRunner mBackAnimationRunner;
 
     @Mock
-    private Transitions mTransitions;
-
-    @Mock
     private ShellController mShellController;
 
-    private BackAnimationController mController;
+    @Mock
+    private BackAnimationBackground mAnimationBackground;
 
-    private int mEventTime = 0;
+    private BackAnimationController mController;
     private TestableContentResolver mContentResolver;
     private TestableLooper mTestableLooper;
 
@@ -127,19 +131,18 @@
         mController = new BackAnimationController(mShellInit, mShellController,
                 mShellExecutor, new Handler(mTestableLooper.getLooper()),
                 mActivityTaskManager, mContext,
-                mContentResolver, mTransitions);
+                mContentResolver, mAnimationBackground);
         mController.setEnableUAnimation(true);
         mShellInit.init();
-        mEventTime = 0;
         mShellExecutor.flushAll();
     }
 
-    private void createNavigationInfo(int backType, IOnBackInvokedCallback onBackInvokedCallback) {
+    private void createNavigationInfo(int backType, boolean enableAnimation) {
         BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
                 .setType(backType)
                 .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
-                .setOnBackInvokedCallback(onBackInvokedCallback)
-                .setPrepareRemoteAnimation(true);
+                .setOnBackInvokedCallback(mAppCallback)
+                .setPrepareRemoteAnimation(enableAnimation);
 
         createNavigationInfo(builder);
     }
@@ -180,26 +183,39 @@
     }
 
     @Test
-    public void verifyAnimationFinishes() {
-        RemoteAnimationTarget animationTarget = createAnimationTarget();
-        boolean[] backNavigationDone = new boolean[]{false};
-        boolean[] triggerBack = new boolean[]{false};
-        createNavigationInfo(new BackNavigationInfo.Builder()
-                .setType(BackNavigationInfo.TYPE_CROSS_ACTIVITY)
-                .setOnBackNavigationDone(
-                        new RemoteCallback(result -> {
-                            backNavigationDone[0] = true;
-                            triggerBack[0] = result.getBoolean(KEY_TRIGGER_BACK);
-                        })));
-        triggerBackGesture();
-        assertTrue("Navigation Done callback not called", backNavigationDone[0]);
-        assertTrue("TriggerBack should have been true", triggerBack[0]);
+    public void verifyNavigationFinishes() throws RemoteException {
+        final int[] testTypes = new int[] {BackNavigationInfo.TYPE_RETURN_TO_HOME,
+                BackNavigationInfo.TYPE_CROSS_TASK,
+                BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+                BackNavigationInfo.TYPE_DIALOG_CLOSE,
+                BackNavigationInfo.TYPE_CALLBACK };
+
+        for (int type: testTypes) {
+            registerAnimation(type);
+        }
+
+        for (int type: testTypes) {
+            final ResultListener result  = new ResultListener();
+            createNavigationInfo(new BackNavigationInfo.Builder()
+                    .setType(type)
+                    .setOnBackInvokedCallback(mAppCallback)
+                    .setPrepareRemoteAnimation(true)
+                    .setOnBackNavigationDone(new RemoteCallback(result)));
+            triggerBackGesture();
+            simulateRemoteAnimationStart(type);
+            simulateRemoteAnimationFinished();
+            mShellExecutor.flushAll();
+
+            assertTrue("Navigation Done callback not called for "
+                    + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+            assertTrue("TriggerBack should have been true", result.mTriggerBack);
+        }
     }
 
     @Test
     public void backToHome_dispatchesEvents() throws RemoteException {
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
-        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, mIOnBackInvokedCallback);
+        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
 
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
 
@@ -207,14 +223,17 @@
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
 
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
-        verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+
+        verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class));
         verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
-        verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class));
+        ArgumentCaptor<BackMotionEvent> backEventCaptor =
+                ArgumentCaptor.forClass(BackMotionEvent.class);
+        verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
 
         // Check that back invocation is dispatched.
         mController.setTriggerBack(true);   // Fake trigger back
         doMotionEvent(MotionEvent.ACTION_UP, 0);
-        verify(mIOnBackInvokedCallback).onBackInvoked();
+        verify(mAnimatorCallback).onBackInvoked();
     }
 
     @Test
@@ -225,99 +244,97 @@
         mController = new BackAnimationController(shellInit, mShellController,
                 mShellExecutor, new Handler(mTestableLooper.getLooper()),
                 mActivityTaskManager, mContext,
-                mContentResolver, mTransitions);
+                mContentResolver, mAnimationBackground);
         shellInit.init();
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
+        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
 
-        IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class);
-        ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+        ArgumentCaptor<BackMotionEvent> backEventCaptor =
+                ArgumentCaptor.forClass(BackMotionEvent.class);
 
-        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback);
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false);
 
         triggerBackGesture();
 
-        verify(appCallback, never()).onBackStarted(any(BackEvent.class));
-        verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
-        verify(appCallback, times(1)).onBackInvoked();
+        verify(mAppCallback, never()).onBackStarted(any());
+        verify(mAppCallback, never()).onBackProgressed(backEventCaptor.capture());
+        verify(mAppCallback, times(1)).onBackInvoked();
 
-        verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackEvent.class));
-        verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
-        verify(mIOnBackInvokedCallback, never()).onBackInvoked();
+        verify(mAnimatorCallback, never()).onBackStarted(any());
+        verify(mAnimatorCallback, never()).onBackProgressed(backEventCaptor.capture());
+        verify(mAnimatorCallback, never()).onBackInvoked();
         verify(mBackAnimationRunner, never()).onAnimationStart(
                 anyInt(), any(), any(), any(), any());
     }
 
     @Test
     public void ignoresGesture_transitionInProgress() throws RemoteException {
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
-        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
 
         triggerBackGesture();
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
         // Check that back invocation is dispatched.
-        verify(mIOnBackInvokedCallback).onBackInvoked();
+        verify(mAnimatorCallback).onBackInvoked();
         verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
 
-        reset(mIOnBackInvokedCallback);
+        reset(mAnimatorCallback);
         reset(mBackAnimationRunner);
 
         // Verify that we prevent animation from restarting if another gestures happens before
         // the previous transition is finished.
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
-        verifyNoMoreInteractions(mIOnBackInvokedCallback);
-        mController.onBackAnimationFinished();
-        // Pretend the transition handler called finishAnimation.
-        mController.finishBackNavigation();
+        verifyNoMoreInteractions(mAnimatorCallback);
+
+        // Finish back navigation.
+        simulateRemoteAnimationFinished();
 
         // Verify that more events from a rejected swipe cannot start animation.
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
         doMotionEvent(MotionEvent.ACTION_UP, 0);
-        verifyNoMoreInteractions(mIOnBackInvokedCallback);
+        verifyNoMoreInteractions(mAnimatorCallback);
 
         // Verify that we start accepting gestures again once transition finishes.
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
 
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
-        verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+        verify(mAnimatorCallback).onBackStarted(any());
         verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
     }
 
     @Test
     public void acceptsGesture_transitionTimeout() throws RemoteException {
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
-        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
+
+        // In case it is still running in animation.
+        doNothing().when(mAnimatorCallback).onBackInvoked();
 
         triggerBackGesture();
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
 
-        reset(mIOnBackInvokedCallback);
-
         // Simulate transition timeout.
         mShellExecutor.flushAll();
-        mController.onBackAnimationFinished();
-        // Pretend the transition handler called finishAnimation.
-        mController.finishBackNavigation();
+        reset(mAnimatorCallback);
 
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
-        verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+        verify(mAnimatorCallback).onBackStarted(any());
     }
 
-
     @Test
     public void cancelBackInvokeWhenLostFocus() throws RemoteException {
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
+        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
 
-        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
 
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         // Check that back start and progress is dispatched when first move.
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
 
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
-        verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+        verify(mAnimatorCallback).onBackStarted(any());
         verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
 
         // Check that back invocation is dispatched.
@@ -327,11 +344,74 @@
         IBinder token = mock(IBinder.class);
         mController.mFocusObserver.focusLost(token);
         mShellExecutor.flushAll();
-        verify(mIOnBackInvokedCallback).onBackCancelled();
+        verify(mAnimatorCallback).onBackCancelled();
 
         // No more back invoke.
         doMotionEvent(MotionEvent.ACTION_UP, 0);
-        verify(mIOnBackInvokedCallback, never()).onBackInvoked();
+        verify(mAnimatorCallback, never()).onBackInvoked();
+    }
+
+    @Test
+    public void animationNotDefined() throws RemoteException {
+        final int[] testTypes = new int[] {
+                BackNavigationInfo.TYPE_RETURN_TO_HOME,
+                BackNavigationInfo.TYPE_CROSS_TASK,
+                BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+                BackNavigationInfo.TYPE_DIALOG_CLOSE};
+
+        for (int type: testTypes) {
+            unregisterAnimation(type);
+        }
+
+        for (int type: testTypes) {
+            final ResultListener result = new ResultListener();
+            createNavigationInfo(new BackNavigationInfo.Builder()
+                    .setType(type)
+                    .setOnBackInvokedCallback(mAppCallback)
+                    .setPrepareRemoteAnimation(true)
+                    .setOnBackNavigationDone(new RemoteCallback(result)));
+            triggerBackGesture();
+            simulateRemoteAnimationStart(type);
+            mShellExecutor.flushAll();
+
+            assertTrue("Navigation Done callback not called for "
+                    + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+            assertTrue("TriggerBack should have been true", result.mTriggerBack);
+        }
+
+        verify(mAppCallback, never()).onBackStarted(any());
+        verify(mAppCallback, never()).onBackProgressed(any());
+        verify(mAppCallback, times(testTypes.length)).onBackInvoked();
+
+        verify(mAnimatorCallback, never()).onBackStarted(any());
+        verify(mAnimatorCallback, never()).onBackProgressed(any());
+        verify(mAnimatorCallback, never()).onBackInvoked();
+    }
+
+    @Test
+    public void callbackShouldDeliverProgress() throws RemoteException {
+        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+
+        final int type = BackNavigationInfo.TYPE_CALLBACK;
+        final ResultListener result = new ResultListener();
+        createNavigationInfo(new BackNavigationInfo.Builder()
+                .setType(type)
+                .setOnBackInvokedCallback(mAppCallback)
+                .setOnBackNavigationDone(new RemoteCallback(result)));
+        triggerBackGesture();
+        mShellExecutor.flushAll();
+
+        assertTrue("Navigation Done callback not called for "
+                + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+        assertTrue("TriggerBack should have been true", result.mTriggerBack);
+
+        verify(mAppCallback, times(1)).onBackStarted(any());
+        verify(mAppCallback, times(1)).onBackProgressed(any());
+        verify(mAppCallback, times(1)).onBackInvoked();
+
+        verify(mAnimatorCallback, never()).onBackStarted(any());
+        verify(mAnimatorCallback, never()).onBackProgressed(any());
+        verify(mAnimatorCallback, never()).onBackInvoked();
     }
 
     private void doMotionEvent(int actionDown, int coordinate) {
@@ -339,7 +419,6 @@
                 coordinate, coordinate,
                 actionDown,
                 BackEvent.EDGE_LEFT);
-        mEventTime += 10;
     }
 
     private void simulateRemoteAnimationStart(int type) throws RemoteException {
@@ -351,4 +430,28 @@
             mShellExecutor.flushAll();
         }
     }
+
+    private void simulateRemoteAnimationFinished() {
+        mController.onBackAnimationFinished();
+        mController.finishBackNavigation();
+    }
+
+    private void registerAnimation(int type) {
+        mController.registerAnimation(type,
+                new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
+    }
+
+    private void unregisterAnimation(int type) {
+        mController.unregisterAnimation(type);
+    }
+
+    private static class ResultListener implements RemoteCallback.OnResultListener {
+        boolean mBackNavigationDone = false;
+        boolean mTriggerBack = false;
+        @Override
+        public void onResult(@Nullable Bundle result) {
+            mBackNavigationDone = true;
+            mTriggerBack = result.getBoolean(KEY_TRIGGER_BACK);
+        }
+    };
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/OWNERS
new file mode 100644
index 0000000..1e0f9bc
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/OWNERS
@@ -0,0 +1,5 @@
+# WM shell sub-module back navigation owners
+# Bug component: 1152663
+shanh@google.com
+arthurhung@google.com
+wilsonshih@google.com
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
index 3aefc3f..ba9c159 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 
 import android.window.BackEvent;
+import android.window.BackMotionEvent;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -38,7 +39,7 @@
     @Test
     public void generatesProgress_onStart() {
         mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
-        BackEvent event = mTouchTracker.createStartEvent(null);
+        BackMotionEvent event = mTouchTracker.createStartEvent(null);
         assertEquals(event.getProgress(), 0f, 0f);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index a6f19e7..40f2e88 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -97,13 +97,13 @@
 
     @Test
     public void showInsets_schedulesNoWorkOnExecutor() {
-        mPerDisplay.showInsets(ime(), true);
+        mPerDisplay.showInsets(ime(), true /* fromIme */, null /* statsToken */);
         verifyZeroInteractions(mExecutor);
     }
 
     @Test
     public void hideInsets_schedulesNoWorkOnExecutor() {
-        mPerDisplay.hideInsets(ime(), true);
+        mPerDisplay.hideInsets(ime(), true /* fromIme */, null /* statsToken */);
         verifyZeroInteractions(mExecutor);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 39db328..956f1cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.os.RemoteException;
 import android.util.SparseArray;
@@ -33,6 +34,7 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.WindowInsets;
+import android.view.inputmethod.ImeTracker;
 
 import androidx.test.filters.SmallTest;
 
@@ -111,8 +113,10 @@
                 WindowInsets.Type.defaultVisible());
         mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
         mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null);
-        mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false);
-        mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false);
+        mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false,
+                null /* statsToken */);
+        mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false,
+                null /* statsToken */);
         mExecutor.flushAll();
 
         assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
@@ -131,8 +135,10 @@
                 WindowInsets.Type.defaultVisible());
         mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
         mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null);
-        mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false);
-        mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false);
+        mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false,
+                null /* statsToken */);
+        mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false,
+                null /* statsToken */);
         mExecutor.flushAll();
 
         assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
@@ -191,12 +197,12 @@
         }
 
         @Override
-        public void showInsets(int types, boolean fromIme) {
+        public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {
             showInsetsCount++;
         }
 
         @Override
-        public void hideInsets(int types, boolean fromIme) {
+        public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {
             hideInsetsCount++;
         }
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index f6d6c03..3d77948 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -105,7 +105,8 @@
     @Test
     public void testUpdateDivideBounds() {
         mSplitLayout.updateDivideBounds(anyInt());
-        verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class));
+        verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class), anyInt(),
+                anyInt());
     }
 
     @Test
@@ -140,7 +141,7 @@
         DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
                 DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START);
 
-        mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget);
+        mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
         waitDividerFlingFinished();
         verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false), anyInt());
     }
@@ -152,7 +153,7 @@
         DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
                 DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END);
 
-        mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget);
+        mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
         waitDividerFlingFinished();
         verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true), anyInt());
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 79b520c..dad9133 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -16,10 +16,13 @@
 
 package com.android.wm.shell.desktopmode;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
@@ -30,13 +33,14 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -46,6 +50,7 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 import android.window.WindowContainerTransaction.Change;
+import android.window.WindowContainerTransaction.HierarchyOp;
 
 import androidx.test.filters.SmallTest;
 
@@ -68,6 +73,9 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class DesktopModeControllerTest extends ShellTestCase {
@@ -83,9 +91,7 @@
     @Mock
     private Handler mMockHandler;
     @Mock
-    private Transitions mMockTransitions;
-    private TestShellExecutor mExecutor;
-
+    private Transitions mTransitions;
     private DesktopModeController mController;
     private DesktopModeTaskRepository mDesktopModeTaskRepository;
     private ShellInit mShellInit;
@@ -94,23 +100,21 @@
     @Before
     public void setUp() {
         mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking();
+        when(DesktopModeStatus.isSupported()).thenReturn(true);
         when(DesktopModeStatus.isActive(any())).thenReturn(true);
 
         mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
-        mExecutor = new TestShellExecutor();
 
         mDesktopModeTaskRepository = new DesktopModeTaskRepository();
 
-        mController = new DesktopModeController(mContext, mShellInit, mShellController,
-                mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mMockTransitions,
-                mDesktopModeTaskRepository, mMockHandler, mExecutor);
+        mController = createController();
 
-        when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
-                new WindowContainerTransaction());
+        when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
 
         mShellInit.init();
         clearInvocations(mShellTaskOrganizer);
         clearInvocations(mRootTaskDisplayAreaOrganizer);
+        clearInvocations(mTransitions);
     }
 
     @After
@@ -120,131 +124,204 @@
 
     @Test
     public void instantiate_addInitCallback() {
-        verify(mShellInit, times(1)).addInitCallback(any(), any());
+        verify(mShellInit).addInitCallback(any(), any());
     }
 
     @Test
-    public void testDesktopModeEnabled_taskWmClearedDisplaySetToFreeform() {
-        // Create a fake WCT to simulate setting task windowing mode to undefined
-        WindowContainerTransaction taskWct = new WindowContainerTransaction();
-        MockToken taskMockToken = new MockToken();
-        taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED);
-        when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
-                mContext.getDisplayId())).thenReturn(taskWct);
+    public void instantiate_flagOff_doNotAddInitCallback() {
+        when(DesktopModeStatus.isSupported()).thenReturn(false);
+        clearInvocations(mShellInit);
 
-        // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
-        MockToken displayMockToken = new MockToken();
-        DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
-                mContext.getDisplayId(), 0);
-        when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
-                .thenReturn(displayAreaInfo);
+        createController();
 
-        // The test
+        verify(mShellInit, never()).addInitCallback(any(), any());
+    }
+
+    @Test
+    public void testDesktopModeEnabled_rootTdaSetToFreeform() {
+        DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
+
         mController.updateDesktopModeActive(true);
+        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
 
-        ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
-                WindowContainerTransaction.class);
-        verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
-
-        // WCT should have 2 changes - clear task wm mode and set display wm mode
-        WindowContainerTransaction wct = arg.getValue();
-        assertThat(wct.getChanges()).hasSize(2);
-
-        // Verify executed WCT has a change for setting task windowing mode to undefined
-        Change taskWmModeChange = wct.getChanges().get(taskMockToken.binder());
-        assertThat(taskWmModeChange).isNotNull();
-        assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
-
-        // Verify executed WCT has a change for setting display windowing mode to freeform
-        Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
-        assertThat(displayWmModeChange).isNotNull();
-        assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
+        // 1 change: Root TDA windowing mode
+        assertThat(wct.getChanges().size()).isEqualTo(1);
+        // Verify WCT has a change for setting windowing mode to freeform
+        Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
+        assertThat(change).isNotNull();
+        assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
     }
 
     @Test
-    public void testDesktopModeDisabled_taskWmAndBoundsClearedDisplaySetToFullscreen() {
-        // Create a fake WCT to simulate setting task windowing mode to undefined
-        WindowContainerTransaction taskWmWct = new WindowContainerTransaction();
-        MockToken taskWmMockToken = new MockToken();
-        taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED);
-        when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
-                mContext.getDisplayId())).thenReturn(taskWmWct);
+    public void testDesktopModeDisabled_rootTdaSetToFullscreen() {
+        DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
 
-        // Create a fake WCT to simulate clearing task bounds
-        WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction();
-        MockToken taskBoundsMockToken = new MockToken();
-        taskBoundsWct.setBounds(taskBoundsMockToken.token(), null);
-        when(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(
-                mContext.getDisplayId())).thenReturn(taskBoundsWct);
-
-        // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
-        MockToken displayMockToken = new MockToken();
-        DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
-                mContext.getDisplayId(), 0);
-        when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
-                .thenReturn(displayAreaInfo);
-
-        // The test
         mController.updateDesktopModeActive(false);
+        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
 
-        ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
-                WindowContainerTransaction.class);
-        verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
-
-        // WCT should have 3 changes - clear task wm mode and bounds and set display wm mode
-        WindowContainerTransaction wct = arg.getValue();
-        assertThat(wct.getChanges()).hasSize(3);
-
-        // Verify executed WCT has a change for setting task windowing mode to undefined
-        Change taskWmMode = wct.getChanges().get(taskWmMockToken.binder());
-        assertThat(taskWmMode).isNotNull();
-        assertThat(taskWmMode.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
-
-        // Verify executed WCT has a change for clearing task bounds
-        Change bounds = wct.getChanges().get(taskBoundsMockToken.binder());
-        assertThat(bounds).isNotNull();
-        assertThat(bounds.getWindowSetMask() & WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
-        assertThat(bounds.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
-
-        // Verify executed WCT has a change for setting display windowing mode to fullscreen
-        Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
-        assertThat(displayWmModeChange).isNotNull();
-        assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+        // 1 change: Root TDA windowing mode
+        assertThat(wct.getChanges().size()).isEqualTo(1);
+        // Verify WCT has a change for setting windowing mode to fullscreen
+        Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
+        assertThat(change).isNotNull();
+        assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
     }
 
     @Test
-    public void testShowDesktopApps() {
-        // Set up two active tasks on desktop
-        mDesktopModeTaskRepository.addActiveTask(1);
-        mDesktopModeTaskRepository.addActiveTask(2);
-        MockToken token1 = new MockToken();
-        MockToken token2 = new MockToken();
-        ActivityManager.RunningTaskInfo taskInfo1 = new TestRunningTaskInfoBuilder().setToken(
-                token1.token()).setLastActiveTime(100).build();
-        ActivityManager.RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder().setToken(
-                token2.token()).setLastActiveTime(200).build();
-        when(mShellTaskOrganizer.getRunningTaskInfo(1)).thenReturn(taskInfo1);
-        when(mShellTaskOrganizer.getRunningTaskInfo(2)).thenReturn(taskInfo2);
+    public void testDesktopModeEnabled_windowingModeCleared() {
+        createMockDisplayArea();
+        RunningTaskInfo freeformTask = createFreeformTask();
+        RunningTaskInfo fullscreenTask = createFullscreenTask();
+        RunningTaskInfo homeTask = createHomeTask();
+        when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+                Arrays.asList(freeformTask, fullscreenTask, homeTask)));
+
+        mController.updateDesktopModeActive(true);
+        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+        // 2 changes: Root TDA windowing mode and 1 task
+        assertThat(wct.getChanges().size()).isEqualTo(2);
+        // No changes for tasks that are not standard or freeform
+        assertThat(wct.getChanges().get(fullscreenTask.token.asBinder())).isNull();
+        assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
+        // Standard freeform task has windowing mode cleared
+        Change change = wct.getChanges().get(freeformTask.token.asBinder());
+        assertThat(change).isNotNull();
+        assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+    }
+
+    @Test
+    public void testDesktopModeDisabled_windowingModeAndBoundsCleared() {
+        createMockDisplayArea();
+        RunningTaskInfo freeformTask = createFreeformTask();
+        RunningTaskInfo fullscreenTask = createFullscreenTask();
+        RunningTaskInfo homeTask = createHomeTask();
+        when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+                Arrays.asList(freeformTask, fullscreenTask, homeTask)));
+
+        mController.updateDesktopModeActive(false);
+        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+        // 3 changes: Root TDA windowing mode and 2 tasks
+        assertThat(wct.getChanges().size()).isEqualTo(3);
+        // No changes to home task
+        assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
+        // Standard tasks have bounds cleared
+        assertThatBoundsCleared(wct.getChanges().get(freeformTask.token.asBinder()));
+        assertThatBoundsCleared(wct.getChanges().get(fullscreenTask.token.asBinder()));
+        // Freeform standard tasks have windowing mode cleared
+        assertThat(wct.getChanges().get(
+                freeformTask.token.asBinder()).getWindowingMode()).isEqualTo(
+                WINDOWING_MODE_UNDEFINED);
+    }
+
+    @Test
+    public void testDesktopModeEnabled_homeTaskBehindVisibleTask() {
+        createMockDisplayArea();
+        RunningTaskInfo fullscreenTask1 = createFullscreenTask();
+        fullscreenTask1.isVisible = true;
+        RunningTaskInfo fullscreenTask2 = createFullscreenTask();
+        fullscreenTask2.isVisible = false;
+        RunningTaskInfo homeTask = createHomeTask();
+        when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+                Arrays.asList(fullscreenTask1, fullscreenTask2, homeTask)));
+
+        mController.updateDesktopModeActive(true);
+        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+        // Check that there are hierarchy changes for home task and visible task
+        assertThat(wct.getHierarchyOps()).hasSize(2);
+        // First show home task
+        HierarchyOp op1 = wct.getHierarchyOps().get(0);
+        assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+        assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder());
+
+        // Then visible task on top of it
+        HierarchyOp op2 = wct.getHierarchyOps().get(1);
+        assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+        assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder());
+    }
+
+    @Test
+    public void testShowDesktopApps_allAppsInvisible_bringsToFront() {
+        // Set up two active tasks on desktop, task2 is on top of task1.
+        RunningTaskInfo freeformTask1 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+                freeformTask1.taskId, false /* visible */);
+        RunningTaskInfo freeformTask2 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+                freeformTask2.taskId, false /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
+                freeformTask1);
+        when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
+                freeformTask2);
 
         // Run show desktop apps logic
         mController.showDesktopApps();
-        ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
-                WindowContainerTransaction.class);
-        verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
-        WindowContainerTransaction wct = wctCaptor.getValue();
 
+        final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
         // Check wct has reorder calls
         assertThat(wct.getHierarchyOps()).hasSize(2);
 
-        // Task 2 has activity later, must be first
-        WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+        // Task 1 appeared first, must be first reorder to top.
+        HierarchyOp op1 = wct.getHierarchyOps().get(0);
         assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op1.getContainer()).isEqualTo(token2.binder());
+        assertThat(op1.getContainer()).isEqualTo(freeformTask1.token.asBinder());
 
-        // Task 1 should be second
-        WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(0);
+        // Task 2 appeared last, must be last reorder to top.
+        HierarchyOp op2 = wct.getHierarchyOps().get(1);
         assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op2.getContainer()).isEqualTo(token2.binder());
+        assertThat(op2.getContainer()).isEqualTo(freeformTask2.token.asBinder());
+    }
+
+    @Test
+    public void testShowDesktopApps_appsAlreadyVisible_doesNothing() {
+        final RunningTaskInfo task1 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
+        final RunningTaskInfo task2 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
+
+        mController.showDesktopApps();
+
+        final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+        // No reordering needed.
+        assertThat(wct.getHierarchyOps()).isEmpty();
+    }
+
+    @Test
+    public void testShowDesktopApps_someAppsInvisible_reordersAll() {
+        final RunningTaskInfo task1 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, false /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
+        final RunningTaskInfo task2 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
+
+        mController.showDesktopApps();
+
+        final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+        // Both tasks should be reordered to top, even if one was already visible.
+        assertThat(wct.getHierarchyOps()).hasSize(2);
+        final HierarchyOp op1 = wct.getHierarchyOps().get(0);
+        assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+        assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
+        final HierarchyOp op2 = wct.getHierarchyOps().get(1);
+        assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+        assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
     }
 
     @Test
@@ -266,7 +343,7 @@
 
     @Test
     public void testHandleTransitionRequest_notFreeform_returnsNull() {
-        ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+        RunningTaskInfo trigger = new RunningTaskInfo();
         trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         WindowContainerTransaction wct = mController.handleRequest(
                 new Binder(),
@@ -276,7 +353,7 @@
 
     @Test
     public void testHandleTransitionRequest_returnsWct() {
-        ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+        RunningTaskInfo trigger = new RunningTaskInfo();
         trigger.token = new MockToken().mToken;
         trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
         WindowContainerTransaction wct = mController.handleRequest(
@@ -285,6 +362,74 @@
         assertThat(wct).isNotNull();
     }
 
+    private DesktopModeController createController() {
+        return new DesktopModeController(mContext, mShellInit, mShellController,
+                mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
+                mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
+    }
+
+    private DisplayAreaInfo createMockDisplayArea() {
+        DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().mToken,
+                mContext.getDisplayId(), 0);
+        when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
+                .thenReturn(displayAreaInfo);
+        return displayAreaInfo;
+    }
+
+    private RunningTaskInfo createFreeformTask() {
+        return new TestRunningTaskInfoBuilder()
+                .setToken(new MockToken().token())
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                .setLastActiveTime(100)
+                .build();
+    }
+
+    private RunningTaskInfo createFullscreenTask() {
+        return new TestRunningTaskInfoBuilder()
+                .setToken(new MockToken().token())
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setLastActiveTime(100)
+                .build();
+    }
+
+    private RunningTaskInfo createHomeTask() {
+        return new TestRunningTaskInfoBuilder()
+                .setToken(new MockToken().token())
+                .setActivityType(ACTIVITY_TYPE_HOME)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setLastActiveTime(100)
+                .build();
+    }
+
+    private WindowContainerTransaction getDesktopModeSwitchTransaction() {
+        ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+                WindowContainerTransaction.class);
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            verify(mTransitions).startTransition(eq(TRANSIT_CHANGE), arg.capture(), any());
+        } else {
+            verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
+        }
+        return arg.getValue();
+    }
+
+    private WindowContainerTransaction getBringAppsToFrontTransaction() {
+        final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+                WindowContainerTransaction.class);
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), arg.capture(), any());
+        } else {
+            verify(mShellTaskOrganizer).applyTransaction(arg.capture());
+        }
+        return arg.getValue();
+    }
+
+    private void assertThatBoundsCleared(Change change) {
+        assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue();
+        assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
+    }
+
     private static class MockToken {
         private final WindowContainerToken mToken;
         private final IBinder mBinder;
@@ -298,9 +443,5 @@
         WindowContainerToken token() {
             return mToken;
         }
-
-        IBinder binder() {
-            return mBinder;
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index aaa5c8a..1e43a59 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -140,6 +140,32 @@
         assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3)
     }
 
+    @Test
+    fun addOrMoveFreeformTaskToTop_didNotExist_addsToTop() {
+        repo.addOrMoveFreeformTaskToTop(5)
+        repo.addOrMoveFreeformTaskToTop(6)
+        repo.addOrMoveFreeformTaskToTop(7)
+
+        val tasks = repo.getFreeformTasksInZOrder()
+        assertThat(tasks.size).isEqualTo(3)
+        assertThat(tasks[0]).isEqualTo(7)
+        assertThat(tasks[1]).isEqualTo(6)
+        assertThat(tasks[2]).isEqualTo(5)
+    }
+
+    @Test
+    fun addOrMoveFreeformTaskToTop_alreadyExists_movesToTop() {
+        repo.addOrMoveFreeformTaskToTop(5)
+        repo.addOrMoveFreeformTaskToTop(6)
+        repo.addOrMoveFreeformTaskToTop(7)
+
+        repo.addOrMoveFreeformTaskToTop(6)
+
+        val tasks = repo.getFreeformTasksInZOrder()
+        assertThat(tasks.size).isEqualTo(3)
+        assertThat(tasks.first()).isEqualTo(6)
+    }
+
     class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
         var activeTaskChangedCalls = 0
         override fun onActiveTasksChanged() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
deleted file mode 100644
index d378a17..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
+++ /dev/null
@@ -1,261 +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.wm.shell.floating;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.wm.shell.floating.FloatingTasksController.SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
-
-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.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.graphics.Insets;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.floating.views.FloatingTaskLayer;
-import com.android.wm.shell.sysui.ShellCommandHandler;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.sysui.ShellSharedConstants;
-
-import org.junit.After;
-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.Optional;
-
-/**
- * Tests for the floating tasks controller.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class FloatingTasksControllerTest extends ShellTestCase {
-    // Some behavior in the controller constructor is dependent on this so we can only
-    // validate if it's working for the real value for those things.
-    private static final boolean FLOATING_TASKS_ACTUALLY_ENABLED =
-            SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false);
-
-    @Mock private ShellInit mShellInit;
-    @Mock private ShellController mShellController;
-    @Mock private WindowManager mWindowManager;
-    @Mock private ShellTaskOrganizer mTaskOrganizer;
-    @Captor private ArgumentCaptor<FloatingTaskLayer> mFloatingTaskLayerCaptor;
-
-    private FloatingTasksController mController;
-
-    @Before
-    public void setUp() throws RemoteException {
-        MockitoAnnotations.initMocks(this);
-
-        WindowMetrics windowMetrics = mock(WindowMetrics.class);
-        WindowInsets windowInsets = mock(WindowInsets.class);
-        Insets insets = Insets.of(0, 0, 0, 0);
-        when(mWindowManager.getCurrentWindowMetrics()).thenReturn(windowMetrics);
-        when(windowMetrics.getWindowInsets()).thenReturn(windowInsets);
-        when(windowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1000, 1000));
-        when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(insets);
-
-        // For the purposes of this test, just run everything synchronously
-        ShellExecutor shellExecutor = new TestShellExecutor();
-        when(mTaskOrganizer.getExecutor()).thenReturn(shellExecutor);
-    }
-
-    @After
-    public void tearDown() {
-        if (mController != null) {
-            mController.removeTask();
-            mController = null;
-        }
-    }
-
-    private void setUpTabletConfig() {
-        Configuration config = mock(Configuration.class);
-        config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
-        mController.setConfig(config);
-    }
-
-    private void setUpPhoneConfig() {
-        Configuration config = mock(Configuration.class);
-        config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET - 1;
-        mController.setConfig(config);
-    }
-
-    private void createController() {
-        mController = new FloatingTasksController(mContext,
-                mShellInit,
-                mShellController,
-                mock(ShellCommandHandler.class),
-                Optional.empty(),
-                mWindowManager,
-                mTaskOrganizer,
-                mock(TaskViewTransitions.class),
-                mock(ShellExecutor.class),
-                mock(ShellExecutor.class),
-                mock(SyncTransactionQueue.class));
-        spyOn(mController);
-    }
-
-    //
-    // Shell specific
-    //
-    @Test
-    public void instantiateController_addInitCallback() {
-        if (FLOATING_TASKS_ACTUALLY_ENABLED) {
-            createController();
-            setUpTabletConfig();
-
-            verify(mShellInit, times(1)).addInitCallback(any(), any());
-        }
-    }
-
-    @Test
-    public void instantiateController_doesntAddInitCallback() {
-        if (!FLOATING_TASKS_ACTUALLY_ENABLED) {
-            createController();
-
-            verify(mShellInit, never()).addInitCallback(any(), any());
-        }
-    }
-
-    @Test
-    public void onInit_registerConfigChangeListener() {
-        if (FLOATING_TASKS_ACTUALLY_ENABLED) {
-            createController();
-            setUpTabletConfig();
-            mController.onInit();
-
-            verify(mShellController, times(1)).addConfigurationChangeListener(any());
-        }
-    }
-
-    @Test
-    public void onInit_addExternalInterface() {
-        if (FLOATING_TASKS_ACTUALLY_ENABLED) {
-            createController();
-            setUpTabletConfig();
-            mController.onInit();
-
-            verify(mShellController, times(1)).addExternalInterface(
-                    ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS, any(), any());
-        }
-    }
-
-    //
-    // Tests for floating layer, which is only available for tablets.
-    //
-
-    @Test
-    public void testIsFloatingLayerAvailable_true() {
-        createController();
-        setUpTabletConfig();
-        assertThat(mController.isFloatingLayerAvailable()).isTrue();
-    }
-
-    @Test
-    public void testIsFloatingLayerAvailable_false() {
-        createController();
-        setUpPhoneConfig();
-        assertThat(mController.isFloatingLayerAvailable()).isFalse();
-    }
-
-    //
-    // Tests for floating tasks being enabled, guarded by sysprop flag.
-    //
-
-    @Test
-    public void testIsFloatingTasksEnabled_true() {
-        createController();
-        mController.setFloatingTasksEnabled(true);
-        setUpTabletConfig();
-        assertThat(mController.isFloatingTasksEnabled()).isTrue();
-    }
-
-    @Test
-    public void testIsFloatingTasksEnabled_false() {
-        createController();
-        mController.setFloatingTasksEnabled(false);
-        setUpTabletConfig();
-        assertThat(mController.isFloatingTasksEnabled()).isFalse();
-    }
-
-    //
-    // Tests for behavior depending on flags
-    //
-
-    @Test
-    public void testShowTaskIntent_enabled() {
-        createController();
-        mController.setFloatingTasksEnabled(true);
-        setUpTabletConfig();
-
-        mController.showTask(mock(Intent.class));
-        verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any());
-        assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1);
-    }
-
-    @Test
-    public void testShowTaskIntent_notEnabled() {
-        createController();
-        mController.setFloatingTasksEnabled(false);
-        setUpTabletConfig();
-
-        mController.showTask(mock(Intent.class));
-        verify(mWindowManager, never()).addView(any(), any());
-    }
-
-    @Test
-    public void testRemoveTask() {
-        createController();
-        mController.setFloatingTasksEnabled(true);
-        setUpTabletConfig();
-
-        mController.showTask(mock(Intent.class));
-        verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any());
-        assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1);
-
-        mController.removeTask();
-        verify(mWindowManager).removeView(mFloatingTaskLayerCaptor.capture());
-        assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(0);
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 7068a84..48415d4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -103,7 +103,7 @@
         mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
         mTransitionObserver.onTransitionStarting(transition);
 
-        verify(mWindowDecorViewModel).createWindowDecoration(
+        verify(mWindowDecorViewModel).onTaskOpening(
                 change.getTaskInfo(), change.getLeash(), startT, finishT);
     }
 
@@ -120,7 +120,7 @@
         mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
         mTransitionObserver.onTransitionStarting(transition);
 
-        verify(mWindowDecorViewModel).setupWindowDecorationForTransition(
+        verify(mWindowDecorViewModel).onTaskClosing(
                 change.getTaskInfo(), startT, finishT);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
new file mode 100644
index 0000000..736d4cf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
@@ -0,0 +1,3 @@
+# WM shell sub-module TV pip owners
+galinap@google.com
+bronger@google.com
\ No newline at end of file
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..f8ded77 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
@@ -16,18 +16,24 @@
 
 package com.android.wm.shell.splitscreen;
 
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -35,10 +41,13 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
 import android.content.ComponentName;
 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;
 
@@ -64,11 +73,11 @@
 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.Optional;
-
 /**
  * Tests for {@link SplitScreenController}
  */
@@ -90,18 +99,21 @@
     @Mock Transitions mTransitions;
     @Mock TransactionPool mTransactionPool;
     @Mock IconProvider mIconProvider;
-    @Mock Optional<RecentTasksController> mRecentTasks;
+    @Mock StageCoordinator mStageCoordinator;
+    @Mock RecentTasksController mRecentTasks;
+    @Captor ArgumentCaptor<Intent> mIntentCaptor;
 
     private SplitScreenController mSplitScreenController;
 
     @Before
     public void setup() {
+        assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
         MockitoAnnotations.initMocks(this);
         mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
                 mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
                 mRootTDAOrganizer, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
-                mIconProvider, mRecentTasks, mMainExecutor));
+                mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator));
     }
 
     @Test
@@ -110,6 +122,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void instantiateController_registerDumpCallback() {
         doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
         when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
@@ -118,6 +131,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void instantiateController_registerCommandCallback() {
         doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
         when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
@@ -126,6 +140,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void testControllerRegistersKeyguardChangeListener() {
         doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
         when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
@@ -134,6 +149,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void instantiateController_addExternalInterface() {
         doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
         when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
@@ -143,58 +159,100 @@
     }
 
     @Test
-    public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
-        doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
-        doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
-
-        // Verify launching the same activity returns true.
+    public void testStartIntent_appendsNoUserActionFlag() {
         Intent startIntent = createStartIntent("startActivity");
-        ActivityManager.RunningTaskInfo focusTaskInfo =
-                createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
-        doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
-        assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
-                startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
 
-        // Verify launching different activity returns false.
-        Intent diffIntent = createStartIntent("diffActivity");
-        focusTaskInfo =
-                createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent);
-        doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
-        assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
-                startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+        mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+        verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+                eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+        assertEquals(FLAG_ACTIVITY_NO_USER_ACTION,
+                mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
     }
 
     @Test
-    public void testShouldAddMultipleTaskFlag_inSplitScreen() {
-        doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+    public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
+        doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
         Intent startIntent = createStartIntent("startActivity");
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+        // Put the same component to the top running task
+        ActivityManager.RunningTaskInfo topRunningTask =
+                createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+        doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
+        doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+
+        mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+        verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+                eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+        assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK,
+                mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK);
+    }
+
+    @Test
+    public void startIntent_multiInstancesSupported_startTaskInBackgroundBeforeSplitActivated() {
+        doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+        doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+        Intent startIntent = createStartIntent("startActivity");
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+        // Put the same component to the top running task
+        ActivityManager.RunningTaskInfo topRunningTask =
+                createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+        doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
+        doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+        // Put the same component into a task in the background
+        ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
+        doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
+
+        mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+        verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+                isNull());
+    }
+
+    @Test
+    public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
+        doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+        doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+        Intent startIntent = createStartIntent("startActivity");
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+        // Put the same component into another side of the split
+        doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
         ActivityManager.RunningTaskInfo sameTaskInfo =
                 createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
-        Intent diffIntent = createStartIntent("diffActivity");
-        ActivityManager.RunningTaskInfo differentTaskInfo =
-                createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent);
+        doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+                SPLIT_POSITION_BOTTOM_OR_RIGHT);
+        // Put the same component into a task in the background
+        doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
+                .findTaskInBackground(any());
 
-        // Verify launching the same activity return false.
-        doReturn(sameTaskInfo).when(mSplitScreenController)
-                .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
-        assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
-                startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+        mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
 
-        // Verify launching the same activity as adjacent returns true.
-        doReturn(differentTaskInfo).when(mSplitScreenController)
-                .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
-        doReturn(sameTaskInfo).when(mSplitScreenController)
-                .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
-        assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
-                startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+        verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+                isNull());
+    }
 
-        // Verify launching different activity from adjacent returns false.
-        doReturn(differentTaskInfo).when(mSplitScreenController)
-                .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
-        doReturn(differentTaskInfo).when(mSplitScreenController)
-                .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
-        assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
-                startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+    @Test
+    public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
+        doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(any());
+        Intent startIntent = createStartIntent("startActivity");
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+        // Put the same component into another side of the split
+        doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+        ActivityManager.RunningTaskInfo sameTaskInfo =
+                createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
+        doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+                SPLIT_POSITION_BOTTOM_OR_RIGHT);
+
+        mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+        verify(mStageCoordinator).switchSplitPosition(anyString());
     }
 
     private Intent createStartIntent(String activityName) {
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/startingsurface/TaskSnapshotWindowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
deleted file mode 100644
index 004df2a2..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
+++ /dev/null
@@ -1,295 +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.wm.shell.startingsurface;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-
-import android.app.ActivityManager.TaskDescription;
-import android.content.ComponentName;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorSpace;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
-import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.WindowInsets;
-import android.window.TaskSnapshot;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestShellExecutor;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test class for {@link TaskSnapshotWindow}.
- *
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TaskSnapshotWindowTest extends ShellTestCase {
-
-    private TaskSnapshotWindow mWindow;
-
-    private void setupSurface(int width, int height) {
-        setupSurface(width, height, new Rect(), 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
-                new Rect(0, 0, width, height));
-    }
-
-    private void setupSurface(int width, int height, Rect contentInsets, int sysuiVis,
-            int windowFlags, Rect taskBounds) {
-        // Previously when constructing TaskSnapshots for this test, scale was 1.0f, so to mimic
-        // this behavior set the taskSize to be the same as the taskBounds width and height. The
-        // taskBounds passed here are assumed to be the same task bounds as when the snapshot was
-        // taken. We assume there is no aspect ratio mismatch between the screenshot and the
-        // taskBounds
-        assertEquals(width, taskBounds.width());
-        assertEquals(height, taskBounds.height());
-        Point taskSize = new Point(taskBounds.width(), taskBounds.height());
-
-        final TaskSnapshot snapshot = createTaskSnapshot(width, height, taskSize, contentInsets);
-        mWindow = new TaskSnapshotWindow(new SurfaceControl(), snapshot, "Test",
-                createTaskDescription(Color.WHITE, Color.RED, Color.BLUE),
-                0 /* appearance */, windowFlags /* windowFlags */, 0 /* privateWindowFlags */,
-                taskBounds, ORIENTATION_PORTRAIT, ACTIVITY_TYPE_STANDARD,
-                WindowInsets.Type.defaultVisible(), null /* clearWindow */,
-                new TestShellExecutor());
-    }
-
-    private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize,
-            Rect contentInsets) {
-        final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888,
-                1, HardwareBuffer.USAGE_CPU_READ_RARELY);
-        return new TaskSnapshot(
-                System.currentTimeMillis(),
-                new ComponentName("", ""), buffer,
-                ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
-                Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets */,
-                false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
-                0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */);
-    }
-
-    private static TaskDescription createTaskDescription(int background, int statusBar,
-            int navigationBar) {
-        final TaskDescription td = new TaskDescription();
-        td.setBackgroundColor(background);
-        td.setStatusBarColor(statusBar);
-        td.setNavigationBarColor(navigationBar);
-        return td;
-    }
-
-    @Test
-    public void fillEmptyBackground_fillHorizontally() {
-        setupSurface(200, 100);
-        final Canvas mockCanvas = mock(Canvas.class);
-        when(mockCanvas.getWidth()).thenReturn(200);
-        when(mockCanvas.getHeight()).thenReturn(100);
-        mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200));
-        verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
-    }
-
-    @Test
-    public void fillEmptyBackground_fillVertically() {
-        setupSurface(100, 200);
-        final Canvas mockCanvas = mock(Canvas.class);
-        when(mockCanvas.getWidth()).thenReturn(100);
-        when(mockCanvas.getHeight()).thenReturn(200);
-        mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100));
-        verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any());
-    }
-
-    @Test
-    public void fillEmptyBackground_fillBoth() {
-        setupSurface(200, 200);
-        final Canvas mockCanvas = mock(Canvas.class);
-        when(mockCanvas.getWidth()).thenReturn(200);
-        when(mockCanvas.getHeight()).thenReturn(200);
-        mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
-        verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
-        verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any());
-    }
-
-    @Test
-    public void fillEmptyBackground_dontFill_sameSize() {
-        setupSurface(100, 100);
-        final Canvas mockCanvas = mock(Canvas.class);
-        when(mockCanvas.getWidth()).thenReturn(100);
-        when(mockCanvas.getHeight()).thenReturn(100);
-        mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
-        verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
-    }
-
-    @Test
-    public void fillEmptyBackground_dontFill_bitmapLarger() {
-        setupSurface(100, 100);
-        final Canvas mockCanvas = mock(Canvas.class);
-        when(mockCanvas.getWidth()).thenReturn(100);
-        when(mockCanvas.getHeight()).thenReturn(100);
-        mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200));
-        verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
-    }
-
-    @Test
-    public void testCalculateSnapshotCrop() {
-        setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 0, 100, 100));
-        assertEquals(new Rect(0, 0, 100, 90), mWindow.calculateSnapshotCrop());
-    }
-
-    @Test
-    public void testCalculateSnapshotCrop_taskNotOnTop() {
-        setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 50, 100, 150));
-        assertEquals(new Rect(0, 10, 100, 90), mWindow.calculateSnapshotCrop());
-    }
-
-    @Test
-    public void testCalculateSnapshotCrop_navBarLeft() {
-        setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, 0, new Rect(0, 0, 100, 100));
-        assertEquals(new Rect(10, 0, 100, 100), mWindow.calculateSnapshotCrop());
-    }
-
-    @Test
-    public void testCalculateSnapshotCrop_navBarRight() {
-        setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, 0, new Rect(0, 0, 100, 100));
-        assertEquals(new Rect(0, 0, 90, 100), mWindow.calculateSnapshotCrop());
-    }
-
-    @Test
-    public void testCalculateSnapshotCrop_waterfall() {
-        setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100));
-        assertEquals(new Rect(5, 0, 95, 90), mWindow.calculateSnapshotCrop());
-    }
-
-    @Test
-    public void testCalculateSnapshotFrame() {
-        setupSurface(100, 100);
-        final Rect insets = new Rect(0, 10, 0, 10);
-        mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
-        assertEquals(new Rect(0, 0, 100, 80),
-                mWindow.calculateSnapshotFrame(new Rect(0, 10, 100, 90)));
-    }
-
-    @Test
-    public void testCalculateSnapshotFrame_navBarLeft() {
-        setupSurface(100, 100);
-        final Rect insets = new Rect(10, 10, 0, 0);
-        mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
-        assertEquals(new Rect(10, 0, 100, 90),
-                mWindow.calculateSnapshotFrame(new Rect(10, 10, 100, 100)));
-    }
-
-    @Test
-    public void testCalculateSnapshotFrame_waterfall() {
-        setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100));
-        final Rect insets = new Rect(0, 10, 0, 10);
-        mWindow.setFrames(new Rect(5, 0, 95, 100), insets);
-        assertEquals(new Rect(0, 0, 90, 90),
-                mWindow.calculateSnapshotFrame(new Rect(5, 0, 95, 90)));
-    }
-
-    @Test
-    public void testDrawStatusBarBackground() {
-        setupSurface(100, 100);
-        final Rect insets = new Rect(0, 10, 10, 0);
-        mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
-        final Canvas mockCanvas = mock(Canvas.class);
-        when(mockCanvas.getWidth()).thenReturn(100);
-        when(mockCanvas.getHeight()).thenReturn(100);
-        mWindow.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 50, 100));
-        verify(mockCanvas).drawRect(eq(50.0f), eq(0.0f), eq(90.0f), eq(10.0f), any());
-    }
-
-    @Test
-    public void testDrawStatusBarBackground_nullFrame() {
-        setupSurface(100, 100);
-        final Rect insets = new Rect(0, 10, 10, 0);
-        mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
-        final Canvas mockCanvas = mock(Canvas.class);
-        when(mockCanvas.getWidth()).thenReturn(100);
-        when(mockCanvas.getHeight()).thenReturn(100);
-        mWindow.drawStatusBarBackground(mockCanvas, null);
-        verify(mockCanvas).drawRect(eq(0.0f), eq(0.0f), eq(90.0f), eq(10.0f), any());
-    }
-
-    @Test
-    public void testDrawStatusBarBackground_nope() {
-        setupSurface(100, 100);
-        final Rect insets = new Rect(0, 10, 10, 0);
-        mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
-        final Canvas mockCanvas = mock(Canvas.class);
-        when(mockCanvas.getWidth()).thenReturn(100);
-        when(mockCanvas.getHeight()).thenReturn(100);
-        mWindow.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 100, 100));
-        verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
-    }
-
-    @Test
-    public void testDrawNavigationBarBackground() {
-        final Rect insets = new Rect(0, 10, 0, 10);
-        setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
-                new Rect(0, 0, 100, 100));
-        mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
-        final Canvas mockCanvas = mock(Canvas.class);
-        when(mockCanvas.getWidth()).thenReturn(100);
-        when(mockCanvas.getHeight()).thenReturn(100);
-        mWindow.drawNavigationBarBackground(mockCanvas);
-        verify(mockCanvas).drawRect(eq(new Rect(0, 90, 100, 100)), any());
-    }
-
-    @Test
-    public void testDrawNavigationBarBackground_left() {
-        final Rect insets = new Rect(10, 10, 0, 0);
-        setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
-                new Rect(0, 0, 100, 100));
-        mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
-        final Canvas mockCanvas = mock(Canvas.class);
-        when(mockCanvas.getWidth()).thenReturn(100);
-        when(mockCanvas.getHeight()).thenReturn(100);
-        mWindow.drawNavigationBarBackground(mockCanvas);
-        verify(mockCanvas).drawRect(eq(new Rect(0, 0, 10, 100)), any());
-    }
-
-    @Test
-    public void testDrawNavigationBarBackground_right() {
-        final Rect insets = new Rect(0, 10, 10, 0);
-        setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
-                new Rect(0, 0, 100, 100));
-        mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
-        final Canvas mockCanvas = mock(Canvas.class);
-        when(mockCanvas.getWidth()).thenReturn(100);
-        when(mockCanvas.getHeight()).thenReturn(100);
-        mWindow.drawNavigationBarBackground(mockCanvas);
-        verify(mockCanvas).drawRect(eq(new Rect(90, 0, 100, 100)), any());
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
new file mode 100644
index 0000000..ad6fced
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
@@ -0,0 +1,218 @@
+/*
+ * 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.windowdecor;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.InputChannel;
+import android.view.InputMonitor;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.GrantPermissionRule;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.DesktopModeController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+/** Tests of {@link CaptionWindowDecorViewModel} */
+@SmallTest
+public class CaptionWindowDecorViewModelTests extends ShellTestCase {
+    @Mock private CaptionWindowDecoration mCaptionWindowDecoration;
+
+    @Mock private CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
+
+    @Mock private Handler mMainHandler;
+
+    @Mock private Choreographer mMainChoreographer;
+
+    @Mock private ShellTaskOrganizer mTaskOrganizer;
+
+    @Mock private DisplayController mDisplayController;
+
+    @Mock private SyncTransactionQueue mSyncQueue;
+
+    @Mock private DesktopModeController mDesktopModeController;
+
+    @Mock private InputMonitor mInputMonitor;
+
+    @Mock private InputChannel mInputChannel;
+
+    @Mock private CaptionWindowDecorViewModel.EventReceiverFactory mEventReceiverFactory;
+
+    @Mock private CaptionWindowDecorViewModel.EventReceiver mEventReceiver;
+
+    @Mock private InputManager mInputManager;
+
+    private final List<InputManager> mMockInputManagers = new ArrayList<>();
+
+    private CaptionWindowDecorViewModel mCaptionWindowDecorViewModel;
+
+    @Before
+    public void setUp() {
+        mMockInputManagers.add(mInputManager);
+
+        mCaptionWindowDecorViewModel =
+            new CaptionWindowDecorViewModel(
+                mContext,
+                mMainHandler,
+                mMainChoreographer,
+                mTaskOrganizer,
+                mDisplayController,
+                mSyncQueue,
+                Optional.of(mDesktopModeController),
+                mCaptionWindowDecorFactory,
+                new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class)));
+        mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory);
+
+        doReturn(mCaptionWindowDecoration)
+            .when(mCaptionWindowDecorFactory)
+            .create(any(), any(), any(), any(), any(), any(), any(), any());
+
+        when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor);
+        when(mEventReceiverFactory.create(any(), any(), any())).thenReturn(mEventReceiver);
+        when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel);
+    }
+
+    @Test
+    public void testDeleteCaptionOnChangeTransitionWhenNecessary() throws Exception {
+        Looper.prepare();
+        final int taskId = 1;
+        final ActivityManager.RunningTaskInfo taskInfo =
+                createTaskInfo(taskId, WINDOWING_MODE_FREEFORM);
+        SurfaceControl surfaceControl = mock(SurfaceControl.class);
+        final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+        final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+        GrantPermissionRule.grant(android.Manifest.permission.MONITOR_INPUT);
+
+        mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+        verify(mCaptionWindowDecorFactory)
+                .create(
+                    mContext,
+                    mDisplayController,
+                    mTaskOrganizer,
+                    taskInfo,
+                    surfaceControl,
+                    mMainHandler,
+                    mMainChoreographer,
+                    mSyncQueue);
+
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+        mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+        verify(mCaptionWindowDecoration).close();
+    }
+
+    @Test
+    public void testCreateCaptionOnChangeTransitionWhenNecessary() throws Exception {
+        final int taskId = 1;
+        final ActivityManager.RunningTaskInfo taskInfo =
+                createTaskInfo(taskId, WINDOWING_MODE_UNDEFINED);
+        SurfaceControl surfaceControl = mock(SurfaceControl.class);
+        final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+        final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+
+        mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+
+        verify(mCaptionWindowDecorFactory, never())
+                .create(
+                    mContext,
+                    mDisplayController,
+                    mTaskOrganizer,
+                    taskInfo,
+                    surfaceControl,
+                    mMainHandler,
+                    mMainChoreographer,
+                    mSyncQueue);
+
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+
+        mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+
+        verify(mCaptionWindowDecorFactory)
+                .create(
+                    mContext,
+                    mDisplayController,
+                    mTaskOrganizer,
+                    taskInfo,
+                    surfaceControl,
+                    mMainHandler,
+                    mMainChoreographer,
+                    mSyncQueue);
+    }
+
+    private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
+        ActivityManager.RunningTaskInfo taskInfo =
+                 new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setVisible(true)
+                .build();
+        taskInfo.taskId = taskId;
+        taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+        return taskInfo;
+    }
+
+    private static class MockObjectSupplier<T> implements Supplier<T> {
+        private final List<T> mObjects;
+        private final Supplier<T> mDefaultSupplier;
+        private int mNumOfCalls = 0;
+
+        private MockObjectSupplier(List<T> objects, Supplier<T> defaultSupplier) {
+            mObjects = objects;
+            mDefaultSupplier = defaultSupplier;
+        }
+
+        @Override
+        public T get() {
+            final T mock =
+                    mNumOfCalls < mObjects.size() ? mObjects.get(mNumOfCalls)
+                        : mDefaultSupplier.get();
+            ++mNumOfCalls;
+            return mock;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
new file mode 100644
index 0000000..ac10ddb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -0,0 +1,130 @@
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.graphics.Rect
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [TaskPositioner].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:TaskPositionerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TaskPositionerTest : ShellTestCase() {
+
+    @Mock
+    private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
+    @Mock
+    private lateinit var mockWindowDecoration: WindowDecoration<*>
+    @Mock
+    private lateinit var mockDragStartListener: TaskPositioner.DragStartListener
+
+    @Mock
+    private lateinit var taskToken: WindowContainerToken
+    @Mock
+    private lateinit var taskBinder: IBinder
+
+    private lateinit var taskPositioner: TaskPositioner
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        taskPositioner = TaskPositioner(
+                mockShellTaskOrganizer,
+                mockWindowDecoration,
+                mockDragStartListener
+        )
+        `when`(taskToken.asBinder()).thenReturn(taskBinder)
+        mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+            taskId = TASK_ID
+            token = taskToken
+            configuration.windowConfiguration.bounds = STARTING_BOUNDS
+        }
+    }
+
+    @Test
+    fun testDragResize_move_skipsDragResizingFlag() {
+        taskPositioner.onDragResizeStart(
+                CTRL_TYPE_UNDEFINED, // Move
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        // Move the task 10px to the right.
+        val newX = STARTING_BOUNDS.left.toFloat() + 10
+        val newY = STARTING_BOUNDS.top.toFloat()
+        taskPositioner.onDragResizeMove(
+                newX,
+                newY
+        )
+
+        taskPositioner.onDragResizeEnd(newX, newY)
+
+        verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+                        change.dragResizing
+            }
+        })
+    }
+
+    @Test
+    fun testDragResize_resize_setsDragResizingFlag() {
+        taskPositioner.onDragResizeStart(
+                CTRL_TYPE_RIGHT, // Resize right
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        // Resize the task by 10px to the right.
+        val newX = STARTING_BOUNDS.right.toFloat() + 10
+        val newY = STARTING_BOUNDS.top.toFloat()
+        taskPositioner.onDragResizeMove(
+                newX,
+                newY
+        )
+
+        taskPositioner.onDragResizeEnd(newX, newY)
+
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+                        change.dragResizing
+            }
+        })
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+                        !change.dragResizing
+            }
+        })
+    }
+
+    companion object {
+        private const val TASK_ID = 5
+        private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+    }
+}
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..dd9ab98 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,20 @@
     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();
+
+        mRelayoutParams.mLayoutResId = 0;
+        mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
+        // Caption should have fixed width except in testLayoutResultCalculation_fullWidthCaption()
+        mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width;
+        mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
 
         doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
                 .create(any(), any(), any());
@@ -227,8 +237,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 +252,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 +376,123 @@
         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();
+    }
+
+    @Test
+    public void testLayoutResultCalculation_fullWidthCaption() {
+        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);
+
+        mRelayoutParams.mCaptionWidthId = Resources.ID_NULL;
+        windowDecor.relayout(taskInfo);
+
+        verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
+        verify(captionContainerSurfaceBuilder).setContainerLayer();
+        verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
+        // Width of the captionContainerSurface should match the width of TASK_BOUNDS
+        verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
+        verify(mMockSurfaceControlStartT).show(captionContainerSurface);
+    }
+
     private TestWindowDecoration createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
         return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
@@ -421,13 +548,23 @@
 
         @Override
         void relayout(ActivityManager.RunningTaskInfo taskInfo) {
-            mRelayoutParams.mLayoutResId = 0;
-            mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
-            mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width;
-            mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
-
             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/Android.bp b/libs/androidfw/Android.bp
index eb8d26a..b1f327c 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -211,6 +211,8 @@
         "tests/data/**/*.apk",
         "tests/data/**/*.arsc",
         "tests/data/**/*.idmap",
+        ":FrameworkResourcesSparseTestApp",
+        ":FrameworkResourcesNotSparseTestApp",
     ],
     test_suites: ["device-tests"],
 }
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
old mode 100755
new mode 100644
index 9aa3787..15aaae2
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -18,6 +18,7 @@
 
 #include "android-base/errors.h"
 #include "android-base/logging.h"
+#include "android-base/utf8.h"
 
 namespace android {
 
@@ -83,15 +84,16 @@
     return {};
   }
 
+  std::string overlay_path(loaded_idmap->OverlayApkPath());
+  auto fd = unique_fd(base::utf8::open(overlay_path.c_str(), O_RDONLY | O_CLOEXEC));
   std::unique_ptr<AssetsProvider> overlay_assets;
-  const std::string overlay_path(loaded_idmap->OverlayApkPath());
-  if (IsFabricatedOverlay(overlay_path)) {
+  if (IsFabricatedOverlay(fd)) {
     // Fabricated overlays do not contain resource definitions. All of the overlay resource values
     // are defined inline in the idmap.
-    overlay_assets = EmptyAssetsProvider::Create(overlay_path);
+    overlay_assets = EmptyAssetsProvider::Create(std::move(overlay_path));
   } else {
     // The overlay should be an APK.
-    overlay_assets = ZipAssetsProvider::Create(overlay_path, flags);
+    overlay_assets = ZipAssetsProvider::Create(std::move(overlay_path), flags, std::move(fd));
   }
   if (overlay_assets == nullptr) {
     return {};
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 1381bdd..68f5e4a8 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -22,6 +22,7 @@
 #include <iterator>
 #include <map>
 #include <set>
+#include <span>
 
 #include "android-base/logging.h"
 #include "android-base/stringprintf.h"
@@ -43,28 +44,19 @@
 
 using EntryValue = std::variant<Res_value, incfs::verified_map_ptr<ResTable_map_entry>>;
 
+/* NOTE: table_entry has been verified in LoadedPackage::GetEntryFromOffset(),
+ * and so access to ->value() and ->map_entry() are safe here
+ */
 base::expected<EntryValue, IOError> GetEntryValue(
     incfs::verified_map_ptr<ResTable_entry> table_entry) {
-  const uint16_t entry_size = dtohs(table_entry->size);
+  const uint16_t entry_size = table_entry->size();
 
   // Check if the entry represents a bag value.
-  if (entry_size >= sizeof(ResTable_map_entry) &&
-      (dtohs(table_entry->flags) & ResTable_entry::FLAG_COMPLEX)) {
-    const auto map_entry = table_entry.convert<ResTable_map_entry>();
-    if (!map_entry) {
-      return base::unexpected(IOError::PAGES_MISSING);
-    }
-    return map_entry.verified();
+  if (entry_size >= sizeof(ResTable_map_entry) && table_entry->is_complex()) {
+    return table_entry.convert<ResTable_map_entry>().verified();
   }
 
-  // The entry represents a non-bag value.
-  const auto entry_value = table_entry.offset(entry_size).convert<Res_value>();
-  if (!entry_value) {
-    return base::unexpected(IOError::PAGES_MISSING);
-  }
-  Res_value value;
-  value.copyFrom_dtoh(entry_value.value());
-  return value;
+  return table_entry->value();
 }
 
 } // namespace
@@ -120,7 +112,7 @@
   // A mapping from path of apk assets that could be target packages of overlays to the runtime
   // package id of its first loaded package. Overlays currently can only override resources in the
   // first package in the target resource table.
-  std::unordered_map<std::string, uint8_t> target_assets_package_ids;
+  std::unordered_map<std::string_view, uint8_t> target_assets_package_ids;
 
   // Overlay resources are not directly referenced by an application so their resource ids
   // can change throughout the application's lifetime. Assign overlay package ids last.
@@ -143,7 +135,7 @@
     if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) {
       // The target package must precede the overlay package in the apk assets paths in order
       // to take effect.
-      auto iter = target_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath()));
+      auto iter = target_assets_package_ids.find(loaded_idmap->TargetApkPath());
       if (iter == target_assets_package_ids.end()) {
          LOG(INFO) << "failed to find target package for overlay "
                    << loaded_idmap->OverlayApkPath();
@@ -188,7 +180,7 @@
         if (overlay_ref_table != nullptr) {
           // If this package is from an overlay, use a dynamic reference table that can rewrite
           // overlay resource ids to their corresponding target resource ids.
-          new_group.dynamic_ref_table = overlay_ref_table;
+          new_group.dynamic_ref_table = std::move(overlay_ref_table);
         }
 
         DynamicRefTable* ref_table = new_group.dynamic_ref_table.get();
@@ -196,9 +188,9 @@
         ref_table->mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
       }
 
-      // Add the package and to the set of packages with the same ID.
+      // Add the package to the set of packages with the same ID.
       PackageGroup* package_group = &package_groups_[idx];
-      package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
+      package_group->packages_.emplace_back().loaded_package_ = package.get();
       package_group->cookies_.push_back(apk_assets_cookies[apk_assets]);
 
       // Add the package name -> build time ID mappings.
@@ -210,29 +202,38 @@
 
       if (auto apk_assets_path = apk_assets->GetPath()) {
         // Overlay target ApkAssets must have been created using path based load apis.
-        target_assets_package_ids.insert(std::make_pair(std::string(*apk_assets_path), package_id));
+        target_assets_package_ids.emplace(*apk_assets_path, package_id);
       }
     }
   }
 
   // Now assign the runtime IDs so that we have a build-time to runtime ID map.
-  const auto package_groups_end = package_groups_.end();
-  for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) {
-    const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName();
-    for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
-      iter2->dynamic_ref_table->addMapping(String16(package_name.c_str(), package_name.size()),
-                                           iter->dynamic_ref_table->mAssignedPackageId);
-
-      // Add the alias resources to the dynamic reference table of every package group. Since
-      // staging aliases can only be defined by the framework package (which is not a shared
-      // library), the compile-time package id of the framework is the same across all packages
-      // that compile against the framework.
-      for (const auto& package : iter->packages_) {
-        for (const auto& entry : package.loaded_package_->GetAliasResourceIdMap()) {
-          iter2->dynamic_ref_table->addAlias(entry.first, entry.second);
-        }
-      }
+  DynamicRefTable::AliasMap aliases;
+  for (const auto& group : package_groups_) {
+    const std::string& package_name = group.packages_[0].loaded_package_->GetPackageName();
+    const auto name_16 = String16(package_name.c_str(), package_name.size());
+    for (auto&& inner_group : package_groups_) {
+      inner_group.dynamic_ref_table->addMapping(name_16,
+                                                group.dynamic_ref_table->mAssignedPackageId);
     }
+
+    for (const auto& package : group.packages_) {
+      const auto& package_aliases = package.loaded_package_->GetAliasResourceIdMap();
+      aliases.insert(aliases.end(), package_aliases.begin(), package_aliases.end());
+    }
+  }
+
+  if (!aliases.empty()) {
+    std::sort(aliases.begin(), aliases.end(), [](auto&& l, auto&& r) { return l.first < r.first; });
+
+    // Add the alias resources to the dynamic reference table of every package group. Since
+    // staging aliases can only be defined by the framework package (which is not a shared
+    // library), the compile-time package id of the framework is the same across all packages
+    // that compile against the framework.
+    for (auto& group : std::span(package_groups_.data(), package_groups_.size() - 1)) {
+      group.dynamic_ref_table->setAliases(aliases);
+    }
+    package_groups_.back().dynamic_ref_table->setAliases(std::move(aliases));
   }
 }
 
@@ -326,7 +327,7 @@
   return &loaded_package->GetOverlayableMap();
 }
 
-bool AssetManager2::GetOverlayablesToString(const android::StringPiece& package_name,
+bool AssetManager2::GetOverlayablesToString(android::StringPiece package_name,
                                             std::string* out) const {
   uint8_t package_id = 0U;
   for (const auto& apk_assets : apk_assets_) {
@@ -373,7 +374,7 @@
         const std::string name = ToFormattedResourceString(*res_name);
         output.append(base::StringPrintf(
             "resource='%s' overlayable='%s' actor='%s' policy='0x%08x'\n",
-            name.c_str(), info->name.c_str(), info->actor.c_str(), info->policy_flags));
+            name.c_str(), info->name.data(), info->actor.data(), info->policy_flags));
       }
     }
   }
@@ -501,7 +502,7 @@
       continue;
     }
 
-    auto func = [&](const StringPiece& name, FileType type) {
+    auto func = [&](StringPiece name, FileType type) {
       AssetDir::FileInfo info;
       info.setFileName(String8(name.data(), name.size()));
       info.setFileType(type);
@@ -580,7 +581,7 @@
 
   // Retrieve the package group from the package id of the resource id.
   if (UNLIKELY(!is_valid_resid(resid))) {
-    LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
+    LOG(ERROR) << base::StringPrintf("Invalid resource ID 0x%08x.", resid);
     return base::unexpected(std::nullopt);
   }
 
@@ -589,7 +590,7 @@
   const uint16_t entry_idx = get_entry_id(resid);
   uint8_t package_idx = package_ids_[package_id];
   if (UNLIKELY(package_idx == 0xff)) {
-    ANDROID_LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.",
+    ANDROID_LOG(ERROR) << base::StringPrintf("No package ID %02x found for resource ID 0x%08x.",
                                              package_id, resid);
     return base::unexpected(std::nullopt);
   }
@@ -814,17 +815,12 @@
     return base::unexpected(std::nullopt);
   }
 
-  auto best_entry_result = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
-  if (!best_entry_result.has_value()) {
-    return base::unexpected(best_entry_result.error());
+  auto best_entry_verified = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
+  if (!best_entry_verified.has_value()) {
+    return base::unexpected(best_entry_verified.error());
   }
 
-  const incfs::map_ptr<ResTable_entry> best_entry = *best_entry_result;
-  if (!best_entry) {
-    return base::unexpected(IOError::PAGES_MISSING);
-  }
-
-  const auto entry = GetEntryValue(best_entry.verified());
+  const auto entry = GetEntryValue(*best_entry_verified);
   if (!entry.has_value()) {
     return base::unexpected(entry.error());
   }
@@ -837,7 +833,7 @@
     .package_name = &best_package->GetPackageName(),
     .type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1),
     .entry_string_ref = StringPoolRef(best_package->GetKeyStringPool(),
-                                      best_entry->key.index),
+                                      (*best_entry_verified)->key()),
     .dynamic_ref_table = package_group.dynamic_ref_table.get(),
   };
 }
@@ -1285,7 +1281,7 @@
   return result;
 }
 
-static bool Utf8ToUtf16(const StringPiece& str, std::u16string* out) {
+static bool Utf8ToUtf16(StringPiece str, std::u16string* out) {
   ssize_t len =
       utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str.data()), str.size(), false);
   if (len < 0) {
@@ -1360,22 +1356,22 @@
 
 void AssetManager2::RebuildFilterList() {
   for (PackageGroup& group : package_groups_) {
-    for (ConfiguredPackage& impl : group.packages_) {
-      // Destroy it.
-      impl.filtered_configs_.~ByteBucketArray();
-
-      // Re-create it.
-      new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>();
-
+    for (ConfiguredPackage& package : group.packages_) {
+      package.filtered_configs_.forEachItem([](auto, auto& fcg) { fcg.type_entries.clear(); });
       // Create the filters here.
-      impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
-        FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_id - 1);
+      package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
+        FilteredConfigGroup* group = nullptr;
         for (const auto& type_entry : type_spec.type_entries) {
           if (type_entry.config.match(configuration_)) {
-            group.type_entries.push_back(&type_entry);
+            if (!group) {
+              group = &package.filtered_configs_.editItemAt(type_id - 1);
+            }
+            group->type_entries.push_back(&type_entry);
           }
         }
       });
+      package.filtered_configs_.trimBuckets(
+          [](const auto& fcg) { return fcg.type_entries.empty(); });
     }
   }
 }
@@ -1416,30 +1412,34 @@
 std::unique_ptr<Theme> AssetManager2::NewTheme() {
   constexpr size_t kInitialReserveSize = 32;
   auto theme = std::unique_ptr<Theme>(new Theme(this));
+  theme->keys_.reserve(kInitialReserveSize);
   theme->entries_.reserve(kInitialReserveSize);
   return theme;
 }
 
+void AssetManager2::ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func,
+                                   package_property_t excluded_property_flags) const {
+  for (const PackageGroup& package_group : package_groups_) {
+    const auto loaded_package = package_group.packages_.front().loaded_package_;
+    if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U
+        && !func(loaded_package->GetPackageName(),
+                 package_group.dynamic_ref_table->mAssignedPackageId)) {
+      return;
+    }
+  }
+}
+
 Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {
 }
 
 Theme::~Theme() = default;
 
 struct Theme::Entry {
-  uint32_t attr_res_id;
   ApkAssetsCookie cookie;
   uint32_t type_spec_flags;
   Res_value value;
 };
 
-namespace {
-struct ThemeEntryKeyComparer {
-  bool operator() (const Theme::Entry& entry, uint32_t attr_res_id) const noexcept {
-    return entry.attr_res_id < attr_res_id;
-  }
-};
-} // namespace
-
 base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, bool force) {
   ATRACE_NAME("Theme::ApplyStyle");
 
@@ -1468,18 +1468,20 @@
       continue;
     }
 
-    auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), attr_res_id,
-                                     ThemeEntryKeyComparer{});
-    if (entry_it != entries_.end() && entry_it->attr_res_id == attr_res_id) {
+    const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), attr_res_id);
+    const auto entry_it = entries_.begin() + (key_it - keys_.begin());
+    if (key_it != keys_.end() && *key_it == attr_res_id) {
       if (is_undefined) {
         // DATA_NULL_UNDEFINED clears the value of the attribute in the theme only when `force` is
-        /// true.
+        // true.
+        keys_.erase(key_it);
         entries_.erase(entry_it);
       } else if (force) {
-        *entry_it = Entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value};
+        *entry_it = Entry{it->cookie, (*bag)->type_spec_flags, it->value};
       }
     } else {
-      entries_.insert(entry_it, Entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value});
+      keys_.insert(key_it, attr_res_id);
+      entries_.insert(entry_it, Entry{it->cookie, (*bag)->type_spec_flags, it->value});
     }
   }
   return {};
@@ -1490,6 +1492,7 @@
   ATRACE_NAME("Theme::Rebase");
   // Reset the entries without changing the vector capacity to prevent reallocations during
   // ApplyStyle.
+  keys_.clear();
   entries_.clear();
   asset_manager_ = am;
   for (size_t i = 0; i < style_count; i++) {
@@ -1498,16 +1501,14 @@
 }
 
 std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid) const {
-
   constexpr const uint32_t kMaxIterations = 20;
   uint32_t type_spec_flags = 0u;
   for (uint32_t i = 0; i <= kMaxIterations; i++) {
-    auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), resid,
-                                     ThemeEntryKeyComparer{});
-    if (entry_it == entries_.end() || entry_it->attr_res_id != resid) {
+    const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), resid);
+    if (key_it == keys_.end() || *key_it != resid) {
       return std::nullopt;
     }
-
+    const auto entry_it = entries_.begin() + (key_it - keys_.begin());
     type_spec_flags |= entry_it->type_spec_flags;
     if (entry_it->value.dataType == Res_value::TYPE_ATTRIBUTE) {
       resid = entry_it->value.data;
@@ -1541,6 +1542,7 @@
 }
 
 void Theme::Clear() {
+  keys_.clear();
   entries_.clear();
 }
 
@@ -1552,18 +1554,19 @@
   type_spec_flags_ = source.type_spec_flags_;
 
   if (asset_manager_ == source.asset_manager_) {
+    keys_ = source.keys_;
     entries_ = source.entries_;
   } else {
-    std::map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies;
-    typedef std::map<int, int> SourceToDestinationRuntimePackageMap;
-    std::map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
+    std::unordered_map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies;
+    using SourceToDestinationRuntimePackageMap = std::unordered_map<int, int>;
+    std::unordered_map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
 
     // Determine which ApkAssets are loaded in both theme AssetManagers.
-    const auto src_assets = source.asset_manager_->GetApkAssets();
+    const auto& src_assets = source.asset_manager_->GetApkAssets();
     for (size_t i = 0; i < src_assets.size(); i++) {
       const ApkAssets* src_asset = src_assets[i];
 
-      const auto dest_assets = asset_manager_->GetApkAssets();
+      const auto& dest_assets = asset_manager_->GetApkAssets();
       for (size_t j = 0; j < dest_assets.size(); j++) {
         const ApkAssets* dest_asset = dest_assets[j];
         if (src_asset != dest_asset) {
@@ -1584,15 +1587,17 @@
         }
 
         src_to_dest_asset_cookies.insert(std::make_pair(i, j));
-        src_asset_cookie_id_map.insert(std::make_pair(i, package_map));
+        src_asset_cookie_id_map.insert(std::make_pair(i, std::move(package_map)));
         break;
       }
     }
 
     // Reset the data in the destination theme.
+    keys_.clear();
     entries_.clear();
 
-    for (const auto& entry : source.entries_) {
+    for (size_t i = 0, size = source.entries_.size(); i != size; ++i) {
+      const auto& entry = source.entries_[i];
       bool is_reference = (entry.value.dataType == Res_value::TYPE_ATTRIBUTE
                            || entry.value.dataType == Res_value::TYPE_REFERENCE
                            || entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE
@@ -1632,13 +1637,15 @@
         }
       }
 
+      const auto source_res_id = source.keys_[i];
+
       // The package id of the attribute needs to be rewritten to the package id of the
       // attribute in the destination.
-      int attribute_dest_package_id = get_package_id(entry.attr_res_id);
+      int attribute_dest_package_id = get_package_id(source_res_id);
       if (attribute_dest_package_id != 0x01) {
         // Find the cookie of the attribute resource id in the source AssetManager
         base::expected<FindEntryResult, NullOrIOError> attribute_entry_result =
-            source.asset_manager_->FindEntry(entry.attr_res_id, 0 /* density_override */ ,
+            source.asset_manager_->FindEntry(source_res_id, 0 /* density_override */ ,
                                              true /* stop_at_first_match */,
                                              true /* ignore_configuration */);
         if (UNLIKELY(IsIOError(attribute_entry_result))) {
@@ -1662,16 +1669,15 @@
         attribute_dest_package_id = attribute_dest_package->second;
       }
 
-      auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(entry.attr_res_id),
-                                     get_entry_id(entry.attr_res_id));
-      Theme::Entry new_entry{dest_attr_id, data_dest_cookie, entry.type_spec_flags,
-                                            Res_value{.dataType = entry.value.dataType,
-                                                      .data = attribute_data}};
-
+      auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(source_res_id),
+                                     get_entry_id(source_res_id));
+      const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), dest_attr_id);
+      const auto entry_it = entries_.begin() + (key_it - keys_.begin());
       // Since the entries were cleared, the attribute resource id has yet been mapped to any value.
-      auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), dest_attr_id,
-                                       ThemeEntryKeyComparer{});
-      entries_.insert(entry_it, new_entry);
+      keys_.insert(key_it, dest_attr_id);
+      entries_.insert(entry_it, Entry{data_dest_cookie, entry.type_spec_flags,
+                                      Res_value{.dataType = entry.value.dataType,
+                                                .data = attribute_data}});
     }
   }
   return {};
@@ -1679,9 +1685,11 @@
 
 void Theme::Dump() const {
   LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_);
-  for (auto& entry : entries_) {
+  for (size_t i = 0, size = keys_.size(); i != size; ++i) {
+    auto res_id = keys_[i];
+    const auto& entry = entries_[i];
     LOG(INFO) << base::StringPrintf("  entry(0x%08x)=(0x%08x) type=(0x%02x), cookie(%d)",
-                                    entry.attr_res_id, entry.value.data, entry.value.dataType,
+                                    res_id, entry.value.data, entry.value.dataType,
                                     entry.cookie);
   }
 }
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index bce34d3..2d3c065 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -73,9 +73,6 @@
                                           (path != nullptr) ? base::unique_fd(-1) : std::move(fd));
 }
 
-ZipAssetsProvider::PathOrDebugName::PathOrDebugName(std::string&& value, bool is_path)
-    : value_(std::forward<std::string>(value)), is_path_(is_path) {}
-
 const std::string* ZipAssetsProvider::PathOrDebugName::GetPath() const {
   return is_path_ ? &value_ : nullptr;
 }
@@ -84,34 +81,42 @@
   return value_;
 }
 
+void ZipAssetsProvider::ZipCloser::operator()(ZipArchive* a) const {
+  ::CloseArchive(a);
+}
+
 ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path,
                                      package_property_t flags, time_t last_mod_time)
-    : zip_handle_(handle, ::CloseArchive),
-      name_(std::forward<PathOrDebugName>(path)),
+    : zip_handle_(handle),
+      name_(std::move(path)),
       flags_(flags),
       last_mod_time_(last_mod_time) {}
 
 std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
-                                                             package_property_t flags) {
+                                                             package_property_t flags,
+                                                             base::unique_fd fd) {
+  const auto released_fd = fd.ok() ? fd.release() : -1;
   ZipArchiveHandle handle;
-  if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) {
+  if (int32_t result = released_fd < 0 ? OpenArchive(path.c_str(), &handle)
+                                       : OpenArchiveFd(released_fd, path.c_str(), &handle)) {
     LOG(ERROR) << "Failed to open APK '" << path << "': " << ::ErrorCodeString(result);
     CloseArchive(handle);
     return {};
   }
 
   struct stat sb{.st_mtime = -1};
-  if (stat(path.c_str(), &sb) < 0) {
-    // Stat requires execute permissions on all directories path to the file. If the process does
-    // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
-    // always have to return true.
-    LOG(WARNING) << "Failed to stat file '" << path << "': "
-                 << base::SystemErrorCodeToString(errno);
+  // Skip all up-to-date checks if the file won't ever change.
+  if (!isReadonlyFilesystem(path.c_str())) {
+    if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) {
+      // Stat requires execute permissions on all directories path to the file. If the process does
+      // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+      // always have to return true.
+      PLOG(WARNING) << "Failed to stat file '" << path << "'";
+    }
   }
 
   return std::unique_ptr<ZipAssetsProvider>(
-      new ZipAssetsProvider(handle, PathOrDebugName{std::move(path),
-                                                    true /* is_path */}, flags, sb.st_mtime));
+      new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, sb.st_mtime));
 }
 
 std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
@@ -133,17 +138,19 @@
   }
 
   struct stat sb{.st_mtime = -1};
-  if (fstat(released_fd, &sb) < 0) {
-    // Stat requires execute permissions on all directories path to the file. If the process does
-    // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
-    // always have to return true.
-    LOG(WARNING) << "Failed to fstat file '" << friendly_name << "': "
-                 << base::SystemErrorCodeToString(errno);
+  // Skip all up-to-date checks if the file won't ever change.
+  if (!isReadonlyFilesystem(released_fd)) {
+    if (fstat(released_fd, &sb) < 0) {
+      // Stat requires execute permissions on all directories path to the file. If the process does
+      // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+      // always have to return true.
+      LOG(WARNING) << "Failed to fstat file '" << friendly_name
+                   << "': " << base::SystemErrorCodeToString(errno);
+    }
   }
 
-  return std::unique_ptr<ZipAssetsProvider>(
-      new ZipAssetsProvider(handle, PathOrDebugName{std::move(friendly_name),
-                                                    false /* is_path */}, flags, sb.st_mtime));
+  return std::unique_ptr<ZipAssetsProvider>(new ZipAssetsProvider(
+      handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, sb.st_mtime));
 }
 
 std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path,
@@ -210,9 +217,9 @@
     return asset;
 }
 
-bool ZipAssetsProvider::ForEachFile(const std::string& root_path,
-                                    const std::function<void(const StringPiece&, FileType)>& f)
-                                    const {
+bool ZipAssetsProvider::ForEachFile(
+    const std::string& root_path,
+    base::function_ref<void(StringPiece, FileType)> f) const {
     std::string root_path_full = root_path;
     if (root_path_full.back() != '/') {
       root_path_full += '/';
@@ -238,8 +245,7 @@
       if (!leaf_file_path.empty()) {
         auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
         if (iter != leaf_file_path.end()) {
-          std::string dir =
-              leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
+          std::string dir(leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)));
           dirs.insert(std::move(dir));
         } else {
           f(leaf_file_path, kFileTypeRegular);
@@ -277,6 +283,9 @@
 }
 
 bool ZipAssetsProvider::IsUpToDate() const {
+  if (last_mod_time_ == -1) {
+    return true;
+  }
   struct stat sb{};
   if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
     // If fstat fails on the zip archive, return true so the zip archive the resource system does
@@ -287,10 +296,10 @@
 }
 
 DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time)
-    : dir_(std::forward<std::string>(path)), last_mod_time_(last_mod_time) {}
+    : dir_(std::move(path)), last_mod_time_(last_mod_time) {}
 
 std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
-  struct stat sb{};
+  struct stat sb;
   const int result = stat(path.c_str(), &sb);
   if (result == -1) {
     LOG(ERROR) << "Failed to find directory '" << path << "'.";
@@ -302,12 +311,13 @@
     return nullptr;
   }
 
-  if (path[path.size() - 1] != OS_PATH_SEPARATOR) {
+  if (path.back() != OS_PATH_SEPARATOR) {
     path += OS_PATH_SEPARATOR;
   }
 
-  return std::unique_ptr<DirectoryAssetsProvider>(new DirectoryAssetsProvider(std::move(path),
-                                                                              sb.st_mtime));
+  const bool isReadonly = isReadonlyFilesystem(path.c_str());
+  return std::unique_ptr<DirectoryAssetsProvider>(
+      new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime));
 }
 
 std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
@@ -324,8 +334,7 @@
 
 bool DirectoryAssetsProvider::ForEachFile(
     const std::string& /* root_path */,
-    const std::function<void(const StringPiece&, FileType)>& /* f */)
-    const {
+    base::function_ref<void(StringPiece, FileType)> /* f */) const {
   return true;
 }
 
@@ -338,7 +347,10 @@
 }
 
 bool DirectoryAssetsProvider::IsUpToDate() const {
-  struct stat sb{};
+  if (last_mod_time_ == -1) {
+    return true;
+  }
+  struct stat sb;
   if (stat(dir_.c_str(), &sb) < 0) {
     // If stat fails on the zip archive, return true so the zip archive the resource system does
     // attempt to refresh the ApkAsset.
@@ -349,8 +361,7 @@
 
 MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary,
                                          std::unique_ptr<AssetsProvider>&& secondary)
-                      : primary_(std::forward<std::unique_ptr<AssetsProvider>>(primary)),
-                        secondary_(std::forward<std::unique_ptr<AssetsProvider>>(secondary)) {
+    : primary_(std::move(primary)), secondary_(std::move(secondary)) {
   debug_name_ = primary_->GetDebugName() + " and " + secondary_->GetDebugName();
   path_ = (primary_->GetDebugName() != kEmptyDebugString) ? primary_->GetPath()
                                                           : secondary_->GetPath();
@@ -372,9 +383,9 @@
   return (asset) ? std::move(asset) : secondary_->Open(path, mode, file_exists);
 }
 
-bool MultiAssetsProvider::ForEachFile(const std::string& root_path,
-                                      const std::function<void(const StringPiece&, FileType)>& f)
-                                      const {
+bool MultiAssetsProvider::ForEachFile(
+    const std::string& root_path,
+    base::function_ref<void(StringPiece, FileType)> f) const {
   return primary_->ForEachFile(root_path, f) && secondary_->ForEachFile(root_path, f);
 }
 
@@ -397,8 +408,8 @@
   return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider({}));
 }
 
-std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(const std::string& path) {
-  return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(path));
+std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(std::string path) {
+  return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(std::move(path)));
 }
 
 std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* path */,
@@ -412,7 +423,7 @@
 
 bool EmptyAssetsProvider::ForEachFile(
     const std::string& /* root_path */,
-    const std::function<void(const StringPiece&, FileType)>& /* f */) const {
+    base::function_ref<void(StringPiece, FileType)> /* f */) const {
   return true;
 }
 
@@ -435,4 +446,4 @@
   return true;
 }
 
-} // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/libs/androidfw/ConfigDescription.cpp b/libs/androidfw/ConfigDescription.cpp
index 19ead95..93a7d17 100644
--- a/libs/androidfw/ConfigDescription.cpp
+++ b/libs/androidfw/ConfigDescription.cpp
@@ -637,7 +637,7 @@
   return true;
 }
 
-bool ConfigDescription::Parse(const StringPiece& str, ConfigDescription* out) {
+bool ConfigDescription::Parse(StringPiece str, ConfigDescription* out) {
   std::vector<std::string> parts = util::SplitAndLowercase(str, '-');
 
   ConfigDescription config;
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index e122d48..8983574 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -201,7 +201,7 @@
       const auto& config = configurations_[value.config_index];
       values_map[config] = value.value;
     }
-    return Result(values_map);
+    return Result(std::move(values_map));
   }
   return {};
 }
@@ -250,8 +250,7 @@
 }
 } // namespace
 
-LoadedIdmap::LoadedIdmap(std::string&& idmap_path,
-                         const Idmap_header* header,
+LoadedIdmap::LoadedIdmap(std::string&& idmap_path, const Idmap_header* header,
                          const Idmap_data_header* data_header,
                          const Idmap_target_entry* target_entries,
                          const Idmap_target_entry_inline* target_inline_entries,
@@ -259,23 +258,21 @@
                          const ConfigDescription* configs,
                          const Idmap_overlay_entry* overlay_entries,
                          std::unique_ptr<ResStringPool>&& string_pool,
-                         std::string_view overlay_apk_path,
-                         std::string_view target_apk_path)
-     : header_(header),
-       data_header_(data_header),
-       target_entries_(target_entries),
-       target_inline_entries_(target_inline_entries),
-       inline_entry_values_(inline_entry_values),
-       configurations_(configs),
-       overlay_entries_(overlay_entries),
-       string_pool_(std::move(string_pool)),
-       idmap_path_(std::move(idmap_path)),
-       overlay_apk_path_(overlay_apk_path),
-       target_apk_path_(target_apk_path),
-       idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {}
+                         std::string_view overlay_apk_path, std::string_view target_apk_path)
+    : header_(header),
+      data_header_(data_header),
+      target_entries_(target_entries),
+      target_inline_entries_(target_inline_entries),
+      inline_entry_values_(inline_entry_values),
+      configurations_(configs),
+      overlay_entries_(overlay_entries),
+      string_pool_(std::move(string_pool)),
+      idmap_path_(std::move(idmap_path)),
+      overlay_apk_path_(overlay_apk_path),
+      target_apk_path_(target_apk_path),
+      idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {}
 
-std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path,
-                                               const StringPiece& idmap_data) {
+std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) {
   ATRACE_CALL();
   size_t data_size = idmap_data.size();
   auto data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data());
@@ -365,7 +362,7 @@
 
   // Can't use make_unique because LoadedIdmap constructor is private.
   return std::unique_ptr<LoadedIdmap>(
-      new LoadedIdmap(idmap_path.to_string(), header, data_header, target_entries,
+      new LoadedIdmap(std::string(idmap_path), header, data_header, target_entries,
                       target_inline_entries, target_inline_entry_values, configurations,
                       overlay_entries, std::move(idmap_string_pool), *target_path, *overlay_path));
 }
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 5b69cca..c0fdfe2 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -88,7 +88,9 @@
   // Make sure that there is enough room for the entry offsets.
   const size_t offsets_offset = dtohs(header->header.headerSize);
   const size_t entries_offset = dtohl(header->entriesStart);
-  const size_t offsets_length = sizeof(uint32_t) * entry_count;
+  const size_t offsets_length = header->flags & ResTable_type::FLAG_OFFSET16
+                                    ? sizeof(uint16_t) * entry_count
+                                    : sizeof(uint32_t) * entry_count;
 
   if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) {
     LOG(ERROR) << "RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data.";
@@ -107,8 +109,8 @@
   return true;
 }
 
-static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
-    incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) {
+static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+VerifyResTableEntry(incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) {
   // Check that the offset is aligned.
   if (UNLIKELY(entry_offset & 0x03U)) {
     LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned.";
@@ -136,7 +138,7 @@
     return base::unexpected(IOError::PAGES_MISSING);
   }
 
-  const size_t entry_size = dtohs(entry->size);
+  const size_t entry_size = entry->size();
   if (UNLIKELY(entry_size < sizeof(entry.value()))) {
     LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
                << " is too small.";
@@ -149,6 +151,11 @@
     return base::unexpected(std::nullopt);
   }
 
+  // If entry is compact, value is already encoded, and a compact entry
+  // cannot be a map_entry, we are done verifying
+  if (entry->is_compact())
+    return entry.verified();
+
   if (entry_size < sizeof(ResTable_map_entry)) {
     // There needs to be room for one Res_value struct.
     if (UNLIKELY(entry_offset + entry_size > chunk_size - sizeof(Res_value))) {
@@ -192,7 +199,7 @@
       return base::unexpected(std::nullopt);
     }
   }
-  return {};
+  return entry.verified();
 }
 
 LoadedPackage::iterator::iterator(const LoadedPackage* lp, size_t ti, size_t ei)
@@ -228,7 +235,7 @@
           entryIndex_);
 }
 
-base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry(
+base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry(
     incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index) {
   base::expected<uint32_t, NullOrIOError> entry_offset = GetEntryOffset(type_chunk, entry_index);
   if (UNLIKELY(!entry_offset.has_value())) {
@@ -242,14 +249,13 @@
   // The configuration matches and is better than the previous selection.
   // Find the entry value if it exists for this configuration.
   const size_t entry_count = dtohl(type_chunk->entryCount);
-  const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
+  const auto offsets = type_chunk.offset(dtohs(type_chunk->header.headerSize));
 
   // Check if there is the desired entry in this type.
   if (type_chunk->flags & ResTable_type::FLAG_SPARSE) {
     // This is encoded as a sparse map, so perform a binary search.
     bool error = false;
-    auto sparse_indices = type_chunk.offset(offsets_offset)
-                                    .convert<ResTable_sparseTypeEntry>().iterator();
+    auto sparse_indices = offsets.convert<ResTable_sparseTypeEntry>().iterator();
     auto sparse_indices_end = sparse_indices + entry_count;
     auto result = std::lower_bound(sparse_indices, sparse_indices_end, entry_index,
                                    [&error](const incfs::map_ptr<ResTable_sparseTypeEntry>& entry,
@@ -284,26 +290,36 @@
     return base::unexpected(std::nullopt);
   }
 
-  const auto entry_offset_ptr = type_chunk.offset(offsets_offset).convert<uint32_t>() + entry_index;
-  if (UNLIKELY(!entry_offset_ptr)) {
-    return base::unexpected(IOError::PAGES_MISSING);
+  uint32_t result;
+
+  if (type_chunk->flags & ResTable_type::FLAG_OFFSET16) {
+    const auto entry_offset_ptr = offsets.convert<uint16_t>() + entry_index;
+    if (UNLIKELY(!entry_offset_ptr)) {
+      return base::unexpected(IOError::PAGES_MISSING);
+    }
+    result = offset_from16(entry_offset_ptr.value());
+  } else {
+    const auto entry_offset_ptr = offsets.convert<uint32_t>() + entry_index;
+    if (UNLIKELY(!entry_offset_ptr)) {
+      return base::unexpected(IOError::PAGES_MISSING);
+    }
+    result = dtohl(entry_offset_ptr.value());
   }
 
-  const uint32_t value = dtohl(entry_offset_ptr.value());
-  if (value == ResTable_type::NO_ENTRY) {
+  if (result == ResTable_type::NO_ENTRY) {
     return base::unexpected(std::nullopt);
   }
-
-  return value;
+  return result;
 }
 
-base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntryFromOffset(
-    incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset) {
+base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+LoadedPackage::GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk,
+                                  uint32_t offset) {
   auto valid = VerifyResTableEntry(type_chunk, offset);
   if (UNLIKELY(!valid.has_value())) {
     return base::unexpected(valid.error());
   }
-  return type_chunk.offset(offset + dtohl(type_chunk->entriesStart)).convert<ResTable_entry>();
+  return valid;
 }
 
 base::expected<std::monostate, IOError> LoadedPackage::CollectConfigurations(
@@ -376,31 +392,42 @@
   for (const auto& type_entry : type_spec->type_entries) {
     const incfs::verified_map_ptr<ResTable_type>& type = type_entry.type;
 
-    size_t entry_count = dtohl(type->entryCount);
-    for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
-      auto entry_offset_ptr = type.offset(dtohs(type->header.headerSize)).convert<uint32_t>() +
-          entry_idx;
-      if (!entry_offset_ptr) {
-        return base::unexpected(IOError::PAGES_MISSING);
-      }
+    const size_t entry_count = dtohl(type->entryCount);
+    const auto entry_offsets = type.offset(dtohs(type->header.headerSize));
 
+    for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
       uint32_t offset;
       uint16_t res_idx;
       if (type->flags & ResTable_type::FLAG_SPARSE) {
-        auto sparse_entry = entry_offset_ptr.convert<ResTable_sparseTypeEntry>();
+        auto sparse_entry = entry_offsets.convert<ResTable_sparseTypeEntry>() + entry_idx;
+        if (!sparse_entry) {
+          return base::unexpected(IOError::PAGES_MISSING);
+        }
         offset = dtohs(sparse_entry->offset) * 4u;
         res_idx  = dtohs(sparse_entry->idx);
+      } else if (type->flags & ResTable_type::FLAG_OFFSET16) {
+        auto entry = entry_offsets.convert<uint16_t>() + entry_idx;
+        if (!entry) {
+          return base::unexpected(IOError::PAGES_MISSING);
+        }
+        offset = offset_from16(entry.value());
+        res_idx = entry_idx;
       } else {
-        offset = dtohl(entry_offset_ptr.value());
+        auto entry = entry_offsets.convert<uint32_t>() + entry_idx;
+        if (!entry) {
+          return base::unexpected(IOError::PAGES_MISSING);
+        }
+        offset = dtohl(entry.value());
         res_idx = entry_idx;
       }
+
       if (offset != ResTable_type::NO_ENTRY) {
         auto entry = type.offset(dtohl(type->entriesStart) + offset).convert<ResTable_entry>();
         if (!entry) {
           return base::unexpected(IOError::PAGES_MISSING);
         }
 
-        if (dtohl(entry->key.index) == static_cast<uint32_t>(*key_idx)) {
+        if (entry->key() == static_cast<uint32_t>(*key_idx)) {
           // The package ID will be overridden by the caller (due to runtime assignment of package
           // IDs for shared libraries).
           return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx);
@@ -618,16 +645,16 @@
         }
 
         std::string name;
-        util::ReadUtf16StringFromDevice(overlayable->name, arraysize(overlayable->name), &name);
+        util::ReadUtf16StringFromDevice(overlayable->name, std::size(overlayable->name), &name);
         std::string actor;
-        util::ReadUtf16StringFromDevice(overlayable->actor, arraysize(overlayable->actor), &actor);
-
-        if (loaded_package->overlayable_map_.find(name) !=
-            loaded_package->overlayable_map_.end()) {
-          LOG(ERROR) << "Multiple <overlayable> blocks with the same name '" << name << "'.";
+        util::ReadUtf16StringFromDevice(overlayable->actor, std::size(overlayable->actor), &actor);
+        auto [name_to_actor_it, inserted] =
+            loaded_package->overlayable_map_.emplace(std::move(name), std::move(actor));
+        if (!inserted) {
+          LOG(ERROR) << "Multiple <overlayable> blocks with the same name '"
+                     << name_to_actor_it->first << "'.";
           return {};
         }
-        loaded_package->overlayable_map_.emplace(name, actor);
 
         // Iterate over the overlayable policy chunks contained within the overlayable chunk data
         ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size());
@@ -642,7 +669,6 @@
                 LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small.";
                 return {};
               }
-
               if ((overlayable_child_chunk.data_size() / sizeof(ResTable_ref))
                   < dtohl(policy_header->entry_count)) {
                 LOG(ERROR) <<  "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small to hold entries.";
@@ -664,8 +690,8 @@
 
               // Add the pairing of overlayable properties and resource ids to the package
               OverlayableInfo overlayable_info {
-                .name = name,
-                .actor = actor,
+                .name = name_to_actor_it->first,
+                .actor = name_to_actor_it->second,
                 .policy_flags = policy_header->policy_flags
               };
               loaded_package->overlayable_infos_.emplace_back(std::move(overlayable_info), std::move(ids));
@@ -709,6 +735,7 @@
         const auto entry_end = entry_begin + dtohl(lib_alias->count);
         std::unordered_set<uint32_t> finalized_ids;
         finalized_ids.reserve(entry_end - entry_begin);
+        loaded_package->alias_id_map_.reserve(entry_end - entry_begin);
         for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
           if (!entry_iter) {
             LOG(ERROR) << "NULL ResTable_staged_alias_entry record??";
@@ -722,13 +749,20 @@
           }
 
           auto staged_id = dtohl(entry_iter->stagedResId);
-          auto [_, success] = loaded_package->alias_id_map_.emplace(staged_id, finalized_id);
-          if (!success) {
+          loaded_package->alias_id_map_.emplace_back(staged_id, finalized_id);
+        }
+
+        std::sort(loaded_package->alias_id_map_.begin(), loaded_package->alias_id_map_.end(),
+            [](auto&& l, auto&& r) { return l.first < r.first; });
+        const auto duplicate_it =
+            std::adjacent_find(loaded_package->alias_id_map_.begin(),
+                               loaded_package->alias_id_map_.end(),
+                               [](auto&& l, auto&& r) { return l.first == r.first; });
+          if (duplicate_it != loaded_package->alias_id_map_.end()) {
             LOG(ERROR) << StringPrintf("Repeated staged resource id '%08x' in staged aliases.",
-                                       staged_id);
+                                       duplicate_it->first);
             return {};
           }
-        }
       } break;
 
       default:
diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp
index d87a3ce..272a988 100644
--- a/libs/androidfw/Locale.cpp
+++ b/libs/androidfw/Locale.cpp
@@ -66,7 +66,7 @@
   return std::all_of(std::begin(str), std::end(str), ::isdigit);
 }
 
-bool LocaleValue::InitFromFilterString(const StringPiece& str) {
+bool LocaleValue::InitFromFilterString(StringPiece str) {
   // A locale (as specified in the filter) is an underscore separated name such
   // as "en_US", "en_Latn_US", or "en_US_POSIX".
   std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
@@ -132,11 +132,11 @@
   return true;
 }
 
-bool LocaleValue::InitFromBcp47Tag(const StringPiece& bcp47tag) {
+bool LocaleValue::InitFromBcp47Tag(StringPiece bcp47tag) {
   return InitFromBcp47TagImpl(bcp47tag, '-');
 }
 
-bool LocaleValue::InitFromBcp47TagImpl(const StringPiece& bcp47tag, const char separator) {
+bool LocaleValue::InitFromBcp47TagImpl(StringPiece bcp47tag, const char separator) {
   std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator);
   if (subtags.size() == 1) {
     set_language(subtags[0].c_str());
diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp
index b3fb145..b68143d 100644
--- a/libs/androidfw/LocaleDataTables.cpp
+++ b/libs/androidfw/LocaleDataTables.cpp
@@ -143,6 +143,7 @@
     {0xACE00000u, 46u}, // ahl -> Latn
     {0xB8E00000u,  1u}, // aho -> Ahom
     {0x99200000u, 46u}, // ajg -> Latn
+    {0xCD200000u,  2u}, // ajt -> Arab
     {0x616B0000u, 46u}, // ak -> Latn
     {0xA9400000u, 101u}, // akk -> Xsux
     {0x81600000u, 46u}, // ala -> Latn
@@ -1053,6 +1054,7 @@
     {0xB70D0000u, 46u}, // nyn -> Latn
     {0xA32D0000u, 46u}, // nzi -> Latn
     {0x6F630000u, 46u}, // oc -> Latn
+    {0x6F634553u, 46u}, // oc-ES -> Latn
     {0x88CE0000u, 46u}, // ogc -> Latn
     {0x6F6A0000u, 11u}, // oj -> Cans
     {0xC92E0000u, 11u}, // ojs -> Cans
@@ -1093,6 +1095,7 @@
     {0xB4EF0000u, 71u}, // phn -> Phnx
     {0xAD0F0000u, 46u}, // pil -> Latn
     {0xBD0F0000u, 46u}, // pip -> Latn
+    {0xC90F0000u, 46u}, // pis -> Latn
     {0x814F0000u,  9u}, // pka -> Brah
     {0xB94F0000u, 46u}, // pko -> Latn
     {0x706C0000u, 46u}, // pl -> Latn
@@ -1204,12 +1207,14 @@
     {0xE1720000u, 46u}, // sly -> Latn
     {0x736D0000u, 46u}, // sm -> Latn
     {0x81920000u, 46u}, // sma -> Latn
+    {0x8D920000u, 46u}, // smd -> Latn
     {0xA5920000u, 46u}, // smj -> Latn
     {0xB5920000u, 46u}, // smn -> Latn
     {0xBD920000u, 76u}, // smp -> Samr
     {0xC1920000u, 46u}, // smq -> Latn
     {0xC9920000u, 46u}, // sms -> Latn
     {0x736E0000u, 46u}, // sn -> Latn
+    {0x85B20000u, 46u}, // snb -> Latn
     {0x89B20000u, 46u}, // snc -> Latn
     {0xA9B20000u, 46u}, // snk -> Latn
     {0xBDB20000u, 46u}, // snp -> Latn
@@ -1314,6 +1319,7 @@
     {0x746F0000u, 46u}, // to -> Latn
     {0x95D30000u, 46u}, // tof -> Latn
     {0x99D30000u, 46u}, // tog -> Latn
+    {0xA9D30000u, 46u}, // tok -> Latn
     {0xC1D30000u, 46u}, // toq -> Latn
     {0xA1F30000u, 46u}, // tpi -> Latn
     {0xB1F30000u, 46u}, // tpm -> Latn
@@ -1527,6 +1533,7 @@
     0x61665A414C61746ELLU, // af_Latn_ZA
     0xC0C0434D4C61746ELLU, // agq_Latn_CM
     0xB8E0494E41686F6DLLU, // aho_Ahom_IN
+    0xCD20544E41726162LLU, // ajt_Arab_TN
     0x616B47484C61746ELLU, // ak_Latn_GH
     0xA940495158737578LLU, // akk_Xsux_IQ
     0xB560584B4C61746ELLU, // aln_Latn_XK
@@ -1534,6 +1541,7 @@
     0x616D455445746869LLU, // am_Ethi_ET
     0xB9804E474C61746ELLU, // amo_Latn_NG
     0x616E45534C61746ELLU, // an_Latn_ES
+    0xB5A04E474C61746ELLU, // ann_Latn_NG
     0xE5C049444C61746ELLU, // aoz_Latn_ID
     0x8DE0544741726162LLU, // apd_Arab_TG
     0x6172454741726162LLU, // ar_Arab_EG
@@ -2039,6 +2047,7 @@
     0xB88F49525870656FLLU, // peo_Xpeo_IR
     0xACAF44454C61746ELLU, // pfl_Latn_DE
     0xB4EF4C4250686E78LLU, // phn_Phnx_LB
+    0xC90F53424C61746ELLU, // pis_Latn_SB
     0x814F494E42726168LLU, // pka_Brah_IN
     0xB94F4B454C61746ELLU, // pko_Latn_KE
     0x706C504C4C61746ELLU, // pl_Latn_PL
@@ -2119,11 +2128,13 @@
     0xE17249444C61746ELLU, // sly_Latn_ID
     0x736D57534C61746ELLU, // sm_Latn_WS
     0x819253454C61746ELLU, // sma_Latn_SE
+    0x8D92414F4C61746ELLU, // smd_Latn_AO
     0xA59253454C61746ELLU, // smj_Latn_SE
     0xB59246494C61746ELLU, // smn_Latn_FI
     0xBD92494C53616D72LLU, // smp_Samr_IL
     0xC99246494C61746ELLU, // sms_Latn_FI
     0x736E5A574C61746ELLU, // sn_Latn_ZW
+    0x85B24D594C61746ELLU, // snb_Latn_MY
     0xA9B24D4C4C61746ELLU, // snk_Latn_ML
     0x736F534F4C61746ELLU, // so_Latn_SO
     0x99D2555A536F6764LLU, // sog_Sogd_UZ
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 5e8a623..31516dc 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -33,7 +33,9 @@
 #include <type_traits>
 #include <vector>
 
+#include <android-base/file.h>
 #include <android-base/macros.h>
+#include <android-base/utf8.h>
 #include <androidfw/ByteBucketArray.h>
 #include <androidfw/ResourceTypes.h>
 #include <androidfw/TypeWrappers.h>
@@ -236,12 +238,23 @@
 }
 
 bool IsFabricatedOverlay(const std::string& path) {
-  std::ifstream fin(path);
-  uint32_t magic;
-  if (fin.read(reinterpret_cast<char*>(&magic), sizeof(uint32_t))) {
-    return magic == kFabricatedOverlayMagic;
+  return IsFabricatedOverlay(path.c_str());
+}
+
+bool IsFabricatedOverlay(const char* path) {
+  auto fd = base::unique_fd(base::utf8::open(path, O_RDONLY|O_CLOEXEC));
+  if (fd < 0) {
+    return false;
   }
-  return false;
+  return IsFabricatedOverlay(fd);
+}
+
+bool IsFabricatedOverlay(base::borrowed_fd fd) {
+  uint32_t magic;
+  if (!base::ReadFullyAtOffset(fd, &magic, sizeof(magic), 0)) {
+    return false;
+  }
+  return magic == kFabricatedOverlayMagic;
 }
 
 static bool assertIdmapHeader(const void* idmap, size_t size) {
@@ -4487,20 +4500,14 @@
         return err;
     }
 
-    if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) {
+    if (entry.entry->map_entry()) {
         if (!mayBeBag) {
             ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID);
         }
         return BAD_VALUE;
     }
 
-    const Res_value* value = reinterpret_cast<const Res_value*>(
-            reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size);
-
-    outValue->size = dtohs(value->size);
-    outValue->res0 = value->res0;
-    outValue->dataType = value->dataType;
-    outValue->data = dtohl(value->data);
+    *outValue = entry.entry->value();
 
     // The reference may be pointing to a resource in a shared library. These
     // references have build-time generated package IDs. These ids may not match
@@ -4691,11 +4698,10 @@
         return err;
     }
 
-    const uint16_t entrySize = dtohs(entry.entry->size);
-    const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
-        ? dtohl(((const ResTable_map_entry*)entry.entry)->parent.ident) : 0;
-    const uint32_t count = entrySize >= sizeof(ResTable_map_entry)
-        ? dtohl(((const ResTable_map_entry*)entry.entry)->count) : 0;
+    const uint16_t entrySize = entry.entry->size();
+    const ResTable_map_entry* map_entry = entry.entry->map_entry();
+    const uint32_t parent = map_entry ? dtohl(map_entry->parent.ident) : 0;
+    const uint32_t count = map_entry ? dtohl(map_entry->count) : 0;
 
     size_t N = count;
 
@@ -4759,7 +4765,7 @@
 
     // Now merge in the new attributes...
     size_t curOff = (reinterpret_cast<uintptr_t>(entry.entry) - reinterpret_cast<uintptr_t>(entry.type))
-        + dtohs(entry.entry->size);
+        + entrySize;
     const ResTable_map* map;
     bag_entry* entries = (bag_entry*)(set+1);
     size_t curEntry = 0;
@@ -5137,7 +5143,7 @@
                     continue;
                 }
 
-                if (dtohl(entry->key.index) == (size_t) *ei) {
+                if (entry->key() == (size_t) *ei) {
                     uint32_t resId = Res_MAKEID(group->id - 1, typeIndex, iter.index());
                     if (outTypeSpecFlags) {
                         Entry result;
@@ -6600,8 +6606,12 @@
                     // Entry does not exist.
                     continue;
                 }
-
-                thisOffset = dtohl(eindex[realEntryIndex]);
+                if (thisType->flags & ResTable_type::FLAG_OFFSET16) {
+                    auto eindex16 = reinterpret_cast<const uint16_t*>(eindex);
+                    thisOffset = offset_from16(eindex16[realEntryIndex]);
+                } else {
+                    thisOffset = dtohl(eindex[realEntryIndex]);
+                }
             }
 
             if (thisOffset == ResTable_type::NO_ENTRY) {
@@ -6651,8 +6661,8 @@
 
     const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>(
             reinterpret_cast<const uint8_t*>(bestType) + bestOffset);
-    if (dtohs(entry->size) < sizeof(*entry)) {
-        ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size));
+    if (entry->size() < sizeof(*entry)) {
+        ALOGW("ResTable_entry size 0x%zx is too small", entry->size());
         return BAD_TYPE;
     }
 
@@ -6663,7 +6673,7 @@
         outEntry->specFlags = specFlags;
         outEntry->package = bestPackage;
         outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset);
-        outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index));
+        outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, entry->key());
     }
     return NO_ERROR;
 }
@@ -6991,11 +7001,10 @@
 DynamicRefTable::DynamicRefTable() : DynamicRefTable(0, false) {}
 
 DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib)
-    : mAssignedPackageId(packageId)
+    : mLookupTable()
+    , mAssignedPackageId(packageId)
     , mAppAsLib(appAsLib)
 {
-    memset(mLookupTable, 0, sizeof(mLookupTable));
-
     // Reserved package ids
     mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID;
     mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID;
@@ -7076,10 +7085,6 @@
     mLookupTable[buildPackageId] = runtimePackageId;
 }
 
-void DynamicRefTable::addAlias(uint32_t stagedId, uint32_t finalizedId) {
-  mAliasId[stagedId] = finalizedId;
-}
-
 status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
     uint32_t res = *resId;
     size_t packageId = Res_GETPACKAGE(res) + 1;
@@ -7089,11 +7094,12 @@
         return NO_ERROR;
     }
 
-    auto alias_id = mAliasId.find(res);
-    if (alias_id != mAliasId.end()) {
+    const auto alias_it = std::lower_bound(mAliasId.begin(), mAliasId.end(), res,
+        [](const AliasMap::value_type& pair, uint32_t val) { return pair.first < val; });
+    if (alias_it != mAliasId.end() && alias_it->first == res) {
       // Rewrite the resource id to its alias resource id. Since the alias resource id is a
       // compile-time id, it still needs to be resolved further.
-      res = alias_id->second;
+      res = alias_it->second;
     }
 
     if (packageId == SYS_PACKAGE_ID || (packageId == APP_PACKAGE_ID && !mAppAsLib)) {
@@ -7653,6 +7659,9 @@
                         if (type->flags & ResTable_type::FLAG_SPARSE) {
                             printf(" [sparse]");
                         }
+                        if (type->flags & ResTable_type::FLAG_OFFSET16) {
+                            printf(" [offset16]");
+                        }
                     }
 
                     printf(":\n");
@@ -7684,7 +7693,13 @@
                             thisOffset = static_cast<uint32_t>(dtohs(entry->offset)) * 4u;
                         } else {
                             entryId = entryIndex;
-                            thisOffset = dtohl(eindex[entryIndex]);
+                            if (type->flags & ResTable_type::FLAG_OFFSET16) {
+                                const auto eindex16 =
+                                    reinterpret_cast<const uint16_t*>(eindex);
+                                thisOffset = offset_from16(eindex16[entryIndex]);
+                            } else {
+                                thisOffset = dtohl(eindex[entryIndex]);
+                            }
                             if (thisOffset == ResTable_type::NO_ENTRY) {
                                 continue;
                             }
@@ -7734,7 +7749,7 @@
                             continue;
                         }
 
-                        uintptr_t esize = dtohs(ent->size);
+                        uintptr_t esize = ent->size();
                         if ((esize&0x3) != 0) {
                             printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize);
                             continue;
@@ -7746,30 +7761,27 @@
                         }
 
                         const Res_value* valuePtr = NULL;
-                        const ResTable_map_entry* bagPtr = NULL;
+                        const ResTable_map_entry* bagPtr = ent->map_entry();
                         Res_value value;
-                        if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) {
+                        if (bagPtr) {
                             printf("<bag>");
-                            bagPtr = (const ResTable_map_entry*)ent;
                         } else {
-                            valuePtr = (const Res_value*)
-                                (((const uint8_t*)ent) + esize);
-                            value.copyFrom_dtoh(*valuePtr);
+                            value = ent->value();
                             printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)",
                                    (int)value.dataType, (int)value.data,
                                    (int)value.size, (int)value.res0);
                         }
 
-                        if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) {
+                        if (ent->flags() & ResTable_entry::FLAG_PUBLIC) {
                             printf(" (PUBLIC)");
                         }
                         printf("\n");
 
                         if (inclValues) {
-                            if (valuePtr != NULL) {
+                            if (bagPtr == NULL) {
                                 printf("          ");
                                 print_value(typeConfigs->package, value);
-                            } else if (bagPtr != NULL) {
+                            } else {
                                 const int N = dtohl(bagPtr->count);
                                 const uint8_t* baseMapPtr = (const uint8_t*)ent;
                                 size_t mapOffset = esize;
diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp
index 87fb2c0..ccb6156 100644
--- a/libs/androidfw/ResourceUtils.cpp
+++ b/libs/androidfw/ResourceUtils.cpp
@@ -18,7 +18,7 @@
 
 namespace android {
 
-bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
+bool ExtractResourceName(StringPiece str, StringPiece* out_package, StringPiece* out_type,
                          StringPiece* out_entry) {
   *out_package = "";
   *out_type = "";
@@ -33,16 +33,16 @@
   while (current != end) {
     if (out_type->size() == 0 && *current == '/') {
       has_type_separator = true;
-      out_type->assign(start, current - start);
+      *out_type = StringPiece(start, current - start);
       start = current + 1;
     } else if (out_package->size() == 0 && *current == ':') {
       has_package_separator = true;
-      out_package->assign(start, current - start);
+      *out_package = StringPiece(start, current - start);
       start = current + 1;
     }
     current++;
   }
-  out_entry->assign(start, end - start);
+  *out_entry = StringPiece(start, end - start);
 
   return !(has_package_separator && out_package->empty()) &&
          !(has_type_separator && out_type->empty());
@@ -50,7 +50,7 @@
 
 base::expected<AssetManager2::ResourceName, NullOrIOError> ToResourceName(
     const StringPoolRef& type_string_ref, const StringPoolRef& entry_string_ref,
-    const StringPiece& package_name) {
+    StringPiece package_name) {
   AssetManager2::ResourceName name{
     .package = package_name.data(),
     .package_len = package_name.size(),
diff --git a/libs/androidfw/StringPool.cpp b/libs/androidfw/StringPool.cpp
index b59e906..1cb8df3 100644
--- a/libs/androidfw/StringPool.cpp
+++ b/libs/androidfw/StringPool.cpp
@@ -161,16 +161,15 @@
   return entry_->context;
 }
 
-StringPool::Ref StringPool::MakeRef(const StringPiece& str) {
+StringPool::Ref StringPool::MakeRef(StringPiece str) {
   return MakeRefImpl(str, Context{}, true);
 }
 
-StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context) {
+StringPool::Ref StringPool::MakeRef(StringPiece str, const Context& context) {
   return MakeRefImpl(str, context, true);
 }
 
-StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& context,
-                                        bool unique) {
+StringPool::Ref StringPool::MakeRefImpl(StringPiece str, const Context& context, bool unique) {
   if (unique) {
     auto range = indexed_strings_.equal_range(str);
     for (auto iter = range.first; iter != range.second; ++iter) {
@@ -181,7 +180,7 @@
   }
 
   std::unique_ptr<Entry> entry(new Entry());
-  entry->value = str.to_string();
+  entry->value = std::string(str);
   entry->context = context;
   entry->index_ = strings_.size();
   entry->ref_ = 0;
diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp
index 647aa19..70d14a1 100644
--- a/libs/androidfw/TypeWrappers.cpp
+++ b/libs/androidfw/TypeWrappers.cpp
@@ -59,7 +59,9 @@
             + dtohl(type->header.size);
     const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>(
             reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize));
-    if (reinterpret_cast<uintptr_t>(entryIndices) + (sizeof(uint32_t) * entryCount) > containerEnd) {
+    const size_t indexSize = type->flags & ResTable_type::FLAG_OFFSET16 ?
+                                    sizeof(uint16_t) : sizeof(uint32_t);
+    if (reinterpret_cast<uintptr_t>(entryIndices) + (indexSize * entryCount) > containerEnd) {
         ALOGE("Type's entry indices extend beyond its boundaries");
         return NULL;
     }
@@ -73,6 +75,9 @@
       }
 
       entryOffset = static_cast<uint32_t>(dtohs(ResTable_sparseTypeEntry{*iter}.offset)) * 4u;
+    } else if (type->flags & ResTable_type::FLAG_OFFSET16) {
+      auto entryIndices16 = reinterpret_cast<const uint16_t*>(entryIndices);
+      entryOffset = offset_from16(entryIndices16[mIndex]);
     } else {
       entryOffset = dtohl(entryIndices[mIndex]);
     }
@@ -91,11 +96,11 @@
     if (reinterpret_cast<uintptr_t>(entry) > containerEnd - sizeof(*entry)) {
         ALOGE("Entry offset at index %u points outside the Type's boundaries", mIndex);
         return NULL;
-    } else if (reinterpret_cast<uintptr_t>(entry) + dtohs(entry->size) > containerEnd) {
+    } else if (reinterpret_cast<uintptr_t>(entry) + entry->size() > containerEnd) {
         ALOGE("Entry at index %u extends beyond Type's boundaries", mIndex);
         return NULL;
-    } else if (dtohs(entry->size) < sizeof(*entry)) {
-        ALOGE("Entry at index %u is too small (%u)", mIndex, dtohs(entry->size));
+    } else if (entry->size() < sizeof(*entry)) {
+        ALOGE("Entry at index %u is too small (%zu)", mIndex, entry->size());
         return NULL;
     }
     return entry;
diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp
index 52ad0dc..be55fe8 100644
--- a/libs/androidfw/Util.cpp
+++ b/libs/androidfw/Util.cpp
@@ -42,7 +42,7 @@
   }
 }
 
-std::u16string Utf8ToUtf16(const StringPiece& utf8) {
+std::u16string Utf8ToUtf16(StringPiece utf8) {
   ssize_t utf16_length =
       utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
   if (utf16_length <= 0) {
@@ -56,7 +56,7 @@
   return utf16;
 }
 
-std::string Utf16ToUtf8(const StringPiece16& utf16) {
+std::string Utf16ToUtf8(StringPiece16 utf16) {
   ssize_t utf8_length = utf16_to_utf8_length(utf16.data(), utf16.length());
   if (utf8_length <= 0) {
     return {};
@@ -68,7 +68,7 @@
   return utf8;
 }
 
-std::string Utf8ToModifiedUtf8(const std::string& utf8) {
+std::string Utf8ToModifiedUtf8(std::string_view utf8) {
   // Java uses Modified UTF-8 which only supports the 1, 2, and 3 byte formats of UTF-8. To encode
   // 4 byte UTF-8 codepoints, Modified UTF-8 allows the use of surrogate pairs in the same format
   // of CESU-8 surrogate pairs. Calculate the size of the utf8 string with all 4 byte UTF-8
@@ -86,7 +86,7 @@
 
   // Early out if no 4 byte codepoints are found
   if (size == modified_size) {
-    return utf8;
+    return std::string(utf8);
   }
 
   std::string output;
@@ -115,7 +115,7 @@
   return output;
 }
 
-std::string ModifiedUtf8ToUtf8(const std::string& modified_utf8) {
+std::string ModifiedUtf8ToUtf8(std::string_view modified_utf8) {
   // The UTF-8 representation will have a byte length less than or equal to the Modified UTF-8
   // representation.
   std::string output;
@@ -170,30 +170,28 @@
   return output;
 }
 
-static std::vector<std::string> SplitAndTransform(
-    const StringPiece& str, char sep, const std::function<char(char)>& f) {
+template <class Func>
+static std::vector<std::string> SplitAndTransform(StringPiece str, char sep, Func&& f) {
   std::vector<std::string> parts;
   const StringPiece::const_iterator end = std::end(str);
   StringPiece::const_iterator start = std::begin(str);
   StringPiece::const_iterator current;
   do {
     current = std::find(start, end, sep);
-    parts.emplace_back(str.substr(start, current).to_string());
-    if (f) {
-      std::string& part = parts.back();
-      std::transform(part.begin(), part.end(), part.begin(), f);
-    }
+    parts.emplace_back(StringPiece(start, current - start));
+    std::string& part = parts.back();
+    std::transform(part.begin(), part.end(), part.begin(), f);
     start = current + 1;
   } while (current != end);
   return parts;
 }
 
-std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) {
-  return SplitAndTransform(str, sep, ::tolower);
+std::vector<std::string> SplitAndLowercase(StringPiece str, char sep) {
+  return SplitAndTransform(str, sep, [](char c) { return ::tolower(c); });
 }
 
 std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer) {
-  std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
+  auto data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
   uint8_t* p = data.get();
   for (const auto& block : buffer) {
     memcpy(p, block.buffer.get(), block.size);
@@ -211,7 +209,7 @@
 
 std::string GetString(const android::ResStringPool& pool, size_t idx) {
   if (auto str = pool.string8At(idx); str.ok()) {
-    return ModifiedUtf8ToUtf8(str->to_string());
+    return ModifiedUtf8ToUtf8(*str);
   }
   return Utf16ToUtf8(GetString16(pool, idx));
 }
diff --git a/libs/androidfw/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp
index 58fc5bb..a1385f2 100644
--- a/libs/androidfw/ZipUtils.cpp
+++ b/libs/androidfw/ZipUtils.cpp
@@ -35,7 +35,7 @@
 using namespace android;
 
 // TODO: This can go away once the only remaining usage in aapt goes away.
-class FileReader : public zip_archive::Reader {
+class FileReader final : public zip_archive::Reader {
   public:
     explicit FileReader(FILE* fp) : Reader(), mFp(fp), mCurrentOffset(0) {
     }
@@ -66,7 +66,7 @@
     mutable off64_t mCurrentOffset;
 };
 
-class FdReader : public zip_archive::Reader {
+class FdReader final : public zip_archive::Reader {
   public:
     explicit FdReader(int fd) : mFd(fd) {
     }
@@ -79,7 +79,7 @@
     const int mFd;
 };
 
-class BufferReader : public zip_archive::Reader {
+class BufferReader final : public zip_archive::Reader {
   public:
     BufferReader(incfs::map_ptr<void> input, size_t inputSize) : Reader(),
         mInput(input.convert<uint8_t>()),
@@ -105,7 +105,7 @@
     const size_t mInputSize;
 };
 
-class BufferWriter : public zip_archive::Writer {
+class BufferWriter final : public zip_archive::Writer {
   public:
     BufferWriter(void* output, size_t outputSize) : Writer(),
         mOutput(reinterpret_cast<uint8_t*>(output)), mOutputSize(outputSize), mBytesWritten(0) {
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 1bde792..f10cb9b 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -17,6 +17,7 @@
 #ifndef ANDROIDFW_ASSETMANAGER2_H_
 #define ANDROIDFW_ASSETMANAGER2_H_
 
+#include "android-base/function_ref.h"
 #include "android-base/macros.h"
 
 #include <array>
@@ -104,7 +105,7 @@
   // new resource IDs.
   bool SetApkAssets(std::vector<const ApkAssets*> apk_assets, bool invalidate_caches = true);
 
-  inline const std::vector<const ApkAssets*> GetApkAssets() const {
+  inline const std::vector<const ApkAssets*>& GetApkAssets() const {
     return apk_assets_;
   }
 
@@ -124,8 +125,7 @@
   uint8_t GetAssignedPackageId(const LoadedPackage* package) const;
 
   // Returns a string representation of the overlayable API of a package.
-  bool GetOverlayablesToString(const android::StringPiece& package_name,
-                               std::string* out) const;
+  bool GetOverlayablesToString(android::StringPiece package_name, std::string* out) const;
 
   const std::unordered_map<std::string, std::string>* GetOverlayableMapForPackage(
       uint32_t package_id) const;
@@ -321,17 +321,8 @@
   // Creates a new Theme from this AssetManager.
   std::unique_ptr<Theme> NewTheme();
 
-  void ForEachPackage(const std::function<bool(const std::string&, uint8_t)> func,
-                      package_property_t excluded_property_flags = 0U) const {
-    for (const PackageGroup& package_group : package_groups_) {
-      const auto loaded_package = package_group.packages_.front().loaded_package_;
-      if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U
-          && !func(loaded_package->GetPackageName(),
-                   package_group.dynamic_ref_table->mAssignedPackageId)) {
-        return;
-      }
-    }
-  }
+  void ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func,
+                      package_property_t excluded_property_flags = 0U) const;
 
   void DumpToLog() const;
 
@@ -572,6 +563,7 @@
   AssetManager2* asset_manager_ = nullptr;
   uint32_t type_spec_flags_ = 0u;
 
+  std::vector<uint32_t> keys_;
   std::vector<Entry> entries_;
 };
 
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index 966ec74..d33c325 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -20,6 +20,7 @@
 #include <memory>
 #include <string>
 
+#include "android-base/function_ref.h"
 #include "android-base/macros.h"
 #include "android-base/unique_fd.h"
 
@@ -46,7 +47,7 @@
   // Iterate over all files and directories provided by the interface. The order of iteration is
   // stable.
   virtual bool ForEachFile(const std::string& path,
-                           const std::function<void(const StringPiece&, FileType)>& f) const = 0;
+                           base::function_ref<void(StringPiece, FileType)> f) const = 0;
 
   // Retrieves the path to the contents of the AssetsProvider on disk. The path could represent an
   // APk, a directory, or some other file type.
@@ -80,8 +81,8 @@
 
 // Supplies assets from a zip archive.
 struct ZipAssetsProvider : public AssetsProvider {
-  static std::unique_ptr<ZipAssetsProvider> Create(std::string path,
-                                                   package_property_t flags);
+  static std::unique_ptr<ZipAssetsProvider> Create(std::string path, package_property_t flags,
+                                                   base::unique_fd fd = {});
 
   static std::unique_ptr<ZipAssetsProvider> Create(base::unique_fd fd,
                                                    std::string friendly_name,
@@ -90,7 +91,7 @@
                                                    off64_t len = kUnknownLength);
 
   bool ForEachFile(const std::string& root_path,
-                   const std::function<void(const StringPiece&, FileType)>& f) const override;
+                   base::function_ref<void(StringPiece, FileType)> f) const override;
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
@@ -108,7 +109,12 @@
                     time_t last_mod_time);
 
   struct PathOrDebugName {
-    PathOrDebugName(std::string&& value, bool is_path);
+    static PathOrDebugName Path(std::string value) {
+      return {std::move(value), true};
+    }
+    static PathOrDebugName DebugName(std::string value) {
+      return {std::move(value), false};
+    }
 
     // Retrieves the path or null if this class represents a debug name.
     WARN_UNUSED const std::string* GetPath() const;
@@ -117,11 +123,16 @@
     WARN_UNUSED const std::string& GetDebugName() const;
 
    private:
+    PathOrDebugName(std::string value, bool is_path) : value_(std::move(value)), is_path_(is_path) {
+    }
     std::string value_;
     bool is_path_;
   };
 
-  std::unique_ptr<ZipArchive, void (*)(ZipArchive*)> zip_handle_;
+  struct ZipCloser {
+    void operator()(ZipArchive* a) const;
+  };
+  std::unique_ptr<ZipArchive, ZipCloser> zip_handle_;
   PathOrDebugName name_;
   package_property_t flags_;
   time_t last_mod_time_;
@@ -132,7 +143,7 @@
   static std::unique_ptr<DirectoryAssetsProvider> Create(std::string root_dir);
 
   bool ForEachFile(const std::string& path,
-                   const std::function<void(const StringPiece&, FileType)>& f) const override;
+                   base::function_ref<void(StringPiece, FileType)> f) const override;
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
@@ -157,7 +168,7 @@
                                                 std::unique_ptr<AssetsProvider>&& secondary);
 
   bool ForEachFile(const std::string& root_path,
-                   const std::function<void(const StringPiece&, FileType)>& f) const override;
+                   base::function_ref<void(StringPiece, FileType)> f) const override;
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
@@ -181,10 +192,10 @@
 // Does not provide any assets.
 struct EmptyAssetsProvider : public AssetsProvider {
   static std::unique_ptr<AssetsProvider> Create();
-  static std::unique_ptr<AssetsProvider> Create(const std::string& path);
+  static std::unique_ptr<AssetsProvider> Create(std::string path);
 
   bool ForEachFile(const std::string& path,
-                  const std::function<void(const StringPiece&, FileType)>& f) const override;
+                   base::function_ref<void(StringPiece, FileType)> f) const override;
 
   WARN_UNUSED std::optional<std::string_view> GetPath() const override;
   WARN_UNUSED const std::string& GetDebugName() const override;
diff --git a/libs/androidfw/include/androidfw/ByteBucketArray.h b/libs/androidfw/include/androidfw/ByteBucketArray.h
index 949c9445..ca0a9ed 100644
--- a/libs/androidfw/include/androidfw/ByteBucketArray.h
+++ b/libs/androidfw/include/androidfw/ByteBucketArray.h
@@ -17,6 +17,7 @@
 #ifndef __BYTE_BUCKET_ARRAY_H
 #define __BYTE_BUCKET_ARRAY_H
 
+#include <algorithm>
 #include <cstdint>
 #include <cstring>
 
@@ -31,14 +32,16 @@
 template <typename T>
 class ByteBucketArray {
  public:
-  ByteBucketArray() : default_() { memset(buckets_, 0, sizeof(buckets_)); }
+  ByteBucketArray() {
+    memset(buckets_, 0, sizeof(buckets_));
+  }
 
   ~ByteBucketArray() {
-    for (size_t i = 0; i < kNumBuckets; i++) {
-      if (buckets_[i] != NULL) {
-        delete[] buckets_[i];
-      }
-    }
+    deleteBuckets();
+  }
+
+  void clear() {
+    deleteBuckets();
     memset(buckets_, 0, sizeof(buckets_));
   }
 
@@ -53,7 +56,7 @@
 
     uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
     T* bucket = buckets_[bucket_index];
-    if (bucket == NULL) {
+    if (bucket == nullptr) {
       return default_;
     }
     return bucket[0x0f & static_cast<uint8_t>(index)];
@@ -64,9 +67,9 @@
                           << ") with size=" << size();
 
     uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
-    T* bucket = buckets_[bucket_index];
-    if (bucket == NULL) {
-      bucket = buckets_[bucket_index] = new T[kBucketSize]();
+    T*& bucket = buckets_[bucket_index];
+    if (bucket == nullptr) {
+      bucket = new T[kBucketSize]();
     }
     return bucket[0x0f & static_cast<uint8_t>(index)];
   }
@@ -80,11 +83,44 @@
     return true;
   }
 
+  template <class Func>
+  void forEachItem(Func f) {
+    for (size_t i = 0; i < kNumBuckets; i++) {
+      const auto bucket = buckets_[i];
+      if (bucket != nullptr) {
+        for (size_t j = 0; j < kBucketSize; j++) {
+          f((i << 4) | j, bucket[j]);
+        }
+      }
+    }
+  }
+
+  template <class Func>
+  void trimBuckets(Func isEmptyFunc) {
+    for (size_t i = 0; i < kNumBuckets; i++) {
+      const auto bucket = buckets_[i];
+      if (bucket != nullptr) {
+        if (std::all_of(bucket, bucket + kBucketSize, isEmptyFunc)) {
+          delete[] bucket;
+          buckets_[i] = nullptr;
+        }
+      }
+    }
+  }
+
  private:
   enum { kNumBuckets = 16, kBucketSize = 16 };
 
+  void deleteBuckets() {
+    for (size_t i = 0; i < kNumBuckets; i++) {
+      if (buckets_[i] != nullptr) {
+        delete[] buckets_[i];
+      }
+    }
+  }
+
   T* buckets_[kNumBuckets];
-  T default_;
+  static inline const T default_ = {};
 };
 
 }  // namespace android
diff --git a/libs/androidfw/include/androidfw/ConfigDescription.h b/libs/androidfw/include/androidfw/ConfigDescription.h
index 61d10cd..71087cd 100644
--- a/libs/androidfw/include/androidfw/ConfigDescription.h
+++ b/libs/androidfw/include/androidfw/ConfigDescription.h
@@ -72,7 +72,7 @@
    * The resulting configuration has the appropriate sdkVersion defined
    * for backwards compatibility.
    */
-  static bool Parse(const android::StringPiece& str, ConfigDescription* out = nullptr);
+  static bool Parse(android::StringPiece str, ConfigDescription* out = nullptr);
 
   /**
    * If the configuration uses an axis that was added after
diff --git a/libs/androidfw/include/androidfw/IDiagnostics.h b/libs/androidfw/include/androidfw/IDiagnostics.h
index 273b05a..4d5844e 100644
--- a/libs/androidfw/include/androidfw/IDiagnostics.h
+++ b/libs/androidfw/include/androidfw/IDiagnostics.h
@@ -35,7 +35,7 @@
  public:
   DiagMessage() = default;
 
-  explicit DiagMessage(const android::StringPiece& src) : source_(src) {
+  explicit DiagMessage(android::StringPiece src) : source_(src) {
   }
 
   explicit DiagMessage(const Source& src) : source_(src) {
@@ -61,7 +61,7 @@
 
 template <>
 inline DiagMessage& DiagMessage::operator<<(const ::std::u16string& value) {
-  message_ << android::StringPiece16(value);
+  message_ << value;
   return *this;
 }
 
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index a1cbbbf..6068912 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -93,8 +93,8 @@
    public:
     Result() = default;
     explicit Result(uint32_t value) : data_(value) {};
-    explicit Result(const std::map<ConfigDescription, Res_value> &value)
-        : data_(value) { };
+    explicit Result(std::map<ConfigDescription, Res_value> value) : data_(std::move(value)) {
+    }
 
     // Returns `true` if the resource is overlaid.
     explicit operator bool() const {
@@ -157,8 +157,7 @@
 class LoadedIdmap {
  public:
   // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
-  static std::unique_ptr<LoadedIdmap> Load(const StringPiece& idmap_path,
-                                           const StringPiece& idmap_data);
+  static std::unique_ptr<LoadedIdmap> Load(StringPiece idmap_path, StringPiece idmap_data);
 
   // Returns the path to the IDMAP.
   std::string_view IdmapPath() const {
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index e459639..4d12885 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -99,8 +99,8 @@
 };
 
 struct OverlayableInfo {
-  std::string name;
-  std::string actor;
+  std::string_view name;
+  std::string_view actor;
   uint32_t policy_flags;
 };
 
@@ -166,14 +166,14 @@
   base::expected<uint32_t, NullOrIOError> FindEntryByName(const std::u16string& type_name,
                                                           const std::u16string& entry_name) const;
 
-  static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntry(
-      incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
+  static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+      GetEntry(incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
 
   static base::expected<uint32_t, NullOrIOError> GetEntryOffset(
       incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
 
-  static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntryFromOffset(
-      incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
+  static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+      GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
 
   // Returns the string pool where type names are stored.
   const ResStringPool* GetTypeStringPool() const {
@@ -275,7 +275,7 @@
     return overlayable_map_;
   }
 
-  const std::map<uint32_t, uint32_t>& GetAliasResourceIdMap() const {
+  const std::vector<std::pair<uint32_t, uint32_t>>& GetAliasResourceIdMap() const {
     return alias_id_map_;
   }
 
@@ -295,8 +295,8 @@
   std::unordered_map<uint8_t, TypeSpec> type_specs_;
   ByteBucketArray<uint32_t> resource_ids_;
   std::vector<DynamicPackageEntry> dynamic_package_map_;
-  std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
-  std::map<uint32_t, uint32_t> alias_id_map_;
+  std::vector<std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
+  std::vector<std::pair<uint32_t, uint32_t>> alias_id_map_;
 
   // A map of overlayable name to actor
   std::unordered_map<std::string, std::string> overlayable_map_;
diff --git a/libs/androidfw/include/androidfw/Locale.h b/libs/androidfw/include/androidfw/Locale.h
index 484ed79..8934bed 100644
--- a/libs/androidfw/include/androidfw/Locale.h
+++ b/libs/androidfw/include/androidfw/Locale.h
@@ -39,10 +39,10 @@
   /**
    * Initialize this LocaleValue from a config string.
    */
-  bool InitFromFilterString(const android::StringPiece& config);
+  bool InitFromFilterString(android::StringPiece config);
 
   // Initializes this LocaleValue from a BCP-47 locale tag.
-  bool InitFromBcp47Tag(const android::StringPiece& bcp47tag);
+  bool InitFromBcp47Tag(android::StringPiece bcp47tag);
 
   /**
    * Initialize this LocaleValue from parts of a vector.
@@ -70,7 +70,7 @@
   inline bool operator>(const LocaleValue& o) const;
 
  private:
-  bool InitFromBcp47TagImpl(const android::StringPiece& bcp47tag, const char separator);
+  bool InitFromBcp47TagImpl(android::StringPiece bcp47tag, const char separator);
 
   void set_language(const char* language);
   void set_region(const char* language);
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 9309091..52321da 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -21,11 +21,13 @@
 #define _LIBS_UTILS_RESOURCE_TYPES_H
 
 #include <android-base/expected.h>
+#include <android-base/unique_fd.h>
 
 #include <androidfw/Asset.h>
 #include <androidfw/Errors.h>
 #include <androidfw/LocaleData.h>
 #include <androidfw/StringPiece.h>
+#include <utils/ByteOrder.h>
 #include <utils/Errors.h>
 #include <utils/String16.h>
 #include <utils/Vector.h>
@@ -53,10 +55,12 @@
 // The version should only be changed when a backwards-incompatible change must be made to the
 // fabricated overlay file format. Old fabricated overlays must be migrated to the new file format
 // to prevent losing fabricated overlay data.
-constexpr const uint32_t kFabricatedOverlayCurrentVersion = 2;
+constexpr const uint32_t kFabricatedOverlayCurrentVersion = 3;
 
 // Returns whether or not the path represents a fabricated overlay.
 bool IsFabricatedOverlay(const std::string& path);
+bool IsFabricatedOverlay(const char* path);
+bool IsFabricatedOverlay(android::base::borrowed_fd fd);
 
 /**
  * In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
@@ -1437,6 +1441,10 @@
         // Mark any types that use this with a v26 qualifier to prevent runtime issues on older
         // platforms.
         FLAG_SPARSE = 0x01,
+
+        // If set, the offsets to the entries are encoded in 16-bit, real_offset = offset * 4u
+        // An 16-bit offset of 0xffffu means a NO_ENTRY
+        FLAG_OFFSET16 = 0x02,
     };
     uint8_t flags;
 
@@ -1453,6 +1461,11 @@
     ResTable_config config;
 };
 
+// Convert a 16-bit offset to 32-bit if FLAG_OFFSET16 is set
+static inline uint32_t offset_from16(uint16_t off16) {
+    return dtohs(off16) == 0xffffu ? ResTable_type::NO_ENTRY : dtohs(off16) * 4u;
+}
+
 // The minimum size required to read any version of ResTable_type.
 constexpr size_t kResTableTypeMinSize =
     sizeof(ResTable_type) - sizeof(ResTable_config) + sizeof(ResTable_config::size);
@@ -1480,6 +1493,8 @@
 static_assert(sizeof(ResTable_sparseTypeEntry) == sizeof(uint32_t),
         "ResTable_sparseTypeEntry must be 4 bytes in size");
 
+struct ResTable_map_entry;
+
 /**
  * This is the beginning of information about an entry in the resource
  * table.  It holds the reference to the name of this entry, and is
@@ -1487,12 +1502,11 @@
  *   * A Res_value structure, if FLAG_COMPLEX is -not- set.
  *   * An array of ResTable_map structures, if FLAG_COMPLEX is set.
  *     These supply a set of name/value mappings of data.
+ *   * If FLAG_COMPACT is set, this entry is a compact entry for
+ *     simple values only
  */
-struct ResTable_entry
+union ResTable_entry
 {
-    // Number of bytes in this structure.
-    uint16_t size;
-
     enum {
         // If set, this is a complex entry, holding a set of name/value
         // mappings.  It is followed by an array of ResTable_map structures.
@@ -1504,18 +1518,91 @@
         // resources of the same name/type. This is only useful during
         // linking with other resource tables.
         FLAG_WEAK = 0x0004,
+        // If set, this is a compact entry with data type and value directly
+        // encoded in the this entry, see ResTable_entry::compact
+        FLAG_COMPACT = 0x0008,
     };
-    uint16_t flags;
-    
-    // Reference into ResTable_package::keyStrings identifying this entry.
-    struct ResStringPool_ref key;
+
+    struct Full {
+        // Number of bytes in this structure.
+        uint16_t size;
+
+        uint16_t flags;
+
+        // Reference into ResTable_package::keyStrings identifying this entry.
+        struct ResStringPool_ref key;
+    } full;
+
+    /* A compact entry is indicated by FLAG_COMPACT, with flags at the same
+     * offset as a normal entry. This is only for simple data values where
+     *
+     * - size for entry or value can be inferred (both being 8 bytes).
+     * - key index is encoded in 16-bit
+     * - dataType is encoded as the higher 8-bit of flags
+     * - data is encoded directly in this entry
+     */
+    struct Compact {
+        uint16_t key;
+        uint16_t flags;
+        uint32_t data;
+    } compact;
+
+    uint16_t flags()  const { return dtohs(full.flags); };
+    bool is_compact() const { return flags() & FLAG_COMPACT; }
+    bool is_complex() const { return flags() & FLAG_COMPLEX; }
+
+    size_t size() const {
+        return is_compact() ? sizeof(ResTable_entry) : dtohs(this->full.size);
+    }
+
+    uint32_t key() const {
+        return is_compact() ? dtohs(this->compact.key) : dtohl(this->full.key.index);
+    }
+
+    /* Always verify the memory associated with this entry and its value
+     * before calling value() or map_entry()
+     */
+    Res_value value() const {
+        Res_value v;
+        if (is_compact()) {
+            v.size = sizeof(Res_value);
+            v.res0 = 0;
+            v.data = dtohl(this->compact.data);
+            v.dataType = dtohs(compact.flags) >> 8;
+        } else {
+            auto vaddr = reinterpret_cast<const uint8_t*>(this) + dtohs(this->full.size);
+            auto value = reinterpret_cast<const Res_value*>(vaddr);
+            v.size = dtohs(value->size);
+            v.res0 = value->res0;
+            v.data = dtohl(value->data);
+            v.dataType = value->dataType;
+        }
+        return v;
+    }
+
+    const ResTable_map_entry* map_entry() const {
+        return is_complex() && !is_compact() ?
+            reinterpret_cast<const ResTable_map_entry*>(this) : nullptr;
+    }
 };
 
+/* Make sure size of ResTable_entry::Full and ResTable_entry::Compact
+ * be the same as ResTable_entry. This is to allow iteration of entries
+ * to work in either cases.
+ *
+ * The offset of flags must be at the same place for both structures,
+ * to ensure the correct reading to decide whether this is a full entry
+ * or a compact entry.
+ */
+static_assert(sizeof(ResTable_entry) == sizeof(ResTable_entry::Full));
+static_assert(sizeof(ResTable_entry) == sizeof(ResTable_entry::Compact));
+static_assert(offsetof(ResTable_entry, full.flags) == offsetof(ResTable_entry, compact.flags));
+
 /**
  * Extended form of a ResTable_entry for map entries, defining a parent map
  * resource from which to inherit values.
  */
-struct ResTable_map_entry : public ResTable_entry
+struct ResTable_map_entry : public ResTable_entry::Full
 {
     // Resource identifier of the parent mapping, or 0 if there is none.
     // This is always treated as a TYPE_DYNAMIC_REFERENCE.
@@ -1746,6 +1833,28 @@
   return first;
 }
 
+using ResourceId = uint32_t;  // 0xpptteeee
+
+using DataType = uint8_t;    // Res_value::dataType
+using DataValue = uint32_t;  // Res_value::data
+
+struct OverlayManifestInfo {
+  std::string package_name;     // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string name;             // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string target_package;   // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string target_name;      // NOLINT(misc-non-private-member-variables-in-classes)
+  ResourceId resource_mapping;  // NOLINT(misc-non-private-member-variables-in-classes)
+};
+
+struct FabricatedOverlayEntryParameters {
+  std::string resource_name;
+  DataType data_type;
+  DataValue data_value;
+  std::string data_string_value;
+  std::optional<android::base::borrowed_fd> data_binary_value;
+  std::string configuration;
+};
+
 class AssetManager2;
 
 /**
@@ -1776,7 +1885,10 @@
 
     void addMapping(uint8_t buildPackageId, uint8_t runtimePackageId);
 
-    void addAlias(uint32_t stagedId, uint32_t finalizedId);
+    using AliasMap = std::vector<std::pair<uint32_t, uint32_t>>;
+    void setAliases(AliasMap aliases) {
+        mAliasId = std::move(aliases);
+    }
 
     // Returns whether or not the value must be looked up.
     bool requiresLookup(const Res_value* value) const;
@@ -1790,12 +1902,12 @@
         return mEntries;
     }
 
-private:
-    uint8_t                         mAssignedPackageId;
-    uint8_t                         mLookupTable[256];
-    KeyedVector<String16, uint8_t>  mEntries;
-    bool                            mAppAsLib;
-    std::map<uint32_t, uint32_t>    mAliasId;
+   private:
+    uint8_t mLookupTable[256];
+    uint8_t mAssignedPackageId;
+    bool mAppAsLib;
+    KeyedVector<String16, uint8_t> mEntries;
+    AliasMap mAliasId;
 };
 
 bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue);
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
index bd1c440..2d90a52 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -25,14 +25,14 @@
 // Extracts the package, type, and name from a string of the format: [[package:]type/]name
 // Validation must be performed on each extracted piece.
 // Returns false if there was a syntax error.
-bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
+bool ExtractResourceName(StringPiece str, StringPiece* out_package, StringPiece* out_type,
                          StringPiece* out_entry);
 
 // Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName.
 // Useful for getting resource name without re-running AssetManager2::FindEntry searches.
 base::expected<AssetManager2::ResourceName, NullOrIOError> ToResourceName(
     const StringPoolRef& type_string_ref, const StringPoolRef& entry_string_ref,
-    const StringPiece& package_name);
+    StringPiece package_name);
 
 // Formats a ResourceName to "package:type/entry_name".
 std::string ToFormattedResourceString(const AssetManager2::ResourceName& resource_name);
diff --git a/libs/androidfw/include/androidfw/Source.h b/libs/androidfw/include/androidfw/Source.h
index 0421a91..ddc9ba4 100644
--- a/libs/androidfw/include/androidfw/Source.h
+++ b/libs/androidfw/include/androidfw/Source.h
@@ -34,15 +34,14 @@
 
   Source() = default;
 
-  inline Source(const android::StringPiece& path) : path(path.to_string()) {  // NOLINT(implicit)
+  inline Source(android::StringPiece path) : path(path) {  // NOLINT(implicit)
   }
 
-  inline Source(const android::StringPiece& path, const android::StringPiece& archive)
-      : path(path.to_string()), archive(archive.to_string()) {
+  inline Source(android::StringPiece path, android::StringPiece archive)
+      : path(path), archive(archive) {
   }
 
-  inline Source(const android::StringPiece& path, size_t line)
-      : path(path.to_string()), line(line) {
+  inline Source(android::StringPiece path, size_t line) : path(path), line(line) {
   }
 
   inline Source WithLine(size_t line) const {
diff --git a/libs/androidfw/include/androidfw/StringPiece.h b/libs/androidfw/include/androidfw/StringPiece.h
index fac2fa4..f6cc64e 100644
--- a/libs/androidfw/include/androidfw/StringPiece.h
+++ b/libs/androidfw/include/androidfw/StringPiece.h
@@ -19,307 +19,37 @@
 
 #include <ostream>
 #include <string>
+#include <string_view>
 
-#include "utils/JenkinsHash.h"
 #include "utils/Unicode.h"
 
 namespace android {
 
-// Read only wrapper around basic C strings. Prevents excessive copying.
-// StringPiece does not own the data it is wrapping. The lifetime of the underlying
-// data must outlive this StringPiece.
-//
-// WARNING: When creating from std::basic_string<>, moving the original
-// std::basic_string<> will invalidate the data held in a BasicStringPiece<>.
-// BasicStringPiece<> should only be used transitively.
-//
-// NOTE: When creating an std::pair<StringPiece, T> using std::make_pair(),
-// passing an std::string will first copy the string, then create a StringPiece
-// on the copy, which is then immediately destroyed.
-// Instead, create a StringPiece explicitly:
-//
-// std::string my_string = "foo";
-// std::make_pair<StringPiece, T>(StringPiece(my_string), ...);
-template <typename TChar>
-class BasicStringPiece {
- public:
-  using const_iterator = const TChar*;
-  using difference_type = size_t;
-  using size_type = size_t;
-
-  // End of string marker.
-  constexpr static const size_t npos = static_cast<size_t>(-1);
-
-  BasicStringPiece();
-  BasicStringPiece(const BasicStringPiece<TChar>& str);
-  BasicStringPiece(const std::basic_string<TChar>& str);  // NOLINT(google-explicit-constructor)
-  BasicStringPiece(const TChar* str);                     // NOLINT(google-explicit-constructor)
-  BasicStringPiece(const TChar* str, size_t len);
-
-  BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs);
-  BasicStringPiece<TChar>& assign(const TChar* str, size_t len);
-
-  BasicStringPiece<TChar> substr(size_t start, size_t len = npos) const;
-  BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin,
-                                 BasicStringPiece<TChar>::const_iterator end) const;
-
-  const TChar* data() const;
-  size_t length() const;
-  size_t size() const;
-  bool empty() const;
-  std::basic_string<TChar> to_string() const;
-
-  bool contains(const BasicStringPiece<TChar>& rhs) const;
-  int compare(const BasicStringPiece<TChar>& rhs) const;
-  bool operator<(const BasicStringPiece<TChar>& rhs) const;
-  bool operator>(const BasicStringPiece<TChar>& rhs) const;
-  bool operator==(const BasicStringPiece<TChar>& rhs) const;
-  bool operator!=(const BasicStringPiece<TChar>& rhs) const;
-
-  const_iterator begin() const;
-  const_iterator end() const;
-
- private:
-  const TChar* data_;
-  size_t length_;
-};
+template <class T>
+using BasicStringPiece = std::basic_string_view<T>;
 
 using StringPiece = BasicStringPiece<char>;
 using StringPiece16 = BasicStringPiece<char16_t>;
 
-//
-// BasicStringPiece implementation.
-//
-
-template <typename TChar>
-constexpr const size_t BasicStringPiece<TChar>::npos;
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece() : data_(nullptr), length_(0) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str)
-    : data_(str.data_), length_(str.length_) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str)
-    : data_(str.data()), length_(str.length()) {}
-
-template <>
-inline BasicStringPiece<char>::BasicStringPiece(const char* str)
-    : data_(str), length_(str != nullptr ? strlen(str) : 0) {}
-
-template <>
-inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str)
-    : data_(str), length_(str != nullptr ? strlen16(str) : 0) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len)
-    : data_(str), length_(len) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::operator=(
-    const BasicStringPiece<TChar>& rhs) {
-  data_ = rhs.data_;
-  length_ = rhs.length_;
-  return *this;
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) {
-  data_ = str;
-  length_ = len;
-  return *this;
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(size_t start, size_t len) const {
-  if (len == npos) {
-    len = length_ - start;
-  }
-
-  if (start > length_ || start + len > length_) {
-    return BasicStringPiece<TChar>();
-  }
-  return BasicStringPiece<TChar>(data_ + start, len);
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(
-    BasicStringPiece<TChar>::const_iterator begin,
-    BasicStringPiece<TChar>::const_iterator end) const {
-  return BasicStringPiece<TChar>(begin, end - begin);
-}
-
-template <typename TChar>
-inline const TChar* BasicStringPiece<TChar>::data() const {
-  return data_;
-}
-
-template <typename TChar>
-inline size_t BasicStringPiece<TChar>::length() const {
-  return length_;
-}
-
-template <typename TChar>
-inline size_t BasicStringPiece<TChar>::size() const {
-  return length_;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::empty() const {
-  return length_ == 0;
-}
-
-template <typename TChar>
-inline std::basic_string<TChar> BasicStringPiece<TChar>::to_string() const {
-  return std::basic_string<TChar>(data_, length_);
-}
-
-template <>
-inline bool BasicStringPiece<char>::contains(const BasicStringPiece<char>& rhs) const {
-  if (!data_ || !rhs.data_) {
-    return false;
-  }
-  if (rhs.length_ > length_) {
-    return false;
-  }
-  return strstr(data_, rhs.data_) != nullptr;
-}
-
-template <>
-inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const {
-  const char nullStr = '\0';
-  const char* b1 = data_ != nullptr ? data_ : &nullStr;
-  const char* e1 = b1 + length_;
-  const char* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
-  const char* e2 = b2 + rhs.length_;
-
-  while (b1 < e1 && b2 < e2) {
-    const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++);
-    if (d) {
-      return d;
-    }
-  }
-  return static_cast<int>(length_ - rhs.length_);
-}
-
-inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char16_t>& str) {
-  const ssize_t result_len = utf16_to_utf8_length(str.data(), str.size());
-  if (result_len < 0) {
-    // Empty string.
-    return out;
-  }
-
-  std::string result;
-  result.resize(static_cast<size_t>(result_len));
-  utf16_to_utf8(str.data(), str.length(), &*result.begin(), static_cast<size_t>(result_len) + 1);
-  return out << result;
-}
-
-template <>
-inline bool BasicStringPiece<char16_t>::contains(const BasicStringPiece<char16_t>& rhs) const {
-  if (!data_ || !rhs.data_) {
-    return false;
-  }
-  if (rhs.length_ > length_) {
-    return false;
-  }
-  return strstr16(data_, rhs.data_) != nullptr;
-}
-
-template <>
-inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const {
-  const char16_t nullStr = u'\0';
-  const char16_t* b1 = data_ != nullptr ? data_ : &nullStr;
-  const char16_t* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
-  return strzcmp16(b1, length_, b2, rhs.length_);
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator<(const BasicStringPiece<TChar>& rhs) const {
-  return compare(rhs) < 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator>(const BasicStringPiece<TChar>& rhs) const {
-  return compare(rhs) > 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator==(const BasicStringPiece<TChar>& rhs) const {
-  return compare(rhs) == 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator!=(const BasicStringPiece<TChar>& rhs) const {
-  return compare(rhs) != 0;
-}
-
-template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const {
-  return data_;
-}
-
-template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const {
-  return data_ + length_;
-}
-
-template <typename TChar>
-inline bool operator==(const TChar* lhs, const BasicStringPiece<TChar>& rhs) {
-  return BasicStringPiece<TChar>(lhs) == rhs;
-}
-
-template <typename TChar>
-inline bool operator!=(const TChar* lhs, const BasicStringPiece<TChar>& rhs) {
-  return BasicStringPiece<TChar>(lhs) != rhs;
-}
-
-inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) {
-  return out.write(str.data(), str.size());
-}
-
-template <typename TChar>
-inline ::std::basic_string<TChar>& operator+=(::std::basic_string<TChar>& lhs,
-                                              const BasicStringPiece<TChar>& rhs) {
-  return lhs.append(rhs.data(), rhs.size());
-}
-
-template <typename TChar>
-inline bool operator==(const ::std::basic_string<TChar>& lhs, const BasicStringPiece<TChar>& rhs) {
-  return BasicStringPiece<TChar>(lhs) == rhs;
-}
-
-template <typename TChar>
-inline bool operator!=(const ::std::basic_string<TChar>& lhs, const BasicStringPiece<TChar>& rhs) {
-  return BasicStringPiece<TChar>(lhs) != rhs;
-}
-
 }  // namespace android
 
-inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) {
+namespace std {
+
+inline ::std::ostream& operator<<(::std::ostream& out, ::std::u16string_view str) {
   ssize_t utf8_len = utf16_to_utf8_length(str.data(), str.size());
   if (utf8_len < 0) {
-    return out << "???";
+    return out;  // empty
   }
 
   std::string utf8;
   utf8.resize(static_cast<size_t>(utf8_len));
-  utf16_to_utf8(str.data(), str.size(), &*utf8.begin(), utf8_len + 1);
+  utf16_to_utf8(str.data(), str.size(), utf8.data(), utf8_len + 1);
   return out << utf8;
 }
 
-namespace std {
-
-template <typename TChar>
-struct hash<android::BasicStringPiece<TChar>> {
-  size_t operator()(const android::BasicStringPiece<TChar>& str) const {
-    uint32_t hashCode = android::JenkinsHashMixBytes(
-        0, reinterpret_cast<const uint8_t*>(str.data()), sizeof(TChar) * str.size());
-    return static_cast<size_t>(hashCode);
-  }
-};
+inline ::std::ostream& operator<<(::std::ostream& out, const ::std::u16string& str) {
+  return out << std::u16string_view(str);
+}
 
 }  // namespace std
 
diff --git a/libs/androidfw/include/androidfw/StringPool.h b/libs/androidfw/include/androidfw/StringPool.h
index 25174d8..0190ab5 100644
--- a/libs/androidfw/include/androidfw/StringPool.h
+++ b/libs/androidfw/include/androidfw/StringPool.h
@@ -167,11 +167,11 @@
 
   // Adds a string to the pool, unless it already exists. Returns a reference to the string in the
   // pool.
-  Ref MakeRef(const android::StringPiece& str);
+  Ref MakeRef(android::StringPiece str);
 
   // Adds a string to the pool, unless it already exists, with a context object that can be used
   // when sorting the string pool. Returns a reference to the string in the pool.
-  Ref MakeRef(const android::StringPiece& str, const Context& context);
+  Ref MakeRef(android::StringPiece str, const Context& context);
 
   // Adds a string from another string pool. Returns a reference to the string in the string pool.
   Ref MakeRef(const Ref& ref);
@@ -215,7 +215,7 @@
 
   static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag);
 
-  Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique);
+  Ref MakeRefImpl(android::StringPiece str, const Context& context, bool unique);
   void ReAssignIndices();
 
   std::vector<std::unique_ptr<Entry>> strings_;
diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h
index 1bbc7f5..a188abb 100644
--- a/libs/androidfw/include/androidfw/Util.h
+++ b/libs/androidfw/include/androidfw/Util.h
@@ -22,7 +22,6 @@
 
 #include <cstdlib>
 #include <memory>
-#include <sstream>
 #include <vector>
 
 #include "androidfw/BigBuffer.h"
@@ -33,7 +32,14 @@
 #ifdef __ANDROID__
 #define ANDROID_LOG(x) LOG(x)
 #else
-#define ANDROID_LOG(x) std::stringstream()
+namespace android {
+// No default logging for aapt2, as it's too noisy for a command line dev tool.
+struct NullLogger {
+  template <class T>
+  friend const NullLogger& operator<<(const NullLogger& l, const T&) { return l; }
+};
+}
+#define ANDROID_LOG(x) (android::NullLogger{})
 #endif
 
 namespace android {
@@ -49,90 +55,28 @@
   return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
 }
 
-// Based on std::unique_ptr, but uses free() to release malloc'ed memory
-// without incurring the size increase of holding on to a custom deleter.
-template <typename T>
-class unique_cptr {
- public:
-  using pointer = typename std::add_pointer<T>::type;
-
-  constexpr unique_cptr() : ptr_(nullptr) {}
-  constexpr explicit unique_cptr(std::nullptr_t) : ptr_(nullptr) {}
-  explicit unique_cptr(pointer ptr) : ptr_(ptr) {}
-  unique_cptr(unique_cptr&& o) noexcept : ptr_(o.ptr_) { o.ptr_ = nullptr; }
-
-  ~unique_cptr() { std::free(reinterpret_cast<void*>(ptr_)); }
-
-  inline unique_cptr& operator=(unique_cptr&& o) noexcept {
-    if (&o == this) {
-      return *this;
-    }
-
-    std::free(reinterpret_cast<void*>(ptr_));
-    ptr_ = o.ptr_;
-    o.ptr_ = nullptr;
-    return *this;
+// Based on std::unique_ptr, but uses free() to release malloc'ed memory.
+struct FreeDeleter {
+  void operator()(void* ptr) const {
+    ::free(ptr);
   }
-
-  inline unique_cptr& operator=(std::nullptr_t) {
-    std::free(reinterpret_cast<void*>(ptr_));
-    ptr_ = nullptr;
-    return *this;
-  }
-
-  pointer release() {
-    pointer result = ptr_;
-    ptr_ = nullptr;
-    return result;
-  }
-
-  inline pointer get() const { return ptr_; }
-
-  void reset(pointer ptr = pointer()) {
-    if (ptr == ptr_) {
-      return;
-    }
-
-    pointer old_ptr = ptr_;
-    ptr_ = ptr;
-    std::free(reinterpret_cast<void*>(old_ptr));
-  }
-
-  inline void swap(unique_cptr& o) { std::swap(ptr_, o.ptr_); }
-
-  inline explicit operator bool() const { return ptr_ != nullptr; }
-
-  inline typename std::add_lvalue_reference<T>::type operator*() const { return *ptr_; }
-
-  inline pointer operator->() const { return ptr_; }
-
-  inline bool operator==(const unique_cptr& o) const { return ptr_ == o.ptr_; }
-
-  inline bool operator!=(const unique_cptr& o) const { return ptr_ != o.ptr_; }
-
-  inline bool operator==(std::nullptr_t) const { return ptr_ == nullptr; }
-
-  inline bool operator!=(std::nullptr_t) const { return ptr_ != nullptr; }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(unique_cptr);
-
-  pointer ptr_;
 };
+template <typename T>
+using unique_cptr = std::unique_ptr<T, FreeDeleter>;
 
 void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out);
 
 // Converts a UTF-8 string to a UTF-16 string.
-std::u16string Utf8ToUtf16(const StringPiece& utf8);
+std::u16string Utf8ToUtf16(StringPiece utf8);
 
 // Converts a UTF-16 string to a UTF-8 string.
-std::string Utf16ToUtf8(const StringPiece16& utf16);
+std::string Utf16ToUtf8(StringPiece16 utf16);
 
 // Converts a UTF8 string into Modified UTF8
-std::string Utf8ToModifiedUtf8(const std::string& utf8);
+std::string Utf8ToModifiedUtf8(std::string_view utf8);
 
 // Converts a Modified UTF8 string into a UTF8 string
-std::string ModifiedUtf8ToUtf8(const std::string& modified_utf8);
+std::string ModifiedUtf8ToUtf8(std::string_view modified_utf8);
 
 inline uint16_t HostToDevice16(uint16_t value) {
   return htods(value);
@@ -150,15 +94,15 @@
   return dtohl(value);
 }
 
-std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);
+std::vector<std::string> SplitAndLowercase(android::StringPiece str, char sep);
+
+inline bool IsFourByteAligned(const void* data) {
+  return ((uintptr_t)data & 0x3U) == 0;
+}
 
 template <typename T>
 inline bool IsFourByteAligned(const incfs::map_ptr<T>& data) {
-  return ((size_t)data.unsafe_ptr() & 0x3U) == 0;
-}
-
-inline bool IsFourByteAligned(const void* data) {
-  return ((size_t)data & 0x3U) == 0;
+  return IsFourByteAligned(data.unsafe_ptr());
 }
 
 // Helper method to extract a UTF-16 string from a StringPool. If the string is stored as UTF-8,
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index 5a5a0e2..d40d24e 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -44,6 +44,10 @@
 /* get the file's modification date; returns -1 w/errno set on failure */
 time_t getFileModDate(const char* fileName);
 
+// Check if |path| or |fd| resides on a readonly filesystem.
+bool isReadonlyFilesystem(const char* path);
+bool isReadonlyFilesystem(int fd);
+
 }; // namespace android
 
 #endif // _LIBS_ANDROID_FW_MISC_H
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index 5285420..d3949e9 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -21,12 +21,17 @@
 //
 #include <androidfw/misc.h>
 
-#include <sys/stat.h>
-#include <cstring>
-#include <errno.h>
-#include <cstdio>
+#include "android-base/logging.h"
 
-using namespace android;
+#ifdef __linux__
+#include <sys/statvfs.h>
+#include <sys/vfs.h>
+#endif  // __linux__
+
+#include <cstring>
+#include <cstdio>
+#include <errno.h>
+#include <sys/stat.h>
 
 namespace android {
 
@@ -41,8 +46,7 @@
         if (errno == ENOENT || errno == ENOTDIR)
             return kFileTypeNonexistent;
         else {
-            fprintf(stderr, "getFileType got errno=%d on '%s'\n",
-                errno, fileName);
+            PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
             return kFileTypeUnknown;
         }
     } else {
@@ -82,4 +86,32 @@
     return sb.st_mtime;
 }
 
+#ifndef __linux__
+// No need to implement these on the host, the functions only matter on a device.
+bool isReadonlyFilesystem(const char*) {
+    return false;
+}
+bool isReadonlyFilesystem(int) {
+    return false;
+}
+#else   // __linux__
+bool isReadonlyFilesystem(const char* path) {
+    struct statfs sfs;
+    if (::statfs(path, &sfs)) {
+        PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed";
+        return false;
+    }
+    return (sfs.f_flags & ST_RDONLY) != 0;
+}
+
+bool isReadonlyFilesystem(int fd) {
+    struct statfs sfs;
+    if (::fstatfs(fd, &sfs)) {
+        PLOG(ERROR) << "isReadonlyFilesystem(): fstatfs(" << fd << ") failed";
+        return false;
+    }
+    return (sfs.f_flags & ST_RDONLY) != 0;
+}
+#endif  // __linux__
+
 }; // namespace android
diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp
index ddd8ab8..1c89c61 100644
--- a/libs/androidfw/tests/AttributeResolution_bench.cpp
+++ b/libs/androidfw/tests/AttributeResolution_bench.cpp
@@ -120,8 +120,8 @@
     return;
   }
 
-  std::unique_ptr<Asset> asset = assetmanager.OpenNonAsset(layout_path->to_string(), value->cookie,
-                                                           Asset::ACCESS_BUFFER);
+  std::unique_ptr<Asset> asset =
+      assetmanager.OpenNonAsset(std::string(*layout_path), value->cookie, Asset::ACCESS_BUFFER);
   if (asset == nullptr) {
     state.SkipWithError("failed to load layout");
     return;
diff --git a/libs/androidfw/tests/ByteBucketArray_test.cpp b/libs/androidfw/tests/ByteBucketArray_test.cpp
index 5d464c7..9c36cfb 100644
--- a/libs/androidfw/tests/ByteBucketArray_test.cpp
+++ b/libs/androidfw/tests/ByteBucketArray_test.cpp
@@ -52,4 +52,57 @@
   }
 }
 
+TEST(ByteBucketArrayTest, TestForEach) {
+  ByteBucketArray<int> bba;
+  ASSERT_TRUE(bba.set(0, 1));
+  ASSERT_TRUE(bba.set(10, 2));
+  ASSERT_TRUE(bba.set(26, 3));
+  ASSERT_TRUE(bba.set(129, 4));
+  ASSERT_TRUE(bba.set(234, 5));
+
+  int count = 0;
+  bba.forEachItem([&count](auto i, auto val) {
+    ++count;
+    switch (i) {
+      case 0:
+        EXPECT_EQ(1, val);
+        break;
+      case 10:
+        EXPECT_EQ(2, val);
+        break;
+      case 26:
+        EXPECT_EQ(3, val);
+        break;
+      case 129:
+        EXPECT_EQ(4, val);
+        break;
+      case 234:
+        EXPECT_EQ(5, val);
+        break;
+      default:
+        EXPECT_EQ(0, val);
+        break;
+    }
+  });
+  ASSERT_EQ(4 * 16, count);
+}
+
+TEST(ByteBucketArrayTest, TestTrimBuckets) {
+  ByteBucketArray<int> bba;
+  ASSERT_TRUE(bba.set(0, 1));
+  ASSERT_TRUE(bba.set(255, 2));
+  {
+    bba.trimBuckets([](auto val) { return val < 2; });
+    int count = 0;
+    bba.forEachItem([&count](auto, auto) { ++count; });
+    ASSERT_EQ(1 * 16, count);
+  }
+  {
+    bba.trimBuckets([](auto val) { return val < 3; });
+    int count = 0;
+    bba.forEachItem([&count](auto, auto) { ++count; });
+    ASSERT_EQ(0, count);
+  }
+}
+
 }  // namespace android
diff --git a/libs/androidfw/tests/ConfigDescription_test.cpp b/libs/androidfw/tests/ConfigDescription_test.cpp
index ce7f805..8fed0a4 100644
--- a/libs/androidfw/tests/ConfigDescription_test.cpp
+++ b/libs/androidfw/tests/ConfigDescription_test.cpp
@@ -25,8 +25,8 @@
 
 namespace android {
 
-static ::testing::AssertionResult TestParse(
-    const StringPiece& input, ConfigDescription* config = nullptr) {
+static ::testing::AssertionResult TestParse(StringPiece input,
+                                            ConfigDescription* config = nullptr) {
   if (ConfigDescription::Parse(input, config)) {
     return ::testing::AssertionSuccess() << input << " was successfully parsed";
   }
@@ -138,7 +138,7 @@
   EXPECT_EQ(std::string("vrheadset-v26"), config.toString().string());
 }
 
-static inline ConfigDescription ParseConfigOrDie(const android::StringPiece& str) {
+static inline ConfigDescription ParseConfigOrDie(android::StringPiece str) {
   ConfigDescription config;
   CHECK(ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str;
   return config;
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/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index d214e2d..c90ec19 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -71,62 +71,6 @@
   ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
 }
 
-TEST(LoadedArscTest, LoadSparseEntryApp) {
-  std::string contents;
-  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
-                                      &contents));
-
-  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
-                                                                   contents.length());
-  ASSERT_THAT(loaded_arsc, NotNull());
-
-  const LoadedPackage* package =
-      loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9));
-  ASSERT_THAT(package, NotNull());
-
-  const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1;
-  const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9);
-
-  const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
-  ASSERT_THAT(type_spec, NotNull());
-  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
-
-  auto type = type_spec->type_entries[0];
-  ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
-}
-
-TEST(LoadedArscTest, FindSparseEntryApp) {
-  std::string contents;
-  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
-                                      &contents));
-
-  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
-                                                                   contents.length());
-  ASSERT_THAT(loaded_arsc, NotNull());
-
-  const LoadedPackage* package =
-      loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_v26));
-  ASSERT_THAT(package, NotNull());
-
-  const uint8_t type_index = get_type_id(sparse::R::string::only_v26) - 1;
-  const uint16_t entry_index = get_entry_id(sparse::R::string::only_v26);
-
-  const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
-  ASSERT_THAT(type_spec, NotNull());
-  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
-
-  // Ensure that AAPT2 sparsely encoded the v26 config as expected.
-  auto type_entry = std::find_if(
-    type_spec->type_entries.begin(), type_spec->type_entries.end(),
-    [](const TypeSpec::TypeEntry& x) { return x.config.sdkVersion == 26; });
-  ASSERT_NE(type_entry, type_spec->type_entries.end());
-  ASSERT_NE(type_entry->type->flags & ResTable_type::FLAG_SPARSE, 0);
-
-  // Test fetching a resource with only sparsely encoded configs by name.
-  auto id = package->FindEntryByName(u"string", u"only_v26");
-  ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_v26, 0));
-}
-
 TEST(LoadedArscTest, LoadSharedLibrary) {
   std::string contents;
   ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc",
@@ -404,4 +348,84 @@
 // sizeof(Res_value) might not be backwards compatible.
 // TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); }
 
+class LoadedArscParameterizedTest :
+    public testing::TestWithParam<std::string> {
+};
+
+TEST_P(LoadedArscParameterizedTest, LoadSparseEntryApp) {
+  std::string contents;
+  ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
+                                                                   contents.length());
+  ASSERT_THAT(loaded_arsc, NotNull());
+
+  const LoadedPackage* package =
+      loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9));
+  ASSERT_THAT(package, NotNull());
+
+  const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1;
+  const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9);
+
+  const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+  ASSERT_THAT(type_spec, NotNull());
+  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
+
+  auto type = type_spec->type_entries[0];
+  ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
+}
+
+TEST_P(LoadedArscParameterizedTest, FindSparseEntryApp) {
+  std::string contents;
+  ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
+                                                                   contents.length());
+  ASSERT_THAT(loaded_arsc, NotNull());
+
+  const LoadedPackage* package =
+      loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_land));
+  ASSERT_THAT(package, NotNull());
+
+  const uint8_t type_index = get_type_id(sparse::R::string::only_land) - 1;
+
+  const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+  ASSERT_THAT(type_spec, NotNull());
+  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
+
+  // Type Entry with default orientation is not sparse encoded because the ratio of
+  // populated entries to total entries is above threshold.
+  // Only find out default locale because Soong build system will introduce pseudo
+  // locales for the apk generated at runtime.
+  auto type_entry_default = std::find_if(
+    type_spec->type_entries.begin(), type_spec->type_entries.end(),
+    [] (const TypeSpec::TypeEntry& x) { return x.config.orientation == 0 &&
+                                               x.config.locale == 0; });
+  ASSERT_NE(type_entry_default, type_spec->type_entries.end());
+  ASSERT_EQ(type_entry_default->type->flags & ResTable_type::FLAG_SPARSE, 0);
+
+  // Type Entry with land orientation is sparse encoded as expected.
+  // Only find out default locale because Soong build system will introduce pseudo
+  // locales for the apk generated at runtime.
+  auto type_entry_land = std::find_if(
+    type_spec->type_entries.begin(), type_spec->type_entries.end(),
+    [](const TypeSpec::TypeEntry& x) { return x.config.orientation ==
+                                              ResTable_config::ORIENTATION_LAND &&
+                                              x.config.locale == 0; });
+  ASSERT_NE(type_entry_land, type_spec->type_entries.end());
+  ASSERT_NE(type_entry_land->type->flags & ResTable_type::FLAG_SPARSE, 0);
+
+  // Test fetching a resource with only sparsely encoded configs by name.
+  auto id = package->FindEntryByName(u"string", u"only_land");
+  ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_land, 0));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        FrameWorkResourcesLoadedArscTests,
+        LoadedArscParameterizedTest,
+        ::testing::Values(
+          base::GetExecutableDirectory() + "/tests/data/sparse/sparse.apk",
+          base::GetExecutableDirectory() + "/FrameworkResourcesSparseTestApp.apk"
+        ));
+
 }  // namespace android
diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp
index 9aeb00c..fbf7098 100644
--- a/libs/androidfw/tests/ResTable_test.cpp
+++ b/libs/androidfw/tests/ResTable_test.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "androidfw/ResourceTypes.h"
+#include "android-base/file.h"
 
 #include <codecvt>
 #include <locale>
@@ -41,34 +42,6 @@
   ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
 }
 
-TEST(ResTableTest, ShouldLoadSparseEntriesSuccessfully) {
-  std::string contents;
-  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
-                                      &contents));
-
-  ResTable table;
-  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
-
-  ResTable_config config;
-  memset(&config, 0, sizeof(config));
-  config.sdkVersion = 26;
-  table.setParameters(&config);
-
-  String16 name(u"com.android.sparse:integer/foo_9");
-  uint32_t flags;
-  uint32_t resid =
-      table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags);
-  ASSERT_NE(0u, resid);
-
-  Res_value val;
-  ResTable_config selected_config;
-  ASSERT_GE(
-      table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config),
-      0);
-  EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
-  EXPECT_EQ(900u, val.data);
-}
-
 TEST(ResTableTest, SimpleTypeIsRetrievedCorrectly) {
   std::string contents;
   ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
@@ -476,4 +449,43 @@
   ASSERT_FALSE(invalid_pool->stringAt(invalid_val.data).has_value());
 }
 
+class ResTableParameterizedTest :
+    public testing::TestWithParam<std::string> {
+};
+
+TEST_P(ResTableParameterizedTest, ShouldLoadSparseEntriesSuccessfully) {
+  std::string contents;
+  ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+  ResTable table;
+  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
+
+  ResTable_config config;
+  memset(&config, 0, sizeof(config));
+  config.orientation = ResTable_config::ORIENTATION_LAND;
+  table.setParameters(&config);
+
+  String16 name(u"com.android.sparse:integer/foo_9");
+  uint32_t flags;
+  uint32_t resid =
+      table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags);
+  ASSERT_NE(0u, resid);
+
+  Res_value val;
+  ResTable_config selected_config;
+  ASSERT_GE(
+      table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config),
+      0);
+  EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+  EXPECT_EQ(900u, val.data);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        FrameWorkResourcesResTableTests,
+        ResTableParameterizedTest,
+        ::testing::Values(
+           base::GetExecutableDirectory() + "/tests/data/sparse/sparse.apk",
+           base::GetExecutableDirectory() + "/FrameworkResourcesSparseTestApp.apk"
+        ));
+
 }  // namespace android
diff --git a/libs/androidfw/tests/SparseEntry_bench.cpp b/libs/androidfw/tests/SparseEntry_bench.cpp
index c9b4ad8..fffeeb8 100644
--- a/libs/androidfw/tests/SparseEntry_bench.cpp
+++ b/libs/androidfw/tests/SparseEntry_bench.cpp
@@ -16,6 +16,7 @@
 
 #include "androidfw/AssetManager.h"
 #include "androidfw/ResourceTypes.h"
+#include "android-base/file.h"
 
 #include "BenchmarkHelpers.h"
 #include "data/sparse/R.h"
@@ -24,40 +25,74 @@
 
 namespace android {
 
+static void BM_SparseEntryGetResourceHelper(const std::vector<std::string>& paths,
+                    uint32_t resid, benchmark::State& state, void (*GetResourceBenchmarkFunc)(
+                    const std::vector<std::string>&, const ResTable_config*,
+                    uint32_t, benchmark::State&)){
+    ResTable_config config;
+    memset(&config, 0, sizeof(config));
+    config.orientation = ResTable_config::ORIENTATION_LAND;
+    GetResourceBenchmarkFunc(paths, &config, resid, state);
+}
+
 static void BM_SparseEntryGetResourceOldSparse(benchmark::State& state, uint32_t resid) {
-  ResTable_config config;
-  memset(&config, 0, sizeof(config));
-  config.sdkVersion = 26;
-  GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state);
+  BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/sparse.apk"}, resid,
+                                    state, &GetResourceBenchmarkOld);
 }
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Small, sparse::R::integer::foo_9);
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Large, sparse::R::string::foo_999);
 
 static void BM_SparseEntryGetResourceOldNotSparse(benchmark::State& state, uint32_t resid) {
-  ResTable_config config;
-  memset(&config, 0, sizeof(config));
-  config.sdkVersion = 26;
-  GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state);
+   BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/not_sparse.apk"}, resid,
+                                   state, &GetResourceBenchmarkOld);
 }
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Small, sparse::R::integer::foo_9);
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Large, sparse::R::string::foo_999);
 
 static void BM_SparseEntryGetResourceSparse(benchmark::State& state, uint32_t resid) {
-  ResTable_config config;
-  memset(&config, 0, sizeof(config));
-  config.sdkVersion = 26;
-  GetResourceBenchmark({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state);
+  BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/sparse.apk"}, resid,
+                                  state, &GetResourceBenchmark);
 }
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Small, sparse::R::integer::foo_9);
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Large, sparse::R::string::foo_999);
 
 static void BM_SparseEntryGetResourceNotSparse(benchmark::State& state, uint32_t resid) {
-  ResTable_config config;
-  memset(&config, 0, sizeof(config));
-  config.sdkVersion = 26;
-  GetResourceBenchmark({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state);
+  BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/not_sparse.apk"}, resid,
+                                  state, &GetResourceBenchmark);
 }
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Small, sparse::R::integer::foo_9);
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Large, sparse::R::string::foo_999);
 
+static void BM_SparseEntryGetResourceOldSparseRuntime(benchmark::State& state, uint32_t resid) {
+  BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+                                  "/FrameworkResourcesSparseTestApp.apk"}, resid, state,
+                                  &GetResourceBenchmarkOld);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceOldNotSparseRuntime(benchmark::State& state, uint32_t resid) {
+  BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+                                  "/FrameworkResourcesNotSparseTestApp.apk"}, resid, state,
+                                  &GetResourceBenchmarkOld);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceSparseRuntime(benchmark::State& state, uint32_t resid) {
+  BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+                                  "/FrameworkResourcesSparseTestApp.apk"}, resid, state,
+                                  &GetResourceBenchmark);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceNotSparseRuntime(benchmark::State& state, uint32_t resid) {
+  BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+                                  "/FrameworkResourcesNotSparseTestApp.apk"}, resid, state,
+                                  &GetResourceBenchmark);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparseRuntime, Large, sparse::R::string::foo_999);
+
 }  // namespace android
diff --git a/libs/androidfw/tests/StringPiece_test.cpp b/libs/androidfw/tests/StringPiece_test.cpp
index 316a5c1..822e527 100644
--- a/libs/androidfw/tests/StringPiece_test.cpp
+++ b/libs/androidfw/tests/StringPiece_test.cpp
@@ -60,36 +60,4 @@
   EXPECT_TRUE(StringPiece(car) > banana);
 }
 
-TEST(StringPieceTest, ContainsOtherStringPiece) {
-  StringPiece text("I am a leaf on the wind.");
-  StringPiece start_needle("I am");
-  StringPiece end_needle("wind.");
-  StringPiece middle_needle("leaf");
-  StringPiece empty_needle("");
-  StringPiece missing_needle("soar");
-  StringPiece long_needle("This string is longer than the text.");
-
-  EXPECT_TRUE(text.contains(start_needle));
-  EXPECT_TRUE(text.contains(end_needle));
-  EXPECT_TRUE(text.contains(middle_needle));
-  EXPECT_TRUE(text.contains(empty_needle));
-  EXPECT_FALSE(text.contains(missing_needle));
-  EXPECT_FALSE(text.contains(long_needle));
-
-  StringPiece16 text16(u"I am a leaf on the wind.");
-  StringPiece16 start_needle16(u"I am");
-  StringPiece16 end_needle16(u"wind.");
-  StringPiece16 middle_needle16(u"leaf");
-  StringPiece16 empty_needle16(u"");
-  StringPiece16 missing_needle16(u"soar");
-  StringPiece16 long_needle16(u"This string is longer than the text.");
-
-  EXPECT_TRUE(text16.contains(start_needle16));
-  EXPECT_TRUE(text16.contains(end_needle16));
-  EXPECT_TRUE(text16.contains(middle_needle16));
-  EXPECT_TRUE(text16.contains(empty_needle16));
-  EXPECT_FALSE(text16.contains(missing_needle16));
-  EXPECT_FALSE(text16.contains(long_needle16));
-}
-
 }  // namespace android
diff --git a/libs/androidfw/tests/StringPool_test.cpp b/libs/androidfw/tests/StringPool_test.cpp
index 047d457..0e0acae 100644
--- a/libs/androidfw/tests/StringPool_test.cpp
+++ b/libs/androidfw/tests/StringPool_test.cpp
@@ -321,15 +321,15 @@
   ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
   auto str = test.string8At(0);
   ASSERT_TRUE(str.has_value());
-  EXPECT_THAT(str->to_string(), Eq("\xED\xA0\x81\xED\xB0\x80"));
+  EXPECT_THAT(*str, Eq("\xED\xA0\x81\xED\xB0\x80"));
 
   str = test.string8At(1);
   ASSERT_TRUE(str.has_value());
-  EXPECT_THAT(str->to_string(), Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar"));
+  EXPECT_THAT(*str, Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar"));
 
   str = test.string8At(2);
   ASSERT_TRUE(str.has_value());
-  EXPECT_THAT(str->to_string(), Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7"));
+  EXPECT_THAT(*str, Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7"));
 
   // Check that retrieving the strings returns the original UTF-8 character bytes
   EXPECT_THAT(android::util::GetString(test, 0), Eq("\xF0\x90\x90\x80"));
diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp
index d69abe5..ed30904 100644
--- a/libs/androidfw/tests/TypeWrappers_test.cpp
+++ b/libs/androidfw/tests/TypeWrappers_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <algorithm>
 #include <androidfw/ResourceTypes.h>
 #include <androidfw/TypeWrappers.h>
 #include <utils/String8.h>
@@ -22,88 +23,123 @@
 
 namespace android {
 
-void* createTypeData() {
-    ResTable_type t;
-    memset(&t, 0, sizeof(t));
+// create a ResTable_type in memory with a vector of Res_value*
+static ResTable_type* createTypeTable(std::vector<Res_value*>& values,
+                             bool compact_entry = false,
+                             bool short_offsets = false)
+{
+    ResTable_type t{};
     t.header.type = RES_TABLE_TYPE_TYPE;
     t.header.headerSize = sizeof(t);
+    t.header.size = sizeof(t);
     t.id = 1;
-    t.entryCount = 3;
+    t.flags = short_offsets ? ResTable_type::FLAG_OFFSET16 : 0;
 
-    uint32_t offsets[3];
-    t.entriesStart = t.header.headerSize + sizeof(offsets);
-    t.header.size = t.entriesStart;
+    t.header.size += values.size() * (short_offsets ? sizeof(uint16_t) : sizeof(uint32_t));
+    t.entriesStart = t.header.size;
+    t.entryCount = values.size();
 
-    offsets[0] = 0;
-    ResTable_entry e1;
-    memset(&e1, 0, sizeof(e1));
-    e1.size = sizeof(e1);
-    e1.key.index = 0;
-    t.header.size += sizeof(e1);
+    size_t entry_size = compact_entry ? sizeof(ResTable_entry)
+                                      : sizeof(ResTable_entry) + sizeof(Res_value);
+    for (auto const v : values) {
+        t.header.size += v ? entry_size : 0;
+    }
 
-    Res_value v1;
-    memset(&v1, 0, sizeof(v1));
-    t.header.size += sizeof(v1);
+    uint8_t* data = (uint8_t *)malloc(t.header.size);
+    uint8_t* p_header = data;
+    uint8_t* p_offsets = data + t.header.headerSize;
+    uint8_t* p_entries = data + t.entriesStart;
 
-    offsets[1] = ResTable_type::NO_ENTRY;
+    memcpy(p_header, &t, sizeof(t));
 
-    offsets[2] = sizeof(e1) + sizeof(v1);
-    ResTable_entry e2;
-    memset(&e2, 0, sizeof(e2));
-    e2.size = sizeof(e2);
-    e2.key.index = 1;
-    t.header.size += sizeof(e2);
+    size_t i = 0, entry_offset = 0;
+    uint32_t k = 0;
+    for (auto const& v : values) {
+        if (short_offsets) {
+            uint16_t *p = reinterpret_cast<uint16_t *>(p_offsets) + i;
+            *p = v ? (entry_offset >> 2) & 0xffffu : 0xffffu;
+        } else {
+            uint32_t *p = reinterpret_cast<uint32_t *>(p_offsets) + i;
+            *p = v ? entry_offset : ResTable_type::NO_ENTRY;
+        }
 
-    Res_value v2;
-    memset(&v2, 0, sizeof(v2));
-    t.header.size += sizeof(v2);
-
-    uint8_t* data = (uint8_t*)malloc(t.header.size);
-    uint8_t* p = data;
-    memcpy(p, &t, sizeof(t));
-    p += sizeof(t);
-    memcpy(p, offsets, sizeof(offsets));
-    p += sizeof(offsets);
-    memcpy(p, &e1, sizeof(e1));
-    p += sizeof(e1);
-    memcpy(p, &v1, sizeof(v1));
-    p += sizeof(v1);
-    memcpy(p, &e2, sizeof(e2));
-    p += sizeof(e2);
-    memcpy(p, &v2, sizeof(v2));
-    p += sizeof(v2);
-    return data;
+        if (v) {
+            ResTable_entry entry{};
+            if (compact_entry) {
+                entry.compact.key = i;
+                entry.compact.flags = ResTable_entry::FLAG_COMPACT | (v->dataType << 8);
+                entry.compact.data = v->data;
+                memcpy(p_entries, &entry, sizeof(entry)); p_entries += sizeof(entry);
+                entry_offset += sizeof(entry);
+            } else {
+                Res_value value{};
+                entry.full.size = sizeof(entry);
+                entry.full.key.index = i;
+                value = *v;
+                memcpy(p_entries, &entry, sizeof(entry)); p_entries += sizeof(entry);
+                memcpy(p_entries, &value, sizeof(value)); p_entries += sizeof(value);
+                entry_offset += sizeof(entry) + sizeof(value);
+            }
+        }
+        i++;
+    }
+    return reinterpret_cast<ResTable_type*>(data);
 }
 
 TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) {
-    ResTable_type* data = (ResTable_type*) createTypeData();
+    std::vector<Res_value *> values;
 
-    TypeVariant v(data);
+    Res_value *v1 = new Res_value{};
+    values.push_back(v1);
 
-    TypeVariant::iterator iter = v.beginEntries();
-    ASSERT_EQ(uint32_t(0), iter.index());
-    ASSERT_TRUE(NULL != *iter);
-    ASSERT_EQ(uint32_t(0), iter->key.index);
-    ASSERT_NE(v.endEntries(), iter);
+    values.push_back(nullptr);
 
-    iter++;
+    Res_value *v2 = new Res_value{};
+    values.push_back(v2);
 
-    ASSERT_EQ(uint32_t(1), iter.index());
-    ASSERT_TRUE(NULL == *iter);
-    ASSERT_NE(v.endEntries(), iter);
+    Res_value *v3 = new Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x12345678};
+    values.push_back(v3);
 
-    iter++;
+    // test for combinations of compact_entry and short_offsets
+    for (size_t i = 0; i < 4; i++) {
+        bool compact_entry = i & 0x1, short_offsets = i & 0x2;
+        ResTable_type* data = createTypeTable(values, compact_entry, short_offsets);
+        TypeVariant v(data);
 
-    ASSERT_EQ(uint32_t(2), iter.index());
-    ASSERT_TRUE(NULL != *iter);
-    ASSERT_EQ(uint32_t(1), iter->key.index);
-    ASSERT_NE(v.endEntries(), iter);
+        TypeVariant::iterator iter = v.beginEntries();
+        ASSERT_EQ(uint32_t(0), iter.index());
+        ASSERT_TRUE(NULL != *iter);
+        ASSERT_EQ(uint32_t(0), iter->key());
+        ASSERT_NE(v.endEntries(), iter);
 
-    iter++;
+        iter++;
 
-    ASSERT_EQ(v.endEntries(), iter);
+        ASSERT_EQ(uint32_t(1), iter.index());
+        ASSERT_TRUE(NULL == *iter);
+        ASSERT_NE(v.endEntries(), iter);
 
-    free(data);
+        iter++;
+
+        ASSERT_EQ(uint32_t(2), iter.index());
+        ASSERT_TRUE(NULL != *iter);
+        ASSERT_EQ(uint32_t(2), iter->key());
+        ASSERT_NE(v.endEntries(), iter);
+
+        iter++;
+
+        ASSERT_EQ(uint32_t(3), iter.index());
+        ASSERT_TRUE(NULL != *iter);
+        ASSERT_EQ(iter->is_compact(), compact_entry);
+        ASSERT_EQ(uint32_t(3), iter->key());
+        ASSERT_EQ(uint32_t(0x12345678), iter->value().data);
+        ASSERT_EQ(Res_value::TYPE_STRING, iter->value().dataType);
+
+        iter++;
+
+        ASSERT_EQ(v.endEntries(), iter);
+
+        free(data);
+    }
 }
 
 } // namespace android
diff --git a/libs/androidfw/tests/data/sparse/Android.bp b/libs/androidfw/tests/data/sparse/Android.bp
new file mode 100644
index 0000000..b0da375c
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/Android.bp
@@ -0,0 +1,23 @@
+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_libs_androidfw_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_libs_androidfw_license"],
+}
+
+android_test_helper_app {
+    name: "FrameworkResourcesSparseTestApp",
+    sdk_version: "current",
+    min_sdk_version: "32",
+    aaptflags: [
+        "--enable-sparse-encoding",
+    ],
+}
+
+android_test_helper_app {
+    name: "FrameworkResourcesNotSparseTestApp",
+    sdk_version: "current",
+    min_sdk_version: "32",
+}
diff --git a/libs/androidfw/tests/data/sparse/AndroidManifest.xml b/libs/androidfw/tests/data/sparse/AndroidManifest.xml
index 27911b6..9c23a72 100644
--- a/libs/androidfw/tests/data/sparse/AndroidManifest.xml
+++ b/libs/androidfw/tests/data/sparse/AndroidManifest.xml
@@ -17,4 +17,5 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.sparse">
     <application />
+    <uses-sdk android:minSdkVersion="32" />
 </manifest>
diff --git a/libs/androidfw/tests/data/sparse/R.h b/libs/androidfw/tests/data/sparse/R.h
index 2492dbf..a66e1af 100644
--- a/libs/androidfw/tests/data/sparse/R.h
+++ b/libs/androidfw/tests/data/sparse/R.h
@@ -42,7 +42,7 @@
   struct string {
     enum : uint32_t {
       foo_999 = 0x7f0203e7,
-      only_v26 = 0x7f0203e8
+      only_land = 0x7f0203e8
     };
   };
 };
diff --git a/libs/androidfw/tests/data/sparse/gen_strings.sh b/libs/androidfw/tests/data/sparse/gen_strings.sh
index 4ea5468..114ecbb 100755
--- a/libs/androidfw/tests/data/sparse/gen_strings.sh
+++ b/libs/androidfw/tests/data/sparse/gen_strings.sh
@@ -1,20 +1,20 @@
 #!/bin/bash
 
 OUTPUT_default=res/values/strings.xml
-OUTPUT_v26=res/values-v26/strings.xml
+OUTPUT_land=res/values-land/strings.xml
 
 echo "<resources>" > $OUTPUT_default
-echo "<resources>" > $OUTPUT_v26
+echo "<resources>" > $OUTPUT_land
 for i in {0..999}
 do
     echo "  <string name=\"foo_$i\">$i</string>" >> $OUTPUT_default
     if [ "$(($i % 3))" -eq "0" ]
     then
-        echo "  <string name=\"foo_$i\">$(($i * 10))</string>" >> $OUTPUT_v26
+        echo "  <string name=\"foo_$i\">$(($i * 10))</string>" >> $OUTPUT_land
     fi
 done
 echo "</resources>" >> $OUTPUT_default
 
-echo "  <string name=\"only_v26\">only v26</string>" >> $OUTPUT_v26
-echo "</resources>" >> $OUTPUT_v26
+echo "  <string name=\"only_land\">only land</string>" >> $OUTPUT_land
+echo "</resources>" >> $OUTPUT_land
 
diff --git a/libs/androidfw/tests/data/sparse/not_sparse.apk b/libs/androidfw/tests/data/sparse/not_sparse.apk
index b08a621..4d4d4a8 100644
--- a/libs/androidfw/tests/data/sparse/not_sparse.apk
+++ b/libs/androidfw/tests/data/sparse/not_sparse.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/sparse/res/values-land/strings.xml b/libs/androidfw/tests/data/sparse/res/values-land/strings.xml
new file mode 100644
index 0000000..66222c3
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/res/values-land/strings.xml
@@ -0,0 +1,337 @@
+<resources>
+  <string name="foo_0">0</string>
+  <string name="foo_3">30</string>
+  <string name="foo_6">60</string>
+  <string name="foo_9">90</string>
+  <string name="foo_12">120</string>
+  <string name="foo_15">150</string>
+  <string name="foo_18">180</string>
+  <string name="foo_21">210</string>
+  <string name="foo_24">240</string>
+  <string name="foo_27">270</string>
+  <string name="foo_30">300</string>
+  <string name="foo_33">330</string>
+  <string name="foo_36">360</string>
+  <string name="foo_39">390</string>
+  <string name="foo_42">420</string>
+  <string name="foo_45">450</string>
+  <string name="foo_48">480</string>
+  <string name="foo_51">510</string>
+  <string name="foo_54">540</string>
+  <string name="foo_57">570</string>
+  <string name="foo_60">600</string>
+  <string name="foo_63">630</string>
+  <string name="foo_66">660</string>
+  <string name="foo_69">690</string>
+  <string name="foo_72">720</string>
+  <string name="foo_75">750</string>
+  <string name="foo_78">780</string>
+  <string name="foo_81">810</string>
+  <string name="foo_84">840</string>
+  <string name="foo_87">870</string>
+  <string name="foo_90">900</string>
+  <string name="foo_93">930</string>
+  <string name="foo_96">960</string>
+  <string name="foo_99">990</string>
+  <string name="foo_102">1020</string>
+  <string name="foo_105">1050</string>
+  <string name="foo_108">1080</string>
+  <string name="foo_111">1110</string>
+  <string name="foo_114">1140</string>
+  <string name="foo_117">1170</string>
+  <string name="foo_120">1200</string>
+  <string name="foo_123">1230</string>
+  <string name="foo_126">1260</string>
+  <string name="foo_129">1290</string>
+  <string name="foo_132">1320</string>
+  <string name="foo_135">1350</string>
+  <string name="foo_138">1380</string>
+  <string name="foo_141">1410</string>
+  <string name="foo_144">1440</string>
+  <string name="foo_147">1470</string>
+  <string name="foo_150">1500</string>
+  <string name="foo_153">1530</string>
+  <string name="foo_156">1560</string>
+  <string name="foo_159">1590</string>
+  <string name="foo_162">1620</string>
+  <string name="foo_165">1650</string>
+  <string name="foo_168">1680</string>
+  <string name="foo_171">1710</string>
+  <string name="foo_174">1740</string>
+  <string name="foo_177">1770</string>
+  <string name="foo_180">1800</string>
+  <string name="foo_183">1830</string>
+  <string name="foo_186">1860</string>
+  <string name="foo_189">1890</string>
+  <string name="foo_192">1920</string>
+  <string name="foo_195">1950</string>
+  <string name="foo_198">1980</string>
+  <string name="foo_201">2010</string>
+  <string name="foo_204">2040</string>
+  <string name="foo_207">2070</string>
+  <string name="foo_210">2100</string>
+  <string name="foo_213">2130</string>
+  <string name="foo_216">2160</string>
+  <string name="foo_219">2190</string>
+  <string name="foo_222">2220</string>
+  <string name="foo_225">2250</string>
+  <string name="foo_228">2280</string>
+  <string name="foo_231">2310</string>
+  <string name="foo_234">2340</string>
+  <string name="foo_237">2370</string>
+  <string name="foo_240">2400</string>
+  <string name="foo_243">2430</string>
+  <string name="foo_246">2460</string>
+  <string name="foo_249">2490</string>
+  <string name="foo_252">2520</string>
+  <string name="foo_255">2550</string>
+  <string name="foo_258">2580</string>
+  <string name="foo_261">2610</string>
+  <string name="foo_264">2640</string>
+  <string name="foo_267">2670</string>
+  <string name="foo_270">2700</string>
+  <string name="foo_273">2730</string>
+  <string name="foo_276">2760</string>
+  <string name="foo_279">2790</string>
+  <string name="foo_282">2820</string>
+  <string name="foo_285">2850</string>
+  <string name="foo_288">2880</string>
+  <string name="foo_291">2910</string>
+  <string name="foo_294">2940</string>
+  <string name="foo_297">2970</string>
+  <string name="foo_300">3000</string>
+  <string name="foo_303">3030</string>
+  <string name="foo_306">3060</string>
+  <string name="foo_309">3090</string>
+  <string name="foo_312">3120</string>
+  <string name="foo_315">3150</string>
+  <string name="foo_318">3180</string>
+  <string name="foo_321">3210</string>
+  <string name="foo_324">3240</string>
+  <string name="foo_327">3270</string>
+  <string name="foo_330">3300</string>
+  <string name="foo_333">3330</string>
+  <string name="foo_336">3360</string>
+  <string name="foo_339">3390</string>
+  <string name="foo_342">3420</string>
+  <string name="foo_345">3450</string>
+  <string name="foo_348">3480</string>
+  <string name="foo_351">3510</string>
+  <string name="foo_354">3540</string>
+  <string name="foo_357">3570</string>
+  <string name="foo_360">3600</string>
+  <string name="foo_363">3630</string>
+  <string name="foo_366">3660</string>
+  <string name="foo_369">3690</string>
+  <string name="foo_372">3720</string>
+  <string name="foo_375">3750</string>
+  <string name="foo_378">3780</string>
+  <string name="foo_381">3810</string>
+  <string name="foo_384">3840</string>
+  <string name="foo_387">3870</string>
+  <string name="foo_390">3900</string>
+  <string name="foo_393">3930</string>
+  <string name="foo_396">3960</string>
+  <string name="foo_399">3990</string>
+  <string name="foo_402">4020</string>
+  <string name="foo_405">4050</string>
+  <string name="foo_408">4080</string>
+  <string name="foo_411">4110</string>
+  <string name="foo_414">4140</string>
+  <string name="foo_417">4170</string>
+  <string name="foo_420">4200</string>
+  <string name="foo_423">4230</string>
+  <string name="foo_426">4260</string>
+  <string name="foo_429">4290</string>
+  <string name="foo_432">4320</string>
+  <string name="foo_435">4350</string>
+  <string name="foo_438">4380</string>
+  <string name="foo_441">4410</string>
+  <string name="foo_444">4440</string>
+  <string name="foo_447">4470</string>
+  <string name="foo_450">4500</string>
+  <string name="foo_453">4530</string>
+  <string name="foo_456">4560</string>
+  <string name="foo_459">4590</string>
+  <string name="foo_462">4620</string>
+  <string name="foo_465">4650</string>
+  <string name="foo_468">4680</string>
+  <string name="foo_471">4710</string>
+  <string name="foo_474">4740</string>
+  <string name="foo_477">4770</string>
+  <string name="foo_480">4800</string>
+  <string name="foo_483">4830</string>
+  <string name="foo_486">4860</string>
+  <string name="foo_489">4890</string>
+  <string name="foo_492">4920</string>
+  <string name="foo_495">4950</string>
+  <string name="foo_498">4980</string>
+  <string name="foo_501">5010</string>
+  <string name="foo_504">5040</string>
+  <string name="foo_507">5070</string>
+  <string name="foo_510">5100</string>
+  <string name="foo_513">5130</string>
+  <string name="foo_516">5160</string>
+  <string name="foo_519">5190</string>
+  <string name="foo_522">5220</string>
+  <string name="foo_525">5250</string>
+  <string name="foo_528">5280</string>
+  <string name="foo_531">5310</string>
+  <string name="foo_534">5340</string>
+  <string name="foo_537">5370</string>
+  <string name="foo_540">5400</string>
+  <string name="foo_543">5430</string>
+  <string name="foo_546">5460</string>
+  <string name="foo_549">5490</string>
+  <string name="foo_552">5520</string>
+  <string name="foo_555">5550</string>
+  <string name="foo_558">5580</string>
+  <string name="foo_561">5610</string>
+  <string name="foo_564">5640</string>
+  <string name="foo_567">5670</string>
+  <string name="foo_570">5700</string>
+  <string name="foo_573">5730</string>
+  <string name="foo_576">5760</string>
+  <string name="foo_579">5790</string>
+  <string name="foo_582">5820</string>
+  <string name="foo_585">5850</string>
+  <string name="foo_588">5880</string>
+  <string name="foo_591">5910</string>
+  <string name="foo_594">5940</string>
+  <string name="foo_597">5970</string>
+  <string name="foo_600">6000</string>
+  <string name="foo_603">6030</string>
+  <string name="foo_606">6060</string>
+  <string name="foo_609">6090</string>
+  <string name="foo_612">6120</string>
+  <string name="foo_615">6150</string>
+  <string name="foo_618">6180</string>
+  <string name="foo_621">6210</string>
+  <string name="foo_624">6240</string>
+  <string name="foo_627">6270</string>
+  <string name="foo_630">6300</string>
+  <string name="foo_633">6330</string>
+  <string name="foo_636">6360</string>
+  <string name="foo_639">6390</string>
+  <string name="foo_642">6420</string>
+  <string name="foo_645">6450</string>
+  <string name="foo_648">6480</string>
+  <string name="foo_651">6510</string>
+  <string name="foo_654">6540</string>
+  <string name="foo_657">6570</string>
+  <string name="foo_660">6600</string>
+  <string name="foo_663">6630</string>
+  <string name="foo_666">6660</string>
+  <string name="foo_669">6690</string>
+  <string name="foo_672">6720</string>
+  <string name="foo_675">6750</string>
+  <string name="foo_678">6780</string>
+  <string name="foo_681">6810</string>
+  <string name="foo_684">6840</string>
+  <string name="foo_687">6870</string>
+  <string name="foo_690">6900</string>
+  <string name="foo_693">6930</string>
+  <string name="foo_696">6960</string>
+  <string name="foo_699">6990</string>
+  <string name="foo_702">7020</string>
+  <string name="foo_705">7050</string>
+  <string name="foo_708">7080</string>
+  <string name="foo_711">7110</string>
+  <string name="foo_714">7140</string>
+  <string name="foo_717">7170</string>
+  <string name="foo_720">7200</string>
+  <string name="foo_723">7230</string>
+  <string name="foo_726">7260</string>
+  <string name="foo_729">7290</string>
+  <string name="foo_732">7320</string>
+  <string name="foo_735">7350</string>
+  <string name="foo_738">7380</string>
+  <string name="foo_741">7410</string>
+  <string name="foo_744">7440</string>
+  <string name="foo_747">7470</string>
+  <string name="foo_750">7500</string>
+  <string name="foo_753">7530</string>
+  <string name="foo_756">7560</string>
+  <string name="foo_759">7590</string>
+  <string name="foo_762">7620</string>
+  <string name="foo_765">7650</string>
+  <string name="foo_768">7680</string>
+  <string name="foo_771">7710</string>
+  <string name="foo_774">7740</string>
+  <string name="foo_777">7770</string>
+  <string name="foo_780">7800</string>
+  <string name="foo_783">7830</string>
+  <string name="foo_786">7860</string>
+  <string name="foo_789">7890</string>
+  <string name="foo_792">7920</string>
+  <string name="foo_795">7950</string>
+  <string name="foo_798">7980</string>
+  <string name="foo_801">8010</string>
+  <string name="foo_804">8040</string>
+  <string name="foo_807">8070</string>
+  <string name="foo_810">8100</string>
+  <string name="foo_813">8130</string>
+  <string name="foo_816">8160</string>
+  <string name="foo_819">8190</string>
+  <string name="foo_822">8220</string>
+  <string name="foo_825">8250</string>
+  <string name="foo_828">8280</string>
+  <string name="foo_831">8310</string>
+  <string name="foo_834">8340</string>
+  <string name="foo_837">8370</string>
+  <string name="foo_840">8400</string>
+  <string name="foo_843">8430</string>
+  <string name="foo_846">8460</string>
+  <string name="foo_849">8490</string>
+  <string name="foo_852">8520</string>
+  <string name="foo_855">8550</string>
+  <string name="foo_858">8580</string>
+  <string name="foo_861">8610</string>
+  <string name="foo_864">8640</string>
+  <string name="foo_867">8670</string>
+  <string name="foo_870">8700</string>
+  <string name="foo_873">8730</string>
+  <string name="foo_876">8760</string>
+  <string name="foo_879">8790</string>
+  <string name="foo_882">8820</string>
+  <string name="foo_885">8850</string>
+  <string name="foo_888">8880</string>
+  <string name="foo_891">8910</string>
+  <string name="foo_894">8940</string>
+  <string name="foo_897">8970</string>
+  <string name="foo_900">9000</string>
+  <string name="foo_903">9030</string>
+  <string name="foo_906">9060</string>
+  <string name="foo_909">9090</string>
+  <string name="foo_912">9120</string>
+  <string name="foo_915">9150</string>
+  <string name="foo_918">9180</string>
+  <string name="foo_921">9210</string>
+  <string name="foo_924">9240</string>
+  <string name="foo_927">9270</string>
+  <string name="foo_930">9300</string>
+  <string name="foo_933">9330</string>
+  <string name="foo_936">9360</string>
+  <string name="foo_939">9390</string>
+  <string name="foo_942">9420</string>
+  <string name="foo_945">9450</string>
+  <string name="foo_948">9480</string>
+  <string name="foo_951">9510</string>
+  <string name="foo_954">9540</string>
+  <string name="foo_957">9570</string>
+  <string name="foo_960">9600</string>
+  <string name="foo_963">9630</string>
+  <string name="foo_966">9660</string>
+  <string name="foo_969">9690</string>
+  <string name="foo_972">9720</string>
+  <string name="foo_975">9750</string>
+  <string name="foo_978">9780</string>
+  <string name="foo_981">9810</string>
+  <string name="foo_984">9840</string>
+  <string name="foo_987">9870</string>
+  <string name="foo_990">9900</string>
+  <string name="foo_993">9930</string>
+  <string name="foo_996">9960</string>
+  <string name="foo_999">9990</string>
+  <string name="only_land">only land</string>
+</resources>
diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/values.xml b/libs/androidfw/tests/data/sparse/res/values-land/values.xml
similarity index 100%
rename from libs/androidfw/tests/data/sparse/res/values-v26/values.xml
rename to libs/androidfw/tests/data/sparse/res/values-land/values.xml
diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml b/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
deleted file mode 100644
index d116087e..0000000
--- a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
+++ /dev/null
@@ -1,337 +0,0 @@
-<resources>
-  <string name="foo_0">0</string>
-  <string name="foo_3">30</string>
-  <string name="foo_6">60</string>
-  <string name="foo_9">90</string>
-  <string name="foo_12">120</string>
-  <string name="foo_15">150</string>
-  <string name="foo_18">180</string>
-  <string name="foo_21">210</string>
-  <string name="foo_24">240</string>
-  <string name="foo_27">270</string>
-  <string name="foo_30">300</string>
-  <string name="foo_33">330</string>
-  <string name="foo_36">360</string>
-  <string name="foo_39">390</string>
-  <string name="foo_42">420</string>
-  <string name="foo_45">450</string>
-  <string name="foo_48">480</string>
-  <string name="foo_51">510</string>
-  <string name="foo_54">540</string>
-  <string name="foo_57">570</string>
-  <string name="foo_60">600</string>
-  <string name="foo_63">630</string>
-  <string name="foo_66">660</string>
-  <string name="foo_69">690</string>
-  <string name="foo_72">720</string>
-  <string name="foo_75">750</string>
-  <string name="foo_78">780</string>
-  <string name="foo_81">810</string>
-  <string name="foo_84">840</string>
-  <string name="foo_87">870</string>
-  <string name="foo_90">900</string>
-  <string name="foo_93">930</string>
-  <string name="foo_96">960</string>
-  <string name="foo_99">990</string>
-  <string name="foo_102">1020</string>
-  <string name="foo_105">1050</string>
-  <string name="foo_108">1080</string>
-  <string name="foo_111">1110</string>
-  <string name="foo_114">1140</string>
-  <string name="foo_117">1170</string>
-  <string name="foo_120">1200</string>
-  <string name="foo_123">1230</string>
-  <string name="foo_126">1260</string>
-  <string name="foo_129">1290</string>
-  <string name="foo_132">1320</string>
-  <string name="foo_135">1350</string>
-  <string name="foo_138">1380</string>
-  <string name="foo_141">1410</string>
-  <string name="foo_144">1440</string>
-  <string name="foo_147">1470</string>
-  <string name="foo_150">1500</string>
-  <string name="foo_153">1530</string>
-  <string name="foo_156">1560</string>
-  <string name="foo_159">1590</string>
-  <string name="foo_162">1620</string>
-  <string name="foo_165">1650</string>
-  <string name="foo_168">1680</string>
-  <string name="foo_171">1710</string>
-  <string name="foo_174">1740</string>
-  <string name="foo_177">1770</string>
-  <string name="foo_180">1800</string>
-  <string name="foo_183">1830</string>
-  <string name="foo_186">1860</string>
-  <string name="foo_189">1890</string>
-  <string name="foo_192">1920</string>
-  <string name="foo_195">1950</string>
-  <string name="foo_198">1980</string>
-  <string name="foo_201">2010</string>
-  <string name="foo_204">2040</string>
-  <string name="foo_207">2070</string>
-  <string name="foo_210">2100</string>
-  <string name="foo_213">2130</string>
-  <string name="foo_216">2160</string>
-  <string name="foo_219">2190</string>
-  <string name="foo_222">2220</string>
-  <string name="foo_225">2250</string>
-  <string name="foo_228">2280</string>
-  <string name="foo_231">2310</string>
-  <string name="foo_234">2340</string>
-  <string name="foo_237">2370</string>
-  <string name="foo_240">2400</string>
-  <string name="foo_243">2430</string>
-  <string name="foo_246">2460</string>
-  <string name="foo_249">2490</string>
-  <string name="foo_252">2520</string>
-  <string name="foo_255">2550</string>
-  <string name="foo_258">2580</string>
-  <string name="foo_261">2610</string>
-  <string name="foo_264">2640</string>
-  <string name="foo_267">2670</string>
-  <string name="foo_270">2700</string>
-  <string name="foo_273">2730</string>
-  <string name="foo_276">2760</string>
-  <string name="foo_279">2790</string>
-  <string name="foo_282">2820</string>
-  <string name="foo_285">2850</string>
-  <string name="foo_288">2880</string>
-  <string name="foo_291">2910</string>
-  <string name="foo_294">2940</string>
-  <string name="foo_297">2970</string>
-  <string name="foo_300">3000</string>
-  <string name="foo_303">3030</string>
-  <string name="foo_306">3060</string>
-  <string name="foo_309">3090</string>
-  <string name="foo_312">3120</string>
-  <string name="foo_315">3150</string>
-  <string name="foo_318">3180</string>
-  <string name="foo_321">3210</string>
-  <string name="foo_324">3240</string>
-  <string name="foo_327">3270</string>
-  <string name="foo_330">3300</string>
-  <string name="foo_333">3330</string>
-  <string name="foo_336">3360</string>
-  <string name="foo_339">3390</string>
-  <string name="foo_342">3420</string>
-  <string name="foo_345">3450</string>
-  <string name="foo_348">3480</string>
-  <string name="foo_351">3510</string>
-  <string name="foo_354">3540</string>
-  <string name="foo_357">3570</string>
-  <string name="foo_360">3600</string>
-  <string name="foo_363">3630</string>
-  <string name="foo_366">3660</string>
-  <string name="foo_369">3690</string>
-  <string name="foo_372">3720</string>
-  <string name="foo_375">3750</string>
-  <string name="foo_378">3780</string>
-  <string name="foo_381">3810</string>
-  <string name="foo_384">3840</string>
-  <string name="foo_387">3870</string>
-  <string name="foo_390">3900</string>
-  <string name="foo_393">3930</string>
-  <string name="foo_396">3960</string>
-  <string name="foo_399">3990</string>
-  <string name="foo_402">4020</string>
-  <string name="foo_405">4050</string>
-  <string name="foo_408">4080</string>
-  <string name="foo_411">4110</string>
-  <string name="foo_414">4140</string>
-  <string name="foo_417">4170</string>
-  <string name="foo_420">4200</string>
-  <string name="foo_423">4230</string>
-  <string name="foo_426">4260</string>
-  <string name="foo_429">4290</string>
-  <string name="foo_432">4320</string>
-  <string name="foo_435">4350</string>
-  <string name="foo_438">4380</string>
-  <string name="foo_441">4410</string>
-  <string name="foo_444">4440</string>
-  <string name="foo_447">4470</string>
-  <string name="foo_450">4500</string>
-  <string name="foo_453">4530</string>
-  <string name="foo_456">4560</string>
-  <string name="foo_459">4590</string>
-  <string name="foo_462">4620</string>
-  <string name="foo_465">4650</string>
-  <string name="foo_468">4680</string>
-  <string name="foo_471">4710</string>
-  <string name="foo_474">4740</string>
-  <string name="foo_477">4770</string>
-  <string name="foo_480">4800</string>
-  <string name="foo_483">4830</string>
-  <string name="foo_486">4860</string>
-  <string name="foo_489">4890</string>
-  <string name="foo_492">4920</string>
-  <string name="foo_495">4950</string>
-  <string name="foo_498">4980</string>
-  <string name="foo_501">5010</string>
-  <string name="foo_504">5040</string>
-  <string name="foo_507">5070</string>
-  <string name="foo_510">5100</string>
-  <string name="foo_513">5130</string>
-  <string name="foo_516">5160</string>
-  <string name="foo_519">5190</string>
-  <string name="foo_522">5220</string>
-  <string name="foo_525">5250</string>
-  <string name="foo_528">5280</string>
-  <string name="foo_531">5310</string>
-  <string name="foo_534">5340</string>
-  <string name="foo_537">5370</string>
-  <string name="foo_540">5400</string>
-  <string name="foo_543">5430</string>
-  <string name="foo_546">5460</string>
-  <string name="foo_549">5490</string>
-  <string name="foo_552">5520</string>
-  <string name="foo_555">5550</string>
-  <string name="foo_558">5580</string>
-  <string name="foo_561">5610</string>
-  <string name="foo_564">5640</string>
-  <string name="foo_567">5670</string>
-  <string name="foo_570">5700</string>
-  <string name="foo_573">5730</string>
-  <string name="foo_576">5760</string>
-  <string name="foo_579">5790</string>
-  <string name="foo_582">5820</string>
-  <string name="foo_585">5850</string>
-  <string name="foo_588">5880</string>
-  <string name="foo_591">5910</string>
-  <string name="foo_594">5940</string>
-  <string name="foo_597">5970</string>
-  <string name="foo_600">6000</string>
-  <string name="foo_603">6030</string>
-  <string name="foo_606">6060</string>
-  <string name="foo_609">6090</string>
-  <string name="foo_612">6120</string>
-  <string name="foo_615">6150</string>
-  <string name="foo_618">6180</string>
-  <string name="foo_621">6210</string>
-  <string name="foo_624">6240</string>
-  <string name="foo_627">6270</string>
-  <string name="foo_630">6300</string>
-  <string name="foo_633">6330</string>
-  <string name="foo_636">6360</string>
-  <string name="foo_639">6390</string>
-  <string name="foo_642">6420</string>
-  <string name="foo_645">6450</string>
-  <string name="foo_648">6480</string>
-  <string name="foo_651">6510</string>
-  <string name="foo_654">6540</string>
-  <string name="foo_657">6570</string>
-  <string name="foo_660">6600</string>
-  <string name="foo_663">6630</string>
-  <string name="foo_666">6660</string>
-  <string name="foo_669">6690</string>
-  <string name="foo_672">6720</string>
-  <string name="foo_675">6750</string>
-  <string name="foo_678">6780</string>
-  <string name="foo_681">6810</string>
-  <string name="foo_684">6840</string>
-  <string name="foo_687">6870</string>
-  <string name="foo_690">6900</string>
-  <string name="foo_693">6930</string>
-  <string name="foo_696">6960</string>
-  <string name="foo_699">6990</string>
-  <string name="foo_702">7020</string>
-  <string name="foo_705">7050</string>
-  <string name="foo_708">7080</string>
-  <string name="foo_711">7110</string>
-  <string name="foo_714">7140</string>
-  <string name="foo_717">7170</string>
-  <string name="foo_720">7200</string>
-  <string name="foo_723">7230</string>
-  <string name="foo_726">7260</string>
-  <string name="foo_729">7290</string>
-  <string name="foo_732">7320</string>
-  <string name="foo_735">7350</string>
-  <string name="foo_738">7380</string>
-  <string name="foo_741">7410</string>
-  <string name="foo_744">7440</string>
-  <string name="foo_747">7470</string>
-  <string name="foo_750">7500</string>
-  <string name="foo_753">7530</string>
-  <string name="foo_756">7560</string>
-  <string name="foo_759">7590</string>
-  <string name="foo_762">7620</string>
-  <string name="foo_765">7650</string>
-  <string name="foo_768">7680</string>
-  <string name="foo_771">7710</string>
-  <string name="foo_774">7740</string>
-  <string name="foo_777">7770</string>
-  <string name="foo_780">7800</string>
-  <string name="foo_783">7830</string>
-  <string name="foo_786">7860</string>
-  <string name="foo_789">7890</string>
-  <string name="foo_792">7920</string>
-  <string name="foo_795">7950</string>
-  <string name="foo_798">7980</string>
-  <string name="foo_801">8010</string>
-  <string name="foo_804">8040</string>
-  <string name="foo_807">8070</string>
-  <string name="foo_810">8100</string>
-  <string name="foo_813">8130</string>
-  <string name="foo_816">8160</string>
-  <string name="foo_819">8190</string>
-  <string name="foo_822">8220</string>
-  <string name="foo_825">8250</string>
-  <string name="foo_828">8280</string>
-  <string name="foo_831">8310</string>
-  <string name="foo_834">8340</string>
-  <string name="foo_837">8370</string>
-  <string name="foo_840">8400</string>
-  <string name="foo_843">8430</string>
-  <string name="foo_846">8460</string>
-  <string name="foo_849">8490</string>
-  <string name="foo_852">8520</string>
-  <string name="foo_855">8550</string>
-  <string name="foo_858">8580</string>
-  <string name="foo_861">8610</string>
-  <string name="foo_864">8640</string>
-  <string name="foo_867">8670</string>
-  <string name="foo_870">8700</string>
-  <string name="foo_873">8730</string>
-  <string name="foo_876">8760</string>
-  <string name="foo_879">8790</string>
-  <string name="foo_882">8820</string>
-  <string name="foo_885">8850</string>
-  <string name="foo_888">8880</string>
-  <string name="foo_891">8910</string>
-  <string name="foo_894">8940</string>
-  <string name="foo_897">8970</string>
-  <string name="foo_900">9000</string>
-  <string name="foo_903">9030</string>
-  <string name="foo_906">9060</string>
-  <string name="foo_909">9090</string>
-  <string name="foo_912">9120</string>
-  <string name="foo_915">9150</string>
-  <string name="foo_918">9180</string>
-  <string name="foo_921">9210</string>
-  <string name="foo_924">9240</string>
-  <string name="foo_927">9270</string>
-  <string name="foo_930">9300</string>
-  <string name="foo_933">9330</string>
-  <string name="foo_936">9360</string>
-  <string name="foo_939">9390</string>
-  <string name="foo_942">9420</string>
-  <string name="foo_945">9450</string>
-  <string name="foo_948">9480</string>
-  <string name="foo_951">9510</string>
-  <string name="foo_954">9540</string>
-  <string name="foo_957">9570</string>
-  <string name="foo_960">9600</string>
-  <string name="foo_963">9630</string>
-  <string name="foo_966">9660</string>
-  <string name="foo_969">9690</string>
-  <string name="foo_972">9720</string>
-  <string name="foo_975">9750</string>
-  <string name="foo_978">9780</string>
-  <string name="foo_981">9810</string>
-  <string name="foo_984">9840</string>
-  <string name="foo_987">9870</string>
-  <string name="foo_990">9900</string>
-  <string name="foo_993">9930</string>
-  <string name="foo_996">9960</string>
-  <string name="foo_999">9990</string>
-  <string name="only_v26">only v26</string>
-</resources>
diff --git a/libs/androidfw/tests/data/sparse/sparse.apk b/libs/androidfw/tests/data/sparse/sparse.apk
index 9fd01fb..0f2d75a 100644
--- a/libs/androidfw/tests/data/sparse/sparse.apk
+++ b/libs/androidfw/tests/data/sparse/sparse.apk
Binary files differ
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 29f3773..3e3d77b 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -340,6 +340,8 @@
         "jni/Graphics.cpp",
         "jni/ImageDecoder.cpp",
         "jni/Interpolator.cpp",
+        "jni/MeshSpecification.cpp",
+        "jni/Mesh.cpp",
         "jni/MaskFilter.cpp",
         "jni/NinePatch.cpp",
         "jni/NinePatchPeeker.cpp",
@@ -570,6 +572,7 @@
                 "renderthread/VulkanSurface.cpp",
                 "renderthread/RenderProxy.cpp",
                 "renderthread/RenderThread.cpp",
+                "renderthread/HintSessionWrapper.cpp",
                 "service/GraphicsStatsService.cpp",
                 "thread/CommonPool.cpp",
                 "utils/GLUtils.cpp",
@@ -589,6 +592,7 @@
                 "ProfileData.cpp",
                 "ProfileDataContainer.cpp",
                 "Readback.cpp",
+                "Tonemapper.cpp",
                 "TreeInfo.cpp",
                 "WebViewFunctorManager.cpp",
                 "protos/graphicsstats.proto",
@@ -636,7 +640,7 @@
 cc_defaults {
     name: "hwui_test_defaults",
     defaults: ["hwui_defaults"],
-    test_suites: ["device-tests"],
+    test_suites: ["general-tests"],
     header_libs: ["libandroid_headers_private"],
     target: {
         android: {
@@ -678,6 +682,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/AndroidTest.xml b/libs/hwui/AndroidTest.xml
index 381fb9f..911315f 100644
--- a/libs/hwui/AndroidTest.xml
+++ b/libs/hwui/AndroidTest.xml
@@ -16,22 +16,22 @@
 <configuration description="Config for hwuimicro">
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
-        <option name="push" value="hwui_unit_tests->/data/nativetest/hwui_unit_tests" />
-        <option name="push" value="hwuimicro->/data/benchmarktest/hwuimicro" />
-        <option name="push" value="hwuimacro->/data/benchmarktest/hwuimacro" />
+        <option name="push" value="hwui_unit_tests->/data/local/tmp/nativetest/hwui_unit_tests" />
+        <option name="push" value="hwuimicro->/data/local/tmp/benchmarktest/hwuimicro" />
+        <option name="push" value="hwuimacro->/data/local/tmp/benchmarktest/hwuimacro" />
     </target_preparer>
     <option name="test-suite-tag" value="apct" />
     <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/nativetest" />
+        <option name="native-test-device-path" value="/data/local/tmp/nativetest" />
         <option name="module-name" value="hwui_unit_tests" />
     </test>
     <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
-        <option name="native-benchmark-device-path" value="/data/benchmarktest" />
+        <option name="native-benchmark-device-path" value="/data/local/tmp/benchmarktest" />
         <option name="benchmark-module-name" value="hwuimicro" />
         <option name="file-exclusion-filter-regex" value=".*\.config$" />
     </test>
     <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
-        <option name="native-benchmark-device-path" value="/data/benchmarktest" />
+        <option name="native-benchmark-device-path" value="/data/local/tmp/benchmarktest" />
         <option name="benchmark-module-name" value="hwuimacro" />
         <option name="file-exclusion-filter-regex" value=".*\.config$" />
     </test>
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/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index 673041a..cd4fae8 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -17,6 +17,7 @@
 #include "CanvasTransform.h"
 
 #include <SkAndroidFrameworkUtils.h>
+#include <SkBlendMode.h>
 #include <SkColorFilter.h>
 #include <SkGradientShader.h>
 #include <SkHighContrastFilter.h>
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index 9a4c550..a7f8f61 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <SkBlendMode.h>
 #include <SkColorFilter.h>
 #include <SkImage.h>
 #include <SkMatrix.h>
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index 4ec782f..e2127ef 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -52,3 +52,4 @@
 X(DrawVectorDrawable)
 X(DrawRippleDrawable)
 X(DrawWebView)
+X(DrawMesh)
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index 564ee4f..b15b6cb 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -104,6 +104,7 @@
         set(FrameInfoIndex::AnimationStart) = vsyncTime;
         set(FrameInfoIndex::PerformTraversalsStart) = vsyncTime;
         set(FrameInfoIndex::DrawStart) = vsyncTime;
+        set(FrameInfoIndex::FrameStartTime) = vsyncTime;
         set(FrameInfoIndex::FrameDeadline) = frameDeadline;
         set(FrameInfoIndex::FrameInterval) = frameInterval;
         return *this;
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index 9053c12..fc3118a 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -20,6 +20,8 @@
 #include "utils/Color.h"
 #include "utils/MathUtils.h"
 
+#include <SkBlendMode.h>
+
 #include <log/log.h>
 
 namespace android {
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
index e86b338..41ced8c 100644
--- a/libs/hwui/MemoryPolicy.h
+++ b/libs/hwui/MemoryPolicy.h
@@ -43,13 +43,13 @@
     float backgroundRetentionPercent = 0.5f;
     // How long after the last renderer goes away before the GPU context is released. A value
     // of 0 means only drop the context on background TRIM signals
-    nsecs_t contextTimeout = 0_ms;
+    nsecs_t contextTimeout = 10_s;
     // The minimum amount of time to hold onto items in the resource cache
     // The actual time used will be the max of this & when frames were actually rendered
     nsecs_t minimumResourceRetention = 10_s;
     // If false, use only TRIM_UI_HIDDEN to drive background cache limits;
     // If true, use all signals (such as all contexts are stopped) to drive the limits
-    bool useAlternativeUiHidden = false;
+    bool useAlternativeUiHidden = true;
     // Whether or not to only purge scratch resources when triggering UI Hidden or background
     // collection
     bool purgeScratchOnly = true;
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 02c2e67..8dcd6db 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -16,16 +16,6 @@
 
 #include "Readback.h"
 
-#include <sync/sync.h>
-#include <system/window.h>
-
-#include <gui/TraceUtils.h>
-#include "DeferredLayerUpdater.h"
-#include "Properties.h"
-#include "hwui/Bitmap.h"
-#include "pipeline/skia/LayerDrawable.h"
-#include "renderthread/EglManager.h"
-#include "renderthread/VulkanManager.h"
 #include <SkBitmap.h>
 #include <SkBlendMode.h>
 #include <SkCanvas.h>
@@ -38,6 +28,19 @@
 #include <SkRefCnt.h>
 #include <SkSamplingOptions.h>
 #include <SkSurface.h>
+#include <gui/TraceUtils.h>
+#include <private/android/AHardwareBufferHelpers.h>
+#include <shaders/shaders.h>
+#include <sync/sync.h>
+#include <system/window.h>
+
+#include "DeferredLayerUpdater.h"
+#include "Properties.h"
+#include "Tonemapper.h"
+#include "hwui/Bitmap.h"
+#include "pipeline/skia/LayerDrawable.h"
+#include "renderthread/EglManager.h"
+#include "renderthread/VulkanManager.h"
 #include "utils/Color.h"
 #include "utils/MathUtils.h"
 #include "utils/NdkUtils.h"
@@ -91,8 +94,18 @@
         }
     }
 
-    sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace(
-            static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window)));
+    int32_t dataspace = ANativeWindow_getBuffersDataSpace(window);
+
+    // If the application is not updating the Surface themselves, e.g., another
+    // process is producing buffers for the application to display, then
+    // ANativeWindow_getBuffersDataSpace will return an unknown answer, so grab
+    // the dataspace from buffer metadata instead, if it exists.
+    if (dataspace == 0) {
+        dataspace = AHardwareBuffer_getDataSpace(sourceBuffer.get());
+    }
+
+    sk_sp<SkColorSpace> colorSpace =
+            DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace));
     sk_sp<SkImage> image =
             SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
 
@@ -227,6 +240,10 @@
     const bool hasBufferCrop = cropRect.left < cropRect.right && cropRect.top < cropRect.bottom;
     auto constraint =
             hasBufferCrop ? SkCanvas::kStrict_SrcRectConstraint : SkCanvas::kFast_SrcRectConstraint;
+
+    static constexpr float kMaxLuminanceNits = 4000.f;
+    tonemapPaint(image->imageInfo(), canvas->imageInfo(), kMaxLuminanceNits, paint);
+
     canvas->drawImageRect(image, imageSrcRect, imageDstRect, sampling, &paint, constraint);
     canvas->restore();
 
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index f5ebfd5..3f21940 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -15,13 +15,16 @@
  */
 
 #include "RecordingCanvas.h"
-#include <hwui/Paint.h>
 
 #include <GrRecordingContext.h>
+#include <SkMesh.h>
+#include <hwui/Paint.h>
 
 #include <experimental/type_traits>
+#include <utility>
 
 #include "SkAndroidFrameworkUtils.h"
+#include "SkBlendMode.h"
 #include "SkCanvas.h"
 #include "SkCanvasPriv.h"
 #include "SkColor.h"
@@ -270,7 +273,6 @@
     SkPaint paint;
     void draw(SkCanvas* c, const SkMatrix&) const { c->drawDRRect(outer, inner, paint); }
 };
-
 struct DrawAnnotation final : Op {
     static const auto kType = Type::DrawAnnotation;
     DrawAnnotation(const SkRect& rect, SkData* value) : rect(rect), value(sk_ref_sp(value)) {}
@@ -452,6 +454,16 @@
         c->drawVertices(vertices, mode, paint);
     }
 };
+struct DrawMesh final : Op {
+    static const auto kType = Type::DrawMesh;
+    DrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
+            : mesh(mesh), blender(std::move(blender)), paint(paint) {}
+
+    SkMesh mesh;
+    sk_sp<SkBlender> blender;
+    SkPaint paint;
+    void draw(SkCanvas* c, const SkMatrix&) const { c->drawMesh(mesh, blender, paint); }
+};
 struct DrawAtlas final : Op {
     static const auto kType = Type::DrawAtlas;
     DrawAtlas(const SkImage* atlas, int count, SkBlendMode mode, const SkSamplingOptions& sampling,
@@ -763,6 +775,10 @@
 void DisplayListData::drawVertices(const SkVertices* vert, SkBlendMode mode, const SkPaint& paint) {
     this->push<DrawVertices>(0, vert, mode, paint);
 }
+void DisplayListData::drawMesh(const SkMesh& mesh, const sk_sp<SkBlender>& blender,
+                               const SkPaint& paint) {
+    this->push<DrawMesh>(0, mesh, blender, paint);
+}
 void DisplayListData::drawAtlas(const SkImage* atlas, const SkRSXform xforms[], const SkRect texs[],
                                 const SkColor colors[], int count, SkBlendMode xfermode,
                                 const SkSamplingOptions& sampling, const SkRect* cull,
@@ -1105,6 +1121,10 @@
                                            SkBlendMode mode, const SkPaint& paint) {
     fDL->drawVertices(vertices, mode, paint);
 }
+void RecordingCanvas::onDrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender,
+                                 const SkPaint& paint) {
+    fDL->drawMesh(mesh, blender, paint);
+}
 void RecordingCanvas::onDrawAtlas2(const SkImage* atlas, const SkRSXform xforms[],
                                    const SkRect texs[], const SkColor colors[], int count,
                                    SkBlendMode bmode, const SkSamplingOptions& sampling,
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 35bec93..2539694 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -34,6 +34,7 @@
 #include <SkRuntimeEffect.h>
 #include <vector>
 
+enum class SkBlendMode;
 class SkRRect;
 
 namespace android {
@@ -111,6 +112,8 @@
     void drawRRect(const SkRRect&, const SkPaint&);
     void drawDRRect(const SkRRect&, const SkRRect&, const SkPaint&);
 
+    void drawMesh(const SkMesh&, const sk_sp<SkBlender>&, const SkPaint&);
+
     void drawAnnotation(const SkRect&, const char*, SkData*);
     void drawDrawable(SkDrawable*, const SkMatrix*);
     void drawPicture(const SkPicture*, const SkMatrix*, const SkPaint*);
@@ -210,6 +213,7 @@
                      const SkPaint&) override;
     void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override;
     void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void onDrawMesh(const SkMesh&, sk_sp<SkBlender>, const SkPaint&) override;
     void onDrawAtlas2(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int,
                      SkBlendMode, const SkSamplingOptions&, const SkRect*, const SkPaint*) override;
     void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 473afbd..d83d78f 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -570,6 +570,10 @@
     applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawVertices(vertices, mode, p); });
 }
 
+void SkiaCanvas::drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) {
+    mCanvas->drawMesh(mesh, blender, paint);
+}
+
 // ----------------------------------------------------------------------------
 // Canvas draw operations: Bitmaps
 // ----------------------------------------------------------------------------
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 51007c5..31e3b4c 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -33,6 +33,7 @@
 #include <cassert>
 #include <optional>
 
+enum class SkBlendMode;
 class SkRRect;
 
 namespace android {
@@ -119,8 +120,8 @@
     virtual void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
                                const Paint& paint) override;
 
-   virtual void drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner,
-                               const Paint& paint) override;
+    virtual void drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner,
+                                     const Paint& paint) override;
 
     virtual void drawCircle(float x, float y, float radius, const Paint& paint) override;
     virtual void drawOval(float left, float top, float right, float bottom,
@@ -129,6 +130,8 @@
                          float sweepAngle, bool useCenter, const Paint& paint) override;
     virtual void drawPath(const SkPath& path, const Paint& paint) override;
     virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) override;
+    virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender,
+                          const SkPaint& paint) override;
 
     virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) override;
     virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* paint) override;
diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp
index 0695dd1..153c3b6 100644
--- a/libs/hwui/SkiaInterpolator.cpp
+++ b/libs/hwui/SkiaInterpolator.cpp
@@ -17,6 +17,8 @@
 #include "SkiaInterpolator.h"
 
 #include "include/core/SkMath.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkTypes.h"
 #include "include/private/SkFixed.h"
 #include "include/private/SkMalloc.h"
 #include "include/private/SkTo.h"
diff --git a/libs/hwui/TEST_MAPPING b/libs/hwui/TEST_MAPPING
index b1719a9..03682e8 100644
--- a/libs/hwui/TEST_MAPPING
+++ b/libs/hwui/TEST_MAPPING
@@ -5,6 +5,9 @@
     },
     {
       "name": "CtsAccelerationTestCases"
+    },
+    {
+      "name": "hwui_unit_tests"
     }
   ],
   "imports": [
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
new file mode 100644
index 0000000..a7e76b6
--- /dev/null
+++ b/libs/hwui/Tonemapper.cpp
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+#include "Tonemapper.h"
+
+#include <SkRuntimeEffect.h>
+#include <log/log.h>
+#include <shaders/shaders.h>
+
+#include "utils/Color.h"
+
+namespace android::uirenderer {
+
+namespace {
+
+class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder {
+public:
+    explicit ColorFilterRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect)
+            : SkRuntimeEffectBuilder(std::move(effect)) {}
+
+    sk_sp<SkColorFilter> makeColorFilter() {
+        return this->effect()->makeColorFilter(this->uniforms());
+    }
+};
+
+static sk_sp<SkColorFilter> createLinearEffectColorFilter(const shaders::LinearEffect& linearEffect,
+                                                          float maxDisplayLuminance,
+                                                          float currentDisplayLuminanceNits,
+                                                          float maxLuminance) {
+    auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
+    auto [runtimeEffect, error] = SkRuntimeEffect::MakeForColorFilter(std::move(shaderString));
+    if (!runtimeEffect) {
+        LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str());
+    }
+
+    ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect));
+
+    const auto uniforms =
+            shaders::buildLinearEffectUniforms(linearEffect, android::mat4(), maxDisplayLuminance,
+                                               currentDisplayLuminanceNits, maxLuminance);
+
+    for (const auto& uniform : uniforms) {
+        effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
+    }
+
+    return effectBuilder.makeColorFilter();
+}
+
+static bool extractTransfer(ui::Dataspace dataspace) {
+    return dataspace & HAL_DATASPACE_TRANSFER_MASK;
+}
+
+static bool isHdrDataspace(ui::Dataspace dataspace) {
+    const auto transfer = extractTransfer(dataspace);
+
+    return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
+}
+
+static ui::Dataspace getDataspace(const SkImageInfo& image) {
+    return static_cast<ui::Dataspace>(
+            ColorSpaceToADataSpace(image.colorSpace(), image.colorType()));
+}
+
+}  // namespace
+
+// Given a source and destination image info, and the max content luminance, generate a tonemaping
+// shader and tag it on the supplied paint.
+void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
+                  SkPaint& paint) {
+    const auto sourceDataspace = getDataspace(source);
+    const auto destinationDataspace = getDataspace(destination);
+
+    if (extractTransfer(sourceDataspace) != extractTransfer(destinationDataspace) &&
+        (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace))) {
+        const auto effect = shaders::LinearEffect{
+                .inputDataspace = sourceDataspace,
+                .outputDataspace = destinationDataspace,
+                .undoPremultipliedAlpha = source.alphaType() == kPremul_SkAlphaType,
+                .fakeInputDataspace = destinationDataspace,
+                .type = shaders::LinearEffect::SkSLType::ColorFilter};
+        constexpr float kMaxDisplayBrightnessNits = 1000.f;
+        constexpr float kCurrentDisplayBrightnessNits = 500.f;
+        sk_sp<SkColorFilter> colorFilter = createLinearEffectColorFilter(
+                effect, kMaxDisplayBrightnessNits, kCurrentDisplayBrightnessNits, maxLuminanceNits);
+
+        if (paint.getColorFilter()) {
+            paint.setColorFilter(SkColorFilters::Compose(paint.refColorFilter(), colorFilter));
+        } else {
+            paint.setColorFilter(colorFilter);
+        }
+    }
+}
+
+}  // namespace android::uirenderer
diff --git a/libs/hwui/Tonemapper.h b/libs/hwui/Tonemapper.h
new file mode 100644
index 0000000..c0d5325
--- /dev/null
+++ b/libs/hwui/Tonemapper.h
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <SkCanvas.h>
+
+namespace android::uirenderer {
+
+// Given a source and destination image info, and the max content luminance, generate a tonemaping
+// shader and tag it on the supplied paint.
+void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
+                  SkPaint& paint);
+
+}  // namespace android::uirenderer
diff --git a/libs/hwui/apex/android_paint.cpp b/libs/hwui/apex/android_paint.cpp
index 70bd085..cc79cba 100644
--- a/libs/hwui/apex/android_paint.cpp
+++ b/libs/hwui/apex/android_paint.cpp
@@ -19,6 +19,7 @@
 #include "TypeCast.h"
 
 #include <hwui/Paint.h>
+#include <SkBlendMode.h>
 
 using namespace android;
 
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 39725a5..e6cfa7b 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -76,6 +76,8 @@
 extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
 extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
 extern int register_android_graphics_text_TextShaper(JNIEnv *env);
+extern int register_android_graphics_MeshSpecification(JNIEnv* env);
+extern int register_android_graphics_Mesh(JNIEnv* env);
 
 extern int register_android_util_PathParser(JNIEnv* env);
 extern int register_android_view_DisplayListCanvas(JNIEnv* env);
@@ -143,6 +145,8 @@
             REG_JNI(register_android_graphics_text_MeasuredText),
             REG_JNI(register_android_graphics_text_LineBreaker),
             REG_JNI(register_android_graphics_text_TextShaper),
+            REG_JNI(register_android_graphics_MeshSpecification),
+            REG_JNI(register_android_graphics_Mesh),
 
             REG_JNI(register_android_util_PathParser),
             REG_JNI(register_android_view_RenderNode),
diff --git a/libs/hwui/hwui/BlurDrawLooper.cpp b/libs/hwui/hwui/BlurDrawLooper.cpp
index d4b0198..8b52551 100644
--- a/libs/hwui/hwui/BlurDrawLooper.cpp
+++ b/libs/hwui/hwui/BlurDrawLooper.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "BlurDrawLooper.h"
+#include <SkBlurTypes.h>
 #include <SkColorSpace.h>
 #include <SkMaskFilter.h>
 
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 82d23b5..2a20191 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -30,6 +30,7 @@
 #include <SkMatrix.h>
 
 class SkAnimatedImage;
+enum class SkBlendMode;
 class SkCanvasState;
 class SkRRect;
 class SkRuntimeShaderBuilder;
@@ -226,6 +227,7 @@
                          float sweepAngle, bool useCenter, const Paint& paint) = 0;
     virtual void drawPath(const SkPath& path, const Paint& paint) = 0;
     virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) = 0;
+    virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender>, const SkPaint& paint) = 0;
 
     // Bitmap-based
     virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) = 0;
diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp
index cef21f9..4bd7ef4 100644
--- a/libs/hwui/jni/ColorFilter.cpp
+++ b/libs/hwui/jni/ColorFilter.cpp
@@ -17,6 +17,7 @@
 
 #include "GraphicsJNI.h"
 
+#include "SkBlendMode.h"
 #include "SkColorFilter.h"
 #include "SkColorMatrixFilter.h"
 
diff --git a/libs/hwui/jni/MaskFilter.cpp b/libs/hwui/jni/MaskFilter.cpp
index 5383032..048ce02 100644
--- a/libs/hwui/jni/MaskFilter.cpp
+++ b/libs/hwui/jni/MaskFilter.cpp
@@ -2,6 +2,7 @@
 #include "SkMaskFilter.h"
 #include "SkBlurMask.h"
 #include "SkBlurMaskFilter.h"
+#include "SkBlurTypes.h"
 #include "SkTableMaskFilter.h"
 
 static void ThrowIAE_IfNull(JNIEnv* env, void* ptr) {
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
new file mode 100644
index 0000000..7c732d7
--- /dev/null
+++ b/libs/hwui/jni/Mesh.cpp
@@ -0,0 +1,220 @@
+/*
+ * 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 <GLES/gl.h>
+#include <Mesh.h>
+#include <SkMesh.h>
+
+#include "GraphicsJNI.h"
+#include "graphics_jni_helpers.h"
+
+namespace android {
+
+sk_sp<SkMesh::VertexBuffer> genVertexBuffer(JNIEnv* env, jobject buffer, int size,
+                                            jboolean isDirect) {
+    auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect);
+    auto vertexBuffer = SkMesh::MakeVertexBuffer(nullptr, buff.data(), size);
+    return vertexBuffer;
+}
+
+sk_sp<SkMesh::IndexBuffer> genIndexBuffer(JNIEnv* env, jobject buffer, int size,
+                                          jboolean isDirect) {
+    auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect);
+    auto indexBuffer = SkMesh::MakeIndexBuffer(nullptr, buff.data(), size);
+    return indexBuffer;
+}
+
+// TODO(b/260252882): undefine SK_LEGACY_MESH_MAKE and remove this.
+template <typename T>
+SkMesh get_mesh_from_result(T&& result) {
+#ifdef SK_LEGACY_MESH_MAKE
+    return result;
+#else
+    return result.mesh;
+#endif
+}
+
+static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+                  jboolean isDirect, jint vertexCount, jint vertexOffset, jint left, jint top,
+                  jint right, jint bottom) {
+    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+    sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
+            genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect);
+    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+    auto mesh = get_mesh_from_result(SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer,
+                                                  vertexCount, vertexOffset, nullptr, skRect));
+    auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
+    return reinterpret_cast<jlong>(meshPtr.release());
+}
+
+static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+                         jboolean isVertexDirect, jint vertexCount, jint vertexOffset,
+                         jobject indexBuffer, jboolean isIndexDirect, jint indexCount,
+                         jint indexOffset, jint left, jint top, jint right, jint bottom) {
+    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+    sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
+            genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isVertexDirect);
+    sk_sp<SkMesh::IndexBuffer> skIndexBuffer =
+            genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect);
+    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+    auto mesh = get_mesh_from_result(SkMesh::MakeIndexed(
+            skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
+            skIndexBuffer, indexCount, indexOffset, nullptr, skRect));
+    auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
+    return reinterpret_cast<jlong>(meshPtr.release());
+}
+
+static void updateMesh(JNIEnv* env, jobject, jlong meshWrapper, jboolean indexed) {
+    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
+    auto mesh = wrapper->mesh;
+    if (indexed) {
+        wrapper->mesh = get_mesh_from_result(SkMesh::MakeIndexed(
+                sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
+                mesh.vertexCount(), mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()),
+                mesh.indexCount(), mesh.indexOffset(), wrapper->builder.fUniforms, mesh.bounds()));
+    } else {
+        wrapper->mesh = get_mesh_from_result(
+                SkMesh::Make(sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
+                             mesh.vertexCount(), mesh.vertexOffset(), wrapper->builder.fUniforms,
+                             mesh.bounds()));
+    }
+}
+
+static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
+    va_end(args);
+    return ret;
+}
+
+static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
+    switch (type) {
+        case SkRuntimeEffect::Uniform::Type::kFloat:
+        case SkRuntimeEffect::Uniform::Type::kFloat2:
+        case SkRuntimeEffect::Uniform::Type::kFloat3:
+        case SkRuntimeEffect::Uniform::Type::kFloat4:
+        case SkRuntimeEffect::Uniform::Type::kFloat2x2:
+        case SkRuntimeEffect::Uniform::Type::kFloat3x3:
+        case SkRuntimeEffect::Uniform::Type::kFloat4x4:
+            return false;
+        case SkRuntimeEffect::Uniform::Type::kInt:
+        case SkRuntimeEffect::Uniform::Type::kInt2:
+        case SkRuntimeEffect::Uniform::Type::kInt3:
+        case SkRuntimeEffect::Uniform::Type::kInt4:
+            return true;
+    }
+}
+
+static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+                                      const char* uniformName, const float values[], int count,
+                                      bool isColor) {
+    MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
+    if (uniform.fVar == nullptr) {
+        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+    } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
+        if (isColor) {
+            jniThrowExceptionFmt(
+                    env, "java/lang/IllegalArgumentException",
+                    "attempting to set a color uniform using the non-color specific APIs: %s %x",
+                    uniformName, uniform.fVar->flags);
+        } else {
+            ThrowIAEFmt(env,
+                        "attempting to set a non-color uniform using the setColorUniform APIs: %s",
+                        uniformName);
+        }
+    } else if (isIntUniformType(uniform.fVar->type)) {
+        ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
+                    uniformName);
+    } else if (!uniform.set<float>(values, count)) {
+        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
+    }
+}
+
+static void updateFloatUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+                                jfloat value1, jfloat value2, jfloat value3, jfloat value4,
+                                jint count) {
+    auto* builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    ScopedUtfChars name(env, uniformName);
+    const float values[4] = {value1, value2, value3, value4};
+    nativeUpdateFloatUniforms(env, builder, name.c_str(), values, count, false);
+}
+
+static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring jUniformName,
+                                     jfloatArray jvalues, jboolean isColor) {
+    auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    ScopedUtfChars name(env, jUniformName);
+    AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
+    nativeUpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(),
+                              isColor);
+}
+
+static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+                                    const char* uniformName, const int values[], int count) {
+    MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
+    if (uniform.fVar == nullptr) {
+        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+    } else if (!isIntUniformType(uniform.fVar->type)) {
+        ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
+                    uniformName);
+    } else if (!uniform.set<int>(values, count)) {
+        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
+    }
+}
+
+static void updateIntUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+                              jint value1, jint value2, jint value3, jint value4, jint count) {
+    auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    ScopedUtfChars name(env, uniformName);
+    const int values[4] = {value1, value2, value3, value4};
+    nativeUpdateIntUniforms(env, builder, name.c_str(), values, count);
+}
+
+static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+                                   jintArray values) {
+    auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    ScopedUtfChars name(env, uniformName);
+    AutoJavaIntArray autoValues(env, values, 0);
+    nativeUpdateIntUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length());
+}
+
+static void MeshWrapper_destroy(MeshWrapper* wrapper) {
+    delete wrapper;
+}
+
+static jlong getMeshFinalizer(JNIEnv*, jobject) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy));
+}
+
+static const JNINativeMethod gMeshMethods[] = {
+        {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer},
+        {"nativeMake", "(JILjava/nio/Buffer;ZIIIIII)J", (void*)make},
+        {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIIIII)J",
+         (void*)makeIndexed},
+        {"nativeUpdateMesh", "(JZ)V", (void*)updateMesh},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}};
+
+int register_android_graphics_Mesh(JNIEnv* env) {
+    android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods));
+    return 0;
+}
+
+}  // namespace android
diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h
new file mode 100644
index 0000000..aa014a5
--- /dev/null
+++ b/libs/hwui/jni/Mesh.h
@@ -0,0 +1,261 @@
+/*
+ * 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.
+ */
+
+#ifndef FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
+#define FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
+
+#include <SkMesh.h>
+#include <jni.h>
+
+#include <utility>
+
+#include "graphics_jni_helpers.h"
+
+#define gIndexByteSize 2
+
+// A smart pointer that provides read only access to Java.nio.Buffer. This handles both
+// direct and indrect buffers, allowing access to the underlying data in both
+// situations. If passed a null buffer, we will throw NullPointerException,
+// and c_data will return nullptr.
+//
+// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void *
+// conversion.
+class ScopedJavaNioBuffer {
+public:
+    ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, jint size, jboolean isDirect)
+            : mEnv(env), mBuffer(buffer) {
+        if (buffer == nullptr) {
+            mDataBase = nullptr;
+            mData = nullptr;
+            jniThrowNullPointerException(env);
+        } else {
+            mArray = (jarray) nullptr;
+            if (isDirect) {
+                mData = getDirectBufferPointer(mEnv, mBuffer);
+            } else {
+                mData = setIndirectData(size);
+            }
+        }
+    }
+
+    ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); }
+
+    ~ScopedJavaNioBuffer() { reset(); }
+
+    void reset() {
+        if (mDataBase) {
+            releasePointer(mEnv, mArray, mDataBase, JNI_FALSE);
+            mDataBase = nullptr;
+        }
+    }
+
+    ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept {
+        if (this != &rhs) {
+            reset();
+
+            mEnv = rhs.mEnv;
+            mBuffer = rhs.mBuffer;
+            mDataBase = rhs.mDataBase;
+            mData = rhs.mData;
+            mArray = rhs.mArray;
+            rhs.mEnv = nullptr;
+            rhs.mData = nullptr;
+            rhs.mBuffer = nullptr;
+            rhs.mArray = nullptr;
+            rhs.mDataBase = nullptr;
+        }
+        return *this;
+    }
+
+    const void* data() const { return mData; }
+
+private:
+    /**
+     * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
+     * from a java.nio.Buffer.
+     */
+    void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
+        if (buffer == nullptr) {
+            return nullptr;
+        }
+
+        jint position;
+        jint limit;
+        jint elementSizeShift;
+        jlong pointer;
+        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+        if (pointer == 0) {
+            jniThrowException(mEnv, "java/lang/IllegalArgumentException",
+                              "Must use a native order direct Buffer");
+            return nullptr;
+        }
+        pointer += position << elementSizeShift;
+        return reinterpret_cast<void*>(pointer);
+    }
+
+    static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
+        env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
+    }
+
+    static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining,
+                            jint* offset) {
+        jint position;
+        jint limit;
+        jint elementSizeShift;
+
+        jlong pointer;
+        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+        *remaining = (limit - position) << elementSizeShift;
+        if (pointer != 0L) {
+            *array = nullptr;
+            pointer += position << elementSizeShift;
+            return reinterpret_cast<void*>(pointer);
+        }
+
+        *array = jniGetNioBufferBaseArray(env, buffer);
+        *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
+        return nullptr;
+    }
+
+    /**
+     * This is a copy of
+     * static void android_glBufferData__IILjava_nio_Buffer_2I
+     * from com_google_android_gles_jni_GLImpl.cpp
+     */
+    void* setIndirectData(jint size) {
+        jint exception;
+        const char* exceptionType;
+        const char* exceptionMessage;
+        jint bufferOffset = (jint)0;
+        jint remaining;
+        void* tempData;
+
+        if (mBuffer) {
+            tempData =
+                    (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset);
+            if (remaining < size) {
+                exception = 1;
+                exceptionType = "java/lang/IllegalArgumentException";
+                exceptionMessage = "remaining() < size < needed";
+                goto exit;
+            }
+        }
+        if (mBuffer && tempData == nullptr) {
+            mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0);
+            tempData = (void*)(mDataBase + bufferOffset);
+        }
+        return tempData;
+    exit:
+        if (mArray) {
+            releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE);
+        }
+        if (exception) {
+            jniThrowException(mEnv, exceptionType, exceptionMessage);
+        }
+        return nullptr;
+    }
+
+    JNIEnv* mEnv;
+
+    // Java Buffer data
+    void* mData;
+    jobject mBuffer;
+
+    // Indirect Buffer Data
+    jarray mArray;
+    char* mDataBase;
+};
+
+class MeshUniformBuilder {
+public:
+    struct MeshUniform {
+        template <typename T>
+        std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=(
+                const T& val) {
+            if (!fVar) {
+                SkDEBUGFAIL("Assigning to missing variable");
+            } else if (sizeof(val) != fVar->sizeInBytes()) {
+                SkDEBUGFAIL("Incorrect value size");
+            } else {
+                memcpy(SkTAddOffset<void>(fOwner->writableUniformData(), fVar->offset), &val,
+                       szeof(val));
+            }
+        }
+
+        MeshUniform& operator=(const SkMatrix& val) {
+            if (!fVar) {
+                SkDEBUGFAIL("Assigning to missing variable");
+            } else if (fVar->sizeInBytes() != 9 * sizeof(float)) {
+                SkDEBUGFAIL("Incorrect value size");
+            } else {
+                float* data =
+                        SkTAddOffset<float>(fOwner->writableUniformData(), (ptrdiff_t)fVar->offset);
+                data[0] = val.get(0);
+                data[1] = val.get(3);
+                data[2] = val.get(6);
+                data[3] = val.get(1);
+                data[4] = val.get(4);
+                data[5] = val.get(7);
+                data[6] = val.get(2);
+                data[7] = val.get(5);
+                data[8] = val.get(8);
+            }
+            return *this;
+        }
+
+        template <typename T>
+        bool set(const T val[], const int count) {
+            static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable");
+            if (!fVar) {
+                SkDEBUGFAIL("Assigning to missing variable");
+                return false;
+            } else if (sizeof(T) * count != fVar->sizeInBytes()) {
+                SkDEBUGFAIL("Incorrect value size");
+                return false;
+            } else {
+                memcpy(SkTAddOffset<void>(fOwner->writableUniformData(), fVar->offset), val,
+                       sizeof(T) * count);
+            }
+            return true;
+        }
+
+        MeshUniformBuilder* fOwner;
+        const SkRuntimeEffect::Uniform* fVar;
+    };
+    MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; }
+
+    explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
+        fMeshSpec = sk_sp(meshSpec);
+    }
+
+    sk_sp<SkData> fUniforms;
+
+private:
+    void* writableUniformData() {
+        if (!fUniforms->unique()) {
+            fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size());
+        }
+        return fUniforms->writable_data();
+    }
+
+    sk_sp<SkMeshSpecification> fMeshSpec;
+};
+
+struct MeshWrapper {
+    SkMesh mesh;
+    MeshUniformBuilder builder;
+};
+#endif  // FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp
new file mode 100644
index 0000000..619a3ed
--- /dev/null
+++ b/libs/hwui/jni/MeshSpecification.cpp
@@ -0,0 +1,165 @@
+/*
+ * 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 <SkMesh.h>
+
+#include "GraphicsJNI.h"
+#include "graphics_jni_helpers.h"
+
+namespace android {
+
+using Attribute = SkMeshSpecification::Attribute;
+using Varying = SkMeshSpecification::Varying;
+
+static struct {
+    jclass clazz{};
+    jfieldID type{};
+    jfieldID offset{};
+    jfieldID name{};
+} gAttributeInfo;
+
+static struct {
+    jclass clazz{};
+    jfieldID type{};
+    jfieldID name{};
+} gVaryingInfo;
+
+std::vector<Attribute> extractAttributes(JNIEnv* env, jobjectArray attributes) {
+    int size = env->GetArrayLength(attributes);
+    std::vector<Attribute> attVector;
+    attVector.reserve(size);
+    for (int i = 0; i < size; i++) {
+        jobject attribute = env->GetObjectArrayElement(attributes, i);
+        auto name = (jstring)env->GetObjectField(attribute, gAttributeInfo.name);
+        auto attName = ScopedUtfChars(env, name);
+        Attribute temp{Attribute::Type(env->GetIntField(attribute, gAttributeInfo.type)),
+                       static_cast<size_t>(env->GetIntField(attribute, gAttributeInfo.offset)),
+                       SkString(attName.c_str())};
+        attVector.push_back(std::move(temp));
+    }
+    return attVector;
+}
+
+std::vector<Varying> extractVaryings(JNIEnv* env, jobjectArray varyings) {
+    int size = env->GetArrayLength(varyings);
+    std::vector<Varying> varyVector;
+    varyVector.reserve(size);
+    for (int i = 0; i < size; i++) {
+        jobject varying = env->GetObjectArrayElement(varyings, i);
+        auto name = (jstring)env->GetObjectField(varying, gVaryingInfo.name);
+        auto varyName = ScopedUtfChars(env, name);
+        Varying temp{Varying::Type(env->GetIntField(varying, gVaryingInfo.type)),
+                     SkString(varyName.c_str())};
+        varyVector.push_back(std::move(temp));
+    }
+
+    return varyVector;
+}
+
+static jlong Make(JNIEnv* env, jobject thiz, jobjectArray attributeArray, jint vertexStride,
+                  jobjectArray varyingArray, jstring vertexShader, jstring fragmentShader) {
+    auto attributes = extractAttributes(env, attributeArray);
+    auto varyings = extractVaryings(env, varyingArray);
+    auto skVertexShader = ScopedUtfChars(env, vertexShader);
+    auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
+    auto meshSpecResult = SkMeshSpecification::Make(attributes, vertexStride, varyings,
+                                                    SkString(skVertexShader.c_str()),
+                                                    SkString(skFragmentShader.c_str()));
+
+    if (meshSpecResult.specification.get() == nullptr) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str());
+    }
+
+    return reinterpret_cast<jlong>(meshSpecResult.specification.release());
+}
+
+static jlong MakeWithCS(JNIEnv* env, jobject thiz, jobjectArray attributeArray, jint vertexStride,
+                        jobjectArray varyingArray, jstring vertexShader, jstring fragmentShader,
+                        jlong colorSpace) {
+    auto attributes = extractAttributes(env, attributeArray);
+    auto varyings = extractVaryings(env, varyingArray);
+    auto skVertexShader = ScopedUtfChars(env, vertexShader);
+    auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
+    auto meshSpecResult = SkMeshSpecification::Make(
+            attributes, vertexStride, varyings, SkString(skVertexShader.c_str()),
+            SkString(skFragmentShader.c_str()), GraphicsJNI::getNativeColorSpace(colorSpace));
+
+    if (meshSpecResult.specification.get() == nullptr) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str());
+    }
+
+    return reinterpret_cast<jlong>(meshSpecResult.specification.release());
+}
+
+static jlong MakeWithAlpha(JNIEnv* env, jobject thiz, jobjectArray attributeArray,
+                           jint vertexStride, jobjectArray varyingArray, jstring vertexShader,
+                           jstring fragmentShader, jlong colorSpace, jint alphaType) {
+    auto attributes = extractAttributes(env, attributeArray);
+    auto varyings = extractVaryings(env, varyingArray);
+    auto skVertexShader = ScopedUtfChars(env, vertexShader);
+    auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
+    auto meshSpecResult = SkMeshSpecification::Make(
+            attributes, vertexStride, varyings, SkString(skVertexShader.c_str()),
+            SkString(skFragmentShader.c_str()), GraphicsJNI::getNativeColorSpace(colorSpace),
+            SkAlphaType(alphaType));
+
+    if (meshSpecResult.specification.get() == nullptr) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str());
+    }
+
+    return reinterpret_cast<jlong>(meshSpecResult.specification.release());
+}
+
+static void MeshSpecification_safeUnref(SkMeshSpecification* meshSpec) {
+    SkSafeUnref(meshSpec);
+}
+
+static jlong getMeshSpecificationFinalizer() {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshSpecification_safeUnref));
+}
+
+static const JNINativeMethod gMeshSpecificationMethods[] = {
+        {"nativeGetFinalizer", "()J", (void*)getMeshSpecificationFinalizer},
+        {"nativeMake",
+         "([Landroid/graphics/MeshSpecification$Attribute;I[Landroid/graphics/"
+         "MeshSpecification$Varying;"
+         "Ljava/lang/String;Ljava/lang/String;)J",
+         (void*)Make},
+        {"nativeMakeWithCS",
+         "([Landroid/graphics/MeshSpecification$Attribute;I"
+         "[Landroid/graphics/MeshSpecification$Varying;Ljava/lang/String;Ljava/lang/String;J)J",
+         (void*)MakeWithCS},
+        {"nativeMakeWithAlpha",
+         "([Landroid/graphics/MeshSpecification$Attribute;I"
+         "[Landroid/graphics/MeshSpecification$Varying;Ljava/lang/String;Ljava/lang/String;JI)J",
+         (void*)MakeWithAlpha}};
+
+int register_android_graphics_MeshSpecification(JNIEnv* env) {
+    android::RegisterMethodsOrDie(env, "android/graphics/MeshSpecification",
+                                  gMeshSpecificationMethods, NELEM(gMeshSpecificationMethods));
+
+    gAttributeInfo.clazz = env->FindClass("android/graphics/MeshSpecification$Attribute");
+    gAttributeInfo.type = env->GetFieldID(gAttributeInfo.clazz, "mType", "I");
+    gAttributeInfo.offset = env->GetFieldID(gAttributeInfo.clazz, "mOffset", "I");
+    gAttributeInfo.name = env->GetFieldID(gAttributeInfo.clazz, "mName", "Ljava/lang/String;");
+
+    gVaryingInfo.clazz = env->FindClass("android/graphics/MeshSpecification$Varying");
+    gVaryingInfo.type = env->GetFieldID(gVaryingInfo.clazz, "mType", "I");
+    gVaryingInfo.name = env->GetFieldID(gVaryingInfo.clazz, "mName", "Ljava/lang/String;");
+    return 0;
+}
+
+}  // namespace android
diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp
index 213f35a..f3db170 100644
--- a/libs/hwui/jni/RenderEffect.cpp
+++ b/libs/hwui/jni/RenderEffect.cpp
@@ -15,6 +15,7 @@
  */
 #include "Bitmap.h"
 #include "GraphicsJNI.h"
+#include "SkBlendMode.h"
 #include "SkImageFilter.h"
 #include "SkImageFilters.h"
 #include "graphics_jni_helpers.h"
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 0513447..8a4d4e1 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -21,6 +21,7 @@
 #else
 #define __ANDROID_API_P__ 28
 #endif
+#include <Mesh.h>
 #include <androidfw/ResourceTypes.h>
 #include <hwui/Canvas.h>
 #include <hwui/Paint.h>
@@ -30,8 +31,8 @@
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedStringChars.h>
 
-#include "FontUtils.h"
 #include "Bitmap.h"
+#include "FontUtils.h"
 #include "SkBitmap.h"
 #include "SkBlendMode.h"
 #include "SkClipOp.h"
@@ -42,10 +43,10 @@
 #include "SkMatrix.h"
 #include "SkPath.h"
 #include "SkPoint.h"
+#include "SkRRect.h"
 #include "SkRect.h"
 #include "SkRefCnt.h"
 #include "SkRegion.h"
-#include "SkRRect.h"
 #include "SkScalar.h"
 #include "SkVertices.h"
 
@@ -443,6 +444,14 @@
                            blendMode, *paint);
 }
 
+static void drawMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong meshHandle, jint modeHandle,
+                     jlong paintHandle) {
+    const SkMesh mesh = reinterpret_cast<MeshWrapper*>(meshHandle)->mesh;
+    SkBlendMode blendMode = static_cast<SkBlendMode>(modeHandle);
+    SkPaint* paint = reinterpret_cast<Paint*>(paintHandle);
+    get_canvas(canvasHandle)->drawMesh(mesh, SkBlender::Mode(blendMode), *paint);
+}
+
 static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
         jlong chunkHandle, jfloat left, jfloat top, jfloat right, jfloat bottom,
         jlong paintHandle, jint dstDensity, jint srcDensity) {
@@ -761,38 +770,38 @@
 // If called from Canvas these are regular JNI
 // If called from DisplayListCanvas they are @FastNative
 static const JNINativeMethod gDrawMethods[] = {
-    {"nDrawColor","(JII)V", (void*) CanvasJNI::drawColor},
-    {"nDrawColor","(JJJI)V", (void*) CanvasJNI::drawColorLong},
-    {"nDrawPaint","(JJ)V", (void*) CanvasJNI::drawPaint},
-    {"nDrawPoint", "(JFFJ)V", (void*) CanvasJNI::drawPoint},
-    {"nDrawPoints", "(J[FIIJ)V", (void*) CanvasJNI::drawPoints},
-    {"nDrawLine", "(JFFFFJ)V", (void*) CanvasJNI::drawLine},
-    {"nDrawLines", "(J[FIIJ)V", (void*) CanvasJNI::drawLines},
-    {"nDrawRect","(JFFFFJ)V", (void*) CanvasJNI::drawRect},
-    {"nDrawRegion", "(JJJ)V", (void*) CanvasJNI::drawRegion },
-    {"nDrawRoundRect","(JFFFFFFJ)V", (void*) CanvasJNI::drawRoundRect},
-    {"nDrawDoubleRoundRect", "(JFFFFFFFFFFFFJ)V", (void*) CanvasJNI::drawDoubleRoundRectXY},
-    {"nDrawDoubleRoundRect", "(JFFFF[FFFFF[FJ)V", (void*) CanvasJNI::drawDoubleRoundRectRadii},
-    {"nDrawCircle","(JFFFJ)V", (void*) CanvasJNI::drawCircle},
-    {"nDrawOval","(JFFFFJ)V", (void*) CanvasJNI::drawOval},
-    {"nDrawArc","(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc},
-    {"nDrawPath","(JJJ)V", (void*) CanvasJNI::drawPath},
-    {"nDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices},
-    {"nDrawNinePatch", "(JJJFFFFJII)V", (void*)CanvasJNI::drawNinePatch},
-    {"nDrawBitmapMatrix", "(JJJJ)V", (void*)CanvasJNI::drawBitmapMatrix},
-    {"nDrawBitmapMesh", "(JJII[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
-    {"nDrawBitmap","(JJFFJIII)V", (void*) CanvasJNI::drawBitmap},
-    {"nDrawBitmap","(JJFFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect},
-    {"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
-    {"nDrawGlyphs", "(J[I[FIIIJJ)V", (void*)CanvasJNI::drawGlyphs},
-    {"nDrawText","(J[CIIFFIJ)V", (void*) CanvasJNI::drawTextChars},
-    {"nDrawText","(JLjava/lang/String;IIFFIJ)V", (void*) CanvasJNI::drawTextString},
-    {"nDrawTextRun","(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars},
-    {"nDrawTextRun","(JLjava/lang/String;IIIIFFZJ)V", (void*) CanvasJNI::drawTextRunString},
-    {"nDrawTextOnPath","(J[CIIJFFIJ)V", (void*) CanvasJNI::drawTextOnPathChars},
-    {"nDrawTextOnPath","(JLjava/lang/String;JFFIJ)V", (void*) CanvasJNI::drawTextOnPathString},
-    {"nPunchHole", "(JFFFFFFF)V", (void*) CanvasJNI::punchHole}
-};
+        {"nDrawColor", "(JII)V", (void*)CanvasJNI::drawColor},
+        {"nDrawColor", "(JJJI)V", (void*)CanvasJNI::drawColorLong},
+        {"nDrawPaint", "(JJ)V", (void*)CanvasJNI::drawPaint},
+        {"nDrawPoint", "(JFFJ)V", (void*)CanvasJNI::drawPoint},
+        {"nDrawPoints", "(J[FIIJ)V", (void*)CanvasJNI::drawPoints},
+        {"nDrawLine", "(JFFFFJ)V", (void*)CanvasJNI::drawLine},
+        {"nDrawLines", "(J[FIIJ)V", (void*)CanvasJNI::drawLines},
+        {"nDrawRect", "(JFFFFJ)V", (void*)CanvasJNI::drawRect},
+        {"nDrawRegion", "(JJJ)V", (void*)CanvasJNI::drawRegion},
+        {"nDrawRoundRect", "(JFFFFFFJ)V", (void*)CanvasJNI::drawRoundRect},
+        {"nDrawDoubleRoundRect", "(JFFFFFFFFFFFFJ)V", (void*)CanvasJNI::drawDoubleRoundRectXY},
+        {"nDrawDoubleRoundRect", "(JFFFF[FFFFF[FJ)V", (void*)CanvasJNI::drawDoubleRoundRectRadii},
+        {"nDrawCircle", "(JFFFJ)V", (void*)CanvasJNI::drawCircle},
+        {"nDrawOval", "(JFFFFJ)V", (void*)CanvasJNI::drawOval},
+        {"nDrawArc", "(JFFFFFFZJ)V", (void*)CanvasJNI::drawArc},
+        {"nDrawPath", "(JJJ)V", (void*)CanvasJNI::drawPath},
+        {"nDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices},
+        {"nDrawMesh", "(JJIJ)V", (void*)CanvasJNI::drawMesh},
+        {"nDrawNinePatch", "(JJJFFFFJII)V", (void*)CanvasJNI::drawNinePatch},
+        {"nDrawBitmapMatrix", "(JJJJ)V", (void*)CanvasJNI::drawBitmapMatrix},
+        {"nDrawBitmapMesh", "(JJII[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
+        {"nDrawBitmap", "(JJFFJIII)V", (void*)CanvasJNI::drawBitmap},
+        {"nDrawBitmap", "(JJFFFFFFFFJII)V", (void*)CanvasJNI::drawBitmapRect},
+        {"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
+        {"nDrawGlyphs", "(J[I[FIIIJJ)V", (void*)CanvasJNI::drawGlyphs},
+        {"nDrawText", "(J[CIIFFIJ)V", (void*)CanvasJNI::drawTextChars},
+        {"nDrawText", "(JLjava/lang/String;IIFFIJ)V", (void*)CanvasJNI::drawTextString},
+        {"nDrawTextRun", "(J[CIIIIFFZJJ)V", (void*)CanvasJNI::drawTextRunChars},
+        {"nDrawTextRun", "(JLjava/lang/String;IIIIFFZJ)V", (void*)CanvasJNI::drawTextRunString},
+        {"nDrawTextOnPath", "(J[CIIJFFIJ)V", (void*)CanvasJNI::drawTextOnPathChars},
+        {"nDrawTextOnPath", "(JLjava/lang/String;JFFIJ)V", (void*)CanvasJNI::drawTextOnPathString},
+        {"nPunchHole", "(JFFFFFFF)V", (void*)CanvasJNI::punchHole}};
 
 int register_android_graphics_Canvas(JNIEnv* env) {
     int ret = 0;
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index f603e23..0663121 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -860,6 +860,11 @@
     RenderProxy::setRtAnimationsEnabled(enabled);
 }
 
+static void android_view_ThreadedRenderer_notifyCallbackPending(JNIEnv*, jclass, jlong proxyPtr) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    proxy->notifyCallbackPending();
+}
+
 // Plumbs the display density down to DeviceInfo.
 static void android_view_ThreadedRenderer_setDisplayDensityDpi(JNIEnv*, jclass, jint densityDpi) {
     // Convert from dpi to density-independent pixels.
@@ -1037,6 +1042,8 @@
         {"nIsDrawingEnabled", "()Z", (void*)android_view_ThreadedRenderer_isDrawingEnabled},
         {"nSetRtAnimationsEnabled", "(Z)V",
          (void*)android_view_ThreadedRenderer_setRtAnimationsEnabled},
+        {"nNotifyCallbackPending", "(J)V",
+         (void*)android_view_ThreadedRenderer_notifyCallbackPending},
 };
 
 static JavaVM* mJvm = nullptr;
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 3ba5409..99f54c1 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -25,6 +25,7 @@
 #include "SkColorFilter.h"
 #include "SkRuntimeEffect.h"
 #include "SkSurface.h"
+#include "Tonemapper.h"
 #include "gl/GrGLTypes.h"
 #include "math/mat4.h"
 #include "system/graphics-base-v1.0.h"
@@ -76,37 +77,6 @@
              isIntegerAligned(dstDevRect.y()));
 }
 
-static sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader,
-                                                const shaders::LinearEffect& linearEffect,
-                                                float maxDisplayLuminance,
-                                                float currentDisplayLuminanceNits,
-                                                float maxLuminance) {
-    auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
-    auto [runtimeEffect, error] = SkRuntimeEffect::MakeForShader(std::move(shaderString));
-    if (!runtimeEffect) {
-        LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str());
-    }
-
-    SkRuntimeShaderBuilder effectBuilder(std::move(runtimeEffect));
-
-    effectBuilder.child("child") = std::move(shader);
-
-    const auto uniforms = shaders::buildLinearEffectUniforms(
-            linearEffect, mat4(), maxDisplayLuminance, currentDisplayLuminanceNits, maxLuminance);
-
-    for (const auto& uniform : uniforms) {
-        effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
-    }
-
-    return effectBuilder.makeShader();
-}
-
-static bool isHdrDataspace(ui::Dataspace dataspace) {
-    const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
-
-    return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
-}
-
 static void adjustCropForYUV(uint32_t format, int bufferWidth, int bufferHeight, SkRect* cropRect) {
     // Chroma channels of YUV420 images are subsampled we may need to shrink the crop region by
     // a whole texel on each side. Since skia still adds its own 0.5 inset, we apply an
@@ -215,31 +185,10 @@
             sampling = SkSamplingOptions(SkFilterMode::kLinear);
         }
 
-        const auto sourceDataspace = static_cast<ui::Dataspace>(
-                ColorSpaceToADataSpace(layerImage->colorSpace(), layerImage->colorType()));
-        const SkImageInfo& imageInfo = canvas->imageInfo();
-        const auto destinationDataspace = static_cast<ui::Dataspace>(
-                ColorSpaceToADataSpace(imageInfo.colorSpace(), imageInfo.colorType()));
-
-        if (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace)) {
-            const auto effect = shaders::LinearEffect{
-                    .inputDataspace = sourceDataspace,
-                    .outputDataspace = destinationDataspace,
-                    .undoPremultipliedAlpha = layerImage->alphaType() == kPremul_SkAlphaType,
-                    .fakeInputDataspace = destinationDataspace};
-            auto shader = layerImage->makeShader(sampling,
-                                                 SkMatrix::RectToRect(skiaSrcRect, skiaDestRect));
-            constexpr float kMaxDisplayBrightess = 1000.f;
-            constexpr float kCurrentDisplayBrightness = 500.f;
-            shader = createLinearEffectShader(std::move(shader), effect, kMaxDisplayBrightess,
-                                              kCurrentDisplayBrightness,
-                                              layer->getMaxLuminanceNits());
-            paint.setShader(shader);
-            canvas->drawRect(skiaDestRect, paint);
-        } else {
-            canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
-                                  constraint);
-        }
+        tonemapPaint(layerImage->imageInfo(), canvas->imageInfo(), layer->getMaxLuminanceNits(),
+                     paint);
+        canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
+                              constraint);
 
         canvas->restore();
         // restore the original matrix
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/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
index 6c390c3..c7582e7 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
@@ -18,6 +18,7 @@
 
 #include "SkiaUtils.h"
 
+#include <SkBlendMode.h>
 #include <SkCanvas.h>
 #include <SkDrawable.h>
 #include <SkMatrix.h>
diff --git a/libs/hwui/pipeline/skia/StretchMask.cpp b/libs/hwui/pipeline/skia/StretchMask.cpp
index 2dbeb3a..b169c92 100644
--- a/libs/hwui/pipeline/skia/StretchMask.cpp
+++ b/libs/hwui/pipeline/skia/StretchMask.cpp
@@ -14,8 +14,10 @@
  * limitations under the License.
  */
 #include "StretchMask.h"
-#include "SkSurface.h"
+
+#include "SkBlendMode.h"
 #include "SkCanvas.h"
+#include "SkSurface.h"
 #include "TransformCanvas.h"
 #include "SkiaDisplayList.h"
 
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index 3c7617d..e168a7b 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -33,6 +33,8 @@
 #include "thread/ThreadBase.h"
 #include "utils/TimeUtils.h"
 
+#include <SkBlendMode.h>
+
 namespace android {
 namespace uirenderer {
 namespace skiapipeline {
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 1d24e71..1c76884 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -98,7 +98,6 @@
     auto& cache = skiapipeline::ShaderCache::get();
     cache.initShaderDiskCache(identity, size);
     contextOptions->fPersistentCache = &cache;
-    contextOptions->fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting;
 }
 
 void CacheManager::trimMemory(TrimLevel mode) {
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index d09bc47..64839d0 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -71,16 +71,19 @@
 } /* namespace */
 
 CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent,
-                                     RenderNode* rootRenderNode, IContextFactory* contextFactory) {
+                                     RenderNode* rootRenderNode, IContextFactory* contextFactory,
+                                     int32_t uiThreadId, int32_t renderThreadId) {
     auto renderType = Properties::getRenderPipelineType();
 
     switch (renderType) {
         case RenderPipelineType::SkiaGL:
             return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
-                                     std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread));
+                                     std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread),
+                                     uiThreadId, renderThreadId);
         case RenderPipelineType::SkiaVulkan:
             return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
-                                     std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread));
+                                     std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread),
+                                     uiThreadId, renderThreadId);
         default:
             LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType);
             break;
@@ -110,7 +113,8 @@
 
 CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
                              IContextFactory* contextFactory,
-                             std::unique_ptr<IRenderPipeline> renderPipeline)
+                             std::unique_ptr<IRenderPipeline> renderPipeline, pid_t uiThreadId,
+                             pid_t renderThreadId)
         : mRenderThread(thread)
         , mGenerationID(0)
         , mOpaque(!translucent)
@@ -118,7 +122,8 @@
         , mJankTracker(&thread.globalProfileData())
         , mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos())
         , mContentDrawBounds(0, 0, 0, 0)
-        , mRenderPipeline(std::move(renderPipeline)) {
+        , mRenderPipeline(std::move(renderPipeline))
+        , mHintSessionWrapper(uiThreadId, renderThreadId) {
     mRenderThread.cacheManager().registerCanvasContext(this);
     rootRenderNode->makeRoot();
     mRenderNodes.emplace_back(rootRenderNode);
@@ -472,16 +477,22 @@
     mRenderThread.pushBackFrameCallback(this);
 }
 
-std::optional<nsecs_t> CanvasContext::draw() {
+void CanvasContext::draw() {
     if (auto grContext = getGrContext()) {
         if (grContext->abandoned()) {
             LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw");
-            return std::nullopt;
+            return;
         }
     }
     SkRect dirty;
     mDamageAccumulator.finish(&dirty);
 
+    // reset syncDelayDuration each time we draw
+    nsecs_t syncDelayDuration = mSyncDelayDuration;
+    nsecs_t idleDuration = mIdleDuration;
+    mSyncDelayDuration = 0;
+    mIdleDuration = 0;
+
     if (!Properties::isDrawingEnabled() ||
         (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw())) {
         mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
@@ -498,7 +509,7 @@
             std::invoke(func, false /* didProduceBuffer */);
         }
         mFrameCommitCallbacks.clear();
-        return std::nullopt;
+        return;
     }
 
     ScopedActiveContext activeContext(this);
@@ -650,10 +661,25 @@
         }
     }
 
+    int64_t intendedVsync = mCurrentFrameInfo->get(FrameInfoIndex::IntendedVsync);
+    int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline);
+    int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
+
+    mHintSessionWrapper.updateTargetWorkDuration(frameDeadline - intendedVsync);
+
+    if (didDraw) {
+        int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+        int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
+        int64_t actualDuration = frameDuration -
+                                 (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
+                                 dequeueBufferDuration - idleDuration;
+        mHintSessionWrapper.reportActualWorkDuration(actualDuration);
+    }
+
+    mLastDequeueBufferDuration = dequeueBufferDuration;
+
     mRenderThread.cacheManager().onFrameCompleted();
-    return didDraw ? std::make_optional(
-                             mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration))
-                   : std::nullopt;
+    return;
 }
 
 void CanvasContext::reportMetricsWithPresentTime() {
@@ -766,6 +792,8 @@
 // Called by choreographer to do an RT-driven animation
 void CanvasContext::doFrame() {
     if (!mRenderPipeline->isSurfaceReady()) return;
+    mIdleDuration =
+            systemTime(SYSTEM_TIME_MONOTONIC) - mRenderThread.timeLord().computeFrameTimeNanos();
     prepareAndDraw(nullptr);
 }
 
@@ -974,6 +1002,14 @@
     }
 }
 
+void CanvasContext::sendLoadResetHint() {
+    mHintSessionWrapper.sendLoadResetHint();
+}
+
+void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
+    mSyncDelayDuration = duration;
+}
+
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index db96cfb..e875c42 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -16,22 +16,6 @@
 
 #pragma once
 
-#include "DamageAccumulator.h"
-#include "FrameInfo.h"
-#include "FrameInfoVisualizer.h"
-#include "FrameMetricsReporter.h"
-#include "IContextFactory.h"
-#include "IRenderPipeline.h"
-#include "JankTracker.h"
-#include "LayerUpdateQueue.h"
-#include "Lighting.h"
-#include "ReliableSurface.h"
-#include "RenderNode.h"
-#include "renderthread/RenderTask.h"
-#include "renderthread/RenderThread.h"
-#include "utils/RingBuffer.h"
-#include "ColorMode.h"
-
 #include <SkBitmap.h>
 #include <SkRect.h>
 #include <SkSize.h>
@@ -46,6 +30,23 @@
 #include <utility>
 #include <vector>
 
+#include "ColorMode.h"
+#include "DamageAccumulator.h"
+#include "FrameInfo.h"
+#include "FrameInfoVisualizer.h"
+#include "FrameMetricsReporter.h"
+#include "HintSessionWrapper.h"
+#include "IContextFactory.h"
+#include "IRenderPipeline.h"
+#include "JankTracker.h"
+#include "LayerUpdateQueue.h"
+#include "Lighting.h"
+#include "ReliableSurface.h"
+#include "RenderNode.h"
+#include "renderthread/RenderTask.h"
+#include "renderthread/RenderThread.h"
+#include "utils/RingBuffer.h"
+
 namespace android {
 namespace uirenderer {
 
@@ -66,7 +67,8 @@
 class CanvasContext : public IFrameCallback {
 public:
     static CanvasContext* create(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
-                                 IContextFactory* contextFactory);
+                                 IContextFactory* contextFactory, pid_t uiThreadId,
+                                 pid_t renderThreadId);
     virtual ~CanvasContext();
 
     /**
@@ -138,7 +140,7 @@
     bool makeCurrent();
     void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued, RenderNode* target);
     // Returns the DequeueBufferDuration.
-    std::optional<nsecs_t> draw();
+    void draw();
     void destroy();
 
     // IFrameCallback, Choreographer-driven frame callback entry point
@@ -214,9 +216,14 @@
 
     static CanvasContext* getActiveContext();
 
+    void sendLoadResetHint();
+
+    void setSyncDelayDuration(nsecs_t duration);
+
 private:
     CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
-                  IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline);
+                  IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline,
+                  pid_t uiThreadId, pid_t renderThreadId);
 
     friend class RegisterFrameCallbackTask;
     // TODO: Replace with something better for layer & other GL object
@@ -330,6 +337,11 @@
 
     std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback;
     std::function<void()> mPrepareSurfaceControlForWebviewCallback;
+
+    HintSessionWrapper mHintSessionWrapper;
+    nsecs_t mLastDequeueBufferDuration = 0;
+    nsecs_t mSyncDelayDuration = 0;
+    nsecs_t mIdleDuration = 0;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index dc7676c..1cc82fd 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -16,7 +16,6 @@
 
 #include "DrawFrameTask.h"
 
-#include <dlfcn.h>
 #include <gui/TraceUtils.h>
 #include <utils/Log.h>
 
@@ -28,64 +27,11 @@
 #include "../RenderNode.h"
 #include "CanvasContext.h"
 #include "RenderThread.h"
-#include "thread/CommonPool.h"
-#include "utils/TimeUtils.h"
 
 namespace android {
 namespace uirenderer {
 namespace renderthread {
 
-namespace {
-
-typedef APerformanceHintManager* (*APH_getManager)();
-typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
-                                                      size_t, int64_t);
-typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
-typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
-typedef void (*APH_closeSession)(APerformanceHintSession* session);
-
-bool gAPerformanceHintBindingInitialized = false;
-APH_getManager gAPH_getManagerFn = nullptr;
-APH_createSession gAPH_createSessionFn = nullptr;
-APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
-APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
-APH_closeSession gAPH_closeSessionFn = nullptr;
-
-void ensureAPerformanceHintBindingInitialized() {
-    if (gAPerformanceHintBindingInitialized) return;
-
-    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
-    LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!");
-
-    gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
-    LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr,
-                        "Failed to find required symbol APerformanceHint_getManager!");
-
-    gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
-    LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
-                        "Failed to find required symbol APerformanceHint_createSession!");
-
-    gAPH_updateTargetWorkDurationFn = (APH_updateTargetWorkDuration)dlsym(
-            handle_, "APerformanceHint_updateTargetWorkDuration");
-    LOG_ALWAYS_FATAL_IF(
-            gAPH_updateTargetWorkDurationFn == nullptr,
-            "Failed to find required symbol APerformanceHint_updateTargetWorkDuration!");
-
-    gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym(
-            handle_, "APerformanceHint_reportActualWorkDuration");
-    LOG_ALWAYS_FATAL_IF(
-            gAPH_reportActualWorkDurationFn == nullptr,
-            "Failed to find required symbol APerformanceHint_reportActualWorkDuration!");
-
-    gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
-    LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
-                        "Failed to find required symbol APerformanceHint_closeSession!");
-
-    gAPerformanceHintBindingInitialized = true;
-}
-
-}  // namespace
-
 DrawFrameTask::DrawFrameTask()
         : mRenderThread(nullptr)
         , mContext(nullptr)
@@ -94,13 +40,11 @@
 
 DrawFrameTask::~DrawFrameTask() {}
 
-void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode,
-                               int32_t uiThreadId, int32_t renderThreadId) {
+void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context,
+                               RenderNode* targetNode) {
     mRenderThread = thread;
     mContext = context;
     mTargetNode = targetNode;
-    mUiThreadId = uiThreadId;
-    mRenderThreadId = renderThreadId;
 }
 
 void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -144,11 +88,11 @@
 void DrawFrameTask::run() {
     const int64_t vsyncId = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameTimelineVsyncId)];
     ATRACE_FORMAT("DrawFrames %" PRId64, vsyncId);
-    nsecs_t syncDelayDuration = systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued;
+
+    mContext->setSyncDelayDuration(systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued);
 
     bool canUnblockUiThread;
     bool canDrawThisFrame;
-    bool didDraw = false;
     {
         TreeInfo info(TreeInfo::MODE_FULL, *mContext);
         info.forceDrawFrame = mForceDrawFrame;
@@ -169,9 +113,6 @@
     std::function<void()> frameCompleteCallback = std::move(mFrameCompleteCallback);
     mFrameCallback = nullptr;
     mFrameCompleteCallback = nullptr;
-    int64_t intendedVsync = mFrameInfo[static_cast<int>(FrameInfoIndex::IntendedVsync)];
-    int64_t frameDeadline = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameDeadline)];
-    int64_t frameStartTime = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameStartTime)];
 
     // From this point on anything in "this" is *UNSAFE TO ACCESS*
     if (canUnblockUiThread) {
@@ -182,18 +123,15 @@
     if (CC_UNLIKELY(frameCallback)) {
         context->enqueueFrameWork([frameCallback, context, syncResult = mSyncResult,
                                    frameNr = context->getFrameNumber()]() {
-            auto frameCommitCallback = std::move(frameCallback(syncResult, frameNr));
+            auto frameCommitCallback = frameCallback(syncResult, frameNr);
             if (frameCommitCallback) {
                 context->addFrameCommitListener(std::move(frameCommitCallback));
             }
         });
     }
 
-    nsecs_t dequeueBufferDuration = 0;
     if (CC_LIKELY(canDrawThisFrame)) {
-        std::optional<nsecs_t> drawResult = context->draw();
-        didDraw = drawResult.has_value();
-        dequeueBufferDuration = drawResult.value_or(0);
+        context->draw();
     } 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.
@@ -212,31 +150,6 @@
     if (!canUnblockUiThread) {
         unblockUiThread();
     }
-
-    if (!mHintSessionWrapper) mHintSessionWrapper.emplace(mUiThreadId, mRenderThreadId);
-
-    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 &&
-        targetWorkDuration < kSanityCheckUpperBound &&
-        targetWorkDuration != mLastTargetWorkDuration) {
-        mLastTargetWorkDuration = targetWorkDuration;
-        mHintSessionWrapper->updateTargetWorkDuration(targetWorkDuration);
-    }
-
-    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;
 }
 
 bool DrawFrameTask::syncFrameState(TreeInfo& info) {
@@ -289,44 +202,6 @@
     mSignal.signal();
 }
 
-DrawFrameTask::HintSessionWrapper::HintSessionWrapper(int32_t uiThreadId, int32_t renderThreadId) {
-    if (!Properties::useHintManager) return;
-    if (uiThreadId < 0 || renderThreadId < 0) return;
-
-    ensureAPerformanceHintBindingInitialized();
-
-    APerformanceHintManager* manager = gAPH_getManagerFn();
-    if (!manager) return;
-
-    std::vector<int32_t> tids = CommonPool::getThreadIds();
-    tids.push_back(uiThreadId);
-    tids.push_back(renderThreadId);
-
-    // DrawFrameTask code will always set a target duration before reporting actual durations.
-    // So this is just a placeholder value that's never used.
-    int64_t dummyTargetDurationNanos = 16666667;
-    mHintSession =
-            gAPH_createSessionFn(manager, tids.data(), tids.size(), dummyTargetDurationNanos);
-}
-
-DrawFrameTask::HintSessionWrapper::~HintSessionWrapper() {
-    if (mHintSession) {
-        gAPH_closeSessionFn(mHintSession);
-    }
-}
-
-void DrawFrameTask::HintSessionWrapper::updateTargetWorkDuration(long targetDurationNanos) {
-    if (mHintSession) {
-        gAPH_updateTargetWorkDurationFn(mHintSession, targetDurationNanos);
-    }
-}
-
-void DrawFrameTask::HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {
-    if (mHintSession) {
-        gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
-    }
-}
-
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index d6fc292..fafab24 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -16,7 +16,6 @@
 #ifndef DRAWFRAMETASK_H
 #define DRAWFRAMETASK_H
 
-#include <android/performance_hint.h>
 #include <utils/Condition.h>
 #include <utils/Mutex.h>
 #include <utils/StrongPointer.h>
@@ -61,8 +60,7 @@
     DrawFrameTask();
     virtual ~DrawFrameTask();
 
-    void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode,
-                    int32_t uiThreadId, int32_t renderThreadId);
+    void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode);
     void setContentDrawBounds(int left, int top, int right, int bottom) {
         mContentDrawBounds.set(left, top, right, bottom);
     }
@@ -91,18 +89,6 @@
     void forceDrawNextFrame() { mForceDrawFrame = true; }
 
 private:
-    class HintSessionWrapper {
-    public:
-        HintSessionWrapper(int32_t uiThreadId, int32_t renderThreadId);
-        ~HintSessionWrapper();
-
-        void updateTargetWorkDuration(long targetDurationNanos);
-        void reportActualWorkDuration(long actualDurationNanos);
-
-    private:
-        APerformanceHintSession* mHintSession = nullptr;
-    };
-
     void postAndWait();
     bool syncFrameState(TreeInfo& info);
     void unblockUiThread();
@@ -113,8 +99,6 @@
     RenderThread* mRenderThread;
     CanvasContext* mContext;
     RenderNode* mTargetNode = nullptr;
-    int32_t mUiThreadId = -1;
-    int32_t mRenderThreadId = -1;
     Rect mContentDrawBounds;
 
     /*********************************************
@@ -131,10 +115,6 @@
     std::function<void(bool)> mFrameCommitCallback;
     std::function<void()> mFrameCompleteCallback;
 
-    nsecs_t mLastDequeueBufferDuration = 0;
-    nsecs_t mLastTargetWorkDuration = 0;
-    std::optional<HintSessionWrapper> mHintSessionWrapper;
-
     bool mForceDrawFrame = false;
 };
 
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
new file mode 100644
index 0000000..edacef0
--- /dev/null
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -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.
+ */
+
+#include "HintSessionWrapper.h"
+
+#include <dlfcn.h>
+#include <utils/Log.h>
+
+#include <vector>
+
+#include "../Properties.h"
+#include "thread/CommonPool.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+namespace {
+
+typedef APerformanceHintManager* (*APH_getManager)();
+typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
+                                                      size_t, int64_t);
+typedef void (*APH_closeSession)(APerformanceHintSession* session);
+typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
+typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
+typedef void (*APH_sendHint)(APerformanceHintSession* session, int32_t);
+
+bool gAPerformanceHintBindingInitialized = false;
+APH_getManager gAPH_getManagerFn = nullptr;
+APH_createSession gAPH_createSessionFn = nullptr;
+APH_closeSession gAPH_closeSessionFn = nullptr;
+APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
+APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
+APH_sendHint gAPH_sendHintFn = nullptr;
+
+void ensureAPerformanceHintBindingInitialized() {
+    if (gAPerformanceHintBindingInitialized) return;
+
+    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+    LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!");
+
+    gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
+    LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr,
+                        "Failed to find required symbol APerformanceHint_getManager!");
+
+    gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
+    LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
+                        "Failed to find required symbol APerformanceHint_createSession!");
+
+    gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
+    LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
+                        "Failed to find required symbol APerformanceHint_closeSession!");
+
+    gAPH_updateTargetWorkDurationFn = (APH_updateTargetWorkDuration)dlsym(
+            handle_, "APerformanceHint_updateTargetWorkDuration");
+    LOG_ALWAYS_FATAL_IF(
+            gAPH_updateTargetWorkDurationFn == nullptr,
+            "Failed to find required symbol APerformanceHint_updateTargetWorkDuration!");
+
+    gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym(
+            handle_, "APerformanceHint_reportActualWorkDuration");
+    LOG_ALWAYS_FATAL_IF(
+            gAPH_reportActualWorkDurationFn == nullptr,
+            "Failed to find required symbol APerformanceHint_reportActualWorkDuration!");
+
+    gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint");
+    LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr,
+                        "Failed to find required symbol APerformanceHint_sendHint!");
+
+    gAPerformanceHintBindingInitialized = true;
+}
+
+}  // namespace
+
+HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId)
+        : mUiThreadId(uiThreadId), mRenderThreadId(renderThreadId) {}
+
+HintSessionWrapper::~HintSessionWrapper() {
+    if (mHintSession) {
+        gAPH_closeSessionFn(mHintSession);
+    }
+}
+
+bool HintSessionWrapper::useHintSession() {
+    if (!Properties::useHintManager || !Properties::isDrawingEnabled()) return false;
+    if (mHintSession) return true;
+    // If session does not exist, create it;
+    // this defers session creation until we try to actually use it.
+    if (!mSessionValid) return false;
+    return init();
+}
+
+bool HintSessionWrapper::init() {
+    if (mUiThreadId < 0 || mRenderThreadId < 0) return false;
+
+    // Assume that if we return before the end, it broke
+    mSessionValid = false;
+
+    ensureAPerformanceHintBindingInitialized();
+
+    APerformanceHintManager* manager = gAPH_getManagerFn();
+    if (!manager) return false;
+
+    std::vector<pid_t> tids = CommonPool::getThreadIds();
+    tids.push_back(mUiThreadId);
+    tids.push_back(mRenderThreadId);
+
+    // Use a placeholder target value to initialize,
+    // this will always be replaced elsewhere before it gets used
+    int64_t defaultTargetDurationNanos = 16666667;
+    mHintSession =
+            gAPH_createSessionFn(manager, tids.data(), tids.size(), defaultTargetDurationNanos);
+
+    mSessionValid = !!mHintSession;
+    return mSessionValid;
+}
+
+void HintSessionWrapper::updateTargetWorkDuration(long targetWorkDurationNanos) {
+    if (!useHintSession()) return;
+    targetWorkDurationNanos = targetWorkDurationNanos * Properties::targetCpuTimePercentage / 100;
+    if (targetWorkDurationNanos != mLastTargetWorkDuration &&
+        targetWorkDurationNanos > kSanityCheckLowerBound &&
+        targetWorkDurationNanos < kSanityCheckUpperBound) {
+        mLastTargetWorkDuration = targetWorkDurationNanos;
+        gAPH_updateTargetWorkDurationFn(mHintSession, targetWorkDurationNanos);
+    }
+    mLastFrameNotification = systemTime();
+}
+
+void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {
+    if (!useHintSession()) return;
+    if (actualDurationNanos > kSanityCheckLowerBound &&
+        actualDurationNanos < kSanityCheckUpperBound) {
+        gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
+    }
+}
+
+void HintSessionWrapper::sendLoadResetHint() {
+    if (!useHintSession()) return;
+    nsecs_t now = systemTime();
+    if (now - mLastFrameNotification > kResetHintTimeout) {
+        gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_RESET));
+    }
+    mLastFrameNotification = now;
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
new file mode 100644
index 0000000..fcbc101
--- /dev/null
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -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.
+ */
+
+#pragma once
+
+#include <android/performance_hint.h>
+
+#include "utils/TimeUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+namespace renderthread {
+
+class HintSessionWrapper {
+public:
+    HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId);
+    ~HintSessionWrapper();
+
+    void updateTargetWorkDuration(long targetDurationNanos);
+    void reportActualWorkDuration(long actualDurationNanos);
+    void sendLoadResetHint();
+
+private:
+    bool useHintSession();
+    bool init();
+    APerformanceHintSession* mHintSession = nullptr;
+
+    nsecs_t mLastFrameNotification = 0;
+    nsecs_t mLastTargetWorkDuration = 0;
+
+    pid_t mUiThreadId;
+    pid_t mRenderThreadId;
+
+    bool mSessionValid = true;
+
+    static constexpr nsecs_t kResetHintTimeout = 100_ms;
+    static constexpr int64_t kSanityCheckLowerBound = 100_us;
+    static constexpr int64_t kSanityCheckUpperBound = 10_s;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 3324715..07f5a78 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -42,11 +42,13 @@
 RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode,
                          IContextFactory* contextFactory)
         : mRenderThread(RenderThread::getInstance()), mContext(nullptr) {
-    mContext = mRenderThread.queue().runSync([&]() -> CanvasContext* {
-        return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory);
+    pid_t uiThreadId = pthread_gettid_np(pthread_self());
+    pid_t renderThreadId = getRenderThreadTid();
+    mContext = mRenderThread.queue().runSync([=, this]() -> CanvasContext* {
+        return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory,
+                                     uiThreadId, renderThreadId);
     });
-    mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode,
-                              pthread_gettid_np(pthread_self()), getRenderThreadTid());
+    mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
 }
 
 RenderProxy::~RenderProxy() {
@@ -55,7 +57,7 @@
 
 void RenderProxy::destroyContext() {
     if (mContext) {
-        mDrawFrameTask.setContext(nullptr, nullptr, nullptr, -1, -1);
+        mDrawFrameTask.setContext(nullptr, nullptr, nullptr);
         // This is also a fence as we need to be certain that there are no
         // outstanding mDrawFrame tasks posted before it is destroyed
         mRenderThread.queue().runSync([this]() { delete mContext; });
@@ -236,6 +238,10 @@
     mRenderThread.queue().post([this]() { mContext->notifyFramePending(); });
 }
 
+void RenderProxy::notifyCallbackPending() {
+    mRenderThread.queue().post([this]() { mContext->sendLoadResetHint(); });
+}
+
 void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) {
     mRenderThread.queue().runSync([&]() {
         std::lock_guard lock(mRenderThread.getJankDataMutex());
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 2a99a73..a21faa8 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -109,6 +109,7 @@
     static int maxTextureSize();
     void stopDrawing();
     void notifyFramePending();
+    void notifyCallbackPending();
 
     void dumpProfileInfo(int fd, int dumpFlags);
     // Not exported, only used for testing
diff --git a/libs/hwui/tests/common/CallCountingCanvas.h b/libs/hwui/tests/common/CallCountingCanvas.h
index d3c41191..dc36a2e 100644
--- a/libs/hwui/tests/common/CallCountingCanvas.h
+++ b/libs/hwui/tests/common/CallCountingCanvas.h
@@ -19,6 +19,8 @@
 #include <SkCanvasVirtualEnforcer.h>
 #include <SkNoDrawCanvas.h>
 
+enum class SkBlendMode;
+
 namespace android {
 namespace uirenderer {
 namespace test {
diff --git a/libs/hwui/tests/common/TestListViewSceneBase.cpp b/libs/hwui/tests/common/TestListViewSceneBase.cpp
index 43df4a0..e70d44c 100644
--- a/libs/hwui/tests/common/TestListViewSceneBase.cpp
+++ b/libs/hwui/tests/common/TestListViewSceneBase.cpp
@@ -19,6 +19,8 @@
 #include "TestContext.h"
 #include "TestUtils.h"
 
+#include <SkBlendMode.h>
+
 #include <utils/Color.h>
 
 namespace android {
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/common/scenes/BitmapFillrate.cpp b/libs/hwui/tests/common/scenes/BitmapFillrate.cpp
index 5af7d43..19e87f8 100644
--- a/libs/hwui/tests/common/scenes/BitmapFillrate.cpp
+++ b/libs/hwui/tests/common/scenes/BitmapFillrate.cpp
@@ -19,6 +19,7 @@
 #include "utils/Color.h"
 
 #include <SkBitmap.h>
+#include <SkBlendMode.h>
 
 using namespace android;
 using namespace android::uirenderer;
diff --git a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
index 2a016ac..3a1ea8c 100644
--- a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
@@ -16,6 +16,8 @@
 
 #include "TestSceneBase.h"
 
+#include <SkBlendMode.h>
+
 class ClippingAnimation;
 
 static TestScene::Registrar _RectGrid(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
index 4271d2f..484289a 100644
--- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -20,6 +20,8 @@
 #include <hwui/Paint.h>
 #include <minikin/Layout.h>
 
+#include <SkBlendMode.h>
+
 #include <cstdio>
 
 class GlyphStressAnimation;
diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
index 0d5ca6d..dfdd0d8 100644
--- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
+++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
@@ -17,6 +17,7 @@
 #include "TestSceneBase.h"
 #include "utils/Color.h"
 
+#include <SkBlendMode.h>
 #include <SkColorSpace.h>
 #include <SkGradientShader.h>
 #include <SkImagePriv.h>
diff --git a/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp b/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp
index cac2fb3..2955fb2 100644
--- a/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp
@@ -16,6 +16,8 @@
 
 #include "TestSceneBase.h"
 
+#include <SkBlendMode.h>
+
 class HwLayerAnimation;
 
 static TestScene::Registrar _HwLayer(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp b/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp
index 77a59df..8c9a614 100644
--- a/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp
@@ -16,6 +16,8 @@
 
 #include "TestSceneBase.h"
 
+#include <SkBlendMode.h>
+
 class HwLayerSizeAnimation;
 
 static TestScene::Registrar _HwLayerSize(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/JankyScene.cpp b/libs/hwui/tests/common/scenes/JankyScene.cpp
index f5e6b31..250b986 100644
--- a/libs/hwui/tests/common/scenes/JankyScene.cpp
+++ b/libs/hwui/tests/common/scenes/JankyScene.cpp
@@ -16,6 +16,8 @@
 
 #include "TestSceneBase.h"
 
+#include <SkBlendMode.h>
+
 #include <unistd.h>
 
 class JankyScene;
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index 5eaf185..f669dbc 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -17,6 +17,7 @@
 #include "TestSceneBase.h"
 #include "tests/common/TestListViewSceneBase.h"
 #include "hwui/Paint.h"
+#include <SkBlendMode.h>
 #include <SkGradientShader.h>
 
 class ListOfFadedTextAnimation;
diff --git a/libs/hwui/tests/common/scenes/OvalAnimation.cpp b/libs/hwui/tests/common/scenes/OvalAnimation.cpp
index 402c1ec..1a2af83 100644
--- a/libs/hwui/tests/common/scenes/OvalAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/OvalAnimation.cpp
@@ -17,6 +17,8 @@
 #include "TestSceneBase.h"
 #include "utils/Color.h"
 
+#include <SkBlendMode.h>
+
 class OvalAnimation;
 
 static TestScene::Registrar _Oval(TestScene::Info{"oval", "Draws 1 oval.",
diff --git a/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp b/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp
index fb1b000..25cf4d6 100644
--- a/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp
@@ -16,6 +16,8 @@
 
 #include "TestSceneBase.h"
 
+#include <SkBlendMode.h>
+
 class PartialDamageAnimation;
 
 static TestScene::Registrar _PartialDamage(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp
index 1e343c1..969514c 100644
--- a/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp
@@ -16,6 +16,8 @@
 
 #include <vector>
 
+#include <SkBlendMode.h>
+
 #include "TestSceneBase.h"
 
 class PathClippingAnimation : public TestScene {
diff --git a/libs/hwui/tests/common/scenes/RectGridAnimation.cpp b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp
index f37bcbc..99e7858 100644
--- a/libs/hwui/tests/common/scenes/RectGridAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp
@@ -16,6 +16,8 @@
 
 #include "TestSceneBase.h"
 
+#include <SkBlendMode.h>
+
 class RectGridAnimation;
 
 static TestScene::Registrar _RectGrid(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
index e9f353d..2c27969 100644
--- a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
@@ -16,6 +16,8 @@
 
 #include "TestSceneBase.h"
 
+#include <SkBlendMode.h>
+
 #include <vector>
 
 class RoundRectClippingAnimation : public TestScene {
diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
index 252f539..ee30c13 100644
--- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
@@ -16,6 +16,7 @@
 
 #include <hwui/Paint.h>
 #include <minikin/Layout.h>
+#include <SkBlendMode.h>
 #include <string>
 #include "TestSceneBase.h"
 
diff --git a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
index 31a8ae1..d5060c7 100644
--- a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
@@ -16,6 +16,8 @@
 
 #include "TestSceneBase.h"
 
+#include <SkBlendMode.h>
+
 class SaveLayerAnimation;
 
 static TestScene::Registrar _SaveLayer(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp b/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp
index c13e80e..827ddab 100644
--- a/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp
+++ b/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp
@@ -16,6 +16,8 @@
 
 #include "TestSceneBase.h"
 
+#include <SkBlendMode.h>
+
 class ShadowGrid2Animation;
 
 static TestScene::Registrar _ShadowGrid2(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp b/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp
index 772b98e..a4fb10c 100644
--- a/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp
@@ -16,6 +16,8 @@
 
 #include "TestSceneBase.h"
 
+#include <SkBlendMode.h>
+
 class ShadowGridAnimation;
 
 static TestScene::Registrar _ShadowGrid(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp b/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp
index 0019da5..58c0372 100644
--- a/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp
@@ -16,6 +16,8 @@
 
 #include "TestSceneBase.h"
 
+#include <SkBlendMode.h>
+
 class ShadowShaderAnimation;
 
 static TestScene::Registrar _ShadowShader(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
index 70a1557d..c0c3dfd 100644
--- a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
@@ -17,6 +17,8 @@
 #include "TestSceneBase.h"
 #include "utils/Color.h"
 
+#include <SkBlendMode.h>
+
 #include <cstdio>
 
 class ShapeAnimation;
diff --git a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
index 2aeb42c..40f2ed0 100644
--- a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
@@ -16,6 +16,7 @@
 
 #include "TestSceneBase.h"
 
+#include <SkBlendMode.h>
 #include <SkColorFilter.h>
 #include <SkColorMatrix.h>
 #include <SkGradientShader.h>
diff --git a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
index 57a260c..a9e7a34 100644
--- a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
@@ -16,6 +16,7 @@
 
 #include "TestSceneBase.h"
 
+#include <SkBlendMode.h>
 #include <SkGradientShader.h>
 
 class SimpleGradientAnimation;
diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
index 7d3ca96..bb95490 100644
--- a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <SkBitmap.h>
+#include <SkBlendMode.h>
 #include <SkCanvas.h>
 #include <SkColor.h>
 #include <SkFont.h>
diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp
index d3090367..78146b8 100644
--- a/libs/hwui/tests/common/scenes/TextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp
@@ -17,6 +17,8 @@
 #include "TestSceneBase.h"
 #include "hwui/Paint.h"
 
+#include <SkBlendMode.h>
+
 class TextAnimation;
 
 static TestScene::Registrar _Text(TestScene::Info{"text", "Draws a bunch of text.",
diff --git a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
index 9cd1075..a55b725 100644
--- a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
+++ b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
@@ -22,6 +22,8 @@
 #include "pipeline/skia/SkiaDisplayList.h"
 #include "tests/common/TestUtils.h"
 
+#include <SkBlendMode.h>
+
 using namespace android;
 using namespace android::uirenderer;
 using namespace android::uirenderer::skiapipeline;
diff --git a/libs/hwui/tests/microbench/RenderNodeBench.cpp b/libs/hwui/tests/microbench/RenderNodeBench.cpp
index 6aed251..72946c4 100644
--- a/libs/hwui/tests/microbench/RenderNodeBench.cpp
+++ b/libs/hwui/tests/microbench/RenderNodeBench.cpp
@@ -19,6 +19,8 @@
 #include "hwui/Canvas.h"
 #include "RenderNode.h"
 
+#include <SkBlendMode.h>
+
 using namespace android;
 using namespace android::uirenderer;
 
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/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp
index df06ead..508e198 100644
--- a/libs/hwui/tests/unit/CacheManagerTests.cpp
+++ b/libs/hwui/tests/unit/CacheManagerTests.cpp
@@ -32,7 +32,8 @@
     return cacheUsage;
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, trimMemory) {
+// TOOD(258700630): fix this test and re-enable
+RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) {
     int32_t width = DeviceInfo::get()->getWidth();
     int32_t height = DeviceInfo::get()->getHeight();
     GrDirectContext* grContext = renderThread.getGrContext();
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
index 1771c35..88420a5 100644
--- a/libs/hwui/tests/unit/CanvasContextTests.cpp
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -36,7 +36,7 @@
     auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
-            CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+            CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
 
     ASSERT_FALSE(canvasContext->hasSurface());
 
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index d2b1ef9..1f6edf3 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -23,6 +23,7 @@
 
 #include <tests/common/CallCountingCanvas.h>
 
+#include "SkBlendMode.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
 #include "SkColor.h"
diff --git a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
index 098b4cc..92fd829 100644
--- a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
+++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
+#include <android-base/macros.h>
 #include <gtest/gtest.h>
-
-#include "protos/graphicsstats.pb.h"
-#include "service/GraphicsStatsService.h"
-
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 
+#include "protos/graphicsstats.pb.h"
+#include "service/GraphicsStatsService.h"
+
 using namespace android;
 using namespace android::uirenderer;
 
@@ -49,11 +49,7 @@
 
 // No code left untested
 TEST(GraphicsStats, findRootPath) {
-#ifdef __LP64__
-    std::string expected = "/data/nativetest64/hwui_unit_tests";
-#else
-    std::string expected = "/data/nativetest/hwui_unit_tests";
-#endif
+    std::string expected = "/data/local/tmp/nativetest/hwui_unit_tests/" ABI_STRING;
     EXPECT_EQ(expected, findRootPath());
 }
 
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index ec949b8..596bd37 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -17,6 +17,7 @@
 #include <VectorDrawable.h>
 #include <gtest/gtest.h>
 
+#include <SkBlendMode.h>
 #include <SkClipStack.h>
 #include <SkSurface_Base.h>
 #include <string.h>
@@ -334,7 +335,7 @@
             "A");
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
-            CanvasContext::create(renderThread, false, parent.get(), &contextFactory));
+            CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0));
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
     DamageAccumulator damageAccumulator;
     info.damageAccumulator = &damageAccumulator;
@@ -398,7 +399,7 @@
                                       "A");
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
-            CanvasContext::create(renderThread, false, parent.get(), &contextFactory));
+            CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0));
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
     DamageAccumulator damageAccumulator;
     info.damageAccumulator = &damageAccumulator;
@@ -518,7 +519,7 @@
     // prepareTree is required to find, which receivers have backward projected nodes
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
-            CanvasContext::create(renderThread, false, parent.get(), &contextFactory));
+            CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0));
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
     DamageAccumulator damageAccumulator;
     info.damageAccumulator = &damageAccumulator;
@@ -618,7 +619,7 @@
     // prepareTree is required to find, which receivers have backward projected nodes
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
-            CanvasContext::create(renderThread, false, parent.get(), &contextFactory));
+            CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0));
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
     DamageAccumulator damageAccumulator;
     info.damageAccumulator = &damageAccumulator;
@@ -634,7 +635,7 @@
 static int drawNode(RenderThread& renderThread, const sp<RenderNode>& renderNode) {
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
-            CanvasContext::create(renderThread, false, renderNode.get(), &contextFactory));
+            CanvasContext::create(renderThread, false, renderNode.get(), &contextFactory, 0, 0));
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
     DamageAccumulator damageAccumulator;
     info.damageAccumulator = &damageAccumulator;
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 61bd646..80796f4 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -274,7 +274,7 @@
     auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
-            CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+            CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
     DamageAccumulator damageAccumulator;
     info.damageAccumulator = &damageAccumulator;
@@ -310,7 +310,7 @@
             });
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
-            CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+            CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
     canvasContext->setSurface(nullptr);
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
     DamageAccumulator damageAccumulator;
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 50d9f56..87c5216 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -17,10 +17,19 @@
 #include "tests/common/TestUtils.h"
 
 #include <hwui/Paint.h>
+#include <SkAlphaType.h>
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
 #include <SkCanvasStateUtils.h>
+#include <SkColor.h>
 #include <SkColorSpace.h>
+#include <SkColorType.h>
+#include <SkImageInfo.h>
 #include <SkPicture.h>
 #include <SkPictureRecorder.h>
+#include <SkRefCnt.h>
+#include <SkSurface.h>
 #include <gtest/gtest.h>
 
 using namespace android;
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 3d5aca4..f825d7c 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -142,7 +142,7 @@
     auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
-            CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+            CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
     TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
     DamageAccumulator damageAccumulator;
     info.damageAccumulator = &damageAccumulator;
@@ -201,7 +201,7 @@
     auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
-            CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+            CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
 
     // Set up a Surface so that we can position the VectorDrawable offscreen.
     test::TestContext testContext;
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 7419f8f..4d0595e 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -17,6 +17,7 @@
 #include <VectorDrawable.h>
 #include <gtest/gtest.h>
 
+#include <SkBlendMode.h>
 #include <SkClipStack.h>
 #include <SkSurface_Base.h>
 #include <string.h>
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index 94bcb11..f44f9d0 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -19,6 +19,7 @@
 #include <GLES2/gl2.h>
 #include <utils/Blur.h>
 
+#include <SkBlendMode.h>
 #include <SkColorFilter.h>
 #include <SkPaint.h>
 #include <SkShader.h>
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 0e7b7ff..a835167 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -199,8 +199,7 @@
     width = viewport.deviceWidth;
     height = viewport.deviceHeight;
 
-    if (viewport.orientation == DISPLAY_ORIENTATION_90 ||
-        viewport.orientation == DISPLAY_ORIENTATION_270) {
+    if (viewport.orientation == ui::ROTATION_90 || viewport.orientation == ui::ROTATION_270) {
         std::swap(width, height);
     }
 }
@@ -244,38 +243,42 @@
 
         // Undo the previous rotation.
         switch (oldViewport.orientation) {
-            case DISPLAY_ORIENTATION_90:
+            case ui::ROTATION_90:
                 temp = x;
                 x = oldViewport.deviceHeight - y;
                 y = temp;
                 break;
-            case DISPLAY_ORIENTATION_180:
+            case ui::ROTATION_180:
                 x = oldViewport.deviceWidth - x;
                 y = oldViewport.deviceHeight - y;
                 break;
-            case DISPLAY_ORIENTATION_270:
+            case ui::ROTATION_270:
                 temp = x;
                 x = y;
                 y = oldViewport.deviceWidth - temp;
                 break;
+            case ui::ROTATION_0:
+                break;
         }
 
         // Perform the new rotation.
         switch (viewport.orientation) {
-            case DISPLAY_ORIENTATION_90:
+            case ui::ROTATION_90:
                 temp = x;
                 x = y;
                 y = viewport.deviceHeight - temp;
                 break;
-            case DISPLAY_ORIENTATION_180:
+            case ui::ROTATION_180:
                 x = viewport.deviceWidth - x;
                 y = viewport.deviceHeight - y;
                 break;
-            case DISPLAY_ORIENTATION_270:
+            case ui::ROTATION_270:
                 temp = x;
                 x = viewport.deviceWidth - y;
                 y = temp;
                 break;
+            case ui::ROTATION_0:
+                break;
         }
 
         // Apply offsets to convert from the pixel center to the pixel top-left corner position
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 54f893e..099efd3 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -22,10 +22,18 @@
 #include <SkBlendMode.h>
 #include <SkCanvas.h>
 #include <SkColor.h>
+#include <android-base/stringprintf.h>
 #include <android-base/thread_annotations.h>
+#include <ftl/enum.h>
+
+#include <mutex>
 
 #include "PointerControllerContext.h"
 
+#define INDENT "  "
+#define INDENT2 "    "
+#define INDENT3 "      "
+
 namespace android {
 
 namespace {
@@ -223,7 +231,7 @@
 }
 
 void PointerController::clearSpotsLocked() {
-    for (auto& [displayID, spotController] : mLocked.spotControllers) {
+    for (auto& [displayId, spotController] : mLocked.spotControllers) {
         spotController.clearSpots();
     }
 }
@@ -235,7 +243,7 @@
 void PointerController::reloadPointerResources() {
     std::scoped_lock lock(getLock());
 
-    for (auto& [displayID, spotController] : mLocked.spotControllers) {
+    for (auto& [displayId, spotController] : mLocked.spotControllers) {
         spotController.reloadSpotResources();
     }
 
@@ -286,13 +294,13 @@
 
     std::scoped_lock lock(getLock());
     for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) {
-        int32_t displayID = it->first;
-        if (!displayIdSet.count(displayID)) {
+        int32_t displayId = it->first;
+        if (!displayIdSet.count(displayId)) {
             /*
              * Ensures that an in-progress animation won't dereference
              * a null pointer to TouchSpotController.
              */
-            mContext.removeAnimationCallback(displayID);
+            mContext.removeAnimationCallback(displayId);
             it = mLocked.spotControllers.erase(it);
         } else {
             ++it;
@@ -313,4 +321,20 @@
     return it != di.end() ? it->transform : kIdentityTransform;
 }
 
+void PointerController::dump(std::string& dump) {
+    dump += INDENT "PointerController:\n";
+    std::scoped_lock lock(getLock());
+    dump += StringPrintf(INDENT2 "Presentation: %s\n",
+                         ftl::enum_string(mLocked.presentation).c_str());
+    dump += StringPrintf(INDENT2 "Pointer Display ID: %" PRIu32 "\n", mLocked.pointerDisplayId);
+    dump += StringPrintf(INDENT2 "Viewports:\n");
+    for (const auto& info : mLocked.mDisplayInfos) {
+        info.dump(dump, INDENT3);
+    }
+    dump += INDENT2 "Spot Controllers:\n";
+    for (const auto& [_, spotController] : mLocked.spotControllers) {
+        spotController.dump(dump, INDENT3);
+    }
+}
+
 } // namespace android
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 33480e8..48d5a57 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -27,6 +27,7 @@
 
 #include <map>
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "MouseCursorController.h"
@@ -75,6 +76,8 @@
     void onDisplayInfosChangedLocked(const std::vector<gui::DisplayInfo>& displayInfos)
             REQUIRES(getLock());
 
+    void dump(std::string& dump);
+
 protected:
     using WindowListenerConsumer =
             std::function<void(const sp<android::gui::WindowInfosListener>&)>;
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index 4ac66c4..d9fe599 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -21,8 +21,15 @@
 
 #include "TouchSpotController.h"
 
+#include <android-base/stringprintf.h>
+#include <input/PrintTools.h>
 #include <log/log.h>
 
+#include <mutex>
+
+#define INDENT "  "
+#define INDENT2 "    "
+
 namespace {
 // Time to spend fading out the spot completely.
 const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
@@ -53,6 +60,12 @@
     }
 }
 
+void TouchSpotController::Spot::dump(std::string& out, const char* prefix) const {
+    out += prefix;
+    base::StringAppendF(&out, "Spot{id=%" PRIx32 ", alpha=%f, scale=%f, pos=[%f, %f]}\n", id, alpha,
+                        scale, x, y);
+}
+
 // --- TouchSpotController ---
 
 TouchSpotController::TouchSpotController(int32_t displayId, PointerControllerContext& context)
@@ -255,4 +268,22 @@
     mContext.addAnimationCallback(mDisplayId, func);
 }
 
+void TouchSpotController::dump(std::string& out, const char* prefix) const {
+    using base::StringAppendF;
+    out += prefix;
+    out += "SpotController:\n";
+    out += prefix;
+    StringAppendF(&out, INDENT "DisplayId: %" PRId32 "\n", mDisplayId);
+    std::scoped_lock lock(mLock);
+    out += prefix;
+    StringAppendF(&out, INDENT "Animating: %s\n", toString(mLocked.animating));
+    out += prefix;
+    out += INDENT "Spots:\n";
+    std::string spotPrefix = prefix;
+    spotPrefix += INDENT2;
+    for (const auto& spot : mLocked.displaySpots) {
+        spot->dump(out, spotPrefix.c_str());
+    }
+}
+
 } // namespace android
diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h
index 703de36..5bbc75d 100644
--- a/libs/input/TouchSpotController.h
+++ b/libs/input/TouchSpotController.h
@@ -38,6 +38,8 @@
     void reloadSpotResources();
     bool doAnimations(nsecs_t timestamp);
 
+    void dump(std::string& out, const char* prefix = "") const;
+
 private:
     struct Spot {
         static const uint32_t INVALID_ID = 0xffffffff;
@@ -58,6 +60,7 @@
                 mLastIcon(nullptr) {}
 
         void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId);
+        void dump(std::string& out, const char* prefix = "") const;
 
     private:
         const SpriteIcon* mLastIcon;
diff --git a/location/java/android/location/Country.java b/location/java/android/location/Country.java
index 8c40338..8e1bb1f0 100644
--- a/location/java/android/location/Country.java
+++ b/location/java/android/location/Country.java
@@ -16,6 +16,9 @@
 
 package android.location;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -28,7 +31,8 @@
  *
  * @hide
  */
-public class Country implements Parcelable {
+@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+public final class Country implements Parcelable {
     /**
      * The country code came from the mobile network
      */
@@ -78,6 +82,8 @@
      *        <li>{@link #COUNTRY_SOURCE_SIM}</li>
      *        <li>{@link #COUNTRY_SOURCE_LOCALE}</li>
      *        </ul>
+     *
+     * @hide
      */
     @UnsupportedAppUsage
     public Country(final String countryIso, final int source) {
@@ -100,6 +106,7 @@
         mTimestamp = timestamp;
     }
 
+    /** @hide */
     public Country(Country country) {
         mCountryIso = country.mCountryIso;
         mSource = country.mSource;
@@ -109,8 +116,8 @@
     /**
      * @return the ISO 3166-1 two letters country code
      */
-    @UnsupportedAppUsage
-    public final String getCountryIso() {
+    @NonNull
+    public String getCountryIso() {
         return mCountryIso;
     }
 
@@ -124,20 +131,22 @@
      *         <li>{@link #COUNTRY_SOURCE_LOCALE}</li>
      *         </ul>
      */
-    @UnsupportedAppUsage
-    public final int getSource() {
+    public int getSource() {
         return mSource;
     }
 
     /**
      * Returns the time that this object was created (which we assume to be the time that the source
      * was consulted).
+     *
+     * @hide
      */
-    public final long getTimestamp() {
+    public long getTimestamp() {
         return mTimestamp;
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<Country> CREATOR = new Parcelable.Creator<Country>() {
+    @android.annotation.NonNull
+    public static final Parcelable.Creator<Country> CREATOR = new Parcelable.Creator<Country>() {
         public Country createFromParcel(Parcel in) {
             return new Country(in.readString(), in.readInt(), in.readLong());
         }
@@ -147,11 +156,13 @@
         }
     };
 
+    @Override
     public int describeContents() {
         return 0;
     }
 
-    public void writeToParcel(Parcel parcel, int flags) {
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
         parcel.writeString(mCountryIso);
         parcel.writeInt(mSource);
         parcel.writeLong(mTimestamp);
@@ -161,9 +172,10 @@
      * Returns true if this {@link Country} is equivalent to the given object. This ignores
      * the timestamp value and just checks for equivalence of countryIso and source values.
      * Returns false otherwise.
+     *
      */
     @Override
-    public boolean equals(Object object) {
+    public boolean equals(@Nullable Object object) {
         if (object == this) {
             return true;
         }
@@ -194,12 +206,15 @@
      * @param country the country to compare
      * @return true if the specified country's countryIso field is equal to this
      *         country's, false otherwise.
+     *
+     * @hide
      */
     public boolean equalsIgnoreSource(Country country) {
         return country != null && mCountryIso.equals(country.getCountryIso());
     }
 
     @Override
+    @NonNull
     public String toString() {
         return "Country {ISO=" + mCountryIso + ", source=" + mSource + ", time=" + mTimestamp + "}";
     }
diff --git a/location/java/android/location/CountryDetector.java b/location/java/android/location/CountryDetector.java
index e344b82..3a0edfc 100644
--- a/location/java/android/location/CountryDetector.java
+++ b/location/java/android/location/CountryDetector.java
@@ -16,6 +16,9 @@
 
 package android.location;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -47,6 +50,7 @@
  *
  * @hide
  */
+@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
 @SystemService(Context.COUNTRY_DETECTOR)
 public class CountryDetector {
 
@@ -101,7 +105,7 @@
      * @return the country if it is available immediately, otherwise null will
      *         be returned.
      */
-    @UnsupportedAppUsage
+    @Nullable
     public Country detectCountry() {
         try {
             return mService.detectCountry();
@@ -120,8 +124,7 @@
      *        implement the callback mechanism. If looper is null then the
      *        callbacks will be called on the main thread.
      */
-    @UnsupportedAppUsage
-    public void addCountryListener(CountryListener listener, Looper looper) {
+    public void addCountryListener(@NonNull CountryListener listener, @Nullable Looper looper) {
         synchronized (mListeners) {
             if (!mListeners.containsKey(listener)) {
                 ListenerTransport transport = new ListenerTransport(listener, looper);
@@ -138,8 +141,7 @@
     /**
      * Remove the listener
      */
-    @UnsupportedAppUsage
-    public void removeCountryListener(CountryListener listener) {
+    public void removeCountryListener(@NonNull CountryListener listener) {
         synchronized (mListeners) {
             ListenerTransport transport = mListeners.get(listener);
             if (transport != null) {
diff --git a/location/java/android/location/CountryListener.java b/location/java/android/location/CountryListener.java
index eb67205..5c06d82 100644
--- a/location/java/android/location/CountryListener.java
+++ b/location/java/android/location/CountryListener.java
@@ -16,7 +16,8 @@
 
 package android.location;
 
-import android.compat.annotation.UnsupportedAppUsage;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 
 /**
  * The listener for receiving the notification when the country is detected or
@@ -24,10 +25,11 @@
  *
  * @hide
  */
+@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
 public interface CountryListener {
     /**
      * @param country the changed or detected country.
+     *
      */
-    @UnsupportedAppUsage
-    void onCountryDetected(Country country);
+    void onCountryDetected(@NonNull Country country);
 }
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index b38f9ea..a6da0a3 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -24,6 +24,9 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -123,20 +126,24 @@
      * @hide
      */
     public static GnssCapabilities empty() {
-        return new GnssCapabilities(0, 0, 0);
+        return new GnssCapabilities(0, 0, 0, Collections.emptyList());
     }
 
     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) {
+        Objects.requireNonNull(gnssSignalTypes);
         mTopFlags = topFlags;
         mMeasurementCorrectionsFlags = measurementCorrectionsFlags;
         mPowerFlags = powerFlags;
+        mGnssSignalTypes = Collections.unmodifiableList(gnssSignalTypes);
     }
 
     /**
@@ -148,7 +155,8 @@
         if (mTopFlags == flags) {
             return this;
         } else {
-            return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags);
+            return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags,
+                    mGnssSignalTypes);
         }
     }
 
@@ -163,7 +171,8 @@
         if (mMeasurementCorrectionsFlags == flags) {
             return this;
         } else {
-            return new GnssCapabilities(mTopFlags, flags, mPowerFlags);
+            return new GnssCapabilities(mTopFlags, flags, mPowerFlags,
+                    mGnssSignalTypes);
         }
     }
 
@@ -177,7 +186,23 @@
         if (mPowerFlags == flags) {
             return this;
         } else {
-            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags);
+            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags,
+                    mGnssSignalTypes);
+        }
+    }
+
+    /**
+     * Returns a new GnssCapabilities object with a list of GnssSignalType.
+     *
+     * @hide
+     */
+    public GnssCapabilities withSignalTypes(@NonNull List<GnssSignalType> gnssSignalTypes) {
+        Objects.requireNonNull(gnssSignalTypes);
+        if (mGnssSignalTypes.equals(gnssSignalTypes)) {
+            return this;
+        } else {
+            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags,
+                    new ArrayList<>(gnssSignalTypes));
         }
     }
 
@@ -424,6 +449,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) {
@@ -436,19 +469,21 @@
         GnssCapabilities that = (GnssCapabilities) o;
         return mTopFlags == that.mTopFlags
                 && mMeasurementCorrectionsFlags == that.mMeasurementCorrectionsFlags
-                && mPowerFlags == that.mPowerFlags;
+                && mPowerFlags == that.mPowerFlags
+                && mGnssSignalTypes.equals(that.mGnssSignalTypes);
     }
 
     @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 +502,7 @@
         parcel.writeInt(mTopFlags);
         parcel.writeInt(mMeasurementCorrectionsFlags);
         parcel.writeInt(mPowerFlags);
+        parcel.writeTypedList(mGnssSignalTypes);
     }
 
     @Override
@@ -545,6 +581,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 +601,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 = Collections.emptyList();
         }
 
         public Builder(@NonNull GnssCapabilities capabilities) {
             mTopFlags = capabilities.mTopFlags;
             mMeasurementCorrectionsFlags = capabilities.mMeasurementCorrectionsFlags;
             mPowerFlags = capabilities.mPowerFlags;
+            mGnssSignalTypes = capabilities.mGnssSignalTypes;
         }
 
         /**
@@ -779,10 +821,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/location/java/android/location/GnssMeasurementsEvent.java b/location/java/android/location/GnssMeasurementsEvent.java
index 0397740..a8b0dc2 100644
--- a/location/java/android/location/GnssMeasurementsEvent.java
+++ b/location/java/android/location/GnssMeasurementsEvent.java
@@ -40,6 +40,7 @@
     private final GnssClock mClock;
     private final List<GnssMeasurement> mMeasurements;
     private final List<GnssAutomaticGainControl> mGnssAgcs;
+    private final boolean mIsFullTracking;
 
     /**
      * Used for receiving GNSS satellite measurements from the GNSS engine.
@@ -124,10 +125,12 @@
      */
     private GnssMeasurementsEvent(@NonNull GnssClock clock,
             @NonNull List<GnssMeasurement> measurements,
-            @NonNull List<GnssAutomaticGainControl> agcs) {
+            @NonNull List<GnssAutomaticGainControl> agcs,
+            boolean isFullTracking) {
         mMeasurements = measurements;
         mGnssAgcs = agcs;
         mClock = clock;
+        mIsFullTracking = isFullTracking;
     }
 
     /**
@@ -156,15 +159,31 @@
         return mGnssAgcs;
     }
 
+    /**
+     * True indicates that this event was produced while the chipset was in full tracking mode, ie,
+     * the GNSS chipset switched off duty cycling. In this mode, no clock discontinuities are
+     * expected and, when supported, carrier phase should be continuous in good signal conditions.
+     * All non-blocklisted, healthy constellations, satellites and frequency bands must be tracked
+     * and reported in this mode.
+     *
+     * False indicates that the GNSS chipset may optimize power via duty cycling, constellations and
+     * frequency limits, etc.
+     */
+    public boolean getIsFullTracking() {
+        return mIsFullTracking;
+    }
+
     public static final @android.annotation.NonNull Creator<GnssMeasurementsEvent> CREATOR =
             new Creator<GnssMeasurementsEvent>() {
         @Override
         public GnssMeasurementsEvent createFromParcel(Parcel in) {
-            GnssClock clock = in.readParcelable(getClass().getClassLoader(), android.location.GnssClock.class);
+            GnssClock clock = in.readParcelable(getClass().getClassLoader(),
+                    android.location.GnssClock.class);
             List<GnssMeasurement> measurements = in.createTypedArrayList(GnssMeasurement.CREATOR);
             List<GnssAutomaticGainControl> agcs = in.createTypedArrayList(
                     GnssAutomaticGainControl.CREATOR);
-            return new GnssMeasurementsEvent(clock, measurements, agcs);
+            boolean isFullTracking = in.readBoolean();
+            return new GnssMeasurementsEvent(clock, measurements, agcs, isFullTracking);
         }
 
         @Override
@@ -183,6 +202,7 @@
         parcel.writeParcelable(mClock, flags);
         parcel.writeTypedList(mMeasurements);
         parcel.writeTypedList(mGnssAgcs);
+        parcel.writeBoolean(mIsFullTracking);
     }
 
     @Override
@@ -191,6 +211,7 @@
         builder.append(mClock);
         builder.append(' ').append(mMeasurements.toString());
         builder.append(' ').append(mGnssAgcs.toString());
+        builder.append(" isFullTracking=").append(mIsFullTracking);
         builder.append("]");
         return builder.toString();
     }
@@ -200,6 +221,7 @@
         private GnssClock mClock;
         private List<GnssMeasurement> mMeasurements;
         private List<GnssAutomaticGainControl> mGnssAgcs;
+        private boolean mIsFullTracking;
 
         /**
          * Constructs a {@link GnssMeasurementsEvent.Builder} instance.
@@ -218,6 +240,7 @@
             mClock = event.getClock();
             mMeasurements = (List<GnssMeasurement>) event.getMeasurements();
             mGnssAgcs = (List<GnssAutomaticGainControl>) event.getGnssAutomaticGainControls();
+            mIsFullTracking = event.getIsFullTracking();
         }
 
         /**
@@ -276,10 +299,29 @@
             return this;
         }
 
+        /**
+         * Sets whether the GNSS chipset was in the full tracking mode at the time this event was
+         * produced.
+         *
+         * True indicates that this event was produced while the chipset was in full tracking
+         * mode, ie, the GNSS chipset switched off duty cycling. In this mode, no clock
+         * discontinuities are expected and, when supported, carrier phase should be continuous in
+         * good signal conditions. All non-blocklisted, healthy constellations, satellites and
+         * frequency bands must be tracked and reported in this mode.
+         *
+         * False indicates that the GNSS chipset may optimize power via duty cycling, constellations
+         * and frequency limits, etc.
+         */
+        @NonNull
+        public Builder setIsFullTracking(boolean isFullTracking) {
+            mIsFullTracking = isFullTracking;
+            return this;
+        }
+
         /** Builds a {@link GnssMeasurementsEvent} instance as specified by this builder. */
         @NonNull
         public GnssMeasurementsEvent build() {
-            return new GnssMeasurementsEvent(mClock, mMeasurements, mGnssAgcs);
+            return new GnssMeasurementsEvent(mClock, mMeasurements, mGnssAgcs, mIsFullTracking);
         }
     }
 }
diff --git a/location/java/android/location/GnssSignalType.aidl b/location/java/android/location/GnssSignalType.aidl
new file mode 100644
index 0000000..1c43fe5
--- /dev/null
+++ b/location/java/android/location/GnssSignalType.aidl
@@ -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.
+ */
+
+package android.location;
+
+parcelable GnssSignalType;
diff --git a/location/java/android/location/GnssSignalType.java b/location/java/android/location/GnssSignalType.java
new file mode 100644
index 0000000..16c3f2e
--- /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/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
new file mode 100644
index 0000000..506128e
--- /dev/null
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -0,0 +1,175 @@
+/*
+ * 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.altitude;
+
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.content.Context;
+import android.location.Location;
+
+import com.android.internal.location.altitude.GeoidHeightMap;
+import com.android.internal.location.altitude.S2CellIdUtils;
+import com.android.internal.location.altitude.nano.MapParamsProto;
+import com.android.internal.util.Preconditions;
+
+import java.io.IOException;
+
+/**
+ * Converts altitudes reported above the World Geodetic System 1984 (WGS84) reference ellipsoid
+ * into ones above Mean Sea Level.
+ */
+public final class AltitudeConverter {
+
+    private static final double MAX_ABS_VALID_LATITUDE = 90;
+    private static final double MAX_ABS_VALID_LONGITUDE = 180;
+
+    /** Manages a mapping of geoid heights associated with S2 cells. */
+    private final GeoidHeightMap mGeoidHeightMap = new GeoidHeightMap();
+
+    /**
+     * Creates an instance that manages an independent cache to optimized conversions of locations
+     * in proximity to one another.
+     */
+    public AltitudeConverter() {
+    }
+
+    /**
+     * Throws an {@link IllegalArgumentException} if the {@code location} has an invalid latitude,
+     * longitude, or altitude above WGS84.
+     */
+    private static void validate(@NonNull Location location) {
+        Preconditions.checkArgument(
+                isFiniteAndAtAbsMost(location.getLatitude(), MAX_ABS_VALID_LATITUDE),
+                "Invalid latitude: %f", location.getLatitude());
+        Preconditions.checkArgument(
+                isFiniteAndAtAbsMost(location.getLongitude(), MAX_ABS_VALID_LONGITUDE),
+                "Invalid longitude: %f", location.getLongitude());
+        Preconditions.checkArgument(location.hasAltitude(), "Missing altitude above WGS84");
+        Preconditions.checkArgument(Double.isFinite(location.getAltitude()),
+                "Invalid altitude above WGS84: %f", location.getAltitude());
+    }
+
+    private static boolean isFiniteAndAtAbsMost(double value, double rhs) {
+        return Double.isFinite(value) && Math.abs(value) <= rhs;
+    }
+
+    /**
+     * Returns the four S2 cell IDs for the map square associated with the {@code location}.
+     *
+     * <p>The first map cell contains the location, while the others are located horizontally,
+     * vertically, and diagonally, in that order, with respect to the S2 (i,j) coordinate system. If
+     * the diagonal map cell does not exist (i.e., the location is near an S2 cube vertex), its
+     * corresponding ID is set to zero.
+     */
+    @NonNull
+    private static long[] findMapSquare(@NonNull MapParamsProto params,
+            @NonNull Location location) {
+        long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
+                location.getLongitude());
+
+        // (0,0) cell.
+        long s0 = S2CellIdUtils.getParent(s2CellId, params.mapS2Level);
+        long[] edgeNeighbors = new long[4];
+        S2CellIdUtils.getEdgeNeighbors(s0, edgeNeighbors);
+
+        // (1,0) cell.
+        int i1 = S2CellIdUtils.getI(s2CellId) > S2CellIdUtils.getI(s0) ? -1 : 1;
+        long s1 = edgeNeighbors[i1 + 2];
+
+        // (0,1) cell.
+        int i2 = S2CellIdUtils.getJ(s2CellId) > S2CellIdUtils.getJ(s0) ? 1 : -1;
+        long s2 = edgeNeighbors[i2 + 1];
+
+        // (1,1) cell.
+        S2CellIdUtils.getEdgeNeighbors(s1, edgeNeighbors);
+        long s3 = 0;
+        for (int i = 0; i < edgeNeighbors.length; i++) {
+            if (edgeNeighbors[i] == s0) {
+                int i3 = (i + i1 * i2 + edgeNeighbors.length) % edgeNeighbors.length;
+                s3 = edgeNeighbors[i3] == s2 ? 0 : edgeNeighbors[i3];
+                break;
+            }
+        }
+
+        // Reuse edge neighbors' array to avoid an extra allocation.
+        edgeNeighbors[0] = s0;
+        edgeNeighbors[1] = s1;
+        edgeNeighbors[2] = s2;
+        edgeNeighbors[3] = s3;
+        return edgeNeighbors;
+    }
+
+    /**
+     * Adds to {@code location} the bilinearly interpolated Mean Sea Level altitude. In addition, a
+     * Mean Sea Level altitude accuracy is added if the {@code location} has a valid vertical
+     * accuracy; otherwise, does not add a corresponding accuracy.
+     */
+    private static void addMslAltitude(@NonNull MapParamsProto params, @NonNull long[] s2CellIds,
+            @NonNull double[] geoidHeightsMeters, @NonNull Location location) {
+        long s0 = s2CellIds[0];
+        double h0 = geoidHeightsMeters[0];
+        double h1 = geoidHeightsMeters[1];
+        double h2 = geoidHeightsMeters[2];
+        double h3 = s2CellIds[3] == 0 ? h0 : geoidHeightsMeters[3];
+
+        // Bilinear interpolation on an S2 square of size equal to that of a map cell. wi and wj
+        // are the normalized [0,1] weights in the i and j directions, respectively, allowing us to
+        // employ the simplified unit square formulation.
+        long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
+                location.getLongitude());
+        double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
+        double wi = Math.abs(S2CellIdUtils.getI(s2CellId) - S2CellIdUtils.getI(s0)) / sizeIj;
+        double wj = Math.abs(S2CellIdUtils.getJ(s2CellId) - S2CellIdUtils.getJ(s0)) / sizeIj;
+        double offsetMeters = h0 + (h1 - h0) * wi + (h2 - h0) * wj + (h3 - h1 - h2 + h0) * wi * wj;
+
+        location.setMslAltitudeMeters(location.getAltitude() - offsetMeters);
+        if (location.hasVerticalAccuracy()) {
+            double verticalAccuracyMeters = location.getVerticalAccuracyMeters();
+            if (Double.isFinite(verticalAccuracyMeters) && verticalAccuracyMeters >= 0) {
+                location.setMslAltitudeAccuracyMeters(
+                        (float) Math.hypot(verticalAccuracyMeters, params.modelRmseMeters));
+            }
+        }
+    }
+
+    /**
+     * Adds a Mean Sea Level altitude to the {@code location}. In addition, adds a Mean Sea Level
+     * altitude accuracy if the {@code location} has a finite and non-negative vertical accuracy;
+     * otherwise, does not add a corresponding accuracy.
+     *
+     * <p>Must be called off the main thread as data may be loaded from raw assets. Throws an
+     * {@link IOException} if an I/O error occurs when loading data.
+     *
+     * <p>Throws an {@link IllegalArgumentException} if the {@code location} has an invalid
+     * latitude, longitude, or altitude above WGS84. Specifically:
+     *
+     * <ul>
+     *     <li>The latitude must be between -90 and 90, both inclusive.
+     *     <li>The longitude must be between -180 and 180, both inclusive.
+     *     <li>The altitude above WGS84 must be finite.
+     * </ul>
+     */
+    @WorkerThread
+    public void addMslAltitude(@NonNull Context context, @NonNull Location location)
+            throws IOException {
+        validate(location);
+        MapParamsProto params = GeoidHeightMap.getParams(context);
+        long[] s2CellIds = findMapSquare(params, location);
+        double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, context, s2CellIds);
+        addMslAltitude(params, s2CellIds, geoidHeightsMeters, location);
+    }
+}
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index 529eddd..5acec79 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -101,6 +101,15 @@
     public static final String ACTION_FUSED_PROVIDER =
             "com.android.location.service.FusedLocationProvider";
 
+    /**
+     * The action the wrapping service should have in its intent filter to implement the
+     * {@link android.location.LocationManager#GPS_PROVIDER}.
+     *
+     * @hide
+     */
+    public static final String ACTION_GNSS_PROVIDER =
+            "android.location.provider.action.GNSS_PROVIDER";
+
     final String mTag;
     final @Nullable String mAttributionTag;
     final IBinder mBinder;
diff --git a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
new file mode 100644
index 0000000..6430eb4
--- /dev/null
+++ b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
@@ -0,0 +1,367 @@
+/*
+ * 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.location.altitude;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.LruCache;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.altitude.nano.MapParamsProto;
+import com.android.internal.location.altitude.nano.S2TileProto;
+import com.android.internal.util.Preconditions;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+/**
+ * Manages a mapping of geoid heights associated with S2 cells, referred to as MAP CELLS.
+ *
+ * <p>Tiles are used extensively to reduce the number of entries needed to be stored in memory and
+ * on disk. A tile associates geoid heights with all map cells of a common parent at a specified S2
+ * level.
+ *
+ * <p>Since bilinear interpolation considers at most four map cells at a time, at most four tiles
+ * are simultaneously stored in memory. These tiles, referred to as CACHE TILES, are each keyed by
+ * its common parent's S2 cell ID, referred to as a CACHE KEY.
+ *
+ * <p>Absent cache tiles needed for interpolation are constructed from larger tiles stored on disk.
+ * The latter tiles, referred to as DISK TILES, are each keyed by its common parent's S2 cell token,
+ * referred to as a DISK TOKEN.
+ */
+public final class GeoidHeightMap {
+
+    private static final Object sLock = new Object();
+
+    @GuardedBy("sLock")
+    @Nullable
+    private static MapParamsProto sParams;
+
+    /** Defines a cache large enough to hold all cache tiles needed for interpolation. */
+    private final LruCache<Long, S2TileProto> mCacheTiles = new LruCache<>(4);
+
+    /**
+     * Returns the singleton parameter instance for a spherically projected geoid height map and its
+     * corresponding tile management.
+     */
+    @NonNull
+    public static MapParamsProto getParams(@NonNull Context context) throws IOException {
+        synchronized (sLock) {
+            if (sParams == null) {
+                try (InputStream is = context.getApplicationContext().getAssets().open(
+                        "geoid_height_map/map-params.pb")) {
+                    sParams = MapParamsProto.parseFrom(is.readAllBytes());
+                }
+            }
+            return sParams;
+        }
+    }
+
+    private static long getCacheKey(@NonNull MapParamsProto params, long s2CellId) {
+        return S2CellIdUtils.getParent(s2CellId, params.cacheTileS2Level);
+    }
+
+    @NonNull
+    private static String getDiskToken(@NonNull MapParamsProto params, long s2CellId) {
+        return S2CellIdUtils.getToken(
+                S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level));
+    }
+
+    /**
+     * Adds to {@code values} values in the unit interval [0, 1] for the map cells identified by
+     * {@code s2CellIds}. Returns true if values are present for all non-zero IDs; otherwise,
+     * returns false and adds NaNs for absent values.
+     */
+    private static boolean getUnitIntervalValues(@NonNull MapParamsProto params,
+            @NonNull TileFunction tileFunction,
+            @NonNull long[] s2CellIds, @NonNull double[] values) {
+        int len = s2CellIds.length;
+
+        S2TileProto[] tiles = new S2TileProto[len];
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] != 0) {
+                tiles[i] = tileFunction.getTile(s2CellIds[i]);
+            }
+            values[i] = Double.NaN;
+        }
+
+        for (int i = 0; i < len; i++) {
+            if (tiles[i] == null || !Double.isNaN(values[i])) {
+                continue;
+            }
+
+            mergeByteBufferValues(params, s2CellIds, tiles, i, values);
+            mergeByteJpegValues(params, s2CellIds, tiles, i, values);
+            mergeBytePngValues(params, s2CellIds, tiles, i, values);
+        }
+
+        boolean allFound = true;
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] == 0) {
+                continue;
+            }
+            if (Double.isNaN(values[i])) {
+                allFound = false;
+            } else {
+                values[i] = (((int) values[i]) & 0xFF) / 255.0;
+            }
+        }
+        return allFound;
+    }
+
+    @SuppressWarnings("ReferenceEquality")
+    private static void mergeByteBufferValues(@NonNull MapParamsProto params,
+            @NonNull long[] s2CellIds,
+            @NonNull S2TileProto[] tiles,
+            int tileIndex, @NonNull double[] values) {
+        byte[] bytes = tiles[tileIndex].byteBuffer;
+        if (bytes == null || bytes.length == 0) {
+            return;
+        }
+
+        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).asReadOnlyBuffer();
+        int tileS2Level = params.mapS2Level - Integer.numberOfTrailingZeros(byteBuffer.limit()) / 2;
+        int numBitsLeftOfTile = 2 * tileS2Level + 3;
+
+        for (int i = tileIndex; i < tiles.length; i++) {
+            if (tiles[i] != tiles[tileIndex]) {
+                continue;
+            }
+
+            long maskedS2CellId = s2CellIds[i] & (-1L >>> numBitsLeftOfTile);
+            int numBitsRightOfMap = 2 * (S2CellIdUtils.MAX_LEVEL - params.mapS2Level) + 1;
+            int bufferIndex = (int) (maskedS2CellId >>> numBitsRightOfMap);
+            values[i] = Double.isNaN(values[i]) ? 0 : values[i];
+            values[i] += ((int) byteBuffer.get(bufferIndex)) & 0xFF;
+        }
+    }
+
+    private static void mergeByteJpegValues(@NonNull MapParamsProto params,
+            @NonNull long[] s2CellIds,
+            @NonNull S2TileProto[] tiles,
+            int tileIndex, @NonNull double[] values) {
+        mergeByteImageValues(params, tiles[tileIndex].byteJpeg, s2CellIds, tiles, tileIndex,
+                values);
+    }
+
+    private static void mergeBytePngValues(@NonNull MapParamsProto params,
+            @NonNull long[] s2CellIds,
+            @NonNull S2TileProto[] tiles,
+            int tileIndex, @NonNull double[] values) {
+        mergeByteImageValues(params, tiles[tileIndex].bytePng, s2CellIds, tiles, tileIndex, values);
+    }
+
+    @SuppressWarnings("ReferenceEquality")
+    private static void mergeByteImageValues(@NonNull MapParamsProto params, @NonNull byte[] bytes,
+            @NonNull long[] s2CellIds,
+            @NonNull S2TileProto[] tiles, int tileIndex, @NonNull double[] values) {
+        if (bytes == null || bytes.length == 0) {
+            return;
+        }
+        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+        if (bitmap == null) {
+            return;
+        }
+
+        for (int i = tileIndex; i < tiles.length; i++) {
+            if (s2CellIds[i] == 0 || tiles[i] != tiles[tileIndex]) {
+                continue;
+            }
+
+            values[i] = Double.isNaN(values[i]) ? 0 : values[i];
+            values[i] += bitmap.getPixel(getIndexX(params, s2CellIds[i], bitmap.getWidth()),
+                    getIndexY(params, s2CellIds[i], bitmap.getHeight())) & 0xFF;
+        }
+    }
+
+    /** Returns the X index for an S2 cell within an S2 tile image of specified width. */
+    private static int getIndexX(@NonNull MapParamsProto params, long s2CellId, int width) {
+        return getIndexXOrY(params, S2CellIdUtils.getI(s2CellId), width);
+    }
+
+    /** Returns the Y index for an S2 cell within an S2 tile image of specified height. */
+    private static int getIndexY(@NonNull MapParamsProto params, long s2CellId, int height) {
+        return getIndexXOrY(params, S2CellIdUtils.getJ(s2CellId), height);
+    }
+
+    private static int getIndexXOrY(@NonNull MapParamsProto params, int iOrJ, int widthOrHeight) {
+        return (iOrJ >> (S2CellIdUtils.MAX_LEVEL - params.mapS2Level)) % widthOrHeight;
+    }
+
+    /**
+     * Returns the geoid heights in meters associated with the map cells identified by
+     * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for a
+     * non-zero ID.
+     */
+    @NonNull
+    public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context,
+            @NonNull long[] s2CellIds) throws IOException {
+        Preconditions.checkArgument(s2CellIds.length == 4);
+        for (long s2CellId : s2CellIds) {
+            Preconditions.checkArgument(
+                    s2CellId == 0 || S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level);
+        }
+
+        double[] heightsMeters = new double[s2CellIds.length];
+        if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) {
+            return heightsMeters;
+        }
+
+        TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds);
+        if (getGeoidHeights(params, loadedTiles, s2CellIds, heightsMeters)) {
+            return heightsMeters;
+        }
+        throw new IOException("Unable to calculate geoid heights from raw assets.");
+    }
+
+    /**
+     * Adds to {@code heightsMeters} the geoid heights in meters associated with the map cells
+     * identified by {@code s2CellIds}. Returns true if heights are present for all non-zero IDs;
+     * otherwise, returns false and adds NaNs for absent heights.
+     */
+    private boolean getGeoidHeights(@NonNull MapParamsProto params,
+            @NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
+            @NonNull double[] heightsMeters) {
+        boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, heightsMeters);
+        for (int i = 0; i < heightsMeters.length; i++) {
+            // NaNs are properly preserved.
+            heightsMeters[i] *= params.modelAMeters;
+            heightsMeters[i] += params.modelBMeters;
+        }
+        return allFound;
+    }
+
+    @NonNull
+    private TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params,
+            @NonNull Context context, @NonNull long[] s2CellIds) throws IOException {
+        int len = s2CellIds.length;
+
+        // Enable batch loading by finding all cache keys upfront.
+        long[] cacheKeys = new long[len];
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] == 0) {
+                continue;
+            }
+            cacheKeys[i] = getCacheKey(params, s2CellIds[i]);
+        }
+
+        // Attempt to load tiles from cache.
+        S2TileProto[] loadedTiles = new S2TileProto[len];
+        String[] diskTokens = new String[len];
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] == 0 || diskTokens[i] != null) {
+                continue;
+            }
+            loadedTiles[i] = mCacheTiles.get(cacheKeys[i]);
+            diskTokens[i] = getDiskToken(params, cacheKeys[i]);
+
+            // Batch across common cache key.
+            for (int j = i + 1; j < len; j++) {
+                if (cacheKeys[j] == cacheKeys[i]) {
+                    loadedTiles[j] = loadedTiles[i];
+                    diskTokens[j] = diskTokens[i];
+                }
+            }
+        }
+
+        // Attempt to load tiles from disk.
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] == 0 || loadedTiles[i] != null) {
+                continue;
+            }
+
+            S2TileProto tile;
+            try (InputStream is = context.getApplicationContext().getAssets().open(
+                    "geoid_height_map/tile-" + diskTokens[i] + ".pb")) {
+                tile = S2TileProto.parseFrom(is.readAllBytes());
+            }
+            mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles);
+        }
+
+        return s2CellId -> {
+            if (s2CellId == 0) {
+                return null;
+            }
+            long cacheKey = getCacheKey(params, s2CellId);
+            for (int i = 0; i < cacheKeys.length; i++) {
+                if (cacheKeys[i] == cacheKey) {
+                    return loadedTiles[i];
+                }
+            }
+            return null;
+        };
+    }
+
+    private void mergeFromDiskTile(@NonNull MapParamsProto params, @NonNull S2TileProto diskTile,
+            @NonNull long[] cacheKeys, @NonNull String[] diskTokens, int diskTokenIndex,
+            @NonNull S2TileProto[] loadedTiles) throws IOException {
+        int len = cacheKeys.length;
+        int numMapCellsPerCacheTile = 1 << (2 * (params.mapS2Level - params.cacheTileS2Level));
+
+        // Reusable arrays.
+        long[] s2CellIds = new long[numMapCellsPerCacheTile];
+        double[] values = new double[numMapCellsPerCacheTile];
+
+        // Each cache key identifies a different sub-tile of the disk tile.
+        TileFunction diskTileFunction = s2CellId -> diskTile;
+        for (int i = diskTokenIndex; i < len; i++) {
+            if (!Objects.equals(diskTokens[i], diskTokens[diskTokenIndex])
+                    || loadedTiles[i] != null) {
+                continue;
+            }
+
+            // Find all map cells within the current cache tile.
+            long s2CellId = S2CellIdUtils.getTraversalStart(cacheKeys[i], params.mapS2Level);
+            for (int j = 0; j < numMapCellsPerCacheTile; j++) {
+                s2CellIds[j] = s2CellId;
+                s2CellId = S2CellIdUtils.getTraversalNext(s2CellId);
+            }
+
+            if (!getUnitIntervalValues(params, diskTileFunction, s2CellIds, values)) {
+                throw new IOException("Corrupted disk tile of disk token: " + diskTokens[i]);
+            }
+
+            loadedTiles[i] = new S2TileProto();
+            loadedTiles[i].byteBuffer = new byte[numMapCellsPerCacheTile];
+            for (int j = 0; j < numMapCellsPerCacheTile; j++) {
+                loadedTiles[i].byteBuffer[j] = (byte) Math.round(values[j] * 0xFF);
+            }
+
+            // Batch across common cache key.
+            for (int j = i + 1; j < len; j++) {
+                if (cacheKeys[j] == cacheKeys[i]) {
+                    loadedTiles[j] = loadedTiles[i];
+                }
+            }
+
+            // Side load into tile cache.
+            mCacheTiles.put(cacheKeys[i], loadedTiles[i]);
+        }
+    }
+
+    /** Defines a function-like object to retrieve tiles for map cells. */
+    private interface TileFunction {
+
+        @Nullable
+        S2TileProto getTile(long s2CellId);
+    }
+}
diff --git a/location/java/com/android/internal/location/altitude/S2CellIdUtils.java b/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
new file mode 100644
index 0000000..5f11387
--- /dev/null
+++ b/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
@@ -0,0 +1,653 @@
+/*
+ * 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.location.altitude;
+
+import android.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Provides lightweight S2 cell ID utilities without traditional geometry dependencies.
+ *
+ * <p>See <a href="https://s2geometry.io/">the S2 Geometry Library website</a> for more details.
+ */
+public final class S2CellIdUtils {
+
+    /** The level of all leaf S2 cells. */
+    public static final int MAX_LEVEL = 30;
+
+    private static final int MAX_SIZE = 1 << MAX_LEVEL;
+    private static final double ONE_OVER_MAX_SIZE = 1.0 / MAX_SIZE;
+    private static final int NUM_FACES = 6;
+    private static final int POS_BITS = 2 * MAX_LEVEL + 1;
+    private static final int SWAP_MASK = 0x1;
+    private static final int LOOKUP_BITS = 4;
+    private static final int LOOKUP_MASK = (1 << LOOKUP_BITS) - 1;
+    private static final int INVERT_MASK = 0x2;
+    private static final int LEAF_MASK = 0x1;
+    private static final int[] LOOKUP_POS = new int[1 << (2 * LOOKUP_BITS + 2)];
+    private static final int[] LOOKUP_IJ = new int[1 << (2 * LOOKUP_BITS + 2)];
+    private static final int[] POS_TO_ORIENTATION = {SWAP_MASK, 0, 0, INVERT_MASK + SWAP_MASK};
+    private static final int[][] POS_TO_IJ =
+            {{0, 1, 3, 2}, {0, 2, 3, 1}, {3, 2, 0, 1}, {3, 1, 0, 2}};
+    private static final double UV_LIMIT = calculateUvLimit();
+    private static final UvTransform[] UV_TRANSFORMS = createUvTransforms();
+    private static final XyzTransform[] XYZ_TRANSFORMS = createXyzTransforms();
+
+    // Used to encode (i, j, o) coordinates into primitive longs.
+    private static final int I_SHIFT = 33;
+    private static final int J_SHIFT = 2;
+    private static final long J_MASK = (1L << 31) - 1;
+
+    static {
+        initLookupCells();
+    }
+
+    /** Prevents instantiation. */
+    private S2CellIdUtils() {
+    }
+
+    /**
+     * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
+     * degrees.
+     */
+    public static long fromLatLngDegrees(double latDegrees, double lngDegrees) {
+        return fromLatLngRadians(Math.toRadians(latDegrees), Math.toRadians(lngDegrees));
+    }
+
+    /**
+     * Returns the ID of the parent of the specified S2 cell at the specified parent level.
+     * Behavior is undefined for invalid S2 cell IDs or parent levels not in
+     * [0, {@code getLevel(s2CellId)}[.
+     */
+    public static long getParent(long s2CellId, int level) {
+        long newLsb = getLowestOnBitForLevel(level);
+        return (s2CellId & -newLsb) | newLsb;
+    }
+
+    /**
+     * Inserts into {@code neighbors} the four S2 cell IDs corresponding to the neighboring
+     * cells adjacent across the specified cell's four edges. This array must be of minimum
+     * length four, and elements at the tail end of the array not corresponding to a neighbor
+     * are set to zero. A reference to this array is returned.
+     *
+     * <p>Inserts in the order of down, right, up, and left directions, in that order. All
+     * neighbors are guaranteed to be distinct.
+     */
+    public static void getEdgeNeighbors(long s2CellId, @NonNull long[] neighbors) {
+        int level = getLevel(s2CellId);
+        int size = levelToSizeIj(level);
+        int face = getFace(s2CellId);
+        long ijo = toIjo(s2CellId);
+        int i = ijoToI(ijo);
+        int j = ijoToJ(ijo);
+
+        int iPlusSize = i + size;
+        int iMinusSize = i - size;
+        int jPlusSize = j + size;
+        int jMinusSize = j - size;
+        boolean iPlusSizeLtMax = iPlusSize < MAX_SIZE;
+        boolean iMinusSizeGteZero = iMinusSize >= 0;
+        boolean jPlusSizeLtMax = jPlusSize < MAX_SIZE;
+        boolean jMinusSizeGteZero = jMinusSize >= 0;
+
+        int index = 0;
+        // Down direction.
+        neighbors[index++] = getParent(fromFijSame(face, i, jMinusSize, jMinusSizeGteZero),
+                level);
+        // Right direction.
+        neighbors[index++] = getParent(fromFijSame(face, iPlusSize, j, iPlusSizeLtMax), level);
+        // Up direction.
+        neighbors[index++] = getParent(fromFijSame(face, i, jPlusSize, jPlusSizeLtMax), level);
+        // Left direction.
+        neighbors[index++] = getParent(fromFijSame(face, iMinusSize, j, iMinusSizeGteZero),
+                level);
+
+        // Pad end of neighbor array with zeros.
+        Arrays.fill(neighbors, index, neighbors.length, 0);
+    }
+
+    /** Returns the "i" coordinate for the specified S2 cell. */
+    public static int getI(long s2CellId) {
+        return ijoToI(toIjo(s2CellId));
+    }
+
+    /** Returns the "j" coordinate for the specified S2 cell. */
+    public static int getJ(long s2CellId) {
+        return ijoToJ(toIjo(s2CellId));
+    }
+
+    /**
+     * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
+     * radians.
+     */
+    private static long fromLatLngRadians(double latRadians, double lngRadians) {
+        double cosLat = Math.cos(latRadians);
+        double x = Math.cos(lngRadians) * cosLat;
+        double y = Math.sin(lngRadians) * cosLat;
+        double z = Math.sin(latRadians);
+        return fromXyz(x, y, z);
+    }
+
+    /**
+     * Returns the level of the specified S2 cell. The returned level is in [0, 30] for valid
+     * S2 cell IDs. Behavior is undefined for invalid S2 cell IDs.
+     */
+    static int getLevel(long s2CellId) {
+        if (isLeaf(s2CellId)) {
+            return MAX_LEVEL;
+        }
+        return MAX_LEVEL - (Long.numberOfTrailingZeros(s2CellId) >> 1);
+    }
+
+    /** Returns the lowest-numbered bit that is on for the specified S2 cell. */
+    static long getLowestOnBit(long s2CellId) {
+        return s2CellId & -s2CellId;
+    }
+
+    /** Returns the lowest-numbered bit that is on for any S2 cell on the specified level. */
+    static long getLowestOnBitForLevel(int level) {
+        return 1L << (2 * (MAX_LEVEL - level));
+    }
+
+    /**
+     * Returns the ID of the first S2 cell in a traversal of the children S2 cells at the specified
+     * level, in Hilbert curve order.
+     */
+    static long getTraversalStart(long s2CellId, int level) {
+        return s2CellId - getLowestOnBit(s2CellId) + getLowestOnBitForLevel(level);
+    }
+
+    /** Returns the ID of the next S2 cell at the same level along the Hilbert curve. */
+    static long getTraversalNext(long s2CellId) {
+        return s2CellId + (getLowestOnBit(s2CellId) << 1);
+    }
+
+    /**
+     * Encodes the S2 cell id to compact text strings suitable for display or indexing. Cells at
+     * lower levels (i.e., larger cells) are encoded into fewer characters.
+     */
+    @NonNull
+    static String getToken(long s2CellId) {
+        if (s2CellId == 0) {
+            return "X";
+        }
+
+        // Convert to a hex string with as many digits as necessary.
+        String hex = Long.toHexString(s2CellId).toLowerCase(Locale.US);
+        // Prefix 0s to get a length 16 string.
+        String padded = padStart(hex);
+        // Trim zeroes off the end.
+        return padded.replaceAll("0*$", "");
+    }
+
+    private static String padStart(String string) {
+        if (string.length() >= 16) {
+            return string;
+        }
+        return "0".repeat(16 - string.length()) + string;
+    }
+
+    /** Returns the leaf S2 cell ID of the specified (x, y, z) coordinate. */
+    private static long fromXyz(double x, double y, double z) {
+        int face = xyzToFace(x, y, z);
+        UvTransform uvTransform = UV_TRANSFORMS[face];
+        double u = uvTransform.xyzToU(x, y, z);
+        double v = uvTransform.xyzToV(x, y, z);
+        return fromFuv(face, u, v);
+    }
+
+    /** Returns the leaf S2 cell ID of the specified (face, u, v) coordinate. */
+    private static long fromFuv(int face, double u, double v) {
+        int i = uToI(u);
+        int j = vToJ(v);
+        return fromFij(face, i, j);
+    }
+
+    /** Returns the leaf S2 cell ID of the specified (face, i, j) coordinate. */
+    private static long fromFij(int face, int i, int j) {
+        int bits = (face & SWAP_MASK);
+        // Update most significant bits.
+        long msb = ((long) face) << (POS_BITS - 33);
+        for (int k = 7; k >= 4; --k) {
+            bits = lookupBits(i, j, k, bits);
+            msb = updateBits(msb, k, bits);
+            bits = maskBits(bits);
+        }
+        // Update least significant bits.
+        long lsb = 0;
+        for (int k = 3; k >= 0; --k) {
+            bits = lookupBits(i, j, k, bits);
+            lsb = updateBits(lsb, k, bits);
+            bits = maskBits(bits);
+        }
+        return (((msb << 32) + lsb) << 1) + 1;
+    }
+
+    private static long fromFijWrap(int face, int i, int j) {
+        double u = iToU(i);
+        double v = jToV(j);
+
+        XyzTransform xyzTransform = XYZ_TRANSFORMS[face];
+        double x = xyzTransform.uvToX(u, v);
+        double y = xyzTransform.uvToY(u, v);
+        double z = xyzTransform.uvToZ(u, v);
+
+        int newFace = xyzToFace(x, y, z);
+        UvTransform uvTransform = UV_TRANSFORMS[newFace];
+        double newU = uvTransform.xyzToU(x, y, z);
+        double newV = uvTransform.xyzToV(x, y, z);
+
+        int newI = uShiftIntoI(newU);
+        int newJ = vShiftIntoJ(newV);
+        return fromFij(newFace, newI, newJ);
+    }
+
+    private static long fromFijSame(int face, int i, int j, boolean isSameFace) {
+        if (isSameFace) {
+            return fromFij(face, i, j);
+        }
+        return fromFijWrap(face, i, j);
+    }
+
+    /**
+     * Returns the face associated with the specified (x, y, z) coordinate. For a coordinate
+     * on a face boundary, the returned face is arbitrary but repeatable.
+     */
+    private static int xyzToFace(double x, double y, double z) {
+        double absX = Math.abs(x);
+        double absY = Math.abs(y);
+        double absZ = Math.abs(z);
+        if (absX > absY) {
+            if (absX > absZ) {
+                return (x < 0) ? 3 : 0;
+            }
+            return (z < 0) ? 5 : 2;
+        }
+        if (absY > absZ) {
+            return (y < 0) ? 4 : 1;
+        }
+        return (z < 0) ? 5 : 2;
+    }
+
+    private static int uToI(double u) {
+        double s;
+        if (u >= 0) {
+            s = 0.5 * Math.sqrt(1 + 3 * u);
+        } else {
+            s = 1 - 0.5 * Math.sqrt(1 - 3 * u);
+        }
+        return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
+    }
+
+    private static int vToJ(double v) {
+        // Same calculation as uToI.
+        return uToI(v);
+    }
+
+    private static int lookupBits(int i, int j, int k, int bits) {
+        bits += ((i >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << (LOOKUP_BITS + 2);
+        bits += ((j >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << 2;
+        return LOOKUP_POS[bits];
+    }
+
+    private static long updateBits(long sb, int k, int bits) {
+        return sb | ((((long) bits) >> 2) << ((k & 0x3) * 2 * LOOKUP_BITS));
+    }
+
+    private static int maskBits(int bits) {
+        return bits & (SWAP_MASK | INVERT_MASK);
+    }
+
+    private static int getFace(long s2CellId) {
+        return (int) (s2CellId >>> POS_BITS);
+    }
+
+    private static boolean isLeaf(long s2CellId) {
+        return ((int) s2CellId & LEAF_MASK) != 0;
+    }
+
+    private static double iToU(int i) {
+        int satI = Math.max(-1, Math.min(MAX_SIZE, i));
+        return Math.max(
+                -UV_LIMIT,
+                Math.min(UV_LIMIT, ONE_OVER_MAX_SIZE * ((satI << 1) + 1 - MAX_SIZE)));
+    }
+
+    private static double jToV(int j) {
+        // Same calculation as iToU.
+        return iToU(j);
+    }
+
+    private static long toIjo(long s2CellId) {
+        int face = getFace(s2CellId);
+        int bits = face & SWAP_MASK;
+        int i = 0;
+        int j = 0;
+        for (int k = 7; k >= 0; --k) {
+            int nbits = (k == 7) ? (MAX_LEVEL - 7 * LOOKUP_BITS) : LOOKUP_BITS;
+            bits += ((int) (s2CellId >>> (k * 2 * LOOKUP_BITS + 1)) & ((1 << (2 * nbits))
+                    - 1)) << 2;
+            bits = LOOKUP_IJ[bits];
+            i += (bits >> (LOOKUP_BITS + 2)) << (k * LOOKUP_BITS);
+            j += ((bits >> 2) & ((1 << LOOKUP_BITS) - 1)) << (k * LOOKUP_BITS);
+            bits &= (SWAP_MASK | INVERT_MASK);
+        }
+        int orientation =
+                ((getLowestOnBit(s2CellId) & 0x1111111111111110L) != 0) ? (bits ^ SWAP_MASK)
+                        : bits;
+        return (((long) i) << I_SHIFT) | (((long) j) << J_SHIFT) | orientation;
+    }
+
+    private static int ijoToI(long ijo) {
+        return (int) (ijo >>> I_SHIFT);
+    }
+
+    private static int ijoToJ(long ijo) {
+        return (int) ((ijo >>> J_SHIFT) & J_MASK);
+    }
+
+    private static int uShiftIntoI(double u) {
+        double s = 0.5 * (u + 1);
+        return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
+    }
+
+    private static int vShiftIntoJ(double v) {
+        // Same calculation as uShiftIntoI.
+        return uShiftIntoI(v);
+    }
+
+    private static int levelToSizeIj(int level) {
+        return 1 << (MAX_LEVEL - level);
+    }
+
+    private static void initLookupCells() {
+        initLookupCell(0, 0, 0, 0, 0, 0);
+        initLookupCell(0, 0, 0, SWAP_MASK, 0, SWAP_MASK);
+        initLookupCell(0, 0, 0, INVERT_MASK, 0, INVERT_MASK);
+        initLookupCell(0, 0, 0, SWAP_MASK | INVERT_MASK, 0, SWAP_MASK | INVERT_MASK);
+    }
+
+    private static void initLookupCell(
+            int level, int i, int j, int origOrientation, int pos, int orientation) {
+        if (level == LOOKUP_BITS) {
+            int ij = (i << LOOKUP_BITS) + j;
+            LOOKUP_POS[(ij << 2) + origOrientation] = (pos << 2) + orientation;
+            LOOKUP_IJ[(pos << 2) + origOrientation] = (ij << 2) + orientation;
+        } else {
+            level++;
+            i <<= 1;
+            j <<= 1;
+            pos <<= 2;
+            for (int subPos = 0; subPos < 4; subPos++) {
+                int ij = POS_TO_IJ[orientation][subPos];
+                int orientationMask = POS_TO_ORIENTATION[subPos];
+                initLookupCell(
+                        level,
+                        i + (ij >>> 1),
+                        j + (ij & 0x1),
+                        origOrientation,
+                        pos + subPos,
+                        orientation ^ orientationMask);
+            }
+        }
+    }
+
+    private static double calculateUvLimit() {
+        double machEps = 1.0;
+        do {
+            machEps /= 2.0f;
+        } while ((1.0 + (machEps / 2.0)) != 1.0);
+        return 1.0 + machEps;
+    }
+
+    @NonNull
+    private static UvTransform[] createUvTransforms() {
+        UvTransform[] uvTransforms = new UvTransform[NUM_FACES];
+        uvTransforms[0] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return y / x;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return z / x;
+                    }
+                };
+        uvTransforms[1] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return -x / y;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return z / y;
+                    }
+                };
+        uvTransforms[2] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return -x / z;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return -y / z;
+                    }
+                };
+        uvTransforms[3] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return z / x;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return y / x;
+                    }
+                };
+        uvTransforms[4] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return z / y;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return -x / y;
+                    }
+                };
+        uvTransforms[5] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return -y / z;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return -x / z;
+                    }
+                };
+        return uvTransforms;
+    }
+
+    @NonNull
+    private static XyzTransform[] createXyzTransforms() {
+        XyzTransform[] xyzTransforms = new XyzTransform[NUM_FACES];
+        xyzTransforms[0] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return 1;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return u;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return v;
+                    }
+                };
+        xyzTransforms[1] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return -u;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return 1;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return v;
+                    }
+                };
+        xyzTransforms[2] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return -u;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return -v;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return 1;
+                    }
+                };
+        xyzTransforms[3] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return -1;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return -v;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return -u;
+                    }
+                };
+        xyzTransforms[4] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return v;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return -1;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return -u;
+                    }
+                };
+        xyzTransforms[5] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return v;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return u;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return -1;
+                    }
+                };
+        return xyzTransforms;
+    }
+
+    /**
+     * Transform from (x, y, z) coordinates to (u, v) coordinates, indexed by face. For a
+     * (x, y, z) coordinate within a face, each element of the resulting (u, v) coordinate
+     * should lie in the inclusive range [-1, 1], with the face center having a (u, v)
+     * coordinate equal to (0, 0).
+     */
+    private interface UvTransform {
+
+        /**
+         * Returns for the specified (x, y, z) coordinate the corresponding u-coordinate
+         * (which may lie outside the range [-1, 1]).
+         */
+        double xyzToU(double x, double y, double z);
+
+        /**
+         * Returns for the specified (x, y, z) coordinate the corresponding v-coordinate
+         * (which may lie outside the range [-1, 1]).
+         */
+        double xyzToV(double x, double y, double z);
+    }
+
+    /**
+     * Transform from (u, v) coordinates to (x, y, z) coordinates, indexed by face. The
+     * resulting vectors are not necessarily of unit length.
+     */
+    private interface XyzTransform {
+
+        /** Returns for the specified (u, v) coordinate the corresponding x-coordinate. */
+        double uvToX(double u, double v);
+
+        /** Returns for the specified (u, v) coordinate the corresponding y-coordinate. */
+        double uvToY(double u, double v);
+
+        /** Returns for the specified (u, v) coordinate the corresponding z-coordinate. */
+        double uvToZ(double u, double v);
+    }
+}
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 3d08959..3b30b1d 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -89,6 +89,7 @@
     public static final int TYPE_USB_ACCESSORY    = 12;
     /**
      * A device type describing the audio device associated with a dock.
+     * @see #TYPE_DOCK_ANALOG
      */
     public static final int TYPE_DOCK             = 13;
     /**
@@ -182,6 +183,11 @@
      */
     public static final int TYPE_BLE_BROADCAST   = 30;
 
+    /**
+     * A device type describing the audio device associated with a dock using an analog connection.
+     */
+    public static final int TYPE_DOCK_ANALOG = 31;
+
     /** @hide */
     @IntDef(flag = false, prefix = "TYPE", value = {
             TYPE_BUILTIN_EARPIECE,
@@ -213,7 +219,8 @@
             TYPE_BLE_HEADSET,
             TYPE_BLE_SPEAKER,
             TYPE_ECHO_REFERENCE,
-            TYPE_BLE_BROADCAST}
+            TYPE_BLE_BROADCAST,
+            TYPE_DOCK_ANALOG}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceType {}
@@ -240,7 +247,8 @@
             TYPE_BLE_HEADSET,
             TYPE_HDMI_ARC,
             TYPE_HDMI_EARC,
-            TYPE_ECHO_REFERENCE}
+            TYPE_ECHO_REFERENCE,
+            TYPE_DOCK_ANALOG}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceTypeIn {}
@@ -271,7 +279,8 @@
             TYPE_BUILTIN_SPEAKER_SAFE,
             TYPE_BLE_HEADSET,
             TYPE_BLE_SPEAKER,
-            TYPE_BLE_BROADCAST}
+            TYPE_BLE_BROADCAST,
+            TYPE_DOCK_ANALOG}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceTypeOut {}
@@ -304,6 +313,7 @@
             case TYPE_BLE_HEADSET:
             case TYPE_BLE_SPEAKER:
             case TYPE_BLE_BROADCAST:
+            case TYPE_DOCK_ANALOG:
                 return true;
             default:
                 return false;
@@ -334,6 +344,7 @@
             case TYPE_HDMI_ARC:
             case TYPE_HDMI_EARC:
             case TYPE_ECHO_REFERENCE:
+            case TYPE_DOCK_ANALOG:
                 return true;
             default:
                 return false;
@@ -573,7 +584,7 @@
    /**
      * @return The device type identifier of the audio device (i.e. TYPE_BUILTIN_SPEAKER).
      */
-    public int getType() {
+    public @AudioDeviceType int getType() {
         return INT_TO_EXT_DEVICE_MAPPING.get(mPort.type(), TYPE_UNKNOWN);
     }
 
@@ -624,7 +635,7 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES, TYPE_BLUETOOTH_A2DP);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER, TYPE_BLUETOOTH_A2DP);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_HDMI, TYPE_HDMI);
-        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET, TYPE_DOCK);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET, TYPE_DOCK_ANALOG);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET, TYPE_DOCK);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_USB_ACCESSORY, TYPE_USB_ACCESSORY);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_USB_DEVICE, TYPE_USB_DEVICE);
@@ -652,7 +663,7 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_HDMI, TYPE_HDMI);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_TELEPHONY_RX, TYPE_TELEPHONY);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BACK_MIC, TYPE_BUILTIN_MIC);
-        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET, TYPE_DOCK);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET, TYPE_DOCK_ANALOG);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_DGTL_DOCK_HEADSET, TYPE_DOCK);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_USB_ACCESSORY, TYPE_USB_ACCESSORY);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_USB_DEVICE, TYPE_USB_DEVICE);
@@ -687,7 +698,8 @@
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_USB_DEVICE, AudioSystem.DEVICE_OUT_USB_DEVICE);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_USB_HEADSET, AudioSystem.DEVICE_OUT_USB_HEADSET);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_USB_ACCESSORY, AudioSystem.DEVICE_OUT_USB_ACCESSORY);
-        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_DOCK, AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_DOCK, AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_DOCK_ANALOG, AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_FM, AudioSystem.DEVICE_OUT_FM);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_TELEPHONY, AudioSystem.DEVICE_OUT_TELEPHONY_TX);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_AUX_LINE, AudioSystem.DEVICE_OUT_AUX_LINE);
@@ -710,7 +722,9 @@
                 TYPE_WIRED_HEADSET, AudioSystem.DEVICE_IN_WIRED_HEADSET);
         EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_HDMI, AudioSystem.DEVICE_IN_HDMI);
         EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_TELEPHONY, AudioSystem.DEVICE_IN_TELEPHONY_RX);
-        EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_DOCK, AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET);
+        EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_DOCK, AudioSystem.DEVICE_IN_DGTL_DOCK_HEADSET);
+        EXT_TO_INT_INPUT_DEVICE_MAPPING.put(
+                TYPE_DOCK_ANALOG, AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET);
         EXT_TO_INT_INPUT_DEVICE_MAPPING.put(
                 TYPE_USB_ACCESSORY, AudioSystem.DEVICE_IN_USB_ACCESSORY);
         EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_USB_DEVICE, AudioSystem.DEVICE_IN_USB_DEVICE);
diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java
index 40f6dc5..4e2ce91 100644
--- a/media/java/android/media/AudioDeviceVolumeManager.java
+++ b/media/java/android/media/AudioDeviceVolumeManager.java
@@ -21,7 +21,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.IBinder;
@@ -44,8 +43,7 @@
 @SystemApi
 public class AudioDeviceVolumeManager {
 
-    // define when using Log.*
-    //private static final String TAG = "AudioDeviceVolumeManager";
+    private static final String TAG = "AudioDeviceVolumeManager";
 
     /** @hide
      * Indicates no special treatment in the handling of the volume adjustment */
@@ -70,20 +68,15 @@
     private static IAudioService sService;
 
     private final @NonNull String mPackageName;
-    private final @Nullable String mAttributionTag;
 
     /**
+     * @hide
      * Constructor
      * @param context the Context for the device volume operations
      */
-    @SuppressLint("ManagerConstructor")
-    // reason for suppression: even though the functionality handled by this class is implemented in
-    // AudioService, we want to avoid bloating android.media.AudioManager
-    // with @SystemApi functionality
     public AudioDeviceVolumeManager(@NonNull Context context) {
         Objects.requireNonNull(context);
         mPackageName = context.getApplicationContext().getOpPackageName();
-        mAttributionTag = context.getApplicationContext().getAttributionTag();
     }
 
     /**
@@ -325,10 +318,11 @@
      * @param ada the device for which volume is to be modified
      */
     @SystemApi
+    // TODO alternatively require MODIFY_AUDIO_SYSTEM_SETTINGS when defined
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) {
         try {
-            getService().setDeviceVolume(vi, ada, mPackageName, mAttributionTag);
+            getService().setDeviceVolume(vi, ada, mPackageName);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
@@ -336,6 +330,30 @@
 
     /**
      * @hide
+     * Returns the volume on the given audio device for the given volume information.
+     * For instance if using a {@link VolumeInfo} configured for {@link AudioManager#STREAM_ALARM},
+     * it will return the alarm volume. When no volume index has ever been set for the given
+     * device, the default volume will be returned (the volume setting that would have been
+     * applied if playback for that use case had started).
+     * @param vi the volume information, only stream-based volumes are supported. Information
+     *           other than the stream type is ignored.
+     * @param ada the device for which volume is to be retrieved
+     */
+    @SystemApi
+    // TODO alternatively require MODIFY_AUDIO_SYSTEM_SETTINGS when defined
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi,
+            @NonNull AudioDeviceAttributes ada) {
+        try {
+            return getService().getDeviceVolume(vi, ada, mPackageName);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return VolumeInfo.getDefaultVolumeInfo();
+    }
+
+    /**
+     * @hide
      * Return human-readable name for volume behavior
      * @param behavior one of the volume behaviors defined in AudioManager
      * @return a string for the given behavior
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 980acca..ccd4ed0 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -350,10 +350,29 @@
     public static final int ENCODING_MPEGH_LC_L3 = 25;
     /** Audio data format: MPEG-H low complexity profile, level 4 */
     public static final int ENCODING_MPEGH_LC_L4 = 26;
-    /** Audio data format: DTS UHD compressed */
-    public static final int ENCODING_DTS_UHD = 27;
+    /** Audio data format: DTS UHD Profile-1 compressed (aka DTS:X Profile 1)
+     * Has the same meaning and value as ENCODING_DTS_UHD_P1.
+     * @deprecated Use {@link #ENCODING_DTS_UHD_P1} instead. */
+    @Deprecated public static final int ENCODING_DTS_UHD = 27;
     /** Audio data format: DRA compressed */
     public static final int ENCODING_DRA = 28;
+    /** Audio data format: DTS HD Master Audio compressed
+     * DTS HD Master Audio stream is variable bit rate and contains lossless audio.
+     * Use {@link #ENCODING_DTS_HD_MA} for lossless audio content (DTS-HD MA Lossless)
+     * and use {@link #ENCODING_DTS_HD} for other DTS bitstreams with extension substream
+     * (DTS 8Ch Discrete, DTS Hi Res, DTS Express). */
+    public static final int ENCODING_DTS_HD_MA = 29;
+    /** Audio data format: DTS UHD Profile-1 compressed (aka DTS:X Profile 1)
+     * Has the same meaning and value as the deprecated {@link #ENCODING_DTS_UHD}.*/
+    public static final int ENCODING_DTS_UHD_P1 = 27;
+    /** Audio data format: DTS UHD Profile-2 compressed
+     * DTS-UHD Profile-2 supports delivery of Channel-Based Audio, Object-Based Audio
+     * and High Order Ambisonic presentations up to the fourth order.
+     * Use {@link #ENCODING_DTS_UHD_P1} to transmit DTS UHD Profile 1 (aka DTS:X Profile 1)
+     * bitstream.
+     * Use {@link #ENCODING_DTS_UHD_P2} to transmit DTS UHD Profile 2 (aka DTS:X Profile 2)
+     * bitstream. */
+    public static final int ENCODING_DTS_UHD_P2 = 30;
 
     /** @hide */
     public static String toLogFriendlyEncoding(int enc) {
@@ -410,10 +429,14 @@
                 return "ENCODING_MPEGH_LC_L3";
             case ENCODING_MPEGH_LC_L4:
                 return "ENCODING_MPEGH_LC_L4";
-            case ENCODING_DTS_UHD:
-                return "ENCODING_DTS_UHD";
+            case ENCODING_DTS_UHD_P1:
+                return "ENCODING_DTS_UHD_P1";
             case ENCODING_DRA:
                 return "ENCODING_DRA";
+            case ENCODING_DTS_HD_MA:
+                return "ENCODING_DTS_HD_MA";
+            case ENCODING_DTS_UHD_P2:
+                return "ENCODING_DTS_UHD_P2";
             default :
                 return "invalid encoding " + enc;
         }
@@ -771,8 +794,10 @@
             case ENCODING_MPEGH_BL_L4:
             case ENCODING_MPEGH_LC_L3:
             case ENCODING_MPEGH_LC_L4:
-            case ENCODING_DTS_UHD:
+            case ENCODING_DTS_UHD_P1:
             case ENCODING_DRA:
+            case ENCODING_DTS_HD_MA:
+            case ENCODING_DTS_UHD_P2:
                 return true;
             default:
                 return false;
@@ -808,8 +833,10 @@
             case ENCODING_MPEGH_BL_L4:
             case ENCODING_MPEGH_LC_L3:
             case ENCODING_MPEGH_LC_L4:
-            case ENCODING_DTS_UHD:
+            case ENCODING_DTS_UHD_P1:
             case ENCODING_DRA:
+            case ENCODING_DTS_HD_MA:
+            case ENCODING_DTS_UHD_P2:
                 return true;
             default:
                 return false;
@@ -848,8 +875,10 @@
             case ENCODING_MPEGH_BL_L4:
             case ENCODING_MPEGH_LC_L3:
             case ENCODING_MPEGH_LC_L4:
-            case ENCODING_DTS_UHD:
+            case ENCODING_DTS_UHD_P1:
             case ENCODING_DRA:
+            case ENCODING_DTS_HD_MA:
+            case ENCODING_DTS_UHD_P2:
                 return false;
             case ENCODING_INVALID:
             default:
@@ -888,8 +917,10 @@
             case ENCODING_MPEGH_BL_L4:
             case ENCODING_MPEGH_LC_L3:
             case ENCODING_MPEGH_LC_L4:
-            case ENCODING_DTS_UHD:
+            case ENCODING_DTS_UHD_P1:
             case ENCODING_DRA:
+            case ENCODING_DTS_HD_MA:
+            case ENCODING_DTS_UHD_P2:
                 return false;
             case ENCODING_INVALID:
             default:
@@ -1176,8 +1207,10 @@
                 case ENCODING_MPEGH_BL_L4:
                 case ENCODING_MPEGH_LC_L3:
                 case ENCODING_MPEGH_LC_L4:
-                case ENCODING_DTS_UHD:
+                case ENCODING_DTS_UHD_P1:
                 case ENCODING_DRA:
+                case ENCODING_DTS_HD_MA:
+                case ENCODING_DTS_UHD_P2:
                     mEncoding = encoding;
                     break;
                 case ENCODING_INVALID:
@@ -1405,8 +1438,10 @@
         ENCODING_MPEGH_BL_L4,
         ENCODING_MPEGH_LC_L3,
         ENCODING_MPEGH_LC_L4,
-        ENCODING_DTS_UHD,
-        ENCODING_DRA }
+        ENCODING_DTS_UHD_P1,
+        ENCODING_DRA,
+        ENCODING_DTS_HD_MA,
+        ENCODING_DTS_UHD_P2 }
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface Encoding {}
@@ -1426,8 +1461,10 @@
             ENCODING_MPEGH_BL_L4,
             ENCODING_MPEGH_LC_L3,
             ENCODING_MPEGH_LC_L4,
-            ENCODING_DTS_UHD,
-            ENCODING_DRA
+            ENCODING_DTS_UHD_P1,
+            ENCODING_DRA,
+            ENCODING_DTS_HD_MA,
+            ENCODING_DTS_UHD_P2
     };
 
     /** @hide */
@@ -1445,8 +1482,10 @@
             ENCODING_MPEGH_BL_L4,
             ENCODING_MPEGH_LC_L3,
             ENCODING_MPEGH_LC_L4,
-            ENCODING_DTS_UHD,
-            ENCODING_DRA }
+            ENCODING_DTS_UHD_P1,
+            ENCODING_DRA,
+            ENCODING_DTS_HD_MA,
+            ENCODING_DTS_UHD_P2 }
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface SurroundSoundEncoding {}
@@ -1488,10 +1527,14 @@
                 return "MPEG-H 3D Audio low complexity profile level 3";
             case ENCODING_MPEGH_LC_L4:
                 return "MPEG-H 3D Audio low complexity profile level 4";
-            case ENCODING_DTS_UHD:
-                return "DTS UHD";
+            case ENCODING_DTS_UHD_P1:
+                return "DTS UHD Profile 1";
             case ENCODING_DRA:
                 return "DRA";
+            case ENCODING_DTS_HD_MA:
+                return "DTS HD Master Audio";
+            case ENCODING_DTS_UHD_P2:
+                return "DTS UHD Profile 2";
             default:
                 return "Unknown surround sound format";
         }
diff --git a/media/java/android/media/AudioHalVersionInfo.aidl b/media/java/android/media/AudioHalVersionInfo.aidl
new file mode 100644
index 0000000..a83f8c8
--- /dev/null
+++ b/media/java/android/media/AudioHalVersionInfo.aidl
@@ -0,0 +1,18 @@
+/* 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.media;
+
+parcelable AudioHalVersionInfo;
diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java
new file mode 100644
index 0000000..985a758
--- /dev/null
+++ b/media/java/android/media/AudioHalVersionInfo.java
@@ -0,0 +1,174 @@
+/*
+ * 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.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Defines the audio HAL version.
+ *
+ * @hide
+ */
+@TestApi
+public final class AudioHalVersionInfo implements Parcelable, Comparable<AudioHalVersionInfo> {
+    /**
+     * Indicate the audio HAL is implemented with HIDL (HAL interface definition language).
+     *
+     * @see <a href="https://source.android.com/docs/core/architecture/hidl/">HIDL</a>
+     *     <p>The value of AUDIO_HAL_TYPE_HIDL should match the value of {@link
+     *     android.media.AudioHalVersion.Type#HIDL}.
+     */
+    public static final int AUDIO_HAL_TYPE_HIDL = 0;
+
+    /**
+     * Indicate the audio HAL is implemented with AIDL (Android Interface Definition Language).
+     *
+     * @see <a href="https://source.android.com/docs/core/architecture/aidl/">AIDL</a>
+     *     <p>The value of AUDIO_HAL_TYPE_AIDL should match the value of {@link
+     *     android.media.AudioHalVersion.Type#AIDL}.
+     */
+    public static final int AUDIO_HAL_TYPE_AIDL = 1;
+
+    /** @hide */
+    @IntDef(
+            flag = false,
+            prefix = "AUDIO_HAL_TYPE_",
+            value = {AUDIO_HAL_TYPE_HIDL, AUDIO_HAL_TYPE_AIDL})
+    public @interface AudioHalType {}
+
+    /** AudioHalVersionInfo object of all valid Audio HAL versions. */
+    public static final @NonNull AudioHalVersionInfo AIDL_1_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_AIDL, 1 /* major */, 0 /* minor */);
+
+    public static final @NonNull AudioHalVersionInfo HIDL_7_1 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 7 /* major */, 1 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_7_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 7 /* major */, 0 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_6_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 6 /* major */, 0 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_5_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 5 /* major */, 0 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_4_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 4 /* major */, 0 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_2_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 2 /* major */, 0 /* minor */);
+
+    /**
+     * List of all valid Audio HAL versions. This list need to be in sync with sAudioHALVersions
+     * defined in frameworks/av/media/libaudiohal/FactoryHalHidl.cpp.
+     */
+    // TODO: add AIDL_1_0 with sAudioHALVersions.
+    public static final @NonNull List<AudioHalVersionInfo> VERSIONS =
+            List.of(HIDL_7_1, HIDL_7_0, HIDL_6_0, HIDL_5_0, HIDL_4_0);
+
+    private static final String TAG = "AudioHalVersionInfo";
+    private AudioHalVersion mHalVersion = new AudioHalVersion();
+
+    public @AudioHalType int getHalType() {
+        return mHalVersion.type;
+    }
+
+    public int getMajorVersion() {
+        return mHalVersion.major;
+    }
+
+    public int getMinorVersion() {
+        return mHalVersion.minor;
+    }
+
+    /** String representative of AudioHalVersion.Type */
+    private static @NonNull String typeToString(@AudioHalType int type) {
+        if (type == AudioHalVersion.Type.HIDL) {
+            return "HIDL";
+        } else if (type == AudioHalVersion.Type.AIDL) {
+            return "AIDL";
+        } else {
+            return "INVALID";
+        }
+    }
+
+    /** String representative of type, major and minor */
+    private static @NonNull String toString(@AudioHalType int type, int major, int minor) {
+        return typeToString(type) + ":" + Integer.toString(major) + "." + Integer.toString(minor);
+    }
+
+    private AudioHalVersionInfo(@AudioHalType int type, int major, int minor) {
+        mHalVersion.type = type;
+        mHalVersion.major = major;
+        mHalVersion.minor = minor;
+    }
+
+    private AudioHalVersionInfo(Parcel in) {
+        mHalVersion = in.readTypedObject(AudioHalVersion.CREATOR);
+    }
+
+    /** String representative of this (AudioHalVersionInfo) object */
+    @Override
+    public String toString() {
+        return toString(mHalVersion.type, mHalVersion.major, mHalVersion.minor);
+    }
+
+    /**
+     * Compare two HAL versions by comparing their index in VERSIONS.
+     *
+     * <p>Normally all AudioHalVersionInfo object to compare should exist in the VERSIONS list. If
+     * both candidates exist in the VERSIONS list, smaller index means newer. Any candidate not
+     * exist in the VERSIONS list will be considered to be oldest version.
+     *
+     * @return 0 if the HAL version is the same as the other HAL version. Positive if the HAL
+     *     version is newer than the other HAL version. Negative if the HAL version is older than
+     *     the other version.
+     */
+    @Override
+    public int compareTo(@NonNull AudioHalVersionInfo other) {
+        int indexOther = VERSIONS.indexOf(other);
+        int indexThis = VERSIONS.indexOf(this);
+        if (indexThis < 0 || indexOther < 0) {
+            return indexThis - indexOther;
+        }
+        return indexOther - indexThis;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull android.os.Parcel out, int flag) {
+        out.writeTypedObject(mHalVersion, flag);
+    }
+
+    public static final @NonNull Parcelable.Creator<AudioHalVersionInfo> CREATOR =
+            new Parcelable.Creator<AudioHalVersionInfo>() {
+                @Override
+                public AudioHalVersionInfo createFromParcel(@NonNull Parcel in) {
+                    return new AudioHalVersionInfo(in);
+                }
+
+                @Override
+                public AudioHalVersionInfo[] newArray(int size) {
+                    return new AudioHalVersionInfo[size];
+                }
+            };
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index d975e96..ae0d45f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1212,7 +1212,13 @@
         }
     }
 
-    private static boolean isPublicStreamType(int streamType) {
+    /**
+     * @hide
+     * Checks whether a stream type is part of the public SDK
+     * @param streamType
+     * @return true if the stream type is available in SDK
+     */
+    public static boolean isPublicStreamType(int streamType) {
         switch (streamType) {
             case STREAM_VOICE_CALL:
             case STREAM_SYSTEM:
@@ -2328,10 +2334,9 @@
         return AudioSystem.SUCCESS;
     }
 
-    private final Map<Integer, Object> mDevRoleForCapturePresetListeners = new HashMap<>(){{
-            put(AudioSystem.DEVICE_ROLE_PREFERRED,
-                    new DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>());
-        }};
+    private final Map<Integer, Object> mDevRoleForCapturePresetListeners = Map.of(
+            AudioSystem.DEVICE_ROLE_PREFERRED,
+            new DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>());
 
     private class DevRoleListenerInfo<T> {
         final @NonNull Executor mExecutor;
@@ -6515,15 +6520,17 @@
     // AudioPort implementation
     //
 
-    static final int AUDIOPORT_GENERATION_INIT = 0;
-    static Integer sAudioPortGeneration = new Integer(AUDIOPORT_GENERATION_INIT);
-    static ArrayList<AudioPort> sAudioPortsCached = new ArrayList<AudioPort>();
-    static ArrayList<AudioPort> sPreviousAudioPortsCached = new ArrayList<AudioPort>();
-    static ArrayList<AudioPatch> sAudioPatchesCached = new ArrayList<AudioPatch>();
+    private static final int AUDIOPORT_GENERATION_INIT = 0;
+    private static Object sAudioPortGenerationLock = new Object();
+    @GuardedBy("sAudioPortGenerationLock")
+    private static int sAudioPortGeneration = AUDIOPORT_GENERATION_INIT;
+    private static ArrayList<AudioPort> sAudioPortsCached = new ArrayList<AudioPort>();
+    private static ArrayList<AudioPort> sPreviousAudioPortsCached = new ArrayList<AudioPort>();
+    private static ArrayList<AudioPatch> sAudioPatchesCached = new ArrayList<AudioPatch>();
 
     static int resetAudioPortGeneration() {
         int generation;
-        synchronized (sAudioPortGeneration) {
+        synchronized (sAudioPortGenerationLock) {
             generation = sAudioPortGeneration;
             sAudioPortGeneration = AUDIOPORT_GENERATION_INIT;
         }
@@ -6533,7 +6540,7 @@
     static int updateAudioPortCache(ArrayList<AudioPort> ports, ArrayList<AudioPatch> patches,
                                     ArrayList<AudioPort> previousPorts) {
         sAudioPortEventHandler.init();
-        synchronized (sAudioPortGeneration) {
+        synchronized (sAudioPortGenerationLock) {
 
             if (sAudioPortGeneration == AUDIOPORT_GENERATION_INIT) {
                 int[] patchGeneration = new int[1];
@@ -7663,8 +7670,10 @@
      * or video calls. This method can be used by voice or video chat applications to select a
      * different audio device than the one selected by default by the platform.
      * <p>The device selection is expressed as an {@link AudioDeviceInfo} among devices returned by
-     * {@link #getAvailableCommunicationDevices()}.
-     * The selection is active as long as the requesting application process lives, until
+     * {@link #getAvailableCommunicationDevices()}. Note that only devices in a sink role
+     * (AKA output devices, see {@link AudioDeviceInfo#isSink()}) can be specified. The matching
+     * source device is selected automatically by the platform.
+     * <p>The selection is active as long as the requesting application process lives, until
      * {@link #clearCommunicationDevice} is called or until the device is disconnected.
      * It is therefore important for applications to clear the request when a call ends or the
      * the requesting activity or service is stopped or destroyed.
@@ -8493,13 +8502,14 @@
     }
 
     /**
-     * Returns the audio HAL version in the form MAJOR.MINOR. If there is no audio HAL found, null
-     * will be returned.
+     * Returns an {@link AudioHalVersionInfo} indicating the Audio Hal Version. If there is no audio
+     * HAL found, null will be returned.
      *
+     * @return @see @link #AudioHalVersionInfo The version of Audio HAL.
      * @hide
      */
     @TestApi
-    public static @Nullable String getHalVersion() {
+    public static @Nullable AudioHalVersionInfo getHalVersion() {
         try {
             return getService().getHalVersion();
         } catch (RemoteException e) {
diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java
index ca175b4..0f962f9 100644
--- a/media/java/android/media/AudioMetadata.java
+++ b/media/java/android/media/AudioMetadata.java
@@ -30,6 +30,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -446,14 +447,13 @@
     // BaseMap is corresponding to audio_utils::metadata::Data
     private static final int AUDIO_METADATA_OBJ_TYPE_BASEMAP = 6;
 
-    private static final HashMap<Class, Integer> AUDIO_METADATA_OBJ_TYPES = new HashMap<>() {{
-            put(Integer.class, AUDIO_METADATA_OBJ_TYPE_INT);
-            put(Long.class, AUDIO_METADATA_OBJ_TYPE_LONG);
-            put(Float.class, AUDIO_METADATA_OBJ_TYPE_FLOAT);
-            put(Double.class, AUDIO_METADATA_OBJ_TYPE_DOUBLE);
-            put(String.class, AUDIO_METADATA_OBJ_TYPE_STRING);
-            put(BaseMap.class, AUDIO_METADATA_OBJ_TYPE_BASEMAP);
-        }};
+    private static final Map<Class, Integer> AUDIO_METADATA_OBJ_TYPES = Map.of(
+            Integer.class, AUDIO_METADATA_OBJ_TYPE_INT,
+            Long.class, AUDIO_METADATA_OBJ_TYPE_LONG,
+            Float.class, AUDIO_METADATA_OBJ_TYPE_FLOAT,
+            Double.class, AUDIO_METADATA_OBJ_TYPE_DOUBLE,
+            String.class, AUDIO_METADATA_OBJ_TYPE_STRING,
+            BaseMap.class, AUDIO_METADATA_OBJ_TYPE_BASEMAP);
 
     private static final Charset AUDIO_METADATA_CHARSET = StandardCharsets.UTF_8;
 
@@ -634,8 +634,8 @@
      *     Datum corresponds to Object
      ****************************************************************************************/
 
-    private static final HashMap<Integer, DataPackage<?>> DATA_PACKAGES = new HashMap<>() {{
-            put(AUDIO_METADATA_OBJ_TYPE_INT, new DataPackage<Integer>() {
+    private static final Map<Integer, DataPackage<?>> DATA_PACKAGES = Map.of(
+            AUDIO_METADATA_OBJ_TYPE_INT, new DataPackage<Integer>() {
                 @Override
                 @Nullable
                 public Integer unpack(ByteBuffer buffer) {
@@ -647,8 +647,8 @@
                     output.putInt(obj);
                     return true;
                 }
-            });
-            put(AUDIO_METADATA_OBJ_TYPE_LONG, new DataPackage<Long>() {
+            },
+            AUDIO_METADATA_OBJ_TYPE_LONG, new DataPackage<Long>() {
                 @Override
                 @Nullable
                 public Long unpack(ByteBuffer buffer) {
@@ -660,8 +660,8 @@
                     output.putLong(obj);
                     return true;
                 }
-            });
-            put(AUDIO_METADATA_OBJ_TYPE_FLOAT, new DataPackage<Float>() {
+            },
+            AUDIO_METADATA_OBJ_TYPE_FLOAT, new DataPackage<Float>() {
                 @Override
                 @Nullable
                 public Float unpack(ByteBuffer buffer) {
@@ -673,8 +673,8 @@
                     output.putFloat(obj);
                     return true;
                 }
-            });
-            put(AUDIO_METADATA_OBJ_TYPE_DOUBLE, new DataPackage<Double>() {
+            },
+            AUDIO_METADATA_OBJ_TYPE_DOUBLE, new DataPackage<Double>() {
                 @Override
                 @Nullable
                 public Double unpack(ByteBuffer buffer) {
@@ -686,8 +686,8 @@
                     output.putDouble(obj);
                     return true;
                 }
-            });
-            put(AUDIO_METADATA_OBJ_TYPE_STRING, new DataPackage<String>() {
+            },
+            AUDIO_METADATA_OBJ_TYPE_STRING, new DataPackage<String>() {
                 @Override
                 @Nullable
                 public String unpack(ByteBuffer buffer) {
@@ -713,9 +713,9 @@
                     output.put(valueArr);
                     return true;
                 }
-            });
-            put(AUDIO_METADATA_OBJ_TYPE_BASEMAP, new BaseMapPackage());
-        }};
+            },
+            AUDIO_METADATA_OBJ_TYPE_BASEMAP, new BaseMapPackage());
+
     // ObjectPackage is a special case that it is expected to unpack audio_utils::metadata::Datum,
     // which contains data type and data size besides the payload for the data.
     private static final ObjectPackage OBJECT_PACKAGE = new ObjectPackage();
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 819358b..59a0f7b 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,52 @@
 
     /**
      * @hide
-     * Mute state used for anonymization.
-     */
-    public static final int PLAYER_MUTE_INVALID = -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 +310,7 @@
         mPlayerType = pic.mPlayerType;
         mClientUid = uid;
         mClientPid = pid;
-        mMutedState = PLAYER_MUTE_INVALID;
+        mMutedState = 0;
         mDeviceId = PLAYER_DEVICEID_INVALID;
         mPlayerState = PLAYER_STATE_IDLE;
         mPlayerAttr = pic.mAttributes;
@@ -352,7 +359,7 @@
         anonymCopy.mPlayerAttr = builder.build();
         anonymCopy.mDeviceId = in.mDeviceId;
         // anonymized data
-        anonymCopy.mMutedState = PLAYER_MUTE_INVALID;
+        anonymCopy.mMutedState = 0;
         anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
         anonymCopy.mClientUid = PLAYER_UPID_INVALID;
         anonymCopy.mClientPid = PLAYER_UPID_INVALID;
@@ -413,9 +420,26 @@
 
     /**
      * @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;
+    }
+
+    /**
+     * @hide
+     * Returns a bitmask expressing the mute state as a combination of MUTED_BY_* flags.
+     * <br>A value of 0 corresponds to an unmuted player.
+     * @return the mute state.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @PlayerMuteEvent public int getMutedBy() {
         return mMutedState;
     }
 
@@ -569,6 +593,12 @@
         }
     }
 
+    private boolean isMuteAffectingActiveState() {
+        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 +610,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 +713,25 @@
                 "/").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 ");
-        } else if (mMutedState == 0) {
+        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/AudioPresentation.java b/media/java/android/media/AudioPresentation.java
index 47358be..05f3c5a 100644
--- a/media/java/android/media/AudioPresentation.java
+++ b/media/java/android/media/AudioPresentation.java
@@ -54,7 +54,11 @@
     private final int mProgramId;
     private final ULocale mLanguage;
 
-    /** @hide */
+    /**
+     * The ContentClassifier int definitions represent the AudioPresentation content
+     * classifier (as per TS 103 190-1 v1.2.1 4.3.3.8.1)
+     * @hide
+     */
     @IntDef(
         value = {
         CONTENT_UNKNOWN,
@@ -67,11 +71,6 @@
         CONTENT_EMERGENCY,
         CONTENT_VOICEOVER,
     })
-
-    /**
-     * The ContentClassifier int definitions represent the AudioPresentation content
-     * classifier (as per TS 103 190-1 v1.2.1 4.3.3.8.1)
-    */
     @Retention(RetentionPolicy.SOURCE)
     public @interface ContentClassifier {}
 
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 6764890..a48edac 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -438,6 +438,22 @@
                 return "AUDIO_FORMAT_APTX_TWSP";
             case /* AUDIO_FORMAT_LC3             */ 0x2B000000:
                 return "AUDIO_FORMAT_LC3";
+            case /* AUDIO_FORMAT_MPEGH           */ 0x2C000000:
+                return "AUDIO_FORMAT_MPEGH";
+            case /* AUDIO_FORMAT_IEC60958        */ 0x2D000000:
+                return "AUDIO_FORMAT_IEC60958";
+            case /* AUDIO_FORMAT_DTS_UHD         */ 0x2E000000:
+                return "AUDIO_FORMAT_DTS_UHD";
+            case /* AUDIO_FORMAT_DRA             */ 0x2F000000:
+                return "AUDIO_FORMAT_DRA";
+            case /* AUDIO_FORMAT_APTX_ADAPTIVE_QLEA */ 0x30000000:
+                return "AUDIO_FORMAT_APTX_ADAPTIVE_QLEA";
+            case /* AUDIO_FORMAT_APTX_ADAPTIVE_R4   */ 0x31000000:
+                return "AUDIO_FORMAT_APTX_ADAPTIVE_R4";
+            case /* AUDIO_FORMAT_DTS_HD_MA       */ 0x32000000:
+                return "AUDIO_FORMAT_DTS_HD_MA";
+            case /* AUDIO_FORMAT_DTS_UHD_P2      */ 0x33000000:
+                return "AUDIO_FORMAT_DTS_UHD_P2";
 
             /* Aliases */
             case /* AUDIO_FORMAT_PCM_16_BIT        */ 0x1:
@@ -510,10 +526,14 @@
                 return "AUDIO_FORMAT_MAT_2_0"; // (MAT | MAT_SUB_2_0)
             case /* AUDIO_FORMAT_MAT_2_1           */ 0x24000003:
                 return "AUDIO_FORMAT_MAT_2_1"; // (MAT | MAT_SUB_2_1)
-            case /* AUDIO_FORMAT_DTS_UHD */           0x2E000000:
-                return "AUDIO_FORMAT_DTS_UHD";
-            case /* AUDIO_FORMAT_DRA */           0x2F000000:
-                return "AUDIO_FORMAT_DRA";
+            case /* AUDIO_FORMAT_MPEGH_SUB_BL_L3   */ 0x2C000013:
+                return "AUDIO_FORMAT_MPEGH_SUB_BL_L3";
+            case /* AUDIO_FORMAT_MPEGH_SUB_BL_L4   */ 0x2C000014:
+                return "AUDIO_FORMAT_MPEGH_SUB_BL_L4";
+            case /* AUDIO_FORMAT_MPEGH_SUB_LC_L3   */ 0x2C000023:
+                return "AUDIO_FORMAT_MPEGH_SUB_LC_L3";
+            case /* AUDIO_FORMAT_MPEGH_SUB_LC_L4   */ 0x2C000024:
+                return "AUDIO_FORMAT_MPEGH_SUB_LC_L4";
             default:
                 return "AUDIO_FORMAT_(" + audioFormat + ")";
         }
@@ -2407,4 +2427,3 @@
      */
     final static int NATIVE_EVENT_ROUTING_CHANGE = 1000;
 }
-
diff --git a/media/java/android/media/AudioTimestamp.aidl b/media/java/android/media/AudioTimestamp.aidl
new file mode 100644
index 0000000..2c161b3
--- /dev/null
+++ b/media/java/android/media/AudioTimestamp.aidl
@@ -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.
+ */
+
+package android.media;
+
+parcelable AudioTimestamp;
diff --git a/media/java/android/media/AudioTimestamp.java b/media/java/android/media/AudioTimestamp.java
index be8ca15..a3277e5 100644
--- a/media/java/android/media/AudioTimestamp.java
+++ b/media/java/android/media/AudioTimestamp.java
@@ -16,11 +16,14 @@
 
 package android.media;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-import android.annotation.IntDef;
-
 /**
  * Structure that groups a position in frame units relative to an assumed audio stream,
  * together with the estimated time when that frame enters or leaves the audio
@@ -33,8 +36,7 @@
  * @see AudioTrack#getTimestamp AudioTrack.getTimestamp(AudioTimestamp)
  * @see AudioRecord#getTimestamp AudioRecord.getTimestamp(AudioTimestamp, int)
  */
-public final class AudioTimestamp
-{
+public final class AudioTimestamp implements Parcelable {
     /**
      * Clock monotonic or its equivalent on the system,
      * in the same units and timebase as {@link java.lang.System#nanoTime}.
@@ -86,4 +88,47 @@
      * with a timebase of {@link #TIMEBASE_MONOTONIC}.
      */
     public long nanoTime;
+
+    public AudioTimestamp() {
+    }
+
+    private AudioTimestamp(@NonNull Parcel in) {
+        framePosition = in.readLong();
+        nanoTime = in.readLong();
+    }
+
+    @Override
+    public String toString() {
+        return "AudioTimeStamp:"
+                + " framePos=" + framePosition
+                + " nanoTime=" + nanoTime;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeLong(framePosition);
+        dest.writeLong(nanoTime);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Creates an instance from a {@link Parcel}.
+     */
+    @NonNull
+    public static final Creator<AudioTimestamp> CREATOR = new Creator<>() {
+        @Override
+        public AudioTimestamp createFromParcel(@NonNull Parcel in) {
+            return new AudioTimestamp(in);
+        }
+
+        @Override
+        public AudioTimestamp[] newArray(int size) {
+            return new AudioTimestamp[size];
+        }
+    };
 }
+
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 85cd342..c2c752e 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -48,8 +48,8 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.NioUtils;
-import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -1233,18 +1233,6 @@
         }
 
         /**
-         * Sets the tuner configuration for the {@code AudioTrack}.
-         *
-         * The {@link AudioTrack.TunerConfiguration} consists of parameters obtained from
-         * the Android TV tuner API which indicate the audio content stream id and the
-         * synchronization id for the {@code AudioTrack}.
-         *
-         * @param tunerConfiguration obtained by {@link AudioTrack.TunerConfiguration.Builder}.
-         * @return the same Builder instance.
-         * @hide
-         */
-
-        /**
          * @hide
          * Sets the {@link AudioTrack} call redirection mode.
          * Used when creating an AudioTrack to inject audio to call uplink path. The mode
@@ -1867,26 +1855,24 @@
     }
 
     // General pair map
-    private static final HashMap<String, Integer> CHANNEL_PAIR_MAP = new HashMap<>() {{
-        put("front", AudioFormat.CHANNEL_OUT_FRONT_LEFT
-                | AudioFormat.CHANNEL_OUT_FRONT_RIGHT);
-        put("back", AudioFormat.CHANNEL_OUT_BACK_LEFT
-                | AudioFormat.CHANNEL_OUT_BACK_RIGHT);
-        put("front of center", AudioFormat.CHANNEL_OUT_FRONT_LEFT_OF_CENTER
-                | AudioFormat.CHANNEL_OUT_FRONT_RIGHT_OF_CENTER);
-        put("side", AudioFormat.CHANNEL_OUT_SIDE_LEFT
-                | AudioFormat.CHANNEL_OUT_SIDE_RIGHT);
-        put("top front", AudioFormat.CHANNEL_OUT_TOP_FRONT_LEFT
-                | AudioFormat.CHANNEL_OUT_TOP_FRONT_RIGHT);
-        put("top back", AudioFormat.CHANNEL_OUT_TOP_BACK_LEFT
-                | AudioFormat.CHANNEL_OUT_TOP_BACK_RIGHT);
-        put("top side", AudioFormat.CHANNEL_OUT_TOP_SIDE_LEFT
-                | AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT);
-        put("bottom front", AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT
-                | AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT);
-        put("front wide", AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT
-                | AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT);
-    }};
+    private static final Map<String, Integer> CHANNEL_PAIR_MAP = Map.of(
+            "front", AudioFormat.CHANNEL_OUT_FRONT_LEFT
+                    | AudioFormat.CHANNEL_OUT_FRONT_RIGHT,
+            "back", AudioFormat.CHANNEL_OUT_BACK_LEFT
+                    | AudioFormat.CHANNEL_OUT_BACK_RIGHT,
+            "front of center", AudioFormat.CHANNEL_OUT_FRONT_LEFT_OF_CENTER
+                    | AudioFormat.CHANNEL_OUT_FRONT_RIGHT_OF_CENTER,
+            "side", AudioFormat.CHANNEL_OUT_SIDE_LEFT | AudioFormat.CHANNEL_OUT_SIDE_RIGHT,
+            "top front", AudioFormat.CHANNEL_OUT_TOP_FRONT_LEFT
+                    | AudioFormat.CHANNEL_OUT_TOP_FRONT_RIGHT,
+            "top back", AudioFormat.CHANNEL_OUT_TOP_BACK_LEFT
+                    | AudioFormat.CHANNEL_OUT_TOP_BACK_RIGHT,
+            "top side", AudioFormat.CHANNEL_OUT_TOP_SIDE_LEFT
+                    | AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT,
+            "bottom front", AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT
+                    | AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT,
+            "front wide", AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT
+                    | AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT);
 
     /**
      * Convenience method to check that the channel configuration (a.k.a channel mask) is supported
@@ -1924,7 +1910,7 @@
                 return false;
         }
         // Check all pairs to see that they are matched (front duplicated here).
-        for (HashMap.Entry<String, Integer> e : CHANNEL_PAIR_MAP.entrySet()) {
+        for (Map.Entry<String, Integer> e : CHANNEL_PAIR_MAP.entrySet()) {
             final int positionPair = e.getValue();
             if ((channelConfig & positionPair) != 0
                     && (channelConfig & positionPair) != positionPair) {
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 0c8cacd..23f87ab 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -70,6 +70,7 @@
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TimeZone;
 import java.util.regex.Matcher;
@@ -3753,6 +3754,11 @@
                 // Skip input stream to the end of the EXIF chunk
                 totalInputStream.skipBytes(WEBP_CHUNK_TYPE_BYTE_LENGTH);
                 int exifChunkLength = totalInputStream.readInt();
+                // RIFF chunks have a single padding byte at the end if the declared chunk size is
+                // odd.
+                if (exifChunkLength % 2 != 0) {
+                    exifChunkLength++;
+                }
                 totalInputStream.skipBytes(exifChunkLength);
 
                 // Write new EXIF chunk to output stream
@@ -3823,7 +3829,7 @@
                     int widthAndHeight = 0;
                     int width = 0;
                     int height = 0;
-                    int alpha = 0;
+                    boolean alpha = false;
                     // Save VP8 frame data for later
                     byte[] vp8Frame = new byte[3];
 
@@ -3858,7 +3864,7 @@
                         width = ((widthAndHeight << 18) >> 18) + 1;
                         height = ((widthAndHeight << 4) >> 18) + 1;
                         // Retrieve alpha bit
-                        alpha = widthAndHeight & (1 << 3);
+                        alpha = (widthAndHeight & (1 << 28)) != 0;
                         bytesToRead -= (1 /* VP8L signature */ + 4);
                     }
 
@@ -3866,10 +3872,12 @@
                     nonHeaderOutputStream.write(WEBP_CHUNK_TYPE_VP8X);
                     nonHeaderOutputStream.writeInt(WEBP_CHUNK_TYPE_VP8X_DEFAULT_LENGTH);
                     byte[] data = new byte[WEBP_CHUNK_TYPE_VP8X_DEFAULT_LENGTH];
+                    // ALPHA flag
+                    if (alpha) {
+                        data[0] = (byte) (data[0] | (1 << 4));
+                    }
                     // EXIF flag
                     data[0] = (byte) (data[0] | (1 << 3));
-                    // ALPHA flag
-                    data[0] = (byte) (data[0] | (alpha << 4));
                     // VP8X stores Width - 1 and Height - 1 values
                     width -= 1;
                     height -= 1;
@@ -4836,12 +4844,13 @@
             for (int i = 1; i < entryValues.length; ++i) {
                 final Pair<Integer, Integer> guessDataFormat = guessDataFormat(entryValues[i]);
                 int first = -1, second = -1;
-                if (guessDataFormat.first == dataFormat.first
-                        || guessDataFormat.second == dataFormat.first) {
+                if (Objects.equals(guessDataFormat.first, dataFormat.first)
+                        || Objects.equals(guessDataFormat.second, dataFormat.first)) {
                     first = dataFormat.first;
                 }
-                if (dataFormat.second != -1 && (guessDataFormat.first == dataFormat.second
-                        || guessDataFormat.second == dataFormat.second)) {
+                if (dataFormat.second != -1
+                        && (Objects.equals(guessDataFormat.first, dataFormat.second)
+                        || Objects.equals(guessDataFormat.second, dataFormat.second))) {
                     second = dataFormat.second;
                 }
                 if (first == -1 && second == -1) {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
old mode 100755
new mode 100644
index 7c9e494..ee453a4
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -22,6 +22,7 @@
 import android.media.AudioDeviceAttributes;
 import android.media.AudioFormat;
 import android.media.AudioFocusInfo;
+import android.media.AudioHalVersionInfo;
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
@@ -102,7 +103,10 @@
             in String callingPackage, in String attributionTag);
 
     void setDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
-            in String callingPackage, in String attributionTag);
+            in String callingPackage);
+
+    VolumeInfo getDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
+            in String callingPackage);
 
     oneway void handleVolumeKey(in KeyEvent event, boolean isOnTv,
             String callingPackage, String caller);
@@ -568,5 +572,5 @@
             in AudioDeviceAttributes device, in List<VolumeInfo> volumes,
             boolean handlesvolumeAdjustment);
 
-    String getHalVersion();
+    AudioHalVersionInfo getHalVersion();
 }
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index 9f3c3ff..bb31859 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -19,6 +19,7 @@
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2Info;
 import android.media.RouteDiscoveryPreference;
+import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
 
 /**
@@ -30,6 +31,8 @@
     void notifySessionReleased(in RoutingSessionInfo session);
     void notifyDiscoveryPreferenceChanged(String packageName,
             in RouteDiscoveryPreference discoveryPreference);
+    void notifyRouteListingPreferenceChange(String packageName,
+            in @nullable RouteListingPreference routeListingPreference);
     void notifyRoutesUpdated(in List<MediaRoute2Info> routes);
     void notifyRequestFailed(int requestId, int reason);
 }
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 742207b..bddda4a 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -23,6 +23,7 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRouterClientState;
 import android.media.RouteDiscoveryPreference;
+import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Bundle;
 
@@ -57,6 +58,8 @@
     void unregisterRouter2(IMediaRouter2 router);
     void setDiscoveryRequestWithRouter2(IMediaRouter2 router,
             in RouteDiscoveryPreference preference);
+    void setRouteListingPreference(IMediaRouter2 router,
+            in @nullable RouteListingPreference routeListingPreference);
     void setRouteVolumeWithRouter2(IMediaRouter2 router, in MediaRoute2Info route, int volume);
 
     void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, long managerRequestId,
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 8a03afb..d6fe6825 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -86,8 +86,10 @@
      *
      * <p>
      * The format is one of the values from
-     * {@link android.graphics.ImageFormat ImageFormat}. The mapping between the
-     * formats and the planes is as follows:
+     * {@link android.graphics.ImageFormat ImageFormat},
+     * {@link android.graphics.PixelFormat PixelFormat}, or
+     * {@link android.hardware.HardwareBuffer HardwareBuffer}. The mapping between the
+     * formats and the planes is as follows (any formats not listed will have 1 plane):
      * </p>
      *
      * <table>
@@ -171,15 +173,18 @@
      * </tr>
      * <tr>
      *   <td>{@link android.graphics.ImageFormat#YCBCR_P010 YCBCR_P010}</td>
-     *   <td>1</td>
+     *   <td>3</td>
      *   <td>P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane
-     *     followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit
-     *     little-endian value, with the lower 6 bits set to zero.
+     *     followed by a Wx(H/2) Cb and Cr planes. Each sample is represented by a 16-bit
+     *     little-endian value, with the lower 6 bits set to zero. Since this is guaranteed to be
+     *     a semi-planar format, the Cb plane can also be treated as an interleaved Cb/Cr plane.
      *   </td>
      * </tr>
      * </table>
      *
      * @see android.graphics.ImageFormat
+     * @see android.graphics.PixelFormat
+     * @see android.hardware.HardwareBuffer
      */
     public abstract int getFormat();
 
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index bf30c50..30d90a8 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -4106,6 +4106,22 @@
         public static final int AV1Level72      = 0x400000;
         public static final int AV1Level73      = 0x800000;
 
+        /** DTS codec profile for DTS HRA. */
+        @SuppressLint("AllUpper")
+        public static final int DTS_HDProfileHRA = 0x1;
+        /** DTS codec profile for DTS Express. */
+        @SuppressLint("AllUpper")
+        public static final int DTS_HDProfileLBR = 0x2;
+        /** DTS codec profile for DTS-HD Master Audio */
+        @SuppressLint("AllUpper")
+        public static final int DTS_HDProfileMA = 0x4;
+        /** DTS codec profile for DTS:X Profile 1 */
+        @SuppressLint("AllUpper")
+        public static final int DTS_UHDProfileP1 = 0x1;
+        /** DTS codec profile for DTS:X Profile 2 */
+        @SuppressLint("AllUpper")
+        public static final int DTS_UHDProfileP2 = 0x2;
+
         /**
          * The profile of the media content. Depending on the type of media this can be
          * one of the profile values defined in this class.
diff --git a/media/java/android/media/MediaHTTPService.java b/media/java/android/media/MediaHTTPService.java
index 3008067..2342a42 100644
--- a/media/java/android/media/MediaHTTPService.java
+++ b/media/java/android/media/MediaHTTPService.java
@@ -21,6 +21,8 @@
 import android.os.IBinder;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.net.CookieHandler;
 import java.net.CookieManager;
 import java.net.CookieStore;
@@ -31,7 +33,9 @@
 public class MediaHTTPService extends IMediaHTTPService.Stub {
     private static final String TAG = "MediaHTTPService";
     @Nullable private List<HttpCookie> mCookies;
-    private Boolean mCookieStoreInitialized = new Boolean(false);
+    private final Object mCookieStoreInitializedLock = new Object();
+    @GuardedBy("mCookieStoreInitializedLock")
+    private boolean mCookieStoreInitialized = false;
 
     public MediaHTTPService(@Nullable List<HttpCookie> cookies) {
         mCookies = cookies;
@@ -40,7 +44,7 @@
 
     public IMediaHTTPConnection makeHTTPConnection() {
 
-        synchronized (mCookieStoreInitialized) {
+        synchronized (mCookieStoreInitializedLock) {
             // Only need to do it once for all connections
             if ( !mCookieStoreInitialized )  {
                 CookieHandler cookieHandler = CookieHandler.getDefault();
@@ -78,8 +82,8 @@
 
                 Log.v(TAG, "makeHTTPConnection(" + this + "): cookieHandler: " + cookieHandler +
                         " Cookies: " + mCookies);
-            }   // mCookieStoreInitialized
-        }   // synchronized
+            }
+        }
 
         return new MediaHTTPConnection();
     }
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 77b5746..79a5902 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -2507,6 +2507,8 @@
      *
      * @see android.media.MediaPlayer#getTrackInfo
      */
+    // The creator needs to be pulic, which requires removing the @UnsupportedAppUsage
+    @SuppressWarnings("ParcelableCreator")
     static public class TrackInfo implements Parcelable {
         /**
          * Gets the track type.
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 681e112..e8648cc 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -28,6 +28,8 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.internal.util.Preconditions;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -374,6 +376,7 @@
 
     MediaRoute2Info(@NonNull Parcel in) {
         mId = in.readString();
+        Preconditions.checkArgument(!TextUtils.isEmpty(mId));
         mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mFeatures = in.createStringArrayList();
         mType = in.readInt();
@@ -403,7 +406,7 @@
      */
     @NonNull
     public String getId() {
-        if (mProviderId != null) {
+        if (!TextUtils.isEmpty(mProviderId)) {
             return toUniqueId(mProviderId, mId);
         } else {
             return mId;
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 2e7896e..b57476f 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -387,7 +387,7 @@
                 try {
                     mMediaRouterService.registerClientGroupId(mClient, groupId);
                 } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to register group ID of the client.", ex);
+                    ex.rethrowFromSystemServer();
                 }
             }
         }
@@ -439,7 +439,7 @@
                     try {
                         mMediaRouterService.unregisterClient(mClient);
                     } catch (RemoteException ex) {
-                        Log.e(TAG, "Unable to unregister media router client.", ex);
+                        ex.rethrowFromSystemServer();
                     }
                     mClient = null;
                 }
@@ -466,7 +466,7 @@
                     mMediaRouterService.setDiscoveryRequest(mClient,
                             mDiscoveryRequestRouteTypes, mDiscoverRequestActiveScan);
                 } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to publish media router client discovery request.", ex);
+                    ex.rethrowFromSystemServer();
                 }
             }
         }
@@ -478,7 +478,7 @@
                             mSelectedRoute != null ? mSelectedRoute.mGlobalRouteId : null,
                             explicit);
                 } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to publish media router client selected route.", ex);
+                    ex.rethrowFromSystemServer();
                 }
             }
         }
@@ -490,7 +490,7 @@
                 try {
                     mClientState = mMediaRouterService.getState(mClient);
                 } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to retrieve media router client state.", ex);
+                    ex.rethrowFromSystemServer();
                 }
             }
             final ArrayList<MediaRouterClientState.RouteInfo> globalRoutes =
@@ -535,7 +535,7 @@
                     mMediaRouterService.requestSetVolume(mClient,
                             route.mGlobalRouteId, volume);
                 } catch (RemoteException ex) {
-                    Log.w(TAG, "Unable to request volume change.", ex);
+                    ex.rethrowFromSystemServer();
                 }
             }
         }
@@ -546,7 +546,7 @@
                     mMediaRouterService.requestUpdateVolume(mClient,
                             route.mGlobalRouteId, direction);
                 } catch (RemoteException ex) {
-                    Log.w(TAG, "Unable to request volume change.", ex);
+                    ex.rethrowFromSystemServer();
                 }
             }
         }
@@ -653,7 +653,7 @@
                 try {
                     return mMediaRouterService.isPlaybackActive(mClient);
                 } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to retrieve playback active state.", ex);
+                    ex.rethrowFromSystemServer();
                 }
             }
             return false;
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 26cb9f8..d57a56a 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -104,14 +104,18 @@
     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.
      */
     @GuardedBy("mLock")
     final Map<String, MediaRoute2Info> mRoutes = new ArrayMap<>();
 
+    @GuardedBy("mLock")
+    @Nullable
+    private RouteListingPreference mRouteListingPreference;
+
     final RoutingController mSystemController;
 
     @GuardedBy("mLock")
@@ -305,7 +309,7 @@
             currentSystemRoutes = mMediaRouterService.getSystemRoutes();
             currentSystemSessionInfo = mMediaRouterService.getSystemSessionInfo();
         } catch (RemoteException ex) {
-            Log.e(TAG, "Unable to get current system's routes / session info", ex);
+            ex.rethrowFromSystemServer();
         }
 
         if (currentSystemRoutes == null || currentSystemRoutes.isEmpty()) {
@@ -407,14 +411,14 @@
                     mMediaRouterService.registerRouter2(stub, mPackageName);
                     mStub = stub;
                 } catch (RemoteException ex) {
-                    Log.e(TAG, "registerRouteCallback: Unable to register MediaRouter2.", ex);
+                    ex.rethrowFromSystemServer();
                 }
             }
             if (mStub != null && updateDiscoveryPreferenceIfNeededLocked()) {
                 try {
                     mMediaRouterService.setDiscoveryRequestWithRouter2(mStub, mDiscoveryPreference);
                 } catch (RemoteException ex) {
-                    Log.e(TAG, "registerRouteCallback: Unable to set discovery request.", ex);
+                    ex.rethrowFromSystemServer();
                 }
             }
         }
@@ -454,13 +458,59 @@
                 try {
                     mMediaRouterService.unregisterRouter2(mStub);
                 } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to unregister media router.", ex);
+                    ex.rethrowFromSystemServer();
                 }
                 mStub = null;
             }
         }
     }
 
+    /**
+     * Sets the {@link RouteListingPreference} of the app associated to this media router.
+     *
+     * <p>Use this method to inform the system UI of the routes that you would like to list for
+     * media routing, via the Output Switcher.
+     *
+     * <p>You should call this method before {@link #registerRouteCallback registering any route
+     * callbacks} and immediately after receiving any {@link RouteCallback#onRoutesUpdated route
+     * updates} in order to keep the system UI in a consistent state. You can also call this method
+     * at any other point to update the listing preference dynamically.
+     *
+     * <p>Notes:
+     *
+     * <ol>
+     *   <li>You should not include the ids of two or more routes with a match in their {@link
+     *       MediaRoute2Info#getDeduplicationIds() deduplication ids}. If you do, the system will
+     *       deduplicate them using its own criteria.
+     *   <li>You can use this method to rank routes in the output switcher, placing the more
+     *       important routes first. The system might override the proposed ranking.
+     *   <li>You can use this method to avoid listing routes using dynamic criteria. For example,
+     *       you can limit access to a specific type of device according to runtime criteria.
+     * </ol>
+     *
+     * @param routeListingPreference The {@link RouteListingPreference} for the system to use for
+     *     route listing. When null, the system uses its default listing criteria.
+     */
+    public void setRouteListingPreference(@Nullable RouteListingPreference routeListingPreference) {
+        synchronized (mLock) {
+            if (Objects.equals(mRouteListingPreference, routeListingPreference)) {
+                // Nothing changed. We return early to save a call to the system server.
+                return;
+            }
+            mRouteListingPreference = routeListingPreference;
+            try {
+                if (mStub == null) {
+                    MediaRouter2Stub stub = new MediaRouter2Stub();
+                    mMediaRouterService.registerRouter2(stub, mPackageName);
+                    mStub = stub;
+                }
+                mMediaRouterService.setRouteListingPreference(mStub, mRouteListingPreference);
+            } catch (RemoteException ex) {
+                ex.rethrowFromSystemServer();
+            }
+        }
+    }
+
     @GuardedBy("mLock")
     private boolean updateDiscoveryPreferenceIfNeededLocked() {
         RouteDiscoveryPreference newDiscoveryPreference = new RouteDiscoveryPreference.Builder(
@@ -603,7 +653,7 @@
      */
     public void transferTo(@NonNull MediaRoute2Info route) {
         if (isSystemRouter()) {
-            sManager.selectRoute(mClientPackageName, route);
+            sManager.transfer(mClientPackageName, route);
             return;
         }
 
@@ -1769,7 +1819,7 @@
                     try {
                         mMediaRouterService.releaseSessionWithRouter2(mStub, getId());
                     } catch (RemoteException ex) {
-                        Log.e(TAG, "Unable to release session", ex);
+                        ex.rethrowFromSystemServer();
                     }
                 }
 
@@ -1787,7 +1837,7 @@
                     try {
                         mMediaRouterService.unregisterRouter2(mStub);
                     } catch (RemoteException ex) {
-                        Log.e(TAG, "releaseInternal: Unable to unregister media router.", ex);
+                        ex.rethrowFromSystemServer();
                     }
                     mStub = null;
                 }
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index b6f07f4..aea6bcb 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -35,6 +35,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -75,13 +76,7 @@
     private static MediaRouter2Manager sInstance;
 
     private final MediaSessionManager mMediaSessionManager;
-
-    final String mPackageName;
-
-    private final Context mContext;
-
     private final Client mClient;
-
     private final IMediaRouterService mMediaRouterService;
     private final AtomicInteger mScanRequestCount = new AtomicInteger(/* initialValue= */ 0);
     final Handler mHandler;
@@ -93,6 +88,11 @@
     @NonNull
     final ConcurrentMap<String, RouteDiscoveryPreference> mDiscoveryPreferenceMap =
             new ConcurrentHashMap<>();
+    // TODO(b/241888071): Merge mDiscoveryPreferenceMap and mPackageToRouteListingPreferenceMap into
+    //     a single record object maintained by a single package-to-record map.
+    @NonNull
+    private final ConcurrentMap<String, RouteListingPreference>
+            mPackageToRouteListingPreferenceMap = new ConcurrentHashMap<>();
 
     private final AtomicInteger mNextRequestId = new AtomicInteger(1);
     private final CopyOnWriteArrayList<TransferRequest> mTransferRequests =
@@ -114,16 +114,14 @@
     }
 
     private MediaRouter2Manager(Context context) {
-        mContext = context.getApplicationContext();
         mMediaRouterService = IMediaRouterService.Stub.asInterface(
                 ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
         mMediaSessionManager = (MediaSessionManager) context
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
-        mPackageName = mContext.getPackageName();
         mHandler = new Handler(context.getMainLooper());
         mClient = new Client();
         try {
-            mMediaRouterService.registerManager(mClient, mPackageName);
+            mMediaRouterService.registerManager(mClient, context.getPackageName());
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -355,6 +353,16 @@
     }
 
     /**
+     * Returns the {@link RouteListingPreference} of the app with the given {@code packageName}, or
+     * null if the app has not set any.
+     */
+    @Nullable
+    public RouteListingPreference getRouteListingPreference(@NonNull String packageName) {
+        Preconditions.checkArgument(!TextUtils.isEmpty(packageName));
+        return mPackageToRouteListingPreferenceMap.get(packageName);
+    }
+
+    /**
      * Gets the system routing session for the given {@code packageName}.
      * Apps can select a route that is not the global route. (e.g. an app can select the device
      * route while BT route is available.)
@@ -448,14 +456,16 @@
     }
 
     /**
-     * Selects media route for the specified package name.
+     * Transfers a {@link RoutingSessionInfo routing session} belonging to a specified package name
+     * to a {@link MediaRoute2Info media route}.
+     *
+     * <p>Same as {@link #transfer(RoutingSessionInfo, MediaRoute2Info)}, but resolves the routing
+     * session based on the provided package name.
      */
-    public void selectRoute(@NonNull String packageName, @NonNull MediaRoute2Info route) {
+    public void transfer(@NonNull String packageName, @NonNull MediaRoute2Info route) {
         Objects.requireNonNull(packageName, "packageName must not be null");
         Objects.requireNonNull(route, "route must not be null");
 
-        Log.v(TAG, "Selecting route. packageName= " + packageName + ", route=" + route);
-
         List<RoutingSessionInfo> sessionInfos = getRoutingSessions(packageName);
         RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
         transfer(targetSession, route);
@@ -684,6 +694,24 @@
         }
     }
 
+    private void updateRouteListingPreference(
+            @NonNull String packageName, @Nullable RouteListingPreference routeListingPreference) {
+        RouteListingPreference oldRouteListingPreference =
+                routeListingPreference == null
+                        ? mPackageToRouteListingPreferenceMap.remove(packageName)
+                        : mPackageToRouteListingPreferenceMap.put(
+                                packageName, routeListingPreference);
+        if (Objects.equals(oldRouteListingPreference, routeListingPreference)) {
+            return;
+        }
+        for (CallbackRecord record : mCallbackRecords) {
+            record.mExecutor.execute(
+                    () ->
+                            record.mCallback.onRouteListingPreferenceUpdated(
+                                    packageName, routeListingPreference));
+        }
+    }
+
     /**
      * Gets the unmodifiable list of selected routes for the session.
      */
@@ -969,6 +997,19 @@
         }
 
         /**
+         * Called when the app with the given {@code packageName} updates its {@link
+         * MediaRouter2#setRouteListingPreference route listing preference}.
+         *
+         * @param packageName The package name of the app that changed its listing preference.
+         * @param routeListingPreference The new {@link RouteListingPreference} set by the app with
+         *     the given {@code packageName}. Maybe null if an app has unset its preference (by
+         *     passing null to {@link MediaRouter2#setRouteListingPreference}).
+         */
+        default void onRouteListingPreferenceUpdated(
+                @NonNull String packageName,
+                @Nullable RouteListingPreference routeListingPreference) {}
+
+        /**
          * Called when a previous request has failed.
          *
          * @param reason the reason that the request has failed. Can be one of followings:
@@ -1054,6 +1095,17 @@
         }
 
         @Override
+        public void notifyRouteListingPreferenceChange(
+                String packageName, @Nullable RouteListingPreference routeListingPreference) {
+            mHandler.sendMessage(
+                    obtainMessage(
+                            MediaRouter2Manager::updateRouteListingPreference,
+                            MediaRouter2Manager.this,
+                            packageName,
+                            routeListingPreference));
+        }
+
+        @Override
         public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
             mHandler.sendMessage(
                     obtainMessage(
diff --git a/media/java/android/media/MediaRouter2Utils.java b/media/java/android/media/MediaRouter2Utils.java
index c15972d..6f946ee 100644
--- a/media/java/android/media/MediaRouter2Utils.java
+++ b/media/java/android/media/MediaRouter2Utils.java
@@ -21,6 +21,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 /**
  * @hide
  */
@@ -31,14 +33,8 @@
 
     @NonNull
     public static String toUniqueId(@NonNull String providerId, @NonNull String id) {
-        if (TextUtils.isEmpty(providerId)) {
-            Log.w(TAG, "toUniqueId: providerId shouldn't be empty");
-            return null;
-        }
-        if (TextUtils.isEmpty(id)) {
-            Log.w(TAG, "toUniqueId: id shouldn't be null");
-            return null;
-        }
+        Preconditions.checkArgument(!TextUtils.isEmpty(providerId)
+                && !TextUtils.isEmpty(id));
 
         return providerId + SEPARATOR + id;
     }
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 82c3139..e78dc31 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -89,6 +89,7 @@
             .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
             .build();
+    private boolean mPreferBuiltinDevice;
     // playback properties, use synchronized with mPlaybackSettingsLock
     private boolean mIsLooping = false;
     private float mVolume = 1.0f;
@@ -157,14 +158,47 @@
     }
 
     /**
-     * Creates a local media player for the ringtone using currently set attributes.
+     * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is
+     * the one on which outgoing audio for SIM calls is played.
+     *
+     * @param audioManager the audio manage.
+     * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if
+     *     none can be found.
+     */
+    private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) {
+        AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (AudioDeviceInfo device : deviceList) {
+            if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                return device;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Sets the preferred device of the ringtong playback to the built-in device.
+     *
      * @hide
      */
-    public void createLocalMediaPlayer() {
+    public boolean preferBuiltinDevice(boolean enable) {
+        mPreferBuiltinDevice = enable;
+        if (mLocalPlayer == null) {
+            return true;
+        }
+        return mLocalPlayer.setPreferredDevice(getBuiltinDevice(mAudioManager));
+    }
+
+    /**
+     * Creates a local media player for the ringtone using currently set attributes.
+     * @return true if media player creation succeeded or is deferred,
+     * false if it did not succeed and can't be tried remotely.
+     * @hide
+     */
+    public boolean createLocalMediaPlayer() {
         Trace.beginSection("createLocalMediaPlayer");
         if (mUri == null) {
             Log.e(TAG, "Could not create media player as no URI was provided.");
-            return;
+            return mAllowRemote && mRemotePlayer != null;
         }
         destroyLocalPlayer();
         // try opening uri locally before delegating to remote player
@@ -172,6 +206,8 @@
         try {
             mLocalPlayer.setDataSource(mContext, mUri);
             mLocalPlayer.setAudioAttributes(mAudioAttributes);
+            mLocalPlayer.setPreferredDevice(
+                    mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null);
             synchronized (mPlaybackSettingsLock) {
                 applyPlaybackProperties_sync();
             }
@@ -195,6 +231,30 @@
             }
         }
         Trace.endSection();
+        return mLocalPlayer != null || (mAllowRemote && mRemotePlayer != null);
+    }
+
+    /**
+     * Same as AudioManager.hasHapticChannels except it assumes an already created ringtone.
+     * If the ringtone has not been created, it will load based on URI provided at {@link #setUri}
+     * and if not URI has been set, it will assume no haptic channels are present.
+     * @hide
+     */
+    public boolean hasHapticChannels() {
+        // FIXME: support remote player, or internalize haptic channels support and remove entirely.
+        try {
+            android.os.Trace.beginSection("Ringtone.hasHapticChannels");
+            if (mLocalPlayer != null) {
+                for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) {
+                    if (trackInfo.hasHapticChannels()) {
+                        return true;
+                    }
+                }
+            }
+        } finally {
+            android.os.Trace.endSection();
+        }
+        return false;
     }
 
     /**
@@ -423,7 +483,6 @@
      */
     public void setUri(Uri uri, @Nullable VolumeShaper.Configuration volumeShaperConfig) {
         mVolumeShaperConfig = volumeShaperConfig;
-
         mUri = uri;
         if (mUri == null) {
             destroyLocalPlayer();
@@ -443,10 +502,11 @@
         if (mLocalPlayer != null) {
             // Play ringtones if stream volume is over 0 or if it is a haptic-only ringtone
             // (typically because ringer mode is vibrate).
-            boolean isHapticOnly = AudioManager.hasHapticChannels(mContext, mUri)
-                    && !mAudioAttributes.areHapticChannelsMuted() && mVolume == 0;
-            if (isHapticOnly || mAudioManager.getStreamVolume(
-                    AudioAttributes.toLegacyStreamType(mAudioAttributes)) != 0) {
+            if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes))
+                    != 0) {
+                startLocalPlayer();
+            } else if (!mAudioAttributes.areHapticChannelsMuted() && hasHapticChannels()) {
+                // is haptic only ringtone
                 startLocalPlayer();
             }
         } else if (mAllowRemote && (mRemotePlayer != null) && (mUri != null)) {
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 27db41c..1e270b1 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -720,11 +720,14 @@
             @Nullable VolumeShaper.Configuration volumeShaperConfig,
             AudioAttributes audioAttributes) {
         // Don't set the stream type
-        Ringtone ringtone =
-                getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig, false);
+        Ringtone ringtone = getRingtone(context, ringtoneUri, -1 /* streamType */,
+                volumeShaperConfig, false);
         if (ringtone != null) {
             ringtone.setAudioAttributesField(audioAttributes);
-            ringtone.createLocalMediaPlayer();
+            if (!ringtone.createLocalMediaPlayer()) {
+                Log.e(TAG, "Failed to open ringtone " + ringtoneUri);
+                return null;
+            }
         }
         return ringtone;
     }
@@ -750,19 +753,6 @@
                 createLocalMediaPlayer);
     }
 
-    //FIXME bypass the notion of stream types within the class
-    /**
-     * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI on
-     * the given stream type. Normally, if you change the stream type on the returned
-     * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just
-     * an optimized route to avoid that.
-     *
-     * @param streamType The stream type for the ringtone, or -1 if it should
-     *            not be set (and the default used instead).
-     * @param volumeShaperConfig config for volume shaper of the ringtone if applied.
-     * @see #getRingtone(Context, Uri)
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType,
             @Nullable VolumeShaper.Configuration volumeShaperConfig,
             boolean createLocalMediaPlayer) {
@@ -776,7 +766,10 @@
             r.setVolumeShaperConfig(volumeShaperConfig);
             r.setUri(ringtoneUri, volumeShaperConfig);
             if (createLocalMediaPlayer) {
-                r.createLocalMediaPlayer();
+                if (!r.createLocalMediaPlayer()) {
+                    Log.e(TAG, "Failed to open ringtone " + ringtoneUri);
+                    return null;
+                }
             }
             return r;
         } catch (Exception ex) {
@@ -833,6 +826,19 @@
         if(!isInternalRingtoneUri(ringtoneUri)) {
             ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId());
         }
+
+        final String mimeType = resolver.getType(ringtoneUri);
+        if (mimeType == null) {
+            Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
+                    + " ignored: failure to find mimeType (no access from this context?)");
+            return;
+        }
+        if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) {
+            Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
+                    + " ignored: associated mimeType:" + mimeType + " is not an audio type");
+            return;
+        }
+
         Settings.System.putStringForUser(resolver, setting,
                 ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId());
 
@@ -1158,24 +1164,38 @@
             }
 
             // Try finding the scanned ringtone
-            final String filename = getDefaultRingtoneFilename(type);
-            final String whichAudio = getQueryStringForType(type);
-            final String where = MediaColumns.DISPLAY_NAME + "=? AND " + whichAudio + "=?";
-            final Uri baseUri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI;
-            try (Cursor cursor = context.getContentResolver().query(baseUri,
-                    new String[] { MediaColumns._ID },
-                    where,
-                    new String[] { filename, "1" }, null)) {
-                if (cursor.moveToFirst()) {
-                    final Uri ringtoneUri = context.getContentResolver().canonicalizeOrElse(
-                            ContentUris.withAppendedId(baseUri, cursor.getLong(0)));
-                    RingtoneManager.setActualDefaultRingtoneUri(context, type, ringtoneUri);
-                    Settings.System.putInt(context.getContentResolver(), setting, 1);
-                }
+            Uri ringtoneUri = computeDefaultRingtoneUri(context, type);
+            if (ringtoneUri != null) {
+                RingtoneManager.setActualDefaultRingtoneUri(context, type, ringtoneUri);
+                Settings.System.putInt(context.getContentResolver(), setting, 1);
             }
         }
     }
 
+    /**
+     * @param type the type of ringtone (e.g {@link #TYPE_RINGTONE})
+     * @return the system default URI if found, null otherwise.
+     */
+    private static Uri computeDefaultRingtoneUri(@NonNull Context context, int type) {
+        // Try finding the scanned ringtone
+        final String filename = getDefaultRingtoneFilename(type);
+        final String whichAudio = getQueryStringForType(type);
+        final String where = MediaColumns.DISPLAY_NAME + "=? AND " + whichAudio + "=?";
+        final Uri baseUri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI;
+        try (Cursor cursor = context.getContentResolver().query(baseUri,
+                new String[] { MediaColumns._ID },
+                where,
+                new String[] { filename, "1" }, null)) {
+            if (cursor.moveToFirst()) {
+                final Uri ringtoneUri = context.getContentResolver().canonicalizeOrElse(
+                        ContentUris.withAppendedId(baseUri, cursor.getLong(0)));
+                return ringtoneUri;
+            }
+        }
+
+        return null;
+    }
+
     private static String getDefaultRingtoneSetting(int type) {
         switch (type) {
             case TYPE_RINGTONE: return "ringtone_set";
diff --git a/media/java/android/media/RouteListingPreference.aidl b/media/java/android/media/RouteListingPreference.aidl
new file mode 100644
index 0000000..844dc8f
--- /dev/null
+++ b/media/java/android/media/RouteListingPreference.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.media;
+
+parcelable RouteListingPreference;
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
new file mode 100644
index 0000000..62f233e
--- /dev/null
+++ b/media/java/android/media/RouteListingPreference.java
@@ -0,0 +1,260 @@
+/*
+ * 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.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Allows applications to customize the list of routes used for media routing (for example, in the
+ * System UI Output Switcher).
+ *
+ * @see MediaRouter2#setRouteListingPreference
+ */
+public final class RouteListingPreference implements Parcelable {
+
+    @NonNull
+    public static final Creator<RouteListingPreference> CREATOR =
+            new Creator<>() {
+                @Override
+                public RouteListingPreference createFromParcel(Parcel in) {
+                    return new RouteListingPreference(in);
+                }
+
+                @Override
+                public RouteListingPreference[] newArray(int size) {
+                    return new RouteListingPreference[size];
+                }
+            };
+
+    @NonNull private final List<Item> mItems;
+
+    /**
+     * Creates an instance with the given values.
+     *
+     * @param items See {@link #getItems()}.
+     */
+    public RouteListingPreference(@NonNull List<Item> items) {
+        mItems = List.copyOf(Objects.requireNonNull(items));
+    }
+
+    private RouteListingPreference(Parcel in) {
+        List<Item> items =
+                in.readParcelableList(new ArrayList<>(), Item.class.getClassLoader(), Item.class);
+        mItems = List.copyOf(items);
+    }
+
+    /**
+     * Returns an unmodifiable list containing the items that the app wants to be listed for media
+     * routing.
+     */
+    @NonNull
+    public List<Item> getItems() {
+        return mItems;
+    }
+
+    // RouteListingPreference Parcelable implementation.
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelableList(mItems, flags);
+    }
+
+    // Equals and hashCode.
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof RouteListingPreference)) {
+            return false;
+        }
+        RouteListingPreference that = (RouteListingPreference) other;
+        return mItems.equals(that.mItems);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mItems);
+    }
+
+    // Internal classes.
+
+    /** Holds preference information for a specific route in a media routing listing. */
+    public static final class Item implements Parcelable {
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(
+                flag = true,
+                prefix = {"FLAG_"},
+                value = {FLAG_ONGOING_SESSION, FLAG_SUGGESTED_ROUTE})
+        public @interface Flags {}
+
+        /**
+         * The corresponding route is already hosting a session with the app that owns this listing
+         * preference.
+         */
+        public static final int FLAG_ONGOING_SESSION = 1;
+
+        /**
+         * The corresponding route is specially likely to be selected by the user.
+         *
+         * <p>A UI reflecting this preference may reserve a specific space for suggested routes,
+         * making it more accessible to the user. If the number of suggested routes exceeds the
+         * number supported by the UI, the routes listed first in {@link
+         * RouteListingPreference#getItems()} will take priority.
+         */
+        public static final int FLAG_SUGGESTED_ROUTE = 1 << 1;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(
+                prefix = {"DISABLE_REASON_"},
+                value = {DISABLE_REASON_NONE, DISABLE_REASON_SUBSCRIPTION_REQUIRED})
+        public @interface DisableReason {}
+
+        /** The corresponding route is available for routing. */
+        public static final int DISABLE_REASON_NONE = 0;
+        /**
+         * The corresponding route requires a special subscription in order to be available for
+         * routing.
+         */
+        public static final int DISABLE_REASON_SUBSCRIPTION_REQUIRED = 1;
+
+        @NonNull
+        public static final Creator<Item> CREATOR =
+                new Creator<>() {
+                    @Override
+                    public Item createFromParcel(Parcel in) {
+                        return new Item(in);
+                    }
+
+                    @Override
+                    public Item[] newArray(int size) {
+                        return new Item[size];
+                    }
+                };
+
+        @NonNull private final String mRouteId;
+        @Flags private final int mFlags;
+        @DisableReason private final int mDisableReason;
+
+        /**
+         * Creates an instance with the given value.
+         *
+         * @param routeId See {@link #getRouteId()}. Must not be empty.
+         * @param flags See {@link #getFlags()}.
+         * @param disableReason See {@link #getDisableReason()}.
+         */
+        public Item(@NonNull String routeId, @Flags int flags, @DisableReason int disableReason) {
+            Preconditions.checkArgument(!TextUtils.isEmpty(routeId));
+            mRouteId = routeId;
+            mFlags = flags;
+            mDisableReason = disableReason;
+        }
+
+        private Item(Parcel in) {
+            String routeId = in.readString();
+            Preconditions.checkArgument(!TextUtils.isEmpty(routeId));
+            mRouteId = routeId;
+            mFlags = in.readInt();
+            mDisableReason = in.readInt();
+        }
+
+        /** Returns the id of the route that corresponds to this route listing preference item. */
+        @NonNull
+        public String getRouteId() {
+            return mRouteId;
+        }
+
+        /**
+         * Returns the flags associated to the route that corresponds to this item.
+         *
+         * @see #FLAG_ONGOING_SESSION
+         * @see #FLAG_SUGGESTED_ROUTE
+         */
+        @Flags
+        public int getFlags() {
+            return mFlags;
+        }
+
+        /**
+         * Returns the reason for the corresponding route to be disabled, or {@link
+         * #DISABLE_REASON_NONE} if the route is not disabled.
+         *
+         * @see #DISABLE_REASON_NONE
+         * @see #DISABLE_REASON_SUBSCRIPTION_REQUIRED
+         */
+        @DisableReason
+        public int getDisableReason() {
+            return mDisableReason;
+        }
+
+        // Item Parcelable implementation.
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeString(mRouteId);
+            dest.writeInt(mFlags);
+            dest.writeInt(mDisableReason);
+        }
+
+        // Equals and hashCode.
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof Item)) {
+                return false;
+            }
+            Item item = (Item) other;
+            return mRouteId.equals(item.mRouteId)
+                    && mFlags == item.mFlags
+                    && mDisableReason == item.mDisableReason;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mRouteId, mFlags, mDisableReason);
+        }
+    }
+}
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index 10973ab..0982132 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -25,6 +25,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -106,9 +108,9 @@
     }
 
     RoutingSessionInfo(@NonNull Parcel src) {
-        Objects.requireNonNull(src, "src must not be null.");
+        mId = src.readString();
+        Preconditions.checkArgument(!TextUtils.isEmpty(mId));
 
-        mId = ensureString(src.readString());
         mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src);
         mOwnerPackageName = src.readString();
         mClientPackageName = ensureString(src.readString());
@@ -177,7 +179,7 @@
      */
     @NonNull
     public String getId() {
-        if (mProviderId != null) {
+        if (!TextUtils.isEmpty(mProviderId)) {
             return MediaRouter2Utils.toUniqueId(mProviderId, mId);
         } else {
             return mId;
@@ -421,7 +423,7 @@
         }
 
         // mProviderId can be null if not set. Return the original list for this case.
-        if (mProviderId == null) {
+        if (TextUtils.isEmpty(mProviderId)) {
             return routeIds;
         }
 
diff --git a/media/java/android/media/VolumeInfo.java b/media/java/android/media/VolumeInfo.java
index 6b4f604..afb44bb 100644
--- a/media/java/android/media/VolumeInfo.java
+++ b/media/java/android/media/VolumeInfo.java
@@ -28,7 +28,6 @@
 import android.os.ServiceManager;
 import android.util.Log;
 
-import java.util.List;
 import java.util.Objects;
 
 /**
@@ -36,33 +35,36 @@
  * A class to represent volume information.
  * Can be used to represent volume associated with a stream type or {@link AudioVolumeGroup}.
  * Volume index is optional when used to represent a category of volume.
- * Index ranges are supported too, making the representation of volume changes agnostic to the
- * range (e.g. can be used to map BT A2DP absolute volume range to internal range).
+ * Volume ranges are supported too, making the representation of volume changes agnostic regarding
+ * the range of values that are supported (e.g. can be used to map BT A2DP absolute volume range to
+ * internal range).
  */
 @SystemApi
 public final class VolumeInfo implements Parcelable {
     private static final String TAG = "VolumeInfo";
 
     private final boolean mUsesStreamType; // false implies AudioVolumeGroup is used
+    private final boolean mHasMuteCommand;
     private final boolean mIsMuted;
     private final int mVolIndex;
     private final int mMinVolIndex;
     private final int mMaxVolIndex;
-    private final int mVolGroupId;
-    private final int mStreamType;
+    private final @Nullable AudioVolumeGroup mVolGroup;
+    private final @AudioManager.PublicStreamTypes int mStreamType;
 
     private static IAudioService sService;
     private static VolumeInfo sDefaultVolumeInfo;
 
-    private VolumeInfo(boolean usesStreamType, boolean isMuted, int volIndex,
-            int minVolIndex, int maxVolIndex,
-            int volGroupId, int streamType) {
+    private VolumeInfo(boolean usesStreamType, boolean hasMuteCommand, boolean isMuted,
+            int volIndex, int minVolIndex, int maxVolIndex,
+            AudioVolumeGroup volGroup, int streamType) {
         mUsesStreamType = usesStreamType;
+        mHasMuteCommand = hasMuteCommand;
         mIsMuted = isMuted;
         mVolIndex = volIndex;
         mMinVolIndex = minVolIndex;
         mMaxVolIndex = maxVolIndex;
-        mVolGroupId = volGroupId;
+        mVolGroup = volGroup;
         mStreamType = streamType;
     }
 
@@ -79,8 +81,10 @@
     /**
      * Returns the associated stream type, or will throw if {@link #hasStreamType()} returned false.
      * @return a stream type value, see AudioManager.STREAM_*
+     * @throws IllegalStateException when called on a VolumeInfo not configured for
+     *      stream types.
      */
-    public int getStreamType() {
+    public @AudioManager.PublicStreamTypes int getStreamType() {
         if (!mUsesStreamType) {
             throw new IllegalStateException("VolumeInfo doesn't use stream types");
         }
@@ -99,24 +103,28 @@
     /**
      * Returns the associated volume group, or will throw if {@link #hasVolumeGroup()} returned
      * false.
-     * @return the volume group corresponding to this VolumeInfo, or null if an error occurred
-     * in the volume group management
+     * @return the volume group corresponding to this VolumeInfo
+     * @throws IllegalStateException when called on a VolumeInfo not configured for
+     * volume groups.
      */
-    public @Nullable AudioVolumeGroup getVolumeGroup() {
+    public @NonNull AudioVolumeGroup getVolumeGroup() {
         if (mUsesStreamType) {
             throw new IllegalStateException("VolumeInfo doesn't use AudioVolumeGroup");
         }
-        List<AudioVolumeGroup> volGroups = AudioVolumeGroup.getAudioVolumeGroups();
-        for (AudioVolumeGroup group : volGroups) {
-            if (group.getId() == mVolGroupId) {
-                return group;
-            }
-        }
-        return null;
+        return mVolGroup;
     }
 
     /**
-     * Returns whether this instance is conveying a mute state.
+     * Return whether this instance is conveying a mute state
+     * @return true if the muted state was explicitly set for this instance
+     */
+    public boolean hasMuteCommand() {
+        return mHasMuteCommand;
+    }
+
+    /**
+     * Returns whether this instance is conveying a mute state that was explicitly set
+     * by {@link Builder#setMuted(boolean)}, false otherwise
      * @return true if the volume state is muted
      */
     public boolean isMuted() {
@@ -183,18 +191,21 @@
      */
     public static final class Builder {
         private boolean mUsesStreamType = true; // false implies AudioVolumeGroup is used
-        private int mStreamType = AudioManager.STREAM_MUSIC;
+        private @AudioManager.PublicStreamTypes int mStreamType = AudioManager.STREAM_MUSIC;
+        private boolean mHasMuteCommand = false;
         private boolean mIsMuted = false;
         private int mVolIndex = INDEX_NOT_SET;
         private int mMinVolIndex = INDEX_NOT_SET;
         private int mMaxVolIndex = INDEX_NOT_SET;
-        private int mVolGroupId = -Integer.MIN_VALUE;
+        private @Nullable AudioVolumeGroup mVolGroup;
 
         /**
          * Builder constructor for stream type-based VolumeInfo
          */
-        public Builder(int streamType) {
-            // TODO validate stream type
+        public Builder(@AudioManager.PublicStreamTypes int streamType) {
+            if (!AudioManager.isPublicStreamType(streamType)) {
+                throw new IllegalArgumentException("Not a valid public stream type " + streamType);
+            }
             mUsesStreamType = true;
             mStreamType = streamType;
         }
@@ -206,7 +217,7 @@
             Objects.requireNonNull(volGroup);
             mUsesStreamType = false;
             mStreamType = -Integer.MIN_VALUE;
-            mVolGroupId = volGroup.getId();
+            mVolGroup = volGroup;
         }
 
         /**
@@ -217,11 +228,12 @@
             Objects.requireNonNull(info);
             mUsesStreamType = info.mUsesStreamType;
             mStreamType = info.mStreamType;
+            mHasMuteCommand = info.mHasMuteCommand;
             mIsMuted = info.mIsMuted;
             mVolIndex = info.mVolIndex;
             mMinVolIndex = info.mMinVolIndex;
             mMaxVolIndex = info.mMaxVolIndex;
-            mVolGroupId = info.mVolGroupId;
+            mVolGroup = info.mVolGroup;
         }
 
         /**
@@ -230,6 +242,7 @@
          * @return the same builder instance
          */
         public @NonNull Builder setMuted(boolean isMuted) {
+            mHasMuteCommand = true;
             mIsMuted = isMuted;
             return this;
         }
@@ -239,7 +252,6 @@
          * @param volIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown
          * @return the same builder instance
          */
-        // TODO should we allow muted true + volume index set? (useful when toggling mute on/off?)
         public @NonNull Builder setVolumeIndex(int volIndex) {
             if (volIndex != INDEX_NOT_SET && volIndex < 0) {
                 throw new IllegalArgumentException("Volume index cannot be negative");
@@ -294,9 +306,9 @@
                 throw new IllegalArgumentException("Min volume index:" + mMinVolIndex
                         + " greater than max index:" + mMaxVolIndex);
             }
-            return new VolumeInfo(mUsesStreamType, mIsMuted,
+            return new VolumeInfo(mUsesStreamType, mHasMuteCommand, mIsMuted,
                     mVolIndex, mMinVolIndex, mMaxVolIndex,
-                    mVolGroupId, mStreamType);
+                    mVolGroup, mStreamType);
         }
     }
 
@@ -304,8 +316,8 @@
     // Parcelable
     @Override
     public int hashCode() {
-        return Objects.hash(mUsesStreamType, mStreamType, mIsMuted,
-                mVolIndex, mMinVolIndex, mMaxVolIndex, mVolGroupId);
+        return Objects.hash(mUsesStreamType, mHasMuteCommand, mStreamType, mIsMuted,
+                mVolIndex, mMinVolIndex, mMaxVolIndex, mVolGroup);
     }
 
     @Override
@@ -316,19 +328,20 @@
         VolumeInfo that = (VolumeInfo) o;
         return ((mUsesStreamType == that.mUsesStreamType)
                 && (mStreamType == that.mStreamType)
-            && (mIsMuted == that.mIsMuted)
-            && (mVolIndex == that.mVolIndex)
-            && (mMinVolIndex == that.mMinVolIndex)
-            && (mMaxVolIndex == that.mMaxVolIndex)
-            && (mVolGroupId == that.mVolGroupId));
+                && (mHasMuteCommand == that.mHasMuteCommand)
+                && (mIsMuted == that.mIsMuted)
+                && (mVolIndex == that.mVolIndex)
+                && (mMinVolIndex == that.mMinVolIndex)
+                && (mMaxVolIndex == that.mMaxVolIndex)
+                && Objects.equals(mVolGroup, that.mVolGroup));
     }
 
     @Override
     public String toString() {
         return new String("VolumeInfo:"
                 + (mUsesStreamType ? (" streamType:" + mStreamType)
-                    : (" volGroupId" + mVolGroupId))
-                + " muted:" + mIsMuted
+                    : (" volGroup:" + mVolGroup))
+                + (mHasMuteCommand ? (" muted:" + mIsMuted) : ("[no mute cmd]"))
                 + ((mVolIndex != INDEX_NOT_SET) ? (" volIndex:" + mVolIndex) : "")
                 + ((mMinVolIndex != INDEX_NOT_SET) ? (" min:" + mMinVolIndex) : "")
                 + ((mMaxVolIndex != INDEX_NOT_SET) ? (" max:" + mMaxVolIndex) : ""));
@@ -343,21 +356,29 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeBoolean(mUsesStreamType);
         dest.writeInt(mStreamType);
+        dest.writeBoolean(mHasMuteCommand);
         dest.writeBoolean(mIsMuted);
         dest.writeInt(mVolIndex);
         dest.writeInt(mMinVolIndex);
         dest.writeInt(mMaxVolIndex);
-        dest.writeInt(mVolGroupId);
+        if (!mUsesStreamType) {
+            mVolGroup.writeToParcel(dest, 0 /*ignored*/);
+        }
     }
 
     private VolumeInfo(@NonNull Parcel in) {
         mUsesStreamType = in.readBoolean();
         mStreamType = in.readInt();
+        mHasMuteCommand = in.readBoolean();
         mIsMuted = in.readBoolean();
         mVolIndex = in.readInt();
         mMinVolIndex = in.readInt();
         mMaxVolIndex = in.readInt();
-        mVolGroupId = in.readInt();
+        if (!mUsesStreamType) {
+            mVolGroup = AudioVolumeGroup.CREATOR.createFromParcel(in);
+        } else {
+            mVolGroup = null;
+        }
     }
 
     public static final @NonNull Parcelable.Creator<VolumeInfo> CREATOR =
diff --git a/media/java/android/media/audio/common/AidlConversion.java b/media/java/android/media/audio/common/AidlConversion.java
index 87634aa..4cf3b3e 100644
--- a/media/java/android/media/audio/common/AidlConversion.java
+++ b/media/java/android/media/audio/common/AidlConversion.java
@@ -509,7 +509,7 @@
                     } else if (MediaFormat.MIMETYPE_AUDIO_MPEGH_LC_L4.equals(aidl.encoding)) {
                         return AudioFormat.ENCODING_MPEGH_LC_L4;
                     } else if (MediaFormat.MIMETYPE_AUDIO_DTS_UHD.equals(aidl.encoding)) {
-                        return AudioFormat.ENCODING_DTS_UHD;
+                        return AudioFormat.ENCODING_DTS_UHD_P1;
                     } else if (MediaFormat.MIMETYPE_AUDIO_DRA.equals(aidl.encoding)) {
                         return AudioFormat.ENCODING_DRA;
                     } else {
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 1bd12af..7e1bbe3 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -244,12 +244,9 @@
                 mCallback = null;
                 return;
             }
-            if (handler == null) {
-                handler = new Handler();
-            }
+            Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
             callback.mSession = this;
-            CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
-                    callback);
+            CallbackMessageHandler msgHandler = new CallbackMessageHandler(looper, callback);
             mCallback = msgHandler;
         }
     }
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 6ae7dfb..9b8ec5e 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -45,6 +45,7 @@
     void onRequestTrackInfoList(int seq);
     void onRequestCurrentTvInputId(int seq);
     void onRequestStartRecording(in Uri programUri, int seq);
+    void onRequestStopRecording(in String recordingId, int seq);
     void onRequestSigning(
             in String id, in String algorithm, in String alias, in byte[] data, int seq);
     void onAdRequest(in AdRequest request, int Seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 84b9c9e..38fc717 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -64,6 +64,7 @@
     void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId);
     void notifySignalStrength(in IBinder sessionToken, int stength, int userId);
     void notifyRecordingStarted(in IBinder sessionToken, in String recordingId, int userId);
+    void notifyRecordingStopped(in IBinder sessionToken, in String recordingId, int userId);
     void setSurface(in IBinder sessionToken, in Surface surface, int userId);
     void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
             int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 95b4ffa..9e33536 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -54,6 +54,7 @@
     void notifyContentBlocked(in String rating);
     void notifySignalStrength(int strength);
     void notifyRecordingStarted(in String recordingId);
+    void notifyRecordingStopped(in String recordingId);
     void setSurface(in Surface surface);
     void dispatchSurfaceChanged(int format, int width, int height);
     void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 6478057..4ce5871 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -44,6 +44,7 @@
     void onRequestTrackInfoList();
     void onRequestCurrentTvInputId();
     void onRequestStartRecording(in Uri programUri);
+    void onRequestStopRecording(in String recordingId);
     void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data);
     void onAdRequest(in AdRequest request);
 }
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 042cb15..a2fdfe0 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -82,6 +82,7 @@
     private static final int DO_RELAYOUT_MEDIA_VIEW = 28;
     private static final int DO_REMOVE_MEDIA_VIEW = 29;
     private static final int DO_NOTIFY_RECORDING_STARTED = 30;
+    private static final int DO_NOTIFY_RECORDING_STOPPED = 31;
 
     private final HandlerCaller mCaller;
     private Session mSessionImpl;
@@ -169,6 +170,10 @@
                 mSessionImpl.notifyRecordingStarted((String) msg.obj);
                 break;
             }
+            case DO_NOTIFY_RECORDING_STOPPED: {
+                mSessionImpl.notifyRecordingStopped((String) msg.obj);
+                break;
+            }
             case DO_SEND_SIGNING_RESULT: {
                 SomeArgs args = (SomeArgs) msg.obj;
                 mSessionImpl.sendSigningResult((String) args.arg1, (byte[]) args.arg2);
@@ -392,6 +397,12 @@
     }
 
     @Override
+    public void notifyRecordingStopped(String recordingId) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(
+                DO_NOTIFY_RECORDING_STOPPED, recordingId));
+    }
+
+    @Override
     public void setSurface(Surface surface) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
     }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index a27fd10..287df40 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -499,6 +499,18 @@
             }
 
             @Override
+            public void onRequestStopRecording(String recordingId, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestStopRecording(recordingId);
+                }
+            }
+
+            @Override
             public void onRequestSigning(
                     String id, String algorithm, String alias, byte[] data, int seq) {
                 synchronized (mSessionCallbackRecordMap) {
@@ -1047,7 +1059,7 @@
             }
         }
 
-        void notifyRecordingStarted(@Nullable String recordingId) {
+        void notifyRecordingStarted(String recordingId) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
                 return;
@@ -1059,6 +1071,18 @@
             }
         }
 
+        void notifyRecordingStopped(String recordingId) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyRecordingStopped(mToken, recordingId, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
@@ -1729,6 +1753,15 @@
             });
         }
 
+        void postRequestStopRecording(String recordingId) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestStopRecording(mSession, recordingId);
+                }
+            });
+        }
+
         void postRequestSigning(String id, String algorithm, String alias, byte[] data) {
             mHandler.post(new Runnable() {
                 @Override
@@ -1884,11 +1917,22 @@
          * called.
          *
          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+         * @param programUri The Uri of the program to be recorded.
          */
         public void onRequestStartRecording(Session session, Uri programUri) {
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#RequestStopRecording} is
+         * called.
+         *
+         * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+         * @param recordingId The recordingId of the recording to be stopped.
+         */
+        public void onRequestStopRecording(Session session, String recordingId) {
+        }
+
+        /**
          * This is called when
          * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
          * called.
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 3d65effa..90eed9e 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -456,11 +456,22 @@
 
         /**
          * Receives started recording's ID.
+         *
+         * @param recordingId The ID of the recording started
+         */
+        public void onRecordingStarted(@NonNull String recordingId) {
+        }
+
+        /**
+         * Receives stopped recording's ID.
+         *
+         * @param recordingId The ID of the recording stopped
          * @hide
          */
-        public void onRecordingStarted(@Nullable String recordingId) {
+        public void onRecordingStopped(@NonNull String recordingId) {
         }
 
+
         /**
          * Receives signing result.
          * @param signingId the ID to identify the request. It's the same as the corresponding ID in
@@ -914,7 +925,15 @@
         /**
          * Requests starting of recording
          *
-         * @hide
+         * <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
+         * call {@link android.media.tv.TvRecordingClient#startRecording(Uri)} with the provided
+         * {@code programUri}.
+         * A non-null {@code programUri} implies the started recording should be of that specific
+         * program, whereas null {@code programUri} does not impose such a requirement and the
+         * recording can span across multiple TV programs.
+         *
+         * @param programUri The URI for the TV program to record.
+         * @see android.media.tv.TvRecordingClient#startRecording(Uri)
          */
         @CallSuper
         public void requestStartRecording(@Nullable Uri programUri) {
@@ -933,6 +952,33 @@
         }
 
         /**
+         * Requests starting of recording
+         *
+         * <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
+         * call {@link android.media.tv.TvRecordingClient#stopRecording()}.
+         * @see android.media.tv.TvRecordingClient#stopRecording()
+         *
+         * @hide
+         */
+        @CallSuper
+        public void requestStopRecording(@NonNull String recordingId) {
+            executeOrPostRunnableOnMainThread(() -> {
+                try {
+                    if (DEBUG) {
+                        Log.d(TAG, "requestStopRecording");
+                    }
+                    if (mSessionCallback != null) {
+                        mSessionCallback.onRequestStopRecording(recordingId);
+                    }
+                } catch (RemoteException e) {
+                    Log.w(TAG, "error in requestStopRecording", e);
+                }
+            });
+        }
+
+
+
+        /**
          * Requests signing of the given data.
          *
          * <p>This is used when the corresponding server of the broadcast-independent interactive
@@ -1142,11 +1188,21 @@
             onAdResponse(response);
         }
 
+        /**
+         * Calls {@link #onRecordingStarted(String)}.
+         */
         void notifyRecordingStarted(String recordingId) {
             onRecordingStarted(recordingId);
         }
 
         /**
+         * Calls {@link #onRecordingStopped(String)}.
+         */
+        void notifyRecordingStopped(String recordingId) {
+            onRecordingStopped(recordingId);
+        }
+
+        /**
          * Notifies when the session state is changed.
          *
          * @param state the current session state.
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 76ba69c..fcd781b 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -581,13 +581,12 @@
     }
 
     /**
-     * Alerts the TV interactive app that a recording has been started with recordingId
+     * Alerts the TV interactive app that a recording has been started.
      *
-     * @param recordingId The Id of the recording started
-     *
-     * @hide
+     * @param recordingId The ID of the recording started. This ID is created and maintained by the
+     *                    TV app and is used to identify the recording in the future.
      */
-    public void notifyRecordingStarted(@Nullable String recordingId) {
+    public void notifyRecordingStarted(@NonNull String recordingId) {
         if (DEBUG) {
             Log.d(TAG, "notifyRecordingStarted");
         }
@@ -597,6 +596,23 @@
     }
 
     /**
+     * Alerts the TV interactive app that a recording has been stopped.
+     *
+     * @param recordingId The ID of the recording stopped. This ID is created and maintained
+     *                    by the TV app when a recording is started.
+     * @see TvInteractiveAppView#notifyRecordingStarted(String)
+     * @hide
+     */
+    public void notifyRecordingStopped(@NonNull String recordingId) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyRecordingStopped");
+        }
+        if (mSession != null) {
+            mSession.notifyRecordingStopped(recordingId);
+        }
+    }
+
+    /**
      * Sends signing result to related TV interactive app.
      *
      * <p>This is used when the corresponding server of the broadcast-independent interactive
@@ -859,10 +875,9 @@
         /**
          * This is called when {@link TvInteractiveAppService.Session#requestStartRecording(Uri)}
          * is called.
+         *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
          * @param programUri The program URI to record
-         *
-         * @hide
          */
         public void onRequestStartRecording(
                 @NonNull String iAppServiceId,
@@ -870,6 +885,19 @@
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#requestStopRecording()}
+         * is called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @param recordingId The ID of the recording to stop.
+         * @hide
+         */
+        public void onRequestStopRecording(
+                @NonNull String iAppServiceId,
+                @NonNull String recordingId) {
+        }
+
+        /**
          * This is called when
          * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
          * called.
@@ -1207,6 +1235,20 @@
         }
 
         @Override
+        public void onRequestStopRecording(Session session, String recordingId) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestStopRecording");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestStopRecording - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onRequestStopRecording(mIAppServiceId, recordingId);
+            }
+        }
+
+        @Override
         public void onRequestSigning(
                 Session session, String id, String algorithm, String alias, byte[] data) {
             if (DEBUG) {
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 51b976b..7039a3e 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -998,6 +998,7 @@
     private native int nativeScan(int settingsType, FrontendSettings settings, int scanType);
     private native int nativeStopScan();
     private native int nativeSetLnb(Lnb lnb);
+    private native boolean nativeIsLnaSupported();
     private native int nativeSetLna(boolean enable);
     private native FrontendStatus nativeGetFrontendStatus(int[] statusTypes);
     private native Integer nativeGetAvSyncHwId(Filter filter);
@@ -1382,11 +1383,32 @@
     }
 
     /**
+     * Is Low Noise Amplifier (LNA) supported by the Tuner.
+     *
+     * <p>This API is only supported by Tuner HAL 3.0 or higher.
+     * Unsupported version would throw UnsupportedOperationException. Use
+     * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+     *
+     * @return {@code true} if supported, otherwise {@code false}.
+     * @throws UnsupportedOperationException if the Tuner HAL version is lower than 3.0
+     * @see android.media.tv.tuner.TunerVersionChecker#TUNER_VERSION_3_0
+     */
+    public boolean isLnaSupported() {
+        if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+                TunerVersionChecker.TUNER_VERSION_3_0, "isLnaSupported")) {
+            throw new UnsupportedOperationException("Tuner HAL version "
+                    + TunerVersionChecker.getTunerVersion() + " doesn't support this method.");
+        }
+        return nativeIsLnaSupported();
+    }
+
+    /**
      * Enable or Disable Low Noise Amplifier (LNA).
      *
      * @param enable {@code true} to activate LNA module; {@code false} to deactivate LNA.
      *
-     * @return result status of the operation.
+     * @return result status of the operation. {@link #RESULT_UNAVAILABLE} if the device doesn't
+     *         support LNA.
      */
     @Result
     public int setLnaEnabled(boolean enable) {
@@ -2376,19 +2398,20 @@
     }
 
     /**
-     * Request a frontend by frontend id.
+     * Request a frontend by frontend info.
      *
      * <p> This API is used if the applications want to select a desired frontend before
      * {@link tune} to use a specific satellite or sending SatCR DiSEqC command for {@link tune}.
      *
-     * @param desiredId the desired fronted Id. It can be retrieved by
+     * @param desiredFrontendInfo the FrontendInfo of the desired fronted. It can be retrieved by
      * {@link getAvailableFrontendInfos}
      *
      * @return result status of open operation.
      * @throws SecurityException if the caller does not have appropriate permissions.
      */
     @Result
-    public int requestFrontendById(int desiredId) {
+    public int applyFrontend(@NonNull FrontendInfo desiredFrontendInfo) {
+        Objects.requireNonNull(desiredFrontendInfo, "desiredFrontendInfo must not be null");
         mFrontendLock.lock();
         try {
             if (mFeOwnerTuner != null) {
@@ -2399,17 +2422,12 @@
                 Log.e(TAG, "A frontend has been opened before");
                 return RESULT_INVALID_STATE;
             }
-            FrontendInfo frontendInfo = getFrontendInfoById(desiredId);
-            if (frontendInfo == null) {
-                Log.e(TAG, "Failed to get a FrontendInfo by frontend id: " + desiredId);
-                return RESULT_UNAVAILABLE;
-            }
-            int frontendType = frontendInfo.getType();
+            mFrontendType = desiredFrontendInfo.getType();
+            mDesiredFrontendId = desiredFrontendInfo.getId();
             if (DEBUG) {
-                Log.d(TAG, "Opening frontend with type " + frontendType + ", id " + desiredId);
+                Log.d(TAG, "Applying frontend with type " + mFrontendType + ", id "
+                        + mDesiredFrontendId);
             }
-            mFrontendType = frontendType;
-            mDesiredFrontendId = desiredId;
             if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
                 return RESULT_UNAVAILABLE;
             }
diff --git a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
index 1a65832..4bcc3c6 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
@@ -28,6 +28,7 @@
 import android.os.Process;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FrameworkStatsLog;
 
 import java.util.concurrent.Executor;
@@ -48,7 +49,9 @@
     private static int sInstantId = 0;
     private int mSegmentId = 0;
     private int mOverflow;
-    private Boolean mIsStopped = true;
+    private final Object mIsStoppedLock = new Object();
+    @GuardedBy("mIsStoppedLock")
+    private boolean mIsStopped = true;
     private final Object mListenerLock = new Object();
 
     private native int nativeAttachFilter(Filter filter);
@@ -178,7 +181,7 @@
                 .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId,
                     FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD,
                     FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STARTED, mSegmentId, 0);
-        synchronized (mIsStopped) {
+        synchronized (mIsStoppedLock) {
             int result = nativeStartDvr();
             if (result == Tuner.RESULT_SUCCESS) {
                 mIsStopped = false;
@@ -201,7 +204,7 @@
                 .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId,
                     FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD,
                     FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STOPPED, mSegmentId, mOverflow);
-        synchronized (mIsStopped) {
+        synchronized (mIsStoppedLock) {
             int result = nativeStopDvr();
             if (result == Tuner.RESULT_SUCCESS) {
                 mIsStopped = true;
@@ -219,7 +222,7 @@
      */
     @Result
     public int flush() {
-        synchronized (mIsStopped) {
+        synchronized (mIsStoppedLock) {
             if (mIsStopped) {
                 return nativeFlushDvr();
             }
diff --git a/media/java/android/media/tv/tuner/filter/AvSettings.java b/media/java/android/media/tv/tuner/filter/AvSettings.java
index 15811d2..9144087 100644
--- a/media/java/android/media/tv/tuner/filter/AvSettings.java
+++ b/media/java/android/media/tv/tuner/filter/AvSettings.java
@@ -38,7 +38,8 @@
                     VIDEO_STREAM_TYPE_MPEG1, VIDEO_STREAM_TYPE_MPEG2,
                     VIDEO_STREAM_TYPE_MPEG4P2, VIDEO_STREAM_TYPE_AVC, VIDEO_STREAM_TYPE_HEVC,
                     VIDEO_STREAM_TYPE_VC1, VIDEO_STREAM_TYPE_VP8, VIDEO_STREAM_TYPE_VP9,
-                    VIDEO_STREAM_TYPE_AV1, VIDEO_STREAM_TYPE_AVS, VIDEO_STREAM_TYPE_AVS2})
+                    VIDEO_STREAM_TYPE_AV1, VIDEO_STREAM_TYPE_AVS, VIDEO_STREAM_TYPE_AVS2,
+                    VIDEO_STREAM_TYPE_VVC})
     @Retention(RetentionPolicy.SOURCE)
     public @interface VideoStreamType {}
 
@@ -76,6 +77,10 @@
      */
     public static final int VIDEO_STREAM_TYPE_HEVC = android.hardware.tv.tuner.VideoStreamType.HEVC;
     /*
+     * ITU-T Rec. H.266 and ISO/IEC 23090-3
+     */
+    public static final int VIDEO_STREAM_TYPE_VVC = android.hardware.tv.tuner.VideoStreamType.VVC;
+    /*
      * Microsoft VC.1
      */
     public static final int VIDEO_STREAM_TYPE_VC1 = android.hardware.tv.tuner.VideoStreamType.VC1;
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index d0973f4..8568c43 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -349,6 +349,15 @@
                         + mMainType + ", filter subtype=" + mSubtype + ". config main type="
                         + config.getType() + ", config subtype=" + subType);
             }
+            // Tuner only support VVC after tuner 3.0
+            if (s instanceof RecordSettings
+                    && ((RecordSettings) s).getScIndexType() == RecordSettings.INDEX_TYPE_SC_VVC
+                    && !TunerVersionChecker.isHigherOrEqualVersionTo(
+                            TunerVersionChecker.TUNER_VERSION_3_0)) {
+                Log.e(TAG, "Tuner version " + TunerVersionChecker.getTunerVersion()
+                        + " does not support VVC");
+                return Tuner.RESULT_UNAVAILABLE;
+            }
             return nativeConfigureFilter(config.getType(), subType, config);
         }
     }
diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java
index b16d9fb..698bbba 100644
--- a/media/java/android/media/tv/tuner/filter/RecordSettings.java
+++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java
@@ -23,8 +23,10 @@
 import android.hardware.tv.tuner.DemuxScAvcIndex;
 import android.hardware.tv.tuner.DemuxScHevcIndex;
 import android.hardware.tv.tuner.DemuxScIndex;
+import android.hardware.tv.tuner.DemuxScVvcIndex;
 import android.hardware.tv.tuner.DemuxTsIndex;
 import android.media.tv.tuner.TunerUtils;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -138,7 +140,8 @@
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "INDEX_TYPE_", value =
-            {INDEX_TYPE_NONE, INDEX_TYPE_SC, INDEX_TYPE_SC_HEVC, INDEX_TYPE_SC_AVC})
+            {INDEX_TYPE_NONE, INDEX_TYPE_SC, INDEX_TYPE_SC_HEVC, INDEX_TYPE_SC_AVC,
+             INDEX_TYPE_SC_VVC})
     public @interface ScIndexType {}
 
     /**
@@ -157,6 +160,10 @@
      * Start Code index for AVC.
      */
     public static final int INDEX_TYPE_SC_AVC = DemuxRecordScIndexType.SC_AVC;
+    /**
+     * Start Code index for VVC.
+     */
+    public static final int INDEX_TYPE_SC_VVC = DemuxRecordScIndexType.SC_VVC;
 
     /**
      * Indexes can be tagged by Start Code in PES (Packetized Elementary Stream)
@@ -253,6 +260,46 @@
     public static final int SC_HEVC_INDEX_SLICE_TRAIL_CRA = DemuxScHevcIndex.SLICE_TRAIL_CRA;
 
     /**
+     * Indexes can be tagged by NAL unit group in VVC according to ISO/IEC 23090-3.
+     *
+     * @hide
+     */
+    @IntDef(value = {SC_VVC_INDEX_SLICE_IDR_W_RADL, SC_VVC_INDEX_SLICE_IDR_N_LP,
+            SC_VVC_INDEX_SLICE_CRA, SC_VVC_INDEX_SLICE_GDR, SC_VVC_INDEX_VPS, SC_VVC_INDEX_SPS,
+            SC_VVC_INDEX_AUD})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScVvcIndex{}
+
+    /**
+     * SC VVC index SLICE_IDR_W_RADL (nal_unit_type=IDR_W_RADL) for random access key frame.
+     */
+    public static final int SC_VVC_INDEX_SLICE_IDR_W_RADL = DemuxScVvcIndex.SLICE_IDR_W_RADL;
+    /**
+     * SC VVC index SLICE_IDR_N_LP (nal_unit_type=IDR_N_LP) for random access key frame.
+     */
+    public static final int SC_VVC_INDEX_SLICE_IDR_N_LP = DemuxScVvcIndex.SLICE_IDR_N_LP;
+    /**
+     * SC VVC index SLICE_CRA (nal_unit_type=CRA_NUT) for random access key frame.
+     */
+    public static final int SC_VVC_INDEX_SLICE_CRA = DemuxScVvcIndex.SLICE_CRA;
+    /**
+     * SC VVC index SLICE_GDR (nal_unit_type=GDR_NUT) for random access point.
+     */
+    public static final int SC_VVC_INDEX_SLICE_GDR = DemuxScVvcIndex.SLICE_GDR;
+    /**
+     * Optional SC VVC index VPS (nal_unit_type=VPS_NUT) for sequence level info.
+     */
+    public static final int SC_VVC_INDEX_VPS = DemuxScVvcIndex.VPS;
+    /**
+     * SC VVC index SPS (nal_unit_type=SPS_NUT) for sequence level info.
+     */
+    public static final int SC_VVC_INDEX_SPS = DemuxScVvcIndex.SPS;
+    /**
+     * SC VVC index AUD (nal_unit_type=AUD_NUT) for AU (frame) boundary.
+     */
+    public static final int SC_VVC_INDEX_AUD = DemuxScVvcIndex.AUD;
+
+    /**
      * @hide
      */
     @IntDef(prefix = "SC_",
@@ -261,6 +308,11 @@
                 SC_INDEX_P_FRAME,
                 SC_INDEX_B_FRAME,
                 SC_INDEX_SEQUENCE,
+                SC_INDEX_I_SLICE,
+                SC_INDEX_P_SLICE,
+                SC_INDEX_B_SLICE,
+                SC_INDEX_SI_SLICE,
+                SC_INDEX_SP_SLICE,
                 SC_HEVC_INDEX_SPS,
                 SC_HEVC_INDEX_AUD,
                 SC_HEVC_INDEX_SLICE_CE_BLA_W_LP,
@@ -269,6 +321,13 @@
                 SC_HEVC_INDEX_SLICE_IDR_W_RADL,
                 SC_HEVC_INDEX_SLICE_IDR_N_LP,
                 SC_HEVC_INDEX_SLICE_TRAIL_CRA,
+                SC_VVC_INDEX_SLICE_IDR_W_RADL,
+                SC_VVC_INDEX_SLICE_IDR_N_LP,
+                SC_VVC_INDEX_SLICE_CRA,
+                SC_VVC_INDEX_SLICE_GDR,
+                SC_VVC_INDEX_VPS,
+                SC_VVC_INDEX_SPS,
+                SC_VVC_INDEX_AUD
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ScIndexMask {}
diff --git a/media/java/android/mtp/MtpPropertyGroup.java b/media/java/android/mtp/MtpPropertyGroup.java
index aff2e1b4..89e5e0d 100644
--- a/media/java/android/mtp/MtpPropertyGroup.java
+++ b/media/java/android/mtp/MtpPropertyGroup.java
@@ -230,7 +230,7 @@
                 case MtpConstants.PROPERTY_PERSISTENT_UID:
                     // The persistent uid must be unique and never reused among all objects,
                     // and remain the same between sessions.
-                    long puid = (object.getPath().toString().hashCode() << 32)
+                    long puid = (((long) object.getPath().toString().hashCode()) << 32)
                             + object.getModifiedTime();
                     list.append(id, property.code, property.type, puid);
                     break;
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/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index c18edcd..58078cf 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -360,6 +360,7 @@
                 lnb,
                 gFields.onLnbEventID,
                 (jint)lnbEventType);
+        env->DeleteLocalRef(lnb);
     } else {
         ALOGE("LnbClientCallbackImpl::onEvent:"
                 "Lnb object has been freed. Ignoring callback.");
@@ -378,6 +379,8 @@
                 lnb,
                 gFields.onLnbDiseqcMessageID,
                 array);
+        env->DeleteLocalRef(lnb);
+        env->DeleteLocalRef(array);
     } else {
         ALOGE("LnbClientCallbackImpl::onDiseqcMessage:"
                 "Lnb object has been freed. Ignoring callback.");
@@ -404,6 +407,7 @@
     jobject dvr(env->NewLocalRef(mDvrObj));
     if (!env->IsSameObject(dvr, nullptr)) {
         env->CallVoidMethod(dvr, gFields.onDvrRecordStatusID, (jint)status);
+        env->DeleteLocalRef(dvr);
     } else {
         ALOGE("DvrClientCallbackImpl::onRecordStatus:"
                 "Dvr object has been freed. Ignoring callback.");
@@ -416,6 +420,7 @@
     jobject dvr(env->NewLocalRef(mDvrObj));
     if (!env->IsSameObject(dvr, nullptr)) {
         env->CallVoidMethod(dvr, gFields.onDvrPlaybackStatusID, (jint)status);
+        env->DeleteLocalRef(dvr);
     } else {
         ALOGE("DvrClientCallbackImpl::onPlaybackStatus:"
                 "Dvr object has been freed. Ignoring callback.");
@@ -603,6 +608,7 @@
 
     jobject obj = env->NewObject(eventClazz, eventInit, tableId, version, sectionNum, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size,
@@ -655,6 +661,8 @@
         sc = mediaEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scAvc>();
         // Java uses the values defined by HIDL HAL. Left shift 4 bits.
         sc = sc << 4;
+    } else if (mediaEvent.scIndexMask.getTag() == DemuxFilterScIndexMask::Tag::scVvc) {
+        sc = mediaEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scVvc>();
     }
 
     jobject obj = env->NewObject(eventClazz, eventInit, streamId, isPtsPresent, pts, isDtsPresent,
@@ -673,6 +681,10 @@
     }
 
     env->SetObjectArrayElement(arr, size, obj);
+    if(audioDescriptor != nullptr) {
+        env->DeleteLocalRef(audioDescriptor);
+    }
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getPesEvent(jobjectArray &arr, const int size,
@@ -688,6 +700,7 @@
 
     jobject obj = env->NewObject(eventClazz, eventInit, streamId, dataLength, mpuSequenceNumber);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int size,
@@ -715,6 +728,8 @@
         sc = tsRecordEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scAvc>();
         // Java uses the values defined by HIDL HAL. Left shift 4 bits.
         sc = sc << 4;
+    } else if (tsRecordEvent.scIndexMask.getTag() == DemuxFilterScIndexMask::Tag::scVvc) {
+        sc = tsRecordEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scVvc>();
     }
 
     jint ts = tsRecordEvent.tsIndexMask;
@@ -725,6 +740,7 @@
     jobject obj =
             env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber, pts, firstMbInSlice);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getMmtpRecordEvent(jobjectArray &arr, const int size,
@@ -745,6 +761,7 @@
     jobject obj = env->NewObject(eventClazz, eventInit, scHevcIndexMask, byteNumber,
                                  mpuSequenceNumber, pts, firstMbInSlice, tsIndexMask);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getDownloadEvent(jobjectArray &arr, const int size,
@@ -764,6 +781,7 @@
     jobject obj = env->NewObject(eventClazz, eventInit, itemId, downloadId, mpuSequenceNumber,
                                  itemFragmentIndex, lastItemFragmentIndex, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getIpPayloadEvent(jobjectArray &arr, const int size,
@@ -776,6 +794,7 @@
     jint dataLength = ipPayloadEvent.dataLength;
     jobject obj = env->NewObject(eventClazz, eventInit, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getTemiEvent(jobjectArray &arr, const int size,
@@ -794,6 +813,8 @@
 
     jobject obj = env->NewObject(eventClazz, eventInit, pts, descrTag, array);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(array);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getScramblingStatusEvent(jobjectArray &arr, const int size,
@@ -807,6 +828,7 @@
                     .get<DemuxFilterMonitorEvent::Tag::scramblingStatus>();
     jobject obj = env->NewObject(eventClazz, eventInit, scramblingStatus);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getIpCidChangeEvent(jobjectArray &arr, const int size,
@@ -819,6 +841,7 @@
                                                  .get<DemuxFilterMonitorEvent::Tag::cid>();
     jobject obj = env->NewObject(eventClazz, eventInit, cid);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getRestartEvent(jobjectArray &arr, const int size,
@@ -830,6 +853,7 @@
     const int32_t &startId = event.get<DemuxFilterEvent::Tag::startId>();
     jobject obj = env->NewObject(eventClazz, eventInit, startId);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &events) {
@@ -922,10 +946,12 @@
             methodID = gFields.onSharedFilterEventID;
         }
         env->CallVoidMethod(filter, methodID, array);
+        env->DeleteLocalRef(filter);
     } else {
         ALOGE("FilterClientCallbackImpl::onFilterEvent:"
               "Filter object has been freed. Ignoring callback.");
     }
+    env->DeleteLocalRef(array);
 }
 
 void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
@@ -938,6 +964,7 @@
             methodID = gFields.onSharedFilterStatusID;
         }
         env->CallVoidMethod(filter, methodID, (jint)static_cast<uint8_t>(status));
+        env->DeleteLocalRef(filter);
     } else {
         ALOGE("FilterClientCallbackImpl::onFilterStatus:"
               "Filter object has been freed. Ignoring callback.");
@@ -1006,6 +1033,7 @@
                     frontend,
                     gFields.onFrontendEventID,
                     (jint)frontendEventType);
+            env->DeleteLocalRef(frontend);
         } else {
             ALOGW("FrontendClientCallbackImpl::onEvent:"
                     "Frontend object has been freed. Ignoring callback.");
@@ -1028,6 +1056,7 @@
             continue;
         }
         executeOnScanMessage(env, clazz, frontend, type, message);
+        env->DeleteLocalRef(frontend);
     }
 }
 
@@ -1069,6 +1098,7 @@
             env->SetLongArrayRegion(freqs, 0, v.size(), reinterpret_cast<jlong *>(&v[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onFrequenciesReport", "([J)V"),
                                 freqs);
+            env->DeleteLocalRef(freqs);
             break;
         }
         case FrontendScanMessageType::SYMBOL_RATE: {
@@ -1077,6 +1107,7 @@
             env->SetIntArrayRegion(symbolRates, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onSymbolRates", "([I)V"),
                                 symbolRates);
+            env->DeleteLocalRef(symbolRates);
             break;
         }
         case FrontendScanMessageType::HIERARCHY: {
@@ -1094,6 +1125,7 @@
             jintArray plpIds = env->NewIntArray(jintV.size());
             env->SetIntArrayRegion(plpIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onPlpIds", "([I)V"), plpIds);
+            env->DeleteLocalRef(plpIds);
             break;
         }
         case FrontendScanMessageType::GROUP_IDS: {
@@ -1101,6 +1133,7 @@
             jintArray groupIds = env->NewIntArray(jintV.size());
             env->SetIntArrayRegion(groupIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onGroupIds", "([I)V"), groupIds);
+            env->DeleteLocalRef(groupIds);
             break;
         }
         case FrontendScanMessageType::INPUT_STREAM_IDS: {
@@ -1109,6 +1142,7 @@
             env->SetIntArrayRegion(streamIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onInputStreamIds", "([I)V"),
                                 streamIds);
+            env->DeleteLocalRef(streamIds);
             break;
         }
         case FrontendScanMessageType::STANDARD: {
@@ -1142,12 +1176,14 @@
                 jboolean lls = info.bLlsFlag;
                 jobject obj = env->NewObject(plpClazz, init, plpId, lls);
                 env->SetObjectArrayElement(array, i, obj);
+                env->DeleteLocalRef(obj);
             }
             env->CallVoidMethod(frontend,
                                 env->GetMethodID(clazz, "onAtsc3PlpInfos",
                                                  "([Landroid/media/tv/tuner/frontend/"
                                                  "Atsc3PlpInfo;)V"),
                                 array);
+            env->DeleteLocalRef(array);
             break;
         }
         case FrontendScanMessageType::MODULATION: {
@@ -1219,6 +1255,7 @@
             env->SetIntArrayRegion(cellIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onDvbtCellIdsReported", "([I)V"),
                                 cellIds);
+            env->DeleteLocalRef(cellIds);
             break;
         }
         default:
@@ -1673,6 +1710,7 @@
     for (int i = 0; i < size; i++) {
         jobject readinessObj = env->NewObject(clazz, init, intTypes[i], readiness[i]);
         env->SetObjectArrayElement(valObj, i, readinessObj);
+        env->DeleteLocalRef(readinessObj);
     }
     return valObj;
 }
@@ -1785,6 +1823,13 @@
     return (int)result;
 }
 
+bool JTuner::isLnaSupported() {
+    if (sTunerClient == nullptr) {
+        return (int)Result::NOT_INITIALIZED;
+    }
+    return sTunerClient->isLnaSupported();
+}
+
 int JTuner::setLna(bool enable) {
     if (sTunerClient == nullptr) {
         return (int)Result::NOT_INITIALIZED;
@@ -2081,6 +2126,7 @@
                 jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
                                                        s.get<FrontendStatus::Tag::isDemodLocked>());
                 env->SetObjectField(statusObj, field, newBooleanObj);
+                env->DeleteLocalRef(newBooleanObj);
                 break;
             }
             case FrontendStatus::Tag::snr: {
@@ -2088,6 +2134,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::snr>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::ber: {
@@ -2095,6 +2142,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::ber>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::per: {
@@ -2102,6 +2150,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::per>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::preBer: {
@@ -2109,6 +2158,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::preBer>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::signalQuality: {
@@ -2116,6 +2166,7 @@
                 jobject newIntegerObj = env->NewObject(intClazz, initInt,
                                                        s.get<FrontendStatus::Tag::signalQuality>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::signalStrength: {
@@ -2124,6 +2175,7 @@
                         env->NewObject(intClazz, initInt,
                                        s.get<FrontendStatus::Tag::signalStrength>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::symbolRate: {
@@ -2131,6 +2183,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::symbolRate>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::innerFec: {
@@ -2141,6 +2194,7 @@
                         env->NewObject(longClazz, initLong,
                                        static_cast<long>(s.get<FrontendStatus::Tag::innerFec>()));
                 env->SetObjectField(statusObj, field, newLongObj);
+                env->DeleteLocalRef(newLongObj);
                 break;
             }
             case FrontendStatus::Tag::modulationStatus: {
@@ -2183,6 +2237,7 @@
                 if (valid) {
                     jobject newIntegerObj = env->NewObject(intClazz, initInt, intModulation);
                     env->SetObjectField(statusObj, field, newIntegerObj);
+                    env->DeleteLocalRef(newIntegerObj);
                 }
                 break;
             }
@@ -2192,6 +2247,7 @@
                         env->NewObject(intClazz, initInt,
                                        static_cast<jint>(s.get<FrontendStatus::Tag::inversion>()));
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::lnbVoltage: {
@@ -2200,6 +2256,7 @@
                         env->NewObject(intClazz, initInt,
                                        static_cast<jint>(s.get<FrontendStatus::Tag::lnbVoltage>()));
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::plpId: {
@@ -2207,6 +2264,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::plpId>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::isEWBS: {
@@ -2214,6 +2272,7 @@
                 jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
                                                        s.get<FrontendStatus::Tag::isEWBS>());
                 env->SetObjectField(statusObj, field, newBooleanObj);
+                env->DeleteLocalRef(newBooleanObj);
                 break;
             }
             case FrontendStatus::Tag::agc: {
@@ -2221,6 +2280,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::agc>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::isLnaOn: {
@@ -2228,6 +2288,7 @@
                 jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
                                                        s.get<FrontendStatus::Tag::isLnaOn>());
                 env->SetObjectField(statusObj, field, newBooleanObj);
+                env->DeleteLocalRef(newBooleanObj);
                 break;
             }
             case FrontendStatus::Tag::isLayerError: {
@@ -2241,6 +2302,7 @@
                     env->SetBooleanArrayRegion(valObj, i, 1, &x);
                 }
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::mer: {
@@ -2248,6 +2310,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::mer>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::freqOffset: {
@@ -2255,6 +2318,7 @@
                 jobject newLongObj = env->NewObject(longClazz, initLong,
                                                     s.get<FrontendStatus::Tag::freqOffset>());
                 env->SetObjectField(statusObj, field, newLongObj);
+                env->DeleteLocalRef(newLongObj);
                 break;
             }
             case FrontendStatus::Tag::hierarchy: {
@@ -2263,6 +2327,7 @@
                         env->NewObject(intClazz, initInt,
                                        static_cast<jint>(s.get<FrontendStatus::Tag::hierarchy>()));
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::isRfLocked: {
@@ -2270,6 +2335,7 @@
                 jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
                                                        s.get<FrontendStatus::Tag::isRfLocked>());
                 env->SetObjectField(statusObj, field, newBooleanObj);
+                env->DeleteLocalRef(newBooleanObj);
                 break;
             }
             case FrontendStatus::Tag::plpInfo: {
@@ -2289,9 +2355,11 @@
 
                     jobject plpObj = env->NewObject(plpClazz, initPlp, plpId, isLocked, uec);
                     env->SetObjectArrayElement(valObj, i, plpObj);
+                    env->DeleteLocalRef(plpObj);
                 }
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::modulations: {
@@ -2374,6 +2442,7 @@
                 if (valid) {
                     env->SetObjectField(statusObj, field, valObj);
                 }
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::bers: {
@@ -2384,6 +2453,7 @@
                 env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::codeRates: {
@@ -2394,6 +2464,7 @@
                 env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::bandwidth: {
@@ -2434,6 +2505,7 @@
                 if (valid) {
                     jobject newIntegerObj = env->NewObject(intClazz, initInt, intBandwidth);
                     env->SetObjectField(statusObj, field, newIntegerObj);
+                    env->DeleteLocalRef(newIntegerObj);
                 }
                 break;
             }
@@ -2465,6 +2537,7 @@
                 if (valid) {
                     jobject newIntegerObj = env->NewObject(intClazz, initInt, intInterval);
                     env->SetObjectField(statusObj, field, newIntegerObj);
+                    env->DeleteLocalRef(newIntegerObj);
                 }
                 break;
             }
@@ -2497,6 +2570,7 @@
                 if (valid) {
                     jobject newIntegerObj = env->NewObject(intClazz, initInt, intTransmissionMode);
                     env->SetObjectField(statusObj, field, newIntegerObj);
+                    env->DeleteLocalRef(newIntegerObj);
                 }
                 break;
             }
@@ -2505,6 +2579,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::uec>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::systemId: {
@@ -2512,6 +2587,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::systemId>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::interleaving: {
@@ -2558,6 +2634,7 @@
                 if (valid) {
                     env->SetObjectField(statusObj, field, valObj);
                 }
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::isdbtSegment: {
@@ -2568,6 +2645,7 @@
                 env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint*>(&v[0]));
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::tsDataRate: {
@@ -2578,6 +2656,7 @@
                 env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::rollOff: {
@@ -2605,6 +2684,7 @@
                 if (valid) {
                     jobject newIntegerObj = env->NewObject(intClazz, initInt, intRollOff);
                     env->SetObjectField(statusObj, field, newIntegerObj);
+                    env->DeleteLocalRef(newIntegerObj);
                 }
                 break;
             }
@@ -2613,6 +2693,7 @@
                 jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
                                                        s.get<FrontendStatus::Tag::isMiso>());
                 env->SetObjectField(statusObj, field, newBooleanObj);
+                env->DeleteLocalRef(newBooleanObj);
                 break;
             }
             case FrontendStatus::Tag::isLinear: {
@@ -2620,6 +2701,7 @@
                 jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
                                                        s.get<FrontendStatus::Tag::isLinear>());
                 env->SetObjectField(statusObj, field, newBooleanObj);
+                env->DeleteLocalRef(newBooleanObj);
                 break;
             }
             case FrontendStatus::Tag::isShortFrames: {
@@ -2627,6 +2709,7 @@
                 jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
                                                        s.get<FrontendStatus::Tag::isShortFrames>());
                 env->SetObjectField(statusObj, field, newBooleanObj);
+                env->DeleteLocalRef(newBooleanObj);
                 break;
             }
             case FrontendStatus::Tag::isdbtMode: {
@@ -2634,6 +2717,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::isdbtMode>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::partialReceptionFlag: {
@@ -2643,6 +2727,7 @@
                         env->NewObject(intClazz, initInt,
                                        s.get<FrontendStatus::Tag::partialReceptionFlag>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::streamIdList: {
@@ -2653,6 +2738,7 @@
                 env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::dvbtCellIds: {
@@ -2663,6 +2749,7 @@
                 env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::allPlpInfo: {
@@ -2678,9 +2765,11 @@
                     jobject plpObj = env->NewObject(plpClazz, initPlp, plpInfos[i].plpId,
                                                     plpInfos[i].bLlsFlag);
                     env->SetObjectArrayElement(valObj, i, plpObj);
+                    env->DeleteLocalRef(plpObj);
                 }
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
         }
@@ -2837,6 +2926,7 @@
                 .fec = fec,
         };
         plps[i] = frontendAtsc3PlpSettings;
+        env->DeleteLocalRef(plp);
     }
     return plps;
 }
@@ -3192,6 +3282,7 @@
                 env->GetIntField(layer, env->GetFieldID(layerClazz, "mCodeRate", "I")));
         frontendIsdbtSettings.layerSettings[i].numOfSegment =
                 env->GetIntField(layer, env->GetFieldID(layerClazz, "mNumOfSegments", "I"));
+        env->DeleteLocalRef(layer);
     }
 
     frontendSettings.set<FrontendSettings::Tag::isdbt>(frontendIsdbtSettings);
@@ -3482,6 +3573,11 @@
     return tuner->setLnb(lnbClient);
 }
 
+static bool android_media_tv_Tuner_is_lna_supported(JNIEnv *env, jobject thiz) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->isLnaSupported();
+}
+
 static int android_media_tv_Tuner_set_lna(JNIEnv *env, jobject thiz, jboolean enable) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->setLna(enable);
@@ -3727,6 +3823,8 @@
     } else if (scIndexType == DemuxRecordScIndexType::SC_AVC) {
         // Java uses the values defined by HIDL HAL. Right shift 4 bits.
         filterRecordSettings.scIndexMask.set<DemuxFilterScIndexMask::Tag::scAvc>(scIndexMask >> 4);
+    } else if (scIndexType == DemuxRecordScIndexType::SC_VVC) {
+        filterRecordSettings.scIndexMask.set<DemuxFilterScIndexMask::Tag::scVvc>(scIndexMask);
     }
     return filterRecordSettings;
 }
@@ -4730,6 +4828,7 @@
             (void *)android_media_tv_Tuner_scan },
     { "nativeStopScan", "()I", (void *)android_media_tv_Tuner_stop_scan },
     { "nativeSetLnb", "(Landroid/media/tv/tuner/Lnb;)I", (void *)android_media_tv_Tuner_set_lnb },
+    { "nativeIsLnaSupported", "()Z", (void *)android_media_tv_Tuner_is_lna_supported },
     { "nativeSetLna", "(Z)I", (void *)android_media_tv_Tuner_set_lna },
     { "nativeGetFrontendStatus", "([I)Landroid/media/tv/tuner/frontend/FrontendStatus;",
             (void *)android_media_tv_Tuner_get_frontend_status },
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index c74b2df..2b69e89 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -189,6 +189,7 @@
     int scan(const FrontendSettings& settings, FrontendScanType scanType);
     int stopScan();
     int setLnb(sp<LnbClient> lnbClient);
+    bool isLnaSupported();
     int setLna(bool enable);
     jobject openLnbByHandle(int handle);
     jobject openLnbByName(jstring name);
diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index 8515874..ab28fb4 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -210,4 +210,17 @@
     return -1;
 }
 
+bool TunerClient::isLnaSupported() {
+    if (mTunerService != nullptr) {
+        bool lnaSupported;
+        Status s = mTunerService->isLnaSupported(&lnaSupported);
+        if (!s.isOk()) {
+            return false;
+        }
+        return lnaSupported;
+    }
+
+    return false;
+}
+
 }  // namespace android
diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h
index 5410c1b..3f8b21c 100644
--- a/media/jni/tuner/TunerClient.h
+++ b/media/jni/tuner/TunerClient.h
@@ -148,6 +148,11 @@
      */
     int getMaxNumberOfFrontends(FrontendType frontendType);
 
+    /**
+     * Is Low Noise Amplifier (LNA) supported.
+     */
+    bool isLnaSupported();
+
 private:
     /**
      * An AIDL Tuner Service assigned at the first time the Tuner Client connects with
diff --git a/media/mca/effect/java/android/media/effect/EffectFactory.java b/media/mca/effect/java/android/media/effect/EffectFactory.java
index f6fcba7..cbb2736 100644
--- a/media/mca/effect/java/android/media/effect/EffectFactory.java
+++ b/media/mca/effect/java/android/media/effect/EffectFactory.java
@@ -486,11 +486,9 @@
 
     private Effect instantiateEffect(Class effectClass, String name) {
         // Make sure this is an Effect subclass
-        try {
-            effectClass.asSubclass(Effect.class);
-        } catch (ClassCastException e) {
+        if (!Effect.class.isAssignableFrom(effectClass)) {
             throw new IllegalArgumentException("Attempting to allocate effect '" + effectClass
-                + "' which is not a subclass of Effect!", e);
+                + "' which is not a subclass of Effect!");
         }
 
         // Look for the correct constructor
diff --git a/media/mca/filterfw/java/android/filterfw/core/Filter.java b/media/mca/filterfw/java/android/filterfw/core/Filter.java
index a608ef5..e82c046 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Filter.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Filter.java
@@ -90,9 +90,7 @@
             return false;
         }
         // Then make sure it's a subclass of Filter.
-        try {
-            filterClass.asSubclass(Filter.class);
-        } catch (ClassCastException e) {
+        if (!Filter.class.isAssignableFrom(filterClass)) {
             return false;
         }
         return true;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java b/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java
index 779df99..736e511 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java
@@ -112,9 +112,7 @@
 
     public Filter createFilterByClass(Class filterClass, String filterName) {
         // Make sure this is a Filter subclass
-        try {
-            filterClass.asSubclass(Filter.class);
-        } catch (ClassCastException e) {
+        if (!Filter.class.isAssignableFrom(filterClass)) {
             throw new IllegalArgumentException("Attempting to allocate class '" + filterClass
                 + "' which is not a subclass of Filter!");
         }
diff --git a/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java b/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java
index 8cf9a13..6ff1885 100644
--- a/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java
+++ b/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java
@@ -55,12 +55,12 @@
 
     public int getInt(String key) {
         Object result = get(key);
-        return result != null ? (Integer)result : null;
+        return result != null ? (Integer) result : 0;
     }
 
     public float getFloat(String key) {
         Object result = get(key);
-        return result != null ? (Float)result : null;
+        return result != null ? (Float) result : 0;
     }
 
     @Override
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/MtpClient.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/MtpClient.java
index edb5e37..158e698 100644
--- a/media/tests/CameraBrowser/src/com/android/camerabrowser/MtpClient.java
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/MtpClient.java
@@ -158,7 +158,7 @@
         filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
         filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
         filter.addAction(ACTION_USB_PERMISSION);
-        context.registerReceiver(mUsbReceiver, filter);
+        context.registerReceiver(mUsbReceiver, filter, Context.RECEIVER_EXPORTED/*UNAUDITED*/);
     }
 
     /**
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
index c5281657..8c05725 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
@@ -296,7 +296,7 @@
                 mMemWriter.write("End Memory :" + mEndMemory + "\n");
             }
         } catch (Exception e) {
-            e.toString();
+            // TODO
         }
     }
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
index 39add7e..c814eba 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
@@ -264,8 +264,14 @@
                 builder.append("**");
             }
 
-            if (elem instanceof Number) {
-                builder.append(String.format("%x", elem));
+            if (elem instanceof Byte) {
+                builder.append(String.format("%x", (Byte) elem));
+            } else if (elem instanceof Short) {
+                builder.append(String.format("%x", (Short) elem));
+            } else if (elem instanceof Integer) {
+                builder.append(String.format("%x", (Integer) elem));
+            } else if (elem instanceof Long) {
+                builder.append(String.format("%x", (Long) elem));
             } else {
                 builder.append(elem);
             }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
index 195df78..006f4e9 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
@@ -23,6 +23,7 @@
 import android.graphics.BitmapFactory;
 import android.media.ExifInterface;
 import android.os.Environment;
+import android.os.FileUtils;
 import android.test.AndroidTestCase;
 import android.util.Log;
 import android.system.ErrnoException;
@@ -37,6 +38,8 @@
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Objects;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -353,20 +356,23 @@
         }
     }
 
-    private void testSaveAttributes_withFileName(File imageFile, ExpectedValue expectedValue)
+    private void testSaveAttributes_withFileName(File srcFile, ExpectedValue expectedValue)
             throws IOException {
+        File imageFile = clone(srcFile);
         String verboseTag = imageFile.getName();
 
         ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
         exifInterface.saveAttributes();
         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
         compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+        assertBitmapsEquivalent(srcFile, imageFile);
+        assertSecondSaveProducesSameSizeFile(imageFile);
 
         // Test for modifying one attribute.
+        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
         String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
         exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
         exifInterface.saveAttributes();
-        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
         assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
         // Restore the backup value.
         exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
@@ -481,4 +487,56 @@
         // Test if it is possible to parse the volantis generated JPEG smoothly.
         testExifInterfaceForJpeg(VOLANTIS_JPEG, R.array.volantis_jpg);
     }
+
+    /**
+     * Asserts that {@code expectedImageFile} and {@code actualImageFile} can be decoded by
+     * {@link BitmapFactory} and the results have the same width, height and MIME type.
+     *
+     * <p>This does not check the image itself for similarity/equality.
+     */
+    private void assertBitmapsEquivalent(File expectedImageFile, File actualImageFile) {
+        BitmapFactory.Options expectedOptions = new BitmapFactory.Options();
+        Bitmap expectedBitmap = Objects.requireNonNull(
+                BitmapFactory.decodeFile(expectedImageFile.getAbsolutePath(), expectedOptions));
+        BitmapFactory.Options actualOptions = new BitmapFactory.Options();
+        Bitmap actualBitmap = Objects.requireNonNull(
+                BitmapFactory.decodeFile(actualImageFile.getAbsolutePath(), actualOptions));
+
+        assertEquals(expectedOptions.outWidth, actualOptions.outWidth);
+        assertEquals(expectedOptions.outHeight, actualOptions.outHeight);
+        assertEquals(expectedOptions.outMimeType, actualOptions.outMimeType);
+        assertEquals(expectedBitmap.getWidth(), actualBitmap.getWidth());
+        assertEquals(expectedBitmap.getHeight(), actualBitmap.getHeight());
+    }
+
+    /**
+     * Asserts that saving the file the second time (without modifying any attributes) produces
+     * exactly the same length file as the first save. The first save (with no modifications) is
+     * expected to (possibly) change the file length because {@link ExifInterface} may move/reformat
+     * the Exif block within the file, but the second save should not make further modifications.
+     */
+    private void assertSecondSaveProducesSameSizeFile(File imageFileAfterOneSave)
+            throws IOException {
+        File imageFileAfterTwoSaves = clone(imageFileAfterOneSave);
+        ExifInterface exifInterface = new ExifInterface(imageFileAfterTwoSaves.getAbsolutePath());
+        exifInterface.saveAttributes();
+        if (imageFileAfterOneSave.getAbsolutePath().endsWith(".png")
+                || imageFileAfterOneSave.getAbsolutePath().endsWith(".webp")) {
+            // PNG and (some) WebP files are (surprisingly) modified between the first and second
+            // save (b/249097443), so we check the difference between second and third save instead.
+            File imageFileAfterThreeSaves = clone(imageFileAfterTwoSaves);
+            exifInterface = new ExifInterface(imageFileAfterThreeSaves.getAbsolutePath());
+            exifInterface.saveAttributes();
+            assertEquals(imageFileAfterTwoSaves.length(), imageFileAfterThreeSaves.length());
+        } else {
+            assertEquals(imageFileAfterOneSave.length(), imageFileAfterTwoSaves.length());
+        }
+    }
+
+    private static File clone(File original) throws IOException {
+        final File cloned =
+                File.createTempFile("tmp_", +System.nanoTime() + "_" + original.getName());
+        FileUtils.copyFileOrThrow(original, cloned);
+        return cloned;
+    }
 }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
index fd1c2d3..37dd4b5 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
@@ -18,7 +18,7 @@
 
 import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;;
+import android.test.suitebuilder.annotation.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/media/tests/MediaRouter/AndroidTest.xml b/media/tests/MediaRouter/AndroidTest.xml
index d350e05..3b8c846 100644
--- a/media/tests/MediaRouter/AndroidTest.xml
+++ b/media/tests/MediaRouter/AndroidTest.xml
@@ -6,6 +6,8 @@
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
     <option name="test-suite-tag" value="apct"/>
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
     <option name="test-tag" value="MediaRouterTest"/>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 810b408..4193ffa 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -384,7 +384,7 @@
         MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
         assertThat(routeToSelect).isNotNull();
 
-        mManager.selectRoute(mPackageName, routeToSelect);
+        mManager.transfer(mPackageName, routeToSelect);
         assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
         assertThat(mManager.getRemoteSessions()).hasSize(1);
     }
@@ -410,7 +410,7 @@
 
         assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(1);
 
-        mManager.selectRoute(mPackageName, routeToSelect);
+        mManager.transfer(mPackageName, routeToSelect);
         assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
 
         List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
@@ -514,7 +514,7 @@
             }
         });
         awaitOnRouteChangedManager(
-                () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)),
+                () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID1)),
                 ROUTE_ID1,
                 route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
         assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
@@ -525,7 +525,7 @@
         RoutingSessionInfo sessionInfo = sessions.get(1);
 
         awaitOnRouteChangedManager(
-                () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)),
+                () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)),
                 ROUTE_ID5_TO_TRANSFER_TO,
                 route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
 
@@ -583,9 +583,9 @@
         assertThat(route1).isNotNull();
         assertThat(route2).isNotNull();
 
-        mManager.selectRoute(mPackageName, route1);
+        mManager.transfer(mPackageName, route1);
         assertThat(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
-        mManager.selectRoute(mPackageName, route2);
+        mManager.transfer(mPackageName, route2);
         assertThat(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
 
         // onTransferFailed/onSessionReleased should not be called.
@@ -703,7 +703,7 @@
             }
         });
 
-        mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1));
+        mManager.transfer(mPackageName, routes.get(ROUTE_ID1));
         assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
 
         List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
@@ -858,7 +858,7 @@
         });
 
         mRouter2.setOnGetControllerHintsListener(listener);
-        mManager.selectRoute(mPackageName, route);
+        mManager.transfer(mPackageName, route);
         assertThat(hintLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
         assertThat(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
 
@@ -903,7 +903,7 @@
             }
         });
 
-        mManager.selectRoute(mPackageName, routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+        mManager.transfer(mPackageName, routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
         assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
     }
 
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 8594ba5..254eb44 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -34,6 +34,7 @@
 
 cc_defaults {
     name: "libandroid_defaults",
+    cpp_std: "gnu++20",
     cflags: [
         "-Wall",
         "-Werror",
@@ -94,6 +95,7 @@
         "libpowermanager",
         "android.hardware.configstore@1.0",
         "android.hardware.configstore-utils",
+        "android.hardware.power-V4-ndk",
         "libnativedisplay",
     ],
 
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index cb0f22f..9b0f020 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -238,7 +238,7 @@
     ASurfaceControl_createFromWindow; # introduced=29
     ASurfaceControl_acquire; # introduced=31
     ASurfaceControl_release; # introduced=29
-    ASurfaceControl_fromSurfaceControl; # introduced=34
+    ASurfaceControl_fromJava; # introduced=34
     ASurfaceTexture_acquireANativeWindow; # introduced=28
     ASurfaceTexture_attachToGLContext; # introduced=28
     ASurfaceTexture_detachFromGLContext; # introduced=28
@@ -256,7 +256,7 @@
     ASurfaceTransaction_apply; # introduced=29
     ASurfaceTransaction_create; # introduced=29
     ASurfaceTransaction_delete; # introduced=29
-    ASurfaceTransaction_fromTransaction; # introduced=34
+    ASurfaceTransaction_fromJava; # introduced=34
     ASurfaceTransaction_reparent; # introduced=29
     ASurfaceTransaction_setBuffer; # introduced=29
     ASurfaceTransaction_setBufferAlpha; # introduced=29
@@ -330,6 +330,7 @@
     APerformanceHint_updateTargetWorkDuration; # introduced=Tiramisu
     APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu
     APerformanceHint_closeSession; # introduced=Tiramisu
+    APerformanceHint_sendHint; # introduced=UpsideDownCake
   local:
     *;
 };
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index d627984..9e97bd3 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "perf_hint"
 
+#include <aidl/android/hardware/power/SessionHint.h>
 #include <android/os/IHintManager.h>
 #include <android/os/IHintSession.h>
 #include <android/performance_hint.h>
@@ -25,14 +26,21 @@
 #include <performance_hint_private.h>
 #include <utils/SystemClock.h>
 
+#include <chrono>
 #include <utility>
 #include <vector>
 
 using namespace android;
 using namespace android::os;
 
+using namespace std::chrono_literals;
+
+using AidlSessionHint = aidl::android::hardware::power::SessionHint;
+
 struct APerformanceHintSession;
 
+constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
+
 struct APerformanceHintManager {
 public:
     static APerformanceHintManager* getInstance();
@@ -61,6 +69,7 @@
 
     int updateTargetWorkDuration(int64_t targetDurationNanos);
     int reportActualWorkDuration(int64_t actualDurationNanos);
+    int sendHint(int32_t hint);
 
 private:
     friend struct APerformanceHintManager;
@@ -70,8 +79,12 @@
     const int64_t mPreferredRateNanos;
     // Target duration for choosing update rate
     int64_t mTargetDurationNanos;
-    // Last update timestamp
-    int64_t mLastUpdateTimestamp;
+    // First target hit timestamp
+    int64_t mFirstTargetMetTimestamp;
+    // Last target hit timestamp
+    int64_t mLastTargetMetTimestamp;
+    // Last hint reported from sendHint indexed by hint value
+    std::vector<int64_t> mLastHintSentTimestamp;
     // Cached samples
     std::vector<int64_t> mActualDurationsNanos;
     std::vector<int64_t> mTimestampsNanos;
@@ -143,7 +156,13 @@
       : mHintSession(std::move(session)),
         mPreferredRateNanos(preferredRateNanos),
         mTargetDurationNanos(targetDurationNanos),
-        mLastUpdateTimestamp(elapsedRealtimeNano()) {}
+        mFirstTargetMetTimestamp(0),
+        mLastTargetMetTimestamp(0) {
+    const std::vector<AidlSessionHint> sessionHintRange{ndk::enum_range<AidlSessionHint>().begin(),
+                                                        ndk::enum_range<AidlSessionHint>().end()};
+
+    mLastHintSentTimestamp = std::vector<int64_t>(sessionHintRange.size(), 0);
+}
 
 APerformanceHintSession::~APerformanceHintSession() {
     binder::Status ret = mHintSession->close();
@@ -159,7 +178,7 @@
     }
     binder::Status ret = mHintSession->updateTargetWorkDuration(targetDurationNanos);
     if (!ret.isOk()) {
-        ALOGE("%s: HintSessionn updateTargetWorkDuration failed: %s", __FUNCTION__,
+        ALOGE("%s: HintSession updateTargetWorkDuration failed: %s", __FUNCTION__,
               ret.exceptionMessage().c_str());
         return EPIPE;
     }
@@ -170,7 +189,8 @@
      */
     mActualDurationsNanos.clear();
     mTimestampsNanos.clear();
-    mLastUpdateTimestamp = elapsedRealtimeNano();
+    mFirstTargetMetTimestamp = 0;
+    mLastTargetMetTimestamp = 0;
     return 0;
 }
 
@@ -183,25 +203,60 @@
     mActualDurationsNanos.push_back(actualDurationNanos);
     mTimestampsNanos.push_back(now);
 
-    /**
-     * Cache the hint if the hint is not overtime and the mLastUpdateTimestamp is
-     * still in the mPreferredRateNanos duration.
-     */
-    if (actualDurationNanos < mTargetDurationNanos &&
-        now - mLastUpdateTimestamp <= mPreferredRateNanos) {
-        return 0;
+    if (actualDurationNanos >= mTargetDurationNanos) {
+        // Reset timestamps if we are equal or over the target.
+        mFirstTargetMetTimestamp = 0;
+    } else {
+        // Set mFirstTargetMetTimestamp for first time meeting target.
+        if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp ||
+            (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) {
+            mFirstTargetMetTimestamp = now;
+        }
+        /**
+         * Rate limit the change if the update is over mPreferredRateNanos since first
+         * meeting target and less than mPreferredRateNanos since last meeting target.
+         */
+        if (now - mFirstTargetMetTimestamp > mPreferredRateNanos &&
+            now - mLastTargetMetTimestamp <= mPreferredRateNanos) {
+            return 0;
+        }
+        mLastTargetMetTimestamp = now;
     }
 
     binder::Status ret =
             mHintSession->reportActualWorkDuration(mActualDurationsNanos, mTimestampsNanos);
-    mActualDurationsNanos.clear();
-    mTimestampsNanos.clear();
     if (!ret.isOk()) {
         ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__,
               ret.exceptionMessage().c_str());
+        mFirstTargetMetTimestamp = 0;
+        mLastTargetMetTimestamp = 0;
         return EPIPE;
     }
-    mLastUpdateTimestamp = now;
+    mActualDurationsNanos.clear();
+    mTimestampsNanos.clear();
+
+    return 0;
+}
+
+int APerformanceHintSession::sendHint(int32_t hint) {
+    if (hint < 0 || hint >= static_cast<int32_t>(mLastHintSentTimestamp.size())) {
+        ALOGE("%s: invalid session hint %d", __FUNCTION__, hint);
+        return EINVAL;
+    }
+    int64_t now = elapsedRealtimeNano();
+
+    // Limit sendHint to a pre-detemined rate for safety
+    if (now < (mLastHintSentTimestamp[hint] + SEND_HINT_TIMEOUT)) {
+        return 0;
+    }
+
+    binder::Status ret = mHintSession->sendHint(hint);
+
+    if (!ret.isOk()) {
+        ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
+        return EPIPE;
+    }
+    mLastHintSentTimestamp[hint] = now;
     return 0;
 }
 
@@ -230,6 +285,10 @@
     return session->reportActualWorkDuration(actualDurationNanos);
 }
 
+int APerformanceHint_sendHint(APerformanceHintSession* session, int32_t hint) {
+    return session->sendHint(hint);
+}
+
 void APerformanceHint_closeSession(APerformanceHintSession* session) {
     delete session;
 }
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 8913799..ea20c6c 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -138,17 +138,14 @@
     SurfaceControl_release(surfaceControl);
 }
 
-ASurfaceControl* ASurfaceControl_fromSurfaceControl(JNIEnv* env, jobject surfaceControlObj) {
-    LOG_ALWAYS_FATAL_IF(!env,
-                        "nullptr passed to ASurfaceControl_fromSurfaceControl as env argument");
+ASurfaceControl* ASurfaceControl_fromJava(JNIEnv* env, jobject surfaceControlObj) {
+    LOG_ALWAYS_FATAL_IF(!env, "nullptr passed to ASurfaceControl_fromJava as env argument");
     LOG_ALWAYS_FATAL_IF(!surfaceControlObj,
-                        "nullptr passed to ASurfaceControl_fromSurfaceControl as surfaceControlObj "
-                        "argument");
+                        "nullptr passed to ASurfaceControl_fromJava as surfaceControlObj argument");
     SurfaceControl* surfaceControl =
             android_view_SurfaceControl_getNativeSurfaceControl(env, surfaceControlObj);
     LOG_ALWAYS_FATAL_IF(!surfaceControl,
-                        "surfaceControlObj passed to ASurfaceControl_fromSurfaceControl is not "
-                        "valid");
+                        "surfaceControlObj passed to ASurfaceControl_fromJava is not valid");
     SurfaceControl_acquire(surfaceControl);
     return reinterpret_cast<ASurfaceControl*>(surfaceControl);
 }
@@ -209,17 +206,15 @@
     delete transaction;
 }
 
-ASurfaceTransaction* ASurfaceTransaction_fromTransaction(JNIEnv* env, jobject transactionObj) {
-    LOG_ALWAYS_FATAL_IF(!env,
-                        "nullptr passed to ASurfaceTransaction_fromTransaction as env argument");
+ASurfaceTransaction* ASurfaceTransaction_fromJava(JNIEnv* env, jobject transactionObj) {
+    LOG_ALWAYS_FATAL_IF(!env, "nullptr passed to ASurfaceTransaction_fromJava as env argument");
     LOG_ALWAYS_FATAL_IF(!transactionObj,
-                        "nullptr passed to ASurfaceTransaction_fromTransaction as transactionObj "
+                        "nullptr passed to ASurfaceTransaction_fromJava as transactionObj "
                         "argument");
     Transaction* transaction =
             android_view_SurfaceTransaction_getNativeSurfaceTransaction(env, transactionObj);
     LOG_ALWAYS_FATAL_IF(!transaction,
-                        "surfaceControlObj passed to ASurfaceTransaction_fromTransaction is not "
-                        "valid");
+                        "surfaceControlObj passed to ASurfaceTransaction_fromJava is not valid");
     return reinterpret_cast<ASurfaceTransaction*>(transaction);
 }
 
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 30d0c35..fe3132e 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -66,9 +66,6 @@
         return mFilePath == o.mFilePath && mLocale == o.mLocale && mWeight == o.mWeight &&
                 mItalic == o.mItalic && mCollectionIndex == o.mCollectionIndex && mAxes == o.mAxes;
     }
-
-    AFont() = default;
-    AFont(const AFont&) = default;
 };
 
 struct FontHasher {
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index b17850e..0c2d3b6 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -51,6 +51,7 @@
                 (const ::std::vector<int64_t>& actualDurationNanos,
                  const ::std::vector<int64_t>& timeStampNanos),
                 (override));
+    MOCK_METHOD(Status, sendHint, (int32_t hints), (override));
     MOCK_METHOD(Status, close, (), (override));
     MOCK_METHOD(IBinder*, onAsBinder, (), (override));
 };
@@ -121,6 +122,22 @@
     result = APerformanceHint_reportActualWorkDuration(session, -1L);
     EXPECT_EQ(EINVAL, result);
 
+    int hintId = 2;
+    EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(1));
+    result = APerformanceHint_sendHint(session, hintId);
+    EXPECT_EQ(0, result);
+    usleep(110000); // Sleep for longer than the update timeout.
+    EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(1));
+    result = APerformanceHint_sendHint(session, hintId);
+    EXPECT_EQ(0, result);
+    // Expect to get rate limited if we try to send faster than the limiter allows
+    EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(0));
+    result = APerformanceHint_sendHint(session, hintId);
+    EXPECT_EQ(0, result);
+
+    result = APerformanceHint_sendHint(session, -1);
+    EXPECT_EQ(EINVAL, result);
+
     EXPECT_CALL(*iSession, close()).Times(Exactly(1));
     APerformanceHint_closeSession(session);
 }
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 1709dfd..10c570b 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -93,7 +93,7 @@
     ],
     static_libs: ["libarect"],
     fuzz_config: {
-        cc: ["scroggo@google.com"],
+        cc: ["dichenzhang@google.com","scroggo@google.com"],
         asan_options: [
             "detect_odr_violation=1",
         ],
diff --git a/packages/BackupRestoreConfirmation/res/values-en-rCA/strings.xml b/packages/BackupRestoreConfirmation/res/values-en-rCA/strings.xml
index d096d98..d5019d5 100644
--- a/packages/BackupRestoreConfirmation/res/values-en-rCA/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-en-rCA/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="backup_confirm_title" msgid="827563724209303345">"Full backup"</string>
-    <string name="restore_confirm_title" msgid="5469365809567486602">"Full restoration"</string>
+    <string name="restore_confirm_title" msgid="5469365809567486602">"Full restore"</string>
     <string name="backup_confirm_text" msgid="1878021282758896593">"A full backup of all data to a connected desktop computer has been requested. Do you want to allow this to happen?\n\nIf you did not request the backup yourself, do not allow the operation to proceed."</string>
     <string name="allow_backup_button_label" msgid="4217228747769644068">"Back up my data"</string>
     <string name="deny_backup_button_label" msgid="6009119115581097708">"Do not back up"</string>
@@ -32,7 +32,7 @@
     <string name="restore_enc_password_text" msgid="6140898525580710823">"If the restore data is encrypted, please enter the password below:"</string>
     <string name="toast_backup_started" msgid="550354281452756121">"Backup starting..."</string>
     <string name="toast_backup_ended" msgid="3818080769548726424">"Backup finished"</string>
-    <string name="toast_restore_started" msgid="7881679218971277385">"Restoration starting..."</string>
-    <string name="toast_restore_ended" msgid="1764041639199696132">"Restoration ended"</string>
+    <string name="toast_restore_started" msgid="7881679218971277385">"Restore starting..."</string>
+    <string name="toast_restore_ended" msgid="1764041639199696132">"Restore ended"</string>
     <string name="toast_timeout" msgid="5276598587087626877">"Operation timed out"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/Android.bp b/packages/CarrierDefaultApp/Android.bp
index 6990ad0..a216381 100644
--- a/packages/CarrierDefaultApp/Android.bp
+++ b/packages/CarrierDefaultApp/Android.bp
@@ -10,7 +10,12 @@
 android_app {
     name: "CarrierDefaultApp",
     srcs: ["src/**/*.java"],
-    libs: ["SliceStore"],
+    libs: ["SlicePurchaseController"],
     platform_apis: true,
     certificate: "platform",
+    optimize: {
+        proguard_flags_files: [
+            "proguard.flags",
+        ],
+    },
 }
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index a5b104b..c4bb17c 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -34,7 +34,8 @@
         android:label="@string/app_name"
         android:directBootAware="true"
         android:usesCleartextTraffic="true"
-        android:icon="@mipmap/ic_launcher_android">
+        android:icon="@mipmap/ic_launcher_android"
+        android:debuggable="true">
         <receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver"
             android:exported="true">
             <intent-filter>
@@ -73,16 +74,16 @@
             </intent-filter>
         </activity-alias>
 
-        <receiver android:name="com.android.carrierdefaultapp.SliceStoreBroadcastReceiver"
+        <receiver android:name="com.android.carrierdefaultapp.SlicePurchaseBroadcastReceiver"
                   android:exported="true">
             <intent-filter>
-                <action android:name="com.android.phone.slicestore.action.START_SLICE_STORE" />
-                <action android:name="com.android.phone.slicestore.action.SLICE_STORE_RESPONSE_TIMEOUT" />
-                <action android:name="com.android.phone.slicestore.action.NOTIFICATION_CANCELED" />
+                <action android:name="com.android.phone.slice.action.START_SLICE_PURCHASE_APP" />
+                <action android:name="com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_TIMEOUT" />
+                <action android:name="com.android.phone.slice.action.NOTIFICATION_CANCELED" />
             </intent-filter>
         </receiver>
-        <activity android:name="com.android.carrierdefaultapp.SliceStoreActivity"
-                  android:label="@string/slice_store_label"
+        <activity android:name="com.android.carrierdefaultapp.SlicePurchaseActivity"
+                  android:label="@string/slice_purchase_app_label"
                   android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
new file mode 100644
index 0000000..67d2184
--- /dev/null
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
@@ -0,0 +1,79 @@
+<!--
+  ~ 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
+    SlicePurchaseWebInterface. Test slice purchase application behavior 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_purchase_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_purchase_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 SlicePurchaseActivity</title>
+    <script type="text/javascript" src="slice_purchase_test.js"></script>
+</head>
+<body>
+    <h1>Test SlicePurchaseActivity</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(2, 'FAILURE_CODE_SERVER_UNREACHABLE')">
+        Notify purchase failed
+    </button>
+    <p id="purchase_failed"></p>
+</body>
+</html>
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.js b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
new file mode 100644
index 0000000..02c4fea
--- /dev/null
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_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 = SlicePurchaseWebInterface.getRequestedCapability();
+    document.getElementById("requested_capability").innerHTML =
+            "Premium capability requested: " + capability;
+}
+
+function testNotifyPurchaseSuccessful(duration_ms_long = 0) {
+    SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration_ms_long);
+    document.getElementById("purchase_successful").innerHTML =
+            "Notified purchase success for duration: " + duration_ms_long;
+}
+
+function testNotifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") {
+    SlicePurchaseWebInterface.notifyPurchaseFailed(failure_code, failure_reason);
+    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/res/values-af/strings.xml b/packages/CarrierDefaultApp/res/values-af/strings.xml
index 51cb6c8..3bc18ce 100644
--- a/packages/CarrierDefaultApp/res/values-af/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-af/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Die netwerk waarby jy probeer aansluit, het sekuriteitkwessies."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Byvoorbeeld, die aanmeldbladsy behoort dalk nie aan die organisasie wat gewys word nie."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Gaan in elk geval deur blaaier voort"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-am/strings.xml b/packages/CarrierDefaultApp/res/values-am/strings.xml
index d5d50ac..0efdbc4 100644
--- a/packages/CarrierDefaultApp/res/values-am/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-am/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"ለመቀላቀል እየሞከሩ ያሉት አውታረ መረብ የደህንነት ችግሮች አሉበት።"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ለምሳሌ፣ የመግቢያ ገጹ የሚታየው ድርጅት ላይሆን ይችላል።"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ለማንኛውም በአሳሽ በኩል ይቀጥሉ"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ar/strings.xml b/packages/CarrierDefaultApp/res/values-ar/strings.xml
index 0c67de7..cd979b2 100644
--- a/packages/CarrierDefaultApp/res/values-ar/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ar/strings.xml
@@ -16,4 +16,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"الشبكة التي تحاول الانضمام إليها بها مشاكل أمنية."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"على سبيل المثال، قد لا تنتمي صفحة تسجيل الدخول إلى المؤسسة المعروضة."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"المتابعة على أي حال عبر المتصفح"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-as/strings.xml b/packages/CarrierDefaultApp/res/values-as/strings.xml
index 4b36b06..fdafe2b 100644
--- a/packages/CarrierDefaultApp/res/values-as/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-as/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"আপুনি সংযোগ কৰিবলৈ বিচৰা নেটৱৰ্কটোত সুৰক্ষাজনিত সমস্যা আছে।"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"উদাহৰণস্বৰূপে, আপোনাক দেখুওৱা লগ ইনৰ পৃষ্ঠাটো প্ৰতিষ্ঠানটোৰ নিজা নহ\'বও পাৰে।"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"তথাপিও ব্ৰাউজাৰৰ জৰিয়তে অব্যাহত ৰাখক"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-az/strings.xml b/packages/CarrierDefaultApp/res/values-az/strings.xml
index d1af3c9..32c3c1c 100644
--- a/packages/CarrierDefaultApp/res/values-az/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-az/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Qoşulmaq istədiyiniz şəbəkənin təhlükəsizlik problemləri var."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Məsələn, giriş səhifəsi göstərilən təşkilata aid olmaya bilər."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Hər bir halda brazuer ilə davam edin"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml b/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
index 5d55790..932fc03 100644
--- a/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Mreža kojoj pokušavate da se pridružite ima bezbednosnih problema."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Na primer, stranica za prijavljivanje možda ne pripada prikazanoj organizaciji."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Ipak nastavi preko pregledača"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-be/strings.xml b/packages/CarrierDefaultApp/res/values-be/strings.xml
index 3ad85f2..20606f6 100644
--- a/packages/CarrierDefaultApp/res/values-be/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-be/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"У сеткі, да якой вы спрабуеце далучыцца, ёсць праблемы з бяспекай."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Напрыклад, старонка ўваходу можа не належаць указанай арганізацыі."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Усё роўна працягнуць праз браўзер"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-bg/strings.xml b/packages/CarrierDefaultApp/res/values-bg/strings.xml
index f5308f0..46a9db5 100644
--- a/packages/CarrierDefaultApp/res/values-bg/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-bg/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Мрежата, към която опитвате да се присъедините, има проблеми със сигурността."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Например страницата за вход може да не принадлежи на показаната организация."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Продължаване през браузър въпреки това"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-bn/strings.xml b/packages/CarrierDefaultApp/res/values-bn/strings.xml
index 448c42b..0826ae1 100644
--- a/packages/CarrierDefaultApp/res/values-bn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-bn/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"আপনি যে নেটওয়ার্কে যোগ দেওয়ার চেষ্টা করছেন সেটিতে নিরাপত্তাজনিত সমস্যা আছে।"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"যেমন, লগ-ইন পৃষ্ঠাটি যে প্রতিষ্ঠানের পৃষ্ঠা বলে দেখানো আছে, আসলে তা নাও হতে পারে৷"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"যাই হোক, ব্রাউজারের মাধ্যমে চালিয়ে যান"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-bs/strings.xml b/packages/CarrierDefaultApp/res/values-bs/strings.xml
index bc1ff33..e2bc342 100644
--- a/packages/CarrierDefaultApp/res/values-bs/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-bs/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Mreža kojoj pokušavate pristupiti ima sigurnosnih problema."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Naprimjer, stranica za prijavljivanje možda ne pripada prikazanoj organizaciji."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Ipak nastavi preko preglednika"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ca/strings.xml b/packages/CarrierDefaultApp/res/values-ca/strings.xml
index 66a8f37..bdde567 100644
--- a/packages/CarrierDefaultApp/res/values-ca/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ca/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"La xarxa a què et vols connectar té problemes de seguretat."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Per exemple, la pàgina d\'inici de sessió podria no pertànyer a l\'organització que es mostra."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continua igualment mitjançant el navegador"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-cs/strings.xml b/packages/CarrierDefaultApp/res/values-cs/strings.xml
index 5431836..d5fdac9 100644
--- a/packages/CarrierDefaultApp/res/values-cs/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-cs/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Síť, ke které se pokoušíte připojit, má bezpečnostní problémy."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Přihlašovací stránka například nemusí patřit zobrazované organizaci."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Přesto pokračovat prostřednictvím prohlížeče"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-da/strings.xml b/packages/CarrierDefaultApp/res/values-da/strings.xml
index b212117..8b2bb7c 100644
--- a/packages/CarrierDefaultApp/res/values-da/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-da/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Der er sikkerhedsproblemer på det netværk, du forsøger at logge ind på."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Det er f.eks. ikke sikkert, at loginsiden tilhører den anførte organisation."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Fortsæt alligevel via browseren"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-de/strings.xml b/packages/CarrierDefaultApp/res/values-de/strings.xml
index 95639ad..21af41c 100644
--- a/packages/CarrierDefaultApp/res/values-de/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-de/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Im Netzwerk, zu dem du eine Verbindung herstellen möchtest, liegen Sicherheitsprobleme vor."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Beispiel: Die Log-in-Seite gehört eventuell nicht zur angezeigten Organisation."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Trotzdem in einem Browser fortfahren"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-el/strings.xml b/packages/CarrierDefaultApp/res/values-el/strings.xml
index 016e68f..7514314 100644
--- a/packages/CarrierDefaultApp/res/values-el/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-el/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Παρουσιάζονται προβλήματα ασφάλειας στο δίκτυο στο οποίο προσπαθείτε να συνδεθείτε."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Για παράδειγμα, η σελίδα σύνδεσης ενδέχεται να μην ανήκει στον οργανισμό που εμφανίζεται."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Συνέχεια ούτως ή άλλως μέσω του προγράμματος περιήγησης"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rAU/strings.xml b/packages/CarrierDefaultApp/res/values-en-rAU/strings.xml
index a925a30..11d9437 100644
--- a/packages/CarrierDefaultApp/res/values-en-rAU/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rAU/strings.xml
@@ -14,4 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"The network that you’re trying to join has security issues."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page might not belong to the organisation shown."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continue anyway via browser"</string>
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Network boost"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommends a data boost"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Buy a network boost for better performance"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Not now"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Manage"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Purchase a network boost."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rCA/strings.xml b/packages/CarrierDefaultApp/res/values-en-rCA/strings.xml
index a925a30..b5e53ae 100644
--- a/packages/CarrierDefaultApp/res/values-en-rCA/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rCA/strings.xml
@@ -2,7 +2,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
-    <string name="android_system_label" msgid="2797790869522345065">"Mobile Operator"</string>
+    <string name="android_system_label" msgid="2797790869522345065">"Mobile Carrier"</string>
     <string name="portal_notification_id" msgid="5155057562457079297">"Mobile data has run out"</string>
     <string name="no_data_notification_id" msgid="668400731803969521">"Your mobile data has been deactivated"</string>
     <string name="portal_notification_detail" msgid="2295729385924660881">"Tap to visit the %s website"</string>
@@ -11,7 +11,13 @@
     <string name="no_mobile_data_connection" msgid="544980465184147010">"Add data or roaming plan through %s"</string>
     <string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Mobile data status"</string>
     <string name="action_bar_label" msgid="4290345990334377177">"Sign in to mobile network"</string>
-    <string name="ssl_error_warning" msgid="3127935140338254180">"The network that you’re trying to join has security issues."</string>
-    <string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page might not belong to the organisation shown."</string>
+    <string name="ssl_error_warning" msgid="3127935140338254180">"The network you’re trying to join has security issues."</string>
+    <string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page may not belong to the organization shown."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continue anyway via browser"</string>
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Network boost"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommends a data boost"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Buy a network boost for better performance"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Not now"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Manage"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Purchase a network boost."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rGB/strings.xml b/packages/CarrierDefaultApp/res/values-en-rGB/strings.xml
index a925a30..11d9437 100644
--- a/packages/CarrierDefaultApp/res/values-en-rGB/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rGB/strings.xml
@@ -14,4 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"The network that you’re trying to join has security issues."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page might not belong to the organisation shown."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continue anyway via browser"</string>
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Network boost"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommends a data boost"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Buy a network boost for better performance"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Not now"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Manage"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Purchase a network boost."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rIN/strings.xml b/packages/CarrierDefaultApp/res/values-en-rIN/strings.xml
index a925a30..11d9437 100644
--- a/packages/CarrierDefaultApp/res/values-en-rIN/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rIN/strings.xml
@@ -14,4 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"The network that you’re trying to join has security issues."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page might not belong to the organisation shown."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continue anyway via browser"</string>
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Network boost"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommends a data boost"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Buy a network boost for better performance"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Not now"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Manage"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Purchase a network boost."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rXC/strings.xml b/packages/CarrierDefaultApp/res/values-en-rXC/strings.xml
index d7ae1ce..f8a50e4 100644
--- a/packages/CarrierDefaultApp/res/values-en-rXC/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rXC/strings.xml
@@ -14,4 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‎‎‎‏‎‏‎‏‎‎‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‏‎‎‎The network you’re trying to join has security issues.‎‏‎‎‏‎"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎For example, the login page may not belong to the organization shown.‎‏‎‎‏‎"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‎‎‎‎‎‎Continue anyway via browser‎‏‎‎‏‎"</string>
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‏‎‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‎Network boost‎‏‎‎‏‎"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‏‎‎‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‎‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‎‎%s recommends a data boost‎‏‎‎‏‎"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‎‎‎‎‎Buy a network boost for better performance‎‏‎‎‏‎"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‎Not now‎‏‎‎‏‎"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‎‎‎‎‎‎‎‏‏‏‏‎‎Manage‎‏‎‎‏‎"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‎‏‎‏‎‎‎‎‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‎‏‎‏‎‏‏‏‏‎‏‏‎‎Purchase a network boost.‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml b/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml
index 0455603..bddae48 100644
--- a/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"La red a la que intentas conectarte tiene problemas de seguridad."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Por ejemplo, es posible que la página de acceso no pertenezca a la organización que aparece."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuar de todos modos desde el navegador"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-es/strings.xml b/packages/CarrierDefaultApp/res/values-es/strings.xml
index b5d038c..dd56770 100644
--- a/packages/CarrierDefaultApp/res/values-es/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-es/strings.xml
@@ -8,10 +8,22 @@
     <string name="portal_notification_detail" msgid="2295729385924660881">"Toca para acceder al sitio web de %s"</string>
     <string name="no_data_notification_detail" msgid="3112125343857014825">"Ponte en contacto con tu proveedor de servicios (%s)"</string>
     <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Sin conexión de datos móviles"</string>
-    <string name="no_mobile_data_connection" msgid="544980465184147010">"Añade un plan de datos o de itinerancia a través de %s"</string>
+    <string name="no_mobile_data_connection" msgid="544980465184147010">"Añade un plan de datos o de roaming a través de %s"</string>
     <string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Estado de la conexión de datos móviles"</string>
     <string name="action_bar_label" msgid="4290345990334377177">"Iniciar sesión en una red móvil"</string>
     <string name="ssl_error_warning" msgid="3127935140338254180">"La red a la que intentas unirte tiene problemas de seguridad."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Por ejemplo, es posible que la página de inicio de sesión no pertenezca a la organización mostrada."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuar de todos modos a través del navegador"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-et/strings.xml b/packages/CarrierDefaultApp/res/values-et/strings.xml
index 28cc9a9..8cdc291 100644
--- a/packages/CarrierDefaultApp/res/values-et/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-et/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Võrgul, millega üritate ühenduse luua, on turvaprobleeme."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Näiteks ei pruugi sisselogimisleht kuuluda kuvatavale organisatsioonile."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Jätka siiski brauseris"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-eu/strings.xml b/packages/CarrierDefaultApp/res/values-eu/strings.xml
index 04e641f..22565dc 100644
--- a/packages/CarrierDefaultApp/res/values-eu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-eu/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Erabili nahi duzun sareak segurtasun-arazoak ditu."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Adibidez, baliteke saioa hasteko orria adierazitako erakundearena ez izatea."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Jarraitu arakatzailearen bidez, halere"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-fa/strings.xml b/packages/CarrierDefaultApp/res/values-fa/strings.xml
index 5328a03..ecb9930 100644
--- a/packages/CarrierDefaultApp/res/values-fa/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fa/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"شبکه‌ای که می‌خواهید به آن بپیوندید مشکلات امنیتی دارد."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"به عنوان مثال، صفحه ورود به سیستم ممکن است متعلق به سازمان نشان داده شده نباشد."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"درهر صورت ازطریق مرورگر ادامه یابد"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-fi/strings.xml b/packages/CarrierDefaultApp/res/values-fi/strings.xml
index d416c1d..850f9db 100644
--- a/packages/CarrierDefaultApp/res/values-fi/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fi/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Verkossa, johon yrität muodostaa yhteyttä, havaittiin turvallisuusongelmia."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Kirjautumissivu ei välttämättä kuulu näytetylle organisaatiolle."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Jatka selaimen kautta"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml b/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml
index 1b8c262..61fc2e4 100644
--- a/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Le réseau que vous essayez de joindre présente des problèmes de sécurité."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Par exemple, la page de connexion pourrait ne pas appartenir à l\'organisation représentée."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuer quand même dans un navigateur"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-fr/strings.xml b/packages/CarrierDefaultApp/res/values-fr/strings.xml
index 3ae9570..ef1857d 100644
--- a/packages/CarrierDefaultApp/res/values-fr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fr/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Le réseau auquel vous essayez de vous connecter présente des problèmes de sécurité."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Par exemple, la page de connexion peut ne pas appartenir à l\'organisation représentée."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuer quand même dans le navigateur"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-gl/strings.xml b/packages/CarrierDefaultApp/res/values-gl/strings.xml
index 4f199ca..6dde8a8 100644
--- a/packages/CarrierDefaultApp/res/values-gl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-gl/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"A rede á que tentas unirte ten problemas de seguranza."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, é posible que a páxina de inicio de sesión non pertenza á organización que se mostra."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuar igualmente co navegador"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-gu/strings.xml b/packages/CarrierDefaultApp/res/values-gu/strings.xml
index 57710d0..473a035 100644
--- a/packages/CarrierDefaultApp/res/values-gu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-gu/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"તમે જોડાવાનો પ્રયાસ કરી રહ્યા છો તે નેટવર્કમાં સુરક્ષા સંબંધી સમસ્યાઓ છે."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ઉદાહરણ તરીકે, લોગિન પૃષ્ઠ બતાવવામાં આવેલી સંસ્થાનું ન પણ હોય."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"તો પણ બ્રાઉઝર મારફતે ચાલુ રાખો"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-hi/strings.xml b/packages/CarrierDefaultApp/res/values-hi/strings.xml
index b9d6f42..d878c1c 100644
--- a/packages/CarrierDefaultApp/res/values-hi/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hi/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"आप जिस नेटवर्क में शामिल होने की कोशिश कर रहे हैं उसमें सुरक्षा से जुड़ी समस्‍याएं हैं."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"उदाहरण के लिए, हो सकता है कि लॉगिन पेज दिखाए गए संगठन का ना हो."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ब्राउज़र के ज़रिए किसी भी तरह जारी रखें"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-hr/strings.xml b/packages/CarrierDefaultApp/res/values-hr/strings.xml
index 66531a7..0da2280 100644
--- a/packages/CarrierDefaultApp/res/values-hr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hr/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Mreža kojoj se pokušavate pridružiti ima sigurnosne poteškoće."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Na primjer, stranica za prijavu možda ne pripada prikazanoj organizaciji."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Ipak nastavi putem preglednika"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-hu/strings.xml b/packages/CarrierDefaultApp/res/values-hu/strings.xml
index 4ae6ea6..95c1db8 100644
--- a/packages/CarrierDefaultApp/res/values-hu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hu/strings.xml
@@ -8,10 +8,22 @@
     <string name="portal_notification_detail" msgid="2295729385924660881">"Koppintson a(z) %s webhely meglátogatásához"</string>
     <string name="no_data_notification_detail" msgid="3112125343857014825">"Vegye fel a kapcsolatot szolgáltatójával (%s)"</string>
     <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Nincs mobiladat-kapcsolat"</string>
-    <string name="no_mobile_data_connection" msgid="544980465184147010">"Adjon hozzá előfizetést vagy barangolási csomagot a következőn keresztül: %s"</string>
+    <string name="no_mobile_data_connection" msgid="544980465184147010">"Adjon hozzá előfizetést vagy roamingcsomagot a következőn keresztül: %s"</string>
     <string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Mobiladat-állapot"</string>
     <string name="action_bar_label" msgid="4290345990334377177">"Bejelentkezés a mobilhálózatra"</string>
     <string name="ssl_error_warning" msgid="3127935140338254180">"Biztonsági problémák vannak azzal a hálózattal, amelyhez csatlakozni szeretne."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Például lehetséges, hogy a bejelentkezési oldal nem a megjelenített szervezethez tartozik."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Folytatás ennek ellenére böngészőn keresztül"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-hy/strings.xml b/packages/CarrierDefaultApp/res/values-hy/strings.xml
index 99398bc..a846e04 100644
--- a/packages/CarrierDefaultApp/res/values-hy/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hy/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Ցանցը, որին փորձում եք միանալ, անվտանգության խնդիրներ ունի:"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Օրինակ՝ մուտքի էջը կարող է ցուցադրված կազմակերպության էջը չլինել:"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Շարունակել դիտարկիչի միջոցով"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-in/strings.xml b/packages/CarrierDefaultApp/res/values-in/strings.xml
index f48d31f..488ad09 100644
--- a/packages/CarrierDefaultApp/res/values-in/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-in/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Jaringan yang ingin Anda masuki memiliki masalah keamanan."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Misalnya, halaman login mungkin bukan milik organisasi yang ditampilkan."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Tetap lanjutkan melalui browser"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-is/strings.xml b/packages/CarrierDefaultApp/res/values-is/strings.xml
index cdba5be..ab4981d 100644
--- a/packages/CarrierDefaultApp/res/values-is/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-is/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Öryggisvandamál eru á netinu sem þú ert að reyna að tengjast."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Til dæmis getur verið að innskráningarsíðan tilheyri ekki fyrirtækinu sem birtist."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Halda samt áfram í vafra"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-it/strings.xml b/packages/CarrierDefaultApp/res/values-it/strings.xml
index a62ae86..95ea060 100644
--- a/packages/CarrierDefaultApp/res/values-it/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-it/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"La rete a cui stai tentando di accedere presenta problemi di sicurezza."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Ad esempio, la pagina di accesso potrebbe non appartenere all\'organizzazione indicata."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continua comunque dal browser"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-iw/strings.xml b/packages/CarrierDefaultApp/res/values-iw/strings.xml
index 550936c..263ed1a 100644
--- a/packages/CarrierDefaultApp/res/values-iw/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-iw/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"יש בעיות אבטחה ברשת שאליה אתה מנסה להתחבר."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"לדוגמה, ייתכן שדף ההתחברות אינו שייך לארגון המוצג."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"המשך בכל זאת באמצעות דפדפן"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ja/strings.xml b/packages/CarrierDefaultApp/res/values-ja/strings.xml
index e5977ae..3b22ae1 100644
--- a/packages/CarrierDefaultApp/res/values-ja/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ja/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"接続しようとしているネットワークにセキュリティの問題があります。"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"たとえば、ログインページが表示されている組織に属していない可能性があります。"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ブラウザから続行"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ka/strings.xml b/packages/CarrierDefaultApp/res/values-ka/strings.xml
index 91ae46d..4b9cd38 100644
--- a/packages/CarrierDefaultApp/res/values-ka/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ka/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"ქსელს, რომელთან დაკავშრებასაც ცდილობთ, უსაფრთხოების პრობლემები აქვს."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"მაგალითად, სისტემაში შესვლის გვერდი შეიძლება არ ეკუთვნოდეს ნაჩვენებ ორგანიზაციას."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"მაინც ბრაუზერში გაგრძელება"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-kk/strings.xml b/packages/CarrierDefaultApp/res/values-kk/strings.xml
index 0fb57bc..fce8dd2 100644
--- a/packages/CarrierDefaultApp/res/values-kk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-kk/strings.xml
@@ -3,15 +3,27 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
     <string name="android_system_label" msgid="2797790869522345065">"Мобильдік байланыс операторы"</string>
-    <string name="portal_notification_id" msgid="5155057562457079297">"Мобильдік деректер бітті"</string>
-    <string name="no_data_notification_id" msgid="668400731803969521">"Мобильдік деректер өшірілді"</string>
+    <string name="portal_notification_id" msgid="5155057562457079297">"Мобильдік интернет бітті"</string>
+    <string name="no_data_notification_id" msgid="668400731803969521">"Мобильдік интернет өшірілді"</string>
     <string name="portal_notification_detail" msgid="2295729385924660881">"%s вебсайтына кіру үшін түртіңіз"</string>
     <string name="no_data_notification_detail" msgid="3112125343857014825">"Қызмет көрсетушіге (%s) хабарласыңыз"</string>
-    <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Мобильдік деректер байланысы жоқ"</string>
+    <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Мобильдік интернет байланысы жоқ"</string>
     <string name="no_mobile_data_connection" msgid="544980465184147010">"%s арқылы деректер не роуминг жоспарын енгізу"</string>
     <string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Мобильді деректер күйі"</string>
     <string name="action_bar_label" msgid="4290345990334377177">"Мобильдік желіге тіркелу"</string>
     <string name="ssl_error_warning" msgid="3127935140338254180">"Қосылайын деп жатқан желіңізде қауіпсіздік мәселелері бар."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Мысалы, кіру беті көрсетілген ұйымға тиесілі болмауы мүмкін."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Бәрібір браузер арқылы жалғастыру"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-km/strings.xml b/packages/CarrierDefaultApp/res/values-km/strings.xml
index 6ef1066..983f09e 100644
--- a/packages/CarrierDefaultApp/res/values-km/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-km/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"បណ្តាញដែលអ្នកកំពុងព្យាយាមចូលមានបញ្ហាសុវត្ថិភាព។"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ឧទាហរណ៍៖ ទំព័រចូលនេះអាចនឹងមិនមែនជាកម្មសិទ្ធិរបស់ស្ថាប័នដែលបានបង្ហាញនេះទេ។"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"យ៉ាងណាក៏ដោយនៅតែបន្តតាមរយៈកម្មវិធីរុករកតាមអ៊ីនធឺណិត"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-kn/strings.xml b/packages/CarrierDefaultApp/res/values-kn/strings.xml
index ea4b09a..86b29ce 100644
--- a/packages/CarrierDefaultApp/res/values-kn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-kn/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"ನೀವು ಸೇರಬೇಕೆಂದಿರುವ ನೆಟ್‌ವರ್ಕ್, ಭದ್ರತೆ ಸಮಸ್ಯೆಗಳನ್ನು ಹೊಂದಿದೆ."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ಉದಾಹರಣೆಗೆ, ಲಾಗಿನ್ ಪುಟವು ತೋರಿಸಲಾಗಿರುವ ಸಂಸ್ಥೆಗೆ ಸಂಬಂಧಿಸಿಲ್ಲದಿರಬಹುದು."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ಪರವಾಗಿಲ್ಲ, ಬ್ರೌಸರ್ ಮೂಲಕ ಮುಂದುವರಿಸಿ"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ko/strings.xml b/packages/CarrierDefaultApp/res/values-ko/strings.xml
index d6b3d61..3c6f4a6 100644
--- a/packages/CarrierDefaultApp/res/values-ko/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ko/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"가입하려는 네트워크에 보안 문제가 있습니다."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"예를 들어 로그인 페이지가 표시된 조직에 속하지 않을 수 있습니다."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"브라우저를 통해 계속하기"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ky/strings.xml b/packages/CarrierDefaultApp/res/values-ky/strings.xml
index 199476f..3cece7d 100644
--- a/packages/CarrierDefaultApp/res/values-ky/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ky/strings.xml
@@ -1,17 +1,29 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="5247871339820894594">"ОператордунДемейкиКолдонмосу"</string>
+    <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
     <string name="android_system_label" msgid="2797790869522345065">"Мобилдик байланыш оператору"</string>
     <string name="portal_notification_id" msgid="5155057562457079297">"Мобилдик Интернетиңиздин трафиги түгөндү"</string>
     <string name="no_data_notification_id" msgid="668400731803969521">"Мобилдик Интернет өчүрүлгөн"</string>
     <string name="portal_notification_detail" msgid="2295729385924660881">"%s сайтына баш багуу үчүн басыңыз"</string>
     <string name="no_data_notification_detail" msgid="3112125343857014825">"%s Интернет провайдери менен байланышыңыз"</string>
     <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Мобилдик Интернет жок"</string>
-    <string name="no_mobile_data_connection" msgid="544980465184147010">"%s аркылуу дайындарды же роуминг планын кошуу"</string>
+    <string name="no_mobile_data_connection" msgid="544980465184147010">"%s аркылуу маалыматтарды же роуминг планын кошуу"</string>
     <string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Мобилдик Интернеттин абалы"</string>
     <string name="action_bar_label" msgid="4290345990334377177">"Мобилдик тармакка кирүү"</string>
     <string name="ssl_error_warning" msgid="3127935140338254180">"Кошулайын деген тармагыңызда коопсуздук көйгөйлөрү бар."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Мисалы, аккаунтка кирүү баракчасы көрсөтүлгөн уюмга таандык эмес болушу мүмкүн."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Баары бир серепчи аркылуу улантуу"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-lo/strings.xml b/packages/CarrierDefaultApp/res/values-lo/strings.xml
index 4a21d7c..c6c0533 100644
--- a/packages/CarrierDefaultApp/res/values-lo/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-lo/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"ເຄືອຂ່າຍທີ່ທ່ານກຳລັງເຂົ້າຮ່ວມມີບັນຫາຄວາມປອດໄພ."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ຕົວຢ່າງ, ໜ້າເຂົ້າສູ່ລະບົບອາດຈະບໍ່ແມ່ນຂອງອົງກອນທີ່ປາກົດ."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ດຳເນີນການຕໍ່ຜ່ານໂປຣແກຣມທ່ອງເວັບ"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-lt/strings.xml b/packages/CarrierDefaultApp/res/values-lt/strings.xml
index be452b7..fe33b1d 100644
--- a/packages/CarrierDefaultApp/res/values-lt/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-lt/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Kilo tinklo, prie kurio bandote prisijungti, saugos problemų."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Pavyzdžiui, prisijungimo puslapis gali nepriklausyti rodomai organizacijai."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Vis tiek tęsti naudojant naršyklę"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-lv/strings.xml b/packages/CarrierDefaultApp/res/values-lv/strings.xml
index 80a9b58..f8864e2 100644
--- a/packages/CarrierDefaultApp/res/values-lv/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-lv/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Tīklā, kuram mēģināt pievienoties, ir drošības problēmas."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Piemēram, pieteikšanās lapa, iespējams, nepieder norādītajai organizācijai."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Tomēr turpināt, izmantojot pārlūkprogrammu"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-mk/strings.xml b/packages/CarrierDefaultApp/res/values-mk/strings.xml
index 96b222c..0b8daaf 100644
--- a/packages/CarrierDefaultApp/res/values-mk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-mk/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Мрежата на која се обидувате да се придружите има проблеми со безбедноста."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"На пример, страницата за најавување може да не припаѓа на прикажаната организација."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Сепак продолжи преку прелистувач"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ml/strings.xml b/packages/CarrierDefaultApp/res/values-ml/strings.xml
index ae08ade..f27d4d8 100644
--- a/packages/CarrierDefaultApp/res/values-ml/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ml/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"നിങ്ങൾ ചേരാൻ ശ്രമിക്കുന്ന നെറ്റ്‌വർക്കിൽ സുരക്ഷാ പ്രശ്‌നങ്ങളുണ്ടായിരിക്കാം."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ഉദാഹരണത്തിന്, കാണിച്ചിരിക്കുന്ന ഓർഗനൈസേഷന്റേതായിരിക്കില്ല ലോഗിൻ പേജ്."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"എന്തായാലും ബ്രൗസർ വഴി തുടരുക"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-mn/strings.xml b/packages/CarrierDefaultApp/res/values-mn/strings.xml
index 1a9b72e..354bd34 100644
--- a/packages/CarrierDefaultApp/res/values-mn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-mn/strings.xml
@@ -5,7 +5,7 @@
     <string name="android_system_label" msgid="2797790869522345065">"Мобайл оператор компани"</string>
     <string name="portal_notification_id" msgid="5155057562457079297">"Мобайл дата дууссан"</string>
     <string name="no_data_notification_id" msgid="668400731803969521">"Таны мобайл датаг идэвхгүй болгосон"</string>
-    <string name="portal_notification_detail" msgid="2295729385924660881">"%s вэб хуудсанд зочлохын тулд товших"</string>
+    <string name="portal_notification_detail" msgid="2295729385924660881">"%s веб хуудсанд зочлохын тулд товших"</string>
     <string name="no_data_notification_detail" msgid="3112125343857014825">"%s үйлчилгээ үзүүлэгчтэйгээ холбогдоно уу"</string>
     <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Мобайл дата холболт алга"</string>
     <string name="no_mobile_data_connection" msgid="544980465184147010">"Дата эсвэл роуминг төлөвлөгөөг %s-р нэмнэ үү"</string>
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Таны холбогдох гэж буй сүлжээ аюулгүй байдлын асуудалтай байна."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Жишээлбэл нэвтрэх хуудас нь харагдаж буй байгууллагынх биш байж болно."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Ямар ч тохиолдолд хөтчөөр үргэлжлүүлэх"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-mr/strings.xml b/packages/CarrierDefaultApp/res/values-mr/strings.xml
index 79cc4aa..c61d3c8 100644
--- a/packages/CarrierDefaultApp/res/values-mr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-mr/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"तुम्ही ज्या नेटवर्कमध्‍ये सामील होण्याचा प्रयत्न करत आहात त्यात सुरक्षितता समस्या आहेत."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"उदाहरणार्थ, लॉग इन पृष्‍ठ दर्शवलेल्या संस्थेच्या मालकीचे नसू शकते."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"तरीही ब्राउझरद्वारे सुरू ठेवा"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ms/strings.xml b/packages/CarrierDefaultApp/res/values-ms/strings.xml
index 7aca5f0..366463f 100644
--- a/packages/CarrierDefaultApp/res/values-ms/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ms/strings.xml
@@ -5,7 +5,7 @@
     <string name="android_system_label" msgid="2797790869522345065">"Pembawa Mudah Alih"</string>
     <string name="portal_notification_id" msgid="5155057562457079297">"Data mudah alih telah habis"</string>
     <string name="no_data_notification_id" msgid="668400731803969521">"Data mudah alih anda telah dinyahaktifkan"</string>
-    <string name="portal_notification_detail" msgid="2295729385924660881">"Ketik untuk melawat tapak web %s"</string>
+    <string name="portal_notification_detail" msgid="2295729385924660881">"Ketik untuk melawat laman web %s"</string>
     <string name="no_data_notification_detail" msgid="3112125343857014825">"Sila hubungi penyedia perkhidmatan anda, %s"</string>
     <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Tiada sambungan data mudah alih"</string>
     <string name="no_mobile_data_connection" msgid="544980465184147010">"Tambahkan data atau pelan perayauan melalui %s"</string>
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Rangkaian yang cuba anda sertai mempunyai isu keselamatan."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Contohnya, halaman log masuk mungkin bukan milik organisasi yang ditunjukkan."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Teruskan juga melalui penyemak imbas"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-my/strings.xml b/packages/CarrierDefaultApp/res/values-my/strings.xml
index 82372f9..2fa6188 100644
--- a/packages/CarrierDefaultApp/res/values-my/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-my/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"သင်ချိတ်ဆက်ရန် ကြိုးစားနေသည့် ကွန်ရက်တွင် လုံခြုံရေးပြဿနာများ ရှိနေသည်။"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ဥပမာ− ဝင်ရောက်ရန် စာမျက်နှာသည် ပြသထားသည့် အဖွဲ့အစည်းနှင့် သက်ဆိုင်မှုမရှိခြင်း ဖြစ်နိုင်ပါသည်။"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"မည်သို့ပင်ဖြစ်စေ ဘရောက်ဇာမှတစ်ဆင့် ရှေ့ဆက်ရန်"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-nb/strings.xml b/packages/CarrierDefaultApp/res/values-nb/strings.xml
index 1bb9826..16f8eaa 100644
--- a/packages/CarrierDefaultApp/res/values-nb/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-nb/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Nettverket du prøver å logge på, har sikkerhetsproblemer."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Det er for eksempel mulig at påloggingssiden ikke tilhører organisasjonen som vises."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Fortsett likevel via nettleseren"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ne/strings.xml b/packages/CarrierDefaultApp/res/values-ne/strings.xml
index 2349f9d..cb175df 100644
--- a/packages/CarrierDefaultApp/res/values-ne/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ne/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"तपाईंले सामेल हुने प्रयास गरिरहनु भएको नेटवर्कमा सुरक्षा सम्बन्धी समस्याहरू छन्।"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"उदाहरणका लागि, लग इन पृष्ठ देखाइएको संस्थाको नहुन सक्छ।"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"जे भए पनि ब्राउजर मार्फत जारी राख्नुहोस्"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-nl/strings.xml b/packages/CarrierDefaultApp/res/values-nl/strings.xml
index 4e2c09b..8511ff5 100644
--- a/packages/CarrierDefaultApp/res/values-nl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-nl/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Het netwerk waarmee je verbinding probeert te maken, heeft beveiligingsproblemen."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Zo hoort de weergegeven inlogpagina misschien niet bij de weergegeven organisatie."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Toch doorgaan via browser"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-or/strings.xml b/packages/CarrierDefaultApp/res/values-or/strings.xml
index fd51ed0..65fd7bb 100644
--- a/packages/CarrierDefaultApp/res/values-or/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-or/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"ଆପଣ ଯୋଗ ଦେବାକୁ ଚେଷ୍ଟା କରୁଥିବା ନେଟୱର୍କର ସୁରକ୍ଷା ସମସ୍ୟା ଅଛି।"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ଉଦାହରଣସ୍ୱରୂପ, ଲଗଇନ୍‍ ପୃଷ୍ଠା ଦେଖାଯାଇଥିବା ସଂସ୍ଥାର ହୋଇନଥାଇପାରେ।"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ବ୍ରାଉଜର୍‍ ଜରିଆରେ ଯେମିତିବି ହେଉ ଜାରି ରଖନ୍ତୁ"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-pa/strings.xml b/packages/CarrierDefaultApp/res/values-pa/strings.xml
index f4d4053..0f096ab 100644
--- a/packages/CarrierDefaultApp/res/values-pa/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pa/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"ਤੁਸੀਂ ਜਿਸ ਨੈੱਟਵਰਕ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਰਹੇ ਹੋ ਉਸ ਵਿੱਚ ਸੁਰੱਖਿਆ ਸਬੰਧੀ ਸਮੱਸਿਆਵਾਂ ਹਨ।"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ਉਦਾਹਰਣ ਵੱਜੋਂ, ਲੌਗ-ਇਨ ਪੰਨਾ ਦਿਖਾਈ ਗਈ ਸੰਸਥਾ ਨਾਲ ਸੰਬੰਧਿਤ ਨਹੀਂ ਹੋ ਸਕਦਾ ਹੈ।"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ਬ੍ਰਾਊਜ਼ਰ ਰਾਹੀਂ ਫਿਰ ਵੀ ਜਾਰੀ ਰੱਖੋ"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-pl/strings.xml b/packages/CarrierDefaultApp/res/values-pl/strings.xml
index ac45e27..08bc767 100644
--- a/packages/CarrierDefaultApp/res/values-pl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pl/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"W sieci, z którą próbujesz się połączyć, występują problemy z zabezpieczeniami."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Na przykład strona logowania może nie należeć do wyświetlanej organizacji."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Kontynuuj mimo to w przeglądarce"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml b/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml
index 926de65..23b4152 100644
--- a/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"A rede à qual você está tentando se conectar tem problemas de segurança."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, a página de login pode não pertencer à organização mostrada."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuar mesmo assim pelo navegador"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml b/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml
index 107a9c2..34a564d 100644
--- a/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml
@@ -14,4 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"A rede à qual está a tentar aceder tem problemas de segurança."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, a página de início de sessão pode não pertencer à entidade apresentada."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuar mesmo assim através do navegador"</string>
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Otimização de rede"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recomenda um serviço de otimização de dados"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Compre um serviço de otimização de rede para melhorar o desempenho"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Agora não"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Gerir"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Comprar um serviço de otimização de rede."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-pt/strings.xml b/packages/CarrierDefaultApp/res/values-pt/strings.xml
index 926de65..23b4152 100644
--- a/packages/CarrierDefaultApp/res/values-pt/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pt/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"A rede à qual você está tentando se conectar tem problemas de segurança."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, a página de login pode não pertencer à organização mostrada."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuar mesmo assim pelo navegador"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ro/strings.xml b/packages/CarrierDefaultApp/res/values-ro/strings.xml
index b91aa813..165952c 100644
--- a/packages/CarrierDefaultApp/res/values-ro/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ro/strings.xml
@@ -5,13 +5,25 @@
     <string name="android_system_label" msgid="2797790869522345065">"Operator de telefonie mobilă"</string>
     <string name="portal_notification_id" msgid="5155057562457079297">"Datele mobile au expirat"</string>
     <string name="no_data_notification_id" msgid="668400731803969521">"Datele mobile au fost dezactivate"</string>
-    <string name="portal_notification_detail" msgid="2295729385924660881">"Atingeți pentru a accesa site-ul %s"</string>
-    <string name="no_data_notification_detail" msgid="3112125343857014825">"Contactați furnizorul de servicii %s"</string>
+    <string name="portal_notification_detail" msgid="2295729385924660881">"Atinge pentru a accesa site-ul %s"</string>
+    <string name="no_data_notification_detail" msgid="3112125343857014825">"Contactează furnizorul de servicii %s"</string>
     <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Nu există o conexiune de date mobile"</string>
-    <string name="no_mobile_data_connection" msgid="544980465184147010">"Adăugați un plan de date sau de roaming prin %s"</string>
+    <string name="no_mobile_data_connection" msgid="544980465184147010">"Adaugă un plan de date sau de roaming prin %s"</string>
     <string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Starea datelor mobile"</string>
-    <string name="action_bar_label" msgid="4290345990334377177">"Conectați-vă la rețeaua mobilă"</string>
+    <string name="action_bar_label" msgid="4290345990334377177">"Conectează-te la rețeaua mobilă"</string>
     <string name="ssl_error_warning" msgid="3127935140338254180">"Rețeaua la care încercați să vă conectați are probleme de securitate."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"De exemplu, este posibil ca pagina de conectare să nu aparțină organizației afișate."</string>
-    <string name="ssl_error_continue" msgid="1138548463994095584">"Continuați oricum prin browser"</string>
+    <string name="ssl_error_continue" msgid="1138548463994095584">"Continuă oricum prin browser"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ru/strings.xml b/packages/CarrierDefaultApp/res/values-ru/strings.xml
index ff24f1f..77ce91f 100644
--- a/packages/CarrierDefaultApp/res/values-ru/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ru/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Сеть, к которой вы хотите подключиться, небезопасна."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Например, страница входа в аккаунт может быть фиктивной."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Продолжить в браузере"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-si/strings.xml b/packages/CarrierDefaultApp/res/values-si/strings.xml
index 378a534..fe981ca 100644
--- a/packages/CarrierDefaultApp/res/values-si/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-si/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"ඔබ සම්බන්ධ වීමට උත්සහ කරන ජාලයේ ආරක්ෂක ගැටළු ඇත."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"උදාහරණයක් ලෙස, පුරනය වන පිටුව පෙන්වා ඇති සංවිධානයට අයිති නැති විය හැක."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"කෙසේ වුවත් බ්‍රවුසරය හරහා ඉදිරියට යන්න"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-sk/strings.xml b/packages/CarrierDefaultApp/res/values-sk/strings.xml
index 9fe38da..1a2ef10 100644
--- a/packages/CarrierDefaultApp/res/values-sk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sk/strings.xml
@@ -14,4 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Sieť, ku ktorej sa pokúšate pripojiť, má problémy so zabezpečením"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Napríklad prihlasovacia stránka nemusí patriť uvedenej organizácii."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Pokračovať pomocou prehliadača"</string>
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Zrýchlenie siete"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s odporúča zrýchlenie dátového pripojenia"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Kúpte si zrýchlenie siete zvyšujúce výkon"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Teraz nie"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Spravovať"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Kúpte si zrýchlenie siete."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-sl/strings.xml b/packages/CarrierDefaultApp/res/values-sl/strings.xml
index bdbc155..1a0f74b9 100644
--- a/packages/CarrierDefaultApp/res/values-sl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sl/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Omrežje, ki se mu poskušate pridružiti, ima varnostne težave."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Stran za prijavo na primer morda ne pripada prikazani organizaciji."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Vseeno nadaljuj v brskalniku"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-sq/strings.xml b/packages/CarrierDefaultApp/res/values-sq/strings.xml
index d4899e0..f6e1935 100644
--- a/packages/CarrierDefaultApp/res/values-sq/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sq/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Rrjeti në të cilin po përpiqesh të bashkohesh ka probleme sigurie."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"për shembull, faqja e identifikimit mund të mos i përkasë organizatës së shfaqur."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Vazhdo gjithsesi nëpërmjet shfletuesit"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-sr/strings.xml b/packages/CarrierDefaultApp/res/values-sr/strings.xml
index 34c3bdc..e615ead 100644
--- a/packages/CarrierDefaultApp/res/values-sr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sr/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Мрежа којој покушавате да се придружите има безбедносних проблема."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"На пример, страница за пријављивање можда не припада приказаној организацији."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Ипак настави преко прегледача"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-sv/strings.xml b/packages/CarrierDefaultApp/res/values-sv/strings.xml
index 4e76c8d..778663b 100644
--- a/packages/CarrierDefaultApp/res/values-sv/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sv/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Nätverket du försöker ansluta till har säkerhetsproblem."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Det kan t.ex. hända att inloggningssidan inte tillhör den organisation som visas."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Fortsätt ändå via webbläsaren"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-sw/strings.xml b/packages/CarrierDefaultApp/res/values-sw/strings.xml
index a52a733..4f0745c 100644
--- a/packages/CarrierDefaultApp/res/values-sw/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sw/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Mtandao unaojaribu kujiunga nao una matatizo ya usalama."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Kwa mfano, ukurasa wa kuingia katika akaunti unaweza usiwe unamilikiwa na shirika lililoonyeshwa."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Endelea hata hivyo kupitia kivinjari"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ta/strings.xml b/packages/CarrierDefaultApp/res/values-ta/strings.xml
index 1a786fa..a1d2928 100644
--- a/packages/CarrierDefaultApp/res/values-ta/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ta/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"நீங்கள் சேர முயலும் நெட்வொர்க்கில் பாதுகாப்புச் சிக்கல்கள் உள்ளன."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"எடுத்துக்காட்டாக, உள்நுழைவுப் பக்கமானது காட்டப்படும் அமைப்பிற்குச் சொந்தமானதாக இல்லாமல் இருக்கலாம்."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"பரவாயில்லை, உலாவி வழியாகத் தொடர்க"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-te/strings.xml b/packages/CarrierDefaultApp/res/values-te/strings.xml
index 8877c0c..7139903 100644
--- a/packages/CarrierDefaultApp/res/values-te/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-te/strings.xml
@@ -3,15 +3,27 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
     <string name="android_system_label" msgid="2797790869522345065">"మొబైల్ క్యారియర్"</string>
-    <string name="portal_notification_id" msgid="5155057562457079297">"మొబైల్ డేటాని పూర్తిగా ఉపయోగించారు"</string>
+    <string name="portal_notification_id" msgid="5155057562457079297">"మొబైల్ డేటాను పూర్తిగా ఉపయోగించారు"</string>
     <string name="no_data_notification_id" msgid="668400731803969521">"మీ మొబైల్ డేటా నిష్క్రియం చేయబడింది"</string>
     <string name="portal_notification_detail" msgid="2295729385924660881">"%s వెబ్‌సైట్‌ని సందర్శించడం కోసం నొక్కండి"</string>
     <string name="no_data_notification_detail" msgid="3112125343857014825">"దయచేసి మీ సేవా ప్రదాత %sని సంప్రదించండి"</string>
     <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"మొబైల్ డేటా కనెక్షన్ లేదు"</string>
     <string name="no_mobile_data_connection" msgid="544980465184147010">"%s ద్వారా డేటాను లేదా రోమింగ్ ప్లాన్‌ను జోడించండి"</string>
     <string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"మొబైల్ డేటా స్థితి"</string>
-    <string name="action_bar_label" msgid="4290345990334377177">"మొబైల్ నెట్‌వర్క్‌కి సైన్ ఇన్ చేయి"</string>
+    <string name="action_bar_label" msgid="4290345990334377177">"మొబైల్ నెట్‌వర్క్‌కి సైన్ ఇన్ చేయండి"</string>
     <string name="ssl_error_warning" msgid="3127935140338254180">"మీరు చేరడానికి ప్రయత్నిస్తున్న నెట్‌వర్క్ భద్రతా సమస్యలను కలిగి ఉంది."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ఉదాహరణకు, లాగిన్ పేజీ చూపిన సంస్థకు చెందినది కాకపోవచ్చు."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ఏదేమైనా బ్రౌజర్ ద్వారా కొనసాగించు"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-th/strings.xml b/packages/CarrierDefaultApp/res/values-th/strings.xml
index 8d30cfd..5c63bb1 100644
--- a/packages/CarrierDefaultApp/res/values-th/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-th/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"เครือข่ายที่คุณพยายามเข้าร่วมมีปัญหาด้านความปลอดภัย"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ตัวอย่างเช่น หน้าเข้าสู่ระบบอาจไม่ใช่ขององค์กรที่แสดงไว้"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ดำเนินการต่อผ่านเบราว์เซอร์"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-tl/strings.xml b/packages/CarrierDefaultApp/res/values-tl/strings.xml
index 083ec9a..9e320c8 100644
--- a/packages/CarrierDefaultApp/res/values-tl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-tl/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"May mga isyu sa seguridad ang network na sinusubukan mong salihan."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Halimbawa, maaaring hindi pag-aari ng ipinapakitang organisasyon ang page ng login."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Magpatuloy pa rin sa pamamagitan ng browser"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-tr/strings.xml b/packages/CarrierDefaultApp/res/values-tr/strings.xml
index aa17431..63616cc 100644
--- a/packages/CarrierDefaultApp/res/values-tr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-tr/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Katılmaya çalıştığınız ağda güvenlik sorunları var."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Örneğin, giriş sayfası, gösterilen kuruluşa ait olmayabilir."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Yine de tarayıcıyla devam et"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-uk/strings.xml b/packages/CarrierDefaultApp/res/values-uk/strings.xml
index 8381e35..bd44327 100644
--- a/packages/CarrierDefaultApp/res/values-uk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-uk/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"У мережі, до якої ви намагаєтеся під’єднатись, є проблеми з безпекою."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Наприклад, сторінка входу може не належати вказаній організації."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Усе одно продовжити у веб-переглядачі"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ur/strings.xml b/packages/CarrierDefaultApp/res/values-ur/strings.xml
index fc286b8..3294cf5 100644
--- a/packages/CarrierDefaultApp/res/values-ur/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ur/strings.xml
@@ -6,7 +6,7 @@
     <string name="portal_notification_id" msgid="5155057562457079297">"موبائل ڈیٹا ختم ہو چکا ہے"</string>
     <string name="no_data_notification_id" msgid="668400731803969521">"آپ کا موبائل ڈیٹا غیر فعال کر دیا گیا ہے"</string>
     <string name="portal_notification_detail" msgid="2295729385924660881">"‏‎%s ویب سائٹ ملاحظہ کرنے کیلئے تھپتھپائیں"</string>
-    <string name="no_data_notification_detail" msgid="3112125343857014825">"‏براہ کرم اپنے خدمت کے فراہم کنندہ %s سے رابطہ کریں"</string>
+    <string name="no_data_notification_detail" msgid="3112125343857014825">"‏براہ کرم اپنے سروس فراہم کنندہ %s سے رابطہ کریں"</string>
     <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"کوئی موبائل ڈیٹا کنکشن نہیں ہے"</string>
     <string name="no_mobile_data_connection" msgid="544980465184147010">"‏%s کے ذریعے ڈیٹا یا رومنگ پلان شامل کریں"</string>
     <string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"موبائل ڈیٹا کی صورت حال"</string>
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"آپ جس نیٹ ورک میں شامل ہونے کی کوشش کر رہے ہیں، اس میں سیکیورٹی کے مسائل ہیں۔"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"مثال کے طور پر ہو سکتا ہے کہ لاگ ان صفحہ دکھائی گئی تنظیم سے تعلق نہ رکھتا ہو۔"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"براؤزر کے ذریعے بہرحال جاری رکھیں"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-uz/strings.xml b/packages/CarrierDefaultApp/res/values-uz/strings.xml
index f2801c8..4eca545 100644
--- a/packages/CarrierDefaultApp/res/values-uz/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-uz/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Siz ulanmoqchi bo‘lgan tarmoqda xavfsizlik bilan bog‘liq muammolar mavjud."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Masalan, tizimga kirish sahifasi ko‘rsatilgan tashkilotga tegishli bo‘lmasligi mumkin."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Brauzerda davom ettirish"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-vi/strings.xml b/packages/CarrierDefaultApp/res/values-vi/strings.xml
index 1047cd4..d8f15e8 100644
--- a/packages/CarrierDefaultApp/res/values-vi/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-vi/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Mạng mà bạn đang cố gắng tham gia có vấn đề về bảo mật."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Ví dụ: trang đăng nhập có thể không thuộc về tổ chức được hiển thị."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Vẫn tiếp tục qua trình duyệt"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml b/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml
index f84cedb..4ce19f5 100644
--- a/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"您尝试加入的网络存在安全问题。"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"例如,登录页面可能并不属于页面上显示的单位。"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"仍然通过浏览器继续操作"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml b/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml
index ad76306..f019beb 100644
--- a/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"您正在嘗試加入的網絡有安全性問題。"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"例如,登入頁面可能並不屬於所顯示的機構。"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"仍要透過瀏覽器繼續操作"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml b/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml
index ccf95c1..32724b5 100644
--- a/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"你嘗試加入的網路有安全性問題。"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"例如,登入網頁中顯示的機構可能並非該網頁實際隸屬的機構。"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"仍要透過瀏覽器繼續操作"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-zu/strings.xml b/packages/CarrierDefaultApp/res/values-zu/strings.xml
index 4ef80c1..669822c 100644
--- a/packages/CarrierDefaultApp/res/values-zu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zu/strings.xml
@@ -14,4 +14,16 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Inethiwekhi ozama ukuyijoyina inezinkinga zokuvikela."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Isibonelo, ikhasi lokungena ngemvume kungenzeka lingelenhlangano ebonisiwe."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Qhubeka noma kunjalo ngesiphequluli"</string>
+    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+    <skip />
+    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+    <skip />
+    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+    <skip />
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index ce88a40..3dcdf00 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -15,7 +15,7 @@
     <string name="ssl_error_continue">Continue anyway via browser</string>
 
     <!-- Telephony notification channel name for network boost notifications. -->
-    <string name="network_boost_notification_channel">Network Boost</string>
+    <string name="network_boost_notification_channel">Network boost</string>
     <!-- Notification title text for the network boost notification. -->
     <string name="network_boost_notification_title">%s recommends a data boost</string>
     <!-- Notification detail text for the network boost notification. -->
@@ -25,6 +25,6 @@
     <!-- Notification button text to manage the network boost notification. -->
     <string name="network_boost_notification_button_manage">Manage</string>
 
-    <!-- Label to display when the slice store opens. -->
-    <string name="slice_store_label">Purchase a network boost.</string>
+    <!-- Label to display when the slice purchase application opens. -->
+    <string name="slice_purchase_app_label">Purchase a network boost.</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
new file mode 100644
index 0000000..c524037
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -0,0 +1,210 @@
+/*
+ * 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.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.URLUtil;
+import android.webkit.WebView;
+
+import com.android.phone.slice.SlicePurchaseController;
+
+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 SlicePurchaseWebInterface.getRequestedCapability()}.
+ * If the purchase is successful, the carrier website shall notify the slice purchase application
+ * using the JavaScript interface method
+ * {@code SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration)}, where {@code duration} is
+ * the optional duration of the network boost.
+ * If the purchase was not successful, the carrier website shall notify the slice purchase
+ * application using the JavaScript interface method
+ * {@code SlicePurchaseWebInterface.notifyPurchaseFailed(code, reason)}, where {@code code} is the
+ * {@link SlicePurchaseController.FailureCode} indicating the reason for failure and {@code reason}
+ * is the human-readable reason for failure if the failure code is
+ * {@link SlicePurchaseController#FAILURE_CODE_UNKNOWN}.
+ * 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 SlicePurchaseActivity extends Activity {
+    private static final String TAG = "SlicePurchaseActivity";
+
+    @NonNull private WebView mWebView;
+    @NonNull private Context mApplicationContext;
+    @NonNull private Intent mIntent;
+    @Nullable private URL mUrl;
+    private int mSubId;
+    @TelephonyManager.PremiumCapability protected int mCapability;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mIntent = getIntent();
+        mSubId = mIntent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        mCapability = mIntent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
+        mApplicationContext = getApplicationContext();
+        mUrl = getUrl();
+        logd("onCreate: subId=" + mSubId + ", capability="
+                + TelephonyManager.convertPremiumCapabilityToString(mCapability)
+                + ", url=" + mUrl);
+
+        // Cancel network boost notification
+        mApplicationContext.getSystemService(NotificationManager.class)
+                .cancel(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
+
+        // Verify intent and values are valid
+        if (!SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)) {
+            loge("Not starting SlicePurchaseActivity with an invalid Intent: " + mIntent);
+            SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+                    mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
+            finishAndRemoveTask();
+            return;
+        }
+        if (mUrl == null) {
+            String error = "Unable to create a URL from carrier configs.";
+            loge(error);
+            Intent data = new Intent();
+            data.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE,
+                    SlicePurchaseController.FAILURE_CODE_CARRIER_URL_UNAVAILABLE);
+            data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, error);
+            SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
+                    mIntent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
+            finishAndRemoveTask();
+            return;
+        }
+        if (mSubId != SubscriptionManager.getDefaultSubscriptionId()) {
+            loge("Unable to start the slice purchase application on the non-default data "
+                    + "subscription: " + mSubId);
+            SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+                    mIntent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION);
+            finishAndRemoveTask();
+            return;
+        }
+
+        // Create a reference to this activity in SlicePurchaseBroadcastReceiver
+        SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(mCapability, this);
+
+        // Create and configure WebView
+        setupWebView();
+    }
+
+    protected void onPurchaseSuccessful(long duration) {
+        logd("onPurchaseSuccessful: Carrier website indicated successfully purchased premium "
+                + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability)
+                + (duration > 0 ? " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes."
+                : "."));
+        Intent intent = new Intent();
+        intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_DURATION, duration);
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
+                mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent);
+        finishAndRemoveTask();
+    }
+
+    protected void onPurchaseFailed(@SlicePurchaseController.FailureCode int failureCode,
+            @Nullable String failureReason) {
+        logd("onPurchaseFailed: Carrier website indicated purchase failed for premium capability "
+                + TelephonyManager.convertPremiumCapabilityToString(mCapability) + " with code: "
+                + failureCode + " and reason: " + failureReason);
+        Intent data = new Intent();
+        data.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE, failureCode);
+        data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, failureReason);
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
+                mIntent, SlicePurchaseController.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
+        //  the slice purchase application.
+        if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
+            mWebView.goBack();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    protected void onDestroy() {
+        logd("onDestroy: User canceled the purchase by closing the application.");
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+                mIntent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
+        SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(mCapability);
+        super.onDestroy();
+    }
+
+    @Nullable private URL getUrl() {
+        String url = mApplicationContext.getSystemService(CarrierConfigManager.class)
+                .getConfigForSubId(mSubId).getString(
+                        CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
+        boolean isUrlValid = URLUtil.isValidUrl(url);
+        if (URLUtil.isAssetUrl(url)) {
+            isUrlValid = url.equals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
+        }
+        if (isUrlValid) {
+            try {
+                return new URL(url);
+            } catch (MalformedURLException ignored) {
+            }
+        }
+        loge("Invalid URL: " + url);
+        return null;
+    }
+
+    private void setupWebView() {
+        // Create WebView
+        mWebView = new WebView(this);
+
+        // Enable JavaScript for the carrier purchase website to send results back to
+        //  the slice purchase application.
+        mWebView.getSettings().setJavaScriptEnabled(true);
+        mWebView.addJavascriptInterface(
+                new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface");
+
+        // Display WebView
+        setContentView(mWebView);
+
+        // Load the URL
+        mWebView.loadUrl(mUrl.toString());
+    }
+
+    private static void logd(@NonNull String s) {
+        Log.d(TAG, s);
+    }
+
+    private static void loge(@NonNull String s) {
+        Log.e(TAG, s);
+    }
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
new file mode 100644
index 0000000..b322b8b
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -0,0 +1,368 @@
+/*
+ * 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.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+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;
+import android.util.Log;
+import android.webkit.WebView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.phone.slice.SlicePurchaseController;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * The SlicePurchaseBroadcastReceiver listens for
+ * {@link SlicePurchaseController#ACTION_START_SLICE_PURCHASE_APP} from the SlicePurchaseController
+ * in the phone process to start the slice purchase application. It displays the network boost
+ * notification to the user and will start the {@link SlicePurchaseActivity} to display the
+ * {@link WebView} to purchase network boosts from the user's carrier.
+ */
+public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
+    private static final String TAG = "SlicePurchaseBroadcastReceiver";
+
+    /**
+     * 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 SlicePurchaseActivity} for each capability, if it exists. */
+    private static final Map<Integer, WeakReference<SlicePurchaseActivity>>
+            sSlicePurchaseActivities = new HashMap<>();
+
+    /** Channel ID for the network boost notification. */
+    private static final String NETWORK_BOOST_NOTIFICATION_CHANNEL_ID = "network_boost";
+    /** Tag for the network boost notification. */
+    public static final String NETWORK_BOOST_NOTIFICATION_TAG = "SlicePurchaseApp.Notification";
+    /** Action for when the user clicks the "Not now" button on the network boost notification. */
+    private static final String ACTION_NOTIFICATION_CANCELED =
+            "com.android.phone.slice.action.NOTIFICATION_CANCELED";
+
+    /**
+     * Create a weak reference to {@link SlicePurchaseActivity}. The reference will be removed when
+     * {@link SlicePurchaseActivity#onDestroy()} is called.
+     *
+     * @param capability The premium capability requested.
+     * @param slicePurchaseActivity The instance of SlicePurchaseActivity.
+     */
+    public static void updateSlicePurchaseActivity(
+            @TelephonyManager.PremiumCapability int capability,
+            @NonNull SlicePurchaseActivity slicePurchaseActivity) {
+        sSlicePurchaseActivities.put(capability, new WeakReference<>(slicePurchaseActivity));
+    }
+
+    /**
+     * Remove the weak reference to {@link SlicePurchaseActivity} when
+     * {@link SlicePurchaseActivity#onDestroy()} is called.
+     *
+     * @param capability The premium capability requested.
+     */
+    public static void removeSlicePurchaseActivity(
+            @TelephonyManager.PremiumCapability int capability) {
+        sSlicePurchaseActivities.remove(capability);
+    }
+
+    /**
+     * Send the PendingIntent containing the corresponding slice purchase application response.
+     *
+     * @param intent The Intent containing the PendingIntent extra.
+     * @param extra The extra to get the PendingIntent to send.
+     */
+    public static void sendSlicePurchaseAppResponse(@NonNull Intent intent, @NonNull String extra) {
+        PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
+        if (pendingIntent == null) {
+            loge("PendingIntent does not exist for extra: " + extra);
+            return;
+        }
+        try {
+            pendingIntent.send();
+        } catch (PendingIntent.CanceledException e) {
+            loge("Unable to send " + getPendingIntentType(extra) + " intent: " + e);
+        }
+    }
+
+    /**
+     * Send the PendingIntent containing the corresponding slice purchase application 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 sendSlicePurchaseAppResponseWithData(@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 slice purchase
+     * application. This checks that all necessary extras exist and that the values are valid.
+     *
+     * @param intent The intent to check
+     * @return {@code true} if the intent is valid and {@code false} otherwise.
+     */
+    public static boolean isIntentValid(@NonNull Intent intent) {
+        int phoneId = intent.getIntExtra(SlicePurchaseController.EXTRA_PHONE_ID,
+                SubscriptionManager.INVALID_PHONE_INDEX);
+        if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) {
+            loge("isIntentValid: invalid phone index: " + phoneId);
+            return false;
+        }
+
+        int subId = intent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            loge("isIntentValid: invalid subscription ID: " + subId);
+            return false;
+        }
+
+        int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
+        if (capability == SlicePurchaseController.PREMIUM_CAPABILITY_INVALID) {
+            loge("isIntentValid: invalid premium capability: " + capability);
+            return false;
+        }
+
+        String appName = intent.getStringExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME);
+        if (TextUtils.isEmpty(appName)) {
+            loge("isIntentValid: empty requesting application name: " + appName);
+            return false;
+        }
+
+        return isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_CANCELED)
+                && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR)
+                && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED)
+                && isPendingIntentValid(intent,
+                        SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION)
+                && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_SUCCESS);
+    }
+
+    private static boolean isPendingIntentValid(@NonNull Intent intent, @NonNull String extra) {
+        String intentType = getPendingIntentType(extra);
+        PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
+        if (pendingIntent == null) {
+            loge("isPendingIntentValid: " + intentType + " intent not found.");
+            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) {
+        switch (extra) {
+            case SlicePurchaseController.EXTRA_INTENT_CANCELED: return "canceled";
+            case SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR: return "carrier error";
+            case SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
+            case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION:
+                return "not default data subscription";
+            case SlicePurchaseController.EXTRA_INTENT_SUCCESS: return "success";
+            default: {
+                loge("Unknown pending intent extra: " + extra);
+                return "unknown(" + extra + ")";
+            }
+        }
+    }
+
+    @Override
+    public void onReceive(@NonNull Context context, @NonNull Intent intent) {
+        logd("onReceive intent: " + intent.getAction());
+        switch (intent.getAction()) {
+            case SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP:
+                onDisplayBoosterNotification(context, intent);
+                break;
+            case SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT:
+                onTimeout(context, intent);
+                break;
+            case ACTION_NOTIFICATION_CANCELED:
+                onUserCanceled(context, intent);
+                break;
+            default:
+                loge("Received unknown action: " + intent.getAction());
+        }
+    }
+
+    private void onDisplayBoosterNotification(@NonNull Context context, @NonNull Intent intent) {
+        if (!isIntentValid(intent)) {
+            sendSlicePurchaseAppResponse(intent,
+                    SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
+            return;
+        }
+
+        NotificationChannel channel = new NotificationChannel(
+                NETWORK_BOOST_NOTIFICATION_CHANNEL_ID,
+                context.getResources().getString(R.string.network_boost_notification_channel),
+                NotificationManager.IMPORTANCE_DEFAULT);
+        // CarrierDefaultApp notifications are unblockable by default. Make this channel blockable
+        //  to allow users to disable notifications posted to this channel without affecting other
+        //  notifications in this application.
+        channel.setBlockable(true);
+        context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+
+        Notification notification =
+                new Notification.Builder(context, NETWORK_BOOST_NOTIFICATION_CHANNEL_ID)
+                        .setContentTitle(String.format(context.getResources().getString(
+                                R.string.network_boost_notification_title),
+                                intent.getStringExtra(
+                                        SlicePurchaseController.EXTRA_REQUESTING_APP_NAME)))
+                        .setContentText(context.getResources().getString(
+                                R.string.network_boost_notification_detail))
+                        .setSmallIcon(R.drawable.ic_network_boost)
+                        .setContentIntent(createContentIntent(context, intent, 1))
+                        .setDeleteIntent(intent.getParcelableExtra(
+                                SlicePurchaseController.EXTRA_INTENT_CANCELED, PendingIntent.class))
+                        // Add an action for the "Not now" button, which has the same behavior as
+                        // the user canceling or closing the notification.
+                        .addAction(new Notification.Action.Builder(
+                                Icon.createWithResource(context, R.drawable.ic_network_boost),
+                                context.getResources().getString(
+                                        R.string.network_boost_notification_button_not_now),
+                                createCanceledIntent(context, intent)).build())
+                        // Add an action for the "Manage" button, which has the same behavior as
+                        // the user clicking on the notification.
+                        .addAction(new Notification.Action.Builder(
+                                Icon.createWithResource(context, R.drawable.ic_network_boost),
+                                context.getResources().getString(
+                                        R.string.network_boost_notification_button_manage),
+                                createContentIntent(context, intent, 2)).build())
+                        .build();
+
+        int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
+        logd("Display the booster notification for capability "
+                + TelephonyManager.convertPremiumCapabilityToString(capability));
+        context.getSystemService(NotificationManager.class).notifyAsUser(
+                NETWORK_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL);
+    }
+
+    /**
+     * Create the intent for when the user clicks on the "Manage" button on the network boost
+     * notification or the notification itself. This will open {@link SlicePurchaseActivity}.
+     *
+     * @param context The Context to create the intent for.
+     * @param intent The source Intent used to launch the slice purchase application.
+     * @param requestCode The request code for the PendingIntent.
+     *
+     * @return The intent to start {@link SlicePurchaseActivity}.
+     */
+    @VisibleForTesting
+    @NonNull public PendingIntent createContentIntent(@NonNull Context context,
+            @NonNull Intent intent, int requestCode) {
+        Intent i = new Intent(context, SlicePurchaseActivity.class);
+        i.setComponent(ComponentName.unflattenFromString(
+                "com.android.carrierdefaultapp/.SlicePurchaseActivity"));
+        i.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
+                | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        i.putExtras(intent);
+        return PendingIntent.getActivityAsUser(context, requestCode, i,
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE, null /* options */,
+                UserHandle.CURRENT);
+    }
+
+    /**
+     * Create the canceled intent for when the user clicks the "Not now" button on the network boost
+     * notification. This will send {@link #ACTION_NOTIFICATION_CANCELED} and has the same function
+     * as if the user had canceled or removed the notification.
+     *
+     * @param context The Context to create the intent for.
+     * @param intent The source Intent used to launch the slice purchase application.
+     *
+     * @return The canceled intent.
+     */
+    @VisibleForTesting
+    @NonNull public PendingIntent createCanceledIntent(@NonNull Context context,
+            @NonNull Intent intent) {
+        Intent i = new Intent(ACTION_NOTIFICATION_CANCELED);
+        i.setComponent(ComponentName.unflattenFromString(
+                "com.android.carrierdefaultapp/.SlicePurchaseBroadcastReceiver"));
+        i.putExtras(intent);
+        return PendingIntent.getBroadcast(context, 0, i,
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
+    }
+
+    private void onTimeout(@NonNull Context context, @NonNull Intent intent) {
+        int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
+        logd("Purchase capability " + TelephonyManager.convertPremiumCapabilityToString(capability)
+                + " timed out.");
+        if (sSlicePurchaseActivities.get(capability) == null) {
+            // Notification is still active
+            logd("Closing booster notification since the user did not respond in time.");
+            context.getSystemService(NotificationManager.class).cancelAsUser(
+                    NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
+        } else {
+            // Notification was dismissed but SlicePurchaseActivity is still active
+            logd("Closing slice purchase application WebView since the user did not complete the "
+                    + "purchase in time.");
+            sSlicePurchaseActivities.get(capability).get().finishAndRemoveTask();
+        }
+    }
+
+    private void onUserCanceled(@NonNull Context context, @NonNull Intent intent) {
+        int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
+        logd("onUserCanceled: " + TelephonyManager.convertPremiumCapabilityToString(capability));
+        context.getSystemService(NotificationManager.class)
+                .cancelAsUser(NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
+        sendSlicePurchaseAppResponse(intent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
+    }
+
+    private static void logd(String s) {
+        Log.d(TAG, s);
+    }
+
+    private static void loge(String s) {
+        Log.e(TAG, s);
+    }
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java
new file mode 100644
index 0000000..8547898
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java
@@ -0,0 +1,93 @@
+/*
+ * 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.slice.SlicePurchaseController;
+
+/**
+ * Slice purchase web interface class allowing carrier websites to send responses back to the
+ * slice purchase application using JavaScript.
+ */
+public class SlicePurchaseWebInterface {
+    @NonNull SlicePurchaseActivity mActivity;
+
+    public SlicePurchaseWebInterface(@NonNull SlicePurchaseActivity 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) {
+     *         SlicePurchaseWebInterface.getRequestedCapability();
+     *     }
+     * </script>
+     */
+    @JavascriptInterface
+    @TelephonyManager.PremiumCapability public int getRequestedCapability() {
+        return mActivity.mCapability;
+    }
+
+    /**
+     * Interface method allowing the carrier website to notify the slice purchase application 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) {
+     *         SlicePurchaseWebInterface.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 slice purchase application of
+     * a failed premium capability purchase.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function notifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") {
+     *         SlicePurchaseWebInterface.notifyPurchaseFailed();
+     *     }
+     * </script>
+     *
+     * @param failureCode The failure code.
+     * @param failureReason If the failure code is
+     *                      {@link SlicePurchaseController#FAILURE_CODE_UNKNOWN},
+     *                      the human-readable reason for failure.
+     */
+    @JavascriptInterface
+    public void notifyPurchaseFailed(@SlicePurchaseController.FailureCode int failureCode,
+            @Nullable String failureReason) {
+        mActivity.onPurchaseFailed(failureCode, failureReason);
+    }
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
deleted file mode 100644
index 602e31c..0000000
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
+++ /dev/null
@@ -1,122 +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.carrierdefaultapp;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.NotificationManager;
-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.webkit.WebView;
-
-import com.android.phone.slicestore.SliceStore;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
-/**
- * Activity that launches when the user clicks on the network boost notification.
- */
-public class SliceStoreActivity extends Activity {
-    private static final String TAG = "SliceStoreActivity";
-
-    private URL mUrl;
-    private WebView mWebView;
-    private int mPhoneId;
-    private int mSubId;
-    private @TelephonyManager.PremiumCapability 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="
-                + TelephonyManager.convertPremiumCapabilityToString(mCapability)
-                + ", mUrl=" + mUrl);
-        getApplicationContext().getSystemService(NotificationManager.class)
-                .cancel(SliceStoreBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
-        if (!SliceStoreBroadcastReceiver.isIntentValid(intent)) {
-            loge("Not starting SliceStoreActivity with an invalid Intent: " + intent);
-            SliceStoreBroadcastReceiver.sendSliceStoreResponse(
-                    intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED);
-            finishAndRemoveTask();
-            return;
-        }
-        if (mUrl == null) {
-            loge("Unable to create a URL from carrier configs.");
-            SliceStoreBroadcastReceiver.sendSliceStoreResponse(
-                    intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR);
-            finishAndRemoveTask();
-            return;
-        }
-        if (mSubId != SubscriptionManager.getDefaultSubscriptionId()) {
-            loge("Unable to start SliceStore on the non-default data subscription: " + mSubId);
-            SliceStoreBroadcastReceiver.sendSliceStoreResponse(
-                    intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA);
-            finishAndRemoveTask();
-            return;
-        }
-
-        SliceStoreBroadcastReceiver.updateSliceStoreActivity(mCapability, this);
-
-        mWebView = new WebView(this);
-        setContentView(mWebView);
-        mWebView.loadUrl(mUrl.toString());
-        // TODO(b/245882601): Get back response from WebView
-    }
-
-    @Override
-    protected void onDestroy() {
-        logd("onDestroy: User canceled the purchase by closing the application.");
-        SliceStoreBroadcastReceiver.sendSliceStoreResponse(
-                getIntent(), SliceStore.EXTRA_INTENT_CANCELED);
-        SliceStoreBroadcastReceiver.removeSliceStoreActivity(mCapability);
-        super.onDestroy();
-    }
-
-    private @Nullable URL getUrl() {
-        String url = getApplicationContext().getSystemService(CarrierConfigManager.class)
-                .getConfigForSubId(mSubId).getString(
-                        CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
-        try {
-            return new URL(url);
-        } catch (MalformedURLException e) {
-            loge("Invalid URL: " + url);
-        }
-        return null;
-    }
-
-    private static void logd(@NonNull String s) {
-        Log.d(TAG, s);
-    }
-
-    private static void loge(@NonNull String s) {
-        Log.e(TAG, s);
-    }
-}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
deleted file mode 100644
index 7eb851d..0000000
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
+++ /dev/null
@@ -1,315 +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.carrierdefaultapp;
-
-import android.annotation.NonNull;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.Icon;
-import android.os.UserHandle;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.Log;
-import android.webkit.WebView;
-
-import com.android.phone.slicestore.SliceStore;
-
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * The SliceStoreBroadcastReceiver listens for {@link SliceStore#ACTION_START_SLICE_STORE} from the
- * SliceStore in the phone process to start the SliceStore application. It displays the network
- * boost notification to the user and will start the {@link SliceStoreActivity} to display the
- * {@link WebView} to purchase network boosts from the user's carrier.
- */
-public class SliceStoreBroadcastReceiver extends BroadcastReceiver{
-    private static final String TAG = "SliceStoreBroadcastReceiver";
-
-    /** Weak references to {@link SliceStoreActivity} for each capability, if it exists. */
-    private static final Map<Integer, WeakReference<SliceStoreActivity>> sSliceStoreActivities =
-            new HashMap<>();
-
-    /** Channel ID for the network boost notification. */
-    private static final String NETWORK_BOOST_NOTIFICATION_CHANNEL_ID = "network_boost";
-    /** Tag for the network boost notification. */
-    public static final String NETWORK_BOOST_NOTIFICATION_TAG = "SliceStore.Notification";
-    /** Action for when the user clicks the "Not now" button on the network boost notification. */
-    private static final String ACTION_NOTIFICATION_CANCELED =
-            "com.android.phone.slicestore.action.NOTIFICATION_CANCELED";
-
-    /**
-     * Create a weak reference to {@link SliceStoreActivity}. The reference will be removed when
-     * {@link SliceStoreActivity#onDestroy()} is called.
-     *
-     * @param capability The premium capability requested.
-     * @param sliceStoreActivity The instance of SliceStoreActivity.
-     */
-    public static void updateSliceStoreActivity(@TelephonyManager.PremiumCapability int capability,
-            @NonNull SliceStoreActivity sliceStoreActivity) {
-        sSliceStoreActivities.put(capability, new WeakReference<>(sliceStoreActivity));
-    }
-
-    /**
-     * Remove the weak reference to {@link SliceStoreActivity} when
-     * {@link SliceStoreActivity#onDestroy()} is called.
-     *
-     * @param capability The premium capability requested.
-     */
-    public static void removeSliceStoreActivity(
-            @TelephonyManager.PremiumCapability int capability) {
-        sSliceStoreActivities.remove(capability);
-    }
-
-    /**
-     * Send the PendingIntent containing the corresponding SliceStore response.
-     *
-     * @param intent The Intent containing the PendingIntent extra.
-     * @param extra The extra to get the PendingIntent to send.
-     */
-    public static void sendSliceStoreResponse(@NonNull Intent intent, @NonNull String extra) {
-        PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
-        if (pendingIntent == null) {
-            loge("PendingIntent does not exist for extra: " + extra);
-            return;
-        }
-        try {
-            pendingIntent.send();
-        } 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.
-     *
-     * @param intent The intent to check
-     * @return {@code true} if the intent is valid and {@code false} otherwise.
-     */
-    public static boolean isIntentValid(@NonNull Intent intent) {
-        int phoneId = intent.getIntExtra(SliceStore.EXTRA_PHONE_ID,
-                SubscriptionManager.INVALID_PHONE_INDEX);
-        if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) {
-            loge("isIntentValid: invalid phone index: " + phoneId);
-            return false;
-        }
-
-        int subId = intent.getIntExtra(SliceStore.EXTRA_SUB_ID,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            loge("isIntentValid: invalid subscription ID: " + subId);
-            return false;
-        }
-
-        int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
-                SliceStore.PREMIUM_CAPABILITY_INVALID);
-        if (capability == SliceStore.PREMIUM_CAPABILITY_INVALID) {
-            loge("isIntentValid: invalid premium capability: " + capability);
-            return false;
-        }
-
-        String appName = intent.getStringExtra(SliceStore.EXTRA_REQUESTING_APP_NAME);
-        if (TextUtils.isEmpty(appName)) {
-            loge("isIntentValid: empty requesting application name: " + appName);
-            return false;
-        }
-
-        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);
-    }
-
-    private static boolean isPendingIntentValid(@NonNull Intent intent, @NonNull String extra) {
-        String intentType = getPendingIntentType(extra);
-        PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
-        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;
-    }
-
-    @NonNull private static String getPendingIntentType(@NonNull String extra) {
-        switch (extra) {
-            case SliceStore.EXTRA_INTENT_CANCELED: return "canceled";
-            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";
-            default: {
-                loge("Unknown pending intent extra: " + extra);
-                return "unknown(" + extra + ")";
-            }
-        }
-    }
-
-    @Override
-    public void onReceive(@NonNull Context context, @NonNull Intent intent) {
-        logd("onReceive intent: " + intent.getAction());
-        switch (intent.getAction()) {
-            case SliceStore.ACTION_START_SLICE_STORE:
-                onDisplayBoosterNotification(context, intent);
-                break;
-            case SliceStore.ACTION_SLICE_STORE_RESPONSE_TIMEOUT:
-                onTimeout(context, intent);
-                break;
-            case ACTION_NOTIFICATION_CANCELED:
-                onUserCanceled(context, intent);
-                break;
-            default:
-                loge("Received unknown action: " + intent.getAction());
-        }
-    }
-
-    private void onDisplayBoosterNotification(@NonNull Context context, @NonNull Intent intent) {
-        if (!isIntentValid(intent)) {
-            sendSliceStoreResponse(intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED);
-            return;
-        }
-
-        context.getSystemService(NotificationManager.class).createNotificationChannel(
-                new NotificationChannel(NETWORK_BOOST_NOTIFICATION_CHANNEL_ID,
-                        context.getResources().getString(
-                                R.string.network_boost_notification_channel),
-                        NotificationManager.IMPORTANCE_DEFAULT));
-
-        Notification notification =
-                new Notification.Builder(context, NETWORK_BOOST_NOTIFICATION_CHANNEL_ID)
-                        .setContentTitle(String.format(context.getResources().getString(
-                                R.string.network_boost_notification_title),
-                                intent.getStringExtra(SliceStore.EXTRA_REQUESTING_APP_NAME)))
-                        .setContentText(context.getResources().getString(
-                                R.string.network_boost_notification_detail))
-                        .setSmallIcon(R.drawable.ic_network_boost)
-                        .setContentIntent(createContentIntent(context, intent, 1))
-                        .setDeleteIntent(intent.getParcelableExtra(
-                                SliceStore.EXTRA_INTENT_CANCELED, PendingIntent.class))
-                        // Add an action for the "Not now" button, which has the same behavior as
-                        // the user canceling or closing the notification.
-                        .addAction(new Notification.Action.Builder(
-                                Icon.createWithResource(context, R.drawable.ic_network_boost),
-                                context.getResources().getString(
-                                        R.string.network_boost_notification_button_not_now),
-                                createCanceledIntent(context, intent)).build())
-                        // Add an action for the "Manage" button, which has the same behavior as
-                        // the user clicking on the notification.
-                        .addAction(new Notification.Action.Builder(
-                                Icon.createWithResource(context, R.drawable.ic_network_boost),
-                                context.getResources().getString(
-                                        R.string.network_boost_notification_button_manage),
-                                createContentIntent(context, intent, 2)).build())
-                        .build();
-
-        int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
-                SliceStore.PREMIUM_CAPABILITY_INVALID);
-        logd("Display the booster notification for capability "
-                + TelephonyManager.convertPremiumCapabilityToString(capability));
-        context.getSystemService(NotificationManager.class).notifyAsUser(
-                NETWORK_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL);
-    }
-
-    /**
-     * Create the intent for when the user clicks on the "Manage" button on the network boost
-     * notification or the notification itself. This will open {@link SliceStoreActivity}.
-     *
-     * @param context The Context to create the intent for.
-     * @param intent The source Intent used to launch the SliceStore application.
-     * @param requestCode The request code for the PendingIntent.
-     *
-     * @return The intent to start {@link SliceStoreActivity}.
-     */
-    @NonNull private PendingIntent createContentIntent(@NonNull Context context,
-            @NonNull Intent intent, int requestCode) {
-        Intent i = new Intent(context, SliceStoreActivity.class);
-        i.setComponent(ComponentName.unflattenFromString(
-                "com.android.carrierdefaultapp/.SliceStoreActivity"));
-        i.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
-                | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        i.putExtras(intent);
-        return PendingIntent.getActivityAsUser(context, requestCode, i,
-                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE, null /* options */,
-                UserHandle.CURRENT);
-    }
-
-    /**
-     * Create the canceled intent for when the user clicks the "Not now" button on the network boost
-     * notification. This will send {@link #ACTION_NOTIFICATION_CANCELED} and has the same function
-     * as if the user had canceled or removed the notification.
-     *
-     * @param context The Context to create the intent for.
-     * @param intent The source Intent used to launch the SliceStore application.
-     *
-     * @return The canceled intent.
-     */
-    @NonNull private PendingIntent createCanceledIntent(@NonNull Context context,
-            @NonNull Intent intent) {
-        Intent i = new Intent(ACTION_NOTIFICATION_CANCELED);
-        i.setComponent(ComponentName.unflattenFromString(
-                "com.android.carrierdefaultapp/.SliceStoreBroadcastReceiver"));
-        i.putExtras(intent);
-        return PendingIntent.getBroadcast(context, 0, i,
-                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
-    }
-
-    private void onTimeout(@NonNull Context context, @NonNull Intent intent) {
-        int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
-                SliceStore.PREMIUM_CAPABILITY_INVALID);
-        logd("Purchase capability " + TelephonyManager.convertPremiumCapabilityToString(capability)
-                + " timed out.");
-        if (sSliceStoreActivities.get(capability) == null) {
-            // Notification is still active
-            logd("Closing booster notification since the user did not respond in time.");
-            context.getSystemService(NotificationManager.class).cancelAsUser(
-                    NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
-        } else {
-            // Notification was dismissed but SliceStoreActivity is still active
-            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?
-        }
-    }
-
-    private void onUserCanceled(@NonNull Context context, @NonNull Intent intent) {
-        int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
-                SliceStore.PREMIUM_CAPABILITY_INVALID);
-        logd("onUserCanceled: " + TelephonyManager.convertPremiumCapabilityToString(capability));
-        context.getSystemService(NotificationManager.class)
-                .cancelAsUser(NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
-        sendSliceStoreResponse(intent, SliceStore.EXTRA_INTENT_CANCELED);
-    }
-
-    private static void logd(String s) {
-        Log.d(TAG, s);
-    }
-
-    private static void loge(String s) {
-        Log.e(TAG, s);
-    }
-}
diff --git a/packages/CarrierDefaultApp/tests/unit/Android.bp b/packages/CarrierDefaultApp/tests/unit/Android.bp
index 54c9016..cdf7957 100644
--- a/packages/CarrierDefaultApp/tests/unit/Android.bp
+++ b/packages/CarrierDefaultApp/tests/unit/Android.bp
@@ -27,11 +27,13 @@
     libs: [
         "android.test.runner",
         "android.test.base",
+        "SlicePurchaseController",
     ],
     static_libs: [
         "androidx.test.rules",
-        "mockito-target-minus-junit4",
+        "mockito-target-inline-minus-junit4",
     ],
+    jni_libs: ["libdexmakerjvmtiagent"],
     // Include all test java files.
     srcs: ["src/**/*.java"],
     platform_apis: true,
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
new file mode 100644
index 0000000..cecc86d
--- /dev/null
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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 static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.test.ActivityUnitTestCase;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.phone.slice.SlicePurchaseController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchaseActivity> {
+    private static final String TAG = "SlicePurchaseActivityTest";
+    private static final String URL = "file:///android_asset/slice_purchase_test.html";
+    private static final int PHONE_ID = 0;
+
+    @Mock PendingIntent mPendingIntent;
+    @Mock PendingIntent mCanceledIntent;
+    @Mock CarrierConfigManager mCarrierConfigManager;
+    @Mock NotificationManager mNotificationManager;
+    @Mock PersistableBundle mPersistableBundle;
+
+    private SlicePurchaseActivity mSlicePurchaseActivity;
+    private Context mContext;
+
+    public SlicePurchaseActivityTest() {
+        super(SlicePurchaseActivity.class);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        injectInstrumentation(InstrumentationRegistry.getInstrumentation());
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+
+        // setup context
+        mContext = spy(getInstrumentation().getTargetContext());
+        doReturn(mCarrierConfigManager).when(mContext)
+                .getSystemService(eq(CarrierConfigManager.class));
+        doReturn(URL).when(mPersistableBundle).getString(
+                CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
+        doReturn(mPersistableBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+        doReturn(mNotificationManager).when(mContext)
+                .getSystemService(eq(NotificationManager.class));
+        doReturn(mContext).when(mContext).getApplicationContext();
+        setActivityContext(mContext);
+
+        // set up intent
+        Intent intent = new Intent();
+        intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
+        intent.putExtra(SlicePurchaseController.EXTRA_SUB_ID,
+                SubscriptionManager.getDefaultDataSubscriptionId());
+        intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+        intent.putExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME, TAG);
+        Intent spiedIntent = spy(intent);
+
+        // set up pending intents
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
+        doReturn(true).when(mPendingIntent).isBroadcast();
+        doReturn(mPendingIntent).when(spiedIntent).getParcelableExtra(
+                anyString(), eq(PendingIntent.class));
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mCanceledIntent).getCreatorPackage();
+        doReturn(true).when(mCanceledIntent).isBroadcast();
+        doReturn(mCanceledIntent).when(spiedIntent).getParcelableExtra(
+                eq(SlicePurchaseController.EXTRA_INTENT_CANCELED), eq(PendingIntent.class));
+
+        mSlicePurchaseActivity = startActivity(spiedIntent, null, null);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSlicePurchaseActivity.onDestroy();
+        super.tearDown();
+    }
+
+    @Test
+    public void testOnPurchaseSuccessful() throws Exception {
+        int duration = 5 * 60 * 1000; // 5 minutes
+        int invalidDuration = -1;
+        mSlicePurchaseActivity.onPurchaseSuccessful(duration);
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture());
+        Intent intent = intentCaptor.getValue();
+        assertEquals(duration, intent.getLongExtra(
+                SlicePurchaseController.EXTRA_PURCHASE_DURATION, invalidDuration));
+    }
+
+    @Test
+    public void testOnPurchaseFailed() throws Exception {
+        int failureCode = SlicePurchaseController.FAILURE_CODE_SERVER_UNREACHABLE;
+        String failureReason = "Server unreachable";
+        mSlicePurchaseActivity.onPurchaseFailed(failureCode, failureReason);
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture());
+        Intent intent = intentCaptor.getValue();
+        assertEquals(failureCode, intent.getIntExtra(
+                SlicePurchaseController.EXTRA_FAILURE_CODE, failureCode));
+        assertEquals(failureReason, intent.getStringExtra(
+                SlicePurchaseController.EXTRA_FAILURE_REASON));
+    }
+
+    @Test
+    public void testOnUserCanceled() throws Exception {
+        mSlicePurchaseActivity.onDestroy();
+        verify(mCanceledIntent).send();
+    }
+}
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
new file mode 100644
index 0000000..5765e5b
--- /dev/null
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
@@ -0,0 +1,250 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.DisplayMetrics;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.phone.slice.SlicePurchaseController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class SlicePurchaseBroadcastReceiverTest {
+    private static final int PHONE_ID = 0;
+    private static final String TAG = "SlicePurchaseBroadcastReceiverTest";
+    private static final String EXTRA = "EXTRA";
+
+    @Mock Intent mIntent;
+    @Mock Intent mDataIntent;
+    @Mock PendingIntent mPendingIntent;
+    @Mock PendingIntent mCanceledIntent;
+    @Mock PendingIntent mContentIntent1;
+    @Mock PendingIntent mContentIntent2;
+    @Mock Context mContext;
+    @Mock Resources mResources;
+    @Mock NotificationManager mNotificationManager;
+    @Mock ApplicationInfo mApplicationInfo;
+    @Mock PackageManager mPackageManager;
+    @Mock DisplayMetrics mDisplayMetrics;
+    @Mock SlicePurchaseActivity mSlicePurchaseActivity;
+
+    private SlicePurchaseBroadcastReceiver mSlicePurchaseBroadcastReceiver;
+    private ArgumentCaptor<Intent> mIntentCaptor;
+    private ArgumentCaptor<Notification> mNotificationCaptor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doReturn(mNotificationManager).when(mContext)
+                .getSystemService(eq(NotificationManager.class));
+
+        mIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        mNotificationCaptor = ArgumentCaptor.forClass(Notification.class);
+        mSlicePurchaseBroadcastReceiver = spy(new SlicePurchaseBroadcastReceiver());
+    }
+
+    @Test
+    public void testSendSlicePurchaseAppResponse() throws Exception {
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(mIntent, EXTRA);
+        verify(mPendingIntent, never()).send();
+
+        doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+                eq(EXTRA), eq(PendingIntent.class));
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(mIntent, EXTRA);
+        verify(mPendingIntent).send();
+    }
+
+    @Test
+    public void testSendSlicePurchaseAppResponseWithData() throws Exception {
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
+                mContext, mIntent, EXTRA, mDataIntent);
+        verify(mPendingIntent, never()).send(eq(mContext), eq(0), any(Intent.class));
+
+        doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+                eq(EXTRA), eq(PendingIntent.class));
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
+                mContext, mIntent, EXTRA, mDataIntent);
+        verify(mPendingIntent).send(eq(mContext), eq(0), mIntentCaptor.capture());
+        assertEquals(mDataIntent, mIntentCaptor.getValue());
+    }
+
+    @Test
+    public void testIsIntentValid() {
+        assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
+
+        // set up intent
+        doReturn(PHONE_ID).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
+        doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
+        doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+        doReturn(TAG).when(mIntent).getStringExtra(
+                eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
+        assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
+
+        // set up pending intent
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
+        doReturn(true).when(mPendingIntent).isBroadcast();
+        doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+                anyString(), eq(PendingIntent.class));
+        assertTrue(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
+    }
+
+    @Test
+    public void testDisplayBoosterNotification() {
+        // set up intent
+        doReturn(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP).when(mIntent).getAction();
+        doReturn(PHONE_ID).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
+        doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
+        doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+        doReturn(TAG).when(mIntent).getStringExtra(
+                eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
+
+        // set up pending intent
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
+        doReturn(true).when(mPendingIntent).isBroadcast();
+        doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+                anyString(), eq(PendingIntent.class));
+
+        // set up notification
+        doReturn(mResources).when(mContext).getResources();
+        doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics();
+        doReturn("").when(mResources).getString(anyInt());
+        doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+
+        // set up intents created by broadcast receiver
+        doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
+                eq(mContext), eq(mIntent), eq(1));
+        doReturn(mContentIntent2).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
+                eq(mContext), eq(mIntent), eq(2));
+        doReturn(mCanceledIntent).when(mSlicePurchaseBroadcastReceiver).createCanceledIntent(
+                eq(mContext), eq(mIntent));
+
+        // send ACTION_START_SLICE_PURCHASE_APP
+        mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+        // verify network boost notification was shown
+        verify(mNotificationManager).notifyAsUser(
+                eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+                eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+                mNotificationCaptor.capture(),
+                eq(UserHandle.ALL));
+
+        Notification notification = mNotificationCaptor.getValue();
+        assertEquals(mContentIntent1, notification.contentIntent);
+        assertEquals(mPendingIntent, notification.deleteIntent);
+        assertEquals(2, notification.actions.length);
+        assertEquals(mCanceledIntent, notification.actions[0].actionIntent);
+        assertEquals(mContentIntent2, notification.actions[1].actionIntent);
+    }
+
+
+    @Test
+    public void testNotificationCanceled() {
+        // set up intent
+        doReturn("com.android.phone.slice.action.NOTIFICATION_CANCELED").when(mIntent).getAction();
+        doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+
+        // send ACTION_NOTIFICATION_CANCELED
+        mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+        // verify notification was canceled
+        verify(mNotificationManager).cancelAsUser(
+                eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+                eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+                eq(UserHandle.ALL));
+    }
+
+    @Test
+    public void testNotificationTimeout() {
+        // set up intent
+        doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
+                .getAction();
+        doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+
+        // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
+        mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+        // verify notification was canceled
+        verify(mNotificationManager).cancelAsUser(
+                eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+                eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+                eq(UserHandle.ALL));
+    }
+
+    @Test
+    // TODO: WebView/Activity should not close on timeout.
+    //  This test should be removed once implementation is fixed.
+    public void testActivityTimeout() {
+        // create and track activity
+        SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, mSlicePurchaseActivity);
+
+        // set up intent
+        doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
+                .getAction();
+        doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+                eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+
+        // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
+        mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+        // verify activity was canceled
+        verify(mSlicePurchaseActivity).finishAndRemoveTask();
+
+        // untrack activity
+        SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+    }
+}
diff --git a/packages/CompanionDeviceManager/Android.bp b/packages/CompanionDeviceManager/Android.bp
index 9f5bfd4..f6458c2 100644
--- a/packages/CompanionDeviceManager/Android.bp
+++ b/packages/CompanionDeviceManager/Android.bp
@@ -41,6 +41,7 @@
         "androidx.lifecycle_lifecycle-livedata",
         "androidx.lifecycle_lifecycle-extensions",
         "androidx.recyclerview_recyclerview",
+        "androidx-constraintlayout_constraintlayout",
         "androidx.appcompat_appcompat",
     ],
 
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_apps.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_app_streaming.xml
similarity index 100%
rename from packages/CompanionDeviceManager/res/drawable-night/ic_apps.xml
rename to packages/CompanionDeviceManager/res/drawable-night/ic_permission_app_streaming.xml
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_calendar.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_calendar.xml
new file mode 100644
index 0000000..d7ea3a2
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_calendar.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_200">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M5,22Q4.175,22 3.587,21.413Q3,20.825 3,20V6Q3,5.175 3.587,4.588Q4.175,4 5,4H6V2H8V4H16V2H18V4H19Q19.825,4 20.413,4.588Q21,5.175 21,6V20Q21,20.825 20.413,21.413Q19.825,22 19,22ZM5,20H19Q19,20 19,20Q19,20 19,20V10H5V20Q5,20 5,20Q5,20 5,20Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_contacts.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_contacts.xml
new file mode 100644
index 0000000..41e4044
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_contacts.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_200">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M4,23V21H20V23ZM4,3V1H20V3ZM12,13Q13.25,13 14.125,12.125Q15,11.25 15,10Q15,8.75 14.125,7.875Q13.25,7 12,7Q10.75,7 9.875,7.875Q9,8.75 9,10Q9,11.25 9.875,12.125Q10.75,13 12,13ZM4,20Q3.175,20 2.588,19.413Q2,18.825 2,18V6Q2,5.175 2.588,4.588Q3.175,4 4,4H20Q20.825,4 21.413,4.588Q22,5.175 22,6V18Q22,18.825 21.413,19.413Q20.825,20 20,20ZM5.75,18Q6.875,16.6 8.475,15.8Q10.075,15 12,15Q13.925,15 15.525,15.8Q17.125,16.6 18.25,18H20Q20,18 20,18Q20,18 20,18V6Q20,6 20,6Q20,6 20,6H4Q4,6 4,6Q4,6 4,6V18Q4,18 4,18Q4,18 4,18ZM8.7,18H15.3Q14.575,17.5 13.738,17.25Q12.9,17 12,17Q11.1,17 10.263,17.25Q9.425,17.5 8.7,18ZM12,11Q11.575,11 11.288,10.712Q11,10.425 11,10Q11,9.575 11.288,9.287Q11.575,9 12,9Q12.425,9 12.713,9.287Q13,9.575 13,10Q13,10.425 12.713,10.712Q12.425,11 12,11ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_devices.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_devices.xml
new file mode 100644
index 0000000..1611861
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_devices.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path android:fillColor="@android:color/system_accent1_200"
+          android:pathData="M12,16.4 L7.6,12 12,7.6 16.4,12ZM13.4,21.375Q13.125,21.65 12.75,21.8Q12.375,21.95 12,21.95Q11.625,21.95 11.25,21.8Q10.875,21.65 10.6,21.375L2.625,13.4Q2.35,13.125 2.2,12.75Q2.05,12.375 2.05,12Q2.05,11.625 2.2,11.25Q2.35,10.875 2.625,10.6L10.575,2.65Q10.875,2.35 11.238,2.2Q11.6,2.05 12,2.05Q12.4,2.05 12.762,2.2Q13.125,2.35 13.425,2.65L21.375,10.6Q21.65,10.875 21.8,11.25Q21.95,11.625 21.95,12Q21.95,12.375 21.8,12.75Q21.65,13.125 21.375,13.4ZM12,19.2 L19.2,12Q19.2,12 19.2,12Q19.2,12 19.2,12L12,4.8Q12,4.8 12,4.8Q12,4.8 12,4.8L4.8,12Q4.8,12 4.8,12Q4.8,12 4.8,12L12,19.2Q12,19.2 12,19.2Q12,19.2 12,19.2Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_notifications.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_notifications.xml
similarity index 100%
rename from packages/CompanionDeviceManager/res/drawable-night/ic_notifications.xml
rename to packages/CompanionDeviceManager/res/drawable-night/ic_permission_notifications.xml
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_phone.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_phone.xml
new file mode 100644
index 0000000..49467ed
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_phone.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_200">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M19.95,21Q16.725,21 13.663,19.562Q10.6,18.125 8.238,15.762Q5.875,13.4 4.438,10.337Q3,7.275 3,4.05Q3,3.6 3.3,3.3Q3.6,3 4.05,3H8.1Q8.45,3 8.725,3.225Q9,3.45 9.05,3.8L9.7,7.3Q9.75,7.65 9.688,7.937Q9.625,8.225 9.4,8.45L7,10.9Q8.05,12.7 9.625,14.275Q11.2,15.85 13.1,17L15.45,14.65Q15.675,14.425 16.038,14.312Q16.4,14.2 16.75,14.25L20.2,14.95Q20.55,15.025 20.775,15.287Q21,15.55 21,15.9V19.95Q21,20.4 20.7,20.7Q20.4,21 19.95,21Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_sms.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_sms.xml
new file mode 100644
index 0000000..859c06f
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_sms.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_200">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M8,11Q8.425,11 8.713,10.712Q9,10.425 9,10Q9,9.575 8.713,9.287Q8.425,9 8,9Q7.575,9 7.287,9.287Q7,9.575 7,10Q7,10.425 7.287,10.712Q7.575,11 8,11ZM12,11Q12.425,11 12.713,10.712Q13,10.425 13,10Q13,9.575 12.713,9.287Q12.425,9 12,9Q11.575,9 11.288,9.287Q11,9.575 11,10Q11,10.425 11.288,10.712Q11.575,11 12,11ZM16,11Q16.425,11 16.712,10.712Q17,10.425 17,10Q17,9.575 16.712,9.287Q16.425,9 16,9Q15.575,9 15.288,9.287Q15,9.575 15,10Q15,10.425 15.288,10.712Q15.575,11 16,11ZM2,22V4Q2,3.175 2.588,2.587Q3.175,2 4,2H20Q20.825,2 21.413,2.587Q22,3.175 22,4V16Q22,16.825 21.413,17.413Q20.825,18 20,18H6ZM4,16H20Q20,16 20,16Q20,16 20,16V4Q20,4 20,4Q20,4 20,4H4Q4,4 4,4Q4,4 4,4V16ZM4,16V4Q4,4 4,4Q4,4 4,4Q4,4 4,4Q4,4 4,4V16Q4,16 4,16Q4,16 4,16Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_storage.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_storage.xml
similarity index 100%
rename from packages/CompanionDeviceManager/res/drawable-night/ic_storage.xml
rename to packages/CompanionDeviceManager/res/drawable-night/ic_permission_storage.xml
diff --git a/packages/CompanionDeviceManager/res/drawable/btn_expand_less.xml b/packages/CompanionDeviceManager/res/drawable/btn_expand_less.xml
new file mode 100644
index 0000000..99db560
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/btn_expand_less.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_neutral1_500">
+    <path android:fillColor="@android:color/white" android:pathData="M7.4,15.05 L6.35,13.975 12,8.325 17.65,13.975 16.6,15.05 12,10.45Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/btn_expand_more.xml b/packages/CompanionDeviceManager/res/drawable/btn_expand_more.xml
new file mode 100644
index 0000000..8518cfa
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/btn_expand_more.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_neutral1_500">
+    <path android:fillColor="@android:color/white" android:pathData="M12,15.05 L6.35,9.375 7.4,8.325 12,12.925 16.6,8.325 17.65,9.375Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml b/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml
index 2a8eb24..15f6987 100644
--- a/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml
+++ b/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml
@@ -15,7 +15,8 @@
   ~ limitations under the License.
   -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp" android:height="24dp"
+        android:width="24dp"
+        android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24"
         android:tint="?attr/colorControlNormal">
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_apps.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_app_streaming.xml
similarity index 100%
rename from packages/CompanionDeviceManager/res/drawable/ic_apps.xml
rename to packages/CompanionDeviceManager/res/drawable/ic_permission_app_streaming.xml
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_calendar.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_calendar.xml
new file mode 100644
index 0000000..3dc53e7
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_calendar.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_600">
+    <path android:fillColor="@android:color/white" android:pathData="M5,22Q4.175,22 3.587,21.413Q3,20.825 3,20V6Q3,5.175 3.587,4.588Q4.175,4 5,4H6V2H8V4H16V2H18V4H19Q19.825,4 20.413,4.588Q21,5.175 21,6V20Q21,20.825 20.413,21.413Q19.825,22 19,22ZM5,20H19Q19,20 19,20Q19,20 19,20V10H5V20Q5,20 5,20Q5,20 5,20Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_contacts.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_contacts.xml
new file mode 100644
index 0000000..2dfda8d
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_contacts.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_600">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M4,23V21H20V23ZM4,3V1H20V3ZM12,13Q13.25,13 14.125,12.125Q15,11.25 15,10Q15,8.75 14.125,7.875Q13.25,7 12,7Q10.75,7 9.875,7.875Q9,8.75 9,10Q9,11.25 9.875,12.125Q10.75,13 12,13ZM4,20Q3.175,20 2.588,19.413Q2,18.825 2,18V6Q2,5.175 2.588,4.588Q3.175,4 4,4H20Q20.825,4 21.413,4.588Q22,5.175 22,6V18Q22,18.825 21.413,19.413Q20.825,20 20,20ZM5.75,18Q6.875,16.6 8.475,15.8Q10.075,15 12,15Q13.925,15 15.525,15.8Q17.125,16.6 18.25,18H20Q20,18 20,18Q20,18 20,18V6Q20,6 20,6Q20,6 20,6H4Q4,6 4,6Q4,6 4,6V18Q4,18 4,18Q4,18 4,18ZM8.7,18H15.3Q14.575,17.5 13.738,17.25Q12.9,17 12,17Q11.1,17 10.263,17.25Q9.425,17.5 8.7,18ZM12,11Q11.575,11 11.288,10.712Q11,10.425 11,10Q11,9.575 11.288,9.287Q11.575,9 12,9Q12.425,9 12.713,9.287Q13,9.575 13,10Q13,10.425 12.713,10.712Q12.425,11 12,11ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_nearby_devices.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_nearby_devices.xml
new file mode 100644
index 0000000..49a6fe3
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_nearby_devices.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path android:fillColor="@android:color/system_accent1_600"
+          android:pathData="M12,16.4 L7.6,12 12,7.6 16.4,12ZM13.4,21.375Q13.125,21.65 12.75,21.8Q12.375,21.95 12,21.95Q11.625,21.95 11.25,21.8Q10.875,21.65 10.6,21.375L2.625,13.4Q2.35,13.125 2.2,12.75Q2.05,12.375 2.05,12Q2.05,11.625 2.2,11.25Q2.35,10.875 2.625,10.6L10.575,2.65Q10.875,2.35 11.238,2.2Q11.6,2.05 12,2.05Q12.4,2.05 12.762,2.2Q13.125,2.35 13.425,2.65L21.375,10.6Q21.65,10.875 21.8,11.25Q21.95,11.625 21.95,12Q21.95,12.375 21.8,12.75Q21.65,13.125 21.375,13.4ZM12,19.2 L19.2,12Q19.2,12 19.2,12Q19.2,12 19.2,12L12,4.8Q12,4.8 12,4.8Q12,4.8 12,4.8L4.8,12Q4.8,12 4.8,12Q4.8,12 4.8,12L12,19.2Q12,19.2 12,19.2Q12,19.2 12,19.2Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_notifications.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_notifications.xml
similarity index 100%
rename from packages/CompanionDeviceManager/res/drawable/ic_notifications.xml
rename to packages/CompanionDeviceManager/res/drawable/ic_permission_notifications.xml
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_phone.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_phone.xml
new file mode 100644
index 0000000..cc1c5b5
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_phone.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_600">
+    <path android:fillColor="@android:color/white" android:pathData="M19.95,21Q16.725,21 13.663,19.562Q10.6,18.125 8.238,15.762Q5.875,13.4 4.438,10.337Q3,7.275 3,4.05Q3,3.6 3.3,3.3Q3.6,3 4.05,3H8.1Q8.45,3 8.725,3.225Q9,3.45 9.05,3.8L9.7,7.3Q9.75,7.65 9.688,7.937Q9.625,8.225 9.4,8.45L7,10.9Q8.05,12.7 9.625,14.275Q11.2,15.85 13.1,17L15.45,14.65Q15.675,14.425 16.038,14.312Q16.4,14.2 16.75,14.25L20.2,14.95Q20.55,15.025 20.775,15.287Q21,15.55 21,15.9V19.95Q21,20.4 20.7,20.7Q20.4,21 19.95,21Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_sms.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_sms.xml
new file mode 100644
index 0000000..7f76a60
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_sms.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_600">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M8,11Q8.425,11 8.713,10.712Q9,10.425 9,10Q9,9.575 8.713,9.287Q8.425,9 8,9Q7.575,9 7.287,9.287Q7,9.575 7,10Q7,10.425 7.287,10.712Q7.575,11 8,11ZM12,11Q12.425,11 12.713,10.712Q13,10.425 13,10Q13,9.575 12.713,9.287Q12.425,9 12,9Q11.575,9 11.288,9.287Q11,9.575 11,10Q11,10.425 11.288,10.712Q11.575,11 12,11ZM16,11Q16.425,11 16.712,10.712Q17,10.425 17,10Q17,9.575 16.712,9.287Q16.425,9 16,9Q15.575,9 15.288,9.287Q15,9.575 15,10Q15,10.425 15.288,10.712Q15.575,11 16,11ZM2,22V4Q2,3.175 2.588,2.587Q3.175,2 4,2H20Q20.825,2 21.413,2.587Q22,3.175 22,4V16Q22,16.825 21.413,17.413Q20.825,18 20,18H6ZM4,16H20Q20,16 20,16Q20,16 20,16V4Q20,4 20,4Q20,4 20,4H4Q4,4 4,4Q4,4 4,4V16ZM4,16V4Q4,4 4,4Q4,4 4,4Q4,4 4,4Q4,4 4,4V16Q4,16 4,16Q4,16 4,16Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_storage.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_storage.xml
similarity index 100%
rename from packages/CompanionDeviceManager/res/drawable/ic_storage.xml
rename to packages/CompanionDeviceManager/res/drawable/ic_permission_storage.xml
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_watch.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_watch.xml
new file mode 100644
index 0000000..dd247ee
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_watch.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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path android:fillColor="@android:color/white" android:pathData="M9,22 L7.65,17.45Q6.45,16.5 5.725,15.075Q5,13.65 5,12Q5,10.35 5.725,8.925Q6.45,7.5 7.65,6.55L9,2H15L16.35,6.55Q17.55,7.5 18.275,8.925Q19,10.35 19,12Q19,13.65 18.275,15.075Q17.55,16.5 16.35,17.45L15,22ZM12,17Q14.075,17 15.538,15.537Q17,14.075 17,12Q17,9.925 15.538,8.462Q14.075,7 12,7Q9.925,7 8.463,8.462Q7,9.925 7,12Q7,14.075 8.463,15.537Q9.925,17 12,17Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index 520ade8..22805f6 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -57,36 +57,39 @@
                 android:layout_height="0dp"
                 android:layout_weight="1">
 
-                <LinearLayout
-                    android:id="@+id/multiple_device_list"
+                <androidx.constraintlayout.widget.ConstraintLayout
+                    xmlns:app="http://schemas.android.com/apk/res-auto"
+                    android:id="@+id/constraint_list"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:layout_marginTop="12dp"
-                    android:layout_marginBottom="12dp"
-                    android:orientation="vertical"
                     android:visibility="gone">
 
+                    <androidx.recyclerview.widget.RecyclerView
+                        android:id="@+id/device_list"
+                        android:layout_width="match_parent"
+                        android:layout_height="200dp"
+                        android:scrollbars="vertical"
+                        android:visibility="gone" />
+
+                    <androidx.recyclerview.widget.RecyclerView
+                        android:id="@+id/permission_list"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:scrollbars="vertical"
+                        android:fadeScrollbars="false"
+                        app:layout_constraintHeight_max="220dp"
+                        android:visibility="gone" />
+
                     <View
                         android:id="@+id/border_top"
                         style="@style/DeviceListBorder" />
 
-                    <androidx.recyclerview.widget.RecyclerView
-                        android:id="@+id/device_list"
-                        android:layout_width="match_parent"
-                        android:scrollbars="vertical"
-                        android:layout_marginBottom="12dp"
-                        android:layout_height="200dp" />
-
                     <View
                         android:id="@+id/border_bottom"
+                        app:layout_constraintBottom_toBottomOf="parent"
                         style="@style/DeviceListBorder" />
 
-                </LinearLayout>
-
-                <androidx.recyclerview.widget.RecyclerView
-                    android:id="@+id/permission_list"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content" />
+                </androidx.constraintlayout.widget.ConstraintLayout>
 
                 <ProgressBar
                     android:id="@+id/spinner_multiple_device"
@@ -132,6 +135,8 @@
                     style="@style/NegativeButtonMultipleDevices"
                     android:textColor="?android:textColorPrimary"
                     android:visibility="gone"
+                    android:layout_marginTop="12dp"
+                    android:layout_marginBottom="12dp"
                     android:text="@string/consent_no" />
             </LinearLayout>
 
diff --git a/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml b/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
index 1f922b9..ddff2cb 100644
--- a/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
@@ -46,6 +46,7 @@
                 android:layout_height="wrap_content"
                 android:gravity="center"
                 android:textColor="?android:attr/textColorPrimary"
+                style="@style/TextAppearance"
                 android:textSize="22sp" />
 
             <TextView
@@ -58,6 +59,7 @@
                 android:layout_marginBottom="32dp"
                 android:gravity="center"
                 android:textColor="?android:attr/textColorSecondary"
+                style="@style/TextAppearance"
                 android:textSize="14sp" />
 
             <LinearLayout
@@ -70,6 +72,7 @@
                 <Button
                     android:id="@+id/btn_back"
                     style="@style/VendorHelperBackButton"
+                    android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
                     android:text="@string/consent_back" />
 
             </LinearLayout>
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
index d4439f9..ac5294a 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
@@ -39,6 +39,6 @@
         android:layout_height="wrap_content"
         android:paddingStart="24dp"
         android:paddingEnd="24dp"
-        android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
+        style="@style/TextAppearance"/>
 
 </LinearLayout>
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
index a3d71b9..ab2d815 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
@@ -34,26 +34,43 @@
         android:contentDescription="@null"/>
 
     <LinearLayout
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="vertical"
         android:gravity="center_vertical"
-        android:padding="6dp">
+        android:padding="6dp"
+        android:layout_weight="1">
 
         <TextView
             android:id="@+id/permission_name"
-            android:layout_width="match_parent"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:textSize="16sp"
-            android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
+            android:layout_marginTop="2dp"
+            style="@style/TextAppearance"
+            android:textColor="?android:attr/textColorPrimary"/>
 
         <TextView
             android:id="@+id/permission_summary"
-            android:layout_width="match_parent"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:textSize="14sp"
+            android:layout_marginTop="2dp"
+            style="@style/TextAppearance"
             android:textColor="?android:attr/textColorSecondary"/>
 
     </LinearLayout>
 
+    <ImageButton
+        android:id="@+id/permission_expand_button"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:src="@drawable/btn_expand_more"
+        android:layout_marginTop="8dp"
+        android:layout_marginStart="24dp"
+        android:background="@android:color/transparent"
+        android:clickable="false"
+        android:importantForAccessibility="no"
+        android:contentDescription="@null"/>
+
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values-af/strings.xml b/packages/CompanionDeviceManager/res/values-af/strings.xml
index f51266c..3d8369a 100644
--- a/packages/CompanionDeviceManager/res/values-af/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-af/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Gee &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang tot jou &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"horlosie"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Kies \'n <xliff:g id="PROFILE_NAME">%1$s</xliff:g> om deur &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; bestuur te word"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Hierdie program is nodig om jou <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te bestuur. <xliff:g id="APP_NAME">%2$s</xliff:g> sal toegelaat word om interaksie met jou kennisgewings te hê, en sal toegang hê tot jou Foon-, SMS-, Kontakte-, Kalender- en Toestelle in die Omtrek-toestemming."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Programme"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Stroom jou foon se programme"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Hierdie app is nodig om jou <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te bestuur. <xliff:g id="APP_NAME">%2$s</xliff:g> sal toegelaat word om interaksie met jou kennisgewings te hê, en sal toegang hê tot jou Foon-, SMS-, Kontakte-, Kalender-, Oproeprekords- en Toestelle in die Omtrek-toestemming."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Hierdie app is nodig om jou <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te bestuur. <xliff:g id="APP_NAME">%2$s</xliff:g> sal toegelaat word om interaksie met die volgende toestemmings te hê:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Gee &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang tot hierdie inligting op jou foon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Oorkruistoestel-dienste"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> versoek tans namens jou <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> toestemming om programme tussen jou toestelle te stroom"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Gee &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang tot hierdie inligting op jou foon"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Kennisgewings"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Kan alle kennisgewings lees, insluitend inligting soos kontakte, boodskappe en foto\'s"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Foto\'s en media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Dienste"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> versoek tans namens jou <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> toegang tot jou foon se foto\'s, media en kennisgewings"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"toestel"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Dit kan mikrofoon-, kamera- en liggingtoegang insluit, asook ander sensitiewe toestemmings op &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Jy kan hierdie toestemmings enige tyd verander in jou Instellings op &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Program-ikoon"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Meer Inligting-knoppie"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Foon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontakte"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalender"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Toestelle in die omtrek"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Foto\'s en media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Kennisgewings"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Het toegang tot jou foonnommer en netwerkinligting. Word vereis vir die maak van oproepe en VoIP, stemboodskapdiens, oproepherleiding en die wysiging van oproeprekords"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Kan jou kontaklys lees, skep, of wysig, en het toegang tot die lys van al die rekeninge wat op jou toestel gebruik word"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Kan alle kennisgewings lees, insluitend inligting soos kontakte, boodskappe en foto\'s"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stroom jou foon se apps"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-am/strings.xml b/packages/CompanionDeviceManager/res/values-am/strings.xml
index 15b39d2..99d2041 100644
--- a/packages/CompanionDeviceManager/res/values-am/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-am/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; የእርስዎን &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; መሣሪያ እንዲደርስ ይፍቀዱለት"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ሰዓት"</string>
     <string name="chooser_title" msgid="2262294130493605839">"በ&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; የሚተዳደር <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ይምረጡ"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"የእርስዎን <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ለማስተዳደር ይህ መተግበሪያ ያስፈልጋል። <xliff:g id="APP_NAME">%2$s</xliff:g> ከማሳወቂያዎችዎ ጋር መስተጋብር እንዲፈጥር እና የእርስዎን ስልክ፣ ኤስኤምኤስ፣ እውቂያዎች፣ የቀን መቁጠሪያ፣ የጥሪ ምዝገባ ማስታወሻዎች እና በአቅራቢያ ያሉ የመሣሪያዎች ፈቃዶች እንዲደርስ ይፈቀድለታል።"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"መተግበሪያዎች"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"የስልክዎን መተግበሪያዎች በዥረት ይልቀቁ"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"የእርስዎን <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ለማስተዳደር መተግበሪያው ያስፈልጋል። <xliff:g id="APP_NAME">%2$s</xliff:g> ከማሳወቂያዎችዎ ጋር መስተጋብር እንዲፈጥር እና የእርስዎን ስልክ፣ ኤስኤምኤስ፣ ዕውቂያዎች፣ የቀን መቁጠሪያ፣ የጥሪ ምዝገባ ማስታወሻዎች እና በአቅራቢያ ያሉ የመሣሪያዎች ፈቃዶች እንዲደርስ ይፈቀድለታል።"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"የእርስዎን <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ለማስተዳደር መተግበሪያው ያስፈልጋል። <xliff:g id="APP_NAME">%2$s</xliff:g> ከእነዚህ ፈቃዶች ጋር መስተጋብር እንዲፈጥር ይፈቀድለታል፦"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ይህን መረጃ ከስልክዎ እንዲደርስበት ይፍቀዱለት"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"መሣሪያ ተሻጋሪ አገልግሎቶች"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> በእርስዎ መሣሪያዎች መካከል መተግበሪያዎችን በዥረት ለመልቀቅ የእርስዎን <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ወክሎ ፈቃድ እየጠየቀ ነው"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ይህን መረጃ ከስልክዎ ላይ እንዲደርስ ይፍቀዱለት"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"ማሳወቂያዎች"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"እንደ እውቂያዎች፣ መልዕክቶች እና ፎቶዎች ያሉ መረጃዎችን ጨምሮ ሁሉንም ማሳወቂያዎች ማንበብ ይችላል"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"ፎቶዎች እና ሚዲያ"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"የGoogle Play አገልግሎቶች"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> የስልክዎን ፎቶዎች፣ ሚዲያ እና ማሳወቂያዎች ለመድረስ የእርስዎን <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ወክሎ ፈቃድ እየጠየቀ ነው"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"መሣሪያ"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;ይህ የማይክሮፎን፣ የካሜራ እና የአካባቢ መዳረሻ እና ሌሎች በ&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt; ላይ ያሉ አደገኛ ፈቃዶችን ሊያካትት ይችላል።እነዚህን ፈቃዶች በማንኛውም ጊዜ በ&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;ላይ ቅንብሮችዎ ውስጥ መቀየር ይችላሉ።"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"የመተግበሪያ አዶ"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"የተጨማሪ መረጃ አዝራር"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"ስልክ"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"ኤስኤምኤስ"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"ዕውቂያዎች"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"ቀን መቁጠሪያ"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"በአቅራቢያ ያሉ መሣሪያዎች"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"ፎቶዎች እና ሚዲያ"</string>
+    <string name="permission_notification" msgid="693762568127741203">"ማሳወቂያዎች"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"መተግበሪያዎች"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"የእርስዎን ስልክ ቁጥር እና የአውታረ መረብ መረጃ መድረስ ይችላል። ጥሪዎችን ለማድረግ እና VoIP፣ የድምፅ መልዕክት፣ የጥሪ ማዘዋወር እና የጥሪ ምዝገባ ማስታወሻዎችን ለማርትዕ ያስፈልጋል"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"የእኛን የዕውቂያ ዝርዝር ማንበብ፣ መፍጠር ወይም ማርትዕ እንዲሁም በመሣሪያዎ ላይ ጥቅም ላይ የዋሉትን ሁሉንም መለያዎች ዝርዝር መድረስ ይችላል"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"እንደ እውቂያዎች፣ መልዕክቶች እና ፎቶዎች ያሉ መረጃዎችን ጨምሮ ሁሉንም ማሳወቂያዎች ማንበብ ይችላል"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"የስልክዎን መተግበሪያዎች በዥረት ይልቀቁ"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml
index 13dcb69..051a629 100644
--- a/packages/CompanionDeviceManager/res/values-ar/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"‏السماح لتطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; بالوصول إلى &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"الساعة"</string>
     <string name="chooser_title" msgid="2262294130493605839">"‏اختَر <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ليديرها تطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"هذا التطبيق مطلوب لإدارة <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. سيتم السماح لتطبيق <xliff:g id="APP_NAME">%2$s</xliff:g> بالتفاعل مع الإشعارات والوصول إلى أذونات الهاتف والرسائل القصيرة وجهات الاتصال والتقويم وسجلّات المكالمات والأجهزة المجاورة."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"التطبيقات"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"بث تطبيقات هاتفك"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"التطبيق مطلوب لإدارة \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". سيتم السماح لتطبيق \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" بالتفاعل مع الإشعارات والوصول إلى أذونات الهاتف والرسائل القصيرة وجهات الاتصال والتقويم وسجلّات المكالمات والأجهزة المجاورة."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"التطبيق مطلوب لإدارة \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". سيتم السماح للتطبيق \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" بالتفاعل مع هذه الأذونات."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‏السماح لتطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; بالوصول إلى هذه المعلومات من هاتفك"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"الخدمات التي تعمل بين الأجهزة"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"يطلب تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> الحصول على إذن نيابةً عن <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> لمشاركة التطبيقات بين أجهزتك."</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"‏السماح لتطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; بالوصول إلى هذه المعلومات من هاتفك"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"الإشعارات"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"يمكن لهذا الملف الشخصي قراءة جميع الإشعارات، بما في ذلك المعلومات، مثل جهات الاتصال والرسائل والصور."</string>
-    <string name="permission_storage" msgid="6831099350839392343">"الصور والوسائط"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"‏خدمات Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"يطلب تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> الحصول على إذن نيابةً عن <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> للوصول إلى الصور والوسائط والإشعارات في هاتفك."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"جهاز"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"‏&lt;p&gt;قد تتضمَّن هذه الأذونات الوصول إلى الميكروفون والكاميرا والموقع الجغرافي وغيرها من أذونات الوصول إلى المعلومات الحسّاسة على &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;يمكنك تغيير هذه الأذونات في أي وقت في إعداداتك على &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"رمز التطبيق"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"زر مزيد من المعلومات"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"الهاتف"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"الرسائل القصيرة"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"جهات الاتصال"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"التقويم"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"الأجهزة المجاورة"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"الصور والوسائط"</string>
+    <string name="permission_notification" msgid="693762568127741203">"الإشعارات"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"التطبيقات"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"يسمح هذا الإذن بالوصول إلى رقم هاتفك ومعلومات الشبكة. ويجب منح هذا الإذن لإجراء مكالمات وتلقّي بريد صوتي عبر بروتوكول الصوت على الإنترنت وإعادة توجيه المكالمات وتعديل سجلات المكالمات."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"يسمح هذا الإذن بقراءة قائمة جهات الاتصال أو إنشائها أو تعديلها وكذلك قائمة كل الحسابات المُستخدَمة على جهازك."</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"يمكن لهذا الملف الشخصي قراءة جميع الإشعارات، بما في ذلك المعلومات، مثل جهات الاتصال والرسائل والصور."</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"بث تطبيقات هاتفك"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-as/strings.xml b/packages/CompanionDeviceManager/res/values-as/strings.xml
index 9df589a..94c9325 100644
--- a/packages/CompanionDeviceManager/res/values-as/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-as/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ক আপোনাৰ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; এক্সেছ কৰিবলৈ দিয়ক"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ঘড়ী"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;এ পৰিচালনা কৰিব লগা এটা <xliff:g id="PROFILE_NAME">%1$s</xliff:g> বাছনি কৰক"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"আপোনাৰ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> পৰিচালনা কৰিবলৈ এই এপ্‌টোৰ আৱশ্যক। <xliff:g id="APP_NAME">%2$s</xliff:g>ক আপোনাৰ জাননী ব্যৱহাৰ কৰিবলৈ আৰু আপোনাৰ ফ’ন, এছএমএছ, সম্পৰ্ক ,কেলেণ্ডাৰ, কল লগ আৰু নিকটৱৰ্তী ডিভাইচৰ অনুমতি এক্সেছ কৰিবলৈ দিয়া হ’ব।"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"এপ্‌"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"আপোনাৰ ফ’নৰ এপ্‌ ষ্ট্ৰীম কৰক"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"আপোনাৰ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> পৰিচালনা কৰিবলৈ এপ্‌টোৰ আৱশ্যক। <xliff:g id="APP_NAME">%2$s</xliff:g>ক আপোনাৰ জাননী ব্যৱহাৰ কৰিবলৈ আৰু আপোনাৰ ফ’ন, এছএমএছ, সম্পৰ্ক ,কেলেণ্ডাৰ, কল লগ আৰু নিকটৱৰ্তী ডিভাইচৰ অনুমতি এক্সেছ কৰিবলৈ দিয়া হ’ব।"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"আপোনাৰ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> পৰিচালনা কৰিবলৈ এপ্‌টোৰ আৱশ্যক। <xliff:g id="APP_NAME">%2$s</xliff:g>ক এই অনুমতিসমূহৰ সৈতে ভাব-বিনিময় কৰিবলৈ দিয়া হ’ব:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ক আপোনাৰ ফ’নৰ পৰা এই তথ্যখিনি এক্সেছ কৰাৰ অনুমতি দিয়ক"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ক্ৰছ-ডিভাইচ সেৱাসমূহ"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ আপোনাৰ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>ৰ হৈ আপোনাৰ ডিভাইচসমূহৰ মাজত এপ্‌ ষ্ট্ৰীম কৰাৰ বাবে অনুৰোধ জনাইছে"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ক আপোনাৰ ফ’নৰ পৰা এই তথ্যখিনি এক্সেছ কৰাৰ অনুমতি দিয়ক"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"জাননী"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"সম্পৰ্কসূচী, বাৰ্তা আৰু ফট’ৰ দৰে তথ্যকে ধৰি আটাইবোৰ জাননী পঢ়িব পাৰে"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"ফট’ আৰু মিডিয়া"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play সেৱা"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ আপোনাৰ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>ৰ হৈ আপোনাৰ ফ’নৰ ফট’, মিডিয়া আৰু জাননী এক্সেছ কৰাৰ বাবে অনুৰোধ জনাইছে"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ডিভাইচ"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;ইয়াত &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;ত মাইক্ৰ’ফ’ন, কেমেৰা আৰু অৱস্থানৰ এক্সেছ আৰু অন্য সংবেদশীল অনুমতিসমূহ প্ৰদান কৰাটো অন্তৰ্ভুক্ত হ’ব পাৰে।&lt;/p&gt; &lt;p&gt;আপুনি যিকোনো সময়তে &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;ত থকা আপোনাৰ ছেটিঙত এই অনুমতিসমূহ সলনি কৰিব পাৰে।&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"এপৰ চিহ্ন"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"অধিক তথ্যৰ বুটাম"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"ফ’ন"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"এছএমএছ"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"সম্পৰ্ক"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"কেলেণ্ডাৰ"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"নিকটৱৰ্তী ডিভাইচ"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"ফট’ আৰু মিডিয়া"</string>
+    <string name="permission_notification" msgid="693762568127741203">"জাননী"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"এপ্"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"কল আৰু VoIP, ভইচমেইল, কল ৰিডাইৰেক্ট আৰু কলৰ লগ সম্পাদনা কৰিবলৈ আৱশ্যক হোৱা আপোনাৰ ফ’ন নম্বৰ আৰু নেটৱৰ্কৰ তথ্য এক্সেছ কৰিব পাৰে"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"আমাৰ সম্পৰ্কসূচী পঢ়িব, সৃষ্টি কৰিব অথবা সম্পাদনা কৰিব পাৰে আৰু লগতে আপোনাৰ ডিভাইচত ব্যৱহাৰ কৰা আটাইবোৰ একাউণ্টৰ সূচীখন এক্সেছ কৰিব পাৰে"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"সম্পৰ্কসূচী, বাৰ্তা আৰু ফট’ৰ দৰে তথ্যকে ধৰি আটাইবোৰ জাননী পঢ়িব পাৰে"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"আপোনাৰ ফ’নৰ এপ্ ষ্ট্ৰীম কৰক"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-az/strings.xml b/packages/CompanionDeviceManager/res/values-az/strings.xml
index 4ef3111..625275dc 100644
--- a/packages/CompanionDeviceManager/res/values-az/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-az/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinin &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazınıza girişinə icazə verin"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"izləyin"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; tərəfindən idarə ediləcək <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Bu tətbiq <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızı idarə etmək üçün lazımdır. <xliff:g id="APP_NAME">%2$s</xliff:g> bildirişlərinizə, Telefon, SMS, Kontaktlar, Təqvim, Zəng qeydləri və Yaxınlıqdakı cihaz icazələrinə giriş əldə edəcək."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Tətbiqlər"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Telefonunuzun tətbiqlərini yayımlayın"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Tətbiq <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızı idarə etmək üçün lazımdır. <xliff:g id="APP_NAME">%2$s</xliff:g> bildirişlərinizə, Telefon, SMS, Kontaktlar, Təqvim, Zəng qeydləri və Yaxınlıqdakı cihaz icazələrinə giriş əldə edəcək."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Tətbiq <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızı idarə etmək üçün lazımdır. <xliff:g id="APP_NAME">%2$s</xliff:g> bu icazələrlə qarşılıqlı əlaqəyə icazə veriləcək:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinə telefonunuzdan bu məlumata giriş icazəsi verin"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cihazlararası xidmətlər"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqi <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> adından cihazlarınız arasında tətbiqləri yayımlamaq üçün icazə istəyir"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinə telefonunuzdan bu məlumata giriş icazəsi verin"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Bildirişlər"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Bütün bildirişləri, o cümlədən kontaktlar, mesajlar və fotolar kimi məlumatları oxuya bilər"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Foto və media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play xidmətləri"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqi <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> adından telefonunuzun fotoları, mediası və bildirişlərinə giriş üçün icazə istəyir"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"cihaz"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Buraya &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; cihazındakı Mikrofon, Kamera və Məkana girişi və digər həssas icazələr daxil ola bilər.&lt;/p&gt; &lt;p&gt;Bu icazələri istənilən vaxt &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; cihazında ayarlarınızda dəyişə bilərsiniz.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Tətbiq İkonası"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Ətraflı Məlumat Düyməsi"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontakt"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Təqvim"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Yaxınlıqdakı cihazlar"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Foto və media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Bildirişlər"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Tətbiqlər"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Telefon nömrənizə və şəbəkə məlumatınıza giriş edə bilər. Zəng etmək və VoIP, səsli poçt, zəng yönləndirməsi və zəng qeydlərini redaktə etmək üçün tələb olunur"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Kontakt siyahımızı oxuya, yarada və ya redaktə edə, həmçinin cihazınızda istifadə edilən bütün hesabların siyahısına giriş edə bilər"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Bütün bildirişləri, o cümlədən kontaktlar, mesajlar və fotolar kimi məlumatları oxuya bilər"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefonunuzun tətbiqlərini yayımlayın"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
index baf55d2..4b12d60 100644
--- a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Dozvolite da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"sat"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Odaberite profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Ova aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> će dobiti dozvolu za interakciju sa obaveštenjima i pristup dozvolama za telefon, SMS, kontakte, kalendar, evidencije poziva i uređaje u blizini."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Aplikacije"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Strimujte aplikacije na telefonu"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> će dobiti dozvolu za interakciju sa obaveštenjima i pristup dozvolama za telefon, SMS, kontakte, kalendar, evidencije poziva i uređaje u blizini."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> će dobiti dozvolu za interakciju sa ovim dozvolama:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Dozvolite da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa ovim informacijama sa telefona"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Usluge na više uređaja"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> zahteva dozvolu u ime uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> za strimovanje aplikacija između uređaja"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Dozvolite da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa ovim informacijama sa telefona"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Obaveštenja"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Može da čita sva obaveštenja, uključujući informacije poput kontakata, poruka i slika"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Slike i mediji"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play usluge"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahteva dozvolu u ime uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> za pristup slikama, medijskom sadržaju i obaveštenjima sa telefona"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;To može da obuhvata pristup mikrofonu, kameri i lokaciji, kao i drugim osetljivim dozvolama na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;U svakom trenutku možete da promenite te dozvole u Podešavanjima na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacije"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Dugme za više informacija"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontakti"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalendar"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Uređaji u blizini"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Slike i mediji"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Obaveštenja"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacije"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Može da pristupa vašem broju telefona i informacijama o mreži. Neophodno za upućivanje poziva i VoIP, govornu poštu, preusmeravanje poziva i izmene evidencije poziva"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Može da čita, kreira ili menja listu kontakata, kao i da pristupa listi svih naloga koji se koriste na vašem uređaju"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Može da čita sva obaveštenja, uključujući informacije poput kontakata, poruka i slika"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Strimujte aplikacije na telefonu"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml
index 276127f..a11f9c1 100644
--- a/packages/CompanionDeviceManager/res/values-be/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-be/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Дазвольце праграме &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ да вашай прылады &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"гадзіннік"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Выберыце прыладу (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), якой будзе кіраваць праграма &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Гэта праграма неабходная для кіравання прыладай \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". <xliff:g id="APP_NAME">%2$s</xliff:g> зможа ўзаемадзейнічаць з вашымі апавяшчэннямі і атрымае доступ да тэлефона, SMS, кантактаў, календара, журналаў выклікаў і прылад паблізу."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Праграмы"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Трансліруйце змесціва праграм з вашага тэлефона"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Гэта праграма неабходная для кіравання прыладай \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". <xliff:g id="APP_NAME">%2$s</xliff:g> зможа ўзаемадзейнічаць з вашымі апавяшчэннямі і атрымае доступ да тэлефона, SMS, кантактаў, календара, журналаў выклікаў і прылад паблізу."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Гэта праграма неабходная для кіравання прыладай \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". <xliff:g id="APP_NAME">%2$s</xliff:g> зможа выкарыстоўваць наступныя дазволы:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Дазвольце праграме &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; мець доступ да гэтай інфармацыі з вашага тэлефона"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Сэрвісы для некалькіх прылад"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>\" на трансляцыю праграм паміж вашымі прыладамі"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Дазвольце праграме &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; мець доступ да гэтай інфармацыі з вашага тэлефона"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Апавяшчэнні"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Можа счытваць усе апавяшчэнні, уключаючы паведамленні, фота і інфармацыю пра кантакты"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Фота і медыяфайлы"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Сэрвісы Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>\" на доступ да фота, медыяфайлаў і апавяшчэнняў на вашым тэлефоне"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"прылада"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Дазволы могуць уключаць доступ да мікрафона, камеры і даных пра месцазнаходжанне, а таксама да іншай канфідэнцыяльнай інфармацыі на прыладзе &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Вы можаце ў любы час змяніць гэтыя дазволы ў Наладах на прыладзе &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Значок праграмы"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Кнопка \"Даведацца больш\""</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Тэлефон"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Кантакты"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Каляндар"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Прылады паблізу"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Фота і медыяфайлы"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Апавяшчэнні"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Праграмы"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Доступ да вашага нумара тэлефона і інфармацыі пра сетку. Гэты дазвол патрабуецца, каб рабіць звычайныя і VoIP-выклікі, адпраўляць галасавыя паведамленні, перанакіроўваць выклікі і рэдагаваць журналы выклікаў"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Магчымасць чытаць, ствараць і рэдагаваць спіс кантактаў, а таксама атрымліваць доступ да спіса ўсіх уліковых запісаў на вашай прыладзе"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Можа счытваць усе апавяшчэнні, уключаючы паведамленні, фота і інфармацыю пра кантакты"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Трансляцыя змесціва праграм з вашага тэлефона"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-bg/strings.xml b/packages/CompanionDeviceManager/res/values-bg/strings.xml
index 6b17ffd..e75f392 100644
--- a/packages/CompanionDeviceManager/res/values-bg/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bg/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Разрешаване на &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да осъществява достъп до устройството ви &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"часовник"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Изберете устройство (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), което да се управлява от &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Това приложение е необходимо за управление на <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ще получи разрешение да взаимодейства с известията ви и да осъществява достъп до разрешенията за телефона, SMS съобщенията, контактите, календара, списъците с обажданията и разрешенията за устройства в близост."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Приложения"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Поточно предаване на приложенията на телефона ви"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Това приложение е необходимо за управление на устройството ви (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>). <xliff:g id="APP_NAME">%2$s</xliff:g> ще получи разрешение да взаимодейства с известията ви и да осъществява достъп до разрешенията за телефона, SMS съобщенията, контактите, календара, списъците с обажданията и разрешенията за устройства в близост."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Това приложение е необходимо за управление на устройството ви (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>). <xliff:g id="APP_NAME">%2$s</xliff:g> ще получи разрешение да взаимодейства със следните разрешения:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Разрешете на &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да осъществява достъп до тази информация от телефона ви"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Услуги за различни устройства"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ иска разрешение от името на <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> да предава поточно приложения между устройствата ви"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Разрешете на &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да осъществява достъп до тази информация от телефона ви"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Известия"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Може да чете всички известия, включително различна информация, като например контакти, съобщения и снимки"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Снимки и мултимедия"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Услуги за Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> иска разрешение от името на <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> за достъп до снимките, мултимедията и известията на телефона ви"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"устройство"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Това може да включва достъп до микрофона, камерата и местоположението, както и други разрешения за достъп до поверителна информация на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Можете да промените тези разрешения по всяко време от настройките на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Икона на приложението"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Бутон за още информация"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Контакти"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Календар"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Устройства в близост"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Снимки и мултимедия"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Известия"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Приложения"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Може да осъществява достъп до номера и мрежата на телефона ви. Изисква се за провеждането на обаждания и разговори през VoIP, гласовата поща, пренасочването на обаждания и редактирането на списъците с обажданията"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Може да чете, създава и редактира записи в списъка с контактите ви, както и да осъществява достъп до списъка с всички профили, използвани на устройството ви"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Може да чете всички известия, включително различна информация, като например контакти, съобщения и снимки"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Поточно предаване на приложенията на телефона ви"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-bn/strings.xml b/packages/CompanionDeviceManager/res/values-bn/strings.xml
index 1eaf142..8930a81 100644
--- a/packages/CompanionDeviceManager/res/values-bn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bn/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"আপনার &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; অ্যাক্সেস করার জন্য &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-কে অনুমতি দিন"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ঘড়ি"</string>
     <string name="chooser_title" msgid="2262294130493605839">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> বেছে নিন যেটি &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ম্যানেজ করবে"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"এই অ্যাপকে আপনার <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ম্যানেজ করতে দিতে হবে। <xliff:g id="APP_NAME">%2$s</xliff:g>-কে আপনার বিজ্ঞপ্তির সাথে ইন্টার‌্যাক্ট এবং ফোন, এসএমএস, পরিচিতি, ক্যালেন্ডার, কল লগ ও আশেপাশের ডিভাইস অ্যাক্সেস করার অনুমতি দেওয়া হবে।"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"অ্যাপ"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"আপনার ফোনের অ্যাপ স্ট্রিমিংয়ের মাধ্যমে কাস্ট করুন"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"আপনার <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ম্যানেজ করার জন্য অ্যাপটি প্রয়োজন। <xliff:g id="APP_NAME">%2$s</xliff:g>-কে আপনার বিজ্ঞপ্তির সাথে ইন্টার‌্যাক্ট করার এবং ফোন, এসএমএস, পরিচিতি, ক্যালেন্ডার, কল লগ ও আশেপাশের ডিভাইস অ্যাক্সেস করার অনুমতি দেওয়া হবে।"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"আপনার <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ম্যানেজ করার জন্য অ্যাপটি প্রয়োজন। <xliff:g id="APP_NAME">%2$s</xliff:g> অ্যাপকে এইসব অনুমতির সাথে ইন্টার‌্যাক্ট করার জন্য অনুমোদন দেওয়া হবে:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"আপনার ফোন থেকে &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; অ্যাপকে এই তথ্য অ্যাক্সেস করার অনুমতি দিন"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ক্রস-ডিভাইস পরিষেবা"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"আপনার ডিভাইসগুলির মধ্যে অ্যাপ স্ট্রিম করার জন্য <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-এর হয়ে অনুমতি চাইছে"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"আপনার ফোন থেকে &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-কে এই তথ্য অ্যাক্সেস করার অনুমতি দিন"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"বিজ্ঞপ্তি"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"সব বিজ্ঞপ্তি পড়তে পারবে, যার মধ্যে পরিচিতি, মেসেজ ও ফটোর মতো তথ্য অন্তর্ভুক্ত"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"ফটো ও মিডিয়া"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play পরিষেবা"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"আপনার ফোনের ফটো, মিডিয়া এবং তথ্য অ্যাক্সেস করার জন্য <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-এর হয়ে অনুমতি চাইছে"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ডিভাইস"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;এটি &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&amp;gt-এ হয়ত মাইক্রোফোন, ক্যামেরা এবং লোকেশনের অ্যাক্সেস ও অন্যান্য সংবেদনশীল অনুমতি অন্তর্ভুক্ত করতে পারে;আপনি যেকোনও সময় &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;-এর \'সেটিংস\'-এ গিয়ে এইসব অনুমতি পরিবর্তন করতে পারবেন"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"অ্যাপের আইকন"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"আরও তথ্য সংক্রান্ত বোতাম"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"ফোন"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"এসএমএস"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"পরিচিতি"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"ক্যালেন্ডার"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"আশেপাশের ডিভাইস"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"ফটো ও মিডিয়া"</string>
+    <string name="permission_notification" msgid="693762568127741203">"বিজ্ঞপ্তি"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"অ্যাপ"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"আপনার ফোন নম্বর ও নেটওয়ার্ক সংক্রান্ত তথ্য অ্যাক্সেস করতে পারবে। কল করার জন্য এবং VoIP, ভয়েসমেল, কল রিডাইরেক্ট ও কল লগ এডিট করার জন্য যা প্রয়োজন"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"আমাদের পরিচিতি তালিকা দেখতে, তৈরি বা এডিট করতে পারবে, পাশাপাশি আপনার ডিভাইসে ব্যবহার করা হয় এমন সবকটি অ্যাকাউন্টের তালিকা অ্যাক্সেস করতে পারবে"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"সব বিজ্ঞপ্তি পড়তে পারবে, যার মধ্যে পরিচিতি, মেসেজ ও ফটোর মতো তথ্য অন্তর্ভুক্ত"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"আপনার ফোনের অ্যাপ স্ট্রিম করুন"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-bs/strings.xml b/packages/CompanionDeviceManager/res/values-bs/strings.xml
index 8c941ac..2a45533 100644
--- a/packages/CompanionDeviceManager/res/values-bs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bs/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Dozvolite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"sat"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Odaberite uređaj <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Ova aplikacija je potrebna za upravljanje profilom: <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikaciji <xliff:g id="APP_NAME">%2$s</xliff:g> će se dozvoliti da ostvari interakciju s vašim obavještenjima i da pristupi odobrenjima za Telefon, SMS, Kontakte, Kalendar, Zapisnike poziva i Uređaje u blizini."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Aplikacije"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Prenosite aplikacije s telefona"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikaciji <xliff:g id="APP_NAME">%2$s</xliff:g> će se dozvoliti da ostvaruje interakciju s vašim obavještenjima i da pristupa odobrenjima za telefon, SMS-ove, kontakte, kalendar, zapisnike poziva i uređaje u blizini."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikaciji <xliff:g id="APP_NAME">%2$s</xliff:g> će biti dozvoljena interakcija s ovim odobrenjima:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Dozvolite da aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa ovim informacijama s telefona"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Usluga na više uređaja"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> u ime uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> zahtijeva odobrenje da prenosi aplikacije između vaših uređaja"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Dozvolite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa ovim informacijama s vašeg telefona"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Obavještenja"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Može čitati sva obavještenja, uključujući informacije kao što su kontakti, poruke i fotografije"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotografije i mediji"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play usluge"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> u ime uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> zahtijeva odobrenje da pristupi fotografijama, medijima i odobrenjima na vašem telefonu"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Ovo može uključivati pristup mikrofonu, kameri i lokaciji i druga osjetljiva odobrenja na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Uvijek možete promijeniti ova odobrenja u Postavkama na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacije"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Dugme Više informacija"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontakti"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalendar"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Uređaji u blizini"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotografije i mediji"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Obavještenja"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacije"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Može pristupiti vašem broju telefona i informacijama o mreži. Potrebno je za upućivanje poziva i VoIP, govornu poštu, preusmjeravanje poziva te uređivanje zapisnika poziva"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Može čitati, kreirati ili uređivati našu listu kontakata te pristupiti listi svih računa koji se koriste na vašem uređaju"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Može čitati sva obavještenja, uključujući informacije kao što su kontakti, poruke i fotografije"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Prenosite aplikacije s telefona"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ca/strings.xml b/packages/CompanionDeviceManager/res/values-ca/strings.xml
index 5cc72ae..8995cdd 100644
--- a/packages/CompanionDeviceManager/res/values-ca/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ca/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Permet que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; accedeixi a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"rellotge"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Tria un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> perquè el gestioni &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Aquesta aplicació es necessita per gestionar <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> tindrà permís per interaccionar amb les teves notificacions i accedir al telèfon, als SMS, als contactes, al calendari, als registres de trucades i als dispositius propers."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Aplicacions"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Reprodueix en continu aplicacions del telèfon"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"L\'aplicació és necessària per gestionar <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> tindrà permís per interaccionar amb les teves notificacions i accedir al telèfon, als SMS, als contactes, al calendari, als registres de trucades i als dispositius propers."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"L\'aplicació és necessària per gestionar <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> tindrà permís per interaccionar amb aquests permisos:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permet que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; accedeixi a aquesta informació del telèfon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Serveis multidispositiu"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> demana permís en nom del teu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> per reproduir en continu aplicacions entre els dispositius"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Permet que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; accedeixi a aquesta informació del telèfon"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notificacions"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Pot llegir totes les notificacions, inclosa informació com ara els contactes, els missatges i les fotos"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotos i contingut multimèdia"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Serveis de Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> demana permís en nom del teu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> per accedir a les fotos, el contingut multimèdia i les notificacions del telèfon"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositiu"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Això pot incloure accés al micròfon, a la càmera i a la ubicació, i altres permisos sensibles a &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Pots canviar aquests permisos en qualsevol moment a &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;, a Configuració.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Icona de l\'aplicació"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Botó Més informació"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telèfon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contactes"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Calendari"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositius propers"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotos i contingut multimèdia"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notificacions"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Aplicacions"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Pot accedir al teu número de telèfon i a la informació de la xarxa. Es requereix per fer trucades i VoIP, enviar missatges de veu, redirigir trucades i editar els registres de trucades."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Pot llegir, crear o editar la nostra llista de contactes i també accedir a la llista de tots els comptes que s\'utilitzen al teu dispositiu"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Pot llegir totes les notificacions, inclosa informació com ara els contactes, els missatges i les fotos"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Reprodueix en continu aplicacions del telèfon"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-cs/strings.xml b/packages/CompanionDeviceManager/res/values-cs/strings.xml
index 0d44528..fdf93d8 100644
--- a/packages/CompanionDeviceManager/res/values-cs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-cs/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Povolit aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; přístup k vašemu zařízení &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"hodinky"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Vyberte zařízení <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, které chcete spravovat pomocí aplikace &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Tato aplikace je nutná ke správě zařízení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikace <xliff:g id="APP_NAME">%2$s</xliff:g> bude moci interagovat s vašimi oznámeními a získá přístup k telefonu, SMS, kontaktům, kalendáři, seznamům hovorů a zařízením v okolí."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Aplikace"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Streamujte aplikace v telefonu"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Aplikace je nutná ke správě zařízení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikace <xliff:g id="APP_NAME">%2$s</xliff:g> bude moci interagovat s vašimi oznámeními a získá přístup k telefonu, SMS, kontaktům, kalendáři, seznamům hovorů a zařízením v okolí."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikace je nutná ke správě zařízení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikace <xliff:g id="APP_NAME">%2$s</xliff:g> bude moci interagovat s těmito oprávněními:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Povolte aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; přístup k těmto informacím z vašeho telefonu"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Služby pro více zařízení"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> požaduje za vaše zařízení <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> oprávnění ke streamování aplikací mezi zařízeními"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Povolte aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; přístup k těmto informacím z vašeho telefonu"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Oznámení"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Může číst veškerá oznámení včetně informací, jako jsou kontakty, zprávy a fotky"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotky a média"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Služby Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> požaduje za vaše zařízení <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> oprávnění k přístupu k fotkám, médiím a oznámením v telefonu"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"zařízení"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Může být zahrnut přístup k mikrofonu, fotoaparátu a poloze a další citlivá oprávnění na zařízení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Tato oprávnění můžete v Nastavení na zařízení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; kdykoliv změnit.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikace"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Tlačítko Další informace"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontakty"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalendář"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Zařízení v okolí"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotky a média"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Oznámení"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikace"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Má přístup k vašemu telefonnímu číslu a informacím o síti. Vyžadováno pro volání a VoIP, hlasové zprávy, přesměrování hovorů a úpravy seznamů hovorů."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Může načítat, vytvářet a upravovat váš seznam kontaktů a má přístup k seznamu všech účtů používaných v zařízení"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Může číst veškerá oznámení včetně informací, jako jsou kontakty, zprávy a fotky"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streamujte aplikace v telefonu"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-da/strings.xml b/packages/CompanionDeviceManager/res/values-da/strings.xml
index a043978..51fcc62 100644
--- a/packages/CompanionDeviceManager/res/values-da/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-da/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Tillad, at &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; får adgang til dit &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ur"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Vælg den enhed (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), som skal administreres af &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Du skal bruge denne app for at administrere dit <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tilladelse til at interagere med dine notifikationer og får adgang til dine tilladelser Opkald, Sms, Kalender, Opkaldshistorik og Enheder i nærheden."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Stream din telefons apps"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Du skal bruge denne app for at administrere <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tilladelse til at interagere med dine notifikationer og får adgang til dine tilladelser for Opkald, Sms, Kalender, Opkaldshistorik og Enheder i nærheden."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Du skal bruge denne app for at administrere <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får mulighed for at interagere med følgende tilladelser:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Giv &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; adgang til disse oplysninger fra din telefon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Tjenester, som kan tilsluttes en anden enhed"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> anmoder om tilladelse på vegne af din <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> til at streame apps mellem dine enheder"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Tillad, at &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; får adgang til disse oplysninger fra din telefon"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notifikationer"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Kan læse alle notifikationer, herunder oplysninger som f.eks. kontakter, beskeder og billeder"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Billeder og medier"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play-tjenester"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> anmoder om tilladelse på vegne af din <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> til at få adgang til din telefons billeder, medier og notifikationer"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"enhed"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Dette kan omfatte mikrofon-, kamera- og lokationsadgang samt andre tilladelser til at tilgå følsomme oplysninger på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Du kan til enhver tid ændre disse tilladelser under Indstillinger på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Appikon"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Knappen Flere oplysninger"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"Sms"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontakter"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalender"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Enheder i nærheden"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Billeder og medier"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notifikationer"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Har adgang til oplysninger om dit telefonnummer og netværk. Påkrævet for at foretage opkald og VoIP, talebeskeder, omdirigering og redigering af opkaldshistorikken"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Kan læse, oprette eller redigere din liste over kontakter samt tilgå lister for alle de konti, der bruges på din enhed"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Kan læse alle notifikationer, herunder oplysninger som f.eks. kontakter, beskeder og billeder"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream din telefons apps"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-de/strings.xml b/packages/CompanionDeviceManager/res/values-de/strings.xml
index 77fb0bc..8747256 100644
--- a/packages/CompanionDeviceManager/res/values-de/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-de/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; erlauben, auf dein Gerät (&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;) zuzugreifen"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"Smartwatch"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Gerät „<xliff:g id="PROFILE_NAME">%1$s</xliff:g>“ auswählen, das von &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; verwaltet werden soll"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Diese App wird zur Verwaltung des Geräts „<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“ benötigt. <xliff:g id="APP_NAME">%2$s</xliff:g> darf mit deinen Benachrichtigungen interagieren und auf die Berechtigungen für „Telefon“, „SMS“, „Kontakte“, „Kalender“, „Anrufliste“ und „Geräte in der Nähe“ zugreifen."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Smartphone-Apps streamen"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Die App wird zur Verwaltung deines Geräts (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) benötigt. <xliff:g id="APP_NAME">%2$s</xliff:g> darf mit deinen Benachrichtigungen interagieren und auf die Berechtigungen „Telefon“, „SMS“, „Kontakte“, „Kalender“, „Anrufliste“ und „Geräte in der Nähe“ zugreifen."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Die App wird zur Verwaltung deines Geräts (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) benötigt. <xliff:g id="APP_NAME">%2$s</xliff:g> darf mit diesen Berechtigungen interagieren:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; Zugriff auf diese Informationen von deinem Smartphone gewähren"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Geräteübergreifende Dienste"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> bittet für dein <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> um die Berechtigung zum Streamen von Apps zwischen deinen Geräten"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; Zugriff auf diese Informationen von deinem Smartphone gewähren"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Benachrichtigungen"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Kann alle Benachrichtigungen lesen, einschließlich Informationen wie Kontakten, Nachrichten und Fotos"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotos und Medien"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play-Dienste"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> bittet im Namen deines <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> um die Berechtigung zum Zugriff auf die Fotos, Medien und Benachrichtigungen deines Smartphones"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"Gerät"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Dazu können Berechtigungen für Mikrofon, Kamera und Standortzugriff sowie andere vertrauliche Berechtigungen auf &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; gehören.&lt;/p&gt;&lt;p&gt;Sie lassen sich jederzeit in den Einstellungen auf &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; ändern.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"App-Symbol"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Weitere-Infos-Schaltfläche"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontakte"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalender"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Geräte in der Nähe"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotos und Medien"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Benachrichtigungen"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Darf auf deine Telefonnummer und Netzwerkinformationen zugreifen. Erforderlich für normale und VoIP-Anrufe, Mailbox, Anrufweiterleitung und das Bearbeiten von Anruflisten"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Darf deine Kontaktliste lesen, erstellen oder bearbeiten sowie auf die Kontaktliste aller auf diesem Gerät verwendeten Konten zugreifen"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Kann alle Benachrichtigungen lesen, einschließlich Informationen wie Kontakten, Nachrichten und Fotos"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Smartphone-Apps streamen"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-el/strings.xml b/packages/CompanionDeviceManager/res/values-el/strings.xml
index 56d7dcd..ccd5611 100644
--- a/packages/CompanionDeviceManager/res/values-el/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-el/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Επιτρέψτε στην εφαρμογή &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; να έχει πρόσβαση στη συσκευή &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ρολόι"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Επιλέξτε ένα προφίλ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> για διαχείριση από την εφαρμογή &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Αυτή η εφαρμογή είναι απαραίτητη για τη διαχείριση της συσκευής <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Η εφαρμογή <xliff:g id="APP_NAME">%2$s</xliff:g> θα επιτρέπεται να αλληλεπιδρά με τις ειδοποιήσεις και να έχει πρόσβαση στις άδειες Τηλέφωνο, SMS, Επαφές, Ημερολόγιο, Αρχεία καταγραφής κλήσεων και Συσκευές σε κοντινή απόσταση."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Εφαρμογές"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Μεταδώστε σε ροή τις εφαρμογές του τηλεφώνου σας"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Η εφαρμογή είναι απαραίτητη για τη διαχείριση της συσκευής <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Η εφαρμογή <xliff:g id="APP_NAME">%2$s</xliff:g> θα επιτρέπεται να αλληλεπιδρά με τις ειδοποιήσεις και να έχει πρόσβαση στις άδειες Τηλέφωνο, SMS, Επαφές, Ημερολόγιο, Αρχεία καταγραφής κλήσεων και Συσκευές σε κοντινή απόσταση."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Η εφαρμογή είναι απαραίτητη για τη διαχείριση της συσκευής <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Η εφαρμογή <xliff:g id="APP_NAME">%2$s</xliff:g> θα επιτρέπεται να αλληλεπιδρά με τις εξής άδειες:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Να επιτρέπεται στο &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; η πρόσβαση σε αυτές τις πληροφορίες από το τηλέφωνό σας."</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Υπηρεσίες πολλών συσκευών"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> ζητά εκ μέρους της συσκευής σας <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> άδεια για ροή εφαρμογών μεταξύ των συσκευών σας"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Επιτρέψτε στην εφαρμογή &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; να έχει πρόσβαση σε αυτές τις πληροφορίες από το τηλέφωνό σας"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Ειδοποιήσεις"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Μπορεί να διαβάσει όλες τις ειδοποιήσεις, συμπεριλαμβανομένων πληροφοριών όπως επαφές, μηνύματα και φωτογραφίες"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Φωτογραφίες και μέσα"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Υπηρεσίες Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> ζητά εκ μέρους της συσκευής σας <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> άδεια για πρόσβαση στις φωτογραφίες, τα αρχεία μέσων και τις ειδοποιήσεις του τηλεφώνου σας"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"συσκευή"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Αυτές μπορεί να περιλαμβάνουν πρόσβαση σε μικρόφωνο, κάμερα και τοποθεσία και άλλες άδειες πρόσβασης σε ευαίσθητες πληροφορίες στη συσκευή &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Μπορείτε να αλλάξετε αυτές τις άδειες ανά πάσα στιγμή στις Ρυθμίσεις σας στη συσκευή &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Εικονίδιο εφαρμογής"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Κουμπί περισσότερων πληροφορ."</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Τηλέφωνο"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Επαφές"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Ημερολόγιο"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Συσκευές σε κοντινή απόσταση"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Φωτογραφίες και μέσα"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Ειδοποιήσεις"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Εφαρμογές"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Δυνατότητα πρόσβασης στον αριθμό τηλεφώνου σας και στις πληροφορίες δικτύου. Απαιτείται για την πραγματοποίηση κλήσεων και για υπηρεσίες VoIP, μηνύματα αυτόματου τηλεφωνητή, ανακατεύθυνση κλήσεων και επεξεργασία αρχείων καταγραφής κλήσεων"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Δυνατότητα ανάγνωσης, δημιουργίας ή επεξεργασίας της λίστας επαφών σας, καθώς και πρόσβασης στη λίστα επαφών όλων των λογαριασμών που χρησιμοποιούνται στη συσκευή σας"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Μπορεί να διαβάσει όλες τις ειδοποιήσεις, συμπεριλαμβανομένων πληροφοριών όπως επαφές, μηνύματα και φωτογραφίες"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Μεταδώστε σε ροή τις εφαρμογές του τηλεφώνου σας"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
index c80620e..1d7623f 100644
--- a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access your &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"This app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, calendar, call logs and Nearby devices permissions."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Stream your phone’s apps"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, calendar, call logs and Nearby devices permissions."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with these permissions:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream apps between your devices"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to access your phone’s photos, media and notifications"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;This may include microphone, camera and location access, and other sensitive permissions on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;You can change these permissions at any time in your settings on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"App icon"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"More information button"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Nearby devices"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Can access your phone number and network info. Required for making calls and VoIP, voicemail, call redirect and editing call logs"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Can read, create or edit our contact list, as well as access the list of all accounts used on your device"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream your phone’s apps"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
index c80620e..61ae537 100644
--- a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access your &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"This app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, calendar, call logs and Nearby devices permissions."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Stream your phone’s apps"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with these permissions:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream apps between your devices"</string>
@@ -30,19 +29,31 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to access your phone’s photos, media and notifications"</string>
+    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to access your phone’s photos, media, and notifications"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
     <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
-    <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+    <string name="consent_no" msgid="2640796915611404382">"Don’t allow"</string>
     <string name="consent_back" msgid="2560683030046918882">"Back"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Give apps on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; the same permissions as on &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;This may include microphone, camera and location access, and other sensitive permissions on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;You can change these permissions at any time in your settings on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"App icon"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"More information button"</string>
+    <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;This may include Microphone, Camera, and Location access, and other sensitive permissions on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;You can change these permissions any time in your Settings on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
+    <string name="vendor_icon_description" msgid="4445875290032225965">"App Icon"</string>
+    <string name="vendor_header_button_description" msgid="6566660389500630608">"More Information Button"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Nearby devices"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Can access your phone number and network info. Required for making calls and VoIP, voicemail, call redirect, and editing call logs"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Can read, create, or edit our contact list, as well as access the list of all accounts used on your device"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages, and photos"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream your phone’s apps"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
index c80620e..1d7623f 100644
--- a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access your &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"This app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, calendar, call logs and Nearby devices permissions."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Stream your phone’s apps"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, calendar, call logs and Nearby devices permissions."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with these permissions:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream apps between your devices"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to access your phone’s photos, media and notifications"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;This may include microphone, camera and location access, and other sensitive permissions on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;You can change these permissions at any time in your settings on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"App icon"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"More information button"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Nearby devices"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Can access your phone number and network info. Required for making calls and VoIP, voicemail, call redirect and editing call logs"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Can read, create or edit our contact list, as well as access the list of all accounts used on your device"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream your phone’s apps"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
index c80620e..1d7623f 100644
--- a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access your &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"This app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, calendar, call logs and Nearby devices permissions."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Stream your phone’s apps"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, calendar, call logs and Nearby devices permissions."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with these permissions:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream apps between your devices"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to access your phone’s photos, media and notifications"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;This may include microphone, camera and location access, and other sensitive permissions on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;You can change these permissions at any time in your settings on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"App icon"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"More information button"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Nearby devices"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Can access your phone number and network info. Required for making calls and VoIP, voicemail, call redirect and editing call logs"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Can read, create or edit our contact list, as well as access the list of all accounts used on your device"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream your phone’s apps"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
index 1b8833b..d40ac6a 100644
--- a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‎‏‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to access your &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;‎‏‎‎‏‎"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎watch‎‏‎‎‏‎"</string>
     <string name="chooser_title" msgid="2262294130493605839">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎Choose a ‎‏‎‎‏‏‎<xliff:g id="PROFILE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to be managed by &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;‎‏‎‎‏‎"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‎‎‎‎‏‎‎‏‏‎‎‎‎‏‎‏‏‎‎‏‎‏‏‎‏‎‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‎‎This app is needed to manage your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎. ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.‎‏‎‎‏‎"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‏‎‎‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‎‎Apps‎‏‎‎‏‎"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‎‏‎‏‏‎‎‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‎‏‏‏‎‏‎‎‏‎‎‏‏‏‎Stream your phone’s apps‎‏‎‎‏‎"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‎‎‎‏‎‏‏‎‏‏‎‏‏‎‏‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎The app is needed to manage your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎. ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.‎‏‎‎‏‎"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎The app is needed to manage your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎. ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to interact with these permissions:‎‏‎‎‏‎"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‏‏‎‎‎‏‎‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‏‎‎‏‏‎‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to access this information from your phone‎‏‎‎‏‎"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‎‎‎‎‏‎‏‎‏‎‎‏‎‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‎Cross-device services‎‏‎‎‏‎"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is requesting permission on behalf of your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ to stream apps between your devices‎‏‎‎‏‎"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‎‎‏‎‏‎‎‎‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‏‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to access this information from your phone‎‏‎‎‏‎"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‎‏‎‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎Notifications‎‏‎‎‏‎"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‏‏‏‎‎‎‏‏‎Can read all notifications, including information like contacts, messages, and photos‎‏‎‎‏‎"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‏‏‏‎Photos and media‎‏‎‎‏‎"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‏‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‏‏‎‎‏‎‏‏‏‎‏‎‏‎‎‏‎‏‎Google Play services‎‏‎‎‏‎"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‏‎‏‎‎‎‎‎‏‏‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is requesting permission on behalf of your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ to access your phone’s photos, media, and notifications‎‏‎‎‏‎"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‏‏‏‎‏‏‎‏‏‎‎‎device‎‏‎‎‏‎"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‎‏‎‏‎‎‏‏‏‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‏‏‎‎‎&lt;p&gt;This may include Microphone, Camera, and Location access, and other sensitive permissions on &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;You can change these permissions any time in your Settings on &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;.&lt;/p&gt;‎‏‎‎‏‎"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‏‎‏‏‎‎‎‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎App Icon‎‏‎‎‏‎"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‏‏‎‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎‎‎More Information Button‎‏‎‎‏‎"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎Phone‎‏‎‎‏‎"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‎‎‎‎‏‎‎SMS‎‏‎‎‏‎"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‏‎‎‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‎Contacts‎‏‎‎‏‎"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‎‏‎‏‎‎‏‎‏‏‏‎‏‎‏‎‏‏‏‎‏‏‎‏‏‏‏‎‎‎‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‏‎‏‏‎Calendar‎‏‎‎‏‎"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‎‎‎‎‏‎‎‏‎‎‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‎‎‏‎Nearby devices‎‏‎‎‏‎"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‏‏‏‎Photos and media‎‏‎‎‏‎"</string>
+    <string name="permission_notification" msgid="693762568127741203">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‎‏‎‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎Notifications‎‏‎‎‏‎"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‏‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‏‎‎‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‏‎‎Apps‎‏‎‎‏‎"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‏‎‏‎Can access your phone number and network info. Required for making calls and VoIP, voicemail, call redirect, and editing call logs‎‏‎‎‏‎"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‎‎‎‎‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‎Can read, create, or edit our contact list, as well as access the list of all accounts used on your device‎‏‎‎‏‎"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‏‏‏‎‎‎‏‏‎Can read all notifications, including information like contacts, messages, and photos‎‏‎‎‏‎"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎Stream your phone’s apps‎‏‎‎‏‎"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
index b7511ba..ef7e59d 100644
--- a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Permite que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a tu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"reloj"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para que la app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; lo administre"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Esta app es necesaria para administrar tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> podrá interactuar con tus notificaciones y acceder a los permisos de Teléfono, SMS, Contactos, Calendario, Llamadas y Dispositivos cercanos."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Transmitir las apps de tu teléfono"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Esta app es necesaria para administrar tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> podrá interactuar con tus notificaciones y acceder a los permisos de Teléfono, SMS, Contactos, Calendario, Llamadas y Dispositivos cercanos."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Esta app es necesaria para administrar tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> podrá interactuar con estos permisos:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permite que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información de tu teléfono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servicios multidispositivo"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicita tu permiso en nombre de <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para transmitir apps entre dispositivos"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Permite que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información de tu teléfono"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notificaciones"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Puede leer todas las notificaciones, incluso con información como contactos, mensajes y fotos"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotos y contenido multimedia"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Servicios de Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicita tu permiso en nombre de <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para acceder a las fotos, el contenido multimedia y las notificaciones de tu teléfono"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Esto puede incluir el acceso al micrófono, la cámara y la ubicación, así como otros permisos sensibles del dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Puedes cambiar estos permisos en cualquier momento en la Configuración del dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ícono de la app"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Botón Más información"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Teléfono"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contactos"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Calendario"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivos cercanos"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotos y contenido multimedia"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notificaciones"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Puede acceder a tu número de teléfono y a la información de la red (es obligatorio para realizar llamadas VoIP, enviar mensajes de voz, redireccionar llamadas y editar registros de llamadas)"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Puede leer, crear o editar la lista de contactos, además de acceder a la lista de contactos para todas las cuentas que se usan en tu dispositivo"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Puede leer todas las notificaciones, incluso con información como contactos, mensajes y fotos"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Transmitir las apps de tu teléfono"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-es/strings.xml b/packages/CompanionDeviceManager/res/values-es/strings.xml
index 11ed3c3..82ff28a 100644
--- a/packages/CompanionDeviceManager/res/values-es/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a tu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"reloj"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para gestionarlo con &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Se necesita esta aplicación para gestionar tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> podrá interactuar con tus notificaciones y acceder a tus permisos de teléfono, SMS, contactos, calendario, registros de llamadas y dispositivos cercanos."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Aplicaciones"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Proyecta aplicaciones de tu teléfono"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Se necesita la aplicación para gestionar tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> podrá interactuar con tus notificaciones y acceder a tus permisos de teléfono, SMS, contactos, calendario, registros de llamadas y dispositivos cercanos."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Se necesita la aplicación para gestionar tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Se permitirá que <xliff:g id="APP_NAME">%2$s</xliff:g> interaccione con los siguientes permisos:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información de tu teléfono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servicios multidispositivo"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pidiendo permiso en nombre de tu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para emitir aplicaciones en otros dispositivos tuyos"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información de tu teléfono"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notificaciones"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Puede leer todas las notificaciones, incluida información como contactos, mensajes y fotos"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotos y elementos multimedia"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Servicios de Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pidiendo permiso en nombre de tu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para acceder a las fotos, los archivos multimedia y las notificaciones de tu teléfono"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Esto puede incluir acceso al micrófono, la cámara y la ubicación, así como otros permisos sensibles de &lt;p&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Puedes cambiar estos permisos cuando quieras en los ajustes de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;."</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Icono de la aplicación"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Botón Más información"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Teléfono"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contactos"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Calendario"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivos cercanos"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotos y elementos multimedia"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notificaciones"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Aplicaciones"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Puede acceder a tu número de teléfono e información de red. Es necesario para hacer llamadas y VoIP, enviar mensajes de voz, redirigir llamadas y editar registros de llamadas"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Puede leer, crear o editar tu lista de contactos, así como acceder a la lista de contactos de todas las cuentas que se usan en tu dispositivo"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Puede leer todas las notificaciones, incluida información como contactos, mensajes y fotos"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Muestra en streaming las aplicaciones de tu teléfono"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-et/strings.xml b/packages/CompanionDeviceManager/res/values-et/strings.xml
index 40a55b5..ab42dda 100644
--- a/packages/CompanionDeviceManager/res/values-et/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-et/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Lubage rakendusel &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; teie seadmele &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; juurde pääseda"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"käekell"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Valige seade <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, mida haldab rakendus &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Seda rakendust on vaja teie profiili <xliff:g id="DEVICE_NAME">%1$s</xliff:g> haldamiseks. Rakendusel <xliff:g id="APP_NAME">%2$s</xliff:g> lubatakse kasutada teie märguandeid ja pääseda juurde teie telefoni, SMS-ide, kontaktide, kalendri, kõnelogide ja läheduses olevate seadmete lubadele."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Rakendused"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Telefoni rakenduste voogesitamine"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Seda rakendust on vaja teie seadme <xliff:g id="DEVICE_NAME">%1$s</xliff:g> haldamiseks. Rakendusel <xliff:g id="APP_NAME">%2$s</xliff:g> lubatakse kasutada teie märguandeid ning pääseda juurde teie telefoni, SMS-ide, kontaktide, kalendri, kõnelogide ja läheduses olevate seadmete lubadele."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Seda rakendust on vaja teie seadme <xliff:g id="DEVICE_NAME">%1$s</xliff:g> haldamiseks. Rakendusel <xliff:g id="APP_NAME">%2$s</xliff:g> lubatakse kasutada järgmisi lube."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Lubage rakendusel &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pääseda teie telefonis juurde sellele teabele"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Seadmeülesed teenused"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> taotleb teie seadme <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nimel luba teie seadmete vahel rakendusi voogesitada"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Lubage rakendusel &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pääseda teie telefonis juurde sellele teabele"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Märguanded"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Kõikide märguannete, sealhulgas teabe, nagu kontaktid, sõnumid ja fotod, lugemine"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotod ja meedia"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play teenused"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> taotleb teie seadme <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nimel luba pääseda juurde telefoni fotodele, meediale ja märguannetele"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"seade"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;See võib hõlmata mikrofoni, kaamerat ja juurdepääsu asukohale ning muid tundlikke lube seadmes &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Võite neid lube seadme &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; seadetes igal ajal muuta.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Rakenduse ikoon"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Nupp Lisateave"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontaktid"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalender"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Läheduses olevad seadmed"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotod ja meedia"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Märguanded"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Rakendused"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Pääseb juurde teie telefoninumbrile ja võrguteabele. Nõutav helistamiseks, VoIP-i ja kõneposti kasutamiseks, kõnede ümbersuunamiseks ning kõnelogide muutmiseks."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Saab lugeda, luua või muuta kontaktiloendit ja pääseda juurde kõigi teie seadmes kasutatavate kontode loendile"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Kõikide märguannete, sealhulgas teabe, nagu kontaktid, sõnumid ja fotod, lugemine"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefoni rakenduste voogesitamine"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-eu/strings.xml b/packages/CompanionDeviceManager/res/values-eu/strings.xml
index 83d0e02..66d433d 100644
--- a/packages/CompanionDeviceManager/res/values-eu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-eu/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Eman &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; atzitzeko baimena &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"erlojua"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Aukeratu &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; aplikazioak kudeatu beharreko <xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> kudeatzeko beharrezkoa da aplikazio hau. Jakinarazpenekin interakzioan aritzeko eta telefonoa, SMSak, kontaktuak, egutegia, deien erregistroa eta inguruko gailuak atzitzeko baimenak izango ditu <xliff:g id="APP_NAME">%2$s</xliff:g> aplikazioak."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Aplikazioak"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Igorri zuzenean telefonoko aplikazioak"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> kudeatzeko behar da aplikazioa. Jakinarazpenekin interakzioan aritzeko, eta telefonoa, SMSak, kontaktuak, egutegia, deien erregistroak eta inguruko gailuak erabiltzeko baimenak izango ditu <xliff:g id="APP_NAME">%2$s</xliff:g> aplikazioak."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> kudeatzeko behar da aplikazioa. Baimen hauek izango ditu <xliff:g id="APP_NAME">%2$s</xliff:g> aplikazioak:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Eman informazioa telefonotik hartzeko baimena &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Gailu baterako baino gehiagotarako zerbitzuak"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Gailu batetik bestera aplikazioak igortzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> gailuaren izenean"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Eman telefonoko informazio hau atzitzeko baimena &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Jakinarazpenak"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Jakinarazpen guztiak irakur ditzake; besteak beste, kontaktuak, mezuak, argazkiak eta antzeko informazioa"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Argazkiak eta multimedia-edukia"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Services"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Telefonoko argazkiak, multimedia-edukia eta jakinarazpenak atzitzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> gailuaren izenean"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"gailua"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Haien artean, baliteke &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; gailuaren mikrofonoa, kamera, kokapenerako sarbidea eta beste kontuzko baimen batzuk egotea.&lt;/p&gt; &lt;p&gt;Baimen horiek edonoiz alda ditzakezu &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; gailuaren ezarpenetan.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Aplikazioaren ikonoa"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Informazio gehiagorako botoia"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefonoa"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMSak"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontaktuak"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Egutegia"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Inguruko gailuak"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Argazkiak eta multimedia-edukia"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Jakinarazpenak"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikazioak"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Telefono-zenbakia eta sareari buruzko informazioa atzi ditzake. Dei arruntak eta VoIP bidezko deiak egiteko, erantzungailurako, deiak birbideratzeko aukerarako eta deien erregistroan editatzeko behar da."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Kontaktuen zerrenda irakurri, sortu edo edita dezake, baita kontuan erabilitako kontu guztien zerrenda atzitu ere"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Jakinarazpen guztiak irakur ditzake; besteak beste, kontaktuak, mezuak, argazkiak eta antzeko informazioa"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Igorri zuzenean telefonoko aplikazioak"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml
index 263f3ea61..b00cb5b 100644
--- a/packages/CompanionDeviceManager/res/values-fa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"‏به &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; اجازه دهید به &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; دسترسی داشته باشد"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ساعت"</string>
     <string name="chooser_title" msgid="2262294130493605839">"‏انتخاب <xliff:g id="PROFILE_NAME">%1$s</xliff:g> برای مدیریت کردن با &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>‏&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"این برنامه برای مدیریت <xliff:g id="DEVICE_NAME">%1$s</xliff:g> شما لازم است. <xliff:g id="APP_NAME">%2$s</xliff:g> می‌تواند با اعلان‌های شما تعامل داشته باشد و به اجازه‌های «تلفن»، «پیامک»، «مخاطبین»، «تقویم»، «گزارش‌های تماس» و «دستگاه‌های اطراف» دسترسی خواهد داشت."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"برنامه‌ها"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"جاری‌سازی برنامه‌های تلفن"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"این برنامه برای مدیریت <xliff:g id="DEVICE_NAME">%1$s</xliff:g> شما لازم است. <xliff:g id="APP_NAME">%2$s</xliff:g> می‌تواند با اعلان‌های شما تعامل داشته باشد و به اجازه‌های «تلفن»، «پیامک»، «مخاطبین»، «تقویم»، «گزارش‌های تماس» و «دستگاه‌های اطراف» دسترسی خواهد داشت."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"این برنامه برای مدیریت <xliff:g id="DEVICE_NAME">%1$s</xliff:g> شما لازم است. <xliff:g id="APP_NAME">%2$s</xliff:g> مجاز است با این اجازه‌ها تعامل داشته باشد:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‏اجازه دادن به &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; برای دسترسی به اطلاعات تلفن"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"سرویس‌های بین‌دستگاهی"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> اجازه می‌خواهد ازطرف <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> برنامه‌ها را بین دستگاه‌های شما جاری‌سازی کند"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"‏به &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; اجازه دسترسی به این اطلاعات در دستگاهتان داده شود"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"اعلان‌ها"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"می‌تواند همه اعلان‌ها، ازجمله اطلاعاتی مثل مخاطبین، پیام‌ها، و عکس‌ها را بخواند"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"عکس‌ها و رسانه‌ها"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"‏خدمات Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> اجازه می‌خواهد ازطرف <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> به عکس‌ها، رسانه‌ها، و اعلان‌های تلفن شما دسترسی پیدا کند"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"دستگاه"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"‏&lt;p&gt;این اجازه‌ها می‌تواند شامل دسترسی به «میکروفون»، «دوربین»، و «مکان»، و دیگر اجازه‌های حساس در &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; شود.&lt;/p&gt; &lt;p&gt;هروقت بخواهید می‌توانید این اجازه‌ها را در «تنظیمات» در &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; تغییر دهید.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"نماد برنامه"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"دکمه اطلاعات بیشتر"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"تلفن"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"پیامک"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"مخاطبین"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"تقویم"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"دستگاه‌های اطراف"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"عکس‌ها و رسانه‌ها"</string>
+    <string name="permission_notification" msgid="693762568127741203">"اعلان‌ها"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"برنامه‌ها"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"‏می‌تواند به شماره تلفن و اطلاعات شبکه‌تان دسترسی داشته باشد. برای برقراری تماس‌های تلفنی و VoIP، استفاده از پست صوتی، هدایت تماس، و ویرایش گزارش‌های تماس لازم است"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"می‌تواند فهرست مخاطبین ما را بخواند و ایجاد یا ویرایش کند و همچنین می‌تواند به فهرست همه حساب‌های مورداستفاده در دستگاهتان دسترسی داشته باشد"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"می‌تواند همه اعلان‌ها، ازجمله اطلاعاتی مثل مخاطبین، پیام‌ها، و عکس‌ها را بخواند"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"جاری‌سازی برنامه‌های تلفن"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fi/strings.xml b/packages/CompanionDeviceManager/res/values-fi/strings.xml
index 67252c5..d4136c7 100644
--- a/packages/CompanionDeviceManager/res/values-fi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fi/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Salli, että &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; saa pääsyn laitteeseesi: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"kello"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Valitse <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, jota &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; hallinnoi"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Profiilin (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) ylläpitoon tarvitaan tätä sovellusta. <xliff:g id="APP_NAME">%2$s</xliff:g> saa luvan hallinnoida ilmoituksiasi sekä pääsyn puhelimeen, tekstiviesteihin, yhteystietoihin, kalenteriin, puhelulokeihin ja lähellä olevat laitteet ‑lupiin."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Sovellukset"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Striimaa puhelimen sovelluksia"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Laitteen (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) ylläpitoon tarvitaan tätä sovellusta. <xliff:g id="APP_NAME">%2$s</xliff:g> saa luvan hallinnoida ilmoituksiasi sekä pääsyn puhelimeen, tekstiviesteihin, yhteystietoihin, kalenteriin, puhelulokeihin ja lähellä olevat laitteet ‑lupiin."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Laitteen (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) ylläpitoon tarvitaan tätä sovellusta. <xliff:g id="APP_NAME">%2$s</xliff:g> saa käyttää näitä lupia:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Salli, että &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; saa pääsyn näihin puhelimesi tietoihin"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Laitteidenväliset palvelut"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> pyytää laitteesi (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) puolesta lupaa striimata sovelluksia laitteidesi välillä"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Salli pääsy tähän tietoon puhelimellasi: &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Ilmoitukset"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Voi lukea kaikkia ilmoituksia, esim. kontakteihin, viesteihin ja kuviin liittyviä tietoja"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Kuvat ja media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Palvelut"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> pyytää laitteesi (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) puolesta lupaa päästä puhelimesi kuviin, mediaan ja ilmoituksiin"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"laite"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Tähän voi kuulua pääsy mikrofoniin, kameraan ja sijaintiin sekä muita arkaluontoisia lupia laitteella &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Voit muuttaa lupia asetuksista milloin tahansa laitteella &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Sovelluskuvake"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Lisätietopainike"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Puhelin"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"Tekstiviesti"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Yhteystiedot"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalenteri"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Lähellä olevat laitteet"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Kuvat ja media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Ilmoitukset"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Sovellukset"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Voi nähdä puhelinnumerosi ja verkon tiedot. Tätä tarvitaan puheluiden soittamiseen, VoIP:n, puhelinvastaajan ja puheluiden uudelleenohjauksen käyttämiseen sekä puhelulokien muokkaamiseen."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Voi luoda yhteystietolistan tai lukea tai muokata sitä sekä avata listan kaikilla tileillä, joita käytetään laitteellasi."</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Voi lukea kaikkia ilmoituksia, esim. kontakteihin, viesteihin ja kuviin liittyviä tietoja"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Striimaa puhelimen sovelluksia"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
index b85099a..7c17039 100644
--- a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à votre &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Choisissez un(e) <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré(e) par &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Cette application est nécessaire pour gérer votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> aura l\'autorisation d\'interagir avec vos notifications et d\'accéder aux autorisations suivantes : téléphone, messages texte, contacts, agenda, journaux d\'appels et appareils à proximité."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Applications"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Diffusez les applications de votre téléphone"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"L\'application est nécessaire pour gérer votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> aura l\'autorisation d\'interagir avec vos notifications et d\'accéder aux autorisations suivantes : téléphone, messages texte, contacts, agenda, journaux d\'appels et appareils à proximité."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"L\'application est nécessaire pour gérer votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> aura l\'autorisation d\'interagir avec ces autorisations :"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Autorisez &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à ces informations à partir de votre téléphone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Services multiappareils"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> pour diffuser des applications entre vos appareils"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Autorisez &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à ces informations à partir de votre téléphone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Peut lire toutes les notifications, y compris les renseignements tels que les contacts, les messages et les photos"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Photos et fichiers multimédias"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Services Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> pour accéder aux photos, aux fichiers multimédias et aux notifications de votre téléphone"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Cela peut comprendre l\'accès au microphone, à l\'appareil photo et à la position, ainsi que d\'autres autorisations sensibles sur &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Vous pouvez modifier ces autorisations en tout temps dans vos paramètres sur &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Icône de l\'application"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Bouton En savoir plus"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Téléphone"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"Messages texte"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Agenda"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Appareils à proximité"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Photos et fichiers multimédias"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Applications"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Peut accéder à votre numéro de téléphone et à vos renseignements de réseau. Ceci est nécessaire pour passer des appels téléphoniques et des appels voix sur IP, laisser un message vocal, rediriger les appels et modifier les journaux d\'appels"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Peut lire, créer ou modifier notre liste de contacts et accéder à la liste de tous les comptes utilisés sur votre appareil"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Peut lire toutes les notifications, y compris les renseignements tels que les contacts, les messages et les photos"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Diffusez les applications de votre téléphone"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml
index 8a13866..fde2322 100644
--- a/packages/CompanionDeviceManager/res/values-fr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à votre &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Sélectionnez le/la <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré(e) par &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Cette appli est nécessaire pour gérer votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> aura l\'autorisation d\'interagir avec vos notifications et d\'accéder au téléphone, aux SMS, aux contacts, à l\'agenda, aux journaux d\'appels et aux appareils à proximité."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Applis"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Diffuser en streaming les applis de votre téléphone"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Cette appli est nécessaire pour gérer votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> aura l\'autorisation d\'interagir avec vos notifications et d\'accéder au téléphone, aux SMS, aux contacts, à l\'agenda, aux journaux d\'appels et aux appareils à proximité."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Cette appli est nécessaire pour gérer votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> pourra interagir avec ces autorisations :"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à ces informations depuis votre téléphone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Services inter-appareils"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> pour caster des applis d\'un appareil à l\'autre"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à ces informations depuis votre téléphone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Peut lire toutes les notifications, y compris des informations comme les contacts, messages et photos"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Photos et contenus multimédias"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Services Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> pour accéder aux photos, contenus multimédias et notifications de votre téléphone"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Il peut s\'agir de l\'accès au micro, à l\'appareil photo et à la position, et d\'autres autorisations sensibles sur l\'appareil &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Vous pouvez modifier ces autorisations à tout moment dans les paramètres de l\'appareil &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Icône d\'application"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Bouton Plus d\'informations"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Téléphone"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Agenda"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Appareils à proximité"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Photos et contenus multimédias"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Applis"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Peut accéder à votre numéro de téléphone et aux informations réseau. Nécessaire pour passer des appels et pour VoIP, la messagerie vocale, la redirection d\'appels et la modification des journaux d\'appels."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Peut lire, créer ou modifier votre liste de contacts, et accéder à la liste de tous les comptes utilisés sur votre appareil"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Peut lire toutes les notifications, y compris des informations comme les contacts, messages et photos"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Diffuser en streaming les applis de votre téléphone"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-gl/strings.xml b/packages/CompanionDeviceManager/res/values-gl/strings.xml
index 8134e64..214c3f5 100644
--- a/packages/CompanionDeviceManager/res/values-gl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gl/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda ao teu dispositivo (&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;)"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"reloxo"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Escolle un perfil (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) para que o xestione a aplicación &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Esta aplicación é necesaria para xestionar o teu dispositivo (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>). <xliff:g id="APP_NAME">%2$s</xliff:g> poderá interactuar coas túas notificacións e acceder aos permisos do teu teléfono, das SMS, dos contactos, do calendario, dos rexistros de chamadas e dos dispositivos próximos."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Aplicacións"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Emite as aplicacións do teu teléfono"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"A aplicación é necesaria para xestionar o teu dispositivo (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>). <xliff:g id="APP_NAME">%2$s</xliff:g> poderá interactuar coas túas notificacións e acceder aos permisos do teu teléfono, das SMS, dos contactos, do calendario, dos rexistros de chamadas e dos dispositivos próximos."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"A aplicación é necesaria para xestionar o teu dispositivo (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>). <xliff:g id="APP_NAME">%2$s</xliff:g> poderá interactuar con estes permisos:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permitir que a aplicación &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información desde o teu teléfono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servizos multidispositivo"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> está solicitando permiso en nome do teu dispositivo (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) para emitir contido de aplicacións entre os teus aparellos"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información do teu teléfono"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notificacións"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificacións (que poden incluír información como contactos, mensaxes e fotos)"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotos e contido multimedia"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Servizos de Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> está solicitando permiso en nome do teu dispositivo (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) para acceder ás fotos, ao contido multimedia e ás notificacións do teléfono"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Con esta acción podes conceder acceso ao micrófono, á cámara e á localización, así como outros permisos de acceso á información confidencial de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Podes cambiar estes permisos en calquera momento na configuración de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Icona de aplicación"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Botón de máis información"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Teléfono"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contactos"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Calendario"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivos próximos"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotos e contido multimedia"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notificacións"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Aplicacións"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Pode acceder ao teu número de teléfono e á información de rede do dispositivo. Necesítase para facer chamadas, usar VoIP, acceder ao correo de voz, redirixir chamadas e modificar os rexistros de chamadas"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Pode ler, crear ou editar a túa lista de contactos, así como acceder á lista de todas as contas usadas no teu dispositivo"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificacións (que poden incluír información como contactos, mensaxes e fotos)"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Emite as aplicacións do teu teléfono"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-gu/strings.xml b/packages/CompanionDeviceManager/res/values-gu/strings.xml
index c6a8330..6f5ebea 100644
--- a/packages/CompanionDeviceManager/res/values-gu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gu/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"તમારા &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;ને ઍક્સેસ કરવાની &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ને મંજૂરી આપો"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"સ્માર્ટવૉચ"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; દ્વારા મેનેજ કરવા માટે કોઈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> પસંદ કરો"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"તમારી <xliff:g id="DEVICE_NAME">%1$s</xliff:g> મેનેજ કરવા માટે આ ઍપ જરૂરી છે. <xliff:g id="APP_NAME">%2$s</xliff:g>ને તમારા નોટિફિકેશન સાથે ક્રિયાપ્રતિક્રિયા કરવાની તેમજ તમારો ફોન, SMS, સંપર્કો, કૅલેન્ડર, કૉલ લૉગ અને નજીકનાં ડિવાઇસની પરવાનગીઓ ઍક્સેસ કરવાની મંજૂરી આપવામાં આવશે."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"ઍપ"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"તમારા ફોનની ઍપ સ્ટ્રીમ કરો"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"તમારી <xliff:g id="DEVICE_NAME">%1$s</xliff:g> મેનેજ કરવા માટે ઍપ જરૂરી છે. <xliff:g id="APP_NAME">%2$s</xliff:g>ને તમારા નોટિફિકેશન સાથે ક્રિયાપ્રતિક્રિયા કરવાની તેમજ તમારો ફોન, SMS, સંપર્કો, કૅલેન્ડર, કૉલ લૉગ અને નજીકના ડિવાઇસની પરવાનગીઓ ઍક્સેસ કરવાની મંજૂરી આપવામાં આવશે."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"તમારી <xliff:g id="DEVICE_NAME">%1$s</xliff:g> મેનેજ કરવા માટે ઍપ જરૂરી છે. <xliff:g id="APP_NAME">%2$s</xliff:g>ને આ પરવાનગીઓ સાથે ક્રિયાપ્રતિક્રિયા કરવાની મંજૂરી આપવામાં આવશે:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"તમારા ફોનમાંથી આ માહિતી ઍક્સેસ કરવા માટે, &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ને મંજૂરી આપો"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ક્રોસ-ડિવાઇસ સેવાઓ"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> તમારા <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> વતી તમારા ડિવાઇસ વચ્ચે ઍપ સ્ટ્રીમ કરવાની પરવાનગીની વિનંતી કરી રહી છે"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"તમારા ફોનમાંથી આ માહિતી ઍક્સેસ કરવા માટે, &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ને મંજૂરી આપો"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"નોટિફિકેશન"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"સંપર્કો, મેસેજ અને ફોટા જેવી માહિતી સહિતના બધા નોટિફિકેશન વાંચી શકે છે"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"ફોટા અને મીડિયા"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play સેવાઓ"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> તમારા <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> વતી તમારા ફોનના ફોટા, મીડિયા અને નોટિફિકેશન ઍક્સેસ કરવાની પરવાનગીની વિનંતી કરી રહી છે"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ડિવાઇસ"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;આમાં &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; પરના માઇક્રોફોન, કૅમેરા અને સ્થાનના ઍક્સેસ તથા અન્ય સંવેદનશીલ માહિતીની પરવાનગીઓ શામેલ હોઈ શકે છે.&lt;/p&gt; &lt;p&gt;તમે &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; પર તમારા સેટિંગમાં તમે કોઈપણ સમયે આ પરવાનગીઓને બદલી શકો છો.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"ઍપનું આઇકન"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"વધુ માહિતી માટેનું બટન"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"ફોન"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"સંપર્કો"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"કૅલેન્ડર"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"નજીકના ડિવાઇસ"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"ફોટા અને મીડિયા"</string>
+    <string name="permission_notification" msgid="693762568127741203">"નોટિફિકેશન"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"ઍપ"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"તમારો ફોન નંબર અને નેટવર્કની માહિતી ઍક્સેસ કરી શકે છે. કૉલ અને VoIP કૉલ, વૉઇસમેઇલ કરવા, કૉલ રીડાયરેક્ટ કરવા તથા કૉલ લૉગમાં ફેરફાર કરવા માટે જરૂરી છે"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"તમારી સંપર્ક સૂચિ વાંચી, બનાવી શકે છે અથવા તેમાં ફેરફાર કરી શકે છે તેમજ તમારા ડિવાઇસ પર ઉપયોગમાં લેવાતા બધા એકાઉન્ટની સંપર્ક સૂચિને ઍક્સેસ કરી શકે છે"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"સંપર્કો, મેસેજ અને ફોટા જેવી માહિતી સહિતના બધા નોટિફિકેશન વાંચી શકે છે"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"તમારા ફોનની ઍપ સ્ટ્રીમ કરો"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hi/strings.xml b/packages/CompanionDeviceManager/res/values-hi/strings.xml
index c4ca37c..978e333 100644
--- a/packages/CompanionDeviceManager/res/values-hi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hi/strings.xml
@@ -20,9 +20,10 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; को &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ऐक्सेस करने की अनुमति दें"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"स्मार्टवॉच"</string>
     <string name="chooser_title" msgid="2262294130493605839">"कोई <xliff:g id="PROFILE_NAME">%1$s</xliff:g> चुनें, ताकि उसे &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; की मदद से मैनेज किया जा सके"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"यह ऐप्लिकेशन, <xliff:g id="DEVICE_NAME">%1$s</xliff:g> को मैनेज करने के लिए ज़रूरी है. <xliff:g id="APP_NAME">%2$s</xliff:g> आपकी सूचनाओं पर कार्रवाई कर पाएगा. साथ ही, इसे आपके फ़ोन, एसएमएस, संपर्कों, कैलेंडर, कॉल लॉग, और आस-पास मौजूद डिवाइसों को ऐक्सेस करने की अनुमति मिल पाएगी."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"ऐप्लिकेशन"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"अपने फ़ोन के ऐप्लिकेशन को स्ट्रीम करें"</string>
+    <!-- no translation found for summary_watch (4085794790142204006) -->
+    <skip />
+    <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
+    <skip />
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; को अपने फ़ोन से यह जानकारी ऐक्सेस करने की अनुमति दें"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"क्रॉस-डिवाइस से जुड़ी सेवाएं"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> आपके <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> की ओर से, आपके डिवाइसों के बीच ऐप्लिकेशन को स्ट्रीम करने की अनुमति मांग रहा है"</string>
@@ -30,10 +31,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; को अपने फ़ोन से यह जानकारी ऐक्सेस करने की अनुमति दें"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"सूचनाएं"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"इससे सभी सूचनाएं देखी जा सकती हैं. इनमें संपर्क, मैसेज, और फ़ोटो जैसी जानकारी शामिल होती है"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"फ़ोटो और मीडिया"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> आपके <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> की ओर से, फ़ोन में मौजूद फ़ोटो, मीडिया, और सूचनाओं को ऐक्सेस करने की अनुमति मांग रहा है"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"डिवाइस"</string>
@@ -45,4 +42,29 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;इसमें &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; पर मौजूद माइक्रोफ़ोन, कैमरा, जगह की जानकारी को ऐक्सेस करने, और अन्य संवेदनशील जानकारी ऐक्सेस करने की अनुमतियां शामिल हो सकती हैं.&lt;/p&gt; &lt;p&gt;किसी भी समय &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; की सेटिंग में जाकर, इन अनुमतियों में बदलाव किए जा सकते हैं.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"ऐप्लिकेशन आइकॉन"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"ज़्यादा जानकारी वाला बटन"</string>
+    <!-- no translation found for permission_phone (2661081078692784919) -->
+    <skip />
+    <!-- no translation found for permission_sms (6337141296535774786) -->
+    <skip />
+    <!-- no translation found for permission_contacts (3858319347208004438) -->
+    <skip />
+    <!-- no translation found for permission_calendar (6805668388691290395) -->
+    <skip />
+    <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
+    <skip />
+    <string name="permission_storage" msgid="6831099350839392343">"फ़ोटो और मीडिया"</string>
+    <string name="permission_notification" msgid="693762568127741203">"सूचनाएं"</string>
+    <!-- no translation found for permission_app_streaming (6009695219091526422) -->
+    <skip />
+    <!-- no translation found for permission_phone_summary (6154198036705702389) -->
+    <skip />
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
+    <skip />
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"इससे सभी सूचनाएं देखी जा सकती हैं. इनमें संपर्क, मैसेज, और फ़ोटो जैसी जानकारी शामिल होती है"</string>
+    <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
+    <skip />
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hr/strings.xml b/packages/CompanionDeviceManager/res/values-hr/strings.xml
index 0c6d3a2..437ab99 100644
--- a/packages/CompanionDeviceManager/res/values-hr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hr/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Dopustite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa vašem uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"satom"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Odaberite profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Ta je aplikacija potrebna za upravljanje vašim uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će stupati u interakciju s vašim obavijestima i pristupati dopuštenjima za telefon, SMS-ove, kontakte, kalendar, zapisnike poziva i uređaje u blizini."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Aplikacije"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Streaming aplikacija vašeg telefona"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Aplikacija je potrebna za upravljanje vašim uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će stupati u interakciju s vašim obavijestima i pristupati dopuštenjima za telefon, SMS-ove, kontakte, kalendar, zapisnike poziva i uređaje u blizini."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikacija je potrebna za upravljanje vašim uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će stupati u interakciju s ovim dopuštenjima:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Omogućite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa informacijama s vašeg telefona"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Usluge na različitim uređajima"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahtijeva dopuštenje u ime vašeg uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> za emitiranje aplikacija između vaših uređaja"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Omogućite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa informacijama s vašeg telefona"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Obavijesti"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Može čitati sve obavijesti, uključujući informacije kao što su kontakti, poruke i fotografije"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotografije i mediji"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Usluge za Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahtijeva dopuštenje u ime vašeg uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> za pristup fotografijama, medijskim sadržajima i obavijestima na telefonu"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;To može uključivati pristup mikrofonu, kameri i lokaciji i druga dopuštenja za osjetljive podatke na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Ta dopuštenja uvijek možete promijeniti u postavkama na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacije"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Gumb Više informacija"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontakti"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalendar"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Uređaji u blizini"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotografije i mediji"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Obavijesti"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacije"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Može pristupati vašem broju telefona i podacima o mreži. Potrebno je za uspostavu poziva i VoIP, govornu poštu, preusmjeravanje poziva i uređivanje zapisnika poziva"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Može čitati, izrađivati ili uređivati vaš popis kontakata te pristupati popisu kontakata svih računa korištenih na vašem uređaju"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Može čitati sve obavijesti, uključujući informacije kao što su kontakti, poruke i fotografije"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streaming aplikacija vašeg telefona"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hu/strings.xml b/packages/CompanionDeviceManager/res/values-hu/strings.xml
index ac458b3..de7aac1 100644
--- a/packages/CompanionDeviceManager/res/values-hu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hu/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"A(z) &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; hozzáférésének engedélyezése a(z) &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; eszközhöz"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"óra"</string>
     <string name="chooser_title" msgid="2262294130493605839">"A(z) &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; alkalmazással kezelni kívánt <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiválasztása"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Szükség van erre az alkalmazásra a következő kezeléséhez: <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A(z) <xliff:g id="APP_NAME">%2$s</xliff:g> műveleteket végezhet majd az értesítésekkel, és hozzáférhet a telefonra, az SMS-ekre, a névjegyekre, a naptárra, a hívásnaplókra és a közeli eszközökre vonatkozó engedélyekhez."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Alkalmazások"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"A telefon alkalmazásainak streamelése"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Szükség van az alkalmazásra a következő kezeléséhez: <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A(z) <xliff:g id="APP_NAME">%2$s</xliff:g> műveleteket végezhet majd az értesítésekkel, és hozzáférhet a telefonra, az SMS-ekre, a névjegyekre, a naptárra, a hívásnaplókra és a közeli eszközökre vonatkozó engedélyekhez."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Szükség van az alkalmazásra a következő kezeléséhez: <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A(z) <xliff:g id="APP_NAME">%2$s</xliff:g> műveleteket végezhet majd ezekkel az engedélyekkel:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Engedélyezi a(z) „<xliff:g id="APP_NAME">%1$s</xliff:g>” alkalmazás számára az információhoz való hozzáférést a telefonról"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Többeszközös szolgáltatások"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> engedélyt kér a(z) <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nevében az alkalmazások eszközök közötti streameléséhez"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Engedélyezi a(z) „<xliff:g id="APP_NAME">%1$s</xliff:g>” alkalmazás számára az információhoz való hozzáférést a telefonról"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Értesítések"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Elolvashat minden értesítést, ideértve az olyan információkat, mint a névjegyek, az üzenetek és a fotók"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotók és médiatartalmak"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play-szolgáltatások"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> engedélyt kér a(z) <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nevében a telefonon tárolt fotókhoz, médiatartalmakhoz és értesítésekhez való hozzáféréshez"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"eszköz"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Ide tartozhatnak a mikrofonhoz, a kamerához és a helyhez való hozzáférések, valamint a(z) &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; eszközön érvényes egyéb, bizalmas adatokra vonatkozó hozzáférési engedélyek is.&lt;/p&gt; &lt;p&gt;Ezeket az engedélyeket bármikor módosíthatja a(z) &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; eszköz beállításai között.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Alkalmazás ikonja"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"További információ gomb"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Címtár"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Naptár"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Közeli eszközök"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotók és médiatartalmak"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Értesítések"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Alkalmazások"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Hozzáférhet telefonszámához és hálózati adataihoz. Hívások és VoIP indításához, hívásátirányításhoz és hívásnaplók szerkesztéséhez szükséges."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Olvashatja, létrehozhatja és szerkesztheti a névjegylistánkat, valamint hozzáférhet az eszközén használt összes fiók listájához"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Elolvashat minden értesítést, ideértve az olyan információkat, mint a névjegyek, az üzenetek és a fotók"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"A telefon alkalmazásainak streamelése"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hy/strings.xml b/packages/CompanionDeviceManager/res/values-hy/strings.xml
index b4b29cb..acbb453c1 100644
--- a/packages/CompanionDeviceManager/res/values-hy/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hy/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Թույլատրեք &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածին կառավարել ձեր &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; սարքը"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ժամացույց"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Ընտրեք <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ը, որը պետք է կառավարվի &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; հավելվածի կողմից"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Այս հավելվածն անհրաժեշտ է ձեր <xliff:g id="DEVICE_NAME">%1$s</xliff:g> սարքը կառավարելու համար։ <xliff:g id="APP_NAME">%2$s</xliff:g> հավելվածը կկարողանա փոխազդել ձեր ծանուցումների հետ և կստանա «Հեռախոս», «SMS», «Կոնտակտներ», «Օրացույց», «Կանչերի ցուցակ» և «Մոտակա սարքեր» թույլտվությունները։"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Հավելվածներ"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Հեռարձակել հեռախոսի հավելվածները"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Այս հավելվածն անհրաժեշտ է ձեր <xliff:g id="DEVICE_NAME">%1$s</xliff:g> պրոֆիլը կառավարելու համար։ <xliff:g id="APP_NAME">%2$s</xliff:g> հավելվածը կկարողանա փոխազդել ձեր ծանուցումների հետ և կստանա «Հեռախոս», «SMS», «Կոնտակտներ», «Օրացույց», «Կանչերի ցուցակ» և «Մոտակա սարքեր» թույլտվությունները։"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Այս հավելվածն անհրաժեշտ է ձեր <xliff:g id="DEVICE_NAME">%1$s</xliff:g> պրոֆիլը կառավարելու համար։ <xliff:g id="APP_NAME">%2$s</xliff:g> հավելվածին կթույլատրվի օգտվել այս թույլտվություններից․"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Թույլատրեք &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածին օգտագործել այս տեղեկությունները ձեր հեռախոսից"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Միջսարքային ծառայություններ"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը ձեր <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> սարքի անունից թույլտվություն է խնդրում՝ ձեր սարքերի միջև հավելվածներ հեռարձակելու համար"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Թույլատրեք &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածին օգտագործել այս տեղեկությունները ձեր հեռախոսից"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Ծանուցումներ"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Կարող է կարդալ բոլոր ծանուցումները, ներառյալ տեղեկությունները, օրինակ՝ կոնտակտները, հաղորդագրությունները և լուսանկարները"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Լուսանկարներ և մուլտիմեդիա"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play ծառայություններ"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը ձեր <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> սարքի անունից թույլտվություն է խնդրում՝ ձեր հեռախոսի լուսանկարները, մեդիաֆայլերն ու ծանուցումները տեսնելու համար"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"սարք"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Դրանք կարող են ներառել խոսափողի, տեսախցիկի և տեղադրության տվյալների օգտագործման թույլտվությունները, ինչպես նաև կոնֆիդենցիալ տեղեկությունների օգտագործման այլ թույլտվություններ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; սարքում։&lt;/p&gt; &lt;p&gt;Այդ թույլտվությունները ցանկացած ժամանակ կարելի է փոխել &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; սարքի ձեր կարգավորումներում։&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Հավելվածի պատկերակ"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"«Այլ տեղեկություններ» կոճակ"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Հեռախոս"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Կոնտակտներ"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Օրացույց"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Մոտակա սարքեր"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Լուսանկարներ և մուլտիմեդիա"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Ծանուցումներ"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Հավելվածներ"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Կարող է օգտագործել ձեր հեռախոսահամարը և ցանցի մասին տեղեկությունները։ Պահանջվում է սովորական և VoIP զանգեր կատարելու, ձայնային փոստի, զանգերի վերահասցեավորման և զանգերի մատյանները փոփոխելու համար"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Կարող է կարդալ, ստեղծել և փոփոխել կոնտակտների ցանկը, ինչպես նաև բացել ձեր սարքի բոլոր հաշիվների ցանկը"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Կարող է կարդալ բոլոր ծանուցումները, ներառյալ տեղեկությունները, օրինակ՝ կոնտակտները, հաղորդագրությունները և լուսանկարները"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Հեռարձակել հեռախոսի հավելվածները"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-in/strings.xml b/packages/CompanionDeviceManager/res/values-in/strings.xml
index 82fa00a..9c1c2e0 100644
--- a/packages/CompanionDeviceManager/res/values-in/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-in/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Izinkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"smartwatch"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk dikelola oleh &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Aplikasi ini diperlukan untuk mengelola <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> akan diizinkan berinteraksi dengan notifikasi dan mengakses izin Telepon, SMS, Kontak, Kalender, Log panggilan, dan Perangkat di sekitar."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Aplikasi"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Streaming aplikasi ponsel"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Aplikasi diperlukan untuk mengelola <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> akan diizinkan berinteraksi dengan notifikasi dan mengakses izin Telepon, SMS, Kontak, Kalender, Log panggilan, dan Perangkat di sekitar."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikasi diperlukan untuk mengelola <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> akan diizinkan berinteraksi dengan izin ini:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Izinkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; untuk mengakses informasi ini dari ponsel Anda"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Layanan lintas perangkat"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> meminta izin atas nama <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> untuk menstreaming aplikasi di antara perangkat Anda"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Izinkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses informasi ini dari ponsel Anda"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notifikasi"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Dapat membaca semua notifikasi, termasuk informasi seperti kontak, pesan, dan foto"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Foto dan media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Layanan Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> meminta izin atas nama <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> untuk mengakses foto, media, dan notifikasi ponsel Anda"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"perangkat"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Ini termasuk akses Mikrofon, Kamera, dan Lokasi, serta izin sensitif lainnya di &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Anda dapat mengubah izin ini kapan saja di Setelan pada &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ikon Aplikasi"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Tombol Informasi Lainnya"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telepon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontak"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalender"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Perangkat di sekitar"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Foto dan media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notifikasi"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikasi"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Dapat mengakses nomor telepon dan info jaringan. Diperlukan untuk melakukan panggilan dan VoIP, pesan suara, pengalihan panggilan, dan pengeditan log panggilan"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Dapat membaca, membuat, atau mengedit daftar kontak, serta mengakses daftar kontak untuk semua akun yang digunakan di perangkat"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Dapat membaca semua notifikasi, termasuk informasi seperti kontak, pesan, dan foto"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streaming aplikasi ponsel"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml
index 59f4f89..3f8c1c3 100644
--- a/packages/CompanionDeviceManager/res/values-is/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-is/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Veita &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aðgang að &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"úr"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Velja <xliff:g id="PROFILE_NAME">%1$s</xliff:g> sem &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; á að stjórna"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Þetta forrit er nauðsynlegt til að hafa umsjón með <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> fær aðgang að tilkynningum og heimildum síma, SMS, tengiliða, dagatals, símtalaskráa og nálægra tækja."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Forrit"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Streymdu forritum símans"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Forritið er nauðsynlegt til að hafa umsjón með <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> fær aðgang að tilkynningum og heimildum síma, SMS, tengiliða, dagatals, símtalaskráa og nálægra tækja."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Forritið er nauðsynlegt til að hafa umsjón með <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> fær aðgang að eftirfarandi heimildum:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Veita &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aðgang að þessum upplýsingum úr símanum þínum"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Þjónustur á milli tækja"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> sendir beiðni um heimild til straumspilunar forrita á milli tækjanna þinna fyrir hönd <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Veita &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aðgang að þessum upplýsingum úr símanum þínum"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Tilkynningar"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Getur lesið allar tilkynningar, þar á meðal upplýsingar á borð við tengiliði, skilaboð og myndir"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Myndir og efni"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Þjónusta Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> sendir beiðni um aðgang að myndum, margmiðlunarefni og tilkynningum símans þíns fyrir hönd <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"tæki"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Þetta kann að fela í sér aðgang að hljóðnema, myndavél og staðsetningu og aðrar heimildir fyrir viðkvæmu efni í &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Hægt er að breyta þessum heimildum hvenær sem er í stillingunum í &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Tákn forrits"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Hnappur fyrir upplýsingar"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Sími"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Tengiliðir"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Dagatal"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Nálæg tæki"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Myndir og efni"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Tilkynningar"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Forrit"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Fær aðgang að símanúmeri og netkerfisupplýsingum. Nauðsynlegt til að hringja símtöl og netsímtöl, fyrir talhólf, framsendingu símtala og breytingar símtalaskráa"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Getur lesið, búið til eða breytt tengiliðalista og fær auk þess aðgang að tengiliðalista allra reikninga sem eru notaðir í tækinu"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Getur lesið allar tilkynningar, þar á meðal upplýsingar á borð við tengiliði, skilaboð og myndir"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streymdu forritum símans"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-it/strings.xml b/packages/CompanionDeviceManager/res/values-it/strings.xml
index 793f0b8..1d53be9 100644
--- a/packages/CompanionDeviceManager/res/values-it/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-it/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Consenti all\'app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di accedere &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"orologio"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Scegli un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> da gestire con &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Questa app è necessaria per gestire il tuo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. L\'app <xliff:g id="APP_NAME">%2$s</xliff:g> potrà interagire con le tue notifiche e accedere alle autorizzazioni Telefono, SMS, Contatti, Calendar, Registri chiamate e Dispositivi nelle vicinanze."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"App"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Trasmetti in streaming le app del tuo telefono"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Questa app è necessaria per gestire <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> potrà interagire con le tue notifiche e accedere alle autorizzazioni Telefono, SMS, Contatti, Calendario, Registri chiamate e Dispositivi nelle vicinanze."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Questa app è necessaria per gestire <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> potrà interagire con le seguenti autorizzazioni:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Consenti a &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di accedere a queste informazioni dal tuo telefono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servizi cross-device"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> richiede per conto del tuo <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> l\'autorizzazione a trasmettere app in streaming tra i dispositivi"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Consenti a &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di accedere a questa informazione dal tuo telefono"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notifiche"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Puoi leggere tutte le notifiche, incluse le informazioni come contatti, messaggi e foto"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Foto e contenuti multimediali"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Services"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> richiede per conto del tuo <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> l\'autorizzazione ad accedere a foto, contenuti multimediali e notifiche del telefono"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Potrebbero essere incluse le autorizzazioni di accesso al microfono, alla fotocamera e alla posizione, nonché altre autorizzazioni sensibili su &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Puoi cambiare queste autorizzazioni in qualsiasi momento nelle Impostazioni su &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Icona dell\'app"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Pulsante Altre informazioni"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefono"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contatti"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Calendario"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivi nelle vicinanze"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Foto e contenuti multimediali"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notifiche"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"App"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Può accedere al tuo numero di telefono e alle informazioni della rete. Necessaria per chiamate e VoIP, segreteria, deviazione delle chiamate e modifica dei registri chiamate"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Può leggere, creare o modificare l\'elenco contatti, nonché accedere all\'elenco contatti di tutti gli account usati sul dispositivo"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Puoi leggere tutte le notifiche, incluse le informazioni come contatti, messaggi e foto"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Trasmetti in streaming le app del tuo telefono"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-iw/strings.xml b/packages/CompanionDeviceManager/res/values-iw/strings.xml
index b414125..a169c78 100644
--- a/packages/CompanionDeviceManager/res/values-iw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-iw/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"‏אישור לאפליקציה ‎&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&amp;g;‎‏ לגשת אל ‎&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;‎‏"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"שעון"</string>
     <string name="chooser_title" msgid="2262294130493605839">"‏בחירת <xliff:g id="PROFILE_NAME">%1$s</xliff:g> לניהול באמצעות &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"‏האפליקציה הזו נחוצה כדי לנהל את <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. האפליקציה <xliff:g id="APP_NAME">%2$s</xliff:g> תוכל לבצע פעולות בהתראות ותקבל הרשאות גישה לטלפון, ל-SMS לאנשי הקשר, ליומן, ליומני השיחות ולמכשירים בקרבת מקום."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"אפליקציות"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"שידור אפליקציות מהטלפון"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"‏האפליקציה הזו נחוצה כדי לנהל את <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. האפליקציה <xliff:g id="APP_NAME">%2$s</xliff:g> תוכל לבצע פעולות בהתראות ותקבל הרשאות גישה לטלפון, ל-SMS לאנשי הקשר, ליומן, ליומני השיחות ולמכשירים בקרבת מקום."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"האפליקציה הזו נחוצה כדי לנהל את <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. האפליקציה <xliff:g id="APP_NAME">%2$s</xliff:g> תוכל לקיים אינטראקציה עם ההרשאות הבאות:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‏מתן אישור לאפליקציה &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; לגשת למידע הזה מהטלפון שלך"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"שירותים למספר מכשירים"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מבקשת הרשאה עבור מכשיר <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> כדי לשדר אפליקציות בין המכשירים שלך"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"‏מתן אישור לאפליקציה &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; לגשת למידע הזה מהטלפון שלך"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"התראות"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"גישת קריאה לכל ההתראות, כולל מידע כמו אנשי קשר, הודעות ותמונות."</string>
-    <string name="permission_storage" msgid="6831099350839392343">"תמונות ומדיה"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Services"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מבקשת הרשאה עבור מכשיר <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> כדי לגשת לתמונות, למדיה ולהתראות בטלפון שלך"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"מכשיר"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"‏‎&lt;p&gt;‎‏ההרשאות עשויות לכלול גישה למיקרופון, למצלמה ולמיקום, וכן גישה למידע רגיש אחר ב-‎&lt;/strong&gt;‎‏<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>‎&lt;/strong&gt;.&lt;/p&amp;gt‎;‎ ‎&lt;p&gt;אפשר לשנות את ההרשאות האלה בכל שלב בהגדרות של‏ ‎&lt;strong&gt;‎‏<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>‏‎&lt;/strong&gt;.&lt;/p&gt;‎‏"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"סמל האפליקציה"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"לחצן מידע נוסף"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"טלפון"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"אנשי קשר"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"יומן"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"מכשירים בקרבת מקום"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"תמונות ומדיה"</string>
+    <string name="permission_notification" msgid="693762568127741203">"התראות"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"אפליקציות"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"‏אפשרות לגשת למספר הטלפון ופרטי הרשת שלך. הדבר נדרש לצורך ביצוע שיחות ו-VoIP, להודעה קולית, להפניית שיחות ולעריכת יומני שיחות"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"אפשרות לקרוא, ליצור או לערוך את רשימת אנשי הקשר שלנו, וגם לגשת לרשימה של כל החשבונות שבהם נעשה שימוש במכשיר שלך"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"גישת קריאה לכל ההתראות, כולל מידע כמו אנשי קשר, הודעות ותמונות."</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"שידור אפליקציות מהטלפון"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml
index a3cd4ee3..686e2dd 100644
--- a/packages/CompanionDeviceManager/res/values-ja/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; に &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; へのアクセスを許可"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ウォッチ"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; の管理対象となる<xliff:g id="PROFILE_NAME">%1$s</xliff:g>の選択"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"このアプリは<xliff:g id="DEVICE_NAME">%1$s</xliff:g>の管理に必要です。<xliff:g id="APP_NAME">%2$s</xliff:g> は、通知の使用と、電話、SMS、連絡先、カレンダー、通話履歴、付近のデバイスの権限へのアクセスが可能となります。"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"アプリ"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"スマートフォンのアプリをストリーミングします"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"このアプリは<xliff:g id="DEVICE_NAME">%1$s</xliff:g>の管理に必要です。<xliff:g id="APP_NAME">%2$s</xliff:g> は、通知の使用と、電話、SMS、連絡先、カレンダー、通話履歴、付近のデバイスの権限へのアクセスが可能となります。"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"このアプリは<xliff:g id="DEVICE_NAME">%1$s</xliff:g>の管理に必要です。<xliff:g id="APP_NAME">%2$s</xliff:g> は、次の権限の使用が可能となります。"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"スマートフォンのこの情報へのアクセスを &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; に許可"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"クロスデバイス サービス"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> が <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> に代わってデバイス間でアプリをストリーミングする権限をリクエストしています"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"スマートフォンのこの情報へのアクセスを &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; に許可"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"通知"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"連絡先、メッセージ、写真に関する情報を含め、すべての通知を読み取ることができます"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"写真とメディア"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play 開発者サービス"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> が <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> に代わってスマートフォンの写真、メディア、通知にアクセスする権限をリクエストしています"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"デバイス"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;これには、&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; のマイク、カメラ、位置情報へのアクセスや、その他の機密情報に関わる権限が含まれる可能性があります。&lt;/p&gt; &lt;p&gt;これらの権限は &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; の [設定] でいつでも変更できます。&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"アプリのアイコン"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"詳細情報ボタン"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"電話"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"連絡先"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"カレンダー"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"付近のデバイス"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"写真とメディア"</string>
+    <string name="permission_notification" msgid="693762568127741203">"通知"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"アプリ"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"電話番号とネットワーク情報にアクセスできます。電話と VoIP の発信、ボイスメールの送信、通話のリダイレクト、通話履歴の編集に必要です"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"連絡先リストの読み取り、作成、編集を行えるほか、デバイスで使用するすべてのアカウントのリストにアクセスできます"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"連絡先、メッセージ、写真に関する情報を含め、すべての通知を読み取ることができます"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"スマートフォンのアプリをストリーミングします"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ka/strings.xml b/packages/CompanionDeviceManager/res/values-ka/strings.xml
index dbb1760..b0ef1c3 100644
--- a/packages/CompanionDeviceManager/res/values-ka/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ka/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"დაუშვით &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>-ის&lt;/strong&gt;, წვდომა თქვენს &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>-ზე&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"საათი"</string>
     <string name="chooser_title" msgid="2262294130493605839">"აირჩიეთ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, რომელიც უნდა მართოს &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;-მა"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"ეს აპი საჭიროა თქვენი <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ს სამართავად. <xliff:g id="APP_NAME">%2$s</xliff:g> შეძლებს თქვენს შეტყობინებებთან ინტერაქციას და თქვენი ტელეფონის, SMS-ების, კონტაქტებისა, კალენდრის, ზარების ჟურნალისა და ახლომახლო მოწყობილობების ნებართვებზე წვდომას."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"აპები"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"თქვენი ტელეფონის აპების სტრიმინგი"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"ეს აპი საჭიროა, რომ მართოთ თქვენი <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> შეძლებს თქვენს შეტყობინებებთან ინტერაქციას და თქვენი ტელეფონის, SMS-ების, კონტაქტებისა, კალენდრის, ზარების ჟურნალისა და ახლომახლო მოწყობილობების ნებართვებზე წვდომას."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"ეს აპი საჭიროა, რომ მართოთ თქვენი <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> შეძლებს ინტერაქციას შემდეგი ნებართვებით:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"ნება დართეთ, რომ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; აპს ჰქონდეს ამ ინფორმაციაზე წვდომა თქვენი ტელეფონიდან"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"მოწყობილობათშორისი სერვისები"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ითხოვს უფლებას თქვენი <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-ის სახელით, რომ მოწყობილობებს შორის აპების სტრიმინგი შეძლოს"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"ნება დართეთ, რომ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; აპს ჰქონდეს ამ ინფორმაციაზე წვდომა თქვენი ტელეფონიდან"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"შეტყობინებები"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"შეუძლია წაიკითხოს ყველა შეტყობინება, მათ შორის ისეთი ინფორმაცია, როგორიცაა კონტაქტები, ტექსტური შეტყობინებები და ფოტოები"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"ფოტოები და მედია"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> ითხოვს უფლებას თქვენი <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-ის სახელით, რომ წვდომა ჰქონდეს თქვენი ტელეფონის ფოტოებზე, მედიასა და შეტყობინებებზე"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"მოწყობილობა"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;აღნიშნული შეიძლება მოიცავდეს მიკროფონზე, კამერასა და მდებარეობაზე წვდომას თუ სხვა ნებართვას სენსიტიურ ინფორმაციაზე &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;-ში.&lt;/p&gt; &lt;p&gt;ამ ნებართვების შეცვლა ნებისმიერ დროს შეგიძლიათ თქვენი პარამეტრებიდან &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;-ში.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"აპის ხატულა"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"დამატებითი ინფორმაციის ღილაკი"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"კონტაქტები"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"კალენდარი"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"ახლომახლო მოწყობილობები"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"ფოტოები და მედია"</string>
+    <string name="permission_notification" msgid="693762568127741203">"შეტყობინებები"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"აპები"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"შეუძლია თქვენი ტელეფონის ნომერსა და ქსელის ინფორმაციაზე წვდომა. საჭიროა ზარების განსახორციელებლად და VoIP-ისთვის, ხმოვანი ფოსტისთვის, ზარის გადამისამართებისა და ზარების ჟურნალების რედაქტირებისთვის"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"შეუძლია ჩვენი კონტაქტების სიის წაკითხვა, შექმნა ან რედაქტირება, ასევე, თქვენს მოწყობილობაზე გამოყენებული ყველა ანგარიშის კონტაქტების სიაზე წვდომა"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"შეუძლია წაიკითხოს ყველა შეტყობინება, მათ შორის ისეთი ინფორმაცია, როგორიცაა კონტაქტები, ტექსტური შეტყობინებები და ფოტოები"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"თქვენი ტელეფონის აპების სტრიმინგი"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-kk/strings.xml b/packages/CompanionDeviceManager/res/values-kk/strings.xml
index 0d92a97b..18ab580 100644
--- a/packages/CompanionDeviceManager/res/values-kk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kk/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; қолданбасына &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; құрылғысын пайдалануға рұқсат беру"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"сағат"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; арқылы басқарылатын <xliff:g id="PROFILE_NAME">%1$s</xliff:g> құрылғысын таңдаңыз"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Бұл қолданба <xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысын басқару үшін қажет. <xliff:g id="APP_NAME">%2$s</xliff:g> қолданбасына хабарландырулар жіберу, Телефон, SMS, Контактілер, Күнтізбе, Қоңырау журналдары қолданбаларын және маңайдағы құрылғыларды пайдалану рұқсаттары беріледі."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Қолданбалар"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Телефон қолданбаларын трансляциялайды."</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Қолданба <xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысын басқару үшін қажет. <xliff:g id="APP_NAME">%2$s</xliff:g> қолданбасына хабарландыруларға қатысты әрекет жасау, телефон, SMS, контактілер, күнтізбе, қоңырау журналдары қолданбаларын және маңайдағы құрылғыларды пайдалану рұқсаттары беріледі."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Қолданба <xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысын басқару үшін қажет. <xliff:g id="APP_NAME">%2$s</xliff:g> қолданбасына осы рұқсаттар беріледі:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; қолданбасына телефоныңыздағы осы ақпаратты пайдалануға рұқсат беріңіз."</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Аралық құрылғы қызметтері"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> атынан құрылғылар арасында қолданбалар трансляциялау үшін рұқсат сұрайды."</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; қолданбасына телефоныңыздағы осы ақпаратты пайдалануға рұқсат беріңіз."</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Хабарландырулар"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Барлық хабарландыруды, соның ішінде контактілер, хабарлар және фотосуреттер сияқты ақпаратты оқи алады."</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Фотосуреттер мен медиафайлдар"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play қызметтері"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> атынан телефондағы фотосуреттерді, медиафайлдар мен хабарландыруларды пайдалану үшін рұқсат сұрайды."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"құрылғы"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Оларға микрофонды, камераны және геодеректі пайдалану рұқсаттары, сондай-ақ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; құрылғысына берілетін басқа да құпия ақпарат рұқсаттары кіруі мүмкін.&lt;/p&gt; &lt;p&gt;Бұл рұқсаттарды кез келген уақытта &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; құрылғысындағы параметрлерден өзгерте аласыз.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Қолданба белгішесі"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"\"Қосымша ақпарат\" түймесі"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Контактілер"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Күнтізбе"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Маңайдағы құрылғылар"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Фотосуреттер мен медиафайлдар"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Хабарландырулар"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Қолданбалар"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Телефон нөміріңізге және желі ақпаратына қол жеткізе алады. Қоңырау шалу, VoIP, дауыстық хабар жіберу, қоңырау бағытын ауыстыру және қоңырау журналдарын өзгерту үшін қажет."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Контактілер тізімін оқуға, жасауға немесе өзгертуге, сондай-ақ құрылғыңызда қолданылатын барлық аккаунт тізімін пайдалануға рұқсат беріледі."</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Барлық хабарландыруды, соның ішінде контактілер, хабарлар және фотосуреттер сияқты ақпаратты оқи алады."</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Телефон қолданбаларын трансляциялайды."</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-km/strings.xml b/packages/CompanionDeviceManager/res/values-km/strings.xml
index 4d85cfd..c90b3a2 100644
--- a/packages/CompanionDeviceManager/res/values-km/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-km/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"អនុញ្ញាតឱ្យ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ចូលប្រើប្រាស់ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; របស់អ្នក"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"នាឡិកា"</string>
     <string name="chooser_title" msgid="2262294130493605839">"ជ្រើសរើស <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ដើម្បីឱ្យស្ថិតក្រោម​ការគ្រប់គ្រងរបស់ &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"ត្រូវការកម្មវិធីនេះ ដើម្បីគ្រប់គ្រង <xliff:g id="DEVICE_NAME">%1$s</xliff:g> របស់អ្នក។ <xliff:g id="APP_NAME">%2$s</xliff:g> នឹងត្រូវបានអនុញ្ញាតឱ្យ​ធ្វើអន្តរកម្មជាមួយ​ការជូនដំណឹងរបស់អ្នក និងចូលប្រើការអនុញ្ញាតទូរសព្ទ, SMS, ទំនាក់ទំនង, ប្រតិទិន, កំណត់​ហេតុ​ហៅ​ទូរសព្ទ និងឧបករណ៍នៅជិតរបស់អ្នក។"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"កម្មវិធី"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"ផ្សាយកម្មវិធីរបស់ទូរសព្ទអ្នក"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"ត្រូវការកម្មវិធីនេះ ដើម្បីគ្រប់គ្រង <xliff:g id="DEVICE_NAME">%1$s</xliff:g> របស់អ្នក។ <xliff:g id="APP_NAME">%2$s</xliff:g> នឹងត្រូវបានអនុញ្ញាតឱ្យ​ធ្វើអន្តរកម្មជាមួយ​ការជូនដំណឹងរបស់អ្នក និងចូលប្រើការអនុញ្ញាតទូរសព្ទ, SMS, ទំនាក់ទំនង, ប្រតិទិន, កំណត់​ហេតុ​ហៅ​ទូរសព្ទ និងឧបករណ៍នៅជិតរបស់អ្នក។"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"ត្រូវការកម្មវិធីនេះ ដើម្បីគ្រប់គ្រង <xliff:g id="DEVICE_NAME">%1$s</xliff:g> របស់អ្នក។ <xliff:g id="APP_NAME">%2$s</xliff:g> នឹងត្រូវបានអនុញ្ញាតឱ្យធ្វើអន្តរកម្មជាមួយការអនុញ្ញាតទាំងនេះ៖"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"អនុញ្ញាតឱ្យ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ចូលប្រើព័ត៌មាននេះពីទូរសព្ទរបស់អ្នក"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"សេវាកម្មឆ្លងកាត់ឧបករណ៍"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងស្នើសុំការអនុញ្ញាតជំនួសឱ្យ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> របស់អ្នក ដើម្បីបញ្ចាំងកម្មវិធីរវាងឧបករណ៍របស់អ្នក"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"អនុញ្ញាតឱ្យ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ចូលមើលព័ត៌មាននេះពីទូរសព្ទរបស់អ្នក"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"ការ​ជូនដំណឹង"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"អាចអាន​ការជូនដំណឹង​ទាំងអស់ រួមទាំង​ព័ត៌មាន​ដូចជាទំនាក់ទំនង សារ និងរូបថត"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"រូបថត និងមេឌៀ"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"សេវាកម្ម Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងស្នើសុំការអនុញ្ញាតជំនួសឱ្យ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> របស់អ្នក ដើម្បីចូលប្រើរូបថត មេឌៀ និងការជូនដំណឹងរបស់ទូរសព្ទអ្នក"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ឧបករណ៍"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;សកម្មភាពនេះ​អាចរួមបញ្ចូល​ការចូលប្រើ​ទីតាំង កាមេរ៉ា និងមីក្រូហ្វូន និងការអនុញ្ញាត​ដែលមានលក្ខណៈ​រសើបផ្សេងទៀត​នៅលើ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;។&lt;/p&gt; &lt;p&gt;អ្នកអាចប្ដូរ​ការអនុញ្ញាតទាំងនេះ​បានគ្រប់ពេលវេលា​នៅក្នុងការកំណត់​នៅលើ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;។&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"រូប​កម្មវិធី"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"ប៊ូតុងព័ត៌មានបន្ថែម"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"ទូរសព្ទ"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"ប្រតិទិន"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"ឧបករណ៍នៅជិត"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"រូបថត និងមេឌៀ"</string>
+    <string name="permission_notification" msgid="693762568127741203">"ការ​ជូនដំណឹង"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"កម្មវិធី"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"អាចចូលមើលលេខទូរសព្ទ និងព័ត៌មានបណ្ដាញរបស់អ្នក។ ត្រូវបានតម្រូវសម្រាប់ការហៅទូរសព្ទនិង VoIP, សារជាសំឡេង, ការបញ្ជូនបន្តការហៅទូរសព្ទ និងការកែកំណត់ហេតុហៅទូរសព្ទ"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"អាចអាន បង្កើត ឬកែបញ្ជីទំនាក់ទំនងរបស់យើង ក៏ដូចជាចូលប្រើបញ្ជីគណនីទាំងអស់ដែលត្រូវបានប្រើនៅលើឧបករណ៍របស់អ្នក"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"អាចអាន​ការជូនដំណឹង​ទាំងអស់ រួមទាំង​ព័ត៌មាន​ដូចជាទំនាក់ទំនង សារ និងរូបថត"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"ផ្សាយកម្មវិធីរបស់ទូរសព្ទអ្នក"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-kn/strings.xml b/packages/CompanionDeviceManager/res/values-kn/strings.xml
index a8a790a..f60f7bc 100644
--- a/packages/CompanionDeviceManager/res/values-kn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kn/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"ನಿಮ್ಮ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ಅನ್ನು ಪ್ರವೇಶಿಸಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಗೆ ಅನುಮತಿಸಿ"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ವೀಕ್ಷಿಸಿ"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ಮೂಲಕ ನಿರ್ವಹಿಸಬೇಕಾದ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"ನಿಮ್ಮ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. ಅನ್ನು ನಿರ್ವಹಿಸಲು ಈ ಆ್ಯಪ್‌ನ ಅಗತ್ಯವಿದೆ. ನಿಮ್ಮ ಅಧಿಸೂಚನೆಗಳೊಂದಿಗೆ ಸಂವಹನ ನಡೆಸಲು ಮತ್ತು ನಿಮ್ಮ ಫೋನ್, SMS, ಸಂಪರ್ಕಗಳು, Calendar, ಕರೆಯ ಲಾಗ್‌ಗಳು ಹಾಗೂ ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳ ಅನುಮತಿಗಳನ್ನು ಪ್ರವೇಶಿಸಲು <xliff:g id="APP_NAME">%2$s</xliff:g> ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"ಆ್ಯಪ್‌ಗಳು"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"ನಿಮ್ಮ ಫೋನ್‍ನ ಆ್ಯಪ್‌ಗಳನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಿ"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"ನಿಮ್ಮ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಅನ್ನು ನಿರ್ವಹಿಸಲು ಆ್ಯಪ್‌ನ ಅಗತ್ಯವಿದೆ. ನಿಮ್ಮ ಅಧಿಸೂಚನೆಗಳೊಂದಿಗೆ ಸಂವಹನ ನಡೆಸಲು ಮತ್ತು ನಿಮ್ಮ ಫೋನ್, SMS, ಸಂಪರ್ಕಗಳು, Calendar, ಕರೆಯ ಲಾಗ್‌ಗಳು ಹಾಗೂ ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳ ಅನುಮತಿಗಳನ್ನು ಪ್ರವೇಶಿಸಲು <xliff:g id="APP_NAME">%2$s</xliff:g> ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"ನಿಮ್ಮ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಅನ್ನು ನಿರ್ವಹಿಸಲು ಆ್ಯಪ್‌ನ ಅಗತ್ಯವಿದೆ. <xliff:g id="APP_NAME">%2$s</xliff:g> ಗೆ ಈ ಅನುಮತಿಗಳೊಂದಿಗೆ ಸಂವಹನ ನಡೆಸಲು ಅನುಮತಿಸಲಾಗುವುದು:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"ನಿಮ್ಮ ಫೋನ್ ಮೂಲಕ ಈ ಮಾಹಿತಿಯನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಗೆ ಅನುಮತಿಸಿ"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ಕ್ರಾಸ್-ಡಿವೈಸ್ ಸೇವೆಗಳು"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"ನಿಮ್ಮ ಸಾಧನಗಳ ನಡುವೆ ಆ್ಯಪ್‌ಗಳನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಲು ನಿಮ್ಮ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ನ ಪರವಾಗಿ <xliff:g id="APP_NAME">%1$s</xliff:g> ಅನುಮತಿಯನ್ನು ವಿನಂತಿಸಿಕೊಳ್ಳುತ್ತಿದೆ"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"ನಿಮ್ಮ ಫೋನ್ ಮೂಲಕ ಈ ಮಾಹಿತಿಯನ್ನು ಪ್ರವೇಶಿಸಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಗೆ ಅನುಮತಿಸಿ"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"ಅಧಿಸೂಚನೆಗಳು"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"ಸಂಪರ್ಕಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಫೋಟೋಗಳಂತಹ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಂತೆ ಎಲ್ಲಾ ಅಧಿಸೂಚನೆಗಳನ್ನು ಓದಬಹುದು"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"ಫೋಟೋಗಳು ಮತ್ತು ಮಾಧ್ಯಮ"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play ಸೇವೆಗಳು"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"ನಿಮ್ಮ ಫೋನ್‌ನ ಫೋಟೋಗಳು, ಮೀಡಿಯಾ ಮತ್ತು ಅಧಿಸೂಚನೆಗಳನ್ನು ಪ್ರವೇಶಿಸಲು ನಿಮ್ಮ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ನ ಪರವಾಗಿ <xliff:g id="APP_NAME">%1$s</xliff:g> ಅನುಮತಿಯನ್ನು ವಿನಂತಿಸಿಕೊಳ್ಳುತ್ತಿದೆ"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ಸಾಧನ"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;ಇದು &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; ನಲ್ಲಿನ ಮೈಕ್ರೊಫೋನ್, ಕ್ಯಾಮರಾ ಮತ್ತು ಸ್ಥಳ ಆ್ಯಕ್ಸೆಸ್ ಹಾಗೂ ಇತರ ಸೂಕ್ಷ್ಮ ಅನುಮತಿಗಳನ್ನು ಹೊಂದಿರಬಹುದು&lt;p&gt;&lt;/p&gt; &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; ನಲ್ಲಿನ ನಿಮ್ಮ ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ನೀವು ಈ ಅನುಮತಿಗಳನ್ನು ಯಾವಾಗ ಬೇಕಾದರೂ ಬದಲಾಯಿಸಬಹುದು.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"ಆ್ಯಪ್ ಐಕಾನ್"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"ಹೆಚ್ಚಿನ ಮಾಹಿತಿಯ ಬಟನ್"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"ಫೋನ್"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"ಸಂಪರ್ಕಗಳು"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳು"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"ಫೋಟೋಗಳು ಮತ್ತು ಮಾಧ್ಯಮ"</string>
+    <string name="permission_notification" msgid="693762568127741203">"ಅಧಿಸೂಚನೆಗಳು"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"ಆ್ಯಪ್‌ಗಳು"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"ನಿಮ್ಮ ಫೋನ್ ಸಂಖ್ಯೆ ಮತ್ತು ನೆಟ್‌ವರ್ಕ್ ಮಾಹಿತಿಯನ್ನು ಪ್ರವೇಶಿಸಬಹುದು. ಕರೆಗಳನ್ನು ಮಾಡಲು ಮತ್ತು VoIP, ಧ್ವನಿಮೇಲ್, ಕರೆ ಮರುನಿರ್ದೇಶನ ಮತ್ತು ಕರೆಯ ಲಾಗ್‌ಗಳನ್ನು ಎಡಿಟ್ ಮಾಡಲು ಅಗತ್ಯವಿದೆ"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"ನಮ್ಮ ಸಂಪರ್ಕ ಪಟ್ಟಿಯನ್ನು ಓದಬಹುದು, ರಚಿಸಬಹುದು ಅಥವಾ ಎಡಿಟ್ ಮಾಡಬಹುದು, ಹಾಗೆಯೇ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಬಳಸಲಾದ ಎಲ್ಲಾ ಖಾತೆಗಳ ಪಟ್ಟಿಯನ್ನು ಪ್ರವೇಶಿಸಬಹುದು"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"ಸಂಪರ್ಕಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಫೋಟೋಗಳಂತಹ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಂತೆ ಎಲ್ಲಾ ಅಧಿಸೂಚನೆಗಳನ್ನು ಓದಬಹುದು"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"ನಿಮ್ಮ ಫೋನ್‍ನ ಆ್ಯಪ್‌ಗಳನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಿ"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ko/strings.xml b/packages/CompanionDeviceManager/res/values-ko/strings.xml
index c78affa..3442ab2 100644
--- a/packages/CompanionDeviceManager/res/values-ko/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ko/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;에서 내 &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; 기기에 액세스하도록 허용"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"시계"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;에서 관리할 <xliff:g id="PROFILE_NAME">%1$s</xliff:g>을(를) 선택"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"이 앱은 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 프로필을 관리하는 데 필요합니다. <xliff:g id="APP_NAME">%2$s</xliff:g>에서 알림과 상호작용하고 내 전화, SMS, 연락처, Calendar, 통화 기록, 근처 기기에 대한 권한을 갖게 됩니다."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"앱"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"휴대전화의 앱을 스트리밍합니다."</string>
+    <string name="summary_watch" msgid="4085794790142204006">"이 앱은 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 기기를 관리하는 데 필요합니다. <xliff:g id="APP_NAME">%2$s</xliff:g>에서 알림과 상호작용하고 내 전화, SMS, 연락처, Calendar, 통화 기록, 근처 기기에 대한 권한을 갖게 됩니다."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"이 앱은 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 기기를 관리하는 데 필요합니다. <xliff:g id="APP_NAME">%2$s</xliff:g>에서 다음 권한과 상호작용할 수 있습니다."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;이 휴대전화의 이 정보에 액세스하도록 허용합니다."</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"교차 기기 서비스"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> 대신 기기 간에 앱을 스트리밍할 수 있는 권한을 요청하고 있습니다."</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; 앱이 휴대전화에서 이 정보에 액세스하도록 허용"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"알림"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"연락처, 메시지, 사진 등의 정보를 포함한 모든 알림을 읽을 수 있습니다."</string>
-    <string name="permission_storage" msgid="6831099350839392343">"사진 및 미디어"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play 서비스"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> 대신 휴대전화의 사진, 미디어, 알림에 액세스할 수 있는 권한을 요청하고 있습니다."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"기기"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;여기에는 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;에서 허용했던 마이크, 카메라, 위치 정보 액세스 권한 및 기타 민감한 권한이 포함될 수 있습니다.&lt;/p&gt; &lt;p&gt;언제든지 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;의 설정에서 이러한 권한을 변경할 수 있습니다.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"앱 아이콘"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"추가 정보 버튼"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"전화"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"연락처"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"캘린더"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"근처 기기"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"사진 및 미디어"</string>
+    <string name="permission_notification" msgid="693762568127741203">"알림"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"앱"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"전화번호 및 네트워크 정보에 액세스할 수 있습니다. 전화 걸기 및 VoIP, 음성사서함, 통화 리디렉션, 통화 기록 수정 시 필요합니다."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"연락처 목록을 읽고, 만들고, 수정할 수 있으며 기기에서 사용되는 모든 계정의 목록에도 액세스할 수 있습니다."</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"연락처, 메시지, 사진 등의 정보를 포함한 모든 알림을 읽을 수 있습니다."</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"휴대전화의 앱을 스트리밍합니다."</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml
index ead2037..36a72ff 100644
--- a/packages/CompanionDeviceManager/res/values-ky/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; колдонмосуна &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; түзмөгүңүзгө кирүүгө уруксат бериңиз"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"саат"</string>
     <string name="chooser_title" msgid="2262294130493605839">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; тарабынан башкарылсын"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Бул колдонмо <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүңүздү башкаруу үчүн керек. <xliff:g id="APP_NAME">%2$s</xliff:g> билдирмелериңизди көрүп, телефонуңуз, SMS билдирүүлөр, байланыштар, жылнаама, чалуулар тизмеси жана жакын жердеги түзмөктөргө болгон уруксаттарды пайдалана алат."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Колдонмолор"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Телефондогу колдонмолорду алып ойнотуу"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Бул колдонмо <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүңүздү тескөө үчүн керек. <xliff:g id="APP_NAME">%2$s</xliff:g> билдирмелериңизди көрүп, телефонуңуз, SMS билдирүүлөр, байланыштар, жылнаама, чалуулар тизмеси жана жакын жердеги түзмөктөргө болгон уруксаттарды пайдалана алат."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Бул колдонмо <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүңүздү тескөө үчүн керек. <xliff:g id="APP_NAME">%2$s</xliff:g> төмөнкү уруксаттарды пайдалана алат:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; колдонмосуна телефонуңуздагы ушул маалыматты көрүүгө уруксат бериңиз"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Түзмөктөр аралык кызматтар"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> түзмөгүңүздүн атынан түзмөктөрүңүздүн ортосунда колдонмолорду өткөрүүгө уруксат сурап жатат"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; колдонмосуна телефонуңуздагы ушул маалыматты көрүүгө уруксат бериңиз"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Билдирмелер"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Бардык билдирмелерди, анын ичинде байланыштар, билдирүүлөр жана сүрөттөр сыяктуу маалыматты окуй алат"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Сүрөттөр жана медиафайлдар"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play кызматтары"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> түзмөгүңүздүн атынан телефондогу сүрөттөрдү, медиа файлдарды жана билдирмелерди колдонууга уруксат сурап жатат"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"түзмөк"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Бул уруксаттарга микрофонго, камерага жана жайгашкан жерге кирүү мүмкүнчүлүгү жана &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; түзмөгүндөгү башка купуя маалыматты көрүүгө уруксаттар кириши мүмкүн.&lt;/p&gt; &lt;p&gt;Бул уруксаттарды каалаган убакта &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; түзмөгүндөгү Жөндөөлөрдөн өзгөртө аласыз.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Колдонмонун сүрөтчөсү"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Дагы маалымат баскычы"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Байланыштар"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Жылнаама"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Жакын жердеги түзмөктөр"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Сүрөттөр жана медиафайлдар"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Билдирмелер"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Колдонмолор"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Телефонуңуздун номерин жана тармак тууралуу маалыматты көрө алат. Чалуу жана VoIP, үн почтасы, чалууларды багыттоо жана чалуулар тизмесин түзөтүү үчүн талап кылынат"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Байланыштар тизмесин окуп, түзүп же түзөтүп жана түзмөгүңүздөгү бардык аккаунттардагы тизмелерге кире алат"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Бардык билдирмелерди, анын ичинде байланыштар, билдирүүлөр жана сүрөттөр сыяктуу маалыматты окуй алат"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Телефондогу колдонмолорду алып ойнотуу"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-lo/strings.xml b/packages/CompanionDeviceManager/res/values-lo/strings.xml
index 6a9197e..9283eb7 100644
--- a/packages/CompanionDeviceManager/res/values-lo/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lo/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"ອະນຸຍາດ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ໃຫ້ເຂົ້າເຖິງ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ຂອງທ່ານໄດ້"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ໂມງ"</string>
     <string name="chooser_title" msgid="2262294130493605839">"ເລືອກ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ເພື່ອໃຫ້ຖືກຈັດການໂດຍ &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"ຕ້ອງໃຊ້ແອັບນີ້ເພື່ອຈັດການ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ຂອງທ່ານ. <xliff:g id="APP_NAME">%2$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ໂຕ້ຕອບກັບການແຈ້ງເຕືອນຂອງທ່ານ ແລະ ເຂົ້າເຖິງການອະນຸຍາດໂທລະສັບ, SMS, ລາຍຊື່ຜູ້ຕິດຕໍ່, ປະຕິທິນ, ບັນທຶກການໂທ ແລະ ອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງຂອງທ່ານ."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"ແອັບ"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"ສະຕຣີມແອັບຂອງໂທລະສັບທ່ານ"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"ຕ້ອງໃຊ້ແອັບດັ່ງກ່າວເພື່ອຈັດການ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ຂອງທ່ານ. <xliff:g id="APP_NAME">%2$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ໂຕ້ຕອບກັບການແຈ້ງເຕືອນຂອງທ່ານ ແລະ ເຂົ້າເຖິງການອະນຸຍາດໂທລະສັບ, SMS, ລາຍຊື່ຜູ້ຕິດຕໍ່, ປະຕິທິນ, ບັນທຶກການໂທ ແລະ ອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງຂອງທ່ານ."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"ຕ້ອງໃຊ້ແອັບດັ່ງກ່າວເພື່ອຈັດການ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ຂອງທ່ານ. <xliff:g id="APP_NAME">%2$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ໂຕ້ຕອບກັບການອະນຸຍາດເຫຼົ່ານີ້:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"ອະນຸຍາດ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ໃຫ້ເຂົ້າເຖິງຂໍ້ມູນນີ້ຈາກໂທລະສັບຂອງທ່ານໄດ້"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ບໍລິການຂ້າມອຸປະກອນ"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງຮ້ອງຂໍການອະນຸຍາດໃນນາມຂອງ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ເພື່ອສະຕຣີມແອັບລະຫວ່າງອຸປະກອນຂອງທ່ານ"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"ອະນຸຍາດ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ໃຫ້ເຂົ້າເຖິງຂໍ້ມູນນີ້ຈາກໂທລະສັບຂອງທ່ານໄດ້"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"ການແຈ້ງເຕືອນ"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"ສາມາດອ່ານການແຈ້ງເຕືອນທັງໝົດ, ຮວມທັງຂໍ້ມູນ ເຊັ່ນ: ລາຍຊື່ຜູ້ຕິດຕໍ່, ຂໍ້ຄວາມ ແລະ ຮູບພາບ"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"ຮູບພາບ ແລະ ມີເດຍ"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"ບໍລິການ Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງຮ້ອງຂໍການອະນຸຍາດໃນນາມຂອງ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ເພື່ອເຂົ້າເຖິງຮູບພາບ, ມີເດຍ ແລະ ການແຈ້ງເຕືອນຂອງໂທລະສັບທ່ານ"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ອຸປະກອນ"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;ນີ້ອາດຮວມສິດເຂົ້າເຖິງໄມໂຄຣໂຟນ, ກ້ອງຖ່າຍຮູບ ແລະ ສະຖານທີ່, ຮວມທັງການອະນຸຍາດທີ່ລະອຽດອ່ອນອື່ນໆຢູ່ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;ທ່ານສາມາດປ່ຽນການອະນຸຍາດເຫຼົ່ານີ້ຕອນໃດກໍໄດ້ໃນການຕັ້ງຄ່າຂອງທ່ານຢູ່ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"ໄອຄອນແອັບ"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"ປຸ່ມຂໍ້ມູນເພີ່ມເຕີມ"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"ໂທລະສັບ"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"ລາຍຊື່ຜູ້ຕິດຕໍ່"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"ປະຕິທິນ"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"ອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງ"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"ຮູບພາບ ແລະ ມີເດຍ"</string>
+    <string name="permission_notification" msgid="693762568127741203">"ການແຈ້ງເຕືອນ"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"ແອັບ"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"ສາມາດເຂົ້າເຖິງເບີໂທລະສັບ ແລະ ຂໍ້ມູນເຄືອຂ່າຍຂອງທ່ານ. ເຊິ່ງຈຳເປັນສຳລັບການໂທ ແລະ VoIP, ຂໍ້ຄວາມສຽງ, ການປ່ຽນເສັ້ນທາງການໂທ ແລະ ການແກ້ໄຂບັນທຶກການໂທ"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"ສາມາດອ່ານ, ສ້າງ ຫຼື ແກ້ໄຂລາຍຊື່ຜູ້ຕິດຕໍ່ຂອງພວກເຮົາ, ຮວມທັງເຂົ້າເຖິງລາຍຊື່ຂອງບັນຊີທັງໝົດທີ່ໃຊ້ຢູ່ໃນອຸປະກອນຂອງທ່ານ"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"ສາມາດອ່ານການແຈ້ງເຕືອນທັງໝົດ, ຮວມທັງຂໍ້ມູນ ເຊັ່ນ: ລາຍຊື່ຜູ້ຕິດຕໍ່, ຂໍ້ຄວາມ ແລະ ຮູບພາບ"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"ສະຕຣີມແອັບຂອງໂທລະສັບທ່ານ"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-lt/strings.xml b/packages/CompanionDeviceManager/res/values-lt/strings.xml
index f2cbfa0..3483cf3 100644
--- a/packages/CompanionDeviceManager/res/values-lt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lt/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Leisti &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pasiekti jūsų &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"laikrodį"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Jūsų <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, kurį valdys &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; (pasirinkite)"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Ši programa reikalinga norint tvarkyti jūsų „<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“. „<xliff:g id="APP_NAME">%2$s</xliff:g>“ bus leidžiama sąveikauti su pranešimų funkcija ir pasiekti telefono, SMS, Kontaktų, Kalendoriaus, Skambučių žurnalų ir įrenginių netoliese leidimus."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Programos"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Telefono programų perdavimas srautu"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Programa reikalinga norint tvarkyti jūsų <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. „<xliff:g id="APP_NAME">%2$s</xliff:g>“ bus leidžiama sąveikauti su pranešimų funkcija ir pasiekti Telefono, SMS, Kontaktų, Kalendoriaus, Skambučių žurnalų ir Įrenginių netoliese leidimus."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Programa reikalinga norint tvarkyti jūsų <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. „<xliff:g id="APP_NAME">%2$s</xliff:g>“ bus leidžiama sąveikauti su toliau nurodytais leidimais."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Leisti &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pasiekti šią informaciją iš jūsų telefono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Pasl. keliuose įrenginiuose"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ prašo leidimo jūsų „<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>“ vardu, kad galėtų srautu perduoti programas iš vieno įrenginio į kitą"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Leisti &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pasiekti šią informaciją iš jūsų telefono"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Pranešimai"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Galima skaityti visus pranešimus, įskaitant tokią informaciją kaip kontaktai, pranešimai ir nuotraukos"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Nuotraukos ir medija"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"„Google Play“ paslaugos"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ prašo leidimo jūsų „<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>“ vardu, kad galėtų pasiekti telefono nuotraukas, mediją ir pranešimus"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"įrenginys"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Gali būti įtraukti prieigos prie mikrofono, kameros ir vietovės leidimai ir kiti leidimai pasiekti neskelbtiną informaciją &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; įrenginyje.&lt;/p&gt; &lt;p&gt;Šiuos leidimus galite bet kada pakeisti „Nustatymų“ skiltyje &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; įrenginyje.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Programos piktograma"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Mygtukas „Daugiau informacijos“"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefonas"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontaktai"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalendorius"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Įrenginiai netoliese"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Nuotraukos ir medija"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Pranešimai"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Programos"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Galima pasiekti telefono numerio ir tinklo informaciją. Reikalinga norint skambinti ir naudoti „VoIP“, balso paštą, skambučių peradresavimo funkciją bei redaguoti skambučių žurnalus"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Galima skaityti, kurti ar redaguoti kontaktų sąrašą bei pasiekti visų įrenginyje naudojamų paskyrų sąrašą"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Galima skaityti visus pranešimus, įskaitant tokią informaciją kaip kontaktai, pranešimai ir nuotraukos"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefono programų perdavimas srautu"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-lv/strings.xml b/packages/CompanionDeviceManager/res/values-lv/strings.xml
index e8947c7..039cc4d 100644
--- a/packages/CompanionDeviceManager/res/values-lv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lv/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Atļauja lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; piekļūt jūsu ierīcei &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"pulkstenis"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Profila (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) izvēle, ko pārvaldīt lietotnē &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Šī lietotne ir nepieciešama jūsu ierīces (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) pārvaldībai. Lietotnei <xliff:g id="APP_NAME">%2$s</xliff:g> tiks atļauts mijiedarboties ar jūsu paziņojumiem un piekļūt šādām atļaujām: Tālrunis, Īsziņas, Kontaktpersonas, Kalendārs, Zvanu žurnāli un Tuvumā esošas ierīces."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Lietotnes"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Var straumēt jūsu tālruņa lietotnes"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Šī lietotne ir nepieciešama jūsu ierīces (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) pārvaldībai. Lietotnei <xliff:g id="APP_NAME">%2$s</xliff:g> tiks atļauts mijiedarboties ar jūsu paziņojumiem un piekļūt šādām atļaujām: Tālrunis, Īsziņas, Kontaktpersonas, Kalendārs, Zvanu žurnāli un Tuvumā esošas ierīces."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Šī lietotne ir nepieciešama jūsu ierīces (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) pārvaldībai. Lietotnei <xliff:g id="APP_NAME">%2$s</xliff:g> tiks atļauts mijiedarboties ar šīm atļaujām:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Atļaut lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; piekļūt šai informācijai no jūsu tālruņa"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Vairāku ierīču pakalpojumi"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> pieprasa atļauju straumēt lietotnes starp jūsu ierīcēm šīs ierīces vārdā: <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Atļaut lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; piekļūt šai informācijai no jūsu tālruņa"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Paziņojumi"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Var lasīt visus paziņojumus, tostarp tādu informāciju kā kontaktpersonas, ziņojumi un fotoattēli."</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotoattēli un multivides faili"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play pakalpojumi"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> pieprasa atļauju piekļūt jūsu tālruņa fotoattēliem, multivides saturam un paziņojumiem šīs ierīces vārdā: <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ierīce"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Tās var būt mikrofona, kameras, atrašanās vietas piekļuves atļaujas un citas sensitīvas atļaujas ierīcē &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Atļaujas jebkurā brīdī varat mainīt ierīces &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; iestatījumos."</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Lietotnes ikona"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Plašākas informācijas poga"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Tālrunis"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"Īsziņas"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontaktpersonas"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalendārs"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Tuvumā esošas ierīces"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotoattēli un multivides faili"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Paziņojumi"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Lietotnes"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Var piekļūt jūsu tālruņa numuram un tīkla informācijai. Nepieciešama, lai veiktu zvanus un VoIP zvanus, izmantotu balss pastu, pāradresētu zvanus un rediģētu zvanu žurnālus."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Var lasīt, izveidot vai rediģēt jūsu kontaktpersonu sarakstu, kā arī piekļūt visu jūsu ierīcē izmantoto kontu kontaktpersonu sarakstiem"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Var lasīt visus paziņojumus, tostarp tādu informāciju kā kontaktpersonas, ziņojumi un fotoattēli."</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Straumēt jūsu tālruņa lietotnes"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-mk/strings.xml b/packages/CompanionDeviceManager/res/values-mk/strings.xml
index 6ef9e5d..938932d 100644
--- a/packages/CompanionDeviceManager/res/values-mk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mk/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Дозволете &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да пристапува до вашиот &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"часовник"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Изберете <xliff:g id="PROFILE_NAME">%1$s</xliff:g> со којшто ќе управува &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Апликацијава е потребна за управување со вашиот <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ќе може да остварува интеракција со известувањата и да пристапува до дозволите за телефонот, SMS, контактите, календарот, евиденцијата на повици и уредите во близина."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Апликации"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Стримувајте ги апликациите на телефонот"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Апликацијата е потребна за управување со вашиот <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ќе може да остварува интеракција со известувањата и да пристапува до дозволите за „Телефон“, SMS, „Контакти“, „Календар“, „Евиденција на повици“ и „Уреди во близина“."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Апликацијата е потребна за управување со вашиот <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ќе може да остварува интеракција со следниве дозволи:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Овозможете &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да пристапува до овие податоци на телефонот"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Повеќенаменски услуги"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> бара дозвола во име на вашиот <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> за да стримува апликации помеѓу вашите уреди"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Дозволете &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да пристапува до овие податоци на телефонот"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Известувања"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"може да ги чита сите известувања, вклучително и податоци како контакти, пораки и фотографии"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Аудиовизуелни содржини"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Услуги на Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> бара дозвола во име на вашиот <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> за да пристапува до фотографиите, аудиовизуелните содржини и известувањата на телефонот"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"уред"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Ова може да вклучува пристап до микрофон, камера и локација и други чувствителни дозволи на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Може да ги промените дозволиве во секое време во вашите „Поставки“ на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Икона на апликацијата"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Копче за повеќе информации"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Контакти"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Календар"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Уреди во близина"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Аудиовизуелни содржини"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Известувања"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Апликации"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Може да пристапува до вашиот телефонски број и податоците за мрежата. Потребно за упатување повици и VoIP, говорна пошта, пренасочување повици и изменување евиденција на повици"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Може да го чита, создава или изменува вашиот список со контакти, како и да пристапува до списоците со контакти на сите сметки што се користат на уредот"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"може да ги чита сите известувања, вклучително и податоци како контакти, пораки и фотографии"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Стримувајте ги апликациите на телефонот"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ml/strings.xml b/packages/CompanionDeviceManager/res/values-ml/strings.xml
index 07e6a43..15434fc 100644
--- a/packages/CompanionDeviceManager/res/values-ml/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ml/strings.xml
@@ -20,9 +20,10 @@
     <string name="confirmation_title" msgid="3785000297483688997">"നിങ്ങളുടെ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ആക്‌സസ് ചെയ്യാൻ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; എന്നതിനെ അനുവദിക്കുക"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"വാച്ച്"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ഉപയോഗിച്ച് മാനേജ് ചെയ്യുന്നതിന് ഒരു <xliff:g id="PROFILE_NAME">%1$s</xliff:g> തിരഞ്ഞെടുക്കുക"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"നിങ്ങളുടെ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> മാനേജ് ചെയ്യാൻ ഈ ആപ്പ് ആവശ്യമാണ്. നിങ്ങളുടെ അറിയിപ്പുകളുമായി ആശയവിനിമയം നടത്താനും നിങ്ങളുടെ ഫോൺ, SMS, കോൺടാക്‌റ്റുകൾ, കലണ്ടർ, കോൾ ചരിത്രം, സമീപമുള്ള ഉപകരണങ്ങളുടെ അനുമതികൾ എന്നിവ ആക്‌സസ് ചെയ്യാനും <xliff:g id="APP_NAME">%2$s</xliff:g> എന്നതിനെ അനുവദിക്കും."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"ആപ്പുകൾ"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"നിങ്ങളുടെ ഫോണിലെ ആപ്പുകൾ സ്‌ട്രീം ചെയ്യാൻ"</string>
+    <!-- no translation found for summary_watch (4085794790142204006) -->
+    <skip />
+    <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
+    <skip />
     <string name="title_app_streaming" msgid="2270331024626446950">"നിങ്ങളുടെ ഫോണിൽ നിന്ന് ഈ വിവരങ്ങൾ ആക്‌സസ് ചെയ്യാൻ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ആപ്പിനെ അനുവദിക്കുക"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ക്രോസ്-ഉപകരണ സേവനങ്ങൾ"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"നിങ്ങളുടെ ഉപകരണങ്ങളിൽ ഒന്നിൽ നിന്ന് അടുത്തതിലേക്ക് ആപ്പുകൾ സ്ട്രീം ചെയ്യാൻ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ഉപകരണത്തിന് വേണ്ടി <xliff:g id="APP_NAME">%1$s</xliff:g> അനുമതി അഭ്യർത്ഥിക്കുന്നു"</string>
@@ -30,10 +31,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"നിങ്ങളുടെ ഫോണിൽ നിന്ന് ഈ വിവരങ്ങൾ ആക്‌സസ് ചെയ്യാൻ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ആപ്പിനെ അനുവദിക്കുക"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"അറിയിപ്പുകൾ"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"കോൺടാക്‌റ്റുകൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ മുതലായ വിവരങ്ങൾ ഉൾപ്പെടെയുള്ള എല്ലാ അറിയിപ്പുകളും വായിക്കാനാകും"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"ഫോട്ടോകളും മീഡിയയും"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play സേവനങ്ങൾ"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"നിങ്ങളുടെ ഫോണിലെ ഫോട്ടോകൾ, മീഡിയ, അറിയിപ്പുകൾ എന്നിവ ആക്സസ് ചെയ്യാൻ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ഉപകരണത്തിന് വേണ്ടി <xliff:g id="APP_NAME">%1$s</xliff:g> അനുമതി അഭ്യർത്ഥിക്കുന്നു"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ഉപകരണം"</string>
@@ -45,4 +42,29 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; എന്നതിലെ മൈക്രോഫോൺ, ക്യാമറ, ലൊക്കേഷൻ ആക്‌സസ്, സെൻസിറ്റീവ് വിവരങ്ങൾക്കുള്ള മറ്റ് അനുമതികൾ എന്നിവയും ഇതിൽ ഉൾപ്പെട്ടേക്കാം&lt;p&gt;നിങ്ങൾക്ക് &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; എന്നതിലെ ക്രമീകരണത്തിൽ ഏതുസമയത്തും ഈ അനുമതികൾ മാറ്റാം."</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"ആപ്പ് ഐക്കൺ"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"കൂടുതൽ വിവരങ്ങൾ ബട്ടൺ"</string>
+    <!-- no translation found for permission_phone (2661081078692784919) -->
+    <skip />
+    <!-- no translation found for permission_sms (6337141296535774786) -->
+    <skip />
+    <!-- no translation found for permission_contacts (3858319347208004438) -->
+    <skip />
+    <!-- no translation found for permission_calendar (6805668388691290395) -->
+    <skip />
+    <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
+    <skip />
+    <string name="permission_storage" msgid="6831099350839392343">"ഫോട്ടോകളും മീഡിയയും"</string>
+    <string name="permission_notification" msgid="693762568127741203">"അറിയിപ്പുകൾ"</string>
+    <!-- no translation found for permission_app_streaming (6009695219091526422) -->
+    <skip />
+    <!-- no translation found for permission_phone_summary (6154198036705702389) -->
+    <skip />
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
+    <skip />
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"കോൺടാക്‌റ്റുകൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ മുതലായ വിവരങ്ങൾ ഉൾപ്പെടെയുള്ള എല്ലാ അറിയിപ്പുകളും വായിക്കാനാകും"</string>
+    <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
+    <skip />
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-mn/strings.xml b/packages/CompanionDeviceManager/res/values-mn/strings.xml
index 1c74e48..511e9eb 100644
--- a/packages/CompanionDeviceManager/res/values-mn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mn/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;-д таны &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-д хандахыг зөвшөөрнө үү"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"цаг"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;-н удирдах<xliff:g id="PROFILE_NAME">%1$s</xliff:g>-г сонгоно уу"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Энэ апп таны <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-г удирдахад шаардлагатай. <xliff:g id="APP_NAME">%2$s</xliff:g>-д таны мэдэгдэлтэй харилцан үйлдэл хийж, Утас, SMS, Харилцагчид, Календарь, Дуудлагын жагсаалт болон Ойролцоох төхөөрөмжүүдийн зөвшөөрөлд хандахыг зөвшөөрнө."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Аппууд"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Таны утасны аппуудыг дамжуулах"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Энэ апп таны <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-г удирдахад шаардлагатай. <xliff:g id="APP_NAME">%2$s</xliff:g>-д таны мэдэгдэлтэй харилцан үйлдэл хийж, Утас, SMS, Харилцагчид, Календарь, Дуудлагын жагсаалт болон Ойролцоох төхөөрөмжүүдийн зөвшөөрөлд хандахыг зөвшөөрнө."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Энэ апп таны <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-г удирдахад шаардлагатай. <xliff:g id="APP_NAME">%2$s</xliff:g>-д эдгээр зөвшөөрөлтэй харилцан үйлдэл хийхийг зөвшөөрнө:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-д таны утаснаас энэ мэдээлэлд хандахыг зөвшөөрнө үү"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Төхөөрөмж хоорондын үйлчилгээ"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Таны төхөөрөмжүүд хооронд апп дамжуулахын тулд <xliff:g id="APP_NAME">%1$s</xliff:g> таны <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-н өмнөөс зөвшөөрөл хүсэж байна"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-д таны утаснаас энэ мэдээлэлд хандахыг зөвшөөрнө үү"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Мэдэгдэл"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Харилцагчид, мессеж болон зураг зэрэг мэдээллийг оруулаад бүх мэдэгдлийг унших боломжтой"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Зураг болон медиа"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play үйлчилгээ"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Таны утасны зураг, медиа болон мэдэгдэлд хандахын тулд <xliff:g id="APP_NAME">%1$s</xliff:g> таны <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-н өмнөөс зөвшөөрөл хүсэж байна"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"төхөөрөмж"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Үүнд Микрофон, Камер болон Байршлын хандалт болон &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; дээрх бусад эмзэг зөвшөөрөл багтаж болно.&lt;/p&gt; &lt;p&gt;Та эдгээр зөвшөөрлийг &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; дээрх Тохиргоо хэсэгтээ хүссэн үедээ өөрчлөх боломжтой.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Aппын дүрс тэмдэг"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Дэлгэрэнгүй мэдээллийн товчлуур"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Утас"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Харилцагчид"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Календарь"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Ойролцоох төхөөрөмжүүд"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Зураг болон медиа"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Мэдэгдэл"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Аппууд"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Таны утасны дугаар болон сүлжээний мэдээлэлд хандах боломжтой. Дуудлага хийх, VoIP, дуут шуудан, дуудлагыг дахин чиглүүлэх болон дуудлагын жагсаалтыг засахад шаардлагатай"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Манай харилцагчийн жагсаалтыг унших, үүсгэх эсвэл засахаас гадна таны төхөөрөмж дээр ашигласан бүх бүртгэлийн жагсаалтад хандах боломжтой"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Харилцагчид, мессеж болон зураг зэрэг мэдээллийг оруулаад бүх мэдэгдлийг унших боломжтой"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Утасныхаа аппуудыг дамжуулаарай"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-mr/strings.xml b/packages/CompanionDeviceManager/res/values-mr/strings.xml
index 1cc0412..37a8349 100644
--- a/packages/CompanionDeviceManager/res/values-mr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mr/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"तुमचे &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; अ‍ॅक्सेस करण्यासाठी &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ला अनुमती द्या"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"वॉच"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; द्वारे व्यवस्थापित करण्यासाठी <xliff:g id="PROFILE_NAME">%1$s</xliff:g> निवडा"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"तुमची <xliff:g id="DEVICE_NAME">%1$s</xliff:g> प्रोफाइल व्यवस्थापित करण्यासाठी हे ॲप आवश्यक आहे. <xliff:g id="APP_NAME">%2$s</xliff:g> ला तुमच्या सूचनांशी संवाद साधण्याची आणि तुमचा फोन, एसएमएस, संपर्क कॅलेंडर, कॉल लॉग व जवळपासच्या डिव्हाइसच्या परवानग्या अ‍ॅक्सेस करण्याची अनुमती मिळेल."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"ॲप्स"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"फोनवरील ॲप्स स्ट्रीम करा"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"तुमचे <xliff:g id="DEVICE_NAME">%1$s</xliff:g> व्यवस्थापित करण्यासाठी हे ॲप आवश्यक आहे. <xliff:g id="APP_NAME">%2$s</xliff:g> ला तुमच्या सूचनांशी संवाद साधण्याची आणि तुमचा फोन, एसएमएस, संपर्क कॅलेंडर, कॉल लॉग व जवळपासच्या डिव्हाइसच्या परवानग्या अ‍ॅक्सेस करण्याची अनुमती मिळेल."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"तुमचे <xliff:g id="DEVICE_NAME">%1$s</xliff:g> व्यवस्थापित करण्यासाठी हे ॲप आवश्यक आहे. <xliff:g id="APP_NAME">%2$s</xliff:g> ला पुढील परवानग्यांशी संवाद साधण्याची अनुमती मिळेल:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ला ही माहिती तुमच्या फोनवरून अ‍ॅक्सेस करण्यासाठी अनुमती द्या"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"क्रॉस-डिव्हाइस सेवा"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"तुमच्या डिव्हाइसदरम्यान ॲप्स स्ट्रीम करण्यासाठी <xliff:g id="APP_NAME">%1$s</xliff:g> हे तुमच्या <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> च्या वतीने परवानगीची विनंती करत आहे"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ला ही माहिती तुमच्या फोनवरून अ‍ॅक्सेस करण्यासाठी अनुमती द्या"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"सूचना"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"संपर्क, मेसेज आणि फोटो यांसारख्या माहितीचा समावेश असलेल्या सर्व सूचना वाचू शकते"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"फोटो आणि मीडिया"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play सेवा"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"तुमच्या फोनमधील फोटो, मीडिया आणि सूचना ॲक्सेस करण्यासाठी <xliff:g id="APP_NAME">%1$s</xliff:g> हे तुमच्या <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> च्या वतीने परवानगीची विनंती करत आहे"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"डिव्हाइस"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;यामध्ये &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;strong&gt; वरील मायक्रोफोन, कॅमेरा आणि स्थान अ‍ॅक्सेस व इतर संवेदनशील परवानग्यांचा समावेश असू शकतो &lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;तुम्ही &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; वर तुमच्या सेटिंग्ज मध्ये या परवानग्या कधीही बदलू शकता&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"अ‍ॅप आयकन"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"अधिक माहिती बटण"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"फोन"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"एसएमएस"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"जवळपासची डिव्हाइस"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"फोटो आणि मीडिया"</string>
+    <string name="permission_notification" msgid="693762568127741203">"सूचना"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"ॲप्स"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"तुमचा फोन नंबर आणि नेटवर्कची माहिती अ‍ॅक्सेस करू शकते. कॉल करणे व VoIP, व्हॉइसमेल, कॉल रीडिरेक्ट करणे आणि कॉल लॉग संपादित करणे यांसाठी आवश्यक"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"आमची संपर्क सूची रीड, तयार आणि संपादित करू शकते, तसेच तुमच्या डिव्हाइसवर वापरल्या जाणाऱ्या खात्यांची सूची अ‍ॅक्सेस करू शकते"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"संपर्क, मेसेज आणि फोटो यांसारख्या माहितीचा समावेश असलेल्या सर्व सूचना वाचू शकते"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"तुमच्या फोनवरील ॲप्स स्ट्रीम करा"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ms/strings.xml b/packages/CompanionDeviceManager/res/values-ms/strings.xml
index 02743f0..ac9acbc 100644
--- a/packages/CompanionDeviceManager/res/values-ms/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ms/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Benarkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; anda"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"jam tangan"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk diurus oleh &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Apl ini diperlukan untuk mengurus <xliff:g id="DEVICE_NAME">%1$s</xliff:g> anda. <xliff:g id="APP_NAME">%2$s</xliff:g> akan dibenarkan berinteraksi dengan pemberitahuan anda dan mengakses kebenaran Telefon, SMS, Kenalan, Kalendar, Log panggilan dan Peranti berdekatan anda."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Apl"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Strim apl telefon anda"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Apl ini diperlukan untuk mengurus <xliff:g id="DEVICE_NAME">%1$s</xliff:g> anda. <xliff:g id="APP_NAME">%2$s</xliff:g> akan dibenarkan berinteraksi dengan pemberitahuan anda dan mengakses kebenaran Telefon, SMS, Kenalan, Kalendar, Log panggilan serta Peranti berdekatan anda."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Apl ini diperlukan untuk mengurus <xliff:g id="DEVICE_NAME">%1$s</xliff:g> anda. <xliff:g id="APP_NAME">%2$s</xliff:g> akan dibenarkan berinteraksi dengan kebenaran ini:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Benarkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses maklumat ini daripada telefon anda"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Perkhidmatan silang peranti"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang meminta kebenaran bagi pihak <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> anda untuk menstrim apl antara peranti anda"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Benarkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; untuk mengakses maklumat ini daripada telefon anda"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Pemberitahuan"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Boleh membaca semua pemberitahuan, termasuk maklumat seperti kenalan, mesej dan foto"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Foto dan media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Perkhidmatan Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang meminta kebenaran bagi pihak <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> anda untuk mengakses foto, media dan pemberitahuan telefon anda"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"peranti"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Ini mungkin termasuk akses Mikrofon, Kamera dan Lokasi serta kebenaran sensitif lain pada &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Anda boleh menukar kebenaran ini pada bila-bila masa dalam Tetapan anda pada &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ikon Apl"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Butang Maklumat Lagi"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kenalan"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalendar"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Peranti berdekatan"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Foto dan media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Pemberitahuan"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Apl"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Boleh mengakses nombor telefon dan maklumat rangkaian anda. Diperlukan untuk membuat panggilan dan VoIP, mel suara, ubah hala panggilan dan mengedit log panggilan"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Boleh membaca, membuat atau mengedit senarai kenalan kami, serta mengakses senarai semua akaun yang digunakan pada peranti anda"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Boleh membaca semua pemberitahuan, termasuk maklumat seperti kenalan, mesej dan foto"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Strim apl telefon anda"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-my/strings.xml b/packages/CompanionDeviceManager/res/values-my/strings.xml
index 61272cc..271ddee 100644
--- a/packages/CompanionDeviceManager/res/values-my/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-my/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"သင်၏ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ကို သုံးရန် &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ကို ခွင့်ပြုပါ"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"နာရီ"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; က စီမံခန့်ခွဲရန် <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ကို ရွေးချယ်ပါ"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"သင်၏ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်ကိုလိုအပ်သည်။ သင်၏ ‘ဖုန်း’၊ ‘SMS စာတိုစနစ်’၊ ‘အဆက်အသွယ်များ’၊ ‘ပြက္ခဒိန်’၊ ‘ခေါ်ဆိုမှတ်တမ်း’ နှင့် \'အနီးတစ်ဝိုက်ရှိ စက်များ\' ခွင့်ပြုချက်များကို သုံးရန်နှင့် အကြောင်းကြားချက်များကို ပြန်လှန်တုံ့ပြန်ရန် <xliff:g id="APP_NAME">%2$s</xliff:g> အား ခွင့်ပြုပါမည်။"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"အက်ပ်များ"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"သင့်ဖုန်းရှိအက်ပ်များကို တိုက်ရိုက်လွှင့်နိုင်သည်"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"သင်၏ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်လိုအပ်သည်။ သင်၏ဖုန်း၊ SMS စာတိုစနစ်၊ အဆက်အသွယ်များ၊ ပြက္ခဒိန်၊ ခေါ်ဆိုမှတ်တမ်းနှင့် အနီးတစ်ဝိုက်ရှိ စက်များဆိုင်ရာ ခွင့်ပြုချက်များသုံးရန်၊ အကြောင်းကြားချက်များနှင့် ပြန်လှန်တုံ့ပြန်ရန် <xliff:g id="APP_NAME">%2$s</xliff:g> ကို ခွင့်ပြုမည်။"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"သင်၏ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်လိုအပ်သည်။ ဤခွင့်ပြုချက်များနှင့် ပြန်လှန်တုံ့ပြန်ရန် <xliff:g id="APP_NAME">%2$s</xliff:g> ကို ခွင့်ပြုမည်-"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ကို သင့်ဖုန်းမှ ဤအချက်အလက် သုံးခွင့်ပြုမည်"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"စက်များကြားသုံး ဝန်ဆောင်မှုများ"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် သင်၏စက်များအကြား အက်ပ်များတိုက်ရိုက်လွှင့်ရန် <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ကိုယ်စား ခွင့်ပြုချက်တောင်းနေသည်"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; အား သင့်ဖုန်းမှ ဤအချက်အလက် သုံးခွင့်ပြုခြင်း"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"အကြောင်းကြားချက်များ"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"အဆက်အသွယ်၊ မက်ဆေ့ဂျ်နှင့် ဓာတ်ပုံကဲ့သို့ အချက်အလက်များအပါအဝင် အကြောင်းကြားချက်အားလုံးကို ဖတ်နိုင်သည်"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"ဓာတ်ပုံနှင့် မီဒီယာများ"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play ဝန်ဆောင်မှုများ"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် သင့်ဖုန်း၏ ဓာတ်ပုံ၊ မီဒီယာနှင့် အကြောင်းကြားချက်များသုံးရန် <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ကိုယ်စား ခွင့်ပြုချက်တောင်းနေသည်"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"စက်"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;၎င်းတွင် မိုက်ခရိုဖုန်း၊ ကင်မရာ၊ တည်နေရာ အသုံးပြုခွင့်အပြင် &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;ပေါ်ရှိ အခြား သတိထားရမည့် ခွင့်ပြုချက်များ ပါဝင်နိုင်သည်။&lt;/p&gt; &lt;p&gt;ဤခွင့်ပြုချက်များကို &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;ပေါ်ရှိ သင်၏ဆက်တင်များတွင် အချိန်မရွေးပြောင်းနိုင်သည်။&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"အက်ပ်သင်္ကေတ"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"နောက်ထပ်အချက်အလက်များ ခလုတ်"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"ဖုန်း"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS စာတိုစနစ်"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"အဆက်အသွယ်များ"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"ပြက္ခဒိန်"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"အနီးတစ်ဝိုက်ရှိ စက်များ"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"ဓာတ်ပုံနှင့် မီဒီယာများ"</string>
+    <string name="permission_notification" msgid="693762568127741203">"အကြောင်းကြားချက်များ"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"အက်ပ်များ"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"သင့်ဖုန်းနံပါတ်နှင့် ကွန်ရက်အချက်အလက်ကို သုံးနိုင်သည်။ ဖုန်းခေါ်ဆိုခြင်းနှင့် VoIP၊ အသံမေးလ်၊ ခေါ်ဆိုမှု တစ်ဆင့်ပြန်လွှဲခြင်းနှင့် ခေါ်ဆိုမှတ်တမ်းများ တည်းဖြတ်ခြင်းတို့အတွက် လိုအပ်သည်"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"အဆက်အသွယ် စာရင်းကို ဖတ်နိုင်၊ ပြုလုပ်နိုင် (သို့) တည်းဖြတ်နိုင်သည့်ပြင် သင့်စက်တွင်သုံးသော အကောင့်အားလုံး၏စာရင်းကို သုံးနိုင်သည်"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"အဆက်အသွယ်၊ မက်ဆေ့ဂျ်နှင့် ဓာတ်ပုံကဲ့သို့ အချက်အလက်များအပါအဝင် အကြောင်းကြားချက်အားလုံးကို ဖတ်နိုင်သည်"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"သင့်ဖုန်းရှိအက်ပ်များကို တိုက်ရိုက်ဖွင့်နိုင်သည်"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-nb/strings.xml b/packages/CompanionDeviceManager/res/values-nb/strings.xml
index 6df06c1..01f4d32 100644
--- a/packages/CompanionDeviceManager/res/values-nb/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nb/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Tillat at &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; bruker &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"klokke"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Velg <xliff:g id="PROFILE_NAME">%1$s</xliff:g> som skal administreres av &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Denne appen kreves for å administrere <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tillatelse til å samhandle med varslene dine og får tilgang til tillatelser for telefon, SMS, kontakter, kalender, samtalelogger og enheter i nærheten."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Apper"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Strøm appene på telefonen"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Appen kreves for å administrere <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tillatelse til å samhandle med varslene dine og har tilgang til tillatelsene for telefon, SMS, kontakter, kalender, samtalelogger og enheter i nærheten."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Appen kreves for å administrere <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tillatelse til å samhandle med disse tillatelsene:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Gi &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tilgang til denne informasjonen fra telefonen din"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Tjenester på flere enheter"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ber om tillatelse til å strømme apper mellom enhetene dine, på vegne av <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Gi &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tilgang til denne informasjonen fra telefonen din"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Varsler"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Kan lese alle varsler, inkludert informasjon som kontakter, meldinger og bilder"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Bilder og medier"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play-tjenester"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> ber om tillatelse til å få tilgang til bilder, medier og varsler på telefonen din, på vegne av <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"enhet"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Dette kan inkludere tilgang til mikrofon, kamera og posisjon samt andre sensitive tillatelser på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Du kan når som helst endre disse tillatelsene i innstillingene på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Appikon"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Mer informasjon-knapp"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontakter"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalender"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Enheter i nærheten"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Bilder og medier"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Varsler"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Apper"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Kan se telefonnummeret ditt og nettverksinformasjon. Dette kreves for samtaler og VoIP, talepost, viderekobling av anrop og endring av samtalelogger"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Kan lese, opprette eller endre kontaktlisten din og se listen over alle kontoene som brukes på enheten"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Kan lese alle varsler, inkludert informasjon som kontakter, meldinger og bilder"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Strøm appene på telefonen"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ne/strings.xml b/packages/CompanionDeviceManager/res/values-ne/strings.xml
index fc22508..d7d3459 100644
--- a/packages/CompanionDeviceManager/res/values-ne/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ne/strings.xml
@@ -20,9 +20,10 @@
     <string name="confirmation_title" msgid="3785000297483688997">"आफ्नो &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; लाई &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; प्रयोग गर्ने अनुमति दिनुहोस्"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"घडी"</string>
     <string name="chooser_title" msgid="2262294130493605839">"आफूले &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; प्रयोग गरी व्यवस्थापन गर्न चाहेको <xliff:g id="PROFILE_NAME">%1$s</xliff:g> चयन गर्नुहोस्"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"तपाईंको <xliff:g id="DEVICE_NAME">%1$s</xliff:g> व्यवस्थापन गर्न यो एपलाई अनुमति दिनु पर्ने हुन्छ। <xliff:g id="APP_NAME">%2$s</xliff:g> लाई तपाईंका सूचना हेर्ने र फोन, SMS, कन्ट्याक्ट, पात्रो, कल लग तथा नजिकैका डिभाइससम्बन्धी अनुमतिहरू हेर्ने तथा प्रयोग गर्ने अनुमति दिइने छ।"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"एपहरू"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"आफ्नो फोनका एपहरू प्रयोग गर्नुहोस्"</string>
+    <!-- no translation found for summary_watch (4085794790142204006) -->
+    <skip />
+    <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
+    <skip />
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; लाई तपाईंको फोनमा भएको यो जानकारी हेर्ने तथा प्रयोग गर्ने अनुमति दिनुहोस्"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"क्रस-डिभाइस सेवाहरू"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> तपाईंको डिभाइस <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> को तर्फबाट तपाईंका कुनै एउटा डिभाइसबाट अर्को डिभाइसमा एप स्ट्रिम गर्ने अनुमति माग्दै छ"</string>
@@ -30,10 +31,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; लाई तपाईंको फोनमा भएको यो जानकारी हेर्ने तथा प्रयोग गर्ने अनुमति दिनुहोस्"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"सूचनाहरू"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"कन्ट्याक्ट, म्यासेज र फोटोलगायतका जानकारीसहित सबै सूचनाहरू पढ्न सक्छ"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"फोटो र मिडिया"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> तपाईंको डिभाइस <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> को तर्फबाट तपाईंको फोनमा भएका फोटो, मिडिया र सूचनाहरू हेर्ने तथा प्रयोग गर्ने अनुमति माग्दै छ"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"यन्त्र"</string>
@@ -45,4 +42,29 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;यसअन्तर्गत &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; का माइक्रोफोन, क्यामेरा र लोकेसन प्रयोग गर्ने अनुमतिका तथा अन्य संवेदनशील अनुमति समावेश हुन्छन्।&lt;/p&gt; &lt;p&gt;तपाईं &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; का सेटिङमा गई जुनसुकै बेला यी अनुमति परिवर्तन गर्न सक्नुहुन्छ।&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"एपको आइकन"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"थप जानकारी देखाउने बटन"</string>
+    <!-- no translation found for permission_phone (2661081078692784919) -->
+    <skip />
+    <!-- no translation found for permission_sms (6337141296535774786) -->
+    <skip />
+    <!-- no translation found for permission_contacts (3858319347208004438) -->
+    <skip />
+    <!-- no translation found for permission_calendar (6805668388691290395) -->
+    <skip />
+    <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
+    <skip />
+    <string name="permission_storage" msgid="6831099350839392343">"फोटो र मिडिया"</string>
+    <string name="permission_notification" msgid="693762568127741203">"सूचनाहरू"</string>
+    <!-- no translation found for permission_app_streaming (6009695219091526422) -->
+    <skip />
+    <!-- no translation found for permission_phone_summary (6154198036705702389) -->
+    <skip />
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
+    <skip />
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"कन्ट्याक्ट, म्यासेज र फोटोलगायतका जानकारीसहित सबै सूचनाहरू पढ्न सक्छ"</string>
+    <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
+    <skip />
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml
index 9c7cc3c..28366f6 100644
--- a/packages/CompanionDeviceManager/res/values-nl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang geven tot je &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"horloge"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Een <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiezen om te beheren met &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Deze app is vereist om je <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> kan interactie hebben met je meldingen en toegang krijgen tot rechten voor Telefoon, Sms, Contacten, Agenda, Gesprekslijsten en Apparaten in de buurt."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Stream de apps van je telefoon"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"De app is vereist om je <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> kan interactie hebben met je meldingen en toegang krijgen tot rechten voor Telefoon, Sms, Contacten, Agenda, Gesprekslijsten en Apparaten in de buurt."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"De app is vereist om je <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> heeft toestemming om interactie te hebben met de volgende rechten:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang geven tot deze informatie op je telefoon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device-services"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> vraagt namens jouw <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> toestemming om apps te streamen tussen je apparaten"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang geven tot deze informatie op je telefoon"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Meldingen"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Kan alle meldingen lezen, waaronder informatie zoals contacten, berichten en foto\'s"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Foto\'s en media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play-services"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> vraagt namens jouw <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> toegang tot de foto\'s, media en meldingen van je telefoon"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"apparaat"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Dit kan toegang tot de microfoon, camera en je locatie en andere gevoelige rechten op je &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; omvatten.&lt;/p&gt; &lt;p&gt;Je kunt deze rechten op elk moment wijzigen in je Instellingen op de <xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"App-icoon"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Knop Meer informatie"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefoon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"Sms"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contacten"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Agenda"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Apparaten in de buurt"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Foto\'s en media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Meldingen"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Heeft toegang tot je telefoonnummer en netwerkgegevens. Vereist voor bellen en VoIP, voicemail, gesprek omleiden en gesprekslijsten bewerken."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Kan je contactenlijst lezen, maken of bewerken, en heeft toegang tot de contactenlijst voor alle accounts die op je apparaat worden gebruikt."</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Kan alle meldingen lezen, waaronder informatie zoals contacten, berichten en foto\'s"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream de apps van je telefoon"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-or/strings.xml b/packages/CompanionDeviceManager/res/values-or/strings.xml
index e567806..5f7f52e 100644
--- a/packages/CompanionDeviceManager/res/values-or/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-or/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"ଆପଣଙ୍କ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;କୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;କୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ୱାଚ୍"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ଦ୍ୱାରା ପରିଚାଳିତ ହେବା ପାଇଁ ଏକ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>କୁ ବାଛନ୍ତୁ"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"ଆପଣଙ୍କ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>କୁ ପରିଚାଳନା କରିବା ପାଇଁ ଏହି ଆପ ଆବଶ୍ୟକ। ଆପଣଙ୍କ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ସହ ଇଣ୍ଟରାକ୍ଟ କରିବା ଏବଂ ଆପଣଙ୍କ ଫୋନ, SMS, ଯୋଗାଯୋଗ, କ୍ୟାଲେଣ୍ଡର, କଲ ଲଗ ଏବଂ ଆଖପାଖର ଡିଭାଇସ ଅନୁମତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%2$s</xliff:g>କୁ ଅନୁମତି ଦିଆଯିବ।"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"ଆପ୍ସ"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"ଆପଣଙ୍କ ଫୋନର ଆପ୍ସକୁ ଷ୍ଟ୍ରିମ କରନ୍ତୁ"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"ଆପଣଙ୍କ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>କୁ ପରିଚାଳନା କରିବା ପାଇଁ ଆପ ଆବଶ୍ୟକ। ଆପଣଙ୍କ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ସହ ଇଣ୍ଟରାକ୍ଟ କରିବା ଏବଂ ଆପଣଙ୍କର ଫୋନ, SMS, କଣ୍ଟାକ୍ଟ, କେଲେଣ୍ଡର, କଲ ଲଗ ଓ ଆଖପାଖର ଡିଭାଇସ ଅନୁମତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%2$s</xliff:g>କୁ ଅନୁମତି ଦିଆଯିବ।"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"ଆପଣଙ୍କ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>କୁ ପରିଚାଳନା କରିବା ପାଇଁ ଆପ ଆବଶ୍ୟକ। ଏହି ଅନୁମତିଗୁଡ଼ିକ ସହ ଇଣ୍ଟରାକ୍ଟ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%2$s</xliff:g>କୁ ଅନୁମତି ଦିଆଯିବ:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"ଆପଣଙ୍କ ଫୋନରୁ ଏହି ସୂଚନାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;କୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"କ୍ରସ-ଡିଭାଇସ ସେବାଗୁଡ଼ିକ"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"ଆପଣଙ୍କ ଡିଭାଇସଗୁଡ଼ିକ ମଧ୍ୟରେ ଆପ୍ସକୁ ଷ୍ଟ୍ରିମ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଆପଣଙ୍କର <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ତରଫରୁ ଅନୁମତି ପାଇଁ ଅନୁରୋଧ କରୁଛି"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"ଆପଣଙ୍କ ଫୋନରୁ ଏହି ସୂଚନାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;କୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"ଯୋଗାଯୋଗ, ମେସେଜ ଏବଂ ଫଟୋଗୁଡ଼ିକ ପରି ସୂଚନା ସମେତ ସମସ୍ତ ବିଜ୍ଞପ୍ତିକୁ ପଢ଼ିପାରିବ"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"ଫଟୋ ଏବଂ ମିଡିଆ"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play ସେବାଗୁଡ଼ିକ"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"ଆପଣଙ୍କ ଫୋନର ଫଟୋ, ମିଡିଆ ଏବଂ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଆପଣଙ୍କର <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ତରଫରୁ ଅନୁମତି ପାଇଁ ଅନୁରୋଧ କରୁଛି"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ଡିଭାଇସ୍"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;ଏହା &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;ରେ ମାଇକ୍ରୋଫୋନ, କ୍ୟାମେରା ଏବଂ ଲୋକେସନ ଆକ୍ସେସ ଓ ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ଅନୁମତିଗୁଡ଼ିକୁ ଅନ୍ତର୍ଭୁକ୍ତ କରିପାରେ।&lt;/p&gt; &lt;p&gt;ଆପଣ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;ରେ ଯେ କୌଣସି ସମୟରେ ଆପଣଙ୍କ ସେଟିଂସରେ ଏହି ଅନୁମତିଗୁଡ଼ିକୁ ପରିବର୍ତ୍ତନ କରିପାରିବେ।&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"ଆପ ଆଇକନ"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"ଅଧିକ ସୂଚନା ବଟନ"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"ଫୋନ"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"କଣ୍ଟାକ୍ଟଗୁଡ଼ିକ"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"କେଲେଣ୍ଡର"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"ଆଖପାଖର ଡିଭାଇସଗୁଡ଼ିକ"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"ଫଟୋ ଏବଂ ମିଡିଆ"</string>
+    <string name="permission_notification" msgid="693762568127741203">"ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"ଆପ୍ସ"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"ଆପଣଙ୍କ ଫୋନ ନମ୍ବର ଏବଂ ନେଟୱାର୍କ ସୂଚନା ଆକ୍ସେସ କରିପାରିବେ।କଲ କରିବା ଓ VoIP, ଭଏସମେଲ, କଲ ଡାଇରେକ୍ଟ ଏବଂ କଲ ଲଗକୁ ଏଡିଟ କରିବା ପାଇଁ ଆବଶ୍ୟକ"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"ଆପଣଙ୍କ କଣ୍ଟାକ୍ଟ ତାଲିକା ପଢ଼ିପାରିବେ, ତିଆରି କିମ୍ବା ଏଡିଟ କରିପାରିବେ ତଥା ଆପଣଙ୍କ ଡିଭାଇସରେ ବ୍ୟବହାର କରାଯାଇଥିବା ସମସ୍ତ ଆକାଉଣ୍ଟର ତାଲିକାକୁ ଆକ୍ସେସ କରିପାରିବେ"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"ଯୋଗାଯୋଗ, ମେସେଜ ଏବଂ ଫଟୋଗୁଡ଼ିକ ପରି ସୂଚନା ସମେତ ସମସ୍ତ ବିଜ୍ଞପ୍ତିକୁ ପଢ଼ିପାରିବ"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"ଆପଣଙ୍କ ଫୋନର ଆପ୍ସକୁ ଷ୍ଟ୍ରିମ କରନ୍ତୁ"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pa/strings.xml b/packages/CompanionDeviceManager/res/values-pa/strings.xml
index dba72eb..4d9b8d3 100644
--- a/packages/CompanionDeviceManager/res/values-pa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pa/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ਨੂੰ ਤੁਹਾਡੇ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ਸਮਾਰਟ-ਵਾਚ"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ਵੱਲੋਂ ਪ੍ਰਬੰਧਿਤ ਕੀਤੇ ਜਾਣ ਲਈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ਚੁਣੋ"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"ਤੁਹਾਡੇ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ ਇਹ ਐਪ ਲੋੜੀਂਦੀ ਹੈ। <xliff:g id="APP_NAME">%2$s</xliff:g> ਨੂੰ ਤੁਹਾਡੀਆਂ ਸੂਚਨਾਵਾਂ ਨਾਲ ਅੰਤਰਕਿਰਿਆ ਕਰਨ ਅਤੇ ਤੁਹਾਡੇ ਫ਼ੋਨ, SMS, ਸੰਪਰਕਾਂ, ਕੈਲੰਡਰ, ਕਾਲ ਲੌਗਾਂ ਅਤੇ ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸਾਂ ਸੰਬੰਧੀ ਇਜਾਜ਼ਤਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ।"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"ਐਪਾਂ"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"ਆਪਣੇ ਫ਼ੋਨ ਦੀਆਂ ਐਪਾਂ ਨੂੰ ਸਟ੍ਰੀਮ ਕਰੋ"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"ਇਹ ਐਪ ਤੁਹਾਡੇ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ ਲੋੜੀਂਦੀ ਹੈ। <xliff:g id="APP_NAME">%2$s</xliff:g> ਨੂੰ ਤੁਹਾਡੀਆਂ ਸੂਚਨਾਵਾਂ ਨਾਲ ਅੰਤਰਕਿਰਿਆ ਕਰਨ ਅਤੇ ਤੁਹਾਡੇ ਫ਼ੋਨ, SMS, ਸੰਪਰਕਾਂ, ਕੈਲੰਡਰ, ਕਾਲ ਲੌਗਾਂ ਅਤੇ ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸਾਂ ਸੰਬੰਧੀ ਇਜਾਜ਼ਤਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ।"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"ਇਹ ਐਪ ਤੁਹਾਡੇ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ ਲੋੜੀਂਦੀ ਹੈ। <xliff:g id="APP_NAME">%2$s</xliff:g> ਨੂੰ ਇਨ੍ਹਾਂ ਇਜਾਜ਼ਤਾਂ ਨਾਲ ਅੰਤਰਕਿਰਿਆ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ਨੂੰ ਤੁਹਾਡੇ ਫ਼ੋਨ ਤੋਂ ਇਸ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ਕ੍ਰਾਸ-ਡੀਵਾਈਸ ਸੇਵਾਵਾਂ"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਤੁਹਾਡੇ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ਦੀ ਤਰਫ਼ੋਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸਾਂ ਵਿਚਕਾਰ ਐਪਾਂ ਨੂੰ ਸਟ੍ਰੀਮ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਮੰਗ ਰਹੀ ਹੈ"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ਨੂੰ ਤੁਹਾਡੇ ਫ਼ੋਨ ਤੋਂ ਇਸ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"ਸੂਚਨਾਵਾਂ"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"ਤੁਸੀਂ ਸਾਰੀਆਂ ਸੂਚਨਾਵਾਂ ਪੜ੍ਹ ਸਕਦੇ ਹੋ, ਜਿਨ੍ਹਾਂ ਵਿੱਚ ਸੰਪਰਕਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਫ਼ੋਟੋਆਂ ਵਰਗੀ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੁੰਦੀ ਹੈ"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"ਫ਼ੋਟੋਆਂ ਅਤੇ ਮੀਡੀਆ"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play ਸੇਵਾਵਾਂ"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਤੁਹਾਡੇ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ਦੀ ਤਰਫ਼ੋਂ ਤੁਹਾਡੇ ਫ਼ੋਨ ਦੀਆਂ ਫ਼ੋਟੋਆਂ, ਮੀਡੀਆ ਅਤੇ ਸੂਚਨਾਵਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਮੰਗ ਰਹੀ ਹੈ"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ਡੀਵਾਈਸ"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;ਇਸ ਵਿੱਚ ਮਾਈਕ੍ਰੋਫ਼ੋਨ, ਕੈਮਰਾ, ਟਿਕਾਣਾ ਪਹੁੰਚ ਅਤੇ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; \'ਤੇ ਮੌਜੂਦ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਸੰਬੰਧੀ ਇਜਾਜ਼ਤਾਂ ਸ਼ਾਮਲ ਹੋ ਸਕਦੀਆਂ ਹਨ।&lt;/p&gt; &lt;p&gt;ਤੁਸੀਂ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; \'ਤੇ ਮੌਜੂਦ ਆਪਣੀਆਂ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਕਦੇ ਵੀ ਇਨ੍ਹਾਂ ਇਜਾਜ਼ਤਾਂ ਨੂੰ ਬਦਲ ਸਕਦੇ ਹੋ।&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"ਐਪ ਪ੍ਰਤੀਕ"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"ਹੋਰ ਜਾਣਕਾਰੀ ਬਟਨ"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"ਫ਼ੋਨ"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"ਸੰਪਰਕ"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"ਕੈਲੰਡਰ"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸ"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"ਫ਼ੋਟੋਆਂ ਅਤੇ ਮੀਡੀਆ"</string>
+    <string name="permission_notification" msgid="693762568127741203">"ਸੂਚਨਾਵਾਂ"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"ਐਪਾਂ"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"ਤੁਹਾਡੇ ਫ਼ੋਨ ਨੰਬਰ ਅਤੇ ਨੈੱਟਵਰਕ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ। ਇਹ ਕਾਲਾਂ ਅਤੇ VoIP, ਵੌਇਸਮੇਲ, ਕਾਲ ਨੂੰ ਰੀਡਾਇਰੈਕਟ ਕਰਨ ਅਤੇ ਕਾਲ ਲੌਗਾਂ ਦਾ ਸੰਪਾਦਨ ਕਰਨ ਲਈ ਲੋੜੀਂਦੀ ਹੈ"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"ਤੁਹਾਡੀ ਸੰਪਰਕ ਸੂਚੀ ਨੂੰ ਪੜ੍ਹ, ਬਣਾ ਸਕਦੀ ਹੈ ਜਾਂ ਉਸਦਾ ਸੰਪਾਦਨ ਕਰ ਸਕਦੀ ਹੈ ਅਤੇ ਇਸ ਤੋਂ ਇਲਾਵਾ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਵਰਤੇ ਗਏ ਸਾਰੇ ਖਾਤਿਆਂ ਦੀ ਸੂਚੀ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"ਤੁਸੀਂ ਸਾਰੀਆਂ ਸੂਚਨਾਵਾਂ ਪੜ੍ਹ ਸਕਦੇ ਹੋ, ਜਿਨ੍ਹਾਂ ਵਿੱਚ ਸੰਪਰਕਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਫ਼ੋਟੋਆਂ ਵਰਗੀ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੁੰਦੀ ਹੈ"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"ਆਪਣੇ ਫ਼ੋਨ ਦੀਆਂ ਐਪਾਂ ਨੂੰ ਸਟ੍ਰੀਮ ਕਰੋ"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pl/strings.xml b/packages/CompanionDeviceManager/res/values-pl/strings.xml
index a632026..2c7a038 100644
--- a/packages/CompanionDeviceManager/res/values-pl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pl/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Zezwól na dostęp aplikacji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; do urządzenia &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"zegarek"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Wybierz profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, którym ma zarządzać aplikacja &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Ta aplikacja jest niezbędna do zarządzania profilem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacja <xliff:g id="APP_NAME">%2$s</xliff:g> będzie mogła korzystać z powiadomień oraz uprawnień dotyczących telefonu, SMS-ów, kontaktów, kalendarza, rejestrów połączeń i urządzeń w pobliżu."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Aplikacje"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Odtwarzaj strumieniowo aplikacje z telefonu"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Aplikacja jest niezbędna do zarządzania profilem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacja <xliff:g id="APP_NAME">%2$s</xliff:g> będzie mogła korzystać z powiadomień oraz uprawnień dotyczących telefonu, SMS-ów, kontaktów, kalendarza, rejestrów połączeń i Urządzeń w pobliżu."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikacja jest niezbędna do zarządzania profilem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacja <xliff:g id="APP_NAME">%2$s</xliff:g> będzie mogła korzystać z tych powiadomień:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Zezwól urządzeniu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na dostęp do tych informacji na Twoim telefonie"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Usługi na innym urządzeniu"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> prosi w imieniu urządzenia <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> o uprawnienia dotyczące strumieniowego odtwarzania treści z aplikacji na innym urządzeniu"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Zezwól aplikacji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na dostęp do tych informacji na Twoim telefonie"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Powiadomienia"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Może odczytywać wszystkie powiadomienia, w tym informacje takie jak kontakty, wiadomości i zdjęcia"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Zdjęcia i multimedia"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Usługi Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> prosi w imieniu urządzenia <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> o uprawnienia dotyczące dostępu do zdjęć, multimediów i powiadomień na telefonie"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"urządzenie"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Mogą one obejmować dane dostęp do Mikrofonu, Aparatu i lokalizacji oraz inne uprawnienia newralgiczne na urządzeniu &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Możesz w dowolnym momencie zmienić uprawnienia na urządzeniu &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacji"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Przycisk – więcej informacji"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS-y"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontakty"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalendarz"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Urządzenia w pobliżu"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Zdjęcia i multimedia"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Powiadomienia"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacje"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Może korzystać z numeru telefonu i informacji o sieci. Wymagane przy nawiązywaniu połączeń, korzystaniu z VoIP oraz poczty głosowej, przekierowywaniu połączeń oraz edytowaniu rejestrów połączeń"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Może odczytywać, tworzyć i edytować listę kontaktów, jak również korzystać z listy wszystkich kont używanych na urządzeniu"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Może odczytywać wszystkie powiadomienia, w tym informacje takie jak kontakty, wiadomości i zdjęcia"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Odtwarzaj strumieniowo aplikacje z telefonu"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
index 60a4079..1a0d4d9 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Esse app é necessário para gerenciar seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. O <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com suas notificações e acessar os apps Telefone, SMS, Contatos, Google Agenda, registros de chamadas e as permissões de dispositivos por perto."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Fazer transmissão dos apps no seu smartphone"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"O app é necessário para gerenciar seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. O <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com suas notificações e acessar os apps Telefone, SMS, Contatos, Google Agenda, registros de chamadas e as permissões de dispositivos por perto."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"O app é necessário para gerenciar seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com estas permissões:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse estas informações do smartphone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Serviços entre dispositivos"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para fazer streaming de apps entre seus dispositivos"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Autorizar que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse estas informações do smartphone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notificações"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contatos, mensagens e fotos"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotos e mídia"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Services"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para acessar fotos, mídia e notificações do smartphone."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Isso pode incluir acesso a microfone, câmera e localização e outras permissões sensíveis no &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Você pode mudar essas permissões a qualquer momento nas configurações do &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ícone do app"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Botão \"Mais informações\""</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Smartphone"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contatos"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Agenda"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivos por perto"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotos e mídia"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notificações"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Acessar seu número de telefone e informações da rede. Necessária para chamadas e VoIP, correio de voz, redirecionamento de chamadas e edição de registros de chamadas"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Ler, criar ou editar sua lista de contatos, e também acessar a lista de contatos de todas as contas usadas no seu dispositivo"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contatos, mensagens e fotos"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Fazer transmissão dos apps no seu smartphone"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
index 8eabaf8..5f3eeeb 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Permita que a app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aceda ao seu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerido pela app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Esta app é necessária para gerir o seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A app <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com as suas notificações e aceder às autorizações do Telefone, SMS, Contactos, Calendário, Registos de chamadas e Dispositivos próximos."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Faça stream das apps do telemóvel"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"A app é necessária para gerir o seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A app <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com as suas notificações e aceder às autorizações do Telemóvel, SMS, Contactos, Calendário, Registos de chamadas e Dispositivos próximos."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"A app é necessária para gerir o seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A app <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com estas autorizações:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permita que a app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aceda a estas informações do seu telemóvel"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Serviços entre dispositivos"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a pedir autorização em nome do seu dispositivo <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para fazer stream de apps entre os seus dispositivos"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Permita que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aceda a estas informações do seu telemóvel"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notificações"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contactos, mensagens e fotos"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotos e multimédia"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Serviços do Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a pedir autorização em nome do seu dispositivo <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para aceder às fotos, ao conteúdo multimédia e às notificações do seu telemóvel"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Isto pode incluir o acesso ao microfone, câmara e localização, bem como a outras autorizações confidenciais no dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Pode alterar estas autorizações em qualquer altura nas Definições do dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ícone da app"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Botão Mais informações"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telemóvel"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contactos"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Calendário"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivos próximos"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotos e multimédia"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notificações"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Pode aceder ao seu número de telefone e informações da rede. É precisa para fazer chamadas e VoIP (voice over Internet Protocol), o correio de voz, redirecionar a chamada e editar registos de chamadas"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Pode ler, criar ou editar a nossa lista de contactos e aceder à lista de todas as contas usadas no seu dispositivo"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contactos, mensagens e fotos"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Faça stream das apps do telemóvel"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt/strings.xml b/packages/CompanionDeviceManager/res/values-pt/strings.xml
index 60a4079..1a0d4d9 100644
--- a/packages/CompanionDeviceManager/res/values-pt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Esse app é necessário para gerenciar seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. O <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com suas notificações e acessar os apps Telefone, SMS, Contatos, Google Agenda, registros de chamadas e as permissões de dispositivos por perto."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Fazer transmissão dos apps no seu smartphone"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"O app é necessário para gerenciar seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. O <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com suas notificações e acessar os apps Telefone, SMS, Contatos, Google Agenda, registros de chamadas e as permissões de dispositivos por perto."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"O app é necessário para gerenciar seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com estas permissões:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse estas informações do smartphone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Serviços entre dispositivos"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para fazer streaming de apps entre seus dispositivos"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Autorizar que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse estas informações do smartphone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notificações"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contatos, mensagens e fotos"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotos e mídia"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Services"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para acessar fotos, mídia e notificações do smartphone."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Isso pode incluir acesso a microfone, câmera e localização e outras permissões sensíveis no &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Você pode mudar essas permissões a qualquer momento nas configurações do &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ícone do app"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Botão \"Mais informações\""</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Smartphone"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Contatos"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Agenda"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivos por perto"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotos e mídia"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notificações"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Acessar seu número de telefone e informações da rede. Necessária para chamadas e VoIP, correio de voz, redirecionamento de chamadas e edição de registros de chamadas"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Ler, criar ou editar sua lista de contatos, e também acessar a lista de contatos de todas as contas usadas no seu dispositivo"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contatos, mensagens e fotos"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Fazer transmissão dos apps no seu smartphone"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ro/strings.xml b/packages/CompanionDeviceManager/res/values-ro/strings.xml
index d1f949d..35c0888 100644
--- a/packages/CompanionDeviceManager/res/values-ro/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ro/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Permite ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să acceseze dispozitivul &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ceas"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Alege un profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> pe care să îl gestioneze &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Această aplicație este necesară pentru a gestiona <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> va putea să interacționeze cu notificările și să acceseze permisiunile pentru Telefon, SMS, Agendă, Calendar, Jurnale de apeluri și Dispozitive din apropiere."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Aplicații"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Să redea în stream aplicațiile telefonului"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Aplicația este necesară pentru a gestiona <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> va putea să interacționeze cu notificările tale și să îți acceseze permisiunile pentru Telefon, SMS, Agendă, Calendar, Jurnale de apeluri și Dispozitive din apropiere."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplicația este necesară pentru a gestiona <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> va putea să interacționeze cu următoarele permisiuni:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permite ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să acceseze aceste informații de pe telefon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servicii pe mai multe dispozitive"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicită permisiunea pentru <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> de a reda în stream aplicații între dispozitivele tale"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Permite ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să acceseze aceste informații de pe telefon"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Notificări"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Poate să citească toate notificările, inclusiv informații cum ar fi agenda, mesajele și fotografiile"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotografii și media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Servicii Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicită permisiunea pentru <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> de a accesa fotografiile, conținutul media și notificările de pe telefon"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispozitiv"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Aici pot fi incluse accesul la microfon, la camera foto, la locație și alte permisiuni de accesare a informațiilor sensibile de pe &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Poți modifica oricând aceste permisiuni din Setările de pe &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Pictograma aplicației"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Butonul Mai multe informații"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Agendă"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispozitive din apropiere"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotografii și media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Notificări"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Aplicații"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Poate să acceseze numărul tău de telefon și informațiile despre rețea. Permisiunea este necesară pentru inițierea apelurilor și VoIP, mesaje vocale, redirecționarea apelurilor și editarea jurnalelor de apeluri"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Poate să citească, să creeze sau să editeze agenda, precum și să acceseze lista tuturor conturilor folosite pe dispozitiv"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Poate să citească toate notificările, inclusiv informații cum ar fi agenda, mesajele și fotografiile"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Să redea în stream aplicațiile telefonului"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ru/strings.xml b/packages/CompanionDeviceManager/res/values-ru/strings.xml
index f519239..612601a 100644
--- a/packages/CompanionDeviceManager/res/values-ru/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ru/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Разрешите приложению &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ к устройству &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"часы"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Выберите устройство (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), которым будет управлять приложение &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Это приложение необходимо для управления устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Приложение \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" получит доступ к уведомлениям, а также следующие разрешения: телефон, SMS, контакты, календарь, список вызовов и устройства поблизости."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Приложения"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Трансляция приложений с телефона."</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Это приложение необходимо для управления устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Приложение \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" получит доступ к уведомлениям, а также следующие разрешения: телефон, SMS, контакты, календарь, список вызовов и устройства поблизости."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Это приложение необходимо для управления устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Приложение \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" получит следующие разрешения:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Разрешите приложению &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; получать эту информацию с вашего телефона"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Сервисы стриминга приложений"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запрашивает разрешение от имени вашего устройства <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>, чтобы транслировать приложения между вашими устройствами."</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Разрешите приложению &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; получать эту информацию с вашего телефона"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Уведомления"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Чтение всех уведомлений, в том числе сведений о контактах, сообщениях и фотографиях."</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Фотографии и медиафайлы"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Сервисы Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запрашивает разрешение от имени вашего устройства <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>, чтобы получить доступ к фотографиям, медиаконтенту и уведомлениям на телефоне."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"устройство"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Сюда может входить доступ к микрофону, камере и данным о местоположении, а также другие разрешения на доступ к конфиденциальной информации на устройстве &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Вы можете в любое время изменить разрешения в настройках устройства &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Значок приложения"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Кнопка информации"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Контакты"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Календарь"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Устройства поблизости"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Фотографии и медиафайлы"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Уведомления"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Приложения"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Доступ к номеру телефона и информации о сети. Это разрешение необходимо, чтобы совершать обычные и VoIP-звонки, отправлять голосовые сообщения, перенаправлять вызовы и редактировать списки вызовов."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Возможность читать, создавать и редактировать список контактов, а также получать доступ к списку всех аккаунтов на вашем устройстве."</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Чтение всех уведомлений, в том числе сведений о контактах, сообщениях и фотографиях."</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Трансляция приложений с телефона."</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-si/strings.xml b/packages/CompanionDeviceManager/res/values-si/strings.xml
index bf5361e..0743dba 100644
--- a/packages/CompanionDeviceManager/res/values-si/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-si/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; හට ඔබගේ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; කළමනාකරණය කිරීමට ඉඩ දෙන්න"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ඔරලෝසුව"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; මගින් කළමනාකරණය කරනු ලැබීමට <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ක් තෝරන්න"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"මෙම යෙදුමට ඔබගේ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> කළමනාකරණය කිරීමට අවශ්‍යයි. <xliff:g id="APP_NAME">%2$s</xliff:g> ඔබගේ දැනුම්දීම් සමඟ අන්තර්ක්‍රියා කිරීමට සහ ඔබගේ දුරකථනය, SMS, සම්බන්ධතා, දින දර්ශනය, ඇමතුම් ලොග සහ අවට උපාංග අවසර වෙත ප්‍රවේශ වීමට ඉඩ දෙනු ඇත."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"යෙදුම්"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"ඔබගේ දුරකථනයේ යෙදුම් ප්‍රවාහ කරන්න"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"ඔබේ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> කළමනා කිරීමට මෙම යෙදුම අවශ්‍යයි. <xliff:g id="APP_NAME">%2$s</xliff:g> ඔබේ දැනුම්දීම් සමග අන්තර්ක්‍රියා කිරීමට සහ ඔබේ දුරකථනය, කෙටිපණිවුඩය, සම්බන්‍ධතා, දිනදර්ශනය, ඇමතුම් ලොග සහ අවට උපාංග අවසර වෙත ප්‍රවේශ වීමට ඉඩ දෙයි."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"ඔබේ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> කළමනා කිරීමට මෙම යෙදුම අවශ්‍යයි. <xliff:g id="APP_NAME">%2$s</xliff:g> හට මෙම අවසර සමග අන්තර්ක්‍රියා කිරීමට අවසර දෙනු ලැබේ:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; හට ඔබගේ දුරකථනයෙන් මෙම තොරතුරුවලට ප්‍රවේශ වීමට ඉඩ දෙන්න"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"හරස්-උපාංග සේවා"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ඔබගේ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> වෙනුවෙන් ඔබගේ උපාංග අතර යෙදුම් ප්‍රවාහ කිරීමට අවසරය ඉල්ලමින් සිටියි"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; හට ඔබගේ දුරකථනයෙන් මෙම තොරතුරුවලට ප්‍රවේශ වීමට ඉඩ දෙන්න"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"දැනුම්දීම්"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"සම්බන්ධතා, පණිවිඩ සහ ඡායාරූප වැනි තොරතුරු ඇතුළුව සියලු දැනුම්දීම් කියවිය හැකිය"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"ඡායාරූප සහ මාධ්‍ය"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play සේවා"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> ඔබගේ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> වෙනුවෙන් ඔබගේ දුරකථනයෙහි ඡායාරූප, මාධ්‍ය සහ දැනුම්දීම් වෙත ප්‍රවේශ වීමට අවසරය ඉල්ලමින් සිටියි"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"උපාංගය"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;මෙයට මයික්‍රෆෝනය, කැමරාව සහ ස්ථාන ප්‍රවේශය සහ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; හි අනෙකුත් සංවේදී අවසර ඇතුළත් විය හැකිය.&lt;/p&gt; &lt;p&gt;ඔබට ඔබගේ සැකසීම් තුළ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; හිදී ඕනෑම වේලාවක මෙම අවසර වෙනස් කළ හැකිය.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"යෙදුම් නිරූපකය"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"වැඩිදුර තොරතුරු බොත්තම"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"දුරකථනය"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"කෙටිපණිවුඩය"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"සම්බන්‍ධතා"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"දිනදර්ශනය"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"අවට උපාංග"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"ඡායාරූප සහ මාධ්‍ය"</string>
+    <string name="permission_notification" msgid="693762568127741203">"දැනුම්දීම්"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"යෙදුම්"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"ඔබේ දුරකථන අංකයට සහ ජාල තොරතුරු වෙත ප්‍රවේශ විය හැක. ඇමතුම් සහ VoIP, හඬ තැපැල්, ඇමතුම් ප්‍රතියෝමුව, සහ ඇමතුම් සටහන් සංස්කරණය සඳහා අවශ්‍යයි."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"අපගේ සම්බන්‍ධතා ලැයිස්තුව කියවීමට, සෑදීමට, හෝ සංස්කරණ කිරීමට මෙන් ම ඔබේ උපාංගය මත භාවිත කරනු ලබන සියලුම ගිණුම් ලැයිස්තු වෙත ප්‍රවේශ වීමට හැකිය"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"සම්බන්ධතා, පණිවිඩ සහ ඡායාරූප වැනි තොරතුරු ඇතුළුව සියලු දැනුම්දීම් කියවිය හැකිය"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"ඔබේ දුරකථනයේ යෙදුම් ප්‍රවාහ කරන්න"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml
index ff19fa5..933c289 100644
--- a/packages/CompanionDeviceManager/res/values-sk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Povoľte aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; prístup k zariadeniu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"hodinky"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Vyberte profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, ktorý bude spravovať aplikácia &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Táto aplikácia sa vyžaduje na správu zariadenia <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude môcť interagovať s vašimi upozorneniami a získa prístup k povoleniam telefónu, SMS, kontaktov, kalendára, zoznamu hovorov a zariadení v okolí."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Aplikácie"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Streamovať aplikácie telefónu"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Aplikácia sa vyžaduje na správu zariadenia <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude môcť interagovať s vašimi upozorneniami a získa prístup k povoleniam telefónu, SMS, kontaktov, kalendára, zoznamu hovorov a zariadení v okolí."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikácia sa vyžaduje na správu zariadenia <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude môcť interagovať s týmito povoleniami:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Povoľte aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; prístup k týmto informáciám z vášho telefónu"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Služby pre viacero zariadení"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje povolenie na streamovanie aplikácií medzi vašimi zariadeniami v mene tohto zariadenia (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>)"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Povoľte aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; prístup k týmto informáciám z vášho telefónu"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Upozornenia"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Môže čítať všetky upozornenia vrátane informácií, ako sú kontakty, správy a fotky"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotky a médiá"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Služby Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje povolenie na prístup k fotkám, médiám a upozorneniam vášho telefónu v mene tohto zariadenia (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>)"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"zariadenie"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Môžu zahŕňať prístup k mikrofónu, kamere a polohe a ďalšie citlivé povolenia v zariadení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Tieto povolenia môžete kedykoľvek zmeniť v Nastaveniach v zariadení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikácie"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Tlačidlo Ďalšie informácie"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefón"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontakty"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalendár"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Zariadenia v okolí"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotky a médiá"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Upozornenia"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikácie"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Má prístup k vášmu telefónnemu číslu a informáciám o sieti. Vyžaduje sa na volanie a VoIP, fungovanie hlasovej schránky, presmerovanie hovorov a upravovanie zoznamu hovorov"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Môže čítať, vytvárať alebo upravovať náš zoznam kontaktov, ako aj získavať prístup k zoznamu všetkých účtov používaných vo vašom zariadení"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Môže čítať všetky upozornenia vrátane informácií, ako sú kontakty, správy a fotky"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streamovať aplikácie telefónu"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sl/strings.xml b/packages/CompanionDeviceManager/res/values-sl/strings.xml
index 14feef6f..676da68 100644
--- a/packages/CompanionDeviceManager/res/values-sl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sl/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dovolite dostop do naprave &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ura"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Izbira naprave <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, ki jo bo upravljala aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Ta aplikacija je potrebna za upravljanje naprave »<xliff:g id="DEVICE_NAME">%1$s</xliff:g>«. Aplikaciji <xliff:g id="APP_NAME">%2$s</xliff:g> bosta omogočena interakcija z obvestili in uporaba dovoljenj Telefon, SMS, Stiki, Koledar, Dnevniki klicev in Naprave v bližini."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Aplikacije"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Pretočno predvajanje aplikacij telefona"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Ta aplikacija je potrebna za upravljanje naprave »<xliff:g id="DEVICE_NAME">%1$s</xliff:g>«. Aplikaciji <xliff:g id="APP_NAME">%2$s</xliff:g> bosta omogočeni interakcija z obvestili in uporaba dovoljenj Telefon, SMS, Stiki, Koledar, Dnevniki klicev in Naprave v bližini."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Ta aplikacija je potrebna za upravljanje naprave »<xliff:g id="DEVICE_NAME">%1$s</xliff:g>«. Aplikaciji <xliff:g id="APP_NAME">%2$s</xliff:g> bo omogočena interakcija s temi dovoljenji:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Dovolite, da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dostopa do teh podatkov v vašem telefonu"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Storitve za zunanje naprave"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> v imenu naprave »<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>« zahteva dovoljenje za pretočno predvajanje aplikacij v vaših napravah."</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Dovolite, da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dostopa do teh podatkov v vašem telefonu"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Obvestila"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Lahko bere vsa obvestila, vključno s podatki, kot so stiki, sporočila in fotografije."</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotografije in predstavnost"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Storitve Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> v imenu naprave »<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>« zahteva dovoljenje za dostop do fotografij, predstavnosti in obvestil v telefonu."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"naprava"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;To lahko vključuje dostop do mikrofona, fotoaparata in lokacije ter druga občutljiva dovoljenja v napravi &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Ta dovoljenja lahko kadar koli spremenite v nastavitvah v napravi &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacije"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Gumb za več informacij"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Stiki"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Koledar"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Naprave v bližini"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotografije in predstavnost"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Obvestila"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacije"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Lahko dostopa do telefonske številke in podatkov o omrežju. Obvezno za opravljanje klicev, uporabo storitve VoIP in glasovne pošte, preusmerjanje klicev in urejanje dnevnikov klicev."</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Lahko bere, ustvarja ali ureja seznam stikov in dostopa do seznama stikov vseh računov, uporabljenih v napravi."</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Lahko bere vsa obvestila, vključno s podatki, kot so stiki, sporočila in fotografije."</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Pretočno predvajanje aplikacij telefona"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sq/strings.xml b/packages/CompanionDeviceManager/res/values-sq/strings.xml
index cefbff8..7bd86ce 100644
--- a/packages/CompanionDeviceManager/res/values-sq/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sq/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Lejo që &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; të ketë qasje te &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ora inteligjente"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Zgjidh një profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> që do të menaxhohet nga &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Ky aplikacion nevojitet për të menaxhuar profilin tënd të <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> do të lejohet të ndërveprojë me njoftimet e tua dhe të ketë qasje te lejet e \"Telefonit\", \"SMS-ve\", \"Kontakteve\", \"Kalendarit\", \"Evidencave të telefonatave\" dhe \"Pajisjeve në afërsi\"."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Aplikacionet"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Transmeto aplikacionet e telefonit tënd"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Aplikacioni nevojitet për të menaxhuar profilin tënd të \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". <xliff:g id="APP_NAME">%2$s</xliff:g> do të lejohet të ndërveprojë me njoftimet e tua dhe të ketë qasje te lejet e \"Telefonit\", \"SMS-ve\", \"Kontakteve\", \"Kalendarit\", \"Evidencave të telefonatave\" dhe të \"Pajisjeve në afërsi\"."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Aplikacioni nevojitet për të menaxhuar profilin tënd të \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". <xliff:g id="APP_NAME">%2$s</xliff:g> do të lejohet të ndërveprojë me këto leje:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Lejo që &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; të ketë qasje në këtë informacion nga telefoni yt"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Shërbimet mes pajisjeve"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> po kërkon leje në emër të <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> për të transmetuar aplikacione ndërmjet pajisjeve të tua"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Lejo që &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; të ketë qasje në këtë informacion nga telefoni yt"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Njoftimet"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Mund të lexojë të gjitha njoftimet, duke përfshirë informacione si kontaktet, mesazhet dhe fotografitë"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotografitë dhe media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Shërbimet e Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> po kërkon leje në emër të <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> për të marrë qasje te fotografitë, media dhe njoftimet e telefonit tënd"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"pajisja"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Kjo mund të përfshijë qasjen te \"Mikrofoni\", \"Kamera\", \"Vendndodhja\" dhe leje të tjera për informacione delikate në &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&amp;gtTi mund t\'i ndryshosh këto leje në çdo kohë te \"Cilësimet\" në &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona e aplikacionit"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Butoni \"Më shumë informacione\""</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefoni"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontaktet"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalendari"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Pajisjet në afërsi"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotografitë dhe media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Njoftimet"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacionet"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Mund të qaset te informacionet e numrit të telefonit dhe të rrjetit. Kërkohet për të bërë telefonata dhe VoIP, postë zanore, ridrejtim të telefonatës dhe modifikim të evidencave të telefonatave"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Mund të lexojë, të krijojë ose të modifikojë listën tënde të kontakteve si dhe të qaset në listën e të gjitha llogarive të përdorura në pajisjen tënde"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Mund të lexojë të gjitha njoftimet, duke përfshirë informacione si kontaktet, mesazhet dhe fotografitë"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Transmeto aplikacionet e telefonit tënd"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sr/strings.xml b/packages/CompanionDeviceManager/res/values-sr/strings.xml
index 0d05e1a..73cf13d 100644
--- a/packages/CompanionDeviceManager/res/values-sr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sr/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Дозволите да &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; приступа уређају &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"сат"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Одаберите профил <xliff:g id="PROFILE_NAME">%1$s</xliff:g> којим ће управљати апликација &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Ова апликација је потребна за управљање уређајем <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ће добити дозволу за интеракцију са обавештењима и приступ дозволама за телефон, SMS, контакте, календар, евиденције позива и уређаје у близини."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Апликације"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Стримујте апликације на телефону"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Апликација је потребна за управљање уређајем <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ће добити дозволу за интеракцију са обавештењима и приступ дозволама за телефон, SMS, контакте, календар, евиденције позива и уређаје у близини."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Апликација је потребна за управљање уређајем <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ће добити дозволу за интеракцију са овим дозволама:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Дозволите да &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; приступа овим информацијама са телефона"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Услуге на више уређаја"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> захтева дозволу у име уређаја <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> за стримовање апликација између уређаја"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Дозволите да &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; приступа овим информацијама са телефона"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Обавештења"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Може да чита сва обавештења, укључујући информације попут контаката, порука и слика"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Слике и медији"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play услуге"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> захтева дозволу у име уређаја <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> за приступ сликама, медијском садржају и обавештењима са телефона"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"уређај"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;То може да обухвата приступ микрофону, камери и локацији, као и другим осетљивим дозволама на уређају &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;У сваком тренутку можете да промените те дозволе у Подешавањима на уређају &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Икона апликације"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Дугме за више информација"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Контакти"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Календар"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Уређаји у близини"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Слике и медији"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Обавештења"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Апликације"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Може да приступа вашем броју телефона и информацијама о мрежи. Неопходно за упућивање позива и VoIP, говорну пошту, преусмеравање позива и измене евиденције позива"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Може да чита, креира или мења листу контаката, као и да приступа листи свих налога који се користе на вашем уређају"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Може да чита сва обавештења, укључујући информације попут контаката, порука и слика"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Стримујте апликације на телефону"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sv/strings.xml b/packages/CompanionDeviceManager/res/values-sv/strings.xml
index e2799b5..ceb7e40 100644
--- a/packages/CompanionDeviceManager/res/values-sv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sv/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Tillåt att &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; får åtkomst till din &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"klocka"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Välj en <xliff:g id="PROFILE_NAME">%1$s</xliff:g> för hantering av &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Appen behövs för att hantera <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tillåtelse att interagera med dina aviseringar och får åtkomst till behörigheterna Telefon, Sms, Kontakter, Kalender, Samtalsloggar och Enheter i närheten."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Appar"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Streama telefonens appar"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Appen behövs för att hantera <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tillåtelse att interagera med dina aviseringar och får åtkomst till behörigheterna Telefon, Sms, Kontakter, Kalender, Samtalsloggar och Enheter i närheten."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Appen behövs för att hantera <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tillåtelse att interagera med följande behörigheter:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Ge &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; åtkomstbehörighet till denna information på telefonen"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Tjänster för flera enheter"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> begär behörighet att låta <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> streama appar mellan enheter"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Ge &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; åtkomstbehörighet till denna information på telefonen"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Aviseringar"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Kan läsa alla aviseringar, inklusive information som kontakter, meddelanden och foton"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Foton och media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play-tjänster"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> begär behörighet att ge <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> åtkomst till foton, mediefiler och aviseringar på telefonen"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"enhet"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Det kan gälla behörighet till mikrofon, kamera och plats och åtkomstbehörighet till andra känsliga uppgifter på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Du kan när som helst ändra behörigheterna i inställningarna på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Appikon"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Knappen Mer information"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"Sms"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontakter"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalender"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Enheter i närheten"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Foton och media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Aviseringar"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Appar"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Kan få åtkomst till ditt telefonnummer och din nätverksinformation. Krävs för att ringa samtal och VoIP-samtal, röstbrevlådan, omdirigering av samtal och redigering av samtalsloggar"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Kan läsa, skapa eller redigera din kontaktlista samt få åtkomst till kontaktlistan för alla konton som används på enheten"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Kan läsa alla aviseringar, inklusive information som kontakter, meddelanden och foton"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streama telefonens appar"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml
index 812b4df..856dab1 100644
--- a/packages/CompanionDeviceManager/res/values-sw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Ruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifikie &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; yako"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"saa"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Chagua <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ili idhibitiwe na &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Programu hii inahitajika ili udhibiti wasifu wako wa <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> itaruhusiwa kufikia arifa zako na kufikia ruhusa zako za Simu, SMS, Anwani, Kalenda, Rekodi za nambari za simu na Vifaa vilivyo karibu."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Programu"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Tiririsha programu za simu yako"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Programu hii inahitajika ili udhibiti <xliff:g id="DEVICE_NAME">%1$s</xliff:g> yako. <xliff:g id="APP_NAME">%2$s</xliff:g> itaruhusiwa kufikia arifa zako na kufikia ruhusa zako za Simu, SMS, Anwani, Kalenda, Rekodi za nambari za simu na Vifaa vilivyo karibu."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Programu hii inahitajika ili udhibiti <xliff:g id="DEVICE_NAME">%1$s</xliff:g> yako. <xliff:g id="APP_NAME">%2$s</xliff:g> itaruhusiwa kufikia ruhusa hizi:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Ruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifikie maelezo haya kutoka kwenye simu yako"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Huduma za kifaa kilichounganishwa kwingine"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Programu ya <xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yako ili itiririshe programu kati ya vifaa vyako"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Ruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifikie maelezo haya kutoka kwenye simu yako"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Arifa"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Inaweza kusoma arifa zote, ikiwa ni pamoja na maelezo kama vile anwani, ujumbe na picha"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Picha na maudhui"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Huduma za Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Programu ya <xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yako ili ifikie picha, maudhui na arifa za simu yako"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"kifaa"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Hii huenda ikajumuisha ufikiaji wa Maikrofoni, Kamera na Mahali, pamoja na ruhusa nyingine nyeti kwenye &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Unaweza kubadilisha ruhusa hizi muda wowote katika Mipangilio yako kwenye &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Aikoni ya Programu"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Kitufe cha Maelezo Zaidi"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Simu"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Anwani"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Kalenda"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Vifaa vilivyo karibu"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Picha na maudhui"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Arifa"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Programu"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Inaweza kufikia nambari yako ya simu na maelezo ya mtandao. Inahitajika kwa ajili ya kupiga simu na VoIP, ujumbe wa sauti, uelekezaji wa simu kwingine na kubadilisha rekodi za nambari za simu"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Inaweza kusoma, kuunda au kubadilisha orodha yetu ya anwani na pia kufikia orodha ya akaunti zote zinazotumiwa kwenye kifaa chako"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Inaweza kusoma arifa zote, ikiwa ni pamoja na maelezo kama vile anwani, ujumbe na picha"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Tiririsha programu za simu yako"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ta/strings.xml b/packages/CompanionDeviceManager/res/values-ta/strings.xml
index fca9e0a..e75b75e 100644
--- a/packages/CompanionDeviceManager/res/values-ta/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ta/strings.xml
@@ -20,9 +20,10 @@
     <string name="confirmation_title" msgid="3785000297483688997">"உங்கள் &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; சாதனத்தை அணுக &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ஆப்ஸை அனுமதியுங்கள்"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"வாட்ச்"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ஆப்ஸ் நிர்வகிக்கக்கூடிய <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ஐத் தேர்ந்தெடுங்கள்"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"உங்கள் <xliff:g id="DEVICE_NAME">%1$s</xliff:g> சாதனத்தை நிர்வகிக்க இந்த ஆப்ஸ் தேவைப்படுகிறது. உங்கள் அறிவிப்புகளைப் பயன்படுத்துவதற்கான அனுமதியையும் மொபைல், மெசேஜ், தொடர்புகள், கேலெண்டர், அழைப்புப் பதிவுகள், அருகிலுள்ள சாதனங்கள் ஆகியவற்றின் அனுமதிகளுக்கான அணுகலையும் <xliff:g id="APP_NAME">%2$s</xliff:g> ஆப்ஸ் பெறும்."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"ஆப்ஸ்"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"உங்கள் மொபைலின் ஆப்ஸை ஸ்ட்ரீம் செய்யலாம்"</string>
+    <!-- no translation found for summary_watch (4085794790142204006) -->
+    <skip />
+    <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
+    <skip />
     <string name="title_app_streaming" msgid="2270331024626446950">"மொபைலில் உள்ள இந்தத் தகவல்களை அணுக, &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ஆப்ஸை அனுமதிக்கவும்"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"பன்முக சாதன சேவைகள்"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"உங்கள் சாதனங்களுக்கு இடையே ஆப்ஸை ஸ்ட்ரீம் செய்ய உங்கள் <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> சார்பாக <xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் அனுமதியைக் கோருகிறது"</string>
@@ -30,10 +31,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"உங்கள் மொபைலிலிருந்து இந்தத் தகவலை அணுக &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ஆப்ஸை அனுமதியுங்கள்"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"அறிவிப்புகள்"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"தொடர்புகள், மெசேஜ்கள், படங்கள் போன்ற தகவல்கள் உட்பட அனைத்து அறிவிப்புகளையும் படிக்க முடியும்"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"படங்கள் மற்றும் மீடியா"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play சேவைகள்"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"உங்கள் மொபைலில் உள்ள படங்கள், மீடியா, அறிவிப்புகள் ஆகியவற்றை அணுக உங்கள் <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> சார்பாக <xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் அனுமதியைக் கோருகிறது"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"சாதனம்"</string>
@@ -45,4 +42,29 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt; சாதனத்தில் உள்ள மைக்ரோஃபோன், கேமரா, இருப்பிட அணுகல், பாதுகாக்கவேண்டிய பிற தகவல்கள் ஆகியவற்றுக்கான அனுமதிகள் இதிலடங்கும்.&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; சாதனத்தில் உள்ள அமைப்புகளில் இந்த அனுமதிகளை எப்போது வேண்டுமானாலும் நீங்கள் மாற்றிக்கொள்ளலாம்."</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"ஆப்ஸ் ஐகான்"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"கூடுதல் தகவல்கள் பட்டன்"</string>
+    <!-- no translation found for permission_phone (2661081078692784919) -->
+    <skip />
+    <!-- no translation found for permission_sms (6337141296535774786) -->
+    <skip />
+    <!-- no translation found for permission_contacts (3858319347208004438) -->
+    <skip />
+    <!-- no translation found for permission_calendar (6805668388691290395) -->
+    <skip />
+    <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
+    <skip />
+    <string name="permission_storage" msgid="6831099350839392343">"படங்கள் மற்றும் மீடியா"</string>
+    <string name="permission_notification" msgid="693762568127741203">"அறிவிப்புகள்"</string>
+    <!-- no translation found for permission_app_streaming (6009695219091526422) -->
+    <skip />
+    <!-- no translation found for permission_phone_summary (6154198036705702389) -->
+    <skip />
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
+    <skip />
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"தொடர்புகள், மெசேஜ்கள், படங்கள் போன்ற தகவல்கள் உட்பட அனைத்து அறிவிப்புகளையும் படிக்க முடியும்"</string>
+    <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
+    <skip />
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-te/strings.xml b/packages/CompanionDeviceManager/res/values-te/strings.xml
index c318796..3a09d68 100644
--- a/packages/CompanionDeviceManager/res/values-te/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-te/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"మీ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;ను యాక్సెస్ చేయడానికి &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ను అనుమతించండి"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"వాచ్"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ద్వారా మేనేజ్ చేయబడటానికి ఒక <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ను ఎంచుకోండి"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"మీ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ను మేనేజ్ చేయడానికి ఈ యాప్ అవసరం. మీ నోటిఫికేషన్‌లతో ఇంటరాక్ట్ అవ్వడానికి అలాగే మీ ఫోన్, SMS, కాంటాక్ట్‌లు, Calendar కాల్ లాగ్‌లు, సమీపంలోని పరికరాల అనుమతులను యాక్సెస్ చేయడానికి <xliff:g id="APP_NAME">%2$s</xliff:g> అనుమతించబడుతుంది."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"యాప్‌లు"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"మీ ఫోన్ యాప్‌లను స్ట్రీమ్ చేయండి"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"మీ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>‌ను మేనేజ్ చేయడానికి ఈ యాప్ అవసరం. మీ నోటిఫికేషన్‌లతో ఇంటరాక్ట్ అవ్వడానికి, అలాగే మీ ఫోన్, SMS, కాంటాక్ట్‌లు, క్యాలెండర్, కాల్ లాగ్‌లు, సమీపంలోని పరికరాల అనుమతులను యాక్సెస్ చేయడానికి <xliff:g id="APP_NAME">%2$s</xliff:g> అనుమతించబడుతుంది."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"మీ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>‌ను మేనేజ్ చేయడానికి ఈ యాప్ అవసరం. ఈ అనుమతులతో ఇంటరాక్ట్ అవ్వడానికి <xliff:g id="APP_NAME">%2$s</xliff:g> అనుమతించబడుతుంది:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"మీ ఫోన్ నుండి ఈ సమాచారాన్ని యాక్సెస్ చేయడానికి &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; యాప్‌ను అనుమతించండి"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"మీ పరికరాల మధ్య యాప్‌లను స్ట్రీమ్ చేయడానికి <xliff:g id="APP_NAME">%1$s</xliff:g> మీ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> తరఫున అనుమతిని రిక్వెస్ట్ చేస్తోంది"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"మీ ఫోన్ నుండి ఈ సమాచారాన్ని యాక్సెస్ చేయడానికి &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; యాప్‌ను అనుమతించండి"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"నోటిఫికేషన్‌లు"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"కాంటాక్ట్‌లు, మెసేజ్‌లు, ఫోటోల వంటి సమాచారంతో సహా అన్ని నోటిఫికేషన్‌లను చదవగలదు"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"ఫోటోలు, మీడియా"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play సర్వీసులు"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> మీ ఫోన్‌లోని ఫోటోలను, మీడియాను, ఇంకా నోటిఫికేషన్‌లను యాక్సెస్ చేయడానికి మీ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> తరఫున అనుమతిని రిక్వెస్ట్ చేస్తోంది"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"పరికరం"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;లో మైక్రోఫోన్, కెమెరా, లొకేషన్ యాక్సెస్, ఇంకా ఇతర గోప్యమైన సమాచార యాక్సెస్ అనుమతులు ఇందులో ఉండవచ్చు.&lt;/p&gt; &lt;p&gt;మీరు &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;లో మీ సెట్టింగ్‌లలో ఎప్పుడైనా ఈ అనుమతులను మార్చవచ్చు.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"యాప్ చిహ్నం"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"మరింత సమాచారం బటన్"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"ఫోన్"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"కాంటాక్ట్‌లు"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"క్యాలెండర్"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"సమీపంలోని పరికరాలు"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"ఫోటోలు, మీడియా"</string>
+    <string name="permission_notification" msgid="693762568127741203">"నోటిఫికేషన్‌లు"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"యాప్‌లు"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"మీ ఫోన్ నంబర్, ఇంకా నెట్‌వర్క్ సమాచారాన్ని యాక్సెస్ చేయగలదు. కాల్స్ చేయడానికి, VoIP కాల్స్ చేయడానికి, వాయిస్ మెయిల్‌కు, కాల్ మళ్లింపునకు, ఇంకా కాల్ లాగ్‌లను ఎడిట్ చేయడానికి ఇది అవసరం"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"మీ కాంటాక్ట్ లిస్ట్‌ను చదవడం, క్రియేట్ చేయడం, లేదా ఎడిట్ చేయడంతో పాటు మీ పరికరంలో ఉపయోగించబడే ఖాతాలన్నింటి లిస్ట్‌ను యాక్సెస్ కూడా చేయగలదు"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"కాంటాక్ట్‌లు, మెసేజ్‌లు, ఫోటోల వంటి సమాచారంతో సహా అన్ని నోటిఫికేషన్‌లను చదవగలదు"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"మీ ఫోన్‌లోని యాప్‌లను స్ట్రీమ్ చేయండి"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-th/strings.xml b/packages/CompanionDeviceManager/res/values-th/strings.xml
index 7c31a21..522c29f 100644
--- a/packages/CompanionDeviceManager/res/values-th/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-th/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"อนุญาตให้ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; เข้าถึง &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ของคุณ"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"นาฬิกา"</string>
     <string name="chooser_title" msgid="2262294130493605839">"เลือก<xliff:g id="PROFILE_NAME">%1$s</xliff:g>ที่จะให้มีการจัดการโดย &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"ต้องใช้แอปนี้ในการจัดการ<xliff:g id="DEVICE_NAME">%1$s</xliff:g> <xliff:g id="APP_NAME">%2$s</xliff:g> จะได้รับอนุญาตให้โต้ตอบกับการแจ้งเตือนและได้รับสิทธิ์เข้าถึงโทรศัพท์, SMS, รายชื่อติดต่อ, ปฏิทิน, บันทึกการโทร และอุปกรณ์ที่อยู่ใกล้เคียง"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"แอป"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"สตรีมแอปของโทรศัพท์คุณ"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"ต้องใช้แอปนี้ในการจัดการ<xliff:g id="DEVICE_NAME">%1$s</xliff:g> <xliff:g id="APP_NAME">%2$s</xliff:g> จะได้รับอนุญาตให้โต้ตอบกับการแจ้งเตือนและได้รับสิทธิ์เข้าถึงโทรศัพท์, SMS, รายชื่อติดต่อ, ปฏิทิน, บันทึกการโทร และอุปกรณ์ที่อยู่ใกล้เคียง"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"ต้องใช้แอปนี้ในการจัดการ<xliff:g id="DEVICE_NAME">%1$s</xliff:g> <xliff:g id="APP_NAME">%2$s</xliff:g> จะได้รับอนุญาตให้โต้ตอบกับสิทธิ์เหล่านี้"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"อนุญาตให้ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; เข้าถึงข้อมูลนี้จากโทรศัพท์ของคุณ"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"บริการหลายอุปกรณ์"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังขอสิทธิ์ในนามของ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> เพื่อสตรีมแอประหว่างอุปกรณ์ต่างๆ ของคุณ"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"อนุญาตให้ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; เข้าถึงข้อมูลนี้จากโทรศัพท์ของคุณ"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"การแจ้งเตือน"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"สามารถอ่านการแจ้งเตือนทั้งหมด รวมถึงข้อมูลอย่างรายชื่อติดต่อ ข้อความ และรูปภาพ"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"รูปภาพและสื่อ"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"บริการ Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังขอสิทธิ์ในนามของ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> เพื่อเข้าถึงรูปภาพ สื่อ และการแจ้งเตือนในโทรศัพท์ของคุณ"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"อุปกรณ์"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;โดยอาจรวมถึงสิทธิ์เข้าถึงไมโครโฟน กล้อง และตำแหน่ง ตลอดจนสิทธิ์ที่มีความละเอียดอ่อนอื่นๆ ใน &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;คุณเปลี่ยนแปลงสิทธิ์เหล่านี้ได้ทุกเมื่อในการตั้งค่าใน &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"ไอคอนแอป"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"ปุ่มข้อมูลเพิ่มเติม"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"โทรศัพท์"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"รายชื่อติดต่อ"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"ปฏิทิน"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"อุปกรณ์ที่อยู่ใกล้เคียง"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"รูปภาพและสื่อ"</string>
+    <string name="permission_notification" msgid="693762568127741203">"การแจ้งเตือน"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"แอป"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"สามารถเข้าถึงหมายเลขโทรศัพท์และข้อมูลเครือข่ายของคุณ จำเป็นสำหรับการโทรและ VoIP, ข้อความเสียง, การเปลี่ยนเส้นทางการโทร และการแก้ไขบันทึกการโทร"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"สามารถอ่าน สร้าง หรือแก้ไขข้อมูลรายชื่อติดต่อของเรา รวมทั้งเข้าถึงข้อมูลรายชื่อติดต่อของทุกบัญชีที่ใช้ในอุปกรณ์ของคุณ"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"สามารถอ่านการแจ้งเตือนทั้งหมด รวมถึงข้อมูลอย่างรายชื่อติดต่อ ข้อความ และรูปภาพ"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"สตรีมแอปของโทรศัพท์คุณ"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-tl/strings.xml b/packages/CompanionDeviceManager/res/values-tl/strings.xml
index e86bc41..79c23aa 100644
--- a/packages/CompanionDeviceManager/res/values-tl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tl/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Payagan ang &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na i-access ang iyong &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"relo"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Pumili ng <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para pamahalaan ng &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Kailangan ang app na ito para mapamahalaan ang iyong <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Papayagan ang <xliff:g id="APP_NAME">%2$s</xliff:g> na makipag-ugnayan sa mga notification mo at i-access ang iyong pahintulot sa Telepono, SMS, Mga Contact, Kalendaryo, Log ng mga tawag, at Mga kalapit na device."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Mga App"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"I-stream ang mga app ng iyong telepono"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Kailangan ang app para mapamahalaan ang iyong <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Papayagan ang <xliff:g id="APP_NAME">%2$s</xliff:g> na makipag-ugnayan sa mga notification mo at i-access ang iyong pahintulot sa Telepono, SMS, Mga Contact, Kalendaryo, Log ng mga tawag, at Mga kalapit na device."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Kailangan ang app para mapamahalaan ang iyong <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Papayagan ang <xliff:g id="APP_NAME">%2$s</xliff:g> na makipag-ugnayan sa mga pahintulot na ito:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Payagan ang &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na i-access ang impormasyong ito sa iyong telepono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Mga cross-device na serbisyo"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Ang <xliff:g id="APP_NAME">%1$s</xliff:g> ay humihiling ng pahintulot sa ngalan ng iyong <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para mag-stream ng mga app sa pagitan ng mga device mo"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Payagan ang &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na i-access ang impormasyon sa iyong telepono"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Mga Notification"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Magbasa ng lahat ng notification, kabilang ang impormasyon gaya ng mga contact, mensahe, at larawan"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Mga larawan at media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Mga serbisyo ng Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Ang <xliff:g id="APP_NAME">%1$s</xliff:g> ay humihiling ng pahintulot sa ngalan ng iyong <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para i-access ang mga larawan, media, at notification ng telepono mo"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Posibleng kabilang dito ang access sa Mikropono, Camera, at Lokasyon, at iba pang pahintulot sa sensitibong impormasyon sa &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Puwede mong baguhin ang mga pahintulot na ito anumang oras sa iyong Mga Setting sa &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Icon ng App"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Button ng Dagdag Impormasyon"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telepono"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Mga Contact"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Calendar"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Mga kalapit na device"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Mga larawan at media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Mga Notification"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Mga App"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Naa-access ang iyong numero ng telepono at impormasyon ng network. Kinakailangan para sa mga pagtawag at VoIP, voicemail, pag-redirect ng tawag, at pag-edit ng mga log ng tawag"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Nakaka-read, nakakagawa, o nakakapag-edit ng aming listahan ng contact, pati na rin nakaka-access ng listahan ng lahat ng account na ginamit sa iyong device"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Magbasa ng lahat ng notification, kabilang ang impormasyon gaya ng mga contact, mensahe, at larawan"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"I-stream ang mga app ng iyong telepono"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-tr/strings.xml b/packages/CompanionDeviceManager/res/values-tr/strings.xml
index 756bcbb..ea4e20a 100644
--- a/packages/CompanionDeviceManager/res/values-tr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tr/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazınıza erişmesi için &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulamasına izin verin"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"saat"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; tarafından yönetilecek bir <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Bu uygulama, <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızın yönetilmesi için gereklidir. <xliff:g id="APP_NAME">%2$s</xliff:g> adlı uygulamanın bildirimlerinizle etkileşimde bulunup Telefon, SMS, Kişiler, Takvim, Arama kayıtları ve Yakındaki cihazlar izinlerinize erişmesine izin verilir."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Uygulamalar"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Telefonunuzun uygulamalarını yayınlama"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Bu uygulama, <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızın yönetilmesi için gereklidir. <xliff:g id="APP_NAME">%2$s</xliff:g> adlı uygulamanın bildirimlerinizle etkileşimde bulunup Telefon, SMS, Kişiler, Takvim, Arama kayıtları ve Yakındaki cihazlar izinlerinize erişmesine izin verilir."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Bu uygulama, <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızın yönetilmesi için gereklidir. <xliff:g id="APP_NAME">%2$s</xliff:g> uygulamasının şu izinlerle etkileşime girmesine izin verilir:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulamasının, telefonunuzdaki bu bilgilere erişmesine izin verin"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cihazlar arası hizmetler"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g>, cihazlarınız arasında uygulama akışı gerçekleştirmek için <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> cihazınız adına izin istiyor"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulamasının, telefonunuzdaki bu bilgilere erişmesine izin verin"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Bildirimler"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Kişiler, mesajlar ve fotoğraflar da dahil olmak üzere tüm bildirimleri okuyabilir"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Fotoğraflar ve medya"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play hizmetleri"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g>, telefonunuzdaki fotoğraf, medya ve bildirimlere erişmek için <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> cihazınız adına izin istiyor"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"cihaz"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Mikrofon, Kamera ve Konum erişiminin yanı sıra &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; cihazındaki diğer hassas bilgilere erişim izinleri de bu kapsamda olabilir.&lt;/p&gt; &lt;p&gt;Bu izinleri istediğiniz zaman &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; cihazındaki Ayarlar bölümünden değiştirebilirsiniz.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Uygulama Simgesi"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Daha Fazla Bilgi Düğmesi"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kişiler"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Takvim"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Yakındaki cihazlar"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Fotoğraflar ve medya"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Bildirimler"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Uygulamalar"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Telefon numaranıza ve ağ bilgilerinize erişebilir. Arama, VoIP, sesli mesaj, arama yönlendirme gibi işlemleri gerçekleştirmek ve arama kayıtlarını düzenlemek için gereklidir"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Kişi listesini okuyabilir, oluşturabilir veya düzenleyebilir, ayrıca cihazınızda kullanılan tüm hesapların listesine erişebilir"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Kişiler, mesajlar ve fotoğraflar da dahil olmak üzere tüm bildirimleri okuyabilir"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefonunuzun uygulamalarını yayınlama"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-uk/strings.xml b/packages/CompanionDeviceManager/res/values-uk/strings.xml
index cc9f6b5..79b03ea 100644
--- a/packages/CompanionDeviceManager/res/values-uk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uk/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Надати додатку &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ до пристрою &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"годинник"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Виберіть <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, яким керуватиме додаток &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Цей додаток потрібен, щоб керувати пристроєм <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Додаток <xliff:g id="APP_NAME">%2$s</xliff:g> зможе взаємодіяти з вашими сповіщеннями й отримає дозволи \"Телефон\", \"SMS\", \"Контакти\", \"Календар\", \"Журнали викликів\" і \"Пристрої поблизу\"."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Додатки"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Транслювати додатки телефона"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Цей додаток потрібен, щоб керувати пристроєм \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Додаток <xliff:g id="APP_NAME">%2$s</xliff:g> зможе взаємодіяти з вашими сповіщеннями й отримає дозволи \"Телефон\", \"SMS\", \"Контакти\", \"Календар\", \"Журнали викликів\" і \"Пристрої поблизу\"."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Цей додаток потрібен, щоб керувати пристроєм \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Додаток <xliff:g id="APP_NAME">%2$s</xliff:g> зможе взаємодіяти з такими дозволами:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Надайте додатку &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ до цієї інформації з телефона"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Сервіси для кількох пристроїв"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> від імені вашого пристрою <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> запитує дозвіл на трансляцію додатків між вашими пристроями"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Надайте пристрою &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ до цієї інформації з телефона"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Сповіщення"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Може читати всі сповіщення, зокрема таку інформацію, як контакти, повідомлення та фотографії"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Фотографії та медіафайли"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Сервіси Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> від імені вашого пристрою <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> запитує дозвіл на доступ до фотографій, медіафайлів і сповіщень вашого телефона"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"пристрій"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Це може бути доступ до мікрофона, камери та геоданих, а також до іншої конфіденційної інформації на пристрої &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Ви можете будь-коли змінити ці дозволи в налаштуваннях на пристрої &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Значок додатка"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Кнопка \"Докладніше\""</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Контакти"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Календар"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Пристрої поблизу"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Фотографії та медіафайли"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Сповіщення"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Додатки"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Може переглядати ваш номер телефону й інформацію про мережу. Потрібно для здійснення викликів і зв’язку через VoIP, голосової пошти, переадресації викликів і редагування журналів викликів"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Може читати, створювати або редагувати список контактів, а також переглядати список усіх облікових записів, які використовуються на вашому пристрої"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Може читати всі сповіщення, зокрема таку інформацію, як контакти, повідомлення та фотографії"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Транслювати додатки телефона"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ur/strings.xml b/packages/CompanionDeviceManager/res/values-ur/strings.xml
index 65b2ba5..71473f7 100644
--- a/packages/CompanionDeviceManager/res/values-ur/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ur/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"‏‎&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;‎ کو اپنے ‎&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;‎ تک رسائی کی اجازت دیں"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"دیکھیں"</string>
     <string name="chooser_title" msgid="2262294130493605839">"‏&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; کے ذریعے نظم کئے جانے کیلئے <xliff:g id="PROFILE_NAME">%1$s</xliff:g> کو منتخب کریں"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"‏آپ کے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کا نظم کرنے کے لئے اس ایپ کی ضرورت ہے۔ <xliff:g id="APP_NAME">%2$s</xliff:g> کو آپ کی اطلاعات کے ساتھ تعامل کرنے اور آپ کے فون، SMS، رابطوں، کیلنڈر، کال لاگز اور قریبی آلات کی اجازتوں تک رسائی کی اجازت ہوگی۔"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"ایپس"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"اپنے فون کی ایپس کی سلسلہ بندی کریں"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"‏آپ کے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کا نظم کرنے کے لئے ایپ کی ضرورت ہے۔ <xliff:g id="APP_NAME">%2$s</xliff:g> کو آپ کی اطلاعات کے ساتھ تعامل کرنے اور آپ کے فون، SMS، رابطوں، کیلنڈر، کال لاگز اور قریبی آلات کی اجازتوں تک رسائی کی اجازت ہوگی۔"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"آپ کے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کا نظم کرنے کے لئے ایپ کی ضرورت ہے۔ <xliff:g id="APP_NAME">%2$s</xliff:g> کو ان اجازتوں کے ساتھ تعامل کرنے کی اجازت ہوگی:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‏اپنے فون سے ان معلومات تک رسائی حاصل کرنے کی &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; کو اجازت دیں"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"کراس ڈیوائس سروسز"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ایپ آپ کے <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> کی جانب سے آپ کے آلات کے درمیان ایپس کی سلسلہ بندی کرنے کی اجازت کی درخواست کر رہی ہے"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"‏اپنے فون سے اس معلومات تک رسائی حاصل کرنے کی &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; کو اجازت دیں"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"اطلاعات"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"رابطوں، پیغامات اور تصاویر جیسی معلومات سمیت تمام اطلاعات پڑھ سکتے ہیں"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"تصاویر اور میڈیا"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"‏Google Play سروسز"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> ایپ آپ کے <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> کی جانب سے آپ کے فون کی تصاویر، میڈیا اور اطلاعات تک رسائی کی اجازت طلب کر رہی ہے"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"آلہ"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"‏&lt;p&gt;اس میں مائیکروفون، کیمرا اور مقام تک رسائی اور ;‎&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&amp;gt پر دیگر حساس اجازتیں شامل ہو سکتی ہیں۔&lt;/p&gt; &lt;p&gt;آپ ‎&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>;&lt;/strong&amp;gt پر کسی بھی وقت اپنی ترتیبات میں ان اجازتوں کو تبدیل کر سکتے ہیں۔&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"ایپ کا آئیکن"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"مزید معلومات کا بٹن"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"فون"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"رابطے"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"کیلنڈر"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"قریبی آلات"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"تصاویر اور میڈیا"</string>
+    <string name="permission_notification" msgid="693762568127741203">"اطلاعات"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"ایپس"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"‏آپ کے فون نمبر اور نیٹ ورک کی معلومات تک رسائی حاصل کر سکتی ہے۔ کالز کرنے اور VoIP، صوتی میل، کال ری ڈائریکٹ، اور کال لاگز میں ترمیم کرنے کے لیے درکار ہے"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"ہماری رابطوں کی فہرست پڑھ سکتی ہے، اسے تخلیق سکتی ہے یا اس میں ترمیم کر سکتی ہے، نیز آپ کے آلے پر استعمال ہونے والے تمام اکاؤنٹس کی فہرست تک رسائی حاصل کر سکتی ہے"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"رابطوں، پیغامات اور تصاویر جیسی معلومات سمیت تمام اطلاعات پڑھ سکتے ہیں"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"اپنے فون کی ایپس کی سلسلہ بندی کریں"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-uz/strings.xml b/packages/CompanionDeviceManager/res/values-uz/strings.xml
index befb370..721a338 100644
--- a/packages/CompanionDeviceManager/res/values-uz/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uz/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; qurilmasiga kirish uchun &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga ruxsat bering"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"soat"</string>
     <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; boshqaradigan <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qurilmasini tanlang"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Bu ilova <xliff:g id="DEVICE_NAME">%1$s</xliff:g> profilini boshqarish uchun kerak. <xliff:g id="APP_NAME">%2$s</xliff:g> ilovasiga bildirishnomalar bilan ishlash va telefon, SMS, kontaktlar, taqvim, chaqiruvlar jurnali va yaqin-atrofdagi qurilmalarga kirishga ruxsat beriladi."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Ilovalar"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Telefondagi ilovalarni translatsiya qilish"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Ilova <xliff:g id="DEVICE_NAME">%1$s</xliff:g> profilini boshqarish uchun kerak. <xliff:g id="APP_NAME">%2$s</xliff:g> ilovasiga bildirishnomalar bilan ishlash va telefon, SMS, kontaktlar, taqvim, chaqiruvlar jurnali va yaqin-atrofdagi qurilmalarga kirishga ruxsat beriladi."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Ilova <xliff:g id="DEVICE_NAME">%1$s</xliff:g> profilini boshqarish uchun kerak. <xliff:g id="APP_NAME">%2$s</xliff:g> ilovasiga quyidagi ruxsatlar bilan ishlashga ruxsat beriladi:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga telefondagi ushbu maʼlumot uchun ruxsat bering"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Qurilmalararo xizmatlar"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"Qurilamalararo ilovalar strimingi uchun <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nomidan ruxsat soʻramoqda"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga telefondagi ushbu maʼlumot uchun ruxsat bering"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Bildirishnomalar"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Barcha bildirishnomalarni, jumladan, kontaktlar, xabarlar va suratlarni oʻqishi mumkin"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Suratlar va media"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play xizmatlari"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"Telefoningizdagi rasm, media va bildirishnomalarga kirish uchun <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nomidan ruxsat soʻramoqda"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"qurilma"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Bunga &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; qurilmasidagi Mikrofon, Kamera, Joylashuv kabi muhim ruxsatlar kirishi mumkin.&lt;/p&gt; &lt;p&gt;Bu ruxsatlarni istalgan vaqt &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; Sozlamalari orqali oʻzgartirish mumkin.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Ilova belgisi"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Batafsil axborot tugmasi"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Kontaktlar"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Taqvim"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Atrofdagi qurilmalar"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Suratlar va media"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Bildirishnomalar"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Ilovalar"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Telefon raqamingiz va tarmoq maʼlumotlariga kira oladi. Telefon qilish va VoIP, ovozli xabar, chaqiruvlarni yoʻnaltirish va chaqiruvlar jurnallarini tahrirlash uchun talab qilinadi"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Kontaktlar roʻyxatini oʻqishi, yaratishi yoki tahrirlashi, shuningdek, qurilmangizda foydalaniladigan barcha hisoblar roʻyxatiga kirishi mumkin"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Barcha bildirishnomalarni, jumladan, kontaktlar, xabarlar va suratlarni oʻqishi mumkin"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefondagi ilovalarni translatsiya qilish"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-vi/strings.xml b/packages/CompanionDeviceManager/res/values-vi/strings.xml
index b29e08c..cb9e558 100644
--- a/packages/CompanionDeviceManager/res/values-vi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-vi/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Cho phép &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; truy cập &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; của bạn"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"đồng hồ"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Chọn một <xliff:g id="PROFILE_NAME">%1$s</xliff:g> sẽ do &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; quản lý"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"Cần có ứng dụng này để quản lý <xliff:g id="DEVICE_NAME">%1$s</xliff:g> của bạn. <xliff:g id="APP_NAME">%2$s</xliff:g> sẽ được phép tương tác với các thông báo và truy cập vào Điện thoại, SMS, Danh bạ, Lịch, Nhật ký cuộc gọi và quyền đối với Thiết bị ở gần."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Ứng dụng"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Truyền các ứng dụng trên điện thoại của bạn"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"Cần có ứng dụng này để quản lý <xliff:g id="DEVICE_NAME">%1$s</xliff:g> của bạn. <xliff:g id="APP_NAME">%2$s</xliff:g> sẽ được phép tương tác với thông báo và truy cập vào Điện thoại, SMS, Danh bạ, Lịch, Nhật ký cuộc gọi và Thiết bị ở gần."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"Cần có ứng dụng này để quản lý <xliff:g id="DEVICE_NAME">%1$s</xliff:g> của bạn. <xliff:g id="APP_NAME">%2$s</xliff:g> được quyền tương tác với những chức năng sau:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Cho phép &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; truy cập vào thông tin này trên điện thoại của bạn"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Dịch vụ trên nhiều thiết bị"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang yêu cầu quyền thay cho <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> để truyền trực tuyến ứng dụng giữa các thiết bị của bạn"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Cho phép &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; truy cập vào thông tin này trên điện thoại của bạn"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Thông báo"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Có thể đọc tất cả các thông báo, kể cả những thông tin như danh bạ, tin nhắn và ảnh"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Ảnh và nội dung nghe nhìn"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Dịch vụ Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang yêu cầu quyền thay cho <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> để truy cập vào ảnh, nội dung nghe nhìn và thông báo trên điện thoại của bạn."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"thiết bị"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Những quyền này có thể bao gồm quyền truy cập vào micrô, máy ảnh và thông tin vị trí, cũng như các quyền truy cập thông tin nhạy cảm khác trên &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Bạn có thể thay đổi những quyền này bất cứ lúc nào trong phần Cài đặt trên &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Biểu tượng ứng dụng"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Nút thông tin khác"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Điện thoại"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"Tin nhắn SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Danh bạ"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Lịch"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Thiết bị ở gần"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Ảnh và nội dung nghe nhìn"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Thông báo"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Ứng dụng"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Có thể truy cập vào thông tin mạng và số điện thoại của bạn. Điện thoại cần được cấp quyền này để gọi điện và gọi bằng dịch vụ VoIP, soạn thư thoại, chuyển hướng cuộc gọi, đồng thời chỉnh sửa nhật ký cuộc gọi"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Có thể tạo, đọc, chỉnh sửa, đồng thời truy cập danh bạ của mọi tài khoản được sử dụng trên thiết bị"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Có thể đọc tất cả các thông báo, kể cả những thông tin như danh bạ, tin nhắn và ảnh"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Truyền các ứng dụng trên điện thoại của bạn"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
index 7b3be44..dad4709 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"允许&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;访问您的&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"手表"</string>
     <string name="chooser_title" msgid="2262294130493605839">"选择要由&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"需要使用此应用,才能管理您的“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”。“<xliff:g id="APP_NAME">%2$s</xliff:g>”将能与通知互动,并可获得电话、短信、通讯录、日历、通话记录和附近的设备访问权限。"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"应用"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"流式传输手机上的应用"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"需要使用此应用,才能管理您的<xliff:g id="DEVICE_NAME">%1$s</xliff:g>。<xliff:g id="APP_NAME">%2$s</xliff:g>将能与通知交互,并可获得电话、短信、通讯录、日历、通话记录和附近设备的访问权限。"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"需要使用此应用,才能管理您的<xliff:g id="DEVICE_NAME">%1$s</xliff:g>。<xliff:g id="APP_NAME">%2$s</xliff:g>可与以下权限交互:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"允许“<xliff:g id="APP_NAME">%1$s</xliff:g>”&lt;strong&gt;&lt;/strong&gt;访问您手机中的这项信息"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"跨设备服务"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正代表您的<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>请求在您的设备之间流式传输应用内容"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"允许 &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; 访问您手机中的这项信息"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"通知"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"可以读取所有通知,包括合同、消息和照片等信息"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"照片和媒体内容"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play 服务"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正代表您的<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>请求访问您手机上的照片、媒体内容和通知"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"设备"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;这可能包括&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;的麦克风、摄像头和位置信息访问权限,以及其他敏感权限。&lt;/p&gt; &lt;p&gt;您可以随时在&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;的“设置”中更改这些权限。&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"应用图标"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"更多信息按钮"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"手机"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"短信"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"通讯录"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"日历"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"附近的设备"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"照片和媒体内容"</string>
+    <string name="permission_notification" msgid="693762568127741203">"通知"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"应用"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"可以访问您的电话号码和网络信息。具备此权限才能实现电话拨打以及 VoIP、语音信箱、电话转接和通话记录编辑功能"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"可以读取、创建或修改您的联系人列表,以及访问您设备上使用的所有帐号的联系人列表"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"可以读取所有通知,包括合同、消息和照片等信息"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"流式传输手机上的应用"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
index ede2369..50c4214 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"允許&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; 存取您的 &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"手錶"</string>
     <string name="chooser_title" msgid="2262294130493605839">"選擇由 &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; 管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"必須使用此應用程式,才能管理<xliff:g id="DEVICE_NAME">%1$s</xliff:g>。<xliff:g id="APP_NAME">%2$s</xliff:g> 將可存取通知、電話、短訊、通訊錄和日曆、通話記錄和附近的裝置權限。"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"應用程式"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"串流播放手機應用程式內容"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"必須使用此應用程式,才能管理「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」。「<xliff:g id="APP_NAME">%2$s</xliff:g>」將可存取通知、電話、短訊、通訊錄和日曆、通話記錄和附近的裝置權限。"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"必須使用此應用程式,才能管理「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」。「<xliff:g id="APP_NAME">%2$s</xliff:g>」將可存取以下權限:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取您手機中的這項資料"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"跨裝置服務"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在為 <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> 要求權限,以在裝置之間串流應用程式內容"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取您手機中的這項資料"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"通知"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"可以讀取所有通知,包括聯絡人、訊息和電話等資訊"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"相片和媒體"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play 服務"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表 <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> 要求權限,以便存取手機上的相片、媒體和通知"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"裝置"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;這可能包括 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt; 的麥克風、相機和位置存取權和其他敏感資料權限。您隨時可透過 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; 變更這些權限。"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"應用程式圖示"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"「更多資料」按鈕"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"手機"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"短訊"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"通訊錄"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"日曆"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"附近的裝置"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"相片和媒體"</string>
+    <string name="permission_notification" msgid="693762568127741203">"通知"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"應用程式"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"可存取您的電話號碼及網絡資訊。必須授予此權限才可撥打電話和 VoIP 通話、留言、轉駁來電及編輯通話記錄"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"可讀取、建立或編輯通訊錄,以及存取您裝置上所有用過的帳戶清單"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"可以讀取所有通知,包括聯絡人、訊息和電話等資訊"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"串流播放手機應用程式內容"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
index 675072b..7cbd9a7 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"手錶"</string>
     <string name="chooser_title" msgid="2262294130493605839">"選擇要讓「<xliff:g id="APP_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"你必須使用這個應用程式,才能管理「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」。「<xliff:g id="APP_NAME">%2$s</xliff:g>」將可存取通知、電話、簡訊、聯絡人和日曆、通話記錄和鄰近裝置的權限。"</string>
-    <string name="permission_apps" msgid="6142133265286656158">"應用程式"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"串流傳輸手機應用程式內容"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"你必須使用這個應用程式,才能管理「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」。「<xliff:g id="APP_NAME">%2$s</xliff:g>」將可存取通知、電話、簡訊、聯絡人和日曆、通話記錄和鄰近裝置的權限。"</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"你必須使用這個應用程式,才能管理「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」。「<xliff:g id="APP_NAME">%2$s</xliff:g>」將可與下列權限互動:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取手機中的這項資訊"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"跨裝置服務"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表你的「<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>」要求必要權限,以便在裝置之間串流傳輸應用程式內容"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取你手機中的這項資訊"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"通知"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"可讀取所有通知,包括聯絡人、訊息和電話等資訊"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"相片和媒體"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play 服務"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表你的「<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>」要求必要權限,以便存取手機上的相片、媒體和通知"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"裝置"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;這可能包括「<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;.&lt;/p&gt;的麥克風、相機和位置資訊存取權和其他機密權限。&lt;/p&gt; &lt;p&gt;你隨時可透過「<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;的設定變更這些權限。&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"應用程式圖示"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"更多資訊按鈕"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"電話"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"簡訊"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"聯絡人"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"日曆"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"鄰近裝置"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"相片和媒體"</string>
+    <string name="permission_notification" msgid="693762568127741203">"通知"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"應用程式"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"可存取你的電話號碼和網路資訊。你必須授予這項權限,才能撥打電話和進行 VoIP 通話、聽取語音留言、轉接來電,以及編輯通話記錄"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"可讀取、建立或編輯你的聯絡人清單,還能存取裝置上所有帳戶的聯絡人清單"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"可讀取所有通知,包括聯絡人、訊息和電話等資訊"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"串流傳輸手機應用程式內容"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zu/strings.xml b/packages/CompanionDeviceManager/res/values-zu/strings.xml
index ec87f2d..231f71c 100644
--- a/packages/CompanionDeviceManager/res/values-zu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zu/strings.xml
@@ -20,9 +20,8 @@
     <string name="confirmation_title" msgid="3785000297483688997">"Vumela i-&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ukuthi ifinyelele i-&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; yakho"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"buka"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Khetha i-<xliff:g id="PROFILE_NAME">%1$s</xliff:g> ezophathwa yi-&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="3002344206574997652">"I-app iyadingeka ukuphatha i-<xliff:g id="DEVICE_NAME">%1$s</xliff:g> yakho. I-<xliff:g id="APP_NAME">%2$s</xliff:g> izovunyelwa ukuthi ihlanganyele nezaziso zakho futhi ifinyelele Ifoni yakho, i-SMS, Oxhumana nabo, Ikhalenda, Amarekhodi wamakholi Nezimvume zamadivayisi aseduze."</string>
-    <string name="permission_apps" msgid="6142133265286656158">"Ama-app"</string>
-    <string name="permission_apps_summary" msgid="798718816711515431">"Sakaza ama-app wefoni yakho"</string>
+    <string name="summary_watch" msgid="4085794790142204006">"I-app iyadingeka ukuphatha i-<xliff:g id="DEVICE_NAME">%1$s</xliff:g> yakho. I-<xliff:g id="APP_NAME">%2$s</xliff:g> izovunyelwa ukuthi ihlanganyele nezaziso zakho futhi ifinyelele Ifoni yakho, i-SMS, Oxhumana nabo, Ikhalenda, Amarekhodi wamakholi Nezimvume zamadivayisi aseduze."</string>
+    <string name="summary_watch_single_device" msgid="1523091550243476756">"I-app iyadingeka ukuphatha i-<xliff:g id="DEVICE_NAME">%1$s</xliff:g> yakho. <xliff:g id="APP_NAME">%2$s</xliff:g> uzovunyelwa ukusebenzisana nalezi zimvume:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Vumela i-&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifinyelele lolu lwazi kusukela efonini yakho"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Amasevisi amadivayisi amaningi"</string>
     <string name="helper_summary_app_streaming" msgid="5977509499890099">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> icela imvume esikhundleni se-<xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yakho ukuze isakaze-bukhoma ama-app phakathi kwamadivayisi akho"</string>
@@ -30,10 +29,6 @@
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Vumela &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ukufinyelela lolu lwazi kusuka efonini yakho"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="permission_notification" msgid="693762568127741203">"Izaziso"</string>
-    <string name="permission_notification_summary" msgid="884075314530071011">"Ingafunda zonke izaziso, okubandakanya ulwazi olufana noxhumana nabo, imilayezo, nezithombe"</string>
-    <string name="permission_storage" msgid="6831099350839392343">"Izithombe nemidiya"</string>
-    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Amasevisi we-Google Play"</string>
     <string name="helper_summary_computer" msgid="9050724687678157852">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> icela imvume esikhundleni se-<xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yakho ukuze ifinyelele izithombe zefoni yakho, imidiya nezaziso"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"idivayisi"</string>
@@ -45,4 +40,20 @@
     <string name="permission_sync_summary" msgid="4866838188678457084">"&lt;p&gt;Lokhu kungase kuhlanganisa Imakrofoni, Ikhamera, kanye Nokufinyelela kwendawo, kanye nezinye izimvume ezibucayi &lt;strong&gt;ku-<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Ungashintsha lezi zimvume nganoma yisiphi isikhathi Kumasethingi akho &lt;strong&gt;ku-<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.&lt;/p&gt;"</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"Isithonjana Se-app"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Inkinobho Yolwazi Olwengeziwe"</string>
+    <string name="permission_phone" msgid="2661081078692784919">"Ifoni"</string>
+    <string name="permission_sms" msgid="6337141296535774786">"I-SMS"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Oxhumana nabo"</string>
+    <string name="permission_calendar" msgid="6805668388691290395">"Ikhalenda"</string>
+    <string name="permission_nearby_devices" msgid="7530973297737123481">"Amadivayisi aseduze"</string>
+    <string name="permission_storage" msgid="6831099350839392343">"Izithombe nemidiya"</string>
+    <string name="permission_notification" msgid="693762568127741203">"Izaziso"</string>
+    <string name="permission_app_streaming" msgid="6009695219091526422">"Ama-app"</string>
+    <string name="permission_phone_summary" msgid="6154198036705702389">"Ingakwazi ukufinyelela inombolo yakho yefoni kanye nolwazi lwenethiwekhi. Iyadingeka ekwenzeni amakholi ne-VoIP, ivoyisimeyili, ukuqondisa kabusha ikholi, nokuhlela amarekhodi amakholi"</string>
+    <string name="permission_sms_summary" msgid="5107174184224165570"></string>
+    <string name="permission_contacts_summary" msgid="7850901746005070792">"Angafunda, asungule, noma ahlele uhlu lwethu loxhumana nabo, futhi afinyelele uhlu lwawo wonke ama-akhawunti asetshenziswa kudivayisi yakho"</string>
+    <string name="permission_calendar_summary" msgid="9070743747408808156"></string>
+    <string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
+    <string name="permission_notification_summary" msgid="884075314530071011">"Ingafunda zonke izaziso, okubandakanya ulwazi olufana noxhumana nabo, imilayezo, nezithombe"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Sakaza ama-app wefoni yakho"</string>
+    <string name="permission_storage_summary" msgid="3918240895519506417"></string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 83dbbf3..97201e2 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -31,16 +31,13 @@
     <string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%2$s</xliff:g>&lt;/strong&gt;</string>
 
     <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile (type) [CHAR LIMIT=NONE] -->
-    <string name="summary_watch">This app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
+    <string name="summary_watch">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
+
+    <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
+    <string name="summary_watch_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
 
     <!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
 
-    <!-- Apps permission will be granted of APP_STREAMING profile [CHAR LIMIT=30] -->
-    <string name="permission_apps">Apps</string>
-
-    <!-- Description of apps permission of APP_STREAMING profile [CHAR LIMIT=NONE] -->
-    <string name="permission_apps_summary">Stream your phone\u2019s apps</string>
-
     <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
     <string name="title_app_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone</string>
 
@@ -66,18 +63,6 @@
     <!-- Description of the privileges the application will get if associated with the companion device of COMPUTER profile (type) [CHAR LIMIT=NONE] -->
     <string name="summary_computer"></string>
 
-    <!-- Notification permission will be granted of COMPUTER profile [CHAR LIMIT=30] -->
-    <string name="permission_notification">Notifications</string>
-
-    <!-- Description of notification permission of COMPUTER profile [CHAR LIMIT=NONE] -->
-    <string name="permission_notification_summary">Can read all notifications, including information like contacts, messages, and photos</string>
-
-    <!-- Storage permission will be granted of COMPUTER profile [CHAR LIMIT=30] -->
-    <string name="permission_storage">Photos and media</string>
-
-    <!-- Description of storage permission of COMPUTER profile [CHAR LIMIT=NONE] -->
-    <string name="permission_storage_summary"></string>
-
     <!-- Title of the helper dialog for COMPUTER profile [CHAR LIMIT=30]. -->
     <string name="helper_title_computer">Google Play services</string>
 
@@ -117,4 +102,57 @@
     <!--Description for information icon [CHAR LIMIT=30]-->
     <string name="vendor_header_button_description">More Information Button</string>
 
+    <!-- ================= Permissions ================= -->
+
+    <!-- Phone permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_phone">Phone</string>
+
+    <!-- SMS permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_sms">SMS</string>
+
+    <!-- Contacts permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_contacts">Contacts</string>
+
+    <!-- Calendar permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_calendar">Calendar</string>
+
+    <!-- Calendar permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_nearby_devices">Nearby devices</string>
+
+    <!-- Storage permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_storage">Photos and media</string>
+
+    <!-- Notification permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_notification">Notifications</string>
+
+    <!-- Apps permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_app_streaming">Apps</string>
+
+    <!-- Description of phone permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_phone_summary">Can access your phone number and network info. Required for making calls and VoIP, voicemail, call redirect, and editing call logs</string>
+
+    <!-- Description of SMS permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <!-- TODO(b/253644212) Need the description for sms permission  -->
+    <string name="permission_sms_summary"></string>
+
+    <!-- Description of contacts permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_contacts_summary">Can read, create, or edit our contact list, as well as access the list of all accounts used on your device</string>
+
+    <!-- Description of calendar permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <!-- TODO(b/253644212) Need the description for calendar permission  -->
+    <string name="permission_calendar_summary"></string>
+
+    <!-- Description of nearby devices' permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <!-- TODO(b/253644212) Need the description for nearby devices' permission  -->
+    <string name="permission_nearby_devices_summary"></string>
+
+    <!-- Description of notification permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_notification_summary">Can read all notifications, including information like contacts, messages, and photos</string>
+
+    <!-- Description of app streaming permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_app_streaming_summary">Stream your phone\u2019s apps</string>
+
+    <!-- Description of storage permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_storage_summary"></string>
+
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index 2000d96..3c75cd5 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -36,7 +36,7 @@
     </style>
 
     <style name="DescriptionTitle"
-           parent="@*android:style/TextAppearance.Widget.Toolbar.Title">
+           parent="@android:style/TextAppearance.DeviceDefault.Medium">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:gravity">center</item>
@@ -46,7 +46,8 @@
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
-    <style name="DescriptionSummary">
+    <style name="DescriptionSummary"
+           parent="@android:style/TextAppearance.DeviceDefault.Medium">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_marginTop">18dp</item>
@@ -61,6 +62,7 @@
         <item name="android:layout_width">70dp</item>
         <item name="android:layout_height">48dp</item>
         <item name="android:textAllCaps">false</item>
+        <item name="android:textSize">14sp</item>
         <item name="android:textColor">@android:color/system_neutral1_900</item>
         <item name="android:background">@drawable/helper_back_button</item>
     </style>
@@ -73,6 +75,7 @@
         <item name="android:textAllCaps">false</item>
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">@android:color/system_neutral1_900</item>
+        <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
         <item name="android:background">@drawable/btn_positive_bottom</item>
     </style>
 
@@ -85,6 +88,7 @@
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">@android:color/system_neutral1_900</item>
         <item name="android:layout_marginTop">4dp</item>
+        <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
         <item name="android:background">@drawable/btn_negative_top</item>
     </style>
 
@@ -93,13 +97,17 @@
         <item name="android:layout_width">100dp</item>
         <item name="android:layout_height">36dp</item>
         <item name="android:textAllCaps">false</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
         <item name="android:background">@drawable/btn_negative_multiple_devices</item>
     </style>
 
     <style name="DeviceListBorder">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">1dp</item>
-        <item name="android:background">@android:color/system_accent1_300</item>
+        <item name="android:layout_marginStart">32dp</item>
+        <item name="android:layout_marginEnd">32dp</item>
+        <item name="android:background">@android:color/system_neutral1_200</item>
     </style>
 
     <style name="Spinner"
@@ -115,4 +123,9 @@
         <item name="android:fillViewport">true</item>
         <item name="android:clipChildren">false</item>
     </style>
+
+    <style name="TextAppearance">
+        <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
+    </style>
+
 </resources>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index a7e1a59..3a3a5d2 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -22,6 +22,7 @@
 import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
 import static android.companion.CompanionDeviceManager.REASON_CANCELED;
 import static android.companion.CompanionDeviceManager.REASON_DISCOVERY_TIMEOUT;
+import static android.companion.CompanionDeviceManager.REASON_INTERNAL_ERROR;
 import static android.companion.CompanionDeviceManager.REASON_USER_REJECTED;
 import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT;
 import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
@@ -30,9 +31,14 @@
 
 import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState;
 import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT;
-import static com.android.companiondevicemanager.PermissionListAdapter.TYPE_APPS;
-import static com.android.companiondevicemanager.PermissionListAdapter.TYPE_NOTIFICATION;
-import static com.android.companiondevicemanager.PermissionListAdapter.TYPE_STORAGE;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_APP_STREAMING;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALENDAR;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CONTACTS;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICES;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NOTIFICATION;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_PHONE;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_SMS;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_STORAGE;
 import static com.android.companiondevicemanager.Utils.getApplicationLabel;
 import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
 import static com.android.companiondevicemanager.Utils.getIcon;
@@ -54,6 +60,9 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.graphics.BlendMode;
+import android.graphics.BlendModeColorFilter;
+import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.net.MacAddress;
 import android.os.Bundle;
@@ -63,6 +72,7 @@
 import android.text.Spanned;
 import android.util.Log;
 import android.view.View;
+import android.view.ViewTreeObserver;
 import android.widget.Button;
 import android.widget.ImageButton;
 import android.widget.ImageView;
@@ -71,12 +81,14 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -139,8 +151,14 @@
     // Present for multiple devices' association requests only.
     private Button mButtonNotAllowMultipleDevices;
 
+    // Present for top and bottom borders for permissions list and device list.
+    private View mBorderTop;
+    private View mBorderBottom;
+
     private LinearLayout mAssociationConfirmationDialog;
-    private LinearLayout mMultipleDeviceList;
+    // Contains device list, permission list and top/bottom borders.
+    private ConstraintLayout mConstraintList;
+    // Only present for self-managed association requests.
     private RelativeLayout mVendorHeader;
 
     // The recycler view is only shown for multiple-device regular association request, after
@@ -149,7 +167,7 @@
     private @Nullable DeviceListAdapter mDeviceAdapter;
 
 
-    // The recycler view is only shown for selfManaged association request.
+    // The recycler view is only shown for selfManaged and singleDevice  association request.
     private @Nullable RecyclerView mPermissionListRecyclerView;
     private @Nullable PermissionListAdapter mPermissionListAdapter;
 
@@ -163,6 +181,8 @@
 
     private @Nullable List<Integer> mPermissionTypes;
 
+    private LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this);
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         if (DEBUG) Log.d(TAG, "onCreate()");
@@ -203,6 +223,7 @@
         initUI();
     }
 
+    @SuppressWarnings("MissingSuperCall") // TODO: Fix me
     @Override
     protected void onNewIntent(Intent intent) {
         // Force cancels the CDM dialog if this activity receives another intent with
@@ -210,10 +231,10 @@
         boolean forCancelDialog = intent.getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION, false);
 
         if (forCancelDialog) {
-
             Log.i(TAG, "Cancelling the user confirmation");
 
-            cancel(false, false);
+            cancel(/* discoveryTimeOut */ false,
+                    /* userRejected */ false, /* internalError */ false);
             return;
         }
 
@@ -240,7 +261,8 @@
 
         // TODO: handle config changes without cancelling.
         if (!isDone()) {
-            cancel(/* discoveryTimeOut */ false, /* userRejected */ false); // will finish()
+            cancel(/* discoveryTimeOut */ false,
+                    /* userRejected */ false, /* internalError */ false); // will finish()
         }
     }
 
@@ -281,10 +303,13 @@
 
         setContentView(R.layout.activity_confirmation);
 
-        mMultipleDeviceList = findViewById(R.id.multiple_device_list);
+        mConstraintList = findViewById(R.id.constraint_list);
         mAssociationConfirmationDialog = findViewById(R.id.association_confirmation);
         mVendorHeader = findViewById(R.id.vendor_header);
 
+        mBorderTop = findViewById(R.id.border_top);
+        mBorderBottom = findViewById(R.id.border_bottom);
+
         mTitle = findViewById(R.id.title);
         mSummary = findViewById(R.id.summary);
 
@@ -325,7 +350,8 @@
     private void onDiscoveryStateChanged(DiscoveryState newState) {
         if (newState == FINISHED_TIMEOUT
                 && CompanionDeviceDiscoveryService.getScanResult().getValue().isEmpty()) {
-            cancel(/* discoveryTimeOut */ true, /* userRejected */ false);
+            cancel(/* discoveryTimeOut */ true,
+                    /* userRejected */ false, /* internalError */ false);
         }
     }
 
@@ -363,12 +389,14 @@
         mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
     }
 
-    private void cancel(boolean discoveryTimeout, boolean userRejected) {
+    private void cancel(boolean discoveryTimeout, boolean userRejected, boolean internalError) {
         if (DEBUG) {
             Log.i(TAG, "cancel(), discoveryTimeout="
                     + discoveryTimeout
                     + ", userRejected="
-                    + userRejected, new Exception("Stack Trace Dump"));
+                    + userRejected
+                    + ", internalError="
+                    + internalError, new Exception("Stack Trace Dump"));
         }
 
         if (isDone()) {
@@ -390,9 +418,12 @@
         } else if (discoveryTimeout) {
             cancelReason = REASON_DISCOVERY_TIMEOUT;
             resultCode = RESULT_DISCOVERY_TIMEOUT;
+        } else if (internalError) {
+            cancelReason = REASON_INTERNAL_ERROR;
+            resultCode = RESULT_INTERNAL_ERROR;
         } else {
             cancelReason = REASON_CANCELED;
-            resultCode = RESULT_CANCELED;
+            resultCode = CompanionDeviceManager.RESULT_CANCELED;
         }
 
         // First send callback to the app directly...
@@ -448,14 +479,16 @@
             }
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
-            setResultAndFinish(null, RESULT_INTERNAL_ERROR);
+            cancel(/* discoveryTimeout */ false,
+                    /* userRejected */ false, /* internalError */ true);
             return;
         }
 
+        // TODO(b/253644212): Add maps for profile -> title, summary, permissions
         switch (deviceProfile) {
             case DEVICE_PROFILE_APP_STREAMING:
                 title = getHtmlFromResources(this, R.string.title_app_streaming, deviceName);
-                mPermissionTypes.add(TYPE_APPS);
+                mPermissionTypes.add(PERMISSION_APP_STREAMING);
                 break;
 
             case DEVICE_PROFILE_AUTOMOTIVE_PROJECTION:
@@ -465,25 +498,27 @@
 
             case DEVICE_PROFILE_COMPUTER:
                 title = getHtmlFromResources(this, R.string.title_computer, deviceName);
-                mPermissionTypes.add(TYPE_NOTIFICATION);
-                mPermissionTypes.add(TYPE_STORAGE);
+                mPermissionTypes.addAll(Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_STORAGE));
                 break;
 
             default:
                 throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
 
+        // Summary is not needed for selfManaged dialog.
         mSummary.setVisibility(View.GONE);
 
-        mPermissionListAdapter.setPermissionType(mPermissionTypes);
-        mPermissionListRecyclerView.setAdapter(mPermissionListAdapter);
-        mPermissionListRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+        setupPermissionList();
 
         mTitle.setText(title);
         mVendorHeaderName.setText(vendorName);
-        mDeviceListRecyclerView.setVisibility(View.GONE);
-        mProfileIcon.setVisibility(View.GONE);
         mVendorHeader.setVisibility(View.VISIBLE);
+        mVendorHeader.setVisibility(View.VISIBLE);
+        mProfileIcon.setVisibility(View.GONE);
+        mDeviceListRecyclerView.setVisibility(View.GONE);
+        // Top and bottom borders should be gone for selfManaged dialog.
+        mBorderTop.setVisibility(View.GONE);
+        mBorderBottom.setVisibility(View.GONE);
     }
 
     private void initUiForSingleDevice(CharSequence appLabel) {
@@ -491,11 +526,15 @@
 
         final String deviceProfile = mRequest.getDeviceProfile();
 
+        mPermissionTypes = new ArrayList<>();
+
         CompanionDeviceDiscoveryService.getScanResult().observe(this,
                 deviceFilterPairs -> updateSingleDeviceUi(
                         deviceFilterPairs, deviceProfile, appLabel));
 
         mSingleDeviceSpinner.setVisibility(View.VISIBLE);
+        // Hide permission list and confirmation dialog first before the
+        // first matched device is found.
         mPermissionListRecyclerView.setVisibility(View.GONE);
         mDeviceListRecyclerView.setVisibility(View.GONE);
         mAssociationConfirmationDialog.setVisibility(View.GONE);
@@ -526,11 +565,20 @@
             title = getHtmlFromResources(this, R.string.confirmation_title, appLabel, deviceName);
             summary = getHtmlFromResources(this, R.string.summary_generic);
             profileIcon = getIcon(this, R.drawable.ic_device_other);
+            // Summary is not needed for null profile.
             mSummary.setVisibility(View.GONE);
+            mConstraintList.setVisibility(View.GONE);
         } else if (deviceProfile.equals(DEVICE_PROFILE_WATCH)) {
-            title = getHtmlFromResources(this, R.string.confirmation_title, appLabel, profileName);
-            summary = getHtmlFromResources(this, R.string.summary_watch, deviceName, appLabel);
+            title = getHtmlFromResources(this, R.string.confirmation_title, appLabel, deviceName);
+            summary = getHtmlFromResources(
+                    this, R.string.summary_watch_single_device, profileName, appLabel);
             profileIcon = getIcon(this, R.drawable.ic_watch);
+
+            mPermissionTypes.addAll(Arrays.asList(
+                    PERMISSION_NOTIFICATION, PERMISSION_PHONE, PERMISSION_SMS, PERMISSION_CONTACTS,
+                    PERMISSION_CALENDAR, PERMISSION_NEARBY_DEVICES));
+
+            setupPermissionList();
         } else {
             throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
@@ -585,8 +633,9 @@
         // "Remove" consent button: users would need to click on the list item.
         mButtonAllow.setVisibility(View.GONE);
         mButtonNotAllow.setVisibility(View.GONE);
+        mDeviceListRecyclerView.setVisibility(View.VISIBLE);
         mButtonNotAllowMultipleDevices.setVisibility(View.VISIBLE);
-        mMultipleDeviceList.setVisibility(View.VISIBLE);
+        mConstraintList.setVisibility(View.VISIBLE);
         mMultipleDeviceSpinner.setVisibility(View.VISIBLE);
     }
 
@@ -626,7 +675,7 @@
         // Disable the button, to prevent more clicks.
         v.setEnabled(false);
 
-        cancel(/* discoveryTimeout */ false, /* userRejected */ true);
+        cancel(/* discoveryTimeout */ false, /* userRejected */ true, /* internalError */ false);
     }
 
     private void onShowHelperDialog(View view) {
@@ -643,6 +692,80 @@
         return mApproved || mCancelled;
     }
 
+    // Set up the mPermissionListRecyclerView, including set up the adapter,
+    // initiate the layoutManager for the recyclerview, add listeners for monitoring the scrolling
+    // and when mPermissionListRecyclerView is fully populated.
+    // Lastly, disable the Allow and Don't allow buttons.
+    private void setupPermissionList() {
+        mPermissionListAdapter.setPermissionType(mPermissionTypes);
+        mPermissionListRecyclerView.setAdapter(mPermissionListAdapter);
+        mPermissionListRecyclerView.setLayoutManager(mPermissionsLayoutManager);
+
+        disableButtons();
+
+        // Enable buttons once users scroll down to the bottom of the permission list.
+        mPermissionListRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+            @Override
+            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+                super.onScrollStateChanged(recyclerView, newState);
+                if (!recyclerView.canScrollVertically(1)) {
+                    enableButtons();
+                }
+            }
+        });
+        // Enable buttons if last item in the permission list is visible to the users when
+        // mPermissionListRecyclerView is fully populated.
+        mPermissionListRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        LinearLayoutManager layoutManager =
+                                (LinearLayoutManager) mPermissionListRecyclerView
+                                        .getLayoutManager();
+                        int lastVisibleItemPosition =
+                                layoutManager.findLastCompletelyVisibleItemPosition();
+                        int numItems = mPermissionListRecyclerView.getAdapter().getItemCount();
+
+                        if (lastVisibleItemPosition >= numItems - 1) {
+                            enableButtons();
+                        }
+
+                        mPermissionListRecyclerView.getViewTreeObserver()
+                                .removeOnGlobalLayoutListener(this);
+                    }
+                });
+
+        mConstraintList.setVisibility(View.VISIBLE);
+        mPermissionListRecyclerView.setVisibility(View.VISIBLE);
+    }
+
+    // Disable and grey out the Allow and Don't allow buttons if the last permission in the
+    // permission list is not visible to the users.
+    private void disableButtons() {
+        mButtonAllow.setEnabled(false);
+        mButtonNotAllow.setEnabled(false);
+        mButtonAllow.setTextColor(
+                getResources().getColor(android.R.color.system_neutral1_400, null));
+        mButtonNotAllow.setTextColor(
+                getResources().getColor(android.R.color.system_neutral1_400, null));
+        mButtonAllow.getBackground().setColorFilter(
+                (new BlendModeColorFilter(Color.LTGRAY,  BlendMode.DARKEN)));
+        mButtonNotAllow.getBackground().setColorFilter(
+                (new BlendModeColorFilter(Color.LTGRAY,  BlendMode.DARKEN)));
+    }
+    // Enable and restore the color for the Allow and Don't allow buttons if the last permission in
+    // the permission list is visible to the users.
+    private void enableButtons() {
+        mButtonAllow.setEnabled(true);
+        mButtonNotAllow.setEnabled(true);
+        mButtonAllow.getBackground().setColorFilter(null);
+        mButtonNotAllow.getBackground().setColorFilter(null);
+        mButtonAllow.setTextColor(
+                getResources().getColor(android.R.color.system_neutral1_900, null));
+        mButtonNotAllow.setTextColor(
+                getResources().getColor(android.R.color.system_neutral1_900, null));
+    }
+
     private final ResultReceiver mOnAssociationCreatedReceiver =
             new ResultReceiver(Handler.getMain()) {
                 @Override
@@ -651,7 +774,7 @@
                         final AssociationInfo association = data.getParcelable(
                                 EXTRA_ASSOCIATION, AssociationInfo.class);
                         requireNonNull(association);
-                        setResultAndFinish(association, RESULT_OK);
+                        setResultAndFinish(association, CompanionDeviceManager.RESULT_OK);
                     } else {
                         setResultAndFinish(null, resultCode);
                     }
@@ -660,7 +783,7 @@
 
     @Override
     public void onShowHelperDialogFailed() {
-        setResultAndFinish(null, RESULT_INTERNAL_ERROR);
+        cancel(/* discoveryTimeout */ false, /* userRejected */ false, /* internalError */ true);
     }
 
     @Override
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
index 895b729..0ee94a2 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
@@ -27,6 +27,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.TextView;
 
@@ -37,37 +38,58 @@
 
 class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.ViewHolder> {
     private final Context mContext;
-
     private List<Integer> mPermissions;
+    // Add the expand buttons if permissions are more than PERMISSION_SIZE in the permission list.
+    private static final int PERMISSION_SIZE = 2;
 
-    static final int TYPE_NOTIFICATION = 0;
-    static final int TYPE_STORAGE = 1;
-    static final int TYPE_APPS = 2;
+    static final int PERMISSION_NOTIFICATION = 0;
+    static final int PERMISSION_STORAGE = 1;
+    static final int PERMISSION_APP_STREAMING = 2;
+    static final int PERMISSION_PHONE = 3;
+    static final int PERMISSION_SMS = 4;
+    static final int PERMISSION_CONTACTS = 5;
+    static final int PERMISSION_CALENDAR = 6;
+    static final int PERMISSION_NEARBY_DEVICES = 7;
 
     private static final Map<Integer, Integer> sTitleMap;
     static {
         final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(TYPE_NOTIFICATION, R.string.permission_notification);
-        map.put(TYPE_STORAGE, R.string.permission_storage);
-        map.put(TYPE_APPS, R.string.permission_apps);
+        map.put(PERMISSION_NOTIFICATION, R.string.permission_notification);
+        map.put(PERMISSION_STORAGE, R.string.permission_storage);
+        map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming);
+        map.put(PERMISSION_PHONE, R.string.permission_phone);
+        map.put(PERMISSION_SMS, R.string.permission_sms);
+        map.put(PERMISSION_CONTACTS, R.string.permission_contacts);
+        map.put(PERMISSION_CALENDAR, R.string.permission_calendar);
+        map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices);
         sTitleMap = unmodifiableMap(map);
     }
 
     private static final Map<Integer, Integer> sSummaryMap;
     static {
         final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(TYPE_NOTIFICATION, R.string.permission_notification_summary);
-        map.put(TYPE_STORAGE, R.string.permission_storage_summary);
-        map.put(TYPE_APPS, R.string.permission_apps_summary);
+        map.put(PERMISSION_NOTIFICATION, R.string.permission_notification_summary);
+        map.put(PERMISSION_STORAGE, R.string.permission_storage_summary);
+        map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming_summary);
+        map.put(PERMISSION_PHONE, R.string.permission_phone_summary);
+        map.put(PERMISSION_SMS, R.string.permission_sms_summary);
+        map.put(PERMISSION_CONTACTS, R.string.permission_contacts_summary);
+        map.put(PERMISSION_CALENDAR, R.string.permission_calendar_summary);
+        map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices_summary);
         sSummaryMap = unmodifiableMap(map);
     }
 
     private static final Map<Integer, Integer> sIconMap;
     static {
         final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(TYPE_NOTIFICATION, R.drawable.ic_notifications);
-        map.put(TYPE_STORAGE, R.drawable.ic_storage);
-        map.put(TYPE_APPS, R.drawable.ic_apps);
+        map.put(PERMISSION_NOTIFICATION, R.drawable.ic_permission_notifications);
+        map.put(PERMISSION_STORAGE, R.drawable.ic_permission_storage);
+        map.put(PERMISSION_APP_STREAMING, R.drawable.ic_permission_app_streaming);
+        map.put(PERMISSION_PHONE, R.drawable.ic_permission_phone);
+        map.put(PERMISSION_SMS, R.drawable.ic_permission_sms);
+        map.put(PERMISSION_CONTACTS, R.drawable.ic_permission_contacts);
+        map.put(PERMISSION_CALENDAR, R.drawable.ic_permission_calendar);
+        map.put(PERMISSION_NEARBY_DEVICES, R.drawable.ic_permission_nearby_devices);
         sIconMap = unmodifiableMap(map);
     }
 
@@ -82,6 +104,29 @@
         ViewHolder viewHolder = new ViewHolder(view);
         viewHolder.mPermissionIcon.setImageDrawable(getIcon(mContext, sIconMap.get(viewType)));
 
+        if (viewHolder.mExpandButton.getTag() == null) {
+            viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more);
+        }
+        // Add expand buttons if the permissions are more than PERMISSION_SIZE in this list.
+        if (mPermissions.size() > PERMISSION_SIZE) {
+            view.setOnClickListener(v -> {
+                if ((Integer) viewHolder.mExpandButton.getTag() == R.drawable.btn_expand_more) {
+                    viewHolder.mExpandButton.setImageResource(R.drawable.btn_expand_less);
+
+                    if (viewHolder.mSummary != null) {
+                        viewHolder.mPermissionSummary.setText(viewHolder.mSummary);
+                    }
+
+                    viewHolder.mPermissionSummary.setVisibility(View.VISIBLE);
+                    viewHolder.mExpandButton.setTag(R.drawable.btn_expand_less);
+                } else {
+                    viewHolder.mExpandButton.setImageResource(R.drawable.btn_expand_more);
+                    viewHolder.mPermissionSummary.setVisibility(View.GONE);
+                    viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more);
+                }
+            });
+        }
+
         return viewHolder;
     }
 
@@ -91,8 +136,15 @@
         final Spanned title = getHtmlFromResources(mContext, sTitleMap.get(type));
         final Spanned summary = getHtmlFromResources(mContext, sSummaryMap.get(type));
 
+        holder.mSummary = summary;
         holder.mPermissionName.setText(title);
-        holder.mPermissionSummary.setText(summary);
+
+        if (mPermissions.size() <= PERMISSION_SIZE) {
+            holder.mPermissionSummary.setText(summary);
+            holder.mExpandButton.setVisibility(View.GONE);
+        } else {
+            holder.mPermissionSummary.setVisibility(View.GONE);
+        }
     }
 
     @Override
@@ -114,11 +166,14 @@
         private final TextView mPermissionName;
         private final TextView mPermissionSummary;
         private final ImageView mPermissionIcon;
+        private final ImageButton mExpandButton;
+        private Spanned mSummary = null;
         ViewHolder(View itemView) {
             super(itemView);
             mPermissionName = itemView.findViewById(R.id.permission_name);
             mPermissionSummary = itemView.findViewById(R.id.permission_summary);
             mPermissionIcon = itemView.findViewById(R.id.permission_icon);
+            mExpandButton = itemView.findViewById(R.id.permission_expand_button);
         }
     }
 
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index 25529bb..d8577c3 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -17,7 +17,11 @@
     static_libs: [
         "androidx.activity_activity-compose",
         "androidx.appcompat_appcompat",
-        "androidx.compose.material_material",
+        "androidx.compose.animation_animation-core",
+        "androidx.compose.foundation_foundation",
+        "androidx.compose.material3_material3",
+        "androidx.compose.material_material-icons-core",
+        "androidx.compose.material_material-icons-extended",
         "androidx.compose.runtime_runtime",
         "androidx.compose.ui_ui",
         "androidx.compose.ui_ui-tooling",
@@ -27,6 +31,7 @@
         "androidx.lifecycle_lifecycle-runtime-ktx",
         "androidx.lifecycle_lifecycle-viewmodel-compose",
         "androidx.recyclerview_recyclerview",
+        "kotlinx-coroutines-core",
     ],
 
     platform_apis: true,
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
index 586ef86..bd27dab 100644
--- a/packages/CredentialManager/AndroidManifest.xml
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -36,8 +36,6 @@
         android:name=".CredentialSelectorActivity"
         android:exported="true"
         android:label="@string/app_name"
-        android:launchMode="singleInstance"
-        android:noHistory="true"
         android:excludeFromRecents="true"
         android:theme="@style/Theme.CredentialSelector">
     </activity>
diff --git a/packages/CredentialManager/res/drawable/ic_face.xml b/packages/CredentialManager/res/drawable/ic_face.xml
new file mode 100644
index 0000000..16fe144
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_face.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<!--TODO: Testing only icon. Remove later. -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        tools:ignore="VectorPath"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="#808080"
+        android:pathData="M9.025,14.275Q8.5,14.275 8.125,13.9Q7.75,13.525 7.75,13Q7.75,12.475 8.125,12.1Q8.5,11.725 9.025,11.725Q9.575,11.725 9.938,12.1Q10.3,12.475 10.3,13Q10.3,13.525 9.938,13.9Q9.575,14.275 9.025,14.275ZM14.975,14.275Q14.425,14.275 14.062,13.9Q13.7,13.525 13.7,13Q13.7,12.475 14.062,12.1Q14.425,11.725 14.975,11.725Q15.5,11.725 15.875,12.1Q16.25,12.475 16.25,13Q16.25,13.525 15.875,13.9Q15.5,14.275 14.975,14.275ZM12,19.925Q15.325,19.925 17.625,17.625Q19.925,15.325 19.925,12Q19.925,11.4 19.85,10.85Q19.775,10.3 19.575,9.775Q19.05,9.9 18.538,9.962Q18.025,10.025 17.45,10.025Q15.2,10.025 13.188,9.062Q11.175,8.1 9.775,6.375Q8.975,8.3 7.5,9.712Q6.025,11.125 4.075,11.85Q4.075,11.9 4.075,11.925Q4.075,11.95 4.075,12Q4.075,15.325 6.375,17.625Q8.675,19.925 12,19.925ZM12,22.2Q9.9,22.2 8.038,21.4Q6.175,20.6 4.788,19.225Q3.4,17.85 2.6,15.988Q1.8,14.125 1.8,12Q1.8,9.875 2.6,8.012Q3.4,6.15 4.788,4.775Q6.175,3.4 8.038,2.6Q9.9,1.8 12,1.8Q14.125,1.8 15.988,2.6Q17.85,3.4 19.225,4.775Q20.6,6.15 21.4,8.012Q22.2,9.875 22.2,12Q22.2,14.125 21.4,15.988Q20.6,17.85 19.225,19.225Q17.85,20.6 15.988,21.4Q14.125,22.2 12,22.2Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_manage_accounts.xml b/packages/CredentialManager/res/drawable/ic_manage_accounts.xml
new file mode 100644
index 0000000..adad2f1
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_manage_accounts.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<!--TODO: Testing only icon. Remove later. -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        tools:ignore="VectorPath"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="#808080"
+        android:pathData="M16.1,21.2 L15.775,19.675Q15.5,19.55 15.25,19.425Q15,19.3 14.75,19.1L13.275,19.575L12.2,17.75L13.375,16.725Q13.325,16.4 13.325,16.112Q13.325,15.825 13.375,15.5L12.2,14.475L13.275,12.65L14.75,13.1Q15,12.925 15.25,12.787Q15.5,12.65 15.775,12.55L16.1,11.025H18.25L18.55,12.55Q18.825,12.65 19.075,12.8Q19.325,12.95 19.575,13.15L21.05,12.65L22.125,14.525L20.95,15.55Q21.025,15.825 21.013,16.137Q21,16.45 20.95,16.725L22.125,17.75L21.05,19.575L19.575,19.1Q19.325,19.3 19.075,19.425Q18.825,19.55 18.55,19.675L18.25,21.2ZM1.8,20.3V17.3Q1.8,16.375 2.275,15.613Q2.75,14.85 3.5,14.475Q4.775,13.825 6.425,13.362Q8.075,12.9 10,12.9Q10.2,12.9 10.4,12.9Q10.6,12.9 10.775,12.95Q9.925,14.85 10.062,16.738Q10.2,18.625 11.4,20.3ZM17.175,18.075Q17.975,18.075 18.55,17.487Q19.125,16.9 19.125,16.1Q19.125,15.3 18.55,14.725Q17.975,14.15 17.175,14.15Q16.375,14.15 15.788,14.725Q15.2,15.3 15.2,16.1Q15.2,16.9 15.788,17.487Q16.375,18.075 17.175,18.075ZM10,11.9Q8.25,11.9 7.025,10.662Q5.8,9.425 5.8,7.7Q5.8,5.95 7.025,4.725Q8.25,3.5 10,3.5Q11.75,3.5 12.975,4.725Q14.2,5.95 14.2,7.7Q14.2,9.425 12.975,10.662Q11.75,11.9 10,11.9Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_other_devices.xml b/packages/CredentialManager/res/drawable/ic_other_devices.xml
new file mode 100644
index 0000000..754648c
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_other_devices.xml
@@ -0,0 +1,15 @@
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="20dp"
+    android:height="20dp"
+    android:viewportWidth="20"
+    android:viewportHeight="20">
+    <path
+        android:name="path"
+        android:pathData="M 7.6 4.72 L 7.6 7.6 L 4.72 7.6 L 4.72 4.72 L 7.6 4.72 Z M 9.04 3.28 L 3.28 3.28 L 3.28 9.04 L 9.04 9.04 L 9.04 3.28 Z M 7.6 12.4 L 7.6 15.28 L 4.72 15.28 L 4.72 12.4 L 7.6 12.4 Z M 9.04 10.96 L 3.28 10.96 L 3.28 16.72 L 9.04 16.72 L 9.04 10.96 Z M 15.28 4.72 L 15.28 7.6 L 12.4 7.6 L 12.4 4.72 L 15.28 4.72 Z M 16.72 3.28 L 10.96 3.28 L 10.96 9.04 L 16.72 9.04 L 16.72 3.28 Z M 10.96 10.96 L 12.4 10.96 L 12.4 12.4 L 10.96 12.4 L 10.96 10.96 Z M 12.4 12.4 L 13.84 12.4 L 13.84 13.84 L 12.4 13.84 L 12.4 12.4 Z M 13.84 10.96 L 15.28 10.96 L 15.28 12.4 L 13.84 12.4 L 13.84 10.96 Z M 10.96 13.84 L 12.4 13.84 L 12.4 15.28 L 10.96 15.28 L 10.96 13.84 Z M 12.4 15.28 L 13.84 15.28 L 13.84 16.72 L 12.4 16.72 L 12.4 15.28 Z M 13.84 13.84 L 15.28 13.84 L 15.28 15.28 L 13.84 15.28 L 13.84 13.84 Z M 15.28 12.4 L 16.72 12.4 L 16.72 13.84 L 15.28 13.84 L 15.28 12.4 Z M 15.28 15.28 L 16.72 15.28 L 16.72 16.72 L 15.28 16.72 L 15.28 15.28 Z M 19.6 5.2 L 17.68 5.2 L 17.68 2.32 L 14.8 2.32 L 14.8 0.4 L 19.6 0.4 L 19.6 5.2 Z M 19.6 19.6 L 19.6 14.8 L 17.68 14.8 L 17.68 17.68 L 14.8 17.68 L 14.8 19.6 L 19.6 19.6 Z M 0.4 19.6 L 5.2 19.6 L 5.2 17.68 L 2.32 17.68 L 2.32 14.8 L 0.4 14.8 L 0.4 19.6 Z M 0.4 0.4 L 0.4 5.2 L 2.32 5.2 L 2.32 2.32 L 5.2 2.32 L 5.2 0.4 L 0.4 0.4 Z"
+        android:fillColor="#000000"
+        android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_other_sign_in.xml b/packages/CredentialManager/res/drawable/ic_other_sign_in.xml
new file mode 100644
index 0000000..8150197
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_other_sign_in.xml
@@ -0,0 +1,36 @@
+<!--
+  ~ 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:name="path"
+        android:pathData="M 20 19 L 12 19 L 12 21 L 20 21 C 21.1 21 22 20.1 22 19 L 22 5 C 22 3.9 21.1 3 20 3 L 12 3 L 12 5 L 20 5 L 20 19 Z"
+        android:fillColor="#000"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_1"
+        android:pathData="M 12 7 L 10.6 8.4 L 13.2 11 L 8.85 11 C 8.42 9.55 7.09 8.5 5.5 8.5 C 3.57 8.5 2 10.07 2 12 C 2 13.93 3.57 15.5 5.5 15.5 C 7.09 15.5 8.42 14.45 8.85 13 L 13.2 13 L 10.6 15.6 L 12 17 L 17 12 L 12 7 Z M 5.5 13.5 C 4.67 13.5 4 12.83 4 12 C 4 11.17 4.67 10.5 5.5 10.5 C 6.33 10.5 7 11.17 7 12 C 7 12.83 6.33 13.5 5.5 13.5 Z"
+        android:fillColor="#000"
+        android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png
new file mode 100644
index 0000000..388d098
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png
Binary files differ
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml
new file mode 100644
index 0000000..9e4f424
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml
@@ -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.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="21dp"
+    android:height="17dp"
+    android:viewportWidth="21"
+    android:viewportHeight="17">
+    <path
+        android:name="path"
+        android:pathData="M 4 2.941 L 20 2.941 L 20 0.941 L 4 0.941 C 2.9 0.941 2 1.841 2 2.941 L 2 13.941 L 0 13.941 L 0 16.941 L 11 16.941 L 11 13.941 L 4 13.941 L 4 2.941 Z M 20 4.941 L 14 4.941 C 13.45 4.941 13 5.391 13 5.941 L 13 15.941 C 13 16.491 13.45 16.941 14 16.941 L 20 16.941 C 20.55 16.941 21 16.491 21 15.941 L 21 5.941 C 21 5.391 20.55 4.941 20 4.941 Z M 15 13.941 L 19 13.941 L 19 6.941 L 15 6.941 L 15 13.941 Z"
+        android:fillColor="#5f6368"
+        android:strokeWidth="1"
+        android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml
new file mode 100644
index 0000000..b6ee4f9
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="19dp"
+    android:height="21dp"
+    android:viewportWidth="19"
+    android:viewportHeight="21">
+    <path
+        android:name="path"
+        android:pathData="M 0.25 8.591 C 0.133 8.508 0.058 8.408 0.025 8.291 C 0.008 8.158 0.05 8.025 0.15 7.891 C 1.183 6.475 2.475 5.375 4.025 4.591 C 5.592 3.808 7.258 3.416 9.025 3.416 C 10.792 3.416 12.458 3.8 14.025 4.566 C 15.592 5.316 16.9 6.408 17.95 7.841 C 18.067 7.991 18.1 8.125 18.05 8.241 C 18.017 8.358 17.95 8.458 17.85 8.541 C 17.75 8.625 17.633 8.666 17.5 8.666 C 17.367 8.65 17.25 8.575 17.15 8.441 C 16.233 7.141 15.05 6.15 13.6 5.466 C 12.167 4.766 10.642 4.416 9.025 4.416 C 7.408 4.416 5.892 4.766 4.475 5.466 C 3.058 6.15 1.883 7.141 0.95 8.441 C 0.85 8.591 0.733 8.675 0.6 8.691 C 0.467 8.708 0.35 8.675 0.25 8.591 Z M 11.85 20.916 C 10.117 20.483 8.7 19.625 7.6 18.341 C 6.5 17.041 5.95 15.458 5.95 13.591 C 5.95 12.758 6.25 12.058 6.85 11.491 C 7.45 10.925 8.175 10.641 9.025 10.641 C 9.875 10.641 10.6 10.925 11.2 11.491 C 11.8 12.058 12.1 12.758 12.1 13.591 C 12.1 14.141 12.308 14.608 12.725 14.991 C 13.142 15.358 13.633 15.541 14.2 15.541 C 14.767 15.541 15.25 15.358 15.65 14.991 C 16.05 14.608 16.25 14.141 16.25 13.591 C 16.25 11.658 15.542 10.033 14.125 8.716 C 12.708 7.4 11.017 6.741 9.05 6.741 C 7.083 6.741 5.392 7.4 3.975 8.716 C 2.558 10.033 1.85 11.65 1.85 13.566 C 1.85 13.966 1.883 14.466 1.95 15.066 C 2.033 15.666 2.217 16.366 2.5 17.166 C 2.55 17.316 2.542 17.45 2.475 17.566 C 2.425 17.683 2.333 17.766 2.2 17.816 C 2.067 17.866 1.933 17.866 1.8 17.816 C 1.683 17.75 1.6 17.65 1.55 17.516 C 1.3 16.866 1.117 16.225 1 15.591 C 0.9 14.941 0.85 14.275 0.85 13.591 C 0.85 11.375 1.65 9.516 3.25 8.016 C 4.867 6.516 6.792 5.766 9.025 5.766 C 11.275 5.766 13.208 6.516 14.825 8.016 C 16.442 9.516 17.25 11.375 17.25 13.591 C 17.25 14.425 16.95 15.125 16.35 15.691 C 15.767 16.241 15.05 16.516 14.2 16.516 C 13.35 16.516 12.617 16.241 12 15.691 C 11.4 15.125 11.1 14.425 11.1 13.591 C 11.1 13.041 10.892 12.583 10.475 12.216 C 10.075 11.833 9.592 11.641 9.025 11.641 C 8.458 11.641 7.967 11.833 7.55 12.216 C 7.15 12.583 6.95 13.041 6.95 13.591 C 6.95 15.208 7.425 16.558 8.375 17.641 C 9.342 18.725 10.583 19.483 12.1 19.916 C 12.25 19.966 12.35 20.05 12.4 20.166 C 12.45 20.283 12.458 20.408 12.425 20.541 C 12.392 20.658 12.325 20.758 12.225 20.841 C 12.125 20.925 12 20.95 11.85 20.916 Z M 3.5 3.366 C 3.367 3.45 3.233 3.475 3.1 3.441 C 2.967 3.391 2.867 3.3 2.8 3.166 C 2.733 3.033 2.717 2.916 2.75 2.816 C 2.783 2.7 2.867 2.6 3 2.516 C 3.933 2.016 4.908 1.633 5.925 1.366 C 6.942 1.1 7.975 0.966 9.025 0.966 C 10.092 0.966 11.133 1.1 12.15 1.366 C 13.167 1.616 14.15 1.983 15.1 2.466 C 15.25 2.55 15.333 2.65 15.35 2.766 C 15.383 2.883 15.375 3 15.325 3.116 C 15.275 3.233 15.192 3.325 15.075 3.391 C 14.958 3.458 14.817 3.45 14.65 3.366 C 13.767 2.916 12.85 2.575 11.9 2.341 C 10.967 2.091 10.008 1.966 9.025 1.966 C 8.058 1.966 7.108 2.083 6.175 2.316 C 5.242 2.533 4.35 2.883 3.5 3.366 Z M 6.45 20.566 C 5.467 19.533 4.708 18.483 4.175 17.416 C 3.658 16.333 3.4 15.058 3.4 13.591 C 3.4 12.075 3.95 10.8 5.05 9.766 C 6.15 8.716 7.475 8.191 9.025 8.191 C 10.575 8.191 11.908 8.716 13.025 9.766 C 14.142 10.8 14.7 12.075 14.7 13.591 C 14.7 13.741 14.65 13.866 14.55 13.966 C 14.467 14.05 14.35 14.091 14.2 14.091 C 14.067 14.091 13.95 14.05 13.85 13.966 C 13.75 13.866 13.7 13.741 13.7 13.591 C 13.7 12.341 13.233 11.3 12.3 10.466 C 11.383 9.616 10.292 9.191 9.025 9.191 C 7.758 9.191 6.667 9.616 5.75 10.466 C 4.85 11.3 4.4 12.341 4.4 13.591 C 4.4 14.941 4.633 16.091 5.1 17.041 C 5.567 17.975 6.25 18.916 7.15 19.866 C 7.25 19.966 7.3 20.083 7.3 20.216 C 7.3 20.35 7.25 20.466 7.15 20.566 C 7.05 20.666 6.933 20.716 6.8 20.716 C 6.667 20.716 6.55 20.666 6.45 20.566 Z M 14 18.866 C 12.517 18.866 11.225 18.366 10.125 17.366 C 9.042 16.366 8.5 15.108 8.5 13.591 C 8.5 13.458 8.542 13.341 8.625 13.241 C 8.725 13.141 8.85 13.091 9 13.091 C 9.15 13.091 9.267 13.141 9.35 13.241 C 9.45 13.341 9.5 13.458 9.5 13.591 C 9.5 14.841 9.95 15.866 10.85 16.666 C 11.75 17.466 12.8 17.866 14 17.866 C 14.1 17.866 14.242 17.858 14.425 17.841 C 14.608 17.825 14.8 17.8 15 17.766 C 15.15 17.733 15.275 17.758 15.375 17.841 C 15.492 17.908 15.567 18.016 15.6 18.166 C 15.633 18.3 15.608 18.416 15.525 18.516 C 15.442 18.616 15.333 18.683 15.2 18.716 C 14.9 18.8 14.633 18.85 14.4 18.866 L 14 18.866 Z"
+        android:fillColor="#5e6144"
+        android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml
new file mode 100644
index 0000000..61800f1
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="20dp"
+    android:height="17dp"
+    android:viewportWidth="20"
+    android:viewportHeight="17">
+    <path
+        android:name="path"
+        android:pathData="M 2 16.941 C 1.45 16.941 0.975 16.75 0.575 16.366 C 0.192 15.966 0 15.491 0 14.941 L 0 2.941 C 0 2.391 0.192 1.925 0.575 1.541 C 0.975 1.141 1.45 0.941 2 0.941 L 18 0.941 C 18.55 0.941 19.017 1.141 19.4 1.541 C 19.8 1.925 20 2.391 20 2.941 L 20 14.941 C 20 15.491 19.8 15.966 19.4 16.366 C 19.017 16.75 18.55 16.941 18 16.941 L 2 16.941 Z M 4.5 11.941 L 5.65 11.941 L 5.65 5.941 L 4.75 5.941 L 3 7.191 L 3.6 8.091 L 4.5 7.441 L 4.5 11.941 Z M 7.6 11.941 L 11.5 11.941 L 11.5 10.941 L 9.15 10.941 L 9.1 10.891 C 9.45 10.558 9.733 10.275 9.95 10.041 C 10.183 9.808 10.367 9.625 10.5 9.491 C 10.8 9.191 11.025 8.891 11.175 8.591 C 11.325 8.291 11.4 7.975 11.4 7.641 C 11.4 7.158 11.217 6.758 10.85 6.441 C 10.483 6.108 10.017 5.941 9.45 5.941 C 9.017 5.941 8.625 6.066 8.275 6.316 C 7.925 6.566 7.683 6.891 7.55 7.291 L 8.55 7.691 C 8.633 7.475 8.75 7.308 8.9 7.191 C 9.067 7.058 9.25 6.991 9.45 6.991 C 9.7 6.991 9.9 7.058 10.05 7.191 C 10.217 7.325 10.3 7.491 10.3 7.691 C 10.3 7.875 10.267 8.05 10.2 8.216 C 10.133 8.366 9.983 8.558 9.75 8.791 L 8.95 9.591 C 8.6 9.941 8.15 10.391 7.6 10.941 L 7.6 11.941 Z M 15 11.941 C 15.6 11.941 16.083 11.775 16.45 11.441 C 16.817 11.108 17 10.675 17 10.141 C 17 9.841 16.917 9.575 16.75 9.341 C 16.583 9.108 16.35 8.925 16.05 8.791 L 16.05 8.741 C 16.283 8.608 16.467 8.441 16.6 8.241 C 16.733 8.025 16.8 7.775 16.8 7.491 C 16.8 7.041 16.625 6.675 16.275 6.391 C 15.925 6.091 15.483 5.941 14.95 5.941 C 14.533 5.941 14.142 6.066 13.775 6.316 C 13.425 6.55 13.2 6.841 13.1 7.191 L 14.1 7.591 C 14.167 7.391 14.275 7.233 14.425 7.116 C 14.575 7 14.75 6.941 14.95 6.941 C 15.167 6.941 15.342 7.008 15.475 7.141 C 15.625 7.258 15.7 7.408 15.7 7.591 C 15.7 7.825 15.617 8.008 15.45 8.141 C 15.283 8.275 15.067 8.341 14.8 8.341 L 14.35 8.341 L 14.35 9.341 L 14.85 9.341 C 15.183 9.341 15.442 9.408 15.625 9.541 C 15.808 9.675 15.9 9.858 15.9 10.091 C 15.9 10.308 15.808 10.5 15.625 10.666 C 15.442 10.816 15.233 10.891 15 10.891 C 14.717 10.891 14.5 10.833 14.35 10.716 C 14.2 10.583 14.067 10.358 13.95 10.041 L 12.95 10.441 C 13.067 10.925 13.3 11.3 13.65 11.566 C 14.017 11.816 14.467 11.941 15 11.941 Z M 2 14.941 L 18 14.941 L 18 2.941 L 2 2.941 L 2 14.941 Z M 2 14.941 L 2 2.941 L 2 14.941 Z"
+        android:fillColor="#5e6144"
+        android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_password.xml b/packages/CredentialManager/res/drawable/ic_password.xml
new file mode 100644
index 0000000..bf3056a
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_password.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:name="path"
+        android:pathData="M 8.71 10.29 C 8.52 10.1 8.28 10 8 10 L 7.75 10 L 7.75 8.75 C 7.75 7.98 7.48 7.33 6.95 6.8 C 6.42 6.27 5.77 6 5 6 C 4.23 6 3.58 6.27 3.05 6.8 C 2.52 7.33 2.25 7.98 2.25 8.75 L 2.25 10 L 2 10 C 1.72 10 1.48 10.1 1.29 10.29 C 1.1 10.48 1 10.72 1 11 L 1 16 C 1 16.28 1.1 16.52 1.29 16.71 C 1.48 16.9 1.72 17 2 17 L 8 17 C 8.28 17 8.52 16.9 8.71 16.71 C 8.9 16.52 9 16.28 9 16 L 9 11 C 9 10.72 8.9 10.48 8.71 10.29 Z M 6.25 10 L 3.75 10 L 3.75 8.75 C 3.75 8.4 3.87 8.1 4.11 7.86 C 4.35 7.62 4.65 7.5 5 7.5 C 5.35 7.5 5.65 7.62 5.89 7.86 C 6.13 8.1 6.25 8.4 6.25 8.75 L 6.25 10 Z M 10 14 L 23 14 L 23 16 L 10 16 Z M 21.5 9 C 21.102 9 20.721 9.158 20.439 9.439 C 20.158 9.721 20 10.102 20 10.5 C 20 10.898 20.158 11.279 20.439 11.561 C 20.721 11.842 21.102 12 21.5 12 C 21.898 12 22.279 11.842 22.561 11.561 C 22.842 11.279 23 10.898 23 10.5 C 23 10.102 22.842 9.721 22.561 9.439 C 22.279 9.158 21.898 9 21.5 9 Z M 16.5 9 C 16.102 9 15.721 9.158 15.439 9.439 C 15.158 9.721 15 10.102 15 10.5 C 15 10.898 15.158 11.279 15.439 11.561 C 15.721 11.842 16.102 12 16.5 12 C 16.898 12 17.279 11.842 17.561 11.561 C 17.842 11.279 18 10.898 18 10.5 C 18 10.102 17.842 9.721 17.561 9.439 C 17.279 9.158 16.898 9 16.5 9 Z M 11.5 9 C 11.102 9 10.721 9.158 10.439 9.439 C 10.158 9.721 10 10.102 10 10.5 C 10 10.898 10.158 11.279 10.439 11.561 C 10.721 11.842 11.102 12 11.5 12 C 11.898 12 12.279 11.842 12.561 11.561 C 12.842 11.279 13 10.898 13 10.5 C 13 10.102 12.842 9.721 12.561 9.439 C 12.279 9.158 11.898 9 11.5 9 Z"
+        android:fillColor="#000"
+        android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
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-af/strings.xml b/packages/CredentialManager/res/values-af/strings.xml
new file mode 100644
index 0000000..377c13f
--- /dev/null
+++ b/packages/CredentialManager/res/values-af/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Eiebewysbestuurder"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Kanselleer"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Gaan voort"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Skep op ’n ander plek"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Stoor in ’n ander plek"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Gebruik ’n ander toestel"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Stoor op ’n ander toestel"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"’n Maklike manier om veilig aan te meld"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gebruik jou vingerafdruk, gesig of skermslot om aan te meld met ’n unieke wagwoordsleutel wat nie vergeet of gesteel kan word nie. Kom meer te wete"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Kies waar om <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"stoor jou wagwoord"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"stoor jou aanmeldinligting"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Skep ’n wagwoordsleutel in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Stoor jou wagwoord in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Stoor jou aanmeldinligting in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Jy kan jou <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> op enige toestel gebruik. Dit is in <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> gestoor vir <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"wagwoordsleutel"</string>
+    <string name="password" msgid="6738570945182936667">"wagwoord"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"aanmeldings"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gebruik <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> vir al jou aanmeldings?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Stel as verstek"</string>
+    <string name="use_once" msgid="9027366575315399714">"Gebruik een keer"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wagwoorde, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> wagwoordsleutels"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wagwoorde"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> wagwoordsleutels"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Wagwoordsleutel"</string>
+    <string name="another_device" msgid="5147276802037801217">"’n Ander toestel"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Ander wagwoordbestuurders"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Maak sigblad toe"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gaan terug na die vorige bladsy"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gebruik jou gestoorde wagwoordsleutel vir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gebruik jou gestoorde aanmelding vir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Kies ’n gestoorde aanmelding vir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Meld op ’n ander manier aan"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nee, dankie"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Gaan voort"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Aanmeldopsies"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Vir <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Geslote wagwoordbestuurders"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tik om te ontsluit"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Bestuur aanmeldings"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Van ’n ander toestel af"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gebruik ’n ander toestel"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-am/strings.xml b/packages/CredentialManager/res/values-am/strings.xml
new file mode 100644
index 0000000..b80fe2c
--- /dev/null
+++ b/packages/CredentialManager/res/values-am/strings.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <!-- no translation found for string_cancel (6369133483981306063) -->
+    <skip />
+    <!-- no translation found for string_continue (1346732695941131882) -->
+    <skip />
+    <!-- no translation found for string_create_in_another_place (1033635365843437603) -->
+    <skip />
+    <!-- no translation found for string_save_to_another_place (7590325934591079193) -->
+    <skip />
+    <!-- no translation found for string_use_another_device (8754514926121520445) -->
+    <skip />
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"ወደ ሌላ መሣሪያ ያስቀምጡ"</string>
+    <!-- no translation found for passkey_creation_intro_title (402553911484409884) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body (7493320456005579290) -->
+    <skip />
+    <!-- no translation found for choose_provider_title (7245243990139698508) -->
+    <skip />
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <!-- no translation found for save_your_password (6597736507991704307) -->
+    <skip />
+    <!-- no translation found for save_your_sign_in_info (7213978049817076882) -->
+    <skip />
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <!-- no translation found for choose_create_option_passkey_title (4146408187146573131) -->
+    <skip />
+    <!-- no translation found for choose_create_option_password_title (8812546498357380545) -->
+    <skip />
+    <!-- no translation found for choose_create_option_sign_in_title (6318246378475961834) -->
+    <skip />
+    <!-- no translation found for choose_create_option_description (4419171903963100257) -->
+    <skip />
+    <!-- no translation found for passkey (632353688396759522) -->
+    <skip />
+    <!-- no translation found for password (6738570945182936667) -->
+    <skip />
+    <!-- no translation found for sign_ins (4710739369149469208) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <!-- no translation found for use_provider_for_all_title (4201020195058980757) -->
+    <skip />
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <!-- no translation found for set_as_default (4415328591568654603) -->
+    <skip />
+    <!-- no translation found for use_once (9027366575315399714) -->
+    <skip />
+    <!-- no translation found for more_options_usage_passwords_passkeys (4794903978126339473) -->
+    <skip />
+    <!-- no translation found for more_options_usage_passwords (1632047277723187813) -->
+    <skip />
+    <!-- no translation found for more_options_usage_passkeys (5390320437243042237) -->
+    <skip />
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <!-- no translation found for another_device (5147276802037801217) -->
+    <skip />
+    <!-- no translation found for other_password_manager (565790221427004141) -->
+    <skip />
+    <!-- no translation found for close_sheet (1393792015338908262) -->
+    <skip />
+    <!-- no translation found for accessibility_back_arrow_button (3233198183497842492) -->
+    <skip />
+    <!-- no translation found for get_dialog_title_use_passkey_for (6236608872708021767) -->
+    <skip />
+    <!-- no translation found for get_dialog_title_use_sign_in_for (5283099528915572980) -->
+    <skip />
+    <!-- no translation found for get_dialog_title_choose_sign_in_for (1361715440877613701) -->
+    <skip />
+    <!-- no translation found for get_dialog_use_saved_passkey_for (4618100798664888512) -->
+    <skip />
+    <!-- no translation found for get_dialog_button_label_no_thanks (8114363019023838533) -->
+    <skip />
+    <!-- no translation found for get_dialog_button_label_continue (6446201694794283870) -->
+    <skip />
+    <!-- no translation found for get_dialog_title_sign_in_options (2092876443114893618) -->
+    <skip />
+    <!-- no translation found for get_dialog_heading_for_username (3456868514554204776) -->
+    <skip />
+    <!-- no translation found for get_dialog_heading_locked_password_managers (8911514851762862180) -->
+    <skip />
+    <!-- no translation found for locked_credential_entry_label_subtext (9213450912991988691) -->
+    <skip />
+    <!-- no translation found for get_dialog_heading_manage_sign_ins (3522556476480676782) -->
+    <skip />
+    <!-- no translation found for get_dialog_heading_from_another_device (1166697017046724072) -->
+    <skip />
+    <!-- no translation found for get_dialog_option_headline_use_a_different_device (8201578814988047549) -->
+    <skip />
+</resources>
diff --git a/packages/CredentialManager/res/values-ar/strings.xml b/packages/CredentialManager/res/values-ar/strings.xml
new file mode 100644
index 0000000..a5c85c5
--- /dev/null
+++ b/packages/CredentialManager/res/values-ar/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"إلغاء"</string>
+    <string name="string_continue" msgid="1346732695941131882">"متابعة"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"الإنشاء في مكان آخر"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"الحفظ في مكان آخر"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"استخدام جهاز آخر"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"الحفظ على جهاز آخر"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"طريقة بسيطة لتسجيل الدخول بأمان"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"استخدِم بصمة إصبعك أو وجهك أو قفل الشاشة لتسجيل الدخول باستخدام مفتاح مرور فريد لا يمكن نسيانه أو سرقته. مزيد من المعلومات"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"اختيار مكان <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"حفظ كلمة المرور"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"حفظ معلومات تسجيل الدخول"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"هل تريد إنشاء مفتاح مرور في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"هل تريد حفظ كلمة مرورك في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"هل تريد حفظ معلومات تسجيل الدخول في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"يمكنك استخدام <xliff:g id="TYPE">%2$s</xliff:g> الخاص بـ \"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g>\" على أي جهاز. ويتم حفظه في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>\" للحساب \"<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>\"."</string>
+    <string name="passkey" msgid="632353688396759522">"مفتاح مرور"</string>
+    <string name="password" msgid="6738570945182936667">"كلمة المرور"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"عمليات تسجيل الدخول"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"هل تريد استخدام \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\" لكل عمليات تسجيل الدخول؟"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"ضبط الخيار كتلقائي"</string>
+    <string name="use_once" msgid="9027366575315399714">"الاستخدام مرة واحدة"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"عدد كلمات المرور هو <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>، و عدد مفاتيح المرور هو <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"عدد كلمات المرور: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"عدد مفاتيح المرور: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"جهاز آخر"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"خدمات مدراء كلمات المرور الأخرى"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"إغلاق ورقة البيانات"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"العودة إلى الصفحة السابقة"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"هل تريد استخدام مفتاح المرور المحفوظ لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"؟"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"هل تريد استخدام بيانات اعتماد تسجيل الدخول المحفوظة لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"؟"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"اختيار بيانات اعتماد تسجيل دخول محفوظة لـ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"تسجيل الدخول بطريقة أخرى"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"لا، شكرًا"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"متابعة"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"خيارات تسجيل الدخول"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"معلومات تسجيل دخول \"<xliff:g id="USERNAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"خدمات إدارة كلمات المرور المقفولة"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"انقر لإلغاء القفل."</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"إداراة عمليات تسجيل الدخول"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"من جهاز آخر"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"استخدام جهاز مختلف"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-as/strings.xml b/packages/CredentialManager/res/values-as/strings.xml
new file mode 100644
index 0000000..4d0ba68
--- /dev/null
+++ b/packages/CredentialManager/res/values-as/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"CredentialManager"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"বাতিল কৰক"</string>
+    <string name="string_continue" msgid="1346732695941131882">"অব্যাহত ৰাখক"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"অন্য ঠাইত সৃষ্টি কৰক"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"অন্য ঠাইত ছেভ কৰক"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"অন্য ডিভাইচ ব্যৱহাৰ কৰক"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"অন্য এটা ডিভাইচত ছেভ কৰক"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"সুৰক্ষিতভাৱে ছাইন ইন কৰাৰ এক সৰল উপায়"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"পাহৰি নোযোৱা অথবা চুৰি কৰিব নোৱৰা এটা অদ্বিতীয় পাছকী ব্যৱহাৰ কৰি ছাইন ইন কৰিবলৈ আপোনাৰ ফিংগাৰপ্ৰিণ্ট, মুখাৱয়ব অথবা স্ক্ৰীন লক ব্যৱহাৰ কৰক। অধিক জানক"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"ক’ত <xliff:g id="CREATETYPES">%1$s</xliff:g> সেয়া বাছনি কৰক"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"আপোনাৰ পাছৱৰ্ড ছেভ কৰক"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"আপোনাৰ ছাইন ইন কৰাৰ তথ্য ছেভ কৰক"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ত পাছকী সৃষ্টি কৰিবনে?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ত আপোনাৰ পাছৱৰ্ড ছেভ কৰিবনে?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ত আপোনাৰ ছাইন ইন কৰাৰ তথ্য ছেভ কৰিবনে?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"আপুনি আপোনাৰ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> যিকোনো ডিভাইচত ব্যৱহাৰ কৰিব পাৰে। এইটো <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>ৰ বাবে <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>ত ছেভ কৰা হৈছে"</string>
+    <string name="passkey" msgid="632353688396759522">"পাছকী"</string>
+    <string name="password" msgid="6738570945182936667">"পাছৱৰ্ড"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"ছাইন-ইন"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"আপোনাৰ আটাইবোৰ ছাইন ইনৰ বাবে <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ব্যৱহাৰ কৰিবনে?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"ডিফ’ল্ট হিচাপে ছেট কৰক"</string>
+    <string name="use_once" msgid="9027366575315399714">"এবাৰ ব্যৱহাৰ কৰক"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> টা পাছৱৰ্ড, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> টা পাছকী"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> টা পাছৱৰ্ড"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> টা পাছকী"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"পাছকী"</string>
+    <string name="another_device" msgid="5147276802037801217">"অন্য এটা ডিভাইচ"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"অন্য পাছৱৰ্ড পৰিচালক"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"শ্বীট বন্ধ কৰক"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"পূৰ্বৱৰ্তী পৃষ্ঠালৈ ঘূৰি যাওক"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে আপোনাৰ ছেভ হৈ থকা পাছকী ব্যৱহাৰ কৰিবনে?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে আপোনাৰ ছেভ হৈ থকা ছাইন ইন তথ্য ব্যৱহাৰ কৰিবনে?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে ছেভ হৈ থকা এটা ছাইন ইন বাছনি কৰক"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"অন্য উপায়েৰে ছাইন ইন কৰক"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"নালাগে, ধন্যবাদ"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"অব্যাহত ৰাখক"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ছাইন ইনৰ বিকল্প"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>ৰ বাবে"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"লক হৈ থকা পাছৱৰ্ড পৰিচালক"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"আনলক কৰিবলৈ টিপক"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ছাইন ইন পৰিচালনা কৰক"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"অন্য এটা ডিভাইচৰ পৰা"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"অন্য এটা ডিভাইচ ব্যৱহাৰ কৰক"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-az/strings.xml b/packages/CredentialManager/res/values-az/strings.xml
new file mode 100644
index 0000000..14313f7
--- /dev/null
+++ b/packages/CredentialManager/res/values-az/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Giriş Məlumatları Meneceri"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Ləğv edin"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Davam edin"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Başqa yerdə yaradın"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Başqa yerdə yadda saxlayın"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Digər cihaz istifadə edin"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Başqa cihazda yadda saxlayın"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Təhlükəsiz daxil olmağın sadə yolu"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Unutmaq və ya oğurlamaq mümkün olmayan unikal giriş açarı ilə daxil olmaq üçün barmaq izi, üz və ya ekran kilidindən istifadə edin. Ətraflı məlumat"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> üçün yer seçin"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"parolunuzu yadda saxlayın"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"giriş məlumatınızı yadda saxlayın"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xidmətində giriş açarı yaradılsın?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Parol <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xidmətində saxlanılsın?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Giriş məlumatınız <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xidmətində saxlanılsın?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> domenini istənilən cihazda istifadə edə bilərsiniz. <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> üçün <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> xidmətində saxlanılıb"</string>
+    <string name="passkey" msgid="632353688396759522">"giriş açarı"</string>
+    <string name="password" msgid="6738570945182936667">"parol"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"girişlər"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Bütün girişlər üçün <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> istifadə edilsin?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Defolt olaraq seçin"</string>
+    <string name="use_once" msgid="9027366575315399714">"Bir dəfə istifadə edin"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parol, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> giriş açarı"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parol"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> giriş açarı"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Giriş açarı"</string>
+    <string name="another_device" msgid="5147276802037801217">"Digər cihaz"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Digər parol menecerləri"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Səhifəni bağlayın"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Əvvəlki səhifəyə qayıdın"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün yadda saxlanmış giriş açarı istifadə edilsin?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün yadda saxlanmış girişdən istifadə edilsin?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün yadda saxlanmış girişi seçin"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Başqa üsulla daxil olun"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Xeyr, təşəkkürlər"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Davam edin"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Giriş seçimləri"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> üçün"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Kilidli parol menecerləri"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Kilidi açmaq üçün tıklayın"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Girişləri idarə edin"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Başqa cihazdan"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Başqa cihaz istifadə edin"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..c58ec14
--- /dev/null
+++ b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Otkaži"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Nastavi"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Napravi na drugom mestu"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Sačuvaj na drugom mestu"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Koristi drugi uređaj"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Sačuvaj na drugi uređaj"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednostavan način da se bezbedno prijavljujete"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Koristite otisak prsta, zaključavanje licem ili zaključavanje ekrana da biste se prijavili pomoću jedinstvenog pristupnog koda koji ne može da se zaboravi ili ukrade. Saznajte više"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Odaberite lokaciju za: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"sačuvajte lozinku"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"sačuvajte podatke o prijavljivanju"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Želite da napravite pristupni kôd kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Želite da sačuvate lozinku kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Želite da sačuvate podatke o prijavljivanju kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Možete da koristite tip domena <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> na bilo kom uređaju. Čuva se kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> za: <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"pristupni kôd"</string>
+    <string name="password" msgid="6738570945182936667">"lozinka"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"prijavljivanja"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite da za sva prijavljivanja koristite: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Podesi kao podrazumevano"</string>
+    <string name="use_once" msgid="9027366575315399714">"Koristi jednom"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, pristupnih kodova:<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"Lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Pristupnih kodova: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Drugi uređaj"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Drugi menadžeri lozinki"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Zatvorite tabelu"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Vratite se na prethodnu stranicu"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Želite da koristite sačuvani pristupni kôd za: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Želite da koristite sačuvane podatke za prijavljivanje za: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Odaberite sačuvano prijavljivanje za: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prijavite se na drugi način"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ne, hvala"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Nastavi"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcije za prijavljivanje"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Za: <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Menadžeri zaključanih lozinki"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Dodirnite da biste otključali"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Upravljajte prijavljivanjima"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Sa drugog uređaja"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Koristi drugi uređaj"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-be/strings.xml b/packages/CredentialManager/res/values-be/strings.xml
new file mode 100644
index 0000000..3c23afd
--- /dev/null
+++ b/packages/CredentialManager/res/values-be/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Скасаваць"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Далей"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Стварыць у іншым месцы"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Захаваць у іншым месцы"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Скарыстаць іншую прыладу"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Захаваць на іншую прыладу"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Просты спосаб бяспечнага ўваходу"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Для ўваходу з унікальным ключом доступу, які нельга згубіць ці ўкрасці, можна скарыстаць адбітак пальца, распазнаванне твару ці разблакіроўку экрана. Даведацца больш"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Выберыце, дзе <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"захаваць пароль"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"захаваць інфармацыю пра спосаб уваходу"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Стварыць ключ доступу ў папцы \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Захаваць пароль у папку \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Захаваць інфармацыю пра спосаб уваходу ў папку \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Вы можаце выкарыстоўваць <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> на любой прыладзе. Даныя для \"<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>\" захоўваюцца ў папку \"<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>\""</string>
+    <string name="passkey" msgid="632353688396759522">"ключ доступу"</string>
+    <string name="password" msgid="6738570945182936667">"пароль"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"спосабы ўваходу"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Выкарыстоўваць папку \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\" для ўсіх спосабаў уваходу?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Выкарыстоўваць стандартна"</string>
+    <string name="use_once" msgid="9027366575315399714">"Скарыстаць адзін раз"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Пароляў: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, ключоў доступу: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"Пароляў: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Ключоў доступу: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Іншая прылада"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Іншыя спосабы ўваходу"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Закрыць аркуш"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Вярнуцца да папярэдняй старонкі"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Скарыстаць захаваны ключ доступу для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Скарыстаць захаваныя спосабы ўваходу для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Выберыце захаваны спосаб уваходу для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Увайсці іншым спосабам"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Не, дзякуй"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Далей"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Спосабы ўваходу"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Для карыстальніка <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заблакіраваныя спосабы ўваходу"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Націсніце, каб разблакіраваць"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Кіраваць спосабамі ўваходу"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"З іншай прылады"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Скарыстаць іншую прыладу"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-bg/strings.xml b/packages/CredentialManager/res/values-bg/strings.xml
new file mode 100644
index 0000000..af7eb17
--- /dev/null
+++ b/packages/CredentialManager/res/values-bg/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Отказ"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Напред"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Създаване другаде"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Запазване на друго място"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Използване на друго устройство"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Запазване на друго устройство"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Лесен начин за безопасно влизане в профил"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Използвайте отпечатъка, лицето или опцията си за заключване на екрана, за да влизате в профила си с помощта на уникален код за достъп, който не може да бъде забравен или откраднат. Научете повече"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Изберете място за <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"запазване на паролата ви"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"запазване на данните ви за вход"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Да се създаде ли код за достъп в(ъв) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Искате ли да запазите паролата си в(ъв) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Искате ли да запазите данните си за вход в(ъв) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Можете да използвате <xliff:g id="TYPE">%2$s</xliff:g> за <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> на всяко устройство. Тези данни се запазват в(ъв) <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> за <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"код за достъп"</string>
+    <string name="password" msgid="6738570945182936667">"парола"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"данни за вход"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Да се използва ли <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> за всичките ви данни за вход?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Задаване като основно"</string>
+    <string name="use_once" msgid="9027366575315399714">"Еднократно използване"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> пароли, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> кода за достъп"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> пароли"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> кода за достъп"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Друго устройство"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Други мениджъри на пароли"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Затваряне на таблицата"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Назад към предишната страница"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Да се използва ли запазеният ви код за достъп за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Да се използват ли запазените ви данни за вход за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Изберете запазени данни за вход за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Влизане в профила по друг начин"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Не, благодаря"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Напред"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Опции за влизане в профила"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"За <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заключени мениджъри на пароли"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Докоснете, за да отключите"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Управление на данните за вход"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"От друго устройство"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Използване на друго устройство"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-bn/strings.xml b/packages/CredentialManager/res/values-bn/strings.xml
new file mode 100644
index 0000000..152e5bd
--- /dev/null
+++ b/packages/CredentialManager/res/values-bn/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"বাতিল করুন"</string>
+    <string name="string_continue" msgid="1346732695941131882">"চালিয়ে যান"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"অন্য জায়গায় তৈরি করুন"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"অন্য জায়গায় সেভ করুন"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"অন্য ডিভাইস ব্যবহার করুন"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"অন্য ডিভাইসে সেভ করুন"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"নিরাপদে সাইন-ইন করার একটি সহজ উপায়"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"একটি অনন্য পাসকী ব্যবহার করে সাইন-ইন করতে আপনার ফিঙ্গারপ্রিন্ট, মুখ বা স্ক্রিন লক ব্যবহার করুন যেটি ভুলে যাবেন না বা হারিয়ে যাবেন না। আরও জানুন"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> কোথায় সেভ করবেন তা বেছে নিন"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"আপনার পাসওয়ার্ড সেভ করুন"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"আপনার সাইন-ইন করা সম্পর্কিত তথ্য সেভ করুন"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-এ পাসকী তৈরি করবেন?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"আপনার পাসওয়ার্ড <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-এ সেভ করবেন?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"আপনার সাইন-ইন করা সম্পর্কিত তথ্য <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-এ সেভ করবেন?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"যেকোনও ডিভাইসে নিজের <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ব্যবহার করতে পারবেন। এটি <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>-এর জন্য <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>-এ সেভ করা আছে"</string>
+    <string name="passkey" msgid="632353688396759522">"পাসকী"</string>
+    <string name="password" msgid="6738570945182936667">"পাসওয়ার্ড"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"সাইন-ইন"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"আপনার সব সাইন-ইনের জন্য <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ব্যবহার করবেন?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"ডিফল্ট হিসেবে সেট করুন"</string>
+    <string name="use_once" msgid="9027366575315399714">"একবার ব্যবহার করুন"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>টি পাসওয়ার্ড, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>টি পাসকী"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>টি পাসওয়ার্ড"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>টি পাসকী"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"অন্য ডিভাইস"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"অন্যান্য Password Manager"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"শিট বন্ধ করুন"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"আগের পৃষ্ঠায় ফিরে যান"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য আপনার সেভ করা পাসকী ব্যবহার করবেন?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য আপনার সেভ করা সাইন-ইন সম্পর্কিত ক্রেডেনশিয়াল ব্যবহার করবেন?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য সাইন-ইন করা সম্পর্কিত ক্রেডেনশিয়াল বেছে নিন"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"অন্যভাবে সাইন-ইন করুন"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"না থাক"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"চালিয়ে যান"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"সাইন-ইন করার বিকল্প"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>-এর জন্য"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"লক করা Password Manager"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"আনলক করতে ট্যাপ করুন"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"সাইন-ইন করার ক্রেডেনশিয়াল ম্যানেজ করুন"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"অন্য ডিভাইস থেকে"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"আলাদা ডিভাইস ব্যবহার করুন"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-bs/strings.xml b/packages/CredentialManager/res/values-bs/strings.xml
new file mode 100644
index 0000000..d774b88
--- /dev/null
+++ b/packages/CredentialManager/res/values-bs/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Upravitelj akreditiva"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Otkaži"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Nastavi"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Kreirajte na drugom mjestu"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Sačuvajte na drugom mjestu"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Koristite drugi uređaj"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Sačuvajte na drugom uređaju"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednostavan način za sigurnu prijavu"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Koristite otisak prsta, lice ili zaključavanje ekrana da se prijavite jedinstvenim pristupnim ključem koji se ne može zaboraviti niti ukrasti. Saznajte više"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Odaberite gdje <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"sačuvajte lozinku"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"sačuvajte informacije za prijavu"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Kreirati pristupni ključ na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Sačuvati lozinku na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Sačuvati informacije za prijavu na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Možete koristiti <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> na bilo kojem uređaju. Sačuvan je na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> za <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"pristupni ključ"</string>
+    <string name="password" msgid="6738570945182936667">"lozinka"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"prijave"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Koristiti uslugu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> za sve vaše prijave?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Postavi kao zadano"</string>
+    <string name="use_once" msgid="9027366575315399714">"Koristi jednom"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Broj lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>; broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"Broj lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Pristupni ključ"</string>
+    <string name="another_device" msgid="5147276802037801217">"Drugi uređaj"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Drugi upravitelji lozinki"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Zatvaranje tabele"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Povratak na prethodnu stranicu"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Koristiti sačuvani pristupni ključ za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Koristiti sačuvanu prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Odaberite sačuvanu prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prijavite se na drugi način"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ne, hvala"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Nastavi"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcije prijave"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Za osobu <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Zaključani upravitelji lozinki"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Dodirnite da otključate"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Upravljajte prijavama"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"S drugog uređaja"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Upotrijebite drugi uređaj"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-ca/strings.xml b/packages/CredentialManager/res/values-ca/strings.xml
new file mode 100644
index 0000000..bfd9164
--- /dev/null
+++ b/packages/CredentialManager/res/values-ca/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Cancel·la"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continua"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Crea en un altre lloc"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Desa en un altre lloc"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Utilitza un altre dispositiu"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Desa en un altre dispositiu"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Una manera senzilla i segura d\'iniciar la sessió"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Utilitza l\'empremta digital, la cara o el bloqueig de pantalla per iniciar la sessió amb una clau d\'accés única que no es pot oblidar ni robar. Més informació"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Tria on vols <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"desar la contrasenya"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"desar la teva informació d\'inici de sessió"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vols crear una clau d\'accés a <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vols desar la contrasenya a <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vols desar la teva informació d\'inici de sessió a <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Pots utilitzar <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> en qualsevol dispositiu. Està desat a <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> per a <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"clau d\'accés"</string>
+    <string name="password" msgid="6738570945182936667">"contrasenya"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"inicis de sessió"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vols utilitzar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> per a tots els teus inicis de sessió?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Estableix com a predeterminada"</string>
+    <string name="use_once" msgid="9027366575315399714">"Utilitza un cop"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasenyes, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> claus d\'accés"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasenyes"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> claus d\'accés"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Un altre dispositiu"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Altres gestors de contrasenyes"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Tanca el full"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Torna a la pàgina anterior"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vols utilitzar la clau d\'accés desada per a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vols utilitzar l\'inici de sessió desat per a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Tria un inici de sessió desat per a <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Inicia la sessió d\'una altra manera"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No, gràcies"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continua"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcions d\'inici de sessió"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Per a <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestors de contrasenyes bloquejats"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Toca per desbloquejar"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gestiona els inicis de sessió"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Des d\'un altre dispositiu"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Utilitza un dispositiu diferent"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-cs/strings.xml b/packages/CredentialManager/res/values-cs/strings.xml
new file mode 100644
index 0000000..72a5f98
--- /dev/null
+++ b/packages/CredentialManager/res/values-cs/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Zrušit"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Pokračovat"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Vytvořit na jiném místě"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Uložit na jiné místo"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Použít jiné zařízení"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Uložit do jiného zařízení"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednoduchý způsob, jak se bezpečně přihlásit"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Použijte svůj otisk prstu, obličej nebo zámek obrazovky k přihlášení pomocí jedinečného přístupového klíče, který nemůžete zapomenout a který vám nikdo nemůže odcizit. Další informace"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Zvolte, kde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"uložte si heslo"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"uložte své přihlašovací údaje"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vytvořit přístupový klíč v <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Uložit heslo do <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Uložit přihlašovací údaje do <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Svoje <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> můžete používat na libovolném zařízení. Ukládá se do <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> pro <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"přístupový klíč"</string>
+    <string name="password" msgid="6738570945182936667">"heslo"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"přihlášení"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Používat <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pro všechna přihlášení?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Nastavit jako výchozí"</string>
+    <string name="use_once" msgid="9027366575315399714">"Použít jednou"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Počet hesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, počet přístupových klíčů: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"Počet hesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Počet přístupových klíčů: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Jiné zařízení"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Další správci hesel"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Zavřít list"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Zpět na předchozí stránku"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Použít uložený přístupový klíč pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Použít uložené přihlášení pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Vyberte uložené přihlášení pro <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Přihlásit se jinak"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ne, díky"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Pokračovat"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Možnosti přihlašování"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pro uživatele <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Uzamčení správci hesel"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Klepnutím odemknete"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Spravovat přihlášení"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Z jiného zařízení"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Použít jiné zařízení"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-da/strings.xml b/packages/CredentialManager/res/values-da/strings.xml
new file mode 100644
index 0000000..a05137e
--- /dev/null
+++ b/packages/CredentialManager/res/values-da/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Annuller"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Fortsæt"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Opret et andet sted"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Gem et andet sted"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Brug en anden enhed"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Gem på en anden enhed"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"En nemmere og mere sikker måde at logge ind på"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Brug dit fingeraftryk, dit ansigt eller din skærmlås for at logge ind med en unik adgangsnøgle, der hverken kan glemmes eller stjæles. Få flere oplysninger"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Vælg, hvor du vil <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"gem din adgangskode"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"gem dine loginoplysninger"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vil du oprette en adgangsnøgle i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vil du gemme din adgangskode i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vil du gemme dine loginoplysninger i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Du kan bruge din <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> på enhver enhed. Den gemmes i <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> for <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"adgangsnøgle"</string>
+    <string name="password" msgid="6738570945182936667">"adgangskode"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"loginmetoder"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vil du bruge <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> til alle dine loginmetoder?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Angiv som standard"</string>
+    <string name="use_once" msgid="9027366575315399714">"Brug én gang"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> adgangskoder, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> adgangsnøgler"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> adgangskoder"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> adgangsnøgler"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"En anden enhed"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Andre adgangskodeadministratorer"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Luk arket"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gå tilbage til den forrige side"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vil du bruge din gemte adgangsnøgle til <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vil du bruge din gemte loginmetode til <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Vælg en gemt loginmetode til <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Log ind på en anden måde"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nej tak"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Fortsæt"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Valgmuligheder for login"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Låste adgangskodeadministratorer"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tryk for at låse op"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Administrer loginmetoder"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Fra en anden enhed"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Brug en anden enhed"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-de/strings.xml b/packages/CredentialManager/res/values-de/strings.xml
new file mode 100644
index 0000000..fa1e8f1
--- /dev/null
+++ b/packages/CredentialManager/res/values-de/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Anmeldedaten-Manager"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Abbrechen"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Weiter"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"An anderem Speicherort erstellen"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"An anderem Ort speichern"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Anderes Gerät verwenden"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Auf einem anderen Gerät speichern"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Einfach und sicher anmelden"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Verwende deinen Fingerabdruck oder deine Gesichts- bzw. Displaysperre, um dich mit einem eindeutigen Passkey anzumelden, der nicht vergessen oder gestohlen werden kann. Weitere Informationen"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Ort auswählen für: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"Passwort speichern"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"Anmeldedaten speichern"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Passkey hier erstellen: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Passwort hier speichern: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Anmeldedaten hier speichern: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Du kannst <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> auf jedem beliebigen Gerät verwenden. Gespeichert (<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>) für <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"Passkey"</string>
+    <string name="password" msgid="6738570945182936667">"Passwort"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"Anmeldungen"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> für alle Anmeldungen verwenden?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Als Standard festlegen"</string>
+    <string name="use_once" msgid="9027366575315399714">"Einmal verwenden"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> Passwörter, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> Passkeys"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> Passwörter"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> Passkeys"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
+    <string name="another_device" msgid="5147276802037801217">"Ein anderes Gerät"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Andere Passwortmanager"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Tabellenblatt schließen"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Zurück zur vorherigen Seite"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gespeicherten Passkey für <xliff:g id="APP_NAME">%1$s</xliff:g> verwenden?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gespeicherte Anmeldedaten für <xliff:g id="APP_NAME">%1$s</xliff:g> verwenden?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Gespeicherte Anmeldedaten für <xliff:g id="APP_NAME">%1$s</xliff:g> auswählen"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Andere Anmeldeoption auswählen"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nein danke"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Weiter"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Anmeldeoptionen"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Für <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gesperrte Passwortmanager"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Zum Entsperren tippen"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Anmeldedaten verwalten"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Von einem anderen Gerät"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Anderes Gerät verwenden"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-el/strings.xml b/packages/CredentialManager/res/values-el/strings.xml
new file mode 100644
index 0000000..706d9f3
--- /dev/null
+++ b/packages/CredentialManager/res/values-el/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Ακύρωση"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Συνέχεια"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Δημιουργία σε άλλη θέση"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Αποθήκευση σε άλλη θέση"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Χρήση άλλης συσκευής"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Αποθήκευση σε άλλη συσκευή"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Ένας απλός τρόπος για ασφαλή σύνδεση"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Χρησιμοποιήστε το δακτυλικό αποτύπωμα, το πρόσωπο ή το κλείδωμα οθόνης σας για να συνδεθείτε με ένα μοναδικό κλειδί πρόσβασης που δεν είναι δυνατό να ξεχάσετε ή να κλαπεί. Μάθετε περισσότερα"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Επιλέξτε θέση για <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"αποθήκευση του κωδικού πρόσβασής σας"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"αποθήκευση των στοιχείων σύνδεσής σας"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Να δημιουργηθει κλειδί πρόσβασης στο <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>;"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Να αποθηκευτεί ο κωδικός πρόσβασής σας στο <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>;"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Να αποθηκευτούν τα στοιχεία σύνδεσής σας στο <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>;"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Μπορείτε να χρησιμοποιήσετε το στοιχείο τύπου <xliff:g id="TYPE">%2$s</xliff:g> του τομέα <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> σε οποιαδήποτε συσκευή. Αποθηκεύτηκε στο <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> για <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"κλειδί πρόσβασης"</string>
+    <string name="password" msgid="6738570945182936667">"κωδικός πρόσβασης"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"στοιχεία σύνδεσης"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Να χρησιμοποιηθεί το <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> για όλες τις συνδέσεις σας;"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Ορισμός ως προεπιλογής"</string>
+    <string name="use_once" msgid="9027366575315399714">"Χρήση μία φορά"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> κωδικοί πρόσβασης, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> κλειδιά πρόσβασης"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> κωδικοί πρόσβασης"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> κλειδιά πρόσβασης"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Κλειδί πρόσβασης"</string>
+    <string name="another_device" msgid="5147276802037801217">"Άλλη συσκευή"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Άλλοι διαχειριστές κωδικών πρόσβασης"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Κλείσιμο φύλλου"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Επιστροφή στην προηγούμενη σελίδα"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Να χρησιμοποιηθεί το αποθηκευμένο κλειδί πρόσβασης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Να χρησιμοποιηθούν τα αποθηκευμένα στοιχεία σύνδεσης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Επιλογή αποθηκευμένων στοιχείων σύνδεσης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Σύνδεση με άλλον τρόπο"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Όχι, ευχαριστώ"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Συνέχεια"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Επιλογές σύνδεσης"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Για τον χρήστη <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Κλειδωμένοι διαχειριστές κωδικών πρόσβασης"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Πατήστε για ξεκλείδωμα"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Διαχείριση στοιχείων σύνδεσης"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Από άλλη συσκευή"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Χρήση διαφορετικής συσκευής"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-en-rAU/strings.xml b/packages/CredentialManager/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..7adeded
--- /dev/null
+++ b/packages/CredentialManager/res/values-en-rAU/strings.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Credential manager"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Cancel"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continue"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Save to another place"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Use another device"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Save to another device"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
+    <string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string>
+    <string name="choose_provider_body" msgid="8045759834416308059">"Set a default password manager to save your passwords and passkeys and sign in faster next time."</string>
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"You can use your <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> on any device. It is saved to <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> for <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"passkey"</string>
+    <string name="password" msgid="6738570945182936667">"password"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Create passkey in"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Save password to"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Save sign-in to"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Create a passkey in another device?"</string>
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"This password manager will store your passwords and passkeys to help you easily sign in."</string>
+    <string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
+    <string name="use_once" msgid="9027366575315399714">"Use once"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
+    <string name="another_device" msgid="5147276802037801217">"Another device"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Go back to the previous page"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Use your saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Use your saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Sign in another way"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No thanks"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continue"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sign-in options"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Locked password managers"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tap to unlock"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Manage sign-ins"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-en-rCA/strings.xml b/packages/CredentialManager/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..8a8b884
--- /dev/null
+++ b/packages/CredentialManager/res/values-en-rCA/strings.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Cancel"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continue"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Save to another place"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Use another device"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Save to another device"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
+    <string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string>
+    <string name="choose_provider_body" msgid="8045759834416308059">"Set a default password manager to save your passwords and passkeys and sign in faster next time."</string>
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"You can use your <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> on any device. It is saved to <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> for <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"passkey"</string>
+    <string name="password" msgid="6738570945182936667">"password"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Create passkey in"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Save password to"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Save sign-in to"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Create a passkey in another device?"</string>
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"This password manager will store your passwords and passkeys to help you easily sign in."</string>
+    <string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
+    <string name="use_once" msgid="9027366575315399714">"Use once"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
+    <string name="another_device" msgid="5147276802037801217">"Another device"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Go back to the previous page"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Use your saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Use your saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Sign in another way"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No thanks"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continue"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sign-in options"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Locked password managers"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tap to unlock"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Manage sign-ins"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-en-rGB/strings.xml b/packages/CredentialManager/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..7adeded
--- /dev/null
+++ b/packages/CredentialManager/res/values-en-rGB/strings.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Credential manager"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Cancel"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continue"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Save to another place"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Use another device"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Save to another device"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
+    <string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string>
+    <string name="choose_provider_body" msgid="8045759834416308059">"Set a default password manager to save your passwords and passkeys and sign in faster next time."</string>
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"You can use your <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> on any device. It is saved to <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> for <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"passkey"</string>
+    <string name="password" msgid="6738570945182936667">"password"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Create passkey in"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Save password to"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Save sign-in to"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Create a passkey in another device?"</string>
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"This password manager will store your passwords and passkeys to help you easily sign in."</string>
+    <string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
+    <string name="use_once" msgid="9027366575315399714">"Use once"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
+    <string name="another_device" msgid="5147276802037801217">"Another device"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Go back to the previous page"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Use your saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Use your saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Sign in another way"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No thanks"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continue"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sign-in options"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Locked password managers"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tap to unlock"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Manage sign-ins"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-en-rIN/strings.xml b/packages/CredentialManager/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..7adeded
--- /dev/null
+++ b/packages/CredentialManager/res/values-en-rIN/strings.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Credential manager"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Cancel"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continue"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Save to another place"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Use another device"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Save to another device"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
+    <string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string>
+    <string name="choose_provider_body" msgid="8045759834416308059">"Set a default password manager to save your passwords and passkeys and sign in faster next time."</string>
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"You can use your <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> on any device. It is saved to <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> for <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"passkey"</string>
+    <string name="password" msgid="6738570945182936667">"password"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Create passkey in"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Save password to"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Save sign-in to"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Create a passkey in another device?"</string>
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"This password manager will store your passwords and passkeys to help you easily sign in."</string>
+    <string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
+    <string name="use_once" msgid="9027366575315399714">"Use once"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
+    <string name="another_device" msgid="5147276802037801217">"Another device"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Go back to the previous page"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Use your saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Use your saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Sign in another way"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No thanks"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continue"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sign-in options"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Locked password managers"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tap to unlock"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Manage sign-ins"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-en-rXC/strings.xml b/packages/CredentialManager/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..85e94df
--- /dev/null
+++ b/packages/CredentialManager/res/values-en-rXC/strings.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‎‎‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‏‎‏‏‏‎‎‎‏‎‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎Credential Manager‎‏‎‎‏‎"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‏‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‏‎‏‏‏‏‏‎‎‎‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎Cancel‎‏‎‎‏‎"</string>
+    <string name="string_continue" msgid="1346732695941131882">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‎‎‎‎‏‎‎‎‏‏‏‎‎‏‎‎‎‎‎‎‎‏‏‎‏‏‎‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‎Continue‎‏‎‎‏‎"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‏‎‏‎‏‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‎‏‏‎‎‎‎‏‎‎‎‎‏‎‎‎‎‎‏‎‎‎‏‏‎Create in another place‎‏‎‎‏‎"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‏‏‎‎‏‎‎‎‎‎‏‎‎‏‏‏‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‏‏‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‎‎‏‎Save to another place‎‏‎‎‏‎"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‎‎‏‎‎‎‎‎‎‏‎‏‎‏‎‎‎‎‎‎‏‏‎‏‎‎‏‏‏‏‎‏‎Use another device‎‏‎‎‏‎"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‏‏‎‎‎‏‏‏‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‏‎‎Save to another device‎‏‎‎‏‎"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‏‎‎‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎A simple way to sign in safely‎‏‎‎‏‎"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‎‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more‎‏‎‎‏‎"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‎‎‏‎‎‎‏‏‏‎‎‎‎‎‏‎‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‎Choose where to ‎‏‎‎‏‏‎<xliff:g id="CREATETYPES">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‎‏‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‎‎‏‎‎‎create your passkeys‎‏‎‎‏‎"</string>
+    <string name="save_your_password" msgid="6597736507991704307">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‎‏‎‏‏‏‏‎‎‏‏‎save your password‎‏‎‎‏‎"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‎‏‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‎‎‏‎‎‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‎‎save your sign-in info‎‏‎‎‏‎"</string>
+    <string name="choose_provider_body" msgid="8045759834416308059">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‏‏‎Set a default password manager to save your passwords and passkeys and sign in faster next time.‎‏‎‎‏‎"</string>
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‎‏‏‏‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‏‎‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎Create a passkey in ‎‏‎‎‏‏‎<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎Save your password to ‎‏‎‎‏‏‎<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎Save your sign-in info to ‎‏‎‎‏‏‎<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‎‏‏‏‎‏‏‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‎‎‏‎You can use your ‎‏‎‎‏‏‎<xliff:g id="APPDOMAINNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="TYPE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ on any device. It is saved to ‎‏‎‎‏‏‎<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>‎‏‎‎‏‏‏‎ for ‎‏‎‎‏‏‎<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="passkey" msgid="632353688396759522">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‏‏‎‏‎‎‏‎‎‏‎‎‏‎‏‎‏‎‎‎‏‎‎‏‎‎‏‎‏‎‎‏‏‎‎‎‎‎‎‎‎‎‏‏‏‏‎‎‎‏‎‎passkey‎‏‎‎‏‎"</string>
+    <string name="password" msgid="6738570945182936667">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‏‎‏‏‎password‎‏‎‎‏‎"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎sign-ins‎‏‎‎‏‎"</string>
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‎‎‏‏‏‏‎‎‏‎‏‎‎‎‎‎‏‎‎‎‏‏‏‏‎‎‏‎Create passkey in‎‏‎‎‏‎"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‎‎‎‏‎‏‎‏‎‎‎‏‏‏‎‏‏‎‏‎‏‏‏‎‎‏‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‏‎‏‎Save password to‎‏‎‎‏‎"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‏‏‏‎‎‎‎‎‏‎‎‏‏‏‏‏‎‎‏‎‎‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‎‎‏‎‏‎‏‏‎‏‎‎‎‎Save sign-in to‎‏‎‎‏‎"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‏‏‎‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎Create a passkey in another device?‎‏‎‎‏‎"</string>
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎‎‎‎‎‎‏‎‎‏‎‏‏‏‎‏‏‏‎‎‏‎‏‏‏‏‎‎‏‎‏‎‏‎Use ‎‏‎‎‏‏‎<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ for all your sign-ins?‎‏‎‎‏‎"</string>
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‎‎‏‏‎‎‎‎‎‎‎‎‏‎‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎This password manager will store your passwords and passkeys to help you easily sign in.‎‏‎‎‏‎"</string>
+    <string name="set_as_default" msgid="4415328591568654603">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‎‏‏‎‎‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‎‏‎‎‎‎‏‎‏‏‎Set as default‎‏‎‎‏‎"</string>
+    <string name="use_once" msgid="9027366575315399714">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‏‎‎‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‎‏‎‎‎‎‏‎‎‎‏‎‎Use once‎‏‎‎‏‎"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‎‏‎‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎ passwords, ‎‏‎‎‏‏‎<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>‎‏‎‎‏‏‏‎ passkeys‎‏‎‎‏‎"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‎‏‏‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‎‏‎‏‎‏‏‏‏‎‏‎‎‏‏‎‎‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎ passwords‎‏‎‎‏‎"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‏‎‏‏‎‎‏‏‎‎‏‎‏‏‎‎‏‏‎‏‏‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎ passkeys‎‏‎‎‏‎"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‏‏‏‎‏‎‏‏‎‎‏‎‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎Passkey‎‏‎‎‏‎"</string>
+    <string name="another_device" msgid="5147276802037801217">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎‏‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‏‏‎‎‎‎‏‎‎‎‎‎‎‎‏‎Another device‎‏‎‎‏‎"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‏‎‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‏‎‏‏‎‏‎Other password managers‎‏‎‎‏‎"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‏‎‏‏‏‏‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‎‎‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‎‏‏‎‎Close sheet‎‏‎‎‏‎"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‏‎‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‎‏‏‏‏‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎Go back to the previous page‎‏‎‎‏‎"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‏‏‎‎‎‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‏‎Use your saved passkey for ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‎‎‎Use your saved sign-in for ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‏‎‏‎Choose a saved sign-in for ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‎‏‎‏‏‎‏‏‎‎‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‎‏‎‏‏‏‏‎‎‏‎‏‎‎‎‎‏‏‎‎‎‎‎‎‎Sign in another way‎‏‎‎‏‎"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‎‏‎‏‎‎‎‏‎‏‎No thanks‎‏‎‎‏‎"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‎‎Continue‎‏‎‎‏‎"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‎‏‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‏‎‎Sign-in options‎‏‎‎‏‎"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‏‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎For ‎‏‎‎‏‏‎<xliff:g id="USERNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‏‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‎‎‎‎‏‏‎‎‏‎‎‎Locked password managers‎‏‎‎‏‎"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‎‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‎‏‏‎Tap to unlock‎‏‎‎‏‎"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‎‎‏‎‏‎‏‎‎‎‏‎‎‏‏‏‎‎‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‎‎Manage sign-ins‎‏‎‎‏‎"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‎‎‏‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎From another device‎‏‎‎‏‎"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‏‏‎‏‏‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‏‏‏‏‎‏‎Use a different device‎‏‎‎‏‎"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-es-rUS/strings.xml b/packages/CredentialManager/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..5b8e442
--- /dev/null
+++ b/packages/CredentialManager/res/values-es-rUS/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Crear en otra ubicación"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Guardar en otra ubicación"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Usar otro dispositivo"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Guardar en otro dispositivo"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Un modo simple y seguro de ingresar"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Usa tu huella dactilar, tu rostro o el bloqueo de pantalla para acceder con una llave de acceso única que no olvidarás ni podrán robarte. Más información"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Elige dónde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"guardar tu contraseña"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"guardar tu información de acceso"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"¿Quieres crear una llave de acceso en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"¿Quieres guardar tu contraseña de <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"¿Quieres guardar tu información de acceso a <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Puedes usar tu <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> en cualquier dispositivo. Se guardó en <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> para <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>."</string>
+    <string name="passkey" msgid="632353688396759522">"llave de acceso"</string>
+    <string name="password" msgid="6738570945182936667">"contraseña"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"accesos"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"¿Quieres usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos tus accesos?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Establecer como predeterminado"</string>
+    <string name="use_once" msgid="9027366575315399714">"Usar una vez"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> llaves de acceso, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> contraseñas"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contraseñas"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> llaves de acceso"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Llave de acceso"</string>
+    <string name="another_device" msgid="5147276802037801217">"Otro dispositivo"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Otros administradores de contraseñas"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Cerrar hoja"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Volver a la página anterior"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"¿Quieres usar tu llave de acceso guardada para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"¿Quieres usar tu acceso guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Elige un acceso guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Acceder de otra forma"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No, gracias"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opciones de acceso"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Administradores de contraseñas bloqueados"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Presiona para desbloquear"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Administrar accesos"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Desde otro dispositivo"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar otra voz"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-es/strings.xml b/packages/CredentialManager/res/values-es/strings.xml
new file mode 100644
index 0000000..19fde72
--- /dev/null
+++ b/packages/CredentialManager/res/values-es/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Crear en otro lugar"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Guardar en otro lugar"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Usar otro dispositivo"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Guardar en otro dispositivo"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Una forma sencilla y segura de iniciar sesión"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Usa la huella digital, la cara o el bloqueo de pantalla para iniciar sesión con una llave de acceso única que no se puede olvidar ni robar. Más información"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Elige dónde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"guardar tu contraseña"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"guardar tu información de inicio de sesión"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"¿Crear una llave de acceso en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"¿Guardar tu contraseña en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"¿Guardar tu información de inicio de sesión en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Puedes usar tu <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> en cualquier dispositivo. Se guarda en <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> para <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>."</string>
+    <string name="passkey" msgid="632353688396759522">"llave de acceso"</string>
+    <string name="password" msgid="6738570945182936667">"contraseña"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"inicios de sesión"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"¿Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos tus inicios de sesión?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Fijar como predeterminado"</string>
+    <string name="use_once" msgid="9027366575315399714">"Usar una vez"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contraseñas, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> llaves de acceso"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contraseñas"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> llaves de acceso"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Otro dispositivo"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Otros gestores de contraseñas"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Cerrar hoja"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Volver a la página anterior"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"¿Usar la llave de acceso guardada para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"¿Usar el inicio de sesión guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Elige un inicio de sesión guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Iniciar sesión de otra manera"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No, gracias"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opciones de inicio de sesión"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestores de contraseñas bloqueados"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Toca para desbloquear"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gestionar inicios de sesión"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De otro dispositivo"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar otro dispositivo"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-et/strings.xml b/packages/CredentialManager/res/values-et/strings.xml
new file mode 100644
index 0000000..5b1b070
--- /dev/null
+++ b/packages/CredentialManager/res/values-et/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Tühista"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Jätka"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Loo teises kohas"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Salvesta teise kohta"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Kasuta teist seadet"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Salvesta teise seadmesse"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Lihtne viis turvaliselt sisselogimiseks"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Kasutage sõrmejälge, nägu või ekraanilukku, et logida sisse unikaalse pääsuvõtmega, mida ei saa unustada ega varastada. Lisateave"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Valige, kus <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"parool salvestada"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"sisselogimisandmed salvestada"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Kas luua teenuses <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pääsuvõti?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Kas salvestada parool teenusesse <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Kas salvestada sisselogimisteave teenusesse <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Saate rakendust <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> kasutada mis tahes seadmes. Salvestatakse teenusesse <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> – <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"pääsukood"</string>
+    <string name="password" msgid="6738570945182936667">"parool"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"sisselogimisandmed"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Kas kasutada teenust <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kõigi teie sisselogimisandmete puhul?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Määra vaikeseadeks"</string>
+    <string name="use_once" msgid="9027366575315399714">"Kasuta ühe korra"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parooli, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> pääsuvõtit"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parooli"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> pääsuvõtit"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Teine seade"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Muud paroolihaldurid"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Sule leht"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Minge tagasi eelmisele lehele"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Kas kasutada rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks salvestatud pääsuvõtit?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Kas kasutada rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks salvestatud sisselogimisandmeid?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Valige rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks salvestatud sisselogimisandmed"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Logige sisse muul viisil"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Tänan, ei"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Jätka"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sisselogimise valikud"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Kasutajale <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Lukustatud paroolihaldurid"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Avamiseks puudutage"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Sisselogimisandmete haldamine"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Muus seadmes"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Kasuta teist seadet"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-eu/strings.xml b/packages/CredentialManager/res/values-eu/strings.xml
new file mode 100644
index 0000000..b2c1fe5
--- /dev/null
+++ b/packages/CredentialManager/res/values-eu/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Utzi"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Egin aurrera"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Sortu beste toki batean"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Gorde beste toki batean"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Erabili beste gailu bat"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Gorde beste gailu batean"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Segurtasun osoz saioa hasteko modu erraza"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Erabili hatz-marka, aurpegia edo pantailaren blokeoa ahaztu edo lapurtu ezin den sarbide-gako baten bidez saioa hasteko. Lortu informazio gehiago"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Aukeratu non <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"gorde pasahitza"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"gorde kredentzialei buruzko informazioa"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Sarbide-gako bat sortu nahi duzu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> aplikazioan?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Pasahitza <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> aplikazioan gorde nahi duzu?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Kredentzialei buruzko informazioa <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> aplikazioan gorde nahi duzu?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> aplikazioko <xliff:g id="TYPE">%2$s</xliff:g> edozein gailutan erabil dezakezu. <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> aplikazioan dago gordeta (<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>)."</string>
+    <string name="passkey" msgid="632353688396759522">"sarbide-gakoa"</string>
+    <string name="password" msgid="6738570945182936667">"pasahitza"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"kredentzialak"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> erabili nahi duzu kredentzial guztietarako?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Ezarri lehenetsi gisa"</string>
+    <string name="use_once" msgid="9027366575315399714">"Erabili behin"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> pasahitz eta <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> sarbide-gako"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> pasahitz"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> sarbide-gako"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Beste gailu bat"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Beste pasahitz-kudeatzaile batzuk"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Itxi orria"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Itzuli aurreko orrira"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako gorde duzun sarbide-gakoa erabili nahi duzu?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako gorde dituzun kredentzialak erabili nahi dituzu?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Aukeratu <xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako gorde dituzun kredentzialak"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Hasi saioa beste modu batean"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ez, eskerrik asko"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Egin aurrera"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Saioa hasteko aukerak"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> erabiltzailearenak"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Blokeatutako pasahitz-kudeatzaileak"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Desblokeatzeko, sakatu hau"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Kudeatu kredentzialak"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Beste gailu batean gordetakoak"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Erabili beste gailu bat"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-fa/strings.xml b/packages/CredentialManager/res/values-fa/strings.xml
new file mode 100644
index 0000000..98b487c
--- /dev/null
+++ b/packages/CredentialManager/res/values-fa/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"لغو"</string>
+    <string name="string_continue" msgid="1346732695941131882">"ادامه"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"ایجاد در مکانی دیگر"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"ذخیره در مکانی دیگر"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"استفاده از دستگاهی دیگر"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"ذخیره در دستگاهی دیگر"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"روشی ساده برای ورود به سیستم ایمن"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"برای ورود به سیستم با گذرکلیدی یکتا که غیرقابل فراموش شدن یا دزدیده شدن باشد، از اثر انگشت، چهره، یا قفل صفحه استفاده کنید. بیشتر بدانید"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"انتخاب محل <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"ذخیره گذرواژه"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"ذخیره اطلاعات ورود به سیستم"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"گذرکلید در <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ایجاد شود؟"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"گذرواژه در <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ذخیره شود؟"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"اطلاعات ورود به سیستم در <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ذخیره شود؟"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"می‌توانید از <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> در هر دستگاهی استفاده کنید. در <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> برای <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> ذخیره می‌شود"</string>
+    <string name="passkey" msgid="632353688396759522">"گذرکلید"</string>
+    <string name="password" msgid="6738570945182936667">"گذرواژه"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"ورود به سیستم‌ها"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"از <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> برای همه ورود به سیستم‌ها استفاده شود؟"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"تنظیم به‌عنوان پیش‌فرض"</string>
+    <string name="use_once" msgid="9027366575315399714">"یک‌بار استفاده"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> گذرواژه، <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> گذرکلید"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> گذرواژه"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> گذرکلید"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"دستگاهی دیگر"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"دیگر مدیران گذرواژه"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"بستن برگ"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"برگشتن به صفحه قبلی"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"گذرکلید ذخیره‌شده برای <xliff:g id="APP_NAME">%1$s</xliff:g> استفاده شود؟"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ورود به سیستم ذخیره‌شده برای <xliff:g id="APP_NAME">%1$s</xliff:g> استفاده شود؟"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"انتخاب ورود به سیستم ذخیره‌شده برای <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ورود به سیستم به روشی دیگر"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"نه متشکرم"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ادامه"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"گزینه‌های ورود به سیستم"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"برای <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"مدیران گذرواژه قفل‌شده"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"برای باز کردن قفل ضربه بزنید"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"مدیریت ورود به سیستم‌ها"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"از دستگاهی دیگر"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"استفاده از دستگاه دیگری"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-fi/strings.xml b/packages/CredentialManager/res/values-fi/strings.xml
new file mode 100644
index 0000000..9ad178a
--- /dev/null
+++ b/packages/CredentialManager/res/values-fi/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Peru"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Jatka"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Luo muualla"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Tallenna muualle"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Käytä toista laitetta"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Tallenna toiselle laitteelle"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Helppo tapa kirjautua turvallisesti sisään"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Käytä sormenjälkeä, kasvoja tai näytön lukitusta, niin voit kirjautua sisään yksilöllisellä avainkoodilla, jota ei voi unohtaa tai varastaa. Lue lisää"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Valitse paikka: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"tallenna salasanasi"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"tallenna kirjautumistiedot"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Luodaanko avainkoodi (<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>)?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Tallennetaanko salasanasi tänne: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Tallennetaanko kirjautumistietosi tänne: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> (<xliff:g id="TYPE">%2$s</xliff:g>) on käytettävissä millä tahansa laitteella. Se tallennetaan tänne: <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> (<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>)"</string>
+    <string name="passkey" msgid="632353688396759522">"avainkoodi"</string>
+    <string name="password" msgid="6738570945182936667">"salasana"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"sisäänkirjautumiset"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Otetaanko <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> käyttöön kaikissa sisäänkirjautumisissa?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Aseta oletukseksi"</string>
+    <string name="use_once" msgid="9027366575315399714">"Käytä kerran"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> salasanaa, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> avainkoodia"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> salasanaa"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> avainkoodia"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Toinen laite"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Muut salasanojen ylläpitotyökalut"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Sulje taulukko"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Takaisin edelliselle sivulle"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Käytetäänkö tallennettua avainkoodiasi täällä: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Käytetäänkö tallennettuja kirjautumistietoja täällä: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Valitse tallennetut kirjautumistiedot (<xliff:g id="APP_NAME">%1$s</xliff:g>)"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Kirjaudu sisään toisella tavalla"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ei kiitos"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Jatka"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Kirjautumisvaihtoehdot"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Käyttäjä: <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Lukitut salasanojen ylläpitotyökalut"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Avaa napauttamalla"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Muuta kirjautumistietoja"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Toiselta laitteelta"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Käytä toista laitetta"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-fr-rCA/strings.xml b/packages/CredentialManager/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..a2e7581
--- /dev/null
+++ b/packages/CredentialManager/res/values-fr-rCA/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Gestionnaire d\'identifiants"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Annuler"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continuer"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Créer à un autre emplacement"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Enregistrer à un autre emplacement"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Utiliser un autre appareil"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Enregistrer sur un autre appareil"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Une manière simple de se connecter en toute sécurité"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Utilisez vos empreintes digitales, votre visage ou un écran de verrouillage pour vous connecter avec une clé d\'accès unique qui ne peut pas être oubliée ou volée. En savoir plus"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Choisir où <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"enregistrer votre mot de passe"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"enregistrer vos données de connexion"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Créer une clé d\'accès dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Enregistrer votre mot de passe dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Enregistrer vos données de connexion dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Vous pouvez utiliser votre <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> sur n\'importe quel appareil. Il est enregistré sur <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> pour <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"clé d\'accès"</string>
+    <string name="password" msgid="6738570945182936667">"mot de passe"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"connexions"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Utiliser <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pour toutes vos connexions?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Définir par défaut"</string>
+    <string name="use_once" msgid="9027366575315399714">"Utiliser une fois"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> clés d\'accès"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> clés d\'accès"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Clé d\'accès"</string>
+    <string name="another_device" msgid="5147276802037801217">"Un autre appareil"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Autres gestionnaires de mots de passe"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Fermer la feuille"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Retourner à la page précédente"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Utiliser votre clé d\'accès enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Utiliser votre connexion enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choisir une connexion enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Se connecter d\'une autre manière"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Non merci"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuer"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Options de connexion"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pour <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestionnaires de mots de passe verrouillés"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Toucher pour déverrouiller"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gérer les connexions"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"À partir d\'un autre appareil"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Utiliser un autre appareil"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-fr/strings.xml b/packages/CredentialManager/res/values-fr/strings.xml
new file mode 100644
index 0000000..2b280fb
--- /dev/null
+++ b/packages/CredentialManager/res/values-fr/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Gestionnaire d\'identifiants"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Annuler"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continuer"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Créer ailleurs"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Enregistrer ailleurs"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Utiliser un autre appareil"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Enregistrer sur un autre appareil"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Une façon simple et sécurisée de vous connecter"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Utilisez votre empreinte digitale, votre visage ou le verrouillage de l\'écran pour vous connecter avec une clé d\'accès unique que vous ne pourrez pas oublier ni vous faire voler. En savoir plus"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Choisir où <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"enregistrer votre mot de passe"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"enregistrer vos informations de connexion"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Créer une clé d\'accès dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Enregistrer votre mot de passe dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Enregistrer vos informations de connexion dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Vous pouvez utiliser votre <xliff:g id="TYPE">%2$s</xliff:g> <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> sur n\'importe quel appareil. Il est enregistré dans <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> pour <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>."</string>
+    <string name="passkey" msgid="632353688396759522">"clé d\'accès"</string>
+    <string name="password" msgid="6738570945182936667">"mot de passe"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"connexions"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Utiliser <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pour toutes vos connexions ?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Définir par défaut"</string>
+    <string name="use_once" msgid="9027366575315399714">"Utiliser une fois"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> clés d\'accès"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> clés d\'accès"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Clé d\'accès"</string>
+    <string name="another_device" msgid="5147276802037801217">"Un autre appareil"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Autres gestionnaires de mots de passe"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Fermer la feuille"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Revenir à la page précédente"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Utiliser votre clé d\'accès enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Utiliser vos informations de connexion enregistrées pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choisir des informations de connexion enregistrées pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Se connecter d\'une autre manière"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Non, merci"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuer"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Options de connexion"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pour <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestionnaires de mots de passe verrouillés"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Appuyer pour déverrouiller"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gérer les connexions"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Depuis un autre appareil"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Utiliser un autre appareil"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-gl/strings.xml b/packages/CredentialManager/res/values-gl/strings.xml
new file mode 100644
index 0000000..cc03ca4
--- /dev/null
+++ b/packages/CredentialManager/res/values-gl/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Xestor de credenciais"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Crear noutro lugar"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Gardar noutro lugar"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Usar outro dispositivo"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Gardar noutro dispositivo"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Un xeito fácil de iniciar sesión de forma segura"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Usa a impresión dixital, a cara ou o bloqueo de pantalla para iniciar sesión cunha clave de acceso única que non podes esquecer nin cha poden roubar. Máis información"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Escolle onde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"gardar o contrasinal"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"gardar a información de inicio de sesión"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Queres crear unha clave de acceso en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Queres gardar o contrasinal en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Queres gardar a información de inicio de sesión en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Podes usar o teu <xliff:g id="TYPE">%2$s</xliff:g> de <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> en calquera dispositivo. Está gardado en <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> para <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"clave de acceso"</string>
+    <string name="password" msgid="6738570945182936667">"contrasinal"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"métodos de inicio de sesión"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Queres usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> cada vez que inicies sesión?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Establecer como predeterminado"</string>
+    <string name="use_once" msgid="9027366575315399714">"Usar unha vez"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasinais, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> claves de acceso"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasinais"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> claves de acceso"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Clave de acceso"</string>
+    <string name="another_device" msgid="5147276802037801217">"Outro dispositivo"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Outros xestores de contrasinais"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Pechar folla"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Volver á páxina anterior"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Queres usar a clave de acceso gardada para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Queres usar o método de inicio de sesión gardado para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Escolle un método de inicio de sesión gardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Iniciar sesión doutra forma"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Non, grazas"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcións de inicio de sesión"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Xestores de contrasinais bloqueados"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Toca para desbloquear"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Xestionar os métodos de inicio de sesión"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Doutro dispositivo"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar outro dispositivo"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-gu/strings.xml b/packages/CredentialManager/res/values-gu/strings.xml
new file mode 100644
index 0000000..f796d20
--- /dev/null
+++ b/packages/CredentialManager/res/values-gu/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"લૉગ ઇન વિગતોના મેનેજર"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"રદ કરો"</string>
+    <string name="string_continue" msgid="1346732695941131882">"ચાલુ રાખો"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"કોઈ અન્ય સ્થાન પર બનાવો"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"કોઈ અન્ય સ્થાન પર સાચવો"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"કોઈ અન્ય ડિવાઇસનો ઉપયોગ કરો"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"અન્ય ડિવાઇસ પર સાચવો"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"સલામત રીતે સાઇન ઇન કરવાની સરળ રીત"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ભૂલી ન શકાય કે ચોરાઈ ન જાય, તેવી કોઈ વિશિષ્ટ પાસકી વડે સાઇન ઇન કરવા માટે, તમારી ફિંગરપ્રિન્ટ, ચહેરો અથવા સ્ક્રીન લૉકનો ઉપયોગ કરો. વધુ જાણો"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ક્યાં સાચવવી છે, તે પસંદ કરો"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"તમારો પાસવર્ડ સાચવો"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"તમારી સાઇન-ઇનની માહિતી સાચવો"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"શું <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>માં કોઈ પાસકી બનાવીએ?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"શું તમારો પાસવર્ડ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>માં સાચવીએ?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"શું તમારી સાઇન-ઇનની માહિતી <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>માં સાચવીએ?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"તમે કોઈપણ ડિવાઇસ પર તમારા <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g>નો ઉપયોગ કરી શકો છો. <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> માટે <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>માં તેને સાચવવામાં આવે છે"</string>
+    <string name="passkey" msgid="632353688396759522">"પાસકી"</string>
+    <string name="password" msgid="6738570945182936667">"પાસવર્ડ"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"સાઇન-ઇન"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"શું તમારા બધા સાઇન-ઇન માટે <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>નો ઉપયોગ કરીએ?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"ડિફૉલ્ટ તરીકે સેટ કરો"</string>
+    <string name="use_once" msgid="9027366575315399714">"એકવાર ઉપયોગ કરો"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> પાસવર્ડ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> પાસકી"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> પાસવર્ડ"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> પાસકી"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"પાસકી"</string>
+    <string name="another_device" msgid="5147276802037801217">"કોઈ અન્ય ડિવાઇસ"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"અન્ય પાસવર્ડ મેનેજર"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"શીટ બંધ કરો"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"પાછલા પેજ પર પરત જાઓ"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> માટે શું તમારી સાચવેલી પાસકીનો ઉપયોગ કરીએ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> માટે શું તમારા સાચવેલા સાઇન-ઇનનો ઉપયોગ કરીએ?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> માટે કોઈ સાચવેલું સાઇન-ઇન પસંદ કરો"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"કોઈ અન્ય રીતે સાઇન ઇન કરો"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"ના, આભાર"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ચાલુ રાખો"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"સાઇન-ઇનના વિકલ્પો"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> માટે"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"લૉક કરેલા પાસવર્ડ મેનેજર"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"અનલૉક કરવા માટે ટૅપ કરો"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"સાઇન-ઇન મેનેજ કરો"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"કોઈ અન્ય ડિવાઇસમાંથી"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"કોઈ અન્ય ડિવાઇસનો ઉપયોગ કરો"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-hi/strings.xml b/packages/CredentialManager/res/values-hi/strings.xml
new file mode 100644
index 0000000..fbf1c02
--- /dev/null
+++ b/packages/CredentialManager/res/values-hi/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"रद्द करें"</string>
+    <string name="string_continue" msgid="1346732695941131882">"जारी रखें"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"दूसरी जगह पर बनाएं"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"दूसरी जगह पर सेव करें"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"दूसरे डिवाइस का इस्तेमाल करें"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"दूसरे डिवाइस पर सेव करें"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"सुरक्षित तरीके से साइन इन करने का आसान तरीका"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"साइन इन करने के लिए फ़िंगरप्रिंट, फ़ेस या स्क्रीन लॉक जैसी यूनीक पासकी का इस्तेमाल करें. इन्हें, न तो भुलाया जा सकता है न ही चुराया जा सकता है. ज़्यादा जानें"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"चुनें कि <xliff:g id="CREATETYPES">%1$s</xliff:g> कहां पर सेव करना है"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"अपना पासवर्ड सेव करें"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"साइन इन से जुड़ी अपनी जानकारी सेव करें"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"क्या आपको <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> में पासकी बनानी है?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"क्या आपको अपना पासवर्ड <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> में सेव करना है?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"क्या आपको साइन इन करने से जुड़ी जानकारी <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> में सेव करनी है?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"अपना <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> किसी भी डिवाइस पर इस्तेमाल किया जा सकता है. इसे <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> के लिए, <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> में सेव किया जाता है"</string>
+    <string name="passkey" msgid="632353688396759522">"पासकी"</string>
+    <string name="password" msgid="6738570945182936667">"पासवर्ड"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"साइन इन"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"क्या आपको साइन इन से जुड़ी सारी जानकारी सेव करने के लिए, <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> का इस्तेमाल करना है?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"डिफ़ॉल्ट के तौर पर सेट करें"</string>
+    <string name="use_once" msgid="9027366575315399714">"इसका इस्तेमाल एक बार किया जा सकता है"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड और <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> पासकी"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> पासकी"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"दूसरा डिवाइस"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"दूसरे पासवर्ड मैनेजर"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"शीट बंद करें"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"पिछले पेज पर वापस जाएं"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"क्या आपको <xliff:g id="APP_NAME">%1$s</xliff:g> पर साइन इन करने के लिए, सेव की गई पासकी का इस्तेमाल करना है?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"क्या आपको <xliff:g id="APP_NAME">%1$s</xliff:g> पर साइन इन करने के लिए, सेव की गई जानकारी का इस्तेमाल करना है?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> पर साइन इन करने के लिए, सेव की गई जानकारी में से चुनें"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"किसी दूसरे तरीके से साइन इन करें"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"रहने दें"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"जारी रखें"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"साइन इन करने के विकल्प"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> के लिए"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"लॉक किए गए पासवर्ड मैनेजर"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"अनलॉक करने के लिए टैप करें"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"साइन इन करने की सुविधा को मैनेज करें"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"किसी दूसरे डिवाइस से"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"दूसरे डिवाइस का इस्तेमाल करें"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-hr/strings.xml b/packages/CredentialManager/res/values-hr/strings.xml
new file mode 100644
index 0000000..6c1952f
--- /dev/null
+++ b/packages/CredentialManager/res/values-hr/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Odustani"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Nastavi"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Izradi na drugom mjestu"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Spremi na drugom mjestu"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Upotrijebite neki drugi uređaj"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Spremi na drugi uređaj"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednostavan način za sigurnu prijavu"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Prijavite se otiskom prsta, licem ili zaključavanjem zaslona kao jedinstvenim pristupnim ključem koji je nemoguće zaboraviti ili ukrasti. Saznajte više"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Odaberite mjesto za sljedeće: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"spremi zaporku"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"spremi podatke za prijavu"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Želite li izraditi pristupni ključ na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Želite li spremiti zaporku na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Želite li spremiti podatke o prijavi na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Aplikaciju <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> možete upotrijebiti na bilo kojem uređaju. Sprema se na uslugu <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> za: <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"pristupni ključ"</string>
+    <string name="password" msgid="6738570945182936667">"zaporka"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"prijave"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite li upotrebljavati uslugu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> za sve prijave?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Postavi kao zadano"</string>
+    <string name="use_once" msgid="9027366575315399714">"Upotrijebi jednom"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Broj zaporki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"Broj zaporki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Drugi uređaj"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Drugi upravitelji zaporki"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Zatvaranje lista"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Vratite se na prethodnu stranicu"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Želite li upotrijebiti spremljeni pristupni ključ za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Želite li upotrijebiti spremljene podatke za prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Odaberite spremljene podatke za prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prijavite se na neki drugi način"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ne, hvala"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Nastavi"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcije prijave"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Za korisnika <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Upravitelji zaključanih zaporki"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Dodirnite za otključavanje"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Upravljanje prijavama"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Na drugom uređaju"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Upotrijebite drugi uređaj"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-hu/strings.xml b/packages/CredentialManager/res/values-hu/strings.xml
new file mode 100644
index 0000000..0efa3e8
--- /dev/null
+++ b/packages/CredentialManager/res/values-hu/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Tanúsítványkezelő"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Mégse"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Folytatás"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Létrehozás másik helyen"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Mentés másik helyre"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Másik eszköz használata"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Mentés másik eszközre"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"A biztonságos bejelentkezés egyszerű módja"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Ujjlenyomatát, arcát vagy képernyőzárát használva egyedi azonosítókulccsal jelentkezhet be, amelyet nem lehet elfelejteni vagy ellopni. További információ."</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Válassza ki a(z) <xliff:g id="CREATETYPES">%1$s</xliff:g> helyét"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"jelszó mentése"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"bejelentkezési adatok mentése"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Létrehoz azonosítókulcsot a(z) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> szolgáltatásban?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Menti jelszavát a(z) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> szolgáltatásba?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Menti bejelentkezési adatait a(z) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> szolgáltatásba?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"A(z) <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> bármilyen eszközön használható. A(z) <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> szolgáltatásba van mentve a következő számára: <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"azonosítókulcs"</string>
+    <string name="password" msgid="6738570945182936667">"jelszó"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"bejelentkezési adatok"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Szeretné a következőt használni az összes bejelentkezési adatához: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Beállítás alapértelmezettként"</string>
+    <string name="use_once" msgid="9027366575315399714">"Egyszeri használat"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> jelszó, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> azonosítókulcs"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> jelszó"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> azonosítókulcs"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Azonosítókulcs"</string>
+    <string name="another_device" msgid="5147276802037801217">"Másik eszköz"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Egyéb jelszókezelők"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Munkalap bezárása"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Vissza az előző oldalra"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Szeretné a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz mentett azonosítókulcsot használni?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Szeretné a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz mentett bejelentkezési adatait használni?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Mentett bejelentkezési adatok választása a következő számára: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Bejelentkezés más módon"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Most nem"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Folytatás"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Bejelentkezési beállítások"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Zárolt jelszókezelők"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Koppintson a feloldáshoz"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Bejelentkezési adatok kezelése"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Másik eszközről"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Másik eszköz használata"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-hy/strings.xml b/packages/CredentialManager/res/values-hy/strings.xml
new file mode 100644
index 0000000..de47e9f
--- /dev/null
+++ b/packages/CredentialManager/res/values-hy/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Չեղարկել"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Շարունակել"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Ստեղծել այլ տեղում"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Պահել այլ տեղում"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Օգտագործել այլ սարք"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Պահել մեկ այլ սարքում"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Մուտք գործելու անվտանգ և պարզ եղանակ"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Օգտագործեք ձեր մատնահետքը, դեմքը կամ էկրանի կողպումը՝ մուտք գործելու հաշիվ եզակի անցաբառի միջոցով, որը հնարավոր չէ կոտրել կամ մոռանալ։ Իմանալ ավելին"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Ընտրեք, թե որտեղ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"պահել գաղտնաբառը"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"պահել մուտքի տվյալները"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Ստեղծե՞լ անցաբառ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածում"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Պահե՞լ ձեր գաղտնաբառը <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածում"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Պահե՞լ ձեր մուտքի տվյալները <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածում"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Դուք կարող եք օգտագործել <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ցանկացած սարքում։ <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>-ի տվյալները պահված են <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> հավելվածում։"</string>
+    <string name="passkey" msgid="632353688396759522">"անցաբառ"</string>
+    <string name="password" msgid="6738570945182936667">"գաղտնաբառ"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"մուտք"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Միշտ մուտք գործե՞լ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածի միջոցով"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Նշել որպես կանխադրված"</string>
+    <string name="use_once" msgid="9027366575315399714">"Օգտագործել մեկ անգամ"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> գաղտնաբառ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> անցաբառ"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> գաղտնաբառ"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> անցաբառ"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Այլ սարք"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Գաղտնաբառերի այլ կառավարիչներ"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Փակել թերթը"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Անցնել նախորդ էջ"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Օգտագործե՞լ պահված անցաբառը <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Օգտագործե՞լ մուտքի պահված տվյալները <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Ընտրեք մուտքի պահված տվյալներ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Մուտք գործել այլ եղանակով"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ոչ, շնորհակալություն"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Շարունակել"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Մուտքի տարբերակներ"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>-ի համար"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Գաղտնաբառերի կողպված կառավարիչներ"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Հպեք՝ ապակողպելու համար"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Մուտքի կառավարում"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Մեկ այլ սարքից"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Օգտագործել այլ սարք"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-in/strings.xml b/packages/CredentialManager/res/values-in/strings.xml
new file mode 100644
index 0000000..d980d44
--- /dev/null
+++ b/packages/CredentialManager/res/values-in/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Pengelola Kredensial"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Batal"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Lanjutkan"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Buat di tempat lain"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Simpan ke tempat lain"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Gunakan perangkat lain"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Simpan ke perangkat lain"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Cara mudah untuk login dengan aman"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gunakan sidik jari, wajah, atau kunci layar untuk login dengan kunci sandi unik yang mudah diingat dan tidak dapat dicuri. Pelajari lebih lanjut"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Pilih tempat untuk <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"menyimpan sandi Anda"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"menyimpan info login Anda"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Buat kunci sandi di <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Simpan sandi ke <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Simpan info login ke <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Anda dapat menggunakan <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> di perangkat mana pun. Disimpan ke <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> untuk <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"kunci sandi"</string>
+    <string name="password" msgid="6738570945182936667">"sandi"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"login"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gunakan <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> untuk semua info login Anda?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Setel sebagai default"</string>
+    <string name="use_once" msgid="9027366575315399714">"Gunakan sekali"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> sandi, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> kunci sandi"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> sandi"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> kunci sandi"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Kunci sandi"</string>
+    <string name="another_device" msgid="5147276802037801217">"Perangkat lain"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Pengelola sandi lainnya"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Tutup sheet"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Kembali ke halaman sebelumnya"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gunakan kunci sandi tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gunakan info login tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pilih info login tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Login dengan cara lain"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Lain kali"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Lanjutkan"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opsi login"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Untuk <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Pengelola sandi terkunci"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Ketuk untuk membuka kunci"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Kelola login"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Dari perangkat lain"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gunakan perangkat lain"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-is/strings.xml b/packages/CredentialManager/res/values-is/strings.xml
new file mode 100644
index 0000000..3fd6af2
--- /dev/null
+++ b/packages/CredentialManager/res/values-is/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Hætta við"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Áfram"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Búa til annarsstaðar"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Vista annarsstaðar"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Nota annað tæki"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Vista í öðru tæki"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Einföld leið við örugga innskráningu"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Notaðu fingrafar, andlit eða skjálás til að skrá þig inn með einkvæmum aðgangslykli sem ekki er hægt að gleyma eða stela. Nánar"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Veldu hvar á að <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"vistaðu aðgangsorðið"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"vistaðu innskráningarupplýsingarnar"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Búa til aðgangslykil í <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vista aðgangsorð í <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vista innskráningarupplýsingar í <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Þú getur notað <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> í hvaða tæki sem er. Það er vistað á <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> fyrir <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"aðgangslykill"</string>
+    <string name="password" msgid="6738570945182936667">"aðgangsorð"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"innskráningar"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Nota <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> fyrir allar innskráningar?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Stilla sem sjálfgefið"</string>
+    <string name="use_once" msgid="9027366575315399714">"Nota einu sinni"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> aðgangsorð, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> aðgangslyklar"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> aðgangsorð"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> aðgangslyklar"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Annað tæki"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Önnur aðgangsorðastjórnun"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Loka blaði"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Fara aftur á fyrri síðu"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Notað vistaðan aðgangslykil fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Nota vistaða innskráningu fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Veldu vistaða innskráningu fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Skrá inn með öðrum hætti"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nei takk"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Áfram"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Innskráningarkostir"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Fyrir: <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Læst aðgangsorðastjórnun"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Ýttu til að opna"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Stjórna innskráningu"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Úr öðru tæki"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Nota annað tæki"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-it/strings.xml b/packages/CredentialManager/res/values-it/strings.xml
new file mode 100644
index 0000000..3a7b0fb
--- /dev/null
+++ b/packages/CredentialManager/res/values-it/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Gestore delle credenziali"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Annulla"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continua"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Crea in un altro luogo"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Salva in un altro luogo"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Usa un altro dispositivo"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Salva su un altro dispositivo"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Un modo semplice per accedere in sicurezza"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Usa l\'impronta digitale, il volto o il blocco schermo per accedere con una passkey unica che non può essere dimenticata o rubata. Scopri di più"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Scegli dove <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"salva la password"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"salva le tue informazioni di accesso"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vuoi creare una passkey su <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vuoi salvare la password su <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vuoi salvare le informazioni di accesso su <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Puoi utilizzare <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> su qualsiasi dispositivo. Questa informazione è salvata su <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> per <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"passkey"</string>
+    <string name="password" msgid="6738570945182936667">"password"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"accessi"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vuoi usare <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> per tutti gli accessi?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Imposta come valore predefinito"</string>
+    <string name="use_once" msgid="9027366575315399714">"Usa una volta"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> password, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkey"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> password"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkey"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
+    <string name="another_device" msgid="5147276802037801217">"Un altro dispositivo"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Altri gestori delle password"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Chiudi il foglio"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Torna alla pagina precedente"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vuoi usare la passkey salvata per <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vuoi usare l\'accesso salvato per <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Scegli un accesso salvato per <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Accedi in un altro modo"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No, grazie"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continua"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opzioni di accesso"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Per <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestori delle password bloccati"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tocca per sbloccare"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gestisci gli accessi"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Da un altro dispositivo"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usa un dispositivo diverso"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-iw/strings.xml b/packages/CredentialManager/res/values-iw/strings.xml
new file mode 100644
index 0000000..397ad60
--- /dev/null
+++ b/packages/CredentialManager/res/values-iw/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"מנהל פרטי הכניסה"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"ביטול"</string>
+    <string name="string_continue" msgid="1346732695941131882">"המשך"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"יצירה במקום אחר"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"שמירה במקום אחר"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"שימוש במכשיר אחר"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"שמירה במכשיר אחר"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"דרך פשוטה להיכנס לחשבון בבטחה"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"אפשר להשתמש בטביעת אצבע, בזיהוי פנים או בנעילת מסך כדי להיכנס לחשבון עם מפתח גישה ייחודי שאי אפשר לשכוח או לגנוב אותו. מידע נוסף"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"צריך לבחור לאן <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"שמירת הסיסמה שלך"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"שמירת פרטי הכניסה שלך"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"ליצור מפתח גישה ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"לשמור את הסיסמה ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"לשמור את פרטי הכניסה ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"אפשר להשתמש ב-<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> מסוג <xliff:g id="TYPE">%2$s</xliff:g> בכל מכשיר. הוא שמור ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> ל-<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"מפתח גישה"</string>
+    <string name="password" msgid="6738570945182936667">"סיסמה"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"פרטי כניסה"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"להשתמש ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> בכל הכניסות?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"הגדרה כברירת מחדל"</string>
+    <string name="use_once" msgid="9027366575315399714">"שימוש פעם אחת"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> סיסמאות, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> מפתחות גישה"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> סיסמאות"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> מפתחות גישה"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"מפתח גישה"</string>
+    <string name="another_device" msgid="5147276802037801217">"מכשיר אחר"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"מנהלי סיסמאות אחרים"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"סגירת הגיליון"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"חזרה לדף הקודם"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"להשתמש במפתח גישה שנשמר עבור <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"להשתמש בפרטי הכניסה שנשמרו עבור <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"בחירת פרטי כניסה שמורים עבור <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"כניסה בדרך אחרת"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"לא תודה"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"המשך"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"אפשרויות כניסה לחשבון"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"עבור <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"מנהלי סיסמאות נעולים"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"יש להקיש כדי לבטל את הנעילה"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ניהול כניסות"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ממכשיר אחר"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"צריך להשתמש במכשיר אחר"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-ja/strings.xml b/packages/CredentialManager/res/values-ja/strings.xml
new file mode 100644
index 0000000..0340b66
--- /dev/null
+++ b/packages/CredentialManager/res/values-ja/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"認証情報マネージャー"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"キャンセル"</string>
+    <string name="string_continue" msgid="1346732695941131882">"続行"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"別の場所で作成"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"別の場所に保存"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"別のデバイスを使用"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"他のデバイスに保存"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"安全にログインする簡単な方法"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"忘れたり盗まれたりする可能性がある一意のパスキーと合わせて、ログインに指紋認証、顔認証、画面ロックを使用できます。詳細"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> の保存場所の選択"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"パスワードを保存"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"ログイン情報を保存"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> でパスキーを作成しますか?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> にパスワードを保存しますか?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> にログイン情報を保存しますか?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> の <xliff:g id="TYPE">%2$s</xliff:g> はどのデバイスでも使用できます。<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> の <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> に保存されます"</string>
+    <string name="passkey" msgid="632353688396759522">"パスキー"</string>
+    <string name="password" msgid="6738570945182936667">"パスワード"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"ログイン"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"ログインのたびに <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> を使用しますか?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"デフォルトに設定"</string>
+    <string name="use_once" msgid="9027366575315399714">"1 回使用"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 件のパスワード、<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 件のパスキー"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 件のパスワード"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 件のパスキー"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"パスキー"</string>
+    <string name="another_device" msgid="5147276802037801217">"別のデバイス"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"他のパスワード マネージャー"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"シートを閉じます"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"前のページに戻ります"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> の保存したパスキーを使用しますか?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> の保存したログイン情報を使用しますか?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> の保存したログイン情報の選択"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"別の方法でログイン"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"利用しない"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"続行"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ログイン オプション"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> 用"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"パスワード マネージャー ロック中"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"タップしてロック解除"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ログインを管理"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"別のデバイスを使う"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"別のデバイスを使用"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml
new file mode 100644
index 0000000..3da7ea3
--- /dev/null
+++ b/packages/CredentialManager/res/values-ka/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"ავტორიზაციის მონაცემების მმართველი"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"გაუქმება"</string>
+    <string name="string_continue" msgid="1346732695941131882">"გაგრძელება"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"სხვა სივრცეში შექმნა"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"სხვა სივრცეში შენახვა"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"სხვა მოწყობილობის გამოყენება"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"სხვა მოწყობილობაზე შენახვა"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"უსაფრთხოდ შესვლის მარტივი გზა"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"გამოიყენეთ თქვენი თითის ანაბეჭდი, სახის ამოცნობა და ეკრანის დაბლოკვა სისტემაში უნიკალური წვდომის გასაღებით შესასვლელად, რომლის დავიწყება ან მოპარვა შეუძლებელია. შეიტყვეთ მეტი"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"აირჩიეთ, სად უნდა <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"შეინახეთ თქვენი პაროლი"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"შეინახეთ თქვენი სისტემაში შესვლის ინფორმაცია"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"გსურთ წვდომის გასაღების შექმნა <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-ში?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"გსურთ თქვენი პაროლის შენახვა <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-ში?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"გსურთ თქვენი სისტემაში შესვლის მონაცემების შენახვა <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-ში?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"შეგიძლიათ გამოიყენოთ თქვენი <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ნებისმიერ მოწყობილობაზე. ის შეინახება <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>-ზე <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>-თვის"</string>
+    <string name="passkey" msgid="632353688396759522">"წვდომის გასაღები"</string>
+    <string name="password" msgid="6738570945182936667">"პაროლი"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"სისტემაში შესვლა"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"გსურთ, გამოიყენოთ<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> სისტემაში ყველა შესვლისთვის?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"ნაგულისხმევად დაყენება"</string>
+    <string name="use_once" msgid="9027366575315399714">"ერთხელ გამოყენება"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> პაროლი, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> წვდომის გასაღები"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> პაროლი"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> წვდომის გასაღები"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"წვდომის გასაღები"</string>
+    <string name="another_device" msgid="5147276802037801217">"სხვა მოწყობილობა"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"პაროლების სხვა მმართველები"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"ფურცლის დახურვა"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"წინა გვერდზე დაბრუნება"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"გსურთ თქვენი დამახსოვრებული წვდომის გასაღების გამოყენება აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"გსურთ თქვენი დამახსოვრებული სისტემაში შესვლის მონაცემების გამოყენება აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"აირჩიეთ სისტემაში შესვლის ინფორმაცია აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"სხვა ხერხით შესვლა"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"არა, გმადლობთ"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"გაგრძელება"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"სისტემაში შესვლის ვარიანტები"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>-ისთვის"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ჩაკეტილი პაროლის მმართველები"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"შეეხეთ განსაბლოკად"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"სისტემაში შესვლის მონაცემების მართვა"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"სხვა მოწყობილობიდან"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"გამოიყენეთ სხვა მოწყობილობა"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-kk/strings.xml b/packages/CredentialManager/res/values-kk/strings.xml
new file mode 100644
index 0000000..9491f8e
--- /dev/null
+++ b/packages/CredentialManager/res/values-kk/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Бас тарту"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Жалғастыру"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Басқа орында жасау"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Басқа орынға сақтау"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Басқа құрылғыны пайдалану"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Басқа құрылғыға сақтау"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Қауіпсіз кірудің оңай жолы"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Ұмытылмайтын немесе ұрланбайтын бірегей кіру кілтінің көмегімен кіру үшін саусақ ізін, бетті анықтау функциясын немесе экран құлпын пайдаланыңыз. Толық ақпарат"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> таңдау"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"құпия сөзді сақтау"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"тіркелу деректерін сақтау"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> қолданбасында кіру кілті жасалсын ба?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> қолданбасына құпия сөз сақталсын ба?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> қолданбасына тіркелу деректері сақталсын ба?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> кез келген құрылғыда пайдаланылады. <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> тіркелу деректері <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> қолданбасында сақталады."</string>
+    <string name="passkey" msgid="632353688396759522">"кіру кілті"</string>
+    <string name="password" msgid="6738570945182936667">"құпия сөз"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"кіру әрекеттері"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Барлық кіру әрекеті үшін <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> пайдаланылсын ба?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Әдепкі етіп орнату"</string>
+    <string name="use_once" msgid="9027366575315399714">"Бір рет пайдалану"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> құпия сөз, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> кіру кілті"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> құпия сөз"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> кіру кілті"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Басқа құрылғы"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Басқа құпия сөз менеджерлері"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Парақты жабу"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Алдыңғы бетке оралу"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін сақталған кіру кілті пайдаланылсын ба?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін сақталған тіркелу деректері пайдаланылсын ба?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін сақталған тіркелу деректерін таңдаңыз"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Басқаша кіру"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Жоқ, рақмет"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Жалғастыру"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Кіру опциялары"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> үшін"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Құлыпталған құпия сөз менеджерлері"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Құлыпты ашу үшін түртіңіз."</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Кіру әрекеттерін басқару"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Басқа құрылғыдан жасау"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Басқа құрылғыны пайдалану"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-km/strings.xml b/packages/CredentialManager/res/values-km/strings.xml
new file mode 100644
index 0000000..80167fc
--- /dev/null
+++ b/packages/CredentialManager/res/values-km/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"បោះបង់"</string>
+    <string name="string_continue" msgid="1346732695941131882">"បន្ត"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"បង្កើតនៅកន្លែងផ្សេងទៀត"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"រក្សាទុកក្នុងកន្លែងផ្សេងទៀត"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"ប្រើ​ឧបករណ៍​ផ្សេងទៀត"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"រក្សាទុកទៅក្នុងឧបករណ៍ផ្សេង"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"វិធីដ៏សាមញ្ញ ដើម្បីចូលគណនីដោយសុវត្ថិភាព"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ប្រើស្នាមម្រាមដៃ មុខ ឬការចាក់សោអេក្រង់របស់អ្នក ដើម្បីចូលគណនីដោយប្រើកូដសម្ងាត់ខុសប្លែកពីគេដែលមិនអាចភ្លេច ឬត្រូវគេលួច។ ស្វែងយល់បន្ថែម"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"ជ្រើសរើសកន្លែងដែលត្រូវ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"រក្សាទុកពាក្យសម្ងាត់របស់អ្នក"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"រក្សាទុកព័ត៌មានចូលគណនីរបស់អ្នក"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"បង្កើតកូដសម្ងាត់នៅក្នុង <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ឬ?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"រក្សាទុកពាក្យសម្ងាត់របស់អ្នកក្នុង <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ឬ?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"រក្សាទុកព័ត៌មានចូលគណនីរបស់អ្នកក្នុង <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ឬ?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"អ្នកអាចប្រើ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> របស់អ្នកនៅលើឧបករណ៍ណាក៏បាន។ វាត្រូវបានរក្សាទុកក្នុង <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> សម្រាប់ <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"កូដសម្ងាត់"</string>
+    <string name="password" msgid="6738570945182936667">"ពាក្យសម្ងាត់"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"ការចូល​គណនី"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"ប្រើ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> សម្រាប់ការចូលគណនីទាំងអស់របស់អ្នកឬ?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"កំណត់ជាលំនាំដើម"</string>
+    <string name="use_once" msgid="9027366575315399714">"ប្រើម្ដង"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"ពាក្យសម្ងាត់ <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> កូដសម្ងាត់ <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"ពាក្យសម្ងាត់ <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"កូដសម្ងាត់ <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"ឧបករណ៍​ផ្សេងទៀត"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់ផ្សេងទៀត"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"បិទសន្លឹក"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ត្រឡប់ទៅ​ទំព័រ​មុនវិញ"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ប្រើកូដសម្ងាត់ដែលបានរក្សាទុករបស់អ្នកសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g> ឬ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ប្រើការចូល​គណនីដែលបានរក្សាទុករបស់អ្នកសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g> ឬ?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"ជ្រើសរើសការចូលគណនីដែលបានរក្សាទុកសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ចូលគណនីដោយប្រើវិធីផ្សេងទៀត"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"ទេ អរគុណ"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"បន្ត"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ជម្រើស​ចូលគណនី"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"សម្រាប់ <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់ដែលបានចាក់សោ"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"ចុចដើម្បីដោះសោ"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"គ្រប់គ្រងការចូល​គណនី"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ពីឧបករណ៍ផ្សេងទៀត"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ប្រើឧបករណ៍ផ្សេង"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-kn/strings.xml b/packages/CredentialManager/res/values-kn/strings.xml
new file mode 100644
index 0000000..96304ac
--- /dev/null
+++ b/packages/CredentialManager/res/values-kn/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"ರುಜುವಾತು ನಿರ್ವಾಹಕ"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"ರದ್ದುಗೊಳಿಸಿ"</string>
+    <string name="string_continue" msgid="1346732695941131882">"ಮುಂದುವರಿಸಿ"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"ಮತ್ತೊಂದು ಸ್ಥಳದಲ್ಲಿ ರಚಿಸಿ"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"ಮತ್ತೊಂದು ಸ್ಥಳದಲ್ಲಿ ಉಳಿಸಿ"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"ಬೇರೊಂದು ಸಾಧನವನ್ನು ಬಳಸಿ"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"ಬೇರೊಂದು ಸಾಧನದಲ್ಲಿ ಉಳಿಸಿ"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"ಸುರಕ್ಷಿತವಾಗಿ ಸೈನ್ ಇನ್ ಮಾಡುವ ಸುಲಭ ವಿಧಾನ"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ಮರೆಯಲಾಗದ ಅಥವಾ ಕದಿಯಲಾಗದ ಅನನ್ಯ ಪಾಸ್‌ಕೀ ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಲು ನಿಮ್ಮ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್, ಫೇಸ್ ಲಾಕ್ ಅಥವಾ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಬಳಸಿ. ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ಅನ್ನು ಎಲ್ಲಿ ಉಳಿಸಬೇಕು ಎಂದು ಆಯ್ಕೆಮಾಡಿ"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್‌ ಉಳಿಸಿ"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"ನಿಮ್ಮ ಸೈನ್-ಇನ್ ಮಾಹಿತಿ ಉಳಿಸಿ"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ನಲ್ಲಿ ಪಾಸ್‌ಕೀ ರಚಿಸಬೇಕೆ?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ಗೆ ಉಳಿಸಬೇಕೆ?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ನಿಮ್ಮ ಸೈನ್ ಇನ್ ಮಾಹಿತಿಯನ್ನು <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ಗೆ ಉಳಿಸಬೇಕೆ?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"ನೀವು ಯಾವುದೇ ಸಾಧನದಲ್ಲಿ ನಿಮ್ಮ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ಅನ್ನು ಬಳಸಬಹುದು. ಇದನ್ನು <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> ಗಾಗಿ <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> ಗೆ ಉಳಿಸಲಾಗಿದೆ"</string>
+    <string name="passkey" msgid="632353688396759522">"ಪಾಸ್‌ಕೀ"</string>
+    <string name="password" msgid="6738570945182936667">"ಪಾಸ್‌ವರ್ಡ್"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"ಸೈನ್-ಇನ್‌ಗಳು"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"ನಿಮ್ಮ ಎಲ್ಲಾ ಸೈನ್-ಇನ್‌ಗಳಿಗಾಗಿ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ಅನ್ನು ಬಳಸುವುದೇ?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"ಡೀಫಾಲ್ಟ್ ಆಗಿ ಸೆಟ್ ಮಾಡಿ"</string>
+    <string name="use_once" msgid="9027366575315399714">"ಒಂದು ಬಾರಿ ಬಳಸಿ"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ಪಾಸ್‌ಕೀಗಳು"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ಪಾಸ್‌ವರ್ಡ್‌ಗಳು"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ಪಾಸ್‌ಕೀಗಳು"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"ಪಾಸ್‌ಕೀ"</string>
+    <string name="another_device" msgid="5147276802037801217">"ಮತ್ತೊಂದು ಸಾಧನ"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"ಇತರ ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕರು"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"ಶೀಟ್ ಮುಚ್ಚಿರಿ"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ಹಿಂದಿನ ಪುಟಕ್ಕೆ ಹಿಂದಿರುಗಿ"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಉಳಿಸಲಾದ ನಿಮ್ಮ ಪಾಸ್‌ಕೀ ಅನ್ನು ಬಳಸಬೇಕೆ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಉಳಿಸಲಾದ ನಿಮ್ಮ ಸೈನ್-ಇನ್ ಅನ್ನು ಬಳಸಬೇಕೆ?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಉಳಿಸಲಾದ ಸೈನ್-ಇನ್ ಮಾಹಿತಿಯನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ಬೇರೆ ವಿಧಾನದಲ್ಲಿ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"ಬೇಡ"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ಮುಂದುವರಿಸಿ"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ಸೈನ್ ಇನ್ ಆಯ್ಕೆಗಳು"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> ಗಾಗಿ"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕರನ್ನು ಲಾಕ್ ಮಾಡಲಾಗಿದೆ"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"ಅನ್‌ಲಾಕ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ಸೈನ್-ಇನ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ಮತ್ತೊಂದು ಸಾಧನದಿಂದ"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ಬೇರೆ ಸಾಧನವನ್ನು ಬಳಸಿ"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-ko/strings.xml b/packages/CredentialManager/res/values-ko/strings.xml
new file mode 100644
index 0000000..58518c1
--- /dev/null
+++ b/packages/CredentialManager/res/values-ko/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"취소"</string>
+    <string name="string_continue" msgid="1346732695941131882">"계속"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"다른 위치에 만들기"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"다른 위치에 저장"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"다른 기기 사용"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"다른 기기에 저장"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"안전하게 로그인하는 간단한 방법"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"지문, 얼굴 인식 또는 화면 잠금을 통해 잊어버리거나 분실할 염려가 없는 고유한 패스키로 로그인하세요. 자세히 알아보기"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> 작업을 위한 위치 선택"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"비밀번호 저장"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"로그인 정보 저장"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>에 패스키를 만드시겠습니까?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>에 비밀번호를 저장하시겠습니까?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>에 로그인 정보를 저장하시겠습니까?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"기기에서 <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g>을(를) 사용할 수 있습니다. <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>을(를) 위해 <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>에 저장되어 있습니다."</string>
+    <string name="passkey" msgid="632353688396759522">"패스키"</string>
+    <string name="password" msgid="6738570945182936667">"비밀번호"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"로그인 정보"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"모든 로그인에 <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>을(를) 사용하시겠습니까?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"기본값으로 설정"</string>
+    <string name="use_once" msgid="9027366575315399714">"한 번 사용"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"비밀번호 <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>개, 패스키 <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>개"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"비밀번호 <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>개"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"패스키 <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>개"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"다른 기기"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"기타 비밀번호 관리자"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"시트 닫기"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"이전 페이지로 돌아가기"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱용 저장된 패스키를 사용하시겠습니까?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱용 저장된 로그인 정보를 사용하시겠습니까?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱용 저장된 로그인 정보 선택"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"다른 방법으로 로그인"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"아니요"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"계속"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"로그인 옵션"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>님의 로그인 정보"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"잠긴 비밀번호 관리자"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"잠금 해제하려면 탭하세요."</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"로그인 관리"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"다른 기기에서"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"다른 기기 사용"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-ky/strings.xml b/packages/CredentialManager/res/values-ky/strings.xml
new file mode 100644
index 0000000..298657e
--- /dev/null
+++ b/packages/CredentialManager/res/values-ky/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Жок"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Улантуу"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Башка жерде түзүү"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Башка жерге сактоо"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Башка түзмөк колдонуу"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Башка түзмөккө сактоо"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Коопсуз кирүүнүн жөнөкөй жолу"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Унутуп калууга же уурдатууга мүмкүн эмес болгон уникалдуу ачкыч менен манжа изин, жүзүнөн таанып ачуу же экранды кулпулоо функцияларын колдонуп өзүңүздү ырастай аласыз. Кененирээк"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> үчүн жер тандаңыз"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"сырсөзүңүздү сактаңыз"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"кирүү маалыматын сактаңыз"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> колдонмосунда мүмкүндүк алуу ачкычын түзөсүзбү?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Сырсөзүңүздү <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> колдонмосунда сактайсызбы?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Кирүү маалыматын <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> колдонмосунда сактайсызбы?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> каалаган түзмөктө колдонулат. <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> байланыштуу маалыматтар <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> колдонмосунда сакталат"</string>
+    <string name="passkey" msgid="632353688396759522">"мүмкүндүк алуу ачкычы"</string>
+    <string name="password" msgid="6738570945182936667">"сырсөз"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"кирүүлөр"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> бардык аккаунттарга кирүү үчүн колдонулсунбу?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Демейки катары коюу"</string>
+    <string name="use_once" msgid="9027366575315399714">"Бир жолу колдонуу"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> сырсөз, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> мүмкүндүк алуу ачкычы"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> сырсөз"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> мүмкүндүк алуу ачкычы"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Башка түзмөк"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Башка сырсөздөрдү башкаргычтар"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Баракты жабуу"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Мурунку бетке кайтуу"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган мүмкүндүк алуу ачкычын колдоносузбу?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган кирүү параметрин колдоносузбу?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн кирүү маалыматын тандаңыз"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Башка жол менен кирүү"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Жок, рахмат"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Улантуу"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Аккаунтка кирүү параметрлери"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> үчүн"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Кулпуланган сырсөздөрдү башкаргычтар"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Кулпусун ачуу үчүн таптаңыз"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Кирүү параметрлерин тескөө"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Башка түзмөктөн"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Башка түзмөктү колдонуу"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-lo/strings.xml b/packages/CredentialManager/res/values-lo/strings.xml
new file mode 100644
index 0000000..215262b
--- /dev/null
+++ b/packages/CredentialManager/res/values-lo/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"ຍົກເລີກ"</string>
+    <string name="string_continue" msgid="1346732695941131882">"ສືບຕໍ່"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"ສ້າງໃນບ່ອນອື່ນ"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"ບັນທຶກໃສ່ບ່ອນອື່ນ"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"ໃຊ້ອຸປະກອນອື່ນ"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"ບັນທຶກໃສ່ອຸປະກອນອື່ນ"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"ວິທີງ່າຍໆໃນການເຂົ້າສູ່ລະບົບຢ່າງປອດໄພ"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ໃຊ້ລາຍນິ້ວມື, ໃບໜ້າ ຫຼື ລັອກໜ້າຈໍຂອງທ່ານເພື່ອເຂົ້າສູ່ລະບົບດ້ວຍກະແຈຜ່ານທີ່ບໍ່ຊ້ຳກັນເພື່ອບໍ່ໃຫ້ລືມ ຫຼື ຖືກລັກໄດ້. ສຶກສາເພີ່ມເຕີມ"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"ເລືອກບ່ອນທີ່ຈະ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"ບັນທຶກລະຫັດຜ່ານຂອງທ່ານ"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"ບັນທຶກຂໍ້ມູນການເຂົ້າສູ່ລະບົບຂອງທ່ານ"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"ສ້າງກະແຈຜ່ານໃນ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ບໍ?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"ບັນທຶກລະຫັດຜ່ານຂອງທ່ານໃສ່ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ບໍ?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ບັນທຶກຂໍ້ມູນການເຂົ້າສູ່ລະບົບຂອງທ່ານໃສ່ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ບໍ?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"ທ່ານສາມາດໃຊ້ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ຂອງທ່ານຢູ່ອຸປະກອນໃດກໍໄດ້. ມັນຈະຖືກບັນທຶກໃສ່ <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> ສຳລັບ <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"ກະແຈຜ່ານ"</string>
+    <string name="password" msgid="6738570945182936667">"ລະຫັດຜ່ານ"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"ການເຂົ້າສູ່ລະບົບ"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"ໃຊ້ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ສຳລັບການເຂົ້າສູ່ລະບົບທັງໝົດຂອງທ່ານບໍ?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"ຕັ້ງເປັນຄ່າເລີ່ມຕົ້ນ"</string>
+    <string name="use_once" msgid="9027366575315399714">"ໃຊ້ເທື່ອດຽວ"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ລະຫັດຜ່ານ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ກະແຈຜ່ານ"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ລະຫັດຜ່ານ"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ກະແຈຜ່ານ"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"ອຸປະກອນອື່ນ"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"ຕົວຈັດການລະຫັດຜ່ານອື່ນໆ"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"ປິດຊີດ"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ກັບຄືນໄປຫາໜ້າກ່ອນໜ້ານີ້"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ໃຊ້ກະແຈຜ່ານທີ່ບັນທຶກໄວ້ຂອງທ່ານສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ໃຊ້ການເຂົ້າສູ່ລະບົບທີ່ບັນທຶກໄວ້ຂອງທ່ານສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"ເລືອກການເຂົ້າສູ່ລະບົບທີ່ບັນທຶກໄວ້ສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ເຂົ້າສູ່ລະບົບດ້ວຍວິທີອື່ນ"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"ບໍ່, ຂອບໃຈ"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ສືບຕໍ່"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ຕົວເລືອກການເຂົ້າສູ່ລະບົບ"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"ສຳລັບ <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ຕົວຈັດການລະຫັດຜ່ານທີ່ລັອກໄວ້"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"ແຕະເພື່ອປົດລັອກ"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ຈັດການການເຂົ້າສູ່ລະບົບ"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ຈາກອຸປະກອນອື່ນ"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ໃຊ້ອຸປະກອນອື່ນ"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-lt/strings.xml b/packages/CredentialManager/res/values-lt/strings.xml
new file mode 100644
index 0000000..6125fe3
--- /dev/null
+++ b/packages/CredentialManager/res/values-lt/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Prisijungimo duomenų tvarkytuvė"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Atšaukti"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Tęsti"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Sukurti kitoje vietoje"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Išsaugoti kitoje vietoje"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Naudoti kitą įrenginį"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Išsaugoti kitame įrenginyje"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Paprastas saugaus prisijungimo metodas"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Naudodami piršto atspaudą, veidą ar ekrano užraktą prisijunkite su unikaliu „passkey“, kurio neįmanoma pamiršti ar pavogti. Sužinokite daugiau"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Pasirinkite, kur <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"išsaugoti slaptažodį"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"išsaugoti prisijungimo informaciją"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Sukurti „passkey“ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Išsaugoti slaptažodį <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Išsaugoti prisijungimo informaciją <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Galite naudoti <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> bet kuriame įrenginyje. Jis išsaugomas <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> (<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>)"</string>
+    <string name="passkey" msgid="632353688396759522">"„passkey“"</string>
+    <string name="password" msgid="6738570945182936667">"slaptažodis"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"prisijungimo informacija"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Naudoti <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> visada prisijungiant?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Nustatyti kaip numatytąjį"</string>
+    <string name="use_once" msgid="9027366575315399714">"Naudoti vieną kartą"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"slaptažodžių: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, „passkey“: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"slaptažodžių: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"„passkey“: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Slaptažodis"</string>
+    <string name="another_device" msgid="5147276802037801217">"Kitas įrenginys"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Kitos slaptažodžių tvarkyklės"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Uždaryti lapą"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Grįžti į ankstesnį puslapį"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Naudoti išsaugotą „passkey“ programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Naudoti išsaugotą prisijungimo informaciją programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pasirinkite išsaugotą prisijungimo informaciją programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prisijungti kitu būdu"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ne, ačiū"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Tęsti"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Prisijungimo parinktys"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Skirta <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Užrakintos slaptažodžių tvarkyklės"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Palieskite, kad atrakintumėte"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Tvarkyti prisijungimo informaciją"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Naudojant kitą įrenginį"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Naudoti kitą įrenginį"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-lv/strings.xml b/packages/CredentialManager/res/values-lv/strings.xml
new file mode 100644
index 0000000..43c036f
--- /dev/null
+++ b/packages/CredentialManager/res/values-lv/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Akreditācijas datu pārvaldnieks"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Atcelt"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Turpināt"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Izveidot citur"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Saglabāt citur"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Izmantot citu ierīci"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Saglabāt citā ierīcē"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Vienkāršs veids, kā droši pierakstīties"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Izmantojiet pirksta nospiedumu, autorizāciju pēc sejas vai ekrāna bloķēšanu, lai pierakstītos ar unikālu piekļuves atslēgu, ko nevar aizmirst vai nozagt. Uzziniet vairāk."</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Izvēlieties, kur: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"saglabāt paroli"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"saglabāt pierakstīšanās informāciju"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vai izveidot piekļuves atslēgu šeit: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vai saglabāt paroli šeit: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vai saglabāt pierakstīšanās informāciju šeit: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Jebkurā ierīcē varat izmantot šo: <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g>. Tas tiek saglabāt šeit: <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>; mērķis: <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>."</string>
+    <string name="passkey" msgid="632353688396759522">"piekļuves atslēga"</string>
+    <string name="password" msgid="6738570945182936667">"parole"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"pierakstīšanās informācija"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vai vienmēr izmantot <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>, lai pierakstītos?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Iestatīt kā noklusējumu"</string>
+    <string name="use_once" msgid="9027366575315399714">"Izmantot vienreiz"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> paroles, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> piekļuves atslēgas"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> paroles"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> piekļuves atslēgas"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Piekļuves atslēga"</string>
+    <string name="another_device" msgid="5147276802037801217">"Cita ierīce"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Citi paroļu pārvaldnieki"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Aizvērt lapu"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Atgriezties iepriekšējā lapā"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vai izmantot saglabāto piekļuves atslēgu lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vai izmantot saglabāto pierakstīšanās informāciju lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Saglabātas pierakstīšanās informācijas izvēle lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Pierakstīties citā veidā"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nē, paldies"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Turpināt"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Pierakstīšanās opcijas"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Lietotājam <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Paroļu pārvaldnieki, kuros nepieciešams autentificēties"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Pieskarieties, lai atbloķētu"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Pierakstīšanās informācijas pārvaldība"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"No citas ierīces"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Izmantot citu ierīci"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-mk/strings.xml b/packages/CredentialManager/res/values-mk/strings.xml
new file mode 100644
index 0000000..059f042
--- /dev/null
+++ b/packages/CredentialManager/res/values-mk/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Управник на акредитиви"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Откажи"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Продолжи"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Создајте на друго место"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Зачувајте на друго место"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Употребете друг уред"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Зачувајте на друг уред"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Едноставен начин за безбедно најавување"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Користете го отпечатокот, заклучувањето со лик или заклучувањето екран за да се најавите со единствен криптографски клуч што не може да се заборави или украде. Дознајте повеќе"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Изберете каде да <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"се зачува лозинката"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"се зачуваат податоците за најавување"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Да се создаде криптографски клуч во <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Да се зачува вашата лозинка во <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Да се зачуваат вашите податоци за најавување во <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Вашиот <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> од типот <xliff:g id="TYPE">%2$s</xliff:g> може да го користите на секој уред. Зачуван е на <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> за <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"криптографски клуч"</string>
+    <string name="password" msgid="6738570945182936667">"лозинка"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"најавувања"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Да се користи <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> за сите ваши најавувања?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Постави како стандардна опција"</string>
+    <string name="use_once" msgid="9027366575315399714">"Употребете еднаш"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> лозинки, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> криптографски клучеви"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> лозинки"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> криптографски клучеви"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Криптографски клуч"</string>
+    <string name="another_device" msgid="5147276802037801217">"Друг уред"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Други управници со лозинки"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Затворете го листот"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Врати се на претходната страница"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Да се користи вашиот зачуван криптографски клуч за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Да се користи вашето зачувано најавување за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Изберете зачувано најавување за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Најавете се на друг начин"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Не, фала"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Продолжи"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Опции за најавување"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"За <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заклучени управници со лозинки"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Допрете за да отклучите"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Управувајте со најавувањата"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Од друг уред"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Употребете друг уред"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-ml/strings.xml b/packages/CredentialManager/res/values-ml/strings.xml
new file mode 100644
index 0000000..e4f6d69
--- /dev/null
+++ b/packages/CredentialManager/res/values-ml/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"ക്രെഡൻഷ്യൽ മാനേജർ"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"റദ്ദാക്കുക"</string>
+    <string name="string_continue" msgid="1346732695941131882">"തുടരുക"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"മറ്റൊരു സ്ഥലത്ത് സൃഷ്‌ടിക്കുക"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"മറ്റൊരു സ്ഥലത്തേക്ക് സംരക്ഷിക്കുക"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"മറ്റൊരു ഉപകരണം ഉപയോഗിക്കുക"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"മറ്റൊരു ഉപകരണത്തിലേക്ക് സംരക്ഷിക്കുക"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"സുരക്ഷിതമായി സൈൻ ഇൻ ചെയ്യാനുള്ള ലളിതമായ മാർഗ്ഗം"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"മറന്നുപോകാനും മോഷ്‌ടിക്കാനും സാധ്യതയില്ലാത്ത തനത് പാസ്‌കീ ഉപയോഗിച്ച് സൈൻ ഇൻ ചെയ്യാൻ നിങ്ങളുടെ ഫിംഗർപ്രിന്റോ മുഖമോ സ്‌ക്രീൻ ലോക്കോ ഉപയോഗിക്കുക. കൂടുതലറിയുക"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"എവിടെ <xliff:g id="CREATETYPES">%1$s</xliff:g> എന്ന് തിരഞ്ഞെടുക്കുക"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"നിങ്ങളുടെ പാസ്‌വേഡ് സംരക്ഷിക്കുക"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"നിങ്ങളുടെ സൈൻ ഇൻ വിവരങ്ങൾ സംരക്ഷിക്കുക"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> എന്നതിൽ ഒരു പാസ്‌കീ സൃഷ്‌ടിക്കണോ?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"പാസ്‌വേഡ് <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> എന്നതിലേക്ക് സംരക്ഷിക്കണോ?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"സൈൻ ഇൻ വിവരങ്ങൾ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> എന്നതിലേക്ക് സംരക്ഷിക്കണോ?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"നിങ്ങളുടെ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ഏത് ഉപകരണത്തിലും നിങ്ങൾക്ക് ഉപയോഗിക്കാം. ഇത് <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> എന്നതിനായി <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> എന്നതിലേക്ക് സംരക്ഷിച്ചു"</string>
+    <string name="passkey" msgid="632353688396759522">"പാസ്‌കീ"</string>
+    <string name="password" msgid="6738570945182936667">"പാസ്‌വേഡ്"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"സൈൻ ഇന്നുകൾ"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"നിങ്ങളുടെ എല്ലാ സൈൻ ഇന്നുകൾക്കും <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ഉപയോഗിക്കണോ?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"ഡിഫോൾട്ടായി സജ്ജീകരിക്കുക"</string>
+    <string name="use_once" msgid="9027366575315399714">"ഒരു തവണ ഉപയോഗിക്കുക"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> പാസ്‌വേഡുകൾ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> പാസ്‌കീകൾ"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> പാസ്‌വേഡുകൾ"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> പാസ്‌കീകൾ"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"പാസ്‌കീ"</string>
+    <string name="another_device" msgid="5147276802037801217">"മറ്റൊരു ഉപകരണം"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"മറ്റ് പാസ്‌വേഡ് മാനേജർമാർ"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"ഷീറ്റ് അടയ്ക്കുക"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"മുമ്പത്തെ പേജിലേക്ക് മടങ്ങുക"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനായി നിങ്ങൾ സംരക്ഷിച്ച പാസ്‌കീ ഉപയോഗിക്കണോ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനായി നിങ്ങൾ സംരക്ഷിച്ച സൈൻ ഇൻ ഉപയോഗിക്കണോ?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനായി ഒരു സംരക്ഷിച്ച സൈൻ ഇൻ തിരഞ്ഞെടുക്കുക"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"മറ്റൊരു രീതിയിൽ സൈൻ ഇൻ ചെയ്യുക"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"വേണ്ട"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"തുടരുക"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"സൈൻ ഇൻ ഓപ്ഷനുകൾ"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> എന്നയാൾക്ക്"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ലോക്ക് ചെയ്‌ത പാസ്‌വേഡ് സൈൻ ഇൻ മാനേജർമാർ"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"അൺലോക്ക് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"സൈൻ ഇന്നുകൾ മാനേജ് ചെയ്യുക"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"മറ്റൊരു ഉപകരണത്തിൽ നിന്ന്"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"മറ്റൊരു ഉപകരണം ഉപയോഗിക്കുക"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-mn/strings.xml b/packages/CredentialManager/res/values-mn/strings.xml
new file mode 100644
index 0000000..3f8d4ca
--- /dev/null
+++ b/packages/CredentialManager/res/values-mn/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Мандат үнэмлэхийн менежер"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Цуцлах"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Үргэлжлүүлэх"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Өөр газар үүсгэх"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Өөр газар хадгалах"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Өөр төхөөрөмж ашиглах"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Өөр төхөөрөмжид хадгалах"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Аюулгүй нэвтрэх энгийн арга"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Мартах эсвэл хулгайд алдах боломжгүй өвөрмөц passkey-н хамт нэвтрэх хурууны хээ, царай эсвэл дэлгэцийн түгжээгээ ашиглана уу. Нэмэлт мэдээлэл авах"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Хаана <xliff:g id="CREATETYPES">%1$s</xliff:g>-г сонгоно уу"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"нууц үгээ хадгалах"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"нэвтрэх мэдээллээ хадгалах"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-д passkey үүсгэх үү?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Нууц үгээ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-д хадгалах уу?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Нэвтрэх мэдээллээ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-д хадгалах уу?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Та өөрийн <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g>-г дурын төхөөрөмж дээр ашиглах боломжтой. Үүнийг <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>-д <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>-д зориулж хадгалсан"</string>
+    <string name="passkey" msgid="632353688396759522">"passkey"</string>
+    <string name="password" msgid="6738570945182936667">"нууц үг"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"нэвтрэлт"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-г бүх нэвтрэлтдээ ашиглах уу?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Өгөгдмөлөөр тохируулах"</string>
+    <string name="use_once" msgid="9027366575315399714">"Нэг удаа ашиглах"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> нууц үг, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkey"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> нууц үг"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkey"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
+    <string name="another_device" msgid="5147276802037801217">"Өөр төхөөрөмж"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Нууц үгний бусад менежер"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Хүснэгтийг хаах"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Өмнөх хуудас руу буцах"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д өөрийн хадгалсан passkey-г ашиглах уу?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д хадгалсан нэвтрэх мэдээллээ ашиглах уу?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д зориулж хадгалсан нэвтрэх мэдээллийг сонгоно уу"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Өөр аргаар нэвтрэх"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Үгүй, баярлалаа"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Үргэлжлүүлэх"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Нэвтрэх сонголт"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>-д"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Түгжээтэй нууц үгний менежерүүд"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Түгжээг тайлахын тулд товшино уу"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Нэвтрэлтийг удирдах"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Өөр төхөөрөмжөөс"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Өөр төхөөрөмж ашиглах"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-mr/strings.xml b/packages/CredentialManager/res/values-mr/strings.xml
new file mode 100644
index 0000000..aa6f253
--- /dev/null
+++ b/packages/CredentialManager/res/values-mr/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"रद्द करा"</string>
+    <string name="string_continue" msgid="1346732695941131882">"पुढे सुरू ठेवा"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"दुसऱ्या ठिकाणी तयार करा"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"दुसऱ्या ठिकाणी सेव्ह करा"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"दुसरे डिव्‍हाइस वापरा"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"दुसऱ्या डिव्हाइसवर सेव्ह करा"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"सुरक्षितपणे साइन इन करण्याचा सोपा मार्ग"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"युनिक पासकीसह साइन इन करण्यासाठी तुमचे फिंगरप्रिंट, फेस किंवा स्क्रीन लॉक वापरा, जे विसरता येणार नाही किंवा चोरीला जाणार नाही. अधिक जाणून घ्या"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> कुठे करायचे ते निवडा"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"तुमचा पासवर्ड सेव्ह करा"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"तुमची साइन-इन माहिती सेव्ह करा"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> मध्ये पासकी तयार करायची का?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"तुमचा पासवर्ड <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> वर सेव्ह करायचा का?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"तुमची साइन-इन माहिती <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> वर सेव्ह करायची का?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"तुम्ही कोणत्याही डिव्हाइसवर तुमचे <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> वापरू शकता. ते <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> साठी <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> वर सेव्ह केले जाते"</string>
+    <string name="passkey" msgid="632353688396759522">"पासकी"</string>
+    <string name="password" msgid="6738570945182936667">"पासवर्ड"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"साइन-इन"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"तुमच्या सर्व साइन-इन साठी <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>वापरायचे का?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"डिफॉल्ट म्हणून सेट करा"</string>
+    <string name="use_once" msgid="9027366575315399714">"एकदा वापरा"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> पासकी"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> पासकी"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"इतर डिव्हाइस"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"इतर पासवर्ड व्यवस्थापक"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"शीट बंद करा"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"मागील पेजवर परत जा"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी तुमची सेव्ह केलेली पासकी वापरायची का?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी तुमचे सेव्ह केलेले साइन-इन वापरायचे का?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी सेव्ह केलेले साइन-इन निवडा"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"दुसऱ्या मार्गाने साइन इन करा"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"नाही, नको"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"पुढे सुरू ठेवा"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"साइन इन पर्याय"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> साठी"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"लॉक केलेले पासवर्ड व्यवस्थापक"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"अनलॉक करण्यासाठी टॅप करा"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"साइन-इन व्यवस्थापित करा"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"दुसऱ्या डिव्हाइस वरून"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"वेगळे डिव्हाइस वापरा"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-ms/strings.xml b/packages/CredentialManager/res/values-ms/strings.xml
new file mode 100644
index 0000000..d5f8c0e
--- /dev/null
+++ b/packages/CredentialManager/res/values-ms/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Pengurus Bukti Kelayakan"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Batal"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Teruskan"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Buat di tempat lain"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Simpan di tempat lain"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Gunakan peranti lain"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Simpan kepada peranti lain"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Cara mudah untuk log masuk dengan selamat"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gunakan cap jari, wajah atau kunci skrin anda untuk log masuk menggunakan kunci laluan unik yang tidak boleh dilupakan atau dicuri. Ketahui lebih lanjut"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Pilih tempat untuk <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"simpan kata laluan anda"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"simpan maklumat log masuk anda"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Buat kunci laluan dalam <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Simpan kata laluan anda pada <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Simpan maklumat log masuk anda pada <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Anda boleh menggunakan <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> anda pada mana-mana peranti. Ia disimpan pada <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> untuk <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"kunci laluan"</string>
+    <string name="password" msgid="6738570945182936667">"kata laluan"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"log masuk"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gunakan <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> untuk semua log masuk anda?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Tetapkan sebagai lalai"</string>
+    <string name="use_once" msgid="9027366575315399714">"Gunakan sekali"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Kata laluan <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, kunci laluan <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"Kata laluan <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Kunci laluan <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Kunci laluan"</string>
+    <string name="another_device" msgid="5147276802037801217">"Peranti lain"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Password Manager lain"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Tutup helaian"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Kembali ke halaman sebelumnya"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gunakan kunci laluan anda yang telah disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gunakan maklumat log masuk anda yang telah disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pilih log masuk yang telah disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Log masuk menggunakan cara lain"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Tidak perlu"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Teruskan"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Pilihan log masuk"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Untuk <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Password Manager dikunci"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Ketik untuk membuka kunci"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Urus log masuk"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Daripada peranti lain"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gunakan peranti yang lain"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-my/strings.xml b/packages/CredentialManager/res/values-my/strings.xml
new file mode 100644
index 0000000..eda2f741
--- /dev/null
+++ b/packages/CredentialManager/res/values-my/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"မလုပ်တော့"</string>
+    <string name="string_continue" msgid="1346732695941131882">"ရှေ့ဆက်ရန်"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"နောက်တစ်နေရာတွင် ပြုလုပ်ရန်"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"နောက်တစ်နေရာတွင် သိမ်းရန်"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"စက်နောက်တစ်ခု သုံးရန်"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"စက်နောက်တစ်ခုတွင် သိမ်းရန်"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"လုံခြုံစွာလက်မှတ်ထိုးဝင်ရန် ရိုးရှင်းသောနည်း"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"မေ့မသွား (သို့) ခိုးမသွားနိုင်သော သီးခြားလျှို့ဝှက်ကီးဖြင့် လက်မှတ်ထိုးဝင်ရန် သင့်လက်ဗွေ၊ မျက်နှာ (သို့) ဖန်သားပြင်လော့ခ် သုံးနိုင်သည်။ ပိုမိုလေ့လာရန်"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ရန် နေရာရွေးပါ"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"သင့်စကားဝှက် သိမ်းရန်"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"သင်၏ လက်မှတ်ထိုးဝင်သည့်အချက်အလက်ကို သိမ်းရန်"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> တွင် လျှို့ဝှက်ကီး ပြုလုပ်မလား။"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"သင့်စကားဝှက်ကို <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> တွင် သိမ်းမလား။"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"သင်၏ လက်မှတ်ထိုးဝင်သည့်အချက်အလက်ကို <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> တွင် သိမ်းမလား။"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"မည်သည့်စက်တွင်မဆို သင်၏ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ကို သုံးနိုင်သည်။ ၎င်းကို <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> အတွက် <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> တွင် သိမ်းလိုက်သည်"</string>
+    <string name="passkey" msgid="632353688396759522">"လျှို့ဝှက်ကီး"</string>
+    <string name="password" msgid="6738570945182936667">"စကားဝှက်"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"လက်မှတ်ထိုးဝင်မှုများ"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"သင်၏လက်မှတ်ထိုးဝင်မှု အားလုံးအတွက် <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> သုံးမလား။"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"မူရင်းအဖြစ် သတ်မှတ်ရန်"</string>
+    <string name="use_once" msgid="9027366575315399714">"တစ်ကြိမ်သုံးရန်"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"စကားဝှက် <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ခု၊ လျှို့ဝှက်ကီး <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ခု"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"စကားဝှက် <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ခု"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"လျှို့ဝှက်ကီး <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ခု"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"စက်နောက်တစ်ခု"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"အခြားစကားဝှက်မန်နေဂျာများ"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"စာမျက်နှာ ပိတ်ရန်"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ယခင်စာမျက်နှာကို ပြန်သွားပါ"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသောလျှို့ဝှက်ကီး သုံးမလား။"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသောလက်မှတ်ထိုးဝင်မှု သုံးမလား။"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသော လက်မှတ်ထိုးဝင်မှုကို ရွေးပါ"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"နောက်တစ်နည်းဖြင့် လက်မှတ်ထိုးဝင်ရန်"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"မလိုပါ"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ရှေ့ဆက်ရန်"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"လက်မှတ်ထိုးဝင်ရန် နည်းလမ်းများ"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> အတွက်"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"လော့ခ်ချထားသည့် စကားဝှက်မန်နေဂျာများ"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"ဖွင့်ရန် တို့ပါ"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"လက်မှတ်ထိုးဝင်မှုများ စီမံခြင်း"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"စက်နောက်တစ်ခုမှ"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"အခြားစက်သုံးရန်"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-nb/strings.xml b/packages/CredentialManager/res/values-nb/strings.xml
new file mode 100644
index 0000000..82854b8
--- /dev/null
+++ b/packages/CredentialManager/res/values-nb/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Avbryt"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Fortsett"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Opprett på et annet sted"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Lagre på et annet sted"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Bruk en annen enhet"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Lagre på en annen enhet"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"En enkel og trygg påloggingsmåte"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Bruk fingeravtrykk, ansiktet eller en skjermlås til å logge på med en unik tilgangsnøkkel du verken kan glemme eller miste. Finn ut mer"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Velg hvor <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"lagre passordet ditt"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"lagre påloggingsinformasjonen din"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vil du opprette en tilgangsnøkkel i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vil du lagre passordet i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vil du lagre påloggingsinformasjonen i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Du kan bruke <xliff:g id="TYPE">%2$s</xliff:g>-elementet for <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> på alle slags enheter. Den lagres i <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> for <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"tilgangsnøkkel"</string>
+    <string name="password" msgid="6738570945182936667">"passord"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"pålogginger"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vil du bruke <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for alle pålogginger?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Angi som standard"</string>
+    <string name="use_once" msgid="9027366575315399714">"Bruk én gang"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passord, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> tilgangsnøkler"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passord"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> tilgangsnøkler"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"En annen enhet"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Andre løsninger for passordlagring"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Lukk arket"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gå tilbake til den forrige siden"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vil du bruke den lagrede tilgangsnøkkelen for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vil du bruke den lagrede påloggingen for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Velg en lagret pålogging for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Bruk en annen påloggingsmetode"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nei takk"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Fortsett"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Påloggingsalternativer"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Låste løsninger for passordlagring"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Trykk for å låse opp"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Administrer pålogginger"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Fra en annen enhet"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Bruk en annen enhet"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-ne/strings.xml b/packages/CredentialManager/res/values-ne/strings.xml
new file mode 100644
index 0000000..23f4f43
--- /dev/null
+++ b/packages/CredentialManager/res/values-ne/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"रद्द गर्नुहोस्"</string>
+    <string name="string_continue" msgid="1346732695941131882">"जारी राख्नुहोस्"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"अर्को ठाउँमा बनाउनुहोस्"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"अर्को ठाउँमा सेभ गर्नुहोस्"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"अर्को डिभाइस प्रयोग गर्नुहोस्"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"अर्को डिभाइसमा सेभ गर्नुहोस्"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"सुरक्षित तरिकाले साइन इन गर्ने सरल तरिका"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"नभुलिने वा चोरी नहुने खालको अद्वितीय पासकीका साथै आफ्ना फिंगरप्रिन्ट, अनुहार वा स्क्रिन लक प्रयोग गरी साइन इन गर्नुहोस्। थप जान्नुहोस्"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> सेभ गर्ने ठाउँ छनौट गर्नुहोस्"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"आफ्नो पासवर्ड सेभ गर्नुहोस्"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"आफ्नो साइन इनसम्बन्धी जानकारी सेभ गर्नुहोस्"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> मा पासकी बनाउने हो?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"तपाईंको पासवर्ड <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> मा सेभ गर्ने हो?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"तपाईंको साइन इनसम्बन्धी जानकारी <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> मा सेभ गर्ने हो?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"तपाईं जुनसुकै डिभाइसमा आफ्नो <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> प्रयोग गर्न सक्नुहुन्छ। यसलाई <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> का लागि <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> मा सेभ गरिएको छ"</string>
+    <string name="passkey" msgid="632353688396759522">"पासकी"</string>
+    <string name="password" msgid="6738570945182936667">"पासवर्ड"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"साइन इनसम्बन्धी जानकारी"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"तपाईंले साइन इन गर्ने सबै डिभाइसहरूमा <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> प्रयोग गर्ने हो?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"डिफल्ट जानकारीका रूपमा सेट गर्नुहोस्"</string>
+    <string name="use_once" msgid="9027366575315399714">"एक पटक प्रयोग गर्नुहोस्"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> वटा पासवर्ड, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> वटा पासकी"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> वटा पासवर्ड"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> वटा पासकी"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"अर्को डिभाइस"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"अन्य पासवर्ड म्यानेजरहरू"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"पाना बन्द गर्नुहोस्"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"अघिल्लो पेजमा फर्कनुहोस्"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"आफूले सेभ गरेको पासकी प्रयोग गरी <xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्ने हो?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"आफूले सेभ गरेको साइन इनसम्बन्धी जानकारी प्रयोग गरी <xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्ने हो?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्नका लागि सेभ गरिएका साइन इनसम्बन्धी जानकारी छनौट गर्नुहोस्"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"अर्कै विधि प्रयोग गरी साइन इन गर्नुहोस्"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"पर्दैन, धन्यवाद"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"जारी राख्नुहोस्"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"साइन‍ इनसम्बन्धी विकल्पहरू"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> का लागि"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"लक गरिएका पासवर्ड म्यानेजरहरू"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"अनलक गर्न ट्याप गर्नुहोस्"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"साइन इनसम्बन्धी विकल्पहरू व्यवस्थापन गर्नुहोस्"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"अर्को डिभाइसका लागि"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"अर्कै डिभाइस प्रयोग गरी हेर्नुहोस्"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-nl/strings.xml b/packages/CredentialManager/res/values-nl/strings.xml
new file mode 100644
index 0000000..c91a318
--- /dev/null
+++ b/packages/CredentialManager/res/values-nl/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Annuleren"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Doorgaan"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Op een andere locatie maken"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Op een andere locatie opslaan"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Een ander apparaat gebruiken"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Opslaan op een ander apparaat"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Een makkelijke manier om beveiligd in te loggen"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gebruik je vingerafdruk, gezichtsvergrendeling of schermvergrendeling om in te loggen met een unieke toegangssleutel die je niet kunt vergeten en die anderen niet kunnen stelen. Meer informatie"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Een locatie kiezen voor <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"je wachtwoord opslaan"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"je inloggegevens opslaan"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Een toegangssleutel maken in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Je wachtwoord opslaan in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Je inloggegevens opslaan in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Je kunt je <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> op elk apparaat gebruiken. Het wordt opgeslagen in <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> voor <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"toegangssleutel"</string>
+    <string name="password" msgid="6738570945182936667">"wachtwoord"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"inloggegevens"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> elke keer gebruiken als je inlogt?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Instellen als standaard"</string>
+    <string name="use_once" msgid="9027366575315399714">"Eén keer gebruiken"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wachtwoorden, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> toegangssleutels"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wachtwoorden"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> toegangssleutels"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Toegangssleutel"</string>
+    <string name="another_device" msgid="5147276802037801217">"Een ander apparaat"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Andere wachtwoordmanagers"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Blad sluiten"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Ga terug naar de vorige pagina"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Je opgeslagen toegangssleutel voor <xliff:g id="APP_NAME">%1$s</xliff:g> gebruiken?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Je opgeslagen inloggegevens voor <xliff:g id="APP_NAME">%1$s</xliff:g> gebruiken?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Opgeslagen inloggegevens kiezen voor <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Inloggen via een andere methode"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nee, bedankt"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Doorgaan"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opties voor inloggen"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Voor <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Vergrendelde wachtwoordmanagers"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tik om te ontgrendelen"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Inloggegevens beheren"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Via een ander apparaat"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Een ander apparaat gebruiken"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-or/strings.xml b/packages/CredentialManager/res/values-or/strings.xml
new file mode 100644
index 0000000..838ddfe
--- /dev/null
+++ b/packages/CredentialManager/res/values-or/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"ବାତିଲ କରନ୍ତୁ"</string>
+    <string name="string_continue" msgid="1346732695941131882">"ଜାରି ରଖନ୍ତୁ"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"ଅନ୍ୟ ଏକ ସ୍ଥାନରେ ତିଆରି କରନ୍ତୁ"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"ଅନ୍ୟ ଏକ ସ୍ଥାନରେ ସେଭ କରନ୍ତୁ"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"ଅନ୍ୟ ଏହି ଡିଭାଇସ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"ଅନ୍ୟ ଏକ ଡିଭାଇସରେ ସେଭ କରନ୍ତୁ"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"ସୁରକ୍ଷିତ ଭାବେ ସାଇନ ଇନ କରିବାର ଏକ ସରଳ ଉପାୟ"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ଏକ ସ୍ୱତନ୍ତ୍ର ପାସକୀ ମାଧ୍ୟମରେ ସାଇନ ଇନ କରିବା ପାଇଁ ଆପଣଙ୍କ ଟିପଚିହ୍ନ, ଫେସ କିମ୍ବା ସ୍କ୍ରିନ ଲକ ବ୍ୟବହାର କରନ୍ତୁ ଯାହାକୁ ଭୁଲି ପାରିବେ ନାହିଁ କିମ୍ବା ଚୋରି ହୋଇପାରିବ ନାହିଁ। ଅଧିକ ଜାଣନ୍ତୁ"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"କେଉଁଠି <xliff:g id="CREATETYPES">%1$s</xliff:g> କରିବେ, ତାହା ବାଛନ୍ତୁ"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"ଆପଣଙ୍କ ପାସୱାର୍ଡ ସେଭ କରନ୍ତୁ"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"ଆପଣଙ୍କ ସାଇନ-ଇନ ସୂଚନା ସେଭ କରନ୍ତୁ"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ରେ ଏକ ପାସକୀ ତିଆରି କରିବେ?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"ଆପଣଙ୍କ ପାସୱାର୍ଡକୁ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ରେ ସେଭ କରିବେ?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ଆପଣଙ୍କ ସାଇନ-ଇନ ସୂଚନାକୁ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ରେ ସେଭ କରିବେ?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"ଆପଣ ଯେ କୌଣସି ଡିଭାଇସରେ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g>କୁ ବ୍ୟବହାର କରିପାରିବେ। ଏହାକୁ <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> ପାଇଁ <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>ରେ ସେଭ କରାଯାଏ"</string>
+    <string name="passkey" msgid="632353688396759522">"ପାସକୀ"</string>
+    <string name="password" msgid="6738570945182936667">"ପାସୱାର୍ଡ"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"ସାଇନ-ଇନ"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"ଆପଣଙ୍କ ସମସ୍ତ ସାଇନ-ଇନ ପାଇଁ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ବ୍ୟବହାର କରିବେ?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"ଡିଫଲ୍ଟ ଭାବେ ସେଟ କରନ୍ତୁ"</string>
+    <string name="use_once" msgid="9027366575315399714">"ଥରେ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ଟି ପାସୱାର୍ଡ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>ଟି ପାସକୀ"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ଟି ପାସୱାର୍ଡ"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>ଟି ପାସକୀ"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"ଅନ୍ୟ ଏକ ଡିଭାଇସ"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"ଅନ୍ୟ Password Manager"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"ସିଟ ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ପୂର୍ବବର୍ତ୍ତୀ ପୃଷ୍ଠାକୁ ଫେରନ୍ତୁ"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ସେଭ କରାଯାଇଥିବା ଆପଣଙ୍କ ପାସକୀ ବ୍ୟବହାର କରିବେ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ସେଭ କରାଯାଇଥିବା ଆପଣଙ୍କ ସାଇନ-ଇନ ବ୍ୟବହାର କରିବେ?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ସେଭ କରାଯାଇଥିବା ଏକ ସାଇନ-ଇନ ବାଛନ୍ତୁ"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ଅନ୍ୟ ଏକ ଉପାୟରେ ସାଇନ ଇନ କରନ୍ତୁ"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"ନା, ଧନ୍ୟବାଦ"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ଜାରି ରଖନ୍ତୁ"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ସାଇନ ଇନ ବିକଳ୍ପଗୁଡ଼ିକ"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>ରେ"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ଲକ ଥିବା Password Manager"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"ଅନଲକ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ସାଇନ-ଇନ ପରିଚାଳନା କରନ୍ତୁ"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ଅନ୍ୟ ଏକ ଡିଭାଇସରୁ"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ଏକ ଭିନ୍ନ ଡିଭାଇସ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-pa/strings.xml b/packages/CredentialManager/res/values-pa/strings.xml
new file mode 100644
index 0000000..74b2ab1
--- /dev/null
+++ b/packages/CredentialManager/res/values-pa/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"ਰੱਦ ਕਰੋ"</string>
+    <string name="string_continue" msgid="1346732695941131882">"ਜਾਰੀ ਰੱਖੋ"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"ਕਿਸੇ ਹੋਰ ਥਾਂ \'ਤੇ ਬਣਾਓ"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"ਕਿਸੇ ਹੋਰ ਥਾਂ \'ਤੇ ਰੱਖਿਅਤ ਕਰੋ"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"ਕੋਈ ਹੋਰ ਡੀਵਾਈਸ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"ਕਿਸੇ ਹੋਰ ਡੀਵਾਈਸ \'ਤੇ ਰੱਖਿਅਤ ਕਰੋ"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਦਾ ਆਸਾਨ ਤਰੀਕਾ"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ਵਿਲੱਖਣ ਪਾਸਕੀ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਵਾਸਤੇ ਆਪਣੇ ਫਿੰਗਰਪ੍ਰਿੰਟ, ਚਿਹਰੇ ਜਾਂ ਸਕ੍ਰੀਨ ਲਾਕ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਿਸਨੂੰ ਭੁੱਲਿਆ ਜਾਂ ਚੋਰੀ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ। ਹੋਰ ਜਾਣੋ"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ਲਈ ਕੋਈ ਥਾਂ ਚੁਣੋ"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"ਆਪਣਾ ਪਾਸਵਰਡ ਰੱਖਿਅਤ ਕਰੋ"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"ਆਪਣੀ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਰੱਖਿਅਤ ਕਰੋ"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"ਕੀ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਵਿੱਚ ਪਾਸਕੀ ਨੂੰ ਬਣਾਉਣਾ ਹੈ?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"ਕੀ ਆਪਣੇ ਪਾਸਵਰਡ ਨੂੰ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਵਿੱਚ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ਕੀ ਆਪਣੀ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਨੂੰ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਵਿੱਚ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"ਤੁਸੀਂ ਕਿਸੇ ਵੀ ਡੀਵਾਈਸ \'ਤੇ ਆਪਣੀ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੇ ਹੋ। ਇਸਨੂੰ <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> ਲਈ <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> ਵਿੱਚ ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="passkey" msgid="632353688396759522">"ਪਾਸਕੀ"</string>
+    <string name="password" msgid="6738570945182936667">"ਪਾਸਵਰਡ"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"ਸਾਈਨ-ਇਨਾਂ ਦੀ ਜਾਣਕਾਰੀ"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"ਕੀ ਆਪਣੇ ਸਾਰੇ ਸਾਈਨ-ਇਨਾਂ ਲਈ<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਵਜੋਂ ਸੈੱਟ ਕਰੋ"</string>
+    <string name="use_once" msgid="9027366575315399714">"ਇੱਕ ਵਾਰ ਵਰਤੋ"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ਪਾਸਵਰਡ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ਪਾਸਕੀਆਂ"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ਪਾਸਵਰਡ"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ਪਾਸਕੀਆਂ"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"ਹੋਰ ਡੀਵਾਈਸ"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"ਹੋਰ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"ਸ਼ੀਟ ਬੰਦ ਕਰੋ"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ਪਿਛਲੇ ਪੰਨੇ \'ਤੇ ਵਾਪਸ ਜਾਓ"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਆਪਣੀ ਰੱਖਿਅਤ ਕੀਤੀ ਪਾਸਕੀ ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਆਪਣੀ ਰੱਖਿਅਤ ਕੀਤੀ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਰੱਖਿਅਤ ਕੀਤੀ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਚੁਣੋ"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ਕਿਸੇ ਹੋਰ ਤਰੀਕੇ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰੋ"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ਜਾਰੀ ਰੱਖੋ"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ਸਾਈਨ-ਇਨ ਕਰਨ ਦੇ ਵਿਕਲਪ"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> ਲਈ"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ਲਾਕ ਕੀਤੇ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ਸਾਈਨ-ਇਨਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ਹੋਰ ਡੀਵਾਈਸ ਤੋਂ"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ਵੱਖਰੇ ਡੀਵਾਈਸ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-pl/strings.xml b/packages/CredentialManager/res/values-pl/strings.xml
new file mode 100644
index 0000000..af6bc9d
--- /dev/null
+++ b/packages/CredentialManager/res/values-pl/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Anuluj"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Dalej"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Utwórz w innym miejscu"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Zapisz w innym miejscu"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Użyj innego urządzenia"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Zapisz na innym urządzeniu"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Prosty sposób na bezpieczne logowanie"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Używaj odcisku palca, rozpoznawania twarzy lub blokady ekranu, aby logować się z wykorzystaniem unikalnego klucza, którego nie można zapomnieć ani utracić w wyniku kradzieży. Więcej informacji"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Wybierz, gdzie chcesz <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"zapisać hasło"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"zapisać dane logowania"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Utworzyć klucz w usłudze <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Zapisać hasło w usłudze <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Zapisać dane logowania w usłudze <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Elementu <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> typu <xliff:g id="TYPE">%2$s</xliff:g> możesz używać na każdym urządzeniu. Jest zapisany w usłudze <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> (<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>)"</string>
+    <string name="passkey" msgid="632353688396759522">"klucz"</string>
+    <string name="password" msgid="6738570945182936667">"hasło"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"dane logowania"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Używać usługi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> w przypadku wszystkich danych logowania?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Ustaw jako domyślną"</string>
+    <string name="use_once" msgid="9027366575315399714">"Użyj raz"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Hasła: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, klucze: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"Hasła: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Klucze: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Inne urządzenie"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Inne menedżery haseł"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Zamknij arkusz"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Wróć do poprzedniej strony"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Użyć zapisanego klucza dla aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Użyć zapisanych danych logowania dla aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Wybierz zapisane dane logowania dla aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Zaloguj się w inny sposób"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nie, dziękuję"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Dalej"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcje logowania"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Zablokowane menedżery haseł"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Kliknij, aby odblokować"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Zarządzanie danymi logowania"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Na innym urządzeniu"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Użyj innego urządzenia"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-pt-rBR/strings.xml b/packages/CredentialManager/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..d950bb4
--- /dev/null
+++ b/packages/CredentialManager/res/values-pt-rBR/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Gerenciador de credenciais"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Criar em outro lugar"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Salvar em outro lugar"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Usar outro dispositivo"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Salvar em outro dispositivo"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Uma maneira simples de fazer login com segurança"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use a impressão digital, o reconhecimento facial ou um bloqueio de tela para fazer login com uma única chave de acesso que não pode ser esquecida ou perdida. Saiba mais"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Escolha onde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"salvar sua senha"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"salvar suas informações de login"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Criar uma chave de acesso em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Salvar sua senha em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Salvar suas informações de login em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Você pode usar seu <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> em qualquer dispositivo. Ele está salvo em <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> para <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
+    <string name="password" msgid="6738570945182936667">"senha"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"logins"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus logins?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Definir como padrão"</string>
+    <string name="use_once" msgid="9027366575315399714">"Usar uma vez"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> chaves de acesso"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Chave de acesso"</string>
+    <string name="another_device" msgid="5147276802037801217">"Outro dispositivo"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Outros gerenciadores de senha"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Fechar página"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Voltar à página anterior"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Usar sua chave de acesso salva para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Usar suas informações de login salvas para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Escolher um login salvo para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Fazer login de outra forma"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Agora não"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opções de login"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gerenciadores de senha bloqueados"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Toque para desbloquear"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gerenciar logins"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De outro dispositivo"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar um dispositivo diferente"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-pt-rPT/strings.xml b/packages/CredentialManager/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..c46143c
--- /dev/null
+++ b/packages/CredentialManager/res/values-pt-rPT/strings.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Criar noutro lugar"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Guardar noutro lugar"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Usar outro dispositivo"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Guardar noutro dispositivo"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Uma forma simples de iniciar sessão em segurança"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use a sua impressão digital, rosto ou bloqueio de ecrã para iniciar sessão com uma chave de acesso única que não pode ser esquecida nem perdida. Saiba mais"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Escolha onde quer guardar <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"criar as suas chaves de acesso"</string>
+    <string name="save_your_password" msgid="6597736507991704307">"guardar a sua palavra-passe"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"guardar as suas informações de início de sessão"</string>
+    <string name="choose_provider_body" msgid="8045759834416308059">"Defina um gestor de palavras-passe predefinido para guardar as suas palavras-passe e chaves de acesso e iniciar sessão mais rapidamente da próxima vez."</string>
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Criar uma chave de acesso em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Guardar a sua palavra-passe em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Guardar as suas informações de início de sessão em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Pode usar <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> em qualquer dispositivo. É guardado em <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> para <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
+    <string name="password" msgid="6738570945182936667">"palavra-passe"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"inícios de sessão"</string>
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Criar chave de acesso em"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Guardar palavra-passe em"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Guardar início de sessão em"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Criar uma chave de acesso noutro dispositivo?"</string>
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus inícios de sessão?"</string>
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Este gestor de palavras-passe armazena as suas palavras-passe e chaves de acesso para ajudar a iniciar sessão facilmente."</string>
+    <string name="set_as_default" msgid="4415328591568654603">"Definir como predefinição"</string>
+    <string name="use_once" msgid="9027366575315399714">"Usar uma vez"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> palavras-passe, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> palavras-passe"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> chaves de acesso"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Chave de acesso"</string>
+    <string name="another_device" msgid="5147276802037801217">"Outro dispositivo"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Outros gestores de palavras-passe"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Fechar folha"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Volte à página anterior"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Usar a sua chave de acesso guardada na app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Usar o seu início de sessão guardado na app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Escolha um início de sessão guardado para a app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Iniciar sessão de outra forma"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Não"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opções de início de sessão"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestores de palavras-passe bloqueados"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Toque para desbloquear"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Faça a gestão dos inícios de sessão"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De outro dispositivo"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use um dispositivo diferente"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-pt/strings.xml b/packages/CredentialManager/res/values-pt/strings.xml
new file mode 100644
index 0000000..d950bb4
--- /dev/null
+++ b/packages/CredentialManager/res/values-pt/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Gerenciador de credenciais"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Criar em outro lugar"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Salvar em outro lugar"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Usar outro dispositivo"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Salvar em outro dispositivo"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Uma maneira simples de fazer login com segurança"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use a impressão digital, o reconhecimento facial ou um bloqueio de tela para fazer login com uma única chave de acesso que não pode ser esquecida ou perdida. Saiba mais"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Escolha onde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"salvar sua senha"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"salvar suas informações de login"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Criar uma chave de acesso em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Salvar sua senha em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Salvar suas informações de login em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Você pode usar seu <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> em qualquer dispositivo. Ele está salvo em <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> para <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
+    <string name="password" msgid="6738570945182936667">"senha"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"logins"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus logins?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Definir como padrão"</string>
+    <string name="use_once" msgid="9027366575315399714">"Usar uma vez"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> chaves de acesso"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Chave de acesso"</string>
+    <string name="another_device" msgid="5147276802037801217">"Outro dispositivo"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Outros gerenciadores de senha"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Fechar página"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Voltar à página anterior"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Usar sua chave de acesso salva para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Usar suas informações de login salvas para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Escolher um login salvo para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Fazer login de outra forma"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Agora não"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opções de login"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gerenciadores de senha bloqueados"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Toque para desbloquear"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gerenciar logins"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De outro dispositivo"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar um dispositivo diferente"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-ro/strings.xml b/packages/CredentialManager/res/values-ro/strings.xml
new file mode 100644
index 0000000..6947281
--- /dev/null
+++ b/packages/CredentialManager/res/values-ro/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Anulează"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Continuă"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Creează în alt loc"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Salvează în alt loc"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Folosește alt dispozitiv"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Salvează pe alt dispozitiv"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Un mod simplu de a te conecta în siguranță"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Folosește-ți amprenta, fața sau blocarea ecranului ca să te conectezi cu o cheie de acces unică, pe care nu o poți uita și care nu poate fi furată. Află mai multe"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Alege unde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"salvează parola"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"salvează informațiile de conectare"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Creezi o cheie de acces în <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Salvezi parola în <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Salvezi informațiile de conectare în <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Poți folosi <xliff:g id="TYPE">%2$s</xliff:g> <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> pe orice dispozitiv. Se salvează în <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> pentru <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"cheie de acces"</string>
+    <string name="password" msgid="6738570945182936667">"parolă"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"date de conectare"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Folosești <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pentru toate conectările?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Setează ca prestabilite"</string>
+    <string name="use_once" msgid="9027366575315399714">"Folosește o dată"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parole, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chei de acces"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parole"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> chei de acces"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Alt dispozitiv"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Alți manageri de parole"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Închide foaia"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Revino la pagina precedentă"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Folosești cheia de acces salvată pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Folosești datele de conectare salvate pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Alege o conectare salvată pentru <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Conectează-te altfel"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nu, mulțumesc"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuă"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opțiuni de conectare"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pentru <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Manageri de parole blocate"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Atinge pentru a debloca"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gestionează acreditările"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De pe alt dispozitiv"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Folosește alt dispozitiv"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-ru/strings.xml b/packages/CredentialManager/res/values-ru/strings.xml
new file mode 100644
index 0000000..dcc643e
--- /dev/null
+++ b/packages/CredentialManager/res/values-ru/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Менеджер учетных данных"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Отмена"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Продолжить"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Создать в другом месте"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Сохранить в другом месте"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Использовать другое устройство"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Сохранить на другом устройстве"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Простой и безопасный способ входа"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"С уникальным ключом доступа, который невозможно украсть или забыть, вы можете подтверждать свою личность по отпечатку пальца, с помощью фейсконтроля или блокировки экрана. Подробнее…"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Выберите, где <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"сохранить пароль"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"сохранить данные для входа"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Создать ключ доступа в приложении \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Сохранить пароль в приложении \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Сохранить учетные данные в приложении \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Вы можете использовать <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> на любом устройстве. Данные <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> сохраняются в приложении \"<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>\"."</string>
+    <string name="passkey" msgid="632353688396759522">"ключ доступа"</string>
+    <string name="password" msgid="6738570945182936667">"пароль"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"входы"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Всегда входить с помощью приложения \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Использовать по умолчанию"</string>
+    <string name="use_once" msgid="9027366575315399714">"Использовать один раз"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Пароли (<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>) и ключи доступа (<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>)"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"Пароли (<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>)"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Ключи доступа (<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>)"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Ключ доступа"</string>
+    <string name="another_device" msgid="5147276802037801217">"Другое устройство"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Другие менеджеры паролей"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Закрыть лист"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Вернуться на предыдущую страницу"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Использовать сохраненный ключ доступа для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Использовать сохраненные учетные данные для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Выберите сохраненные данные для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Войти другим способом"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Нет, спасибо"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Продолжить"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Варианты входа"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Для пользователя <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заблокированные менеджеры паролей"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Нажмите для разблокировки"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Управление входом"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"С другого устройства"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Использовать другое устройство"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-si/strings.xml b/packages/CredentialManager/res/values-si/strings.xml
new file mode 100644
index 0000000..bf885a9
--- /dev/null
+++ b/packages/CredentialManager/res/values-si/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"අවලංගු කරන්න"</string>
+    <string name="string_continue" msgid="1346732695941131882">"ඉදිරියට යන්න"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"වෙනත් ස්ථානයක තනන්න"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"වෙනත් ස්ථානයකට සුරකින්න"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"වෙනත් උපාංගයක් භාවිතා කරන්න"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"වෙනත් උපාංගයකට සුරකින්න"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"සුරක්ෂිතව පුරනය වීමට සරල ක්‍රමයක්"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"අමතක කළ නොහැකි හෝ සොරකම් කළ නොහැකි අනන්‍ය මුරයතුරක් සමග පුරනය වීමට ඔබේ ඇඟිලි සලකුණ, මුහුණ හෝ තිර අගුල භාවිතා කරන්න. තව දැන ගන්න⁠"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> කොතැනක ද යන්න තෝරා ගන්න"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"ඔබේ මුරපදය සුරකින්න"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"ඔබේ පුරනය වීමේ තතු සුරකින්න"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> තුළ මුරයතුරක් තනන්න ද?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"ඔබේ මුරපදය <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> වෙත සුරකින්න ද?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ඔබේ පුරනය වීමේ තතු <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> වෙත සුරකින්න ද?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"ඔබට ඕනෑම උපාංගයක ඔබේ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> භාවිතා කළ හැක. එය <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> සඳහා <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> වෙත සුරැකෙයි"</string>
+    <string name="passkey" msgid="632353688396759522">"මුරයතුර"</string>
+    <string name="password" msgid="6738570945182936667">"මුරපදය"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"පුරනය වීම්"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"ඔබේ සියලු පුරනය වීම් සඳහා <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> භාවිතා කරන්න ද?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"පෙරනිමි ලෙස සකසන්න"</string>
+    <string name="use_once" msgid="9027366575315399714">"වරක් භාවිතා කරන්න"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"මුරපද <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ක්, මුරයතුරු <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>ක්"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"මුරපද <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ක්"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"මුරයතුරු <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>ක්"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"වෙනත් උපාංගයක්"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"වෙනත් මුරපද කළමනාකරුවන්"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"පත්‍රය වසන්න"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"පෙර පිටුවට ආපසු යන්න"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා ඔබේ සුරැකි මුරයතුර භාවිතා කරන්න ද?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා ඔබේ සුරැකි පුරනය භාවිතා කරන්න ද?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා සුරැකි පුරනයක් තෝරා ගන්න"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"වෙනත් ආකාරයකින් පුරන්න"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"එපා ස්තුතියි"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ඉදිරියට යන්න"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"පුරනය වීමේ විකල්ප"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> සඳහා"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"අගුළු දැමූ මුරපද කළමනාකරුවන්"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"අගුළු හැරීමට තට්ටු කරන්න"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"පුරනය වීම් කළමනාකරණය කරන්න"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"වෙනත් උපාංගයකින්"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"වෙනස් උපාංගයක් භාවිතා කරන්න"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-sk/strings.xml b/packages/CredentialManager/res/values-sk/strings.xml
new file mode 100644
index 0000000..1c73c57
--- /dev/null
+++ b/packages/CredentialManager/res/values-sk/strings.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Správca prihlasovacích údajov"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Zrušiť"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Pokračovať"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Vytvoriť inde"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Uložiť inde"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Použiť iné zariadenie"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Uložiť do iného zariadenia"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednoduchý spôsob bezpečného prihlasovania"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Použite odtlačok prsta, tvár alebo zámku obrazovky a prihláste sa jedinečným prístupovým kľúčom, ktorý sa nedá zabudnúť ani ukradnúť. Ďalšie informácie"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Vyberte, kam <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"vytvoriť prístupové kľúče"</string>
+    <string name="save_your_password" msgid="6597736507991704307">"uložiť heslo"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"uložiť prihlasovacie údaje"</string>
+    <string name="choose_provider_body" msgid="8045759834416308059">"Nastavte predvoleného správcu hesiel, aby ukladal vaše heslá aj prístupové kľúče, a nabudúce sa prihláste rýchlejšie."</string>
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Chcete vytvoriť prístupový kľúč v službe <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Chcete uložiť heslo do služby <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Chcete uložiť svoje prihlasovacie údaje do služby <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> môžete používať v ľubovoľnom zariadení. Ukladá sa do služby <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> pre <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>."</string>
+    <string name="passkey" msgid="632353688396759522">"prístupový kľúč"</string>
+    <string name="password" msgid="6738570945182936667">"heslo"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"prihlasovacie údaje"</string>
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Vytvorenie prístupového kľúča v umiestnení"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Uloženie hesla do umiestnenia"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Uloženie prihlasovacích údajov do umiestnenia"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Chcete vytvoriť prístupový kľúč v inom zariadení?"</string>
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Chcete pre všetky svoje prihlasovacie údaje použiť <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Tento správca hesiel uchová vaše heslá a prístupové kľúče, aby vám pomohol ľahšie sa prihlasovať."</string>
+    <string name="set_as_default" msgid="4415328591568654603">"Nastaviť ako predvolené"</string>
+    <string name="use_once" msgid="9027366575315399714">"Použiť raz"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Počet hesiel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, počet prístupových kľúčov <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"Počet hesiel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Počet prístupových kľúčov: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Prístupový kľúč"</string>
+    <string name="another_device" msgid="5147276802037801217">"Iné zariadenie"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Iní správcovia hesiel"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Zavrieť hárok"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Prejsť späť na predchádzajúcu stránku"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Chcete pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g> použiť uložený prístupový kľúč?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Chcete pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g> použiť uložené prihlasovacie údaje?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Vyberte uložené prihlasovacie údaje pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prihláste sa inak"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nie, vďaka"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Pokračovať"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Možnosti prihlásenia"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pre používateľa <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Správcovia uzamknutých hesiel"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Odomknite klepnutím"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Spravovať prihlasovacie údaje"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Z iného zariadenia"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Použiť iné zariadenie"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-sl/strings.xml b/packages/CredentialManager/res/values-sl/strings.xml
new file mode 100644
index 0000000..969f290
--- /dev/null
+++ b/packages/CredentialManager/res/values-sl/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Upravitelj poverilnic"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Prekliči"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Naprej"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Ustvarjanje na drugem mestu"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Shranjevanje na drugo mesto"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Uporabi drugo napravo"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Shrani v drugo napravo"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Preprost način za varno prijavo"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Če se želite prijaviti z enoličnim ključem za dostop, ki ga ni mogoče pozabiti ali ukrasti, uporabite prstni odtis, obraz ali nastavljeni način za odklepanje zaslona. Več o tem"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Izberite mesto za <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"shranjevanje gesla"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"shranjevanje podatkov za prijavo"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Želite ustvariti ključ za dostop pod »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Želite shraniti geslo pod »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Želite shraniti podatke za prijavo pod »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="TYPE">%2$s</xliff:g> za <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> lahko uporabite v kateri koli napravi. Shranjeno je pod »<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>« za <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>."</string>
+    <string name="passkey" msgid="632353688396759522">"ključ za dostop"</string>
+    <string name="password" msgid="6738570945182936667">"geslo"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"prijave"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite za vse prijave uporabiti »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Nastavi kot privzeto"</string>
+    <string name="use_once" msgid="9027366575315399714">"Uporabi enkrat"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Št. gesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, št. ključev za dostop: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"Št. gesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Št. ključev za dostop: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Ključ za dostop"</string>
+    <string name="another_device" msgid="5147276802037801217">"Druga naprava"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Drugi upravitelji gesel"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Zapri list"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Nazaj na prejšnjo stran"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Želite uporabiti shranjeni ključ za dostop do aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Želite uporabiti shranjene podatke za prijavo v aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Izberite shranjene podatke za prijavo v aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prijava na drug način"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ne, hvala"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Naprej"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Možnosti prijave"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Za uporabnika <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Zaklenjeni upravitelji gesel"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Dotaknite se, da odklenete"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Upravljanje podatkov za prijavo"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Iz druge naprave"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Uporaba druge naprave"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-sq/strings.xml b/packages/CredentialManager/res/values-sq/strings.xml
new file mode 100644
index 0000000..bce0683
--- /dev/null
+++ b/packages/CredentialManager/res/values-sq/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Anulo"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Vazhdo"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Krijo në një vend tjetër"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Ruaj në një vend tjetër"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Përdor një pajisje tjetër"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Ruaj në një pajisje tjetër"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Një mënyrë e thjeshtë për t\'u identifikuar në mënyrë të sigurt"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Përdor gjurmën e gishtit, fytyrën ose kyçjen e ekranit për t\'u identifikuar me një çelës unik kalimi i cili nuk mund të harrohet ose të vidhet. Mëso më shumë"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Zgjidh se ku të <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"ruaj fjalëkalimin"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"ruaj informacionet e tua të identifikimit"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Të krijohet një çelës kalimi në <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Të ruhet fjalëkalimi në <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Të ruhen informacionet e tua të identifikimit në <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Mund të përdorësh <xliff:g id="TYPE">%2$s</xliff:g> të <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> në çdo pajisje. Ai është i ruajtur te <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> për <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"çelësi i kalimit"</string>
+    <string name="password" msgid="6738570945182936667">"fjalëkalimi"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"identifikimet"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Të përdoret <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> për të gjitha identifikimet?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Cakto si parazgjedhje"</string>
+    <string name="use_once" msgid="9027366575315399714">"Përdor një herë"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> fjalëkalime, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> çelësa kalimi"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> fjalëkalime"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> çelësa kalimi"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Një pajisje tjetër"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Menaxherët e tjerë të fjalëkalimeve"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Mbyll fletën"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Kthehu te faqja e mëparshme"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Të përdoret fjalëkalimi yt i ruajtur për <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Të përdoret identifikimi yt i ruajtur për <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Zgjidh një identifikim të ruajtur për <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Identifikohu me një mënyrë tjetër"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Jo, faleminderit"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Vazhdo"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opsionet e identifikimit"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Për <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Menaxherët e fjalëkalimeve të kyçura"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Trokit për të shkyçur"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Identifikimet e menaxhimit"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Nga një pajisje tjetër"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Përdor një pajisje tjetër"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-sr/strings.xml b/packages/CredentialManager/res/values-sr/strings.xml
new file mode 100644
index 0000000..6a5235c
--- /dev/null
+++ b/packages/CredentialManager/res/values-sr/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Откажи"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Настави"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Направи на другом месту"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Сачувај на другом месту"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Користи други уређај"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Сачувај на други уређај"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Једноставан начин да се безбедно пријављујете"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Користите отисак прста, закључавање лицем или закључавање екрана да бисте се пријавили помоћу јединственог приступног кода који не може да се заборави или украде. Сазнајте више"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Одаберите локацију за: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"сачувајте лозинку"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"сачувајте податке о пријављивању"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Желите да направите приступни кôд код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Желите да сачувате лозинку код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Желите да сачувате податке о пријављивању код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Можете да користите тип домена <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> на било ком уређају. Чува се код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> за: <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"приступни кôд"</string>
+    <string name="password" msgid="6738570945182936667">"лозинка"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"пријављивања"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Желите да за сва пријављивања користите: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Подеси као подразумевано"</string>
+    <string name="use_once" msgid="9027366575315399714">"Користи једном"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Лозинки: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, приступних кодова:<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"Лозинки: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Приступних кодова: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Други уређај"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Други менаџери лозинки"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Затворите табелу"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Вратите се на претходну страницу"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Желите да користите сачувани приступни кôд за: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Желите да користите сачуване податке за пријављивање за: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Одаберите сачувано пријављивање за: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Пријавите се на други начин"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Не, хвала"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Настави"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Опције за пријављивање"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"За: <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Менаџери закључаних лозинки"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Додирните да бисте откључали"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Управљајте пријављивањима"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Са другог уређаја"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Користи други уређај"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-sv/strings.xml b/packages/CredentialManager/res/values-sv/strings.xml
new file mode 100644
index 0000000..a4fffb9
--- /dev/null
+++ b/packages/CredentialManager/res/values-sv/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Avbryt"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Fortsätt"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Skapa på en annan plats"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Spara på en annan plats"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Använd en annan enhet"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Spara på en annan enhet"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Ett enkelt sätt att logga in säkert på"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Använd ditt fingeravtryck, ansikte eller skärmlås om du vill logga in med en unik nyckel som inte kan glömmas bort eller bli stulen. Läs mer"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Välj var du <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"spara lösenordet"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"spara inloggningsuppgifterna"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vill du skapa en nyckel i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vill du spara ditt lösenord i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vill du spara dina inloggningsuppgifter i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Du kan använda <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> på alla enheter. Du kan spara det i <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> för <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"nyckel"</string>
+    <string name="password" msgid="6738570945182936667">"lösenord"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"inloggningar"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vill du använda <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> för alla dina inloggningar?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Ange som standard"</string>
+    <string name="use_once" msgid="9027366575315399714">"Använd en gång"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> lösenord, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> nycklar"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> lösenord"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> nycklar"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Nyckel"</string>
+    <string name="another_device" msgid="5147276802037801217">"En annan enhet"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Andra lösenordshanterare"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Stäng kalkylarket"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gå tillbaka till föregående sida"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vill du använda din sparade nyckel för <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vill du använda dina sparade inloggningsuppgifter för <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Välj en sparad inloggning för <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Logga in på ett annat sätt"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nej tack"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Fortsätt"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Inloggningsalternativ"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"För <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Låsta lösenordshanterare"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tryck för att låsa upp"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Hantera inloggningar"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Via en annan enhet"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Använd en annan enhet"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-sw/strings.xml b/packages/CredentialManager/res/values-sw/strings.xml
new file mode 100644
index 0000000..bfd1074
--- /dev/null
+++ b/packages/CredentialManager/res/values-sw/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Ghairi"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Endelea"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Unda katika sehemu nyingine"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Hifadhi sehemu nyingine"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Tumia kifaa kingine"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Hifadhi kwenye kifaa kingine"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Njia rahisi ya kuingia katika akaunti kwa usalama"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Tumia alama ya vidole, uso au kipengele cha kufunga skrini ili uingie katika kaunti kwa kutumia nenosiri la kipekee ambalo haliwezi kusahaulika au kuibiwa. Pata maelezo zaidi"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Chagua mahali pa <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"hifadhi nenosiri lako"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"hifadhi maelezo yako ya kuingia katika akaunti"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Ungependa kuunda ufunguo wa siri katika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Ungependa kuhifadhi nenosiri lako kwenye <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Ungependa kuhifadhi maelezo yako ya kuingia katika akaunti kwenye <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Unaweza kutumia <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> yako ya <xliff:g id="TYPE">%2$s</xliff:g> kwenye kifaa chochote. Imehifadhiwa kwenye <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> kwa ajili ya <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"ufunguo wa siri"</string>
+    <string name="password" msgid="6738570945182936667">"nenosiri"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"michakato ya kuingia katika akaunti"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Ungependa kutumia <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kwa ajili ya michakato yako yote ya kuingia katika akaunti?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Weka iwe chaguomsingi"</string>
+    <string name="use_once" msgid="9027366575315399714">"Tumia mara moja"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Manenosiri <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, funguo <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> za siri"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"Manenosiri <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Funguo <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> za siri"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Kifaa kingine"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Vidhibiti vinginevyo vya manenosiri"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Funga laha"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Rudi kwenye ukurasa uliotangulia"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Ungependa kutumia ufunguo wa siri uliohifadhiwa wa<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Ungependa kutumia kitambulisho kilichohifadhiwa cha kuingia katika akaunti ya <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Chagua vitambulisho vilivyohifadhiwa kwa ajili ya kuingia katika akaunti ya <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Ingia katika akaunti kwa kutumia njia nyingine"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Hapana"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Endelea"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Chaguo za kuingia katika akaunti"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Kwa ajili ya <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Vidhibiti vya manenosiri vilivyofungwa"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Gusa ili ufungue"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Dhibiti michakato ya kuingia katika akaunti"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Kutoka kwenye kifaa kingine"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Tumia kifaa tofauti"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-ta/strings.xml b/packages/CredentialManager/res/values-ta/strings.xml
new file mode 100644
index 0000000..10c5259
--- /dev/null
+++ b/packages/CredentialManager/res/values-ta/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"ரத்துசெய்"</string>
+    <string name="string_continue" msgid="1346732695941131882">"தொடர்க"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"மற்றொரு இடத்தில் உருவாக்கவும்"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"மற்றொரு இடத்தில் சேமிக்கவும்"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"மற்றொரு சாதனத்தைப் பயன்படுத்தவும்"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"வேறொரு சாதனத்தில் சேமியுங்கள்"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"பாதுகாப்பாக உள்நுழைவதற்கான எளிய வழி"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"தனித்துவமான கடவுக்குறியீடு (மறக்காதவை அல்லது திருடமுடியாதவை) மூலம் உள்நுழைய, உங்கள் கைரேகை, முகம் அல்லது திரைப்பூட்டைப் பயன்படுத்தி உள்நுழையவும். மேலும் அறிக"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> எங்கே காட்டப்பட வேண்டும் என்பதைத் தேர்வுசெய்தல்"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"உங்கள் கடவுச்சொல்லைச் சேமிக்கவும்"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"உங்கள் உள்நுழைவு தகவலைச் சேமிக்கவும்"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ல் கடவுக்குறியீட்டை உருவாக்க வேண்டுமா?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"உங்கள் கடவுச்சொல்லை <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ல் சேமிக்க வேண்டுமா?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"உங்கள் உள்நுழைவுத் தகவலை <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ல் சேமிக்க வேண்டுமா?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"உங்கள் <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ஐ எந்தச் சாதனத்தில் வேண்டுமானாலும் பயன்படுத்தலாம். <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>ல் <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>க்காகச் சேமிக்கப்பட்டது"</string>
+    <string name="passkey" msgid="632353688396759522">"கடவுக்குறியீடு"</string>
+    <string name="password" msgid="6738570945182936667">"கடவுச்சொல்"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"உள்நுழைவுகள்"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"உங்கள் அனைத்து உள்நுழைவுகளுக்கும் <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ஐப் பயன்படுத்தவா?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"இயல்பானதாக அமை"</string>
+    <string name="use_once" msgid="9027366575315399714">"ஒருமுறை பயன்படுத்தவும்"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> கடவுச்சொற்கள், <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> கடவுக்குறியீடுகள்"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> கடவுச்சொற்கள்"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> கடவுக்குறியீடுகள்"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"மற்றொரு சாதனம்"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"பிற கடவுச்சொல் நிர்வாகிகள்"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"ஷீட்டை மூடும்"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"முந்தைய பக்கத்திற்குச் செல்லும்"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கு ஏற்கெனவே சேமிக்கப்பட்ட கடவுக்குறியீட்டைப் பயன்படுத்தவா?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கு ஏற்கெனவே சேமிக்கப்பட்ட உள்நுழைவுத் தகவலைப் பயன்படுத்தவா?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கு ஏற்கெனவே சேமிக்கப்பட்ட உள்நுழைவுத் தகவலைத் தேர்வுசெய்யவும்"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"வேறு முறையில் உள்நுழைக"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"வேண்டாம்"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"தொடர்க"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"உள்நுழைவு விருப்பங்கள்"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>க்கு"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"பூட்டப்பட்ட கடவுச்சொல் நிர்வாகிகள்"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"அன்லாக் செய்ய தட்டவும்"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"உள்நுழைவுகளை நிர்வகித்தல்"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"மற்றொரு சாதனத்திலிருந்து பயன்படுத்து"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"வேறு சாதனத்தைப் பயன்படுத்து"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-te/strings.xml b/packages/CredentialManager/res/values-te/strings.xml
new file mode 100644
index 0000000..f7617b3
--- /dev/null
+++ b/packages/CredentialManager/res/values-te/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"రద్దు చేయండి"</string>
+    <string name="string_continue" msgid="1346732695941131882">"కొనసాగించండి"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"మరొక స్థలంలో క్రియేట్ చేయండి"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"మరొక స్థలంలో సేవ్ చేయండి"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"మరొక పరికరాన్ని ఉపయోగించండి"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"మరొక పరికరంలో సేవ్ చేయండి"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"సురక్షితంగా సైన్ ఇన్ చేయడానికి సులభమైన మార్గం"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"మర్చిపోలేని లేదా దొంగిలించలేని ప్రత్యేకమైన పాస్-కీతో సైన్ ఇన్ చేయడానికి మీ వేలిముద్ర, ముఖం లేదా స్క్రీన్ లాక్‌ను ఉపయోగించండి. మరింత తెలుసుకోండి"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"ఎక్కడ <xliff:g id="CREATETYPES">%1$s</xliff:g> చేయాలో ఎంచుకోండి"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"మీ పాస్‌వర్డ్‌ను సేవ్ చేయండి"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"మీ సైన్ ఇన్ సమాచారాన్ని సేవ్ చేయండి"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>లో పాస్-కీని క్రియేట్ చేయాలా?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"మీ పాస్‌వర్డ్‌ను <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>కు సేవ్ చేయాలా?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"మీ సైన్ ఇన్ సమాచారాన్ని <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>కు సేవ్ చేయాలా?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"మీరు మీ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g>ని ఏ పరికరంలోనైనా ఉపయోగించవచ్చు. ఇది <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> కోసం <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>లో సేవ్ చేయబడింది"</string>
+    <string name="passkey" msgid="632353688396759522">"పాస్-కీ"</string>
+    <string name="password" msgid="6738570945182936667">"పాస్‌వర్డ్"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"సైన్‌ ఇన్‌లు"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"మీ అన్ని సైన్-ఇన్ వివరాల కోసం <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ను ఉపయోగించాలా?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"ఆటోమేటిక్ సెట్టింగ్‌గా సెట్ చేయండి"</string>
+    <string name="use_once" msgid="9027366575315399714">"ఒకసారి ఉపయోగించండి"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> పాస్‌వర్డ్‌లు, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> పాస్-కీలు"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> పాస్‌వర్డ్‌లు"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> పాస్-కీలు"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"మరొక పరికరం"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"ఇతర పాస్‌వర్డ్ మేనేజర్‌లు"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"షీట్‌ను మూసివేయండి"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"మునుపటి పేజీకి తిరిగి వెళ్లండి"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం మీ సేవ్ చేసిన పాస్-కీ వివరాలను ఉపయోగించాలా?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం మీరు సేవ్ చేసిన సైన్ ఇన్ వివరాలను ఉపయోగించాలా?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం సేవ్ చేసిన సైన్ ఇన్ వివరాలను ఎంచుకోండి"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"మరొక పద్ధతిలో సైన్ ఇన్ చేయండి"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"వద్దు, థ్యాంక్స్"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"కొనసాగించండి"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"సైన్ ఇన్ ఆప్షన్‌లు"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> కోసం"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"లాక్ చేయబడిన పాస్‌వర్డ్ మేనేజర్‌లు"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"అన్‌లాక్ చేయడానికి ట్యాప్ చేయండి"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"సైన్‌ ఇన్‌లను మేనేజ్ చేయండి"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"మరొక పరికరం నుండి"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"వేరే పరికరాన్ని ఉపయోగించండి"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-th/strings.xml b/packages/CredentialManager/res/values-th/strings.xml
new file mode 100644
index 0000000..d70e94a
--- /dev/null
+++ b/packages/CredentialManager/res/values-th/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"เครื่องมือจัดการข้อมูลเข้าสู่ระบบ"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"ยกเลิก"</string>
+    <string name="string_continue" msgid="1346732695941131882">"ต่อไป"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"สร้างในตำแหน่งอื่น"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"บันทึกลงในตำแหน่งอื่น"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"ใช้อุปกรณ์อื่น"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"ย้ายไปยังอุปกรณ์อื่น"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"วิธีง่ายๆ ในการลงชื่อเข้าใช้อย่างปลอดภัย"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ใช้ลายนิ้วมือ ใบหน้า หรือล็อกหน้าจอในการลงชื่อเข้าใช้ด้วยพาสคีย์ที่ไม่ซ้ำกันเพื่อไม่ให้ลืมหรือถูกขโมยได้ ดูข้อมูลเพิ่มเติม"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"เลือกตำแหน่งที่จะ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"บันทึกรหัสผ่าน"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"บันทึกข้อมูลการลงชื่อเข้าใช้"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"สร้างพาสคีย์ใน <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ใช่ไหม"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"บันทึกรหัสผ่านลงใน <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ใช่ไหม"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"บันทึกข้อมูลการลงชื่อเข้าใช้ลงใน <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ใช่ไหม"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"คุณใช้ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ในอุปกรณ์ใดก็ได้ โดยจะบันทึกลงใน <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> สำหรับ <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"พาสคีย์"</string>
+    <string name="password" msgid="6738570945182936667">"รหัสผ่าน"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"การลงชื่อเข้าใช้"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"ใช้ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> สำหรับการลงชื่อเข้าใช้ทั้งหมดใช่ไหม"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"ตั้งเป็นค่าเริ่มต้น"</string>
+    <string name="use_once" msgid="9027366575315399714">"ใช้ครั้งเดียว"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"รหัสผ่าน <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> รายการ พาสคีย์ <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> รายการ"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"รหัสผ่าน <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> รายการ"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"พาสคีย์ <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> รายการ"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"พาสคีย์"</string>
+    <string name="another_device" msgid="5147276802037801217">"อุปกรณ์อื่น"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"เครื่องมือจัดการรหัสผ่านอื่นๆ"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"ปิดชีต"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"กลับไปยังหน้าก่อนหน้า"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ใช้พาสคีย์ที่บันทึกไว้สำหรับ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" ใช่ไหม"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ใช้การลงชื่อเข้าใช้ที่บันทึกไว้สำหรับ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" ใช่ไหม"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"เลือกการลงชื่อเข้าใช้ที่บันทึกไว้สำหรับ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ลงชื่อเข้าใช้ด้วยวิธีอื่น"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"ไม่เป็นไร"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ต่อไป"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ตัวเลือกการลงชื่อเข้าใช้"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"สำหรับ <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"เครื่องมือจัดการรหัสผ่านที่ล็อกไว้"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"แตะเพื่อปลดล็อก"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"จัดการการลงชื่อเข้าใช้"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"จากอุปกรณ์อื่น"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ใช้อุปกรณ์อื่น"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-tl/strings.xml b/packages/CredentialManager/res/values-tl/strings.xml
new file mode 100644
index 0000000..01fd2f0
--- /dev/null
+++ b/packages/CredentialManager/res/values-tl/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"Manager ng Kredensyal"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"Kanselahin"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Magpatuloy"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Gumawa sa ibang lugar"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"I-save sa ibang lugar"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Gumamit ng ibang device"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"I-save sa ibang device"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Simpleng paraan para mag-sign in lang ligtas"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gamitin ang iyong fingerprint, mukha, o lock ng screen para mag-sign in gamit ang natatanging passkey na hindi makakalimutan o mananakaw. Matuto pa"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Piliin kung saan <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"i-save ang iyong password"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"i-save ang iyong impormasyon sa pag-sign in"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Gumawa ng passkey sa <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"I-save ang iyong password sa <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"I-save ang iyong impormasyon sa pag-sign in sa <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Magagamit mo ang iyong <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> sa anumang device. Naka-save ito sa <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> para sa <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"passkey"</string>
+    <string name="password" msgid="6738570945182936667">"password"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"mga sign-in"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gamitin ang <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para sa lahat ng iyong pag-sign in?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Itakda bilang default"</string>
+    <string name="use_once" msgid="9027366575315399714">"Gamitin nang isang beses"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> (na) password, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> (na) passkey"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> (na) password"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> (na) passkey"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
+    <string name="another_device" msgid="5147276802037801217">"Ibang device"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Iba pang password manager"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Isara ang sheet"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Bumalik sa nakaraang page"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gamitin ang iyong naka-save na passkey para sa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gamitin ang iyong naka-save na sign-in para sa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pumili ng naka-save na sign-in para sa <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Mag-sign in sa ibang paraan"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Hindi, salamat na lang"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Magpatuloy"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Mga opsyon sa pag-sign in"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para kay <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Mga naka-lock na password manager"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"I-tap para i-unlock"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Pamahalaan ang mga sign-in"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Mula sa ibang device"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gumamit ng ibang device"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml
new file mode 100644
index 0000000..30ed43e
--- /dev/null
+++ b/packages/CredentialManager/res/values-tr/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"İptal"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Devam"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Başka bir yerde oluşturun"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Başka bir yere kaydedin"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Başka bir cihaz kullan"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Başka bir cihaza kaydet"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Güvenli bir şekilde oturum açmanın basit yolu"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Parmak iziniz, yüzünüz ya da ekran kilidinizi kullanarak unutması veya çalınması mümkün olmayan benzersiz bir şifre anahtarıyla oturum açın. Daha fazla bilgi"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> yerini seçin"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"şifrenizi kaydedin"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"oturum açma bilgilerinizi kaydedin"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> içinde şifre anahtarı oluşturulsun mu?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Şifreniz <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> içine kaydedilsin mi?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Oturum açma bilgileriniz <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> içine kaydedilsin mi?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> herhangi bir cihazda kullanılabilir. <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> için <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> içine kaydedilir"</string>
+    <string name="passkey" msgid="632353688396759522">"şifre anahtarı"</string>
+    <string name="password" msgid="6738570945182936667">"şifre"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"oturum aç"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Tüm oturum açma işlemlerinizde <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kullanılsın mı?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Varsayılan olarak ayarla"</string>
+    <string name="use_once" msgid="9027366575315399714">"Bir kez kullanın"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> şifre, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> şifre anahtarı"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> şifre"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> şifre anahtarı"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Başka bir cihaz"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Diğer şifre yöneticileri"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Sayfayı kapat"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Önceki sayfaya geri dön"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı şifre anahtarınız kullanılsın mı?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı oturum açma bilgileriniz kullanılsın mı?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı oturum açma bilgilerini kullanın"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Başka bir yöntemle oturum aç"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Hayır, teşekkürler"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Devam"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Oturum açma seçenekleri"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> için"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Kilitli şifre yöneticileri"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Kilidi açmak için dokunun"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Oturum açma bilgilerini yönetin"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Başka bir cihazdan"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Farklı bir cihaz kullan"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-uk/strings.xml b/packages/CredentialManager/res/values-uk/strings.xml
new file mode 100644
index 0000000..69d4612
--- /dev/null
+++ b/packages/CredentialManager/res/values-uk/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Скасувати"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Продовжити"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Створити в іншому місці"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Зберегти в іншому місці"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Скористатись іншим пристроєм"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Зберегти на іншому пристрої"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Зручний спосіб для безпечного входу"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Користуйтеся відбитком пальця, фейсконтролем або іншим способом розблокування екрана, щоб входити в обліковий запис за допомогою унікального ключа доступу, який неможливо забути чи викрасти. Докладніше"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Виберіть, де <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"зберегти пароль"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"зберегти дані для входу"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Створити ключ доступу в сервісі <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Зберегти ваш пароль у сервісі <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Зберегти ваші дані для входу в сервіс <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Ви можете використовувати <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> на будь-якому пристрої. Його збережено в постачальника послуг \"<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>\" для <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"ключ доступу"</string>
+    <string name="password" msgid="6738570945182936667">"пароль"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"дані для входу"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Використовувати сервіс <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> в усіх випадках входу?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Вибрати за умовчанням"</string>
+    <string name="use_once" msgid="9027366575315399714">"Скористатися раз"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Кількість паролів: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>; кількість ключів доступу: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"Кількість паролів: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Кількість ключів доступу: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Інший пристрій"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Інші менеджери паролів"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Закрити аркуш"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Повернутися на попередню сторінку"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Використати збережений ключ доступу для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Використати збережені дані для входу для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Виберіть збережені дані для входу в додаток <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Увійти іншим способом"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ні, дякую"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Продовжити"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Опції входу"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Для користувача <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заблоковані менеджери паролів"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Торкніться, щоб розблокувати"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Керування даними для входу"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"З іншого пристрою"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Використовувати інший пристрій"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-ur/strings.xml b/packages/CredentialManager/res/values-ur/strings.xml
new file mode 100644
index 0000000..2d66079
--- /dev/null
+++ b/packages/CredentialManager/res/values-ur/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"سند سے متعلق مینیجر"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"منسوخ کریں"</string>
+    <string name="string_continue" msgid="1346732695941131882">"جاری رکھیں"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"دوسرے مقام میں تخلیق کریں"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"دوسرے مقام میں محفوظ کریں"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"کوئی دوسرا آلہ استعمال کریں"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"دوسرے آلے میں محفوظ کریں"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"محفوظ طریقے سے سائن ان کرنے کا آسان طریقہ"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"اپنے فنگر پرنٹ، چہرے یا اسکرین لاک کا استعمال کریں تاکہ ایک ایسی منفرد پاس کی سے سائن ان کیا جا سکے جسے بھولا یا چوری نہیں کیا جا سکتا۔ مزید جانیں"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> کی جگہ منتخب کریں"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"اپنا پاس ورڈ محفوظ کریں"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"اپنے سائن ان کی معلومات محفوظ کریں"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> میں پاس کی تخلیق کریں؟"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"اپنا پاس ورڈ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> میں محفوظ کریں؟"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"اپنے سائن ان کی معلومات کو <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> میں محفوظ کریں؟"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"آپ کسی بھی آلے پر اپنا <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> استعمال کر سکتے ہیں۔ یہ <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> کے <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> میں محفوظ ہو جاتا ہے"</string>
+    <string name="passkey" msgid="632353688396759522">"پاس کی"</string>
+    <string name="password" msgid="6738570945182936667">"پاس ورڈ"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"سائن انز"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"اپنے سبھی سائن انز کے لیے <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> کا استعمال کریں؟"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"بطور ڈیفالٹ سیٹ کریں"</string>
+    <string name="use_once" msgid="9027366575315399714">"ایک بار استعمال کریں"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> پاس ورڈز، <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> پاس کیز"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> پاس ورڈز"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> پاس کیز"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"پاس کی"</string>
+    <string name="another_device" msgid="5147276802037801217">"دوسرا آلہ"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"دیگر پاس ورڈ مینیجرز"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"شیٹ بند کریں"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"گزشتہ صفحے پر واپس جائیں"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے اپنی محفوظ کردہ پاس کی استعمال کریں؟"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے اپنے محفوظ کردہ سائن ان کو استعمال کریں؟"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے محفوظ کردہ سائن انز منتخب کریں"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"دوسرے طریقے سے سائن ان کریں"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"نہیں شکریہ"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"جاری رکھیں"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"سائن ان کے اختیارات"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> کے لیے"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"مقفل کردہ پاس ورڈ مینیجرز"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"غیر مقفل کرنے کے لیے تھپتھپائیں"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"سائن انز کا نظم کریں"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"دوسرے آلے سے"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ایک مختلف آلہ استعمال کریں"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-uz/strings.xml b/packages/CredentialManager/res/values-uz/strings.xml
new file mode 100644
index 0000000..4ac35b2
--- /dev/null
+++ b/packages/CredentialManager/res/values-uz/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Bekor qilish"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Davom etish"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Boshqa joyda yaratish"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Boshqa joyga saqlash"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Boshqa qurilmadan foydalaning"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Boshqa qurilmaga saqlash"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Xavfsiz kirishning oddiy usuli"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Esda qoladigan maxsus kalit bilan kirishda barmoq izi, yuz axboroti yoki ekran qulfidan foydalaning. Batafsil"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> joyini tanlang"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"Parolni saqlash"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"kirish axborotini saqlang"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Kalit <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xizmatida yaratilsinmi?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Parol <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xizmatida saqlansinmi?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Hisob maʼlumotlari <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xizmatida saqlansinmi?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> istalgan qurilmada ishlatilishi mumkin. U <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> uchun <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> xizmatiga saqlandi"</string>
+    <string name="passkey" msgid="632353688396759522">"kalit"</string>
+    <string name="password" msgid="6738570945182936667">"parol"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"kirishlar"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Hamma kirishlarda <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ishlatilsinmi?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Birlamchi deb belgilash"</string>
+    <string name="use_once" msgid="9027366575315399714">"Bir marta ishlatish"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ta parol, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ta kalit"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ta parol"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ta kalit"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Boshqa qurilma"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Boshqa parol menejerlari"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Varaqni yopish"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Avvalgi sahifaga qaytish"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun saqlangan kalit ishlatilsinmi?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun saqlangan maʼlumotlar ishlatilsinmi?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> hisob maʼlumotlarini tanlang"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Boshqa usul orqali kirish"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Kerak emas"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Davom etish"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Kirish parametrlari"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> uchun"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Qulfli parol menejerlari"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Qulfni ochish uchun bosing"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Hisob maʼlumotlarini boshqarish"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Boshqa qurilmada"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Boshqa qurilmadan foydalanish"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-vi/strings.xml b/packages/CredentialManager/res/values-vi/strings.xml
new file mode 100644
index 0000000..fd5b986
--- /dev/null
+++ b/packages/CredentialManager/res/values-vi/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Huỷ"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Tiếp tục"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Tạo ở vị trí khác"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Lưu vào vị trí khác"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Dùng thiết bị khác"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Lưu vào thiết bị khác"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Cách đơn giản để đăng nhập an toàn"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Dùng vân tay, khuôn mặt hoặc phương thức khoá màn hình để đăng nhập bằng một mã xác thực duy nhất mà bạn không lo sẽ quên hay bị đánh cắp. Tìm hiểu thêm"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Chọn vị trí <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"lưu mật khẩu của bạn"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"lưu thông tin đăng nhập của bạn"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Tạo một mã xác thực trong <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Lưu mật khẩu của bạn vào <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Lưu thông tin đăng nhập của bạn vào <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Bạn có thể sử dụng <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> trên mọi thiết bị. Thông tin này được lưu vào <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> cho <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"mã xác thực"</string>
+    <string name="password" msgid="6738570945182936667">"mật khẩu"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"thông tin đăng nhập"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Dùng <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> cho mọi thông tin đăng nhập của bạn?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Đặt làm mặc định"</string>
+    <string name="use_once" msgid="9027366575315399714">"Dùng một lần"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mật khẩu, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> mã xác thực"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mật khẩu"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> mã xác thực"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Thiết bị khác"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Trình quản lý mật khẩu khác"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Đóng trang tính"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Quay lại trang trước"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Dùng mã xác thực bạn đã lưu cho <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Dùng thông tin đăng nhập bạn đã lưu cho <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Chọn thông tin đăng nhập đã lưu cho <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Đăng nhập bằng cách khác"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Không, cảm ơn"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Tiếp tục"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Tuỳ chọn đăng nhập"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Cho <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Trình quản lý mật khẩu đã khoá"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Nhấn để mở khoá"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Quản lý thông tin đăng nhập"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Từ một thiết bị khác"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Dùng thiết bị khác"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..a14dd2f
--- /dev/null
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"取消"</string>
+    <string name="string_continue" msgid="1346732695941131882">"继续"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"在另一位置创建"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"保存到另一位置"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"使用另一台设备"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"保存到其他设备"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"简单又安全的登录方式"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"借助指纹、人脸识别或屏幕锁定功能,使用不会被忘记或被盗且具有唯一性的通行密钥登录。了解详情"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"选择<xliff:g id="CREATETYPES">%1$s</xliff:g>的位置"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"保存您的密码"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"保存您的登录信息"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"在“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”中创建通行密钥?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"将您的密码保存至“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"将您的登录信息保存至“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"您可以在任意设备上使用 <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g>。它会保存到“<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>”的<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"通行密钥"</string>
+    <string name="password" msgid="6738570945182936667">"密码"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"登录"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"将“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”用于您的所有登录信息?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"设为默认项"</string>
+    <string name="use_once" msgid="9027366575315399714">"使用一次"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 个密码,<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 个通行密钥"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 个密码"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 个通行密钥"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"另一台设备"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"其他密码管理工具"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"关闭工作表"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"返回上一页"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"将您已保存的通行密钥用于<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"将您已保存的登录信息用于<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"为<xliff:g id="APP_NAME">%1$s</xliff:g>选择已保存的登录信息"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"以另一种方式登录"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"不用了"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"继续"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"登录选项"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"用户:<xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"已锁定的密码管理工具"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"点按即可解锁"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"管理登录信息"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"通过另一台设备"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"使用其他设备"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-zh-rHK/strings.xml b/packages/CredentialManager/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..71dfa1a
--- /dev/null
+++ b/packages/CredentialManager/res/values-zh-rHK/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"憑證管理工具"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"取消"</string>
+    <string name="string_continue" msgid="1346732695941131882">"繼續"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"在其他位置建立"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"儲存至其他位置"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"改用其他裝置"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"儲存至其他裝置"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"安全又簡便的登入方式"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"使用指紋、面孔或螢幕鎖定配合密鑰登入。密鑰獨一無二,您不用擔心忘記密鑰或密鑰被盜。瞭解詳情"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"選擇「<xliff:g id="CREATETYPES">%1$s</xliff:g>」的位置"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"儲存密碼"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"儲存登入資料"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"要在「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」建立密鑰嗎?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"要將密碼儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」嗎?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"要將登入資料儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」嗎?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"您可以在任何裝置上使用「<xliff:g id="APPDOMAINNAME">%1$s</xliff:g>」<xliff:g id="TYPE">%2$s</xliff:g>。系統會將此資料儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>」,供「<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>」使用"</string>
+    <string name="passkey" msgid="632353688396759522">"密鑰"</string>
+    <string name="password" msgid="6738570945182936667">"密碼"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"登入資料"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"要將「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」用於所有的登入資料嗎?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"設定為預設"</string>
+    <string name="use_once" msgid="9027366575315399714">"單次使用"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼,<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 個密鑰"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 個密鑰"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"密碼金鑰"</string>
+    <string name="another_device" msgid="5147276802037801217">"其他裝置"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"其他密碼管理工具"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"閂工作表"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"返回上一頁"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密鑰嗎?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資料嗎?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資料"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"使用其他方式登入"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"不用了,謝謝"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"繼續"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"登入選項"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> 專用"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"已鎖定的密碼管理工具"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"輕按即可解鎖"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"管理登入資料"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"透過其他裝置"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"使用其他裝置"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-zh-rTW/strings.xml b/packages/CredentialManager/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..0d636c2
--- /dev/null
+++ b/packages/CredentialManager/res/values-zh-rTW/strings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4539824758261855508">"憑證管理工具"</string>
+    <string name="string_cancel" msgid="6369133483981306063">"取消"</string>
+    <string name="string_continue" msgid="1346732695941131882">"繼續"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"在其他位置建立"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"儲存至其他位置"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"改用其他裝置"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"儲存至其他裝置"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"安全又簡單的登入方式"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"登入帳戶時,你可以使用指紋、人臉或螢幕鎖定功能搭配不重複的密碼金鑰,不必擔心忘記密碼金鑰或遭人竊取。瞭解詳情"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"選擇「<xliff:g id="CREATETYPES">%1$s</xliff:g>」的位置"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"儲存密碼"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"儲存登入資訊"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"要在「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」建立密碼金鑰嗎?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"要將密碼儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」嗎?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"要將登入資訊儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」嗎?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"你可以在任何裝置上使用「<xliff:g id="APPDOMAINNAME">%1$s</xliff:g>」<xliff:g id="TYPE">%2$s</xliff:g>。系統會將這項資料儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>」,以供「<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>」使用"</string>
+    <string name="passkey" msgid="632353688396759522">"密碼金鑰"</string>
+    <string name="password" msgid="6738570945182936667">"密碼"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"登入資訊"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"要將「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」用於所有的登入資訊嗎?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"設為預設"</string>
+    <string name="use_once" msgid="9027366575315399714">"單次使用"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼,<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 個密碼金鑰"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 個密碼金鑰"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"密碼金鑰"</string>
+    <string name="another_device" msgid="5147276802037801217">"其他裝置"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"其他密碼管理工具"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"關閉功能表"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"返回上一頁"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密碼金鑰嗎?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資訊嗎?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資訊"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"使用其他方式登入"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"不用了,謝謝"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"繼續"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"登入選項"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> 專用"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"已鎖定的密碼管理工具"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"輕觸即可解鎖"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"管理登入資訊"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"透過其他裝置"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"使用其他裝置"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-zu/strings.xml b/packages/CredentialManager/res/values-zu/strings.xml
new file mode 100644
index 0000000..a35c6d2
--- /dev/null
+++ b/packages/CredentialManager/res/values-zu/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for app_name (4539824758261855508) -->
+    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"Khansela"</string>
+    <string name="string_continue" msgid="1346732695941131882">"Qhubeka"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"Sungula kwenye indawo"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"Londoloza kwenye indawo"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"Sebenzisa enye idivayisi"</string>
+    <string name="string_save_to_another_device" msgid="1959562542075194458">"Londoloza kwenye idivayisi"</string>
+    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Indlela elula yokungena ngemvume ngokuphephile"</string>
+    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Sebenzisa isigxivizo somunwe, ubuso noma ukukhiya isikrini ukuze ungene ngemvume ngokhiye wokudlula oyingqayizivele ongenakulibaleka noma owebiwe. Funda kabanzi"</string>
+    <string name="choose_provider_title" msgid="7245243990139698508">"Khetha lapho onga-<xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"Londoloza iphasiwedi yakho"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"londoloza ulwazi lwakho lokungena ngemvume"</string>
+    <!-- no translation found for choose_provider_body (8045759834416308059) -->
+    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Sungula ukhiye wokungena ku-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"Londoloza ulwazi lwakho lwephasiwedi ku-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Londoloza ulwazi lwakho lokungena ngemvume ku-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"Ungasebenzisa i-<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> kunoma iyiphi idivayisi. Ilondolozelwe i-<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> ku-<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>"</string>
+    <string name="passkey" msgid="632353688396759522">"ukhiye wokudlula"</string>
+    <string name="password" msgid="6738570945182936667">"iphasiwedi"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"ukungena ngemvume"</string>
+    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+    <skip />
+    <!-- no translation found for save_password_to_title (3450480045270186421) -->
+    <skip />
+    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+    <skip />
+    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"Sebenzisa i-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kukho konke ukungena kwakho ngemvume?"</string>
+    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"Setha njengokuzenzakalelayo"</string>
+    <string name="use_once" msgid="9027366575315399714">"Sebenzisa kanye"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Amaphasiwedi angu-<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, okhiye bokudlula abangu-<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"Amaphasiwedi angu-<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Okhiye bokudlula abangu-<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+    <skip />
+    <string name="another_device" msgid="5147276802037801217">"Enye idivayisi"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"Abanye abaphathi bephasiwedi"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"Vala ishidi"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Buyela emuva ekhasini langaphambilini"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Sebenzisa ukhiye wakho wokungena olondoloziwe <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Sebenzisa ukungena kwakho ngemvume okulondoloziwe <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Khetha ukungena ngemvume okulondoloziwe kwakho <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Ngena ngemvume ngenye indlela"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Cha ngiyabonga"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Qhubeka"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Okungakhethwa kukho kokungena ngemvume"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Okuka-<xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Abaphathi bephasiwedi abakhiyiwe"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Thepha ukuze uvule"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Phatha ukungena ngemvume"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Kusukela kwenye idivayisi"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Sebenzisa idivayisi ehlukile"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index c6779fa..870d983 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -1,25 +1,113 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string name="app_name">CredentialManager</string>
+  <!-- The name of this application. Credential Manager is a service that centralizes and provides
+  access to a user's credentials used to sign in to various apps. [CHAR LIMIT=80] -->
+  <string name="app_name">Credential Manager</string>
+
+  <!-- Strings for the create flow. -->
+  <!-- Button label to close the dialog when the user does not want to create the credential. [CHAR LIMIT=40] -->
   <string name="string_cancel">Cancel</string>
+  <!-- Button label to confirm choosing the default dialog information and continue. [CHAR LIMIT=40] -->
   <string name="string_continue">Continue</string>
-  <string name="string_more_options">More options</string>
+  <!-- This appears as a text button where users can click to create this passkey in other available places. [CHAR LIMIT=80] -->
   <string name="string_create_in_another_place">Create in another place</string>
+  <!-- This appears as a text button where users can click to create this password or other credential types in other available places. [CHAR LIMIT=80] -->
   <string name="string_save_to_another_place">Save to another place</string>
-  <string name="string_no_thanks">No thanks</string>
-  <string name="passkey_creation_intro_title">A simple way to sign in safely</string>
-  <string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
-  <string name="choose_provider_title">Choose your default provider</string>
-  <string name="choose_provider_body">This provider will store passkeys and passwords for you and help you easily autofill and sign in. Learn more</string>
-  <string name="choose_create_option_passkey_title">Create a passkey in <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string>
-  <string name="choose_create_option_password_title">Save your password to <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string>
-  <string name="choose_create_option_sign_in_title">Save your sign-in info to <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string>
-  <string name="choose_sign_in_title">Use saved sign in</string>
-  <string name="create_passkey_at">Create passkey at</string>
-  <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for all your sign-ins?</string>
+  <!-- This appears as a text button where users can click to use another device to create this credential. [CHAR LIMIT=80] -->
+  <string name="string_use_another_device">Use another device</string>
+  <!-- This appears as a text button where users can click to save this credential to another device. [CHAR LIMIT=80] -->
+  <string name="string_save_to_another_device">Save to another device</string>
+  <!-- This appears as the title of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
+  <string name="passkey_creation_intro_title">Safer with passkeys</string>
+  <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the passwords side. [CHAR LIMIT=200] -->
+  <string name="passkey_creation_intro_body_password">No need to create or remember complex passwords</string>
+  <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the safety side. [CHAR LIMIT=200] -->
+  <string name="passkey_creation_intro_body_fingerprint">Use your fingerprint, face, or screen lock to create a unique passkey</string>
+  <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the using other devices side. [CHAR LIMIT=200] -->
+  <string name="passkey_creation_intro_body_device">Passkeys are saved to a password manager, so you can sign in on other devices</string>
+  <!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
+  <string name="choose_provider_title">Choose where to <xliff:g id="createTypes" example="create your passkeys">%1$s</xliff:g></string>
+  <!-- Create types which are inserted as a placeholder for string choose_provider_title. [CHAR LIMIT=200] -->
+  <string name="create_your_passkeys">create your passkeys</string>
+  <string name="save_your_password">save your password</string>
+  <string name="save_your_sign_in_info">save your sign-in info</string>
+
+  <!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
+  <string name="choose_provider_body">Set a default password manager to save your passwords and passkeys and sign in faster next time.</string>
+  <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is passkey. [CHAR LIMIT=200] -->
+  <string name="choose_create_option_passkey_title">Create a passkey in <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+  <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is password. [CHAR LIMIT=200] -->
+  <string name="choose_create_option_password_title">Save your password to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+  <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] -->
+  <string name="choose_create_option_sign_in_title">Save your sign-in info to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+  <!-- This appears as the description body of the modal bottom sheet for users to choose the create option inside a provider. [CHAR LIMIT=200] -->
+  <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName" example="Tribank">%1$s</xliff:g> <xliff:g id="type" example="passkey">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName" example="elisa.beckett@gmail.com">%4$s</xliff:g></string>
+  <!-- Types which are inserted as a placeholder for string choose_create_option_description. [CHAR LIMIT=200] -->
+  <string name="passkey">passkey</string>
+  <string name="password">password</string>
+  <string name="sign_ins">sign-ins</string>
+
+  <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created passkey can be created to. [CHAR LIMIT=200] -->
+  <string name="create_passkey_in_title">Create passkey in</string>
+  <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created password can be saved to. [CHAR LIMIT=200] -->
+  <string name="save_password_to_title">Save password to</string>
+  <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created other credential types can be saved to. [CHAR LIMIT=200] -->
+  <string name="save_sign_in_to_title">Save sign-in to</string>
+  <!-- This appears as the title of the modal bottom sheet for users to choose to create a passkey on another device. [CHAR LIMIT=200] -->
+  <string name="create_passkey_in_other_device_title">Create a passkey in another device?</string>
+  <!-- This appears as the title of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
+  <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g> for all your sign-ins?</string>
+  <!-- TODO: Check the wording here. -->
+  <!-- This appears as the description body of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
+  <string name="use_provider_for_all_description">This password manager will store your passwords and passkeys to help you easily sign in.</string>
+  <!-- Button label to set the selected provider on the modal bottom sheet as default. [CHAR LIMIT=40] -->
   <string name="set_as_default">Set as default</string>
+  <!-- Button label to set the selected provider on the modal bottom sheet not as default but just use once. [CHAR LIMIT=40] -->
   <string name="use_once">Use once</string>
-  <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>
+  <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are passwords and passkeys. [CHAR LIMIT=80] -->
+  <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber" example="1">%1$s</xliff:g> passwords, <xliff:g id="passkeysNumber" example="2">%2$s</xliff:g> passkeys</string>
+  <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are only passwords. [CHAR LIMIT=80] -->
+  <string name="more_options_usage_passwords"><xliff:g id="passwordsNumber" example="3">%1$s</xliff:g> passwords</string>
+  <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are only passkeys. [CHAR LIMIT=80] -->
+  <string name="more_options_usage_passkeys"><xliff:g id="passkeysNumber" example="4">%1$s</xliff:g> passkeys</string>
+  <!-- Appears before a request display name when the credential type is passkey . [CHAR LIMIT=80] -->
+  <string name="passkey_before_subtitle">Passkey</string>
+  <!-- Appears as an option row title that users can choose to use another device for this creation. [CHAR LIMIT=80] -->
+  <string name="another_device">Another device</string>
+  <!-- Appears as an option row title that users can choose to view other disabled providers. [CHAR LIMIT=80] -->
+  <string name="other_password_manager">Other password managers</string>
+  <!-- Spoken content description of an element which will close the sheet when clicked. -->
+  <string name="close_sheet">"Close sheet"</string>
+  <!-- Spoken content description of the back arrow button. -->
+  <string name="accessibility_back_arrow_button">"Go back to the previous page"</string>
+
+  <!-- Strings for the get flow. -->
+  <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
+  <string name="get_dialog_title_use_passkey_for">Use your saved passkey for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+  <!-- This appears as the title of the dialog asking for user confirmation to use the single previously saved credential to sign in to the app. [CHAR LIMIT=200] -->
+  <string name="get_dialog_title_use_sign_in_for">Use your saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+  <!-- This appears as the title of the dialog asking for user to make a choice from various previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
+  <string name="get_dialog_title_choose_sign_in_for">Choose a saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+  <!-- Appears as an option row for viewing all the available sign-in options. [CHAR LIMIT=80] -->
+  <string name="get_dialog_use_saved_passkey_for">Sign in another way</string>
+  <!-- Button label to close the dialog when the user does not want to use any sign-in. [CHAR LIMIT=40] -->
+  <string name="get_dialog_button_label_no_thanks">No thanks</string>
+  <!-- Button label to continue with the selected sign-in. [CHAR LIMIT=40] -->
+  <string name="get_dialog_button_label_continue">Continue</string>
+  <!-- Separator for sign-in type and username in a sign-in entry. -->
+  <string name="get_dialog_sign_in_type_username_separator" translatable="false">" - "</string>
+  <!-- Modal bottom sheet title for displaying all the available sign-in options. [CHAR LIMIT=80] -->
+  <string name="get_dialog_title_sign_in_options">Sign-in options</string>
+  <!-- Column heading for displaying sign-ins for a specific username. [CHAR LIMIT=80] -->
+  <string name="get_dialog_heading_for_username">For <xliff:g id="username" example="becket@gmail.com">%1$s</xliff:g></string>
+  <!-- Column heading for displaying locked (that is, the user needs to first authenticate via pin, fingerprint, faceId, etc.) sign-ins. [CHAR LIMIT=80] -->
+  <string name="get_dialog_heading_locked_password_managers">Locked password managers</string>
+  <!-- Explanatory sub/body text for an option entry to use a locked (that is, the user needs to first authenticate via pin, fingerprint, faceId, etc.) sign-in. [CHAR LIMIT=120] -->
+  <string name="locked_credential_entry_label_subtext">Tap to unlock</string>
+  <!-- Column heading for displaying action chips for managing sign-ins from each credential provider. [CHAR LIMIT=80] -->
+  <string name="get_dialog_heading_manage_sign_ins">Manage sign-ins</string>
+  <!-- Column heading for displaying option to use sign-ins saved on a different device. [CHAR LIMIT=80] -->
+  <string name="get_dialog_heading_from_another_device">From another device</string>
+  <!-- Headline text for an option to use sign-ins saved on a different device. [CHAR LIMIT=120] -->
+  <string name="get_dialog_option_headline_use_a_different_device">Use a different device</string>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/themes.xml b/packages/CredentialManager/res/values/themes.xml
index feec746..a58a038 100644
--- a/packages/CredentialManager/res/values/themes.xml
+++ b/packages/CredentialManager/res/values/themes.xml
@@ -2,11 +2,12 @@
 <resources>
 
   <style name="Theme.CredentialSelector" parent="@android:style/ThemeOverlay.Material">
-    <item name="android:statusBarColor">@color/purple_700</item>
+    <item name="android:statusBarColor">@android:color/transparent</item>
     <item name="android:windowContentOverlay">@null</item>
     <item name="android:windowNoTitle">true</item>
     <item name="android:windowBackground">@android:color/transparent</item>
     <item name="android:windowIsTranslucent">true</item>
     <item name="android:colorBackgroundCacheHint">@null</item>
+    <item name="fontFamily">google-sans</item>
   </style>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt
deleted file mode 100644
index ee4f4ca..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.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 credential entry used during the get credential flow.
- *
- * TODO: move to jetpack.
- */
-class CredentialEntryUi(
-  val userName: CharSequence,
-  val displayName: CharSequence?,
-  val icon: Icon?,
-  val usageData: CharSequence?,
-  // TODO: add last used.
-) {
-  companion object {
-    fun fromSlice(slice: Slice): CredentialEntryUi {
-      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 CredentialEntryUi(title!!, subTitle, icon, usageData)
-    }
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 56fb1a9..2780c3c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -16,35 +16,47 @@
 
 package com.android.credentialmanager
 
+import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
+import android.app.PendingIntent
 import android.app.slice.Slice
 import android.app.slice.SliceSpec
 import android.content.Context
 import android.content.Intent
 import android.credentials.CreateCredentialRequest
+import android.credentials.GetCredentialOption
+import android.credentials.GetCredentialRequest
 import android.credentials.ui.Constants
 import android.credentials.ui.Entry
+import android.credentials.ui.CreateCredentialProviderData
+import android.credentials.ui.GetCredentialProviderData
+import android.credentials.ui.DisabledProviderData
 import android.credentials.ui.ProviderData
 import android.credentials.ui.RequestInfo
 import android.credentials.ui.BaseDialogResult
+import android.credentials.ui.ProviderPendingIntentResponse
 import android.credentials.ui.UserSelectionDialogResult
 import android.graphics.drawable.Icon
 import android.os.Binder
 import android.os.Bundle
 import android.os.ResultReceiver
-import com.android.credentialmanager.createflow.CreatePasskeyUiState
-import com.android.credentialmanager.createflow.CreateScreenState
-import com.android.credentialmanager.createflow.RequestDisplayInfo
+import android.service.credentials.CredentialProviderService
+import com.android.credentialmanager.createflow.CreateCredentialUiState
+import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.RemoteInfo
 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.developer.CreatePasswordRequest.Companion.toBundle
+import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
 
 // Consider repo per screen, similar to view model?
 class CredentialManagerRepo(
   private val context: Context,
   intent: Intent,
 ) {
-  private val requestInfo: RequestInfo
-  private val providerList: List<ProviderData>
+  val requestInfo: RequestInfo
+  private val providerEnabledList: List<ProviderData>
+  private val providerDisabledList: List<DisabledProviderData>?
   // TODO: require non-null.
   val resultReceiver: ResultReceiver?
 
@@ -52,12 +64,30 @@
     requestInfo = intent.extras?.getParcelable(
       RequestInfo.EXTRA_REQUEST_INFO,
       RequestInfo::class.java
-    ) ?: testRequestInfo()
+    ) ?: testCreatePasskeyRequestInfo()
 
-    providerList = intent.extras?.getParcelableArrayList(
-      ProviderData.EXTRA_PROVIDER_DATA_LIST,
-      ProviderData::class.java
-    ) ?: testProviderList()
+    providerEnabledList = when (requestInfo.type) {
+      RequestInfo.TYPE_CREATE ->
+        intent.extras?.getParcelableArrayList(
+                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+                CreateCredentialProviderData::class.java
+        ) ?: testCreateCredentialEnabledProviderList()
+      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}")
+      }
+    }
+
+    providerDisabledList =
+      intent.extras?.getParcelableArrayList(
+        ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST,
+        DisabledProviderData::class.java
+      ) ?: testDisabledProviderList()
 
     resultReceiver = intent.getParcelableExtra(
       Constants.EXTRA_RESULT_RECEIVER,
@@ -71,12 +101,19 @@
     resultReceiver?.send(BaseDialogResult.RESULT_CODE_DIALOG_CANCELED, resultData)
   }
 
-  fun onOptionSelected(providerPackageName: String, entryKey: String, entrySubkey: String) {
+  fun onOptionSelected(
+    providerPackageName: String,
+    entryKey: String,
+    entrySubkey: String,
+    resultCode: Int? = null,
+    resultData: Intent? = null,
+  ) {
     val userSelectionDialogResult = UserSelectionDialogResult(
       requestInfo.token,
       providerPackageName,
       entryKey,
-      entrySubkey
+      entrySubkey,
+      if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
     )
     val resultData = Bundle()
     UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultData)
@@ -84,33 +121,52 @@
   }
 
   fun getCredentialInitialUiState(): GetCredentialUiState {
-    val providerList = GetFlowUtils.toProviderList(providerList, context)
+    val providerEnabledList = GetFlowUtils.toProviderList(
+    // TODO: handle runtime cast error
+      providerEnabledList as List<GetCredentialProviderData>, context)
     // TODO: covert from real requestInfo
-    val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo(
-      "Elisa Beckett",
-      "beckett-bakert@gmail.com",
-      TYPE_PUBLIC_KEY_CREDENTIAL,
-      "tribank")
+    val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo("the app")
     return GetCredentialUiState(
-      providerList,
-      GetScreenState.CREDENTIAL_SELECTION,
+      providerEnabledList,
+      GetScreenState.PRIMARY_SELECTION,
       requestDisplayInfo,
-      providerList.first()
     )
   }
 
-  fun createPasskeyInitialUiState(): CreatePasskeyUiState {
-    val providerList = CreateFlowUtils.toProviderList(providerList, context)
-    // TODO: covert from real requestInfo
-    val requestDisplayInfo = RequestDisplayInfo(
-      "Elisa Beckett",
-      "beckett-bakert@gmail.com",
-      TYPE_PUBLIC_KEY_CREDENTIAL,
-      "tribank")
-    return CreatePasskeyUiState(
-      providers = providerList,
-      currentScreenState = CreateScreenState.PASSKEY_INTRO,
+  fun createCredentialInitialUiState(): CreateCredentialUiState {
+    val requestDisplayInfo = CreateFlowUtils.toRequestDisplayInfo(requestInfo, context)
+    val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
+      // Handle runtime cast error
+      providerEnabledList as List<CreateCredentialProviderData>, requestDisplayInfo, context)
+    val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
+      // Handle runtime cast error
+      providerDisabledList, context)
+    var defaultProvider: EnabledProviderInfo? = null
+    var remoteEntry: RemoteInfo? = null
+    var createOptionSize = 0
+    var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
+    providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
+      providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
+      if (providerInfo.isDefault) {defaultProvider = providerInfo}
+      if (providerInfo.remoteEntry != null) {
+        remoteEntry = providerInfo.remoteEntry!!
+      }
+      if (providerInfo.createOptions.isNotEmpty()) {
+        createOptionSize += providerInfo.createOptions.size
+        lastSeenProviderWithNonEmptyCreateOptions = providerInfo
+      }
+    }
+    return CreateCredentialUiState(
+      enabledProviders = providerEnabledList,
+      disabledProviders = providerDisabledList,
+      CreateFlowUtils.toCreateScreenState(
+        createOptionSize, false,
+        requestDisplayInfo, defaultProvider, remoteEntry),
       requestDisplayInfo,
+      false,
+      CreateFlowUtils.toActiveEntry(
+        /*defaultProvider=*/defaultProvider, createOptionSize,
+        lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
     )
   }
 
@@ -130,83 +186,342 @@
   }
 
   // TODO: below are prototype functionalities. To be removed for productionization.
-  private fun testProviderList(): List<ProviderData> {
+  private fun testCreateCredentialEnabledProviderList(): List<CreateCredentialProviderData> {
     return listOf(
-      ProviderData.Builder(
-        "com.google",
-        "Google Password Manager",
-        Icon.createWithResource(context, R.drawable.ic_launcher_foreground))
+      CreateCredentialProviderData
+        .Builder("io.enpass.app")
+        .setSaveEntries(
+          listOf<Entry>(
+            newCreateEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
+              20, 7, 27, 10000),
+            newCreateEntry("key1", "subkey-2", "elisa.work@google.com",
+              20, 7, 27, 11000),
+          )
+        )
+        .setRemoteEntry(
+          newRemoteEntry("key2", "subkey-1")
+        )
+        .setIsDefaultProvider(true)
+        .build(),
+      CreateCredentialProviderData
+        .Builder("com.dashlane")
+        .setSaveEntries(
+          listOf<Entry>(
+            newCreateEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
+              20, 7, 27, 30000),
+            newCreateEntry("key1", "subkey-4", "elisa.work@dashlane.com",
+              20, 7, 27, 31000),
+          )
+        )
+        .build(),
+    )
+  }
+
+  private fun testDisabledProviderList(): List<DisabledProviderData>? {
+    return listOf(
+      DisabledProviderData("com.lastpass.lpandroid"),
+      DisabledProviderData("com.google.android.youtube")
+    )
+  }
+
+  private fun testGetCredentialProviderList(): List<GetCredentialProviderData> {
+    return listOf(
+      GetCredentialProviderData.Builder("io.enpass.app")
         .setCredentialEntries(
           listOf<Entry>(
-            newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
-              "Elisa Backett", "20 passwords and 7 passkeys saved"),
-            newEntry("key1", "subkey-2", "elisa.work@google.com",
-              "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+            newGetEntry(
+              "key1", "subkey-1", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
+              "elisa.bakery@gmail.com", "Elisa Beckett", 300L
+            ),
+            newGetEntry(
+              "key1", "subkey-2", TYPE_PASSWORD_CREDENTIAL, "Password",
+              "elisa.bakery@gmail.com", null, 300L
+            ),
+            newGetEntry(
+              "key1", "subkey-3", TYPE_PASSWORD_CREDENTIAL, "Password",
+              "elisa.family@outlook.com", null, 100L
+            ),
           )
+        ).setAuthenticationEntry(
+          newAuthenticationEntry("key2", "subkey-1", TYPE_PASSWORD_CREDENTIAL)
         ).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"),
-          ),
+          listOf(
+            newActionEntry(
+              "key3", "subkey-1", TYPE_PASSWORD_CREDENTIAL,
+              Icon.createWithResource(context, R.drawable.ic_manage_accounts),
+              "Open Google Password Manager", "elisa.beckett@gmail.com"
+            ),
+            newActionEntry(
+              "key3", "subkey-2", TYPE_PASSWORD_CREDENTIAL,
+              Icon.createWithResource(context, R.drawable.ic_manage_accounts),
+              "Open Google Password Manager", "beckett-family@gmail.com"
+            ),
+          )
+        ).setRemoteEntry(
+          newRemoteEntry("key4", "subkey-1")
         ).build(),
-      ProviderData.Builder(
-        "com.dashlane",
-        "Dashlane",
-        Icon.createWithResource(context, R.drawable.ic_launcher_foreground))
+      GetCredentialProviderData.Builder("com.dashlane")
         .setCredentialEntries(
           listOf<Entry>(
-            newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
-              "Elisa Backett", "20 passwords and 7 passkeys saved"),
-            newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
-              "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+            newGetEntry(
+              "key1", "subkey-1", TYPE_PASSWORD_CREDENTIAL, "Password",
+              "elisa.family@outlook.com", null, 600L
+            ),
+            newGetEntry(
+              "key1", "subkey-2", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
+              "elisa.family@outlook.com", null, 100L
+            ),
           )
+        ).setAuthenticationEntry(
+          newAuthenticationEntry("key2", "subkey-1", TYPE_PASSWORD_CREDENTIAL)
         ).setActionChips(
-          listOf<Entry>(
-            newEntry("key2", "subkey-3", "Manage Accounts",
-              "Manage your accounts in the dashlane app",
-                     "20 passwords and 7 passkeys saved"),
-          ),
+          listOf(
+            newActionEntry(
+              "key3", "subkey-1", TYPE_PASSWORD_CREDENTIAL,
+              Icon.createWithResource(context, R.drawable.ic_face),
+              "Open Enpass"
+            ),
+          )
         ).build(),
     )
   }
 
-  private fun newEntry(
+  private fun newActionEntry(
     key: String,
     subkey: String,
-    title: String,
-    subtitle: String,
-    usageData: String
+    credentialType: String,
+    icon: Icon,
+    text: String,
+    subtext: String? = null,
   ): Entry {
     val slice = Slice.Builder(
-      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
+      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
+    ).addText(
+      text, null, listOf(Entry.HINT_ACTION_TITLE)
+    ).addIcon(icon, null, listOf(Entry.HINT_ACTION_ICON))
+    if (subtext != null) {
+      slice.addText(subtext, null, listOf(Entry.HINT_ACTION_SUBTEXT))
+    }
+    return Entry(
+      key,
+      subkey,
+      slice.build()
     )
-      .addText(title, null, listOf(Entry.HINT_TITLE))
-      .addText(subtitle, null, listOf(Entry.HINT_SUBTITLE))
+  }
+
+  private fun newAuthenticationEntry(
+    key: String,
+    subkey: String,
+    credentialType: String,
+  ): Entry {
+    val slice = Slice.Builder(
+      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
+    )
+    return Entry(
+      key,
+      subkey,
+      slice.build()
+    )
+  }
+
+  private fun newGetEntry(
+    key: String,
+    subkey: String,
+    credentialType: String,
+    credentialTypeDisplayName: String,
+    userName: String,
+    userDisplayName: String?,
+    lastUsedTimeMillis: Long?,
+  ): Entry {
+    val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
+      .setPackage("com.androidauth.androidvault")
+    intent.putExtra("provider_extra_sample", "testprovider")
+
+    val pendingIntent = PendingIntent.getActivity(context, 1,
+      intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+              or PendingIntent.FLAG_ONE_SHOT))
+
+    val slice = Slice.Builder(
+      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
+    ).addText(
+      credentialTypeDisplayName, null, listOf(Entry.HINT_CREDENTIAL_TYPE_DISPLAY_NAME)
+    ).addText(
+      userName, null, listOf(Entry.HINT_USER_NAME)
+    ).addIcon(
+      Icon.createWithResource(context, R.drawable.ic_passkey),
+      null,
+      listOf(Entry.HINT_PROFILE_ICON))
+    if (userDisplayName != null) {
+      slice.addText(userDisplayName, null, listOf(Entry.HINT_PASSKEY_USER_DISPLAY_NAME))
+    }
+    if (lastUsedTimeMillis != null) {
+      slice.addLong(lastUsedTimeMillis, null, listOf(Entry.HINT_LAST_USED_TIME_MILLIS))
+    }
+    return Entry(
+      key,
+      subkey,
+      slice.build(),
+      pendingIntent,
+      null
+    )
+  }
+
+  private fun newCreateEntry(
+    key: String,
+    subkey: String,
+    providerDisplayName: String,
+    passwordCount: Int,
+    passkeyCount: Int,
+    totalCredentialCount: Int,
+    lastUsedTimeMillis: Long,
+  ): Entry {
+    val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
+      .setPackage("com.androidauth.androidvault")
+    intent.putExtra("provider_extra_sample", "testprovider")
+    val pendingIntent = PendingIntent.getActivity(context, 1,
+      intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+              or PendingIntent.FLAG_ONE_SHOT))
+    val createPasswordRequest = android.service.credentials.CreateCredentialRequest(
+            context.applicationInfo.packageName,
+            TYPE_PASSWORD_CREDENTIAL,
+            toBundle("beckett-bakert@gmail.com", "password123")
+    )
+    val fillInIntent = Intent().putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
+            createPasswordRequest)
+
+    val slice = Slice.Builder(
+      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
+    ).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,
       subkey,
-      slice
+      slice,
+      pendingIntent,
+      fillInIntent,
     )
   }
 
-  private fun testRequestInfo(): RequestInfo {
+  private fun newRemoteEntry(
+    key: String,
+    subkey: String,
+  ): Entry {
+    return Entry(
+      key,
+      subkey,
+      Slice.Builder(
+        Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
+      ).build()
+    )
+  }
+
+  private fun testCreatePasskeyRequestInfo(): RequestInfo {
+    val request = CreatePublicKeyCredentialRequest("{\"extensions\": {\n" +
+            "                     \"webauthn.loc\": true\n" +
+            "                   },\n" +
+            "                   \"attestation\": \"direct\",\n" +
+            "                   \"challenge\": \"-rSQHXSQUdaK1N-La5bE-JPt6EVAW4SxX1K_tXhZ_Gk\",\n" +
+            "                   \"user\": {\n" +
+            "                     \"displayName\": \"testName\",\n" +
+            "                     \"name\": \"credManTesting@gmail.com\",\n" +
+            "                     \"id\": \"eD4o2KoXLpgegAtnM5cDhhUPvvk2\"\n" +
+            "                   },\n" +
+            "                   \"excludeCredentials\": [],\n" +
+            "                   \"rp\": {\n" +
+            "                     \"name\": \"Address Book\",\n" +
+            "                     \"id\": \"addressbook-c7876.uc.r.appspot.com\"\n" +
+            "                   },\n" +
+            "                   \"timeout\": 60000,\n" +
+            "                   \"pubKeyCredParams\": [\n" +
+            "                     {\n" +
+            "                       \"type\": \"public-key\",\n" +
+            "                       \"alg\": -7\n" +
+            "                     },\n" +
+            "                     {\n" +
+            "                       \"type\": \"public-key\",\n" +
+            "                       \"alg\": -257\n" +
+            "                     },\n" +
+            "                     {\n" +
+            "                       \"type\": \"public-key\",\n" +
+            "                       \"alg\": -37\n" +
+            "                     }\n" +
+            "                   ],\n" +
+            "                   \"authenticatorSelection\": {\n" +
+            "                     \"residentKey\": \"required\",\n" +
+            "                     \"requireResidentKey\": true\n" +
+            "                   }}")
+    val credentialData = request.data
+    return RequestInfo.newCreateRequestInfo(
+      Binder(),
+      CreateCredentialRequest(
+        TYPE_PUBLIC_KEY_CREDENTIAL,
+        credentialData,
+        // TODO: populate with actual data
+        /*candidateQueryData=*/ Bundle(),
+        /*requireSystemProvider=*/ false
+      ),
+      /*isFirstUsage=*/false,
+      "tribank"
+    )
+  }
+
+  private fun testCreatePasswordRequestInfo(): RequestInfo {
+    val data = toBundle("beckett-bakert@gmail.com", "password123")
+    return RequestInfo.newCreateRequestInfo(
+      Binder(),
+      CreateCredentialRequest(
+        TYPE_PASSWORD_CREDENTIAL,
+        data,
+        // TODO: populate with actual data
+        /*candidateQueryData=*/ Bundle(),
+        /*requireSystemProvider=*/ false
+      ),
+      /*isFirstUsage=*/false,
+      "tribank"
+    )
+  }
+
+  private fun testCreateOtherCredentialRequestInfo(): RequestInfo {
     val data = Bundle()
     return RequestInfo.newCreateRequestInfo(
       Binder(),
       CreateCredentialRequest(
-        // TODO: use the jetpack type and utils once defined.
-        TYPE_PUBLIC_KEY_CREDENTIAL,
-        data
+        "other-sign-ins",
+        data,
+        /*candidateQueryData=*/ Bundle(),
+        /*requireSystemProvider=*/ false
       ),
       /*isFirstUsage=*/false,
+      "tribank"
+    )
+  }
+
+  private fun testGetRequestInfo(): RequestInfo {
+    return RequestInfo.newGetRequestInfo(
+      Binder(),
+      GetCredentialRequest.Builder()
+        .addGetCredentialOption(
+          GetCredentialOption(
+            TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), /*requireSystemProvider=*/ false)
+        )
+        .build(),
+      /*isFirstUsage=*/false,
       "tribank.us"
     )
   }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 78edaa9..6a4c599 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -16,20 +16,24 @@
 
 package com.android.credentialmanager
 
-import android.credentials.ui.RequestInfo
 import android.os.Bundle
 import android.util.Log
 import androidx.activity.ComponentActivity
+import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.compose.setContent
+import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.lifecycle.Observer
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.android.credentialmanager.common.DialogType
 import com.android.credentialmanager.common.DialogResult
+import com.android.credentialmanager.common.ProviderActivityResult
 import com.android.credentialmanager.common.ResultState
-import com.android.credentialmanager.createflow.CreatePasskeyScreen
-import com.android.credentialmanager.createflow.CreatePasskeyViewModel
+import com.android.credentialmanager.createflow.CreateCredentialScreen
+import com.android.credentialmanager.createflow.CreateCredentialViewModel
 import com.android.credentialmanager.getflow.GetCredentialScreen
 import com.android.credentialmanager.getflow.GetCredentialViewModel
 import com.android.credentialmanager.ui.theme.CredentialSelectorTheme
@@ -39,36 +43,35 @@
   override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
     CredentialManagerRepo.setup(this, intent)
-    val requestInfo = intent.extras?.getParcelable<RequestInfo>(RequestInfo.EXTRA_REQUEST_INFO)
-    if (requestInfo != null) {
-      val requestType = requestInfo.type
-      setContent {
-        CredentialSelectorTheme {
-          CredentialManagerBottomSheet(requestType)
-        }
-      }
-    } else {
-      // TODO: prototype only code to be removed. In production should exit.
-      setContent {
-        CredentialSelectorTheme {
-          CredentialManagerBottomSheet(RequestInfo.TYPE_CREATE)
-        }
+    val requestInfo = CredentialManagerRepo.getInstance().requestInfo
+    setContent {
+      CredentialSelectorTheme {
+        CredentialManagerBottomSheet(DialogType.toDialogType(requestInfo.type))
       }
     }
   }
 
   @ExperimentalMaterialApi
   @Composable
-  fun CredentialManagerBottomSheet(operationType: String) {
-    val dialogType = DialogType.toDialogType(operationType)
+  fun CredentialManagerBottomSheet(dialogType: DialogType) {
+    val providerActivityResult = remember { mutableStateOf<ProviderActivityResult?>(null) }
+    val launcher = rememberLauncherForActivityResult(
+      ActivityResultContracts.StartIntentSenderForResult()
+    ) {
+      providerActivityResult.value = ProviderActivityResult(it.resultCode, it.data)
+    }
     when (dialogType) {
       DialogType.CREATE_PASSKEY -> {
-        val viewModel: CreatePasskeyViewModel = viewModel()
+        val viewModel: CreateCredentialViewModel = viewModel()
         viewModel.observeDialogResult().observe(
           this@CredentialSelectorActivity,
           onCancel
         )
-        CreatePasskeyScreen(viewModel = viewModel)
+        providerActivityResult.value?.let {
+          viewModel.onProviderActivityResult(it)
+          providerActivityResult.value = null
+        }
+        CreateCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
       }
       DialogType.GET_CREDENTIALS -> {
         val viewModel: GetCredentialViewModel = viewModel()
@@ -76,7 +79,11 @@
           this@CredentialSelectorActivity,
           onCancel
         )
-        GetCredentialScreen(viewModel = viewModel)
+        providerActivityResult.value?.let {
+          viewModel.onProviderActivityResult(it)
+          providerActivityResult.value = null
+        }
+        GetCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
       }
       else -> {
         Log.w("AccountSelector", "Unknown type, not rendering any UI")
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 2ba8748..0d7e819 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -16,52 +16,161 @@
 
 package com.android.credentialmanager
 
+import android.content.ComponentName
 import android.content.Context
+import android.content.pm.PackageManager
 import android.credentials.ui.Entry
-import android.credentials.ui.ProviderData
+import android.credentials.ui.GetCredentialProviderData
+import android.credentials.ui.CreateCredentialProviderData
+import android.credentials.ui.DisabledProviderData
+import android.credentials.ui.RequestInfo
+import android.graphics.drawable.Drawable
 import com.android.credentialmanager.createflow.CreateOptionInfo
-import com.android.credentialmanager.getflow.CredentialOptionInfo
+import com.android.credentialmanager.createflow.RemoteInfo
+import com.android.credentialmanager.createflow.RequestDisplayInfo
+import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.ActiveEntry
+import com.android.credentialmanager.getflow.ActionEntryInfo
+import com.android.credentialmanager.getflow.AuthenticationEntryInfo
+import com.android.credentialmanager.getflow.CredentialEntryInfo
 import com.android.credentialmanager.getflow.ProviderInfo
+import com.android.credentialmanager.getflow.RemoteEntryInfo
+import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest
+import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
+import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
+import com.android.credentialmanager.jetpack.provider.ActionUi
+import com.android.credentialmanager.jetpack.provider.CredentialEntryUi
+import com.android.credentialmanager.jetpack.provider.SaveEntryUi
+import org.json.JSONObject
 
 /** 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> {
+      val packageManager = context.packageManager
       return providerDataList.map {
+        // TODO: get from the actual service info
+        val componentName = ComponentName.unflattenFromString(it.providerFlattenedComponentName)
+        var packageName = componentName?.packageName
+        if (componentName == null) {
+          // TODO: Remove once test data is fixed
+          packageName = it.providerFlattenedComponentName
+        }
+
+        val pkgInfo = packageManager
+          .getPackageInfo(packageName!!,
+            PackageManager.PackageInfoFlags.of(0))
+        val providerDisplayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString()
+        // TODO: decide what to do when failed to load a provider icon
+        val providerIcon = pkgInfo.applicationInfo.loadIcon(packageManager)!!
         ProviderInfo(
-          // TODO: replace to extract from the service data structure when available
-          icon = context.getDrawable(R.drawable.ic_passkey)!!,
-          name = it.providerFlattenedComponentName,
-          displayName = it.providerDisplayName,
-          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
-          credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context)
+          id = it.providerFlattenedComponentName,
+          // TODO: decide what to do when failed to load a provider icon
+          icon = providerIcon,
+          displayName = providerDisplayName,
+          credentialEntryList = getCredentialOptionInfoList(
+            it.providerFlattenedComponentName, it.credentialEntries, context),
+          authenticationEntry = getAuthenticationEntry(
+            it.providerFlattenedComponentName,
+            providerDisplayName,
+            providerIcon,
+            it.authenticationEntry),
+          remoteEntry = getRemoteEntry(it.providerFlattenedComponentName, it.remoteEntry),
+          actionEntryList = getActionEntryList(
+            it.providerFlattenedComponentName, it.actionChips, context),
         )
       }
     }
 
 
     /* From service data structure to UI credential entry list representation. */
-    private fun toCredentialOptionInfoList(
+    private fun getCredentialOptionInfoList(
+      providerId: String,
       credentialEntries: List<Entry>,
       context: Context,
-    ): List<CredentialOptionInfo> {
+    ): List<CredentialEntryInfo> {
       return credentialEntries.map {
         val credentialEntryUi = CredentialEntryUi.fromSlice(it.slice)
 
         // Consider directly move the UI object into the class.
-        return@map CredentialOptionInfo(
-          // 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",
+        return@map CredentialEntryInfo(
+          providerId = providerId,
           entryKey = it.key,
           entrySubkey = it.subkey,
-          usageData = credentialEntryUi.usageData?.toString() ?: "Unknown usageData",
+          pendingIntent = it.pendingIntent,
+          fillInIntent = it.frameworkExtrasIntent,
+          credentialType = credentialEntryUi.credentialType.toString(),
+          credentialTypeDisplayName = credentialEntryUi.credentialTypeDisplayName.toString(),
+          userName = credentialEntryUi.userName.toString(),
+          displayName = credentialEntryUi.userDisplayName?.toString(),
+          // TODO: proper fallback
+          icon = credentialEntryUi.entryIcon?.loadDrawable(context)
+            ?: context.getDrawable(R.drawable.ic_passkey)!!,
+          lastUsedTimeMillis = credentialEntryUi.lastUsedTimeMillis,
+        )
+      }
+    }
+
+    private fun getAuthenticationEntry(
+      providerId: String,
+      providerDisplayName: String,
+      providerIcon: Drawable,
+      authEntry: Entry?,
+    ): AuthenticationEntryInfo? {
+      // TODO: should also call fromSlice after getting the official jetpack code.
+
+      if (authEntry == null) {
+        return null
+      }
+      return AuthenticationEntryInfo(
+        providerId = providerId,
+        entryKey = authEntry.key,
+        entrySubkey = authEntry.subkey,
+        pendingIntent = authEntry.pendingIntent,
+        fillInIntent = authEntry.frameworkExtrasIntent,
+        title = providerDisplayName,
+        icon = providerIcon,
+      )
+    }
+
+    private fun getRemoteEntry(providerId: String, remoteEntry: Entry?): RemoteEntryInfo? {
+      // TODO: should also call fromSlice after getting the official jetpack code.
+      if (remoteEntry == null) {
+        return null
+      }
+      return RemoteEntryInfo(
+        providerId = providerId,
+        entryKey = remoteEntry.key,
+        entrySubkey = remoteEntry.subkey,
+        pendingIntent = remoteEntry.pendingIntent,
+        fillInIntent = remoteEntry.frameworkExtrasIntent,
+      )
+    }
+
+    private fun getActionEntryList(
+      providerId: String,
+      actionEntries: List<Entry>,
+      context: Context,
+    ): List<ActionEntryInfo> {
+      return actionEntries.map {
+        val actionEntryUi = ActionUi.fromSlice(it.slice)
+
+        return@map ActionEntryInfo(
+          providerId = providerId,
+          entryKey = it.key,
+          entrySubkey = it.subkey,
+          pendingIntent = it.pendingIntent,
+          fillInIntent = it.frameworkExtrasIntent,
+          title = actionEntryUi.text.toString(),
+          // TODO: gracefully fail
+          icon = actionEntryUi.icon.loadDrawable(context)!!,
+          subTitle = actionEntryUi.subtext?.toString(),
         )
       }
     }
@@ -71,24 +180,158 @@
 class CreateFlowUtils {
   companion object {
 
-    fun toProviderList(
-      providerDataList: List<ProviderData>,
+    fun toEnabledProviderList(
+      providerDataList: List<CreateCredentialProviderData>,
+      requestDisplayInfo: RequestDisplayInfo,
       context: Context,
-    ): List<com.android.credentialmanager.createflow.ProviderInfo> {
+    ): List<com.android.credentialmanager.createflow.EnabledProviderInfo> {
+      // TODO: get from the actual service info
+      val packageManager = context.packageManager
+
       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)!!,
+        val componentName = ComponentName.unflattenFromString(it.providerFlattenedComponentName)
+        var packageName = componentName?.packageName
+        if (componentName == null) {
+          // TODO: Remove once test data is fixed
+          packageName = it.providerFlattenedComponentName
+        }
+
+        val pkgInfo = packageManager
+          .getPackageInfo(packageName!!,
+            PackageManager.PackageInfoFlags.of(0))
+        com.android.credentialmanager.createflow.EnabledProviderInfo(
+          // TODO: decide what to do when failed to load a provider icon
+          icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
           name = it.providerFlattenedComponentName,
-          displayName = it.providerDisplayName,
-          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
-          createOptions = toCreationOptionInfoList(it.credentialEntries, context),
+          displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
+          createOptions = toCreationOptionInfoList(
+            it.providerFlattenedComponentName, it.saveEntries, requestDisplayInfo, context),
+          isDefault = it.isDefaultProvider,
+          remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
         )
       }
     }
 
+    fun toDisabledProviderList(
+      providerDataList: List<DisabledProviderData>?,
+      context: Context,
+    ): List<com.android.credentialmanager.createflow.DisabledProviderInfo>? {
+      // TODO: get from the actual service info
+      val packageManager = context.packageManager
+      return providerDataList?.map {
+        val pkgInfo = packageManager
+          .getPackageInfo(it.providerFlattenedComponentName,
+            PackageManager.PackageInfoFlags.of(0))
+        com.android.credentialmanager.createflow.DisabledProviderInfo(
+          icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
+          name = it.providerFlattenedComponentName,
+          displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
+        )
+      }
+    }
+
+    fun toRequestDisplayInfo(
+      requestInfo: RequestInfo,
+      context: Context,
+    ): RequestDisplayInfo {
+      val createCredentialRequest = requestInfo.createCredentialRequest
+      val createCredentialRequestJetpack = createCredentialRequest?.let {
+        CreateCredentialRequest.createFrom(
+          it
+        )
+      }
+      when (createCredentialRequestJetpack) {
+        is CreatePasswordRequest -> {
+          return RequestDisplayInfo(
+            createCredentialRequestJetpack.id,
+            createCredentialRequestJetpack.password,
+            createCredentialRequestJetpack.type,
+            requestInfo.appPackageName,
+            context.getDrawable(R.drawable.ic_password)!!,
+            requestInfo.isFirstUsage
+          )
+        }
+        is CreatePublicKeyCredentialRequest -> {
+          val requestJson = createCredentialRequestJetpack.requestJson
+          val json = JSONObject(requestJson)
+          var name = ""
+          var displayName = ""
+          if (json.has("user")) {
+            val user: JSONObject = json.getJSONObject("user")
+            name = user.getString("name")
+            displayName = user.getString("displayName")
+          }
+          return RequestDisplayInfo(
+            name,
+            displayName,
+            createCredentialRequestJetpack.type,
+            requestInfo.appPackageName,
+            context.getDrawable(R.drawable.ic_passkey)!!,
+            requestInfo.isFirstUsage)
+        }
+        // TODO: correctly parsing for other sign-ins
+        else -> {
+          return RequestDisplayInfo(
+            "beckett-bakert@gmail.com",
+            "Elisa Beckett",
+            "other-sign-ins",
+            requestInfo.appPackageName,
+            context.getDrawable(R.drawable.ic_other_sign_in)!!,
+            requestInfo.isFirstUsage)
+        }
+      }
+    }
+
+    fun toCreateScreenState(
+      createOptionSize: Int,
+      isOnPasskeyIntroStateAlready: Boolean,
+      requestDisplayInfo: RequestDisplayInfo,
+      defaultProvider: EnabledProviderInfo?,
+      remoteEntry: RemoteInfo?,
+    ): CreateScreenState {
+      return if (requestDisplayInfo.isFirstUsage && requestDisplayInfo
+          .type == TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) {
+        CreateScreenState.PASSKEY_INTRO
+      } else if (
+        (defaultProvider == null || defaultProvider.createOptions.isEmpty()
+                ) && createOptionSize > 1) {
+        CreateScreenState.PROVIDER_SELECTION
+      } else if (
+        ((defaultProvider == null || defaultProvider.createOptions.isEmpty()
+                ) && createOptionSize == 1) || (
+                defaultProvider != null && defaultProvider.createOptions.isNotEmpty())) {
+        CreateScreenState.CREATION_OPTION_SELECTION
+      } else if (createOptionSize == 0 && remoteEntry != null) {
+        CreateScreenState.EXTERNAL_ONLY_SELECTION
+      } else {
+          // TODO: properly handle error and gracefully finish itself
+          throw java.lang.IllegalStateException("Empty provider list.")
+      }
+    }
+
+   fun toActiveEntry(
+      defaultProvider: EnabledProviderInfo?,
+      createOptionSize: Int,
+      lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?,
+      remoteEntry: RemoteInfo?,
+    ): ActiveEntry? {
+      return if (
+        defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
+        ActiveEntry(defaultProvider, remoteEntry)
+      } else if (
+        defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
+      ) {
+        ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
+      } else if (createOptionSize == 1) {
+        ActiveEntry(lastSeenProviderWithNonEmptyCreateOptions!!,
+          lastSeenProviderWithNonEmptyCreateOptions.createOptions.first())
+      } else null
+    }
+
     private fun toCreationOptionInfoList(
+      providerId: String,
       creationEntries: List<Entry>,
+      requestDisplayInfo: RequestDisplayInfo,
       context: Context,
     ): List<CreateOptionInfo> {
       return creationEntries.map {
@@ -96,15 +339,36 @@
 
         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",
+          providerId = providerId,
           entryKey = it.key,
           entrySubkey = it.subkey,
-          usageData = saveEntryUi.usageData?.toString() ?: "Unknown usageData",
+          pendingIntent = it.pendingIntent,
+          fillInIntent = it.frameworkExtrasIntent,
+          userProviderDisplayName = saveEntryUi.userProviderAccountName as String,
+          profileIcon = saveEntryUi.profileIcon?.loadDrawable(context)
+            ?: requestDisplayInfo.typeIcon,
+          passwordCount = saveEntryUi.passwordCount ?: 0,
+          passkeyCount = saveEntryUi.passkeyCount ?: 0,
+          totalCredentialCount = saveEntryUi.totalCredentialCount ?: 0,
+          lastUsedTimeMillis = saveEntryUi.lastUsedTimeMillis ?: 0,
         )
       }
     }
+
+    private fun toRemoteInfo(
+      providerId: String,
+      remoteEntry: Entry?,
+    ): RemoteInfo? {
+      // TODO: should also call fromSlice after getting the official jetpack code.
+      return if (remoteEntry != null) {
+        RemoteInfo(
+          providerId = providerId,
+          entryKey = remoteEntry.key,
+          entrySubkey = remoteEntry.subkey,
+          pendingIntent = remoteEntry.pendingIntent,
+          fillInIntent = remoteEntry.frameworkExtrasIntent,
+        )
+      } else null
+    }
   }
 }
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/common/ProviderActivityResult.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ProviderActivityResult.kt
new file mode 100644
index 0000000..9e33d51
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ProviderActivityResult.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.common
+
+import android.content.Intent
+
+data class ProviderActivityResult(
+    val resultCode: Int,
+    val data: Intent?,
+)
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
new file mode 100644
index 0000000..f1f453d
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
@@ -0,0 +1,480 @@
+/*
+ * 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.common.material
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.isSpecified
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.collapse
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.dismiss
+import androidx.compose.ui.semantics.expand
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import com.android.credentialmanager.R
+import com.android.credentialmanager.common.material.ModalBottomSheetValue.Expanded
+import com.android.credentialmanager.common.material.ModalBottomSheetValue.HalfExpanded
+import com.android.credentialmanager.common.material.ModalBottomSheetValue.Hidden
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.launch
+import kotlin.math.max
+import kotlin.math.roundToInt
+
+/**
+ * Possible values of [ModalBottomSheetState].
+ */
+enum class ModalBottomSheetValue {
+    /**
+     * The bottom sheet is not visible.
+     */
+    Hidden,
+
+    /**
+     * The bottom sheet is visible at full height.
+     */
+    Expanded,
+
+    /**
+     * The bottom sheet is partially visible at 50% of the screen height. This state is only
+     * enabled if the height of the bottom sheet is more than 50% of the screen height.
+     */
+    HalfExpanded
+}
+
+/**
+ * State of the [ModalBottomSheetLayout] composable.
+ *
+ * @param initialValue The initial value of the state. <b>Must not be set to
+ * [ModalBottomSheetValue.HalfExpanded] if [isSkipHalfExpanded] is set to true.</b>
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param isSkipHalfExpanded Whether the half expanded state, if the sheet is tall enough, should
+ * be skipped. If true, the sheet will always expand to the [Expanded] state and move to the
+ * [Hidden] state when hiding the sheet, either programmatically or by user interaction.
+ * <b>Must not be set to true if the [initialValue] is [ModalBottomSheetValue.HalfExpanded].</b>
+ * If supplied with [ModalBottomSheetValue.HalfExpanded] for the [initialValue], an
+ * [IllegalArgumentException] will be thrown.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+class ModalBottomSheetState(
+    initialValue: ModalBottomSheetValue,
+    animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
+    internal val isSkipHalfExpanded: Boolean,
+    confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
+) : SwipeableState<ModalBottomSheetValue>(
+    initialValue = initialValue,
+    animationSpec = animationSpec,
+    confirmStateChange = confirmStateChange
+) {
+    /**
+     * Whether the bottom sheet is visible.
+     */
+    val isVisible: Boolean
+        get() = currentValue != Hidden
+
+    internal val hasHalfExpandedState: Boolean
+        get() = anchors.values.contains(HalfExpanded)
+
+    constructor(
+        initialValue: ModalBottomSheetValue,
+        animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
+        confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
+    ) : this(initialValue, animationSpec, isSkipHalfExpanded = false, confirmStateChange)
+
+    init {
+        if (isSkipHalfExpanded) {
+            require(initialValue != HalfExpanded) {
+                "The initial value must not be set to HalfExpanded if skipHalfExpanded is set to" +
+                        " true."
+            }
+        }
+    }
+
+    /**
+     * Show the bottom sheet with animation and suspend until it's shown. If the sheet is taller
+     * than 50% of the parent's height, the bottom sheet will be half expanded. Otherwise it will be
+     * fully expanded.
+     *
+     * @throws [CancellationException] if the animation is interrupted
+     */
+    suspend fun show() {
+        val targetValue = when {
+            hasHalfExpandedState -> HalfExpanded
+            else -> Expanded
+        }
+        animateTo(targetValue = targetValue)
+    }
+
+    /**
+     * Half expand the bottom sheet if half expand is enabled with animation and suspend until it
+     * animation is complete or cancelled
+     *
+     * @throws [CancellationException] if the animation is interrupted
+     */
+    internal suspend fun halfExpand() {
+        if (!hasHalfExpandedState) {
+            return
+        }
+        animateTo(HalfExpanded)
+    }
+
+    /**
+     * Fully expand the bottom sheet with animation and suspend until it if fully expanded or
+     * animation has been cancelled.
+     * *
+     * @throws [CancellationException] if the animation is interrupted
+     */
+    internal suspend fun expand() = animateTo(Expanded)
+
+    /**
+     * Hide the bottom sheet with animation and suspend until it if fully hidden or animation has
+     * been cancelled.
+     *
+     * @throws [CancellationException] if the animation is interrupted
+     */
+    suspend fun hide() = animateTo(Hidden)
+
+    internal val nestedScrollConnection = this.PreUpPostDownNestedScrollConnection
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [ModalBottomSheetState].
+         */
+        fun Saver(
+            animationSpec: AnimationSpec<Float>,
+            skipHalfExpanded: Boolean,
+            confirmStateChange: (ModalBottomSheetValue) -> Boolean
+        ): Saver<ModalBottomSheetState, *> = Saver(
+            save = { it.currentValue },
+            restore = {
+                ModalBottomSheetState(
+                    initialValue = it,
+                    animationSpec = animationSpec,
+                    isSkipHalfExpanded = skipHalfExpanded,
+                    confirmStateChange = confirmStateChange
+                )
+            }
+        )
+
+        /**
+         * The default [Saver] implementation for [ModalBottomSheetState].
+         */
+        @Deprecated(
+            message = "Please specify the skipHalfExpanded parameter",
+            replaceWith = ReplaceWith(
+                "ModalBottomSheetState.Saver(" +
+                        "animationSpec = animationSpec," +
+                        "skipHalfExpanded = ," +
+                        "confirmStateChange = confirmStateChange" +
+                        ")"
+            )
+        )
+        fun Saver(
+            animationSpec: AnimationSpec<Float>,
+            confirmStateChange: (ModalBottomSheetValue) -> Boolean
+        ): Saver<ModalBottomSheetState, *> = Saver(
+            animationSpec = animationSpec,
+            skipHalfExpanded = false,
+            confirmStateChange = confirmStateChange
+        )
+    }
+}
+
+/**
+ * Create a [ModalBottomSheetState] and [remember] it.
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param skipHalfExpanded Whether the half expanded state, if the sheet is tall enough, should
+ * be skipped. If true, the sheet will always expand to the [Expanded] state and move to the
+ * [Hidden] state when hiding the sheet, either programmatically or by user interaction.
+ * <b>Must not be set to true if the [initialValue] is [ModalBottomSheetValue.HalfExpanded].</b>
+ * If supplied with [ModalBottomSheetValue.HalfExpanded] for the [initialValue], an
+ * [IllegalArgumentException] will be thrown.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+fun rememberModalBottomSheetState(
+    initialValue: ModalBottomSheetValue,
+    animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
+    skipHalfExpanded: Boolean,
+    confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
+): ModalBottomSheetState {
+    return rememberSaveable(
+        initialValue, animationSpec, skipHalfExpanded, confirmStateChange,
+        saver = ModalBottomSheetState.Saver(
+            animationSpec = animationSpec,
+            skipHalfExpanded = skipHalfExpanded,
+            confirmStateChange = confirmStateChange
+        )
+    ) {
+        ModalBottomSheetState(
+            initialValue = initialValue,
+            animationSpec = animationSpec,
+            isSkipHalfExpanded = skipHalfExpanded,
+            confirmStateChange = confirmStateChange
+        )
+    }
+}
+
+/**
+ * Create a [ModalBottomSheetState] and [remember] it.
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+fun rememberModalBottomSheetState(
+    initialValue: ModalBottomSheetValue,
+    animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
+    confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
+): ModalBottomSheetState = rememberModalBottomSheetState(
+    initialValue = initialValue,
+    animationSpec = animationSpec,
+    skipHalfExpanded = false,
+    confirmStateChange = confirmStateChange
+)
+
+/**
+ * <a href="https://material.io/components/sheets-bottom#modal-bottom-sheet" class="external" target="_blank">Material Design modal bottom sheet</a>.
+ *
+ * Modal bottom sheets present a set of choices while blocking interaction with the rest of the
+ * screen. They are an alternative to inline menus and simple dialogs, providing
+ * additional room for content, iconography, and actions.
+ *
+ * ![Modal bottom sheet image](https://developer.android.com/images/reference/androidx/compose/material/modal-bottom-sheet.png)
+ *
+ * A simple example of a modal bottom sheet looks like this:
+ *
+ * @sample androidx.compose.material.samples.ModalBottomSheetSample
+ *
+ * @param sheetContent The content of the bottom sheet.
+ * @param modifier Optional [Modifier] for the entire component.
+ * @param sheetState The state of the bottom sheet.
+ * @param sheetShape The shape of the bottom sheet.
+ * @param sheetElevation The elevation of the bottom sheet.
+ * @param sheetBackgroundColor The background color of the bottom sheet.
+ * @param sheetContentColor The preferred content color provided by the bottom sheet to its
+ * children. Defaults to the matching content color for [sheetBackgroundColor], or if that is not
+ * a color from the theme, this will keep the same content color set above the bottom sheet.
+ * @param scrimColor The color of the scrim that is applied to the rest of the screen when the
+ * bottom sheet is visible. If the color passed is [Color.Unspecified], then a scrim will no
+ * longer be applied and the bottom sheet will not block interaction with the rest of the screen
+ * when visible.
+ * @param content The content of rest of the screen.
+ */
+@Composable
+fun ModalBottomSheetLayout(
+    sheetContent: @Composable ColumnScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    sheetState: ModalBottomSheetState =
+        rememberModalBottomSheetState(Hidden),
+    sheetShape: Shape = MaterialTheme.shapes.large,
+    sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
+    sheetBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+    sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
+    scrimColor: Color = ModalBottomSheetDefaults.scrimColor,
+    content: @Composable () -> Unit
+) {
+    val scope = rememberCoroutineScope()
+    BoxWithConstraints(modifier) {
+        val fullHeight = constraints.maxHeight.toFloat()
+        val sheetHeightState = remember { mutableStateOf<Float?>(null) }
+        Box(Modifier.fillMaxSize()) {
+            content()
+            Scrim(
+                color = scrimColor,
+                onDismiss = {
+                    if (sheetState.confirmStateChange(Hidden)) {
+                        scope.launch { sheetState.hide() }
+                    }
+                },
+                visible = sheetState.targetValue != Hidden
+            )
+        }
+        Surface(
+            Modifier
+                .fillMaxWidth()
+                .nestedScroll(sheetState.nestedScrollConnection)
+                .offset {
+                    val y = if (sheetState.anchors.isEmpty()) {
+                        // if we don't know our anchors yet, render the sheet as hidden
+                        fullHeight.roundToInt()
+                    } else {
+                        // if we do know our anchors, respect them
+                        sheetState.offset.value.roundToInt()
+                    }
+                    IntOffset(0, y)
+                }
+                .bottomSheetSwipeable(sheetState, fullHeight, sheetHeightState)
+                .onGloballyPositioned {
+                    sheetHeightState.value = it.size.height.toFloat()
+                }
+                .semantics {
+                    if (sheetState.isVisible) {
+                        dismiss {
+                            if (sheetState.confirmStateChange(Hidden)) {
+                                scope.launch { sheetState.hide() }
+                            }
+                            true
+                        }
+                        if (sheetState.currentValue == HalfExpanded) {
+                            expand {
+                                if (sheetState.confirmStateChange(Expanded)) {
+                                    scope.launch { sheetState.expand() }
+                                }
+                                true
+                            }
+                        } else if (sheetState.hasHalfExpandedState) {
+                            collapse {
+                                if (sheetState.confirmStateChange(HalfExpanded)) {
+                                    scope.launch { sheetState.halfExpand() }
+                                }
+                                true
+                            }
+                        }
+                    }
+                },
+            shape = sheetShape,
+            shadowElevation = sheetElevation,
+            color = sheetBackgroundColor,
+            contentColor = sheetContentColor
+        ) {
+            Column(content = sheetContent)
+        }
+    }
+}
+
+@Suppress("ModifierInspectorInfo")
+private fun Modifier.bottomSheetSwipeable(
+    sheetState: ModalBottomSheetState,
+    fullHeight: Float,
+    sheetHeightState: State<Float?>
+): Modifier {
+    val sheetHeight = sheetHeightState.value
+    val modifier = if (sheetHeight != null) {
+        val anchors = if (sheetHeight < fullHeight / 2 || sheetState.isSkipHalfExpanded) {
+            mapOf(
+                fullHeight to Hidden,
+                fullHeight - sheetHeight to Expanded
+            )
+        } else {
+            mapOf(
+                fullHeight to Hidden,
+                fullHeight / 2 to HalfExpanded,
+                max(0f, fullHeight - sheetHeight) to Expanded
+            )
+        }
+        Modifier.swipeable(
+            state = sheetState,
+            anchors = anchors,
+            orientation = Orientation.Vertical,
+            enabled = sheetState.currentValue != Hidden,
+            resistance = null
+        )
+    } else {
+        Modifier
+    }
+
+    return this.then(modifier)
+}
+
+@Composable
+private fun Scrim(
+    color: Color,
+    onDismiss: () -> Unit,
+    visible: Boolean
+) {
+    if (color.isSpecified) {
+        val alpha by animateFloatAsState(
+            targetValue = if (visible) 1f else 0f,
+            animationSpec = TweenSpec()
+        )
+        LocalConfiguration.current
+        val resources = LocalContext.current.resources
+        val closeSheet = resources.getString(R.string.close_sheet)
+        val dismissModifier = if (visible) {
+            Modifier
+                .pointerInput(onDismiss) { detectTapGestures { onDismiss() } }
+                .semantics(mergeDescendants = true) {
+                    contentDescription = closeSheet
+                    onClick { onDismiss(); true }
+                }
+        } else {
+            Modifier
+        }
+
+        Canvas(
+            Modifier
+                .fillMaxSize()
+                .then(dismissModifier)
+        ) {
+            drawRect(color = color, alpha = alpha)
+        }
+    }
+}
+
+/**
+ * Contains useful Defaults for [ModalBottomSheetLayout].
+ */
+object ModalBottomSheetDefaults {
+
+    /**
+     * The default elevation used by [ModalBottomSheetLayout].
+     */
+    val Elevation = 16.dp
+
+    /**
+     * The default scrim color used by [ModalBottomSheetLayout].
+     */
+    val scrimColor: Color
+        @Composable
+        get() = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.32f)
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/Swipeable.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/Swipeable.kt
new file mode 100644
index 0000000..3e2de83
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/Swipeable.kt
@@ -0,0 +1,875 @@
+/*
+ * 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.common.material
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.foundation.gestures.DraggableState
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.lerp
+import com.android.credentialmanager.common.material.SwipeableDefaults.AnimationSpec
+import com.android.credentialmanager.common.material.SwipeableDefaults.StandardResistanceFactor
+import com.android.credentialmanager.common.material.SwipeableDefaults.VelocityThreshold
+import com.android.credentialmanager.common.material.SwipeableDefaults.resistanceConfig
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.launch
+import kotlin.math.PI
+import kotlin.math.abs
+import kotlin.math.sign
+import kotlin.math.sin
+
+/**
+ * State of the [swipeable] modifier.
+ *
+ * This contains necessary information about any ongoing swipe or animation and provides methods
+ * to change the state either immediately or by starting an animation. To create and remember a
+ * [SwipeableState] with the default animation clock, use [rememberSwipeableState].
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Stable
+open class SwipeableState<T>(
+    initialValue: T,
+    internal val animationSpec: AnimationSpec<Float> = AnimationSpec,
+    internal val confirmStateChange: (newValue: T) -> Boolean = { true }
+) {
+    /**
+     * The current value of the state.
+     *
+     * If no swipe or animation is in progress, this corresponds to the anchor at which the
+     * [swipeable] is currently settled. If a swipe or animation is in progress, this corresponds
+     * the last anchor at which the [swipeable] was settled before the swipe or animation started.
+     */
+    var currentValue: T by mutableStateOf(initialValue)
+        private set
+
+    /**
+     * Whether the state is currently animating.
+     */
+    var isAnimationRunning: Boolean by mutableStateOf(false)
+        private set
+
+    /**
+     * The current position (in pixels) of the [swipeable].
+     *
+     * You should use this state to offset your content accordingly. The recommended way is to
+     * use `Modifier.offsetPx`. This includes the resistance by default, if resistance is enabled.
+     */
+    val offset: State<Float> get() = offsetState
+
+    /**
+     * The amount by which the [swipeable] has been swiped past its bounds.
+     */
+    val overflow: State<Float> get() = overflowState
+
+    // Use `Float.NaN` as a placeholder while the state is uninitialised.
+    private val offsetState = mutableStateOf(0f)
+    private val overflowState = mutableStateOf(0f)
+
+    // the source of truth for the "real"(non ui) position
+    // basically position in bounds + overflow
+    private val absoluteOffset = mutableStateOf(0f)
+
+    // current animation target, if animating, otherwise null
+    private val animationTarget = mutableStateOf<Float?>(null)
+
+    internal var anchors by mutableStateOf(emptyMap<Float, T>())
+
+    private val latestNonEmptyAnchorsFlow: Flow<Map<Float, T>> =
+        snapshotFlow { anchors }
+            .filter { it.isNotEmpty() }
+            .take(1)
+
+    internal var minBound = Float.NEGATIVE_INFINITY
+    internal var maxBound = Float.POSITIVE_INFINITY
+
+    internal fun ensureInit(newAnchors: Map<Float, T>) {
+        if (anchors.isEmpty()) {
+            // need to do initial synchronization synchronously :(
+            val initialOffset = newAnchors.getOffset(currentValue)
+            requireNotNull(initialOffset) {
+                "The initial value must have an associated anchor."
+            }
+            offsetState.value = initialOffset
+            absoluteOffset.value = initialOffset
+        }
+    }
+
+    internal suspend fun processNewAnchors(
+        oldAnchors: Map<Float, T>,
+        newAnchors: Map<Float, T>
+    ) {
+        if (oldAnchors.isEmpty()) {
+            // If this is the first time that we receive anchors, then we need to initialise
+            // the state so we snap to the offset associated to the initial value.
+            minBound = newAnchors.keys.minOrNull()!!
+            maxBound = newAnchors.keys.maxOrNull()!!
+            val initialOffset = newAnchors.getOffset(currentValue)
+            requireNotNull(initialOffset) {
+                "The initial value must have an associated anchor."
+            }
+            snapInternalToOffset(initialOffset)
+        } else if (newAnchors != oldAnchors) {
+            // If we have received new anchors, then the offset of the current value might
+            // have changed, so we need to animate to the new offset. If the current value
+            // has been removed from the anchors then we animate to the closest anchor
+            // instead. Note that this stops any ongoing animation.
+            minBound = Float.NEGATIVE_INFINITY
+            maxBound = Float.POSITIVE_INFINITY
+            val animationTargetValue = animationTarget.value
+            // if we're in the animation already, let's find it a new home
+            val targetOffset = if (animationTargetValue != null) {
+                // first, try to map old state to the new state
+                val oldState = oldAnchors[animationTargetValue]
+                val newState = newAnchors.getOffset(oldState)
+                // return new state if exists, or find the closes one among new anchors
+                newState ?: newAnchors.keys.minByOrNull { abs(it - animationTargetValue) }!!
+            } else {
+                // we're not animating, proceed by finding the new anchors for an old value
+                val actualOldValue = oldAnchors[offset.value]
+                val value = if (actualOldValue == currentValue) currentValue else actualOldValue
+                newAnchors.getOffset(value) ?: newAnchors
+                    .keys.minByOrNull { abs(it - offset.value) }!!
+            }
+            try {
+                animateInternalToOffset(targetOffset, animationSpec)
+            } catch (c: CancellationException) {
+                // If the animation was interrupted for any reason, snap as a last resort.
+                snapInternalToOffset(targetOffset)
+            } finally {
+                currentValue = newAnchors.getValue(targetOffset)
+                minBound = newAnchors.keys.minOrNull()!!
+                maxBound = newAnchors.keys.maxOrNull()!!
+            }
+        }
+    }
+
+    internal var thresholds: (Float, Float) -> Float by mutableStateOf({ _, _ -> 0f })
+
+    internal var velocityThreshold by mutableStateOf(0f)
+
+    internal var resistance: ResistanceConfig? by mutableStateOf(null)
+
+    internal val draggableState = DraggableState {
+        val newAbsolute = absoluteOffset.value + it
+        val clamped = newAbsolute.coerceIn(minBound, maxBound)
+        val overflow = newAbsolute - clamped
+        val resistanceDelta = resistance?.computeResistance(overflow) ?: 0f
+        offsetState.value = clamped + resistanceDelta
+        overflowState.value = overflow
+        absoluteOffset.value = newAbsolute
+    }
+
+    private suspend fun snapInternalToOffset(target: Float) {
+        draggableState.drag {
+            dragBy(target - absoluteOffset.value)
+        }
+    }
+
+    private suspend fun animateInternalToOffset(target: Float, spec: AnimationSpec<Float>) {
+        draggableState.drag {
+            var prevValue = absoluteOffset.value
+            animationTarget.value = target
+            isAnimationRunning = true
+            try {
+                Animatable(prevValue).animateTo(target, spec) {
+                    dragBy(this.value - prevValue)
+                    prevValue = this.value
+                }
+            } finally {
+                animationTarget.value = null
+                isAnimationRunning = false
+            }
+        }
+    }
+
+    /**
+     * The target value of the state.
+     *
+     * If a swipe is in progress, this is the value that the [swipeable] would animate to if the
+     * swipe finished. If an animation is running, this is the target value of that animation.
+     * Finally, if no swipe or animation is in progress, this is the same as the [currentValue].
+     */
+    val targetValue: T
+        get() {
+            // TODO(calintat): Track current velocity (b/149549482) and use that here.
+            val target = animationTarget.value ?: computeTarget(
+                offset = offset.value,
+                lastValue = anchors.getOffset(currentValue) ?: offset.value,
+                anchors = anchors.keys,
+                thresholds = thresholds,
+                velocity = 0f,
+                velocityThreshold = Float.POSITIVE_INFINITY
+            )
+            return anchors[target] ?: currentValue
+        }
+
+    /**
+     * Information about the ongoing swipe or animation, if any. See [SwipeProgress] for details.
+     *
+     * If no swipe or animation is in progress, this returns `SwipeProgress(value, value, 1f)`.
+     */
+    val progress: SwipeProgress<T>
+        get() {
+            val bounds = findBounds(offset.value, anchors.keys)
+            val from: T
+            val to: T
+            val fraction: Float
+            when (bounds.size) {
+                0 -> {
+                    from = currentValue
+                    to = currentValue
+                    fraction = 1f
+                }
+                1 -> {
+                    from = anchors.getValue(bounds[0])
+                    to = anchors.getValue(bounds[0])
+                    fraction = 1f
+                }
+                else -> {
+                    val (a, b) =
+                        if (direction > 0f) {
+                            bounds[0] to bounds[1]
+                        } else {
+                            bounds[1] to bounds[0]
+                        }
+                    from = anchors.getValue(a)
+                    to = anchors.getValue(b)
+                    fraction = (offset.value - a) / (b - a)
+                }
+            }
+            return SwipeProgress(from, to, fraction)
+        }
+
+    /**
+     * The direction in which the [swipeable] is moving, relative to the current [currentValue].
+     *
+     * This will be either 1f if it is is moving from left to right or top to bottom, -1f if it is
+     * moving from right to left or bottom to top, or 0f if no swipe or animation is in progress.
+     */
+    val direction: Float
+        get() = anchors.getOffset(currentValue)?.let { sign(offset.value - it) } ?: 0f
+
+    /**
+     * Set the state without any animation and suspend until it's set
+     *
+     * @param targetValue The new target value to set [currentValue] to.
+     */
+    suspend fun snapTo(targetValue: T) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            val targetOffset = anchors.getOffset(targetValue)
+            requireNotNull(targetOffset) {
+                "The target value must have an associated anchor."
+            }
+            snapInternalToOffset(targetOffset)
+            currentValue = targetValue
+        }
+    }
+
+    /**
+     * Set the state to the target value by starting an animation.
+     *
+     * @param targetValue The new value to animate to.
+     * @param anim The animation that will be used to animate to the new value.
+     */
+    suspend fun animateTo(targetValue: T, anim: AnimationSpec<Float> = animationSpec) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            try {
+                val targetOffset = anchors.getOffset(targetValue)
+                requireNotNull(targetOffset) {
+                    "The target value must have an associated anchor."
+                }
+                animateInternalToOffset(targetOffset, anim)
+            } finally {
+                val endOffset = absoluteOffset.value
+                val endValue = anchors
+                    // fighting rounding error once again, anchor should be as close as 0.5 pixels
+                    .filterKeys { anchorOffset -> abs(anchorOffset - endOffset) < 0.5f }
+                    .values.firstOrNull() ?: currentValue
+                currentValue = endValue
+            }
+        }
+    }
+
+    /**
+     * Perform fling with settling to one of the anchors which is determined by the given
+     * [velocity]. Fling with settling [swipeable] will always consume all the velocity provided
+     * since it will settle at the anchor.
+     *
+     * In general cases, [swipeable] flings by itself when being swiped. This method is to be
+     * used for nested scroll logic that wraps the [swipeable]. In nested scroll developer may
+     * want to trigger settling fling when the child scroll container reaches the bound.
+     *
+     * @param velocity velocity to fling and settle with
+     *
+     * @return the reason fling ended
+     */
+    suspend fun performFling(velocity: Float) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            val lastAnchor = anchors.getOffset(currentValue)!!
+            val targetValue = computeTarget(
+                offset = offset.value,
+                lastValue = lastAnchor,
+                anchors = anchors.keys,
+                thresholds = thresholds,
+                velocity = velocity,
+                velocityThreshold = velocityThreshold
+            )
+            val targetState = anchors[targetValue]
+            if (targetState != null && confirmStateChange(targetState)) animateTo(targetState)
+            // If the user vetoed the state change, rollback to the previous state.
+            else animateInternalToOffset(lastAnchor, animationSpec)
+        }
+    }
+
+    /**
+     * Force [swipeable] to consume drag delta provided from outside of the regular [swipeable]
+     * gesture flow.
+     *
+     * Note: This method performs generic drag and it won't settle to any particular anchor, *
+     * leaving swipeable in between anchors. When done dragging, [performFling] must be
+     * called as well to ensure swipeable will settle at the anchor.
+     *
+     * In general cases, [swipeable] drags by itself when being swiped. This method is to be
+     * used for nested scroll logic that wraps the [swipeable]. In nested scroll developer may
+     * want to force drag when the child scroll container reaches the bound.
+     *
+     * @param delta delta in pixels to drag by
+     *
+     * @return the amount of [delta] consumed
+     */
+    fun performDrag(delta: Float): Float {
+        val potentiallyConsumed = absoluteOffset.value + delta
+        val clamped = potentiallyConsumed.coerceIn(minBound, maxBound)
+        val deltaToConsume = clamped - absoluteOffset.value
+        if (abs(deltaToConsume) > 0) {
+            draggableState.dispatchRawDelta(deltaToConsume)
+        }
+        return deltaToConsume
+    }
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [SwipeableState].
+         */
+        fun <T : Any> Saver(
+            animationSpec: AnimationSpec<Float>,
+            confirmStateChange: (T) -> Boolean
+        ) = Saver<SwipeableState<T>, T>(
+            save = { it.currentValue },
+            restore = { SwipeableState(it, animationSpec, confirmStateChange) }
+        )
+    }
+}
+
+/**
+ * Collects information about the ongoing swipe or animation in [swipeable].
+ *
+ * To access this information, use [SwipeableState.progress].
+ *
+ * @param from The state corresponding to the anchor we are moving away from.
+ * @param to The state corresponding to the anchor we are moving towards.
+ * @param fraction The fraction that the current position represents between [from] and [to].
+ * Must be between `0` and `1`.
+ */
+@Immutable
+class SwipeProgress<T>(
+    val from: T,
+    val to: T,
+    /*@FloatRange(from = 0.0, to = 1.0)*/
+    val fraction: Float
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is SwipeProgress<*>) return false
+
+        if (from != other.from) return false
+        if (to != other.to) return false
+        if (fraction != other.fraction) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = from?.hashCode() ?: 0
+        result = 31 * result + (to?.hashCode() ?: 0)
+        result = 31 * result + fraction.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "SwipeProgress(from=$from, to=$to, fraction=$fraction)"
+    }
+}
+
+/**
+ * Create and [remember] a [SwipeableState] with the default animation clock.
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+fun <T : Any> rememberSwipeableState(
+    initialValue: T,
+    animationSpec: AnimationSpec<Float> = AnimationSpec,
+    confirmStateChange: (newValue: T) -> Boolean = { true }
+): SwipeableState<T> {
+    return rememberSaveable(
+        saver = SwipeableState.Saver(
+            animationSpec = animationSpec,
+            confirmStateChange = confirmStateChange
+        )
+    ) {
+        SwipeableState(
+            initialValue = initialValue,
+            animationSpec = animationSpec,
+            confirmStateChange = confirmStateChange
+        )
+    }
+}
+
+/**
+ * Create and [remember] a [SwipeableState] which is kept in sync with another state, i.e.:
+ *  1. Whenever the [value] changes, the [SwipeableState] will be animated to that new value.
+ *  2. Whenever the value of the [SwipeableState] changes (e.g. after a swipe), the owner of the
+ *  [value] will be notified to update their state to the new value of the [SwipeableState] by
+ *  invoking [onValueChange]. If the owner does not update their state to the provided value for
+ *  some reason, then the [SwipeableState] will perform a rollback to the previous, correct value.
+ */
+@Composable
+internal fun <T : Any> rememberSwipeableStateFor(
+    value: T,
+    onValueChange: (T) -> Unit,
+    animationSpec: AnimationSpec<Float> = AnimationSpec
+): SwipeableState<T> {
+    val swipeableState = remember {
+        SwipeableState(
+            initialValue = value,
+            animationSpec = animationSpec,
+            confirmStateChange = { true }
+        )
+    }
+    val forceAnimationCheck = remember { mutableStateOf(false) }
+    LaunchedEffect(value, forceAnimationCheck.value) {
+        if (value != swipeableState.currentValue) {
+            swipeableState.animateTo(value)
+        }
+    }
+    DisposableEffect(swipeableState.currentValue) {
+        if (value != swipeableState.currentValue) {
+            onValueChange(swipeableState.currentValue)
+            forceAnimationCheck.value = !forceAnimationCheck.value
+        }
+        onDispose { }
+    }
+    return swipeableState
+}
+
+/**
+ * Enable swipe gestures between a set of predefined states.
+ *
+ * To use this, you must provide a map of anchors (in pixels) to states (of type [T]).
+ * Note that this map cannot be empty and cannot have two anchors mapped to the same state.
+ *
+ * When a swipe is detected, the offset of the [SwipeableState] will be updated with the swipe
+ * delta. You should use this offset to move your content accordingly (see `Modifier.offsetPx`).
+ * When the swipe ends, the offset will be animated to one of the anchors and when that anchor is
+ * reached, the value of the [SwipeableState] will also be updated to the state corresponding to
+ * the new anchor. The target anchor is calculated based on the provided positional [thresholds].
+ *
+ * Swiping is constrained between the minimum and maximum anchors. If the user attempts to swipe
+ * past these bounds, a resistance effect will be applied by default. The amount of resistance at
+ * each edge is specified by the [resistance] config. To disable all resistance, set it to `null`.
+ *
+ * For an example of a [swipeable] with three states, see:
+ *
+ * @sample androidx.compose.material.samples.SwipeableSample
+ *
+ * @param T The type of the state.
+ * @param state The state of the [swipeable].
+ * @param anchors Pairs of anchors and states, used to map anchors to states and vice versa.
+ * @param thresholds Specifies where the thresholds between the states are. The thresholds will be
+ * used to determine which state to animate to when swiping stops. This is represented as a lambda
+ * that takes two states and returns the threshold between them in the form of a [ThresholdConfig].
+ * Note that the order of the states corresponds to the swipe direction.
+ * @param orientation The orientation in which the [swipeable] can be swiped.
+ * @param enabled Whether this [swipeable] is enabled and should react to the user's input.
+ * @param reverseDirection Whether to reverse the direction of the swipe, so a top to bottom
+ * swipe will behave like bottom to top, and a left to right swipe will behave like right to left.
+ * @param interactionSource Optional [MutableInteractionSource] that will passed on to
+ * the internal [Modifier.draggable].
+ * @param resistance Controls how much resistance will be applied when swiping past the bounds.
+ * @param velocityThreshold The threshold (in dp per second) that the end velocity has to exceed
+ * in order to animate to the next state, even if the positional [thresholds] have not been reached.
+ */
+fun <T> Modifier.swipeable(
+    state: SwipeableState<T>,
+    anchors: Map<Float, T>,
+    orientation: Orientation,
+    enabled: Boolean = true,
+    reverseDirection: Boolean = false,
+    interactionSource: MutableInteractionSource? = null,
+    thresholds: (from: T, to: T) -> ThresholdConfig = { _, _ -> FixedThreshold(56.dp) },
+    resistance: ResistanceConfig? = resistanceConfig(anchors.keys),
+    velocityThreshold: Dp = VelocityThreshold
+) = composed(
+    inspectorInfo = debugInspectorInfo {
+        name = "swipeable"
+        properties["state"] = state
+        properties["anchors"] = anchors
+        properties["orientation"] = orientation
+        properties["enabled"] = enabled
+        properties["reverseDirection"] = reverseDirection
+        properties["interactionSource"] = interactionSource
+        properties["thresholds"] = thresholds
+        properties["resistance"] = resistance
+        properties["velocityThreshold"] = velocityThreshold
+    }
+) {
+    require(anchors.isNotEmpty()) {
+        "You must have at least one anchor."
+    }
+    require(anchors.values.distinct().count() == anchors.size) {
+        "You cannot have two anchors mapped to the same state."
+    }
+    val density = LocalDensity.current
+    state.ensureInit(anchors)
+    LaunchedEffect(anchors, state) {
+        val oldAnchors = state.anchors
+        state.anchors = anchors
+        state.resistance = resistance
+        state.thresholds = { a, b ->
+            val from = anchors.getValue(a)
+            val to = anchors.getValue(b)
+            with(thresholds(from, to)) { density.computeThreshold(a, b) }
+        }
+        with(density) {
+            state.velocityThreshold = velocityThreshold.toPx()
+        }
+        state.processNewAnchors(oldAnchors, anchors)
+    }
+
+    Modifier.draggable(
+        orientation = orientation,
+        enabled = enabled,
+        reverseDirection = reverseDirection,
+        interactionSource = interactionSource,
+        startDragImmediately = state.isAnimationRunning,
+        onDragStopped = { velocity -> launch { state.performFling(velocity) } },
+        state = state.draggableState
+    )
+}
+
+/**
+ * Interface to compute a threshold between two anchors/states in a [swipeable].
+ *
+ * To define a [ThresholdConfig], consider using [FixedThreshold] and [FractionalThreshold].
+ */
+@Stable
+interface ThresholdConfig {
+    /**
+     * Compute the value of the threshold (in pixels), once the values of the anchors are known.
+     */
+    fun Density.computeThreshold(fromValue: Float, toValue: Float): Float
+}
+
+/**
+ * A fixed threshold will be at an [offset] away from the first anchor.
+ *
+ * @param offset The offset (in dp) that the threshold will be at.
+ */
+@Immutable
+data class FixedThreshold(private val offset: Dp) : ThresholdConfig {
+    override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
+        return fromValue + offset.toPx() * sign(toValue - fromValue)
+    }
+}
+
+/**
+ * A fractional threshold will be at a [fraction] of the way between the two anchors.
+ *
+ * @param fraction The fraction (between 0 and 1) that the threshold will be at.
+ */
+@Immutable
+data class FractionalThreshold(
+    /*@FloatRange(from = 0.0, to = 1.0)*/
+    private val fraction: Float
+) : ThresholdConfig {
+    override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
+        return lerp(fromValue, toValue, fraction)
+    }
+}
+
+/**
+ * Specifies how resistance is calculated in [swipeable].
+ *
+ * There are two things needed to calculate resistance: the resistance basis determines how much
+ * overflow will be consumed to achieve maximum resistance, and the resistance factor determines
+ * the amount of resistance (the larger the resistance factor, the stronger the resistance).
+ *
+ * The resistance basis is usually either the size of the component which [swipeable] is applied
+ * to, or the distance between the minimum and maximum anchors. For a constructor in which the
+ * resistance basis defaults to the latter, consider using [resistanceConfig].
+ *
+ * You may specify different resistance factors for each bound. Consider using one of the default
+ * resistance factors in [SwipeableDefaults]: `StandardResistanceFactor` to convey that the user
+ * has run out of things to see, and `StiffResistanceFactor` to convey that the user cannot swipe
+ * this right now. Also, you can set either factor to 0 to disable resistance at that bound.
+ *
+ * @param basis Specifies the maximum amount of overflow that will be consumed. Must be positive.
+ * @param factorAtMin The factor by which to scale the resistance at the minimum bound.
+ * Must not be negative.
+ * @param factorAtMax The factor by which to scale the resistance at the maximum bound.
+ * Must not be negative.
+ */
+@Immutable
+class ResistanceConfig(
+    /*@FloatRange(from = 0.0, fromInclusive = false)*/
+    val basis: Float,
+    /*@FloatRange(from = 0.0)*/
+    val factorAtMin: Float = StandardResistanceFactor,
+    /*@FloatRange(from = 0.0)*/
+    val factorAtMax: Float = StandardResistanceFactor
+) {
+    fun computeResistance(overflow: Float): Float {
+        val factor = if (overflow < 0) factorAtMin else factorAtMax
+        if (factor == 0f) return 0f
+        val progress = (overflow / basis).coerceIn(-1f, 1f)
+        return basis / factor * sin(progress * PI.toFloat() / 2)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ResistanceConfig) return false
+
+        if (basis != other.basis) return false
+        if (factorAtMin != other.factorAtMin) return false
+        if (factorAtMax != other.factorAtMax) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = basis.hashCode()
+        result = 31 * result + factorAtMin.hashCode()
+        result = 31 * result + factorAtMax.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "ResistanceConfig(basis=$basis, factorAtMin=$factorAtMin, factorAtMax=$factorAtMax)"
+    }
+}
+
+/**
+ *  Given an offset x and a set of anchors, return a list of anchors:
+ *   1. [ ] if the set of anchors is empty,
+ *   2. [ x' ] if x is equal to one of the anchors, accounting for a small rounding error, where x'
+ *      is x rounded to the exact value of the matching anchor,
+ *   3. [ min ] if min is the minimum anchor and x < min,
+ *   4. [ max ] if max is the maximum anchor and x > max, or
+ *   5. [ a , b ] if a and b are anchors such that a < x < b and b - a is minimal.
+ */
+private fun findBounds(
+    offset: Float,
+    anchors: Set<Float>
+): List<Float> {
+    // Find the anchors the target lies between with a little bit of rounding error.
+    val a = anchors.filter { it <= offset + 0.001 }.maxOrNull()
+    val b = anchors.filter { it >= offset - 0.001 }.minOrNull()
+
+    return when {
+        a == null ->
+            // case 1 or 3
+            listOfNotNull(b)
+        b == null ->
+            // case 4
+            listOf(a)
+        a == b ->
+            // case 2
+            // Can't return offset itself here since it might not be exactly equal
+            // to the anchor, despite being considered an exact match.
+            listOf(a)
+        else ->
+            // case 5
+            listOf(a, b)
+    }
+}
+
+private fun computeTarget(
+    offset: Float,
+    lastValue: Float,
+    anchors: Set<Float>,
+    thresholds: (Float, Float) -> Float,
+    velocity: Float,
+    velocityThreshold: Float
+): Float {
+    val bounds = findBounds(offset, anchors)
+    return when (bounds.size) {
+        0 -> lastValue
+        1 -> bounds[0]
+        else -> {
+            val lower = bounds[0]
+            val upper = bounds[1]
+            if (lastValue <= offset) {
+                // Swiping from lower to upper (positive).
+                if (velocity >= velocityThreshold) {
+                    return upper
+                } else {
+                    val threshold = thresholds(lower, upper)
+                    if (offset < threshold) lower else upper
+                }
+            } else {
+                // Swiping from upper to lower (negative).
+                if (velocity <= -velocityThreshold) {
+                    return lower
+                } else {
+                    val threshold = thresholds(upper, lower)
+                    if (offset > threshold) upper else lower
+                }
+            }
+        }
+    }
+}
+
+private fun <T> Map<Float, T>.getOffset(state: T): Float? {
+    return entries.firstOrNull { it.value == state }?.key
+}
+
+/**
+ * Contains useful defaults for [swipeable] and [SwipeableState].
+ */
+object SwipeableDefaults {
+    /**
+     * The default animation used by [SwipeableState].
+     */
+    val AnimationSpec = SpringSpec<Float>()
+
+    /**
+     * The default velocity threshold (1.8 dp per millisecond) used by [swipeable].
+     */
+    val VelocityThreshold = 125.dp
+
+    /**
+     * A stiff resistance factor which indicates that swiping isn't available right now.
+     */
+    const val StiffResistanceFactor = 20f
+
+    /**
+     * A standard resistance factor which indicates that the user has run out of things to see.
+     */
+    const val StandardResistanceFactor = 10f
+
+    /**
+     * The default resistance config used by [swipeable].
+     *
+     * This returns `null` if there is one anchor. If there are at least two anchors, it returns
+     * a [ResistanceConfig] with the resistance basis equal to the distance between the two bounds.
+     */
+    fun resistanceConfig(
+        anchors: Set<Float>,
+        factorAtMin: Float = StandardResistanceFactor,
+        factorAtMax: Float = StandardResistanceFactor
+    ): ResistanceConfig? {
+        return if (anchors.size <= 1) {
+            null
+        } else {
+            val basis = anchors.maxOrNull()!! - anchors.minOrNull()!!
+            ResistanceConfig(basis, factorAtMin, factorAtMax)
+        }
+    }
+}
+
+// temp default nested scroll connection for swipeables which desire as an opt in
+// revisit in b/174756744 as all types will have their own specific connection probably
+internal val <T> SwipeableState<T>.PreUpPostDownNestedScrollConnection: NestedScrollConnection
+    get() = object : NestedScrollConnection {
+        override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+            val delta = available.toFloat()
+            return if (delta < 0 && source == NestedScrollSource.Drag) {
+                performDrag(delta).toOffset()
+            } else {
+                Offset.Zero
+            }
+        }
+
+        override fun onPostScroll(
+            consumed: Offset,
+            available: Offset,
+            source: NestedScrollSource
+        ): Offset {
+            return if (source == NestedScrollSource.Drag) {
+                performDrag(available.toFloat()).toOffset()
+            } else {
+                Offset.Zero
+            }
+        }
+
+        override suspend fun onPreFling(available: Velocity): Velocity {
+            val toFling = Offset(available.x, available.y).toFloat()
+            return if (toFling < 0 && offset.value > minBound) {
+                performFling(velocity = toFling)
+                // since we go to the anchor with tween settling, consume all for the best UX
+                available
+            } else {
+                Velocity.Zero
+            }
+        }
+
+        override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+            performFling(velocity = Offset(available.x, available.y).toFloat())
+            return available
+        }
+
+        private fun Float.toOffset(): Offset = Offset(0f, this)
+
+        private fun Offset.toFloat(): Float = this.y
+    }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
new file mode 100644
index 0000000..80764b5
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.common.ui
+
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+
+@Composable
+fun CancelButton(text: String, onClick: () -> Unit) {
+    TextButton(
+        onClick = onClick,
+        colors = ButtonDefaults.textButtonColors(
+            contentColor = MaterialTheme.colorScheme.primary,
+        )
+    ) {
+        Text(text = text)
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
new file mode 100644
index 0000000..aaabce3
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.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.credentialmanager.common.ui
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Shape
+
+/**
+ * By default the card is filled with surfaceVariant color. This container card instead fills the
+ * background color with surface corlor.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ContainerCard(
+    modifier: Modifier = Modifier,
+    shape: Shape = CardDefaults.shape,
+    border: BorderStroke? = null,
+    content: @Composable ColumnScope.() -> Unit,
+) {
+    Card(
+        modifier = modifier,
+        shape = shape,
+        border = border,
+        colors = CardDefaults.cardColors(
+            containerColor = MaterialTheme.colorScheme.surface,
+        ),
+        content = content,
+    )
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
new file mode 100644
index 0000000..d8ee750
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.common.ui
+
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.FilledTonalButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+
+@Composable
+fun ConfirmButton(text: String, onClick: () -> Unit) {
+    FilledTonalButton(
+        onClick = onClick,
+        colors = ButtonDefaults.filledTonalButtonColors(
+            containerColor = MaterialTheme.colorScheme.primaryContainer,
+            contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+        )
+    ) {
+        Text(text = text)
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
new file mode 100644
index 0000000..aefd534
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.common.ui
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SuggestionChip
+import androidx.compose.material3.SuggestionChipDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.android.credentialmanager.ui.theme.EntryShape
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun Entry(
+    onClick: () -> Unit,
+    label: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    icon: @Composable (() -> Unit)? = null,
+) {
+    SuggestionChip(
+        modifier = modifier.fillMaxWidth(),
+        onClick = onClick,
+        shape = EntryShape.FullSmallRoundedCorner,
+        label = label,
+        icon = icon,
+        border = null,
+        colors = SuggestionChipDefaults.suggestionChipColors(
+            containerColor = MaterialTheme.colorScheme.surfaceVariant,
+            labelColor = MaterialTheme.colorScheme.onSurfaceVariant,
+            iconContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+        ),
+    )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun TransparentBackgroundEntry(
+    onClick: () -> Unit,
+    label: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    icon: @Composable (() -> Unit)? = null,
+) {
+    SuggestionChip(
+        modifier = modifier.fillMaxWidth(),
+        onClick = onClick,
+        label = label,
+        icon = icon,
+        border = null,
+        colors = SuggestionChipDefaults.suggestionChipColors(
+            containerColor = Color.Transparent,
+        ),
+    )
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
new file mode 100644
index 0000000..3a66dda
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.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.credentialmanager.common.ui
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+
+@Composable
+fun TextOnSurface(
+    text: String,
+    modifier: Modifier = Modifier,
+    textAlign: TextAlign? = null,
+    style: TextStyle,
+) {
+    TextInternal(
+        text = text,
+        color = MaterialTheme.colorScheme.onSurface,
+        modifier = modifier,
+        textAlign = textAlign,
+        style = style,
+    )
+}
+
+@Composable
+fun TextSecondary(
+    text: String,
+    modifier: Modifier = Modifier,
+    textAlign: TextAlign? = null,
+    style: TextStyle,
+) {
+    TextInternal(
+        text = text,
+        color = MaterialTheme.colorScheme.secondary,
+        modifier = modifier,
+        textAlign = textAlign,
+        style = style,
+    )
+}
+
+@Composable
+fun TextOnSurfaceVariant(
+    text: String,
+    modifier: Modifier = Modifier,
+    textAlign: TextAlign? = null,
+    style: TextStyle,
+) {
+    TextInternal(
+        text = text,
+        color = MaterialTheme.colorScheme.onSurfaceVariant,
+        modifier = modifier,
+        textAlign = textAlign,
+        style = style,
+    )
+}
+
+@Composable
+private fun TextInternal(
+    text: String,
+    color: Color,
+    modifier: Modifier,
+    textAlign: TextAlign?,
+    style: TextStyle,
+) {
+    Text(text = text, color = color, modifier = modifier, textAlign = textAlign, style = style)
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
new file mode 100644
index 0000000..b417b38
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -0,0 +1,933 @@
+@file:OptIn(ExperimentalMaterial3Api::class)
+
+package com.android.credentialmanager.createflow
+
+import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
+import androidx.activity.compose.ManagedActivityResultLauncher
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.IntentSenderRequest
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Divider
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.outlined.NewReleases
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
+import com.android.credentialmanager.R
+import com.android.credentialmanager.common.material.ModalBottomSheetLayout
+import com.android.credentialmanager.common.material.ModalBottomSheetValue
+import com.android.credentialmanager.common.material.rememberModalBottomSheetState
+import com.android.credentialmanager.common.ui.CancelButton
+import com.android.credentialmanager.common.ui.ConfirmButton
+import com.android.credentialmanager.common.ui.Entry
+import com.android.credentialmanager.common.ui.TextOnSurface
+import com.android.credentialmanager.common.ui.TextSecondary
+import com.android.credentialmanager.common.ui.TextOnSurfaceVariant
+import com.android.credentialmanager.common.ui.ContainerCard
+import com.android.credentialmanager.ui.theme.EntryShape
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun CreateCredentialScreen(
+    viewModel: CreateCredentialViewModel,
+    providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
+) {
+    val state = rememberModalBottomSheetState(
+        initialValue = ModalBottomSheetValue.Expanded,
+        skipHalfExpanded = true
+    )
+    ModalBottomSheetLayout(
+        sheetBackgroundColor = MaterialTheme.colorScheme.surface,
+        sheetState = state,
+        sheetContent = {
+            val uiState = viewModel.uiState
+            if (!uiState.hidden) {
+                when (uiState.currentScreenState) {
+                    CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
+                        onConfirm = viewModel::onConfirmIntro,
+                        onCancel = viewModel::onCancel,
+                    )
+                    CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
+                        requestDisplayInfo = uiState.requestDisplayInfo,
+                        enabledProviderList = uiState.enabledProviders,
+                        disabledProviderList = uiState.disabledProviders,
+                        onCancel = viewModel::onCancel,
+                        onOptionSelected = viewModel::onEntrySelectedFromFirstUseScreen,
+                        onDisabledPasswordManagerSelected =
+                        viewModel::onDisabledPasswordManagerSelected,
+                        onRemoteEntrySelected = viewModel::onEntrySelected,
+                    )
+                    CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
+                        requestDisplayInfo = uiState.requestDisplayInfo,
+                        enabledProviderList = uiState.enabledProviders,
+                        providerInfo = uiState.activeEntry?.activeProvider!!,
+                        createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
+                        showActiveEntryOnly = uiState.showActiveEntryOnly,
+                        onOptionSelected = viewModel::onEntrySelected,
+                        onConfirm = viewModel::onConfirmEntrySelected,
+                        onCancel = viewModel::onCancel,
+                        onMoreOptionsSelected = viewModel::onMoreOptionsSelected,
+                    )
+                    CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
+                        requestDisplayInfo = uiState.requestDisplayInfo,
+                        enabledProviderList = uiState.enabledProviders,
+                        disabledProviderList = uiState.disabledProviders,
+                        onBackButtonSelected = viewModel::onBackButtonSelected,
+                        onOptionSelected = viewModel::onEntrySelectedFromMoreOptionScreen,
+                        onDisabledPasswordManagerSelected =
+                        viewModel::onDisabledPasswordManagerSelected,
+                        onRemoteEntrySelected = viewModel::onEntrySelected,
+                    )
+                    CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
+                        providerInfo = uiState.activeEntry?.activeProvider!!,
+                        onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
+                    )
+                    CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
+                        requestDisplayInfo = uiState.requestDisplayInfo,
+                        activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!,
+                        onOptionSelected = viewModel::onEntrySelected,
+                        onConfirm = viewModel::onConfirmEntrySelected,
+                        onCancel = viewModel::onCancel,
+                    )
+                }
+            } else if (uiState.selectedEntry != null && !uiState.providerActivityPending) {
+                viewModel.launchProviderUi(providerActivityLauncher)
+            }
+        },
+        scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
+        sheetShape = EntryShape.TopRoundedCorner,
+    ) {}
+    LaunchedEffect(state.currentValue) {
+        if (state.currentValue == ModalBottomSheetValue.Hidden) {
+            viewModel.onCancel()
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ConfirmationCard(
+    onConfirm: () -> Unit,
+    onCancel: () -> Unit,
+) {
+    ContainerCard() {
+        Column() {
+            Image(
+                painter = painterResource(R.drawable.ic_passkeys_onboarding),
+                contentDescription = null,
+                modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+                    .padding(top = 24.dp, bottom = 12.dp).size(316.dp, 168.dp)
+            )
+            TextOnSurface(
+                text = stringResource(R.string.passkey_creation_intro_title),
+                style = MaterialTheme.typography.titleMedium,
+                modifier = Modifier
+                    .padding(horizontal = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally),
+                textAlign = TextAlign.Center,
+            )
+            Divider(
+                thickness = 16.dp,
+                color = Color.Transparent
+            )
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                verticalAlignment = Alignment.CenterVertically,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                Image(
+                    modifier = Modifier.size(24.dp),
+                    painter = painterResource(R.drawable.ic_passkeys_onboarding_password),
+                    contentDescription = null
+                )
+                TextSecondary(
+                    text = stringResource(R.string.passkey_creation_intro_body_password),
+                    style = MaterialTheme.typography.bodyMedium,
+                    modifier = Modifier.padding(start = 16.dp),
+                )
+            }
+            Divider(
+                thickness = 16.dp,
+                color = Color.Transparent
+            )
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                verticalAlignment = Alignment.CenterVertically,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                Image(
+                    modifier = Modifier.size(24.dp),
+                    painter = painterResource(R.drawable.ic_passkeys_onboarding_fingerprint),
+                    contentDescription = null
+                )
+                TextSecondary(
+                    text = stringResource(R.string.passkey_creation_intro_body_fingerprint),
+                    style = MaterialTheme.typography.bodyMedium,
+                    modifier = Modifier.padding(start = 16.dp),
+                )
+            }
+            Divider(
+                thickness = 16.dp,
+                color = Color.Transparent
+            )
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                verticalAlignment = Alignment.CenterVertically,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                Image(
+                    modifier = Modifier.size(24.dp),
+                    painter = painterResource(R.drawable.ic_passkeys_onboarding_device),
+                    contentDescription = null
+                )
+                TextSecondary(
+                    text = stringResource(R.string.passkey_creation_intro_body_device),
+                    style = MaterialTheme.typography.bodyMedium,
+                    modifier = Modifier.padding(start = 16.dp),
+                )
+            }
+            Divider(
+                thickness = 32.dp,
+                color = Color.Transparent
+            )
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                CancelButton(
+                    stringResource(R.string.string_cancel),
+                    onClick = onCancel
+                )
+                ConfirmButton(
+                    stringResource(R.string.string_continue),
+                    onClick = onConfirm
+                )
+            }
+            Divider(
+                thickness = 18.dp,
+                color = Color.Transparent,
+                modifier = Modifier.padding(bottom = 18.dp)
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ProviderSelectionCard(
+    requestDisplayInfo: RequestDisplayInfo,
+    enabledProviderList: List<EnabledProviderInfo>,
+    disabledProviderList: List<DisabledProviderInfo>?,
+    onOptionSelected: (ActiveEntry) -> Unit,
+    onDisabledPasswordManagerSelected: () -> Unit,
+    onCancel: () -> Unit,
+    onRemoteEntrySelected: (EntryInfo) -> Unit,
+) {
+    ContainerCard() {
+        Column() {
+            Icon(
+                bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(),
+                contentDescription = null,
+                tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+                modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+                    .padding(top = 24.dp, bottom = 16.dp).size(32.dp)
+            )
+            TextOnSurface(
+                text = stringResource(
+                    R.string.choose_provider_title,
+                    when (requestDisplayInfo.type) {
+                        TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_your_passkeys)
+                        TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.save_your_password)
+                        else -> stringResource(R.string.save_your_sign_in_info)
+                    },
+                ),
+                style = MaterialTheme.typography.titleMedium,
+                modifier = Modifier.padding(horizontal = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally),
+                textAlign = TextAlign.Center,
+            )
+            Divider(
+                thickness = 16.dp,
+                color = Color.Transparent
+            )
+            TextSecondary(
+                text = stringResource(R.string.choose_provider_body),
+                style = MaterialTheme.typography.bodyLarge,
+                modifier = Modifier.padding(horizontal = 28.dp),
+            )
+            Divider(
+                thickness = 18.dp,
+                color = Color.Transparent
+            )
+            ContainerCard(
+                shape = MaterialTheme.shapes.medium,
+                modifier = Modifier
+                    .padding(horizontal = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally),
+            ) {
+                LazyColumn(
+                    verticalArrangement = Arrangement.spacedBy(2.dp)
+                ) {
+                    enabledProviderList.forEach { enabledProviderInfo ->
+                        enabledProviderInfo.createOptions.forEach { createOptionInfo ->
+                            item {
+                                MoreOptionsInfoRow(
+                                    providerInfo = enabledProviderInfo,
+                                    createOptionInfo = createOptionInfo,
+                                    onOptionSelected = {
+                                        onOptionSelected(
+                                            ActiveEntry(
+                                                enabledProviderInfo,
+                                                createOptionInfo
+                                            )
+                                        )
+                                    })
+                            }
+                        }
+                    }
+                    if (disabledProviderList != null) {
+                        item {
+                            MoreOptionsDisabledProvidersRow(
+                                disabledProviders = disabledProviderList,
+                                onDisabledPasswordManagerSelected =
+                                onDisabledPasswordManagerSelected,
+                            )
+                        }
+                    }
+                }
+            }
+            // TODO: handle the error situation that if multiple remoteInfos exists
+            enabledProviderList.forEach { enabledProvider ->
+                if (enabledProvider.remoteEntry != null) {
+                    TextButton(
+                        onClick = {
+                            onRemoteEntrySelected(enabledProvider.remoteEntry!!)
+                        },
+                        modifier = Modifier
+                            .padding(horizontal = 24.dp)
+                            .align(alignment = Alignment.CenterHorizontally),
+                        colors = ButtonDefaults.textButtonColors(
+                            contentColor = MaterialTheme.colorScheme.primary,
+                        )
+                    ) {
+                        Text(
+                            text = stringResource(R.string.string_save_to_another_device),
+                            textAlign = TextAlign.Center,
+                        )
+                    }
+                }
+            }
+            Divider(
+                thickness = 24.dp,
+                color = Color.Transparent
+            )
+            Row(
+                horizontalArrangement = Arrangement.Start,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                CancelButton(stringResource(R.string.string_cancel), onCancel)
+            }
+            Divider(
+                thickness = 18.dp,
+                color = Color.Transparent,
+                modifier = Modifier.padding(bottom = 16.dp)
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MoreOptionsSelectionCard(
+    requestDisplayInfo: RequestDisplayInfo,
+    enabledProviderList: List<EnabledProviderInfo>,
+    disabledProviderList: List<DisabledProviderInfo>?,
+    onBackButtonSelected: () -> Unit,
+    onOptionSelected: (ActiveEntry) -> Unit,
+    onDisabledPasswordManagerSelected: () -> Unit,
+    onRemoteEntrySelected: (EntryInfo) -> Unit,
+) {
+    ContainerCard() {
+        Column() {
+            TopAppBar(
+                title = {
+                    TextOnSurface(
+                        text = when (requestDisplayInfo.type) {
+                            TYPE_PUBLIC_KEY_CREDENTIAL ->
+                                stringResource(R.string.create_passkey_in_title)
+                            TYPE_PASSWORD_CREDENTIAL ->
+                                stringResource(R.string.save_password_to_title)
+                            else -> stringResource(R.string.save_sign_in_to_title)
+                        },
+                        style = MaterialTheme.typography.titleMedium,
+                    )
+                },
+                navigationIcon = {
+                    IconButton(onClick = onBackButtonSelected) {
+                        Icon(
+                            Icons.Filled.ArrowBack,
+                            stringResource(R.string.accessibility_back_arrow_button)
+                        )
+                    }
+                },
+                colors = TopAppBarDefaults.smallTopAppBarColors
+                    (containerColor = Color.Transparent),
+            )
+            Divider(
+                thickness = 8.dp,
+                color = Color.Transparent
+            )
+            ContainerCard(
+                shape = MaterialTheme.shapes.medium,
+                modifier = Modifier
+                    .padding(horizontal = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally)
+            ) {
+                LazyColumn(
+                    verticalArrangement = Arrangement.spacedBy(2.dp)
+                ) {
+                    enabledProviderList.forEach { enabledProviderInfo ->
+                        enabledProviderInfo.createOptions.forEach { createOptionInfo ->
+                            item {
+                                MoreOptionsInfoRow(
+                                    providerInfo = enabledProviderInfo,
+                                    createOptionInfo = createOptionInfo,
+                                    onOptionSelected = {
+                                        onOptionSelected(
+                                            ActiveEntry(
+                                                enabledProviderInfo,
+                                                createOptionInfo
+                                            )
+                                        )
+                                    })
+                            }
+                        }
+                    }
+                    if (disabledProviderList != null) {
+                        item {
+                            MoreOptionsDisabledProvidersRow(
+                                disabledProviders = disabledProviderList,
+                                onDisabledPasswordManagerSelected =
+                                onDisabledPasswordManagerSelected,
+                            )
+                        }
+                    }
+                    // TODO: handle the error situation that if multiple remoteInfos exists
+                    enabledProviderList.forEach {
+                        if (it.remoteEntry != null) {
+                            item {
+                                RemoteEntryRow(
+                                    remoteInfo = it.remoteEntry!!,
+                                    onRemoteEntrySelected = onRemoteEntrySelected,
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+            Divider(
+                thickness = 18.dp,
+                color = Color.Transparent,
+                modifier = Modifier.padding(bottom = 40.dp)
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MoreOptionsRowIntroCard(
+    providerInfo: EnabledProviderInfo,
+    onDefaultOrNotSelected: () -> Unit,
+) {
+    ContainerCard() {
+        Column() {
+            Icon(
+                Icons.Outlined.NewReleases,
+                contentDescription = null,
+                modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+                    .padding(all = 24.dp)
+            )
+            TextOnSurface(
+                text = stringResource(
+                    R.string.use_provider_for_all_title,
+                    providerInfo.displayName
+                ),
+                style = MaterialTheme.typography.titleMedium,
+                modifier = Modifier.padding(horizontal = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally),
+                textAlign = TextAlign.Center,
+            )
+            TextSecondary(
+                text = stringResource(R.string.use_provider_for_all_description),
+                style = MaterialTheme.typography.bodyLarge,
+                modifier = Modifier.padding(all = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally),
+            )
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                CancelButton(
+                    stringResource(R.string.use_once),
+                    onClick = onDefaultOrNotSelected
+                )
+                ConfirmButton(
+                    stringResource(R.string.set_as_default),
+                    onClick = onDefaultOrNotSelected
+                )
+            }
+            Divider(
+                thickness = 18.dp,
+                color = Color.Transparent,
+                modifier = Modifier.padding(bottom = 40.dp)
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun CreationSelectionCard(
+    requestDisplayInfo: RequestDisplayInfo,
+    enabledProviderList: List<EnabledProviderInfo>,
+    providerInfo: EnabledProviderInfo,
+    createOptionInfo: CreateOptionInfo,
+    showActiveEntryOnly: Boolean,
+    onOptionSelected: (EntryInfo) -> Unit,
+    onConfirm: () -> Unit,
+    onCancel: () -> Unit,
+    onMoreOptionsSelected: () -> Unit,
+) {
+    ContainerCard() {
+        Column() {
+            Icon(
+                bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+                contentDescription = null,
+                tint = Color.Unspecified,
+                modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+                    .padding(all = 24.dp).size(32.dp)
+            )
+            TextOnSurface(
+                text = when (requestDisplayInfo.type) {
+                    TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(
+                        R.string.choose_create_option_passkey_title,
+                        providerInfo.displayName
+                    )
+                    TYPE_PASSWORD_CREDENTIAL -> stringResource(
+                        R.string.choose_create_option_password_title,
+                        providerInfo.displayName
+                    )
+                    else -> stringResource(
+                        R.string.choose_create_option_sign_in_title,
+                        providerInfo.displayName
+                    )
+                },
+                style = MaterialTheme.typography.titleMedium,
+                modifier = Modifier.padding(horizontal = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally),
+                textAlign = TextAlign.Center,
+            )
+            if (createOptionInfo.userProviderDisplayName != null) {
+                TextSecondary(
+                    text = stringResource(
+                        R.string.choose_create_option_description,
+                        requestDisplayInfo.appDomainName,
+                        when (requestDisplayInfo.type) {
+                            TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkey)
+                            TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.password)
+                            else -> stringResource(R.string.sign_ins)
+                        },
+                        providerInfo.displayName,
+                        createOptionInfo.userProviderDisplayName
+                    ),
+                    style = MaterialTheme.typography.bodyLarge,
+                    modifier = Modifier.padding(all = 24.dp)
+                        .align(alignment = Alignment.CenterHorizontally),
+                )
+            }
+            ContainerCard(
+                shape = MaterialTheme.shapes.medium,
+                modifier = Modifier
+                    .padding(horizontal = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally),
+            ) {
+                PrimaryCreateOptionRow(
+                    requestDisplayInfo = requestDisplayInfo,
+                    entryInfo = createOptionInfo,
+                    onOptionSelected = onOptionSelected
+                )
+            }
+            if (!showActiveEntryOnly) {
+                var createOptionsSize = 0
+                enabledProviderList.forEach { enabledProvider ->
+                    createOptionsSize += enabledProvider.createOptions.size
+                }
+                if (createOptionsSize > 1) {
+                    TextButton(
+                        onClick = onMoreOptionsSelected,
+                        modifier = Modifier
+                            .padding(horizontal = 24.dp)
+                            .align(alignment = Alignment.CenterHorizontally),
+                        colors = ButtonDefaults.textButtonColors(
+                            contentColor = MaterialTheme.colorScheme.primary,
+                        ),
+                    ) {
+                        Text(
+                            text =
+                            when (requestDisplayInfo.type) {
+                                TYPE_PUBLIC_KEY_CREDENTIAL ->
+                                    stringResource(R.string.string_create_in_another_place)
+                                else -> stringResource(R.string.string_save_to_another_place)
+                            },
+                            textAlign = TextAlign.Center,
+                        )
+                    }
+                } else if (
+                    requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL
+                ) {
+                    // TODO: handle the error situation that if multiple remoteInfos exists
+                    enabledProviderList.forEach { enabledProvider ->
+                        if (enabledProvider.remoteEntry != null) {
+                            TextButton(
+                                onClick = {
+                                    onOptionSelected(enabledProvider.remoteEntry!!)
+                                },
+                                modifier = Modifier
+                                    .padding(horizontal = 24.dp)
+                                    .align(alignment = Alignment.CenterHorizontally),
+                                colors = ButtonDefaults.textButtonColors(
+                                    contentColor = MaterialTheme.colorScheme.primary,
+                                ),
+                            ) {
+                                Text(
+                                    text = stringResource(R.string.string_use_another_device),
+                                    textAlign = TextAlign.Center,
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+            Divider(
+                thickness = 24.dp,
+                color = Color.Transparent
+            )
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                CancelButton(
+                    stringResource(R.string.string_cancel),
+                    onClick = onCancel
+                )
+                ConfirmButton(
+                    stringResource(R.string.string_continue),
+                    onClick = onConfirm
+                )
+            }
+            Divider(
+                thickness = 18.dp,
+                color = Color.Transparent,
+                modifier = Modifier.padding(bottom = 16.dp)
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ExternalOnlySelectionCard(
+    requestDisplayInfo: RequestDisplayInfo,
+    activeRemoteEntry: EntryInfo,
+    onOptionSelected: (EntryInfo) -> Unit,
+    onConfirm: () -> Unit,
+    onCancel: () -> Unit,
+) {
+    ContainerCard() {
+        Column() {
+            Icon(
+                painter = painterResource(R.drawable.ic_other_devices),
+                contentDescription = null,
+                tint = Color.Unspecified,
+                modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+                    .padding(all = 24.dp).size(32.dp)
+            )
+            TextOnSurface(
+                text = stringResource(R.string.create_passkey_in_other_device_title),
+                style = MaterialTheme.typography.titleMedium,
+                modifier = Modifier.padding(horizontal = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally),
+                textAlign = TextAlign.Center,
+            )
+            Divider(
+                thickness = 24.dp,
+                color = Color.Transparent
+            )
+            ContainerCard(
+                shape = MaterialTheme.shapes.medium,
+                modifier = Modifier
+                    .padding(horizontal = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally),
+            ) {
+                PrimaryCreateOptionRow(
+                    requestDisplayInfo = requestDisplayInfo,
+                    entryInfo = activeRemoteEntry,
+                    onOptionSelected = onOptionSelected
+                )
+            }
+            Divider(
+                thickness = 24.dp,
+                color = Color.Transparent
+            )
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                CancelButton(
+                    stringResource(R.string.string_cancel),
+                    onClick = onCancel
+                )
+                ConfirmButton(
+                    stringResource(R.string.string_continue),
+                    onClick = onConfirm
+                )
+            }
+            Divider(
+                thickness = 18.dp,
+                color = Color.Transparent,
+                modifier = Modifier.padding(bottom = 16.dp)
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun PrimaryCreateOptionRow(
+    requestDisplayInfo: RequestDisplayInfo,
+    entryInfo: EntryInfo,
+    onOptionSelected: (EntryInfo) -> Unit
+) {
+    Entry(
+        onClick = { onOptionSelected(entryInfo) },
+        icon = {
+            Icon(
+                bitmap = if (entryInfo is CreateOptionInfo) {
+                    entryInfo.profileIcon.toBitmap().asImageBitmap()
+                } else {
+                    requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()
+                },
+                contentDescription = null,
+                tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+                modifier = Modifier.padding(horizontal = 18.dp).size(32.dp)
+            )
+        },
+        label = {
+            Column() {
+                // TODO: Add the function to hide/view password when the type is create password
+                when (requestDisplayInfo.type) {
+                    TYPE_PUBLIC_KEY_CREDENTIAL -> {
+                        TextOnSurfaceVariant(
+                            text = requestDisplayInfo.title,
+                            style = MaterialTheme.typography.titleLarge,
+                            modifier = Modifier.padding(top = 16.dp),
+                        )
+                        TextSecondary(
+                            text = if (requestDisplayInfo.subtitle != null) {
+                                stringResource(
+                                    R.string.passkey_before_subtitle
+                                ) + " - " + requestDisplayInfo.subtitle
+                            } else {
+                                stringResource(R.string.passkey_before_subtitle)
+                            },
+                            style = MaterialTheme.typography.bodyMedium,
+                            modifier = Modifier.padding(bottom = 16.dp),
+                        )
+                    }
+                    TYPE_PASSWORD_CREDENTIAL -> {
+                        TextOnSurfaceVariant(
+                            text = requestDisplayInfo.title,
+                            style = MaterialTheme.typography.titleLarge,
+                            modifier = Modifier.padding(top = 16.dp),
+                        )
+                        TextSecondary(
+                            // This subtitle would never be null for create password
+                            text = requestDisplayInfo.subtitle ?: "",
+                            style = MaterialTheme.typography.bodyMedium,
+                            modifier = Modifier.padding(bottom = 16.dp),
+                        )
+                    }
+                    else -> {
+                        TextOnSurfaceVariant(
+                            text = requestDisplayInfo.title,
+                            style = MaterialTheme.typography.titleLarge,
+                            modifier = Modifier.padding(top = 16.dp, bottom = 16.dp),
+                        )
+                    }
+                }
+            }
+        }
+    )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MoreOptionsInfoRow(
+    providerInfo: EnabledProviderInfo,
+    createOptionInfo: CreateOptionInfo,
+    onOptionSelected: () -> Unit
+) {
+    Entry(
+        onClick = onOptionSelected,
+        icon = {
+            Image(
+                modifier = Modifier.padding(horizontal = 16.dp).size(32.dp),
+                bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+                contentDescription = null
+            )
+        },
+        label = {
+            Column() {
+                TextOnSurfaceVariant(
+                    text = providerInfo.displayName,
+                    style = MaterialTheme.typography.titleLarge,
+                    modifier = Modifier.padding(top = 16.dp),
+                )
+                if (createOptionInfo.userProviderDisplayName != null) {
+                    TextSecondary(
+                        text = createOptionInfo.userProviderDisplayName,
+                        style = MaterialTheme.typography.bodyMedium,
+                    )
+                }
+                if (createOptionInfo.passwordCount != null &&
+                    createOptionInfo.passkeyCount != null
+                ) {
+                    TextSecondary(
+                        text =
+                        stringResource(
+                            R.string.more_options_usage_passwords_passkeys,
+                            createOptionInfo.passwordCount,
+                            createOptionInfo.passkeyCount
+                        ),
+                        style = MaterialTheme.typography.bodyMedium,
+                        modifier = Modifier.padding(bottom = 16.dp),
+                    )
+                } else if (createOptionInfo.passwordCount != null) {
+                    TextSecondary(
+                        text =
+                        stringResource(
+                            R.string.more_options_usage_passwords,
+                            createOptionInfo.passwordCount
+                        ),
+                        style = MaterialTheme.typography.bodyMedium,
+                        modifier = Modifier.padding(bottom = 16.dp),
+                    )
+                } else if (createOptionInfo.passkeyCount != null) {
+                    TextSecondary(
+                        text =
+                        stringResource(
+                            R.string.more_options_usage_passkeys,
+                            createOptionInfo.passkeyCount
+                        ),
+                        style = MaterialTheme.typography.bodyMedium,
+                        modifier = Modifier.padding(bottom = 16.dp),
+                    )
+                } else if (createOptionInfo.totalCredentialCount != null) {
+                    // TODO: Handle the case when there is total count
+                    // but no passwords and passkeys after design is set
+                }
+            }
+        }
+    )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MoreOptionsDisabledProvidersRow(
+    disabledProviders: List<ProviderInfo>,
+    onDisabledPasswordManagerSelected: () -> Unit,
+) {
+    Entry(
+        onClick = onDisabledPasswordManagerSelected,
+        icon = {
+            Icon(
+                Icons.Filled.Add,
+                contentDescription = null,
+                modifier = Modifier.padding(start = 16.dp)
+            )
+        },
+        label = {
+            Column() {
+                TextOnSurfaceVariant(
+                    text = stringResource(R.string.other_password_manager),
+                    style = MaterialTheme.typography.titleLarge,
+                    modifier = Modifier.padding(top = 16.dp, start = 16.dp),
+                )
+                // TODO: Update the subtitle once design is confirmed
+                TextSecondary(
+                    text = disabledProviders.joinToString(separator = ", ") { it.displayName },
+                    style = MaterialTheme.typography.bodyMedium,
+                    modifier = Modifier.padding(bottom = 16.dp, start = 16.dp),
+                )
+            }
+        }
+    )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun RemoteEntryRow(
+    remoteInfo: RemoteInfo,
+    onRemoteEntrySelected: (RemoteInfo) -> Unit,
+) {
+    Entry(
+        onClick = { onRemoteEntrySelected(remoteInfo) },
+        icon = {
+            Icon(
+                painter = painterResource(R.drawable.ic_other_devices),
+                contentDescription = null,
+                tint = Color.Unspecified,
+                modifier = Modifier.padding(start = 18.dp)
+            )
+        },
+        label = {
+            Column() {
+                TextOnSurfaceVariant(
+                    text = stringResource(R.string.another_device),
+                    style = MaterialTheme.typography.titleLarge,
+                    modifier = Modifier.padding(start = 16.dp, top = 18.dp, bottom = 18.dp)
+                        .align(alignment = Alignment.CenterHorizontally),
+                )
+            }
+        }
+    )
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
new file mode 100644
index 0000000..518aaee
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -0,0 +1,220 @@
+/*
+ * 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.createflow
+
+import android.app.Activity
+import android.util.Log
+import androidx.activity.compose.ManagedActivityResultLauncher
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.IntentSenderRequest
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.CreateFlowUtils
+import com.android.credentialmanager.CredentialManagerRepo
+import com.android.credentialmanager.common.DialogResult
+import com.android.credentialmanager.common.ProviderActivityResult
+import com.android.credentialmanager.common.ResultState
+
+data class CreateCredentialUiState(
+  val enabledProviders: List<EnabledProviderInfo>,
+  val disabledProviders: List<DisabledProviderInfo>? = null,
+  val currentScreenState: CreateScreenState,
+  val requestDisplayInfo: RequestDisplayInfo,
+  val showActiveEntryOnly: Boolean,
+  val activeEntry: ActiveEntry? = null,
+  val selectedEntry: EntryInfo? = null,
+  val hidden: Boolean = false,
+  val providerActivityPending: Boolean = false,
+)
+
+class CreateCredentialViewModel(
+  credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance()
+) : ViewModel() {
+
+  var uiState by mutableStateOf(credManRepo.createCredentialInitialUiState())
+    private set
+
+  val dialogResult: MutableLiveData<DialogResult> by lazy {
+    MutableLiveData<DialogResult>()
+  }
+
+  fun observeDialogResult(): LiveData<DialogResult> {
+    return dialogResult
+  }
+
+  fun onConfirmIntro() {
+    var createOptionSize = 0
+    var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
+    var remoteEntry: RemoteInfo? = null
+    uiState.enabledProviders.forEach {
+      enabledProvider ->
+      if (enabledProvider.createOptions.isNotEmpty()) {
+        createOptionSize += enabledProvider.createOptions.size
+        lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
+      }
+      if (enabledProvider.remoteEntry != null) {
+        remoteEntry = enabledProvider.remoteEntry!!
+      }
+    }
+    uiState = uiState.copy(
+      currentScreenState = CreateFlowUtils.toCreateScreenState(
+        createOptionSize, true,
+        uiState.requestDisplayInfo, null, remoteEntry),
+      showActiveEntryOnly = createOptionSize > 1,
+      activeEntry = CreateFlowUtils.toActiveEntry(
+        null, createOptionSize, lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
+    )
+  }
+
+  fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
+    return uiState.enabledProviders.single {
+      it.name.equals(providerName)
+    }
+  }
+
+  fun onMoreOptionsSelected() {
+    uiState = uiState.copy(
+      currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
+    )
+  }
+
+  fun onBackButtonSelected() {
+    uiState = uiState.copy(
+        currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+    )
+  }
+
+  fun onEntrySelectedFromMoreOptionScreen(activeEntry: ActiveEntry) {
+    uiState = uiState.copy(
+      currentScreenState = CreateScreenState.MORE_OPTIONS_ROW_INTRO,
+      showActiveEntryOnly = false,
+      activeEntry = activeEntry
+    )
+  }
+
+  fun onEntrySelectedFromFirstUseScreen(activeEntry: ActiveEntry) {
+    uiState = uiState.copy(
+      currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+      showActiveEntryOnly = true,
+      activeEntry = activeEntry
+    )
+  }
+
+  fun onDisabledPasswordManagerSelected() {
+    // TODO: Complete this function
+  }
+
+  fun onCancel() {
+    CredentialManagerRepo.getInstance().onCancel()
+    dialogResult.value = DialogResult(ResultState.CANCELED)
+  }
+
+  fun onDefaultOrNotSelected() {
+    uiState = uiState.copy(
+      currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+    )
+    // TODO: implement the if choose as default or not logic later
+  }
+
+  fun onEntrySelected(selectedEntry: EntryInfo) {
+    val providerId = selectedEntry.providerId
+    val entryKey = selectedEntry.entryKey
+    val entrySubkey = selectedEntry.entrySubkey
+    Log.d(
+      "Account Selector", "Option selected for entry: " +
+              " {provider=$providerId, key=$entryKey, subkey=$entrySubkey")
+    if (selectedEntry.pendingIntent != null) {
+      uiState = uiState.copy(
+        selectedEntry = selectedEntry,
+        hidden = true,
+      )
+    } else {
+      CredentialManagerRepo.getInstance().onOptionSelected(
+        providerId,
+        entryKey,
+        entrySubkey
+      )
+      dialogResult.value = DialogResult(
+        ResultState.COMPLETE,
+      )
+    }
+  }
+
+  fun launchProviderUi(
+    launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
+  ) {
+    val entry = uiState.selectedEntry
+    if (entry != null && entry.pendingIntent != null) {
+      uiState = uiState.copy(
+        providerActivityPending = true,
+      )
+      val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
+        .setFillInIntent(entry.fillInIntent).build()
+      launcher.launch(intentSenderRequest)
+    } else {
+      Log.w("Account Selector", "No provider UI to launch")
+    }
+  }
+
+  fun onConfirmEntrySelected() {
+    val selectedEntry = uiState.activeEntry?.activeEntryInfo
+    if (selectedEntry != null) {
+      onEntrySelected(selectedEntry)
+    } else {
+      Log.w("Account Selector",
+        "Illegal state: confirm is pressed but activeEntry isn't set.")
+      dialogResult.value = DialogResult(
+        ResultState.COMPLETE,
+      )
+    }
+  }
+
+  fun onProviderActivityResult(providerActivityResult: ProviderActivityResult) {
+    val entry = uiState.selectedEntry
+    val resultCode = providerActivityResult.resultCode
+    val resultData = providerActivityResult.data
+    if (resultCode == Activity.RESULT_CANCELED) {
+      // Re-display the CredMan UI if the user canceled from the provider UI.
+      uiState = uiState.copy(
+        selectedEntry = null,
+        hidden = false,
+        providerActivityPending = false,
+      )
+    } else {
+      if (entry != null) {
+        val providerId = entry.providerId
+        Log.d("Account Selector", "Got provider activity result: {provider=" +
+                "$providerId, key=${entry.entryKey}, subkey=${entry.entrySubkey}, " +
+                "resultCode=$resultCode, resultData=$resultData}"
+        )
+        CredentialManagerRepo.getInstance().onOptionSelected(
+          providerId, entry.entryKey, entry.entrySubkey, resultCode, resultData,
+        )
+      } else {
+        Log.w("Account Selector",
+          "Illegal state: received a provider result but found no matching entry.")
+      }
+      dialogResult.value = DialogResult(
+        ResultState.COMPLETE,
+      )
+    }
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index cb2bf10..21abe08 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -16,30 +16,68 @@
 
 package com.android.credentialmanager.createflow
 
+import android.app.PendingIntent
+import android.content.Intent
 import android.graphics.drawable.Drawable
 
-data class ProviderInfo(
+open class ProviderInfo(
   val icon: Drawable,
   val name: String,
   val displayName: String,
-  val credentialTypeIcon: Drawable,
-  val createOptions: List<CreateOptionInfo>,
 )
 
-data class CreateOptionInfo(
-  val icon: Drawable,
-  val title: String,
-  val subtitle: String,
+class EnabledProviderInfo(
+  icon: Drawable,
+  name: String,
+  displayName: String,
+  var createOptions: List<CreateOptionInfo>,
+  val isDefault: Boolean,
+  var remoteEntry: RemoteInfo?,
+) : ProviderInfo(icon, name, displayName)
+
+class DisabledProviderInfo(
+  icon: Drawable,
+  name: String,
+  displayName: String,
+) : ProviderInfo(icon, name, displayName)
+
+open class EntryInfo (
+  val providerId: String,
   val entryKey: String,
   val entrySubkey: String,
-  val usageData: String
+  val pendingIntent: PendingIntent?,
+  val fillInIntent: Intent?,
 )
 
+class CreateOptionInfo(
+  providerId: String,
+  entryKey: String,
+  entrySubkey: String,
+  pendingIntent: PendingIntent?,
+  fillInIntent: Intent?,
+  val userProviderDisplayName: String?,
+  val profileIcon: Drawable,
+  val passwordCount: Int?,
+  val passkeyCount: Int?,
+  val totalCredentialCount: Int?,
+  val lastUsedTimeMillis: Long?,
+) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+
+class RemoteInfo(
+  providerId: String,
+  entryKey: String,
+  entrySubkey: String,
+  pendingIntent: PendingIntent?,
+  fillInIntent: Intent?,
+) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+
 data class RequestDisplayInfo(
-  val userName: String,
-  val displayName: String,
+  val title: String,
+  val subtitle: String?,
   val type: String,
   val appDomainName: String,
+  val typeIcon: Drawable,
+  val isFirstUsage: Boolean,
 )
 
 /**
@@ -47,8 +85,8 @@
  * user selects a different entry on the more option page.
  */
 data class ActiveEntry (
-  val activeProvider: ProviderInfo,
-  val activeCreateOptionInfo: CreateOptionInfo,
+  val activeProvider: EnabledProviderInfo,
+  val activeEntryInfo: EntryInfo,
 )
 
 /** The name of the current screen. */
@@ -58,4 +96,5 @@
   CREATION_OPTION_SELECTION,
   MORE_OPTIONS_SELECTION,
   MORE_OPTIONS_ROW_INTRO,
+  EXTERNAL_ONLY_SELECTION,
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
deleted file mode 100644
index 8e30208..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ /dev/null
@@ -1,577 +0,0 @@
-package com.android.credentialmanager.createflow
-
-import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material.Button
-import androidx.compose.material.ButtonColors
-import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.Card
-import androidx.compose.material.Chip
-import androidx.compose.material.ChipDefaults
-import androidx.compose.material.Divider
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.ModalBottomSheetLayout
-import androidx.compose.material.ModalBottomSheetValue
-import androidx.compose.material.Text
-import androidx.compose.material.TextButton
-import androidx.compose.material.TopAppBar
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.material.rememberModalBottomSheetState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
-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.ui.theme.Grey100
-import com.android.credentialmanager.ui.theme.Shapes
-import com.android.credentialmanager.ui.theme.Typography
-import com.android.credentialmanager.ui.theme.lightBackgroundColor
-import com.android.credentialmanager.ui.theme.lightColorAccentSecondary
-import com.android.credentialmanager.ui.theme.lightSurface1
-
-@ExperimentalMaterialApi
-@Composable
-fun CreatePasskeyScreen(
-  viewModel: CreatePasskeyViewModel,
-) {
-  val state = rememberModalBottomSheetState(
-    initialValue = ModalBottomSheetValue.Expanded,
-    skipHalfExpanded = true
-  )
-  ModalBottomSheetLayout(
-    sheetState = state,
-    sheetContent = {
-      val uiState = viewModel.uiState
-      when (uiState.currentScreenState) {
-        CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
-          onConfirm = viewModel::onConfirmIntro,
-          onCancel = viewModel::onCancel,
-        )
-        CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
-          providerList = uiState.providers,
-          onCancel = viewModel::onCancel,
-          onProviderSelected = viewModel::onProviderSelected
-        )
-        CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
-          requestDisplayInfo = uiState.requestDisplayInfo,
-          providerInfo = uiState.activeEntry?.activeProvider!!,
-          createOptionInfo = uiState.activeEntry.activeCreateOptionInfo,
-          onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected,
-          onConfirm = viewModel::onPrimaryCreateOptionInfoSelected,
-          onCancel = viewModel::onCancel,
-          multiProvider = uiState.providers.size > 1,
-          onMoreOptionsSelected = viewModel::onMoreOptionsSelected
-        )
-        CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
-            providerList = uiState.providers,
-            onBackButtonSelected = viewModel::onBackButtonSelected,
-            onOptionSelected = viewModel::onMoreOptionsRowSelected
-          )
-        CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
-          providerInfo = uiState.activeEntry?.activeProvider!!,
-          onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
-        )
-      }
-    },
-    scrimColor = Color.Transparent,
-    sheetShape = Shapes.medium,
-  ) {}
-  LaunchedEffect(state.currentValue) {
-    if (state.currentValue == ModalBottomSheetValue.Hidden) {
-      viewModel.onCancel()
-    }
-  }
-}
-
-@Composable
-fun ConfirmationCard(
-  onConfirm: () -> Unit,
-  onCancel: () -> Unit,
-) {
-  Card(
-    backgroundColor = lightBackgroundColor,
-  ) {
-    Column() {
-      Icon(
-        painter = painterResource(R.drawable.ic_passkey),
-        contentDescription = null,
-        tint = Color.Unspecified,
-        modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
-      )
-      Text(
-        text = stringResource(R.string.passkey_creation_intro_title),
-        style = Typography.subtitle1,
-        modifier = Modifier
-          .padding(horizontal = 24.dp)
-          .align(alignment = Alignment.CenterHorizontally)
-      )
-      Divider(
-        thickness = 24.dp,
-        color = Color.Transparent
-      )
-      Text(
-        text = stringResource(R.string.passkey_creation_intro_body),
-        style = Typography.body1,
-        modifier = Modifier.padding(horizontal = 28.dp)
-      )
-      Divider(
-        thickness = 48.dp,
-        color = Color.Transparent
-      )
-      Row(
-        horizontalArrangement = Arrangement.SpaceBetween,
-        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-      ) {
-        CancelButton(
-          stringResource(R.string.string_cancel),
-          onclick = onCancel
-        )
-        ConfirmButton(
-          stringResource(R.string.string_continue),
-          onclick = onConfirm
-        )
-      }
-      Divider(
-        thickness = 18.dp,
-        color = Color.Transparent,
-        modifier = Modifier.padding(bottom = 16.dp)
-      )
-    }
-  }
-}
-
-@ExperimentalMaterialApi
-@Composable
-fun ProviderSelectionCard(
-  providerList: List<ProviderInfo>,
-  onProviderSelected: (String) -> Unit,
-  onCancel: () -> Unit
-) {
-  Card(
-    backgroundColor = lightBackgroundColor,
-  ) {
-    Column() {
-      Text(
-        text = stringResource(R.string.choose_provider_title),
-        style = Typography.subtitle1,
-        modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
-      )
-      Text(
-        text = stringResource(R.string.choose_provider_body),
-        style = Typography.body1,
-        modifier = Modifier.padding(horizontal = 28.dp)
-      )
-      Divider(
-        thickness = 24.dp,
-        color = Color.Transparent
-      )
-      Card(
-        shape = Shapes.medium,
-        modifier = Modifier
-          .padding(horizontal = 24.dp)
-          .align(alignment = Alignment.CenterHorizontally)
-      ) {
-        LazyColumn(
-          verticalArrangement = Arrangement.spacedBy(2.dp)
-        ) {
-          providerList.forEach {
-            item {
-              ProviderRow(providerInfo = it, onProviderSelected = onProviderSelected)
-            }
-          }
-        }
-      }
-      Divider(
-        thickness = 24.dp,
-        color = Color.Transparent
-      )
-      Row(
-        horizontalArrangement = Arrangement.Start,
-        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-      ) {
-        CancelButton(stringResource(R.string.string_cancel), onCancel)
-      }
-      Divider(
-        thickness = 18.dp,
-        color = Color.Transparent,
-        modifier = Modifier.padding(bottom = 16.dp)
-      )
-    }
-  }
-}
-
-@ExperimentalMaterialApi
-@Composable
-fun MoreOptionsSelectionCard(
-  providerList: List<ProviderInfo>,
-  onBackButtonSelected: () -> Unit,
-  onOptionSelected: (ActiveEntry) -> Unit
-) {
-  Card(
-    backgroundColor = lightBackgroundColor,
-  ) {
-    Column() {
-      TopAppBar(
-        title = {
-          Text(text = stringResource(R.string.string_more_options), style = Typography.subtitle1)
-        },
-        backgroundColor = lightBackgroundColor,
-        elevation = 0.dp,
-        navigationIcon =
-        {
-          IconButton(onClick = onBackButtonSelected) {
-            Icon(Icons.Filled.ArrowBack, "backIcon"
-            )
-          }
-        }
-      )
-      Divider(
-         thickness = 24.dp,
-         color = Color.Transparent
-      )
-      Text(
-        text = stringResource(R.string.create_passkey_at),
-        style = Typography.body1,
-        modifier = Modifier.padding(horizontal = 28.dp),
-        textAlign = TextAlign.Center
-      )
-      Card(
-        shape = Shapes.medium,
-        modifier = Modifier
-          .padding(horizontal = 24.dp)
-          .align(alignment = Alignment.CenterHorizontally)
-      ) {
-        LazyColumn(
-          verticalArrangement = Arrangement.spacedBy(2.dp)
-        ) {
-          // TODO: change the order according to usage frequency
-          providerList.forEach { providerInfo ->
-            providerInfo.createOptions.forEach { createOptionInfo ->
-              item {
-                MoreOptionsInfoRow(
-                  providerInfo = providerInfo,
-                  createOptionInfo = createOptionInfo,
-                  onOptionSelected = {
-                    onOptionSelected(ActiveEntry(providerInfo, createOptionInfo))
-                  })
-              }
-            }
-          }
-        }
-      }
-      Divider(
-        thickness = 18.dp,
-        color = Color.Transparent,
-        modifier = Modifier.padding(bottom = 40.dp)
-      )
-    }
-  }
-}
-
-@ExperimentalMaterialApi
-@Composable
-fun MoreOptionsRowIntroCard(
-  providerInfo: ProviderInfo,
-  onDefaultOrNotSelected: () -> Unit,
-) {
-  Card(
-    backgroundColor = lightBackgroundColor,
-  ) {
-    Column() {
-      Text(
-        text = stringResource(R.string.use_provider_for_all_title, providerInfo.displayName),
-        style = Typography.subtitle1,
-        modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
-      )
-      Row(
-        horizontalArrangement = Arrangement.SpaceBetween,
-        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-      ) {
-        CancelButton(
-          stringResource(R.string.use_once),
-          onclick = onDefaultOrNotSelected
-        )
-        ConfirmButton(
-          stringResource(R.string.set_as_default),
-          onclick = onDefaultOrNotSelected
-        )
-      }
-      Divider(
-        thickness = 18.dp,
-        color = Color.Transparent,
-        modifier = Modifier.padding(bottom = 40.dp)
-      )
-    }
-  }
-}
-
-@ExperimentalMaterialApi
-@Composable
-fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit) {
-  Chip(
-    modifier = Modifier.fillMaxWidth(),
-    onClick = {onProviderSelected(providerInfo.name)},
-    leadingIcon = {
-      Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
-            bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
-            // painter = painterResource(R.drawable.ic_passkey),
-            // TODO: add description.
-            contentDescription = "")
-    },
-    colors = ChipDefaults.chipColors(
-      backgroundColor = Grey100,
-      leadingIconContentColor = Grey100
-    ),
-    shape = Shapes.large
-  ) {
-    Text(
-      text = providerInfo.displayName,
-      style = Typography.button,
-      modifier = Modifier.padding(vertical = 18.dp)
-    )
-  }
-}
-
-@Composable
-fun CancelButton(text: String, onclick: () -> Unit) {
-  val colors = ButtonDefaults.buttonColors(
-    backgroundColor = lightBackgroundColor
-  )
-  NavigationButton(
-    border = BorderStroke(1.dp, lightSurface1),
-    colors = colors,
-    text = text,
-    onclick = onclick)
-}
-
-@Composable
-fun ConfirmButton(text: String, onclick: () -> Unit) {
-  val colors = ButtonDefaults.buttonColors(
-    backgroundColor = lightColorAccentSecondary
-  )
-  NavigationButton(
-    colors = colors,
-    text = text,
-    onclick = onclick)
-}
-
-@Composable
-fun NavigationButton(
-    border: BorderStroke? = null,
-    colors: ButtonColors,
-    text: String,
-    onclick: () -> Unit
-) {
-  Button(
-    onClick = onclick,
-    shape = Shapes.small,
-    colors = colors,
-    border = border
-  ) {
-    Text(text = text, style = Typography.button)
-  }
-}
-
-@ExperimentalMaterialApi
-@Composable
-fun CreationSelectionCard(
-  requestDisplayInfo: RequestDisplayInfo,
-  providerInfo: ProviderInfo,
-  createOptionInfo: CreateOptionInfo,
-  onOptionSelected: () -> Unit,
-  onConfirm: () -> Unit,
-  onCancel: () -> Unit,
-  multiProvider: Boolean,
-  onMoreOptionsSelected: () -> Unit,
-) {
-  Card(
-    backgroundColor = lightBackgroundColor,
-  ) {
-    Column() {
-      Icon(
-        bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
-        contentDescription = null,
-        tint = Color.Unspecified,
-        modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
-      )
-      Text(
-        text = when (requestDisplayInfo.type) {
-          TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.choose_create_option_passkey_title,
-            providerInfo.displayName)
-          TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.choose_create_option_password_title,
-            providerInfo.displayName)
-          else -> stringResource(R.string.choose_create_option_sign_in_title,
-            providerInfo.displayName)
-        },
-        style = Typography.subtitle1,
-        modifier = Modifier.padding(horizontal = 24.dp)
-          .align(alignment = Alignment.CenterHorizontally),
-        textAlign = TextAlign.Center,
-      )
-      Text(
-        text = requestDisplayInfo.appDomainName,
-        style = Typography.body2,
-        modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
-      )
-      Text(
-        text = stringResource(
-          R.string.choose_create_option_description,
-          when (requestDisplayInfo.type) {
-            TYPE_PUBLIC_KEY_CREDENTIAL -> "passkeys"
-            TYPE_PASSWORD_CREDENTIAL -> "passwords"
-            else -> "sign-ins"
-          },
-          providerInfo.displayName,
-          createOptionInfo.title),
-        style = Typography.body1,
-        modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
-      )
-      Card(
-        shape = Shapes.medium,
-        modifier = Modifier
-          .padding(horizontal = 24.dp)
-          .align(alignment = Alignment.CenterHorizontally)
-      ) {
-        LazyColumn(
-          verticalArrangement = Arrangement.spacedBy(2.dp)
-        ) {
-            item {
-              PrimaryCreateOptionRow(requestDisplayInfo = requestDisplayInfo,
-                onOptionSelected = onOptionSelected)
-            }
-        }
-      }
-      if (multiProvider) {
-        TextButton(
-          onClick = onMoreOptionsSelected,
-          modifier = Modifier
-          .padding(horizontal = 24.dp)
-          .align(alignment = Alignment.CenterHorizontally)){
-          Text(
-              text =
-                when (requestDisplayInfo.type) {
-                  TYPE_PUBLIC_KEY_CREDENTIAL ->
-                    stringResource(R.string.string_create_in_another_place)
-                  else -> stringResource(R.string.string_save_to_another_place)},
-            textAlign = TextAlign.Center,
-          )
-        }
-      }
-      Divider(
-        thickness = 24.dp,
-        color = Color.Transparent
-      )
-      Row(
-        horizontalArrangement = Arrangement.SpaceBetween,
-        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-      ) {
-        CancelButton(
-          stringResource(R.string.string_cancel),
-          onclick = onCancel
-        )
-        ConfirmButton(
-          stringResource(R.string.string_continue),
-          onclick = onConfirm
-        )
-      }
-      Divider(
-        thickness = 18.dp,
-        color = Color.Transparent,
-        modifier = Modifier.padding(bottom = 16.dp)
-      )
-    }
-  }
-}
-
-@ExperimentalMaterialApi
-@Composable
-fun PrimaryCreateOptionRow(
-  requestDisplayInfo: RequestDisplayInfo,
-  onOptionSelected: () -> Unit
-) {
-  Chip(
-    modifier = Modifier.fillMaxWidth(),
-    onClick = onOptionSelected,
-    // TODO: Add an icon generated by provider according to requestDisplayInfo type
-    colors = ChipDefaults.chipColors(
-      backgroundColor = Grey100,
-      leadingIconContentColor = Grey100
-    ),
-    shape = Shapes.large
-  ) {
-    Column() {
-      Text(
-        text = requestDisplayInfo.userName,
-        style = Typography.h6,
-        modifier = Modifier.padding(top = 16.dp)
-      )
-      Text(
-        text = requestDisplayInfo.displayName,
-        style = Typography.body2,
-        modifier = Modifier.padding(bottom = 16.dp)
-      )
-    }
-  }
-}
-
-@ExperimentalMaterialApi
-@Composable
-fun MoreOptionsInfoRow(
-  providerInfo: ProviderInfo,
-  createOptionInfo: CreateOptionInfo,
-  onOptionSelected: () -> Unit
-) {
-    Chip(
-        modifier = Modifier.fillMaxWidth(),
-        onClick = onOptionSelected,
-        leadingIcon = {
-            Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
-                bitmap = createOptionInfo.icon.toBitmap().asImageBitmap(),
-                // painter = painterResource(R.drawable.ic_passkey),
-                // TODO: add description.
-                contentDescription = "")
-        },
-        colors = ChipDefaults.chipColors(
-            backgroundColor = Grey100,
-            leadingIconContentColor = Grey100
-        ),
-        shape = Shapes.large
-    ) {
-        Column() {
-            Text(
-                text =
-                if (providerInfo.createOptions.size > 1)
-                {stringResource(R.string.more_options_title_multiple_options,
-                  providerInfo.displayName, createOptionInfo.title)} else {
-                  stringResource(R.string.more_options_title_one_option,
-                    providerInfo.displayName)},
-                style = Typography.h6,
-                modifier = Modifier.padding(top = 16.dp)
-            )
-            Text(
-                text = createOptionInfo.usageData,
-                style = Typography.body2,
-                modifier = Modifier.padding(bottom = 16.dp)
-            )
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
deleted file mode 100644
index 615da4e..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ /dev/null
@@ -1,149 +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.createflow
-
-import android.util.Log
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.CredentialManagerRepo
-import com.android.credentialmanager.common.DialogResult
-import com.android.credentialmanager.common.ResultState
-
-data class CreatePasskeyUiState(
-  val providers: List<ProviderInfo>,
-  val currentScreenState: CreateScreenState,
-  val requestDisplayInfo: RequestDisplayInfo,
-  val activeEntry: ActiveEntry? = null,
-)
-
-class CreatePasskeyViewModel(
-  credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance()
-) : ViewModel() {
-
-  var uiState by mutableStateOf(credManRepo.createPasskeyInitialUiState())
-    private set
-
-  val dialogResult: MutableLiveData<DialogResult> by lazy {
-    MutableLiveData<DialogResult>()
-  }
-
-  fun observeDialogResult(): LiveData<DialogResult> {
-    return dialogResult
-  }
-
-  fun onConfirmIntro() {
-    if (uiState.providers.size > 1) {
-      uiState = uiState.copy(
-        currentScreenState = CreateScreenState.PROVIDER_SELECTION
-      )
-    } else if (uiState.providers.size == 1){
-      uiState = uiState.copy(
-        currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
-        activeEntry = ActiveEntry(uiState.providers.first(),
-          uiState.providers.first().createOptions.first())
-      )
-    } else {
-      throw java.lang.IllegalStateException("Empty provider list.")
-    }
-  }
-
-  fun onProviderSelected(providerName: String) {
-    uiState = uiState.copy(
-      currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
-      activeEntry = ActiveEntry(getProviderInfoByName(providerName),
-        getProviderInfoByName(providerName).createOptions.first())
-    )
-  }
-
-  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)
-    }
-  }
-
-  fun onMoreOptionsSelected() {
-    uiState = uiState.copy(
-      currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
-    )
-  }
-
-  fun onBackButtonSelected() {
-    uiState = uiState.copy(
-        currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
-    )
-  }
-
-  fun onMoreOptionsRowSelected(activeEntry: ActiveEntry) {
-    uiState = uiState.copy(
-      currentScreenState = CreateScreenState.MORE_OPTIONS_ROW_INTRO,
-      activeEntry = activeEntry
-    )
-  }
-
-  fun onCancel() {
-    CredentialManagerRepo.getInstance().onCancel()
-    dialogResult.value = DialogResult(ResultState.CANCELED)
-  }
-
-  fun onDefaultOrNotSelected() {
-    uiState = uiState.copy(
-      currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
-    )
-    // TODO: implement the if choose as default or not logic later
-  }
-
-  fun onPrimaryCreateOptionInfoSelected() {
-    var createOptionEntryKey = uiState.activeEntry?.activeCreateOptionInfo?.entryKey
-    var createOptionEntrySubkey = uiState.activeEntry?.activeCreateOptionInfo?.entrySubkey
-    Log.d(
-      "Account Selector",
-      "Option selected for creation: " +
-              "{key = $createOptionEntryKey, subkey = $createOptionEntrySubkey}"
-    )
-    if (createOptionEntryKey != null && createOptionEntrySubkey != null) {
-      CredentialManagerRepo.getInstance().onOptionSelected(
-        uiState.activeEntry?.activeProvider!!.name,
-        createOptionEntryKey,
-        createOptionEntrySubkey
-      )
-    } else {
-      TODO("Gracefully handle illegal state.")
-    }
-    dialogResult.value = DialogResult(
-      ResultState.COMPLETE,
-    )
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index e3398c0..d6d7122 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -16,202 +16,505 @@
 
 package com.android.credentialmanager.getflow
 
+import android.text.TextUtils
+import androidx.activity.compose.ManagedActivityResultLauncher
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.IntentSenderRequest
+
 import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material.Card
-import androidx.compose.material.Chip
-import androidx.compose.material.ChipDefaults
-import androidx.compose.material.Divider
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.Icon
-import androidx.compose.material.ModalBottomSheetLayout
-import androidx.compose.material.ModalBottomSheetValue
-import androidx.compose.material.Text
-import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.Divider
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import androidx.core.graphics.drawable.toBitmap
 import com.android.credentialmanager.R
-import com.android.credentialmanager.createflow.CancelButton
-import com.android.credentialmanager.ui.theme.Grey100
-import com.android.credentialmanager.ui.theme.Shapes
-import com.android.credentialmanager.ui.theme.Typography
-import com.android.credentialmanager.ui.theme.lightBackgroundColor
+import com.android.credentialmanager.common.material.ModalBottomSheetLayout
+import com.android.credentialmanager.common.material.ModalBottomSheetValue
+import com.android.credentialmanager.common.material.rememberModalBottomSheetState
+import com.android.credentialmanager.common.ui.CancelButton
+import com.android.credentialmanager.common.ui.Entry
+import com.android.credentialmanager.common.ui.TextOnSurface
+import com.android.credentialmanager.common.ui.TextSecondary
+import com.android.credentialmanager.common.ui.TextOnSurfaceVariant
+import com.android.credentialmanager.common.ui.ContainerCard
+import com.android.credentialmanager.common.ui.TransparentBackgroundEntry
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
+import com.android.credentialmanager.ui.theme.EntryShape
 
-@ExperimentalMaterialApi
 @Composable
 fun GetCredentialScreen(
-  viewModel: GetCredentialViewModel,
+    viewModel: GetCredentialViewModel,
+    providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
 ) {
-  val state = rememberModalBottomSheetState(
-    initialValue = ModalBottomSheetValue.Expanded,
-    skipHalfExpanded = true
-  )
-  ModalBottomSheetLayout(
-    sheetState = state,
-    sheetContent = {
-      val uiState = viewModel.uiState
-      when (uiState.currentScreenState) {
-        GetScreenState.CREDENTIAL_SELECTION -> CredentialSelectionCard(
-          requestDisplayInfo = uiState.requestDisplayInfo,
-          providerInfo = uiState.selectedProvider!!,
-          onCancel = viewModel::onCancel,
-          onOptionSelected = viewModel::onCredentailSelected,
-          multiProvider = uiState.providers.size > 1,
-          onMoreOptionSelected = viewModel::onMoreOptionSelected,
-        )
-      }
-    },
-    scrimColor = Color.Transparent,
-    sheetShape = Shapes.medium,
-  ) {}
-  LaunchedEffect(state.currentValue) {
-    if (state.currentValue == ModalBottomSheetValue.Hidden) {
-      viewModel.onCancel()
-    }
-  }
-}
-
-@ExperimentalMaterialApi
-@Composable
-fun CredentialSelectionCard(
-  requestDisplayInfo: RequestDisplayInfo,
-  providerInfo: ProviderInfo,
-  onOptionSelected: (String, String) -> Unit,
-  onCancel: () -> Unit,
-  multiProvider: Boolean,
-  onMoreOptionSelected: () -> Unit,
-) {
-  Card(
-    backgroundColor = lightBackgroundColor,
-  ) {
-    Column() {
-      Icon(
-        bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
-        contentDescription = null,
-        tint = Color.Unspecified,
-        modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
-      )
-      Text(
-        text = stringResource(R.string.choose_sign_in_title),
-        style = Typography.subtitle1,
-        modifier = Modifier
-          .padding(all = 24.dp)
-          .align(alignment = Alignment.CenterHorizontally)
-      )
-      Text(
-        text = requestDisplayInfo.appDomainName,
-        style = Typography.body2,
-        modifier = Modifier.padding(horizontal = 28.dp)
-      )
-      Divider(
-        thickness = 24.dp,
-        color = Color.Transparent
-      )
-      Card(
-        shape = Shapes.medium,
-        modifier = Modifier
-          .padding(horizontal = 24.dp)
-          .align(alignment = Alignment.CenterHorizontally)
-      ) {
-        LazyColumn(
-          verticalArrangement = Arrangement.spacedBy(2.dp)
-        ) {
-          providerInfo.credentialOptions.forEach {
-            item {
-              CredentialOptionRow(credentialOptionInfo = it, onOptionSelected = onOptionSelected)
-            }
-          }
-          if (multiProvider) {
-            item {
-              MoreOptionRow(onSelect = onMoreOptionSelected)
-            }
-          }
-        }
-      }
-      Divider(
-        thickness = 24.dp,
-        color = Color.Transparent
-      )
-      Row(
-        horizontalArrangement = Arrangement.Start,
-        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-      ) {
-        CancelButton(stringResource(R.string.string_no_thanks), onCancel)
-      }
-      Divider(
-        thickness = 18.dp,
-        color = Color.Transparent,
-        modifier = Modifier.padding(bottom = 16.dp)
-      )
-    }
-  }
-}
-
-@ExperimentalMaterialApi
-@Composable
-fun CredentialOptionRow(
-    credentialOptionInfo: CredentialOptionInfo,
-    onOptionSelected: (String, String) -> Unit,
-) {
-  Chip(
-    modifier = Modifier.fillMaxWidth(),
-    onClick = {onOptionSelected(credentialOptionInfo.entryKey, credentialOptionInfo.entrySubkey)},
-    leadingIcon = {
-      Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
-            bitmap = credentialOptionInfo.icon.toBitmap().asImageBitmap(),
-        // TODO: add description.
-            contentDescription = "")
-    },
-    colors = ChipDefaults.chipColors(
-      backgroundColor = Grey100,
-      leadingIconContentColor = Grey100
-    ),
-    shape = Shapes.large
-  ) {
-    Column() {
-      Text(
-        text = credentialOptionInfo.title,
-        style = Typography.h6,
-        modifier = Modifier.padding(top = 16.dp)
-      )
-      Text(
-        text = credentialOptionInfo.subtitle,
-        style = Typography.body2,
-        modifier = Modifier.padding(bottom = 16.dp)
-      )
-    }
-  }
-}
-
-@ExperimentalMaterialApi
-@Composable
-fun MoreOptionRow(onSelect: () -> Unit) {
-  Chip(
-    modifier = Modifier.fillMaxWidth().height(52.dp),
-    onClick = onSelect,
-    colors = ChipDefaults.chipColors(
-      backgroundColor = Grey100,
-      leadingIconContentColor = Grey100
-    ),
-    shape = Shapes.large
-  ) {
-    Text(
-      text = stringResource(R.string.string_more_options),
-      style = Typography.h6,
+    val state = rememberModalBottomSheetState(
+        initialValue = ModalBottomSheetValue.Expanded,
+        skipHalfExpanded = true
     )
-  }
+    ModalBottomSheetLayout(
+        sheetBackgroundColor = MaterialTheme.colorScheme.surface,
+        modifier = Modifier.background(Color.Transparent),
+        sheetState = state,
+        sheetContent = {
+            val uiState = viewModel.uiState
+            if (!uiState.hidden) {
+                when (uiState.currentScreenState) {
+                    GetScreenState.PRIMARY_SELECTION -> PrimarySelectionCard(
+                        requestDisplayInfo = uiState.requestDisplayInfo,
+                        providerDisplayInfo = uiState.providerDisplayInfo,
+                        onEntrySelected = viewModel::onEntrySelected,
+                        onCancel = viewModel::onCancel,
+                        onMoreOptionSelected = viewModel::onMoreOptionSelected,
+                    )
+                    GetScreenState.ALL_SIGN_IN_OPTIONS -> AllSignInOptionCard(
+                        providerInfoList = uiState.providerInfoList,
+                        providerDisplayInfo = uiState.providerDisplayInfo,
+                        onEntrySelected = viewModel::onEntrySelected,
+                        onBackButtonClicked = viewModel::onBackToPrimarySelectionScreen,
+                    )
+                }
+            } else if (uiState.selectedEntry != null && !uiState.providerActivityPending) {
+                viewModel.launchProviderUi(providerActivityLauncher)
+            }
+        },
+        scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
+        sheetShape = EntryShape.TopRoundedCorner,
+    ) {}
+    LaunchedEffect(state.currentValue) {
+        if (state.currentValue == ModalBottomSheetValue.Hidden) {
+            viewModel.onCancel()
+        }
+    }
+}
+
+/** Draws the primary credential selection page. */
+@Composable
+fun PrimarySelectionCard(
+    requestDisplayInfo: RequestDisplayInfo,
+    providerDisplayInfo: ProviderDisplayInfo,
+    onEntrySelected: (EntryInfo) -> Unit,
+    onCancel: () -> Unit,
+    onMoreOptionSelected: () -> Unit,
+) {
+    val sortedUserNameToCredentialEntryList =
+        providerDisplayInfo.sortedUserNameToCredentialEntryList
+    val authenticationEntryList = providerDisplayInfo.authenticationEntryList
+    ContainerCard() {
+        Column() {
+            TextOnSurface(
+                modifier = Modifier.padding(all = 24.dp),
+                textAlign = TextAlign.Center,
+                style = MaterialTheme.typography.headlineSmall,
+                text = stringResource(
+                    if (sortedUserNameToCredentialEntryList.size == 1) {
+                        if (sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList
+                                .first().credentialType
+                            == PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL
+                        )
+                            R.string.get_dialog_title_use_passkey_for
+                        else R.string.get_dialog_title_use_sign_in_for
+                    } else R.string.get_dialog_title_choose_sign_in_for,
+                    requestDisplayInfo.appDomainName
+                ),
+            )
+
+            ContainerCard(
+                shape = MaterialTheme.shapes.medium,
+                modifier = Modifier
+                    .padding(horizontal = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally)
+            ) {
+                LazyColumn(
+                    verticalArrangement = Arrangement.spacedBy(2.dp)
+                ) {
+                    items(sortedUserNameToCredentialEntryList) {
+                        CredentialEntryRow(
+                            credentialEntryInfo = it.sortedCredentialEntryList.first(),
+                            onEntrySelected = onEntrySelected,
+                        )
+                    }
+                    items(authenticationEntryList) {
+                        AuthenticationEntryRow(
+                            authenticationEntryInfo = it,
+                            onEntrySelected = onEntrySelected,
+                        )
+                    }
+                    item {
+                        SignInAnotherWayRow(onSelect = onMoreOptionSelected)
+                    }
+                }
+            }
+            Divider(
+                thickness = 24.dp,
+                color = Color.Transparent
+            )
+            Row(
+                horizontalArrangement = Arrangement.Start,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                CancelButton(stringResource(R.string.get_dialog_button_label_no_thanks), onCancel)
+            }
+            Divider(
+                thickness = 18.dp,
+                color = Color.Transparent,
+                modifier = Modifier.padding(bottom = 16.dp)
+            )
+        }
+    }
+}
+
+/** Draws the secondary credential selection page, where all sign-in options are listed. */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AllSignInOptionCard(
+    providerInfoList: List<ProviderInfo>,
+    providerDisplayInfo: ProviderDisplayInfo,
+    onEntrySelected: (EntryInfo) -> Unit,
+    onBackButtonClicked: () -> Unit,
+) {
+    val sortedUserNameToCredentialEntryList =
+        providerDisplayInfo.sortedUserNameToCredentialEntryList
+    val authenticationEntryList = providerDisplayInfo.authenticationEntryList
+    ContainerCard() {
+        Column() {
+            TopAppBar(
+                colors = TopAppBarDefaults.smallTopAppBarColors(
+                    containerColor = Color.Transparent,
+                ),
+                title = {
+                    TextOnSurface(
+                        text = stringResource(R.string.get_dialog_title_sign_in_options),
+                        style = MaterialTheme.typography.titleMedium
+                    )
+                },
+                navigationIcon = {
+                    IconButton(onClick = onBackButtonClicked) {
+                        Icon(
+                            Icons.Filled.ArrowBack,
+                            contentDescription = stringResource(
+                                R.string.accessibility_back_arrow_button)
+                        )
+                    }
+                },
+                modifier = Modifier.padding(top = 12.dp)
+            )
+
+            ContainerCard(
+                shape = MaterialTheme.shapes.large,
+                modifier = Modifier
+                    .padding(start = 24.dp, end = 24.dp, bottom = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally),
+            ) {
+                LazyColumn(
+                    verticalArrangement = Arrangement.spacedBy(8.dp)
+                ) {
+                    // For username
+                    items(sortedUserNameToCredentialEntryList) { item ->
+                        PerUserNameCredentials(
+                            perUserNameCredentialEntryList = item,
+                            onEntrySelected = onEntrySelected,
+                        )
+                    }
+                    // Locked password manager
+                    if (!authenticationEntryList.isEmpty()) {
+                        item {
+                            LockedCredentials(
+                                authenticationEntryList = authenticationEntryList,
+                                onEntrySelected = onEntrySelected,
+                            )
+                        }
+                    }
+                    // From another device
+                    val remoteEntry = providerDisplayInfo.remoteEntry
+                    if (remoteEntry != null) {
+                        item {
+                            RemoteEntryCard(
+                                remoteEntry = remoteEntry,
+                                onEntrySelected = onEntrySelected,
+                            )
+                        }
+                    }
+                    // Manage sign-ins (action chips)
+                    item {
+                        ActionChips(
+                            providerInfoList = providerInfoList,
+                            onEntrySelected = onEntrySelected
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
+
+// TODO: create separate rows for primary and secondary pages.
+// TODO: reuse rows and columns across types.
+
+@Composable
+fun ActionChips(
+    providerInfoList: List<ProviderInfo>,
+    onEntrySelected: (EntryInfo) -> Unit,
+) {
+    val actionChips = providerInfoList.flatMap { it.actionEntryList }
+    if (actionChips.isEmpty()) {
+        return
+    }
+
+    TextSecondary(
+        text = stringResource(R.string.get_dialog_heading_manage_sign_ins),
+        style = MaterialTheme.typography.labelLarge,
+        modifier = Modifier.padding(vertical = 8.dp)
+    )
+    // TODO: tweak padding.
+    ContainerCard(
+        modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+        shape = MaterialTheme.shapes.medium,
+    ) {
+        Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
+            actionChips.forEach {
+                ActionEntryRow(it, onEntrySelected)
+            }
+        }
+    }
+}
+
+@Composable
+fun RemoteEntryCard(
+    remoteEntry: RemoteEntryInfo,
+    onEntrySelected: (EntryInfo) -> Unit,
+) {
+    TextSecondary(
+        text = stringResource(R.string.get_dialog_heading_from_another_device),
+        style = MaterialTheme.typography.labelLarge,
+        modifier = Modifier.padding(vertical = 8.dp)
+    )
+    ContainerCard(
+        modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+        shape = MaterialTheme.shapes.medium,
+    ) {
+        Column(
+            modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+            verticalArrangement = Arrangement.spacedBy(2.dp),
+        ) {
+            Entry(
+                onClick = { onEntrySelected(remoteEntry) },
+                icon = {
+                    Icon(
+                        painter = painterResource(R.drawable.ic_other_devices),
+                        contentDescription = null,
+                        tint = Color.Unspecified,
+                        modifier = Modifier.padding(start = 18.dp)
+                    )
+                },
+                label = {
+                    TextOnSurfaceVariant(
+                        text = stringResource(
+                            R.string.get_dialog_option_headline_use_a_different_device),
+                        style = MaterialTheme.typography.titleLarge,
+                        modifier = Modifier.padding(start = 16.dp, top = 18.dp, bottom = 18.dp)
+                            .align(alignment = Alignment.CenterHorizontally)
+                    )
+                }
+            )
+        }
+    }
+}
+
+@Composable
+fun LockedCredentials(
+    authenticationEntryList: List<AuthenticationEntryInfo>,
+    onEntrySelected: (EntryInfo) -> Unit,
+) {
+    TextSecondary(
+        text = stringResource(R.string.get_dialog_heading_locked_password_managers),
+        style = MaterialTheme.typography.labelLarge,
+        modifier = Modifier.padding(vertical = 8.dp)
+    )
+    ContainerCard(
+        modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+        shape = MaterialTheme.shapes.medium,
+    ) {
+        Column(
+            modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+            verticalArrangement = Arrangement.spacedBy(2.dp),
+        ) {
+            authenticationEntryList.forEach {
+                AuthenticationEntryRow(it, onEntrySelected)
+            }
+        }
+    }
+}
+
+@Composable
+fun PerUserNameCredentials(
+    perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
+    onEntrySelected: (EntryInfo) -> Unit,
+) {
+    TextSecondary(
+        text = stringResource(
+            R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName
+        ),
+        style = MaterialTheme.typography.labelLarge,
+        modifier = Modifier.padding(vertical = 8.dp)
+    )
+    ContainerCard(
+        modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+        shape = MaterialTheme.shapes.medium,
+    ) {
+        Column(
+            modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+            verticalArrangement = Arrangement.spacedBy(2.dp),
+        ) {
+            perUserNameCredentialEntryList.sortedCredentialEntryList.forEach {
+                CredentialEntryRow(it, onEntrySelected)
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun CredentialEntryRow(
+    credentialEntryInfo: CredentialEntryInfo,
+    onEntrySelected: (EntryInfo) -> Unit,
+) {
+    Entry(
+        onClick = { onEntrySelected(credentialEntryInfo) },
+        icon = {
+            Image(
+                modifier = Modifier.padding(start = 10.dp).size(32.dp),
+                bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
+                // TODO: add description.
+                contentDescription = ""
+            )
+        },
+        label = {
+            Column() {
+                // TODO: fix the text values.
+                TextOnSurfaceVariant(
+                    text = credentialEntryInfo.userName,
+                    style = MaterialTheme.typography.titleLarge,
+                    modifier = Modifier.padding(top = 16.dp)
+                )
+                TextSecondary(
+                    text =
+                    if (TextUtils.isEmpty(credentialEntryInfo.displayName))
+                        credentialEntryInfo.credentialTypeDisplayName
+                    else
+                        credentialEntryInfo.credentialTypeDisplayName +
+                                stringResource(
+                                    R.string.get_dialog_sign_in_type_username_separator) +
+                                credentialEntryInfo.displayName,
+                    style = MaterialTheme.typography.bodyMedium,
+                    modifier = Modifier.padding(bottom = 16.dp)
+                )
+            }
+        }
+    )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AuthenticationEntryRow(
+    authenticationEntryInfo: AuthenticationEntryInfo,
+    onEntrySelected: (EntryInfo) -> Unit,
+) {
+    Entry(
+        onClick = { onEntrySelected(authenticationEntryInfo) },
+        icon = {
+            Image(
+                modifier = Modifier.padding(start = 10.dp).size(32.dp),
+                bitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(),
+                // TODO: add description.
+                contentDescription = ""
+            )
+        },
+        label = {
+            Column() {
+                // TODO: fix the text values.
+                TextOnSurfaceVariant(
+                    text = authenticationEntryInfo.title,
+                    style = MaterialTheme.typography.titleLarge,
+                    modifier = Modifier.padding(top = 16.dp)
+                )
+                TextSecondary(
+                    text = stringResource(R.string.locked_credential_entry_label_subtext),
+                    style = MaterialTheme.typography.bodyMedium,
+                    modifier = Modifier.padding(bottom = 16.dp)
+                )
+            }
+        }
+    )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ActionEntryRow(
+    actionEntryInfo: ActionEntryInfo,
+    onEntrySelected: (EntryInfo) -> Unit,
+) {
+    TransparentBackgroundEntry(
+        icon = {
+            Image(
+                modifier = Modifier.padding(start = 10.dp).size(32.dp),
+                bitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(),
+                // TODO: add description.
+                contentDescription = ""
+            )
+        },
+        label = {
+            Column() {
+                TextOnSurfaceVariant(
+                    text = actionEntryInfo.title,
+                    style = MaterialTheme.typography.titleLarge,
+                )
+                if (actionEntryInfo.subTitle != null) {
+                    TextSecondary(
+                        text = actionEntryInfo.subTitle,
+                        style = MaterialTheme.typography.bodyMedium,
+                    )
+                }
+            }
+        },
+        onClick = { onEntrySelected(actionEntryInfo) },
+    )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SignInAnotherWayRow(onSelect: () -> Unit) {
+    Entry(
+        onClick = onSelect,
+        label = {
+            TextOnSurfaceVariant(
+                text = stringResource(R.string.get_dialog_use_saved_passkey_for),
+                style = MaterialTheme.typography.titleLarge,
+                modifier = Modifier.padding(vertical = 16.dp)
+            )
+        }
+    )
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 7b6c30a..7b80124 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -16,7 +16,11 @@
 
 package com.android.credentialmanager.getflow
 
+import android.app.Activity
 import android.util.Log
+import androidx.activity.compose.ManagedActivityResultLauncher
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.IntentSenderRequest
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -25,13 +29,19 @@
 import androidx.lifecycle.ViewModel
 import com.android.credentialmanager.CredentialManagerRepo
 import com.android.credentialmanager.common.DialogResult
+import com.android.credentialmanager.common.ProviderActivityResult
 import com.android.credentialmanager.common.ResultState
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
+import com.android.internal.util.Preconditions
 
 data class GetCredentialUiState(
-  val providers: List<ProviderInfo>,
+  val providerInfoList: List<ProviderInfo>,
   val currentScreenState: GetScreenState,
   val requestDisplayInfo: RequestDisplayInfo,
-  val selectedProvider: ProviderInfo? = null,
+  val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
+  val selectedEntry: EntryInfo? = null,
+  val hidden: Boolean = false,
+  val providerActivityPending: Boolean = false,
 )
 
 class GetCredentialViewModel(
@@ -49,20 +59,78 @@
     return dialogResult
   }
 
-  fun onCredentailSelected(entryKey: String, entrySubkey: String) {
-    Log.d("Account Selector", "credential selected: {key=$entryKey,subkey=$entrySubkey}")
-    CredentialManagerRepo.getInstance().onOptionSelected(
-      uiState.selectedProvider!!.name,
-      entryKey,
-      entrySubkey
-    )
-    dialogResult.value = DialogResult(
-      ResultState.COMPLETE,
-    )
+  fun onEntrySelected(entry: EntryInfo) {
+    Log.d("Account Selector", "credential selected:" +
+            " {provider=${entry.providerId}, key=${entry.entryKey}, subkey=${entry.entrySubkey}}")
+    if (entry.pendingIntent != null) {
+      uiState = uiState.copy(
+        selectedEntry = entry,
+        hidden = true,
+      )
+    } else {
+      CredentialManagerRepo.getInstance().onOptionSelected(
+        entry.providerId, entry.entryKey, entry.entrySubkey,
+      )
+      dialogResult.value = DialogResult(ResultState.COMPLETE)
+    }
+  }
+
+  fun launchProviderUi(
+    launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
+  ) {
+    val entry = uiState.selectedEntry
+    if (entry != null && entry.pendingIntent != null) {
+      uiState = uiState.copy(
+        providerActivityPending = true,
+      )
+      val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
+        .setFillInIntent(entry.fillInIntent).build()
+      launcher.launch(intentSenderRequest)
+    } else {
+      Log.w("Account Selector", "No provider UI to launch")
+    }
+  }
+
+  fun onProviderActivityResult(providerActivityResult: ProviderActivityResult) {
+    val entry = uiState.selectedEntry
+    val resultCode = providerActivityResult.resultCode
+    val resultData = providerActivityResult.data
+    if (resultCode == Activity.RESULT_CANCELED) {
+      // Re-display the CredMan UI if the user canceled from the provider UI.
+      uiState = uiState.copy(
+        selectedEntry = null,
+        hidden = false,
+        providerActivityPending = false,
+      )
+    } else {
+      if (entry != null) {
+        Log.d("Account Selector", "Got provider activity result: {provider=" +
+                "${entry.providerId}, key=${entry.entryKey}, subkey=${entry.entrySubkey}, " +
+                "resultCode=$resultCode, resultData=$resultData}"
+        )
+        CredentialManagerRepo.getInstance().onOptionSelected(
+          entry.providerId, entry.entryKey, entry.entrySubkey,
+          resultCode, resultData,
+        )
+      } else {
+        Log.w("Account Selector",
+          "Illegal state: received a provider result but found no matching entry.")
+      }
+      dialogResult.value = DialogResult(ResultState.COMPLETE)
+    }
   }
 
   fun onMoreOptionSelected() {
     Log.d("Account Selector", "More Option selected")
+    uiState = uiState.copy(
+      currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS
+    )
+  }
+
+  fun onBackToPrimarySelectionScreen() {
+    uiState = uiState.copy(
+      currentScreenState = GetScreenState.PRIMARY_SELECTION
+    )
   }
 
   fun onCancel() {
@@ -70,3 +138,83 @@
     dialogResult.value = DialogResult(ResultState.CANCELED)
   }
 }
+
+private fun toProviderDisplayInfo(
+  providerInfoList: List<ProviderInfo>
+): ProviderDisplayInfo {
+
+  val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>()
+  val authenticationEntryList = mutableListOf<AuthenticationEntryInfo>()
+  val remoteEntryList = mutableListOf<RemoteEntryInfo>()
+  providerInfoList.forEach { providerInfo ->
+    if (providerInfo.authenticationEntry != null) {
+      authenticationEntryList.add(providerInfo.authenticationEntry)
+    }
+    if (providerInfo.remoteEntry != null) {
+      remoteEntryList.add(providerInfo.remoteEntry)
+    }
+
+    providerInfo.credentialEntryList.forEach {
+      userNameToCredentialEntryMap.compute(
+        it.userName
+      ) {
+          _, v ->
+        if (v == null) {
+          mutableListOf(it)
+        } else {
+          v.add(it)
+          v
+        }
+      }
+    }
+  }
+  // There can only be at most one remote entry
+  // TODO: fail elegantly
+  Preconditions.checkState(remoteEntryList.size <= 1)
+
+  // Compose sortedUserNameToCredentialEntryList
+  val comparator = CredentialEntryInfoComparator()
+  // Sort per username
+  userNameToCredentialEntryMap.values.forEach {
+    it.sortWith(comparator)
+  }
+  // Transform to list of PerUserNameCredentialEntryLists and then sort across usernames
+  val sortedUserNameToCredentialEntryList = userNameToCredentialEntryMap.map {
+    PerUserNameCredentialEntryList(it.key, it.value)
+  }.sortedWith(
+    compareBy(comparator) { it.sortedCredentialEntryList.first() }
+  )
+
+  return ProviderDisplayInfo(
+    sortedUserNameToCredentialEntryList = sortedUserNameToCredentialEntryList,
+    authenticationEntryList = authenticationEntryList,
+    remoteEntry = remoteEntryList.getOrNull(0),
+  )
+}
+
+internal class CredentialEntryInfoComparator : Comparator<CredentialEntryInfo> {
+  override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
+    // First order by last used timestamp
+    if (p0.lastUsedTimeMillis != null && p1.lastUsedTimeMillis != null) {
+      if (p0.lastUsedTimeMillis < p1.lastUsedTimeMillis) {
+        return 1
+      } else if (p0.lastUsedTimeMillis > p1.lastUsedTimeMillis) {
+        return -1
+      }
+    } else if (p0.lastUsedTimeMillis != null && p0.lastUsedTimeMillis > 0) {
+      return -1
+    } else if (p1.lastUsedTimeMillis != null && p1.lastUsedTimeMillis > 0) {
+      return 1
+    }
+
+    // Then prefer passkey type for its security benefits
+    if (p0.credentialType != p1.credentialType) {
+      if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p0.credentialType) {
+        return -1
+      } else if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p1.credentialType) {
+        return 1
+      }
+    }
+    return 0
+  }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index b6ecd37..0c3baff 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -16,33 +16,108 @@
 
 package com.android.credentialmanager.getflow
 
+import android.app.PendingIntent
+import android.content.Intent
 import android.graphics.drawable.Drawable
 
 data class ProviderInfo(
+  /**
+   * Unique id (component name) of this provider.
+   * Not for display purpose - [displayName] should be used for ui rendering.
+   */
+  val id: String,
   val icon: Drawable,
-  val name: String,
   val displayName: String,
-  val credentialTypeIcon: Drawable,
-  val credentialOptions: List<CredentialOptionInfo>,
+  val credentialEntryList: List<CredentialEntryInfo>,
+  val authenticationEntry: AuthenticationEntryInfo?,
+  val remoteEntry: RemoteEntryInfo?,
+  val actionEntryList: List<ActionEntryInfo>,
 )
 
-data class CredentialOptionInfo(
-  val icon: Drawable,
-  val title: String,
-  val subtitle: String,
+/** Display-centric data structure derived from the [ProviderInfo]. This abstraction is not grouping
+ *  by the provider id but instead focuses on structures convenient for display purposes. */
+data class ProviderDisplayInfo(
+  /**
+   * The credential entries grouped by userName, derived from all entries of the [providerInfoList].
+   * Note that the list order matters to the display order.
+   */
+  val sortedUserNameToCredentialEntryList: List<PerUserNameCredentialEntryList>,
+  val authenticationEntryList: List<AuthenticationEntryInfo>,
+  val remoteEntry: RemoteEntryInfo?
+)
+
+abstract class EntryInfo (
+  /** Unique id combination of this entry. Not for display purpose. */
+  val providerId: String,
   val entryKey: String,
   val entrySubkey: String,
-  val usageData: String
+  val pendingIntent: PendingIntent?,
+  val fillInIntent: Intent?,
 )
 
-data class RequestDisplayInfo(
+class CredentialEntryInfo(
+  providerId: String,
+  entryKey: String,
+  entrySubkey: String,
+  pendingIntent: PendingIntent?,
+  fillInIntent: Intent?,
+  /** Type of this credential used for sorting. Not localized so must not be directly displayed. */
+  val credentialType: String,
+  /** Localized type value of this credential used for display purpose. */
+  val credentialTypeDisplayName: String,
   val userName: String,
-  val displayName: String,
-  val type: String,
+  val displayName: String?,
+  val icon: Drawable,
+  val lastUsedTimeMillis: Long?,
+) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+
+class AuthenticationEntryInfo(
+  providerId: String,
+  entryKey: String,
+  entrySubkey: String,
+  pendingIntent: PendingIntent?,
+  fillInIntent: Intent?,
+  val title: String,
+  val icon: Drawable,
+) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+
+class RemoteEntryInfo(
+  providerId: String,
+  entryKey: String,
+  entrySubkey: String,
+  pendingIntent: PendingIntent?,
+  fillInIntent: Intent?,
+) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+
+class ActionEntryInfo(
+  providerId: String,
+  entryKey: String,
+  entrySubkey: String,
+  pendingIntent: PendingIntent?,
+  fillInIntent: Intent?,
+  val title: String,
+  val icon: Drawable,
+  val subTitle: String?,
+) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
+
+data class RequestDisplayInfo(
   val appDomainName: String,
 )
 
+/**
+ * @property userName the userName that groups all the entries in this list
+ * @property sortedCredentialEntryList the credential entries associated with the [userName] sorted
+ *                                     by last used timestamps and then by credential types
+ */
+data class PerUserNameCredentialEntryList(
+  val userName: String,
+  val sortedCredentialEntryList: List<CredentialEntryInfo>,
+)
+
 /** The name of the current screen. */
 enum class GetScreenState {
-  CREDENTIAL_SELECTION,
+  /** The primary credential selection page. */
+  PRIMARY_SELECTION,
+  /** The secondary credential selection page, where all sign-in options are listed. */
+  ALL_SIGN_IN_OPTIONS,
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt
deleted file mode 100644
index d4341b4..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt
+++ /dev/null
@@ -1,53 +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.jetpack
-
-import android.app.slice.Slice
-import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
-
-/**
- * UI representation for a credential entry used during the get credential flow.
- *
- * TODO: move to jetpack.
- */
-class ActionUi(
-  val icon: Icon,
-  val text: CharSequence,
-  val subtext: CharSequence?,
-) {
-  companion object {
-    fun fromSlice(slice: Slice): ActionUi {
-      var icon: Icon? = null
-      var text: CharSequence? = null
-      var subtext: CharSequence? = null
-
-      val items = slice.items
-      items.forEach {
-        if (it.hasHint(Entry.HINT_ACTION_ICON)) {
-          icon = it.icon
-        } else if (it.hasHint(Entry.HINT_ACTION_TITLE)) {
-          text = it.text
-        } else if (it.hasHint(Entry.HINT_ACTION_SUBTEXT)) {
-          subtext = it.text
-        }
-      }
-      // TODO: fail NPE more elegantly.
-      return ActionUi(icon!!, text!!, subtext)
-    }
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt
deleted file mode 100644
index d6f1b5f..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt
+++ /dev/null
@@ -1,46 +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.jetpack
-
-import android.app.slice.Slice
-import android.graphics.drawable.Icon
-
-/**
- * UI representation for a credential entry used during the get credential flow.
- *
- * TODO: move to jetpack.
- */
-abstract class CredentialEntryUi(
-  val credentialTypeIcon: Icon,
-  val profileIcon: Icon?,
-  val lastUsedTimeMillis: Long?,
-  val note: CharSequence?,
-) {
-  companion object {
-    fun fromSlice(slice: Slice): CredentialEntryUi {
-      return when (slice.spec?.type) {
-        TYPE_PUBLIC_KEY_CREDENTIAL -> PasskeyCredentialEntryUi.fromSlice(slice)
-        TYPE_PASSWORD_CREDENTIAL -> PasswordCredentialEntryUi.fromSlice(slice)
-        else -> throw IllegalArgumentException("Unexpected type: ${slice.spec?.type}")
-      }
-    }
-
-    const val TYPE_PUBLIC_KEY_CREDENTIAL: String =
-      "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"
-    const val TYPE_PASSWORD_CREDENTIAL: String = "androidx.credentials.TYPE_PASSWORD"
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt
deleted file mode 100644
index bb3b206..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt
+++ /dev/null
@@ -1,63 +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.jetpack
-
-import android.app.slice.Slice
-import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
-
-class PasskeyCredentialEntryUi(
-  val userName: CharSequence,
-  val userDisplayName: CharSequence?,
-  credentialTypeIcon: Icon,
-  profileIcon: Icon?,
-  lastUsedTimeMillis: Long?,
-  note: CharSequence?,
-) : CredentialEntryUi(credentialTypeIcon, profileIcon, lastUsedTimeMillis, note) {
-  companion object {
-    fun fromSlice(slice: Slice): CredentialEntryUi {
-      var userName: CharSequence? = null
-      var userDisplayName: CharSequence? = null
-      var credentialTypeIcon: Icon? = null
-      var profileIcon: Icon? = null
-      var lastUsedTimeMillis: Long? = null
-      var note: CharSequence? = null
-
-      val items = slice.items
-      items.forEach {
-        if (it.hasHint(Entry.HINT_USER_NAME)) {
-          userName = it.text
-        } else if (it.hasHint(Entry.HINT_PASSKEY_USER_DISPLAY_NAME)) {
-          userDisplayName = it.text
-        } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) {
-          credentialTypeIcon = it.icon
-        } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
-          profileIcon = it.icon
-        } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
-          lastUsedTimeMillis = it.long
-        } else if (it.hasHint(Entry.HINT_NOTE)) {
-          note = it.text
-        }
-      }
-      // TODO: fail NPE more elegantly.
-      return PasskeyCredentialEntryUi(
-        userName!!, userDisplayName, credentialTypeIcon!!,
-        profileIcon, lastUsedTimeMillis, note,
-      )
-    }
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt
deleted file mode 100644
index 7311b70..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt
+++ /dev/null
@@ -1,68 +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.jetpack
-
-import android.app.slice.Slice
-import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
-
-/**
- * UI representation for a password credential entry used during the get credential flow.
- *
- * TODO: move to jetpack.
- */
-class PasswordCredentialEntryUi(
-  val userName: CharSequence,
-  val password: CharSequence,
-  credentialTypeIcon: Icon,
-  profileIcon: Icon?,
-  lastUsedTimeMillis: Long?,
-  note: CharSequence?,
-) : CredentialEntryUi(credentialTypeIcon, profileIcon, lastUsedTimeMillis, note) {
-  companion object {
-    fun fromSlice(slice: Slice): CredentialEntryUi {
-      var userName: CharSequence? = null
-      var password: CharSequence? = null
-      var credentialTypeIcon: Icon? = null
-      var profileIcon: Icon? = null
-      var lastUsedTimeMillis: Long? = null
-      var note: CharSequence? = null
-
-      val items = slice.items
-      items.forEach {
-        if (it.hasHint(Entry.HINT_USER_NAME)) {
-          userName = it.text
-        } else if (it.hasHint(Entry.HINT_PASSWORD_VALUE)) {
-          password = it.text
-        } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) {
-          credentialTypeIcon = it.icon
-        } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
-          profileIcon = it.icon
-        } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
-          lastUsedTimeMillis = it.long
-        } else if (it.hasHint(Entry.HINT_NOTE)) {
-          note = it.text
-        }
-      }
-      // TODO: fail NPE more elegantly.
-      return PasswordCredentialEntryUi(
-        userName!!, password!!, credentialTypeIcon!!,
-        profileIcon, lastUsedTimeMillis, note,
-      )
-    }
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt
deleted file mode 100644
index fad3309..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt
+++ /dev/null
@@ -1,73 +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.jetpack
-
-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 userProviderAccountName: CharSequence,
-  val credentialTypeIcon: Icon?,
-  val profileIcon: Icon?,
-  val passwordCount: Int?,
-  val passkeyCount: Int?,
-  val totalCredentialCount: Int?,
-  val lastUsedTimeMillis: Long?,
-) {
-  companion object {
-    fun fromSlice(slice: Slice): SaveEntryUi {
-      var userProviderAccountName: CharSequence? = null
-      var credentialTypeIcon: Icon? = null
-      var profileIcon: Icon? = null
-      var passwordCount: Int? = null
-      var passkeyCount: Int? = null
-      var totalCredentialCount: Int? = null
-      var lastUsedTimeMillis: Long? = null
-
-
-      val items = slice.items
-      items.forEach {
-        if (it.hasHint(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME)) {
-          userProviderAccountName = it.text
-        } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) {
-          credentialTypeIcon = it.icon
-        } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
-          profileIcon = it.icon
-        } else if (it.hasHint(Entry.HINT_PASSWORD_COUNT)) {
-          passwordCount = it.int
-        } else if (it.hasHint(Entry.HINT_PASSKEY_COUNT)) {
-          passkeyCount = it.int
-        } else if (it.hasHint(Entry.HINT_TOTAL_CREDENTIAL_COUNT)) {
-          totalCredentialCount = it.int
-        } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
-          lastUsedTimeMillis = it.long
-        }
-      }
-      // TODO: fail NPE more elegantly.
-      return SaveEntryUi(
-        userProviderAccountName!!, credentialTypeIcon, profileIcon,
-        passwordCount, passkeyCount, totalCredentialCount, lastUsedTimeMillis,
-      )
-    }
-  }
-}
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..008e1b6
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.credentialData)
+                    PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
+                        CreatePublicKeyCredentialBaseRequest.createFrom(from.credentialData)
+                    else ->
+                        CreateCredentialRequest(
+                            from.type, from.credentialData, from.requireSystemProvider()
+                        )
+                }
+            } catch (e: FrameworkClassParsingException) {
+                CreateCredentialRequest(
+                    from.type, from.credentialData, 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..37a4f76
--- /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 ->
+                    CreatePublicKeyCredentialRequest.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/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt
new file mode 100644
index 0000000..ee08e9e
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * 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/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt
new file mode 100644
index 0000000..497c272
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt
@@ -0,0 +1,25 @@
+/*
+ * 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
+
+/**
+ * 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/provider/ActionUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt
new file mode 100644
index 0000000..1e639fe
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.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.credentialmanager.jetpack.provider
+
+import android.app.slice.Slice
+import android.credentials.ui.Entry
+import android.graphics.drawable.Icon
+
+/**
+ * UI representation for a credential entry used during the get credential flow.
+ *
+ * TODO: move to jetpack.
+ */
+class ActionUi(
+  val icon: Icon,
+  val text: CharSequence,
+  val subtext: CharSequence?,
+) {
+  companion object {
+    fun fromSlice(slice: Slice): ActionUi {
+      var icon: Icon? = null
+      var text: CharSequence? = null
+      var subtext: CharSequence? = null
+
+      val items = slice.items
+      items.forEach {
+        if (it.hasHint(Entry.HINT_ACTION_ICON)) {
+          icon = it.icon
+        } else if (it.hasHint(Entry.HINT_ACTION_TITLE)) {
+          text = it.text
+        } else if (it.hasHint(Entry.HINT_ACTION_SUBTEXT)) {
+          subtext = it.text
+        }
+      }
+      // TODO: fail NPE more elegantly.
+      return ActionUi(icon!!, text!!, subtext)
+    }
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
new file mode 100644
index 0000000..1693eb6
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.provider
+
+import android.app.slice.Slice
+import android.credentials.ui.Entry
+import android.graphics.drawable.Icon
+
+/**
+ * UI representation for a credential entry used during the get credential flow.
+ *
+ * TODO: move to jetpack.
+ */
+class CredentialEntryUi(
+  val credentialType: CharSequence,
+  val credentialTypeDisplayName: CharSequence,
+  val userName: CharSequence,
+  val userDisplayName: CharSequence?,
+  val entryIcon: Icon?,
+  val lastUsedTimeMillis: Long?,
+  val note: CharSequence?,
+) {
+  companion object {
+    fun fromSlice(slice: Slice): CredentialEntryUi {
+      var credentialType = slice.spec!!.type
+      var credentialTypeDisplayName: CharSequence? = null
+      var userName: CharSequence? = null
+      var userDisplayName: CharSequence? = null
+      var entryIcon: Icon? = null
+      var lastUsedTimeMillis: Long? = null
+      var note: CharSequence? = null
+
+      val items = slice.items
+      items.forEach {
+        if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_DISPLAY_NAME)) {
+          credentialTypeDisplayName = it.text
+        } else if (it.hasHint(Entry.HINT_USER_NAME)) {
+          userName = it.text
+        } else if (it.hasHint(Entry.HINT_PASSKEY_USER_DISPLAY_NAME)) {
+          userDisplayName = it.text
+        } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
+          entryIcon = it.icon
+        } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
+          lastUsedTimeMillis = it.long
+        } else if (it.hasHint(Entry.HINT_NOTE)) {
+          note = it.text
+        }
+      }
+
+      return CredentialEntryUi(
+        credentialType, credentialTypeDisplayName!!, userName!!, userDisplayName, entryIcon,
+        lastUsedTimeMillis, note,
+      )
+    }
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
new file mode 100644
index 0000000..bcc0531
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
@@ -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.
+ */
+
+package com.android.credentialmanager.jetpack.provider
+
+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 userProviderAccountName: CharSequence?,
+  val credentialTypeIcon: Icon?,
+  val profileIcon: Icon?,
+  val passwordCount: Int?,
+  val passkeyCount: Int?,
+  val totalCredentialCount: Int?,
+  val lastUsedTimeMillis: Long?,
+) {
+  companion object {
+    fun fromSlice(slice: Slice): SaveEntryUi {
+      var userProviderAccountName: CharSequence? = null
+      var credentialTypeIcon: Icon? = null
+      var profileIcon: Icon? = null
+      var passwordCount: Int? = null
+      var passkeyCount: Int? = null
+      var totalCredentialCount: Int? = null
+      var lastUsedTimeMillis: Long? = null
+
+
+      val items = slice.items
+      items.forEach {
+        if (it.hasHint(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME)) {
+          userProviderAccountName = it.text
+        } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) {
+          credentialTypeIcon = it.icon
+        } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
+          profileIcon = it.icon
+        } else if (it.hasHint(Entry.HINT_PASSWORD_COUNT)) {
+          passwordCount = it.int
+        } else if (it.hasHint(Entry.HINT_PASSKEY_COUNT)) {
+          passkeyCount = it.int
+        } else if (it.hasHint(Entry.HINT_TOTAL_CREDENTIAL_COUNT)) {
+          totalCredentialCount = it.int
+        } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
+          lastUsedTimeMillis = it.long
+        }
+      }
+      // TODO: fail NPE more elegantly.
+      return SaveEntryUi(
+        userProviderAccountName!!, credentialTypeIcon, profileIcon,
+        passwordCount, passkeyCount, totalCredentialCount, lastUsedTimeMillis,
+      )
+    }
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
new file mode 100644
index 0000000..15ae329
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
@@ -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.credentialmanager.ui.theme
+
+import android.annotation.ColorInt
+import android.content.Context
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+import com.android.internal.R
+
+/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */
+val LocalAndroidColorScheme =
+    staticCompositionLocalOf<AndroidColorScheme> {
+        throw IllegalStateException(
+            "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " +
+                    "Composable surrounded by a CredentialSelectorTheme {}."
+        )
+    }
+
+/**
+ * The Android color scheme.
+ *
+ * Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future,
+ * most of the colors in this class will be removed in favor of their M3 counterpart.
+ */
+class AndroidColorScheme internal constructor(context: Context) {
+
+    val colorPrimary = getColor(context, R.attr.colorPrimary)
+    val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark)
+    val colorAccent = getColor(context, R.attr.colorAccent)
+    val colorAccentPrimary = getColor(context, R.attr.colorAccentPrimary)
+    val colorAccentSecondary = getColor(context, R.attr.colorAccentSecondary)
+    val colorAccentTertiary = getColor(context, R.attr.colorAccentTertiary)
+    val colorAccentPrimaryVariant = getColor(context, R.attr.colorAccentPrimaryVariant)
+    val colorAccentSecondaryVariant = getColor(context, R.attr.colorAccentSecondaryVariant)
+    val colorAccentTertiaryVariant = getColor(context, R.attr.colorAccentTertiaryVariant)
+    val colorSurface = getColor(context, R.attr.colorSurface)
+    val colorSurfaceHighlight = getColor(context, R.attr.colorSurfaceHighlight)
+    val colorSurfaceVariant = getColor(context, R.attr.colorSurfaceVariant)
+    val colorSurfaceHeader = getColor(context, R.attr.colorSurfaceHeader)
+    val colorError = getColor(context, R.attr.colorError)
+    val colorBackground = getColor(context, R.attr.colorBackground)
+    val colorBackgroundFloating = getColor(context, R.attr.colorBackgroundFloating)
+    val panelColorBackground = getColor(context, R.attr.panelColorBackground)
+    val textColorPrimary = getColor(context, R.attr.textColorPrimary)
+    val textColorSecondary = getColor(context, R.attr.textColorSecondary)
+    val textColorTertiary = getColor(context, R.attr.textColorTertiary)
+    val textColorPrimaryInverse = getColor(context, R.attr.textColorPrimaryInverse)
+    val textColorSecondaryInverse = getColor(context, R.attr.textColorSecondaryInverse)
+    val textColorTertiaryInverse = getColor(context, R.attr.textColorTertiaryInverse)
+    val textColorOnAccent = getColor(context, R.attr.textColorOnAccent)
+    val colorForeground = getColor(context, R.attr.colorForeground)
+    val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)
+
+    private fun getColor(context: Context, attr: Int): Color {
+        val ta = context.obtainStyledAttributes(intArrayOf(attr))
+        @ColorInt val color = ta.getColor(0, 0)
+        ta.recycle()
+        return Color(color)
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
index cba8658..d8a8f16 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
@@ -1,11 +1,20 @@
 package com.android.credentialmanager.ui.theme
 
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Shapes
+import androidx.compose.material3.Shapes
 import androidx.compose.ui.unit.dp
 
 val Shapes = Shapes(
   small = RoundedCornerShape(100.dp),
-  medium = RoundedCornerShape(20.dp),
+  medium = RoundedCornerShape(28.dp),
   large = RoundedCornerShape(0.dp)
 )
+
+object EntryShape {
+  val TopRoundedCorner = RoundedCornerShape(28.dp, 28.dp, 0.dp, 0.dp)
+  val BottomRoundedCorner = RoundedCornerShape(0.dp, 0.dp, 28.dp, 28.dp)
+  // Used for middle entries.
+  val FullSmallRoundedCorner = RoundedCornerShape(4.dp, 4.dp, 4.dp, 4.dp)
+  // Used for when there's a single entry.
+  val FullMediumRoundedCorner = RoundedCornerShape(28.dp, 28.dp, 28.dp, 28.dp)
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
index a9d20ae..3ca0e44 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
@@ -1,47 +1,38 @@
 package com.android.credentialmanager.ui.theme
 
 import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.darkColors
-import androidx.compose.material.lightColors
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
 import androidx.compose.runtime.Composable
-
-private val DarkColorPalette = darkColors(
-  primary = Purple200,
-  primaryVariant = Purple700,
-  secondary = Teal200
-)
-
-private val LightColorPalette = lightColors(
-  primary = Purple500,
-  primaryVariant = Purple700,
-  secondary = Teal200
-
-  /* Other default colors to override
-    background = Color.White,
-    surface = Color.White,
-    onPrimary = Color.White,
-    onSecondary = Color.Black,
-    onBackground = Color.Black,
-    onSurface = Color.Black,
-    */
-)
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
 
 @Composable
 fun CredentialSelectorTheme(
   darkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable () -> Unit
 ) {
-  val colors = if (darkTheme) {
-    DarkColorPalette
-  } else {
-    LightColorPalette
-  }
+  val context = LocalContext.current
+
+  val colorScheme =
+    if (darkTheme) {
+      dynamicDarkColorScheme(context)
+    } else {
+      dynamicLightColorScheme(context)
+    }
+  val androidColorScheme = AndroidColorScheme(context)
+  val typography = Typography
 
   MaterialTheme(
-    colors = colors,
-    typography = Typography,
-    shapes = Shapes,
-    content = content
-  )
+    colorScheme,
+    typography = typography,
+    shapes = Shapes
+  ) {
+    CompositionLocalProvider(
+      LocalAndroidColorScheme provides androidColorScheme,
+    ) {
+      content()
+    }
+  }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
index d8fb01c..e09abbb 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
@@ -1,6 +1,6 @@
 package com.android.credentialmanager.ui.theme
 
-import androidx.compose.material.Typography
+import androidx.compose.material3.Typography
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontWeight
@@ -8,32 +8,32 @@
 
 // Set of Material typography styles to start with
 val Typography = Typography(
-  subtitle1 = TextStyle(
+  titleMedium = TextStyle(
     fontFamily = FontFamily.Default,
     fontWeight = FontWeight.Normal,
     fontSize = 24.sp,
     lineHeight = 32.sp,
   ),
-  body1 = TextStyle(
+  bodyLarge = TextStyle(
     fontFamily = FontFamily.Default,
     fontWeight = FontWeight.Normal,
     fontSize = 14.sp,
     lineHeight = 20.sp,
   ),
-  body2 = TextStyle(
+  bodyMedium = TextStyle(
     fontFamily = FontFamily.Default,
     fontWeight = FontWeight.Normal,
     fontSize = 14.sp,
     lineHeight = 20.sp,
     color = textColorSecondary
   ),
-  button = TextStyle(
+  labelLarge = TextStyle(
     fontFamily = FontFamily.Default,
     fontWeight = FontWeight.Medium,
     fontSize = 14.sp,
     lineHeight = 20.sp,
   ),
-  h6 = TextStyle(
+  titleLarge = TextStyle(
     fontFamily = FontFamily.Default,
     fontWeight = FontWeight.Medium,
     fontSize = 16.sp,
diff --git a/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml b/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml
index 62dba98..0867293 100644
--- a/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml
@@ -3,8 +3,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="keyguard_description" msgid="8582605799129954556">"Please enter your password and continue to Dynamic System Updates"</string>
     <string name="notification_install_completed" msgid="6252047868415172643">"Dynamic system is ready. To start using it, restart your device."</string>
-    <string name="notification_install_inprogress" msgid="7383334330065065017">"Installation in progress"</string>
-    <string name="notification_install_failed" msgid="4066039210317521404">"Installation failed"</string>
+    <string name="notification_install_inprogress" msgid="7383334330065065017">"Install in progress"</string>
+    <string name="notification_install_failed" msgid="4066039210317521404">"Install failed"</string>
     <string name="notification_image_validation_failed" msgid="2720357826403917016">"Image validation failed. Abort installation."</string>
     <string name="notification_dynsystem_in_use" msgid="1053194595682188396">"Currently running a dynamic system. Restart to use the original Android version."</string>
     <string name="notification_action_cancel" msgid="5929299408545961077">"Cancel"</string>
diff --git a/packages/InputDevices/res/values-en-rCA/strings.xml b/packages/InputDevices/res/values-en-rCA/strings.xml
index ab48729..1161783 100644
--- a/packages/InputDevices/res/values-en-rCA/strings.xml
+++ b/packages/InputDevices/res/values-en-rCA/strings.xml
@@ -19,7 +19,7 @@
     <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Swiss German"</string>
     <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgian"</string>
     <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarian"</string>
-    <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgarian, phonetic"</string>
+    <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgarian, Phonetic"</string>
     <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italian"</string>
     <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danish"</string>
     <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norwegian"</string>
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 4757513..fe640ad 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -58,6 +58,7 @@
     privileged: true,
     platform_apis: true,
     rename_resources_package: false,
+    overrides: ["PackageInstaller"],
 
     static_libs: [
         "xz-java",
@@ -76,6 +77,7 @@
     privileged: true,
     platform_apis: true,
     rename_resources_package: false,
+    overrides: ["PackageInstaller"],
 
     static_libs: [
         "xz-java",
diff --git a/packages/PackageInstaller/res/values-af/strings.xml b/packages/PackageInstaller/res/values-af/strings.xml
index a6dcd5d..7460e0a 100644
--- a/packages/PackageInstaller/res/values-af/strings.xml
+++ b/packages/PackageInstaller/res/values-af/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Onbekend"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Jou tablet word vir jou veiligheid tans nie toegelaat om onbekende programme van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Jou TV word vir jou veiligheid tans nie toegelaat om onbekende programme van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Jou horlosie word vir jou veiligheid tans nie toegelaat om onbekende apps van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Jou foon word vir jou veiligheid tans nie toegelaat om onbekende programme van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Jou foon en persoonlike data is meer kwesbaar vir aanvalle deur onbekende programme. Deur hierdie program te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou foon of verlies van data wat uit sy gebruik kan spruit."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Jou tablet en persoonlike data is meer kwesbaar vir aanvalle deur onbekende programme. Deur hierdie program te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou tablet of verlies van data wat uit sy gebruik kan spruit."</string>
diff --git a/packages/PackageInstaller/res/values-am/strings.xml b/packages/PackageInstaller/res/values-am/strings.xml
index e58923a..094ece7 100644
--- a/packages/PackageInstaller/res/values-am/strings.xml
+++ b/packages/PackageInstaller/res/values-am/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"ያልታወቀ"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"ለእርስዎ ደህንነት ሲባል በአሁኑ ጊዜ ጡባዊዎ ከዚህ ምንጭ ያልታወቁ መተግበሪያዎችን እንዲጭን አይፈቀድለትም። ይህን በቅንብሮች ውስጥ መቀየር ይችላሉ።"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"ለእርስዎ ደህንነት ሲባል በአሁኑ ጊዜ የእርስዎ ቲቪ ከዚህ ምንጭ ያልታወቁ መተግበሪያዎችን እንዲጭን አይፈቀድለትም። ይህን በቅንብሮች ውስጥ መቀየር ይችላሉ።"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ለደህንነትዎ ሲባል በአሁኑ ጊዜ የእጅ ሰዓትዎ ያልታወቁ መተግበሪያዎችን ከዚህ ምንጭ እንዲጭን አይፈቀድለትም። ይህን በቅንብሮች ውስጥ መለወጥ ይችላሉ።"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ለእርስዎ ደህንነት ሲባል በአሁኑ ጊዜ ስልክዎ ከዚህ ምንጭ ያልታወቁ መተግበሪያዎችን እንዲጭን አልተፈቀደለትም። ይህን በቅንብሮች ውስጥ መቀየር ይችላሉ።"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"የእርስዎ ስልክ እና የግል ውሂብ በማይታወቁ መተግበሪያዎች ለሚደርሱ ጥቃቶች በይልበልጥ ተጋላጭ ናቸው። ይህን መተግበሪያ በመጫንዎ በእርስዎ ስልክ ላይ ለሚደርስ ማናቸውም ጉዳት ወይም መተግበሪያውን በመጠቀም ለሚከሰት የውሂብ መጥፋት ኃላፊነቱን እንደሚወስዱ ተስማምተዋል።"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"የእርስዎ ጡባዊ እና የግል ውሂብ በማይታወቁ መተግበሪያዎች ለሚደርሱ ጥቃቶች በይበልጥ ተጋላጭ ናቸው። ይህን መተግበሪያ በመጫንዎ በእርስዎ ጡባዊ ላይ ለሚደርስ ማናቸውም ጉዳት ወይም መተግበሪያውን በመጠቀም ለሚከሰት የውሂብ መጥፋት ኃላፊነቱን እንደሚወስዱ ተስማምተዋል።"</string>
diff --git a/packages/PackageInstaller/res/values-ar/strings.xml b/packages/PackageInstaller/res/values-ar/strings.xml
index 1dea809..26b5dae 100644
--- a/packages/PackageInstaller/res/values-ar/strings.xml
+++ b/packages/PackageInstaller/res/values-ar/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"غير معروف"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"لأغراض الأمان، غير مسموح حاليًا لجهازك اللوحي بتثبيت تطبيقات غير معروفة من هذا المصدر. ويمكنك تغيير ذلك في \"الإعدادات\"."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"لأغراض الأمان، غير مسموح حاليًا لجهاز التلفزيون الذي تستخدمه بتثبيت تطبيقات غير معروفة من هذا المصدر. ويمكنك تغيير ذلك في \"الإعدادات\"."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"للحفاظ على أمانك، غير مسموح حاليًا لساعتك بتثبيت تطبيقات غير معروفة من هذا المصدر. يمكنك تغيير ذلك في الإعدادات."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"لأغراض الأمان، غير مسموح حاليًا لهاتفك بتثبيت تطبيقات غير معروفة من هذا المصدر. يمكنك تغيير ذلك في \"الإعدادات\"."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"يعتبر الهاتف والبيانات الشخصية أكثر عرضة لهجوم التطبيقات غير المعروفة. من خلال تثبيت هذا التطبيق، توافق على تحمل مسؤولية أي ضرر يحدث لهاتفك أو فقدان البيانات الذي قد ينتج عن استخدامه."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"يعتبر الجهاز اللوحي والبيانات الشخصية أكثر عرضة لهجوم التطبيقات غير المعروفة. من خلال تثبيت هذا التطبيق، توافق على تحمل مسؤولية أي ضرر يحدث للجهاز اللوحي أو فقدان البيانات الذي قد ينتج عن استخدامه."</string>
diff --git a/packages/PackageInstaller/res/values-as/strings.xml b/packages/PackageInstaller/res/values-as/strings.xml
index 8405335..f11fd41 100644
--- a/packages/PackageInstaller/res/values-as/strings.xml
+++ b/packages/PackageInstaller/res/values-as/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"অজ্ঞাত"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ টেবলেটটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্‌ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ টিভিটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্‌ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ ঘড়ীটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্‌ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ ফ’নটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্‌ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"আপোনাৰ ফ\'ন আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপ্‌টো ইনষ্টল কৰি এপ্‌টোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"আপোনাৰ টেবলেট আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপ্‌টো ইনষ্টল কৰি এপ্‌টোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
diff --git a/packages/PackageInstaller/res/values-az/strings.xml b/packages/PackageInstaller/res/values-az/strings.xml
index e4f8541..9746964 100644
--- a/packages/PackageInstaller/res/values-az/strings.xml
+++ b/packages/PackageInstaller/res/values-az/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Naməlum"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Təhlükəsizliyiniz üçün planşetinizə bu mənbədən olan naməlum tətbiqləri quraşdırmağa icazə verilmir. Bunu Ayarlarda dəyişə bilərsiniz."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Təhlükəsizliyiniz üçün TV-nizə bu mənbədən olan naməlum tətbiqləri quraşdırmağa icazə verilmir. Bunu Ayarlarda dəyişə bilərsiniz."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Təhlükəsizliyiniz üçün saatınıza bu mənbədən olan naməlum tətbiqləri quraşdırmağa icazə verilmir. Bunu Ayarlarda dəyişə bilərsiniz."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Təhlükəsizliyiniz üçün telefonunuza bu mənbədən olan naməlum tətbiqləri quraşdırmağa icazə verilmir. Bunu Ayarlarda dəyişə bilərsiniz."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Şəxsi məlumatlarınız naməlum mənbə tətbiqlərindən olan hücumlar tərəfindən ələ keçirilə bilər. Bu cür tətbiqləri quraşdırmaqla smartfona dəyəcək bütün zədələrə, məlumatlarınızın oğurlanmasına və itirilməsinə görə məsuliyyəti öz üzərinizə götürdüyünüzü qəbul edirsiniz."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Planşet və şəxsi data naməlum tətbiqlərin hücumuna qarşı daha həssasdır. Bu tətbiqi quraşdırmaqla planşetə dəyə biləcək zərər və ya onun istifadəsi nəticəsində baş verə biləcək data itkisinə görə məsuliyyət daşıdığınızı qəbul edirsiniz."</string>
diff --git a/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml b/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml
index f646b20..c149f80 100644
--- a/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml
+++ b/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Nepoznato"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Iz bezbednosnih razloga tabletu trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. To možete da promenite u podešavanjima."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Iz bezbednosnih razloga televizoru trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. To možete da promenite u podešavanjima."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Iz bezbednosnih razloga satu trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. To možete da promenite u Podešavanjima."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Iz bezbednosnih razloga telefonu trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. To možete da promenite u podešavanjima."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefon i lični podaci su podložniji napadu nepoznatih aplikacija. Ako instalirate ovu aplikaciju, prihvatate da ste odgovorni za eventualna oštećenja telefona ili gubitak podataka do kojih može da dođe zbog njenog korišćenja."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tablet i lični podaci su podložniji napadu nepoznatih aplikacija. Ako instalirate ovu aplikaciju, prihvatate da ste odgovorni za eventualna oštećenja tableta ili gubitak podataka do kojih može da dođe zbog njenog korišćenja."</string>
diff --git a/packages/PackageInstaller/res/values-be/strings.xml b/packages/PackageInstaller/res/values-be/strings.xml
index 8a3fd9f..79ea8de 100644
--- a/packages/PackageInstaller/res/values-be/strings.xml
+++ b/packages/PackageInstaller/res/values-be/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Невядома"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"У мэтах бяспекі вашаму планшэту забаронена ўсталёўваць невядомыя праграмы з гэтай крыніцы. Вы можаце змяніць гэты дазвол у Наладах."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"У мэтах бяспекі вашаму тэлевізару забаронена ўсталёўваць невядомыя праграмы з гэтай крыніцы. Вы можаце змяніць гэты дазвол у Наладах."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Дзеля забеспячэння бяспекі вашаму гадзінніку забаронена ўсталёўваць невядомыя праграмы з гэтай крыніцы. Гэтую функцыю можна змяніць у Наладах."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"У мэтах бяспекі вашаму тэлефону забаронена ўсталёўваць невядомыя праграмы з гэтай крыніцы. Вы можаце змяніць гэты дазвол у Наладах."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Ваш тэлефон і асабістыя даныя больш прыступныя для атак невядомых праграм. Усталёўваючы гэту праграму, вы згаджаецеся з тым, што несяце адказнасць за любыя пашкоджанні тэлефона ці страту даных, якія могуць адбыцца ў выніку выкарыстання гэтай праграмы."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Ваш планшэт і асабістыя даныя больш прыступныя для атак невядомых праграм. Усталёўваючы гэту праграму, вы згаджаецеся з тым, што несяце адказнасць за любыя пашкоджанні планшэта ці страту даных, якія могуць адбыцца ў выніку выкарыстання гэтай праграмы."</string>
diff --git a/packages/PackageInstaller/res/values-bg/strings.xml b/packages/PackageInstaller/res/values-bg/strings.xml
index 9bd36e4..3a75898 100644
--- a/packages/PackageInstaller/res/values-bg/strings.xml
+++ b/packages/PackageInstaller/res/values-bg/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Неизвестно"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"От съображения за сигурност понастоящем на таблета ви не могат да се инсталират неизвестни приложения от този източник. Можете да промените това от „Настройки“."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"От съображения за сигурност понастоящем на телевизора ви не могат да се инсталират неизвестни приложения от този източник. Можете да промените това от „Настройки“."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"От съображения за сигурност понастоящем на часовника ви не могат да се инсталират неизвестни приложения от този източник. Можете да промените това от „Настройки“."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"От съображения за сигурност понастоящем на телефона ви не могат да се инсталират неизвестни приложения от този източник. Можете да промените това от „Настройки“."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Телефонът и личните ви данни са по-уязвими към атаки от неизвестни приложения. С инсталирането на това приложение приемате, че носите отговорност при евентуална повреда на телефона или загуба на информация вследствие на използването на приложението."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Таблетът и личните ви данни са по-уязвими към атаки от неизвестни приложения. С инсталирането на това приложение приемате, че носите отговорност при евентуална повреда на таблета или загуба на информация вследствие на използването на приложението."</string>
diff --git a/packages/PackageInstaller/res/values-bn/strings.xml b/packages/PackageInstaller/res/values-bn/strings.xml
index 0edb6d6..97d0750 100644
--- a/packages/PackageInstaller/res/values-bn/strings.xml
+++ b/packages/PackageInstaller/res/values-bn/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"অজানা"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"আপনার নিরাপত্তার জন্য, এই সোর্স থেকে বর্তমানে আপনার ট্যাবে অজানা অ্যাপ ইনস্টল করা যাবে না। আপনি সেটিংস থেকে এটি পরিবর্তন করতে পারবেন।"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"আপনার নিরাপত্তার জন্য, এই সোর্স থেকে বর্তমানে আপনার টিভিতে অজানা অ্যাপ ইনস্টল করা যাবে না। আপনি সেটিংস থেকে এটি পরিবর্তন করতে পারবেন।"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"আপনার নিরাপত্তার জন্য, বর্তমানে এই সোর্স থেকে আপনার ওয়াচে অজানা অ্যাপ ইনস্টল করা যাবে না। আপনি সেটিংস থেকে এটি পরিবর্তন করতে পারবেন।"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"আপনার নিরাপত্তার জন্য, এই সোর্স থেকে বর্তমানে আপনার ফোনে অজানা অ্যাপ ইনস্টল করা যাবে না। আপনি সেটিংস থেকে এটি পরিবর্তন করতে পারবেন।"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"অজানা অ্যাপের দ্বারা আপনার ফোন এবং ব্যক্তিগত ডেটা আক্রান্ত হওয়ার সম্ভাবনা বেশি থাকে। এই অ্যাপটি ইনস্টল করার মাধ্যমে আপনি সম্মত হচ্ছেন যে এটি ব্যবহারের ফলে আপনার ফোনের বা ডেটার কোনও ক্ষতি হলে তার জন্য আপনিই দায়ী থাকবেন।"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"অজানা অ্যাপের দ্বারা আপনার ট্যাবলেট এবং ব্যক্তিগত ডেটা আক্রান্ত হওয়ার সম্ভাবনা বেশি থাকে। এই অ্যাপটি ইনস্টল করার মাধ্যমে আপনি সম্মত হচ্ছেন যে এটি ব্যবহারের ফলে আপনার ট্যাবলেটের বা ডেটার কোনও ক্ষতি হলে তার জন্য আপনিই দায়ী থাকবেন।"</string>
diff --git a/packages/PackageInstaller/res/values-bs/strings.xml b/packages/PackageInstaller/res/values-bs/strings.xml
index edd82bc..d724c20 100644
--- a/packages/PackageInstaller/res/values-bs/strings.xml
+++ b/packages/PackageInstaller/res/values-bs/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Nepoznato"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Radi vaše sigurnosti tabletu trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. Ovo možete promijeniti u Postavkama."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Radi vaše sigurnosti TV-u trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. Ovo možete promijeniti u Postavkama."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Radi vaše sigurnosti, satu trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. Ovo možete promijeniti u Postavkama."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Radi vaše sigurnosti telefonu trenutno nije dozvoljeno da instalira nepoznate aplikacije iz ovog izvora. Ovo možete promijeniti u Postavkama."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Vaši podaci na telefonu i vaši lični podaci izloženiji su napadima nepoznatih aplikacija. Instaliranjem ove aplikacije, prihvatate odgovornost za bilo kakvu štetu na telefonu ili gubitak podataka do kojih može doći korištenjem aplikacije."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Vaši podaci na tabletu i vaši lični podaci izloženiji su napadima nepoznatih aplikacija. Instaliranjem ove aplikacije, prihvatate odgovornost za bilo kakvu štetu na tabletu ili gubitak podataka do kojih može doći korištenjem aplikacije."</string>
diff --git a/packages/PackageInstaller/res/values-ca/strings.xml b/packages/PackageInstaller/res/values-ca/strings.xml
index 577ae27..880bad6 100644
--- a/packages/PackageInstaller/res/values-ca/strings.xml
+++ b/packages/PackageInstaller/res/values-ca/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Desconeguda"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Per la teva seguretat, actualment la tauleta no pot instal·lar aplicacions desconegudes d\'aquesta font. Pots canviar-ho a Configuració."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Per la teva seguretat, actualment la televisió no pot instal·lar aplicacions desconegudes d\'aquesta font. Pots canviar-ho a Configuració."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Per a la teva seguretat, actualment el rellotge no pot instal·lar aplicacions desconegudes d\'aquesta font. Pots canviar-ho a Configuració."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Per la teva seguretat, actualment el telèfon no pot instal·lar aplicacions desconegudes d\'aquesta font. Pots canviar-ho a Configuració."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"El telèfon i les dades personals són més vulnerables als atacs d\'aplicacions desconegudes. En instal·lar aquesta aplicació, acceptes que ets responsable de qualsevol dany que es produeixi al telèfon o de la pèrdua de dades que pugui resultar del seu ús."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"La tauleta i les dades personals són més vulnerables als atacs d\'aplicacions desconegudes. En instal·lar aquesta aplicació, acceptes que ets responsable de qualsevol dany que es produeixi a la tauleta o de la pèrdua de dades que pugui resultar del seu ús."</string>
diff --git a/packages/PackageInstaller/res/values-cs/strings.xml b/packages/PackageInstaller/res/values-cs/strings.xml
index bd18421..11f903a 100644
--- a/packages/PackageInstaller/res/values-cs/strings.xml
+++ b/packages/PackageInstaller/res/values-cs/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Neznámé"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Z bezpečnostních důvodů momentálně není dovoleno do tabletu instalovat neznámé aplikace z tohoto zdroje. Změnit to můžete v Nastavení."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Z bezpečnostních důvodů momentálně není dovoleno do televize instalovat neznámé aplikace z tohoto zdroje. Změnit to můžete v Nastavení."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Z bezpečnostních důvodů momentálně není dovoleno do hodinek instalovat neznámé aplikace z tohoto zdroje. Změnit to můžete v Nastavení."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Z bezpečnostních důvodů momentálně není dovoleno do telefonu instalovat neznámé aplikace z tohoto zdroje. Změnit to můžete v Nastavení."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefon a osobní údaje jsou zranitelnější vůči útoku ze strany neznámých aplikací. Instalací této aplikace přijímáte odpovědnost za případné škody na telefonu nebo ztrátu dat, která může být používáním aplikace způsobena."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tablet a osobní údaje jsou zranitelnější vůči útoku ze strany neznámých aplikací. Instalací této aplikace přijímáte odpovědnost za případné škody na tabletu nebo ztrátu dat, která může být používáním aplikace způsobena."</string>
diff --git a/packages/PackageInstaller/res/values-da/strings.xml b/packages/PackageInstaller/res/values-da/strings.xml
index 32355ca..6feba42 100644
--- a/packages/PackageInstaller/res/values-da/strings.xml
+++ b/packages/PackageInstaller/res/values-da/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Ukendt"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Af hensyn til din sikkerhed har din tablet i øjeblikket ikke tilladelse til at installere ukendte apps fra denne kilde. Du kan ændre dette i Indstillinger."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Af hensyn til din sikkerhed har dit fjernsyn i øjeblikket ikke tilladelse til at installere ukendte apps fra denne kilde. Du kan ændre dette i Indstillinger."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Af hensyn til din sikkerhed har dit ur i øjeblikket ikke tilladelse til at installere ukendte apps fra denne kilde. Du kan ændre dette i Indstillinger."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Af hensyn til din sikkerhed har din telefon i øjeblikket ikke tilladelse til at installere ukendte apps fra denne kilde. Du kan ændre dette i Indstillinger."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Din telefon og dine personlige data er mere sårbare over for angreb fra ukendte apps. Når du installerer denne app, accepterer du, at du er ansvarlig for skader på din telefon eller tab af data, der kan skyldes brug af appen."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Din tablet og dine personlige data er mere sårbare over for angreb fra ukendte apps. Når du installerer denne app, accepterer du, at du er ansvarlig for skader på din tablet eller tab af data, der kan skyldes brug af appen."</string>
diff --git a/packages/PackageInstaller/res/values-de/strings.xml b/packages/PackageInstaller/res/values-de/strings.xml
index 6dc81d0..0537b63 100644
--- a/packages/PackageInstaller/res/values-de/strings.xml
+++ b/packages/PackageInstaller/res/values-de/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Unbekannt"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Aus Sicherheitsgründen kannst du auf deinem Tablet zurzeit keine unbekannten Apps aus dieser Quelle installieren. Das kannst du in den Einstellungen ändern."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Aus Sicherheitsgründen kannst du auf deinem Fernseher zurzeit keine unbekannten Apps aus dieser Quelle installieren. Das kannst du in den Einstellungen ändern."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Aus Sicherheitsgründen kannst du auf deiner Smartwatch zurzeit keine unbekannten Apps aus dieser Quelle installieren. Du kannst das in den Einstellungen ändern."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Aus Sicherheitsgründen kannst du auf deinem Smartphone zurzeit keine unbekannten Apps aus dieser Quelle installieren. Das kannst du in den Einstellungen ändern."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Unbekannte Apps können gefährlich für dein Smartphone und deine personenbezogenen Daten sein. Wenn du diese App installierst, erklärst du dich damit einverstanden, dass du die Verantwortung für alle Schäden an deinem Smartphone und jegliche Datenverluste trägst, die aus der Verwendung dieser App entstehen können."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Unbekannte Apps können gefährlich für dein Tablet und deine personenbezogenen Daten sein. Wenn du diese App installierst, erklärst du dich damit einverstanden, dass du die Verantwortung für alle Schäden an deinem Tablet und jegliche Datenverluste trägst, die aus der Verwendung dieser App entstehen können."</string>
diff --git a/packages/PackageInstaller/res/values-el/strings.xml b/packages/PackageInstaller/res/values-el/strings.xml
index c67f6f7..fa053fc 100644
--- a/packages/PackageInstaller/res/values-el/strings.xml
+++ b/packages/PackageInstaller/res/values-el/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Άγνωστη"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Για λόγους ασφαλείας, δεν επιτρέπεται προς το παρόν η εγκατάσταση άγνωστων εφαρμογών από αυτήν την πηγή στο tablet σας. Μπορείτε να αλλάξετε την επιλογή από τις Ρυθμίσεις."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Για λόγους ασφαλείας, δεν επιτρέπεται προς το παρόν η εγκατάσταση άγνωστων εφαρμογών από αυτήν την πηγή στην τηλεόρασή σας. Μπορείτε να αλλάξετε την επιλογή από τις Ρυθμίσεις."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Για λόγους ασφαλείας, δεν επιτρέπεται προς το παρόν η εγκατάσταση άγνωστων εφαρμογών από αυτήν την πηγή στο ρολόι σας. Αυτό μπορείτε να το αλλάξετε από την ενότητα Ρυθμίσεις."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Για λόγους ασφαλείας, δεν επιτρέπεται προς το παρόν η εγκατάσταση άγνωστων εφαρμογών από αυτήν την πηγή στο τηλέφωνό σας. Μπορείτε να αλλάξετε την επιλογή από τις Ρυθμίσεις."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Το τηλέφωνό σας και τα προσωπικά δεδομένα σας είναι πιο ευάλωτα σε επιθέσεις από άγνωστες εφαρμογές. Με την εγκατάσταση αυτής της εφαρμογής, συμφωνείτε ότι είστε υπεύθυνοι για τυχόν βλάβη που μπορεί να προκληθεί στο τηλέφωνο ή απώλεια δεδομένων που μπορεί να προκύψει από τη χρήση τους."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Το tablet σας και τα προσωπικά δεδομένα σας είναι πιο ευάλωτα σε επιθέσεις από άγνωστες εφαρμογές. Με την εγκατάσταση αυτής της εφαρμογής, συμφωνείτε ότι είστε υπεύθυνοι για τυχόν βλάβη που μπορεί να προκληθεί στο tablet ή απώλεια δεδομένων που μπορεί να προκύψει από τη χρήση τους."</string>
diff --git a/packages/PackageInstaller/res/values-en-rAU/strings.xml b/packages/PackageInstaller/res/values-en-rAU/strings.xml
index f09f7bb..828ac73 100644
--- a/packages/PackageInstaller/res/values-en-rAU/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rAU/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Unknown"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"For your security, your tablet currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"For your security, your TV currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"For your security, your watch currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"For your security, your phone currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Your phone and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your phone or loss of data that may result from its use."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Your tablet and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your tablet or loss of data that may result from its use."</string>
diff --git a/packages/PackageInstaller/res/values-en-rCA/strings.xml b/packages/PackageInstaller/res/values-en-rCA/strings.xml
index f09f7bb..1ad309c 100644
--- a/packages/PackageInstaller/res/values-en-rCA/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rCA/strings.xml
@@ -60,13 +60,13 @@
     <string name="uninstall_update_text" msgid="863648314632448705">"Replace this app with the factory version? All data will be removed."</string>
     <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"Replace this app with the factory version? All data will be removed. This affects all users of this device, including those with work profiles."</string>
     <string name="uninstall_keep_data" msgid="7002379587465487550">"Keep <xliff:g id="SIZE">%1$s</xliff:g> of app data."</string>
-    <string name="uninstalling_notification_channel" msgid="840153394325714653">"Running uninstallations"</string>
-    <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Failed uninstallations"</string>
+    <string name="uninstalling_notification_channel" msgid="840153394325714653">"Running uninstalls"</string>
+    <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Failed uninstalls"</string>
     <string name="uninstalling" msgid="8709566347688966845">"Uninstalling…"</string>
     <string name="uninstalling_app" msgid="8866082646836981397">"Uninstalling <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>…"</string>
-    <string name="uninstall_done" msgid="439354138387969269">"Uninstallation finished."</string>
+    <string name="uninstall_done" msgid="439354138387969269">"Uninstall finished."</string>
     <string name="uninstall_done_app" msgid="4588850984473605768">"Uninstalled <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
-    <string name="uninstall_failed" msgid="1847750968168364332">"Uninstallation unsuccessful."</string>
+    <string name="uninstall_failed" msgid="1847750968168364332">"Uninstall unsuccessful."</string>
     <string name="uninstall_failed_app" msgid="5506028705017601412">"Uninstalling <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> unsuccessful."</string>
     <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"Can\'t uninstall active device admin app"</string>
     <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"Can\'t uninstall active device admin app for <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
@@ -76,21 +76,22 @@
     <string name="manage_device_administrators" msgid="3092696419363842816">"Manage device admin apps"</string>
     <string name="manage_users" msgid="1243995386982560813">"Manage users"</string>
     <string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g> couldn\'t be uninstalled."</string>
-    <string name="Parse_error_dlg_text" msgid="1661404001063076789">"There was a problem while parsing the package."</string>
+    <string name="Parse_error_dlg_text" msgid="1661404001063076789">"There was a problem parsing the package."</string>
     <string name="wear_not_allowed_dlg_title" msgid="8664785993465117517">"Android Wear"</string>
-    <string name="wear_not_allowed_dlg_text" msgid="704615521550939237">"Install/uninstall actions not supported on Wear."</string>
+    <string name="wear_not_allowed_dlg_text" msgid="704615521550939237">"Install/Uninstall actions not supported on Wear."</string>
     <string name="message_staging" msgid="8032722385658438567">"Staging app…"</string>
     <string name="app_name_unknown" msgid="6881210203354323926">"Unknown"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"For your security, your tablet currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"For your security, your TV currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"For your security, your watch currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"For your security, your phone currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Your phone and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your phone or loss of data that may result from its use."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Your tablet and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your tablet or loss of data that may result from its use."</string>
     <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Your TV and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your TV or loss of data that may result from its use."</string>
     <string name="anonymous_source_continue" msgid="4375745439457209366">"Continue"</string>
     <string name="external_sources_settings" msgid="4046964413071713807">"Settings"</string>
-    <string name="wear_app_channel" msgid="1960809674709107850">"Installing/uninstalling Wear apps"</string>
+    <string name="wear_app_channel" msgid="1960809674709107850">"Installing/uninstalling wear apps"</string>
     <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"App installed notification"</string>
     <string name="notification_installation_success_message" msgid="6450467996056038442">"Successfully installed"</string>
-    <string name="notification_installation_success_status" msgid="3172502643504323321">"Successfully installed \'<xliff:g id="APPNAME">%1$s</xliff:g>\'"</string>
+    <string name="notification_installation_success_status" msgid="3172502643504323321">"Successfully installed “<xliff:g id="APPNAME">%1$s</xliff:g>”"</string>
 </resources>
diff --git a/packages/PackageInstaller/res/values-en-rGB/strings.xml b/packages/PackageInstaller/res/values-en-rGB/strings.xml
index f09f7bb..828ac73 100644
--- a/packages/PackageInstaller/res/values-en-rGB/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rGB/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Unknown"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"For your security, your tablet currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"For your security, your TV currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"For your security, your watch currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"For your security, your phone currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Your phone and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your phone or loss of data that may result from its use."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Your tablet and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your tablet or loss of data that may result from its use."</string>
diff --git a/packages/PackageInstaller/res/values-en-rIN/strings.xml b/packages/PackageInstaller/res/values-en-rIN/strings.xml
index f09f7bb..828ac73 100644
--- a/packages/PackageInstaller/res/values-en-rIN/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rIN/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Unknown"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"For your security, your tablet currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"For your security, your TV currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"For your security, your watch currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"For your security, your phone currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Your phone and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your phone or loss of data that may result from its use."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Your tablet and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your tablet or loss of data that may result from its use."</string>
diff --git a/packages/PackageInstaller/res/values-en-rXC/strings.xml b/packages/PackageInstaller/res/values-en-rXC/strings.xml
index fddc164..3e09e89 100644
--- a/packages/PackageInstaller/res/values-en-rXC/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rXC/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‎‏‏‎‎‎‎‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‎‏‏‏‏‎‏‎‏‏‎‎Unknown‎‏‎‎‏‎"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎‏‎‏‎‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‎‏‎‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‎‏‏‏‎For your security, your tablet currently isn’t allowed to install unknown apps from this source. You can change this in Settings.‎‏‎‎‏‎"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‎‏‏‏‏‎‎‎‎‎‏‏‎For your security, your TV currently isn’t allowed to install unknown apps from this source. You can change this in Settings.‎‏‎‎‏‎"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‏‎‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‏‎‎‎‎‎‎‏‎‎‏‏‎‎‎For your security, your watch currently isn’t allowed to install unknown apps from this source. You can change this in Settings.‎‏‎‎‏‎"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‏‏‏‎‏‏‎‎‏‏‏‎For your security, your phone currently isn’t allowed to install unknown apps from this source. You can change this in Settings.‎‏‎‎‏‎"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‎‎‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‎‏‏‏‎‎‏‎‎‎Your phone and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your phone or loss of data that may result from its use.‎‏‎‎‏‎"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‏‎‏‎‏‎‎‎‎‎‎‎‎‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‏‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎Your tablet and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your tablet or loss of data that may result from its use.‎‏‎‎‏‎"</string>
diff --git a/packages/PackageInstaller/res/values-es-rUS/strings.xml b/packages/PackageInstaller/res/values-es-rUS/strings.xml
index a0fff45..cd5cfc3 100644
--- a/packages/PackageInstaller/res/values-es-rUS/strings.xml
+++ b/packages/PackageInstaller/res/values-es-rUS/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Desconocido"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Por tu seguridad, la tablet no tiene permitido actualmente instalar apps desconocidas de esta fuente. Puedes modificar esta opción en Configuración."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Por tu seguridad, la TV no tiene permitido actualmente instalar apps desconocidas de esta fuente. Puedes modificar esta opción en Configuración."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Por tu seguridad, el reloj no tiene permitido actualmente instalar apps desconocidas de esta fuente. Puedes modificar esta opción en Configuración."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Por tu seguridad, el teléfono no tiene permitido actualmente instalar apps desconocidas de esta fuente. Puedes modificar esta opción en Configuración."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"El teléfono y tus datos personales son más vulnerables a los ataques de apps desconocidas. Si instalas esta app, serás responsable de los daños que sufra el teléfono y de la pérdida de datos que pueda ocasionar su uso."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"La tablet y tus datos personales son más vulnerables a los ataques de apps desconocidas. Si instalas esta app, serás responsable de los daños que sufra la tablet y de la pérdida de datos que pueda ocasionar su uso."</string>
diff --git a/packages/PackageInstaller/res/values-es/strings.xml b/packages/PackageInstaller/res/values-es/strings.xml
index 37599c4..9fb37e1 100644
--- a/packages/PackageInstaller/res/values-es/strings.xml
+++ b/packages/PackageInstaller/res/values-es/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Desconocida"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Por tu seguridad, de momento tu tablet no puede instalar aplicaciones desconocidas de esta fuente. Puedes cambiarlo en Ajustes."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Por tu seguridad, de momento tu televisor no puede instalar aplicaciones desconocidas de esta fuente. Puedes cambiarlo en Ajustes."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Por motivos de seguridad, de momento tu reloj no puede instalar aplicaciones desconocidas de esta fuente. Puedes cambiarlo en Configuración."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Por tu seguridad, de momento tu teléfono no puede instalar aplicaciones desconocidas de esta fuente. Puedes cambiarlo en Ajustes."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Tu teléfono y tus datos personales son más vulnerables a los ataques de aplicaciones desconocidas. Al instalar esta aplicación, aceptas ser responsable de cualquier daño que sufra tu teléfono o la pérdida de datos que se pueda derivar de su uso."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tu tablet y tus datos personales son más vulnerables a los ataques de aplicaciones desconocidas. Al instalar esta aplicación, aceptas ser responsable de cualquier daño que sufra tu tablet o la pérdida de datos que se pueda derivar de su uso."</string>
diff --git a/packages/PackageInstaller/res/values-et/strings.xml b/packages/PackageInstaller/res/values-et/strings.xml
index 2324806..e7dedac 100644
--- a/packages/PackageInstaller/res/values-et/strings.xml
+++ b/packages/PackageInstaller/res/values-et/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Teadmata"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Teie turvalisuse huvides ei ole tahvelarvutil praegu lubatud installida sellest allikast tundmatuid rakendusi. Saate seda seadetes muuta."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Teie turvalisuse huvides ei ole teleril praegu lubatud installida sellest allikast tundmatuid rakendusi. Saate seda seadetes muuta."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Teie turvalisuse huvides ei ole kellal praegu lubatud installida sellest allikast tundmatuid rakendusi. Saate seda seadetes muuta."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Teie turvalisuse huvides ei ole telefonil praegu lubatud installida sellest allikast tundmatuid rakendusi. Saate seda seadetes muuta."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Teie telefon ja isiklikud andmed on tundmatute rakenduste rünnakute suhtes haavatavamad. Selle rakenduse installimisel nõustute, et vastutate telefoni kahjude ja andmekao eest, mis võivad tuleneda selliste rakenduste kasutamisest."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Teie tahvelarvuti ja isiklikud andmed on tundmatute rakenduste rünnakute suhtes haavatavamad. Selle rakenduse installimisel nõustute, et vastutate tahvelarvuti kahjude ja andmekao eest, mis võivad tuleneda selliste rakenduste kasutamisest."</string>
diff --git a/packages/PackageInstaller/res/values-eu/strings.xml b/packages/PackageInstaller/res/values-eu/strings.xml
index fe6edce..5b8b0ee 100644
--- a/packages/PackageInstaller/res/values-eu/strings.xml
+++ b/packages/PackageInstaller/res/values-eu/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Ezezaguna"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak tableta honetan. Hori aldatzeko, joan Ezarpenak atalera."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak telebista honetan. Hori aldatzeko, joan Ezarpenak atalera."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak erloju honetan. Hori aldatzeko, joan Ezarpenak atalera."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak telefono honetan. Hori aldatzeko, joan Ezarpenak atalera."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Baliteke telefonoak eta datu pertsonalek aplikazio ezezagunen erasoak jasatea. Aplikazio hau instalatzen baduzu, onartu egingo duzu zeu zarela hura erabiltzeagatik telefonoari agian gertatuko zaizkion kalteen edo datu-galeren erantzulea."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Baliteke tabletak eta datu pertsonalek aplikazio ezezagunen erasoak jasatea. Aplikazio hau instalatzen baduzu, onartu egingo duzu hura erabiltzeagatik tabletari agian gertatuko zaizkion kalteen edo datu-galeren erantzulea zeu izango zarela."</string>
diff --git a/packages/PackageInstaller/res/values-fa/strings.xml b/packages/PackageInstaller/res/values-fa/strings.xml
index b05a087..38c2c22 100644
--- a/packages/PackageInstaller/res/values-fa/strings.xml
+++ b/packages/PackageInstaller/res/values-fa/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"نامشخص"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"برای امنیت شما، درحال‌حاضر رایانه لوحی‌تان اجازه ندارد برنامه‌های ناشناس را از این منبع نصب کنید. می‌توانید آن را در «تنظیمات» تغییر دهید."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"برای امنیت شما، درحال‌حاضر تلویزیونتان اجازه ندارد برنامه‌های ناشناس را از این منبع نصب کنید. می‌توانید آن را در «تنظیمات» تغییر دهید."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"برای امنیت شما، درحال‌حاضر ساعتتان اجازه ندارد برنامه‌های ناشناس را از این منبع نصب کنید. می‌توانید این مورد را در «تنظیمات» تغییر دهید."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"برای امنیت شما، درحال‌حاضر تلفنتان اجازه ندارد برنامه‌های ناشناس را از این منبع نصب کنید. می‌توانید آن را در «تنظیمات» تغییر دهید."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"تلفن و داده‌های شخصی‌تان دربرابر حمله برنامه‌های ناشناس آسیب‌پذیرتر هستند. با نصب این برنامه، موافقت می‌کنید که مسئول هرگونه آسیب به تلفن یا از دست رفتن داده‌ای هستید که ممکن است درنتیجه استفاده از آن به وجود آید."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"رایانه لوحی و داده‌های شخصی‌تان دربرابر حمله برنامه‌های ناشناس آسیب‌پذیرتر هستند. با نصب این برنامه، موافقت می‌کنید که مسئول هرگونه آسیب به رایانه لوحی یا از دست رفتن داده‌ای هستید که ممکن است درنتیجه استفاده از آن به وجود آید."</string>
diff --git a/packages/PackageInstaller/res/values-fi/strings.xml b/packages/PackageInstaller/res/values-fi/strings.xml
index a8048e2..3958ed2 100644
--- a/packages/PackageInstaller/res/values-fi/strings.xml
+++ b/packages/PackageInstaller/res/values-fi/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Tuntematon"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Turvallisuussyistä tablettisi ei tällä hetkellä voi asentaa tuntemattomia sovelluksia tästä lähteestä. Voit muuttaa tätä asetuksissa."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Turvallisuussyistä televisiosi ei tällä hetkellä voi asentaa tuntemattomia sovelluksia tästä lähteestä. Voit muuttaa tätä asetuksissa."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Turvallisuussyistä kellosi ei tällä hetkellä voi asentaa tuntemattomia sovelluksia tästä lähteestä. Voit muuttaa tätä asetuksissa."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Turvallisuussyistä puhelimesi ei tällä hetkellä voi asentaa tuntemattomia sovelluksia tästä lähteestä. Voit muuttaa tätä asetuksissa."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Tuntemattomat sovellukset voivat helpommin kaapata puhelimesi ja henkilökohtaiset tietosi. Lataamalla sovelluksia tästä lähteestä hyväksyt, että olet itse vastuussa puhelimellesi aiheutuvista vahingoista tai tietojen menetyksestä, jotka voivat johtua sovellusten käytöstä."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tuntemattomat sovellukset voivat helpommin kaapata tablettisi ja henkilökohtaiset tietosi. Lataamalla sovelluksia tästä lähteestä hyväksyt, että olet itse vastuussa tabletillesi aiheutuvista vahingoista tai tietojen menetyksestä, jotka voivat johtua sovellusten käytöstä."</string>
diff --git a/packages/PackageInstaller/res/values-fr-rCA/strings.xml b/packages/PackageInstaller/res/values-fr-rCA/strings.xml
index d11336f..9e22fa4 100644
--- a/packages/PackageInstaller/res/values-fr-rCA/strings.xml
+++ b/packages/PackageInstaller/res/values-fr-rCA/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Inconnue"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"À des fins de sécurité, l\'installation d\'applications inconnues provenant de cette source n\'est pas autorisée sur cette tablette. Vous pouvez modifier cette option dans les paramètres."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"À des fins de sécurité, l\'installation d\'applications inconnues provenant de cette source n\'est pas autorisée sur ce téléviseur. Vous pouvez modifier cette option dans les paramètres."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"À des fins de sécurité, l\'installation d\'applications inconnues provenant de cette source n\'est pas autorisée sur cette montre. Vous pouvez modifier cette option dans les paramètres."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"À des fins de sécurité, l\'installation d\'applications inconnues provenant de cette source n\'est pas autorisée sur ce téléphone. Vous pouvez modifier cette option dans les paramètres."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Votre téléphone et vos données personnelles sont plus vulnérables aux attaques provenant d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre téléphone ou de toute perte de données pouvant découler de l\'utilisation de telles applications."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Votre tablette et vos données personnelles sont plus vulnérables aux attaques provenant d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre tablette ou de toute perte de données pouvant découler de l\'utilisation de telles applications."</string>
diff --git a/packages/PackageInstaller/res/values-fr/strings.xml b/packages/PackageInstaller/res/values-fr/strings.xml
index a02851e..0275233d 100644
--- a/packages/PackageInstaller/res/values-fr/strings.xml
+++ b/packages/PackageInstaller/res/values-fr/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Inconnu"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Pour votre sécurité, l\'installation d\'applis inconnues provenant de cette source n\'est pas autorisée sur cette tablette actuellement. Vous pouvez modifier cette option dans les paramètres."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Pour votre sécurité, l\'installation d\'applis inconnues provenant de cette source n\'est pas autorisée sur ce téléviseur actuellement. Vous pouvez modifier cette option dans les paramètres."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Pour votre sécurité, l\'installation d\'applis inconnues provenant de cette source n\'est pas autorisée sur cette montre actuellement. Vous pouvez modifier cette option dans les paramètres."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Pour votre sécurité, l\'installation d\'applis inconnues provenant de cette source n\'est pas autorisée sur ce téléphone actuellement. Vous pouvez modifier cette option dans les paramètres."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Votre téléphone et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre téléphone ou de toute perte de données pouvant découler de son utilisation."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Votre tablette et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre tablette ou de toute perte de données pouvant découler de son utilisation."</string>
diff --git a/packages/PackageInstaller/res/values-gl/strings.xml b/packages/PackageInstaller/res/values-gl/strings.xml
index a6454da..05aec90 100644
--- a/packages/PackageInstaller/res/values-gl/strings.xml
+++ b/packages/PackageInstaller/res/values-gl/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Nome descoñecido"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Por cuestións de seguranza, na tableta non se poden instalar actualmente aplicacións descoñecidas procedentes desta fonte. Podes cambiar esta opción en Configuración."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Por cuestións de seguranza, na televisión non se poden instalar actualmente aplicacións descoñecidas procedentes desta fonte. Podes cambiar esta opción en Configuración."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Por cuestións de seguranza, no reloxo non se poden instalar actualmente aplicacións descoñecidas procedentes desta fonte. Podes cambiar esta opción en Configuración."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Por cuestións de seguranza, no teléfono non se poden instalar actualmente aplicacións descoñecidas procedentes desta fonte. Podes cambiar esta opción en Configuración."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"O teléfono e os datos persoais son máis vulnerables aos ataques de aplicacións descoñecidas. Ao instalar esta aplicación, aceptas que es responsable dos danos ocasionados no teléfono ou da perda dos datos que se poidan derivar do seu uso."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"A tableta e os datos persoais son máis vulnerables aos ataques de aplicacións descoñecidas. Ao instalar esta aplicación, aceptas que es responsable dos danos ocasionados na tableta ou da perda dos datos que se poidan derivar do seu uso."</string>
diff --git a/packages/PackageInstaller/res/values-gu/strings.xml b/packages/PackageInstaller/res/values-gu/strings.xml
index 7851d3d..e109360 100644
--- a/packages/PackageInstaller/res/values-gu/strings.xml
+++ b/packages/PackageInstaller/res/values-gu/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"અજાણ"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"તમારી સુરક્ષા માટે, હાલમાં તમારા ટૅબ્લેટને આ સૉર્સ પરથી અજાણી ઍપ ઇન્સ્ટૉલ કરવાની મંજૂરી નથી. તમે આને સેટિંગમાં બદલી શકો છો."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"તમારી સુરક્ષા માટે, હાલમાં તમારા ટીવીને આ સૉર્સ પરથી અજાણી ઍપ ઇન્સ્ટૉલ કરવાની મંજૂરી નથી. તમે આને સેટિંગમાં બદલી શકો છો."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"તમારી સુરક્ષા માટે, હાલમાં તમારી વૉચને આ સૉર્સ પરથી અજાણી ઍપ ઇન્સ્ટૉલ કરવાની મંજૂરી નથી. તમે સેટિંગમાં જઈને આને બદલી શકો છો."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"તમારી સુરક્ષા માટે, હાલમાં તમારા ફોનને આ સૉર્સ પરથી અજાણી ઍપ ઇન્સ્ટૉલ કરવાની મંજૂરી નથી. તમે આને સેટિંગમાં બદલી શકો છો."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"તમારો ફોન અને વ્યક્તિગત ડેટા અજાણી ઍપ્લિકેશનો દ્વારા હુમલા માટે વધુ સંવેદનશીલ છે. આ ઍપ્લિકેશન ઇન્સ્ટૉલ કરીને તમે સંમત થાઓ છો કે આનો ઉપયોગ કરવાથી તમારા ફોનને થતી કોઈપણ હાનિ અથવા ડેટાના નુકસાન માટે તમે જવાબદાર છો."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"તમારું ટૅબ્લેટ અને વ્યક્તિગત ડેટા અજાણી ઍપ્લિકેશનો દ્વારા હુમલા માટે વધુ સંવેદનશીલ છે. આ ઍપ્લિકેશન ઇન્સ્ટૉલ કરીને તમે સંમત થાઓ છો કે આનો ઉપયોગ કરવાથી તમારા ટૅબ્લેટને થતી કોઈપણ હાનિ અથવા ડેટાના નુકસાન માટે તમે જવાબદાર છો."</string>
diff --git a/packages/PackageInstaller/res/values-hi/strings.xml b/packages/PackageInstaller/res/values-hi/strings.xml
index c6a1f40..702034ed 100644
--- a/packages/PackageInstaller/res/values-hi/strings.xml
+++ b/packages/PackageInstaller/res/values-hi/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"अनजान"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"आपकी सुरक्षा के लिए, आपके टैबलेट को फ़िलहाल इस स्रोत से अनजान ऐप्लिकेशन इंस्टॉल करने की अनुमति नहीं है. आप \'सेटिंग\' में जाकर इसे बदल सकते हैं."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"आपकी सुरक्षा के लिए, आपके टीवी को फ़िलहाल इस स्रोत से अनजान ऐप्लिकेशन इंस्टॉल करने की अनुमति नहीं है. आप \'सेटिंग\' में जाकर इसे बदल सकते हैं."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"आपकी सुरक्षा के लिए, फ़िलहाल स्मार्टवॉच को इस सोर्स से अनजान ऐप्लिकेशन इंस्टॉल करने की अनुमति नहीं है. \'सेटिंग\' में जाकर इसे बदला जा सकता है."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"आपकी सुरक्षा के लिए, आपके फ़ोन को फ़िलहाल इस सोर्स से अनजान ऐप्लिकेशन इंस्टॉल करने की अनुमति नहीं है. \'सेटिंग\' में जाकर इसे बदला जा सकता है."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"आपका फ़ोन और निजी डेटा अनजान ऐप्लिकेशन के हमले को लेकर ज़्यादा संवेदनशील हैं. इस ऐप्लिकेशन को इंस्टॉल करके, आप सहमति देते हैं कि इसके इस्तेमाल के चलते आपके फ़ोन को होने वाले किसी भी नुकसान या डेटा के मिट जाने पर, आप ज़िम्मेदार होंगे."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"आपका टैबलेट और निजी डेटा अनजान ऐप्लिकेशन के हमले को लेकर ज़्यादा संवेदनशील हैं. इस ऐप्लिकेशन को इंस्टॉल करके, आप सहमति देते हैं कि इसके इस्तेमाल के चलते आपके टैबलेट को होने वाले किसी भी नुकसान या डेटा के मिट जाने पर, आप ज़िम्मेदार होंगे."</string>
diff --git a/packages/PackageInstaller/res/values-hr/strings.xml b/packages/PackageInstaller/res/values-hr/strings.xml
index 707eb4e..cccf998 100644
--- a/packages/PackageInstaller/res/values-hr/strings.xml
+++ b/packages/PackageInstaller/res/values-hr/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Nepoznato"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Iz sigurnosnih razloga tablet trenutačno nema dopuštenje za instaliranje nepoznatih aplikacija iz ovog izvora. To možete promijeniti u Postavkama."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Iz sigurnosnih razloga televizor trenutačno nema dopuštenje za instaliranje nepoznatih aplikacija iz ovog izvora. To možete promijeniti u Postavkama."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Iz sigurnosnih razloga sat trenutačno nema dopuštenje za instaliranje nepoznatih aplikacija iz ovog izvora. To možete promijeniti u postavkama."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Iz sigurnosnih razloga telefon trenutačno nema dopuštenje za instaliranje nepoznatih aplikacija iz ovog izvora. To možete promijeniti u Postavkama."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Vaš telefon i osobni podaci podložniji su napadima nepoznatih aplikacija. Instaliranjem te aplikacije prihvaćate odgovornost za oštećenje telefona ili gubitak podataka do kojih može doći uslijed njezine upotrebe."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Vaš tablet i osobni podaci podložniji su napadima nepoznatih aplikacija. Instaliranjem te aplikacije prihvaćate odgovornost za oštećenje tableta ili gubitak podataka do kojih može doći uslijed njezine upotrebe."</string>
diff --git a/packages/PackageInstaller/res/values-hu/strings.xml b/packages/PackageInstaller/res/values-hu/strings.xml
index 70ebadb..3b55307 100644
--- a/packages/PackageInstaller/res/values-hu/strings.xml
+++ b/packages/PackageInstaller/res/values-hu/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Ismeretlen"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Az Ön biztonsága érdekében táblagépe jelenleg nem telepíthet ebből a forrásból származó ismeretlen alkalmazásokat. Ezt módosíthatja a Beállítások között."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Az Ön biztonsága érdekében tévéje jelenleg nem telepíthet ebből a forrásból származó ismeretlen alkalmazásokat. Ezt módosíthatja a Beállítások között."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Az Ön biztonsága érdekében órája jelenleg nem telepíthet ebből a forrásból származó ismeretlen alkalmazásokat. Ezt a Beállítások között módosíthatja."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Az Ön biztonsága érdekében telefonja jelenleg nem telepíthet ebből a forrásból származó ismeretlen alkalmazásokat. Ezt módosíthatja a Beállítások között."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonja és személyes adatai fokozott kockázatnak vannak kitéve az ismeretlen alkalmazások támadásaival szemben. Az alkalmazás telepítésével elfogadja, hogy Ön a felelős az alkalmazás használatából eredő esetleges adatvesztésért és a telefont ért károkért."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Táblagépe és személyes adatai fokozott kockázatnak vannak kitéve az ismeretlen alkalmazások támadásaival szemben. Az alkalmazás telepítésével elfogadja, hogy Ön a felelős az alkalmazás használatából eredő esetleges adatvesztésért és a táblagépet ért károkért."</string>
diff --git a/packages/PackageInstaller/res/values-hy/strings.xml b/packages/PackageInstaller/res/values-hy/strings.xml
index 288c9b1..77b56d4 100644
--- a/packages/PackageInstaller/res/values-hy/strings.xml
+++ b/packages/PackageInstaller/res/values-hy/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Անհայտ"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Անվտանգության նկատառումներից ելնելով՝ ձեր պլանշետում ներկայումս չի թույլատրվում անհայտ հավելվածներ տեղադրել այս աղբյուրից: Սա կարող եք փոխել կարգավորումներում։"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Անվտանգության նկատառումներից ելնելով՝ ձեր հեռուստացույցում ներկայումս չի թույլատրվում անհայտ հավելվածներ տեղադրել այս աղբյուրից: Սա կարող եք փոխել կարգավորումներում։"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Անվտանգության նկատառումներից ելնելով՝ ձեր ժամացույցում ներկայումս չի թույլատրվում անհայտ հավելվածներ տեղադրել այս աղբյուրից։ Սա կարող եք փոխել կարգավորումներում։"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Անվտանգության նկատառումներից ելնելով՝ ձեր հեռախոսում ներկայումս չի թույլատրվում անհայտ հավելվածներ տեղադրել այս աղբյուրից: Սա կարող եք փոխել կարգավորումներում։"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Ձեր հեռախոսը և անձնական տվյալներն առավել խոցելի են անհայտ հավելվածների գրոհների նկատմամբ: Տեղադրելով այս հավելվածը՝ դուք ընդունում եք, որ պատասխանատվություն եք կրում հավելվածի օգտագործման հետևանքով ձեր հեռախոսին հասցված ցանկացած վնասի կամ տվյալների կորստի համար:"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Ձեր պլանշետը և անձնական տվյալներն առավել խոցելի են անհայտ հավելվածների գրոհների նկատմամբ: Տեղադրելով այս հավելվածը՝ դուք ընդունում եք, որ պատասխանատվություն եք կրում հավելվածի օգտագործման հետևանքով ձեր պլանշետին հասցված ցանկացած վնասի կամ տվյալների կորստի համար:"</string>
diff --git a/packages/PackageInstaller/res/values-in/strings.xml b/packages/PackageInstaller/res/values-in/strings.xml
index e3e5606..d49df3e 100644
--- a/packages/PackageInstaller/res/values-in/strings.xml
+++ b/packages/PackageInstaller/res/values-in/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Tidak dikenal"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Demi keamanan, tablet Anda saat ini tidak diizinkan menginstal aplikasi yang tidak dikenal dari sumber ini. Anda dapat mengubahnya di Setelan."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Demi keamanan, TV Anda saat ini tidak diizinkan menginstal aplikasi yang tidak dikenal dari sumber ini. Anda dapat mengubahnya di Setelan."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Demi keamanan, smartwatch Anda saat ini tidak diizinkan menginstal aplikasi yang tidak dikenal dari sumber ini. Anda dapat mengubahnya di Setelan."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Demi keamanan, ponsel Anda saat ini tidak diizinkan menginstal aplikasi yang tidak dikenal dari sumber ini. Anda dapat mengubahnya di Setelan."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Ponsel dan data pribadi Anda lebih rentan terhadap serangan oleh aplikasi yang tidak dikenal. Dengan menginstal aplikasi ini, Anda setuju bahwa Anda bertanggung jawab atas kerusakan ponsel atau kehilangan data yang mungkin diakibatkan oleh penggunaannya."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tablet dan data pribadi Anda lebih rentan terhadap serangan oleh aplikasi yang tidak dikenal. Dengan menginstal aplikasi ini, Anda setuju bahwa Anda bertanggung jawab atas kerusakan tablet atau kehilangan data yang mungkin diakibatkan oleh penggunaannya."</string>
diff --git a/packages/PackageInstaller/res/values-is/strings.xml b/packages/PackageInstaller/res/values-is/strings.xml
index 7f0ee04..138ecc7 100644
--- a/packages/PackageInstaller/res/values-is/strings.xml
+++ b/packages/PackageInstaller/res/values-is/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Óþekkt"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Af öryggisástæðum er ekki heimilt að setja upp óþekkt forrit frá þessum uppruna í spjaldtölvunni þinni. Þú getur breytt þessu í stillingum."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Af öryggisástæðum er ekki heimilt að setja upp óþekkt forrit frá þessum uppruna í sjónvarpinu þínu. Þú getur breytt þessu í stillingum."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Af öryggisástæðum er ekki heimilt að setja upp óþekkt forrit frá þessum uppruna í úrinu þínu. Þú getur breytt þessu í stillingunum."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Af öryggisástæðum er ekki heimilt að setja upp óþekkt forrit frá þessum uppruna í símanum þínum. Þú getur breytt þessu í stillingum."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Síminn þinn og persónuleg gögn eru berskjaldaðri fyrir árásum forrita af óþekktum uppruna. Með uppsetningu þessa forrits samþykkirðu að bera fulla ábyrgð á hverju því tjóni sem verða kann á símanum eða gagnatapi sem leiða kann af notkun þess."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Spjaldtölvan þín og persónuleg gögn eru berskjaldaðri fyrir árásum forrita af óþekktum uppruna. Með uppsetningu þessa forrits samþykkirðu að bera fulla ábyrgð á hverju því tjóni sem verða kann á spjaldtölvunni eða gagnatapi sem leiða kann af notkun þess."</string>
diff --git a/packages/PackageInstaller/res/values-it/strings.xml b/packages/PackageInstaller/res/values-it/strings.xml
index 3fe7ba4..e57e319 100644
--- a/packages/PackageInstaller/res/values-it/strings.xml
+++ b/packages/PackageInstaller/res/values-it/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Sconosciuto"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Per motivi di sicurezza, il tuo tablet non è attualmente autorizzato a installare app sconosciute da questa origine. Puoi modificare questa opzione nelle Impostazioni."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Per motivi di sicurezza, la tua TV non è attualmente autorizzata a installare app sconosciute da questa origine. Puoi modificare questa opzione nelle Impostazioni."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Per motivi di sicurezza, il tuo smartwatch non è attualmente autorizzato a installare app sconosciute da questa origine. Puoi modificare questa preferenza nelle Impostazioni."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Per motivi di sicurezza, il tuo telefono non è attualmente autorizzato a installare app sconosciute da questa origine. Puoi modificare questa opzione nelle Impostazioni."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"I dati del telefono e i dati personali sono più vulnerabili agli attacchi di app sconosciute. Se installi questa app, accetti di essere responsabile degli eventuali danni al telefono o dell\'eventuale perdita di dati derivanti dall\'uso dell\'app."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"I dati del tablet e i dati personali sono più vulnerabili agli attacchi di app sconosciute. Se installi questa app, accetti di essere responsabile degli eventuali danni al tablet o dell\'eventuale perdita di dati derivanti dall\'uso dell\'app."</string>
diff --git a/packages/PackageInstaller/res/values-iw/strings.xml b/packages/PackageInstaller/res/values-iw/strings.xml
index 2d7c988..c923b98 100644
--- a/packages/PackageInstaller/res/values-iw/strings.xml
+++ b/packages/PackageInstaller/res/values-iw/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"לא ידוע"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"לצורכי אבטחה, הטאבלט שלך חסום להתקנת אפליקציות לא מוכרות מהמקור הזה. אפשר לשנות זאת ב\'הגדרות\'."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"לצורכי אבטחה, הטלוויזיה שלך חסומה להתקנת אפליקציות לא מוכרות מהמקור הזה. אפשר לשנות זאת ב\'הגדרות\'."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"לצורכי אבטחה, השעון שלך חסום להתקנת אפליקציות לא מוכרות מהמקור הזה. אפשר לשנות זאת ב\'הגדרות\'."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"לצורכי אבטחה, הטלפון שלך חסום להתקנת אפליקציות לא מוכרות מהמקור הזה. אפשר לשנות זאת ב\'הגדרות\'."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"נתוני הטלפון והנתונים האישיים שלך חשופים יותר בפני התקפות על ידי אפליקציות ממקורות לא ידועים. התקנת האפליקציה הזו מהווה את הסכמתך לכך שהאחריות הבלעדית היא שלך במקרה של אובדן נתונים או גרימת נזק לטלפון שלך בעקבות השימוש באפליקציה."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"נתוני הטאבלט והנתונים האישיים שלך חשופים יותר בפני התקפות על ידי אפליקציות ממקורות לא ידועים. התקנת האפליקציה הזו מהווה את הסכמתך לכך שהאחריות הבלעדית היא שלך במקרה של אובדן נתונים או גרימת נזק לטאבלט בעקבות השימוש באפליקציה."</string>
diff --git a/packages/PackageInstaller/res/values-ja/strings.xml b/packages/PackageInstaller/res/values-ja/strings.xml
index e2a5aaa..bc6f917 100644
--- a/packages/PackageInstaller/res/values-ja/strings.xml
+++ b/packages/PackageInstaller/res/values-ja/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"不明"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"セキュリティ上の理由から、お使いのタブレットでは現在、この提供元からの不明なアプリをインストールすることはできません。これは [設定] で変更できます。"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"セキュリティ上の理由から、お使いのテレビでは現在、この提供元からの不明なアプリをインストールすることはできません。これは [設定] で変更できます。"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"セキュリティ上の理由から、お使いのウォッチでは現在、この提供元からの不明なアプリをインストールすることはできません。これは [設定] で変更できます。"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"セキュリティ上の理由から、お使いのスマートフォンでは現在、この提供元からの不明なアプリをインストールすることはできません。これは [設定] で変更できます。"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"不明なアプリをインストールするとスマートフォンや個人データの侵害に対する安全性が低下します。このアプリをインストールすることで、アプリの使用により生じる可能性があるスマートフォンへの侵害やデータの損失について、ユーザーご自身が単独で責任を負うことに同意することになります。"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"不明なアプリをインストールするとタブレットや個人データの侵害に対する安全性が低下します。このアプリをインストールすることで、アプリの使用により生じる可能性があるタブレットへの侵害やデータの損失について、ユーザーご自身が単独で責任を負うことに同意することになります。"</string>
diff --git a/packages/PackageInstaller/res/values-ka/strings.xml b/packages/PackageInstaller/res/values-ka/strings.xml
index ea6d45e..507aafb 100644
--- a/packages/PackageInstaller/res/values-ka/strings.xml
+++ b/packages/PackageInstaller/res/values-ka/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"უცნობი"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"თქვენივე უსაფრთხოებისთვის თქვენს ტაბლეტს არ აქვს ამ წყაროდან უცნობი აპების ინსტალაციის ნებართვა. ამის შეცვლა პარამეტრებში შეგიძლიათ."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"თქვენივე უსაფრთხოებისთვის თქვენს ტელეფონს არ აქვს ამ წყაროდან უცნობი აპების ინსტალაციის ნებართვა. ამის შეცვლა პარამეტრებში შეგიძლიათ."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"თქვენივე უსაფრთხოებისთვის თქვენს საათს არ აქვს ამ წყაროდან უცნობი აპების ინსტალაციის ნებართვა. ამის შეცვლა პარამეტრებში შეგიძლიათ."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"თქვენივე უსაფრთხოებისთვის, ტელეფონს არ აქვს ამ წყაროდან უცნობი აპების ინსტალაციის უფლება. ამის შეცვლა პარამეტრებში შეგიძლიათ."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"თქვენი ტელეფონი და პერსონალური მონაცემები მეტად დაუცველია უცნობი აპების მხრიდან შეტევების წინაშე. ამ აპის ინსტალაციის შემთხვევაში, თქვენ თანახმა ხართ, პასუხისმგებელი იყოთ მისი გამოყენების შედეგად ტელეფონისთვის მიყენებულ ზიანსა თუ მონაცემების დაკარგვაზე."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"თქვენი ტაბლეტი და პერსონალური მონაცემები მეტად დაუცველია უცნობი აპების მხრიდან შეტევების წინაშე. ამ აპის ინსტალაციის შემთხვევაში, თქვენ თანახმა ხართ, პასუხისმგებელი იყოთ მისი გამოყენების შედეგად ტაბლეტისთვის მიყენებულ ზიანსა თუ მონაცემების დაკარგვაზე."</string>
diff --git a/packages/PackageInstaller/res/values-kk/strings.xml b/packages/PackageInstaller/res/values-kk/strings.xml
index 20eed5b..15eeb4d 100644
--- a/packages/PackageInstaller/res/values-kk/strings.xml
+++ b/packages/PackageInstaller/res/values-kk/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Белгісіз"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Қауіпсіздік үшін планшетке бұл дереккөзден белгісіз қолданбаларды орнатуға рұқсат берілмейді. Мұны \"Параметрлер\" бөлімінен өзгертуіңізге болады."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Қауіпсіздік үшін теледидарға бұл дереккөзден белгісіз қолданбаларды орнатуға рұқсат берілмейді. Мұны \"Параметрлер\" бөлімінен өзгертуіңізге болады."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Қауіпсіздік үшін cағатқа бұл дереккөзден белгісіз қолданбаларды орнатуға рұқсат берілмейді. Мұны \"Параметрлер\" бөлімінен өзгертуіңізге болады."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Қауіпсіздік үшін телефонға бұл дереккөзден белгісіз қолданбаларды орнатуға рұқсат берілмейді. Мұны \"Параметрлер\" бөлімінен өзгертуіңізге болады."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Телефон және жеке деректер белгісіз қолданбалардың шабуылына ұшырауы мүмкін. Бұл қолданбаны орнату арқылы оны пайдалану нәтижесіндегі телефонға келетін залалға немесе деректердің жоғалуына өзіңіз ғана жауапты болатыныңызға келісесіз."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Планшет және жеке деректер белгісіз қолданбалардың шабуылына ұшырауы мүмкін. Бұл қолданбаны орнату арқылы оны пайдалану нәтижесіндегі планшетке келетін залалға немесе деректердің жоғалуына өзіңіз ғана жауапты болатыныңызға келісесіз."</string>
diff --git a/packages/PackageInstaller/res/values-km/strings.xml b/packages/PackageInstaller/res/values-km/strings.xml
index 06c5ea2..bec78b0 100644
--- a/packages/PackageInstaller/res/values-km/strings.xml
+++ b/packages/PackageInstaller/res/values-km/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"មិនស្គាល់"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"ដើម្បីសុវត្ថិភាពរបស់អ្នក បច្ចុប្បន្នទូរទស្សន៍របស់អ្នកមិនត្រូវបានអនុញ្ញាតឱ្យដំឡើងកម្មវិធីដែលមិនស្គាល់ពីប្រភពនេះទេ។ អ្នកអាច​ប្ដូរវាបាន​នៅក្នុងការ​កំណត់។"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"ដើម្បីសុវត្ថិភាពរបស់អ្នក បច្ចុប្បន្នទូរទស្សន៍របស់អ្នកមិនត្រូវបានអនុញ្ញាតឱ្យដំឡើងកម្មវិធីដែលមិនស្គាល់ពីប្រភពនេះទេ។ អ្នកអាច​ប្ដូរវាបាន​នៅក្នុងការ​កំណត់។"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ដើម្បីសុវត្ថិភាពរបស់អ្នក បច្ចុប្បន្ននាឡិការបស់អ្នកមិនត្រូវបានអនុញ្ញាតឱ្យដំឡើងកម្មវិធីដែលមិនស្គាល់ពីប្រភពនេះទេ។ អ្នកអាច​ផ្លាស់ប្ដូរវាបាន​នៅក្នុងការ​កំណត់។"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ដើម្បីសុវត្ថិភាពរបស់អ្នក បច្ចុប្បន្នទូរសព្ទរបស់អ្នកមិនត្រូវបានអនុញ្ញាតឱ្យដំឡើងកម្មវិធីដែលមិនស្គាល់ពីប្រភពនេះទេ។ អ្នកអាច​ប្ដូរវាបាន​នៅក្នុងការ​កំណត់។"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"ទូរសព្ទ និងទិន្នន័យផ្ទាល់ខ្លួនរបស់អ្នកកាន់តែងាយនឹងរងគ្រោះពីការវាយប្រហារពីកម្មវិធីដែលមិនស្គាល់។ ប្រសិនបើដំឡើងកម្មវិធីនេះ អ្នកយល់ព្រមថា អ្នកទទួលខុសត្រូវលើការខូចខាតទាំងឡាយមកលើទូរសព្ទរបស់អ្នក ឬការបាត់បង់ទិន្នន័យ ដែលអាចបណ្ដាលមកពីការប្រើប្រាស់កម្មវិធីនេះ។"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"ថេប្លេត និងទិន្នន័យផ្ទាល់ខ្លួនរបស់អ្នកងាយនឹងរងគ្រោះពីការវាយប្រហារពីកម្មវិធីដែលមិនស្គាល់។ ប្រសិនបើដំឡើងកម្មវិធីនេះ មានន័យថាអ្នកទទួលខុសត្រូវលើការខូចខាតទាំងឡាយចំពោះថេប្លេត ឬការបាត់បង់ទិន្នន័យរបស់អ្នក ដែលអាចបណ្ដាលមកពីការប្រើប្រាស់កម្មវិធីនេះ។"</string>
diff --git a/packages/PackageInstaller/res/values-kn/strings.xml b/packages/PackageInstaller/res/values-kn/strings.xml
index 4c6b2ff..29722c2 100644
--- a/packages/PackageInstaller/res/values-kn/strings.xml
+++ b/packages/PackageInstaller/res/values-kn/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"ಅಪರಿಚಿತ"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"ನಿಮ್ಮ ಸುರಕ್ಷತೆಯ ದೃಷ್ಟಿಯಿಂದ, ಈ ಮೂಲದಿಂದ ಬಂದಿರುವ ಅಪರಿಚಿತ ಆ್ಯಪ್‌ಗಳನ್ನು ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡಲು ಪ್ರಸ್ತುತ ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್‌ಗೆ ಅನುಮತಿಯಿಲ್ಲ. ನೀವು ಇದನ್ನು ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"ನಿಮ್ಮ ಸುರಕ್ಷತೆಯ ದೃಷ್ಟಿಯಿಂದ, ಈ ಮೂಲದಿಂದ ಬಂದಿರುವ ಅಪರಿಚಿತ ಆ್ಯಪ್‌ಗಳನ್ನು ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡಲು ಪ್ರಸ್ತುತ ನಿಮ್ಮ ಟಿವಿಗೆ ಅನುಮತಿಯಿಲ್ಲ. ನೀವು ಇದನ್ನು ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ನಿಮ್ಮ ಸುರಕ್ಷತೆಯ ದೃಷ್ಟಿಯಿಂದ, ಈ ಮೂಲದಿಂದ ಬಂದಿರುವ ಅಪರಿಚಿತ ಆ್ಯಪ್‌ಗಳನ್ನು ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡಲು ಪ್ರಸ್ತುತ ನಿಮ್ಮ ವಾಚ್‌ಗೆ ಅನುಮತಿಯಿಲ್ಲ. ನೀವು ಇದನ್ನು ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ನಿಮ್ಮ ಸುರಕ್ಷತೆಯ ದೃಷ್ಟಿಯಿಂದ, ಈ ಮೂಲದಿಂದ ಬಂದಿರುವ ಅಪರಿಚಿತ ಆ್ಯಪ್‌ಗಳನ್ನು ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡಲು ಪ್ರಸ್ತುತ ನಿಮ್ಮ ಫೋನ್‌ಗೆ ಅನುಮತಿಯಿಲ್ಲ. ನೀವು ಇದನ್ನು ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"ನಿಮ್ಮ ಫೋನ್ ಹಾಗೂ ವೈಯಕ್ತಿಕ ಡೇಟಾ, ಅಪರಿಚಿತ ಆ್ಯಪ್‌ಗಳ ದಾಳಿಗೆ ತುತ್ತಾಗುವ ಸಾಧ್ಯತೆ ಹೆಚ್ಚಾಗಿದೆ. ಈ ಆ್ಯಪ್‌ ಅನ್ನು ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡುವ ಮೂಲಕ, ನಿಮ್ಮ ಫೋನ್‌ಗೆ ಯಾವುದೇ ಹಾನಿ ಉಂಟಾದರೆ ಅಥವಾ ಅದರ ಬಳಕೆಯಿಂದ ಡೇಟಾ ನಷ್ಟವಾದರೆ, ಅದಕ್ಕೆ ನೀವೇ ಜವಾಬ್ದಾರರು ಎನ್ನುವುದನ್ನು ಒಪ್ಪಿಕೊಳ್ಳುತ್ತೀರಿ."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ ಹಾಗೂ ವೈಯಕ್ತಿಕ ಡೇಟಾ, ಅಪರಿಚಿತ ಆ್ಯಪ್‌ಗಳ ದಾಳಿಗೆ ತುತ್ತಾಗುವ ಸಾಧ್ಯತೆ ಹೆಚ್ಚಾಗಿದೆ. ಈ ಆ್ಯಪ್‌ ಅನ್ನು ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡುವ ಮೂಲಕ, ನಿಮ್ಮ ಫೋನ್‌ಗೆ ಯಾವುದೇ ಹಾನಿ ಉಂಟಾದರೆ ಅಥವಾ ಅದರ ಬಳಕೆಯಿಂದ ಡೇಟಾ ನಷ್ಟವಾದರೆ, ಅದಕ್ಕೆ ನೀವೇ ಜವಾಬ್ದಾರರು ಎನ್ನುವುದನ್ನು ಒಪ್ಪಿಕೊಳ್ಳುತ್ತೀರಿ."</string>
diff --git a/packages/PackageInstaller/res/values-ko/strings.xml b/packages/PackageInstaller/res/values-ko/strings.xml
index b1e00a4..dfe2d84 100644
--- a/packages/PackageInstaller/res/values-ko/strings.xml
+++ b/packages/PackageInstaller/res/values-ko/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"알 수 없음"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"보안상의 이유로 이 출처의 알 수 없는 앱을 태블릿에 설치할 수 없습니다. 설정에서 변경할 수 있습니다."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"보안상의 이유로 현재 이 출처의 알 수 없는 앱을 TV에 설치할 수 없습니다. 설정에서 변경할 수 있습니다."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"보안상의 이유로 현재 이 출처의 알 수 없는 앱을 시계에 설치할 수 없습니다. 설정에서 변경할 수 있습니다."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"보안상의 이유로 현재 이 출처의 알 수 없는 앱을 휴대전화에 설치할 수 없습니다. 설정에서 변경할 수 있습니다."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"휴대전화와 개인 데이터는 알 수 없는 앱의 공격에 더욱 취약합니다. 이 앱을 설치하면 앱 사용으로 인해 발생할 수 있는 모든 휴대전화 손상이나 데이터 손실에 사용자가 책임을 진다는 것에 동의하게 됩니다."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"태블릿과 개인 데이터는 알 수 없는 앱의 공격에 더욱 취약합니다. 이 앱을 설치하면 앱 사용으로 인해 발생할 수 있는 모든 태블릿 손상이나 데이터 손실에 사용자가 책임을 진다는 것에 동의하게 됩니다."</string>
diff --git a/packages/PackageInstaller/res/values-ky/strings.xml b/packages/PackageInstaller/res/values-ky/strings.xml
index 2684fd9..f543955 100644
--- a/packages/PackageInstaller/res/values-ky/strings.xml
+++ b/packages/PackageInstaller/res/values-ky/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Белгисиз"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Коопсуздук максатында, планшетиңизге бул булактан колдонмолорду орнотууга болбойт. Бул параметрди каалаган убакта жөндөөлөрдөн өзгөртө аласыз."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Коопсуздук максатында, сыналгыңызга бул булактан колдонмолорду орнотууга болбойт. Бул параметрди каалаган убакта жөндөөлөрдөн өзгөртө аласыз."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Коопсуздукту сактоо максатында, азырынча саатыңызда бул булактан белгисиз колдонмолорду орнотууга уруксат жок. Муну параметрлерден өзгөртсөңүз болот."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Коопсуздук максатында, телефонуңузга бул булактан колдонмолорду орнотууга болбойт. Бул параметрди каалаган убакта жөндөөлөрдөн өзгөртө аласыз."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Телефонуңуз жана жеке дайын-даректериңиз белгисиз колдонмолордон зыян тартып калышы мүмкүн. Бул колдонмону орнотуп, аны пайдалануудан улам телефонуңузга кандайдыр бир зыян келтирилсе же дайын-даректериңизды жоготуп алсаңыз, өзүңүз жооптуу болосуз."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Планшетиңиз жана жеке дайын-даректериңиз белгисиз колдонмолордон зыян тартып калышы мүмкүн. Бул колдонмону орнотуп, аны пайдалануудан улам планшетиңизге кандайдыр бир зыян келтирилсе же дайын-даректериңизды жоготуп алсаңыз, өзүңүз жооптуу болосуз."</string>
diff --git a/packages/PackageInstaller/res/values-lo/strings.xml b/packages/PackageInstaller/res/values-lo/strings.xml
index 868c35d..58df433 100644
--- a/packages/PackageInstaller/res/values-lo/strings.xml
+++ b/packages/PackageInstaller/res/values-lo/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"ບໍ່ຮູ້ຈັກ"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"ເພື່ອຄວາມປອດໄພຂອງທ່ານ, ຕອນນີ້ແທັບເລັດຂອງທ່ານບໍ່ອະນຸຍາດໃຫ້ຕິດຕັ້ງແອັບທີ່ບໍ່ຮູ້ຈັກຈາກແຫຼ່ງທີ່ມານີ້ໄດ້. ທ່ານສາມາດປ່ຽນສິ່ງນີ້ໄດ້ໃນການຕັ້ງຄ່າ."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"ເພື່ອຄວາມປອດໄພຂອງທ່ານ, ຕອນນີ້ໂທລະທັດຂອງທ່ານບໍ່ອະນຸຍາດໃຫ້ຕິດຕັ້ງແອັບທີ່ບໍ່ຮູ້ຈັກຈາກແຫຼ່ງທີ່ມານີ້ໄດ້. ທ່ານສາມາດປ່ຽນສິ່ງນີ້ໄດ້ໃນການຕັ້ງຄ່າ."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ເພື່ອຄວາມປອດໄພຂອງທ່ານ, ປັດຈຸບັນໂມງຂອງທ່ານບໍ່ອະນຸຍາດໃຫ້ຕິດຕັ້ງແອັບທີ່ບໍ່ຮູ້ຈັກຈາກແຫຼ່ງທີ່ມານີ້. ທ່ານສາມາດປ່ຽນສິ່ງນີ້ໄດ້ໃນການຕັ້ງຄ່າ."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ເພື່ອຄວາມປອດໄພຂອງທ່ານ, ຕອນນີ້ໂທລະສັບຂອງທ່ານບໍ່ອະນຸຍາດໃຫ້ຕິດຕັ້ງແອັບທີ່ບໍ່ຮູ້ຈັກຈາກແຫຼ່ງທີ່ມານີ້ໄດ້. ທ່ານສາມາດປ່ຽນສິ່ງນີ້ໄດ້ໃນການຕັ້ງຄ່າ."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"ໂທລະສັບ ແລະ ຂໍ້ມູນສ່ວນຕົວຂອງທ່ານອາດຖືກໂຈມຕີໄດ້ໂດຍແອັບທີ່ບໍ່ຮູ້ຈັກ. ໂດຍການຕິດຕັ້ງແອັບນີ້, ແມ່ນທ່ານຍອມຮັບວ່າທ່ານຈະຮັບຜິດຊອບຕໍ່ຄວາມເສຍຫາຍໃດໆກໍຕາມທີ່ເກີດຂຶ້ນຕໍ່ໂທລະທັດຂອງທ່ານ ຫຼື ການສູນເສຍຂໍ້ມູນທີ່ອາດເກີດຈາກການນຳໃຊ້ມັນ."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"ແທັບເລັດ ແລະ ຂໍ້ມູນສ່ວນຕົວຂອງທ່ານອາດຖືກໂຈມຕີໄດ້ໂດຍແອັບທີ່ບໍ່ຮູ້ຈັກ. ໂດຍການຕິດຕັ້ງແອັບນີ້, ແມ່ນທ່ານຍອມຮັບວ່າທ່ານຈະຮັບຜິດຊອບຕໍ່ຄວາມເສຍຫາຍໃດໆກໍຕາມທີ່ເກີດຂຶ້ນຕໍ່ໂທລະທັດຂອງທ່ານ ຫຼື ການສູນເສຍຂໍ້ມູນທີ່ອາດເກີດຈາກການນຳໃຊ້ມັນ."</string>
diff --git a/packages/PackageInstaller/res/values-lt/strings.xml b/packages/PackageInstaller/res/values-lt/strings.xml
index 4dfbb27..e1e4e64 100644
--- a/packages/PackageInstaller/res/values-lt/strings.xml
+++ b/packages/PackageInstaller/res/values-lt/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Nežinoma"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Saugos sumetimais šiuo metu planšetiniame kompiuteryje neleidžiama diegti nežinomų programų iš šio šaltinio. Tai galite pakeisti nustatymuose."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Saugos sumetimais šiuo metu televizoriuje neleidžiama diegti nežinomų programų iš šio šaltinio. Tai galite pakeisti nustatymuose."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Saugos sumetimais šiuo metu laikrodyje neleidžiama diegti nežinomų programų iš šio šaltinio. Tai galite pakeisti nustatymuose."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Saugos sumetimais šiuo metu telefone neleidžiama diegti nežinomų programų iš šio šaltinio. Tai galite pakeisti nustatymuose."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonas ir asmens duomenys labiau pažeidžiami įdiegus nežinomų programų. Įdiegdami šią programą sutinkate, kad patys esate atsakingi už žalą telefonui arba prarastus duomenis dėl šios programos naudojimo."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Planšetinis kompiuteris ir asmens duomenys labiau pažeidžiami įdiegus nežinomų programų. Įdiegdami šią programą sutinkate, kad patys esate atsakingi už žalą planšetiniam kompiuteriui arba prarastus duomenis dėl šios programos naudojimo."</string>
diff --git a/packages/PackageInstaller/res/values-lv/strings.xml b/packages/PackageInstaller/res/values-lv/strings.xml
index 2a5507f..f765ba9 100644
--- a/packages/PackageInstaller/res/values-lv/strings.xml
+++ b/packages/PackageInstaller/res/values-lv/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Nezināma"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Drošības apsvērumu dēļ jūsu planšetdatorā pašlaik nav atļauts instalēt nezināmas lietotnes no šī avota. Jūs varat to mainīt iestatījumos."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Drošības apsvērumu dēļ jūsu televizorā pašlaik nav atļauts instalēt nezināmas lietotnes no šī avota. Jūs varat to mainīt iestatījumos."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Drošības apsvērumu dēļ jūsu pulkstenī pašlaik nav atļauts instalēt nezināmas lietotnes no šī avota. Šo atļauju varat mainīt iestatījumos."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Drošības apsvērumu dēļ jūsu tālrunī pašlaik nav atļauts instalēt nezināmas lietotnes no šī avota. Jūs varat to mainīt iestatījumos."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Jūsu tālrunis un personas dati ir neaizsargātāki pret uzbrukumiem no nezināmām lietotnēm. Instalējot šo lietotni, jūs piekrītat, ka esat atbildīgs par tālruņa bojājumiem vai datu zudumu, kas var rasties lietotnes dēļ."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Jūsu planšetdators un personas dati ir neaizsargātāki pret uzbrukumiem no nezināmām lietotnēm. Instalējot šo lietotni, jūs piekrītat, ka esat atbildīgs par planšetdatora bojājumiem vai datu zudumu, kas var rasties lietotnes dēļ."</string>
diff --git a/packages/PackageInstaller/res/values-mk/strings.xml b/packages/PackageInstaller/res/values-mk/strings.xml
index 93f37bf..18e4432 100644
--- a/packages/PackageInstaller/res/values-mk/strings.xml
+++ b/packages/PackageInstaller/res/values-mk/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Непозната"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"За ваша безбедност, таблетот нема дозвола за инсталирање непознати апликации од изворов во моментов. Ова може да го промените во „Поставки“."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"За ваша безбедност, телевизорот нема дозвола за инсталирање непознати апликации од изворов во моментов. Ова може да го промените во „Поставки“."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"За ваша безбедност, часовникот нема дозвола за инсталирање непознати апликации од изворов во моментов. Ова може да го промените во „Поставки“."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"За ваша безбедност, телефонот нема дозвола за инсталирање непознати апликации од изворов во моментов. Ова може да го промените во „Поставки“."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Телефонот и личните податоци се поподложни на напади од апликации од непознати извори. Ако ја инсталирате апликацијава, се согласувате дека сте одговорни за каква било штета на телефонот или загуба на податоци поради нејзиното користење."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Таблетот и личните податоци се поподложни на напади од апликации од непознати извори. Ако ја инсталирате апликацијава, се согласувате дека сте одговорни за каква било штета на таблетот или загуба на податоци поради нејзиното користење."</string>
diff --git a/packages/PackageInstaller/res/values-ml/strings.xml b/packages/PackageInstaller/res/values-ml/strings.xml
index 66de3f1..99c6b2a 100644
--- a/packages/PackageInstaller/res/values-ml/strings.xml
+++ b/packages/PackageInstaller/res/values-ml/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"അജ്ഞാതം"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"നിങ്ങളുടെ സുരക്ഷയ്‌ക്ക്, ഈ ഉറവിടത്തിൽ നിന്നുള്ള അജ്ഞാതമായ ആപ്പുകൾ ഇൻസ്‌റ്റാൾ ചെയ്യാൻ നിലവിൽ നിങ്ങളുടെ ടാബ്‌ലെറ്റിന് അനുമതിയില്ല. നിങ്ങൾക്ക് ഇത് ക്രമീകരണത്തിൽ മാറ്റാം."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"നിങ്ങളുടെ സുരക്ഷയ്‌ക്ക്, ഈ ഉറവിടത്തിൽ നിന്നുള്ള അജ്ഞാതമായ ആപ്പുകൾ ഇൻസ്‌റ്റാൾ ചെയ്യാൻ നിലവിൽ നിങ്ങളുടെ ടിവിക്ക് അനുമതിയില്ല. നിങ്ങൾക്ക് ഇത് ക്രമീകരണത്തിൽ മാറ്റാം."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"നിങ്ങളുടെ സുരക്ഷയ്‌ക്ക്, ഈ ഉറവിടത്തിൽ നിന്നുള്ള അജ്ഞാതമായ ആപ്പുകൾ ഇൻസ്‌റ്റാൾ ചെയ്യാൻ നിലവിൽ നിങ്ങളുടെ വാച്ചിന് അനുമതിയില്ല. നിങ്ങൾക്ക് ഇത് ക്രമീകരണത്തിൽ മാറ്റാം."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"നിങ്ങളുടെ സുരക്ഷയ്‌ക്ക്, ഈ ഉറവിടത്തിൽ നിന്നുള്ള അജ്ഞാതമായ ആപ്പുകൾ ഇൻസ്‌റ്റാൾ ചെയ്യാൻ നിലവിൽ നിങ്ങളുടെ ഫോണിന് അനുമതിയില്ല. നിങ്ങൾക്ക് ഇത് ക്രമീകരണത്തിൽ മാറ്റാം."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"അജ്ഞാതമായ ആപ്പുകളാൽ നിങ്ങളുടെ ഫോണും വ്യക്തിഗത ഡാറ്റയും ആക്രമിക്കപ്പെടാനുള്ള സാധ്യത വളരെ കൂടുതലാണ്. ഈ ആപ്പ് ഇൻസ്‌റ്റാൾ ചെയ്യുന്നതിലൂടെ, ഇത് ഉപയോഗിക്കുന്നതിനാൽ നിങ്ങളുടെ ഫോണിന് സംഭവിച്ചേക്കാവുന്ന ഏത് നാശനഷ്‌ടത്തിന്റെയും അല്ലെങ്കിൽ ഡാറ്റാ നഷ്‌ടത്തിന്റെയും ഉത്തരവാദിത്തം നിങ്ങൾക്കായിരിക്കുമെന്ന് അംഗീകരിക്കുന്നു."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"അജ്ഞാതമായ ആപ്പുകളാൽ നിങ്ങളുടെ ടാബ്‌ലെറ്റും വ്യക്തിഗത ഡാറ്റയും ആക്രമിക്കപ്പെടാനുള്ള സാധ്യത വളരെ കൂടുതലാണ്. ഈ ആപ്പ് ഇൻസ്‌റ്റാൾ ചെയ്യുന്നതിലൂടെ, ഇത് ഉപയോഗിക്കുന്നതിനാൽ നിങ്ങളുടെ ടാബ്‌ലെറ്റിന് സംഭവിച്ചേക്കാവുന്ന ഏത് നാശനഷ്‌ടത്തിന്റെയും അല്ലെങ്കിൽ ഡാറ്റാ നഷ്‌ടത്തിന്റെയും ഉത്തരവാദിത്തം നിങ്ങൾക്കായിരിക്കുമെന്ന് അംഗീകരിക്കുന്നു."</string>
diff --git a/packages/PackageInstaller/res/values-mn/strings.xml b/packages/PackageInstaller/res/values-mn/strings.xml
index 5eb0e6c..7b2e1d4 100644
--- a/packages/PackageInstaller/res/values-mn/strings.xml
+++ b/packages/PackageInstaller/res/values-mn/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Тодорхойгүй"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Таны аюулгүй байдлыг хангах үүднээс таны таблетыг одоогоор энэ эх сурвалжаас тодорхойгүй аппууд суулгахыг зөвшөөрөөгүй. Та үүнийг Тохиргоо хэсэгт өөрчлөх боломжтой."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Таны аюулгүй байдлыг хангах үүднээс таны ТВ-ийг одоогоор энэ эх сурвалжаас тодорхойгүй аппууд суулгахыг зөвшөөрөөгүй. Та үүнийг Тохиргоо хэсэгт өөрчлөх боломжтой."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Таны аюулгүй байдлыг хангах үүднээс таны цагийг одоогоор энэ эх сурвалжаас тодорхойгүй аппууд суулгахыг зөвшөөрөөгүй. Та үүнийг Тохиргоо хэсэгт өөрчлөх боломжтой."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Таны аюулгүй байдлыг хангах үүднээс таны утсыг одоогоор энэ эх сурвалжаас тодорхойгүй аппууд суулгахыг зөвшөөрөөгүй. Та үүнийг Тохиргоо хэсэгт өөрчлөх боломжтой."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Таны утас болон хувийн өгөгдөл тодорхойгүй апп суулгасан тохиолдолд гэмтэж болзошгүй. Энэ аппыг суулгаснаар үүнийг ашигласнаас үүдэн таны утсанд гэмтэл гарах, эсвэл өгөгдөл устах зэрэг эрсдэлийг хариуцна гэдгээ зөвшөөрч байна."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Таны таблет болон хувийн өгөгдөл тодорхойгүй апп суулгасан тохиолдолд гэмтэж болзошгүй. Энэ аппыг суулгаснаар үүнийг ашигласнаас үүдэн таны таблетад гэмтэл гарах, эсвэл өгөгдөл устах зэрэг эрсдэлийг хариуцна гэдгээ зөвшөөрч байна."</string>
diff --git a/packages/PackageInstaller/res/values-mr/strings.xml b/packages/PackageInstaller/res/values-mr/strings.xml
index 40bb680..bf23e7a 100644
--- a/packages/PackageInstaller/res/values-mr/strings.xml
+++ b/packages/PackageInstaller/res/values-mr/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"अज्ञात"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"तुमच्या सुरक्षिततेसाठी, तुमच्या टॅबलेटला सध्या या स्रोतावरील अज्ञात अ‍ॅप्स इंस्टॉल करण्याची अनुमती नाही. तुम्ही हे सेटिंग्‍ज मध्‍ये बदलू शकता."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"तुमच्या सुरक्षिततेसाठी, तुमच्या टीव्हीला सध्या या स्रोतावरील अज्ञात अ‍ॅप्स इंस्टॉल करण्याची अनुमती नाही. तुम्ही हे सेटिंग्‍ज मध्‍ये बदलू शकता."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"तुमच्या सुरक्षिततेसाठी, तुमच्या वॉचला सध्या या स्रोतावरील अज्ञात अ‍ॅप्स इंस्टॉल करण्याची अनुमती नाही. तुम्ही हे सेटिंग्‍ज मध्‍ये बदलू शकता."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"तुमच्या सुरक्षेसाठी, तुमच्या फोनला सध्या या स्रोतावरील अज्ञात अ‍ॅप्स इंस्टॉल करण्याची अनुमती नाही. तुम्ही हे सेटिंग्‍ज मध्‍ये बदलू शकता."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"तुमचा फोन आणि वैयक्तिक डेटा अज्ञात अ‍ॅप्‍सकडून होणार्‍या अटॅकमुळे अधिक असुरक्षित आहे. हे अ‍ॅप इंस्टॉल करून, तुम्‍ही सहमती देता की ते वापरल्‍याने होणार्‍या तुमच्‍या फोनचे कोणत्‍याही प्रकारे होणारे नुकसान किंवा डेटा हानीसाठी तुम्‍ही जबाबदार आहात."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"तुमचा टॅबलेट आणि वैयक्तिक डेटा अज्ञात अ‍ॅप्‍सकडून होणार्‍या अटॅकमुळे अधिक असुरक्षित आहे. हे अ‍ॅप इंस्टॉल करून, तुम्‍ही सहमती देता की ते वापरल्‍याने तुमच्‍या टॅबलेटचे कोणत्‍याही प्रकारे होणारे नुकसान किंवा डेटा हानीसाठी तुम्‍ही जबाबदार आहात."</string>
diff --git a/packages/PackageInstaller/res/values-ms/strings.xml b/packages/PackageInstaller/res/values-ms/strings.xml
index 09ed820..15685c1 100644
--- a/packages/PackageInstaller/res/values-ms/strings.xml
+++ b/packages/PackageInstaller/res/values-ms/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Tidak diketahui"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Untuk keselamatan, tablet anda kini tidak dibenarkan memasang apl yang tidak diketahui daripada sumber ini buat masa ini. Anda boleh menukar pilihan ini dalam Tetapan."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Untuk keselamatan, TV anda tidak dibenarkan memasang apl yang tidak diketahui daripada sumber ini buat masa ini. Anda boleh menukar pilihan ini dalam Tetapan."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Untuk keselamatan anda, jam tangan anda tidak dibenarkan memasang apl yang tidak diketahui daripada sumber ini buat masa ini. Anda boleh menukar pilihan ini dalam Tetapan."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Untuk keselamatan anda, telefon anda kini tidak dibenarkan memasang apl yang tidak diketahui daripada sumber ini buat masa ini. Anda boleh menukar pilihan ini dalam Tetapan."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefon dan data peribadi anda lebih mudah diserang oleh apl yang tidak diketahui. Dengan memasang apl ini, anda bersetuju bahawa anda bertanggungjawab atas sebarang kerosakan pada telefon anda atau kehilangan data yang mungkin disebabkan oleh penggunaan apl tersebut."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tablet dan data peribadi anda lebih mudah diserang oleh apl yang tidak diketahui. Dengan memasang apl ini, anda bersetuju bahawa anda bertanggungjawab atas sebarang kerosakan pada tablet anda atau kehilangan data yang mungkin disebabkan oleh penggunaan apl tersebut."</string>
diff --git a/packages/PackageInstaller/res/values-my/strings.xml b/packages/PackageInstaller/res/values-my/strings.xml
index 7b632d0..1c35eb2 100644
--- a/packages/PackageInstaller/res/values-my/strings.xml
+++ b/packages/PackageInstaller/res/values-my/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"အမည်မသိ"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"လုံခြုံရေးအရ ဤရင်းမြစ်မှရယူထားသည့် အမျိုးအမည်မသိသောအက်ပ်များကို သင့်တက်ဘလက်တွင် လောလောဆယ် ထည့်သွင်းခွင့်မရှိပါ။ ၎င်းကို ‘ဆက်တင်များ’ တွင် ပြောင်းနိုင်ပါသည်။"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"လုံခြုံရေးအရ ဤရင်းမြစ်မှရယူထားသည့် အမျိုးအမည်မသိသောအက်ပ်များကို သင့် TV တွင် လောလောဆယ် ထည့်သွင်းခွင့်မရှိပါ။ ၎င်းကို ‘ဆက်တင်များ’ တွင် ပြောင်းနိုင်ပါသည်။"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ဤရင်းမြစ်မှရယူထားသည့် အမျိုးအမည်မသိသောအက်ပ်များကို လုံခြုံရေးအရ သင့်လက်ပတ်နာရီတွင် လောလောဆယ် ထည့်သွင်းခွင့်မပြုပါ။ ၎င်းကို ဆက်တင်များတွင် ပြောင်းနိုင်သည်။"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ဤရင်းမြစ်မှရယူထားသည့် အမျိုးအမည်မသိသောအက်ပ်များကို သင့်လုံခြုံရေးအတွက် သင့်ဖုန်းတွင် လောလောဆယ် ထည့်သွင်းခွင့်ပြုမထားပါ။ ၎င်းကို ‘ဆက်တင်များ’ တွင် ပြောင်းနိုင်ပါသည်။"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"သင်၏ဖုန်းနှင့် ကိုယ်ရေးကိုယ်တာ အချက်အလက်များသည် အမျိုးအမည် မသိသောအက်ပ်များ၏ တိုက်ခိုက်ခြင်းကို ပိုမိုခံရနိုင်ပါသည်။ ဤအက်ပ်ကို ထည့်သွင်းအသုံးပြုခြင်းအားဖြင့် ဖြစ်ပေါ်လာနိုင်သော ဖုန်းပျက်စီးမှု သို့မဟုတ် ဒေတာဆုံးရှုံးမှုများအတွက် သင့်ထံ၌သာ တာဝန်ရှိကြောင်း သဘောတူရာရောက်ပါသည်။"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"သင်၏ တက်ဘလက်နှင့် ကိုယ်ရေးကိုယ်တာ အချက်အလက်များသည် အမျိုးအမည် မသိသောအက်ပ်များ၏ တိုက်ခိုက်ခြင်းကို ပိုမိုခံရနိုင်ပါသည်။ ဤအက်ပ်ကို ထည့်သွင်းအသုံးပြုခြင်းအားဖြင့် ဖြစ်ပေါ်လာနိုင်သော တက်ဘလက်ပျက်စီးမှု သို့မဟုတ် ဒေတာဆုံးရှုံးမှုများအတွက် သင့်ထံ၌သာ တာဝန်ရှိကြောင်း သဘောတူရာရောက်ပါသည်။"</string>
diff --git a/packages/PackageInstaller/res/values-nb/strings.xml b/packages/PackageInstaller/res/values-nb/strings.xml
index a0a00b3..4d4bc8b 100644
--- a/packages/PackageInstaller/res/values-nb/strings.xml
+++ b/packages/PackageInstaller/res/values-nb/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Ukjent"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Nettbrettet har for øyeblikket ikke tillatelse til å installere ukjente apper fra denne kilden, for å ivareta sikkerheten din. Du kan endre dette i innstillingene."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"TV-en har for øyeblikket ikke tillatelse til å installere ukjente apper fra denne kilden, for å ivareta sikkerheten din. Du kan endre dette i innstillingene."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Av sikkerhetsgrunner har klokken for øyeblikket ikke tillatelse til å installere ukjente apper fra denne kilden. Du kan endre dette i innstillingene."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Telefonen har for øyeblikket ikke tillatelse til å installere ukjente apper fra denne kilden, for å ivareta sikkerheten din. Du kan endre dette i innstillingene."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonen din og de personlige dataene dine er mer sårbare for angrep fra ukjente apper. Når du installerer denne appen, samtykker du i at du er ansvarlig for eventuelle skader på telefonen eller tap av data som kan skyldes bruk av appen"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Nettbrettet ditt og de personlige dataene dine er mer sårbare for angrep fra ukjente apper. Når du installerer denne appen, samtykker du i at du er ansvarlig for eventuelle skader på nettbrettet eller tap av data som kan skyldes bruk av appen."</string>
diff --git a/packages/PackageInstaller/res/values-ne/strings.xml b/packages/PackageInstaller/res/values-ne/strings.xml
index 50422d7..40e9382 100644
--- a/packages/PackageInstaller/res/values-ne/strings.xml
+++ b/packages/PackageInstaller/res/values-ne/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"अज्ञात"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"तपाईंको सुरक्षार्थ हाल तपाईंको ट्याब्लेटमा यो स्रोतबाट अज्ञात एपहरू इन्स्टल गर्ने अनुमति दिइएको छैन। तपाईं सेटिङमा गई यो कुरा परिवर्तन गर्न सक्नुहुन्छ।"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"तपाईंको सुरक्षार्थ हाल तपाईंको टिभीमा यो स्रोतबाट अज्ञात एपहरू इन्स्टल गर्ने अनुमति दिइएको छैन। तपाईं सेटिङमा गई यो कुरा परिवर्तन गर्न सक्नुहुन्छ।"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"तपाईंको सुरक्षाका लागि हाल तपाईंको स्मार्टवाचमा यो स्रोतबाट उपलब्ध अज्ञात एपहरू इन्स्टल गर्ने अनुमति दिइएको छैन। तपाईं सेटिङमा गई यो अनुमति बदल्न सक्नुहुन्छ।"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"तपाईंको सुरक्षार्थ हाल तपाईंको फोनमा यो स्रोतबाट अज्ञात एपहरू इन्स्टल गर्ने अनुमति दिइएको छैन। तपाईं सेटिङमा गई यो कुरा परिवर्तन गर्न सक्नुहुन्छ।"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"तपाईंको फोन तथा व्यक्तिगत डेटा अज्ञात एपहरूबाट हुने आक्रमणको चपेटामा पर्ने बढी जोखिममा हुन्छन्। यो एप स्थापना गरेर तपाईं यसको प्रयोगबाट तपाईंको फोनमा हुन सक्ने क्षति वा डेटाको नोक्सानीका लागि स्वयं जिम्मेवार हुने कुरामा सहमत हुनुहुन्छ।"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"तपाईंको ट्याब्लेट तथा व्यक्तिगत डेटा अज्ञात एपहरूबाट हुने आक्रमणको चपेटामा पर्ने बढी जोखिममा हुन्छन्। यो एप स्थापना गरेर तपाईं यसको प्रयोगबाट तपाईंको ट्याब्लेटमा हुन सक्ने क्षति वा डेटाको नोक्सानीका लागि स्वयं जिम्मेवार हुने कुरामा सहमत हुनुहुन्छ।"</string>
diff --git a/packages/PackageInstaller/res/values-nl/strings.xml b/packages/PackageInstaller/res/values-nl/strings.xml
index 53a9a00..9b19458 100644
--- a/packages/PackageInstaller/res/values-nl/strings.xml
+++ b/packages/PackageInstaller/res/values-nl/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Onbekend"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Uit veiligheidsoverwegingen heeft je tablet momenteel geen toestemming om onbekende apps van deze bron te installeren. Je kunt dit wijzigen via Instellingen."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Uit veiligheidsoverwegingen heeft je tv momenteel geen toestemming om onbekende apps van deze bron te installeren. Je kunt dit wijzigen via Instellingen."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Uit veiligheidsoverwegingen heeft je smartwatch momenteel geen toestemming om onbekende apps van deze bron te installeren. Je kunt dit wijzigen via Instellingen."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Uit veiligheidsoverwegingen heeft je telefoon momenteel geen toestemming om onbekende apps van deze bron te installeren. Je kunt dit wijzigen via Instellingen."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Je telefoon en persoonsgegevens zijn kwetsbaarder voor aanvallen door onbekende apps. Als je deze app installeert, ga je ermee akkoord dat je verantwoordelijk bent voor eventuele schade aan je telefoon of gegevensverlies als gevolg van het gebruik van de app."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Je tablet en persoonsgegevens zijn kwetsbaarder voor aanvallen door onbekende apps. Als je deze app installeert, ga je ermee akkoord dat je verantwoordelijk bent voor eventuele schade aan je tablet of gegevensverlies als gevolg van het gebruik van de app."</string>
diff --git a/packages/PackageInstaller/res/values-or/strings.xml b/packages/PackageInstaller/res/values-or/strings.xml
index 75d5d2d..9f3b255 100644
--- a/packages/PackageInstaller/res/values-or/strings.xml
+++ b/packages/PackageInstaller/res/values-or/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"ଅଜଣା"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"ଆପଣଙ୍କ ସୁରକ୍ଷା ପାଇଁ, ଆପଣଙ୍କ ଟାବଲେଟକୁ ବର୍ତ୍ତମାନ ଏହି ସୋର୍ସରୁ ଆସିଥିବା ଅଜଣା ଆପଗୁଡ଼ିକୁ ଇନଷ୍ଟଲ୍ କରିବା ପାଇଁ ଅନୁମତି ଦିଆଯାଇନାହିଁ। ଆପଣ ଏହାକୁ ସେଟିଂସରେ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"ଆପଣଙ୍କ ସୁରକ୍ଷା ପାଇଁ, ଆପଣଙ୍କ ଟିଭିକୁ ବର୍ତ୍ତମାନ ଏହି ସୋର୍ସରୁ ଆସିଥିବା ଅଜଣା ଆପଗୁଡ଼ିକୁ ଇନଷ୍ଟଲ୍ କରିବା ପାଇଁ ଅନୁମତି ଦିଆଯାଇନାହିଁ। ଆପଣ ଏହାକୁ ସେଟିଂସରେ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ଆପଣଙ୍କ ସୁରକ୍ଷା ପାଇଁ ବର୍ତ୍ତମାନ ଏହି ସୋର୍ସରୁ ଅଜଣା ଆପ୍ସକୁ ଇନଷ୍ଟଲ କରିବା ପାଇଁ ଆପଣଙ୍କ ୱାଚକୁ ଅନୁମତି ଦିଆଯାଇନାହିଁ। ଆପଣ ଏହାକୁ ସେଟିଂସରେ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ଆପଣଙ୍କ ସୁରକ୍ଷା ପାଇଁ, ଆପଣଙ୍କ ଫୋନକୁ ବର୍ତ୍ତମାନ ଏହି ସୋର୍ସରୁ ଆସିଥିବା ଅଜଣା ଆପଗୁଡ଼ିକୁ ଇନଷ୍ଟଲ୍ କରିବା ପାଇଁ ଅନୁମତି ଦିଆଯାଇନାହିଁ। ଆପଣ ଏହାକୁ ସେଟିଂସରେ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"ଅଜଣା ଆପ୍‌ ଦ୍ୱାରା ଆପଣଙ୍କ ଫୋନ୍‍ ଏବଂ ବ୍ୟକ୍ତିଗତ ଡାଟାକୁ ନଷ୍ଟ କରାଯାଇପାରିବାର ସମ୍ଭାବନା ବହୁତ ଅଧିକ। ଏହି ଆପ୍‌କୁ ଇନଷ୍ଟଲ୍‌ କରିବାର ଅର୍ଥ ହେଉଛି ଆପଣଙ୍କ ଫୋନ୍‌ରେ ଘଟିବା କୌଣସି ପ୍ରକାର କ୍ଷତି କିମ୍ବା ସେଗୁଡ଼ିକର ବ୍ୟବହାରରୁ ହେବା କୌଣସି ପ୍ରକାର ଡାଟାର ହାନୀ ପାଇଁ ଆପଣ ଦାୟୀ ରହିବାକୁ ରାଜି ହୁଅନ୍ତି।"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"ଅଜଣା ଆପ୍‌ ଦ୍ୱାରା ଆପଣଙ୍କ ଟାବଲେଟ୍‍ ଏବଂ ବ୍ୟକ୍ତିଗତ ଡାଟାକୁ ନଷ୍ଟ କରାଯାଇପାରିବାର ସମ୍ଭାବନା ବହୁତ ଅଧିକ। ଏହି ଆପ୍‌କୁ ଇନଷ୍ଟଲ୍‌ କରିବାର ଅର୍ଥ ହେଉଛି ଆପଣଙ୍କ ଟାବ୍‌ଲେଟ୍‌ରେ ଘଟିବା କୌଣସି ପ୍ରକାର କ୍ଷତି କିମ୍ବା ସେଗୁଡ଼ିକର ବ୍ୟବହାରରୁ ହେବା କୌଣସି ପ୍ରକାର ଡାଟାର ହାନୀ ପାଇଁ ଆପଣ ଦାୟୀ ରହିବାକୁ ରାଜି ହୁଅନ୍ତି।"</string>
diff --git a/packages/PackageInstaller/res/values-pa/strings.xml b/packages/PackageInstaller/res/values-pa/strings.xml
index 05bf127..058af82 100644
--- a/packages/PackageInstaller/res/values-pa/strings.xml
+++ b/packages/PackageInstaller/res/values-pa/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"ਅਗਿਆਤ"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"ਤੁਹਾਡੀ ਸੁਰੱਖਿਆ ਲਈ, ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ ਟੈਬਲੈੱਟ ਨੂੰ ਇਸ ਸਰੋਤ ਤੋਂ ਅਗਿਆਤ ਐਪਾਂ ਸਥਾਪਤ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ। ਤੁਸੀਂ ਇਸ ਨੂੰ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"ਤੁਹਾਡੀ ਸੁਰੱਖਿਆ ਲਈ, ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ ਟੀਵੀ ਨੂੰ ਇਸ ਸਰੋਤ ਤੋਂ ਅਗਿਆਤ ਐਪਾਂ ਸਥਾਪਤ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ। ਤੁਸੀਂ ਇਸ ਨੂੰ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ਤੁਹਾਡੀ ਸੁਰੱਖਿਆ ਲਈ, ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੀ ਘੜੀ ਨੂੰ ਇਸ ਸਰੋਤ ਤੋਂ ਅਗਿਆਤ ਐਪਾਂ ਸਥਾਪਤ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਹੈ। ਤੁਸੀਂ ਇਸ ਨੂੰ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ਤੁਹਾਡੀ ਸੁਰੱਖਿਆ ਲਈ, ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ ਫ਼ੋਨ ਨੂੰ ਇਸ ਸਰੋਤ ਤੋਂ ਅਗਿਆਤ ਐਪਾਂ ਸਥਾਪਤ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਹੈ। ਤੁਸੀਂ ਇਸ ਨੂੰ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"ਅਗਿਆਤ ਐਪਾਂ ਤੋਂ ਹੋਣ ਵਾਲੇ ਹਮਲਿਆਂ ਕਰਕੇ ਤੁਹਾਡੇ ਫ਼ੋਨ ਅਤੇ ਨਿੱਜੀ ਡਾਟੇ ਨਾਲ ਛੇੜਛਾੜ ਹੋ ਸਕਦੀ ਹੈ। ਇਹ ਐਪ ਸਥਾਪਤ ਕਰਕੇ, ਤੁਸੀਂ ਸਹਿਮਤੀ ਦਿੰਦੇ ਹੋ ਕਿ ਆਪਣੇ ਫ਼ੋਨ ਨੂੰ ਹੋਣ ਵਾਲੇ ਕਿਸੇ ਵੀ ਨੁਕਸਾਨ ਜਾਂ ਡਾਟੇ ਦੀ ਹਾਨੀ ਲਈ ਤੁਸੀਂ ਜ਼ਿੰਮੇਵਾਰ ਹੋ ਜੋ ਸ਼ਾਇਦ ਇਸ ਐਪ ਨੂੰ ਵਰਤਣ ਦੇ ਨਤੀਜੇ ਵਜੋਂ ਹੋ ਸਕਦਾ ਹੈ।"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"ਤੁਹਾਡਾ ਟੈਬਲੈੱਟ ਅਤੇ ਨਿੱਜੀ ਡਾਟਾ ਅਗਿਆਤ ਐਪਾਂ ਤੋਂ ਹਮਲੇ ਪ੍ਰਤੀ ਵਧੇਰੇ ਵਿੰਨਣਸ਼ੀਲ ਹਨ। ਇਹ ਐਪ ਸਥਾਪਤ ਕਰਕੇ, ਤੁਸੀਂ ਸਹਿਮਤੀ ਦਿੰਦੇ ਹੋ ਕਿ ਆਪਣੇ ਟੈਬਲੈੱਟ ਨੂੰ ਹੋਣ ਵਾਲੇ ਕਿਸੇ ਵੀ ਨੁਕਸਾਨ ਜਾਂ ਡਾਟੇ ਦੀ ਹਾਨੀ ਲਈ ਤੁਸੀਂ ਜ਼ੁੰਮੇਵਾਰ ਹੋ ਜੋ ਸ਼ਾਇਦ ਇਸ ਐਪ ਨੂੰ ਵਰਤਣ ਦੇ ਨਤੀਜੇ ਵਜੋਂ ਹੋ ਸਕਦਾ ਹੈ।"</string>
diff --git a/packages/PackageInstaller/res/values-pl/strings.xml b/packages/PackageInstaller/res/values-pl/strings.xml
index 6f6469d..39be12f 100644
--- a/packages/PackageInstaller/res/values-pl/strings.xml
+++ b/packages/PackageInstaller/res/values-pl/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Inny"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Ze względów bezpieczeństwa na Twoim tablecie nie można obecnie instalować nieznanych aplikacji z tego źródła. Możesz to zmienić w Ustawieniach."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Ze względów bezpieczeństwa na Twoim telewizorze nie można obecnie instalować nieznanych aplikacji z tego źródła. Możesz to zmienić w Ustawieniach."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Ze względów bezpieczeństwa na Twoim zegarku nie można obecnie instalować nieznanych aplikacji z tego źródła. Możesz to zmienić w Ustawieniach."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Ze względów bezpieczeństwa na Twoim telefonie nie można obecnie instalować nieznanych aplikacji z tego źródła. Możesz to zmienić w Ustawieniach."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Dane na telefonie i prywatne są bardziej narażone na atak nieznanych aplikacji. Instalując tę aplikację, bierzesz na siebie odpowiedzialność za ewentualne uszkodzenie telefonu lub utratę danych w wyniku jej używania."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Dane na tablecie i prywatne są bardziej narażone na atak nieznanych aplikacji. Instalując tę aplikację, bierzesz na siebie odpowiedzialność za ewentualne uszkodzenie tabletu lub utratę danych w wyniku jej używania."</string>
diff --git a/packages/PackageInstaller/res/values-pt-rBR/strings.xml b/packages/PackageInstaller/res/values-pt-rBR/strings.xml
index b9e5be8..97a3eb8 100644
--- a/packages/PackageInstaller/res/values-pt-rBR/strings.xml
+++ b/packages/PackageInstaller/res/values-pt-rBR/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Desconhecido"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Para sua segurança, o tablet não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso nas configurações."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Para sua segurança, a TV não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso nas configurações."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Para sua segurança, o relógio não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso em \"Configurações\"."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Para sua segurança, o smartphone não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso nas configurações."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Seu smartphone e seus dados pessoais estão mais vulneráveis a ataques de apps desconhecidos. Ao instalar esse app, você concorda que é responsável por qualquer perda de dados ou dano ao dispositivo causados pelo uso desses apps."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Seu tablet e seus dados pessoais estão mais vulneráveis a ataques de apps desconhecidos. Ao instalar esse app, você concorda que é responsável por qualquer perda de dados ou dano ao dispositivo causados pelo uso desses apps."</string>
diff --git a/packages/PackageInstaller/res/values-pt-rPT/strings.xml b/packages/PackageInstaller/res/values-pt-rPT/strings.xml
index ae69ed1..ce23ec6 100644
--- a/packages/PackageInstaller/res/values-pt-rPT/strings.xml
+++ b/packages/PackageInstaller/res/values-pt-rPT/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Desconhecida"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Para sua segurança, o tablet não está atualmente autorizado a instalar apps desconhecidas a partir desta origem. Pode alterar esta opção nas Definições."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Para sua segurança, a TV não está atualmente autorizada a instalar apps desconhecidas a partir desta origem. Pode alterar esta opção nas Definições."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Para sua segurança, o relógio não está atualmente autorizado a instalar apps desconhecidas a partir desta origem. Pode alterar esta opção nas Definições."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Para sua segurança, o telemóvel não está atualmente autorizado a instalar apps desconhecidas a partir desta origem. Pode alterar esta opção nas Definições."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"O seu telemóvel e os dados pessoais estão mais vulneráveis a ataques por parte de aplicações desconhecidas. Ao instalar esta app, concorda que é responsável por quaisquer danos causados ao telemóvel ou pelas perdas de dados que possam resultar da utilização da mesma."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"O seu tablet e os dados pessoais estão mais vulneráveis a ataques por parte de aplicações desconhecidas. Ao instalar esta app, concorda que é responsável por quaisquer danos causados ao tablet ou pelas perdas de dados que possam resultar da utilização da mesma."</string>
diff --git a/packages/PackageInstaller/res/values-pt/strings.xml b/packages/PackageInstaller/res/values-pt/strings.xml
index b9e5be8..97a3eb8 100644
--- a/packages/PackageInstaller/res/values-pt/strings.xml
+++ b/packages/PackageInstaller/res/values-pt/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Desconhecido"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Para sua segurança, o tablet não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso nas configurações."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Para sua segurança, a TV não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso nas configurações."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Para sua segurança, o relógio não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso em \"Configurações\"."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Para sua segurança, o smartphone não tem permissão para instalar apps desconhecidos dessa fonte. Você pode mudar isso nas configurações."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Seu smartphone e seus dados pessoais estão mais vulneráveis a ataques de apps desconhecidos. Ao instalar esse app, você concorda que é responsável por qualquer perda de dados ou dano ao dispositivo causados pelo uso desses apps."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Seu tablet e seus dados pessoais estão mais vulneráveis a ataques de apps desconhecidos. Ao instalar esse app, você concorda que é responsável por qualquer perda de dados ou dano ao dispositivo causados pelo uso desses apps."</string>
diff --git a/packages/PackageInstaller/res/values-ro/strings.xml b/packages/PackageInstaller/res/values-ro/strings.xml
index 9ca4543..df99a09 100644
--- a/packages/PackageInstaller/res/values-ro/strings.xml
+++ b/packages/PackageInstaller/res/values-ro/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Necunoscut"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Din motive de securitate, tableta nu are permisiunea să instaleze aplicații necunoscute din această sursă. Poți modifica această opțiune în setări."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Din motive de securitate, televizorul nu are permisiunea să instaleze aplicații necunoscute din această sursă. Poți modifica această opțiune în setări."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Din motive de securitate, ceasul tău nu are permisiunea să instaleze aplicații necunoscute din această sursă. Poți stabili altfel din Setări."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Din motive de securitate, telefonul nu are permisiunea să instaleze aplicații necunoscute din această sursă. Poți modifica această opțiune în setări."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonul și datele tale personale sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalezi această aplicație, accepți că ești singura persoană responsabilă pentru deteriorarea telefonului sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tableta și datele tale personale sunt mai vulnerabile la un atac din partea aplicațiilor necunoscute. Dacă instalezi aplicația, accepți că ești singura persoană responsabilă pentru deteriorarea tabletei sau pentru pierderea datelor, care pot avea loc în urma folosirii acesteia."</string>
diff --git a/packages/PackageInstaller/res/values-ru/strings.xml b/packages/PackageInstaller/res/values-ru/strings.xml
index e0cc4ab..8573752 100644
--- a/packages/PackageInstaller/res/values-ru/strings.xml
+++ b/packages/PackageInstaller/res/values-ru/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Неизвестное приложение"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"В целях безопасности ваш планшет блокирует установку неизвестных приложений из этого источника. Этот параметр можно изменить в настройках."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"В целях безопасности ваш телевизор блокирует установку неизвестных приложений из этого источника. Этот параметр можно изменить в настройках."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"В целях безопасности ваши часы блокируют установку неизвестных приложений из этого источника. Изменить параметры можно в настройках."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"В целях безопасности ваш телефон блокирует установку неизвестных приложений из этого источника. Этот параметр можно изменить в настройках."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Ваши персональные данные и данные телефона более уязвимы для атак приложений из неизвестных источников. Устанавливая это приложение, вы берете на себя всю ответственность за последствия, связанные с его использованием, то есть за любой ущерб, нанесенный телефону, и возможную потерю данных."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Ваши персональные данные и данные планшета более уязвимы для атак приложений из неизвестных источников. Устанавливая это приложение, вы берете на себя всю ответственность за последствия, связанные с его использованием, то есть за любой ущерб, нанесенный планшету, и возможную потерю данных."</string>
diff --git a/packages/PackageInstaller/res/values-si/strings.xml b/packages/PackageInstaller/res/values-si/strings.xml
index fb2344e..5b56298 100644
--- a/packages/PackageInstaller/res/values-si/strings.xml
+++ b/packages/PackageInstaller/res/values-si/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"නොදනී"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"ඔබගේ ආරක්ෂාව සඳහා, ඔබගේ ටැබ්ලටයට දැනට මෙම මූලාශ්‍රයෙන් නොදන්නා යෙදුම් ස්ථාපනය කිරීමට ඉඩ නොදේ. ඔබට සැකසීම් තුළ මෙය වෙනස් කළ හැකිය."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"ඔබගේ ආරක්ෂාව සඳහා, ඔබගේ TV හට දැනට මෙම මූලාශ්‍රයෙන් නොදන්නා යෙදුම් ස්ථාපනය කිරීමට ඉඩ නොදේ. ඔබට සැකසීම් තුළ මෙය වෙනස් කළ හැකිය."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ඔබේ ආරක්ෂාව සඳහා, ඔබේ ඔරලෝසුවට දැනට මෙම මූලාශ්‍රයෙන් නොදන්නා යෙදුම් ස්ථාපනය කිරීමට ඉඩ නොදෙයි. ඔබට සැකසීම් තුළ මෙය වෙනස් කළ හැක."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"ඔබගේ ආරක්ෂාව සඳහා, ඔබගේ දුරකථනයට දැනට මෙම මූලාශ්‍රයෙන් නොදන්නා යෙදුම් ස්ථාපනය කිරීමට ඉඩ නොදේ. ඔබට සැකසීම් තුළ මෙය වෙනස් කළ හැකිය."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"ඔබගේ දුරකථනය සහ පුද්ගලික දත්තවලට නොදන්නා යෙදුම් මඟින් තර්ජන එල්ල කිරීමේ හැකියාව වැඩිය. මෙම යෙදුම් ස්ථාපනය කිරීමෙන් සහ භාවිත කිරීමෙන් ඔබ ඔබේ දුරකථනය සඳහා සිදු වන යම් හානි හෝ එය භාවිත කිරීමේ ප්‍රතිඵලයක් ලෙස සිදු වන දත්ත හානි සඳහා ඔබ වගකිව යුතු බවට එකඟ වේ."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"ඔබගේ ටැබ්ලට් පරිගණකය සහ පුද්ගලික දත්තවලට නොදන්නා යෙදුම් මඟින් තර්ජන එල්ල කිරීමේ හැකියාව වැඩිය. මෙම යෙදුම් ස්ථාපනය කිරීමෙන් සහ භාවිත කිරීමෙන් ඔබ ඔබේ ටැබ්ලට් පරිගණකය සඳහා සිදු වන යම් හානි හෝ එය භාවිත කිරීමේ ප්‍රතිඵලයක් ලෙස සිදු වන දත්ත හානි සඳහා ඔබ වගකිව යුතු බවට එකඟ වේ."</string>
diff --git a/packages/PackageInstaller/res/values-sk/strings.xml b/packages/PackageInstaller/res/values-sk/strings.xml
index c2b23d6..f9acae8 100644
--- a/packages/PackageInstaller/res/values-sk/strings.xml
+++ b/packages/PackageInstaller/res/values-sk/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Neznáma"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Váš tablet momentálne nemôže z bezpečnostných dôvodov inštalovať neznáme aplikácie z tohto zdroja. Môžete to zmeniť v Nastaveniach."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Váš televízor momentálne nemôže z bezpečnostných dôvodov inštalovať neznáme aplikácie z tohto zdroja. Môžete to zmeniť v Nastaveniach."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Z bezpečnostných dôvodov momentálne nemôžete v hodnikách inštalovať neznáme aplikácie z tohto zdroja. Môžete to zmeniť v Nastaveniach."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Váš telefón momentálne nemôže z bezpečnostných dôvodov inštalovať neznáme aplikácie z tohto zdroja. Môžete to zmeniť v nastaveniach."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Váš telefón a osobné údaje sú náchylnejšie na útok z neznámych aplikácií. Inštaláciou tejto aplikácie vyjadrujete súhlas s tým, že nesiete zodpovednosť za akékoľvek poškodenie telefónu alebo stratu údajov, ktoré by mohli nastať pri jej používaní."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Váš tablet a osobné dáta sú náchylnejšie na útok z neznámych aplikácií. Inštaláciou tejto aplikácie vyjadrujete súhlas s tým, že nesiete zodpovednosť za akékoľvek poškodenie tabletu alebo stratu dát, ktoré by mohli nastať pri jej používaní."</string>
diff --git a/packages/PackageInstaller/res/values-sl/strings.xml b/packages/PackageInstaller/res/values-sl/strings.xml
index 926598a..9bf794d 100644
--- a/packages/PackageInstaller/res/values-sl/strings.xml
+++ b/packages/PackageInstaller/res/values-sl/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Neznano"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Tablični računalnik zaradi varnosti trenutno nima dovoljenja za nameščanje neznanih aplikacij iz tega vira. To lahko spremenite v nastavitvah."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Televizor zaradi varnosti trenutno nima dovoljenja za nameščanje neznanih aplikacij iz tega vira. To lahko spremenite v nastavitvah."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Ura zaradi varnosti trenutno nima dovoljenja za nameščanje neznanih aplikacij iz tega vira. To lahko spremenite v nastavitvah."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Telefon zaradi varnosti trenutno nima dovoljenja za nameščanje neznanih aplikacij iz tega vira. To lahko spremenite v nastavitvah."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Neznane aplikacije lahko resno ogrozijo varnost telefona in osebnih podatkov. Z namestitvijo te aplikacije se strinjate, da ste sami odgovorni za morebitno škodo, nastalo v telefonu, ali izgubo podatkov, do katerih lahko pride zaradi uporabe te aplikacije."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Neznane aplikacije lahko resno ogrozijo varnost tabličnega računalnika in osebnih podatkov. Z namestitvijo te aplikacije se strinjate, da ste sami odgovorni za morebitno škodo, nastalo v tabličnem računalniku, ali izgubo podatkov, do katerih lahko pride zaradi uporabe te aplikacije."</string>
diff --git a/packages/PackageInstaller/res/values-sq/strings.xml b/packages/PackageInstaller/res/values-sq/strings.xml
index b8ada36..80c53f9 100644
--- a/packages/PackageInstaller/res/values-sq/strings.xml
+++ b/packages/PackageInstaller/res/values-sq/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"I panjohur"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Për sigurinë tënde, tableti yt nuk lejohet aktualisht të instalojë aplikacione të panjohura nga ky burim. Këtë mund ta ndryshosh te \"Cilësimet\"."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Për sigurinë tënde, televizori yt nuk lejohet aktualisht të instalojë aplikacione të panjohura nga ky burim. Këtë mund ta ndryshosh te \"Cilësimet\"."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Për sigurinë tënde, ora jote nuk lejohet aktualisht të instalojë aplikacione të panjohura nga ky burim. Këtë mund ta ndryshosh te \"Cilësimet\"."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Për sigurinë tënde, telefoni yt nuk lejohet aktualisht të instalojë aplikacione të panjohura nga ky burim. Këtë mund ta ndryshosh te \"Cilësimet\"."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefoni dhe të dhënat e tua personale janë më të cenueshme nga sulmet nga aplikacione të panjohura. Duke instaluar këtë aplikacion, ti pranon se je përgjegjës për çdo dëm ndaj telefonit tënd ose çdo humbje të të dhënave që mund të rezultojë nga përdorimi i tij."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tableti dhe të dhënat e tua personale janë më të cenueshme nga sulmet nga aplikacione të panjohura. Duke instaluar këtë aplikacion, ti pranon se je përgjegjës për çdo dëm ndaj tabletit tënd ose çdo humbje të të dhënave që mund të rezultojë nga përdorimi i tij."</string>
diff --git a/packages/PackageInstaller/res/values-sr/strings.xml b/packages/PackageInstaller/res/values-sr/strings.xml
index d964d3f..eab43ce 100644
--- a/packages/PackageInstaller/res/values-sr/strings.xml
+++ b/packages/PackageInstaller/res/values-sr/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Непознато"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Из безбедносних разлога таблету тренутно није дозвољено да инсталира непознате апликације из овог извора. То можете да промените у подешавањима."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Из безбедносних разлога телевизору тренутно није дозвољено да инсталира непознате апликације из овог извора. То можете да промените у подешавањима."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Из безбедносних разлога сату тренутно није дозвољено да инсталира непознате апликације из овог извора. То можете да промените у Подешавањима."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Из безбедносних разлога телефону тренутно није дозвољено да инсталира непознате апликације из овог извора. То можете да промените у подешавањима."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Телефон и лични подаци су подложнији нападу непознатих апликација. Ако инсталирате ову апликацију, прихватате да сте одговорни за евентуална оштећења телефона или губитак података до којих може да дође због њеног коришћења."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Таблет и лични подаци су подложнији нападу непознатих апликација. Ако инсталирате ову апликацију, прихватате да сте одговорни за евентуална оштећења таблета или губитак података до којих може да дође због њеног коришћења."</string>
diff --git a/packages/PackageInstaller/res/values-sv/strings.xml b/packages/PackageInstaller/res/values-sv/strings.xml
index 231e3e1..6378f4b 100644
--- a/packages/PackageInstaller/res/values-sv/strings.xml
+++ b/packages/PackageInstaller/res/values-sv/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Okänd"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Av säkerhetsskäl får okända appar från den här källan inte installeras på surfplattan. Du kan ändra det här i inställningarna."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Av säkerhetsskäl får okända appar från den här källan inte installeras på tv:n. Du kan ändra det här i inställningarna."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Av säkerhetsskäl får okända appar från den här källan inte installeras på klockan. Du kan ändra det här i inställningarna."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Av säkerhetsskäl får okända appar från den här källan inte installeras på telefonen. Du kan ändra det här i inställningarna."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Din mobil och personliga data är mer sårbara för attacker från okända appar. Genom att installera denna app bekräftar du att du är ansvarig för eventuella skador på mobilen och för dataförlust som kan uppstå vid användning av denna app."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Din surfplatta och personliga data är mer sårbara för attacker från okända appar. Genom att installera denna app bekräftar du att du är ansvarig för eventuella skador på surfplattan och för dataförlust som kan uppstå vid användning av denna app."</string>
diff --git a/packages/PackageInstaller/res/values-sw/strings.xml b/packages/PackageInstaller/res/values-sw/strings.xml
index 65b785f..454259f 100644
--- a/packages/PackageInstaller/res/values-sw/strings.xml
+++ b/packages/PackageInstaller/res/values-sw/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Haijulikani"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Kwa sababu ya usalama wako, kompyuta yako kibao kwa sasa hairuhusiwi kusakinisha programu zisizojulikana kutoka chanzo hiki. Unaweza kubadilisha hili katika Mipangilio."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Kwa sababu ya usalama wako, TV yako kwa sasa hairuhusiwi kusakinisha programu zisizojulikana kutoka chanzo hiki. Unaweza kubadilisha hili katika Mipangilio."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Kwa sababu ya usalama wako, saa yako kwa sasa hairuhusiwi kusakinisha programu zisizojulikana kutoka chanzo hiki. Unaweza kubadilisha hili katika Mipangilio."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Kwa sababu ya usalama wako, simu yako kwa sasa hairuhusiwi kusakinisha programu zisizojulikana kutoka chanzo hiki. Unaweza kubadilisha hili katika Mipangilio."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Data yako ya binafsi na ya simu yako inaweza kuathiriwa na programu ambazo hazijulikani. Kwa kusakinisha programu hii, unakubali kuwajibikia uharibifu wowote kwenye simu yako au kupotea kwa data kutokana na matumizi yake."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Data yako ya binafsi na ya kompyuta yako kibao inaweza kuathiriwa na programu ambazo hazijulikani. Kwa kusakinisha programu hii, unakubali kuwajibikia uharibifu wowote kwenye kompyuta yako kibao au kupotea kwa data kutokana na matumizi yake."</string>
diff --git a/packages/PackageInstaller/res/values-ta/strings.xml b/packages/PackageInstaller/res/values-ta/strings.xml
index 62e531a..b9ae9a4 100644
--- a/packages/PackageInstaller/res/values-ta/strings.xml
+++ b/packages/PackageInstaller/res/values-ta/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"அறியப்படாதது"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"உங்கள் பாதுகாப்புக்காக, இந்த மூலத்திலிருந்து அறியப்படாத ஆப்ஸை டேப்லெட்டில் நிறுவ தற்போது அனுமதியில்லை. இதை அமைப்புகளில் மாற்றலாம்."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"உங்கள் பாதுகாப்புக்காக, இந்த மூலத்திலிருந்து அறியப்படாத ஆப்ஸை டிவியில் நிறுவ தற்போது அனுமதியில்லை. இதை அமைப்புகளில் மாற்றலாம்."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"உங்கள் பாதுகாப்புக்காக, இந்த மூலத்திலிருந்து அறியப்படாத ஆப்ஸை வாட்ச்சில் நிறுவ தற்போது அனுமதியில்லை. இதை அமைப்புகளில் மாற்றலாம்."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"உங்கள் பாதுகாப்புக்காக, இந்த மூலத்திலிருந்து அறியப்படாத ஆப்ஸை மொபைலில் நிறுவ தற்போது அனுமதியில்லை. இதை அமைப்புகளில் மாற்றலாம்."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"அறியப்படாத ஆப்ஸால் உங்கள் மொபைலும் தனிப்பட்ட தரவும் மிக எளிதாகப் பாதிப்புக்குள்ளாகலாம். இந்த ஆப்ஸை நிறுவுவதன் மூலம், இதைப் பயன்படுத்தும்போது மொபைலில் ஏதேனும் சிக்கல் ஏற்பட்டாலோ உங்களது தரவை இழந்தாலோ அதற்கு நீங்களே பொறுப்பாவீர்கள் என்பதை ஏற்கிறீர்கள்."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"அறியப்படாத ஆப்ஸால் உங்கள் டேப்லெட்டும் தனிப்பட்ட தரவும் மிக எளிதாகப் பாதிப்புக்குள்ளாகலாம். இந்த ஆப்ஸை நிறுவுவதன் மூலம், இதைப் பயன்படுத்தும்போது டேப்லெட்டில் ஏதேனும் சிக்கல் ஏற்பட்டாலோ உங்களது தரவை இழந்தாலோ அதற்கு நீங்களே பொறுப்பாவீர்கள் என்பதை ஏற்கிறீர்கள்."</string>
diff --git a/packages/PackageInstaller/res/values-te/strings.xml b/packages/PackageInstaller/res/values-te/strings.xml
index c016bfc..186b28f 100644
--- a/packages/PackageInstaller/res/values-te/strings.xml
+++ b/packages/PackageInstaller/res/values-te/strings.xml
@@ -73,8 +73,8 @@
     <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"ఈ యాప్ కొందరు వినియోగదారులకు లేదా కొన్ని ప్రొఫైళ్లకు అవసరం, ఇతరులకు అన్‌ఇన్‌స్టాల్ చేయబడింది"</string>
     <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"మీ ప్రొఫైల్ కోసం ఈ యాప్ అవసరం, అందువల్ల దీన్ని అన్ఇన్‌స్టాల్ చేయడం కుదరదు."</string>
     <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"మీ పరికర నిర్వాహకులకు ఈ యాప్ అవసరం, అందువల్ల దీన్ని అన్‌ఇన్‌స్టాల్ చేయడం కుదరదు."</string>
-    <string name="manage_device_administrators" msgid="3092696419363842816">"పరికర నిర్వాహక యాప్‌లను నిర్వహించు"</string>
-    <string name="manage_users" msgid="1243995386982560813">"వినియోగదారులను నిర్వహించు"</string>
+    <string name="manage_device_administrators" msgid="3092696419363842816">"పరికర నిర్వాహక యాప్‌లను మేనేజ్ చేయండి"</string>
+    <string name="manage_users" msgid="1243995386982560813">"వినియోగదారులను మేనేజ్ చేయండి"</string>
     <string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g>ని అన్‌ఇన్‌స్టాల్ చేయడం సాధ్యపడలేదు."</string>
     <string name="Parse_error_dlg_text" msgid="1661404001063076789">"ప్యాకేజీని అన్వయించడంలో సమస్య ఏర్పడింది."</string>
     <string name="wear_not_allowed_dlg_title" msgid="8664785993465117517">"Android Wear"</string>
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"తెలియదు"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"మీ సెక్యూరిటీ దృష్ట్యా, ఈ సోర్సు నుండి తెలియని యాప్‌లను ఇన్‌స్టాల్ చేయడానికి మీ టాబ్లెట్ ప్రస్తుతం అనుమతించబడదు. మీరు దీన్ని సెట్టింగ్‌లలో మార్చవచ్చు."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"మీ సెక్యూరిటీ దృష్ట్యా, ఈ సోర్సు నుండి తెలియని యాప్‌లను ఇన్‌స్టాల్ చేయడానికి మీ టీవీ ప్రస్తుతం అనుమతించబడదు. మీరు దీన్ని సెట్టింగ్‌లలో మార్చవచ్చు."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"మీ సెక్యూరిటీ దృష్ట్యా, ఈ సోర్సు నుండి తెలియని యాప్‌లను ఇన్‌స్టాల్ చేయడానికి మీ వాచ్ ప్రస్తుతం అనుమతించబడదు. మీరు దీన్ని సెట్టింగ్‌లలో మార్చవచ్చు."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"మీ సెక్యూరిటీ దృష్ట్యా, ఈ సోర్సు నుండి తెలియని యాప్‌లను ఇన్‌స్టాల్ చేయడానికి మీ ఫోన్ ప్రస్తుతం అనుమతించబడదు. మీరు దీన్ని సెట్టింగ్‌లలో మార్చవచ్చు."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"తెలియని యాప్‌లు మీ ఫోన్ పైన, వ్యక్తిగత డేటా పైన దాడి చేయడానికి ఎక్కువగా అవకాశం ఉంటుంది. ఈ యాప్‌ను ఇన్‌స్టాల్ చేయడం ద్వారా, దాని వినియోగంతో మీ ఫోన్‌కు ఏదైనా నష్టం జరిగితే లేదా మీ డేటాను కోల్పోతే అందుకు మీరే బాధ్యత వహిస్తారని అంగీకరిస్తున్నారు."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"మీ టాబ్లెట్ మరియు వ్యక్తిగత డేటాపై తెలియని యాప్‌లు దాడి చేయడానికి ఎక్కువ అవకాశం ఉంది. మీరు ఈ యాప్‌ను ఇన్‌స్టాల్ చేయడం ద్వారా, దీనిని ఉపయోగించడం వలన మీ టాబ్లెట్‌కు ఏదైనా హాని జరిగినా లేదా డేటా కోల్పోయినా బాధ్యత మీదేనని అంగీకరిస్తున్నారు."</string>
diff --git a/packages/PackageInstaller/res/values-th/strings.xml b/packages/PackageInstaller/res/values-th/strings.xml
index 1014152a..661c30f 100644
--- a/packages/PackageInstaller/res/values-th/strings.xml
+++ b/packages/PackageInstaller/res/values-th/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"ไม่ทราบ"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"เพื่อความปลอดภัย ปัจจุบันไม่อนุญาตให้ติดตั้งแอปที่ไม่รู้จักจากแหล่งที่มานี้ในแท็บเล็ต คุณเปลี่ยนแปลงได้ในการตั้งค่า"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"เพื่อความปลอดภัย ปัจจุบันไม่อนุญาตให้ติดตั้งแอปที่ไม่รู้จักจากแหล่งที่มานี้ในทีวี คุณเปลี่ยนแปลงได้ในการตั้งค่า"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"ปัจจุบันนาฬิกาไม่อนุญาตให้ติดตั้งแอปที่ไม่รู้จักจากแหล่งที่มานี้เพื่อความปลอดภัยของคุณ คุณเปลี่ยนค่านี้ได้ในการตั้งค่า"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"เพื่อความปลอดภัย ปัจจุบันไม่อนุญาตให้ติดตั้งแอปที่ไม่รู้จักจากแหล่งที่มานี้ในโทรศัพท์ ซึ่งคุณเปลี่ยนเป็นอนุญาตได้ในการตั้งค่า"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"โทรศัพท์และข้อมูลส่วนตัวของคุณมีความเสี่ยงมากขึ้นที่จะถูกโจมตีจากแอปที่ไม่รู้จัก การติดตั้งแอปนี้แสดงว่าคุณยอมรับว่าจะรับผิดชอบต่อความเสียหายใดๆ ที่จะเกิดขึ้นกับโทรศัพท์หรือการสูญเสียข้อมูลที่อาจเกิดจากการใช้งานแอปดังกล่าว"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"แท็บเล็ตและข้อมูลส่วนตัวของคุณมีความเสี่ยงมากขึ้นที่จะถูกโจมตีจากแอปที่ไม่รู้จัก การติดตั้งแอปนี้แสดงว่าคุณยอมรับว่าจะรับผิดชอบต่อความเสียหายใดๆ ที่จะเกิดขึ้นกับแท็บเล็ตหรือการสูญเสียข้อมูลที่อาจเกิดจากการใช้งานแอปดังกล่าว"</string>
diff --git a/packages/PackageInstaller/res/values-tl/strings.xml b/packages/PackageInstaller/res/values-tl/strings.xml
index 5606eb5..d298fe3 100644
--- a/packages/PackageInstaller/res/values-tl/strings.xml
+++ b/packages/PackageInstaller/res/values-tl/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Hindi Kilala"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Para sa iyong seguridad, kasalukuyang hindi pinapayagan ang tablet mo na mag-install ng mga hindi kilalang app mula sa source na ito. Mababago mo ito sa Mga Setting."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Para sa iyong seguridad, kasalukuyang hindi pinapayagan ang TV mo na mag-install ng mga hindi kilalang app mula sa source na ito. Mababago mo ito sa Mga Setting."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Para sa iyong seguridad, kasalukuyang hindi pinapayagan ang relo mo na mag-install ng mga hindi kilalang app mula sa source na ito. Mababago mo ito sa Mga Setting."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Para sa iyong seguridad, kasalukuyang hindi pinapayagan ang telepono mo na mag-install ng mga hindi kilalang app mula sa source na ito. Mababago mo ito sa Mga Setting."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Mas nanganganib ang iyong telepono at personal na data sa mga pag-atake mula sa mga hindi kilalang app. Sa pamamagitan ng pag-install ng app na ito, sumasang-ayon kang ikaw ang responsable sa anumang pinsala sa iyong telepono o pagkawala ng data na maaaring magresulta mula sa paggamit nito."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Mas nanganganib ang iyong tablet at personal na data sa mga pag-atake mula sa mga hindi kilalang app. Sa pamamagitan ng pag-install ng app na ito, sumasang-ayon kang ikaw ang responsable sa anumang pinsala sa iyong tablet o pagkawala ng data na maaaring magresulta mula sa paggamit nito."</string>
diff --git a/packages/PackageInstaller/res/values-tr/strings.xml b/packages/PackageInstaller/res/values-tr/strings.xml
index 673511c..4abdfdf 100644
--- a/packages/PackageInstaller/res/values-tr/strings.xml
+++ b/packages/PackageInstaller/res/values-tr/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Bilinmiyor"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Güvenlik nedeniyle şu anda tabletinizin bu kaynaktan bilinmeyen uygulamalar yüklemesine izin verilmemektedir. Bunu Ayarlar\'da değiştirebilirsiniz."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Güvenlik nedeniyle şu anda TV\'nizin bu kaynaktan bilinmeyen uygulamalar yüklemesine izin verilmemektedir. Bunu Ayarlar\'da değiştirebilirsiniz."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Güvenlik nedeniyle şu anda tabletinizin bu kaynaktan bilinmeyen uygulamalar yüklemesine izin verilmemektedir. Bunu Ayarlar\'dan değiştirebilirsiniz."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Güvenlik nedeniyle şu anda telefonunuzun bu kaynaktan bilinmeyen uygulamalar yüklemesine izin verilmemektedir. Bunu Ayarlar\'da değiştirebilirsiniz."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonunuz ve kişisel verileriniz, bilinmeyen uygulamaların saldırılarına karşı daha savunmasızdır. Bu uygulamayı yükleyerek, uygulama kullanımından dolayı telefonunuzda oluşabilecek hasarın veya uğrayabileceğiniz veri kaybının sorumluluğunu kabul etmiş olursunuz."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tabletiniz ve kişisel verileriniz, bilinmeyen uygulamaların saldırılarına karşı daha savunmasızdır. Bu uygulamayı yükleyerek, uygulama kullanımından dolayı tabletinizde oluşabilecek hasarın veya uğrayabileceğiniz veri kaybının sorumluluğunu kabul etmiş olursunuz."</string>
diff --git a/packages/PackageInstaller/res/values-uk/strings.xml b/packages/PackageInstaller/res/values-uk/strings.xml
index 67b3e0b..c2d138a 100644
--- a/packages/PackageInstaller/res/values-uk/strings.xml
+++ b/packages/PackageInstaller/res/values-uk/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Невідомо"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"З міркувань безпеки на вашому планшеті зараз заборонено встановлювати невідомі додатки з цього джерела. Ви можете змінити це в налаштуваннях."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"З міркувань безпеки на вашому телевізорі зараз заборонено встановлювати невідомі додатки з цього джерела. Ви можете змінити це в налаштуваннях."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"З міркувань безпеки на вашому годиннику зараз заборонено встановлювати невідомі додатки з цього джерела. Ви можете змінити це в налаштуваннях."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"З міркувань безпеки на вашому телефоні зараз заборонено встановлювати невідомі додатки з цього джерела. Ви можете змінити це в налаштуваннях."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Ваш телефон і особисті дані більш уразливі до атак невідомих додатків. Установлюючи цей додаток, ви берете на себе відповідальність за пошкодження телефона чи втрату даних унаслідок використання додатка."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Ваш планшет і особисті дані більш уразливі до атак невідомих додатків. Установлюючи цей додаток, ви берете на себе відповідальність за пошкодження планшета чи втрату даних унаслідок використання додатка."</string>
diff --git a/packages/PackageInstaller/res/values-ur/strings.xml b/packages/PackageInstaller/res/values-ur/strings.xml
index 57fc568..1e256a8 100644
--- a/packages/PackageInstaller/res/values-ur/strings.xml
+++ b/packages/PackageInstaller/res/values-ur/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"نامعلوم"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"آپ کی سیکیورٹی کی خاطر فی الحال آپ کے ٹیبلیٹ کو اس ماخذ سے نامعلوم ایپس انسٹال کرنے کی اجازت نہیں ہے۔ آپ ترتیبات میں اسے تبدیل کر سکتے ہیں۔"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"‏آپ کی سیکیورٹی کی خاطر فی الحال آپ کے TV کو اس ماخذ سے نامعلوم ایپس انسٹال کرنے کی اجازت نہیں ہے۔ آپ ترتیبات میں اسے تبدیل کر سکتے ہیں۔"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"آپ کی سیکیورٹی کی خاطر، فی الحال آپ کی گھڑی کو اس ماخذ سے نامعلوم ایپس کو انسٹال کرنے کی اجازت نہیں ہے۔ آپ ترتیبات میں اسے تبدیل کر سکتے ہیں۔"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"آپ کی سیکیورٹی کی خاطر فی الحال آپ کے فون کو اس ماخذ سے نامعلوم ایپس انسٹال کرنے کی اجازت نہیں ہے۔ آپ ترتیبات میں اسے تبدیل کر سکتے ہیں۔"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"آپ کے فون اور ذاتی ڈیٹا کو نامعلوم ایپس کی جانب سے حملے کا زیادہ خطرہ ہے۔ اس ایپ کو انسٹال کر کے، آپ اس بات سے اتفاق کرتے ہیں کہ آپ اس سے اپنے فون کو ہونے والے کسی بھی نقصان یا ڈیٹا کے نقصان کے لئے خود ذمہ دار ہیں۔"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"آپ کے ٹیبلیٹ اور ذاتی ڈیٹا کو نامعلوم ایپس کی جانب سے حملے کا زیادہ خطرہ ہے۔ اس ایپ کو انسٹال کر کے، آپ اس بات سے اتفاق کرتے ہیں کہ آپ اس سے اپنے ٹیبلیٹ کو ہونے والے کسی بھی نقصان یا ڈیٹا کے نقصان کے لئے خود ذمہ دار ہیں۔"</string>
diff --git a/packages/PackageInstaller/res/values-uz/strings.xml b/packages/PackageInstaller/res/values-uz/strings.xml
index 275ac47..4f6349c 100644
--- a/packages/PackageInstaller/res/values-uz/strings.xml
+++ b/packages/PackageInstaller/res/values-uz/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Noaniq"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Xavfsizlik yuzasidan, planshetingizga hozirda bu manbadan notanish ilovalarni o‘rnatishga ruxsat berilmagan. Buni sozlamalardan oʻzgartirish mumkin."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Xavfsizlik yuzasidan, televizoringizga hozirda bu manbadan notanish ilovalarni o‘rnatishga ruxsat berilmagan. Buni sozlamalardan oʻzgartirish mumkin."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Xavfsizlik yuzasidan, soatingizga hozirda bu manbadan notanish ilovalarni oʻrnatishga ruxsat berilmagan. Buni sozlamalardan oʻzgartirish mumkin."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Xavfsizlik yuzasidan, telefoningizga hozir ushbu manbadan notanish ilovalarni o‘rnatishga ruxsat berilmagan. Buni Sozlamalarda oʻzgartirishingiz mumkin."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefoningiz va shaxsiy axborotlaringiz notanish ilovalar hujumiga zaif bo‘ladi. Bu ilovani o‘rnatish bilan telefoningizga yetkaziladigan shikast va axborotlaringizni o‘chirib yuborilishiga javobgarlikni o‘z zimmangizga olasiz."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Planshetingiz va shaxsiy axborotlaringiz notanish ilovalar hujumiga zaif bo‘ladi. Bu ilovani o‘rnatish bilan planshetingizga yetkaziladigan shikast va axborotlaringizni o‘chirib yuborilishiga javobgarlikni o‘z zimmangizga olasiz."</string>
diff --git a/packages/PackageInstaller/res/values-vi/strings.xml b/packages/PackageInstaller/res/values-vi/strings.xml
index eacbe1d..62bca19 100644
--- a/packages/PackageInstaller/res/values-vi/strings.xml
+++ b/packages/PackageInstaller/res/values-vi/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Không xác định"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Để bảo mật, máy tính bảng của bạn hiện không được phép cài đặt các ứng dụng không xác định từ nguồn này. Bạn có thể thay đổi chế độ này trong phần Cài đặt."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Để bảo mật, TV của bạn hiện không được phép cài đặt các ứng dụng không xác định từ nguồn này. Bạn có thể thay đổi chế độ này trong phần Cài đặt."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Để bảo mật cho bạn, đồng hồ của bạn hiện không được phép cài đặt những ứng dụng không xác định qua nguồn này. Bạn có thể thay đổi chế độ này trong phần Cài đặt."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Để bảo mật, điện thoại của bạn hiện không được phép cài đặt các ứng dụng không xác định từ nguồn này. Bạn có thể thay đổi chế độ này trong phần Cài đặt."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Điện thoại và dữ liệu cá nhân của bạn dễ bị các ứng dụng không xác định tấn công hơn. Bằng cách cài đặt ứng dụng này, bạn đồng ý tự chịu trách nhiệm cho mọi hỏng hóc đối với điện thoại của mình hoặc mất mát dữ liệu có thể phát sinh do sử dụng ứng dụng này."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Máy tính bảng và dữ liệu cá nhân của bạn dễ bị các ứng dụng không xác định tấn công hơn. Bằng cách cài đặt ứng dụng này, bạn đồng ý tự chịu trách nhiệm cho mọi hỏng hóc đối với máy tính bảng của mình hoặc mất mát dữ liệu có thể phát sinh do sử dụng ứng dụng này."</string>
diff --git a/packages/PackageInstaller/res/values-zh-rCN/strings.xml b/packages/PackageInstaller/res/values-zh-rCN/strings.xml
index cbebb21..9d9824e 100644
--- a/packages/PackageInstaller/res/values-zh-rCN/strings.xml
+++ b/packages/PackageInstaller/res/values-zh-rCN/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"未知"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"出于安全考虑,目前已禁止您的平板电脑安装来自此来源的未知应用。您可以在“设置”中对此进行更改。"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"出于安全考虑,目前已禁止您的电视安装来自此来源的未知应用。您可以在“设置”中对此进行更改。"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"出于安全考虑,目前已禁止您的手表安装来自此来源的未知应用。您可以在“设置”中对此进行更改。"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"出于安全考虑,您的手机目前不允许安装来自此来源的未知应用。您可以在“设置”中对此进行更改。"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"来历不明的应用很可能会损害您的手机和个人数据。安装该应用即表示,您同意对于因使用该应用可能导致的任何手机损坏或数据丢失情况,您负有全部责任。"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"来历不明的应用很可能会损害您的平板电脑和个人数据。安装该应用即表示,您同意对于因使用该应用可能导致的任何平板电脑损坏或数据丢失情况,您负有全部责任。"</string>
diff --git a/packages/PackageInstaller/res/values-zh-rHK/strings.xml b/packages/PackageInstaller/res/values-zh-rHK/strings.xml
index dce8cfe..dcad49c 100644
--- a/packages/PackageInstaller/res/values-zh-rHK/strings.xml
+++ b/packages/PackageInstaller/res/values-zh-rHK/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"不明"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"為安全起見,您的平板電腦目前不得安裝此來源的不明應用程式。您可以在「設定」中變更這項設定。"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"為安全起見,您的電視目前不得安裝此來源的不明應用程式。您可以在「設定」中變更這項設定。"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"為安全起見,您的手錶目前不得安裝此來源的不明應用程式。您可以在「設定」中變更這項設定。"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"為安全起見,您的手機目前不得安裝此來源的不明應用程式。您可以在「設定」中變更這項設定。"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"來源不明的應用程式可能會侵害您的手機和個人資料。安裝此應用程式,即表示您同意承擔因使用這個應用程式而導致手機損壞或資料遺失的責任。"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"來源不明的應用程式可能會侵害您的平板電腦和個人資料。安裝此應用程式,即表示您同意承擔因使用這個應用程式而導致平板電腦損壞或資料遺失的責任。"</string>
diff --git a/packages/PackageInstaller/res/values-zh-rTW/strings.xml b/packages/PackageInstaller/res/values-zh-rTW/strings.xml
index 67c684b..f53791b 100644
--- a/packages/PackageInstaller/res/values-zh-rTW/strings.xml
+++ b/packages/PackageInstaller/res/values-zh-rTW/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"不明"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"為了安全起見,你的平板電腦目前禁止安裝這個來源提供的不明應用程式。如要進行變更,請前往「設定」。"</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"為了安全起見,你的電視目前禁止安裝這個來源提供的不明應用程式。如要進行變更,請前往「設定」。"</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"為了安全起見,你的智慧手錶目前禁止安裝這個來源的不明應用程式。如要變更,請前往「設定」。"</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"為了安全起見,你的手機目前無法安裝此來源提供的不明應用程式。如要進行變更,請前往「設定」。"</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"來歷不明的應用程式可能會損害你的手機和個人資料。如因安裝及使用這個應用程式,導致你的手機受損或資料遺失,請自行負責。"</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"來歷不明的應用程式可能會損害你的平板電腦和個人資料。如因安裝及使用這個應用程式,導致你的平板電腦受損或資料遺失,請自行負責。"</string>
diff --git a/packages/PackageInstaller/res/values-zu/strings.xml b/packages/PackageInstaller/res/values-zu/strings.xml
index ef88552..5aa13eb 100644
--- a/packages/PackageInstaller/res/values-zu/strings.xml
+++ b/packages/PackageInstaller/res/values-zu/strings.xml
@@ -83,6 +83,7 @@
     <string name="app_name_unknown" msgid="6881210203354323926">"Akwaziwa"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Mayelana nokuphepha kwakho, ithebulethi yakho okwamanje ayivumelekanga ukufaka ama-app angaziwa avela kulo mthombo. Ungashintsha lokhu kumaSethingi."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Mayelana nokuphepha kwakho, i-TV yakho okwamanje ayivumelekanga ukufaka ama-app angaziwa avela kulo mthombo. Ungashintsha lokhu kumaSethingi."</string>
+    <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Ngenxa yokuvikeleka kwakho, ithebulethi yakho okwamanje ayivumelekanga ukufaka ama-app angaziwa avela kulo mthombo. Ungashintsha lokhu kumaSethingi."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Mayelana nokuvikeleka kwakho, ifoni yakho okwamanje ayivunyelwe ukufaka ama-app angaziwa avela kulo mthombo. Ungashintsha lokhu kumaSethingi."</string>
     <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Idatha yakho yefoni neyohlelo lwakho lokusebenza isengcupheni kakhulu ekuhlaselweni izinhlelo zokusebenza ezingaziwa. Ngokufaka lolu hlelo lokusebenza, uyavuma ukuthi unesibopho sanoma ikuphi ukonakala kufoni yakho noma ukulahlekelwa kwedatha okungabangelwa ukusetshenziswa kwayo."</string>
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Ithebulethi yakho nedatha yomuntu siqu zisengcupheni kakhulu ekuhlaselweni izinhlelo zokusebenza ezingaziwa. Ngokufaka lolu hlelo lokusebenza, uyavuma ukuthi unesibopho sanoma ikuphi ukonakala kuthebulethi yakho noma ukulahleka kwedatha okungabangelwa ukusetshenziswa kwayo."</string>
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index 688d116..6a3b239 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -189,6 +189,9 @@
     <!-- Text to show in warning dialog on the tv when the app source is not trusted [CHAR LIMIT=NONE] -->
     <string name="untrusted_external_source_warning" product="tv">For your security, your TV currently isn’t allowed to install unknown apps from this source. You can change this in Settings.</string>
 
+    <!-- Text to show in warning dialog on the wear when the app source is not trusted [CHAR LIMIT=NONE] -->
+    <string name="untrusted_external_source_warning" product="watch">For your security, your watch currently isn’t allowed to install unknown apps from this source. You can change this in Settings.</string>
+
     <!-- Text to show in warning dialog on the phone when the app source is not trusted [CHAR LIMIT=NONE] -->
     <string name="untrusted_external_source_warning" product="default">For your security, your phone currently isn’t allowed to install unknown apps from this source. You can change this in Settings.</string>
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index bfab9be..88c1036 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -35,6 +35,8 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.EventLog;
 import android.util.Log;
 
 import java.util.Arrays;
@@ -96,6 +98,22 @@
                 mAbortInstall = true;
             }
         }
+
+        final String installerPackageNameFromIntent = getIntent().getStringExtra(
+                Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+        if (installerPackageNameFromIntent != null) {
+            final String callingPkgName = getLaunchedFromPackage();
+            if (!TextUtils.equals(installerPackageNameFromIntent, callingPkgName)
+                    && mPackageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES,
+                    callingPkgName) != PackageManager.PERMISSION_GRANTED) {
+                Log.e(LOG_TAG, "The given installer package name " + installerPackageNameFromIntent
+                        + " is invalid. Remove it.");
+                EventLog.writeEvent(0x534e4554, "236687884", getLaunchedFromUid(),
+                        "Invalid EXTRA_INSTALLER_PACKAGE_NAME");
+                getIntent().removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+            }
+        }
+
         if (mAbortInstall) {
             setResult(RESULT_CANCELED);
             finish();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index de76632..fa93670 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -124,9 +124,8 @@
     private static final int DLG_INSTALL_ERROR = DLG_BASE + 4;
     private static final int DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER = DLG_BASE + 5;
     private static final int DLG_ANONYMOUS_SOURCE = DLG_BASE + 6;
-    private static final int DLG_NOT_SUPPORTED_ON_WEAR = DLG_BASE + 7;
-    private static final int DLG_EXTERNAL_SOURCE_BLOCKED = DLG_BASE + 8;
-    private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = DLG_BASE + 9;
+    private static final int DLG_EXTERNAL_SOURCE_BLOCKED = DLG_BASE + 7;
+    private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = DLG_BASE + 8;
 
     // If unknown sources are temporary allowed
     private boolean mAllowUnknownSources;
@@ -189,8 +188,6 @@
             case DLG_INSTALL_ERROR:
                 return InstallErrorDialog.newInstance(
                         mPm.getApplicationLabel(mPkgInfo.applicationInfo));
-            case DLG_NOT_SUPPORTED_ON_WEAR:
-                return NotSupportedOnWearDialog.newInstance();
             case DLG_INSTALL_APPS_RESTRICTED_FOR_USER:
                 return SimpleErrorDialog.newInstance(
                         R.string.install_apps_user_restriction_dlg_text);
@@ -379,12 +376,8 @@
             return;
         }
 
-        if (DeviceUtils.isWear(this)) {
-            showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
-            return;
-        }
-
         final boolean wasSetUp = processAppSnippet(packageSource);
+
         if (mLocalLOGV) Log.i(TAG, "wasSetUp: " + wasSetUp);
 
         if (!wasSetUp) {
@@ -779,21 +772,6 @@
     }
 
     /**
-     * An error dialog shown when the app is not supported on wear
-     */
-    public static class NotSupportedOnWearDialog extends SimpleErrorDialog {
-        static SimpleErrorDialog newInstance() {
-            return SimpleErrorDialog.newInstance(R.string.wear_not_allowed_dlg_text);
-        }
-
-        @Override
-        public void onCancel(DialogInterface dialog) {
-            getActivity().setResult(RESULT_OK);
-            getActivity().finish();
-        }
-    }
-
-    /**
      * An error dialog shown when the device is out of space
      */
     public static class OutOfSpaceDialog extends AppErrorDialog {
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
index 93e6271..3029d10 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
@@ -55,18 +55,15 @@
     private static final String PRIVET_SERVICE = "_privet._tcp";
 
     /** The required mDNS service types */
-    private static final Set<String> PRINTER_SERVICE_TYPE = new HashSet<String>() {{
-        // Not checking _printer_._sub
-        add(PRIVET_SERVICE);
-    }};
+    private static final Set<String> PRINTER_SERVICE_TYPE = Set.of(
+            PRIVET_SERVICE); // Not checking _printer_._sub
 
     /** All possible connection states */
-    private static final Set<String> POSSIBLE_CONNECTION_STATES = new HashSet<String>() {{
-        add("online");
-        add("offline");
-        add("connecting");
-        add("not-configured");
-    }};
+    private static final Set<String> POSSIBLE_CONNECTION_STATES = Set.of(
+            "online",
+            "offline",
+            "connecting",
+            "not-configured");
 
     private static final byte SUPPORTED_TXTVERS = '1';
 
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
index 34e7e3d..0c5de27 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
@@ -37,9 +37,7 @@
 public class MDNSFilterPlugin implements PrintServicePlugin {
 
     /** The mDNS service types supported */
-    private static final Set<String> PRINTER_SERVICE_TYPES = new HashSet<String>() {{
-        add("_ipp._tcp");
-    }};
+    private static final Set<String> PRINTER_SERVICE_TYPES = Set.of("_ipp._tcp");
 
     /**
      * The printer filter for {@link MDNSFilteredDiscovery} passing only mDNS results
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java
index d03bb1d..b9983c3 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java
@@ -23,7 +23,6 @@
 import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
 import com.android.printservice.recommendation.util.MDNSUtils;
 
-import java.util.HashSet;
 import java.util.Set;
 
 /**
@@ -32,10 +31,7 @@
 class PrinterFilterMopria implements MDNSFilteredDiscovery.PrinterFilter {
     private static final String TAG = "PrinterFilterMopria";
 
-    static final Set<String> MOPRIA_MDNS_SERVICES = new HashSet<String>() {{
-        add("_ipp._tcp");
-        add("_ipps._tcp");
-    }};
+    static final Set<String> MOPRIA_MDNS_SERVICES = Set.of("_ipp._tcp", "_ipps._tcp");
 
     private static final String PDL__PDF = "application/pdf";
     private static final String PDL__PCLM = "application/PCLm";
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java
index b9b9098..680dd84 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java
@@ -25,7 +25,6 @@
 import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
 import com.android.printservice.recommendation.util.MDNSUtils;
 
-import java.util.HashSet;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
@@ -36,9 +35,7 @@
 class PrinterFilterSamsung implements MDNSFilteredDiscovery.PrinterFilter {
     private static final String TAG = "PrinterFilterSamsung";
 
-    static final Set<String> SAMSUNG_MDNS_SERVICES = new HashSet<String>() {{
-        add("_pdl-datastream._tcp");
-    }};
+    static final Set<String> SAMSUNG_MDNS_SERVICES = Set.of("_pdl-datastream._tcp");
 
     private static final String[] NOT_SUPPORTED_MODELS = new String[]{
             "SCX-5x15",
@@ -57,9 +54,7 @@
     private static final String ATTR_PRODUCT = "product";
     private static final String ATTR_TY = "ty";
 
-    private static Set<String> SAMUNG_VENDOR_SET = new HashSet<String>() {{
-        add("samsung");
-    }};
+    private static final Set<String> SAMUNG_VENDOR_SET = Set.of("samsung");
 
     @Override
     public boolean matchesCriteria(NsdServiceInfo nsdServiceInfo) {
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
index ae1bdce..cbd5833 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
@@ -29,10 +29,11 @@
 import java.util.Set;
 
 public class SamsungRecommendationPlugin implements PrintServicePlugin {
-    private static final Set<String> ALL_MDNS_SERVICES = new HashSet<String>() {{
-        addAll(PrinterFilterMopria.MOPRIA_MDNS_SERVICES);
-        addAll(PrinterFilterSamsung.SAMSUNG_MDNS_SERVICES);
-    }};
+    private static final Set<String> ALL_MDNS_SERVICES = new HashSet<String>();
+    static {
+        ALL_MDNS_SERVICES.addAll(PrinterFilterMopria.MOPRIA_MDNS_SERVICES);
+        ALL_MDNS_SERVICES.addAll(PrinterFilterSamsung.SAMSUNG_MDNS_SERVICES);
+    }
 
     private final @NonNull Context mContext;
     private final @NonNull MDNSFilteredDiscovery mMDNSFilteredDiscovery;
diff --git a/packages/PrintSpooler/res/values-en-rCA/strings.xml b/packages/PrintSpooler/res/values-en-rCA/strings.xml
index 7fbfeb3..606cc3e 100644
--- a/packages/PrintSpooler/res/values-en-rCA/strings.xml
+++ b/packages/PrintSpooler/res/values-en-rCA/strings.xml
@@ -23,21 +23,21 @@
     <string name="label_copies_summary" msgid="3861966063536529540">"Copies:"</string>
     <string name="label_paper_size" msgid="908654383827777759">"Paper size"</string>
     <string name="label_paper_size_summary" msgid="5668204981332138168">"Paper size:"</string>
-    <string name="label_color" msgid="1108690305218188969">"Colour"</string>
+    <string name="label_color" msgid="1108690305218188969">"Color"</string>
     <string name="label_duplex" msgid="5370037254347072243">"Two-sided"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
     <string name="label_pages" msgid="7768589729282182230">"Pages"</string>
     <string name="destination_default_text" msgid="5422708056807065710">"Select a printer"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"All <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Range of <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
-    <string name="pages_range_example" msgid="8558694453556945172">"e.g. 1–5,8,11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"e.g. 1—5,8,11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Print preview"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Install PDF viewer for preview"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Printing app crashed"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Generating print job"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Save as PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"All printers…"</string>
-    <string name="print_dialog" msgid="32628687461331979">"Print dialogue"</string>
+    <string name="print_dialog" msgid="32628687461331979">"Print dialog"</string>
     <string name="current_page_template" msgid="5145005201131935302">"<xliff:g id="CURRENT_PAGE">%1$d</xliff:g>/<xliff:g id="PAGE_COUNT">%2$d</xliff:g>"</string>
     <string name="page_description_template" msgid="6831239682256197161">"Page <xliff:g id="CURRENT_PAGE">%1$d</xliff:g> of <xliff:g id="PAGE_COUNT">%2$d</xliff:g>"</string>
     <string name="summary_template" msgid="8899734908625669193">"Summary, copies <xliff:g id="COPIES">%1$s</xliff:g>, paper size <xliff:g id="PAPER_SIZE">%2$s</xliff:g>"</string>
@@ -91,7 +91,7 @@
     <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Your document may pass through one or more servers on its way to the printer."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Black &amp; White"</item>
-    <item msgid="2762241247228983754">"Colour"</item>
+    <item msgid="2762241247228983754">"Color"</item>
   </string-array>
   <string-array name="duplex_mode_labels">
     <item msgid="3882302912790928315">"None"</item>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index 00b3736..b0aa8f1 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -402,7 +402,7 @@
 
         @Override
         public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
-            if ((isOptionsClosed() || isOptionsClosed()) && dy <= 0) {
+            if (isOptionsClosed() && dy <= 0) {
                 return;
             }
 
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/FooterPreference/Android.bp b/packages/SettingsLib/FooterPreference/Android.bp
index 0929706..bcedf50 100644
--- a/packages/SettingsLib/FooterPreference/Android.bp
+++ b/packages/SettingsLib/FooterPreference/Android.bp
@@ -23,5 +23,6 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.permission",
+        "com.android.healthconnect",
     ],
 }
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index dd29827..372a276 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -25,5 +25,6 @@
         "//apex_available:platform",
         "com.android.adservices",
         "com.android.cellbroadcast",
+        "com.android.healthconnect",
     ],
 }
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index 8eafbdf..a53782a 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -3,10 +3,11 @@
 edgarwang@google.com
 emilychuang@google.com
 evanlaird@google.com
+hanxu@google.com
 juliacr@google.com
 leifhendrik@google.com
-tmfang@google.com
 virgild@google.com
+ykhung@google.com
 
 # Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
 per-file *.xml=*
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/ProfileSelector/AndroidManifest.xml b/packages/SettingsLib/ProfileSelector/AndroidManifest.xml
new file mode 100644
index 0000000..a57469e
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<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/ProfileSelector/res/values/strings.xml b/packages/SettingsLib/ProfileSelector/res/values/strings.xml
new file mode 100644
index 0000000..68d4047
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values/strings.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.
+  -->
+
+<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/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 4fb77d7..dea9bf4 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -16,14 +16,15 @@
 
 buildscript {
     ext {
-        spa_min_sdk = 21
-        spa_target_sdk = 33
-        jetpack_compose_version = '1.3.0'
+        BUILD_TOOLS_VERSION = "30.0.3"
+        MIN_SDK = 21
+        TARGET_SDK = 33
+        jetpack_compose_version = '1.4.0-alpha01'
         jetpack_compose_compiler_version = '1.3.2'
     }
 }
 plugins {
-    id 'com.android.application' version '7.3.0' apply false
-    id 'com.android.library' version '7.3.0' apply false
+    id 'com.android.application' version '7.3.1' apply false
+    id 'com.android.library' version '7.3.1' apply false
     id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
 }
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index f1a24af..d32d659 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -17,16 +17,17 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.settingslib.spa.gallery">
 
-    <uses-sdk android:minSdkVersion="21"/>
+    <uses-sdk android:minSdkVersion="21" />
 
     <application
         android:name=".GalleryApplication"
+        android:enableOnBackInvokedCallback="true"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_label"
-        android:supportsRtl="true"
-        android:enableOnBackInvokedCallback="true">
+        android:supportsRtl="true">
         <activity
             android:name=".GalleryMainActivity"
+            android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -35,24 +36,36 @@
         </activity>
 
         <provider
-            android:name=".GalleryEntryProvider"
-            android:authorities="com.android.spa.gallery.provider"
-            android:enabled="true"
+            android:name="com.android.settingslib.spa.search.SpaSearchProvider"
+            android:authorities="com.android.spa.gallery.search.provider"
             android:exported="false">
         </provider>
 
+        <provider android:name="com.android.settingslib.spa.slice.SpaSliceProvider"
+            android:authorities="com.android.spa.gallery.slice.provider"
+            android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.app.slice.category.SLICE" />
+            </intent-filter>
+        </provider>
+
+        <receiver
+            android:name="com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver"
+            android:exported="false">
+        </receiver>
+
         <activity
-            android:name="com.android.settingslib.spa.framework.debug.BlankActivity"
+            android:name="com.android.settingslib.spa.debug.BlankActivity"
             android:exported="true">
         </activity>
         <activity
-            android:name="com.android.settingslib.spa.framework.debug.DebugActivity"
+            android:name="com.android.settingslib.spa.debug.DebugActivity"
             android:exported="true">
         </activity>
         <provider
-            android:name="com.android.settingslib.spa.framework.debug.DebugProvider"
-            android:authorities="com.android.spa.gallery.debug"
-            android:enabled="true"
+            android:name="com.android.settingslib.spa.debug.DebugProvider"
+            android:authorities="com.android.spa.gallery.debug.provider"
             android:exported="false">
         </provider>
 
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index 20dd707..7868aff 100644
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -21,12 +21,13 @@
 
 android {
     namespace 'com.android.settingslib.spa.gallery'
-    compileSdk spa_target_sdk
+    compileSdk TARGET_SDK
+    buildToolsVersion = BUILD_TOOLS_VERSION
 
     defaultConfig {
         applicationId "com.android.settingslib.spa.gallery"
-        minSdk spa_min_sdk
-        targetSdk spa_target_sdk
+        minSdk MIN_SDK
+        targetSdk TARGET_SDK
         versionCode 1
         versionName "1.0"
     }
@@ -57,5 +58,5 @@
 }
 
 dependencies {
-    implementation(project(":spa"))
+    implementation project(":spa")
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
deleted file mode 100644
index 817c209f..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ /dev/null
@@ -1,21 +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.settingslib.spa.gallery
-
-import com.android.settingslib.spa.framework.EntryProvider
-
-class GalleryEntryProvider : EntryProvider()
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 92f4fe4..db49909 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
@@ -22,10 +22,13 @@
 import com.android.settingslib.spa.framework.common.SpaEnvironment
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
+import com.android.settingslib.spa.gallery.dialog.AlterDialogPageProvider
 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.LoadingBarPageProvider
 import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
 import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
@@ -36,6 +39,7 @@
 import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
 import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
 import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
+import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver
 
 /**
  * Enum to define all SPP name here.
@@ -69,6 +73,9 @@
                 CategoryPageProvider,
                 ActionButtonPageProvider,
                 ProgressBarPageProvider,
+                LoadingBarPageProvider,
+                ChartPageProvider,
+                AlterDialogPageProvider,
             ),
             rootPages = listOf(
                 HomePageProvider.createSettingsPage(),
@@ -76,9 +83,12 @@
         )
     }
 
-    override val browseActivityClass = GalleryMainActivity::class.java
-
-    override val entryProviderAuthorities = "com.android.spa.gallery.provider"
-
     override val logger = LocalLogger()
+
+    override val browseActivityClass = GalleryMainActivity::class.java
+    override val sliceBroadcastReceiverClass = SpaSliceBroadcastReceiver::class.java
+
+    // For debugging
+    override val searchProviderAuthorities = "com.android.spa.gallery.search.provider"
+    override val sliceProviderAuthorities = "com.android.spa.gallery.slice.provider"
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
index 96e2498..decc292 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
@@ -46,7 +46,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlterDialogPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlterDialogPage.kt
new file mode 100644
index 0000000..063b61c
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlterDialogPage.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.gallery.dialog
+
+import android.os.Bundle
+import androidx.compose.material3.Text
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.dialog.AlertDialogButton
+import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+
+private const val TITLE = "AlterDialogPage"
+
+object AlterDialogPageProvider : SettingsPageProvider {
+    override val name = "AlterDialogPage"
+    private val owner = createSettingsPage()
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> = listOf(
+        SettingsEntryBuilder.create("AlterDialog", owner).setUiLayoutFn {
+            val alertDialogPresenter = rememberAlertDialogPresenter(
+                confirmButton = AlertDialogButton("Ok"),
+                dismissButton = AlertDialogButton("Cancel"),
+                title = "Title",
+                text = { Text("Text") },
+            )
+            Preference(object : PreferenceModel {
+                override val title = "Show AlterDialog"
+                override val onClick = alertDialogPresenter::open
+            })
+        }.build(),
+    )
+
+    fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner)
+        .setUiLayoutFn {
+            Preference(object : PreferenceModel {
+                override val title = TITLE
+                override val onClick = navigator(name)
+            })
+        }
+
+    override fun getTitle(arguments: Bundle?) = TITLE
+}
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 83e3f78..5d26b34 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,7 +18,6 @@
 
 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
@@ -28,10 +27,13 @@
 import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
+import com.android.settingslib.spa.gallery.dialog.AlterDialogPageProvider
 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.LoadingBarPageProvider
 import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
 import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
@@ -57,12 +59,19 @@
             CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            LoadingBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            AlterDialogPageProvider.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))
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 60ff362..42ac1ac 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
@@ -55,7 +55,6 @@
         entryList.add(
             createEntry(owner, EntryEnum.STRING_PARAM)
                 // Set attributes
-                .setIsAllowSearch(true)
                 .setIsSearchDataDynamic(true)
                 .setSearchDataFn { ArgumentPageModel.genStringParamSearchData() }
                 .setUiLayoutFn {
@@ -67,7 +66,6 @@
         entryList.add(
             createEntry(owner, EntryEnum.INT_PARAM)
                 // Set attributes
-                .setIsAllowSearch(true)
                 .setIsSearchDataDynamic(true)
                 .setSearchDataFn { ArgumentPageModel.genIntParamSearchData() }
                 .setUiLayoutFn {
@@ -90,8 +88,6 @@
             owner = createSettingsPage(arguments),
             displayName = "${name}_$stringParam",
         )
-            // Set attributes
-            .setIsAllowSearch(false)
             .setSearchDataFn { ArgumentPageModel.genInjectSearchData() }
             .setUiLayoutFn {
                 // Set ui rendering
@@ -99,9 +95,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))
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..7f21a4d
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
@@ -0,0 +1,161 @@
+/*
+ * 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))
+            .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..9f24ea9 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
@@ -20,6 +20,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.EntrySearchData
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPage
@@ -42,7 +43,7 @@
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create( "Some Preference", owner)
-                .setIsAllowSearch(true)
+                .setSearchDataFn { EntrySearchData(title = "Some Preference") }
                 .setUiLayoutFn {
                     Preference(remember {
                         object : PreferenceModel {
@@ -58,7 +59,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -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..44f0343 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
@@ -26,12 +26,11 @@
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.gallery.R
-import com.android.settingslib.spa.widget.Illustration
-import com.android.settingslib.spa.widget.IllustrationModel
-import com.android.settingslib.spa.widget.ResourceType
+import com.android.settingslib.spa.widget.illustration.Illustration
+import com.android.settingslib.spa.widget.illustration.IllustrationModel
+import com.android.settingslib.spa.widget.illustration.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"
 
@@ -73,7 +72,6 @@
 
      fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -82,13 +80,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/LoadingBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt
new file mode 100644
index 0000000..4332a81
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+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.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.CircularLoadingBar
+import com.android.settingslib.spa.widget.ui.LinearLoadingBar
+
+private const val TITLE = "Sample LoadingBar"
+
+object LoadingBarPageProvider : SettingsPageProvider {
+    override val name = "LoadingBar"
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+            .setUiLayoutFn {
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                })
+            }
+    }
+
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        var loading by remember { mutableStateOf(true) }
+        RegularScaffold(title = getTitle(arguments)) {
+            Button(
+                onClick = { loading = !loading },
+                modifier = Modifier.padding(start = 20.dp)
+            ) {
+                if (loading) {
+                    Text(text = "Stop")
+                } else {
+                    Text(text = "Resume")
+                }
+            }
+        }
+
+        LinearLoadingBar(isLoading = loading, yOffset = 104.dp)
+        CircularLoadingBar(isLoading = loading)
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun LoadingBarPagePreview() {
+    SettingsTheme {
+        LoadingBarPageProvider.Page(null)
+    }
+}
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..20d90dd 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
@@ -27,7 +27,6 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -39,9 +38,7 @@
 import com.android.settingslib.spa.widget.preference.ProgressBarPreferenceModel
 import com.android.settingslib.spa.widget.preference.ProgressBarWithDataPreference
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
-import com.android.settingslib.spa.widget.ui.CircularLoadingBar
 import com.android.settingslib.spa.widget.ui.CircularProgressBar
-import com.android.settingslib.spa.widget.ui.LinearLoadingBar
 import kotlinx.coroutines.delay
 
 private const val TITLE = "Sample ProgressBar"
@@ -51,7 +48,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -60,20 +56,16 @@
             }
     }
 
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+
     @Composable
     override fun Page(arguments: Bundle?) {
-        // Mocks a loading time of 2 seconds.
-        var loading by remember { mutableStateOf(true) }
-        LaunchedEffect(Unit) {
-            delay(2000)
-            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) {
-                delay(2000)
                 while (progress < 1f) {
                     delay(100)
                     progress += 0.01f
@@ -82,19 +74,11 @@
                 progress = 0.4f
             }
 
-            // Show as a placeholder for progress bar
             LargeProgressBar(progress)
-            // The remaining information only shows after loading complete.
-            if (!loading) {
-                SimpleProgressBar()
-                ProgressBarWithData()
-                CircularProgressBar(progress = progress, radius = 160f)
-            }
+            SimpleProgressBar()
+            ProgressBarWithData()
+            CircularProgressBar(progress = progress, radius = 160f)
         }
-
-        // Add loading bar examples, running for 2 seconds.
-        LinearLoadingBar(isLoading = loading, yOffset = 64.dp)
-        CircularLoadingBar(isLoading = loading)
     }
 }
 
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..c0d0abc 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
@@ -40,7 +40,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -49,9 +48,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..a62ec7b 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"
 
@@ -49,7 +48,6 @@
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create("Simple Slider", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SliderPreference(object : SliderPreferenceModel {
                         override val title = "Simple Slider"
@@ -59,7 +57,6 @@
         )
         entryList.add(
             SettingsEntryBuilder.create("Slider with icon", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SliderPreference(object : SliderPreferenceModel {
                         override val title = "Slider with icon"
@@ -73,7 +70,6 @@
         )
         entryList.add(
             SettingsEntryBuilder.create("Slider with changeable icon", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     val initValue = 0
                     var icon by remember { mutableStateOf(Icons.Outlined.MusicOff) }
@@ -94,7 +90,6 @@
         )
         entryList.add(
             SettingsEntryBuilder.create("Slider with steps", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SliderPreference(object : SliderPreferenceModel {
                         override val title = "Slider with steps"
@@ -110,7 +105,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -119,13 +113,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..67e35dc 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"
 
@@ -45,14 +44,12 @@
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create( "MainSwitchPreference", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleMainSwitchPreference()
                 }.build()
         )
         entryList.add(
             SettingsEntryBuilder.create( "MainSwitchPreference not changeable", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleNotChangeableMainSwitchPreference()
                 }.build()
@@ -63,7 +60,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -72,13 +68,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..eddede7 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"
 
@@ -45,7 +43,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = owner)
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -54,12 +51,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 2c2782b..238204a 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
@@ -27,6 +27,7 @@
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.EntrySliceData
 import com.android.settingslib.spa.framework.common.EntryStatusData
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -35,8 +36,10 @@
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.createIntent
 import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
+import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
@@ -46,11 +49,15 @@
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
+import com.android.settingslib.spa.slice.createBrowsePendingIntent
+import com.android.settingslib.spa.slice.provider.createDemoActionSlice
+import com.android.settingslib.spa.slice.provider.createDemoBrowseSlice
+import com.android.settingslib.spa.slice.provider.createDemoSlice
 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
+import kotlinx.coroutines.delay
 
 private const val TAG = "PreferencePage"
 
@@ -81,16 +88,15 @@
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             createEntry(EntryEnum.SIMPLE_PREFERENCE)
-                .setIsAllowSearch(true)
                 .setMacro {
                     spaLogger.message(TAG, "create macro for ${EntryEnum.SIMPLE_PREFERENCE}")
                     SimplePreferenceMacro(title = SIMPLE_PREFERENCE_TITLE)
                 }
+                .setStatusDataFn { EntryStatusData(isDisabled = false) }
                 .build()
         )
         entryList.add(
             createEntry(EntryEnum.SUMMARY_PREFERENCE)
-                .setIsAllowSearch(true)
                 .setMacro {
                     spaLogger.message(TAG, "create macro for ${EntryEnum.SUMMARY_PREFERENCE}")
                     SimplePreferenceMacro(
@@ -99,12 +105,12 @@
                         searchKeywords = SIMPLE_PREFERENCE_KEYWORDS,
                     )
                 }
+                .setStatusDataFn { EntryStatusData(isDisabled = true) }
                 .build()
         )
         entryList.add(singleLineSummaryEntry())
         entryList.add(
             createEntry(EntryEnum.DISABLED_PREFERENCE)
-                .setIsAllowSearch(true)
                 .setHasMutableStatus(true)
                 .setMacro {
                     spaLogger.message(TAG, "create macro for ${EntryEnum.DISABLED_PREFERENCE}")
@@ -120,7 +126,6 @@
         )
         entryList.add(
             createEntry(EntryEnum.ASYNC_SUMMARY_PREFERENCE)
-                .setIsAllowSearch(true)
                 .setHasMutableStatus(true)
                 .setSearchDataFn {
                     EntrySearchData(title = ASYNC_PREFERENCE_TITLE)
@@ -128,18 +133,37 @@
                 .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
                         }
                     )
+                }
+                .setSliceDataFn { sliceUri, _ ->
+                    val createSliceImpl = { s: String ->
+                        createDemoBrowseSlice(
+                            sliceUri = sliceUri,
+                            title = ASYNC_PREFERENCE_TITLE,
+                            summary = s,
+                        )
+                    }
+                    return@setSliceDataFn object : EntrySliceData() {
+                        init {
+                            postValue(createSliceImpl("(loading)"))
+                        }
+
+                        override suspend fun asyncRunner() {
+                            spaLogger.message(TAG, "Async entry loading")
+                            delay(2000L)
+                            postValue(createSliceImpl(ASYNC_PREFERENCE_SUMMARY))
+                        }
+                    }
                 }.build()
         )
         entryList.add(
             createEntry(EntryEnum.MANUAL_UPDATE_PREFERENCE)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     val model = PreferencePageModel.create()
                     val manualUpdaterSummary = remember { model.getManualUpdaterSummary() }
@@ -153,11 +177,32 @@
                             }
                         }
                     )
+                }
+                .setSliceDataFn { sliceUri, args ->
+                    val createSliceImpl = { v: Int ->
+                        createDemoActionSlice(
+                            sliceUri = sliceUri,
+                            title = MANUAL_UPDATE_PREFERENCE_TITLE,
+                            summary = "manual update value $v",
+                        )
+                    }
+
+                    return@setSliceDataFn object : EntrySliceData() {
+                        private var tick = args?.getString("init")?.toInt() ?: 0
+
+                        init {
+                            postValue(createSliceImpl(tick))
+                        }
+
+                        override suspend fun asyncAction() {
+                            tick++
+                            postValue(createSliceImpl(tick))
+                        }
+                    }
                 }.build()
         )
         entryList.add(
             createEntry(EntryEnum.AUTO_UPDATE_PREFERENCE)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     val model = PreferencePageModel.create()
                     val autoUpdaterSummary = remember { model.getAutoUpdaterSummary() }
@@ -171,14 +216,39 @@
                         }
                     )
                 }
-                .build()
+                .setSliceDataFn { sliceUri, args ->
+                    val createSliceImpl = { v: Int ->
+                        createDemoBrowseSlice(
+                            sliceUri = sliceUri,
+                            title = AUTO_UPDATE_PREFERENCE_TITLE,
+                            summary = "auto update value $v",
+                        )
+                    }
+
+                    return@setSliceDataFn object : EntrySliceData() {
+                        private var tick = args?.getString("init")?.toInt() ?: 0
+
+                        init {
+                            postValue(createSliceImpl(tick))
+                        }
+
+                        override suspend fun asyncRunner() {
+                            spaLogger.message(TAG, "autoUpdater.active")
+                            while (true) {
+                                delay(1000L)
+                                tick++
+                                spaLogger.message(TAG, "autoUpdater.value $tick")
+                                postValue(createSliceImpl(tick))
+                            }
+                        }
+                    }
+                }.build()
         )
 
         return entryList
     }
 
     private fun singleLineSummaryEntry() = createEntry(EntryEnum.SINGLE_LINE_SUMMARY_PREFERENCE)
-        .setIsAllowSearch(true)
         .setUiLayoutFn {
             Preference(
                 model = object : PreferenceModel {
@@ -194,7 +264,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = owner)
-            .setIsAllowSearch(true)
             .setMacro {
                 spaLogger.message(TAG, "create macro for INJECT entry")
                 SimplePreferenceMacro(
@@ -202,15 +271,26 @@
                     clickRoute = SettingsPageProviderEnum.PREFERENCE.name
                 )
             }
+            .setSliceDataFn { sliceUri, _ ->
+                val intent = owner.createIntent()?.createBrowsePendingIntent()
+                    ?: return@setSliceDataFn null
+                return@setSliceDataFn object : EntrySliceData() {
+                    init {
+                        postValue(
+                            createDemoSlice(
+                                sliceUri = sliceUri,
+                                title = PAGE_TITLE,
+                                summary = "Injected Entry",
+                                intent = intent,
+                            )
+                        )
+                    }
+                }
+            }
     }
 
-    @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
     }
 }
 
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..fc6f10f 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
@@ -44,7 +44,7 @@
         const val DISABLE_PREFERENCE_TITLE = "Disabled"
         const val DISABLE_PREFERENCE_SUMMARY = "Disabled summary"
         const val ASYNC_PREFERENCE_TITLE = "Async Preference"
-        private const val ASYNC_PREFERENCE_SUMMARY = "Async summary"
+        const val ASYNC_PREFERENCE_SUMMARY = "Async summary"
         const val MANUAL_UPDATE_PREFERENCE_TITLE = "Manual Updater"
         const val AUTO_UPDATE_PREFERENCE_TITLE = "Auto Updater"
         val SIMPLE_PREFERENCE_KEYWORDS = listOf("simple keyword1", "simple keyword2")
@@ -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..067911c 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
@@ -17,6 +17,8 @@
 package com.android.settingslib.spa.gallery.preference
 
 import android.os.Bundle
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.produceState
@@ -34,7 +36,7 @@
 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 com.android.settingslib.spa.widget.ui.SettingsIcon
 import kotlinx.coroutines.delay
 
 private const val TITLE = "Sample SwitchPreference"
@@ -47,39 +49,40 @@
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create( "SwitchPreference", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleSwitchPreference()
                 }.build()
         )
         entryList.add(
             SettingsEntryBuilder.create( "SwitchPreference with summary", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleSwitchPreferenceWithSummary()
                 }.build()
         )
         entryList.add(
             SettingsEntryBuilder.create( "SwitchPreference with async summary", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleSwitchPreferenceWithAsyncSummary()
                 }.build()
         )
         entryList.add(
             SettingsEntryBuilder.create( "SwitchPreference not changeable", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleNotChangeableSwitchPreference()
                 }.build()
         )
+        entryList.add(
+            SettingsEntryBuilder.create( "SwitchPreference with icon", owner)
+                .setUiLayoutFn {
+                    SampleSwitchPreferenceWithIcon()
+                }.build()
+        )
 
         return entryList
     }
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -88,13 +91,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
     }
 }
 
@@ -154,6 +152,21 @@
     })
 }
 
+@Composable
+private fun SampleSwitchPreferenceWithIcon() {
+    val checked = rememberSaveable { mutableStateOf(true) }
+    SwitchPreference(remember {
+        object : SwitchPreferenceModel {
+            override val title = "SwitchPreference"
+            override val checked = checked
+            override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+            override val icon = @Composable {
+                SettingsIcon(imageVector = Icons.Outlined.AirplanemodeActive)
+            }
+        }
+    })
+}
+
 @Preview(showBackground = true)
 @Composable
 private fun SwitchPreferencePagePreview() {
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..33e5e8d 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"
@@ -47,28 +46,24 @@
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create( "TwoTargetSwitchPreference", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleTwoTargetSwitchPreference()
                 }.build()
         )
         entryList.add(
             SettingsEntryBuilder.create( "TwoTargetSwitchPreference with summary", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleTwoTargetSwitchPreferenceWithSummary()
                 }.build()
         )
         entryList.add(
             SettingsEntryBuilder.create( "TwoTargetSwitchPreference with async summary", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleTwoTargetSwitchPreferenceWithAsyncSummary()
                 }.build()
         )
         entryList.add(
             SettingsEntryBuilder.create( "TwoTargetSwitchPreference not changeable", owner)
-                .setIsAllowSearch(true)
                 .setUiLayoutFn {
                     SampleNotChangeableTwoTargetSwitchPreference()
                 }.build()
@@ -79,7 +74,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -88,13 +82,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..cb58abf6 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
@@ -39,7 +39,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -48,41 +47,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..ba769d2 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
@@ -40,7 +40,6 @@
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
-            .setIsAllowSearch(true)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -49,9 +48,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..1c5a1ce 100644
--- a/packages/SettingsLib/Spa/settings.gradle
+++ b/packages/SettingsLib/Spa/settings.gradle
@@ -26,9 +26,10 @@
     repositories {
         google()
         mavenCentral()
+        maven { url "https://jitpack.io"}
     }
 }
 rootProject.name = "SpaLib"
 include ':spa'
 include ':gallery'
-include ':tests'
+include ':testutils'
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 8b29366..3ea3b5c 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -24,19 +24,23 @@
     srcs: ["src/**/*.kt"],
 
     static_libs: [
+        "androidx.slice_slice-builders",
+        "androidx.slice_slice-core",
+        "androidx.slice_slice-view",
         "androidx.compose.material3_material3",
         "androidx.compose.material_material-icons-extended",
         "androidx.compose.runtime_runtime",
         "androidx.compose.runtime_runtime-livedata",
         "androidx.compose.ui_ui-tooling-preview",
         "androidx.lifecycle_lifecycle-livedata-ktx",
+        "androidx.lifecycle_lifecycle-runtime-compose",
         "androidx.navigation_navigation-compose",
         "com.google.android.material_material",
         "lottie_compose",
+        "MPAndroidChart",
     ],
     kotlincflags: [
         "-Xjvm-default=all",
-        "-opt-in=kotlin.RequiresOptIn",
     ],
     min_sdk_version: "31",
 }
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 3b159e9..19963fb 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -21,11 +21,14 @@
 
 android {
     namespace 'com.android.settingslib.spa'
-    compileSdk spa_target_sdk
+    compileSdk TARGET_SDK
+    buildToolsVersion = BUILD_TOOLS_VERSION
 
     defaultConfig {
-        minSdk spa_min_sdk
-        targetSdk spa_target_sdk
+        minSdk MIN_SDK
+        targetSdk TARGET_SDK
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }
 
     sourceSets {
@@ -36,6 +39,13 @@
             res.srcDirs = ["res"]
             manifest.srcFile "AndroidManifest.xml"
         }
+        androidTest {
+            kotlin {
+                srcDir "../tests/src"
+            }
+            res.srcDirs = ["../tests/res"]
+            manifest.srcFile "../tests/AndroidManifest.xml"
+        }
     }
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_8
@@ -43,7 +53,7 @@
     }
     kotlinOptions {
         jvmTarget = '1.8'
-        freeCompilerArgs = ["-Xjvm-default=all", "-opt-in=kotlin.RequiresOptIn"]
+        freeCompilerArgs = ["-Xjvm-default=all"]
     }
     buildFeatures {
         compose true
@@ -51,17 +61,54 @@
     composeOptions {
         kotlinCompilerExtensionVersion jetpack_compose_compiler_version
     }
+    buildTypes {
+        debug {
+            testCoverageEnabled = true
+        }
+    }
 }
 
 dependencies {
+    String jetpack_lifecycle_version = "2.6.0-alpha03"
+
     api "androidx.appcompat:appcompat:1.7.0-alpha01"
+    api "androidx.slice:slice-builders:1.1.0-alpha02"
+    api "androidx.slice:slice-core:1.1.0-alpha02"
+    api "androidx.slice:slice-view:1.1.0-alpha02"
     api "androidx.compose.material3:material3:1.1.0-alpha01"
     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-alpha03"
-    api "androidx.navigation:navigation-compose:2.5.0"
+    api "androidx.lifecycle:lifecycle-livedata-ktx:$jetpack_lifecycle_version"
+    api "androidx.lifecycle:lifecycle-runtime-compose:$jetpack_lifecycle_version"
+    api "androidx.navigation:navigation-compose:2.6.0-alpha04"
+    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"
+
+    androidTestImplementation project(":testutils")
+    androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:2.28.1"
+}
+
+task coverageReport(type: JacocoReport, dependsOn: "connectedDebugAndroidTest") {
+    group = "Reporting"
+    description = "Generate Jacoco coverage reports after running tests."
+
+    sourceDirectories.from = files("src")
+    classDirectories.from = fileTree(
+            dir: "$buildDir/tmp/kotlin-classes/debug",
+            excludes: [
+                    "com/android/settingslib/spa/debug/**",
+
+                    // Excludes files forked from AndroidX.
+                    "com/android/settingslib/spa/widget/scaffold/CustomizedAppBar*",
+                    "com/android/settingslib/spa/widget/scaffold/TopAppBarColors*",
+
+                    // Excludes files forked from Accompanist.
+                    "com/android/settingslib/spa/framework/compose/DrawablePainter*",
+                    "com/android/settingslib/spa/framework/compose/Pager*",
+            ],
+    )
+    executionData.from = fileTree(dir: "$buildDir/outputs/code_coverage/debugAndroidTest/connected")
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
new file mode 100644
index 0000000..f7cbdae
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
@@ -0,0 +1,251 @@
+/*
+ * 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.debug
+
+import android.net.Uri
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.navigation.NavType
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.R
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.compose.localNavController
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.SESSION_BROWSE
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
+import com.android.settingslib.spa.slice.fromEntry
+import com.android.settingslib.spa.slice.presenter.SliceDemo
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.HomeScaffold
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+
+private const val TAG = "DebugActivity"
+private const val ROUTE_ROOT = "root"
+private const val ROUTE_All_PAGES = "pages"
+private const val ROUTE_All_ENTRIES = "entries"
+private const val ROUTE_All_SLICES = "slices"
+private const val ROUTE_PAGE = "page"
+private const val ROUTE_ENTRY = "entry"
+private const val PARAM_NAME_PAGE_ID = "pid"
+private const val PARAM_NAME_ENTRY_ID = "eid"
+
+/**
+ * The Debug Activity to display all Spa Pages & Entries.
+ * One can open the debug activity by:
+ *   $ adb shell am start -n <Package>/com.android.settingslib.spa.framework.debug.DebugActivity
+ * For gallery, Package = com.android.settingslib.spa.gallery
+ */
+class DebugActivity : ComponentActivity() {
+    private val spaEnvironment get() = SpaEnvironmentFactory.instance
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        setTheme(R.style.Theme_SpaLib)
+        super.onCreate(savedInstanceState)
+        spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
+
+        setContent {
+            SettingsTheme {
+                MainContent()
+            }
+        }
+    }
+
+    @Composable
+    private fun MainContent() {
+        val navController = rememberNavController()
+        CompositionLocalProvider(navController.localNavController()) {
+            NavHost(navController, ROUTE_ROOT) {
+                composable(route = ROUTE_ROOT) { RootPage() }
+                composable(route = ROUTE_All_PAGES) { AllPages() }
+                composable(route = ROUTE_All_ENTRIES) { AllEntries() }
+                composable(route = ROUTE_All_SLICES) { AllSlices() }
+                composable(
+                    route = "$ROUTE_PAGE/{$PARAM_NAME_PAGE_ID}",
+                    arguments = listOf(
+                        navArgument(PARAM_NAME_PAGE_ID) { type = NavType.StringType },
+                    )
+                ) { navBackStackEntry -> OnePage(navBackStackEntry.arguments) }
+                composable(
+                    route = "$ROUTE_ENTRY/{$PARAM_NAME_ENTRY_ID}",
+                    arguments = listOf(
+                        navArgument(PARAM_NAME_ENTRY_ID) { type = NavType.StringType },
+                    )
+                ) { navBackStackEntry -> OneEntry(navBackStackEntry.arguments) }
+            }
+        }
+    }
+
+    @Composable
+    fun RootPage() {
+        val entryRepository by spaEnvironment.entryRepository
+        val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() }
+        val allEntry = remember { entryRepository.getAllEntries() }
+        val allSliceEntry =
+            remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
+        HomeScaffold(title = "Settings Debug") {
+            Preference(object : PreferenceModel {
+                override val title = "List All Pages (${allPageWithEntry.size})"
+                override val onClick = navigator(route = ROUTE_All_PAGES)
+            })
+            Preference(object : PreferenceModel {
+                override val title = "List All Entries (${allEntry.size})"
+                override val onClick = navigator(route = ROUTE_All_ENTRIES)
+            })
+            Preference(object : PreferenceModel {
+                override val title = "List All Slices (${allSliceEntry.size})"
+                override val onClick = navigator(route = ROUTE_All_SLICES)
+            })
+        }
+    }
+
+    @Composable
+    fun AllPages() {
+        val entryRepository by spaEnvironment.entryRepository
+        val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() }
+        RegularScaffold(title = "All Pages (${allPageWithEntry.size})") {
+            for (pageWithEntry in allPageWithEntry) {
+                val page = pageWithEntry.page
+                Preference(object : PreferenceModel {
+                    override val title = "${page.debugBrief()} (${pageWithEntry.entries.size})"
+                    override val summary = page.debugArguments().toState()
+                    override val onClick = navigator(route = ROUTE_PAGE + "/${page.id}")
+                })
+            }
+        }
+    }
+
+    @Composable
+    fun AllEntries() {
+        val entryRepository by spaEnvironment.entryRepository
+        val allEntry = remember { entryRepository.getAllEntries() }
+        RegularScaffold(title = "All Entries (${allEntry.size})") {
+            EntryList(allEntry)
+        }
+    }
+
+    @Composable
+    fun AllSlices() {
+        val entryRepository by spaEnvironment.entryRepository
+        val authority = spaEnvironment.sliceProviderAuthorities
+        val allSliceEntry =
+            remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
+        RegularScaffold(title = "All Slices (${allSliceEntry.size})") {
+            for (entry in allSliceEntry) {
+                SliceDemo(sliceUri = Uri.Builder().fromEntry(entry, authority).build())
+            }
+        }
+    }
+
+    @Composable
+    fun OnePage(arguments: Bundle?) {
+        val entryRepository by spaEnvironment.entryRepository
+        val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "")
+        val pageWithEntry = entryRepository.getPageWithEntry(id)!!
+        val page = pageWithEntry.page
+        RegularScaffold(title = "Page - ${page.debugBrief()}") {
+            Text(text = "id = ${page.id}")
+            Text(text = page.debugArguments())
+            Text(text = "Entry size: ${pageWithEntry.entries.size}")
+            Preference(model = object : PreferenceModel {
+                override val title = "open page"
+                override val enabled = (spaEnvironment.browseActivityClass != null &&
+                    page.isBrowsable()).toState()
+                override val onClick = openPage(page)
+            })
+            EntryList(pageWithEntry.entries)
+        }
+    }
+
+    @Composable
+    fun OneEntry(arguments: Bundle?) {
+        val entryRepository by spaEnvironment.entryRepository
+        val id = arguments!!.getString(PARAM_NAME_ENTRY_ID, "")
+        val entry = entryRepository.getEntry(id)!!
+        val entryContent = remember { entry.debugContent(entryRepository) }
+        RegularScaffold(title = "Entry - ${entry.debugBrief()}") {
+            Preference(model = object : PreferenceModel {
+                override val title = "open entry"
+                override val enabled = (spaEnvironment.browseActivityClass != null &&
+                    entry.containerPage().isBrowsable())
+                    .toState()
+                override val onClick = openEntry(entry)
+            })
+            Text(text = entryContent)
+        }
+    }
+
+    @Composable
+    private fun EntryList(entries: Collection<SettingsEntry>) {
+        for (entry in entries) {
+            Preference(object : PreferenceModel {
+                override val title = entry.debugBrief()
+                override val summary =
+                    "${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState()
+                override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}")
+            })
+        }
+    }
+
+    @Composable
+    private fun openPage(page: SettingsPage): (() -> Unit)? {
+        val context = LocalContext.current
+        val intent =
+            page.createIntent(SESSION_BROWSE) ?: return null
+        val route = page.buildRoute()
+        return {
+            spaEnvironment.logger.message(
+                TAG, "OpenPage: $route", category = LogCategory.FRAMEWORK
+            )
+            context.startActivity(intent)
+        }
+    }
+
+    @Composable
+    private fun openEntry(entry: SettingsEntry): (() -> Unit)? {
+        val context = LocalContext.current
+        val intent = entry.createIntent(SESSION_SEARCH)
+            ?: return null
+        val route = entry.containerPage().buildRoute()
+        return {
+            spaEnvironment.logger.message(
+                TAG, "OpenEntry: $route", category = LogCategory.FRAMEWORK
+            )
+            context.startActivity(intent)
+        }
+    }
+}
+
+/**
+ * A blank activity without any page.
+ */
+class BlankActivity : ComponentActivity()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
new file mode 100644
index 0000000..6ecf9c3
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
@@ -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.settingslib.spa.debug
+
+import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.EntryStatusData
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryRepository
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.util.normalize
+
+private fun EntrySearchData.debugContent(): String {
+    val content = listOf(
+        "search_title = $title",
+        "search_keyword = $keyword",
+    )
+    return content.joinToString("\n")
+}
+
+private fun EntryStatusData.debugContent(): String {
+    val content = listOf(
+        "is_disabled = $isDisabled",
+        "is_switch_off = $isSwitchOff",
+    )
+    return content.joinToString("\n")
+}
+
+fun SettingsPage.debugArguments(): String {
+    val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
+    if (normArguments == null || normArguments.isEmpty) return "[No arguments]"
+    return normArguments.toString().removeRange(0, 6)
+}
+
+fun SettingsPage.debugBrief(): String {
+    return displayName
+}
+
+fun SettingsEntry.debugBrief(): String {
+    return "${owner.displayName}:$displayName"
+}
+
+fun SettingsEntry.debugContent(entryRepository: SettingsEntryRepository): String {
+    val searchData = getSearchData()
+    val statusData = getStatusData()
+    val entryPathWithName = entryRepository.getEntryPathWithDisplayName(id)
+    val entryPathWithTitle = entryRepository.getEntryPathWithTitle(id,
+        searchData?.title ?: displayName)
+    val content = listOf(
+        "------ STATIC ------",
+        "id = $id",
+        "owner = ${owner.debugBrief()} ${owner.debugArguments()}",
+        "linkFrom = ${fromPage?.debugBrief()} ${fromPage?.debugArguments()}",
+        "linkTo = ${toPage?.debugBrief()} ${toPage?.debugArguments()}",
+        "hierarchy_path = $entryPathWithName",
+        "------ SEARCH ------",
+        "search_path = $entryPathWithTitle",
+        searchData?.debugContent() ?: "no search data",
+        statusData?.debugContent() ?: "no status data",
+    )
+    return content.joinToString("\n")
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
new file mode 100644
index 0000000..838c0cf
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
@@ -0,0 +1,205 @@
+/*
+ * 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.debug
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.URI_INTENT_SCHEME
+import android.content.UriMatcher
+import android.content.pm.ProviderInfo
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.net.Uri
+import android.util.Log
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.util.KEY_DESTINATION
+import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.util.KEY_SESSION_SOURCE_NAME
+import com.android.settingslib.spa.framework.util.SESSION_BROWSE
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
+
+private const val TAG = "DebugProvider"
+
+/**
+ * The content provider to return debug data.
+ * One can query the provider result by:
+ *   $ adb shell content query --uri content://<AuthorityPath>/<QueryPath>
+ * For gallery, AuthorityPath = com.android.spa.gallery.debug
+ * Some examples:
+ *   $ adb shell content query --uri content://<AuthorityPath>/page_debug
+ *   $ adb shell content query --uri content://<AuthorityPath>/entry_debug
+ *   $ adb shell content query --uri content://<AuthorityPath>/page_info
+ *   $ adb shell content query --uri content://<AuthorityPath>/entry_info
+ */
+class DebugProvider : ContentProvider() {
+    private val spaEnvironment get() = SpaEnvironmentFactory.instance
+    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
+
+    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
+        TODO("Implement this to handle requests to delete one or more rows")
+    }
+
+    override fun getType(uri: Uri): String? {
+        TODO(
+            "Implement this to handle requests for the MIME type of the data" +
+                "at the given URI"
+        )
+    }
+
+    override fun insert(uri: Uri, values: ContentValues?): Uri? {
+        TODO("Implement this to handle requests to insert a new row.")
+    }
+
+    override fun update(
+        uri: Uri,
+        values: ContentValues?,
+        selection: String?,
+        selectionArgs: Array<String>?
+    ): Int {
+        TODO("Implement this to handle requests to update one or more rows.")
+    }
+
+    override fun onCreate(): Boolean {
+        Log.d(TAG, "onCreate")
+        return true
+    }
+
+    override fun attachInfo(context: Context?, info: ProviderInfo?) {
+        if (info != null) {
+            QueryEnum.PAGE_DEBUG_QUERY.addUri(uriMatcher, info.authority)
+            QueryEnum.ENTRY_DEBUG_QUERY.addUri(uriMatcher, info.authority)
+            QueryEnum.PAGE_INFO_QUERY.addUri(uriMatcher, info.authority)
+            QueryEnum.ENTRY_INFO_QUERY.addUri(uriMatcher, info.authority)
+        }
+        super.attachInfo(context, info)
+    }
+
+    override fun query(
+        uri: Uri,
+        projection: Array<String>?,
+        selection: String?,
+        selectionArgs: Array<String>?,
+        sortOrder: String?
+    ): Cursor? {
+        return try {
+            when (uriMatcher.match(uri)) {
+                QueryEnum.PAGE_DEBUG_QUERY.queryMatchCode -> queryPageDebug()
+                QueryEnum.ENTRY_DEBUG_QUERY.queryMatchCode -> queryEntryDebug()
+                QueryEnum.PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo()
+                QueryEnum.ENTRY_INFO_QUERY.queryMatchCode -> queryEntryInfo()
+                else -> throw UnsupportedOperationException("Unknown Uri $uri")
+            }
+        } catch (e: UnsupportedOperationException) {
+            throw e
+        } catch (e: Exception) {
+            Log.e(TAG, "Provider querying exception:", e)
+            null
+        }
+    }
+
+    private fun queryPageDebug(): Cursor {
+        val entryRepository by spaEnvironment.entryRepository
+        val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns())
+        for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
+            val page = pageWithEntry.page
+            if (!page.isBrowsable()) continue
+            val command = createBrowseAdbCommand(
+                destination = page.buildRoute(),
+                sessionName = SESSION_BROWSE
+            )
+            if (command != null) {
+                cursor.newRow().add(ColumnEnum.PAGE_START_ADB.id, command)
+            }
+        }
+        return cursor
+    }
+
+    private fun queryEntryDebug(): Cursor {
+        val entryRepository by spaEnvironment.entryRepository
+        val cursor = MatrixCursor(QueryEnum.ENTRY_DEBUG_QUERY.getColumns())
+        for (entry in entryRepository.getAllEntries()) {
+            val page = entry.containerPage()
+            if (!page.isBrowsable()) continue
+            val command = createBrowseAdbCommand(
+                destination = page.buildRoute(),
+                entryId = entry.id,
+                sessionName = SESSION_SEARCH
+            )
+            if (command != null) {
+                cursor.newRow().add(ColumnEnum.ENTRY_START_ADB.id, command)
+            }
+        }
+        return cursor
+    }
+
+    private fun queryPageInfo(): Cursor {
+        val entryRepository by spaEnvironment.entryRepository
+        val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns())
+        for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
+            val page = pageWithEntry.page
+            val intent = page.createIntent(SESSION_BROWSE) ?: Intent()
+            cursor.newRow()
+                .add(ColumnEnum.PAGE_ID.id, page.id)
+                .add(ColumnEnum.PAGE_NAME.id, page.displayName)
+                .add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute())
+                .add(ColumnEnum.PAGE_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
+                .add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size)
+                .add(ColumnEnum.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0)
+        }
+        return cursor
+    }
+
+    private fun queryEntryInfo(): Cursor {
+        val entryRepository by spaEnvironment.entryRepository
+        val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns())
+        for (entry in entryRepository.getAllEntries()) {
+            val intent = entry.createIntent(SESSION_SEARCH) ?: Intent()
+            cursor.newRow()
+                .add(ColumnEnum.ENTRY_ID.id, entry.id)
+                .add(ColumnEnum.ENTRY_NAME.id, entry.displayName)
+                .add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute())
+                .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
+                .add(
+                    ColumnEnum.ENTRY_HIERARCHY_PATH.id,
+                    entryRepository.getEntryPathWithDisplayName(entry.id)
+                )
+        }
+        return cursor
+    }
+}
+
+private fun createBrowseAdbCommand(
+    destination: String? = null,
+    entryId: String? = null,
+    sessionName: String? = null,
+): String? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+    val packageName = context.packageName
+    val activityName = browseActivityClass.name.replace(packageName, "")
+    val destinationParam =
+        if (destination != null) " -e $KEY_DESTINATION $destination" else ""
+    val highlightParam =
+        if (entryId != null) " -e $KEY_HIGHLIGHT_ENTRY $entryId" else ""
+    val sessionParam =
+        if (sessionName != null) " -e $KEY_SESSION_SOURCE_NAME $sessionName" else ""
+    return "adb shell am start -n $packageName/$activityName" +
+        "$destinationParam$highlightParam$sessionParam"
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
new file mode 100644
index 0000000..bb9a134
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.debug
+
+import android.content.UriMatcher
+
+/**
+ * Enum to define all column names in provider.
+ */
+enum class ColumnEnum(val id: String) {
+    // Columns related to page
+    PAGE_ID("pageId"),
+    PAGE_NAME("pageName"),
+    PAGE_ROUTE("pageRoute"),
+    PAGE_INTENT_URI("pageIntent"),
+    PAGE_ENTRY_COUNT("entryCount"),
+    HAS_RUNTIME_PARAM("hasRuntimeParam"),
+    PAGE_START_ADB("pageStartAdb"),
+
+    // Columns related to entry
+    ENTRY_ID("entryId"),
+    ENTRY_NAME("entryName"),
+    ENTRY_ROUTE("entryRoute"),
+    ENTRY_INTENT_URI("entryIntent"),
+    ENTRY_HIERARCHY_PATH("entryPath"),
+    ENTRY_START_ADB("entryStartAdb"),
+}
+
+/**
+ * Enum to define all queries supported in the provider.
+ */
+enum class QueryEnum(
+    val queryPath: String,
+    val queryMatchCode: Int,
+    val columnNames: List<ColumnEnum>
+) {
+    // For debug
+    PAGE_DEBUG_QUERY(
+        "page_debug", 1,
+        listOf(ColumnEnum.PAGE_START_ADB)
+    ),
+    ENTRY_DEBUG_QUERY(
+        "entry_debug", 2,
+        listOf(ColumnEnum.ENTRY_START_ADB)
+    ),
+
+    // page related queries.
+    PAGE_INFO_QUERY(
+        "page_info", 100,
+        listOf(
+            ColumnEnum.PAGE_ID,
+            ColumnEnum.PAGE_NAME,
+            ColumnEnum.PAGE_ROUTE,
+            ColumnEnum.PAGE_INTENT_URI,
+            ColumnEnum.PAGE_ENTRY_COUNT,
+            ColumnEnum.HAS_RUNTIME_PARAM,
+        )
+    ),
+
+    // entry related queries
+    ENTRY_INFO_QUERY(
+        "entry_info", 200,
+        listOf(
+            ColumnEnum.ENTRY_ID,
+            ColumnEnum.ENTRY_NAME,
+            ColumnEnum.ENTRY_ROUTE,
+            ColumnEnum.ENTRY_INTENT_URI,
+            ColumnEnum.ENTRY_HIERARCHY_PATH,
+        )
+    ),
+}
+
+internal fun QueryEnum.getColumns(): Array<String> {
+    return columnNames.map { it.id }.toTypedArray()
+}
+
+internal fun QueryEnum.getIndex(name: ColumnEnum): Int {
+    return columnNames.indexOf(name)
+}
+
+internal fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
+    uriMatcher.addURI(authority, queryPath, queryMatchCode)
+}
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 c3c90ab..aa10cc8 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
@@ -16,20 +16,17 @@
 
 package com.android.settingslib.spa.framework
 
+import android.content.Intent
 import android.os.Bundle
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
+import androidx.annotation.VisibleForTesting
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.mutableStateOf
-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
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
@@ -37,12 +34,17 @@
 import com.android.settingslib.spa.R
 import com.android.settingslib.spa.framework.common.LogCategory
 import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.LocalNavController
 import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
 import com.android.settingslib.spa.framework.compose.localNavController
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.PageEvent
+import com.android.settingslib.spa.framework.util.getDestination
+import com.android.settingslib.spa.framework.util.getEntryId
+import com.android.settingslib.spa.framework.util.getSessionName
 import com.android.settingslib.spa.framework.util.navRoute
 
 private const val TAG = "BrowseActivity"
@@ -74,86 +76,65 @@
 
         setContent {
             SettingsTheme {
-                MainContent()
+                val sppRepository by spaEnvironment.pageProviderRepository
+                BrowseContent(sppRepository, intent)
             }
         }
     }
+}
 
-    @Composable
-    private fun MainContent() {
-        val sppRepository by spaEnvironment.pageProviderRepository
-        val navController = rememberNavController()
-        val nullPage = SettingsPage.createNull()
-        CompositionLocalProvider(navController.localNavController()) {
-            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 ->
-                        PageLogger(remember(navBackStackEntry.arguments) {
-                            spp.createSettingsPage(arguments = navBackStackEntry.arguments)
-                        })
-
-                        spp.Page(navBackStackEntry.arguments)
-                    }
-                }
-            }
-            InitialDestinationNavigator()
-        }
-    }
-
-    @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) }
-        if (destinationNavigated.value) return
-        destinationNavigated.value = true
+@VisibleForTesting
+@Composable
+fun BrowseContent(sppRepository: SettingsPageProviderRepository, initialIntent: Intent? = null) {
+    val navController = rememberNavController()
+    CompositionLocalProvider(navController.localNavController()) {
         val controller = LocalNavController.current as NavControllerWrapperImpl
-        LaunchedEffect(Unit) {
-            val destination =
-                intent?.getStringExtra(KEY_DESTINATION) ?: sppRepository.getDefaultStartPage()
-            val highlightEntryId = intent?.getStringExtra(KEY_HIGHLIGHT_ENTRY)
-            if (destination.isNotEmpty()) {
-                controller.highlightId = highlightEntryId
-                val navController = controller.navController
-                navController.navigate(destination) {
-                    popUpTo(navController.graph.findStartDestination().id) {
-                        inclusive = true
-                    }
-                }
+        controller.NavContent(sppRepository.getAllProviders())
+        controller.InitialDestination(initialIntent, sppRepository.getDefaultStartPage())
+    }
+}
+
+@Composable
+private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<SettingsPageProvider>) {
+    val nullPage = SettingsPage.createNull()
+    NavHost(
+        navController = navController,
+        startDestination = nullPage.sppName,
+    ) {
+        composable(nullPage.sppName) {}
+        for (spp in allProvider) {
+            composable(
+                route = spp.name + spp.parameter.navRoute(),
+                arguments = spp.parameter,
+            ) { navBackStackEntry ->
+                spp.PageEvent(navBackStackEntry.arguments)
+                spp.Page(navBackStackEntry.arguments)
             }
         }
     }
+}
 
-    companion object {
-        const val KEY_DESTINATION = "spaActivityDestination"
-        const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
+@Composable
+private fun NavControllerWrapperImpl.InitialDestination(
+    initialIntent: Intent?,
+    defaultDestination: String
+) {
+    val destinationNavigated = rememberSaveable { mutableStateOf(false) }
+    if (destinationNavigated.value) return
+    destinationNavigated.value = true
+
+    val initialDestination = initialIntent?.getDestination() ?: defaultDestination
+    if (initialDestination.isEmpty()) return
+    val initialEntryId = initialIntent?.getEntryId()
+    val sessionSourceName = initialIntent?.getSessionName()
+
+    LaunchedEffect(Unit) {
+        highlightId = initialEntryId
+        sessionName = sessionSourceName
+        navController.navigate(initialDestination) {
+            popUpTo(navController.graph.findStartDestination().id) {
+                inclusive = true
+            }
+        }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
deleted file mode 100644
index 38f41bc..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
+++ /dev/null
@@ -1,183 +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.settingslib.spa.framework
-
-import android.content.ContentProvider
-import android.content.ContentValues
-import android.content.Context
-import android.content.Intent
-import android.content.UriMatcher
-import android.content.pm.ProviderInfo
-import android.database.Cursor
-import android.database.MatrixCursor
-import android.net.Uri
-import android.util.Log
-import com.android.settingslib.spa.framework.common.ColumnEnum
-import com.android.settingslib.spa.framework.common.QueryEnum
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.addUri
-import com.android.settingslib.spa.framework.common.getColumns
-
-private const val TAG = "EntryProvider"
-
-/**
- * The content provider to return entry related data, which can be used for search and hierarchy.
- * One can query the provider result by:
- *   $ adb shell content query --uri content://<AuthorityPath>/<QueryPath>
- * For gallery, AuthorityPath = com.android.spa.gallery.provider
- * For Settings, AuthorityPath = com.android.settings.spa.provider
- * Some examples:
- *   $ adb shell content query --uri content://<AuthorityPath>/search_static
- *   $ adb shell content query --uri content://<AuthorityPath>/search_dynamic
- *   $ adb shell content query --uri content://<AuthorityPath>/search_mutable_status
- *   $ adb shell content query --uri content://<AuthorityPath>/search_immutable_status
- */
-open class EntryProvider : ContentProvider() {
-    private val spaEnvironment get() = SpaEnvironmentFactory.instance
-    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
-
-    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
-        TODO("Implement this to handle requests to delete one or more rows")
-    }
-
-    override fun getType(uri: Uri): String? {
-        TODO(
-            "Implement this to handle requests for the MIME type of the data" +
-                "at the given URI"
-        )
-    }
-
-    override fun insert(uri: Uri, values: ContentValues?): Uri? {
-        TODO("Implement this to handle requests to insert a new row.")
-    }
-
-    override fun update(
-        uri: Uri,
-        values: ContentValues?,
-        selection: String?,
-        selectionArgs: Array<String>?
-    ): Int {
-        TODO("Implement this to handle requests to update one or more rows.")
-    }
-
-    override fun onCreate(): Boolean {
-        Log.d(TAG, "onCreate")
-        return true
-    }
-
-    override fun attachInfo(context: Context?, info: ProviderInfo?) {
-        if (info != null) {
-            QueryEnum.SEARCH_STATIC_DATA_QUERY.addUri(uriMatcher, info.authority)
-            QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.addUri(uriMatcher, info.authority)
-            QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.addUri(uriMatcher, info.authority)
-            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.addUri(uriMatcher, info.authority)
-        }
-        super.attachInfo(context, info)
-    }
-
-    override fun query(
-        uri: Uri,
-        projection: Array<String>?,
-        selection: String?,
-        selectionArgs: Array<String>?,
-        sortOrder: String?
-    ): Cursor? {
-        return try {
-            when (uriMatcher.match(uri)) {
-                QueryEnum.SEARCH_STATIC_DATA_QUERY.queryMatchCode -> querySearchStaticData()
-                QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.queryMatchCode -> querySearchDynamicData()
-                QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.queryMatchCode ->
-                    querySearchMutableStatusData()
-                QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.queryMatchCode ->
-                    querySearchImmutableStatusData()
-                else -> throw UnsupportedOperationException("Unknown Uri $uri")
-            }
-        } catch (e: UnsupportedOperationException) {
-            throw e
-        } catch (e: Exception) {
-            Log.e(TAG, "Provider querying exception:", e)
-            null
-        }
-    }
-
-    private fun querySearchImmutableStatusData(): Cursor {
-        val entryRepository by spaEnvironment.entryRepository
-        val cursor = MatrixCursor(QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.getColumns())
-        for (entry in entryRepository.getAllEntries()) {
-            if (!entry.isAllowSearch || entry.mutableStatus) continue
-            fetchStatusData(entry, cursor)
-        }
-        return cursor
-    }
-
-    private fun querySearchMutableStatusData(): Cursor {
-        val entryRepository by spaEnvironment.entryRepository
-        val cursor = MatrixCursor(QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.getColumns())
-        for (entry in entryRepository.getAllEntries()) {
-            if (!entry.isAllowSearch || !entry.mutableStatus) continue
-            fetchStatusData(entry, cursor)
-        }
-        return cursor
-    }
-
-    private fun querySearchStaticData(): Cursor {
-        val entryRepository by spaEnvironment.entryRepository
-        val cursor = MatrixCursor(QueryEnum.SEARCH_STATIC_DATA_QUERY.getColumns())
-        for (entry in entryRepository.getAllEntries()) {
-            if (!entry.isAllowSearch || entry.isSearchDataDynamic) continue
-            fetchSearchData(entry, cursor)
-        }
-        return cursor
-    }
-
-    private fun querySearchDynamicData(): Cursor {
-        val entryRepository by spaEnvironment.entryRepository
-        val cursor = MatrixCursor(QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.getColumns())
-        for (entry in entryRepository.getAllEntries()) {
-            if (!entry.isAllowSearch || !entry.isSearchDataDynamic) continue
-            fetchSearchData(entry, cursor)
-        }
-        return cursor
-    }
-
-    private fun fetchSearchData(entry: SettingsEntry, cursor: MatrixCursor) {
-        val entryRepository by spaEnvironment.entryRepository
-        val browseActivityClass = spaEnvironment.browseActivityClass
-
-        // Fetch search data. We can add runtime arguments later if necessary
-        val searchData = entry.getSearchData() ?: return
-        val intent = entry.containerPage()
-            .createBrowseIntent(context, browseActivityClass, entry.id)
-            ?: Intent()
-        cursor.newRow()
-            .add(ColumnEnum.ENTRY_ID.id, entry.id)
-            .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(Intent.URI_INTENT_SCHEME))
-            .add(ColumnEnum.SEARCH_TITLE.id, searchData.title)
-            .add(ColumnEnum.SEARCH_KEYWORD.id, searchData.keyword)
-            .add(ColumnEnum.SEARCH_PATH.id,
-                entryRepository.getEntryPathWithTitle(entry.id, searchData.title))
-    }
-
-    private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) {
-        // Fetch status data. We can add runtime arguments later if necessary
-        val statusData = entry.getStatusData() ?: return
-        cursor.newRow()
-            .add(ColumnEnum.ENTRY_ID.id, entry.id)
-            .add(ColumnEnum.SEARCH_STATUS_DISABLED.id, statusData.isDisabled)
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.kt
new file mode 100644
index 0000000..fc551a8
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.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.settingslib.spa.framework.common
+
+import androidx.lifecycle.LiveData
+import androidx.slice.Slice
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+open class EntrySliceData : LiveData<Slice?>() {
+    private val asyncRunnerScope = CoroutineScope(Dispatchers.IO)
+    private var asyncRunnerJob: Job? = null
+    private var asyncActionJob: Job? = null
+    private var isActive = false
+
+    open suspend fun asyncRunner() {}
+
+    open suspend fun asyncAction() {}
+
+    override fun onActive() {
+        asyncRunnerJob?.cancel()
+        asyncRunnerJob = asyncRunnerScope.launch { asyncRunner() }
+        isActive = true
+    }
+
+    override fun onInactive() {
+        asyncRunnerJob?.cancel()
+        asyncRunnerJob = null
+        asyncActionJob?.cancel()
+        asyncActionJob = null
+        isActive = false
+    }
+
+    fun isActive(): Boolean {
+        return isActive
+    }
+
+    fun doAction() {
+        asyncActionJob?.cancel()
+        asyncActionJob = asyncRunnerScope.launch { asyncAction() }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
deleted file mode 100644
index 121c07f..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
+++ /dev/null
@@ -1,138 +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.settingslib.spa.framework.common
-
-import android.content.UriMatcher
-
-/**
- * Enum to define all column names in provider.
- */
-enum class ColumnEnum(val id: String) {
-    // Columns related to page
-    PAGE_ID("pageId"),
-    PAGE_NAME("pageName"),
-    PAGE_ROUTE("pageRoute"),
-    PAGE_INTENT_URI("pageIntent"),
-    PAGE_ENTRY_COUNT("entryCount"),
-    HAS_RUNTIME_PARAM("hasRuntimeParam"),
-    PAGE_START_ADB("pageStartAdb"),
-
-    // Columns related to entry
-    ENTRY_ID("entryId"),
-    ENTRY_NAME("entryName"),
-    ENTRY_ROUTE("entryRoute"),
-    ENTRY_INTENT_URI("entryIntent"),
-    ENTRY_HIERARCHY_PATH("entryPath"),
-    ENTRY_START_ADB("entryStartAdb"),
-
-    // Columns related to search
-    SEARCH_TITLE("searchTitle"),
-    SEARCH_KEYWORD("searchKw"),
-    SEARCH_PATH("searchPath"),
-    SEARCH_STATUS_DISABLED("searchDisabled"),
-}
-
-/**
- * Enum to define all queries supported in the provider.
- */
-enum class QueryEnum(
-    val queryPath: String,
-    val queryMatchCode: Int,
-    val columnNames: List<ColumnEnum>
-) {
-    // For debug
-    PAGE_DEBUG_QUERY(
-        "page_debug", 1,
-        listOf(ColumnEnum.PAGE_START_ADB)
-    ),
-    ENTRY_DEBUG_QUERY(
-        "entry_debug", 2,
-        listOf(ColumnEnum.ENTRY_START_ADB)
-    ),
-
-    // page related queries.
-    PAGE_INFO_QUERY(
-        "page_info", 100,
-        listOf(
-            ColumnEnum.PAGE_ID,
-            ColumnEnum.PAGE_NAME,
-            ColumnEnum.PAGE_ROUTE,
-            ColumnEnum.PAGE_INTENT_URI,
-            ColumnEnum.PAGE_ENTRY_COUNT,
-            ColumnEnum.HAS_RUNTIME_PARAM,
-        )
-    ),
-
-    // entry related queries
-    ENTRY_INFO_QUERY(
-        "entry_info", 200,
-        listOf(
-            ColumnEnum.ENTRY_ID,
-            ColumnEnum.ENTRY_NAME,
-            ColumnEnum.ENTRY_ROUTE,
-            ColumnEnum.ENTRY_INTENT_URI,
-            ColumnEnum.ENTRY_HIERARCHY_PATH,
-        )
-    ),
-
-    SEARCH_STATIC_DATA_QUERY(
-        "search_static", 301,
-        listOf(
-            ColumnEnum.ENTRY_ID,
-            ColumnEnum.ENTRY_INTENT_URI,
-            ColumnEnum.SEARCH_TITLE,
-            ColumnEnum.SEARCH_KEYWORD,
-            ColumnEnum.SEARCH_PATH,
-        )
-    ),
-    SEARCH_DYNAMIC_DATA_QUERY(
-        "search_dynamic", 302,
-        listOf(
-            ColumnEnum.ENTRY_ID,
-            ColumnEnum.ENTRY_INTENT_URI,
-            ColumnEnum.SEARCH_TITLE,
-            ColumnEnum.SEARCH_KEYWORD,
-            ColumnEnum.SEARCH_PATH,
-        )
-    ),
-    SEARCH_IMMUTABLE_STATUS_DATA_QUERY(
-        "search_immutable_status", 303,
-        listOf(
-            ColumnEnum.ENTRY_ID,
-            ColumnEnum.SEARCH_STATUS_DISABLED,
-        )
-    ),
-    SEARCH_MUTABLE_STATUS_DATA_QUERY(
-        "search_mutable_status", 304,
-        listOf(
-            ColumnEnum.ENTRY_ID,
-            ColumnEnum.SEARCH_STATUS_DISABLED,
-        )
-    ),
-}
-
-internal fun QueryEnum.getColumns(): Array<String> {
-    return columnNames.map { it.id }.toTypedArray()
-}
-
-internal fun QueryEnum.getIndex(name: ColumnEnum): Int {
-    return columnNames.indexOf(name)
-}
-
-internal fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
-    uriMatcher.addURI(authority, queryPath, queryMatchCode)
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 224fe1d..4985b44 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.spa.framework.common
 
+import android.net.Uri
 import android.os.Bundle
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
@@ -34,11 +35,18 @@
         get() = null
     val isHighlighted: Boolean
         get() = false
+    val arguments: Bundle?
+        get() = null
 }
 
 val LocalEntryDataProvider =
     compositionLocalOf<EntryData> { object : EntryData {} }
 
+typealias UiLayerRenderer = @Composable (arguments: Bundle?) -> Unit
+typealias StatusDataGetter = (arguments: Bundle?) -> EntryStatusData?
+typealias SearchDataGetter = (arguments: Bundle?) -> EntrySearchData?
+typealias SliceDataGetter = (sliceUri: Uri, arguments: Bundle?) -> EntrySliceData?
+
 /**
  * Defines data of a Settings entry.
  */
@@ -71,7 +79,10 @@
 
     // Indicate whether the status of entry is mutable.
     // If so, for instance, we'll reindex its status for search.
-    val mutableStatus: Boolean = false,
+    val hasMutableStatus: Boolean = false,
+
+    // Indicate whether the entry has SliceProvider support.
+    val hasSliceSupport: Boolean = false,
 
     /**
      * ========================================
@@ -83,13 +94,19 @@
      * API to get the status data of the entry, such as isDisabled / isSwitchOff.
      * Returns null if this entry do NOT have any status.
      */
-    private val statusDataImpl: (arguments: Bundle?) -> EntryStatusData? = { null },
+    private val statusDataImpl: StatusDataGetter = { null },
 
     /**
      * API to get Search indexing data for this entry, such as title / keyword.
      * Returns null if this entry do NOT support search.
      */
-    private val searchDataImpl: (arguments: Bundle?) -> EntrySearchData? = { null },
+    private val searchDataImpl: SearchDataGetter = { null },
+
+    /**
+     * API to get Slice data of this entry. The Slice data is implemented as a LiveData,
+     * and is associated with the Slice's lifecycle (pin / unpin) by the framework.
+     */
+    private val sliceDataImpl: SliceDataGetter = { _: Uri, _: Bundle? -> null },
 
     /**
      * API to Render UI of this entry directly. For now, we use it in the internal injection, to
@@ -97,7 +114,7 @@
      * injected entry. In the long term, we may deprecate the @Composable Page() API in SPP, and
      * use each entries' UI rendering function in the page instead.
      */
-    private val uiLayoutImpl: (@Composable (arguments: Bundle?) -> Unit) = {},
+    private val uiLayoutImpl: UiLayerRenderer = {},
 ) {
     fun containerPage(): SettingsPage {
         // The Container page of the entry, which is the from-page or
@@ -106,11 +123,11 @@
     }
 
     private fun fullArgument(runtimeArguments: Bundle? = null): Bundle {
-        val arguments = Bundle()
-        if (owner.arguments != null) arguments.putAll(owner.arguments)
-        // Put runtime args later, which can override page args.
-        if (runtimeArguments != null) arguments.putAll(runtimeArguments)
-        return arguments
+        return Bundle().apply {
+            if (owner.arguments != null) putAll(owner.arguments)
+            // Put runtime args later, which can override page args.
+            if (runtimeArguments != null) putAll(runtimeArguments)
+        }
     }
 
     fun getStatusData(runtimeArguments: Bundle? = null): EntryStatusData? {
@@ -121,21 +138,27 @@
         return searchDataImpl(fullArgument(runtimeArguments))
     }
 
+    fun getSliceData(sliceUri: Uri, runtimeArguments: Bundle? = null): EntrySliceData? {
+        return sliceDataImpl(sliceUri, fullArgument(runtimeArguments))
+    }
+
     @Composable
     fun UiLayout(runtimeArguments: Bundle? = null) {
-        CompositionLocalProvider(provideLocalEntryData()) {
-            uiLayoutImpl(fullArgument(runtimeArguments))
+        val arguments = remember { fullArgument(runtimeArguments) }
+        CompositionLocalProvider(provideLocalEntryData(arguments)) {
+            uiLayoutImpl(arguments)
         }
     }
 
     @Composable
-    fun provideLocalEntryData(): ProvidedValue<EntryData> {
+    fun provideLocalEntryData(arguments: Bundle): ProvidedValue<EntryData> {
         val controller = LocalNavController.current
         return LocalEntryDataProvider provides remember {
             object : EntryData {
                 override val pageId = containerPage().id
                 override val entryId = id
                 override val isHighlighted = controller.highlightEntryId == id
+                override val arguments = arguments
             }
         }
     }
@@ -152,12 +175,14 @@
     // Attributes
     private var isAllowSearch: Boolean = false
     private var isSearchDataDynamic: Boolean = false
-    private var mutableStatus: Boolean = false
+    private var hasMutableStatus: Boolean = false
+    private var hasSliceSupport: Boolean = false
 
     // Functions
-    private var statusDataFn: (arguments: Bundle?) -> EntryStatusData? = { null }
-    private var searchDataFn: (arguments: Bundle?) -> EntrySearchData? = { null }
-    private var uiLayoutFn: (@Composable (arguments: Bundle?) -> Unit) = { }
+    private var uiLayoutFn: UiLayerRenderer = { }
+    private var statusDataFn: StatusDataGetter = { null }
+    private var searchDataFn: SearchDataGetter = { null }
+    private var sliceDataFn: SliceDataGetter = { _: Uri, _: Bundle? -> null }
 
     fun build(): SettingsEntry {
         return SettingsEntry(
@@ -173,11 +198,13 @@
             // attributes
             isAllowSearch = isAllowSearch,
             isSearchDataDynamic = isSearchDataDynamic,
-            mutableStatus = mutableStatus,
+            hasMutableStatus = hasMutableStatus,
+            hasSliceSupport = hasSliceSupport,
 
             // functions
             statusDataImpl = statusDataFn,
             searchDataImpl = searchDataFn,
+            sliceDataImpl = sliceDataFn,
             uiLayoutImpl = uiLayoutFn,
         )
     }
@@ -196,18 +223,13 @@
         return this
     }
 
-    fun setIsAllowSearch(isAllowSearch: Boolean): SettingsEntryBuilder {
-        this.isAllowSearch = isAllowSearch
-        return this
-    }
-
     fun setIsSearchDataDynamic(isDynamic: Boolean): SettingsEntryBuilder {
         this.isSearchDataDynamic = isDynamic
         return this
     }
 
     fun setHasMutableStatus(hasMutableStatus: Boolean): SettingsEntryBuilder {
-        this.mutableStatus = hasMutableStatus
+        this.hasMutableStatus = hasMutableStatus
         return this
     }
 
@@ -221,17 +243,30 @@
         return this
     }
 
-    fun setStatusDataFn(fn: (arguments: Bundle?) -> EntryStatusData?): SettingsEntryBuilder {
+    fun setStatusDataFn(fn: StatusDataGetter): SettingsEntryBuilder {
         this.statusDataFn = fn
         return this
     }
 
-    fun setSearchDataFn(fn: (arguments: Bundle?) -> EntrySearchData?): SettingsEntryBuilder {
+    fun setSearchDataFn(fn: SearchDataGetter): SettingsEntryBuilder {
         this.searchDataFn = fn
+        this.isAllowSearch = true
         return this
     }
 
-    fun setUiLayoutFn(fn: @Composable (arguments: Bundle?) -> Unit): SettingsEntryBuilder {
+    fun clearSearchDataFn(): SettingsEntryBuilder {
+        this.searchDataFn = { null }
+        this.isAllowSearch = false
+        return this
+    }
+
+    fun setSliceDataFn(fn: SliceDataGetter): SettingsEntryBuilder {
+        this.sliceDataFn = fn
+        this.hasSliceSupport = true
+        return this
+    }
+
+    fun setUiLayoutFn(fn: UiLayerRenderer): SettingsEntryBuilder {
         this.uiLayoutFn = fn
         return this
     }
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..2bfa2a4 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
@@ -16,13 +16,8 @@
 
 package com.android.settingslib.spa.framework.common
 
-import android.app.Activity
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
 import android.os.Bundle
 import androidx.navigation.NamedNavArgument
-import com.android.settingslib.spa.framework.BrowseActivity
 import com.android.settingslib.spa.framework.util.isRuntimeParam
 import com.android.settingslib.spa.framework.util.navLink
 import com.android.settingslib.spa.framework.util.normalize
@@ -74,7 +69,7 @@
             parameter: List<NamedNavArgument> = emptyList(),
             arguments: Bundle? = null
         ): String {
-            val normArguments = parameter.normalize(arguments)
+            val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
             return "$name:${normArguments?.toString()}".toHashId()
         }
     }
@@ -95,66 +90,21 @@
         return false
     }
 
-    fun getTitle(): String? {
-        val sppRepository by SpaEnvironmentFactory.instance.pageProviderRepository
-        return sppRepository.getProviderOrNull(sppName)?.getTitle(arguments)
-    }
-
-    fun enterPage() {
-        SpaEnvironmentFactory.instance.logger.event(
-            id,
-            LogEvent.PAGE_ENTER,
-            category = LogCategory.FRAMEWORK,
-            details = displayName,
-        )
-    }
-
-    fun leavePage() {
-        SpaEnvironmentFactory.instance.logger.event(
-            id,
-            LogEvent.PAGE_LEAVE,
-            category = LogCategory.FRAMEWORK,
-            details = displayName,
-        )
-    }
-
-    fun createBrowseIntent(
-        context: Context?,
-        browseActivityClass: Class<out Activity>?,
-        entryId: String? = null
-    ): Intent? {
-        if (!isBrowsable(context, browseActivityClass)) return null
-        return Intent().setComponent(ComponentName(context!!, browseActivityClass!!))
-            .apply {
-                putExtra(BrowseActivity.KEY_DESTINATION, buildRoute())
-                if (entryId != null) {
-                    putExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY, entryId)
-                }
-            }
-    }
-
-    fun createBrowseAdbCommand(
-        context: Context?,
-        browseActivityClass: Class<out Activity>?,
-        entryId: String? = null
-    ): String? {
-        if (!isBrowsable(context, browseActivityClass)) return null
-        val packageName = context!!.packageName
-        val activityName = browseActivityClass!!.name.replace(packageName, "")
-        val destinationParam = " -e ${BrowseActivity.KEY_DESTINATION} ${buildRoute()}"
-        val highlightParam =
-            if (entryId != null) " -e ${BrowseActivity.KEY_HIGHLIGHT_ENTRY} $entryId" else ""
-        return "adb shell am start -n $packageName/$activityName$destinationParam$highlightParam"
-    }
-
-    fun isBrowsable(context: Context?, browseActivityClass: Class<out Activity>?): Boolean {
-        return context != null &&
-            browseActivityClass != null &&
-            !isCreateBy(NULL_PAGE_NAME) &&
+    fun isBrowsable(): Boolean {
+        return !isCreateBy(NULL_PAGE_NAME) &&
             !hasRuntimeParam()
     }
 }
 
+fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
+    return SettingsPage.create(
+        name = name,
+        displayName = displayName,
+        parameter = parameter,
+        arguments = arguments
+    )
+}
+
 fun String.toHashId(): String {
     return this.hashCode().toUInt().toString(36)
 }
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..940005d 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.
@@ -29,27 +30,24 @@
     val name: String
 
     /** The display name of this page provider, for better readability. */
-    val displayName: String?
-        get() = null
+    val displayName: String
+        get() = name
 
     /** The page parameters, default is no parameters. */
     val parameter: List<NamedNavArgument>
         get() = emptyList()
 
-    /** The [Composable] used to render this page. */
-    @Composable
-    fun Page(arguments: Bundle?)
+    fun getTitle(arguments: Bundle?): String = displayName
 
     fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()
 
-    fun getTitle(arguments: Bundle?): String = displayName ?: name
-}
-
-fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
-    return SettingsPage.create(
-        name = name,
-        displayName = displayName,
-        parameter = parameter,
-        arguments = arguments
-    )
+    /** The [Composable] used to render this page. */
+    @Composable
+    fun Page(arguments: Bundle?) {
+        RegularScaffold(title = getTitle(arguments)) {
+            for (entry in buildEntry(arguments)) {
+                entry.UiLayout()
+            }
+        }
+    }
 }
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 6073425..6d0b810 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,10 +17,12 @@
 package com.android.settingslib.spa.framework.common
 
 import android.app.Activity
+import android.content.BroadcastReceiver
 import android.content.Context
 import android.util.Log
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.platform.LocalContext
+import com.android.settingslib.spa.slice.SettingsSliceDataRepository
 
 private const val TAG = "SpaEnvironment"
 
@@ -46,6 +48,10 @@
         Log.d(TAG, "resetForPreview")
     }
 
+    fun isReady(): Boolean {
+        return spaEnvironment != null
+    }
+
     val instance: SpaEnvironment
         get() {
             if (spaEnvironment == null)
@@ -59,13 +65,19 @@
 
     val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
 
-    val appContext: Context = context.applicationContext
+    val sliceDataRepository = lazy { SettingsSliceDataRepository(entryRepository.value) }
 
-    open val browseActivityClass: Class<out Activity>? = null
-
-    open val entryProviderAuthorities: String? = null
+    // In Robolectric test, applicationContext is not available. Use context as fallback.
+    val appContext: Context = context.applicationContext ?: context
 
     open val logger: SpaLogger = object : SpaLogger {}
 
+    open val browseActivityClass: Class<out Activity>? = null
+    open val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = null
+
+    // Specify provider authorities for debugging purpose.
+    open val searchProviderAuthorities: String? = null
+    open val sliceProviderAuthorities: String? = null
+
     // TODO: add other environment setup here.
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
index 00a0362..6ecb7fa 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.spa.framework.common
 
+import android.os.Bundle
 import android.util.Log
 
 // Defines the category of the log, for quick filter
@@ -38,10 +39,13 @@
 
     // Entry related events.
     ENTRY_CLICK,
-    ENTRY_SWITCH_ON,
-    ENTRY_SWITCH_OFF,
+    ENTRY_SWITCH,
 }
 
+internal const val LOG_DATA_DISPLAY_NAME = "name"
+internal const val LOG_DATA_SESSION_NAME = "session"
+internal const val LOG_DATA_SWITCH_STATUS = "switch"
+
 /**
  * The interface of logger in Spa
  */
@@ -54,7 +58,7 @@
         id: String,
         event: LogEvent,
         category: LogCategory = LogCategory.DEFAULT,
-        details: String? = null
+        extraData: Bundle = Bundle.EMPTY
     ) {
     }
 }
@@ -64,8 +68,8 @@
         Log.d("SpaMsg-$category", "[$tag] $msg")
     }
 
-    override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
-        val extraMsg = if (details == null) "" else " ($details)"
-        Log.d("SpaEvent-$category", "[$id] $event$extraMsg")
+    override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) {
+        val extraMsg = extraData.toString().removeRange(0, 6)
+        Log.d("SpaEvent-$category", "[$id] $event $extraMsg")
     }
 }
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
index ae325f8..e3e1220 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
@@ -20,6 +20,7 @@
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.ColorDrawable
 import android.graphics.drawable.Drawable
+import android.os.Build
 import android.os.Handler
 import android.os.Looper
 import android.view.View
@@ -117,13 +118,17 @@
         return true
     }
 
-    override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean =
-        drawable.setLayoutDirection(
-            when (layoutDirection) {
-                LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
-                LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
-            }
-        )
+    override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
+        if (Build.VERSION.SDK_INT >= 23) {
+            return drawable.setLayoutDirection(
+                when (layoutDirection) {
+                    LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
+                    LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
+                }
+            )
+        }
+        return false
+    }
 
     override val intrinsicSize: Size get() = drawableIntrinsicSize
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt
deleted file mode 100644
index dbf8836..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * 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 com.android.settingslib.spa.framework.compose
-
-import android.annotation.SuppressLint
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.produceState
-import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.repeatOnLifecycle
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.withContext
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
-
-/**
- * *************************************************************************************************
- * This file was forked from AndroidX:
- * lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/FlowExt.kt
- * TODO: Replace with AndroidX when it's usable.
- */
-
-/**
- * Collects values from this [StateFlow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * The [StateFlow.value] is used as an initial value. Every time there would be new value posted
- * into the [StateFlow] the returned [State] will be updated causing recomposition of every
- * [State.value] usage whenever the [lifecycleOwner]'s lifecycle is at least [minActiveState].
- *
- * This [StateFlow] is collected every time the [lifecycleOwner]'s lifecycle reaches the
- * [minActiveState] Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle
- * falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this`
- * flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@SuppressLint("StateFlowValueCalledInComposition")
-@Composable
-fun <T> StateFlow<T>.collectAsStateWithLifecycle(
-    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
-    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
-    context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
-    initialValue = this.value,
-    lifecycle = lifecycleOwner.lifecycle,
-    minActiveState = minActiveState,
-    context = context
-)
-
-/**
- * Collects values from this [StateFlow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * The [StateFlow.value] is used as an initial value. Every time there would be new value posted
- * into the [StateFlow] the returned [State] will be updated causing recomposition of every
- * [State.value] usage whenever the [lifecycle] is at least [minActiveState].
- *
- * This [StateFlow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle
- * state. The collection stops when [lifecycle] falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param lifecycle [Lifecycle] used to restart collecting `this` flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@SuppressLint("StateFlowValueCalledInComposition")
-@Composable
-fun <T> StateFlow<T>.collectAsStateWithLifecycle(
-    lifecycle: Lifecycle,
-    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
-    context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
-    initialValue = this.value,
-    lifecycle = lifecycle,
-    minActiveState = minActiveState,
-    context = context
-)
-
-/**
- * Collects values from this [Flow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * Every time there would be new value posted into the [Flow] the returned [State] will be updated
- * causing recomposition of every [State.value] usage whenever the [lifecycleOwner]'s lifecycle is
- * at least [minActiveState].
- *
- * This [Flow] is collected every time the [lifecycleOwner]'s lifecycle reaches the [minActiveState]
- * Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle falls below
- * [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param initialValue The initial value given to the returned [State.value].
- * @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this`
- * flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@Composable
-fun <T> Flow<T>.collectAsStateWithLifecycle(
-    initialValue: T,
-    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
-    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
-    context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
-    initialValue = initialValue,
-    lifecycle = lifecycleOwner.lifecycle,
-    minActiveState = minActiveState,
-    context = context
-)
-
-/**
- * Collects values from this [Flow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * Every time there would be new value posted into the [Flow] the returned [State] will be updated
- * causing recomposition of every [State.value] usage whenever the [lifecycle] is at
- * least [minActiveState].
- *
- * This [Flow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle
- * state. The collection stops when [lifecycle] falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param initialValue The initial value given to the returned [State.value].
- * @param lifecycle [Lifecycle] used to restart collecting `this` flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@Composable
-fun <T> Flow<T>.collectAsStateWithLifecycle(
-    initialValue: T,
-    lifecycle: Lifecycle,
-    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
-    context: CoroutineContext = EmptyCoroutineContext
-): State<T> {
-    return produceState(initialValue, this, lifecycle, minActiveState, context) {
-        lifecycle.repeatOnLifecycle(minActiveState) {
-            if (context == EmptyCoroutineContext) {
-                this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
-            } else withContext(context) {
-                this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
-            }
-        }
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt
index 8d0313f..3f7cc19 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.foundation.text.KeyboardActionScope
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.snapshotFlow
@@ -32,7 +31,7 @@
  */
 @OptIn(ExperimentalComposeUiApi::class)
 @Composable
-fun hideKeyboardAction(): KeyboardActionScope.() -> Unit {
+fun hideKeyboardAction(): () -> Unit {
     val keyboardController = LocalSoftwareKeyboardController.current
     return { keyboardController?.hide() }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
index 382c498..eb2bffe 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
@@ -29,7 +29,10 @@
     fun navigateBack()
 
     val highlightEntryId: String?
-      get() = null
+        get() = null
+
+    val sessionSourceName: String?
+        get() = null
 }
 
 @Composable
@@ -63,6 +66,7 @@
     private val onBackPressedDispatcher: OnBackPressedDispatcher?,
 ) : NavControllerWrapper {
     var highlightId: String? = null
+    var sessionName: String? = null
 
     override fun navigate(route: String) {
         navController.navigate(route)
@@ -73,5 +77,8 @@
     }
 
     override val highlightEntryId: String?
-      get() = highlightId
+        get() = highlightId
+
+    override val sessionSourceName: String?
+        get() = sessionName
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
index 4df7794..392089a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
@@ -19,8 +19,6 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.calculateEndPadding
-import androidx.compose.foundation.layout.calculateStartPadding
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyRow
@@ -36,7 +34,6 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
@@ -123,7 +120,7 @@
     contentPadding: PaddingValues = PaddingValues(0.dp),
     horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
     key: ((page: Int) -> Any)? = null,
-    content: @Composable() (PagerScope.(page: Int) -> Unit),
+    content: @Composable PagerScope.(page: Int) -> Unit,
 ) {
     Pager(
         count = count,
@@ -175,24 +172,8 @@
             .collect { state.updateCurrentPageBasedOnLazyListState() }
     }
     val density = LocalDensity.current
-    val layoutDirection = LocalLayoutDirection.current
-    LaunchedEffect(density, contentPadding, isVertical, layoutDirection, reverseLayout, state) {
-        with(density) {
-            // this should be exposed on LazyListLayoutInfo instead. b/200920410
-            state.afterContentPadding = if (isVertical) {
-                if (!reverseLayout) {
-                    contentPadding.calculateBottomPadding()
-                } else {
-                    contentPadding.calculateTopPadding()
-                }
-            } else {
-                if (!reverseLayout) {
-                    contentPadding.calculateEndPadding(layoutDirection)
-                } else {
-                    contentPadding.calculateStartPadding(layoutDirection)
-                }
-            }.roundToPx()
-        }
+    LaunchedEffect(density, state, itemSpacing) {
+        with(density) { state.itemSpacing = itemSpacing.roundToPx() }
     }
 
     val pagerScope = remember(state) { PagerScopeImpl(state) }
@@ -203,6 +184,7 @@
         ConsumeFlingNestedScrollConnection(
             consumeHorizontal = !isVertical,
             consumeVertical = isVertical,
+            pagerState = state,
         )
     }
 
@@ -268,6 +250,7 @@
 private class ConsumeFlingNestedScrollConnection(
     private val consumeHorizontal: Boolean,
     private val consumeVertical: Boolean,
+    private val pagerState: PagerState,
 ) : NestedScrollConnection {
     override fun onPostScroll(
         consumed: Offset,
@@ -281,9 +264,15 @@
     }
 
     override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
-        // We can consume all post fling velocity on the main-axis
-        // so that it doesn't propagate up to the Pager
-        return available.consume(consumeHorizontal, consumeVertical)
+        return if (pagerState.currentPageOffset != 0f) {
+            // The Pager is already scrolling. This means that a nested scroll child was
+            // scrolled to end, and the Pager can use this fling
+            Velocity.Zero
+        } else {
+            // A nested scroll child is still scrolling. We can consume all post fling
+            // velocity on the main-axis so that it doesn't propagate up to the Pager
+            available.consume(consumeHorizontal, consumeVertical)
+        }
     }
 }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
index 21ba117..480335d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
@@ -85,12 +85,14 @@
             return layoutInfo.visibleItemsInfo.maxByOrNull {
                 val start = maxOf(it.offset, 0)
                 val end = minOf(
-                    it.offset + it.size, layoutInfo.viewportEndOffset - afterContentPadding)
+                    it.offset + it.size,
+                    layoutInfo.viewportEndOffset - layoutInfo.afterContentPadding
+                )
                 end - start
             }
         }
 
-    internal var afterContentPadding = 0
+    internal var itemSpacing by mutableStateOf(0)
 
     private val currentPageLayoutInfo: LazyListItemInfo?
         get() = lazyListState.layoutInfo.visibleItemsInfo.lastOrNull {
@@ -135,9 +137,7 @@
      */
     val currentPageOffset: Float by derivedStateOf {
         currentPageLayoutInfo?.let {
-            // We coerce since itemSpacing can make the offset > 1f.
-            // We don't want to count spacing in the offset so cap it to 1f
-            (-it.offset / it.size.toFloat()).coerceIn(-1f, 1f)
+            (-it.offset / (it.size + itemSpacing).toFloat()).coerceIn(-0.5f, 0.5f)
         } ?: 0f
     }
 
@@ -187,28 +187,26 @@
                     // offset from the size
                     lazyListState.animateScrollToItem(
                         index = page,
-                        scrollOffset = (target.size * pageOffset).roundToInt()
+                        scrollOffset = ((target.size + itemSpacing) * pageOffset).roundToInt()
                     )
                 } else if (layoutInfo.visibleItemsInfo.isNotEmpty()) {
                     // If we don't, we use the current page size as a guide
-                    val currentSize = layoutInfo.visibleItemsInfo.first().size
+                    val currentSize = layoutInfo.visibleItemsInfo.first().size + itemSpacing
                     lazyListState.animateScrollToItem(
                         index = page,
                         scrollOffset = (currentSize * pageOffset).roundToInt()
                     )
 
                     // The target should be visible now
-                    target = lazyListState.layoutInfo.visibleItemsInfo.firstOrNull {
-                        it.index == page
-                    }
+                    target = layoutInfo.visibleItemsInfo.firstOrNull { it.index == page }
 
-                    if (target != null && target.size != currentSize) {
+                    if (target != null && target.size + itemSpacing != currentSize) {
                         // If the size we used for calculating the offset differs from the actual
                         // target page size, we need to scroll again. This doesn't look great,
                         // but there's not much else we can do.
                         lazyListState.animateScrollToItem(
                             index = page,
-                            scrollOffset = (target.size * pageOffset).roundToInt()
+                            scrollOffset = ((target.size + itemSpacing) * pageOffset).roundToInt()
                         )
                     }
                 }
@@ -248,7 +246,7 @@
             if (pageOffset.absoluteValue > 0.0001f) {
                 currentPageLayoutInfo?.let {
                     scroll {
-                        scrollBy(it.size * pageOffset)
+                        scrollBy((it.size + itemSpacing) * pageOffset)
                     }
                 }
             }
@@ -295,7 +293,7 @@
     }
 
     private fun requireCurrentPageOffset(value: Float, name: String) {
-        require(value in -1f..1f) { "$name must be >= 0 and <= 1" }
+        require(value in -1f..1f) { "$name must be >= -1 and <= 1" }
     }
 
     companion object {
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
deleted file mode 100644
index 26491d5..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
+++ /dev/null
@@ -1,227 +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.settingslib.spa.framework.debug
-
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import androidx.navigation.NavType
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
-import androidx.navigation.navArgument
-import com.android.settingslib.spa.R
-import com.android.settingslib.spa.framework.common.LogCategory
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsPage
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.compose.localNavController
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.compose.toState
-import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.HomeScaffold
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
-
-private const val TAG = "DebugActivity"
-private const val ROUTE_ROOT = "root"
-private const val ROUTE_All_PAGES = "pages"
-private const val ROUTE_All_ENTRIES = "entries"
-private const val ROUTE_PAGE = "page"
-private const val ROUTE_ENTRY = "entry"
-private const val PARAM_NAME_PAGE_ID = "pid"
-private const val PARAM_NAME_ENTRY_ID = "eid"
-
-/**
- * The Debug Activity to display all Spa Pages & Entries.
- * One can open the debug activity by:
- *   $ adb shell am start -n <Package>/com.android.settingslib.spa.framework.debug.DebugActivity
- * For gallery, Package = com.android.settingslib.spa.gallery
- */
-class DebugActivity : ComponentActivity() {
-    private val spaEnvironment get() = SpaEnvironmentFactory.instance
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        setTheme(R.style.Theme_SpaLib)
-        super.onCreate(savedInstanceState)
-        spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
-
-        setContent {
-            SettingsTheme {
-                MainContent()
-            }
-        }
-    }
-
-    @Composable
-    private fun MainContent() {
-        val navController = rememberNavController()
-        CompositionLocalProvider(navController.localNavController()) {
-            NavHost(navController, ROUTE_ROOT) {
-                composable(route = ROUTE_ROOT) { RootPage() }
-                composable(route = ROUTE_All_PAGES) { AllPages() }
-                composable(route = ROUTE_All_ENTRIES) { AllEntries() }
-                composable(
-                    route = "$ROUTE_PAGE/{$PARAM_NAME_PAGE_ID}",
-                    arguments = listOf(
-                        navArgument(PARAM_NAME_PAGE_ID) { type = NavType.StringType },
-                    )
-                ) { navBackStackEntry -> OnePage(navBackStackEntry.arguments) }
-                composable(
-                    route = "$ROUTE_ENTRY/{$PARAM_NAME_ENTRY_ID}",
-                    arguments = listOf(
-                        navArgument(PARAM_NAME_ENTRY_ID) { type = NavType.StringType },
-                    )
-                ) { navBackStackEntry -> OneEntry(navBackStackEntry.arguments) }
-            }
-        }
-    }
-
-    @Composable
-    fun RootPage() {
-        val entryRepository by spaEnvironment.entryRepository
-        val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() }
-        val allEntry = remember { entryRepository.getAllEntries() }
-        HomeScaffold(title = "Settings Debug") {
-            Preference(object : PreferenceModel {
-                override val title = "List All Pages (${allPageWithEntry.size})"
-                override val onClick = navigator(route = ROUTE_All_PAGES)
-            })
-            Preference(object : PreferenceModel {
-                override val title = "List All Entries (${allEntry.size})"
-                override val onClick = navigator(route = ROUTE_All_ENTRIES)
-            })
-        }
-    }
-
-    @Composable
-    fun AllPages() {
-        val entryRepository by spaEnvironment.entryRepository
-        val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() }
-        RegularScaffold(title = "All Pages (${allPageWithEntry.size})") {
-            for (pageWithEntry in allPageWithEntry) {
-                val page = pageWithEntry.page
-                Preference(object : PreferenceModel {
-                    override val title = "${page.debugBrief()} (${pageWithEntry.entries.size})"
-                    override val summary = page.debugArguments().toState()
-                    override val onClick = navigator(route = ROUTE_PAGE + "/${page.id}")
-                })
-            }
-        }
-    }
-
-    @Composable
-    fun AllEntries() {
-        val entryRepository by spaEnvironment.entryRepository
-        val allEntry = remember { entryRepository.getAllEntries() }
-        RegularScaffold(title = "All Entries (${allEntry.size})") {
-            EntryList(allEntry)
-        }
-    }
-
-    @Composable
-    fun OnePage(arguments: Bundle?) {
-        val context = LocalContext.current
-        val entryRepository by spaEnvironment.entryRepository
-        val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "")
-        val pageWithEntry = entryRepository.getPageWithEntry(id)!!
-        val page = pageWithEntry.page
-        RegularScaffold(title = "Page - ${page.debugBrief()}") {
-            Text(text = "id = ${page.id}")
-            Text(text = page.debugArguments())
-            Text(text = "Entry size: ${pageWithEntry.entries.size}")
-            Preference(model = object : PreferenceModel {
-                override val title = "open page"
-                override val enabled =
-                    page.isBrowsable(context, spaEnvironment.browseActivityClass).toState()
-                override val onClick = openPage(page)
-            })
-            EntryList(pageWithEntry.entries)
-        }
-    }
-
-    @Composable
-    fun OneEntry(arguments: Bundle?) {
-        val context = LocalContext.current
-        val entryRepository by spaEnvironment.entryRepository
-        val id = arguments!!.getString(PARAM_NAME_ENTRY_ID, "")
-        val entry = entryRepository.getEntry(id)!!
-        val entryContent = remember { entry.debugContent(entryRepository) }
-        RegularScaffold(title = "Entry - ${entry.debugBrief()}") {
-            Preference(model = object : PreferenceModel {
-                override val title = "open entry"
-                override val enabled =
-                    entry.containerPage().isBrowsable(context, spaEnvironment.browseActivityClass)
-                        .toState()
-                override val onClick = openEntry(entry)
-            })
-            Text(text = entryContent)
-        }
-    }
-
-    @Composable
-    private fun EntryList(entries: Collection<SettingsEntry>) {
-        for (entry in entries) {
-            Preference(object : PreferenceModel {
-                override val title = entry.debugBrief()
-                override val summary =
-                    "${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState()
-                override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}")
-            })
-        }
-    }
-
-    @Composable
-    private fun openPage(page: SettingsPage): (() -> Unit)? {
-        val context = LocalContext.current
-        val intent =
-            page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: return null
-        val route = page.buildRoute()
-        return {
-            spaEnvironment.logger.message(
-                TAG, "OpenPage: $route", category = LogCategory.FRAMEWORK
-            )
-            context.startActivity(intent)
-        }
-    }
-
-    @Composable
-    private fun openEntry(entry: SettingsEntry): (() -> Unit)? {
-        val context = LocalContext.current
-        val intent = entry.containerPage()
-            .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id)
-            ?: return null
-        val route = entry.containerPage().buildRoute()
-        return {
-            spaEnvironment.logger.message(
-                TAG, "OpenEntry: $route", category = LogCategory.FRAMEWORK
-            )
-            context.startActivity(intent)
-        }
-    }
-}
-
-/**
- * A blank activity without any page.
- */
-class BlankActivity : ComponentActivity()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugFormat.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugFormat.kt
deleted file mode 100644
index 538d2b5..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugFormat.kt
+++ /dev/null
@@ -1,75 +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.settingslib.spa.framework.debug
-
-import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.EntryStatusData
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryRepository
-import com.android.settingslib.spa.framework.common.SettingsPage
-import com.android.settingslib.spa.framework.util.normalize
-
-private fun EntrySearchData.debugContent(): String {
-    val content = listOf(
-        "search_title = $title",
-        "search_keyword = $keyword",
-    )
-    return content.joinToString("\n")
-}
-
-private fun EntryStatusData.debugContent(): String {
-    val content = listOf(
-        "is_disabled = $isDisabled",
-        "is_switch_off = $isSwitchOff",
-    )
-    return content.joinToString("\n")
-}
-
-fun SettingsPage.debugArguments(): String {
-    val normArguments = parameter.normalize(arguments)
-    if (normArguments == null || normArguments.isEmpty) return "[No arguments]"
-    return normArguments.toString().removeRange(0, 6)
-}
-
-fun SettingsPage.debugBrief(): String {
-    return displayName
-}
-
-fun SettingsEntry.debugBrief(): String {
-    return "${owner.displayName}:$displayName"
-}
-
-fun SettingsEntry.debugContent(entryRepository: SettingsEntryRepository): String {
-    val searchData = getSearchData()
-    val statusData = getStatusData()
-    val entryPathWithName = entryRepository.getEntryPathWithDisplayName(id)
-    val entryPathWithTitle = entryRepository.getEntryPathWithTitle(id,
-        searchData?.title ?: displayName)
-    val content = listOf(
-        "------ STATIC ------",
-        "id = $id",
-        "owner = ${owner.debugBrief()} ${owner.debugArguments()}",
-        "linkFrom = ${fromPage?.debugBrief()} ${fromPage?.debugArguments()}",
-        "linkTo = ${toPage?.debugBrief()} ${toPage?.debugArguments()}",
-        "hierarchy_path = $entryPathWithName",
-        "------ SEARCH ------",
-        "search_path = $entryPathWithTitle",
-        searchData?.debugContent() ?: "no search data",
-        statusData?.debugContent() ?: "no status data",
-    )
-    return content.joinToString("\n")
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt
deleted file mode 100644
index 399278d..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt
+++ /dev/null
@@ -1,178 +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.settingslib.spa.framework.debug
-
-import android.content.ContentProvider
-import android.content.ContentValues
-import android.content.Context
-import android.content.Intent
-import android.content.Intent.URI_INTENT_SCHEME
-import android.content.UriMatcher
-import android.content.pm.ProviderInfo
-import android.database.Cursor
-import android.database.MatrixCursor
-import android.net.Uri
-import android.util.Log
-import com.android.settingslib.spa.framework.common.ColumnEnum
-import com.android.settingslib.spa.framework.common.QueryEnum
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.addUri
-import com.android.settingslib.spa.framework.common.getColumns
-
-private const val TAG = "DebugProvider"
-
-/**
- * The content provider to return debug data.
- * One can query the provider result by:
- *   $ adb shell content query --uri content://<AuthorityPath>/<QueryPath>
- * For gallery, AuthorityPath = com.android.spa.gallery.debug
- * Some examples:
- *   $ adb shell content query --uri content://<AuthorityPath>/page_debug
- *   $ adb shell content query --uri content://<AuthorityPath>/entry_debug
- *   $ adb shell content query --uri content://<AuthorityPath>/page_info
- *   $ adb shell content query --uri content://<AuthorityPath>/entry_info
- */
-class DebugProvider : ContentProvider() {
-    private val spaEnvironment get() = SpaEnvironmentFactory.instance
-    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
-
-    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
-        TODO("Implement this to handle requests to delete one or more rows")
-    }
-
-    override fun getType(uri: Uri): String? {
-        TODO(
-            "Implement this to handle requests for the MIME type of the data" +
-                "at the given URI"
-        )
-    }
-
-    override fun insert(uri: Uri, values: ContentValues?): Uri? {
-        TODO("Implement this to handle requests to insert a new row.")
-    }
-
-    override fun update(
-        uri: Uri,
-        values: ContentValues?,
-        selection: String?,
-        selectionArgs: Array<String>?
-    ): Int {
-        TODO("Implement this to handle requests to update one or more rows.")
-    }
-
-    override fun onCreate(): Boolean {
-        Log.d(TAG, "onCreate")
-        return true
-    }
-
-    override fun attachInfo(context: Context?, info: ProviderInfo?) {
-        if (info != null) {
-            QueryEnum.PAGE_DEBUG_QUERY.addUri(uriMatcher, info.authority)
-            QueryEnum.ENTRY_DEBUG_QUERY.addUri(uriMatcher, info.authority)
-            QueryEnum.PAGE_INFO_QUERY.addUri(uriMatcher, info.authority)
-            QueryEnum.ENTRY_INFO_QUERY.addUri(uriMatcher, info.authority)
-        }
-        super.attachInfo(context, info)
-    }
-
-    override fun query(
-        uri: Uri,
-        projection: Array<String>?,
-        selection: String?,
-        selectionArgs: Array<String>?,
-        sortOrder: String?
-    ): Cursor? {
-        return try {
-            when (uriMatcher.match(uri)) {
-                QueryEnum.PAGE_DEBUG_QUERY.queryMatchCode -> queryPageDebug()
-                QueryEnum.ENTRY_DEBUG_QUERY.queryMatchCode -> queryEntryDebug()
-                QueryEnum.PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo()
-                QueryEnum.ENTRY_INFO_QUERY.queryMatchCode -> queryEntryInfo()
-                else -> throw UnsupportedOperationException("Unknown Uri $uri")
-            }
-        } catch (e: UnsupportedOperationException) {
-            throw e
-        } catch (e: Exception) {
-            Log.e(TAG, "Provider querying exception:", e)
-            null
-        }
-    }
-
-    private fun queryPageDebug(): Cursor {
-        val entryRepository by spaEnvironment.entryRepository
-        val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns())
-        for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
-            val command = pageWithEntry.page.createBrowseAdbCommand(
-                context,
-                spaEnvironment.browseActivityClass
-            )
-            if (command != null) {
-                cursor.newRow().add(ColumnEnum.PAGE_START_ADB.id, command)
-            }
-        }
-        return cursor
-    }
-
-    private fun queryEntryDebug(): Cursor {
-        val entryRepository by spaEnvironment.entryRepository
-        val cursor = MatrixCursor(QueryEnum.ENTRY_DEBUG_QUERY.getColumns())
-        for (entry in entryRepository.getAllEntries()) {
-            val command = entry.containerPage()
-                .createBrowseAdbCommand(context, spaEnvironment.browseActivityClass, entry.id)
-            if (command != null) {
-                cursor.newRow().add(ColumnEnum.ENTRY_START_ADB.id, command)
-            }
-        }
-        return cursor
-    }
-
-    private fun queryPageInfo(): Cursor {
-        val entryRepository by spaEnvironment.entryRepository
-        val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns())
-        for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
-            val page = pageWithEntry.page
-            val intent =
-                page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: Intent()
-            cursor.newRow()
-                .add(ColumnEnum.PAGE_ID.id, page.id)
-                .add(ColumnEnum.PAGE_NAME.id, page.displayName)
-                .add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute())
-                .add(ColumnEnum.PAGE_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
-                .add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size)
-                .add(ColumnEnum.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0)
-        }
-        return cursor
-    }
-
-    private fun queryEntryInfo(): Cursor {
-        val entryRepository by spaEnvironment.entryRepository
-        val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns())
-        for (entry in entryRepository.getAllEntries()) {
-            val intent = entry.containerPage()
-                .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id)
-                ?: Intent()
-            cursor.newRow()
-                .add(ColumnEnum.ENTRY_ID.id, entry.id)
-                .add(ColumnEnum.ENTRY_NAME.id, entry.displayName)
-                .add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute())
-                .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
-                .add(ColumnEnum.ENTRY_HIERARCHY_PATH.id,
-                    entryRepository.getEntryPathWithDisplayName(entry.id))
-        }
-        return cursor
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index c2223e6..7962e60 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -40,6 +40,9 @@
     /** The size when app icon is displayed in App info page. */
     val appIconInfoSize = 48.dp
 
+    /** The [PaddingValues] for buttons. */
+    val buttonPadding = PaddingValues(horizontal = itemPaddingEnd, vertical = 12.dp)
+
     /** The sizes info of illustration widget. */
     val illustrationMaxWidth = 412.dp
     val illustrationMaxHeight = 300.dp
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/framework/theme/SettingsTypography.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
index 03699bf..5eaa495 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
@@ -142,3 +142,7 @@
     val settingsFontFamily = rememberSettingsFontFamily()
     return remember { SettingsTypography(settingsFontFamily).typography }
 }
+
+/** Creates a new [TextStyle] which font weight set to medium. */
+internal fun TextStyle.toMediumWeight() =
+    copy(fontWeight = FontWeight.Medium, letterSpacing = 0.01.em)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
new file mode 100644
index 0000000..90c44b5
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.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.settingslib.spa.framework.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 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 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/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
new file mode 100644
index 0000000..8ff4368
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.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.settingslib.spa.framework.util
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.core.os.bundleOf
+import com.android.settingslib.spa.framework.common.LOG_DATA_SWITCH_STATUS
+import com.android.settingslib.spa.framework.common.LocalEntryDataProvider
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.LogEvent
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+
+@Composable
+fun logEntryEvent(): (event: LogEvent, extraData: Bundle) -> Unit {
+    val entryId = LocalEntryDataProvider.current.entryId ?: return { _, _ -> }
+    val arguments = LocalEntryDataProvider.current.arguments
+    return { event, extraData ->
+        SpaEnvironmentFactory.instance.logger.event(
+            entryId, event, category = LogCategory.VIEW, extraData = extraData.apply {
+                if (arguments != null) putAll(arguments)
+            }
+        )
+    }
+}
+
+@Composable
+fun wrapOnClickWithLog(onClick: (() -> Unit)?): (() -> Unit)? {
+    if (onClick == null) return null
+    val logEvent = logEntryEvent()
+    return {
+        logEvent(LogEvent.ENTRY_CLICK, bundleOf())
+        onClick()
+    }
+}
+
+@Composable
+fun wrapOnSwitchWithLog(onSwitch: ((checked: Boolean) -> Unit)?): ((checked: Boolean) -> Unit)? {
+    if (onSwitch == null) return null
+    val logEvent = logEntryEvent()
+    return {
+        logEvent(LogEvent.ENTRY_SWITCH, bundleOf(LOG_DATA_SWITCH_STATUS to it))
+        onSwitch(it)
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
index d801840..8bfcff8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
@@ -16,15 +16,17 @@
 
 package com.android.settingslib.spa.framework.util
 
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.State
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChangedBy
-import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.take
+
+/**
+ * Returns a [Flow] whose values are a list which containing the results of applying the given
+ * [transform] function to each element in the original flow's list.
+ */
+inline fun <T, R> Flow<List<T>>.mapItem(crossinline transform: (T) -> R): Flow<List<R>> =
+    map { list -> list.map(transform) }
 
 /**
  * Returns a [Flow] whose values are a list which containing the results of asynchronously applying
@@ -34,33 +36,13 @@
     map { list -> list.asyncMap(transform) }
 
 /**
+ * Returns a [Flow] whose values are a list containing only elements matching the given [predicate].
+ */
+inline fun <T> Flow<List<T>>.filterItem(crossinline predicate: (T) -> Boolean): Flow<List<T>> =
+    map { list -> list.filter(predicate) }
+
+/**
  * Delays the flow a little bit, wait the other flow's first value.
  */
 fun <T1, T2> Flow<T1>.waitFirst(otherFlow: Flow<T2>): Flow<T1> =
-    combine(otherFlow.distinctUntilChangedBy {}) { value, _ -> value }
-
-/**
- * Returns a [Flow] whose values are generated list by combining the most recently emitted non null
- * values by each flow.
- */
-inline fun <reified T : Any> combineToList(vararg flows: Flow<T?>): Flow<List<T>> = combine(
-    flows.asList(),
-) { array: Array<T?> -> array.filterNotNull() }
-
-class StateFlowBridge<T> {
-    private val stateFlow = MutableStateFlow<T?>(null)
-    val flow = stateFlow.filterNotNull()
-
-    fun setIfAbsent(value: T) {
-        if (stateFlow.value == null) {
-            stateFlow.value = value
-        }
-    }
-
-    @Composable
-    fun Sync(state: State<T>) {
-        LaunchedEffect(state.value) {
-            stateFlow.value = state.value
-        }
-    }
-}
+    combine(otherFlow.take(1)) { value, _ -> value }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/MessageFormats.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/MessageFormats.kt
new file mode 100644
index 0000000..2adfcca
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/MessageFormats.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.settingslib.spa.framework.util
+
+import android.content.Context
+import android.content.res.Resources
+import android.icu.text.MessageFormat
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.annotation.StringRes
+import java.util.Locale
+
+@RequiresApi(Build.VERSION_CODES.N)
+fun Context.formatString(@StringRes resId: Int, vararg arguments: Pair<String, Any>): String =
+    resources.formatString(resId, *arguments)
+
+@RequiresApi(Build.VERSION_CODES.N)
+fun Resources.formatString(@StringRes resId: Int, vararg arguments: Pair<String, Any>): String =
+    MessageFormat(getString(resId), Locale.getDefault(Locale.Category.FORMAT))
+        .format(mapOf(*arguments))
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
new file mode 100644
index 0000000..271443e
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -0,0 +1,72 @@
+/*
+ * 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 android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.core.os.bundleOf
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import com.android.settingslib.spa.framework.common.LOG_DATA_DISPLAY_NAME
+import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.LogEvent
+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.compose.LocalNavController
+
+@Composable
+internal fun SettingsPageProvider.PageEvent(arguments: Bundle? = null) {
+    val page = remember(arguments) { createSettingsPage(arguments) }
+    val lifecycleOwner = LocalLifecycleOwner.current
+    val navController = LocalNavController.current
+    DisposableEffect(lifecycleOwner) {
+        val observer = LifecycleEventObserver { _, event ->
+            val logPageEvent: (event: LogEvent) -> Unit = {
+                SpaEnvironmentFactory.instance.logger.event(
+                    id = page.id,
+                    event = it,
+                    category = LogCategory.FRAMEWORK,
+                    extraData = bundleOf(
+                        LOG_DATA_DISPLAY_NAME to page.displayName,
+                        LOG_DATA_SESSION_NAME to navController.sessionSourceName,
+                    ).apply {
+                        val normArguments = parameter.normalize(arguments)
+                        if (normArguments != null) putAll(normArguments)
+                    }
+                )
+            }
+            if (event == Lifecycle.Event.ON_START) {
+                logPageEvent(LogEvent.PAGE_ENTER)
+            } else if (event == Lifecycle.Event.ON_STOP) {
+                logPageEvent(LogEvent.PAGE_LEAVE)
+            }
+        }
+
+        // Add the observer to the lifecycle
+        lifecycleOwner.lifecycle.addObserver(observer)
+
+        // When the effect leaves the Composition, remove the observer
+        onDispose {
+            lifecycleOwner.lifecycle.removeObserver(observer)
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
index f10d3b0..be303f0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
@@ -47,12 +47,15 @@
     return argsArray.joinToString("") { arg -> "/$arg" }
 }
 
-fun List<NamedNavArgument>.normalize(arguments: Bundle? = null): Bundle? {
+fun List<NamedNavArgument>.normalize(
+    arguments: Bundle? = null,
+    eraseRuntimeValues: Boolean = false
+): Bundle? {
     if (this.isEmpty()) return null
     val normArgs = Bundle()
     for (navArg in this) {
         // Erase value of runtime parameters.
-        if (navArg.isRuntimeParam()) {
+        if (navArg.isRuntimeParam() && eraseRuntimeValues) {
             normArgs.putString(navArg.name, null)
             continue
         }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
new file mode 100644
index 0000000..2c3c2e0
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
@@ -0,0 +1,85 @@
+/*
+ * 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 android.content.ComponentName
+import android.content.Intent
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+
+const val SESSION_BROWSE = "browse"
+const val SESSION_SEARCH = "search"
+const val SESSION_SLICE = "slice"
+
+const val KEY_DESTINATION = "spaActivityDestination"
+const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
+const val KEY_SESSION_SOURCE_NAME = "sessionSource"
+
+val SPA_INTENT_RESERVED_KEYS = listOf(
+    KEY_DESTINATION,
+    KEY_HIGHLIGHT_ENTRY,
+    KEY_SESSION_SOURCE_NAME
+)
+
+private fun createBaseIntent(): Intent? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+    return Intent().setComponent(ComponentName(context, browseActivityClass))
+}
+
+fun SettingsPage.createIntent(sessionName: String? = null): Intent? {
+    if (!isBrowsable()) return null
+    return createBaseIntent()?.appendSpaParams(
+        destination = buildRoute(),
+        sessionName = sessionName
+    )
+}
+
+fun SettingsEntry.createIntent(sessionName: String? = null): Intent? {
+    val sp = containerPage()
+    if (!sp.isBrowsable()) return null
+    return createBaseIntent()?.appendSpaParams(
+        destination = sp.buildRoute(),
+        entryId = id,
+        sessionName = sessionName
+    )
+}
+
+fun Intent.appendSpaParams(
+    destination: String? = null,
+    entryId: String? = null,
+    sessionName: String? = null
+): Intent {
+    return apply {
+        if (destination != null) putExtra(KEY_DESTINATION, destination)
+        if (entryId != null) putExtra(KEY_HIGHLIGHT_ENTRY, entryId)
+        if (sessionName != null) putExtra(KEY_SESSION_SOURCE_NAME, sessionName)
+    }
+}
+
+fun Intent.getDestination(): String? {
+    return getStringExtra(KEY_DESTINATION)
+}
+
+fun Intent.getEntryId(): String? {
+    return getStringExtra(KEY_HIGHLIGHT_ENTRY)
+}
+
+fun Intent.getSessionName(): String? {
+    return getStringExtra(KEY_SESSION_SOURCE_NAME)
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/StateFlowBridge.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/StateFlowBridge.kt
new file mode 100644
index 0000000..494e69b
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/StateFlowBridge.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+/** A StateFlow holder which value could be set or sync from [State]. */
+class StateFlowBridge<T> {
+    private val stateFlow = MutableStateFlow<T?>(null)
+    val flow = stateFlow.filterNotNull()
+
+    fun setIfAbsent(value: T) {
+        if (stateFlow.value == null) {
+            stateFlow.value = value
+        }
+    }
+
+    @Composable
+    fun Sync(state: State<T>) {
+        LaunchedEffect(state.value) {
+            stateFlow.value = state.value
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt
deleted file mode 100644
index 8d0a35c..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt
+++ /dev/null
@@ -1,52 +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.settingslib.spa.framework.util
-
-import androidx.compose.runtime.Composable
-import com.android.settingslib.spa.framework.common.LocalEntryDataProvider
-import com.android.settingslib.spa.framework.common.LogCategory
-import com.android.settingslib.spa.framework.common.LogEvent
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-
-@Composable
-fun logEntryEvent(): (event: LogEvent) -> Unit {
-    val entryId = LocalEntryDataProvider.current.entryId ?: return {}
-    return {
-        SpaEnvironmentFactory.instance.logger.event(entryId, it, category = LogCategory.VIEW)
-    }
-}
-
-@Composable
-fun wrapOnClickWithLog(onClick: (() -> Unit)?): (() -> Unit)? {
-    if (onClick == null) return null
-    val logEvent = logEntryEvent()
-    return {
-        logEvent(LogEvent.ENTRY_CLICK)
-        onClick()
-    }
-}
-
-@Composable
-fun wrapOnSwitchWithLog(onSwitch: ((checked: Boolean) -> Unit)?): ((checked: Boolean) -> Unit)? {
-    if (onSwitch == null) return null
-    val logEvent = logEntryEvent()
-    return {
-        val event = if (it) LogEvent.ENTRY_SWITCH_ON else LogEvent.ENTRY_SWITCH_OFF
-        logEvent(event)
-        onSwitch(it)
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
new file mode 100644
index 0000000..2301f04
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.search
+
+/**
+ * Intent action used to identify SpaSearchProvider instances. This is used in the {@code
+ * <intent-filter>} of a {@code <provider>}.
+ */
+const val PROVIDER_INTERFACE = "android.content.action.SPA_SEARCH_PROVIDER"
+
+/** ContentProvider path for search static data */
+const val SEARCH_STATIC_DATA = "search_static_data"
+
+/** ContentProvider path for search dynamic data */
+const val SEARCH_DYNAMIC_DATA = "search_dynamic_data"
+
+/** ContentProvider path for search immutable status */
+const val SEARCH_IMMUTABLE_STATUS = "search_immutable_status"
+
+/** ContentProvider path for search mutable status */
+const val SEARCH_MUTABLE_STATUS = "search_mutable_status"
+
+/** Enum to define all column names in provider. */
+enum class ColumnEnum(val id: String) {
+    ENTRY_ID("entryId"),
+    SEARCH_TITLE("searchTitle"),
+    SEARCH_KEYWORD("searchKw"),
+    SEARCH_PATH("searchPath"),
+    INTENT_TARGET_PACKAGE("intentTargetPackage"),
+    INTENT_TARGET_CLASS("intentTargetClass"),
+    INTENT_EXTRAS("intentExtras"),
+    SLICE_URI("sliceUri"),
+    LEGACY_KEY("legacyKey"),
+    ENTRY_DISABLED("entryDisabled"),
+}
+
+/** Enum to define all queries supported in the provider. */
+@SuppressWarnings("Immutable")
+enum class QueryEnum(
+    val queryPath: String,
+    val columnNames: List<ColumnEnum>
+) {
+    SEARCH_STATIC_DATA_QUERY(
+        SEARCH_STATIC_DATA,
+        listOf(
+            ColumnEnum.ENTRY_ID,
+            ColumnEnum.SEARCH_TITLE,
+            ColumnEnum.SEARCH_KEYWORD,
+            ColumnEnum.SEARCH_PATH,
+            ColumnEnum.INTENT_TARGET_PACKAGE,
+            ColumnEnum.INTENT_TARGET_CLASS,
+            ColumnEnum.INTENT_EXTRAS,
+            ColumnEnum.SLICE_URI,
+            ColumnEnum.LEGACY_KEY
+        )
+    ),
+    SEARCH_DYNAMIC_DATA_QUERY(
+        SEARCH_DYNAMIC_DATA,
+        listOf(
+            ColumnEnum.ENTRY_ID,
+            ColumnEnum.SEARCH_TITLE,
+            ColumnEnum.SEARCH_KEYWORD,
+            ColumnEnum.SEARCH_PATH,
+            ColumnEnum.INTENT_TARGET_PACKAGE,
+            ColumnEnum.INTENT_TARGET_CLASS,
+            ColumnEnum.SLICE_URI,
+            ColumnEnum.LEGACY_KEY
+        )
+    ),
+    SEARCH_IMMUTABLE_STATUS_DATA_QUERY(
+        SEARCH_IMMUTABLE_STATUS,
+        listOf(
+            ColumnEnum.ENTRY_ID,
+            ColumnEnum.ENTRY_DISABLED,
+        )
+    ),
+    SEARCH_MUTABLE_STATUS_DATA_QUERY(
+        SEARCH_MUTABLE_STATUS,
+        listOf(
+            ColumnEnum.ENTRY_ID,
+            ColumnEnum.ENTRY_DISABLED,
+        )
+    ),
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
new file mode 100644
index 0000000..21bc75a
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.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.search
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.content.UriMatcher
+import android.content.pm.ProviderInfo
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.net.Uri
+import android.os.Parcel
+import android.os.Parcelable
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.settingslib.spa.framework.common.EntryStatusData
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
+import com.android.settingslib.spa.slice.fromEntry
+
+
+private const val TAG = "SpaSearchProvider"
+
+/**
+ * The content provider to return entry related data, which can be used for search and hierarchy.
+ * One can query the provider result by:
+ *   $ adb shell content query --uri content://<AuthorityPath>/<QueryPath>
+ * For gallery, AuthorityPath = com.android.spa.gallery.search.provider
+ * For Settings, AuthorityPath = com.android.settings.spa.search.provider"
+ * Some examples:
+ *   $ adb shell content query --uri content://<AuthorityPath>/search_static_data
+ *   $ adb shell content query --uri content://<AuthorityPath>/search_dynamic_data
+ *   $ adb shell content query --uri content://<AuthorityPath>/search_immutable_status
+ *   $ adb shell content query --uri content://<AuthorityPath>/search_mutable_status
+ */
+class SpaSearchProvider : ContentProvider() {
+    private val spaEnvironment get() = SpaEnvironmentFactory.instance
+    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
+
+    private val queryMatchCode = mapOf(
+        SEARCH_STATIC_DATA to 301,
+        SEARCH_DYNAMIC_DATA to 302,
+        SEARCH_MUTABLE_STATUS to 303,
+        SEARCH_IMMUTABLE_STATUS to 304
+    )
+
+    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
+        TODO("Implement this to handle requests to delete one or more rows")
+    }
+
+    override fun getType(uri: Uri): String? {
+        TODO(
+            "Implement this to handle requests for the MIME type of the data" +
+                "at the given URI"
+        )
+    }
+
+    override fun insert(uri: Uri, values: ContentValues?): Uri? {
+        TODO("Implement this to handle requests to insert a new row.")
+    }
+
+    override fun update(
+        uri: Uri,
+        values: ContentValues?,
+        selection: String?,
+        selectionArgs: Array<String>?
+    ): Int {
+        TODO("Implement this to handle requests to update one or more rows.")
+    }
+
+    override fun onCreate(): Boolean {
+        Log.d(TAG, "onCreate")
+        return true
+    }
+
+    override fun attachInfo(context: Context?, info: ProviderInfo?) {
+        if (info != null) {
+            for (entry in queryMatchCode) {
+                uriMatcher.addURI(info.authority, entry.key, entry.value)
+            }
+        }
+        super.attachInfo(context, info)
+    }
+
+    override fun query(
+        uri: Uri,
+        projection: Array<String>?,
+        selection: String?,
+        selectionArgs: Array<String>?,
+        sortOrder: String?
+    ): Cursor? {
+        return try {
+            when (uriMatcher.match(uri)) {
+                queryMatchCode[SEARCH_STATIC_DATA] -> querySearchStaticData()
+                queryMatchCode[SEARCH_DYNAMIC_DATA] -> querySearchDynamicData()
+                queryMatchCode[SEARCH_MUTABLE_STATUS] ->
+                    querySearchMutableStatusData()
+                queryMatchCode[SEARCH_IMMUTABLE_STATUS] ->
+                    querySearchImmutableStatusData()
+                else -> throw UnsupportedOperationException("Unknown Uri $uri")
+            }
+        } catch (e: UnsupportedOperationException) {
+            throw e
+        } catch (e: Exception) {
+            Log.e(TAG, "Provider querying exception:", e)
+            null
+        }
+    }
+
+    @VisibleForTesting
+    fun querySearchImmutableStatusData(): Cursor {
+        val entryRepository by spaEnvironment.entryRepository
+        val cursor = MatrixCursor(QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.getColumns())
+        for (entry in entryRepository.getAllEntries()) {
+            if (!entry.isAllowSearch || entry.hasMutableStatus) continue
+            fetchStatusData(entry, cursor)
+        }
+        return cursor
+    }
+
+    @VisibleForTesting
+    fun querySearchMutableStatusData(): Cursor {
+        val entryRepository by spaEnvironment.entryRepository
+        val cursor = MatrixCursor(QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.getColumns())
+        for (entry in entryRepository.getAllEntries()) {
+            if (!entry.isAllowSearch || !entry.hasMutableStatus) continue
+            fetchStatusData(entry, cursor)
+        }
+        return cursor
+    }
+
+    @VisibleForTesting
+    fun querySearchStaticData(): Cursor {
+        val entryRepository by spaEnvironment.entryRepository
+        val cursor = MatrixCursor(QueryEnum.SEARCH_STATIC_DATA_QUERY.getColumns())
+        for (entry in entryRepository.getAllEntries()) {
+            if (!entry.isAllowSearch || entry.isSearchDataDynamic) continue
+            fetchSearchData(entry, cursor)
+        }
+        return cursor
+    }
+
+    @VisibleForTesting
+    fun querySearchDynamicData(): Cursor {
+        val entryRepository by spaEnvironment.entryRepository
+        val cursor = MatrixCursor(QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.getColumns())
+        for (entry in entryRepository.getAllEntries()) {
+            if (!entry.isAllowSearch || !entry.isSearchDataDynamic) continue
+            fetchSearchData(entry, cursor)
+        }
+        return cursor
+    }
+
+    private fun fetchSearchData(entry: SettingsEntry, cursor: MatrixCursor) {
+        val entryRepository by spaEnvironment.entryRepository
+
+        // Fetch search data. We can add runtime arguments later if necessary
+        val searchData = entry.getSearchData() ?: return
+        val intent = entry.createIntent(SESSION_SEARCH)
+        val row = cursor.newRow().add(ColumnEnum.ENTRY_ID.id, entry.id)
+            .add(ColumnEnum.SEARCH_TITLE.id, searchData.title)
+            .add(ColumnEnum.SEARCH_KEYWORD.id, searchData.keyword)
+            .add(
+                ColumnEnum.SEARCH_PATH.id,
+                entryRepository.getEntryPathWithTitle(entry.id, searchData.title)
+            )
+        intent?.let {
+            row.add(ColumnEnum.INTENT_TARGET_PACKAGE.id, spaEnvironment.appContext.packageName)
+                .add(ColumnEnum.INTENT_TARGET_CLASS.id, spaEnvironment.browseActivityClass?.name)
+                .add(ColumnEnum.INTENT_EXTRAS.id, marshall(intent.extras))
+        }
+        if (entry.hasSliceSupport)
+            row.add(
+                ColumnEnum.SLICE_URI.id, Uri.Builder()
+                    .fromEntry(entry, spaEnvironment.sliceProviderAuthorities)
+            )
+        // TODO: support legacy key
+    }
+
+    private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) {
+        // Fetch status data. We can add runtime arguments later if necessary
+        val statusData = entry.getStatusData() ?: EntryStatusData()
+        cursor.newRow()
+            .add(ColumnEnum.ENTRY_ID.id, entry.id)
+            .add(ColumnEnum.ENTRY_DISABLED.id, statusData.isDisabled)
+    }
+
+    private fun QueryEnum.getColumns(): Array<String> {
+        return columnNames.map { it.id }.toTypedArray()
+    }
+
+    private fun marshall(parcelable: Parcelable?): ByteArray? {
+        if (parcelable == null) return null
+        val parcel = Parcel.obtain()
+        parcelable.writeToParcel(parcel, 0)
+        val bytes = parcel.marshall()
+        parcel.recycle()
+        return bytes
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
new file mode 100644
index 0000000..7a4750d
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.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.settingslib.spa.slice
+
+import android.net.Uri
+import android.util.Log
+import com.android.settingslib.spa.framework.common.EntrySliceData
+import com.android.settingslib.spa.framework.common.SettingsEntryRepository
+import com.android.settingslib.spa.framework.util.getEntryId
+
+private const val TAG = "SliceDataRepository"
+
+class SettingsSliceDataRepository(private val entryRepository: SettingsEntryRepository) {
+    // The map of slice uri to its EntrySliceData, a.k.a. LiveData<Slice?>
+    private val sliceDataMap: MutableMap<String, EntrySliceData> = mutableMapOf()
+
+    // Note: mark this function synchronized, so that we can get the same livedata during the
+    // whole lifecycle of a Slice.
+    @Synchronized
+    fun getOrBuildSliceData(sliceUri: Uri): EntrySliceData? {
+        val sliceString = sliceUri.getSliceId() ?: return null
+        return sliceDataMap[sliceString] ?: buildLiveDataImpl(sliceUri)?.let {
+            sliceDataMap[sliceString] = it
+            it
+        }
+    }
+
+    fun getActiveSliceData(sliceUri: Uri): EntrySliceData? {
+        val sliceString = sliceUri.getSliceId() ?: return null
+        val sliceData = sliceDataMap[sliceString] ?: return null
+        return if (sliceData.isActive()) sliceData else null
+    }
+
+    private fun buildLiveDataImpl(sliceUri: Uri): EntrySliceData? {
+        Log.d(TAG, "buildLiveData: $sliceUri")
+
+        val entryId = sliceUri.getEntryId() ?: return null
+        val entry = entryRepository.getEntry(entryId) ?: return null
+        if (!entry.hasSliceSupport) return null
+        val arguments = sliceUri.getRuntimeArguments()
+        return entry.getSliceData(runtimeArguments = arguments, sliceUri = sliceUri)
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
new file mode 100644
index 0000000..f362890
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
@@ -0,0 +1,150 @@
+/*
+ * 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.slice
+
+import android.app.Activity
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.util.KEY_DESTINATION
+import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.util.SESSION_SLICE
+import com.android.settingslib.spa.framework.util.SPA_INTENT_RESERVED_KEYS
+import com.android.settingslib.spa.framework.util.appendSpaParams
+import com.android.settingslib.spa.framework.util.getDestination
+import com.android.settingslib.spa.framework.util.getEntryId
+
+// Defines SliceUri, which contains special query parameters:
+//  -- KEY_DESTINATION: The route that this slice is navigated to.
+//  -- KEY_HIGHLIGHT_ENTRY: The entry id of this slice
+//  Other parameters can considered as runtime parameters.
+// Use {entryId, runtimeParams} as the unique Id of this Slice.
+typealias SliceUri = Uri
+
+fun SliceUri.getEntryId(): String? {
+    return getQueryParameter(KEY_HIGHLIGHT_ENTRY)
+}
+
+fun SliceUri.getDestination(): String? {
+    return getQueryParameter(KEY_DESTINATION)
+}
+
+fun SliceUri.getRuntimeArguments(): Bundle {
+    val params = Bundle()
+    for (queryName in queryParameterNames) {
+        if (SPA_INTENT_RESERVED_KEYS.contains(queryName)) continue
+        params.putString(queryName, getQueryParameter(queryName))
+    }
+    return params
+}
+
+fun SliceUri.getSliceId(): String? {
+    val entryId = getEntryId() ?: return null
+    val params = getRuntimeArguments()
+    return "${entryId}_$params"
+}
+
+fun Uri.Builder.appendSpaParams(
+    destination: String? = null,
+    entryId: String? = null,
+    runtimeArguments: Bundle? = null
+): Uri.Builder {
+    if (destination != null) appendQueryParameter(KEY_DESTINATION, destination)
+    if (entryId != null) appendQueryParameter(KEY_HIGHLIGHT_ENTRY, entryId)
+    if (runtimeArguments != null) {
+        for (key in runtimeArguments.keySet()) {
+            appendQueryParameter(key, runtimeArguments.getString(key, ""))
+        }
+    }
+    return this
+}
+
+fun Uri.Builder.fromEntry(
+    entry: SettingsEntry,
+    authority: String?,
+    runtimeArguments: Bundle? = null
+): Uri.Builder {
+    if (authority == null) return this
+    val sp = entry.containerPage()
+    return scheme("content").authority(authority).appendSpaParams(
+        destination = sp.buildRoute(),
+        entryId = entry.id,
+        runtimeArguments = runtimeArguments
+    )
+}
+
+fun SliceUri.createBroadcastPendingIntent(): PendingIntent? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    val sliceBroadcastClass =
+        SpaEnvironmentFactory.instance.sliceBroadcastReceiverClass ?: return null
+    val entryId = getEntryId() ?: return null
+    return createBroadcastPendingIntent(context, sliceBroadcastClass, entryId)
+}
+
+fun SliceUri.createBrowsePendingIntent(): PendingIntent? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+    val destination = getDestination() ?: return null
+    val entryId = getEntryId()
+    return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
+}
+
+fun Intent.createBrowsePendingIntent(): PendingIntent? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+    val destination = getDestination() ?: return null
+    val entryId = getEntryId()
+    return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
+}
+
+private fun createBrowsePendingIntent(
+    context: Context,
+    browseActivityClass: Class<out Activity>,
+    destination: String,
+    entryId: String?
+): PendingIntent {
+    val intent = Intent().setComponent(ComponentName(context, browseActivityClass))
+        .appendSpaParams(destination, entryId, SESSION_SLICE)
+        .apply {
+            // Set both extra and data (which is a Uri) in Slice Intent:
+            // 1) extra is used in SPA navigation framework
+            // 2) data is used in Slice framework
+            data = Uri.Builder().appendSpaParams(destination, entryId).build()
+            flags = Intent.FLAG_ACTIVITY_NEW_TASK
+        }
+
+    return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+}
+
+private fun createBroadcastPendingIntent(
+    context: Context,
+    sliceBroadcastClass: Class<out BroadcastReceiver>,
+    entryId: String
+): PendingIntent {
+    val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass))
+        .apply { data = Uri.Builder().appendSpaParams(entryId = entryId).build() }
+    return PendingIntent.getBroadcast(
+        context, 0 /* requestCode */, intent,
+        PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
+    )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
new file mode 100644
index 0000000..39cb431
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.slice
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+
+class SpaSliceBroadcastReceiver : BroadcastReceiver() {
+    override fun onReceive(context: Context?, intent: Intent?) {
+        val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
+        val sliceUri = intent?.data ?: return
+        val sliceData = sliceRepository.getActiveSliceData(sliceUri) ?: return
+        sliceData.doAction()
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
new file mode 100644
index 0000000..b809c0f
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.slice
+
+import android.net.Uri
+import android.util.Log
+import androidx.lifecycle.Observer
+import androidx.slice.Slice
+import androidx.slice.SliceProvider
+import com.android.settingslib.spa.framework.common.EntrySliceData
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+
+private const val TAG = "SpaSliceProvider"
+
+class SpaSliceProvider : SliceProvider(), Observer<Slice?> {
+    private fun getOrPutSliceData(sliceUri: Uri): EntrySliceData? {
+        if (!SpaEnvironmentFactory.isReady()) return null
+        val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
+        return sliceRepository.getOrBuildSliceData(sliceUri)
+    }
+
+    override fun onBindSlice(sliceUri: Uri): Slice? {
+        if (context == null) return null
+        Log.d(TAG, "onBindSlice: $sliceUri")
+        return getOrPutSliceData(sliceUri)?.value
+    }
+
+    override fun onSlicePinned(sliceUri: Uri) {
+        Log.d(TAG, "onSlicePinned: $sliceUri")
+        super.onSlicePinned(sliceUri)
+        val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
+        runBlocking {
+            withContext(Dispatchers.Main) {
+                sliceLiveData.observeForever(this@SpaSliceProvider)
+            }
+        }
+    }
+
+    override fun onSliceUnpinned(sliceUri: Uri) {
+        Log.d(TAG, "onSliceUnpinned: $sliceUri")
+        super.onSliceUnpinned(sliceUri)
+        val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
+        runBlocking {
+            withContext(Dispatchers.Main) {
+                sliceLiveData.removeObserver(this@SpaSliceProvider)
+            }
+        }
+    }
+
+    override fun onChanged(slice: Slice?) {
+        val uri = slice?.uri ?: return
+        Log.d(TAG, "onChanged: $uri")
+        context?.contentResolver?.notifyChange(uri, null)
+    }
+
+    override fun onCreateSliceProvider(): Boolean {
+        Log.d(TAG, "onCreateSliceProvider")
+        return true
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
new file mode 100644
index 0000000..cff1c0c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.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.settingslib.spa.slice.presenter
+
+import android.net.Uri
+import androidx.compose.material3.Divider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.slice.widget.SliceLiveData
+import androidx.slice.widget.SliceView
+
+@Composable
+fun SliceDemo(sliceUri: Uri) {
+    val context = LocalContext.current
+    val lifecycleOwner = LocalLifecycleOwner.current
+    val sliceData = remember {
+        SliceLiveData.fromUri(context, sliceUri)
+    }
+
+    Divider()
+    AndroidView(
+        factory = { localContext ->
+            val view = SliceView(localContext)
+            view.setShowTitleItems(true)
+            view.isScrollable = false
+            view
+        },
+        update = { view -> sliceData.observe(lifecycleOwner, view) }
+    )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
new file mode 100644
index 0000000..3d7d565
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
@@ -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 com.android.settingslib.spa.slice.provider
+
+import android.app.PendingIntent
+import android.content.Context
+import android.net.Uri
+import androidx.core.graphics.drawable.IconCompat
+import androidx.slice.Slice
+import androidx.slice.SliceManager
+import androidx.slice.builders.ListBuilder
+import androidx.slice.builders.SliceAction
+import androidx.slice.core.R
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.slice.createBroadcastPendingIntent
+import com.android.settingslib.spa.slice.createBrowsePendingIntent
+
+fun createDemoBrowseSlice(sliceUri: Uri, title: String, summary: String): Slice? {
+    val intent = sliceUri.createBrowsePendingIntent() ?: return null
+    return createDemoSlice(sliceUri, title, summary, intent)
+}
+
+fun createDemoActionSlice(sliceUri: Uri, title: String, summary: String): Slice? {
+    val intent = sliceUri.createBroadcastPendingIntent() ?: return null
+    return createDemoSlice(sliceUri, title, summary, intent)
+}
+
+fun createDemoSlice(sliceUri: Uri, title: String, summary: String, intent: PendingIntent): Slice? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    if (!SliceManager.getInstance(context).pinnedSlices.contains(sliceUri)) return null
+    return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
+        .addRow(ListBuilder.RowBuilder().apply {
+            setPrimaryAction(createSliceAction(context, intent))
+            setTitle(title)
+            setSubtitle(summary)
+        }).build()
+}
+
+private fun createSliceAction(context: Context, intent: PendingIntent): SliceAction {
+    return SliceAction.create(
+        intent,
+        IconCompat.createWithResource(context, R.drawable.notification_action_background),
+        ListBuilder.ICON_IMAGE,
+        "Enter app"
+    )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/Illustration.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/Illustration.kt
deleted file mode 100644
index cd8a02a..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/Illustration.kt
+++ /dev/null
@@ -1,102 +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.settingslib.spa.widget
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.sizeIn
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import com.android.settingslib.spa.framework.theme.SettingsDimension
-import com.android.settingslib.spa.widget.ui.ImageBox
-import com.android.settingslib.spa.widget.ui.Lottie
-enum class ResourceType { IMAGE, LOTTIE }
-
-/**
- * The widget model for [Illustration] widget.
- */
-interface IllustrationModel {
-    /**
-     * The resource id of this [Illustration].
-     */
-    val resId: Int
-
-    /**
-     * The resource type of the [Illustration].
-     *
-     * It should be Lottie or Image.
-     */
-    val resourceType: ResourceType
-}
-
-/**
- * Illustration widget.
- *
- * Data is provided through [IllustrationModel].
- */
-@Composable
-fun Illustration(model: IllustrationModel) {
-    Illustration(
-        resId = model.resId,
-        resourceType = model.resourceType,
-        modifier = Modifier,
-    )
-}
-
-@Composable
-fun Illustration(
-    resId: Int,
-    resourceType: ResourceType,
-    modifier: Modifier = Modifier
-) {
-    Column(
-        modifier = modifier
-            .fillMaxWidth()
-            .padding(horizontal = SettingsDimension.illustrationPadding),
-        horizontalAlignment = Alignment.CenterHorizontally,
-    ) {
-        val illustrationModifier = modifier
-            .sizeIn(
-                maxWidth = SettingsDimension.illustrationMaxWidth,
-                maxHeight = SettingsDimension.illustrationMaxHeight,
-            )
-            .clip(RoundedCornerShape(SettingsDimension.illustrationCornerRadius))
-            .background(color = MaterialTheme.colorScheme.surface)
-
-        when (resourceType) {
-            ResourceType.LOTTIE -> {
-                Lottie(
-                    resId = resId,
-                    modifier = illustrationModifier,
-                )
-            }
-            ResourceType.IMAGE -> {
-                ImageBox(
-                    resId = resId,
-                    contentDescription = null,
-                    modifier = illustrationModifier,
-                )
-            }
-        }
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
index a9d1c37..5e6c614 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
@@ -64,7 +64,7 @@
 fun ActionButtons(actionButtons: List<ActionButton>) {
     Row(
         Modifier
-            .padding(SettingsDimension.itemPaddingVertical)
+            .padding(SettingsDimension.buttonPadding)
             .clip(SettingsShape.CornerLarge)
             .height(IntrinsicSize.Min)
     ) {
@@ -94,9 +94,7 @@
         ),
         contentPadding = PaddingValues(horizontal = 4.dp, vertical = 20.dp),
     ) {
-        Column(
-            horizontalAlignment = Alignment.CenterHorizontally,
-        ) {
+        Column(horizontalAlignment = Alignment.CenterHorizontally) {
             Icon(
                 imageVector = actionButton.imageVector,
                 contentDescription = null,
@@ -105,7 +103,7 @@
             Spacer(Modifier.height(4.dp))
             Text(
                 text = actionButton.text,
-                style = MaterialTheme.typography.labelLarge,
+                style = MaterialTheme.typography.labelMedium,
             )
         }
     }
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/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.kt
new file mode 100644
index 0000000..70bc017
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.kt
@@ -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.settingslib.spa.widget.chart
+
+import androidx.compose.ui.graphics.Color
+
+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/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
new file mode 100644
index 0000000..d6f5328
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
@@ -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.spa.widget.dialog
+
+import android.content.res.Configuration
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.DialogProperties
+
+data class AlertDialogButton(
+    val text: String,
+    val onClick: () -> Unit = {},
+)
+
+interface AlertDialogPresenter {
+    /** Opens the dialog. */
+    fun open()
+
+    /** Closes the dialog. */
+    fun close()
+}
+
+@Composable
+fun rememberAlertDialogPresenter(
+    confirmButton: AlertDialogButton? = null,
+    dismissButton: AlertDialogButton? = null,
+    title: String? = null,
+    text: @Composable (() -> Unit)? = null,
+): AlertDialogPresenter {
+    var openDialog by rememberSaveable { mutableStateOf(false) }
+    val alertDialogPresenter = remember {
+        object : AlertDialogPresenter {
+            override fun open() {
+                openDialog = true
+            }
+
+            override fun close() {
+                openDialog = false
+            }
+        }
+    }
+    if (openDialog) {
+        alertDialogPresenter.SettingsAlertDialog(confirmButton, dismissButton, title, text)
+    }
+    return alertDialogPresenter
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+private fun AlertDialogPresenter.SettingsAlertDialog(
+    confirmButton: AlertDialogButton?,
+    dismissButton: AlertDialogButton?,
+    title: String?,
+    text: @Composable (() -> Unit)?,
+) {
+    val configuration = LocalConfiguration.current
+    AlertDialog(
+        onDismissRequest = ::close,
+        modifier = when (configuration.orientation) {
+            Configuration.ORIENTATION_LANDSCAPE -> {
+                Modifier.width(configuration.screenWidthDp.dp * 0.6f)
+            }
+            else -> Modifier
+        },
+        confirmButton = { confirmButton?.let { Button(it) } },
+        dismissButton = dismissButton?.let { { Button(it) } },
+        title = title?.let { { Text(it) } },
+        text = text?.let {
+            {
+                Column(Modifier.verticalScroll(rememberScrollState())) {
+                    text()
+                }
+            }
+        },
+        properties = DialogProperties(usePlatformDefaultWidth = false),
+    )
+}
+
+@Composable
+private fun AlertDialogPresenter.Button(button: AlertDialogButton) {
+    TextButton(
+        onClick = {
+            close()
+            button.onClick()
+        },
+    ) {
+        Text(button.text)
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
new file mode 100644
index 0000000..7cc9bf7
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.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.settingslib.spa.widget.illustration
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.ui.ImageBox
+import com.android.settingslib.spa.widget.ui.Lottie
+enum class ResourceType { IMAGE, LOTTIE }
+
+/**
+ * The widget model for [Illustration] widget.
+ */
+interface IllustrationModel {
+    /**
+     * The resource id of this [Illustration].
+     */
+    val resId: Int
+
+    /**
+     * The resource type of the [Illustration].
+     *
+     * It should be Lottie or Image.
+     */
+    val resourceType: ResourceType
+}
+
+/**
+ * Illustration widget.
+ *
+ * Data is provided through [IllustrationModel].
+ */
+@Composable
+fun Illustration(model: IllustrationModel) {
+    Illustration(
+        resId = model.resId,
+        resourceType = model.resourceType,
+        modifier = Modifier,
+    )
+}
+
+@Composable
+fun Illustration(
+    resId: Int,
+    resourceType: ResourceType,
+    modifier: Modifier = Modifier
+) {
+    Column(
+        modifier = modifier
+            .fillMaxWidth()
+            .padding(horizontal = SettingsDimension.illustrationPadding),
+        horizontalAlignment = Alignment.CenterHorizontally,
+    ) {
+        val illustrationModifier = modifier
+            .sizeIn(
+                maxWidth = SettingsDimension.illustrationMaxWidth,
+                maxHeight = SettingsDimension.illustrationMaxHeight,
+            )
+            .clip(RoundedCornerShape(SettingsDimension.illustrationCornerRadius))
+            .background(color = MaterialTheme.colorScheme.surface)
+
+        when (resourceType) {
+            ResourceType.LOTTIE -> {
+                Lottie(
+                    resId = resId,
+                    modifier = illustrationModifier,
+                )
+            }
+            ResourceType.IMAGE -> {
+                ImageBox(
+                    resId = resId,
+                    contentDescription = null,
+                    modifier = illustrationModifier,
+                )
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
index db95e23..3e04b16 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
@@ -28,7 +28,7 @@
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsShape
 import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.util.EntryHighlight
+import com.android.settingslib.spa.framework.util.EntryHighlight
 
 @Composable
 fun MainSwitchPreference(model: SwitchPreferenceModel) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index 895edf7..b6099e9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -24,12 +24,11 @@
 import androidx.compose.ui.graphics.vector.ImageVector
 import com.android.settingslib.spa.framework.common.EntryMacro
 import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.EntryStatusData
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.util.EntryHighlight
 import com.android.settingslib.spa.framework.util.wrapOnClickWithLog
 import com.android.settingslib.spa.widget.ui.createSettingsIcon
-import com.android.settingslib.spa.widget.util.EntryHighlight
 
 data class SimplePreferenceMacro(
     val title: String,
@@ -56,10 +55,6 @@
             keyword = searchKeywords
         )
     }
-
-    override fun getStatusData(): EntryStatusData {
-        return EntryStatusData(isDisabled = false)
-    }
 }
 
 /**
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
index 4ee2af0..7bca38f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
@@ -31,8 +31,8 @@
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.EntryHighlight
 import com.android.settingslib.spa.widget.ui.SettingsSlider
-import com.android.settingslib.spa.widget.util.EntryHighlight
 
 /**
  * The widget model for [SliderPreference] widget.
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
index 2d60619..b67eb3d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
@@ -20,6 +20,8 @@
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.selection.toggleable
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
@@ -31,9 +33,10 @@
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.EntryHighlight
 import com.android.settingslib.spa.framework.util.wrapOnSwitchWithLog
+import com.android.settingslib.spa.widget.ui.SettingsIcon
 import com.android.settingslib.spa.widget.ui.SettingsSwitch
-import com.android.settingslib.spa.widget.util.EntryHighlight
 
 /**
  * The widget model for [SwitchPreference] widget.
@@ -51,6 +54,14 @@
         get() = stateOf("")
 
     /**
+     * The icon of this [Preference].
+     *
+     * Default is `null` which means no icon.
+     */
+    val icon: (@Composable () -> Unit)?
+        get() = null
+
+    /**
      * Indicates whether this [SwitchPreference] is checked.
      *
      * This can be `null` during the data loading before the data is available.
@@ -84,6 +95,7 @@
         InternalSwitchPreference(
             title = model.title,
             summary = model.summary,
+            icon = model.icon,
             checked = model.checked,
             changeable = model.changeable,
             onCheckedChange = model.onCheckedChange,
@@ -95,6 +107,7 @@
 internal fun InternalSwitchPreference(
     title: String,
     summary: State<String> = "".toState(),
+    icon: @Composable (() -> Unit)? = null,
     checked: State<Boolean?>,
     changeable: State<Boolean> = true.toState(),
     paddingStart: Dp = SettingsDimension.itemPaddingStart,
@@ -125,6 +138,7 @@
         paddingStart = paddingStart,
         paddingEnd = paddingEnd,
         paddingVertical = paddingVertical,
+        icon = icon,
     ) {
         SettingsSwitch(
             checked = checked,
@@ -152,6 +166,15 @@
                 checked = false.toState(),
                 onCheckedChange = {},
             )
+            InternalSwitchPreference(
+                title = "Use Dark theme",
+                summary = "Summary".toState(),
+                checked = true.toState(),
+                onCheckedChange = {},
+                icon = @Composable {
+                    SettingsIcon(imageVector = Icons.Outlined.AirplanemodeActive)
+                },
+            )
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
index fbfcaaa..63de2c8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
@@ -17,7 +17,7 @@
 package com.android.settingslib.spa.widget.preference
 
 import androidx.compose.runtime.Composable
-import com.android.settingslib.spa.widget.util.EntryHighlight
+import com.android.settingslib.spa.framework.util.EntryHighlight
 import com.android.settingslib.spa.widget.ui.SettingsSwitch
 
 @Composable
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
index 764973f..32b283e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -17,20 +17,13 @@
 package com.android.settingslib.spa.widget.scaffold
 
 import androidx.appcompat.R
-import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.ArrowBack
 import androidx.compose.material.icons.outlined.Clear
 import androidx.compose.material.icons.outlined.FindInPage
-import androidx.compose.material.icons.outlined.MoreVert
-import androidx.compose.material3.DropdownMenu
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.runtime.Composable
-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.res.stringResource
 import com.android.settingslib.spa.framework.compose.LocalNavController
 
@@ -82,27 +75,3 @@
         )
     }
 }
-
-@Composable
-fun MoreOptionsAction(
-    content: @Composable ColumnScope.(onDismissRequest: () -> Unit) -> Unit,
-) {
-    var expanded by rememberSaveable { mutableStateOf(false) }
-    MoreOptionsActionButton { expanded = true }
-    val onDismissRequest = { expanded = false }
-    DropdownMenu(
-        expanded = expanded,
-        onDismissRequest = onDismissRequest,
-        content = { content(onDismissRequest) },
-    )
-}
-
-@Composable
-private fun MoreOptionsActionButton(onClick: () -> Unit) {
-    IconButton(onClick) {
-        Icon(
-            imageVector = Icons.Outlined.MoreVert,
-            contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
-        )
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
new file mode 100644
index 0000000..ae88ed7
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -0,0 +1,607 @@
+/*
+ * 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 com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.AnimationState
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.core.FastOutLinearInEasing
+import androidx.compose.animation.core.animateDecay
+import androidx.compose.animation.core.animateTo
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.gestures.rememberDraggableState
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+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.foundation.layout.windowInsetsPadding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ProvideTextStyle
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
+import androidx.compose.material3.TopAppBarState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.LastBaseline
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+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 kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.roundToInt
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun CustomizedTopAppBar(
+    title: @Composable () -> Unit,
+    navigationIcon: @Composable () -> Unit = {},
+    actions: @Composable RowScope.() -> Unit = {},
+) {
+    SingleRowTopAppBar(
+        title = title,
+        titleTextStyle = MaterialTheme.typography.titleMedium,
+        navigationIcon = navigationIcon,
+        actions = actions,
+        windowInsets = TopAppBarDefaults.windowInsets,
+        colors = topAppBarColors(),
+    )
+}
+
+/**
+ * The customized LargeTopAppBar for Settings.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun CustomizedLargeTopAppBar(
+    title: String,
+    modifier: Modifier = Modifier,
+    navigationIcon: @Composable () -> Unit = {},
+    actions: @Composable RowScope.() -> Unit = {},
+    scrollBehavior: TopAppBarScrollBehavior? = null,
+) {
+    TwoRowsTopAppBar(
+        title = { Title(title = title, maxLines = 2) },
+        titleTextStyle = MaterialTheme.typography.displaySmall,
+        smallTitleTextStyle = MaterialTheme.typography.titleMedium,
+        titleBottomPadding = LargeTitleBottomPadding,
+        smallTitle = { Title(title = title, maxLines = 1) },
+        modifier = modifier,
+        navigationIcon = navigationIcon,
+        actions = actions,
+        colors = topAppBarColors(),
+        windowInsets = TopAppBarDefaults.windowInsets,
+        maxHeight = 176.dp,
+        pinnedHeight = ContainerHeight,
+        scrollBehavior = scrollBehavior,
+    )
+}
+
+@Composable
+private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) {
+    Text(
+        text = title,
+        modifier = Modifier
+            .padding(
+                WindowInsets.navigationBars
+                    .asPaddingValues()
+                    .horizontalValues()
+            )
+            .padding(
+                start = SettingsDimension.itemPaddingAround,
+                end = SettingsDimension.itemPaddingEnd,
+            ),
+        overflow = TextOverflow.Ellipsis,
+        maxLines = maxLines,
+    )
+}
+
+@Composable
+private fun topAppBarColors() = TopAppBarColors(
+    containerColor = MaterialTheme.colorScheme.background,
+    scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader,
+    navigationIconContentColor = MaterialTheme.colorScheme.onSurface,
+    titleContentColor = MaterialTheme.colorScheme.onSurface,
+    actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+)
+
+/**
+ * Represents the colors used by a top app bar in different states.
+ * This implementation animates the container color according to the top app bar scroll state. It
+ * does not animate the leading, headline, or trailing colors.
+ */
+@Stable
+private class TopAppBarColors(
+    val containerColor: Color,
+    val scrolledContainerColor: Color,
+    val navigationIconContentColor: Color,
+    val titleContentColor: Color,
+    val actionIconContentColor: Color,
+) {
+
+    /**
+     * Represents the container color used for the top app bar.
+     *
+     * A [colorTransitionFraction] provides a percentage value that can be used to generate a color.
+     * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from
+     * the [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction].
+     *
+     * @param colorTransitionFraction a `0.0` to `1.0` value that represents a color transition
+     * percentage
+     */
+    @Composable
+    fun containerColor(colorTransitionFraction: Float): Color {
+        return lerp(
+            containerColor,
+            scrolledContainerColor,
+            FastOutLinearInEasing.transform(colorTransitionFraction)
+        )
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || other !is TopAppBarColors) return false
+
+        if (containerColor != other.containerColor) return false
+        if (scrolledContainerColor != other.scrolledContainerColor) return false
+        if (navigationIconContentColor != other.navigationIconContentColor) return false
+        if (titleContentColor != other.titleContentColor) return false
+        if (actionIconContentColor != other.actionIconContentColor) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = containerColor.hashCode()
+        result = 31 * result + scrolledContainerColor.hashCode()
+        result = 31 * result + navigationIconContentColor.hashCode()
+        result = 31 * result + titleContentColor.hashCode()
+        result = 31 * result + actionIconContentColor.hashCode()
+
+        return result
+    }
+}
+
+/**
+ * A single-row top app bar that is designed to be called by the small and center aligned top app
+ * bar composables.
+ */
+@Composable
+private fun SingleRowTopAppBar(
+    title: @Composable () -> Unit,
+    titleTextStyle: TextStyle,
+    navigationIcon: @Composable () -> Unit,
+    actions: @Composable (RowScope.() -> Unit),
+    windowInsets: WindowInsets,
+    colors: TopAppBarColors,
+) {
+    // Wrap the given actions in a Row.
+    val actionsRow = @Composable {
+        Row(
+            horizontalArrangement = Arrangement.End,
+            verticalAlignment = Alignment.CenterVertically,
+            content = actions
+        )
+    }
+
+    // Compose a Surface with a TopAppBarLayout content.
+    Surface(color = colors.scrolledContainerColor) {
+        val height = LocalDensity.current.run { ContainerHeight.toPx() }
+        TopAppBarLayout(
+            modifier = Modifier
+                .windowInsetsPadding(windowInsets)
+                // clip after padding so we don't show the title over the inset area
+                .clipToBounds(),
+            heightPx = height,
+            navigationIconContentColor = colors.navigationIconContentColor,
+            titleContentColor = colors.titleContentColor,
+            actionIconContentColor = colors.actionIconContentColor,
+            title = title,
+            titleTextStyle = titleTextStyle,
+            titleAlpha = 1f,
+            titleVerticalArrangement = Arrangement.Center,
+            titleHorizontalArrangement = Arrangement.Start,
+            titleBottomPadding = 0,
+            hideTitleSemantics = false,
+            navigationIcon = navigationIcon,
+            actions = actionsRow,
+        )
+    }
+}
+
+/**
+ * A two-rows top app bar that is designed to be called by the Large and Medium top app bar
+ * composables.
+ *
+ * @throws [IllegalArgumentException] if the given [maxHeight] is equal or smaller than the
+ * [pinnedHeight]
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun TwoRowsTopAppBar(
+    modifier: Modifier = Modifier,
+    title: @Composable () -> Unit,
+    titleTextStyle: TextStyle,
+    titleBottomPadding: Dp,
+    smallTitle: @Composable () -> Unit,
+    smallTitleTextStyle: TextStyle,
+    navigationIcon: @Composable () -> Unit,
+    actions: @Composable RowScope.() -> Unit,
+    windowInsets: WindowInsets,
+    colors: TopAppBarColors,
+    maxHeight: Dp,
+    pinnedHeight: Dp,
+    scrollBehavior: TopAppBarScrollBehavior?
+) {
+    if (maxHeight <= pinnedHeight) {
+        throw IllegalArgumentException(
+            "A TwoRowsTopAppBar max height should be greater than its pinned height"
+        )
+    }
+    val pinnedHeightPx: Float
+    val maxHeightPx: Float
+    val titleBottomPaddingPx: Int
+    LocalDensity.current.run {
+        pinnedHeightPx = pinnedHeight.toPx()
+        maxHeightPx = maxHeight.toPx()
+        titleBottomPaddingPx = titleBottomPadding.roundToPx()
+    }
+
+    // Sets the app bar's height offset limit to hide just the bottom title area and keep top title
+    // visible when collapsed.
+    SideEffect {
+        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
+            scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx
+        }
+    }
+
+    // Obtain the container Color from the TopAppBarColors using the `collapsedFraction`, as the
+    // bottom part of this TwoRowsTopAppBar changes color at the same rate the app bar expands or
+    // collapse.
+    // This will potentially animate or interpolate a transition between the container color and the
+    // container's scrolled color according to the app bar's scroll state.
+    val colorTransitionFraction = scrollBehavior?.state?.collapsedFraction ?: 0f
+    val appBarContainerColor by rememberUpdatedState(colors.containerColor(colorTransitionFraction))
+
+    // Wrap the given actions in a Row.
+    val actionsRow = @Composable {
+        Row(
+            horizontalArrangement = Arrangement.End,
+            verticalAlignment = Alignment.CenterVertically,
+            content = actions
+        )
+    }
+    val topTitleAlpha = TopTitleAlphaEasing.transform(colorTransitionFraction)
+    val bottomTitleAlpha = 1f - colorTransitionFraction
+    // Hide the top row title semantics when its alpha value goes below 0.5 threshold.
+    // Hide the bottom row title semantics when the top title semantics are active.
+    val hideTopRowSemantics = colorTransitionFraction < 0.5f
+    val hideBottomRowSemantics = !hideTopRowSemantics
+
+    // Set up support for resizing the top app bar when vertically dragging the bar itself.
+    val appBarDragModifier = if (scrollBehavior != null && !scrollBehavior.isPinned) {
+        Modifier.draggable(
+            orientation = Orientation.Vertical,
+            state = rememberDraggableState { delta ->
+                scrollBehavior.state.heightOffset = scrollBehavior.state.heightOffset + delta
+            },
+            onDragStopped = { velocity ->
+                settleAppBar(
+                    scrollBehavior.state,
+                    velocity,
+                    scrollBehavior.flingAnimationSpec,
+                    scrollBehavior.snapAnimationSpec
+                )
+            }
+        )
+    } else {
+        Modifier
+    }
+
+    Surface(modifier = modifier.then(appBarDragModifier), color = appBarContainerColor) {
+        Column {
+            TopAppBarLayout(
+                modifier = Modifier
+                    .windowInsetsPadding(windowInsets)
+                    // clip after padding so we don't show the title over the inset area
+                    .clipToBounds(),
+                heightPx = pinnedHeightPx,
+                navigationIconContentColor = colors.navigationIconContentColor,
+                titleContentColor = colors.titleContentColor,
+                actionIconContentColor = colors.actionIconContentColor,
+                title = smallTitle,
+                titleTextStyle = smallTitleTextStyle,
+                titleAlpha = topTitleAlpha,
+                titleVerticalArrangement = Arrangement.Center,
+                titleHorizontalArrangement = Arrangement.Start,
+                titleBottomPadding = 0,
+                hideTitleSemantics = hideTopRowSemantics,
+                navigationIcon = navigationIcon,
+                actions = actionsRow,
+            )
+            TopAppBarLayout(
+                modifier = Modifier.clipToBounds(),
+                heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.heightOffset
+                    ?: 0f),
+                navigationIconContentColor = colors.navigationIconContentColor,
+                titleContentColor = colors.titleContentColor,
+                actionIconContentColor = colors.actionIconContentColor,
+                title = title,
+                titleTextStyle = titleTextStyle,
+                titleAlpha = bottomTitleAlpha,
+                titleVerticalArrangement = Arrangement.Bottom,
+                titleHorizontalArrangement = Arrangement.Start,
+                titleBottomPadding = titleBottomPaddingPx,
+                hideTitleSemantics = hideBottomRowSemantics,
+                navigationIcon = {},
+                actions = {}
+            )
+        }
+    }
+}
+
+/**
+ * The base [Layout] for all top app bars. This function lays out a top app bar navigation icon
+ * (leading icon), a title (header), and action icons (trailing icons). Note that the navigation and
+ * the actions are optional.
+ *
+ * @param heightPx the total height this layout is capped to
+ * @param navigationIconContentColor the content color that will be applied via a
+ * [LocalContentColor] when composing the navigation icon
+ * @param titleContentColor the color that will be applied via a [LocalContentColor] when composing
+ * the title
+ * @param actionIconContentColor the content color that will be applied via a [LocalContentColor]
+ * when composing the action icons
+ * @param title the top app bar title (header)
+ * @param titleTextStyle the title's text style
+ * @param modifier a [Modifier]
+ * @param titleAlpha the title's alpha
+ * @param titleVerticalArrangement the title's vertical arrangement
+ * @param titleHorizontalArrangement the title's horizontal arrangement
+ * @param titleBottomPadding the title's bottom padding
+ * @param hideTitleSemantics hides the title node from the semantic tree. Apply this
+ * boolean when this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics
+ * from accessibility services. This is needed to avoid having multiple titles visible to
+ * accessibility services at the same time, when animating between collapsed / expanded states.
+ * @param navigationIcon a navigation icon [Composable]
+ * @param actions actions [Composable]
+ */
+@Composable
+private fun TopAppBarLayout(
+    modifier: Modifier,
+    heightPx: Float,
+    navigationIconContentColor: Color,
+    titleContentColor: Color,
+    actionIconContentColor: Color,
+    title: @Composable () -> Unit,
+    titleTextStyle: TextStyle,
+    titleAlpha: Float,
+    titleVerticalArrangement: Arrangement.Vertical,
+    titleHorizontalArrangement: Arrangement.Horizontal,
+    titleBottomPadding: Int,
+    hideTitleSemantics: Boolean,
+    navigationIcon: @Composable () -> Unit,
+    actions: @Composable () -> Unit,
+) {
+    Layout(
+        {
+            Box(
+                Modifier
+                    .layoutId("navigationIcon")
+                    .padding(start = TopAppBarHorizontalPadding)
+            ) {
+                CompositionLocalProvider(
+                    LocalContentColor provides navigationIconContentColor,
+                    content = navigationIcon
+                )
+            }
+            Box(
+                Modifier
+                    .layoutId("title")
+                    .padding(horizontal = TopAppBarHorizontalPadding)
+                    .then(if (hideTitleSemantics) Modifier.clearAndSetSemantics { } else Modifier)
+                    .graphicsLayer(alpha = titleAlpha)
+            ) {
+                ProvideTextStyle(value = titleTextStyle) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides titleContentColor,
+                        content = title
+                    )
+                }
+            }
+            Box(
+                Modifier
+                    .layoutId("actionIcons")
+                    .padding(end = TopAppBarHorizontalPadding)
+            ) {
+                CompositionLocalProvider(
+                    LocalContentColor provides actionIconContentColor,
+                    content = actions
+                )
+            }
+        },
+        modifier = modifier
+    ) { measurables, constraints ->
+        val navigationIconPlaceable =
+            measurables.first { it.layoutId == "navigationIcon" }
+                .measure(constraints.copy(minWidth = 0))
+        val actionIconsPlaceable =
+            measurables.first { it.layoutId == "actionIcons" }
+                .measure(constraints.copy(minWidth = 0))
+
+        val maxTitleWidth = if (constraints.maxWidth == Constraints.Infinity) {
+            constraints.maxWidth
+        } else {
+            (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width)
+                .coerceAtLeast(0)
+        }
+        val titlePlaceable =
+            measurables.first { it.layoutId == "title" }
+                .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth))
+
+        // Locate the title's baseline.
+        val titleBaseline =
+            if (titlePlaceable[LastBaseline] != AlignmentLine.Unspecified) {
+                titlePlaceable[LastBaseline]
+            } else {
+                0
+            }
+
+        val layoutHeight = heightPx.roundToInt()
+
+        layout(constraints.maxWidth, layoutHeight) {
+            // Navigation icon
+            navigationIconPlaceable.placeRelative(
+                x = 0,
+                y = (layoutHeight - navigationIconPlaceable.height) / 2
+            )
+
+            // Title
+            titlePlaceable.placeRelative(
+                x = when (titleHorizontalArrangement) {
+                    Arrangement.Center -> (constraints.maxWidth - titlePlaceable.width) / 2
+                    Arrangement.End ->
+                        constraints.maxWidth - titlePlaceable.width - actionIconsPlaceable.width
+                    // Arrangement.Start.
+                    // A TopAppBarTitleInset will make sure the title is offset in case the
+                    // navigation icon is missing.
+                    else -> max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width)
+                },
+                y = when (titleVerticalArrangement) {
+                    Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2
+                    // Apply bottom padding from the title's baseline only when the Arrangement is
+                    // "Bottom".
+                    Arrangement.Bottom ->
+                        if (titleBottomPadding == 0) layoutHeight - titlePlaceable.height
+                        else layoutHeight - titlePlaceable.height - max(
+                            0,
+                            titleBottomPadding - titlePlaceable.height + titleBaseline
+                        )
+                    // Arrangement.Top
+                    else -> 0
+                }
+            )
+
+            // Action icons
+            actionIconsPlaceable.placeRelative(
+                x = constraints.maxWidth - actionIconsPlaceable.width,
+                y = (layoutHeight - actionIconsPlaceable.height) / 2
+            )
+        }
+    }
+}
+
+
+/**
+ * Settles the app bar by flinging, in case the given velocity is greater than zero, and snapping
+ * after the fling settles.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+private suspend fun settleAppBar(
+    state: TopAppBarState,
+    velocity: Float,
+    flingAnimationSpec: DecayAnimationSpec<Float>?,
+    snapAnimationSpec: AnimationSpec<Float>?
+): Velocity {
+    // Check if the app bar is completely collapsed/expanded. If so, no need to settle the app bar,
+    // and just return Zero Velocity.
+    // Note that we don't check for 0f due to float precision with the collapsedFraction
+    // calculation.
+    if (state.collapsedFraction < 0.01f || state.collapsedFraction == 1f) {
+        return Velocity.Zero
+    }
+    var remainingVelocity = velocity
+    // In case there is an initial velocity that was left after a previous user fling, animate to
+    // continue the motion to expand or collapse the app bar.
+    if (flingAnimationSpec != null && abs(velocity) > 1f) {
+        var lastValue = 0f
+        AnimationState(
+            initialValue = 0f,
+            initialVelocity = velocity,
+        )
+            .animateDecay(flingAnimationSpec) {
+                val delta = value - lastValue
+                val initialHeightOffset = state.heightOffset
+                state.heightOffset = initialHeightOffset + delta
+                val consumed = abs(initialHeightOffset - state.heightOffset)
+                lastValue = value
+                remainingVelocity = this.velocity
+                // avoid rounding errors and stop if anything is unconsumed
+                if (abs(delta - consumed) > 0.5f) this.cancelAnimation()
+            }
+    }
+    // Snap if animation specs were provided.
+    if (snapAnimationSpec != null) {
+        if (state.heightOffset < 0 &&
+            state.heightOffset > state.heightOffsetLimit
+        ) {
+            AnimationState(initialValue = state.heightOffset).animateTo(
+                if (state.collapsedFraction < 0.5f) {
+                    0f
+                } else {
+                    state.heightOffsetLimit
+                },
+                animationSpec = snapAnimationSpec
+            ) { state.heightOffset = value }
+        }
+    }
+
+    return Velocity(0f, remainingVelocity)
+}
+
+// An easing function used to compute the alpha value that is applied to the top title part of a
+// Medium or Large app bar.
+private val TopTitleAlphaEasing = CubicBezierEasing(.8f, 0f, .8f, .15f)
+
+private val ContainerHeight = 56.dp
+private val LargeTitleBottomPadding = 28.dp
+private val TopAppBarHorizontalPadding = 4.dp
+
+// A title inset when the App-Bar is a Medium or Large one. Also used to size a spacer when the
+// navigation icon is missing.
+private val TopAppBarTitleInset = 16.dp - TopAppBarHorizontalPadding
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
new file mode 100644
index 0000000..84fea15
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.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.settingslib.spa.widget.scaffold
+
+import androidx.appcompat.R
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.MoreVert
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.res.stringResource
+
+/**
+ * Scope for the children of [MoreOptionsAction].
+ */
+interface MoreOptionsScope {
+    fun dismiss()
+
+    @Composable
+    fun MenuItem(text: String, enabled: Boolean = true, onClick: () -> Unit) {
+        DropdownMenuItem(
+            text = { Text(text) },
+            onClick = {
+                dismiss()
+                onClick()
+            },
+            enabled = enabled,
+        )
+    }
+}
+
+@Composable
+fun MoreOptionsAction(
+    content: @Composable MoreOptionsScope.() -> Unit,
+) {
+    var expanded by rememberSaveable { mutableStateOf(false) }
+    MoreOptionsActionButton { expanded = true }
+    val onDismiss = { expanded = false }
+    DropdownMenu(expanded = expanded, onDismissRequest = onDismiss) {
+        val moreOptionsScope = remember(this) {
+            object : MoreOptionsScope {
+                override fun dismiss() {
+                    onDismiss()
+                }
+            }
+        }
+        moreOptionsScope.content()
+    }
+}
+
+@Composable
+private fun MoreOptionsActionButton(onClick: () -> Unit) {
+    IconButton(onClick) {
+        Icon(
+            imageVector = Icons.Outlined.MoreVert,
+            contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
+        )
+    }
+}
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 efc623a..4218489b 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
@@ -24,17 +24,14 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.statusBarsPadding
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 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
@@ -136,7 +133,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun SearchTopAppBar(
     query: TextFieldValue,
@@ -144,20 +140,16 @@
     onClose: () -> Unit,
     actions: @Composable RowScope.() -> Unit = {},
 ) {
-    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),
-        )
-    }
+    CustomizedTopAppBar(
+        title = { SearchBox(query, onQueryChange) },
+        navigationIcon = { CollapseAction(onClose) },
+        actions = {
+            if (query.text.isNotEmpty()) {
+                ClearAction { onQueryChange(TextFieldValue()) }
+            }
+            actions()
+        },
+    )
     BackHandler { onClose() }
 }
 
@@ -166,6 +158,7 @@
 private fun SearchBox(query: TextFieldValue, onQueryChange: (TextFieldValue) -> Unit) {
     val focusRequester = remember { FocusRequester() }
     val textStyle = MaterialTheme.typography.bodyLarge
+    val hideKeyboardAction = hideKeyboardAction()
     TextField(
         value = query,
         onValueChange = onQueryChange,
@@ -181,7 +174,7 @@
             )
         },
         keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
-        keyboardActions = KeyboardActions(onSearch = hideKeyboardAction()),
+        keyboardActions = KeyboardActions(onSearch = { hideKeyboardAction() }),
         singleLine = true,
         colors = TextFieldDefaults.textFieldColors(
             containerColor = Color.Transparent,
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 f7cb035..3311792 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,24 +17,9 @@
 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.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
@@ -43,20 +28,12 @@
     scrollBehavior: TopAppBarScrollBehavior,
     actions: @Composable RowScope.() -> Unit,
 ) {
-    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,
-        )
-    }
+    CustomizedLargeTopAppBar(
+        title = title,
+        navigationIcon = { NavigateBack() },
+        actions = actions,
+        scrollBehavior = scrollBehavior,
+    )
 }
 
 @OptIn(ExperimentalMaterial3Api::class)
@@ -65,22 +42,3 @@
         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
-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/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
index 123354f..5f2344e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -30,18 +30,24 @@
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.toMediumWeight
 
 @Composable
-fun SettingsTitle(title: State<String>) {
-    SettingsTitle(title.value)
+fun SettingsTitle(title: State<String>, useMediumWeight: Boolean = false) {
+    SettingsTitle(title.value, useMediumWeight)
 }
 
 @Composable
-fun SettingsTitle(title: String) {
+fun SettingsTitle(title: String, useMediumWeight: Boolean = false) {
     Text(
         text = title,
         color = MaterialTheme.colorScheme.onSurface,
-        style = MaterialTheme.typography.titleMedium,
+        style = MaterialTheme.typography.titleMedium.let {
+            when (useMediumWeight) {
+                true -> it.toMediumWeight()
+                else -> it
+            }
+        },
     )
 }
 
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
deleted file mode 100644
index e26bdf7..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.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.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 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 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..6974005 100644
--- a/packages/SettingsLib/Spa/tests/Android.bp
+++ b/packages/SettingsLib/Spa/tests/Android.bp
@@ -26,12 +26,11 @@
 
     static_libs: [
         "SpaLib",
-        "androidx.test.runner",
-        "androidx.test.ext.junit",
+        "SpaLibTestUtils",
         "androidx.compose.runtime_runtime",
-        "androidx.compose.ui_ui-test-junit4",
-        "androidx.compose.ui_ui-test-manifest",
-        "truth-prebuilt",
+        "androidx.test.ext.junit",
+        "androidx.test.runner",
+        "mockito-target-minus-junit4",
     ],
     kotlincflags: ["-Xjvm-default=all"],
     min_sdk_version: "31",
diff --git a/packages/SettingsLib/Spa/tests/AndroidManifest.xml b/packages/SettingsLib/Spa/tests/AndroidManifest.xml
index e2db594..1fda4e0 100644
--- a/packages/SettingsLib/Spa/tests/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/tests/AndroidManifest.xml
@@ -15,7 +15,7 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.settingslib.spa.tests">
+    package="com.android.settingslib.spa.test">
 
     <uses-sdk android:minSdkVersion="21"/>
 
@@ -26,6 +26,6 @@
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
         android:label="Tests for SpaLib"
-        android:targetPackage="com.android.settingslib.spa.tests">
+        android:targetPackage="com.android.settingslib.spa.test">
     </instrumentation>
 </manifest>
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
deleted file mode 100644
index 4b4c6a3..0000000
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.
- */
-
-plugins {
-    id 'com.android.library'
-    id 'kotlin-android'
-}
-
-android {
-    namespace 'com.android.settingslib.spa.tests'
-    compileSdk spa_target_sdk
-
-    defaultConfig {
-        minSdk spa_min_sdk
-        targetSdk spa_target_sdk
-
-        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-    }
-
-    sourceSets {
-        main {
-            res.srcDirs = ["res"]
-        }
-        androidTest {
-            kotlin {
-                srcDir "src"
-            }
-            manifest.srcFile "AndroidManifest.xml"
-        }
-    }
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_8
-        targetCompatibility JavaVersion.VERSION_1_8
-    }
-    kotlinOptions {
-        jvmTarget = '1.8'
-        freeCompilerArgs = ["-Xjvm-default=all"]
-    }
-    buildFeatures {
-        compose true
-    }
-    composeOptions {
-        kotlinCompilerExtensionVersion jetpack_compose_compiler_version
-    }
-}
-
-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'
-    androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
-}
diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml
new file mode 100644
index 0000000..1ca425c
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/res/values/strings.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.
+  -->
+
+<resources>
+    <string name="test_quantity_strings">{count, plural,
+        =1    {There is one song found.}
+        other {There are # songs found.}
+    }</string>
+
+    <string name="test_quantity_strings_with_param">{count, plural,
+        =1    {There is one song found in {place}.}
+        other {There are # songs found in {place}.}
+    }</string>
+</resources>
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
new file mode 100644
index 0000000..bd5884d
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
@@ -0,0 +1,85 @@
+/*
+ * 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
+
+import android.content.Context
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.LogEvent
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
+import com.android.settingslib.spa.tests.testutils.SppHome
+import com.android.settingslib.spa.testutils.waitUntil
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val WAIT_UNTIL_TIMEOUT = 1000L
+
+@RunWith(AndroidJUnit4::class)
+class BrowseActivityTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaLogger = SpaLoggerForTest()
+    private val spaEnvironment =
+        SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()), logger = spaLogger)
+
+    @Test
+    fun testBrowsePage() {
+        spaLogger.reset()
+        SpaEnvironmentFactory.reset(spaEnvironment)
+
+        val sppRepository by spaEnvironment.pageProviderRepository
+        val sppHome = sppRepository.getProviderOrNull("SppHome")!!
+        val pageHome = sppHome.createSettingsPage()
+        val sppLayer1 = sppRepository.getProviderOrNull("SppLayer1")!!
+        val pageLayer1 = sppLayer1.createSettingsPage()
+
+        composeTestRule.setContent { BrowseContent(sppRepository) }
+
+        composeTestRule.onNodeWithText(sppHome.getTitle(null)).assertIsDisplayed()
+        spaLogger.verifyPageEvent(pageHome.id, 1, 0)
+        spaLogger.verifyPageEvent(pageLayer1.id, 0, 0)
+
+        // click to layer1 page
+        composeTestRule.onNodeWithText("SppHome to Layer1").assertIsDisplayed().performClick()
+        waitUntil(WAIT_UNTIL_TIMEOUT) {
+            composeTestRule.onAllNodesWithText(sppLayer1.getTitle(null))
+                .fetchSemanticsNodes().size == 1
+        }
+        spaLogger.verifyPageEvent(pageHome.id, 1, 1)
+        spaLogger.verifyPageEvent(pageLayer1.id, 1, 0)
+    }
+}
+
+private fun SpaLoggerForTest.verifyPageEvent(id: String, entryCount: Int, leaveCount: Int) {
+    Truth.assertThat(getEventCount(id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
+        .isEqualTo(entryCount)
+    Truth.assertThat(getEventCount(id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
+        .isEqualTo(leaveCount)
+}
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..c0b7464
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SppHome
+import com.android.settingslib.spa.tests.testutils.SppLayer1
+import com.android.settingslib.spa.tests.testutils.SppLayer2
+import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
+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, listOf(SppHome.createSettingsPage()))
+    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..f98963c
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -0,0 +1,172 @@
+/*
+ * 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.android.settingslib.spa.tests.testutils.getUniqueEntryId
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
+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.hasMutableStatus).isFalse()
+        assertThat(entry.hasSliceSupport).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 entryBuilder = SettingsEntryBuilder.create(owner, "myEntry")
+            .setDisplayName("myEntryDisplay")
+            .setIsSearchDataDynamic(false)
+            .setHasMutableStatus(true)
+            .setSearchDataFn { null }
+            .setSliceDataFn { _, _ -> null }
+        val entry = entryBuilder.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.hasMutableStatus).isTrue()
+        assertThat(entry.hasSliceSupport).isTrue()
+
+        val entry2 = entryBuilder.clearSearchDataFn().build()
+        assertThat(entry2.isAllowSearch).isFalse()
+    }
+
+    @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) }
+        assertThat(entry.isAllowSearch).isTrue()
+        assertThat(entry.isSearchDataDynamic).isFalse()
+        assertThat(entry.hasMutableStatus).isFalse()
+        assertThat(entry.hasSliceSupport).isFalse()
+        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..1f5de2d
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -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 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.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
+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 spaEnvironment = SpaEnvironmentForTest(context)
+
+    @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()).isFalse()
+    }
+
+    @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()).isTrue()
+    }
+
+    @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()).isTrue()
+    }
+
+    @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()).isFalse()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt
new file mode 100644
index 0000000..944ef7f
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.Text
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.platform.SoftwareKeyboardController
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+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.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalComposeUiApi::class)
+@RunWith(AndroidJUnit4::class)
+class KeyboardsTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    lateinit var keyboardController: SoftwareKeyboardController
+
+    @Test
+    fun hideKeyboardAction_callControllerHide() {
+        lateinit var action: () -> Unit
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalSoftwareKeyboardController provides keyboardController) {
+                action = hideKeyboardAction()
+            }
+        }
+
+        action()
+
+        verify(keyboardController).hide()
+    }
+
+    @Test
+    fun rememberLazyListStateAndHideKeyboardWhenStartScroll_notCallHideInitially() {
+        setLazyColumn(scroll = false)
+
+        verify(keyboardController, never()).hide()
+    }
+
+    @Test
+    fun rememberLazyListStateAndHideKeyboardWhenStartScroll_callHideWhenScroll() {
+        setLazyColumn(scroll = true)
+
+        verify(keyboardController).hide()
+    }
+
+    private fun setLazyColumn(scroll: Boolean) {
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalSoftwareKeyboardController provides keyboardController) {
+                val lazyListState = rememberLazyListStateAndHideKeyboardWhenStartScroll()
+                LazyColumn(
+                    modifier = Modifier.size(100.dp),
+                    state = lazyListState,
+                ) {
+                    items(count = 10) {
+                        Text(text = it.toString())
+                    }
+                }
+                if (scroll) {
+                    LaunchedEffect(Unit) {
+                        lazyListState.animateScrollToItem(1)
+                    }
+                }
+            }
+        }
+    }
+}
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/FlowsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt
new file mode 100644
index 0000000..4dcdea9
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt
@@ -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.settingslib.spa.framework.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.count
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class FlowsTest {
+    @Test
+    fun mapItem() = runTest {
+        val inputFlow = flowOf(listOf("A", "BB", "CCC"))
+
+        val outputFlow = inputFlow.mapItem { it.length }
+
+        assertThat(outputFlow.first()).containsExactly(1, 2, 3).inOrder()
+    }
+
+    @Test
+    fun asyncMapItem() = runTest {
+        val inputFlow = flowOf(listOf("A", "BB", "CCC"))
+
+        val outputFlow = inputFlow.asyncMapItem { it.length }
+
+        assertThat(outputFlow.first()).containsExactly(1, 2, 3).inOrder()
+    }
+
+    @Test
+    fun filterItem() = runTest {
+        val inputFlow = flowOf(listOf("A", "BB", "CCC"))
+
+        val outputFlow = inputFlow.filterItem { it.length >= 2 }
+
+        assertThat(outputFlow.first()).containsExactly("BB", "CCC").inOrder()
+    }
+
+    @Test
+    fun waitFirst_otherFlowEmpty() = runTest {
+        val mainFlow = flowOf("A")
+        val otherFlow = emptyFlow<String>()
+
+        val outputFlow = mainFlow.waitFirst(otherFlow)
+
+        assertThat(outputFlow.count()).isEqualTo(0)
+    }
+
+    @Test
+    fun waitFirst_otherFlowOneValue() = runTest {
+        val mainFlow = flowOf("A")
+        val otherFlow = flowOf("B")
+
+        val outputFlow = mainFlow.waitFirst(otherFlow)
+
+        assertThat(outputFlow.toList()).containsExactly("A")
+    }
+
+    @Test
+    fun waitFirst_otherFlowTwoValues() = runTest {
+        val mainFlow = flowOf("A")
+        val otherFlow = flowOf("B", "B")
+
+        val outputFlow = mainFlow.waitFirst(otherFlow)
+
+        assertThat(outputFlow.toList()).containsExactly("A")
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/MessageFormatsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/MessageFormatsTest.kt
new file mode 100644
index 0000000..2017ad1
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/MessageFormatsTest.kt
@@ -0,0 +1,66 @@
+/*
+ * 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 android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.test.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MessageFormatsTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun formatString_one() {
+        val message = context.formatString(R.string.test_quantity_strings, "count" to 1)
+
+        assertThat(message).isEqualTo("There is one song found.")
+    }
+
+    @Test
+    fun formatString_other() {
+        val message = context.formatString(R.string.test_quantity_strings, "count" to 2)
+
+        assertThat(message).isEqualTo("There are 2 songs found.")
+    }
+
+    @Test
+    fun formatString_withParam_one() {
+        val message = context.formatString(
+            R.string.test_quantity_strings_with_param,
+            "count" to 1,
+            "place" to "phone",
+        )
+
+        assertThat(message).isEqualTo("There is one song found in phone.")
+    }
+
+    @Test
+    fun formatString_withParam_other() {
+        val message = context.formatString(
+            R.string.test_quantity_strings_with_param,
+            "count" to 2,
+            "place" to "phone",
+        )
+
+        assertThat(message).isEqualTo("There are 2 songs found in phone.")
+    }
+}
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..0aa846c
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
@@ -0,0 +1,186 @@
+/*
+ * 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[{unset_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=rtStr, string_param=myStr, unset_int_param=null}]"
+        )
+
+        val setAllParam = navArguments.normalize(
+            bundleOf(
+                "string_param" to "myStr",
+                "int_param" to 10,
+                "rt_param" to "rtStr",
+            ),
+            eraseRuntimeValues = true
+        )
+        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/framework/util/SpaIntentTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
new file mode 100644
index 0000000..1854728
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.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.util
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SpaIntentTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment = SpaEnvironmentForTest(context)
+
+    @Before
+    fun setEnvironment() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+    }
+
+    @Test
+    fun testCreateIntent() {
+        val nullPage = SettingsPage.createNull()
+        Truth.assertThat(nullPage.createIntent()).isNull()
+        Truth.assertThat(SettingsEntryBuilder.createInject(nullPage).build().createIntent())
+            .isNull()
+
+        val page = spaEnvironment.createPage("SppHome")
+        val pageIntent = page.createIntent()
+        Truth.assertThat(pageIntent).isNotNull()
+        Truth.assertThat(pageIntent!!.getDestination()).isEqualTo(page.buildRoute())
+        Truth.assertThat(pageIntent.getEntryId()).isNull()
+        Truth.assertThat(pageIntent.getSessionName()).isNull()
+
+        val entry = SettingsEntryBuilder.createInject(page).build()
+        val entryIntent = entry.createIntent(SESSION_SEARCH)
+        Truth.assertThat(entryIntent).isNotNull()
+        Truth.assertThat(entryIntent!!.getDestination()).isEqualTo(page.buildRoute())
+        Truth.assertThat(entryIntent.getEntryId()).isEqualTo(entry.id)
+        Truth.assertThat(entryIntent.getSessionName()).isEqualTo(SESSION_SEARCH)
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt
new file mode 100644
index 0000000..e1d9a28
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.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.settingslib.spa.framework.util
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class StateFlowBridgeTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun stateFlowBridge_initial() = runTest {
+        val stateFlowBridge = StateFlowBridge<String>()
+
+        val flow = stateFlowBridge.flow
+
+        val first = flow.firstWithTimeoutOrNull()
+        assertThat(first).isNull()
+    }
+
+    @Test
+    fun stateFlowBridge_setIfAbsent() = runTest {
+        val stateFlowBridge = StateFlowBridge<String>()
+
+        stateFlowBridge.setIfAbsent("A")
+
+        val first = stateFlowBridge.flow.firstWithTimeoutOrNull()
+        assertThat(first).isEqualTo("A")
+    }
+
+    @Test
+    fun stateFlowBridge_sync() = runTest {
+        val stateFlowBridge = StateFlowBridge<String>()
+
+        composeTestRule.setContent {
+            stateFlowBridge.Sync(stateOf("A"))
+        }
+
+        val first = stateFlowBridge.flow.firstWithTimeoutOrNull()
+        assertThat(first).isEqualTo("A")
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
new file mode 100644
index 0000000..831aded
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
@@ -0,0 +1,184 @@
+/*
+ * 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.search
+
+import android.content.Context
+import android.database.Cursor
+import android.os.Bundle
+import android.os.Parcel
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SppForSearch
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SpaSearchProviderTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment =
+        SpaEnvironmentForTest(context, listOf(SppForSearch.createSettingsPage()))
+    private val searchProvider = SpaSearchProvider()
+    private val pageOwner = spaEnvironment.createPage("SppForSearch")
+
+    @Test
+    fun testQuerySearchStatusData() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+
+        val immutableStatus = searchProvider.querySearchImmutableStatusData()
+        Truth.assertThat(immutableStatus.count).isEqualTo(2)
+        immutableStatus.moveToFirst()
+        immutableStatus.checkValue(
+            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchStaticWithNoStatus")
+        )
+        immutableStatus.checkValue(
+            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+            ColumnEnum.ENTRY_DISABLED,
+            false.toString()
+        )
+
+        immutableStatus.moveToNext()
+        immutableStatus.checkValue(
+            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
+        )
+        immutableStatus.checkValue(
+            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
+        )
+
+        val mutableStatus = searchProvider.querySearchMutableStatusData()
+        Truth.assertThat(mutableStatus.count).isEqualTo(2)
+        mutableStatus.moveToFirst()
+        mutableStatus.checkValue(
+            QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchStaticWithMutableStatus")
+        )
+        mutableStatus.checkValue(
+            QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, false.toString()
+        )
+
+        mutableStatus.moveToNext()
+        mutableStatus.checkValue(
+            QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchDynamicWithMutableStatus")
+        )
+        mutableStatus.checkValue(
+            QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
+        )
+    }
+
+    @Test
+    fun testQuerySearchIndexData() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+
+        val staticData = searchProvider.querySearchStaticData()
+        Truth.assertThat(staticData.count).isEqualTo(2)
+        staticData.moveToFirst()
+        staticData.checkValue(
+            QueryEnum.SEARCH_STATIC_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchStaticWithNoStatus")
+        )
+        staticData.checkValue(
+            QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.SEARCH_TITLE, "SearchStaticWithNoStatus"
+        )
+        staticData.checkValue(
+            QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.SEARCH_KEYWORD, listOf("").toString()
+        )
+        staticData.checkValue(
+            QueryEnum.SEARCH_STATIC_DATA_QUERY,
+            ColumnEnum.SEARCH_PATH,
+            listOf("SearchStaticWithNoStatus", "SppForSearch").toString()
+        )
+        staticData.checkValue(
+            QueryEnum.SEARCH_STATIC_DATA_QUERY,
+            ColumnEnum.INTENT_TARGET_PACKAGE,
+            spaEnvironment.appContext.packageName
+        )
+        staticData.checkValue(
+            QueryEnum.SEARCH_STATIC_DATA_QUERY,
+            ColumnEnum.INTENT_TARGET_CLASS,
+            "com.android.settingslib.spa.tests.testutils.BlankActivity"
+        )
+
+        // Check extras in intent
+        val bundle =
+            staticData.getExtras(QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.INTENT_EXTRAS)
+        Truth.assertThat(bundle).isNotNull()
+        Truth.assertThat(bundle!!.size()).isEqualTo(3)
+        Truth.assertThat(bundle.getString("spaActivityDestination")).isEqualTo("SppForSearch")
+        Truth.assertThat(bundle.getString("highlightEntry"))
+            .isEqualTo(pageOwner.getEntryId("SearchStaticWithNoStatus"))
+        Truth.assertThat(bundle.getString("sessionSource")).isEqualTo("search")
+
+        staticData.moveToNext()
+        staticData.checkValue(
+            QueryEnum.SEARCH_STATIC_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchStaticWithMutableStatus")
+        )
+
+        val dynamicData = searchProvider.querySearchDynamicData()
+        Truth.assertThat(dynamicData.count).isEqualTo(2)
+        dynamicData.moveToFirst()
+        dynamicData.checkValue(
+            QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchDynamicWithMutableStatus")
+        )
+
+        dynamicData.moveToNext()
+        dynamicData.checkValue(
+            QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
+        )
+        dynamicData.checkValue(
+            QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
+            ColumnEnum.SEARCH_KEYWORD,
+            listOf("kw1", "kw2").toString()
+        )
+    }
+}
+
+private fun Cursor.checkValue(query: QueryEnum, column: ColumnEnum, value: String) {
+    Truth.assertThat(getString(query.columnNames.indexOf(column))).isEqualTo(value)
+}
+
+private fun Cursor.getExtras(query: QueryEnum, column: ColumnEnum): Bundle? {
+    val extrasByte = getBlob(query.columnNames.indexOf(column)) ?: return null
+    val parcel = Parcel.obtain()
+    parcel.unmarshall(extrasByte, 0, extrasByte.size)
+    parcel.setDataPosition(0)
+    val bundle = Bundle()
+    bundle.readFromParcel(parcel)
+    return bundle
+}
+
+private fun SettingsPage.getEntryId(name: String): String {
+    return SettingsEntryBuilder.create(this, name).build().id
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
new file mode 100644
index 0000000..1bdba29
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
@@ -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.settingslib.spa.slice
+
+import android.content.Context
+import android.net.Uri
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.lifecycle.Observer
+import androidx.slice.Slice
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SppHome
+import com.android.settingslib.spa.tests.testutils.SppLayer2
+import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsSliceDataRepositoryTest {
+    @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment =
+        SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
+    private val sliceDataRepository by spaEnvironment.sliceDataRepository
+
+    @Test
+    fun getOrBuildSliceDataTest() {
+        // Slice empty
+        assertThat(sliceDataRepository.getOrBuildSliceData(Uri.EMPTY)).isNull()
+
+        // Slice supported
+        val page = SppLayer2.createSettingsPage()
+        val entryId = getUniqueEntryId("Layer2Entry1", page)
+        val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
+        assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2")
+        assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
+        val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
+        assertThat(sliceData).isNotNull()
+        assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri)).isSameInstanceAs(sliceData)
+
+        // Slice unsupported
+        val entryId2 = getUniqueEntryId("Layer2Entry2", page)
+        val sliceUri2 = Uri.Builder().appendSpaParams(page.buildRoute(), entryId2).build()
+        assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2")
+        assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]")
+        assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri2)).isNull()
+    }
+
+    @Test
+    fun getActiveSliceDataTest() {
+        val page = SppLayer2.createSettingsPage()
+        val entryId = getUniqueEntryId("Layer2Entry1", page)
+        val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
+
+        // build slice data first
+        val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
+
+        // slice data is inactive
+        assertThat(sliceData!!.isActive()).isFalse()
+        assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
+
+        // slice data is active
+        val observer = Observer<Slice?> { }
+        sliceData.observeForever(observer)
+        assertThat(sliceData.isActive()).isTrue()
+        assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isSameInstanceAs(sliceData)
+
+        // slice data is inactive again
+        sliceData.removeObserver(observer)
+        assertThat(sliceData.isActive()).isFalse()
+        assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
new file mode 100644
index 0000000..d1c4e51
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.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.settingslib.spa.slice
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import androidx.core.os.bundleOf
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SliceUtilTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment = SpaEnvironmentForTest(context)
+
+    @Test
+    fun sliceUriTest() {
+        assertThat(Uri.EMPTY.getEntryId()).isNull()
+        assertThat(Uri.EMPTY.getDestination()).isNull()
+        assertThat(Uri.EMPTY.getRuntimeArguments().size()).isEqualTo(0)
+        assertThat(Uri.EMPTY.getSliceId()).isNull()
+
+        // valid slice uri
+        val dest = "myRoute"
+        val entryId = "myEntry"
+        val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
+        assertThat(sliceUriWithoutParams.getEntryId()).isEqualTo(entryId)
+        assertThat(sliceUriWithoutParams.getDestination()).isEqualTo(dest)
+        assertThat(sliceUriWithoutParams.getRuntimeArguments().size()).isEqualTo(0)
+        assertThat(sliceUriWithoutParams.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
+
+        val sliceUriWithParams =
+            Uri.Builder().appendSpaParams(dest, entryId, bundleOf("p1" to "v1")).build()
+        assertThat(sliceUriWithParams.getEntryId()).isEqualTo(entryId)
+        assertThat(sliceUriWithParams.getDestination()).isEqualTo(dest)
+        assertThat(sliceUriWithParams.getRuntimeArguments().size()).isEqualTo(1)
+        assertThat(sliceUriWithParams.getSliceId()).isEqualTo("${entryId}_Bundle[{p1=v1}]")
+    }
+
+    @Test
+    fun createBroadcastPendingIntentTest() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+
+        // Empty Slice Uri
+        assertThat(Uri.EMPTY.createBroadcastPendingIntent()).isNull()
+
+        // Valid Slice Uri
+        val dest = "myRoute"
+        val entryId = "myEntry"
+        val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
+        val pendingIntent = sliceUriWithoutParams.createBroadcastPendingIntent()
+        assertThat(pendingIntent).isNotNull()
+        assertThat(pendingIntent!!.isBroadcast).isTrue()
+        assertThat(pendingIntent.isImmutable).isFalse()
+    }
+
+    @Test
+    fun createBrowsePendingIntentTest() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
+
+        // Empty Slice Uri
+        assertThat(Uri.EMPTY.createBrowsePendingIntent()).isNull()
+
+        // Empty Intent
+        assertThat(Intent().createBrowsePendingIntent()).isNull()
+
+        // Valid Slice Uri
+        val dest = "myRoute"
+        val entryId = "myEntry"
+        val sliceUri = Uri.Builder().appendSpaParams(dest, entryId).build()
+        val pendingIntent = sliceUri.createBrowsePendingIntent()
+        assertThat(pendingIntent).isNotNull()
+        assertThat(pendingIntent!!.isActivity).isTrue()
+        assertThat(pendingIntent.isImmutable).isTrue()
+
+        // Valid Intent
+        val intent = Intent().apply {
+            putExtra("spaActivityDestination", dest)
+            putExtra("highlightEntry", entryId)
+        }
+        val pendingIntent2 = intent.createBrowsePendingIntent()
+        assertThat(pendingIntent2).isNotNull()
+        assertThat(pendingIntent2!!.isActivity).isTrue()
+        assertThat(pendingIntent2.isImmutable).isTrue()
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
new file mode 100644
index 0000000..f38bd08
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -0,0 +1,218 @@
+/*
+ * 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.tests.testutils
+
+import android.app.Activity
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.BrowseActivity
+import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.EntrySliceData
+import com.android.settingslib.spa.framework.common.EntryStatusData
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.LogEvent
+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.common.SettingsPageProviderRepository
+import com.android.settingslib.spa.framework.common.SpaEnvironment
+import com.android.settingslib.spa.framework.common.SpaLogger
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
+
+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[key] ?: 0) + 1
+    }
+
+    override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) {
+        val key = EventCountKey(id, event, category)
+        eventCount[key] = (eventCount[key] ?: 0) + 1
+    }
+
+    fun getMessageCount(tag: String, msg: String, category: LogCategory): Int {
+        val key = MsgCountKey("[$tag]$msg", category)
+        return messageCount[key] ?: 0
+    }
+
+    fun getEventCount(id: String, event: LogEvent, category: LogCategory): Int {
+        val key = EventCountKey(id, event, category)
+        return eventCount[key] ?: 0
+    }
+
+    fun reset() {
+        messageCount.clear()
+        eventCount.clear()
+    }
+}
+
+class BlankActivity : BrowseActivity()
+class BlankSliceBroadcastReceiver : BroadcastReceiver() {
+    override fun onReceive(p0: Context?, p1: Intent?) {}
+}
+
+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())
+            .setMacro {
+                SimplePreferenceMacro(
+                    title = "SppHome to Layer1",
+                    clickRoute = name
+                )
+            }
+    }
+
+    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")
+                .setSliceDataFn { _, _ ->
+                    return@setSliceDataFn object : EntrySliceData() {
+                        init {
+                            postValue(null)
+                        }
+                    }
+                }
+                .build(),
+            SettingsEntryBuilder.create(owner, "Layer2Entry2").build(),
+        )
+    }
+}
+
+object SppForSearch : SettingsPageProvider {
+    override val name = "SppForSearch"
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SettingsEntryBuilder.create(owner, "SearchStaticWithNoStatus")
+                .setSearchDataFn { EntrySearchData(title = "SearchStaticWithNoStatus") }
+                .build(),
+            SettingsEntryBuilder.create(owner, "SearchStaticWithMutableStatus")
+                .setHasMutableStatus(true)
+                .setSearchDataFn { EntrySearchData(title = "SearchStaticWithMutableStatus") }
+                .setStatusDataFn { EntryStatusData(isSwitchOff = true) }
+                .build(),
+            SettingsEntryBuilder.create(owner, "SearchDynamicWithMutableStatus")
+                .setIsSearchDataDynamic(true)
+                .setHasMutableStatus(true)
+                .setSearchDataFn { EntrySearchData(title = "SearchDynamicWithMutableStatus") }
+                .setStatusDataFn { EntryStatusData(isDisabled = true) }
+                .build(),
+            SettingsEntryBuilder.create(owner, "SearchDynamicWithImmutableStatus")
+                .setIsSearchDataDynamic(true)
+                .setSearchDataFn {
+                    EntrySearchData(
+                        title = "SearchDynamicWithImmutableStatus",
+                        keyword = listOf("kw1", "kw2")
+                    )
+                }
+                .setStatusDataFn { EntryStatusData(isDisabled = true) }
+                .build(),
+        )
+    }
+}
+
+class SpaEnvironmentForTest(
+    context: Context,
+    rootPages: List<SettingsPage> = emptyList(),
+    override val browseActivityClass: Class<out Activity>? = BlankActivity::class.java,
+    override val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? =
+        BlankSliceBroadcastReceiver::class.java,
+    override val logger: SpaLogger = object : SpaLogger {}
+) : SpaEnvironment(context) {
+
+    override val pageProviderRepository = lazy {
+        SettingsPageProviderRepository(
+            listOf(
+                SppHome, SppLayer1, SppLayer2,
+                SppForSearch,
+                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 },
+                    )
+                },
+            ),
+            rootPages
+        )
+    }
+
+    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/tests/testutils/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
new file mode 100644
index 0000000..ce9b791
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.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.tests.testutils
+
+import android.os.Bundle
+import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.toHashId
+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, eraseRuntimeValues = true)
+    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/widget/IllustrationTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/IllustrationTest.kt
deleted file mode 100644
index 54abec9..0000000
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/IllustrationTest.kt
+++ /dev/null
@@ -1,87 +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.settingslib.spa.widget
-
-import androidx.annotation.DrawableRes
-import androidx.annotation.RawRes
-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.assertIsNotDisplayed
-import androidx.compose.ui.test.filterToOne
-import androidx.compose.ui.test.hasAnyAncestor
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.tests.R
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class IllustrationTest {
-    @get:Rule
-    val composeTestRule = createComposeRule()
-
-    private val DrawableId = SemanticsPropertyKey<Int>("DrawableResId")
-    private var SemanticsPropertyReceiver.drawableId by DrawableId
-
-    @Test
-    fun image_displayed() {
-        val resId = R.drawable.accessibility_captioning_banner
-        composeTestRule.setContent {
-            Illustration(
-                resId = resId,
-                resourceType = ResourceType.IMAGE,
-                modifier = Modifier.semantics { drawableId = resId }
-            )
-        }
-
-        fun hasDrawable(@DrawableRes id: Int): SemanticsMatcher =
-            SemanticsMatcher.expectValue(DrawableId, id)
-
-        val isIllustrationNode = hasAnyAncestor(hasDrawable(resId))
-        composeTestRule.onAllNodes(hasDrawable(resId))
-            .filterToOne(isIllustrationNode)
-            .assertIsDisplayed()
-    }
-
-    private val RawId = SemanticsPropertyKey<Int>("RawResId")
-    private var SemanticsPropertyReceiver.rawId by RawId
-
-    @Test
-    fun empty_lottie_not_displayed() {
-        val resId = R.raw.empty
-        composeTestRule.setContent {
-            Illustration(
-                resId = resId,
-                resourceType = ResourceType.LOTTIE,
-                modifier = Modifier.semantics { rawId = resId }
-            )
-        }
-
-        fun hasRaw(@RawRes id: Int): SemanticsMatcher =
-            SemanticsMatcher.expectValue(RawId, id)
-
-        val isIllustrationNode = hasAnyAncestor(hasRaw(resId))
-        composeTestRule.onAllNodes(hasRaw(resId))
-            .filterToOne(isIllustrationNode)
-            .assertIsNotDisplayed()
-    }
-}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
index 8101a94..f59b0de 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
@@ -17,11 +17,13 @@
 package com.android.settingslib.spa.widget.button
 
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Close
 import androidx.compose.material.icons.outlined.Launch
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.getBoundsInRoot
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
@@ -66,4 +68,19 @@
 
         assertThat(clicked).isTrue()
     }
+
+    @Test
+    fun twoButtons_positionIsAligned() {
+        composeTestRule.setContent {
+            ActionButtons(
+                listOf(
+                    ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                    ActionButton(text = "Close", imageVector = Icons.Outlined.Close) {},
+                )
+            )
+        }
+
+        assertThat(composeTestRule.onNodeWithText("Open").getBoundsInRoot().top)
+            .isEqualTo(composeTestRule.onNodeWithText("Close").getBoundsInRoot().top)
+    }
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt
new file mode 100644
index 0000000..2230d6c
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt
@@ -0,0 +1,98 @@
+/*
+ * 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 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 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(this.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/dialog/SettingsAlertDialogTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt
new file mode 100644
index 0000000..9468f95
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt
@@ -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 com.android.settingslib.spa.widget.dialog
+
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.onDialogText
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsAlertDialogTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_whenDialogNotOpen_notDisplayed() {
+        composeTestRule.setContent {
+            rememberAlertDialogPresenter(title = TITLE)
+        }
+
+        composeTestRule.onDialogText(TITLE).assertDoesNotExist()
+    }
+
+    @Test
+    fun title_displayed() {
+        setAndOpenDialog {
+            rememberAlertDialogPresenter(title = TITLE)
+        }
+
+        composeTestRule.onDialogText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun text_displayed() {
+        setAndOpenDialog {
+            rememberAlertDialogPresenter(text = { Text(TEXT) })
+        }
+
+        composeTestRule.onDialogText(TEXT).assertIsDisplayed()
+    }
+
+    @Test
+    fun confirmButton_displayed() {
+        setAndOpenDialog {
+            rememberAlertDialogPresenter(confirmButton = AlertDialogButton(CONFIRM_TEXT))
+        }
+
+        composeTestRule.onDialogText(CONFIRM_TEXT).assertIsDisplayed()
+    }
+
+    @Test
+    fun confirmButton_clickable() {
+        var confirmButtonClicked = false
+        setAndOpenDialog {
+            rememberAlertDialogPresenter(confirmButton = AlertDialogButton(CONFIRM_TEXT) {
+                confirmButtonClicked = true
+            })
+        }
+
+        composeTestRule.onDialogText(CONFIRM_TEXT).performClick()
+
+        assertThat(confirmButtonClicked).isTrue()
+    }
+
+    @Test
+    fun dismissButton_displayed() {
+        setAndOpenDialog {
+            rememberAlertDialogPresenter(dismissButton = AlertDialogButton(DISMISS_TEXT))
+        }
+
+        composeTestRule.onDialogText(DISMISS_TEXT).assertIsDisplayed()
+    }
+
+    @Test
+    fun dismissButton_clickable() {
+        var dismissButtonClicked = false
+        setAndOpenDialog {
+            rememberAlertDialogPresenter(dismissButton = AlertDialogButton(DISMISS_TEXT) {
+                dismissButtonClicked = true
+            })
+        }
+
+        composeTestRule.onDialogText(DISMISS_TEXT).performClick()
+
+        assertThat(dismissButtonClicked).isTrue()
+    }
+
+    private fun setAndOpenDialog(dialog: @Composable () -> AlertDialogPresenter) {
+        composeTestRule.setContent {
+            val dialogPresenter = dialog()
+            LaunchedEffect(Unit) {
+                dialogPresenter.open()
+            }
+        }
+    }
+
+    private companion object {
+        const val CONFIRM_TEXT = "Confirm"
+        const val DISMISS_TEXT = "Dismiss"
+        const val TITLE = "Title"
+        const val TEXT = "Text"
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/illustration/IllustrationTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/illustration/IllustrationTest.kt
new file mode 100644
index 0000000..105fdc8
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/illustration/IllustrationTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.illustration
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.RawRes
+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.assertIsNotDisplayed
+import androidx.compose.ui.test.filterToOne
+import androidx.compose.ui.test.hasAnyAncestor
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.test.R
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class IllustrationTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val drawableId = SemanticsPropertyKey<Int>("DrawableResId")
+    private var SemanticsPropertyReceiver.drawableId by drawableId
+
+    @Test
+    fun image_displayed() {
+        val resId = R.drawable.accessibility_captioning_banner
+        composeTestRule.setContent {
+            Illustration(
+                resId = resId,
+                resourceType = ResourceType.IMAGE,
+                modifier = Modifier.semantics { drawableId = resId }
+            )
+        }
+
+        fun hasDrawable(@DrawableRes id: Int): SemanticsMatcher =
+            SemanticsMatcher.expectValue(drawableId, id)
+
+        val isIllustrationNode = hasAnyAncestor(hasDrawable(resId))
+        composeTestRule.onAllNodes(hasDrawable(resId))
+            .filterToOne(isIllustrationNode)
+            .assertIsDisplayed()
+    }
+
+    private val rawId = SemanticsPropertyKey<Int>("RawResId")
+    private var SemanticsPropertyReceiver.rawId by rawId
+
+    @Test
+    fun empty_lottie_not_displayed() {
+        val resId = R.raw.empty
+        composeTestRule.setContent {
+            Illustration(
+                resId = resId,
+                resourceType = ResourceType.LOTTIE,
+                modifier = Modifier.semantics { rawId = resId }
+            )
+        }
+
+        fun hasRaw(@RawRes id: Int): SemanticsMatcher =
+            SemanticsMatcher.expectValue(rawId, id)
+
+        val isIllustrationNode = hasAnyAncestor(hasRaw(resId))
+        composeTestRule.onAllNodes(hasRaw(resId))
+            .filterToOne(isIllustrationNode)
+            .assertIsNotDisplayed()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/HomeScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/HomeScaffoldTest.kt
new file mode 100644
index 0000000..6ccbbd0
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/HomeScaffoldTest.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 HomeScaffoldTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_isDisplayed() {
+        composeTestRule.setContent {
+            HomeScaffold(title = TITLE) {
+                Text(text = "AAA")
+                Text(text = "BBB")
+            }
+        }
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun items_areDisplayed() {
+        composeTestRule.setContent {
+            HomeScaffold(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/MoreOptionsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/MoreOptionsTest.kt
new file mode 100644
index 0000000..019a22e
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/MoreOptionsTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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 android.content.Context
+import androidx.appcompat.R
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+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 MoreOptionsTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun moreOptionsAction_collapseAtBegin() {
+        composeTestRule.setContent {
+            MoreOptionsAction {
+                MenuItem(text = ITEM_TEXT) {}
+            }
+        }
+
+        composeTestRule.onNodeWithText(ITEM_TEXT).assertDoesNotExist()
+    }
+
+    @Test
+    fun moreOptionsAction_canExpand() {
+        composeTestRule.setContent {
+            MoreOptionsAction {
+                MenuItem(text = ITEM_TEXT) {}
+            }
+        }
+        composeTestRule.onNodeWithContentDescription(
+            context.getString(R.string.abc_action_menu_overflow_description)
+        ).performClick()
+
+        composeTestRule.onNodeWithText(ITEM_TEXT).assertIsDisplayed()
+    }
+
+    @Test
+    fun moreOptionsAction_itemClicked() {
+        var menuItemClicked = false
+
+        composeTestRule.setContent {
+            MoreOptionsAction {
+                MenuItem(text = ITEM_TEXT) {
+                    menuItemClicked = true
+                }
+            }
+        }
+        composeTestRule.onNodeWithContentDescription(
+            context.getString(R.string.abc_action_menu_overflow_description)
+        ).performClick()
+        composeTestRule.onNodeWithText(ITEM_TEXT).performClick()
+
+        assertThat(menuItemClicked).isTrue()
+    }
+
+    private companion object {
+        const val ITEM_TEXT = "item text"
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt
new file mode 100644
index 0000000..9d0501f
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.ui
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FooterTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun footer_isEmpty() {
+        composeTestRule.setContent {
+            Footer(footerText = "")
+        }
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
+    fun footer_notEmpty_displayed() {
+        composeTestRule.setContent {
+            Footer(footerText = FOOTER_TEXT)
+        }
+
+        composeTestRule.onNodeWithText(FOOTER_TEXT).assertIsDisplayed()
+    }
+
+    private companion object {
+        const val FOOTER_TEXT = "Footer text"
+    }
+}
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
new file mode 100644
index 0000000..2c1e1c2
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+    name: "SpaLibTestUtils",
+
+    srcs: ["src/**/*.kt"],
+
+    static_libs: [
+        "SpaLib",
+        "androidx.arch.core_core-testing",
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.ui_ui-test-junit4",
+        "androidx.compose.ui_ui-test-manifest",
+        "mockito",
+        "truth-prebuilt",
+    ],
+    kotlincflags: [
+        "-Xjvm-default=all",
+    ],
+    min_sdk_version: "31",
+}
diff --git a/packages/SettingsLib/Spa/testutils/AndroidManifest.xml b/packages/SettingsLib/Spa/testutils/AndroidManifest.xml
new file mode 100644
index 0000000..1aa7782
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?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.settingslib.spa.testutils">
+    <uses-sdk android:minSdkVersion="21"/>
+</manifest>
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
new file mode 100644
index 0000000..dd7058d
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+plugins {
+    id 'com.android.library'
+    id 'kotlin-android'
+}
+
+android {
+    compileSdk TARGET_SDK
+    buildToolsVersion = BUILD_TOOLS_VERSION
+
+    defaultConfig {
+        minSdk MIN_SDK
+        targetSdk TARGET_SDK
+    }
+
+    sourceSets {
+        main {
+            kotlin {
+                srcDir "src"
+            }
+            manifest.srcFile "AndroidManifest.xml"
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+        freeCompilerArgs = ["-Xjvm-default=all"]
+    }
+    buildFeatures {
+        compose true
+    }
+    composeOptions {
+        kotlinCompilerExtensionVersion jetpack_compose_compiler_version
+    }
+}
+
+dependencies {
+    api project(":spa")
+
+    api "androidx.arch.core:core-testing:2.1.0"
+    api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
+    api "com.google.truth:truth:1.1.3"
+    api "org.mockito:mockito-core:2.21.0"
+    debugApi "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
+}
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt
new file mode 100644
index 0000000..a5d1f40
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.testutils
+
+import androidx.compose.ui.test.ComposeTimeoutException
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.hasAnyAncestor
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.isDialog
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+
+/** Blocks until the found a semantics node that match the given condition. */
+fun ComposeContentTestRule.waitUntilExists(matcher: SemanticsMatcher) = waitUntil {
+    onAllNodes(matcher).fetchSemanticsNodes().isNotEmpty()
+}
+
+/** Blocks until the timeout is reached. */
+fun ComposeContentTestRule.delay(timeoutMillis: Long = 1_000) = try {
+    waitUntil(timeoutMillis) { false }
+} catch (_: ComposeTimeoutException) {
+    // Expected
+}
+
+/** Finds a text node that within dialog. */
+fun ComposeContentTestRule.onDialogText(text: String): SemanticsNodeInteraction =
+    onNode(hasAnyAncestor(isDialog()) and hasText(text))
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FakeNavControllerWrapper.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FakeNavControllerWrapper.kt
new file mode 100644
index 0000000..5a3044d
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FakeNavControllerWrapper.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.testutils
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import com.android.settingslib.spa.framework.compose.LocalNavController
+import com.android.settingslib.spa.framework.compose.NavControllerWrapper
+
+class FakeNavControllerWrapper : NavControllerWrapper {
+    var navigateCalledWith: String? = null
+    var navigateBackIsCalled = false
+
+    override fun navigate(route: String) {
+        navigateCalledWith = route
+    }
+
+    override fun navigateBack() {
+        navigateBackIsCalled = true
+    }
+
+    @Composable
+    fun Wrapper(content: @Composable () -> Unit) {
+        CompositionLocalProvider(LocalNavController provides this) {
+            content()
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt
new file mode 100644
index 0000000..7a11499
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.testutils
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.withTimeoutOrNull
+
+suspend fun <T> Flow<T>.firstWithTimeoutOrNull(timeMillis: Long = 500): T? =
+    withTimeoutOrNull(timeMillis) {
+        first()
+    }
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt
new file mode 100644
index 0000000..dddda55
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.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.settingslib.spa.testutils
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+
+fun <T> LiveData<T>.getOrAwaitValue(
+    timeout: Long = 1,
+    timeUnit: TimeUnit = TimeUnit.SECONDS,
+    afterObserve: () -> Unit = {},
+): T? {
+    var data: T? = null
+    val latch = CountDownLatch(1)
+    val observer = Observer<T> { newData ->
+        data = newData
+        latch.countDown()
+    }
+    this.observeForever(observer)
+
+    afterObserve()
+
+    try {
+        // Don't wait indefinitely if the LiveData is not set.
+        if (!latch.await(timeout, timeUnit)) {
+            throw TimeoutException("LiveData value was never set.")
+        }
+    } finally {
+        this.removeObserver(observer)
+    }
+
+    return data
+}
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/MockitoHelper.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/MockitoHelper.kt
new file mode 100644
index 0000000..5ba54c1
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/MockitoHelper.kt
@@ -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.settingslib.spa.testutils
+
+import org.mockito.Mockito
+
+/**
+ * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when null is
+ * returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> any(type: Class<T>): T = Mockito.any(type)
+
+inline fun <reified T> any(): T = any(T::class.java)
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SpaTest.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SpaTest.kt
new file mode 100644
index 0000000..a397bb4
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SpaTest.kt
@@ -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.
+ */
+
+package com.android.settingslib.spa.testutils
+
+import java.util.concurrent.TimeoutException
+
+/**
+ * Blocks until the given condition is satisfied.
+ */
+fun waitUntil(timeoutMillis: Long = 1000, condition: () -> Boolean) {
+    val startTime = System.currentTimeMillis()
+    while (!condition()) {
+        // Let Android run measure, draw and in general any other async operations.
+        Thread.sleep(10)
+        if (System.currentTimeMillis() - startTime > timeoutMillis) {
+            throw TimeoutException("Condition still not satisfied after $timeoutMillis ms")
+        }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index 18ae09ea..4a7418f 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -30,7 +30,6 @@
     ],
     kotlincflags: [
         "-Xjvm-default=all",
-        "-opt-in=kotlin.RequiresOptIn",
     ],
 }
 
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-af/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-af/strings.xml
new file mode 100644
index 0000000..3ad7bb0
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-af/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Geen apps nie."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Wys stelsel"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Versteek stelsel"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Toegelaat"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nie toegelaat nie"</string>
+    <string name="version_text" msgid="4001669804596458577">"weergawe <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-am/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-am/strings.xml
new file mode 100644
index 0000000..4c2525bfd
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-am/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"መተግበሪያዎች የሉም።"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"ሥርዓትን አሳይ"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"ሥርዓትን ደብቅ"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"ይፈቀዳል"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"አይፈቀድም"</string>
+    <string name="version_text" msgid="4001669804596458577">"ሥሪት <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ar/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ar/strings.xml
new file mode 100644
index 0000000..436914d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ar/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"ليس هناك أي تطبيقات."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"إظهار عمليات النظام"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"إخفاء عمليات النظام"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"مسموح به"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"غير مسموح به"</string>
+    <string name="version_text" msgid="4001669804596458577">"الإصدار <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-as/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-as/strings.xml
new file mode 100644
index 0000000..c1c88f2
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-as/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"কোনো এপ্‌ নাই।"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"ছিষ্টেম দেখুৱাওক"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"ছিষ্টেম লুকুৱাওক"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"অনুমতি দিয়া হৈছে"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"অনুমতি নাই"</string>
+    <string name="version_text" msgid="4001669804596458577">"সংস্কৰণ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-az/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-az/strings.xml
new file mode 100644
index 0000000..4fc090a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-az/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Tətbiq yoxdur."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Sistemi göstərin"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Sistemi gizlədin"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"İcazə verildi"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"İcazə verilməyib"</string>
+    <string name="version_text" msgid="4001669804596458577">"versiya <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..3708503
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-b+sr+Latn/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Nema aplikacija."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Prikaži sistemske"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Sakrij sistemske"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dozvoljeno"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nije dozvoljeno"</string>
+    <string name="version_text" msgid="4001669804596458577">"verzija <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-be/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-be/strings.xml
new file mode 100644
index 0000000..38fb12b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-be/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Няма праграм."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Паказаць сістэмныя працэсы"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Схаваць сістэмныя працэсы"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Дазволена"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Забаронена"</string>
+    <string name="version_text" msgid="4001669804596458577">"версія <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-bg/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-bg/strings.xml
new file mode 100644
index 0000000..b9b03bf
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-bg/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Няма приложения."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Показване на системните процеси"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Скриване на системните процеси"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Разрешено"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Не е разрешено"</string>
+    <string name="version_text" msgid="4001669804596458577">"версия <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-bn/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-bn/strings.xml
new file mode 100644
index 0000000..b805b3c
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-bn/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"কোনও অ্যাপ নেই।"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"সিস্টেম দেখুন"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"সিস্টেম লুকান"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"অনুমোদিত"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"অননুমোদিত"</string>
+    <string name="version_text" msgid="4001669804596458577">"ভার্সন <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-bs/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-bs/strings.xml
new file mode 100644
index 0000000..9ceb340
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-bs/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Nema aplikacija."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Prikaži sistemske aplikacije"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Sakrij sistemske aplikacije"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dozvoljeno"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nije dozvoljeno"</string>
+    <string name="version_text" msgid="4001669804596458577">"verzija <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ca/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ca/strings.xml
new file mode 100644
index 0000000..00cb41b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ca/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"No hi ha cap aplicació."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Mostra el sistema"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Amaga el sistema"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Amb permís"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Sense permís"</string>
+    <string name="version_text" msgid="4001669804596458577">"versió <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-cs/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-cs/strings.xml
new file mode 100644
index 0000000..7b28f11
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-cs/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Žádné aplikace"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Zobrazit systémové aplikace"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Skrýt systémové aplikace"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Povoleno"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nepovoleno"</string>
+    <string name="version_text" msgid="4001669804596458577">"verze <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-da/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-da/strings.xml
new file mode 100644
index 0000000..f1893be
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-da/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Ingen apps."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Vis system"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Skjul system"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Tilladt"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ikke tilladt"</string>
+    <string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-de/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-de/strings.xml
new file mode 100644
index 0000000..471a7a7
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-de/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Keine Apps."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"System-Apps anzeigen"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"System-Apps ausblenden"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Zulässig"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nicht zulässig"</string>
+    <string name="version_text" msgid="4001669804596458577">"Version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-el/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-el/strings.xml
new file mode 100644
index 0000000..6c46e27
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-el/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Δεν υπάρχουν εφαρμογές."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Εμφάνιση συστήματος"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Απόκρυψη συστήματος"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Επιτρέπεται"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Δεν επιτρέπεται"</string>
+    <string name="version_text" msgid="4001669804596458577">"έκδοση <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rAU/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..de48ff1
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rAU/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"No apps."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Show system"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Hide system"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string>
+    <string name="version_text" msgid="4001669804596458577">"Version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rCA/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..bc88528
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rCA/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"No apps."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Show system"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Hide system"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string>
+    <string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rGB/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..de48ff1
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rGB/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"No apps."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Show system"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Hide system"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string>
+    <string name="version_text" msgid="4001669804596458577">"Version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rIN/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..de48ff1
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rIN/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"No apps."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Show system"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Hide system"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string>
+    <string name="version_text" msgid="4001669804596458577">"Version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rXC/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..c395286
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rXC/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‏‎‏‏‎No apps.‎‏‎‎‏‎"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‎‎‏‎‏‏‎‏‏‏‎‏‏‏‎‏‎‏‏‎‏‏‎‎‎‎‏‏‏‎‎‏‎‎‎Show system‎‏‎‎‏‎"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‎‎‎Hide system‎‏‎‎‏‎"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‏‏‎‏‏‎‎‏‏‎‏‎‎‎‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‏‏‏‎Allowed‎‏‎‎‏‎"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‏‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‏‎‏‏‎‎‎‎‎‎‎Not allowed‎‏‎‎‏‎"</string>
+    <string name="version_text" msgid="4001669804596458577">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‎‎‎‏‏‎‎‏‎‎‏‎‏‏‏‏‏‎‎‎‏‎‎‏‎‏‏‎‏‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎version ‎‏‎‎‏‏‎<xliff:g id="VERSION_NUM">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-es-rUS/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..d211eeb
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-es-rUS/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"No hay apps."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Mostrar sistema"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Ocultar sistema"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitida"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"No se permite"</string>
+    <string name="version_text" msgid="4001669804596458577">"versión <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-es/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-es/strings.xml
new file mode 100644
index 0000000..d907cb8
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-es/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"No hay aplicaciones."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Mostrar sistema"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Ocultar sistema"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitida"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"No permitida"</string>
+    <string name="version_text" msgid="4001669804596458577">"versión <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-et/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-et/strings.xml
new file mode 100644
index 0000000..2be2967
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-et/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Rakendusi pole."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Kuva süsteem"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Peida süsteem"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Lubatud"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Pole lubatud"</string>
+    <string name="version_text" msgid="4001669804596458577">"versioon <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-eu/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-eu/strings.xml
new file mode 100644
index 0000000..7fb2ee28
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-eu/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Ez dago aplikaziorik."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Erakutsi sistemaren aplikazioak"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Ezkutatu sistemaren aplikazioak"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Baimena dauka"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ez dauka baimenik"</string>
+    <string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> bertsioa"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-fa/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-fa/strings.xml
new file mode 100644
index 0000000..7711cac
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-fa/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"برنامه‌ای وجود ندارد."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"نمایش سیستم"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"پنهان کردن سیستم"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"مجاز"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"غیرمجاز"</string>
+    <string name="version_text" msgid="4001669804596458577">"نسخه <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-fi/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-fi/strings.xml
new file mode 100644
index 0000000..af65bfe
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-fi/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Ei sovelluksia."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Näytä järjestelmä"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Piilota järjestelmä"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Sallittu"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ei sallittu"</string>
+    <string name="version_text" msgid="4001669804596458577">"versio <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-fr-rCA/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..31f1ee0
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-fr-rCA/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Aucune application"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Afficher le système"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Masquer le système"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Autorisé"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Non autorisée"</string>
+    <string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-fr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-fr/strings.xml
new file mode 100644
index 0000000..5b7b5e6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-fr/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Aucune appli."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Afficher le système"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Masquer le système"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Autorisé"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Non autorisé"</string>
+    <string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-gl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-gl/strings.xml
new file mode 100644
index 0000000..3d1f9d8
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-gl/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Ningunha aplicación"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Mostrar sistema"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Ocultar sistema"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitida"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Non permitida"</string>
+    <string name="version_text" msgid="4001669804596458577">"versión <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-gu/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-gu/strings.xml
new file mode 100644
index 0000000..c598b70
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-gu/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"કોઈ ઍપ નથી."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"સિસ્ટમ બતાવો"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"સિસ્ટમ છુપાવો"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"મંજૂરી છે"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"મંજૂરી નથી"</string>
+    <string name="version_text" msgid="4001669804596458577">"વર્ઝન <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-hi/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-hi/strings.xml
new file mode 100644
index 0000000..809afd3
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-hi/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"कोई ऐप्लिकेशन नहीं है."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"सिस्टम के ऐप्लिकेशन दिखाएं"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"सिस्टम के ऐप्लिकेशन छिपाएं"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"अनुमति है"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"अनुमति नहीं है"</string>
+    <string name="version_text" msgid="4001669804596458577">"वर्शन <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-hr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-hr/strings.xml
new file mode 100644
index 0000000..1a87974
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-hr/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Nema aplikacija."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Prikaži sustav"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Sakrij sustav"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dopušteno"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nije dopušteno"</string>
+    <string name="version_text" msgid="4001669804596458577">"verzija <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-hu/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-hu/strings.xml
new file mode 100644
index 0000000..1ae7cdf
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-hu/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Nincsenek alkalmazások."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Rendszerfolyamatok megjelenítése"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Rendszerfolyamatok elrejtése"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Engedélyezve"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nem engedélyezett"</string>
+    <string name="version_text" msgid="4001669804596458577">"verzió: <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-hy/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-hy/strings.xml
new file mode 100644
index 0000000..353af77
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-hy/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Հավելվածներ չկան։"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Ցույց տալ համակարգային գործընթացները"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Թաքցնել համակարգային գործընթացները"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Թույլատրված է"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Արգելված է"</string>
+    <string name="version_text" msgid="4001669804596458577">"տարբերակ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-in/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-in/strings.xml
new file mode 100644
index 0000000..8b766b0
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-in/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Tidak ada aplikasi."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Tampilkan sistem"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Sembunyikan sistem"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Diizinkan"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Tidak diizinkan"</string>
+    <string name="version_text" msgid="4001669804596458577">"versi <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-is/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-is/strings.xml
new file mode 100644
index 0000000..cbd412d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-is/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Engin forrit."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Sýna kerfi"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Fela kerfi"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Leyft"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ekki leyft"</string>
+    <string name="version_text" msgid="4001669804596458577">"útgáfa <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-it/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-it/strings.xml
new file mode 100644
index 0000000..d83c70d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-it/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Nessuna app."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Mostra sistema"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Nascondi sistema"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Consentita"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Non consentita"</string>
+    <string name="version_text" msgid="4001669804596458577">"versione <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-iw/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-iw/strings.xml
new file mode 100644
index 0000000..7ed8a6b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-iw/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"אין אפליקציות."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"הצגת תהליכי מערכת"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"הסתרת תהליכי מערכת"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"יש הרשאה"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"אין הרשאה"</string>
+    <string name="version_text" msgid="4001669804596458577">"גרסה <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ja/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ja/strings.xml
new file mode 100644
index 0000000..b12cb5c
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ja/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"アプリはありません。"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"システムアプリを表示"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"システムアプリを表示しない"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"許可"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"許可しない"</string>
+    <string name="version_text" msgid="4001669804596458577">"バージョン <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ka/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ka/strings.xml
new file mode 100644
index 0000000..c01a028
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ka/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"არ არის აპები."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"სისტემის ჩვენება"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"სისტემის დამალვა"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"დაშვებულია"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"დაუშვებელია"</string>
+    <string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> ვერსია"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-kk/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-kk/strings.xml
new file mode 100644
index 0000000..fb94404
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-kk/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Қолданба жоқ."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Жүйені көрсету"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Жүйені жасыру"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Рұқсат етілген"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Рұқсат етілмеген"</string>
+    <string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> нұсқасы"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-km/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-km/strings.xml
new file mode 100644
index 0000000..610123d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-km/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"គ្មាន​កម្មវិធី។"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"បង្ហាញ​ប្រព័ន្ធ"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"លាក់ប្រព័ន្ធ"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"បាន​អនុញ្ញាត"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"មិន​អនុញ្ញាត​ទេ"</string>
+    <string name="version_text" msgid="4001669804596458577">"កំណែ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-kn/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-kn/strings.xml
new file mode 100644
index 0000000..b61c008
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-kn/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"ಯಾವುದೇ ಆ್ಯಪ್‍‍ಗಳಿಲ್ಲ."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"ಸಿಸ್ಟಂ ತೋರಿಸಿ"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"ಸಿಸ್ಟಂ ಮರೆಮಾಡಿ"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"ಅನುಮತಿಸಲಾಗಿದೆ"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ"</string>
+    <string name="version_text" msgid="4001669804596458577">"ಆವೃತ್ತಿ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml
new file mode 100644
index 0000000..660dc0e
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"앱 없음"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"시스템 표시"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"시스템 숨기기"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"허용됨"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"허용되지 않음"</string>
+    <string name="version_text" msgid="4001669804596458577">"버전 <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ky/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ky/strings.xml
new file mode 100644
index 0000000..898ec09
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ky/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Бир дагы колдонмо жок."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Системаны көрсөтүү"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Системаны жашыруу"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Уруксат берилген"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Тыюу салынган"</string>
+    <string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> версиясы"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-lo/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-lo/strings.xml
new file mode 100644
index 0000000..d0f77e4
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-lo/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"ບໍ່ມີແອັບ."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"ສະແດງລະບົບ"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"ເຊື່ອງລະບົບ"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"ອະນຸຍາດແລ້ວ"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ບໍ່ໄດ້ອະນຸຍາດ"</string>
+    <string name="version_text" msgid="4001669804596458577">"ເວີຊັນ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-lt/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-lt/strings.xml
new file mode 100644
index 0000000..7d33f91
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-lt/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Nėra programų"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Rodyti sistemą"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Slėpti sistemą"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Leidžiama"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Neleidžiama"</string>
+    <string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> versija"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-lv/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-lv/strings.xml
new file mode 100644
index 0000000..66dc44d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-lv/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Nav lietotņu."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Rādīt sistēmas procesus"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Slēpt sistēmas procesus"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Atļauja piešķirta"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Atļauja nav piešķirta"</string>
+    <string name="version_text" msgid="4001669804596458577">"versija <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-mk/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-mk/strings.xml
new file mode 100644
index 0000000..db55600
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-mk/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Нема апликации."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Прикажи го системот"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Сокриј го системот"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Со дозволен пристап"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Без дозволен пристап"</string>
+    <string name="version_text" msgid="4001669804596458577">"верзија <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ml/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ml/strings.xml
new file mode 100644
index 0000000..c99d0d3
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ml/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"ആപ്പുകളൊന്നുമില്ല."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"സിസ്‌റ്റം കാണിക്കുക"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"സിസ്‌റ്റം മറയ്ക്കുക"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"അനുവാദം ലഭിച്ചു"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"അനുവാദം ലഭിച്ചില്ല"</string>
+    <string name="version_text" msgid="4001669804596458577">"പതിപ്പ് <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-mn/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-mn/strings.xml
new file mode 100644
index 0000000..bc0864b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-mn/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Апп байхгүй."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Системийг харуулах"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Системийг нуух"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Зөвшөөрсөн"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Зөвшөөрөөгүй"</string>
+    <string name="version_text" msgid="4001669804596458577">"хувилбар <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-mr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-mr/strings.xml
new file mode 100644
index 0000000..55a178b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-mr/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"अ‍ॅप्स नाहीत."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"सिस्टीम दाखवा"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"सिस्टीम लपवा"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"अनुमती असलेले"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"अनुमती नाही"</string>
+    <string name="version_text" msgid="4001669804596458577">"आवृत्ती <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ms/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ms/strings.xml
new file mode 100644
index 0000000..a736de9
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ms/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Tiada aplikasi."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Tunjukkan sistem"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Sembunyikan sistem"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dibenarkan"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Tidak dibenarkan"</string>
+    <string name="version_text" msgid="4001669804596458577">"versi <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-my/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-my/strings.xml
new file mode 100644
index 0000000..3bed608
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-my/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"အက်ပ်မရှိပါ။"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"စနစ်ကိုပြပါ"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"စနစ်ကိုဖျောက်ထားရန်"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"ခွင့်ပြုထားသည်"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ခွင့်ပြုမထားပါ"</string>
+    <string name="version_text" msgid="4001669804596458577">"ဗားရှင်း <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-nb/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-nb/strings.xml
new file mode 100644
index 0000000..d6bd063
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-nb/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Ingen apper."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Vis systemprosesser"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Skjul systemprosesser"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Tillatt"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ikke tillatt"</string>
+    <string name="version_text" msgid="4001669804596458577">"versjon <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ne/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ne/strings.xml
new file mode 100644
index 0000000..97db4ea
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ne/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"कुनै पनि एप छैन।"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"सिस्टम देखाइयोस्"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"सिस्टम लुकाइयोस्"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"अनुमति दिइएका एप"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"अनुमति नदिइएका एप"</string>
+    <string name="version_text" msgid="4001669804596458577">"संस्करण <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-nl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-nl/strings.xml
new file mode 100644
index 0000000..7157e3f
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-nl/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Geen apps."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Systeem-apps tonen"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Systeem-apps verbergen"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Toegestaan"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Niet toegestaan"</string>
+    <string name="version_text" msgid="4001669804596458577">"versie <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-or/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-or/strings.xml
new file mode 100644
index 0000000..24779e3
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-or/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"କୌଣସି ଆପ୍ସ ନାହିଁ।"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"ସିଷ୍ଟମ ଦେଖାନ୍ତୁ"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"ସିଷ୍ଟମକୁ ଲୁଚାନ୍ତୁ"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"ଅନୁମତି ଦିଆଯାଇଛି"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ଅନୁମତି ଦିଆଯାଇନାହିଁ"</string>
+    <string name="version_text" msgid="4001669804596458577">"ଭର୍ସନ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pa/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pa/strings.xml
new file mode 100644
index 0000000..fe1a3eb
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pa/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"ਕੋਈ ਐਪ ਨਹੀਂ।"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"ਸਿਸਟਮ ਦਿਖਾਓ"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"ਸਿਸਟਮ ਲੁਕਾਓ"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"ਆਗਿਆ ਹੈ"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ਆਗਿਆ ਨਹੀਂ ਹੈ"</string>
+    <string name="version_text" msgid="4001669804596458577">"ਵਰਜਨ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pl/strings.xml
new file mode 100644
index 0000000..9d5ba9d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pl/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Brak aplikacji."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Pokaż systemowe"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Ukryj systemowe"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dozwolone"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Niedozwolone"</string>
+    <string name="version_text" msgid="4001669804596458577">"wersja <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pt-rBR/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..18a31d8
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pt-rBR/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Nenhum app."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Mostrar sistema"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Ocultar sistema"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitido"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Não permitido"</string>
+    <string name="version_text" msgid="4001669804596458577">"Versão <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pt-rPT/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..ddf5e51
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pt-rPT/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Sem apps."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Mostrar sistema"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Ocultar sistema"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitida"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Não permitida"</string>
+    <string name="version_text" msgid="4001669804596458577">"versão <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pt/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pt/strings.xml
new file mode 100644
index 0000000..18a31d8
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pt/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Nenhum app."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Mostrar sistema"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Ocultar sistema"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitido"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Não permitido"</string>
+    <string name="version_text" msgid="4001669804596458577">"Versão <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ro/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ro/strings.xml
new file mode 100644
index 0000000..ef58fb8
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ro/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Nu există aplicații."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Afișează procesele de sistem"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Ascunde procesele de sistem"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permisă"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nepermisă"</string>
+    <string name="version_text" msgid="4001669804596458577">"versiunea <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ru/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ru/strings.xml
new file mode 100644
index 0000000..6d69c80
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ru/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Нет приложений."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Показать системные процессы"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Скрыть системные процессы"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Разрешено"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Запрещено"</string>
+    <string name="version_text" msgid="4001669804596458577">"версия <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-si/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-si/strings.xml
new file mode 100644
index 0000000..d2c20e6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-si/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"යෙදුම් නොමැත."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"පද්ධතිය පෙන්වන්න"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"පද්ධතිය සඟවන්න"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"ඉඩ දුන්"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ඉඩ නොදෙන"</string>
+    <string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> අනුවාදය"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sk/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sk/strings.xml
new file mode 100644
index 0000000..0d0984f
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sk/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Žiadne aplikácie."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Zobraziť systémové procesy"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Skryť systémové procesy"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Povolené"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nepovolené"</string>
+    <string name="version_text" msgid="4001669804596458577">"verzia <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sl/strings.xml
new file mode 100644
index 0000000..c8bd15a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sl/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Ni aplikacij."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Prikaži sistemske procese"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Skrij sistemske procese"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dovoljeno"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ni dovoljeno"</string>
+    <string name="version_text" msgid="4001669804596458577">"različica <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sq/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sq/strings.xml
new file mode 100644
index 0000000..112868a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sq/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Asnjë aplikacion"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Shfaq sistemin"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Fshih sistemin"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Lejohet"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nuk lejohet"</string>
+    <string name="version_text" msgid="4001669804596458577">"versioni <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sr/strings.xml
new file mode 100644
index 0000000..4c99d60
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sr/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Нема апликација."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Прикажи системске"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Сакриј системске"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Дозвољено"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Није дозвољено"</string>
+    <string name="version_text" msgid="4001669804596458577">"верзија <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sv/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sv/strings.xml
new file mode 100644
index 0000000..1dd5efd
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sv/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Inga appar."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Visa systemet"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Dölj systemet"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Tillåts"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Tillåts inte"</string>
+    <string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sw/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sw/strings.xml
new file mode 100644
index 0000000..a0ee70c
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sw/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Hakuna programu yoyote."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Onyesha mfumo"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Ficha mfumo"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Inaruhusiwa"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Hairuhusiwi"</string>
+    <string name="version_text" msgid="4001669804596458577">"toleo la <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ta/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ta/strings.xml
new file mode 100644
index 0000000..36d64e8
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ta/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"ஆப்ஸ் இல்லை."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"சிஸ்டம் ஆப்ஸைக் காட்டு"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"சிஸ்டம் ஆப்ஸை மறை"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"அனுமதிக்கப்பட்டது"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"அனுமதிக்கப்படவில்லை"</string>
+    <string name="version_text" msgid="4001669804596458577">"பதிப்பு <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-te/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-te/strings.xml
new file mode 100644
index 0000000..a908dd4
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-te/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"యాప్‌లు ఏవి లేవు."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"సిస్టమ్ ప్రాసెస్‌లను చూపించండి"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"సిస్టమ్‌ను దాచండి"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"అనుమతించబడినవి"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"అనుమతించబడలేదు"</string>
+    <string name="version_text" msgid="4001669804596458577">"వెర్షన్ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-th/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-th/strings.xml
new file mode 100644
index 0000000..1d7db1a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-th/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"ไม่มีแอป"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"แสดงระบบ"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"ซ่อนระบบ"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"อนุญาต"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ไม่อนุญาต"</string>
+    <string name="version_text" msgid="4001669804596458577">"เวอร์ชัน <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-tl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-tl/strings.xml
new file mode 100644
index 0000000..fb56559a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-tl/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Walang app."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Ipakita ang system"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Itago ang system"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Pinapahintulutan"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Hindi pinapahintulutan"</string>
+    <string name="version_text" msgid="4001669804596458577">"bersyon <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-tr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-tr/strings.xml
new file mode 100644
index 0000000..5f5722b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-tr/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Uygulama yok"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Sistemi göster"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Sistemi gizle"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"İzin veriliyor"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"İzin verilmiyor"</string>
+    <string name="version_text" msgid="4001669804596458577">"sürüm: <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-uk/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-uk/strings.xml
new file mode 100644
index 0000000..a8632ca
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-uk/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Додатків немає."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Показати системні додатки"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Сховати системні додатки"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Дозволено"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Заборонено"</string>
+    <string name="version_text" msgid="4001669804596458577">"версія <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ur/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ur/strings.xml
new file mode 100644
index 0000000..4b969bb
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ur/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"کوئی ایپ نہیں ہے۔"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"سسٹم دکھائیں"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"سسٹم چھپائیں"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"اجازت ہے"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"اجازت نہیں ہے"</string>
+    <string name="version_text" msgid="4001669804596458577">"ورژن <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-uz/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-uz/strings.xml
new file mode 100644
index 0000000..aed34e6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-uz/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Hech narsa topilmadi."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Tizimga oid jarayonlar"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Tizimga oid jarayonlarni berkitish"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Ruxsat berilgan"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ruxsat berilmagan"</string>
+    <string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> versiya"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-vi/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-vi/strings.xml
new file mode 100644
index 0000000..75700c7
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-vi/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Không có ứng dụng nào."</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Hiện hệ thống"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Ẩn hệ thống"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Được phép"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Không được phép"</string>
+    <string name="version_text" msgid="4001669804596458577">"phiên bản <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-zh-rCN/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..2c864cb
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-zh-rCN/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"没有应用。"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"显示系统进程"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"隐藏系统进程"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"已允许"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"不允许"</string>
+    <string name="version_text" msgid="4001669804596458577">"版本 <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-zh-rHK/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..667a10a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-zh-rHK/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"沒有應用程式。"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"顯示系統程序"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"隱藏系統程序"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"允許"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"不允許"</string>
+    <string name="version_text" msgid="4001669804596458577">"版本 <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..667a10a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"沒有應用程式。"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"顯示系統程序"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"隱藏系統程序"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"允許"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"不允許"</string>
+    <string name="version_text" msgid="4001669804596458577">"版本 <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-zu/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-zu/strings.xml
new file mode 100644
index 0000000..d3a614a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-zu/strings.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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="no_applications" msgid="5800789569715871963">"Awekho ama-app"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"Bonisa isistimu"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Fihla isistimu"</string>
+    <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Kuvumelekile"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Akuvumelekile"</string>
+    <string name="version_text" msgid="4001669804596458577">"Uhlobo <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
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..76f6611 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
@@ -16,17 +16,22 @@
 
 package com.android.settingslib.spaprivileged.framework.common
 
+import android.app.ActivityManager
 import android.app.AlarmManager
 import android.app.AppOpsManager
 import android.app.admin.DevicePolicyManager
 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
 import android.permission.PermissionControllerManager
 
+/** The [ActivityManager] instance. */
+val Context.activityManager get() = getSystemService(ActivityManager::class.java)!!
+
 /** The [AlarmManager] instance. */
 val Context.alarmManager get() = getSystemService(AlarmManager::class.java)!!
 
@@ -36,6 +41,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/framework/compose/DisposableBroadcastReceiverAsUser.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
index a7de4ce..b2ea4a0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
@@ -23,7 +23,6 @@
 import android.os.UserHandle
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.lifecycle.Lifecycle
@@ -34,24 +33,25 @@
  */
 @Composable
 fun DisposableBroadcastReceiverAsUser(
-    userId: Int,
     intentFilter: IntentFilter,
+    userHandle: UserHandle,
+    onStart: () -> Unit = {},
     onReceive: (Intent) -> Unit,
 ) {
-    val broadcastReceiver = remember {
-        object : BroadcastReceiver() {
+    val context = LocalContext.current
+    val lifecycleOwner = LocalLifecycleOwner.current
+    DisposableEffect(lifecycleOwner) {
+        val broadcastReceiver = object : BroadcastReceiver() {
             override fun onReceive(context: Context, intent: Intent) {
                 onReceive(intent)
             }
         }
-    }
-    val context = LocalContext.current
-    val lifecycleOwner = LocalLifecycleOwner.current
-    DisposableEffect(lifecycleOwner) {
         val observer = LifecycleEventObserver { _, event ->
             if (event == Lifecycle.Event.ON_START) {
                 context.registerReceiverAsUser(
-                    broadcastReceiver, UserHandle.of(userId), intentFilter, null, null)
+                    broadcastReceiver, userHandle, intentFilter, null, null
+                )
+                onStart()
             } else if (event == Lifecycle.Event.ON_STOP) {
                 context.unregisterReceiver(broadcastReceiver)
             }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
index 373b57f..6999908 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
@@ -33,7 +33,8 @@
      *
      * @return the [AppRecord] list which will be displayed.
      */
-    fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>): Flow<List<T>>
+    fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>): Flow<List<T>> =
+        recordListFlow
 
     /**
      * This function is called when the App List's loading is finished and displayed to the user.
@@ -54,10 +55,18 @@
     )
 
     /**
+     * Gets the group title of this item.
+     *
+     * Note: Items should be sorted by group in [getComparator] first, this [getGroupTitle] will not
+     * change the list order.
+     */
+    fun getGroupTitle(option: Int, record: T): String? = null
+
+    /**
      * Gets the summary for the given app record.
      *
      * @return null if no summary should be displayed.
      */
     @Composable
-    fun getSummary(option: Int, record: T): State<String>?
+    fun getSummary(option: Int, record: T): State<String>? = null
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index ee89003..4c144b2 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -21,18 +21,15 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.async
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
 
 /**
  * The config used to load the App List.
  */
-internal data class AppListConfig(
+data class AppListConfig(
     val userId: Int,
     val showInstantApps: Boolean,
 )
@@ -40,36 +37,42 @@
 /**
  * The repository to load the App List data.
  */
-internal class AppListRepository(context: Context) {
+internal interface AppListRepository {
+    /** Loads the list of [ApplicationInfo]. */
+    suspend fun loadApps(config: AppListConfig): List<ApplicationInfo>
+
+    /** Gets the flow of predicate that could used to filter system app. */
+    fun showSystemPredicate(
+        userIdFlow: Flow<Int>,
+        showSystemFlow: Flow<Boolean>,
+    ): Flow<(app: ApplicationInfo) -> Boolean>
+}
+
+
+internal class AppListRepositoryImpl(context: Context) : AppListRepository {
     private val packageManager = context.packageManager
 
-    fun loadApps(configFlow: Flow<AppListConfig>): Flow<List<ApplicationInfo>> = configFlow
-        .map { loadApps(it) }
-        .flowOn(Dispatchers.Default)
+    override suspend fun loadApps(config: AppListConfig): List<ApplicationInfo> = coroutineScope {
+        val hiddenSystemModulesDeferred = async {
+            packageManager.getInstalledModules(0)
+                .filter { it.isHidden }
+                .map { it.packageName }
+                .toSet()
+        }
+        val flags = PackageManager.ApplicationInfoFlags.of(
+            (PackageManager.MATCH_DISABLED_COMPONENTS or
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
+        )
+        val installedApplicationsAsUser =
+            packageManager.getInstalledApplicationsAsUser(flags, config.userId)
 
-    private suspend fun loadApps(config: AppListConfig): List<ApplicationInfo> {
-        return coroutineScope {
-            val hiddenSystemModulesDeferred = async {
-                packageManager.getInstalledModules(0)
-                    .filter { it.isHidden }
-                    .map { it.packageName }
-                    .toSet()
-            }
-            val flags = PackageManager.ApplicationInfoFlags.of(
-                (PackageManager.MATCH_DISABLED_COMPONENTS or
-                    PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
-            )
-            val installedApplicationsAsUser =
-                packageManager.getInstalledApplicationsAsUser(flags, config.userId)
-
-            val hiddenSystemModules = hiddenSystemModulesDeferred.await()
-            installedApplicationsAsUser.filter { app ->
-                app.isInAppList(config.showInstantApps, hiddenSystemModules)
-            }
+        val hiddenSystemModules = hiddenSystemModulesDeferred.await()
+        installedApplicationsAsUser.filter { app ->
+            app.isInAppList(config.showInstantApps, hiddenSystemModules)
         }
     }
 
-    fun showSystemPredicate(
+    override fun showSystemPredicate(
         userIdFlow: Flow<Int>,
         showSystemFlow: Flow<Boolean>,
     ): Flow<(app: ApplicationInfo) -> Boolean> =
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
index 1e487da..650b278 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.settingslib.spaprivileged.model.app
 
 import android.app.Application
+import android.content.Context
 import android.content.pm.ApplicationInfo
 import android.icu.text.Collator
 import androidx.lifecycle.AndroidViewModel
@@ -27,12 +28,16 @@
 import java.util.concurrent.ConcurrentHashMap
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.plus
 
 internal data class AppListData<T : AppRecord>(
@@ -43,9 +48,15 @@
         AppListData(appEntries.filter(predicate), option)
 }
 
-@OptIn(ExperimentalCoroutinesApi::class)
 internal class AppListViewModel<T : AppRecord>(
     application: Application,
+) : AppListViewModelImpl<T>(application)
+
+@OptIn(ExperimentalCoroutinesApi::class)
+internal open class AppListViewModelImpl<T : AppRecord>(
+    application: Application,
+    appListRepositoryFactory: (Context) -> AppListRepository = ::AppListRepositoryImpl,
+    appRepositoryFactory: (Context) -> AppRepository = ::AppRepositoryImpl,
 ) : AndroidViewModel(application) {
     val appListConfig = StateFlowBridge<AppListConfig>()
     val listModel = StateFlowBridge<AppListModel<T>>()
@@ -53,16 +64,18 @@
     val option = StateFlowBridge<Int>()
     val searchQuery = StateFlowBridge<String>()
 
-    private val appListRepository = AppListRepository(application)
-    private val appRepository = AppRepositoryImpl(application)
+    private val appListRepository = appListRepositoryFactory(application)
+    private val appRepository = appRepositoryFactory(application)
     private val collator = Collator.getInstance().freeze()
     private val labelMap = ConcurrentHashMap<String, String>()
-    private val scope = viewModelScope + Dispatchers.Default
+    private val scope = viewModelScope + Dispatchers.IO
 
     private val userIdFlow = appListConfig.flow.map { it.userId }
 
+    private val appsStateFlow = MutableStateFlow<List<ApplicationInfo>?>(null)
+
     private val recordListFlow = listModel.flow
-        .flatMapLatest { it.transform(userIdFlow, appListRepository.loadApps(appListConfig.flow)) }
+        .flatMapLatest { it.transform(userIdFlow, appsStateFlow.filterNotNull()) }
         .shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
 
     private val systemFilteredFlow =
@@ -83,6 +96,12 @@
         scheduleOnFirstLoaded()
     }
 
+    fun reloadApps() {
+        viewModelScope.launch {
+            appsStateFlow.value = appListRepository.loadApps(appListConfig.flow.first())
+        }
+    }
+
     private fun filterAndSort(option: Int) = listModel.flow.flatMapLatest { listModel ->
         listModel.filter(userIdFlow, option, systemFilteredFlow)
             .asyncMapItem { record ->
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index 71cf23c..c08169e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.spaprivileged.model.app
 
-import android.app.AppOpsManager
 import android.app.AppOpsManager.MODE_ALLOWED
 import android.app.AppOpsManager.MODE_ERRORED
 import android.app.AppOpsManager.Mode
@@ -25,34 +24,41 @@
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.map
+import com.android.settingslib.spaprivileged.framework.common.appOpsManager
+
+interface IAppOpsController {
+    val mode: LiveData<Int>
+    val isAllowed: LiveData<Boolean>
+        get() = mode.map { it == MODE_ALLOWED }
+
+    fun setAllowed(allowed: Boolean)
+
+    @Mode
+    fun getMode(): Int
+}
 
 class AppOpsController(
     context: Context,
     private val app: ApplicationInfo,
     private val op: Int,
-) {
-    private val appOpsManager = checkNotNull(context.getSystemService(AppOpsManager::class.java))
+) : IAppOpsController {
+    private val appOpsManager = context.appOpsManager
 
-    val mode: LiveData<Int>
+    override val mode: LiveData<Int>
         get() = _mode
-    val isAllowed: LiveData<Boolean>
-        get() = _mode.map { it == MODE_ALLOWED }
 
-    fun setAllowed(allowed: Boolean) {
+    override fun setAllowed(allowed: Boolean) {
         val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
         appOpsManager.setMode(op, app.uid, app.packageName, mode)
         _mode.postValue(mode)
     }
 
     @Mode
-    fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
+    override fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
 
     private val _mode = object : MutableLiveData<Int>() {
         override fun onActive() {
             postValue(getMode())
         }
-
-        override fun onInactive() {
-        }
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index 34f12af..90710db 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -22,8 +22,10 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.produceState
+import androidx.compose.ui.res.stringResource
 import com.android.settingslib.Utils
 import com.android.settingslib.spa.framework.compose.rememberContext
+import com.android.settingslib.spaprivileged.R
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 
@@ -34,7 +36,12 @@
     fun loadLabel(app: ApplicationInfo): String
 
     @Composable
-    fun produceLabel(app: ApplicationInfo): State<String>
+    fun produceLabel(app: ApplicationInfo) =
+        produceState(initialValue = stringResource(R.string.summary_placeholder), app) {
+            withContext(Dispatchers.IO) {
+                value = loadLabel(app)
+            }
+        }
 
     @Composable
     fun produceIcon(app: ApplicationInfo): State<Drawable?>
@@ -46,13 +53,6 @@
     override fun loadLabel(app: ApplicationInfo): String = app.loadLabel(packageManager).toString()
 
     @Composable
-    override fun produceLabel(app: ApplicationInfo) = produceState(initialValue = "", app) {
-        withContext(Dispatchers.Default) {
-            value = app.loadLabel(packageManager).toString()
-        }
-    }
-
-    @Composable
     override fun produceIcon(app: ApplicationInfo) =
         produceState<Drawable?>(initialValue = null, app) {
             withContext(Dispatchers.Default) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
index 215d22c..cb0bfc6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
@@ -26,43 +26,76 @@
 
 private const val TAG = "PackageManagers"
 
-object PackageManagers {
-    private val iPackageManager by lazy { AppGlobals.getPackageManager() }
-
-    fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? =
-        getPackageInfoAsUser(packageName, 0, userId)
-
-    fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo? =
-        PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
+interface IPackageManagers {
+    fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo?
+    fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo?
 
     /** Checks whether a package is installed for a given user. */
-    fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean =
+    fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean
+    fun ApplicationInfo.hasRequestPermission(permission: String): Boolean
+
+    /** Checks whether a permission is currently granted to the application. */
+    fun ApplicationInfo.hasGrantPermission(permission: String): Boolean
+
+    suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String>
+    fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo?
+}
+
+object PackageManagers : IPackageManagers by PackageManagersImpl(PackageManagerWrapperImpl)
+
+internal interface PackageManagerWrapper {
+    fun getPackageInfoAsUserCached(
+        packageName: String,
+        flags: Long,
+        userId: Int,
+    ): PackageInfo?
+}
+
+internal object PackageManagerWrapperImpl : PackageManagerWrapper {
+    override fun getPackageInfoAsUserCached(
+        packageName: String,
+        flags: Long,
+        userId: Int,
+    ): PackageInfo? = PackageManager.getPackageInfoAsUserCached(packageName, flags, userId)
+}
+
+internal class PackageManagersImpl(
+    private val packageManagerWrapper: PackageManagerWrapper,
+) : IPackageManagers {
+    private val iPackageManager by lazy { AppGlobals.getPackageManager() }
+
+    override fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? =
+        getPackageInfoAsUser(packageName, 0, userId)
+
+    override fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo? =
+        PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
+
+    override fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean =
         getApplicationInfoAsUser(packageName, userId)?.hasFlag(ApplicationInfo.FLAG_INSTALLED)
             ?: false
 
-    fun ApplicationInfo.hasRequestPermission(permission: String): Boolean {
+    override fun ApplicationInfo.hasRequestPermission(permission: String): Boolean {
         val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
         return packageInfo?.requestedPermissions?.let {
             permission in it
         } ?: false
     }
 
-    fun ApplicationInfo.hasGrantPermission(permission: String): Boolean {
+    override fun ApplicationInfo.hasGrantPermission(permission: String): Boolean {
         val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
-            ?: return false
-        val index = packageInfo.requestedPermissions.indexOf(permission)
+        val index = packageInfo?.requestedPermissions?.indexOf(permission) ?: return false
         return index >= 0 &&
             packageInfo.requestedPermissionsFlags[index].hasFlag(REQUESTED_PERMISSION_GRANTED)
     }
 
-    suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String> =
+    override suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String> =
         iPackageManager.getAppOpPermissionPackages(permission, userId).asIterable().asyncFilter {
             iPackageManager.isPackageAvailable(it, userId)
         }.toSet()
 
-    fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
+    override fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
         try {
-            PackageManager.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
+            packageManagerWrapper.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
         } catch (e: PackageManager.NameNotFoundException) {
             Log.w(TAG, "getPackageInfoAsUserCached() failed", e)
             null
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index b1adc9d..ae362c8 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -20,27 +20,41 @@
 import android.content.Context
 import android.os.UserHandle
 import android.os.UserManager
-import androidx.lifecycle.liveData
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.settingslib.RestrictedLockUtils
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
 import com.android.settingslib.RestrictedLockUtilsInternal
 import com.android.settingslib.spaprivileged.R
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
 
 data class Restrictions(
     val userId: Int,
     val keys: List<String>,
 )
 
-sealed class RestrictedMode
+sealed interface RestrictedMode
 
-object NoRestricted : RestrictedMode()
+object NoRestricted : RestrictedMode
 
-object BaseUserRestricted : RestrictedMode()
+object BaseUserRestricted : RestrictedMode
 
-data class BlockedByAdmin(
-    val enterpriseRepository: EnterpriseRepository,
-    val enforcedAdmin: EnforcedAdmin,
-) : RestrictedMode() {
-    fun getSummary(checked: Boolean?): String = when (checked) {
+interface BlockedByAdmin : RestrictedMode {
+    fun getSummary(checked: Boolean?): String
+    fun sendShowAdminSupportDetailsIntent()
+}
+
+private data class BlockedByAdminImpl(
+    private val context: Context,
+    private val enforcedAdmin: EnforcedAdmin,
+) : BlockedByAdmin {
+    private val enterpriseRepository by lazy { EnterpriseRepository(context) }
+
+    override fun getSummary(checked: Boolean?) = when (checked) {
         true -> enterpriseRepository.getEnterpriseString(
             Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY, R.string.enabled_by_admin
         )
@@ -49,18 +63,33 @@
         )
         else -> ""
     }
+
+    override fun sendShowAdminSupportDetailsIntent() {
+        RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, enforcedAdmin)
+    }
 }
 
-class RestrictionsProvider(
+interface RestrictionsProvider {
+    @Composable
+    fun restrictedModeState(): State<RestrictedMode?>
+}
+
+typealias RestrictionsProviderFactory = (Context, Restrictions) -> RestrictionsProvider
+
+internal class RestrictionsProviderImpl(
     private val context: Context,
     private val restrictions: Restrictions,
-) {
+) : RestrictionsProvider {
     private val userManager by lazy { UserManager.get(context) }
-    private val enterpriseRepository by lazy { EnterpriseRepository(context) }
 
-    val restrictedMode = liveData {
+    private val restrictedMode = flow {
         emit(getRestrictedMode())
-    }
+    }.flowOn(Dispatchers.IO)
+
+    @OptIn(ExperimentalLifecycleComposeApi::class)
+    @Composable
+    override fun restrictedModeState() =
+        restrictedMode.collectAsStateWithLifecycle(initialValue = null)
 
     private fun getRestrictedMode(): RestrictedMode {
         for (key in restrictions.keys) {
@@ -71,12 +100,7 @@
         for (key in restrictions.keys) {
             RestrictedLockUtilsInternal
                 .checkIfRestrictionEnforced(context, key, restrictions.userId)
-                ?.let {
-                    return BlockedByAdmin(
-                        enterpriseRepository = enterpriseRepository,
-                        enforcedAdmin = it,
-                    )
-                }
+                ?.let { return BlockedByAdminImpl(context = context, enforcedAdmin = it) }
         }
         return NoRestricted
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index 9611b13..16ca70f 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -101,5 +101,5 @@
 @Composable
 internal fun AppLabel(app: ApplicationInfo) {
     val appRepository = rememberAppRepository()
-    SettingsTitle(appRepository.produceLabel(app))
+    SettingsTitle(title = appRepository.produceLabel(app), useMediumWeight = true)
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
index 8b19c5b..0b45da6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
@@ -16,11 +16,12 @@
 
 package com.android.settingslib.spaprivileged.template.app
 
+import android.content.pm.PackageInfo
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.Footer
-import com.android.settingslib.spaprivileged.model.app.PackageManagers
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 
 @Composable
 fun AppInfoPage(
@@ -28,18 +29,16 @@
     packageName: String,
     userId: Int,
     footerText: String,
-    content: @Composable () -> Unit,
+    packageManagers: IPackageManagers,
+    content: @Composable PackageInfo.() -> Unit,
 ) {
+    val packageInfo = remember(packageName, userId) {
+        packageManagers.getPackageInfoAsUser(packageName, userId)
+    } ?: return
     RegularScaffold(title = title) {
-        val appInfoProvider = remember {
-            PackageManagers.getPackageInfoAsUser(packageName, userId)?.let { packageInfo ->
-                AppInfoProvider(packageInfo)
-            }
-        } ?: return@RegularScaffold
+        remember(packageInfo) { AppInfoProvider(packageInfo) }.AppInfo(displayVersion = true)
 
-        appInfoProvider.AppInfo(displayVersion = true)
-
-        content()
+        packageInfo.content()
 
         Footer(footerText)
     }
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 3cd8378..2e0d853 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
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.spaprivileged.template.app
 
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.lazy.LazyColumn
@@ -31,8 +34,11 @@
 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.widget.ui.CategoryTitle
 import com.android.settingslib.spa.widget.ui.PlaceholderTitle
 import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
+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
@@ -41,6 +47,22 @@
 import kotlinx.coroutines.Dispatchers
 
 private const val TAG = "AppList"
+private const val CONTENT_TYPE_HEADER = "header"
+
+data class AppListState(
+    val showSystem: State<Boolean>,
+    val option: State<Int>,
+    val searchQuery: State<String>,
+)
+
+data class AppListInput<T : AppRecord>(
+    val config: AppListConfig,
+    val listModel: AppListModel<T>,
+    val state: AppListState,
+    val header: @Composable () -> Unit,
+    val appItem: @Composable AppListItemModel<T>.() -> Unit,
+    val bottomPadding: Dp,
+)
 
 /**
  * The template to render an App List.
@@ -48,24 +70,24 @@
  * This UI element will take the remaining space on the screen to show the App List.
  */
 @Composable
-internal fun <T : AppRecord> AppList(
-    appListConfig: AppListConfig,
-    listModel: AppListModel<T>,
-    showSystem: State<Boolean>,
-    option: State<Int>,
-    searchQuery: State<String>,
-    appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
-    bottomPadding: Dp,
+fun <T : AppRecord> AppListInput<T>.AppList() {
+    AppListImpl { loadAppListData(config, listModel, state) }
+}
+
+@Composable
+internal fun <T : AppRecord> AppListInput<T>.AppListImpl(
+    appListDataSupplier: @Composable () -> State<AppListData<T>?>,
 ) {
-    LogCompositions(TAG, appListConfig.userId.toString())
-    val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
-    AppListWidget(appListData, listModel, appItem, bottomPadding)
+    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,
 ) {
@@ -81,32 +103,55 @@
             state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
             contentPadding = PaddingValues(bottom = bottomPadding),
         ) {
+            item(contentType = CONTENT_TYPE_HEADER) {
+                header()
+            }
+
             items(count = list.size, key = { option to list[it].record.app.packageName }) {
+                remember(list) { listModel.getGroupTitleIfFirst(option, list, it) }
+                    ?.let { group -> CategoryTitle(title = group) }
+
                 val appEntry = list[it]
                 val summary = listModel.getSummary(option, appEntry.record) ?: "".toState()
-                val itemModel = remember(appEntry) {
+                appItem(remember(appEntry) {
                     AppListItemModel(appEntry.record, appEntry.label, summary)
-                }
-                appItem(itemModel)
+                })
             }
         }
     }
 }
 
-@Composable
-private fun <T : AppRecord> loadAppEntries(
-    appListConfig: AppListConfig,
-    listModel: AppListModel<T>,
-    showSystem: State<Boolean>,
-    option: State<Int>,
-    searchQuery: State<String>,
-): State<AppListData<T>?> {
-    val viewModel: AppListViewModel<T> = viewModel(key = appListConfig.userId.toString())
-    viewModel.appListConfig.setIfAbsent(appListConfig)
-    viewModel.listModel.setIfAbsent(listModel)
-    viewModel.showSystem.Sync(showSystem)
-    viewModel.option.Sync(option)
-    viewModel.searchQuery.Sync(searchQuery)
+/** Returns group title if this is the first item of the group. */
+private fun <T : AppRecord> AppListModel<T>.getGroupTitleIfFirst(
+    option: Int,
+    list: List<AppEntry<T>>,
+    index: Int,
+): String? = getGroupTitle(option, list[index].record)?.takeIf {
+    index == 0 || it != getGroupTitle(option, list[index - 1].record)
+}
 
-    return viewModel.appListDataFlow.collectAsState(null, Dispatchers.Default)
+@Composable
+private fun <T : AppRecord> loadAppListData(
+    config: AppListConfig,
+    listModel: AppListModel<T>,
+    state: AppListState,
+): State<AppListData<T>?> {
+    val viewModel: AppListViewModel<T> = viewModel(key = config.userId.toString())
+    viewModel.appListConfig.setIfAbsent(config)
+    viewModel.listModel.setIfAbsent(listModel)
+    viewModel.showSystem.Sync(state.showSystem)
+    viewModel.option.Sync(state.option)
+    viewModel.searchQuery.Sync(state.searchQuery)
+
+    DisposableBroadcastReceiverAsUser(
+        intentFilter = IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply {
+            addAction(Intent.ACTION_PACKAGE_REMOVED)
+            addAction(Intent.ACTION_PACKAGE_CHANGED)
+            addDataScheme("package")
+        },
+        userHandle = UserHandle.of(config.userId),
+        onStart = { viewModel.reloadApps() },
+    ) { viewModel.reloadApps() }
+
+    return viewModel.appListDataFlow.collectAsState(null, Dispatchers.IO)
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
index ac3f8ff..6d0d7d6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
@@ -28,23 +28,20 @@
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 
-class AppListItemModel<T : AppRecord>(
+data class AppListItemModel<T : AppRecord>(
     val record: T,
     val label: String,
     val summary: State<String>,
 )
 
 @Composable
-fun <T : AppRecord> AppListItem(
-    itemModel: AppListItemModel<T>,
-    onClick: () -> Unit,
-) {
+fun <T : AppRecord> AppListItemModel<T>.AppListItem(onClick: () -> Unit) {
     Preference(remember {
         object : PreferenceModel {
-            override val title = itemModel.label
-            override val summary = itemModel.summary
+            override val title = label
+            override val summary = this@AppListItem.summary
             override val icon = @Composable {
-                AppIcon(app = itemModel.record.app, size = SettingsDimension.appIconItemSize)
+                AppIcon(app = record.app, size = SettingsDimension.appIconItemSize)
             }
             override val onClick = onClick
         }
@@ -58,7 +55,6 @@
         val record = object : AppRecord {
             override val app = LocalContext.current.applicationInfo
         }
-        val itemModel = AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState())
-        AppListItem(itemModel) {}
+        AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState()).AppListItem {}
     }
 }
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 2953367..cb35fb0 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
@@ -18,8 +18,6 @@
 
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -27,6 +25,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction
+import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
 import com.android.settingslib.spa.widget.scaffold.SearchScaffold
 import com.android.settingslib.spa.widget.ui.Spinner
 import com.android.settingslib.spaprivileged.R
@@ -37,6 +36,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,13 +45,19 @@
     listModel: AppListModel<T>,
     showInstantApps: Boolean = false,
     primaryUserOnly: Boolean = false,
-    appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+    moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
+    header: @Composable () -> Unit = {},
+    appList: @Composable AppListInput<T>.() -> Unit = { AppList() },
+    appItem: @Composable AppListItemModel<T>.() -> Unit,
 ) {
     val showSystem = rememberSaveable { mutableStateOf(false) }
     SearchScaffold(
         title = title,
         actions = {
-            ShowSystemAction(showSystem.value) { showSystem.value = it }
+            MoreOptionsAction {
+                ShowSystemAction(showSystem.value) { showSystem.value = it }
+                moreOptions()
+            }
         },
     ) { bottomPadding, searchQuery ->
         WorkProfilePager(primaryUserOnly) { userInfo ->
@@ -58,33 +65,34 @@
                 val options = remember { listModel.getSpinnerOptions() }
                 val selectedOption = rememberSaveable { mutableStateOf(0) }
                 Spinner(options, selectedOption.value) { selectedOption.value = it }
-                AppList(
-                    appListConfig = AppListConfig(
+                val appListInput = AppListInput(
+                    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,
                 )
+                appList(appListInput)
             }
         }
     }
 }
 
 @Composable
-private fun ShowSystemAction(showSystem: Boolean, setShowSystem: (showSystem: Boolean) -> Unit) {
-    MoreOptionsAction { onDismissRequest ->
-        val menuText = if (showSystem) R.string.menu_hide_system else R.string.menu_show_system
-        DropdownMenuItem(
-            text = { Text(stringResource(menuText)) },
-            onClick = {
-                onDismissRequest()
-                setShowSystem(!showSystem)
-            },
-        )
+private fun MoreOptionsScope.ShowSystemAction(
+    showSystem: Boolean,
+    setShowSystem: (showSystem: Boolean) -> Unit,
+) {
+    val menuText = if (showSystem) R.string.menu_hide_system else R.string.menu_show_system
+    MenuItem(text = stringResource(menuText)) {
+        setShowSystem(!showSystem)
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
index 5290bec..452971b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
@@ -9,8 +9,7 @@
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 
 @Composable
-fun <T : AppRecord> AppListSwitchItem(
-    itemModel: AppListItemModel<T>,
+fun <T : AppRecord> AppListItemModel<T>.AppListSwitchItem(
     onClick: () -> Unit,
     checked: State<Boolean?>,
     changeable: State<Boolean>,
@@ -19,14 +18,14 @@
     TwoTargetSwitchPreference(
         model = remember {
             object : SwitchPreferenceModel {
-                override val title = itemModel.label
-                override val summary = itemModel.summary
+                override val title = label
+                override val summary = this@AppListSwitchItem.summary
                 override val checked = checked
                 override val changeable = changeable
                 override val onCheckedChange = onCheckedChange
             }
         },
-        icon = { AppIcon(itemModel.record.app, SettingsDimension.appIconItemSize) },
+        icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
         onClick = onClick,
     )
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index c6f41d3..a357832 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -25,11 +25,12 @@
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.livedata.observeAsState
 import androidx.compose.runtime.remember
+import com.android.settingslib.spa.framework.util.filterItem
 import com.android.settingslib.spaprivileged.model.app.AppOpsController
 import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.IAppOpsController
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.model.app.PackageManagers
-import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasGrantPermission
-import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasRequestPermission
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
@@ -37,21 +38,24 @@
 data class AppOpPermissionRecord(
     override val app: ApplicationInfo,
     val hasRequestPermission: Boolean,
-    var appOpsController: AppOpsController,
+    var appOpsController: IAppOpsController,
 ) : AppRecord
 
-abstract class AppOpPermissionListModel(private val context: Context) :
-    TogglePermissionAppListModel<AppOpPermissionRecord> {
+abstract class AppOpPermissionListModel(
+    private val context: Context,
+    private val packageManagers: IPackageManagers = PackageManagers,
+) : TogglePermissionAppListModel<AppOpPermissionRecord> {
 
     abstract val appOp: Int
     abstract val permission: String
 
+    /** These not changeable packages will also be hidden from app list. */
     private val notChangeablePackages =
         setOf("android", "com.android.systemui", context.packageName)
 
     override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
         userIdFlow.map { userId ->
-            PackageManagers.getAppOpPermissionPackages(userId, permission)
+            packageManagers.getAppOpPermissionPackages(userId, permission)
         }.combine(appListFlow) { packageNames, appList ->
             appList.map { app ->
                 AppOpPermissionRecord(
@@ -64,14 +68,12 @@
 
     override fun transformItem(app: ApplicationInfo) = AppOpPermissionRecord(
         app = app,
-        hasRequestPermission = app.hasRequestPermission(permission),
+        hasRequestPermission = with(packageManagers) { app.hasRequestPermission(permission) },
         appOpsController = AppOpsController(context = context, app = app, op = appOp),
     )
 
     override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
-        recordListFlow.map { recordList ->
-            recordList.filter { it.hasRequestPermission }
-        }
+        recordListFlow.filterItem(::isChangeable)
 
     /**
      * Defining the default behavior as permissible as long as the package requested this permission
@@ -85,7 +87,9 @@
                 when (mode.value) {
                     null -> null
                     MODE_ALLOWED -> true
-                    MODE_DEFAULT -> record.app.hasGrantPermission(permission)
+                    MODE_DEFAULT -> with(packageManagers) {
+                        record.app.hasGrantPermission(permission)
+                    }
                     else -> false
                 }
             }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index de5a4a2..5ae5ada 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.content.pm.ApplicationInfo
 import android.os.Bundle
+import androidx.annotation.VisibleForTesting
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
@@ -38,23 +39,15 @@
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
 import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.model.app.PackageManagers
 import com.android.settingslib.spaprivileged.model.app.toRoute
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
 import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
 import kotlinx.coroutines.Dispatchers
 
-private const val ENTRY_NAME = "AllowControl"
-private const val PERMISSION = "permission"
-private const val PACKAGE_NAME = "rt_packageName"
-private const val USER_ID = "rt_userId"
-private const val PAGE_NAME = "TogglePermissionAppInfoPage"
-private val PAGE_PARAMETER = listOf(
-    navArgument(PERMISSION) { type = NavType.StringType },
-    navArgument(PACKAGE_NAME) { type = NavType.StringType },
-    navArgument(USER_ID) { type = NavType.IntType },
-)
-
 internal class TogglePermissionAppInfoPageProvider(
     private val appListTemplate: TogglePermissionAppListTemplate,
 ) : SettingsPageProvider {
@@ -64,11 +57,7 @@
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
         val owner = SettingsPage.create(name, parameter = parameter, arguments = arguments)
-        val entryList = mutableListOf<SettingsEntry>()
-        entryList.add(
-            SettingsEntryBuilder.create(ENTRY_NAME, owner).setIsAllowSearch(false).build()
-        )
-        return entryList
+        return listOf(SettingsEntryBuilder.create("AllowControl", owner).build())
     }
 
     @Composable
@@ -76,11 +65,22 @@
         val permissionType = arguments?.getString(PERMISSION)!!
         val packageName = arguments.getString(PACKAGE_NAME)!!
         val userId = arguments.getInt(USER_ID)
-        val listModel = appListTemplate.rememberModel(permissionType)
-        TogglePermissionAppInfoPage(listModel, packageName, userId)
+        appListTemplate.rememberModel(permissionType)
+            .TogglePermissionAppInfoPage(packageName, userId)
     }
 
     companion object {
+        private const val PAGE_NAME = "TogglePermissionAppInfoPage"
+        private const val PERMISSION = "permission"
+        private const val PACKAGE_NAME = "rt_packageName"
+        private const val USER_ID = "rt_userId"
+
+        private val PAGE_PARAMETER = listOf(
+            navArgument(PERMISSION) { type = NavType.StringType },
+            navArgument(PACKAGE_NAME) { type = NavType.StringType },
+            navArgument(USER_ID) { type = NavType.IntType },
+        )
+
         @Composable
         fun navigator(permissionType: String, app: ApplicationInfo) =
             navigator(route = "$PAGE_NAME/$permissionType/${app.toRoute()}")
@@ -116,43 +116,36 @@
     }
 }
 
+@VisibleForTesting
 @Composable
-private fun TogglePermissionAppInfoPage(
-    listModel: TogglePermissionAppListModel<out AppRecord>,
+internal fun TogglePermissionAppListModel<out AppRecord>.TogglePermissionAppInfoPage(
     packageName: String,
     userId: Int,
+    packageManagers: IPackageManagers = PackageManagers,
+    restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
 ) {
     AppInfoPage(
-        title = stringResource(listModel.pageTitleResId),
+        title = stringResource(pageTitleResId),
         packageName = packageName,
         userId = userId,
-        footerText = stringResource(listModel.footerResId),
+        footerText = stringResource(footerResId),
+        packageManagers = packageManagers,
     ) {
-        val model = createSwitchModel(listModel, packageName, userId) ?: return@AppInfoPage
-        LaunchedEffect(model, Dispatchers.Default) {
-            model.initState()
-        }
-        RestrictedSwitchPreference(model, Restrictions(userId, listModel.switchRestrictionKeys))
+        val model = createSwitchModel(applicationInfo)
+        val restrictions = Restrictions(userId, switchRestrictionKeys)
+        RestrictedSwitchPreference(model, restrictions, restrictionsProviderFactory)
     }
 }
 
 @Composable
-private fun <T : AppRecord> createSwitchModel(
-    listModel: TogglePermissionAppListModel<T>,
-    packageName: String,
-    userId: Int,
-): TogglePermissionSwitchModel<T>? {
-    val record = remember {
-        PackageManagers.getApplicationInfoAsUser(packageName, userId)?.let { app ->
-            listModel.transformItem(app)
-        }
-    } ?: return null
-
+private fun <T : AppRecord> TogglePermissionAppListModel<T>.createSwitchModel(
+    app: ApplicationInfo,
+): TogglePermissionSwitchModel<T> {
     val context = LocalContext.current
-    val isAllowed = listModel.isAllowed(record)
-    return remember {
-        TogglePermissionSwitchModel(context, listModel, record, isAllowed)
-    }
+    val record = remember(app) { transformItem(app) }
+    val isAllowed = isAllowed(record)
+    return remember(record) { TogglePermissionSwitchModel(context, this, record, isAllowed) }
+        .also { model -> LaunchedEffect(model, Dispatchers.IO) { model.initState() } }
 }
 
 private class TogglePermissionSwitchModel<T : AppRecord>(
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index ec7d75e..00eb607 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -22,7 +22,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.livedata.observeAsState
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
@@ -43,7 +42,7 @@
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.userId
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
-import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
 import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
 import kotlinx.coroutines.flow.Flow
 
@@ -71,7 +70,6 @@
             entryList.add(
                 SettingsEntryBuilder.createLinkFrom("${ENTRY_NAME}_$category", appListPage)
                     .setLink(toPage = appInfoPage)
-                    .setIsAllowSearch(false)
                     .build()
             )
         }
@@ -93,12 +91,11 @@
         AppListPage(
             title = stringResource(listModel.pageTitleResId),
             listModel = internalListModel,
-        ) { itemModel ->
+        ) {
             AppListItem(
-                itemModel = itemModel,
                 onClick = TogglePermissionAppInfoPageProvider.navigator(
                     permissionType = permissionType,
-                    app = itemModel.record.app,
+                    app = record.app,
                 ),
             )
         }
@@ -121,7 +118,7 @@
                 parameter = PAGE_PARAMETER,
                 arguments = bundleOf(PERMISSION to permissionType)
             )
-            return SettingsEntryBuilder.createInject(owner = appListPage).setIsAllowSearch(false)
+            return SettingsEntryBuilder.createInject(owner = appListPage)
                 .setUiLayoutFn {
                     val listModel = rememberContext(listModelSupplier)
                     Preference(
@@ -146,9 +143,7 @@
         listModel.filter(userIdFlow, recordListFlow)
 
     @Composable
-    override fun getSummary(option: Int, record: T): State<String> {
-        return getSummary(record)
-    }
+    override fun getSummary(option: Int, record: T) = getSummary(record)
 
     @Composable
     fun getSummary(record: T): State<String> {
@@ -157,27 +152,27 @@
                 userId = record.app.userId,
                 keys = listModel.switchRestrictionKeys,
             )
-            RestrictionsProvider(context, restrictions)
+            RestrictionsProviderImpl(context, restrictions)
         }
-        val restrictedMode = restrictionsProvider.restrictedMode.observeAsState()
+        val restrictedMode = restrictionsProvider.restrictedModeState()
         val allowed = listModel.isAllowed(record)
         return remember {
             derivedStateOf {
                 RestrictedSwitchPreference.getSummary(
                     context = context,
                     restrictedMode = restrictedMode.value,
-                    noRestrictedSummary = getNoRestrictedSummary(allowed),
+                    summaryIfNoRestricted = getSummaryIfNoRestricted(allowed),
                     checked = allowed,
                 ).value
             }
         }
     }
 
-    private fun getNoRestrictedSummary(allowed: State<Boolean?>) = derivedStateOf {
+    private fun getSummaryIfNoRestricted(allowed: State<Boolean?>) = derivedStateOf {
         when (allowed.value) {
             true -> context.getString(R.string.app_permission_summary_allowed)
             false -> context.getString(R.string.app_permission_summary_not_allowed)
-            else -> context.getString(R.string.summary_placeholder)
+            null -> context.getString(R.string.summary_placeholder)
         }
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index 31fd3ad..b08b6df 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -22,12 +22,13 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.livedata.observeAsState
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.semantics.Role
-import com.android.settingslib.RestrictedLockUtils
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.state.ToggleableState
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
@@ -37,33 +38,40 @@
 import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
-import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
 
 @Composable
-fun RestrictedSwitchPreference(model: SwitchPreferenceModel, restrictions: Restrictions) {
+fun RestrictedSwitchPreference(
+    model: SwitchPreferenceModel,
+    restrictions: Restrictions,
+    restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
+) {
     if (restrictions.keys.isEmpty()) {
         SwitchPreference(model)
         return
     }
     val context = LocalContext.current
-    val restrictionsProvider = remember { RestrictionsProvider(context, restrictions) }
-    val restrictedMode = restrictionsProvider.restrictedMode.observeAsState().value ?: return
+    val restrictionsProvider = remember(restrictions) {
+        restrictionsProviderFactory(context, restrictions)
+    }
+    val restrictedMode = restrictionsProvider.restrictedModeState().value
     val restrictedSwitchModel = remember(restrictedMode) {
         RestrictedSwitchPreferenceModel(context, model, restrictedMode)
     }
-    Box(remember { restrictedSwitchModel.getModifier() }) {
+    restrictedSwitchModel.RestrictionWrapper {
         SwitchPreference(restrictedSwitchModel)
     }
 }
 
-object RestrictedSwitchPreference {
+internal object RestrictedSwitchPreference {
     fun getSummary(
         context: Context,
         restrictedMode: RestrictedMode?,
-        noRestrictedSummary: State<String>,
+        summaryIfNoRestricted: State<String>,
         checked: State<Boolean?>,
     ): State<String> = when (restrictedMode) {
-        is NoRestricted -> noRestrictedSummary
+        is NoRestricted -> summaryIfNoRestricted
         is BaseUserRestricted -> stateOf(context.getString(R.string.disabled))
         is BlockedByAdmin -> derivedStateOf { restrictedMode.getSummary(checked.value) }
         null -> stateOf(context.getString(R.string.summary_placeholder))
@@ -71,43 +79,64 @@
 }
 
 private class RestrictedSwitchPreferenceModel(
-    private val context: Context,
+    context: Context,
     model: SwitchPreferenceModel,
-    private val restrictedMode: RestrictedMode,
+    private val restrictedMode: RestrictedMode?,
 ) : SwitchPreferenceModel {
     override val title = model.title
 
     override val summary = RestrictedSwitchPreference.getSummary(
         context = context,
         restrictedMode = restrictedMode,
-        noRestrictedSummary = model.summary,
+        summaryIfNoRestricted = model.summary,
         checked = model.checked,
     )
 
     override val checked = when (restrictedMode) {
+        null -> stateOf(null)
         is NoRestricted -> model.checked
         is BaseUserRestricted -> stateOf(false)
         is BlockedByAdmin -> model.checked
     }
 
     override val changeable = when (restrictedMode) {
+        null -> stateOf(false)
         is NoRestricted -> model.changeable
         is BaseUserRestricted -> stateOf(false)
         is BlockedByAdmin -> stateOf(false)
     }
 
     override val onCheckedChange = when (restrictedMode) {
+        null -> null
         is NoRestricted -> model.onCheckedChange
-        is BaseUserRestricted -> null
+        // Need to pass a non null onCheckedChange to enable semantics ToggleableState, although
+        // since changeable is false this will not be called.
+        is BaseUserRestricted -> model.onCheckedChange
+        // Pass null since semantics ToggleableState is provided in RestrictionWrapper.
         is BlockedByAdmin -> null
     }
 
-    fun getModifier(): Modifier = when (restrictedMode) {
-        is BlockedByAdmin -> Modifier.clickable(role = Role.Switch) {
-            RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
-                context, restrictedMode.enforcedAdmin
-            )
+    @Composable
+    fun RestrictionWrapper(content: @Composable () -> Unit) {
+        if (restrictedMode !is BlockedByAdmin) {
+            content()
+            return
         }
-        else -> Modifier
+        Box(
+            Modifier
+                .clickable(
+                    role = Role.Switch,
+                    onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
+                )
+                .semantics {
+                    this.toggleableState = ToggleableState(checked.value)
+                },
+        ) { content() }
+    }
+
+    private fun ToggleableState(value: Boolean?) = when (value) {
+        true -> ToggleableState.On
+        false -> ToggleableState.Off
+        null -> ToggleableState.Indeterminate
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
new file mode 100644
index 0000000..8c1421a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.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.settingslib.spaprivileged.template.scaffold
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+
+@Composable
+fun MoreOptionsScope.RestrictedMenuItem(
+    text: String,
+    restrictions: Restrictions,
+    onClick: () -> Unit,
+) {
+    RestrictedMenuItemImpl(text, restrictions, onClick, ::RestrictionsProviderImpl)
+}
+
+@Composable
+internal fun MoreOptionsScope.RestrictedMenuItemImpl(
+    text: String,
+    restrictions: Restrictions,
+    onClick: () -> Unit,
+    restrictionsProviderFactory: (Context, Restrictions) -> RestrictionsProvider,
+) {
+    val context = LocalContext.current
+    val restrictionsProvider = remember(restrictions) {
+        restrictionsProviderFactory(context, restrictions)
+    }
+    val restrictedMode = restrictionsProvider.restrictedModeState().value
+    MenuItem(text = text, enabled = restrictedMode !is BaseUserRestricted) {
+        when (restrictedMode) {
+            is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent()
+            else -> onClick()
+        }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
index 5afe21e..5fa4f77 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -31,14 +31,9 @@
     ],
 
     static_libs: [
-        "androidx.compose.ui_ui-test-junit4",
-        "androidx.compose.ui_ui-test-manifest",
+        "SpaLibTestUtils",
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "mockito-target-minus-junit4",
-        "truth-prebuilt",
-    ],
-    kotlincflags: [
-        "-opt-in=kotlin.RequiresOptIn",
     ],
 }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
index c4f490e..8d384e8 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
+++ b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.settingslib.spaprivileged.tests">
+    package="com.android.settingslib.spaprivileged.test">
 
     <application>
         <uses-library android:name="android.test.runner" />
@@ -24,5 +24,5 @@
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
         android:label="Tests for SpaPrivilegedLib"
-        android:targetPackage="com.android.settingslib.spaprivileged.tests" />
+        android:targetPackage="com.android.settingslib.spaprivileged.test" />
 </manifest>
diff --git a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
new file mode 100644
index 0000000..bdc0ba8
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
@@ -0,0 +1,35 @@
+<!--
+  ~ 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>
+    <!-- Test Permission title. [DO NOT TRANSLATE] -->
+    <string name="test_permission_title" translatable="false">Test Permission</string>
+
+    <!-- Test Permission switch title. [DO NOT TRANSLATE] -->
+    <string name="test_permission_switch_title" translatable="false">Allow Test Permission</string>
+
+    <!-- Test Permission footer. [DO NOT TRANSLATE] -->
+    <string name="test_permission_footer" translatable="false">Test Permission is for testing.</string>
+
+    <!-- Test App Op Permission title. [DO NOT TRANSLATE] -->
+    <string name="test_app_op_permission_title" translatable="false">Test App Op Permission</string>
+
+    <!-- Test App Op Permission switch title. [DO NOT TRANSLATE] -->
+    <string name="test_app_op_permission_switch_title" translatable="false">Allow Test App Op Permission</string>
+
+    <!-- Test App Op Permission footer. [DO NOT TRANSLATE] -->
+    <string name="test_app_op_permission_footer" translatable="false">Test App Op Permission is for testing.</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
new file mode 100644
index 0000000..01f4cc6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.framework.compose
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.junit4.createComposeRule
+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.any
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class DisposableBroadcastReceiverAsUserTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    private lateinit var context: Context
+
+    private var registeredBroadcastReceiver: BroadcastReceiver? = null
+
+    @Before
+    fun setUp() {
+        whenever(context.registerReceiverAsUser(any(), any(), any(), any(), any()))
+            .thenAnswer {
+                registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver
+                null
+            }
+    }
+
+    @Test
+    fun broadcastReceiver_registered() {
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                DisposableBroadcastReceiverAsUser(IntentFilter(), USER_HANDLE) {}
+            }
+        }
+
+        assertThat(registeredBroadcastReceiver).isNotNull()
+    }
+
+    @Test
+    fun broadcastReceiver_isCalledOnReceive() {
+        var onReceiveIsCalled = false
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                DisposableBroadcastReceiverAsUser(IntentFilter(), USER_HANDLE) {
+                    onReceiveIsCalled = true
+                }
+            }
+        }
+
+        registeredBroadcastReceiver!!.onReceive(context, Intent())
+
+        assertThat(onReceiveIsCalled).isTrue()
+    }
+
+    @Test
+    fun broadcastReceiver_onStartIsCalled() {
+        var onStartIsCalled = false
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                DisposableBroadcastReceiverAsUser(
+                    intentFilter = IntentFilter(),
+                    userHandle = USER_HANDLE,
+                    onStart = { onStartIsCalled = true },
+                    onReceive = {},
+                )
+            }
+        }
+
+        assertThat(onStartIsCalled).isTrue()
+    }
+
+    private companion object {
+        val USER_HANDLE: UserHandle = UserHandle.of(0)
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index 5d5a24e..c4f2df2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -24,9 +24,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.toList
-import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Rule
@@ -36,18 +33,14 @@
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.eq
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
-
-private const val USER_ID = 0
+import org.mockito.Mockito.`when` as whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class AppListRepositoryTest {
-
-    @JvmField
-    @Rule
+    @get:Rule
     val mockito: MockitoRule = MockitoJUnit.rule()
 
     @Mock
@@ -80,36 +73,28 @@
             packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
         ).thenReturn(emptyList())
 
-        repository = AppListRepository(context)
+        repository = AppListRepositoryImpl(context)
     }
 
     @Test
     fun notShowInstantApps() = runTest {
         val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
 
-        val appListFlow = repository.loadApps(flowOf(appListConfig))
+        val appListFlow = repository.loadApps(appListConfig)
 
-        launch {
-            val flowValues = mutableListOf<List<ApplicationInfo>>()
-            appListFlow.toList(flowValues)
-            assertThat(flowValues).hasSize(1)
-
-            assertThat(flowValues[0]).containsExactly(normalApp)
-        }
+        assertThat(appListFlow).containsExactly(normalApp)
     }
 
     @Test
     fun showInstantApps() = runTest {
         val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = true)
 
-        val appListFlow = repository.loadApps(flowOf(appListConfig))
+        val appListFlow = repository.loadApps(appListConfig)
 
-        launch {
-            val flowValues = mutableListOf<List<ApplicationInfo>>()
-            appListFlow.toList(flowValues)
-            assertThat(flowValues).hasSize(1)
+        assertThat(appListFlow).containsExactly(normalApp, instantApp)
+    }
 
-            assertThat(flowValues[0]).containsExactly(normalApp, instantApp)
-        }
+    private companion object {
+        const val USER_ID = 0
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
new file mode 100644
index 0000000..b9c875b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -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.settingslib.spaprivileged.model.app
+
+import android.app.Application
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.util.mapItem
+import com.android.settingslib.spa.testutils.waitUntil
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class AppListViewModelTest {
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    private lateinit var application: Application
+
+    private val listModel = TestAppListModel()
+
+    private fun createViewModel(): AppListViewModelImpl<TestAppRecord> {
+        val viewModel = AppListViewModelImpl<TestAppRecord>(
+            application = application,
+            appListRepositoryFactory = { FakeAppListRepository },
+            appRepositoryFactory = { FakeAppRepository },
+        )
+        viewModel.appListConfig.setIfAbsent(CONFIG)
+        viewModel.listModel.setIfAbsent(listModel)
+        viewModel.showSystem.setIfAbsent(false)
+        viewModel.option.setIfAbsent(0)
+        viewModel.searchQuery.setIfAbsent("")
+        viewModel.reloadApps()
+        return viewModel
+    }
+
+    @Test
+    fun appListDataFlow() = runTest {
+        val viewModel = createViewModel()
+
+        val (appEntries, option) = viewModel.appListDataFlow.first()
+
+        assertThat(appEntries).hasSize(1)
+        assertThat(appEntries[0].record.app).isSameInstanceAs(APP)
+        assertThat(appEntries[0].label).isEqualTo(LABEL)
+        assertThat(option).isEqualTo(0)
+    }
+
+    @Test
+    fun onFirstLoaded_calledWhenLoaded() = runTest {
+        val viewModel = createViewModel()
+
+        viewModel.appListDataFlow.first()
+
+        waitUntil { listModel.onFirstLoadedCalled }
+    }
+
+    private object FakeAppListRepository : AppListRepository {
+        override suspend fun loadApps(config: AppListConfig) = listOf(APP)
+
+        override fun showSystemPredicate(
+            userIdFlow: Flow<Int>,
+            showSystemFlow: Flow<Boolean>,
+        ): Flow<(app: ApplicationInfo) -> Boolean> = flowOf { true }
+    }
+
+    private object FakeAppRepository : AppRepository {
+        override fun loadLabel(app: ApplicationInfo) = LABEL
+
+        @Composable
+        override fun produceIcon(app: ApplicationInfo) = stateOf(null)
+    }
+
+    private companion object {
+        const val USER_ID = 0
+        const val PACKAGE_NAME = "package.name"
+        const val LABEL = "Label"
+        val CONFIG = AppListConfig(userId = USER_ID, showInstantApps = false)
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+        }
+    }
+}
+
+private data class TestAppRecord(override val app: ApplicationInfo) : AppRecord
+
+private class TestAppListModel : AppListModel<TestAppRecord> {
+    var onFirstLoadedCalled = false
+
+    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+        appListFlow.mapItem(::TestAppRecord)
+
+    override suspend fun onFirstLoaded(recordList: List<TestAppRecord>) {
+        onFirstLoadedCalled = true
+    }
+}
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
index 4207490..4002655 100644
--- 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
@@ -40,8 +40,7 @@
 
 @RunWith(AndroidJUnit4::class)
 class PackageManagerExtTest {
-    @JvmField
-    @Rule
+    @get:Rule
     val mockito: MockitoRule = MockitoJUnit.rule()
 
     @Mock
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
new file mode 100644
index 0000000..6c31f4b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+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 PackageManagersTest {
+
+    private val fakePackageManagerWrapper = FakePackageManagerWrapper()
+
+    private val packageManagersImpl = PackageManagersImpl(fakePackageManagerWrapper)
+
+    @Test
+    fun hasGrantPermission_packageInfoIsNull_returnFalse() {
+        fakePackageManagerWrapper.fakePackageInfo = null
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isFalse()
+    }
+
+    @Test
+    fun hasGrantPermission_requestedPermissionsIsNull_returnFalse() {
+        fakePackageManagerWrapper.fakePackageInfo = PackageInfo()
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isFalse()
+    }
+
+    @Test
+    fun hasGrantPermission_notRequested_returnFalse() {
+        fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+            requestedPermissions = arrayOf(PERMISSION_B)
+            requestedPermissionsFlags = intArrayOf(PackageInfo.REQUESTED_PERMISSION_GRANTED)
+        }
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isFalse()
+    }
+
+    @Test
+    fun hasGrantPermission_notGranted_returnFalse() {
+        fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+            requestedPermissions = arrayOf(PERMISSION_A, PERMISSION_B)
+            requestedPermissionsFlags = intArrayOf(0, PackageInfo.REQUESTED_PERMISSION_GRANTED)
+        }
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isFalse()
+    }
+
+    @Test
+    fun hasGrantPermission_granted_returnTrue() {
+        fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+            requestedPermissions = arrayOf(PERMISSION_A, PERMISSION_B)
+            requestedPermissionsFlags = intArrayOf(PackageInfo.REQUESTED_PERMISSION_GRANTED, 0)
+        }
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isTrue()
+    }
+
+    private inner class FakePackageManagerWrapper : PackageManagerWrapper {
+        var fakePackageInfo: PackageInfo? = null
+
+        override fun getPackageInfoAsUserCached(
+            packageName: String,
+            flags: Long,
+            userId: Int,
+        ): PackageInfo? = fakePackageInfo
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "packageName"
+        const val PERMISSION_A = "permission.A"
+        const val PERMISSION_B = "permission.B"
+        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/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
new file mode 100644
index 0000000..bb56c10
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppInfoTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun appInfoLabel_isDisplayed() {
+        val packageInfo = PackageInfo().apply {
+            applicationInfo = APP
+        }
+        val appInfoProvider = AppInfoProvider(packageInfo)
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                appInfoProvider.AppInfo()
+            }
+        }
+
+        composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+    }
+
+    @Test
+    fun appInfoVersion_whenDisplayVersionIsFalse() {
+        val packageInfo = PackageInfo().apply {
+            applicationInfo = APP
+            versionName = VERSION_NAME
+        }
+        val appInfoProvider = AppInfoProvider(packageInfo)
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                appInfoProvider.AppInfo(displayVersion = false)
+            }
+        }
+
+        composeTestRule.onNodeWithText(VERSION_NAME).assertDoesNotExist()
+    }
+
+    @Test
+    fun appInfoVersion_whenDisplayVersionIsTrue() {
+        val packageInfo = PackageInfo().apply {
+            applicationInfo = APP
+            versionName = VERSION_NAME
+        }
+        val appInfoProvider = AppInfoProvider(packageInfo)
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                appInfoProvider.AppInfo(displayVersion = true)
+            }
+        }
+
+        composeTestRule.onNodeWithText(VERSION_NAME).assertIsDisplayed()
+    }
+
+    @Test
+    fun footerAppVersion_versionIsDisplayed() {
+        val packageInfo = PackageInfo().apply {
+            applicationInfo = APP
+            versionName = VERSION_NAME
+        }
+        val appInfoProvider = AppInfoProvider(packageInfo)
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                appInfoProvider.FooterAppVersion()
+            }
+        }
+
+        composeTestRule.onNodeWithText("version $VERSION_NAME").assertIsDisplayed()
+    }
+
+    private companion object {
+        const val LABEL = "Label"
+        const val VERSION_NAME = "VersionName"
+        val APP = object : ApplicationInfo() {
+            override fun loadLabel(pm: PackageManager) = LABEL
+        }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
new file mode 100644
index 0000000..f2267f6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
@@ -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 com.android.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListPageTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun title_isDisplayed() {
+        setContent()
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun appListState_hasCorrectInitialState() {
+        val inputState by setContent()
+
+        val state = inputState!!.state
+        assertThat(state.showSystem.value).isFalse()
+        assertThat(state.option.value).isEqualTo(0)
+        assertThat(state.searchQuery.value).isEqualTo("")
+    }
+
+    @Test
+    fun canShowSystem() {
+        val inputState by setContent()
+
+        composeTestRule.onNodeWithContentDescription(
+            context.getString(R.string.abc_action_menu_overflow_description)
+        ).performClick()
+        composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick()
+
+        val state = inputState!!.state
+        assertThat(state.showSystem.value).isTrue()
+    }
+
+    @Test
+    fun afterShowSystem_displayHideSystem() {
+        setContent()
+        composeTestRule.onNodeWithContentDescription(
+            context.getString(R.string.abc_action_menu_overflow_description)
+        ).performClick()
+        composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick()
+
+        composeTestRule.onNodeWithContentDescription(
+            context.getString(R.string.abc_action_menu_overflow_description)
+        ).performClick()
+
+        composeTestRule.onNodeWithText(context.getString(R.string.menu_hide_system))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun whenHasOptions_firstOptionDisplayed() {
+        val inputState by setContent(options = listOf(OPTION_0, OPTION_1))
+
+        composeTestRule.onNodeWithText(OPTION_0).assertIsDisplayed()
+        composeTestRule.onNodeWithText(OPTION_1).assertDoesNotExist()
+        val state = inputState!!.state
+        assertThat(state.option.value).isEqualTo(0)
+    }
+
+    @Test
+    fun whenHasOptions_couldSwitchOption() {
+        val inputState by setContent(options = listOf(OPTION_0, OPTION_1))
+
+        composeTestRule.onNodeWithText(OPTION_0).performClick()
+        composeTestRule.onNodeWithText(OPTION_1).performClick()
+
+        composeTestRule.onNodeWithText(OPTION_1).assertIsDisplayed()
+        composeTestRule.onNodeWithText(OPTION_0).assertDoesNotExist()
+        val state = inputState!!.state
+        assertThat(state.option.value).isEqualTo(1)
+    }
+
+    private fun setContent(
+        options: List<String> = emptyList(),
+        header: @Composable () -> Unit = {},
+    ): State<AppListInput<TestAppRecord>?> {
+        val appListState = mutableStateOf<AppListInput<TestAppRecord>?>(null)
+        composeTestRule.setContent {
+            AppListPage(
+                title = TITLE,
+                listModel = TestAppListModel(options),
+                header = header,
+                appItem = { AppListItem {} },
+                appList = { appListState.value = this },
+            )
+        }
+        return appListState
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val OPTION_0 = "Option 1"
+        const val OPTION_1 = "Option 2"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
new file mode 100644
index 0000000..abdcd3b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
@@ -0,0 +1,189 @@
+/*
+ * 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.pm.ApplicationInfo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.isToggleable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListSwitchItemTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun appLabel_displayed() {
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListSwitchItem(
+                onClick = {},
+                checked = stateOf(null),
+                changeable = stateOf(false),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+    }
+
+    @Test
+    fun summary_displayed() {
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListSwitchItem(
+                onClick = {},
+                checked = stateOf(null),
+                changeable = stateOf(false),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed()
+    }
+
+    @Test
+    fun title_onClick() {
+        var titleClicked = false
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListSwitchItem(
+                onClick = { titleClicked = true },
+                checked = stateOf(false),
+                changeable = stateOf(false),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNodeWithText(LABEL).performClick()
+
+        assertThat(titleClicked).isTrue()
+    }
+
+    @Test
+    fun switch_checkIsNull() {
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListSwitchItem(
+                onClick = {},
+                checked = stateOf(null),
+                changeable = stateOf(false),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNode(isToggleable()).assertDoesNotExist()
+    }
+
+    @Test
+    fun switch_checked() {
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListSwitchItem(
+                onClick = {},
+                checked = stateOf(true),
+                changeable = stateOf(false),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNode(isToggleable()).assertIsOn()
+    }
+
+    @Test
+    fun switch_notChecked() {
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListSwitchItem(
+                onClick = {},
+                checked = stateOf(false),
+                changeable = stateOf(false),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNode(isToggleable()).assertIsOff()
+    }
+
+    @Test
+    fun switch_changeable() {
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListSwitchItem(
+                onClick = {},
+                checked = stateOf(false),
+                changeable = stateOf(true),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNode(isToggleable()).assertIsEnabled()
+    }
+
+    @Test
+    fun switch_notChangeable() {
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListSwitchItem(
+                onClick = {},
+                checked = stateOf(false),
+                changeable = stateOf(false),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNode(isToggleable()).assertIsNotEnabled()
+    }
+
+    @Test
+    fun switch_onClick() {
+        var switchClicked = false
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListSwitchItem(
+                onClick = {},
+                checked = stateOf(false),
+                changeable = stateOf(true),
+                onCheckedChange = { switchClicked = true },
+            )
+        }
+
+        composeTestRule.onNode(isToggleable()).performClick()
+
+        assertThat(switchClicked).isTrue()
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "package.name"
+        const val LABEL = "Label"
+        const val SUMMARY = "Summary"
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+        }
+        val ITEM_MODEL = AppListItemModel(
+            record = object : AppRecord {
+                override val app = APP
+            },
+            label = LABEL,
+            summary = stateOf(SUMMARY),
+        )
+    }
+}
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..2677669
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.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.tests.testutils.TestAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val 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_A))
+
+        composeTestRule.onNodeWithText(APP_ENTRY_A.label).assertIsDisplayed()
+    }
+
+    @Test
+    fun couldShowHeader() {
+        setContent(appEntries = listOf(APP_ENTRY_A), header = { Text(HEADER) })
+
+        composeTestRule.onNodeWithText(HEADER).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenNotGrouped_groupTitleDoesNotExist() {
+        setContent(appEntries = listOf(APP_ENTRY_A, APP_ENTRY_B), enableGrouping = false)
+
+        composeTestRule.onNodeWithText(GROUP_A).assertDoesNotExist()
+        composeTestRule.onNodeWithText(GROUP_B).assertDoesNotExist()
+    }
+
+    @Test
+    fun whenGrouped_groupTitleDisplayed() {
+        setContent(appEntries = listOf(APP_ENTRY_A, APP_ENTRY_B), enableGrouping = true)
+
+        composeTestRule.onNodeWithText(GROUP_A).assertIsDisplayed()
+        composeTestRule.onNodeWithText(GROUP_B).assertIsDisplayed()
+    }
+
+    private fun setContent(
+        appEntries: List<AppEntry<TestAppRecord>>,
+        header: @Composable () -> Unit = {},
+        enableGrouping: Boolean = false,
+    ) {
+        composeTestRule.setContent {
+            val appListInput = AppListInput(
+                config = AppListConfig(userId = USER_ID, showInstantApps = false),
+                listModel = TestAppListModel(enableGrouping = enableGrouping),
+                state = AppListState(
+                    showSystem = false.toState(),
+                    option = 0.toState(),
+                    searchQuery = "".toState(),
+                ),
+                header = header,
+                appItem = { AppListItem {} },
+                bottomPadding = 0.dp,
+            )
+            appListInput.AppListImpl { stateOf(AppListData(appEntries, option = 0)) }
+        }
+    }
+
+    private companion object {
+        const val USER_ID = 0
+        const val HEADER = "Header"
+        const val GROUP_A = "Group A"
+        const val GROUP_B = "Group B"
+        val APP_ENTRY_A = AppEntry(
+            record = TestAppRecord(
+                app = ApplicationInfo().apply {
+                    packageName = "package.name.a"
+                },
+                group = GROUP_A,
+            ),
+            label = "Label A",
+            labelCollationKey = CollationKey("", byteArrayOf()),
+        )
+        val APP_ENTRY_B = AppEntry(
+            record = TestAppRecord(
+                app = ApplicationInfo().apply {
+                    packageName = "package.name.b"
+                },
+                group = GROUP_B,
+            ),
+            label = "Label B",
+            labelCollationKey = CollationKey("", byteArrayOf()),
+        )
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
new file mode 100644
index 0000000..f1d9abe
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -0,0 +1,264 @@
+/*
+ * 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.app.AppOpsManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.State
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.lifecycle.liveData
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spaprivileged.model.app.IAppOpsController
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
+import com.android.settingslib.spaprivileged.test.R
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+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.mockito.Mockito.`when` as whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class AppOpPermissionAppListTest {
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Mock
+    private lateinit var packageManagers: IPackageManagers
+
+    private lateinit var listModel: TestAppOpPermissionAppListModel
+
+    @Before
+    fun setUp() = runTest {
+        whenever(packageManagers.getAppOpPermissionPackages(USER_ID, PERMISSION))
+            .thenReturn(emptySet())
+        listModel = TestAppOpPermissionAppListModel()
+    }
+
+    @Test
+    fun transformItem_recordHasCorrectApp() {
+        val record = listModel.transformItem(APP)
+
+        assertThat(record.app).isSameInstanceAs(APP)
+    }
+
+    @Test
+    fun transformItem_hasRequestPermission() = runTest {
+        with(packageManagers) {
+            whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(true)
+        }
+
+        val record = listModel.transformItem(APP)
+
+        assertThat(record.hasRequestPermission).isTrue()
+    }
+
+    @Test
+    fun transformItem_notRequestPermission() = runTest {
+        with(packageManagers) {
+            whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false)
+        }
+
+        val record = listModel.transformItem(APP)
+
+        assertThat(record.hasRequestPermission).isFalse()
+    }
+
+    @Test
+    fun filter() = runTest {
+        with(packageManagers) {
+            whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false)
+        }
+        val record = AppOpPermissionRecord(
+            app = APP,
+            hasRequestPermission = false,
+            appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+        )
+
+        val recordListFlow = listModel.filter(flowOf(USER_ID), flowOf(listOf(record)))
+
+        val recordList = recordListFlow.firstWithTimeoutOrNull()!!
+        assertThat(recordList).isEmpty()
+    }
+
+    @Test
+    fun isAllowed_allowed() {
+        val record = AppOpPermissionRecord(
+            app = APP,
+            hasRequestPermission = true,
+            appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED),
+        )
+
+        val isAllowed = getIsAllowed(record)
+
+        assertThat(isAllowed).isTrue()
+    }
+
+    @Test
+    fun isAllowed_defaultAndHasGrantPermission() {
+        with(packageManagers) {
+            whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(true)
+        }
+        val record = AppOpPermissionRecord(
+            app = APP,
+            hasRequestPermission = true,
+            appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+        )
+
+        val isAllowed = getIsAllowed(record)
+
+        assertThat(isAllowed).isTrue()
+    }
+
+    @Test
+    fun isAllowed_defaultAndNotGrantPermission() {
+        with(packageManagers) {
+            whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(false)
+        }
+        val record = AppOpPermissionRecord(
+            app = APP,
+            hasRequestPermission = true,
+            appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+        )
+
+        val isAllowed = getIsAllowed(record)
+
+        assertThat(isAllowed).isFalse()
+    }
+
+    @Test
+    fun isAllowed_notAllowed() {
+        val record = AppOpPermissionRecord(
+            app = APP,
+            hasRequestPermission = true,
+            appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
+        )
+
+        val isAllowed = getIsAllowed(record)
+
+        assertThat(isAllowed).isFalse()
+    }
+
+    @Test
+    fun isChangeable_notRequestPermission() {
+        val record = AppOpPermissionRecord(
+            app = APP,
+            hasRequestPermission = false,
+            appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+        )
+
+        val isChangeable = listModel.isChangeable(record)
+
+        assertThat(isChangeable).isFalse()
+    }
+
+    @Test
+    fun isChangeable_notChangeablePackages() {
+        val record = AppOpPermissionRecord(
+            app = NOT_CHANGEABLE_APP,
+            hasRequestPermission = true,
+            appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+        )
+
+        val isChangeable = listModel.isChangeable(record)
+
+        assertThat(isChangeable).isFalse()
+    }
+
+    @Test
+    fun isChangeable_hasRequestPermissionAndChangeable() {
+        val record = AppOpPermissionRecord(
+            app = APP,
+            hasRequestPermission = true,
+            appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+        )
+
+        val isChangeable = listModel.isChangeable(record)
+
+        assertThat(isChangeable).isTrue()
+    }
+
+    @Test
+    fun setAllowed() {
+        val appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT)
+        val record = AppOpPermissionRecord(
+            app = APP,
+            hasRequestPermission = true,
+            appOpsController = appOpsController,
+        )
+
+        listModel.setAllowed(record = record, newAllowed = true)
+
+        assertThat(appOpsController.setAllowedCalledWith).isTrue()
+    }
+
+    private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
+        lateinit var isAllowedState: State<Boolean?>
+        composeTestRule.setContent {
+            isAllowedState = listModel.isAllowed(record)
+        }
+        return isAllowedState.value
+    }
+
+    private inner class TestAppOpPermissionAppListModel :
+        AppOpPermissionListModel(context, packageManagers) {
+        override val pageTitleResId = R.string.test_app_op_permission_title
+        override val switchTitleResId = R.string.test_app_op_permission_switch_title
+        override val footerResId = R.string.test_app_op_permission_footer
+        override val appOp = AppOpsManager.OP_MANAGE_MEDIA
+        override val permission = PERMISSION
+    }
+
+    private companion object {
+        const val USER_ID = 0
+        const val PACKAGE_NAME = "package.name"
+        const val PERMISSION = "PERMISSION"
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+        }
+        val NOT_CHANGEABLE_APP = ApplicationInfo().apply {
+            packageName = "android"
+        }
+    }
+}
+
+private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController {
+    var setAllowedCalledWith: Boolean? = null
+
+    override val mode = liveData { emit(fakeMode) }
+
+    override fun setAllowed(allowed: Boolean) {
+        setAllowedCalledWith = allowed
+    }
+
+    override fun getMode() = fakeMode
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
index cec6d7d..066e28a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
@@ -28,7 +28,6 @@
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
 import com.android.settingslib.spaprivileged.model.app.userHandle
-import com.google.common.truth.Truth.assertThat
 import java.util.UUID
 import org.junit.Before
 import org.junit.Rule
@@ -42,15 +41,14 @@
 
 @RunWith(AndroidJUnit4::class)
 class AppStorageSizeTest {
-    @JvmField
-    @Rule
+    @get:Rule
     val mockito: MockitoRule = MockitoJUnit.rule()
 
     @get:Rule
     val composeTestRule = createComposeRule()
 
     @Spy
-    private var context: Context = ApplicationProvider.getApplicationContext()
+    private val context: Context = ApplicationProvider.getApplicationContext()
 
     @Mock
     private lateinit var storageStatsManager: StorageStatsManager
@@ -77,7 +75,7 @@
             }
         }
 
-        assertThat(storageSize.value).isEqualTo("123 B")
+        composeTestRule.waitUntil { storageSize.value == "123 B" }
     }
 
     companion object {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
new file mode 100644
index 0000000..ecad08a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.content.pm.PackageInfo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListProvider
+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.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class TogglePermissionAppInfoPageTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Mock
+    private lateinit var packageManagers: IPackageManagers
+
+    private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+    private val appListTemplate =
+        TogglePermissionAppListTemplate(listOf(TestTogglePermissionAppListProvider))
+
+    private val appInfoPageProvider = TogglePermissionAppInfoPageProvider(appListTemplate)
+
+    @Before
+    fun setUp() {
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+        whenever(packageManagers.getPackageInfoAsUser(PACKAGE_NAME, USER_ID))
+            .thenReturn(PACKAGE_INFO)
+    }
+
+    @Test
+    fun buildEntry() {
+        val entryList = appInfoPageProvider.buildEntry(null)
+
+        assertThat(entryList).hasSize(1)
+        assertThat(entryList[0].displayName).isEqualTo("AllowControl")
+    }
+
+    @Test
+    fun title_isDisplayed() {
+        val listModel = TestTogglePermissionAppListModel()
+
+        setTogglePermissionAppInfoPage(listModel)
+
+        composeTestRule.onNodeWithText(context.getString(listModel.pageTitleResId))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun whenAllowed_switchIsOn() {
+        val listModel = TestTogglePermissionAppListModel(isAllowed = true)
+
+        setTogglePermissionAppInfoPage(listModel)
+
+        composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
+            .assertIsOn()
+    }
+
+    @Test
+    fun whenNotAllowed_switchIsOff() {
+        val listModel = TestTogglePermissionAppListModel(isAllowed = false)
+
+        setTogglePermissionAppInfoPage(listModel)
+
+        composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
+            .assertIsOff()
+    }
+
+    @Test
+    fun whenNotChangeable_switchNotEnabled() {
+        val listModel = TestTogglePermissionAppListModel(isAllowed = false, isChangeable = false)
+
+        setTogglePermissionAppInfoPage(listModel)
+
+        composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
+            .assertIsDisplayed()
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun footer_isDisplayed() {
+        val listModel = TestTogglePermissionAppListModel()
+
+        setTogglePermissionAppInfoPage(listModel)
+
+        composeTestRule.onNodeWithText(context.getString(listModel.footerResId))
+            .assertIsDisplayed()
+    }
+
+    private fun setTogglePermissionAppInfoPage(listModel: TestTogglePermissionAppListModel) {
+        composeTestRule.setContent {
+            listModel.TogglePermissionAppInfoPage(
+                packageName = PACKAGE_NAME,
+                userId = USER_ID,
+                packageManagers = packageManagers,
+                restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+            )
+        }
+    }
+
+    private companion object {
+        const val USER_ID = 0
+        const val PACKAGE_NAME = "package.name"
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+        }
+        val PACKAGE_INFO = PackageInfo().apply {
+            packageName = PACKAGE_NAME
+            applicationInfo = APP
+        }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
new file mode 100644
index 0000000..355dfb6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.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.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.test.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TogglePermissionAppListPageTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun appListInjectEntry_titleDisplayed() {
+        val entry = TogglePermissionAppListPageProvider.buildInjectEntry(PERMISSION_TYPE) {
+            TestTogglePermissionAppListModel()
+        }.build()
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                entry.UiLayout()
+            }
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun appListRoute() {
+        val route = TogglePermissionAppListPageProvider.getRoute(PERMISSION_TYPE)
+
+        assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+    }
+
+    private companion object {
+        const val PERMISSION_TYPE = "test.PERMISSION"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
new file mode 100644
index 0000000..1818f2d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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 androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.test.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TogglePermissionAppListTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun appListInjectEntry_titleDisplayed() {
+        val entry = TestTogglePermissionAppListProvider.buildAppListInjectEntry().build()
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                entry.UiLayout()
+            }
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun appListRoute() {
+        val route = TestTogglePermissionAppListProvider.getAppListRoute()
+
+        assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+    }
+
+    @Test
+    fun togglePermissionAppListTemplate_createPageProviders() {
+        val togglePermissionAppListTemplate =
+            TogglePermissionAppListTemplate(listOf(TestTogglePermissionAppListProvider))
+
+        val createPageProviders = togglePermissionAppListTemplate.createPageProviders()
+
+        assertThat(createPageProviders).hasSize(2)
+        assertThat(createPageProviders.any { it is TogglePermissionAppListPageProvider }).isTrue()
+        assertThat(createPageProviders.any { it is TogglePermissionAppInfoPageProvider }).isTrue()
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
new file mode 100644
index 0000000..a13c483
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.preference
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.isOff
+import androidx.compose.ui.test.isOn
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedSwitchPreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+
+    private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+    private val switchPreferenceModel = object : SwitchPreferenceModel {
+        override val title = TITLE
+        override val checked = mutableStateOf(true)
+        override val onCheckedChange: (Boolean) -> Unit = { checked.value = it }
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNode(isOn()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_toggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenNoRestricted_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNode(isOn()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenNoRestricted_toggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled()
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_notToggleable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNodeWithText(FakeBlockedByAdmin.SUMMARY).assertIsDisplayed()
+        composeTestRule.onNode(isOn()).assertIsDisplayed()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_click() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+    }
+
+    private fun setContent(restrictions: Restrictions) {
+        composeTestRule.setContent {
+            RestrictedSwitchPreference(switchPreferenceModel, restrictions) { _, _ ->
+                fakeRestrictionsProvider
+            }
+        }
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val USER_ID = 0
+        const val RESTRICTION_KEY = "restriction_key"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
new file mode 100644
index 0000000..983284c
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
@@ -0,0 +1,151 @@
+/*
+ * 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.scaffold
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedMenuItemTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+
+    private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+    private var menuItemOnClickIsCalled = false
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenRestrictionsKeysIsEmpty_clickable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(menuItemOnClickIsCalled).isTrue()
+    }
+
+    @Test
+    fun whenNoRestricted_enabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenNoRestricted_clickable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(menuItemOnClickIsCalled).isTrue()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed().assertIsNotEnabled()
+    }
+
+    @Test
+    fun whenBaseUserRestricted_notClickable() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(menuItemOnClickIsCalled).isFalse()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_disabled() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun whenBlockedByAdmin_onClick_showAdminSupportDetails() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions)
+        composeTestRule.onRoot().performClick()
+
+        assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+        assertThat(menuItemOnClickIsCalled).isFalse()
+    }
+
+    private fun setContent(restrictions: Restrictions) {
+        val fakeMoreOptionsScope = object : MoreOptionsScope {
+            override fun dismiss() {}
+        }
+        composeTestRule.setContent {
+            fakeMoreOptionsScope.RestrictedMenuItemImpl(
+                text = TEXT,
+                restrictions = restrictions,
+                onClick = { menuItemOnClickIsCalled = true },
+                restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+            )
+        }
+    }
+
+    private companion object {
+        const val TEXT = "Text"
+        const val USER_ID = 0
+        const val RESTRICTION_KEY = "restriction_key"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
new file mode 100644
index 0000000..93fa17d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.tests.testutils
+
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+
+class FakeBlockedByAdmin : BlockedByAdmin {
+    var sendShowAdminSupportDetailsIntentIsCalled = false
+
+    override fun getSummary(checked: Boolean?) = SUMMARY
+
+    override fun sendShowAdminSupportDetailsIntent() {
+        sendShowAdminSupportDetailsIntentIsCalled = true
+    }
+
+    companion object {
+        const val SUMMARY = "Blocked by admin"
+    }
+}
+
+class FakeRestrictionsProvider : RestrictionsProvider {
+    var restrictedMode: RestrictedMode? = null
+
+    @Composable
+    override fun restrictedModeState() = stateOf(restrictedMode)
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
new file mode 100644
index 0000000..ada4016
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.tests.testutils
+
+import android.content.pm.ApplicationInfo
+import com.android.settingslib.spa.framework.util.mapItem
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import kotlinx.coroutines.flow.Flow
+
+data class TestAppRecord(
+    override val app: ApplicationInfo,
+    val group: String? = null,
+) : AppRecord
+
+class TestAppListModel(
+    private val options: List<String> = emptyList(),
+    private val enableGrouping: Boolean = false,
+) : AppListModel<TestAppRecord> {
+    override fun getSpinnerOptions() = options
+
+    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+        appListFlow.mapItem(::TestAppRecord)
+
+    override fun getGroupTitle(option: Int, record: TestAppRecord) =
+        if (enableGrouping) record.group else null
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
new file mode 100644
index 0000000..b13fbb3
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
@@ -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 com.android.settingslib.spaprivileged.tests.testutils
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.test.R
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel
+import kotlinx.coroutines.flow.Flow
+
+class TestTogglePermissionAppListModel(
+    private val isAllowed: Boolean? = null,
+    private val isChangeable: Boolean = false,
+) : TogglePermissionAppListModel<TestAppRecord> {
+    override val pageTitleResId = R.string.test_permission_title
+    override val switchTitleResId = R.string.test_permission_switch_title
+    override val footerResId = R.string.test_permission_footer
+
+    override fun transformItem(app: ApplicationInfo) = TestAppRecord(app = app)
+
+    override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<TestAppRecord>>) =
+        recordListFlow
+
+    @Composable
+    override fun isAllowed(record: TestAppRecord) = stateOf(isAllowed)
+
+    override fun isChangeable(record: TestAppRecord) = isChangeable
+
+    override fun setAllowed(record: TestAppRecord, newAllowed: Boolean) {}
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt
new file mode 100644
index 0000000..354bbf5
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.tests.testutils
+
+import android.content.Context
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
+
+object TestTogglePermissionAppListProvider : TogglePermissionAppListProvider {
+    override val permissionType = "test.PERMISSION"
+    override fun createModel(context: Context) = TestTogglePermissionAppListModel()
+}
diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp
index 3baef4b..e9c6aed 100644
--- a/packages/SettingsLib/TwoTargetPreference/Android.bp
+++ b/packages/SettingsLib/TwoTargetPreference/Android.bp
@@ -23,5 +23,6 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.permission",
+        "com.android.healthconnect",
     ],
 }
diff --git a/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml
new file mode 100644
index 0000000..46abff8
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M1.03 8.47l0.43-4.96h4.33v1.17H2.48L2.25 7.39C2.66 7.1 3.1 6.96 3.57 6.96c0.77 0 1.38 0.3 1.83 0.9 s0.66 1.41 0.66 2.43c0 1.03-0.24 1.84-0.72 2.43S4.2 13.6 3.36 13.6c-0.75 0-1.36-0.24-1.83-0.73s-0.74-1.16-0.81-2.02h1.13 c0.07 0.57 0.23 1 0.49 1.29s0.59 0.43 1.01 0.43c0.47 0 0.84-0.2 1.1-0.61c0.26-0.41 0.4-0.96 0.4-1.65 c0-0.65-0.14-1.18-0.43-1.59S3.76 8.09 3.28 8.09c-0.4 0-0.72 0.1-0.96 0.31L1.99 8.73L1.03 8.47z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M 18.93,5.74 L 18.93,3.39 L 17.63,3.39 L 17.63,5.74 L 15.28,5.74 L 15.28,7.04 L 17.63,7.04 L 17.63,9.39 L 18.93,9.39 L 18.93,7.04 L 21.28,7.04 L 21.28,5.74 z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.78 12.24l-0.22 0.27c-0.63 0.73-1.55 1.1-2.76 1.1c-1.08 0-1.92-0.36-2.53-1.07s-0.93-1.72-0.94-3.02V7.56 c0-1.39 0.28-2.44 0.84-3.13s1.39-1.04 2.51-1.04c0.95 0 1.69 0.26 2.23 0.79s0.83 1.28 0.89 2.26h-1.25 c-0.05-0.62-0.22-1.1-0.52-1.45s-0.74-0.52-1.34-0.52c-0.72 0-1.24 0.23-1.57 0.7S8.6 6.37 8.59 7.4v2.03c0 1 0.19 1.77 0.57 2.31 c0.38 0.54 0.93 0.8 1.65 0.8c0.67 0 1.19-0.16 1.54-0.49l0.18-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index a39a2b9..0234330 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> oor tot vol"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> oor tot vol"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laaiproses is onderbreek"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Laai"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Laai tans vinnig"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index d953678..29b188c 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"እስኪሞላ ድረስ <xliff:g id="TIME">%1$s</xliff:g> ይቀራል"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - እስኪሞላ ድረስ <xliff:g id="TIME">%2$s</xliff:g> ይቀራል"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ኃይል መሙላት ባለበት ቆሟል"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ያልታወቀ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ኃይል በመሙላት ላይ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ኃይል በፍጥነት በመሙላት ላይ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 3b00d26..28a3f18 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"يتبقّى <xliff:g id="TIME">%1$s</xliff:g> حتى اكتمال شحن البطارية."</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - يتبقّى <xliff:g id="TIME">%2$s</xliff:g> حتى اكتمال شحن البطارية."</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - تم إيقاف الشحن مؤقتًا"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"غير معروف"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"جارٍ الشحن"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"جارٍ الشحن سريعًا"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 0acd7ee6..fc54e04 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"সম্পূৰ্ণ হ’বলৈ <xliff:g id="TIME">%1$s</xliff:g> বাকী আছে"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"সম্পূৰ্ণ হ’বলৈ <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> বাকী আছে"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - চাৰ্জিং পজ কৰা হৈছে"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"অজ্ঞাত"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"চাৰ্জ কৰি থকা হৈছে"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"দ্ৰুততাৰে চাৰ্জ হৈছে"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 33efb4fd..e2e294f 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Tam şarj edilənədək <xliff:g id="TIME">%1$s</xliff:g> qalıb"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - tam şarj edilənədək <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj durdurulub"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Naməlum"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Enerji doldurma"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Sürətlə doldurulur"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 108a136..479c9ca 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do kraja punjenja"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do kraja punjenja"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Punjenje je zaustavljeno"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Puni se"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo se puni"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 5a70202..b45996c 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Да поўнай зарадкі засталося <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – да поўнай зарадкі засталося: <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Зарадка прыпынена"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Невядома"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарадка"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Хуткая зарадка"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 7e97dd3..ecb5208 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Оставащо време до пълно зареждане: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оставащо време до пълно зареждане: <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: Зареждането е на пауза"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарежда се"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Зарежда се бързо"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 023be24..fda27ed 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>-এ ব্যাটারি পুরো চার্জ হয়ে যাবে"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>-এ ব্যাটারি পুরো চার্জ হয়ে যাবে"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - চার্জিং পজ করা হয়েছে"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"অজানা"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"চার্জ হচ্ছে"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"দ্রুত চার্জ হচ্ছে"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 3cb4190..cae96e9 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do potpune napunjenosti"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do potpune napunjenosti"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Punjenje je pauzirano"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo punjenje"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index efa29ee..51a4a02 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> per completar la càrrega"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> per completar la càrrega"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: la càrrega s\'ha posat en pausa"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconegut"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"S\'està carregant"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregant ràpidament"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 3736f93..a50e527 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do úplného nabití"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabití"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Nabíjení je pozastaveno"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznámé"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíjí se"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Rychlé nabíjení"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 849604d..95e0130 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Fuldt opladet om <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – fuldt opladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Opladningen er sat på pause"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukendt"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Oplader"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Oplader hurtigt"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index d736d59..12fce37 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Voll in <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – voll in <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladevorgang angehalten"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unbekannt"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Wird aufgeladen"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Schnelles Aufladen"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index e76b933..f54d5d2 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Απομένουν <xliff:g id="TIME">%1$s</xliff:g> για πλήρη φόρτιση"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - Απομένουν <xliff:g id="TIME">%2$s</xliff:g> για πλήρη φόρτιση"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Η φόρτιση τέθηκε σε παύση"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Άγνωστο"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Φόρτιση"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ταχεία φόρτιση"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 09bc065..208dbfa 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/arrays.xml b/packages/SettingsLib/res/values-en-rCA/arrays.xml
index 327e4e9..8a57232 100644
--- a/packages/SettingsLib/res/values-en-rCA/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rCA/arrays.xml
@@ -86,7 +86,7 @@
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
-    <item msgid="2494959071796102843">"Use system selection (default)"</item>
+    <item msgid="2494959071796102843">"Use System Selection (Default)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
     <item msgid="720249083677397051">"AAC"</item>
     <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
@@ -96,7 +96,7 @@
     <item msgid="506175145534048710">"Opus"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_summaries">
-    <item msgid="8868109554557331312">"Use system selection (default)"</item>
+    <item msgid="8868109554557331312">"Use System Selection (Default)"</item>
     <item msgid="9024885861221697796">"SBC"</item>
     <item msgid="4688890470703790013">"AAC"</item>
     <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
@@ -106,52 +106,52 @@
     <item msgid="7940970833006181407">"Opus"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
-    <item msgid="926809261293414607">"Use system selection (default)"</item>
+    <item msgid="926809261293414607">"Use System Selection (Default)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
     <item msgid="3208896645474529394">"48.0 kHz"</item>
     <item msgid="8420261949134022577">"88.2 kHz"</item>
     <item msgid="8887519571067543785">"96.0 kHz"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
-    <item msgid="2284090879080331090">"Use system selection (default)"</item>
+    <item msgid="2284090879080331090">"Use System Selection (Default)"</item>
     <item msgid="1872276250541651186">"44.1 kHz"</item>
     <item msgid="8736780630001704004">"48.0 kHz"</item>
     <item msgid="7698585706868856888">"88.2 kHz"</item>
     <item msgid="8946330945963372966">"96.0 kHz"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
-    <item msgid="2574107108483219051">"Use system selection (default)"</item>
+    <item msgid="2574107108483219051">"Use System Selection (Default)"</item>
     <item msgid="4671992321419011165">"16 bits/sample"</item>
     <item msgid="1933898806184763940">"24 bits/sample"</item>
     <item msgid="1212577207279552119">"32 bits/sample"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
-    <item msgid="9196208128729063711">"Use system selection (default)"</item>
+    <item msgid="9196208128729063711">"Use System Selection (Default)"</item>
     <item msgid="1084497364516370912">"16 bits/sample"</item>
     <item msgid="2077889391457961734">"24 bits/sample"</item>
     <item msgid="3836844909491316925">"32 bits/sample"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
-    <item msgid="3014194562841654656">"Use system selection (default)"</item>
+    <item msgid="3014194562841654656">"Use System Selection (Default)"</item>
     <item msgid="5982952342181788248">"Mono"</item>
     <item msgid="927546067692441494">"Stereo"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
-    <item msgid="1997302811102880485">"Use system selection (default)"</item>
+    <item msgid="1997302811102880485">"Use System Selection (Default)"</item>
     <item msgid="8005696114958453588">"Mono"</item>
     <item msgid="1333279807604675720">"Stereo"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
-    <item msgid="1241278021345116816">"Optimised for Audio Quality (990kbps/909kbps)"</item>
-    <item msgid="3523665555859696539">"Balanced Audio And Connection Quality (660 kbps/606 kbps)"</item>
-    <item msgid="886408010459747589">"Optimised for Connection Quality (330kbps/303kbps)"</item>
+    <item msgid="1241278021345116816">"Optimized for Audio Quality (990kbps/909kbps)"</item>
+    <item msgid="3523665555859696539">"Balanced Audio And Connection Quality (660kbps/606kbps)"</item>
+    <item msgid="886408010459747589">"Optimized for Connection Quality (330kbps/303kbps)"</item>
     <item msgid="3808414041654351577">"Best Effort (Adaptive Bit Rate)"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
-    <item msgid="804499336721569838">"Optimised for Audio Quality"</item>
-    <item msgid="7451422070435297462">"Balanced Audio and Connection Quality"</item>
-    <item msgid="6173114545795428901">"Optimised for Connection Quality"</item>
-    <item msgid="4349908264188040530">"Best effort (adaptive bit rate)"</item>
+    <item msgid="804499336721569838">"Optimized for Audio Quality"</item>
+    <item msgid="7451422070435297462">"Balanced Audio And Connection Quality"</item>
+    <item msgid="6173114545795428901">"Optimized for Connection Quality"</item>
+    <item msgid="4349908264188040530">"Best Effort (Adaptive Bit Rate)"</item>
   </string-array>
   <string-array name="bluetooth_audio_active_device_summaries">
     <item msgid="8019740759207729126"></item>
@@ -161,25 +161,25 @@
   </string-array>
   <string-array name="select_logd_size_titles">
     <item msgid="1191094707770726722">"Off"</item>
-    <item msgid="7839165897132179888">"64 K"</item>
-    <item msgid="2715700596495505626">"256 K"</item>
-    <item msgid="7099386891713159947">"1 M"</item>
-    <item msgid="6069075827077845520">"4 M"</item>
-    <item msgid="6078203297886482480">"8 M"</item>
+    <item msgid="7839165897132179888">"64K"</item>
+    <item msgid="2715700596495505626">"256K"</item>
+    <item msgid="7099386891713159947">"1M"</item>
+    <item msgid="6069075827077845520">"4M"</item>
+    <item msgid="6078203297886482480">"8M"</item>
   </string-array>
   <string-array name="select_logd_size_lowram_titles">
     <item msgid="1145807928339101085">"Off"</item>
-    <item msgid="4064786181089783077">"64 K"</item>
-    <item msgid="3052710745383602630">"256 K"</item>
-    <item msgid="3691785423374588514">"1 M"</item>
+    <item msgid="4064786181089783077">"64K"</item>
+    <item msgid="3052710745383602630">"256K"</item>
+    <item msgid="3691785423374588514">"1M"</item>
   </string-array>
   <string-array name="select_logd_size_summaries">
     <item msgid="409235464399258501">"Off"</item>
-    <item msgid="4195153527464162486">"64 K per log buffer"</item>
-    <item msgid="7464037639415220106">"256 K per log buffer"</item>
-    <item msgid="8539423820514360724">"1 M per log buffer"</item>
-    <item msgid="1984761927103140651">"4 M per log buffer"</item>
-    <item msgid="2983219471251787208">"8 M per log buffer"</item>
+    <item msgid="4195153527464162486">"64K per log buffer"</item>
+    <item msgid="7464037639415220106">"256K per log buffer"</item>
+    <item msgid="8539423820514360724">"1M per log buffer"</item>
+    <item msgid="1984761927103140651">"4M per log buffer"</item>
+    <item msgid="2983219471251787208">"8M per log buffer"</item>
   </string-array>
   <string-array name="select_logpersist_titles">
     <item msgid="704720725704372366">"Off"</item>
@@ -222,7 +222,7 @@
   </string-array>
   <string-array name="overlay_display_devices_entries">
     <item msgid="4497393944195787240">"None"</item>
-    <item msgid="8461943978957133391">"480 p"</item>
+    <item msgid="8461943978957133391">"480p"</item>
     <item msgid="6923083594932909205">"480p (secure)"</item>
     <item msgid="1226941831391497335">"720p"</item>
     <item msgid="7051983425968643928">"720p (secure)"</item>
@@ -258,10 +258,10 @@
   <string-array name="app_process_limit_entries">
     <item msgid="794656271086646068">"Standard limit"</item>
     <item msgid="8628438298170567201">"No background processes"</item>
-    <item msgid="915752993383950932">"At most, 1 process"</item>
-    <item msgid="8554877790859095133">"At most, 2 processes"</item>
-    <item msgid="9060830517215174315">"At most, 3 processes"</item>
-    <item msgid="6506681373060736204">"At most, 4 processes"</item>
+    <item msgid="915752993383950932">"At most 1 process"</item>
+    <item msgid="8554877790859095133">"At most 2 processes"</item>
+    <item msgid="9060830517215174315">"At most 3 processes"</item>
+    <item msgid="6506681373060736204">"At most 4 processes"</item>
   </string-array>
   <string-array name="usb_configuration_titles">
     <item msgid="3358668781763928157">"Charging"</item>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 77b20a6..aac80ab 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -46,8 +46,8 @@
     <string name="wifi_security_passpoint" msgid="2209078477216565387">"Passpoint"</string>
     <string name="wifi_security_sae" msgid="3644520541721422843">"WPA3-Personal"</string>
     <string name="wifi_security_psk_sae" msgid="8135104122179904684">"WPA2/WPA3-Personal"</string>
-    <string name="wifi_security_none_owe" msgid="5241745828327404101">"None/Enhanced open"</string>
-    <string name="wifi_security_owe" msgid="3343421403561657809">"Enhanced open"</string>
+    <string name="wifi_security_none_owe" msgid="5241745828327404101">"None/Enhanced Open"</string>
+    <string name="wifi_security_owe" msgid="3343421403561657809">"Enhanced Open"</string>
     <string name="wifi_security_eap_suiteb" msgid="415842785991698142">"WPA3-Enterprise 192-bit"</string>
     <string name="wifi_remembered" msgid="3266709779723179188">"Saved"</string>
     <string name="wifi_disconnected" msgid="7054450256284661757">"Disconnected"</string>
@@ -59,29 +59,29 @@
     <string name="wifi_check_password_try_again" msgid="8817789642851605628">"Check password and try again"</string>
     <string name="wifi_not_in_range" msgid="1541760821805777772">"Not in range"</string>
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Won\'t automatically connect"</string>
-    <string name="wifi_no_internet" msgid="1774198889176926299">"No Internet access"</string>
+    <string name="wifi_no_internet" msgid="1774198889176926299">"No internet access"</string>
     <string name="saved_network" msgid="7143698034077223645">"Saved by <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatically connected via %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatically connected via network rating provider"</string>
     <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string>
-    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string>
+    <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No internet"</string>
     <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string>
-    <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string>
-    <string name="wifi_status_sign_in_required" msgid="2236267500459526855">"Sign-in required"</string>
+    <string name="wifi_status_no_internet" msgid="3799933875988829048">"No internet"</string>
+    <string name="wifi_status_sign_in_required" msgid="2236267500459526855">"Sign in required"</string>
     <string name="wifi_ap_unable_to_handle_new_sta" msgid="5885145407184194503">"Access point temporarily full"</string>
     <string name="osu_opening_provider" msgid="4318105381295178285">"Opening <xliff:g id="PASSPOINTPROVIDER">%1$s</xliff:g>"</string>
     <string name="osu_connect_failed" msgid="9107873364807159193">"Couldn’t connect"</string>
     <string name="osu_completing_sign_up" msgid="8412636665040390901">"Completing sign-up…"</string>
-    <string name="osu_sign_up_failed" msgid="5605453599586001793">"Couldn’t complete sign-up. Tap to try again"</string>
+    <string name="osu_sign_up_failed" msgid="5605453599586001793">"Couldn’t complete sign-up. Tap to try again."</string>
     <string name="osu_sign_up_complete" msgid="7640183358878916847">"Sign-up complete. Connecting…"</string>
     <string name="speed_label_slow" msgid="6069917670665664161">"Slow"</string>
     <string name="speed_label_okay" msgid="1253594383880810424">"OK"</string>
     <string name="speed_label_fast" msgid="2677719134596044051">"Fast"</string>
-    <string name="speed_label_very_fast" msgid="8215718029533182439">"Very fast"</string>
+    <string name="speed_label_very_fast" msgid="8215718029533182439">"Very Fast"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Expired"</string>
-    <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
+    <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="bluetooth_disconnected" msgid="7739366554710388701">"Disconnected"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Disconnecting…"</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"Connecting…"</string>
@@ -110,8 +110,8 @@
     <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Contacts and call history sharing"</string>
     <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"Use for contacts and call history sharing"</string>
     <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Internet connection sharing"</string>
-    <string name="bluetooth_profile_map" msgid="8907204701162107271">"Text messages"</string>
-    <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM access"</string>
+    <string name="bluetooth_profile_map" msgid="8907204701162107271">"Text Messages"</string>
+    <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM Access"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
     <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string>
@@ -120,14 +120,14 @@
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string>
-    <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file-transfer server"</string>
+    <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file transfer server"</string>
     <string name="bluetooth_map_profile_summary_connected" msgid="4141725591784669181">"Connected to map"</string>
     <string name="bluetooth_sap_profile_summary_connected" msgid="1280297388033001037">"Connected to SAP"</string>
-    <string name="bluetooth_opp_profile_summary_not_connected" msgid="3959741824627764954">"Not connected to file-transfer server"</string>
+    <string name="bluetooth_opp_profile_summary_not_connected" msgid="3959741824627764954">"Not connected to file transfer server"</string>
     <string name="bluetooth_hid_profile_summary_connected" msgid="3923653977051684833">"Connected to input device"</string>
-    <string name="bluetooth_pan_user_profile_summary_connected" msgid="380469653827505727">"Connected to device for Internet access"</string>
-    <string name="bluetooth_pan_nap_profile_summary_connected" msgid="3744773111299503493">"Sharing local Internet connection with device"</string>
-    <string name="bluetooth_pan_profile_summary_use_for" msgid="7422039765025340313">"Use for Internet access"</string>
+    <string name="bluetooth_pan_user_profile_summary_connected" msgid="380469653827505727">"Connected to device for internet access"</string>
+    <string name="bluetooth_pan_nap_profile_summary_connected" msgid="3744773111299503493">"Sharing local internet connection with device"</string>
+    <string name="bluetooth_pan_profile_summary_use_for" msgid="7422039765025340313">"Use for internet access"</string>
     <string name="bluetooth_map_profile_summary_use_for" msgid="4453622103977592583">"Use for map"</string>
     <string name="bluetooth_sap_profile_summary_use_for" msgid="6204902866176714046">"Use for SIM access"</string>
     <string name="bluetooth_a2dp_profile_summary_use_for" msgid="7324694226276491807">"Use for media audio"</string>
@@ -146,17 +146,17 @@
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Pairing rejected by <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Computer"</string>
     <string name="bluetooth_talkback_headset" msgid="3406852564400882682">"Headset"</string>
-    <string name="bluetooth_talkback_phone" msgid="868393783858123880">"Telephone"</string>
+    <string name="bluetooth_talkback_phone" msgid="868393783858123880">"Phone"</string>
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imaging"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Headphone"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Input Peripheral"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
-    <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi off."</string>
-    <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi disconnected."</string>
-    <string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"Wi-Fi one bar."</string>
-    <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi two bars."</string>
-    <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi three bars."</string>
-    <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal full."</string>
+    <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi off."</string>
+    <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi disconnected."</string>
+    <string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"Wifi one bar."</string>
+    <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi two bars."</string>
+    <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi three bars."</string>
+    <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi signal full."</string>
     <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open network"</string>
     <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Secure network"</string>
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -178,7 +178,7 @@
     <string name="tts_default_rate_title" msgid="3964187817364304022">"Speech rate"</string>
     <string name="tts_default_rate_summary" msgid="3781937042151716987">"Speed at which the text is spoken"</string>
     <string name="tts_default_pitch_title" msgid="6988592215554485479">"Pitch"</string>
-    <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Affects the tone of the synthesised speech"</string>
+    <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Affects the tone of the synthesized speech"</string>
     <string name="tts_default_lang_title" msgid="4698933575028098940">"Language"</string>
     <string name="tts_lang_use_system" msgid="6312945299804012406">"Use system language"</string>
     <string name="tts_lang_not_selected" msgid="7927823081096056147">"Language not selected"</string>
@@ -188,7 +188,7 @@
     <string name="tts_install_data_title" msgid="1829942496472751703">"Install voice data"</string>
     <string name="tts_install_data_summary" msgid="3608874324992243851">"Install the voice data required for speech synthesis"</string>
     <string name="tts_engine_security_warning" msgid="3372432853837988146">"This speech synthesis engine may be able to collect all the text that will be spoken, including personal data like passwords and credit card numbers. It comes from the <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> engine. Enable the use of this speech synthesis engine?"</string>
-    <string name="tts_engine_network_required" msgid="8722087649733906851">"This language requires a working network connection for Text-to-Speech output."</string>
+    <string name="tts_engine_network_required" msgid="8722087649733906851">"This language requires a working network connection for text-to-speech output."</string>
     <string name="tts_default_sample_string" msgid="6388016028292967973">"This is an example of speech synthesis"</string>
     <string name="tts_status_title" msgid="8190784181389278640">"Default language status"</string>
     <string name="tts_status_ok" msgid="8583076006537547379">"<xliff:g id="LOCALE">%1$s</xliff:g> is fully supported"</string>
@@ -224,7 +224,7 @@
     <string name="apn_settings_not_available" msgid="1147111671403342300">"Access Point Name settings are not available for this user"</string>
     <string name="enable_adb" msgid="8072776357237289039">"USB debugging"</string>
     <string name="enable_adb_summary" msgid="3711526030096574316">"Debug mode when USB is connected"</string>
-    <string name="clear_adb_keys" msgid="3010148733140369917">"Revoke USB debugging authorisations"</string>
+    <string name="clear_adb_keys" msgid="3010148733140369917">"Revoke USB debugging authorizations"</string>
     <string name="enable_adb_wireless" msgid="6973226350963971018">"Wireless debugging"</string>
     <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Debug mode when Wi‑Fi is connected"</string>
     <string name="adb_wireless_error" msgid="721958772149779856">"Error"</string>
@@ -233,22 +233,22 @@
     <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Pair device with QR code"</string>
     <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Pair new devices using QR code scanner"</string>
     <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Pair device with pairing code"</string>
-    <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Pair new devices using six-digit code"</string>
+    <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Pair new devices using six digit code"</string>
     <string name="adb_paired_devices_title" msgid="5268997341526217362">"Paired devices"</string>
     <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Currently connected"</string>
     <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Device details"</string>
     <string name="adb_device_forget" msgid="193072400783068417">"Forget"</string>
     <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Device fingerprint: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
     <string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Connection unsuccessful"</string>
-    <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Make sure that <xliff:g id="DEVICE_NAME">%1$s</xliff:g> is connected to the correct network"</string>
+    <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Make sure <xliff:g id="DEVICE_NAME">%1$s</xliff:g> is connected to the correct network"</string>
     <string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Pair with device"</string>
     <string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Wi‑Fi pairing code"</string>
     <string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"Pairing unsuccessful"</string>
-    <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Make sure that the device is connected to the same network."</string>
+    <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Make sure the device is connected to the same network."</string>
     <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Pair device over Wi‑Fi by scanning a QR code"</string>
     <string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Pairing device…"</string>
     <string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Failed to pair the device. Either the QR code was incorrect, or the device is not connected to the same network."</string>
-    <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address and port"</string>
+    <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address &amp; Port"</string>
     <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Scan QR code"</string>
     <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Pair device over Wi‑Fi by scanning a QR code"</string>
     <string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Please connect to a Wi‑Fi network"</string>
@@ -268,28 +268,28 @@
     <string name="mock_location_app_set" msgid="4706722469342913843">"Mock location app: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="debug_networking_category" msgid="6829757985772659599">"Networking"</string>
     <string name="wifi_display_certification" msgid="1805579519992520381">"Wireless display certification"</string>
-    <string name="wifi_verbose_logging" msgid="1785910450009679371">"Enable Wi‑Fi verbose logging"</string>
+    <string name="wifi_verbose_logging" msgid="1785910450009679371">"Enable Wi‑Fi Verbose Logging"</string>
     <string name="wifi_scan_throttling" msgid="2985624788509913617">"Wi‑Fi scan throttling"</string>
-    <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Wi‑Fi non‑persistent MAC randomisation"</string>
+    <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Wi‑Fi non‑persistent MAC randomization"</string>
     <string name="mobile_data_always_on" msgid="8275958101875563572">"Mobile data always active"</string>
     <string name="tethering_hardware_offload" msgid="4116053719006939161">"Tethering hardware acceleration"</string>
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Show Bluetooth devices without names"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Disable absolute volume"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Enable Gabeldorsche"</string>
-    <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP version"</string>
+    <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP Version"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Select Bluetooth AVRCP Version"</string>
-    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP version"</string>
-    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Select Bluetooth MAP version"</string>
-    <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth audio codec"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP Version"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Select Bluetooth MAP Version"</string>
+    <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth Audio Codec"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Trigger Bluetooth Audio Codec\nSelection"</string>
-    <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth audio sample rate"</string>
+    <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth Audio Sample Rate"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"Trigger Bluetooth Audio Codec\nSelection: Sample Rate"</string>
-    <string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"Grey-out means not supported by phone or headset"</string>
-    <string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"Bluetooth audio bits per sample"</string>
+    <string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"Gray-out means not supported by phone or headset"</string>
+    <string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"Bluetooth Audio Bits Per Sample"</string>
     <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"Trigger Bluetooth Audio Codec\nSelection: Bits Per Sample"</string>
-    <string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"Bluetooth audio channel mode"</string>
+    <string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"Bluetooth Audio Channel Mode"</string>
     <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="2076949781460359589">"Trigger Bluetooth Audio Codec\nSelection: Channel Mode"</string>
-    <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"Bluetooth audio LDAC codec: Playback quality"</string>
+    <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="7274396574659784285">"Trigger Bluetooth Audio LDAC\nCodec Selection: Playback Quality"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="2040810756832027227">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
     <string name="select_private_dns_configuration_title" msgid="7887550926056143018">"Private DNS"</string>
@@ -301,14 +301,14 @@
     <string name="private_dns_mode_provider_failure" msgid="8356259467861515108">"Couldn\'t connect"</string>
     <string name="wifi_display_certification_summary" msgid="8111151348106907513">"Show options for wireless display certification"</string>
     <string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
-    <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Reduces battery drain and improves network performance"</string>
-    <string name="wifi_non_persistent_mac_randomization_summary" msgid="2159794543105053930">"When this mode is enabled, this device’s MAC address may change each time that it connects to a network that has MAC randomisation enabled."</string>
+    <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Reduces battery drain &amp; improves network performance"</string>
+    <string name="wifi_non_persistent_mac_randomization_summary" msgid="2159794543105053930">"When this mode is enabled, this device’s MAC address may change each time it connects to a network that has MAC randomization enabled."</string>
     <string name="wifi_metered_label" msgid="8737187690304098638">"Metered"</string>
     <string name="wifi_unmetered_label" msgid="6174142840934095093">"Unmetered"</string>
     <string name="select_logd_size_title" msgid="1604578195914595173">"Logger buffer sizes"</string>
     <string name="select_logd_size_dialog_title" msgid="2105401994681013578">"Select Logger sizes per log buffer"</string>
     <string name="dev_logpersist_clear_warning_title" msgid="8631859265777337991">"Clear logger persistent storage?"</string>
-    <string name="dev_logpersist_clear_warning_message" msgid="6447590867594287413">"When we are no longer monitoring with the persistent logger, we are required to erase the logger data resident on your device."</string>
+    <string name="dev_logpersist_clear_warning_message" msgid="6447590867594287413">"When we no longer are monitoring with the persistent logger, we are required to erase the logger data resident on your device."</string>
     <string name="select_logpersist_title" msgid="447071974007104196">"Store logger data persistently on device"</string>
     <string name="select_logpersist_dialog_title" msgid="7745193591195485594">"Select log buffers to store persistently on device"</string>
     <string name="select_usb_configuration_title" msgid="6339801314922294586">"Select USB Configuration"</string>
@@ -319,22 +319,22 @@
     <string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Always keep mobile data active, even when Wi‑Fi is active (for fast network switching)."</string>
     <string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Use tethering hardware acceleration if available"</string>
     <string name="adb_warning_title" msgid="7708653449506485728">"Allow USB debugging?"</string>
-    <string name="adb_warning_message" msgid="8145270656419669221">"USB debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification and read log data."</string>
+    <string name="adb_warning_message" msgid="8145270656419669221">"USB debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data."</string>
     <string name="adbwifi_warning_title" msgid="727104571653031865">"Allow wireless debugging?"</string>
     <string name="adbwifi_warning_message" msgid="8005936574322702388">"Wireless debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data."</string>
-    <string name="adb_keys_warning_message" msgid="2968555274488101220">"Revoke access to USB debugging from all computers you\'ve previously authorised?"</string>
+    <string name="adb_keys_warning_message" msgid="2968555274488101220">"Revoke access to USB debugging from all computers you’ve previously authorized?"</string>
     <string name="dev_settings_warning_title" msgid="8251234890169074553">"Allow development settings?"</string>
     <string name="dev_settings_warning_message" msgid="37741686486073668">"These settings are intended for development use only. They can cause your device and the applications on it to break or misbehave."</string>
     <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Verify apps over USB"</string>
-    <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Check apps installed via ADB/ADT for harmful behaviour."</string>
+    <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Check apps installed via ADB/ADT for harmful behavior."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth devices without names (MAC addresses only) will be displayed"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control."</string>
     <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Enables the Bluetooth Gabeldorsche feature stack."</string>
-    <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Enables the enhanced connectivity feature."</string>
+    <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Enables the Enhanced Connectivity feature."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Local terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Enable terminal app that offers local shell access"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP checking"</string>
-    <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"Set HDCP checking behaviour"</string>
+    <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"Set HDCP checking behavior"</string>
     <string name="debug_debugging_category" msgid="535341063709248842">"Debugging"</string>
     <string name="debug_app" msgid="8903350241392391766">"Select debug app"</string>
     <string name="debug_app_not_set" msgid="1934083001283807188">"No debug application set"</string>
@@ -363,7 +363,7 @@
     <string name="debug_hw_overdraw" msgid="8944851091008756796">"Debug GPU overdraw"</string>
     <string name="disable_overlays" msgid="4206590799671557143">"Disable HW overlays"</string>
     <string name="disable_overlays_summary" msgid="1954852414363338166">"Always use GPU for screen compositing"</string>
-    <string name="simulate_color_space" msgid="1206503300335835151">"Simulate colour space"</string>
+    <string name="simulate_color_space" msgid="1206503300335835151">"Simulate color space"</string>
     <string name="enable_opengl_traces_title" msgid="4638773318659125196">"Enable OpenGL traces"</string>
     <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Disable USB audio routing"</string>
     <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Disable automatic routing to USB audio peripherals"</string>
@@ -379,31 +379,31 @@
     <string name="enable_gpu_debug_layers" msgid="4986675516188740397">"Enable GPU debug layers"</string>
     <string name="enable_gpu_debug_layers_summary" msgid="4921521407377170481">"Allow loading GPU debug layers for debug apps"</string>
     <string name="enable_verbose_vendor_logging" msgid="1196698788267682072">"Enable verbose vendor logging"</string>
-    <string name="enable_verbose_vendor_logging_summary" msgid="5426292185780393708">"Include additional device-specific vendor logs in bug reports, which may contain private information, use more battery and/or use more storage."</string>
+    <string name="enable_verbose_vendor_logging_summary" msgid="5426292185780393708">"Include additional device-specific vendor logs in bug reports, which may contain private information, use more battery, and/or use more storage."</string>
     <string name="window_animation_scale_title" msgid="5236381298376812508">"Window animation scale"</string>
     <string name="transition_animation_scale_title" msgid="1278477690695439337">"Transition animation scale"</string>
     <string name="animator_duration_scale_title" msgid="7082913931326085176">"Animator duration scale"</string>
     <string name="overlay_display_devices_title" msgid="5411894622334469607">"Simulate secondary displays"</string>
     <string name="debug_applications_category" msgid="5394089406638954196">"Apps"</string>
-    <string name="immediately_destroy_activities" msgid="1826287490705167403">"Don\'t keep activities"</string>
+    <string name="immediately_destroy_activities" msgid="1826287490705167403">"Don’t keep activities"</string>
     <string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"Destroy every activity as soon as the user leaves it"</string>
     <string name="app_process_limit_title" msgid="8361367869453043007">"Background process limit"</string>
     <string name="show_all_anrs" msgid="9160563836616468726">"Show background ANRs"</string>
-    <string name="show_all_anrs_summary" msgid="8562788834431971392">"Display App Not Responding dialogue for background apps"</string>
+    <string name="show_all_anrs_summary" msgid="8562788834431971392">"Display App Not Responding dialog for background apps"</string>
     <string name="show_notification_channel_warnings" msgid="3448282400127597331">"Show notification channel warnings"</string>
     <string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"Displays on-screen warning when an app posts a notification without a valid channel"</string>
     <string name="force_allow_on_external" msgid="9187902444231637880">"Force allow apps on external"</string>
     <string name="force_allow_on_external_summary" msgid="8525425782530728238">"Makes any app eligible to be written to external storage, regardless of manifest values"</string>
-    <string name="force_resizable_activities" msgid="7143612144399959606">"Force activities to be resizeable"</string>
-    <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizeable for multi-window, regardless of manifest values."</string>
+    <string name="force_resizable_activities" msgid="7143612144399959606">"Force activities to be resizable"</string>
+    <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizable for multi-window, regardless of manifest values."</string>
     <string name="enable_freeform_support" msgid="7599125687603914253">"Enable freeform windows"</string>
     <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Enable support for experimental freeform windows."</string>
     <string name="desktop_mode" msgid="2389067840550544462">"Desktop mode"</string>
     <string name="local_backup_password_title" msgid="4631017948933578709">"Desktop backup password"</string>
-    <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren\'t currently protected"</string>
+    <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren’t currently protected"</string>
     <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tap to change or remove the password for desktop full backups"</string>
     <string name="local_backup_password_toast_success" msgid="4891666204428091604">"New backup password set"</string>
-    <string name="local_backup_password_toast_confirmation_mismatch" msgid="2994718182129097733">"New password and confirmation don\'t match"</string>
+    <string name="local_backup_password_toast_confirmation_mismatch" msgid="2994718182129097733">"New password and confirmation don’t match"</string>
     <string name="local_backup_password_toast_validation_failure" msgid="714669442363647122">"Failure setting backup password"</string>
     <string name="loading_injected_setting_summary" msgid="8394446285689070348">"Loading…"</string>
   <string-array name="color_mode_names">
@@ -412,9 +412,9 @@
     <item msgid="6564241960833766170">"Standard"</item>
   </string-array>
   <string-array name="color_mode_descriptions">
-    <item msgid="6828141153199944847">"Enhanced colours"</item>
-    <item msgid="4548987861791236754">"Natural colours as seen by the eye"</item>
-    <item msgid="1282170165150762976">"Colours optimised for digital content"</item>
+    <item msgid="6828141153199944847">"Enhanced colors"</item>
+    <item msgid="4548987861791236754">"Natural colors as seen by the eye"</item>
+    <item msgid="1282170165150762976">"Colors optimized for digital content"</item>
   </string-array>
     <string name="inactive_apps_title" msgid="5372523625297212320">"Standby apps"</string>
     <string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Inactive. Tap to toggle."</string>
@@ -431,15 +431,15 @@
     <string name="select_webview_provider_title" msgid="3917815648099445503">"WebView implementation"</string>
     <string name="select_webview_provider_dialog_title" msgid="2444261109877277714">"Set WebView implementation"</string>
     <string name="select_webview_provider_toast_text" msgid="8512254949169359848">"This choice is no longer valid. Try again."</string>
-    <string name="picture_color_mode" msgid="1013807330552931903">"Picture colour mode"</string>
+    <string name="picture_color_mode" msgid="1013807330552931903">"Picture color mode"</string>
     <string name="picture_color_mode_desc" msgid="151780973768136200">"Use sRGB"</string>
     <string name="daltonizer_mode_disabled" msgid="403424372812399228">"Disabled"</string>
     <string name="daltonizer_mode_monochromacy" msgid="362060873835885014">"Monochromacy"</string>
     <string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Deuteranomaly (red-green)"</string>
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string>
-    <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Colour correction can be helpful when you want to:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;See colours more accurately&lt;/li&gt; &lt;li&gt;&amp;nbsp;Remove colours to help you focus&lt;/li&gt; &lt;/ol&gt;"</string>
+    <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Color correction"</string>
+    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Color correction can be helpful when you want to:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;See colors more accurately&lt;/li&gt; &lt;li&gt;&amp;nbsp;Remove colors to help you focus&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
@@ -466,8 +466,9 @@
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="4374784375644214578">"Device may shut down soon (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
-    <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
+    <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging to <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
@@ -477,9 +478,9 @@
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string>
     <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connected, not charging"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
-    <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully charged"</string>
+    <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully Charged"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlled by admin"</string>
-    <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by restricted setting"</string>
+    <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by Restricted Setting"</string>
     <string name="disabled" msgid="8017887509554714950">"Disabled"</string>
     <string name="external_source_trusted" msgid="1146522036773132905">"Allowed"</string>
     <string name="external_source_untrusted" msgid="5037891688911672227">"Not allowed"</string>
@@ -505,13 +506,13 @@
     <string name="active_input_method_subtypes" msgid="4232680535471633046">"Active input methods"</string>
     <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Use system languages"</string>
     <string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"Failed to open settings for <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
-    <string name="ime_security_warning" msgid="6547562217880551450">"This input method may be able to collect all the text that you type, including personal data like passwords and credit card numbers. It comes from the app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Use this input method?"</string>
+    <string name="ime_security_warning" msgid="6547562217880551450">"This input method may be able to collect all the text you type, including personal data like passwords and credit card numbers. It comes from the app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Use this input method?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"Note: After a reboot, this app can\'t start until you unlock your phone"</string>
     <string name="ims_reg_title" msgid="8197592958123671062">"IMS registration state"</string>
     <string name="ims_reg_status_registered" msgid="884916398194885457">"Registered"</string>
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Not registered"</string>
     <string name="status_unavailable" msgid="5279036186589861608">"Unavailable"</string>
-    <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomised"</string>
+    <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomized"</string>
     <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{1 device connected}other{# devices connected}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"More time."</string>
     <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Less time."</string>
@@ -520,7 +521,7 @@
     <string name="done" msgid="381184316122520313">"Done"</string>
     <string name="alarms_and_reminders_label" msgid="6918395649731424294">"Alarms and reminders"</string>
     <string name="alarms_and_reminders_switch_title" msgid="4939393911531826222">"Allow setting alarms and reminders"</string>
-    <string name="alarms_and_reminders_title" msgid="8819933264635406032">"Alarms and reminders"</string>
+    <string name="alarms_and_reminders_title" msgid="8819933264635406032">"Alarms &amp; reminders"</string>
     <string name="alarms_and_reminders_footer_title" msgid="6302587438389079695">"Allow this app to set alarms and schedule time-sensitive actions. This lets the app run in the background, which may use more battery.\n\nIf this permission is off, existing alarms and time-based events scheduled by this app won’t work."</string>
     <string name="keywords_alarms_and_reminders" msgid="6633360095891110611">"schedule, alarm, reminder, clock"</string>
     <string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"Turn on"</string>
@@ -539,7 +540,7 @@
     <string name="media_transfer_this_device_name" product="default" msgid="2357329267148436433">"This phone"</string>
     <string name="media_transfer_this_device_name" product="tablet" msgid="3714653244000242800">"This tablet"</string>
     <string name="media_transfer_this_phone" msgid="7194341457812151531">"This phone"</string>
-    <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off and back on"</string>
+    <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off &amp; back on"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired audio device"</string>
     <string name="help_label" msgid="3528360748637781274">"Help and feedback"</string>
     <string name="storage_category" msgid="2287342585424631813">"Storage"</string>
@@ -548,23 +549,23 @@
     <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"There is no shared data for this user."</string>
     <string name="shared_data_query_failure_text" msgid="3489828881998773687">"There was an error fetching shared data. Try again."</string>
     <string name="blob_id_text" msgid="8680078988996308061">"Shared data ID: <xliff:g id="BLOB_ID">%d</xliff:g>"</string>
-    <string name="blob_expires_text" msgid="7882727111491739331">"Expires on <xliff:g id="DATE">%s</xliff:g>"</string>
+    <string name="blob_expires_text" msgid="7882727111491739331">"Expires at <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"There was an error deleting the shared data."</string>
     <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"There are no leases acquired for this shared data. Would you like to delete it?"</string>
     <string name="accessor_info_title" msgid="8289823651512477787">"Apps sharing data"</string>
     <string name="accessor_no_description_text" msgid="7510967452505591456">"No description provided by the app."</string>
-    <string name="accessor_expires_text" msgid="4625619273236786252">"Lease expires on <xliff:g id="DATE">%s</xliff:g>"</string>
+    <string name="accessor_expires_text" msgid="4625619273236786252">"Lease expires at <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="delete_blob_text" msgid="2819192607255625697">"Delete shared data"</string>
-    <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"Are you sure that you want to delete this shared data?"</string>
+    <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"Are you sure you want to delete this shared data?"</string>
     <string name="user_add_user_item_summary" msgid="5748424612724703400">"Users have their own apps and content"</string>
     <string name="user_add_profile_item_summary" msgid="5418602404308968028">"You can restrict access to apps and content from your account"</string>
     <string name="user_add_user_item_title" msgid="2394272381086965029">"User"</string>
     <string name="user_add_profile_item_title" msgid="3111051717414643029">"Restricted profile"</string>
     <string name="user_add_user_title" msgid="5457079143694924885">"Add new user?"</string>
-    <string name="user_add_user_message_long" msgid="1527434966294733380">"You can share this device with other people by creating additional users. Each user has their own space, which they can customise with apps, wallpaper and so on. Users can also adjust device settings such as Wi‑Fi that affect everyone.\n\nWhen you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. Accessibility settings and services may not transfer to the new user."</string>
+    <string name="user_add_user_message_long" msgid="1527434966294733380">"You can share this device with other people by creating additional users. Each user has their own space, which they can customize with apps, wallpaper, and so on. Users can also adjust device settings like Wi‑Fi that affect everyone.\n\nWhen you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. Accessibility settings and services may not transfer to the new user."</string>
     <string name="user_add_user_message_short" msgid="3295959985795716166">"When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users."</string>
     <string name="user_setup_dialog_title" msgid="8037342066381939995">"Set up user now?"</string>
-    <string name="user_setup_dialog_message" msgid="269931619868102841">"Make sure that the person is available to take the device and set up their space."</string>
+    <string name="user_setup_dialog_message" msgid="269931619868102841">"Make sure the person is available to take the device and set up their space"</string>
     <string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Set up profile now?"</string>
     <string name="user_setup_button_setup_now" msgid="1708269547187760639">"Set up now"</string>
     <string name="user_setup_button_setup_later" msgid="8712980133555493516">"Not now"</string>
@@ -573,7 +574,7 @@
     <string name="user_new_profile_name" msgid="2405500423304678841">"New profile"</string>
     <string name="user_info_settings_title" msgid="6351390762733279907">"User info"</string>
     <string name="profile_info_settings_title" msgid="105699672534365099">"Profile info"</string>
-    <string name="user_need_lock_message" msgid="4311424336209509301">"Before you can create a restricted profile, you\'ll need to set up a screen lock to protect your apps and personal data."</string>
+    <string name="user_need_lock_message" msgid="4311424336209509301">"Before you can create a restricted profile, you’ll need to set up a screen lock to protect your apps and personal data."</string>
     <string name="user_set_lock_button" msgid="1427128184982594856">"Set lock"</string>
     <string name="user_switch_to_user" msgid="6975428297154968543">"Switch to <xliff:g id="USER_NAME">%s</xliff:g>"</string>
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
@@ -616,7 +617,7 @@
     <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string>
     <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
     <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
-    <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphones"</string>
+    <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
     <string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"On"</string>
     <string name="wifi_hotspot_switch_off_text" msgid="7245567251496959764">"Off"</string>
     <string name="carrier_network_change_mode" msgid="4257621815706644026">"Carrier network changing"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 09bc065..208dbfa 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 09bc065..208dbfa 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index f82a757..bfd7e83 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -468,6 +468,7 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="TIME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ left until full‎‏‎‎‏‎"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - ‎‏‎‎‏‏‎<xliff:g id="TIME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ left until full‎‏‎‎‏‎"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‎‏‎‏‎‏‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - Charging is paused‎‏‎‎‏‎"</string>
+    <string name="power_charging_future_paused" msgid="6829683663982987290">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - Charging to ‎‏‎‎‏‏‎<xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎Unknown‎‏‎‎‏‎"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‏‎‏‎Charging‎‏‎‎‏‎"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‏‏‏‎‎‎‏‎‎‎‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‏‏‎‏‏‎‎‎‎‏‎‏‎Charging rapidly‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 1a4e307..c11d6eb0 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -232,7 +232,7 @@
     <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para ver y usar los dispositivos disponibles, activa la depuración inalámbrica"</string>
     <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Vincular dispositivo mediante código QR"</string>
     <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Vincular dispositivos nuevos mediante escáner de código QR"</string>
-    <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Vincular dispositivo con código de sincronización"</string>
+    <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Vincular dispositivo con un código de vinculación"</string>
     <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Vincular dispositivos nuevos mediante código de seis dígitos"</string>
     <string name="adb_paired_devices_title" msgid="5268997341526217362">"Dispositivos vinculados"</string>
     <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Conectado actualmente"</string>
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> para completar"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> para completar"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Se pausó la carga"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Cargando rápidamente"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index fdf99e0..1ef351a 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> hasta la carga completa"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> hasta la carga completa"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga en pausa"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carga rápida"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index bcd395c..e156011 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Täislaadimiseks kulub <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – täislaadimiseks kulub <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimine on peatatud"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tundmatu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Laadimine"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Kiirlaadimine"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 29d7d95..e5f399c 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> guztiz kargatu arte"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> guztiz kargatu arte"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kargatze-prozesua etenda dago"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ezezaguna"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Kargatzen"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Bizkor kargatzen"</string>
@@ -610,7 +612,7 @@
     <string name="user_image_photo_selector" msgid="433658323306627093">"Hautatu argazki bat"</string>
     <string name="failed_attempts_now_wiping_device" msgid="4016329172216428897">"Saiakera oker gehiegi egin dituzu. Gailu honetako datuak ezabatu egingo dira."</string>
     <string name="failed_attempts_now_wiping_user" msgid="469060411789668050">"Saiakera oker gehiegi egin dituzu. Erabiltzailea ezabatu egingo da."</string>
-    <string name="failed_attempts_now_wiping_profile" msgid="7626589520888963129">"Saiakera oker gehiegi egin dituzu. Laneko profila eta bertako datuak ezabatu egingo dira."</string>
+    <string name="failed_attempts_now_wiping_profile" msgid="7626589520888963129">"Saiakera oker gehiegi egin dituzu. Laneko profila eta bertako datuak ezabatuko dira."</string>
     <string name="failed_attempts_now_wiping_dialog_dismiss" msgid="2749889771223578925">"Baztertu"</string>
     <string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Gailuaren balio lehenetsia"</string>
     <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desgaituta"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index d40f692..ccacfde 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> تا شارژ کامل باقی مانده است"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> تا شارژ کامل باقی مانده است"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - شارژ موقتاً متوقف شده است"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ناشناس"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"در حال شارژ شدن"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"درحال شارژ شدن سریع"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 0d6036a..101dc67 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> kunnes täynnä"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> kunnes täynnä"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Lataus on keskeytetty"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tuntematon"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Ladataan"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Nopea lataus"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index f276236..ff58a51 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> jusqu\'à la recharge complète"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la recharge complète)"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - La recharge est interrompue"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Charge en cours…"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Recharge rapide"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 7c4afa7..f9725f6 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Chargée à 100 %% dans <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - chargée à 100 %% dans <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – La recharge est en pause"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Batterie en charge"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charge rapide"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 95d5e21..3ceb99c 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> para completar a carga"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> para completar a carga)"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: a carga está en pausa"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Descoñecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Cargando rapidamente"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index e8acbd5..1821546 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"પૂર્ણ ચાર્જ થવામાં <xliff:g id="TIME">%1$s</xliff:g> બાકી છે"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - પૂર્ણ ચાર્જ થવામાં <xliff:g id="TIME">%2$s</xliff:g> બાકી છે"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જિંગ થોભાવવામાં આવ્યું છે"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"અજાણ્યું"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ચાર્જ થઈ રહ્યું છે"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ઝડપથી ચાર્જ થાય છે"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 1c4e4a0..4b899bb 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -174,7 +174,7 @@
     <string name="launch_defaults_some" msgid="3631650616557252926">"कुछ डिफ़ॉल्‍ट सेट हैं"</string>
     <string name="launch_defaults_none" msgid="8049374306261262709">"कोई डिफ़ॉल्‍ट सेट नहीं है"</string>
     <string name="tts_settings" msgid="8130616705989351312">"लेख से बोली सेटिंग"</string>
-    <string name="tts_settings_title" msgid="7602210956640483039">"लिखाई को बोली में बदलना"</string>
+    <string name="tts_settings_title" msgid="7602210956640483039">"लिखाई को बोली में बदलने की सुविधा"</string>
     <string name="tts_default_rate_title" msgid="3964187817364304022">"बोली दर"</string>
     <string name="tts_default_rate_summary" msgid="3781937042151716987">"बोलने की गति तय करें"</string>
     <string name="tts_default_pitch_title" msgid="6988592215554485479">"पिच"</string>
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> में बैटरी पूरी चार्ज हो जाएगी"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> में बैटरी पूरी चार्ज हो जाएगी"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंग को रोका गया है"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हो रही है"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"तेज़ चार्ज हो रही है"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 06166f6..46c8b90 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do napunjenosti"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do napunjenosti"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je pauzirano"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo punjenje"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index af5a114..d74490f 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> a teljes töltöttségig"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> a teljes töltöttségig"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – A töltés szünetel"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ismeretlen"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Töltés"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Gyorstöltés"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 61c3e7e..c26f94f 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> մինչև լրիվ լիցքավորումը"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> մինչև լրիվ լիցքավորումը"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Լիցքավորումը դադարեցված է"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Անհայտ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Լիցքավորում"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Արագ լիցքավորում"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index d7f5857..203485f 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> lagi sampai penuh"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi sampai penuh"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengisian daya dijeda"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengisi daya"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mengisi daya cepat"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 4862904..9185877 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> fram að fullri hleðslu"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> fram að fullri hleðslu"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Hlé var gert á hleðslu"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Óþekkt"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Í hleðslu"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hröð hleðsla"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 9e5a224..0cd9e1e 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> alla ricarica completa"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> alla ricarica completa"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ricarica in pausa"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Sconosciuta"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"In carica"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ricarica veloce"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index c6afad9..6293105 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"הזמן הנותר לטעינה מלאה: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – הזמן הנותר לטעינה מלאה: <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – הטעינה הושהתה"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"לא ידוע"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"בטעינה"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"הסוללה נטענת מהר"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 43fe178..9a9d6ea 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"完了まであと <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - 完了まであと <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電は一時停止中"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"急速充電中"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index c498b61..2fa2d80 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"სრულ დატენვამდე დარჩენილია <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> — სრულ დატენვამდე დარჩენილია <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - დატენვა ᲨეᲩერებულია"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"უცნობი"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"იტენება"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"სწრაფად იტენება"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 314683b..a332004 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Толық зарядталғанға дейін <xliff:g id="TIME">%1$s</xliff:g> қалды."</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – толық зарядталғанға дейін <xliff:g id="TIME">%2$s</xliff:g> қалды."</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядтау кідіртілді."</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгісіз"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарядталуда"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Жылдам зарядталуда"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 00668fe..2d78295 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> ទៀតទើបពេញ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - នៅសល់ <xliff:g id="TIME">%2$s</xliff:g> ទៀតទើបពេញ"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ការសាកថ្មត្រូវបានផ្អាក"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"មិន​ស្គាល់"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"កំពុងសាក​ថ្ម"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"កំពុងសាកថ្មយ៉ាងឆាប់រហ័ស"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index ebf742b..5db4b16 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> - ಸಮಯದಲ್ಲಿ ಪೂರ್ತಿ ಚಾರ್ಜ್ ಆಗುತ್ತದೆ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ತಿ ಚಾರ್ಜ್ ಆಗುತ್ತದೆ"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜಿಂಗ್ ಅನ್ನು ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ಅಪರಿಚಿತ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ವೇಗದ ಚಾರ್ಜಿಂಗ್"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 21c8091..30ab26c 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> 후 충전 완료"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> 후 충전 완료"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 일시중지됨"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"알 수 없음"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"충전 중"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"고속 충전 중"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 076f317a..baacfd7 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> кийин толук кубатталат"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> кийин толук кубатталат"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Кубаттоо тындырылды"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгисиз"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Кубатталууда"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ыкчам кубатталууда"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 9ebe67a..36755f7 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ຍັງເຫຼືອອີກ <xliff:g id="TIME">%1$s</xliff:g> ຈຶ່ງຈະສາກເຕັມ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"ຍັງເຫຼືອອີກ <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ຈຶ່ງຈະສາກເຕັມ"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ການສາກໄຟຖືກຢຸດໄວ້ຊົ່ວຄາວ"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ບໍ່ຮູ້ຈັກ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ກຳລັງສາກໄຟ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ກຳລັງສາກໄຟດ່ວນ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index fcea778..50652ea 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Liko <xliff:g id="TIME">%1$s</xliff:g>, kol bus visiškai įkrauta"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – liko <xliff:g id="TIME">%2$s</xliff:g>, kol bus visiškai įkrauta"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – įkrovimas pristabdytas"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nežinomas"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Kraunasi..."</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Greitai įkraunama"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index c280737..c199fea 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> līdz pilnai uzlādei"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="TIME">%2$s</xliff:g> līdz pilnai uzlādei"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> — uzlāde ir pārtraukta"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nezināms"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Uzlāde"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Notiek ātrā uzlāde"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index f849900..957f68b 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до полна батерија"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> до полна батерија"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Полнењето е паузирано"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Се полни"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Брзо полнење"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index cc15e03..1cc92ca 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"പൂർണ്ണമാകാൻ <xliff:g id="TIME">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - പൂർണ്ണമാകാൻ <xliff:g id="TIME">%2$s</xliff:g> ശേഷിക്കുന്നു"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ചാർജിംഗ് താൽക്കാലികമായി നിർത്തി"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"അജ്ഞാതം"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ചാർജ് ചെയ്യുന്നു"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"അതിവേഗ ചാർജിംഗ്"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 3701a30..a107c70 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Дүүрэх хүртэл <xliff:g id="TIME">%1$s</xliff:g> үлдсэн"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - дүүрэх хүртэл <xliff:g id="TIME">%2$s</xliff:g> үлдсэн"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Цэнэглэхийг түр зогсоосон"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Тодорхойгүй"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Цэнэглэж байна"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Хурдан цэнэглэж байна"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 3fffc1a..d5c83a8 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"पूर्ण चार्ज होण्यासाठी <xliff:g id="TIME">%1$s</xliff:g> शिल्लक आहेत"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - पूर्ण चार्ज होण्यासाठी <xliff:g id="TIME">%2$s</xliff:g> शिल्लक आहे"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्ज करणे थांबवले आहे"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज होत आहे"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"वेगाने चार्ज होत आहे"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 79553c0..ae8be8e 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> lagi sebelum penuh"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi sebelum penuh"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengecasan dijeda"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengecas"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mengecas dgn cepat"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index fc38626..f16cdca 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"အားပြည့်ရန် <xliff:g id="TIME">%1$s</xliff:g> လိုသည်"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"အားပြည့်ရန် <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> လိုသည်"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားသွင်းခြင်းကို ခဏရပ်ထားသည်"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"မသိ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"အားသွင်းနေပါသည်"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"အမြန် အားသွင်းနေသည်"</string>
diff --git a/packages/SettingsLib/res/values-nb/arrays.xml b/packages/SettingsLib/res/values-nb/arrays.xml
index 7e65fa0..928ebc3 100644
--- a/packages/SettingsLib/res/values-nb/arrays.xml
+++ b/packages/SettingsLib/res/values-nb/arrays.xml
@@ -49,9 +49,9 @@
     <item msgid="1999413958589971747">"Unngår dårlig tilkobling midlertidig"</item>
   </string-array>
   <string-array name="hdcp_checking_titles">
-    <item msgid="2377230797542526134">"Kontrollér aldri"</item>
-    <item msgid="3919638466823112484">"Kontrollér kun DRM-innhold"</item>
-    <item msgid="9048424957228926377">"Kontrollér alltid"</item>
+    <item msgid="2377230797542526134">"Kontroller aldri"</item>
+    <item msgid="3919638466823112484">"Kontroller kun DRM-innhold"</item>
+    <item msgid="9048424957228926377">"Kontroller alltid"</item>
   </string-array>
   <string-array name="hdcp_checking_summaries">
     <item msgid="4045840870658484038">"Bruk aldri HDCP-kontroll"</item>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index c292e88..f7a10a6 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -427,7 +427,7 @@
     <string name="transcode_notification" msgid="5560515979793436168">"Vis omkodingsvarsler"</string>
     <string name="transcode_disable_cache" msgid="3160069309377467045">"Slå av omkodingsbuffer"</string>
     <string name="runningservices_settings_title" msgid="6460099290493086515">"Aktive tjenester"</string>
-    <string name="runningservices_settings_summary" msgid="1046080643262665743">"Se og kontrollér tjenester som kjører"</string>
+    <string name="runningservices_settings_summary" msgid="1046080643262665743">"Se og kontroller tjenester som kjører"</string>
     <string name="select_webview_provider_title" msgid="3917815648099445503">"WebView-implementering"</string>
     <string name="select_webview_provider_dialog_title" msgid="2444261109877277714">"Angi WebView-implementering"</string>
     <string name="select_webview_provider_toast_text" msgid="8512254949169359848">"Dette valget er ikke gyldig lenger. Prøv på nytt."</string>
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Fulladet om <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Fulladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ladingen er satt på pause"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukjent"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Lader"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Lader raskt"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 777c897..a966f15 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"पूरा चार्ज हुन <xliff:g id="TIME">%1$s</xliff:g> लाग्ने छ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - पूरा चार्ज हुन <xliff:g id="TIME">%2$s</xliff:g> लाग्ने छ"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्ज गर्ने प्रक्रिया रोकिएको छ"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हुँदै छ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"द्रुत गतिमा चार्ज गरिँदै छ"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index d30a21f..c5406ee 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Vol over <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - vol over <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Opladen is onderbroken"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Opladen"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Snel opladen"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 0120768..37cc25e 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ପୂର୍ଣ୍ଣ ହେବାକୁ ଆଉ <xliff:g id="TIME">%1$s</xliff:g> ବାକି ଅଛି"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - ପୂର୍ଣ୍ଣ ହେବାକୁ ଆଉ <xliff:g id="TIME">%2$s</xliff:g> ବାକି ଅଛି"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ଚାର୍ଜିଂକୁ ବିରତ କରାଯାଇଛି"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ଅଜ୍ଞାତ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ଚାର୍ଜ ହେଉଛି"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ଶୀଘ୍ର ଚାର୍ଜ ହେଉଛି"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 1481767..767cbc5 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ਬੈਟਰੀ ਪੂਰੀ ਚਾਰਜ ਹੋਣ ਵਿੱਚ <xliff:g id="TIME">%1$s</xliff:g> ਬਾਕੀ"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਬੈਟਰੀ ਪੂਰੀ ਚਾਰਜ ਹੋਣ ਵਿੱਚ <xliff:g id="TIME">%2$s</xliff:g> ਬਾਕੀ"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਚਾਰਜਿੰਗ ਨੂੰ ਰੋਕਿਆ ਗਿਆ ਹੈ"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ਅਗਿਆਤ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ਤੇਜ਼ ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 83f8e49..48bc82e 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do pełnego naładowania"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do pełnego naładowania"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – ładowanie zostało wstrzymane"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Nieznane"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Ładowanie"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Szybkie ładowanie"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index d9a7c69..68a1c49 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até a conclusão"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a conclusão"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: o carregamento está pausado"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregando rápido"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index c24589c..87bab50 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até à carga máxima"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> até à carga máxima"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – O carregamento está pausado"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"A carregar"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregamento rápido"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index d9a7c69..68a1c49 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até a conclusão"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a conclusão"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: o carregamento está pausado"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregando rápido"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 04c58ae..f81ef4c 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> până la finalizare"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> până la finalizare"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Încărcarea este întreruptă"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Necunoscut"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Se încarcă"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Se încarcă rapid"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 704d848..4dcee13 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до полной зарядки"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до полной зарядки"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: зарядка приостановлена"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Идет зарядка"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Быстрая зарядка"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index d67060e..17c59ea 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"සම්පූර්ණ වීමට <xliff:g id="TIME">%1$s</xliff:g>ක් ඉතිරියි"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - සම්පූර්ණ වීමට <xliff:g id="TIME">%2$s</xliff:g>ක් ඉතිරියි"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය විරාම කර ඇත"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"නොදනී"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ආරෝපණය වෙමින්"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ශීඝ්‍ර ආරෝපණය"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 1982893..d9ce6cf 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do úplného nabitia"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Nabíjanie je pozastavené"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznáme"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíja sa"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Rýchle nabíjanie"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 262ecfd..8f9b059 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Še <xliff:g id="TIME">%1$s</xliff:g> do napolnjenosti"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – še <xliff:g id="TIME">%2$s</xliff:g> do napolnjenosti"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Polnjenje je začasno zaustavljeno"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznano"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Polnjenje"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hitro polnjenje"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index d0f1f7c..d467eea 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> derisa të mbushet"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> derisa të mbushet"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Karikimi është vendosur në pauzë"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"I panjohur"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Po karikohet"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Karikim i shpejtë"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index b102d3a..9cc43d9 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до краја пуњења"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до краја пуњења"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Пуњење је заустављено"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Пуни се"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Брзо се пуни"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 17a06c2..89deafa 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> kvar tills fulladdat"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> kvar tills fulladdat"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Laddningen har pausats"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Okänd"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Laddar"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Laddas snabbt"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 72a3751..1fa5edd 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Zimesalia <xliff:g id="TIME">%1$s</xliff:g> ijae chaji"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> zimesalia ijae chaji"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Imesitisha kuchaji"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Haijulikani"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Inachaji"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Inachaji kwa kasi"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 8714958..1b7b643 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"முழுவதும் சார்ஜாக <xliff:g id="TIME">%1$s</xliff:g> ஆகும்"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - முழுவதும் சார்ஜாக <xliff:g id="TIME">%2$s</xliff:g> ஆகும்"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - சார்ஜ் ஏறுவது இடைநிறுத்தப்பட்டுள்ளது"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"அறியப்படாத"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"சார்ஜ் ஆகிறது"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"வேகமாக சார்ஜாகிறது"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 5e00b1d..27e9dbd 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జింగ్ పాజ్ చేయబడింది"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"తెలియదు"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"ఛార్జ్ అవుతోంది"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"వేగవంతమైన ఛార్జింగ్"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index f386e5d..6b9c077 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"อีก <xliff:g id="TIME">%1$s</xliff:g>จึงจะเต็ม"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - อีก <xliff:g id="TIME">%2$s</xliff:g> จึงจะเต็ม"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - การชาร์จหยุดชั่วคราว"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"ไม่ทราบ"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"กำลังชาร์จ"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"กำลังชาร์จอย่างเร็ว"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index db27382..fddc1d0 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> na lang bago mapuno"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> na lang bago mapuno"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Naka-pause ang pag-charge"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Hindi Kilala"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Nagcha-charge"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mabilis na charge"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 8f4bae4..31dcedb8 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Tamamen şarj olmasına <xliff:g id="TIME">%1$s</xliff:g> kaldı"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - Tamamen şarj olmasına <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g>: Şarj işlemi duraklatıldı"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Bilinmiyor"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Şarj oluyor"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hızlı şarj oluyor"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index a0c68c1..2fdc227 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до повного заряду"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до повного заряду"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Заряджання призупинено"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Невідомо"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Заряджається"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Швидке заряджання"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index d0a1204..82dbdb3 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"مکمل چارج ہونے میں <xliff:g id="TIME">%1$s</xliff:g> باقی ہے"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"مکمل چارج ہونے میں <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> باقی ہے"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - چارجنگ موقوف ہے"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"نامعلوم"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"چارج ہو رہا ہے"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"تیزی سے چارج ہو رہا ہے"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 1738e01..ab997b6 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Toʻlishiga <xliff:g id="TIME">%1$s</xliff:g> qoldi"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Toʻlishiga <xliff:g id="TIME">%2$s</xliff:g> qoldi"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Quvvatlash pauza qilindi"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Noma’lum"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Quvvat olmoqda"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Tezkor quvvat olmoqda"</string>
@@ -548,7 +550,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/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index f7f4c0e..44c820a5 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -268,7 +268,7 @@
     <string name="mock_location_app_set" msgid="4706722469342913843">"Ứng dụng vị trí mô phỏng: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="debug_networking_category" msgid="6829757985772659599">"Mạng"</string>
     <string name="wifi_display_certification" msgid="1805579519992520381">"Chứng nhận hiển thị không dây"</string>
-    <string name="wifi_verbose_logging" msgid="1785910450009679371">"Bật ghi nhật ký chi tiết Wi‑Fi"</string>
+    <string name="wifi_verbose_logging" msgid="1785910450009679371">"Bật tính năng ghi nhật ký chi tiết Wi‑Fi"</string>
     <string name="wifi_scan_throttling" msgid="2985624788509913617">"Hạn chế quét tìm Wi-Fi"</string>
     <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Tạo địa chỉ MAC ngẫu nhiên, không cố định mỗi khi kết nối Wi-Fi"</string>
     <string name="mobile_data_always_on" msgid="8275958101875563572">"Dữ liệu di động luôn hoạt động"</string>
@@ -280,7 +280,7 @@
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Chọn phiên bản Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Phiên bản Bluetooth MAP"</string>
     <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Chọn phiên bản Bluetooth MAP"</string>
-    <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec âm thanh Bluetooth"</string>
+    <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bộ mã hoá và giải mã âm thanh qua Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Kích hoạt chế độ chọn codec\nâm thanh Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Tốc độ lấy mẫu âm thanh Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"Kích hoạt chế độ chọn codec\nâm thanh Bluetooth: Tần số lấy mẫu"</string>
@@ -374,7 +374,7 @@
     <string name="window_blurs" msgid="6831008984828425106">"Cho phép làm mờ cửa sổ"</string>
     <string name="force_msaa" msgid="4081288296137775550">"Bắt buộc 4x MSAA"</string>
     <string name="force_msaa_summary" msgid="9070437493586769500">"Bật 4x MSAA trong ứng dụng OpenGL ES 2.0"</string>
-    <string name="show_non_rect_clip" msgid="7499758654867881817">"Gỡ lỗi hoạt động của clip không phải là hình chữ nhật"</string>
+    <string name="show_non_rect_clip" msgid="7499758654867881817">"Gỡ lỗi hoạt động của đoạn không phải hình chữ nhật"</string>
     <string name="track_frame_time" msgid="522674651937771106">"Kết xuất HWUI cấu hình"</string>
     <string name="enable_gpu_debug_layers" msgid="4986675516188740397">"Bật lớp gỡ lỗi GPU"</string>
     <string name="enable_gpu_debug_layers_summary" msgid="4921521407377170481">"Cho phép tải lớp gỡ lỗi GPU cho ứng dụng gỡ lỗi"</string>
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> nữa là pin đầy"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> nữa là pin đầy"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> – Đã tạm dừng sạc"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Không xác định"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Đang sạc"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Đang sạc nhanh"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index f536bb9..eae1c39 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"还需<xliff:g id="TIME">%1$s</xliff:g>充满"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - 还需<xliff:g id="TIME">%2$s</xliff:g>充满"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充电已暂停"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"正在充电"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"正在快速充电"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 357302b..4371bb3 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>後充滿電"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充滿電"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 已暫停充電"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 4ae28304..d76a4a4 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>後充飽"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充飽"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - 已暫停充電"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index c113dc8..4dc7651 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -468,6 +468,8 @@
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> okusele kuze kugcwale"</string>
     <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> okusele kuze kugcwale"</string>
     <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ukushaja kumisiwe okwesikhashana"</string>
+    <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
+    <skip />
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Akwaziwa"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Iyashaja"</string>
     <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ishaja ngokushesha"</string>
diff --git a/packages/SettingsLib/res/values/carrierid_icon_overrides.xml b/packages/SettingsLib/res/values/carrierid_icon_overrides.xml
new file mode 100644
index 0000000..d2ae52d
--- /dev/null
+++ b/packages/SettingsLib/res/values/carrierid_icon_overrides.xml
@@ -0,0 +1,32 @@
+<?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.
+  -->
+<!--
+  ~ This resource file exists to enumerate all network type icon overrides on a
+  ~ per-carrierId basis
+-->
+<resources>
+    <!--
+    Network type (RAT) icon overrides can be configured here on a per-carrierId basis.
+        1. Add a new TypedArray here, using the naming scheme below
+        2. The entries are (NetworkType, drawable ID) pairs
+        3. Add this array's ID to the MAPPING field of MobileIconCarrierIdOverrides.kt
+    -->
+    <array name="carrierId_2032_iconOverrides">
+        <item>5G_PLUS</item>
+        <item>@drawable/ic_5g_plus_mobiledata_default</item>
+    </array>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 2e0155b..b693996 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1104,7 +1104,9 @@
     <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
     <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> left until full</string>
     <!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited -->
-    <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging is paused</string>
+    <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging paused</string>
+    <!-- [CHAR_LIMIT=80] Label for battery charging future pause -->
+    <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging to <xliff:g id="dock_defender_threshold">%2$s</xliff:g></string>
 
     <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
     <string name="battery_info_status_unknown">Unknown</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index a822e18..a78256a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -376,27 +376,19 @@
     /**
      * Determine whether a package is a "system package", in which case certain things (like
      * disabling notifications or disabling the package altogether) should be disallowed.
+     * <p>
+     * Note: This function is just for UI treatment, and should not be used for security purposes.
+     *
+     * @deprecated Use {@link ApplicationInfo#isSignedWithPlatformKey()} and
+     * {@link #isEssentialPackage} instead.
      */
+    @Deprecated
     public static boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo pkg) {
         if (sSystemSignature == null) {
             sSystemSignature = new Signature[]{getSystemSignature(pm)};
         }
-        if (sPermissionControllerPackageName == null) {
-            sPermissionControllerPackageName = pm.getPermissionControllerPackageName();
-        }
-        if (sServicesSystemSharedLibPackageName == null) {
-            sServicesSystemSharedLibPackageName = pm.getServicesSystemSharedLibraryPackageName();
-        }
-        if (sSharedSystemSharedLibPackageName == null) {
-            sSharedSystemSharedLibPackageName = pm.getSharedSystemSharedLibraryPackageName();
-        }
-        return (sSystemSignature[0] != null
-                && sSystemSignature[0].equals(getFirstSignature(pkg)))
-                || pkg.packageName.equals(sPermissionControllerPackageName)
-                || pkg.packageName.equals(sServicesSystemSharedLibPackageName)
-                || pkg.packageName.equals(sSharedSystemSharedLibPackageName)
-                || pkg.packageName.equals(PrintManager.PRINT_SPOOLER_PACKAGE_NAME)
-                || isDeviceProvisioningPackage(resources, pkg.packageName);
+        return (sSystemSignature[0] != null && sSystemSignature[0].equals(getFirstSignature(pkg)))
+                || isEssentialPackage(resources, pm, pkg.packageName);
     }
 
     private static Signature getFirstSignature(PackageInfo pkg) {
@@ -416,6 +408,29 @@
     }
 
     /**
+     * Determine whether a package is a "essential package".
+     * <p>
+     * In which case certain things (like disabling the package) should be disallowed.
+     */
+    public static boolean isEssentialPackage(
+            Resources resources, PackageManager pm, String packageName) {
+        if (sPermissionControllerPackageName == null) {
+            sPermissionControllerPackageName = pm.getPermissionControllerPackageName();
+        }
+        if (sServicesSystemSharedLibPackageName == null) {
+            sServicesSystemSharedLibPackageName = pm.getServicesSystemSharedLibraryPackageName();
+        }
+        if (sSharedSystemSharedLibPackageName == null) {
+            sSharedSystemSharedLibPackageName = pm.getSharedSystemSharedLibraryPackageName();
+        }
+        return packageName.equals(sPermissionControllerPackageName)
+                || packageName.equals(sServicesSystemSharedLibPackageName)
+                || packageName.equals(sSharedSystemSharedLibPackageName)
+                || packageName.equals(PrintManager.PRINT_SPOOLER_PACKAGE_NAME)
+                || isDeviceProvisioningPackage(resources, packageName);
+    }
+
+    /**
      * Returns {@code true} if the supplied package is the device provisioning app. Otherwise,
      * returns {@code false}.
      */
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 7913c16..ca5f57d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -36,8 +36,10 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -1577,8 +1579,8 @@
         public long internalSize;
         public long externalSize;
         public String labelDescription;
-
         public boolean mounted;
+        public boolean showInPersonalTab;
 
         /**
          * Setting this to {@code true} prevents the entry to be filtered by
@@ -1596,6 +1598,11 @@
          */
         public boolean isHomeApp;
 
+        /**
+         * Whether or not it's a cloned app .
+         */
+        public boolean isCloned;
+
         public String getNormalizedLabel() {
             if (normalizedLabel != null) {
                 return normalizedLabel;
@@ -1635,6 +1642,37 @@
                 ThreadUtils.postOnBackgroundThread(
                         () -> this.ensureLabelDescriptionLocked(context));
             }
+            UserManager um = UserManager.get(context);
+            this.showInPersonalTab = shouldShowInPersonalTab(um, info.uid);
+            UserInfo userInfo = um.getUserInfo(UserHandle.getUserId(info.uid));
+            if (userInfo != null) {
+                this.isCloned = userInfo.isCloneProfile();
+            }
+        }
+
+        /**
+         * Checks if the user that the app belongs to have the property
+         * {@link UserProperties#SHOW_IN_SETTINGS_WITH_PARENT} set.
+         */
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        boolean shouldShowInPersonalTab(UserManager userManager, int uid) {
+            int userId = UserHandle.getUserId(uid);
+
+            // Regardless of apk version, if the app belongs to the current user then return true.
+            if (userId == ActivityManager.getCurrentUser()) {
+                return true;
+            }
+
+            // For sdk version < 34, if the app doesn't belong to the current user,
+            // then as per earlier behaviour the app shouldn't be displayed in personal tab.
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                return false;
+            }
+
+            UserProperties userProperties = userManager.getUserProperties(
+                        UserHandle.of(userId));
+            return userProperties.getShowInSettings()
+                        == UserProperties.SHOW_IN_SETTINGS_WITH_PARENT;
         }
 
         public void ensureLabel(Context context) {
@@ -1784,7 +1822,7 @@
 
         @Override
         public boolean filterApp(AppEntry entry) {
-            return UserHandle.getUserId(entry.info.uid) == mCurrentUser;
+            return entry.showInPersonalTab;
         }
     };
 
@@ -1811,7 +1849,7 @@
 
         @Override
         public boolean filterApp(AppEntry entry) {
-            return UserHandle.getUserId(entry.info.uid) != mCurrentUser;
+            return !entry.showInPersonalTab;
         }
     };
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
index 6ce72bb..3af64e2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
@@ -82,4 +82,4 @@
             Log.e(TAG, "localBluetoothAdapter is NULL!!");
         }
     }
-};
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 5c796af..a36cbc0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -115,12 +115,24 @@
         }
 
         List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
+        int resId = 0;
         for (LocalBluetoothProfile profile : profiles) {
-            int resId = profile.getDrawableResource(btClass);
-            if (resId != 0) {
-                return new Pair<>(getBluetoothDrawable(context, resId), null);
+            int profileResId = profile.getDrawableResource(btClass);
+            if (profileResId != 0) {
+                // The device should show hearing aid icon if it contains any hearing aid related
+                // profiles
+                if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) {
+                    return new Pair<>(getBluetoothDrawable(context, profileResId), null);
+                }
+                if (resId == 0) {
+                    resId = profileResId;
+                }
             }
         }
+        if (resId != 0) {
+            return new Pair<>(getBluetoothDrawable(context, resId), null);
+        }
+
         if (btClass != null) {
             if (doesClassMatch(btClass, BluetoothClass.PROFILE_HEADSET)) {
                 return new Pair<>(
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 950ee21..2951001 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -77,9 +77,7 @@
     private final LocalBluetoothProfileManager mProfileManager;
     private final Object mProfileLock = new Object();
     BluetoothDevice mDevice;
-    private int mDeviceSide;
-    private int mDeviceMode;
-    private long mHiSyncId;
+    private HearingAidInfo mHearingAidInfo;
     private int mGroupId;
 
     // Need this since there is no method for getting RSSI
@@ -160,12 +158,16 @@
         mProfileManager = profileManager;
         mDevice = device;
         fillData();
-        mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
         mGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
         initDrawableCache();
         mUnpairing = false;
     }
 
+    /** Clears any pending messages in the message queue. */
+    public void release() {
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
     private void initDrawableCache() {
         int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
         int cacheSize = maxMemory / 8;
@@ -339,32 +341,34 @@
         connectDevice();
     }
 
-    public int getDeviceSide() {
-        return mDeviceSide;
+    public HearingAidInfo getHearingAidInfo() {
+        return mHearingAidInfo;
     }
 
-    public void setDeviceSide(int side) {
-        mDeviceSide = side;
+    public void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
+        mHearingAidInfo = hearingAidInfo;
+    }
+
+    /**
+     * @return {@code true} if {@code cachedBluetoothDevice} is hearing aid device
+     */
+    public boolean isHearingAidDevice() {
+        return mHearingAidInfo != null;
+    }
+
+    public int getDeviceSide() {
+        return mHearingAidInfo != null
+                ? mHearingAidInfo.getSide() : HearingAidInfo.DeviceSide.SIDE_INVALID;
     }
 
     public int getDeviceMode() {
-        return mDeviceMode;
-    }
-
-    public void setDeviceMode(int mode) {
-        mDeviceMode = mode;
+        return mHearingAidInfo != null
+                ? mHearingAidInfo.getMode() : HearingAidInfo.DeviceMode.MODE_INVALID;
     }
 
     public long getHiSyncId() {
-        return mHiSyncId;
-    }
-
-    public void setHiSyncId(long id) {
-        mHiSyncId = id;
-    }
-
-    public boolean isHearingAidDevice() {
-        return mHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID;
+        return mHearingAidInfo != null
+                ? mHearingAidInfo.getHiSyncId() : BluetoothHearingAid.HI_SYNC_ID_INVALID;
     }
 
     /**
@@ -668,6 +672,12 @@
      * @param bluetoothProfile the Bluetooth profile
      */
     public void onActiveDeviceChanged(boolean isActive, int bluetoothProfile) {
+        if (BluetoothUtils.D) {
+            Log.d(TAG, "onActiveDeviceChanged: "
+                    + "profile " + BluetoothProfile.getProfileName(bluetoothProfile)
+                    + ", device " + mDevice.getAnonymizedAddress()
+                    + ", isActive " + isActive);
+        }
         boolean changed = false;
         switch (bluetoothProfile) {
         case BluetoothProfile.A2DP:
@@ -778,8 +788,6 @@
         ParcelUuid[] localUuids = new ParcelUuid[uuidsList.size()];
         uuidsList.toArray(localUuids);
 
-        if (localUuids == null) return false;
-
         /*
          * Now we know if the device supports PBAP, update permissions...
          */
@@ -1161,15 +1169,24 @@
 
                 // Try to show left/right information if can not get it from battery for hearing
                 // aids specifically.
-                if (mIsActiveDeviceHearingAid
+                boolean isActiveAshaHearingAid = mIsActiveDeviceHearingAid;
+                boolean isActiveLeAudioHearingAid = mIsActiveDeviceLeAudio
+                        && isConnectedHapClientDevice();
+                if ((isActiveAshaHearingAid || isActiveLeAudioHearingAid)
                         && stringRes == R.string.bluetooth_active_no_battery_level) {
+                    final Set<CachedBluetoothDevice> memberDevices = getMemberDevice();
                     final CachedBluetoothDevice subDevice = getSubDevice();
-                    if (subDevice != null && subDevice.isConnected()) {
+                    if (memberDevices.stream().anyMatch(m -> m.isConnected())) {
+                        stringRes = R.string.bluetooth_hearing_aid_left_and_right_active;
+                    } else if (subDevice != null && subDevice.isConnected()) {
                         stringRes = R.string.bluetooth_hearing_aid_left_and_right_active;
                     } else {
-                        if (mDeviceSide == HearingAidProfile.DeviceSide.SIDE_LEFT) {
+                        int deviceSide = getDeviceSide();
+                        if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) {
+                            stringRes = R.string.bluetooth_hearing_aid_left_and_right_active;
+                        } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT) {
                             stringRes = R.string.bluetooth_hearing_aid_left_active;
-                        } else if (mDeviceSide == HearingAidProfile.DeviceSide.SIDE_RIGHT) {
+                        } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_RIGHT) {
                             stringRes = R.string.bluetooth_hearing_aid_right_active;
                         } else {
                             stringRes = R.string.bluetooth_active_no_battery_level;
@@ -1365,15 +1382,41 @@
     }
 
     /**
-     * @return {@code true} if {@code cachedBluetoothDevice} is Hearing Aid device
+     * @return {@code true} if {@code cachedBluetoothDevice} is ASHA hearing aid device
      */
-    public boolean isConnectedHearingAidDevice() {
+    public boolean isConnectedAshaHearingAidDevice() {
         HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
         return hearingAidProfile != null && hearingAidProfile.getConnectionStatus(mDevice) ==
                 BluetoothProfile.STATE_CONNECTED;
     }
 
     /**
+     * @return {@code true} if {@code cachedBluetoothDevice} is HAP device
+     */
+    public boolean isConnectedHapClientDevice() {
+        HapClientProfile hapClientProfile = mProfileManager.getHapClientProfile();
+        return hapClientProfile != null && hapClientProfile.getConnectionStatus(mDevice)
+                == BluetoothProfile.STATE_CONNECTED;
+    }
+
+    /**
+     * @return {@code true} if {@code cachedBluetoothDevice} is LeAudio hearing aid device
+     */
+    public boolean isConnectedLeAudioHearingAidDevice() {
+        return isConnectedHapClientDevice() && isConnectedLeAudioDevice();
+    }
+
+    /**
+     * @return {@code true} if {@code cachedBluetoothDevice} is hearing aid device
+     *
+     * The device may be an ASHA hearing aid that supports {@link HearingAidProfile} or a LeAudio
+     * hearing aid that supports {@link HapClientProfile} and {@link LeAudioProfile}.
+     */
+    public boolean isConnectedHearingAidDevice() {
+        return isConnectedAshaHearingAidDevice() || isConnectedLeAudioHearingAidDevice();
+    }
+
+    /**
      * @return {@code true} if {@code cachedBluetoothDevice} is LeAudio device
      */
     public boolean isConnectedLeAudioDevice() {
@@ -1401,17 +1444,19 @@
         BluetoothDevice tmpDevice = mDevice;
         final short tmpRssi = mRssi;
         final boolean tmpJustDiscovered = mJustDiscovered;
-        final int tmpDeviceSide = mDeviceSide;
+        final HearingAidInfo tmpHearingAidInfo = mHearingAidInfo;
         // Set main device from sub device
+        release();
         mDevice = mSubDevice.mDevice;
         mRssi = mSubDevice.mRssi;
         mJustDiscovered = mSubDevice.mJustDiscovered;
-        mDeviceSide = mSubDevice.mDeviceSide;
+        mHearingAidInfo = mSubDevice.mHearingAidInfo;
         // Set sub device from backup
+        mSubDevice.release();
         mSubDevice.mDevice = tmpDevice;
         mSubDevice.mRssi = tmpRssi;
         mSubDevice.mJustDiscovered = tmpJustDiscovered;
-        mSubDevice.mDeviceSide = tmpDeviceSide;
+        mSubDevice.mHearingAidInfo = tmpHearingAidInfo;
         fetchActiveDevices();
     }
 
@@ -1433,6 +1478,7 @@
      * Remove a device from the member device sets.
      */
     public void removeMemberDevice(CachedBluetoothDevice memberDevice) {
+        memberDevice.release();
         mMemberDevices.remove(memberDevice);
     }
 
@@ -1450,11 +1496,13 @@
         final short tmpRssi = mRssi;
         final boolean tmpJustDiscovered = mJustDiscovered;
         // Set main device from sub device
+        release();
         mDevice = newMainDevice.mDevice;
         mRssi = newMainDevice.mRssi;
         mJustDiscovered = newMainDevice.mJustDiscovered;
 
         // Set sub device from backup
+        newMainDevice.release();
         newMainDevice.mDevice = tmpDevice;
         newMainDevice.mRssi = tmpRssi;
         newMainDevice.mJustDiscovered = tmpJustDiscovered;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 6bc1160..221836b 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();
@@ -224,8 +223,14 @@
 
     public synchronized void clearNonBondedDevices() {
         clearNonBondedSubDevices();
-        mCachedDevices.removeIf(cachedDevice
-            -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE);
+        final List<CachedBluetoothDevice> removedCachedDevice = new ArrayList<>();
+        mCachedDevices.stream()
+                .filter(cachedDevice -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE)
+                .forEach(cachedDevice -> {
+                    cachedDevice.release();
+                    removedCachedDevice.add(cachedDevice);
+                });
+        mCachedDevices.removeAll(removedCachedDevice);
     }
 
     private void clearNonBondedSubDevices() {
@@ -246,6 +251,7 @@
             if (subDevice != null
                     && subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
                 // Sub device exists and it is not bonded
+                subDevice.release();
                 cachedDevice.setSubDevice(null);
             }
         }
@@ -295,6 +301,7 @@
                 }
                 if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
                     cachedDevice.setJustDiscovered(false);
+                    cachedDevice.release();
                     mCachedDevices.remove(i);
                 }
             }
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..6b7f733
--- /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 supportsSynchronizedPresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportsSynchronizedPresets(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 supportsIndependentPresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportsIndependentPresets(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 supportsDynamicPresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportsDynamicPresets(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 supportsWritablePresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportsWritablePresets(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/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index cf4e1ee..ebfec0a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -27,7 +27,7 @@
 import java.util.Set;
 
 /**
- * HearingAidDeviceManager manages the set of remote HearingAid Bluetooth devices.
+ * HearingAidDeviceManager manages the set of remote HearingAid(ASHA) Bluetooth devices.
  */
 public class HearingAidDeviceManager {
     private static final String TAG = "HearingAidDeviceManager";
@@ -44,12 +44,12 @@
     void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) {
         long hiSyncId = getHiSyncId(newDevice.getDevice());
         if (isValidHiSyncId(hiSyncId)) {
-            // Once hiSyncId is valid, assign hiSyncId
-            newDevice.setHiSyncId(hiSyncId);
-            final int side = getDeviceSide(newDevice.getDevice());
-            final int mode = getDeviceMode(newDevice.getDevice());
-            newDevice.setDeviceSide(side);
-            newDevice.setDeviceMode(mode);
+            // Once hiSyncId is valid, assign hearing aid info
+            final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+                    .setAshaDeviceSide(getDeviceSide(newDevice.getDevice()))
+                    .setAshaDeviceMode(getDeviceMode(newDevice.getDevice()))
+                    .setHiSyncId(hiSyncId);
+            newDevice.setHearingAidInfo(infoBuilder.build());
         }
     }
 
@@ -123,12 +123,14 @@
                 final long newHiSyncId = getHiSyncId(cachedDevice.getDevice());
                 // Do nothing if there is no HiSyncId on Bluetooth device
                 if (isValidHiSyncId(newHiSyncId)) {
-                    cachedDevice.setHiSyncId(newHiSyncId);
+                    // Once hiSyncId is valid, assign hearing aid info
+                    final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+                            .setAshaDeviceSide(getDeviceSide(cachedDevice.getDevice()))
+                            .setAshaDeviceMode(getDeviceMode(cachedDevice.getDevice()))
+                            .setHiSyncId(newHiSyncId);
+                    cachedDevice.setHearingAidInfo(infoBuilder.build());
+
                     newSyncIdSet.add(newHiSyncId);
-                    final int side = getDeviceSide(cachedDevice.getDevice());
-                    final int mode = getDeviceMode(cachedDevice.getDevice());
-                    cachedDevice.setDeviceSide(side);
-                    cachedDevice.setDeviceMode(mode);
                 }
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java
new file mode 100644
index 0000000..ef08c92
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java
@@ -0,0 +1,263 @@
+/*
+ * 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 android.annotation.IntDef;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
+import android.util.SparseIntArray;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/** Hearing aids information and constants that shared within hearing aids related profiles */
+public class HearingAidInfo {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            DeviceSide.SIDE_INVALID,
+            DeviceSide.SIDE_LEFT,
+            DeviceSide.SIDE_RIGHT,
+            DeviceSide.SIDE_LEFT_AND_RIGHT,
+    })
+
+    /** Side definition for hearing aids. */
+    public @interface DeviceSide {
+        int SIDE_INVALID = -1;
+        int SIDE_LEFT = 0;
+        int SIDE_RIGHT = 1;
+        int SIDE_LEFT_AND_RIGHT = 2;
+    }
+
+    @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @IntDef({
+            DeviceMode.MODE_INVALID,
+            DeviceMode.MODE_MONAURAL,
+            DeviceMode.MODE_BINAURAL,
+            DeviceMode.MODE_BANDED,
+    })
+
+    /** Mode definition for hearing aids. */
+    public @interface DeviceMode {
+        int MODE_INVALID = -1;
+        int MODE_MONAURAL = 0;
+        int MODE_BINAURAL = 1;
+        int MODE_BANDED = 2;
+    }
+
+    private final int mSide;
+    private final int mMode;
+    private final long mHiSyncId;
+
+    private HearingAidInfo(int side, int mode, long hiSyncId) {
+        mSide = side;
+        mMode = mode;
+        mHiSyncId = hiSyncId;
+    }
+
+    @DeviceSide
+    public int getSide() {
+        return mSide;
+    }
+
+    @DeviceMode
+    public int getMode() {
+        return mMode;
+    }
+
+    public long getHiSyncId() {
+        return mHiSyncId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof HearingAidInfo)) {
+            return false;
+        }
+        HearingAidInfo that = (HearingAidInfo) o;
+        return mSide == that.mSide && mMode == that.mMode && mHiSyncId == that.mHiSyncId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSide, mMode, mHiSyncId);
+    }
+
+    @Override
+    public String toString() {
+        return "HearingAidInfo{"
+                + "mSide=" + mSide
+                + ", mMode=" + mMode
+                + ", mHiSyncId=" + mHiSyncId
+                + '}';
+    }
+
+    @DeviceSide
+    private static int convertAshaDeviceSideToInternalSide(int ashaDeviceSide) {
+        return ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING.get(
+                ashaDeviceSide, DeviceSide.SIDE_INVALID);
+    }
+
+    @DeviceMode
+    private static int convertAshaDeviceModeToInternalMode(int ashaDeviceMode) {
+        return ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING.get(
+                ashaDeviceMode, DeviceMode.MODE_INVALID);
+    }
+
+    @DeviceSide
+    private static int convertLeAudioLocationToInternalSide(int leAudioLocation) {
+        boolean isLeft = (leAudioLocation & LE_AUDIO_LOCATION_LEFT) != 0;
+        boolean isRight = (leAudioLocation & LE_AUDIO_LOCATION_RIGHT) != 0;
+        if (isLeft && isRight) {
+            return DeviceSide.SIDE_LEFT_AND_RIGHT;
+        } else if (isLeft) {
+            return DeviceSide.SIDE_LEFT;
+        } else if (isRight) {
+            return DeviceSide.SIDE_RIGHT;
+        }
+        return DeviceSide.SIDE_INVALID;
+    }
+
+    @DeviceMode
+    private static int convertHapDeviceTypeToInternalMode(int hapDeviceType) {
+        return HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.get(hapDeviceType, DeviceMode.MODE_INVALID);
+    }
+
+    /** Builder class for constructing {@link HearingAidInfo} objects. */
+    public static final class Builder {
+        private int mSide = DeviceSide.SIDE_INVALID;
+        private int mMode = DeviceMode.MODE_INVALID;
+        private long mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
+
+        /**
+         * Configure the hearing device mode.
+         * @param ashaDeviceMode one of the hearing aid device modes defined in HearingAidProfile
+         * {@link HearingAidProfile.DeviceMode}
+         */
+        public Builder setAshaDeviceMode(int ashaDeviceMode) {
+            mMode = convertAshaDeviceModeToInternalMode(ashaDeviceMode);
+            return this;
+        }
+
+        /**
+         * Configure the hearing device mode.
+         * @param hapDeviceType one of the hearing aid device types defined in HapClientProfile
+         * {@link HapClientProfile.HearingAidType}
+         */
+        public Builder setHapDeviceType(int hapDeviceType) {
+            mMode = convertHapDeviceTypeToInternalMode(hapDeviceType);
+            return this;
+        }
+
+        /**
+         * Configure the hearing device side.
+         * @param ashaDeviceSide one of the hearing aid device sides defined in HearingAidProfile
+         * {@link HearingAidProfile.DeviceSide}
+         */
+        public Builder setAshaDeviceSide(int ashaDeviceSide) {
+            mSide = convertAshaDeviceSideToInternalSide(ashaDeviceSide);
+            return this;
+        }
+
+        /**
+         * Configure the hearing device side.
+         * @param leAudioLocation one of the audio location defined in BluetoothLeAudio
+         * {@link BluetoothLeAudio.AudioLocation}
+         */
+        public Builder setLeAudioLocation(int leAudioLocation) {
+            mSide = convertLeAudioLocationToInternalSide(leAudioLocation);
+            return this;
+        }
+
+        /**
+         * Configure the hearing aid hiSyncId.
+         * @param hiSyncId the ASHA hearing aid id
+         */
+        public Builder setHiSyncId(long hiSyncId) {
+            mHiSyncId = hiSyncId;
+            return this;
+        }
+
+        /** Build the configured {@link HearingAidInfo} */
+        public HearingAidInfo build() {
+            return new HearingAidInfo(mSide, mMode, mHiSyncId);
+        }
+    }
+
+    private static final int LE_AUDIO_LOCATION_LEFT =
+            BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT
+                    | BluetoothLeAudio.AUDIO_LOCATION_BACK_LEFT
+                    | BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT_OF_CENTER
+                    | BluetoothLeAudio.AUDIO_LOCATION_SIDE_LEFT
+                    | BluetoothLeAudio.AUDIO_LOCATION_TOP_FRONT_LEFT
+                    | BluetoothLeAudio.AUDIO_LOCATION_TOP_BACK_LEFT
+                    | BluetoothLeAudio.AUDIO_LOCATION_TOP_SIDE_LEFT
+                    | BluetoothLeAudio.AUDIO_LOCATION_BOTTOM_FRONT_LEFT
+                    | BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT_WIDE
+                    | BluetoothLeAudio.AUDIO_LOCATION_LEFT_SURROUND;
+
+    private static final int LE_AUDIO_LOCATION_RIGHT =
+            BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT
+                    | BluetoothLeAudio.AUDIO_LOCATION_BACK_RIGHT
+                    | BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT_OF_CENTER
+                    | BluetoothLeAudio.AUDIO_LOCATION_SIDE_RIGHT
+                    | BluetoothLeAudio.AUDIO_LOCATION_TOP_FRONT_RIGHT
+                    | BluetoothLeAudio.AUDIO_LOCATION_TOP_BACK_RIGHT
+                    | BluetoothLeAudio.AUDIO_LOCATION_TOP_SIDE_RIGHT
+                    | BluetoothLeAudio.AUDIO_LOCATION_BOTTOM_FRONT_RIGHT
+                    | BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT_WIDE
+                    | BluetoothLeAudio.AUDIO_LOCATION_RIGHT_SURROUND;
+
+    private static final SparseIntArray ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING;
+    private static final SparseIntArray ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING;
+    private static final SparseIntArray HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING;
+
+    static {
+        ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING = new SparseIntArray();
+        ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING.put(
+                HearingAidProfile.DeviceSide.SIDE_INVALID, DeviceSide.SIDE_INVALID);
+        ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING.put(
+                HearingAidProfile.DeviceSide.SIDE_LEFT, DeviceSide.SIDE_LEFT);
+        ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING.put(
+                HearingAidProfile.DeviceSide.SIDE_RIGHT, DeviceSide.SIDE_RIGHT);
+
+        ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING = new SparseIntArray();
+        ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING.put(
+                HearingAidProfile.DeviceMode.MODE_INVALID, DeviceMode.MODE_INVALID);
+        ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING.put(
+                HearingAidProfile.DeviceMode.MODE_MONAURAL, DeviceMode.MODE_MONAURAL);
+        ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING.put(
+                HearingAidProfile.DeviceMode.MODE_BINAURAL, DeviceMode.MODE_BINAURAL);
+
+        HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING = new SparseIntArray();
+        HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+                HapClientProfile.HearingAidType.TYPE_INVALID, DeviceMode.MODE_INVALID);
+        HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+                HapClientProfile.HearingAidType.TYPE_BINAURAL, DeviceMode.MODE_BINAURAL);
+        HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+                HapClientProfile.HearingAidType.TYPE_MONAURAL, DeviceMode.MODE_MONAURAL);
+        HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+                HapClientProfile.HearingAidType.TYPE_BANDED, DeviceMode.MODE_BANDED);
+        HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+                HapClientProfile.HearingAidType.TYPE_RFU, DeviceMode.MODE_INVALID);
+
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index fb861da..a3c2e70 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -21,6 +21,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothHearingAid;
@@ -103,6 +104,7 @@
     private PbapClientProfile mPbapClientProfile;
     private PbapServerProfile mPbapProfile;
     private HearingAidProfile mHearingAidProfile;
+    private HapClientProfile mHapClientProfile;
     private CsipSetCoordinatorProfile mCsipSetCoordinatorProfile;
     private LeAudioProfile mLeAudioProfile;
     private LocalBluetoothLeBroadcast mLeAudioBroadcast;
@@ -189,6 +191,12 @@
             addProfile(mHearingAidProfile, HearingAidProfile.NAME,
                     BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
         }
+        if (mHapClientProfile == null && supportedList.contains(BluetoothProfile.HAP_CLIENT)) {
+            if (DEBUG) Log.d(TAG, "Adding local HAP_CLIENT profile");
+            mHapClientProfile = new HapClientProfile(mContext, mDeviceManager, this);
+            addProfile(mHapClientProfile, HapClientProfile.NAME,
+                    BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED);
+        }
         if (mHidProfile == null && supportedList.contains(BluetoothProfile.HID_HOST)) {
             if (DEBUG) Log.d(TAG, "Adding local HID_HOST profile");
             mHidProfile = new HidProfile(mContext, mDeviceManager, this);
@@ -337,25 +345,44 @@
                 Log.i(TAG, "Failed to connect " + mProfile + " device");
             }
 
-            if (getHearingAidProfile() != null &&
-                mProfile instanceof HearingAidProfile &&
-                (newState == BluetoothProfile.STATE_CONNECTED)) {
-                final int side = getHearingAidProfile().getDeviceSide(cachedDevice.getDevice());
-                final int mode = getHearingAidProfile().getDeviceMode(cachedDevice.getDevice());
-                cachedDevice.setDeviceSide(side);
-                cachedDevice.setDeviceMode(mode);
+            if (getHearingAidProfile() != null
+                    && mProfile instanceof HearingAidProfile
+                    && (newState == BluetoothProfile.STATE_CONNECTED)) {
 
                 // Check if the HiSyncID has being initialized
                 if (cachedDevice.getHiSyncId() == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
                     long newHiSyncId = getHearingAidProfile().getHiSyncId(cachedDevice.getDevice());
                     if (newHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
-                        cachedDevice.setHiSyncId(newHiSyncId);
+                        final BluetoothDevice device = cachedDevice.getDevice();
+                        final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+                                .setAshaDeviceSide(getHearingAidProfile().getDeviceSide(device))
+                                .setAshaDeviceMode(getHearingAidProfile().getDeviceMode(device))
+                                .setHiSyncId(newHiSyncId);
+                        cachedDevice.setHearingAidInfo(infoBuilder.build());
                     }
                 }
-
                 HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice);
             }
 
+            final boolean isHapClientProfile = getHapClientProfile() != null
+                    && mProfile instanceof HapClientProfile;
+            final boolean isLeAudioProfile = getLeAudioProfile() != null
+                    && mProfile instanceof LeAudioProfile;
+            final boolean isHapClientOrLeAudioProfile = isHapClientProfile || isLeAudioProfile;
+            if (isHapClientOrLeAudioProfile && newState == BluetoothProfile.STATE_CONNECTED) {
+
+                // Checks if both profiles are connected to the device. Hearing aid info need
+                // to be retrieved from these profiles separately.
+                if (cachedDevice.isConnectedLeAudioHearingAidDevice()) {
+                    final BluetoothDevice device = cachedDevice.getDevice();
+                    final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+                            .setLeAudioLocation(getLeAudioProfile().getAudioLocation(device))
+                            .setHapDeviceType(getHapClientProfile().getHearingAidType(device));
+                    cachedDevice.setHearingAidInfo(infoBuilder.build());
+                    HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice);
+                }
+            }
+
             if (getCsipSetCoordinatorProfile() != null
                     && mProfile instanceof CsipSetCoordinatorProfile
                     && newState == BluetoothProfile.STATE_CONNECTED) {
@@ -524,6 +551,10 @@
         return mHearingAidProfile;
     }
 
+    public HapClientProfile getHapClientProfile() {
+        return mHapClientProfile;
+    }
+
     public LeAudioProfile getLeAudioProfile() {
         return mLeAudioProfile;
     }
@@ -675,6 +706,11 @@
             removedProfiles.remove(mHearingAidProfile);
         }
 
+        if (mHapClientProfile != null && ArrayUtils.contains(uuids, BluetoothUuid.HAS)) {
+            profiles.add(mHapClientProfile);
+            removedProfiles.remove(mHapClientProfile);
+        }
+
         if (mSapProfile != null && ArrayUtils.contains(uuids, BluetoothUuid.SAP)) {
             profiles.add(mSapProfile);
             removedProfiles.remove(mSapProfile);
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
index 3e33da5..ece8986 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
@@ -50,6 +50,34 @@
 
     @Override
     public void clicked(int sourceCategory, String key) {
+        final LogMaker logMaker = new LogMaker(MetricsProto.MetricsEvent.ACTION_SETTINGS_TILE_CLICK)
+                .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+        if (sourceCategory != MetricsProto.MetricsEvent.VIEW_UNKNOWN) {
+            logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, sourceCategory);
+        }
+        if (!TextUtils.isEmpty(key)) {
+            logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME,
+                    key);
+        }
+        MetricsLogger.action(logMaker);
+    }
+
+    @Override
+    public void changed(int category, String key, int value) {
+        final LogMaker logMaker = new LogMaker(
+                MetricsProto.MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE)
+                .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+        if (category != MetricsProto.MetricsEvent.VIEW_UNKNOWN) {
+            logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, category);
+        }
+        if (!TextUtils.isEmpty(key)) {
+            logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME,
+                    key);
+            logMaker.addTaggedData(
+                    MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
+                    value);
+        }
+        MetricsLogger.action(logMaker);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
index cceca13..dcd6cce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
@@ -39,6 +39,11 @@
     void clicked(int category, String key);
 
     /**
+     * Logs a value changed event when user changed item value.
+     */
+    void changed(int category, String key, int value);
+
+    /**
      * Logs an user action.
      */
     void action(Context context, int category, Pair<Integer, Object>... taggedData);
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 915421a..09abc39 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -108,6 +108,19 @@
     }
 
     /**
+     * Logs a value changed event when user changed item value.
+     *
+     * @param category the target page id
+     * @param key the key id that user clicked
+     * @param value the value that user changed which converted to integer
+     */
+    public void changed(int category, String key, int value) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.changed(category, key, value);
+        }
+    }
+
+    /**
      * Logs a simple action without page id or attribution
      *
      * @param category the target page
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
index 869de0de..067afa4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
@@ -35,15 +35,22 @@
     private static final String LOG_TAG = "SharedPreferencesLogger";
 
     private final String mTag;
+    private final int mMetricCategory;
     private final Context mContext;
     private final MetricsFeatureProvider mMetricsFeature;
     private final Set<String> mPreferenceKeySet;
 
     public SharedPreferencesLogger(Context context, String tag,
             MetricsFeatureProvider metricsFeature) {
+        this(context, tag, metricsFeature, SettingsEnums.PAGE_UNKNOWN);
+    }
+
+    public SharedPreferencesLogger(Context context, String tag,
+            MetricsFeatureProvider metricsFeature, int metricCategory) {
         mContext = context;
         mTag = tag;
         mMetricsFeature = metricsFeature;
+        mMetricCategory = metricCategory;
         mPreferenceKeySet = new ConcurrentSkipListSet<>();
     }
 
@@ -151,20 +158,15 @@
             return;
         }
         // Pref key exists in set, log its change in metrics.
-        mMetricsFeature.action(SettingsEnums.PAGE_UNKNOWN,
-                SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
-                SettingsEnums.PAGE_UNKNOWN,
-                prefKey,
-                intVal);
+        mMetricsFeature.changed(mMetricCategory, key, intVal);
     }
 
     @VisibleForTesting
     void logPackageName(String key, String value) {
-        final String prefKey = mTag + "/" + key;
-        mMetricsFeature.action(SettingsEnums.PAGE_UNKNOWN,
+        mMetricsFeature.action(mMetricCategory,
                 SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
                 SettingsEnums.PAGE_UNKNOWN,
-                prefKey + ":" + value,
+                key + ":" + value,
                 0);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index 34da305..3e710e4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -24,7 +24,6 @@
 import android.os.UserHandle;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
-import android.text.TextUtils;
 import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.Slog;
@@ -221,17 +220,14 @@
     }
 
     /**
-     * Reverts battery saver schedule mode to none if we are in a bad state where routine mode
-     * is selected but no app is configured to actually provide the signal.
+     * Reverts battery saver schedule mode to none if routine mode is selected.
      * @param context a valid context
      */
     public static void revertScheduleToNoneIfNeeded(Context context) {
         ContentResolver resolver = context.getContentResolver();
         final int currentMode = Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
                 PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
-        boolean providerConfigured = !TextUtils.isEmpty(context.getString(
-                com.android.internal.R.string.config_batterySaverScheduleProvider));
-        if (currentMode == PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC && !providerConfigured) {
+        if (currentMode == PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC) {
             Global.putInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
             Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
                     PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 132a631..8b68a09 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -66,7 +66,7 @@
     public BatteryStatus(Intent batteryChangedIntent) {
         status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
         plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0);
-        level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0);
+        level = getBatteryLevel(batteryChangedIntent);
         health = batteryChangedIntent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
         present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true);
 
@@ -188,7 +188,7 @@
      */
     public static boolean isCharged(Intent batteryChangedIntent) {
         int status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
-        int level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0);
+        int level = getBatteryLevel(batteryChangedIntent);
         return isCharged(status, level);
     }
 
@@ -204,4 +204,13 @@
     public static boolean isCharged(int status, int level) {
         return status == BATTERY_STATUS_FULL || level >= 100;
     }
+
+    /** Gets the battery level from the intent. */
+    public static int getBatteryLevel(Intent batteryChangedIntent) {
+        final int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+        final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
+        return scale == 0
+                ? -1 /*invalid battery level*/
+                : Math.round((level / (float) scale) * 100f);
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
index 5fa04f9..faea5b2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
@@ -412,14 +412,13 @@
     }
 
     companion object {
-        private const val TAG = "ThemedBatteryDrawable"
-        private const val WIDTH = 12f
-        private const val HEIGHT = 20f
+        const val WIDTH = 12f
+        const val HEIGHT = 20f
         private const val CRITICAL_LEVEL = 15
         // On a 12x20 grid, how wide to make the fill protection stroke.
         // Scales when our size changes
         private const val PROTECTION_STROKE_WIDTH = 3f
         // Arbitrarily chosen for visibility at small sizes
-        private const val PROTECTION_MIN_STROKE_WIDTH = 6f
+        const val PROTECTION_MIN_STROKE_WIDTH = 6f
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index c829bc3..3ba51d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -293,7 +293,7 @@
             return false;
         }
         setConnectedRecord();
-        mRouterManager.selectRoute(mPackageName, mRouteInfo);
+        mRouterManager.transfer(mPackageName, mRouteInfo);
         return true;
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
index df6929e..b3a52b9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
@@ -16,6 +16,7 @@
 package com.android.settingslib.media;
 
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
 import android.media.MediaRoute2Info;
 
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -32,7 +33,9 @@
      */
     public static String getId(CachedBluetoothDevice cachedDevice) {
         if (cachedDevice.isHearingAidDevice()) {
-            return Long.toString(cachedDevice.getHiSyncId());
+            if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+                return Long.toString(cachedDevice.getHiSyncId());
+            }
         }
         return cachedDevice.getAddress();
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt
new file mode 100644
index 0000000..a0395b5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.mobile
+
+import android.annotation.DrawableRes
+import android.content.res.Resources
+import android.content.res.TypedArray
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.settingslib.R
+import com.android.settingslib.SignalIcon.MobileIconGroup
+
+/**
+ * This class defines a network type (3G, 4G, etc.) override mechanism on a per-carrierId basis.
+ *
+ * Traditionally, carrier-customized network type iconography was achieved using the `MCC/MNC`
+ * resource qualifiers, and swapping out the drawable resource by name. It would look like this:
+ *
+ *     res/
+ *       drawable/
+ *         3g_mobiledata_icon.xml
+ *       drawable-MCC-MNC/
+ *         3g_mobiledata_icon.xml
+ *
+ * This would mean that, provided a context created with this MCC/MNC configuration set, loading
+ * the network type icon through [MobileIconGroup] would provide a carrier-defined network type
+ * icon rather than the AOSP-defined default.
+ *
+ * The MCC/MNC mechanism no longer can fully define carrier-specific network type icons, because
+ * there is no longer a 1:1 mapping between MCC/MNC and carrier. With the advent of MVNOs, multiple
+ * carriers can have the same MCC/MNC value, but wish to differentiate based on their carrier ID.
+ * CarrierId is a newer concept than MCC/MNC, and provides more granularity when it comes to
+ * determining the carrier (e.g. MVNOs can share MCC/MNC values with the network owner), therefore
+ * it can fit all of the same use cases currently handled by `MCC/MNC`, without the need to apply a
+ * configuration context in order to get the proper UI for a given SIM icon.
+ *
+ * NOTE: CarrierId icon overrides will always take precedence over those defined using `MCC/MNC`
+ * resource qualifiers.
+ *
+ * [MAPPING] encodes the relationship between CarrierId and the corresponding override array
+ * that exists in the config.xml. An alternative approach could be to generate the resource name
+ * by string concatenation at run-time:
+ *
+ *    val resName = "carrierId_$carrierId_iconOverrides"
+ *    val override = resources.getResourceIdentifier(resName)
+ *
+ * However, that's going to be far less efficient until MAPPING grows to a sufficient size. For now,
+ * given a relatively small number of entries, we should just maintain the mapping here.
+ */
+interface MobileIconCarrierIdOverrides {
+    @DrawableRes
+    fun getOverrideFor(carrierId: Int, networkType: String, resources: Resources): Int
+    fun carrierIdEntryExists(carrierId: Int): Boolean
+}
+
+class MobileIconCarrierIdOverridesImpl : MobileIconCarrierIdOverrides {
+    @DrawableRes
+    override fun getOverrideFor(carrierId: Int, networkType: String, resources: Resources): Int {
+        val resId = MAPPING[carrierId] ?: return 0
+        val ta = resources.obtainTypedArray(resId)
+        val map = parseNetworkIconOverrideTypedArray(ta)
+        ta.recycle()
+        return map[networkType] ?: 0
+    }
+
+    override fun carrierIdEntryExists(carrierId: Int) =
+        overrideExists(carrierId, MAPPING)
+
+    companion object {
+        private const val TAG = "MobileIconOverrides"
+        /**
+         * This map maintains the lookup from the canonical carrier ID (see below link) to the
+         * corresponding overlay resource. New overrides should add an entry below in order to
+         * change the network type icon resources based on carrier ID
+         *
+         * Refer to the link below for the canonical mapping maintained in AOSP:
+         * https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb
+         */
+        private val MAPPING = mapOf(
+            // 2032 == Xfinity Mobile
+            2032 to R.array.carrierId_2032_iconOverrides,
+        )
+
+        /**
+         * Parse `carrierId_XXXX_iconOverrides` for a particular network type. The resource file
+         * "carrierid_icon_overrides.xml" defines a TypedArray format for overriding specific
+         * network type icons (a.k.a. RAT icons) for a particular carrier ID. The format is defined
+         * as an array of (network type name, drawable) pairs:
+         *    <array name="carrierId_XXXX_iconOverrides>
+         *        <item>NET_TYPE_1</item>
+         *        <item>@drawable/net_type_1_override</item>
+         *        <item>NET_TYPE_2</item>
+         *        <item>@drawable/net_type_2_override</item>
+         *    </array>
+         *
+         * @param ta the [TypedArray] defined in carrierid_icon_overrides.xml
+         * @return the overridden drawable resource ID if it exists, or 0 if it does not
+         */
+        @VisibleForTesting
+        @JvmStatic
+        fun parseNetworkIconOverrideTypedArray(ta: TypedArray): Map<String, Int> {
+            if (ta.length() % 2 != 0) {
+                Log.w(TAG,
+                    "override must contain an even number of (key, value) entries. skipping")
+
+                return mapOf()
+            }
+
+            val result = mutableMapOf<String, Int>()
+            // The array is defined as Pair(String, resourceId), so walk by 2
+            for (i in 0 until ta.length() step 2) {
+                val key = ta.getString(i)
+                val override = ta.getResourceId(i + 1, 0)
+                if (key == null || override == 0) {
+                    Log.w(TAG, "Invalid override found. Skipping")
+                    continue
+                }
+                result[key] = override
+            }
+
+            return result
+        }
+
+        @JvmStatic
+        private fun overrideExists(carrierId: Int, mapping: Map<Int, Int>): Boolean =
+            mapping.containsKey(carrierId)
+    }
+}
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 30d3820..250187f2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
@@ -23,8 +23,19 @@
 import android.telephony.UiccSlotInfo;
 import android.telephony.UiccSlotMapping;
 
+import java.util.List;
+
 public class DataServiceUtils {
 
+    public static <T> boolean shouldUpdateEntityList(List<T> oldList, List<T> newList) {
+        if ((oldList != null &&
+                (newList.isEmpty() || !newList.equals(oldList)))
+                || (!newList.isEmpty() && oldList == null)) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Represents columns of the MobileNetworkInfoData table, define these columns from
      * {@see MobileNetworkUtils} or relevant common APIs.
@@ -103,6 +114,11 @@
          */
         public static final String COLUMN_SHOW_TOGGLE_FOR_PHYSICAL_SIM = "showToggleForPhysicalSim";
 
+        /**
+         * The name of the subscription's data roaming state column,
+         * {@see TelephonyManager#isDataRoamingEnabled()}.
+         */
+        public static final String COLUMN_IS_DATA_ROAMING_ENABLED = "isDataRoamingEnabled";
     }
 
     /**
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 ca457b0..c92204f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
@@ -120,7 +120,7 @@
      * Query the mobileNetwork info by the subscription ID from the MobileNetworkInfoEntity
      * table.
      */
-    public LiveData<MobileNetworkInfoEntity> queryMobileNetworkInfoById(String id) {
+    public MobileNetworkInfoEntity queryMobileNetworkInfoById(String id) {
         return mMobileNetworkInfoDao().queryMobileNetworkInfoBySubId(id);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoDao.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoDao.java
index 299a445..6709772 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoDao.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoDao.java
@@ -36,7 +36,7 @@
 
     @Query("SELECT * FROM " + DataServiceUtils.MobileNetworkInfoData.TABLE_NAME + " WHERE "
             + DataServiceUtils.MobileNetworkInfoData.COLUMN_ID + " = :subId")
-    LiveData<MobileNetworkInfoEntity> queryMobileNetworkInfoBySubId(String subId);
+    MobileNetworkInfoEntity queryMobileNetworkInfoBySubId(String subId);
 
     @Query("SELECT * FROM " + DataServiceUtils.MobileNetworkInfoData.TABLE_NAME + " WHERE "
             + DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_MOBILE_DATA_ENABLED
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java
index a12e0c8..e72346d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.mobile.dataservice;
 
+import android.text.TextUtils;
+
 import androidx.annotation.NonNull;
 import androidx.room.ColumnInfo;
 import androidx.room.Entity;
@@ -28,7 +30,7 @@
             boolean isContactDiscoveryVisible, boolean isMobileDataEnabled, boolean isCdmaOptions,
             boolean isGsmOptions, boolean isWorldMode, boolean shouldDisplayNetworkSelectOptions,
             boolean isTdscdmaSupported, boolean activeNetworkIsCellular,
-            boolean showToggleForPhysicalSim) {
+            boolean showToggleForPhysicalSim, boolean isDataRoamingEnabled) {
         this.subId = subId;
         this.isContactDiscoveryEnabled = isContactDiscoveryEnabled;
         this.isContactDiscoveryVisible = isContactDiscoveryVisible;
@@ -40,6 +42,7 @@
         this.isTdscdmaSupported = isTdscdmaSupported;
         this.activeNetworkIsCellular = activeNetworkIsCellular;
         this.showToggleForPhysicalSim = showToggleForPhysicalSim;
+        this.isDataRoamingEnabled = isDataRoamingEnabled;
     }
 
     @PrimaryKey
@@ -78,6 +81,51 @@
     @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_SHOW_TOGGLE_FOR_PHYSICAL_SIM)
     public boolean showToggleForPhysicalSim;
 
+    @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_DATA_ROAMING_ENABLED)
+    public boolean isDataRoamingEnabled;
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + subId.hashCode();
+        result = 31 * result + Boolean.hashCode(isContactDiscoveryEnabled);
+        result = 31 * result + Boolean.hashCode(isContactDiscoveryVisible);
+        result = 31 * result + Boolean.hashCode(isMobileDataEnabled);
+        result = 31 * result + Boolean.hashCode(isCdmaOptions);
+        result = 31 * result + Boolean.hashCode(isGsmOptions);
+        result = 31 * result + Boolean.hashCode(isWorldMode);
+        result = 31 * result + Boolean.hashCode(shouldDisplayNetworkSelectOptions);
+        result = 31 * result + Boolean.hashCode(isTdscdmaSupported);
+        result = 31 * result + Boolean.hashCode(activeNetworkIsCellular);
+        result = 31 * result + Boolean.hashCode(showToggleForPhysicalSim);
+        result = 31 * result + Boolean.hashCode(isDataRoamingEnabled);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof MobileNetworkInfoEntity)) {
+            return false;
+        }
+
+        MobileNetworkInfoEntity info = (MobileNetworkInfoEntity) obj;
+        return  TextUtils.equals(subId, info.subId)
+                && isContactDiscoveryEnabled == info.isContactDiscoveryEnabled
+                && isContactDiscoveryVisible == info.isContactDiscoveryVisible
+                && isMobileDataEnabled == info.isMobileDataEnabled
+                && isCdmaOptions == info.isCdmaOptions
+                && isGsmOptions == info.isGsmOptions
+                && isWorldMode == info.isWorldMode
+                && shouldDisplayNetworkSelectOptions == info.shouldDisplayNetworkSelectOptions
+                && isTdscdmaSupported == info.isTdscdmaSupported
+                && activeNetworkIsCellular == info.activeNetworkIsCellular
+                && showToggleForPhysicalSim == info.showToggleForPhysicalSim
+                && isDataRoamingEnabled == info.isDataRoamingEnabled;
+    }
+
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append(" {MobileNetworkInfoEntity(subId = ")
@@ -102,6 +150,8 @@
                 .append(activeNetworkIsCellular)
                 .append(", showToggleForPhysicalSim = ")
                 .append(showToggleForPhysicalSim)
+                .append(", isDataRoamingEnabled = ")
+                .append(isDataRoamingEnabled)
                 .append(")}");
         return builder.toString();
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java
index 532462b..2ccf295 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.mobile.dataservice;
 
+import android.text.TextUtils;
+
 import androidx.annotation.NonNull;
 import androidx.room.ColumnInfo;
 import androidx.room.Entity;
@@ -73,6 +75,45 @@
     @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_PORT_INDEX)
     public int portIndex;
 
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + subId.hashCode();
+        result = 31 * result + physicalSlotIndex.hashCode();
+        result = 31 * result + logicalSlotIndex;
+        result = 31 * result + cardId;
+        result = 31 * result + Boolean.hashCode(isEuicc);
+        result = 31 * result + Boolean.hashCode(isMultipleEnabledProfilesSupported);
+        result = 31 * result + cardState;
+        result = 31 * result + Boolean.hashCode(isRemovable);
+        result = 31 * result + Boolean.hashCode(isActive);
+        result = 31 * result + portIndex;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof UiccInfoEntity)) {
+            return false;
+        }
+
+        UiccInfoEntity info = (UiccInfoEntity) obj;
+        return  TextUtils.equals(subId, info.subId)
+                && TextUtils.equals(physicalSlotIndex, info.physicalSlotIndex)
+                && logicalSlotIndex == info.logicalSlotIndex
+                && cardId == info.cardId
+                && isEuicc == info.isEuicc
+                && isMultipleEnabledProfilesSupported == info.isMultipleEnabledProfilesSupported
+                && cardState == info.cardState
+                && isRemovable == info.isRemovable
+                && isActive == info.isActive
+                && portIndex == info.portIndex;
+    }
+
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append(" {UiccInfoEntity(subId = ")
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/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index f1e1e7d..c5598bf 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -293,4 +293,15 @@
 
         assertThat(ApplicationsState.FILTER_MOVIES.filterApp(mEntry)).isFalse();
     }
+
+    @Test
+    public void testPersonalAndWorkFiltersDisplaysCorrectApps() {
+        mEntry.showInPersonalTab = true;
+        assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isTrue();
+        assertThat(ApplicationsState.FILTER_WORK.filterApp(mEntry)).isFalse();
+
+        mEntry.showInPersonalTab = false;
+        assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
+        assertThat(ApplicationsState.FILTER_WORK.filterApp(mEntry)).isTrue();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 336cdd3..291f6a3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -317,7 +317,9 @@
 
     @Test
     public void getBatteryStatus_statusIsFull_returnFullString() {
-        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100);
+        final Intent intent = new Intent()
+                .putExtra(BatteryManager.EXTRA_LEVEL, 100)
+                .putExtra(BatteryManager.EXTRA_SCALE, 100);
         final Resources resources = mContext.getResources();
 
         assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
@@ -326,7 +328,9 @@
 
     @Test
     public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() {
-        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100);
+        final Intent intent = new Intent()
+                .putExtra(BatteryManager.EXTRA_LEVEL, 100)
+                .putExtra(BatteryManager.EXTRA_SCALE, 100);
         final Resources resources = mContext.getResources();
 
         assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index fc2bf0a..96e64ea 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -17,6 +17,7 @@
 package com.android.settingslib.applications;
 
 import static android.os.UserHandle.MU_ENABLED;
+import static android.os.UserHandle.USER_SYSTEM;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -48,9 +49,11 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
+import android.content.pm.UserProperties;
 import android.content.res.Resources;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -58,6 +61,8 @@
 import android.text.TextUtils;
 import android.util.IconDrawableFactory;
 
+import androidx.test.core.app.ApplicationProvider;
+
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 import com.android.settingslib.applications.ApplicationsState.Callbacks;
 import com.android.settingslib.applications.ApplicationsState.Session;
@@ -71,6 +76,7 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
@@ -79,6 +85,7 @@
 import org.robolectric.shadow.api.Shadow;
 import org.robolectric.shadows.ShadowContextImpl;
 import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.util.ReflectionHelpers;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -95,6 +102,7 @@
     private final static String LAUNCHABLE_PACKAGE_NAME = "com.android.launchable";
 
     private static final int PROFILE_USERID = 10;
+    private static final int PROFILE_USERID2 = 11;
 
     private static final String PKG_1 = "PKG1";
     private static final int OWNER_UID_1 = 1001;
@@ -106,6 +114,10 @@
 
     private static final String PKG_3 = "PKG3";
     private static final int OWNER_UID_3 = 1003;
+    private static final int PROFILE_UID_3 = UserHandle.getUid(PROFILE_USERID2, OWNER_UID_3);
+
+    private static final String CLONE_USER = "clone_user";
+    private static final String RANDOM_USER = "random_user";
 
     /** Class under test */
     private ApplicationsState mApplicationsState;
@@ -113,6 +125,8 @@
 
     private Application mApplication;
 
+    @Spy
+    Context mContext = ApplicationProvider.getApplicationContext();
     @Mock
     private Callbacks mCallbacks;
     @Captor
@@ -738,4 +752,53 @@
         when(configChanges.applyNewConfig(any(Resources.class))).thenReturn(false);
         mApplicationsState.setInterestingConfigChanges(configChanges);
     }
+
+    @Test
+    public void shouldShowInPersonalTab_forCurrentUser_returnsTrue() {
+        UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
+        ApplicationInfo appInfo = createApplicationInfo(PKG_1);
+        AppEntry primaryUserApp = createAppEntry(appInfo, 1);
+
+        assertThat(primaryUserApp.shouldShowInPersonalTab(um, appInfo.uid)).isTrue();
+    }
+
+    @Test
+    public void shouldShowInPersonalTab_userProfilePreU_returnsFalse() {
+        UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
+        ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
+                Build.VERSION_CODES.TIRAMISU);
+        // Create an app (and subsequent AppEntry) in a non-primary user profile.
+        ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1);
+        AppEntry nonPrimaryUserApp = createAppEntry(appInfo1, 1);
+
+        assertThat(nonPrimaryUserApp.shouldShowInPersonalTab(um, appInfo1.uid)).isFalse();
+    }
+
+    @Test
+    public void shouldShowInPersonalTab_currentUserIsParent_returnsAsPerUserPropertyOfProfile1() {
+        // Mark system user as parent for both profile users.
+        UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
+        ShadowUserManager shadowUserManager = Shadow.extract(um);
+        shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID,
+                CLONE_USER, 0);
+        shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID2,
+                RANDOM_USER, 0);
+        shadowUserManager.setupUserProperty(PROFILE_USERID,
+                /*showInSettings*/ UserProperties.SHOW_IN_SETTINGS_WITH_PARENT);
+        shadowUserManager.setupUserProperty(PROFILE_USERID2,
+                /*showInSettings*/ UserProperties.SHOW_IN_SETTINGS_NO);
+
+        ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
+                Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+
+        // Treat PROFILE_USERID as a clone user profile and create an app PKG_1 in it.
+        ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1);
+        // Treat PROFILE_USERID2 as a random non-primary profile and create an app PKG_3 in it.
+        ApplicationInfo appInfo2 = createApplicationInfo(PKG_3, PROFILE_UID_3);
+        AppEntry nonPrimaryUserApp1 = createAppEntry(appInfo1, 1);
+        AppEntry nonPrimaryUserApp2 = createAppEntry(appInfo2, 2);
+
+        assertThat(nonPrimaryUserApp1.shouldShowInPersonalTab(um, appInfo1.uid)).isTrue();
+        assertThat(nonPrimaryUserApp2.shouldShowInPersonalTab(um, appInfo2.uid)).isFalse();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 61802a8..f06623d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -327,8 +327,10 @@
         when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
         CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
         CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
-        cachedDevice1.setHiSyncId(HISYNCID1);
-        cachedDevice2.setHiSyncId(HISYNCID1);
+        cachedDevice1.setHearingAidInfo(
+                new HearingAidInfo.Builder().setHiSyncId(HISYNCID1).build());
+        cachedDevice2.setHearingAidInfo(
+                new HearingAidInfo.Builder().setHiSyncId(HISYNCID1).build());
         cachedDevice1.setSubDevice(cachedDevice2);
 
         // Call onDeviceUnpaired for the one in mCachedDevices.
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 79e9938..77c19a1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -29,6 +29,7 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothStatusCodes;
 import android.content.Context;
@@ -74,6 +75,10 @@
     @Mock
     private HearingAidProfile mHearingAidProfile;
     @Mock
+    private HapClientProfile mHapClientProfile;
+    @Mock
+    private LeAudioProfile mLeAudioProfile;
+    @Mock
     private BluetoothDevice mDevice;
     @Mock
     private BluetoothDevice mSubDevice;
@@ -354,7 +359,7 @@
         assertThat(mCachedDevice.getConnectionSummary()).isNull();
 
         // Set device as Active for Hearing Aid and test connection state summary
-        mCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT);
+        mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, left only");
 
@@ -399,7 +404,7 @@
         //   1. Profile:       {HEARING_AID, Connected, Active, Right ear}
         //   2. Audio Manager: In Call
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
-        mCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT);
+        mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
 
@@ -413,9 +418,9 @@
         // Arrange:
         //   1. Profile:       {HEARING_AID, Connected, Active, Both ear}
         //   2. Audio Manager: In Call
-        mCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT);
+        mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
-        mSubCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT);
+        mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
         updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
         mCachedDevice.setSubDevice(mSubCachedDevice);
         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
@@ -443,6 +448,26 @@
     }
 
     @Test
+    public void getConnectionSummary_testActiveDeviceLeAudioHearingAid() {
+        // Test without battery level
+        // Set HAP Client and LE Audio profile to be connected and test connection state summary
+        when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
+        updateProfileStatus(mHapClientProfile, BluetoothProfile.STATE_CONNECTED);
+        updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isNull();
+
+        // Set device as Active for LE Audio and test connection state summary
+        mCachedDevice.setHearingAidInfo(getLeftLeAudioHearingAidInfo());
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.LE_AUDIO);
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, left only");
+
+        // Set LE Audio profile to be disconnected and test connection state summary
+        mCachedDevice.onActiveDeviceChanged(false, BluetoothProfile.LE_AUDIO);
+        mCachedDevice.onProfileStateChanged(mLeAudioProfile, BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isNull();
+    }
+
+    @Test
     public void getConnectionSummary_testMultipleProfilesActiveDevice() {
         // Test without battery level
         // Set A2DP and HFP profiles to be connected and test connection state summary
@@ -859,21 +884,21 @@
     }
 
     @Test
-    public void isConnectedHearingAidDevice_connected_returnTrue() {
+    public void isConnectedAshaHearingAidDevice_connected_returnTrue() {
         when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
         when(mHearingAidProfile.getConnectionStatus(mDevice)).
                 thenReturn(BluetoothProfile.STATE_CONNECTED);
 
-        assertThat(mCachedDevice.isConnectedHearingAidDevice()).isTrue();
+        assertThat(mCachedDevice.isConnectedAshaHearingAidDevice()).isTrue();
     }
 
     @Test
-    public void isConnectedHearingAidDevice_disconnected_returnFalse() {
+    public void isConnectedAshaHearingAidDevice_disconnected_returnFalse() {
         when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
         when(mHearingAidProfile.getConnectionStatus(mDevice)).
                 thenReturn(BluetoothProfile.STATE_DISCONNECTED);
 
-        assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse();
+        assertThat(mCachedDevice.isConnectedAshaHearingAidDevice()).isFalse();
     }
 
     @Test
@@ -891,10 +916,10 @@
     }
 
     @Test
-    public void isConnectedHearingAidDevice_profileIsNull_returnFalse() {
+    public void isConnectedAshaHearingAidDevice_profileIsNull_returnFalse() {
         when(mProfileManager.getHearingAidProfile()).thenReturn(null);
 
-        assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse();
+        assertThat(mCachedDevice.isConnectedAshaHearingAidDevice()).isFalse();
     }
 
     @Test
@@ -965,33 +990,33 @@
 
         mCachedDevice.mRssi = RSSI_1;
         mCachedDevice.mJustDiscovered = JUSTDISCOVERED_1;
-        mCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT);
+        mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
         mSubCachedDevice.mRssi = RSSI_2;
         mSubCachedDevice.mJustDiscovered = JUSTDISCOVERED_2;
-        mSubCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT);
+        mSubCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
         mCachedDevice.setSubDevice(mSubCachedDevice);
 
         mCachedDevice.switchSubDeviceContent();
 
+        verify(mCachedDevice).release();
         assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2);
         assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2);
         assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice);
-        assertThat(mCachedDevice.getDeviceSide()).isEqualTo(
-                HearingAidProfile.DeviceSide.SIDE_RIGHT);
+        assertThat(mCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
+        verify(mSubCachedDevice).release();
         assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1);
         assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1);
         assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
-        assertThat(mSubCachedDevice.getDeviceSide()).isEqualTo(
-                HearingAidProfile.DeviceSide.SIDE_LEFT);
+        assertThat(mSubCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
     }
 
     @Test
     public void getConnectionSummary_profileConnectedFail_showErrorMessage() {
-        final A2dpProfile profle = mock(A2dpProfile.class);
-        mCachedDevice.onProfileStateChanged(profle, BluetoothProfile.STATE_CONNECTED);
+        final A2dpProfile profile = mock(A2dpProfile.class);
+        mCachedDevice.onProfileStateChanged(profile, BluetoothProfile.STATE_CONNECTED);
         mCachedDevice.setProfileConnectedStatus(BluetoothProfile.A2DP, true);
 
-        when(profle.getConnectionStatus(mDevice)).thenReturn(BluetoothProfile.STATE_CONNECTED);
+        when(profile.getConnectionStatus(mDevice)).thenReturn(BluetoothProfile.STATE_CONNECTED);
 
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
                 mContext.getString(R.string.profile_connect_timeout_subtext));
@@ -1069,4 +1094,55 @@
         assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
         assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
     }
+
+    @Test
+    public void isConnectedHearingAidDevice_isConnectedAshaHearingAidDevice_returnTrue() {
+        when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+
+        updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+
+        assertThat(mCachedDevice.isConnectedHearingAidDevice()).isTrue();
+    }
+
+    @Test
+    public void isConnectedHearingAidDevice_isConnectedLeAudioHearingAidDevice_returnTrue() {
+        when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
+        when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+
+        updateProfileStatus(mHapClientProfile, BluetoothProfile.STATE_CONNECTED);
+        updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED);
+
+        assertThat(mCachedDevice.isConnectedHearingAidDevice()).isTrue();
+    }
+
+    @Test
+    public void isConnectedHearingAidDevice_isNotAnyConnectedHearingAidDevice_returnFalse() {
+        when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+        when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
+        when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+
+        updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_DISCONNECTED);
+        updateProfileStatus(mHapClientProfile, BluetoothProfile.STATE_DISCONNECTED);
+        updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_DISCONNECTED);
+
+        assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse();
+    }
+
+    private HearingAidInfo getLeftAshaHearingAidInfo() {
+        return new HearingAidInfo.Builder()
+                .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT)
+                .build();
+    }
+
+    private HearingAidInfo getRightAshaHearingAidInfo() {
+        return new HearingAidInfo.Builder()
+                .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT)
+                .build();
+    }
+
+    private HearingAidInfo getLeftLeAudioHearingAidInfo() {
+        return new HearingAidInfo.Builder()
+                .setLeAudioLocation(BluetoothLeAudio.AUDIO_LOCATION_SIDE_LEFT)
+                .build();
+    }
 }
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/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 611b0a4..470d8e0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -106,7 +107,7 @@
      * deviceSide, deviceMode.
      */
     @Test
-    public void initHearingAidDeviceIfNeeded_validHiSyncId_setHearingAidInfos() {
+    public void initHearingAidDeviceIfNeeded_validHiSyncId_setHearingAidInfo() {
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
         when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(
                 HearingAidProfile.DeviceMode.MODE_BINAURAL);
@@ -118,21 +119,22 @@
 
         assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
         assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
-                HearingAidProfile.DeviceMode.MODE_BINAURAL);
+                HearingAidInfo.DeviceMode.MODE_BINAURAL);
         assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(
-                HearingAidProfile.DeviceSide.SIDE_RIGHT);
+                HearingAidInfo.DeviceSide.SIDE_RIGHT);
     }
 
     /**
      * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId will not be assigned
      */
     @Test
-    public void initHearingAidDeviceIfNeeded_invalidHiSyncId_notToSetHiSyncId() {
+    public void initHearingAidDeviceIfNeeded_invalidHiSyncId_notToSetHearingAidInfo() {
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
                 BluetoothHearingAid.HI_SYNC_ID_INVALID);
+
         mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1);
 
-        verify(mCachedDevice1, never()).setHiSyncId(anyLong());
+        verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
     }
 
     /**
@@ -140,9 +142,10 @@
      */
     @Test
     public void setSubDeviceIfNeeded_sameHiSyncId_setSubDevice() {
-        mCachedDevice1.setHiSyncId(HISYNCID1);
-        mCachedDevice2.setHiSyncId(HISYNCID1);
+        mCachedDevice1.setHearingAidInfo(getLeftAshaHearingAidInfo(HISYNCID1));
+        mCachedDevice2.setHearingAidInfo(getRightAshaHearingAidInfo(HISYNCID1));
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+
         mHearingAidDeviceManager.setSubDeviceIfNeeded(mCachedDevice2);
 
         assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2);
@@ -153,9 +156,10 @@
      */
     @Test
     public void setSubDeviceIfNeeded_differentHiSyncId_notSetSubDevice() {
-        mCachedDevice1.setHiSyncId(HISYNCID1);
-        mCachedDevice2.setHiSyncId(HISYNCID2);
+        mCachedDevice1.setHearingAidInfo(getLeftAshaHearingAidInfo(HISYNCID1));
+        mCachedDevice2.setHearingAidInfo(getRightAshaHearingAidInfo(HISYNCID2));
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+
         mHearingAidDeviceManager.setSubDeviceIfNeeded(mCachedDevice2);
 
         assertThat(mCachedDevice1.getSubDevice()).isNull();
@@ -276,9 +280,9 @@
 
         assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
         assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
-                HearingAidProfile.DeviceMode.MODE_BINAURAL);
+                HearingAidInfo.DeviceMode.MODE_BINAURAL);
         assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(
-                HearingAidProfile.DeviceSide.SIDE_RIGHT);
+                HearingAidInfo.DeviceSide.SIDE_RIGHT);
         verify(mHearingAidDeviceManager).onHiSyncIdChanged(HISYNCID1);
     }
 
@@ -389,11 +393,9 @@
     @Test
     public void onProfileConnectionStateChanged_disconnected_mainDevice_subDeviceConnected_switch()
     {
-        when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1);
-        mCachedDevice1.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT);
-        when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1);
+        mCachedDevice1.setHearingAidInfo(getLeftAshaHearingAidInfo(HISYNCID1));
+        mCachedDevice2.setHearingAidInfo(getRightAshaHearingAidInfo(HISYNCID1));
         when(mCachedDevice2.isConnected()).thenReturn(true);
-        mCachedDevice2.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT);
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
         mCachedDevice1.setSubDevice(mCachedDevice2);
 
@@ -404,10 +406,8 @@
 
         assertThat(mCachedDevice1.mDevice).isEqualTo(mDevice2);
         assertThat(mCachedDevice2.mDevice).isEqualTo(mDevice1);
-        assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(
-                HearingAidProfile.DeviceSide.SIDE_RIGHT);
-        assertThat(mCachedDevice2.getDeviceSide()).isEqualTo(
-                HearingAidProfile.DeviceSide.SIDE_LEFT);
+        assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
+        assertThat(mCachedDevice2.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
         verify(mCachedDevice1).refresh();
     }
 
@@ -455,4 +455,18 @@
         assertThat(mHearingAidDeviceManager.findMainDevice(mCachedDevice2)).
                 isEqualTo(mCachedDevice1);
     }
+
+    private HearingAidInfo getLeftAshaHearingAidInfo(long hiSyncId) {
+        return new HearingAidInfo.Builder()
+                .setAshaDeviceSide(HearingAidInfo.DeviceSide.SIDE_LEFT)
+                .setHiSyncId(hiSyncId)
+                .build();
+    }
+
+    private HearingAidInfo getRightAshaHearingAidInfo(long hiSyncId) {
+        return new HearingAidInfo.Builder()
+                .setAshaDeviceSide(HearingAidInfo.DeviceSide.SIDE_RIGHT)
+                .setHiSyncId(hiSyncId)
+                .build();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
index 89de81f..a2b208a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
@@ -39,7 +39,6 @@
 
     private static final String TEST_TAG = "tag";
     private static final String TEST_KEY = "key";
-    private static final String TEST_TAGGED_KEY = TEST_TAG + "/" + TEST_KEY;
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mContext;
@@ -65,10 +64,8 @@
         editor.putInt(TEST_KEY, 2);
         editor.putInt(TEST_KEY, 2);
 
-        verify(mMetricsFeature, times(6)).action(eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
-                eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(TEST_TAGGED_KEY),
+        verify(mMetricsFeature, times(6)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+                eq(TEST_KEY),
                 anyInt());
     }
 
@@ -82,15 +79,11 @@
         editor.putBoolean(TEST_KEY, false);
 
 
-        verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
-                SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
-                SettingsEnums.PAGE_UNKNOWN,
-                TEST_TAGGED_KEY,
+        verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
+                TEST_KEY,
                 1);
-        verify(mMetricsFeature, times(3)).action(SettingsEnums.PAGE_UNKNOWN,
-                SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
-                SettingsEnums.PAGE_UNKNOWN,
-                TEST_TAGGED_KEY,
+        verify(mMetricsFeature, times(3)).changed(SettingsEnums.PAGE_UNKNOWN,
+                TEST_KEY,
                 0);
     }
 
@@ -103,10 +96,8 @@
         editor.putLong(TEST_KEY, 1);
         editor.putLong(TEST_KEY, 2);
 
-        verify(mMetricsFeature, times(4)).action(eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
-                eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(TEST_TAGGED_KEY),
+        verify(mMetricsFeature, times(4)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+                eq(TEST_KEY),
                 anyInt());
     }
 
@@ -117,10 +108,8 @@
         editor.putLong(TEST_KEY, 1);
         editor.putLong(TEST_KEY, veryBigNumber);
 
-        verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
-                SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
-                SettingsEnums.PAGE_UNKNOWN,
-                TEST_TAGGED_KEY,
+        verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
+                TEST_KEY,
                 Integer.MAX_VALUE);
     }
 
@@ -131,10 +120,8 @@
         editor.putLong(TEST_KEY, 1);
         editor.putLong(TEST_KEY, veryNegativeNumber);
 
-        verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
-                SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
-                SettingsEnums.PAGE_UNKNOWN,
-                TEST_TAGGED_KEY, Integer.MIN_VALUE);
+        verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
+                TEST_KEY, Integer.MIN_VALUE);
     }
 
     @Test
@@ -146,10 +133,8 @@
         editor.putFloat(TEST_KEY, 1);
         editor.putFloat(TEST_KEY, 2);
 
-        verify(mMetricsFeature, times(4)).action(eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
-                eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(TEST_TAGGED_KEY),
+        verify(mMetricsFeature, times(4)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+                eq(TEST_KEY),
                 anyInt());
     }
 
@@ -159,7 +144,7 @@
         verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
                 ACTION_SETTINGS_PREFERENCE_CHANGE,
                 SettingsEnums.PAGE_UNKNOWN,
-                "tag/key:com.android.settings",
+                "key:com.android.settings",
                 0);
     }
 
@@ -170,10 +155,8 @@
         mSharedPrefLogger.logValue(TEST_KEY, "62");
         mSharedPrefLogger.logValue(TEST_KEY, "0");
 
-        verify(mMetricsFeature, times(3)).action(eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
-                eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(TEST_TAGGED_KEY),
+        verify(mMetricsFeature, times(3)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+                eq(TEST_KEY),
                 anyInt());
     }
 
@@ -185,10 +168,8 @@
         mSharedPrefLogger.logValue(TEST_KEY, "4.2");
         mSharedPrefLogger.logValue(TEST_KEY, "3.0");
 
-        verify(mMetricsFeature, times(0)).action(eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
-                eq(SettingsEnums.PAGE_UNKNOWN),
-                eq(TEST_TAGGED_KEY),
+        verify(mMetricsFeature, times(0)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+                eq(TEST_KEY),
                 anyInt());
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index 5399f8a..c058a61 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -461,7 +461,7 @@
     public void connect_shouldSelectRoute() {
         mInfoMediaDevice1.connect();
 
-        verify(mMediaRouter2Manager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo1);
+        verify(mMediaRouter2Manager).transfer(TEST_PACKAGE_NAME, mRouteInfo1);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java
new file mode 100644
index 0000000..740261d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.mobile;
+
+import static com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl.parseNetworkIconOverrideTypedArray;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.TypedArray;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Map;
+
+@RunWith(RobolectricTestRunner.class)
+public final class MobileIconCarrierIdOverridesTest {
+    private static final String OVERRIDE_ICON_1_NAME = "name_1";
+    private static final int OVERRIDE_ICON_1_RES = 1;
+
+    private static final String OVERRIDE_ICON_2_NAME = "name_2";
+    private static final int OVERRIDE_ICON_2_RES = 2;
+
+    NetworkOverrideTypedArrayMock mResourceMock;
+
+    @Before
+    public void setUp() {
+        mResourceMock = new NetworkOverrideTypedArrayMock(
+                new String[] { OVERRIDE_ICON_1_NAME, OVERRIDE_ICON_2_NAME },
+                new int[] { OVERRIDE_ICON_1_RES, OVERRIDE_ICON_2_RES }
+        );
+    }
+
+    @Test
+    public void testParse_singleOverride() {
+        mResourceMock.setOverrides(
+                new String[] { OVERRIDE_ICON_1_NAME },
+                new int[] { OVERRIDE_ICON_1_RES }
+        );
+
+        Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock());
+
+        assertThat(parsed.get(OVERRIDE_ICON_1_NAME)).isEqualTo(OVERRIDE_ICON_1_RES);
+    }
+
+    @Test
+    public void testParse_multipleOverrides() {
+        mResourceMock.setOverrides(
+                new String[] { OVERRIDE_ICON_1_NAME, OVERRIDE_ICON_2_NAME },
+                new int[] { OVERRIDE_ICON_1_RES, OVERRIDE_ICON_2_RES }
+        );
+
+        Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock());
+
+        assertThat(parsed.get(OVERRIDE_ICON_2_NAME)).isEqualTo(OVERRIDE_ICON_2_RES);
+        assertThat(parsed.get(OVERRIDE_ICON_1_NAME)).isEqualTo(OVERRIDE_ICON_1_RES);
+    }
+
+    @Test
+    public void testParse_nonexistentKey_isNull() {
+        mResourceMock.setOverrides(
+                new String[] { OVERRIDE_ICON_1_NAME },
+                new int[] { OVERRIDE_ICON_1_RES }
+        );
+
+        Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock());
+
+        assertThat(parsed.get(OVERRIDE_ICON_2_NAME)).isNull();
+    }
+
+    static class NetworkOverrideTypedArrayMock {
+        private Object[] mInterleaved;
+
+        private final TypedArray mMockTypedArray = mock(TypedArray.class);
+
+        NetworkOverrideTypedArrayMock(
+                String[] networkTypes,
+                int[] iconOverrides) {
+
+            mInterleaved = interleaveTypes(networkTypes, iconOverrides);
+
+            doAnswer(invocation -> {
+                return mInterleaved[(int) invocation.getArgument(0)];
+            }).when(mMockTypedArray).getString(/* index */ anyInt());
+
+            doAnswer(invocation -> {
+                return mInterleaved[(int) invocation.getArgument(0)];
+            }).when(mMockTypedArray).getResourceId(/* index */ anyInt(), /* default */ anyInt());
+
+            when(mMockTypedArray.length()).thenAnswer(invocation -> {
+                return mInterleaved.length;
+            });
+        }
+
+        TypedArray getMock() {
+            return mMockTypedArray;
+        }
+
+        void setOverrides(String[] types, int[] resIds) {
+            mInterleaved = interleaveTypes(types, resIds);
+        }
+
+        private Object[] interleaveTypes(String[] strs, int[] ints) {
+            assertThat(strs.length).isEqualTo(ints.length);
+
+            Object[] ret = new Object[strs.length * 2];
+
+            // Keep track of where we are in the interleaved array, but iterate the overrides
+            int interleavedIndex = 0;
+            for (int i = 0; i < strs.length; i++) {
+                ret[interleavedIndex] = strs[i];
+                interleavedIndex += 1;
+                ret[interleavedIndex] = ints[i];
+                interleavedIndex += 1;
+            }
+            return ret;
+        }
+    }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index def7ddc..267c196 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -53,6 +53,7 @@
         Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
         Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
         Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
+        Settings.Secure.CONTRAST_LEVEL,
         Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET,
         Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED,
         Settings.Secure.ACCESSIBILITY_CAPTIONING_LOCALE,
@@ -122,6 +123,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/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index e30dd2f..46876b4 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -175,6 +175,7 @@
         VALIDATORS.put(Global.ADVANCED_BATTERY_USAGE_AMOUNT, PERCENTAGE_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
+        VALIDATORS.put(Global.STYLUS_EVER_USED, BOOLEAN_VALIDATOR);
 
         VALIDATORS.put(Global.Wearable.HAS_PAY_TOKENS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.GMS_CHECKIN_TIMEOUT_MIN, ANY_INTEGER_VALIDATOR);
@@ -335,6 +336,7 @@
         VALIDATORS.put(Global.Wearable.COOLDOWN_MODE_ON, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 Global.Wearable.GESTURE_TOUCH_AND_HOLD_WATCH_FACE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.Wearable.RSB_WAKE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.BEDTIME_MODE, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index cde4bc4..def9c19 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -86,6 +86,7 @@
         VALIDATORS.put(Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.CONTRAST_LEVEL, new InclusiveFloatRangeValidator(-1f, 1f));
         VALIDATORS.put(
                 Secure.ACCESSIBILITY_CAPTIONING_PRESET,
                 new DiscreteValueValidator(new String[] {"-1", "0", "1", "2", "3", "4"}));
@@ -177,6 +178,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/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 4e2bce2..17078c4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1762,6 +1762,9 @@
                 Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
                 SecureSettingsProto.Accessibility.HIGH_TEXT_CONTRAST_ENABLED);
         dumpSetting(s, p,
+                Settings.Secure.CONTRAST_LEVEL,
+                SecureSettingsProto.Accessibility.CONTRAST_LEVEL);
+        dumpSetting(s, p,
                 Settings.Secure.FONT_WEIGHT_ADJUSTMENT,
                 SecureSettingsProto.FONT_WEIGHT_ADJUSTMENT);
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index fa96a2f..9192086 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -112,10 +112,10 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.providers.settings.SettingsState.Setting;
 
-import libcore.util.HexEncoding;
-
 import com.google.android.collect.Sets;
 
+import libcore.util.HexEncoding;
+
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
@@ -1144,7 +1144,7 @@
             Slog.v(LOG_TAG, "getConfigSetting(" + name + ")");
         }
 
-        DeviceConfig.enforceReadPermission(getContext(), /*namespace=*/name.split("/")[0]);
+        Settings.Config.enforceReadPermission(/*namespace=*/name.split("/")[0]);
 
         // Get the value.
         synchronized (mLock) {
@@ -1317,7 +1317,7 @@
             Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
         }
 
-        DeviceConfig.enforceReadPermission(getContext(),
+        Settings.Config.enforceReadPermission(
                 prefix != null ? prefix.split("/")[0] : null);
 
         synchronized (mLock) {
@@ -3595,8 +3595,8 @@
 
         private Uri getNotificationUriFor(int key, String name) {
             if (isConfigSettingsKey(key)) {
-                return (name != null) ? Uri.withAppendedPath(DeviceConfig.CONTENT_URI, name)
-                        : DeviceConfig.CONTENT_URI;
+                return (name != null) ? Uri.withAppendedPath(Settings.Config.CONTENT_URI, name)
+                        : Settings.Config.CONTENT_URI;
             } else if (isGlobalSettingsKey(key)) {
                 return (name != null) ? Uri.withAppendedPath(Settings.Global.CONTENT_URI, name)
                         : Settings.Global.CONTENT_URI;
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index aea2f52..153f0b4 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -272,6 +272,7 @@
                     Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
                     Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
                     Settings.Global.STYLUS_HANDWRITING_ENABLED,
+                    Settings.Global.STYLUS_EVER_USED,
                     Settings.Global.ENABLE_ADB_INCREMENTAL_INSTALL_DEFAULT,
                     Settings.Global.ENABLE_MULTI_SLOT_TIMEOUT_MILLIS,
                     Settings.Global.ENHANCED_4G_MODE_ENABLED,
@@ -658,7 +659,8 @@
                     Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
                     Settings.Global.Wearable.BEDTIME_MODE,
                     Settings.Global.Wearable.BEDTIME_HARD_MODE,
-                    Settings.Global.Wearable.EARLY_UPDATES_STATUS);
+                    Settings.Global.Wearable.EARLY_UPDATES_STATUS,
+                    Settings.Global.Wearable.RSB_WAKE_ENABLED);
 
     private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
              newHashSet(
@@ -770,6 +772,7 @@
                  Settings.Secure.SLEEP_TIMEOUT,
                  Settings.Secure.SMS_DEFAULT_APPLICATION,
                  Settings.Secure.SPELL_CHECKER_ENABLED,  // Intentionally removed in Q
+                 Settings.Secure.STYLUS_BUTTONS_DISABLED,
                  Settings.Secure.TRUST_AGENTS_INITIALIZED,
                  Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED,
                  Settings.Secure.TV_APP_USES_NON_SYSTEM_INPUTS,
@@ -884,7 +887,7 @@
                         Settings.Secure.SHOW_QR_CODE_SCANNER_SETTING,
                         Settings.Secure.SKIP_ACCESSIBILITY_SHORTCUT_DIALOG_TIMEOUT_RESTRICTION,
                         Settings.Secure.SPATIAL_AUDIO_ENABLED,
-                        Settings.Secure.TIMEOUT_TO_USER_ZERO,
+                        Settings.Secure.TIMEOUT_TO_DOCK_USER,
                         Settings.Secure.UI_NIGHT_MODE_LAST_COMPUTED,
                         Settings.Secure.UI_NIGHT_MODE_OVERRIDE_OFF,
                         Settings.Secure.UI_NIGHT_MODE_OVERRIDE_ON);
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
index e588b3d..753378b 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
@@ -22,7 +22,6 @@
 
 import android.content.ContentResolver;
 import android.os.Bundle;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
@@ -180,14 +179,14 @@
             args.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
         }
         resolver.call(
-                DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
+                Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
     }
 
     private static String getFromContentProvider(ContentResolver resolver, String namespace,
             String key) {
         String compositeName = namespace + "/" + key;
         Bundle result = resolver.call(
-                DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
+                Settings.Config.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
         assertNotNull(result);
         return result.getString(Settings.NameValueTable.VALUE);
     }
@@ -196,7 +195,8 @@
             String key) {
         String compositeName = namespace + "/" + key;
         Bundle result = resolver.call(
-                DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
+                Settings.Config.CONTENT_URI,
+                Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
         assertNotNull(result);
         return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
     }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 90fab08..d3ba5e6 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -720,6 +720,137 @@
     <!-- Permission required for CTS test - CtsDeviceLockTestCases -->
     <uses-permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE" />
 
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
+
+    <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.CAPTURE_MEDIA_OUTPUT" />
+
+    <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.CAPTURE_TUNER_AUDIO_INPUT" />
+
+    <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT" />
+
+    <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
+
+    <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.USE_EXACT_ALARM" />
+
+    <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED" />
+    <uses-permission android:name="android.permission.health.READ_BASAL_BODY_TEMPERATURE" />
+    <uses-permission android:name="android.permission.health.READ_BASAL_METABOLIC_RATE" />
+    <uses-permission android:name="android.permission.health.READ_BLOOD_GLUCOSE" />
+    <uses-permission android:name="android.permission.health.READ_BLOOD_PRESSURE" />
+    <uses-permission android:name="android.permission.health.READ_BODY_FAT" />
+    <uses-permission android:name="android.permission.health.READ_BODY_TEMPERATURE" />
+    <uses-permission android:name="android.permission.health.READ_BODY_WATER_MASS" />
+    <uses-permission android:name="android.permission.health.READ_BONE_MASS" />
+    <uses-permission android:name="android.permission.health.READ_CERVICAL_MUCUS" />
+    <uses-permission android:name="android.permission.health.READ_DISTANCE" />
+    <uses-permission android:name="android.permission.health.READ_ELEVATION_GAINED" />
+    <uses-permission android:name="android.permission.health.READ_EXERCISE" />
+    <uses-permission android:name="android.permission.health.READ_FLOORS_CLIMBED" />
+    <uses-permission android:name="android.permission.health.READ_HEART_RATE" />
+    <uses-permission android:name="android.permission.health.READ_HEART_RATE_VARIABILITY" />
+    <uses-permission android:name="android.permission.health.READ_HEIGHT" />
+    <uses-permission android:name="android.permission.health.READ_HIP_CIRCUMFERENCE" />
+    <uses-permission android:name="android.permission.health.READ_HYDRATION" />
+    <uses-permission android:name="android.permission.health.READ_LEAN_BODY_MASS" />
+    <uses-permission android:name="android.permission.health.READ_MENSTRUATION" />
+    <uses-permission android:name="android.permission.health.READ_NUTRITION" />
+    <uses-permission android:name="android.permission.health.READ_OVULATION_TEST" />
+    <uses-permission android:name="android.permission.health.READ_OXYGEN_SATURATION" />
+    <uses-permission android:name="android.permission.health.READ_POWER" />
+    <uses-permission android:name="android.permission.health.READ_RESPIRATORY_RATE" />
+    <uses-permission android:name="android.permission.health.READ_RESTING_HEART_RATE" />
+    <uses-permission android:name="android.permission.health.READ_SEXUAL_ACTIVITY" />
+    <uses-permission android:name="android.permission.health.READ_SLEEP" />
+    <uses-permission android:name="android.permission.health.READ_SPEED" />
+    <uses-permission android:name="android.permission.health.READ_STEPS" />
+    <uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED" />
+    <uses-permission android:name="android.permission.health.READ_VO2_MAX" />
+    <uses-permission android:name="android.permission.health.READ_WAIST_CIRCUMFERENCE" />
+    <uses-permission android:name="android.permission.health.READ_WEIGHT" />
+    <uses-permission android:name="android.permission.health.READ_WHEELCHAIR_PUSHES" />
+    <uses-permission android:name="android.permission.health.WRITE_ACTIVE_CALORIES_BURNED" />
+    <uses-permission android:name="android.permission.health.WRITE_BASAL_BODY_TEMPERATURE" />
+    <uses-permission android:name="android.permission.health.WRITE_BASAL_METABOLIC_RATE" />
+    <uses-permission android:name="android.permission.health.WRITE_BLOOD_GLUCOSE" />
+    <uses-permission android:name="android.permission.health.WRITE_BLOOD_PRESSURE" />
+    <uses-permission android:name="android.permission.health.WRITE_BODY_FAT" />
+    <uses-permission android:name="android.permission.health.WRITE_BODY_TEMPERATURE" />
+    <uses-permission android:name="android.permission.health.WRITE_BODY_WATER_MASS" />
+    <uses-permission android:name="android.permission.health.WRITE_BONE_MASS" />
+    <uses-permission android:name="android.permission.health.WRITE_CERVICAL_MUCUS" />
+    <uses-permission android:name="android.permission.health.WRITE_DISTANCE" />
+    <uses-permission android:name="android.permission.health.WRITE_ELEVATION_GAINED" />
+    <uses-permission android:name="android.permission.health.WRITE_EXERCISE" />
+    <uses-permission android:name="android.permission.health.WRITE_FLOORS_CLIMBED" />
+    <uses-permission android:name="android.permission.health.WRITE_HEART_RATE" />
+    <uses-permission android:name="android.permission.health.WRITE_HEART_RATE_VARIABILITY" />
+    <uses-permission android:name="android.permission.health.WRITE_HEIGHT" />
+    <uses-permission android:name="android.permission.health.WRITE_HIP_CIRCUMFERENCE" />
+    <uses-permission android:name="android.permission.health.WRITE_HYDRATION" />
+    <uses-permission android:name="android.permission.health.WRITE_LEAN_BODY_MASS" />
+    <uses-permission android:name="android.permission.health.WRITE_MENSTRUATION" />
+    <uses-permission android:name="android.permission.health.WRITE_NUTRITION" />
+    <uses-permission android:name="android.permission.health.WRITE_OVULATION_TEST" />
+    <uses-permission android:name="android.permission.health.WRITE_OXYGEN_SATURATION" />
+    <uses-permission android:name="android.permission.health.WRITE_POWER" />
+    <uses-permission android:name="android.permission.health.WRITE_RESPIRATORY_RATE" />
+    <uses-permission android:name="android.permission.health.WRITE_RESTING_HEART_RATE" />
+    <uses-permission android:name="android.permission.health.WRITE_SEXUAL_ACTIVITY" />
+    <uses-permission android:name="android.permission.health.WRITE_SLEEP" />
+    <uses-permission android:name="android.permission.health.WRITE_SPEED" />
+    <uses-permission android:name="android.permission.health.WRITE_STEPS" />
+    <uses-permission android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED" />
+    <uses-permission android:name="android.permission.health.WRITE_VO2_MAX" />
+    <uses-permission android:name="android.permission.health.WRITE_WAIST_CIRCUMFERENCE" />
+    <uses-permission android:name="android.permission.health.WRITE_WEIGHT" />
+    <uses-permission android:name="android.permission.health.WRITE_WHEELCHAIR_PUSHES" />
+
+    <!-- Permission required for CTS test - ApplicationExemptionsTests -->
+    <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/Shell/res/values-en-rCA/strings.xml b/packages/Shell/res/values-en-rCA/strings.xml
index 5462813..65ab725 100644
--- a/packages/Shell/res/values-en-rCA/strings.xml
+++ b/packages/Shell/res/values-en-rCA/strings.xml
@@ -28,7 +28,7 @@
     <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"Select to share your bug report without a screenshot or wait for the screenshot to finish"</string>
     <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Tap to share your bug report without a screenshot or wait for the screenshot to finish"</string>
     <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Tap to share your bug report without a screenshot or wait for the screenshot to finish"</string>
-    <string name="bugreport_confirm" msgid="5917407234515812495">"Bug reports contain data from the system\'s various log files, which may include data that you consider sensitive (such as app-usage and location data). Only share bug reports with people and apps that you trust."</string>
+    <string name="bugreport_confirm" msgid="5917407234515812495">"Bug reports contain data from the system\'s various log files, which may include data you consider sensitive (such as app-usage and location data). Only share bug reports with people and apps you trust."</string>
     <string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Don\'t show again"</string>
     <string name="bugreport_storage_title" msgid="5332488144740527109">"Bug reports"</string>
     <string name="bugreport_unreadable_text" msgid="586517851044535486">"Bug report file could not be read"</string>
diff --git a/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java b/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java
index e34f5c8..d866653 100644
--- a/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java
+++ b/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java
@@ -104,7 +104,7 @@
 
             final IntentFilter filter = new IntentFilter();
             filter.addAction(CUSTOM_ACTION_SEND_MULTIPLE_INTENT);
-            context.registerReceiver(receiver, filter);
+            context.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED);
         }
 
         /**
diff --git a/packages/SimAppDialog/res/values-en-rCA/strings.xml b/packages/SimAppDialog/res/values-en-rCA/strings.xml
index 1ddbaf9..7983c04 100644
--- a/packages/SimAppDialog/res/values-en-rCA/strings.xml
+++ b/packages/SimAppDialog/res/values-en-rCA/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="app_name" msgid="8898068901680117589">"Sim app dialogue"</string>
+    <string name="app_name" msgid="8898068901680117589">"Sim App Dialog"</string>
     <string name="install_carrier_app_title" msgid="334729104862562585">"Activate mobile service"</string>
     <string name="install_carrier_app_description" msgid="4014303558674923797">"To get your new SIM working properly, you\'ll need to install the <xliff:g id="ID_1">%1$s</xliff:g> app"</string>
     <string name="install_carrier_app_description_default" msgid="7356830245205847840">"To get your new SIM working properly, you\'ll need to install the carrier app"</string>
diff --git a/packages/SoundPicker/res/values-en-rCA/strings.xml b/packages/SoundPicker/res/values-en-rCA/strings.xml
index 4c237b9..b0708356 100644
--- a/packages/SoundPicker/res/values-en-rCA/strings.xml
+++ b/packages/SoundPicker/res/values-en-rCA/strings.xml
@@ -23,7 +23,7 @@
     <string name="add_alarm_text" msgid="3545497316166999225">"Add alarm"</string>
     <string name="add_notification_text" msgid="4431129543300614788">"Add notification"</string>
     <string name="delete_ringtone_text" msgid="201443984070732499">"Delete"</string>
-    <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
-    <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
+    <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add custom ringtone"</string>
+    <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete custom ringtone"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
 </resources>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 753e110..87354c7 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -91,6 +91,7 @@
         "SystemUIAnimationLib",
         "SystemUIPluginLib",
         "SystemUISharedLib",
+        "SystemUICustomizationLib",
         "SystemUI-statsd",
         "SettingsLib",
         "androidx.core_core-ktx",
@@ -114,6 +115,7 @@
         "androidx.dynamicanimation_dynamicanimation",
         "androidx-constraintlayout_constraintlayout",
         "androidx.exifinterface_exifinterface",
+        "androidx.test.ext.junit",
         "com.google.android.material_material",
         "kotlinx_coroutines_android",
         "kotlinx_coroutines",
@@ -125,6 +127,7 @@
         "jsr330",
         "lottie",
         "LowLightDreamLib",
+        "motion_tool_lib",
     ],
     manifest: "AndroidManifest.xml",
 
@@ -165,30 +168,20 @@
 }
 
 android_library {
-    name: "SystemUI-tests",
+    name: "SystemUI-tests-base",
     manifest: "tests/AndroidManifest-base.xml",
-    additional_manifests: ["tests/AndroidManifest.xml"],
-
     resource_dirs: [
         "tests/res",
         "res-product",
         "res-keyguard",
         "res",
     ],
-    srcs: [
-        "tests/src/**/*.kt",
-        "tests/src/**/*.java",
-        "src/**/*.kt",
-        "src/**/*.java",
-        "src/**/I*.aidl",
-        ":ReleaseJavaFiles",
-        ":SystemUI-tests-utils",
-    ],
     static_libs: [
         "WifiTrackerLib",
         "SystemUIAnimationLib",
         "SystemUIPluginLib",
         "SystemUISharedLib",
+        "SystemUICustomizationLib",
         "SystemUI-statsd",
         "SettingsLib",
         "androidx.viewpager2_viewpager2",
@@ -221,8 +214,6 @@
         "metrics-helper-lib",
         "hamcrest-library",
         "androidx.test.rules",
-        "androidx.test.uiautomator_uiautomator",
-        "mockito-target-extended-minus-junit4",
         "testables",
         "truth-prebuilt",
         "monet",
@@ -230,6 +221,28 @@
         "jsr330",
         "WindowManager-Shell",
         "LowLightDreamLib",
+        "motion_tool_lib",
+    ],
+}
+
+android_library {
+    name: "SystemUI-tests",
+    manifest: "tests/AndroidManifest-base.xml",
+    additional_manifests: ["tests/AndroidManifest.xml"],
+    srcs: [
+        "tests/src/**/*.kt",
+        "tests/src/**/*.java",
+        "src/**/*.kt",
+        "src/**/*.java",
+        "src/**/I*.aidl",
+        ":ReleaseJavaFiles",
+        ":SystemUI-tests-utils",
+    ],
+    static_libs: [
+        "SystemUI-tests-base",
+        "androidx.test.uiautomator_uiautomator",
+        "mockito-target-extended-minus-junit4",
+        "androidx.test.ext.junit",
     ],
     libs: [
         "android.test.runner",
@@ -247,6 +260,45 @@
     },
 }
 
+android_app {
+    name: "SystemUIRobo-stub",
+    defaults: [
+        "platform_app_defaults",
+        "SystemUI_app_defaults",
+    ],
+    manifest: "tests/AndroidManifest-base.xml",
+    static_libs: [
+        "SystemUI-tests-base",
+    ],
+    aaptflags: [
+        "--extra-packages",
+        "com.android.systemui",
+    ],
+    dont_merge_manifests: true,
+    platform_apis: true,
+    system_ext_specific: true,
+    certificate: "platform",
+    privileged: true,
+    resource_dirs: [],
+}
+
+android_robolectric_test {
+    name: "SystemUiRoboTests",
+    srcs: [
+        "tests/robolectric/src/**/*.kt",
+        "tests/robolectric/src/**/*.java",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+        "truth-prebuilt",
+    ],
+    kotlincflags: ["-Xjvm-default=enable"],
+    instrumentation_for: "SystemUIRobo-stub",
+    java_resource_dirs: ["tests/robolectric/config"],
+}
+
 // Opt-out config for optimizing the SystemUI target using R8.
 // Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via
 // `SYSTEMUI_OPTIMIZE_JAVA := false`.
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4267ba2..a8216e8 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -126,6 +126,9 @@
     <uses-permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES" />
     <uses-permission android:name="android.permission.INPUT_CONSUMER" />
 
+    <!-- DeviceStateManager -->
+    <uses-permission android:name="android.permission.CONTROL_DEVICE_STATE" />
+
     <!-- DreamManager -->
     <uses-permission android:name="android.permission.READ_DREAM_STATE" />
     <uses-permission android:name="android.permission.WRITE_DREAM_STATE" />
@@ -195,6 +198,9 @@
     <permission android:name="com.android.systemui.permission.FLAGS"
                 android:protectionLevel="signature" />
 
+    <permission android:name="android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Adding Quick Settings tiles -->
     <uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
 
@@ -557,6 +563,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"
@@ -898,6 +914,29 @@
         <service android:name=".controls.controller.AuxiliaryPersistenceWrapper$DeletionJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE"/>
 
+        <!-- region Note Task -->
+        <activity
+            android:name=".notetask.shortcut.CreateNoteTaskShortcutActivity"
+            android:enabled="false"
+            android:exported="true"
+            android:excludeFromRecents="true"
+            android:theme="@android:style/Theme.NoDisplay"
+            android:label="@string/note_task_button_label"
+            android:icon="@drawable/ic_note_task_button">
+
+            <intent-filter>
+                <action android:name="android.intent.action.CREATE_SHORTCUT" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".notetask.shortcut.LaunchNoteTaskActivity"
+            android:exported="true"
+            android:excludeFromRecents="true"
+            android:theme="@android:style/Theme.NoDisplay" />
+        <!-- endregion -->
+
         <!-- started from ControlsRequestReceiver -->
         <activity
             android:name=".controls.management.ControlsRequestDialog"
@@ -961,10 +1000,11 @@
 
         <receiver android:name=".media.dialog.MediaOutputDialogReceiver"
                   android:exported="true">
-            <intent-filter>
+            <intent-filter android:priority="1">
                 <action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" />
                 <action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG" />
                 <action android:name="com.android.systemui.action.DISMISS_MEDIA_OUTPUT_DIALOG" />
+                <action android:name="android.intent.action.SHOW_OUTPUT_SWITCHER" />
             </intent-filter>
         </receiver>
 
@@ -976,5 +1016,18 @@
                 <action android:name="com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG" />
             </intent-filter>
         </receiver>
+
+        <activity android:name=".logcat.LogAccessDialogActivity"
+                  android:theme="@android:style/Theme.Translucent.NoTitleBar"
+                  android:excludeFromRecents="true"
+                  android:exported="false">
+        </activity>
+
+        <provider
+            android:authorities="com.android.systemui.keyguard.quickaffordance"
+            android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
+            android:exported="true"
+            android:permission="android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES"
+            />
     </application>
 </manifest>
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index f7bcf1f..8acc2f8 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -36,6 +36,9 @@
 
     static_libs: [
         "PluginCoreLib",
+        "androidx.core_core-animation-nodeps",
+        "androidx.core_core-ktx",
+        "androidx.annotation_annotation",
     ],
 
     manifest: "AndroidManifest.xml",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index fdfad2b..54aa351 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -75,7 +75,7 @@
      */
     interface Controller {
         /** The [ViewRootImpl] of this controller. */
-        val viewRoot: ViewRootImpl
+        val viewRoot: ViewRootImpl?
 
         /**
          * The identity object of the source animated by this controller. This animator will ensure
@@ -807,15 +807,17 @@
      * inversely, removed from the overlay when the source is moved back to its original position).
      */
     private fun synchronizeNextDraw(then: () -> Unit) {
-        if (forceDisableSynchronization) {
-            // Don't synchronize when inside an automated test.
+        val controllerRootView = controller.viewRoot?.view
+        if (forceDisableSynchronization || controllerRootView == null) {
+            // Don't synchronize when inside an automated test or if the controller root view is
+            // detached.
             then()
             return
         }
 
-        ViewRootSync.synchronizeNextDraw(controller.viewRoot.view, decorView, then)
+        ViewRootSync.synchronizeNextDraw(controllerRootView, decorView, then)
         decorView.invalidate()
-        controller.viewRoot.view.invalidate()
+        controllerRootView.invalidate()
     }
 
     private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
index 8063483..9dbb920 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
@@ -27,7 +27,10 @@
 import android.view.animation.PathInterpolator;
 
 /**
- * Utility class to receive interpolators from
+ * Utility class to receive interpolators from.
+ *
+ * Make sure that changes made to this class are also reflected in {@link InterpolatorsAndroidX}.
+ * Please consider using the androidx dependencies featuring better testability altogether.
  */
 public class Interpolators {
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java b/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
new file mode 100644
index 0000000..8da87feb
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
@@ -0,0 +1,219 @@
+/*
+ * 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.animation;
+
+import android.graphics.Path;
+import android.util.MathUtils;
+
+import androidx.core.animation.AccelerateDecelerateInterpolator;
+import androidx.core.animation.AccelerateInterpolator;
+import androidx.core.animation.BounceInterpolator;
+import androidx.core.animation.DecelerateInterpolator;
+import androidx.core.animation.Interpolator;
+import androidx.core.animation.LinearInterpolator;
+import androidx.core.animation.PathInterpolator;
+
+/**
+ * Utility class to receive interpolators from. (androidx compatible version)
+ *
+ * This is the androidx compatible version of {@link Interpolators}. Make sure that changes made to
+ * this class are also reflected in {@link Interpolators}.
+ *
+ * Using the androidx versions of {@link androidx.core.animation.ValueAnimator} or
+ * {@link androidx.core.animation.ObjectAnimator} improves animation testability. This file provides
+ * the androidx compatible versions of the interpolators defined in {@link Interpolators}.
+ * AnimatorTestRule can be used in Tests to manipulate the animation under test (e.g. artificially
+ * advancing the time).
+ */
+public class InterpolatorsAndroidX {
+
+    /*
+     * ============================================================================================
+     * Emphasized interpolators.
+     * ============================================================================================
+     */
+
+    /**
+     * The default emphasized interpolator. Used for hero / emphasized movement of content.
+     */
+    public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
+
+    /**
+     * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
+     * is disappearing e.g. when moving off screen.
+     */
+    public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
+            0.3f, 0f, 0.8f, 0.15f);
+
+    /**
+     * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
+     * is appearing e.g. when coming from off screen
+     */
+    public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
+            0.05f, 0.7f, 0.1f, 1f);
+
+
+    /*
+     * ============================================================================================
+     * Standard interpolators.
+     * ============================================================================================
+     */
+
+    /**
+     * The standard interpolator that should be used on every normal animation
+     */
+    public static final Interpolator STANDARD = new PathInterpolator(
+            0.2f, 0f, 0f, 1f);
+
+    /**
+     * The standard accelerating interpolator that should be used on every regular movement of
+     * content that is disappearing e.g. when moving off screen.
+     */
+    public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(
+            0.3f, 0f, 1f, 1f);
+
+    /**
+     * The standard decelerating interpolator that should be used on every regular movement of
+     * content that is appearing e.g. when coming from off screen.
+     */
+    public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(
+            0f, 0f, 0f, 1f);
+
+    /*
+     * ============================================================================================
+     * Legacy
+     * ============================================================================================
+     */
+
+    /**
+     * The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN.
+     */
+    public static final Interpolator LEGACY = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
+    /**
+     * The default legacy accelerating interpolator as defined in Material 1.
+     * Also known as FAST_OUT_LINEAR_IN.
+     */
+    public static final Interpolator LEGACY_ACCELERATE = new PathInterpolator(0.4f, 0f, 1f, 1f);
+
+    /**
+     * The default legacy decelerating interpolator as defined in Material 1.
+     * Also known as LINEAR_OUT_SLOW_IN.
+     */
+    public static final Interpolator LEGACY_DECELERATE = new PathInterpolator(0f, 0f, 0.2f, 1f);
+
+    /**
+     * Linear interpolator. Often used if the interpolator is for different properties who need
+     * different interpolations.
+     */
+    public static final Interpolator LINEAR = new LinearInterpolator();
+
+    /*
+    * ============================================================================================
+    * Custom interpolators
+    * ============================================================================================
+    */
+
+    public static final Interpolator FAST_OUT_SLOW_IN = LEGACY;
+    public static final Interpolator FAST_OUT_LINEAR_IN = LEGACY_ACCELERATE;
+    public static final Interpolator LINEAR_OUT_SLOW_IN = LEGACY_DECELERATE;
+
+    /**
+     * Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
+     * goes from 1 to 0 instead of 0 to 1).
+     */
+    public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
+            new PathInterpolator(0.8f, 0f, 0.6f, 1f);
+    public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f);
+    public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+    public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
+    public static final Interpolator ACCELERATE = new AccelerateInterpolator();
+    public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
+    public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
+    public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
+    public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
+    public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f,
+            1.1f);
+    public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f,
+            1);
+    public static final Interpolator BOUNCE = new BounceInterpolator();
+    /**
+     * For state transitions on the control panel that lives in GlobalActions.
+     */
+    public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
+            1.0f);
+
+    /**
+     * Interpolator to be used when animating a move based on a click. Pair with enough duration.
+     */
+    public static final Interpolator TOUCH_RESPONSE =
+            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+
+    /**
+     * Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t
+     * goes from 1 to 0 instead of 0 to 1).
+     */
+    public static final Interpolator TOUCH_RESPONSE_REVERSE =
+            new PathInterpolator(0.9f, 0f, 0.7f, 1f);
+
+    /*
+     * ============================================================================================
+     * Functions / Utilities
+     * ============================================================================================
+     */
+
+    /**
+     * Calculate the amount of overshoot using an exponential falloff function with desired
+     * properties, where the overshoot smoothly transitions at the 1.0f boundary into the
+     * overshoot, retaining its acceleration.
+     *
+     * @param progress a progress value going from 0 to 1
+     * @param overshootAmount the amount > 0 of overshoot desired. A value of 0.1 means the max
+     *                        value of the overall progress will be at 1.1.
+     * @param overshootStart the point in (0,1] where the result should reach 1
+     * @return the interpolated overshoot
+     */
+    public static float getOvershootInterpolation(float progress, float overshootAmount,
+            float overshootStart) {
+        if (overshootAmount == 0.0f || overshootStart == 0.0f) {
+            throw new IllegalArgumentException("Invalid values for overshoot");
+        }
+        float b = MathUtils.log((overshootAmount + 1) / (overshootAmount)) / overshootStart;
+        return MathUtils.max(0.0f,
+                (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f));
+    }
+
+    /**
+     * Similar to {@link #getOvershootInterpolation(float, float, float)} but the overshoot
+     * starts immediately here, instead of first having a section of non-overshooting
+     *
+     * @param progress a progress value going from 0 to 1
+     */
+    public static float getOvershootInterpolation(float progress) {
+        return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
+    }
+
+    // Create the default emphasized interpolator
+    private static PathInterpolator createEmphasizedInterpolator() {
+        Path path = new Path();
+        // Doing the same as fast_out_extra_slow_in
+        path.moveTo(0f, 0f);
+        path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
+        path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
+        return new PathInterpolator(path);
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index f9c6841..0e2d23b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -195,8 +195,16 @@
             val out = ArrayList<RemoteAnimationTarget>()
             for (i in info.changes.indices) {
                 val change = info.changes[i]
-                val changeIsWallpaper = change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0
-                if (wallpapers != changeIsWallpaper) continue
+                if (change.hasFlags(TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+                    // For embedded container, when the parent Task is also in the transition, we
+                    // should only animate the parent Task.
+                    if (change.parent != null) continue
+                    // For embedded container without parent, we should only animate if it fills
+                    // the Task. Otherwise we may animate only partial of the Task.
+                    if (!change.hasFlags(TransitionInfo.FLAG_FILLS_TASK)) continue
+                }
+                // Check if it is wallpaper
+                if (wallpapers != change.hasFlags(TransitionInfo.FLAG_IS_WALLPAPER)) continue
                 out.add(createTarget(change, info.changes.size - i, info, t))
                 if (leashMap != null) {
                     leashMap[change.leash] = out[out.size - 1].leash
@@ -320,9 +328,7 @@
                                 counterWallpaper.cleanUp(finishTransaction)
                                 // Release surface references now. This is apparently to free GPU
                                 // memory while doing quick operations (eg. during CTS).
-                                for (i in info.changes.indices.reversed()) {
-                                    info.changes[i].leash.release()
-                                }
+                                info.releaseAllSurfaces()
                                 for (i in leashMap.size - 1 downTo 0) {
                                     leashMap.valueAt(i).release()
                                 }
@@ -331,6 +337,7 @@
                                         null /* wct */,
                                         finishTransaction
                                     )
+                                    finishTransaction.close()
                                 } catch (e: RemoteException) {
                                     Log.e(
                                         "ActivityOptionsCompat",
@@ -364,6 +371,9 @@
                 ) {
                     // TODO: hook up merge to recents onTaskAppeared if applicable. Until then,
                     //       ignore any incoming merges.
+                    // Clean up stuff though cuz GC takes too long for benchmark tests.
+                    t.close()
+                    info.releaseAllSurfaces()
                 }
             }
         }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index ecee598..964ef8c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -28,7 +28,7 @@
     private val source: View,
     override val cuj: DialogCuj?,
 ) : DialogLaunchAnimator.Controller {
-    override val viewRoot: ViewRootImpl
+    override val viewRoot: ViewRootImpl?
         get() = source.viewRootImpl
 
     override val sourceIdentity: Any = source
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
new file mode 100644
index 0000000..93e78ac
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/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.surfaceeffects.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/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
new file mode 100644
index 0000000..b8dc223
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.surfaceeffects.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
+import androidx.annotation.VisibleForTesting
+
+/**
+ * A view that allows multiple ripples to play.
+ *
+ * Use [MultiRippleController] to play ripple animations.
+ */
+class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    val ripples = ArrayList<RippleAnimation>()
+    private val listeners = ArrayList<RipplesFinishedListener>()
+    private val ripplePaint = Paint()
+    private var isWarningLogged = false
+
+    companion object {
+        private const val TAG = "MultiRippleView"
+
+        interface RipplesFinishedListener {
+            /** Triggered when all the ripples finish running. */
+            fun onRipplesFinish()
+        }
+    }
+
+    fun addRipplesFinishedListener(listener: RipplesFinishedListener) {
+        listeners.add(listener)
+    }
+
+    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()
+        } else { // Nothing is playing.
+            listeners.forEach { listener -> listener.onRipplesFinish() }
+        }
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
new file mode 100644
index 0000000..0e3d41c
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.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.surfaceeffects.ripple
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import androidx.annotation.VisibleForTesting
+import androidx.core.graphics.ColorUtils
+
+/** A single ripple animation. */
+class RippleAnimation(private val config: RippleAnimationConfig) {
+    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    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/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
new file mode 100644
index 0000000..773ac55
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
@@ -0,0 +1,32 @@
+package com.android.systemui.surfaceeffects.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 = 115 // full opacity is 255.
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
new file mode 100644
index 0000000..f55fb97
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -0,0 +1,240 @@
+/*
+ * 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.surfaceeffects.ripple
+
+import android.graphics.PointF
+import android.graphics.RuntimeShader
+import android.util.MathUtils
+import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
+
+/**
+ * Shader class that renders an expanding ripple effect. The ripple contains three elements:
+ *
+ * 1. an expanding filled [RippleShape] that appears in the beginning and quickly fades away
+ * 2. an expanding ring that appears throughout the effect
+ * 3. an expanding ring-shaped area that reveals noise over #2.
+ *
+ * The ripple shader will be default to the circle shape if not specified.
+ *
+ * Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
+ */
+class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
+    RuntimeShader(buildShader(rippleShape)) {
+
+    /** Shapes that the [RippleShader] supports. */
+    enum class RippleShape {
+        CIRCLE,
+        ROUNDED_BOX,
+        ELLIPSE
+    }
+    // language=AGSL
+    companion object {
+        private const val SHADER_UNIFORMS =
+            """
+            uniform vec2 in_center;
+            uniform vec2 in_size;
+            uniform float in_progress;
+            uniform float in_cornerRadius;
+            uniform float in_thickness;
+            uniform float in_time;
+            uniform float in_distort_radial;
+            uniform float in_distort_xy;
+            uniform float in_fadeSparkle;
+            uniform float in_fadeFill;
+            uniform float in_fadeRing;
+            uniform float in_blur;
+            uniform float in_pixelDensity;
+            layout(color) uniform vec4 in_color;
+            uniform float in_sparkle_strength;
+        """
+
+        private const val SHADER_CIRCLE_MAIN =
+            """
+            vec4 main(vec2 p) {
+                vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
+                float radius = in_size.x * 0.5;
+                float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
+                float inside = soften(sdCircle(p_distorted-in_center, radius * 1.2), in_blur);
+                float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
+                    * (1.-sparkleRing) * in_fadeSparkle;
+
+                float rippleInsideAlpha = (1.-inside) * in_fadeFill;
+                float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
+                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
+                vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
+                return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
+            }
+        """
+
+        private const val SHADER_ROUNDED_BOX_MAIN =
+            """
+            vec4 main(vec2 p) {
+                float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
+                    in_thickness), in_blur);
+                float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
+                    in_blur);
+                float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
+                    * (1.-sparkleRing) * in_fadeSparkle;
+
+                float rippleInsideAlpha = (1.-inside) * in_fadeFill;
+                float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
+                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
+                vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
+                return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
+            }
+        """
+
+        private const val SHADER_ELLIPSE_MAIN =
+            """
+            vec4 main(vec2 p) {
+                vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
+
+                float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
+                float inside = soften(sdEllipse(p_distorted-in_center, in_size * 1.2), in_blur);
+                float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
+                    * (1.-sparkleRing) * in_fadeSparkle;
+
+                float rippleInsideAlpha = (1.-inside) * in_fadeFill;
+                float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
+                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
+                vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
+                return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
+            }
+        """
+
+        private const val CIRCLE_SHADER =
+            SHADER_UNIFORMS +
+                ShaderUtilLibrary.SHADER_LIB +
+                SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+                SdfShaderLibrary.CIRCLE_SDF +
+                SHADER_CIRCLE_MAIN
+        private const val ROUNDED_BOX_SHADER =
+            SHADER_UNIFORMS +
+                ShaderUtilLibrary.SHADER_LIB +
+                SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+                SdfShaderLibrary.ROUNDED_BOX_SDF +
+                SHADER_ROUNDED_BOX_MAIN
+        private const val ELLIPSE_SHADER =
+            SHADER_UNIFORMS +
+                ShaderUtilLibrary.SHADER_LIB +
+                SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+                SdfShaderLibrary.ELLIPSE_SDF +
+                SHADER_ELLIPSE_MAIN
+
+        private fun buildShader(rippleShape: RippleShape): String =
+            when (rippleShape) {
+                RippleShape.CIRCLE -> CIRCLE_SHADER
+                RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
+                RippleShape.ELLIPSE -> ELLIPSE_SHADER
+            }
+
+        private fun subProgress(start: Float, end: Float, progress: Float): Float {
+            val min = Math.min(start, end)
+            val max = Math.max(start, end)
+            val sub = Math.min(Math.max(progress, min), max)
+            return (sub - start) / (end - start)
+        }
+    }
+
+    /** Sets the center position of the ripple. */
+    fun setCenter(x: Float, y: Float) {
+        setFloatUniform("in_center", x, y)
+    }
+
+    /** Max width of the ripple. */
+    private var maxSize: PointF = PointF()
+    fun setMaxSize(width: Float, height: Float) {
+        maxSize.x = width
+        maxSize.y = height
+    }
+
+    /** Progress of the ripple. Float value between [0, 1]. */
+    var progress: Float = 0.0f
+        set(value) {
+            field = value
+            setFloatUniform("in_progress", value)
+            val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
+
+            setFloatUniform(
+                "in_size",
+                /* width= */ maxSize.x * curvedProg,
+                /* height= */ maxSize.y * curvedProg
+            )
+            setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
+            // radius should not exceed width and height values.
+            setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * curvedProg)
+
+            setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
+
+            val fadeIn = subProgress(0f, 0.1f, value)
+            val fadeOutNoise = subProgress(0.4f, 1f, value)
+            var fadeOutRipple = 0f
+            var fadeFill = 0f
+            if (!rippleFill) {
+                fadeFill = subProgress(0f, 0.6f, value)
+                fadeOutRipple = subProgress(0.3f, 1f, value)
+            }
+            setFloatUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
+            setFloatUniform("in_fadeFill", 1 - fadeFill)
+            setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
+        }
+
+    /** Play time since the start of the effect. */
+    var time: Float = 0.0f
+        set(value) {
+            field = value
+            setFloatUniform("in_time", value)
+        }
+
+    /** A hex value representing the ripple color, in the format of ARGB */
+    var color: Int = 0xffffff
+        set(value) {
+            field = value
+            setColorUniform("in_color", value)
+        }
+
+    /**
+     * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus with
+     * strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1 it's
+     * opaque white and looks the most grainy.
+     */
+    var sparkleStrength: Float = 0.0f
+        set(value) {
+            field = value
+            setFloatUniform("in_sparkle_strength", value)
+        }
+
+    /** Distortion strength of the ripple. Expected value between[0, 1]. */
+    var distortionStrength: Float = 0.0f
+        set(value) {
+            field = value
+            setFloatUniform("in_distort_radial", 75 * progress * value)
+            setFloatUniform("in_distort_xy", 75 * value)
+        }
+
+    var pixelDensity: Float = 1.0f
+        set(value) {
+            field = value
+            setFloatUniform("in_pixelDensity", value)
+        }
+
+    /**
+     * True if the ripple should stayed filled in as it expands to give a filled-in circle effect.
+     * False for a ring effect.
+     */
+    var rippleFill: Boolean = false
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
new file mode 100644
index 0000000..ae28a8b
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -0,0 +1,173 @@
+/*
+ * 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.surfaceeffects.ripple
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.util.AttributeSet
+import android.view.View
+import androidx.core.graphics.ColorUtils
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape
+
+/**
+ * A generic expanding ripple effect.
+ *
+ * Set up the shader with a desired [RippleShape] using [setupShader], [setMaxSize] and [setCenter],
+ * then call [startRipple] to trigger the ripple expansion.
+ */
+open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+
+    private lateinit var rippleShader: RippleShader
+    lateinit var rippleShape: RippleShape
+        private set
+
+    private val ripplePaint = Paint()
+    protected val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
+
+    var duration: Long = 1750
+
+    private var maxWidth: Float = 0.0f
+    private var maxHeight: Float = 0.0f
+    fun setMaxSize(maxWidth: Float, maxHeight: Float) {
+        this.maxWidth = maxWidth
+        this.maxHeight = maxHeight
+        rippleShader.setMaxSize(maxWidth, maxHeight)
+    }
+
+    private var centerX: Float = 0.0f
+    private var centerY: Float = 0.0f
+    fun setCenter(x: Float, y: Float) {
+        this.centerX = x
+        this.centerY = y
+        rippleShader.setCenter(x, y)
+    }
+
+    override fun onConfigurationChanged(newConfig: Configuration?) {
+        rippleShader.pixelDensity = resources.displayMetrics.density
+        super.onConfigurationChanged(newConfig)
+    }
+
+    override fun onAttachedToWindow() {
+        rippleShader.pixelDensity = resources.displayMetrics.density
+        super.onAttachedToWindow()
+    }
+
+    /** Initializes the shader. Must be called before [startRipple]. */
+    fun setupShader(rippleShape: RippleShape = RippleShape.CIRCLE) {
+        this.rippleShape = rippleShape
+        rippleShader = RippleShader(rippleShape)
+
+        rippleShader.color = RippleAnimationConfig.RIPPLE_DEFAULT_COLOR
+        rippleShader.progress = 0f
+        rippleShader.sparkleStrength = RippleAnimationConfig.RIPPLE_SPARKLE_STRENGTH
+        rippleShader.pixelDensity = resources.displayMetrics.density
+
+        ripplePaint.shader = rippleShader
+    }
+
+    @JvmOverloads
+    fun startRipple(onAnimationEnd: Runnable? = null) {
+        if (animator.isRunning) {
+            return // Ignore if ripple effect is already playing
+        }
+        animator.duration = duration
+        animator.addUpdateListener { updateListener ->
+            val now = updateListener.currentPlayTime
+            val progress = updateListener.animatedValue as Float
+            rippleShader.progress = progress
+            rippleShader.distortionStrength = 1 - progress
+            rippleShader.time = now.toFloat()
+            invalidate()
+        }
+        animator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator?) {
+                    onAnimationEnd?.run()
+                }
+            }
+        )
+        animator.start()
+    }
+
+    /**
+     * 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-255].
+     */
+    fun setColor(color: Int, alpha: Int = RippleAnimationConfig.RIPPLE_DEFAULT_ALPHA) {
+        rippleShader.color = ColorUtils.setAlphaComponent(color, alpha)
+    }
+
+    /**
+     * Set whether the ripple should remain filled as the ripple expands.
+     *
+     * See [RippleShader.rippleFill].
+     */
+    fun setRippleFill(rippleFill: Boolean) {
+        rippleShader.rippleFill = rippleFill
+    }
+
+    /** Set the intensity of the sparkles. */
+    fun setSparkleStrength(strength: Float) {
+        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
+            // if it's unsupported.
+            return
+        }
+        // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the
+        // active effect area. Values here should be kept in sync with the animation implementation
+        // in the ripple shader.
+        if (rippleShape == RippleShape.CIRCLE) {
+            val maskRadius =
+                (1 -
+                    (1 - rippleShader.progress) *
+                        (1 - rippleShader.progress) *
+                        (1 - rippleShader.progress)) * maxWidth
+            canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
+        } else {
+            val maskWidth =
+                (1 -
+                    (1 - rippleShader.progress) *
+                        (1 - rippleShader.progress) *
+                        (1 - rippleShader.progress)) * maxWidth * 2
+            val maskHeight =
+                (1 -
+                    (1 - rippleShader.progress) *
+                        (1 - rippleShader.progress) *
+                        (1 - rippleShader.progress)) * maxHeight * 2
+            canvas.drawRect(
+                /* left= */ centerX - maskWidth,
+                /* top= */ centerY - maskHeight,
+                /* right= */ centerX + maskWidth,
+                /* bottom= */ centerY + maskHeight,
+                ripplePaint
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
new file mode 100644
index 0000000..8b2f466
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.surfaceeffects.shaderutil
+
+/** Library class that contains 2D signed distance functions. */
+class SdfShaderLibrary {
+    // language=AGSL
+    companion object {
+        const val CIRCLE_SDF =
+            """
+            float sdCircle(vec2 p, float r) {
+                return (length(p)-r) / r;
+            }
+
+            float circleRing(vec2 p, float radius) {
+                float thicknessHalf = radius * 0.25;
+
+                float outerCircle = sdCircle(p, radius + thicknessHalf);
+                float innerCircle = sdCircle(p, radius);
+
+                return subtract(outerCircle, innerCircle);
+            }
+        """
+
+        const val ROUNDED_BOX_SDF =
+            """
+            float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) {
+                size *= 0.5;
+                cornerRadius *= 0.5;
+                vec2 d = abs(p)-size+cornerRadius;
+
+                float outside = length(max(d, 0.0));
+                float inside = min(max(d.x, d.y), 0.0);
+
+                return (outside+inside-cornerRadius)/size.y;
+            }
+
+            float roundedBoxRing(vec2 p, vec2 size, float cornerRadius,
+                float borderThickness) {
+                float outerRoundBox = sdRoundedBox(p, size, cornerRadius);
+                float innerRoundBox = sdRoundedBox(p, size - vec2(borderThickness),
+                    cornerRadius - borderThickness);
+                return subtract(outerRoundBox, innerRoundBox);
+            }
+        """
+
+        // Used non-trigonometry parametrization and Halley's method (iterative) for root finding.
+        // This is more expensive than the regular circle SDF, recommend to use the circle SDF if
+        // possible.
+        const val ELLIPSE_SDF =
+            """float sdEllipse(vec2 p, vec2 wh) {
+            wh *= 0.5;
+
+            // symmetry
+            (wh.x > wh.y) ? wh = wh.yx, p = abs(p.yx) : p = abs(p);
+
+            vec2 u = wh*p, v = wh*wh;
+
+            float U1 = u.y/2.0;  float U5 = 4.0*U1;
+            float U2 = v.y-v.x;  float U6 = 6.0*U1;
+            float U3 = u.x-U2;   float U7 = 3.0*U3;
+            float U4 = u.x+U2;
+
+            float t = 0.5;
+            for (int i = 0; i < 3; i ++) {
+                float F1 = t*(t*t*(U1*t+U3)+U4)-U1;
+                float F2 = t*t*(U5*t+U7)+U4;
+                float F3 = t*(U6*t+U7);
+
+                t += (F1*F2)/(F1*F3-F2*F2);
+            }
+
+            t = clamp(t, 0.0, 1.0);
+
+            float d = distance(p, wh*vec2(1.0-t*t,2.0*t)/(t*t+1.0));
+            d /= wh.y;
+
+            return (dot(p/wh,p/wh)>1.0) ? d : -d;
+        }
+
+        float ellipseRing(vec2 p, vec2 wh) {
+            vec2 thicknessHalf = wh * 0.25;
+
+            float outerEllipse = sdEllipse(p, wh + thicknessHalf);
+            float innerEllipse = sdEllipse(p, wh);
+
+            return subtract(outerEllipse, innerEllipse);
+        }
+        """
+
+        const val SHADER_SDF_OPERATION_LIB =
+            """
+            float soften(float d, float blur) {
+                float blurHalf = blur * 0.5;
+                return smoothstep(-blurHalf, blurHalf, d);
+            }
+
+            float subtract(float outer, float inner) {
+                return max(outer, -inner);
+            }
+        """
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
new file mode 100644
index 0000000..d78e0c1
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
@@ -0,0 +1,148 @@
+/*
+ * 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.surfaceeffects.shaderutil
+
+/** A common utility functions that are used for computing shaders. */
+class ShaderUtilLibrary {
+    // language=AGSL
+    companion object {
+        const val SHADER_LIB =
+            """
+            float triangleNoise(vec2 n) {
+                n  = fract(n * vec2(5.3987, 5.4421));
+                n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
+                float xy = n.x * n.y;
+                // compute in [0..2[ and remap to [-1.0..1.0[
+                return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
+            }
+
+            const float PI = 3.1415926535897932384626;
+
+            float sparkles(vec2 uv, float t) {
+                float n = triangleNoise(uv);
+                float s = 0.0;
+                for (float i = 0; i < 4; i += 1) {
+                    float l = i * 0.01;
+                    float h = l + 0.1;
+                    float o = smoothstep(n - l, h, n);
+                    o *= abs(sin(PI * o * (t + 0.55 * i)));
+                    s += o;
+                }
+                return s;
+            }
+
+            vec2 distort(vec2 p, float time, float distort_amount_radial,
+                float distort_amount_xy) {
+                    float angle = atan(p.y, p.x);
+                      return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
+                                cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
+                         + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
+                                cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
+            }
+
+            // Return range [-1, 1].
+            vec3 hash(vec3 p) {
+                p = fract(p * vec3(.3456, .1234, .9876));
+                p += dot(p, p.yxz + 43.21);
+                p = (p.xxy + p.yxx) * p.zyx;
+                return (fract(sin(p) * 4567.1234567) - .5) * 2.;
+            }
+
+            // Skew factors (non-uniform).
+            const float SKEW = 0.3333333;  // 1/3
+            const float UNSKEW = 0.1666667;  // 1/6
+
+            // Return range roughly [-1,1].
+            // It's because the hash function (that returns a random gradient vector) returns
+            // different magnitude of vectors. Noise doesn't have to be in the precise range thus
+            // skipped normalize.
+            float simplex3d(vec3 p) {
+                // Skew the input coordinate, so that we get squashed cubical grid
+                vec3 s = floor(p + (p.x + p.y + p.z) * SKEW);
+
+                // Unskew back
+                vec3 u = s - (s.x + s.y + s.z) * UNSKEW;
+
+                // Unskewed coordinate that is relative to p, to compute the noise contribution
+                // based on the distance.
+                vec3 c0 = p - u;
+
+                // We have six simplices (in this case tetrahedron, since we are in 3D) that we
+                // could possibly in.
+                // Here, we are finding the correct tetrahedron (simplex shape), and traverse its
+                // four vertices (c0..3) when computing noise contribution.
+                // The way we find them is by comparing c0's x,y,z values.
+                // For example in 2D, we can find the triangle (simplex shape in 2D) that we are in
+                // by comparing x and y values. i.e. x>y lower, x<y, upper triangle.
+                // Same applies in 3D.
+                //
+                // Below indicates the offsets (or offset directions) when c0=(x0,y0,z0)
+                // x0>y0>z0: (1,0,0), (1,1,0), (1,1,1)
+                // x0>z0>y0: (1,0,0), (1,0,1), (1,1,1)
+                // z0>x0>y0: (0,0,1), (1,0,1), (1,1,1)
+                // z0>y0>x0: (0,0,1), (0,1,1), (1,1,1)
+                // y0>z0>x0: (0,1,0), (0,1,1), (1,1,1)
+                // y0>x0>z0: (0,1,0), (1,1,0), (1,1,1)
+                //
+                // The rule is:
+                // * For offset1, set 1 at the max component, otherwise 0.
+                // * For offset2, set 0 at the min component, otherwise 1.
+                // * For offset3, set 1 for all.
+                //
+                // Encode x0-y0, y0-z0, z0-x0 in a vec3
+                vec3 en = c0 - c0.yzx;
+                // Each represents whether x0>y0, y0>z0, z0>x0
+                en = step(vec3(0.), en);
+                // en.zxy encodes z0>x0, x0>y0, y0>x0
+                vec3 offset1 = en * (1. - en.zxy); // find max
+                vec3 offset2 = 1. - en.zxy * (1. - en); // 1-(find min)
+                vec3 offset3 = vec3(1.);
+
+                vec3 c1 = c0 - offset1 + UNSKEW;
+                vec3 c2 = c0 - offset2 + UNSKEW * 2.;
+                vec3 c3 = c0 - offset3 + UNSKEW * 3.;
+
+                // Kernel summation: dot(max(0, r^2-d^2))^4, noise contribution)
+                //
+                // First compute d^2, squared distance to the point.
+                vec4 w; // w = max(0, r^2 - d^2))
+                w.x = dot(c0, c0);
+                w.y = dot(c1, c1);
+                w.z = dot(c2, c2);
+                w.w = dot(c3, c3);
+
+                // Noise contribution should decay to zero before they cross the simplex boundary.
+                // Usually r^2 is 0.5 or 0.6;
+                // 0.5 ensures continuity but 0.6 increases the visual quality for the application
+                // where discontinuity isn't noticeable.
+                w = max(0.6 - w, 0.);
+
+                // Noise contribution from each point.
+                vec4 nc;
+                nc.x = dot(hash(s), c0);
+                nc.y = dot(hash(s + offset1), c1);
+                nc.z = dot(hash(s + offset2), c2);
+                nc.w = dot(hash(s + offset3), c3);
+
+                nc *= w*w*w*w;
+
+                // Add all the noise contributions.
+                // Should multiply by the possible max contribution to adjust the range in [-1,1].
+                return dot(vec4(32.), nc);
+            }
+            """
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
new file mode 100644
index 0000000..5ac3aad7
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.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.surfaceeffects.turbulencenoise
+
+import android.graphics.BlendMode
+import android.graphics.Color
+
+/** Turbulence noise animation configuration. */
+data class TurbulenceNoiseAnimationConfig(
+    /** The number of grids that is used to generate noise. */
+    val gridCount: Float = DEFAULT_NOISE_GRID_COUNT,
+
+    /** Multiplier for the noise luma matte. Increase this for brighter effects. */
+    val luminosityMultiplier: Float = DEFAULT_LUMINOSITY_MULTIPLIER,
+
+    /**
+     * Noise move speed variables.
+     *
+     * Its sign determines the direction; magnitude determines the speed. <ul>
+     * ```
+     *     <li> [noiseMoveSpeedX] positive: right to left; negative: left to right.
+     *     <li> [noiseMoveSpeedY] positive: bottom to top; negative: top to bottom.
+     *     <li> [noiseMoveSpeedZ] its sign doesn't matter much, as it moves in Z direction. Use it
+     *     to add turbulence in place.
+     * ```
+     * </ul>
+     */
+    val noiseMoveSpeedX: Float = 0f,
+    val noiseMoveSpeedY: Float = 0f,
+    val noiseMoveSpeedZ: Float = DEFAULT_NOISE_SPEED_Z,
+
+    /** Color of the effect. */
+    var color: Int = DEFAULT_COLOR,
+    /** Background color of the effect. */
+    val backgroundColor: Int = DEFAULT_BACKGROUND_COLOR,
+    val opacity: Int = DEFAULT_OPACITY,
+    val width: Float = 0f,
+    val height: Float = 0f,
+    val duration: Float = DEFAULT_NOISE_DURATION_IN_MILLIS,
+    val pixelDensity: Float = 1f,
+    val blendMode: BlendMode = DEFAULT_BLEND_MODE,
+    val onAnimationEnd: Runnable? = null
+) {
+    companion object {
+        const val DEFAULT_NOISE_DURATION_IN_MILLIS = 7500F
+        const val DEFAULT_LUMINOSITY_MULTIPLIER = 1f
+        const val DEFAULT_NOISE_GRID_COUNT = 1.2f
+        const val DEFAULT_NOISE_SPEED_Z = 0.3f
+        const val DEFAULT_OPACITY = 150 // full opacity is 255.
+        const val DEFAULT_COLOR = Color.WHITE
+        const val DEFAULT_BACKGROUND_COLOR = Color.BLACK
+        val DEFAULT_BLEND_MODE = BlendMode.SRC_OVER
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
new file mode 100644
index 0000000..4c7e5f4
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.surfaceeffects.turbulencenoise
+
+/** A controller that plays [TurbulenceNoiseView]. */
+class TurbulenceNoiseController(private val turbulenceNoiseView: TurbulenceNoiseView) {
+    /** Updates the color of the noise. */
+    fun updateNoiseColor(color: Int) {
+        turbulenceNoiseView.updateColor(color)
+    }
+
+    // TODO: add cancel and/ or pause once design requirements become clear.
+    /** Plays [TurbulenceNoiseView] with the given config. */
+    fun play(turbulenceNoiseAnimationConfig: TurbulenceNoiseAnimationConfig) {
+        turbulenceNoiseView.play(turbulenceNoiseAnimationConfig)
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
new file mode 100644
index 0000000..19c114d
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.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.systemui.surfaceeffects.turbulencenoise
+
+import android.graphics.RuntimeShader
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
+import java.lang.Float.max
+
+/** Shader that renders turbulence simplex noise, with no octave. */
+class TurbulenceNoiseShader : RuntimeShader(TURBULENCE_NOISE_SHADER) {
+    // language=AGSL
+    companion object {
+        private const val UNIFORMS =
+            """
+            uniform float in_gridNum;
+            uniform vec3 in_noiseMove;
+            uniform vec2 in_size;
+            uniform float in_aspectRatio;
+            uniform float in_opacity;
+            uniform float in_pixelDensity;
+            layout(color) uniform vec4 in_color;
+            layout(color) uniform vec4 in_backgroundColor;
+        """
+
+        private const val SHADER_LIB =
+            """
+            float getLuminosity(vec3 c) {
+                return 0.3*c.r + 0.59*c.g + 0.11*c.b;
+            }
+
+            vec3 maskLuminosity(vec3 dest, float lum) {
+                dest.rgb *= vec3(lum);
+                // Clip back into the legal range
+                dest = clamp(dest, vec3(0.), vec3(1.0));
+                return dest;
+            }
+        """
+
+        private const val MAIN_SHADER =
+            """
+            vec4 main(vec2 p) {
+                vec2 uv = p / in_size.xy;
+                uv.x *= in_aspectRatio;
+
+                vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
+                float luma = simplex3d(noiseP) * in_opacity;
+                vec3 mask = maskLuminosity(in_color.rgb, luma);
+                vec3 color = in_backgroundColor.rgb + mask * 0.6;
+
+                // Add dither with triangle distribution to avoid color banding. Ok to dither in the
+                // shader here as we are in gamma space.
+                float dither = triangleNoise(p * in_pixelDensity) / 255.;
+
+                // The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to
+                // multiply rgb with a to get the correct result.
+                color = (color + dither.rrr) * in_color.a;
+                return vec4(color, in_color.a);
+            }
+        """
+
+        private const val TURBULENCE_NOISE_SHADER =
+            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SHADER_LIB + MAIN_SHADER
+    }
+
+    /** Sets the number of grid for generating noise. */
+    fun setGridCount(gridNumber: Float = 1.0f) {
+        setFloatUniform("in_gridNum", gridNumber)
+    }
+
+    /**
+     * Sets the pixel density of the screen.
+     *
+     * Used it for noise dithering.
+     */
+    fun setPixelDensity(pixelDensity: Float) {
+        setFloatUniform("in_pixelDensity", pixelDensity)
+    }
+
+    /** Sets the noise color of the effect. */
+    fun setColor(color: Int) {
+        setColorUniform("in_color", color)
+    }
+
+    /** Sets the background color of the effect. */
+    fun setBackgroundColor(color: Int) {
+        setColorUniform("in_backgroundColor", color)
+    }
+
+    /**
+     * Sets the opacity to achieve fade in/ out of the animation.
+     *
+     * Expected value range is [1, 0].
+     */
+    fun setOpacity(opacity: Float) {
+        setFloatUniform("in_opacity", opacity)
+    }
+
+    /** Sets the size of the shader. */
+    fun setSize(width: Float, height: Float) {
+        setFloatUniform("in_size", width, height)
+        setFloatUniform("in_aspectRatio", width / max(height, 0.001f))
+    }
+
+    /** Sets noise move speed in x, y, and z direction. */
+    fun setNoiseMove(x: Float, y: Float, z: Float) {
+        setFloatUniform("in_noiseMove", x, y, z)
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
new file mode 100644
index 0000000..68712c6
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.surfaceeffects.turbulencenoise
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.util.AttributeSet
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.core.graphics.ColorUtils
+import java.util.Random
+import kotlin.math.sin
+
+/** View that renders turbulence noise effect. */
+class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+
+    companion object {
+        private const val MS_TO_SEC = 0.001f
+        private const val TWO_PI = Math.PI.toFloat() * 2f
+    }
+
+    @VisibleForTesting val turbulenceNoiseShader = TurbulenceNoiseShader()
+    private val paint = Paint().apply { this.shader = turbulenceNoiseShader }
+    private val random = Random()
+    private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
+    private var config: TurbulenceNoiseAnimationConfig? = null
+
+    val isPlaying: Boolean
+        get() = animator.isRunning
+
+    init {
+        // Only visible during the animation.
+        visibility = INVISIBLE
+    }
+
+    /** Updates the color during the animation. No-op if there's no animation playing. */
+    fun updateColor(color: Int) {
+        config?.let {
+            it.color = color
+            applyConfig(it)
+        }
+    }
+
+    override fun onDraw(canvas: Canvas?) {
+        if (canvas == null || !canvas.isHardwareAccelerated) {
+            // Drawing with the turbulence noise shader requires hardware acceleration, so skip
+            // if it's unsupported.
+            return
+        }
+
+        canvas.drawPaint(paint)
+    }
+
+    fun play(config: TurbulenceNoiseAnimationConfig) {
+        if (isPlaying) {
+            return // Ignore if the animation is playing.
+        }
+        visibility = VISIBLE
+        applyConfig(config)
+
+        // Add random offset to avoid same patterned noise.
+        val offsetX = random.nextFloat()
+        val offsetY = random.nextFloat()
+
+        animator.duration = config.duration.toLong()
+        animator.addUpdateListener { updateListener ->
+            val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
+            // Remap [0,1] to [0, 2*PI]
+            val progress = TWO_PI * updateListener.animatedValue as Float
+
+            turbulenceNoiseShader.setNoiseMove(
+                offsetX + timeInSec * config.noiseMoveSpeedX,
+                offsetY + timeInSec * config.noiseMoveSpeedY,
+                timeInSec * config.noiseMoveSpeedZ
+            )
+
+            // Fade in and out the noise as the animation progress.
+            // TODO: replace it with a better curve
+            turbulenceNoiseShader.setOpacity(sin(TWO_PI - progress) * config.luminosityMultiplier)
+
+            invalidate()
+        }
+
+        animator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    visibility = INVISIBLE
+                    config.onAnimationEnd?.run()
+                }
+            }
+        )
+        animator.start()
+    }
+
+    private fun applyConfig(config: TurbulenceNoiseAnimationConfig) {
+        this.config = config
+        with(turbulenceNoiseShader) {
+            setGridCount(config.gridCount)
+            setColor(ColorUtils.setAlphaComponent(config.color, config.opacity))
+            setBackgroundColor(config.backgroundColor)
+            setSize(config.width, config.height)
+            setPixelDensity(config.pixelDensity)
+        }
+        paint.blendMode = config.blendMode
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
index 1d808ba..74e6d85 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
@@ -59,11 +59,11 @@
                     !hasWorkerThreadAnnotation(context, node.getParentOfType(UClass::class.java))
             ) {
                 context.report(
-                    ISSUE,
-                    method,
-                    context.getLocation(node),
-                    "This method should be annotated with `@WorkerThread` because " +
-                        "it calls ${method.name}",
+                    issue = ISSUE,
+                    location = context.getLocation(node),
+                    message =
+                        "This method should be annotated with `@WorkerThread` because " +
+                            "it calls ${method.name}",
                 )
             }
         }
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
index 1129929..344d0a3 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
@@ -52,10 +52,9 @@
         val evaluator = context.evaluator
         if (evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
             context.report(
-                    ISSUE,
-                    method,
-                    context.getNameLocation(node),
-                    "`Context.${method.name}()` should be replaced with " +
+                    issue = ISSUE,
+                    location = context.getNameLocation(node),
+                    message = "`Context.${method.name}()` should be replaced with " +
                     "`BroadcastSender.${method.name}()`"
             )
         }
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
index bab76ab..14099eb 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
@@ -38,10 +38,9 @@
     override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
         if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
             context.report(
-                ISSUE,
-                method,
-                context.getNameLocation(node),
-                "Replace with injected `@Main Executor`."
+                issue = ISSUE,
+                location = context.getNameLocation(node),
+                message = "Replace with injected `@Main Executor`."
             )
         }
     }
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
index b622900..aa4b2f7 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
@@ -44,11 +44,11 @@
                 method.containingClass?.qualifiedName == CLASS_CONTEXT
         ) {
             context.report(
-                ISSUE,
-                method,
-                context.getNameLocation(node),
-                "Use `@Inject` to get system-level service handles instead of " +
-                    "`Context.getSystemService()`"
+                issue = ISSUE,
+                location = context.getNameLocation(node),
+                message =
+                    "Use `@Inject` to get system-level service handles instead of " +
+                        "`Context.getSystemService()`"
             )
         } else if (
             evaluator.isStatic(method) &&
@@ -56,10 +56,10 @@
                 method.containingClass?.qualifiedName == "android.accounts.AccountManager"
         ) {
             context.report(
-                ISSUE,
-                method,
-                context.getNameLocation(node),
-                "Replace `AccountManager.get()` with an injected instance of `AccountManager`"
+                issue = ISSUE,
+                location = context.getNameLocation(node),
+                message =
+                    "Replace `AccountManager.get()` with an injected instance of `AccountManager`"
             )
         }
     }
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index 4ba3afc..5840e8f 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -38,10 +38,10 @@
     override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
         if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
             context.report(
-                    ISSUE,
-                    method,
-                    context.getNameLocation(node),
-                    "Register `BroadcastReceiver` using `BroadcastDispatcher` instead of `Context`"
+                    issue = ISSUE,
+                    location = context.getNameLocation(node),
+                    message = "Register `BroadcastReceiver` using `BroadcastDispatcher` instead " +
+                    "of `Context`"
             )
         }
     }
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
index 7be21a5..b15a41b 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
@@ -46,10 +46,10 @@
                 method.containingClass?.qualifiedName == "android.app.ActivityManager"
         ) {
             context.report(
-                ISSUE_SLOW_USER_ID_QUERY,
-                method,
-                context.getNameLocation(node),
-                "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`"
+                issue = ISSUE_SLOW_USER_ID_QUERY,
+                location = context.getNameLocation(node),
+                message =
+                    "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`"
             )
         }
         if (
@@ -58,10 +58,9 @@
                 method.containingClass?.qualifiedName == "android.os.UserManager"
         ) {
             context.report(
-                ISSUE_SLOW_USER_INFO_QUERY,
-                method,
-                context.getNameLocation(node),
-                "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`"
+                issue = ISSUE_SLOW_USER_INFO_QUERY,
+                location = context.getNameLocation(node),
+                message = "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`"
             )
         }
     }
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
index 4b9aa13..bf02589 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
@@ -44,10 +44,9 @@
         val evaluator = context.evaluator
         if (evaluator.isMemberInClass(referenced as? PsiField, "android.graphics.Bitmap.Config")) {
             context.report(
-                    ISSUE,
-                    referenced,
-                    context.getNameLocation(reference),
-                    "Replace software bitmap with `Config.HARDWARE`"
+                    issue = ISSUE,
+                    location = context.getNameLocation(reference),
+                    message = "Replace software bitmap with `Config.HARDWARE`"
             )
         }
     }
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
index 1db0725..22f15bd 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
@@ -66,10 +66,9 @@
         val subclassName = className.substring(CLASS_SETTINGS.length + 1)
 
         context.report(
-            ISSUE,
-            method,
-            context.getNameLocation(node),
-            "`@Inject` a ${subclassName}Settings instead"
+            issue = ISSUE,
+            location = context.getNameLocation(node),
+            message = "`@Inject` a ${subclassName}Settings instead"
         )
     }
 
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
index c35ac61..426211e 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
@@ -126,6 +126,32 @@
     }
 
     @Test
+    fun testSuppressUnbindService() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+                    import android.content.ServiceConnection;
+
+                    @SuppressLint("BindServiceOnMainThread")
+                    public class TestClass {
+                        public void unbind(Context context, ServiceConnection connection) {
+                          context.unbindService(connection);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(BindServiceOnMainThreadDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
     fun testWorkerMethod() {
         lint()
             .files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index 376acb5..30b68f7 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -129,6 +129,34 @@
     }
 
     @Test
+    fun testSuppressSendBroadcastInActivity() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.app.Activity;
+                    import android.os.UserHandle;
+
+                    public class TestClass {
+                        @SuppressWarnings("BroadcastSentViaContext")
+                        public void send(Activity activity) {
+                          Intent intent = new Intent(Intent.ACTION_VIEW);
+                          activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission");
+                        }
+
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(BroadcastSentViaContextDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
     fun testSendBroadcastInBroadcastSender() {
         lint()
             .files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index 301c338..ed3d14a 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
@@ -61,6 +61,32 @@
     }
 
     @Test
+    fun testSuppressGetMainThreadHandler() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+                    import android.os.Handler;
+
+                    @SuppressWarnings("NonInjectedMainThread")
+                    public class TestClass {
+                        public void test(Context context) {
+                          Handler mainThreadHandler = context.getMainThreadHandler();
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(NonInjectedMainThreadDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
     fun testGetMainLooper() {
         lint()
             .files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
index 0a74bfc..846129a 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -91,6 +91,32 @@
     }
 
     @Test
+    fun testSuppressGetServiceWithClass() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                        package test.pkg;
+                        import android.content.Context;
+                        import android.os.UserManager;
+
+                        public class TestClass {
+                            @SuppressLint("NonInjectedService")
+                            public void getSystemServiceWithoutDagger(Context context) {
+                                context.getSystemService(UserManager.class);
+                            }
+                        }
+                        """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(NonInjectedServiceDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
     fun testGetAccountManager() {
         lint()
             .files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index 9ed7aa0..0ac8f8e 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -63,6 +63,34 @@
     }
 
     @Test
+    fun testSuppressRegisterReceiver() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.BroadcastReceiver;
+                    import android.content.Context;
+                    import android.content.IntentFilter;
+
+                    @SuppressWarnings("RegisterReceiverViaContext")
+                    public class TestClass {
+                        public void bind(Context context, BroadcastReceiver receiver,
+                            IntentFilter filter) {
+                          context.registerReceiver(receiver, filter, 0);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterReceiverViaContextDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
     fun testRegisterReceiverAsUser() {
         lint()
             .files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index 54cac7b..34a4249 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -76,7 +76,7 @@
                         import android.os.UserManager;
 
                         public class TestClass {
-                            public void slewlyGetUserInfo(UserManager userManager) {
+                            public void slowlyGetUserInfo(UserManager userManager) {
                                 userManager.getUserInfo();
                             }
                         }
@@ -101,6 +101,34 @@
     }
 
     @Test
+    fun testSuppressGetUserInfo() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                        package test.pkg;
+                        import android.os.UserManager;
+
+                        public class TestClass {
+                            @SuppressWarnings("SlowUserInfoQuery")
+                            public void slowlyGetUserInfo(UserManager userManager) {
+                                userManager.getUserInfo();
+                            }
+                        }
+                        """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(
+                SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
+                SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
     fun testUserTrackerGetUserId() {
         lint()
             .files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index c632636..34becc6 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -63,6 +63,31 @@
     }
 
     @Test
+    fun testSuppressSoftwareBitmap() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                    import android.graphics.Bitmap;
+
+                    @SuppressWarnings("SoftwareBitmap")
+                    public class TestClass {
+                        public void test() {
+                          Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565);
+                          Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+                        }
+                    }
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(SoftwareBitmapDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
     fun testHardwareBitmap() {
         lint()
             .files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
index b83ed70..efe4c90 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
@@ -28,7 +28,7 @@
     override fun getIssues(): List<Issue> = listOf(StaticSettingsProviderDetector.ISSUE)
 
     @Test
-    fun testGetServiceWithString() {
+    fun testSuppressGetServiceWithString() {
         lint()
             .files(
                 TestFiles.java(
@@ -204,5 +204,34 @@
             )
     }
 
+    @Test
+    fun testGetServiceWithString() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                        package test.pkg;
+
+                        import android.provider.Settings;
+                        import android.provider.Settings.Global;
+                        import android.provider.Settings.Secure;
+
+                        public class TestClass {
+                            @SuppressWarnings("StaticSettingsProvider")
+                            public void getSystemServiceWithoutDagger(Context context) {
+                                final ContentResolver cr = mContext.getContentResolver();
+                                Global.getFloat(cr, Settings.Global.UNLOCK_SOUND);
+                            }
+                        }
+                        """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(StaticSettingsProviderDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
     private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
index 50c3d7e..d6db574 100644
--- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
@@ -262,7 +262,7 @@
 
     private fun dialogController(cuj: DialogCuj?): DialogLaunchAnimator.Controller {
         return object : DialogLaunchAnimator.Controller {
-            override val viewRoot: ViewRootImpl = composeViewRoot.viewRootImpl
+            override val viewRoot: ViewRootImpl? = composeViewRoot.viewRootImpl
             override val sourceIdentity: Any = this@ExpandableControllerImpl
             override val cuj: DialogCuj? = cuj
 
diff --git a/packages/SystemUI/compose/features/AndroidManifest.xml b/packages/SystemUI/compose/features/AndroidManifest.xml
index eada40e..278a89f 100644
--- a/packages/SystemUI/compose/features/AndroidManifest.xml
+++ b/packages/SystemUI/compose/features/AndroidManifest.xml
@@ -34,6 +34,11 @@
             android:enabled="false"
             tools:replace="android:authorities"
             tools:node="remove" />
+        <provider android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
+            android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled"
+            android:enabled="false"
+            tools:replace="android:authorities"
+            tools:node="remove" />
         <provider android:name="com.android.keyguard.clock.ClockOptionsProvider"
             android:authorities="com.android.systemui.test.keyguard.clock.disabled"
             android:enabled="false"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
deleted file mode 100644
index 4d94bab..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
+++ /dev/null
@@ -1,392 +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.user.ui.compose
-
-import android.graphics.drawable.Drawable
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.sizeIn
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.graphics.painter.ColorPainter
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.colorResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.core.graphics.drawable.toBitmap
-import com.android.systemui.common.ui.compose.load
-import com.android.systemui.compose.SysUiOutlinedButton
-import com.android.systemui.compose.SysUiTextButton
-import com.android.systemui.compose.features.R
-import com.android.systemui.compose.theme.LocalAndroidColorScheme
-import com.android.systemui.user.ui.viewmodel.UserActionViewModel
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.user.ui.viewmodel.UserViewModel
-import java.lang.Integer.min
-import kotlin.math.ceil
-
-@Composable
-fun UserSwitcherScreen(
-    viewModel: UserSwitcherViewModel,
-    onFinished: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    val isFinishRequested: Boolean by viewModel.isFinishRequested.collectAsState(false)
-    val users: List<UserViewModel> by viewModel.users.collectAsState(emptyList())
-    val maxUserColumns: Int by viewModel.maximumUserColumns.collectAsState(1)
-    val menuActions: List<UserActionViewModel> by viewModel.menu.collectAsState(emptyList())
-    val isOpenMenuButtonVisible: Boolean by viewModel.isOpenMenuButtonVisible.collectAsState(false)
-    val isMenuVisible: Boolean by viewModel.isMenuVisible.collectAsState(false)
-
-    UserSwitcherScreenStateless(
-        isFinishRequested = isFinishRequested,
-        users = users,
-        maxUserColumns = maxUserColumns,
-        menuActions = menuActions,
-        isOpenMenuButtonVisible = isOpenMenuButtonVisible,
-        isMenuVisible = isMenuVisible,
-        onMenuClosed = viewModel::onMenuClosed,
-        onOpenMenuButtonClicked = viewModel::onOpenMenuButtonClicked,
-        onCancelButtonClicked = viewModel::onCancelButtonClicked,
-        onFinished = {
-            onFinished()
-            viewModel.onFinished()
-        },
-        modifier = modifier,
-    )
-}
-
-@Composable
-private fun UserSwitcherScreenStateless(
-    isFinishRequested: Boolean,
-    users: List<UserViewModel>,
-    maxUserColumns: Int,
-    menuActions: List<UserActionViewModel>,
-    isOpenMenuButtonVisible: Boolean,
-    isMenuVisible: Boolean,
-    onMenuClosed: () -> Unit,
-    onOpenMenuButtonClicked: () -> Unit,
-    onCancelButtonClicked: () -> Unit,
-    onFinished: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    LaunchedEffect(isFinishRequested) {
-        if (isFinishRequested) {
-            onFinished()
-        }
-    }
-
-    Box(
-        modifier =
-            modifier
-                .fillMaxSize()
-                .padding(
-                    horizontal = 60.dp,
-                    vertical = 40.dp,
-                ),
-    ) {
-        UserGrid(
-            users = users,
-            maxUserColumns = maxUserColumns,
-            modifier = Modifier.align(Alignment.Center),
-        )
-
-        Buttons(
-            menuActions = menuActions,
-            isOpenMenuButtonVisible = isOpenMenuButtonVisible,
-            isMenuVisible = isMenuVisible,
-            onMenuClosed = onMenuClosed,
-            onOpenMenuButtonClicked = onOpenMenuButtonClicked,
-            onCancelButtonClicked = onCancelButtonClicked,
-            modifier = Modifier.align(Alignment.BottomEnd),
-        )
-    }
-}
-
-@Composable
-private fun UserGrid(
-    users: List<UserViewModel>,
-    maxUserColumns: Int,
-    modifier: Modifier = Modifier,
-) {
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        verticalArrangement = Arrangement.spacedBy(44.dp),
-        modifier = modifier,
-    ) {
-        val rowCount = ceil(users.size / maxUserColumns.toFloat()).toInt()
-        (0 until rowCount).forEach { rowIndex ->
-            Row(
-                horizontalArrangement = Arrangement.spacedBy(64.dp),
-                modifier = modifier,
-            ) {
-                val fromIndex = rowIndex * maxUserColumns
-                val toIndex = min(users.size, (rowIndex + 1) * maxUserColumns)
-                users.subList(fromIndex, toIndex).forEach { user ->
-                    UserItem(
-                        viewModel = user,
-                    )
-                }
-            }
-        }
-    }
-}
-
-@Composable
-private fun UserItem(
-    viewModel: UserViewModel,
-) {
-    val onClicked = viewModel.onClicked
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        modifier =
-            if (onClicked != null) {
-                    Modifier.clickable { onClicked() }
-                } else {
-                    Modifier
-                }
-                .alpha(viewModel.alpha),
-    ) {
-        Box {
-            UserItemBackground(modifier = Modifier.align(Alignment.Center).size(222.dp))
-
-            UserItemIcon(
-                image = viewModel.image,
-                isSelectionMarkerVisible = viewModel.isSelectionMarkerVisible,
-                modifier = Modifier.align(Alignment.Center).size(222.dp)
-            )
-        }
-
-        // User name
-        val text = viewModel.name.load()
-        if (text != null) {
-            // We use the box to center-align the text vertically as that is not possible with Text
-            // alone.
-            Box(
-                modifier = Modifier.size(width = 222.dp, height = 48.dp),
-            ) {
-                Text(
-                    text = text,
-                    style = MaterialTheme.typography.titleLarge,
-                    color = colorResource(com.android.internal.R.color.system_neutral1_50),
-                    maxLines = 1,
-                    overflow = TextOverflow.Ellipsis,
-                    modifier = Modifier.align(Alignment.Center),
-                )
-            }
-        }
-    }
-}
-
-@Composable
-private fun UserItemBackground(
-    modifier: Modifier = Modifier,
-) {
-    Image(
-        painter = ColorPainter(LocalAndroidColorScheme.current.colorBackground),
-        contentDescription = null,
-        modifier = modifier.clip(CircleShape),
-    )
-}
-
-@Composable
-private fun UserItemIcon(
-    image: Drawable,
-    isSelectionMarkerVisible: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    Image(
-        bitmap = image.toBitmap().asImageBitmap(),
-        contentDescription = null,
-        modifier =
-            if (isSelectionMarkerVisible) {
-                    // Draws a ring
-                    modifier.border(
-                        width = 8.dp,
-                        color = LocalAndroidColorScheme.current.colorAccentPrimary,
-                        shape = CircleShape,
-                    )
-                } else {
-                    modifier
-                }
-                .padding(16.dp)
-                .clip(CircleShape)
-    )
-}
-
-@Composable
-private fun Buttons(
-    menuActions: List<UserActionViewModel>,
-    isOpenMenuButtonVisible: Boolean,
-    isMenuVisible: Boolean,
-    onMenuClosed: () -> Unit,
-    onOpenMenuButtonClicked: () -> Unit,
-    onCancelButtonClicked: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    Row(
-        modifier = modifier,
-    ) {
-        // Cancel button.
-        SysUiTextButton(
-            onClick = onCancelButtonClicked,
-        ) {
-            Text(stringResource(R.string.cancel))
-        }
-
-        // "Open menu" button.
-        if (isOpenMenuButtonVisible) {
-            Spacer(modifier = Modifier.width(8.dp))
-            // To properly use a DropdownMenu in Compose, we need to wrap the button that opens it
-            // and the menu itself in a Box.
-            Box {
-                SysUiOutlinedButton(
-                    onClick = onOpenMenuButtonClicked,
-                ) {
-                    Text(stringResource(R.string.add))
-                }
-                Menu(
-                    viewModel = menuActions,
-                    isMenuVisible = isMenuVisible,
-                    onMenuClosed = onMenuClosed,
-                )
-            }
-        }
-    }
-}
-
-@Composable
-private fun Menu(
-    viewModel: List<UserActionViewModel>,
-    isMenuVisible: Boolean,
-    onMenuClosed: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    val maxItemWidth = LocalConfiguration.current.screenWidthDp.dp / 4
-    DropdownMenu(
-        expanded = isMenuVisible,
-        onDismissRequest = onMenuClosed,
-        modifier =
-            modifier.background(
-                color = MaterialTheme.colorScheme.inverseOnSurface,
-            ),
-    ) {
-        viewModel.forEachIndexed { index, action ->
-            MenuItem(
-                viewModel = action,
-                onClicked = { action.onClicked() },
-                topPadding =
-                    if (index == 0) {
-                        16.dp
-                    } else {
-                        0.dp
-                    },
-                bottomPadding =
-                    if (index == viewModel.size - 1) {
-                        16.dp
-                    } else {
-                        0.dp
-                    },
-                modifier = Modifier.sizeIn(maxWidth = maxItemWidth),
-            )
-        }
-    }
-}
-
-@Composable
-private fun MenuItem(
-    viewModel: UserActionViewModel,
-    onClicked: () -> Unit,
-    topPadding: Dp,
-    bottomPadding: Dp,
-    modifier: Modifier = Modifier,
-) {
-    val context = LocalContext.current
-    val density = LocalDensity.current
-
-    val icon =
-        remember(viewModel.iconResourceId) {
-            val drawable =
-                checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId))
-            val size = with(density) { 20.dp.toPx() }.toInt()
-            drawable
-                .toBitmap(
-                    width = size,
-                    height = size,
-                )
-                .asImageBitmap()
-        }
-
-    DropdownMenuItem(
-        text = {
-            Text(
-                text = stringResource(viewModel.textResourceId),
-                style = MaterialTheme.typography.bodyMedium,
-            )
-        },
-        onClick = onClicked,
-        leadingIcon = {
-            Spacer(modifier = Modifier.width(10.dp))
-            Image(
-                bitmap = icon,
-                contentDescription = null,
-            )
-        },
-        modifier =
-            modifier
-                .heightIn(
-                    min = 56.dp,
-                )
-                .padding(
-                    start = 18.dp,
-                    end = 65.dp,
-                    top = topPadding,
-                    bottom = bottomPadding,
-                ),
-    )
-}
diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
index e611e8b..979e1a0 100644
--- a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
+++ b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
@@ -38,12 +38,18 @@
 import platform.test.screenshot.getEmulatedDevicePathConfig
 
 /** A rule for Compose screenshot diff tests. */
-class ComposeScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
+class ComposeScreenshotTestRule(
+    emulationSpec: DeviceEmulationSpec,
+    assetPathRelativeToBuildRoot: String
+) : TestRule {
     private val colorsRule = MaterialYouColorsRule()
     private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
     private val screenshotRule =
         ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+            SystemUIGoldenImagePathManager(
+                getEmulatedDevicePathConfig(emulationSpec),
+                assetPathRelativeToBuildRoot
+            )
         )
     private val composeRule = createAndroidComposeRule<ScreenshotActivity>()
     private val delegateRule =
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
new file mode 100644
index 0000000..dc450bb
--- /dev/null
+++ b/packages/SystemUI/customization/Android.bp
@@ -0,0 +1,52 @@
+// 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+    name: "SystemUICustomizationLib",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+        "src/**/*.aidl",
+    ],
+    static_libs: [
+        "PluginCoreLib",
+        "SystemUIAnimationLib",
+        "SystemUIPluginLib",
+        "SystemUIUnfoldLib",
+        "androidx.dynamicanimation_dynamicanimation",
+        "androidx.concurrent_concurrent-futures",
+        "androidx.lifecycle_lifecycle-runtime-ktx",
+        "androidx.lifecycle_lifecycle-viewmodel-ktx",
+        "androidx.recyclerview_recyclerview",
+        "kotlinx_coroutines_android",
+        "kotlinx_coroutines",
+        "dagger2",
+        "jsr330",
+    ],
+    resource_dirs: [
+        "res",
+    ],
+    min_sdk_version: "current",
+    plugins: ["dagger2-compiler"],
+    kotlincflags: ["-Xjvm-default=enable"],
+}
diff --git a/packages/SystemUI/customization/AndroidManifest.xml b/packages/SystemUI/customization/AndroidManifest.xml
new file mode 100644
index 0000000..3277bff
--- /dev/null
+++ b/packages/SystemUI/customization/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.customization">
+
+</manifest>
diff --git a/packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml b/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
similarity index 100%
rename from packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml
rename to packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
diff --git a/packages/SystemUI/shared/res/layout/clock_default_large.xml b/packages/SystemUI/customization/res/layout/clock_default_large.xml
similarity index 100%
rename from packages/SystemUI/shared/res/layout/clock_default_large.xml
rename to packages/SystemUI/customization/res/layout/clock_default_large.xml
diff --git a/packages/SystemUI/shared/res/layout/clock_default_small.xml b/packages/SystemUI/customization/res/layout/clock_default_small.xml
similarity index 100%
rename from packages/SystemUI/shared/res/layout/clock_default_small.xml
rename to packages/SystemUI/customization/res/layout/clock_default_small.xml
diff --git a/packages/SystemUI/customization/res/values-h700dp/dimens.xml b/packages/SystemUI/customization/res/values-h700dp/dimens.xml
new file mode 100644
index 0000000..2a15981a
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-h700dp/dimens.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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>
+    <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
+    <dimen name="large_clock_text_size">170dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-h800dp/dimens.xml b/packages/SystemUI/customization/res/values-h800dp/dimens.xml
new file mode 100644
index 0000000..60afc8a
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-h800dp/dimens.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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>
+    <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
+    <dimen name="large_clock_text_size">200dp</dimen>
+</resources>
diff --git a/packages/SystemUI/customization/res/values/attrs.xml b/packages/SystemUI/customization/res/values/attrs.xml
new file mode 100644
index 0000000..f9d66ee
--- /dev/null
+++ b/packages/SystemUI/customization/res/values/attrs.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.
+-->
+
+<!-- Formatting note: terminate all comments with a period, to avoid breaking
+     the documentation output. To suppress comment lines from the documentation
+     output, insert an eat-comment element after the comment lines.
+-->
+
+<resources>
+    <declare-styleable name="AnimatableClockView">
+        <attr name="dozeWeight" format="integer" />
+        <attr name="lockScreenWeight" format="integer" />
+        <attr name="chargeAnimationDelay" format="integer" />
+    </declare-styleable>
+</resources>
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
new file mode 100644
index 0000000..ba8f284
--- /dev/null
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -0,0 +1,27 @@
+<?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.
+*/
+-->
+<resources>
+    <!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
+    <dimen name="large_clock_text_size">150dp</dimen>
+    <dimen name="small_clock_text_size">86dp</dimen>
+
+    <!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
+    <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
+    <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
+    <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/res/values/donottranslate.xml b/packages/SystemUI/customization/res/values/donottranslate.xml
similarity index 100%
rename from packages/SystemUI/shared/res/values/donottranslate.xml
rename to packages/SystemUI/customization/res/values/donottranslate.xml
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
new file mode 100644
index 0000000..462b90a
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -0,0 +1,673 @@
+/*
+ * 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.clocks
+
+import android.animation.TimeInterpolator
+import android.annotation.ColorInt
+import android.annotation.FloatRange
+import android.annotation.IntRange
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.text.Layout
+import android.text.TextUtils
+import android.text.format.DateFormat
+import android.util.AttributeSet
+import android.util.MathUtils
+import android.widget.TextView
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.animation.GlyphCallback
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.TextAnimator
+import com.android.systemui.customization.R
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import java.io.PrintWriter
+import java.util.Calendar
+import java.util.Locale
+import java.util.TimeZone
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
+ * The time's text color is a gradient that changes its colors based on its controller.
+ */
+@SuppressLint("AppCompatCustomView")
+class AnimatableClockView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : TextView(context, attrs, defStyleAttr, defStyleRes) {
+    var tag: String = "UnnamedClockView"
+    var logBuffer: LogBuffer? = null
+
+    private val time = Calendar.getInstance()
+
+    private val dozingWeightInternal: Int
+    private val lockScreenWeightInternal: Int
+    private val isSingleLineInternal: Boolean
+
+    private var format: CharSequence? = null
+    private var descFormat: CharSequence? = null
+
+    @ColorInt
+    private var dozingColor = 0
+
+    @ColorInt
+    private var lockScreenColor = 0
+
+    private var lineSpacingScale = 1f
+    private val chargeAnimationDelay: Int
+    private var textAnimator: TextAnimator? = null
+    private var onTextAnimatorInitialized: Runnable? = null
+
+    @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator =
+        { layout, invalidateCb -> TextAnimator(layout, invalidateCb) }
+    @VisibleForTesting var isAnimationEnabled: Boolean = true
+    @VisibleForTesting var timeOverrideInMillis: Long? = null
+
+    val dozingWeight: Int
+        get() = if (useBoldedVersion()) dozingWeightInternal + 100 else dozingWeightInternal
+
+    val lockScreenWeight: Int
+        get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
+
+    /**
+     * The number of pixels below the baseline. For fonts that support languages such as
+     * Burmese, this space can be significant and should be accounted for when computing layout.
+     */
+    val bottom get() = paint?.fontMetrics?.bottom ?: 0f
+
+    init {
+        val animatableClockViewAttributes = context.obtainStyledAttributes(
+            attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes
+        )
+
+        try {
+            dozingWeightInternal = animatableClockViewAttributes.getInt(
+                R.styleable.AnimatableClockView_dozeWeight,
+                100
+            )
+            lockScreenWeightInternal = animatableClockViewAttributes.getInt(
+                R.styleable.AnimatableClockView_lockScreenWeight,
+                300
+            )
+            chargeAnimationDelay = animatableClockViewAttributes.getInt(
+                R.styleable.AnimatableClockView_chargeAnimationDelay, 200
+            )
+        } finally {
+            animatableClockViewAttributes.recycle()
+        }
+
+        val textViewAttributes = context.obtainStyledAttributes(
+            attrs, android.R.styleable.TextView,
+            defStyleAttr, defStyleRes
+        )
+
+        isSingleLineInternal =
+            try {
+                textViewAttributes.getBoolean(android.R.styleable.TextView_singleLine, false)
+            } finally {
+                textViewAttributes.recycle()
+            }
+
+        refreshFormat()
+    }
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        logBuffer?.log(tag, DEBUG, "onAttachedToWindow")
+        refreshFormat()
+    }
+
+    /**
+     * Whether to use a bolded version based on the user specified fontWeightAdjustment.
+     */
+    fun useBoldedVersion(): Boolean {
+        // "Bold text" fontWeightAdjustment is 300.
+        return resources.configuration.fontWeightAdjustment > 100
+    }
+
+    fun refreshTime() {
+        time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
+        contentDescription = DateFormat.format(descFormat, time)
+        val formattedText = DateFormat.format(format, time)
+        logBuffer?.log(tag, DEBUG,
+                { str1 = formattedText?.toString() },
+                { "refreshTime: new formattedText=$str1" }
+        )
+        // Setting text actually triggers a layout pass (because the text view is set to
+        // wrap_content width and TextView always relayouts for this). Avoid needless
+        // relayout if the text didn't actually change.
+        if (!TextUtils.equals(text, formattedText)) {
+            text = formattedText
+            logBuffer?.log(tag, DEBUG,
+                    { str1 = formattedText?.toString() },
+                    { "refreshTime: done setting new time text to: $str1" }
+            )
+            // Because the TextLayout may mutate under the hood as a result of the new text, we
+            // notify the TextAnimator that it may have changed and request a measure/layout. A
+            // crash will occur on the next invocation of setTextStyle if the layout is mutated
+            // without being notified TextInterpolator being notified.
+            if (layout != null) {
+                textAnimator?.updateLayout(layout)
+                logBuffer?.log(tag, DEBUG, "refreshTime: done updating textAnimator layout")
+            }
+            requestLayout()
+            logBuffer?.log(tag, DEBUG, "refreshTime: after requestLayout")
+        }
+    }
+
+    fun onTimeZoneChanged(timeZone: TimeZone?) {
+        time.timeZone = timeZone
+        refreshFormat()
+        logBuffer?.log(tag, DEBUG,
+                { str1 = timeZone?.toString() },
+                { "onTimeZoneChanged newTimeZone=$str1" }
+        )
+    }
+
+    @SuppressLint("DrawAllocation")
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+        val animator = textAnimator
+        if (animator == null) {
+            textAnimator = textAnimatorFactory(layout, ::invalidate)
+            onTextAnimatorInitialized?.run()
+            onTextAnimatorInitialized = null
+        } else {
+            animator.updateLayout(layout)
+        }
+        logBuffer?.log(tag, DEBUG, "onMeasure")
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        // Use textAnimator to render text if animation is enabled.
+        // Otherwise default to using standard draw functions.
+        if (isAnimationEnabled) {
+            // intentionally doesn't call super.onDraw here or else the text will be rendered twice
+            textAnimator?.draw(canvas)
+        } else {
+            super.onDraw(canvas)
+        }
+        logBuffer?.log(tag, DEBUG, "onDraw lastDraw")
+    }
+
+    override fun invalidate() {
+        super.invalidate()
+        logBuffer?.log(tag, DEBUG, "invalidate")
+    }
+
+    override fun onTextChanged(
+            text: CharSequence,
+            start: Int,
+            lengthBefore: Int,
+            lengthAfter: Int
+    ) {
+        super.onTextChanged(text, start, lengthBefore, lengthAfter)
+        logBuffer?.log(tag, DEBUG,
+                { str1 = text.toString() },
+                { "onTextChanged text=$str1" }
+        )
+    }
+
+    fun setLineSpacingScale(scale: Float) {
+        lineSpacingScale = scale
+        setLineSpacing(0f, lineSpacingScale)
+    }
+
+    fun setColors(@ColorInt dozingColor: Int, lockScreenColor: Int) {
+        this.dozingColor = dozingColor
+        this.lockScreenColor = lockScreenColor
+    }
+
+    fun animateColorChange() {
+        logBuffer?.log(tag, DEBUG, "animateColorChange")
+        setTextStyle(
+            weight = lockScreenWeight,
+            textSize = -1f,
+            color = null, /* using current color */
+            animate = false,
+            duration = 0,
+            delay = 0,
+            onAnimationEnd = null
+        )
+        setTextStyle(
+            weight = lockScreenWeight,
+            textSize = -1f,
+            color = lockScreenColor,
+            animate = true,
+            duration = COLOR_ANIM_DURATION,
+            delay = 0,
+            onAnimationEnd = null
+        )
+    }
+
+    fun animateAppearOnLockscreen() {
+        logBuffer?.log(tag, DEBUG, "animateAppearOnLockscreen")
+        setTextStyle(
+            weight = dozingWeight,
+            textSize = -1f,
+            color = lockScreenColor,
+            animate = false,
+            duration = 0,
+            delay = 0,
+            onAnimationEnd = null
+        )
+        setTextStyle(
+            weight = lockScreenWeight,
+            textSize = -1f,
+            color = lockScreenColor,
+            animate = isAnimationEnabled,
+            duration = APPEAR_ANIM_DURATION,
+            delay = 0,
+            onAnimationEnd = null
+        )
+    }
+
+    fun animateFoldAppear(animate: Boolean = true) {
+        if (isAnimationEnabled && textAnimator == null) {
+            return
+        }
+        logBuffer?.log(tag, DEBUG, "animateFoldAppear")
+        setTextStyle(
+            weight = lockScreenWeightInternal,
+            textSize = -1f,
+            color = lockScreenColor,
+            animate = false,
+            duration = 0,
+            delay = 0,
+            onAnimationEnd = null
+        )
+        setTextStyle(
+            weight = dozingWeightInternal,
+            textSize = -1f,
+            color = dozingColor,
+            animate = animate && isAnimationEnabled,
+            interpolator = Interpolators.EMPHASIZED_DECELERATE,
+            duration = ANIMATION_DURATION_FOLD_TO_AOD.toLong(),
+            delay = 0,
+            onAnimationEnd = null
+        )
+    }
+
+    fun animateCharge(isDozing: () -> Boolean) {
+        if (textAnimator == null || textAnimator!!.isRunning()) {
+            // Skip charge animation if dozing animation is already playing.
+            return
+        }
+        logBuffer?.log(tag, DEBUG, "animateCharge")
+        val startAnimPhase2 = Runnable {
+            setTextStyle(
+                weight = if (isDozing()) dozingWeight else lockScreenWeight,
+                textSize = -1f,
+                color = null,
+                animate = isAnimationEnabled,
+                duration = CHARGE_ANIM_DURATION_PHASE_1,
+                delay = 0,
+                onAnimationEnd = null
+            )
+        }
+        setTextStyle(
+            weight = if (isDozing()) lockScreenWeight else dozingWeight,
+            textSize = -1f,
+            color = null,
+            animate = isAnimationEnabled,
+            duration = CHARGE_ANIM_DURATION_PHASE_0,
+            delay = chargeAnimationDelay.toLong(),
+            onAnimationEnd = startAnimPhase2
+        )
+    }
+
+    fun animateDoze(isDozing: Boolean, animate: Boolean) {
+        logBuffer?.log(tag, DEBUG, "animateDoze")
+        setTextStyle(
+            weight = if (isDozing) dozingWeight else lockScreenWeight,
+            textSize = -1f,
+            color = if (isDozing) dozingColor else lockScreenColor,
+            animate = animate && isAnimationEnabled,
+            duration = DOZE_ANIM_DURATION,
+            delay = 0,
+            onAnimationEnd = null
+        )
+    }
+
+    // The offset of each glyph from where it should be.
+    private var glyphOffsets = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f)
+
+    private var lastSeenAnimationProgress = 1.0f
+
+    // If the animation is being reversed, the target offset for each glyph for the "stop".
+    private var animationCancelStartPosition = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f)
+    private var animationCancelStopPosition = 0.0f
+
+    // Whether the currently playing animation needed a stop (and thus, is shortened).
+    private var currentAnimationNeededStop = false
+
+    private val glyphFilter: GlyphCallback = { positionedGlyph, _ ->
+        val offset = positionedGlyph.lineNo * DIGITS_PER_LINE + positionedGlyph.glyphIndex
+        if (offset < glyphOffsets.size) {
+            positionedGlyph.x += glyphOffsets[offset]
+        }
+    }
+
+    /**
+     * Set text style with an optional animation.
+     *
+     * By passing -1 to weight, the view preserves its current weight.
+     * By passing -1 to textSize, the view preserves its current text size.
+     * By passing null to color, the view preserves its current color.
+     *
+     * @param weight text weight.
+     * @param textSize font size.
+     * @param animate true to animate the text style change, otherwise false.
+     */
+    private fun setTextStyle(
+        @IntRange(from = 0, to = 1000) weight: Int,
+        @FloatRange(from = 0.0) textSize: Float,
+        color: Int?,
+        animate: Boolean,
+        interpolator: TimeInterpolator?,
+        duration: Long,
+        delay: Long,
+        onAnimationEnd: Runnable?
+    ) {
+        if (textAnimator != null) {
+            textAnimator?.setTextStyle(
+                weight = weight,
+                textSize = textSize,
+                color = color,
+                animate = animate && isAnimationEnabled,
+                duration = duration,
+                interpolator = interpolator,
+                delay = delay,
+                onAnimationEnd = onAnimationEnd
+            )
+            textAnimator?.glyphFilter = glyphFilter
+            if (color != null && !isAnimationEnabled) {
+                setTextColor(color)
+            }
+        } else {
+            // when the text animator is set, update its start values
+            onTextAnimatorInitialized = Runnable {
+                textAnimator?.setTextStyle(
+                    weight = weight,
+                    textSize = textSize,
+                    color = color,
+                    animate = false,
+                    duration = duration,
+                    interpolator = interpolator,
+                    delay = delay,
+                    onAnimationEnd = onAnimationEnd
+                )
+                textAnimator?.glyphFilter = glyphFilter
+                if (color != null && !isAnimationEnabled) {
+                    setTextColor(color)
+                }
+            }
+        }
+    }
+
+    private fun setTextStyle(
+        @IntRange(from = 0, to = 1000) weight: Int,
+        @FloatRange(from = 0.0) textSize: Float,
+        color: Int?,
+        animate: Boolean,
+        duration: Long,
+        delay: Long,
+        onAnimationEnd: Runnable?
+    ) {
+        setTextStyle(
+            weight = weight,
+            textSize = textSize,
+            color = color,
+            animate = animate && isAnimationEnabled,
+            interpolator = null,
+            duration = duration,
+            delay = delay,
+            onAnimationEnd = onAnimationEnd
+        )
+    }
+
+    fun refreshFormat() = refreshFormat(DateFormat.is24HourFormat(context))
+    fun refreshFormat(use24HourFormat: Boolean) {
+        Patterns.update(context)
+
+        format = when {
+            isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
+            !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR
+            isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
+            else -> DOUBLE_LINE_FORMAT_12_HOUR
+        }
+        logBuffer?.log(tag, DEBUG,
+                { str1 = format?.toString() },
+                { "refreshFormat format=$str1" }
+        )
+
+        descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
+        refreshTime()
+    }
+
+    fun dump(pw: PrintWriter) {
+        pw.println("$this")
+        pw.println("    measuredWidth=$measuredWidth")
+        pw.println("    measuredHeight=$measuredHeight")
+        pw.println("    singleLineInternal=$isSingleLineInternal")
+        pw.println("    currText=$text")
+        pw.println("    currTimeContextDesc=$contentDescription")
+        pw.println("    dozingWeightInternal=$dozingWeightInternal")
+        pw.println("    lockScreenWeightInternal=$lockScreenWeightInternal")
+        pw.println("    dozingColor=$dozingColor")
+        pw.println("    lockScreenColor=$lockScreenColor")
+        pw.println("    time=$time")
+    }
+
+    fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) {
+        // Do we need to cancel an in-flight animation?
+        // Need to also check against 0.0f here; we can sometimes get two calls with fraction == 0,
+        // which trips up the check otherwise.
+        if (lastSeenAnimationProgress != 1.0f &&
+                lastSeenAnimationProgress != 0.0f &&
+                fraction == 0.0f) {
+            // New animation, but need to stop the old one. Figure out where each glyph currently
+            // is in relation to the box position. After that, use the leading digit's current
+            // position as the stop target.
+            currentAnimationNeededStop = true
+
+            // We assume that the current glyph offsets would be relative to the "from" position.
+            val moveAmount = toRect.left - fromRect.left
+
+            // Remap the current glyph offsets to be relative to the new "end" position, and figure
+            // out the start/end positions for the stop animation.
+            for (i in 0 until NUM_DIGITS) {
+                glyphOffsets[i] = -moveAmount + glyphOffsets[i]
+                animationCancelStartPosition[i] = glyphOffsets[i]
+            }
+
+            // Use the leading digit's offset as the stop position.
+            if (toRect.left > fromRect.left) {
+                // It _was_ moving left
+                animationCancelStopPosition = glyphOffsets[0]
+            } else {
+                // It was moving right
+                animationCancelStopPosition = glyphOffsets[1]
+            }
+        }
+
+        // Is there a cancellation in progress?
+        if (currentAnimationNeededStop && fraction < ANIMATION_CANCELLATION_TIME) {
+            val animationStopProgress = MathUtils.constrainedMap(
+                    0.0f, 1.0f, 0.0f, ANIMATION_CANCELLATION_TIME, fraction
+            )
+
+            // One of the digits has already stopped.
+            val animationStopStep = 1.0f / (NUM_DIGITS - 1)
+
+            for (i in 0 until NUM_DIGITS) {
+                val stopAmount = if (toRect.left > fromRect.left) {
+                    // It was moving left (before flipping)
+                    MOVE_LEFT_DELAYS[i] * animationStopStep
+                } else {
+                    // It was moving right (before flipping)
+                    MOVE_RIGHT_DELAYS[i] * animationStopStep
+                }
+
+                // Leading digit stops immediately.
+                if (stopAmount == 0.0f) {
+                    glyphOffsets[i] = animationCancelStopPosition
+                } else {
+                    val actualStopAmount = MathUtils.constrainedMap(
+                            0.0f, 1.0f, 0.0f, stopAmount, animationStopProgress
+                    )
+                    val easedProgress = MOVE_INTERPOLATOR.getInterpolation(actualStopAmount)
+                    val glyphMoveAmount =
+                            animationCancelStopPosition - animationCancelStartPosition[i]
+                    glyphOffsets[i] =
+                            animationCancelStartPosition[i] + glyphMoveAmount * easedProgress
+                }
+            }
+        } else {
+            // Normal part of the animation.
+            // Do we need to remap the animation progress to take account of the cancellation?
+            val actualFraction = if (currentAnimationNeededStop) {
+                MathUtils.constrainedMap(
+                        0.0f, 1.0f, ANIMATION_CANCELLATION_TIME, 1.0f, fraction
+                )
+            } else {
+                fraction
+            }
+
+            val digitFractions = (0 until NUM_DIGITS).map {
+                // The delay for each digit, in terms of fraction (i.e. the digit should not move
+                // during 0.0 - 0.1).
+                val initialDelay = if (toRect.left > fromRect.left) {
+                    MOVE_RIGHT_DELAYS[it] * MOVE_DIGIT_STEP
+                } else {
+                    MOVE_LEFT_DELAYS[it] * MOVE_DIGIT_STEP
+                }
+
+                val f = MathUtils.constrainedMap(
+                        0.0f, 1.0f,
+                        initialDelay, initialDelay + AVAILABLE_ANIMATION_TIME,
+                        actualFraction
+                )
+                MOVE_INTERPOLATOR.getInterpolation(max(min(f, 1.0f), 0.0f))
+            }
+
+            // Was there an animation halt?
+            val moveAmount = if (currentAnimationNeededStop) {
+                // Only need to animate over the remaining space if the animation was aborted.
+                -animationCancelStopPosition
+            } else {
+                toRect.left.toFloat() - fromRect.left.toFloat()
+            }
+
+            for (i in 0 until NUM_DIGITS) {
+                glyphOffsets[i] = -moveAmount + (moveAmount * digitFractions[i])
+            }
+        }
+
+        invalidate()
+
+        if (fraction == 1.0f) {
+            // Reset
+            currentAnimationNeededStop = false
+        }
+
+        lastSeenAnimationProgress = fraction
+
+        // Ensure that the actual clock container is always in the "end" position.
+        this.setLeftTopRightBottom(toRect.left, toRect.top, toRect.right, toRect.bottom)
+    }
+
+    // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
+    // This is an optimization to ensure we only recompute the patterns when the inputs change.
+    private object Patterns {
+        var sClockView12: String? = null
+        var sClockView24: String? = null
+        var sCacheKey: String? = null
+
+        fun update(context: Context) {
+            val locale = Locale.getDefault()
+            val res = context.resources
+            val clockView12Skel = res.getString(R.string.clock_12hr_format)
+            val clockView24Skel = res.getString(R.string.clock_24hr_format)
+            val key = locale.toString() + clockView12Skel + clockView24Skel
+            if (key == sCacheKey) return
+
+            val clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel)
+            sClockView12 = clockView12
+
+            // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
+            // format.  The following code removes the AM/PM indicator if we didn't want it.
+            if (!clockView12Skel.contains("a")) {
+                sClockView12 = clockView12.replace("a".toRegex(), "").trim { it <= ' ' }
+            }
+
+            sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel)
+            sCacheKey = key
+        }
+    }
+
+    companion object {
+        private val TAG = AnimatableClockView::class.simpleName
+        const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600
+        private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
+        private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
+        private const val DOZE_ANIM_DURATION: Long = 300
+        private const val APPEAR_ANIM_DURATION: Long = 350
+        private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
+        private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
+        private const val COLOR_ANIM_DURATION: Long = 400
+
+        // Constants for the animation
+        private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
+
+        // Calculate the positions of all of the digits...
+        // Offset each digit by, say, 0.1
+        // This means that each digit needs to move over a slice of "fractions", i.e. digit 0 should
+        // move from 0.0 - 0.7, digit 1 from 0.1 - 0.8, digit 2 from 0.2 - 0.9, and digit 3
+        // from 0.3 - 1.0.
+        private const val NUM_DIGITS = 4
+        private const val DIGITS_PER_LINE = 2
+
+        // How much of "fraction" to spend on canceling the animation, if needed
+        private const val ANIMATION_CANCELLATION_TIME = 0.4f
+
+        // Delays. Each digit's animation should have a slight delay, so we get a nice
+        // "stepping" effect. When moving right, the second digit of the hour should move first.
+        // When moving left, the first digit of the hour should move first. The lists encode
+        // the delay for each digit (hour[0], hour[1], minute[0], minute[1]), to be multiplied
+        // by delayMultiplier.
+        private val MOVE_LEFT_DELAYS = listOf(0, 1, 2, 3)
+        private val MOVE_RIGHT_DELAYS = listOf(1, 0, 3, 2)
+
+        // How much delay to apply to each subsequent digit. This is measured in terms of "fraction"
+        // (i.e. a value of 0.1 would cause a digit to wait until fraction had hit 0.1, or 0.2 etc
+        // before moving).
+        //
+        // The current specs dictate that each digit should have a 33ms gap between them. The
+        // overall time is 1s right now.
+        private const val MOVE_DIGIT_STEP = 0.033f
+
+        // Total available transition time for each digit, taking into account the step. If step is
+        // 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
+        private val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1)
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
new file mode 100644
index 0000000..59b4848
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -0,0 +1,229 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.database.ContentObserver
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Handler
+import android.provider.Settings
+import android.util.Log
+import androidx.annotation.OpenForTesting
+import com.android.internal.annotations.Keep
+import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.ClockId
+import com.android.systemui.plugins.ClockMetadata
+import com.android.systemui.plugins.ClockProvider
+import com.android.systemui.plugins.ClockProviderPlugin
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.plugins.PluginManager
+import org.json.JSONObject
+
+private val TAG = ClockRegistry::class.simpleName
+private const val DEBUG = true
+
+/** ClockRegistry aggregates providers and plugins */
+open class ClockRegistry(
+    val context: Context,
+    val pluginManager: PluginManager,
+    val handler: Handler,
+    val isEnabled: Boolean,
+    userHandle: Int,
+    defaultClockProvider: ClockProvider,
+    val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
+) {
+    // Usually this would be a typealias, but a SAM provides better java interop
+    fun interface ClockChangeListener {
+        fun onClockChanged()
+    }
+
+    private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
+    private val clockChangeListeners = mutableListOf<ClockChangeListener>()
+    private val settingObserver = object : ContentObserver(handler) {
+        override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
+            clockChangeListeners.forEach { it.onClockChanged() }
+    }
+
+    private val pluginListener = object : PluginListener<ClockProviderPlugin> {
+        override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) =
+            connectClocks(plugin)
+
+        override fun onPluginDisconnected(plugin: ClockProviderPlugin) =
+            disconnectClocks(plugin)
+    }
+
+    open var currentClockId: ClockId
+        get() {
+            return try {
+                val json = Settings.Secure.getString(
+                    context.contentResolver,
+                    Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
+                )
+                if (json == null || json.isEmpty()) {
+                    return fallbackClockId
+                }
+                ClockSetting.deserialize(json).clockId
+            } catch (ex: Exception) {
+                Log.e(TAG, "Failed to parse clock setting", ex)
+                fallbackClockId
+            }
+        }
+        set(value) {
+            try {
+                val json = ClockSetting.serialize(ClockSetting(value, System.currentTimeMillis()))
+                Settings.Secure.putString(
+                    context.contentResolver,
+                    Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
+                )
+            } catch (ex: Exception) {
+                Log.e(TAG, "Failed to set clock setting", ex)
+            }
+        }
+
+    init {
+        connectClocks(defaultClockProvider)
+        if (!availableClocks.containsKey(DEFAULT_CLOCK_ID)) {
+            throw IllegalArgumentException(
+                "$defaultClockProvider did not register clock at $DEFAULT_CLOCK_ID"
+            )
+        }
+
+        if (isEnabled) {
+            pluginManager.addPluginListener(
+                pluginListener,
+                ClockProviderPlugin::class.java,
+                /*allowMultiple=*/ true
+            )
+            context.contentResolver.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+                /*notifyForDescendants=*/ false,
+                settingObserver,
+                userHandle
+            )
+        }
+    }
+
+    private fun connectClocks(provider: ClockProvider) {
+        val currentId = currentClockId
+        for (clock in provider.getClocks()) {
+            val id = clock.clockId
+            val current = availableClocks[id]
+            if (current != null) {
+                Log.e(
+                    TAG,
+                    "Clock Id conflict: $id is registered by both " +
+                        "${provider::class.simpleName} and ${current.provider::class.simpleName}"
+                )
+                return
+            }
+
+            availableClocks[id] = ClockInfo(clock, provider)
+            if (DEBUG) {
+                Log.i(TAG, "Added ${clock.clockId}")
+            }
+
+            if (currentId == id) {
+                if (DEBUG) {
+                    Log.i(TAG, "Current clock ($currentId) was connected")
+                }
+                clockChangeListeners.forEach { it.onClockChanged() }
+            }
+        }
+    }
+
+    private fun disconnectClocks(provider: ClockProvider) {
+        val currentId = currentClockId
+        for (clock in provider.getClocks()) {
+            availableClocks.remove(clock.clockId)
+            if (DEBUG) {
+                Log.i(TAG, "Removed ${clock.clockId}")
+            }
+
+            if (currentId == clock.clockId) {
+                Log.w(TAG, "Current clock ($currentId) was disconnected")
+                clockChangeListeners.forEach { it.onClockChanged() }
+            }
+        }
+    }
+
+    @OpenForTesting
+    open fun getClocks(): List<ClockMetadata> {
+        if (!isEnabled) {
+            return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
+        }
+        return availableClocks.map { (_, clock) -> clock.metadata }
+    }
+
+    fun getClockThumbnail(clockId: ClockId): Drawable? =
+        availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
+
+    fun createExampleClock(clockId: ClockId): ClockController? = createClock(clockId)
+
+    fun registerClockChangeListener(listener: ClockChangeListener) =
+        clockChangeListeners.add(listener)
+
+    fun unregisterClockChangeListener(listener: ClockChangeListener) =
+        clockChangeListeners.remove(listener)
+
+    fun createCurrentClock(): ClockController {
+        val clockId = currentClockId
+        if (isEnabled && clockId.isNotEmpty()) {
+            val clock = createClock(clockId)
+            if (clock != null) {
+                if (DEBUG) {
+                    Log.i(TAG, "Rendering clock $clockId")
+                }
+                return clock
+            } else {
+                Log.e(TAG, "Clock $clockId not found; using default")
+            }
+        }
+
+        return createClock(DEFAULT_CLOCK_ID)!!
+    }
+
+    private fun createClock(clockId: ClockId): ClockController? =
+        availableClocks[clockId]?.provider?.createClock(clockId)
+
+    private data class ClockInfo(
+        val metadata: ClockMetadata,
+        val provider: ClockProvider
+    )
+
+    @Keep
+    data class ClockSetting(
+        val clockId: ClockId,
+        val _applied_timestamp: Long?
+    ) {
+        companion object {
+            private val KEY_CLOCK_ID = "clockId"
+            private val KEY_TIMESTAMP = "_applied_timestamp"
+
+            fun serialize(setting: ClockSetting): String {
+                return JSONObject()
+                    .put(KEY_CLOCK_ID, setting.clockId)
+                    .put(KEY_TIMESTAMP, setting._applied_timestamp)
+                    .toString()
+            }
+
+            fun deserialize(jsonStr: String): ClockSetting {
+                val json = JSONObject(jsonStr)
+                return ClockSetting(
+                    json.getString(KEY_CLOCK_ID),
+                    if (!json.isNull(KEY_TIMESTAMP)) json.getLong(KEY_TIMESTAMP) else null)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
new file mode 100644
index 0000000..c540f0f
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -0,0 +1,265 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Color
+import android.graphics.Rect
+import android.icu.text.NumberFormat
+import android.util.TypedValue
+import android.view.LayoutInflater
+import android.widget.FrameLayout
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.customization.R
+import com.android.systemui.plugins.ClockAnimations
+import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.ClockEvents
+import com.android.systemui.plugins.ClockFaceController
+import com.android.systemui.plugins.ClockFaceEvents
+import com.android.systemui.plugins.log.LogBuffer
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+
+private val TAG = DefaultClockController::class.simpleName
+
+/**
+ * Controls the default clock visuals.
+ *
+ * This serves as an adapter between the clock interface and the AnimatableClockView used by the
+ * existing lockscreen clock.
+ */
+class DefaultClockController(
+    ctx: Context,
+    private val layoutInflater: LayoutInflater,
+    private val resources: Resources,
+) : ClockController {
+    override val smallClock: DefaultClockFaceController
+    override val largeClock: LargeClockFaceController
+    private val clocks: List<AnimatableClockView>
+
+    private val burmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"))
+    private val burmeseNumerals = burmeseNf.format(FORMAT_NUMBER.toLong())
+    private val burmeseLineSpacing =
+        resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
+    private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
+
+    override val events: DefaultClockEvents
+    override lateinit var animations: DefaultClockAnimations
+        private set
+
+    init {
+        val parent = FrameLayout(ctx)
+        smallClock =
+            DefaultClockFaceController(
+                layoutInflater.inflate(R.layout.clock_default_small, parent, false)
+                    as AnimatableClockView
+            )
+        largeClock =
+            LargeClockFaceController(
+                layoutInflater.inflate(R.layout.clock_default_large, parent, false)
+                    as AnimatableClockView
+            )
+        clocks = listOf(smallClock.view, largeClock.view)
+
+        events = DefaultClockEvents()
+        animations = DefaultClockAnimations(0f, 0f)
+        events.onLocaleChanged(Locale.getDefault())
+    }
+
+    override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+        largeClock.recomputePadding(null)
+        animations = DefaultClockAnimations(dozeFraction, foldFraction)
+        events.onColorPaletteChanged(resources)
+        events.onTimeZoneChanged(TimeZone.getDefault())
+        events.onTimeTick()
+    }
+
+    override fun setLogBuffer(logBuffer: LogBuffer) {
+        smallClock.view.tag = "smallClockView"
+        largeClock.view.tag = "largeClockView"
+        smallClock.view.logBuffer = logBuffer
+        largeClock.view.logBuffer = logBuffer
+    }
+
+    open inner class DefaultClockFaceController(
+        override val view: AnimatableClockView,
+    ) : ClockFaceController {
+
+        // MAGENTA is a placeholder, and will be assigned correctly in initialize
+        private var currentColor = Color.MAGENTA
+        private var isRegionDark = false
+        protected var targetRegion: Rect? = null
+
+        init {
+            view.setColors(currentColor, currentColor)
+        }
+
+        override val events =
+            object : ClockFaceEvents {
+                override fun onRegionDarknessChanged(isRegionDark: Boolean) {
+                    this@DefaultClockFaceController.isRegionDark = isRegionDark
+                    updateColor()
+                }
+
+                override fun onTargetRegionChanged(targetRegion: Rect?) {
+                    this@DefaultClockFaceController.targetRegion = targetRegion
+                    recomputePadding(targetRegion)
+                }
+
+                override fun onFontSettingChanged(fontSizePx: Float) {
+                    view.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx)
+                    recomputePadding(targetRegion)
+                }
+            }
+
+        open fun recomputePadding(targetRegion: Rect?) {}
+
+        fun updateColor() {
+            val color =
+                if (isRegionDark) {
+                    resources.getColor(android.R.color.system_accent1_100)
+                } else {
+                    resources.getColor(android.R.color.system_accent2_600)
+                }
+
+            if (currentColor == color) {
+                return
+            }
+
+            currentColor = color
+            view.setColors(DOZE_COLOR, color)
+            if (!animations.dozeState.isActive) {
+                view.animateColorChange()
+            }
+        }
+    }
+
+    inner class LargeClockFaceController(
+        view: AnimatableClockView,
+    ) : DefaultClockFaceController(view) {
+        override fun recomputePadding(targetRegion: Rect?) {
+            // Ignore Target Region until top padding fixed in aod
+            val lp = view.getLayoutParams() as FrameLayout.LayoutParams
+            lp.topMargin = (-0.5f * view.bottom).toInt()
+            view.setLayoutParams(lp)
+        }
+
+        fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) {
+            view.moveForSplitShade(fromRect, toRect, fraction)
+        }
+    }
+
+    inner class DefaultClockEvents : ClockEvents {
+        override fun onTimeTick() = clocks.forEach { it.refreshTime() }
+
+        override fun onTimeFormatChanged(is24Hr: Boolean) =
+            clocks.forEach { it.refreshFormat(is24Hr) }
+
+        override fun onTimeZoneChanged(timeZone: TimeZone) =
+            clocks.forEach { it.onTimeZoneChanged(timeZone) }
+
+        override fun onColorPaletteChanged(resources: Resources) {
+            largeClock.updateColor()
+            smallClock.updateColor()
+        }
+
+        override fun onLocaleChanged(locale: Locale) {
+            val nf = NumberFormat.getInstance(locale)
+            if (nf.format(FORMAT_NUMBER.toLong()) == burmeseNumerals) {
+                clocks.forEach { it.setLineSpacingScale(burmeseLineSpacing) }
+            } else {
+                clocks.forEach { it.setLineSpacingScale(defaultLineSpacing) }
+            }
+
+            clocks.forEach { it.refreshFormat() }
+        }
+    }
+
+    inner class DefaultClockAnimations(
+        dozeFraction: Float,
+        foldFraction: Float,
+    ) : ClockAnimations {
+        internal val dozeState = AnimationState(dozeFraction)
+        private val foldState = AnimationState(foldFraction)
+
+        init {
+            if (foldState.isActive) {
+                clocks.forEach { it.animateFoldAppear(false) }
+            } else {
+                clocks.forEach { it.animateDoze(dozeState.isActive, false) }
+            }
+        }
+
+        override fun enter() {
+            if (!dozeState.isActive) {
+                clocks.forEach { it.animateAppearOnLockscreen() }
+            }
+        }
+
+        override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } }
+
+        override fun fold(fraction: Float) {
+            val (hasChanged, hasJumped) = foldState.update(fraction)
+            if (hasChanged) {
+                clocks.forEach { it.animateFoldAppear(!hasJumped) }
+            }
+        }
+
+        override fun doze(fraction: Float) {
+            val (hasChanged, hasJumped) = dozeState.update(fraction)
+            if (hasChanged) {
+                clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }
+            }
+        }
+
+        override fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {
+            largeClock.moveForSplitShade(fromRect, toRect, fraction)
+        }
+
+        override val hasCustomPositionUpdatedAnimation: Boolean
+            get() = true
+    }
+
+    class AnimationState(
+        var fraction: Float,
+    ) {
+        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)
+            isActive = newFraction > fraction
+            fraction = newFraction
+            return Pair(wasActive != isActive, hasJumped)
+        }
+    }
+
+    override fun dump(pw: PrintWriter) {
+        pw.print("smallClock=")
+        smallClock.view.dump(pw)
+
+        pw.print("largeClock=")
+        largeClock.view.dump(pw)
+    }
+
+    companion object {
+        @VisibleForTesting const val DOZE_COLOR = Color.WHITE
+        private const val FORMAT_NUMBER = 1234567890
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
new file mode 100644
index 0000000..4c0504b
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.drawable.Drawable
+import android.view.LayoutInflater
+import com.android.systemui.customization.R
+import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.ClockId
+import com.android.systemui.plugins.ClockMetadata
+import com.android.systemui.plugins.ClockProvider
+
+private val TAG = DefaultClockProvider::class.simpleName
+const val DEFAULT_CLOCK_NAME = "Default Clock"
+const val DEFAULT_CLOCK_ID = "DEFAULT"
+
+/** Provides the default system clock */
+class DefaultClockProvider constructor(
+    val ctx: Context,
+    val layoutInflater: LayoutInflater,
+    val resources: Resources
+) : ClockProvider {
+    override fun getClocks(): List<ClockMetadata> =
+        listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
+
+    override fun createClock(id: ClockId): ClockController {
+        if (id != DEFAULT_CLOCK_ID) {
+            throw IllegalArgumentException("$id is unsupported by $TAG")
+        }
+
+        return DefaultClockController(ctx, layoutInflater, resources)
+    }
+
+    override fun getClockThumbnail(id: ClockId): Drawable? {
+        if (id != DEFAULT_CLOCK_ID) {
+            throw IllegalArgumentException("$id is unsupported by $TAG")
+        }
+
+        // TODO: Update placeholder to actual resource
+        return resources.getDrawable(R.drawable.clock_default_thumbnail, null)
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
new file mode 100644
index 0000000..cb1a5f9
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
@@ -0,0 +1,198 @@
+/*
+ * 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.shared.quickaffordance.data.content
+
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+
+class FakeKeyguardQuickAffordanceProviderClient(
+    slots: List<KeyguardQuickAffordanceProviderClient.Slot> =
+        listOf(
+            KeyguardQuickAffordanceProviderClient.Slot(
+                id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                capacity = 1,
+            ),
+            KeyguardQuickAffordanceProviderClient.Slot(
+                id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                capacity = 1,
+            ),
+        ),
+    affordances: List<KeyguardQuickAffordanceProviderClient.Affordance> =
+        listOf(
+            KeyguardQuickAffordanceProviderClient.Affordance(
+                id = AFFORDANCE_1,
+                name = AFFORDANCE_1,
+                iconResourceId = 1,
+            ),
+            KeyguardQuickAffordanceProviderClient.Affordance(
+                id = AFFORDANCE_2,
+                name = AFFORDANCE_2,
+                iconResourceId = 2,
+            ),
+            KeyguardQuickAffordanceProviderClient.Affordance(
+                id = AFFORDANCE_3,
+                name = AFFORDANCE_3,
+                iconResourceId = 3,
+            ),
+        ),
+    flags: List<KeyguardQuickAffordanceProviderClient.Flag> =
+        listOf(
+            KeyguardQuickAffordanceProviderClient.Flag(
+                name = KeyguardQuickAffordanceProviderContract.FlagsTable.FLAG_NAME_FEATURE_ENABLED,
+                value = true,
+            )
+        ),
+) : KeyguardQuickAffordanceProviderClient {
+
+    private val slots = MutableStateFlow(slots)
+    private val affordances = MutableStateFlow(affordances)
+    private val flags = MutableStateFlow(flags)
+
+    private val selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
+
+    override suspend fun insertSelection(slotId: String, affordanceId: String) {
+        val slotCapacity =
+            querySlots().find { it.id == slotId }?.capacity
+                ?: error("Slot with ID \"$slotId\" not found!")
+        val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList()
+        while (affordances.size + 1 > slotCapacity) {
+            affordances.removeAt(0)
+        }
+        affordances.remove(affordanceId)
+        affordances.add(affordanceId)
+        selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
+    }
+
+    override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> {
+        return slots.value
+    }
+
+    override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> {
+        return flags.value
+    }
+
+    override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> {
+        return slots.asStateFlow()
+    }
+
+    override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> {
+        return flags.asStateFlow()
+    }
+
+    override suspend fun queryAffordances():
+        List<KeyguardQuickAffordanceProviderClient.Affordance> {
+        return affordances.value
+    }
+
+    override fun observeAffordances():
+        Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> {
+        return affordances.asStateFlow()
+    }
+
+    override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> {
+        return toSelectionList(selections.value, affordances.value)
+    }
+
+    override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> {
+        return combine(selections, affordances) { selections, affordances ->
+            toSelectionList(selections, affordances)
+        }
+    }
+
+    override suspend fun deleteSelection(slotId: String, affordanceId: String) {
+        val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList()
+        affordances.remove(affordanceId)
+
+        selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
+    }
+
+    override suspend fun deleteAllSelections(slotId: String) {
+        selections.value = selections.value.toMutableMap().apply { this[slotId] = emptyList() }
+    }
+
+    override suspend fun getAffordanceIcon(iconResourceId: Int, tintColor: Int): Drawable {
+        return when (iconResourceId) {
+            1 -> ICON_1
+            2 -> ICON_2
+            3 -> ICON_3
+            else -> BitmapDrawable()
+        }
+    }
+
+    fun setFlag(
+        name: String,
+        value: Boolean,
+    ) {
+        flags.value =
+            flags.value.toMutableList().apply {
+                removeIf { it.name == name }
+                add(KeyguardQuickAffordanceProviderClient.Flag(name = name, value = value))
+            }
+    }
+
+    fun setSlotCapacity(slotId: String, capacity: Int) {
+        slots.value =
+            slots.value.toMutableList().apply {
+                val index = indexOfFirst { it.id == slotId }
+                check(index != -1) { "Slot with ID \"$slotId\" doesn't exist!" }
+                set(
+                    index,
+                    KeyguardQuickAffordanceProviderClient.Slot(id = slotId, capacity = capacity)
+                )
+            }
+    }
+
+    fun addAffordance(affordance: KeyguardQuickAffordanceProviderClient.Affordance): Int {
+        affordances.value = affordances.value + listOf(affordance)
+        return affordances.value.size - 1
+    }
+
+    private fun toSelectionList(
+        selections: Map<String, List<String>>,
+        affordances: List<KeyguardQuickAffordanceProviderClient.Affordance>,
+    ): List<KeyguardQuickAffordanceProviderClient.Selection> {
+        return selections
+            .map { (slotId, affordanceIds) ->
+                affordanceIds.map { affordanceId ->
+                    val affordanceName =
+                        affordances.find { it.id == affordanceId }?.name
+                            ?: error("No affordance with ID of \"$affordanceId\"!")
+                    KeyguardQuickAffordanceProviderClient.Selection(
+                        slotId = slotId,
+                        affordanceId = affordanceId,
+                        affordanceName = affordanceName,
+                    )
+                }
+            }
+            .flatten()
+    }
+
+    companion object {
+        const val AFFORDANCE_1 = "affordance_1"
+        const val AFFORDANCE_2 = "affordance_2"
+        const val AFFORDANCE_3 = "affordance_3"
+        val ICON_1 = BitmapDrawable()
+        val ICON_2 = BitmapDrawable()
+        val ICON_3 = BitmapDrawable()
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt
new file mode 100644
index 0000000..3213b2e
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt
@@ -0,0 +1,479 @@
+/*
+ * 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.shared.quickaffordance.data.content
+
+import android.annotation.SuppressLint
+import android.content.ContentValues
+import android.content.Context
+import android.database.ContentObserver
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import androidx.annotation.DrawableRes
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/** Client for using a content provider implementing the [Contract]. */
+interface KeyguardQuickAffordanceProviderClient {
+
+    /**
+     * Selects an affordance with the given ID for a slot on the lock screen with the given ID.
+     *
+     * Note that the maximum number of selected affordances on this slot is automatically enforced.
+     * Selecting a slot that is already full (e.g. already has a number of selected affordances at
+     * its maximum capacity) will automatically remove the oldest selected affordance before adding
+     * the one passed in this call. Additionally, selecting an affordance that's already one of the
+     * selected affordances on the slot will move the selected affordance to the newest location in
+     * the slot.
+     */
+    suspend fun insertSelection(
+        slotId: String,
+        affordanceId: String,
+    )
+
+    /** Returns all available slots supported by the device. */
+    suspend fun querySlots(): List<Slot>
+
+    /** Returns the list of flags. */
+    suspend fun queryFlags(): List<Flag>
+
+    /**
+     * Returns [Flow] for observing the collection of slots.
+     *
+     * @see [querySlots]
+     */
+    fun observeSlots(): Flow<List<Slot>>
+
+    /**
+     * Returns [Flow] for observing the collection of flags.
+     *
+     * @see [queryFlags]
+     */
+    fun observeFlags(): Flow<List<Flag>>
+
+    /**
+     * Returns all available affordances supported by the device, regardless of current slot
+     * placement.
+     */
+    suspend fun queryAffordances(): List<Affordance>
+
+    /**
+     * Returns [Flow] for observing the collection of affordances.
+     *
+     * @see [queryAffordances]
+     */
+    fun observeAffordances(): Flow<List<Affordance>>
+
+    /** Returns the current slot-affordance selections. */
+    suspend fun querySelections(): List<Selection>
+
+    /**
+     * Returns [Flow] for observing the collection of selections.
+     *
+     * @see [querySelections]
+     */
+    fun observeSelections(): Flow<List<Selection>>
+
+    /** Unselects an affordance with the given ID from the slot with the given ID. */
+    suspend fun deleteSelection(
+        slotId: String,
+        affordanceId: String,
+    )
+
+    /** Unselects all affordances from the slot with the given ID. */
+    suspend fun deleteAllSelections(
+        slotId: String,
+    )
+
+    /** Returns a [Drawable] with the given ID, loaded from the system UI package. */
+    suspend fun getAffordanceIcon(
+        @DrawableRes iconResourceId: Int,
+        tintColor: Int = Color.WHITE,
+    ): Drawable
+
+    /** Models a slot. A position that quick affordances can be positioned in. */
+    data class Slot(
+        /** Unique ID of the slot. */
+        val id: String,
+        /**
+         * The maximum number of quick affordances that are allowed to be positioned in this slot.
+         */
+        val capacity: Int,
+    )
+
+    /**
+     * Models a quick affordance. An action that can be selected by the user to appear in one or
+     * more slots on the lock screen.
+     */
+    data class Affordance(
+        /** Unique ID of the quick affordance. */
+        val id: String,
+        /** User-facing label for this affordance. */
+        val name: String,
+        /**
+         * Resource ID for the user-facing icon for this affordance. This resource is hosted by the
+         * System UI process so it must be used with
+         * `PackageManager.getResourcesForApplication(String)`.
+         */
+        val iconResourceId: Int,
+        /**
+         * Whether the affordance is enabled. Disabled affordances should be shown on the picker but
+         * should be rendered as "disabled". When tapped, the enablement properties should be used
+         * to populate UI that would explain to the user what to do in order to re-enable this
+         * affordance.
+         */
+        val isEnabled: Boolean = true,
+        /**
+         * If the affordance is disabled, this is a set of instruction messages to be shown to the
+         * user when the disabled affordance is selected. The instructions should help the user
+         * figure out what to do in order to re-neable this affordance.
+         */
+        val enablementInstructions: List<String>? = null,
+        /**
+         * If the affordance is disabled, this is a label for a button shown together with the set
+         * of instruction messages when the disabled affordance is selected. The button should help
+         * send the user to a flow that would help them achieve the instructions and re-enable this
+         * affordance.
+         *
+         * If `null`, the button should not be shown.
+         */
+        val enablementActionText: String? = null,
+        /**
+         * If the affordance is disabled, this is a "component name" of the format
+         * `packageName/action` to be used as an `Intent` for `startActivity` when the action button
+         * (shown together with the set of instruction messages when the disabled affordance is
+         * selected) is clicked by the user. The button should help send the user to a flow that
+         * would help them achieve the instructions and re-enable this affordance.
+         *
+         * If `null`, the button should not be shown.
+         */
+        val enablementActionComponentName: String? = null,
+    )
+
+    /** Models a selection of a quick affordance on a slot. */
+    data class Selection(
+        /** The unique ID of the slot. */
+        val slotId: String,
+        /** The unique ID of the quick affordance. */
+        val affordanceId: String,
+        /** The user-visible label for the quick affordance. */
+        val affordanceName: String,
+    )
+
+    /** Models a System UI flag. */
+    data class Flag(
+        /** The name of the flag. */
+        val name: String,
+        /** The value of the flag. */
+        val value: Boolean,
+    )
+}
+
+class KeyguardQuickAffordanceProviderClientImpl(
+    private val context: Context,
+    private val backgroundDispatcher: CoroutineDispatcher,
+) : KeyguardQuickAffordanceProviderClient {
+
+    override suspend fun insertSelection(
+        slotId: String,
+        affordanceId: String,
+    ) {
+        withContext(backgroundDispatcher) {
+            context.contentResolver.insert(
+                Contract.SelectionTable.URI,
+                ContentValues().apply {
+                    put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
+                    put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
+                }
+            )
+        }
+    }
+
+    override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> {
+        return withContext(backgroundDispatcher) {
+            context.contentResolver
+                .query(
+                    Contract.SlotTable.URI,
+                    null,
+                    null,
+                    null,
+                    null,
+                )
+                ?.use { cursor ->
+                    buildList {
+                        val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
+                        val capacityColumnIndex =
+                            cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
+                        if (idColumnIndex == -1 || capacityColumnIndex == -1) {
+                            return@buildList
+                        }
+
+                        while (cursor.moveToNext()) {
+                            add(
+                                KeyguardQuickAffordanceProviderClient.Slot(
+                                    id = cursor.getString(idColumnIndex),
+                                    capacity = cursor.getInt(capacityColumnIndex),
+                                )
+                            )
+                        }
+                    }
+                }
+        }
+            ?: emptyList()
+    }
+
+    override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> {
+        return withContext(backgroundDispatcher) {
+            context.contentResolver
+                .query(
+                    Contract.FlagsTable.URI,
+                    null,
+                    null,
+                    null,
+                    null,
+                )
+                ?.use { cursor ->
+                    buildList {
+                        val nameColumnIndex =
+                            cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME)
+                        val valueColumnIndex =
+                            cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE)
+                        if (nameColumnIndex == -1 || valueColumnIndex == -1) {
+                            return@buildList
+                        }
+
+                        while (cursor.moveToNext()) {
+                            add(
+                                KeyguardQuickAffordanceProviderClient.Flag(
+                                    name = cursor.getString(nameColumnIndex),
+                                    value = cursor.getInt(valueColumnIndex) == 1,
+                                )
+                            )
+                        }
+                    }
+                }
+        }
+            ?: emptyList()
+    }
+
+    override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> {
+        return observeUri(Contract.SlotTable.URI).map { querySlots() }
+    }
+
+    override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> {
+        return observeUri(Contract.FlagsTable.URI).map { queryFlags() }
+    }
+
+    override suspend fun queryAffordances():
+        List<KeyguardQuickAffordanceProviderClient.Affordance> {
+        return withContext(backgroundDispatcher) {
+            context.contentResolver
+                .query(
+                    Contract.AffordanceTable.URI,
+                    null,
+                    null,
+                    null,
+                    null,
+                )
+                ?.use { cursor ->
+                    buildList {
+                        val idColumnIndex =
+                            cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
+                        val nameColumnIndex =
+                            cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
+                        val iconColumnIndex =
+                            cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
+                        val isEnabledColumnIndex =
+                            cursor.getColumnIndex(Contract.AffordanceTable.Columns.IS_ENABLED)
+                        val enablementInstructionsColumnIndex =
+                            cursor.getColumnIndex(
+                                Contract.AffordanceTable.Columns.ENABLEMENT_INSTRUCTIONS
+                            )
+                        val enablementActionTextColumnIndex =
+                            cursor.getColumnIndex(
+                                Contract.AffordanceTable.Columns.ENABLEMENT_ACTION_TEXT
+                            )
+                        val enablementComponentNameColumnIndex =
+                            cursor.getColumnIndex(
+                                Contract.AffordanceTable.Columns.ENABLEMENT_COMPONENT_NAME
+                            )
+                        if (
+                            idColumnIndex == -1 ||
+                                nameColumnIndex == -1 ||
+                                iconColumnIndex == -1 ||
+                                isEnabledColumnIndex == -1 ||
+                                enablementInstructionsColumnIndex == -1 ||
+                                enablementActionTextColumnIndex == -1 ||
+                                enablementComponentNameColumnIndex == -1
+                        ) {
+                            return@buildList
+                        }
+
+                        while (cursor.moveToNext()) {
+                            add(
+                                KeyguardQuickAffordanceProviderClient.Affordance(
+                                    id = cursor.getString(idColumnIndex),
+                                    name = cursor.getString(nameColumnIndex),
+                                    iconResourceId = cursor.getInt(iconColumnIndex),
+                                    isEnabled = cursor.getInt(isEnabledColumnIndex) == 1,
+                                    enablementInstructions =
+                                        cursor
+                                            .getString(enablementInstructionsColumnIndex)
+                                            ?.split(
+                                                Contract.AffordanceTable
+                                                    .ENABLEMENT_INSTRUCTIONS_DELIMITER
+                                            ),
+                                    enablementActionText =
+                                        cursor.getString(enablementActionTextColumnIndex),
+                                    enablementActionComponentName =
+                                        cursor.getString(enablementComponentNameColumnIndex),
+                                )
+                            )
+                        }
+                    }
+                }
+        }
+            ?: emptyList()
+    }
+
+    override fun observeAffordances():
+        Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> {
+        return observeUri(Contract.AffordanceTable.URI).map { queryAffordances() }
+    }
+
+    override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> {
+        return withContext(backgroundDispatcher) {
+            context.contentResolver
+                .query(
+                    Contract.SelectionTable.URI,
+                    null,
+                    null,
+                    null,
+                    null,
+                )
+                ?.use { cursor ->
+                    buildList {
+                        val slotIdColumnIndex =
+                            cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
+                        val affordanceIdColumnIndex =
+                            cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
+                        val affordanceNameColumnIndex =
+                            cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME)
+                        if (
+                            slotIdColumnIndex == -1 ||
+                                affordanceIdColumnIndex == -1 ||
+                                affordanceNameColumnIndex == -1
+                        ) {
+                            return@buildList
+                        }
+
+                        while (cursor.moveToNext()) {
+                            add(
+                                KeyguardQuickAffordanceProviderClient.Selection(
+                                    slotId = cursor.getString(slotIdColumnIndex),
+                                    affordanceId = cursor.getString(affordanceIdColumnIndex),
+                                    affordanceName = cursor.getString(affordanceNameColumnIndex),
+                                )
+                            )
+                        }
+                    }
+                }
+        }
+            ?: emptyList()
+    }
+
+    override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> {
+        return observeUri(Contract.SelectionTable.URI).map { querySelections() }
+    }
+
+    override suspend fun deleteSelection(
+        slotId: String,
+        affordanceId: String,
+    ) {
+        withContext(backgroundDispatcher) {
+            context.contentResolver.delete(
+                Contract.SelectionTable.URI,
+                "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
+                    " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
+                arrayOf(
+                    slotId,
+                    affordanceId,
+                ),
+            )
+        }
+    }
+
+    override suspend fun deleteAllSelections(
+        slotId: String,
+    ) {
+        withContext(backgroundDispatcher) {
+            context.contentResolver.delete(
+                Contract.SelectionTable.URI,
+                Contract.SelectionTable.Columns.SLOT_ID,
+                arrayOf(
+                    slotId,
+                ),
+            )
+        }
+    }
+
+    @SuppressLint("UseCompatLoadingForDrawables")
+    override suspend fun getAffordanceIcon(
+        @DrawableRes iconResourceId: Int,
+        tintColor: Int,
+    ): Drawable {
+        return withContext(backgroundDispatcher) {
+            context.packageManager
+                .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
+                .getDrawable(iconResourceId, context.theme)
+                .apply { setTint(tintColor) }
+        }
+    }
+
+    private fun observeUri(
+        uri: Uri,
+    ): Flow<Unit> {
+        return callbackFlow {
+                val observer =
+                    object : ContentObserver(null) {
+                        override fun onChange(selfChange: Boolean) {
+                            trySend(Unit)
+                        }
+                    }
+
+                context.contentResolver.registerContentObserver(
+                    uri,
+                    /* notifyForDescendants= */ true,
+                    observer,
+                )
+
+                awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+            }
+            .onStart { emit(Unit) }
+    }
+
+    companion object {
+        private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
new file mode 100644
index 0000000..17be74b
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.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.systemui.shared.quickaffordance.data.content
+
+import android.content.ContentResolver
+import android.net.Uri
+
+/** Contract definitions for querying content about keyguard quick affordances. */
+object KeyguardQuickAffordanceProviderContract {
+
+    const val AUTHORITY = "com.android.systemui.keyguard.quickaffordance"
+    const val PERMISSION = "android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES"
+
+    private val BASE_URI: Uri =
+        Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build()
+
+    /**
+     * Table for slots.
+     *
+     * Slots are positions where affordances can be placed on the lock screen. Affordances that are
+     * placed on slots are said to be "selected". The system supports the idea of multiple
+     * affordances per slot, though the implementation may limit the number of affordances on each
+     * slot.
+     *
+     * Supported operations:
+     * - Query - to know which slots are available, query the [SlotTable.URI] [Uri]. The result set
+     * will contain rows with the [SlotTable.Columns] columns.
+     */
+    object SlotTable {
+        const val TABLE_NAME = "slots"
+        val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+
+        object Columns {
+            /** String. Unique ID for this slot. */
+            const val ID = "id"
+            /** Integer. The maximum number of affordances that can be placed in the slot. */
+            const val CAPACITY = "capacity"
+        }
+    }
+
+    /**
+     * Table for affordances.
+     *
+     * Affordances are actions/buttons that the user can execute. They are placed on slots on the
+     * lock screen.
+     *
+     * Supported operations:
+     * - Query - to know about all the affordances that are available on the device, regardless of
+     * which ones are currently selected, query the [AffordanceTable.URI] [Uri]. The result set will
+     * contain rows, each with the columns specified in [AffordanceTable.Columns].
+     */
+    object AffordanceTable {
+        const val TABLE_NAME = "affordances"
+        val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+        const val ENABLEMENT_INSTRUCTIONS_DELIMITER = "]["
+        const val COMPONENT_NAME_SEPARATOR = "/"
+
+        object Columns {
+            /** String. Unique ID for this affordance. */
+            const val ID = "id"
+            /** String. User-visible name for this affordance. */
+            const val NAME = "name"
+            /**
+             * Integer. Resource ID for the drawable to load for this affordance. This is a resource
+             * ID from the system UI package.
+             */
+            const val ICON = "icon"
+            /** Integer. `1` if the affordance is enabled or `0` if it disabled. */
+            const val IS_ENABLED = "is_enabled"
+            /**
+             * String. List of strings, delimited by [ENABLEMENT_INSTRUCTIONS_DELIMITER] to be shown
+             * to the user if the affordance is disabled and the user selects the affordance. The
+             * first one is a title while the rest are the steps needed to re-enable the affordance.
+             */
+            const val ENABLEMENT_INSTRUCTIONS = "enablement_instructions"
+            /**
+             * String. Optional label for a button that, when clicked, opens a destination activity
+             * where the user can re-enable the disabled affordance.
+             */
+            const val ENABLEMENT_ACTION_TEXT = "enablement_action_text"
+            /**
+             * String. Optional package name and activity action string, delimited by
+             * [COMPONENT_NAME_SEPARATOR] to use with an `Intent` to start an activity that opens a
+             * destination where the user can re-enable the disabled affordance.
+             */
+            const val ENABLEMENT_COMPONENT_NAME = "enablement_action_intent"
+        }
+    }
+
+    /**
+     * Table for selections.
+     *
+     * Selections are pairs of slot and affordance IDs.
+     *
+     * Supported operations:
+     * - Insert - to insert an affordance and place it in a slot, insert values for the columns into
+     * the [SelectionTable.URI] [Uri]. The maximum capacity rule is enforced by the system.
+     * Selecting a new affordance for a slot that is already full will automatically remove the
+     * oldest affordance from the slot.
+     * - Query - to know which affordances are set on which slots, query the [SelectionTable.URI]
+     * [Uri]. The result set will contain rows, each of which with the columns from
+     * [SelectionTable.Columns].
+     * - Delete - to unselect an affordance, removing it from a slot, delete from the
+     * [SelectionTable.URI] [Uri], passing in values for each column.
+     */
+    object SelectionTable {
+        const val TABLE_NAME = "selections"
+        val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+
+        object Columns {
+            /** String. Unique ID for the slot. */
+            const val SLOT_ID = "slot_id"
+            /** String. Unique ID for the selected affordance. */
+            const val AFFORDANCE_ID = "affordance_id"
+            /** String. Human-readable name for the affordance. */
+            const val AFFORDANCE_NAME = "affordance_name"
+        }
+    }
+
+    /**
+     * Table for flags.
+     *
+     * Flags are key-value pairs.
+     *
+     * Supported operations:
+     * - Query - to know the values of flags, query the [FlagsTable.URI] [Uri]. The result set will
+     * contain rows, each of which with the columns from [FlagsTable.Columns].
+     */
+    object FlagsTable {
+        const val TABLE_NAME = "flags"
+        val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+
+        /**
+         * Flag denoting whether the customizable lock screen quick affordances feature is enabled.
+         */
+        const val FLAG_NAME_FEATURE_ENABLED = "is_feature_enabled"
+
+        object Columns {
+            /** String. Unique ID for the flag. */
+            const val NAME = "name"
+            /** Int. Value of the flag. `1` means `true` and `0` means `false`. */
+            const val VALUE = "value"
+        }
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt
new file mode 100644
index 0000000..2dc7a28
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.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.shared.keyguard.shared.model
+
+/**
+ * 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/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
index 95b986f..01d4f00 100644
--- a/packages/SystemUI/docs/device-entry/quickaffordance.md
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -1,25 +1,73 @@
 # Keyguard Quick Affordances
-These are interactive UI elements that appear at the bottom of the lockscreen when the device is
-locked. They allow the user to perform quick actions without unlocking their device. For example:
+Quick Affordances are interactive UI elements that appear on the lock screen when the device is
+locked. They allow the user to perform quick actions without necessarily unlocking their device. For example:
 opening an screen that lets them control the smart devices in their home, access their touch-to-pay
-credit card, etc.
+credit card, turn on the flashlight, etc.
 
-## Adding a new Quick Affordance
-### Step 1: create a new quick affordance config
-* Create a new class under the [systemui/keyguard/domain/quickaffordance](../../src/com/android/systemui/keyguard/domain/quickaffordance) directory
-* Please make sure that the class is injected through the Dagger dependency injection system by using the `@Inject` annotation on its main constructor and the `@SysUISingleton` annotation at class level, to make sure only one instance of the class is ever instantiated
-* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
-  * The `state` Flow property must emit `State.Hidden` when the feature is not enabled!
-  * It is safe to assume that `onQuickAffordanceClicked` will not be invoked if-and-only-if the previous rule is followed
-  * When implementing `onQuickAffordanceClicked`, the implementation can do something or it can ask the framework to start an activity using an `Intent` provided by the implementation
-* Please include a unit test for your new implementation under [the correct directory](../../tests/src/com/android/systemui/keyguard/domain/quickaffordance)
+## Creating a new Quick Affordance
+All Quick Affordances are defined in System UI code.
 
-### Step 2: choose a position and priority
-* Add the new class as a dependency in the constructor of [KeyguardQuickAffordanceRegistry](../../src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt)
-* Place the new class in one of the available positions in the `configsByPosition` property, note:
-  * In each position, there is a list. The order matters. The order of that list is the priority order in which the framework considers each config. The first config whose state property returns `State.Visible` determines the button that is shown for that position
-  * Please only add to one position. The framework treats each position individually and there is no good way to prevent the same config from making its button appear in more than one position at the same time
+To implement a new Quick Affordance, a developer may add a new implementation of `KeyguardQuickAffordanceConfig` in the `packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance` package/directory and add it to the set defined in `KeyguardDataQuickAffordanceModule`.
 
-### Step 3: manually verify the new quick affordance
-* Build and launch SysUI on a device
-* Verify that the quick affordance button for the new implementation is correctly visible and clicking it does the right thing
+Tests belong in the `packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance` package. This should be enough for the system to pick up the new config and make it available for selection by the user.
+
+## Slots
+"Slots" is what we call the position of Quick Affordances on the lock screen. Each slot has a unique ID and a capacity denoting how many Quick Affordances can be "placed" in that slot.
+
+By default, AOSP ships with a "bottom right" and a "bottom left" slot, each with a slot capacity of `1`, allowing only one Quick Affordance on each side of the lock screen.
+
+### Customizing Slots
+OEMs may choose to override the IDs and number of slots and/or override the default capacities. This can be achieved by overridding the `config_keyguardQuickAffordanceSlots` resource in `packages/SystemUI/res/values/config.xml`.
+
+### Default Quick Affordances
+OEMs may also choose to predefine default Quick Affordances for each slot. To achieve this, a developer may override the `config_keyguardQuickAffordanceDefaults` resource in `packages/SystemUI/res/values/config.xml`. Note that defaults only work until the user of the device selects a different quick affordance for that slot, even if they select the "None" option.
+
+## Selections
+"Selections" are many-to-many relationships between slots and quick affordances. We add a selection when the user selects a quick affordance for a specific slot. We remove a selection when the user un-selects a quick affordance in a slot or when the user selects an additional quick affordance for a slot that is already at capacity. The definition of each slot tells us the maximum number of quick affordances that may be selected for each slot.
+
+## Building a Quick affordance Picker Experience
+This section describes how to implement a potential picker, selector, or configuration experience for quick affordances.
+
+### Accessing Quick Affordance Data
+Quick Affordances structured data are exposed to other applications through the `KeyguardQuickAffordanceProvider` content provider which is owned by the System UI process.
+
+To access this content provider, applications must have the `android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES` permission which is a signature and privileged permission, limiting access to system apps or apps signed by the same signature as System UI. The `KeyguardQuickAffordanceProviderContract` file defines the content provider schema for consumers.
+
+Generally speaking, there are three important tables served by the content provider: `slots`, `affordances`, and `selections`. There is also a `flags` table, but that's not important and may be ignored.
+
+The `slots`, `affordances`, and `selections` tables may be queried using their `Uri` resulting with a `Cursor` where each row represents a slot, affordance, or selection, respectively. Note that the affordance list does not include the "None" option.
+
+### Modifying Quick Affordance Data
+The `selections` table accepts `insert` or `delete` operations to either add or remove a quick affordance on a slot.
+* To add a selection of a quick affordance on a slot, execute the `insert` operation on the `selections` table `Uri` and include the `slot_id` of the slot and `affordance_id` of the affordance, both in the `ContentValues`
+* To remove a selection of a specific quick affordance from a slot, execute the `delete` operation on the `selections` table `Uri` and include the `slot_id` of the slot and the `affordance_id` of the affordance to remove as the first and second selection arguments, respectively
+* To remove all selections of any currently-selected quick affordance from a specific slot, repeat the above, but omit the `affordance_id`
+
+### The Picker Experience
+A picker experience may:
+* Show the list of available slots based on the result of the `slots` table query
+* Show the list of available quick affordances on the device (regardless of selection) based on the result of the `affordances` table query
+* Show the quick affordances already selected for each slot based on the result of the `selections` table query
+* Select one quick affordance per slot at a time
+* Unselect an already-selected quick affordance from a slot
+* Unselect all already-selected quick affordances from a slot
+
+## Debugging
+To see the current state of the system, you can run `dumpsys`:
+
+```
+$ adb shell dumpsys activity service com.android.systemui/.SystemUIService KeyguardQuickAffordances
+```
+
+The output will spell out the current slot configuration, selections, and collection of available affordances, for example:
+```
+    KeyguardQuickAffordances:
+    ----------------------------------------------------------------------------
+    Slots & selections:
+        bottom_start: home (capacity = 1)
+        bottom_end is empty (capacity = 1)
+    Available affordances on device:
+        home ("Home")
+        wallet ("Wallet")
+        qr_code_scanner ("QR code scanner")
+```
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 0f037e4..7290e7e 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -16,7 +16,6 @@
 -packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
 -packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
 -packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
 -packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt
 -packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
 -packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
@@ -24,11 +23,9 @@
 -packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
 -packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
 -packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
--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/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+-packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+-packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.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 +680,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
@@ -758,7 +753,7 @@
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 89f5c2c..66e44b9 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -70,10 +70,10 @@
     }
 
     /** Optional method for dumping debug information */
-    fun dump(pw: PrintWriter) { }
+    fun dump(pw: PrintWriter) {}
 
     /** Optional method for debug logging */
-    fun setLogBuffer(logBuffer: LogBuffer) { }
+    fun setLogBuffer(logBuffer: LogBuffer) {}
 }
 
 /** Interface for a specific clock face version rendered by the clock */
@@ -88,40 +88,37 @@
 /** Events that should call when various rendering parameters change */
 interface ClockEvents {
     /** Call every time tick */
-    fun onTimeTick() { }
+    fun onTimeTick() {}
 
     /** Call whenever timezone changes */
-    fun onTimeZoneChanged(timeZone: TimeZone) { }
+    fun onTimeZoneChanged(timeZone: TimeZone) {}
 
     /** Call whenever the text time format changes (12hr vs 24hr) */
-    fun onTimeFormatChanged(is24Hr: Boolean) { }
+    fun onTimeFormatChanged(is24Hr: Boolean) {}
 
     /** Call whenever the locale changes */
-    fun onLocaleChanged(locale: Locale) { }
-
-    /** Call whenever font settings change */
-    fun onFontSettingChanged() { }
+    fun onLocaleChanged(locale: Locale) {}
 
     /** Call whenever the color palette should update */
-    fun onColorPaletteChanged(resources: Resources) { }
+    fun onColorPaletteChanged(resources: Resources) {}
 }
 
 /** Methods which trigger various clock animations */
 interface ClockAnimations {
     /** Runs an enter animation (if any) */
-    fun enter() { }
+    fun enter() {}
 
     /** Sets how far into AOD the device currently is. */
-    fun doze(fraction: Float) { }
+    fun doze(fraction: Float) {}
 
     /** Sets how far into the folding animation the device is. */
-    fun fold(fraction: Float) { }
+    fun fold(fraction: Float) {}
 
     /** Runs the battery animation (if any). */
-    fun charge() { }
+    fun charge() {}
 
     /** Move the clock, for example, if the notification tray appears in split-shade mode. */
-    fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) { }
+    fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {}
 
     /**
      * Whether this clock has a custom position update animation. If true, the keyguard will call
@@ -135,11 +132,26 @@
 /** Events that have specific data about the related face */
 interface ClockFaceEvents {
     /** Region Darkness specific to the clock face */
-    fun onRegionDarknessChanged(isDark: Boolean) { }
+    fun onRegionDarknessChanged(isDark: Boolean) {}
+
+    /**
+     * Call whenever font settings change. Pass in a target font size in pixels. The specific clock
+     * design is allowed to ignore this target size on a case-by-case basis.
+     */
+    fun onFontSettingChanged(fontSizePx: Float) {}
+
+    /**
+     * Target region information for the clock face. For small clock, this will match the bounds of
+     * the parent view mostly, but have a target height based on the height of the default clock.
+     * For large clocks, the parent view is the entire device size, but most clocks will want to
+     * render within the centered targetRect to avoid obstructing other elements. The specified
+     * targetRegion is relative to the parent view.
+     */
+    fun onTargetRegionChanged(targetRegion: Rect?) {}
 }
 
 /** Some data about a clock design */
 data class ClockMetadata(
     val clockId: ClockId,
-    val name: String
+    val name: String,
 )
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
index c50340c..e52a57f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
@@ -82,6 +82,18 @@
     boolean isFalseTap(@Penalty int penalty);
 
     /**
+     * Returns true if the FalsingManager thinks the last gesture was not a valid long tap.
+     *
+     * Use this method to validate a long tap for launching an action, like long press on a UMO
+     *
+     * The only parameter, penalty, indicates how much this should affect future gesture
+     * classifications if this long tap looks like a false.
+     * As long taps are hard to confirm as false or otherwise,
+     * a low penalty value is encouraged unless context indicates otherwise.
+     */
+    boolean isFalseLongTap(@Penalty int penalty);
+
+    /**
      * Returns true if the last two gestures do not look like a double tap.
      *
      * Only works on data that has already been reported to the FalsingManager. Be sure that
diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginManager.java
new file mode 100644
index 0000000..80c64cd
--- /dev/null
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginManager.java
@@ -0,0 +1,58 @@
+/*
+ * 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.plugins;
+
+import android.text.TextUtils;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+public interface PluginManager {
+
+    String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED";
+
+    // must be one of the channels created in NotificationChannels.java
+    String NOTIFICATION_CHANNEL_ID = "ALR";
+
+    /** Returns plugins that don't get disabled when an exceptoin occurs. */
+    String[] getPrivilegedPlugins();
+
+    /** */
+    <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls);
+    /** */
+    <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls,
+            boolean allowMultiple);
+    <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+            Class<T> cls);
+    <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+            Class<T> cls, boolean allowMultiple);
+
+    void removePluginListener(PluginListener<?> listener);
+
+    <T> boolean dependsOn(Plugin p, Class<T> cls);
+
+    class Helper {
+        public static <P> String getAction(Class<P> cls) {
+            ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
+            if (info == null) {
+                throw new RuntimeException(cls + " doesn't provide an interface");
+            }
+            if (TextUtils.isEmpty(info.action())) {
+                throw new RuntimeException(cls + " doesn't provide an action");
+            }
+            return info.action();
+        }
+    }
+
+}
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index a3b4b38..e598afe 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -25,6 +25,9 @@
 
 -keep class ** extends androidx.preference.PreferenceFragment
 -keep class com.android.systemui.tuner.*
+
+# The plugins subpackage acts as a shared library that might be referenced in
+# dynamically-loaded plugin APKs.
 -keep class com.android.systemui.plugins.** {
     *;
 }
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml
new file mode 100644
index 0000000..e850d68
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="#1f1f1f"
+      android:pathData="M8,22V11L6,8V2H18V8L16,11V22ZM12,15.5Q11.375,15.5 10.938,15.062Q10.5,14.625 10.5,14Q10.5,13.375 10.938,12.938Q11.375,12.5 12,12.5Q12.625,12.5 13.062,12.938Q13.5,13.375 13.5,14Q13.5,14.625 13.062,15.062Q12.625,15.5 12,15.5ZM8,5H16V4H8ZM16,7H8V7.4L10,10.4V20H14V10.4L16,7.4ZM12,12Z"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml
new file mode 100644
index 0000000..91b9ae5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="#1f1f1f"
+      android:pathData="M6,5V2H18V5ZM12,15.5Q12.625,15.5 13.062,15.062Q13.5,14.625 13.5,14Q13.5,13.375 13.062,12.938Q12.625,12.5 12,12.5Q11.375,12.5 10.938,12.938Q10.5,13.375 10.5,14Q10.5,14.625 10.938,15.062Q11.375,15.5 12,15.5ZM8,22V11L6,8V7H18V8L16,11V22Z"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
index 898935f..2cac9c7 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
@@ -21,8 +21,6 @@
     android:id="@+id/keyguard_bouncer_user_switcher"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:clipChildren="false"
-    android:clipToPadding="false"
     android:orientation="vertical"
     android:gravity="center"
     android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index d64587d..218c5cc 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -31,12 +31,13 @@
         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_marginTop="@dimen/keyguard_large_clock_top_margin"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
         android:clipChildren="false"
         android:visibility="gone" />
 
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index 2b7bdc2..c772c96 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -27,7 +27,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    androidprv:layout_maxWidth="@dimen/keyguard_security_width"
+    androidprv:layout_maxWidth="@dimen/biometric_auth_pattern_view_max_size"
     android:layout_gravity="center_horizontal|bottom"
     android:clipChildren="false"
     android:clipToPadding="false">
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..a948c04 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -23,7 +23,7 @@
     <string name="keyguard_enter_your_pin" msgid="5429932527814874032">"Enter your PIN"</string>
     <string name="keyguard_enter_your_pattern" msgid="351503370332324745">"Enter your pattern"</string>
     <string name="keyguard_enter_your_password" msgid="7225626204122735501">"Enter your password"</string>
-    <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Invalid card."</string>
+    <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Invalid Card."</string>
     <string name="keyguard_charged" msgid="5478247181205188995">"Charged"</string>
     <string name="keyguard_plugged_in_wireless" msgid="2537874724955057383">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging wirelessly"</string>
     <string name="keyguard_plugged_in_dock" msgid="2122073051904360987">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging"</string>
@@ -70,7 +70,7 @@
     <string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"Incorrect SIM PIN code you must now contact your carrier to unlock your device."</string>
     <string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{Incorrect SIM PIN code, you have # remaining attempt before you must contact your carrier to unlock your device.}other{Incorrect SIM PIN code, you have # remaining attempts. }}"</string>
     <string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"SIM is unusable. Contact your carrier."</string>
-    <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Incorrect SIM PUK code; you have # remaining attempt before SIM becomes permanently unusable.}other{Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}}"</string>
+    <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Incorrect SIM PUK code, you have # remaining attempt before SIM becomes permanently unusable.}other{Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}}"</string>
     <string name="kg_password_pin_failed" msgid="5136259126330604009">"SIM PIN operation failed!"</string>
     <string name="kg_password_puk_failed" msgid="6778867411556937118">"SIM PUK operation failed!"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Switch input method"</string>
@@ -78,21 +78,17 @@
     <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>
+    <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognized"</string>
     <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"To use Face Unlock, turn on camera access in Settings"</string>
     <string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Enter SIM PIN. You have # remaining attempt before you must contact your carrier to unlock your device.}other{Enter SIM PIN. You have # remaining attempts.}}"</string>
     <string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{SIM is now disabled. Enter PUK code to continue. You have # remaining attempt before SIM becomes permanently unusable. Contact carrier for details.}other{SIM is now disabled. Enter PUK code to continue. You have # remaining attempts before SIM becomes permanently unusable. Contact carrier for details.}}"</string>
     <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="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+    <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-h650dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
index 669f8fb..e5e17b7 100644
--- a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
@@ -17,4 +17,7 @@
 
 <resources>
     <dimen name="widget_big_font_size">54dp</dimen>
+
+    <!-- Margin above the ambient indication container -->
+    <dimen name="ambient_indication_container_margin_top">10dp</dimen>
 </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-land/dimens.xml b/packages/SystemUI/res-keyguard/values-land/dimens.xml
index a4e7a5f..f1aa544 100644
--- a/packages/SystemUI/res-keyguard/values-land/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-land/dimens.xml
@@ -27,4 +27,6 @@
     <integer name="scaled_password_text_size">26</integer>
 
     <dimen name="bouncer_user_switcher_y_trans">@dimen/status_bar_height</dimen>
+    <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
+    <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
 </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..57e5f8a 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -74,16 +74,13 @@
     <string name="kg_password_pin_failed" msgid="5136259126330604009">"Bewerking met pincode voor simkaart is mislukt."</string>
     <string name="kg_password_puk_failed" msgid="6778867411556937118">"Bewerking met pukcode voor simkaart is mislukt."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Invoermethode wijzigen"</string>
-    <string name="airplane_mode" msgid="2528005343938497866">"Vliegtuigmodus"</string>
+    <string name="airplane_mode" msgid="2528005343938497866">"Vliegtuig­modus"</string>
     <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-sw540dp-port/dimens.xml b/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml
deleted file mode 100644
index a3c37e4..0000000
--- a/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/assets/res/any/dimens.xml
-**
-** Copyright 2013, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT 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>
-    <!-- Height of the sliding KeyguardSecurityContainer
-        (includes 2x keyguard_security_view_top_margin) -->
-    <dimen name="keyguard_security_height">550dp</dimen>
-</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
index 1dc61c5..b7a1bb4 100644
--- a/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
@@ -17,10 +17,5 @@
 */
 -->
 <resources>
-
-    <!-- Height of the sliding KeyguardSecurityContainer
-         (includes 2x keyguard_security_view_top_margin) -->
-    <dimen name="keyguard_security_height">470dp</dimen>
-
     <dimen name="widget_big_font_size">100dp</dimen>
 </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-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 0a55cf7..6cc5b9d 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -29,9 +29,6 @@
          (includes 2x keyguard_security_view_top_margin) -->
     <dimen name="keyguard_security_height">420dp</dimen>
 
-    <!-- Max Height of the sliding KeyguardSecurityContainer
-         (includes 2x keyguard_security_view_top_margin) -->
-
     <!-- pin/password field max height -->
     <dimen name="keyguard_password_height">80dp</dimen>
 
@@ -41,6 +38,9 @@
     <!-- Minimum bottom margin under the security view -->
     <dimen name="keyguard_security_view_bottom_margin">60dp</dimen>
 
+    <!-- Margin above the ambient indication container -->
+    <dimen name="ambient_indication_container_margin_top">0dp</dimen>
+
     <dimen name="keyguard_eca_top_margin">18dp</dimen>
     <dimen name="keyguard_eca_bottom_margin">12dp</dimen>
 
@@ -124,6 +124,8 @@
     <dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen>
     <dimen name="bouncer_user_switcher_header_padding_end">44dp</dimen>
     <dimen name="bouncer_user_switcher_y_trans">0dp</dimen>
+    <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
+    <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
 
     <!-- 2 * the margin + size should equal the plus_margin -->
     <dimen name="user_switcher_icon_large_margin">16dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index a129fb6..da485a9 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -53,7 +53,7 @@
     <string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
 
     <!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited.  -->
-    <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging is paused to protect battery</string>
+    <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging paused to protect battery</string>
 
     <!-- On the keyguard screen, when pattern lock is disabled, only tell them to press menu to unlock.  This is shown in small font at the bottom. -->
     <string name="keyguard_instructions_when_pattern_disabled">Press Menu to unlock.</string>
diff --git a/packages/SystemUI/res-product/values-en-rCA/strings.xml b/packages/SystemUI/res-product/values-en-rCA/strings.xml
index 04e63f5..131c42a 100644
--- a/packages/SystemUI/res-product/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-product/values-en-rCA/strings.xml
@@ -25,7 +25,7 @@
     <string name="inattentive_sleep_warning_message" product="default" msgid="5693904520452332224">"The device will soon turn off; press to keep it on."</string>
     <string name="keyguard_missing_sim_message" product="tablet" msgid="5018086454277963787">"No SIM card in tablet."</string>
     <string name="keyguard_missing_sim_message" product="default" msgid="7053347843877341391">"No SIM card in phone."</string>
-    <string name="kg_invalid_confirm_pin_hint" product="default" msgid="6278551068943958651">"PIN codes do not match"</string>
+    <string name="kg_invalid_confirm_pin_hint" product="default" msgid="6278551068943958651">"PIN codes does not match"</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="302165994845009232">"You have incorrectly attempted to unlock the tablet <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, this tablet will be reset, which will delete all its data."</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="2594813176164266847">"You have incorrectly attempted to unlock the phone <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, this phone will be reset, which will delete all its data."</string>
     <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="8710104080409538587">"You have incorrectly attempted to unlock the tablet <xliff:g id="NUMBER">%d</xliff:g> times. This tablet will be reset, which will delete all its data."</string>
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml
new file mode 100644
index 0000000..eefe364
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml
@@ -0,0 +1,26 @@
+<!--

+    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.

+-->

+<shape xmlns:android="http://schemas.android.com/apk/res/android"

+    android:shape="oval">

+    <solid android:color="@color/accessibility_window_magnifier_button_bg" />

+    <size

+        android:width="40dp"

+        android:height="40dp"/>

+    <corners android:radius="2dp"/>

+    <stroke

+        android:color="@color/accessibility_window_magnifier_button_bg_stroke"

+        android:width="1dp" />

+ </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
index 88d8f78f..569ee76 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
@@ -30,7 +30,7 @@
     </item>
     <item android:id="@android:id/progress"
           android:gravity="center_vertical|fill_horizontal">
-            <com.android.systemui.util.RoundedCornerProgressDrawable
+            <com.android.systemui.util.BrightnessProgressDrawable
                 android:drawable="@drawable/brightness_progress_full_drawable"
             />
     </item>
diff --git a/core/res/res/drawable/grant_permissions_buttons_bottom.xml b/packages/SystemUI/res/drawable/grant_permissions_buttons_bottom.xml
similarity index 100%
rename from core/res/res/drawable/grant_permissions_buttons_bottom.xml
rename to packages/SystemUI/res/drawable/grant_permissions_buttons_bottom.xml
diff --git a/core/res/res/drawable/grant_permissions_buttons_top.xml b/packages/SystemUI/res/drawable/grant_permissions_buttons_top.xml
similarity index 100%
rename from core/res/res/drawable/grant_permissions_buttons_top.xml
rename to packages/SystemUI/res/drawable/grant_permissions_buttons_top.xml
diff --git a/packages/SystemUI/res/drawable/ic_doc_document.xml b/packages/SystemUI/res/drawable/ic_doc_document.xml
new file mode 100644
index 0000000..df9ddab
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_doc_document.xml
@@ -0,0 +1,24 @@
+<!--
+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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+            android:fillColor="#FF4883F3"
+            android:pathData="M19 3H5c-1.1 0,-2 .9,-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2,-.9 2,-2V5c0,-1.1,-.9,-2,-2,-2zm-1.99 6H7V7h10.01v2zm0 4H7v-2h10.01v2zm-3 4H7v-2h7.01v2z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_left.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_left.xml
new file mode 100644
index 0000000..d25cd65
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_left.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M0,24l0,-4l24,-0l0,4z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M0,24l0,-24l4,-0l0,24z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_right.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_right.xml
new file mode 100644
index 0000000..bb6df3a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_right.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M24,24l-4,-0l-0,-24l4,-0z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M24,24l-24,-0l-0,-4l24,-0z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_top_left.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_top_left.xml
new file mode 100644
index 0000000..8f25930
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_top_left.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M0,0h4v24h-4z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M0,0h24v4h-24z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_top_right.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_top_right.xml
new file mode 100644
index 0000000..291db44
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_top_right.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M24,0l-0,4l-24,0l-0,-4z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M24,0l-0,24l-4,0l-0,-24z"
+      android:strokeWidth="1"
+      android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_note_task_button.xml b/packages/SystemUI/res/drawable/ic_note_task_button.xml
new file mode 100644
index 0000000..bb5e224
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_note_task_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="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#636C6F"
+        android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
+    <path
+        android:fillColor="#636C6F"
+        android:fillType="evenOdd"
+        android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_ring_volume.xml b/packages/SystemUI/res/drawable/ic_ring_volume.xml
new file mode 100644
index 0000000..343fe5d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ring_volume.xml
@@ -0,0 +1,26 @@
+<!--
+    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="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/colorControlNormal">
+  <path
+      android:pathData="M11,7V2H13V7ZM17.6,9.85 L16.2,8.4 19.75,4.85 21.15,6.3ZM6.4,9.85 L2.85,6.3 4.25,4.85 7.8,8.4ZM12,12Q14.95,12 17.812,13.188Q20.675,14.375 22.9,16.75Q23.2,17.05 23.2,17.45Q23.2,17.85 22.9,18.15L20.6,20.4Q20.325,20.675 19.963,20.7Q19.6,20.725 19.3,20.5L16.4,18.3Q16.2,18.15 16.1,17.95Q16,17.75 16,17.5V14.65Q15.05,14.35 14.05,14.175Q13.05,14 12,14Q10.95,14 9.95,14.175Q8.95,14.35 8,14.65V17.5Q8,17.75 7.9,17.95Q7.8,18.15 7.6,18.3L4.7,20.5Q4.4,20.725 4.038,20.7Q3.675,20.675 3.4,20.4L1.1,18.15Q0.8,17.85 0.8,17.45Q0.8,17.05 1.1,16.75Q3.3,14.375 6.175,13.188Q9.05,12 12,12ZM6,15.35Q5.275,15.725 4.6,16.212Q3.925,16.7 3.2,17.3L4.2,18.3L6,16.9ZM18,15.4V16.9L19.8,18.3L20.8,17.35Q20.075,16.7 19.4,16.225Q18.725,15.75 18,15.4ZM6,15.35Q6,15.35 6,15.35Q6,15.35 6,15.35ZM18,15.4Q18,15.4 18,15.4Q18,15.4 18,15.4Z"
+      android:fillColor="?android:attr/colorPrimary"/>
+
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_ring_volume_off.xml b/packages/SystemUI/res/drawable/ic_ring_volume_off.xml
new file mode 100644
index 0000000..74f30d1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ring_volume_off.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/colorControlNormal">
+<path
+      android:pathData="M0.8,4.2l8.1,8.1c-2.2,0.5 -5.2,1.6 -7.8,4.4c-0.4,0.4 -0.4,1 0,1.4l2.3,2.3c0.3,0.3 0.9,0.4 1.3,0.1l2.9,-2.2C7.8,18.1 8,17.8 8,17.5v-2.9c0.9,-0.3 1.7,-0.5 2.7,-0.6l8.5,8.5l1.4,-1.4L2.2,2.8L0.8,4.2z"
+    android:fillColor="?android:attr/colorPrimary"/>
+  <path
+      android:pathData="M11,2h2v5h-2z"
+      android:fillColor="?android:attr/colorPrimary"/>
+  <path
+      android:pathData="M21.2,6.3l-1.4,-1.4l-3.6,3.6l1.4,1.4C17.6,9.8 21,6.3 21.2,6.3z"
+      android:fillColor="?android:attr/colorPrimary"/>
+  <path
+      android:pathData="M22.9,16.7c-2.8,-3 -6.2,-4.1 -8.4,-4.5l7.2,7.2l1.3,-1.3C23.3,17.7 23.3,17.1 22.9,16.7z"
+      android:fillColor="?android:attr/colorPrimary"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_speaker_mute.xml b/packages/SystemUI/res/drawable/ic_speaker_mute.xml
new file mode 100644
index 0000000..4e402cf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker_mute.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/textColorPrimary"
+    android:autoMirrored="true">
+    <path android:fillColor="#FFFFFFFF"
+        android:pathData="M19.8,22.6 L16.775,19.575Q16.15,19.975 15.45,20.263Q14.75,20.55 14,20.725V18.675Q14.35,18.55 14.688,18.425Q15.025,18.3 15.325,18.125L12,14.8V20L7,15H3V9H6.2L1.4,4.2L2.8,2.8L21.2,21.2ZM19.6,16.8 L18.15,15.35Q18.575,14.575 18.788,13.725Q19,12.875 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,13.3 20.638,14.525Q20.275,15.75 19.6,16.8ZM16.25,13.45 L14,11.2V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,12.375 16.438,12.738Q16.375,13.1 16.25,13.45ZM12,9.2 L9.4,6.6 12,4ZM10,15.15V12.8L8.2,11H5V13H7.85ZM9.1,11.9Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_speaker_on.xml b/packages/SystemUI/res/drawable/ic_speaker_on.xml
new file mode 100644
index 0000000..2a90e05
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker_on.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/textColorPrimary"
+    android:autoMirrored="true">
+    <path android:fillColor="#FFFFFFFF"
+        android:pathData="M14,20.725V18.675Q16.25,18.025 17.625,16.175Q19,14.325 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,15.15 19.05,17.587Q17.1,20.025 14,20.725ZM3,15V9H7L12,4V20L7,15ZM14,16V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,13.275 15.838,14.362Q15.175,15.45 14,16ZM10,8.85 L7.85,11H5V13H7.85L10,15.15ZM7.5,12Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_watch.xml b/packages/SystemUI/res/drawable/ic_watch.xml
new file mode 100644
index 0000000..8ff880c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_watch.xml
@@ -0,0 +1,29 @@
+<?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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M16,0L8,0l-0.95,5.73C5.19,7.19 4,9.45 4,12s1.19,4.81 3.05,6.27L8,24
+        h8l0.96,-5.73C18.81,16.81 20,14.54 20,12s-1.19,-4.81 -3.04,-6.27L16,0z
+        M12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/internet_dialog_selected_effect.xml b/packages/SystemUI/res/drawable/internet_dialog_selected_effect.xml
new file mode 100644
index 0000000..8f6b4c2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_selected_effect.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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/white"/>
+            <corners android:radius="?android:attr/buttonCornerRadius"/>
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/screenrecord_options_spinner_popup_background.xml b/packages/SystemUI/res/drawable/screenrecord_options_spinner_popup_background.xml
new file mode 100644
index 0000000..9a02296
--- /dev/null
+++ b/packages/SystemUI/res/drawable/screenrecord_options_spinner_popup_background.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ 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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+        android:shape="rectangle">
+    <corners android:radius="@dimen/screenrecord_spinner_background_radius"/>
+    <solid android:color="?androidprv:attr/colorAccentSecondary" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenshare_options_spinner_background.xml b/packages/SystemUI/res/drawable/screenshare_options_spinner_background.xml
new file mode 100644
index 0000000..34e7d0a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/screenshare_options_spinner_background.xml
@@ -0,0 +1,35 @@
+<!--
+  ~ 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.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:paddingMode="stack">
+    <item>
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/screenrecord_spinner_background_radius" />
+            <stroke
+                android:width="1dp"
+                android:color="?androidprv:attr/textColorTertiary" />
+            <solid android:color="@android:color/transparent"/>
+        </shape>
+    </item>
+    <item
+        android:drawable="@drawable/ic_ksh_key_down"
+        android:gravity="end|center_vertical"
+        android:textColor="?androidprv:attr/textColorPrimary"
+        android:width="@dimen/screenrecord_spinner_arrow_size"
+        android:height="@dimen/screenrecord_spinner_arrow_size"
+        android:end="20dp" />
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/user_switcher_fullscreen_button_bg.xml b/packages/SystemUI/res/drawable/user_switcher_fullscreen_button_bg.xml
new file mode 100644
index 0000000..ae0f4b2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/user_switcher_fullscreen_button_bg.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.
+  -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:insetTop="@dimen/dialog_button_vertical_inset"
+    android:insetBottom="@dimen/dialog_button_vertical_inset">
+    <ripple android:color="?android:attr/colorControlHighlight">
+        <item android:id="@android:id/mask">
+            <shape android:shape="rectangle">
+                <solid android:color="@android:color/white"/>
+                <corners android:radius="20dp"/>
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="rectangle">
+                <corners android:radius="20dp"/>
+                <solid android:color="@android:color/transparent"/>
+                <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
+                    android:width="1dp"
+                    />
+                <padding android:left="@dimen/dialog_button_horizontal_padding"
+                    android:top="@dimen/dialog_button_vertical_padding"
+                    android:right="@dimen/dialog_button_horizontal_padding"
+                    android:bottom="@dimen/dialog_button_vertical_padding"/>
+            </shape>
+        </item>
+    </ripple>
+</inset>
diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
index 6e0e38b..88f138f 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
@@ -71,8 +71,8 @@
         <com.android.internal.widget.LockPatternView
             android:id="@+id/lockPattern"
             android:layout_gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"/>
+            android:layout_width="@dimen/biometric_auth_pattern_view_size"
+            android:layout_height="@dimen/biometric_auth_pattern_view_size"/>
 
         <TextView
             android:id="@+id/error"
diff --git a/packages/SystemUI/res/layout-sw600dp/global_actions_controls_list_view.xml b/packages/SystemUI/res/layout-sw600dp/global_actions_controls_list_view.xml
deleted file mode 100644
index ef49b9c..0000000
--- a/packages/SystemUI/res/layout-sw600dp/global_actions_controls_list_view.xml
+++ /dev/null
@@ -1,30 +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.
-  -->
-
-<com.android.systemui.globalactions.MinHeightScrollView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:scrollbars="none">
-  <LinearLayout
-      android:id="@+id/global_actions_controls_list"
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:orientation="vertical"
-      android:layout_marginLeft="@dimen/global_actions_side_margin"
-      android:layout_marginRight="@dimen/global_actions_side_margin" />
-</com.android.systemui.globalactions.MinHeightScrollView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/activity_rear_display_education.xml b/packages/SystemUI/res/layout/activity_rear_display_education.xml
new file mode 100644
index 0000000..73d3f02
--- /dev/null
+++ b/packages/SystemUI/res/layout/activity_rear_display_education.xml
@@ -0,0 +1,63 @@
+<?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"
+             xmlns:app="http://schemas.android.com/apk/res-auto"
+             android:layout_width="wrap_content"
+             android:layout_height="wrap_content"
+             android:orientation="vertical"
+             android:gravity="center" >
+
+    <androidx.cardview.widget.CardView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:cardElevation="0dp"
+        app:cardCornerRadius="28dp"
+        app:cardBackgroundColor="@color/rear_display_overlay_animation_background_color">
+
+            <com.airbnb.lottie.LottieAnimationView
+                android:id="@+id/rear_display_folded_animation"
+                android:layout_width="@dimen/rear_display_animation_width"
+                android:layout_height="@dimen/rear_display_animation_height"
+                android:layout_gravity="center"
+                android:contentDescription="@null"
+                android:scaleType="fitXY"
+                app:lottie_rawRes="@raw/rear_display_folded"
+                app:lottie_autoPlay="true"
+                app:lottie_repeatMode="reverse"/>
+    </androidx.cardview.widget.CardView>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/rear_display_fold_bottom_sheet_title"
+        android:textAppearance="@style/TextAppearance.Dialog.Title"
+        android:lineSpacingExtra="2sp"
+        android:paddingTop="@dimen/rear_display_title_top_padding"
+        android:paddingBottom="@dimen/rear_display_title_bottom_padding"
+        android:gravity="center_horizontal|center_vertical"
+    />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/rear_display_bottom_sheet_description"
+        android:textAppearance="@style/TextAppearance.Dialog.Body"
+        android:lineSpacingExtra="2sp"
+        android:translationY="-1.24sp"
+        android:gravity="center_horizontal|top"
+    />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
new file mode 100644
index 0000000..20b93d9
--- /dev/null
+++ b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
@@ -0,0 +1,75 @@
+<?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"
+              xmlns:app="http://schemas.android.com/apk/res-auto"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:orientation="vertical"
+              android:gravity="center" >
+
+    <androidx.cardview.widget.CardView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:cardElevation="0dp"
+        app:cardCornerRadius="28dp"
+        app:cardBackgroundColor="@color/rear_display_overlay_animation_background_color">
+
+        <com.airbnb.lottie.LottieAnimationView
+            android:id="@+id/rear_display_folded_animation"
+            android:layout_width="@dimen/rear_display_animation_width"
+            android:layout_height="@dimen/rear_display_animation_height"
+            android:layout_gravity="center"
+            android:contentDescription="@null"
+            android:scaleType="fitXY"
+            app:lottie_rawRes="@raw/rear_display_turnaround"
+            app:lottie_autoPlay="true"
+            app:lottie_repeatMode="reverse"/>
+    </androidx.cardview.widget.CardView>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/rear_display_unfold_bottom_sheet_title"
+        android:textAppearance="@style/TextAppearance.Dialog.Title"
+        android:lineSpacingExtra="2sp"
+        android:paddingTop="@dimen/rear_display_title_top_padding"
+        android:paddingBottom="@dimen/rear_display_title_bottom_padding"
+        android:gravity="center_horizontal|center_vertical"
+    />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/rear_display_bottom_sheet_description"
+        android:textAppearance="@style/TextAppearance.Dialog.Body"
+        android:lineSpacingExtra="2sp"
+        android:translationY="-1.24sp"
+        android:gravity="center_horizontal|top"
+    />
+
+    <TextView
+        android:id="@+id/rear_display_warning_text_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/rear_display_bottom_sheet_warning"
+        android:textAppearance="@style/TextAppearance.Dialog.Body"
+        android:lineSpacingExtra="2sp"
+        android:gravity="center_horizontal|top"
+    />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 891c6af..81ca3718 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -67,8 +67,8 @@
         <com.android.internal.widget.LockPatternView
             android:id="@+id/lockPattern"
             android:layout_gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"/>
+            android:layout_width="@dimen/biometric_auth_pattern_view_size"
+            android:layout_height="@dimen/biometric_auth_pattern_view_size"/>
 
         <TextView
             android:id="@+id/error"
diff --git a/packages/SystemUI/res/layout/controls_fullscreen.xml b/packages/SystemUI/res/layout/controls_fullscreen.xml
index 11a5665..e08e63b 100644
--- a/packages/SystemUI/res/layout/controls_fullscreen.xml
+++ b/packages/SystemUI/res/layout/controls_fullscreen.xml
@@ -15,28 +15,19 @@
      limitations under the License.
 -->
 
-<LinearLayout
+<FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/control_detail_root"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
 
-  <com.android.systemui.globalactions.MinHeightScrollView
-      android:layout_width="match_parent"
-      android:layout_height="0dp"
-      android:layout_weight="1"
-      android:orientation="vertical"
-      android:scrollbars="none">
 
     <LinearLayout
         android:id="@+id/global_actions_controls"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:clipChildren="false"
+        android:layout_height="match_parent"
         android:orientation="vertical"
-        android:clipToPadding="false"
         android:paddingHorizontal="@dimen/controls_padding_horizontal" />
 
-  </com.android.systemui.globalactions.MinHeightScrollView>
-</LinearLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index 9d01148..9efad22 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -18,7 +18,7 @@
 
   <LinearLayout
       android:layout_width="match_parent"
-      android:layout_height="match_parent"
+      android:layout_height="wrap_content"
       android:orientation="horizontal"
       android:layout_marginTop="@dimen/controls_top_margin"
       android:layout_marginBottom="@dimen/controls_header_bottom_margin">
@@ -71,5 +71,27 @@
         android:background="?android:attr/selectableItemBackgroundBorderless" />
   </LinearLayout>
 
-  <include layout="@layout/global_actions_controls_list_view" />
+  <ScrollView
+        android:id="@+id/controls_scroll_view"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:clipChildren="true"
+        android:scrollbars="none">
+    <include layout="@layout/global_actions_controls_list_view" />
+
+  </ScrollView>
+
+  <FrameLayout
+      android:id="@+id/controls_panel"
+      android:layout_width="match_parent"
+      android:layout_height="0dp"
+      android:layout_weight="1"
+      android:layout_marginLeft="@dimen/global_actions_side_margin"
+      android:layout_marginRight="@dimen/global_actions_side_margin"
+      android:background="#ff0000"
+      android:padding="@dimen/global_actions_side_margin"
+      android:visibility="gone"
+      />
 </merge>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index 2d67d95..efcb6f3 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -14,25 +14,32 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
 -->
-<com.android.systemui.shared.shadow.DoubleShadowTextClock
+<FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/time_view"
     android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:fontFamily="@*android:string/config_clockFontFamily"
-    android:textColor="@android:color/white"
-    android:format12Hour="@string/dream_time_complication_12_hr_time_format"
-    android:format24Hour="@string/dream_time_complication_24_hr_time_format"
-    android:fontFeatureSettings="pnum, lnum"
-    android:letterSpacing="0.02"
-    android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"
-    app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius"
-    app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx"
-    app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy"
-    app:keyShadowAlpha="0.3"
-    app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius"
-    app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx"
-    app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy"
-    app:ambientShadowAlpha="0.3"
-/>
+    android:layout_height="wrap_content">
+
+    <com.android.systemui.shared.shadow.DoubleShadowTextClock
+        android:id="@+id/time_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:fontFamily="@*android:string/config_clockFontFamily"
+        android:textColor="@android:color/white"
+        android:format12Hour="@string/dream_time_complication_12_hr_time_format"
+        android:format24Hour="@string/dream_time_complication_24_hr_time_format"
+        android:fontFeatureSettings="pnum, lnum"
+        android:letterSpacing="0.02"
+        android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"
+        android:translationY="@dimen/dream_overlay_complication_clock_time_translation_y"
+        app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius"
+        app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx"
+        app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy"
+        app:keyShadowAlpha="0.3"
+        app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius"
+        app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx"
+        app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy"
+        app:ambientShadowAlpha="0.3"
+        />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
index 4f0a78e..de96e97 100644
--- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -14,16 +14,21 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
 -->
-<ImageView
+<FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/home_controls_chip"
-    android:layout_height="@dimen/keyguard_affordance_fixed_height"
-    android:layout_width="@dimen/keyguard_affordance_fixed_width"
-    android:layout_gravity="bottom|start"
-    android:scaleType="center"
-    android:tint="?android:attr/textColorPrimary"
-    android:src="@drawable/controls_icon"
-    android:background="@drawable/keyguard_bottom_affordance_bg"
-    android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
-    android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
-    android:contentDescription="@string/quick_controls_title" />
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding">
+
+    <ImageView
+        android:id="@+id/home_controls_chip"
+        android:layout_height="@dimen/keyguard_affordance_fixed_height"
+        android:layout_width="@dimen/keyguard_affordance_fixed_width"
+        android:layout_gravity="bottom|start"
+        android:scaleType="center"
+        android:tint="?android:attr/textColorPrimary"
+        android:src="@drawable/controls_icon"
+        android:background="@drawable/keyguard_bottom_affordance_bg"
+        android:contentDescription="@string/quick_controls_title" />
+
+</FrameLayout>
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/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index ae2537f..f14be41 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -312,22 +312,15 @@
 
             <LinearLayout
                 android:id="@+id/see_all_layout"
-                android:layout_width="match_parent"
+                style="@style/InternetDialog.Network"
                 android:layout_height="64dp"
-                android:clickable="true"
-                android:focusable="true"
-                android:background="?android:attr/selectableItemBackground"
-                android:gravity="center_vertical|center_horizontal"
-                android:orientation="horizontal"
-                android:paddingStart="22dp"
-                android:paddingEnd="22dp">
+                android:paddingStart="20dp">
 
                 <FrameLayout
                     android:layout_width="24dp"
                     android:layout_height="24dp"
                     android:clickable="false"
-                    android:layout_gravity="center_vertical|start"
-                    android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+                    android:layout_gravity="center_vertical|start">
                     <ImageView
                         android:id="@+id/arrow_forward"
                         android:src="@drawable/ic_arrow_forward"
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index d27fa19..8b85940 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -34,30 +34,13 @@
         android:paddingTop="@dimen/status_bar_padding_top"
         android:layout_alignParentEnd="true"
         android:gravity="center_vertical|end" >
-        <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+
+        <include
             android:id="@+id/user_switcher_container"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:gravity="center"
-            android:orientation="horizontal"
-            android:paddingTop="4dp"
-            android:paddingBottom="4dp"
-            android:paddingStart="8dp"
-            android:paddingEnd="8dp"
-            android:background="@drawable/status_bar_user_chip_bg"
-            android:visibility="visible" >
-            <ImageView android:id="@+id/current_user_avatar"
-                android:layout_width="@dimen/multi_user_avatar_keyguard_size"
-                android:layout_height="@dimen/multi_user_avatar_keyguard_size"
-                android:scaleType="centerInside"
-                android:paddingEnd="4dp" />
-
-            <TextView android:id="@+id/current_user_name"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:textAppearance="@style/TextAppearance.StatusBar.Clock"
-                />
-        </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+            android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+            layout="@layout/status_bar_user_chip_container" />
 
         <FrameLayout android:id="@+id/system_icons_container"
             android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/log_access_user_consent_dialog_permission.xml b/packages/SystemUI/res/layout/log_access_user_consent_dialog_permission.xml
new file mode 100644
index 0000000..89e36ac
--- /dev/null
+++ b/packages/SystemUI/res/layout/log_access_user_consent_dialog_permission.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** 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.
+*/
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:layout_width="380dp"
+        android:layout_height="match_parent"
+        android:clipToPadding="false">
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical"
+            android:gravity="center"
+            android:paddingLeft="24dp"
+            android:paddingRight="24dp"
+            android:paddingTop="24dp"
+            android:paddingBottom="24dp">
+
+        <ImageView
+                android:id="@+id/log_access_image_view"
+                android:layout_width="32dp"
+                android:layout_height="32dp"
+                android:layout_marginBottom="16dp"
+                android:src="@drawable/ic_doc_document"
+                tools:layout_editor_absoluteX="148dp"
+                tools:layout_editor_absoluteY="35dp"
+                android:gravity="center" />
+
+        <TextView
+                android:id="@+id/log_access_dialog_title"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_marginBottom="32dp"
+                android:text="@string/log_access_confirmation_title"
+                android:textAppearance="@style/AllowLogAccess"
+                android:textColor="?android:attr/textColorPrimary"
+                android:gravity="center" />
+
+        <TextView
+                android:id="@+id/log_access_dialog_body"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:layout_marginBottom="40dp"
+                android:text="@string/log_access_confirmation_body"
+                android:textAppearance="@style/PrimaryAllowLogAccess"
+                android:textColor="?android:attr/textColorPrimary"
+                android:gravity="center" />
+
+        <Space
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
+
+        <LinearLayout
+                android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+            <Button
+                    android:id="@+id/log_access_dialog_allow_button"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/log_access_confirmation_allow"
+                    style="?permissionGrantButtonTopStyle"
+                    android:textAppearance="@style/PermissionGrantButtonTextAppearance"
+                    android:layout_marginBottom="5dp"
+                    android:layout_centerHorizontal="true"
+                    android:layout_alignParentTop="true"
+                    android:layout_alignParentBottom="true"
+                    android:clipToOutline="true"
+                    android:gravity="center" />
+
+            <Button
+                    android:id="@+id/log_access_dialog_deny_button"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/log_access_confirmation_deny"
+                    style="?permissionGrantButtonBottomStyle"
+                    android:textAppearance="@style/PermissionGrantButtonTextAppearance"
+                    android:layout_centerHorizontal="true"
+                    android:layout_alignParentTop="true"
+                    android:layout_alignParentBottom="true"
+                    android:clipToOutline="true"
+                    android:gravity="center" />
+        </LinearLayout>
+    </LinearLayout>
+</ScrollView>
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index c526d9c..13c9a5e 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -35,7 +35,6 @@
         android:layout_height="@dimen/qs_media_session_height_expanded"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         android:translationZ="0dp"
         android:scaleType="centerCrop"
@@ -44,6 +43,24 @@
         android:background="@drawable/qs_media_outline_album_bg"
         />
 
+    <com.android.systemui.surfaceeffects.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" />
+
+    <com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
+        android:id="@+id/turbulence_noise_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/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
index 79ba7ead..aa655e6 100644
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
@@ -41,7 +41,7 @@
         android:layout_width="@dimen/qs_media_app_icon_size"
         android:layout_height="@dimen/qs_media_app_icon_size"
         android:layout_marginStart="@dimen/qs_media_padding"
-        android:layout_marginTop="@dimen/qs_media_padding"
+        android:layout_marginTop="@dimen/qs_media_rec_icon_top_margin"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
 
diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml
new file mode 100644
index 0000000..3f0eea9
--- /dev/null
+++ b/packages/SystemUI/res/layout/screen_record_options.xml
@@ -0,0 +1,88 @@
+<!--
+  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:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingBottom="@dimen/screenrecord_options_padding_bottom">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <ImageView
+            android:layout_width="@dimen/screenrecord_option_icon_size"
+            android:layout_height="@dimen/screenrecord_option_icon_size"
+            android:src="@drawable/ic_mic_26dp"
+            android:tint="?android:attr/textColorSecondary"
+            android:layout_gravity="center_vertical"
+            android:layout_weight="0"
+            android:layout_marginRight="@dimen/screenrecord_option_padding"
+            android:importantForAccessibility="no"/>
+        <Spinner
+            android:id="@+id/screen_recording_options"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:minHeight="48dp"
+            android:layout_weight="1"
+            android:popupBackground="@drawable/screenrecord_spinner_background"
+            android:dropDownWidth="274dp"
+            android:importantForAccessibility="yes"/>
+        <Switch
+            android:layout_width="wrap_content"
+            android:minWidth="48dp"
+            android:layout_height="48dp"
+            android:layout_weight="0"
+            android:layout_gravity="end"
+            android:id="@+id/screenrecord_audio_switch"
+            style="@style/ScreenRecord.Switch"
+            android:importantForAccessibility="yes"/>
+    </LinearLayout>
+    <LinearLayout
+        android:id="@+id/show_taps"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:layout_marginTop="@dimen/screenrecord_option_padding">
+        <ImageView
+            android:layout_width="@dimen/screenrecord_option_icon_size"
+            android:layout_height="@dimen/screenrecord_option_icon_size"
+            android:layout_weight="0"
+            android:src="@drawable/ic_touch"
+            android:tint="?android:attr/textColorSecondary"
+            android:layout_gravity="center_vertical"
+            android:layout_marginRight="@dimen/screenrecord_option_padding"
+            android:importantForAccessibility="no"/>
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:minHeight="48dp"
+            android:layout_weight="1"
+            android:gravity="center_vertical"
+            android:text="@string/screenrecord_taps_label"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:fontFamily="@*android:string/config_bodyFontFamily"
+            android:textColor="?android:attr/textColorPrimary"
+            android:contentDescription="@string/screenrecord_taps_label"/>
+        <Switch
+            android:layout_width="wrap_content"
+            android:minWidth="48dp"
+            android:layout_height="48dp"
+            android:layout_weight="0"
+            android:id="@+id/screenrecord_taps_switch"
+            style="@style/ScreenRecord.Switch"
+            android:importantForAccessibility="yes"/>
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
new file mode 100644
index 0000000..bd71989
--- /dev/null
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -0,0 +1,98 @@
+<!--
+  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.
+  -->
+
+<!-- Scrollview is necessary to fit everything in landscape layout -->
+<ScrollView  xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:id="@+id/screen_share_permission_dialog"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="@dimen/dialog_side_padding"
+        android:paddingEnd="@dimen/dialog_side_padding"
+        android:paddingTop="@dimen/dialog_top_padding"
+        android:paddingBottom="@dimen/dialog_bottom_padding"
+        android:orientation="vertical"
+        android:gravity="center_horizontal">
+
+        <ImageView
+            android:id="@+id/screen_share_dialog_icon"
+            android:layout_width="@dimen/screenrecord_logo_size"
+            android:layout_height="@dimen/screenrecord_logo_size"
+            android:src="@drawable/ic_media_projection_permission"
+            android:tint="?androidprv:attr/colorAccentPrimary"
+            android:importantForAccessibility="no"/>
+        <TextView
+            android:id="@+id/screen_share_dialog_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:fontFamily="@*android:string/config_headlineFontFamily"
+            android:layout_marginTop="@dimen/screenrecord_title_margin_top"
+            android:gravity="center"/>
+        <Spinner
+            android:id="@+id/screen_share_mode_spinner"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/screenrecord_spinner_height"
+            android:layout_marginTop="@dimen/screenrecord_spinner_margin"
+            android:layout_marginBottom="@dimen/screenrecord_spinner_margin"
+            android:gravity="center_vertical"
+            android:background="@drawable/screenshare_options_spinner_background"
+            android:popupBackground="@drawable/screenrecord_options_spinner_popup_background"/>
+        <ViewStub
+            android:id="@+id/options_stub"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+        <TextView
+            android:id="@+id/text_warning"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/screenrecord_description"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:textColorSecondary"
+            android:gravity="start"
+            android:lineHeight="@dimen/screenrecord_warning_line_height"/>
+
+        <!-- Buttons -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_marginTop="@dimen/screenrecord_buttons_margin_top">
+            <TextView
+                android:id="@+id/button_cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="0"
+                android:text="@string/cancel"
+                style="@style/Widget.Dialog.Button.BorderButton" />
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"/>
+            <TextView
+                android:id="@+id/button_start"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="0"
+                android:text="@string/screenrecord_start"
+                style="@style/Widget.Dialog.Button" />
+        </LinearLayout>
+    </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml b/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml
new file mode 100644
index 0000000..66c2155
--- /dev/null
+++ b/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml
@@ -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.
+  -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:id="@android:id/text1"
+    android:textAppearance="?android:attr/textAppearanceMedium"
+    android:textColor="?androidprv:attr/textColorOnAccent"
+    android:singleLine="true"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/screenrecord_spinner_height"
+    android:gravity="center_vertical"
+    android:ellipsize="marquee"
+    android:paddingStart="@dimen/screenrecord_spinner_text_padding_start"
+    android:paddingEnd="@dimen/screenrecord_spinner_text_padding_end"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_share_dialog_spinner_text.xml b/packages/SystemUI/res/layout/screen_share_dialog_spinner_text.xml
new file mode 100644
index 0000000..4cc4cba
--- /dev/null
+++ b/packages/SystemUI/res/layout/screen_share_dialog_spinner_text.xml
@@ -0,0 +1,26 @@
+<!--
+  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.
+  -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:textAppearance="?android:attr/textAppearanceMedium"
+    android:fontFamily="@*android:string/config_bodyFontFamily"
+    android:textColor="?android:textColorPrimary"
+    android:singleLine="true"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_vertical"
+    android:ellipsize="marquee"
+    android:paddingStart="@dimen/screenrecord_spinner_text_padding_start"
+    android:paddingEnd="@dimen/screenrecord_spinner_text_padding_end"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 1ac78d4..8842992 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -44,7 +44,7 @@
         app:layout_constraintHorizontal_bias="0"
         app:layout_constraintWidth_percent="1.0"
         app:layout_constraintWidth_max="wrap"
-        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/screenshot_message_container"
         app:layout_constraintStart_toEndOf="@+id/screenshot_preview_border"
         app:layout_constraintEnd_toEndOf="parent">
         <LinearLayout
@@ -70,7 +70,7 @@
         android:alpha="0"
         android:background="@drawable/overlay_border"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/screenshot_message_container"
         app:layout_constraintEnd_toEndOf="@id/screenshot_preview_end"
         app:layout_constraintTop_toTopOf="@id/screenshot_preview_top"/>
     <androidx.constraintlayout.widget.Barrier
@@ -142,4 +142,41 @@
         app:layout_constraintStart_toStartOf="@id/screenshot_preview"
         app:layout_constraintTop_toTopOf="@id/screenshot_preview"
         android:elevation="7dp"/>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/screenshot_message_container"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
+        android:layout_marginVertical="4dp"
+        android:paddingHorizontal="@dimen/overlay_action_container_padding_right"
+        android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
+        android:elevation="4dp"
+        android:background="@drawable/action_chip_container_background"
+        android:visibility="gone"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent">
+
+        <ImageView
+            android:id="@+id/screenshot_message_icon"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:paddingEnd="4dp"
+            android:src="@drawable/ic_work_app_badge"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"/>
+
+        <TextView
+            android:id="@+id/screenshot_message_content"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="start"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toEndOf="@id/screenshot_message_icon"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
 </com.android.systemui.screenshot.DraggableConstraintLayout>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 80e65a3..64aa629 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -55,6 +55,7 @@
             android:id="@+id/status_bar_start_side_container"
             android:layout_height="match_parent"
             android:layout_width="0dp"
+            android:clipChildren="false"
             android:layout_weight="1">
 
             <!-- Container that is wrapped around the views on the start half of the status bar.
@@ -136,31 +137,12 @@
                 android:gravity="center_vertical|end"
                 android:clipChildren="false">
 
-                <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+                <include
                     android:id="@+id/user_switcher_container"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:gravity="center"
-                    android:orientation="horizontal"
-                    android:paddingTop="4dp"
-                    android:paddingBottom="4dp"
-                    android:paddingStart="8dp"
-                    android:paddingEnd="8dp"
-                    android:layout_marginEnd="16dp"
-                    android:background="@drawable/status_bar_user_chip_bg"
-                    android:visibility="visible" >
-                    <ImageView android:id="@+id/current_user_avatar"
-                        android:layout_width="@dimen/multi_user_avatar_keyguard_size"
-                        android:layout_height="@dimen/multi_user_avatar_keyguard_size"
-                        android:scaleType="centerInside"
-                        android:paddingEnd="4dp" />
-
-                    <TextView android:id="@+id/current_user_name"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:textAppearance="@style/TextAppearance.StatusBar.Clock"
-                        />
-                </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+                    android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+                    layout="@layout/status_bar_user_chip_container" />
 
                 <include layout="@layout/system_icons" />
             </com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_no_notifications.xml b/packages/SystemUI/res/layout/status_bar_no_notifications.xml
index a2abdb2..856ba92 100644
--- a/packages/SystemUI/res/layout/status_bar_no_notifications.xml
+++ b/packages/SystemUI/res/layout/status_bar_no_notifications.xml
@@ -21,12 +21,29 @@
         android:layout_height="wrap_content"
         android:visibility="gone"
         >
-    <TextView
-            android:id="@+id/no_notifications"
+    <LinearLayout android:orientation="vertical"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:minHeight="64dp"
-            android:textAppearance="?android:attr/textAppearanceButton"
+            android:layout_gravity="center"
             android:gravity="center"
-            android:text="@string/empty_shade_text"/>
+            >
+        <TextView
+                android:id="@+id/no_notifications"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:minHeight="64dp"
+                android:gravity="center"
+                android:textAppearance="?android:attr/textAppearanceButton"
+                android:text="@string/empty_shade_text"/>
+        <TextView
+                android:id="@+id/no_notifications_footer"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:gravity="center"
+                android:drawablePadding="8dp"
+                android:visibility="gone"
+                android:textAppearance="?android:attr/textAppearanceButton"
+                android:text="@string/unlock_to_see_notif_text"/>
+    </LinearLayout>
 </com.android.systemui.statusbar.EmptyShadeView>
diff --git a/packages/SystemUI/res/layout/status_bar_user_chip_container.xml b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
new file mode 100644
index 0000000..b374074
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
@@ -0,0 +1,40 @@
+<?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.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/user_switcher_container"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:orientation="horizontal"
+    android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+    android:background="@drawable/status_bar_user_chip_bg"
+    android:visibility="visible" >
+    <ImageView android:id="@+id/current_user_avatar"
+        android:layout_width="@dimen/status_bar_user_chip_avatar_size"
+        android:layout_height="@dimen/status_bar_user_chip_avatar_size"
+        android:layout_margin="4dp"
+        android:scaleType="centerInside" />
+
+    <TextView android:id="@+id/current_user_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingEnd="8dp"
+        android:textAppearance="@style/TextAppearance.StatusBar.UserChip"
+        />
+</com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 8388b67..bafdb11 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -26,12 +26,12 @@
     android:fitsSystemWindows="true">
 
     <com.android.systemui.statusbar.BackDropView
-            android:id="@+id/backdrop"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:visibility="gone"
-            sysui:ignoreRightInset="true"
-            >
+        android:id="@+id/backdrop"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"
+        sysui:ignoreRightInset="true"
+    >
         <ImageView android:id="@+id/backdrop_back"
                    android:layout_width="match_parent"
                    android:scaleType="centerCrop"
@@ -49,7 +49,7 @@
         android:layout_height="match_parent"
         android:importantForAccessibility="no"
         sysui:ignoreRightInset="true"
-        />
+    />
 
     <com.android.systemui.scrim.ScrimView
         android:id="@+id/scrim_notifications"
@@ -57,17 +57,17 @@
         android:layout_height="match_parent"
         android:importantForAccessibility="no"
         sysui:ignoreRightInset="true"
-        />
+    />
 
     <com.android.systemui.statusbar.LightRevealScrim
-            android:id="@+id/light_reveal_scrim"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent" />
+        android:id="@+id/light_reveal_scrim"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 
     <include layout="@layout/status_bar_expanded"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="invisible" />
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:visibility="invisible" />
 
     <include layout="@layout/brightness_mirror_container" />
 
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
index c2c79cb..fa9d739 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"
+        android:background="@drawable/user_switcher_fullscreen_button_bg"
+        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/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
index 0bff47c..0be7328 100644
--- a/packages/SystemUI/res/layout/window_magnifier_view.xml
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -40,19 +40,22 @@
             android:id="@+id/top_handle"
             android:layout_width="match_parent"
             android:layout_height="@dimen/magnification_border_drag_size"
-            android:layout_alignParentTop="true"/>
+            android:layout_alignParentTop="true"
+            android:accessibilityTraversalAfter="@id/left_handle"/>
 
         <View
             android:id="@+id/right_handle"
             android:layout_width="@dimen/magnification_border_drag_size"
             android:layout_height="match_parent"
-            android:layout_alignParentEnd="true"/>
+            android:layout_alignParentEnd="true"
+            android:accessibilityTraversalAfter="@id/top_handle"/>
 
         <View
             android:id="@+id/bottom_handle"
             android:layout_width="match_parent"
             android:layout_height="@dimen/magnification_border_drag_size"
-            android:layout_alignParentBottom="true"/>
+            android:layout_alignParentBottom="true"
+            android:accessibilityTraversalAfter="@id/right_handle"/>
 
         <SurfaceView
             android:id="@+id/surface_view"
@@ -62,6 +65,58 @@
     </RelativeLayout>
 
     <ImageView
+        android:id="@+id/top_right_corner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/magnification_outer_border_margin"
+        android:layout_gravity="right|top"
+        android:paddingTop="@dimen/magnifier_drag_handle_padding"
+        android:paddingEnd="@dimen/magnifier_drag_handle_padding"
+        android:scaleType="center"
+        android:contentDescription="@string/magnification_drag_corner_to_resize"
+        android:src="@drawable/ic_magnification_corner_top_right"
+        android:accessibilityTraversalAfter="@id/top_left_corner"/>
+
+    <ImageView
+        android:id="@+id/top_left_corner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/magnification_outer_border_margin"
+        android:layout_gravity="left|top"
+        android:paddingTop="@dimen/magnifier_drag_handle_padding"
+        android:paddingStart="@dimen/magnifier_drag_handle_padding"
+        android:scaleType="center"
+        android:contentDescription="@string/magnification_drag_corner_to_resize"
+        android:src="@drawable/ic_magnification_corner_top_left"
+        android:accessibilityTraversalAfter="@id/bottom_handle"/>
+
+    <ImageView
+        android:id="@+id/bottom_right_corner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/magnification_outer_border_margin"
+        android:layout_gravity="right|bottom"
+        android:paddingEnd="@dimen/magnifier_drag_handle_padding"
+        android:paddingBottom="@dimen/magnifier_drag_handle_padding"
+        android:scaleType="center"
+        android:contentDescription="@string/magnification_drag_corner_to_resize"
+        android:src="@drawable/ic_magnification_corner_bottom_right"
+        android:accessibilityTraversalAfter="@id/top_right_corner"/>
+
+    <ImageView
+        android:id="@+id/bottom_left_corner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/magnification_outer_border_margin"
+        android:layout_gravity="left|bottom"
+        android:paddingStart="@dimen/magnifier_drag_handle_padding"
+        android:paddingBottom="@dimen/magnifier_drag_handle_padding"
+        android:scaleType="center"
+        android:contentDescription="@string/magnification_drag_corner_to_resize"
+        android:src="@drawable/ic_magnification_corner_bottom_left"
+        android:accessibilityTraversalAfter="@id/bottom_right_corner"/>
+
+    <ImageView
         android:id="@+id/drag_handle"
         android:layout_width="@dimen/magnification_drag_view_size"
         android:layout_height="@dimen/magnification_drag_view_size"
diff --git a/packages/SystemUI/res/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml
index 887e3e7..f1bc883 100644
--- a/packages/SystemUI/res/layout/wireless_charging_layout.xml
+++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml
@@ -22,7 +22,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <com.android.systemui.ripple.RippleView
+    <com.android.systemui.surfaceeffects.ripple.RippleView
         android:id="@+id/wireless_charging_ripple"
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/raw/biometricprompt_folded_base_bottomright.json b/packages/SystemUI/res/raw/biometricprompt_folded_base_bottomright.json
new file mode 100644
index 0000000..2797996
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_folded_base_bottomright.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_portrait_base_bottomright","ddd":0,"assets":[{"id":"comp_0","nm":"biometricprompt_landscape_base","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Null_Circle","parent":1,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle mask 3","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Finger","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-60,"s":[55]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":110,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":140,"s":[10]},{"t":170,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-60,"s":[92.146,-65.896,0],"to":[1.361,6.667,0],"ti":[-1.361,-6.667,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.167,"y":0.167},"t":0,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.7,"y":0.7},"t":110,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"t":170,"s":[100.313,-25.896,0]}],"ix":2,"l":2},"a":{"a":0,"k":[160.315,58.684,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-11.013,2.518],[5.251,5.023],[8.982,-2.829],[-0.264,-5.587]],"o":[[12.768,-2.854],[-14.961,2.071],[-6.004,1.89],[8.052,1.403]],"v":[[5.115,7.499],[19.814,-10.087],[-16.489,-3.588],[-24.801,8.684]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[34.67,28.053],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.231,-7],[-27.395,-1.197],[-26.792,4.092],[14.179,15.736]],"o":[[-17.931,5.646],[56.062,2.45],[-1.765,-22.396],[-51.819,17.744]],"v":[[-62.102,-8.314],[-39.958,30.079],[80.033,25.905],[54.879,-32.529]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[80.283,32.779],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"circle mask 7","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"circle mask","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey800","cl":"grey800","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"circle mask 6","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"circle mask 2","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"circle mask 4","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":1,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"circle mask 5","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":1,"nm":".black","cl":"black","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".grey800","cl":"grey800","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".grey900","cl":"grey900","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[0,18.167,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[0,10.667,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"Shape Layer 4","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".grey900","cl":"grey900","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"device frame mask","parent":24,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,1.167,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".blue400","cl":"blue400","parent":18,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-115.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-199,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-159,"s":[100.25,-105.667,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-0.75,-14,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":3,"nm":"device frame mask 5","parent":18,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-165,"op":6.00000000000001,"st":-271,"bm":0},{"ddd":0,"ind":28,"ty":4,"nm":"device frame mask 9","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":29,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-145,"s":[50]},{"t":-75,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-165,"s":[0,0]},{"t":-75,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":73,"s":[50]},{"t":113,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":30,"ty":4,"nm":"device frame mask 8","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":31,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-165,"s":[50]},{"t":-95,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-195,"s":[0,0]},{"t":-105,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[50]},{"t":83,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":32,"ty":4,"nm":"device frame mask 7","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":33,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-195,"s":[50]},{"t":-125,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-225,"s":[0,0]},{"t":-135,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[50]},{"t":53,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":34,"ty":4,"nm":"device frame mask 6","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0},{"ddd":0,"ind":35,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-225,"s":[50]},{"t":-155,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-255,"s":[0,0]},{"t":-165,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-17,"s":[50]},{"t":23,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0}]}],"layers":[{"ddd":0,"ind":6,"ty":0,"nm":"biometricprompt_landscape_base","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[170,170,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":340,"h":340,"ip":0,"op":900,"st":0,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
diff --git a/packages/SystemUI/res/raw/biometricprompt_folded_base_default.json b/packages/SystemUI/res/raw/biometricprompt_folded_base_default.json
new file mode 100644
index 0000000..bf65b34
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_folded_base_default.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_landscape_base","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Null_Circle","parent":1,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle mask 3","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Finger","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-60,"s":[55]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":110,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":140,"s":[10]},{"t":170,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-60,"s":[92.146,-65.896,0],"to":[1.361,6.667,0],"ti":[-1.361,-6.667,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.167,"y":0.167},"t":0,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.7,"y":0.7},"t":110,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"t":170,"s":[100.313,-25.896,0]}],"ix":2,"l":2},"a":{"a":0,"k":[160.315,58.684,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-11.013,2.518],[5.251,5.023],[8.982,-2.829],[-0.264,-5.587]],"o":[[12.768,-2.854],[-14.961,2.071],[-6.004,1.89],[8.052,1.403]],"v":[[5.115,7.499],[19.814,-10.087],[-16.489,-3.588],[-24.801,8.684]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[34.67,28.053],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.231,-7],[-27.395,-1.197],[-26.792,4.092],[14.179,15.736]],"o":[[-17.931,5.646],[56.062,2.45],[-1.765,-22.396],[-51.819,17.744]],"v":[[-62.102,-8.314],[-39.958,30.079],[80.033,25.905],[54.879,-32.529]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[80.283,32.779],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"circle mask 7","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"circle mask","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey800","cl":"grey800","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"circle mask 6","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"circle mask 2","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"circle mask 4","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":1,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"circle mask 5","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":1,"nm":".black","cl":"black","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".grey800","cl":"grey800","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".grey900","cl":"grey900","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[0,18.167,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[0,10.667,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"Shape Layer 4","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".grey900","cl":"grey900","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"device frame mask","parent":24,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,1.167,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".blue400","cl":"blue400","parent":18,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-115.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-199,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-159,"s":[100.25,-105.667,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-0.75,-14,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":3,"nm":"device frame mask 5","parent":18,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-165,"op":6.00000000000001,"st":-271,"bm":0},{"ddd":0,"ind":28,"ty":4,"nm":"device frame mask 9","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":29,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-145,"s":[50]},{"t":-75,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-165,"s":[0,0]},{"t":-75,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":73,"s":[50]},{"t":113,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":30,"ty":4,"nm":"device frame mask 8","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":31,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-165,"s":[50]},{"t":-95,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-195,"s":[0,0]},{"t":-105,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[50]},{"t":83,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":32,"ty":4,"nm":"device frame mask 7","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":33,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-195,"s":[50]},{"t":-125,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-225,"s":[0,0]},{"t":-135,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[50]},{"t":53,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":34,"ty":4,"nm":"device frame mask 6","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0},{"ddd":0,"ind":35,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-225,"s":[50]},{"t":-155,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-255,"s":[0,0]},{"t":-165,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-17,"s":[50]},{"t":23,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
diff --git a/packages/SystemUI/res/raw/biometricprompt_folded_base_topleft.json b/packages/SystemUI/res/raw/biometricprompt_folded_base_topleft.json
new file mode 100644
index 0000000..7351d7c
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_folded_base_topleft.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Portrait_Base_TopLeft","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":6,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null_Circle","parent":6,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey600","cl":"grey600","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"circle mask 3","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger_Flipped","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[-24.98,-35.709,0],"ix":2,"l":2},"a":{"a":0,"k":[31.791,75.23,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[5.03,5.25],[-2.83,8.98],[-5.59,-0.26],[2.52,-11.02]],"o":[[-2.85,12.77],[2.07,-14.96],[1.9,-6],[1.4,8.05],[0,0]],"v":[[7.5,4.99],[-10.09,19.69],[-3.59,-16.61],[8.69,-24.92],[7.5,5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.8,24.94],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-7.01,22.23],[-1.2,-27.39],[4.09,-26.79],[15.73,14.18]],"o":[[5.64,-17.93],[2.45,56.06],[-22.4,-1.77],[17.73,-51.82]],"v":[[-7.57,-66.9],[30.82,-44.76],[26.65,75.23],[-31.78,50.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[31.79,75.23],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"circle mask 7","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey600","cl":"grey600","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"circle mask","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"circle mask 6","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"circle mask 2","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"circle mask 4","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":1,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"circle mask 5","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":1,"nm":".black","cl":"black","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".grey800","cl":"grey800","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":".grey900","cl":"grey900","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-87.156,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[100.25,-94.656,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":25,"ty":4,"nm":"Shape Layer 4","parent":6,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":26,"ty":4,"nm":".grey900","cl":"grey900","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
diff --git a/packages/SystemUI/res/raw/rear_display_folded.json b/packages/SystemUI/res/raw/rear_display_folded.json
new file mode 100644
index 0000000..5140f41
--- /dev/null
+++ b/packages/SystemUI/res/raw/rear_display_folded.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":181,"w":412,"h":300,"nm":"Close to Open - Generic Version V01","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"SHUTTER_ANIMATION 2","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":270.564,"ix":3},"y":{"a":0,"k":239.916,"ix":4}},"a":{"a":0,"k":[460.228,450.736,0],"ix":1,"l":2},"s":{"a":0,"k":[138.889,138.889,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.528,0],[0,-2.528],[2.528,0],[0,2.528]],"o":[[2.528,0],[0,2.528],[-2.528,0],[0,-2.528]],"v":[[0,-4.5],[4.5,0],[0,4.5],[-4.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 599","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[460.228,450.736],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[140,140],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"SHUTTER_01","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-5.337,0],[0,-5.337],[5.337,0],[0,5.337]],"o":[[5.337,0],[0,5.337],[-5.337,0],[0,-5.337]],"v":[[0,-9.5],[9.5,0],[0,9.5],[-9.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 598","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[460.228,450.736],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"SHUTTER_02","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":196,"st":-90,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"CAMERA_CASING","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.125,149.875,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[20,20,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":77.334,"s":[{"i":[[0.121,-8.986],[-0.129,-6.81],[0,0],[0.079,-13.74],[-0.055,-2.977],[0,0],[-0.084,4.25],[0,5.834],[0,20.89],[-0.063,-6.336]],"o":[[-0.029,2.163],[0.129,6.811],[0,0],[-0.079,13.74],[0.123,6.697],[0,0],[0.045,-2.268],[0,-6.193],[0,-0.486],[0.247,24.895]],"v":[[-248.474,-424.663],[-248.252,-408.338],[-247.986,-395.258],[-248.158,-368.523],[-248.248,-337.322],[-243.792,-329.875],[-243.834,-338.44],[-243.875,-350.098],[-244.25,-495.639],[-248.374,-488.414]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78,"s":[{"i":[[0.121,-8.986],[-0.035,-6.791],[0,0],[0.257,-54.598],[-0.08,-2.976],[0,0],[-0.084,4.25],[0,5.834],[0,20.89],[0.187,-5.461]],"o":[[-0.029,2.163],[0.035,6.791],[0,0],[-0.257,54.598],[0.121,4.51],[0,0],[0.045,-2.268],[0,-6.193],[0,-0.486],[-0.853,24.882]],"v":[[-246.724,-424.581],[-246.689,-408.295],[-246.611,-395.254],[-247.145,-286.802],[-247.56,-173.885],[-239.293,-170.875],[-239.335,-179.44],[-239.251,-191.098],[-239,-495.889],[-246.437,-493.601]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79,"s":[{"i":[[0.714,-8.993],[0.023,-5.892],[0,0],[0.218,-62.519],[-0.04,-3.021],[0,0],[-2.041,1.97],[0,5.92],[0,21.197],[1.374,-7.356]],"o":[[-0.174,2.184],[-0.023,5.892],[0,0],[-0.218,62.519],[0.061,5.942],[0,0],[0.335,-2.259],[0,-6.284],[0,-0.493],[-3.801,24.629]],"v":[[-249.702,-423.916],[-249.966,-409.403],[-249.968,-398.166],[-250.414,-273.882],[-250.801,-145.067],[-238.043,-139.562],[-231.043,-142.595],[-229.959,-182.924],[-230,-497.57],[-247.031,-494.301]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[1.306,-9],[0.081,-4.992],[0,0],[0.2,-61.534],[0,-3.065],[0,0],[-6.497,19.5],[0,6.005],[0,21.503],[2.562,-9.251]],"o":[[-0.32,2.204],[-0.081,4.992],[0,0],[-0.2,61.535],[0,7.375],[0,0],[0.738,-2.215],[0,-6.375],[0,-0.5],[-6.75,24.375]],"v":[[-252.681,-423.25],[-253.243,-410.511],[-253.325,-401.078],[-253.726,-278.775],[-254.126,-151.875],[-236.876,-143.875],[-224.128,-171.375],[-221.128,-194.75],[-221,-499.25],[-241.375,-496.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[2.977,-4.617],[0.592,-15.582],[0.085,-28.87],[-7.478,-2.938],[0,0],[-7.716,4.062],[0.774,19.04],[0.351,22.315],[4.742,-7.015]],"o":[[0,0],[-5.02,7.785],[-0.054,1.42],[-0.036,12.426],[7.58,-2.313],[0,0],[6.882,-3.623],[-0.821,-20.201],[-4.272,-4.668],[-5.774,8.542]],"v":[[-281.915,-495.91],[-289.56,-482.16],[-299.005,-452.549],[-298.089,-174.301],[-285.022,-158.312],[-272.428,-163.108],[-254.243,-172.187],[-246.548,-198.415],[-247.911,-507.832],[-271.41,-509.792]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83,"s":[{"i":[[0,0],[5.84,-4.09],[0.943,-15.582],[-0.504,-28.868],[-9.184,-4.813],[0,0],[-12.283,4.062],[0.017,19.08],[0.558,22.315],[10.444,-5.833]],"o":[[0,0],[-9.041,6.332],[-0.086,1.42],[0.293,16.801],[12.066,-2.313],[0,0],[10.956,-3.623],[-0.018,-20.335],[-6.483,-6.543],[-12.057,6.734]],"v":[[-335.521,-505.91],[-351.465,-495.91],[-370.479,-463.799],[-369.043,-170.551],[-352.691,-149.562],[-328.662,-154.983],[-300.058,-162.812],[-286.232,-189.04],[-289.767,-509.707],[-316.694,-517.917]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86,"s":[{"i":[[0,0],[10.236,-2.84],[1.247,-15.582],[0,-28.874],[-6.428,0.187],[0,0],[-16.249,4.062],[0,17.165],[0.739,22.315],[11.672,-3.958]],"o":[[0,0],[-13.709,3.803],[-0.114,1.42],[0,17.426],[15.447,-2.313],[0,0],[14.494,-3.623],[0,-20.252],[-3.545,-8.418],[-17.281,5.861]],"v":[[-411.704,-507.785],[-438.297,-499.035],[-463.451,-466.924],[-464.325,-183.676],[-446.9,-161.437],[-413.235,-166.858],[-377.21,-173.437],[-354.78,-199.04],[-356.455,-507.207],[-381.047,-517.292]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[11.399,-2.84],[1.389,-15.582],[-0.154,-28.874],[-10.466,-4.813],[0,0],[-18.094,4.062],[0.311,21.54],[0.823,22.315],[12.693,-2.083]],"o":[[0,0],[-15.266,3.803],[-0.127,1.42],[0.136,25.551],[16.344,-2.733],[0,0],[16.14,-3.623],[-0.293,-20.255],[-6.423,-10.918],[-20.046,3.29]],"v":[[-453.081,-516.535],[-484.085,-509.035],[-512.096,-476.924],[-511.208,-201.176],[-491.095,-170.812],[-450.071,-176.858],[-415.594,-182.187],[-391.248,-214.04],[-394.202,-507.207],[-419.568,-523.542]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91,"s":[{"i":[[0,0],[11.98,-2.84],[1.46,-15.582],[-0.514,-28.87],[-22.839,0.574],[0,0],[-19.017,4.062],[-1.512,28.415],[0.865,22.315],[18.516,-1.458]],"o":[[0,0],[-16.044,3.803],[-0.133,1.42],[0.514,28.87],[17.426,-0.438],[0,0],[16.962,-3.623],[1.076,-20.231],[-2.031,-12.793],[-21.281,1.676]],"v":[[-517.52,-526.535],[-550.105,-520.285],[-579.544,-488.174],[-580.475,-228.676],[-552.254,-182.687],[-515.05,-186.858],[-479.472,-193.437],[-453.852,-232.165],[-454.219,-505.957],[-481.641,-531.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96,"s":[{"i":[[0,0],[9.594,-0.965],[1.674,-15.582],[-0.589,-28.87],[-26.176,0.574],[0,0],[-26.125,4.687],[-0.648,20.252],[0.991,22.315],[23.63,-1.458]],"o":[[0,0],[-18.792,1.889],[-0.153,1.42],[0.589,28.87],[19.971,-0.438],[0,0],[19.531,-3.504],[0.648,-20.252],[-5.417,-15.918],[-24.412,1.507]],"v":[[-566.624,-545.91],[-605.219,-542.785],[-638.958,-510.674],[-640.256,-243.676],[-605.048,-197.687],[-564.208,-201.233],[-524.5,-205.937],[-494.422,-252.79],[-495.208,-520.332],[-526.755,-548.542]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101,"s":[{"i":[[0,0],[10.756,0.606],[1.787,-15.582],[-0.629,-28.87],[-29.482,0.187],[0,0],[-26.75,1.562],[-0.693,20.252],[1.058,22.315],[29.006,2.879]],"o":[[0,0],[-20.135,-1.134],[-0.163,1.42],[0.629,28.87],[22.579,-0.143],[0,0],[21.139,-1.234],[0.693,-20.252],[-0.991,-20.899],[-22.881,-2.271]],"v":[[-590.882,-561.535],[-630.768,-559.035],[-666.802,-526.924],[-667.496,-244.926],[-626.768,-198.312],[-587.72,-199.358],[-544.491,-201.562],[-512.976,-245.915],[-513.29,-522.832],[-546.76,-561.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.524,-1.302],[0,0],[-22.316,2.187],[-0.707,20.252],[1.081,22.315],[29.62,2.879]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[23.032,1.051],[0,0],[21.519,-2.108],[0.707,-20.252],[-1.012,-20.899],[-23.365,-2.271]],"v":[[-603.82,-572.785],[-647.049,-572.785],[-683.846,-540.674],[-684.555,-258.676],[-646.156,-212.687],[-598.091,-212.483],[-558.309,-214.687],[-525.505,-260.29],[-527.086,-532.832],[-561.264,-571.667]],"c":true}]},{"t":115,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.524,-1.302],[0,0],[-21.06,0.551],[-0.707,20.252],[1.081,22.315],[29.62,2.879]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[23.032,1.051],[0,0],[21.614,-0.566],[0.707,-20.252],[-1.012,-20.899],[-23.365,-2.271]],"v":[[-605.07,-581.535],[-648.299,-581.535],[-685.096,-549.424],[-685.805,-263.676],[-647.406,-217.687],[-601.841,-216.858],[-560.092,-217.187],[-527.348,-259.04],[-528.336,-541.582],[-562.514,-580.417]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294118524,0.250980407,0.262745112181,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":78,"op":241,"st":60,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"FRONT_LENS","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[269.043,44.23,0],"ix":2,"l":2},"a":{"a":0,"k":[387.188,-561.875,0],"ix":1,"l":2},"s":{"a":0,"k":[18.519,18.519,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-10.77,0],[0,-10.77],[10.77,0],[0,10.77]],"o":[[10.77,0],[0,10.77],[-10.77,0],[0,-10.77]],"v":[[1.484,-19.734],[20.984,-0.234],[1.484,19.266],[-18.016,-0.234]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[385.5,-561.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":241,"st":60,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"FRONT_SCREEN 2","parent":5,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[268.577,150.446,0],"ix":2,"l":2},"a":{"a":0,"k":[-2.142,1.067,0],"ix":1,"l":2},"s":{"a":0,"k":[18.894,18.894,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[635,1210],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":50,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0.641,0.871],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":241,"st":60,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Figure","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[207.459,144.79,0],"to":[0.312,0,0],"ti":[-1.146,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":63.334,"s":[209.334,144.79,0],"to":[1.146,0,0],"ti":[-2.125,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":66.666,"s":[214.334,144.79,0],"to":[2.125,0,0],"ti":[-2.812,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[222.084,144.79,0],"to":[2.812,0,0],"ti":[-4.396,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73.334,"s":[231.209,144.79,0],"to":[4.396,0,0],"ti":[-5.104,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[248.459,144.79,0],"to":[5.104,0,0],"ti":[-3.812,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[261.834,144.79,0],"to":[3.812,0,0],"ti":[-2.667,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[271.334,144.79,0],"to":[2.667,0,0],"ti":[-1.812,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[277.834,144.79,0],"to":[1.812,0,0],"ti":[-1.083,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106.666,"s":[282.209,144.79,0],"to":[1.083,0,0],"ti":[-0.458,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113.334,"s":[284.334,144.79,0],"to":[0.458,0,0],"ti":[-0.104,0,0]},{"t":120,"s":[284.959,144.79,0]}],"ix":2,"l":2},"a":{"a":0,"k":[270.209,145.54,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.55,0.55,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.001]},"t":60,"s":[108,108,100]},{"t":120,"s":[106.5,106.5,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.939,-1.387],[6.061,-1.3],[0.907,1.308]],"o":[[1.066,0.711],[2.423,1.734],[-4.85,1.04],[0,0]],"v":[[-0.582,-6.237],[5.035,-2.37],[2.01,5.979],[-6.77,3.354]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.596078455448,0.321568638086,0.239215686917,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.12,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[272.026,152.086],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Nose","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.926,7.277],[-8.848,0],[-3.783,2.669],[18.309,-0.059],[0.81,-15.17],[-4.916,0]],"o":[[3.926,7.277],[4.982,0],[-0.845,-15.213],[-18.267,0.059],[3.755,2.604],[8.848,0]],"v":[[-0.106,3.616],[20.359,15.839],[33.729,11.589],[-0.12,-15.891],[-33.793,11.7],[-20.571,15.839]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,9.805],[9.805,0],[0,-9.805],[-9.805,0]],"o":[[0,-9.805],[-9.805,0],[0,9.805],[9.805,0]],"v":[[-6.59,-22.526],[-24.345,-40.281],[-42.099,-22.526],[-24.345,-4.772]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.337254911661,0.23137255013,0.129411771894,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[270.47,108.276],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Hair","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-7.446,13.962],[0,0],[0,0],[-0.313,2.62],[0.015,4.351],[0,0],[-18.83,0.06],[-0.051,-15.806],[0,0],[-0.49,-2.669],[-1.873,-4.352],[-0.89,-3.988],[0,-4.307],[8.014,-5.508],[12.335,-0.04],[8.035,5.309],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.222,-4.479],[0.309,-2.598],[0,0],[-0.051,-15.806],[18.83,-0.06],[0,0],[0.013,4.276],[0.495,2.702],[3.604,8.387],[0.884,3.962],[0,7.459],[-7.997,5.495],[-12.35,0.04],[-8.04,-5.313],[0,0],[0,-6.072]],"v":[[-36.861,-4.598],[-36.804,-4.703],[-36.772,-4.819],[-34.61,-14.535],[-34.324,-24.06],[-34.324,-24.128],[-0.614,-53.129],[33.278,-24.345],[33.278,-24.252],[33.789,-14.641],[37.134,-4.922],[43.41,12.489],[44.307,23.718],[31.53,43.997],[0.092,53.129],[-31.449,44.589],[-44.307,24.009],[-44.307,23.977]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[4.598,1.656],[-2.64,7.412],[-4.598,-1.656],[2.64,-7.412]],"o":[[-4.6,-1.657],[2.64,-7.412],[4.6,1.657],[-2.64,7.412]],"v":[[30.635,-0.09],[27.088,-16.51],[40.195,-26.931],[43.742,-10.511]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[4.598,-1.656],[2.64,7.412],[-4.598,1.656],[-2.64,-7.412]],"o":[[-4.6,1.657],[-2.64,-7.412],[4.6,-1.657],[2.64,7.412]],"v":[[-32.206,-0.025],[-45.315,-10.446],[-41.768,-26.866],[-28.659,-16.445]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431391716,0.403921574354,0.305882364511,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[270.996,145.54],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Head","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[32.75,0],[5.125,-6.5],[0,0],[0,0],[0.125,7.75]],"o":[[-36.75,0],[-0.25,11],[0,0],[0,0],[-14.25,-12.5]],"v":[[269.5,203.875],[202.375,231.25],[202.125,265.125],[335.375,265.125],[335.375,229.75]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.105865478516,0.450958251953,0.901947021484,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Body","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,-5.243]],"o":[[0,0],[0,0],[0,0],[5.243,0],[0,0]],"v":[[335.369,264.28],[202.496,264.441],[202.496,35.601],[325.98,35.551],[335.473,45.044]],"c":true},"ix":2},"nm":"Mask","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.815673828125,0.88232421875,0.980377197266,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Background","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":660,"st":60,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"FOLDABLE_BODY","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.125,149.875,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[20,20,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,-16.75],[0,0],[0.131,-20.875],[0,0],[0,0],[-1.054,43.75],[0,0],[55.442,-0.625],[0,0],[0,0]],"o":[[0,0],[0.07,32.5],[0,0],[0,0],[50.138,1],[0,0],[0.75,-40.75],[0,0],[0,0],[-0.317,13.125]],"v":[[-380.75,-475.25],[-381.945,472.625],[-381.756,573.5],[-381.766,647.625],[278.237,648.25],[356.555,572.75],[356.375,-566.125],[277.933,-644.25],[-380.936,-644.5],[-380.183,-550.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":63.334,"s":[{"i":[[0,-16.75],[0,0],[0.131,-20.875],[0,0],[0,0],[-1.054,43.75],[0,0],[55.442,-0.625],[0,0],[0,0]],"o":[[0,0],[0.07,32.5],[0,0],[0,0],[50.138,1],[0,0],[0.75,-40.75],[0,0],[0,0],[-0.317,13.125]],"v":[[-373.625,-475],[-373.633,472.562],[-373.444,573.438],[-373.454,647.562],[287.112,648.375],[364.93,573.625],[364.75,-566.938],[285.995,-645],[-373.811,-644.25],[-373.058,-550.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":66.666,"s":[{"i":[[0,-16.75],[0,0],[0.131,-20.875],[0,0],[0,0],[-1.054,43.75],[0,0],[55.442,-0.625],[0,0],[0,0]],"o":[[0,0],[0.07,32.5],[0,0],[0,0],[50.138,1],[0,0],[0.75,-40.75],[0,0],[0,0],[-0.317,13.125]],"v":[[-350,-475.25],[-349.82,472.5],[-349.631,573.375],[-349.641,647.5],[311.487,647],[388.805,573],[389.125,-566.75],[310.058,-644.75],[-350.186,-644.5],[-349.433,-550.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[0,-16.75],[0,0],[0.131,-20.875],[0,0],[0,0],[-1.054,43.75],[0,0],[54.942,-0.625],[0,0],[0,0]],"o":[[0,0],[0.07,32.5],[0,0],[0,0],[50.138,1],[0,0],[0.187,-41.375],[0,0],[0,0],[-0.317,13.125]],"v":[[-315.875,-476.875],[-315.445,471],[-315.256,571.875],[-315.266,646],[348.612,647.562],[427.555,572.75],[427.438,-565.75],[349.058,-643.688],[-316.077,-643.625],[-315.308,-552.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73.334,"s":[{"i":[[0,-16.75],[0,0],[0.131,-20.875],[0,0],[0,0],[-1.054,43.75],[0,0],[54.442,-0.625],[0,0],[0,0]],"o":[[0,0],[0.07,32.5],[0,0],[0,0],[50.138,1],[0,0],[0.75,-40.75],[0,0],[0,0],[-0.317,13.125]],"v":[[-278.75,-478],[-278.57,469.75],[-278.381,570.625],[-278.391,644.75],[395.237,645.125],[471.554,571],[471.25,-564],[393.808,-642.125],[-278.968,-642.25],[-278.183,-553.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,-16.75],[0,0],[-0.119,-21.25],[0,0],[0,0],[-1.054,42.938],[0,0],[54.442,-0.625],[0,0],[0,0]],"o":[[0,0],[0.32,32.625],[0,0],[0,0],[50.075,0.562],[0,0],[0.75,-40.75],[0,0],[0,0],[0.183,12.75]],"v":[[-248.25,-479],[-248.82,468.5],[-248.381,550.25],[-248.516,643.75],[440.3,644.25],[515.804,570.375],[516,-564.375],[438.058,-641],[-248.468,-640.125],[-248.308,-541.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79.334,"s":[{"i":[[0,-16.75],[0,0],[0.081,-18.8],[0,0],[0,0],[-1.054,42.287],[0,0],[54.442,-0.625],[0,0],[0,0]],"o":[[0,0],[0.52,32.725],[0,0],[0,0],[50.025,0.212],[0,0],[0.75,-40.75],[0,0],[0,0],[-1.317,12.05]],"v":[[-223.65,-479.7],[-224.12,467.9],[-224.081,534.35],[-224.116,642.5],[471.15,643.15],[548.004,570.275],[548.2,-564.675],[468.658,-640.1],[-224.668,-638.65],[-224.208,-523.1]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,-16.75],[0,0],[-2.744,-18],[0,0],[0,0],[-1.054,42.125],[0,0],[54.442,-0.625],[0,0],[0,0]],"o":[[0,0],[0.57,32.75],[0,0],[0,0],[50.013,0.125],[0,0],[0.75,-40.75],[0,0],[0,0],[-1.692,11.875]],"v":[[-233.75,-479.75],[-233.57,467.5],[-229.881,530.125],[-213.641,642.5],[478.862,642.875],[556.054,570.25],[556.25,-564.75],[476.308,-639.875],[-212.468,-638.5],[-230.683,-518.375]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80.666,"s":[{"i":[[0.133,-15.433],[0,0],[-4.881,-15.762],[0,0],[0,0],[-1.067,41.975],[0,0],[54.08,-0.675],[0,0],[0,0]],"o":[[0,0],[0.458,32.95],[0,0],[0,0],[49.838,0.162],[0,0],[0.75,-40.625],[0,0],[0,0],[-6.455,9.608]],"v":[[-267.883,-471.067],[-267.329,462.242],[-262.735,514.346],[-201.341,642],[486.187,642.638],[563.142,570.212],[563.35,-564.412],[483.87,-639.625],[-201.81,-638.275],[-260.045,-517.358]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82,"s":[{"i":[[0.4,-12.8],[0,0],[-9.156,-11.287],[0,0],[0,0],[-1.092,41.675],[0,0],[53.355,-0.775],[0,0],[0,0]],"o":[[0,0],[0.233,33.35],[0,0],[0,0],[49.488,0.238],[0,0],[0.75,-40.375],[0,0],[0,0],[-10.48,6.825]],"v":[[-331.65,-460.45],[-331.47,451.725],[-319.444,518.037],[-181.241,641.75],[500.837,642.162],[577.317,570.138],[577.55,-563.737],[498.995,-639.125],[-180.493,-637.825],[-318.77,-515.325]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[0,-16.75],[0,0],[-13.431,-6.812],[0,0],[0,0],[-1.117,41.375],[0,0],[52.63,-0.875],[0,0],[0,0]],"o":[[0,0],[0.008,33.75],[0,0],[0,0],[49.138,0.312],[0,0],[0.75,-40.125],[0,0],[0,0],[-18.005,8.375]],"v":[[-387.75,-461.75],[-388.82,454.125],[-369.569,523.312],[-159.641,641.25],[515.487,641.688],[591.492,570.062],[591.75,-563.062],[514.12,-638.625],[-160.843,-637.375],[-367.495,-521.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.666,"s":[{"i":[[0,-16.75],[0,0],[-20.006,-7.188],[0,0],[0,0],[-1.142,41.075],[0,0],[49.955,-0.925],[0,0],[0,0]],"o":[[0,0],[-0.217,34.15],[0,0],[0,0],[48.788,0.387],[0,0],[0.75,-39.875],[0,0],[0,0],[-24.98,7.975]],"v":[[-439.2,-468.15],[-438.72,458.425],[-413.244,530.237],[-141.041,640.35],[527.537,641.212],[603.067,569.987],[603.15,-564.787],[526.445,-638.125],[-143.993,-637.125],[-411.07,-528.125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0,-16.75],[0,0],[-29.869,-7.75],[0,0],[0,0],[-1.179,40.625],[0,0],[45.942,-1],[0,0],[0,0]],"o":[[0,0],[-0.555,34.75],[0,0],[0,0],[48.263,0.5],[0,0],[0.75,-39.5],[0,0],[0,0],[-35.442,7.375]],"v":[[-503.25,-477.75],[-503.57,464.25],[-468.756,540],[-113.141,639],[545.612,640.5],[620.429,569.875],[620.25,-567.375],[544.933,-637.375],[-118.718,-636.75],[-463.308,-537.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0.062,-18.625],[0,0],[-31.869,-6.438],[0,0],[0,0],[-1.179,40.438],[0,0],[44.63,-1],[0,0],[0,0]],"o":[[0,0],[-0.43,34.75],[0,0],[0,0],[47.638,0.625],[0,0],[0.5,-39.062],[0,0],[0,0],[-38.817,5.938]],"v":[[-584.938,-494.688],[-585.445,486.188],[-542.506,557.375],[-88.141,639],[570.612,639.75],[646.054,568.625],[646.188,-566.875],[570.495,-636.625],[-92.093,-636],[-537.183,-555.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0.125,-20.5],[0,0],[-33.869,-5.125],[0,0],[0,0],[-1.179,40.25],[0,0],[43.317,-1],[0,0],[0,0]],"o":[[0,0],[-0.305,34.75],[0,0],[0,0],[47.013,0.75],[0,0],[0.25,-38.625],[0,0],[0,0],[-42.192,4.5]],"v":[[-640.125,-510.625],[-640.32,507.125],[-589.256,573.75],[-63.141,639],[591.112,639.25],[667.179,567.625],[666.625,-567.5],[593.058,-635.5],[-65.468,-635.25],[-584.558,-571.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96.666,"s":[{"i":[[0.062,-30.685],[0,0],[-36.306,-4.562],[0,0],[0,0],[-0.59,40.56],[0,0],[43.317,-1],[0,0],[0,0]],"o":[[0,0],[-0.43,36.25],[0,0],[0,0],[45.45,0.25],[0,0],[0.25,-38.625],[0,0],[0,0],[-41.442,3.938]],"v":[[-676.125,-519.625],[-676.008,522.062],[-617.819,589.812],[-46.391,638.5],[609.862,638.75],[684.617,567.25],[684.375,-566.125],[610.808,-634.75],[-47.593,-634.5],[-616.558,-587.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,-40.869],[0,0],[-38.744,-4],[0,0],[0,0],[0,40.869],[0,0],[43.317,-1],[0,0],[0,0]],"o":[[0,0],[-0.555,37.75],[0,0],[0,0],[43.888,-0.25],[0,0],[0.25,-38.625],[0,0],[0,0],[-40.692,3.375]],"v":[[-697.625,-528.125],[-696.945,535.5],[-634.381,602.875],[-29.641,638],[625.612,638.25],[699.054,566.875],[699.125,-564.75],[625.558,-634],[-29.718,-633.75],[-633.558,-599.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106.666,"s":[{"i":[[0,-40.869],[0,0],[-42.994,-3.25],[0,0],[0,0],[0,40.869],[0,0],[43.317,-1],[0,0],[0,0]],"o":[[0,0],[-0.93,42.625],[0,0],[0,0],[43.888,-0.25],[0,0],[0.25,-38.625],[0,0],[0,0],[-41.067,2.125]],"v":[[-715.875,-547.625],[-715.945,550.5],[-647.881,622.875],[-8.891,637.25],[646.487,637.75],[719.679,565.375],[718.875,-563.25],[646.183,-633.75],[-9.218,-633.875],[-649.058,-619.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113.334,"s":[{"i":[[0,-40.869],[0,0],[-44.369,-2],[0,0],[0,0],[0,40.869],[0,0],[43.317,-1],[0,0],[0,0]],"o":[[0,0],[-0.305,42.5],[0,0],[0,0],[43.888,-0.25],[0,0],[0.25,-38.625],[0,0],[0,0],[-40.423,0]],"v":[[-720.125,-562.375],[-720.82,561.25],[-649.131,634],[3.109,637.25],[658.987,636.625],[729.679,566.125],[729.625,-564.75],[657.308,-633.375],[2.907,-633.625],[-649.808,-630.5]],"c":true}]},{"t":120,"s":[{"i":[[0,-40.869],[0,0],[-46.994,-1.25],[0,0],[0,0],[0,40.869],[0,0],[43.317,-1],[0,0],[0,0]],"o":[[0,0],[-0.305,42.5],[0,0],[0,0],[43.888,-0.25],[0,0],[0.75,-40],[0,0],[0,0],[-40.423,0]],"v":[[-721.75,-564.375],[-721.57,561.5],[-649.631,637.75],[-3.641,637.25],[659.862,637],[733.179,565.625],[732.75,-564.875],[659.058,-634.25],[2.032,-633.75],[-648.683,-634.375]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":18,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":241,"st":60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"BUTTON","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[71.498,114.825,0],"ix":2,"l":2},"a":{"a":0,"k":[-698.509,46.873,0],"ix":1,"l":2},"s":{"a":0,"k":[40,18,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-0.549,1.538],[0,0]],"o":[[0,0],[-0.554,-3.125],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[27.005,73.269],[26.312,72.137],[26,64.269],[25.741,-76.022],[26.183,-82.526],[27.125,-83.046]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-0.987,1.288],[0,0]],"o":[[0,0],[-0.741,-2.125],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[10.88,76.769],[7.625,75.627],[6.812,68.26],[6.553,-78.594],[7.808,-85.536],[11.375,-86.11]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-1.424,1.038],[0,0]],"o":[[0,0],[-0.929,-1.125],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-1.995,80.394],[-4.313,79.127],[-5.625,72.26],[-5.884,-81.156],[-3.817,-88.536],[-1.625,-89.046]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-1.581,0.831],[0,0]],"o":[[0,0],[-1.154,-1.025],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-4.92,81.663],[-10.263,80.552],[-11.8,73.81],[-11.934,-81.831],[-9.692,-89.236],[-4.55,-89.626]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-1.817,0.519],[0,0]],"o":[[0,0],[-1.491,-0.875],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-10.917,83.519],[-16.922,82.69],[-18.797,76.135],[-18.744,-82.844],[-16.239,-90.286],[-10.547,-90.546]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.052,0.208],[0,0]],"o":[[0,0],[-1.829,-0.725],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-16.289,85.394],[-21.456,84.827],[-23.669,78.46],[-23.428,-83.856],[-20.661,-91.336],[-15.919,-91.446]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106.666,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.054,-0.625],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-20.027,86.635],[-23.438,86.252],[-25.875,80.01],[-25.509,-84.531],[-22.567,-92.036],[-19.656,-92.055]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.069,-0.563],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-20.345,86.897],[-24.538,86.552],[-26.994,80.535],[-26.609,-85.031],[-23.661,-92.186],[-19.969,-92.205]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":109.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.116,-0.375],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-21.718,87.685],[-26.535,87.452],[-29.048,82.11],[-28.605,-86.531],[-25.64,-92.636],[-21.323,-92.655]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.163,-0.188],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-22.982,88.472],[-27.674,88.352],[-30.243,83.685],[-29.742,-88.031],[-26.76,-93.086],[-22.568,-93.105]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112.666,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.194,-0.063],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-24.293,88.997],[-28.485,88.952],[-31.091,84.735],[-30.553,-89.031],[-27.558,-93.386],[-23.866,-93.405]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.209,0],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-24.558,89.26],[-28.188,89.252],[-30.812,85.26],[-30.255,-89.531],[-27.255,-93.536],[-24.125,-93.555]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":114.666,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.209,0],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-24.765,89.36],[-28.394,89.352],[-31.219,85.359],[-30.748,-89.532],[-27.548,-93.536],[-24.419,-93.555]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.209,0],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-25.373,90.005],[-27.502,89.998],[-31.127,86],[-31.005,-89.291],[-27.005,-93.291],[-25.375,-93.31]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-698.509,46.873],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":96,"op":241,"st":60,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"BUTTON 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[71.498,179.325,0],"ix":2,"l":2},"a":{"a":0,"k":[-698.509,46.873,0],"ix":1,"l":2},"s":{"a":0,"k":[40,25,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-0.549,1.538],[0,0]],"o":[[0,0],[-0.554,-3.125],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[27.005,73.269],[26.312,72.137],[26,64.269],[25.741,-76.022],[26.183,-82.526],[27.125,-83.046]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-0.987,1.288],[0,0]],"o":[[0,0],[-0.741,-2.125],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[10.88,76.769],[7.625,75.627],[6.812,68.26],[6.553,-78.594],[7.808,-85.536],[11.375,-86.11]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-1.424,1.038],[0,0]],"o":[[0,0],[-0.929,-1.125],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-1.995,80.394],[-4.313,79.127],[-5.625,72.26],[-5.884,-81.156],[-3.817,-88.536],[-1.625,-89.046]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-1.581,0.831],[0,0]],"o":[[0,0],[-1.154,-1.025],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-4.92,81.663],[-10.263,80.552],[-11.8,73.81],[-11.934,-81.831],[-9.692,-89.236],[-4.55,-89.626]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-1.817,0.519],[0,0]],"o":[[0,0],[-1.491,-0.875],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-10.917,83.519],[-16.922,82.69],[-18.797,76.135],[-18.744,-82.844],[-16.239,-90.286],[-10.547,-90.546]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.052,0.208],[0,0]],"o":[[0,0],[-1.829,-0.725],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-16.289,85.394],[-21.456,84.827],[-23.669,78.46],[-23.428,-83.856],[-20.661,-91.336],[-15.919,-91.446]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106.666,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.054,-0.625],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-20.027,86.635],[-23.438,86.252],[-25.875,80.01],[-25.509,-84.531],[-22.567,-92.036],[-19.656,-92.055]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.069,-0.563],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-20.345,86.897],[-24.538,86.552],[-26.994,80.535],[-26.609,-85.031],[-23.661,-92.186],[-19.969,-92.205]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":109.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.116,-0.375],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-21.718,87.685],[-26.535,87.452],[-29.048,82.11],[-28.605,-86.531],[-25.64,-92.636],[-21.323,-92.655]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.163,-0.188],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-22.982,88.472],[-27.674,88.352],[-30.243,83.685],[-29.742,-88.031],[-26.76,-93.086],[-22.568,-93.105]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112.666,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.194,-0.063],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-24.293,88.997],[-28.485,88.952],[-31.091,84.735],[-30.553,-89.031],[-27.558,-93.386],[-23.866,-93.405]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.209,0],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-24.558,89.26],[-28.188,89.252],[-30.812,85.26],[-30.255,-89.531],[-27.255,-93.536],[-24.125,-93.555]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":114.666,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.209,0],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-24.765,89.36],[-28.394,89.352],[-31.219,85.359],[-30.748,-89.532],[-27.548,-93.536],[-24.419,-93.555]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.209,0],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-25.373,89.63],[-27.502,89.623],[-31.127,85.625],[-31.005,-89.666],[-27.005,-93.666],[-25.375,-93.685]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-698.509,46.873],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":96,"op":241,"st":60,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/rear_display_turnaround.json b/packages/SystemUI/res/raw/rear_display_turnaround.json
new file mode 100644
index 0000000..82204c7
--- /dev/null
+++ b/packages/SystemUI/res/raw/rear_display_turnaround.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":181,"w":412,"h":300,"nm":"Turnaround - Generic Version V01","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"SHUTTER_ANIMATION 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":55,"s":[100]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":320.842,"ix":3},"y":{"a":0,"k":149.716,"ix":4}},"a":{"a":0,"k":[460.228,450.736,0],"ix":1,"l":2},"s":{"a":0,"k":[150,150,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.528,0],[0,-2.528],[2.528,0],[0,2.528]],"o":[[2.528,0],[0,2.528],[-2.528,0],[0,-2.528]],"v":[[0,-4.5],[4.5,0],[0,4.5],[-4.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 599","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[460.228,450.736],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[140,140],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"SHUTTER_01","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-5.337,0],[0,-5.337],[5.337,0],[0,5.337]],"o":[[5.337,0],[0,5.337],[-5.337,0],[0,-5.337]],"v":[[0,-9.5],[9.5,0],[0,9.5],[-9.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 598","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[460.228,450.736],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"SHUTTER_02","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":-90,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"SHUTTER_ANIMATION","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":51,"s":[0]},{"t":56,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":286.342,"ix":3},"y":{"a":0,"k":245.716,"ix":4}},"a":{"a":0,"k":[460.228,450.736,0],"ix":1,"l":2},"s":{"a":0,"k":[150,150,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.528,0],[0,-2.528],[2.528,0],[0,2.528]],"o":[[2.528,0],[0,2.528],[-2.528,0],[0,-2.528]],"v":[[0,-4.5],[4.5,0],[0,4.5],[-4.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 599","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[460.228,450.736],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[140,140],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":129,"s":[0]},{"t":139,"s":[100]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"SHUTTER_01","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-5.337,0],[0,-5.337],[5.337,0],[0,5.337]],"o":[[5.337,0],[0,5.337],[-5.337,0],[0,-5.337]],"v":[[0,-9.5],[9.5,0],[0,9.5],[-9.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 598","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[460.228,450.736],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":129,"s":[0]},{"t":139,"s":[100]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"SHUTTER_02","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":129,"op":279,"st":39,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"PATCH 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[207.062,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99.166,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[54.347,-125.337],[54.347,125.337],[-15.847,125.337],[-15.847,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[34.347,-125.337],[34.347,125.337],[-35.847,125.337],[-35.847,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":109.166,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-3.683,-125.337],[-3.683,125.337],[-73.877,125.337],[-73.877,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-3.867,-125.337],[-3.867,125.337],[-74.061,125.337],[-74.061,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113.334,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.606,-125.337],[-1.606,125.337],[-71.799,125.337],[-71.799,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":115,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[0.775,-125.337],[0.775,125.337],[-69.418,125.337],[-69.418,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":117.5,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[4.347,-125.337],[4.347,125.337],[-65.847,125.337],[-65.847,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":121.666,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[9.686,-125.337],[9.686,125.337],[-60.508,125.337],[-60.508,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":122.5,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[11.954,-125.337],[11.954,125.337],[-58.24,125.337],[-58.24,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":125.834,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[14.759,-125.337],[14.759,125.337],[-55.435,125.337],[-55.435,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126.666,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[15.668,-125.337],[15.668,125.337],[-54.525,125.337],[-54.525,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":128.334,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[15.987,-125.337],[15.987,125.337],[-54.206,125.337],[-54.206,-125.337]],"c":true}]},{"t":135,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[16.847,-125.337],[16.847,125.337],[-53.347,125.337],[-53.347,-125.337]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-674.097,51.337],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":99,"op":300,"st":60,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"PATCH","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[207.908,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[722.046,-80],[722.046,165.813],[708.25,165.813],[708.25,-80]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[736.168,-80],[736.168,165.813],[722.372,165.813],[722.372,-80]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65.834,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[737.98,-80],[737.98,165.813],[724.184,165.813],[724.184,-80]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":66.666,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[739.435,-80],[739.435,165.813],[725.639,165.813],[725.639,-80]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":67.5,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[740.796,-80],[740.796,165.813],[727,165.813],[727,-80]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":69.166,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[743.651,-79.231],[743.651,166.583],[729.855,166.583],[729.855,-79.231]],"c":true}]},{"t":72.5,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[738.112,-77.692],[738.112,168.121],[724.316,168.121],[724.316,-77.692]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":74,"st":60,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"BUTTON_OUT_BOTTOM 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":60,"s":[206.312,95.219,0],"to":[0,-2.417,0],"ti":[0,0,0]},{"i":{"x":0.333,"y":1},"o":{"x":0.167,"y":0.167},"t":88,"s":[206.312,80.719,0],"to":[0,0,0],"ti":[0,-2.417,0]},{"t":128,"s":[206.312,95.219,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,18,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,-9.113],[0,0],[3.544,-0.062],[0,9.113],[0,0],[-1.905,0.228]],"o":[[0,0],[0,9.113],[-1.908,0.034],[0,0],[0,-9.113],[4.17,-0.5]],"v":[[687.673,-97.826],[687.673,65.299],[679.205,76.875],[675.75,65.375],[675.75,-97.75],[679.205,-108.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,-9.113],[0,0],[4.532,-0.062],[0,9.113],[0,0],[-2.436,0.228]],"o":[[0,0],[0,9.113],[-2.44,0.034],[0,0],[0,-9.113],[5.332,-0.5]],"v":[[699.977,-97.944],[699.977,68.306],[689.293,80],[684.875,68.5],[684.875,-97.75],[689.293,-108.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[0,-9.113],[0,0],[4.532,-0.062],[0,9.113],[0,0],[-2.436,0.228]],"o":[[0,0],[0,9.113],[-2.44,0.034],[0,0],[0,-9.113],[5.332,-0.5]],"v":[[711.227,-100.491],[711.227,76.697],[700.543,87.875],[696.125,76.375],[696.125,-100.812],[700.543,-111.688]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73,"s":[{"i":[[0,-9.113],[0,0],[5.113,-0.062],[0,9.113],[0,0],[-2.748,0.228]],"o":[[0,0],[0,9.113],[-2.753,0.034],[0,0],[0,-9.113],[6.016,-0.5]],"v":[[700.685,-102.905],[700.685,81.345],[692.568,93.125],[687.583,81.625],[687.583,-102.625],[692.568,-113.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74.166,"s":[{"i":[[0,-9.113],[0,0],[5.694,-0.062],[0,9.113],[0,0],[-3.061,0.228]],"o":[[0,0],[0,9.113],[-3.065,0.034],[0,0],[0,-9.113],[6.699,-0.5]],"v":[[691.581,-103.125],[691.581,83.5],[686.03,95],[680.479,83.5],[680.479,-103.125],[686.03,-114]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[0,-9.113],[0,0],[6.275,-0.062],[0,9.113],[0,0],[-3.373,0.228]],"o":[[0,0],[0,9.113],[-3.378,0.034],[0,0],[0,-9.113],[7.383,-0.5]],"v":[[683.359,-103.625],[683.359,85.375],[677.242,96.875],[671.125,85.375],[671.125,-103.625],[677.242,-114.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75.834,"s":[{"i":[[0,-9.113],[0,0],[6.856,-0.062],[0,9.113],[0,0],[-3.685,0.228]],"o":[[0,0],[0,9.113],[-3.691,0.034],[0,0],[0,-9.113],[8.066,-0.5]],"v":[[672.305,-104.125],[672.305,87.25],[665.621,98.75],[658.938,87.25],[658.938,-104.125],[665.621,-115]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,-9.113],[0,0],[7.438,-0.062],[0,9.113],[0,0],[-3.997,0.228]],"o":[[0,0],[0,9.113],[-4.004,0.034],[0,0],[0,-9.113],[8.75,-0.5]],"v":[[657.5,-104.625],[657.5,89.125],[650.25,100.625],[643,89.125],[643,-104.625],[650.25,-115.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":77.5,"s":[{"i":[[0,-9.113],[0,0],[7.149,-0.047],[0,9.113],[0,0],[-4.569,0.171]],"o":[[0,0],[0,9.113],[-4.573,0.025],[0,0],[0,-9.113],[8.133,-0.375]],"v":[[637.562,-104.531],[637.562,90.531],[629.281,103.281],[621,90.531],[621,-104.531],[629.281,-116.812]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78.334,"s":[{"i":[[0,-9.113],[0,0],[6.86,-0.031],[0,9.113],[0,0],[-5.14,0.114]],"o":[[0,0],[0,9.113],[-5.143,0.017],[0,0],[0,-9.113],[7.516,-0.25]],"v":[[612.625,-104.438],[612.625,91.938],[603.312,105.938],[594,91.938],[594,-104.438],[603.312,-118.125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79.166,"s":[{"i":[[0,-9.113],[0,0],[6.571,-0.016],[0,9.113],[0,0],[-5.711,0.057]],"o":[[0,0],[0,9.113],[-5.713,0.008],[0,0],[0,-9.113],[6.899,-0.125]],"v":[[580.188,-104.094],[580.188,93.594],[569.844,108.844],[559.5,93.594],[559.5,-104.094],[569.844,-119.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,-9.113],[0,0],[6.282,0],[0,9.113],[0,0],[-6.282,0]],"o":[[0,0],[0,9.113],[-6.282,0],[0,0],[0,-9.113],[6.282,0]],"v":[[540.25,-103.75],[540.25,95.25],[528.875,111.75],[517.5,95.25],[517.5,-103.75],[528.875,-120.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80.834,"s":[{"i":[[0,-9.113],[0,0],[6.731,0],[0,9.113],[0,0],[-6.731,0]],"o":[[0,0],[0,9.113],[-6.731,0],[0,0],[0,-9.113],[6.731,0]],"v":[[491.25,-104.562],[491.25,97.688],[479.062,114.188],[466.875,97.688],[466.875,-104.562],[479.062,-121.062]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0,-9.113],[0,0],[7.18,0],[0,9.113],[0,0],[-7.18,0]],"o":[[0,0],[0,9.113],[-7.18,0],[0,0],[0,-9.113],[7.18,0]],"v":[[432.958,-105.375],[432.958,100.125],[419.958,116.625],[406.958,100.125],[406.958,-105.375],[419.958,-121.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82.5,"s":[{"i":[[0,-9.113],[0,0],[7.628,0],[0,9.113],[0,0],[-7.628,0]],"o":[[0,0],[0,9.113],[-7.628,0],[0,0],[0,-9.113],[7.628,0]],"v":[[364.854,-106.188],[364.854,102.562],[351.042,119.062],[337.229,102.562],[337.229,-106.188],[351.042,-122.688]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[0,-9.113],[0,0],[8.077,0],[0,9.113],[0,0],[-8.077,0]],"o":[[0,0],[0,9.113],[-8.077,0],[0,0],[0,-9.113],[8.077,0]],"v":[[286.75,-107],[286.75,105],[272.125,121.5],[257.5,105],[257.5,-107],[272.125,-123.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[0,-9.113],[0,0],[8.33,0],[0,9.113],[0,0],[-8.33,0]],"o":[[0,0],[0,9.113],[-8.33,0],[0,0],[0,-9.113],[8.33,0]],"v":[[201.667,-107.5],[201.667,106.167],[186.583,122.667],[171.5,106.167],[171.5,-107.5],[186.583,-124]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85,"s":[{"i":[[0,-9.113],[0,0],[8.583,0],[0,9.113],[0,0],[-8.583,0]],"o":[[0,0],[0,9.113],[-8.583,0],[0,0],[0,-9.113],[8.583,0]],"v":[[110.333,-108],[110.333,107.333],[94.792,123.833],[79.25,107.333],[79.25,-108],[94.792,-124.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85.834,"s":[{"i":[[0,-9.113],[0,0],[8.837,0],[0,9.113],[0,0],[-8.837,0]],"o":[[0,0],[0,9.113],[-8.837,0],[0,0],[0,-9.113],[8.837,0]],"v":[[16.5,-108.5],[16.5,108.5],[0.5,125],[-15.5,108.5],[-15.5,-108.5],[0.5,-125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0,-9.113],[0,0],[8.698,0],[0,9.113],[0,0],[-8.699,0]],"o":[[0,0],[0,9.113],[-8.699,0],[0,0],[0,-9.113],[8.698,0]],"v":[[-75.766,-108.229],[-75.766,107.667],[-91.516,124.167],[-107.266,107.667],[-107.266,-108.229],[-91.516,-124.729]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0,-9.113],[0,0],[8.56,0],[0,9.113],[0,0],[-8.56,0]],"o":[[0,0],[0,9.113],[-8.56,0],[0,0],[0,-9.113],[8.56,0]],"v":[[-166.781,-107.958],[-166.781,106.833],[-182.281,123.333],[-197.781,106.833],[-197.781,-107.958],[-182.281,-124.458]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88.334,"s":[{"i":[[0,-9.113],[0,0],[8.422,0],[0,9.113],[0,0],[-8.422,0]],"o":[[0,0],[0,9.113],[-8.422,0],[0,0],[0,-9.113],[8.422,0]],"v":[[-252.172,-107.688],[-252.172,106],[-267.422,122.5],[-282.672,106],[-282.672,-107.688],[-267.422,-124.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89.166,"s":[{"i":[[0,-9.113],[0,0],[8.284,0],[0,9.113],[0,0],[-8.284,0]],"o":[[0,0],[0,9.113],[-8.284,0],[0,0],[0,-9.113],[8.284,0]],"v":[[-330.062,-107.417],[-330.062,105.167],[-345.062,121.667],[-360.062,105.167],[-360.062,-107.417],[-345.062,-123.917]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,-9.113],[0,0],[8.146,0],[0,9.113],[0,0],[-8.146,0]],"o":[[0,0],[0,9.113],[-8.146,0],[0,0],[0,-9.113],[8.146,0]],"v":[[-400.781,-107.146],[-400.781,103.396],[-415.531,119.896],[-430.281,103.396],[-430.281,-107.146],[-415.531,-123.646]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90.834,"s":[{"i":[[0,-9.113],[0,0],[8.008,0],[0,9.113],[0,0],[-8.008,0]],"o":[[0,0],[0,9.113],[-8.008,0],[0,0],[0,-9.113],[8.008,0]],"v":[[-462.75,-106.875],[-462.75,101.625],[-477.25,118.125],[-491.75,101.625],[-491.75,-106.875],[-477.25,-123.375]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91.666,"s":[{"i":[[0,-9.113],[0,0],[7.525,0],[0,9.113],[0,0],[-7.525,0]],"o":[[0,0],[0,9.113],[-7.525,0],[0,0],[0,-9.113],[7.525,0]],"v":[[-518.308,-106.5],[-518.308,100.125],[-531.933,116.125],[-545.558,100.125],[-545.558,-106.5],[-531.933,-122.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.5,"s":[{"i":[[0,-9.113],[0,0],[7.042,0],[0,9.113],[0,0],[-7.042,0]],"o":[[0,0],[0,9.113],[-7.042,0],[0,0],[0,-9.113],[7.042,0]],"v":[[-566.367,-106.125],[-566.367,98.625],[-579.117,114.125],[-591.867,98.625],[-591.867,-106.125],[-579.117,-121.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0,-9.113],[0,0],[6.558,0],[0,9.113],[0,0],[-6.558,0]],"o":[[0,0],[0,9.113],[-6.558,0],[0,0],[0,-9.113],[6.558,0]],"v":[[-606.925,-105.75],[-606.925,97.125],[-618.8,112.125],[-630.675,97.125],[-630.675,-105.75],[-618.8,-120.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94.166,"s":[{"i":[[0,-9.113],[0,0],[6.075,0],[0,9.113],[0,0],[-6.075,0]],"o":[[0,0],[0,9.113],[-6.075,0],[0,0],[0,-9.113],[6.075,0]],"v":[[-641.963,-105.375],[-641.963,95.625],[-652.963,110.125],[-663.963,95.625],[-663.963,-105.375],[-652.963,-119.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,-9.113],[0,0],[5.592,0],[0,9.113],[0,0],[-5.592,0]],"o":[[0,0],[0,9.113],[-5.592,0],[0,0],[0,-9.113],[5.592,0]],"v":[[-671.062,-105],[-671.062,94.125],[-681.188,108.125],[-691.312,94.125],[-691.312,-105],[-681.188,-119]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95.834,"s":[{"i":[[0,-9.113],[0,0],[5.295,0],[0,9.113],[0,0],[-5.295,0]],"o":[[0,0],[0,9.113],[-5.295,0],[0,0],[0,-9.113],[5.295,0]],"v":[[-696.971,-104.5],[-696.971,92.5],[-706.558,106.25],[-716.146,92.5],[-716.146,-104.5],[-706.558,-118.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96.666,"s":[{"i":[[0,-9.113],[0,0],[4.998,0],[0,9.113],[0,0],[-4.998,0]],"o":[[0,0],[0,9.113],[-4.998,0],[0,0],[0,-9.113],[4.998,0]],"v":[[-718.192,-104],[-718.192,90.875],[-727.242,104.375],[-736.292,90.875],[-736.292,-104],[-727.242,-117.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0,-9.113],[0,0],[4.701,0],[0,9.113],[0,0],[-4.701,0]],"o":[[0,0],[0,9.113],[-4.701,0],[0,0],[0,-9.113],[4.701,0]],"v":[[-735.35,-103.5],[-735.35,89.25],[-743.862,102.5],[-752.375,89.25],[-752.375,-103.5],[-743.862,-116.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98.334,"s":[{"i":[[0,-9.113],[0,0],[4.404,0],[0,9.113],[0,0],[-4.404,0]],"o":[[0,0],[0,9.113],[-4.404,0],[0,0],[0,-9.113],[4.404,0]],"v":[[-750.425,-103],[-750.425,87.625],[-758.4,100.625],[-766.375,87.625],[-766.375,-103],[-758.4,-116]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99.166,"s":[{"i":[[0,-9.113],[0,0],[4.108,0],[0,9.113],[0,0],[-4.108,0]],"o":[[0,0],[0,9.113],[-4.108,0],[0,0],[0,-9.113],[4.108,0]],"v":[[-761.75,-102.5],[-761.75,86],[-769.188,98.75],[-776.625,86],[-776.625,-102.5],[-769.188,-115.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,-9.113],[0,0],[3.844,-0.015],[0.141,9.991],[0,0],[-4.51,0]],"o":[[0,0],[0,9.113],[-4.635,0.031],[0,0],[0.016,-9.897],[3.844,0]],"v":[[-771.312,-102.188],[-771.312,84.594],[-778.273,97.344],[-785.234,84.594],[-785.234,-102.188],[-778.273,-114.938]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[0,-9.113],[0,0],[3.581,-0.031],[0.281,10.869],[0,0],[-4.913,0]],"o":[[0,0],[0,9.113],[-5.163,0.062],[0,0],[0.031,-10.681],[3.581,0]],"v":[[-779.25,-101.875],[-779.25,83.188],[-785.734,95.938],[-792.219,83.188],[-792.219,-101.875],[-785.734,-114.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.666,"s":[{"i":[[0,-9.113],[0,0],[3.317,-0.046],[0.422,11.747],[0,0],[-5.316,0]],"o":[[0,0],[0,9.113],[-5.691,0.094],[0,0],[0.047,-11.466],[3.318,0]],"v":[[-786.125,-101.562],[-786.125,81.781],[-792.133,94.531],[-798.141,81.781],[-798.141,-101.562],[-792.133,-114.312]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":102.5,"s":[{"i":[[0,-9.113],[0,0],[3.054,-0.061],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-6.219,0.125],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-791.75,-101.25],[-791.75,80.375],[-797.281,93.125],[-802.812,80.375],[-802.812,-101.25],[-797.281,-114]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[0,-9.113],[0,0],[3.054,-0.07],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-6.51,0.153],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-796.934,-100.556],[-796.934,78.292],[-802.465,91.042],[-807.997,78.292],[-807.997,-100.556],[-802.465,-113.306]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.834,"s":[{"i":[[0,-9.113],[0,0],[3.054,-0.079],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-6.802,0.181],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-799.618,-99.861],[-799.618,76.208],[-805.149,88.958],[-810.681,76.208],[-810.681,-99.861],[-805.149,-112.611]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":108.334,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.092],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.24,0.222],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-800.61,-98.819],[-800.61,73.083],[-806.141,85.833],[-811.672,73.083],[-811.672,-98.819],[-806.141,-111.569]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-799.188,-98.125],[-799.188,71],[-804.719,83.75],[-814.636,70.875],[-814.636,-98.25],[-804.719,-110.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-787.944,-96.125],[-787.944,66.75],[-793.475,79.5],[-804.019,66.875],[-804.019,-96],[-793.475,-108.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-780.109,-96.579],[-780.109,65.77],[-785.64,78.355],[-796.184,65.566],[-796.184,-96.783],[-785.64,-108.836]],"c":true}]},{"t":128,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-779.207,-96.704],[-779.207,65.118],[-784.738,77.54],[-794.029,65.112],[-794.029,-96.71],[-784.738,-108.467]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[53.5,61],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":60,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"BUTTON_OUT_BOTTOM","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,157.219,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,25,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,-9.113],[0,0],[3.544,-0.062],[0,9.113],[0,0],[-1.905,0.228]],"o":[[0,0],[0,9.113],[-1.908,0.034],[0,0],[0,-9.113],[4.17,-0.5]],"v":[[688.299,-98],[688.299,65.125],[679.205,76.875],[675.75,65.375],[675.75,-97.75],[679.205,-108.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,-9.113],[0,0],[4.532,-0.062],[0,9.113],[0,0],[-2.436,0.228]],"o":[[0,0],[0,9.113],[-2.44,0.034],[0,0],[0,-9.113],[5.332,-0.5]],"v":[[701.856,-97.875],[701.856,68.375],[689.293,80],[684.875,68.5],[684.875,-97.75],[689.293,-108.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[0,-9.113],[0,0],[4.532,-0.062],[0,9.113],[0,0],[-2.436,0.228]],"o":[[0,0],[0,9.113],[-2.44,0.034],[0,0],[0,-9.113],[5.332,-0.5]],"v":[[709.974,-101.062],[709.974,76.125],[700.543,87.875],[696.125,76.375],[696.125,-100.812],[700.543,-111.688]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73,"s":[{"i":[[0,-9.113],[0,0],[5.113,-0.062],[0,9.113],[0,0],[-2.748,0.228]],"o":[[0,0],[0,9.113],[-2.753,0.034],[0,0],[0,-9.113],[6.016,-0.5]],"v":[[703.818,-102.625],[703.818,81.625],[692.568,93.125],[687.583,81.625],[687.583,-102.625],[692.568,-113.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74.166,"s":[{"i":[[0,-9.113],[0,0],[5.694,-0.062],[0,9.113],[0,0],[-3.061,0.228]],"o":[[0,0],[0,9.113],[-3.065,0.034],[0,0],[0,-9.113],[6.699,-0.5]],"v":[[691.581,-103.125],[691.581,83.5],[686.03,95],[680.479,83.5],[680.479,-103.125],[686.03,-114]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[0,-9.113],[0,0],[6.275,-0.062],[0,9.113],[0,0],[-3.373,0.228]],"o":[[0,0],[0,9.113],[-3.378,0.034],[0,0],[0,-9.113],[7.383,-0.5]],"v":[[683.359,-103.625],[683.359,85.375],[677.242,96.875],[671.125,85.375],[671.125,-103.625],[677.242,-114.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75.834,"s":[{"i":[[0,-9.113],[0,0],[6.856,-0.062],[0,9.113],[0,0],[-3.685,0.228]],"o":[[0,0],[0,9.113],[-3.691,0.034],[0,0],[0,-9.113],[8.066,-0.5]],"v":[[672.305,-104.125],[672.305,87.25],[665.621,98.75],[658.938,87.25],[658.938,-104.125],[665.621,-115]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,-9.113],[0,0],[7.438,-0.062],[0,9.113],[0,0],[-3.997,0.228]],"o":[[0,0],[0,9.113],[-4.004,0.034],[0,0],[0,-9.113],[8.75,-0.5]],"v":[[657.5,-104.625],[657.5,89.125],[650.25,100.625],[643,89.125],[643,-104.625],[650.25,-115.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":77.5,"s":[{"i":[[0,-9.113],[0,0],[7.149,-0.047],[0,9.113],[0,0],[-4.569,0.171]],"o":[[0,0],[0,9.113],[-4.573,0.025],[0,0],[0,-9.113],[8.133,-0.375]],"v":[[637.562,-104.531],[637.562,90.531],[629.281,103.281],[621,90.531],[621,-104.531],[629.281,-116.812]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78.334,"s":[{"i":[[0,-9.113],[0,0],[6.86,-0.031],[0,9.113],[0,0],[-5.14,0.114]],"o":[[0,0],[0,9.113],[-5.143,0.017],[0,0],[0,-9.113],[7.516,-0.25]],"v":[[612.625,-104.438],[612.625,91.938],[603.312,105.938],[594,91.938],[594,-104.438],[603.312,-118.125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79.166,"s":[{"i":[[0,-9.113],[0,0],[6.571,-0.016],[0,9.113],[0,0],[-5.711,0.057]],"o":[[0,0],[0,9.113],[-5.713,0.008],[0,0],[0,-9.113],[6.899,-0.125]],"v":[[580.188,-104.094],[580.188,93.594],[569.844,108.844],[559.5,93.594],[559.5,-104.094],[569.844,-119.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,-9.113],[0,0],[6.282,0],[0,9.113],[0,0],[-6.282,0]],"o":[[0,0],[0,9.113],[-6.282,0],[0,0],[0,-9.113],[6.282,0]],"v":[[540.25,-103.75],[540.25,95.25],[528.875,111.75],[517.5,95.25],[517.5,-103.75],[528.875,-120.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80.834,"s":[{"i":[[0,-9.113],[0,0],[6.731,0],[0,9.113],[0,0],[-6.731,0]],"o":[[0,0],[0,9.113],[-6.731,0],[0,0],[0,-9.113],[6.731,0]],"v":[[491.25,-104.562],[491.25,97.688],[479.062,114.188],[466.875,97.688],[466.875,-104.562],[479.062,-121.062]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0,-9.113],[0,0],[7.18,0],[0,9.113],[0,0],[-7.18,0]],"o":[[0,0],[0,9.113],[-7.18,0],[0,0],[0,-9.113],[7.18,0]],"v":[[432.958,-105.375],[432.958,100.125],[419.958,116.625],[406.958,100.125],[406.958,-105.375],[419.958,-121.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82.5,"s":[{"i":[[0,-9.113],[0,0],[7.628,0],[0,9.113],[0,0],[-7.628,0]],"o":[[0,0],[0,9.113],[-7.628,0],[0,0],[0,-9.113],[7.628,0]],"v":[[364.854,-106.188],[364.854,102.562],[351.042,119.062],[337.229,102.562],[337.229,-106.188],[351.042,-122.688]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[0,-9.113],[0,0],[8.077,0],[0,9.113],[0,0],[-8.077,0]],"o":[[0,0],[0,9.113],[-8.077,0],[0,0],[0,-9.113],[8.077,0]],"v":[[286.75,-107],[286.75,105],[272.125,121.5],[257.5,105],[257.5,-107],[272.125,-123.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[0,-9.113],[0,0],[8.33,0],[0,9.113],[0,0],[-8.33,0]],"o":[[0,0],[0,9.113],[-8.33,0],[0,0],[0,-9.113],[8.33,0]],"v":[[201.667,-107.5],[201.667,106.167],[186.583,122.667],[171.5,106.167],[171.5,-107.5],[186.583,-124]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85,"s":[{"i":[[0,-9.113],[0,0],[8.583,0],[0,9.113],[0,0],[-8.583,0]],"o":[[0,0],[0,9.113],[-8.583,0],[0,0],[0,-9.113],[8.583,0]],"v":[[110.333,-108],[110.333,107.333],[94.792,123.833],[79.25,107.333],[79.25,-108],[94.792,-124.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85.834,"s":[{"i":[[0,-9.113],[0,0],[8.837,0],[0,9.113],[0,0],[-8.837,0]],"o":[[0,0],[0,9.113],[-8.837,0],[0,0],[0,-9.113],[8.837,0]],"v":[[16.5,-108.5],[16.5,108.5],[0.5,125],[-15.5,108.5],[-15.5,-108.5],[0.5,-125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0,-9.113],[0,0],[8.698,0],[0,9.113],[0,0],[-8.699,0]],"o":[[0,0],[0,9.113],[-8.699,0],[0,0],[0,-9.113],[8.698,0]],"v":[[-75.766,-108.229],[-75.766,107.667],[-91.516,124.167],[-107.266,107.667],[-107.266,-108.229],[-91.516,-124.729]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0,-9.113],[0,0],[8.56,0],[0,9.113],[0,0],[-8.56,0]],"o":[[0,0],[0,9.113],[-8.56,0],[0,0],[0,-9.113],[8.56,0]],"v":[[-166.781,-107.958],[-166.781,106.833],[-182.281,123.333],[-197.781,106.833],[-197.781,-107.958],[-182.281,-124.458]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88.334,"s":[{"i":[[0,-9.113],[0,0],[8.422,0],[0,9.113],[0,0],[-8.422,0]],"o":[[0,0],[0,9.113],[-8.422,0],[0,0],[0,-9.113],[8.422,0]],"v":[[-252.172,-107.688],[-252.172,106],[-267.422,122.5],[-282.672,106],[-282.672,-107.688],[-267.422,-124.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89.166,"s":[{"i":[[0,-9.113],[0,0],[8.284,0],[0,9.113],[0,0],[-8.284,0]],"o":[[0,0],[0,9.113],[-8.284,0],[0,0],[0,-9.113],[8.284,0]],"v":[[-330.062,-107.417],[-330.062,105.167],[-345.062,121.667],[-360.062,105.167],[-360.062,-107.417],[-345.062,-123.917]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,-9.113],[0,0],[8.146,0],[0,9.113],[0,0],[-8.146,0]],"o":[[0,0],[0,9.113],[-8.146,0],[0,0],[0,-9.113],[8.146,0]],"v":[[-400.781,-107.146],[-400.781,103.396],[-415.531,119.896],[-430.281,103.396],[-430.281,-107.146],[-415.531,-123.646]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90.834,"s":[{"i":[[0,-9.113],[0,0],[8.008,0],[0,9.113],[0,0],[-8.008,0]],"o":[[0,0],[0,9.113],[-8.008,0],[0,0],[0,-9.113],[8.008,0]],"v":[[-462.75,-106.875],[-462.75,101.625],[-477.25,118.125],[-491.75,101.625],[-491.75,-106.875],[-477.25,-123.375]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91.666,"s":[{"i":[[0,-9.113],[0,0],[7.525,0],[0,9.113],[0,0],[-7.525,0]],"o":[[0,0],[0,9.113],[-7.525,0],[0,0],[0,-9.113],[7.525,0]],"v":[[-518.308,-106.5],[-518.308,100.125],[-531.933,116.125],[-545.558,100.125],[-545.558,-106.5],[-531.933,-122.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.5,"s":[{"i":[[0,-9.113],[0,0],[7.042,0],[0,9.113],[0,0],[-7.042,0]],"o":[[0,0],[0,9.113],[-7.042,0],[0,0],[0,-9.113],[7.042,0]],"v":[[-566.367,-106.125],[-566.367,98.625],[-579.117,114.125],[-591.867,98.625],[-591.867,-106.125],[-579.117,-121.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0,-9.113],[0,0],[6.558,0],[0,9.113],[0,0],[-6.558,0]],"o":[[0,0],[0,9.113],[-6.558,0],[0,0],[0,-9.113],[6.558,0]],"v":[[-606.925,-105.75],[-606.925,97.125],[-618.8,112.125],[-630.675,97.125],[-630.675,-105.75],[-618.8,-120.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94.166,"s":[{"i":[[0,-9.113],[0,0],[6.075,0],[0,9.113],[0,0],[-6.075,0]],"o":[[0,0],[0,9.113],[-6.075,0],[0,0],[0,-9.113],[6.075,0]],"v":[[-641.963,-105.375],[-641.963,95.625],[-652.963,110.125],[-663.963,95.625],[-663.963,-105.375],[-652.963,-119.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,-9.113],[0,0],[5.592,0],[0,9.113],[0,0],[-5.592,0]],"o":[[0,0],[0,9.113],[-5.592,0],[0,0],[0,-9.113],[5.592,0]],"v":[[-671.062,-105],[-671.062,94.125],[-681.188,108.125],[-691.312,94.125],[-691.312,-105],[-681.188,-119]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95.834,"s":[{"i":[[0,-9.113],[0,0],[5.295,0],[0,9.113],[0,0],[-5.295,0]],"o":[[0,0],[0,9.113],[-5.295,0],[0,0],[0,-9.113],[5.295,0]],"v":[[-696.971,-104.5],[-696.971,92.5],[-706.558,106.25],[-716.146,92.5],[-716.146,-104.5],[-706.558,-118.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96.666,"s":[{"i":[[0,-9.113],[0,0],[4.998,0],[0,9.113],[0,0],[-4.998,0]],"o":[[0,0],[0,9.113],[-4.998,0],[0,0],[0,-9.113],[4.998,0]],"v":[[-718.192,-104],[-718.192,90.875],[-727.242,104.375],[-736.292,90.875],[-736.292,-104],[-727.242,-117.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0,-9.113],[0,0],[4.701,0],[0,9.113],[0,0],[-4.701,0]],"o":[[0,0],[0,9.113],[-4.701,0],[0,0],[0,-9.113],[4.701,0]],"v":[[-735.35,-103.5],[-735.35,89.25],[-743.862,102.5],[-752.375,89.25],[-752.375,-103.5],[-743.862,-116.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98.334,"s":[{"i":[[0,-9.113],[0,0],[4.404,0],[0,9.113],[0,0],[-4.404,0]],"o":[[0,0],[0,9.113],[-4.404,0],[0,0],[0,-9.113],[4.404,0]],"v":[[-750.425,-103],[-750.425,87.625],[-758.4,100.625],[-766.375,87.625],[-766.375,-103],[-758.4,-116]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99.166,"s":[{"i":[[0,-9.113],[0,0],[4.108,0],[0,9.113],[0,0],[-4.108,0]],"o":[[0,0],[0,9.113],[-4.108,0],[0,0],[0,-9.113],[4.108,0]],"v":[[-761.75,-102.5],[-761.75,86],[-769.188,98.75],[-776.625,86],[-776.625,-102.5],[-769.188,-115.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,-9.113],[0,0],[3.844,-0.015],[0.141,9.991],[0,0],[-4.51,0]],"o":[[0,0],[0,9.113],[-4.635,0.031],[0,0],[0.016,-9.897],[3.844,0]],"v":[[-771.312,-102.188],[-771.312,84.594],[-778.273,97.344],[-785.234,84.594],[-785.234,-102.188],[-778.273,-114.938]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[0,-9.113],[0,0],[3.581,-0.031],[0.281,10.869],[0,0],[-4.913,0]],"o":[[0,0],[0,9.113],[-5.163,0.062],[0,0],[0.031,-10.681],[3.581,0]],"v":[[-779.25,-101.875],[-779.25,83.188],[-785.734,95.938],[-792.219,83.188],[-792.219,-101.875],[-785.734,-114.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.666,"s":[{"i":[[0,-9.113],[0,0],[3.317,-0.046],[0.422,11.747],[0,0],[-5.316,0]],"o":[[0,0],[0,9.113],[-5.691,0.094],[0,0],[0.047,-11.466],[3.318,0]],"v":[[-786.125,-101.562],[-786.125,81.781],[-792.133,94.531],[-798.141,81.781],[-798.141,-101.562],[-792.133,-114.312]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":102.5,"s":[{"i":[[0,-9.113],[0,0],[3.054,-0.061],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-6.219,0.125],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-791.75,-101.25],[-791.75,80.375],[-797.281,93.125],[-802.812,80.375],[-802.812,-101.25],[-797.281,-114]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[0,-9.113],[0,0],[3.054,-0.07],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-6.51,0.153],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-796.934,-100.556],[-796.934,78.292],[-802.465,91.042],[-807.997,78.292],[-807.997,-100.556],[-802.465,-113.306]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.834,"s":[{"i":[[0,-9.113],[0,0],[3.054,-0.079],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-6.802,0.181],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-799.618,-99.861],[-799.618,76.208],[-805.149,88.958],[-810.681,76.208],[-810.681,-99.861],[-805.149,-112.611]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":108.334,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.092],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.24,0.222],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-800.61,-98.819],[-800.61,73.083],[-806.141,85.833],[-811.672,73.083],[-811.672,-98.819],[-806.141,-111.569]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-799.188,-98.125],[-799.188,71],[-804.719,83.75],[-814.636,70.875],[-814.636,-98.25],[-804.719,-110.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-787.944,-96.125],[-787.944,66.75],[-793.475,79.5],[-804.019,66.875],[-804.019,-96],[-793.475,-108.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-780.109,-96.579],[-780.109,65.77],[-785.64,78.355],[-796.184,65.566],[-796.184,-96.783],[-785.64,-108.836]],"c":true}]},{"t":128,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-779.207,-96.704],[-779.207,65.118],[-784.738,77.54],[-794.029,65.112],[-794.029,-96.71],[-784.738,-108.467]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[53.5,61],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":60,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"CENTER_LENS","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.013,149.919,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,-5.969],[5.969,0],[0,0],[0,5.969],[-5.969,0],[0,0]],"o":[[0,5.969],[0,0],[-5.969,0],[0,-5.969],[0,0],[5.969,0]],"v":[[21.408,0],[10.601,10.808],[-10.601,10.808],[-21.408,0],[-10.601,-10.808],[10.601,-10.808]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":62.5,"s":[{"i":[[-0.162,-5.954],[5.954,-0.162],[0,0],[0.162,5.954],[-5.954,0.162],[0,0]],"o":[[0.162,5.954],[0,0],[-5.954,0.162],[-0.162,-5.954],[0,0],[5.954,-0.162]],"v":[[23.471,-4.918],[12.899,6.54],[-8.812,6.806],[-19.793,-3.944],[-9.024,-15.178],[12.5,-15.603]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":64.166,"s":[{"i":[[-0.27,-5.944],[5.944,-0.27],[0,0],[0.27,5.944],[-5.944,0.27],[0,0]],"o":[[0.27,5.944],[0,0],[-5.944,0.27],[-0.27,-5.944],[0,0],[5.944,-0.27]],"v":[[24.429,-9.031],[14.014,2.862],[-8.037,3.305],[-19.132,-7.407],[-8.389,-18.924],[13.349,-19.633]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":66.666,"s":[{"i":[[-0.432,-5.929],[5.929,-0.432],[0,0],[0.432,5.929],[-5.929,0.432],[0,0]],"o":[[0.432,5.929],[0,0],[-5.929,0.432],[-0.432,-5.929],[0,0],[5.929,-0.432]],"v":[[24.667,-16.787],[14.488,-4.243],[-8.074,-3.534],[-19.342,-14.188],[-8.638,-26.132],[13.424,-27.266]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[-0.54,-5.92],[5.92,-0.54],[0,0],[0.54,5.92],[-5.92,0.54],[0,0]],"o":[[0.54,5.92],[0,0],[-5.92,0.54],[-0.54,-5.92],[0,0],[5.92,-0.54]],"v":[[23.575,-22.686],[13.553,-9.709],[-9.348,-8.823],[-20.732,-19.439],[-10.053,-31.666],[12.223,-33.083]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[-0.648,-5.91],[5.91,-0.648],[0,0],[0.648,5.91],[-5.91,0.648],[0,0]],"o":[[0.648,5.91],[0,0],[-5.91,0.648],[-0.648,-5.91],[0,0],[5.91,-0.648]],"v":[[20.259,-30.049],[10.394,-16.638],[-13.048,-15.324],[-24.296,-25.801],[-13.644,-38.312],[9.173,-40.114]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":71.666,"s":[{"i":[[-0.756,-5.9],[5.9,-0.756],[0,0],[0.756,5.9],[-5.9,0.756],[0,0]],"o":[[0.756,5.9],[0,0],[-5.9,0.756],[-0.756,-5.9],[0,0],[5.9,-0.756]],"v":[[14.171,-38.307],[4.463,-24.462],[-19.518,-22.721],[-30.632,-33.06],[-20.005,-45.855],[3.351,-48.04]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":72.5,"s":[{"i":[[-0.81,-5.895],[5.895,-0.81],[0,0],[0.81,5.895],[-5.895,0.81],[0,0]],"o":[[0.81,5.895],[0,0],[-5.895,0.81],[-0.81,-5.895],[0,0],[5.895,-0.81]],"v":[[9.346,-43.279],[-0.283,-29.218],[-24.535,-27.263],[-35.581,-37.533],[-24.967,-50.47],[-1.34,-52.846]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73.334,"s":[{"i":[[-0.864,-5.89],[5.89,-0.864],[0,0],[0.864,5.89],[-5.89,0.864],[0,0]],"o":[[0.864,5.89],[0,0],[-5.89,0.864],[-0.864,-5.89],[0,0],[5.89,-0.864]],"v":[[3.001,-48.298],[-6.55,-34.019],[-30.497,-31.851],[-41.476,-42.052],[-30.875,-55.131],[-7.553,-57.699]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74.166,"s":[{"i":[[-0.918,-5.885],[5.885,-0.918],[0,0],[0.918,5.885],[-5.885,0.918],[0,0]],"o":[[0.918,5.885],[0,0],[-5.885,0.918],[-0.918,-5.885],[0,0],[5.885,-0.918]],"v":[[-4.97,-53.817],[-14.443,-39.321],[-38.084,-36.939],[-48.996,-47.071],[-38.408,-60.292],[-15.391,-63.052]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[-0.972,-5.88],[5.88,-0.972],[0,0],[0.972,5.88],[-5.88,0.972],[0,0]],"o":[[0.972,5.88],[0,0],[-5.88,0.972],[-0.972,-5.88],[0,0],[5.88,-0.972]],"v":[[-14.941,-59.46],[-24.335,-44.748],[-47.671,-42.152],[-58.515,-52.215],[-47.94,-65.577],[-25.229,-68.529]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[-1.08,-5.87],[5.87,-1.08],[0,0],[1.08,5.87],[-5.87,1.08],[0,0]],"o":[[1.08,5.87],[0,0],[-5.87,1.08],[-1.08,-5.87],[0,0],[5.87,-1.08]],"v":[[-42.382,-72.123],[-51.619,-56.976],[-74.346,-53.953],[-85.055,-63.877],[-74.506,-77.524],[-52.404,-80.859]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":77.5,"s":[{"i":[[-0.856,-5.865],[5.839,-1.205],[0,0],[0.985,5.907],[-5.839,1.205],[0,0]],"o":[[0.855,5.886],[0,0],[-5.839,1.205],[-0.983,-5.883],[0,0],[5.839,-1.205]],"v":[[-61.785,-78.763],[-70.703,-63.449],[-91.979,-60.128],[-102.22,-70.048],[-92,-83.986],[-71.252,-87.688]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78.334,"s":[{"i":[[-0.632,-5.86],[5.807,-1.33],[0,0],[0.89,5.944],[-5.807,1.33],[0,0]],"o":[[0.63,5.901],[0,0],[-5.807,1.33],[-0.886,-5.896],[0,0],[5.807,-1.33]],"v":[[-84.656,-85.779],[-93.256,-70.297],[-113.08,-66.679],[-122.854,-76.593],[-112.964,-90.823],[-93.568,-94.893]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79.166,"s":[{"i":[[-0.408,-5.856],[5.776,-1.455],[0,0],[0.796,5.981],[-5.776,1.455],[0,0]],"o":[[0.406,5.917],[0,0],[-5.776,1.455],[-0.79,-5.909],[0,0],[5.776,-1.455]],"v":[[-112.027,-92.795],[-120.308,-77.146],[-138.681,-73.229],[-147.987,-83.138],[-138.427,-97.659],[-120.384,-102.097]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[-0.184,-5.851],[5.744,-1.58],[0,0],[0.701,6.017],[-5.744,1.58],[0,0]],"o":[[0.181,5.932],[0,0],[-5.744,1.58],[-0.693,-5.922],[0,0],[5.744,-1.58]],"v":[[-144.7,-99.873],[-152.663,-84.056],[-169.585,-79.842],[-178.423,-89.745],[-169.193,-104.558],[-152.502,-109.364]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80.834,"s":[{"i":[[0.039,-5.846],[5.713,-1.705],[0,0],[0.606,6.054],[-5.713,1.705],[0,0]],"o":[[-0.044,5.947],[0,0],[-5.713,1.705],[-0.596,-5.935],[0,0],[5.713,-1.705]],"v":[[-181.936,-106.857],[-189.58,-90.873],[-205.051,-86.361],[-213.421,-96.259],[-204.521,-111.364],[-189.183,-116.536]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0.263,-5.841],[5.681,-1.83],[0,0],[0.511,6.091],[-5.681,1.83],[0,0]],"o":[[-0.269,5.963],[0,0],[-5.681,1.83],[-0.499,-5.948],[0,0],[5.682,-1.83]],"v":[[-225.421,-113.467],[-232.747,-97.315],[-246.767,-92.505],[-254.67,-102.398],[-246.098,-117.794],[-232.114,-123.334]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82.5,"s":[{"i":[[0.201,-5.842],[4.849,-1.973],[0,0],[0.428,5.773],[-4.511,1.767],[0,0]],"o":[[-0.205,5.947],[0,0],[-4.849,1.973],[-0.429,-5.935],[0,0],[4.849,-1.973]],"v":[[-275.671,-118.903],[-281.694,-102.971],[-292.741,-98.418],[-299.149,-108.831],[-292.426,-123.38],[-281.329,-128.511]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[0.138,-5.843],[4.017,-2.117],[0,0],[0.345,5.456],[-3.34,1.705],[0,0]],"o":[[-0.141,5.931],[0,0],[-4.017,2.117],[-0.359,-5.922],[0,0],[4.017,-2.117]],"v":[[-331.129,-123.089],[-335.849,-107.376],[-343.923,-103.082],[-348.837,-114.013],[-343.962,-127.716],[-335.753,-132.438]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[0.075,-5.844],[3.184,-2.261],[0,0],[0.262,5.138],[-2.169,1.643],[0,0]],"o":[[-0.077,5.915],[0,0],[-3.184,2.261],[-0.289,-5.909],[0,0],[4.042,-3.442]],"v":[[-389.712,-125.963],[-393.129,-110.469],[-398.605,-106.433],[-402.024,-117.883],[-398.998,-130.74],[-393.301,-136.302]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85,"s":[{"i":[[0.012,-5.845],[2.352,-2.405],[0,0],[0.179,4.821],[-0.999,1.58],[0,0]],"o":[[-0.012,5.899],[0,0],[-2.352,2.405],[-0.219,-5.895],[0,0],[4.066,-4.767]],"v":[[-451.421,-127.712],[-453.534,-112.437],[-455.663,-108.66],[-457.587,-120.628],[-456.41,-132.638],[-453.975,-136.541]],"c":true}]},{"t":85.833984375,"s":[{"i":[[0.012,-5.845],[0.626,-2.62],[0,0],[0.179,4.821],[-0.999,1.58],[0,0]],"o":[[-0.012,5.899],[0,0],[-1.245,-2.898],[-0.219,-5.895],[0,0],[0.566,-0.017]],"v":[[-512.671,-125.712],[-513.534,-115.437],[-514.163,-115.66],[-514.337,-122.878],[-513.66,-133.888],[-512.975,-134.041]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[509.408,-610.192],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":87,"st":60,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"FRONT_LENS 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[-1.125,-0.688],[0,-10.77],[3.375,0.312],[0,10.77]],"o":[[2.027,1.239],[0,10.77],[-1.122,-0.104],[0,-10.77]],"v":[[-333.875,52.938],[-332.25,68.688],[-333.875,88.188],[-335.5,68.688]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88.334,"s":[{"i":[[-1.661,-0.597],[0,-10.77],[3.518,0.277],[0,10.77]],"o":[[2.541,1.071],[0,10.77],[-1.622,-0.098],[0,-10.77]],"v":[[-304.741,51.121],[-301.683,67.085],[-304.277,86.335],[-307.339,67.121]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89.166,"s":[{"i":[[-2.197,-0.507],[0,-10.77],[3.661,0.241],[0,10.77]],"o":[[3.055,0.903],[0,10.77],[-2.123,-0.093],[0,-10.77]],"v":[[-277.357,49.304],[-272.866,65.482],[-276.429,84.482],[-280.929,65.554]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90.834,"s":[{"i":[[-3.27,-0.326],[0,-10.77],[3.946,0.17],[0,10.77]],"o":[[4.083,0.567],[0,10.77],[-3.124,-0.082],[0,-10.77]],"v":[[-227.089,45.67],[-219.732,62.277],[-225.232,80.777],[-232.607,62.42]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91.666,"s":[{"i":[[-3.806,-0.235],[0,-10.77],[4.089,0.134],[0,10.77]],"o":[[4.597,0.399],[0,10.77],[-3.624,-0.077],[0,-10.77]],"v":[[-205.132,43.467],[-196.561,60.289],[-202.811,78.539],[-211.478,60.467]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.5,"s":[{"i":[[-4.342,-0.145],[0,-10.77],[4.232,0.098],[0,10.77]],"o":[[5.111,0.231],[0,10.77],[-4.125,-0.071],[0,-10.77]],"v":[[-185.301,41.265],[-175.515,58.301],[-182.515,76.301],[-192.473,58.515]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[-4.878,-0.054],[0,-10.77],[4.375,0.062],[0,10.77]],"o":[[5.625,0.062],[0,10.77],[-4.625,-0.066],[0,-10.77]],"v":[[-167.125,38.688],[-156.125,55.938],[-163.875,73.688],[-175.125,56.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94.166,"s":[{"i":[[-5.277,0],[-0.118,-10.643],[4.875,0.012],[-0.062,10.649]],"o":[[5.85,0.012],[0.088,10.399],[-5.238,-0.009],[0.07,-10.767]],"v":[[-150.531,36.3],[-139.006,53.575],[-147.306,71.35],[-159.156,53.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[-5.675,0.055],[-0.236,-10.516],[5.375,-0.037],[-0.125,10.528]],"o":[[6.075,-0.038],[0.175,10.028],[-5.852,0.048],[0.14,-10.765]],"v":[[-135.188,33.788],[-123.137,51.088],[-131.988,68.887],[-144.438,51.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95.834,"s":[{"i":[[-6.073,0.11],[-0.353,-10.39],[5.875,-0.087],[-0.188,10.407]],"o":[[6.3,-0.087],[0.262,9.657],[-6.465,0.104],[0.211,-10.763]],"v":[[-121.771,31.306],[-109.196,48.631],[-118.596,66.456],[-131.646,48.656]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96.666,"s":[{"i":[[-6.472,0.164],[-0.471,-10.263],[6.375,-0.138],[-0.25,10.287]],"o":[[6.525,-0.137],[0.35,9.287],[-7.078,0.161],[0.281,-10.76]],"v":[[-109.104,28.825],[-96.004,46.175],[-105.954,64.025],[-119.604,46.125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[-6.87,0.219],[-0.589,-10.137],[6.875,-0.188],[-0.312,10.166]],"o":[[6.75,-0.188],[0.438,8.916],[-7.691,0.218],[0.351,-10.758]],"v":[[-97.75,26.438],[-84.125,43.812],[-94.625,61.688],[-108.875,43.688]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98.334,"s":[{"i":[[-7.268,0.274],[-0.706,-10.01],[7.375,-0.238],[-0.375,10.045]],"o":[[6.975,-0.238],[0.525,8.545],[-8.305,0.275],[0.421,-10.756]],"v":[[-87.767,24.188],[-73.617,41.588],[-84.667,59.488],[-99.517,41.387]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[-8.065,0.383],[-0.942,-9.757],[8.375,-0.338],[-0.5,9.804]],"o":[[7.425,-0.337],[0.7,7.804],[-9.531,0.388],[0.562,-10.751]],"v":[[-70.425,19.688],[-55.225,37.137],[-67.375,55.088],[-83.425,36.788]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.666,"s":[{"i":[[-8.862,0.492],[-1.177,-9.504],[9.375,-0.438],[-0.625,9.562]],"o":[[7.875,-0.438],[0.875,7.062],[-10.758,0.502],[0.702,-10.747]],"v":[[-56.375,15.188],[-40.125,32.688],[-53.375,50.688],[-70.625,32.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103.334,"s":[{"i":[[-9.071,0.394],[-0.942,-9.584],[9.481,-0.35],[-0.5,9.631]],"o":[[8.281,-0.35],[0.7,7.631],[-10.588,0.402],[0.562,-10.579]],"v":[[-45.213,11.238],[-28.625,28.887],[-42.775,47.012],[-60.2,28.488]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[{"i":[[-9.28,0.295],[-0.706,-9.665],[9.588,-0.262],[-0.375,9.7]],"o":[[8.688,-0.262],[0.525,8.2],[-10.417,0.301],[0.421,-10.411]],"v":[[-36.05,7.496],[-19.125,25.296],[-34.175,43.546],[-51.775,24.996]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.834,"s":[{"i":[[-9.384,0.246],[-0.589,-9.705],[9.641,-0.219],[-0.312,9.735]],"o":[[8.891,-0.219],[0.438,8.485],[-10.332,0.251],[0.351,-10.327]],"v":[[-31.875,5.625],[-14.781,23.5],[-30.281,41.812],[-47.969,23.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[-9.593,0.148],[-0.353,-9.786],[9.747,-0.131],[-0.188,9.803]],"o":[[9.297,-0.131],[0.262,9.053],[-10.162,0.151],[0.211,-10.159]],"v":[[-25.337,2.55],[-7.906,20.575],[-24.306,39.012],[-42.169,20.425]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[-9.907,0],[0,-9.907],[9.907,0],[0,9.907]],"o":[[9.907,0],[0,9.907],[-9.907,0],[0,-9.907]],"v":[[-17.125,-2.062],[0.812,16.188],[-16.938,34.812],[-35.062,16.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":114.166,"s":[{"i":[[-10.77,0],[0,-10.77],[10.77,0],[0,10.77]],"o":[[10.77,0],[0,10.77],[-10.77,0],[0,-10.77]],"v":[[-8.781,-9.156],[10.719,10.344],[-8.781,29.844],[-28.281,10.344]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118.334,"s":[{"i":[[-10.77,0],[0,-10.77],[10.77,0],[0,10.77]],"o":[[10.77,0],[0,10.77],[-10.77,0],[0,-10.77]],"v":[[-4.188,-13.375],[15.312,6.125],[-4.188,25.625],[-23.688,6.125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":122.5,"s":[{"i":[[-10.77,0],[0,-10.77],[10.77,0],[0,10.77]],"o":[[10.77,0],[0,10.77],[-10.77,0],[0,-10.77]],"v":[[-1.562,-16.438],[17.938,3.062],[-1.562,22.562],[-21.062,3.062]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126.666,"s":[{"i":[[-10.77,0],[0,-10.77],[10.77,0],[0,10.77]],"o":[[10.77,0],[0,10.77],[-10.77,0],[0,-10.77]],"v":[[-0.188,-18.25],[19.312,1.25],[-0.188,20.75],[-19.688,1.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130.834,"s":[{"i":[[-10.77,0],[0,-10.77],[10.77,0],[0,10.77]],"o":[[10.77,0],[0,10.77],[-10.77,0],[0,-10.77]],"v":[[0.5,-19.469],[20,0.031],[0.5,19.531],[-19,0.031]],"c":true}]},{"t":135,"s":[{"i":[[-10.77,0],[0,-10.77],[10.77,0],[0,10.77]],"o":[[10.77,0],[0,10.77],[-10.77,0],[0,-10.77]],"v":[[0.688,-20.062],[20.188,-0.562],[0.688,18.938],[-18.812,-0.562]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[385.5,-561.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":87,"op":342,"st":60,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"CENTER_SCREEN 3","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[699,-539],[699,539],[653,585],[-0.999,585],[-653,585],[-699,539],[-699,-539],[-653,-585],[1,-585],[653,-585]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":64.166,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[707,-547],[707,547],[661,593],[-0.999,585],[-639.273,577],[-685.273,531],[-685.273,-527],[-639.273,-573],[1,-585],[661,-593]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[715,-563],[715,567],[669,613],[-0.999,585],[-617.545,561],[-663.545,515],[-663.545,-511],[-617.545,-557],[1,-585],[669,-609]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70.834,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[709,-577.4],[709,579],[663,625],[-0.999,585],[-587.709,551.4],[-636.709,505.4],[-636.709,-501.4],[-587.709,-547.4],[1,-585],[663,-623.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":72.5,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[705,-587],[705,587],[659,633],[-0.999,585],[-567.818,545],[-613.818,499],[-613.818,-495],[-567.818,-541],[1,-585],[659,-633]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73.334,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[697.4,-593.4],[697.4,595],[651.4,641],[-0.999,585],[-553.873,539.4],[-599.873,493.4],[-599.873,-490.2],[-553.873,-536.2],[1,-585],[651.4,-639.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74.166,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[693.8,-599.8],[693.8,603],[647.8,649],[-0.999,585],[-537.927,533.8],[-583.927,487.8],[-583.927,-485.4],[-537.927,-531.4],[1,-585],[647.8,-645.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[678.2,-606.2],[678.2,611],[632.2,657],[-0.999,585],[-519.982,528.2],[-565.982,482.2],[-565.982,-480.6],[-519.982,-526.6],[1,-585],[632.2,-652.2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75.834,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[666.6,-612.6],[666.6,619],[620.6,665],[-0.999,585],[-499.036,522.6],[-545.036,476.6],[-545.036,-475.8],[-499.036,-521.8],[1,-585],[620.6,-658.6]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[647,-619],[647,627],[601,673],[-0.999,585],[-478.091,517],[-524.091,471],[-524.091,-471],[-478.091,-517],[1,-585],[601,-665]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78.334,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.223,2.5],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-22.441,-1.8],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[600.891,-638.2],[600.891,649.545],[561.618,691.182],[-5.908,586.273],[-428.856,507],[-467.174,466.273],[-467.174,-463],[-420.674,-508.5],[1,-585.6],[554.891,-684.2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79.166,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.132,3.75],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-20.96,-2.7],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[561.836,-647.8],[561.836,660.818],[525.927,700.273],[-8.363,586.909],[-398.739,502],[-433.216,463.909],[-433.216,-459],[-386.466,-504.25],[1,-585.9],[515.836,-693.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.041,5],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-19.478,-3.6],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[522.782,-657.4],[522.782,672.091],[490.236,709.364],[-10.817,587.546],[-368.621,497],[-399.258,461.546],[-399.258,-455],[-352.258,-500],[1,-586.2],[476.782,-703.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80.834,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-24.95,6.25],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-17.996,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[471.727,-667],[471.727,683.364],[442.545,718.455],[-13.272,588.182],[-332.504,492],[-359.3,459.182],[-359.3,-451],[-312.05,-495.75],[1,-586.5],[425.727,-713]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0.129,-24.588],[0,0],[21.663,1.508],[0,0],[0,0],[0.008,23.224],[0,0],[-21.2,5.875],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-15.329,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[21.8,-2.083]],"v":[[412.352,-679.333],[412.352,694.197],[387.295,726.538],[-16.188,588.015],[-289.754,488.25],[-312.675,457.182],[-312.675,-449],[-272.55,-491.375],[-4.583,-587],[372.894,-720.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82.5,"s":[{"i":[[0.258,-23.77],[0,0],[17.922,3.015],[0,0],[0,0],[0.017,21.043],[0,0],[-17.45,5.5],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-12.663,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[18.194,-4.167]],"v":[[344.977,-691.667],[344.977,705.03],[324.045,734.621],[-19.105,587.849],[-247.004,484.5],[-266.05,455.182],[-266.05,-447],[-233.05,-487],[-10.166,-587.5],[312.06,-728.333]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[0.386,-22.953],[0,0],[14.18,4.523],[0,0],[0,0],[0.025,18.862],[0,0],[-13.7,5.125],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-9.996,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[14.589,-6.25]],"v":[[269.602,-704],[269.602,715.864],[252.795,742.705],[-22.022,587.682],[-204.254,480.75],[-219.425,453.182],[-219.425,-445],[-193.55,-482.625],[-15.75,-588],[243.227,-736]],"c":true}]},{"t":85.833984375,"s":[{"i":[[0.773,-20.5],[0,0],[2.955,9.045],[0,0],[0,0],[0.05,12.318],[0,0],[-2.45,4],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-1.996,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[3.773,-12.5]],"v":[[13.477,-707],[13.477,713.364],[9.045,731.955],[-30.772,587.182],[-58.004,481.5],[-61.55,459.182],[-61.55,-451],[-57.05,-481.5],[-32.5,-581.5],[6.727,-725]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":87,"st":60,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Figure 10","tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":72.5,"s":[100]},{"t":80.833984375,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[206.959,124.54,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[206.959,124.54,0],"to":[-1.125,0,0],"ti":[1.125,0,0]},{"t":85.833984375,"s":[200.209,124.54,0]}],"ix":2,"l":2},"a":{"a":0,"k":[270.209,145.54,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.55,0.55,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,-0.47]},"t":60,"s":[125,125,100]},{"t":110,"s":[125,125,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,0],[-1.939,-1.387],[6.061,-1.3],[0.907,1.308]],"o":[[1.066,0.711],[2.423,1.734],[-4.85,1.04],[0,0]],"v":[[-0.582,-6.237],[5.035,-2.37],[2.01,5.979],[-6.77,3.354]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[0,0],[-1.877,-1.382],[5.87,-1.295],[0.878,1.303]],"o":[[1.032,0.708],[2.346,1.727],[-4.697,1.036],[0,0]],"v":[[-0.131,-5.771],[5.309,-1.919],[2.379,6.397],[-6.124,3.782]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,0],[-1.627,-1.365],[5.086,-1.278],[0.761,1.287]],"o":[[0.894,0.699],[2.033,1.706],[-4.07,1.023],[0,0]],"v":[[1.024,-4.856],[5.738,-1.053],[3.2,7.16],[-4.168,4.578]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0,0],[-0.79,-1.365],[2.613,-0.951],[0.598,1.216]],"o":[[0.434,0.699],[0.987,1.706],[-1.617,0.588],[0,0]],"v":[[-0.98,-5.389],[1.308,-1.586],[-0.339,6.331],[-4.212,3.985]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[0,0],[-0.387,-1.365],[1.28,-0.951],[0.293,1.216]],"o":[[0.213,0.699],[0.483,1.706],[-0.792,0.588],[0,0]],"v":[[-1.492,-5.389],[-0.371,-1.586],[-1.178,6.331],[-3.075,3.985]],"c":false}]},{"t":85.833984375,"s":[{"i":[[0,0],[-0.123,-1.365],[0.408,-0.951],[0.093,1.216]],"o":[[0.068,0.699],[0.154,1.706],[-0.253,0.588],[0,0]],"v":[[-2,-5.389],[-1.642,-1.586],[-1.9,6.331],[-2.505,3.985]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.596078455448,0.321568638086,0.239215686917,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.12,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[272.026,152.086],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Nose","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[-3.926,7.277],[-8.848,0],[-3.783,2.669],[18.309,-0.059],[0.81,-15.17],[-4.916,0]],"o":[[3.926,7.277],[4.982,0],[-0.845,-15.213],[-18.267,0.059],[3.755,2.604],[8.848,0]],"v":[[-0.106,3.616],[20.359,15.839],[33.729,11.589],[-0.12,-15.891],[-33.793,11.7],[-20.571,15.839]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[-3.803,7.248],[-8.57,0],[-3.664,2.658],[17.733,-0.059],[0.785,-15.11],[-4.761,0]],"o":[[3.803,7.248],[4.825,0],[-0.819,-15.153],[-17.692,0.058],[3.637,2.594],[8.57,0]],"v":[[0.379,4.215],[20.2,16.39],[33.149,12.156],[0.365,-15.216],[-32.248,12.267],[-19.442,16.39]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[-3.295,7.158],[-7.426,0],[-3.175,2.625],[15.365,-0.058],[0.472,-17.15],[-4.125,0]],"o":[[3.295,7.158],[4.181,0],[-0.823,-14.189],[-15.33,0.058],[3.151,2.562],[7.426,0]],"v":[[-0.465,5.074],[17.661,15.91],[29.594,11.253],[1.662,-15.066],[-26.597,13.501],[-16.213,17.098]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[-1.951,7.158],[-4.397,0],[-1.88,2.625],[9.099,-0.058],[0.279,-17.15],[-2.443,0]],"o":[[1.951,7.158],[2.476,0],[-0.487,-14.189],[-9.078,0.058],[1.866,2.562],[4.397,0]],"v":[[-0.259,5.074],[10.475,15.91],[17.542,11.253],[1.001,-15.066],[-15.734,13.501],[-9.585,17.098]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[-0.956,7.158],[-2.154,0],[-0.921,2.625],[4.456,-0.058],[0.137,-17.15],[-1.196,0]],"o":[[0.956,7.158],[1.213,0],[-0.239,-14.189],[-4.446,0.058],[0.914,2.562],[2.154,0]],"v":[[-0.818,5.074],[4.203,15.91],[8.137,11.253],[0.036,-15.066],[-7.924,13.501],[-5.622,16.388]],"c":true}]},{"t":85.833984375,"s":[{"i":[[-0.305,7.158],[-0.687,0],[-0.294,2.625],[1.422,-0.058],[0.044,-17.15],[-0.382,0]],"o":[[0.305,7.158],[0.387,0],[-0.076,-14.189],[-1.419,0.058],[0.292,2.562],[0.687,0]],"v":[[-0.725,5.074],[0.877,15.91],[2.132,11.253],[-0.453,-15.066],[-2.992,13.501],[-2.258,16.388]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,9.805],[9.805,0],[0,-9.805],[-9.805,0]],"o":[[0,-9.805],[-9.805,0],[0,9.805],[9.805,0]],"v":[[-6.59,-22.526],[-24.345,-40.281],[-42.099,-22.526],[-24.345,-4.772]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[0,9.767],[9.497,0],[0,-9.767],[-9.497,0]],"o":[[0,-9.767],[-9.497,0],[0,9.767],[9.497,0]],"v":[[-5.901,-21.825],[-23.097,-39.51],[-40.292,-21.825],[-23.097,-4.14]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,9.646],[7.896,0],[0,-9.646],[-7.896,0]],"o":[[0,-9.646],[-7.896,0],[0,9.646],[7.896,0]],"v":[[-4.974,-20.167],[-18.558,-37.632],[-33.567,-20.167],[-19.984,-2.94]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0,9.646],[4.638,0],[0,-9.646],[-4.639,0]],"o":[[0,-9.646],[-4.638,0],[0,9.646],[4.639,0]],"v":[[-2.828,-18.982],[-10.807,-36.448],[-19.625,-18.982],[-11.645,-1.755]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[0,9.435],[2.13,0],[0,-9.435],[-2.131,0]],"o":[[0,-9.435],[-2.13,0],[0,9.435],[2.131,0]],"v":[[-2.351,-18.418],[-6.016,-35.501],[-10.066,-18.418],[-6.401,-1.568]],"c":true}]},{"t":85.833984375,"s":[{"i":[[0,9.435],[0.68,0],[0,-9.435],[-0.68,0]],"o":[[0,-9.435],[-0.68,0],[0,9.435],[0.68,0]],"v":[[-1.214,-18.418],[-2.384,-35.501],[-3.676,-18.418],[-2.506,-1.568]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.337254911661,0.23137255013,0.129411771894,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[270.47,108.276],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Hair","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[-7.446,13.962],[0,0],[0,0],[-0.313,2.62],[0.015,4.351],[0,0],[-18.83,0.06],[-0.051,-15.806],[0,0],[-0.49,-2.669],[-1.873,-4.352],[-0.89,-3.988],[0,-4.307],[8.014,-5.508],[12.335,-0.04],[8.035,5.309],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.222,-4.479],[0.309,-2.598],[0,0],[-0.051,-15.806],[18.83,-0.06],[0,0],[0.013,4.276],[0.495,2.702],[3.604,8.387],[0.884,3.962],[0,7.459],[-7.997,5.495],[-12.35,0.04],[-8.04,-5.313],[0,0],[0,-6.072]],"v":[[-36.861,-4.598],[-36.804,-4.703],[-36.772,-4.819],[-34.61,-14.535],[-34.324,-24.06],[-34.324,-24.128],[-0.614,-53.129],[33.278,-24.345],[33.278,-24.252],[33.789,-14.641],[37.134,-4.922],[43.41,12.489],[44.307,23.718],[31.53,43.997],[0.092,53.129],[-31.449,44.589],[-44.307,24.009],[-44.307,23.977]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[-7.211,13.907],[0,0],[0,0],[-0.301,2.61],[0.014,4.334],[0,0],[-18.237,0.06],[-0.049,-15.744],[0,0],[-0.473,-2.659],[-1.812,-4.336],[-0.861,-3.972],[0,-4.29],[7.763,-5.486],[11.947,-0.04],[7.782,5.289],[0,7.963],[0,0]],"o":[[0,0],[0,0],[1.183,-4.461],[0.299,-2.588],[0,0],[-0.049,-15.745],[18.237,-0.06],[0,0],[0.012,4.259],[0.479,2.691],[3.491,8.354],[0.856,3.947],[0,7.43],[-7.746,5.474],[-11.961,0.04],[-7.787,-5.292],[0,0],[0,-6.048]],"v":[[-35.236,-4.113],[-35.18,-4.218],[-35.15,-4.333],[-33.055,-14.011],[-32.779,-23.499],[-32.779,-23.567],[-0.13,-52.454],[32.696,-23.783],[32.696,-23.69],[33.191,-14.117],[36.43,-4.435],[42.509,12.908],[43.377,24.093],[31.002,44.292],[0.554,53.389],[-29.994,44.882],[-42.447,24.383],[-42.447,24.35]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[-6.248,13.734],[0,0],[0,0],[-0.261,2.577],[0.012,4.28],[0,0],[-15.802,0.059],[-0.043,-15.549],[0,0],[-0.41,-2.626],[-1.57,-4.282],[-0.746,-3.923],[0,-4.237],[6.726,-5.417],[10.352,-0.039],[6.743,5.223],[0,7.864],[0,0]],"o":[[0,0],[0,0],[1.025,-4.406],[0.259,-2.556],[0,0],[-0.237,-16.662],[15.802,-0.059],[0,0],[0.011,4.206],[0.415,2.658],[3.025,8.25],[0.742,3.898],[0,7.337],[-6.712,5.406],[-10.364,0.039],[-6.747,-5.226],[0,0],[0,-5.973]],"v":[[-29.256,-3.138],[-29.208,-3.242],[-29.181,-3.355],[-27.366,-12.913],[-27.127,-22.283],[-27.127,-22.35],[1.163,-52.304],[29.606,-22.564],[29.606,-22.471],[30.035,-13.018],[32.842,-3.456],[38.109,13.671],[38.861,24.717],[28.139,44.665],[2.706,53.173],[-23.764,44.772],[-34.554,24.528],[-34.554,24.496]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[-3.7,13.734],[0,0],[0,0],[-0.155,2.577],[0.007,4.28],[0,0],[-9.358,0.059],[-0.025,-15.549],[0,0],[-0.243,-2.626],[-0.93,-4.282],[-0.442,-3.923],[0,-4.237],[3.983,-5.417],[6.13,-0.039],[3.993,5.223],[0,7.864],[0,0]],"o":[[0,0],[0,0],[0.607,-4.406],[0.153,-2.556],[0,0],[-0.14,-16.662],[9.358,-0.059],[0,0],[0.006,4.206],[0.246,2.658],[1.791,8.25],[0.439,3.898],[0,7.337],[-3.975,5.406],[-6.138,0.039],[-3.996,-5.226],[0,0],[0,-5.973]],"v":[[-17.523,-3.138],[-17.495,-3.242],[-17.479,-3.355],[-16.405,-12.913],[-16.263,-22.283],[-16.263,-22.35],[0.491,-52.304],[17.334,-22.564],[17.334,-22.471],[17.588,-13.018],[19.251,-3.456],[22.37,13.671],[22.815,24.717],[16.465,44.665],[1.404,53.173],[-14.271,44.772],[-20.661,24.528],[-20.661,24.496]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[-1.812,13.734],[0,0],[0,0],[-0.076,2.577],[0.004,4.28],[0,0],[-4.583,0.059],[-0.012,-15.549],[0,0],[-0.119,-2.626],[-0.455,-4.282],[-0.216,-3.923],[0,-4.237],[1.951,-5.417],[3.002,-0.039],[1.956,5.223],[0,7.864],[0,0]],"o":[[0,0],[0,0],[0.297,-4.406],[0.075,-2.556],[0,0],[-0.069,-16.662],[4.583,-0.059],[0,0],[0.003,4.206],[0.12,2.658],[0.877,8.25],[0.215,3.898],[0,7.337],[-1.947,5.406],[-3.006,0.039],[-1.957,-5.226],[0,0],[0,-5.973]],"v":[[-9.069,-3.138],[-9.055,-3.242],[-9.047,-3.355],[-8.521,-12.913],[-8.451,-22.283],[-8.451,-22.35],[-0.482,-52.304],[8.004,-22.564],[8.004,-22.471],[8.128,-13.018],[8.469,-3.22],[9.997,13.434],[10.215,24.48],[7.578,44.665],[0.202,53.173],[-7.476,44.772],[-10.605,24.528],[-10.605,24.496]],"c":true}]},{"t":85.833984375,"s":[{"i":[[-0.578,13.734],[0,0],[0,0],[-0.024,2.577],[0.001,4.28],[0,0],[-1.462,0.059],[-0.004,-15.549],[0,0],[-0.038,-2.626],[-0.145,-4.282],[-0.069,-3.923],[0,-4.237],[0.622,-5.417],[0.958,-0.039],[0.624,5.223],[0,7.864],[0,0]],"o":[[0,0],[0,0],[0.095,-4.406],[0.024,-2.556],[0,0],[-0.022,-16.662],[1.462,-0.059],[0,0],[0.001,4.206],[0.038,2.658],[0.28,8.25],[0.069,3.898],[0,7.337],[-0.621,5.406],[-0.959,0.039],[-0.624,-5.226],[0,0],[0,-5.973]],"v":[[-3.716,-3.138],[-3.711,-3.242],[-3.709,-3.355],[-3.541,-12.913],[-3.519,-22.283],[-3.519,-22.35],[-0.976,-52.304],[1.731,-22.564],[1.731,-22.471],[1.771,-13.018],[1.88,-3.22],[2.367,13.434],[2.437,24.48],[1.596,44.665],[-0.758,53.173],[-3.207,44.772],[-4.206,24.528],[-4.206,24.496]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[4.598,1.656],[-2.64,7.412],[-4.598,-1.656],[2.64,-7.412]],"o":[[-4.6,-1.657],[2.64,-7.412],[4.6,1.657],[-2.64,7.412]],"v":[[30.635,-0.09],[27.088,-16.51],[40.195,-26.931],[43.742,-10.511]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[4.453,1.649],[-2.557,7.383],[-4.453,-1.649],[2.557,-7.383]],"o":[[-4.455,-1.65],[2.557,-7.383],[4.455,1.65],[-2.557,7.383]],"v":[[30.136,0.377],[26.7,-15.979],[39.395,-26.359],[42.831,-10.003]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[3.958,1.629],[-2.273,7.291],[-3.958,-1.629],[2.273,-7.291]],"o":[[-3.96,-1.63],[2.273,-7.291],[3.96,1.63],[-2.273,7.291]],"v":[[26.809,-1.08],[23.755,-17.233],[35.038,-27.484],[38.092,-11.332]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[2.344,1.629],[-1.346,7.291],[-2.344,-1.629],[1.346,-7.291]],"o":[[-2.345,-1.63],[1.346,-7.291],[2.345,1.63],[-1.346,7.291]],"v":[[15.441,-1.791],[13.633,-17.943],[20.314,-28.195],[22.123,-12.042]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[1.148,1.629],[-0.659,7.291],[-1.148,-1.629],[0.659,-7.291]],"o":[[-1.149,-1.63],[0.659,-7.291],[1.148,1.63],[-0.659,7.291]],"v":[[7.076,-1.791],[6.191,-17.943],[9.463,-28.195],[10.349,-12.042]],"c":true}]},{"t":85.833984375,"s":[{"i":[[0.366,1.629],[-0.21,7.291],[-0.366,-1.629],[0.21,-7.291]],"o":[[-0.366,-1.63],[0.21,-7.291],[0.366,1.63],[-0.21,7.291]],"v":[[1.436,-1.791],[1.153,-17.943],[2.197,-28.195],[2.48,-12.042]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[4.598,-1.656],[2.64,7.412],[-4.598,1.656],[-2.64,-7.412]],"o":[[-4.6,1.657],[-2.64,-7.412],[4.6,-1.657],[2.64,7.412]],"v":[[-32.206,-0.025],[-45.315,-10.446],[-41.768,-26.866],[-28.659,-16.445]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[4.453,-1.65],[2.557,7.383],[-4.453,1.65],[-2.557,-7.383]],"o":[[-4.455,1.651],[-2.557,-7.383],[4.455,-1.651],[2.557,7.383]],"v":[[-30.727,0.442],[-43.424,-9.938],[-39.988,-26.294],[-27.292,-15.913]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[3.859,-1.629],[2.216,7.291],[-3.859,1.629],[-2.216,-7.291]],"o":[[-3.86,1.63],[-2.216,-7.291],[3.86,-1.63],[2.216,7.291]],"v":[[-25.349,1.36],[-36.351,-8.891],[-33.374,-25.043],[-22.372,-14.792]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[2.285,-1.629],[1.312,7.291],[-2.285,1.629],[-1.312,-7.291]],"o":[[-2.286,1.63],[-1.312,-7.291],[2.286,-1.63],[1.312,7.291]],"v":[[-14.973,2.071],[-21.488,-8.18],[-19.725,-24.332],[-13.21,-14.081]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[1.119,-1.629],[0.643,7.291],[-1.119,1.629],[-0.643,-7.291]],"o":[[-1.12,1.63],[-0.643,-7.291],[1.12,-1.63],[0.643,7.291]],"v":[[-7.819,2.071],[-11.01,-8.18],[-10.147,-24.332],[-6.956,-14.081]],"c":true}]},{"t":85.833984375,"s":[{"i":[[0.357,-1.629],[0.205,7.291],[-0.357,1.629],[-0.205,-7.291]],"o":[[-0.357,1.63],[-0.205,-7.291],[0.357,-1.63],[0.205,7.291]],"v":[[-3.317,2.071],[-4.335,-8.18],[-4.06,-24.332],[-3.042,-14.081]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431391716,0.403921574354,0.305882364511,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[270.996,145.54],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Head","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[32.75,0],[9.05,-8.723],[0,0],[0,0],[12.963,11.172]],"o":[[-36.75,0],[-9.05,8.723],[0,0],[0,0],[-12.963,-11.172]],"v":[[269.5,203.875],[202.375,231.25],[180.49,265.037],[357.49,265.037],[335.375,229.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[31.944,0],[8.827,-8.723],[0,0],[0,0],[12.644,11.172]],"o":[[-35.846,0],[-8.827,8.723],[0,0],[0,0],[-12.644,-11.172]],"v":[[269.549,203.875],[204.076,231.25],[186.694,262.646],[356.307,266.949],[333.803,229.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[29.964,0.368],[8.765,-9.559],[0,0],[0,0],[10.827,12.493]],"o":[[-28.262,-0.347],[-8.765,9.559],[0,0],[0,0],[-10.827,-12.493]],"v":[[266.904,204.072],[217.226,226.01],[201.569,257.67],[350.704,277.632],[328.366,234.753]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[17.745,0.368],[4.241,-8.325],[0,0],[0,0],[6.509,11.788]],"o":[[-16.737,-0.347],[-4.241,8.325],[0,0],[0,0],[-6.509,-11.788]],"v":[[266.953,203.361],[239.192,224.352],[231.398,251.757],[320.169,284.732],[304.709,236.885]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[8.691,0.368],[2.251,-8.436],[0,0],[0,0],[2.817,13.796]],"o":[[-8.197,-0.347],[-2.251,8.436],[0,0],[0,0],[-2.817,-13.796]],"v":[[268.056,203.598],[254.994,222.223],[251.276,250.581],[295.322,287.805],[287.494,239.724]],"c":true}]},{"t":85.833984375,"s":[{"i":[[2.773,0.368],[0.492,-8.596],[0,0],[0,0],[0.699,12.45]],"o":[[-2.615,-0.347],[-0.492,8.596],[0,0],[0,0],[-0.699,-12.45]],"v":[[269.236,203.598],[265.068,222.223],[263.435,250.823],[278.044,286.855],[275.437,239.724]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.105865478516,0.450958251953,0.901947021484,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Body","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[403.855,266.954],[134.263,267.281],[133.746,34.114],[403.983,33.534]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[407.68,272.93],[141.435,262.261],[141.874,38.656],[408.046,28.992]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":72.5,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[405.196,278.275],[150.215,255.604],[150.652,44.48],[407.113,22.696]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[395.085,285.526],[168.051,250.854],[169.441,49.35],[394.738,15.446]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[372.271,290.107],[194.58,245.643],[195.101,57.011],[373.426,8.258]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[351.971,291.685],[212.112,242.326],[211.844,59.063],[352.098,8.576]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[310.1,293.583],[241.912,240.199],[241.881,60.48],[309.976,6.207]],"c":true}]},{"t":85.833984375,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[279.372,300.675],[263.178,242.092],[263.608,59.062],[278.826,1.479]],"c":true}]}],"ix":2},"nm":"Mask","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.815673828125,0.88232421875,0.980377197266,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Background","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":87,"st":60,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"CAMERA_CASING 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[0,0],[0,13.689],[2.562,0.203],[0,0],[0.235,-13.941],[0,0],[-0.817,-3.47],[0,3.644],[0,21.647],[0,11.447]],"o":[[0,0],[0,-11.515],[-0.278,-0.022],[0,0],[-0.157,9.303],[0,0],[3.866,-2.97],[0,-3.644],[0,-5.657],[0,-17.18]],"v":[[355.812,-582.217],[355.812,-651.939],[349.438,-666.703],[349.561,-458.372],[349.68,-455.309],[349.716,-452.543],[349.634,-442.155],[355.812,-452.231],[355.812,-527.897],[355.812,-557.197]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84,"s":[{"i":[[0,0],[0.181,13.689],[3.692,-3.049],[0,0],[0.235,-13.941],[0,0],[-0.817,-3.47],[0.115,3.644],[0.08,21.647],[0.011,11.447]],"o":[[0,0],[-0.152,-11.515],[-0.215,0.151],[0,0],[-0.157,9.303],[0,0],[3.866,-2.97],[-0.115,-3.644],[-0.021,-5.657],[-0.017,-17.18]],"v":[[291.635,-587.821],[292.523,-742.756],[283.027,-757.519],[282.695,-373.749],[282.814,-370.686],[282.85,-362.418],[282.768,-352.029],[291.612,-362.105],[291.605,-533.5],[291.592,-562.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85,"s":[{"i":[[0,0],[0.355,13.689],[6.542,-0.922],[0,0],[0.37,-13.941],[0,0],[0.057,-3.845],[-0.362,3.644],[-0.253,21.647],[-0.035,11.447]],"o":[[0,0],[-0.298,-11.515],[-0.366,0.052],[0,0],[-0.247,9.303],[0,0],[5.807,-4.595],[0.362,-3.644],[0.066,-5.657],[0.053,-17.18]],"v":[[193.824,-593.342],[194.689,-728.224],[178.533,-742.988],[177.933,-357.043],[178.12,-353.042],[178.176,-343.402],[177.975,-332.388],[193.487,-347.215],[193.916,-523.397],[193.959,-552.697]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86,"s":[{"i":[[0,0],[0.484,13.689],[8.523,-3.859],[0,0],[0.505,-13.941],[0,0],[-1.753,-3.47],[-0.867,3.644],[-0.607,21.647],[-0.085,11.447]],"o":[[0,0],[-0.407,-11.515],[-0.429,0.194],[0,0],[-0.337,9.303],[0,0],[5.997,-7.47],[0.867,-3.644],[0.159,-5.657],[0.128,-17.18]],"v":[[75.679,-597.849],[75.758,-718.949],[53.719,-733.713],[53.693,-344.334],[53.947,-321.896],[54.024,-312.256],[53.705,-300.617],[75.218,-335.193],[75.9,-512.279],[76.004,-541.578]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87,"s":[{"i":[[0,0],[3.485,11.583],[8.955,-4.433],[-0.062,-3.091],[-3.221,-11.86],[0,0],[-2.544,-3.333],[-1.095,5.54],[-0.607,21.647],[3.44,10.582]],"o":[[0,0],[-3.366,-10.6],[-0.589,2.321],[0.062,3.09],[2.943,8.704],[0,0],[8.347,0.703],[0.794,-8.444],[0.159,-5.657],[-3.981,-16.052]],"v":[[0.007,-686.694],[-13.977,-729.743],[-42.282,-733.704],[-42.419,-351.824],[-35.506,-318.32],[-18.172,-282.143],[-17.207,-281.536],[4.303,-291.511],[7.882,-626.94],[5.009,-672.742]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[7.233,7.337],[10.246,-6.152],[-0.246,-12.344],[-9.136,-8.623],[0,0],[-4.913,-2.921],[-1.776,11.215],[-0.607,21.647],[9.871,10.62]],"o":[[0,0],[-7.601,-8.337],[-1.067,8.69],[0.246,12.344],[9.086,7.021],[0,0],[8.647,1.223],[0.576,-22.816],[0.159,-5.657],[-9.656,-13.055]],"v":[[-96.036,-712.675],[-118.855,-734.697],[-147.972,-735.599],[-146.863,-351.098],[-130.294,-314.045],[-110.979,-299.591],[-90.581,-288.487],[-69.43,-302.648],[-68.325,-642.738],[-74.855,-686.091]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,0],[9.484,3.793],[8.601,-12.274],[-0.441,-20.809],[-28.549,-9.001],[0,0],[-12.63,-1.571],[-1.485,15.967],[0.068,21.914],[19.53,9.541]],"o":[[0,0],[-23.711,-9.556],[-2.126,56.648],[0.441,20.809],[21.714,6.2],[0,0],[16.655,-5.885],[0.585,-24.671],[-0.31,-11.754],[-24.908,-11.845]],"v":[[-256.274,-730.569],[-286.913,-744.674],[-328.463,-731.93],[-328.345,-350.477],[-292.838,-292.848],[-262.739,-284.553],[-227.494,-275.913],[-199.734,-301.739],[-198.453,-680.107],[-223.793,-715.645]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93,"s":[{"i":[[0,0],[10.869,1.69],[0.025,-8.377],[-0.642,-28.87],[-23.311,-3.439],[0,0],[-22.891,-2.452],[-0.707,20.252],[1.081,22.315],[30.895,7.44]],"o":[[0,0],[-26.095,-4.058],[-0.004,1.43],[0.642,28.87],[18.067,2.665],[0,0],[13.227,-9.056],[0.707,-20.252],[-1.012,-20.899],[-21.169,-6.833]],"v":[[-441.081,-726.063],[-476.791,-733.581],[-512.894,-713.271],[-514.67,-343.981],[-483.267,-295.903],[-442.821,-290.271],[-385.72,-282.768],[-366.552,-319.504],[-367.271,-675.98],[-402.702,-717.321]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,0],[10.926,1.148],[0.925,-11.98],[-0.642,-28.87],[-25.802,-3.644],[0,0],[-22.916,-2.187],[-0.707,20.252],[1.081,22.315],[31.032,6.862]],"o":[[0,0],[-23.328,-2.596],[-0.085,1.425],[0.642,28.87],[22.11,3.111],[0,0],[17.386,-3.626],[0.707,-20.252],[-1.012,-20.899],[-22.091,-5.737]],"v":[[-499.893,-697.032],[-539.989,-704.551],[-576.438,-678.34],[-576.108,-326.385],[-541.208,-279.351],[-496.322,-272.988],[-445.018,-267.521],[-419.062,-304.309],[-419.608,-647.001],[-459.425,-688.969]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.293,-3.849],[0,0],[-22.941,-1.921],[-0.707,20.252],[1.081,22.315],[31.169,6.285]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[26.153,3.558],[0,0],[21.546,1.805],[0.707,-20.252],[-1.012,-20.899],[-23.012,-4.64]],"v":[[-551.186,-680.532],[-595.667,-688.051],[-632.464,-655.94],[-635.041,-321.32],[-596.641,-275.331],[-547.318,-268.237],[-501.809,-264.806],[-469.065,-301.646],[-464.426,-630.554],[-508.629,-673.148]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.293,-3.849],[0,0],[-22.942,-1.921],[-0.707,20.252],[1.081,22.315],[31.169,6.285]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[26.153,3.558],[0,0],[21.546,1.805],[0.707,-20.252],[-1.012,-20.899],[-23.012,-4.64]],"v":[[-580.634,-669.881],[-625.743,-675.52],[-662.539,-643.409],[-665.116,-312.548],[-626.717,-266.559],[-578.646,-260.091],[-531.884,-256.034],[-499.14,-292.875],[-493.875,-620.529],[-536.825,-663.123]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.293,-3.849],[0,0],[-22.941,-1.921],[-0.707,20.252],[1.081,22.315],[31.169,6.285]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[26.153,3.558],[0,0],[21.546,1.805],[0.707,-20.252],[-1.012,-20.899],[-23.012,-4.64]],"v":[[-607.577,-656.723],[-648.299,-660.483],[-685.096,-628.371],[-687.672,-308.789],[-649.273,-262.799],[-602.456,-256.958],[-554.441,-252.274],[-521.697,-289.115],[-523.323,-607.997],[-565.02,-650.592]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.293,-3.849],[0,0],[-21.06,0.551],[-0.707,20.252],[1.081,22.315],[29.62,2.879]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[26.153,3.558],[0,0],[21.614,-0.566],[0.707,-20.252],[-1.012,-20.899],[-23.365,-2.271]],"v":[[-621.361,-629.154],[-663.337,-631.66],[-700.133,-599.549],[-700.203,-298.764],[-661.804,-252.774],[-612.481,-248.187],[-565.719,-246.009],[-535.481,-286.609],[-538.361,-582.935],[-580.058,-625.529]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.293,-3.849],[0,0],[-21.06,0.551],[-0.707,20.252],[1.081,22.315],[29.62,2.879]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[26.153,3.558],[0,0],[21.614,-0.566],[0.707,-20.252],[-1.012,-20.899],[-23.365,-2.271]],"v":[[-620.108,-617.876],[-663.337,-619.129],[-700.133,-587.018],[-702.71,-292.498],[-664.311,-246.509],[-614.987,-244.427],[-570.731,-243.502],[-537.987,-280.343],[-535.855,-572.91],[-577.551,-615.504]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.457,-2.338],[0,0],[-21.06,0.551],[-0.707,20.252],[1.081,22.315],[29.62,2.879]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[28.045,2.304],[0,0],[21.614,-0.566],[0.707,-20.252],[-1.012,-20.899],[-23.365,-2.271]],"v":[[-620.108,-605.344],[-663.337,-605.345],[-700.133,-573.233],[-699.589,-281.22],[-661.19,-235.231],[-614.372,-231.896],[-570.117,-230.971],[-537.373,-267.812],[-542.12,-561.631],[-576.298,-602.973]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.524,-1.302],[0,0],[-21.06,0.551],[-0.707,20.252],[1.081,22.315],[29.62,2.879]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[23.032,1.051],[0,0],[21.614,-0.566],[0.707,-20.252],[-1.012,-20.899],[-23.365,-2.271]],"v":[[-615.095,-592.813],[-658.324,-592.813],[-695.121,-560.702],[-694.577,-276.207],[-656.177,-230.218],[-610.613,-229.39],[-568.864,-229.718],[-536.12,-266.559],[-535.855,-549.1],[-572.539,-591.695]],"c":true}]},{"t":126,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.524,-1.302],[0,0],[-21.06,0.551],[-0.707,20.252],[1.081,22.315],[29.62,2.879]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[23.032,1.051],[0,0],[21.614,-0.566],[0.707,-20.252],[-1.012,-20.899],[-23.365,-2.271]],"v":[[-605.07,-581.535],[-648.299,-581.535],[-685.096,-549.424],[-685.805,-263.676],[-647.406,-217.687],[-601.841,-216.858],[-560.092,-217.187],[-527.348,-259.04],[-528.336,-541.582],[-562.514,-580.417]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294118524,0.250980407,0.262745112181,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":84,"op":342,"st":60,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"FRONT_SCREEN 4","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0.25,-9.25],[0,0],[0.25,-0.5],[0,0],[0.5,9.25],[0,0],[-2,-2.5],[0,0]],"o":[[0,0],[0.25,3],[0,0],[-2,2.75],[0,0],[0,-16.016],[0,0],[1.75,3.5]],"v":[[-308,-465.5],[-308,477.25],[-308.25,483],[-356.25,568.25],[-364,573.75],[-364,-560.5],[-354.5,-565],[-310.5,-483]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88.334,"s":[{"i":[[1,-13],[0,0],[5,-5],[0,0],[0,16.016],[0,0],[-18,-17],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-11.25,9],[0,0],[0,-16.016],[0,0],[5.5,4.5]],"v":[[-257,-464],[-257,468],[-270.5,493.062],[-345.5,568.5],[-358.25,553.75],[-357,-542.5],[-338,-559],[-265.5,-485.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89.166,"s":[{"i":[[0.667,-14.005],[0,0],[8.672,-3.333],[0,0],[0,16.016],[0,0],[-17.339,-11.333],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-12.839,6],[0,0],[0,-16.016],[0,0],[9.005,3]],"v":[[-210.667,-462.333],[-210.667,466],[-229.333,495.708],[-334.833,568.667],[-353,549.167],[-352.167,-541.5],[-329.833,-562.167],[-226,-489.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0.333,-15.011],[0,0],[12.344,-1.667],[0,0],[0,16.016],[0,0],[-16.677,-5.667],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-14.427,3],[0,0],[0,-16.016],[0,0],[12.511,1.5]],"v":[[-167.833,-460.667],[-167.833,464],[-191.667,498.354],[-324.167,568.833],[-347.75,544.583],[-347.333,-540.5],[-321.667,-565.333],[-190,-493.833]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-127,-459],[-127,462],[-156,501],[-313.5,569],[-342.5,540],[-342.5,-539.5],[-313.5,-568.5],[-156,-498]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-89.667,-464.667],[-89.667,467.333],[-118.667,503],[-308.667,571.333],[-337.667,542.333],[-337.667,-541.333],[-308.667,-570.333],[-118.667,-500.333]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-54.833,-470.333],[-54.833,472.667],[-83.833,505],[-303.833,573.667],[-332.833,544.667],[-332.833,-543.167],[-303.833,-572.167],[-83.833,-502.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-24,-476],[-24,478],[-53,507],[-299,576],[-328,547],[-328,-545],[-299,-574],[-53,-505]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[6.073,-479.4],[6.073,481.5],[-22.927,510.5],[-297.12,577.1],[-326.12,548.1],[-326.12,-546.2],[-297.12,-575.2],[-22.927,-508.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[32.647,-482.8],[32.647,485],[3.647,514],[-295.24,578.2],[-324.24,549.2],[-324.24,-547.4],[-295.24,-576.4],[3.647,-511.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[57.22,-486.2],[57.22,488.5],[28.22,517.5],[-293.36,579.3],[-322.36,550.3],[-322.36,-548.6],[-293.36,-577.6],[28.22,-515.2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[79.46,-489.6],[79.46,492],[50.46,521],[-291.48,580.4],[-320.48,551.4],[-320.48,-549.8],[-291.48,-578.8],[50.46,-518.6]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[99.7,-493],[99.7,495.5],[70.7,524.5],[-289.6,581.5],[-318.6,552.5],[-318.6,-551],[-289.6,-580],[70.7,-522]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[136.58,-499.8],[136.58,502.5],[107.58,531.5],[-285.84,583.7],[-314.84,554.7],[-314.84,-553.4],[-285.84,-582.4],[107.58,-528.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[179.4,-510],[179.4,513],[150.4,542],[-280.2,587],[-309.2,558],[-309.2,-557],[-280.2,-586],[150.4,-539]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103.334,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[201.28,-516],[201.28,518.8],[172.28,547.8],[-279.44,588.2],[-308.44,559.2],[-308.44,-558],[-279.44,-587],[172.28,-545]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[212.053,-519],[212.053,521.7],[183.053,550.7],[-279.06,588.8],[-308.06,559.8],[-308.06,-558.5],[-279.06,-587.5],[183.053,-548]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[220.827,-522],[220.827,524.6],[191.827,553.6],[-278.68,589.4],[-307.68,560.4],[-307.68,-559],[-278.68,-588],[191.827,-551]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[229.1,-525],[229.1,527.5],[200.1,556.5],[-278.3,590],[-307.3,561],[-307.3,-559.5],[-278.3,-588.5],[200.1,-554]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":109.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[256.46,-535.4],[256.46,537.5],[227.46,566.5],[-276.78,592.4],[-305.78,563.4],[-305.78,-561.5],[-276.78,-590.5],[227.46,-564.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[260.8,-538],[260.8,540],[231.8,569],[-276.4,593],[-305.4,564],[-305.4,-562],[-276.4,-591],[231.8,-567]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[273.82,-543.7],[273.82,546],[244.82,575],[-276.16,594.2],[-305.16,565.2],[-305.16,-562.6],[-276.16,-591.6],[244.82,-572.7]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":114.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[280.833,-547.5],[280.833,550],[251.833,579],[-276,595],[-305,566],[-305,-563],[-276,-592],[251.833,-576.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[294.867,-555],[294.867,558],[265.867,587],[-275.6,597],[-304.6,568],[-304.6,-564],[-275.6,-593],[265.867,-584]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":122.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[301.9,-559.5],[301.9,563],[272.9,592],[-275.7,597],[-304.7,568],[-304.7,-565],[-275.7,-594],[272.9,-588.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[306.933,-564],[306.933,568],[277.933,597],[-275.8,597],[-304.8,568],[-304.8,-566],[-275.8,-595],[277.933,-593]],"c":true}]},{"t":135,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[310,-568],[310,570],[281,599],[-276,599],[-305,570],[-305,-568],[-276,-597],[281,-597]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.815686285496,0.882352948189,0.980392158031,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[384,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102,102],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":88,"op":342,"st":60,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Figure 7","tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":93.334,"s":[0]},{"t":101.666015625,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[207.459,144.79,0],"to":[0.312,0,0],"ti":[-1.146,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":62.779,"s":[209.334,144.79,0],"to":[1.146,0,0],"ti":[-2.125,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65.555,"s":[214.334,144.79,0],"to":[2.125,0,0],"ti":[-2.812,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[222.084,144.79,0],"to":[2.812,0,0],"ti":[-4.396,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":71.111,"s":[231.209,144.79,0],"to":[4.396,0,0],"ti":[-5.104,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[248.459,144.79,0],"to":[5.104,0,0],"ti":[-3.812,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82.223,"s":[261.834,144.79,0],"to":[3.812,0,0],"ti":[-2.667,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.779,"s":[271.334,144.79,0],"to":[2.667,0,0],"ti":[-1.812,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[277.834,144.79,0],"to":[1.812,0,0],"ti":[-1.083,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98.889,"s":[282.209,144.79,0],"to":[1.083,0,0],"ti":[-0.458,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.445,"s":[284.334,144.79,0],"to":[0.458,0,0],"ti":[-0.104,0,0]},{"t":110,"s":[284.959,144.79,0]}],"ix":2,"l":2},"a":{"a":0,"k":[270.209,145.54,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.55,0.55,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.001]},"t":60,"s":[108,108,100]},{"t":110,"s":[106.5,106.5,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0,0],[-0.095,-1.387],[0.299,-1.3],[0.045,1.308]],"o":[[0.052,0.711],[0.119,1.734],[-0.239,1.04],[0,0]],"v":[[-57.799,-6.034],[-57.522,-2.167],[-57.671,6.182],[-58.104,3.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0,0],[-0.232,-1.387],[0.725,-1.3],[0.109,1.308]],"o":[[0.127,0.711],[0.29,1.734],[-0.58,1.04],[0,0]],"v":[[-53.069,-6.034],[-52.397,-2.167],[-52.759,6.182],[-53.809,3.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,0],[-0.59,-1.387],[1.846,-1.3],[0.276,1.308]],"o":[[0.325,0.711],[0.738,1.734],[-1.477,1.04],[0,0]],"v":[[-41.097,-6.034],[-39.386,-2.167],[-40.307,6.182],[-42.981,3.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0,0],[-0.953,-1.387],[2.979,-1.3],[0.446,1.308]],"o":[[0.524,0.711],[1.191,1.734],[-2.384,1.04],[0,0]],"v":[[-29.746,-6.034],[-26.986,-2.167],[-28.473,6.182],[-32.788,3.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0,0],[-1.277,-1.387],[3.993,-1.3],[0.598,1.308]],"o":[[0.702,0.711],[1.596,1.734],[-3.195,1.04],[0,0]],"v":[[-19.728,-6.034],[-16.028,-2.167],[-18.021,6.182],[-23.805,3.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[0,0],[-1.471,-1.387],[4.599,-1.3],[0.688,1.308]],"o":[[0.809,0.711],[1.838,1.734],[-3.68,1.04],[0,0]],"v":[[-13.958,-6.034],[-9.695,-2.167],[-11.991,6.182],[-18.654,3.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[0,0],[-1.612,-1.387],[5.042,-1.3],[0.754,1.308]],"o":[[0.886,0.711],[2.015,1.734],[-4.034,1.04],[0,0]],"v":[[-9.542,-6.034],[-4.87,-2.167],[-7.386,6.182],[-14.689,3.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[0,0],[-1.826,-1.387],[5.71,-1.3],[0.854,1.308]],"o":[[1.004,0.711],[2.282,1.734],[-4.569,1.04],[0,0]],"v":[[-8.492,-6.268],[-3.201,-2.402],[-6.051,5.947],[-14.321,3.322]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[0,0],[-1.802,-1.387],[5.635,-1.3],[0.843,1.308]],"o":[[0.991,0.711],[2.252,1.734],[-4.509,1.04],[0,0]],"v":[[-5.336,-6.268],[-0.114,-2.402],[-2.926,5.947],[-11.089,3.322]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[-1.899,-1.387],[5.939,-1.3],[0.889,1.308]],"o":[[1.044,0.711],[2.374,1.734],[-4.752,1.04],[0,0]],"v":[[-2.334,-6.268],[3.169,-2.402],[0.205,5.947],[-8.397,3.322]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,0],[-1.939,-1.387],[6.061,-1.3],[0.907,1.308]],"o":[[1.066,0.711],[2.423,1.734],[-4.85,1.04],[0,0]],"v":[[-0.934,-6.268],[4.683,-2.402],[1.658,5.947],[-7.122,3.322]],"c":false}]},{"t":135,"s":[{"i":[[0,0],[-1.939,-1.387],[6.061,-1.3],[0.907,1.308]],"o":[[1.066,0.711],[2.423,1.734],[-4.85,1.04],[0,0]],"v":[[-0.582,-6.237],[5.035,-2.37],[2.01,5.979],[-6.77,3.354]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.596078455448,0.321568638086,0.239215686917,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.12,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[272.026,152.086],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Nose","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[-0.193,7.277],[-0.436,0],[-0.204,3.66],[0.902,-0.059],[0.071,-14.068],[-0.242,0]],"o":[[0.15,8.062],[0.245,0],[-0.171,-14.63],[-0.9,0.059],[0.185,2.604],[0.436,0]],"v":[[-56.249,5.695],[-55.215,18.856],[-54.713,13.904],[-56.412,-10.293],[-57.945,12.606],[-57.189,17.449]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[-0.47,7.277],[-1.059,0],[-0.496,3.66],[2.191,-0.059],[0.174,-14.068],[-0.588,0]],"o":[[0.364,8.062],[0.596,0],[-0.415,-14.63],[-2.185,0.059],[0.449,2.604],[1.059,0]],"v":[[-51.53,5.695],[-49.016,18.856],[-47.797,13.904],[-51.926,-10.293],[-55.649,12.606],[-53.813,17.449]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[-1.196,7.277],[-2.695,0],[-1.262,3.66],[5.577,-0.059],[0.442,-14.068],[-1.497,0]],"o":[[0.928,8.062],[1.517,0],[-1.057,-14.63],[-5.564,0.059],[1.144,2.604],[2.695,0]],"v":[[-39.584,5.695],[-33.184,18.856],[-30.08,13.904],[-40.592,-10.293],[-50.072,12.606],[-45.396,17.449]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[-1.93,7.277],[-4.349,0],[-1.859,2.669],[8.999,-0.059],[0.713,-14.068],[-2.416,0]],"o":[[1.497,8.062],[2.449,0],[-1.705,-14.63],[-8.978,0.059],[1.845,2.604],[4.349,0]],"v":[[-28.26,5.461],[-18.311,18.388],[-12.925,13.904],[-29.887,-10.293],[-45.183,12.606],[-38.394,17.215]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[-2.586,7.277],[-5.829,0],[-2.492,2.669],[12.062,-0.059],[0.956,-14.068],[-3.238,0]],"o":[[2.007,8.062],[3.282,0],[-0.557,-15.213],[-12.034,0.059],[2.474,2.604],[5.829,0]],"v":[[-18.266,5.461],[-4.931,18.388],[2.289,13.904],[-20.446,-11.464],[-40.949,12.606],[-31.849,17.215]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[-2.979,7.277],[-6.715,0],[-2.871,2.669],[13.894,-0.059],[0.615,-15.17],[-3.73,0]],"o":[[2.311,8.062],[3.781,0],[-0.642,-15.213],[-13.862,0.059],[2.849,2.604],[6.715,0]],"v":[[-12.51,4.992],[2.851,17.685],[11.168,13.904],[-15.292,-12.168],[-38.639,12.606],[-28.157,16.746]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[-3.266,7.277],[-7.36,0],[-3.147,2.669],[15.23,-0.059],[0.674,-15.17],[-4.089,0]],"o":[[2.534,8.062],[4.144,0],[-0.703,-15.213],[-15.194,0.059],[3.123,2.604],[7.36,0]],"v":[[-8.876,4.523],[7.962,17.215],[17.592,12.496],[-10.382,-13.106],[-37.517,11.903],[-26.284,16.511]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[-3.478,7.277],[-7.839,0],[-3.352,2.669],[16.221,-0.059],[0.718,-15.17],[-4.355,0]],"o":[[3.478,7.277],[4.414,0],[-0.749,-15.213],[-16.184,0.059],[3.327,2.604],[7.839,0]],"v":[[-5.981,4.288],[11.703,16.746],[21.96,12.496],[-7.336,-13.576],[-36.487,11.903],[-24.772,16.511]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[-3.65,7.277],[-8.226,0],[-3.517,2.669],[17.022,-0.059],[0.753,-15.17],[-4.57,0]],"o":[[3.65,7.277],[4.632,0],[-0.786,-15.213],[-16.983,0.059],[3.491,2.604],[8.226,0]],"v":[[-3.376,3.819],[15.182,16.511],[26.438,12.496],[-4.797,-14.514],[-35.635,11.668],[-23.342,16.511]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[-3.847,7.277],[-8.67,0],[-3.707,2.669],[17.939,-0.059],[0.794,-15.17],[-4.816,0]],"o":[[3.847,7.277],[4.881,0],[-0.828,-15.213],[-17.898,0.059],[3.679,2.604],[8.67,0]],"v":[[-1.836,3.584],[18.215,15.807],[31.315,11.557],[-1.85,-15.923],[-34.843,11.668],[-21.888,15.807]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[-3.926,7.277],[-8.848,0],[-3.783,2.669],[18.309,-0.059],[0.81,-15.17],[-4.916,0]],"o":[[3.926,7.277],[4.982,0],[-0.845,-15.213],[-18.267,0.059],[3.755,2.604],[8.848,0]],"v":[[-0.458,3.584],[20.007,15.807],[33.377,11.557],[-0.472,-15.923],[-34.145,11.668],[-20.923,15.807]],"c":true}]},{"t":135,"s":[{"i":[[-3.926,7.277],[-8.848,0],[-3.783,2.669],[18.309,-0.059],[0.81,-15.17],[-4.916,0]],"o":[[3.926,7.277],[4.982,0],[-0.845,-15.213],[-18.267,0.059],[3.755,2.604],[8.848,0]],"v":[[-0.106,3.616],[20.359,15.839],[33.729,11.589],[-0.12,-15.891],[-33.793,11.7],[-20.571,15.839]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0,9.283],[0.481,0],[0,-9.283],[-0.481,0]],"o":[[0,-9.283],[-0.481,0],[0,9.283],[0.481,0]],"v":[[-56.552,-17.322],[-57.474,-33.907],[-58.358,-17.994],[-57.435,-0.961]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0,9.283],[1.168,0],[0,-9.283],[-1.168,0]],"o":[[0,-9.283],[-1.168,0],[0,9.283],[1.168,0]],"v":[[-52.264,-17.322],[-54.505,-33.907],[-56.652,-17.994],[-54.41,-0.961]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,9.283],[2.973,0],[0,-9.283],[-2.973,0]],"o":[[0,-9.283],[-2.973,0],[0,9.283],[2.973,0]],"v":[[-41.453,-17.322],[-47.159,-33.907],[-52.623,-17.994],[-46.917,-0.961]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0,9.283],[4.797,0],[0,-9.283],[-4.797,0]],"o":[[0,-9.283],[-4.797,0],[0,9.283],[4.797,0]],"v":[[-31.654,-18.258],[-40.861,-34.843],[-49.678,-18.931],[-40.471,-1.898]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0,9.532],[6.43,0],[0,-9.532],[-6.43,0]],"o":[[0,-9.532],[-6.43,0],[0,9.532],[6.43,0]],"v":[[-22.815,-18.985],[-35.156,-36.015],[-46.974,-19.676],[-34.633,-2.186]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[0,9.532],[7.407,0],[0,-9.532],[-7.407,0]],"o":[[0,-9.532],[-7.407,0],[0,9.532],[7.407,0]],"v":[[-18.02,-19.689],[-32.236,-36.718],[-45.85,-20.379],[-31.633,-2.89]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[0,9.723],[8.119,0],[0,-9.723],[-8.119,0]],"o":[[0,-9.723],[-8.119,0],[0,9.723],[8.119,0]],"v":[[-14.659,-20.051],[-30.241,-37.422],[-45.163,-20.755],[-29.58,-2.915]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[0,9.723],[8.648,0],[0,-9.723],[-8.648,0]],"o":[[0,-9.723],[-8.648,0],[0,9.723],[8.648,0]],"v":[[-11.89,-20.755],[-28.488,-38.126],[-44.381,-21.459],[-27.784,-3.619]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[0,9.723],[9.075,0],[0,-9.723],[-9.075,0]],"o":[[0,-9.723],[-9.075,0],[0,9.723],[9.075,0]],"v":[[-10.316,-21.459],[-26.748,-39.065],[-43.18,-21.459],[-26.748,-3.854]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,9.805],[9.607,0],[0,-9.805],[-9.607,0]],"o":[[0,-9.805],[-9.607,0],[0,9.805],[9.607,0]],"v":[[-8.19,-22.558],[-25.585,-40.312],[-42.981,-22.558],[-25.585,-4.804]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,9.805],[9.805,0],[0,-9.805],[-9.805,0]],"o":[[0,-9.805],[-9.805,0],[0,9.805],[9.805,0]],"v":[[-6.942,-22.558],[-24.697,-40.312],[-42.451,-22.558],[-24.697,-4.804]],"c":true}]},{"t":135,"s":[{"i":[[0,9.805],[9.805,0],[0,-9.805],[-9.805,0]],"o":[[0,-9.805],[-9.805,0],[0,9.805],[9.805,0]],"v":[[-6.59,-22.526],[-24.345,-40.281],[-42.099,-22.526],[-24.345,-4.772]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.337254911661,0.23137255013,0.129411771894,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[270.47,108.276],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Hair","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[-0.312,13.512],[0,0],[0,0],[-0.015,2.62],[0.001,4.351],[0,0],[-0.927,0.06],[-0.139,-15.255],[0,0],[-0.024,-2.669],[-0.092,-4.353],[-0.044,-3.988],[0,-4.307],[0.295,-3.002],[0.482,-0.349],[0.391,4.905],[0,7.994],[0,0]],"o":[[0,0],[0,0],[0.06,-4.479],[0.015,-2.598],[0,0],[-0.003,-15.806],[0.927,-0.06],[0,0],[0.001,4.276],[0.024,2.702],[0.178,8.387],[0.044,3.962],[0,7.459],[-0.49,4.981],[-0.608,0.441],[-0.407,-5.108],[0,0],[0,-6.072]],"v":[[-58.689,-3.691],[-58.726,-4.5],[-58.724,-4.616],[-58.676,-14.567],[-58.471,-23.153],[-58.471,-23.222],[-56.937,-47.531],[-55.247,-22.97],[-55.247,-22.876],[-55.237,-14.204],[-55.068,-5.188],[-54.763,11.754],[-54.743,22.514],[-55.47,41.151],[-56.849,48.639],[-58.387,41.742],[-59.021,24.251],[-59.021,24.218]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[-0.757,13.512],[0,0],[0,0],[-0.037,2.62],[0.002,4.351],[0,0],[-2.253,0.06],[-0.337,-15.255],[0,0],[-0.058,-2.669],[-0.224,-4.353],[-0.106,-3.988],[0,-4.307],[0.717,-3.002],[1.17,-0.349],[0.95,4.905],[0,7.994],[0,0]],"o":[[0,0],[0,0],[0.146,-4.479],[0.037,-2.598],[0,0],[-0.006,-15.806],[2.253,-0.06],[0,0],[0.001,4.276],[0.059,2.702],[0.431,8.387],[0.106,3.962],[0,7.459],[-1.19,4.981],[-1.476,0.441],[-0.989,-5.108],[0,0],[0,-6.072]],"v":[[-56.705,-3.691],[-56.793,-4.5],[-56.789,-4.616],[-56.672,-14.567],[-56.176,-23.153],[-56.176,-23.222],[-52.448,-47.531],[-48.344,-22.97],[-48.344,-22.876],[-48.32,-14.204],[-47.909,-5.188],[-47.168,11.754],[-47.118,22.514],[-48.884,41.151],[-52.234,48.639],[-55.971,41.742],[-57.511,24.251],[-57.511,24.218]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[-1.928,13.512],[0,0],[0,0],[-0.095,2.62],[0.005,4.351],[0,0],[-5.736,0.06],[-0.858,-15.255],[0,0],[-0.149,-2.669],[-0.57,-4.353],[-0.271,-3.988],[0,-4.307],[1.826,-3.002],[2.98,-0.349],[2.418,4.905],[0,7.994],[0,0]],"o":[[0,0],[0,0],[0.372,-4.479],[0.094,-2.598],[0,0],[-0.015,-15.806],[5.736,-0.06],[0,0],[0.004,4.276],[0.151,2.702],[1.098,8.387],[0.269,3.962],[0,7.459],[-3.03,4.981],[-3.758,0.441],[-2.518,-5.108],[0,0],[0,-6.072]],"v":[[-51.946,-3.691],[-52.17,-4.5],[-52.161,-4.616],[-51.862,-14.567],[-50.599,-23.153],[-50.599,-23.222],[-41.108,-47.531],[-30.66,-22.97],[-30.66,-22.876],[-30.598,-14.204],[-29.552,-5.188],[-27.667,11.754],[-27.539,22.514],[-32.036,41.151],[-40.564,48.639],[-50.078,41.742],[-53.999,24.251],[-53.999,24.218]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[-3.112,13.512],[0,0],[0,0],[-0.153,2.62],[0.007,4.351],[0,0],[-9.255,0.06],[-1.384,-15.255],[0,0],[-0.24,-2.669],[-0.919,-4.353],[-0.437,-3.988],[0,-4.307],[2.946,-3.002],[4.808,-0.349],[3.902,4.905],[0,7.994],[0,0]],"o":[[0,0],[0,0],[0.601,-4.479],[0.152,-2.598],[0,0],[-0.025,-15.806],[9.255,-0.06],[0,0],[0.006,4.276],[0.243,2.702],[1.772,8.387],[0.434,3.962],[0,7.459],[-4.889,4.981],[-6.063,0.441],[-4.063,-5.108],[0,0],[0,-6.072]],"v":[[-47.884,-3.691],[-48.247,-4.5],[-48.231,-4.616],[-47.749,-14.567],[-45.712,-23.153],[-45.712,-23.222],[-30.397,-47.531],[-13.538,-22.97],[-13.538,-22.876],[-13.439,-14.204],[-11.75,-5.188],[-8.709,11.754],[-8.502,22.514],[-15.759,41.151],[-29.518,48.639],[-44.87,41.742],[-51.198,24.251],[-51.198,24.218]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[-4.171,13.512],[0,0],[0,0],[-0.205,2.62],[0.01,4.351],[0,0],[-12.405,0.06],[-0.034,-15.806],[0,0],[-0.322,-2.669],[-1.232,-4.353],[-0.586,-3.988],[0,-4.307],[5.048,-4.141],[6.445,-0.349],[5.23,4.905],[0,7.994],[0,0]],"o":[[0,0],[0,0],[0.805,-4.479],[0.203,-2.598],[0,0],[-0.033,-15.806],[12.405,-0.06],[0,0],[0.008,4.276],[0.326,2.702],[2.374,8.387],[0.582,3.962],[0,7.459],[-6.294,5.164],[-8.127,0.441],[-5.446,-5.108],[0,0],[0,-6.072]],"v":[[-44.39,-3.691],[-44.876,-4.5],[-44.856,-4.616],[-44.21,-14.567],[-41.478,-23.153],[-41.478,-23.222],[-20.951,-48.702],[1.646,-22.97],[1.646,-22.876],[1.78,-14.204],[4.043,-5.188],[8.119,11.754],[8.71,22.982],[-1.017,41.619],[-19.46,49.108],[-40.037,42.211],[-48.832,24.251],[-48.832,24.218]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[-4.804,13.512],[0,0],[0,0],[-0.236,2.62],[0.011,4.351],[0,0],[-14.289,0.06],[-0.039,-15.806],[0,0],[-0.371,-2.669],[-1.42,-4.353],[-0.675,-3.988],[0,-4.307],[6.374,-5.095],[7.424,-0.349],[6.025,4.905],[0,7.994],[0,0]],"o":[[0,0],[0,0],[0.927,-4.479],[0.234,-2.598],[0,0],[-0.038,-15.806],[14.289,-0.06],[0,0],[0.01,4.276],[0.375,2.702],[2.735,8.387],[0.671,3.962],[0,7.459],[-6.899,5.515],[-9.362,0.441],[-6.274,-5.108],[0,0],[0,-6.072]],"v":[[-42.523,-3.691],[-43.083,-4.5],[-43.059,-4.616],[-42.315,-14.567],[-39.169,-23.153],[-39.169,-23.222],[-15.793,-49.406],[10.507,-22.97],[10.507,-22.876],[10.661,-14.204],[13.268,-5.188],[17.964,11.754],[18.644,22.982],[7.44,42.557],[-13.805,50.046],[-37.508,43.149],[-47.64,24.251],[-47.64,24.218]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[-5.266,13.512],[0,0],[0,0],[-0.259,2.62],[0.012,4.351],[0,0],[-15.663,0.06],[-0.042,-15.806],[0,0],[-0.407,-2.669],[-1.556,-4.353],[-0.74,-3.988],[0,-4.307],[6.987,-5.095],[10.261,-0.04],[6.684,5.31],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.016,-4.479],[0.257,-2.598],[0,0],[-0.042,-15.806],[15.663,-0.06],[0,0],[0.011,4.276],[0.411,2.702],[2.998,8.387],[0.735,3.962],[0,7.459],[-7.562,5.515],[-10.273,0.04],[-6.687,-5.313],[0,0],[0,-6.072]],"v":[[-41.467,-3.925],[-42.08,-4.735],[-42.054,-4.851],[-40.724,-14.567],[-38.047,-23.857],[-38.047,-23.925],[-10.882,-50.344],[16.919,-24.377],[16.919,-24.283],[17.344,-14.204],[19.173,-5.188],[24.835,11.754],[25.58,22.982],[14.071,42.792],[-10.245,50.515],[-36.741,43.149],[-46.56,24.251],[-46.56,24.218]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[-5.609,13.512],[0,0],[0,0],[-0.276,2.62],[0.013,4.351],[0,0],[-16.682,0.06],[-0.045,-15.806],[0,0],[-0.433,-2.669],[-1.658,-4.353],[-0.788,-3.988],[0,-4.307],[7.101,-5.507],[10.929,-0.04],[7.119,5.31],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.083,-4.479],[0.273,-2.598],[0,0],[-0.045,-15.806],[16.682,-0.06],[0,0],[0.011,4.276],[0.438,2.702],[3.193,8.387],[0.783,3.962],[0,7.459],[-7.085,5.495],[-10.942,0.04],[-7.123,-5.313],[0,0],[0,-6.072]],"v":[[-40.159,-3.925],[-40.813,-4.735],[-40.785,-4.851],[-38.869,-14.567],[-37.017,-23.857],[-37.017,-23.925],[-7.833,-50.813],[21.277,-24.377],[21.277,-24.283],[21.73,-14.673],[24.928,-5.188],[30.958,11.754],[31.752,22.982],[19.493,43.261],[-7.655,50.984],[-35.376,43.384],[-46.085,24.486],[-46.085,24.453]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[-5.886,13.512],[0,0],[0,0],[-0.289,2.62],[0.014,4.351],[0,0],[-17.506,0.06],[-0.047,-15.806],[0,0],[-0.454,-2.669],[-1.739,-4.353],[-0.827,-3.988],[0,-4.307],[7.452,-5.507],[11.468,-0.04],[7.47,5.31],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.136,-4.479],[0.287,-2.598],[0,0],[-0.047,-15.806],[17.506,-0.06],[0,0],[0.012,4.276],[0.46,2.702],[3.351,8.387],[0.822,3.962],[0,7.459],[-7.435,5.495],[-11.482,0.04],[-7.475,-5.313],[0,0],[0,-6.072]],"v":[[-39.463,-4.63],[-39.41,-4.735],[-39.38,-4.851],[-37.37,-14.567],[-36.165,-24.092],[-36.165,-24.16],[-5.294,-51.752],[25.747,-24.377],[25.747,-24.283],[26.223,-14.673],[29.332,-4.954],[35.168,12.458],[36.001,23.687],[24.122,43.965],[-5.107,51.923],[-34.197,43.853],[-45.681,24.016],[-45.681,23.984]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[-7.295,13.962],[0,0],[0,0],[-0.305,2.62],[0.015,4.351],[0,0],[-18.449,0.06],[-0.05,-15.806],[0,0],[-0.479,-2.669],[-1.833,-4.353],[-0.871,-3.988],[0,-4.307],[7.853,-5.507],[12.086,-0.04],[7.873,5.31],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.197,-4.479],[0.302,-2.598],[0,0],[-0.05,-15.806],[18.449,-0.06],[0,0],[0.012,4.276],[0.485,2.702],[3.532,8.387],[0.866,3.962],[0,7.459],[-7.836,5.495],[-12.101,0.04],[-7.877,-5.313],[0,0],[0,-6.072]],"v":[[-37.86,-4.63],[-37.804,-4.735],[-37.773,-4.851],[-35.654,-14.567],[-35.374,-24.092],[-35.374,-24.16],[-2.345,-53.161],[30.863,-24.377],[30.863,-24.283],[31.364,-14.673],[34.641,-4.954],[40.79,12.458],[41.669,23.687],[29.15,43.965],[-1.653,53.097],[-32.557,44.557],[-45.155,23.978],[-45.155,23.945]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[-7.446,13.962],[0,0],[0,0],[-0.313,2.62],[0.015,4.351],[0,0],[-18.83,0.06],[-0.051,-15.806],[0,0],[-0.49,-2.669],[-1.873,-4.352],[-0.89,-3.988],[0,-4.307],[8.014,-5.508],[12.335,-0.04],[8.035,5.309],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.222,-4.479],[0.309,-2.598],[0,0],[-0.051,-15.806],[18.83,-0.06],[0,0],[0.013,4.276],[0.495,2.702],[3.604,8.387],[0.884,3.962],[0,7.459],[-7.997,5.495],[-12.35,0.04],[-8.04,-5.313],[0,0],[0,-6.072]],"v":[[-37.213,-4.63],[-37.156,-4.735],[-37.125,-4.851],[-34.962,-14.567],[-34.676,-24.092],[-34.676,-24.16],[-0.966,-53.161],[32.926,-24.377],[32.926,-24.283],[33.437,-14.673],[36.782,-4.954],[43.058,12.458],[43.955,23.687],[31.177,43.965],[-0.26,53.097],[-31.801,44.557],[-44.659,23.978],[-44.659,23.945]],"c":true}]},{"t":135,"s":[{"i":[[-7.446,13.962],[0,0],[0,0],[-0.313,2.62],[0.015,4.351],[0,0],[-18.83,0.06],[-0.051,-15.806],[0,0],[-0.49,-2.669],[-1.873,-4.352],[-0.89,-3.988],[0,-4.307],[8.014,-5.508],[12.335,-0.04],[8.035,5.309],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.222,-4.479],[0.309,-2.598],[0,0],[-0.051,-15.806],[18.83,-0.06],[0,0],[0.013,4.276],[0.495,2.702],[3.604,8.387],[0.884,3.962],[0,7.459],[-7.997,5.495],[-12.35,0.04],[-8.04,-5.313],[0,0],[0,-6.072]],"v":[[-36.861,-4.598],[-36.804,-4.703],[-36.772,-4.819],[-34.61,-14.535],[-34.324,-24.06],[-34.324,-24.128],[-0.614,-53.129],[33.278,-24.345],[33.278,-24.252],[33.789,-14.641],[37.134,-4.922],[43.41,12.489],[44.307,23.718],[31.53,43.997],[0.092,53.129],[-31.449,44.589],[-44.307,24.009],[-44.307,23.977]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0.226,1.656],[-0.13,7.412],[-0.226,-1.656],[0.13,-7.412]],"o":[[-0.227,-1.657],[0.13,-7.412],[0.227,1.657],[-0.13,7.412]],"v":[[-55.466,2.927],[-55.616,-13.258],[-54.921,-22.506],[-54.796,-6.555]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0.55,1.656],[-0.316,7.412],[-0.55,-1.656],[0.316,-7.412]],"o":[[-0.55,-1.657],[0.316,-7.412],[0.55,1.657],[-0.316,7.412]],"v":[[-48.875,2.927],[-49.239,-13.258],[-47.55,-22.506],[-47.247,-6.555]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[1.401,1.656],[-0.804,7.412],[-1.401,-1.656],[0.804,-7.412]],"o":[[-1.401,-1.657],[0.804,-7.412],[1.401,1.657],[-0.804,7.412]],"v":[[-32.013,2.927],[-32.939,-13.258],[-28.639,-22.506],[-27.867,-6.555]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[2.26,1.656],[-1.298,7.412],[-2.26,-1.656],[1.298,-7.412]],"o":[[-2.261,-1.657],[1.298,-7.412],[2.261,1.657],[-1.298,7.412]],"v":[[-15.721,2.927],[-17.217,-13.258],[-10.278,-22.506],[-9.031,-6.555]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[3.029,1.656],[-1.74,7.412],[-3.029,-1.656],[1.739,-7.412]],"o":[[-3.031,-1.657],[1.74,-7.412],[3.03,1.657],[-1.739,7.412]],"v":[[-0.652,2.224],[-2.656,-13.961],[6.643,-23.209],[8.315,-7.258]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[3.489,1.656],[-2.004,7.412],[-3.489,-1.656],[2.004,-7.412]],"o":[[-3.491,-1.657],[2.004,-7.412],[3.491,1.657],[-2.004,7.412]],"v":[[7.86,1.52],[5.551,-14.665],[16.264,-23.912],[18.189,-7.962]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[3.825,1.656],[-2.196,7.412],[-3.825,-1.656],[2.196,-7.412]],"o":[[-3.826,-1.657],[2.196,-7.412],[3.826,1.657],[-2.196,7.412]],"v":[[14.017,1.051],[11.486,-15.134],[23.228,-24.381],[25.339,-8.431]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[4.074,1.656],[-2.339,7.412],[-4.074,-1.656],[2.339,-7.412]],"o":[[-4.076,-1.657],[2.339,-7.412],[4.076,1.657],[-2.339,7.412]],"v":[[18.936,-0.122],[16.24,-16.307],[28.747,-25.555],[30.995,-9.604]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[4.275,1.656],[-2.455,7.412],[-4.275,-1.656],[2.455,-7.412]],"o":[[-4.277,-1.657],[2.455,-7.412],[4.277,1.657],[-2.455,7.412]],"v":[[23.29,-0.122],[20.462,-16.307],[33.587,-25.555],[35.946,-9.604]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[4.505,1.656],[-2.587,7.412],[-4.505,-1.656],[2.587,-7.412]],"o":[[-4.507,-1.657],[2.587,-7.412],[4.507,1.657],[-2.587,7.412]],"v":[[28.274,-0.122],[24.798,-16.542],[37.64,-26.963],[41.116,-10.543]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[4.598,1.656],[-2.64,7.412],[-4.598,-1.656],[2.64,-7.412]],"o":[[-4.6,-1.657],[2.64,-7.412],[4.6,1.657],[-2.64,7.412]],"v":[[30.283,-0.122],[26.736,-16.542],[39.843,-26.963],[43.39,-10.543]],"c":true}]},{"t":135,"s":[{"i":[[4.598,1.656],[-2.64,7.412],[-4.598,-1.656],[2.64,-7.412]],"o":[[-4.6,-1.657],[2.64,-7.412],[4.6,1.657],[-2.64,7.412]],"v":[[30.635,-0.09],[27.088,-16.51],[40.195,-26.931],[43.742,-10.511]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0.226,-1.656],[0.13,7.412],[-0.226,1.656],[-0.13,-7.412]],"o":[[-0.227,1.657],[-0.13,-7.412],[0.227,-1.657],[0.13,7.412]],"v":[[-58.448,-0.292],[-59.074,-9.887],[-58.879,-25.49],[-58.273,-15.539]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0.55,-1.656],[0.316,7.412],[-0.55,1.656],[-0.316,-7.412]],"o":[[-0.55,1.657],[-0.316,-7.412],[0.55,-1.657],[0.316,7.412]],"v":[[-56.118,-0.292],[-57.64,-9.887],[-57.165,-25.49],[-55.694,-15.539]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[1.401,-1.656],[0.804,7.412],[-1.401,1.656],[-0.804,-7.412]],"o":[[-1.401,1.657],[-0.804,-7.412],[1.401,-1.657],[0.804,7.412]],"v":[[-50.453,-0.292],[-54.326,-9.887],[-53.117,-25.49],[-49.372,-15.539]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[2.26,-1.656],[1.298,7.412],[-2.26,1.656],[-1.298,-7.412]],"o":[[-2.261,1.657],[-1.298,-7.412],[2.261,-1.657],[1.298,7.412]],"v":[[-45.475,-0.292],[-51.726,-9.887],[-49.775,-25.49],[-43.732,-15.539]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[3.029,-1.656],[1.739,7.412],[-3.029,1.656],[-1.739,-7.412]],"o":[[-3.03,1.657],[-1.739,-7.412],[3.03,-1.657],[1.739,7.412]],"v":[[-41.476,-0.761],[-49.854,-10.355],[-47.238,-25.959],[-39.139,-16.007]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[3.489,-1.656],[2.004,7.412],[-3.489,1.656],[-2.004,-7.412]],"o":[[-3.491,1.657],[-2.004,-7.412],[3.491,-1.657],[2.004,7.412]],"v":[[-39.166,-0.761],[-48.816,-10.355],[-45.804,-25.959],[-36.474,-16.007]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[3.825,-1.656],[2.196,7.412],[-3.825,1.656],[-2.196,-7.412]],"o":[[-3.826,1.657],[-2.196,-7.412],[3.826,-1.657],[2.196,7.412]],"v":[[-37.786,0.178],[-47.85,-10.355],[-45.32,-25.959],[-34.835,-16.242]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[4.074,-1.656],[2.339,7.412],[-4.074,1.656],[-2.339,-7.412]],"o":[[-4.075,1.657],[-2.339,-7.412],[4.075,-1.657],[2.339,7.412]],"v":[[-36.739,-0.057],[-47.459,-10.59],[-44.763,-26.193],[-33.597,-16.477]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[4.275,-1.656],[2.455,7.412],[-4.275,1.656],[-2.455,-7.412]],"o":[[-4.277,1.657],[-2.455,-7.412],[4.277,-1.657],[2.455,7.412]],"v":[[-35.135,-0.057],[-46.384,-10.59],[-43.555,-26.193],[-31.837,-16.477]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[4.505,-1.656],[2.587,7.412],[-4.505,1.656],[-2.587,-7.412]],"o":[[-4.507,1.657],[-2.587,-7.412],[4.507,-1.657],[2.587,7.412]],"v":[[-33.299,-0.057],[-46.143,-10.478],[-42.667,-26.898],[-29.823,-16.477]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[4.598,-1.656],[2.64,7.412],[-4.598,1.656],[-2.64,-7.412]],"o":[[-4.6,1.657],[-2.64,-7.412],[4.6,-1.657],[2.64,7.412]],"v":[[-32.558,-0.057],[-45.667,-10.478],[-42.12,-26.898],[-29.011,-16.477]],"c":true}]},{"t":135,"s":[{"i":[[4.598,-1.656],[2.64,7.412],[-4.598,1.656],[-2.64,-7.412]],"o":[[-4.6,1.657],[-2.64,-7.412],[4.6,-1.657],[2.64,7.412]],"v":[[-32.206,-0.025],[-45.315,-10.446],[-41.768,-26.866],[-28.659,-16.445]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431391716,0.403921574354,0.305882364511,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[270.996,145.54],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Head","np":4,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[1.662,-1.078],[0.252,-6.5],[0,0],[0,0],[0.006,7.75]],"o":[[-1.809,1.173],[-0.012,11],[0,0],[0,0],[-0.448,-10.219]],"v":[[214.31,197.746],[210.645,231.218],[210.632,265.093],[217.136,244.459],[217.174,217.753]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[4.038,-1.078],[0.613,-6.5],[0,0],[0,0],[0.015,7.75]],"o":[[-4.394,1.173],[-0.03,11],[0,0],[0,0],[-1.088,-10.219]],"v":[[219.157,197.746],[210.255,231.218],[210.225,265.093],[226.024,244.459],[226.116,217.753]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[10.281,-1.078],[1.561,-6.5],[0,0],[0,0],[0.038,7.75]],"o":[[-11.186,1.173],[-0.076,11],[0,0],[0,0],[-2.771,-10.219]],"v":[[231.439,197.746],[208.774,231.218],[208.698,265.093],[248.922,244.459],[249.155,217.753]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[16.589,-1.078],[2.519,-6.5],[0,0],[0,0],[0.061,7.75]],"o":[[-18.049,1.173],[-0.123,11],[0,0],[0,0],[-6.41,-10.783]],"v":[[241.968,198.682],[206.53,231.218],[206.407,265.093],[271.689,247.502],[271.688,217.753]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[22.236,-1.078],[3.376,-6.5],[0,0],[0,0],[0.082,7.75]],"o":[[-24.192,1.173],[-0.165,11],[0,0],[0,0],[-8.592,-10.783]],"v":[[251.88,199.385],[204.38,231.218],[204.215,265.093],[291.799,250.781],[291.744,219.393]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[25.614,-1.078],[3.889,-6.5],[0,0],[0,0],[0.095,7.75]],"o":[[-27.868,1.173],[-0.19,11],[0,0],[0,0],[-9.898,-10.783]],"v":[[257.047,200.557],[202.871,231.218],[202.681,265.093],[303.634,253.83],[303.602,221.269]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[28.076,-1.078],[4.263,-6.5],[0,0],[0,0],[0.104,7.75]],"o":[[-30.546,1.173],[-0.208,11],[0,0],[0,0],[-11.853,-12.5]],"v":[[259.05,201.261],[201.981,231.218],[201.773,265.093],[312.516,256.879],[312.499,222.442]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[29.015,0],[4.541,-6.5],[0,0],[0,0],[0.111,7.75]],"o":[[-32.559,0],[-0.221,11],[0,0],[0,0],[-12.625,-12.5]],"v":[[263.029,201.731],[201.994,231.218],[201.773,265.093],[319.85,258.99],[319.818,224.789]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[30.448,0],[4.765,-6.5],[0,0],[0,0],[0.116,7.75]],"o":[[-34.167,0],[-0.232,11],[0,0],[0,0],[-13.249,-12.5]],"v":[[265.316,202.435],[202.005,231.218],[201.773,265.093],[325.671,261.572],[325.765,226.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[32.089,0],[5.021,-6.5],[0,0],[0,0],[0.122,7.75]],"o":[[-36.008,0],[-0.245,11],[0,0],[0,0],[-13.962,-12.5]],"v":[[268.491,203.374],[202.018,231.218],[201.773,265.093],[332.444,263.215],[332.444,228.779]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[32.75,0],[5.125,-6.5],[0,0],[0,0],[0.125,7.75]],"o":[[-36.75,0],[-0.25,11],[0,0],[0,0],[-14.25,-12.5]],"v":[[269.148,203.843],[202.023,231.218],[201.773,265.093],[335.023,265.093],[335.023,229.718]],"c":true}]},{"t":135,"s":[{"i":[[32.75,0],[5.125,-6.5],[0,0],[0,0],[0.125,7.75]],"o":[[-36.75,0],[-0.25,11],[0,0],[0,0],[-14.25,-12.5]],"v":[[269.5,203.875],[202.375,231.25],[202.125,265.125],[335.375,265.125],[335.375,229.75]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.105865478516,0.450958251953,0.901947021484,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Body","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":86,"op":660,"st":60,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"FRONT_SCREEN 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":93.334,"s":[0]},{"t":101.666015625,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0.25,-9.25],[0,0],[0.25,-0.5],[0,0],[0.5,9.25],[0,0],[-2,-2.5],[0,0]],"o":[[0,0],[0.25,3],[0,0],[-2,2.75],[0,0],[0,-16.016],[0,0],[1.75,3.5]],"v":[[-308,-465.5],[-308,477.25],[-308.25,483],[-356.25,568.25],[-364,573.75],[-364,-560.5],[-354.5,-565],[-310.5,-483]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88.334,"s":[{"i":[[1,-13],[0,0],[5,-5],[0,0],[0,16.016],[0,0],[-18,-17],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-11.25,9],[0,0],[0,-16.016],[0,0],[5.5,4.5]],"v":[[-257,-464],[-257,468],[-270.5,493.062],[-345.5,568.5],[-358.25,553.75],[-357,-542.5],[-338,-559],[-265.5,-485.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89.166,"s":[{"i":[[0.667,-14.005],[0,0],[8.672,-3.333],[0,0],[0,16.016],[0,0],[-17.339,-11.333],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-12.839,6],[0,0],[0,-16.016],[0,0],[9.005,3]],"v":[[-210.667,-462.333],[-210.667,466],[-229.333,495.708],[-334.833,568.667],[-353,549.167],[-352.167,-541.5],[-329.833,-562.167],[-226,-489.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0.333,-15.011],[0,0],[12.344,-1.667],[0,0],[0,16.016],[0,0],[-16.677,-5.667],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-14.427,3],[0,0],[0,-16.016],[0,0],[12.511,1.5]],"v":[[-167.833,-460.667],[-167.833,464],[-191.667,498.354],[-324.167,568.833],[-347.75,544.583],[-347.333,-540.5],[-321.667,-565.333],[-190,-493.833]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-127,-459],[-127,462],[-156,501],[-313.5,569],[-342.5,540],[-342.5,-539.5],[-313.5,-568.5],[-156,-498]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-89.667,-464.667],[-89.667,467.333],[-118.667,503],[-308.667,571.333],[-337.667,542.333],[-337.667,-541.333],[-308.667,-570.333],[-118.667,-500.333]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-54.833,-470.333],[-54.833,472.667],[-83.833,505],[-303.833,573.667],[-332.833,544.667],[-332.833,-543.167],[-303.833,-572.167],[-83.833,-502.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-24,-476],[-24,478],[-53,507],[-299,576],[-328,547],[-328,-545],[-299,-574],[-53,-505]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[6.073,-479.4],[6.073,481.5],[-22.927,510.5],[-297.12,577.1],[-326.12,548.1],[-326.12,-546.2],[-297.12,-575.2],[-22.927,-508.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[32.647,-482.8],[32.647,485],[3.647,514],[-295.24,578.2],[-324.24,549.2],[-324.24,-547.4],[-295.24,-576.4],[3.647,-511.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[57.22,-486.2],[57.22,488.5],[28.22,517.5],[-293.36,579.3],[-322.36,550.3],[-322.36,-548.6],[-293.36,-577.6],[28.22,-515.2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[79.46,-489.6],[79.46,492],[50.46,521],[-291.48,580.4],[-320.48,551.4],[-320.48,-549.8],[-291.48,-578.8],[50.46,-518.6]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[99.7,-493],[99.7,495.5],[70.7,524.5],[-289.6,581.5],[-318.6,552.5],[-318.6,-551],[-289.6,-580],[70.7,-522]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[136.58,-499.8],[136.58,502.5],[107.58,531.5],[-285.84,583.7],[-314.84,554.7],[-314.84,-553.4],[-285.84,-582.4],[107.58,-528.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[179.4,-510],[179.4,513],[150.4,542],[-280.2,587],[-309.2,558],[-309.2,-557],[-280.2,-586],[150.4,-539]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103.334,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[201.28,-516],[201.28,518.8],[172.28,547.8],[-279.44,588.2],[-308.44,559.2],[-308.44,-558],[-279.44,-587],[172.28,-545]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[212.053,-519],[212.053,521.7],[183.053,550.7],[-279.06,588.8],[-308.06,559.8],[-308.06,-558.5],[-279.06,-587.5],[183.053,-548]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[220.827,-522],[220.827,524.6],[191.827,553.6],[-278.68,589.4],[-307.68,560.4],[-307.68,-559],[-278.68,-588],[191.827,-551]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[229.1,-525],[229.1,527.5],[200.1,556.5],[-278.3,590],[-307.3,561],[-307.3,-559.5],[-278.3,-588.5],[200.1,-554]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":109.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[256.46,-535.4],[256.46,537.5],[227.46,566.5],[-276.78,592.4],[-305.78,563.4],[-305.78,-561.5],[-276.78,-590.5],[227.46,-564.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[260.8,-538],[260.8,540],[231.8,569],[-276.4,593],[-305.4,564],[-305.4,-562],[-276.4,-591],[231.8,-567]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[273.82,-543.7],[273.82,546],[244.82,575],[-276.16,594.2],[-305.16,565.2],[-305.16,-562.6],[-276.16,-591.6],[244.82,-572.7]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":114.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[280.833,-547.5],[280.833,550],[251.833,579],[-276,595],[-305,566],[-305,-563],[-276,-592],[251.833,-576.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[294.867,-555],[294.867,558],[265.867,587],[-275.6,597],[-304.6,568],[-304.6,-564],[-275.6,-593],[265.867,-584]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":122.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[301.9,-559.5],[301.9,563],[272.9,592],[-275.7,597],[-304.7,568],[-304.7,-565],[-275.7,-594],[272.9,-588.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[306.933,-564],[306.933,568],[277.933,597],[-275.8,597],[-304.8,568],[-304.8,-566],[-275.8,-595],[277.933,-593]],"c":true}]},{"t":135,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[310,-568],[310,570],[281,599],[-276,599],[-305,570],[-305,-568],[-276,-597],[281,-597]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.815686285496,0.882352948189,0.980392158031,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[384,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[101.75,101.75],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":87,"op":342,"st":60,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"FRONT_SCREEN 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0.25,-9.25],[0,0],[0.25,-0.5],[0,0],[0.5,9.25],[0,0],[-2,-2.5],[0,0]],"o":[[0,0],[0.25,3],[0,0],[-2,2.75],[0,0],[0,-16.016],[0,0],[1.75,3.5]],"v":[[-308,-465.5],[-308,477.25],[-308.25,483],[-356.25,568.25],[-364,573.75],[-364,-560.5],[-354.5,-565],[-310.5,-483]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88.334,"s":[{"i":[[1,-13],[0,0],[5,-5],[0,0],[0,16.016],[0,0],[-18,-17],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-11.25,9],[0,0],[0,-16.016],[0,0],[5.5,4.5]],"v":[[-257,-464],[-257,468],[-270.5,493.062],[-345.5,568.5],[-358.25,553.75],[-357,-542.5],[-338,-559],[-265.5,-485.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89.166,"s":[{"i":[[0.667,-14.005],[0,0],[8.672,-3.333],[0,0],[0,16.016],[0,0],[-17.339,-11.333],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-12.839,6],[0,0],[0,-16.016],[0,0],[9.005,3]],"v":[[-210.667,-462.333],[-210.667,466],[-229.333,495.708],[-334.833,568.667],[-353,549.167],[-352.167,-541.5],[-329.833,-562.167],[-226,-489.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0.333,-15.011],[0,0],[12.344,-1.667],[0,0],[0,16.016],[0,0],[-16.677,-5.667],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-14.427,3],[0,0],[0,-16.016],[0,0],[12.511,1.5]],"v":[[-167.833,-460.667],[-167.833,464],[-191.667,498.354],[-324.167,568.833],[-347.75,544.583],[-347.333,-540.5],[-321.667,-565.333],[-190,-493.833]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-127,-459],[-127,462],[-156,501],[-313.5,569],[-342.5,540],[-342.5,-539.5],[-313.5,-568.5],[-156,-498]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-89.667,-464.667],[-89.667,467.333],[-118.667,503],[-308.667,571.333],[-337.667,542.333],[-337.667,-541.333],[-308.667,-570.333],[-118.667,-500.333]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-54.833,-470.333],[-54.833,472.667],[-83.833,505],[-303.833,573.667],[-332.833,544.667],[-332.833,-543.167],[-303.833,-572.167],[-83.833,-502.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-24,-476],[-24,478],[-53,507],[-299,576],[-328,547],[-328,-545],[-299,-574],[-53,-505]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[6.073,-479.4],[6.073,481.5],[-22.927,510.5],[-297.12,577.1],[-326.12,548.1],[-326.12,-546.2],[-297.12,-575.2],[-22.927,-508.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[32.647,-482.8],[32.647,485],[3.647,514],[-295.24,578.2],[-324.24,549.2],[-324.24,-547.4],[-295.24,-576.4],[3.647,-511.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[57.22,-486.2],[57.22,488.5],[28.22,517.5],[-293.36,579.3],[-322.36,550.3],[-322.36,-548.6],[-293.36,-577.6],[28.22,-515.2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[79.46,-489.6],[79.46,492],[50.46,521],[-291.48,580.4],[-320.48,551.4],[-320.48,-549.8],[-291.48,-578.8],[50.46,-518.6]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[99.7,-493],[99.7,495.5],[70.7,524.5],[-289.6,581.5],[-318.6,552.5],[-318.6,-551],[-289.6,-580],[70.7,-522]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[136.58,-499.8],[136.58,502.5],[107.58,531.5],[-285.84,583.7],[-314.84,554.7],[-314.84,-553.4],[-285.84,-582.4],[107.58,-528.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[179.4,-510],[179.4,513],[150.4,542],[-280.2,587],[-309.2,558],[-309.2,-557],[-280.2,-586],[150.4,-539]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103.334,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[201.28,-516],[201.28,518.8],[172.28,547.8],[-279.44,588.2],[-308.44,559.2],[-308.44,-558],[-279.44,-587],[172.28,-545]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[212.053,-519],[212.053,521.7],[183.053,550.7],[-279.06,588.8],[-308.06,559.8],[-308.06,-558.5],[-279.06,-587.5],[183.053,-548]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[220.827,-522],[220.827,524.6],[191.827,553.6],[-278.68,589.4],[-307.68,560.4],[-307.68,-559],[-278.68,-588],[191.827,-551]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[229.1,-525],[229.1,527.5],[200.1,556.5],[-278.3,590],[-307.3,561],[-307.3,-559.5],[-278.3,-588.5],[200.1,-554]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":109.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[256.46,-535.4],[256.46,537.5],[227.46,566.5],[-276.78,592.4],[-305.78,563.4],[-305.78,-561.5],[-276.78,-590.5],[227.46,-564.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[260.8,-538],[260.8,540],[231.8,569],[-276.4,593],[-305.4,564],[-305.4,-562],[-276.4,-591],[231.8,-567]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[273.82,-543.7],[273.82,546],[244.82,575],[-276.16,594.2],[-305.16,565.2],[-305.16,-562.6],[-276.16,-591.6],[244.82,-572.7]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":114.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[280.833,-547.5],[280.833,550],[251.833,579],[-276,595],[-305,566],[-305,-563],[-276,-592],[251.833,-576.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118.334,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[294.867,-555],[294.867,558],[265.867,587],[-275.6,597],[-304.6,568],[-304.6,-564],[-275.6,-593],[265.867,-584]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":122.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[301.9,-559.5],[301.9,563],[272.9,592],[-275.7,597],[-304.7,568],[-304.7,-565],[-275.7,-594],[272.9,-588.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[306.933,-564],[306.933,568],[277.933,597],[-275.8,597],[-304.8,568],[-304.8,-566],[-275.8,-595],[277.933,-593]],"c":true}]},{"t":135,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[310,-568],[310,570],[281,599],[-276,599],[-305,570],[-305,-568],[-276,-597],[281,-597]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[384,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[101.75,101.75],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":87,"op":342,"st":60,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"CENTER_SCREEN 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[699,-539],[699,539],[653,585],[-0.999,585],[-653,585],[-699,539],[-699,-539],[-653,-585],[1,-585],[653,-585]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":64.166,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[707,-547],[707,547],[661,593],[-0.999,585],[-639.273,577],[-685.273,531],[-685.273,-527],[-639.273,-573],[1,-585],[661,-593]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[715,-563],[715,567],[669,613],[-0.999,585],[-617.545,561],[-663.545,515],[-663.545,-511],[-617.545,-557],[1,-585],[669,-609]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70.834,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[709,-577.4],[709,579],[663,625],[-0.999,585],[-587.709,551.4],[-636.709,505.4],[-636.709,-501.4],[-587.709,-547.4],[1,-585],[663,-623.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":72.5,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[705,-587],[705,587],[659,633],[-0.999,585],[-567.818,545],[-613.818,499],[-613.818,-495],[-567.818,-541],[1,-585],[659,-633]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73.334,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[697.4,-593.4],[697.4,595],[651.4,641],[-0.999,585],[-553.873,539.4],[-599.873,493.4],[-599.873,-490.2],[-553.873,-536.2],[1,-585],[651.4,-639.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74.166,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[693.8,-599.8],[693.8,603],[647.8,649],[-0.999,585],[-537.927,533.8],[-583.927,487.8],[-583.927,-485.4],[-537.927,-531.4],[1,-585],[647.8,-645.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[678.2,-606.2],[678.2,611],[632.2,657],[-0.999,585],[-519.982,528.2],[-565.982,482.2],[-565.982,-480.6],[-519.982,-526.6],[1,-585],[632.2,-652.2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75.834,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[666.6,-612.6],[666.6,619],[620.6,665],[-0.999,585],[-499.036,522.6],[-545.036,476.6],[-545.036,-475.8],[-499.036,-521.8],[1,-585],[620.6,-658.6]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[647,-619],[647,627],[601,673],[-0.999,585],[-478.091,517],[-524.091,471],[-524.091,-471],[-478.091,-517],[1,-585],[601,-665]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78.334,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.223,2.5],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-22.441,-1.8],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[600.891,-638.2],[600.891,649.545],[561.618,691.182],[-5.908,586.273],[-428.856,507],[-467.174,466.273],[-467.174,-463],[-420.674,-508.5],[1,-585.6],[554.891,-684.2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79.166,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.132,3.75],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-20.96,-2.7],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[561.836,-647.8],[561.836,660.818],[525.927,700.273],[-8.363,586.909],[-398.739,502],[-433.216,463.909],[-433.216,-459],[-386.466,-504.25],[1,-585.9],[515.836,-693.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.041,5],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-19.478,-3.6],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[522.782,-657.4],[522.782,672.091],[490.236,709.364],[-10.817,587.546],[-368.621,497],[-399.258,461.546],[-399.258,-455],[-352.258,-500],[1,-586.2],[476.782,-703.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80.834,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-24.95,6.25],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-17.996,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[471.727,-667],[471.727,683.364],[442.545,718.455],[-13.272,588.182],[-332.504,492],[-359.3,459.182],[-359.3,-451],[-312.05,-495.75],[1,-586.5],[425.727,-713]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0.129,-24.588],[0,0],[21.663,1.508],[0,0],[0,0],[0.008,23.224],[0,0],[-21.2,5.875],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-15.329,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[21.8,-2.083]],"v":[[412.352,-679.333],[412.352,694.197],[387.295,726.538],[-16.188,588.015],[-289.754,488.25],[-312.675,457.182],[-312.675,-449],[-272.55,-491.375],[-4.583,-587],[372.894,-720.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82.5,"s":[{"i":[[0.258,-23.77],[0,0],[17.922,3.015],[0,0],[0,0],[0.017,21.043],[0,0],[-17.45,5.5],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-12.663,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[18.194,-4.167]],"v":[[344.977,-691.667],[344.977,705.03],[324.045,734.621],[-19.105,587.849],[-247.004,484.5],[-266.05,455.182],[-266.05,-447],[-233.05,-487],[-10.166,-587.5],[312.06,-728.333]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[0.386,-22.953],[0,0],[14.18,4.523],[0,0],[0,0],[0.025,18.862],[0,0],[-13.7,5.125],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-9.996,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[14.589,-6.25]],"v":[[269.602,-704],[269.602,715.864],[252.795,742.705],[-22.022,587.682],[-204.254,480.75],[-219.425,453.182],[-219.425,-445],[-193.55,-482.625],[-15.75,-588],[243.227,-736]],"c":true}]},{"t":85.833984375,"s":[{"i":[[0.773,-20.5],[0,0],[2.955,9.045],[0,0],[0,0],[0.05,12.318],[0,0],[-2.45,4],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-1.996,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[3.773,-12.5]],"v":[[13.477,-707],[13.477,713.364],[9.045,731.955],[-30.772,587.182],[-58.004,481.5],[-61.55,459.182],[-61.55,-451],[-57.05,-481.5],[-32.5,-581.5],[6.727,-725]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":87,"st":60,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":"FOLDABLE_BODY 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,-40.869],[0,0],[40.869,0],[0,0],[0,0],[0,40.869],[0,0],[-40.869,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[-40.869,0],[0,0],[0,-40.869],[0,0],[0,0],[40.869,0]],"v":[[733,-567],[733,567],[659,641],[5,643],[-659,641],[-733,567],[-733,-567],[-659,-641],[3.001,-641],[659,-641]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,-40.869],[0,0],[40.869,0],[0,0],[0,0],[-0.091,49.495],[0,0],[-40.869,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[-46.341,0.245],[0,0],[-1.795,-49.895],[0,0],[0,0],[40.869,0]],"v":[[748.332,-581.845],[747.391,584.136],[673.391,658.136],[5,644.125],[-640.409,628.505],[-714.409,554.505],[-714.455,-550.855],[-640.455,-627.105],[3.001,-643.25],[674.332,-655.845]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":69.166,"s":[{"i":[[0,-40.869],[0,0],[40.869,0],[0,0],[0,0],[0,40.869],[0,0],[-40.869,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[-40.869,0],[0,0],[0,-40.869],[0,0],[0,0],[40.869,0]],"v":[[755.15,-598.8],[754.8,603],[680.8,677],[5,643],[-610.25,613.05],[-684.25,539.05],[-685.25,-537.4],[-611.25,-611.4],[3.001,-641],[681.15,-672.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70.834,"s":[{"i":[[0,-40.869],[0,0],[42.179,0.978],[0,0],[0,0],[0,40.869],[0,0],[-43.221,1.138],[0,0],[0,0]],"o":[[0,0],[-0.542,42.899],[0,0],[0,0],[-46.299,-2.698],[0,0],[0,-40.869],[0,0],[0,0],[40.869,0]],"v":[[752.228,-610.767],[752.022,614.022],[675.022,688.422],[5,642.2],[-587.372,604.128],[-663.797,530.128],[-665.175,-528.378],[-588.75,-602.378],[3.001,-641],[678.228,-684.767]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73.334,"s":[{"i":[[0,-40.869],[0,0],[44.144,2.444],[0,0],[0,0],[0,40.869],[0,0],[-46.75,2.844],[0,0],[0,0]],"o":[[0,0],[-1.356,45.944],[0,0],[0,0],[-54.444,-6.745],[0,0],[0,-40.869],[0,0],[0,0],[40.869,0]],"v":[[747.844,-628.717],[747.856,630.556],[666.356,705.556],[5,641],[-553.056,590.745],[-625.618,516.745],[-627.562,-514.844],[-555,-588.844],[3.001,-641],[673.844,-702.717]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[0,-40.869],[0,0],[42.507,1.222],[0,0],[0,0],[0,40.869],[0,0],[-50.375,3.922],[0,0],[0,0]],"o":[[0,0],[-0.678,43.407],[0,0],[0,0],[-54.222,-7.622],[0,0],[0,-40.869],[0,0],[0,0],[46.435,-1.875]],"v":[[734.172,-645.858],[734.428,648.778],[650.678,723.278],[5,642.75],[-513.528,581.122],[-590.559,507.372],[-591.531,-504.922],[-514.5,-578.922],[3.001,-641.875],[649.172,-720.233]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,-40.869],[0,0],[40.869,0],[0,0],[0,0],[0,40.869],[0,0],[-54,5],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[-54,-8.5],[0,0],[0,-40.869],[0,0],[0,0],[52,-3.75]],"v":[[708.5,-663],[709,667],[635,741],[5,644.5],[-474,571.5],[-545.5,498],[-545.5,-495],[-474,-569],[3.001,-642.75],[624.5,-737.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79.166,"s":[{"i":[[0.9,-49.498],[0,0],[45.297,5.4],[0,0],[0,0],[0,40.869],[0,0],[-40.05,6.8],[0,0],[0,0]],"o":[[0,0],[0.3,47.208],[0,0],[0,0],[-39.15,-8.5],[0,0],[-0.3,-34.008],[0,0],[0,0],[52.45,-11.925]],"v":[[636.05,-692.05],[634.9,699.675],[545.45,770.9],[-7.675,640.525],[-400.05,552.55],[-455.15,479.65],[-453.6,-480.1],[-404,-549.2],[-14.199,-644.325],[537.55,-767.925]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80.834,"s":[{"i":[[1.5,-55.25],[0,0],[48.25,9],[0,0],[0,0],[0,40.869],[0,0],[-30.75,8],[0,0],[0,0]],"o":[[0,0],[0.5,51.435],[0,0],[0,0],[-29.25,-8.5],[0,0],[-0.5,-29.435],[0,0],[0,0],[52.75,-17.375]],"v":[[549,-717.25],[549.25,723.125],[453.25,792.5],[-16.125,637.875],[-330.75,540.75],[-370.75,480.75],[-372.75,-481],[-331.5,-538.5],[-21.499,-639.125],[448.75,-787.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[2.15,-55.283],[0,0],[43.55,12.75],[0,0],[0,0],[-0.25,36.729],[0,0],[-24.85,7.933],[0,0],[0,0]],"o":[[0,0],[1.8,55.206],[0,0],[0,0],[-23.033,-8.15],[0,0],[-0.6,-27.148],[0,0],[0,0],[50.633,-18.367]],"v":[[493.267,-729.3],[493.367,733.5],[388.433,800.9],[-21.017,638.133],[-291.55,534.233],[-324,473.767],[-324.933,-478.317],[-290.983,-532.1],[-25.783,-636.917],[386.317,-797.433]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82.5,"s":[{"i":[[2.8,-55.317],[0,0],[38.85,16.5],[0,0],[0,0],[-0.5,32.59],[0,0],[-18.95,7.867],[0,0],[0,0]],"o":[[0,0],[3.1,58.978],[0,0],[0,0],[-16.817,-7.8],[0,0],[-0.7,-24.861],[0,0],[0,0],[48.517,-19.358]],"v":[[427.533,-741.35],[427.483,743.875],[323.617,809.3],[-25.908,638.392],[-252.35,527.717],[-277.25,466.783],[-277.117,-475.633],[-250.467,-525.7],[-30.066,-634.708],[323.883,-807.242]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[3.45,-55.35],[0,0],[34.15,20.25],[0,0],[0,0],[-0.75,28.45],[0,0],[-13.05,7.8],[0,0],[0,0]],"o":[[0,0],[4.4,62.75],[0,0],[0,0],[-10.6,-7.45],[0,0],[-0.8,-22.574],[0,0],[0,0],[46.4,-20.35]],"v":[[351.8,-753.4],[351.6,754.25],[258.8,817.7],[-30.8,638.65],[-213.15,521.2],[-230.5,459.8],[-229.3,-472.95],[-209.95,-519.3],[-34.349,-632.5],[261.45,-817.05]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[3.225,-52.425],[0,0],[29.45,24],[0,0],[0,0],[-0.375,34.66],[0,0],[-10.275,9.4],[0,0],[0,0]],"o":[[0,0],[2.7,62.375],[0,0],[0,0],[-7.55,-7.975],[0,0],[-0.9,-20.287],[0,0],[0,0],[45.7,-22.425]],"v":[[269.9,-765.45],[269.55,764.625],[176.9,819.85],[-34.025,634.95],[-163.325,517.6],[-176.25,463.65],[-175.65,-472.975],[-160.475,-516.65],[-39.674,-626.75],[180.475,-821.025]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85,"s":[{"i":[[3,-49.5],[0,0],[24.75,27.75],[0,0],[0,0],[0,40.869],[0,0],[-7.5,11],[0,0],[0,0]],"o":[[0,0],[1,62],[0,0],[0,0],[-4.5,-8.5],[0,0],[-1,-18],[0,0],[0,0],[45,-24.5]],"v":[[182,-777.5],[181.5,775],[95,822],[-37.25,631.25],[-113.5,514],[-122,467.5],[-122,-473],[-111,-514],[-44.999,-621],[99.5,-825]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85.834,"s":[{"i":[[2.115,-52.053],[0,0],[14.076,24.655],[0,0],[0,0],[0.15,39.565],[0,0],[-4.448,8.548],[0,0],[0,0]],"o":[[0,0],[-2.465,27.536],[0,0],[0,0],[-2.458,-8.114],[0,0],[-0.9,-26.528],[0,0],[0,0],[26.131,-22.782]],"v":[[89.385,-789.28],[89.465,814.464],[14.257,825.512],[-39.14,629.206],[-64.364,505.574],[-68.567,459.152],[-66.85,-455.222],[-64.968,-503.548],[-33.506,-639.074],[16.369,-824.885]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0.346,-57.159],[0,0],[-7.272,18.464],[0,0],[0,0],[0.451,36.956],[0,0],[1.656,3.645],[0,0],[0,0]],"o":[[0,0],[0.605,52.109],[0,0],[0,0],[1.626,-7.341],[0,0],[-0.7,-43.583],[0,0],[0,0],[-11.608,-19.345]],"v":[[-77.846,-794.841],[-76.605,801.391],[5.772,824.536],[14.08,628.118],[25.909,512.723],[30.299,466.457],[32.45,-470.667],[16.094,-533.645],[13.482,-630.223],[3.108,-827.655]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0.346,-57.159],[0,0],[-7.272,18.464],[0,0],[0,0],[0.451,36.956],[0,0],[1.656,3.645],[0,0],[0,0]],"o":[[0,0],[0.605,52.109],[0,0],[0,0],[1.626,-7.341],[0,0],[-0.7,-43.583],[0,0],[0,0],[-11.608,-19.345]],"v":[[-161.846,-788.841],[-160.605,797.391],[-78.228,820.536],[14.08,628.118],[75.909,512.723],[80.299,466.457],[82.45,-470.667],[66.094,-533.645],[13.482,-630.223],[-80.892,-821.655]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88.334,"s":[{"i":[[0.679,-54.879],[0,0],[-13.362,17.618],[0,0],[0,0],[0.601,35.652],[0,0],[2.96,5.097],[0,0],[0,0]],"o":[[0,0],[0.139,54.646],[0,0],[0,0],[3.668,-6.954],[0,0],[-0.709,-37.134],[0,0],[0,0],[-28.144,-20.461]],"v":[[-243.346,-774.288],[-241.973,784.854],[-150.304,818.382],[12.523,630.074],[120.379,515.297],[132.565,465.957],[134.267,-469.042],[115.459,-527.361],[14.143,-629.964],[-152.023,-818.206]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89.166,"s":[{"i":[[1.012,-52.598],[0,0],[-19.453,16.773],[0,0],[0,0],[0.752,34.347],[0,0],[4.265,6.548],[0,0],[0,0]],"o":[[0,0],[-0.326,57.182],[0,0],[0,0],[5.71,-6.568],[0,0],[-0.718,-30.686],[0,0],[0,0],[-44.68,-21.576]],"v":[[-317.846,-759.735],[-316.341,772.318],[-229.381,816.227],[10.967,632.03],[164.848,517.871],[180.831,465.457],[182.083,-467.416],[164.824,-521.076],[14.803,-629.705],[-230.153,-814.758]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[1.346,-50.318],[0,0],[-25.543,15.927],[0,0],[0,0],[0.902,33.043],[0,0],[5.57,8],[0,0],[0,0]],"o":[[0,0],[-0.791,59.718],[0,0],[0,0],[7.752,-6.182],[0,0],[-0.727,-24.237],[0,0],[0,0],[-61.216,-22.691]],"v":[[-385.346,-745.182],[-383.709,759.782],[-294.457,814.073],[9.41,633.986],[209.318,520.445],[226.598,464.957],[227.4,-465.791],[214.189,-514.791],[15.464,-629.445],[-294.284,-811.309]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90.834,"s":[{"i":[[1.418,-49.133],[0,0],[-31.384,13.082],[0,0],[0,0],[0.677,35],[0,0],[7.748,7.5],[0,0],[0,0]],"o":[[0,0],[-0.457,57.887],[0,0],[0,0],[9.794,-5.795],[0,0],[-0.682,-25.276],[0,0],[0,0],[-56.377,-19.098]],"v":[[-443.237,-737.295],[-442.077,748.496],[-351.866,806.918],[10.729,633.609],[244.163,523.811],[267.781,467.033],[268.3,-467.923],[248.074,-518.673],[13.874,-629.811],[-352.623,-804.027]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91.666,"s":[{"i":[[1.491,-47.948],[0,0],[-37.224,10.236],[0,0],[0,0],[0.451,36.956],[0,0],[9.927,7],[0,0],[0,0]],"o":[[0,0],[-0.123,56.057],[0,0],[0,0],[11.836,-5.409],[0,0],[-0.636,-26.316],[0,0],[0,0],[-51.538,-15.505]],"v":[[-496.127,-729.409],[-495.445,737.209],[-404.276,799.764],[12.047,633.232],[279.007,527.177],[306.964,469.109],[307.2,-470.055],[281.96,-522.555],[12.285,-630.177],[-405.962,-796.745]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.5,"s":[{"i":[[1.564,-46.762],[0,0],[-43.065,7.391],[0,0],[0,0],[0.226,38.913],[0,0],[12.105,6.5],[0,0],[0,0]],"o":[[0,0],[0.211,54.226],[0,0],[0,0],[13.878,-5.023],[0,0],[-0.591,-27.356],[0,0],[0,0],[-46.699,-11.911]],"v":[[-539.018,-721.523],[-538.814,725.923],[-446.685,792.609],[13.365,632.855],[313.852,530.543],[342.647,471.185],[342.6,-472.186],[315.845,-526.436],[10.695,-630.543],[-449.301,-789.464]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[1.636,-45.577],[0,0],[-48.906,4.545],[0,0],[0,0],[0,40.869],[0,0],[14.283,6],[0,0],[0,0]],"o":[[0,0],[0.545,52.395],[0,0],[0,0],[15.919,-4.636],[0,0],[-0.545,-28.395],[0,0],[0,0],[-41.86,-8.318]],"v":[[-576.909,-713.636],[-577.182,714.636],[-484.094,785.455],[14.684,632.477],[348.697,533.909],[377.33,473.261],[377,-474.318],[349.731,-530.318],[9.106,-630.909],[-487.64,-782.182]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94.166,"s":[{"i":[[1.5,-45.185],[0,0],[-48.199,4.167],[0,0],[0,0],[0,40.869],[0,0],[16.461,5.5],[0,0],[0,0]],"o":[[0,0],[0.5,51.435],[0,0],[0,0],[17.961,-4.25],[0,0],[-0.5,-29.435],[0,0],[0,0],[-41.74,-7.625]],"v":[[-609.5,-705.25],[-609.25,706.583],[-516.07,777.667],[13.048,633.104],[372.373,539.146],[405.552,476.99],[405.333,-478.125],[373.571,-535.625],[8.099,-632],[-520.404,-774.333]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95.834,"s":[{"i":[[1.227,-44.4],[0,0],[-46.785,3.409],[0,0],[0,0],[0,40.869],[0,0],[20.818,4.5],[0,0],[0,0]],"o":[[0,0],[0.409,49.514],[0,0],[0,0],[22.045,-3.477],[0,0],[-0.409,-31.514],[0,0],[0,0],[-41.501,-6.239]],"v":[[-659.682,-688.477],[-658.386,690.477],[-565.023,762.091],[9.776,634.358],[419.724,549.619],[461.997,484.446],[462,-485.739],[421.25,-546.239],[6.087,-634.182],[-570.932,-758.636]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0.955,-43.615],[0,0],[-45.371,2.652],[0,0],[0,0],[0,40.869],[0,0],[25.175,3.5],[0,0],[0,0]],"o":[[0,0],[0.318,47.593],[0,0],[0,0],[26.129,-2.705],[0,0],[-0.318,-33.592],[0,0],[0,0],[-41.261,-4.852]],"v":[[-693.197,-672.705],[-692.189,674.705],[-601.975,746.515],[6.505,635.612],[457.076,558.093],[507.109,489.902],[506.667,-490.686],[457.596,-554.186],[4.075,-633.03],[-608.126,-744.273]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98.334,"s":[{"i":[[0.818,-43.223],[0,0],[-44.664,2.273],[0,0],[0,0],[0,40.869],[0,0],[27.353,3],[0,0],[0,0]],"o":[[0,0],[0.273,46.632],[0,0],[0,0],[28.171,-2.318],[0,0],[-0.273,-34.632],[0,0],[0,0],[-41.142,-4.159]],"v":[[-705.455,-664.818],[-704.591,666.818],[-615.951,738.727],[4.869,636.239],[475.752,562.33],[526.665,492.631],[526,-493.159],[475.769,-558.159],[3.069,-632.455],[-622.224,-737.091]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0.545,-42.438],[0,0],[-43.251,1.515],[0,0],[0,0],[0,40.869],[0,0],[31.71,2],[0,0],[0,0]],"o":[[0,0],[0.182,44.711],[0,0],[0,0],[32.255,-1.545],[0,0],[-0.182,-36.711],[0,0],[0,0],[-40.902,-2.773]],"v":[[-723.303,-651.879],[-722.727,654.212],[-635.57,726.485],[1.598,637.492],[498.104,569.803],[562.443,499.754],[561.667,-498.773],[497.782,-566.773],[1.056,-632.97],[-643.418,-724.727]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[0.409,-42.046],[0,0],[-42.544,1.136],[0,0],[0,0],[0,40.869],[0,0],[33.888,1.5],[0,0],[0,0]],"o":[[0,0],[0.136,43.75],[0,0],[0,0],[34.297,-1.159],[0,0],[-0.136,-37.751],[0,0],[0,0],[-40.782,-2.08]],"v":[[-731.727,-645.409],[-731.295,647.909],[-644.879,720.364],[-0.038,638.119],[509.28,573.54],[577.332,503.315],[576.5,-501.58],[508.788,-571.08],[0.05,-633.227],[-653.516,-718.545]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.666,"s":[{"i":[[0.273,-41.654],[0,0],[-41.837,0.758],[0,0],[0,0],[0,40.869],[0,0],[36.066,1],[0,0],[0,0]],"o":[[0,0],[0.091,42.79],[0,0],[0,0],[36.339,-0.773],[0,0],[-0.091,-38.79],[0,0],[0,0],[-40.663,-1.386]],"v":[[-737.152,-638.939],[-736.864,641.606],[-651.189,714.242],[-1.674,638.746],[520.456,577.277],[590.222,506.877],[589.333,-504.386],[519.795,-575.386],[-0.956,-633.485],[-660.613,-712.364]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103.334,"s":[{"i":[[0,-40.869],[0,0],[-40.423,0],[0,0],[0,0],[0,40.869],[0,0],[40.423,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[40.423,0],[0,0],[0,-40.869],[0,0],[0,0],[-40.423,0]],"v":[[-744,-626],[-744,629],[-659.808,702],[-4.946,640],[542.808,584.75],[616,514],[615,-510],[541.808,-584],[-2.968,-634],[-670.808,-700]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.834,"s":[{"i":[[0,-40.869],[0,0],[-40.423,0],[0,0],[0,0],[0,40.869],[0,0],[40.423,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[40.423,0],[0,0],[0,-40.869],[0,0],[0,0],[-40.423,0]],"v":[[-744.842,-614.959],[-744.6,619.497],[-661.426,691.301],[-2.779,639.354],[564.646,592.557],[643.41,522.301],[642.956,-517.867],[564.192,-591.867],[-2.968,-634.219],[-671.649,-688.959]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":109.166,"s":[{"i":[[0,-40.869],[0,0],[-40.423,0],[0,0],[0,0],[0,40.869],[0,0],[40.423,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[40.423,0],[0,0],[0,-40.869],[0,0],[0,0],[-40.423,0]],"v":[[-745.964,-600.237],[-745.4,606.826],[-663.584,677.035],[0.11,638.493],[593.764,602.967],[672.957,533.369],[673.23,-528.356],[594.037,-602.356],[-2.968,-634.512],[-672.771,-674.237]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[0,-40.869],[0,0],[-40.423,0],[0,0],[0,0],[0,40.869],[0,0],[40.423,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[40.423,0],[0,0],[0,-40.869],[0,0],[0,0],[-40.423,0]],"v":[[-746.806,-589.196],[-746,597.323],[-665.202,666.336],[2.276,637.847],[615.603,610.775],[688.795,541.67],[689.614,-536.222],[616.422,-610.222],[-2.968,-634.731],[-673.613,-663.196]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,-40.869],[0,0],[-46.482,1.895],[0,0],[0,0],[0,40.869],[0,0],[40.423,0],[0,0],[0,0]],"o":[[0,0],[-1,44.868],[0,0],[0,0],[40.423,0],[0,0],[-0.105,-46.5],[0,0],[0,0],[-40.423,0]],"v":[[-736,-575.053],[-735,579.632],[-656.018,651.105],[8.054,637.474],[642.439,625.645],[714.632,557.105],[715.105,-550],[641.913,-624],[-2.968,-635.316],[-662.808,-649.053]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,-40.869],[0,0],[-44.048,1],[0,0],[0,0],[3,43.078],[0,0],[40.423,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[43.733,1],[0,0],[1.328,-42.15],[0,0],[0,0],[-40.423,0]],"v":[[-725.575,-563.749],[-725.75,566.434],[-649.952,640.197],[0.904,637.851],[657.767,633.903],[730.959,560.672],[730.922,-559.6],[657.73,-633.6],[-2.968,-638.442],[-656.133,-637.749]],"c":true}]},{"t":135,"s":[{"i":[[0,-40.869],[0,0],[-40.423,0],[0,0],[0,0],[0,40.869],[0,0],[40.423,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[40.423,0],[0,0],[0,-40.869],[0,0],[0,0],[-40.423,0]],"v":[[-725,-571],[-725.945,569],[-652.753,640],[-5.891,640],[660.862,640],[734.055,569],[735,-571],[661.808,-639],[-2.968,-639],[-651.808,-639]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":386,"st":60,"bm":10}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 3dc85d70..87ce177 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Laat altyd toe van hierdie rekenaar af"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Laat toe"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB-ontfouting word nie toegelaat nie"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Die gebruiker wat tans by hierdie toestel aangemeld is, kan nie USB-ontfouting aanskakel nie. Skakel na die primêre gebruiker toe oor om hierdie kenmerk te gebruik."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Die gebruiker wat tans by hierdie toestel aangemeld is, kan nie USB-ontfouting aanskakel nie. Skakel na ’n admingebruiker toe oor om hierdie kenmerk te gebruik."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Wil jy die stelseltaal na <xliff:g id="LANGUAGE">%1$s</xliff:g> verander?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"\'n Ander toestel het versoek om die stelseltaal te verander"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Verander taal"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Laat altyd toe op hierdie netwerk"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Laat toe"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Draadlose ontfouting word nie toegelaat nie"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Die gebruiker wat tans by hierdie toestel aangemeld is, kan nie draadlose ontfouting aanskakel nie. Skakel na die primêre gebruiker toe oor om hierdie kenmerk te gebruik."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Die gebruiker wat tans by hierdie toestel aangemeld is, kan nie draadlose ontfouting aanskakel nie. Skakel na ’n admingebruiker toe oor om hierdie kenmerk te gebruik."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB-poort is gedeaktiveer"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Die USB-poort is gedeaktiveer om jou toestel teen vloeistowwe en vuilgoed te beskerm en dit sal nie enige bykomstighede bespeur nie.\n\nJy sal ingelig word wanneer die USB-poort weer gebruik kan word."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB-poort is geaktiveer om laaiers en bykomstighede te bespeur"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Stembystand"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-kodeskandeerder"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Ontsluit"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Toestel is gesluit"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Skandeer tans gesig"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Stuur"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Kan nie gesig herken nie"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gebruik eerder vingerafdruk"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth gekoppel."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batterypersentasie is onbekend."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Gekoppel aan <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Vliegtuigmodus."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN aan."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Battery <xliff:g id="NUMBER">%d</xliff:g> persent."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$s</xliff:g> persent, ongeveer <xliff:g id="TIME">%2$s</xliff:g> oor gegrond op jou gebruik"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$d</xliff:g> persent, ongeveer <xliff:g id="TIME">%2$s</xliff:g> oor gegrond op jou gebruik"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Battery laai tans, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Sien alle kennisgewings"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter geaktiveer."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Luitoestel-vibreer."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Bestuur gebruikers"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofoon beskikbaar"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera beskikbaar"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofoon en kamera beskikbaar"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofoon is aangeskakel"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofoon is afgeskakel"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofoon is vir alle apps en dienste geaktiveer."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofoontoegang is vir alle apps en dienste gedeaktiveer. Jy kan mikrofoontoegang aktiveer in Instellings &gt; Privaatheid &gt; Mikrofoon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofoontoegang is vir alle apps en dienste gedeaktiveer. Jy kan dit in Instellings &gt; Privaatheid &gt; Mikrofoon verander."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera is aangeskakel"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera is afgeskakel"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera is vir alle apps en dienste geaktiveer."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kameratoegang is vir alle apps en dienste gedeaktiveer."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Aktiveer mikrofoontoegang in Instellings om die mikrofoonknoppie te gebruik."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Maak instellings oop."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Ander toestel"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Wissel oorsig"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Jy sal nie deur geluide en vibrasies gepla word nie, behalwe deur wekkers, herinneringe, geleenthede en bellers wat jy spesifiseer. Jy sal steeds enigiets hoor wat jy kies om te speel, insluitend musiek, video\'s en speletjies."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Wanneer jy ’n program deel, opneem of uitsaai, het <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> toegang tot enigiets wat in daardie program sigbaar is of daarin gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Gaan voort"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deel of neem ’n program op"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Laat hierdie app toe om te deel of op te neem?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Wanneer jy deel, opneem of uitsaai, het hierdie app toegang tot enigiets wat op jou skerm sigbaar is of op jou toestel gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Wanneer jy ’n app deel, opneem of uitsaai, het hierdie app toegang tot enigiets wat in daardie program sigbaar is of daarin gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Deur jou IT-admin geblokkeer"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skermskote is deur toestelbeleid gedeaktiveer"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Vee alles uit"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Bestuur"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Geskiedenis"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ontsluit om te gebruik"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Kon nie jou kaarte kry nie; probeer later weer"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Sluitskerminstellings"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skandeer QR-kode"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Werkprofiel"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Vliegtuigmodus"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Jy sal nie jou volgende wekker <xliff:g id="WHEN">%1$s</xliff:g> hoor nie"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Skakel weer oor na <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiele data sal nie outomaties op grond van beskikbaarheid oorskakel nie"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nee, dankie"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, skakel oor"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Vergroot die hele skerm"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Vergroot \'n deel van die skerm"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Wissel"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Laat diagonale rollees toe"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Verander grootte"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Verander vergrotingtipe"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Ontdoen"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} kortpad is verwyder}other{# kortpaaie is verwyder}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Verwyder"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tydelik gekoppel"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Swak verbinding"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EE. d MMM."</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Gee <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> toegang tot alle toestelloglêers?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Gee eenmalige toegang"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Moenie toelaat nie"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Toestelloglêers teken aan wat op jou toestel gebeur. Apps kan hierdie loglêers gebruik om kwessies op te spoor en reg te stel.\n\nSommige loglêers bevat dalk sensitiewe inligting, en daarom moet jy toegang tot alle toestelloglêers net gee aan apps wat jy vertrou. \n\nHierdie app het steeds toegang tot sy eie loglêers as jy nie vir hierdie app toegang tot alle toestelloglêers gee nie. Jou toestelvervaardiger het dalk steeds toegang tot sommige loglêers of inligting op jou toestel."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index fb60f9b..7dfc851 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -52,7 +52,8 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"ሁልጊዜ ከዚህ ኮምፒውተር ፍቀድ"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"ፍቀድ"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"የዩኤስቢ እርማት አይፈቀድም"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"አሁን ወደዚህ መሣሪያ የገባው ተጠቃሚ የዩኤስቢ እርማትን ማብራት አይችልም። ይህን ባህሪ ለመጠቀም ወደ ዋና ተጠቃሚ ይቀይሩ።"</string>
+    <!-- no translation found for usb_debugging_secondary_user_message (1888835696965417845) -->
+    <skip />
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"የስርዓት ቋንቋውን ወደ <xliff:g id="LANGUAGE">%1$s</xliff:g> መቀየር ይፈልጋሉ?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"በሌላ መሳሪያ የተጠየቀ የስርዓት ቋንቋ ለውጥ"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"ቋንቋ ቀይር"</string>
@@ -62,7 +63,8 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"ሁልጊዜ በዚህ አውታረ መረብ ላይ ፍቀድ"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"ፍቀድ"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"ገመድ-አልባ debugging አይፈቀድም"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"በአሁኑ ጊዜ በመለያ ወደዚህ መሣሪያ የገባው ተጠቃሚ የገመድ-አልባ ማረምን ማብራት አይችልም። ይህን ባህሪ ለመጠቀም ወደ ዋና ተጠቃሚ ይቀይሩ።"</string>
+    <!-- no translation found for wifi_debugging_secondary_user_message (9085779370142222881) -->
+    <skip />
     <string name="usb_contaminant_title" msgid="894052515034594113">"የዩኤስቢ ወደብ ተሰናክሏል"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"መሣሪያዎን ከፈሳሽ ወይም ፍርስራሽ ለመጠበቅ ሲባል የዩኤስቢ ወደቡ ተሰናክሏል፣ እና ማናቸውም ተቀጥላዎችን አያገኝም።\n\nየዩኤስቢ ወደቡን እንደገና መጠቀም ችግር በማይኖረው ጊዜ ማሳወቂያ ይደርሰዎታል።"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"ኃይል መሙያዎችን እና ተጨማሪ መሣሪያዎችን ፈልጎ ለማግኘት የነቃ የዩኤስቢ ወደብ"</string>
@@ -125,7 +127,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"የድምጽ እርዳታ"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"የQR ኮድ መቃኛ"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"ክፈት"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"መሣሪያ ተቆልፏል"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"የቅኝት ፊት"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ላክ"</string>
@@ -168,6 +171,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"መልክን መለየት አልተቻለም"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"በምትኩ የጣት አሻራን ይጠቀሙ"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ብሉቱዝ ተያይዟል።"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"የባትሪ መቶኛ አይታወቅም።"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"ከ<xliff:g id="BLUETOOTH">%s</xliff:g> ጋር ተገናኝቷል።"</string>
@@ -178,8 +183,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"የአውሮፕላን ሁነታ።"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"ቪፒኤን በርቷል።"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"የባትሪ <xliff:g id="NUMBER">%d</xliff:g> መቶኛ።"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ባትሪ <xliff:g id="PERCENTAGE">%1$s</xliff:g> በመቶ፣ በአጠቃቀምዎ ላይ በመመስረት <xliff:g id="TIME">%2$s</xliff:g> ገደማ ይቀራል"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ባትሪ <xliff:g id="PERCENTAGE">%1$d</xliff:g> በመቶ፣ በአጠቃቀምዎ ላይ በመመስረት <xliff:g id="TIME">%2$s</xliff:g> ገደማ ይቀራል"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ባትሪ ኃይል በመሙላት ላይ፣ <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"ሁሉንም ማሳወቂያዎች ይመልከቱ"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter ነቅቷል።"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"የስልክ ጥሪ ይንዘር።"</string>
@@ -248,7 +257,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>
@@ -303,6 +312,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"ማይክሮፎን አለ"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ካሜራ አለ"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"ማይክሮፎን እና ካሜራ አለ"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"ማይክሮፎን በርቷል"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"ማይክሮፎን ጠፍቷል"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"ማይክሮፎን ለሁሉም መተግበሪያዎች እና አገልግሎቶች ነቅቷል።"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"የማይክሮፎን መዳረሻ ለሁሉም መተግበሪያዎች እና አገልግሎቶች ተሰናክሏል። የማይክሮፎን መዳረሻን በቅንብሮች &gt; ግላዊነት &gt; ማይክሮፎን ውስጥ ማንቃት ይችላሉ።"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"የማይክሮፎን መዳረሻ ለሁሉም መተግበሪያዎች እና አገልግሎቶች ተሰናክሏል። ይህን በቅንብሮች &gt; ግላዊነት &gt; ማይክሮፎን ውስጥ መቀየር ይችላሉ።"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"ካሜራ በርቷል"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ካሜራ ጠፍቷል"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"ካሜራ ለሁሉም መተግበሪያዎች እና አገልግሎቶች ነቅቷል።"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"የካሜራ መዳረሻ ለሁሉም መተግበሪያዎች እና አገልግሎቶች ተሰናክሏል።"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"የማይክሮፎን አዝራርን ለመጠቀም በቅንብሮች ውስጥ የማይክሮፎን መዳረሻን ያንቁ።"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ቅንብሮችን ክፈት።"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"ሌላ መሣሪያ"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"አጠቃላይ እይታን ቀያይር"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"እርስዎ ከወሰንዋቸው ማንቂያዎች፣ አስታዋሾች፣ ክስተቶች እና ደዋዮች በስተቀር፣ በድምጾች እና ንዝረቶች አይረበሹም። ሙዚቃ፣ ቪዲዮዎች እና ጨዋታዎች ጨምሮ ለመጫወት የሚመርጡትን ማንኛውም ነገር አሁንም ይሰማሉ።"</string>
@@ -373,6 +393,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"አንድን መተግበሪያ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> በዚያ መተግበሪያ ላይ ለሚታይ ወይም ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ቀጥል"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"መተግበሪያ ያጋሩ ወይም ይቅረጹ"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"ይህ መተግበሪያ እንዲያጋራ ወይም እንዲቀርጽ ይፈቀድለት?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ ይህ መተግበሪያ በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"አንድን መተግበሪያ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ ይህ መተግበሪያ በዚያ መተግበሪያ ላይ ለሚታይ ወይም ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"በእርስዎ የአይቲ አስተዳዳሪ ታግዷል"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"የማያ ገጽ ቀረጻ በመሣሪያ መመሪያ ተሰናክሏል"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ሁሉንም አጽዳ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ያቀናብሩ"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ታሪክ"</string>
@@ -488,7 +513,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ለማየት ይክፈቱ"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"የእርስዎን ካርዶች ማግኘት ላይ ችግር ነበር፣ እባክዎ ቆይተው እንደገና ይሞክሩ"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"የገጽ መቆለፊያ ቅንብሮች"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR ኮድ ቃኝ"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"የስራ መገለጫ"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"የአውሮፕላን ሁነታ"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"የእርስዎን ቀጣይ ማንቂያ <xliff:g id="WHEN">%1$s</xliff:g> አይሰሙም"</string>
@@ -727,6 +753,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"ወደ <xliff:g id="CARRIER">%s</xliff:g> ተመልሶ ይቀየር?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"የተንቀሳቃሽ ስልክ ውሂብ በተገኝነት መሰረት በራስ ሰር አይቀይርም"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"አይ አመሰግናለሁ"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"አዎ፣ ቀይር"</string>
     <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>
@@ -767,6 +797,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ሙሉ ገጽ እይታን ያጉሉ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"የማያ ገጹን ክፍል አጉላ"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ማብሪያ/ማጥፊያ"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ሰያፍ ሽብለላን ፍቀድ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"መጠን ቀይር"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"የማጉላት አይነትን ለውጥ"</string>
@@ -785,12 +817,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ቀልብስ"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} አቋራጭ ተወግዷል}one{# አቋራጭ ተወግዷል}other{# አቋራጮች ተወግደዋል}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"አስወግድ"</string>
     <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 +968,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"በጊዜያዊነት ተገናኝቷል"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"ደካማ ግንኙነት"</string>
     <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>
@@ -992,4 +1029,12 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE፣ MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <!-- no translation found for log_access_confirmation_title (4843557604739943395) -->
+    <skip />
+    <!-- no translation found for log_access_confirmation_allow (752147861593202968) -->
+    <skip />
+    <!-- no translation found for log_access_confirmation_deny (2389461495803585795) -->
+    <skip />
+    <!-- no translation found for log_access_confirmation_body (6883031912003112634) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index f8567c7..e84402d 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"السماح دائمًا من هذا الكمبيوتر"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"سماح"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"‏لا يُسمح بتصحيح أخطاء USB"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"‏لا يمكن للمستخدم الذي يسجّل دخوله حاليًا إلى هذا الجهاز تفعيل تصحيح الأخطاء USB. لاستخدام هذه الميزة، يمكنك التبديل إلى المستخدم الأساسي."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"‏لا يمكن للمستخدم المسجِّل دخوله حاليًا على هذا الجهاز تفعيل ميزة \"تصحيح أخطاء الجهاز عبر USB\". لاستخدام هذه الميزة، يمكنك التبديل إلى مستخدم مشرف."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"هل تريد تغيير لغة النظام إلى <xliff:g id="LANGUAGE">%1$s</xliff:g>؟"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"طلب جهاز آخر تغيير لغة النظام."</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"تغيير اللغة"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"السماح باستخدام هذه الميزة على هذه الشبكة دائمًا"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"سماح"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"غير مسموح باستخدام ميزة \"تصحيح الأخطاء اللاسلكي\""</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"لا يمكن للمستخدم المسجِّل دخوله حاليًا على هذا الجهاز تفعيل ميزة \"تصحيح الأخطاء اللاسلكي\". لاستخدام هذه الميزة، يمكنك التبديل إلى المستخدم الأساسي."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"لا يمكن للمستخدم المسجِّل دخوله حاليًا على هذا الجهاز تفعيل ميزة \"تصحيح الأخطاء اللاسلكي\". لاستخدام هذه الميزة، يمكنك التبديل إلى مستخدم مشرف."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"‏تمّ إيقاف منفذ USB"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"‏لحماية جهازك من السوائل أو الشوائب، سيتمّ إيقاف منفذ USB ولن يتم رصد أيّ ملحقات.\n\nوسيتمّ إعلامك عندما يُسمح باستخدام منفذ USB مرة أخرى."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"‏تم تفعيل منفذ USB لاكتشاف أجهزة الشحن والملحقات."</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"المساعد الصوتي"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"محفظة"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"الماسح الضوئي لرمز الاستجابة السريعة"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"فتح القفل"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"الجهاز مُقفل."</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"مسح الوجه"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"إرسال"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"يتعذّر التعرّف على الوجه."</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"يمكنك استخدام بصمة إصبعك."</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"تم توصيل البلوتوث."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"نسبة شحن البطارية غير معروفة."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"متصل بـ <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"وضع الطيران."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"‏الشبكة الافتراضية الخاصة (VPN) قيد التفعيل."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"مستوى البطارية <xliff:g id="NUMBER">%d</xliff:g> في المائة."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"نسبة الشحن بالبطارية <xliff:g id="PERCENTAGE">%1$s</xliff:g> بالمائة، ويتبقى <xliff:g id="TIME">%2$s</xliff:g> تقريبًا بناءً على استخدامك."</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"نسبة الشحن بالبطارية <xliff:g id="PERCENTAGE">%1$d</xliff:g> بالمائة، ويتبقى <xliff:g id="TIME">%2$s</xliff:g> تقريبًا بناءً على استخدامك."</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"جارٍ شحن البطارية، <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"الاطّلاع على جميع الإشعارات"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"تم تفعيل المبرقة الكاتبة."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"رنين مع الاهتزاز."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"يمكنك الوصول إلى الميكروفون الآن."</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"يمكنك الوصول إلى الكاميرا الآن."</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"يمكنك الوصول إلى الميكروفون والكاميرا الآن."</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"تم تفعيل الميكروفون"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"تم إيقاف الميكروفون."</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"تم تفعيل الميكروفون لكل التطبيقات والخدمات."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"‏تم إيقاف إمكانية وصول كل التطبيقات والخدمات إلى الميكروفون. يمكنك تفعيل إمكانية الوصول إلى الميكروفون في الإعدادات &gt; الخصوصية &gt; الميكروفون."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"‏تم إيقاف إمكانية وصول كل التطبيقات والخدمات إلى الميكروفون. يمكنك تغيير ذلك في الإعدادات &gt; الخصوصية &gt; الميكروفون."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"تم تفعيل الكاميرا"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"تم إيقاف الكاميرا"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"تم تفعيل الكاميرا لكل التطبيقات والخدمات."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"تم إيقاف إمكانية وصول كل التطبيقات والخدمات إلى الكاميرا."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"لاستخدام زر الميكروفون، عليك تفعيل إمكانية الوصول إلى الميكروفون في \"الإعدادات\"."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"فتح الإعدادات"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"جهاز آخر"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"تبديل \"النظرة العامة\""</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"لن يتم إزعاجك بالأصوات والاهتزاز، باستثناء المُنبِّهات والتذكيرات والأحداث والمتصلين الذين تحددهم. وسيظل بإمكانك سماع أي عناصر أخرى تختار تشغيلها، بما في ذلك الموسيقى والفيديوهات والألعاب."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"أثناء مشاركة محتوى تطبيق أو تسجيله أو بثه، يمكن لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> الوصول إلى كل العناصر المعروضة أو التي يتم تشغيلها في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"متابعة"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"مشاركة محتوى تطبيق أو تسجيله"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"هل تريد السماح لهذا التطبيق بمشاركة المحتوى أو تسجيله؟"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"أثناء مشاركة المحتوى أو تسجيله أو بثه، يمكن لهذا التطبيق الوصول إلى كل العناصر المرئية على شاشتك أو التي يتم تشغيلها على جهازك، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"أثناء مشاركة محتوى تطبيق أو تسجيله أو بثه، يمكن لهذا التطبيق الوصول إلى كل العناصر المعروضة أو التي يتم تشغيلها في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"حظر مشرف تكنولوجيا المعلومات هذه الميزة"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ميزة \"تصوير الشاشة\" غير مفعَّلة بسبب سياسة الجهاز."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"محو الكل"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"إدارة"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"السجلّ"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"فتح القفل للاستخدام"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"حدثت مشكلة أثناء الحصول على البطاقات، يُرجى إعادة المحاولة لاحقًا."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"إعدادات شاشة القفل"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"مسح رمز الاستجابة السريعة ضوئيًا"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"الملف الشخصي للعمل"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"وضع الطيران"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"لن تسمع المنبّه القادم في <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"هل تريد التبديل مرة أخرى إلى \"<xliff:g id="CARRIER">%s</xliff:g>\"؟"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"لن يتم تلقائيًا تبديل بيانات الجوّال بناءً على التوفّر."</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"لا، شكرًا"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"نعم، أريد التبديل"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"تكبير الشاشة كلها"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"تكبير جزء من الشاشة"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"تبديل"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"السماح بالتمرير القطري"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"تغيير الحجم"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"تغيير نوع التكبير"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"تراجع"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{تمت إزالة اختصار واحد ({label}).}zero{تمت إزالة # اختصار.}two{تمت إزالة اختصارَين.}few{تمت إزالة # اختصارات.}many{تمت إزالة # اختصارًا.}other{تمت إزالة # اختصار.}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"إزالة"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"متصلة مؤقتًا"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"الاتصال ضعيف"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"‏EEE،‏ d‏ MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"هل تريد السماح لتطبيق \"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>\" بالوصول إلى جميع سجلّات الجهاز؟"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"السماح بالوصول إلى السجلّ لمرة واحدة"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"عدم السماح"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"ترصد سجلّات الجهاز ما يحدث على جهازك. يمكن أن تستخدم التطبيقات هذه السجلّات لتحديد المشاكل وحلّها.\n\nقد تحتوي بعض السجلّات على معلومات حساسة، ولذلك يجب عدم السماح بالوصول إلى جميع سجلّات الجهاز إلا للتطبيقات التي تثق بها. \n\nإذا لم تسمح بوصول هذا التطبيق إلى جميع سجلّات الجهاز، يظل بإمكان التطبيق الوصول إلى سجلّاته. ويظل بإمكان الشركة المصنِّعة لجهازك الوصول إلى بعض السجلّات أو المعلومات المتوفّرة على جهازك."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 4fca5f2e..ac2eb5a 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"এই কম্পিউটাৰটোৰ পৰা সদায় অনুমতি দিয়ক"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"অনুমতি দিয়ক"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"ইউএছবি ডিবাগিঙৰ অনুমতি নাই"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"এই ডিভাইচটোত বর্তমান ছাইন ইন হৈ থকা ব্যৱহাৰকাৰীজনে ইউএছবি ডিবাগিং অন কৰিব নোৱাৰে। এই সুবিধাটো ব্যৱহাৰ কৰিবলৈ হ\'লে মুখ্য ব্যৱহাৰকাৰী হিচাপে ছাইন ইন কৰক।"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"বৰ্তমান এই ডিভাইচটোত ছাইন ইন হৈ থকা ব্যৱহাৰকাৰীগৰাকীয়ে ইউএছবি ডিবাগিং অন কৰিব নোৱাৰে। এই সুবিধাটো ব্যৱহাৰ কৰিবলৈ, এগৰাকী প্ৰশাসক ব্যৱহাৰকাৰীলৈ সলনি কৰক।"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"আপুনি ছিষ্টেমৰ ভাষা <xliff:g id="LANGUAGE">%1$s</xliff:g>লৈ সলনি কৰিবলৈ বিচাৰেনে?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"অন্য এটা ডিভাইচে ছিষ্টেমৰ ভাষা সলনি কৰাৰ অনুৰোধ কৰিছে"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"ভাষা সলনি কৰক"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"এই নেটৱৰ্কত সদায় অনুমতি দিয়ক"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"অনুমতি দিয়ক"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"ৱায়াৰলেচ ডি\'বাগিংৰ অনুমতি নাই"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"এই ডিভাইচটোত বর্তমান ছাইন ইন হৈ থকা ব্যৱহাৰকাৰীজনে ৱায়াৰলেচ ডি\'বাগিং অন কৰিব নোৱাৰে। এই সুবিধাটো ব্যৱহাৰ কৰিবলৈ হ’লে প্ৰাথমিক ব্যৱহাৰকাৰী হিচাপে ছাইন ইন কৰক।"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"বৰ্তমান এই ডিভাইচটোত ছাইন ইন হৈ থকা ব্যৱহাৰকাৰীগৰাকীয়ে ৱায়াৰলেচ ডি’বাগিং অন কৰিব নোৱাৰে। এই সুবিধাটো ব্যৱহাৰ কৰিবলৈ, এগৰাকী প্ৰশাসক ব্যৱহাৰকাৰীলৈ সলনি কৰক।"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"ইউএছবি প’ৰ্ট অক্ষম কৰা হ’ল"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"আপোনাৰ ডিভাইচটো তৰল বা ধূলি-মাকতিৰ পৰা ৰক্ষা কৰিবলৈ ইউএছবি প’ৰ্টটো অক্ষম কৰি ৰখা হৈছে ফলত ই কোনো আনুষংগিক সামগ্ৰী ধৰা পেলাব নোৱাৰে।\n\nযেতিয়া ইউএছবি প’ৰ্টটো নিৰাপদভাৱে ব্যৱহাৰ কৰিব পৰা হ’ব তেতিয়া আপোনাক জনোৱা হ’ব।"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"চাৰ্জাৰ আৰু আনুষংগিক সামগ্ৰী চিনাক্ত কৰিবলৈ USB প’ৰ্ট সক্ষম কৰা হ’ল"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"কণ্ঠধ্বনিৰে সহায়"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"ৱালেট"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"কিউআৰ ক’ড স্কেনাৰ"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"আনলক কৰক"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"ডিভাইচটো লক হৈ আছে"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"চেহেৰা স্কেন কৰি থকা হৈছে"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"পঠিয়াওক"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"মুখাৱয়ব চিনিব নোৱাৰি"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ইয়াৰ সলনি ফিংগাৰপ্ৰিণ্ট ব্যৱহাৰ কৰক"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ব্লুটুথ সংযোগ হ’ল।"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"বেটাৰীৰ চাৰ্জৰ শতাংশ অজ্ঞাত।"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>ৰ লগত সংযোগ কৰা হ’ল।"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"এয়াৰপ্লে’ন ম’ড।"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"ভিপিএন অন অৱস্থাত আছে।"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> শতাংশ বেটাৰী।"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"আপোনাৰ ব্যৱহাৰৰ ওপৰত ভিত্তি কৰি বেটাৰী <xliff:g id="PERCENTAGE">%1$s</xliff:g> শতাংশ, প্ৰায় <xliff:g id="TIME">%2$s</xliff:g> বাকী আছে"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"আপোনাৰ ব্যৱহাৰৰ ওপৰত ভিত্তি কৰি বেটাৰী <xliff:g id="PERCENTAGE">%1$d</xliff:g> শতাংশ, প্ৰায় <xliff:g id="TIME">%2$s</xliff:g> বাকী আছে"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"বেটাৰী চাৰ্জ হৈ আছে, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> শতাংশ।"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"আটাইবোৰ জাননী চাওক"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter সক্ষম কৰা হ\'ল৷"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ৰিংগাৰ কম্পন অৱস্থাত আছে৷"</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"মাইক্ৰ’ফ’ন উপলব্ধ"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"কেমেৰা উপলব্ধ"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"মাইক্ৰ’ফ’ন আৰু কেমেৰা উপলব্ধ"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"মাইক্ৰ’ফ’ন অন কৰা হ’ল"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"মাইক্ৰ’ফ’ন অফ কৰা হ’ল"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"আটাইবোৰ এপ্ আৰু সেৱাৰ বাবে মাইক্ৰ’ফ’ন সক্ষম কৰা হৈছে।"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"আটাইবোৰ এপ্ আৰু সেৱাৰ বাবে মাইক্ৰ’ফ’নৰ এক্সেছ অক্ষম কৰা হৈছে। আপুনি ছেটিং &gt; গোপনীয়তা &gt; মাইক্ৰ’ফ’নত মাইক্ৰ’ফ’নৰ এক্সেছ সক্ষম কৰিব পাৰে।"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"আটাইবোৰ এপ্ আৰু সেৱাৰ বাবে মাইক্ৰ’ফ’নৰ এক্সেছ অক্ষম কৰা হৈছে। আপুনি এইটো ছেটিং &gt; গোপনীয়তা &gt; মাইক্ৰ’ফ’নত সলনি কৰিব পাৰে।"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"কেমেৰা অন কৰা হ’ল"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"কেমেৰা অফ কৰা হ’ল"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"আটাইবোৰ এপ্ আৰু সেৱাৰ বাবে কেমেৰা সক্ষম কৰা হৈছে।"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"আটাইবোৰ এপ্ আৰু সেৱাৰ বাবে কেমেৰাৰ এক্সেছ অক্ষম কৰা হৈছে।"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"মাইক্ৰ’ফ’নৰ বুটামটো ব্যৱহাৰ কৰিবলৈ, ছেটিঙত মাইক্ৰ’ফ’নৰ এক্সেছ সক্ষম কৰক।"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ছেটিং খোলক।"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"অন্য ডিভাইচ"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"অৱলোকন ট’গল কৰক"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"আপুনি নিৰ্দিষ্ট কৰা এলাৰ্ম, ৰিমাইণ্ডাৰ, ইভেন্ট আৰু কল কৰোঁতাৰ বাহিৰে আন কোনো শব্দৰ পৰা আপুনি অসুবিধা নাপাব। কিন্তু, সংগীত, ভিডিঅ\' আৰু খেলসমূহকে ধৰি আপুনি প্লে কৰিব খোজা যিকোনো বস্তু তথাপি শুনিব পাৰিব।"</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"অব্যাহত ৰাখক"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"এটা এপ্ শ্বেয়াৰ অথবা ৰেকৰ্ড কৰক"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"এই এপ্‌টোক শ্বেয়াৰ অথবা ৰেকৰ্ড কৰিবলৈ অনুমতি দিবনে?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ এই এপ্‌টোৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ এই এপ্‌টোৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"আপোনাৰ আইটি প্ৰশাসকে অৱৰোধ কৰিছে"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ডিভাইচ সম্পৰ্কীয় নীতিয়ে স্ক্ৰীন কেপশ্বাৰ কৰাটো অক্ষম কৰিছে"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"পৰিচালনা"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ব্যৱহাৰ কৰিবলৈ আনলক কৰক"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"আপোনাৰ কাৰ্ড লাভ কৰোঁতে এটা সমস্যা হৈছে, অনুগ্ৰহ কৰি পাছত পুনৰ চেষ্টা কৰক"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"লক স্ক্ৰীনৰ ছেটিং"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"কিউআৰ ক’ড স্কেন কৰক"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"কৰ্মস্থানৰ প্ৰ\'ফাইল"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"এয়াৰপ্লে’ন ম’ড"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"আপুনি আপোনাৰ পিছৰটো এলাৰ্ম <xliff:g id="WHEN">%1$s</xliff:g> বজাত শুনা নাপাব"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"আকৌ <xliff:g id="CARRIER">%s</xliff:g>লৈ সলনি কৰিবনে?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ম’বাইলৰ ডেটা উপলব্ধতাৰ ওপৰত ভিত্তি কৰি স্বয়ংক্ৰিয়ভাৱে সলনি কৰা নহ’ব"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"নালাগে, ধন্যবাদ"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"হয়, সলনি কৰক"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"পূৰ্ণ স্ক্ৰীন বিবৰ্ধন কৰক"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"স্ক্ৰীনৰ কিছু অংশ বিবৰ্ধন কৰক"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ছুইচ"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"কৰ্ণডালৰ দিশত স্ক্ৰ’ল কৰাৰ সুবিধা"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"আকাৰ সলনি কৰক"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"বিবৰ্ধনৰ প্ৰকাৰ সলনি কৰক"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"আনডু কৰক"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} টা শ্বৰ্টকাট আঁতৰোৱা হ’ল}one{# টা শ্বৰ্টকাট আঁতৰোৱা হ’ল}other{# টা শ্বৰ্টকাট আঁতৰোৱা হ’ল}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"আঁতৰাওক"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"অস্থায়ীভাৱে সংযোগ কৰা হৈছে"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"বেয়া সংযোগ"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>ক আটাইবোৰ ডিভাইচৰ লগ এক্সেছ কৰাৰ অনুমতি প্ৰদান কৰিবনে?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"কেৱল এবাৰ এক্সেছ কৰাৰ অনুমতি দিয়ক"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"অনুমতি নিদিব"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"আপোনাৰ ডিভাইচত কি কি ঘটে সেয়া ডিভাইচ লগে ৰেকৰ্ড কৰে। এপ্‌সমূহে সমস্যা বিচাৰিবলৈ আৰু সমাধান কৰিবলৈ এই লগসমূহ ব্যৱহাৰ কৰিব পাৰে।\n\nকিছুমান লগত সংবেদনশীল তথ্য থাকিব পাৰে, গতিকে কেৱল আপুনি বিশ্বাস কৰা এপকহে আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি দিয়ক। \n\nআপুনি যদি এই এপ্‌টোক আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি নিদিয়ে, তথাপিও ই নিজৰ লগসমূহ এক্সেছ কৰিব পাৰিব। আপোনাৰ ডিভাইচৰ নিৰ্মাতাই তথাপিও হয়তো আপোনাৰ ডিভাইচটোত থকা কিছু লগ অথবা তথ্য এক্সেছ কৰিব পাৰিব।"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 7fa603f..2b15990 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Bu kompüterdən həmişə icazə verilsin"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"İcazə verin"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB ilə sazlama qadağandır"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Hazırda bu cihaza daxil olmuş istifadəçi USB sazlama prosesini aktiv edə bilməz. Bu funksiyadan istifadə etmək üçün əsas istifadəçi hesaba daxil olmalıdır."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Hazırda bu cihaza daxil olmuş istifadəçi USB sazlama prosesini aktiv edə bilməz. Bu funksiyadan istifadə etmək üçün admin istifadəçiyə keçin."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Sistem dili <xliff:g id="LANGUAGE">%1$s</xliff:g> dilinə dəyişdirilsin?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Sistem dilinin dəyişdirilməsi başqa cihaz tərəfindən tələb olunur"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Dili dəyişin"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Bu şəbəkədə həmişə icazə verilsin"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"İcazə verin"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"WiFi sazlamasına icazə verilmir"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Hazırda bu cihaza daxil olmuş istifadəçi WiFi sazlamasını aktiv edə bilmir. Bu funksiyadan istifadə etmək üçün əsas istifadəçiyə keçin."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Hazırda bu cihaza daxil olmuş istifadəçi WiFi sazlamasını aktiv edə bilmir. Bu funksiyadan istifadə etmək üçün admin istifadəçiyə keçin."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB portu deaktiv edildi"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"USB portu deaktivdir. Cihazı maye və zərbədən qorumaq üçün aksesuar aşkarlanmayacaq.\n\nUSB portu yenidən istifadə üçün təhlükəsiz olduqda bildiriş göndəriləcək."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Adapter və aksesuarları aşkarlamaq üçün USB portu aktiv edildi"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Səs Yardımçısı"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Pulqabı"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR Kodu Skaneri"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Kiliddən çıxarın"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Cihaz kilidlənib"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Üzün skan edilməsi"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Göndərin"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Üzü tanımaq olmur"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Barmaq izi istifadə edin"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth qoşulub."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batareyanın faizi naməlumdur."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> üzərindən qoşuldu."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Uçuş rejimi"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN aktivdir."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Batareya <xliff:g id="NUMBER">%d</xliff:g> faizdir."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batareya <xliff:g id="PERCENTAGE">%1$s</xliff:g> faizdir, istifadəyə əsasən təxminən <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batareya <xliff:g id="PERCENTAGE">%1$d</xliff:g> faizdir, istifadəyə əsasən təxminən <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batareya doldurulur, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%% faiz."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Bütün bildirişlərə baxın"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter aktivləşdirilib."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Zəng vibrasiyası"</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"İstifadəçiləri idarə edin"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon əlçatandır"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera əlçatandır"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon və kamera əlçatandır"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon aktiv edilib"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon deaktiv edilib"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon bütün tətbiqlər və xidmətlər üçün aktiv edilib."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofon girişi bütün tətbiqlər və xidmətlər üçün deaktiv edilib. Mikrofon girişini Ayarlar &gt; Məxfilik &gt; Mikrofon bölməsində aktiv edə bilərsiniz."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofon girişi bütün tətbiqlər və xidmətlər üçün deaktiv edilib. Bunu Ayarlar &gt; Məxfilik &gt; Mikrofon bölməsində dəyişə bilərsiniz."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera aktivdir"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera deaktivdir"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera bütün tətbiqlər və xidmətlər üçün aktiv edilib."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kamera girişi bütün tətbiqlər və xidmətlər üçün deaktiv edilib."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Mikrofon düyməsini istifadə etmək üçün Ayarlarda mikrofona girişi aktiv edin."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Ayarları açın."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Digər cihaz"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"İcmala Keçin"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Seçdiyiniz siqnal, xatırladıcı, tədbir və zənglər istisna olmaqla səslər və vibrasiyalar Sizi narahat etməyəcək. Musiqi, video və oyunlar da daxil olmaqla oxutmaq istədiyiniz hər şeyi eşidəcəksiniz."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Paylaşdığınız, qeydə aldığınız və ya yayımladığınız zaman <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tətbiqi həmin tətbiqdə göstərilən və ya oxudulan hər şeyə giriş edə bilir. Odur ki, parollar, ödəniş detalları, mesajlar və ya digər həssas məlumatlarla bağlı diqqətli olun."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Davam edin"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Tətbiqi paylaşın və ya qeydə alın"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Bu tətbiqə paylaşmağa və ya yazmağa icazə verilsin?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Paylaşdığınız, yazdığınız və ya yayımladığınız zaman bu tətbiq ekranınızda görünən və ya cihazınızda oxudulan hər şeyə giriş edə bilir. Odur ki, parollar, ödəniş detalları, mesajlar və ya digər həssas məlumatlarla bağlı diqqətli olun."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Paylaşdığınız, qeydə aldığınız və ya yayımladığınız zaman bu tətbiq həmin tətbiqdə göstərilən və ya oxudulan hər şeyə giriş edə bilir. Odur ki, parollar, ödəniş detalları, mesajlar və ya digər həssas məlumatlarla bağlı diqqətli olun."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"İT admininiz tərəfindən bloklanıb"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekran çəkimi cihaz siyasəti ilə deaktiv edilib"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hamısını silin"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"İdarə edin"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Tarixçə"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"İstifadə etmək üçün kiliddən çıxarın"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Kartların əldə edilməsində problem oldu, sonra yenidən cəhd edin"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Kilid ekranı ayarları"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR kodu skanlayın"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"İş profili"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Təyyarə rejimi"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> zaman növbəti xəbərdarlığınızı eşitməyəcəksiniz"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> operatoruna keçirilsin?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobil data əlçatımlıq əsasında avtomatik olaraq keçirilməyəcək"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Xeyr, təşəkkürlər"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Bəli, keçirin"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Tam ekranı böyüdün"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekran hissəsinin böyüdülməsi"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Dəyişdirici"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Diaqonal sürüşdürməyə icazə verin"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ölçüsünü dəyişin"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Böyütmə növünü dəyişdirin"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Geri qaytarın"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} qısayol silindi}other{# qısayol silindi}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Silin"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Müvəqqəti qoşulub"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Zəif bağlantı"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"HHH, AAA g"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"s:dd"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"ss:dd"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> tətbiqinin bütün cihaz qeydlərinə girişinə icazə verilsin?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Birdəfəlik girişə icazə verin"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"İcazə verməyin"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Cihaz qeydləri cihazınızda baş verənləri qeyd edir. Tətbiqlər problemləri tapmaq və həll etmək üçün bu qeydlərdən istifadə edə bilər.\n\nBəzi qeydlərdə həssas məlumatlar ola bilər, ona görə də yalnız etibar etdiyiniz tətbiqlərin bütün cihaz qeydlərinə giriş etməsinə icazə verin. \n\nBu tətbiqin bütün cihaz qeydlərinə girişinə icazə verməsəniz, o, hələ də öz qeydlərinə giriş edə bilər. Cihaz istehsalçınız hələ də cihazınızda bəzi qeydlərə və ya məlumatlara giriş edə bilər."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index ffe49b2..17d7ffa 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Uvek dozvoli sa ovog računara"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Dozvoli"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Otklanjanje grešaka na USB-u nije dozvoljeno"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Korisnik koji je trenutno prijavljen na ovaj uređaj ne može da uključi otklanjanje grešaka na USB-u. Da biste koristili ovu funkciju, prebacite na primarnog korisnika."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Korisnik koji je trenutno prijavljen na ovaj uređaj ne može da uključi otklanjanje grešaka sa USB-a. Da biste koristili ovu funkciju, pređite na korisnika sa administratorskim pravima."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Da li želite da promenite jezik sistema na <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Drugi uređaj je zatražio promenu jezika sistema"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Promeni jezik"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Uvek dozvoli na ovoj mreži"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Dozvoli"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Bežično otklanjanje grešaka nije dozvoljeno"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Korisnik koji je trenutno prijavljen na ovaj uređaj ne može da uključi bežično otklanjanje grešaka. Da biste koristili ovu funkciju, pređite na primarnog korisnika."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Korisnik koji je trenutno prijavljen na ovaj uređaj ne može da uključi bežično otklanjanje grešaka. Da biste koristili ovu funkciju, pređite na korisnika sa administratorskim pravima."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB port je onemogućen"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Da bi se uređaj zaštitio od tečnosti ili nečistoće, USB port je onemogućen i neće otkrivati dodatnu opremu.\n\nObavestićemo vas kada ponovo budete mogli da koristite USB port."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB port je omogućen radi otkrivanja punjača i dodatne opreme"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Glasovna pomoć"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Novčanik"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Skener QR koda"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Otključajte"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Uređaj je zaključan"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Skeniranje lica"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Pošalji"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Lice nije prepoznato"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Koristite otisak prsta"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth je priključen."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procenat napunjenosti baterije nije poznat."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezani ste sa <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Režim rada u avionu."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN je uključen."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Baterija je na <xliff:g id="NUMBER">%d</xliff:g> posto."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterija je na <xliff:g id="PERCENTAGE">%1$s</xliff:g> posto, preostalo vreme na osnovu korišćenja je <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterija je na <xliff:g id="PERCENTAGE">%1$d</xliff:g> posto, preostalo vreme na osnovu korišćenja je <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Baterija se puni, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> posto."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Pogledajte sva obaveštenja"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter je omogućen."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibracija zvona."</string>
@@ -248,7 +255,7 @@
     <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>
+    <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">"Povezan"</string>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon je dostupan"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera je dostupna"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon i kamera su dostupni"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon je uključen"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon je isključen"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon je omogućen za sve aplikacije i usluge."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Pristup mikrofonu je onemogućen za sve aplikacije i usluge. Možete da omogućite pristup mikrofonu u Podešavanjima &gt; Privatnost &gt; Mikrofon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Pristup mikrofonu je onemogućen za sve aplikacije i usluge. To možete da promenite u Podešavanjima &gt; Privatnost &gt; Mikrofon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera je uključena"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera je isključena"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera je omogućena za sve aplikacije i usluge."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Pristup kameri je onemogućen za sve aplikacije i usluge."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Da biste koristili dugme mikrofona, omogućite pristup mikrofonu u Podešavanjima."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Otvori Podešavanja."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Drugi uređaj"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Uključi/isključi pregled"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Neće vas uznemiravati zvukovi i vibracije osim za alarme, podsetnike, događaje i pozivaoce koje navedete. I dalje ćete čuti sve što odaberete da pustite, uključujući muziku, video snimke i igre."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kada delite, snimate ili prebacujete aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Budite pažljivi sa lozinkama, informacijama o plaćanju, porukama ili drugim osetljivim informacijama."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Nastavi"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Delite ili snimite aplikaciju"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Želite da dozvolite ovoj aplikaciji da deli ili snima?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kada delite, snimate ili prebacujete, ova aplikacija ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Budite pažljivi sa lozinkama, informacijama o plaćanju, porukama ili drugim osetljivim informacijama."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kada delite, snimate ili prebacujete aplikaciju, ova aplikacija ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Budite pažljivi sa lozinkama, informacijama o plaćanju, porukama ili drugim osetljivim informacijama."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokira IT administrator"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje ekrana je onemogućeno smernicama za uređaj"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Istorija"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Otključaj radi korišćenja"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Došlo je do problema pri preuzimanju kartica. Probajte ponovo kasnije"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Podešavanja zaključanog ekrana"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skenirajte QR kôd"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Poslovni profil"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Režim rada u avionu"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sledeći alarm u <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Želite da se vratite na mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilni podaci se neće automatski promeniti na osnovu dostupnosti"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, hvala"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, pređi"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Uvećajte ceo ekran"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Uvećajte deo ekrana"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Pređi"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Dozvoli dijagonalno skrolovanje"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Promeni veličinu"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Promeni tip uvećanja"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Opozovite"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} prečica je uklonjena}one{# prečica je uklonjena}few{# prečice su uklonjene}other{# prečica je uklonjeno}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Uklonite"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Privremeno povezano"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Veza je loša"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"DDD, d. MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"s:min"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"č:min"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Želite da dozvolite da <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> pristupa svim evidencijama uređaja?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Dozvoli jednokratan pristup"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Ne dozvoli"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Evidencije uređaja registruju šta se dešava na uređaju. Aplikacije mogu da koriste te evidencije da bi pronašle i rešile probleme.\n\nNeke evidencije mogu da sadrže osetljive informacije, pa pristup svim evidencijama uređaja treba da dozvoljavate samo aplikacijama u koje imate poverenja. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim evidencijama uređaja, ona i dalje može da pristupa sopstvenim evidencijama. Proizvođač uređaja će možda i dalje moći da pristupa nekim evidencijama ili informacijama na uređaju."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 3e76985..497f8c0 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Заўсёды дазваляць з гэтага камп\'ютара"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Дазволіць"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Адладка па USB забаронена"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Карыстальнік, які зараз увайшоў у гэту прыладу, не можа ўключыць адладку па USB. Каб выкарыстоўваць гэту функцыю, пераключыцеся на асноўнага карыстальніка."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Карыстальнік, які зараз увайшоў у гэтую прыладу, не можа ўключыць адладку цераз USB. Каб выкарыстоўваць гэтую функцыю, пераключыцеся на адміністратара."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Змяніць мову сістэмы на наступную: \"<xliff:g id="LANGUAGE">%1$s</xliff:g>\"?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Іншая прылада запытала змяненне мовы сістэмы"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Змяніць мову"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Заўсёды дазваляць у гэтай сетцы"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Дазволіць"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Адладка па Wi-Fi не дазволена"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Карыстальнік, які зараз увайшоў у гэту прыладу, не можа ўключыць адладку па Wi-Fi. Каб выкарыстоўваць гэту функцыю, пераключыцеся на асноўнага карыстальніка."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Карыстальнік, які зараз увайшоў у гэтую прыладу, не можа ўключыць адладку па Wi-Fi. Каб выкарыстоўваць гэтую функцыю, пераключыцеся на адміністратара."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Порт USB адключаны"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Порт USB адключаны, каб засцерагчы прыладу ад вадкасці і смецця, таму дадатковае абсталяванне не будзе выяўлена.\n\nВы атрымаеце апавяшчэнне, калі порт USB можна будзе выкарыстоўваць зноў."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB-порту дазволена вызначаць зарадныя прылады і аксесуары"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Галасавая дапамога"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Кашалёк"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Сканер QR-кодаў"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Разблакiраваць"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Прылада заблакіравана"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Сканіраванне твару"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Адправіць"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Твар не распазнаны"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Скарыстайце адбітак пальца"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-сувязь."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Працэнт зараду акумулятара невядомы."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Падлучаны да <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Рэжым палёту."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN уключана."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Працэнт зараду акумулятара: <xliff:g id="NUMBER">%d</xliff:g>."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Зарад акумулятара ў працэнтах: <xliff:g id="PERCENTAGE">%1$s</xliff:g>. Пры такім выкарыстанні яго хопіць прыблізна на <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Зарад акумулятара ў працэнтах: <xliff:g id="PERCENTAGE">%1$d</xliff:g>. Пры такім выкарыстанні яго хопіць прыблізна на <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Акумулятар зараджаецца. Бягучы зарад: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Паказаць усе апавяшчэнні"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter уключаны."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Выклік з вібрацыяй."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Мікрафон можна выкарыстоўваць"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камеру можна выкарыстоўваць"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Мікрафон і камеру можна выкарыстоўваць"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Мікрафон уключаны"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Мікрафон выключаны"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Мікрафон уключаны для ўсіх праграм і сэрвісаў."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Доступ да мікрафона адключаны для ўсіх праграм і сэрвісаў. Вы можаце даць доступ да мікрафона праз меню \"Налады &gt; Прыватнасць &gt; Мікрафон\"."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Доступ да мікрафона адключаны для ўсіх праграм і сэрвісаў. Гэту наладу можна змяніць праз меню \"Налады &gt; Прыватнасць &gt; Мікрафон\"."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камера ўключана"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камера выключана"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Камера ўключана для ўсіх праграм і сэрвісаў."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Доступ да камеры адключаны для ўсіх праграм і сэрвісаў."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Каб выкарыстоўваць кнопку мікрафона, дайце доступ да мікрафона ў Наладах."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Адкрыць налады."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Іншая прылада"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Уключыць/выключыць агляд"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Вас не будуць турбаваць гукі і вібрацыя, за выключэннем будзільнікаў, напамінаў, падзей і выбраных вамі абанентаў. Вы будзеце чуць усё, што ўключыце, у тым ліку музыку, відэа і гульні."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Калі пачынаецца абагульванне, запіс ці трансляцыя змесціва праграмы, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> атрымлівае доступ да ўсяго змесціва, якое паказваецца ці прайграецца ў праграме. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў і іншай канфідэнцыяльнай інфармацыі."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Далей"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Абагульванне або запіс праграмы"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Дазволіць гэтай праграме абагульваць або запісваць змесціва?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Калі пачынаецца абагульванне, запіс ці трансляцыя, гэта праграма атрымлівае доступ да ўсяго змесціва, якое паказваецца на экране ці прайграецца на прыладзе. Таму прадухіліце паказ пароляў, звестак пра плацяжы, паведамленняў і іншай канфідэнцыяльнай інфармацыі."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Калі пачынаецца абагульванне, запіс ці трансляцыя змесціва праграмы, гэта праграма атрымлівае доступ да ўсяго змесціва, якое ў ёй паказваецца ці прайграецца. Таму прадухіліце паказ пароляў, звестак пра плацяжы, паведамленняў і іншай канфідэнцыяльнай інфармацыі."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблакіравана вашым ІТ-адміністратарам"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Здыманне экрана адключана згодна з палітыкай прылады"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Ачысціць усё"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Кіраваць"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Гісторыя"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Разблакіраваць для выкарыстання"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Узнікла праблема з загрузкай вашых карт. Паўтарыце спробу пазней"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Налады экрана блакіроўкі"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Сканіраванне QR-кода"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Працоўны профіль"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Рэжым палёту"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Вы не пачуеце наступны будзільнік <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Зноў пераключыцца на аператара \"<xliff:g id="CARRIER">%s</xliff:g>\"?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мабільны інтэрнэт не будзе аўтаматычна пераключацца ў залежнасці ад даступнасці"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Не, дзякуй"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Так, пераключыцца"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Павялічыць увесь экран"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Павялічыць частку экрана"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Пераключальнік"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Дазволіць прагортванне па дыяганалі"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Змяніць памер"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Змяніць тып павелічэння"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Адрабіць"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Выдалены {label} ярлык}one{Выдалены # ярлык}few{Выдалена # ярлыкі}many{Выдалена # ярлыкоў}other{Выдалена # ярлыка}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Выдаліць"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Падключана часова"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Нестабільнае падключэнне"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Дазволіць праграме \"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>\" мець доступ да ўсіх журналаў прылады?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Дазволіць аднаразовы доступ"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Не дазваляць"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Журналы прылад запісваюць усё, што адбываецца на вашай прыладзе. Праграмы выкарыстоўваюць гэтыя журналы для пошуку і выпраўлення памылак.\n\nУ некаторых журналах можа ўтрымлівацца канфідэнцыяльная інфармацыя, таму дазваляйце доступ да ўсіх журналаў прылады толькі тым праграмам, якім вы давяраеце. \n\nКалі вы не дасцё гэтай праграме доступу да ўсіх журналаў прылад, у яе ўсё роўна застанецца доступ да ўласных журналаў. Для вытворцы вашай прылады будуць даступнымі некаторыя журналы і інфармацыя на вашай прыладзе."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 572e13f..0f45de7 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Винаги да се разрешава от този компютър"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Разрешаване"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Отстраняването на грешки през USB не е разрешено"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Потребителят, който понастоящем е влязъл в това устройство, не може да включи функцията за отстраняване на грешки през USB. За да я използвате, превключете към основния потребител."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Потребителят, който понастоящем е влязъл в това устройство, не може да включи функцията за отстраняване на грешки през USB. За да я използвате, превключете към потребител с администраторски достъп."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Искате ли да промените езика на системата на <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Друго устройство е заявило промяна на езика на системата"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Промяна на езика"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Винаги да се разрешава в тази мрежа"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Разрешаване"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Безжичното отстраняване на грешки не е разрешено"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Потребителят, който понастоящем е влязъл в това устройство, не може да включи функцията за безжично отстраняване на грешки. За да използвате, превключете към основния потребител."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Потребителят, който понастоящем е влязъл в това устройство, не може да включи функцията за безжично отстраняване на грешки. За да я използвате, превключете към потребител с администраторски достъп."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB портът е деактивиран"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"С цел защита на устройството ви от течности и замърсяване USB портът е деактивиран и няма да открива аксесоари.\n\nЩе получите известие, когато можете отново да използвате USB порта."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB портът може да разпознава зарядни устройства и аксесоари"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Гласова помощ"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Портфейл"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Скенер за QR кодове"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Отключване"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Устройството е заключено"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Извършва се сканиране на лице"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Изпращане"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Лицето не е разпознато"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Използвайте отпечатък"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth е включен."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Процентът на батерията е неизвестен."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Има връзка с <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Самолетен режим."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"Функцията за виртуална частна мрежа (VPN) е включена."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> процента батерия."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батерията е на <xliff:g id="PERCENTAGE">%1$s</xliff:g> процента. Още около <xliff:g id="TIME">%2$s</xliff:g> въз основа на използването"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батерията е на <xliff:g id="PERCENTAGE">%1$d</xliff:g> процента. Още около <xliff:g id="TIME">%2$s</xliff:g> въз основа на използването"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Батерията се зарежда, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Вижте всички известия"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter бе активиран."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Вибрира при звънене."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Микрофонът е налице"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камерата е налице"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Микрофонът и камерата са налице"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Микрофонът е включен"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Микрофонът е изключен"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Достъпът до микрофона е активиран за всички приложения и услуги."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Достъпът до микрофона е деактивиран за всички приложения и услуги. Можете да го активирате от „Настройки &gt; Поверителност &gt; Микрофон“."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Достъпът до микрофона е деактивиран за всички приложения и услуги. Можете да промените това от „Настройки &gt; Поверителност &gt; Микрофон“."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камерата е включена"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камерата е изключена"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Достъпът до камерата е активиран за всички приложения и услуги."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Достъпът до камерата е деактивиран за всички приложения и услуги."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Активирайте достъпа до микрофона, за да използвате съответния бутон."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Отваряне на настройките."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Друго устройство"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Превключване на общия преглед"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Няма да бъдете обезпокоявани от звуци и вибрирания освен от будилници, напомняния, събития и обаждания от посочени от вас контакти. Пак ще чувате всичко, което изберете да се пусне, включително музика, видеоклипове и игри."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когато споделяте, записвате или предавате, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има достъп до всичко, което се показва или възпроизвежда в това приложение, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Напред"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Споделяне или записване на приложение"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Да се разреши ли на това приложение да споделя или записва?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Когато споделяте, записвате или предавате, това приложение има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Когато споделяте, записвате или предавате, това приложение има достъп до всичко, което се показва или възпроизвежда в него, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано от системния ви администратор"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Заснемането на екрана е деактивирано от правило за устройството"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Управление"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Отключване с цел използване"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"При извличането на картите ви възникна проблем. Моля, опитайте отново по-късно"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Настройки за заключения екран"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR код: сканиране"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Потребителски профил в Work"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Самолетен режим"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Няма да чуете следващия си будилник в <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Искате ли да се върнете към <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мрежата за мобилни данни няма да се превключва автоматично въз основа на наличността"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Не, благодаря"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Да, превключване"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Увеличаване на целия екран"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увеличаване на част от екрана"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Превключване"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Разрешаване на диагонално превъртане"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Преоразмеряване"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Промяна на типа увеличение"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Отмяна"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} пряк път бе премахнат}other{# преки пътища бяха премахнати}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Премахване"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Установена е временна връзка"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Слаба връзка"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Да се разреши ли на <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> достъп до всички регистрационни файлове за устройството?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Разрешаване на еднократен достъп"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Забраняване"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"В регистрационните файлове за устройството се записва какво се извършва на него. Приложенията могат да използват тези регистрационни файлове, за да откриват и отстраняват проблеми.\n\nНякои регистрационни файлове за устройството може да съдържат поверителна информация, затова разрешавайте достъп до всички тях само на приложения, на които имате доверие. \n\nАко не разрешите на това приложение достъп до всички регистрационни файлове за устройството, то пак може да осъществява достъп до собствените си регистрационни файлове. Възможно е производителят на устройството да продължи да има достъп до някои регистрационни файлове или информация на устройството ви."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 645f6ab..5ec4028 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"এই কম্পিউটার থেকে সর্বদা অনুমতি দিন"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"অনুমতি দিন"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB ডিবাগিং অনুমোদিত নয়"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"ব্যবহারকারী এখন এই ডিভাইসে সাইন-ইন করেছেন তাই USB ডিবাগিং চালু করা যাবে না। এই বৈশিষ্ট্যটি ব্যবহার করতে, প্রাথমিক ব্যবহারকারীতে পাল্টে নিন।"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"বর্তমানে এই ডিভাইসে সাইন-ইন করে থাকা ব্যবহারকারী \'USB ডিবাগিং\' চালু করতে পারবেন না। এই ফিচার ব্যবহার করতে, একজন অ্যাডমিন ব্যবহারকারী হিসেবে সাইন-ইন করুন।"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"আপনি কি সিস্টেমের ভাষা পরিবর্তন করে <xliff:g id="LANGUAGE">%1$s</xliff:g> করতে চান?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"অন্য ডিভাইসের দ্বারা সিস্টেমের ভাষা পরিবর্তনের অনুরোধ করা হয়েছে"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"ভাষা পরিবর্তন করুন"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"এই নেটওয়ার্কে সবসময় অনুমতি দিন"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"অনুমতি দিন"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"ওয়্যারলেস ডিবাগিং করা যাবে না"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"ব্যবহারকারী এখন এই ডিভাইসে সাইন-ইন করেছেন তাই ওয়্যারলেস ডিবাগিং চালু করা যাবে না। এই ফিচারটি ব্যবহার করতে, প্রাথমিক ব্যবহারকারীতে পাল্টে নিন।"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"বর্তমানে এই ডিভাইসে সাইন-ইন করে থাকা ব্যবহারকারী \'ওয়্যারলেস ডিবাগিং\' চালু করতে পারবেন না। এই ফিচার ব্যবহার করতে, একজন অ্যাডমিন ব্যবহারকারী হিসেবে সাইন-ইন করুন।"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"ইউএসবি পোর্ট বন্ধ করা হয়েছে"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"কোনও তরল পদার্থ ও ধুলো থেকে আপনার ডিভাইসকে সুরক্ষিত রাখতে, ইউএসবি পোর্ট বন্ধ করা আছে, তাই কোনও অ্যাক্সেসরির শনাক্ত করা যাবে না।\n\nইউএসবি পোর্ট আবার ব্যবহার করা নিরাপদ হলে, আপনাকে জানানো হবে।"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"চার্জার ও আনুষঙ্গিক আইটেম শনাক্ত করার জন্য ইউএসবি চালু করা হয়েছে"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ভয়েস সহায়তা"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR কোড স্ক্যানার"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"আনলক করুন"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"ডিভাইস লক করা আছে"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"ফেস স্ক্যান করা হচ্ছে"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"পাঠান"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"ফেস শনাক্ত করা যায়নি"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"পরিবর্তে ফিঙ্গারপ্রিন্ট ব্যবহার করুন"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ব্লুটুথ সংযুক্ত হয়েছে৷"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ব্যাটারি কত শতাংশ আছে তা জানা যায়নি।"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>এ সংযুক্ত হয়ে আছে।"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"বিমান মোড৷"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN চালু আছে।"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> শতাংশ ব্যাটারি রয়েছে৷"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ব্যাটারি <xliff:g id="PERCENTAGE">%1$s</xliff:g> শতাংশ, বর্তমান ব্যবহারের উপর ভিত্তি করে আর <xliff:g id="TIME">%2$s</xliff:g> চলবে"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ব্যাটারি <xliff:g id="PERCENTAGE">%1$d</xliff:g> শতাংশ, বর্তমান ব্যবহারের উপর ভিত্তি করে আর <xliff:g id="TIME">%2$s</xliff:g> চলবে"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ব্যাটারি চার্জ হচ্ছে, এখন <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> শতাংশ চার্জ আছে৷"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"সমস্ত বিজ্ঞপ্তি দেখুন"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"টেলি টাইপরাইটার সক্ষম করা আছে৷"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"রিং বাজার সাথে স্পন্দিত করুন৷"</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"মাইক্রোফোন উপলভ্য"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ক্যামেরা উপলভ্য"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"মাইক্রোফোন ও ক্যামেরা উপলভ্য"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"মাইক্রোফোন চালু করা আছে"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"মাইক্রোফোন বন্ধ করা আছে"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"সব অ্যাপ ও পরিষেবার জন্য মাইক্রোফোনের অ্যাক্সেস চালু করা হয়েছে।"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"সব অ্যাপ ও পরিষেবার জন্য মাইক্রোফোনের অ্যাক্সেস বন্ধ করা হয়েছে। \'সেটিংস &gt; গোপনীয়তা &gt; মাইক্রোফোন\' বিকল্প থেকে আপনি মাইক্রোফোনের অ্যাক্সেস চালু করতে পারবেন।"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"সব অ্যাপ ও পরিষেবার জন্য মাইক্রোফোনের অ্যাক্সেস বন্ধ করা হয়েছে। \'সেটিংস &gt; গোপনীয়তা &gt; মাইক্রোফোন\' বিকল্প থেকে আপনি এটি পরিবর্তন করতে পারবেন।"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"ক্যামেরা চালু করা হয়েছে"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ক্যামেরা বন্ধ করা হয়েছে"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"সব অ্যাপ ও পরিষেবার জন্য ক্যামেরার অ্যাক্সেস চালু করা হয়েছে।"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"সব অ্যাপ ও পরিষেবার জন্য ক্যামেরার অ্যাক্সেস বন্ধ করা হয়েছে।"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"মাইক্রোফোনের বোতাম ব্যবহার করতে, সেটিংস থেকে মাইক্রোফোনের অ্যাক্সেস চালু করুন।"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"সেটিংস খুলুন।"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"অন্য ডিভাইস"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"\'এক নজরে\' বৈশিষ্ট্যটি চালু বা বন্ধ করুন"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"অ্যালার্ম, রিমাইন্ডার, ইভেন্ট, এবং আপনার নির্দিষ্ট করে দেওয়া ব্যক্তিদের কল ছাড়া অন্য কোনও আওয়াজ বা ভাইব্রেশন হবে না। তবে সঙ্গীত, ভিডিও, এবং গেম সহ আপনি যা কিছু চালাবেন তার আওয়াজ শুনতে পাবেন।"</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"কোনও অ্যাপ আপনার শেয়ার করা, রেকর্ড করা বা কাস্ট করার সময়, সেই অ্যাপে দেখা যায় বা খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"চালিয়ে যান"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"অ্যাপ শেয়ার বা রেকর্ড করা"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"এই অ্যাপকে শেয়ার বা রেকর্ড করার অনুমতি দেবেন?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"আপনি শেয়ার, রেকর্ড বা কাস্ট করার সময় স্ক্রিনে দৃশ্যমান বা ডিভাইসে চালানো হয়েছে এমন সব কিছুই এই অ্যাপ অ্যাক্সেস করতে পারবে। সেই জন্য পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"কোনও অ্যাপের মাধ্যমে শেয়ার, রেকর্ড বা কাস্ট করার সময়, অ্যাপে দৃশ্যমান বা তাতে চালানো হয়েছে এমন সব কিছুই এই অ্যাপ অ্যাক্সেস করতে পারবে। সেই জন্য পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"আপনার আইটি অ্যাডমিন ব্লক করেছেন"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ডিভাইস নীতির কারণে স্ক্রিন ক্যাপচার করার প্রসেস বন্ধ করা আছে"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"সবকিছু সাফ করুন"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"পরিচালনা করুন"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ব্যবহার করতে আনলক করুন"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"আপনার কার্ড সংক্রান্ত তথ্য পেতে সমস্যা হয়েছে, পরে আবার চেষ্টা করুন"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"লক স্ক্রিন সেটিংস"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR কোড স্ক্যান করুন"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"কাজের প্রোফাইল"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"বিমান মোড"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"আপনি আপনার পরবর্তী <xliff:g id="WHEN">%1$s</xliff:g> অ্যালার্ম শুনতে পাবেন না"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"আবার <xliff:g id="CARRIER">%s</xliff:g>-এর ডেটায় পরিবর্তন করবেন?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"উপলভ্যতার উপরে ভিত্তি করে অটোমেটিক মোবাইল ডেটায় পরিবর্তন করা হবে না"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"না থাক"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"হ্যাঁ, পাল্টান"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"সম্পূর্ণ স্ক্রিন বড় করে দেখা"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"স্ক্রিনের কিছুটা অংশ বড় করুন"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"বদল করুন"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"কোণাকুণি স্ক্রল করার অনুমতি দেওয়া"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ছোট বড় করা"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"বড় করে দেখার ধরন পরিবর্তন করা"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"আগের অবস্থায় ফিরুন"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label}টি শর্টকাট সরানো হয়েছে}one{#টি শর্টকাট সরানো হয়েছে}other{#টি শর্টকাট সরানো হয়েছে}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"সরিয়ে দিন"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"সাময়িকভাবে কানেক্ট করা হয়েছে"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"খারাপ কানেকশন"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>-কে ডিভাইসের সব লগ অ্যাক্সেসের অনুমতি দিতে চান?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"এককালীন অ্যাক্সেসের অনুমতি দিন"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"অনুমতি দেবেন না"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"ডিভাইস লগে আপনার ডিভাইসে করা অ্যাক্টিভিটি রেকর্ড করা হয়। অ্যাপ সমস্যা খুঁজে তা সমাধান করতে এইসব লগ ব্যবহার করতে পারে।\n\nকিছু লগে সংবেদনশীল তথ্য থাকতে পারে, তাই বিশ্বাস করেন শুধুমাত্র এমন অ্যাপকেই সব ডিভাইসের লগ অ্যাক্সেসের অনুমতি দিন। \n\nআপনি এই অ্যাপকে ডিভাইসের সব লগ অ্যাক্সেস করার অনুমতি না দিলেও, এটি নিজে লগ অ্যাক্সেস করতে পারবে। ডিভাইস প্রস্তুতকারকও আপনার ডিভাইসের কিছু লগ বা তথ্য হয়ত অ্যাক্সেস করতে পারবে।"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 183dfe0..5017716 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Uvijek dozvoli sa ovog računara"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Dozvoli"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Otklanjanje grešaka putem USB-a nije dozvoljeno"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Korisnik koji je trenutno prijavljen na ovaj uređaj ne može uključiti opciju za otklanjanje grešaka putem USB-a. Da koristite tu funkciju, prebacite se na primarnog korisnika."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Korisnik koji je trenutno prijavljen na uređaju ne može uključiti opciju za otklanjanje grešaka putem USB-a. Da koristite ovu funkciju, prebacite se na administratora."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Želite li promijeniti jezik sistema u <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Promjenu jezika sistema je zatražio drugi uređaj"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Promijeni jezik"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Uvijek dozvoli na ovoj mreži"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Dozvoli"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Bežično otklanjanje grešaka nije dozvoljeno"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Korisnik koji je trenutno prijavljen na ovaj uređaj ne može uključiti bežično otklanjanje grešaka. Da koristite tu funkciju, prebacite se na primarnog korisnika."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Korisnik koji je trenutno prijavljen na uređaju ne može uključiti bežično otklanjanje grešaka. Da koristite ovu funkciju prebacite se na administratora."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB priključak je onemogućen"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"USB priključak je onemogućen kako bi se vaš uređaj zaštitio od tečnosti i nečistoća i neće detektirati priključene uređaje.\n\nDobit ćete obavještenje kada ponovo bude sigurno koristiti USB priključak."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB priključak je omogućen za prepoznavanje punjača i pribora"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Glasovna pomoć"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Novčanik"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Skener QR koda"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Otključaj"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Uređaj je zaključan"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Skeniranje lica"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Pošalji"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Nije moguće prepoznati lice"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Koristite otisak prsta"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth je povezan."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Postotak napunjenosti baterije nije poznat"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezan na <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Način rada u avionu."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN uključen."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Baterija na <xliff:g id="NUMBER">%d</xliff:g> posto."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterija je na <xliff:g id="PERCENTAGE">%1$s</xliff:g> posto. Na osnovu vaše potrošnje preostalo vam je otprilike <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterija je na <xliff:g id="PERCENTAGE">%1$d</xliff:g> posto. Na osnovu vaše potrošnje preostalo vam je otprilike <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Punjenje baterije, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Vidite sva obavještenja"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Omogućena opcija TeleTypewriter."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Zvuk zvona na vibraciji."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon je dostupan"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera je dostupna"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon i kamera su dostuni"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon je uključen"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon je isključen"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon je omogućen za sve aplikacije i usluge."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Pristup mikrofonu je onemogućen za sve aplikacije i usluge. Možete omogućiti pristup mikrofonu u Postavkama &gt; Privatnost &gt; Mikrofon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Pristup mikrofonu je onemogućen za sve aplikacije i usluge. To možete promijeniti u Postavkama &gt; Privatnost &gt; Mikrofon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera je uključena"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera je isključena"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera je omogućena za sve aplikacije i usluge."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Pristup kamere je onemogućen za sve aplikacije i usluge."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Da koristite dugme za mikrofon, omogućite pristup mikrofonu u Postavkama."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Otvori postavke."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Drugi uređaj"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Pregled uključivanja/isključivanja"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Neće vas ometati zvukovi i vibracije, osim alarma, podsjetnika, događaja i pozivalaca koje odredite. I dalje ćete čuti sve što ste odabrali za reprodukciju, uključujući muziku, videozapise i igre."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kada aplikaciju dijelite, snimate ili emitirate, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Nastavi"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dijelite ili snimite aplikaciju"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Dozvoliti aplikaciji da dijeli ili snima?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kada dijelite, snimate ili emitirate, aplikacija ima pristup svemu što je vidljivo na ekranu ili što se reproducira na uređaju. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kada dijelite, snimate ili emitirate aplikaciju, ona ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokirao je vaš IT administrator"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje ekrana je onemogućeno pravilima uređaja"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Očisti sve"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historija"</string>
@@ -488,7 +511,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Otključajte da koristite"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Došlo je do problema prilikom preuzimanja vaših kartica. Pokušajte ponovo kasnije"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Postavke zaključavanja ekrana"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skenirajte QR kôd"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"Čitač QR koda"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Profil za posao"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Način rada u avionu"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sljedeći alarm u <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +750,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vratiti na operatera <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Prijenos podataka na mobilnoj mreži se neće automatski promijeniti na osnovu dostupnosti"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, hvala"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, promijeni"</string>
     <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>
@@ -767,6 +794,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Uvećavanje prikaza preko cijelog ekrana"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Uvećavanje dijela ekrana"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Prekidač"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Dozvoli dijagonalno klizanje"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Promijeni veličinu"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Promijeni vrstu uvećavanja"</string>
@@ -785,12 +814,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Poništavanje"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} prečica je uklonjena}one{# prečica je uklonjena}few{# prečice su uklonjene}other{# prečica je uklonjenao}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Uklanjanje"</string>
     <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 +965,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Privremeno povezano"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Slaba veza"</string>
     <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>
@@ -992,4 +1026,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"DDD, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Dozvoliti aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> da pristupa svim zapisnicima uređaja?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Dozvoli jednokratan pristup"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Nemoj dozvoliti"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Zapisnici uređaja bilježe šta se dešava na uređaju. Aplikacije mogu koristiti te zapisnike da pronađu i isprave probleme.\n\nNeki zapisnici mogu sadržavati osjetljive podatke, zato pristup svim zapisnicima uređaja dozvolite samo aplikacijama kojima vjerujete. \n\nAko ne dozvolite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač uređaja će možda i dalje biti u stanju pristupiti nekim zapisnicima ili podacima na uređaju."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index daa63e6..e270685 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Dona sempre permís des d\'aquest equip"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Permet"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"No es permet la depuració per USB"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"L\'usuari que té iniciada la sessió al dispositiu en aquest moment no pot activar la depuració per USB. Per utilitzar aquesta funció, cal canviar a l\'usuari principal."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"L\'usuari que té iniciada la sessió al dispositiu en aquest moment no pot activar la depuració per USB. Per utilitzar aquesta funció, cal canviar a l\'usuari principal."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Vols canviar l\'idioma del sistema a <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Un altre dispositiu ha sol·licitat canviar l\'idioma del sistema"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Canvia l\'idioma"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Permet sempre en aquesta xarxa"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Permet"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"No es permet la depuració sense fil"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"L\'usuari que té iniciada la sessió al dispositiu en aquest moment no pot activar la depuració sense fil. Per utilitzar aquesta funció, cal canviar a l\'usuari principal."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"L\'usuari que té iniciada la sessió al dispositiu en aquest moment no pot activar la depuració sense fil. Per utilitzar aquesta funció, cal canviar a l\'usuari principal."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"El port USB està desactivat"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Per protegir el teu dispositiu dels líquids o de la pols, el port USB s\'ha desactivat i no detectarà cap accessori.\n\nRebràs una notificació quan puguis tornar a utilitzar-lo."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"S\'ha activat el port USB per detectar carregadors i accessoris"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Assistència per veu"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Escàner de codis QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Desbloqueja"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositiu bloquejat"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"S\'està escanejant la cara"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Envia"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"No es reconeix la cara"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utilitza l\'empremta digital"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connectat."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Es desconeix el percentatge de bateria."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"S\'ha connectat a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Mode d\'avió."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN activada"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> per cent de bateria."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> per cent de bateria amb aproximadament <xliff:g id="TIME">%2$s</xliff:g> de temps restant segons l\'ús que en fas"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> per cent de bateria amb aproximadament <xliff:g id="TIME">%2$s</xliff:g> de temps restant segons l\'ús que en fas"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"La bateria s\'està carregant (<xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%)."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Mostra totes les notificacions"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletip activat."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Mode vibració."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gestiona els usuaris"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Micròfon disponible"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Càmera disponible"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Càmera i micròfon disponibles"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"El micròfon s\'ha activat"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"El micròfon s\'ha desactivat"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"El micròfon està activat per a totes les aplicacions i serveis."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"L\'accés al micròfon està desactivat per a totes les aplicacions i serveis. Pots activar l\'accés al micròfon a Configuració &gt; Privadesa &gt; Micròfon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"L\'accés al micròfon està desactivat per a totes les aplicacions i serveis. Pots canviar-ho a Configuració &gt; Privadesa &gt; Micròfon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"La càmera s\'ha activat"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"La càmera s\'ha desactivat"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"La càmera està activada per a totes les aplicacions i serveis."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"L\'accés a la càmera està desactivat per a totes les aplicacions i serveis."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Per utilitzar el botó de micròfon, activa l\'accés al micròfon a Configuració."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Obre la configuració."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Un altre dispositiu"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Activa o desactiva Aplicacions recents"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"No t\'interromprà cap so ni cap vibració, tret dels de les alarmes, recordatoris, esdeveniments i trucades de les persones que especifiquis. Continuaràs sentint tot allò que decideixis reproduir, com ara música, vídeos i jocs."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quan estàs compartint, gravant o emetent, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continua"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Comparteix o grava una aplicació"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Vols permetre que aquesta aplicació comparteixi o gravi contingut?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Quan estàs compartint, gravant o emetent, aquesta aplicació té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Quan estàs compartint, gravant o emetent, aquesta aplicació té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquejat per l\'administrador de TI"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Les captures de pantalla estan desactivades per la política de dispositius"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Esborra-ho tot"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gestiona"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
@@ -418,8 +441,8 @@
     <string name="monitoring_description_management_network_logging" msgid="216983105036994771">"L\'administrador ha activat el registre de xarxa, que supervisa el trànsit del teu dispositiu."</string>
     <string name="monitoring_description_managed_profile_network_logging" msgid="6932303843097006037">"L\'administrador ha activat el registre de xarxa, que monitora el trànsit al teu perfil de treball, però no al personal."</string>
     <string name="monitoring_description_named_vpn" msgid="7502657784155456414">"Aquest dispositiu es connecta a Internet a través de <xliff:g id="VPN_APP">%1$s</xliff:g>. El teu administrador de TI pot veure l\'activitat de la teva xarxa, inclosos els correus electrònics i les dades de navegació."</string>
-    <string name="monitoring_description_two_named_vpns" msgid="6726394451199620634">"Aquest dispositiu es connecta a Internet a través de <xliff:g id="VPN_APP_0">%1$s</xliff:g> i <xliff:g id="VPN_APP_1">%2$s</xliff:g>. El teu administrador de TI pot veure la teva activitat a la xarxa, inclosos els correus electrònics i les dades de navegació."</string>
-    <string name="monitoring_description_managed_profile_named_vpn" msgid="7254359257263069766">"Les aplicacions de treball es connecten a Internet a través de <xliff:g id="VPN_APP">%1$s</xliff:g>. El teu administrador de TI i el teu proveïdor de VPN poden veure la teva activitat a la xarxa en aplicacions de treball, inclosos els correus electrònics i les dades de navegació."</string>
+    <string name="monitoring_description_two_named_vpns" msgid="6726394451199620634">"Aquest dispositiu es connecta a Internet a través de <xliff:g id="VPN_APP_0">%1$s</xliff:g> i <xliff:g id="VPN_APP_1">%2$s</xliff:g>. El teu administrador de TI pot veure la teva activitat de xarxa, inclosos els correus electrònics i les dades de navegació."</string>
+    <string name="monitoring_description_managed_profile_named_vpn" msgid="7254359257263069766">"Les aplicacions de treball es connecten a Internet a través de <xliff:g id="VPN_APP">%1$s</xliff:g>. El teu administrador de TI i el teu proveïdor de VPN poden veure la teva activitat de xarxa en aplicacions de treball, inclosos els correus electrònics i les dades de navegació."</string>
     <string name="monitoring_description_personal_profile_named_vpn" msgid="5083909710727365452">"Les aplicacions personals es connecten a Internet a través de <xliff:g id="VPN_APP">%1$s</xliff:g>. El teu proveïdor de VPN pot veure l\'activitat de la teva xarxa, inclosos els correus electrònics i les dades de navegació."</string>
     <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string>
     <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Obre la configuració de la VPN"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloqueja per utilitzar"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Hi ha hagut un problema en obtenir les teves targetes; torna-ho a provar més tard"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuració de la pantalla de bloqueig"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Escaneja un codi QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Perfil de treball"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Mode d\'avió"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> no sentiràs la pròxima alarma"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vols tornar a <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Les dades mòbils no canviaran automàticament en funció de la disponibilitat"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No, gràcies"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sí, fes el canvi"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Amplia la pantalla completa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Amplia una part de la pantalla"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Canvia"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permet el desplaçament en diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Canvia la mida"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Canvia el tipus d\'ampliació"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfés"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{S\'ha suprimit la drecera {label}}other{S\'han suprimit # dreceres}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Suprimeix"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Connexió temporal"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Connexió feble"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"hh:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Vols permetre que <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> accedeixi a tots els registres del dispositiu?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Permet l\'accés únic"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"No permetis"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Els registres del dispositiu inclouen informació sobre tot allò que passa al teu dispositiu. Les aplicacions poden utilitzar aquests registres per detectar i corregir problemes.\n\nÉs possible que alguns registres continguin informació sensible; per això només has de donar-hi accés a les aplicacions de confiança. \n\nEncara que no permetis que aquesta aplicació pugui accedir a tots els registres del dispositiu, podrà accedir als seus propis registres. És possible que el fabricant del dispositiu també tingui accés a alguns registres o a informació del teu dispositiu."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index cba9448..02086ea 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Vždy povolit z tohoto počítače"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Povolit"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Ladění přes USB není povoleno"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Uživatel aktuálně přihlášený k tomuto zařízení nemůže zapnout ladění přes USB. Chcete-li tuto funkci použít, přepněte na primárního uživatele."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Uživatel aktuálně přihlášený k tomuto zařízení nemůže zapnout ladění přes USB. Chcete-li tuto funkci používat, přepněte na administrativního uživatele."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Změnit systémový jazyk na <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Jiné zařízení požádalo o změnu systémového jazyka"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Změnit jazyk"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"V této síti vždy povolit"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Povolit"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Bezdrátové ladění není povoleno"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Uživatel aktuálně přihlášený k tomuto zařízení nemůže zapnout bezdrátové ladění. Chcete-li tuto funkci použít, přepněte na primárního uživatele."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Uživatel aktuálně přihlášený k tomuto zařízení nemůže zapnout bezdrátové ladění. Chcete-li tuto funkci používat, přepněte na administrativního uživatele."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Port USB je deaktivován"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Kvůli ochraně vašeho zařízení před tekutinami a nečistotami je port USB zakázán a nerozpozná žádné příslušenství.\n\nAž bude opět bezpečné port USB použít, budeme vás informovat."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Port USB může zjišťovat nabíječky a příslušenství"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Hlasová asistence"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Peněženka"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Čtečka QR kódů"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Odemknout"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Zařízení uzamčeno"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Skenování obličeje"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Odeslat"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Obličej nelze rozpoznat"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Použijte otisk prstu"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Rozhraní Bluetooth je připojeno."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procento baterie není známé."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Připojeno k zařízení <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Režim Letadlo."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN je zapnuto."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Stav baterie: <xliff:g id="NUMBER">%d</xliff:g> procent."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterie je nabitá na <xliff:g id="PERCENTAGE">%1$s</xliff:g> procent, při vašem používání vydrží ještě <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterie je nabitá na <xliff:g id="PERCENTAGE">%1$d</xliff:g> procent, při vašem používání vydrží ještě <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Baterie se nabíjí. Nabito: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Zobrazit všechna oznámení"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Rozhraní TeleTypewriter zapnuto."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibrační vyzvánění."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Správa uživatelů"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon je k dispozici"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Fotoaparát je k dispozici"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon a fotoaparát jsou k dispozici"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon je zapnutý"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon je vypnutý"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Přístup k mikrofonu je aktivován pro všechny aplikace a služby."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Přístup k mikrofonu je deaktivován pro všechny aplikace a služby. Přístup k mikrofonu můžete udělit v Nastavení &gt; Ochrana soukromí &gt; Mikrofon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Přístup k mikrofonu je deaktivován pro všechny aplikace a služby. Můžete to změnit v Nastavení &gt; Ochrana soukromí &gt; Mikrofon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Fotoaparát je zapnutý"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Fotoaparát je vypnutý"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Přístup k fotoaparátu je aktivován pro všechny aplikace a služby."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Přístup k fotoaparátu je deaktivován pro všechny aplikace a služby."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Pokud chcete použít tlačítko mikrofonu, v nastavení udělte přístup k mikrofonu."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Otevřít nastavení."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Další zařízení"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Přepnout přehled"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Nebudou vás rušit zvuky ani vibrace s výjimkou budíků, upozornění, událostí a volajících, které zadáte. Nadále uslyšíte veškerý obsah, který si sami pustíte (např. hudba, videa nebo hry)."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Když sdílíte, nahráváte nebo odesíláte aplikaci, aplikace <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> má přístup k veškerému obsahu, který je v této aplikaci zobrazen nebo přehráván. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Pokračovat"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Sdílení nebo nahrání aplikace"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Povolit této aplikaci sdílet nebo nahrávat?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Když sdílíte, nahráváte nebo odesíláte obsah, aplikace má přístup ke všemu, co je viditelné na obrazovce nebo se přehrává v zařízení. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Když sdílíte, nahráváte nebo odesíláte aplikaci, aplikace má přístup ke všemu, co je v této aplikaci zobrazeno nebo se přehrává. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokováno administrátorem IT"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Záznam obrazovky je zakázán zásadami zařízení"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Smazat vše"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Spravovat"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historie"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odemknout a použít"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Při načítání karet došlo k problému, zkuste to později"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavení obrazovky uzamčení"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Naskenovat QR kód"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Pracovní profil"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Režim Letadlo"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Svůj další budík <xliff:g id="WHEN">%1$s</xliff:g> neuslyšíte"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Přepnout zpět na operátora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilní data se nebudou automaticky přepínat podle dostupnosti"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, díky"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ano, přepnout"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Zvětšit celou obrazovku"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Zvětšit část obrazovky"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Přepnout"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Povolit diagonální posouvání"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Změnit velikost"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Změnit typ zvětšení"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Vrátit zpět"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Zkratka {label} byla odstraněna}few{Byly odstraněny # zkratky}many{Bylo odstraněno # zkratky}other{Bylo odstraněno # zkratek}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Odstranit"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Dočasně připojeno"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Nekvalitní připojení"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE d. MMMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"H:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Povolit aplikaci <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> přístup ke všem protokolům zařízení?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Povolit jednorázový přístup"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Nepovolovat"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Do protokolů zařízení se zaznamenává, co se na zařízení děje. Aplikace tyto protokoly mohou používat k vyhledání a odstranění problémů.\n\nNěkteré protokoly mohou zahrnovat citlivé údaje. Přístup k protokolům zařízení proto povolte pouze aplikacím, kterým důvěřujete. \n\nPokud této aplikaci nepovolíte přístup ke všem protokolům zařízení, bude mít stále přístup ke svým vlastním protokolům. Výrobce zařízení může mít stále přístup k některým protokolům nebo informacím na vašem zařízení."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index a879cbb..1140d3d 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Tillad altid fra denne computer"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Tillad"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB-fejlretning er ikke tilladt"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Den bruger, der i øjeblikket er logget ind på denne enhed, kan ikke aktivere USB-fejlretning. Skift til den primære bruger for at bruge denne funktion."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Den bruger, der i øjeblikket er logget ind på denne enhed, kan ikke aktivere USB-fejlretning. Skift til en administrator for at bruge denne funktion."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Vil du ændre systemsproget til <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"En anden enhed har anmodet om en ændring af systemsproget"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Skift sprog"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Tillad altid på dette netværk"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Tillad"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Trådløs fejlretning er ikke tilladt"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Den bruger, der i øjeblikket er logget ind på denne enhed, kan ikke aktivere trådløs fejlretning. Skift til den primære bruger for at bruge denne funktion."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Den bruger, der i øjeblikket er logget ind på denne enhed, kan ikke aktivere trådløs fejlretning. Skift til en administrator for at bruge denne funktion."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB-porten er deaktiveret"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"USB-porten er blevet deaktiveret for at beskytte din enhed mod væske og snavs. Den kan derfor ikke registrere noget tilbehør.\n\nDu får besked, når du kan bruge USB-porten igen."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB-porten er aktiveret for at registrere opladere og tilbehør"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Taleassistent"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-kodescanner"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Lås op"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Enheden er låst"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanner ansigt"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Send"</string>
@@ -161,13 +162,15 @@
     <string name="biometric_dialog_last_pattern_attempt_before_wipe_profile" msgid="6045224069529284686">"Hvis du angiver et forkert mønster i næste forsøg, slettes din arbejdsprofil og de tilhørende data."</string>
     <string name="biometric_dialog_last_pin_attempt_before_wipe_profile" msgid="545567685899091757">"Hvis du angiver en forkert pinkode i næste forsøg, slettes din arbejdsprofil og de tilhørende data."</string>
     <string name="biometric_dialog_last_password_attempt_before_wipe_profile" msgid="8538032972389729253">"Hvis du angiver en forkert adgangskode i næste forsøg, slettes din arbejdsprofil og de tilhørende data."</string>
-    <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Sæt fingeren på fingeraftrykslæseren"</string>
+    <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Sæt fingeren på fingeraftrykssensoren"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="4465698996175640549">"Ikon for fingeraftryk"</string>
     <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Ansigtet kan ikke genkendes. Brug fingeraftryk i stedet."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Ansigt kan ikke genkendes"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Brug fingeraftryk i stedet"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth tilsluttet."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batteriniveauet er ukendt."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Tilsluttet <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Flytilstand."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN er slået til."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Batteri <xliff:g id="NUMBER">%d</xliff:g> procent."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batteriniveauet er på <xliff:g id="PERCENTAGE">%1$s</xliff:g> procent, så du har ca. <xliff:g id="TIME">%2$s</xliff:g> tilbage, alt efter hvordan du bruger enheden"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batteriniveauet er på <xliff:g id="PERCENTAGE">%1$d</xliff:g> procent, så du har ca. <xliff:g id="TIME">%2$s</xliff:g> tilbage, alt efter hvordan du bruger enheden"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batteriet oplades. <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Se alle notifikationer"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter aktiveret."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ringervibration."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon kan benyttes"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera kan benyttes"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon og kamera kan benyttes"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofonen er aktiveret"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofonen er deaktiveret"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofonen er aktiveret for alle apps og tjenester."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofonadgang er deaktiveret for alle apps og tjenester. Du kan aktivere mikrofonadgang under Indstillinger &gt; Privatliv &gt; Mikrofon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofonadgang er deaktiveret for alle apps og tjenester. Du kan ændre dette under Indstillinger &gt; Privatliv &gt; Mikrofon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kameraet er aktiveret"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kameraet er deaktiveret"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kameraet er aktiveret for alle apps og tjenester."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kameraadgang er deaktiveret for alle apps og tjenester."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Hvis du vil bruge mikrofonknappen, skal du aktivere mikrofonadgang under Indstillinger."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Åbn Indstillinger."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Anden enhed"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Slå Oversigt til/fra"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Du bliver ikke forstyrret af lyde eller vibrationer, undtagen fra alarmer, påmindelser, begivenheder og opkald fra udvalgte personer, du selv angiver. Du kan stadig høre alt, du vælger at afspille, f.eks. musik, videoer og spil."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Når du deler, optager eller caster en app, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsæt"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Del eller optag en app"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Vil du tillade, at denne app deler eller optager?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Når du deler, optager eller caster, har denne app adgang til alt, der vises på din skærm eller afspilles på din enhed. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Når du deler, optager eller caster en app, har denne app adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokeret af din it-administrator"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screenshots er deaktiveret af enhedspolitikken"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lås op for at bruge"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Dine kort kunne ikke hentes. Prøv igen senere."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lås skærmindstillinger"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scan QR-kode"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Arbejdsprofil"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Flytilstand"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Du vil ikke kunne høre din næste alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vil du skifte tilbage til <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobildata skifter ikke automatisk på baggrund af tilgængelighed"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nej tak"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, skift"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Forstør hele skærmen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Forstør en del af skærmen"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Skift"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Tillad diagonal rulning"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Juster"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Skift forstørrelsestype"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Fortryd"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} genvej blev fjernet}one{# genvej blev fjernet}other{# genveje blev fjernet}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Fjern"</string>
     <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>
@@ -924,15 +957,17 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Der er problemer med at aflæse dit batteriniveau"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tryk for at få flere oplysninger"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ingen alarm er indstillet"</string>
-    <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingeraftrykslæser"</string>
+    <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingeraftrykssensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"godkende"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"få adgang til enheden"</string>
     <string name="keyguard_try_fingerprint" msgid="2825130772993061165">"Brug fingeraftryk for at åbne"</string>
-    <string name="accessibility_fingerprint_bouncer" msgid="7189102492498735519">"Godkendelse er påkrævet. Sæt fingeren på fingeraftrykslæseren for at godkende."</string>
+    <string name="accessibility_fingerprint_bouncer" msgid="7189102492498735519">"Godkendelse er påkrævet. Sæt fingeren på fingeraftrykssensoren for at godkende."</string>
     <string name="ongoing_phone_call_content_description" msgid="5332334388483099947">"Igangværende telefonopkald"</string>
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Midlertidigt forbundet"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Dårlig forbindelse"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE d. MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"tt.mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk.mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Vil du give <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> adgang til alle enhedslogs?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Tillad engangsadgang"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Tillad ikke"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Enhedslogs registrerer, hvad der sker på din enhed. Apps kan bruge disse logs til at finde og løse problemer.\n\nNogle logs kan indeholde følsomme oplysninger, så giv kun apps, du har tillid til, adgang til alle enhedslogs. \n\nSelvom du ikke giver denne app adgang til alle enhedslogs, kan den stadig tilgå sine egne logs. Producenten af din enhed kan muligvis fortsat tilgå visse logs eller oplysninger på din enhed."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index fb135ff..5f60e64 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Von diesem Computer immer zulassen"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Erlauben"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB-Debugging nicht zulässig"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Der momentan auf diesem Gerät angemeldete Nutzer kann das USB-Debugging nicht aktivieren. Um diese Funktion verwenden zu können, wechsle zum primären Nutzer."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Der momentan auf diesem Gerät angemeldete Nutzer kann das USB-Debugging nicht aktivieren. Um diese Funktion zu verwenden, muss sich ein Administrator anmelden."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Möchtest du die Systemsprache in <xliff:g id="LANGUAGE">%1$s</xliff:g> ändern?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Von einem anderen Gerät wurde eine Änderung der Systemsprache angefordert"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Sprache ändern"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Immer in diesem Netzwerk zulassen"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Zulassen"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"\"Debugging über WLAN\" nicht zulässig"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Der momentan auf diesem Gerät angemeldete Nutzer kann \"Debugging über WLAN\" nicht aktivieren. Um diese Funktion verwenden zu können, wechsle zum Hauptnutzer."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Der momentan auf diesem Gerät angemeldete Nutzer kann das Debugging über WLAN nicht aktivieren. Um diese Funktion zu verwenden, muss sich ein Administrator anmelden."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB-Port deaktiviert"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Zum Schutz deines Geräts vor Flüssigkeiten oder Fremdkörpern ist der USB-Port zurzeit deaktiviert und erkennt kein Zubehör.\n\nDu wirst benachrichtigt, wenn der USB-Port wieder verwendet werden kann."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Erkennung von Ladegeräten und Zubehör am USB-Port aktiviert"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Sprachassistent"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-Code-Scanner"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Entsperren"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Gerät gesperrt"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Gesicht wird gescannt"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Senden"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Gesicht nicht erkannt"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Fingerabdruck verwenden"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Mit Bluetooth verbunden"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akkustand unbekannt."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Mit <xliff:g id="BLUETOOTH">%s</xliff:g> verbunden"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Flugmodus"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN an."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Akku bei <xliff:g id="NUMBER">%d</xliff:g> Prozent."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Akku bei <xliff:g id="PERCENTAGE">%1$s</xliff:g> Prozent. Bei deinem Nutzungsmuster hast du noch Strom für etwa <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Akku bei <xliff:g id="PERCENTAGE">%1$d</xliff:g> Prozent. Bei deinem Nutzungsmuster hast du noch Strom für etwa <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Akku wird aufgeladen, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> Prozent."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Alle Benachrichtigungen ansehen"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Schreibtelefonie aktiviert"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Klingeltonmodus \"Vibration\""</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Nutzer verwalten"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon verfügbar"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera verfügbar"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon und Kamera verfügbar"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon aktiviert"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon deaktiviert"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon ist für alle Apps und Dienste aktiviert."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofonzugriff ist für alle Apps und Dienste deaktiviert. Du kannst ihn in den Einstellungen unter „Datenschutz“ &gt; „Mikrofon“ aktivieren."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofonzugriff ist für alle Apps und Dienste deaktiviert. Du kannst dies in den Einstellungen unter „Datenschutz“ &gt; „Mikrofon“ ändern."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera eingeschaltet"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera ausgeschaltet"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera ist für alle Apps und Dienste aktiviert."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kamerazugriff ist für alle Apps und Dienste deaktiviert."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Wenn du die Mikrofontaste verwenden möchtest, musst du den Mikrofonzugriff in den Einstellungen aktivieren."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Einstellungen öffnen"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Sonstiges Gerät"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Übersicht ein-/ausblenden"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Klingeltöne und die Vibration werden deaktiviert, außer für Weckrufe, Erinnerungen, Termine sowie Anrufe von zuvor von dir festgelegten Personen. Du hörst jedoch weiterhin Sound, wenn du dir Musik anhörst, Videos ansiehst oder Spiele spielst."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Beim Teilen, Aufnehmen oder Übertragen einer App hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Weiter"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"App teilen oder aufnehmen"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Dieser App das Teilen oder Aufnehmen erlauben?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Beim Teilen, Aufnehmen oder Übertragen hat diese App Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Beim Teilen, Aufnehmen oder Übertragen einer App hat diese App Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Vom IT-Administrator blockiert"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Bildschirmaufnahme ist durch die Geräterichtlinien deaktiviert"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Verwalten"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Verlauf"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Zum Verwenden entsperren"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Beim Abrufen deiner Karten ist ein Fehler aufgetreten – bitte versuch es später noch einmal"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Einstellungen für den Sperrbildschirm"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR-Code scannen"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Arbeitsprofil"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Flugmodus"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Lautloser Weckruf <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Zurück zu <xliff:g id="CARRIER">%s</xliff:g> wechseln?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobile Daten werden nicht je nach Verfügbarkeit automatisch gewechselt"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nein danke"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, wechseln"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ganzen Bildschirm vergrößern"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Teil des Bildschirms vergrößern"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Schalter"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Diagonales Scrollen erlauben"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Größe ändern"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Art der Vergrößerung ändern"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Rückgängig machen"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} Verknüpfung entfernt}other{# Verknüpfungen entfernt}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Entfernen"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Vorübergehend verbunden"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Schwache Verbindung"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d. MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> den Zugriff auf alle Geräteprotokolle erlauben?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Einmaligen Zugriff erlauben"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Nicht erlauben"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"In Geräteprotokollen wird aufgezeichnet, welche Aktionen auf deinem Gerät ausgeführt werden. Apps können diese Protokolle verwenden, um Probleme zu finden und zu beheben.\n\nEinige Protokolle enthalten unter Umständen vertrauliche Informationen. Daher solltest du nur vertrauenswürdigen Apps den Zugriff auf alle Geräteprotokolle erlauben. \n\nWenn du dieser App keinen Zugriff auf alle Geräteprotokolle gewährst, kann sie trotzdem auf ihre eigenen Protokolle zugreifen. Dein Gerätehersteller hat möglicherweise auch Zugriff auf einige Protokolle oder Informationen auf deinem Gerät."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 3d15953..f2341d6 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Να επιτρέπεται πάντα από αυτόν τον υπολογιστή"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Να επιτρέπεται"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Δεν επιτρέπεται ο εντοπισμός σφαλμάτων USB"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Ο χρήστης που είναι συνδεδεμένος αυτήν τη στιγμή σε αυτήν τη συσκευή δεν μπορεί να ενεργοποιήσει τον εντοπισμό σφαλμάτων USB. Για να χρησιμοποιήσετε αυτήν τη λειτουργία, κάντε εναλλαγή στον κύριο χρήστη."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Ο χρήστης που είναι συνδεδεμένος αυτήν τη στιγμή σε αυτήν τη συσκευή δεν μπορεί να ενεργοποιήσει τον εντοπισμό σφαλμάτων USB. Για να χρησιμοποιήσετε αυτήν τη λειτουργία, κάντε εναλλαγή σε έναν χρήστη με δικαιώματα διαχειριστή."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Θέλετε να αλλάξετε τη γλώσσα συστήματος σε <xliff:g id="LANGUAGE">%1$s</xliff:g>;"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Ζητήθηκε αλλαγή της γλώσσας συστήματος από άλλη συσκευή"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Αλλαγή γλώσσας"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Να επιτρέπεται πάντα σε αυτό το δίκτυο"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Να επιτρέπεται"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Ο ασύρματος εντοπισμός σφαλμάτων δεν επιτρέπεται"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Ο χρήστης που είναι συνδεδεμένος αυτήν τη στιγμή στη συγκεκριμένη συσκευή δεν μπορεί να ενεργοποιήσει τον ασύρματο εντοπισμό σφαλμάτων. Για να χρησιμοποιήσετε αυτήν τη λειτουργία, κάντε εναλλαγή στον κύριο χρήστη."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Ο χρήστης που είναι συνδεδεμένος αυτήν τη στιγμή στη συγκεκριμένη συσκευή δεν μπορεί να ενεργοποιήσει τον ασύρματο εντοπισμό σφαλμάτων. Για να χρησιμοποιήσετε αυτήν τη λειτουργία, κάντε εναλλαγή σε έναν χρήστη με δικαιώματα διαχειριστή."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Η θύρα USB απενεργοποιήθηκε"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Για την προστασία της συσκευής σας από υγρασία ή ακαθαρσίες, η θύρα USB έχει απενεργοποιηθεί και δεν θα εντοπίζει τυχόν αξεσουάρ.\n\nΘα ειδοποιηθείτε όταν θα μπορείτε να χρησιμοποιήσετε ξανά τη θύρα USB."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Η θύρα USB ενεργοποιήθηκε για τον εντοπισμό φορτιστών και αξεσουάρ"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Φωνητική υποβοήθηση"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Πορτοφόλι"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Σάρωση κωδικών QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Ξεκλείδωμα"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Η συσκευή κλειδώθηκε"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Σάρωση προσώπου"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Αποστολή"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Αδύνατη η αναγν. προσώπου"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Χρησιμ. δακτυλ. αποτύπ."</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Το Bluetooth είναι συνδεδεμένο."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Άγνωστο ποσοστό μπαταρίας."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Συνδέθηκε στο <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Λειτουργία πτήσης."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ενεργό."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Μπαταρία <xliff:g id="NUMBER">%d</xliff:g> τοις εκατό."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Μπαταρία στο <xliff:g id="PERCENTAGE">%1$s</xliff:g> τοις εκατό. Περίπου <xliff:g id="TIME">%2$s</xliff:g> ακόμη, βάσει της χρήσης σας"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Μπαταρία στο <xliff:g id="PERCENTAGE">%1$d</xliff:g> τοις εκατό. Περίπου <xliff:g id="TIME">%2$s</xliff:g> ακόμη, βάσει της χρήσης σας"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Φόρτιση μπαταρίας: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Δείτε όλες τις ειδοποιήσεις"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Το TeleTypewriter ενεργοποιήθηκε."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Δόνηση ειδοποίησης ήχου."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Διαθέσιμο μικρόφωνο"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Διαθέσιμη κάμερα"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Διαθέσιμη κάμερα και μικρόφωνο"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Το μικρόφωνο ενεργοποιήθηκε"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Το μικρόφωνο απενεργοποιήθηκε"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Το μικρόφωνο ενεργοποιήθηκε για όλες τις εφαρμογές και τις υπηρεσίες."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Η πρόσβαση στο μικρόφωνο είναι απενεργοποιημένη για όλες τις εφαρμογές και τις υπηρεσίες. Μπορείτε να ενεργοποιήσετε την πρόσβαση στο μικρόφωνο από τις Ρυθμίσεις &gt; Απόρρητο &gt; Μικρόφωνο."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Η πρόσβαση στο μικρόφωνο είναι απενεργοποιημένη για όλες τις εφαρμογές και τις υπηρεσίες. Μπορείτε να αλλάξετε την επιλογή από τις Ρυθμίσεις &gt; Απόρρητο &gt; Μικρόφωνο."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Η κάμερα ενεργοποιήθηκε"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Η κάμερα απενεργοποιήθηκε"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Η κάμερα ενεργοποιήθηκε για όλες τις εφαρμογές και τις υπηρεσίες."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Η πρόσβαση στην κάμερα είναι απενεργοποιημένη για όλες τις εφαρμογές και τις υπηρεσίες."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Για να χρησιμοποιήσετε το κουμπί μικροφώνου, ενεργοποιήστε την πρόσβαση στο μικρόφωνο από τις Ρυθμίσεις."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Άνοιγμα ρυθμίσεων."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Άλλη συσκευή"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Εναλλαγή επισκόπησης"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Δεν θα ενοχλείστε από ήχους και δονήσεις, παρά μόνο από ξυπνητήρια, υπενθυμίσεις, συμβάντα και καλούντες που έχετε καθορίσει. Θα εξακολουθείτε να ακούτε όλο το περιεχόμενο που επιλέγετε να αναπαραγάγετε, συμπεριλαμβανομένης της μουσικής, των βίντεο και των παιχνιδιών."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Όταν κάνετε κοινοποίηση, εγγραφή ή μετάδοση μιας εφαρμογής, η εφαρμογή <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> έχει πρόσβαση σε οτιδήποτε είναι ορατό ή αναπαράγεται στη συγκεκριμένη εφαρμογή. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα ή άλλες ευαίσθητες πληροφορίες."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Συνέχεια"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Κοινοποίηση ή εγγραφή εφαρμογής"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Να επιτρέπεται σε αυτήν την εφαρμογή η κοινή χρήση ή εγγραφή;"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Όταν κάνετε κοινοποίηση, εγγραφή ή μετάδοση, αυτή η εφαρμογή έχει πρόσβαση σε οτιδήποτε είναι ορατό στην οθόνη σας ή αναπαράγεται στη συσκευή σας. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα ή άλλες ευαίσθητες πληροφορίες."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Όταν κάνετε κοινοποίηση, εγγραφή ή μετάδοση μιας εφαρμογής, αυτή η εφαρμογή έχει πρόσβαση σε οτιδήποτε είναι ορατό ή αναπαράγεται στη συγκεκριμένη εφαρμογή. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα ή άλλες ευαίσθητες πληροφορίες."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Αποκλείστηκε από τον διαχειριστή IT"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Η καταγραφή οθόνης έχει απενεργοποιηθεί από την πολιτική χρήσης συσκευής."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Διαγραφή όλων"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Διαχείριση"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Ιστορικό"</string>
@@ -488,7 +511,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ξεκλείδωμα για χρήση"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Παρουσιάστηκε πρόβλημα με τη λήψη των καρτών σας. Δοκιμάστε ξανά αργότερα"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ρυθμίσεις κλειδώματος οθόνης"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Σάρωση κωδικού QR"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"Σάρωση κωδικών QR"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Προφίλ εργασίας"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Λειτουργία πτήσης"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Δεν θα ακούσετε το επόμενο ξυπνητήρι σας <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +750,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Επιστροφή σε <xliff:g id="CARRIER">%s</xliff:g>;"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Δεν θα γίνεται αυτόματα εναλλαγή των δεδομένων κινητής τηλεφωνίας βάσει της διαθεσιμότητας"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Όχι, ευχαριστώ"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ναι, εναλλαγή"</string>
     <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>
@@ -767,6 +794,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Μεγέθυνση πλήρους οθόνης"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Μεγέθυνση μέρους της οθόνης"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Εναλλαγή"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Να επιτρέπεται η διαγώνια κύλιση"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Αλλαγή μεγέθους"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Αλλαγή τύπου μεγιστοποίησης"</string>
@@ -785,12 +814,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Αναίρεση"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Η συντόμευση {label} καταργήθηκε}other{Καταργήθηκαν # συντομεύσεις}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Κατάργηση"</string>
     <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 +965,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Προσωρινή σύνδεση"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Ασθενής σύνδεση"</string>
     <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>
@@ -992,4 +1026,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"ΗΗΗ, ΜΜΜ η"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"ώ:λλ"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:λλ"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Να επιτρέπεται στο <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> η πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής;"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Να επιτρέπεται η πρόσβαση για μία φορά"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Να μην επιτρέπεται"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Τα αρχεία καταγραφής συσκευής καταγράφουν ό,τι συμβαίνει στη συσκευή σας. Οι εφαρμογές μπορούν να χρησιμοποιούν αυτά τα αρχεία καταγραφής για να εντοπίζουν και να διορθώνουν ζητήματα.\n\nΟρισμένα αρχεία καταγραφής ενδέχεται να περιέχουν ευαίσθητες πληροφορίες. Ως εκ τούτου, επιτρέψτε την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής μόνο στις εφαρμογές που εμπιστεύεστε. \n\nΕάν δεν επιτρέψετε σε αυτήν την εφαρμογή την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής, η εφαρμογή εξακολουθεί να έχει πρόσβαση στα δικά της αρχεία καταγραφής. Ο κατασκευαστής της συσκευής σας ενδέχεται να εξακολουθεί να έχει πρόσβαση σε ορισμένα αρχεία καταγραφής ή ορισμένες πληροφορίες στη συσκευή σας."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index e7166ac..d036b88 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Always allow from this computer"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Allow"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB debugging not allowed"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to an admin user."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Do you want to change the system language to <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"System language change requested by another device"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Change language"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Always allow on this network"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Allow"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Wireless debugging not allowed"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"The user currently signed in to this device can’t turn on wireless debugging. To use this feature, switch to the primary user."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"The user currently signed in to this device can’t turn on wireless debugging. To use this feature, switch to an admin user."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB port disabled"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"To protect your device from liquid or debris, the USB port is disabled and won’t detect any accessories.\n\nYou’ll be notified when it’s OK to use the USB port again."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB port enabled to detect chargers and accessories"</string>
@@ -125,7 +125,7 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR code scanner"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Unlock"</string>
+    <string name="accessibility_unlock_button" msgid="3613812140816244310">"Unlocked"</string>
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Device locked"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanning face"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Send"</string>
@@ -168,6 +168,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +180,10 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Aeroplane mode"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN on."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Battery <xliff:g id="NUMBER">%d</xliff:g> per cent."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$s</xliff:g> percentage, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$d</xliff:g> percentage, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Battery charging, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> percent."</string>
+    <string name="accessibility_battery_level_charging_paused" msgid="1716051308782906917">"Battery <xliff:g id="PERCENTAGE">%d</xliff:g> per cent. Charging paused for battery protection."</string>
+    <string name="accessibility_battery_level_charging_paused_with_estimate" msgid="4006089349465741762">"Battery <xliff:g id="PERCENTAGE">%1$d</xliff:g> per cent, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage. Charging paused for battery protection."</string>
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"See all notifications"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter enabled."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ringer vibrate."</string>
@@ -248,7 +252,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>
@@ -303,6 +307,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microphone available"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Camera available"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microphone and camera available"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microphone turned on"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microphone turned off"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Microphone is enabled for all apps and services."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Microphone access is disabled for all apps and services. You can enable microphone access in Settings &gt; Privacy &gt; Microphone."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Microphone access is disabled for all apps and services. You can change this in Settings &gt; Privacy &gt; Microphone."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Camera turned on"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Camera turned off"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Camera is enabled for all apps and services."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Camera access is disabled for all apps and services."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"To use the microphone button, enable microphone access in Settings."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Open settings."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Other device"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Toggle Overview"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers you specify. You\'ll still hear anything you choose to play including music, videos and games."</string>
@@ -373,6 +388,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Allow this app to share or record?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"When you\'re sharing, recording or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"When you\'re sharing, recording or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages or other sensitive information."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
@@ -488,7 +508,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scan QR code"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR code scanner"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +747,10 @@
     <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>
+    <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 won\'t 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>
@@ -767,6 +791,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Magnify full screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Switch"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Allow diagonal scrolling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Resize"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Change magnification type"</string>
@@ -785,12 +811,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. 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>
+    <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 +962,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>
@@ -992,4 +1023,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Allow one-time access"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Don’t allow"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index fe746d9..b25bcb5 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -21,8 +21,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4811759950673118541">"System UI"</string>
     <string name="battery_low_title" msgid="5319680173344341779">"Turn on Battery Saver?"</string>
-    <string name="battery_low_description" msgid="3282977755476423966">"You have <xliff:g id="PERCENTAGE">%s</xliff:g> battery left. Battery Saver turns on Dark theme, restricts background activity and delays notifications."</string>
-    <string name="battery_low_intro" msgid="5148725009653088790">"Battery Saver turns on Dark theme, restricts background activity and delays notifications."</string>
+    <string name="battery_low_description" msgid="3282977755476423966">"You have <xliff:g id="PERCENTAGE">%s</xliff:g> battery left. Battery Saver turns on Dark theme, restricts background activity, and delays notifications."</string>
+    <string name="battery_low_intro" msgid="5148725009653088790">"Battery Saver turns on Dark theme, restricts background activity, and delays notifications."</string>
     <string name="battery_low_percent_format" msgid="4276661262843170964">"<xliff:g id="PERCENTAGE">%s</xliff:g> remaining"</string>
     <string name="invalid_charger_title" msgid="938685362320735167">"Can\'t charge via USB"</string>
     <string name="invalid_charger_text" msgid="2339310107232691577">"Use the charger that came with your device"</string>
@@ -30,7 +30,7 @@
     <string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"About Battery Saver"</string>
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Turn on"</string>
     <string name="battery_saver_start_action" msgid="8353766979886287140">"Turn on"</string>
-    <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, thanks"</string>
+    <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No thanks"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Auto-rotate screen"</string>
     <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device."</string>
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Always allow from this computer"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Allow"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB debugging not allowed"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to an admin user."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Do you want to change the system language to <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"System language change requested by another device"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Change language"</string>
@@ -62,9 +62,9 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Always allow on this network"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Allow"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Wireless debugging not allowed"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"The user currently signed in to this device can’t turn on wireless debugging. To use this feature, switch to the primary user."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"The user currently signed in to this device can’t turn on wireless debugging. To use this feature, switch to an admin user."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB port disabled"</string>
-    <string name="usb_contaminant_message" msgid="7730476585174719805">"To protect your device from liquid or debris, the USB port is disabled and won’t detect any accessories.\n\nYou’ll be notified when it’s OK to use the USB port again."</string>
+    <string name="usb_contaminant_message" msgid="7730476585174719805">"To protect your device from liquid or debris, the USB port is disabled and won’t detect any accessories.\n\nYou’ll be notified when it’s okay to use the USB port again."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB port enabled to detect chargers and accessories"</string>
     <string name="usb_disable_contaminant_detection" msgid="3827082183595978641">"Enable USB"</string>
     <string name="learn_more" msgid="4690632085667273811">"Learn more"</string>
@@ -85,23 +85,23 @@
     <string name="screenshot_scroll_label" msgid="2930198809899329367">"Capture more"</string>
     <string name="screenshot_dismiss_description" msgid="4702341245899508786">"Dismiss screenshot"</string>
     <string name="screenshot_preview_description" msgid="7606510140714080474">"Screenshot preview"</string>
-    <string name="screenshot_top_boundary_pct" msgid="2520148599096479332">"Top boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
-    <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bottom boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
-    <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
-    <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
+    <string name="screenshot_top_boundary_pct" msgid="2520148599096479332">"Top boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
+    <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bottom boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
+    <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
+    <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Screen Recorder"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
-    <string name="screenrecord_start_label" msgid="1750350278888217473">"Start recording?"</string>
-    <string name="screenrecord_description" msgid="1123231719680353736">"While recording, the Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string>
+    <string name="screenrecord_start_label" msgid="1750350278888217473">"Start Recording?"</string>
+    <string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages, and audio."</string>
     <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Record entire screen"</string>
     <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Record a single app"</string>
-    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"While you\'re recording, Android has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
-    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"While you\'re recording an app, Android has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"While you\'re recording, Android has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"While you\'re recording an app, Android has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information."</string>
     <string name="screenrecord_start_recording" msgid="348286842544768740">"Start recording"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string>
-    <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls and ringtones"</string>
+    <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls, and ringtones"</string>
     <string name="screenrecord_mic_label" msgid="2111264835791332350">"Microphone"</string>
     <string name="screenrecord_device_audio_and_mic_label" msgid="1831323771978646841">"Device audio and microphone"</string>
     <string name="screenrecord_start" msgid="330991441575775004">"Start"</string>
@@ -124,8 +124,8 @@
     <string name="accessibility_phone_button" msgid="4256353121703100427">"Phone"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
-    <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR code scanner"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Unlock"</string>
+    <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR Code Scanner"</string>
+    <string name="accessibility_unlock_button" msgid="3613812140816244310">"Unlocked"</string>
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Device locked"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanning face"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Send"</string>
@@ -140,8 +140,8 @@
     <string name="biometric_dialog_tap_confirm" msgid="9166350738859143358">"Tap Confirm to complete"</string>
     <string name="biometric_dialog_tap_confirm_with_face" msgid="1092050545851021991">"Unlocked by face. Press the unlock icon to continue."</string>
     <string name="biometric_dialog_tap_confirm_with_face_alt_1" msgid="439152621640507113">"Unlocked by face. Press to continue."</string>
-    <string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"Face recognised. Press to continue."</string>
-    <string name="biometric_dialog_tap_confirm_with_face_alt_3" msgid="2192670471930606539">"Face recognised. Press the unlock icon to continue."</string>
+    <string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"Face recognized. Press to continue."</string>
+    <string name="biometric_dialog_tap_confirm_with_face_alt_3" msgid="2192670471930606539">"Face recognized. Press the unlock icon to continue."</string>
     <string name="biometric_dialog_authenticated" msgid="7337147327545272484">"Authenticated"</string>
     <string name="biometric_dialog_use_pin" msgid="8385294115283000709">"Use PIN"</string>
     <string name="biometric_dialog_use_pattern" msgid="2315593393167211194">"Use pattern"</string>
@@ -163,11 +163,13 @@
     <string name="biometric_dialog_last_password_attempt_before_wipe_profile" msgid="8538032972389729253">"If you enter an incorrect password on the next attempt, your work profile and its data will be deleted."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Touch the fingerprint sensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="4465698996175640549">"Fingerprint icon"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Can’t recognise face. Use fingerprint instead."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Can’t recognize face. Use fingerprint instead."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
+    <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognize face"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -177,9 +179,11 @@
     <string name="cell_data_off" msgid="4886198950247099526">"Off"</string>
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Airplane mode."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN on."</string>
-    <string name="accessibility_battery_level" msgid="5143715405241138822">"Battery <xliff:g id="NUMBER">%d</xliff:g> per cent."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$s</xliff:g> percentage, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
+    <string name="accessibility_battery_level" msgid="5143715405241138822">"Battery <xliff:g id="NUMBER">%d</xliff:g> percent."</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$d</xliff:g> percent, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Battery charging, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> percent."</string>
+    <string name="accessibility_battery_level_charging_paused" msgid="1716051308782906917">"Battery <xliff:g id="PERCENTAGE">%d</xliff:g> percent. Charging paused for battery protection."</string>
+    <string name="accessibility_battery_level_charging_paused_with_estimate" msgid="4006089349465741762">"Battery <xliff:g id="PERCENTAGE">%1$d</xliff:g> percent, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage. Charging paused for battery protection."</string>
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"See all notifications"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter enabled."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ringer vibrate."</string>
@@ -194,7 +198,7 @@
     <string name="accessibility_quick_settings_dnd_none_on" msgid="3235552940146035383">"total silence"</string>
     <string name="accessibility_quick_settings_dnd_alarms_on" msgid="3375848309132140014">"alarms only"</string>
     <string name="accessibility_quick_settings_dnd" msgid="2415967452264206047">"Do Not Disturb."</string>
-    <string name="accessibility_quick_settings_bluetooth" msgid="8250942386687551283">"Bluetooth"</string>
+    <string name="accessibility_quick_settings_bluetooth" msgid="8250942386687551283">"Bluetooth."</string>
     <string name="accessibility_quick_settings_bluetooth_on" msgid="3819082137684078013">"Bluetooth on."</string>
     <string name="accessibility_quick_settings_alarm" msgid="558094529584082090">"Alarm set for <xliff:g id="TIME">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_more_time" msgid="7646479831704665284">"More time."</string>
@@ -203,12 +207,12 @@
     <string name="accessibility_brightness" msgid="5391187016177823721">"Display brightness"</string>
     <string name="data_usage_disabled_dialog_mobile_title" msgid="2286843518689837719">"Mobile data is paused"</string>
     <string name="data_usage_disabled_dialog_title" msgid="9131615296036724838">"Data is paused"</string>
-    <string name="data_usage_disabled_dialog" msgid="7933201635215099780">"The data limit that you set has been reached. You are no longer using mobile data.\n\nIf you resume, charges may apply for data usage."</string>
+    <string name="data_usage_disabled_dialog" msgid="7933201635215099780">"The data limit you set has been reached. You are no longer using mobile data.\n\nIf you resume, charges may apply for data usage."</string>
     <string name="data_usage_disabled_dialog_enable" msgid="2796648546086408937">"Resume"</string>
     <string name="accessibility_location_active" msgid="2845747916764660369">"Location requests active"</string>
     <string name="accessibility_sensors_off_active" msgid="2619725434618911551">"Sensors off active"</string>
     <string name="accessibility_clear_all" msgid="970525598287244592">"Clear all notifications."</string>
-    <string name="notification_group_overflow_indicator" msgid="7605120293801012648">"+<xliff:g id="NUMBER">%s</xliff:g>"</string>
+    <string name="notification_group_overflow_indicator" msgid="7605120293801012648">"+ <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="notification_group_overflow_description" msgid="7176322877233433278">"{count,plural, =1{# more notification inside.}other{# more notifications inside.}}"</string>
     <string name="accessibility_rotation_lock_on_landscape" msgid="936972553861524360">"Screen is locked in landscape orientation."</string>
     <string name="accessibility_rotation_lock_on_portrait" msgid="2356633398683813837">"Screen is locked in portrait orientation."</string>
@@ -227,7 +231,7 @@
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string>
-    <string name="quick_settings_screensaver_label" msgid="1495003469366524120">"Screensaver"</string>
+    <string name="quick_settings_screensaver_label" msgid="1495003469366524120">"Screen saver"</string>
     <string name="quick_settings_camera_label" msgid="5612076679385269339">"Camera access"</string>
     <string name="quick_settings_mic_label" msgid="8392773746295266375">"Mic access"</string>
     <string name="quick_settings_camera_mic_available" msgid="1453719768420394314">"Available"</string>
@@ -246,9 +250,9 @@
     <string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
     <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
     <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_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="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>
@@ -303,13 +307,24 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microphone available"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Camera available"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microphone and camera available"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microphone turned on"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microphone turned off"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Microphone is enabled for all apps and services."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Microphone access is disabled for all apps and services. You can enable microphone access in Settings &gt; Privacy &gt; Microphone."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Microphone access is disabled for all apps and services. You can change this in Settings &gt; Privacy &gt; Microphone."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Camera turned on"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Camera turned off"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Camera is enabled for all apps and services."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Camera access is disabled for all apps and services."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"To use the microphone button, enable microphone access in Settings."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Open settings."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Other device"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Toggle Overview"</string>
-    <string name="zen_priority_introduction" msgid="3159291973383796646">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers you specify. You\'ll still hear anything you choose to play including music, videos and games."</string>
-    <string name="zen_alarms_introduction" msgid="3987266042682300470">"You won\'t be disturbed by sounds and vibrations, except from alarms. You\'ll still hear anything you choose to play including music, videos and games."</string>
-    <string name="zen_priority_customize_button" msgid="4119213187257195047">"Customise"</string>
+    <string name="zen_priority_introduction" msgid="3159291973383796646">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events, and callers you specify. You\'ll still hear anything you choose to play including music, videos, and games."</string>
+    <string name="zen_alarms_introduction" msgid="3987266042682300470">"You won\'t be disturbed by sounds and vibrations, except from alarms. You\'ll still hear anything you choose to play including music, videos, and games."</string>
+    <string name="zen_priority_customize_button" msgid="4119213187257195047">"Customize"</string>
     <string name="zen_silence_introduction_voice" msgid="853573681302712348">"This blocks ALL sounds and vibrations, including from alarms, music, videos, and games. You\'ll still be able to make phone calls."</string>
-    <string name="zen_silence_introduction" msgid="6117517737057344014">"This blocks ALL sounds and vibrations, including from alarms, music, videos and games."</string>
+    <string name="zen_silence_introduction" msgid="6117517737057344014">"This blocks ALL sounds and vibrations, including from alarms, music, videos, and games."</string>
     <string name="notification_tap_again" msgid="4477318164947497249">"Tap again to open"</string>
     <string name="tap_again" msgid="1315420114387908655">"Tap again"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Swipe up to open"</string>
@@ -317,10 +332,10 @@
     <string name="keyguard_face_successful_unlock_swipe" msgid="6180997591385846073">"Unlocked by face. Swipe up to open."</string>
     <string name="keyguard_face_successful_unlock_press" msgid="25520941264602588">"Unlocked by face. Press the unlock icon to open."</string>
     <string name="keyguard_face_successful_unlock_press_alt_1" msgid="5715461103913071474">"Unlocked by face. Press to open."</string>
-    <string name="keyguard_face_successful_unlock_press_alt_2" msgid="8310787946357120406">"Face recognised. Press to open."</string>
-    <string name="keyguard_face_successful_unlock_press_alt_3" msgid="7219030481255573962">"Face recognised. Press the unlock icon to open."</string>
+    <string name="keyguard_face_successful_unlock_press_alt_2" msgid="8310787946357120406">"Face recognized. Press to open."</string>
+    <string name="keyguard_face_successful_unlock_press_alt_3" msgid="7219030481255573962">"Face recognized. Press the unlock icon to open."</string>
     <string name="keyguard_face_successful_unlock" msgid="4203999851465708287">"Unlocked by face"</string>
-    <string name="keyguard_face_successful_unlock_alt1" msgid="5853906076353839628">"Face recognised"</string>
+    <string name="keyguard_face_successful_unlock_alt1" msgid="5853906076353839628">"Face recognized"</string>
   <string-array name="udfps_accessibility_touch_hints">
     <item msgid="1901953991150295169">"Move left"</item>
     <item msgid="5558598599408514296">"Move down"</item>
@@ -352,7 +367,7 @@
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
     <string name="guest_wipe_session_title" msgid="7147965814683990944">"Welcome back, guest!"</string>
     <string name="guest_wipe_session_message" msgid="3393823610257065457">"Do you want to continue your session?"</string>
-    <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Start again"</string>
+    <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Start over"</string>
     <string name="guest_wipe_session_dontwipe" msgid="3211052048269304205">"Yes, continue"</string>
     <string name="guest_notification_app_name" msgid="2110425506754205509">"Guest mode"</string>
     <string name="guest_notification_session_active" msgid="5567273684713471450">"You are in guest mode"</string>
@@ -362,17 +377,22 @@
     <string name="user_remove_user_title" msgid="9124124694835811874">"Remove user?"</string>
     <string name="user_remove_user_message" msgid="6702834122128031833">"All apps and data of this user will be deleted."</string>
     <string name="user_remove_user_remove" msgid="8387386066949061256">"Remove"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
-    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages, and audio that you play."</string>
+    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages, and audio that you play."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Start recording or casting?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Start recording or casting with <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Allow <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> to share or record?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Entire screen"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"A single app"</string>
-    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"When you\'re sharing, recording or casting, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
-    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"When you\'re sharing, recording, or casting, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording, or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Allow this app to share or record?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"When you\'re sharing, recording, or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"When you\'re sharing, recording, or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
@@ -389,19 +409,19 @@
     <string name="quick_settings_disclosure_named_management_monitoring" msgid="2831423806103479812">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> owns this device and may monitor network traffic"</string>
     <string name="quick_settings_financed_disclosure_named_management" msgid="2307703784594859524">"This device is provided by <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="quick_settings_disclosure_management_named_vpn" msgid="4137564460025113168">"This device belongs to your organization and is connected to the internet through <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="quick_settings_disclosure_named_management_named_vpn" msgid="2169227918166358741">"This device belongs to <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> and is connected to the Internet through <xliff:g id="VPN_APP">%2$s</xliff:g>"</string>
+    <string name="quick_settings_disclosure_named_management_named_vpn" msgid="2169227918166358741">"This device belongs to <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> and is connected to the internet through <xliff:g id="VPN_APP">%2$s</xliff:g>"</string>
     <string name="quick_settings_disclosure_management" msgid="5515296598440684962">"This device belongs to your organization"</string>
     <string name="quick_settings_disclosure_named_management" msgid="3476472755775165827">"This device belongs to <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>"</string>
     <string name="quick_settings_disclosure_management_vpns" msgid="929181757984262902">"This device belongs to your organization and is connected to the internet through VPNs"</string>
-    <string name="quick_settings_disclosure_named_management_vpns" msgid="3312645578322079185">"This device belongs to <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> and is connected to the Internet through VPNs"</string>
+    <string name="quick_settings_disclosure_named_management_vpns" msgid="3312645578322079185">"This device belongs to <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> and is connected to the internet through VPNs"</string>
     <string name="quick_settings_disclosure_managed_profile_monitoring" msgid="1423899084754272514">"Your organization may monitor network traffic in your work profile"</string>
     <string name="quick_settings_disclosure_named_managed_profile_monitoring" msgid="8321469176706219860">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> may monitor network traffic in your work profile"</string>
     <string name="quick_settings_disclosure_managed_profile_network_activity" msgid="2636594621387832827">"Work profile network activity is visible to your IT admin"</string>
     <string name="quick_settings_disclosure_monitoring" msgid="8548019955631378680">"Network may be monitored"</string>
-    <string name="quick_settings_disclosure_vpns" msgid="3586175303518266301">"This device is connected to the Internet through VPNs"</string>
-    <string name="quick_settings_disclosure_managed_profile_named_vpn" msgid="153393105176944100">"Your work apps are connected to the Internet through <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="quick_settings_disclosure_personal_profile_named_vpn" msgid="451254750289172191">"Your personal apps are connected to the Internet through <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
-    <string name="quick_settings_disclosure_named_vpn" msgid="6191822916936028208">"This device is connected to the Internet through <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
+    <string name="quick_settings_disclosure_vpns" msgid="3586175303518266301">"This device is connected to the internet through VPNs"</string>
+    <string name="quick_settings_disclosure_managed_profile_named_vpn" msgid="153393105176944100">"Your work apps are connected to the internet through <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
+    <string name="quick_settings_disclosure_personal_profile_named_vpn" msgid="451254750289172191">"Your personal apps are connected to the internet through <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
+    <string name="quick_settings_disclosure_named_vpn" msgid="6191822916936028208">"This device is connected to the internet through <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
     <string name="monitoring_title_financed_device" msgid="3659962357973919387">"This device is provided by <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="monitoring_title_device_owned" msgid="7029691083837606324">"Device management"</string>
     <string name="monitoring_subtitle_vpn" msgid="800485258004629079">"VPN"</string>
@@ -410,22 +430,22 @@
     <string name="monitoring_button_view_policies" msgid="3869724835853502410">"View Policies"</string>
     <string name="monitoring_button_view_controls" msgid="8316440345340701117">"View controls"</string>
     <string name="monitoring_description_named_management" msgid="505833016545056036">"This device belongs to <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>.\n\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nFor more information, contact your IT admin."</string>
-    <string name="monitoring_financed_description_named_management" msgid="6108439201399938668">"<xliff:g id="ORGANIZATION_NAME_0">%1$s</xliff:g> may be able to access data associated with this device, manage apps and change this device\'s settings.\n\nIf you have questions, contact <xliff:g id="ORGANIZATION_NAME_1">%2$s</xliff:g>."</string>
+    <string name="monitoring_financed_description_named_management" msgid="6108439201399938668">"<xliff:g id="ORGANIZATION_NAME_0">%1$s</xliff:g> may be able to access data associated with this device, manage apps, and change this devices settings.\n\nIf you have questions, contact <xliff:g id="ORGANIZATION_NAME_1">%2$s</xliff:g>."</string>
     <string name="monitoring_description_management" msgid="4308879039175729014">"This device belongs to your organization.\n\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nFor more information, contact your IT admin."</string>
     <string name="monitoring_description_management_ca_certificate" msgid="7785013130658110130">"Your organization installed a certificate authority on this device. Your secure network traffic may be monitored or modified."</string>
     <string name="monitoring_description_managed_profile_ca_certificate" msgid="7904323416598435647">"Your organization installed a certificate authority in your work profile. Your secure network traffic may be monitored or modified."</string>
     <string name="monitoring_description_ca_certificate" msgid="448923057059097497">"A certificate authority is installed on this device. Your secure network traffic may be monitored or modified."</string>
     <string name="monitoring_description_management_network_logging" msgid="216983105036994771">"Your admin has turned on network logging, which monitors traffic on your device."</string>
     <string name="monitoring_description_managed_profile_network_logging" msgid="6932303843097006037">"Your admin has turned on network logging, which monitors traffic in your work profile but not in your personal profile."</string>
-    <string name="monitoring_description_named_vpn" msgid="7502657784155456414">"This device is connected to the Internet through <xliff:g id="VPN_APP">%1$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your IT admin."</string>
-    <string name="monitoring_description_two_named_vpns" msgid="6726394451199620634">"This device is connected to the Internet through <xliff:g id="VPN_APP_0">%1$s</xliff:g> and <xliff:g id="VPN_APP_1">%2$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your IT admin."</string>
-    <string name="monitoring_description_managed_profile_named_vpn" msgid="7254359257263069766">"Your work apps are connected to the Internet through <xliff:g id="VPN_APP">%1$s</xliff:g>. Your network activity in work apps, including emails and browsing data, is visible to your IT admin and VPN provider."</string>
-    <string name="monitoring_description_personal_profile_named_vpn" msgid="5083909710727365452">"Your personal apps are connected to the Internet through <xliff:g id="VPN_APP">%1$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your VPN provider."</string>
+    <string name="monitoring_description_named_vpn" msgid="7502657784155456414">"This device is connected to the internet through <xliff:g id="VPN_APP">%1$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your IT admin."</string>
+    <string name="monitoring_description_two_named_vpns" msgid="6726394451199620634">"This device is connected to the internet through <xliff:g id="VPN_APP_0">%1$s</xliff:g> and <xliff:g id="VPN_APP_1">%2$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your IT admin."</string>
+    <string name="monitoring_description_managed_profile_named_vpn" msgid="7254359257263069766">"Your work apps are connected to the internet through <xliff:g id="VPN_APP">%1$s</xliff:g>. Your network activity in work apps, including emails and browsing data, is visible to your IT admin and VPN provider."</string>
+    <string name="monitoring_description_personal_profile_named_vpn" msgid="5083909710727365452">"Your personal apps are connected to the internet through <xliff:g id="VPN_APP">%1$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your VPN provider."</string>
     <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string>
     <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Open VPN settings"</string>
-    <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"This device is managed by your parent. Your parent can see and manage information such as the apps that you use, your location and your screen time."</string>
+    <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"This device is managed by your parent. Your parent can see and manage information such as the apps you use, your location, and your screen time."</string>
     <string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string>
-    <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"Kept unlocked by trust agent"</string>
+    <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"Kept unlocked by TrustAgent"</string>
     <string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
     <string name="accessibility_volume_settings" msgid="1458961116951564784">"Sound settings"</string>
     <string name="volume_odi_captions_tip" msgid="8825655463280990941">"Automatically caption media"</string>
@@ -433,21 +453,21 @@
     <string name="volume_odi_captions_content_description" msgid="4172765742046013630">"Captions overlay"</string>
     <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"enable"</string>
     <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"disable"</string>
-    <string name="sound_settings" msgid="8874581353127418308">"Sound and vibration"</string>
+    <string name="sound_settings" msgid="8874581353127418308">"Sound &amp; vibration"</string>
     <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"Settings"</string>
     <string name="screen_pinning_title" msgid="9058007390337841305">"App is pinned"</string>
     <string name="screen_pinning_description" msgid="8699395373875667743">"This keeps it in view until you unpin. Touch &amp; hold Back and Overview to unpin."</string>
     <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"This keeps it in view until you unpin. Touch &amp; hold Back and Home to unpin."</string>
-    <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"This keeps it in view until you unpin. Swipe up and hold to unpin."</string>
+    <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"This keeps it in view until you unpin. Swipe up &amp; hold to unpin."</string>
     <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"This keeps it in view until you unpin. Touch &amp; hold Overview to unpin."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"This keeps it in view until you unpin. Touch &amp; hold Home to unpin."</string>
-    <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Personal data may be accessible, such as contacts and email content."</string>
+    <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Personal data may be accessible (such as contacts and email content)."</string>
     <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Pinned app may open other apps."</string>
-    <string name="screen_pinning_toast" msgid="8177286912533744328">"To unpin this app, touch and hold Back and Overview buttons"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"To unpin this app, touch and hold Back and Home buttons"</string>
-    <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"To unpin this app, swipe up and hold"</string>
+    <string name="screen_pinning_toast" msgid="8177286912533744328">"To unpin this app, touch &amp; hold Back and Overview buttons"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"To unpin this app, touch &amp; hold Back and Home buttons"</string>
+    <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"To unpin this app, swipe up &amp; hold"</string>
     <string name="screen_pinning_positive" msgid="3285785989665266984">"Got it"</string>
-    <string name="screen_pinning_negative" msgid="6882816864569211666">"No, thanks"</string>
+    <string name="screen_pinning_negative" msgid="6882816864569211666">"No thanks"</string>
     <string name="screen_pinning_start" msgid="7483998671383371313">"App pinned"</string>
     <string name="screen_pinning_exit" msgid="4553787518387346893">"App unpinned"</string>
     <string name="stream_voice_call" msgid="7468348170702375660">"Call"</string>
@@ -457,7 +477,7 @@
     <string name="stream_alarm" msgid="16058075093011694">"Alarm"</string>
     <string name="stream_notification" msgid="7930294049046243939">"Notification"</string>
     <string name="stream_bluetooth_sco" msgid="6234562365528664331">"Bluetooth"</string>
-    <string name="stream_dtmf" msgid="7322536356554673067">"Dual multi-tone frequency"</string>
+    <string name="stream_dtmf" msgid="7322536356554673067">"Dual multi tone frequency"</string>
     <string name="stream_accessibility" msgid="3873610336741987152">"Accessibility"</string>
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ring"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrate"</string>
@@ -486,9 +506,9 @@
     <string name="wallet_secondary_label_no_card" msgid="8488069304491125713">"Tap to open"</string>
     <string name="wallet_secondary_label_updating" msgid="5726130686114928551">"Updating"</string>
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string>
-    <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string>
+    <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards, please try again later"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scan QR code"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR code scanner"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Airplane mode"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -497,17 +517,17 @@
     <string name="accessibility_status_bar_hotspot" msgid="2888479317489131669">"Hotspot"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work profile"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Fun for some but not for all"</string>
-    <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customise the Android user interface. These experimental features may change, break or disappear in future releases. Proceed with caution."</string>
-    <string name="tuner_persistent_warning" msgid="230466285569307806">"These experimental features may change, break or disappear in future releases. Proceed with caution."</string>
+    <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customize the Android user interface. These experimental features may change, break, or disappear in future releases. Proceed with caution."</string>
+    <string name="tuner_persistent_warning" msgid="230466285569307806">"These experimental features may change, break, or disappear in future releases. Proceed with caution."</string>
     <string name="got_it" msgid="477119182261892069">"Got it"</string>
     <string name="tuner_toast" msgid="3812684836514766951">"Congrats! System UI Tuner has been added to Settings"</string>
-    <string name="remove_from_settings" msgid="633775561782209994">"Remove from settings"</string>
+    <string name="remove_from_settings" msgid="633775561782209994">"Remove from Settings"</string>
     <string name="remove_from_settings_prompt" msgid="551565437265615426">"Remove System UI Tuner from Settings and stop using all of its features?"</string>
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Turn on Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Turn on"</string>
     <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Power notification controls"</string>
-    <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"On – Face-based"</string>
+    <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"On - Face-based"</string>
     <string name="power_notification_controls_description" msgid="1334963837572708952">"With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. \n\n"<b>"Level 5"</b>" \n- Show at the top of the notification list \n- Allow full screen interruption \n- Always peek \n\n"<b>"Level 4"</b>" \n- Prevent full screen interruption \n- Always peek \n\n"<b>"Level 3"</b>" \n- Prevent full screen interruption \n- Never peek \n\n"<b>"Level 2"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound and vibration \n\n"<b>"Level 1"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound or vibrate \n- Hide from lock screen and status bar \n- Show at the bottom of the notification list \n\n"<b>"Level 0"</b>" \n- Block all notifications from the app"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Done"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Apply"</string>
@@ -520,10 +540,10 @@
     <string name="notification_channel_summary_default" msgid="3282930979307248890">"May ring or vibrate based on phone settings"</string>
     <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"May ring or vibrate based on phone settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Have the system determine if this notification should make sound or vibration"</string>
-    <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; promoted to default"</string>
-    <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; demoted to silent"</string>
-    <string name="notification_channel_summary_automatic_promoted" msgid="1301710305149590426">"&lt;b&gt;Status:&lt;/b&gt; ranked higher"</string>
-    <string name="notification_channel_summary_automatic_demoted" msgid="1831303964660807700">"&lt;b&gt;Status:&lt;/b&gt; ranked lower"</string>
+    <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; Promoted to Default"</string>
+    <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; Demoted to Silent"</string>
+    <string name="notification_channel_summary_automatic_promoted" msgid="1301710305149590426">"&lt;b&gt;Status:&lt;/b&gt; Ranked Higher"</string>
+    <string name="notification_channel_summary_automatic_demoted" msgid="1831303964660807700">"&lt;b&gt;Status:&lt;/b&gt; Ranked Lower"</string>
     <string name="notification_channel_summary_priority_baseline" msgid="46674690072551234">"Shows at the top of conversation notifications and as a profile picture on lock screen"</string>
     <string name="notification_channel_summary_priority_bubble" msgid="1275413109619074576">"Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble"</string>
     <string name="notification_channel_summary_priority_dnd" msgid="6665395023264154361">"Shows at the top of conversation notifications and as a profile picture on lock screen, interrupts Do Not Disturb"</string>
@@ -536,15 +556,15 @@
     <string name="notification_delegate_header" msgid="1264510071031479920">"Proxied notification"</string>
     <string name="notification_channel_dialog_title" msgid="6856514143093200019">"All <xliff:g id="APP_NAME">%1$s</xliff:g> notifications"</string>
     <string name="see_more_title" msgid="7409317011708185729">"See more"</string>
-    <string name="feedback_alerted" msgid="5192459808484271208">"This notification was automatically &lt;b&gt;promoted to default&lt;/b&gt; by the system."</string>
-    <string name="feedback_silenced" msgid="9116540317466126457">"This notification was automatically &lt;b&gt;demoted to silent&lt;/b&gt; by the system."</string>
+    <string name="feedback_alerted" msgid="5192459808484271208">"This notification was automatically &lt;b&gt;promoted to Default&lt;/b&gt; by the system."</string>
+    <string name="feedback_silenced" msgid="9116540317466126457">"This notification was automatically &lt;b&gt;demoted to Silent&lt;/b&gt; by the system."</string>
     <string name="feedback_promoted" msgid="2125562787759780807">"This notification was automatically &lt;b&gt;ranked higher&lt;/b&gt; in your shade."</string>
     <string name="feedback_demoted" msgid="951884763467110604">"This notification was automatically &lt;b&gt;ranked lower&lt;/b&gt; in your shade."</string>
     <string name="feedback_prompt" msgid="3656728972307896379">"Let the developer know your feedback. Was this correct?"</string>
     <string name="notification_channel_controls_opened_accessibility" msgid="6111817750774381094">"Notification controls for <xliff:g id="APP_NAME">%1$s</xliff:g> opened"</string>
     <string name="notification_channel_controls_closed_accessibility" msgid="1561909368876911701">"Notification controls for <xliff:g id="APP_NAME">%1$s</xliff:g> closed"</string>
     <string name="notification_more_settings" msgid="4936228656989201793">"More settings"</string>
-    <string name="notification_app_settings" msgid="8963648463858039377">"Customise"</string>
+    <string name="notification_app_settings" msgid="8963648463858039377">"Customize"</string>
     <string name="notification_conversation_bubble" msgid="2242180995373949022">"Show bubble"</string>
     <string name="notification_conversation_unbubble" msgid="6908427185031099868">"Remove bubbles"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
@@ -563,7 +583,7 @@
     <string name="keyboard_key_dpad_down" msgid="2110172278574325796">"Down"</string>
     <string name="keyboard_key_dpad_left" msgid="8329738048908755640">"Left"</string>
     <string name="keyboard_key_dpad_right" msgid="6282105433822321767">"Right"</string>
-    <string name="keyboard_key_dpad_center" msgid="4079412840715672825">"Centre"</string>
+    <string name="keyboard_key_dpad_center" msgid="4079412840715672825">"Center"</string>
     <string name="keyboard_key_tab" msgid="4592772350906496730">"Tab"</string>
     <string name="keyboard_key_space" msgid="6980847564173394012">"Space"</string>
     <string name="keyboard_key_enter" msgid="8633362970109751646">"Enter"</string>
@@ -573,7 +593,7 @@
     <string name="keyboard_key_media_next" msgid="8502476691227914952">"Next"</string>
     <string name="keyboard_key_media_previous" msgid="5637875709190955351">"Previous"</string>
     <string name="keyboard_key_media_rewind" msgid="3450387734224327577">"Rewind"</string>
-    <string name="keyboard_key_media_fast_forward" msgid="3572444327046911822">"Fast-Forward"</string>
+    <string name="keyboard_key_media_fast_forward" msgid="3572444327046911822">"Fast Forward"</string>
     <string name="keyboard_key_page_up" msgid="173914303254199845">"Page Up"</string>
     <string name="keyboard_key_page_down" msgid="9035902490071829731">"Page Down"</string>
     <string name="keyboard_key_forward_del" msgid="5325501825762733459">"Delete"</string>
@@ -585,10 +605,10 @@
     <string name="notif_inline_reply_remove_attachment_description" msgid="7954075334095405429">"Remove attachment"</string>
     <string name="keyboard_shortcut_group_system" msgid="1583416273777875970">"System"</string>
     <string name="keyboard_shortcut_group_system_home" msgid="7465138628692109907">"Home"</string>
-    <string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"Recent"</string>
+    <string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"Recents"</string>
     <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Back"</string>
     <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notifications"</string>
-    <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Keyboard shortcuts"</string>
+    <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Keyboard Shortcuts"</string>
     <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Switch keyboard layout"</string>
     <string name="keyboard_shortcut_group_applications" msgid="7386239431100651266">"Applications"</string>
     <string name="keyboard_shortcut_group_applications_assist" msgid="771606231466098742">"Assist"</string>
@@ -630,7 +650,7 @@
     <string name="save" msgid="3392754183673848006">"Save"</string>
     <string name="reset" msgid="8715144064608810383">"Reset"</string>
     <string name="clipboard" msgid="8517342737534284617">"Clipboard"</string>
-    <string name="accessibility_key" msgid="3471162841552818281">"Customised navigation button"</string>
+    <string name="accessibility_key" msgid="3471162841552818281">"Custom navigation button"</string>
     <string name="left_keycode" msgid="8211040899126637342">"Left keycode"</string>
     <string name="right_keycode" msgid="2480715509844798438">"Right keycode"</string>
     <string name="left_icon" msgid="5036278531966897006">"Left icon"</string>
@@ -642,7 +662,7 @@
     <string name="qs_edit" msgid="5583565172803472437">"Edit"</string>
     <string name="tuner_time" msgid="2450785840990529997">"Time"</string>
   <string-array name="clock_options">
-    <item msgid="3986445361435142273">"Show hours, minutes and seconds"</item>
+    <item msgid="3986445361435142273">"Show hours, minutes, and seconds"</item>
     <item msgid="1271006222031257266">"Show hours and minutes (default)"</item>
     <item msgid="6135970080453877218">"Don\'t show this icon"</item>
   </string-array>
@@ -669,7 +689,7 @@
     <string name="accessibility_quick_settings_collapse" msgid="4674876336725041982">"Close quick settings."</string>
     <string name="accessibility_quick_settings_user" msgid="505821942882668619">"Signed in as <xliff:g id="ID_1">%s</xliff:g>"</string>
     <string name="accessibility_quick_settings_choose_user_action" msgid="4554388498186576087">"choose user"</string>
-    <string name="data_connection_no_internet" msgid="691058178914184544">"No Internet"</string>
+    <string name="data_connection_no_internet" msgid="691058178914184544">"No internet"</string>
     <string name="accessibility_quick_settings_open_settings" msgid="536838345505030893">"Open <xliff:g id="ID_1">%s</xliff:g> settings."</string>
     <string name="accessibility_quick_settings_edit" msgid="1523745183383815910">"Edit order of settings."</string>
     <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Power menu"</string>
@@ -677,14 +697,14 @@
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Lock screen"</string>
     <string name="thermal_shutdown_title" msgid="2702966892682930264">"Phone turned off due to heat"</string>
     <string name="thermal_shutdown_message" msgid="6142269839066172984">"Your phone is now running normally.\nTap for more info"</string>
-    <string name="thermal_shutdown_dialog_message" msgid="6745684238183492031">"Your phone was too hot, so it turned off to cool down. Your phone is now running normally.\n\nYour phone may get too hot if you:\n	• Use resource-intensive apps (such as gaming, video or navigation apps)\n	• Download or upload large files\n	• Use your phone in high temperatures"</string>
+    <string name="thermal_shutdown_dialog_message" msgid="6745684238183492031">"Your phone was too hot, so it turned off to cool down. Your phone is now running normally.\n\nYour phone may get too hot if you:\n	• Use resource-intensive apps (such as gaming, video, or navigation apps)\n	• Download or upload large files\n	• Use your phone in high temperatures"</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"See care steps"</string>
     <string name="high_temp_title" msgid="2218333576838496100">"Phone is getting warm"</string>
-    <string name="high_temp_notif_message" msgid="1277346543068257549">"Some features are limited while phone cools down.\nTap for more info"</string>
-    <string name="high_temp_dialog_message" msgid="3793606072661253968">"Your phone will automatically try to cool down. You can still use your phone, but it may run more slowly.\n\nOnce your phone has cooled down, it will run normally."</string>
+    <string name="high_temp_notif_message" msgid="1277346543068257549">"Some features limited while phone cools down.\nTap for more info"</string>
+    <string name="high_temp_dialog_message" msgid="3793606072661253968">"Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally."</string>
     <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"See care steps"</string>
     <string name="high_temp_alarm_title" msgid="8654754369605452169">"Unplug your device"</string>
-    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Your device is getting warm near the charging port. If it’s connected to a charger or USB accessory, unplug it and take care as the cable may also be warm."</string>
+    <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Your device is getting warm near the charging port. If it’s connected to a charger or USB accessory, unplug it, and take care as the cable may also be warm."</string>
     <string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"See care steps"</string>
     <string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Left shortcut"</string>
     <string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Right shortcut"</string>
@@ -710,11 +730,11 @@
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> running"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"App opened without being installed."</string>
-    <string name="instant_apps_message_with_help" msgid="1816952263531203932">"App opened without being installed. Tap to find out more."</string>
+    <string name="instant_apps_message_with_help" msgid="1816952263531203932">"App opened without being installed. Tap to learn more."</string>
     <string name="app_info" msgid="5153758994129963243">"App info"</string>
     <string name="go_to_web" msgid="636673528981366511">"Go to browser"</string>
     <string name="mobile_data" msgid="4564407557775397216">"Mobile data"</string>
-    <string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> – <xliff:g id="ID_2">%2$s</xliff:g>"</string>
+    <string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> — <xliff:g id="ID_2">%2$s</xliff:g>"</string>
     <string name="mobile_carrier_text_format" msgid="8912204177152950766">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="MOBILE_DATA_TYPE">%2$s</xliff:g>"</string>
     <string name="wifi_is_off" msgid="5389597396308001471">"Wi-Fi is off"</string>
     <string name="bt_is_off" msgid="7436344904889461591">"Bluetooth is off"</string>
@@ -725,18 +745,22 @@
     <string name="running_foreground_services_title" msgid="5137313173431186685">"Apps running in background"</string>
     <string name="running_foreground_services_msg" msgid="3009459259222695385">"Tap for details on battery and data usage"</string>
     <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" 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>
-    <string name="slice_permission_text_2" msgid="6758906940360746983">"– It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="slice_permission_text_1" msgid="6675965177075443714">"- It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="slice_permission_text_2" msgid="6758906940360746983">"- It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="slice_permission_checkbox" msgid="4242888137592298523">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
     <string name="slice_permission_allow" msgid="6340449521277951123">"Allow"</string>
     <string name="slice_permission_deny" msgid="6870256451658176895">"Deny"</string>
     <string name="auto_saver_title" msgid="6873691178754086596">"Tap to schedule Battery Saver"</string>
     <string name="auto_saver_text" msgid="3214960308353838764">"Turn on when battery is likely to run out"</string>
-    <string name="no_auto_saver_action" msgid="7467924389609773835">"No, thanks"</string>
+    <string name="no_auto_saver_action" msgid="7467924389609773835">"No thanks"</string>
     <string name="heap_dump_tile_name" msgid="2464189856478823046">"Dump SysUI Heap"</string>
     <string name="ongoing_privacy_dialog_a11y_title" msgid="2205794093673327974">"In use"</string>
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Applications are using your <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
@@ -755,8 +779,8 @@
     <string name="privacy_type_media_projection" msgid="8136723828804251547">"screen recording"</string>
     <string name="music_controls_no_title" msgid="4166497066552290938">"No title"</string>
     <string name="inattentive_sleep_warning_title" msgid="3891371591713990373">"Standby"</string>
-    <string name="magnification_window_title" msgid="4863914360847258333">"Magnification window"</string>
-    <string name="magnification_controls_title" msgid="8421106606708891519">"Magnification window controls"</string>
+    <string name="magnification_window_title" msgid="4863914360847258333">"Magnification Window"</string>
+    <string name="magnification_controls_title" msgid="8421106606708891519">"Magnification Window Controls"</string>
     <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Zoom in"</string>
     <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Zoom out"</string>
     <string name="accessibility_control_move_up" msgid="6622825494014720136">"Move up"</string>
@@ -767,6 +791,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Magnify full screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Switch"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Allow diagonal scrolling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Resize"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Change magnification type"</string>
@@ -783,28 +809,31 @@
     <string name="accessibility_magnification_close" msgid="1099965835844673375">"Close"</string>
     <string name="accessibility_magnifier_edit" msgid="1522877239671820636">"Edit"</string>
     <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_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>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# control added.}other{# controls added.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Removed"</string>
-    <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favourited"</string>
-    <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favourited, position <xliff:g id="NUMBER">%d</xliff:g>"</string>
-    <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Unfavourited"</string>
-    <string name="accessibility_control_change_favorite" msgid="2943178027582253261">"favourite"</string>
-    <string name="accessibility_control_change_unfavorite" msgid="6997408061750740327">"unfavourite"</string>
+    <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favorited"</string>
+    <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favorited, position <xliff:g id="NUMBER">%d</xliff:g>"</string>
+    <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Unfavorited"</string>
+    <string name="accessibility_control_change_favorite" msgid="2943178027582253261">"favorite"</string>
+    <string name="accessibility_control_change_unfavorite" msgid="6997408061750740327">"unfavorite"</string>
     <string name="accessibility_control_move" msgid="8980344493796647792">"Move to position <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="controls_favorite_default_title" msgid="967742178688938137">"Controls"</string>
     <string name="controls_favorite_subtitle" msgid="6481675111056961083">"Choose controls to access from Quick Settings"</string>
-    <string name="controls_favorite_rearrange" msgid="5616952398043063519">"Hold and drag to rearrange controls"</string>
+    <string name="controls_favorite_rearrange" msgid="5616952398043063519">"Hold &amp; drag to rearrange controls"</string>
     <string name="controls_favorite_removed" msgid="5276978408529217272">"All controls removed"</string>
     <string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"Changes not saved"</string>
     <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"See other apps"</string>
@@ -815,11 +844,11 @@
     <string name="controls_dialog_ok" msgid="2770230012857881822">"Add"</string>
     <string name="controls_dialog_message" msgid="342066938390663844">"Suggested by <xliff:g id="APP">%s</xliff:g>"</string>
     <string name="controls_tile_locked" msgid="731547768182831938">"Device locked"</string>
-    <string name="controls_settings_show_controls_dialog_title" msgid="3357852503553809554">"Show and control devices from the lock screen?"</string>
-    <string name="controls_settings_show_controls_dialog_message" msgid="7666211700524587969">"You can add controls for your external devices to the lock screen.\n\nYour device app may allow you to control some devices without unlocking your phone or tablet.\n\nYou can make changes at any time in Settings."</string>
-    <string name="controls_settings_trivial_controls_dialog_title" msgid="7593188157655036677">"Control devices from the lock screen?"</string>
+    <string name="controls_settings_show_controls_dialog_title" msgid="3357852503553809554">"Show and control devices from lock screen?"</string>
+    <string name="controls_settings_show_controls_dialog_message" msgid="7666211700524587969">"You can add controls for your external devices to the lock screen.\n\nYour device app may allow you to control some devices without unlocking your phone or tablet.\n\nYou can make changes any time in Settings."</string>
+    <string name="controls_settings_trivial_controls_dialog_title" msgid="7593188157655036677">"Control devices from lock screen?"</string>
     <string name="controls_settings_trivial_controls_dialog_message" msgid="237183787721917586">"You can control some devices without unlocking your phone or tablet.\n\nYour device app determines which devices can be controlled in this way."</string>
-    <string name="controls_settings_dialog_neutral_button" msgid="4514446354793124140">"No, thanks"</string>
+    <string name="controls_settings_dialog_neutral_button" msgid="4514446354793124140">"No thanks"</string>
     <string name="controls_settings_dialog_positive_button" msgid="436070672551674863">"Yes"</string>
     <string name="controls_pin_use_alphanumeric" msgid="8478371861023048414">"PIN contains letters or symbols"</string>
     <string name="controls_pin_verify" msgid="3452778292918877662">"Verify <xliff:g id="DEVICE">%s</xliff:g>"</string>
@@ -854,7 +883,7 @@
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
-    <string name="controls_error_removed_message" msgid="2885911717034750542">"Couldn’t access <xliff:g id="DEVICE">%1$s</xliff:g>. Check the <xliff:g id="APPLICATION">%2$s</xliff:g> app to make sure that the control is still available and that the app settings haven’t changed."</string>
+    <string name="controls_error_removed_message" msgid="2885911717034750542">"Couldn’t access <xliff:g id="DEVICE">%1$s</xliff:g>. Check the <xliff:g id="APPLICATION">%2$s</xliff:g> app to make sure the control is still available and that the app settings haven’t changed."</string>
     <string name="controls_open_app" msgid="483650971094300141">"Open app"</string>
     <string name="controls_error_generic" msgid="352500456918362905">"Can’t load status"</string>
     <string name="controls_error_failed" msgid="960228639198558525">"Error, try again"</string>
@@ -862,7 +891,7 @@
     <string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string>
-    <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string>
+    <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 device selected"</string>
     <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string>
     <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(disconnected)"</string>
     <string name="media_output_dialog_connect_failed" msgid="3080972621975339387">"Can\'t switch. Tap to try again."</string>
@@ -874,9 +903,9 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"How broadcasting works"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Broadcast"</string>
-    <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"People near you with compatible Bluetooth devices can listen to the media that you\'re broadcasting"</string>
+    <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"People near you with compatible Bluetooth devices can listen to the media you\'re broadcasting"</string>
     <string name="media_output_broadcasting_message" msgid="4150299923404886073">"To listen to your broadcast, people nearby with compatible Bluetooth devices can scan your QR code or use your broadcast name and password"</string>
-    <string name="media_output_broadcast_name" msgid="8786127091542624618">"Broadcast name"</string>
+    <string name="media_output_broadcast_name" msgid="8786127091542624618">"Broadcast Name"</string>
     <string name="media_output_broadcast_code" msgid="870795639644728542">"Password"</string>
     <string name="media_output_broadcast_dialog_save" msgid="7910865591430010198">"Save"</string>
     <string name="media_output_broadcast_starting" msgid="8130153654166235557">"Starting…"</string>
@@ -887,21 +916,21 @@
     <string name="build_number_copy_toast" msgid="877720921605503046">"Build number copied to clipboard."</string>
     <string name="basic_status" msgid="2315371112182658176">"Open conversation"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Conversation widgets"</string>
-    <string name="select_conversation_text" msgid="3376048251434956013">"Tap a conversation to add it to your home screen"</string>
+    <string name="select_conversation_text" msgid="3376048251434956013">"Tap a conversation to add it to your Home screen"</string>
     <string name="no_conversations_text" msgid="5354115541282395015">"Your recent conversations will show up here"</string>
     <string name="priority_conversations" msgid="3967482288896653039">"Priority conversations"</string>
     <string name="recent_conversations" msgid="8531874684782574622">"Recent conversations"</string>
     <string name="days_timestamp" msgid="5821854736213214331">"<xliff:g id="DURATION">%1$s</xliff:g> days ago"</string>
     <string name="one_week_timestamp" msgid="4925600765473875590">"1 week ago"</string>
     <string name="two_weeks_timestamp" msgid="9111801081871962155">"2 weeks ago"</string>
-    <string name="over_one_week_timestamp" msgid="3770560704420807142">"More than 1 week ago"</string>
-    <string name="over_two_weeks_timestamp" msgid="6300507859007874050">"More than 2 weeks ago"</string>
+    <string name="over_one_week_timestamp" msgid="3770560704420807142">"Over 1 week ago"</string>
+    <string name="over_two_weeks_timestamp" msgid="6300507859007874050">"Over 2 weeks ago"</string>
     <string name="birthday_status" msgid="2596961629465396761">"Birthday"</string>
-    <string name="birthday_status_content_description" msgid="682836371128282925">"It\'s <xliff:g id="NAME">%1$s</xliff:g>\'s birthday"</string>
+    <string name="birthday_status_content_description" msgid="682836371128282925">"It\'s <xliff:g id="NAME">%1$s</xliff:g>s birthday"</string>
     <string name="upcoming_birthday_status" msgid="2005452239256870351">"Birthday soon"</string>
-    <string name="upcoming_birthday_status_content_description" msgid="2165036816803797148">"It\'s <xliff:g id="NAME">%1$s</xliff:g>\'s birthday soon"</string>
+    <string name="upcoming_birthday_status_content_description" msgid="2165036816803797148">"It\'s <xliff:g id="NAME">%1$s</xliff:g>s birthday soon"</string>
     <string name="anniversary_status" msgid="1790034157507590838">"Anniversary"</string>
-    <string name="anniversary_status_content_description" msgid="8212171790843327442">"It\'s <xliff:g id="NAME">%1$s</xliff:g>\'s anniversary"</string>
+    <string name="anniversary_status_content_description" msgid="8212171790843327442">"It\'s <xliff:g id="NAME">%1$s</xliff:g>s anniversary"</string>
     <string name="location_status" msgid="1294990572202541812">"Sharing location"</string>
     <string name="location_status_content_description" msgid="2982386178160071305">"<xliff:g id="NAME">%1$s</xliff:g> is sharing location"</string>
     <string name="new_story_status" msgid="9012195158584846525">"New story"</string>
@@ -914,7 +943,7 @@
     <string name="status_before_loading" msgid="1500477307859631381">"Content will show up soon"</string>
     <string name="missed_call" msgid="4228016077700161689">"Missed call"</string>
     <string name="messages_count_overflow_indicator" msgid="7850934067082006043">"<xliff:g id="NUMBER">%d</xliff:g>+"</string>
-    <string name="people_tile_description" msgid="8154966188085545556">"See recent messages, missed calls and status updates"</string>
+    <string name="people_tile_description" msgid="8154966188085545556">"See recent messages, missed calls, and status updates"</string>
     <string name="people_tile_title" msgid="6589377493334871272">"Conversation"</string>
     <string name="paused_by_dnd" msgid="7856941866433556428">"Paused by Do Not Disturb"</string>
     <string name="new_notification_text_content_description" msgid="2915029960094389291">"<xliff:g id="NAME">%1$s</xliff:g> sent a message: <xliff:g id="NOTIFICATION">%2$s</xliff:g>"</string>
@@ -925,14 +954,16 @@
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string>
-    <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Authenticate"</string>
+    <string name="accessibility_authenticate_hint" msgid="798914151813205721">"authenticate"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string>
     <string name="keyguard_try_fingerprint" msgid="2825130772993061165">"Use fingerprint to open"</string>
     <string name="accessibility_fingerprint_bouncer" msgid="7189102492498735519">"Authentication required. Touch the fingerprint sensor to authenticate."</string>
     <string name="ongoing_phone_call_content_description" msgid="5332334388483099947">"Ongoing phone call"</string>
     <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="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>
@@ -944,7 +975,7 @@
     <string name="wifi_failed_connect_message" msgid="4161863112079000071">"Failed to connect to network"</string>
     <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi‑Fi won’t auto-connect for now"</string>
     <string name="see_all_networks" msgid="3773666844913168122">"See all"</string>
-    <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect Ethernet"</string>
+    <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect ethernet"</string>
     <string name="wifi_scan_notify_message" msgid="3753839537448621794">"To improve device experience, apps and services can still scan for Wi‑Fi networks at any time, even when Wi‑Fi is off. You can change this in Wi‑Fi scanning settings. "<annotation id="link">"Change"</annotation></string>
     <string name="turn_off_airplane_mode" msgid="8425587763226548579">"Turn off Airplane mode"</string>
     <string name="qs_tile_request_dialog_text" msgid="3501359944139877694">"<xliff:g id="APPNAME">%1$s</xliff:g> wants to add the following tile to Quick Settings"</string>
@@ -968,13 +999,13 @@
     <string name="clipboard_text_copied" msgid="5100836834278976679">"Text copied"</string>
     <string name="clipboard_image_copied" msgid="3793365360174328722">"Image copied"</string>
     <string name="clipboard_content_copied" msgid="144452398567828145">"Content copied"</string>
-    <string name="clipboard_editor" msgid="2971197550401892843">"Clipboard editor"</string>
+    <string name="clipboard_editor" msgid="2971197550401892843">"Clipboard Editor"</string>
     <string name="clipboard_overlay_window_name" msgid="6450043652167357664">"Clipboard"</string>
     <string name="clipboard_image_preview" msgid="2156475174343538128">"Image preview"</string>
     <string name="clipboard_edit" msgid="4500155216174011640">"edit"</string>
     <string name="add" msgid="81036585205287996">"Add"</string>
     <string name="manage_users" msgid="1823875311934643849">"Manage users"</string>
-    <string name="drag_split_not_supported" msgid="4326847447699729722">"This notification does not support dragging to Split screen."</string>
+    <string name="drag_split_not_supported" msgid="4326847447699729722">"This notification does not support dragging to Splitscreen."</string>
     <string name="dream_overlay_status_bar_wifi_off" msgid="4497069245055003582">"Wi‑Fi unavailable"</string>
     <string name="dream_overlay_status_bar_priority_mode" msgid="5428462123314728739">"Priority mode"</string>
     <string name="dream_overlay_status_bar_alarm_set" msgid="566707328356590886">"Alarm set"</string>
@@ -992,4 +1023,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Allow one-time access"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Don’t allow"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings_tv.xml b/packages/SystemUI/res/values-en-rCA/strings_tv.xml
index e97dbe4..a628846 100644
--- a/packages/SystemUI/res/values-en-rCA/strings_tv.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings_tv.xml
@@ -23,13 +23,13 @@
     <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN is disconnected"</string>
     <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
     <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notifications"</string>
-    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No notifications"</string>
+    <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No Notifications"</string>
     <string name="mic_recording_announcement" msgid="7587123608060316575">"Microphone is recording"</string>
     <string name="camera_recording_announcement" msgid="7240177719403759112">"Camera is recording"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera and microphone are recording"</string>
+    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera and Microphone are recording"</string>
     <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Microphone stopped recording"</string>
     <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Camera stopped recording"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera and microphone stopped recording"</string>
+    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera and Microphone stopped recording"</string>
     <string name="screen_recording_announcement" msgid="2996750593472241520">"Screen recording started"</string>
     <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Screen recording stopped"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index e7166ac..d036b88 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Always allow from this computer"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Allow"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB debugging not allowed"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to an admin user."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Do you want to change the system language to <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"System language change requested by another device"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Change language"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Always allow on this network"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Allow"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Wireless debugging not allowed"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"The user currently signed in to this device can’t turn on wireless debugging. To use this feature, switch to the primary user."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"The user currently signed in to this device can’t turn on wireless debugging. To use this feature, switch to an admin user."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB port disabled"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"To protect your device from liquid or debris, the USB port is disabled and won’t detect any accessories.\n\nYou’ll be notified when it’s OK to use the USB port again."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB port enabled to detect chargers and accessories"</string>
@@ -125,7 +125,7 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR code scanner"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Unlock"</string>
+    <string name="accessibility_unlock_button" msgid="3613812140816244310">"Unlocked"</string>
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Device locked"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanning face"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Send"</string>
@@ -168,6 +168,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +180,10 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Aeroplane mode"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN on."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Battery <xliff:g id="NUMBER">%d</xliff:g> per cent."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$s</xliff:g> percentage, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$d</xliff:g> percentage, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Battery charging, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> percent."</string>
+    <string name="accessibility_battery_level_charging_paused" msgid="1716051308782906917">"Battery <xliff:g id="PERCENTAGE">%d</xliff:g> per cent. Charging paused for battery protection."</string>
+    <string name="accessibility_battery_level_charging_paused_with_estimate" msgid="4006089349465741762">"Battery <xliff:g id="PERCENTAGE">%1$d</xliff:g> per cent, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage. Charging paused for battery protection."</string>
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"See all notifications"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter enabled."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ringer vibrate."</string>
@@ -248,7 +252,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>
@@ -303,6 +307,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microphone available"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Camera available"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microphone and camera available"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microphone turned on"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microphone turned off"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Microphone is enabled for all apps and services."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Microphone access is disabled for all apps and services. You can enable microphone access in Settings &gt; Privacy &gt; Microphone."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Microphone access is disabled for all apps and services. You can change this in Settings &gt; Privacy &gt; Microphone."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Camera turned on"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Camera turned off"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Camera is enabled for all apps and services."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Camera access is disabled for all apps and services."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"To use the microphone button, enable microphone access in Settings."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Open settings."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Other device"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Toggle Overview"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers you specify. You\'ll still hear anything you choose to play including music, videos and games."</string>
@@ -373,6 +388,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Allow this app to share or record?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"When you\'re sharing, recording or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"When you\'re sharing, recording or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages or other sensitive information."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
@@ -488,7 +508,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scan QR code"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR code scanner"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +747,10 @@
     <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>
+    <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 won\'t 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>
@@ -767,6 +791,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Magnify full screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Switch"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Allow diagonal scrolling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Resize"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Change magnification type"</string>
@@ -785,12 +811,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. 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>
+    <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 +962,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>
@@ -992,4 +1023,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Allow one-time access"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Don’t allow"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index e7166ac..d036b88 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Always allow from this computer"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Allow"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB debugging not allowed"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to an admin user."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Do you want to change the system language to <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"System language change requested by another device"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Change language"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Always allow on this network"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Allow"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Wireless debugging not allowed"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"The user currently signed in to this device can’t turn on wireless debugging. To use this feature, switch to the primary user."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"The user currently signed in to this device can’t turn on wireless debugging. To use this feature, switch to an admin user."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB port disabled"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"To protect your device from liquid or debris, the USB port is disabled and won’t detect any accessories.\n\nYou’ll be notified when it’s OK to use the USB port again."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB port enabled to detect chargers and accessories"</string>
@@ -125,7 +125,7 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR code scanner"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Unlock"</string>
+    <string name="accessibility_unlock_button" msgid="3613812140816244310">"Unlocked"</string>
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Device locked"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanning face"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Send"</string>
@@ -168,6 +168,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +180,10 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Aeroplane mode"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN on."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Battery <xliff:g id="NUMBER">%d</xliff:g> per cent."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$s</xliff:g> percentage, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$d</xliff:g> percentage, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Battery charging, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> percent."</string>
+    <string name="accessibility_battery_level_charging_paused" msgid="1716051308782906917">"Battery <xliff:g id="PERCENTAGE">%d</xliff:g> per cent. Charging paused for battery protection."</string>
+    <string name="accessibility_battery_level_charging_paused_with_estimate" msgid="4006089349465741762">"Battery <xliff:g id="PERCENTAGE">%1$d</xliff:g> per cent, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage. Charging paused for battery protection."</string>
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"See all notifications"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter enabled."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ringer vibrate."</string>
@@ -248,7 +252,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>
@@ -303,6 +307,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microphone available"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Camera available"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microphone and camera available"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microphone turned on"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microphone turned off"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Microphone is enabled for all apps and services."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Microphone access is disabled for all apps and services. You can enable microphone access in Settings &gt; Privacy &gt; Microphone."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Microphone access is disabled for all apps and services. You can change this in Settings &gt; Privacy &gt; Microphone."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Camera turned on"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Camera turned off"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Camera is enabled for all apps and services."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Camera access is disabled for all apps and services."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"To use the microphone button, enable microphone access in Settings."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Open settings."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Other device"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Toggle Overview"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers you specify. You\'ll still hear anything you choose to play including music, videos and games."</string>
@@ -373,6 +388,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Allow this app to share or record?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"When you\'re sharing, recording or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"When you\'re sharing, recording or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages or other sensitive information."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
@@ -488,7 +508,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scan QR code"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR code scanner"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +747,10 @@
     <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>
+    <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 won\'t 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>
@@ -767,6 +791,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Magnify full screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Switch"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Allow diagonal scrolling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Resize"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Change magnification type"</string>
@@ -785,12 +811,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. 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>
+    <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 +962,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>
@@ -992,4 +1023,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Allow one-time access"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Don’t allow"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index b55b744..4896ad4 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‎‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‏‏‎‏‎‏‏‎‎‏‎‏‏‏‎‎‎Always allow from this computer‎‏‎‎‏‎"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎Allow‎‏‎‎‏‎"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‎‎‎‎‎‏‏‏‎‎‏‏‏‏‎‏‎‏‎‏‏‎‎‏‏‎‏‎‏‎‎‎‎‎‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‎‎USB debugging not allowed‎‏‎‎‏‎"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‎‏‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‏‏‎‎‏‏‏‏‏‎‎‎‎‏‎‎‏‎‏‎‏‎‏‏‎‎‎The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user.‎‏‎‎‏‎"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‏‎‏‎‎‏‎‏‏‎‎‏‏‎‏‏‏‏‎‏‏‎‏‏‏‎‏‎‏‎The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to an admin user.‎‏‎‎‏‎"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‏‎‏‎‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‎‎Do you want to change the system language to ‎‏‎‎‏‏‎<xliff:g id="LANGUAGE">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎System language change requested by another device‎‏‎‎‏‎"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‏‎‏‏‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‏‎‏‎‏‎‏‎‎Change language‎‏‎‎‏‎"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‎‏‎‎‏‎‏‏‎‎‎‏‎‎‏‎‎‏‏‏‎‎‏‏‎Always allow on this network‎‏‎‎‏‎"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‎‏‎‏‏‏‎‎‏‎‎‏‎‏‏‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‏‏‏‎‎Allow‎‏‎‎‏‎"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‏‏‎‎‎‏‎‎‎‎‏‏‎‎‎‏‏‎‏‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‏‎‏‏‏‎‏‎Wireless debugging not allowed‎‏‎‎‏‎"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‎‏‏‎‏‎‎‏‏‎‏‎‏‎‏‎‎‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‎‎‏‏‏‎The user currently signed in to this device can’t turn on wireless debugging. To use this feature, switch to the primary user.‎‏‎‎‏‎"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‏‎‏‏‎‏‏‎‎‎‏‏‏‎‎‏‏‎‎‏‎‎‏‎‏‎‎‎‏‎‎‎‎‏‎The user currently signed in to this device can’t turn on wireless debugging. To use this feature, switch to an admin user.‎‏‎‎‏‎"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‎‎‎‎‏‏‎‏‎‎‎‏‎‎‏‎‎‏‎‏‏‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‎USB port disabled‎‏‎‎‏‎"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‏‎‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‏‎‏‏‏‎‏‎‎‏‏‏‏‎‏‎To protect your device from liquid or debris, the USB port is disabled and won’t detect any accessories.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎You’ll be notified when it’s okay to use the USB port again.‎‏‎‎‏‎"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‏‎‏‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎USB port enabled to detect chargers and accessories‎‏‎‎‏‎"</string>
@@ -125,7 +125,7 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎‎‏‎‎‏‎‏‏‏‎‏‏‎‎‎‏‏‏‎‎Voice Assist‎‏‎‎‏‎"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‎‎‏‏‎‏‎‏‎‎‎‏‏‎‎‏‎‏‎‏‎‏‏‎‎‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‏‏‎Wallet‎‏‎‎‏‎"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‎‎‏‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‎‏‎‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‎‎‏‎‏‏‎QR Code Scanner‎‏‎‎‏‎"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‎‎‎‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‎‏‏‎‎‎‏‏‎‏‏‎‏‎Unlock‎‏‎‎‏‎"</string>
+    <string name="accessibility_unlock_button" msgid="3613812140816244310">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‎‎‏‏‎‏‏‎‏‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‏‎‏‏‎‎‎‏‎‎‏‏‏‏‎‎‏‎‏‎‏‏‎‎Unlocked‎‏‎‎‏‎"</string>
     <string name="accessibility_lock_icon" msgid="661492842417875775">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‎Device locked‎‏‎‎‏‎"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‏‏‏‎‏‏‏‏‏‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‎‏‏‎Scanning face‎‏‎‎‏‎"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‎‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‏‎‏‎‎‎‎‏‎‎‏‏‎‏‎‎‏‎‏‎Send‎‏‎‎‏‎"</string>
@@ -168,6 +168,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‏‏‎‎‎‏‎‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‏‎‎‎‎‏‏‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎Can’t recognize face‎‏‎‎‏‎"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‎‏‏‏‏‎‎‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‎‎Use fingerprint instead‎‏‎‎‏‎"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‎‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎Bluetooth connected.‎‏‎‎‏‎"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‎‎‏‏‏‏‎‎‎‎‎‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‏‏‎‎‏‏‏‏‎‎‎‎Battery percentage unknown.‎‏‎‎‏‎"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‏‎‎‏‎‎‎‏‎‎‎‏‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‎‏‏‏‎‏‏‏‏‎‎Connected to ‎‏‎‎‏‏‎<xliff:g id="BLUETOOTH">%s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
@@ -178,8 +180,10 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‏‎‎‎‏‏‏‏‎‏‏‏‏‎‎‎‎‎‏‏‎‎‎‏‎‏‏‏‎‏‏‎‎‎‎‎‏‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎Airplane mode.‎‏‎‎‏‎"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‏‏‎‏‏‏‎‏‎‎‏‎‎‎‏‎‎‎‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‏‏‎VPN on.‎‏‎‎‏‎"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‎‎‏‎‎‏‎‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎‏‏‎‎‎‏‏‎‏‎‎‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎Battery ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%d</xliff:g>‎‏‎‎‏‏‏‎ percent.‎‏‎‎‏‎"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‎‏‎‏‏‎‎‎‏‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‎Battery ‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ percent, about ‎‏‎‎‏‏‎<xliff:g id="TIME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ left based on your usage‎‏‎‎‏‎"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‎‏‎‏‏‎‎‎‏‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‎Battery ‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%1$d</xliff:g>‎‏‎‎‏‏‏‎ percent, about ‎‏‎‎‏‏‎<xliff:g id="TIME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ left based on your usage‎‏‎‎‏‎"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎‏‏‎‏‏‎‏‎‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‎‏‎‎‏‏‎‎‏‎‎‎Battery charging, ‎‏‎‎‏‏‎<xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>‎‏‎‎‏‏‏‎ percent.‎‏‎‎‏‎"</string>
+    <string name="accessibility_battery_level_charging_paused" msgid="1716051308782906917">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‎‎‎‏‎‎‎‏‎‏‏‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎‏‎‏‎Battery ‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%d</xliff:g>‎‏‎‎‏‏‏‎ percent. Charging paused for battery protection.‎‏‎‎‏‎"</string>
+    <string name="accessibility_battery_level_charging_paused_with_estimate" msgid="4006089349465741762">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‏‏‎‎‎‏‎‏‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‎Battery ‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%1$d</xliff:g>‎‏‎‎‏‏‏‎ percent, about ‎‏‎‎‏‏‎<xliff:g id="TIME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ left based on your usage. Charging paused for battery protection.‎‏‎‎‏‎"</string>
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‎‎‎‎‎See all notifications‎‏‎‎‏‎"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‏‎‏‎‏‏‎‏‎‎‏‏‏‎‎‏‏‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‏‏‏‏‏‎‎TeleTypewriter enabled.‎‏‎‎‏‎"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎‏‎‏‎‎‎Ringer vibrate.‎‏‎‎‏‎"</string>
@@ -248,7 +252,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>
@@ -303,6 +307,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‎‏‏‎‏‎‎‏‏‏‎‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‏‎‎‎Microphone available‎‏‎‎‏‎"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‏‏‏‏‎‎‎‏‏‎‎‎‎‎‎‏‎‏‏‏‏‏‏‎‎‎‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎Camera available‎‏‎‎‏‎"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‎‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎Microphone and camera available‎‏‎‎‏‎"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‎‏‎‎‎‎‏‏‎‎‎‏‎‎‏‎‎‎‎‎‏‎Microphone turned on‎‏‎‎‏‎"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‏‏‏‎‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‎‏‎Microphone turned off‎‏‎‎‏‎"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‎‏‎‎‏‏‎‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎‎‎‎‎Microphone is enabled for all apps and services.‎‏‎‎‏‎"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‎‏‏‏‎‎‎‎‏‎‏‎‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‎‏‎‎Microphone access is disabled for all apps and services. You can enable microphone access in Settings &gt; Privacy &gt; Microphone.‎‏‎‎‏‎"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‏‎‎‏‏‏‎‎‏‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎Microphone access is disabled for all apps and services. You can change this in Settings &gt; Privacy &gt; Microphone.‎‏‎‎‏‎"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‎‏‏‎‏‎‏‎‎‏‎‎‏‏‏‏‏‏‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎Camera turned on‎‏‎‎‏‎"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‎‎‏‏‎‎‏‎‎‏‎‏‎‎‎‎Camera turned off‎‏‎‎‏‎"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‏‏‏‎‏‎‎‏‎‏‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‎‏‎‎‎‎‏‏‎‎Camera is enabled for all apps and services.‎‏‎‎‏‎"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‎‎‏‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‏‎‎‎‎Camera access is disabled for all apps and services.‎‏‎‎‏‎"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‏‎‏‏‎‎‏‎‏‎‏‎‎‎‎‎‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‎‏‎To use the microphone button, enable microphone access in Settings.‎‏‎‎‏‎"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‎‎‏‎‏‏‎‎‎‏‏‎‏‏‏‎‎‏‏‎‏‏‏‎‎‏‏‏‎‎‎‎Open settings.‎‏‎‎‏‎"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎Other device‎‏‎‎‏‎"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‎‏‎‎‎‏‏‏‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎‎‏‏‎Toggle Overview‎‏‎‎‏‎"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‎‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‎‏‎‎‎‏‎‏‏‎‏‏‏‏‎‏‎‎‏‏‎‎You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events, and callers you specify. You\'ll still hear anything you choose to play including music, videos, and games.‎‏‎‎‏‎"</string>
@@ -373,6 +388,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎When you\'re sharing, recording, or casting an app, ‎‏‎‎‏‏‎<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>‎‏‎‎‏‏‏‎ has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information.‎‏‎‎‏‎"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‏‎‎‎‎‏‏‎‏‏‎‎‎‎‏‏‏‎‎‏‎‎‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‏‏‎‎Continue‎‏‎‎‏‎"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‎‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‎‏‎‏‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎Share or record an app‎‏‎‎‏‎"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‎‏‏‎‏‏‎‎‏‎‎‎‏‏‎‏‎‎‏‏‎‎‏‏‎‏‎‎‏‏‏‏‎‏‎‏‏‏‎‎Allow this app to share or record?‎‏‎‎‏‎"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‏‏‎‏‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎When you\'re sharing, recording, or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information.‎‏‎‎‏‎"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‎‎‎‎‎‎‏‎‏‎‎When you\'re sharing, recording, or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information.‎‏‎‎‏‎"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‎Blocked by your IT admin‎‏‎‎‏‎"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‎Screen capturing is disabled by device policy‎‏‎‎‏‎"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‎‏‏‏‎‏‎‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‎Clear all‎‏‎‎‏‎"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‎‎‎‎‏‏‎‎‏‏‎‏‏‏‏‏‎‏‏‏‏‏‎‎‎Manage‎‏‎‎‏‎"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‏‎‏‏‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎History‎‏‎‎‏‎"</string>
@@ -488,7 +508,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‏‎‏‏‎‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎‏‏‎‎‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‎Unlock to use‎‏‎‎‏‎"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‎‎‏‎‎‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‎‏‏‎‎‏‏‎‏‏‎There was a problem getting your cards, please try again later‎‏‎‎‏‎"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‏‏‏‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‏‎‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‏‎‎‎‏‏‏‏‏‎‏‎‎‏‎‎Lock screen settings‎‏‎‎‏‎"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‏‏‏‎‏‎‏‎‎‎‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‎Scan QR code‎‏‎‎‏‎"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎‏‎‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‎‎‎‏‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‏‎‏‏‏‏‏‎‎QR code scanner‎‏‎‎‏‎"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‎‎Work profile‎‏‎‎‏‎"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‎‎‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‎Airplane mode‎‏‎‎‏‎"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‏‎‎‏‎‎‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎‎‎‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‎You won\'t hear your next alarm ‎‏‎‎‏‏‎<xliff:g id="WHEN">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
@@ -727,6 +747,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>
@@ -767,6 +791,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‎Magnify full screen‎‏‎‎‏‎"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‏‎Magnify part of screen‎‏‎‎‏‎"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‎‏‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎‏‎‏‏‏‎Switch‎‏‎‎‏‎"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‎‏‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎Allow diagonal scrolling‎‏‎‎‏‎"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‎‎‏‎‏‏‏‎‏‎‏‏‎‏‏‎‏‎‏‏‎‏‏‏‏‏‎Resize‎‏‎‎‏‎"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‎‏‎Change magnification type‎‏‎‎‏‎"</string>
@@ -785,12 +811,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 +962,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>
@@ -992,4 +1023,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‏‏‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‏‎‎‏‏‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎EEE, MMM d‎‏‎‎‏‎"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‏‎‏‎‏‏‎‎‎‎‏‎‏‎‎‏‏‏‏‎‏‎‎‏‎‎‏‎h:mm‎‏‎‎‏‎"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‏‏‎‎‏‏‎‏‏‎‏‎kk:mm‎‏‎‎‏‎"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‏‎Allow ‎‏‎‎‏‏‎<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>‎‏‎‎‏‏‏‎ to access all device logs?‎‏‎‎‏‎"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‏‏‏‎‎‎‎‎‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‎‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‎‎‏‏‎‎‎‎Allow one-time access‎‏‎‎‏‎"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‎‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‏‎‎‎‎‎‏‏‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎Don’t allow‎‏‎‎‏‎"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‏‏‎‎‎‏‎‎‎‎‏‏‏‎‎‏‎‏‏‏‎‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‎‏‎‎Device logs record what happens on your device. Apps can use these logs to find and fix issues.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Some logs may contain sensitive info, so only allow apps you trust to access all device logs. ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎If you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index e7a6cf1..247f74e 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Permitir siempre desde esta computadora"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Permitir"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"No tienes permitida la depuración por USB"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"El usuario al que accediste en este dispositivo no puede activar la depuración por USB. Para usar esta función, debes cambiar al usuario principal."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"El usuario al que accediste en este dispositivo no puede activar la depuración por USB. Para usar esa función, debes cambiar a un usuario administrador."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"¿Quiere cambiar el idioma del sistema a <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Cambio de idioma del sistema solicitado por otro dispositivo"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Cambiar idioma"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Permitir siempre en esta red"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Permitir"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"No tienes permitida la depuración inalámbrica"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"El usuario al que accediste en este dispositivo no puede activar la depuración inalámbrica. Para usar esta función, debes cambiar al usuario principal."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"El usuario al que accediste en este dispositivo no puede activar la depuración inalámbrica. Para usar esa función, debes cambiar a un usuario administrador."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Puerto USB inhabilitado"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Para proteger tu dispositivo de líquidos o suciedad, el puerto USB está inhabilitado y no detectará ningún accesorio.\n\nTe avisaremos cuando puedas usar el puerto USB de nuevo."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Se habilitó el puerto USB para detectar cargadores y accesorios"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Asistente voz"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Billetera"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Escáner de código QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Desbloquear"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositivo bloqueado"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Escaneando rostro"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Enviar"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"No se reconoce el rostro"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa la huella dactilar"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Se desconoce el porcentaje de la batería."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Modo de avión"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN activada"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Batería <xliff:g id="NUMBER">%d</xliff:g> por ciento"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batería: <xliff:g id="PERCENTAGE">%1$s</xliff:g> por ciento; tiempo restante: aproximadamente <xliff:g id="TIME">%2$s</xliff:g> en función del uso"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batería: <xliff:g id="PERCENTAGE">%1$d</xliff:g> por ciento; tiempo restante: aproximadamente <xliff:g id="TIME">%2$s</xliff:g> en función del uso"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batería cargando: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Ver todas las notificaciones"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletipo habilitado"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Timbre en vibración"</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Administrar usuarios"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Micrófono disponible"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Cámara disponible"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Micrófono y cámara disponibles"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Se activó el micrófono"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Se desactivó el micrófono"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Se habilitó el micrófono para todos los servicios y las apps."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"El acceso al micrófono está inhabilitado para todos los servicios y las apps. Puedes habilitar su acceso en Configuración &gt; Privacidad &gt; Micrófono."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"El acceso al micrófono está inhabilitado para todos los servicios y las apps. Puedes habilitarlo en Configuración &gt; Privacidad &gt; Micrófono."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Se activó la cámara"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Se desactivó la cámara"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Se habilitó la cámara para todos los servicios y las apps."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"El acceso a la cámara está inhabilitado para todos los servicios y las apps."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Para habilitar el botón de micrófono, habilita su acceso en Configuración."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Abrir Configuración"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Otro dispositivo"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Ocultar o mostrar Recientes"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"No te molestarán los sonidos ni las vibraciones, excepto las alarmas, los recordatorios, los eventos y las llamadas de los emisores que especifiques. Podrás escuchar el contenido que reproduzcas, como música, videos y juegos."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cuando compartas, grabes o transmitas una app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo el contenido que se muestre o reproduzca en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes y otra información sensible."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir o grabar una app"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"¿Quieres permitir que esta app comparta o grabe tu pantalla?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Cuando compartas, grabes o transmitas contenido, esta app podrá acceder a todo lo que sea visible en la pantalla o que reproduzcas en el dispositivo. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes y otra información sensible que pueda estar visible."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Cuando compartas, grabes o transmitas una app, esta app podrá acceder a todo el contenido que se muestre o reproduzca en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes y otra información sensible que pueda estar visible."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueada por tu administrador de TI"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La captura de pantalla está inhabilitada debido a la política del dispositivo"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Administrar"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Ocurrió un problema al obtener las tarjetas; vuelve a intentarlo más tarde"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuración de pantalla de bloqueo"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Escanear QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabajo"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Modo de avión"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"No oirás la próxima alarma a la(s) <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"¿Volver a cambiar a <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Los datos móviles no cambiarán automáticamente en función de la disponibilidad"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No, gracias"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sí, cambiar"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar pantalla completa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte de la pantalla"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Interruptor"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir desplazamiento en diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Cambiar tamaño"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Cambiar tipo de ampliación"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Deshacer"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Se quitó el acceso directo {label}}many{Se quitaron # de accesos directos}other{Se quitaron # accesos directos}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Quitar"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Conectado temporalmente"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexión deficiente"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d de MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"¿Quieres permitir que <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acceda a todos los registros del dispositivo?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Permitir acceso por única vez"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"No permitir"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Los registros del dispositivo permiten documentar lo que sucede en él. Las apps pueden usar estos registros para encontrar y solucionar problemas.\n\nEs posible que algunos registros del dispositivo contengan información sensible, por lo que solo permitimos que accedan a todos ellos apps de tu confianza. \n\nSi no permites que esta app acceda a todos los registros del dispositivo, aún puede acceder a sus propios registros. Además, es posible que el fabricante del dispositivo acceda a algunos registros o información en tu dispositivo."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index e8218b0..615aa4c 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Permitir siempre desde este ordenador"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Permitir"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Depuración USB no permitida"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"El usuario con el que se ha iniciado sesión en este dispositivo no puede activar la depuración USB. Para utilizar esta función, inicia sesión con la cuenta de usuario principal."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"El usuario con el que se ha iniciado sesión en este dispositivo no puede activar la depuración por USB. Para usar esta función, inicia sesión con la cuenta de un usuario administrador."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"¿Quieres cambiar el idioma del sistema a <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Otro dispositivo ha solicitado un cambio en el idioma del sistema"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Cambiar idioma"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Permitir siempre en esta red"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Permitir"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Depuración inalámbrica no permitida"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"El usuario con el que se ha iniciado sesión en este dispositivo no puede activar la depuración inalámbrica. Para utilizar esta función, inicia sesión con la cuenta de usuario principal."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"El usuario con el que se ha iniciado sesión en este dispositivo no puede activar la depuración inalámbrica. Para usar esta función, inicia sesión con la cuenta de un usuario administrador."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Puerto USB inhabilitado"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Para proteger tu dispositivo de los líquidos y la suciedad, el puerto USB se ha inhabilitado y no detectará ningún accesorio.\n\nRecibirás una notificación cuando puedas volver a usarlo."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Puerto USB habilitado para detectar cargadores y accesorios"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Asistente voz"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Escáner de códigos QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Desbloquear"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositivo bloqueado"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Escaneando cara"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Enviar"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"No se reconoce la cara"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa la huella digital"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentaje de batería desconocido."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Modo Avión"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"La red VPN está activada."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> por ciento de batería"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Queda un <xliff:g id="PERCENTAGE">%1$s</xliff:g> por ciento de batería (<xliff:g id="TIME">%2$s</xliff:g> aproximadamente según tu uso)"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Queda un <xliff:g id="PERCENTAGE">%1$d</xliff:g> por ciento de batería (<xliff:g id="TIME">%2$s</xliff:g> aproximadamente según tu uso)"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batería cargándose (<xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%)."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Ver todas las notificaciones"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletipo habilitado"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Modo vibración"</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gestionar usuarios"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Micrófono disponible"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Cámara disponible"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Micrófono y cámara disponible"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Micrófono activado"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Micrófono desactivado"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"El micrófono está habilitado para todas las aplicaciones y servicios."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"El acceso al micrófono está inhabilitado para todas las aplicaciones y servicios. Puedes habilitar el acceso al micrófono en Ajustes &gt; Privacidad &gt; Micrófono."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"El acceso al micrófono está inhabilitado para todas las aplicaciones y servicios. Puedes cambiarlo en Ajustes &gt; Privacidad &gt; Micrófono."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Cámara activada"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Cámara desactivada"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"La cámara está habilitada para todas las aplicaciones y servicios."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"El acceso a la cámara está inhabilitado para todas las aplicaciones y servicios."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Para usar el botón del micrófono, habilita el acceso al micrófono en Ajustes."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Abrir ajustes"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Otro dispositivo"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Mostrar u ocultar aplicaciones recientes"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"No te molestarán los sonidos ni las vibraciones, excepto las alarmas, los recordatorios, los eventos y las llamadas que especifiques. Seguirás escuchando el contenido que quieras reproducir, como música, vídeos y juegos."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cuando compartas, grabes o envíes una aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo lo que muestre o reproduzca la aplicación. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir o grabar una aplicación"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"¿Permitir que esta aplicación que comparta o grabe contenido?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Cuando compartas, grabes o envíes contenido, esta aplicación podrá acceder a todo lo que se vea en tu pantalla o reproduzcas en tu dispositivo. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Cuando compartas, grabes o envíes contenido, esta aplicación podrá acceder a todo lo que se muestre o reproduzca en ella. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueadas por tu administrador de TI"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Las capturas de pantalla están inhabilitadas debido a la política de dispositivos"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionar"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Se ha producido un problema al obtener tus tarjetas. Inténtalo de nuevo más tarde."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ajustes de pantalla de bloqueo"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Escanear código QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabajo"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Modo Avión"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"No oirás la próxima alarma (<xliff:g id="WHEN">%1$s</xliff:g>)"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"¿Cambiar de nuevo a <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Los datos móviles no cambiarán automáticamente en función de la disponibilidad"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No, gracias"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sí, cambiar"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar pantalla completa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte de la pantalla"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Cambiar"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir desplazamiento en diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Cambiar tamaño"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Cambiar tipo de ampliación"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Deshacer"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} acceso directo eliminado}many{# accesos directos eliminados}other{# accesos directos eliminados}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Quitar"</string>
     <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 +872,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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Conectada temporalmente"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexión inestable"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"¿Permitir que <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acceda a todos los registros del dispositivo?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Permitir el acceso una vez"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"No permitir"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Los registros del dispositivo documentan lo que sucede en tu dispositivo. Las aplicaciones pueden usar estos registros para encontrar y solucionar problemas.\n\nComo algunos registros pueden contener información sensible, es mejor que solo permitas que accedan a ellos las aplicaciones en las que confíes. \n\nAunque no permitas que esta aplicación acceda a todos los registros del dispositivo, aún podrá acceder a sus propios registros. El fabricante de tu dispositivo aún puede acceder a algunos registros o información de tu dispositivo."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es/tiles_states_strings.xml b/packages/SystemUI/res/values-es/tiles_states_strings.xml
index d7a8133..fe4cbed 100644
--- a/packages/SystemUI/res/values-es/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-es/tiles_states_strings.xml
@@ -78,8 +78,8 @@
   </string-array>
   <string-array name="tile_states_location">
     <item msgid="3316542218706374405">"No disponible"</item>
-    <item msgid="4813655083852587017">"Desactivado"</item>
-    <item msgid="6744077414775180687">"Activado"</item>
+    <item msgid="4813655083852587017">"Desactivada"</item>
+    <item msgid="6744077414775180687">"Activada"</item>
   </string-array>
   <string-array name="tile_states_hotspot">
     <item msgid="3145597331197351214">"No disponible"</item>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 11a9dc9..d46694e 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Luba alati sellest arvutist"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Luba"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB-silumine pole lubatud"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Sellesse seadmesse praegu sisse logitud kasutaja ei saa USB-silumist sisse lülitada. Selle funktsiooni kasutamiseks vahetage peamisele kasutajale."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Sellesse seadmesse praegu sisse logitud kasutaja ei saa USB-silumist sisse lülitada. Selle funktsiooni kasutamiseks vahetage administraatoriõigustega kasutajale."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Kas soovite süsteemi keeleks määrata järgmise: <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Teine seade taotles süsteemi keele muutmist"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Muuda keelt"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Luba selles võrgus alati"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Luba"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Juhtmevaba silumine pole lubatud"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Sellesse seadmesse praegu sisse logitud kasutaja ei saa juhtmevaba silumist sisse lülitada. Selle funktsiooni kasutamiseks vahetage peamisele kasutajale."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Sellesse seadmesse praegu sisse logitud kasutaja ei saa juhtmevaba silumist sisse lülitada. Selle funktsiooni kasutamiseks vahetage administraatoriõigustega kasutajale."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB-port on keelatud"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Selleks et kaitsta teie seadet vedeliku või mustuse eest, on USB-port keelatud ja see ei tuvasta lisatarvikuid.\n\nKui USB-porti tohib taas kasutada, saate selle kohta märguande."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB-pordil on lubatud tuvastada laadijaid ja tarvikuid"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Häälabi"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-koodi skanner"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Luku avamine"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Seade on lukustatud"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Näo skannimine"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Saada"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Nägu ei õnnestu tuvastada"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Kasutage sõrmejälge"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth on ühendatud."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Aku laetuse protsent on teadmata."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ühendatud: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Lennukirežiim."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN on sees."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Aku: <xliff:g id="NUMBER">%d</xliff:g> protsenti."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Aku protsent <xliff:g id="PERCENTAGE">%1$s</xliff:g>, teie kasutuse põhjal on jäänud ligikaudu <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Aku protsent <xliff:g id="PERCENTAGE">%1$d</xliff:g>, teie kasutuse põhjal on jäänud ligikaudu <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Akut laetakse (<xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%)."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Kõikide märguannete kuvamine"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter lubatud."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibreeriv kõlisti."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Kasutajate haldamine"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon on saadaval"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kaamera on saadaval"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon ja kaamera on saadaval"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon lülitati sisse"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon lülitati välja"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon on kõigi rakenduste ja teenuste jaoks lubatud."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Juurdepääs mikrofonile on kõigi rakenduste ja teenuste jaoks keelatud. Saate juurdepääsu mikrofonile lubada, tehes valikud Seaded &gt; Privaatsus &gt; Mikrofon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Juurdepääs mikrofonile on kõigi rakenduste ja teenuste jaoks keelatud. Saate seda muuta valikutega Seaded &gt; Privaatsus &gt; Mikrofon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kaamera lülitati sisse"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kaamera lülitati välja"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kaamera on kõigi rakenduste ja teenuste jaoks lubatud."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Juurdepääs kaamerale on kõigi rakenduste ja teenuste jaoks keelatud."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Mikrofoninupu kasutamiseks lubage seadetes juurdepääs mikrofonile."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Avage seaded."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Muu seade"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Lehe Ülevaade sisse- ja väljalülitamine"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Helid ja värinad ei sega teid. Kuulete siiski enda määratud äratusi, meeldetuletusi, sündmusi ja helistajaid. Samuti kuulete kõike, mille esitamise ise valite, sh muusika, videod ja mängud."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kui jagate, salvestate või kannate rakendust üle, on rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> juurdepääs kõigele, mida selles rakenduses kuvatakse või esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Jätka"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Rakenduse jagamine või salvestamine"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Kas lubada sellel rakendusel jagada või salvestada?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kui jagate, salvestate või kannate üle, on selle rakendusel juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kui jagate, salvestate või kannate rakendust üle, on sellel rakendusel juurdepääs kõigele, mida selles rakenduses kuvatakse või esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokeeris teie IT-administraator"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekraanikuva jäädvustamine on seadmereeglitega keelatud"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tühjenda kõik"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Haldamine"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Ajalugu"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Avage kasutamiseks"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Teie kaartide hankimisel ilmnes probleem, proovige hiljem uuesti"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lukustuskuva seaded"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR-koodi skannimine"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Tööprofiil"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Lennukirežiim"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Te ei kuule järgmist äratust kell <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Kas vahetada tagasi operaatorile <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiilandmeside operaatorit ei vahetata saadavuse alusel automaatselt"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Tänan, ei"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Jah, vaheta"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Täisekraani suurendamine"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekraanikuva osa suurendamine"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Vaheta"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Luba diagonaalne kerimine"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Muuda suurust"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Muuda suurenduse tüüpi"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Võta tagasi"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} otsetee on eemaldatud}other{# otseteed on eemaldatud}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Eemalda"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ajutiselt ühendatud"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Kehv ühendus"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d. MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Kas anda rakendusele <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> juurdepääs kõigile seadmelogidele?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Luba ühekordne juurdepääs"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Ära luba"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Seadmelogid jäädvustavad, mis teie seadmes toimub. Rakendused saavad neid logisid kasutada probleemide tuvastamiseks ja lahendamiseks.\n\nMõned logid võivad sisaldada tundlikku teavet, seega lubage juurdepääs kõigile seadmelogidele ainult rakendustele, mida usaldate. \n\nKui te ei luba sellel rakendusel kõigile seadmelogidele juurde pääseda, pääseb see siiski juurde oma logidele. Teie seadme tootja võib teie seadmes siiski teatud logidele või teabele juurde pääseda."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 3714781..3af8ce6 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Eman beti ordenagailu honetatik arazteko baimena"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Eman baimena"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Ez da onartzen USB bidezko arazketa"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Gailu honetan saioa hasita daukan erabiltzaileak ezin du aktibatu USB bidezko arazketa. Eginbide hori erabiltzeko, aldatu erabiltzaile nagusira."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Gailu honetan saioa hasita daukan erabiltzaileak ezin du aktibatu USB bidezko arazketa. Eginbide hori erabiltzeko, aldatu administratzailea den erabiltzaile batera."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Hizkuntza honetara aldatu nahi duzu sistemaren hizkuntza: <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Beste gailu batek sistemaren hizkuntza aldatzeko eskatu du"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Aldatu hizkuntza"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Eman baimena beti sare honetan"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Eman baimena"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Ez da onartzen hari gabeko arazketa"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Gailu honetan saioa hasita daukan erabiltzaileak ezin du aktibatu hari gabeko arazketa. Eginbide hori erabiltzeko, aldatu erabiltzaile nagusira."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Gailu honetan saioa hasita daukan erabiltzaileak ezin du aktibatu hari gabeko arazketa. Eginbide hori erabiltzeko, aldatu administratzailea den erabiltzaile batera."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Desgaitu egin da USB ataka"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"USB ataka desgaitu egin da gailua likido edo zikinkeriengandik babesteko, eta ez du hautemango osagarririk.\n\nJakinarazpen bat jasoko duzu USB ataka berriz erabiltzeko moduan dagoenean."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB ataka gaitu da kargagailuak eta osagarriak hautemateko"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Ahots-laguntza"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Diru-zorroa"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR kodeen eskanerra"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Desblokeatu"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Gailua blokeatuta dago"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Aurpegia eskaneatzen"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Bidali"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Ezin da ezagutu aurpegia"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Erabili hatz-marka"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetootha konektatuta."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Bateriaren ehunekoa ezezaguna da."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> gailura konektatuta."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Hegaldi-modua"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN eginbidea aktibatuta."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Bateriaren karga: <xliff:g id="NUMBER">%d</xliff:g>."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateriak ehuneko <xliff:g id="PERCENTAGE">%1$s</xliff:g> dauka kargatuta. Zure erabilera kontuan izanda, <xliff:g id="TIME">%2$s</xliff:g> inguru gelditzen zaizkio."</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateriak ehuneko <xliff:g id="PERCENTAGE">%1$d</xliff:g> dauka kargatuta. Zure erabilera kontuan izanda, <xliff:g id="TIME">%2$s</xliff:g> inguru gelditzen zaizkio."</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Kargatzen ari da bateria. Ehuneko <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> arte kargatu da oraingoz."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Ikusi jakinarazpen guztiak"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter gaituta."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Tonu-jotzailea dardara moduan."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Kudeatu erabiltzaileak"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofonoa erabilgarri dago"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera erabilgarri dago"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofonoa eta kamera erabilgarri daude"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Aktibatu da mikrofonoa"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Desaktibatu da mikrofonoa"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Aplikazio eta zerbitzu guztiek mikrofonoa erabil dezakete."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofonoa erabiltzeko baimena desgaituta dago aplikazio eta zerbitzu guztietarako. Mikrofonoa erabiltzeko baimena gaitzeko, joan Ezarpenak &gt; Pribatutasuna &gt; Mikrofonoa atalera."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofonoa erabiltzeko baimena desgaituta dago aplikazio eta zerbitzu guztietarako. Baimen hori aldatzeko, joan Ezarpenak &gt; Pribatutasuna &gt; Mikrofonoa atalera."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Piztu da kamera"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Itzali da kamera"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Aplikazio eta zerbitzu guztiek kamera erabil dezakete."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kamera erabiltzeko baimena desgaituta dago aplikazio eta zerbitzu guztietarako."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Mikrofonoaren botoia erabiltzeko, gaitu mikrofonoa erabiltzeko baimena ezarpenetan."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Ireki ezarpenak."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Beste gailu bat"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Aldatu ikuspegi orokorra"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Gailuak ez du egingo ez soinurik ez dardararik, baina alarmak, gertaera eta abisuen tonuak, eta aukeratzen dituzun deitzaileen dei-tonuak joko ditu. Bestalde, zuk erreproduzitutako guztia entzungo duzu, besteak beste, musika, bideoak eta jokoak."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Aplikazio bat partekatzen, grabatzen edo igortzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztirako sarbidea du <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Egin aurrera"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partekatu edo grabatu aplikazioak"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Edukia partekatzeko edo grabatzeko baimena eman nahi diozu aplikazioari?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Edukia partekatzen, grabatzen edo igortzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztirako sarbidea du aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Aplikazio bat partekatzen, grabatzen edo igortzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztirako sarbidea du aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IKT saileko administratzaileak blokeatu du"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Pantaila-kapturak egiteko aukera desgaituta dago, gailu-gidalerroei jarraikiz"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Garbitu guztiak"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Kudeatu"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desblokeatu erabiltzeko"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Arazo bat izan da txartelak eskuratzean. Saiatu berriro geroago."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Pantaila blokeatuaren ezarpenak"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Eskaneatu QR kodea"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Work profila"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Hegaldi modua"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Ez duzu entzungo hurrengo alarma (<xliff:g id="WHEN">%1$s</xliff:g>)"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> operadorera aldatu nahi duzu berriro?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Datu-konexioa ez da automatikoki aldatuko erabilgarritasunaren arabera"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ez, eskerrik asko"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Bai, aldatu nahi dut"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Handitu pantaila osoa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Handitu pantailaren zati bat"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Botoia"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Eman diagonalki gora eta behera egiteko aukera erabiltzeko baimena"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Aldatu tamaina"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Aldatu lupa mota"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desegin"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} lasterbide kendu da}other{# lasterbide kendu dira}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Kendu"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Aldi baterako konektatuta"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Konexio ahula"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Gailuko erregistro guztiak atzitzeko baimena eman nahi diozu <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> aplikazioari?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Eman behin erabiltzeko baimena"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Ez eman baimenik"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Gailuko erregistroetan gailuan gertatzen den guztia gordetzen da. Arazoak bilatu eta konpontzeko erabil ditzakete aplikazioek erregistro horiek.\n\nBaliteke erregistro batzuek kontuzko informazioa edukitzea. Beraz, eman gailuko erregistro guztiak atzitzeko baimena fidagarritzat jotzen dituzun aplikazioei bakarrik. \n\nNahiz eta gailuko erregistro guztiak atzitzeko baimena ez eman aplikazio honi, aplikazioak hari dagozkion erregistroak atzitu ahalko ditu. Gainera, baliteke gailuaren fabrikatzaileak gailuko erregistro edo datu batzuk atzitu ahal izatea."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 183a9ed..638d199 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"همیشه از این رایانه انجام شود"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"اجازه دادن"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"‏اشکال‌زدایی USB مجاز نیست"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"‏کاربری که درحال حاضر در این دستگاه وارد سیستم شده است نمی‌تواند اشکال‌زدایی USB را روشن کند. برای استفاده از این قابلیت، به کاربر اصلی تغییر وضعیت دهید."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"‏کاربری که درحال‌حاضر به سیستم این دستگاه وارد شده است نمی‌تواند اشکال‌زدایی USB را روشن کند. برای استفاده از این ویژگی، به کاربر سرپرست تغییر وضعیت دهید."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"آیا می‌خواهید زبان سیستم به <xliff:g id="LANGUAGE">%1$s</xliff:g> تغییر کند؟"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"دستگاه دیگری درخواست کرده است زبان سیستم تغییر کند"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"تغییر زبان"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"همیشه در این شبکه مجاز شود"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"اجازه دادن"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"اشکال‌زدایی بی‌سیم مجاز نیست"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"کاربری که درحال‌حاضر در این دستگاه به سیستم وارد شده است نمی‌تواند اشکال‌زدایی بی‌سیم را روشن کند. برای استفاده از این ویژگی، به کاربر اصلی بروید."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"کاربری که درحال‌حاضر به سیستم این دستگاه وارد شده است نمی‌تواند اشکال‌زدایی بی‌سیم را روشن کند. برای استفاده از این ویژگی، به کاربر سرپرست تغییر وضعیت دهید."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"‏درگاه USB غیرفعال شده است"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"‏برای محافظت از دستگاهتان دربرابر مایعات یا خاکروبه، درگاه USB غیرفعال شده است و هیچ‌کدام از لوازم جانبی را شناسایی نخواهد کرد.\n\nهرزمان که استفاده از درگاه USB امکان‌پذیر باشد، به شما اطلاع داده می‌شود."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"‏درگاه USB برای تشخیص شارژرها و لوازم جانبی فعال شد"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"دستیار صوتی"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"کیف پول"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"کدخوان پاسخ‌سریع"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"باز کردن قفل"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"دستگاه قفل است"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"درحال اسکن کردن چهره"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ارسال"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"چهره شناسایی نشد"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"از اثر انگشت استفاده کنید"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"بلوتوث متصل است."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"درصد شارژ باتری مشخص نیست."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"به <xliff:g id="BLUETOOTH">%s</xliff:g> متصل شد."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"حالت هواپیما."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"‏VPN روشن است."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"باتری <xliff:g id="NUMBER">%d</xliff:g> درصد."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"باتری <xliff:g id="PERCENTAGE">%1$s</xliff:g> درصد شارژ دارد، براساس مصرفتان تقریباً <xliff:g id="TIME">%2$s</xliff:g> شارژ باقی‌مانده است"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"باتری <xliff:g id="PERCENTAGE">%1$d</xliff:g> درصد شارژ دارد، براساس مصرفتان تقریباً <xliff:g id="TIME">%2$s</xliff:g> شارژ باقی‌مانده است"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"در حال شارژ باتری، <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> درصد"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"دیدن همه اعلان‌ها"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"تله‌تایپ فعال شد."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"زنگ لرزشی."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"میکروفون دردسترس است"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"دوربین دردسترس است"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"میکروفون و دوربین دردسترس‌اند"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"میکروفون روشن شد"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"میکروفون خاموش شد"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"میکروفون برای همه برنامه‌ها و سرویس‌ها فعال است."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"‏دسترسی به میکروفون برای همه برنامه‌ها و سرویس‌ها غیرفعال است. می‌توانید دسترسی به میکروفون را در «تنظیمات &gt; حریم خصوصی &gt; میکروفون» فعال کنید."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"‏دسترسی به میکروفون برای همه برنامه‌ها و سرویس‌ها غیرفعال است. می‌توانید آن را در «تنظیمات &gt; حریم خصوصی &gt; میکروفون» تغییر دهید."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"دوربین روشن شد"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"دوربین خاموش شد"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"دوربین برای همه برنامه‌ها و سرویس‌ها فعال است."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"دسترسی به دوربین برای همه برنامه‌ها و سرویس‌ها غیرفعال است."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"برای استفاده از دکمه میکروفون، دسترسی به میکروفون را در «تنظیمات» فعال کنید."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"باز کردن تنظیمات."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"دستگاه دیگر"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"تغییر وضعیت نمای کلی"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"به‌جز هشدارها، یادآوری‌ها، رویدادها و تماس‌گیرندگانی که خودتان مشخص می‌کنید، هیچ صدا و لرزشی نخواهید داشت. همچنان صدای مواردی را که پخش می‌کنید می‌شنوید (ازجمله صدای موسیقی، ویدیو و بازی)."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"وقتی درحال هم‌رسانی، ضبط، یا پخش محتوای برنامه‌ای هستید، <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> به همه محتوایی که در آن برنامه نمایان است یا پخش می‌شود دسترسی دارد. بنابراین مراقب گذرواژه‌ها، جزئیات پرداخت، پیام‌ها، یا دیگر اطلاعات حساس باشید."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ادامه"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"هم‌رسانی یا ضبط برنامه"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"به این برنامه اجازه هم‌رسانی یا ضبط می‌دهید؟"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"وقتی درحال هم‌رسانی، ضبط، یا پخش محتوا هستید، این برنامه به همه محتوایی که در صفحه‌تان نمایان است یا در دستگاهتان پخش می‌شود دسترسی دارد. بنابراین مراقب گذرواژه‌ها، جزئیات پرداخت، پیام‌ها، یا دیگر اطلاعات حساس باشید."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"وقتی درحال هم‌رسانی، ضبط، یا پخش محتوای برنامه‌ای هستید، این برنامه به همه محتوایی که در آن برنامه نمایان است یا پخش می‌شود دسترسی دارد. بنابراین مراقب گذرواژه‌ها، جزئیات پرداخت، پیام‌ها، یا دیگر اطلاعات حساس باشید."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"سرپرست فناوری اطلاعات آن را مسدود کرده است"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"«ضبط صفحه‌نمایش» به‌دلیل خط‌مشی دستگاه غیرفعال است"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"پاک کردن همه موارد"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"مدیریت"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"سابقه"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"برای استفاده، قفل را باز کنید"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"هنگام دریافت کارت‌ها مشکلی پیش آمد، لطفاً بعداً دوباره امتحان کنید"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"تنظیمات صفحه قفل"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"اسکن رمزینه پاسخ‌سریع"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"نمایه کاری"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"حالت هواپیما"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"در ساعت <xliff:g id="WHEN">%1$s</xliff:g>، دیگر صدای زنگ ساعت را نمی‌شنوید"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"می‌خواهید به <xliff:g id="CARRIER">%s</xliff:g> برگردید؟"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"وضعیت داده تلفن همراه به‌طور خودکار براساس دردسترس بودن تغییر نخواهد کرد"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"نه متشکرم"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"بله، عوض شود"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"درشت‌نمایی تمام‌صفحه"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"درشت‌نمایی بخشی از صفحه"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"کلید"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"مجاز کردن پیمایش قطری"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"تغییر اندازه"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"تغییر نوع درشت‌نمایی"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"واگرد"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} میان‌بر برداشته شد}one{# میان‌بر برداشته شد}other{# میان‌بر برداشته شد}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"برداشتن"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"موقتاً متصل است"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"اتصال ضعیف"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"به <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> اجازه می‌دهید به همه گزارش‌های دستگاه دسترسی داشته باشد؟"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"مجاز کردن دسترسی یک‌باره"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"اجازه نمی‌دهم"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"گزارش‌های دستگاه آنچه را در دستگاهتان رخ می‌دهد ثبت می‌کند. برنامه‌ها می‌توانند از این گزارش‌ها برای پیدا کردن مشکلات و رفع آن‌ها استفاده کنند.\n\nبرخی‌از گزارش‌ها ممکن است حاوی اطلاعات حساس باشند، بنابراین فقط به برنامه‌های مورداعتمادتان اجازه دسترسی به همه گزارش‌های دستگاه را بدهید. \n\nاگر به این برنامه اجازه ندهید به همه گزارش‌های دستگاه دسترسی داشته باشد، همچنان می‌تواند به گزارش‌های خودش دسترسی داشته باشد. سازنده دستگاه نیز ممکن است همچنان بتواند به برخی‌از گزارش‌ها یا اطلاعات دستگاهتان دسترسی داشته باشد."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index df05015..25af802 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Salli aina tällä tietokoneella"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Salli"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB-vianetsintää ei sallita"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Laitteelle tällä hetkellä kirjautunut käyttäjä ei voi ottaa USB-vianetsintää käyttöön. Vaihda käyttäjäksi ensisijainen käyttäjä, jotta voit käyttää tätä ominaisuutta."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Laitteelle tällä hetkellä kirjautunut käyttäjä ei voi laittaa USB-vianetsintää päälle. Vaihda järjestelmänvalvojaksi, jotta voit käyttää tätä ominaisuutta."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Haluatko, että järjestelmän kieli on jatkossa <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Toiselta laitteelta pyydetty järjestelmän kielen vaihtamista"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Vaihda kieltä"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Salli aina tässä verkossa"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Salli"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Langatonta virheenkorjausta ei sallita"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Laitteelle tällä hetkellä kirjautunut käyttäjä ei voi ottaa langatonta virheenkorjausta käyttöön. Vaihda käyttäjäksi ensisijainen käyttäjä, jotta voit käyttää tätä ominaisuutta."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Laitteelle tällä hetkellä kirjautunut käyttäjä ei voi laittaa langatonta virheenkorjausta päälle. Vaihda järjestelmänvalvojaksi, jotta voit käyttää tätä ominaisuutta."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB-portti poistettu käytöstä"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Laitteen suojaamiseksi nesteiltä ja lialta USB-portti on poistettu käytöstä, eikä se havaitse lisävarusteita.\n\nSaat ilmoituksen, kun USB-porttia voi taas käyttää."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB-portti on käytössä ja voi havaita latureita sekä lisävarusteita"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Ääniapuri"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-koodiskanneri"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Avaa lukitus"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Laite lukittu"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Kasvojen skannaus"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Lähetä"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Kasvoja ei voi tunnistaa"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Käytä sormenjälkeä"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth yhdistetty."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akun varaustaso ei tiedossa."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Yhteys: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Lentokonetila."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN päällä"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Akun virta <xliff:g id="NUMBER">%d</xliff:g> prosenttia."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Akkua jäljellä <xliff:g id="PERCENTAGE">%1$s</xliff:g> prosenttia eli noin <xliff:g id="TIME">%2$s</xliff:g> käyttösi perusteella"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Akkua jäljellä <xliff:g id="PERCENTAGE">%1$d</xliff:g> prosenttia eli noin <xliff:g id="TIME">%2$s</xliff:g> käyttösi perusteella"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Akku latautuu: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> prosenttia"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Näytä kaikki ilmoitukset"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Tekstipuhelin käytössä."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Soittoääni: värinä."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Ylläpidä käyttäjiä"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofoni käytettävissä"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera käytettävissä"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofoni ja kamera käytettävissä"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofoni laitettu päälle"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofoni laitettu pois päältä"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofonin käyttö on sallittu kaikille sovelluksille ja palveluille."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Pääsy mikrofoniin on estetty kaikilta sovelluksilta ja palveluilta. Jos haluat sallia pääsyn mikrofoniin, valitse Asetukset &gt; Yksityisyys &gt; Mikrofoni."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Pääsy mikrofoniin on estetty kaikilta sovelluksilta ja palveluilta. Jos haluat muuttaa tätä, valitse Asetukset &gt; Yksityisyys &gt; Mikrofoni."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera laitettu päälle"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera laitettu pois päältä"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kameran käyttö on sallittu kaikille sovelluksille ja palveluille."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Pääsy kameraan on estetty kaikilta sovelluksilta ja palveluilta."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Jos haluat käyttää mikrofonipainiketta, salli pääsy mikrofoniin asetuksista."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Avaa asetukset."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Muu laite"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Näytä/piilota viimeisimmät"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Äänet ja värinät eivät häiritse sinua, paitsi jos ne ovat hälytyksiä, muistutuksia, tapahtumia tai määrittämiäsi soittajia. Kuulet edelleen kaiken valitsemasi sisällön, kuten musiikin, videot ja pelit."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kun jaat, tallennat tai striimaat sovellusta, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> saa pääsyn kaikkeen sovelluksessa näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Jatka"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Jaa sovellus tai tallenna sen sisältöä"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Sallitko sovelluksen jakaa tai tallentaa sisältöä?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kun jaat, tallennat tai striimaat sisältöä, sovellus saa pääsyn kaikkeen näytölläsi näkyvään tai laitteellasi toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kun jaat, tallennat tai striimaat sovellusta, tämä sovellus saa pääsyn kaikkeen sovelluksessa näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT-järjestelmänvalvojasi estämä"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Kuvakaappaus on poistettu käytöstä laitekäytännön perusteella"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tyhjennä kaikki"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Muuta asetuksia"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Avaa lukitus ja käytä"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Korttien noutamisessa oli ongelma, yritä myöhemmin uudelleen"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lukitusnäytön asetukset"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skannaa QR-koodi"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Työprofiili"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Lentokonetila"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Et kuule seuraavaa hälytystäsi (<xliff:g id="WHEN">%1$s</xliff:g>)."</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Palauta käyttöön <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiilidata ei vaihdu automaattisesti saatavuuden perusteella"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ei kiitos"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Kyllä, vaihda"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Koko näytön suurennus"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Suurenna osa näytöstä"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Vaihda"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Diagonaalisen vierittämisen salliminen"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Muuta kokoa"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Suurennustyypin muuttaminen"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Kumoa"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} pikanäppäin poistettu}other{# pikanäppäintä poistettu}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Poista"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Väliaikaisesti yhdistetty"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Heikko yhteys"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"VKP, KKK p"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Saako <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> pääsyn kaikkiin laitelokeihin?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Salli kertaluonteinen pääsy"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Älä salli"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Laitteen tapahtumat tallentuvat laitelokeihin. Niiden avulla sovellukset voivat löytää ja korjata ongelmia.\n\nJotkin lokit voivat sisältää arkaluontoista tietoa, joten salli pääsy kaikkiin laitelokeihin vain sovelluksille, joihin luotat. \n\nJos et salli tälle sovellukselle pääsyä kaikkiin laitelokeihin, sillä on kuitenkin pääsy sen omiin lokeihin. Laitteen valmistajalla voi olla pääsy joihinkin lokeihin tai tietoihin laitteella."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index c5c941b..19d61a5 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Toujours autoriser sur cet ordinateur"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Autoriser"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Débogage USB non autorisé"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"L\'utilisateur actuellement connecté sur cet appareil ne peut pas activer le débogage USB. Pour utiliser cette fonctionnalité, l\'utilisateur principal doit se connecter."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"L\'utilisateur actuellement connecté sur cet appareil ne peut pas activer le débogage USB. Pour utiliser cette fonctionnalité, l\'utilisateur administrateur doit se connecter."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Voulez-vous changer la langue du système pour : <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Un autre appareil demande de changer la langue du système"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Changer la langue"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Toujours autoriser sur ce réseau"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Autoriser"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Débogage sans fil non autorisé"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"L\'utilisateur actuellement connecté sur cet appareil ne peut pas activer le débogage sans fil. Pour utiliser cette fonctionnalité, l\'utilisateur principal doit se connecter."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"L\'utilisateur actuellement connecté sur cet appareil ne peut pas activer le débogage sans fil. Pour utiliser cette fonctionnalité, l\'utilisateur administrateur doit se connecter."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Le port USB a été désactivé"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Pour protéger votre appareil des liquides et des débris, le port USB est désactivé et ne pourra pas détecter les accessoires.\n\nVous verrez une notification lorsque vous pourrez utiliser le port USB à nouveau."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Le port USB a été activé afin de détecté les chargeurs et les accessoires"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Assistance vocale"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Portefeuille"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Lecteur de code QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Déverrouiller"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Appareil verrouillé"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Numérisation du visage"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Envoyer"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Visage non reconnu"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utiliser l\'empreinte digitale"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connecté"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pourcentage de la pile inconnu."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connecté à : <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Mode Avion"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"RPV activé."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Pile : <xliff:g id="NUMBER">%d</xliff:g> pour cent"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Pile chargée à <xliff:g id="PERCENTAGE">%1$s</xliff:g> pour cent (environ <xliff:g id="TIME">%2$s</xliff:g> d\'autonomie en fonction de votre usage)"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Pile chargée à <xliff:g id="PERCENTAGE">%1$d</xliff:g> pour cent (environ <xliff:g id="TIME">%2$s</xliff:g> d\'autonomie en fonction de votre usage)"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Pile en charge : <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Afficher toutes les notifications"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Téléscripteur activé"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Sonnerie en mode vibreur"</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gérer les utilisateurs"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microphone disponible"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Appareil photo disponible"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microphone et appareil photo disponibles"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microphone activé"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microphone désactivé"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Le microphone est activé pour toutes les applications et tous les services."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"L\'accès au microphone est désactivé pour toutes les applications et tous les services. Vous pouvez l\'activer dans Paramètres &gt; Confidentialité &gt; Microphone."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"L\'accès au microphone est désactivé pour toutes les applications et tous les services. Vous pouvez modifier cette option dans Paramètres &gt; Confidentialité &gt; Microphone."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Appareil photo activé"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Appareil photo désactivé"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"L\'appareil photo est activé pour toutes les applications et tous les services."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"L\'accès à l\'appareil photo est désactivé pour toutes les applications et tous les services."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Pour utiliser le bouton du microphone, activez l\'accès au microphone dans les paramètres."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Ouvrir les paramètres."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Autre appareil"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Basculer l\'aperçu"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Vous ne serez pas dérangé par les sons et les vibrations, sauf pour les alarmes, les rappels, les événements et les appelants que vous sélectionnez. Vous entendrez tout ce que vous choisissez d\'écouter, y compris la musique, les vidéos et les jeux."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lorsque vous partagez, enregistrez ou diffusez une application, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est affiché ou lu sur cette application. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages ou toute autre information confidentielle."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuer"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partager ou enregistrer une application"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Autoriser cette application à partager ou à enregistrer?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Lorsque vous partagez, enregistrez ou diffusez, cette application a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages ou toute autre information confidentielle."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Lorsque vous partagez, enregistrez ou diffusez une application, cette application a accès à tout ce qui est affiché ou lu sur cette application. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages ou toute autre information confidentielle."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquée par votre administrateur informatique"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La fonctionnalité de capture d\'écran est désactivée par l\'application Device Policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historique"</string>
@@ -488,7 +511,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Déverrouiller pour utiliser"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Un problème est survenu lors de la récupération de vos cartes, veuillez réessayer plus tard"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Paramètres de l\'écran de verrouillage"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Numériser le code QR"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"lecteur de code QR"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Profil professionnel"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Mode Avion"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Vous n\'entendrez pas votre prochaine alarme à <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -607,8 +630,8 @@
     <string name="accessibility_status_bar_headset" msgid="2699275863720926104">"Écouteurs connectés"</string>
     <string name="data_saver" msgid="3484013368530820763">"Économiseur de données"</string>
     <string name="accessibility_data_saver_on" msgid="5394743820189757731">"La fonction Économiseur de données est activée"</string>
-    <string name="switch_bar_on" msgid="1770868129120096114">"Activé"</string>
-    <string name="switch_bar_off" msgid="5669805115416379556">"Désactivé"</string>
+    <string name="switch_bar_on" msgid="1770868129120096114">"Activée"</string>
+    <string name="switch_bar_off" msgid="5669805115416379556">"Désactivée"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Non disponible"</string>
     <string name="accessibility_tile_disabled_by_policy_action_description" msgid="6958422730461646926">"En savoir plus"</string>
     <string name="nav_bar" msgid="4642708685386136807">"Barre de navigation"</string>
@@ -727,6 +750,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Rebasculer vers <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Il n\'y aura pas de basculement automatique entre les données mobiles selon la disponibilité"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Non merci"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Oui, basculer"</string>
     <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>
@@ -767,6 +794,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Agrandir la totalité de l\'écran"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Agrandir une partie de l\'écran"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Commutateur"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Autoriser le défilement en diagonale"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionner"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Changer le type d\'agrandissement"</string>
@@ -785,12 +814,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Annuler"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} raccourci retiré}one{# raccourci retiré}many{# de raccourcis retirés}other{# raccourcis retirés}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Retirer"</string>
     <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 +965,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Connectée temporairement"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Connexion faible"</string>
     <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>
@@ -992,4 +1026,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Autoriser <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> à accéder à l\'ensemble des journaux de l\'appareil?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Autoriser un accès unique"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Ne pas autoriser"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Les journaux de l\'appareil enregistrent ce qui se passe sur celui-ci. Les applications peuvent utiliser ces journaux pour trouver et résoudre des problèmes.\n\nCertains journaux peuvent contenir des renseignements confidentiels. N\'autorisez donc que les applications auxquelles vous faites confiance puisque celles-ci pourront accéder à l\'ensemble des journaux de l\'appareil. \n\nMême si vous n\'autorisez pas cette application à accéder à l\'ensemble des journaux de l\'appareil, elle aura toujours accès à ses propres journaux. Le fabricant de votre appareil pourrait toujours être en mesure d\'accéder à certains journaux ou renseignements sur votre appareil."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 1a2b877..1e3319b 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Toujours autoriser sur cet ordinateur"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Autoriser"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Débogage USB non autorisé"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"L\'utilisateur actuellement connecté sur cet appareil ne peut pas activer le débogage USB. Pour utiliser cette fonctionnalité, l\'utilisateur principal doit se connecter."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"L\'utilisateur actuellement connecté sur cet appareil ne peut pas activer le débogage USB. Pour utiliser cette fonctionnalité, un administrateur doit se connecter."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Voulez-vous définir la langue système sur <xliff:g id="LANGUAGE">%1$s</xliff:g> ?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Changement de langue système demandé par un autre appareil"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Changer de langue"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Toujours autoriser sur ce réseau"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Autoriser"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Débogage sans fil non autorisé"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"L\'utilisateur actuellement connecté sur cet appareil ne peut pas activer le débogage sans fil. Pour que cette fonctionnalité soit disponible, l\'utilisateur principal doit se connecter."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"L\'utilisateur actuellement connecté sur cet appareil ne peut pas activer le débogage sans fil. Pour utiliser cette fonctionnalité, un administrateur doit se connecter."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Port USB désactivé"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Pour protéger votre appareil des liquides et des saletés, le port USB est désactivé et ne détecte plus les accessoires.\n\nVous recevrez une notification lorsque vous pourrez de nouveau utiliser le port USB."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Port USB activé pour détecter les chargeurs et les accessoires"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Assistance vocale"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Lecteur de code QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Déverrouiller"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Appareil verrouillé"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Analyse du visage en cours"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Envoyer"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Visage non reconnu"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utilisez empreinte digit."</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connecté"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pourcentage de la batterie inconnu."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connecté à : <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Mode Avion"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"Le VPN est activé."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Batterie : <xliff:g id="NUMBER">%d</xliff:g> pour cent"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batterie chargée à <xliff:g id="PERCENTAGE">%1$s</xliff:g> pour cent : il reste environ <xliff:g id="TIME">%2$s</xliff:g> d\'autonomie, selon votre utilisation"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batterie chargée à <xliff:g id="PERCENTAGE">%1$d</xliff:g> pour cent : il reste environ <xliff:g id="TIME">%2$s</xliff:g> d\'autonomie, selon votre utilisation"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batterie en charge : <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Afficher toutes les notifications"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Téléscripteur activé"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Sonnerie en mode vibreur"</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gérer les utilisateurs"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Micro accessible"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Caméra accessible"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Micro et caméra accessibles"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Micro activé"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Micro désactivé"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Le micro est activé pour tous les services et applis."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"L\'accès au micro est désactivé pour tous les services et applis. Vous pouvez activer l\'accès au micro dans Paramètres &gt; Confidentialité &gt; Micro."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"L\'accès au micro est désactivé pour tous les services et applis. Vous pouvez modifier cette option dans Paramètres &gt; Confidentialité &gt; Micro."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Appareil photo activé"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Appareil photo désactivé"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"L\'appareil photo est activé pour tous les services et applis."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"L\'accès à l\'appareil photo est désactivé pour tous les services et applis."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Pour utiliser le bouton du micro, activez l\'accès au micro dans les paramètres."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Ouvrir les paramètres."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Autre appareil"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Activer/Désactiver l\'aperçu"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Vous ne serez dérangé par aucun son ni aucune vibration, hormis ceux des alarmes, des rappels, des événements et des appels des contacts de votre choix. Le son continuera de fonctionner notamment pour la musique, les vidéos et les jeux."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lorsque vous partagez, enregistrez ou castez une appli, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuer"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partager ou enregistrer une appli"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Autoriser cette appli à partager ou enregistrer ?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Lorsque vous partagez, enregistrez ou castez, cette appli a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Lorsque vous partagez, enregistrez ou castez une appli, cette appli a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquée par votre administrateur informatique"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La capture d\'écran est désactivée conformément aux règles relatives à l\'appareil"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historique"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Déverrouiller pour utiliser"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Problème de récupération de vos cartes. Réessayez plus tard"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Paramètres de l\'écran de verrouillage"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scanner un code QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Profil professionnel"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Mode Avion"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Vous n\'entendrez pas votre prochaine alarme <xliff:g id="WHEN">%1$s</xliff:g>."</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Rebasculer sur <xliff:g id="CARRIER">%s</xliff:g> ?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Il n\'y aura pas de basculement automatique entre les données mobile selon la disponibilité"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Non, merci"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Oui, revenir"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Agrandir tout l\'écran"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Agrandir une partie de l\'écran"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Changer"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Autoriser le défilement diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionner"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Modifier le type d\'agrandissement"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Annuler"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} raccourci supprimé}one{# raccourci supprimé}many{# raccourcis supprimés}other{# raccourcis supprimés}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Supprimer"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Connectée temporairement"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Connexion médiocre"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM j"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"hh:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Autoriser <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> à accéder à tous les journaux de l\'appareil ?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Autoriser un accès unique"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Ne pas autoriser"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Les journaux enregistrent ce qui se passe sur votre appareil. Les applis peuvent les utiliser pour rechercher et résoudre des problèmes.\n\nCertains journaux pouvant contenir des infos sensibles, autorisez uniquement les applis de confiance à accéder à tous les journaux de l\'appareil. \n\nSi vous refusez à cette appli l\'accès à tous les journaux de l\'appareil, elle a quand même accès aux siens. Le fabricant de l\'appareil peut accéder à certains journaux ou certaines infos sur votre appareil."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index e5def1e..745c919 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Permitir sempre desde este ordenador"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Permitir"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Non se permite a depuración por USB"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"O usuario coa sesión iniciada actualmente neste dispositivo non pode activar a depuración por USB. Para utilizar esta función, cambia ao usuario principal."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"O usuario coa sesión iniciada actualmente neste dispositivo non pode activar a depuración por USB. Para utilizar esta función, cambia ao usuario administrador."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Queres cambiar o idioma do sistema ao <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Outro dispositivo solicitou un cambio do idioma do sistema"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Cambiar idioma"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Permitir sempre nesta rede"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Permitir"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Non se permite a depuración sen fíos"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"O usuario coa sesión iniciada actualmente neste dispositivo non pode activar a depuración sen fíos. Para utilizar esta función, cambia ao usuario principal."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"O usuario coa sesión iniciada actualmente neste dispositivo non pode activar a depuración sen fíos. Para utilizar esta función, cambia ao usuario administrador."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"O porto USB está desactivado"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Para protexer o dispositivo de líquidos ou residuos, desactivouse o porto USB e non detectará ningún accesorio.\n\nRecibirás unha notificación cando o poidas utilizar de novo."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Activouse o porto USB para detectar cargadores e accesorios"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Asistente de voz"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Escáner de código QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Desbloquear"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositivo bloqueado"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Analizando cara"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Enviar"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Non se recoñeceu a cara"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa a impresión dixital"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Descoñécese a porcentaxe da batería."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Modo avión"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"A VPN está activada."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Carga da batería: <xliff:g id="NUMBER">%d</xliff:g> por cento."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batería: <xliff:g id="PERCENTAGE">%1$s</xliff:g> por cento, durará <xliff:g id="TIME">%2$s</xliff:g> co uso que adoitas darlle"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batería: <xliff:g id="PERCENTAGE">%1$d</xliff:g> por cento, durará <xliff:g id="TIME">%2$s</xliff:g> co uso que adoitas darlle"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batería cargando. Nivel: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> por cento."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Ver todas as notificacións"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletipo activado"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Timbre en vibración"</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Administrar usuarios"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"O micrófono está dispoñible"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"A cámara está dispoñible"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"O micrófono e a cámara están dispoñibles"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Activouse o micrófono"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Desactivouse o micrófono"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"O micrófono está activado para todas as aplicacións e servizos."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"O acceso ao micrófono está desactivado para todas as aplicacións e servizos. Podes activar o acceso ao micrófono en Configuración &gt; Privacidade &gt; Micrófono."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"O acceso ao micrófono está desactivado para todas as aplicacións e servizos. Podes cambialo en Configuración &gt; Privacidade &gt; Micrófono."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Activouse a cámara"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Desactivouse a cámara"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"A cámara está activada para todas as aplicacións e servizos."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"O acceso á cámara está desactivado para todas as aplicacións e servizos."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Para usar o botón do micrófono, activa o acceso ao micrófono en Configuración."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Abrir configuración."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Outro dispositivo"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Activar/desactivar Visión xeral"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Non te molestará ningún son nin vibración, agás os procedentes de alarmas, recordatorios, eventos e os emisores de chamada especificados. Seguirás escoitando todo aquilo que decidas reproducir, mesmo a música, os vídeos e os xogos."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cando compartes, gravas ou emites unha aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir ou gravar unha aplicación"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Queres permitir que esta aplicación comparta ou grave contido?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Cando compartes, gravas ou emites contido, esta aplicación ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Cando compartes, gravas ou emites aplicacións, esta aplicación ten acceso a todo o que se vexa ou se reproduza nelas. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"O teu administrador de TI bloqueou esta aplicación"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A política do dispositivo desactivou a opción de capturar a pantalla"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todas"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Xestionar"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Produciuse un problema ao obter as tarxetas. Téntao de novo máis tarde"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuración da pantalla de bloqueo"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Escanear código QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Perfil de traballo"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Modo avión"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Non escoitarás a alarma seguinte <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Seguro que queres cambiar de novo a <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"O uso de datos móbiles non cambiará automaticamente en función da dispoñibilidade"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Non, grazas"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Si, cambiar"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar pantalla completa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Amplía parte da pantalla"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Cambiar"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir desprazamento diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Cambiar tamaño"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Cambiar tipo de ampliación"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfacer"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Quitouse {label} atallo}other{Quitáronse # atallos}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Quitar"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Conectada temporalmente"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexión deficiente"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Queres permitir que a aplicación <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acceda a todos os rexistros do dispositivo?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Permitir acceso unha soa vez"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Non permitir"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Os rexistros do dispositivo dan conta do que ocorre neste. As aplicacións poden usalos para buscar problemas e solucionalos.\n\nAlgúns poden conter información confidencial, polo que che recomendamos que só permitas que accedan a todos os rexistros do dispositivo as aplicacións nas que confíes. \n\nEsta aplicación pode acceder aos seus propios rexistros aínda que non lle permitas acceder a todos. É posible que o fabricante do dispositivo teña acceso a algúns rexistros ou á información do teu dispositivo."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 552b049..b9c2c7f 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"હંમેશા આ કમ્પ્યુટરથી મંજૂરી આપો"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"મંજૂરી આપો"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB ડીબગિંગની મંજૂરી નથી"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"હાલમાં આ ઉપકરણમાં સાઇન ઇન થયેલ વપરાશકર્તા USB ડિબગીંગ ચાલુ કરી શકતા નથી. આ સુવિધાનો ઉપયોગ કરવા માટે પ્રાથમિક વપરાશકર્તા પર સ્વિચ કરો."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"હાલમાં આ ડિવાઇસમાં સાઇન ઇન થયેલા વપરાશકર્તા USB ડિબગિંગ ચાલુ કરી શકતા નથી. આ સુવિધાનો ઉપયોગ કરવા માટે કોઈ ઍડમિન વપરાશકર્તા પર સ્વિચ કરો."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"શું તમે સિસ્ટમની ભાષા બદલીને <xliff:g id="LANGUAGE">%1$s</xliff:g> કરવા માગો છો?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"બીજા ડિવાઇસ દ્વારા સિસ્ટમની ભાષા બદલવાની વિનંતી કરવામાં આવી છે"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"ભાષા બદલો"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"આ નેટવર્ક પર હંમેશા મંજૂરી આપો"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"મંજૂરી આપો"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"વાયરલેસ ડિબગીંગની મંજૂરી નથી"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"હાલમાં આ ડિવાઇસમાં સાઇન ઇન થયેલ વપરાશકર્તા વાયરલેસ ડિબગીંગ ચાલુ કરી શકતાં નથી. આ સુવિધાનો ઉપયોગ કરવા માટે પ્રાથમિક વપરાશકર્તા પર સ્વિચ કરો."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"હાલમાં આ ડિવાઇસમાં સાઇન ઇન થયેલા વપરાશકર્તા વાયરલેસ ડિબગીંગ ચાલુ કરી શકતા નથી. આ સુવિધાનો ઉપયોગ કરવા માટે કોઈ ઍડમિન વપરાશકર્તા પર સ્વિચ કરો."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB પોર્ટ બંધ કરવામાં આવ્યો છે"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"પ્રવાહી અથવા ધૂળથી તમારા ડિવાઇસનું રક્ષણ કરવા માટે, USB પોર્ટ બંધ કરવામાં આવ્યો છે અને કોઈ ઍક્સેસરી શોધશે નહીં.\n\nજ્યારે ફરીથી USB પોર્ટને ઉપયોગમાં લેવાનું સુરક્ષિત હશે ત્યારે તમને નોટિફિકેશન આપવામાં આવશે."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"ચાર્જર અને ઍક્સેસરીની ઓળખ માટે USB પોર્ટ ચાલુ કર્યું"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"વૉઇસ સહાય"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR કોડ સ્કૅનર"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"અનલૉક કરો"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"ડિવાઇસ લૉક કરેલું છે"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"ચહેરો સ્કૅન કરવો"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"મોકલો"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"ચહેરો ઓળખાતો નથી"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"તો ફિંગરપ્રિન્ટ વાપરો"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"બ્લૂટૂથ કનેક્ટ થયું."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"બૅટરીની ટકાવારી અજાણ છે."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> થી કનેક્ટ થયાં."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"એરપ્લેન મોડ."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ચાલુ છે."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"બૅટરી <xliff:g id="NUMBER">%d</xliff:g> ટકા."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"તમારા વપરાશના આધારે બૅટરી <xliff:g id="PERCENTAGE">%1$s</xliff:g> ટકા, જે લગભગ <xliff:g id="TIME">%2$s</xliff:g> સુધી ચાલે તેટલી બચી છે"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"તમારા વપરાશના આધારે બૅટરી <xliff:g id="PERCENTAGE">%1$d</xliff:g> ટકા, જે લગભગ <xliff:g id="TIME">%2$s</xliff:g> સુધી ચાલે તેટલી બચી છે"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"બૅટરી ચાર્જ થઈ રહી છે, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"બધી સૂચના જુઓ"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"ટેલિટાઇપરાઇટર સક્ષમ કર્યું."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"રિંગર વાઇબ્રેટ."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"માઇક્રોફોનનો ઍક્સેસ ઉપલબ્ધ છે"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"કૅમેરાનો ઍક્સેસ ઉપલબ્ધ છે"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"માઇક્રોફોન અને કૅમેરાનો ઍક્સેસ ઉપલબ્ધ છે"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"માઇક્રોફોન ચાલુ કર્યું"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"માઇક્રોફોન બંધ કર્યું"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"બધી ઍપ અને સેવાઓ માટે માઇક્રોફોન ચાલુ કર્યું."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"બધી ઍપ અને સેવાઓ માટે માઇક્રોફોનનો ઍક્સેસ બંધ કર્યો છે. તમે સેટિંગ &gt; પ્રાઇવસી &gt; માઇક્રોફોનમાં જઈને માઇક્રોફોનનો ઍક્સેસ ચાલુ કરી શકો છો."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"બધી ઍપ અને સેવાઓ માટે માઇક્રોફોનનો ઍક્સેસ બંધ કર્યો છે. તમે સેટિંગ &gt; પ્રાઇવસી &gt; માઇક્રોફોનમાં જઈને આને બદલી શકો છો."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"કૅમેરા ચાલુ કર્યો"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"કૅમેરા બંધ કર્યો"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"બધી ઍપ અને સેવાઓ માટે કૅમેરા ચાલુ કર્યો."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"બધી ઍપ અને સેવાઓ માટે કૅમેરાનો ઍક્સેસ બંધ કર્યો છે."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"માઇક્રોફોન બટનનો ઉપયોગ કરવા માટે, સેટિંગમાં માઇક્રોફોનનો ઍક્સેસ ચાલુ કરો."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"સેટિંગ ખોલો."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"અન્ય ડિવાઇસ"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ઝલકને ટૉગલ કરો"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"અલાર્મ, રિમાઇન્ડર, ઇવેન્ટ અને તમે ઉલ્લેખ કરો તે કૉલર સિવાય તમને ધ્વનિ કે વાઇબ્રેશન દ્વારા ખલેલ પહોંચાડવામાં આવશે નહીં. સંગીત, વીડિઓ અને રમતો સહિત તમે જે કંઈપણ ચલાવવાનું પસંદ કરશો તે સંભળાતું રહેશે."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"જ્યારે તમે કોઈ ઍપ શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તે ઍપ પર બતાવવામાં કે ચલાવવામાં આવતી હોય તેવી કોઈપણ વસ્તુનો ઍક્સેસ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ધરાવે છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ અથવા અન્ય સંવેદનશીલ માહિતીની બાબતે સાવચેત રહેશો."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ચાલુ રાખો"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"કોઈ ઍપ શેર કરો અથવા રેકોર્ડ કરો"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"આ ઍપને શેર કે રેકોર્ડ કરવાની મંજૂરી આપીએ?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"જ્યારે તમે શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે આ ઍપ પાસે તમારી સ્ક્રીન પર જોઈ શકાતી કે તમારા ડિવાઇસ પર ચલાવવામાં આવતી બધી વસ્તુઓનો ઍક્સેસ હોય છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ અથવા અન્ય સંવેદનશીલ માહિતીની બાબતે સાવચેત રહેશો."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"જ્યારે તમે કોઈ ઍપને શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તેના પર બતાવવામાં કે ચલાવવામાં આવતી બધી વસ્તુઓનો ઍક્સેસ આ ઍપ પાસે હોય છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ અથવા અન્ય સંવેદનશીલ માહિતીની બાબતે સાવચેત રહેશો."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"તમારા IT ઍડમિન દ્વારા બ્લૉક કરાયેલી"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ડિવાઇસ પૉલિસી અનુસાર સ્ક્રીન કૅપ્ચર કરવાની સુવિધા બંધ કરવામાં આવી છે"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"બધુ સાફ કરો"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"મેનેજ કરો"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ઇતિહાસ"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ઉપયોગ કરવા માટે અનલૉક કરો"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"તમારા કાર્ડની માહિતી મેળવવામાં સમસ્યા આવી હતી, કૃપા કરીને થોડા સમય પછી ફરી પ્રયાસ કરો"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"લૉક સ્ક્રીનના સેટિંગ"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR કોડ સ્કૅન કરો"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"ઑફિસની પ્રોફાઇલ"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"એરપ્લેન મોડ"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"તમે <xliff:g id="WHEN">%1$s</xliff:g> એ તમારો આગલો એલાર્મ સાંભળશો નહીં"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> પર પાછા સ્વિચ કરીએ?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"મોબાઇલ ડેટાને ઉપલબ્ધતાના આધારે ઑટોમૅટિક રીતે સ્વિચ કરવામાં આવશે નહીં"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ના, આભાર"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"હા, સ્વિચ કરો"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"પૂર્ણ સ્ક્રીનને મોટી કરો"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"સ્ક્રીનનો કોઈ ભાગ મોટો કરો"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"સ્વિચ"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ડાયગોનલ સ્ક્રોલિંગને મંજૂરી આપો"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"કદ બદલો"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"મોટું કરવાનો પ્રકાર બદલો"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"છેલ્લો ફેરફાર રદ કરો"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} શૉર્ટકટ કાઢી નાખ્યો}one{# શૉર્ટકટ કાઢી નાખ્યો}other{# શૉર્ટકટ કાઢી નાખ્યો}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"કાઢી નાખો"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"હંગામી રીતે કનેક્ટ કર્યું"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"નબળું કનેક્શન"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"શું <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>ને ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી આપીએ?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"એક-વખતના ઍક્સેસની મંજૂરી આપો"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"મંજૂરી આપશો નહીં"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"તમારા ડિવાઇસ પર થતી કામગીરીને ડિવાઇસ લૉગ રેકોર્ડ કરે છે. ઍપ આ લૉગનો ઉપયોગ સમસ્યાઓ શોધી તેનું નિરાકરણ કરવા માટે કરી શકે છે.\n\nઅમુક લૉગમાં સંવેદનશીલ માહિતી હોઈ શકે, આથી ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી માત્ર તમારી વિશ્વાસપાત્ર ઍપને જ આપો. \n\nજો તમે આ ઍપને ડિવાઇસનો બધો લૉગ ઍક્સેસ કરવાની મંજૂરી ન આપો, તો પણ તે તેના પોતાના લૉગ ઍક્સેસ કરી શકે છે. તમારા ડિવાઇસના નિર્માતા હજી પણ કદાચ તમારા ડિવાઇસ પર અમુક લૉગ અથવા માહિતી ઍક્સેસ કરી શકે છે."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-h700dp/dimens.xml b/packages/SystemUI/res/values-h700dp/dimens.xml
index fbd985e..055308f 100644
--- a/packages/SystemUI/res/values-h700dp/dimens.xml
+++ b/packages/SystemUI/res/values-h700dp/dimens.xml
@@ -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.
@@ -15,6 +15,6 @@
   -->
 
 <resources>
-    <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
-    <dimen name="large_clock_text_size">170dp</dimen>
-</resources>
+    <!-- Margin above the ambient indication container -->
+    <dimen name="ambient_indication_container_margin_top">15dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index 94fe209..3a71994 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -15,9 +15,9 @@
   -->
 
 <resources>
-    <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
-    <dimen name="large_clock_text_size">200dp</dimen>
-
     <!-- With the large clock, move up slightly from the center -->
     <dimen name="keyguard_large_clock_top_margin">-112dp</dimen>
+
+    <!-- Margin above the ambient indication container -->
+    <dimen name="ambient_indication_container_margin_top">20dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index c0052d6..79f9696 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"इस कंप्यूटर से हमेशा अनुमति दें"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"अनुमति दें"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB डीबगिंग की अनुमति नहीं है"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"अभी इस डिवाइस में जिस उपयोगकर्ता ने साइन इन किया है, वो USB डीबगिंग चालू नहीं कर सकता. इस सुविधा का इस्तेमाल करने के लिए, प्राथमिक उपयोगकर्ता में बदलें."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"फ़िलहाल, इस डिवाइस में जिस उपयोगकर्ता ने साइन इन किया है वह यूएसबी डीबग करने की प्रोसेस चालू नहीं कर सकता. इस सुविधा का इस्तेमाल करने के लिए, कृपया खाते के एडमिन के तौर पर साइन इन करें."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"क्या आपको सिस्टम की भाषा <xliff:g id="LANGUAGE">%1$s</xliff:g> में बदलनी है?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"किसी दूसरे डिवाइस से, सिस्टम की भाषा बदलने का अनुरोध किया गया"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"भाषा बदलें"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"इस नेटवर्क पर हमेशा अनुमति दें"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"अनुमति दें"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"वॉयरलेस डीबगिंग की सुविधा चालू करने की अनुमति नहीं है"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"अभी इस डिवाइस में जिस उपयोगकर्ता ने साइन इन कर रखा है वह वॉयरलेस डीबगिंग की सुविधा चालू नहीं कर सकता. इस सुविधा का इस्तेमाल करने के लिए, मुख्य उपयोगकर्ता के तौर पर साइन इन करें."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"फ़िलहाल, इस डिवाइस में जिस उपयोगकर्ता ने साइन इन किया है वह वॉयरलेस डीबगिंग की सुविधा चालू नहीं कर सकता. इस सुविधा का इस्तेमाल करने के लिए, खाते के एडमिन के तौर पर साइन इन करें."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"यूएसबी पोर्ट बंद है"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"तरल चीज़ या कचरे से आपके डिवाइस की सुरक्षा करने के लिए, यूएसबी पोर्ट को बंद कर दिया गया है. साथ ही, इससे किसी भी एक्सेसरी की पहचान नहीं की जा सकेगी.\n\nयूएसबी पोर्ट का दोबारा इस्तेमाल करना सुरक्षित होने पर आपको सूचित किया जाएगा."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"चार्जर और एक्सेसरी पहचानने के लिए यूएसबी पोर्ट को चालू कर दिया गया है"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"आवाज़ से डिवाइस का इस्तेमाल"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"क्यूआर कोड स्कैनर"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"अनलॉक करें"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"डिवाइस लॉक है"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"डिवाइस अनलॉक करने के लिए चेहरा स्कैन किया जाता है"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"भेजें"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"चेहरे की पहचान नहीं हुई"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"फ़िंगरप्रिंट इस्तेमाल करें"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लूटूथ कनेक्ट किया गया."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"इस बारे में जानकारी नहीं है कि अभी बैटरी कितने प्रतिशत चार्ज है."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> से कनेक्ट किया गया."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"हवाई जहाज़ मोड."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN चालू."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> प्रति‍शत बैटरी."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> प्रतिशत बैटरी बची है और आपके इस्तेमाल के हिसाब से यह <xliff:g id="TIME">%2$s</xliff:g> में खत्म हो जाएगी"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> प्रतिशत बैटरी बची है और आपके इस्तेमाल के हिसाब से यह <xliff:g id="TIME">%2$s</xliff:g> में खत्म हो जाएगी"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"बैटरी चार्ज हो रही है, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> प्रतिशत."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"पूरी सूचनाएं देखें"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"टेलीटाइपराइटर सक्षम."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"रिंगर कंपन (वाइब्रेशन)."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"माइक्रोफ़ोन का ऐक्सेस उपलब्ध है"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"कैमरे का ऐक्सेस उपलब्ध है"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"माइक्रोफ़ोन और कैमरे का ऐक्सेस उपलब्ध है"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"माइक्रोफ़ोन का ऐक्सेस चालू किया गया"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"माइक्रोफ़ोन का ऐक्सेस बंद किया गया"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"माइक्रोफ़ोन का ऐक्सेस, सभी ऐप्लिकेशन और सेवाओं के लिए चालू किया गया."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"माइक्रोफ़ोन का ऐक्सेस, सभी ऐप्लिकेशन और सेवाओं के लिए बंद किया गया. इसे चालू करने के लिए, सेटिंग &gt; निजता &gt; माइक्रोफ़ोन पर जाएं."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"माइक्रोफ़ोन का ऐक्सेस, सभी ऐप्लिकेशन और सेवाओं के लिए बंद किया गया. इसे चालू करने के लिए, सेटिंग &gt; निजता &gt; माइक्रोफ़ोन पर जाएं."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"कैमरे का ऐक्सेस चालू किया गया"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"कैमरे का ऐक्सेस बंद किया गया"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"कैमरे का ऐक्सेस, सभी ऐप्लिकेशन और सेवाओं के लिए चालू किया गया."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"सभी ऐप्लिकेशन और सेवाओं के लिए कैमरे का ऐक्सेस बंद किया गया."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"माइक्रोफ़ोन बटन का इस्तेमाल करने के लिए, सेटिंग में जाकर माइक्रोफ़ोन का ऐक्सेस चालू करें."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"सेटिंग खोलें."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"अन्य डिवाइस"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"खास जानकारी टॉगल करें"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"आपको अलार्म, रिमाइंडर, इवेंट और चुनिंदा कॉल करने वालों के अलावा किसी और तरह से (आवाज़ करके और थरथरा कर ) परेशान नहीं किया जाएगा. आप फिर भी संगीत, वीडियो और गेम सहित अपना चुना हुआ सब कुछ सुन सकते हैं."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"शेयर, रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> के पास उस ऐप्लिकेशन पर दिख रही हर चीज़ या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी रखें"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ऐप्लिकेशन शेयर करें या उसकी रिकॉर्डिंग करें"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"क्या इस ऐप्लिकेशन को शेयर या रिकॉर्ड करने की अनुमति देनी है?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"शेयर, रिकॉर्ड या कास्ट करते समय, इस ऐप्लिकेशन के पास स्क्रीन पर दिख रही हर चीज़ या डिवाइस पर चल रहे सभी मीडिया का ऐक्सेस होता है. इसलिए, इस दौरान पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या दूसरी संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"किसी ऐप्लिकेशन को शेयर, रिकॉर्ड या कास्ट करते समय, इस ऐप्लिकेशन के पास उस ऐप्लिकेशन पर दिख रही हर चीज़ या उस पर चल रहे सभी मीडिया का ऐक्सेस होता है. इसलिए, इस दौरान पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या दूसरी संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"आपके आईटी एडमिन ने स्क्रीन कैप्चर करने की सुविधा पर रोक लगाई है"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिवाइस से जुड़ी नीति के तहत स्क्रीन कैप्चर करने की सुविधा बंद है"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सभी को हटाएं"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"मैनेज करें"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"इस्तेमाल करने के लिए, डिवाइस अनलॉक करें"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"आपके कार्ड की जानकारी पाने में कोई समस्या हुई है. कृपया बाद में कोशिश करें"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"लॉक स्क्रीन की सेटिंग"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"क्यूआर कोड स्कैन करें"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"वर्क प्रोफ़ाइल"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"हवाई जहाज़ मोड"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"आपको <xliff:g id="WHEN">%1$s</xliff:g> पर अपना अगला अलार्म नहीं सुनाई देगा"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"क्या आपको मोबाइल डेटा, <xliff:g id="CARRIER">%s</xliff:g> पर वापस से स्विच करना है?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"उपलब्ध होने पर, मोबाइल डेटा अपने-आप स्विच नहीं होगा"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"स्विच न करें"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"स्विच करें"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"फ़ुल स्क्रीन को ज़ूम करें"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"स्क्रीन के किसी हिस्से को ज़ूम करें"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"स्विच"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"तिरछी दिशा में स्क्रोल करने की अनुमति दें"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"साइज़ बदलें"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"ज़ूम करने की सुविधा का टाइप बदलें"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"पहले जैसा करें"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} शॉर्टकट हटाया गया}one{# शॉर्टकट हटाया गया}other{# शॉर्टकट हटाए गए}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"हटाएं"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"इंटरनेट कनेक्शन कुछ समय के लिए है"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"इंटरनेट कनेक्शन खराब है"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"क्या <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> को डिवाइस लॉग का ऐक्सेस देना है?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"एक बार ऐक्सेस करने की अनुमति दें"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"अनुमति न दें"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"डिवाइस लॉग में आपके डिवाइस पर की गई कार्रवाइयां रिकॉर्ड होती हैं. ऐप्लिकेशन, इन लॉग का इस्तेमाल गड़बड़ियां ढूंढने और उन्हें सही करने के लिए करता है.\n\nकुछ लॉग में संवेदनशील जानकारी हो सकती है. इसलिए, सिर्फ़ भरोसेमंद ऐप्लिकेशन को डिवाइस के सभी लॉग का ऐक्सेस दें. \n\nअगर इस ऐप्लिकेशन को डिवाइस के सभी लॉग का ऐक्सेस नहीं दिया जाता है, तब भी यह डिवाइस पर मौजूद अपने लॉग ऐक्सेस कर सकता है. डिवाइस को बनाने वाली कंपनी अब भी डिवाइस के कुछ लॉग या जानकारी को ऐक्सेस कर सकती है."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 60f0c8a..a468071 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Uvijek dopusti s ovog računala"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Dopusti"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Otklanjanje pogrešaka putem USB-a nije dopušteno"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Korisnik koji je trenutačno prijavljen na ovaj uređaj ne može uključiti otklanjanje pogrešaka putem USB-a. Da biste upotrebljavali tu značajku, prijeđite na primarnog korisnika."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Korisnik koji je trenutačno prijavljen na ovaj uređaj ne može uključiti otklanjanje pogrešaka putem USB-a. Da biste upotrebljavali tu značajku, prijeđite na korisnika s administratorskim pravima."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Želite li promijeniti jezik sustava u <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Drugi uređaj zatražio je promjenu jezika sustava"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Promijeni jezik"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Uvijek dopusti na ovoj mreži"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Dopusti"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Bežično otklanjanje pogrešaka nije dopušteno"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Korisnik koji je trenutačno prijavljen na ovaj uređaj ne može uključiti bežično otklanjanje pogrešaka. Da biste upotrebljavali tu značajku, prijeđite na primarnog korisnika."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Korisnik koji je trenutačno prijavljen na ovaj uređaj ne može uključiti bežično otklanjanje pogrešaka. Da biste upotrebljavali tu značajku, prijeđite na korisnika s administratorskim pravima."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Onemogućen je USB priključak"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Radi zaštite uređaja od tekućine ili prljavštine USB priključak onemogućen je i neće otkrivati pribor.\n\nPrimit ćete obavijest kad upotreba USB priključka ponovo bude sigurna."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB priključak omogućen za otkrivanje punjača i opreme"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Glasovna pomoć"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Čitač QR koda"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Otključavanje"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Uređaj je zaključan"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Skeniranje lica"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Pošalji"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Lice nije prepoznato"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Upotrijebite otisak prsta"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth povezan."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Postotak baterije nije poznat."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Spojen na <xliff:g id="BLUETOOTH">%s</xliff:g> ."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Način rada u zrakoplovu"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN uključen."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Baterija <xliff:g id="NUMBER">%d</xliff:g> posto."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterija je na <xliff:g id="PERCENTAGE">%1$s</xliff:g> posto, još otprilike <xliff:g id="TIME">%2$s</xliff:g> na temelju vaše upotrebe"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterija je na <xliff:g id="PERCENTAGE">%1$d</xliff:g> posto, još otprilike <xliff:g id="TIME">%2$s</xliff:g> na temelju vaše upotrebe"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Baterija se puni, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> posto."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Pogledajte sve obavijesti"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter je omogućen."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibracija softvera zvona."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon je dostupan"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera je dostupna"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon i kamera su dostupni"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon je uključen"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon je isključen"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon je omogućen za sve aplikacije i usluge."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Pristup mikrofonu onemogućen je za sve aplikacije i usluge. Pristup mikrofonu možete omogućiti u odjeljku Postavke &gt; Privatnost &gt; Mikrofon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Pristup mikrofonu onemogućen je za sve aplikacije i usluge. Tu postavku možete promijeniti u odjeljku Postavke &gt; Privatnost &gt; Mikrofon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera je uključena"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera je isključena"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera je omogućena za sve aplikacije i usluge."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Pristup kameri onemogućen je za sve aplikacije i usluge."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Da biste koristili gumb mikrofona, omogućite pristup mikrofonu u postavkama."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Otvori postavke"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Ostali uređaji"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Uključivanje/isključivanje pregleda"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Neće vas ometati zvukovi i vibracije, osim alarma, podsjetnika, događaja i pozivatelja koje navedete. I dalje ćete čuti sve što želite reproducirati, uključujući glazbu, videozapise i igre."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kad dijelite, snimate ili emitirate aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Nastavi"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dijeljenje ili snimanje pomoću aplikacije"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Želite li ovoj aplikaciji omogućiti dijeljenje ili bilježenje?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kad dijelite, snimate ili emitirate, ova aplikacija ima pristup svemu što je vidljivo na vašem zaslonu ili se reproducira na vašem uređaju. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kad dijelite, snimate ili emitirate aplikaciju, ova aplikacija ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokirao vaš IT administrator"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje zaslona onemogućeno je u skladu s pravilima za uređaje"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši sve"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Povijest"</string>
@@ -488,7 +511,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Otključajte da biste koristili"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Pojavio se problem prilikom dohvaćanja kartica, pokušajte ponovo kasnije"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Postavke zaključanog zaslona"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skeniraj QR kôd"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"Čitač QR koda"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Poslovni profil"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Način rada u zrakoplovu"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sljedeći alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +750,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vratiti se na mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilni podaci neće se automatski prebaciti na temelju dostupnosti"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, hvala"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, prebaci"</string>
     <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>
@@ -767,6 +794,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Povećajte cijeli zaslon"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Povećaj dio zaslona"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Prebacivanje"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Dopusti dijagonalno pomicanje"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Promijeni veličinu"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Promijeni vrstu povećanja"</string>
@@ -785,12 +814,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Poništi"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Uklonjen je prečac {label}}one{# prečac je uklonjen}few{# prečaca su uklonjena}other{# prečaca je uklonjeno}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Ukloni"</string>
     <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 +965,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Privremeno povezano"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Slaba veza"</string>
     <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>
@@ -992,4 +1026,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE., d. MMM."</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Želite li dopustiti aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> da pristupa svim zapisnicima uređaja?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Omogući jednokratni pristup"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Nemoj dopustiti"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"U zapisnicima uređaja bilježi se što se događa na uređaju. Aplikacije mogu koristiti te zapisnike kako bi pronašle i riješile poteškoće.\n\nNeki zapisnici mogu sadržavati osjetljive podatke, pa pristup svim zapisnicima uređaja odobrite samo pouzdanim aplikacijama. \n\nAko ne dopustite ovoj aplikaciji da pristupa svim zapisnicima uređaja, ona i dalje može pristupati svojim zapisnicima. Proizvođač vašeg uređaja i dalje može pristupati nekim zapisnicima ili podacima na vašem uređaju."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 0f6daa2..2c2a966 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Mindig engedélyezze erről a számítógépről"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Engedélyezés"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Az USB hibakeresése nem engedélyezett"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Az eszközre jelenleg bejelentkezett felhasználó nem engedélyezheti az USB-hibakeresést. A funkció használatához váltson az elsődleges felhasználóra."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Az eszközre jelenleg bejelentkezett felhasználó nem engedélyezheti az USB-hibakeresést. A funkció használatához váltson adminisztrátor felhasználóra."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Kívánja <xliff:g id="LANGUAGE">%1$s</xliff:g> nyelvre módosítani a rendszer nyelvét?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Egy másik eszköz a rendszer nyelvének módosítását kéri"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Nyelvmódosítás"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Mindig engedélyezze ezen a hálózaton"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Engedélyezés"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"A vezeték nélküli hibakeresés nem engedélyezett"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Az eszközre jelenleg bejelentkezett felhasználó nem engedélyezheti a vezeték nélküli hibakeresést. A funkció használatához váltson az elsődleges felhasználóra."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Az eszközre jelenleg bejelentkezett felhasználó nem engedélyezheti a vezeték nélküli hibakeresést. A funkció használatához váltson adminisztrátor felhasználóra."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB-port letiltva"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Az eszköz folyadéktól és szennyeződésektől való megóvása érdekében az USB-portot letiltottuk, így az nem észleli a kiegészítőket.\n\nÉrtesítést küldünk, amikor ismét rendben használhatja az USB-portot."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Az USB-csatlakozó számára engedélyezve van a töltők és más tartozékok észlelése"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Hangsegéd"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-kód-szkennelő"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Feloldás"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Az eszköz zárolva van"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Arc keresése"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Küldés"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Az arc nem ismerhető fel"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Használjon ujjlenyomatot"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth csatlakoztatva."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Az akkumulátor töltöttségi szintje ismeretlen."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Csatlakoztatva a következőhöz: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Repülőgép üzemmód."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN bekapcsolva."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Akkumulátor <xliff:g id="NUMBER">%d</xliff:g> százalék."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Az akkumulátor <xliff:g id="PERCENTAGE">%1$s</xliff:g> százalékon áll, a használati adatok alapján körülbelül <xliff:g id="TIME">%2$s</xliff:g> múlva merül le"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Az akkumulátor <xliff:g id="PERCENTAGE">%1$d</xliff:g> százalékon áll, a használati adatok alapján körülbelül <xliff:g id="TIME">%2$s</xliff:g> múlva merül le"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Tölt az akkumulátor, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> százalék."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Összes értesítés megtekintése"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter engedélyezve."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Csengő rezeg."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Felhasználók kezelése"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"A mikrofon használható"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"A kamera használható"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"A mikrofon és a kamera használható"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon bekapcsolva"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon kikapcsolva"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"A mikrofon az összes alkalmazás és szolgáltatás számára engedélyezve van."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"A mikrofonhoz való hozzáférés az összes alkalmazás és szolgáltatás számára le van tiltva. A mikrofonhoz való hozzáférést a következő menüpontban engedélyezheti: Beállítások &gt; Adatvédelem &gt; Mikrofon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"A mikrofonhoz való hozzáférés az összes alkalmazás és szolgáltatás számára le van tiltva. Ezt a következő menüpontban módosíthatja: Beállítások &gt; Adatvédelem &gt; Mikrofon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera bekapcsolva"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera kikapcsolva"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"A kamera az összes alkalmazás és szolgáltatás számára engedélyezve van."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"A kamerához való hozzáférés az összes alkalmazás és szolgáltatás számára le van tiltva."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Ha használni szeretné a Mikrofon gombot, engedélyezze a mikrofonhoz való hozzáférést a Beállításokban."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Beállítások megnyitása."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Más eszköz"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Áttekintés be- és kikapcsolása"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Az Ön által meghatározott ébresztéseken, emlékeztetőkön, eseményeken és hívókon kívül nem fogja Önt más hang vagy rezgés megzavarni. Továbbra is lesz hangjuk azoknak a tartalmaknak, amelyeket Ön elindít, például zenék, videók és játékok."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Amikor Ön megoszt, rögzít vagy átküld egy alkalmazást, a(z) <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> az adott appban látható vagy lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel és más bizalmas információkkal."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Folytatás"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Alkalmazás megosztása és rögzítése"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Engedélyezi ennek az alkalmazásnak a megosztást és a rögzítést?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Amikor Ön megosztást, rögzítést vagy átküldést végez, az alkalmazás a képernyőn látható vagy az eszközön lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel és más bizalmas információkkal."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Amikor Ön megoszt, rögzít vagy átküld egy alkalmazást, az alkalmazás az adott appban látható vagy lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel és más bizalmas információkkal."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Rendszergazda által letiltva"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A képernyőfelvételt eszközszabályzat tiltja"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Az összes törlése"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Kezelés"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Előzmények"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Oldja fel a használathoz"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Probléma merült fel a kártyák lekérésekor, próbálja újra később"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lezárási képernyő beállításai"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR-kód beolvasása"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Munkahelyi profil"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Repülős üzemmód"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Nem fogja hallani az ébresztést ekkor: <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Átvált a következőre: <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Nem kerül sor a mobiladat-forgalom automatikus átváltására a rendelkezésre állás alapján"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Most nem"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Igen, átváltok"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"A teljes képernyő felnagyítása"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Képernyő bizonyos részének nagyítása"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Váltás"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Átlós görgetés engedélyezése"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Átméretezés"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Nagyítási típus módosítása"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Visszavonás"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} gyorsparancs eltávolítva}other{# gyorsparancs eltávolítva}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Eltávolítás"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ideiglenesen csatlakoztatva"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Gyenge kapcsolat"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, HHH n"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"ó:pp"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"óó:pp"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Engedélyezi a(z) <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> számára, hogy hozzáférjen az összes eszköznaplóhoz?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Egyszeri hozzáférés engedélyezése"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Tiltás"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Az eszköznaplók rögzítik, hogy mi történik az eszközén. Az alkalmazások ezeket a naplókat használhatják a problémák megkeresésére és kijavítására.\n\nBizonyos naplók bizalmas adatokat is tartalmazhatnak, ezért csak olyan alkalmazások számára engedélyezze az összes eszköznaplóhoz való hozzáférést, amelyekben megbízik. \n\nHa nem engedélyezi ennek az alkalmazásnak, hogy hozzáférjen az összes eszköznaplójához, az app továbbra is hozzáférhet a saját naplóihoz. Előfordulhat, hogy az eszköz gyártója továbbra is hozzáfér az eszközön található bizonyos naplókhoz és adatokhoz."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 4724d5c..15b5c6d 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Միշտ թույլատրել այս համակարգչից"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Թույլատրել"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB-ով վրիպազերծումը թույլատրված չէ"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Ընթացիկ հաշվի միջոցով չեք կարող միացնել USB-ով վրիպազերծումը: Այս գործառույթը միացնելու համար մուտք գործեք հիմնական օգտատիրոջ հաշիվ:"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Ընթացիկ հաշվի օգտատերը չի կարող միացնել USB վրիպազերծումը։ Այս գործառույթը միացնելու համար մուտք գործեք ադմինիստրատորի հաշիվ"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Դարձնե՞լ համակարգի լեզուն <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Մեկ այլ սարք համակարգի լեզվի փոփոխության հարցում է ուղարկել"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Փոխել լեզուն"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Միշտ թույլատրել այս ցանցում"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Թույլատրել"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Անլար վրիպազերծումը թույլատրված չէ"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Դուք չեք կարող միացնել անլար վրիպազերծումը ընթացիկ հաշվի միջոցով։ Այս գործառույթը միացնելու համար մուտք գործեք հիմնական օգտատիրոջ հաշիվ։"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Դուք չեք կարող միացնել անլար վրիպազերծումը ընթացիկ հաշվի միջոցով։ Այս գործառույթը միացնելու համար մուտք գործեք ադմինիստրատորի հաշիվ"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB միացքն անջատված է"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"USB միացքն անջատվել է, որպեսզի ձեր սարքը չթրջվի կամ չաղտոտվի: Այժմ USB միացքի միջոցով հնարավոր չէ միացնել այլ սարքեր:\n\nԴուք ծանուցում կստանաք, երբ այն նորից անվտանգ լինի օգտագործել:"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB միացքը միացվել է՝ լիցքավորիչներն ու լրասարքերը հայտնաբերելու համար"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Ձայնային հուշումներ"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR կոդերի սկաներ"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Ապակողպել"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Սարքը կողպված է"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Դեմքի սկանավորում"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Ուղարկել"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Դեմքը չի ճանաչվել"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Օգտագործեք մատնահետք"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-ը միացված է:"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Մարտկոցի լիցքի մակարդակն անհայտ է։"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Միացված է <xliff:g id="BLUETOOTH">%s</xliff:g>-ին:"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Ավիառեժիմ"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"Միացնել VPN-ը։"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Մարտկոցը <xliff:g id="NUMBER">%d</xliff:g> տոկոս է:"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Մարտկոցի լիցքը <xliff:g id="PERCENTAGE">%1$s</xliff:g> տոկոս է և կբավարարի մոտ <xliff:g id="TIME">%2$s</xliff:g>՝ կախված օգտագործման եղանակից:"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Մարտկոցի լիցքը <xliff:g id="PERCENTAGE">%1$d</xliff:g> տոկոս է և կբավարարի մոտ <xliff:g id="TIME">%2$s</xliff:g>՝ կախված օգտագործման եղանակից:"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Մարտկոցը լիցքավորվում է: Լիցքը <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> տոկոս է:"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Տեսնել բոլոր ծանուցումները"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Հեռատիպը միացված է:"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Թրթռազանգ:"</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Խոսափողը հասանելի է"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Տեսախցիկը հասանելի է"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Խոսափողն ու տեսացիկը հասանելի են"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Խոսափողը միացավ"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Խոսափողն անջատվեց"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Խոսափողը բոլոր հավելվածների և ծառայությունների համար միացված է։"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Խոսափողն օգտագործելու թույլտվությունը բոլոր հավելվածների և ծառայությունների համար անջատված է։ Խոսափողի օգտագործումը թույլատրելու համար անցեք Կարգավորումներ &gt; Գաղտնիություն &gt; Խոսափող։"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Խոսափողն օգտագործելու թույլտվությունը բոլոր հավելվածների և ծառայությունների համար անջատված է։ Սա փոխելու համար անցեք Կարգավորումներ &gt; Գաղտնիություն &gt; Խոսափող։"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Տեսախցիկը միացված է"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Տեսախցիկն անջատված է"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Տեսախցիկը բոլոր հավելվածների և ծառայությունների համար միացված է։"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Տեսախցիկն օգտագործելու թույլտվությունը բոլոր հավելվածների և ծառայությունների համար անջատված է։"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Խոսափողի կոճակն օգտագործելու համար կարգավորումներում թույլատրեք խոսափողի օգտագործումը։"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Բացել կարգավորումները։"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Այլ սարք"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Միացնել/անջատել համատեսքը"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Ձայները և թրթռոցները չեն անհանգստացնի ձեզ, բացի ձեր կողմից նշված զարթուցիչները, հիշեցումները, միջոցառումների ծանուցումները և զանգերը։ Դուք կլսեք ձեր ընտրածի նվագարկումը, այդ թվում՝ երաժշտություն, տեսանյութեր և խաղեր:"</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք որևէ հավելվածի էկրանը, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին հասանելի է դառնում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Շարունակել"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Հավելվածի էկրանի ցուցադրում կամ տեսագրում"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Թույլատրե՞լ այս հավելվածին ցուցադրել կամ տեսագրել էկրանը"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք էկրանը, այս հավելվածին հասանելի է դառնում այն ամենը, ինչ տեսանելի է էկրանին և նվագարկվում է ձեր սարքում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք որևէ հավելվածի էկրանը, այս հավելվածին հասանելի է դառնում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Արգելափակվել է ձեր ՏՏ ադմինիստրատորի կողմից"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Էկրանի տեսագրումն անջատված է սարքի կանոնների համաձայն"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Մաքրել բոլորը"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Կառավարել"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Պատմություն"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ապակողպել՝ օգտագործելու համար"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Չհաջողվեց բեռնել քարտերը։ Նորից փորձեք։"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Կողպէկրանի կարգավորումներ"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Սկանավորել QR կոդը"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Android for Work-ի պրոֆիլ"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Ավիառեժիմ"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Ժամը <xliff:g id="WHEN">%1$s</xliff:g>-ի զարթուցիչը չի զանգի"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Անցնե՞լ <xliff:g id="CARRIER">%s</xliff:g> ցանցին"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Սարքն ավտոմատ չի անցնի բջջային ինտերնետին"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ոչ, շնորհակալություն"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Այո"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Խոշորացնել ամբողջ էկրանը"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Խոշորացնել էկրանի որոշակի հատվածը"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Փոխել"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Թույլատրել անկյունագծով ոլորումը"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Փոխել չափը"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Փոխել խոշորացման տեսակը"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Հետարկել"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} դյուրանցում հեռացվեց}one{# դյուրանցում հեռացվեց}other{# դյուրանցում հեռացվեց}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Հեռացնել"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ժամանակավոր կապ"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Թույլ կապ"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Հասանելի դարձնե՞լ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> հավելվածին սարքի բոլոր մատյանները"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Թույլատրել մեկանգամյա մուտքը"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Չթույլատրել"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Այն, ինչ տեղի է ունենում ձեր սարքում, գրանցվում է սարքի մատյաններում։ Հավելվածները կարող են դրանք օգտագործել անսարքությունները հայտնաբերելու և վերացնելու նպատակով։\n\nՔանի որ որոշ մատյաններ անձնական տեղեկություններ են պարունակում, խորհուրդ ենք տալիս հասանելի դարձնել ձեր սարքի բոլոր մատյանները միայն այն հավելվածներին, որոնց վստահում եք։ \n\nԵթե այս հավելվածին նման թույլտվություն չեք տվել, դրան նախկինի պես հասանելի կլինեն իր մատյանները։ Հնարավոր է՝ ձեր սարքի արտադրողին ևս հասանելի լինեն սարքի որոշ մատյաններ և տեղեկություններ։"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 8fefe40..31d2b81 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Selalu izinkan dari komputer ini"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Izinkan"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Debug USB tidak diizinkan"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Pengguna yang sedang login ke perangkat ini tidak dapat mengaktifkan proses debug USB. Beralihlah ke pengguna utama untuk menggunakan fitur ini."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Pengguna yang sedang login ke perangkat ini tidak dapat mengaktifkan proses debug USB. Beralihlah ke pengguna admin untuk menggunakan fitur ini."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Ingin mengubah bahasa sistem menjadi <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Perubahan bahasa sistem diminta oleh perangkat lain"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Ubah bahasa"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Selalu izinkan di jaringan ini"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Izinkan"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Proses debug nirkabel tidak diizinkan"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Pengguna yang sedang login ke perangkat ini tidak dapat mengaktifkan proses debug nirkabel. Beralihlah ke pengguna utama untuk menggunakan fitur ini."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Pengguna yang sedang login ke perangkat ini tidak dapat mengaktifkan proses debug nirkabel. Beralihlah ke pengguna admin untuk menggunakan fitur ini."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Port USB dinonaktifkan"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Untuk melindungi perangkat dari cairan atau kotoran, port USB dinonaktifkan dan tidak akan mendeteksi aksesori apa pun.\n\nAnda akan diberi tahu jika port USB sudah dapat digunakan kembali."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Port USB diaktifkan untuk mendeteksi pengisi daya dan aksesori"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Bantuan Suara"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Pemindai Kode QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Buka kunci"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Perangkat terkunci"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Memindai wajah"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Kirim"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Tidak mengenali wajah"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gunakan sidik jari"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth terhubung."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Persentase baterai tidak diketahui."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Terhubung ke <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Mode pesawat."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN aktif."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Baterai <xliff:g id="NUMBER">%d</xliff:g> persen."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterai <xliff:g id="PERCENTAGE">%1$s</xliff:g> persen, sekitar <xliff:g id="TIME">%2$s</xliff:g> lagi berdasarkan penggunaan Anda"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterai <xliff:g id="PERCENTAGE">%1$d</xliff:g> persen, sekitar <xliff:g id="TIME">%2$s</xliff:g> lagi berdasarkan penggunaan Anda"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Mengisi daya baterai, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> persen."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Lihat semua notifikasi"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter diaktifkan."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Pendering bergetar."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Kelola pengguna"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon tersedia"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera tersedia"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon dan kamera tersedia"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon diaktifkan"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon dinonaktifkan"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon diaktifkan untuk semua aplikasi dan layanan."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Akses mikrofon dinonaktifkan untuk semua aplikasi dan layanan. Anda dapat mengaktifkan akses mikrofon di Setelan &gt; Privasi &gt; Mikrofon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Akses mikrofon dinonaktifkan untuk semua aplikasi dan layanan. Anda dapat mengubahnya di Setelan &gt; Privasi &gt; Mikrofon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera diaktifkan"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera dinonaktifkan"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera diaktifkan untuk semua aplikasi dan layanan."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Akses kamera dinonaktifkan untuk semua aplikasi dan layanan."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Untuk menggunakan tombol mikrofon, aktifkan akses mikrofon di Setelan."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Buka setelan."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Perangkat lainnya"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Aktifkan Ringkasan"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Anda tidak akan terganggu oleh suara dan getaran, kecuali dari alarm, pengingat, acara, dan penelepon yang Anda tentukan. Anda akan tetap mendengar apa pun yang telah dipilih untuk diputar, termasuk musik, video, dan game."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Jika Anda membagikan, merekam, atau mentransmisikan suatu aplikasi, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Lanjutkan"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Bagikan atau rekam aplikasi"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Izinkan aplikasi ini membagikan atau merekam?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Jika Anda membagikan, merekam, atau mentransmisikan, aplikasi ini akan memiliki akses ke semua hal yang ditampilkan di layar atau yang diputar di perangkat Anda. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Jika Anda membagikan, merekam, atau mentransmisikan suatu aplikasi, aplikasi ini akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Diblokir oleh admin IT Anda"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Pengambilan screenshot dinonaktifkan oleh kebijakan perangkat"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hapus semua"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Kelola"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Histori"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Buka kunci untuk menggunakan"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Terjadi error saat mendapatkan kartu Anda, coba lagi nanti"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Setelan layar kunci"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Pindai kode QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Profil kerja"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Mode pesawat"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Anda tidak akan mendengar alarm berikutnya <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Beralih kembali ke <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Data seluler tidak akan dialihkan secara otomatis berdasarkan ketersediaan"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Lain kali"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ya, alihkan"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Memperbesar tampilan layar penuh"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Perbesar sebagian layar"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Alihkan"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Izinkan scrolling diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ubah ukuran"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Ubah jenis pembesaran"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Urungkan"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Pintasan {label} dihapus}other{# pintasan dihapus}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Hapus"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Terhubung sementara"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Koneksi buruk"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Izinkan <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> mengakses semua log perangkat?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Izinkan akses satu kali"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Jangan izinkan"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Log perangkat merekam hal-hal yang terjadi di perangkat Anda. Aplikasi dapat menggunakan log ini untuk menemukan dan memperbaiki masalah.\n\nBeberapa log mungkin berisi info sensitif, jadi hanya izinkan aplikasi yang Anda percayai untuk mengakses semua log perangkat. \n\nJika Anda tidak mengizinkan aplikasi ini mengakses semua log perangkat, aplikasi masih dapat mengakses log-nya sendiri. Produsen perangkat masih dapat mengakses beberapa log atau info di perangkat Anda."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index d22680e..ff12e9c 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Leyfa alltaf úr þessari tölvu"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Leyfa"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB-villuleit ekki leyfð"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Notandinn sem er skráður inn í þetta tæki núna getur ekki kveikt á USB-villuleit. Til þess að nota þennan eiginleika skaltu skipta yfir í aðalnotandann."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Notandinn sem er skráður inn í þetta tæki núna getur ekki kveikt á USB-villuleit. Skiptu yfir í stjórnanda til að nota þennan eiginleika."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Viltu breyta tungumáli kerfis í <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Beiðni frá öðru tæki um að breyta tungumáli kerfis"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Breyta tungumáli"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Leyfa alltaf á þessu neti"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Leyfa"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Þráðlaus villuleit er ekki leyfð"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Notandinn sem er skráður inn í þetta tæki núna getur ekki kveikt á þráðlausri villuleit. Til að nota þennan eiginleika þarf að skipta yfir í aðalnotanda."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Notandinn sem er skráður inn í þetta tæki núna getur ekki kveikt á þráðlausri villuleit. Skiptu yfir í stjórnanda til að nota þennan eiginleika."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB-tengi gert óvirkt"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Til að vernda tækið fyrir vökva og óhreinindum er USB-tengið óvirkt og mun ekki greina aukabúnað.\n\nÞú færð tilkynningu þegar öruggt er að nota USB-tengið aftur."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Kveikt var á USB-tengi til að greina hleðslutæki og aukabúnað"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Raddaðstoð"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Veski"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-kóðaskanni"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Taka úr lás"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Tækið er læst"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Andlit skannað"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Senda"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Andlit þekkist ekki"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Nota fingrafar í staðinn"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth tengt."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Staða rafhlöðu óþekkt."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Tengt við <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Flugstilling"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"Kveikt á VPN."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> prósent á rafhlöðu."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Rafhlaða í <xliff:g id="PERCENTAGE">%1$s</xliff:g> prósentum, um það bil <xliff:g id="TIME">%2$s</xliff:g> eftir miðað við notkun þína"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Rafhlaða í <xliff:g id="PERCENTAGE">%1$d</xliff:g> prósentum, um það bil <xliff:g id="TIME">%2$s</xliff:g> eftir miðað við notkun þína"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Rafhlaða í hleðslu, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Sjá allar tilkynningar"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Fjarriti virkur."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Titrar við hringingu."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Stjórna notendum"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Hljóðnemi tiltækur"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Myndavél tiltæk"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Hljóðnemi og myndavél tiltæk"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Kveikt á hljóðnemanum"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Slökkt á hljóðnemanum"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Kveikt er á hljóðnema fyrir öll forrit og þjónustur."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Slökkt er á aðgangi að hljóðnema fyrir öll forrit og þjónustur. Þú getur veitt aðgang að hljóðnema í „Stillingar &gt; Persónuvernd &gt; Hljóðnemi“."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Slökkt er á aðgangi að hljóðnema fyrir öll forrit og þjónustur. Þú getur breytt þessu í „Stillingar &gt; Persónuvernd &gt; Hljóðnemi“."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kveikt á myndavél"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Slökkt á myndavél"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kveikt er á myndavél fyrir öll forrit og þjónustur."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Slökkt er á aðgangi að myndavél fyrir öll forrit og þjónustur."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Veittu aðgang að hljóðnema í stillingunum til að nota hljóðnemahnappinn."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Opna stillingar."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Annað tæki"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Kveikja/slökkva á yfirliti"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Þú verður ekki fyrir truflunum frá hljóðmerkjum og titringi, fyrir utan vekjara, áminningar, viðburði og símtöl frá þeim sem þú leyfir fyrirfram. Þú heyrir áfram í öllu sem þú velur að spila, svo sem tónlist, myndskeiðum og leikjum."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Þegar þú deilir, tekur upp eða sendir út forrit hefur <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Áfram"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deila eða taka upp forrit"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Leyfa þessu forriti að deila eða taka upp?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Þegar þú deilir, tekur upp eða sendir út fær þetta forrit aðgang að öllu sem sést á skjánum eða spilast í tækinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Þegar þú deilir, tekur upp eða sendir út forrit hefur viðkomandi forrit aðgang að öllu sem sést eða spilast í forritinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Útilokað af kerfisstjóra"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Slökkt er á skjáupptöku í tækjareglum"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Stjórna"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Ferill"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Taktu úr lás til að nota"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Vandamál kom upp við að sækja kortin þín. Reyndu aftur síðar"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Stillingar fyrir læstan skjá"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skanna QR-kóða"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Vinnusnið"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Flugstilling"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Ekki mun heyrast í vekjaranum <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Skipta aftur yfir í <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Ekki verður skipt sjálfkrafa á milli farsímagagna byggt á tiltækileika"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nei takk"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Já, skipta"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Stækka allan skjáinn"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Stækka hluta skjásins"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Rofi"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Leyfa skáflettingu"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Breyta stærð"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Breyta gerð stækkunar"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Afturkalla"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} flýtileið fjarlægð}one{# flýtileið fjarlægð}other{# flýtileiðir fjarlægðar}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Fjarlægja"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tímabundin tenging"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Léleg tenging"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"k:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Veita <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> aðgang að öllum annálum í tækinu?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Leyfa aðgang í eitt skipti"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Ekki leyfa"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Annálar tækisins skrá það sem gerist í tækinu. Forrit geta notað þessa annála til að finna og lagfæra vandamál.\n\nTilteknir annálar innihalda viðkvæmar upplýsingar og því skaltu einungis veita forritum sem þú treystir aðgang að öllum annálum tækisins. \n\nEf þú veitir þessu forriti ekki aðgang að öllum annálum tækisins hefur það áfram aðgang að eigin annálum. Framleiðandi tækisins getur þó hugsanlega opnað tiltekna annála eða upplýsingar í tækinu."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index df6c14f..4a893ed 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Consenti sempre da questo computer"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Consenti"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Debug USB non consentito"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"L\'utente che ha eseguito l\'accesso a questo dispositivo non può attivare il debug USB. Per utilizzare questa funzione, passa all\'utente principale."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"L\'utente che ha eseguito l\'accesso a questo dispositivo non può attivare il debug USB. Per usare questa funzionalità, passa a un utente amministratore."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Vuoi cambiare la lingua di sistema con la lingua <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Cambio della lingua di sistema richiesto da un altro dispositivo"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Cambia lingua"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Consenti sempre su questa rete"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Consenti"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Debug wireless non consentito"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"L\'utente che ha eseguito l\'accesso a questo dispositivo non può attivare il debug wireless. Per utilizzare questa funzionalità, passa all\'utente principale."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"L\'utente che ha eseguito l\'accesso a questo dispositivo non può attivare il debug wireless. Per usare questa funzionalità, passa a un utente amministratore."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Porta USB disattivata"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Per proteggere il dispositivo da liquidi o detriti, la porta USB è stata disattivata e non rileverà gli accessori.\n\nTi avviseremo quando sarà di nuovo possibile utilizzarla."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Porta USB attivata per rilevare caricabatterie e accessori"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Scanner codici QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Sblocca"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositivo bloccato"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Scansione del viso"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Invia"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Volto non riconosciuto"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa l\'impronta"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth collegato."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percentuale della batteria sconosciuta."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connesso a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Modalità aereo."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN attiva."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Batteria: <xliff:g id="NUMBER">%d</xliff:g> percento."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Livello della batteria: <xliff:g id="PERCENTAGE">%1$s</xliff:g> percento. Tempo rimanente in base al tuo utilizzo: <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Livello della batteria: <xliff:g id="PERCENTAGE">%1$d</xliff:g> percento. Tempo rimanente in base al tuo utilizzo: <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batteria in carica, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Visualizza tutte le notifiche"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Telescrivente abilitata."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Suoneria vibrazione."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gestisci utenti"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microfono disponibile"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Fotocamera disponibile"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Fotocamera e microfono disponibili"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microfono attivo"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microfono non attivo"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Il microfono è attivo per tutti i servizi e le app."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"L\'accesso al microfono è disattivato per tutti i servizi e le app. Puoi attivarlo in Impostazioni &gt; Privacy &gt; Microfono."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"L\'accesso al microfono è disattivato per tutti i servizi e le app. Puoi modificare questa preferenza in Impostazioni &gt; Privacy &gt; Microfono."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Fotocamera attiva"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Fotocamera non attiva"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"La fotocamera è attiva per tutti i servizi e le app."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"L\'accesso alla fotocamera è disattivato per tutti i servizi e le app."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Per usare il pulsante del microfono devi attivare l\'accesso al microfono nelle Impostazioni."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Apri le impostazioni"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Altro dispositivo"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Attiva/disattiva la panoramica"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Non verrai disturbato da suoni e vibrazioni, ad eccezione di sveglie, promemoria, eventi, chiamate da contatti da te specificati ed elementi che hai scelto di continuare a riprodurre, inclusi video, musica e giochi."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando condividi, registri o trasmetti un\'app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ha accesso a qualsiasi elemento visualizzato o riprodotto sull\'app. Presta quindi attenzione a password, dati di pagamento, messaggi o altre informazioni sensibili."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continua"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Condividi o registra un\'app"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Consentire all\'app di condividere o registrare?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Quando condividi, registri o trasmetti, l\'app ha accesso a qualsiasi elemento visibile sul tuo schermo o in riproduzione sul tuo dispositivo. Presta quindi attenzione a password, dati di pagamento, messaggi o altre informazioni sensibili."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Quando condividi, registri o trasmetti un\'app, questa app ha accesso a qualsiasi elemento visualizzato o riprodotto su quell\'app. Presta quindi attenzione a password, dati di pagamento, messaggi o altre informazioni sensibili."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloccata dall\'amministratore IT"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"L\'acquisizione schermo è disattivata dai criteri relativi ai dispositivi"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Cancella tutto"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gestisci"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Cronologia"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Sblocca per usare"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Si è verificato un problema durante il recupero delle tue carte. Riprova più tardi."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Impostazioni schermata di blocco"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scansiona codice QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Profilo di lavoro"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Modalità aereo"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Non sentirai la tua prossima sveglia <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vuoi passare nuovamente all\'operatore <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"I dati mobili non passeranno automaticamente all\'operatore in base alla disponibilità"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No, grazie"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sì, confermo"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ingrandisci l\'intero schermo"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ingrandisci parte dello schermo"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Opzione"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Consenti lo scorrimento diagonale"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ridimensiona"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Modifica il tipo di ingrandimento"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Elimina"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} scorciatoia rimossa}many{# scorciatoie rimosse}other{# scorciatoie rimosse}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Rimuovi"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Connessa temporaneamente"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Connessione debole"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM g"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Vuoi consentire all\'app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> di accedere a tutti i log del dispositivo?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Consenti accesso una tantum"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Non consentire"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"I log del dispositivo registrano tutto ciò che succede sul tuo dispositivo. Le app possono usare questi log per individuare problemi e correggerli.\n\nAlcuni log potrebbero contenere informazioni sensibili, quindi concedi l\'accesso a tutti i log del dispositivo soltanto alle app attendibili. \n\nSe le neghi l\'accesso a tutti i log del dispositivo, questa app può comunque accedere ai propri log. Il produttore del tuo dispositivo potrebbe essere comunque in grado di accedere ad alcuni log o informazioni sul dispositivo."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index f24964a..0822415 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"אפשר תמיד מהמחשב הזה"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"יש אישור"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"‏לא ניתן לבצע ניפוי באגים ב-USB"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"‏למשתמש המחובר לחשבון במכשיר הזה אין אפשרות להפעיל ניפוי באגים ב-USB. כדי להשתמש בתכונה הזו יש לעבור אל המשתמש הראשי."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"‏למשתמש המחובר לחשבון במכשיר הזה אין אפשרות להפעיל ניפוי באגים ב-USB. כדי להשתמש בתכונה הזו יש לעבור אל משתמש עם הרשאת אדמין."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"רוצה לשנות את שפת המערכת ל<xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"התקבלה בקשה ממכשיר אחר לשינוי שפת המערכת"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"שינוי שפה"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"לאשר תמיד ברשת הזו"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"אישור"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"אין הרשאה לניפוי באגים אלחוטי"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"למשתמש המחובר לחשבון במכשיר הזה אין אפשרות להפעיל ניפוי באגים אלחוטי. כדי להשתמש בתכונה הזו, יש לעבור אל המשתמש הראשי."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"למשתמש המחובר לחשבון במכשיר הזה אין אפשרות להפעיל ניפוי באגים אלחוטי. כדי להשתמש בתכונה הזו, יש לעבור אל משתמש עם הרשאת אדמין."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"‏יציאת ה-USB מושבתת"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"‏כדי להגן על המכשיר שלך מנוזלים או חלקיקים, יציאת ה-USB מושבתת ולא מזהה אביזרים כלל.\n\nתתקבל התראה כשניתן יהיה להשתמש ביציאת ה-USB."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"‏יציאת USB הופעלה לזיהוי מטענים ואביזרים"</string>
@@ -125,7 +125,7 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"האסיסטנט"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"ארנק"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"‏סורק קודי QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"ביטול נעילה"</string>
+    <string name="accessibility_unlock_button" msgid="3613812140816244310">"הנעילה מבוטלת"</string>
     <string name="accessibility_lock_icon" msgid="661492842417875775">"המכשיר נעול"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"סורק פנים"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"שליחה"</string>
@@ -168,6 +168,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"לא ניתן לזהות את הפנים"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"שימוש בטביעת אצבע במקום זאת"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"‏Bluetooth מחובר."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"אחוז טעינת הסוללה לא ידוע."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"התבצע חיבור אל <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +180,10 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"מצב טיסה"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"‏VPN פועל."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> אחוזים של סוללה."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"רמת הטעינה בסוללה: <xliff:g id="PERCENTAGE">%1$s</xliff:g> אחוזים, הזמן הנותר המשוער על סמך השימוש שלך: <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"רמת הטעינה בסוללה: <xliff:g id="PERCENTAGE">%1$d</xliff:g> אחוזים, הזמן הנותר המשוער על סמך השימוש שלך: <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"הסוללה בטעינה, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <string name="accessibility_battery_level_charging_paused" msgid="1716051308782906917">"<xliff:g id="PERCENTAGE">%d</xliff:g> אחוזים של סוללה. הטעינה בהשהיה כדי להגן על הסוללה."</string>
+    <string name="accessibility_battery_level_charging_paused_with_estimate" msgid="4006089349465741762">"רמת הטעינה בסוללה: <xliff:g id="PERCENTAGE">%1$d</xliff:g> אחוזים, הזמן הנותר המשוער על סמך השימוש שלך: <xliff:g id="TIME">%2$s</xliff:g>. הטעינה בהשהיה כדי להגן על הסוללה."</string>
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"הצגת כל ההתראות"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"‏TeleTypewriter מופעל"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"צלצול ורטט."</string>
@@ -248,7 +252,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>
@@ -303,6 +307,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"המיקרופון זמין"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"המצלמה זמינה"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"המיקרופון והמצלמה זמינים"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"המיקרופון פועל"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"המיקרופון כבוי"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"הגישה למיקרופון הופעלה לכל האפליקציות והשירותים."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"הגישה למיקרופון הושבתה לכל האפליקציות והשירותים. אפשר להפעיל את הגישה למיקרופון ב\'הגדרות\' &gt; \'פרטיות\' &gt; \'מיקרופון\'."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"הגישה למיקרופון הושבתה לכל האפליקציות והשירותים. אפשר לשנות את הגישה ב\'הגדרות\' &gt; \'פרטיות\' &gt; \'מיקרופון\'."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"המצלמה פועלת"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"המצלמה כבויה"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"הגישה למצלמה הופעלה לכל האפליקציות והשירותים."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"הגישה למצלמה הושבתה לכל האפליקציות והשירותים."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"כדי להשתמש בלחצן המיקרופון יש להפעיל את הגישה למיקרופון ב\'הגדרות\'."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"פתיחת ההגדרות."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"מכשיר אחר"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"החלפת מצב של מסכים אחרונים"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"כדי לא להפריע לך, המכשיר לא ירטוט ולא ישמיע שום צליל, חוץ מהתראות, תזכורות, אירועים ושיחות ממתקשרים מסוימים לבחירתך. המצב הזה לא ישפיע על צלילים שהם חלק מתוכן שבחרת להפעיל, כמו מוזיקה, סרטונים ומשחקים."</string>
@@ -373,6 +388,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"‏בזמן שיתוף, הקלטה או העברה (cast) של אפליקציה, תהיה ל-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. כדאי להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"המשך"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"שיתוף או הקלטה של אפליקציה"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"לאפשר לאפליקציה הזו לשתף או להקליט?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"‏בזמן שיתוף, הקלטה או העברה (cast) תהיה לאפליקציה הזו גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך. כדאי להיזהר ולא לחשוף סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"‏בזמן שיתוף, הקלטה או העברה (cast) של אפליקציה, תהיה לאפליקציה הזו גישה לכל מה שמוצג בה או מופעל ממנה. כדאי להיזהר ולא לחשוף סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"‏נחסם על ידי מנהל ה-IT"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"צילום המסך מושבת בגלל מדיניות המכשיר"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ניקוי הכול"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ניהול"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"היסטוריה"</string>
@@ -488,7 +508,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"יש לבטל את הנעילה כדי להשתמש"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"הייתה בעיה בקבלת הכרטיסים שלך. כדאי לנסות שוב מאוחר יותר"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"הגדרות מסך הנעילה"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"‏סריקת קוד QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"פרופיל עבודה"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"מצב טיסה"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"לא ניתן יהיה לשמוע את ההתראה הבאה שלך <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +748,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"לחזור אל <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"לא תתבצע החלפה אוטומטית של חבילת הגלישה על סמך זמינות"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"לא, תודה"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"כן, אני רוצה להחליף"</string>
     <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>
@@ -767,6 +792,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"הגדלה של המסך המלא"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"הגדלת חלק מהמסך"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"מעבר"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"הפעלת גלילה באלכסון"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"שינוי גודל"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"שינוי סוג ההגדלה"</string>
@@ -785,12 +812,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ביטול"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{קיצור הדרך אל {label} הוסר}two{# קיצורי דרך הוסרו}many{# קיצורי דרך הוסרו}other{# קיצורי דרך הוסרו}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"הסרה"</string>
     <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 +963,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"מחובר באופן זמני"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"חיבור באיכות ירודה"</string>
     <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>
@@ -992,4 +1024,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"‏יום EEE,‏ d בMMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"לתת לאפליקציה <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> הרשאת גישה לכל יומני המכשיר?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"הרשאת גישה חד-פעמית"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"אין אישור"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"ביומני המכשיר מתועדת הפעילות במכשיר. האפליקציות יכולות להשתמש ביומנים האלה כדי למצוא בעיות ולפתור אותן.\n\nהמידע בחלק מהיומנים יכול להיות רגיש, לכן יש לתת הרשאת גישה לכל היומנים של המכשיר רק לאפליקציות שסומכים עליהן. \n\nגם אם האפליקציה הזו לא תקבל הרשאת גישה לכל יומני המכשיר, היא תוכל לגשת ליומנים שלה. יכול להיות שליצרן המכשיר עדיין תהיה גישה לחלק מהיומנים או למידע במכשיר שלך."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 7ea7223..977d2da 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"このパソコンからの USB デバッグを常に許可する"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"許可"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB デバッグは許可されていません"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"このデバイスに現在ログインしているユーザーでは、USB デバッグを ON にすることはできません。この機能を使用するには、メインユーザーに切り替えてください。"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"このデバイスに現在ログインしているユーザーは USB デバッグを ON にできません。この機能を使用するには、管理者ユーザーに切り替えてください。"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"システム言語を<xliff:g id="LANGUAGE">%1$s</xliff:g>に変更しますか?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"他のデバイスからシステム言語の変更が要求されました"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"言語を変更"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"このネットワークで常に許可する"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"許可"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"ワイヤレス デバッグは許可されていません"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"このデバイスに現在ログインしているユーザーでは、ワイヤレス デバッグを ON にすることはできません。この機能を使用するには、メインユーザーに切り替えてください。"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"このデバイスに現在ログインしているユーザーはワイヤレス デバッグを ON にできません。この機能を使用するには、管理者ユーザーに切り替えてください。"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB ポート無効"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"液体やゴミからデバイスを保護するため、USB ポートは無効になっています。アクセサリの検出は行われません。\n\nUSB ポートを再び安全に使用できるようになりましたらお知らせします。"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB ポートが有効になり、充電器やアクセサリを検出できるようになりました"</string>
@@ -125,7 +125,7 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"音声アシスト"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"ウォレット"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR コードスキャナ"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"ロック解除"</string>
+    <string name="accessibility_unlock_button" msgid="3613812140816244310">"ロック解除済み"</string>
     <string name="accessibility_lock_icon" msgid="661492842417875775">"デバイスはロックされています"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"顔のスキャン"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"送信"</string>
@@ -168,6 +168,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"顔を認識できません"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"指紋認証をお使いください"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetoothに接続済み。"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"バッテリー残量は不明です。"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>に接続しました。"</string>
@@ -178,8 +180,10 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"機内モード。"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN は ON です。"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"バッテリー残量: <xliff:g id="NUMBER">%d</xliff:g>パーセント"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"バッテリー残量: <xliff:g id="PERCENTAGE">%1$s</xliff:g>、およそ <xliff:g id="TIME">%2$s</xliff:g> にバッテリー切れ(使用状況に基づく)"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"バッテリー残量: <xliff:g id="PERCENTAGE">%1$d</xliff:g>、およそ <xliff:g id="TIME">%2$s</xliff:g> にバッテリー切れ(使用状況に基づく)"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"電池充電中: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>パーセント"</string>
+    <string name="accessibility_battery_level_charging_paused" msgid="1716051308782906917">"バッテリー残量: <xliff:g id="PERCENTAGE">%d</xliff:g>%%。バッテリー保護のため、充電を一時停止しました。"</string>
+    <string name="accessibility_battery_level_charging_paused_with_estimate" msgid="4006089349465741762">"バッテリー残量: <xliff:g id="PERCENTAGE">%1$d</xliff:g>%%、およそ <xliff:g id="TIME">%2$s</xliff:g> にバッテリー切れ(使用状況に基づく)。バッテリー保護のため、充電を一時停止しました。"</string>
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"通知をすべて表示"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"テレタイプライターが有効です。"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"バイブレーション着信。"</string>
@@ -248,7 +252,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>
@@ -303,6 +307,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"マイクを利用できます"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"カメラを利用できます"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"マイクとカメラを利用できます"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"マイクを ON にしました"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"マイクを OFF にしました"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"マイクはすべてのアプリとサービスで有効になっています。"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"マイクへのアクセスは、すべてのアプリとサービスで無効になっています。[設定] &gt; [プライバシー] &gt; [マイク] で有効にできます。"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"マイクへのアクセスは、すべてのアプリとサービスで無効になっています。この設定は、[設定] &gt; [プライバシー] &gt; [マイク] で変更できます。"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"カメラを ON にしました"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"カメラを OFF にしました"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"カメラはすべてのアプリとサービスで有効になっています。"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"カメラへのアクセスは、すべてのアプリとサービスで無効になっています。"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"マイクボタンを使用するには、[設定] でマイクへのアクセスを有効にしてください。"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"設定を開く"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"その他のデバイス"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"概要を切り替え"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"アラーム、リマインダー、予定、指定した人からの着信以外の音やバイブレーションに煩わされることはありません。音楽、動画、ゲームなど再生対象として選択したコンテンツは引き続き再生されます。"</string>
@@ -373,6 +388,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"アプリの共有、録画、キャスト中は、そのアプリで表示されている内容や再生している内容に <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> がアクセスできるため、パスワード、お支払いの詳細、メッセージなどの機密情報にご注意ください。"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"続行"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"アプリの共有、録画"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"このアプリに共有や録画を許可しますか?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"共有、録画、キャスト中は、画面に表示されている内容やデバイスで再生している内容にこのアプリがアクセスできるため、パスワード、お支払いの詳細、メッセージなどの機密情報にご注意ください。"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"アプリの共有、録画、キャスト中は、そのアプリで表示されている内容や再生している内容にこのアプリがアクセスできるため、パスワード、お支払いの詳細、メッセージなどの機密情報にご注意ください。"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 管理者によりブロックされました"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"デバイス ポリシーに基づき、画面のキャプチャが無効になりました"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"すべて消去"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"履歴"</string>
@@ -433,7 +453,7 @@
     <string name="volume_odi_captions_content_description" msgid="4172765742046013630">"字幕のオーバーレイ"</string>
     <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"有効にする"</string>
     <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"無効にする"</string>
-    <string name="sound_settings" msgid="8874581353127418308">"着信音とバイブレーション"</string>
+    <string name="sound_settings" msgid="8874581353127418308">"サウンドとバイブレーション"</string>
     <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"設定"</string>
     <string name="screen_pinning_title" msgid="9058007390337841305">"アプリは固定されています"</string>
     <string name="screen_pinning_description" msgid="8699395373875667743">"固定を解除するまで画面が常に表示されるようになります。[戻る] と [最近] を同時に押し続けると固定が解除されます。"</string>
@@ -488,7 +508,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ロックを解除して使用"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"カードの取得中に問題が発生しました。しばらくしてからもう一度お試しください"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ロック画面の設定"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR コードのスキャン"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR コードスキャナ"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"仕事用プロファイル"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"機内モード"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"次回のアラーム(<xliff:g id="WHEN">%1$s</xliff:g>)は鳴りません"</string>
@@ -727,6 +747,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> に戻しますか?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"利用可能な場合でも、モバイルデータを利用するよう自動的に切り替わることはありません"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"キャンセル"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"切り替える"</string>
     <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>
@@ -767,6 +791,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"画面全体を拡大します"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"画面の一部を拡大します"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"スイッチ"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"斜めスクロールを許可"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"サイズ変更"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"拡大の種類を変更"</string>
@@ -785,12 +811,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"元に戻す"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} 個のショートカットを削除}other{# 個のショートカットを削除}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"削除"</string>
     <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 +962,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"一時的に接続されています"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"接続が不安定です"</string>
     <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>
@@ -992,4 +1023,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> にすべてのデバイスログへのアクセスを許可しますか?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"1 回限りのアクセスを許可"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"許可しない"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"デバイスのログに、このデバイスで発生したことが記録されます。アプリは問題を検出、修正するためにこれらのログを使用することができます。\n\nログによっては機密性の高い情報が含まれている可能性があるため、すべてのデバイスログへのアクセスは信頼できるアプリにのみ許可してください。\n\nすべてのデバイスログへのアクセスを許可しなかった場合も、このアプリはアプリ独自のログにアクセスできます。また、デバイスのメーカーもデバイスの一部のログや情報にアクセスできる可能性があります。"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 93c9d94..486639a 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"ყოველთვის დართე ნება ამ კომპიუტერიდან."</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"დაშვება"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB ხარვეზების გამართვა ნებადართული არაა"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"ამ მოწყობილობაზე ამჟამად შესულ მომხმარებელს არ შეუძლია USB ხარვეზების გამართვის ფუნქციის ჩართვა. ამ ფუნქციის გამოსაყენებლად, მიუერთდით მთავარ მომხმარებელს."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"ამ მოწყობილობაზე ამჟამად შესულ მომხმარებელს არ შეუძლია USB ხარვეზების გამართვის ფუნქციის ჩართვა. ამ ფუნქციის გამოსაყენებლად, მიუერთდით ადმინისტრატორ მომხმარებელს."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"გსურთ, სისტემის ენა შეიცვალოს და გახდეს <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"სისტემის ენის შეცვლა მოითხოვა სხვა მოწყობილობამ"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"ენის შეცვლა"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"ყოველთვის დაშვება ამ ქსელში"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"დაშვება"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"შეცდომების უსადენო გამართვა არ არის დაშვებული"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"ამ მოწყობილობაზე ამჟამად შესულ მომხმარებელს არ შეუძლია შეცდომების უსადენო გამართვის ჩართვა. ამ ფუნქციის გამოსაყენებლად, გადაერთეთ მთავარ მომხმარებელზე."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"ამ მოწყობილობაზე ამჟამად შესულ მომხმარებელს არ შეუძლია შეცდომების უსადენო გამართვის ჩართვა. ამ ფუნქციის გამოსაყენებლად, გადაერთეთ ადმინისტრატორ მომხმარებელზე."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB პორტი გათიშულია"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"თქვენი მოწყობილობის სითხის ან ნადებისგან დასაცავად, USB პორტი გათიშულია და ვერცერთი აქსესუარის აღმოჩენას ვერ შეძლებს.\n\nთქვენ მიიღებთ შეტყობინებას, როდესაც USB პორტის გამოყენება კვლავ შესაძლებელი იქნება."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB პორტი ჩართულია დამტენებისა და აქსესუარების აღმოსაჩენად"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ხმოვანი დახმარება"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"საფულე"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR კოდის სკანერი"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"განბლოკვა"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"მოწყობილობა ჩაკეტილია"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"მიმდინარეობს სახის სკანირება"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"გაგზავნა"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"სახის ამოცნობა შეუძლებ."</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"გამოიყენეთ თითის ანაბეჭდი"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth დაკავშირებულია."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ბატარეის პროცენტული მაჩვენებელი უცნობია."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"დაკავშირებულია <xliff:g id="BLUETOOTH">%s</xliff:g>-თან."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"თვითმფრინავის რეჟიმი"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ჩართულია."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"ბატარეა: <xliff:g id="NUMBER">%d</xliff:g> პროცენტი."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ბატარეა <xliff:g id="PERCENTAGE">%1$s</xliff:g> პროცენტზეა, მოხმარების გათვალისწინებით დარჩა დაახლოებით <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ბატარეა <xliff:g id="PERCENTAGE">%1$d</xliff:g> პროცენტზეა, მოხმარების გათვალისწინებით დარჩა დაახლოებით <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ბატარეა იტენება. ამჟამად არის <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> პროცენტი."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"ყველა შეტყობინების ნახვა"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"ტელეტაიპი ჩართულია."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ვიბრაციის რეჟიმი."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"მიკროფონი ხელმისაწვდომია"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"კამერა ხელმისაწვდომია"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"მიკროფონი და კამერა ხელმისაწვდომია"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"მიკროფონი ჩართულია"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"მიკროფონი გამორთულია"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"მიკროფონი ჩართულია ყველა აპისა და სერვისისთვის."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"მიკროფონზე წვდომა გათიშულია ყველა აპისა და სერვისისთვის. მიკროფონზე წვდომის ჩართვა შეგიძლიათ აქედან: პარამეტრები &gt; კონფიდენციალურობა &gt; მიკროფონი."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"მიკროფონზე წვდომა გათიშულია ყველა აპისა და სერვისისთვის. ამის შეცვლა შეგიძლიათ აქედან: პარამეტრები &gt; კონფიდენციალურობა &gt; მიკროფონი."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"კამერა ჩაირთო"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"კამერა გამოირთო"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"კამერა ჩართულია ყველა აპისა და სერვისისთვის."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"კამერაზე წვდომა გათიშულია ყველა აპისა და სერვისისთვის."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"მიკროფონის ღილაკის გამოსაყენებლად, ჩართეთ მიკროფონზე წვდომა პარამეტრებიდან."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"პარამეტრების გახსნა."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"სხვა მოწყობილობა"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"მიმოხილვის გადართვა"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"თქვენ მიერ მითითებული მაღვიძარების, შეხსენებების, მოვლენებისა და ზარების გარდა, არავითარი ხმა და ვიბრაცია არ შეგაწუხებთ. თქვენ მაინც შეძლებთ სასურველი კონტენტის, მაგალითად, მუსიკის, ვიდეოებისა და თამაშების აუდიოს მოსმენა."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"აპის გაზიარებისას, ჩაწერისას ან ტრანსლირებისას <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> აქვს წვდომა აქვს ყველაფერზე, რაც ჩანს აპში ან ითამაშეთ. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა მგრძნობიარე ინფორმაციასთან."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"გაგრძელება"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"გააზიარეთ ან ჩაწერეთ აპი"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"გსურთ ამ აპისთვის გაზიარების ან ჩაწერის უფლების მიცემა?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"როდესაც თქვენ აზიარებთ, იწერთ ან ტრანსლირებთ, ამ აპს აქვს წვდომა ყველაფერზე, რაც ჩანს თქვენს ეკრანზე ან უკრავს თქვენს მოწყობილობაზე. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა სენსიტიურ ინფორმაციასთან."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"როდესაც თქვენ აზიარებთ, იწერთ ან ტრანსლირებთ, ამ აპს აქვს წვდომა ყველაფერზე, რაც ჩანს თქვენს ეკრანზე ან უკრავს თქვენს მოწყობილობაზე. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა სენსიტიურ ინფორმაციასთან."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"დაბლოკილია თქვენი IT-ადმინისტრატორის მიერ"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ეკრანის აღბეჭდვა გამორთულია მოწყობილობის წესების თანახმად"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ყველას გასუფთავება"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"მართვა"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ისტორია"</string>
@@ -488,7 +511,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"გამოსაყენებლად განბლოკვა"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"თქვენი ბარათების მიღებისას პრობლემა წარმოიშვა. ცადეთ ხელახლა მოგვიანებით"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ჩაკეტილი ეკრანის პარამეტრები"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR კოდის სკანირება"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR კოდის სკანერი"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"სამსახურის პროფილი"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"თვითმფრინავის რეჟიმი"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"ვერ გაიგონებთ მომდევნო მაღვიძარას <xliff:g id="WHEN">%1$s</xliff:g>-ზე"</string>
@@ -727,6 +750,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"გსურთ ისევ <xliff:g id="CARRIER">%s</xliff:g>-ზე გადართვა?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"მობილური მონაცემების ხელმისაწვდომობის მიხედვით ავტომატური გადართვა არ მოხდება"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"არა, გმადლობთ"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"დიახ, გადაირთოს"</string>
     <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>
@@ -767,6 +794,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"გაადიდეთ სრულ ეკრანზე"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ეკრანის ნაწილის გადიდება"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"გადართვა"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"დიაგონალური გადახვევის დაშვება"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ზომის შეცვლა"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"გადიდების ტიპის შეცვლა"</string>
@@ -785,12 +814,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"მოქმედების გაუქმება"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} მალსახმობი ამოშლილია}other{# მალსახმობი ამოშლილია}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"წაშლა"</string>
     <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 +965,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"დროებით დაკავშირებული"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"სუსტი კავშირი"</string>
     <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>
@@ -992,4 +1026,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"დდდ, თთთ თ"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"სთ:წთ"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"სთ:წთ"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"გსურთ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>-ს მიანიჭოთ მოწყობილობის ყველა ჟურნალზე წვდომა?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"ერთჯერადი წვდომის დაშვება"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"არ დაიშვას"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"მოწყობილობის ჟურნალში იწერება, რა ხდება ამ მოწყობილობაზე. აპებს შეუძლია ამ ჟურნალების გამოყენება პრობლემების აღმოსაჩენად და მოსაგვარებლად.\n\nზოგი ჟურნალი შეიძლება სენსიტიური ინფორმაციის მატარებელი იყოს, ამიტომაც მოწყობილობის ყველა ჟურნალზე წვდომა მხოლოდ სანდო აპებს მიანიჭეთ. \n\nთუ ამ აპს მოწყობილობის ყველა ჟურნალზე წვდომას არ მიანიჭებთ, მას მაინც ექნება წვდომა თქვენს ჟურნალებზე. თქვენი მოწყობილობის მწარმოებელს მაინც შეეძლება თქვენი მოწყობილობის ზოგიერთ ჟურნალსა თუ ინფორმაციაზე წვდომა."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index e096b7e..dc57eee 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Осы компьютерден әрқашан рұқсат беру"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Рұқсат беру"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB арқылы түзетуге рұқсат етілмеген"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Бұл құрылғыға қазір кірген пайдаланушы USB арқылы түзетуді іске қосылмайды. Бұл мүмкіндікті пайдалану үшін негізгі пайдаланушыға ауысыңыз."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Бұл құрылғыға жаңа кірген пайдаланушы USB арқылы түзетуді қоса алмайды. Бұл мүмкіндікті пайдалану үшін әкімші пайдаланушыға ауысыңыз."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Жүйе тілін басқа тілге (<xliff:g id="LANGUAGE">%1$s</xliff:g>) ауыстырғыңыз келе ме?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Басқа құрылғыдан жүйе тілін өзгерту туралы сұрау жіберілді."</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Тілді өзгерту"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Осы желіде үнемі рұқсат ету"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Рұқсат ету"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Сымсыз түзетуге рұқсат етілмейді"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Бұл құрылғыға жаңа кірген пайдаланушы сымсыз түзетуді іске қоса алмайды. Бұл мүмкіндікті пайдалану үшін негізгі пайдаланушыға ауысыңыз."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Бұл құрылғыға жаңа кірген пайдаланушы сымсыз түзетуді іске қоса алмайды. Бұл мүмкіндікті пайдалану үшін әкімші пайдаланушыға ауысыңыз."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB порты өшірілді"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Құрылғыңызға сұйықтық немесе қоқыс кіріп кетпеуі үшін, USB порты өшірілген және ешқандай керек-жарақты анықтамайды.\n\nUSB портын қайта пайдалануға болатын кезде хабарландыру аласыз."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Зарядтағыштар мен аксессуарларды анықтау үшін USB порты қосылды."</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Дауыс көмекшісі"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR кодын сканерлеу қолданбасы"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Бекітпесін ашу"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Құрылғы құлыпталды."</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Бетті сканерлеу"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Жіберу"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Бет танылмады."</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Орнына саусақ ізін пайдаланыңыз."</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth қосылған."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарея зарядының мөлшері белгісіз."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> қосылған."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Ұшақ режимі."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN қосулы."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Батарея <xliff:g id="NUMBER">%d</xliff:g> пайыз."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батарея заряды: <xliff:g id="PERCENTAGE">%1$s</xliff:g> пайыз. Пайдалануға байланысты шамамен <xliff:g id="TIME">%2$s</xliff:g> уақытқа жетеді."</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батарея заряды: <xliff:g id="PERCENTAGE">%1$d</xliff:g> пайыз. Пайдалануға байланысты шамамен <xliff:g id="TIME">%2$s</xliff:g> уақытқа жетеді."</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Батарея зарядталуда, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Барлық хабарландыруды қарау"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Телетайп қосылған."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Қоңырау тербелісі."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Микрофон қолжетімді"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камера қолжетімді"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Микрофон мен камера қолжетімді"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Микрофон қосулы"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Микрофон өшірулі"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Микрофон барлық қолданба мен қызмет үшін қосулы."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Микрофон пайдалану рұқсаты барлық қолданба мен қызмет үшін өшірулі. Микрофон пайдалану рұқсатын \"Параметрлер&gt; Құпиялылық &gt; Микрофон\" тармағынан қоса аласыз."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Микрофон пайдалану рұқсаты барлық қолданба мен қызмет үшін өшірулі. Мұны \"Параметрлер &gt; Құпиялылық &gt; Микрофон\" тармағынан өзгерте аласыз."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камера қосулы"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камера өшірулі"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Камера барлық қолданба мен қызмет үшін қосулы."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Камера пайдалану рұқсаты барлық қолданба мен қызмет үшін өшірулі."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Микрофон түймесін пайдалану үшін параметрлерден микрофон пайдалану рұқсатын қосыңыз."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Параметрлерді ашу."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Басқа құрылғы"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Шолуды қосу/өшіру"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Оятқыш, еске салғыш, іс-шара мен өзіңіз көрсеткен контактілердің қоңырауларынан басқа дыбыстар мен дірілдер мазаламайтын болады. Музыка, бейне және ойын сияқты медиафайлдарды қоссаңыз, оларды естисіз."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Қолданба экранын бөлісу, жазу не трансляциялау кезінде <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасы онда көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Жалғастыру"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Қолданба экранын бөлісу не жазу"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Қолданбаға бөлісуге не жазып алуға рұқсат берілсін бе?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Экранды бөлісу, жазып алу не трансляциялау кезінде бұл қолданба экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Қолданбаны бөлісу, жазып алу не трансляциялау кезінде бұл қолданба онда көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Әкімшіңіз бөгеген"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Құрылғы саясатына байланысты экранды түсіру өшірілді."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Барлығын тазалау"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Басқару"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Тарих"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Пайдалану үшін құлыпты ашу"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Карталарыңыз алынбады, кейінірек қайталап көріңіз."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Экран құлпының параметрлері"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR кодын сканерлеу"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Жұмыс профилі"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Ұшақ режимі"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Келесі <xliff:g id="WHEN">%1$s</xliff:g> дабылыңызды есітпейсіз"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> операторына қайта ауысу керек пе?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобильдік интернет операторды қолдану мүмкіндігіне қарай автоматты түрде ауыспайды."</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Жоқ, рақмет"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Иә, ауыстырылсын"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Толық экранды ұлғайту"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Экранның бөлігін ұлғайту"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Ауысу"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Диагональ бойынша айналдыруға рұқсат беру"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Өлшемін өзгерту"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Ұлғайту түрін өзгерту"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Қайтару"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} таңбаша өшірілді.}other{# таңбаша өшірілді.}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Өшіру"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Уақытша байланыс орнатылды."</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Байланыс нашар."</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"d MMM EEEE"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> қолданбасына құрылғының барлық журналын пайдалануға рұқсат берілсін бе?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Бір рет рұқсат беру"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Рұқсат бермеу"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Журналдарға құрылғыда не болып жатқаны жазылады. Қолданбалар бұл журналдарды қате тауып, түзету үшін пайдаланады.\n\nКейбір журналдарда құпия ақпарат болуы мүмкін. Сондықтан құрылғының барлық журналын пайдалану рұқсаты тек сенімді қолданбаларға берілуі керек. \n\nБұл қолданбаға құрылғының барлық журналын пайдалануға рұқсат бермесеңіз де, ол өзінің журналдарын пайдалана береді. Құрылғы өндірушісі де құрылғыдағы кейбір журналдарды немесе ақпаратты пайдалануы мүмкін."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index aa641e4..11fd052 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"អនុញ្ញាត​ជា​និច្ច​សម្រាប់​កុំព្យូទ័រ​នេះ"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"អនុញ្ញាត"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"មិនអនុញ្ញាតការកែកំហុសតាម USB ទេ"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"អ្នកប្រើ​ដែលបច្ចុប្បន្ន​បានចូលគណនី​នៅលើឧបករណ៍នេះ​មិនអាចបើកការកែកំហុស USB បានទេ។ ដើម្បីប្រើមុខងារនេះ សូមប្តូរទៅអ្នកប្រើចម្បង។"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"អ្នកប្រើ​ប្រាស់ដែលបច្ចុប្បន្ន​បានចូលគណនី​នៅលើឧបករណ៍នេះ​មិនអាចបើកការ​ជួសជុលតាម USB បានទេ។ ដើម្បីប្រើប្រាស់មុខងារនេះ សូមប្ដូរទៅអ្នកប្រើប្រាស់​ដែលជាអ្នកគ្រប់គ្រង។"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"តើអ្នកចង់ប្ដូរ​ភាសាប្រព័ន្ធ​ទៅភាសា​<xliff:g id="LANGUAGE">%1$s</xliff:g>​ដែរទេ?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"ការប្ដូរភាសា​ប្រព័ន្ធដែលបានស្នើសុំ​ដោយ​ឧបករណ៍ផ្សេងទៀត"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"​ប្ដូរ​ភាសា"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"អនុញ្ញាត​នៅលើ​បណ្ដាញ​នេះ​ជានិច្ច"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"អនុញ្ញាត"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"មិន​អនុញ្ញាត​ការជួសជុល​ដោយឥតខ្សែ​ទេ"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"អ្នក​ប្រើប្រាស់​ដែលបច្ចុប្បន្ន​បាន​ចូលគណនី​នៅលើ​ឧបករណ៍​នេះ​មិនអាច​បើក​ការជួសជុល​ដោយឥតខ្សែ​បានទេ។ ដើម្បី​ប្រើ​មុខងារ​នេះ សូម​ប្ដូរ​ទៅ​អ្នក​ប្រើប្រាស់​ចម្បង។"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"អ្នក​ប្រើប្រាស់​ដែលបច្ចុប្បន្ន​បាន​ចូលគណនី​នៅលើ​ឧបករណ៍​នេះ​មិនអាច​បើក​ការជួសជុល​ដោយឥតខ្សែ​បានទេ។ ដើម្បី​ប្រើប្រាស់​មុខងារ​នេះ សូម​ប្ដូរ​ទៅ​អ្នកប្រើប្រាស់​ដែលជាអ្នកគ្រប់គ្រង។"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"បានបិទរន្ធ USB"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"ដើម្បី​ការពារ​ឧបករណ៍​របស់អ្នកកុំឱ្យ​ចូលទឹក ឬ​កម្ទេចផ្សេងៗ រន្ធ USB ត្រូវបានបិទ ហើយ​នឹង​មិនស្គាល់​គ្រឿង​បរិក្ខារ​នោះទេ។\n\nអ្នកនឹង​ទទួលបាន​ការជូនដំណឺង នៅពេល​អ្នកអាច​ប្រើប្រាស់​រន្ធ USB ម្ដងទៀត។"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"បាន​បើក​រន្ធ USB ដើម្បី​សម្គាល់​ឆ្នាំងសាក និងគ្រឿងផ្សេងៗ"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ជំនួយសំឡេង"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"កម្មវិធីស្កេនកូដ QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"ដោះ​​សោ"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"បានចាក់សោ​ឧបករណ៍"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"ការ​ស្កេន​មុខ"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ផ្ញើ"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"មិនអាចសម្គាល់មុខបានទេ"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ប្រើស្នាមម្រាមដៃជំនួសវិញ​"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"បាន​តភ្ជាប់​ប៊្លូធូស។"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"មិនដឹងអំពី​ភាគរយថ្មទេ។"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"បាន​ភ្ជាប់​ទៅ <xliff:g id="BLUETOOTH">%s</xliff:g> ។"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"ពេល​ជិះ​យន្តហោះ"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"បើក VPN ។"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"ថ្ម <xliff:g id="NUMBER">%d</xliff:g> ភាគរយ។"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ថ្ម <xliff:g id="PERCENTAGE">%1$s</xliff:g> ភាគរយ អាចប្រើបាន​ប្រហែល <xliff:g id="TIME">%2$s</xliff:g> ទៀត ផ្អែក​លើការ​ប្រើប្រាស់​របស់អ្នក"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ថ្ម <xliff:g id="PERCENTAGE">%1$d</xliff:g> ភាគរយ អាចប្រើបាន​ប្រហែល <xliff:g id="TIME">%2$s</xliff:g> ទៀត ផ្អែក​លើការ​ប្រើប្រាស់​របស់អ្នក"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"កំពុងសាកថ្ម <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> ភាគរយ"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"មើល​ការជូនដំណឹង​ទាំងអស់"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"បាន​បើក​ម៉ាស៊ីន​អង្គុលីលេខ​"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"កម្មវិធី​រោទ៍​ញ័រ។"</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"អាចប្រើមីក្រូហ្វូនបាន"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"អាចប្រើកាមេរ៉ាបាន"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"អាចប្រើកាមេរ៉ា និងមីក្រូហ្វូនបាន"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"បានបើក​មីក្រូហ្វូន"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"បានបិទ​មីក្រូហ្វូន"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"មីក្រូហ្វូនត្រូវបានបើកសម្រាប់កម្មវិធី និងសេវាកម្មទាំងអស់។"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"សិទ្ធិចូលប្រើប្រាស់មីក្រូហ្វូនត្រូវបានបិទសម្រាប់កម្មវិធី និងសេវាកម្មទាំងអស់។ អ្នកអាចបើកសិទ្ធិចូលប្រើប្រាស់មីក្រូហ្វូននៅក្នុងការកំណត់ &gt; ឯកជនភាព &gt; មីក្រូហ្វូន។"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"សិទ្ធិចូលប្រើប្រាស់មីក្រូហ្វូនត្រូវបានបិទសម្រាប់កម្មវិធី និងសេវាកម្មទាំងអស់។ អ្នកអាចផ្លាស់ប្ដូរលក្ខណៈនេះនៅក្នុងការកំណត់ &gt; ឯកជនភាព &gt; មីក្រូហ្វូន។"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"បាន​បើក​កាមេរ៉ា"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"បានបិទ​កាមេរ៉ា"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"កាមេរ៉ាត្រូវបានបើកសម្រាប់កម្មវិធី និងសេវាកម្មទាំងអស់។"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"សិទ្ធិចូលប្រើប្រាស់កាមេរ៉ាត្រូវបានបិទសម្រាប់កម្មវិធី និងសេវាកម្មទាំងអស់។"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"ដើម្បីប្រើប្រាស់ប៊ូតុងមីក្រូហ្វូន សូមបើកសិទ្ធិចូលប្រើប្រាស់មីក្រូហ្វូននៅក្នុងការកំណត់។"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"បើកការកំណត់។"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"ឧបករណ៍ផ្សេងទៀត"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"បិទ/បើក​ទិដ្ឋភាពរួម"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"សំឡេង និងរំញ័រនឹងមិន​រំខានដល់អ្នកឡើយ លើកលែងតែម៉ោងរោទ៍ ការរំលឹក ព្រឹត្តិការណ៍ និងអ្នកហៅទូរសព្ទដែលអ្នកបញ្ជាក់ប៉ុណ្ណោះ។ អ្នកនឹងនៅតែឮសំឡេងសកម្មភាពគ្រប់យ៉ាងដែលអ្នកលេងដដែល រួមទាំងតន្រ្តី វីដេអូ និងហ្គេម។"</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬបញ្ជូនកម្មវិធី <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> មានសិទ្ធិចូលប្រើប្រាស់អ្វីៗដែលបង្ហាញ ឬលេងនៅលើកម្មវិធីនោះ។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះពាក្យសម្ងាត់ ព័ត៌មាន​លម្អិតអំពី​ការ​ទូទាត់ប្រាក់ សារ ឬព័ត៌មានរសើបផ្សេងទៀត។"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"បន្ត"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ចែករំលែក ឬថតកម្មវិធី"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"អនុញ្ញាតកម្មវិធីនេះឱ្យចែករំលែក ឬថតឬ?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"នៅពេលអ្នក​កំពុងចែករំលែក ថត ឬបញ្ជូន កម្មវិធីនេះ​មានសិទ្ធិចូលប្រើប្រាស់អ្វីៗ​ដែលបង្ហាញឱ្យឃើញនៅលើ​អេក្រង់របស់អ្នក ឬលេងនៅលើឧបករណ៍របស់អ្នក។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះពាក្យសម្ងាត់ ព័ត៌មាន​លម្អិតអំពី​ការ​ទូទាត់ប្រាក់ សារ ឬព័ត៌មានរសើបផ្សេងទៀត។"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"នៅពេលអ្នក​កំពុងចែករំលែក ថត ឬបញ្ជូន​កម្មវិធី កម្មវិធីនេះ​មានសិទ្ធិចូលប្រើប្រាស់អ្វីៗ​ដែលបង្ហាញ ឬលេងនៅលើ​កម្មវិធីនោះ។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះ​ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ ឬព័ត៌មានរសើប​ផ្សេងទៀត។"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"បានទប់ស្កាត់ដោយអ្នកគ្រប់គ្រង​ផ្នែកព័ត៌មានវិទ្យា​របស់អ្នក"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ការថតអេក្រង់ត្រូវបានបិទ​ដោយគោលការណ៍ឧបករណ៍"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"សម្អាត​ទាំងអស់"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"គ្រប់គ្រង"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ប្រវត្តិ"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ដោះសោដើម្បីប្រើប្រាស់"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"មានបញ្ហា​ក្នុងការទាញយក​កាត​របស់អ្នក សូម​ព្យាយាមម្ដងទៀត​នៅពេលក្រោយ"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ការកំណត់អេក្រង់ចាក់សោ"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"ស្កេន​កូដ QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"ប្រវត្តិរូបការងារ"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"ពេលជិះយន្តហោះ"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"អ្នកនឹងមិនលឺម៉ោងរោទ៍ <xliff:g id="WHEN">%1$s</xliff:g> បន្ទាប់របស់អ្នកទេ"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"ប្ដូរទៅ <xliff:g id="CARRIER">%s</xliff:g> វិញឬ?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ទិន្នន័យទូរសព្ទចល័តនឹងមិនប្ដូរដោយស្វ័យប្រវត្តិដោយផ្អែកតាមភាពអាចប្រើបាននោះទេ"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ទេ អរគុណ"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"បាទ/ចាស ប្ដូរ"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ពង្រីក​ពេញអេក្រង់"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ពង្រីក​ផ្នែកនៃ​អេក្រង់"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ប៊ូតុងបិទបើក"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"អនុញ្ញាត​ការរំកិលបញ្ឆិត"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ប្ដូរ​ទំហំ"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"ប្ដូរ​ប្រភេទ​ការពង្រីក"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ត្រឡប់វិញ"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{បានដក​ផ្លូវកាត់ {label} ចេញ}other{បាន​ដក​ផ្លូវ​កាត់ # ចេញ}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"ដកចេញ"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"បានភ្ជាប់ជាបណ្ដោះអាសន្ន"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"ការតភ្ជាប់​ខ្សោយ"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"អនុញ្ញាតឱ្យ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់ឬ?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"អនុញ្ញាតឱ្យចូលប្រើ​ប្រាស់តែមួយលើក"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"មិនអនុញ្ញាត"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"កំណត់ហេតុឧបករណ៍កត់ត្រាអ្វីដែលកើតឡើងនៅលើឧបករណ៍របស់អ្នក។ កម្មវិធីអាចប្រើកំណត់ហេតុទាំងនេះ ដើម្បីស្វែងរក និងដោះស្រាយបញ្ហាបាន។\n\nកំណត់ហេតុមួយចំនួនអាចមានព័ត៌មានរសើប ដូច្នេះសូមអនុញ្ញាតឱ្យចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់សម្រាប់តែកម្មវិធីដែលអ្នកទុកចិត្តប៉ុណ្ណោះ។ \n\nប្រសិនបើអ្នកមិនអនុញ្ញាតឱ្យកម្មវិធីនេះចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់ទេ កម្មវិធីនេះនៅតែអាចចូលប្រើកំណត់ហេតុរបស់ខ្លួនផ្ទាល់បាន។ ក្រុមហ៊ុន​ផលិត​ឧបករណ៍របស់អ្នក​ប្រហែលជា​នៅតែអាចចូលប្រើ​កំណត់ហេតុ ឬព័ត៌មានមួយចំនួន​នៅលើឧបករណ៍​របស់អ្នក​បានដដែល។"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index ab3f379..51944a9 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"ಈ ಕಂಪ್ಯೂಟರ್‌ನಿಂದ ಯಾವಾಗಲೂ ಅನುಮತಿಸಿ"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"ಅನುಮತಿಸಿ"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB ಡೀಬಗ್ ಮಾಡುವಿಕೆಯನ್ನು ಅನುಮತಿಸಲಾಗಿಲ್ಲ"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"ಬಳಕೆದಾರರು ಪ್ರಸ್ತುತ ಈ ಸಾಧನಕ್ಕೆ ಸೈನ್ ಇನ್ ಮಾಡಿದ್ದಾರೆ USB ಡೀಬಗ್ ಮಾಡುವುದನ್ನು ಆನ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಈ ವೈಶಿಷ್ಟ್ಯವನ್ನು ಬಳಸಲು, ಪ್ರಾಥಮಿಕ ಬಳಕೆದಾರರಿಗೆ ಬದಲಾಯಿಸಿ."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"ಬಳಕೆದಾರರು ಪ್ರಸ್ತುತ ಈ ಸಾಧನಕ್ಕೆ ಸೈನ್ ಇನ್ ಮಾಡಿದ್ದಾರೆ USB ಡೀಬಗ್ ಮಾಡುವುದನ್ನು ಆನ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಈ ಫೀಚರ್ ಅನ್ನು ಬಳಸಲು, ನಿರ್ವಾಹಕ ಬಳಕೆದಾರರಿಗೆ ಬದಲಾಯಿಸಿ."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"ಸಿಸ್ಟಮ್ ಭಾಷೆಯನ್ನು <xliff:g id="LANGUAGE">%1$s</xliff:g> ಭಾಷೆಗೆ ಬದಲಾಯಿಸಲು ನೀವು ಬಯಸುತ್ತೀರಾ?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"ಬೇರೊಂದು ಸಾಧನದಿಂದ ಸಿಸ್ಟಮ್ ಭಾಷೆಯ ಬದಲಾವಣೆಯನ್ನು ವಿನಂತಿಸಲಾಗಿದೆ"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"ಭಾಷೆಯನ್ನು ಬದಲಾಯಿಸಿ"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"ಈ ನೆಟ್‌ವರ್ಕ್‌ನಲ್ಲಿ ಅನುಮತಿಸಿ"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"ಅನುಮತಿಸಿ"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"ವೈರ್‌ಲೆಸ್ ಡೀಬಗ್ ಮಾಡುವಿಕೆಯನ್ನು ಅನುಮತಿಸಲಾಗಿಲ್ಲ"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"ಬಳಕೆದಾರರು ಪ್ರಸ್ತುತ ಈ ಸಾಧನಕ್ಕೆ ಸೈನ್ ಇನ್ ಮಾಡಿದ್ದಾರೆ ವೈರ್‌ಲೆಸ್ ಡೀಬಗ್ ಮಾಡುವುದನ್ನು ಆನ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಈ ವೈಶಿಷ್ಟ್ಯವನ್ನು ಬಳಸಲು, ಪ್ರಾಥಮಿಕ ಬಳಕೆದಾರರಿಗೆ ಬದಲಾಯಿಸಿ."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"ಬಳಕೆದಾರರು ಪ್ರಸ್ತುತ ಈ ಸಾಧನಕ್ಕೆ ಸೈನ್ ಇನ್ ಮಾಡಿದ್ದಾರೆ ವೈರ್‌ಲೆಸ್ ಡೀಬಗ್ ಮಾಡುವುದನ್ನು ಆನ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಈ ಫೀಚರ್ ಅನ್ನು ಬಳಸಲು, ನಿರ್ವಾಹಕ ಬಳಕೆದಾರರಿಗೆ ಬದಲಾಯಿಸಿ."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB ಪೋರ್ಟ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"ದ್ರವ ಅಥವಾ ಧೂಳಿನ ಕಣಗಳಿಂದ ನಿಮ್ಮ ಸಾಧನವನ್ನು ರಕ್ಷಿಸಲು, USB ಪೋರ್ಟ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ ಹಾಗಾಗಿ ಅದು ಯಾವುದೇ ಪರಿಕರಗಳನ್ನು ಪತ್ತೆ ಮಾಡುವುದಿಲ್ಲ. \n\n USB ಪೋರ್ಟ್ ಬಳಸಲು ಸುರಕ್ಷಿತವಾಗಿದ್ದಾಗ ಮತ್ತೆ ನಿಮಗೆ ಸೂಚಿಸಲಾಗುವುದು."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"ಚಾರ್ಜರ್‌ಗಳು ಮತ್ತು ಪರಿಕರಗಳನ್ನು ಪತ್ತೆಹಚ್ಚಲು USB ಪೋರ್ಟ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ಧ್ವನಿ ಸಹಾಯಕ"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"ವಾಲೆಟ್"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR ಕೋಡ್ ಸ್ಕ್ಯಾನರ್"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"ಅನ್‌ಲಾಕ್"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"ಸಾಧನ ಲಾಕ್ ಆಗಿದೆ"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"ಮುಖವನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ಕಳುಹಿಸಿ"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"ಮುಖ ಗುರುತಿಸಲಾಗುತ್ತಿಲ್ಲ"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ಬದಲಿಗೆ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಬಳಸಿ"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ಬ್ಲೂಟೂತ್‌‌ ಸಂಪರ್ಕಗೊಂಡಿದೆ."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ಬ್ಯಾಟರಿ ಶೇಕಡಾವಾರು ತಿಳಿದಿಲ್ಲ."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ಗೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆ."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"ಏರೋಪ್ಲೇನ್‌ ಮೋಡ್‌"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"ನಲ್ಲಿ VPN"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"ಬ್ಯಾಟರಿ <xliff:g id="NUMBER">%d</xliff:g> ಪ್ರತಿಶತ."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ನಿಮ್ಮ ಬಳಕೆಯ ಆಧಾರದ ಮೇಲೆ ಬ್ಯಾಟರಿಯು ಪ್ರತಿಶತ <xliff:g id="PERCENTAGE">%1$s</xliff:g> ರಷ್ಟು ಮತ್ತು <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ನಿಮ್ಮ ಬಳಕೆಯ ಆಧಾರದ ಮೇಲೆ ಬ್ಯಾಟರಿಯು ಪ್ರತಿಶತ <xliff:g id="PERCENTAGE">%1$d</xliff:g> ರಷ್ಟು ಮತ್ತು <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ಬ್ಯಾಟರಿ <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> ಪ್ರತಿಶತ ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"ಎಲ್ಲಾ ಅಧಿಸೂಚನೆಗಳನ್ನು ನೋಡಿ"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"ಟೆಲಿಟೈಪ್‌ರೈಟರ್ ಸಕ್ರಿಯವಾಗಿದೆ."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ರಿಂಗರ್ ವೈಬ್ರೇಟ್‌."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"ಮೈಕ್ರೊಫೋನ್ ಲಭ್ಯವಿದೆ"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ಕ್ಯಾಮರಾ ಲಭ್ಯವಿದೆ"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"ಮೈಕ್ರೊಫೋನ್ ಮತ್ತು ಕ್ಯಾಮರಾ ಲಭ್ಯವಿದೆ"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ಆನ್ ಮಾಡಲಾಗಿದೆ"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ಆಫ್ ಮಾಡಲಾಗಿದೆ"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು ಹಾಗೂ ಸೇವೆಗಳಿಗಾಗಿ ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು ಹಾಗೂ ಸೇವೆಗಳಿಗಾಗಿ ಮೈಕ್ರೊಫೋನ್ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ. ಸೆಟ್ಟಿಂಗ್‌ಗಳು &gt; ಗೌಪ್ಯತೆ &gt; ಮೈಕ್ರೊಫೋನ್ ಎಂಬಲ್ಲಿಗೆ ಹೋಗುವ ಮೂಲಕ ನೀವು ಮೈಕ್ರೊಫೋನ್ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಬಹುದು."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು ಹಾಗೂ ಸೇವೆಗಳಿಗಾಗಿ ಮೈಕ್ರೊಫೋನ್ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ. ಸೆಟ್ಟಿಂಗ್‌ಗಳು &gt; ಗೌಪ್ಯತೆ &gt; ಮೈಕ್ರೊಫೋನ್ ಎಂಬಲ್ಲಿಗೆ ಹೋಗುವ ಮೂಲಕ ನೀವು ಇದನ್ನು ಬದಲಾಯಿಸಬಹುದು."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"ಕ್ಯಾಮರಾವನ್ನು ಆನ್ ಮಾಡಲಾಗಿದೆ"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ಕ್ಯಾಮರಾವನ್ನು ಆಫ್ ಮಾಡಲಾಗಿದೆ"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು ಹಾಗೂ ಸೇವೆಗಳಿಗಾಗಿ ಕ್ಯಾಮರಾವನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು ಹಾಗೂ ಸೇವೆಗಳಿಗಾಗಿ ಕ್ಯಾಮರಾ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"ಮೈಕ್ರೊಫೋನ್ ಬಟನ್ ಅನ್ನು ಬಳಸಲು, ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಮೈಕ್ರೊಫೋನ್ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಿರಿ."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"ಅನ್ಯ ಸಾಧನ"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ಟಾಗಲ್ ನ ಅವಲೋಕನ"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"ಅಲಾರಾಂಗಳು, ಜ್ಞಾಪನೆಗಳು, ಈವೆಂಟ್‌ಗಳು ಹಾಗೂ ನೀವು ಸೂಚಿಸಿರುವ ಕರೆದಾರರನ್ನು ಹೊರತುಪಡಿಸಿ ಬೇರಾವುದೇ ಸದ್ದುಗಳು ಅಥವಾ ವೈಬ್ರೇಶನ್‌ಗಳು ನಿಮಗೆ ತೊಂದರೆ ನೀಡುವುದಿಲ್ಲ. ಹಾಗಿದ್ದರೂ, ನೀವು ಪ್ಲೇ ಮಾಡುವ ಸಂಗೀತ, ವೀಡಿಯೊಗಳು ಮತ್ತು ಆಟಗಳ ಆಡಿಯೊವನ್ನು ನೀವು ಕೇಳಿಸಿಕೊಳ್ಳುತ್ತೀರಿ."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್‌ನಲ್ಲಿ ತೋರಿಸಲಾಗುವ ಅಥವಾ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ಮುಂದುವರಿಸಿ"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಿ ಅಥವಾ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"ಹಂಚಿಕೊಳ್ಳಲು ಅಥವಾ ರೆಕಾರ್ಡ್ ಮಾಡಲು ಈ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುವುದೇ?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"ನೀವು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ ಈ ಆ್ಯಪ್ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"ನೀವು ಆ್ಯಪ್ ಒಂದನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್‌ನಲ್ಲಿ ತೋರಿಸಲಾಗುವ ಅಥವಾ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಈ ಆ್ಯಪ್ ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ನಿಮ್ಮ IT ನಿರ್ವಾಹಕರು ನಿರ್ಬಂಧಿಸಿದ್ದಾರೆ"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ಸಾಧನ ನೀತಿಯಿಂದ ಸ್ಕ್ರೀನ್ ಕ್ಯಾಪ್ಚರಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ನಿರ್ವಹಿಸಿ"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ಇತಿಹಾಸ"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ಬಳಸಲು ಅನ್‌ಲಾಕ್ ಮಾಡಿ"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"ನಿಮ್ಮ ಕಾರ್ಡ್‌ಗಳನ್ನು ಪಡೆಯುವಾಗ ಸಮಸ್ಯೆ ಉಂಟಾಗಿದೆ, ನಂತರ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ಲಾಕ್ ಸ್ಕ್ರ್ರೀನ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR ಕೋಡ್ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"ಏರ್‌ಪ್ಲೇನ್ ಮೋಡ್"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"ನಿಮ್ಮ ಮುಂದಿನ <xliff:g id="WHEN">%1$s</xliff:g> ಅಲಾರಮ್ ಅನ್ನು ನೀವು ಆಲಿಸುವುದಿಲ್ಲ"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> ಗೆ ಬದಲಿಸುವುದೇ?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ಲಭ್ಯತೆಯ ಆಧಾರದ ಮೇಲೆ ಮೊಬೈಲ್ ಡೇಟಾ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಬದಲಾಗುವುದಿಲ್ಲ"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ಬೇಡ, ಧನ್ಯವಾದಗಳು"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ಹೌದು, ಬದಲಿಸಿ"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ಪೂರ್ಣ ಸ್ಕ್ರೀನ್‌ ಅನ್ನು ಹಿಗ್ಗಿಸಿ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ಸ್ಕ್ರೀನ್‌ನ ಅರ್ಧಭಾಗವನ್ನು ಝೂಮ್ ಮಾಡಿ"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ಸ್ವಿಚ್"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ಡಯಾಗನಲ್ ಸ್ಕ್ರೋಲಿಂಗ್ ಅನ್ನು ಅನುಮತಿಸಿ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ಮರುಗಾತ್ರಗೊಳಿಸಿ"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"ಹಿಗ್ಗಿಸುವಿಕೆಯ ಪ್ರಕಾರವನ್ನು ಬದಲಾಯಿಸಿ"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ರದ್ದುಗೊಳಿಸಿ"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ}one{# ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ}other{# ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"ತೆಗೆದುಹಾಕಿ"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ತಾತ್ಕಾಲಿಕವಾಗಿ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"ಕಳಪೆ ಸಂಪರ್ಕ"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"ಎಲ್ಲಾ ಸಾಧನದ ಲಾಗ್‌ಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ಗೆ ಅನುಮತಿಸುವುದೇ?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"ಒಂದು ಬಾರಿಯ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಿ"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"ಅನುಮತಿಸಬೇಡಿ"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕಾರ್ಯಾಚರಣೆಗಳನ್ನು ಸಾಧನದ ಲಾಗ್‌ಗಳು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತವೆ. ಸಮಸ್ಯೆಗಳನ್ನು ಪತ್ತೆಹಚ್ಚಲು ಮತ್ತು ಪರಿಹರಿಸಲು ಆ್ಯಪ್‌ಗಳು ಈ ಲಾಗ್ ಅನ್ನು ಬಳಸಬಹುದು.\n\nಕೆಲವು ಲಾಗ್‌ಗಳು ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಿರಬಹುದು, ಆದ್ದರಿಂದ ನಿಮ್ಮ ವಿಶ್ವಾಸಾರ್ಹ ಆ್ಯಪ್‌ಗಳಿಗೆ ಮಾತ್ರ ಸಾಧನದ ಎಲ್ಲಾ ಲಾಗ್‌ಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಿ. \n\nಎಲ್ಲಾ ಸಾಧನ ಲಾಗ್‌ಗಳನ್ನು ಪ್ರವೇಶಿಸಲು ನೀವು ಈ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸದಿದ್ದರೆ, ಅದು ಆಗಲೂ ತನ್ನದೇ ಆದ ಲಾಗ್‌ಗಳನ್ನು ಪ್ರವೇಶಿಸಬಹುದು. ನಿಮ್ಮ ಸಾಧನ ತಯಾರಕರಿಗೆ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಕೆಲವು ಲಾಗ್‌ಗಳು ಅಥವಾ ಮಾಹಿತಿಯನ್ನು ಪ್ರವೇಶಿಸಲು ಈಗಲೂ ಸಾಧ್ಯವಾಗುತ್ತದೆ."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 11da089..ded1cc2 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"이 컴퓨터에서 항상 허용"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"허용"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB 디버깅이 허용되지 않음"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"현재 이 기기에 로그인한 사용자는 USB 디버깅을 사용 설정할 수 없습니다. 이 기능을 사용하려면 기본 사용자로 전환하세요."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"현재 이 기기에 로그인한 사용자는 USB 디버깅을 사용 설정할 수 없습니다. 이 기능을 사용하려면 관리자로 전환하세요."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"시스템 언어를 <xliff:g id="LANGUAGE">%1$s</xliff:g>로 변경하시겠습니까?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"다른 기기에서 시스템 언어 변경을 요청함"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"언어 변경"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"이 네트워크에서 항상 허용"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"허용"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"무선 디버깅이 허용되지 않음"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"현재 이 기기에 로그인한 사용자는 무선 디버깅을 사용 설정할 수 없습니다. 이 기능을 사용하려면 기본 사용자로 전환하세요."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"현재 이 기기에 로그인한 사용자는 무선 디버깅을 사용 설정할 수 없습니다. 이 기능을 사용하려면 관리자로 전환하세요."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB 포트 비활성화됨"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"기기를 액체나 이물질로부터 보호하기 위해 USB 포트가 사용 중지되었으며 액세서리를 연결할 수 없습니다.\n\nUSB 포트를 다시 안전하게 사용할 수 있게 되면 알려 드리겠습니다."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"충전기와 액세서리를 감지할 수 있도록 USB 포트가 사용 설정됨"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"음성 지원"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"월렛"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR 코드 스캐너"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"잠금 해제"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"기기 잠김"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"얼굴 스캔 중"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"보내기"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"얼굴을 인식할 수 없습니다."</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"대신 지문을 사용하세요."</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"블루투스가 연결되었습니다."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"배터리 잔량을 알 수 없습니다."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>에 연결되었습니다."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"비행기 모드입니다."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN 켜짐"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"배터리 <xliff:g id="NUMBER">%d</xliff:g>퍼센트"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"배터리 <xliff:g id="PERCENTAGE">%1$s</xliff:g>퍼센트, 평소 사용량 기준 약 <xliff:g id="TIME">%2$s</xliff:g> 남음"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"배터리 <xliff:g id="PERCENTAGE">%1$d</xliff:g>퍼센트, 평소 사용량 기준 약 <xliff:g id="TIME">%2$s</xliff:g> 남음"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"배터리 충전 중, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%입니다."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"모든 알림 보기"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"전신 타자기(TTY)가 사용 설정되었습니다."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"벨소리가 진동입니다."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"마이크 사용 가능"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"카메라 사용 가능"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"마이크 및 카메라 사용 가능"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"마이크 사용 설정됨"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"마이크 사용 중지됨"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"모든 앱 및 서비스의 마이크가 사용 설정됩니다."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"모든 앱 및 서비스의 마이크 액세스가 사용 중지됩니다. 설정 &gt; 개인 정보 보호 &gt; 마이크에서 마이크 액세스를 사용 설정할 수 있습니다."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"모든 앱 및 서비스의 마이크 액세스가 사용 중지됩니다. 설정 &gt; 개인 정보 보호 &gt; 마이크에서 변경할 수 있습니다."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"카메라 사용 설정됨"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"카메라 사용 중지됨"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"모든 앱 및 서비스의 카메라가 사용 설정됩니다."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"모든 앱 및 서비스의 카메라 액세스가 사용 중지됩니다."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"마이크 버튼을 사용하려면 설정에서 마이크 액세스를 사용 설정하세요."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"설정 열기"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"기타 기기"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"최근 사용 버튼 전환"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"알람, 알림, 일정 및 지정한 발신자로부터 받은 전화를 제외한 소리와 진동을 끕니다. 음악, 동영상, 게임 등 재생하도록 선택한 소리는 정상적으로 들립니다."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"앱을 공유하거나 녹화하거나 전송할 때는 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"계속"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"앱 공유 또는 녹화"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"앱에서 공유하거나 기록하도록 허용하시겠습니까?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"공유하거나 녹화하거나 전송할 때 이 앱에서 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"앱을 공유하거나 녹화하거나 전송할 때는 이 앱에서 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 관리자에 의해 차단됨"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"기기 정책에 의해 화면 캡처가 사용 중지되었습니다."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"관리"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"기록"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"잠금 해제하여 사용"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"카드를 가져오는 중에 문제가 발생했습니다. 나중에 다시 시도해 보세요."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"잠금 화면 설정"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR 코드 스캔"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"직장 프로필"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"비행기 모드"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>에 다음 알람을 들을 수 없습니다."</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"다시 <xliff:g id="CARRIER">%s</xliff:g>(으)로 전환할까요?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"모바일 데이터가 가용성에 따라 자동으로 전환하지 않습니다."</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"나중에"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"예, 전환합니다"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"전체 화면 확대"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"화면 일부 확대"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"전환"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"대각선 스크롤 허용"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"크기 조절"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"확대 유형 변경"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"실행취소"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{바로가기 {label}개 삭제됨}other{바로가기 #개 삭제됨}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"삭제"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"일시적으로 연결됨"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"연결 상태 나쁨"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"MMM d일 EEE"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>에서 모든 기기 로그에 액세스하도록 허용하시겠습니까?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"일회성 액세스 허용"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"허용 안함"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"기기 로그에 기기에서 발생한 상황이 기록됩니다. 앱은 문제를 찾고 해결하는 데 이 로그를 사용할 수 있습니다.\n\n일부 로그는 민감한 정보를 포함할 수 있으므로 신뢰할 수 있는 앱만 모든 기기 로그에 액세스하도록 허용하세요. \n\n앱에 전체 기기 로그에 대한 액세스 권한을 부여하지 않아도 앱이 자체 로그에는 액세스할 수 있습니다. 기기 제조업체에서 일부 로그 또는 기기 내 정보에 액세스할 수도 있습니다."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 676076c..a49a1d5 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Бул компүтерден дайыма уруксат берилсин"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Ооба"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB мүчүлүштүктөрүн оңдоого уруксат жок"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Учурда бул аккаунтта USB аркылуу мүчүлүштүктөрдү аныктоо функциясын иштетүүгө болбойт. Негизги колдонуучунун аккаунтуна кириңиз."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Учурда ушул түзмөккө кирип турган колдонуучу USB аркылуу мүчүлүштүктөрдү оңдоо функциясын күйгүзө албайт. Бул функцияны пайдалануу үчүн админге которулуңуз."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Тутум тилин <xliff:g id="LANGUAGE">%1$s</xliff:g> тилине өзгөртөсүзбү?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Тутум тилин өзгөртүү сурамы башка түзмөктөн жөнөтүлдү"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Тилди өзгөртүү"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Бул тармакта ар дайым уруксат берилсин"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Ооба"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Мүчүлүштүктөрдү Wi-Fi аркылуу оңдоого уруксат берилген жок"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Учурда бул түзмөккө кирген колдонуучу мүчүлүштүктөрдү Wi-Fi аркылуу оңдоо функциясын күйгүзө албайт. Бул функцияны колдонуу үчүн негизги колдонуучунун аккаунтуна которулуңуз."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Учурда бул түзмөккө кирип турган колдонуучу мүчүлүштүктөрдү Wi-Fi аркылуу оңдоо функциясын күйгүзө албайт. Бул функцияны колдонуу үчүн админге которулуңуз."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB порту өчүрүлдү"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Түзмөгүңүздүн ичине суюктук же булганч нерселер кирип кетпеши үчүн USB порту өчүрүлдү. Азырынча ал аркылуу башка түзмөктөргө туташууга болбойт.\n\nUSB портун кайра колдонуу мүмкүн болгондо, билдирме аласыз."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Кубаттагычтарды жана аксессуарларды аныктоо үчүн USB оюкчасы иштетилди"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Үн жардамчысы"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Капчык"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR коддорунун сканери"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Кулпусун ачуу"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Түзмөк кулпуланды"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Жүз скандалууда"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Жөнөтүү"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Жүз таанылбай жатат"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Манжа изин колдонуңуз"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth байланышта"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарея кубатынын деңгээли белгисиз."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> менен туташкан."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Учак тартиби."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN күйүк."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Батарея <xliff:g id="NUMBER">%d</xliff:g> пайыз."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батареянын деңгээли <xliff:g id="PERCENTAGE">%1$s</xliff:g> пайыз, колдонгонуңузга караганда болжол менен <xliff:g id="TIME">%2$s</xliff:g> калды"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батареянын деңгээли <xliff:g id="PERCENTAGE">%1$d</xliff:g> пайыз, колдонгонуңузга караганда болжол менен <xliff:g id="TIME">%2$s</xliff:g> калды"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Батарея кубатталууда, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Бардык билдирмелерди көрүү"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"ТелеТайп терүүсү жандырылган."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Шыңгыраганда титирөө."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Микрофон жеткиликтүү"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камера жеткиликтүү"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Микрофон жана камера жеткиликтүү"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Микрофон күйгүзүлдү"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Микрофон өчүрүлдү"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Микрофон бардык колдонмолор жана кызматтар үчүн иштетилди."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Микрофон бардык колдонмолор жана кызматтар үчүн өчүрүлдү. Микрофонду колдонууну иштетүү үчүн &gt; Купуялык &gt; Микрофон бөлүмүнө өтүңүз."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Микрофон бардык колдонмолор жана кызматтар үчүн өчүрүлдү. Муну Параметрлер &gt; Купуялык &gt; Микрофон бөлүмүнөн өзгөртө аласыз."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камера күйгүзүлдү"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камера өчүрүлдү"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Камера бардык колдонмолор жана кызматтар үчүн иштетилди."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Камера бардык колдонмолор жана кызматтар үчүн өчүрүлдү."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Микрофон баскычын колдонуу үчүн Параметрлерден микрофонду колдонууну иштетиңиз."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Параметрлерди ачуу."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Башка түзмөк"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Назар режимин өчүрүү/күйгүзүү"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Ойготкучтардан, эскертүүлөрдөн, жылнаамадагы иш-чараларды эстеткичтерден жана белгиленген байланыштардын чалууларынан тышкары башка үндөр жана дирилдөөлөр тынчыңызды албайт. Бирок ойнотулуп жаткан музыканы, видеолорду жана оюндарды мурдагыдай эле уга бересиз."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Бөлүшүп, жаздырып же тышкы экранда бөлүшкөндө <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ал колдонмодо көрүнүп жана ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөм маалыматын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Улантуу"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Колдонмону бөлүшүү же жаздыруу"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Бул колдонмого бөлүшүп же жаздырууга уруксат бересизби?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Бөлүшүп, жаздырып же тышкы экранга чыгарганда бул колдонмо экраныңызда көрүнүп жана түзмөктө ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Бөлүшүп, жаздырып же тышкы экранга чыгарганда бул колдонмо ал колдонмодо көрсөтүлүп жана ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT администраторуңуз бөгөттөп койгон"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Түзмөк саясаты экрандагыны тартып алууну өчүрүп койгон"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Баарын тазалап салуу"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Башкаруу"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Таржымал"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Колдонуу үчүн кулпусун ачыңыз"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Кыйытмаларды алууда ката кетти. Бир аздан кийин кайталап көрүңүз."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Экранды кулпулоо параметрлери"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR кодун скандоо"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Жумуш профили"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Учак режими"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> боло турган кийинки эскертмени укпайсыз"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Кайра <xliff:g id="CARRIER">%s</xliff:g> байланыш операторуна которуласызбы?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Жеткиликтүү болгондо мобилдик Интернет автоматтык түрдө которулбайт"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Жок, рахмат"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ооба, которулуу"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Толук экранда ачуу"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Экрандын бир бөлүгүн чоңойтуу"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Которулуу"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Диагональ боюнча сыдырууга уруксат берүү"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Өлчөмүн өзгөртүү"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Чоңойтуу түрүн өзгөртүү"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Кайтаруу"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} ыкчам баскыч өчүрүлдү}other{# ыкчам баскыч өчүрүлдү}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Өчүрүү"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Убактылуу туташып турат"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Байланыш начар"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> колдонмосуна түзмөктөгү бардык таржымалдарды жеткиликтүү кыласызбы?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Бир жолу жеткиликтүү кылуу"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Тыюу салуу"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Түзмөктө аткарылган бардык аракеттер түзмөктүн таржымалдарында сакталып калат. Колдонмолор бул таржымалдарды колдонуп, маселелерди оңдошот.\n\nАйрым таржымалдарда купуя маалымат болушу мүмкүн, андыктан ишенимдүү колдонмолорго гана түзмөктөгү бардык таржымалдарды пайдаланууга уруксат бериңиз. \n\nЭгер бул колдонмого түзмөктөгү бардык таржымалдарга кирүүгө тыюу салсаңыз, ал өзүнүн таржымалдарын пайдалана берет. Түзмөктү өндүрүүчү түзмөгүңүздөгү айрым таржымалдарды же маалыматты көрө берет."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 49ef330..fff2544 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -40,6 +40,10 @@
     <dimen name="biometric_dialog_button_negative_max_width">140dp</dimen>
     <dimen name="biometric_dialog_button_positive_max_width">116dp</dimen>
 
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+    <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+
     <dimen name="global_actions_power_dialog_item_height">130dp</dimen>
     <dimen name="global_actions_power_dialog_item_bottom_margin">35dp</dimen>
 
diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml
index aefd998..a0e721e 100644
--- a/packages/SystemUI/res/values-land/styles.xml
+++ b/packages/SystemUI/res/values-land/styles.xml
@@ -29,11 +29,11 @@
 
     <style name="AuthCredentialPatternContainerStyle">
         <item name="android:gravity">center</item>
-        <item name="android:maxHeight">320dp</item>
-        <item name="android:maxWidth">320dp</item>
-        <item name="android:minHeight">200dp</item>
-        <item name="android:minWidth">200dp</item>
-        <item name="android:paddingHorizontal">60dp</item>
+        <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+        <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+        <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+        <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
+        <item name="android:paddingHorizontal">32dp</item>
         <item name="android:paddingVertical">20dp</item>
     </style>
 
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 4fc25e2..b61a347 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"ອະນຸຍາດຈາກຄອມພິວເຕີນີ້ຕະຫຼອດ"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"ອະນຸຍາດ"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"ບໍ່​ອະ​ນຸ​ຍາດ​ໃຫ້​ມີ​ການ​ແກ້​ໄຂ​ບັນ​ຫາ USB"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"ຜູ້ໃຊ້ທີ່ກຳລັງເຂົ້າສູ່ລະບົບອຸປະກອນຢູ່ໃນຕອນນີ້ບໍ່ສາມາດເປີດໃຊ້ການດີບັກ USB ໄດ້. ເພື່ອໃຊ້ຄຸນສົມບັດນີ້, ໃຫ້ສະຫຼັບໄປໃຊ້ຜູ້ໃຊ້ຫຼັກ."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"ຜູ້ໃຊ້ທີ່ເຂົ້າສູ່ລະບົບອຸປະກອນຢູ່ໃນຕອນນີ້ບໍ່ສາມາດເປີດໃຊ້ການດີບັກ USB ໄດ້. ເພື່ອໃຊ້ຄຸນສົມບັດນີ້, ໃຫ້ປ່ຽນໄປເປັນຜູ້ໃຊ້ທີ່ເບິ່ງແຍງລະບົບ."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"ທ່ານຕ້ອງການປ່ຽນພາສາລະບົບເປັນ <xliff:g id="LANGUAGE">%1$s</xliff:g> ບໍ່?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"ມີການຮ້ອງຂໍໃຫ້ປ່ຽນພາສາລະບົບໂດຍອຸປະກອນອື່ນ"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"ປ່ຽນພາສາ"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"ອະນຸຍາດຕະຫຼອດຢູ່ເຄືອຂ່າຍນີ້"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"ອະນຸຍາດ"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ການດີບັກໄຮ້ສາຍ"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"ຜູ້ໃຊ້ທີ່ກຳລັງເຂົ້າສູ່ລະບົບອຸປະກອນຢູ່ໃນຕອນນີ້ບໍ່ສາມາດເປີດໃຊ້ການດີບັກໄຮ້ສາຍໄດ້. ເພື່ອໃຊ້ຄຸນສົມບັດນີ້, ໃຫ້ສະຫຼັບໄປໃຊ້ຜູ້ໃຊ້ຫຼັກ."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"ຜູ້ໃຊ້ທີ່ເຂົ້າສູ່ລະບົບອຸປະກອນຢູ່ໃນຕອນນີ້ບໍ່ສາມາດເປີດໃຊ້ການດີບັກໄຮ້ສາຍໄດ້. ເພື່ອໃຊ້ຄຸນສົມບັດນີ້, ໃຫ້ປ່ຽນໄປເປັນຜູ້ໃຊ້ທີ່ເບິ່ງແຍງລະບົບ."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"ປິດການນຳໃຊ້ຜອດ USB ແລ້ວ"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"ເພື່ອປົກປ້ອງອຸປະກອນຂອງທ່ານຈາກຂອງແຫລວ ຫຼື ເສດດິນຕ່າງໆ, ຜອດ USB ຈຶ່ງຖືກປິດການນຳໃຊ້ ແລະ ຈະບໍ່ກວດຫາອຸປະກອນເສີມໃດໆ.\n\nທ່ານຈະໄດ້ຮັບການແຈ້ງເຕືອນເມື່ອສາມາດໃຊ້ຜອດ USB ໄດ້ອີກເທື່ອໜຶ່ງ."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"ເປີດນຳໃຊ້ USB ແລ້ວເພື່ອກວດຫາສາຍສາກ ແລະ ອຸປະກອນເສີມ"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ຊ່ວຍ​ເຫຼືອ​ທາງ​ສຽງ"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"ຕົວສະແກນລະຫັດ QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"ປົດລັອກ"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"ອຸປະກອນຖືກລັອກໄວ້"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"ການສະແກນໜ້າ"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ສົ່ງ"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"ບໍ່ສາມາດຈຳແນກໃບໜ້າໄດ້"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ກະລຸນາໃຊ້ລາຍນິ້ວມືແທນ"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ເຊື່ອມຕໍ່ Bluetooth ແລ້ວ."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ບໍ່ຮູ້ເປີເຊັນແບັດເຕີຣີ."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"ເຊື່ອມ​ຕໍ່​ຫາ <xliff:g id="BLUETOOTH">%s</xliff:g> ແລ້ວ."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"ໂໝດໃນຍົນ."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ເປີດ."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"ແບັດເຕີຣີ <xliff:g id="NUMBER">%d</xliff:g> ເປີເຊັນ."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ແບັດເຕີຣີ <xliff:g id="PERCENTAGE">%1$s</xliff:g> ເປີເຊັນ, ເຫຼືອປະມານ <xliff:g id="TIME">%2$s</xliff:g> ອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ແບັດເຕີຣີ <xliff:g id="PERCENTAGE">%1$d</xliff:g> ເປີເຊັນ, ເຫຼືອປະມານ <xliff:g id="TIME">%2$s</xliff:g> ອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ກຳລັງສາກແບັດເຕີຣີ, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> ເປີເຊັນ."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"ເບິ່ງການແຈ້ງເຕືອນທັງໝົດ"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter ຖືກເປີດຢູ່."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ສັ່ນເຕືອນພ້ອມສຽງເອີ້ນເຂົ້າ."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"ສາມາດໃຊ້ໄມໂຄຣໂຟນໄດ້"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ສາມາດໃຊ້ກ້ອງຖ່າຍຮູບໄດ້"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"ສາມາດໃຊ້ໄມໂຄຣໂຟນ ແລະ ກ້ອງຖ່າຍຮູບໄດ້"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"ເປີດໄມໂຄຣໂຟນແລ້ວ"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"ປິດໄມໂຄຣໂຟນແລ້ວ"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"ເປີດການນຳໃຊ້ໄມໂຄຣໂຟນສຳລັບແອັບ ແລະ ບໍລິການທັງໝົດແລ້ວ."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"ສິດເຂົ້າເຖິງໄມໂຄຣໂຟນຖືກປິດການນຳໃຊ້ສຳລັບແອັບ ແລະ ບໍລິການທັງໝົດແລ້ວ. ທ່ານສາມາດເປີດການນຳໃຊ້ສິດເຂົ້າເຖິງໄມໂຄຣໂຟນໄດ້ໃນການຕັ້ງຄ່າ &gt; ຄວາມເປັນສ່ວນຕົວ &gt; ໄມໂຄຣໂຟນ."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"ສິດເຂົ້າເຖິງໄມໂຄຣໂຟນຖືກປິດການນຳໃຊ້ສຳລັບແອັບ ແລະ ບໍລິການທັງໝົດແລ້ວ. ທ່ານສາມາດປ່ຽນສິ່ງນີ້ໄດ້ໃນການຕັ້ງຄ່າ &gt; ຄວາມເປັນສ່ວນຕົວ &gt; ໄມໂຄຣໂຟນ."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"ເປີດກ້ອງຖ່າຍຮູບແລ້ວ"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ປິດກ້ອງຖ່າຍຮູບແລ້ວ"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"ເປີດການນຳໃຊ້ກ້ອງຖ່າຍຮູບສຳລັບແອັບ ແລະ ບໍລິການທັງໝົດແລ້ວ."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"ສິດເຂົ້າເຖິງກ້ອງຖ່າຍຮູບຖືກປິດການນຳໃຊ້ສຳລັບແອັບ ແລະ ບໍລິການທັງໝົດແລ້ວ."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"ເພື່ອໃຊ້ປຸ່ມໄມໂຄຣໂຟນ, ໃຫ້ເປີດການນຳໃຊ້ສິດເຂົ້າເຖິງໄມໂຄຣໂຟນໃນການຕັ້ງຄ່າກ່ອນ."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ເປີດການຕັ້ງຄ່າ."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"ອຸປະກອນອື່ນໆ"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ສະຫຼັບພາບຮວມ"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"ທ່ານຈະບໍ່ໄດ້ຮັບການລົບກວນຈາກສຽງ ແລະ ການສັ່ນເຕືອນ, ຍົກເວັ້ນໃນເວລາໂມງປຸກດັງ, ມີການແຈ້ງເຕືອນ ຫຼື ມີສາຍໂທເຂົ້າຈາກຜູ້ໂທທີ່ທ່ານລະບຸໄວ້. ທ່ານອາດຍັງຄົງໄດ້ຍິນຫາກທ່ານເລືອກຫຼິ້ນເພງ, ວິດີໂອ ແລະ ເກມ."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ໃນຕອນທີ່ທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານແອັບ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ມີສິດເຂົ້າເຖິງສິ່ງທີ່ສະແດງ ຫຼື ຫຼິ້ນຢູ່ໃນແອັບນັ້ນ. ດັ່ງນັ້ນໃຫ້ລະມັດລະວັງກ່ຽວກັບລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ ຫຼື ຂໍ້ມູນທີ່ລະອຽດອ່ອນອື່ນໆ."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ສືບຕໍ່"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ແບ່ງປັນ ຫຼື ບັນທຶກແອັບ"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"ອະນຸຍາດໃຫ້ແອັບນີ້ແບ່ງປັນ ຫຼື ບັນທຶກບໍ?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"ເມື່ອທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານ, ແອັບນີ້ຈະມີສິດເຂົ້າເຖິງສິ່ງທີ່ເຫັນໄດ້ໃນໜ້າຈໍຂອງທ່ານ ຫຼື ຫຼິ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ດັ່ງນັ້ນໃຫ້ລະມັດລະວັງເລື່ອງລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ ຫຼື ຂໍ້ມູນທີ່ລະອຽດອ່ອນອື່ນໆ."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"ໃນຕອນທີ່ທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານແອັບ, ແອັບນີ້ຈະມີສິດເຂົ້າເຖິງສິ່ງທີ່ສະແດງ ຫຼື ຫຼິ້ນຢູ່ໃນແອັບນັ້ນ. ດັ່ງນັ້ນໃຫ້ລະມັດລະວັງກ່ຽວກັບລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ ຫຼື ຂໍ້ມູນທີ່ລະອຽດອ່ອນອື່ນໆ."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ຖືກບລັອກໄວ້ໂດຍຜູ້ເບິ່ງແຍງລະບົບໄອທີຂອງທ່ານ"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ການຖ່າຍຮູບໜ້າຈໍຖືກປິດການນຳໃຊ້ໄວ້ໂດຍນະໂຍບາຍອຸປະກອນ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ລຶບລ້າງທັງໝົດ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ຈັດການ"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ປະຫວັດ"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ປົດລັອກເພື່ອໃຊ້"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"ເກີດບັນຫາໃນການໂຫຼດບັດຂອງທ່ານ, ກະລຸນາລອງໃໝ່ໃນພາຍຫຼັງ"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ການຕັ້ງຄ່າໜ້າຈໍລັອກ"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"ສະແກນລະຫັດ QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"​ໂປຣ​ໄຟລ໌​ບ່ອນ​ເຮັດ​ວຽກ"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"ໂໝດເຮືອ​ບິນ"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"ທ່ານ​ຈະ​ບໍ່​ໄດ້​ຍິນ​ສຽງ​ໂມງ​ປ <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"ສະຫຼັບກັບໄປໃຊ້ <xliff:g id="CARRIER">%s</xliff:g> ບໍ?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ອິນເຕີເນັດມືຖືຈະບໍ່ປ່ຽນຕາມຄວາມພ້ອມໃຫ້ບໍລິການໂດຍອັດຕະໂນມັດ"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ບໍ່, ຂອບໃຈ"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ແມ່ນແລ້ວ, ສະຫຼັບ"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ຂະຫຍາຍເຕັມຈໍ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ຂະຫຍາຍບາງສ່ວນຂອງໜ້າຈໍ"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ສະຫຼັບ"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ອະນຸຍາດໃຫ້ເລື່ອນທາງຂວາງ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ປ່ຽນຂະໜາດ"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"ປ່ຽນຮູບແບບການຂະຫຍາຍ"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ຍົກເລີກ"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{ລຶບທາງລັດ {label} ອອກແລ້ວ}other{ລຶບທາງລັດ # ອອກແລ້ວ}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"ລຶບອອກ"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ເຊື່ອມຕໍ່ແລ້ວຊົ່ວຄາວ"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"ສັນຍານເຊື່ອມຕໍ່ຊ້າ"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"ຊມ:ນທ"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"ຊມ:ນທ"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"ອະນຸຍາດໃຫ້ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ເຂົ້າເຖິງບັນທຶກທັງໝົດຂອງອຸປະກອນບໍ?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"ອະນຸຍາດສິດເຂົ້າເຖິງແບບເທື່ອດຽວ"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"ບໍ່ອະນຸຍາດ"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"ບັນທຶກຂອງອຸປະກອນຈະບັນທຶກສິ່ງທີ່ເກີດຂຶ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ແອັບສາມາດໃຊ້ບັນທຶກເຫຼົ່ານີ້ເພື່ອຊອກຫາ ແລະ ແກ້ໄຂບັນຫາໄດ້.\n\nບັນທຶກບາງຢ່າງອາດມີຂໍ້ມູນທີ່ລະອຽດອ່ອນ, ດັ່ງນັ້ນໃຫ້ອະນຸຍາດສະເພາະແອັບທີ່ທ່ານເຊື່ອຖືໄດ້ໃຫ້ເຂົ້າເຖິງບັນທຶກທັງໝົດຂອງອຸປະກອນເທົ່ານັ້ນ. \n\nຫາກທ່ານບໍ່ອະນຸຍາດໃຫ້ແອັບນີ້ເຂົ້າເຖິງບັນທຶກທັງໝົດຂອງອຸປະກອນ, ມັນຈະຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກຂອງຕົວມັນເອງໄດ້ຢູ່. ຜູ້ຜະລິດອຸປະກອນຂອງທ່ານອາດຍັງຄົງສາມາດເຂົ້າເຖິງບັນທຶກ ຫຼື ຂໍ້ມູນບາງຢ່າງຢູ່ອຸປະກອນຂອງທ່ານໄດ້."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index b9d6f33..b3c234e 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Visada leisti iš šio kompiuterio"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Leisti"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB derinimas neleidžiamas"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Šiuo metu prie įrenginio prisijungęs naudotojas negali įjungti USB derinimo. Kad galėtumėte naudoti šią funkciją, perjunkite į pagrindinį naudotoją."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Šiuo metu prie įrenginio prisijungęs naudotojas negali įjungti USB derinimo. Kad galėtumėte naudoti šią funkciją, perjunkite į naudotoją administratorių."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Ar norite pakeisti sistemos kalbą į <xliff:g id="LANGUAGE">%1$s</xliff:g> k.?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Užklausą dėl sistemos kalbos pakeitimo pateikė kitas įrenginys"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Keisti kalbą"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Visada leisti naudojant šį tinklą"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Leisti"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Belaidžio ryšio derinimas neleidžiamas"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Šiuo metu prie įrenginio prisijungęs naudotojas negali įjungti belaidžio ryšio derinimo. Kad galėtumėte naudoti šią funkciją, perjunkite į pagrindinį naudotoją."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Šiuo metu prie įrenginio prisijungęs naudotojas negali įjungti belaidžio ryšio derinimo. Kad galėtumėte naudoti šią funkciją, perjunkite į naudotoją administratorių."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB prievadas išjungtas"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Siekiant apsaugoti įrenginį nuo skysčių ar smulkių dalelių, USB prievadas buvo išjungtas ir neaptiks jokių priedų.\n\nJums bus pranešta, kai galėsite vėl saugiai naudoti USB prievadą."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB prievadas įgalintas aptikti kroviklius ir priedus"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR kodų skaitytuvas"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Atrakinti"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Įrenginys užrakintas"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Nuskaitomas veidas"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Siųsti"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Veidas neatpažintas"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Naudoti piršto antspaudą"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"„Bluetooth“ prijungtas."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akumuliatoriaus energija procentais nežinoma."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Prisijungta prie „<xliff:g id="BLUETOOTH">%s</xliff:g>“."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Lėktuvo režimas."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN įjungtas."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Akumuliatorius: <xliff:g id="NUMBER">%d</xliff:g> proc."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> proc. akumuliatoriaus energijos – liko maždaug <xliff:g id="TIME">%2$s</xliff:g>, atsižvelgiant į naudojimą"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> proc. akumuliatoriaus energijos – liko maždaug <xliff:g id="TIME">%2$s</xliff:g>, atsižvelgiant į naudojimą"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Įkraunamas akumuliatorius, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Žr. visus pranešimus"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"„TeleTypewriter“ įgalinta."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibracija skambinant."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Tvarkyti naudotojus"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofonas pasiekiamas"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Vaizdo kamera pasiekiama"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofonas ir vaizdo kamera pasiekiami"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofonas įjungtas"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofonas išjungtas"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofonas įgalintas veikiant visoms programoms ir paslaugoms."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Visų programų ir paslaugų prieiga prie mikrofono išjungta. Prieigą prie mikrofono galite įjungti nuėję į „Nustatymai“ &gt; „Privatumas“ &gt; „Mikrofonas“."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Visų programų ir paslaugų prieiga prie mikrofono išjungta. Tai galite pakeisti nuėję į „Nustatymai“ &gt; „Privatumas“ &gt; „Mikrofonas“."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Fotoaparatas įjungtas"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Fotoaparatas išjungtas"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Fotoaparatas įgalintas veikiant visoms programoms ir paslaugoms."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Visų programų ir paslaugų prieiga prie fotoaparato išjungta."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Norėdami naudotis mikrofono mygtuku, nustatymuose įjunkite prieigą prie mikrofono."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Atidaryti nustatymus."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Kitas įrenginys"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Perjungti apžvalgą"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Jūsų netrikdys garsai ir vibravimas, išskyrus nurodytų signalų, priminimų, įvykių ir skambintojų garsus. Vis tiek girdėsite viską, ką pasirinksite leisti, įskaitant muziką, vaizdo įrašus ir žaidimus."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kai bendrinate, įrašote ar perduodate turinį, „<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>“ gali pasiekti viską, kas rodoma ar leidžiama programoje. Todėl būkite atsargūs su slaptažodžiais, išsamia mokėjimo metodo informacija, pranešimais ar kita neskelbtina informacija."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Tęsti"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Programos bendrinimas ar įrašymas"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Leisti šiai programai bendrinti arba įrašyti?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kai bendrinate, įrašote ar perduodate turinį, ši programa gali pasiekti viską, kas rodoma ekrane ar leidžiama įrenginyje. Todėl būkite atsargūs su slaptažodžiais, išsamia mokėjimo metodo informacija, pranešimais ar kita neskelbtina informacija."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kai bendrinate, įrašote ar perduodate turinį, ši programa gali pasiekti viską, kas rodoma ar leidžiama programoje. Todėl būkite atsargūs su slaptažodžiais, išsamia mokėjimo metodo informacija, pranešimais ar kita neskelbtina informacija."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Užblokavo jūsų IT administratorius"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekrano fiksavimo funkcija išjungta vadovaujantis įrenginio politika"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Viską išvalyti"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Tvarkyti"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Istorija"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Atrakinti, kad būtų galima naudoti"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Gaunant korteles kilo problema, bandykite dar kartą vėliau"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Užrakinimo ekrano nustatymai"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR kodo nuskaitymas"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Darbo profilis"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Lėktuvo režimas"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Negirdėsite kito signalo <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Perjungti atgal į „<xliff:g id="CARRIER">%s</xliff:g>“?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiliojo ryšio duomenys nebus automatiškai perjungti atsižvelgiant į pasiekiamumą"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, ačiū"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Taip, perjungti"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Viso ekrano didinimas"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Didinti ekrano dalį"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Perjungti"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Slinkimo įstrižai leidimas"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Pakeisti dydį"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Didinimo tipo keitimas"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Anuliuoti"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Pašalintas spartusis klavišas „{label}“}one{Pašalintas # spartusis klavišas}few{Pašalinti # spartieji klavišai}many{Pašalinta # sparčiojo klavišo}other{Pašalinta # sparčiųjų klavišų}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Pašalinti"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Laikinai prijungta"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Prastas ryšys"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Leisti „<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>“ pasiekti visus įrenginio žurnalus?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Leisti vienkartinę prieigą"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Neleisti"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Įrenginio žurnaluose įrašoma, kas įvyksta įrenginyje. Programos gali naudoti šiuos žurnalus, kai reikia surasti ir išspręsti problemas.\n\nKai kuriuose žurnaluose gali būti neskelbtinos informacijos, todėl visus įrenginio žurnalus leiskite pasiekti tik programoms, kuriomis pasitikite. \n\nJei neleisite šiai programai pasiekti visų įrenginio žurnalų, ji vis tiek galės pasiekti savo žurnalus. Įrenginio gamintojui vis tiek gali būti leidžiama pasiekti tam tikrus žurnalus ar informaciją jūsų įrenginyje."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 882ff7c..7dff7c1 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -52,7 +52,8 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Vienmēr atļaut no šī datora"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Atļaut"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB atkļūdošana nav atļauta"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Lietotājs, kurš pašlaik ir pierakstījies šajā ierīcē, nevar iespējot USB atkļūdošanu. Lai izmantotu šo funkciju, pārslēdzieties uz galveno lietotāju."</string>
+    <!-- no translation found for usb_debugging_secondary_user_message (1888835696965417845) -->
+    <skip />
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Vai vēlaties mainīt sistēmas valodu uz šādu: <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Sistēmas valodas maiņu pieprasīja cita ierīce."</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Mainīt valodu"</string>
@@ -62,7 +63,8 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Vienmēr atļaut šajā tīklā"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Atļaut"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Bezvadu atkļūdošana nav atļauta"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Lietotājs, kurš pašlaik ir pierakstījies šajā ierīcē, nevar iespējot bezvadu atkļūdošanu. Lai izmantotu šo funkciju, pārslēdzieties uz galveno lietotāju."</string>
+    <!-- no translation found for wifi_debugging_secondary_user_message (9085779370142222881) -->
+    <skip />
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB pieslēgvieta atspējota"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Lai aizsargātu ierīci no šķidruma un gružiem, USB pieslēgvieta ir atspējota un tajā nevarēs noteikt pieslēgtus piederumus.\n\nKad USB pieslēgvietu atkal drīkstēs izmantot, saņemsiet paziņojumu."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB portam ir iespējota uzlādes ierīču un piederumu noteikšana"</string>
@@ -125,7 +127,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Balss palīgs"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Maks"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Kvadrātkoda skeneris"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Atbloķēt"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Ierīce ir bloķēta"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Sejas skenēšana"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Sūtīt"</string>
@@ -168,6 +171,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Nevar atpazīt seju"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Lietot pirksta nospiedumu"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth savienojums ir izveidots."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akumulatora uzlādes līmenis procentos nav zināms."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ir izveidots savienojum ar <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +183,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Lidmašīnas režīms."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ieslēgts"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Akumulators: <xliff:g id="NUMBER">%d</xliff:g> procenti"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Akumulatora uzlādes līmenis: <xliff:g id="PERCENTAGE">%1$s</xliff:g> procenti. Ņemot vērā lietojumu, atlikušais laiks ir apmēram <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Akumulatora uzlādes līmenis: <xliff:g id="PERCENTAGE">%1$d</xliff:g> procenti. Ņemot vērā lietojumu, atlikušais laiks ir apmēram <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Notiek akumulatora uzlāde, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Skatīt visus paziņojumus"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletaips ir iespējots."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Zvana signāls — vibrācija."</string>
@@ -248,7 +257,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Pārvaldīt lietotājus"</string>
     <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>
@@ -303,6 +312,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofons ir pieejams."</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera ir pieejama."</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofons un kamera ir pieejami."</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofons tika ieslēgts"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofons tika izslēgts"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Piekļuve mikrofonam ir iespējota visām lietotnēm un pakalpojumiem."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Piekļuve mikrofonam ir atspējota visām lietotnēm un pakalpojumiem. Varat iespējot piekļuvi mikrofonam sadaļā Iestatījumi &gt; Konfidencialitāte &gt; Mikrofons."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Piekļuve mikrofonam ir atspējota visām lietotnēm un pakalpojumiem. Varat to mainīt sadaļā Iestatījumi &gt; Konfidencialitāte &gt; Mikrofons."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera tika ieslēgta"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera tika izslēgta"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Piekļuve kamerai ir iespējota visām lietotnēm un pakalpojumiem."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Piekļuve kamerai ir atspējota visām lietotnēm un pakalpojumiem."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Lai varētu izmantot mikrofona pogu, iespējojiet piekļuvi mikrofonam sadaļā Iestatījumi."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Atvērt iestatījumus"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Cita ierīce"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Pārskata pārslēgšana"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Jūs netraucēs skaņas un vibrācija, izņemot signālus, atgādinājumus, pasākumus un zvanītājus, ko būsiet norādījis. Jūs joprojām dzirdēsiet atskaņošanai izvēlētos vienumus, tostarp mūziku, videoklipus un spēles."</string>
@@ -373,6 +393,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lietotnes kopīgošanas, ierakstīšanas vai apraides laikā <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Turpināt"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Lietotnes kopīgošana vai ierakstīšana"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Vai atļaut šai lietotnei kopīgot vai ierakstīt?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kopīgošanas, ierakstīšanas vai apraides laikā šī lietotne var piekļūt visam, kas tiek rādīts jūsu ekrānā vai atskaņots jūsu ierīcē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kopīgošanas, ierakstīšanas vai apraides laikā lietotne var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloķējis jūsu IT administrators"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ierīces politika ir atspējojusi ekrānuzņēmumu izveidi"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Dzēst visu"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Pārvaldīt"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Vēsture"</string>
@@ -488,7 +513,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lai izmantotu, atbloķējiet ekrānu"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Ienesot jūsu kartes, radās problēma. Lūdzu, vēlāk mēģiniet vēlreiz."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Bloķēšanas ekrāna iestatījumi"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Kvadrātkoda skenēšana"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Darba profils"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Lidojuma režīms"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Nākamais signāls (<xliff:g id="WHEN">%1$s</xliff:g>) netiks atskaņots."</string>
@@ -727,6 +753,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vai pārslēgties atpakaļ uz operatoru <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilie dati netiks automātiski pārslēgti, pamatojoties uz pieejamību."</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nē, paldies"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Jā, pārslēgties"</string>
     <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>
@@ -767,6 +797,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Palielināt visu ekrānu"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Palielināt ekrāna daļu"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Pārslēgt"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Atļaut ritināšanu pa diagonāli"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Mainīt lielumu"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Mainīt palielinājuma veidu"</string>
@@ -785,12 +817,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Atsaukt"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Noņemta saīsne “{label}”}zero{Noņemtas # saīsnes}one{Noņemta # saīsne}other{Noņemtas # saīsnes}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Noņemt"</string>
     <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 +968,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Īslaicīgi izveidots savienojums"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Vājš savienojums"</string>
     <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>
@@ -992,4 +1029,12 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d. MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"hh:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <!-- no translation found for log_access_confirmation_title (4843557604739943395) -->
+    <skip />
+    <!-- no translation found for log_access_confirmation_allow (752147861593202968) -->
+    <skip />
+    <!-- no translation found for log_access_confirmation_deny (2389461495803585795) -->
+    <skip />
+    <!-- no translation found for log_access_confirmation_body (6883031912003112634) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 0d98dc6..cf97701 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Секогаш дозволувај од овој компјутер"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Дозволи"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Отстранувањето грешки на USB не е дозволено"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Корисникот што моментално е најавен на уредов не може да вклучи отстранување грешки на USB. За да ја користите функцијава, префрлете се на примарниот корисник."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Корисникот што моментално е најавен на уредов не може да вклучи отстранување грешки преку USB. За да ја користите функцијава, најавете се како администраторски корисник."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Дали сакате да го промените системскиот јазик на <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Побарана е промена на системскиот јазик од друг уред"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Промени го јазикот"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Секогаш дозволувај на оваа мрежа"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Дозволи"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Безжичното отстранување грешки не е дозволено"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Корисникот што моментално е најавен на уредов не може да вклучи безжично отстранување грешки. За да ја користите функцијава, префрлете се на примарниот корисник."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Корисникот што моментално е најавен на уредов не може да вклучи безжично отстранување грешки. За да ја користите функцијава, најавете се како администраторски корисник."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB-портата е оневозможена"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"За да го заштитиме уредот од течност или нечистотија, USB-портата е оневозможена и нема да ги открива додатоците.\n\nЌе ве известиме кога ќе биде во ред да ја користите USB-портата повторно."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB-портата е овозможена за откривање полначи и додатоци"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Гласовна помош"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Скенер на QR-кодови"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Отклучување"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Уредот е заклучен"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Скенирање лице"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Испрати"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Не се препознава ликот"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Користи отпечаток"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth е поврзан."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Процентот на батеријата е непознат."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Поврзано со <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Авионски режим."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN е вклучена."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Батерија <xliff:g id="NUMBER">%d</xliff:g> проценти."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батерија <xliff:g id="PERCENTAGE">%1$s</xliff:g> отсто, уште околу <xliff:g id="TIME">%2$s</xliff:g> според вашето користење"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батерија <xliff:g id="PERCENTAGE">%1$d</xliff:g> отсто, уште околу <xliff:g id="TIME">%2$s</xliff:g> според вашето користење"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Полнење на батеријата, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> отсто."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Видете ги сите известувања"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Овозможен е телепринтер."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ѕвонче на вибрации."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Микрофонот е достапен"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камерата е достапна"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Микрофонот и камерата се достапни"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Микрофонот е вклучен"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Микрофонот е исклучен"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Микрофонот е овозможен за сите апликации и услуги."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Пристапот до микрофонот е оневозможен за сите апликации и услуги. Може да овозможите пристап до микрофонот во „Поставки &gt; Приватност &gt; Микрофон“."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Пристапот до микрофонот е оневозможен за сите апликации и услуги. Ова може да го промените во „Поставки &gt; Приватност &gt; Микрофон“."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камерата е вклучена"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камерата е исклучена"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Камерата е овозможена за сите апликации и услуги."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Пристапот до камерата е оневозможен за сите апликации и услуги."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"За да го користите копчето за микрофон, овозможете пристап до микрофонот во „Поставки“."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Отворете ги поставките."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Друг уред"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Вклучи/исклучи преглед"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Нема да ве вознемируваат звуци и вибрации, освен од аларми, потсетници, настани и повикувачи што ќе ги наведете. Сѐ уште ќе слушате сѐ што ќе изберете да пуштите, како музика, видеа и игри."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Кога споделувате, снимате или емитувате апликација, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има пристап до сѐ што се прикажува или пушта на таа апликација. Затоа, бидете внимателни со лозинки, детали за плаќање, пораки или други чувствителни податоци."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Продолжи"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Споделете или снимете апликација"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Да се дозволи апликацијава да споделува или снима?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Кога споделувате, снимате или емитувате, апликацијава има пристап до сѐ што е видливо на вашиот екран или пуштено на вашиот уред. Затоа, бидете внимателни со лозинки, детали за плаќање, пораки или други чувствителни податоци."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Кога споделувате, снимате или емитувате апликација, апликацијава има пристап до сѐ што се прикажува или пушта на таа апликација. Затоа, бидете внимателни со лозинки, детали за плаќање, пораки или други чувствителни податоци."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано од IT-администраторот"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Снимањето на екранот е оневозможено со правила на уредот"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Избриши сѐ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Управувајте"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Историја"</string>
@@ -488,7 +511,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Отклучете за да користите"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Имаше проблем при преземањето на картичките. Обидете се повторно подоцна"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Поставки за заклучен екран"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Скенирајте QR-код"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"Скенер на QR-кодови"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Работен профил"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Авионски режим"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Нема да го слушнете следниот аларм <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +750,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Да се префрли на <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобилниот интернет нема автоматски да се префрли според достапноста"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Не, фала"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Да, префрли се"</string>
     <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>
@@ -767,6 +794,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Зголемете го целиот екран"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Зголемувајте дел од екранот"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Префрли"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Дозволете дијагонално лизгање"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Промени големина"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Променете го типот на зголемување"</string>
@@ -785,12 +814,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Врати"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Отстранета е {label} кратенка}one{Отстранети се # кратенка}other{Отстранети се # кратенки}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Отстрани"</string>
     <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 +965,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Привремено поврзано"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Слаба интернет-врска"</string>
     <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>
@@ -992,4 +1026,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Да се дозволи <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> да пристапува до сите дневници за евиденција на уредот?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Дозволи еднократен пристап"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Не дозволувај"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Дневниците за евиденција на уредот снимаат што се случува на вашиот уред. Апликациите може да ги користат овие дневници за евиденција за да наоѓаат и поправаат проблеми.\n\nНекои дневници за евиденција може да содржат чувствителни податоци, па затоа дозволете им пристап до сите дневници за евиденција на уредот само на апликациите во кои имате доверба. \n\nАко не ѝ дозволите на апликацијава да пристапува до сите дневници за евиденција на уредот, таа сепак ќе може да пристапува до сопствените дневници за евиденција. Производителот на вашиот уред можеби сепак ќе може да пристапува до некои дневници за евиденција или податоци на уредот."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 868d759..0c72b9a 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"ഈ കമ്പ്യൂട്ടറിൽ നിന്ന് എല്ലായ്പ്പോഴും അനുവദിക്കുക"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"അനുവദിക്കുക"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB ഡീബഗ്ഗിംഗ് അനുവദനീയമല്ല"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"ഉപകരണത്തിൽ ഇപ്പോൾ സൈൻ ഇൻ ചെയ്‌തിരിക്കുന്ന ഉപയോക്താവിന് USB ഡീബഗ്ഗിംഗ് ഓണാക്കാനാകില്ല. ഈ ഫീച്ചർ ഉപയോഗിക്കാൻ പ്രാഥമിക ഉപയോക്താവിലേക്ക് മാറുക."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"ഈ ഉപകരണത്തിൽ ഇപ്പോൾ സൈൻ ഇൻ ചെയ്‌തിരിക്കുന്ന ഉപയോക്താവിന് USB ഡീബഗ് ചെയ്യൽ ഓണാക്കാനാകില്ല. ഈ ഫീച്ചർ ഉപയോഗിക്കാൻ അഡ്‌മിൻ ഉപയോക്താവിലേക്ക് മാറുക."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"സിസ്റ്റത്തിന്റെ ഭാഷ <xliff:g id="LANGUAGE">%1$s</xliff:g> ആക്കണോ?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"സിസ്റ്റത്തിന്റെ ഭാഷ മാറ്റാൻ മറ്റൊരു ഉപകരണം അഭ്യർത്ഥിച്ചു"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"ഭാഷ മാറ്റുക"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"ഈ നെറ്റ്‌വർക്കിൽ എപ്പോഴും അനുവദിക്കുക"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"അനുവദിക്കൂ"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"വയർലെസ് ഡീബഗ്ഗിംഗ് അനുവദനീയമല്ല"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"ഉപകരണത്തിൽ ഇപ്പോൾ സൈൻ ഇൻ ചെയ്‌തിരിക്കുന്ന ഉപയോക്താവിന് വയർലെസ് ഡീബഗ്ഗിംഗ് ഓണാക്കാനാകില്ല. ഈ ഫീച്ചർ ഉപയോഗിക്കാൻ പ്രാഥമിക ഉപയോക്താവിലേക്ക് മാറുക."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"ഈ ഉപകരണത്തിൽ ഇപ്പോൾ സൈൻ ഇൻ ചെയ്‌തിരിക്കുന്ന ഉപയോക്താവിന് വയർലെസ് ഡീബഗ്ഗിംഗ് ഓണാക്കാനാകില്ല. ഈ ഫീച്ചർ ഉപയോഗിക്കാൻ അഡ്‌മിൻ ഉപയോക്താവിലേക്ക് മാറുക."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB പോർട്ട് പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"ദ്രാവകത്തിൽ നിന്നോ പൊടിയിൽ നിന്നോ നിങ്ങളുടെ ഉപകരണത്തെ പരിരക്ഷിക്കാനായി USB പോർട്ട് പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നതിനാൽ അത് ആക്‌സസറികളൊന്നും തിരിച്ചറിയില്ല.\n\n USB പോർട്ട് വീണ്ടും ഉപയോഗിക്കാനാകുമ്പോൾ നിങ്ങളെ അറിയിക്കും."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"ആക്‌സസറികളും ചാർജറുകളും കണ്ടെത്താൻ USB പോർട്ട് പ്രവർത്തനക്ഷമമാക്കുക"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"വോയ്‌സ് സഹായം"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR കോഡ് സ്കാനർ"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"അണ്‍ലോക്ക് ചെയ്യുക"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"ഉപകരണം ലോക്ക് ചെയ്തു"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"മുഖം സ്കാൻ ചെയ്യുന്നു"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"അയയ്ക്കുക"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"മുഖം തിരിച്ചറിയാനാകുന്നില്ല"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"പകരം ഫിംഗർപ്രിന്റ് ഉപയോഗിക്കൂ"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ബ്ലൂടൂത്ത് കണക്‌റ്റുചെയ്തു."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ബാറ്ററി ശതമാനം അജ്ഞാതമാണ്."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> എന്നതിലേക്ക് കണക്‌റ്റുചെയ്‌തു."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"ഫ്ലൈറ്റ് മോഡ്."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ഓണാണ്."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"ബാറ്ററി <xliff:g id="NUMBER">%d</xliff:g> ശതമാനം."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ബാറ്ററി <xliff:g id="PERCENTAGE">%1$s</xliff:g> ശതമാനം, നിങ്ങളുടെ ഉപയോഗത്തിൻ്റെ അടിസ്ഥാനത്തിൽ ഏകദേശം <xliff:g id="TIME">%2$s</xliff:g> സമയം കൂടി ശേഷിക്കുന്നു"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ബാറ്ററി <xliff:g id="PERCENTAGE">%1$d</xliff:g> ശതമാനം, നിങ്ങളുടെ ഉപയോഗത്തിൻ്റെ അടിസ്ഥാനത്തിൽ ഏകദേശം <xliff:g id="TIME">%2$s</xliff:g> സമയം കൂടി ശേഷിക്കുന്നു"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ബാറ്ററി ചാർജ് ചെയ്യുന്നു, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"എല്ലാ അറിയിപ്പുകളും കാണുക"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter പ്രവർത്തനക്ഷമമാണ്."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"റിംഗർ വൈബ്രേറ്റ് ചെയ്യുന്നു."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"മൈക്രോഫോൺ ലഭ്യമാണ്"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ക്യാമറ ലഭ്യമാണ്"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"മൈക്രോഫോണും ക്യാമറയും ലഭ്യമാണ്"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"മൈക്രോഫോൺ ഓണാക്കി"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"മൈക്രോഫോൺ ഓഫാക്കി"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"എല്ലാ ആപ്പുകൾക്കും സേവനങ്ങൾക്കും മൈക്രോഫോൺ പ്രവർത്തനക്ഷമമാക്കിയിരിക്കുന്നു."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"എല്ലാ ആപ്പുകൾക്കും സേവനങ്ങൾക്കും മൈക്രോഫോൺ ആക്സസ് പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു. ക്രമീകരണം &gt; സ്വകാര്യത &gt; മൈക്രോഫോൺ എന്നതിൽ നിങ്ങൾക്ക് മൈക്രോഫോൺ ആക്സസ് പ്രവർത്തനക്ഷമമാക്കാം."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"എല്ലാ ആപ്പുകൾക്കും സേവനങ്ങൾക്കും മൈക്രോഫോൺ ആക്സസ് പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു. ക്രമീകരണം &gt; സ്വകാര്യത &gt; മൈക്രോഫോൺ എന്നതിൽ ഇത് നിങ്ങൾക്ക് മാറ്റാൻ കഴിയും."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"ക്യാമറ ഓണാക്കി"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ക്യാമറ ഓഫാക്കി"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"എല്ലാ ആപ്പുകൾക്കും സേവനങ്ങൾക്കും ക്യാമറ പ്രവർത്തനക്ഷമമാക്കിയിരിക്കുന്നു."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"എല്ലാ ആപ്പുകൾക്കും സേവനങ്ങൾക്കും ക്യാമറാ ആക്സസ് പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"മൈക്രോഫോൺ ബട്ടൺ ഉപയോഗിക്കുന്നതിന്, ക്രമീകരണത്തിൽ മൈക്രോഫോൺ ആക്സസ് പ്രവർത്തനക്ഷമമാക്കുക."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ക്രമീകരണം തുറക്കുക."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"മറ്റ് ഉപകരണം"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"അവലോകനം മാറ്റുക"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"നിങ്ങൾ സജ്ജീകരിച്ച അലാറങ്ങൾ, റിമൈൻഡറുകൾ, ഇവന്റുകൾ, കോളർമാർ എന്നിവയിൽ നിന്നുള്ള ശബ്‌ദങ്ങളും വൈബ്രേഷനുകളുമൊഴികെ മറ്റൊന്നും നിങ്ങളെ ശല്യപ്പെടുത്തുകയില്ല. സംഗീതം, വീഡിയോകൾ, ഗെയിമുകൾ എന്നിവയുൾപ്പെടെ പ്ലേ ചെയ്യുന്നതെന്തും നിങ്ങൾക്ക് ‌തുടർന്നും കേൾക്കാൻ കഴിയും."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ഒരു ആപ്പ് പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിന് ആപ്പിൽ കാണിക്കുന്ന അല്ലെങ്കിൽ പ്ലേ ചെയ്യുന്ന എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"തുടരുക"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ഒരു ആപ്പ് പങ്കിടുക അല്ലെങ്കിൽ റെക്കോർഡ് ചെയ്യുക"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"പങ്കിടാനോ റെക്കോർഡ് ചെയ്യാനോ ഈ ആപ്പിനെ അനുവദിക്കണോ?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, ഈ ആപ്പിന് നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"ഒരു ആപ്പ് പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, ഈ ആപ്പിന് ആപ്പിൽ കാണിക്കുന്ന അല്ലെങ്കിൽ പ്ലേ ചെയ്യുന്ന എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"നിങ്ങളുടെ ഐടി അഡ്‌മിൻ ബ്ലോക്ക് ചെയ്‌തു"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ഉപകരണ നയം, സ്ക്രീൻ ക്യാപ്‌ചർ ചെയ്യൽ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്‌ക്കുക"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"മാനേജ് ചെയ്യുക"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ചരിത്രം"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ഉപയോഗിക്കാൻ അൺലോക്ക് ചെയ്യുക"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"നിങ്ങളുടെ കാർഡുകൾ ലഭ്യമാക്കുന്നതിൽ ഒരു പ്രശ്‌നമുണ്ടായി, പിന്നീട് വീണ്ടും ശ്രമിക്കുക"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ലോക്ക് സ്ക്രീൻ ക്രമീകരണം"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR കോഡ് സ്‌കാൻ ചെയ്യുക"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"ഫ്ലൈറ്റ് മോഡ്"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>-നുള്ള നിങ്ങളുടെ അടുത്ത അലാറം കേൾക്കില്ല"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> എന്നതിലേക്ക് വീണ്ടും മാറണോ?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ലഭ്യതയുടെ അടിസ്ഥാനത്തിൽ, മൊബൈൽ ഡാറ്റ സ്വയമേവ മാറില്ല"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"വേണ്ട"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ഉവ്വ്, മാറുക"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"സ്ക്രീൻ പൂർണ്ണമായും മാഗ്നിഫൈ ചെയ്യുക"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"സ്‌ക്രീനിന്റെ ഭാഗം മാഗ്നിഫൈ ചെയ്യുക"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"മാറുക"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ഡയഗണൽ സ്‌ക്രോളിംഗ് അനുവദിക്കുക"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"വലുപ്പം മാറ്റുക"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"മാഗ്നിഫിക്കേഷൻ തരം മാറ്റുക"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"പഴയപടിയാക്കുക"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} കുറുക്കുവഴി നീക്കം ചെയ്‌തു}other{# കുറുക്കുവഴികൾ നീക്കം ചെയ്‌തു}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"നീക്കം ചെയ്യുക"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"താൽക്കാലികമായി കണക്റ്റ് ചെയ്തിരിക്കുന്നു"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"ദുർബലമായ കണക്ഷൻ"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"എല്ലാ ഉപകരണ ലോഗുകളും ആക്‌സസ് ചെയ്യാൻ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> എന്നതിനെ അനുവദിക്കണോ?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"ഒറ്റത്തവണ ആക്‌സസ് അനുവദിക്കുക"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"അനുവദിക്കരുത്"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"നിങ്ങളുടെ ഉപകരണത്തിൽ സംഭവിക്കുന്ന കാര്യങ്ങൾ ഉപകരണ ലോഗുകൾ റെക്കോർഡ് ചെയ്യുന്നു. പ്രശ്‌നങ്ങൾ കണ്ടെത്തി പരിഹരിക്കുന്നതിന് ആപ്പുകൾക്ക് ഈ ലോഗുകൾ ഉപയോഗിക്കാൻ കഴിയും.\n\nചില ലോഗുകളിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട വിവരങ്ങൾ അടങ്ങിയിരിക്കാൻ സാധ്യതയുള്ളതിനാൽ, എല്ലാ ഉപകരണ ലോഗുകളും ആക്സസ് ചെയ്യാനുള്ള അനുമതി നിങ്ങൾക്ക് വിശ്വാസമുള്ള ആപ്പുകൾക്ക് മാത്രം നൽകുക. \n\nഎല്ലാ ഉപകരണ ലോഗുകളും ആക്‌സസ് ചെയ്യാനുള്ള അനുവാദം നൽകിയില്ലെങ്കിലും, ഈ ആപ്പിന് അതിന്റെ സ്വന്തം ലോഗുകൾ ആക്‌സസ് ചെയ്യാനാകും. നിങ്ങളുടെ ഉപകരണ നിർമ്മാതാവിന് തുടർന്നും നിങ്ങളുടെ ഉപകരണത്തിലെ ചില ലോഗുകളോ വിവരങ്ങളോ ആക്‌സസ് ചെയ്യാനായേക്കും."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 25929e6..a00b0f4 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Энэ компьютерээс орохыг байнга зөвшөөрөх"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Зөвшөөрөх"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB алдаа засалт хийх боломжгүй"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Энэ төхөөрөмжид нэвтэрсэн хэрэглэгч USB дебаг хийх онцлогийг асаах боломжгүй байна. Энэ онцлогийг ашиглахын тулд үндсэн хэрэглэгч рүү сэлгэнэ үү."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Энэ төхөөрөмжид одоогоор нэвтэрсэн хэрэглэгч USB дебагийг асаах боломжгүй. Энэ онцлогийг ашиглахын тулд админ хэрэглэгч рүү сэлгэнэ үү."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Та системийн хэлийг <xliff:g id="LANGUAGE">%1$s</xliff:g> хэл болгож өөрчлөхийг хүсэж байна уу?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Өөр төхөөрөмжөөс системийн хэлийг өөрчлөх хүсэлт тавьсан"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Хэл солих"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Энэ сүлжээн дээр үргэлж зөвшөөрөх"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Зөвшөөрөх"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Wireless debugging-г зөвшөөрөөгүй байна"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Энэ төхөөрөмжид одоогоор нэвтэрсэн байгаа хэрэглэгч wireless debugging-г асаах боломжгүй. Энэ онцлогийг ашиглахын тулд үндсэн хэрэглэгч рүү сэлгэнэ үү."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Энэ төхөөрөмжид одоогоор нэвтэрсэн хэрэглэгч wireless debugging-г асаах боломжгүй. Энэ онцлогийг ашиглахын тулд админ хэрэглэгч рүү сэлгэнэ үү."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB портыг идэвхгүй болгосон"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Таны төхөөрөмжийг шингэн зүйл эсвэл бохирдлоос хамгаалахын тулд USB портыг идэвхгүй болгосон бөгөөд энэ нь ямар ч дагалдах хэрэгслийг илрүүлэхгүй.\n\nТанд USB портыг дахин ашиглахад аюулгүй болох үед мэдэгдэх болно."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Цэнэглэгч болон нэмэлт хэрэгслийг илрүүлэхийн тулд USB портыг идэвхжүүлсэн"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Дуут туслах"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR код сканнер"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Тайлах"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Төхөөрөмжийг түгжсэн"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Скан хийх нүүр царай"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Илгээх"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Царайг танихгүй байна"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Оронд нь хурууны хээ ашиглах"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth холбогдсон."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарейн хувь тодорхойгүй байна."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>-тай холбогдсон."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Нислэгийн горим"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN асаалттай байна."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Батарей <xliff:g id="NUMBER">%d</xliff:g> хувьтай."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батарей <xliff:g id="PERCENTAGE">%1$s</xliff:g> хувьтай байна. Таны хэрэглээнд тулгуурлан ойролцоогоор <xliff:g id="TIME">%2$s</xliff:g> үлдсэн"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батарей <xliff:g id="PERCENTAGE">%1$d</xliff:g> хувьтай байна. Таны хэрэглээнд тулгуурлан ойролцоогоор <xliff:g id="TIME">%2$s</xliff:g> үлдсэн"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Батарейг цэнэглэж байна, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Бүх мэдэгдлийг харах"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter идэвхтэй болов."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Хонхны чичиргээ."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Микрофон боломжтой байна"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камер боломжтой байна"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Микрофон болон камер боломжтой байна"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Микрофоныг асаасан"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Микрофоныг унтраасан"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Микрофоныг бүх програм, үйлчилгээнд идэвхжүүлсэн."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Бүх програм, үйлчилгээнд микрофоны хандалтыг идэвхгүй болгосон. Та микрофоны хандалтыг тохиргоо идэвхжүүлж байна &gt; Нууцлал &gt; Микрофон идэвхжүүлж болно."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Бүх програм, үйлчилгээнд микрофоны хандалтыг идэвхгүй болгосон. Та үүнийг Тохиргоо &gt; Нууцлал &gt; Микрофон идэвхжүүлж болно."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камерыг асаасан"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камерыг унтраасан"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Камерыг бүх програм, үйлчилгээнд идэвхжүүлсэн."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Бүх апп болон үйлчилгээнд камерын хандалтыг идэвхгүй болгосон."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Микрофоны товчлуурыг ашиглахын тулд \"Тохиргоо\" хэсэгт микрофоны хандалтыг идэвхжүүлнэ үү."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Тохиргоог нээнэ үү."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Бусад төхөөрөмж"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Тоймыг асаах/унтраах"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Танд сэрүүлэг, сануулга, арга хэмжээ, таны сонгосон дуудлага илгээгчээс бусад дуу, чичиргээ саад болохгүй. Та хөгжим, видео, тоглоом зэрэг тоглуулахыг хүссэн бүх зүйлээ сонсох боломжтой хэвээр байна."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Таныг хуваалцаж, бичиж эсвэл дамжуулж байх үед <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> нь тухайн апп дээр харуулсан эсвэл тоглуулсан аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж эсвэл бусад эмзэг мэдээлэлд болгоомжтой хандаарай."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Үргэлжлүүлэх"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Хуваалцах эсвэл бичих апп"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Энэ аппад хуваалцах эсвэл бичихийг зөвшөөрөх үү?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Таныг хуваалцаж, бичиж эсвэл дамжуулж байх үед энэ апп нь таны дэлгэц дээр харагдаж буй аливаа зүйл эсвэл төхөөрөмж дээр тань тоглуулж буй зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж эсвэл бусад эмзэг мэдээлэлд болгоомжтой хандаарай."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Таныг хуваалцаж, бичиж эсвэл дамжуулж байх үед энэ апп нь тухайн апп дээр харуулж эсвэл тоглуулж буй аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж эсвэл бусад эмзэг мэдээлэлд болгоомжтой хандаарай."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Таны IT админ блоклосон"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Төхөөрөмжийн бодлогоор дэлгэцийн зураг авахыг идэвхгүй болгосон"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Бүгдийг арилгах"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Удирдах"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Түүх"</string>
@@ -488,7 +511,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ашиглахын тулд түгжээг тайлах"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Таны картыг авахад асуудал гарлаа. Дараа дахин оролдоно уу"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Түгжигдсэн дэлгэцийн тохиргоо"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR код скан хийх"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR код сканнер"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Ажлын профайл"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Нислэгийн горим"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>-т та дараагийн сэрүүлгээ сонсохгүй"</string>
@@ -727,6 +750,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> руу буцаан сэлгэх үү?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобайл дата нь боломжтой эсэхэд тулгуурлан автоматаар сэлгэхгүй"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Үгүй, баярлалаа"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Тийм, сэлгэе"</string>
     <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>
@@ -767,6 +794,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Бүтэн дэлгэцийг томруулах"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Дэлгэцийн нэг хэсгийг томруулах"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Сэлгэх"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Хөндлөн гүйлгэхийг зөвшөөрнө үү"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Хэмжээг өөрчлөх"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Томруулах төрлийг өөрчлөх"</string>
@@ -785,12 +814,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Болих"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} товчлолыг хассан}other{# товчлолыг хассан}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Хасах"</string>
     <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 +965,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Түр зуур холбогдсон"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Холболт сул байна"</string>
     <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>
@@ -992,4 +1026,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"MMM d EEE"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>-д төхөөрөмжийн бүх логт хандахыг зөвшөөрөх үү?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Нэг удаагийн хандалтыг зөвшөөрөх"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Бүү зөвшөөр"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Төхөөрөмжийн лог нь таны төхөөрөмж дээр юу болж байгааг бичдэг. Аппууд эдгээр логийг асуудлыг олох болон засахад ашиглах боломжтой.\n\nЗарим лог эмзэг мэдээлэл агуулж байж магадгүй тул та зөвхөн итгэдэг аппууддаа төхөөрөмжийн бүх логт хандахыг зөвшөөрнө үү. \n\nХэрэв та энэ аппад төхөөрөмжийн бүх логт хандахыг зөвшөөрөхгүй бол энэ нь өөрийн логт хандах боломжтой хэвээр байх болно. Tаны төхөөрөмжийн үйлдвэрлэгч таны төхөөрөмж дээрх зарим лог эсвэл мэдээлэлд хандах боломжтой хэвээр байж магадгүй."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index c6e99a7..e6311d6 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"या संगणकावरून नेहमी अनुमती द्या"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"अनुमती द्या"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB डीबग करण्‍यास अनुमती नाही"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"सध्‍या या डीव्हाइसमध्‍ये साइन इन केलेला वापरकर्ता USB डीबग करणे सुरू करू शकत नाही. हे वैशिष्‍ट्य वापरण्‍यासाठी, प्राथमिक वापरकर्त्‍यावर स्विच करा."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"सध्‍या या डिव्हाइसमध्‍ये साइन इन केलेला वापरकर्ता USB डीबगिंग सुरू करू शकत नाही. हे वैशिष्‍ट्य वापरण्‍यासाठी ॲडमिन वापरकर्त्यावर स्विच करा."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"तुम्हाला सिस्टीमची भाषा <xliff:g id="LANGUAGE">%1$s</xliff:g> वर बदलायची आहे का?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"दुसऱ्या डिव्हाइसद्वारे सिस्टीमची भाषा बदलण्याची विनंती केली गेली"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"भाषा बदला"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"या नेटवर्कवर नेहमी अनुमती द्या"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"अनुमती द्या"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"वायरलेस डीबगिंग करण्याला अनुमती नाही"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"सध्‍या या डिव्हाइसमध्‍ये साइन इन केलेला वापरकर्ता वायरलेस डीबगिंग सुरू करू शकत नाही. हे वैशिष्‍ट्य वापरण्‍यासाठी प्राथमिक वापरकर्त्‍यावर स्विच करा."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"सध्‍या या डिव्हाइसमध्‍ये साइन इन केलेला वापरकर्ता वायरलेस डीबगिंग सुरू करू शकत नाही. हे वैशिष्‍ट्य वापरण्‍यासाठी ॲडमिन वापरकर्त्‍यावर स्विच करा."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB पोर्ट बंद करा"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"तुमच्या डिव्हाइसला ओलावा किंवा धूळीपासून संरक्षित करण्यासाठी, USB पोर्ट बंद आहे आणि अ‍ॅक्सेसरी डिटेक्ट करणार नाही. \n\n तुम्हाला USB पोर्ट पुन्हा वापरणे ठीक आहे तेव्हा सूचित केले जाईल."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"चार्जर आणि अ‍ॅक्सेसरी शोधण्यासाठी USB पोर्ट सुरू केलेले आहे"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"व्हॉइस सहाय्य"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"वॉलेट"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR कोड स्कॅनर"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"अनलॉक करा"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"डिव्हाइस लॉक केले"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"चेहरा स्कॅन करत आहे"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"पाठवा"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"चेहरा ओळखू शकत नाही"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"त्याऐवजी फिंगरप्रिंट वापरा"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लूटूथ कनेक्‍ट केले."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"बॅटरीच्या चार्जिंगची टक्केवारी माहित नाही."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> शी कनेक्‍ट केले."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"विमान मोड."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN सुरू."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"बॅटरी <xliff:g id="NUMBER">%d</xliff:g> टक्के."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"बॅटरी <xliff:g id="PERCENTAGE">%1$s</xliff:g> टक्के, तुमच्या वापराच्या आधारावर सुमारे <xliff:g id="TIME">%2$s</xliff:g> शिल्लक आहे"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"बॅटरी <xliff:g id="PERCENTAGE">%1$d</xliff:g> टक्के, तुमच्या वापराच्या आधारावर सुमारे <xliff:g id="TIME">%2$s</xliff:g> शिल्लक आहे"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"बॅटरी चार्ज होत आहे, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> टक्के."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"सर्व सूचना पहा"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter सक्षम केले."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"रिंगर व्हायब्रेट."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"मायक्रोफोन उपलब्ध आहे"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"कॅमेरा उपलब्ध आहे"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"मायक्रोफोन आणि कॅमेरा या गोष्टी उपलब्ध आहेत"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"मायक्रोफोन सुरू केला"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"मायक्रोफोन बंद केला"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"सर्व ॲप्स आणि सेवांसाठी मायक्रोफोन सुरू केला आहे."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"सर्व ॲप्स आणि सेवांसाठी मायक्रोफोन अ‍ॅक्सेस बंद केला आहे. तुम्ही मायक्रोफोन अ‍ॅक्सेस सेटिंग्ज &gt; गोपनीयता &gt; मायक्रोफोन मधून सुरू करू शकता."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"सर्व ॲप्स आणि सेवांसाठी मायक्रोफोन अ‍ॅक्सेस बंद केला आहे. तुम्ही हे सेटिंग्ज &gt; गोपनीयता &gt; मायक्रोफोन मधून बदलू शकता ."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"कॅमेरा सुरू केला आहे"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"कॅमेरा बंद केला आहे"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"सर्व ॲप्स आणि सेवांसाठी कॅमेरा सुरू केला आहे."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"सर्व ॲप्स आणि सेवांसाठी कॅमेरा अ‍ॅक्सेस बंद केला आहे."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"मायक्रोफोन बटण वापरण्यासाठी, सेटिंग्जमधून मायक्रोफोन अ‍ॅक्सेस सुरू करा."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"सेटिंग्ज उघडा."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"इतर डिव्हाइस"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"अवलोकन टॉगल करा."</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"अलार्म, रिमाइंडर, इव्‍हेंट आणि तुम्ही निश्चित केलेल्या कॉलर व्यतिरिक्त तुम्हाला कोणत्याही आवाज आणि कंपनांचा व्यत्त्यय आणला जाणार नाही. तरीही तुम्ही प्ले करायचे ठरवलेले कोणतेही संगीत, व्हिडिओ आणि गेमचे आवाज ऐकू शकतात."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तुम्ही अ‍ॅप शेअर, रेकॉर्ड किंवा कास्ट करत असताना, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला त्या अ‍ॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"पुढे सुरू ठेवा"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"अ‍ॅप शेअर किंवा रेकॉर्ड करा"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"या अ‍ॅपला शेअर किंवा रेकॉर्ड करण्याची अनुमती द्यायची आहे का?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"तुम्ही शेअर, रेकॉर्ड किंवा कास्ट करत असता, तेव्हा या अ‍ॅपला तुमच्या स्क्रीनवर दाखवलेल्या अथवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"तुम्ही अ‍ॅप शेअर, रेकॉर्ड किंवा कास्ट करत असताना, या अ‍ॅपला त्या अ‍ॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज अथवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"तुमच्या आयटी ॲडमिनने ब्लॉक केले आहे"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिव्हाइस धोरणाने स्‍क्रीन कॅप्‍चर करणे बंद केले आहे"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थापित करा"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"वापरण्यासाठी अनलॉक करा"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"तुमची कार्ड मिळवताना समस्या आली, कृपया नंतर पुन्हा प्रयत्न करा"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"लॉक स्क्रीन सेटिंग्ज"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR कोड स्कॅन करा"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"कार्य प्रोफाईल"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"विमान मोड"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"तुम्ही तुमचा <xliff:g id="WHEN">%1$s</xliff:g> वाजता होणारा पुढील अलार्म ऐकणार नाही"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> वर परत स्विच करायचे आहे का?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"उपलब्धतेच्या आधारावर मोबाइल डेटा आपोआप स्विच होणार नाही"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"नाही, नको"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"होय, स्विच करा"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"फुल स्क्रीन मॅग्निफाय करा"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"स्क्रीनचा काही भाग मॅग्निफाय करा"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"स्विच करा"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"तिरपे स्क्रोल करण्याची अनुमती द्या"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"आकार बदला"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"मॅग्निफिकेशनचा प्रकार बदला"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"पहिल्यासारखे करा"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} शॉर्टकट काढून टाकला आहे}other{# शॉर्टकट काढून टाकले आहेत}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"काढून टाका"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"तात्पुरते कनेक्ट केलेले"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"खराब कनेक्शन"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ला सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती द्यायची आहे का?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"एक वेळ अ‍ॅक्सेसची अनुमती द्या"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"अनुमती देऊ नका"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"तुमच्या डिव्हाइसवर काय होते ते डिव्हाइस लॉग रेकॉर्ड करते. समस्या शोधण्यासाठी आणि त्यांचे निराकरण करण्यासाठी ॲप्स हे लॉग वापरू शकतात.\n\nकाही लॉगमध्ये संवेदनशील माहिती असू शकते, त्यामुळे फक्त तुमचा विश्वास असलेल्या ॲप्सना सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती द्या. \n\nतुम्ही या ॲपला सर्व डिव्हाइस लॉग अ‍ॅक्सेस करण्याची अनुमती न दिल्यास, ते तरीही त्याचा स्वतःचा लॉग अ‍ॅक्सेस करू शकते. तुमच्या डिव्हाइसचा उत्पादक तरीही काही लॉग किंवा तुमच्या डिव्हाइसवरील माहिती अ‍ॅक्सेस करू शकतो."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 99349b1..b76c336 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Sentiasa benarkan komputer ini"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Benarkan"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Penyahpepijatan USB tidak dibenarkan"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Pengguna yang log masuk ke peranti ini pada masa ini tidak boleh menghidupkan penyahpepijatan USB. Untuk menggunakan ciri ini, tukar kepada pengguna utama."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Pengguna yang log masuk ke peranti ini pada masa ini tidak boleh menghidupkan penyahpepijatan USB. Untuk menggunakan ciri ini, tukar kepada pengguna pentadbir."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Adakah anda ingin menukar bahasa sistem kepada <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Permintaan pertukaran bahasa sistem oleh peranti lain"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Tukar bahasa"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Sentiasa benarkan pada rangkaian ini"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Benarkan"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Penyahpepijatan wayarles tidak dibenarkan"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Pengguna yang telah log masuk ke peranti ini pada masa ini tidak boleh menghidupkan penyahpepijatan wayarles. Untuk menggunakan ciri ini, tukar kepada pengguna utama."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Pengguna yang log masuk ke peranti ini pada masa ini tidak boleh menghidupkan penyahpepijatan wayarles. Untuk menggunakan ciri ini, tukar kepada pengguna pentadbir."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Port USB dilumpuhkan"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Untuk melindungi peranti anda daripada cecair atau serpihan, port USB dilumpuhkan dan tidak akan mengesan sebarang aksesori.\n\nAnda akan dimaklumi apabila selamat untuk menggunakan port USB lagi."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Port USB didayakan untuk mengesan pengecas dan aksesori"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Bantuan Suara"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Pengimbas Kod QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Buka kunci"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Peranti dikunci"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Mengimbas wajah"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Hantar"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Tak dapat mengecam wajah"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gunakan cap jari"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth disambungkan."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Peratusan kuasa bateri tidak diketahui."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Disambungkan kepada <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Mod pesawat"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN dihidupkan."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Bateri <xliff:g id="NUMBER">%d</xliff:g> peratus."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateri <xliff:g id="PERCENTAGE">%1$s</xliff:g> peratus, tinggal kira-kira <xliff:g id="TIME">%2$s</xliff:g> lagi berdasarkan penggunaan anda"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateri <xliff:g id="PERCENTAGE">%1$d</xliff:g> peratus, tinggal kira-kira <xliff:g id="TIME">%2$s</xliff:g> lagi berdasarkan penggunaan anda"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Bateri mengecas, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> peratus."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Lihat semua pemberitahuan"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Mesin Teletaip didayakan."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Pendering bergetar."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Urus pengguna"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon tersedia"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera tersedia"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon dan kamera tersedia"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon dihidupkan"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon dimatikan"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon didayakan untuk semua apl dan perkhidmatan."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Akses mikrofon dilumpuhkan untuk semua apl dan perkhidmatan. Anda boleh mendayakan akses mikrofon dalam Tetapan &gt; Privasi &gt; Mikrofon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Akses mikrofon dilumpuhkan untuk semua apl dan perkhidmatan. Anda boleh menukar tetapan ini dalam Tetapan &gt; Privasi &gt; Mikrofon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera dihidupkan"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera dimatikan"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera didayakan untuk semua apl dan perkhidmatan."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Akses kamera dilumpuhkan untuk semua apl dan perkhidmatan."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Untuk menggunakan butang mikrofon, dayakan akses mikrofon dalam Tetapan."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Buka tetapan."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Peranti lain"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Togol Ikhtisar"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Anda tidak akan diganggu oleh bunyi dan getaran, kecuali daripada penggera, peringatan, acara dan pemanggil yang anda tetapkan. Anda masih mendengar item lain yang anda pilih untuk dimainkan termasuk muzik, video dan permainan."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Apabila anda berkongsi, merakam atau menghantar apl, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> mempunyai akses kepada apa-apa yang dipaparkan atau dimainkan pada apl tersebut. Jadi berhati-hati dengan kata laluan, butiran pembayaran, mesej atau maklumat sensitif lain."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Teruskan"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Kongsi atau rakam apl"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Benarkan apl ini berkongsi atau merakam?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Apabila anda berkongsi, merakam atau menghantar, apl ini mempunyai akses kepada apa-apa yang boleh dilihat pada skrin anda atau dimainkan pada peranti anda. Jadi berhati-hati dengan kata laluan, butiran pembayaran, mesej atau maklumat sensitif lain."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Apabila anda berkongsi, merakam atau menghantar apl, apl ini mempunyai akses kepada apa-apa yang dipaparkan atau dimainkan pada apl tersebut. Jadi berhati-hati dengan kata laluan, butiran pembayaran, mesej atau maklumat sensitif lain."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Disekat oleh pentadbir IT anda"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Tangkapan skrin dilumpuhkan oleh dasar peranti"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Kosongkan semua"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Urus"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Sejarah"</string>
@@ -488,7 +511,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Buka kunci untuk menggunakan"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Terdapat masalah sewaktu mendapatkan kad anda. Sila cuba sebentar lagi"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Tetapan skrin kunci"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Imbas kod QR"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"Pengimbas kod QR"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Profil kerja"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Mod pesawat"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Anda tidak akan mendengar penggera yang seterusnya <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +750,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Tukar kembali kepada <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Data mudah alih tidak akan ditukar secara automatik berdasarkan ketersediaan"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Tidak perlu"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ya, tukar"</string>
     <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>
@@ -767,6 +794,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Besarkan skrin penuh"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Besarkan sebahagian skrin"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Tukar"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Benarkan penatalan pepenjuru"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ubah saiz"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Tukar jenis pembesaran"</string>
@@ -785,12 +814,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Buat asal"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} pintasan dialih keluar}other{# pintasan dialih keluar}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Alih keluar"</string>
     <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 +965,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Disambungkan buat sementara waktu"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Sambungan lemah"</string>
     <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>
@@ -992,4 +1026,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Benarkan <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> mengakses semua log peranti?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Benarkan akses satu kali"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Jangan benarkan"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Log peranti merekodkan perkara yang berlaku pada peranti anda. Apl boleh menggunakan log ini untuk menemukan dan membetulkan masalah.\n\nSesetengah log mungkin mengandungi maklumat sensitif, jadi hanya benarkan apl yang anda percaya untuk mengakses semua log peranti. \n\nJika anda tidak membenarkan apl ini mengakses semua log peranti, apl ini masih boleh mengakses log sendiri. Pengilang peranti anda mungkin masih dapat mengakses sesetengah log atau maklumat pada peranti anda."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index f30846a..2a00807 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"ဒီကွန်ပျူတာမှ အမြဲခွင့်ပြုရန်"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"ခွင့်ပြုရန်"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB အမှားပြင်ဆင်ခြင်း ခွင့်မပြုပါ"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"ဤစက်ပစ္စည်းသို့ လက်ရှိဝင်ရောက်ထားသည့် အသုံးပြုသူသည် USB အမှားပြင်ဆင်ခြင်းကို ဖွင့်၍မရပါ။ ဤဝန်ဆောင်မှုကို အသုံးပြုရန် အဓိကအသုံးပြုသူအဖြစ်သို့ ပြောင်းပါ။"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"ဤစက်သို့ လက်ရှိလက်မှတ်ထိုးဝင်ထားသူသည် USB အမှားရှာပြင်ခြင်းကို ဖွင့်၍မရပါ။ ဤဝန်ဆောင်မှု အသုံးပြုရန် စီမံခန့်ခွဲသူအဖြစ်ပြောင်းပါ။"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"စနစ်၏ ဘာသာစကားကို <xliff:g id="LANGUAGE">%1$s</xliff:g> သို့ ပြောင်းလိုသလား။"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"စက်နောက်တစ်ခုက စနစ်၏ ဘာသာစကားပြောင်းရန် တောင်းဆိုထားသည်"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"ဘာသာစကားပြောင်းရန်"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"ဤကွန်ရက်ကို အမြဲခွင့်ပြုပါ"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"ခွင့်ပြုရန်"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"ကြိုးမဲ့ အမှားရှာပြင်ခြင်းကို ခွင့်မပြုပါ"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"ဤစက်ပစ္စည်းသို့ လက်ရှိဝင်ရောက်ထားသည့် အသုံးပြုသူသည် ကြိုးမဲ့ အမှားပြင်ဆင်ခြင်းကို ဖွင့်၍မရပါ။ ဤဝန်ဆောင်မှုကို အသုံးပြုရန် အဓိကအသုံးပြုသူအဖြစ်သို့ ပြောင်းပါ။"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"ဤစက်သို့ လက်ရှိလက်မှတ်ထိုးဝင်ထားသူသည် ကြိုးမဲ့ အမှားရှာပြင်ခြင်းကို ဖွင့်၍မရပါ။ ဤဝန်ဆောင်မှု အသုံးပြုရန် စီမံခန့်ခွဲသူအဖြစ်ပြောင်းပါ။"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB ပို့တ် ပိတ်ပြီးပြီ"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"USB ပို့တ်ကို ပိတ်၍ သင့်ကိရိယာသို့ အရည် သို့မဟုတ် အမှိုက်စများ မဝင်စေရန် ကာကွယ်ပါ၊ မည်သည့် အပိုပစ္စည်းကိုမျှ အာရုံခံသိရှိနိုင်တော့မည် မဟုတ်ပါ။\n\nUSB ပို့တ်ကို ပြန်အသုံးပြုနိုင်သည့်အခါ သင့်ကိုအကြောင်းကြားပါမည်။"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"အားသွင်းကိရိယာနှင့် ဆက်စပ်ပစ္စည်းများ သိရှိရန် USB ပို့တ် ဖွင့်ထားသည်"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"အသံ အကူအညီ"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR ကုဒ်ဖတ်စနစ်"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"သော့ဖွင့်ရန်"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"စက်ပစ္စည်းကို လော့ခ်ချထားသည်"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"မျက်နှာ စကင်ဖတ်နေသည်"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ပို့ရန်"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"မျက်နှာကို မမှတ်မိပါ"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"လက်ဗွေကို အစားထိုးသုံးပါ"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ဘလူးတုသ်ချိတ်ဆက်ထားမှု"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ဘက်ထရီရာခိုင်နှုန်းကို မသိပါ။"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>သို့ ချိတ်ဆက်ထား"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"လေယာဉ်ပျံမုဒ်"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ကို ဖွင့်ထားသည်။"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"ဘက်ထရီ <xliff:g id="NUMBER">%d</xliff:g> ရာခိုင်နှုန်း။"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ဘက်ထရီ <xliff:g id="PERCENTAGE">%1$s</xliff:g> ရာခိုင်နှုန်း၊ သင်၏ အသုံးပြုမှုအပေါ် မူတည်၍ <xliff:g id="TIME">%2$s</xliff:g> ခန့်ကျန်သည်"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ဘက်ထရီ <xliff:g id="PERCENTAGE">%1$d</xliff:g> ရာခိုင်နှုန်း၊ သင်၏ အသုံးပြုမှုအပေါ် မူတည်၍ <xliff:g id="TIME">%2$s</xliff:g> ခန့်ကျန်သည်"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ဘက်ထရီအားသွင်းနေသည်၊ <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%။"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"သတိပေးချက်များအားလုံးကို ကြည့်ရန်"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter ရရှိသည်။"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"တုန်ခါခြင်း ဖုန်းမြည်သံ"</string>
@@ -213,7 +220,7 @@
     <string name="accessibility_rotation_lock_on_landscape" msgid="936972553861524360">"ဖန်သားပြင် အနေအထားက အလျားလိုက်အဖြစ် ပုံသေ လုပ်ထားပါသည်"</string>
     <string name="accessibility_rotation_lock_on_portrait" msgid="2356633398683813837">"ဖန်သားပြင် အနေအထားက ဒေါင်လိုက်အဖြစ် ပုံသေ လုပ်ထားပါသည်"</string>
     <string name="dessert_case" msgid="9104973640704357717">"မုန့်ထည့်သော ပုံး"</string>
-    <string name="start_dreams" msgid="9131802557946276718">"ဖန်သားပြင်အသုံးပြုမှု ချွေတာမှုစနစ်"</string>
+    <string name="start_dreams" msgid="9131802557946276718">"စခရင်နားချိန်"</string>
     <string name="ethernet_label" msgid="2203544727007463351">"အီသာနက်"</string>
     <string name="quick_settings_dnd_label" msgid="7728690179108024338">"မနှောင့်ယှက်ရ"</string>
     <string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"ဘလူးတုသ်"</string>
@@ -227,7 +234,7 @@
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"အော်တို-လည်"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"မျက်နှာပြင်အား အလိုအလျောက်လှည့်ခြင်း"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"တည်နေရာ"</string>
-    <string name="quick_settings_screensaver_label" msgid="1495003469366524120">"စကရင်ချွေတာစနစ်"</string>
+    <string name="quick_settings_screensaver_label" msgid="1495003469366524120">"စကရင်နားချိန်"</string>
     <string name="quick_settings_camera_label" msgid="5612076679385269339">"ကင်မရာသုံးခွင့်"</string>
     <string name="quick_settings_mic_label" msgid="8392773746295266375">"မိုက်သုံးခွင့်"</string>
     <string name="quick_settings_camera_mic_available" msgid="1453719768420394314">"ရနိုင်သည်"</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"မိုက်ခရိုဖုန်း သုံးနိုင်သည်"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ကင်မရာ သုံးနိုင်သည်"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"မိုက်ခရိုဖုန်းနှင့် ကင်မရာ သုံးနိုင်သည်"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"မိုက်ခရိုဖုန်းကို ဖွင့်ထားသည်"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"မိုက်ခရိုဖုန်း ပိတ်ထားသည်"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"အက်ပ်နှင့် ဝန်ဆောင်မှုအားလုံးအတွက် မိုက်ခရိုဖုန်းကို ဖွင့်ထားသည်။"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"အက်ပ်နှင့် ဝန်ဆောင်မှုအားလုံးအတွက် မိုက်ခရိုဖုန်းသုံးခွင့်ကို ပိတ်ထားသည်။ မိုက်ခရိုဖုန်းသုံးခွင့်ကို ဆက်တင်များ &gt; ကိုယ်ရေးအချက်အလက်လုံခြုံမှု &gt; မိုက်ခရိုဖုန်း တွင်ဖွင့်နိုင်သည်။"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"အက်ပ်နှင့် ဝန်ဆောင်မှုအားလုံးအတွက် မိုက်ခရိုဖုန်းသုံးခွင့်ကို ပိတ်ထားသည်။ ၎င်းကို ဆက်တင်များ &gt; ကိုယ်ရေးအချက်အလက်လုံခြုံမှု &gt; မိုက်ခရိုဖုန်း တွင်ပြောင်းနိုင်သည်။"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"ကင်မရာ ဖွင့်ထားသည်"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ကင်မရာ ပိတ်ထားသည်"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"အက်ပ်နှင့် ဝန်ဆောင်မှုအားလုံးအတွက် ကင်မရာကို ဖွင့်ထားသည်။"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"အက်ပ်နှင့် ဝန်ဆောင်မှုအားလုံးအတွက် ကင်မရာသုံးခွင့်ကို ပိတ်ထားသည်။"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"မိုက်ခရိုဖုန်းခလုတ် အသုံးပြုရန် ဆက်တင်များတွင် မိုက်ခရိုဖုန်းသုံးခွင့်ကို ဖွင့်ပါ။"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ဆက်တင်များဖွင့်ရန်။"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"အခြားစက်ပစ္စည်း"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ဖွင့်၊ ပိတ် အနှစ်ချုပ်"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"နှိုးစက်သံ၊ သတိပေးချက်အသံများ၊ ပွဲစဉ်သတိပေးသံများနှင့် သင်ခွင့်ပြုထားသူများထံမှ ဖုန်းခေါ်မှုများမှလွဲ၍ အခြားအသံများနှင့် တုန်ခါမှုများက သင့်ကို အနှောင့်အယှက်ပြုမည် မဟုတ်ပါ။ သို့သော်လည်း သီချင်း၊ ဗီဒီယိုနှင့် ဂိမ်းများအပါအဝင် သင်ကရွေးချယ်ဖွင့်ထားသည့် အရာတိုင်း၏ အသံကိုမူ ကြားနေရဆဲဖြစ်ပါလိမ့်မည်။"</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"အက်ပ်ဖြင့် မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> သည် ၎င်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ရှေ့ဆက်ရန်"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"အက်ပ် မျှဝေခြင်း (သို့) ရိုက်ကူးခြင်း"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"ဤအက်ပ်ကို မျှဝေ (သို့) ရိုက်ကူး ခွင့်ပြုမလား။"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ ဤအက်ပ်သည် သင့်ဖန်သားပြင်ရှိ မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားသတိထားရမည့် အချက်အလက်များနှင့် ပတ်သက်၍ သတိပြုပါ။"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"အက်ပ်ဖြင့် မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ ဤအက်ပ်သည် ၎င်းတွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားသတိထားရမည့် အချက်အလက်များနှင့်ပတ်သက်၍ သတိပြုပါ။"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"သင်၏ IT စီမံခန့်ခွဲသူက ပိတ်ထားသည်"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ကိရိယာဆိုင်ရာ မူဝါဒက ဖန်သားပြင်ပုံဖမ်းခြင်းကို ပိတ်ထားသည်"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံးရှင်းရန်"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"စီမံရန်"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"မှတ်တမ်း"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"သုံးရန် လော့ခ်ဖွင့်ပါ"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"သင်၏ကတ်များ ရယူရာတွင် ပြဿနာရှိနေသည်၊ နောက်မှ ထပ်စမ်းကြည့်ပါ"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"လော့ခ်မျက်နှာပြင် ဆက်တင်များ"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR ကုဒ် စကင်ဖတ်ခြင်း"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"အလုပ် ပရိုဖိုင်"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"လေယာဉ်ပျံမုဒ်"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> ၌သင့်နောက်ထပ် နှိုးစက်ကို ကြားမည်မဟုတ်ပါ"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> သို့ ပြန်ပြောင်းမလား။"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ရနိုင်မှုပေါ် အခြေခံပြီး မိုဘိုင်းဒေတာကို အလိုအလျောက် မပြောင်းပါ"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"မလိုပါ"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ပြောင်းရန်"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ဖန်သားပြင်အပြည့် ချဲ့သည်"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ဖန်သားပြင် တစ်စိတ်တစ်ပိုင်းကို ချဲ့ပါ"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ခလုတ်"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ထောင့်ဖြတ် လှိမ့်ခွင့်ပြုရန်"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"အရွယ်အစားပြန်ပြုပြင်ရန်"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"ချဲ့သည့်ပုံစံ ပြောင်းရန်"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"နောက်ပြန်ရန်"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{ဖြတ်လမ်းလင့်ခ် {label} ခု ဖယ်ရှားပြီးပြီ}other{ဖြတ်လမ်းလင့်ခ် # ခု ဖယ်ရှားပြီးပြီ}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"ဖယ်ရှားရန်"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ယာယီချိတ်ဆက်ထားသည်"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"ချိတ်ဆက်မှုအားနည်းသည်"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE၊ MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ကို စက်မှတ်တမ်းအားလုံး သုံးခွင့်ပြုမလား။"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"တစ်ခါသုံး ဝင်ခွင့်ပေးရန်"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"ခွင့်မပြုပါ"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"သင့်စက်ရှိ အဖြစ်အပျက်များကို စက်မှတ်တမ်းများက မှတ်တမ်းတင်သည်။ အက်ပ်များက ပြဿနာများ ရှာဖွေပြီးဖြေရှင်းရန် ဤမှတ်တမ်းများကို သုံးနိုင်သည်။\n\nအချို့မှတ်တမ်းများတွင် သတိထားရမည့်အချက်အလက်များ ပါဝင်နိုင်သဖြင့် ယုံကြည်ရသည့် အက်ပ်များကိုသာ စက်မှတ်တမ်းအားလုံး သုံးခွင့်ပြုပါ။ \n\nဤအက်ပ်ကို စက်မှတ်တမ်းအားလုံး သုံးခွင့်မပြုသော်လည်း ၎င်းက ကိုယ်ပိုင်မှတ်တမ်းများ သုံးနိုင်သေးသည်။ သင့်စက်ရှိ အချို့မှတ်တမ်းများ (သို့) အချက်အလက်များကို သင့်စက်ထုတ်လုပ်သူက သုံးနိုင်ပါသေးသည်။"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 117c864..b1da115 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Tillat alltid fra denne datamaskinen"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Tillat"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB-feilsøking er ikke tillatt"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Brukeren som for øyeblikket er logget på denne enheten, kan ikke slå på USB-feilsøking. For å bruke denne funksjonen, bytt til hovedbrukeren."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Brukeren som for øyeblikket er logget på denne enheten, kan ikke slå på USB-feilsøking. For å bruke denne funksjonen, bytt til en administratorbruker."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Vil du bytte systemspråk til <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Bytte av systemspråk er forespurt av en annen enhet"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Bytt språk"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Tillat alltid på dette nettverket"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Tillat"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Trådløs feilsøking er ikke tillatt"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Brukeren som for øyeblikket er logget på denne enheten, kan ikke slå på trådløs feilsøking. For å bruke denne funksjonen, bytt til primærbrukeren."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Brukeren som for øyeblikket er logget på denne enheten, kan ikke slå på trådløs feilsøking. For å bruke denne funksjonen, bytt til en administratorbruker."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB-porten er deaktivert"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"For å beskytte enheten din mot væsker eller rusk er USB-porten deaktivert og kan ikke oppdage tilbehør.\n\nDu blir varslet når det er trygt å bruke USB-porten igjen."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Registrering av ladere og tilbehør er slått på for USB-porten"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Talehjelp"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-kodeskanner"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Lås opp"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Enheten er låst"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Skanning av ansikt"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Send"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Ansiktet gjenkjennes ikke"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Bruk fingeravtrykk"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth er tilkoblet."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batteriprosenten er ukjent."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Koblet til <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Flymodus."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN på."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Batteri – <xliff:g id="NUMBER">%d</xliff:g> prosent."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batterinivået er <xliff:g id="PERCENTAGE">%1$s</xliff:g> prosent – omtrent <xliff:g id="TIME">%2$s</xliff:g> gjenstår basert på bruken din"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batterinivået er <xliff:g id="PERCENTAGE">%1$d</xliff:g> prosent – omtrent <xliff:g id="TIME">%2$s</xliff:g> gjenstår basert på bruken din"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batteriet lades – <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> prosent."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Se alle varslene"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter er aktivert."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibreringsmodus."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Administrer brukere"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon er tilgjengelig"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera er tilgjengelig"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon og kamera er tilgjengelig"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofonen er slått på"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofonen er slått av"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofonen er slått på for alle apper og tjenester."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofontilgang er slått av for alle apper og tjenester. Du kan gi mikrofontilgang i Innstillinger &gt; Personvern &gt; Mikrofon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofontilgang er slått av for alle apper og tjenester. Du kan endre dette i Innstillinger &gt; Personvern &gt; Mikrofon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kameraet er slått på"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kameraet er slått av"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kameraet er slått på for alle apper og tjenester."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kameratilgang er slått av for alle apper og tjenester."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"For å bruke mikrofonknappen, gi mikrofontilgang i innstillingene."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Åpne innstillingene."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Annen enhet"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Slå oversikten av eller på"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Du blir ikke forstyrret av lyder og vibrasjoner, med unntak av alarmer, påminnelser, aktiviteter og oppringere du angir. Du kan fremdeles høre alt du velger å spille av, for eksempel musikk, videoer og spill."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Når du deler, tar opp eller caster en app, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tilgang til alt som vises eller spilles av i den aktuelle appen. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsett"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Del eller ta opp en app"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Vil du tillate at denne appen deler eller tar opp?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Når du deler, tar opp eller caster noe, har denne appen tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Når du deler, tar opp eller caster en app, har denne appen tilgang til alt som vises eller spilles av i den aktuelle appen. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokkert av IT-administratoren"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skjermdumper er deaktivert av enhetsinnstillingene"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Fjern alt"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Logg"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lås opp for å bruke"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Det oppsto et problem med henting av kortene. Prøv på nytt senere"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Innstillinger for låseskjermen"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skann QR-koden"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Work-profil"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Flymodus"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Du hører ikke neste innstilte alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vil du bytte tilbake til <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Det byttes ikke mobildataoperatør automatisk basert på tilgjengelighet"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nei takk"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, bytt"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Forstørr hele skjermen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Forstørr en del av skjermen"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Bytt"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Tillat diagonal rulling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Endre størrelse"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Endre forstørringstype"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Angre"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} snarvei er fjernet}other{# snarveier er fjernet}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Fjern"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Koblet til midlertidig"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Dårlig forbindelse"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE d. MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Vil du gi <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> tilgang til alle enhetslogger?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Gi éngangstilgang"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Ikke tillat"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Enhetslogger registrerer det som skjer på enheten din. Apper kan bruke disse loggene til å finne og løse problemer.\n\nNoen logger kan inneholde sensitiv informasjon, så du bør bare gi tilgang til alle enhetslogger til apper du stoler på. \n\nHvis du ikke gir denne appen tilgang til alle enhetslogger, har den fortsatt tilgang til sine egne logger. Enhetsprodusenten kan fortsatt ha tilgang til visse logger eller noe informasjon på enheten din."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 3519715..12b319e 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"यो कम्प्युटरबाट सधैँ अनुमति दिनुहोस्"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"अनुमति दिनुहोस्"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB डिबग गर्न अनुमति छैन"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"हाल यस डिभाइसमा साइन इन हुनुभएको प्रयोगकर्ताले USB डिबग सक्रिय गर्न सक्नुहुन्न। यो सुविधाको प्रयोग गर्न प्राथमिक प्रयोगकर्तामा बदल्नुहोस्‌।"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"हालैमा यस डिभाइसमा साइन इन भएका प्रयोगकर्ताले USB डिबगिङ सक्रिय गर्न सक्दैनन्। यो सुविधा प्रयोग गर्न कृपया खाताका एड्मिनका रूपमा साइन इन गर्नुहोस्।"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"तपाईं सिस्टमको भाषा बदलेर <xliff:g id="LANGUAGE">%1$s</xliff:g> बनाउन चाहनुहुन्छ?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"अर्को डिभाइसले सिस्टमको भाषा परिवर्तन गर्न अनुरोध गरेको छ"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"भाषा परिवर्तन गर्नुहोस्"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"यस नेटवर्कमा सधैँ अनुमति दिइयोस्"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"अनुमति दिनुहोस्"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"वायरलेस डिबगिङ सेवालाई अनुमति दिइएको छैन"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"हाल यस डिभाइसमा साइन इन हुनुभएका प्रयोगकर्ता वायरलेस डिबगिङ सक्रिय गर्न सक्नुहुन्न। यो सुविधाको प्रयोग गर्न प्राथमिक प्रयोगकर्ताको खातामार्फत साइन इन गर्नुहोस्।"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"हालैमा यस डिभाइसमा साइन इन भएका प्रयोगकर्ताले USB डिबगिङ सक्रिय गर्न सक्दैनन्। यो सुविधा प्रयोग गर्न कृपया खाताका एड्मिनका रूपमा साइन इन गर्नुहोस्।"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB पोर्ट असक्षम पारियो"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"तपाईंको यन्त्रलाई तरल पदार्थ वा धुलोबाट जोगाउन यसको USB पोर्ट असक्षम पारिएको छ र यसले कुनै पनि सहायक उपकरणहरू पहिचान गर्ने छैन।\n\nउक्त USB पोर्ट फेरि प्रयोग गर्दा हुन्छ भने तपाईंलाई यसबारे सूचित गरिने छ।"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"चार्जर तथा सामानहरू पत्ता लगाउन सक्षम पारिएको USB पोर्ट"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"आवाज सहायता"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR कोड स्क्यानर"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"खोल्नुहोस्"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"यन्त्र लक गरिएको छ"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"अनुहार स्क्यान गर्दै"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"पठाउनुहोस्"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"अनुहार पहिचान गर्न सकिएन"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लुटुथ जडान भयो।"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ब्याट्रीमा कति प्रतिशत चार्ज छ भन्ने कुराको जानाकरी छैन।"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> मा जडित।"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"हवाइजहाज मोड।"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN सक्रिय छ।"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"ब्याट्री <xliff:g id="NUMBER">%d</xliff:g> प्रतिशत"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ब्याट्रीको चार्ज <xliff:g id="PERCENTAGE">%1$s</xliff:g> प्रतिशत छ, तपाईंको प्रयोगका आधारमा <xliff:g id="TIME">%2$s</xliff:g> बाँकी छ"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ब्याट्रीको चार्ज <xliff:g id="PERCENTAGE">%1$d</xliff:g> प्रतिशत छ, तपाईंको प्रयोगका आधारमा <xliff:g id="TIME">%2$s</xliff:g> बाँकी छ"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ब्याट्री चार्ज हुँदैछ, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> प्रतिशत भयो।"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"सबै सूचनाहरू हेर्नुहोस्"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"टेलि टाइपराइटर सक्षम गरियो।"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"बज्ने कम्पन हुन्छ।"</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"माइक्रोफोन उपलब्ध छ"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"क्यामेरा उपलब्ध छ"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"माइक्रोफोन र क्यामेरा उपलब्ध छन्"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"माइक्रोफोन अन गरियो"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"माइक्रोफोन अफ गरियो"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"सबै एप तथा सेवाहरूलाई माइक्रोफोन प्रयोग गर्ने अनुमति दिइएको छ।"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"कुनै पनि एप तथा सेवालाई माइक्रोफोन प्रयोग गर्ने अनुमति दिइएको छैन। तपाईं \"सेटिङ &gt; गोपनीयता &gt; माइक्रोफोन\" मा गई माइक्रोफोन प्रयोग गर्ने अनुमति दिन सक्नुहुन्छ।"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"कुनै पनि एप तथा सेवालाई माइक्रोफोन प्रयोग गर्ने अनुमति दिइएको छैन। तपाईं \"सेटिङ &gt; गोपनीयता &gt; माइक्रोफोन\" मा गई यो कुरा परिवर्तन गर्न सक्नुहुन्छ।"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"क्यामेरा अन गरियो"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"क्यामेरा अफ गरियो"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"सबै एप तथा सेवाहरूलाई क्यामेरा प्रयोग गर्ने अनुमति दिइएको छ।"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"कुनै पनि एप तथा सेवालाई क्यामेरा प्रयोग गर्ने अनुमति दिइएको छैन।"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"तपाईं माइक्रोफोन बटन प्रयोग गर्न चाहनुहुन्छ भने सेटिङमा गई माइक्रोफोन प्रयोग गर्ने अनुमति दिनुहोस्।"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"सेटिङ खोल्नुहोस्।"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"अर्को डिभाइड"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"परिदृश्य टगल गर्नुहोस्"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"तपाईंलाई अलार्म, रिमाइन्डर, कार्यक्रम र तपाईंले निर्दिष्ट गर्नुभएका कलरहरू बाहेकका ध्वनि र कम्पनहरूले बाधा पुऱ्याउने छैनन्। तपाईंले अझै सङ्गीत, भिडियो र खेलहरू लगायत आफूले प्ले गर्न छनौट गरेका जुनसुकै कुरा सुन्न सक्नुहुनेछ।"</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी राख्नुहोस्"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"सेयर वा रेकर्ड गर्नका लागि एप चयन गर्नुहोस्"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"यो एपलाई सेयर गर्न वा रेकर्ड गर्न दिने हो?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"यो एपले तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले रेकर्ड गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा यो एपले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"तपाईंका सूचना प्रविधि व्यवस्थापकले ब्लक गर्नुभएको छ"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिभाइसको नीतिका कारण स्क्रिन क्याप्चर गर्ने सुविधा अफ गरिएको छ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सबै हटाउनुहोस्"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थित गर्नुहोस्"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"यो वालेट प्रयोग गर्न डिभाइस अनलक गर्नुहोस्"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"तपाईंका कार्डहरू प्राप्त गर्ने क्रममा समस्या भयो, कृपया पछि फेरि प्रयास गर्नुहोस्"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"लक स्क्रिनसम्बन्धी सेटिङ"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR कोड स्क्यान गर्नुहोस्"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"कार्य प्रोफाइल"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"हवाइजहाज मोड"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"तपाईँले आफ्नो अर्को अलार्म <xliff:g id="WHEN">%1$s</xliff:g> सुन्नुहुने छैन"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"फेरि <xliff:g id="CARRIER">%s</xliff:g> को मोबाइल डेटा अन गर्ने हो?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"मोबाइल डेटा उपलब्धताका आधारमा स्वतः बदलिँदैन"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"पर्दैन, धन्यवाद"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"हुन्छ, बदल्नुहोस्"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"पूरै स्क्रिन जुम इन गर्नुहोस्"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"स्क्रिनको केही भाग म्याग्निफाइ गर्नुहोस्"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"बदल्नुहोस्"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"डायगोनल तरिकाले स्क्रोल गर्ने अनुमति दिनुहोस्"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"आकार बदल्नुहोस्"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"जुम इन गर्ने सुविधाको प्रकार बदल्नुहोस्"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"अन्डू गर्नुहोस्"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} वटा सर्टकट हटाइयो}other{# वटा सर्टकट हटाइयो}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"हटाउनुहोस्"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"केही समयका लागि मोबाइल डेटामा कनेक्ट गरिएको छ"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"इन्टरनेट राम्री चलेको छैन"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> लाई डिभाइसका सबै लग हेर्ने अनुमति दिने हो?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"एक पटक प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"अनुमति नदिनुहोस्"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"डिभाइसका लगले तपाईंको डिभाइसमा भएका विभिन्न गतिविधिको अभिलेख राख्छन्। एपहरू यी लगका आधारमा समस्या पत्ता लगाउन र तिनको समाधान गर्न सक्छन्।\n\nकेही लगहरूमा संवेदनशील जानकारी समावेश हुन सक्ने भएकाले आफूले भरोसा गर्ने एपलाई मात्र डिभाइसका सबै लग हेर्ने अनुमति दिनुहोस्। \n\nतपाईंले यो एपलाई डिभाइसका सबै लग हेर्ने अनुमति दिनुभएन भने पनि यसले आफ्नै लग भने हेर्न सक्छ। तपाईंको डिभाइसका उत्पादकले पनि तपाईंको डिभाइसमा भएका केही लग वा जानकारी हेर्न सक्ने सम्भावना हुन्छ।"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 16152f8..08e1bf2 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -78,9 +78,6 @@
     <color name="biometric_dialog_accent">@color/material_dynamic_primary70</color>
     <color name="biometric_dialog_error">#fff28b82</color> <!-- red 300 -->
 
-    <!-- UDFPS colors -->
-    <color name="udfps_enroll_icon">#7DA7F1</color>
-
     <color name="GM2_green_500">#FF41Af6A</color>
     <color name="GM2_blue_500">#5195EA</color>
     <color name="GM2_red_500">#E25142</color>
@@ -103,4 +100,13 @@
     <color name="accessibility_floating_menu_message_text">@*android:color/primary_text_default_material_dark</color>
 
     <color name="people_tile_background">@color/material_dynamic_secondary20</color>
+
+    <!-- UDFPS colors -->
+    <color name="udfps_enroll_icon">#7DA7F1</color>
+    <color name="udfps_moving_target_fill">#475670</color>
+    <!-- 50% of udfps_moving_target_fill-->
+    <color name="udfps_moving_target_fill_error">#80475670</color>
+    <color name="udfps_enroll_progress">#7DA7F1</color>
+    <color name="udfps_enroll_progress_help">#607DA7F1</color>
+    <color name="udfps_enroll_progress_help_with_talkback">#FFEE675C</color>
 </resources>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 6f87169..99bc794 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -24,11 +24,6 @@
         <item name="android:windowIsFloating">true</item>
     </style>
 
-    <style name="TextAppearance.QS.Status" parent="TextAppearance.QS.TileLabel.Secondary">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
-    </style>
-
     <!-- Screenshots -->
     <style name="LongScreenshotActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
         <item name="android:windowNoTitle">true</item>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 3c85148..e384881 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Altijd toestaan vanaf deze computer"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Toestaan"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB-foutopsporing niet toegestaan"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"De gebruiker die momenteel is ingelogd op dit apparaat, kan USB-foutopsporing niet aanzetten. Als je deze functie wilt gebruiken, schakel je naar de primaire gebruiker."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"De gebruiker die momenteel is ingelogd op dit apparaat, kan USB-foutopsporing niet aanzetten. Als je deze functie wilt gebruiken, schakel je over naar een beheerdersaccount."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Wil je de systeemtaal wijzigen naar het <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Wijziging van systeemtaal aangevraagd door een ander apparaat"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Taal wijzigen"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Altijd toestaan in dit netwerk"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Toestaan"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Draadloze foutopsporing niet toegestaan"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"De gebruiker die momenteel is ingelogd op dit apparaat, kan draadloze foutopsporing niet aanzetten. Als je deze functie wilt gebruiken, schakel je over naar de primaire gebruiker."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"De gebruiker die momenteel is ingelogd op dit apparaat, kan draadloze foutopsporing niet aanzetten. Als je deze functie wilt gebruiken, schakel je over naar een beheerdersaccount."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB-poort staat uit"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"De USB-poort staat uit en detecteert geen accessoires, zodat je apparaat wordt beschermd tegen vloeistof en vuil.\n\nJe ontvangt een melding wanneer je de USB-poort weer kunt gebruiken."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB-poort kan opladers en accessoires detecteren"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Spraakassistent"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Portemonnee"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-codescanner"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Ontgrendelen"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Apparaat vergrendeld"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Gezicht scannen"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Verzenden"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Gezicht niet herkend"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Vingerafdruk gebruiken"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-verbinding ingesteld."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batterijpercentage onbekend."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Verbonden met <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Vliegtuigmodus."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN staat aan."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Batterij: <xliff:g id="NUMBER">%d</xliff:g> procent."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batterij op <xliff:g id="PERCENTAGE">%1$s</xliff:g> procent, nog ongeveer <xliff:g id="TIME">%2$s</xliff:g> op basis van je gebruik"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batterij op <xliff:g id="PERCENTAGE">%1$d</xliff:g> procent, nog ongeveer <xliff:g id="TIME">%2$s</xliff:g> op basis van je gebruik"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batterij wordt opgeladen, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%% procent."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Alle meldingen bekijken"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter staat aan."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Belsoftware trilt."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gebruikers beheren"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microfoon beschikbaar"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Camera beschikbaar"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microfoon en camera beschikbaar"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microfoon staat aan"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microfoon staat uit"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Microfoon staat aan voor alle apps en services."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Microfoontoegang staat uit voor alle apps en services. Je kunt microfoontoegang aanzetten via Instellingen &gt; Privacy &gt; Microfoon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Microfoontoegang staat uit voor alle apps en services. Je kunt dit wijzigen via Instellingen &gt; Privacy &gt; Microfoon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Camera staat aan"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Camera staat uit"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Camera staat aan voor alle apps en services."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Cameratoegang staat uit voor alle apps en services."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Als je de microfoonknop wilt gebruiken, zet je microfoontoegang aan via Instellingen."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Instellingen openen."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Ander apparaat"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Overzicht aan- of uitzetten"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Je wordt niet gestoord door geluiden en trillingen, behalve bij wekkers, herinneringen, afspraken en specifieke bellers die je selecteert. Je kunt nog steeds alles horen wat je wilt afspelen, waaronder muziek, video\'s en games."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Als je deelt, opneemt of cast, heeft <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> toegang tot alles dat wordt getoond of afgespeeld in die app. Wees daarom voorzichtig met wachtwoorden, betalingsgegevens, berichten en andere gevoelige informatie."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Doorgaan"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"App delen of opnemen"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Deze app toestaan om te delen of op te nemen?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Als je deelt, opneemt of cast, heeft deze app toegang tot alles dat zichtbaar is op je scherm of wordt afgespeeld op je apparaat. Wees daarom voorzichtig met wachtwoorden, betalingsgegevens, berichten en andere gevoelige informatie."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Als je deelt, opneemt of cast, heeft deze app toegang tot alles dat wordt getoond of afgespeeld in die app. Wees daarom voorzichtig met wachtwoorden, betalingsgegevens, berichten en andere gevoelige informatie."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Geblokkeerd door je IT-beheerder"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Schermopname staat uit vanwege apparaatbeleid"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Alles wissen"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Beheren"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Geschiedenis"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ontgrendelen om te gebruiken"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Er is een probleem opgetreden bij het ophalen van je kaarten. Probeer het later opnieuw."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Instellingen voor vergrendelscherm"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR-code scannen"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Werkprofiel"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Vliegtuigmodus"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Je hoort je volgende wekker niet <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Terugschakelen naar <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiele data worden niet automatisch overgezet op basis van beschikbaarheid"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nee, bedankt"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, overschakelen"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Volledig scherm vergroten"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Deel van het scherm vergroten"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Schakelen"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Diagonaal scrollen toestaan"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Formaat aanpassen"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Vergrotingstype wijzigen"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Ongedaan maken"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Snelkoppeling voor {label} verwijderd}other{# snelkoppelingen verwijderd}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Verwijderen"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tijdelijk verbonden"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Matige verbinding"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE d mmm"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"u:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> toegang geven tot alle apparaatlogboeken?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Eenmalige toegang toestaan"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Niet toestaan"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Apparaatlogboeken leggen vast wat er op je apparaat gebeurt. Apps kunnen deze logboeken gebruiken om problemen op te sporen en te verhelpen.\n\nSommige logboeken kunnen gevoelige informatie bevatten, dus geef alleen apps die je vertrouwt toegang tot alle apparaatlogboeken. \n\nAls je deze app geen toegang tot alle apparaatlogboeken geeft, heeft de app nog wel toegang tot de eigen logboeken. De fabrikant van je apparaat heeft misschien nog steeds toegang tot bepaalde logboeken of informatie op je apparaat."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 1cb3436..09250c3 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"ସବୁବେଳେ ଏହି କମ୍ପ୍ୟୁଟର୍‌ରୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USBରେ ଡିବଗ୍‍ କରାଯାଇପାରିବ ନାହିଁ"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"ସମ୍ପ୍ରତି ସାଇନ୍‍-ଇନ୍‍ କରିଥିବା ୟୁଜର୍‍ ଜଣକ ଏହି ଡିଭାଇସରେ USB ଡିବଗିଙ୍ଗ ଅନ୍‍ କରିପାରିବେ ନାହିଁ। ଏହି ବୈଶିଷ୍ଟ୍ୟ ବ୍ୟବହାର କରିବାକୁ, ପ୍ରାଥମିକ ୟୁଜର୍‍ରେ ସାଇନ୍‍-ଇନ୍‍ କରନ୍ତୁ।"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"ୟୁଜର ବର୍ତ୍ତମାନ ସାଇନ ଇନ କରିଥିବା ଏହି ଡିଭାଇସରେ USB ଡିବଗିଂ ଚାଲୁ କରିପାରିବେ ନାହିଁ। ଏହି ଫିଚର ବ୍ୟବହାର କରିବା ପାଇଁ ଜଣେ ଆଡମିନ ୟୁଜର ଭାବେ ସ୍ୱିଚ କରନ୍ତୁ।"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"ଆପଣ ସିଷ୍ଟମ ଭାଷା <xliff:g id="LANGUAGE">%1$s</xliff:g>କୁ ପରିବର୍ତ୍ତନ କରିବା ପାଇଁ ଚାହାଁନ୍ତି?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"ଅନ୍ୟ ଏକ ଡିଭାଇସ ଦ୍ୱାରା ସିଷ୍ଟମ ଭାଷା ପରିବର୍ତ୍ତନ ପାଇଁ ଅନୁରୋଧ କରାଯାଇଛି"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"ଭାଷା ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"ସର୍ବଦା ଏହି ନେଟୱାର୍କରେ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"ୱାୟାରଲେସ୍ ଡିବଗିଂ ପାଇଁ ଅନୁମତି ନାହିଁ"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"ଉପଯୋଗକର୍ତ୍ତା ବର୍ତ୍ତମାନ ସାଇନ୍-ଇନ୍ କରିଥିବା ଏହି ଡିଭାଇସରେ ୱାୟାରଲେସ୍ ଡିବଗିଂ ଚାଲୁ କରିପାରିବେ ନାହିଁ। ଏହି ଫିଚର୍ ବ୍ୟବହାର କରିବା ପାଇଁ ପ୍ରାଥମିକ ଉପଯୋଗକର୍ତ୍ତାରେ ସ୍ୱିଚ୍ କରନ୍ତୁ।"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"ୟୁଜର ବର୍ତ୍ତମାନ ସାଇନ-ଇନ କରିଥିବା ଏହି ଡିଭାଇସରେ ୱାୟାରଲେସ ଡିବଗିଂ ଚାଲୁ କରିପାରିବେ ନାହିଁ। ଏହି ଫିଚର ବ୍ୟବହାର କରିବା ପାଇଁ ଜଣେ ଆଡମିନ ୟୁଜର ଭାବେ ସ୍ୱିଚ କରନ୍ତୁ।"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB ପୋର୍ଟକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"ଆପଣଙ୍କ ଡିଭାଇସ୍‌କୁ ତରଳ ପଦାର୍ଥ ଏବଂ ଧୂଳିରୁ ସୁରକ୍ଷିତ ରଖିବା ପାଇଁ, USB ପୋର୍ଟକୁ ଅକ୍ଷମ କରାଯାଇଛି ଏବଂ ଏହା କୌଣସି ଉପକରଣ ଚିହ୍ନଟ କରିବ ନାହିଁ। \n\n ଯେତେବେଳେ USB ପୋର୍ଟ ପୁଣିି ବ୍ୟବହାର କରିବାକୁ ସୁରକ୍ଷିତ ହେବ, ସେତେବେଳେ ଆପଣଙ୍କୁ ସୂଚିତ କରାଯିବ।"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"ଚାର୍ଜର୍‍ ଏବଂ ଆକ୍ସେସରିଗୁଡ଼ିକୁ ଚିହ୍ନଟ କରିବାକୁ USB ପୋର୍ଟ ସକ୍ଷମ କରାଯାଇଛି"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ଭଏସ୍‌ ସହାୟକ"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"ୱାଲେଟ୍"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR କୋଡ ସ୍କାନର"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"ଅନଲକ୍‌ କରନ୍ତୁ"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"ଡିଭାଇସ୍ ଲକ୍ ହୋଇଯାଇଛି"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"ଫେସ୍ ସ୍କାନିଙ୍ଗ କରାଯାଉଛି"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ପଠାନ୍ତୁ"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"ଫେସ ଚିହ୍ନଟ ହୋଇପାରିବ ନାହିଁ"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ଟିପଚିହ୍ନ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ବ୍ଲୁଟୂଥ୍‍‌ ସଂଯୋଗ କରାଯାଇଛି।"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ବ୍ୟାଟେରୀ ଶତକଡ଼ା ଅଜଣା ଅଟେ।"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ସହ ସଂଯୁକ୍ତ"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"ଏରୋପ୍ଲେନ୍‍ ମୋଡ୍‌।"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ଅନ୍‍।"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"ବ୍ୟାଟେରୀ <xliff:g id="NUMBER">%d</xliff:g> ଶତକଡ଼ା।"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ବ୍ୟାଟେରୀ <xliff:g id="PERCENTAGE">%1$s</xliff:g> ଶତକଡା, ଆପଣଙ୍କର ବ୍ୟବହାରକୁ ଆଧାର କରି ପାଖାପାଖି <xliff:g id="TIME">%2$s</xliff:g> ବାକି ଅଛି"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ବ୍ୟାଟେରୀ <xliff:g id="PERCENTAGE">%1$d</xliff:g> ଶତକଡା, ଆପଣଙ୍କର ବ୍ୟବହାରକୁ ଆଧାର କରି ପାଖାପାଖି <xliff:g id="TIME">%2$s</xliff:g> ବାକି ଅଛି"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ବ୍ୟାଟେରୀ ଚାର୍ଜ ହେଉଛି, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> ଶତକଡ଼ା।"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"ସମସ୍ତ ବିଜ୍ଞପ୍ତି ଦେଖନ୍ତୁ"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"ଟେଲି-ଟାଇପରାଇଟର୍ ସକ୍ଷମ ଅଛି।"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ରିଙ୍ଗର୍‌ କମ୍ପନରେ ଅଛି।"</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"ମାଇକ୍ରୋଫୋନ ଉପଲବ୍ଧ"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"କ୍ୟାମେରା ଉପଲବ୍ଧ"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"ମାଇକ୍ରୋଫୋନ ଏବଂ କ୍ୟାମେରା ଉପଲବ୍ଧ"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"ମାଇକ୍ରୋଫୋନ ଚାଲୁ କରାଯାଇଛି"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"ମାଇକ୍ରୋଫୋନ ବନ୍ଦ କରାଯାଇଛି"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"ସମସ୍ତ ଆପ୍ସ ଏବଂ ସେବା ପାଇଁ ମାଇକ୍ରୋଫୋନକୁ ସକ୍ଷମ କରାଯାଇଛି।"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"ସମସ୍ତ ଆପ୍ସ ଏବଂ ସେବା ପାଇଁ ମାଇକ୍ରୋଫୋନ ଆକ୍ସେସକୁ ଅକ୍ଷମ କରାଯାଇଛି। ସେଟିଂସ &gt; ଗୋପନୀୟତା &gt; ମାଇକ୍ରୋଫୋନରେ ଆପଣ ମାଇକ୍ରୋଫୋନ ଆକ୍ସେସକୁ ସକ୍ଷମ କରିପାରିବେ।"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"ସମସ୍ତ ଆପ୍ସ ଏବଂ ସେବା ପାଇଁ ମାଇକ୍ରୋଫୋନ ଆକ୍ସେସକୁ ଅକ୍ଷମ କରାଯାଇଛି। ସେଟିଂସ &gt; ଗୋପନୀୟତା &gt; ମାଇକ୍ରୋଫୋନରେ ଆପଣ ଏହାକୁ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"କେମେରା ଚାଲୁ କରାଯାଇଛି"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"କେମେରା ବନ୍ଦ କରାଯାଇଛି"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"ସମସ୍ତ ଆପ୍ସ ଏବଂ ସେବା ପାଇଁ କେମେରାକୁ ସକ୍ଷମ କରାଯାଇଛି।"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"ସମସ୍ତ ଆପ୍ସ ଏବଂ ସେବା ପାଇଁ କେମେରା ଆକ୍ସେସକୁ ଅକ୍ଷମ କରାଯାଇଛି।"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"ମାଇକ୍ରୋଫୋନ ବଟନ ବ୍ୟବହାର କରିବା ପାଇଁ ସେଟିଂସରେ ମାଇକ୍ରୋଫୋନ ଆକ୍ସେସକୁ ସକ୍ଷମ କରନ୍ତୁ।"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ସେଟିଂସ ଖୋଲନ୍ତୁ।"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"ଅନ୍ୟ ଡିଭାଇସ୍"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ସଂକ୍ଷିପ୍ତ ବିବରଣୀକୁ ଟୋଗଲ୍ କରନ୍ତୁ"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"ଆଲାର୍ମ, ରିମାଇଣ୍ଡର୍‌, ଇଭେଣ୍ଟ ଏବଂ ଆପଣ ନିର୍ଦ୍ଦିଷ୍ଟ କରିଥିବା କଲର୍‌ଙ୍କ ବ୍ୟତୀତ ଆପଣଙ୍କ ଧ୍ୟାନ ଅନ୍ୟ କୌଣସି ଧ୍ୱନୀ ଏବଂ ଭାଇବ୍ରେଶନ୍‌ରେ ଆକର୍ଷଣ କରାଯିବନାହିଁ। ମ୍ୟୁଜିକ୍‍, ଭିଡିଓ ଏବଂ ଗେମ୍‌ ସମେତ ନିଜେ ଚଲାଇବାକୁ ବାଛିଥିବା ଅନ୍ୟ ସବୁକିଛି ଆପଣ ଶୁଣିପାରିବେ।"</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ ସେହି ଆପର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ଜାରି ରଖନ୍ତୁ"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ଏକ ଆପକୁ ସେୟାର କିମ୍ବା ରେକର୍ଡ କରନ୍ତୁ"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"ସେୟାର କିମ୍ବା ରେକର୍ଡ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦେବେ?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"ଆପଣ ସେୟାର, ରେକର୍ଡ ବା କାଷ୍ଟ କରିବା ସମୟରେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ ଏହି ଆପର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"ଆପଣ ସେୟାର, ରେକର୍ଡ ବା କାଷ୍ଟ କରିବା ସମୟରେ, ସେହି ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ ଏହି ଆପର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ଆପଣଙ୍କ IT ଆଡମିନଙ୍କ ଦ୍ୱାରା ବ୍ଲକ କରାଯାଇଛି"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ଡିଭାଇସ ନୀତି ଦ୍ୱାରା ସ୍କ୍ରିନ କେପଚରିଂକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ସମସ୍ତ ଖାଲି କରନ୍ତୁ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ପରିଚାଳନା କରନ୍ତୁ"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ଇତିହାସ"</string>
@@ -488,7 +511,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ବ୍ୟବହାର କରିବାକୁ ଅନଲକ୍ କରନ୍ତୁ"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"ଆପଣଙ୍କ କାର୍ଡଗୁଡ଼ିକ ପାଇବାରେ ଏକ ସମସ୍ୟା ହୋଇଥିଲା। ଦୟାକରି ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ସ୍କ୍ରିନ୍ ଲକ୍ ସେଟିଂସ୍"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR କୋଡ ସ୍କାନ କରନ୍ତୁ"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR କୋଡ ସ୍କାନର"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"ୱର୍କ ପ୍ରୋଫାଇଲ୍‌"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"ଏରୋପ୍ଲେନ୍‍ ମୋଡ୍"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>ବେଳେ ଆପଣ ନିଜର ପରବର୍ତ୍ତୀ ଆଲାର୍ମ ଶୁଣିପାରିବେ ନାହିଁ"</string>
@@ -727,6 +750,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g>କୁ ପୁଣି ସ୍ୱିଚ କରିବେ?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ଉପଲବ୍ଧତା ଆଧାରରେ ମୋବାଇଲ ଡାଟା ସ୍ୱଚାଳିତ ଭାବେ ସ୍ୱିଚ ହେବ ନାହିଁ"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ନା, ଧନ୍ୟବାଦ"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ହଁ, ସ୍ୱିଚ କରନ୍ତୁ"</string>
     <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>
@@ -767,6 +794,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନକୁ ମ୍ୟାଗ୍ନିଫାଏ କରନ୍ତୁ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ସ୍କ୍ରିନର ଅଂଶ ମାଗ୍ନିଫାଏ କରନ୍ତୁ"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ସ୍ୱିଚ୍ କରନ୍ତୁ"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ଡାଏଗୋନାଲ ସ୍କ୍ରୋଲିଂକୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ରିସାଇଜ କରନ୍ତୁ"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"ମ୍ୟାଗ୍ନିଫିକେସନ ପ୍ରକାରକୁ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string>
@@ -785,12 +814,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ପୂର୍ବବତ୍ କରନ୍ତୁ"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label}ଟି ସର୍ଟକଟକୁ କାଢ଼ି ଦିଆଯାଇଛି}other{#ଟି ସର୍ଟକଟକୁ କାଢ଼ି ଦିଆଯାଇଛି}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
     <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 +965,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ଅସ୍ଥାୟୀ ରୂପେ କନେକ୍ଟ କରାଯାଇଛି"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"ଦୁର୍ବଳ କନେକ୍ସନ"</string>
     <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>
@@ -992,4 +1026,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"ଗୋଟିଏ-ଥର ଆକ୍ସେସ ପାଇଁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"ଅନୁମତି ଦିଅନ୍ତୁ ନାହିଁ"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"ଆପଣଙ୍କ ଡିଭାଇସରେ ଯାହା ହୁଏ ତାହା ଡିଭାଇସ ଲଗଗୁଡ଼ିକ ରେକର୍ଡ କରେ। ସମସ୍ୟାଗୁଡ଼ିକୁ ଖୋଜି ସମାଧାନ କରିବାକୁ ଆପ୍ସ ଏହି ଲଗଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିପାରିବ।\n\nକିଛି ଲଗରେ ସମ୍ବେଦନଶୀଳ ସୂଚନା ଥାଇପାରେ, ତେଣୁ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଆପଣ ବିଶ୍ୱାସ କରୁଥିବା ଆପ୍ସକୁ ହିଁ କେବଳ ଅନୁମତି ଦିଅନ୍ତୁ। \n\nଯଦି ଆପଣ ସମସ୍ତ ଡିଭାଇସ ଲଗକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦିଅନ୍ତି ନାହିଁ, ତେବେ ଏହା ନିଜର ଡିଭାଇସ ଲଗଗୁଡ଼ିକୁ ଏବେ ବି ଆକ୍ସେସ କରିପାରିବ। ଆପଣଙ୍କ ଡିଭାଇସର ନିର୍ମାତା ଏବେ ବି ଆପଣଙ୍କର ଡିଭାଇସରେ କିଛି ଲଗ କିମ୍ବା ସୂଚନାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ସକ୍ଷମ ହୋଇପାରନ୍ତି।"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 992ffd6..72e1435 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"ਹਮੇਸ਼ਾਂ ਇਸ ਕੰਪਿਊਟਰ ਤੋਂ ਆਗਿਆ ਦਿਓ"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"ਕਰਨ ਦਿਓ"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB ਡਿਬੱਗਿੰਗ ਦੀ ਆਗਿਆ ਨਹੀਂ"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"ਫ਼ਿਲਹਾਲ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਾਈਨ-ਇਨ ਕੀਤਾ ਵਰਤੋਂਕਾਰ USB ਡੀਬੱਗਿੰਗ ਨੂੰ ਚਾਲੂ ਨਹੀਂ ਕਰ ਸਕਦਾ ਹੈ। ਇਸ ਵਿਸ਼ੇਸ਼ਤਾ ਦੀ ਵਰਤੋਂ ਕਰਨ ਲਈ, ਪ੍ਰਸ਼ਾਸਕ ਵਰਤੋਂਕਾਰ \'ਤੇ ਸਵਿੱਚ ਕਰੋ।"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"ਕੀ ਤੁਸੀਂ ਸਿਸਟਮ ਦੀ ਭਾਸ਼ਾ ਬਦਲ ਕੇ <xliff:g id="LANGUAGE">%1$s</xliff:g> ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"ਕਿਸੇ ਹੋਰ ਡੀਵਾਈਸ ਵੱਲੋਂ ਸਿਸਟਮ ਦੀ ਭਾਸ਼ਾ ਬਦਲਣ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਗਈ"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"ਭਾਸ਼ਾ ਬਦਲੋ"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"ਇਸ ਨੈੱਟਵਰਕ \'ਤੇ ਹਮੇਸ਼ਾਂ ਆਗਿਆ ਦਿਓ"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"ਆਗਿਆ ਦਿਓ"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"ਵਾਇਰਲੈੱਸ ਡੀਬੱਗਿੰਗ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"ਵਰਤਮਾਨ ਵਿੱਚ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਾਈਨ-ਇਨ ਕੀਤਾ ਵਰਤੋਂਕਾਰ ਵਾਇਰਲੈੱਸ ਡੀਬੱਗਿੰਗ ਨੂੰ ਚਾਲੂ ਨਹੀਂ ਕਰ ਸਕਦਾ। ਇਸ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਵਰਤਣ ਲਈ, ਮੁੱਖ ਵਰਤੋਂਕਾਰ \'ਤੇ ਬਦਲੋ।"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"ਫ਼ਿਲਹਾਲ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਾਈਨ-ਇਨ ਕੀਤਾ ਵਰਤੋਂਕਾਰ ਵਾਇਰਲੈੱਸ ਡੀਬੱਗਿੰਗ ਨੂੰ ਚਾਲੂ ਨਹੀਂ ਕਰ ਸਕਦਾ। ਇਸ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਵਰਤਣ ਲਈ, ਪ੍ਰਸ਼ਾਸਕ ਵਰਤੋਂਕਾਰ \'ਤੇ ਸਵਿੱਚ ਕਰੋ।"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB ਪੋਰਟ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨੂੰ ਪਾਣੀ ਅਤੇ ਧੂੜ-ਮਿੱਟੀ ਤੋਂ ਬਚਾਉਣ ਲਈ, USB ਪੋਰਟ ਨੂੰ ਬੰਦ ਕੀਤਾ ਗਿਆ ਹੈ ਅਤੇ ਕੋਈ ਵੀ ਐਕਸੈਸਰੀ ਪਛਾਣੀ ਨਹੀਂ ਜਾਵੇਗੀ।\n\nUSB ਪੋਰਟ ਨੂੰ ਦੁਬਾਰਾ ਵਰਤਣਾ ਠੀਕ ਹੋਣ \'ਤੇ ਤੁਹਾਨੂੰ ਸੂਚਿਤ ਕੀਤਾ ਜਾਵੇਗਾ।"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"ਚਾਰਜਰਾਂ ਅਤੇ ਉਪਸਾਧਨਾਂ ਦੀ ਪਛਾਣ ਕਰਨ ਲਈ USB ਪੋਰਟ ਚਾਲੂ ਹੈ"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ਅਵਾਜ਼ੀ ਸਹਾਇਕ"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR ਕੋਡ ਸਕੈਨਰ"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"ਅਣਲਾਕ ਕਰੋ"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"ਡੀਵਾਈਸ ਲਾਕ ਹੈ"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"ਚਿਹਰਾ ਸਕੈਨ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ਭੇਜੋ"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"ਚਿਹਰੇ ਦੀ ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ਇਸਦੀ ਬਜਾਏ ਫਿੰਗਰਪ੍ਰਿੰਟ ਵਰਤੋ"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ਕਨੈਕਟ ਕੀਤੀ।"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ਬੈਟਰੀ ਪ੍ਰਤੀਸ਼ਤ ਅਗਿਆਤ ਹੈ।"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ।"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"ਏਅਰਪਲੇਨ ਮੋਡ।"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ਚਾਲੂ ਹੈ।"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"ਬੈਟਰੀ <xliff:g id="NUMBER">%d</xliff:g> ਪ੍ਰਤੀਸ਼ਤ ਹੈ।"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ਬੈਟਰੀ <xliff:g id="PERCENTAGE">%1$s</xliff:g> ਫ਼ੀਸਦ, ਤੁਹਾਡੀ ਵਰਤੋਂ ਦੇ ਆਧਾਰ \'ਤੇ ਲਗਭਗ <xliff:g id="TIME">%2$s</xliff:g> ਬਾਕੀ"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ਬੈਟਰੀ <xliff:g id="PERCENTAGE">%1$d</xliff:g> ਫ਼ੀਸਦ, ਤੁਹਾਡੀ ਵਰਤੋਂ ਦੇ ਆਧਾਰ \'ਤੇ ਲਗਭਗ <xliff:g id="TIME">%2$s</xliff:g> ਬਾਕੀ"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ਬੈਟਰੀ ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> ਪ੍ਰਤੀਸ਼ਤ ਹੋ ਗਈ।"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"ਸਾਰੀਆਂ ਸੂਚਨਾਵਾਂ ਦੇਖੋ"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"ਟੈਲੀ ਟਾਈਪਰਾਈਟਰ ਸਮਰਥਿਤ।"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ਰਿੰਗਰ ਥਰਥਰਾਹਟ।"</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਉਪਲਬਧ ਹੈ"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ਕੈਮਰਾ ਉਪਲਬਧ ਹੈ"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਅਤੇ ਕੈਮਰਾ ਉਪਲਬਧ ਹੈ"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਲਈ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਚਾਲੂ ਹੈ।"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਲਈ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਪਹੁੰਚ ਬੰਦ ਹੈ। ਤੁਸੀਂ ਸੈਟਿੰਗਾਂ &gt; ਪਰਦੇਦਾਰੀ &gt; ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਵਿੱਚ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਪਹੁੰਚ ਨੂੰ ਚਾਲੂ ਕਰ ਸਕਦੇ ਹੋ।"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਲਈ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਪਹੁੰਚ ਬੰਦ ਹੈ। ਤੁਸੀਂ ਸੈਟਿੰਗਾਂ &gt; ਪਰਦੇਦਾਰੀ &gt; ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਵਿੱਚ ਇਸਨੂੰ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"ਕੈਮਰਾ ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ਕੈਮਰਾ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਲਈ ਕੈਮਰਾ ਚਾਲੂ ਹੈ।"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਲਈ ਕੈਮਰਾ ਪਹੁੰਚ ਬੰਦ ਹੈ।"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਬਟਨ ਦੀ ਵਰਤੋਂ ਕਰਨ ਲਈ, ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਪਹੁੰਚ ਚਾਲੂ ਕਰੋ।"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ਸੈਟਿੰਗਾਂ ਖੋਲ੍ਹੋ।"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"ਹੋਰ ਡੀਵਾਈਸ"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ਰੂਪ-ਰੇਖਾ ਨੂੰ ਟੌਗਲ ਕਰੋ"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"ਧੁਨੀਆਂ ਅਤੇ ਥਰਥਰਾਹਟਾਂ ਤੁਹਾਨੂੰ ਪਰੇਸ਼ਾਨ ਨਹੀਂ ਕਰਨਗੀਆਂ, ਸਿਵਾਏ ਅਲਾਰਮਾਂ, ਯਾਦ-ਦਹਾਨੀਆਂ, ਵਰਤਾਰਿਆਂ, ਅਤੇ ਤੁਹਾਡੇ ਵੱਲੋਂ ਨਿਰਧਾਰਤ ਕੀਤੇ ਕਾਲਰਾਂ ਦੀ ਸੂਰਤ ਵਿੱਚ। ਤੁਸੀਂ ਅਜੇ ਵੀ ਸੰਗੀਤ, ਵੀਡੀਓ ਅਤੇ ਗੇਮਾਂ ਸਮੇਤ ਆਪਣੀ ਚੋਣ ਅਨੁਸਾਰ ਕੁਝ ਵੀ ਸੁਣ ਸਕਦੇ ਹੋ।"</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ਜਾਰੀ ਰੱਖੋ"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ਐਪ ਨੂੰ ਸਾਂਝਾ ਕਰੋ ਜਾਂ ਰਿਕਾਰਡ ਕਰੋ"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"ਕੀ ਇਸ ਐਪ ਨੂੰ ਸਾਂਝਾ ਕਰਨ ਜਾਂ ਰਿਕਾਰਡ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, ਇਸ ਐਪ ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, ਇਸ ਐਪ ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ਤੁਹਾਡੇ ਆਈ.ਟੀ. ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਬਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ਡੀਵਾਈਸ ਨੀਤੀ ਦੇ ਕਾਰਨ ਸਕ੍ਰੀਨ ਕੈਪਚਰ ਕਰਨਾ ਬੰਦ ਹੈ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ਇਤਿਹਾਸ"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ਵਰਤਣ ਲਈ ਅਣਲਾਕ ਕਰੋ"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"ਤੁਹਾਡੇ ਕਾਰਡ ਪ੍ਰਾਪਤ ਕਰਨ ਵਿੱਚ ਕੋਈ ਸਮੱਸਿਆ ਆਈ, ਕਿਰਪਾ ਕਰਕੇ ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ਲਾਕ ਸਕ੍ਰੀਨ ਸੈਟਿੰਗਾਂ"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR ਕੋਡ ਸਕੈਨ ਕਰੋ"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"ਹਵਾਈ-ਜਹਾਜ਼ ਮੋਡ"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"ਤੁਸੀਂ <xliff:g id="WHEN">%1$s</xliff:g> ਵਜੇ ਆਪਣਾ ਅਗਲਾ ਅਲਾਰਮ ਨਹੀਂ ਸੁਣੋਗੇ"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"ਕੀ ਵਾਪਸ <xliff:g id="CARRIER">%s</xliff:g> \'ਤੇ ਸਵਿੱਚ ਕਰਨਾ ਹੈ?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ਮੋਬਾਈਲ ਡਾਟਾ ਉਪਲਬਧਤਾ ਦੇ ਆਧਾਰ \'ਤੇ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਸਵਿੱਚ ਨਹੀਂ ਹੋਵੇਗਾ"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ਹਾਂ, ਸਵਿੱਚ ਕਰੋ"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ਪੂਰੀ ਸਕ੍ਰੀਨ ਨੂੰ ਵੱਡਦਰਸ਼ੀ ਕਰੋ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ਸਕ੍ਰੀਨ ਦੇ ਹਿੱਸੇ ਨੂੰ ਵੱਡਾ ਕਰੋ"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ਸਵਿੱਚ"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ਟੇਡੀ ਦਿਸ਼ਾ ਵਿੱਚ ਸਕ੍ਰੋਲ ਕਰਨ ਦਿਓ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ਆਕਾਰ ਬਦਲੋ"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"ਵੱਡਦਰਸ਼ੀਕਰਨ ਦੀ ਕਿਸਮ ਬਦਲੋ"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ਅਣਕੀਤਾ ਕਰੋ"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਹਟਾਇਆ ਗਿਆ}one{# ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਹਟਾਇਆ ਗਿਆ}other{# ਸ਼ਾਰਟਕੱਟਾਂ ਨੂੰ ਹਟਾਇਆ ਗਿਆ}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"ਹਟਾਓ"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ਕੁਝ ਸਮੇਂ ਲਈ ਕਨੈਕਟ ਹੈ"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"ਖਰਾਬ ਕਨੈਕਸ਼ਨ"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"ਕੀ <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ਨੂੰ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"ਇੱਕ-ਵਾਰ ਲਈ ਪਹੁੰਚ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"ਆਗਿਆ ਨਾ ਦਿਓ"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"ਡੀਵਾਈਸ ਲੌਗਾਂ ਵਿੱਚ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀਆਂ ਕਾਰਵਾਈਆਂ ਰਿਕਾਰਡ ਹੁੰਦੀਆਂ ਹਨ। ਐਪਾਂ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਲੱਭਣ ਅਤੇ ਉਨ੍ਹਾਂ ਦਾ ਹੱਲ ਕਰਨ ਲਈ ਇਨ੍ਹਾਂ ਲੌਗਾਂ ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੀਆਂ ਹਨ।\n\nਕੁਝ ਲੌਗਾਂ ਵਿੱਚ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੋ ਸਕਦੀ ਹੈ, ਇਸ ਲਈ ਸਿਰਫ਼ ਆਪਣੀਆਂ ਭਰੋਸੇਯੋਗ ਐਪਾਂ ਨੂੰ ਹੀ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ। \n\nਜੇ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਸਾਰੇ ਡੀਵਾਈਸ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਦਿੰਦੇ ਹੋ, ਤਾਂ ਇਹ ਹਾਲੇ ਵੀ ਆਪਣੇ ਲੌਗਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ। ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਨਿਰਮਾਤਾ ਹਾਲੇ ਵੀ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਮੌਜੂਦ ਕੁਝ ਲੌਗਾਂ ਜਾਂ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦਾ ਹੈ।"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index b706359..0543d40 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Zawsze zezwalaj z tego komputera"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Zezwalaj"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Debugowanie USB jest niedozwolone"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Użytkownik obecnie zalogowany na tym urządzeniu nie może włączyć debugowania USB. Aby użyć tej funkcji, przełącz się na użytkownika głównego."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Użytkownik obecnie zalogowany na tym urządzeniu nie może włączyć debugowania USB. Aby użyć tej funkcji, przełącz się na administratora."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Czy chcesz zmienić język systemu na <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Inny użytkownik poprosił o zmianę języka systemu"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Zmień język"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Zawsze zezwalaj w tej sieci"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Zezwól"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Debugowanie bezprzewodowe jest niedozwolone"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Użytkownik obecnie zalogowany na tym urządzeniu nie może włączyć debugowania bezprzewodowego. Aby użyć tej funkcji, przełącz się na głównego użytkownika."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Użytkownik obecnie zalogowany na tym urządzeniu nie może włączyć debugowania bezprzewodowego. Aby użyć tej funkcji, przełącz się na administratora."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Port USB wyłączony"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Aby chronić urządzenie przed wilgocią i zanieczyszczeniami, port USB został wyłączony i nie wykryje żadnych akcesoriów.\n\nOtrzymasz powiadomienie, gdy będzie można znów używać portu."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Port USB włączony, by wykrywać ładowarki i akcesoria"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Asystent głosowy"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Portfel"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Skaner kodów QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Odblokuj"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Urządzenie zablokowane"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Skanowanie twarzy"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Wyślij"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Nie można rozpoznać twarzy"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Użyj odcisku palca"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth połączony."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Poziom naładowania baterii jest nieznany."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Połączono z <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Tryb samolotowy."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"Sieć VPN włączona."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Bateria: <xliff:g id="NUMBER">%d</xliff:g> procent."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria <xliff:g id="PERCENTAGE">%1$s</xliff:g> procent, jeszcze <xliff:g id="TIME">%2$s</xliff:g> (na podstawie Twojego sposobu korzystania)"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria <xliff:g id="PERCENTAGE">%1$d</xliff:g> procent, jeszcze <xliff:g id="TIME">%2$s</xliff:g> (na podstawie Twojego sposobu korzystania)"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Ładuję baterię, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> procent."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Zobacz wszystkie powiadomienia"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Dalekopis (TTY) włączony."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Dzwonek z wibracjami."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Zarządzaj użytkownikami"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon jest dostępny"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Aparat jest dostępny"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon i aparat są dostępne"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon włączony"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon wyłączony"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon jest włączony w przypadku wszystkich aplikacji i usług."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Dostęp do mikrofonu jest zablokowany w przypadku wszystkich aplikacji i usług. Możesz włączyć dostęp do mikrofonu, klikając Ustawienia &gt; Prywatność &gt; Mikrofon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Dostęp do mikrofonu jest zablokowany w przypadku wszystkich aplikacji i usług. Możesz to zmienić, klikając Ustawienia &gt; Prywatność &gt; Mikrofon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Aparat włączony"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Aparat wyłączony"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Aparat jest włączony w przypadku wszystkich aplikacji i usług."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Dostęp do aparatu jest zablokowany w przypadku wszystkich aplikacji i usług."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Aby używać przycisku mikrofonu, włącz dostęp do mikrofonu w Ustawieniach."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Otwórz ustawienia"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Inne urządzenie"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Przełącz Przegląd"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Nie będą Cię niepokoić żadne dźwięki ani wibracje z wyjątkiem alarmów, przypomnień, wydarzeń i połączeń od wybranych osób. Będziesz słyszeć wszystkie odtwarzane treści, takie jak muzyka, filmy czy gry."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Podczas udostępniania, nagrywania lub przesyłania treści aplikacja <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Dalej"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Udostępnianie i nagrywanie za pomocą aplikacji"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Zezwolić tej aplikacji na udostępnianie lub nagrywanie?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Podczas udostępniania, nagrywania lub przesyłania treści ta aplikacja ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Podczas udostępniania, nagrywania lub przesyłania treści ta aplikacja ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Zablokowane przez administratora IT"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Zrzuty ekranu są wyłączone zgodnie z zasadami dotyczącymi urządzeń"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Usuń wszystkie"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Zarządzaj"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odblokuj, aby użyć"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Podczas pobierania kart wystąpił problem. Spróbuj ponownie później."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ustawienia ekranu blokady"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Zeskanuj kod QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Profil służbowy"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Tryb samolotowy"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Nie usłyszysz swojego następnego alarmu <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Wrócić do operatora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilna transmisja danych nie będzie automatycznie przełączana na podstawie dostępności"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nie, dziękuję"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Tak, wróć"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Powiększanie pełnego ekranu"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Powiększ część ekranu"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Przełącz"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Zezwalaj na przewijanie poprzeczne"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Zmień rozmiar"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Zmień typ powiększenia"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Cofnij"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} skrót został usunięty}few{# skróty zostały usunięte}many{# skrótów zostało usuniętych}other{# skrótu został usunięte}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Usuń"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tymczasowe połączenie"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Słabe połączenie"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Zezwolić aplikacji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> na dostęp do wszystkich dzienników urządzenia?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Zezwól na jednorazowy dostęp"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Nie zezwalaj"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Dzienniki urządzenia zapisują, co dzieje się na urządzeniu. Aplikacje mogą ich używać do wykrywania i rozwiązywania problemów.\n\nNiektóre dzienniki mogą zawierać poufne dane, dlatego na dostęp do wszystkich dzienników zezwalaj tylko aplikacjom, którym ufasz. \n\nNawet jeśli nie zezwolisz tej aplikacji na dostęp do wszystkich dzienników na urządzeniu, będzie mogła korzystać z własnych. Producent urządzenia nadal będzie mógł używać niektórych dzienników na urządzeniu."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 78a6409..6e116be 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Sempre permitir a partir deste computador"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Permitir"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Depuração USB não permitida"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"O usuário conectado a este dispositivo não pode ativar a depuração USB. Para usar esse recurso, mude para o usuário principal \"NAME\"."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"O usuário conectado a este dispositivo não pode ativar a depuração USB. Para usar esse recurso, entre em uma conta de usuário administrador."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Quer mudar o idioma do sistema para <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Mudança do idioma do sistema solicitada por outro dispositivo"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Alterar idioma"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Sempre permitir nesta rede"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Permitir"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Depuração por Wi-Fi não permitida"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"O usuário conectado a este dispositivo não pode ativar a depuração por Wi-Fi. Para usar esse recurso, conecte-se como o usuário principal."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"O usuário conectado a este dispositivo não pode ativar a depuração por Wi-Fi. Para usar esse recurso, entre em uma conta de usuário administrador."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Porta USB desativada"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Para proteger seu dispositivo de líquidos e detritos, a porta USB está desativada e não detectará nenhum acessório.\n\nVocê receberá uma notificação quando for seguro usar a porta USB novamente."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Porta USB ativada para detectar carregadores e acessórios"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Assistência de voz"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Carteira"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Leitor de código QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Desbloquear"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositivo bloqueado"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Verificando rosto"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Enviar"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Rosto não reconhecido"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use a impressão digital"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentagem da bateria desconhecida."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Modo avião."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ativada."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Bateria em <xliff:g id="NUMBER">%d</xliff:g> por cento."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria com <xliff:g id="PERCENTAGE">%1$s</xliff:g> de carga, tempo restante aproximado, com base no seu uso: <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria com <xliff:g id="PERCENTAGE">%1$d</xliff:g> de carga, tempo restante aproximado, com base no seu uso: <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Bateria carregando: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Ver todas as notificações"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTYpewriter ativado."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibração da campainha."</string>
@@ -248,7 +255,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 de cor"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Config. do usuário"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gerenciar usuários"</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">"Conectado"</string>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microfone disponível"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Câmera disponível"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microfone e câmera disponíveis"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microfone ativado"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microfone desativado"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"O microfone está ativado para todos os apps e serviços."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"O acesso ao microfone está desativado para todos os apps e serviços. Ative em \"Configurações &gt; Privacidade &gt; Microfone\"."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"O acesso ao microfone está desativado para todos os apps e serviços. Você pode mudar isso em \"Configurações &gt; Privacidade &gt; Microfone\"."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Câmera ativada"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Câmera desativada"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"A câmera está ativada para todos os apps e serviços."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"O acesso à câmera está desativado para todos os apps e serviços."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Ative o acesso ao microfone nas Configurações para usar o botão dele."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Abrir configurações."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Outro dispositivo"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Alternar Visão geral"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Você não será perturbado por sons e vibrações, exceto alarmes, lembretes, eventos e chamadas de pessoas especificadas. No entanto, você ouvirá tudo o que decidir reproduzir, como músicas, vídeos e jogos."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando você compartilha, grava ou transmite um app, o <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a todas as informações visíveis na tela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens ou outras informações sensíveis."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartilhar ou gravar um app"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Permitir que este app compartilhe ou grave a tela?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Quando você compartilha, grava ou transmite a tela, este app tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens e outras informações sensíveis."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Quando você compartilha, grava ou transmite a tela, este app tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens e outras informações sensíveis."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Ação bloqueada pelo administrador de TI"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de tela foi desativada pela política do dispositivo"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gerenciar"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao carregar os cards. Tente novamente mais tarde"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configurações de tela de bloqueio"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Ler código QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Modo avião"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Você não ouvirá o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Voltar para a operadora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"A conexão de dados móveis não vai ser alternada automaticamente de acordo com a disponibilidade"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Agora não"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sim, voltar"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar toda a tela"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte da tela"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Trocar"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir rolagem diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionar"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Mudar tipo de ampliação"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfazer"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} atalho removido}one{# atalho removido}many{# de atalhos removidos}other{# atalhos removidos}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Remover"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporariamente conectado"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexão fraca"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d de MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Permitir que o app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acesse todos os registros do dispositivo?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Permitir o acesso único"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Não permitir"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 30233b3..70f09ac 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Permitir sempre a partir deste computador"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Permitir"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Depuração USB não permitida"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"O utilizador com sessão iniciada atualmente neste dispositivo não pode ativar a depuração USB. Para usar esta funcionalidade, mude para o utilizador principal."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"O utilizador com sessão iniciada atualmente neste dispositivo não pode ativar a depuração USB. Para usar esta funcionalidade, mude para um utilizador administrador."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Quer alterar o idioma do sistema para <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Alteração do idioma do sistema solicitada por outro dispositivo"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Alterar idioma"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Permitir sempre nesta rede"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Permitir"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Depuração sem fios não permitida"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"O utilizador com sessão iniciada atualmente neste dispositivo não pode ativar a depuração sem fios. Para usar esta funcionalidade, mude para o utilizador principal."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"O utilizador com sessão iniciada atualmente neste dispositivo não pode ativar a depuração sem fios. Para usar esta funcionalidade, mude para um utilizador administrador."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Porta USB desativada"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Para proteger o dispositivo contra líquidos ou resíduos, a porta USB está desativada e não irá detetar quaisquer acessórios.\n\nSerá notificado quando for seguro utilizar a porta USB novamente."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Porta USB ativada para detetar carregadores e acessórios"</string>
@@ -125,7 +125,7 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Assistente de voz"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Carteira"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Leitor de códigos QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Desbloquear"</string>
+    <string name="accessibility_unlock_button" msgid="3613812140816244310">"Desbloqueado"</string>
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositivo bloqueado"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"A analisar o rosto…"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Enviar"</string>
@@ -143,7 +143,7 @@
     <string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"Rosto reconhecido. Prima para continuar."</string>
     <string name="biometric_dialog_tap_confirm_with_face_alt_3" msgid="2192670471930606539">"Rosto reconhecido. Prima ícone de desbloqueio para continuar"</string>
     <string name="biometric_dialog_authenticated" msgid="7337147327545272484">"Autenticado"</string>
-    <string name="biometric_dialog_use_pin" msgid="8385294115283000709">"Utilizar PIN"</string>
+    <string name="biometric_dialog_use_pin" msgid="8385294115283000709">"Usar PIN"</string>
     <string name="biometric_dialog_use_pattern" msgid="2315593393167211194">"Utilizar padrão"</string>
     <string name="biometric_dialog_use_password" msgid="3445033859393474779">"Utilizar palavra-passe"</string>
     <string name="biometric_dialog_wrong_pin" msgid="1878539073972762803">"PIN incorreto."</string>
@@ -168,6 +168,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Imposs. reconhecer rosto"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usar impressão digital"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ligado."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percentagem da bateria desconhecida."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ligado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +180,10 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Modo de avião"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ativada."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Bateria a <xliff:g id="NUMBER">%d</xliff:g> por cento."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria a <xliff:g id="PERCENTAGE">%1$s</xliff:g> por cento, resta(m) cerca de <xliff:g id="TIME">%2$s</xliff:g> com base na sua utilização."</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria a <xliff:g id="PERCENTAGE">%1$d</xliff:g> por cento, resta(m) cerca de <xliff:g id="TIME">%2$s</xliff:g> com base na sua utilização."</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Bateria a carregar (<xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%)."</string>
+    <string name="accessibility_battery_level_charging_paused" msgid="1716051308782906917">"Bateria a <xliff:g id="PERCENTAGE">%d</xliff:g> por cento. O carregamento foi pausado para proteção da bateria."</string>
+    <string name="accessibility_battery_level_charging_paused_with_estimate" msgid="4006089349465741762">"Bateria a <xliff:g id="PERCENTAGE">%1$d</xliff:g> por cento, resta(m) cerca de <xliff:g id="TIME">%2$s</xliff:g> com base na sua utilização. O carregamento foi pausado para proteção da bateria."</string>
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Ver todas as notificações"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletipo ativado."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Campainha em vibração."</string>
@@ -248,7 +252,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>
@@ -303,6 +307,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microfone disponível"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Câmara disponível"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microfone e câmara disponíveis"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microfone ativado"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microfone desativado"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"O microfone está ativado para todas as apps e serviços."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"O acesso ao microfone está desativado para todas as apps e serviços. Pode ativar o acesso ao microfone em Definições &gt; Privacidade &gt; Microfone."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"O acesso ao microfone está desativado para todas as apps e serviços. Pode alterar esta opção em Definições &gt; Privacidade &gt; Microfone."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Câmara ativada"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Câmara desativada"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"A câmara está ativada para todas as apps e serviços."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"O acesso à câmara está desativado para todas as apps e serviços."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Para usar o botão do microfone, ative o acesso do microfone nas Definições."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Abrir definições."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Outro dispositivo"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Ativar/desativar Vista geral"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Não é incomodado por sons e vibrações, exceto de alarmes, lembretes, eventos e autores de chamadas que especificar. Continua a ouvir tudo o que optar por reproduzir, incluindo música, vídeos e jogos."</string>
@@ -373,6 +388,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando está a partilhar, gravar ou transmitir uma app, a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partilhe ou grave uma app"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Permitir que esta app partilhe ou grave?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Quando está a partilhar, gravar ou transmitir, esta app tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Quando está a partilhar, gravar ou transmitir uma app, esta app tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueado pelo administrador de TI"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de ecrã está desativada pela política do dispositivo"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gerir"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string>
@@ -488,7 +508,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para utilizar"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao obter os seus cartões. Tente mais tarde."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Definições do ecrã de bloqueio"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Leia o código QR"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"Leitor de códigos QR"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Modo de avião"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Não vai ouvir o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +747,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Mudar de novo para <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Os dados móveis não vão mudar automaticamente com base na disponibilidade"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Não"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sim, mudar"</string>
     <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>
@@ -767,6 +791,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar o ecrã inteiro"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte do ecrã"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Mudar"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir deslocamento da página na diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionar"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Alterar tipo de ampliação"</string>
@@ -785,12 +811,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Anular"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} atalho removido}many{# atalhos removidos}other{# atalhos removidos}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Remover"</string>
     <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 +962,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ligado temporariamente"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Ligação fraca"</string>
     <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>
@@ -992,4 +1023,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d de MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Permitir que a app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> aceda a todos os registos do dispositivo?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Permitir acesso único"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Não permitir"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Os registos do dispositivo documentam o que ocorre no seu dispositivo. As apps podem usar esses registos para detetar e corrigir problemas.\n\nAlguns registos podem conter informações confidenciais e, por isso, o acesso a todos os registos do dispositivo só deve ser permitido às apps nas quais confia. \n\nSe não permitir o acesso desta app a todos os registos do dispositivo, esta pode ainda assim aceder aos próprios registos. O fabricante do dispositivo pode continuar a aceder a alguns registos ou informações no seu dispositivo."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 78a6409..6e116be 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Sempre permitir a partir deste computador"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Permitir"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Depuração USB não permitida"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"O usuário conectado a este dispositivo não pode ativar a depuração USB. Para usar esse recurso, mude para o usuário principal \"NAME\"."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"O usuário conectado a este dispositivo não pode ativar a depuração USB. Para usar esse recurso, entre em uma conta de usuário administrador."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Quer mudar o idioma do sistema para <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Mudança do idioma do sistema solicitada por outro dispositivo"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Alterar idioma"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Sempre permitir nesta rede"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Permitir"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Depuração por Wi-Fi não permitida"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"O usuário conectado a este dispositivo não pode ativar a depuração por Wi-Fi. Para usar esse recurso, conecte-se como o usuário principal."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"O usuário conectado a este dispositivo não pode ativar a depuração por Wi-Fi. Para usar esse recurso, entre em uma conta de usuário administrador."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Porta USB desativada"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Para proteger seu dispositivo de líquidos e detritos, a porta USB está desativada e não detectará nenhum acessório.\n\nVocê receberá uma notificação quando for seguro usar a porta USB novamente."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Porta USB ativada para detectar carregadores e acessórios"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Assistência de voz"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Carteira"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Leitor de código QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Desbloquear"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositivo bloqueado"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Verificando rosto"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Enviar"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Rosto não reconhecido"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use a impressão digital"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentagem da bateria desconhecida."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Modo avião."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ativada."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Bateria em <xliff:g id="NUMBER">%d</xliff:g> por cento."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria com <xliff:g id="PERCENTAGE">%1$s</xliff:g> de carga, tempo restante aproximado, com base no seu uso: <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria com <xliff:g id="PERCENTAGE">%1$d</xliff:g> de carga, tempo restante aproximado, com base no seu uso: <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Bateria carregando: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Ver todas as notificações"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTYpewriter ativado."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibração da campainha."</string>
@@ -248,7 +255,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 de cor"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Config. do usuário"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gerenciar usuários"</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">"Conectado"</string>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microfone disponível"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Câmera disponível"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microfone e câmera disponíveis"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microfone ativado"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microfone desativado"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"O microfone está ativado para todos os apps e serviços."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"O acesso ao microfone está desativado para todos os apps e serviços. Ative em \"Configurações &gt; Privacidade &gt; Microfone\"."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"O acesso ao microfone está desativado para todos os apps e serviços. Você pode mudar isso em \"Configurações &gt; Privacidade &gt; Microfone\"."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Câmera ativada"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Câmera desativada"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"A câmera está ativada para todos os apps e serviços."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"O acesso à câmera está desativado para todos os apps e serviços."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Ative o acesso ao microfone nas Configurações para usar o botão dele."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Abrir configurações."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Outro dispositivo"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Alternar Visão geral"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Você não será perturbado por sons e vibrações, exceto alarmes, lembretes, eventos e chamadas de pessoas especificadas. No entanto, você ouvirá tudo o que decidir reproduzir, como músicas, vídeos e jogos."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando você compartilha, grava ou transmite um app, o <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a todas as informações visíveis na tela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens ou outras informações sensíveis."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartilhar ou gravar um app"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Permitir que este app compartilhe ou grave a tela?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Quando você compartilha, grava ou transmite a tela, este app tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens e outras informações sensíveis."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Quando você compartilha, grava ou transmite a tela, este app tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens e outras informações sensíveis."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Ação bloqueada pelo administrador de TI"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de tela foi desativada pela política do dispositivo"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gerenciar"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao carregar os cards. Tente novamente mais tarde"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configurações de tela de bloqueio"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Ler código QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Modo avião"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Você não ouvirá o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Voltar para a operadora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"A conexão de dados móveis não vai ser alternada automaticamente de acordo com a disponibilidade"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Agora não"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sim, voltar"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar toda a tela"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte da tela"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Trocar"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir rolagem diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionar"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Mudar tipo de ampliação"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfazer"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} atalho removido}one{# atalho removido}many{# de atalhos removidos}other{# atalhos removidos}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Remover"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporariamente conectado"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexão fraca"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d de MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Permitir que o app <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> acesse todos os registros do dispositivo?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Permitir o acesso único"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Não permitir"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Os registros do dispositivo gravam o que acontece nele. Os apps podem usar esses registros para encontrar e corrigir problemas.\n\nAlguns registros podem conter informações sensíveis, então autorize o acesso a eles apenas para os apps em que você confia. \n\nSe você não permitir que esse app acesse todos os registros do dispositivo, ele ainda vai poder acessar os próprios. O fabricante do dispositivo também pode ter acesso a alguns registros ou informações."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 0d96062..42e62be 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Permite întotdeauna de pe acest computer"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Permite"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Remedierea erorilor prin USB nu este permisă"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Utilizatorul conectat momentan pe acest dispozitiv nu poate activa remedierea erorilor prin USB. Pentru a folosi această funcție, comută la utilizatorul principal."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Utilizatorul conectat momentan pe acest dispozitiv nu poate activa remedierea erorilor prin USB. Pentru a folosi această funcție, comută la administrator."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Schimbi limba de sistem la <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Alt dispozitiv solicită schimbarea limbii de sistem"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Schimbă limba"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Permite întotdeauna în această rețea"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Permite"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Remedierea erorilor wireless nu este permisă"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Utilizatorul conectat momentan pe acest dispozitiv nu poate activa remedierea erorilor wireless. Pentru a folosi această funcție, comută la utilizatorul principal."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Utilizatorul conectat momentan pe acest dispozitiv nu poate activa remedierea erorilor wireless. Pentru a folosi această funcție, comută la administrator."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Portul USB a fost dezactivat"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Pentru a proteja dispozitivul de lichide sau reziduuri, portul USB este dezactivat și nu va detecta niciun accesoriu.\n\nVei primi o notificare când poți folosi din nou portul USB."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Portul USB a fost activat pentru a detecta încărcătoarele și accesoriile"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Asistent vocal"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Portofel"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Scanner de coduri QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Deblochează"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Dispozitiv blocat"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanarea chipului"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Trimite"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Chip nerecunoscut"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Folosește amprenta"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Conectat prin Bluetooth."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procentajul bateriei este necunoscut."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectat la <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Mod Avion."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"Rețea VPN activată"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Baterie: <xliff:g id="NUMBER">%d</xliff:g> la sută."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Procentul rămas din baterie este <xliff:g id="PERCENTAGE">%1$s</xliff:g>. În baza utilizării, timpul rămas este de aproximativ <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Procentul rămas din baterie este <xliff:g id="PERCENTAGE">%1$d</xliff:g>. În baza utilizării, timpul rămas este de aproximativ <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Bateria se încarcă, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> la sută."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Vezi toate notificările"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter activat."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibrare sonerie."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gestionează utilizatorii"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Microfon disponibil"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Cameră foto disponibilă"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microfon și cameră disponibile"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Microfonul a fost activat"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Microfonul a fost dezactivat"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Microfonul este activat pentru toate aplicațiile și serviciile."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Accesul la microfon este dezactivat pentru toate aplicațiile și serviciile. Activează accesul la microfon în Setări &gt; Confidențialitate &gt; Microfon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Accesul la microfon este dezactivat pentru toate aplicațiile și serviciile. Modifică opțiunea în Setări &gt; Confidențialitate &gt; Microfon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Camera este activată"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Camera este dezactivată"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Camera este activată pentru toate aplicațiile și serviciile."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Accesul la cameră este dezactivat pentru toate aplicațiile și serviciile."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Pentru a folosi butonul microfonului, activează accesul la microfon în Setări."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Deschide setările."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Alt dispozitiv"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Comută secțiunea Recente"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Se vor anunța prin sunete și vibrații numai alarmele, mementourile, evenimentele și apelanții specificați de tine. Totuși, vei auzi tot ce alegi să redai, inclusiv muzică, videoclipuri și jocuri."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Când permiți accesul, înregistrezi sau proiectezi o aplicație, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> are acces la orice se afișează pe ecran sau se redă în aplicație. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuă"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Permite accesul la o aplicație sau înregistreaz-o"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Permiți trimiterea sau înregistrarea din aplicație?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Când permiți accesul, înregistrezi sau proiectezi, aplicația are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Când permiți accesul, înregistrezi sau proiectezi o aplicație, aceasta are acces la orice se afișează pe ecran sau se redă în aplicație. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocată de administratorul IT"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Capturile de ecran sunt dezactivate de politica privind dispozitivele"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Șterge toate notificările"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionează"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Istoric"</string>
@@ -488,7 +511,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Deblochează pentru a folosi"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"A apărut o problemă la preluarea cardurilor. Încearcă din nou mai târziu"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Setările ecranului de blocare"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scanează codul QR"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"Scanner de coduri QR"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Profil de serviciu"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Mod Avion"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Nu vei auzi următoarea alarmă <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +750,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Revii la <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Comutarea la datele mobile nu se va face automat în funcție de disponibilitate"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nu, mulțumesc"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, fac trecerea"</string>
     <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>
@@ -767,6 +794,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Mărește tot ecranul"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Mărește o parte a ecranului"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Comutator"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permite derularea pe diagonală"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionează"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Schimbă tipul de mărire"</string>
@@ -785,12 +814,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Anulează"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} comandă rapidă eliminată}few{# comenzi rapide eliminate}other{# de comenzi rapide eliminate}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Elimină"</string>
     <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 +965,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Conectat temporar"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexiune slabă"</string>
     <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>
@@ -992,4 +1026,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EE, z LLL"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Permiți ca <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> să acceseze toate jurnalele dispozitivului?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Permite accesul o dată"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Nu permite"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Jurnalele dispozitivului înregistrează activitatea de pe dispozitivul tău. Aplicațiile pot folosi aceste jurnale pentru a identifica și a remedia probleme.\n\nUnele jurnale pot să conțină informații sensibile, prin urmare permite accesul la toate jurnalele dispozitivului doar aplicațiilor în care ai încredere. \n\nDacă nu permiți accesul aplicației la toate jurnalele dispozitivului, aceasta poate în continuare să acceseze propriile jurnale. Este posibil ca producătorul dispozitivului să acceseze în continuare unele jurnale sau informații de pe dispozitiv."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 3d13b2c..1bfca04 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Всегда разрешать отладку с этого компьютера"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Разрешить"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Отладка по USB запрещена"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"В этом аккаунте нельзя включить отладку по USB. Перейдите в аккаунт основного пользователя."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"В этом аккаунте нельзя включить отладку по USB. Перейдите в аккаунт администратора."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Изменить системный язык на <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Получен запрос на изменение системного языка от другого устройства."</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Изменить язык"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Всегда разрешать отладку в этой сети"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Разрешить"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Отладка по Wi-Fi запрещена"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"В этом аккаунте нельзя включить отладку по Wi-Fi. Перейдите в аккаунт основного пользователя."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"В этом аккаунте нельзя включить отладку по Wi-Fi. Перейдите в аккаунт администратора."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB-порт отключен"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Чтобы внутрь устройства не попала вода или грязь, USB-порт был отключен. Сейчас через него нельзя подсоединять другие устройства.\n\nКогда USB-порт снова можно будет использовать, вы получите уведомление."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB-порт активен и может распознавать аксессуары и зарядные устройства."</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Аудиоподсказки"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Кошелек"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Сканер QR-кодов"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Разблокировать."</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Устройство заблокировано"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Сканирование лица"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Отправить"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Лицо не распознано."</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Используйте отпечаток."</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-соединение установлено."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Уровень заряда батареи в процентах неизвестен."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>: подключено."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Режим полета."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"Режим VPN включен."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Заряд батареи в процентах: <xliff:g id="NUMBER">%d</xliff:g>."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Заряд батареи в процентах: <xliff:g id="PERCENTAGE">%1$s</xliff:g>. Оценка оставшегося времени работы: <xliff:g id="TIME">%2$s</xliff:g>."</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Заряд батареи в процентах: <xliff:g id="PERCENTAGE">%1$d</xliff:g>. Оценка оставшегося времени работы: <xliff:g id="TIME">%2$s</xliff:g>."</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Зарядка батареи. Текущий заряд: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Показать все уведомления"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Телетайп включен."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Вибровызов."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Микрофон готов."</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камера готова."</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Микрофон и камера готовы."</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Микрофон включен"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Микрофон отключен"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Микрофон включен для всех приложений и сервисов."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Доступ к микрофону отключен для всех приложений и сервисов. Вы можете разрешить доступ к микрофону, перейдя в Настройки &gt; Конфиденциальность &gt; Микрофон."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Доступ к микрофону отключен для всех приложений и сервисов. Вы можете изменить этот параметр, перейдя в Настройки &gt; Конфиденциальность &gt; Микрофон."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камера включена"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камера отключена"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Камера включена для всех приложений и сервисов."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Доступ к камере отключен для всех приложений и сервисов."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Чтобы использовать кнопку микрофона, разрешите доступ к микрофону в настройках."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Открыть настройки"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Другое устройство"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Переключить режим обзора"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Вас не будут отвлекать звуки и вибрация, за исключением сигналов будильника, напоминаний, уведомлений о мероприятиях и звонков от помеченных контактов. Вы по-прежнему будете слышать включенную вами музыку, видео, игры и т. д."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когда вы демонстрируете, транслируете экран или записываете видео с него, приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Далее"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Демонстрация экрана или запись видео с него"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Разрешить приложению демонстрировать экран или записывать видео с него?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Когда вы демонстрируете, транслируете экран или записываете видео с него, это приложение получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Когда вы демонстрируете, транслируете экран или записываете видео с него, это приложение получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблокировано вашим администратором"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Запись экрана отключена в соответствии с правилами для устройства."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Настроить"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Разблокировать для использования"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Не удалось получить информацию о картах. Повторите попытку позже."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Настройки заблокированного экрана"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Сканировать QR-код"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Рабочий профиль"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Режим полета"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Следующий будильник: <xliff:g id="WHEN">%1$s</xliff:g>. Звук отключен."</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Переключиться на сеть \"<xliff:g id="CARRIER">%s</xliff:g>\"?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобильный интернет не будет переключаться автоматически."</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Нет"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Да"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Увеличение всего экрана"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увеличить часть экрана"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Переключить"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Разрешить прокручивать по диагонали"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Изменить размер"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Изменить тип увеличения"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Отменить"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} сочетание клавиш удалено}one{# сочетание клавиш удалено}few{# сочетания клавиш удалено}many{# сочетаний клавиш удалено}other{# сочетания клавиш удалено}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Убрать"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Временное подключение"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Слабый сигнал"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"d MMM EEEE"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Разрешить приложению \"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>\" доступ ко всем журналам устройства?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Разрешить разовый доступ"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Запретить"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"В журналы записывается информация о том, что происходит на устройстве. Приложения могут использовать их, чтобы находить и устранять неполадки.\n\nТак как некоторые журналы могут содержать конфиденциальную информацию, доступ ко всем журналам следует предоставлять только тем приложениям, которым вы доверяете. \n\nЕсли вы не предоставите такой доступ этому приложению, оно по-прежнему сможет просматривать свои журналы. Не исключено, что некоторые журналы или сведения на вашем устройстве будут по-прежнему доступны его производителю."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 01be742..8d06d37 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"සැම විටම මෙම පරිගණකයෙන් ඉඩ ලබා දෙන්න"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"ඉඩ දෙන්න"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB නිදොස්කරණය වෙත අවසර නැහැ"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"දැනට මෙම උපාංගයට පුරා ඇති පරිශීලකයාට USB නිදොස්කරණය ක්‍රියාත්මක කළ නොහැක. මෙම විශේෂාංගය භාවිතා කිරීම සඳහා, මූලික පරිශීලකයා වෙත මාරු වෙන්න."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"දැනට මෙම උපාංගයට පුරා ඇති පරිශීලකයාට USB නිදොස්කරණය ක්‍රියාත්මක කළ නොහැක. මෙම විශේෂාංගය භාවිතා කිරීම සඳහා, පරිපාලක පරිශීලකයෙකු වෙත මාරු වෙන්න."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"ඔබට පද්ධති භාෂාව <xliff:g id="LANGUAGE">%1$s</xliff:g> භාෂාවට වෙනස් කිරීමට අවශ්‍යද?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"වෙනත් උපාංගයකින් පද්ධති භාෂාව වෙනස් කිරීම ඉල්ලා ඇත"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"භාෂාව වෙනස් කරන්න"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"මෙම ජාලයේ සැමවිට ඉඩ දෙන්න"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"ඉඩ දෙන්න"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"නොරැහැන් නිදොස්කරණය ඉඩ දී නැත"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"මෙම උපාංගයට දැනට පුරා ඇති පරිශීලකට නොරැහැන් නිදොස්කරණය ක්‍රියාත්මක කළ නොහැකිය. මෙම විශේෂාංගය භාවිතට, මූලික පරිශීලක වෙත මාරු වෙන්න."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"මෙම උපාංගයට දැනට පුරා ඇති පරිශීලකට නොරැහැන් නිදොස්කරණය ක්‍රියාත්මක කළ නොහැකිය. මෙම විශේෂාංගය භාවිතට, පරිපාලක පරිශීලකයෙකු වෙත මාරු වෙන්න."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB තොට අබලයි"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"ඔබේ උපාංගය ද්‍රවවලින් හෝ කුණුවලින් ආරක්‍ෂා කිරීමට, USB තොට අබල කර තිබෙන අතර, එය කිසිම අමතරාංගයක් අනාවරණ නොකරයි.\n\nනැවතත් USB තොට භාවිත කිරීම හරි නම් ඔබව දැනුම් දෙනු ලැබේ."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"ආරෝපක සහ උපකාරක අංග අනාවරණ කිරීමට USB තොට සබල කර ඇත"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"හඬ සහාය"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR කේත ස්කෑනරය"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"අඟුල අරින්න"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"උපාංගය අගුලු දමා ඇත"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"මුහුණ ස්කෑන් කිරීම"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"යවන්න"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"මුහුණ හඳුනා ගත නොහැක"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ඒ වෙනුවට ඇඟිලි සලකුණ භාවිත කරන්න"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"බ්ලූටූත් සම්බන්ධිතයි."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"බැටරි ප්‍රතිශතය නොදනී."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> වෙත සම්බන්ධ කරන ලදි."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"අහස්යානා ආකාරය."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ක්‍රියාත්මකයි."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"බැටරි ප්‍රතිශතය <xliff:g id="NUMBER">%d</xliff:g>"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"බැටරි ප්‍රතිශතය <xliff:g id="PERCENTAGE">%1$s</xliff:g>, ඔබේ භාවිතයට අනුව <xliff:g id="TIME">%2$s</xliff:g> ක් පමණ ඉතුරුයි"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"බැටරි ප්‍රතිශතය <xliff:g id="PERCENTAGE">%1$d</xliff:g>, ඔබේ භාවිතයට අනුව <xliff:g id="TIME">%2$s</xliff:g> ක් පමණ ඉතුරුයි"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"බැටරිය ආරෝපණය කරමින්, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"සියලු දැනුම්දීම් බලන්න"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter ක්‍රියාත්මකයි."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"හඬ නඟනය කම්පනය වේ."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"මයික්‍රෆෝනය ලබා ගත හැකිය"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"කැමරාව ලබා ගත හැකිය"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"මයික්‍රෆෝනය සහ කැමරාව ලබා ගත හැකිය"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"මයික්‍රෆෝනය ක්‍රියාත්මකයි"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"මයික්‍රෆෝනය ක්‍රියාවිරහිතයි"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"සියලු යෙදුම් සහ සේවා සඳහා මයික්‍රෆෝනය සබල කර ඇත."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"සියලු යෙදුම් සහ සේවා සඳහා මයික්‍රෆෝන ප්‍රවේශය අබල කර ඇත. ඔබට සැකසීම් &gt; පෞද්ගලිකත්වය &gt; මයික්‍රෆෝනය තුළ මයික්‍රෆෝන ප්‍රවේශය සබල කළ හැක."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"සියලු යෙදුම් සහ සේවා සඳහා මයික්‍රෆෝන ප්‍රවේශය අබල කර ඇත. ඔබට මෙය සැකසීම් &gt; පෞද්ගලිකත්වය &gt; මයික්‍රෆෝනය තුළ වෙනස් කළ හැක."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"කැමරාව ක්‍රියාත්මක කර ඇත"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"කැමරාව ක්‍රියාවිරහිතයි"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"සියලු යෙදුම් සහ සේවා සඳහා කැමරාව සබල කර ඇත."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"සියලු යෙදුම් සහ සේවා සඳහා කැමරා ප්‍රවේශය අබල කර ඇත."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"මයික්‍රෆෝන බොත්තම භාවිතය සඳහා, සැකසීම් තුළ මයික්‍රෆෝන ප්‍රවේශය සබල කරන්න."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"සැකසීම් විවෘත කරන්න."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"වෙනත් උපාංගය"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"දළ විශ්ලේෂණය ටොගල කරන්න"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"එලාම සිහිකැඳවීම්, සිදුවීම්, සහ ඔබ සඳහන් කළ අමතන්නන් හැර, ශබ්ද සහ කම්පනවලින් ඔබට බාධා නොවනු ඇත. සංගීතය, වීඩියෝ, සහ ක්‍රීඩා ඇතුළු ඔබ වාදනය කිරීමට තෝරන ලද සියලු දේ ඔබට තවම ඇසෙනු ඇත."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ඔබ යෙදුමක් බෙදා ගන්නා විට, පටිගත කරන විට හෝ විකාශය කරන විට, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්‍රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්‍රවේශම් වන්න."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ඉදිරියට යන්න"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"යෙදුමක් බෙදා ගන්න හෝ පටිගත කරන්න"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"මෙම යෙදුම බෙදා ගැනීමට හෝ පටිගත කිරීමට ඉඩ දෙන්න ද?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"ඔබ බෙදා ගන්නා විට, පටිගත කරන විට, හෝ විකාශය කරන විට, හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්‍රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්‍රවේශම් වන්න."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"ඔබ යෙදුමක් බෙදා ගන්නා විට, පටිගත කරන විට හෝ විකාශය කරන විට, හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්‍රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්‍රවේශම් වන්න."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ඔබේ IT පරිපාලක විසින් අවහිර කර ඇත"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"උපාංග ප්‍රතිපත්තිය මගින් තිර ග්‍රහණය කිරීම අබල කර ඇත"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"සියල්ල හිස් කරන්න"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"කළමනාකරණය කරන්න"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ඉතිහාසය"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"භාවිත කිරීමට අගුලු හරින්න"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"ඔබගේ කාඩ්පත ලබා ගැනීමේ ගැටලුවක් විය, කරුණාකර පසුව නැවත උත්සාහ කරන්න"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"අගුලු තිර සැකසීම්"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR කේතය ස්කෑන් කරන්න"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"කාර්යාල පැතිකඩ"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"ගුවන්යානා ප්‍රකාරය"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"ඔබට ඔබේ ඊළඟ එලාමය <xliff:g id="WHEN">%1$s</xliff:g> නොඇසෙනු ඇත"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> වෙත ආපසු මාරු කරන්නද?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ජංගම දත්ත ලබා ගත හැකි වීමට අනුව ස්වයංක්‍රීයව මාරු නොවෙයි"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"එපා ස්තුතියි"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ඔව්, මාරු කරන්න"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"පූර්ණ තිරය විශාලනය කරන්න"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"තිරයේ කොටසක් විශාලනය කරන්න"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ස්විචය"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"විකර්ණ අනුචලනයට ඉඩ දෙන්න"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ප්‍රතිප්‍රමාණය කරන්න"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"විශාලන වර්ගය වෙනස් කරන්න"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"අස් කරන්න"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} කෙටිමඟ ඉවත් කළා}one{කෙටිමං # ක් ඉවත් කළා}other{කෙටිමං # ක් ඉවත් කළා}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"ඉවත් කරන්න"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"තාවකාලිකව සම්බන්ධ කළා"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"දුර්වල සම්බන්ධතාව"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> හට සියලු උපාංග ලොග ප්‍රවේශ වීමට ඉඩ දෙන්න ද?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"එක් වරක් ප්‍රවේශය ඉඩ දෙන්න"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"ඉඩ නොදෙන්න"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"උපාංග ලොග ඔබේ උපාංගයෙහි සිදු වන දේ වාර්තා කරයි. ගැටලු සොයා ගැනීමට සහ නිරාකරණයට යෙදුම්වලට මෙම ලොග භාවිතා කළ හැක.\n\nසමහර ලොගවල සංවේදී තතු අඩංගු විය හැකි බැවින්, ඔබ විශ්වාස කරන යෙදුම්වලට පමණක් සියලු උපාංග ලොග වෙත ප්‍රවේශ වීමට ඉඩ දෙන්න. \n\nඔබ මෙම යෙදුමට සියලු උපාංග ලොග වෙත ප්‍රවේශ වීමට ඉඩ නොදෙන්නේ නම්, එයට තවමත් එහිම ලොග වෙත ප්‍රවේශ විය හැක. ඔබේ උපාංග නිෂ්පාදකයාට තවමත් ඔබේ උපාංගයෙහි සමහර ලොග හෝ තතු වෙත ප්‍රවේශ විය හැක."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 76dce74..2cd1163 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Vždy povoliť z tohto počítača"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Povoliť"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Ladenie cez USB nie je povolené"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Používateľ, ktorý je práve prihlásený v tomto zariadení, nemôže zapnúť ladenie USB. Ak chcete použiť túto funkciu, prepnite na hlavného používateľa."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Používateľ, ktorý je práve prihlásený v tomto zariadení, nemôže zapnúť ladenie USB. Ak chcete použiť túto funkciu, prepnite na správcu."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Chcete zmeniť jazyk systému na možnosť <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Zmenu jazyka systému vyžiadalo iné zariadenie"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Zmeniť jazyk"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Vždy povoliť v tejto sieti"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Povoliť"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Bezdrôtové ladenie nie je povolené"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Používateľ, ktorý je práve prihlásený v tomto zariadení, nemôže zapnúť bezdrôtové ladenie. Ak chcete použiť túto funkciu, prepnite na hlavného používateľa."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Používateľ, ktorý je práve prihlásený v tomto zariadení, nemôže zapnúť bezdrôtové ladenie. Ak chcete použiť túto funkciu, prepnite na správcu."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Port USB je deaktivovaný"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Port USB je deaktivovaný na zaistenie ochrany zariadenia pred tekutinami alebo nečistotami a nerozpoznáva príslušenstvo.\n\nKeď ho budete môcť znova použiť, upozorníme vás."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Bol povolený port USB na zisťovanie nabíjačiek a príslušenstva"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Hlasový asistent"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Peňaženka"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Skener QR kódov"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Odomknúť"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Zariadenie je uzamknuté"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Skenovanie tváre"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Odoslať"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Tvár sa nedá rozpoznať"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Používať radšej odtlačok"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth pripojené."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percento batérie nie je známe."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Pripojené k zariadeniu <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Režim v lietadle."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN je zapnuté."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Batéria <xliff:g id="NUMBER">%d</xliff:g> percent."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Percentá batérie: <xliff:g id="PERCENTAGE">%1$s</xliff:g>. Na základe vášho používania zostáva <xliff:g id="TIME">%2$s</xliff:g>."</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Percentá batérie: <xliff:g id="PERCENTAGE">%1$d</xliff:g>. Na základe vášho používania zostáva <xliff:g id="TIME">%2$s</xliff:g>."</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Nabíja sa batéria, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Zobraziť všetky upozornenia"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Rozhranie TeleTypewriter je povolené."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibračné zvonenie."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Spravovať používateľov"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofón je k dispozícii"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera je k dispozícii"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofón a kamera sú k dispozícii"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofón je zapnutý"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofón je vypnutý"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofón je povolený pre všetky aplikácie a služby."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Prístup k mikrofónu bol zakázaný pre všetky aplikácie a služby. Môžete ho povoliť v sekcii Nastavenia &gt; Ochrana súkromia &gt; Mikrofón."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Prístup k mikrofónu bol zakázaný pre všetky aplikácie a služby. Môžete to zmeniť v sekcii Nastavenia &gt; Ochrana súkromia &gt; Mikrofón."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera je zapnutá"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera je vypnutá"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera je povolená pre všetky aplikácie a služby."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Prístup ku kamere je zakázaný pre všetky aplikácie a služby."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Ak chcete použiť tlačidlo mikrofónu, povoľte prístup k mikrofónu v Nastaveniach."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Otvoriť nastavenia"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Iné zariadenie"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Prepnúť prehľad"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Nebudú vás vyrušovať zvuky ani vibrácie, iba budíky, pripomenutia, udalosti a volajúci, ktorých určíte. Budete naďalej počuť všetko, čo sa rozhodnete prehrať, ako napríklad hudbu, videá a hry."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Počas zdieľania, nahrávania alebo prenosu bude mať aplikácia <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> prístup k všetkému obsahu, ktorý sa v nej bude zobrazovať alebo prehrávať. Preto venujte zvýšenú pozornosť heslám, platobným údajom, správam a ďalším citlivým údajom."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Pokračovať"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Aplikácia na zdieľanie alebo nahrávanie"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Chcete povoliť tejto aplikácii zdieľať alebo nahrávať?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Počas zdieľania, nahrávania alebo prenosu bude mať táto aplikácia prístup k všetkému na obrazovke, prípadne k obsahu, ktorý sa bude v zariadení prehrávať. Venujte preto zvýšenú pozornosť heslám, platobným údajom, správam a ďalším citlivým údajom."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Počas zdieľania, nahrávania alebo prenosu bude mať táto aplikácia prístup k všetkému obsahu, ktorý sa v nej bude zobrazovať alebo prehrávať. Venujte preto zvýšenú pozornosť heslám, platobným údajom, správam či ďalším citlivým údajom."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokované vaším správcom IT"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snímanie obrazovky je zakázané pravidlami pre zariadenie"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Vymazať všetko"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Spravovať"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"História"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odomknúť a použiť"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Pri načítavaní kariet sa vyskytol problém. Skúste to neskôr."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavenia uzamknutej obrazovky"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skenovanie QR kódu"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Pracovný profil"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Režim v lietadle"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Váš budík o <xliff:g id="WHEN">%1$s</xliff:g> sa nespustí"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Chcete prepnúť späť na operátora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilné dáta sa nebudú automaticky prepínať na základe dostupnosti"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nie, vďaka"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Áno, prepnúť"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Zväčšenie celej obrazovky"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Zväčšiť časť obrazovky"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Prepnúť"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Povoliť diagonálne posúvanie"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Zmeniť veľkosť"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Zmeniť typ zväčšenia"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Späť"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Bola odstránená skratka {label}}few{Boli odstránené # skratky}many{# shortcuts removed}other{Bolo odstránených # skratiek}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Odstrániť"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Dočasne pripojené"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Slabé pripojenie"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d. MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Chcete povoliť aplikácii <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> prístup k všetkým denníkom zariadenia?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Povoliť jednorazový prístup"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Nepovoliť"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Denníky zariadenia zaznamenávajú, čo sa deje vo vašom zariadení. Aplikácie môžu pomocou týchto denníkov vyhľadávať a riešiť problémy.\n\nNiektoré denníky môžu obsahovať citlivé údaje, preto povoľte prístup k všetkým denníkom zariadenia iba dôveryhodným aplikáciám. \n\nAk tejto aplikácii nepovolíte prístup k všetkým denníkom zariadenia, stále bude mať prístup k vlastným denníkom. Výrobca vášho zariadenia bude mať naďalej prístup k niektorým denníkom alebo informáciám vo vašom zariadení."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index cb306c2..f97f834 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Vedno dovoli iz tega računalnika"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Dovoli"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Odpravljanje napak s povezavo USB ni dovoljeno"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Uporabnik, trenutno prijavljen v napravo, ne more vklopiti odpravljanja napak s povezavo USB. Če želite uporabljati to funkcijo, preklopite na primarnega uporabnika."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Uporabnik, trenutno prijavljen v napravo, ne more vklopiti odpravljanja napak s povezavo USB. Če želite uporabljati to funkcijo, preklopite na skrbniškega uporabnika."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Ali želite jezik sistema spremeniti na jezik <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Spremembo jezika sistema je zahtevala druga naprava."</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Spremeni jezik"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Vedno dovoli v tem omrežju"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Dovoli"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Brezžično odpravljanje napak ni dovoljeno"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Uporabnik, trenutno prijavljen v napravo, ne more vklopiti brezžičnega odpravljanja napak. Če želite uporabljati to funkcijo, preklopite na primarnega uporabnika."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Uporabnik, trenutno prijavljen v napravo, ne more vklopiti brezžičnega odpravljanja napak. Če želite uporabljati to funkcijo, preklopite na skrbniškega uporabnika."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Vrata USB so onemogočena"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Zaradi zaščite naprave pred tekočino ali umazanijo so vrata USB onemogočena in ne bodo zaznala nobene dodatne opreme.\n\nKo bo znova varno uporabljati vrata USB, boste obveščeni."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Vrata USB so omogočena za zaznavanje polnilnikov in dodatne opreme"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Glasovni pomočnik"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Google Denarnica"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Optični bralnik kod QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Odkleni"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Naprava je zaklenjena."</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Optično branje obraza"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Pošlji"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Obraz ni bil prepoznan."</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Uporabite prstni odtis."</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Povezava Bluetooth vzpostavljena."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Neznan odstotek napolnjenosti baterije."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezava vzpostavljena z: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Način za letalo."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"Omrežje VPN je vklopljeno."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Baterija <xliff:g id="NUMBER">%d</xliff:g> odstotkov."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Napolnjenost baterije je <xliff:g id="PERCENTAGE">%1$s</xliff:g>, glede na način uporabe imate na voljo še približno <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Napolnjenost baterije je <xliff:g id="PERCENTAGE">%1$d</xliff:g>, glede na način uporabe imate na voljo še približno <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Baterija se polni, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> odstotkov."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Prikaži vsa obvestila"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter omogočen."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Zvonjenje z vibriranjem."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Upravljanje uporabnikov"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon je na voljo"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera je na voljo"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon in kamera sta na voljo"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon je vklopljen"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon je izklopljen"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon je omogočen za vse aplikacije in storitve."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Dostop do mikrofona je onemogočen za vse aplikacije in storitve. Dostop do mikrofona lahko omogočite v »Nastavitve« &gt; »Zasebnost« &gt; »Mikrofon«."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Dostop do mikrofona je onemogočen za vse aplikacije in storitve. To lahko spremenite v »Nastavitve« &gt; »Zasebnost« &gt; »Mikrofon«."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Fotoaparat je vklopljen"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Fotoaparat je izklopljen"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Fotoaparat je omogočen za vse aplikacije in storitve."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Dostop do fotoaparata je onemogočen za vse aplikacije in storitve."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Za uporabo gumba z mikrofonom omogočite dostop do mikrofona v nastavitvah."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Odpri nastavitve"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Druga naprava"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Vklop/izklop pregleda"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Ne bodo vas motili zvoki ali vibriranje, razen v primeru alarmov, opomnikov, dogodkov in klicateljev, ki jih določite. Še vedno pa boste slišali vse, kar se boste odločili predvajati, vključno z glasbo, videoposnetki in igrami."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Pri deljenju, snemanju ali predvajanju aplikacije ima aplikacija <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> dostop do vsega, kar je prikazano ali predvajano v tej aplikaciji, zato bodite previdni z gesli, podatki za plačilo, sporočili ali drugimi občutljivimi podatki."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Naprej"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deljenje ali snemanje aplikacije"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Ali tej aplikaciji dovolite deljenje ali snemanje?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Pri deljenju, snemanju ali predvajanju ima ta aplikacija dostop do vsega, kar je prikazano na zaslonu ali se predvaja v napravi. Zato bodite previdni z gesli, podatki za plačilo, sporočili ali drugimi občutljivimi podatki."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Pri deljenju, snemanju ali predvajanju aplikacije ima ta aplikacija dostop do vsega, kar je prikazano ali predvajano v tisti aplikaciji, zato bodite previdni z gesli, podatki za plačilo, sporočili ali drugimi občutljivimi podatki."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokiral skrbnik za IT"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Zajemanje zaslonske slike je onemogočil pravilnik za naprave."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši vse"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljaj"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Zgodovina"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odklenite za uporabo"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Pri pridobivanju kartic je prišlo do težave. Poskusite znova pozneje."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavitve zaklepanja zaslona"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Optično branje kode QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Profil za Android Work"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Način za letalo"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Naslednjega alarma ob <xliff:g id="WHEN">%1$s</xliff:g> ne boste slišali"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Želite preklopiti nazaj na ponudnika <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Prenos podatkov v mobilnem omrežju ne preklopi samodejno glede na razpoložljivost."</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, hvala"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, preklopi"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Povečanje celotnega zaslona"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Povečava dela zaslona"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Stikalo"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Dovoli diagonalno pomikanje"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Spremeni velikost"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Sprememba vrste povečave"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Razveljavi"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Odstranjena bližnjica za fun. {label}}one{Odstranjena # bližnjica}two{Odstranjeni # bližnjici}few{Odstranjene # bližnjice}other{Odstranjenih # bližnjic}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Odstrani"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Začasno vzpostavljena povezava"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Slaba povezava"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d. MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Ali aplikaciji <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> dovolite dostop do vseh dnevnikov naprave?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Dovoli enkratni dostop"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Ne dovoli"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"V dnevnikih naprave se beleži dogajanje v napravi. Aplikacije lahko te dnevnike uporabijo za iskanje in odpravljanje težav.\n\nNekateri dnevniki morda vsebujejo občutljive podatke, zato dostop do vseh dnevnikov naprave omogočite le aplikacijam, ki jim zaupate. \n\nČe tej aplikaciji ne dovolite dostopa do vseh dnevnikov naprave, bo aplikacija kljub temu lahko dostopala do svojih dnevnikov. Proizvajalec naprave bo morda lahko kljub temu dostopal do nekaterih dnevnikov ali podatkov v napravi."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 9a6d73c..19417a5 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Lejo gjithmonë nga ky kompjuter"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Lejo"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Korrigjimi përmes USB-së nuk lejohet"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Përdoruesi i identifikuar aktualisht në këtë pajisje nuk mund ta aktivizojë korrigjimin përmes USB-së. Për ta përdorur këtë veçori, kalo te përdoruesi parësor."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Përdoruesi i identifikuar aktualisht në këtë pajisje nuk mund ta aktivizojë korrigjimin e USB-së. Për ta përdorur këtë funksion, kalo te përdoruesi administrator."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Dëshiron ta ndryshosh gjuhën e sistemit në <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Një pajisje tjetër kërkoi ndryshimin e gjuhës së sistemit"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Ndrysho gjuhën"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Lejo gjithmonë në këtë rrjet"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Lejo"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Korrigjimi përmes Wi-Fi nuk lejohet"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Përdoruesi i identifikuar aktualisht në këtë pajisje nuk mund ta aktivizojë korrigjimin përmes Wi-Fi. Për ta përdorur këtë veçori, kalo te përdoruesi parësor."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Përdoruesi i identifikuar aktualisht në këtë pajisje nuk mund ta aktivizojë korrigjimin përmes Wi-Fi. Për ta përdorur këtë veçori, kalo te përdoruesi administrator."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Porta e USB-së është çaktivizuar"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Për të mbrojtur pajisjen tënde nga lëngjet apo papastërtitë, porta e USB-së është çaktivizuar dhe nuk do t\'i dallojë aksesorët.\n\nDo të njoftohesh kur të mos jetë problem përdorimi përsëri i portës USB."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Porta USB është aktivizuar për të zbuluar karikuesit dhe aksesorët"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Ndihma zanore"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Portofoli"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Skaneri i kodeve QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Shkyç"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Pajisja është e kyçur"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Po skanon fytyrën"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Dërgo"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Fytyra nuk mund të njihet"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Përdor më mirë gjurmën e gishtit"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Pajisja është lidhur me \"bluetooth\"."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Përqindja e baterisë e panjohur."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Lidhur me <xliff:g id="BLUETOOTH">%s</xliff:g>"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"modaliteti i aeroplanit"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN-ja është aktive."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Bateria ka edhe <xliff:g id="NUMBER">%d</xliff:g> për qind."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria <xliff:g id="PERCENTAGE">%1$s</xliff:g> përqind, rreth <xliff:g id="TIME">%2$s</xliff:g> të mbetura bazuar në përdorimin tënd"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria <xliff:g id="PERCENTAGE">%1$d</xliff:g> përqind, rreth <xliff:g id="TIME">%2$s</xliff:g> të mbetura bazuar në përdorimin tënd"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Bateria po karikohet, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Shiko të gjitha njoftimet"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletajpi është i aktivizuar."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Zile me dridhje."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Menaxho përdoruesit"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofoni ofrohet"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera ofrohet"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofoni dhe kamera ofrohen"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofoni u aktivizua"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofoni u çaktivizua"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofoni është aktivizuar për të gjitha aplikacionet dhe shërbimet."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Qasja te mikrofoni është çaktivizuar për të gjitha aplikacionet dhe shërbimet. Mund ta aktivizosh qasjen te mikrofoni te \"Cilësimet &gt; Privatësia &gt; Mikrofoni\"."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Qasja te mikrofoni është çaktivizuar për të gjitha aplikacionet dhe shërbimet. Këtë mund ta ndryshosh te \"Cilësimet &gt; Privatësia &gt; Mikrofoni\"."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera u aktivizua"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera u çaktivizua"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera është aktivizuar për të gjitha aplikacionet dhe shërbimet."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Qasja te kamera është çaktivizuar për të gjitha aplikacionet dhe shërbimet."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Për të përdorur butonin e mikrofonit, aktivizo qasjen te mikrofoni te \"Cilësimet\"."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Hap cilësimet."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Pajisje tjetër"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Kalo te përmbledhja"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Nuk do të shqetësohesh nga tingujt dhe dridhjet, përveç alarmeve, alarmeve rikujtuese, ngjarjeve dhe telefonuesve që specifikon. Do të vazhdosh të dëgjosh çdo gjë që zgjedh të luash duke përfshirë muzikën, videot dhe lojërat."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Gjatë shpërndarjes, regjistrimit ose transmetimit të një aplikacioni, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Vazhdo"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Shpërndaj ose regjistro një aplikacion"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Të lejohet që ky aplikacion të shpërndajë ose të regjistrojë?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Gjatë shpërndarjes, regjistrimit ose transmetimit, ky aplikacion ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacionet e tjera të ndjeshme."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Gjatë shpërndarjes, regjistrimit ose transmetimit, ky aplikacioni ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacionet e tjera të ndjeshme."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"U bllokua nga administratori yt i teknologjisë së informacionit"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Regjistrimi i ekranit është çaktivizuar nga politika e pajisjes."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Pastroji të gjitha"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Menaxho"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historiku"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Shkyçe për ta përdorur"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Pati një problem me marrjen e kartave të tua. Provo përsëri më vonë"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Cilësimet e ekranit të kyçjes"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skano kodin QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Profili i punës"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Modaliteti i aeroplanit"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Nuk do ta dëgjosh alarmin e radhës në <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Të kalohet përsëri te <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Të dhënat celulare nuk do të ndërrohen automatikisht në bazë të disponueshmërisë"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Jo, faleminderit"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Po, ndërro"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Zmadho ekranin e plotë"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Zmadho një pjesë të ekranit"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Ndërro"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Lejo lëvizjen diagonale"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ndrysho përmasat"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Ndrysho llojin e zmadhimit"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Zhbëj"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Shkurtorja {label} u hoq}other{# shkurtore u hoqën}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Hiq"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Lidhur përkohësisht"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Lidhje e dobët"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Të lejohet që <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> të ketë qasje te të gjitha evidencat e pajisjes?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Lejo qasjen vetëm për një herë"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Mos lejo"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Evidencat e pajisjes regjistrojnë çfarë ndodh në pajisjen tënde. Aplikacionet mund t\'i përdorin këto evidenca për të gjetur dhe rregulluar problemet.\n\nDisa evidenca mund të përmbajnë informacione delikate, ndaj lejo vetëm aplikacionet që u beson të kenë qasje te të gjitha evidencat e pajisjes. \n\nNëse nuk e lejon këtë aplikacion që të ketë qasje te të gjitha evidencat e pajisjes, ai mund të vazhdojë të ketë qasje tek evidencat e tij. Prodhuesi i pajisjes sate mund të jetë ende në gjendje që të ketë qasje te disa evidenca ose informacione në pajisjen tënde."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 0dff11f..0d3a802 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Увек дозволи са овог рачунара"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Дозволи"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Отклањање грешака на USB-у није дозвољено"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Корисник који је тренутно пријављен на овај уређај не може да укључи отклањање грешака на USB-у. Да бисте користили ову функцију, пребаците на примарног корисника."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Корисник који је тренутно пријављен на овај уређај не може да укључи отклањање грешака са USB-а. Да бисте користили ову функцију, пређите на корисника са администраторским правима."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Да ли желите да промените језик система на <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Други уређај је затражио промену језика система"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Промени језик"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Увек дозволи на овој мрежи"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Дозволи"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Бежично отклањање грешака није дозвољено"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Корисник који је тренутно пријављен на овај уређај не може да укључи бежично отклањање грешака. Да бисте користили ову функцију, пређите на примарног корисника."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Корисник који је тренутно пријављен на овај уређај не може да укључи бежично отклањање грешака. Да бисте користили ову функцију, пређите на корисника са администраторским правима."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB порт је онемогућен"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Да би се уређај заштитио од течности или нечистоће, USB порт је онемогућен и неће откривати додатну опрему.\n\nОбавестићемо вас када поново будете могли да користите USB порт."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB порт је омогућен ради откривања пуњача и додатне опреме"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Гласовна помоћ"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Новчаник"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Скенер QR кода"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Откључајте"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Уређај је закључан"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Скенирање лица"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Пошаљи"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Лице није препознато"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Користите отисак прста"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth је прикључен."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Проценат напуњености батерије није познат."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Повезани сте са <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Режим рада у авиону."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN је укључен."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Батерија је на <xliff:g id="NUMBER">%d</xliff:g> посто."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батерија је на <xliff:g id="PERCENTAGE">%1$s</xliff:g> посто, преостало време на основу коришћења је <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батерија је на <xliff:g id="PERCENTAGE">%1$d</xliff:g> посто, преостало време на основу коришћења је <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Батерија се пуни, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> посто."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Погледајте сва обавештења"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter је омогућен."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Вибрација звона."</string>
@@ -248,7 +255,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">"Управљаjте корисницима"</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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Микрофон је доступан"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камера је доступна"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Микрофон и камера су доступни"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Микрофон је укључен"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Микрофон је искључен"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Микрофон је омогућен за све апликације и услуге."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Приступ микрофону је онемогућен за све апликације и услуге. Можете да омогућите приступ микрофону у Подешавањима &gt; Приватност &gt; Микрофон."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Приступ микрофону је онемогућен за све апликације и услуге. То можете да промените у Подешавањима &gt; Приватност &gt; Микрофон."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камера је укључена"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камера је искључена"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Камера је омогућена за све апликације и услуге."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Приступ камери је онемогућен за све апликације и услуге."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Да бисте користили дугме микрофона, омогућите приступ микрофону у Подешавањима."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Отвори Подешавања."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Други уређај"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Укључи/искључи преглед"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Неће вас узнемиравати звукови и вибрације осим за аларме, подсетнике, догађаје и позиваоце које наведете. И даље ћете чути све што одаберете да пустите, укључујући музику, видео снимке и игре."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Када делите, снимате или пребацујете апликацију, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Будите пажљиви са лозинкама, информацијама о плаћању, порукама или другим осетљивим информацијама."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Настави"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Делите или снимите апликацију"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Желите да дозволите овој апликацији да дели или снима?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Када делите, снимате или пребацујете, ова апликација има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Будите пажљиви са лозинкама, информацијама о плаћању, порукама или другим осетљивим информацијама."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Када делите, снимате или пребацујете апликацију, ова апликација има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Будите пажљиви са лозинкама, информацијама о плаћању, порукама или другим осетљивим информацијама."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокира ИТ администратор"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Снимање екрана је онемогућено смерницама за уређај"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Обриши све"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Управљајте"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Историја"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Откључај ради коришћења"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Дошло је до проблема при преузимању картица. Пробајте поново касније"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Подешавања закључаног екрана"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Скенирајте QR кôд"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Пословни профил"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Режим рада у авиону"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Нећете чути следећи аларм у <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Желите да се вратите на мобилног оператера <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобилни подаци се неће аутоматски променити на основу доступности"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Не, хвала"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Да, пређи"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Увећајте цео екран"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увећајте део екрана"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Пређи"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Дозволи дијагонално скроловање"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Промени величину"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Промени тип увећања"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Опозовите"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} пречица је уклоњена}one{# пречица је уклоњена}few{# пречице су уклоњене}other{# пречица је уклоњено}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Уклоните"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Привремено повезано"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Веза је лоша"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"ДДД, д. МММ"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"с:мин"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"ч:мин"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Желите да дозволите да <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> приступа свим евиденцијама уређаја?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Дозволи једнократан приступ"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Не дозволи"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Евиденције уређаја региструју шта се дешава на уређају. Апликације могу да користе те евиденције да би пронашле и решиле проблеме.\n\nНеке евиденције могу да садрже осетљиве информације, па приступ свим евиденцијама уређаја треба да дозвољавате само апликацијама у које имате поверења. \n\nАко не дозволите овој апликацији да приступа свим евиденцијама уређаја, она и даље може да приступа сопственим евиденцијама. Произвођач уређаја ће можда и даље моћи да приступа неким евиденцијама или информацијама на уређају."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index c1cdc5b..d56167e 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Tillåt alltid på den här datorn"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Tillåt"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB-felsökning är inte tillåtet"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Användaren som är inloggad på enheten för närvarande kan inte aktivera USB-felsökning. Byt till den primära användaren om du vill använda den här funktionen."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Användaren som är inloggad på enheten för närvarande kan inte aktivera USB-felsökning. Byt till administratörsanvändaren om du vill använda den här funktionen."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Vill du ändra systemspråket till <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Ändring av systemspråk har begärts av en annan enhet"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Ändra språk"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Tillåt alltid i det här nätverket"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Tillåt"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Trådlös felsökning är inte tillåtet"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Användaren som är inloggad på enheten för närvarande kan inte aktivera trådlös felsökning. Byt till den primära användaren om du vill använda den här funktionen."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Användaren som är inloggad på enheten för närvarande kan inte aktivera trådlös felsökning. Byt till administratörsanvändaren om du vill använda den här funktionen."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB-porten har inaktiverats"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"USB-porten har inaktiverats för att skydda enheten mot vätska eller smuts. Inga tillbehör kommer att hittas.\n\nDu meddelas när det går att använda USB-porten igen."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB-porten har aktiverats för identifiering av laddare och tillbehör"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Röstassistent"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-skanner"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Lås upp"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Enheten är låst"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Registrerar ansikte"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Skicka"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Ansiktet kändes inte igen"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Använd fingeravtryck"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ansluten."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Okänd batterinivå."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ansluten till <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Flygplansläge"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN har aktiverats."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Batteri <xliff:g id="NUMBER">%d</xliff:g> procent."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batteri: <xliff:g id="PERCENTAGE">%1$s</xliff:g> procent, cirka <xliff:g id="TIME">%2$s</xliff:g> kvar utifrån din användning"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batteri: <xliff:g id="PERCENTAGE">%1$d</xliff:g> procent, cirka <xliff:g id="TIME">%2$s</xliff:g> kvar utifrån din användning"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batteriet laddas, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> procent."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Visa alla aviseringar"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter aktiverad."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibrerande ringsignal."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Hantera användare"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofonen kan användas"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kameran kan användas"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofonen och kameran kan användas"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofonen sattes på"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofonen stängdes av"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofonen är aktiverad för alla appar och tjänster."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofonåtkomst är inaktiverad för alla appar och tjänster. Du kan aktivera mikrofonåtkomst i Inställningar &gt; Integritet &gt; Mikrofon."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofonåtkomst är inaktiverad för alla appar och tjänster. Du kan ändra detta i Inställningar &gt; Integritet &gt; Mikrofon."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kameran sattes på"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kameran stängdes av"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kameran är aktiverad för alla appar och tjänster."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kameraåtkomst är inaktiverad för alla appar och tjänster."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Aktivera mikrofonåtkomst i inställningarna om du vill använda mikrofonknappen."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Öppna inställningarna."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Annan enhet"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Aktivera och inaktivera översikten"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Du blir inte störd av ljud och vibrationer, förutom från alarm, påminnelser, händelser och specifika samtal. Ljudet är fortfarande på för sådant du väljer att spela upp, till exempel musik, videor och spel."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"När du delar, spelar in eller castar en app har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> åtkomst till allt som visas eller spelas upp i appen. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsätt"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dela eller spela in en app"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Vill du tillåta att den här appen delar eller spelar in?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"När du delar, spelar in eller castar har den här appen åtkomst till allt som visas på skärmen eller spelas upp på enheten. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"När du delar, spelar in eller castar en app har den här appen åtkomst till allt som visas eller spelas upp i appen. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blockeras av IT-administratören"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skärminspelning är inaktiverat av enhetspolicyn"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Rensa alla"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Hantera"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lås upp för att använda"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Det gick inte att hämta dina kort. Försök igen senare."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Inställningar för låsskärm"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skanna QR-kod"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Jobbprofil"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Flygplansläge"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Nästa alarm, kl. <xliff:g id="WHEN">%1$s</xliff:g>, kommer inte att höras"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vill du byta tillbaka till <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobildatakällan byts inte automatiskt efter tillgänglighet"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nej tack"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, byt"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Förstora hela skärmen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Förstora en del av skärmen"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Reglage"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Tillåt diagonal scrollning"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ändra storlek"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Ändra förstoringstyp"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Ångra"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} genväg har tagits bort}other{# genvägar har tagits bort}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Ta bort"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tillfälligt ansluten"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Dålig anslutning"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h.mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk.mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Vill du tillåta att <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> får åtkomst till alla enhetsloggar?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Tillåt engångsåtkomst"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Tillåt inte"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"I enhetsloggar registreras vad som händer på enheten. Appar kan använda dessa loggar för att hitta och åtgärda problem.\n\nVissa loggar kan innehålla känsliga uppgifter, så du ska bara bevilja appar du litar på åtkomst till alla enhetsloggar. \n\nEn app har åtkomst till sina egna loggar även om du inte ger den åtkomst till alla enhetsloggar. Enhetens tillverkare kan fortfarande ha åtkomst till vissa loggar eller viss information på enheten."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 8ed824e..c28244a 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Ruhusu kutoka kwenye kompyuta hii kila wakati"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Ruhusu"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Utatuzi wa USB hauruhusiwi"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Mtumiaji aliyeingia katika akaunti kwa kutumia kifaa hiki kwa sasa hawezi kuwasha utatuzi wa USB. Ili utumie kipengele hiki, tumia akaunti ya mtumiaji wa msingi."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Mtumiaji aliyeingia katika akaunti kwenye kifaa hiki kwa sasa hawezi kuwasha utatuzi wa USB. Ili utumie kipengele hiki, tumia akaunti ya mtumiaji msimamizi."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Ungependa kubadilisha lugha ya mfumo kuwa <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Mabadiliko ya lugha ya mfumo yameombwa na kifaa kingine"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Badilisha lugha"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Ruhusu kila wakati kwenye mtandao huu"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Ruhusu"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Utatuzi usiotumia waya hauruhusiwi"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Mtumiaji aliyeingia katika akaunti kwenye kifaa hiki kwa sasa hawezi kuwasha utatuzi usiotumia waya. Ili utumie kipengele hiki, badilisha utumie akaunti ya mtumiaji wa msingi."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Mtumiaji aliyeingia katika akaunti kwenye kifaa hiki kwa sasa hawezi kuwasha utatuzi usiotumia waya. Ili utumie kipengele hiki, badilisha utumie akaunti ya mtumiaji msimamizi."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Mlango wa USB umezimwa"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Ili ulinde kifaa chako dhidi ya vitu vyenye unyevu au uchafu, mlango wa USB umezimwa na hautatambua vifaa vyovyote.\n\nUtaarifiwa itapokuwa sawa kutumia mlango wa USB tena."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Mlango wa USB umewezeshwa ili utambue chaja na vifuasi"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Mapendekezo ya Sauti"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Pochi"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Kichanganuzi cha Msimbo wa QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Fungua"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Kifaa kimefungwa"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Inachanganua uso"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Tuma"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Imeshindwa kutambua uso"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Badala yake, tumia alama ya kidole"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth imeunganishwa."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Asilimia ya betri haijulikani."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Imeunganishwa kwenye <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Hali ya ndegeni."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN imewashwa."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Asilimia <xliff:g id="NUMBER">%d</xliff:g> ya betri"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Betri ina asilimia <xliff:g id="PERCENTAGE">%1$s</xliff:g>, zimesalia takribani <xliff:g id="TIME">%2$s</xliff:g> kulingana na jinsi unavyoitumia"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Betri ina asilimia <xliff:g id="PERCENTAGE">%1$d</xliff:g>, zimesalia takribani <xliff:g id="TIME">%2$s</xliff:g> kulingana na jinsi unavyoitumia"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Betri inachaji, asilimia <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Angalia arifa zote"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Kichapishaji cha Tele kimewezeshwa."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Mtetemo wa mlio"</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Dhibiti watumiaji"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Maikrofoni inapatikana"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera inapatikana"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Maikrofoni na kamera zinapatikana"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Umewasha maikrofoni"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Umezima maikrofoni"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Umewasha maikrofoni kwenye programu na huduma zote."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Umezima ufikiaji wa maikrofoni kwenye programu na huduma zote. Unaweza kuruhusu ufikiaji wa maikrofoni kwenye Mipangilio &gt; Faragha &gt; Maikrofoni."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Umezima ufikiaji wa maikrofoni kwenye programu na huduma zote. Unaweza kubadilisha hali hii kwenye Mipangilio &gt; Faragha &gt; Maikrofoni."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Umewasha kamera"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Umezima kamera"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Umewasha kamera kwenye programu na huduma zote."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Umezima ufikiaji wa kamera kwenye programu na huduma zote."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Ili utumie kitufe cha maikrofoni, ruhusu ufikiaji wa maikrofoni kwenye Mipangilio."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Fungua mipangilio."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Kifaa kingine"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Washa Muhtasari"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Hutasumbuliwa na sauti na mitetemo, isipokuwa kengele, vikumbusho, matukio na simu zinazopigwa na watu uliobainisha. Bado utasikia chochote utakachochagua kucheza, ikiwa ni pamoja na muziki, video na michezo."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Unapotuma, kurekodi au kushiriki programu, programu ya <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> inaweza kufikia kitu chochote kitakachoonekana au kuchezwa kwenye programu hiyo. Hivyo kuwa mwangalifu na manenosiri, maelezo ya malipo, ujumbe au maelezo mengine nyeti."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Endelea"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Shiriki au rekodi programu"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Ungependa kuruhusu programu hii ishiriki au irekodi?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Unapotuma, kurekodi au kushiriki, programu hii inaweza kufikia kitu chochote kitakachoonekana kwenye skrini yako au kuchezwa kwenye kifaa chako. Hivyo kuwa mwangalifu na manenosiri, maelezo ya malipo, ujumbe au maelezo mengine nyeti."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Unapotuma, kurekodi au kushiriki programu, programu hii inaweza kufikia kitu chochote kitakachoonekana au kuchezwa kwenye programu hiyo. Hivyo kuwa mwangalifu na manenosiri, maelezo ya malipo, ujumbe au maelezo mengine nyeti."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Umezuiwa na msimamizi wako wa TEHAMA"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Mchakato wa kurekodi skrini umezimwa na sera ya kifaa"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Futa zote"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Dhibiti"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Fungua ili utumie"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Hitilafu imetokea wakati wa kuleta kadi zako, tafadhali jaribu tena baadaye"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Mipangilio ya kufunga skrini"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Changanua msimbo wa QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Wasifu wa kazini"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Hali ya ndegeni"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Hutasikia kengele yako inayofuata ya saa <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Ungependa kubadilisha ili utumie data ya mtandao wa <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Data ya mtandao wa simu haitabadilika kiotomatiki kulingana na upatikanaji"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Hapana"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ndiyo, badili"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Kuza skrini nzima"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Kuza sehemu ya skrini"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Swichi"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Ruhusu usogezaji wa kimshazari"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Badilisha ukubwa"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Badili aina ya ukuzaji"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Tendua"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Njia {label} ya mkato imeondolewa}other{Njia # za mkato zimeondolewa}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Ondoa"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Imeunganishwa kwa muda"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Muunganisho duni"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"saa:dk"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:dk"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Ungependa kuruhusu <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ifikie kumbukumbu zote za kifaa?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Ruhusu ufikiaji wa mara moja"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Usiruhusu"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Kumbukumbu za kifaa hurekodi kinachofanyika kwenye kifaa chako. Programu zinaweza kutumia kumbukumbu hizi ili kutambua na kurekebisha hitilafu.\n\nHuenda baadhi ya kumbukumbu zikawa na taarifa nyeti, hivyo ruhusu tu programu unazoziamini kufikia kumbukumbu zote za kifaa. \n\nIwapo hutaruhusu programu hii ifikie kumbukumbu zote za kifaa, bado inaweza kufikia kumbukumbu zake yenyewe. Huenda mtengenezaji wa kifaa chako bado akaweza kufikia baadhi ya kumbukumbu au taarifa zilizopo kwenye kifaa chako."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sw360dp/dimens.xml b/packages/SystemUI/res/values-sw360dp/dimens.xml
index 65ca70b..03365b3 100644
--- a/packages/SystemUI/res/values-sw360dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw360dp/dimens.xml
@@ -25,5 +25,8 @@
 
     <!-- Home Controls -->
     <dimen name="global_actions_side_margin">12dp</dimen>
+
+    <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+    <dimen name="biometric_auth_pattern_view_size">298dp</dimen>
 </resources>
 
diff --git a/packages/SystemUI/res/values-sw392dp-land/dimens.xml b/packages/SystemUI/res/values-sw392dp-land/dimens.xml
new file mode 100644
index 0000000..1e26a69
--- /dev/null
+++ b/packages/SystemUI/res/values-sw392dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ 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>
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+    <dimen name="biometric_auth_pattern_view_max_size">248dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw392dp/dimens.xml b/packages/SystemUI/res/values-sw392dp/dimens.xml
index 78279ca..96af3c1 100644
--- a/packages/SystemUI/res/values-sw392dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw392dp/dimens.xml
@@ -24,5 +24,8 @@
 
     <!-- Home Controls -->
     <dimen name="global_actions_side_margin">16dp</dimen>
+
+    <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+    <dimen name="biometric_auth_pattern_view_size">298dp</dimen>
 </resources>
 
diff --git a/packages/SystemUI/res/values-sw410dp-land/dimens.xml b/packages/SystemUI/res/values-sw410dp-land/dimens.xml
new file mode 100644
index 0000000..c4d9b9b
--- /dev/null
+++ b/packages/SystemUI/res/values-sw410dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ 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>
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+    <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw410dp/dimens.xml b/packages/SystemUI/res/values-sw410dp/dimens.xml
index 7da47e5..ff6e005 100644
--- a/packages/SystemUI/res/values-sw410dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw410dp/dimens.xml
@@ -27,4 +27,6 @@
     <dimen name="global_actions_grid_item_side_margin">12dp</dimen>
     <dimen name="global_actions_grid_item_height">72dp</dimen>
 
+    <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
 </resources>
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-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index b24ce12..6c7cab5 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -24,6 +24,7 @@
     <!-- margin from keyguard status bar to clock. For split shade it should be
          keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp -->
     <dimen name="keyguard_clock_top_margin">8dp</dimen>
+    <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
 
     <!-- QS-->
     <dimen name="qs_panel_padding_top">16dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp-land/styles.xml b/packages/SystemUI/res/values-sw600dp-land/styles.xml
index 8148d3d..5ca2b43 100644
--- a/packages/SystemUI/res/values-sw600dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/styles.xml
@@ -16,16 +16,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <style name="AuthCredentialPatternContainerStyle">
-        <item name="android:gravity">center</item>
-        <item name="android:maxHeight">420dp</item>
-        <item name="android:maxWidth">420dp</item>
-        <item name="android:minHeight">200dp</item>
-        <item name="android:minWidth">200dp</item>
-        <item name="android:paddingHorizontal">120dp</item>
-        <item name="android:paddingVertical">40dp</item>
-    </style>
-
     <style name="TextAppearance.AuthNonBioCredential.Title">
         <item name="android:fontFamily">google-sans</item>
         <item name="android:layout_marginTop">16dp</item>
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index 347cf29..707bc9e 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -17,9 +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
          pages is margin * 2, and that makes tiles page not appear immediately after user swipes to
diff --git a/packages/SystemUI/res/values-sw600dp-port/styles.xml b/packages/SystemUI/res/values-sw600dp-port/styles.xml
index 771de08..41d931d 100644
--- a/packages/SystemUI/res/values-sw600dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/styles.xml
@@ -24,16 +24,6 @@
         <item name="android:layout_gravity">top</item>
     </style>
 
-    <style name="AuthCredentialPatternContainerStyle">
-        <item name="android:gravity">center</item>
-        <item name="android:maxHeight">420dp</item>
-        <item name="android:maxWidth">420dp</item>
-        <item name="android:minHeight">200dp</item>
-        <item name="android:minWidth">200dp</item>
-        <item name="android:paddingHorizontal">180dp</item>
-        <item name="android:paddingVertical">80dp</item>
-    </style>
-
     <style name="TextAppearance.AuthNonBioCredential.Title">
         <item name="android:fontFamily">google-sans</item>
         <item name="android:layout_marginTop">24dp</item>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 80628f9..f4434e8 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -36,7 +36,4 @@
     <integer name="qs_security_footer_maxLines">1</integer>
 
     <bool name="config_use_large_screen_shade_header">true</bool>
-
-    <!-- Whether to show the side fps hint while on bouncer -->
-    <bool name="config_show_sidefps_hint_on_bouncer">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 599bf30..9bc0dde 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -92,4 +92,6 @@
     <dimen name="lockscreen_shade_status_bar_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
     <dimen name="lockscreen_shade_keyguard_transition_distance">@dimen/lockscreen_shade_media_transition_distance</dimen>
 
+    <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
 </resources>
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..b98165f
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.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.
+  -->
+<!-- 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>
+    <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">186dp</dimen>
+    <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">110dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 868c003..3fc59e3 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -35,6 +35,11 @@
          not appear immediately after user swipes to the side -->
     <dimen name="qs_tiles_page_horizontal_margin">20dp</dimen>
 
+    <!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
+    <dimen name="qs_media_rec_icon_top_margin">27dp</dimen>
+    <dimen name="qs_media_rec_album_size">152dp</dimen>
+    <dimen name="qs_media_rec_album_side_margin">16dp</dimen>
+
     <dimen name="lockscreen_shade_max_over_scroll_amount">42dp</dimen>
 
     <!-- Roughly the same distance as media on LS to media on QS. We will translate by this value
diff --git a/packages/SystemUI/res/values-sw720dp-land/styles.xml b/packages/SystemUI/res/values-sw720dp-land/styles.xml
index f9ed67d..d9406d3 100644
--- a/packages/SystemUI/res/values-sw720dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/styles.xml
@@ -16,16 +16,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <style name="AuthCredentialPatternContainerStyle">
-        <item name="android:gravity">center</item>
-        <item name="android:maxHeight">420dp</item>
-        <item name="android:maxWidth">420dp</item>
-        <item name="android:minHeight">200dp</item>
-        <item name="android:minWidth">200dp</item>
-        <item name="android:paddingHorizontal">120dp</item>
-        <item name="android:paddingVertical">40dp</item>
-    </style>
-
     <style name="TextAppearance.AuthNonBioCredential.Title">
         <item name="android:fontFamily">google-sans</item>
         <item name="android:layout_marginTop">16dp</item>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index 3d8da8a..8b41a44 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -21,9 +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>
     <dimen name="qqs_layout_padding_bottom">40dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-port/styles.xml b/packages/SystemUI/res/values-sw720dp-port/styles.xml
index 78d299c..41d931d 100644
--- a/packages/SystemUI/res/values-sw720dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/styles.xml
@@ -24,16 +24,6 @@
         <item name="android:layout_gravity">top</item>
     </style>
 
-    <style name="AuthCredentialPatternContainerStyle">
-        <item name="android:gravity">center</item>
-        <item name="android:maxHeight">420dp</item>
-        <item name="android:maxWidth">420dp</item>
-        <item name="android:minHeight">200dp</item>
-        <item name="android:minWidth">200dp</item>
-        <item name="android:paddingHorizontal">240dp</item>
-        <item name="android:paddingVertical">120dp</item>
-    </style>
-
     <style name="TextAppearance.AuthNonBioCredential.Title">
         <item name="android:fontFamily">google-sans</item>
         <item name="android:layout_marginTop">24dp</item>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 0705017..927059a 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -22,5 +22,8 @@
     <dimen name="controls_padding_horizontal">75dp</dimen>
 
     <dimen name="large_screen_shade_header_height">56dp</dimen>
+
+    <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
 </resources>
 
diff --git a/packages/SystemUI/res/values-sw800dp/dimens.xml b/packages/SystemUI/res/values-sw800dp/dimens.xml
new file mode 100644
index 0000000..0d82217
--- /dev/null
+++ b/packages/SystemUI/res/values-sw800dp/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.
+  -->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+
+    <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 9397b6c..ccf7b6b 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"இந்தக் கணினியிலிருந்து எப்போதும் அனுமதி"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"அனுமதி"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB பிழைதிருத்தம் அனுமதிக்கப்படவில்லை"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"தற்போது இந்தச் சாதனத்தில் உள்நுழைந்துள்ள பயனரால் USB பிழைதிருத்தத்தை இயக்க முடியாது. இந்த அம்சத்தை இயக்க, முதன்மைப் பயனருக்கு மாறவும்."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"தற்போது இந்தச் சாதனத்தில் உள்நுழைந்துள்ள பயனரால் USB பிழைதிருத்தத்தை இயக்க முடியாது. இந்த அம்சத்தைப் பயன்படுத்த நிர்வாகப் பயனராக உள்நுழையவும்."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"சிஸ்டம் மொழியை <xliff:g id="LANGUAGE">%1$s</xliff:g> மொழிக்கு மாற்ற விரும்புகிறீர்களா?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"சிஸ்டம் மொழியை மாற்றும்படி வேறொரு சாதனம் கோருகிறது"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"மொழியை மாற்று"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"இந்த நெட்வொர்க்கில் எப்போதும் அனுமதி"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"அனுமதி"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"வைஃபை பிழைதிருத்தம் அனுமதிக்கப்படவில்லை"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"தற்போது இந்தச் சாதனத்தில் உள்நுழைந்துள்ள பயனரால் வைஃபை பிழைதிருத்தத்தை இயக்க முடியாது. இந்த அம்சத்தை இயக்க முதன்மைப் பயனருக்கு மாறவும்."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"தற்போது இந்தச் சாதனத்தில் உள்நுழைந்துள்ள பயனரால் வைஃபை பிழைதிருத்தத்தை இயக்க முடியாது. இந்த அம்சத்தைப் பயன்படுத்த நிர்வாகப் பயனராக உள்நுழையவும்."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB போர்ட் முடக்கப்பட்டது"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"தேவையற்றவையில் இருந்து உங்கள் சாதனத்தைப் பாதுகாக்க USB போர்ட் முடக்கப்பட்டுள்ளது. மேலும் எந்தத் துணைக் கருவிகளையும் அது கண்டறியாது.\n\nUSB போர்ட்டை மீண்டும் எப்போது பயன்படுத்தலாம் என்பதைப் பற்றி உங்களுக்குத் தெரிவிக்கப்படும்."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"சார்ஜர்களையும் துணைக்கருவிகளையும் கண்டறிவதற்காக USB போர்ட் இயக்கப்பட்டுள்ளது"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"குரல் உதவி"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR குறியீடு ஸ்கேனர்"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"அன்லாக் செய்"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"சாதனம் பூட்டப்பட்டுள்ளது"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"முகத்தை ஸ்கேன் செய்கிறது"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"அனுப்பு"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"முகத்தை கண்டறிய இயலவில்லை"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"கைரேகையை உபயோகிக்கவும்"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"புளூடூத் இணைக்கப்பட்டது."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"பேட்டரி சதவீதம் தெரியவில்லை."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>க்கு இணைக்கப்பட்டது."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"விமானப் பயன்முறை."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN இயக்கத்தில் உள்ளது."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"பேட்டரி சக்தி <xliff:g id="NUMBER">%d</xliff:g> சதவிகிதம் உள்ளது."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"பேட்டரி: <xliff:g id="PERCENTAGE">%1$s</xliff:g> சதவீதம், உபயோகத்தின் அடிப்படையில் <xliff:g id="TIME">%2$s</xliff:g> மீதமுள்ளது"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"பேட்டரி: <xliff:g id="PERCENTAGE">%1$d</xliff:g> சதவீதம், உபயோகத்தின் அடிப்படையில் <xliff:g id="TIME">%2$s</xliff:g> மீதமுள்ளது"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"பேட்டரி சார்ஜ் ஆகிறது, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> சதவீதம் உள்ளது."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"எல்லா அறிவிப்புகளையும் காட்டும்"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter இயக்கப்பட்டது."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ரிங்கர் அதிர்வு."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"மைக்ரோஃபோன் அணுகல் உள்ளது"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"கேமரா அணுகல் உள்ளது"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"மைக்ரோஃபோன் &amp; கேமரா அணுகல் உள்ளது"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"மைக்ரோஃபோன் அணுகல் இயக்கப்பட்டது"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"மைக்ரோஃபோன் அணுகல் முடக்கப்பட்டது"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"அனைத்து ஆப்ஸுக்கும் சேவைகளுக்கும் மைக்ரோஃபோன் அணுகல் இயக்கப்பட்டது."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"அனைத்து ஆப்ஸுக்கும் சேவைகளுக்கும் மைக்ரோஃபோன் அணுகல் முடக்கப்பட்டது. அமைப்புகள் &gt; தனியுரிமை &gt; மைக்ரோஃபோன் என்பதற்குச் சென்று மைக்ரோஃபோன் அணுகலை இயக்கிக்கொள்ளலாம்."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"அனைத்து ஆப்ஸுக்கும் சேவைகளுக்கும் மைக்ரோஃபோன் அணுகல் முடக்கப்பட்டது. அமைப்புகள் &gt; தனியுரிமை &gt; மைக்ரோஃபோன் என்பதற்குச் சென்று இதை மாற்றிக்கொள்ளலாம்."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"கேமரா அணுகல் இயக்கப்பட்டது"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"கேமரா அணுகல் முடக்கப்பட்டது"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"அனைத்து ஆப்ஸுக்கும் சேவைகளுக்கும் கேமரா அணுகல் இயக்கப்பட்டது."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"அனைத்து ஆப்ஸுக்கும் சேவைகளுக்கும் கேமரா அணுகல் முடக்கப்பட்டது."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"மைக்ரோஃபோன் பட்டனைப் பயன்படுத்த, மைக்ரோஃபோன் அணுகலை அமைப்புகளில் இயக்கவும்."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"அமைப்புகளைத் திற."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"பிற சாதனம்"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"மேலோட்டப் பார்வையை நிலைமாற்று"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"அலாரங்கள், நினைவூட்டல்கள், நிகழ்வுகள் மற்றும் குறிப்பிட்ட அழைப்பாளர்களைத் தவிர்த்து, பிற ஒலிகள் மற்றும் அதிர்வுகளின் தொந்தரவு இருக்காது. எனினும், நீங்கள் எதையேனும் (இசை, வீடியோக்கள், கேம்ஸ் போன்றவை) ஒலிக்கும்படி தேர்ந்தெடுத்திருந்தால், அவை வழக்கம் போல் ஒலிக்கும்."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ஓர் ஆப்ஸை நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ அந்த ஆப்ஸில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"தொடர்க"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ஆப்ஸைப் பகிர்தல் அல்லது ரெக்கார்டு செய்தல்"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"பகிர்வதற்கோ ரெக்கார்டு செய்வதற்கோ இந்த ஆப்ஸை அனுமதிக்கவா?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ திரையில் காட்டப்படும்/சாதனத்தில் பிளே செய்யப்படும் அனைத்தையும் இந்த ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள் அல்லது பாதுகாக்கப்பட வேண்டிய பிற தகவல்கள் குறித்துக் கவனமாக இருங்கள்."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"ஓர் ஆப்ஸை நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ அதில் காட்டப்படும்/பிளே செய்யப்படும் அனைத்தையும் இந்த ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள் அல்லது பாதுகாக்கப்பட வேண்டிய பிற தகவல்கள் குறித்துக் கவனமாக இருங்கள்."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"உங்கள் IT நிர்வாகி தடுத்துள்ளார்"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"\'திரையைப் படமெடுத்தல்\' சாதனக் கொள்கையின்படி முடக்கப்பட்டுள்ளது"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"எல்லாவற்றையும் அழி"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"நிர்வகி"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"இதுவரை வந்த அறிவிப்புகள்"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"பயன்படுத்துவதற்கு அன்லாக் செய்க"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"உங்கள் கார்டுகளின் விவரங்களைப் பெறுவதில் சிக்கல் ஏற்பட்டது, பிறகு முயலவும்"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"பூட்டுத் திரை அமைப்புகள்"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR குறியீட்டை ஸ்கேன் செய்தல்"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"பணிக் கணக்கு"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"விமானப் பயன்முறை"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"அடுத்த அலாரத்தை <xliff:g id="WHEN">%1$s</xliff:g> மணிக்கு கேட்க மாட்டீர்கள்"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g>க்கு மறுபடியும் மாற்றவா?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"கிடைக்கும் நிலையின் அடிப்படையில் மொபைல் டேட்டா தானாகவே மாறாது"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"வேண்டாம்"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"மாற்று"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"முழுத்திரையைப் பெரிதாக்கும்"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"திரையின் ஒரு பகுதியைப் பெரிதாக்கும்"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ஸ்விட்ச்"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"குறுக்கே ஸ்க்ரோல் செய்வதை அனுமதி"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"அளவை மாற்று"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"பெரிதாக்கல் வகையை மாற்று"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"செயல்தவிர்"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} ஷார்ட்கட் அகற்றப்பட்டது}other{# ஷார்ட்கட்கள் அகற்றப்பட்டன}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"அகற்று"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"தற்காலிகமாக இணைக்கப்பட்டுள்ளது"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"இணைப்பு மோசமாக உள்ளது"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"சாதனப் பதிவுகள் அனைத்தையும் <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> அணுக அனுமதிக்கவா?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"ஒருமுறை அணுகலை அனுமதி"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"அனுமதிக்க வேண்டாம்"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"உங்கள் சாதனத்தில் நடப்பவற்றைச் சாதனப் பதிவுகள் ரெக்கார்டு செய்யும். சிக்கல்களைக் கண்டறிந்து சரிசெய்ய ஆப்ஸ் இந்தப் பதிவுகளைப் பயன்படுத்தலாம்.\n\nபாதுகாக்க வேண்டிய தகவல்கள் சில பதிவுகளில் இருக்கக்கூடும் என்பதால் சாதனப் பதிவுகள் அனைத்தையும் அணுக நீங்கள் நம்பும் ஆப்ஸை மட்டுமே அனுமதிக்கவும். \n\nசாதனப் பதிவுகள் அனைத்தையும் அணுக இந்த ஆப்ஸை நீங்கள் அனுமதிக்கவில்லை என்றாலும் அதற்குச் சொந்தமான பதிவுகளை அதனால் அணுக முடியும். உங்கள் சாதனத்திலுள்ள சில பதிவுகளையோ தகவல்களையோ சாதன உற்பத்தியாளரால் தொடர்ந்து அணுக முடியும்."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index f202fae..98a8e37 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"ఈ కంప్యూటర్ నుండి ఎల్లప్పుడూ అనుమతించండి"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"అనుమతించండి"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB డీబగ్గింగ్‌కి అనుమతి లేదు"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"ఈ పరికరానికి ప్రస్తుతం సైన్ ఇన్ చేసిన వినియోగదారు USB డీబగ్గింగ్ ఆన్ చేయలేరు. ఈ ఫీచర్ ఉపయోగించడానికి, ప్రాథమిక వినియోగదారుకి మారాలి."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"ఈ పరికరానికి ప్రస్తుతం సైన్ ఇన్ చేసిన యూజర్ USB డీబగ్గింగ్ ఆన్ చేయలేరు. ఈ ఫీచర్ ఉపయోగించడానికి, అడ్మిన్ యూజర్‌కు స్విచ్ అవ్వండి."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"మీరు సిస్టమ్ భాషను <xliff:g id="LANGUAGE">%1$s</xliff:g> భాషకు మార్చాలనుకుంటున్నారా?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"మరొక పరికరం ద్వారా సిస్టమ్ భాష మార్పు రిక్వెస్ట్ చేయబడింది"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"భాషను మార్చండి"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"ఈ నెట్‌వర్క్ నుండి ఎల్లప్పుడూ అనుమతించండి"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"అనుమతించండి"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"వైర్‌లెస్ డీబగ్గింగ్‌కి అనుమతి లేదు"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"ఈ పరికరానికి ప్రస్తుతం సైన్ ఇన్ చేసిన యూజర్, వైర్‌లెస్ డీబగ్గింగ్ ఆన్ చేయలేరు. ఈ ఫీచర్ ఉపయోగించడానికి, ప్రాథమిక యూజర్ కి స్విచ్ అవ్వండి."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"ఈ పరికరానికి ప్రస్తుతం సైన్ ఇన్ చేసిన యూజర్, వైర్‌లెస్ డీబగ్గింగ్ ఆన్ చేయలేరు. ఈ ఫీచర్ ఉపయోగించడానికి, అడ్మిన్ యూజర్‌కు స్విచ్ అవ్వండి."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB పోర్ట్‌ నిలిపివేయబడింది"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"మీ పరికరంలోకి నీరు లేదా చెత్తాచెదారం చేరిపోకుండా కాపాడటానికి, USB పోర్ట్ నిలిపివేయబడుతుంది, అలాగే యాక్సెసరీలు వేటిని గుర్తించదు.\n\nUSB పోర్ట్‌ను ఉపయోగించడం సురక్షితమేనని నిర్ధారించుకున్న తర్వాత, మళ్లీ మీకో నోటిఫికేషన్‌ రూపంలో తెలియజేయబడుతుంది."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"ఛార్జర్‌లు, యాక్సెసరీలను గుర్తించే విధంగా USB పోర్ట్ ప్రారంభించబడింది"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"వాయిస్ అసిస్టెంట్"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR కోడ్ స్కానర్"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"అన్‌లాక్ చేయి"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"పరికరం లాక్ చేయబడింది"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"ముఖాన్ని స్కాన్ చేస్తోంది"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"పంపు"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"ముఖం గుర్తించడం కుదరలేదు"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"బదులుగా వేలిముద్రను ఉపయోగించండి"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"బ్లూటూత్ కనెక్ట్ చేయబడింది."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"బ్యాటరీ శాతం తెలియదు."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>కి కనెక్ట్ చేయబడింది."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"ఎయిర్‌ప్లేన్ మోడ్."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPNలో."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"బ్యాటరీ <xliff:g id="NUMBER">%d</xliff:g> శాతం."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"బ్యాటరీ <xliff:g id="PERCENTAGE">%1$s</xliff:g> శాతం ఉంది, మీ వినియోగాన్ని బట్టి <xliff:g id="TIME">%2$s</xliff:g> పని చేస్తుంది"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"బ్యాటరీ <xliff:g id="PERCENTAGE">%1$d</xliff:g> శాతం ఉంది, మీ వినియోగాన్ని బట్టి <xliff:g id="TIME">%2$s</xliff:g> పని చేస్తుంది"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"బ్యాటరీ ఛార్జ్ అవుతోంది, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> శాతం వద్ద ఉంది."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"అన్ని నోటిఫికేషన్‌లను చూడండి"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"టెలిటైప్‌రైటర్ ప్రారంభించబడింది."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"రింగర్ వైబ్రేట్‌లో ఉంది."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"మైక్రోఫోన్ అందుబాటులో ఉంది"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"కెమెరా అందుబాటులో ఉంది"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"మైక్రోఫోన్, అలాగే కెమెరా అందుబాటులో ఉంది"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"మైక్రోఫోన్ ఆన్ చేయబడింది"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"మైక్రోఫోన్ ఆఫ్ చేయబడింది"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"యాప్‌లు, అలాగే సర్వీస్‌లన్నింటికీ మైక్రోఫోన్ ఎనేబుల్ చేయబడింది."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"యాప్‌లు, అలాగే సర్వీస్‌లన్నింటికీ మైక్రోఫోన్ యాక్సెస్ డిజేబుల్ చేయబడింది. సెట్టింగ్‌లు &gt; గోప్యత &gt; మైక్రోఫోన్‌లో మీరు మైక్రోఫోన్ యాక్సెస్‌ను ఎనేబుల్ చేయవచ్చు."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"యాప్‌లు, అలాగే సర్వీస్‌లన్నింటికీ మైక్రోఫోన్ యాక్సెస్ డిజేబుల్ చేయబడింది. సెట్టింగ్‌లు &gt; గోప్యత &gt; మైక్రోఫోన్‌లో మీరు దీన్ని మార్చవచ్చు."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"కెమెరా ఆన్ చేయబడింది"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"కెమెరా ఆఫ్ చేయబడింది"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"యాప్‌లు, అలాగే సర్వీస్‌లన్నింటికీ కెమెరా ఎనేబుల్ చేయబడింది."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"యాప్‌లు, అలాగే సర్వీస్‌లన్నింటికీ కెమెరా యాక్సెస్ డిజేబుల్ చేయబడింది."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"మైక్రోఫోన్ బటన్‌ను ఉపయోగించడానికి, సెట్టింగ్‌లలో మైక్రోఫోన్ యాక్సెస్‌ను ఎనేబుల్ చేయండి."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"సెట్టింగ్‌లను తెరవండి."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"ఇతర పరికరం"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"స్థూలదృష్టిని టోగుల్ చేయి"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"మీరు పేర్కొనే అలారాలు, రిమైండర్‌లు, ఈవెంట్‌లు మరియు కాలర్‌ల నుండి మినహా మరే ఇతర ధ్వనులు మరియు వైబ్రేషన్‌లతో మీకు అంతరాయం కలగదు. మీరు ఇప్పటికీ సంగీతం, వీడియోలు మరియు గేమ్‌లతో సహా మీరు ప్లే చేయడానికి ఎంచుకున్నవి ఏవైనా వింటారు."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"మీరు ఏదైనా యాప్‌ను షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, ఆ యాప్‌లో చూపబడిన దేనికైనా లేదా ప్లే అయిన దేనికైనా <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>‌కు యాక్సెస్ ఉంటుంది. కాబట్టి, పాస్‌వర్డ్‌లు, పేమెంట్ వివరాలు, మెసేజ్‌లు, లేదా ఏదైనా ఇతర సున్నితమైన సమాచారం పట్ల జాగ్రత్త వహించండి."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"కొనసాగించండి"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"యాప్‌ను షేర్ చేయండి లేదా రికార్డ్ చేయండి"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"షేర్ చేయడానికి లేదా రికార్డ్ చేయడానికి ఈ యాప్‌ను అనుమతించాలా?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"మీరు షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, ఈ యాప్ మీ స్క్రీన్‌పై కనిపించే దేనికైనా లేదా మీ పరికరంలో ప్లే అయిన దేనికైనా యాక్సెస్‌ను కలిగి ఉంటుంది. కాబట్టి, పాస్‌వర్డ్‌లు, పేమెంట్ వివరాలు, మెసేజ్‌లు, లేదా ఏదైనా ఇతర సున్నితమైన సమాచారం పట్ల జాగ్రత్త వహించండి."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"మీరు ఏదైనా యాప్‌ను షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, ఆ యాప్‌లో చూపబడిన దేనికైనా లేదా ప్లే అయిన దేనికైనా ఈ యాప్ యాక్సెస్‌ను కలిగి ఉంటుంది. కాబట్టి, పాస్‌వర్డ్‌లు, పేమెంట్ వివరాలు, మెసేజ్‌లు, లేదా ఏదైనా ఇతర సున్నితమైన సమాచారం పట్ల జాగ్రత్త వహించండి."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"మీ IT అడ్మిన్ ద్వారా బ్లాక్ చేయబడింది"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"పరికర పాలసీ ద్వారా స్క్రీన్ క్యాప్చర్ చేయడం డిజేబుల్ చేయబడింది"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"అన్నీ క్లియర్ చేయండి"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"మేనేజ్ చేయండి"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"హిస్టరీ"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ఉపయోగించడానికి అన్‌లాక్ చేయండి"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"మీ కార్డ్‌లను పొందడంలో సమస్య ఉంది, దయచేసి తర్వాత మళ్లీ ట్రై చేయండి"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"లాక్ స్క్రీన్ సెట్టింగ్‌లు"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR కోడ్‌ను స్కాన్ చేయండి"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"ఆఫీస్ ప్రొఫైల్‌"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"ఎయిర్‌ప్లేన్ మోడ్"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"మీరు <xliff:g id="WHEN">%1$s</xliff:g> సెట్ చేసిన మీ తర్వాత అలారం మీకు వినిపించదు"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g>కి తిరిగి మారాలా?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"మొబైల్ డేటా లభ్యత ఆధారంగా ఆటోమేటిక్‌గా స్విచ్ అవ్వదు"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"వద్దు, థ్యాంక్స్"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"అవును, మార్చండి"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ఫుల్ స్క్రీన్‌ను మ్యాగ్నిఫై చేయండి"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"స్క్రీన్‌లో భాగాన్ని మ్యాగ్నిఫై చేయండి"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"స్విచ్ చేయి"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"వికర్ణ స్క్రోలింగ్‌ను అనుమతించండి"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"సైజ్ మార్చండి"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"మ్యాగ్నిఫికేషన్ రకాన్ని మార్చండి"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"చర్య రద్దు చేయండి"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} షార్ట్‌కట్ తీసివేయబడింది}other{# షార్ట్‌కట్‌లు తీసివేయబడ్డాయి}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"తీసివేయండి"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"తాత్కాలికంగా కనెక్ట్ చేయబడింది"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"కనెక్షన్ బాగాలేదు"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"అన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>‌ను అనుమతించాలా?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"వన్-టైమ్ యాక్సెస్‌ను అనుమతించండి"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"అనుమతించవద్దు"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"మీ పరికరంలో జరిగే దాన్ని పరికర లాగ్‌లు రికార్డ్ చేస్తాయి. సమస్యలను కనుగొని, పరిష్కరించడానికి యాప్‌లు ఈ లాగ్‌లను ఉపయోగిస్తాయి.\n\nకొన్ని లాగ్‌లలో గోప్యమైన సమాచారం ఉండవచ్చు, కాబట్టి మీరు విశ్వసించే యాప్‌లను మాత్రమే అన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి అనుమతించండి. \n\nఅన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి మీరు ఈ యాప్‌ను అనుమతించకపోతే, అది తన స్వంత లాగ్‌లను ఇప్పటికి యాక్సెస్ చేయగలదు. మీ పరికర తయారీదారు ఇప్పటికీ మీ పరికరంలో కొన్ని లాగ్‌లు లేదా సమాచారాన్ని యాక్సెస్ చేయగలరు."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-television/strings.xml b/packages/SystemUI/res/values-television/strings.xml
new file mode 100644
index 0000000..f30b73e
--- /dev/null
+++ b/packages/SystemUI/res/values-television/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
+    <string name="log_access_confirmation_body">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
+        \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs.
+    </string>
+
+    <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
+    <string name="log_access_confirmation_learn_more" translatable="false"></string>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-television/styles.xml b/packages/SystemUI/res/values-television/styles.xml
index 12020f9..c517845 100644
--- a/packages/SystemUI/res/values-television/styles.xml
+++ b/packages/SystemUI/res/values-television/styles.xml
@@ -63,4 +63,10 @@
         <item name="android:paddingVertical">@dimen/bottom_sheet_button_padding_vertical</item>
         <item name="android:stateListAnimator">@anim/tv_bottom_sheet_button_state_list_animator</item>
     </style>
+
+    <!-- The style for log access consent button -->
+    <style name="LogAccessDialogTheme" parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
+        <item name="permissionGrantButtonTopStyle">?android:buttonBarButtonStyle</item>
+        <item name="permissionGrantButtonBottomStyle">?android:buttonBarButtonStyle</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index f216437..567e17d 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"อนุญาตจากคอมพิวเตอร์เครื่องนี้เสมอ"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"อนุญาต"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"ไม่อนุญาตให้แก้ไขข้อบกพร่องผ่าน USB"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"ผู้ใช้ที่ลงชื่อเข้าใช้อุปกรณ์อยู่ในขณะนี้ไม่สามารถเปิดการแก้ไขข้อบกพร่องผ่าน USB ได้ หากต้องการใช้ฟีเจอร์นี้ ให้เปลี่ยนไปเป็นผู้ใช้หลัก"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"ผู้ใช้ที่ลงชื่อเข้าใช้อุปกรณ์อยู่ในขณะนี้เปิดการแก้ไขข้อบกพร่อง USB ไม่ได้ โปรดเปลี่ยนไปเป็นผู้ใช้ที่ดูแลระบบเพื่อใช้ฟีเจอร์นี้"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"คุณต้องการเปลี่ยนภาษาของระบบเป็นภาษา<xliff:g id="LANGUAGE">%1$s</xliff:g>ใช่ไหม"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"คำขอเปลี่ยนภาษาของระบบโดยอุปกรณ์อื่น"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"เปลี่ยนภาษา"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"อนุญาตเสมอในเครือข่ายนี้"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"อนุญาต"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"ไม่อนุญาตให้แก้ไขข้อบกพร่องผ่าน Wi-Fi"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"ผู้ใช้ที่ลงชื่อเข้าใช้อุปกรณ์อยู่ในขณะนี้เปิดการแก้ไขข้อบกพร่องผ่าน Wi-Fi ไม่ได้ โปรดเปลี่ยนไปเป็นผู้ใช้หลักเพื่อใช้ฟีเจอร์นี้"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"ผู้ใช้ที่ลงชื่อเข้าใช้อุปกรณ์อยู่ในขณะนี้เปิดการแก้ไขข้อบกพร่องผ่าน Wi-Fi ไม่ได้ โปรดเปลี่ยนไปเป็นผู้ใช้ที่ดูแลระบบเพื่อใช้ฟีเจอร์นี้"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"พอร์ต USB ถูกปิดใช้"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"พอร์ต USB ปิดใช้อยู่และจะไม่ตรวจหาอุปกรณ์เสริมใดๆ เพื่อปกป้องอุปกรณ์จากของเหลวและฝุ่นละออง \n\nคุณจะได้รับแจ้งเมื่อใช้พอร์ต USB ได้อีกครั้ง"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"เปิดใช้พอร์ต USB แล้วเพื่อตรวจหาที่ชาร์จและอุปกรณ์เสริม"</string>
@@ -125,7 +125,7 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ตัวช่วยเสียง"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"เครื่องมือสแกนคิวอาร์โค้ด"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"ปลดล็อก"</string>
+    <string name="accessibility_unlock_button" msgid="3613812140816244310">"ปลดล็อกแล้ว"</string>
     <string name="accessibility_lock_icon" msgid="661492842417875775">"อุปกรณ์ถูกล็อก"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"กำลังสแกนใบหน้า"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ส่ง"</string>
@@ -168,6 +168,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"ไม่รู้จักใบหน้า"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ใช้ลายนิ้วมือแทน"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"เชื่อมต่อบลูทูธแล้ว"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ไม่ทราบเปอร์เซ็นต์แบตเตอรี่"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"เชื่อมต่อกับ <xliff:g id="BLUETOOTH">%s</xliff:g> แล้ว"</string>
@@ -178,8 +180,10 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"โหมดบนเครื่องบิน"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN เปิดอยู่"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"แบตเตอรี่ <xliff:g id="NUMBER">%d</xliff:g> เปอร์เซ็นต์"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"แบตเตอรี่ <xliff:g id="PERCENTAGE">%1$s</xliff:g> เปอร์เซ็นต์ ใช้ได้อีกประมาณ <xliff:g id="TIME">%2$s</xliff:g> ทั้งนี้ขึ้นอยู่กับการใช้งานของคุณ"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"แบตเตอรี่ <xliff:g id="PERCENTAGE">%1$d</xliff:g> เปอร์เซ็นต์ ใช้ได้อีกประมาณ <xliff:g id="TIME">%2$s</xliff:g> ทั้งนี้ขึ้นอยู่กับการใช้งานของคุณ"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"กำลังชาร์จแบตเตอรี่ <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> เปอร์เซ็นต์"</string>
+    <string name="accessibility_battery_level_charging_paused" msgid="1716051308782906917">"แบตเตอรี่ <xliff:g id="PERCENTAGE">%d</xliff:g> เปอร์เซ็นต์ หยุดชาร์จชั่วคราวเพื่อยืดอายุแบตเตอรี่"</string>
+    <string name="accessibility_battery_level_charging_paused_with_estimate" msgid="4006089349465741762">"แบตเตอรี่ <xliff:g id="PERCENTAGE">%1$d</xliff:g> เปอร์เซ็นต์ ใช้ได้<xliff:g id="TIME">%2$s</xliff:g> ทั้งนี้ขึ้นอยู่กับการใช้งานของคุณ หยุดชาร์จชั่วคราวเพื่อยืดอายุแบตเตอรี่"</string>
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"ดูการแจ้งเตือนทั้งหมด"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"เปิดใช้งาน TeleTypewriter อยู่"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"เสียงเรียกเข้าแบบสั่น"</string>
@@ -248,7 +252,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>
@@ -303,6 +307,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"ใช้งานไมโครโฟนได้"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"ใช้งานกล้องได้"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"ใช้งานไมโครโฟนและกล้องได้"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"เปิดไมโครโฟนแล้ว"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"ปิดไมโครโฟนแล้ว"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"เปิดไมโครโฟนสำหรับแอปและบริการทั้งหมดแล้ว"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"ปิดการเข้าถึงไมโครโฟนสำหรับแอปและบริการทั้งหมดแล้ว คุณสามารถเปิดการเข้าถึงไมโครโฟนได้จากการตั้งค่า &gt; ความเป็นส่วนตัว &gt; ไมโครโฟน"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"ปิดการเข้าถึงไมโครโฟนสำหรับแอปและบริการทั้งหมดแล้ว คุณเปลี่ยนการตั้งค่านี้ได้ในการตั้งค่า &gt; ความเป็นส่วนตัว &gt; ไมโครโฟน"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"เปิดกล้องแล้ว"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"ปิดกล้องแล้ว"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"เปิดการเข้าถึงกล้องสำหรับแอปและบริการทั้งหมดแล้ว"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"ปิดการเข้าถึงกล้องสำหรับแอปและบริการทั้งหมดแล้ว"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"หากต้องการใช้ปุ่มไมโครโฟน ให้เปิดการเข้าถึงไมโครโฟนในการตั้งค่า"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"เปิดการตั้งค่า"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"อุปกรณ์อื่น"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"สลับภาพรวม"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"คุณจะไม่ถูกรบกวนจากเสียงและการสั่น ยกเว้นเสียงนาฬิกาปลุก การช่วยเตือน กิจกรรม และผู้โทรที่ระบุไว้ คุณจะยังคงได้ยินสิ่งที่คุณเลือกเล่น เช่น เพลง วิดีโอ และเกม"</string>
@@ -373,6 +388,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"เมื่อกำลังแชร์ บันทึก หรือแคสต์แอป \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" จะมีสิทธิ์เข้าถึงทุกสิ่งที่แสดงหรือเล่นอยู่ในแอปดังกล่าว ดังนั้นโปรดระวังเกี่ยวกับรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ หรือข้อมูลที่ละเอียดอ่อนอื่นๆ"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ต่อไป"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"แชร์หรือบันทึกแอป"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"อนุญาตให้แอปนี้แชร์หรือบันทึกไหม"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"เมื่อกำลังแชร์ บันทึก หรือแคสต์ แอปนี้จะมีสิทธิ์เข้าถึงทุกสิ่งที่ปรากฏบนหน้าจอหรือเล่นอยู่ในอุปกรณ์ ดังนั้นโปรดระวังเกี่ยวกับรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ หรือข้อมูลที่ละเอียดอ่อนอื่นๆ"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"เมื่อกำลังแชร์ บันทึก หรือแคสต์แอป แอปนี้จะมีสิทธิ์เข้าถึงทุกสิ่งที่แสดงหรือเล่นอยู่ในแอปดังกล่าว ดังนั้นโปรดระวังเกี่ยวกับรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ หรือข้อมูลที่ละเอียดอ่อนอื่นๆ"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ผู้ดูแลระบบไอทีบล็อกไว้"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"การจับภาพหน้าจอปิดใช้โดยนโยบายด้านอุปกรณ์"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ล้างทั้งหมด"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"จัดการ"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ประวัติ"</string>
@@ -488,7 +508,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ปลดล็อกเพื่อใช้"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"เกิดปัญหาในการดึงข้อมูลบัตรของคุณ โปรดลองอีกครั้งในภายหลัง"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"การตั้งค่าหน้าจอล็อก"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"สแกนคิวอาร์โค้ด"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"เครื่องมือสแกนคิวอาร์โค้ด"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"โปรไฟล์งาน"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"โหมดบนเครื่องบิน"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"คุณจะไม่ได้ยินเสียงปลุกครั้งถัดไปในเวลา <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +747,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"เปลี่ยนกลับเป็น <xliff:g id="CARRIER">%s</xliff:g> หรือไม่"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"อินเทอร์เน็ตมือถือไม่ได้เปลี่ยนตามความพร้อมบริการโดยอัตโนมัติ"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ไม่เป็นไร"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ใช่ เปลี่ยนเลย"</string>
     <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>
@@ -767,6 +791,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ขยายเป็นเต็มหน้าจอ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ขยายบางส่วนของหน้าจอ"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"เปลี่ยน"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"อนุญาตการเลื่อนแบบทแยงมุม"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ปรับขนาด"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"เปลี่ยนประเภทการขยาย"</string>
@@ -785,12 +811,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"เลิกทำ"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{นำทางลัด {label} รายการออกแล้ว}other{นำทางลัด # รายการออกแล้ว}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"นำออก"</string>
     <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 +962,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"เชื่อมต่อแล้วชั่วคราว"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"การเชื่อมต่อไม่ดี"</string>
     <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>
@@ -992,4 +1023,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"HH:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"อนุญาตให้ \"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>\" เข้าถึงบันทึกทั้งหมดของอุปกรณ์ไหม"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"อนุญาตสิทธิ์เข้าถึงแบบครั้งเดียว"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"ไม่อนุญาต"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"บันทึกของอุปกรณ์เก็บข้อมูลสิ่งที่เกิดขึ้นในอุปกรณ์ แอปสามารถใช้บันทึกเหล่านี้เพื่อค้นหาและแก้ไขปัญหา\n\nบันทึกบางรายการอาจมีข้อมูลที่ละเอียดอ่อน คุณจึงควรอนุญาตเฉพาะแอปที่เชื่อถือได้ให้เข้าถึงบันทึกทั้งหมดของอุปกรณ์ \n\nหากคุณไม่อนุญาตให้แอปนี้เข้าถึงบันทึกทั้งหมดของอุปกรณ์ แอปจะยังเข้าถึงบันทึกของตัวเองได้อยู่ ผู้ผลิตอุปกรณ์อาจยังเข้าถึงบันทึกหรือข้อมูลบางรายการในอุปกรณ์ของคุณได้"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index f1acf43..f11d0ec 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Palaging payagan mula sa computer na ito"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Payagan"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Hindi pinapayagan ang pagde-debug sa pamamagitan ng USB"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Hindi mao-on ng user na kasalukuyang naka-sign in sa device na ito ang pag-debug ng USB. Upang magamit ang feature na ito, lumipat sa pangunahing user."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Hindi mao-on ng user na kasalukuyang naka-sign in sa device na ito ang pag-debug ng USB. Para magamit ang feature na ito, lumipat sa admin user."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Gusto mo bang gawing <xliff:g id="LANGUAGE">%1$s</xliff:g> ang wika ng system?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Hiniling ng ibang device na palitan ang wika ng system"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Palitan ang wika"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Palaging payagan sa network na ito"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Payagan"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Hindi pinapayagan ang wireless na pag-debug"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Hindi mao-on ng user na kasalukuyang naka-sign in sa device na ito ang wireless na pag-debug. Para magamit ang feature na ito, lumipat sa pangunahing user."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Hindi mao-on ng user na kasalukuyang naka-sign in sa device na ito ang wireless na pag-debug. Para magamit ang feature na ito, lumipat sa admin user."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Na-disable ang USB port"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Para protektahan ang iyong device sa likido o dumi, na-disable ang USB port at hindi ito makaka-detect ng anumang accessory.\n\nAabisuhan ka kapag ayos nang gamitin ulit ang USB port."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Na-enable ang USB port para ma-detect ang mga charger at accessory"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Scanner ng QR Code"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"I-unlock"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Naka-lock ang device"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Sina-scan ang mukha"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Ipadala"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Hindi makilala ang mukha"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gumamit ng fingerprint"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Nakakonekta ang Bluetooth."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Hindi alam ang porsyento ng baterya."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Nakakonekta sa <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Mode na eroplano."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"Naka-on ang VPN."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Baterya <xliff:g id="NUMBER">%d</xliff:g> (na) porsyento."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> (na) porsyento ang baterya, nasa <xliff:g id="TIME">%2$s</xliff:g> ang natitira batay sa paggamit mo"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> (na) porsyento ang baterya, nasa <xliff:g id="TIME">%2$s</xliff:g> ang natitira batay sa paggamit mo"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Nagcha-charge ang baterya, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> (na) porsyento."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Tingnan ang lahat ng notification"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Pinapagana ang TeleTypewriter."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Pag-vibrate ng ringer."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Available ang mikropono"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Available ang camera"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Available ang mikropono at camera"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Naka-on ang mikropono"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Naka-off ang mikropono"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Naka-enable para sa lahat ng app at serbisyo ang mikropono."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Naka-disable para sa lahat ng app at serbisyo ang access sa mikropono. Puwede mong i-enable ang access sa mikropono sa Mga Setting &gt; Privacy &gt; Mikropono."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Naka-disable para sa lahat ng app at serbisyo ang access sa mikropono. Puwede mo itong baguhin sa Mga Setting &gt; Privacy &gt; Mikropono."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Naka-on ang camera"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Naka-off ang camera"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Naka-enable para sa lahat ng app at serbisyo ang camera."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Naka-disable para sa lahat ng app at serbisyo ang access sa camera."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Para gamitin ang button ng mikropono, i-enable ang access sa mikropono sa Mga Setting."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Buksan ang mga setting."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Iba pang device"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"I-toggle ang Overview"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Hindi ka maiistorbo ng mga tunog at pag-vibrate, maliban mula sa mga alarm, paalala, event, at tumatawag na tutukuyin mo. Maririnig mo pa rin ang kahit na anong piliin mong i-play kabilang ang mga musika, video, at laro."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kapag nagbabahagi, nagre-record, o nagka-cast ka ng app, may access ang <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sa kahit anong ipinapakita o pine-play sa app na iyon. Kaya mag-ingat sa mga password, detalye ng pagbabayad, mensahe, o iba pang impormasyon."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Magpatuloy"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Ibahagi o i-record ang isang app"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Payagan ang app na ito na magbahagi o mag-record?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kapag nagbabahagi, nagre-record, o nagka-cast ka, may access ang app na ito sa kahit anong nakikita sa iyong screen o pine-play sa device mo. Kaya mag-ingat sa mga password, detalye ng pagbabayad, mensahe, o iba pang sensitibong impormasyon."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kapag nagbabahagi, nagre-record, o nagka-cast ka ng app, may access ang app na ito sa kahit anong ipinapakita o pine-play sa app na iyon. Kaya mag-ingat sa mga password, detalye ng pagbabayad, mensahe, o iba pang impormasyon."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Na-block ng iyong IT admin"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Naka-disable ang pag-screen capture ayon sa patakaran ng device"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"I-clear lahat"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Pamahalaan"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
@@ -488,7 +511,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"I-unlock para magamit"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Nagkaproblema sa pagkuha ng iyong mga card, pakisubukan ulit sa ibang pagkakataon"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Mga setting ng lock screen"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"I-scan ang QR code"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"Scanner ng QR code"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Profile sa trabaho"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Airplane mode"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Hindi mo maririnig ang iyong susunod na alarm ng <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +750,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Bumalik sa <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Hindi awtomatikong magbabago ang mobile data base sa availability"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Hindi, salamat na lang"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Oo, lumipat"</string>
     <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>
@@ -767,6 +794,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"I-magnify ang buong screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"I-magnify ang isang bahagi ng screen"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Switch"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Payagan ang diagonal na pag-scroll"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"I-resize"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Palitan ang uri ng pag-magnify"</string>
@@ -785,12 +814,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"I-undo"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} shortcut ang naalis}one{# shortcut ang naalis}other{# na shortcut ang naalis}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Alisin"</string>
     <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 +965,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">"Nakakonekta"</string>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Pansamantalang nakakonekta"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Mahina ang koneksyon"</string>
     <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>
@@ -992,4 +1026,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Payagan ang <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> na i-access ang lahat ng log ng device?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Payagan ang isang beses na pag-access"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Huwag payagan"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Nire-record ng mga log ng device kung ano ang nangyayari sa iyong device. Magagamit ng mga app ang mga log na ito para maghanap at mag-ayos ng mga isyu.\n\nPosibleng maglaman ang ilang log ng sensitibong impormasyon, kaya ang mga app lang na pinagkakatiwalaan mo ang payagang maka-access sa lahat ng log ng device. \n\nKung hindi mo papayagan ang app na ito na i-access ang lahat ng log ng device, maa-access pa rin nito ang mga sarili nitong log. Posible pa ring ma-access ng manufacturer ng iyong device ang ilang log o impormasyon sa device mo."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 7b52a41..11b8d7e 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Bu bilgisayardan her zaman izin ver"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"İzin ver"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB hata ayıklama işlevine izin verilmiyor"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Bu cihazda geçerli olarak oturum açmış olan kullanıcı, USB hata ayıklama özelliğini açamaz. Bu özelliği kullanmak için birincil kullanıcıya geçin."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Şu anda bu cihazda oturum açan kullanıcı, USB hata ayıklamayı açamaz. Bu özelliği kullanabilmek için yönetici olan kullanıcıya geçin."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Sistem dilini <xliff:g id="LANGUAGE">%1$s</xliff:g> olarak değiştirmek istiyor musunuz?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Başka bir cihaz tarafından sistem dilinin değiştirilmesi istendi"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Dili değiştir"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Bu ağda her zaman izin ver"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"İzin ver"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Kablosuz hata ayıklamaya izin verilmiyor"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Bu cihazda şu anda oturum açmış olan kullanıcı, kablosuz hata ayıklama özelliğini açamaz. Bu özelliği kullanmak için birincil kullanıcıya geçin."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Bu cihazda şu anda oturum açmış olan kullanıcı, kablosuz hata ayıklama özelliğini açamaz. Bu özelliği kullanmak için yönetici kullanıcıya geçin."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB bağlantı noktası devre dışı bırakıldı"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Cihazınızı sıvılardan veya tozlardan korumak için USB bağlantı noktası devre dışı bırakıldı ve aksesuarları algılamayacak.\n\nUSB bağlantı noktasını tekrar sorunsuz kullanabileceğiniz zaman bilgilendirileceksiniz."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB bağlantı noktası, şarj cihazlarını ve aksesuarları algılamak üzere etkinleştirildi"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Sesli Yardım"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Cüzdan"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR kodu tarayıcı"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Kilidi aç"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Cihaz kilitlendi"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Yüz taranıyor"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Gönder"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Yüz tanınamadı"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Bunun yerine parmak izi kullanın"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth bağlandı."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pil yüzdesi bilinmiyor."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ile bağlı."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Uçak modu."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN açık."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Pil yüzdesi: <xliff:g id="NUMBER">%d</xliff:g>"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Pil yüzde <xliff:g id="PERCENTAGE">%1$s</xliff:g> dolu. Kullanımınıza göre yaklaşık <xliff:g id="TIME">%2$s</xliff:g> süresi kaldı"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Pil yüzde <xliff:g id="PERCENTAGE">%1$d</xliff:g> dolu. Kullanımınıza göre yaklaşık <xliff:g id="TIME">%2$s</xliff:g> süresi kaldı"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Pil şarj oluyor, yüzde <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Tüm bildirimleri göster"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter etkin."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Telefon zili titreşim."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Kullanıcıları yönet"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon kullanılabilir"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera kullanılabilir"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon ve kamera kullanılabilir"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon açık"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon kapalı"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon tüm uygulama ve hizmetler için etkinleştirildi."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofon erişimi tüm uygulama ve hizmetler için devre dışı bırakıldı. Mikrofon erişimini Ayarlar &gt; Gizlilik &gt; Mikrofon\'da etkinleştirebilirsiniz."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofon erişimi tüm uygulama ve hizmetler için devre dışı bırakıldı. Bunu Ayarlar &gt; Gizlilik &gt; Mikrofon\'da değiştirebilirsiniz."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera açıldı"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera devre dışı bırakıldı"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera tüm uygulama ve hizmetler için etkinleştirildi."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kamera erişimi tüm uygulama ve hizmetler için devre dışı bırakıldı."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Mikrofon düğmesini kullanmak için Ayarlar\'da mikrofon erişimini etkinleştirin."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Ayarları aç."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Diğer cihaz"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Genel bakışı aç/kapat"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Alarmlar, hatırlatıcılar, etkinlikler ve sizin seçtiğiniz kişilerden gelen çağrılar dışında hiçbir ses ve titreşimle rahatsız edilmeyeceksiniz. O sırada çaldığınız müzik, seyrettiğiniz video ya da oynadığınız oyunların sesini duymaya devam edeceksiniz."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Bir uygulamayı paylaşma, kaydetme ve yayınlama özelliklerini kullandığınızda <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Devam"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Uygulamayı paylaşın veya kaydedin"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Bu uygulamanın paylaşım ve kayıt yapmasına izin verilsin mi?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Paylaşım, kayıt ve yayınlama özelliklerini kullandığınızda, ekranınızda görünen veya cihazınızda oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Bir uygulamayı paylaşma, kaydetme ve yayınlama özelliklerini kullandığınızda , söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"BT yöneticiniz tarafından engellendi"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekran görüntüsü alma, cihaz politikası tarafından devre dışı bırakıldı"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tümünü temizle"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Yönet"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Geçmiş"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Kullanmak için kilidi aç"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Kartlarınız alınırken bir sorun oluştu. Lütfen daha sonra tekrar deneyin"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Kilit ekranı ayarları"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR kodunu tara"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"İş profili"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Uçak modu"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> olarak ayarlanmış bir sonraki alarmınızı duymayacaksınız"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> operatörüne geri dönülsün mü?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Uygunluk durumuna göre otomatik olarak mobil veriye geçilmez"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Hayır, teşekkürler"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Evet, geçilsin"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Tam ekran büyütme"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekranın bir parçasını büyütün"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Geç"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Çapraz kaydırmaya izin ver"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Yeniden boyutlandır"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Büyütme türünü değiştir"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Geri al"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} kısayol kaldırıldı}other{# kısayol kaldırıldı}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Kaldır"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Geçici olarak bağlandı"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Bağlantı zayıf"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"d MMM, EEE"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:dd"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> uygulamasının tüm cihaz günlüklerine erişmesine izin verilsin mi?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Tek seferlik erişim izni ver"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"İzin verme"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Cihaz günlükleri, cihazınızda olanları kaydeder. Uygulamalar, sorunları bulup düzeltmek için bu günlükleri kullanabilir.\n\nBazı günlükler hassas bilgiler içerebileceği için yalnızca güvendiğiniz uygulamaların tüm cihaz günlüklerine erişmesine izin verin. \n\nBu uygulamanın tüm cihaz günlüklerine erişmesine izin vermeseniz de kendi günlüklerine erişmeye devam edebilir. Ayrıca, cihaz üreticiniz de cihazınızdaki bazı günlüklere veya bilgilere erişmeye devam edebilir."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 6bd9e30..972ee11 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Завжди дозволяти з цього комп’ютера"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Дозволити"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Ви не можете вмикати налагодження USB"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Користувач поточного облікового запису не може вмикати налагодження USB. Щоб увімкнути цю функцію, увійдіть в обліковий запис основного користувача."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Користувач поточного облікового запису не може вмикати налагодження через USB. Щоб увімкнути цю функцію, увійдіть в обліковий запис адміністратора."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Змінити мову системи на таку: <xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Запит на змінення мови системи надіслано з іншого пристрою"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Змінити мову"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Завжди дозволяти в цій мережі"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Дозволити"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Налагодження через Wi-Fi заборонене"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Користувач поточного облікового запису не може вмикати налагодження через Wi-Fi. Щоб активувати цю функцію, увійдіть в обліковий запис основного користувача."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Користувач поточного облікового запису не може вмикати налагодження через Wi-Fi. Щоб активувати цю функцію, увійдіть в обліковий запис адміністратора."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB-порт вимкнено"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Щоб захистити ваш пристрій від рідини та сміття, USB-порт вимкнено. Він не виявлятиме жодних аксесуарів.\n\nКоли USB-порт можна буде використовувати, ви отримаєте сповіщення."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Порт USB виявлятиме зарядні пристрої та аксесуари"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Голосові підказки"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Гаманець"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Сканер QR-коду"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Розблокувати"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Пристрій заблоковано"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Сканування обличчя"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Надіслати"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Обличчя не розпізнано"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Скористайтеся відбитком"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth під’єднано."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Відсоток заряду акумулятора невідомий."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Підключено до <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Режим польоту."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"Мережу VPN увімкнено."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Заряд акумулятора у відсотках: <xliff:g id="NUMBER">%d</xliff:g>."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Згідно з даними про використання залишилося <xliff:g id="PERCENTAGE">%1$s</xliff:g> заряду акумулятора – близько <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Згідно з даними про використання залишилося <xliff:g id="PERCENTAGE">%1$d</xliff:g> заряду акумулятора – близько <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Акумулятор заряджається, поточний заряд <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> відсотків."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Переглянути всі сповіщення"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Телетайп увімкнено."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Дзвінок на вібросигналі."</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Мікрофон доступний"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Камера доступна"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Мікрофон і камера доступні"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Мікрофон увімкнено"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Мікрофон вимкнено"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Мікрофон увімкнено для всіх додатків і сервісів."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Доступ до мікрофона вимкнено для всіх додатків і сервісів. Щоб надати доступ до мікрофона, виберіть \"Налаштування\" &gt; \"Конфіденційність\" &gt; \"Мікрофон\"."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Доступ до мікрофона вимкнено для всіх додатків і сервісів. Щоб змінити це, виберіть \"Налаштування\" &gt; \"Конфіденційність\" &gt; \"Мікрофон\"."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Камеру ввімкнено"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Камеру вимкнено"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Камеру ввімкнено для всіх додатків і сервісів."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Доступ до камери вимкнено для всіх додатків і сервісів."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Щоб використовувати кнопку мікрофона, надайте доступ до мікрофона в налаштуваннях."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Відкрити налаштування"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Інший пристрій"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Увімкнути або вимкнути огляд"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Ви отримуватиме звукові та вібросигнали лише для вибраних будильників, нагадувань, подій і абонентів. Однак ви чутимете все, що захочете відтворити, зокрема музику, відео й ігри."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Коли ви показуєте, записуєте або транслюєте додаток, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> отримує доступ до всього, що відображається або відтворюється в цьому додатку. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Продовжити"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Показувати або записувати додаток"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Дозволити цьому додатку показувати або записувати екран?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Коли ви показуєте, записуєте або транслюєте екран, цей додаток отримує доступ до всього, що відображається на екрані чи відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Коли ви показуєте, записуєте або транслюєте додаток, доступ до всього, що відображається або відтворюється в ньому, отримує цей додаток. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблоковано адміністратором"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Запис екрана вимкнено згідно з правилами для пристрою"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистити все"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Керувати"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Історія"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Розблокувати, щоб використовувати"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Не вдалось отримати ваші картки. Повторіть спробу пізніше."</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Параметри блокування екрана"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Сканувати QR-код"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Робочий профіль"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Режим польоту"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Наступний сигнал о <xliff:g id="WHEN">%1$s</xliff:g> не пролунає"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Перейти на <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Пристрій не перемикатиметься на мобільний Інтернет автоматично"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ні, дякую"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Так, перемикатися"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Збільшення всього екрана"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Збільшити частину екрана"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Перемкнути"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Дозволити прокручування по діагоналі"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Змінити розмір"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Змінити тип збільшення"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Відмінити"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Функцію спеціальних можливостей \"{label}\" вилучено}one{# функцію спеціальних можливостей вилучено}few{# функції спеціальних можливостей вилучено}many{# функцій спеціальних можливостей вилучено}other{# функції спеціальних можливостей вилучено}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Вилучити"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Тимчасово з’єднано"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Погане з’єднання"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Надати додатку <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> доступ до всіх журналів пристрою?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Надати доступ лише цього разу"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Не надавати"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"У журналах пристрою реєструється все, що відбувається на ньому. За допомогою цих журналів додатки можуть виявляти й усувати проблеми.\n\nДеякі журнали можуть містити конфіденційні дані, тому надавати доступ до всіх журналів пристрою слід лише надійним додаткам. \n\nЯкщо додаток не має доступу до всіх журналів пристрою, він усе одно може використовувати власні журнали. Виробник вашого пристрою все одно може використовувати деякі журнали чи інформацію на ньому."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index ec382b2..6e2b364 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"اس کمپیوٹر سے ہمیشہ اجازت دیں"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"اجازت دیں"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"‏USB ڈیبگ کرنے کی اجازت نہیں ہے"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"‏اس آلہ پر فی الحال سائن ان کردہ صارف USB ڈیبگنگ آن نہیں کر سکتا۔ اس خصوصیت کا استعمال کرنے کیلئے، ابتدائی صارف پر سوئچ کریں۔"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"‏اس آلہ پر فی الحال سائن ان کردہ صارف USB ڈیبگنگ آن نہیں کر سکتا۔ اس خصوصیت کا استعمال کرنے کے لیے، منتظم صارف پر سوئچ کریں۔"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"کیا آپ سسٹم کی زبان کو <xliff:g id="LANGUAGE">%1$s</xliff:g> میں تبدیل کرنا چاہتے ہیں؟"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"کسی دوسرے آلے کے ذریعے سسٹم کی زبان میں تبدیلی کی درخواست کی گئی"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"زبان تبدیل کریں"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"اس نیٹ ورک پر ہمیشہ اجازت دیں"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"اجازت دیں"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"وائرلیس ڈیبگنگ کرنے کی اجازت نہیں ہے"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"اس آلہ پر فی الحال سائن ان کردہ صارف وائرلیس ڈیبگنگ آن نہیں کر سکتا۔ اس خصوصیت کا استعمال کرنے کے ليے، ابتدائی صارف پر سوئچ کریں۔"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"اس آلہ پر فی الحال سائن ان کردہ صارف وائرلیس ڈیبگنگ آن نہیں کر سکتا۔ اس خصوصیت کا استعمال کرنے کے ليے، منتظم صارف پر سوئچ کریں۔"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"‏USB پورٹ غیر فعال ہو گیا"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"‏آپ کے آلے کی سیال یا دھول سے حفاظت کرنے کے لیے، USB پورٹ کو غیر فعال کر دیا گیا ہے اور یہ کسی لوازم کا پتہ نہیں لگا پائے گا۔\n\nUSB پورٹ کا دوبارہ استعمال کرنا ٹھیک ہونے پر آپ کو مطلع کیا جائے گا۔"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"‏چارجرز اور لوازمات کا پتا لگانے کے لیے USB پورٹ فعال ہے"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"صوتی معاون"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"والٹ"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"‏QR کوڈ اسکینر"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"غیر مقفل کریں"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"آلہ مقفل کر دیا گیا"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"اسکیننگ چہرہ"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"بھیجیں"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"چہرے کی پہچان نہیں ہو سکی"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"اس کے بجائے فنگر پرنٹ استعمال کریں"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"بلوٹوتھ مربوط ہے۔"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"بیٹری کی فیصد نامعلوم ہے۔"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> سے منسلک ہیں۔"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"ہوائی جہاز وضع۔"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"‏VPN آن ہے۔"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"بیٹری <xliff:g id="NUMBER">%d</xliff:g> فیصد۔"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"آپ کے استعمال کی بنیاد پر بیٹری <xliff:g id="PERCENTAGE">%1$s</xliff:g> فیصد، تقریباً <xliff:g id="TIME">%2$s</xliff:g> باقی ہے"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"آپ کے استعمال کی بنیاد پر بیٹری <xliff:g id="PERCENTAGE">%1$d</xliff:g> فیصد، تقریباً <xliff:g id="TIME">%2$s</xliff:g> باقی ہے"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"بیٹری چارج ہو رہی ہے، اس وقت <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> فیصد ہے۔"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"تمام اطلاعات دیکھیں"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"ٹیلی ٹائپ رائٹر فعال ہے۔"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"رنگر وائبریٹ۔"</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"مائیکروفون دستیاب ہے"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"کیمرا دستیاب ہے"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"مائیکروفون اور کیمرا دستیاب ہیں"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"مائیکروفون کو آن کر دیا گیا"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"مائیکروفون کو آف کر دیا گیا"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"مائیکروفون سبھی ایپس اور سروسز کے لیے فعال ہو گیا۔"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"‏مائیکروفون تک رسائی سبھی ایپس اور سروسز کے لیے غیر فعال ہو گئی۔ آپ مائیکروفون تک رسائی کو ترتیبات &gt; رازداری &gt; مائیکروفون میں فعال کر سکتے ہیں۔"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"‏مائیکروفون تک رسائی سبھی ایپس اور سروسز کے لیے غیر فعال ہو گئی۔ آپ اسے ترتیبات &gt; رازداری &gt; مائیکروفون میں اسے تبدیل کر سکتے ہیں۔"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"کیمرا آن ہو گیا"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"کیمرا آف ہو گیا"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"کیمرا سبھی ایپس اور سروسز کے لیے فعال ہو گیا۔"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"کیمرا تک رسائی سبھی ایپس اور سروسز کے لیے غیر فعال ہو گئی۔"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"مائیکروفون بٹن کو استعمال کرنے کے لیے، ترتیبات میں مائیکروفون تک رسائی کو فعال کریں۔"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"ترتیبات کھولیں۔"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"دوسرا آلہ"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"مجموعی جائزہ ٹوگل کریں"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"الارمز، یاددہانیوں، ایونٹس اور آپ کے متعین کردہ کالرز کے علاوہ، آپ آوازوں اور وائبریشنز سے ڈسٹرب نہیں ہوں گے۔ موسیقی، ویڈیوز اور گیمز سمیت آپ ابھی بھی ہر وہ چیز سنیں گے جسے چلانے کا آپ انتخاب کرتے ہیں۔"</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو آپ کی اسکرین پر دکھائی گئی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"جاری رکھیں"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ایپ کا اشتراک یا ریکارڈ کریں"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"اس ایپ کو اشتراک یا ریکارڈ کرنے کی اجازت دیں؟"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو اس ایپ کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلنے والی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو اس ایپ کو آپ کی اسکرین پر دکھائی گئی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"‏آپ کے IT منتظم نے مسدود کر دیا"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"اسکرین کو کیپچر کرنا آلہ کی پالیسی کے ذریعے غیر فعال ہے"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"سبھی کو صاف کریں"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"نظم کریں"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"سرگزشت"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"استعمال کرنے کے لیے غیر مقفل کریں"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"آپ کے کارڈز حاصل کرنے میں ایک مسئلہ درپیش تھا، براہ کرم بعد میں دوبارہ کوشش کریں"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"مقفل اسکرین کی ترتیبات"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"‏QR کوڈ اسکین کریں"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"دفتری پروفائل"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"ہوائی جہاز وضع"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"آپ کو <xliff:g id="WHEN">%1$s</xliff:g> بجے اپنا اگلا الارم سنائی نہیں دے گا"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> پر واپس سوئچ کریں؟"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"دستیابی کی بنیاد پر موبائل ڈیٹا خودکار طور پر تبدیل نہیں ہوگا"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"نہیں شکریہ"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ہاں، سوئچ کریں"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"فُل اسکرین کو بڑا کریں"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"اسکرین کا حصہ بڑا کریں"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"سوئچ کریں"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"وتری سکرولنگ کی اجازت دیں"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"سائز تبدیل کریں"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"میگنیفیکیشن کی قسم کو تبدیل کریں"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"کالعدم کریں"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} شارٹ کٹ ہٹا دیا گیا}other{# شارٹ کٹس ہٹا دیے گئے}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"ہٹائیں"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"عارضی طور پر منسلک ہے"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"کمزور کنکشن"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> کو آلے کے تمام لاگز تک رسائی کی اجازت دیں؟"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"یک وقتی رسائی کی اجازت دیں"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"اجازت نہ دیں"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"آپ کے آلے پر جو ہوتا ہے آلے کے لاگز اسے ریکارڈ کر لیتے ہیں۔ ایپس ان لاگز کا استعمال مسائل کو تلاش کرنے اور ان کو حل کرنے کے لیے کر سکتی ہیں۔\n\nکچھ لاگز میں حساس معلومات شامل ہو سکتی ہیں، اس لیے صرف اپنی بھروسے مند ایپس کو ہی آلے کے تمام لاگز تک رسائی کی اجازت دیں۔ \n\nاگر آپ اس ایپ کو آلے کے تمام لاگز تک رسائی کی اجازت نہیں دیتے ہیں تب بھی یہ اپنے لاگز تک رسائی حاصل کر سکتی ہے۔ آپ کے آلے کا مینوفیکچرر اب بھی آپ کے آلے پر کچھ لاگز یا معلومات تک رسائی حاصل کر سکتا ہے۔"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index a666432..b12ea3d 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Doimo ushbu kompyuterdan ruxsat berilsin"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Ruxsat berish"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"USB orqali nosozliklarni tuzatishga ruxsat berilmagan"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Ayni paytda ushbu qurilmaga o‘z hisobi bilan kirgan foydalanuvchi USB orqali nosozliklarni aniqlash funksiyasini yoqa olmaydi. Bu funksiyadan foydalanish uchun asosiy foydalanuvchi profiliga o‘ting."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Ayni paytda ushbu qurilmaga o‘z hisobi bilan kirgan foydalanuvchi USB orqali nosozliklarni tuzatish funksiyasini yoqa olmaydi. Bu funksiyadan foydalanish uchun administrator profiliga oʻting."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Tizim tilini <xliff:g id="LANGUAGE">%1$s</xliff:g> tiliga oʻzgartirishni istaysizmi?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Tizim tilini oʻzgartirishni boshqa qurilma soʻragan"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Tilni almashtirish"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Bu tarmoqda doim ruxsat etilsin"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Ruxsat"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Wi-Fi orqali debagging taqiqlandi"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Ayni paytda ushbu qurilmaga oʻz hisobi bilan kirgan foydalanuvchi Wi-Fi orqali debagging funksiyasini yoqa olmaydi. Bu funksiyadan foydalanish uchun asosiy foydalanuvchi profiliga oʻting."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Ayni paytda ushbu qurilmaga oʻz hisobi bilan kirgan foydalanuvchi Wi-Fi orqali debagging funksiyasini yoqa olmaydi. Bu funksiyadan foydalanish uchun administrator profiliga oʻting."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB port faolsizlashtirildi"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Qurilmangizni suyuqlik va turli parchalardan himoya qilish uchun USB port faolsizlashtiriladi va hech qanday aksessuarni aniqlay olmaydi.\n\nUSB portdan xavfsiz foydalanish mumkin boʻlganda, sizga xabar beriladi."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Quvvatlash moslamalari va aksessuarlarni aniqlash uchun USB port yoqildi"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Ovozli yordam"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR kod skaneri"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Qulfdan chiqarish"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Qurilma qulflandi"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Yuzni skanerlash"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Yuborish"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Yuz aniqlanmadi"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Barmoq izi orqali urining"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ulandi."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batareya quvvati foizi nomaʼlum."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ulangan: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Parvoz rejimi"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN yoniq."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Batareya <xliff:g id="NUMBER">%d</xliff:g> foiz."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batareya quvvati <xliff:g id="PERCENTAGE">%1$s</xliff:g> foiz, joriy holatda yana <xliff:g id="TIME">%2$s</xliff:g> qoldi"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batareya quvvati <xliff:g id="PERCENTAGE">%1$d</xliff:g> foiz, joriy holatda yana <xliff:g id="TIME">%2$s</xliff:g> qoldi"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batareya quvvat olmoqda, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> foiz."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Barcha bildirishnomalarni ko‘rish"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter yoqildi."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibratsiyali qo‘ng‘iroq"</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Foydalanuvchilarni boshqarish"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Mikrofon mavjud"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Kamera mavjud"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Mikrofon va kamera mavjud"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Mikrofon yoqildi"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Mikrofon oʻchirildi"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mikrofon barcha ilovalar va xizmatlar uchun yoqilgan."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofon ruxsati barcha ilovalar va xizmatlar uchun oʻchirilgan. Sozlamalar &gt; Maxfiylik &gt; Mikrofon orqali mikrofon ruxsatini yoqishingiz mumkin."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofon ruxsati barcha ilovalar va xizmatlar uchun oʻchirilgan. Buni Sozlamalar &gt; Maxfiylik &gt; Mikrofon menyusida oʻzgartirishingiz mumkin."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Kamera yoqildi"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Kamera oʻchirildi"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Kamera barcha ilovalar va xizmatlar uchun yoqilgan."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kamera ruxsati barcha ilovalar va xizmatlar uchun oʻchirilgan."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Mikrofon tugmasidan foydalanish uchun Sozlamalar orqali mikrofon ruxsatini yoqing."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Sozlamalarni ochish."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Boshqa qurilma"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Umumiy nazar rejimini almashtirish"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Turli ovoz va tebranishlar endi sizni bezovta qilmaydi. Biroq, signallar, eslatmalar, tadbirlar haqidagi bildirishnomalar va siz tanlagan abonentlardan kelgan chaqiruvlar bundan mustasno. Lekin, ijro etiladigan barcha narsalar, jumladan, musiqa, video va o‘yinlar ovozi eshitiladi."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Ulashish, yozib olish va translatsiya qilish vaqtida <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ilovasi ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar yoki boshqa maxfiy axborot chiqmasligi uchun ehtiyot boʻling."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Davom etish"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Ilovada ulashish yoki yozib olish"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Bu ilovaga ulashish yoki yozib olish uchun ruxsat berilsinmi?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Ulashish, yozib olish va translatsiya qilish vaqtida bu ilova ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar yoki boshqa maxfiy axborot chiqmasligi uchun ehtiyot boʻling."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Ulashish, yozib olish va translatsiya qilish vaqtida bu ilova ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar yoki boshqa maxfiy axborot chiqmasligi uchun ehtiyot boʻling."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"AT administratoringiz tomonidan bloklangan"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekranni tasvirga olish qurilmadan foydalanish tartibi tomonidan faolsizlantirilgan"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hammasini tozalash"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Boshqarish"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Tarix"</string>
@@ -488,7 +511,7 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Foydalanish uchun qulfdan chiqarish"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Bildirgilarni yuklashda xatolik yuz berdi, keyinroq qaytadan urining"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Qulflangan ekran sozlamalari"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR kodni skanerlash"</string>
+    <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR kod skaneri"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Ish profili"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Parvoz rejimi"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Keyingi signal (<xliff:g id="WHEN">%1$s</xliff:g>) chalinmaydi"</string>
@@ -727,6 +750,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> xizmati qaytarilsinmi?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobil internet mavjudligi asosida avtomatik almashtirilmaydi"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Kerak emas"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ha, almashtirilsin"</string>
     <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>
@@ -767,6 +794,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ekranni toʻliq kattalashtirish"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekran qismini kattalashtirish"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Almashtirish"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Diagonal aylantirishga ruxsat berish"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Oʻlchamini oʻzgartirish"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Kattalashtirish turini oʻzgartirish"</string>
@@ -785,12 +814,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Bekor qilish"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} ta yorliq olindi}other{# ta yorliq olindi}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Olib tashlash"</string>
     <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 +965,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Vaqtincha ulangan"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Aloqa beqaror"</string>
     <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>
@@ -992,4 +1026,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"s:dd"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ilovasining qurilmadagi barcha jurnallarga kirishiga ruxsat berilsinmi?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Bir matalik foydalanishga ruxsat berish"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Rad etish"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Qurilma jurnaliga qurilma bilan yuz bergan hodisalar qaydlari yoziladi. Ilovalar bu jurnal qaydlari yordamida muammolarni topishi va bartaraf qilishi mumkin.\n\nAyrim jurnal qaydlarida maxfiy axborotlar yozilishi mumkin, shu sababli qurilmadagi barcha jurnal qaydlariga ruxsatni faqat ishonchli ilovalarga bering. \n\nBu ilovaga qurilmadagi barcha jurnal qaydlariga ruxsat berilmasa ham, u oʻzining jurnalini ocha oladi. Qurilma ishlab chiqaruvchisi ham ayrim jurnallar yoki qurilma haqidagi axborotlarni ocha oladi."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 21200ca..1af2e0e 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Luôn cho phép từ máy tính này"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Cho phép"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Không cho phép chế độ gỡ lỗi qua USB"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Người dùng hiện đã đăng nhập vào thiết bị này không thể bật tính năng gỡ lỗi USB. Để sử dụng tính năng này, hãy chuyển sang người dùng chính."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Người dùng đang đăng nhập trên thiết bị này không thể bật tính năng gỡ lỗi qua USB. Để sử dụng tính năng này, hãy chuyển sang tài khoản người dùng quản trị."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Bạn có muốn thay đổi ngôn ngữ hệ thống thành <xliff:g id="LANGUAGE">%1$s</xliff:g> không?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Thiết bị khác yêu cầu thay đổi ngôn ngữ hệ thống"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Thay đổi ngôn ngữ"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Luôn cho phép trên mạng này"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Cho phép"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Không cho phép gỡ lỗi qua Wi-Fi"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Người dùng hiện đã đăng nhập vào thiết bị này không thể bật tính năng gỡ lỗi qua Wi-Fi. Để sử dụng tính năng này, hãy chuyển sang người dùng chính."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Người dùng đang đăng nhập trên thiết bị này không thể bật tính năng gỡ lỗi qua Wi-Fi. Để sử dụng tính năng này, hãy chuyển sang tài khoản người dùng quản trị."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Đã tắt cổng USB"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Để bảo vệ thiết bị của bạn khỏi chất lỏng hay mảnh vỡ, cổng USB sẽ tắt và không phát hiện được bất kỳ phụ kiện nào.\n\nBạn sẽ nhận được thông báo khi có thể sử dụng lại cổng USB."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Đã bật cổng USB để phát hiện bộ sạc và phụ kiện"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Trợ lý thoại"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"Ví"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Trình quét mã QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Mở khóa"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Đã khóa thiết bị"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Quét tìm khuôn mặt"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Gửi"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Không nhận ra khuôn mặt"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Hãy dùng vân tay"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Đã kết nối bluetooth."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Tỷ lệ phần trăm pin không xác định."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Đã kết nối với <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Chế độ trên máy bay."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN đang bật."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> phần trăm pin."</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> phần trăm pin, còn khoảng <xliff:g id="TIME">%2$s</xliff:g> dựa trên mức sử dụng của bạn"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> phần trăm pin, còn khoảng <xliff:g id="TIME">%2$s</xliff:g> dựa trên mức sử dụng của bạn"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Đang sạc pin, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Xem tất cả thông báo"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Đã bật TeleTypewriter."</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Chuông rung."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Quản lý người dùng"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Micrô đang bật"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Máy ảnh đang bật"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Micrô và máy ảnh đang bật"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Đã bật micrô"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Đã tắt micrô"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Mọi ứng dụng và dịch vụ được phép sử dụng micrô."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mọi ứng dụng và dịch vụ không có quyền truy cập vào micrô. Bạn có thể bật quyền truy cập vào micrô trong phần Cài đặt &gt; Quyền riêng tư &gt; Micrô."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mọi ứng dụng và dịch vụ không có quyền truy cập vào micrô. Bạn có thể thay đổi chế độ này trong phần Cài đặt &gt; Quyền riêng tư &gt; Micrô."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Đã bật máy ảnh"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Đã tắt máy ảnh"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Mọi ứng dụng và dịch vụ được phép sử dụng máy ảnh."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Mọi ứng dụng và dịch vụ không có quyền truy cập vào máy ảnh."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Để dùng nút micrô, hãy bật quyền truy cập vào micrô trong phần Cài đặt."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Mở phần cài đặt."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Thiết bị khác"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Bật/tắt chế độ xem Tổng quan"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Bạn sẽ không bị làm phiền bởi âm thanh và tiếng rung, ngoại trừ báo thức, lời nhắc, sự kiện và người gọi mà bạn chỉ định. Bạn sẽ vẫn nghe thấy mọi thứ bạn chọn phát, bao gồm nhạc, video và trò chơi."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Khi bạn chia sẻ, ghi hoặc truyền ứng dụng, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sẽ có quyền truy cập vào mọi nội dung xuất hiện hoặc phát trên ứng dụng đó. Vì vậy, hãy thận trọng để không làm lộ mật khẩu, thông tin thanh toán, tin nhắn hoặc thông tin nhạy cảm khác."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Tiếp tục"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Chia sẻ hoặc ghi ứng dụng"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Cho phép ứng dụng này chia sẻ hoặc ghi?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Khi bạn chia sẻ, ghi hoặc truyền, ứng dụng này sẽ truy cập được vào mọi nội dung xuất hiện trên màn hình hoặc phát trên thiết bị của bạn. Vì vậy, hãy để ý đến mật khẩu, thông tin thanh toán, tin nhắn hoặc thông tin nhạy cảm khác."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Khi bạn chia sẻ, ghi hoặc truyền ứng dụng, ứng dụng này sẽ truy cập được vào mọi nội dung xuất hiện hoặc phát trên ứng dụng đó. Vì vậy, hãy để ý đến mật khẩu, thông tin thanh toán, tin nhắn hoặc thông tin nhạy cảm khác."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bị quản trị viên CNTT chặn"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Tính năng chụp ảnh màn hình đã bị tắt theo chính sách thiết bị"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Xóa tất cả"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Quản lý"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Lịch sử"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Mở khóa để sử dụng"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Đã xảy ra sự cố khi tải thẻ của bạn. Vui lòng thử lại sau"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Cài đặt màn hình khóa"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Quét mã QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Hồ sơ công việc"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Chế độ máy bay"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Bạn sẽ không nghe thấy báo thức tiếp theo lúc <xliff:g id="WHEN">%1$s</xliff:g> của mình"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Chuyển về <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Dữ liệu di động sẽ không tự động chuyển dựa trên tình trạng phủ sóng"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Không, cảm ơn"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Có, hãy chuyển"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Phóng to toàn màn hình"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Phóng to một phần màn hình"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Chuyển"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Cho phép cuộn chéo"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Đổi kích thước"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Thay đổi kiểu phóng to"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Huỷ"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Đã xoá {label} lối tắt}other{Đã xoá # lối tắt}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Xoá"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tạm thời có kết nối"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Kết nối kém"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Cho phép <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> truy cập vào mọi nhật ký thiết bị?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Cho phép truy cập một lần"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Không cho phép"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Nhật ký thiết bị ghi lại những hoạt động diễn ra trên thiết bị của bạn. Các ứng dụng có thể dùng nhật ký này để tìm và khắc phục vấn đề.\n\nMột số nhật ký có thể chứa thông tin nhạy cảm, vì vậy, bạn chỉ nên cấp quyền truy cập vào mọi nhật ký thiết bị cho những ứng dụng mà mình tin tưởng. \n\nNếu bạn không cho phép ứng dụng này truy cập vào mọi nhật ký thiết bị, thì ứng dụng này vẫn có thể truy cập vào nhật ký của chính nó. Nhà sản xuất thiết bị vẫn có thể truy cập vào một số nhật ký hoặc thông tin trên thiết bị của bạn."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 83508a1..21c9071 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"一律允许使用这台计算机进行调试"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"允许"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"不允许使用 USB 调试功能"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"目前已登录此设备的用户无法开启 USB 调试功能。要使用此功能,请切换为主要用户的帐号。"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"当前已登录此设备的用户无法开启 USB 调试功能。要使用此功能,请切换为管理员用户。"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"是否要将系统语言更改为<xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"另一台设备请求更改系统语言"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"更改语言"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"始终允许通过此网络进行调试"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"允许"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"不允许使用无线调试功能"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"目前已登录此设备的用户无法开启无线调试功能。要使用此功能,请切换为主要用户的帐号。"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"当前已登录此设备的用户无法开启无线调试功能。要使用此功能,请切换为管理员用户。"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB 端口已停用"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"为避免液体或碎屑导致您的设备受损,系统已停用 USB 端口,因此目前无法检测任何配件。\n\n系统会在重新允许使用 USB 端口时通知您。"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB 端口已启用,可检测充电器和配件"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"语音助理"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"钱包"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"二维码扫描器"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"解锁"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"设备已锁定"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"正在扫描面孔"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"发送"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"人脸识别失败"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"改用指纹"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"蓝牙已连接。"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"电池电量百分比未知。"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已连接到<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"飞行模式。"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN 已开启。"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"电池电量为百分之 <xliff:g id="NUMBER">%d</xliff:g>。"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"电池电量为 <xliff:g id="PERCENTAGE">%1$s</xliff:g>,根据您的使用情况,大约还可使用<xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"电池电量为 <xliff:g id="PERCENTAGE">%1$d</xliff:g>,根据您的使用情况,大约还可使用<xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"正在充电,已完成 <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%。"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"查看所有通知"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"电传打字机已启用。"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"振铃器振动。"</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"麦克风可用"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"摄像头可用"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"麦克风和摄像头可用"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"麦克风已开启"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"麦克风已关闭"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"已允许所有应用和服务访问麦克风。"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"已阻止所有应用和服务访问麦克风。您可依次前往“设置”&gt;“隐私设置”&gt;“麦克风”来启用麦克风访问权限。"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"已阻止所有应用和服务访问麦克风。您可依次前往“设置”&gt;“隐私设置”&gt;“麦克风”来更改此权限设置。"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"摄像头已开启"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"摄像头已关闭"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"已允许所有应用和服务访问摄像头。"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"已阻止所有应用和服务访问摄像头。"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"若要使用麦克风按钮,请在“设置”中启用麦克风访问权限。"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"打开设置。"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"其他设备"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"切换概览"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"您将不会受到声音和振动的打扰(闹钟、提醒、活动和所指定来电者的相关提示音除外)。您依然可以听到您选择播放的任何内容(包括音乐、视频和游戏)的相关音效。"</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"在您进行分享、录制或投射时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问通过此应用显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"继续"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或录制应用"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"是否允许此应用进行分享或录制?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"在您进行分享、录制或投屏时,此应用可以访问您的屏幕显示或设备播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"在您进行分享、录制或投屏时,此应用可以访问通过此应用显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"已被 IT 管理员禁止"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"设备政策已停用屏幕截图功能"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"历史记录"</string>
@@ -487,8 +510,9 @@
     <string name="wallet_secondary_label_updating" msgid="5726130686114928551">"正在更新"</string>
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"解锁设备即可使用"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"获取您的卡片时出现问题,请稍后重试"</string>
-    <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"锁定屏幕设置"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"扫描二维码"</string>
+    <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"锁屏设置"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"工作资料"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"飞行模式"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"您在<xliff:g id="WHEN">%1$s</xliff:g>将不会听到下次闹钟响铃"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"切换回 <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"移动流量不会根据可用性自动切换"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"不用了"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"是,切换"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"放大整个屏幕"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大部分屏幕"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"切换"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"允许沿对角线滚动"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"调整大小"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"更改放大类型"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"撤消"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{已移除快捷方式 {label}}other{已移除 # 个快捷方式}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"移除"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"暂时已连接"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"连接状况不佳"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"允许“<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>”访问所有设备日志吗?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"允许访问一次"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"不允许"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"设备日志会记录设备上发生的活动。应用可以使用这些日志查找和修复问题。\n\n部分日志可能包含敏感信息,因此请仅允许您信任的应用访问所有设备日志。\n\n如果您不授予此应用访问所有设备日志的权限,它仍然可以访问自己的日志。您的设备制造商可能仍然能够访问设备上的部分日志或信息。"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 28420b3..eda59b5 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"一律允許透過這部電腦進行"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"允許"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"不允許 USB 偵錯"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"目前登入此裝置的使用者無法啟用 USB 偵錯功能。如要使用此功能,請切換至主要使用者。"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"目前登入這部裝置的使用者無法開啟 USB 偵錯功能。如要使用這項功能,請切換為管理員使用者帳戶。"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"要將系統語言變更為<xliff:g id="LANGUAGE">%1$s</xliff:g>嗎?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"另一部裝置要求變更系統語言"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"變更語言"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"一律允許在此網絡上執行"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"允許"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"不允許無線偵錯功能"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"目前登入此裝置的使用者無法啟用無線偵錯功能。如要使用此功能,請切換至主要使用者。"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"目前登入這部裝置的使用者無法開啟無線偵錯功能。如要使用這項功能,請切換為管理員使用者帳戶。"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"已停用 USB 連接埠"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"為了保護您的裝置免受液體或碎片損害,USB 連接埠已停用,因此不會偵測到任何配件。\n\nUSB 連接埠可安全使用時,您會收到通知。"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"已啟用 USB 連接埠以偵測充電器和配件"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"語音助手"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"錢包"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR 碼掃瞄器"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"解鎖"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"裝置已上鎖"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"掃瞄緊面孔"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"傳送"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"無法辨識面孔"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"請改用指紋"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"電量百分比不明。"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已連線至<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"飛航模式。"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"開咗 VPN。"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"電池電量為百分之 <xliff:g id="NUMBER">%d</xliff:g>。"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"電量仲剩番 <xliff:g id="PERCENTAGE">%1$s</xliff:g>。根據你嘅使用情況,仲可以用大約 <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"電量仲剩番 <xliff:g id="PERCENTAGE">%1$d</xliff:g>。根據你嘅使用情況,仲可以用大約 <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"正在充電:<xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%。"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"睇所有通知"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter (TTY) 已啟用。"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"鈴聲震動。"</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"可使用麥克風"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"可使用相機"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"可使用麥克風和相機"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"麥克風已開啟"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"麥克風已關閉"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"已為所有應用程式和服務啟用麥克風。"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"已停用所有應用程式和服務的麥克風存取權。您可以在 [設定] &gt; [私隱] &gt; [麥克風] 啟用麥克風存取權。"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"已停用所有應用程式和服務的麥克風存取權。您可以在 [設定] &gt; [私隱] &gt; [麥克風] 更改設定。"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"相機已開啟"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"相機已關閉"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"已為所有應用程式和服務啟用相機。"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"已停用所有應用程式和服務的相機存取權。"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"如要使用麥克風按鈕,請在「設定」中啟用麥克風存取權。"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"開啟設定。"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"其他裝置"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"切換概覽"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"您不會受到聲音和震動騷擾 (鬧鐘、提醒、活動和您指定的來電者鈴聲除外)。當您選擇播放音樂、影片和遊戲等,仍可以聽到該內容的聲音。"</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"繼續"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或錄製應用程式"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"要允許此應用程式分享或錄製內容嗎?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"進行分享、錄製或投放時,此應用程式可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"進行分享、錄製或投放時,此應用程式可存取顯示在螢幕畫面上或在該應用程式上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"已被您的 IT 管理員封鎖"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"螢幕截圖功能因裝置政策而停用"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"記錄"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"解鎖即可使用"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"擷取資訊卡時發生問題,請稍後再試。"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"上鎖畫面設定"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"掃瞄 QR 碼"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"工作設定檔"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"飛行模式"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"您不會<xliff:g id="WHEN">%1$s</xliff:g>聽到鬧鐘"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"要切換回「<xliff:g id="CARRIER">%s</xliff:g>」嗎?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"流動數據不會根據可用性自動切換"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"不用了,謝謝"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"是,切換回 DDS 對話框"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"放大成個畫面"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大部分螢幕畫面"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"切換"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"允許斜角捲動"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"調整大小"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"變更放大類型"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"復原"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{已移除 {label} 個捷徑}other{已移除 # 個捷徑}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"移除"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"已暫時連線"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"連線速度欠佳"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"MMM d EEE"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"要允許「<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>」存取所有裝置記錄嗎?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"允許一次性存取"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"不允許"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"系統會透過裝置記錄記下裝置上的活動。應用程式可以根據這些記錄找出問題並修正。\n\n某些記錄可能含有機密資訊,因此請勿讓不信任的應用程式存取所有裝置記錄。\n\n即使你不允許這個應用程式存取所有裝置記錄,這個應用程式仍能存取自己的記錄,而且裝置製造商或許仍可存取裝置的某些記錄或資訊。"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 8a1a9e2..24e85c7 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"一律允許透過這台電腦進行"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"允許"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"無權使用 USB 偵錯功能"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"目前登入這個裝置的使用者無法啟用 USB 偵錯功能。如要使用這項功能,請切換到主要使用者。"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"目前登入這部裝置的使用者無法開啟 USB 偵錯功能。如要使用這項功能,請切換為管理員使用者帳戶。"</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"要將系統語言變更為<xliff:g id="LANGUAGE">%1$s</xliff:g>嗎?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"另一部裝置要求變更系統語言"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"變更語言"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"一律允許透過這個網路執行"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"允許"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"不允許使用無線偵錯功能"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"目前登入這部裝置的使用者無法開啟無線偵錯功能。如要使用這項功能,請切換到主要使用者。"</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"目前登入這部裝置的使用者無法開啟無線偵錯功能。如要使用這項功能,請切換為管理員使用者帳戶。"</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"USB 連接埠已停用"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"為了避免液體或灰塵導致你的裝置受損,系統已停用 USB 連接埠,因此目前無法偵測任何配件。\n\n系統會在可繼續使用 USB 連接埠時通知你。"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB 連接埠已啟用,可偵測充電器和配件"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"語音小幫手"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"錢包"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR code 掃描器"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"解除鎖定"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"裝置已鎖定"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"掃描臉孔"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"傳送"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"無法辨識臉孔"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"請改用指紋"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"電池電量不明。"</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已連線至<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"飛行模式。"</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN 已開啟。"</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"電池電量為百分之 <xliff:g id="NUMBER">%d</xliff:g>。"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"目前的電量為 <xliff:g id="PERCENTAGE">%1$s</xliff:g>。根據你的使用情形,大約還能使用到<xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"目前的電量為 <xliff:g id="PERCENTAGE">%1$d</xliff:g>。根據你的使用情形,大約還能使用到<xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"充電中,已完成 <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%。"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"查看所有通知"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter (TTY) 已啟用。"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"鈴聲震動。"</string>
@@ -248,7 +255,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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"可使用麥克風"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"可使用相機"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"可使用麥克風和相機"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"麥克風已開啟"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"麥克風已關閉"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"所有應用程式和服務的麥克風存取權皆已啟用。"</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"所有應用程式和服務的麥克風存取權皆已停用。如要啟用麥克風存取權,請依序前往「設定」&gt;「隱私權」&gt;「麥克風」。"</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"所有應用程式和服務的麥克風存取權皆已停用。如要變更這項設定,請依序前往「設定」&gt;「隱私權」&gt;「麥克風」。"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"相機已開啟"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"相機已關閉"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"所有應用程式和服務的相機存取權皆已啟用。"</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"所有應用程式和服務的相機存取權皆已停用。"</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"如要使用麥克風按鈕,請前往「設定」啟用麥克風存取權。"</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"開啟設定。"</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"其他裝置"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"切換總覽"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"裝置不會發出音效或震動造成干擾,但是會保留與鬧鐘、提醒、活動和指定來電者有關的設定。如果你選擇播放音樂、影片和遊戲等內容,還是可以聽見相關音訊。"</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放應用程式時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"繼續"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或錄製應用程式"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"要允許這個應用程式分享或錄製嗎?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"進行分享、錄製或投放時,這個應用程式可以存取螢幕畫面上所顯示或裝置上所播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"進行分享、錄製或投放應用程式時,這個應用程式可以存取在其中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 管理員已封鎖這項操作"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"根據裝置政策規定,螢幕畫面擷取功能已停用"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"記錄"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"解鎖即可使用"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"擷取卡片時發生問題,請稍後再試"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"螢幕鎖定設定"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"掃描 QR 圖碼"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"工作資料夾"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"飛航模式"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"你不會聽到下一個<xliff:g id="WHEN">%1$s</xliff:g> 的鬧鐘"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"要切換回「<xliff:g id="CARRIER">%s</xliff:g>」嗎?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"行動數據不會依據可用性自動切換"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"不用了,謝謝"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"是,切換回 DDS 對話方塊"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"放大整個螢幕畫面"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大局部螢幕畫面"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"切換"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"允許沿對角線捲動"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"調整大小"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"變更放大類型"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"復原"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{已移除 {label} 個捷徑}other{已移除 # 個捷徑}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"移除"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"已暫時建立連線"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"連線品質不佳"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"MMM d EEE"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"要允許「<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g>」存取所有裝置記錄嗎?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"允許一次性存取"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"不允許"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"系統會透過裝置記錄記下裝置上的活動。應用程式可以根據這些記錄找出問題並修正。\n\n某些記錄可能含有機密資訊,因此請勿讓不信任的應用程式存取所有裝置記錄。\n\n即使你不允許這個應用程式存取所有裝置記錄,這個應用程式仍能存取自己的記錄,而且裝置製造商或許仍可存取裝置的某些記錄或資訊。"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 9b376f0..70b67ea 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -52,7 +52,7 @@
     <string name="usb_debugging_always" msgid="4003121804294739548">"Hlala uvumela njalo kusuka kule khompyutha"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Vumela"</string>
     <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Ukususa iphutha kwe-USB akuvunyelwe"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Umsebenzisi manje ongene ngemvume kule divayisi entsha akakwazi ukuvula ukulungisa amaphutha ku-USB. Ukuze usebenzise lesi sici, shintshela kumsebenzisi oyinhloko."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="1888835696965417845">"Umsebenzisi ongene ngemvume manje kule divayisi entsha akakwazi ukuvula ukulungisa iphutha le-USB. Ukuze usebenzise lesi sakhi, shintshela kumsebenzisi ongumphathi."</string>
     <string name="hdmi_cec_set_menu_language_title" msgid="1259765420091503742">"Ingabe ufuna ukushintsha ulimi lwesistimu lube isi-<xliff:g id="LANGUAGE">%1$s</xliff:g>?"</string>
     <string name="hdmi_cec_set_menu_language_description" msgid="8176716678074126619">"Ushintsho lolimi lwesistimu lucelwe enye idivayisi"</string>
     <string name="hdmi_cec_set_menu_language_accept" msgid="2513689457281009578">"Shintsha ulimi"</string>
@@ -62,7 +62,7 @@
     <string name="wifi_debugging_always" msgid="2968383799517975155">"Njalo nje vumela le nethiwekhi"</string>
     <string name="wifi_debugging_allow" msgid="4573224609684957886">"Vumela"</string>
     <string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Ukulungisa amaphutha okungenantambo akuvunyelwe"</string>
-    <string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"Umsebenzisi manje ongene ngemvume kule divayisi entsha akakwazi ukuvula ukulungisa amaphutha okungenantambo. Ukuze usebenzise lesi sici, shintshela kumsebenzisi oyinhloko."</string>
+    <string name="wifi_debugging_secondary_user_message" msgid="9085779370142222881">"Umsebenzisi ongene ngemvume manje kule divayisi entsha akakwazi ukuvula ukususa ibhagi nge-Wifi. Ukuze usebenzise lesi sakhi, shintshela kumsebenzisi oyinhloko."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Imbobo ye-USB ikhutshaziwe"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"Ukuze kuvikelwe idivayisi yakho kusukela kuketshezi noma ama-debris, imbobo ye-USB iyakhutshazwa futhi ngeke ize ithole noma iziphi izisetshenziswa.\n\nUzokwaziswa uma sekulungile ukusebenzisa imbobo ye-USB futhi."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"Imbobo ye-USB inikwe amandla ukuze ithole amashaja nezisetshenziswa"</string>
@@ -125,7 +125,8 @@
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Isisekeli sezwi"</string>
     <string name="accessibility_wallet_button" msgid="1458258783460555507">"I-wallet"</string>
     <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Iskena sekhodi ye-QR"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"Vula"</string>
+    <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+    <skip />
     <string name="accessibility_lock_icon" msgid="661492842417875775">"Idivayisi ikhiyiwe"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"Ukuskena ubuso"</string>
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Thumela"</string>
@@ -168,6 +169,8 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Ayikwazi ukubona ubuso"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Kunalokho sebenzisa isigxivizo somunwe"</string>
+    <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
+    <skip />
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ixhunyiwe"</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Iphesenti lebhethri alaziwa."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Xhuma ku-<xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
@@ -178,8 +181,12 @@
     <string name="accessibility_airplane_mode" msgid="1899529214045998505">"Imodi yendiza."</string>
     <string name="accessibility_vpn_on" msgid="8037549696057288731">"I-VPN ivuliwe."</string>
     <string name="accessibility_battery_level" msgid="5143715405241138822">"Iphesenti <xliff:g id="NUMBER">%d</xliff:g> lebhethri"</string>
-    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Amaphesenti ebhethri ngu-<xliff:g id="PERCENTAGE">%1$s</xliff:g>, cishe kusele okungu-<xliff:g id="TIME">%2$s</xliff:g> kusukela ekusetshenzisweni kwakho"</string>
+    <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Amaphesenti ebhethri ngu-<xliff:g id="PERCENTAGE">%1$d</xliff:g>, cishe kusele okungu-<xliff:g id="TIME">%2$s</xliff:g> kusukela ekusetshenzisweni kwakho"</string>
     <string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Ibhethri liyashaja, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%"</string>
+    <!-- no translation found for accessibility_battery_level_charging_paused (1716051308782906917) -->
+    <skip />
+    <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (4006089349465741762) -->
+    <skip />
     <string name="accessibility_overflow_action" msgid="8555835828182509104">"Bona zonke izaziso"</string>
     <string name="accessibility_tty_enabled" msgid="1123180388823381118">"i-TeleTypewriter inikwe amandla"</string>
     <string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ukudlidliza kweringa."</string>
@@ -248,7 +255,7 @@
     <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>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Phatha abasebenzisi"</string>
     <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>
@@ -303,6 +310,17 @@
     <string name="sensor_privacy_mic_unblocked_toast_content" msgid="306555320557065068">"Imakrofoni iyatholakala"</string>
     <string name="sensor_privacy_camera_unblocked_toast_content" msgid="7843105715964332311">"Ikhamera iyatholakala"</string>
     <string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Imakrofoni nekhamera kuyatholakala"</string>
+    <string name="sensor_privacy_mic_turned_on_dialog_title" msgid="6348853159838376513">"Imakrofoni ivuliwe"</string>
+    <string name="sensor_privacy_mic_turned_off_dialog_title" msgid="5760464281790732849">"Imakrofoni ivaliwe"</string>
+    <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Imakrofoni inikwe amandla kuwo wonke ama-app namasevisi."</string>
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Ukufinyelela kumakrofoni kukhutshaziwe kuwo wonke ama-app namasevisi. Unganika amandla ukufinyelela kumakrofoni kokuthi Amasethingi &gt; Ubumfihlo &gt; Imakrofoni."</string>
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Ukufinyelela kumakrofoni kukhutshaziwe kuwo wonke ama-app namasevisi. Ungashintsha lokhu kokuthi Amasethingi &gt; Ubumfihlo &gt; Imakrofoni."</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Ikhamera ivuliwe"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Ikhamera ivaliwe"</string>
+    <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Ikhamera inikwe amandla kuwo wonke ama-app namasevisi."</string>
+    <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Ukufinyelela kukhamera kukhutshaziwe kuwo wonke ama-app namasevisi."</string>
+    <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Ukuze usebenzise inkinobho yemakrofoni, nika amandla ukufinyelela kumakrofoni kokuthi Amasethingi."</string>
+    <string name="sensor_privacy_dialog_open_settings" msgid="1503088305279285048">"Vula izilungiselelo."</string>
     <string name="media_seamless_other_device" msgid="4654849800789196737">"Enye idivayisi"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Guqula ukubuka konke"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"Ngeke uphazanyiswe imisindo nokudlidliza, ngaphandle kusukela kuma-alamu, izikhumbuzi, imicimbi, nabafonayo obacacisayo. Usazozwa noma yini okhetha ukuyidlala okufaka umculo, amavidiyo, namageyimu."</string>
@@ -373,6 +391,11 @@
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Uma wabelana, urekhoda, noma usakaza i-app, i-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> inokufinyelela kunoma yini eboniswayo noma edlalwayo kuleyo app. Ngakho-ke qaphela amagama ayimfihlo, imininingwane yokukhokha, imiyalezo, noma olunye ulwazi olubucayi."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Qhubeka"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Yabelana noma rekhoda i-app"</string>
+    <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Vumela le-app ukwabelana noma ukurekhoda?"</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Uma wabelana, urekhoda, noma usakaza, le-app inokufinyelela kunoma yini ebonakalayo kusikrini sakho noma edlalwa kudivayisi yakho. Ngakho-ke qaphela amagama ayimfihlo, imininingwane yokukhokha, imiyalezo, noma olunye ulwazi olubucayi."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Uma wabelana, urekhoda, noma usakaza i-app, le-app inokufinyelela kunoma yini eboniswayo noma edlalwayo kuleyo app. Ngakho-ke qaphela amagama ayimfihlo, imininingwane yokukhokha, imiyalezo, noma olunye ulwazi olubucayi."</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Kuvinjelwe ngumlawuli wakho we-IT"</string>
+    <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ukuthwebula isikrini kukhutshazwe yinqubomgomo yedivayisi"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Sula konke"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Phatha"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Umlando"</string>
@@ -488,7 +511,8 @@
     <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Vula ukuze usebenzise"</string>
     <string name="wallet_error_generic" msgid="257704570182963611">"Kube khona inkinga yokuthola amakhadi akho, sicela uzame futhi ngemuva kwesikhathi"</string>
     <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Amasethingi okukhiya isikrini"</string>
-    <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skena ikhodi ye-QR"</string>
+    <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+    <skip />
     <string name="status_bar_work" msgid="5238641949837091056">"Iphrofayela yomsebenzi"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Imodi yendiza"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"Ngeke uzwe i-alamu yakho elandelayo ngo-<xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -727,6 +751,10 @@
     <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>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Shintshela emuva ku-<xliff:g id="CARRIER">%s</xliff:g>?"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Idatha yeselula ngeke ishintshe ngokuzenzakalelayo ngokusekelwe ekutholakaleni"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Cha ngiyabonga"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Yebo, shintsha"</string>
     <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>
@@ -767,6 +795,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Khulisa isikrini esigcwele"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Khulisa ingxenye eyesikrini"</string>
     <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Iswishi"</string>
+    <!-- no translation found for magnification_drag_corner_to_resize (1249766311052418130) -->
+    <skip />
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Vumela ukuskrola oku-diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Shintsha usayizi"</string>
     <string name="accessibility_change_magnification_type" msgid="666000085077432421">"Shintsha uhlobo lokukhuliswa"</string>
@@ -785,12 +815,15 @@
     <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>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Hlehlisa"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{Isinqamuleli se-{label} sisusiwe}one{Izinqamuleli ezingu-# zikhishiwe}other{Izinqamuleli ezingu-# zikhishiwe}}"</string>
     <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>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Susa"</string>
     <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 +966,8 @@
     <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>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ixhume okwesikhashana"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"Uxhumo olungeluhle"</string>
     <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>
@@ -992,4 +1027,8 @@
     <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
     <string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
     <string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
+    <string name="log_access_confirmation_title" msgid="4843557604739943395">"Vumela i-<xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> ukuba ifinyelele wonke amalogu edivayisi?"</string>
+    <string name="log_access_confirmation_allow" msgid="752147861593202968">"Vumela ukufinyelela kwesikhathi esisodwa"</string>
+    <string name="log_access_confirmation_deny" msgid="2389461495803585795">"Ungavumeli"</string>
+    <string name="log_access_confirmation_body" msgid="6883031912003112634">"Amalogu edivayisi arekhoda okwenzekayo kudivayisi yakho. Ama-app angasebenzisa lawa malogu ukuze athole futhi alungise izinkinga.\n\nAmanye amalogu angase aqukathe ulwazi olubucayi, ngakho vumela ama-app owathembayo kuphela ukuthi afinyelele wonke amalogu edivayisi. \n\nUma ungayivumeli le app ukuthi ifinyelele wonke amalogu wedivayisi, isengakwazi ukufinyelela amalogu wayo. Umkhiqizi wedivayisi yakho usengakwazi ukufinyelela amanye amalogu noma ulwazi kudivayisi yakho."</string>
 </resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index df0659d..5b6c9d3 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -204,5 +204,20 @@
         <attr name="passwordTextAppearance" format="reference" />
         <attr name="errorTextAppearance" format="reference"/>
     </declare-styleable>
+
+    <declare-styleable name="LogAccessPermissionGrantDialog">
+        <attr name="permissionGrantButtonTopStyle" format="reference"/>
+        <attr name="permissionGrantButtonBottomStyle" format="reference"/>
+    </declare-styleable>
+
+    <declare-styleable name="BiometricsEnrollView">
+        <attr name="biometricsEnrollStyle" format="reference" />
+        <attr name="biometricsEnrollIcon" format="reference|color" />
+        <attr name="biometricsMovingTargetFill" format="reference|color" />
+        <attr name="biometricsMovingTargetFillError" format="reference|color" />
+        <attr name="biometricsEnrollProgress" format="reference|color" />
+        <attr name="biometricsEnrollProgressHelp" format="reference|color" />
+        <attr name="biometricsEnrollProgressHelpWithTalkback" format="reference|color" />
+    </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml
index 8221d78..04fc4b8 100644
--- a/packages/SystemUI/res/values/bools.xml
+++ b/packages/SystemUI/res/values/bools.xml
@@ -25,6 +25,9 @@
     <!-- Whether to enable clipping on Quick Settings -->
     <bool name="qs_enable_clipping">true</bool>
 
+    <!-- Whether to enable clipping on Notification Views -->
+    <bool name="notification_enable_clipping">true</bool>
+
     <!-- Whether to enable transparent background for notification scrims -->
     <bool name="notification_scrim_transparent">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 55b59b6..70d53c7 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -133,13 +133,16 @@
     <color name="biometric_dialog_accent">@color/material_dynamic_primary40</color>
     <color name="biometric_dialog_error">#ffd93025</color>                  <!-- red 600 -->
 
+    <!-- SFPS colors -->
+    <color name="sfps_chevron_fill">@color/material_dynamic_primary90</color>
+
     <!-- UDFPS colors -->
-    <color name="udfps_enroll_icon">#7DA7F1</color>
-    <color name="udfps_moving_target_fill">#475670</color>
+    <color name="udfps_enroll_icon">#699FF3</color>
+    <color name="udfps_moving_target_fill">#C2D7F7</color>
     <!-- 50% of udfps_moving_target_fill-->
-    <color name="udfps_moving_target_fill_error">#80475670</color>
-    <color name="udfps_enroll_progress">#7DA7F1</color>
-    <color name="udfps_enroll_progress_help">#607DA7F1</color>
+    <color name="udfps_moving_target_fill_error">#80C2D7F7</color>
+    <color name="udfps_enroll_progress">#699FF3</color>
+    <color name="udfps_enroll_progress_help">#70699FF3</color>
     <color name="udfps_enroll_progress_help_with_talkback">#FFEE675C</color>
 
     <!-- Floating overlay actions -->
@@ -172,6 +175,10 @@
     <color name="accessibility_magnifier_bg">#FCFCFC</color>
     <color name="accessibility_magnifier_bg_stroke">#E0E0E0</color>
     <color name="accessibility_magnifier_icon_color">#252525</color>
+    <color name="accessibility_window_magnifier_button_bg">#0680FD</color>
+    <color name="accessibility_window_magnifier_icon_color">#FAFAFA</color>
+    <color name="accessibility_window_magnifier_button_bg_stroke">#252525</color>
+    <color name="accessibility_window_magnifier_corner_view_color">#0680FD</color>
 
     <!-- Volume dialog colors -->
     <color name="volume_dialog_background_color">@android:color/transparent</color>
@@ -253,4 +260,8 @@
     <color name="dream_overlay_clock_ambient_text_shadow_color">#4D000000</color>
     <color name="dream_overlay_status_bar_key_text_shadow_color">#66000000</color>
     <color name="dream_overlay_status_bar_ambient_text_shadow_color">#59000000</color>
+
+    <!-- Rear Display Education -->
+    <color name="rear_display_overlay_animation_background_color">#1E1B17</color>
+    <color name="rear_display_overlay_dialog_background_color">#1E1B17</color>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 93982cb..247e44d 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -285,6 +285,9 @@
     <!-- Whether to show the full screen user switcher. -->
     <bool name="config_enableFullscreenUserSwitcher">false</bool>
 
+    <!-- Determines whether the shell features all run on another thread. -->
+    <bool name="config_enableShellMainThread">true</bool>
+
     <!-- SystemUIFactory component -->
     <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIInitializerImpl</string>
 
@@ -485,6 +488,12 @@
     <!-- Whether to show a severe low battery dialog. -->
     <bool name="config_severe_battery_dialog">false</bool>
 
+    <!-- A path representing a shield. Will sometimes be displayed with the battery icon when
+         needed. This path is a 10px wide and 13px tall. -->
+    <string name="config_batterymeterShieldPath" translatable="false">
+        M5 0L0 1.88V6.19C0 9.35 2.13 12.29 5 13.01C7.87 12.29 10 9.35 10 6.19V1.88L5 0Z
+    </string>
+
     <!-- A path similar to frameworks/base/core/res/res/values/config.xml
       config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display
       cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then
@@ -546,7 +555,7 @@
     <string name="config_preferredEmergencySosPackage" translatable="false"></string>
 
     <!-- Whether to show the side fps hint while on bouncer -->
-    <bool name="config_show_sidefps_hint_on_bouncer">false</bool>
+    <bool name="config_show_sidefps_hint_on_bouncer">true</bool>
 
     <!-- Whether to use the split 2-column notification shade -->
     <bool name="config_use_split_notification_shade">false</bool>
@@ -737,12 +746,46 @@
     <!-- How long in milliseconds before full burn-in protection is achieved. -->
     <integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer>
 
+    <!-- The duration in milliseconds of the y-translation animation when waking up from
+         the dream -->
+    <integer name="config_dreamOverlayOutTranslationYDurationMs">333</integer>
+    <!-- The delay in milliseconds of the y-translation animation when waking up from
+         the dream for the complications at the bottom of the screen -->
+    <integer name="config_dreamOverlayOutTranslationYDelayBottomMs">33</integer>
+    <!-- The delay in milliseconds of the y-translation animation when waking up from
+         the dream for the complications at the top of the screen -->
+    <integer name="config_dreamOverlayOutTranslationYDelayTopMs">117</integer>
+    <!-- The duration in milliseconds of the alpha animation when waking up from the dream -->
+    <integer name="config_dreamOverlayOutAlphaDurationMs">200</integer>
+    <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
+         complications at the top of the screen -->
+    <integer name="config_dreamOverlayOutAlphaDelayTopMs">217</integer>
+    <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
+         complications at the bottom of the screen -->
+    <integer name="config_dreamOverlayOutAlphaDelayBottomMs">133</integer>
+    <!-- The duration in milliseconds of the blur animation when waking up from
+         the dream -->
+    <integer name="config_dreamOverlayOutBlurDurationMs">250</integer>
+
     <integer name="complicationFadeOutMs">500</integer>
 
     <integer name="complicationFadeInMs">500</integer>
 
     <integer name="complicationRestoreMs">1000</integer>
 
+    <integer name="complicationFadeOutDelayMs">200</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>
@@ -763,24 +806,27 @@
         <item>com.android.systemui</item>
     </string-array>
 
-    <!-- The thresholds which determine the color used by the AQI dream overlay.
-         NOTE: This must always be kept sorted from low to high -->
-    <integer-array name="config_dreamAqiThresholds">
-        <item>-1</item>
-        <item>50</item>
-        <item>100</item>
-        <item>150</item>
-        <item>200</item>
-        <item>300</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>
 
-    <!-- The color values which correspond to the thresholds above -->
-    <integer-array name="config_dreamAqiColorValues">
-        <item>@color/dream_overlay_aqi_good</item>
-        <item>@color/dream_overlay_aqi_moderate</item>
-        <item>@color/dream_overlay_aqi_unhealthy_sensitive</item>
-        <item>@color/dream_overlay_aqi_unhealthy</item>
-        <item>@color/dream_overlay_aqi_very_unhealthy</item>
-        <item>@color/dream_overlay_aqi_hazardous</item>
-    </integer-array>
+    <!-- A collection of "slots" for placing quick affordance actions on the lock screen when the
+    device is locked. Each item is a string consisting of two parts, separated by the ':' character.
+    The first part is the unique ID for the slot, it is not a human-visible name, but should still
+    be unique across all slots specified. The second part is the capacity and must be a positive
+    integer; this is how many quick affordance actions that user is allowed to add to the slot. -->
+    <string-array name="config_keyguardQuickAffordanceSlots" translatable="false">
+        <item>bottom_start:1</item>
+        <item>bottom_end:1</item>
+    </string-array>
+
+    <!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
+    string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
+    separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
+    default is displayed by System UI as long as the user hasn't made a different choice for that
+    slot. If the user did make a choice, even if the choice is the "None" option, the default is
+    ignored. -->
+    <string-array name="config_keyguardQuickAffordanceDefaults" translatable="false">
+    </string-array>
+
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f02f29a..dc7e4e4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -105,6 +105,12 @@
     so the width of the icon should be 13.0dp * (12.0 / 20.0) -->
     <dimen name="status_bar_battery_icon_width">7.8dp</dimen>
 
+    <!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see
+         @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
+         the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
+         bottom still aligns with the bottom of all the other system icons. See b/258672854. -->
+    <dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen>
+
     <!-- The font size for the clock in the status bar. -->
     <dimen name="status_bar_clock_size">14sp</dimen>
 
@@ -397,6 +403,8 @@
         (quick_qs_offset_height (60dp)  - ongoing_appops_chip_height (24dp) ) / 2 -->
     <dimen name="notifications_top_padding_split_shade">18dp</dimen>
 
+    <dimen name="notifications_unseen_footer_icon_size">16dp</dimen>
+
     <!-- Height of the status bar header bar when on Keyguard -->
     <dimen name="status_bar_header_height_keyguard">40dp</dimen>
 
@@ -407,7 +415,7 @@
     <dimen name="match_parent">-1px</dimen>
 
     <!-- Height of status bar in split shade mode - visible only on large screens -->
-    <dimen name="large_screen_shade_header_height">@*android:dimen/quick_qs_offset_height</dimen>
+    <dimen name="large_screen_shade_header_height">48dp</dimen>
     <dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen>
     <dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen>
 
@@ -762,7 +770,7 @@
     <dimen name="keyguard_lock_padding">20dp</dimen>
 
     <dimen name="keyguard_indication_margin_bottom">32dp</dimen>
-    <dimen name="lock_icon_margin_bottom">110dp</dimen>
+    <dimen name="lock_icon_margin_bottom">74dp</dimen>
     <dimen name="ambient_indication_margin_bottom">71dp</dimen>
 
 
@@ -953,6 +961,10 @@
     <!-- Biometric Auth Credential values -->
     <dimen name="biometric_auth_icon_size">48dp</dimen>
 
+    <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+    <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+
     <!-- Starting text size in sp of batteryLevel for wireless charging animation -->
     <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">
         0
@@ -1045,6 +1057,7 @@
     <dimen name="qs_media_session_collapsed_guideline">144dp</dimen>
 
     <!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
+    <dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
     <dimen name="qs_media_rec_album_size">88dp</dimen>
     <dimen name="qs_media_rec_album_side_margin">16dp</dimen>
     <dimen name="qs_media_rec_album_bottom_margin">8dp</dimen>
@@ -1171,7 +1184,7 @@
 
     <!-- Screen record dialog -->
     <dimen name="screenrecord_option_padding">18dp</dimen>
-    <dimen name="screenrecord_logo_size">26dp</dimen>
+    <dimen name="screenrecord_logo_size">30dp</dimen>
     <dimen name="screenrecord_option_icon_size">24dp</dimen>
     <!-- Screen record status bar icon -->
     <dimen name="screenrecord_status_text_size">14sp</dimen>
@@ -1179,6 +1192,18 @@
     <dimen name="screenrecord_status_icon_width">21dp</dimen>
     <dimen name="screenrecord_status_icon_height">17.5dp</dimen>
     <dimen name="screenrecord_status_icon_bg_radius">8dp</dimen>
+    <!-- Screen record spinner -->
+    <dimen name="screenrecord_spinner_height">72dp</dimen>
+    <dimen name="screenrecord_spinner_margin">24dp</dimen>
+    <dimen name="screenrecord_spinner_text_padding_start">20dp</dimen>
+    <dimen name="screenrecord_spinner_text_padding_end">80dp</dimen>
+    <dimen name="screenrecord_spinner_arrow_size">24dp</dimen>
+    <dimen name="screenrecord_spinner_background_radius">28dp</dimen>
+
+    <dimen name="screenrecord_title_margin_top">20dp</dimen>
+    <dimen name="screenrecord_warning_line_height">20dp</dimen>
+    <dimen name="screenrecord_options_padding_bottom">16dp</dimen>
+    <dimen name="screenrecord_buttons_margin_top">20dp</dimen>
 
     <!-- Keyguard user switcher -->
     <dimen name="kg_user_switcher_text_size">16sp</dimen>
@@ -1335,6 +1360,7 @@
     <dimen name="accessibility_floating_menu_large_width_height">56dp</dimen>
     <dimen name="accessibility_floating_menu_large_single_radius">35dp</dimen>
     <dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen>
+    <dimen name="accessibility_floating_menu_ime_shifting_space">48dp</dimen>
 
     <dimen name="accessibility_floating_menu_message_container_horizontal_padding">15dp</dimen>
     <dimen name="accessibility_floating_menu_message_text_vertical_padding">8dp</dimen>
@@ -1416,6 +1442,11 @@
     <dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen>
     <dimen name="ongoing_call_chip_corner_radius">28dp</dimen>
 
+    <!-- Status bar user chip -->
+    <dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
+    <dimen name="status_bar_user_chip_end_margin">12dp</dimen>
+    <dimen name="status_bar_user_chip_text_size">12sp</dimen>
+
     <!-- Internet panel related dimensions -->
     <dimen name="internet_dialog_list_max_height">662dp</dimen>
     <!-- The height of the WiFi network in Internet panel. -->
@@ -1495,11 +1526,15 @@
     <dimen name="dream_overlay_status_bar_extra_margin">8dp</dimen>
 
     <!-- Dream overlay complications related dimensions -->
-    <dimen name="dream_overlay_complication_clock_time_text_size">86sp</dimen>
+    <dimen name="dream_overlay_complication_clock_time_text_size">86dp</dimen>
+    <dimen name="dream_overlay_complication_clock_time_translation_y">28dp</dimen>
+    <dimen name="dream_overlay_complication_home_controls_padding">28dp</dimen>
     <dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen>
     <dimen name="dream_overlay_complication_preview_text_size">36sp</dimen>
     <dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen>
     <dimen name="dream_overlay_complication_shadow_padding">2dp</dimen>
+    <dimen name="dream_overlay_complication_smartspace_padding">24dp</dimen>
+    <dimen name="dream_overlay_complication_smartspace_max_width">408dp</dimen>
 
     <!-- The position of the end guide, which dream overlay complications can align their start with
          if their end is aligned with the parent end. Represented as the percentage over from the
@@ -1544,6 +1579,7 @@
     <dimen name="dream_overlay_complication_margin">0dp</dimen>
 
     <dimen name="dream_overlay_y_offset">80dp</dimen>
+    <dimen name="dream_overlay_exit_y_offset">40dp</dimen>
 
     <dimen name="dream_aqi_badge_corner_radius">28dp</dimen>
     <dimen name="dream_aqi_badge_padding_vertical">6dp</dimen>
@@ -1596,4 +1632,10 @@
     <dimen name="config_rounded_mask_size">0px</dimen>
     <dimen name="config_rounded_mask_size_top">0px</dimen>
     <dimen name="config_rounded_mask_size_bottom">0px</dimen>
+
+    <!-- Rear Display Education dimens -->
+    <dimen name="rear_display_animation_width">273dp</dimen>
+    <dimen name="rear_display_animation_height">200dp</dimen>
+    <dimen name="rear_display_title_top_padding">24dp</dimen>
+    <dimen name="rear_display_title_bottom_padding">16dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 49dd574..fd2e324 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -36,4 +36,8 @@
       avatar will no longer show on the lockscreen -->
     <bool name="flag_user_switcher_chip">false</bool>
 
+    <!-- Whether the battery icon is allowed to display a shield when battery life is being
+         protected. -->
+    <bool name="flag_battery_shield_icon">false</bool>
+
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 4fd25a9..2b6ab30 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -195,5 +195,7 @@
     <item type="id" name="pm_lite"/>
     <item type="id" name="settings_button_container"/>
 
+    <item type="id" name="log_access_dialog_allow_button" />
+    <item type="id" name="log_access_dialog_deny_button" />
 </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..9ee918d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -142,7 +142,7 @@
     <string name="usb_debugging_secondary_user_title">USB debugging not allowed</string>
 
     <!-- Message of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
-    <string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user.</string>
+    <string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to an admin user.</string>
 
     <!-- Title of confirmation dialog for wireless debugging [CHAR LIMIT=80] -->
     <string name="hdmi_cec_set_menu_language_title">Do you want to change the system language to <xliff:g id="language" example="German">%1$s</xliff:g>?</string>
@@ -172,7 +172,7 @@
     <string name="wifi_debugging_secondary_user_title">Wireless debugging not allowed</string>
 
     <!-- Message of notification shown when trying to enable wireless debugging but a secondary user is the current foreground user. [CHAR LIMIT=NONE] -->
-    <string name="wifi_debugging_secondary_user_message">The user currently signed in to this device can\u2019t turn on wireless debugging. To use this feature, switch to the primary user.</string>
+    <string name="wifi_debugging_secondary_user_message">The user currently signed in to this device can\u2019t turn on wireless debugging. To use this feature, switch to an admin user.</string>
 
     <!-- Title of USB contaminant presence dialog [CHAR LIMIT=NONE] -->
     <string name="usb_contaminant_title">USB port disabled</string>
@@ -200,6 +200,8 @@
 
     <!-- Informs the user that a screenshot is being saved. [CHAR LIMIT=50] -->
     <string name="screenshot_saving_title">Saving screenshot\u2026</string>
+    <!-- Informs the user that a screenshot is being saved. [CHAR LIMIT=50] -->
+    <string name="screenshot_saving_work_profile_title">Saving screenshot to work profile\u2026</string>
     <!-- Notification title displayed when a screenshot is saved to the Gallery. [CHAR LIMIT=50] -->
     <string name="screenshot_saved_title">Screenshot saved</string>
     <!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
@@ -235,6 +237,8 @@
     <string name="screenshot_left_boundary_pct">Left boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string>
     <!-- Content description for the right boundary of the screenshot being cropped, with the current position as a percentage. [CHAR LIMIT=NONE] -->
     <string name="screenshot_right_boundary_pct">Right boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string>
+    <!-- Notification displayed when a screenshot is saved in a work profile. [CHAR LIMIT=NONE] -->
+    <string name="screenshot_work_profile_notification" translatable="false">Work screenshots are saved in the work <xliff:g id="app" example="Files">%1$s</xliff:g> app</string>
 
     <!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
     <string name="screenrecord_name">Screen Recorder</string>
@@ -314,7 +318,7 @@
     <!-- Content description of the QR Code scanner for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_qr_code_scanner_button">QR Code Scanner</string>
     <!-- Content description of the unlock button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_unlock_button">Unlock</string>
+    <string name="accessibility_unlock_button">Unlocked</string>
     <!-- Content description of the lock icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_lock_icon">Device locked</string>
     <!-- Content description hint of the unlock button when fingerprint is on (not shown on the screen). [CHAR LIMIT=NONE] -->
@@ -403,6 +407,8 @@
     <string name="keyguard_face_failed">Can\u2019t recognize face</string>
     <!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] -->
     <string name="keyguard_suggest_fingerprint">Use fingerprint instead</string>
+    <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=59] -->
+    <string name="keyguard_face_unlock_unavailable">Face Unlock unavailable</string>
 
     <!-- Content description of the bluetooth icon when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_bluetooth_connected">Bluetooth connected.</string>
@@ -435,11 +441,17 @@
     <string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string>
 
     <!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$s</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
+    <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g></string>
 
     <!-- Content description of the battery level icon for accessibility while the device is charging (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_battery_level_charging">Battery charging, <xliff:g id="battery_percentage">%d</xliff:g> percent.</string>
 
+    <!-- Content description of the battery level icon for accessibility, with information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent, charging paused for battery protection.</string>
+
+    <!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery *and* information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g>, charging paused for battery protection.</string>
+
     <!-- Content description of overflow icon container of the notifications for accessibility (not shown on the screen)[CHAR LIMIT=NONE] -->
     <string name="accessibility_overflow_action">See all notifications</string>
 
@@ -783,6 +795,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>
 
@@ -989,6 +1057,21 @@
     <!-- Title of the dialog that allows to select an app to share or record [CHAR LIMIT=NONE] -->
     <string name="media_projection_permission_app_selector_title">Share or record an app</string>
 
+    <!-- Media projection permission dialog title when there is no app name (e.g. it could be a system service when casting). [CHAR LIMIT=100] -->
+    <string name="media_projection_permission_dialog_system_service_title">Allow this app to share or record?</string>
+
+    <!-- Media projection permission warning for capturing the whole screen when a system service requests it (e.g. when casting). [CHAR LIMIT=350] -->
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen">When you\'re sharing, recording, or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+
+    <!-- Media projection permission warning for capturing a single app when a system service requests it (e.g. when casting). [CHAR LIMIT=350] -->
+    <string name="media_projection_permission_dialog_system_service_warning_single_app">When you\'re sharing, recording, or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+
+    <!-- Title for the dialog that is shown when screen capturing is disabled by enterprise policy. [CHAR LIMIT=100] -->
+    <string name="screen_capturing_disabled_by_policy_dialog_title">Blocked by your IT admin</string>
+
+    <!-- Description for the dialog that is shown when screen capturing is disabled by enterprise policy. [CHAR LIMIT=350] -->
+    <string name="screen_capturing_disabled_by_policy_dialog_description">Screen capturing is disabled by device policy</string>
+
     <!-- The text to clear all notifications. [CHAR LIMIT=60] -->
     <string name="clear_all_notifications_text">Clear all</string>
 
@@ -1022,6 +1105,12 @@
     <!-- Text which is shown in the notification shade when there are no notifications. [CHAR LIMIT=30] -->
     <string name="empty_shade_text">No notifications</string>
 
+    <!-- Text which is shown in the expanded notification shade when there are currently no notifications visible that the user hasn't already seen. [CHAR LIMIT=30] -->
+    <string name="no_unseen_notif_text">No new notifications</string>
+
+    <!-- Text which is shown in the locked notification shade when there are currently no notifications, but if the user were to unlock, notifications would appear. [CHAR LIMIT=40] -->
+    <string name="unlock_to_see_notif_text">Unlock to see older notifications</string>
+
     <!-- Disclosure at the bottom of Quick Settings that indicates that parental controls are enabled. [CHAR LIMIT=100] -->
     <string name="quick_settings_disclosure_parental_controls">This device is managed by your parent</string>
 
@@ -1294,7 +1383,10 @@
     <string name="wallet_lockscreen_settings_label">Lock screen settings</string>
 
     <!-- QR Code Scanner label, title [CHAR LIMIT=32] -->
-    <string name="qr_code_scanner_title">Scan QR code</string>
+    <string name="qr_code_scanner_title">QR code scanner</string>
+
+    <!-- QR Code Scanner Secondary label when GMS Core is Updating -->
+    <string name="qr_code_scanner_updating_secondary_label">Updating</string>
 
     <!-- Name of the work status bar icon. -->
     <string name="status_bar_work">Work profile</string>
@@ -1915,6 +2007,9 @@
     <!-- SysUI Tuner: Summary of no shortcut being selected [CHAR LIMIT=60] -->
     <string name="lockscreen_none">None</string>
 
+    <!-- ClockId to use when none is set by user -->
+    <string name="lockscreen_clock_id_fallback" translatable="false">DEFAULT</string>
+
     <!-- SysUI Tuner: Format string for describing launching an app [CHAR LIMIT=60] -->
     <string name="tuner_launch_app">Launch <xliff:g id="app" example="Settings">%1$s</xliff:g></string>
 
@@ -2156,6 +2251,8 @@
     <string name="magnification_mode_switch_state_window">Magnify part of screen</string>
     <!-- Click action label for magnification switch. [CHAR LIMIT=NONE] -->
     <string name="magnification_mode_switch_click_label">Switch</string>
+    <!-- Label of the corner of a rectangle that you can tap and drag to resize the magnification area. [CHAR LIMIT=NONE] -->
+    <string name="magnification_drag_corner_to_resize">Drag corner to resize</string>
 
     <!-- Title of the magnification option button allow diagonal scrolling [CHAR LIMIT=NONE]-->
     <string name="accessibility_allow_diagonal_scrolling">Allow diagonal scrolling</string>
@@ -2676,6 +2773,10 @@
         <xliff:g id="weather_condition" example="Partly cloudy">%1$s</xliff:g>, <xliff:g id="temperature" example="7°C">%2$s</xliff:g>
     </string>
 
+    <!-- TODO(b/259369672): Replace with final resource. -->
+    <!-- [CHAR LIMIT=30] Label used to open Note Task -->
+    <string name="note_task_button_label">Notetaking</string>
+
     <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is broadcasting -->
     <string name="broadcasting_description_is_broadcasting">Broadcasting</string>
     <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, title -->
@@ -2697,4 +2798,78 @@
 
     <!-- Time format for the Dream Time Complication for 24-hour time format [CHAR LIMIT=NONE] -->
     <string name="dream_time_complication_24_hr_time_format">kk:mm</string>
+
+    <!-- Title for the log access confirmation dialog. [CHAR LIMIT=NONE] -->
+    <string name="log_access_confirmation_title">Allow <xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> to access all device logs?</string>
+    <!-- Label for the allow button on the log access confirmation dialog. [CHAR LIMIT=40] -->
+    <string name="log_access_confirmation_allow">Allow one-time access</string>
+    <!-- Label for the deny button on the log access confirmation dialog. [CHAR LIMIT=20] -->
+    <string name="log_access_confirmation_deny">Don\u2019t allow</string>
+
+    <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
+    <string name="log_access_confirmation_body">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
+        \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.
+    </string>
+
+    <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
+    <string name="log_access_confirmation_learn_more" translatable="false">&lt;a href="https://support.google.com/android?p=system_logs#topic=7313011"&gt;Learn more&lt;/a&gt;</string>
+
+    <!--
+    Template for an action that opens a specific app. [CHAR LIMIT=16]
+    -->
+    <string name="keyguard_affordance_enablement_dialog_action_template">Open <xliff:g id="appName" example="Wallet">%1$s</xliff:g></string>
+
+    <!--
+    Template for a message shown right before a list of instructions that tell the user what to do
+    in order to enable a shortcut to a specific app. [CHAR LIMIT=NONE]
+    -->
+    <string name="keyguard_affordance_enablement_dialog_message">To add the <xliff:g id="appName" example="Wallet">%1$s</xliff:g> app as a shortcut, make sure</string>
+
+    <!--
+    Requirement for the wallet app to be available for the user to use. This is shown as part of a
+    bulleted list of requirements. When all requirements are met, the app can be accessed through a
+    shortcut button on the lock screen. [CHAR LIMIT=NONE].
+    -->
+    <string name="keyguard_affordance_enablement_dialog_wallet_instruction_1">&#8226; The app is set up</string>
+
+    <!--
+    Requirement for the wallet app to be available for the user to use. This is shown as part of a
+    bulleted list of requirements. When all requirements are met, the app can be accessed through a
+    shortcut button on the lock screen. [CHAR LIMIT=NONE].
+    -->
+    <string name="keyguard_affordance_enablement_dialog_wallet_instruction_2">&#8226; At least one card has been added to Wallet</string>
+
+    <!--
+    Requirement for the QR code scanner functionality to be available for the user to use. This is
+    shown as part of a bulleted list of requirements. When all requirements are met, the piece of
+    functionality can be accessed through a shortcut button on the lock screen. [CHAR LIMIT=NONE].
+    -->
+    <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction">&#8226; Install a camera app</string>
+
+    <!--
+    Requirement for the home app to be available for the user to use. This is shown as part of a
+    bulleted list of requirements. When all requirements are met, the app can be accessed through a
+    shortcut button on the lock screen. [CHAR LIMIT=NONE].
+    -->
+    <string name="keyguard_affordance_enablement_dialog_home_instruction_1">&#8226; The app is set up</string>
+
+    <!--
+    Requirement for the home app to be available for the user to use. This is shown as part of a
+    bulleted list of requirements. When all requirements are met, the app can be accessed through a
+    shortcut button on the lock screen. [CHAR LIMIT=NONE].
+    -->
+    <string name="keyguard_affordance_enablement_dialog_home_instruction_2">&#8226; At least one device is available</string>
+
+    <!-- Text for education page of cancel button to hide the page. [CHAR_LIMIT=NONE] -->
+    <string name="rear_display_bottom_sheet_cancel">Cancel</string>
+    <!-- Text for the user to confirm they flipped the device around. [CHAR_LIMIT=NONE] -->
+    <string name="rear_display_bottom_sheet_confirm">Flip now</string>
+    <!-- Text for education page title to guide user to unfold phone. [CHAR_LIMIT=50] -->
+    <string name="rear_display_fold_bottom_sheet_title">Unfold phone for a better selfie</string>
+    <!-- Text for education page title to guide user to flip to the front display. [CHAR_LIMIT=50] -->
+    <string name="rear_display_unfold_bottom_sheet_title">Flip to front display for a better selfie?</string>
+    <!-- Text for education page description to suggest user to use rear selfie capture. [CHAR_LIMIT=NONE] -->
+    <string name="rear_display_bottom_sheet_description">Use the rear-facing camera for a wider photo with higher resolution.</string>
+    <!-- Text for education page description to warn user that the display will turn off if the button is clicked. [CHAR_LIMIT=NONE] -->
+    <string name="rear_display_bottom_sheet_warning"><b>&#x2731; This screen will turn off</b></string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index e76887b..aafa47f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -23,6 +23,12 @@
         <item name="android:textColor">@color/status_bar_clock_color</item>
     </style>
 
+    <style name="TextAppearance.StatusBar.UserChip" parent="@*android:style/TextAppearance.StatusBar.Icon">
+        <item name="android:textSize">@dimen/status_bar_user_chip_text_size</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:textColor">@color/status_bar_clock_color</item>
+    </style>
+
     <style name="TextAppearance.StatusBar.Expanded" parent="@*android:style/TextAppearance.StatusBar">
         <item name="android:textColor">?android:attr/textColorTertiary</item>
     </style>
@@ -245,11 +251,12 @@
 
     <style name="AuthCredentialPatternContainerStyle">
         <item name="android:gravity">center</item>
-        <item name="android:maxHeight">420dp</item>
-        <item name="android:maxWidth">420dp</item>
-        <item name="android:minHeight">200dp</item>
-        <item name="android:minWidth">200dp</item>
-        <item name="android:padding">20dp</item>
+        <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+        <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+        <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+        <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
+        <item name="android:paddingHorizontal">32dp</item>
+        <item name="android:paddingVertical">20dp</item>
     </style>
 
     <style name="AuthCredentialPinPasswordContainerStyle">
@@ -308,6 +315,10 @@
 
         <!-- Needed for MediaRoute chooser dialog -->
         <item name="*android:isLightTheme">false</item>
+
+        <!-- Biometrics enroll color style -->
+        <item name="biometricsEnrollStyle">@style/BiometricsEnrollStyle</item>
+
     </style>
 
     <style name="Theme.SystemUI.LightWallpaper">
@@ -1091,7 +1102,7 @@
         <item name="android:orientation">horizontal</item>
         <item name="android:focusable">true</item>
         <item name="android:clickable">true</item>
-        <item name="android:background">?android:attr/selectableItemBackground</item>
+        <item name="android:background">@drawable/internet_dialog_selected_effect</item>
     </style>
 
     <style name="InternetDialog.NetworkTitle">
@@ -1274,4 +1285,53 @@
         <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
         <item name="android:textSize">@dimen/broadcast_dialog_btn_text_size</item>
     </style>
+
+    <!-- The style for log access consent dialog -->
+    <style name="LogAccessDialogTheme" parent="@style/Theme.SystemUI.Dialog.Alert">
+        <item name="permissionGrantButtonTopStyle">@style/PermissionGrantButtonTop</item>
+        <item name="permissionGrantButtonBottomStyle">@style/PermissionGrantButtonBottom</item>
+    </style>
+
+    <style name="AllowLogAccess">
+        <item name="android:textSize">24sp</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+    </style>
+
+    <style name="PrimaryAllowLogAccess">
+        <item name="android:textSize">14sp</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+    </style>
+
+    <style name="PermissionGrantButtonTextAppearance">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@android:color/system_neutral1_900</item>
+    </style>
+
+    <style name="PermissionGrantButtonTop"
+           parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored">
+        <item name="android:layout_width">332dp</item>
+        <item name="android:layout_height">56dp</item>
+        <item name="android:layout_marginTop">2dp</item>
+        <item name="android:layout_marginBottom">2dp</item>
+        <item name="android:background">@drawable/grant_permissions_buttons_top</item>
+    </style>
+
+    <style name="PermissionGrantButtonBottom"
+           parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored">
+        <item name="android:layout_width">332dp</item>
+        <item name="android:layout_height">56dp</item>
+        <item name="android:layout_marginTop">2dp</item>
+        <item name="android:layout_marginBottom">2dp</item>
+        <item name="android:background">@drawable/grant_permissions_buttons_bottom</item>
+    </style>
+
+    <style name="BiometricsEnrollStyle">
+        <item name="biometricsEnrollIcon">@color/udfps_enroll_icon</item>
+        <item name="biometricsMovingTargetFill">@color/udfps_moving_target_fill</item>
+        <item name="biometricsMovingTargetFillError">@color/udfps_moving_target_fill_error</item>
+        <item name="biometricsEnrollProgress">@color/udfps_enroll_progress</item>
+        <item name="biometricsEnrollProgressHelp">@color/udfps_enroll_progress_help</item>
+        <item name="biometricsEnrollProgressHelpWithTalkback">@color/udfps_enroll_progress_help_with_talkback</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index de855e2..c32de70 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -124,20 +124,9 @@
         </KeyFrameSet>
     </Transition>
 
-    <Transition
-        android:id="@+id/large_screen_header_transition"
-        app:constraintSetStart="@id/large_screen_header_constraint"
-        app:constraintSetEnd="@id/large_screen_header_constraint"/>
+    <Include app:constraintSet="@xml/large_screen_shade_header"/>
 
-    <!--
-        Placeholder ConstraintSet. They are populated in the controller for this class.
-        This is needed because there's no easy way to just refer to a `ConstraintSet` file. The
-        options are either a layout file or inline the ConstraintSets.
-     -->
-    <ConstraintSet android:id="@id/qqs_header_constraint"/>
+    <Include app:constraintSet="@xml/qs_header"/>
 
-    <ConstraintSet android:id="@id/qs_header_constraint"/>
-
-    <ConstraintSet android:id="@id/large_screen_header_constraint" />
-
+    <Include app:constraintSet="@xml/qqs_header"/>
 </MotionScene>
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index cdbf8ab..06d425c 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -107,7 +107,7 @@
         android:id="@+id/privacy_container">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="0dp"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="@id/date"
             app:layout_constraintBottom_toBottomOf="@id/date"
diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml
index 9115d42..1eb621e 100644
--- a/packages/SystemUI/res/xml/media_session_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_session_collapsed.xml
@@ -34,6 +34,26 @@
         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" />
+
+    <!-- Turbulence noise must have the same constraint as the album art. -->
+    <Constraint
+        android:id="@+id/turbulence_noise_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..64c2ef1 100644
--- a/packages/SystemUI/res/xml/media_session_expanded.xml
+++ b/packages/SystemUI/res/xml/media_session_expanded.xml
@@ -27,6 +27,26 @@
         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" />
+
+    <!-- Turbulence noise must have the same constraint as the album art. -->
+    <Constraint
+        android:id="@+id/turbulence_noise_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/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index 88b4f43..e56e5d5 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -25,7 +25,7 @@
         android:id="@+id/clock">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="0dp"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintStart_toStartOf="@id/begin_guide"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent"
@@ -42,7 +42,7 @@
     <Constraint
         android:id="@+id/date">
         <Layout
-            android:layout_width="0dp"
+            android:layout_width="wrap_content"
             android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
             android:layout_marginStart="8dp"
             app:layout_constrainedWidth="true"
@@ -57,7 +57,7 @@
     <Constraint
         android:id="@+id/statusIcons">
         <Layout
-            android:layout_width="0dp"
+            android:layout_width="wrap_content"
             android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintStart_toEndOf="@id/date"
@@ -65,6 +65,7 @@
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintHorizontal_bias="1"
+            app:layout_constraintHorizontal_chainStyle="packed"
             />
     </Constraint>
 
@@ -73,19 +74,22 @@
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
-            app:layout_constrainedWidth="true"
             app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintStart_toEndOf="@id/statusIcons"
             app:layout_constraintEnd_toEndOf="@id/end_guide"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintHorizontal_bias="1"
+            app:layout_constraintHorizontal_chainStyle="packed"
             />
     </Constraint>
 
     <Constraint
         android:id="@+id/carrier_group">
         <Layout
+            app:layout_constraintWidth_min="48dp"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent"
             />
@@ -98,7 +102,7 @@
         android:id="@+id/privacy_container">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="0dp"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintStart_toEndOf="@id/date"
             app:layout_constraintEnd_toEndOf="@id/end_guide"
             app:layout_constraintTop_toTopOf="parent"
@@ -106,5 +110,4 @@
             app:layout_constraintHorizontal_bias="1"
         />
     </Constraint>
-
 </ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index 8248fcd..eca2b2a 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -22,50 +22,104 @@
 >
 
     <Constraint
+        android:id="@+id/privacy_container">
+        <Layout
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
+            app:layout_constraintEnd_toEndOf="@id/end_guide"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toTopOf="@id/carrier_group"
+            app:layout_constraintHorizontal_bias="1"
+            />
+    </Constraint>
+
+    <Constraint
         android:id="@+id/clock">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="48dp"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/date"
-            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/privacy_container"
+            app:layout_constraintBottom_toBottomOf="@id/carrier_group"
             app:layout_constraintEnd_toStartOf="@id/carrier_group"
             app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
         />
+        <Transform
+            android:scaleX="2.57"
+            android:scaleY="2.57"
+            />
     </Constraint>
 
     <Constraint
         android:id="@+id/date">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="48dp"
+            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+            app:layout_constrainedWidth="true"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toTopOf="@id/clock"
+            app:layout_constraintEnd_toStartOf="@id/space"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/carrier_group"
             app:layout_constraintHorizontal_bias="0"
-        />
-        <Motion
-            app:motionStagger="0.5"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
         />
     </Constraint>
 
     <Constraint
         android:id="@+id/carrier_group">
-        <CustomAttribute
-            app:attributeName="alpha"
-            app:customFloatValue="1"
-        />
+        <Layout
+            app:layout_constraintWidth_min="48dp"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
+            app:layout_constraintStart_toEndOf="@id/clock"
+            app:layout_constraintTop_toBottomOf="@id/privacy_container"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="1"
+            app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
+            />
+        <PropertySet
+            android:alpha="1"
+            />
     </Constraint>
 
     <Constraint
-        android:id="@+id/privacy_container">
+        android:id="@+id/statusIcons">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="48dp"
+            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+            app:layout_constraintStart_toEndOf="@id/space"
+            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+            app:layout_constraintTop_toTopOf="@id/date"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintHorizontal_bias="1"
+            />
+    </Constraint>
+
+    <Constraint
+        android:id="@+id/batteryRemainingIcon">
+        <Layout
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+            app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
+            app:layout_constraintStart_toEndOf="@id/statusIcons"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="@id/date"
-            app:layout_constraintBottom_toBottomOf="@id/date"
-        />
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintHorizontal_bias="1"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
+            />
+    </Constraint>
+
+
+    <Constraint
+        android:id="@id/space">
+        <Layout
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintStart_toEndOf="@id/date"
+            app:layout_constraintEnd_toStartOf="@id/statusIcons"
+            />
     </Constraint>
 </ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml
deleted file mode 100644
index d8a4e77..0000000
--- a/packages/SystemUI/res/xml/qs_header_new.xml
+++ /dev/null
@@ -1,124 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<ConstraintSet
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/qs_header_constraint"
->
-
-    <Constraint
-        android:id="@+id/privacy_container">
-        <Layout
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintEnd_toEndOf="@id/end_guide"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toTopOf="@id/carrier_group"
-            app:layout_constraintHorizontal_bias="1"
-            />
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/clock">
-        <Layout
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/privacy_container"
-            app:layout_constraintBottom_toBottomOf="@id/carrier_group"
-            app:layout_constraintEnd_toStartOf="@id/carrier_group"
-            app:layout_constraintHorizontal_bias="0"
-        />
-        <Transform
-            android:scaleX="2.57"
-            android:scaleY="2.57"
-            />
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/date">
-        <Layout
-            android:layout_width="0dp"
-            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toStartOf="@id/space"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/carrier_group"
-            app:layout_constraintHorizontal_bias="0"
-            app:layout_constraintHorizontal_chainStyle="spread_inside"
-        />
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/carrier_group">
-        <Layout
-            app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
-            android:minHeight="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintWidth_min="48dp"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            app:layout_constraintStart_toEndOf="@id/clock"
-            app:layout_constraintTop_toBottomOf="@id/privacy_container"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintHorizontal_bias="1"
-            app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
-            />
-        <PropertySet
-            android:alpha="1"
-            />
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/statusIcons">
-        <Layout
-            android:layout_width="0dp"
-            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
-            app:layout_constrainedWidth="true"
-            app:layout_constraintStart_toEndOf="@id/space"
-            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
-            app:layout_constraintTop_toTopOf="@id/date"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintHorizontal_bias="1"
-            />
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/batteryRemainingIcon">
-        <Layout
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
-            app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
-            app:layout_constraintStart_toEndOf="@id/statusIcons"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@id/date"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintHorizontal_bias="1"
-            />
-    </Constraint>
-
-
-    <Constraint
-        android:id="@id/space">
-        <Layout
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            app:layout_constraintStart_toEndOf="@id/date"
-            app:layout_constraintEnd_toStartOf="@id/statusIcons"
-            />
-    </Constraint>
-</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
index 49cc483..e032bb9 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
@@ -34,13 +34,19 @@
 /**
  * A rule that allows to run a screenshot diff test on a view that is hosted in another activity.
  */
-class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
+class ExternalViewScreenshotTestRule(
+    emulationSpec: DeviceEmulationSpec,
+    assetPathRelativeToBuildRoot: String
+) : TestRule {
 
     private val colorsRule = MaterialYouColorsRule()
     private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
     private val screenshotRule =
         ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+            SystemUIGoldenImagePathManager(
+                getEmulatedDevicePathConfig(emulationSpec),
+                assetPathRelativeToBuildRoot
+            )
         )
     private val delegateRule =
         RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule)
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
index fafc774..72d8c5a 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
@@ -23,11 +23,11 @@
 /** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */
 class SystemUIGoldenImagePathManager(
     pathConfig: PathConfig,
-    override val assetsPathRelativeToRepo: String = "tests/screenshot/assets"
+    assetsPathRelativeToBuildRoot: String
 ) :
     GoldenImagePathManager(
         appContext = InstrumentationRegistry.getInstrumentation().context,
-        assetsPathRelativeToRepo = assetsPathRelativeToRepo,
+        assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
         deviceLocalPath =
             InstrumentationRegistry.getInstrumentation()
                 .targetContext
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 0b0595f..6d0cc5e 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -34,6 +34,7 @@
 import platform.test.screenshot.DeviceEmulationRule
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.MaterialYouColorsRule
+import platform.test.screenshot.PathConfig
 import platform.test.screenshot.ScreenshotTestRule
 import platform.test.screenshot.getEmulatedDevicePathConfig
 import platform.test.screenshot.matchers.BitmapMatcher
@@ -41,13 +42,18 @@
 /** A rule for View screenshot diff unit tests. */
 class ViewScreenshotTestRule(
     emulationSpec: DeviceEmulationSpec,
-    private val matcher: BitmapMatcher = UnitTestBitmapMatcher
+    private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
+    pathConfig: PathConfig = getEmulatedDevicePathConfig(emulationSpec),
+    assetsPathRelativeToBuildRoot: String
 ) : TestRule {
     private val colorsRule = MaterialYouColorsRule()
     private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
     private val screenshotRule =
         ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+            SystemUIGoldenImagePathManager(
+                getEmulatedDevicePathConfig(emulationSpec),
+                assetsPathRelativeToBuildRoot
+            )
         )
     private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
     private val delegateRule =
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index ffaeaaa..8a0fca0 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -52,18 +52,20 @@
         "SystemUIUnfoldLib",
         "androidx.dynamicanimation_dynamicanimation",
         "androidx.concurrent_concurrent-futures",
-        "gson",
+        "androidx.lifecycle_lifecycle-runtime-ktx",
+        "androidx.lifecycle_lifecycle-viewmodel-ktx",
+        "androidx.recyclerview_recyclerview",
+        "kotlinx_coroutines_android",
+        "kotlinx_coroutines",
         "dagger2",
         "jsr330",
     ],
     resource_dirs: [
         "res",
     ],
-    optimize: {
-        proguard_flags_files: ["proguard.flags"],
-    },
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
+    kotlincflags: ["-Xjvm-default=enable"],
 }
 
 java_library {
diff --git a/packages/SystemUI/shared/proguard.flags b/packages/SystemUI/shared/proguard.flags
deleted file mode 100644
index 5eda045..0000000
--- a/packages/SystemUI/shared/proguard.flags
+++ /dev/null
@@ -1,4 +0,0 @@
-# Retain signatures of TypeToken and its subclasses for gson usage in ClockRegistry
--keepattributes Signature
--keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
--keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
\ No newline at end of file
diff --git a/packages/SystemUI/shared/res/values/attrs.xml b/packages/SystemUI/shared/res/values/attrs.xml
index 96a5840..f3aeaef 100644
--- a/packages/SystemUI/shared/res/values/attrs.xml
+++ b/packages/SystemUI/shared/res/values/attrs.xml
@@ -20,12 +20,6 @@
 -->
 
 <resources>
-    <declare-styleable name="AnimatableClockView">
-        <attr name="dozeWeight" format="integer" />
-        <attr name="lockScreenWeight" format="integer" />
-        <attr name="chargeAnimationDelay" format="integer" />
-    </declare-styleable>
-
     <declare-styleable name="DoubleShadowAttrDeclare">
         <attr name="keyShadowBlur" format="dimension" />
         <attr name="keyShadowOffsetX" format="dimension" />
diff --git a/packages/SystemUI/shared/res/values/dimens.xml b/packages/SystemUI/shared/res/values/dimens.xml
deleted file mode 100644
index 8f90f0f..0000000
--- a/packages/SystemUI/shared/res/values/dimens.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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.
-*/
--->
-<resources>
-    <!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
-    <dimen name="large_clock_text_size">150sp</dimen>
-    <dimen name="small_clock_text_size">86sp</dimen>
-
-    <!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
-    <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
-    <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
-    <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index f7049cf..196f7f0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -22,9 +22,19 @@
 import android.os.Parcel
 import android.os.Parcelable
 
+/**
+ * Base interface for flags that can change value on a running device.
+ * @property id unique id to help identify this flag. Must be unique. This will be removed soon.
+ * @property teamfood Set to true to include this flag as part of the teamfood flag. This will
+ *                    be removed soon.
+ * @property name Used for server-side flagging where appropriate. Also used for display. No spaces.
+ * @property namespace The server-side namespace that this flag lives under.
+ */
 interface Flag<T> {
     val id: Int
     val teamfood: Boolean
+    val name: String
+    val namespace: String
 }
 
 interface ParcelableFlag<T> : Flag<T>, Parcelable {
@@ -38,13 +48,10 @@
 }
 
 interface DeviceConfigFlag<T> : Flag<T> {
-    val name: String
-    val namespace: String
     val default: T
 }
 
 interface SysPropFlag<T> : Flag<T> {
-    val name: String
     val default: T
 }
 
@@ -56,6 +63,8 @@
 // Consider using the "parcelize" kotlin library.
 abstract class BooleanFlag constructor(
     override val id: Int,
+    override val name: String,
+    override val namespace: String,
     override val default: Boolean = false,
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
@@ -71,6 +80,8 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
+        name = parcel.readString(),
+        namespace = parcel.readString(),
         default = parcel.readBoolean(),
         teamfood = parcel.readBoolean(),
         overridden = parcel.readBoolean()
@@ -78,6 +89,8 @@
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
         parcel.writeInt(id)
+        parcel.writeString(name)
+        parcel.writeString(namespace)
         parcel.writeBoolean(default)
         parcel.writeBoolean(teamfood)
         parcel.writeBoolean(overridden)
@@ -89,30 +102,36 @@
  *
  * It can be changed or overridden in debug builds but not in release builds.
  */
-data class UnreleasedFlag @JvmOverloads constructor(
+data class UnreleasedFlag constructor(
     override val id: Int,
+    override val name: String,
+    override val namespace: String,
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
-) : BooleanFlag(id, false, teamfood, overridden)
+) : BooleanFlag(id, name, namespace, false, teamfood, overridden)
 
 /**
- * A Flag that is is true by default.
+ * A Flag that is true by default.
  *
  * It can be changed or overridden in any build, meaning it can be turned off if needed.
  */
-data class ReleasedFlag @JvmOverloads constructor(
+data class ReleasedFlag constructor(
     override val id: Int,
+    override val name: String,
+    override val namespace: String,
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
-) : BooleanFlag(id, true, teamfood, overridden)
+) : BooleanFlag(id, name, namespace, true, teamfood, overridden)
 
 /**
  * A Flag that reads its default values from a resource overlay instead of code.
  *
  * Prefer [UnreleasedFlag] and [ReleasedFlag].
  */
-data class ResourceBooleanFlag @JvmOverloads constructor(
+data class ResourceBooleanFlag constructor(
     override val id: Int,
+    override val name: String,
+    override val namespace: String,
     @BoolRes override val resourceId: Int,
     override val teamfood: Boolean = false
 ) : ResourceFlag<Boolean>
@@ -124,7 +143,7 @@
  *
  * Prefer [UnreleasedFlag] and [ReleasedFlag].
  */
-data class DeviceConfigBooleanFlag @JvmOverloads constructor(
+data class DeviceConfigBooleanFlag constructor(
     override val id: Int,
     override val name: String,
     override val namespace: String,
@@ -139,17 +158,20 @@
  *
  * Prefer [UnreleasedFlag] and [ReleasedFlag].
  */
-data class SysPropBooleanFlag @JvmOverloads constructor(
+data class SysPropBooleanFlag constructor(
     override val id: Int,
     override val name: String,
-    override val default: Boolean = false
+    override val namespace: String,
+    override val default: Boolean = false,
 ) : SysPropFlag<Boolean> {
     // TODO(b/223379190): Teamfood not supported for sysprop flags yet.
     override val teamfood: Boolean = false
 }
 
-data class StringFlag @JvmOverloads constructor(
+data class StringFlag constructor(
     override val id: Int,
+    override val name: String,
+    override val namespace: String,
     override val default: String = "",
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
@@ -164,23 +186,31 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
+        name = parcel.readString(),
+        namespace = parcel.readString(),
         default = parcel.readString() ?: ""
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
         parcel.writeInt(id)
+        parcel.writeString(name)
+        parcel.writeString(namespace)
         parcel.writeString(default)
     }
 }
 
-data class ResourceStringFlag @JvmOverloads constructor(
+data class ResourceStringFlag constructor(
     override val id: Int,
+    override val name: String,
+    override val namespace: String,
     @StringRes override val resourceId: Int,
     override val teamfood: Boolean = false
 ) : ResourceFlag<String>
 
-data class IntFlag @JvmOverloads constructor(
+data class IntFlag constructor(
     override val id: Int,
+    override val name: String,
+    override val namespace: String,
     override val default: Int = 0,
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
@@ -196,25 +226,33 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
+        name = parcel.readString(),
+        namespace = parcel.readString(),
         default = parcel.readInt()
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
         parcel.writeInt(id)
+        parcel.writeString(name)
+        parcel.writeString(namespace)
         parcel.writeInt(default)
     }
 }
 
-data class ResourceIntFlag @JvmOverloads constructor(
+data class ResourceIntFlag constructor(
     override val id: Int,
+    override val name: String,
+    override val namespace: String,
     @IntegerRes override val resourceId: Int,
     override val teamfood: Boolean = false
 ) : ResourceFlag<Int>
 
-data class LongFlag @JvmOverloads constructor(
+data class LongFlag constructor(
     override val id: Int,
     override val default: Long = 0,
     override val teamfood: Boolean = false,
+    override val name: String,
+    override val namespace: String,
     override val overridden: Boolean = false
 ) : ParcelableFlag<Long> {
 
@@ -228,17 +266,23 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
+        name = parcel.readString(),
+        namespace = parcel.readString(),
         default = parcel.readLong()
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
         parcel.writeInt(id)
+        parcel.writeString(name)
+        parcel.writeString(namespace)
         parcel.writeLong(default)
     }
 }
 
-data class FloatFlag @JvmOverloads constructor(
+data class FloatFlag constructor(
     override val id: Int,
+    override val name: String,
+    override val namespace: String,
     override val default: Float = 0f,
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
@@ -254,23 +298,31 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
+        name = parcel.readString(),
+        namespace = parcel.readString(),
         default = parcel.readFloat()
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
         parcel.writeInt(id)
+        parcel.writeString(name)
+        parcel.writeString(namespace)
         parcel.writeFloat(default)
     }
 }
 
-data class ResourceFloatFlag @JvmOverloads constructor(
+data class ResourceFloatFlag constructor(
     override val id: Int,
+    override val name: String,
+    override val namespace: String,
     override val resourceId: Int,
-    override val teamfood: Boolean = false
+    override val teamfood: Boolean = false,
 ) : ResourceFlag<Int>
 
-data class DoubleFlag @JvmOverloads constructor(
+data class DoubleFlag constructor(
     override val id: Int,
+    override val name: String,
+    override val namespace: String,
     override val default: Double = 0.0,
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
@@ -286,11 +338,15 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
+        name = parcel.readString(),
+        namespace = parcel.readString(),
         default = parcel.readDouble()
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
         parcel.writeInt(id)
+        parcel.writeString(name)
+        parcel.writeString(namespace)
         parcel.writeDouble(default)
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index d172690..d85292a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -38,6 +38,7 @@
         const val ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG"
         const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS"
         const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"
+        const val ACTION_SYSUI_STARTED = "com.android.systemui.STARTED"
         const val EXTRA_ID = "id"
         const val EXTRA_VALUE = "value"
         const val EXTRA_FLAGS = "flags"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
index e9ea19d..eeb6031 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
@@ -24,6 +24,7 @@
 private const val FIELD_TYPE = "type"
 private const val TYPE_BOOLEAN = "boolean"
 private const val TYPE_STRING = "string"
+private const val TYPE_INT = "int"
 
 private const val TAG = "FlagSerializer"
 
@@ -77,4 +78,10 @@
     JSONObject::getString
 )
 
+object IntFlagSerializer : FlagSerializer<Int>(
+    TYPE_INT,
+    JSONObject::put,
+    JSONObject::getInt
+)
+
 class InvalidFlagStorageException : Exception("Data found but is invalid")
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/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
deleted file mode 100644
index 236aa66..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ /dev/null
@@ -1,646 +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.clocks
-
-import android.animation.TimeInterpolator
-import android.annotation.ColorInt
-import android.annotation.FloatRange
-import android.annotation.IntRange
-import android.annotation.SuppressLint
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Rect
-import android.text.Layout
-import android.text.TextUtils
-import android.text.format.DateFormat
-import android.util.AttributeSet
-import android.util.MathUtils
-import android.widget.TextView
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.animation.GlyphCallback
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.animation.TextAnimator
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.shared.R
-import java.io.PrintWriter
-import java.util.Calendar
-import java.util.Locale
-import java.util.TimeZone
-import kotlin.math.max
-import kotlin.math.min
-
-/**
- * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
- * The time's text color is a gradient that changes its colors based on its controller.
- */
-@SuppressLint("AppCompatCustomView")
-class AnimatableClockView @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0,
-    defStyleRes: Int = 0
-) : TextView(context, attrs, defStyleAttr, defStyleRes) {
-    var tag: String = "UnnamedClockView"
-    var logBuffer: LogBuffer? = null
-
-    private val time = Calendar.getInstance()
-
-    private val dozingWeightInternal: Int
-    private val lockScreenWeightInternal: Int
-    private val isSingleLineInternal: Boolean
-
-    private var format: CharSequence? = null
-    private var descFormat: CharSequence? = null
-
-    @ColorInt
-    private var dozingColor = 0
-
-    @ColorInt
-    private var lockScreenColor = 0
-
-    private var lineSpacingScale = 1f
-    private val chargeAnimationDelay: Int
-    private var textAnimator: TextAnimator? = null
-    private var onTextAnimatorInitialized: Runnable? = null
-
-    @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator =
-        { layout, invalidateCb -> TextAnimator(layout, invalidateCb) }
-    @VisibleForTesting var isAnimationEnabled: Boolean = true
-    @VisibleForTesting var timeOverrideInMillis: Long? = null
-
-    val dozingWeight: Int
-        get() = if (useBoldedVersion()) dozingWeightInternal + 100 else dozingWeightInternal
-
-    val lockScreenWeight: Int
-        get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
-
-    /**
-     * The number of pixels below the baseline. For fonts that support languages such as
-     * Burmese, this space can be significant and should be accounted for when computing layout.
-     */
-    val bottom get() = paint?.fontMetrics?.bottom ?: 0f
-
-    init {
-        val animatableClockViewAttributes = context.obtainStyledAttributes(
-            attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes
-        )
-
-        try {
-            dozingWeightInternal = animatableClockViewAttributes.getInt(
-                R.styleable.AnimatableClockView_dozeWeight,
-                100
-            )
-            lockScreenWeightInternal = animatableClockViewAttributes.getInt(
-                R.styleable.AnimatableClockView_lockScreenWeight,
-                300
-            )
-            chargeAnimationDelay = animatableClockViewAttributes.getInt(
-                R.styleable.AnimatableClockView_chargeAnimationDelay, 200
-            )
-        } finally {
-            animatableClockViewAttributes.recycle()
-        }
-
-        val textViewAttributes = context.obtainStyledAttributes(
-            attrs, android.R.styleable.TextView,
-            defStyleAttr, defStyleRes
-        )
-
-        isSingleLineInternal =
-            try {
-                textViewAttributes.getBoolean(android.R.styleable.TextView_singleLine, false)
-            } finally {
-                textViewAttributes.recycle()
-            }
-
-        refreshFormat()
-    }
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        logBuffer?.log(tag, DEBUG, "onAttachedToWindow")
-        refreshFormat()
-    }
-
-    /**
-     * Whether to use a bolded version based on the user specified fontWeightAdjustment.
-     */
-    fun useBoldedVersion(): Boolean {
-        // "Bold text" fontWeightAdjustment is 300.
-        return resources.configuration.fontWeightAdjustment > 100
-    }
-
-    fun refreshTime() {
-        time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
-        contentDescription = DateFormat.format(descFormat, time)
-        val formattedText = DateFormat.format(format, time)
-        logBuffer?.log(tag, DEBUG,
-                { str1 = formattedText?.toString() },
-                { "refreshTime: new formattedText=$str1" }
-        )
-        // Setting text actually triggers a layout pass (because the text view is set to
-        // wrap_content width and TextView always relayouts for this). Avoid needless
-        // relayout if the text didn't actually change.
-        if (!TextUtils.equals(text, formattedText)) {
-            text = formattedText
-            logBuffer?.log(tag, DEBUG,
-                    { str1 = formattedText?.toString() },
-                    { "refreshTime: done setting new time text to: $str1" }
-            )
-            // Because the TextLayout may mutate under the hood as a result of the new text, we
-            // notify the TextAnimator that it may have changed and request a measure/layout. A
-            // crash will occur on the next invocation of setTextStyle if the layout is mutated
-            // without being notified TextInterpolator being notified.
-            if (layout != null) {
-                textAnimator?.updateLayout(layout)
-                logBuffer?.log(tag, DEBUG, "refreshTime: done updating textAnimator layout")
-            }
-            requestLayout()
-            logBuffer?.log(tag, DEBUG, "refreshTime: after requestLayout")
-        }
-    }
-
-    fun onTimeZoneChanged(timeZone: TimeZone?) {
-        time.timeZone = timeZone
-        refreshFormat()
-        logBuffer?.log(tag, DEBUG,
-                { str1 = timeZone?.toString() },
-                { "onTimeZoneChanged newTimeZone=$str1" }
-        )
-    }
-
-    @SuppressLint("DrawAllocation")
-    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
-        val animator = textAnimator
-        if (animator == null) {
-            textAnimator = textAnimatorFactory(layout, ::invalidate)
-            onTextAnimatorInitialized?.run()
-            onTextAnimatorInitialized = null
-        } else {
-            animator.updateLayout(layout)
-        }
-        logBuffer?.log(tag, DEBUG, "onMeasure")
-    }
-
-    override fun onDraw(canvas: Canvas) {
-        // Use textAnimator to render text if animation is enabled.
-        // Otherwise default to using standard draw functions.
-        if (isAnimationEnabled) {
-            // intentionally doesn't call super.onDraw here or else the text will be rendered twice
-            textAnimator?.draw(canvas)
-        } else {
-            super.onDraw(canvas)
-        }
-        logBuffer?.log(tag, DEBUG, "onDraw lastDraw")
-    }
-
-    override fun invalidate() {
-        super.invalidate()
-        logBuffer?.log(tag, DEBUG, "invalidate")
-    }
-
-    override fun onTextChanged(
-            text: CharSequence,
-            start: Int,
-            lengthBefore: Int,
-            lengthAfter: Int
-    ) {
-        super.onTextChanged(text, start, lengthBefore, lengthAfter)
-        logBuffer?.log(tag, DEBUG,
-                { str1 = text.toString() },
-                { "onTextChanged text=$str1" }
-        )
-    }
-
-    fun setLineSpacingScale(scale: Float) {
-        lineSpacingScale = scale
-        setLineSpacing(0f, lineSpacingScale)
-    }
-
-    fun setColors(@ColorInt dozingColor: Int, lockScreenColor: Int) {
-        this.dozingColor = dozingColor
-        this.lockScreenColor = lockScreenColor
-    }
-
-    fun animateAppearOnLockscreen() {
-        logBuffer?.log(tag, DEBUG, "animateAppearOnLockscreen")
-        setTextStyle(
-            weight = dozingWeight,
-            textSize = -1f,
-            color = lockScreenColor,
-            animate = false,
-            duration = 0,
-            delay = 0,
-            onAnimationEnd = null
-        )
-        setTextStyle(
-            weight = lockScreenWeight,
-            textSize = -1f,
-            color = lockScreenColor,
-            animate = isAnimationEnabled,
-            duration = APPEAR_ANIM_DURATION,
-            delay = 0,
-            onAnimationEnd = null
-        )
-    }
-
-    fun animateFoldAppear(animate: Boolean = true) {
-        if (isAnimationEnabled && textAnimator == null) {
-            return
-        }
-        logBuffer?.log(tag, DEBUG, "animateFoldAppear")
-        setTextStyle(
-            weight = lockScreenWeightInternal,
-            textSize = -1f,
-            color = lockScreenColor,
-            animate = false,
-            duration = 0,
-            delay = 0,
-            onAnimationEnd = null
-        )
-        setTextStyle(
-            weight = dozingWeightInternal,
-            textSize = -1f,
-            color = dozingColor,
-            animate = animate && isAnimationEnabled,
-            interpolator = Interpolators.EMPHASIZED_DECELERATE,
-            duration = ANIMATION_DURATION_FOLD_TO_AOD.toLong(),
-            delay = 0,
-            onAnimationEnd = null
-        )
-    }
-
-    fun animateCharge(isDozing: () -> Boolean) {
-        if (textAnimator == null || textAnimator!!.isRunning()) {
-            // Skip charge animation if dozing animation is already playing.
-            return
-        }
-        logBuffer?.log(tag, DEBUG, "animateCharge")
-        val startAnimPhase2 = Runnable {
-            setTextStyle(
-                weight = if (isDozing()) dozingWeight else lockScreenWeight,
-                textSize = -1f,
-                color = null,
-                animate = isAnimationEnabled,
-                duration = CHARGE_ANIM_DURATION_PHASE_1,
-                delay = 0,
-                onAnimationEnd = null
-            )
-        }
-        setTextStyle(
-            weight = if (isDozing()) lockScreenWeight else dozingWeight,
-            textSize = -1f,
-            color = null,
-            animate = isAnimationEnabled,
-            duration = CHARGE_ANIM_DURATION_PHASE_0,
-            delay = chargeAnimationDelay.toLong(),
-            onAnimationEnd = startAnimPhase2
-        )
-    }
-
-    fun animateDoze(isDozing: Boolean, animate: Boolean) {
-        logBuffer?.log(tag, DEBUG, "animateDoze")
-        setTextStyle(
-            weight = if (isDozing) dozingWeight else lockScreenWeight,
-            textSize = -1f,
-            color = if (isDozing) dozingColor else lockScreenColor,
-            animate = animate && isAnimationEnabled,
-            duration = DOZE_ANIM_DURATION,
-            delay = 0,
-            onAnimationEnd = null
-        )
-    }
-
-    // The offset of each glyph from where it should be.
-    private var glyphOffsets = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f)
-
-    private var lastSeenAnimationProgress = 1.0f
-
-    // If the animation is being reversed, the target offset for each glyph for the "stop".
-    private var animationCancelStartPosition = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f)
-    private var animationCancelStopPosition = 0.0f
-
-    // Whether the currently playing animation needed a stop (and thus, is shortened).
-    private var currentAnimationNeededStop = false
-
-    private val glyphFilter: GlyphCallback = { positionedGlyph, _ ->
-        val offset = positionedGlyph.lineNo * DIGITS_PER_LINE + positionedGlyph.glyphIndex
-        if (offset < glyphOffsets.size) {
-            positionedGlyph.x += glyphOffsets[offset]
-        }
-    }
-
-    /**
-     * Set text style with an optional animation.
-     *
-     * By passing -1 to weight, the view preserves its current weight.
-     * By passing -1 to textSize, the view preserves its current text size.
-     *
-     * @param weight text weight.
-     * @param textSize font size.
-     * @param animate true to animate the text style change, otherwise false.
-     */
-    private fun setTextStyle(
-        @IntRange(from = 0, to = 1000) weight: Int,
-        @FloatRange(from = 0.0) textSize: Float,
-        color: Int?,
-        animate: Boolean,
-        interpolator: TimeInterpolator?,
-        duration: Long,
-        delay: Long,
-        onAnimationEnd: Runnable?
-    ) {
-        if (textAnimator != null) {
-            textAnimator?.setTextStyle(
-                weight = weight,
-                textSize = textSize,
-                color = color,
-                animate = animate && isAnimationEnabled,
-                duration = duration,
-                interpolator = interpolator,
-                delay = delay,
-                onAnimationEnd = onAnimationEnd
-            )
-            textAnimator?.glyphFilter = glyphFilter
-            if (color != null && !isAnimationEnabled) {
-                setTextColor(color)
-            }
-        } else {
-            // when the text animator is set, update its start values
-            onTextAnimatorInitialized = Runnable {
-                textAnimator?.setTextStyle(
-                    weight = weight,
-                    textSize = textSize,
-                    color = color,
-                    animate = false,
-                    duration = duration,
-                    interpolator = interpolator,
-                    delay = delay,
-                    onAnimationEnd = onAnimationEnd
-                )
-                textAnimator?.glyphFilter = glyphFilter
-                if (color != null && !isAnimationEnabled) {
-                    setTextColor(color)
-                }
-            }
-        }
-    }
-
-    private fun setTextStyle(
-        @IntRange(from = 0, to = 1000) weight: Int,
-        @FloatRange(from = 0.0) textSize: Float,
-        color: Int?,
-        animate: Boolean,
-        duration: Long,
-        delay: Long,
-        onAnimationEnd: Runnable?
-    ) {
-        setTextStyle(
-            weight = weight,
-            textSize = textSize,
-            color = color,
-            animate = animate && isAnimationEnabled,
-            interpolator = null,
-            duration = duration,
-            delay = delay,
-            onAnimationEnd = onAnimationEnd
-        )
-    }
-
-    fun refreshFormat() = refreshFormat(DateFormat.is24HourFormat(context))
-    fun refreshFormat(use24HourFormat: Boolean) {
-        Patterns.update(context)
-
-        format = when {
-            isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
-            !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR
-            isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
-            else -> DOUBLE_LINE_FORMAT_12_HOUR
-        }
-        logBuffer?.log(tag, DEBUG,
-                { str1 = format?.toString() },
-                { "refreshFormat format=$str1" }
-        )
-
-        descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
-        refreshTime()
-    }
-
-    fun dump(pw: PrintWriter) {
-        pw.println("$this")
-        pw.println("    measuredWidth=$measuredWidth")
-        pw.println("    measuredHeight=$measuredHeight")
-        pw.println("    singleLineInternal=$isSingleLineInternal")
-        pw.println("    currText=$text")
-        pw.println("    currTimeContextDesc=$contentDescription")
-        pw.println("    dozingWeightInternal=$dozingWeightInternal")
-        pw.println("    lockScreenWeightInternal=$lockScreenWeightInternal")
-        pw.println("    dozingColor=$dozingColor")
-        pw.println("    lockScreenColor=$lockScreenColor")
-        pw.println("    time=$time")
-    }
-
-    fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) {
-        // Do we need to cancel an in-flight animation?
-        // Need to also check against 0.0f here; we can sometimes get two calls with fraction == 0,
-        // which trips up the check otherwise.
-        if (lastSeenAnimationProgress != 1.0f &&
-                lastSeenAnimationProgress != 0.0f &&
-                fraction == 0.0f) {
-            // New animation, but need to stop the old one. Figure out where each glyph currently
-            // is in relation to the box position. After that, use the leading digit's current
-            // position as the stop target.
-            currentAnimationNeededStop = true
-
-            // We assume that the current glyph offsets would be relative to the "from" position.
-            val moveAmount = toRect.left - fromRect.left
-
-            // Remap the current glyph offsets to be relative to the new "end" position, and figure
-            // out the start/end positions for the stop animation.
-            for (i in 0 until NUM_DIGITS) {
-                glyphOffsets[i] = -moveAmount + glyphOffsets[i]
-                animationCancelStartPosition[i] = glyphOffsets[i]
-            }
-
-            // Use the leading digit's offset as the stop position.
-            if (toRect.left > fromRect.left) {
-                // It _was_ moving left
-                animationCancelStopPosition = glyphOffsets[0]
-            } else {
-                // It was moving right
-                animationCancelStopPosition = glyphOffsets[1]
-            }
-        }
-
-        // Is there a cancellation in progress?
-        if (currentAnimationNeededStop && fraction < ANIMATION_CANCELLATION_TIME) {
-            val animationStopProgress = MathUtils.constrainedMap(
-                    0.0f, 1.0f, 0.0f, ANIMATION_CANCELLATION_TIME, fraction
-            )
-
-            // One of the digits has already stopped.
-            val animationStopStep = 1.0f / (NUM_DIGITS - 1)
-
-            for (i in 0 until NUM_DIGITS) {
-                val stopAmount = if (toRect.left > fromRect.left) {
-                    // It was moving left (before flipping)
-                    MOVE_LEFT_DELAYS[i] * animationStopStep
-                } else {
-                    // It was moving right (before flipping)
-                    MOVE_RIGHT_DELAYS[i] * animationStopStep
-                }
-
-                // Leading digit stops immediately.
-                if (stopAmount == 0.0f) {
-                    glyphOffsets[i] = animationCancelStopPosition
-                } else {
-                    val actualStopAmount = MathUtils.constrainedMap(
-                            0.0f, 1.0f, 0.0f, stopAmount, animationStopProgress
-                    )
-                    val easedProgress = MOVE_INTERPOLATOR.getInterpolation(actualStopAmount)
-                    val glyphMoveAmount =
-                            animationCancelStopPosition - animationCancelStartPosition[i]
-                    glyphOffsets[i] =
-                            animationCancelStartPosition[i] + glyphMoveAmount * easedProgress
-                }
-            }
-        } else {
-            // Normal part of the animation.
-            // Do we need to remap the animation progress to take account of the cancellation?
-            val actualFraction = if (currentAnimationNeededStop) {
-                MathUtils.constrainedMap(
-                        0.0f, 1.0f, ANIMATION_CANCELLATION_TIME, 1.0f, fraction
-                )
-            } else {
-                fraction
-            }
-
-            val digitFractions = (0 until NUM_DIGITS).map {
-                // The delay for each digit, in terms of fraction (i.e. the digit should not move
-                // during 0.0 - 0.1).
-                val initialDelay = if (toRect.left > fromRect.left) {
-                    MOVE_RIGHT_DELAYS[it] * MOVE_DIGIT_STEP
-                } else {
-                    MOVE_LEFT_DELAYS[it] * MOVE_DIGIT_STEP
-                }
-
-                val f = MathUtils.constrainedMap(
-                        0.0f, 1.0f,
-                        initialDelay, initialDelay + AVAILABLE_ANIMATION_TIME,
-                        actualFraction
-                )
-                MOVE_INTERPOLATOR.getInterpolation(max(min(f, 1.0f), 0.0f))
-            }
-
-            // Was there an animation halt?
-            val moveAmount = if (currentAnimationNeededStop) {
-                // Only need to animate over the remaining space if the animation was aborted.
-                -animationCancelStopPosition
-            } else {
-                toRect.left.toFloat() - fromRect.left.toFloat()
-            }
-
-            for (i in 0 until NUM_DIGITS) {
-                glyphOffsets[i] = -moveAmount + (moveAmount * digitFractions[i])
-            }
-        }
-
-        invalidate()
-
-        if (fraction == 1.0f) {
-            // Reset
-            currentAnimationNeededStop = false
-        }
-
-        lastSeenAnimationProgress = fraction
-
-        // Ensure that the actual clock container is always in the "end" position.
-        this.setLeftTopRightBottom(toRect.left, toRect.top, toRect.right, toRect.bottom)
-    }
-
-    // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
-    // This is an optimization to ensure we only recompute the patterns when the inputs change.
-    private object Patterns {
-        var sClockView12: String? = null
-        var sClockView24: String? = null
-        var sCacheKey: String? = null
-
-        fun update(context: Context) {
-            val locale = Locale.getDefault()
-            val res = context.resources
-            val clockView12Skel = res.getString(R.string.clock_12hr_format)
-            val clockView24Skel = res.getString(R.string.clock_24hr_format)
-            val key = locale.toString() + clockView12Skel + clockView24Skel
-            if (key == sCacheKey) return
-
-            val clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel)
-            sClockView12 = clockView12
-
-            // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
-            // format.  The following code removes the AM/PM indicator if we didn't want it.
-            if (!clockView12Skel.contains("a")) {
-                sClockView12 = clockView12.replace("a".toRegex(), "").trim { it <= ' ' }
-            }
-
-            sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel)
-            sCacheKey = key
-        }
-    }
-
-    companion object {
-        private val TAG = AnimatableClockView::class.simpleName
-        const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600
-        private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
-        private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
-        private const val DOZE_ANIM_DURATION: Long = 300
-        private const val APPEAR_ANIM_DURATION: Long = 350
-        private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
-        private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
-
-        // Constants for the animation
-        private val MOVE_INTERPOLATOR = Interpolators.STANDARD
-
-        // Calculate the positions of all of the digits...
-        // Offset each digit by, say, 0.1
-        // This means that each digit needs to move over a slice of "fractions", i.e. digit 0 should
-        // move from 0.0 - 0.7, digit 1 from 0.1 - 0.8, digit 2 from 0.2 - 0.9, and digit 3
-        // from 0.3 - 1.0.
-        private const val NUM_DIGITS = 4
-        private const val DIGITS_PER_LINE = 2
-
-        // How much of "fraction" to spend on canceling the animation, if needed
-        private const val ANIMATION_CANCELLATION_TIME = 0.4f
-
-        // Delays. Each digit's animation should have a slight delay, so we get a nice
-        // "stepping" effect. When moving right, the second digit of the hour should move first.
-        // When moving left, the first digit of the hour should move first. The lists encode
-        // the delay for each digit (hour[0], hour[1], minute[0], minute[1]), to be multiplied
-        // by delayMultiplier.
-        private val MOVE_LEFT_DELAYS = listOf(0, 1, 2, 3)
-        private val MOVE_RIGHT_DELAYS = listOf(1, 0, 3, 2)
-
-        // How much delay to apply to each subsequent digit. This is measured in terms of "fraction"
-        // (i.e. a value of 0.1 would cause a digit to wait until fraction had hit 0.1, or 0.2 etc
-        // before moving).
-        private const val MOVE_DIGIT_STEP = 0.1f
-
-        // Total available transition time for each digit, taking into account the step. If step is
-        // 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
-        private val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1)
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
deleted file mode 100644
index 48821e8..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ /dev/null
@@ -1,205 +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.shared.clocks
-
-import android.content.Context
-import android.database.ContentObserver
-import android.graphics.drawable.Drawable
-import android.net.Uri
-import android.os.Handler
-import android.provider.Settings
-import android.util.Log
-import com.android.internal.annotations.Keep
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.ClockId
-import com.android.systemui.plugins.ClockMetadata
-import com.android.systemui.plugins.ClockProvider
-import com.android.systemui.plugins.ClockProviderPlugin
-import com.android.systemui.plugins.PluginListener
-import com.android.systemui.shared.plugins.PluginManager
-import com.google.gson.Gson
-
-private val TAG = ClockRegistry::class.simpleName
-private const val DEBUG = true
-
-/** ClockRegistry aggregates providers and plugins */
-open class ClockRegistry(
-    val context: Context,
-    val pluginManager: PluginManager,
-    val handler: Handler,
-    val isEnabled: Boolean,
-    userHandle: Int,
-    defaultClockProvider: ClockProvider,
-) {
-    // Usually this would be a typealias, but a SAM provides better java interop
-    fun interface ClockChangeListener {
-        fun onClockChanged()
-    }
-
-    private val gson = Gson()
-    private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
-    private val clockChangeListeners = mutableListOf<ClockChangeListener>()
-    private val settingObserver = object : ContentObserver(handler) {
-        override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
-            clockChangeListeners.forEach { it.onClockChanged() }
-    }
-
-    private val pluginListener = object : PluginListener<ClockProviderPlugin> {
-        override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) =
-            connectClocks(plugin)
-
-        override fun onPluginDisconnected(plugin: ClockProviderPlugin) =
-            disconnectClocks(plugin)
-    }
-
-    open var currentClockId: ClockId
-        get() {
-            return try {
-                val json = Settings.Secure.getString(
-                    context.contentResolver,
-                    Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
-                )
-                gson.fromJson(json, ClockSetting::class.java)?.clockId ?: DEFAULT_CLOCK_ID
-            } catch (ex: Exception) {
-                Log.e(TAG, "Failed to parse clock setting", ex)
-                DEFAULT_CLOCK_ID
-            }
-        }
-        set(value) {
-            try {
-                val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
-                Settings.Secure.putString(
-                    context.contentResolver,
-                    Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
-                )
-            } catch (ex: Exception) {
-                Log.e(TAG, "Failed to set clock setting", ex)
-            }
-        }
-
-    init {
-        connectClocks(defaultClockProvider)
-        if (!availableClocks.containsKey(DEFAULT_CLOCK_ID)) {
-            throw IllegalArgumentException(
-                "$defaultClockProvider did not register clock at $DEFAULT_CLOCK_ID"
-            )
-        }
-
-        if (isEnabled) {
-            pluginManager.addPluginListener(
-                pluginListener,
-                ClockProviderPlugin::class.java,
-                /*allowMultiple=*/ true
-            )
-            context.contentResolver.registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
-                /*notifyForDescendants=*/ false,
-                settingObserver,
-                userHandle
-            )
-        }
-    }
-
-    private fun connectClocks(provider: ClockProvider) {
-        val currentId = currentClockId
-        for (clock in provider.getClocks()) {
-            val id = clock.clockId
-            val current = availableClocks[id]
-            if (current != null) {
-                Log.e(
-                    TAG,
-                    "Clock Id conflict: $id is registered by both " +
-                        "${provider::class.simpleName} and ${current.provider::class.simpleName}"
-                )
-                return
-            }
-
-            availableClocks[id] = ClockInfo(clock, provider)
-            if (DEBUG) {
-                Log.i(TAG, "Added ${clock.clockId}")
-            }
-
-            if (currentId == id) {
-                if (DEBUG) {
-                    Log.i(TAG, "Current clock ($currentId) was connected")
-                }
-                clockChangeListeners.forEach { it.onClockChanged() }
-            }
-        }
-    }
-
-    private fun disconnectClocks(provider: ClockProvider) {
-        val currentId = currentClockId
-        for (clock in provider.getClocks()) {
-            availableClocks.remove(clock.clockId)
-            if (DEBUG) {
-                Log.i(TAG, "Removed ${clock.clockId}")
-            }
-
-            if (currentId == clock.clockId) {
-                Log.w(TAG, "Current clock ($currentId) was disconnected")
-                clockChangeListeners.forEach { it.onClockChanged() }
-            }
-        }
-    }
-
-    fun getClocks(): List<ClockMetadata> {
-        if (!isEnabled) {
-            return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
-        }
-        return availableClocks.map { (_, clock) -> clock.metadata }
-    }
-
-    fun getClockThumbnail(clockId: ClockId): Drawable? =
-        availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
-
-    fun createExampleClock(clockId: ClockId): ClockController? = createClock(clockId)
-
-    fun registerClockChangeListener(listener: ClockChangeListener) =
-        clockChangeListeners.add(listener)
-
-    fun unregisterClockChangeListener(listener: ClockChangeListener) =
-        clockChangeListeners.remove(listener)
-
-    fun createCurrentClock(): ClockController {
-        val clockId = currentClockId
-        if (isEnabled && clockId.isNotEmpty()) {
-            val clock = createClock(clockId)
-            if (clock != null) {
-                if (DEBUG) {
-                    Log.i(TAG, "Rendering clock $clockId")
-                }
-                return clock
-            } else {
-                Log.e(TAG, "Clock $clockId not found; using default")
-            }
-        }
-
-        return createClock(DEFAULT_CLOCK_ID)!!
-    }
-
-    private fun createClock(clockId: ClockId): ClockController? =
-        availableClocks[clockId]?.provider?.createClock(clockId)
-
-    private data class ClockInfo(
-        val metadata: ClockMetadata,
-        val provider: ClockProvider
-    )
-
-    @Keep
-    private data class ClockSetting(
-        val clockId: ClockId,
-        val _applied_timestamp: Long?
-    )
-}
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
deleted file mode 100644
index da1d233..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ /dev/null
@@ -1,261 +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.shared.clocks
-
-import android.content.Context
-import android.content.res.Resources
-import android.graphics.Color
-import android.graphics.Rect
-import android.icu.text.NumberFormat
-import android.util.TypedValue
-import android.view.LayoutInflater
-import android.widget.FrameLayout
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.plugins.ClockAnimations
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.ClockEvents
-import com.android.systemui.plugins.ClockFaceController
-import com.android.systemui.plugins.ClockFaceEvents
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.shared.R
-import java.io.PrintWriter
-import java.util.Locale
-import java.util.TimeZone
-
-private val TAG = DefaultClockController::class.simpleName
-
-/**
- * Controls the default clock visuals.
- *
- * This serves as an adapter between the clock interface and the AnimatableClockView used by the
- * existing lockscreen clock.
- */
-class DefaultClockController(
-    ctx: Context,
-    private val layoutInflater: LayoutInflater,
-    private val resources: Resources,
-) : ClockController {
-    override val smallClock: DefaultClockFaceController
-    override val largeClock: LargeClockFaceController
-    private val clocks: List<AnimatableClockView>
-
-    private val burmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"))
-    private val burmeseNumerals = burmeseNf.format(FORMAT_NUMBER.toLong())
-    private val burmeseLineSpacing =
-        resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
-    private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
-
-    override val events: DefaultClockEvents
-    override lateinit var animations: DefaultClockAnimations
-        private set
-
-    init {
-        val parent = FrameLayout(ctx)
-        smallClock =
-            DefaultClockFaceController(
-                layoutInflater.inflate(R.layout.clock_default_small, parent, false)
-                    as AnimatableClockView
-            )
-        largeClock =
-            LargeClockFaceController(
-                layoutInflater.inflate(R.layout.clock_default_large, parent, false)
-                    as AnimatableClockView
-            )
-        clocks = listOf(smallClock.view, largeClock.view)
-
-        events = DefaultClockEvents()
-        animations = DefaultClockAnimations(0f, 0f)
-        events.onLocaleChanged(Locale.getDefault())
-    }
-
-    override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
-        largeClock.recomputePadding()
-        animations = DefaultClockAnimations(dozeFraction, foldFraction)
-        events.onColorPaletteChanged(resources)
-        events.onTimeZoneChanged(TimeZone.getDefault())
-        events.onTimeTick()
-    }
-
-    override fun setLogBuffer(logBuffer: LogBuffer) {
-        smallClock.view.tag = "smallClockView"
-        largeClock.view.tag = "largeClockView"
-        smallClock.view.logBuffer = logBuffer
-        largeClock.view.logBuffer = logBuffer
-    }
-
-    open inner class DefaultClockFaceController(
-        override val view: AnimatableClockView,
-    ) : ClockFaceController {
-
-        // MAGENTA is a placeholder, and will be assigned correctly in initialize
-        private var currentColor = Color.MAGENTA
-        private var isRegionDark = false
-
-        init {
-            view.setColors(currentColor, currentColor)
-        }
-
-        override val events =
-            object : ClockFaceEvents {
-                override fun onRegionDarknessChanged(isRegionDark: Boolean) {
-                    this@DefaultClockFaceController.isRegionDark = isRegionDark
-                    updateColor()
-                }
-            }
-
-        fun updateColor() {
-            val color =
-                if (isRegionDark) {
-                    resources.getColor(android.R.color.system_accent1_100)
-                } else {
-                    resources.getColor(android.R.color.system_accent2_600)
-                }
-
-            if (currentColor == color) {
-                return
-            }
-
-            currentColor = color
-            view.setColors(DOZE_COLOR, color)
-            view.animateAppearOnLockscreen()
-        }
-    }
-
-    inner class LargeClockFaceController(
-        view: AnimatableClockView,
-    ) : DefaultClockFaceController(view) {
-        fun recomputePadding() {
-            val lp = view.getLayoutParams() as FrameLayout.LayoutParams
-            lp.topMargin = (-0.5f * view.bottom).toInt()
-            view.setLayoutParams(lp)
-        }
-
-        fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) {
-            view.moveForSplitShade(fromRect, toRect, fraction)
-        }
-    }
-
-    inner class DefaultClockEvents : ClockEvents {
-        override fun onTimeTick() = clocks.forEach { it.refreshTime() }
-
-        override fun onTimeFormatChanged(is24Hr: Boolean) =
-            clocks.forEach { it.refreshFormat(is24Hr) }
-
-        override fun onTimeZoneChanged(timeZone: TimeZone) =
-            clocks.forEach { it.onTimeZoneChanged(timeZone) }
-
-        override fun onFontSettingChanged() {
-            smallClock.view.setTextSize(
-                TypedValue.COMPLEX_UNIT_PX,
-                resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
-            )
-            largeClock.view.setTextSize(
-                TypedValue.COMPLEX_UNIT_PX,
-                resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
-            )
-            largeClock.recomputePadding()
-        }
-
-        override fun onColorPaletteChanged(resources: Resources) {
-            largeClock.updateColor()
-            smallClock.updateColor()
-        }
-
-        override fun onLocaleChanged(locale: Locale) {
-            val nf = NumberFormat.getInstance(locale)
-            if (nf.format(FORMAT_NUMBER.toLong()) == burmeseNumerals) {
-                clocks.forEach { it.setLineSpacingScale(burmeseLineSpacing) }
-            } else {
-                clocks.forEach { it.setLineSpacingScale(defaultLineSpacing) }
-            }
-
-            clocks.forEach { it.refreshFormat() }
-        }
-    }
-
-    inner class DefaultClockAnimations(
-        dozeFraction: Float,
-        foldFraction: Float,
-    ) : ClockAnimations {
-        private var foldState = AnimationState(0f)
-        private var dozeState = AnimationState(0f)
-
-        init {
-            dozeState = AnimationState(dozeFraction)
-            foldState = AnimationState(foldFraction)
-
-            if (foldState.isActive) {
-                clocks.forEach { it.animateFoldAppear(false) }
-            } else {
-                clocks.forEach { it.animateDoze(dozeState.isActive, false) }
-            }
-        }
-
-        override fun enter() {
-            if (!dozeState.isActive) {
-                clocks.forEach { it.animateAppearOnLockscreen() }
-            }
-        }
-
-        override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } }
-
-        override fun fold(fraction: Float) {
-            val (hasChanged, hasJumped) = foldState.update(fraction)
-            if (hasChanged) {
-                clocks.forEach { it.animateFoldAppear(!hasJumped) }
-            }
-        }
-
-        override fun doze(fraction: Float) {
-            val (hasChanged, hasJumped) = dozeState.update(fraction)
-            if (hasChanged) {
-                clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }
-            }
-        }
-
-        override fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {
-            largeClock.moveForSplitShade(fromRect, toRect, fraction)
-        }
-
-        override val hasCustomPositionUpdatedAnimation: Boolean
-            get() = true
-    }
-
-    private class AnimationState(
-        var fraction: Float,
-    ) {
-        var isActive: Boolean = fraction < 0.5f
-        fun update(newFraction: Float): Pair<Boolean, Boolean> {
-            val wasActive = isActive
-            val hasJumped =
-                (fraction == 0f && newFraction == 1f) || (fraction == 1f && newFraction == 0f)
-            isActive = newFraction > fraction
-            fraction = newFraction
-            return Pair(wasActive != isActive, hasJumped)
-        }
-    }
-
-    override fun dump(pw: PrintWriter) {
-        pw.print("smallClock=")
-        smallClock.view.dump(pw)
-
-        pw.print("largeClock=")
-        largeClock.view.dump(pw)
-    }
-
-    companion object {
-        @VisibleForTesting const val DOZE_COLOR = Color.WHITE
-        private const val FORMAT_NUMBER = 1234567890
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
deleted file mode 100644
index 6627c5d..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ /dev/null
@@ -1,57 +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.shared.clocks
-
-import android.content.Context
-import android.content.res.Resources
-import android.graphics.drawable.Drawable
-import android.view.LayoutInflater
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.ClockId
-import com.android.systemui.plugins.ClockMetadata
-import com.android.systemui.plugins.ClockProvider
-import com.android.systemui.shared.R
-import javax.inject.Inject
-
-private val TAG = DefaultClockProvider::class.simpleName
-const val DEFAULT_CLOCK_NAME = "Default Clock"
-const val DEFAULT_CLOCK_ID = "DEFAULT"
-
-/** Provides the default system clock */
-class DefaultClockProvider @Inject constructor(
-    val ctx: Context,
-    val layoutInflater: LayoutInflater,
-    @Main val resources: Resources
-) : ClockProvider {
-    override fun getClocks(): List<ClockMetadata> =
-        listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
-
-    override fun createClock(id: ClockId): ClockController {
-        if (id != DEFAULT_CLOCK_ID) {
-            throw IllegalArgumentException("$id is unsupported by $TAG")
-        }
-
-        return DefaultClockController(ctx, layoutInflater, resources)
-    }
-
-    override fun getClockThumbnail(id: ClockId): Drawable? {
-        if (id != DEFAULT_CLOCK_ID) {
-            throw IllegalArgumentException("$id is unsupported by $TAG")
-        }
-
-        // TODO: Update placeholder to actual resource
-        return resources.getDrawable(R.drawable.clock_default_thumbnail, null)
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
index cbd0875..9999f08 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
@@ -117,7 +117,7 @@
             @Override
             public void onSampleCollected(float medianLuma) {
                 if (mSamplingEnabled) {
-                    updateMediaLuma(medianLuma);
+                    updateMedianLuma(medianLuma);
                 }
             }
         };
@@ -261,7 +261,7 @@
         }
     }
 
-    private void updateMediaLuma(float medianLuma) {
+    private void updateMedianLuma(float medianLuma) {
         mCurrentMedianLuma = medianLuma;
 
         // If the difference between the new luma and the current luma is larger than threshold
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
index 9ea4b57..e226d58 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
@@ -38,6 +38,7 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException;
 
 import java.util.ArrayList;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java
deleted file mode 100644
index c89be86..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java
+++ /dev/null
@@ -1,60 +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.shared.plugins;
-
-import android.text.TextUtils;
-
-import com.android.systemui.plugins.Plugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-public interface PluginManager {
-
-    String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED";
-
-    // must be one of the channels created in NotificationChannels.java
-    String NOTIFICATION_CHANNEL_ID = "ALR";
-
-    /** Returns plugins that don't get disabled when an exceptoin occurs. */
-    String[] getPrivilegedPlugins();
-
-    /** */
-    <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls);
-    /** */
-    <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls,
-            boolean allowMultiple);
-    <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
-            Class<T> cls);
-    <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
-            Class<T> cls, boolean allowMultiple);
-
-    void removePluginListener(PluginListener<?> listener);
-
-    <T> boolean dependsOn(Plugin p, Class<T> cls);
-
-    class Helper {
-        public static <P> String getAction(Class<P> cls) {
-            ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
-            if (info == null) {
-                throw new RuntimeException(cls + " doesn't provide an interface");
-            }
-            if (TextUtils.isEmpty(info.action())) {
-                throw new RuntimeException(cls + " doesn't provide an action");
-            }
-            return info.action();
-        }
-    }
-
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
index 131f728..2f9f5b2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
@@ -31,6 +31,7 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;
 
 import java.io.FileDescriptor;
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..abefeba 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,23 +55,11 @@
     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;
 
     /**
-     * Sent when the split screen is resized
-     */
-    void onSplitScreenSecondaryBoundsChanged(in Rect bounds, in Rect insets) = 17;
-
-    /**
      * Sent when suggested rotation button could be shown
      */
     void onRotationProposal(int rotation, boolean isValid) = 18;
@@ -110,4 +93,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/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 647dd47..0890465 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -20,7 +20,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
 
 import android.app.ActivityManager;
 import android.app.ActivityManager.TaskDescription;
@@ -255,7 +255,8 @@
         // Also consider undefined activity type to include tasks in overview right after rebooting
         // the device.
         final boolean isDockable = taskInfo.supportsMultiWindow
-                && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode())
+                && ArrayUtils.contains(
+                        CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, taskInfo.getWindowingMode())
                 && (taskInfo.getActivityType() == ACTIVITY_TYPE_UNDEFINED
                 || ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType()));
         return new Task(taskKey,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index 40c8774..c9ea794 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -39,6 +39,7 @@
     private boolean mIsOrientationChanged;
     private SplitBounds mSplitBounds;
     private int mDesiredStagePosition;
+    private boolean mTaskbarInApp;
 
     public Matrix getMatrix() {
         return mMatrix;
@@ -57,29 +58,61 @@
         mDesiredStagePosition = desiredStagePosition;
     }
 
+    public void setTaskbarInApp(boolean taskbarInApp) {
+        mTaskbarInApp = taskbarInApp;
+    }
+
     /**
      * Updates the matrix based on the provided parameters
      */
     public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
-            int canvasWidth, int canvasHeight, int screenWidthPx, int taskbarSize, boolean isTablet,
+            int canvasWidth, int canvasHeight, int screenWidthPx, int screenHeightPx,
+            int taskbarSize, boolean isTablet,
             int currentRotation, boolean isRtl) {
         boolean isRotated = false;
         boolean isOrientationDifferent;
 
-        float fullscreenTaskWidth = screenWidthPx;
-        if (mSplitBounds != null && !mSplitBounds.appsStackedVertically) {
-            // For landscape, scale the width
-            float taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
-                    ? mSplitBounds.leftTaskPercent
-                    : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
-            // Scale landscape width to that of actual screen
-            fullscreenTaskWidth = screenWidthPx * taskPercent;
-        }
         int thumbnailRotation = thumbnailData.rotation;
         int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
         RectF thumbnailClipHint = new RectF();
-        float canvasScreenRatio = canvasWidth / fullscreenTaskWidth;
-        float scaledTaskbarSize = taskbarSize * canvasScreenRatio;
+
+        float scaledTaskbarSize;
+        float canvasScreenRatio;
+        if (mSplitBounds != null) {
+            float fullscreenTaskWidth;
+            float fullscreenTaskHeight;
+
+            float taskPercent;
+            if (mSplitBounds.appsStackedVertically) {
+                taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT
+                        ? mSplitBounds.topTaskPercent
+                        : (1 - (mSplitBounds.topTaskPercent + mSplitBounds.dividerHeightPercent));
+                // Scale portrait height to that of (actual screen - taskbar inset)
+                fullscreenTaskHeight = (screenHeightPx) * taskPercent;
+                if (mTaskbarInApp) {
+                    canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
+                } else {
+                    if (mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
+                        // Top app isn't cropped at all by taskbar
+                        canvasScreenRatio = 0;
+                    } else {
+                        // Same as fullscreen ratio
+                        canvasScreenRatio = (float) canvasWidth / screenWidthPx;
+                    }
+                }
+            } else {
+                // For landscape, scale the width
+                taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
+                        ? mSplitBounds.leftTaskPercent
+                        : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
+                // Scale landscape width to that of actual screen
+                fullscreenTaskWidth = screenWidthPx * taskPercent;
+                canvasScreenRatio = canvasWidth / fullscreenTaskWidth;
+            }
+        } else {
+            canvasScreenRatio = (float) canvasWidth / screenWidthPx;
+        }
+        scaledTaskbarSize = taskbarSize * canvasScreenRatio;
         thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
 
         float scale = thumbnailData.scale;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
new file mode 100644
index 0000000..0ee813b
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.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.systemui.shared.regionsampling
+
+import android.graphics.Color
+import android.graphics.Rect
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.shared.navigationbar.RegionSamplingHelper
+import com.android.systemui.shared.navigationbar.RegionSamplingHelper.SamplingCallback
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+
+/** 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()
+    private val tmpScreenLocation = IntArray(2)
+    @VisibleForTesting var regionSampler: RegionSamplingHelper? = null
+    private var lightForegroundColor = Color.WHITE
+    private var darkForegroundColor = Color.BLACK
+
+    @VisibleForTesting
+    open fun createRegionSamplingHelper(
+        sampledView: View,
+        callback: SamplingCallback,
+        mainExecutor: Executor?,
+        bgExecutor: Executor?
+    ): RegionSamplingHelper {
+        return RegionSamplingHelper(sampledView, callback, mainExecutor, bgExecutor)
+    }
+
+    /**
+     * Sets the colors to be used for Dark and Light Foreground.
+     *
+     * @param lightColor The color used for Light Foreground.
+     * @param darkColor The color used for Dark Foreground.
+     */
+    fun setForegroundColors(lightColor: Int, darkColor: Int) {
+        lightForegroundColor = lightColor
+        darkForegroundColor = darkColor
+    }
+
+    /**
+     * Determines which foreground color to use based on region darkness.
+     *
+     * @return the determined foreground color
+     */
+    fun currentForegroundColor(): Int {
+        return if (regionDarkness.isDark) {
+            lightForegroundColor
+        } else {
+            darkForegroundColor
+        }
+    }
+
+    private fun convertToClockDarkness(isRegionDark: Boolean): RegionDarkness {
+        return if (isRegionDark) {
+            RegionDarkness.DARK
+        } else {
+            RegionDarkness.LIGHT
+        }
+    }
+
+    fun currentRegionDarkness(): RegionDarkness {
+        return regionDarkness
+    }
+
+    /** Start region sampler */
+    fun startRegionSampler() {
+        regionSampler?.start(samplingBounds)
+    }
+
+    /** Stop region sampler */
+    fun stopRegionSampler() {
+        regionSampler?.stop()
+    }
+
+    /** Dump region sampler */
+    fun dump(pw: PrintWriter) {
+        regionSampler?.dump(pw)
+    }
+
+    init {
+        if (regionSamplingEnabled && sampledView != null) {
+            regionSampler =
+                createRegionSamplingHelper(
+                    sampledView,
+                    object : SamplingCallback {
+                        override fun onRegionDarknessChanged(isRegionDark: Boolean) {
+                            regionDarkness = convertToClockDarkness(isRegionDark)
+                            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.
+                         */
+                        override fun getSampledRegion(sampledView: View): Rect {
+                            val screenLocation = tmpScreenLocation
+                            sampledView.getLocationOnScreen(screenLocation)
+                            val left = screenLocation[0]
+                            val top = screenLocation[1]
+                            samplingBounds.left = left
+                            samplingBounds.top = top
+                            samplingBounds.right = left + sampledView.width
+                            samplingBounds.bottom = top + sampledView.height
+                            return samplingBounds
+                        }
+
+                        override fun isSamplingEnabled(): Boolean {
+                            return regionSamplingEnabled
+                        }
+                    },
+                    mainExecutor,
+                    bgExecutor
+                )
+        }
+        regionSampler?.setWindowVisible(true)
+    }
+}
+
+typealias UpdateColorCallback = () -> Unit
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
deleted file mode 100644
index cd4b999..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
+++ /dev/null
@@ -1,154 +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.shared.regionsampling
-
-import android.graphics.Color
-import android.graphics.Rect
-import android.view.View
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper.SamplingCallback
-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
-) {
-    private var regionDarkness = RegionDarkness.DEFAULT
-    private var samplingBounds = Rect()
-    private val tmpScreenLocation = IntArray(2)
-    @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?
-    ): RegionSamplingHelper {
-        return RegionSamplingHelper(sampledView, callback, mainExecutor, bgExecutor)
-    }
-
-    /**
-     * Sets the colors to be used for Dark and Light Foreground.
-     *
-     * @param lightColor The color used for Light Foreground.
-     * @param darkColor The color used for Dark Foreground.
-     */
-    fun setForegroundColors(lightColor: Int, darkColor: Int) {
-        lightForegroundColor = lightColor
-        darkForegroundColor = darkColor
-    }
-
-    /**
-     * Determines which foreground color to use based on region darkness.
-     *
-     * @return the determined foreground color
-     */
-    fun currentForegroundColor(): Int{
-        return if (regionDarkness.isDark) {
-            lightForegroundColor
-        } else {
-            darkForegroundColor
-        }
-    }
-
-    private fun convertToClockDarkness(isRegionDark: Boolean): RegionDarkness {
-        return if (isRegionDark) {
-            RegionDarkness.DARK
-        } else {
-            RegionDarkness.LIGHT
-        }
-    }
-
-    fun currentRegionDarkness(): RegionDarkness {
-        return regionDarkness
-    }
-
-    /**
-     * Start region sampler
-     */
-    fun startRegionSampler() {
-        regionSampler?.start(samplingBounds)
-    }
-
-    /**
-     * Stop region sampler
-     */
-    fun stopRegionSampler() {
-        regionSampler?.stop()
-    }
-
-    /**
-     * Dump region sampler
-     */
-    fun dump(pw: PrintWriter) {
-        regionSampler?.dump(pw)
-    }
-
-    init {
-        if (regionSamplingEnabled && sampledView != null) {
-            regionSampler = createRegionSamplingHelper(sampledView,
-                    object : SamplingCallback {
-                        override fun onRegionDarknessChanged(isRegionDark: Boolean) {
-                            regionDarkness = convertToClockDarkness(isRegionDark)
-                            updateFun.updateColors()
-                        }
-                        /**
-                        * 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)
-                            val left = screenLocation[0]
-                            val top = screenLocation[1]
-                            samplingBounds.left = left
-                            samplingBounds.top = top
-                            samplingBounds.right = left + sampledView.width
-                            samplingBounds.bottom = top + sampledView.height
-                            return samplingBounds
-                        }
-
-                        override fun isSamplingEnabled(): Boolean {
-                            return regionSamplingEnabled
-                        }
-                    }, mainExecutor, bgExecutor)
-        }
-        regionSampler?.setWindowVisible(true)
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
index 3748eba..19d0a3d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
@@ -71,7 +71,7 @@
                 mKeyShadowInfo.offsetY,
                 mKeyShadowInfo.alpha
             )
-        val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DARKEN)
+        val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DST_ATOP)
         renderNode.setRenderEffect(blend)
         return renderNode
     }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index 8a25096..82d70116 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -53,6 +53,8 @@
             InteractionJankMonitor.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
     public static final int CUJ_RECENTS_SCROLLING =
             InteractionJankMonitor.CUJ_RECENTS_SCROLLING;
+    public static final int CUJ_APP_SWIPE_TO_RECENTS =
+            InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
 
     @IntDef({
             CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -62,7 +64,8 @@
             CUJ_QUICK_SWITCH,
             CUJ_APP_LAUNCH_FROM_WIDGET,
             CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
-            CUJ_RECENTS_SCROLLING
+            CUJ_RECENTS_SCROLLING,
+            CUJ_APP_SWIPE_TO_RECENTS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
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..1b0dacc 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,199 @@
 
 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();
-}
\ No newline at end of file
+
+    @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
+                    // before GC would.
+                    info.releaseAllSurfaces();
+                    // 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);
+                        finishTransaction.close();
+                    } 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);
+                }
+                // Since we're not actually animating, release native memory now
+                t.close();
+                info.releaseAllSurfaces();
+                if (finishRunnable == null) return;
+                onAnimationCancelled(false /* isKeyguardOccluded */);
+                finishRunnable.run();
+            }
+        };
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
deleted file mode 100644
index 1550ab3..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ /dev/null
@@ -1,19 +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.shared.system;
-
-parcelable RemoteTransitionCompat;
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..b7e2494 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();
@@ -182,36 +126,21 @@
             public void mergeAnimation(IBinder transition, TransitionInfo info,
                     SurfaceControl.Transaction t, IBinder mergeTarget,
                     IRemoteTransitionFinishedCallback finishedCallback) {
-                if (!mergeTarget.equals(mToken)) return;
-                if (!mRecentsSession.merge(info, t, recents)) return;
-                try {
-                    finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error merging transition.", e);
+                if (mergeTarget.equals(mToken) && mRecentsSession.merge(info, t, recents)) {
+                    try {
+                        finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error merging transition.", e);
+                    }
+                    // commit taskAppeared after merge transition finished.
+                    mRecentsSession.commitTasksAppearedIfNeeded(recents);
+                } else {
+                    t.close();
+                    info.releaseAllSurfaces();
                 }
-                // commit taskAppeared after merge transition finished.
-                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);
     }
 
     /**
@@ -322,6 +251,8 @@
                 }
                 // In this case, we are "returning" to an already running app, so just consume
                 // the merge and do nothing.
+                info.releaseAllSurfaces();
+                t.close();
                 return true;
             }
             final int layer = mInfo.getChanges().size() * 3;
@@ -338,6 +269,8 @@
                 t.setLayer(targets[i].leash, layer);
             }
             t.apply();
+            // not using the incoming anim-only surfaces
+            info.releaseAnimSurfaces();
             mAppearedTargets = targets;
             return true;
         }
@@ -454,9 +387,7 @@
             }
             // Only release the non-local created surface references. The animator is responsible
             // for releasing the leashes created by local.
-            for (int i = 0; i < mInfo.getChanges().size(); ++i) {
-                mInfo.getChanges().get(i).getLeash().release();
-            }
+            mInfo.releaseAllSurfaces();
             // Reset all members.
             mWrapped = null;
             mFinishCB = null;
@@ -505,161 +436,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/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
new file mode 100644
index 0000000..05372fe
--- /dev/null
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.annotation.BoolRes
+
+object FlagsFactory {
+    private val flagMap = mutableMapOf<String, Flag<*>>()
+
+    val knownFlags: Map<String, Flag<*>>
+        get() {
+            // We need to access Flags in order to initialize our map.
+            assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+            return flagMap
+        }
+
+    fun unreleasedFlag(
+        id: Int,
+        name: String,
+        namespace: String = "systemui",
+        teamfood: Boolean = false
+    ): UnreleasedFlag {
+        val flag = UnreleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
+        FlagsFactory.checkForDupesAndAdd(flag)
+        return flag
+    }
+
+    fun releasedFlag(
+        id: Int,
+        name: String,
+        namespace: String = "systemui",
+        teamfood: Boolean = false
+    ): ReleasedFlag {
+        val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
+        FlagsFactory.checkForDupesAndAdd(flag)
+        return flag
+    }
+
+    fun resourceBooleanFlag(
+        id: Int,
+        @BoolRes resourceId: Int,
+        name: String,
+        namespace: String = "systemui",
+        teamfood: Boolean = false
+    ): ResourceBooleanFlag {
+        val flag =
+            ResourceBooleanFlag(
+                id = id,
+                name = name,
+                namespace = namespace,
+                resourceId = resourceId,
+                teamfood = teamfood
+            )
+        FlagsFactory.checkForDupesAndAdd(flag)
+        return flag
+    }
+
+    fun sysPropBooleanFlag(
+        id: Int,
+        name: String,
+        namespace: String = "systemui",
+        default: Boolean = false
+    ): SysPropBooleanFlag {
+        val flag =
+            SysPropBooleanFlag(id = id, name = name, namespace = "systemui", default = default)
+        FlagsFactory.checkForDupesAndAdd(flag)
+        return flag
+    }
+
+    private fun checkForDupesAndAdd(flag: Flag<*>) {
+        if (flagMap.containsKey(flag.name)) {
+            throw IllegalArgumentException("Name {flag.name} is already registered")
+        }
+        flagMap.forEach {
+            if (it.value.id == flag.id) {
+                throw IllegalArgumentException("Name {flag.id} is already registered")
+            }
+        }
+        flagMap[flag.name] = flag
+    }
+}
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 7b216017..8323d09 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -34,6 +34,9 @@
     @Binds
     abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsDebug): FeatureFlags
 
+    @Binds
+    abstract fun bindsRestarter(debugRestarter: FeatureFlagsDebugRestarter): Restarter
+
     @Module
     companion object {
         @JvmStatic
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
new file mode 100644
index 0000000..27c5699
--- /dev/null
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.annotation.BoolRes
+
+object FlagsFactory {
+    private val flagMap = mutableMapOf<String, Flag<*>>()
+
+    val knownFlags: Map<String, Flag<*>>
+        get() {
+            // We need to access Flags in order to initialize our map.
+            assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+            return flagMap
+        }
+
+    fun unreleasedFlag(
+        id: Int,
+        name: String,
+        namespace: String = "systemui",
+        teamfood: Boolean = false
+    ): UnreleasedFlag {
+        // Unreleased flags are always false in this build.
+        val flag = UnreleasedFlag(id = id, name = "", namespace = "", teamfood = false)
+        return flag
+    }
+
+    fun releasedFlag(
+        id: Int,
+        name: String,
+        namespace: String = "systemui",
+        teamfood: Boolean = false
+    ): ReleasedFlag {
+        val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
+        flagMap[name] = flag
+        return flag
+    }
+
+    fun resourceBooleanFlag(
+        id: Int,
+        @BoolRes resourceId: Int,
+        name: String,
+        namespace: String = "systemui",
+        teamfood: Boolean = false
+    ): ResourceBooleanFlag {
+        val flag =
+            ResourceBooleanFlag(
+                id = id,
+                name = name,
+                namespace = namespace,
+                resourceId = resourceId,
+                teamfood = teamfood
+            )
+        flagMap[name] = flag
+        return flag
+    }
+
+    fun sysPropBooleanFlag(
+        id: Int,
+        name: String,
+        namespace: String = "systemui",
+        default: Boolean = false
+    ): SysPropBooleanFlag {
+        val flag =
+            SysPropBooleanFlag(id = id, name = name, namespace = namespace, default = default)
+        flagMap[name] = flag
+        return flag
+    }
+}
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 aef8876..87beff7 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -27,4 +27,7 @@
 abstract class FlagsModule {
     @Binds
     abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
+
+    @Binds
+    abstract fun bindsRestarter(debugRestarter: FeatureFlagsReleaseRestarter): Restarter
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 450784e..f59bf8e 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -69,10 +69,16 @@
         super.reloadColor()
     }
 
-    override fun setMessage(msg: CharSequence?) {
+    override fun setMessage(msg: CharSequence?, animate: Boolean) {
         if ((msg == textAboutToShow && msg != null) || msg == text) {
             return
         }
+
+        if (!animate) {
+            super.setMessage(msg, animate)
+            return
+        }
+
         textAboutToShow = msg
 
         if (animatorSet.isRunning) {
@@ -89,7 +95,7 @@
         hideAnimator.addListener(
             object : AnimatorListenerAdapter() {
                 override fun onAnimationEnd(animation: Animator?) {
-                    super@BouncerKeyguardMessageArea.setMessage(msg)
+                    super@BouncerKeyguardMessageArea.setMessage(msg, animate)
                 }
             }
         )
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 40a96b0..87e9d56 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -26,6 +26,7 @@
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -34,11 +35,12 @@
 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
@@ -52,6 +54,7 @@
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
 /**
@@ -69,16 +72,20 @@
     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)
+                updateFontSizes()
             }
         }
 
@@ -139,21 +146,17 @@
             bgExecutor: Executor?,
             regionSamplingEnabled: Boolean,
             updateColors: () -> Unit
-    ): RegionSamplingInstance {
-        return RegionSamplingInstance(
+    ): RegionSampler {
+        return RegionSampler(
             sampledView,
             mainExecutor,
             bgExecutor,
             regionSamplingEnabled,
-            object : RegionSamplingInstance.UpdateColorCallback {
-                override fun updateColors() {
-                    updateColors()
-                }
-            })
+            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,10 +164,11 @@
     private val configListener = object : ConfigurationController.ConfigurationListener {
         override fun onThemeChanged() {
             clock?.events?.onColorPaletteChanged(resources)
+            updateColors()
         }
 
         override fun onDensityOrFontScaleChanged() {
-            clock?.events?.onFontSettingChanged()
+            updateFontSizes()
         }
     }
 
@@ -186,8 +190,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 +230,7 @@
                 listenForDozing(this)
                 if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
                     listenForDozeAmountTransition(this)
+                    listenForAnyStateToAodTransition(this)
                 } else {
                     listenForDozeAmount(this)
                 }
@@ -246,6 +253,13 @@
         largeRegionSampler?.stopRegionSampler()
     }
 
+    private fun updateFontSizes() {
+        clock?.smallClock?.events?.onFontSettingChanged(
+            resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat())
+        clock?.largeClock?.events?.onFontSettingChanged(
+            resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
+    }
+
     /**
      * Dump information for debugging
      */
@@ -276,6 +290,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/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 458d22e..a25b281 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -25,7 +25,6 @@
 
 import com.android.internal.util.EmergencyAffordanceManager;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.settingslib.Utils;
 
 /**
  * This class implements a smart emergency button that updates itself based
@@ -91,17 +90,6 @@
         return super.onTouchEvent(event);
     }
 
-    /**
-     * Reload colors from resources.
-     **/
-    public void reloadColors() {
-        int color = Utils.getColorAttrDefaultColor(getContext(),
-                com.android.internal.R.attr.textColorOnAccent);
-        setTextColor(color);
-        setBackground(getContext()
-                .getDrawable(com.android.systemui.R.drawable.kg_emergency_button_background));
-    }
-
     @Override
     public boolean performLongClick() {
         return super.performLongClick();
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index c5190e8..ea808eb 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -135,7 +135,7 @@
             mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
         }
         mActivityTaskManager.stopSystemLockTaskMode();
-        mShadeController.collapsePanel(false);
+        mShadeController.collapseShade(false);
         if (mTelecomManager != null && mTelecomManager.isInCall()) {
             mTelecomManager.showInCallScreen(false);
             if (mEmergencyButtonCallback != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
index 4a41b3f..5bb9367 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
@@ -48,11 +48,13 @@
 import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_OCCLUSION_CHANGED
 import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_RESET
 import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_VISIBILITY_CHANGED
+import com.android.keyguard.InternalFaceAuthReasons.NON_STRONG_BIOMETRIC_ALLOWED_CHANGED
 import com.android.keyguard.InternalFaceAuthReasons.OCCLUDING_APP_REQUESTED
 import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN
 import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN
 import com.android.keyguard.InternalFaceAuthReasons.RETRY_AFTER_HW_UNAVAILABLE
 import com.android.keyguard.InternalFaceAuthReasons.STARTED_WAKING_UP
+import com.android.keyguard.InternalFaceAuthReasons.STRONG_AUTH_ALLOWED_CHANGED
 import com.android.keyguard.InternalFaceAuthReasons.TRUST_DISABLED
 import com.android.keyguard.InternalFaceAuthReasons.TRUST_ENABLED
 import com.android.keyguard.InternalFaceAuthReasons.USER_SWITCHING
@@ -121,6 +123,9 @@
     const val FACE_AUTHENTICATED = "Face auth started/stopped because face is authenticated"
     const val BIOMETRIC_ENABLED =
         "Face auth started/stopped because biometric is enabled on keyguard"
+    const val STRONG_AUTH_ALLOWED_CHANGED = "Face auth stopped because strong auth allowed changed"
+    const val NON_STRONG_BIOMETRIC_ALLOWED_CHANGED =
+        "Face auth stopped because non strong biometric allowed changed"
 }
 
 /**
@@ -204,7 +209,11 @@
     @UiEvent(doc = FACE_AUTHENTICATED)
     FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED(1187, FACE_AUTHENTICATED),
     @UiEvent(doc = BIOMETRIC_ENABLED)
-    FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD(1188, BIOMETRIC_ENABLED);
+    FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD(1188, BIOMETRIC_ENABLED),
+    @UiEvent(doc = STRONG_AUTH_ALLOWED_CHANGED)
+    FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED(1255, STRONG_AUTH_ALLOWED_CHANGED),
+    @UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED)
+    FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED);
 
     override fun getId(): Int = this.id
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 92ba619..860c8e3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -20,7 +20,6 @@
 import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
 import static com.android.keyguard.KeyguardAbsKeyInputView.MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT;
 
-import android.annotation.CallSuper;
 import android.content.res.ColorStateList;
 import android.os.AsyncTask;
 import android.os.CountDownTimer;
@@ -117,13 +116,6 @@
         }
     }
 
-    @CallSuper
-    @Override
-    public void reloadColors() {
-        super.reloadColors();
-        mMessageAreaController.reloadColors();
-    }
-
     @Override
     public boolean needsInput() {
         return false;
@@ -159,10 +151,12 @@
                 int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
                 Map<String, Object> arguments = new HashMap<>();
                 arguments.put("count", secondsRemaining);
-                mMessageAreaController.setMessage(PluralsMessageFormatter.format(
-                        mView.getResources(),
-                        arguments,
-                        R.string.kg_too_many_failed_attempts_countdown));
+                mMessageAreaController.setMessage(
+                        PluralsMessageFormatter.format(
+                            mView.getResources(),
+                            arguments,
+                            R.string.kg_too_many_failed_attempts_countdown),
+                        /* animate= */ false);
             }
 
             @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 8ebad6c..40423cd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -5,6 +5,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -22,6 +23,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+
 /**
  * Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
  */
@@ -46,6 +48,7 @@
      */
     private FrameLayout mSmallClockFrame;
     private FrameLayout mLargeClockFrame;
+    private ClockController mClock;
 
     private View mStatusArea;
     private int mSmartspaceTopOffset;
@@ -95,6 +98,8 @@
     }
 
     void setClock(ClockController clock, int statusBarState) {
+        mClock = clock;
+
         // Disconnect from existing plugin.
         mSmallClockFrame.removeAllViews();
         mLargeClockFrame.removeAllViews();
@@ -108,6 +113,35 @@
         Log.i(TAG, "Attached new clock views to switch");
         mSmallClockFrame.addView(clock.getSmallClock().getView());
         mLargeClockFrame.addView(clock.getLargeClock().getView());
+        updateClockTargetRegions();
+    }
+
+    void updateClockTargetRegions() {
+        if (mClock != null) {
+            if (mSmallClockFrame.isLaidOut()) {
+                int targetHeight =  getResources()
+                        .getDimensionPixelSize(R.dimen.small_clock_text_size);
+                mClock.getSmallClock().getEvents().onTargetRegionChanged(new Rect(
+                        mSmallClockFrame.getLeft(),
+                        mSmallClockFrame.getTop(),
+                        mSmallClockFrame.getRight(),
+                        mSmallClockFrame.getTop() + targetHeight));
+            }
+
+            if (mLargeClockFrame.isLaidOut()) {
+                int largeClockTopMargin = getResources()
+                        .getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
+                int targetHeight = getResources()
+                        .getDimensionPixelSize(R.dimen.large_clock_text_size) * 2;
+                int top = mLargeClockFrame.getHeight() / 2 - targetHeight / 2
+                        + largeClockTopMargin / 2;
+                mClock.getLargeClock().getEvents().onTargetRegionChanged(new Rect(
+                        mLargeClockFrame.getLeft(),
+                        top,
+                        mLargeClockFrame.getRight(),
+                        top + targetHeight));
+            }
+        }
     }
 
     private void updateClockViews(boolean useLargeClock, boolean animate) {
@@ -214,6 +248,10 @@
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
+        if (changed) {
+            post(() -> updateClockTargetRegions());
+        }
+
         if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
             post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index ace942d..e6aae9b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -77,7 +77,7 @@
     @KeyguardClockSwitch.ClockSize
     private int mCurrentClockSize = SMALL;
 
-    private int mKeyguardClockTopMargin = 0;
+    private int mKeyguardSmallClockTopMargin = 0;
     private final ClockRegistry.ClockChangeListener mClockChangedListener;
 
     private ViewGroup mStatusArea;
@@ -162,7 +162,7 @@
         mClockRegistry.registerClockChangeListener(mClockChangedListener);
         setClock(mClockRegistry.createCurrentClock());
         mClockEventController.registerListeners(mView);
-        mKeyguardClockTopMargin =
+        mKeyguardSmallClockTopMargin =
                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
 
         if (mOnlyClock) {
@@ -244,10 +244,12 @@
      */
     public void onDensityOrFontScaleChanged() {
         mView.onDensityOrFontScaleChanged();
-        mKeyguardClockTopMargin =
+        mKeyguardSmallClockTopMargin =
                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
+        mView.updateClockTargetRegions();
     }
 
+
     /**
      * Set which clock should be displayed on the keyguard. The other one will be automatically
      * hidden.
@@ -327,7 +329,7 @@
             return frameHeight / 2 + clockHeight / 2;
         } else {
             int clockHeight = clock.getSmallClock().getView().getHeight();
-            return clockHeight + statusBarHeaderHeight + mKeyguardClockTopMargin;
+            return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
index b2658c9..a5b62b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
@@ -29,5 +29,4 @@
      */
     public static final boolean DEBUG = Log.isLoggable("Keyguard", Log.DEBUG);
     public static final boolean DEBUG_SIM_STATES = true;
-    public static final boolean DEBUG_BIOMETRIC_WAKELOCK = true;
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index db64f05..2b660de 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -21,7 +21,6 @@
 import android.content.res.Resources;
 import android.media.AudioManager;
 import android.os.SystemClock;
-import android.service.trust.TrustAgentService;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 import android.util.MathUtils;
@@ -68,30 +67,24 @@
     private final KeyguardUpdateMonitorCallback mUpdateCallback =
             new KeyguardUpdateMonitorCallback() {
                 @Override
-                public void onTrustGrantedWithFlags(int flags, int userId) {
-                    if (userId != KeyguardUpdateMonitor.getCurrentUser()) return;
-                    boolean bouncerVisible = mView.isVisibleToUser();
-                    boolean temporaryAndRenewable =
-                            (flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)
-                            != 0;
-                    boolean initiatedByUser =
-                            (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
-                    boolean dismissKeyguard =
-                            (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
-
-                    if (initiatedByUser || dismissKeyguard) {
-                        if ((mViewMediatorCallback.isScreenOn() || temporaryAndRenewable)
-                                && (bouncerVisible || dismissKeyguard)) {
-                            if (!bouncerVisible) {
-                                // The trust agent dismissed the keyguard without the user proving
-                                // that they are present (by swiping up to show the bouncer). That's
-                                // fine if the user proved presence via some other way to the trust
-                                //agent.
-                                Log.i(TAG, "TrustAgent dismissed Keyguard.");
-                            }
-                            mSecurityCallback.dismiss(false /* authenticated */, userId,
-                                    /* bypassSecondaryLockScreen */ false, SecurityMode.Invalid);
-                        } else {
+                public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+                        TrustGrantFlags flags, String message) {
+                    if (dismissKeyguard) {
+                        if (!mView.isVisibleToUser()) {
+                            // The trust agent dismissed the keyguard without the user proving
+                            // that they are present (by swiping up to show the bouncer). That's
+                            // fine if the user proved presence via some other way to the trust
+                            // agent.
+                            Log.i(TAG, "TrustAgent dismissed Keyguard.");
+                        }
+                        mSecurityCallback.dismiss(
+                                false /* authenticated */,
+                                KeyguardUpdateMonitor.getCurrentUser(),
+                                /* bypassSecondaryLockScreen */ false,
+                                SecurityMode.Invalid
+                        );
+                    } else {
+                        if (flags.isInitiatedByUser() || flags.dismissKeyguardRequested()) {
                             mViewMediatorCallback.playTrustedSound();
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 73229c3..2e9ad58 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -16,11 +16,11 @@
 
 package com.android.keyguard;
 
-import android.annotation.CallSuper;
 import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.inputmethod.InputMethodManager;
 
@@ -141,18 +141,10 @@
     public void showMessage(CharSequence message, ColorStateList colorState) {
     }
 
-    /**
-     * Reload colors from resources.
-     **/
-    @CallSuper
-    public void reloadColors() {
-        if (mEmergencyButton != null) {
-            mEmergencyButton.reloadColors();
-        }
-    }
-
     public void startAppearAnimation() {
-        mMessageAreaController.setMessage(getInitialMessageResId());
+        if (TextUtils.isEmpty(mMessageAreaController.getMessage())) {
+            mMessageAreaController.setMessage(getInitialMessageResId());
+        }
         mView.startAppearAnimation();
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 71470e8..52ca166 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -26,7 +26,6 @@
     val credentialAttempted: Boolean,
     val deviceInteractive: Boolean,
     val dreaming: Boolean,
-    val encryptedOrLockdown: Boolean,
     val fingerprintDisabled: Boolean,
     val fingerprintLockedOut: Boolean,
     val goingToSleep: Boolean,
@@ -35,7 +34,9 @@
     val keyguardOccluded: Boolean,
     val occludingAppRequestingFp: Boolean,
     val primaryUser: Boolean,
+    val shouldListenSfpsState: Boolean,
     val shouldListenForFingerprintAssistant: Boolean,
+    val strongerAuthRequired: Boolean,
     val switchingUser: Boolean,
     val udfps: Boolean,
     val userDoesNotHaveTrust: Boolean
@@ -49,24 +50,25 @@
     override val listening: Boolean,
     // keep sorted
     val authInterruptActive: Boolean,
-    val becauseCannotSkipBouncer: Boolean,
     val biometricSettingEnabledForUser: Boolean,
     val bouncerFullyShown: Boolean,
-    val faceAuthenticated: Boolean,
+    val faceAndFpNotAuthenticated: Boolean,
+    val faceAuthAllowed: Boolean,
     val faceDisabled: Boolean,
     val faceLockedOut: Boolean,
-    val fpLockedOut: Boolean,
     val goingToSleep: Boolean,
     val keyguardAwake: Boolean,
     val keyguardGoingAway: Boolean,
     val listeningForFaceAssistant: Boolean,
     val occludingAppRequestingFaceAuth: Boolean,
     val primaryUser: Boolean,
-    val scanningAllowedByStrongAuth: Boolean,
     val secureCameraLaunched: Boolean,
+    val supportsDetect: Boolean,
     val switchingUser: Boolean,
     val udfpsBouncerShowing: Boolean,
-) : KeyguardListenModel()
+    val udfpsFingerDown: Boolean,
+    val userNotTrustedOrDetectionIsNeeded: Boolean,
+    ) : KeyguardListenModel()
 /**
  * Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock].
  */
@@ -77,9 +79,8 @@
     // keep sorted
     val awakeKeyguard: Boolean,
     val authInterruptActive: Boolean,
-    val encryptedOrTimedOut: Boolean,
-    val fpLockout: Boolean,
-    val lockDown: Boolean,
+    val fpLockedOut: Boolean,
+    val primaryAuthRequired: Boolean,
     val switchingUser: Boolean,
     val triggerActiveUnlockForAssistant: Boolean,
     val userCanDismissLockScreen: Boolean
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index c79fc2c..0e5f8c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -59,6 +59,7 @@
     @Nullable
     private ViewGroup mContainer;
     private int mTopMargin;
+    protected boolean mAnimate;
 
     public KeyguardMessageArea(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -106,7 +107,7 @@
     }
 
     @Override
-    public void setMessage(CharSequence msg) {
+    public void setMessage(CharSequence msg, boolean animate) {
         if (!TextUtils.isEmpty(msg)) {
             securityMessageChanged(msg);
         } else {
@@ -115,21 +116,12 @@
     }
 
     @Override
-    public void setMessage(int resId) {
-        CharSequence message = null;
-        if (resId != 0) {
-            message = getContext().getResources().getText(resId);
-        }
-        setMessage(message);
-    }
-
-    @Override
     public void formatMessage(int resId, Object... formatArgs) {
         CharSequence message = null;
         if (resId != 0) {
             message = getContext().getString(resId, formatArgs);
         }
-        setMessage(message);
+        setMessage(message, true);
     }
 
     private void securityMessageChanged(CharSequence message) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index 2bd3ca5..6a92162 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -92,22 +92,28 @@
     }
 
     public void setMessage(CharSequence s) {
-        mView.setMessage(s);
+        setMessage(s, true);
+    }
+
+    /**
+     * Sets a message to the underlying text view.
+     */
+    public void setMessage(CharSequence s, boolean animate) {
+        mView.setMessage(s, animate);
     }
 
     public void setMessage(int resId) {
-        mView.setMessage(resId);
+        String message = resId != 0 ? mView.getResources().getString(resId) : null;
+        setMessage(message);
     }
 
     public void setNextMessageColor(ColorStateList colorState) {
         mView.setNextMessageColor(colorState);
     }
 
-    /**
-     * Reload colors from resources.
-     **/
-    public void reloadColors() {
-        mView.reloadColor();
+    /** Returns the message of the underlying TextView. */
+    public CharSequence getMessage() {
+        return mView.getText();
     }
 
     /** Factory for creating {@link com.android.keyguard.KeyguardMessageAreaController}. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 2cc5ccdc..c985fd7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -34,6 +34,7 @@
 import android.graphics.Rect;
 import android.os.Trace;
 import android.util.AttributeSet;
+import android.view.WindowInsets;
 import android.view.WindowInsetsAnimationControlListener;
 import android.view.WindowInsetsAnimationController;
 import android.view.animation.AnimationUtils;
@@ -236,4 +237,50 @@
         return getResources().getString(
                 com.android.internal.R.string.keyguard_accessibility_password_unlock);
     }
+
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        if (!mPasswordEntry.isFocused() && isVisibleToUser()) {
+            mPasswordEntry.requestFocus();
+        }
+        return super.onApplyWindowInsets(insets);
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+        if (hasWindowFocus) {
+            if (isVisibleToUser()) {
+                showKeyboard();
+            } else {
+                hideKeyboard();
+            }
+        }
+    }
+
+    /**
+     * Sends signal to the focused window to show the keyboard.
+     */
+    public void showKeyboard() {
+        post(() -> {
+            if (mPasswordEntry.isAttachedToWindow()
+                    && !mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+                mPasswordEntry.requestFocus();
+                mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime());
+            }
+        });
+    }
+
+    /**
+     * Sends signal to the focused window to hide the keyboard.
+     */
+    public void hideKeyboard() {
+        post(() -> {
+            if (mPasswordEntry.isAttachedToWindow()
+                    && mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+                mPasswordEntry.clearFocus();
+                mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
+            }
+        });
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 0025986..d221e22 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -16,7 +16,6 @@
 
 package com.android.keyguard;
 
-import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.os.UserHandle;
 import android.text.Editable;
@@ -27,7 +26,6 @@
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
-import android.view.WindowInsets;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
@@ -39,7 +37,6 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -95,18 +92,6 @@
         }
     };
 
-    @Override
-    public void reloadColors() {
-        super.reloadColors();
-        int textColor = Utils.getColorAttr(mView.getContext(),
-                android.R.attr.textColorPrimary).getDefaultColor();
-        mPasswordEntry.setTextColor(textColor);
-        mPasswordEntry.setHighlightColor(textColor);
-        mPasswordEntry.setBackgroundTintList(ColorStateList.valueOf(textColor));
-        mPasswordEntry.setForegroundTintList(ColorStateList.valueOf(textColor));
-        mSwitchImeButton.setImageTintList(ColorStateList.valueOf(textColor));
-    }
-
     protected KeyguardPasswordViewController(KeyguardPasswordView view,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             SecurityMode securityMode,
@@ -214,12 +199,9 @@
             return;
         }
 
-        mView.post(() -> {
-            if (mView.isShown()) {
-                mPasswordEntry.requestFocus();
-                mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime());
-            }
-        });
+        if (mView.isShown()) {
+            mView.showKeyboard();
+        }
     }
 
     @Override
@@ -241,16 +223,12 @@
                 super.onPause();
             });
         }
-        if (mPasswordEntry.isAttachedToWindow()) {
-            mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
-        }
+        mView.hideKeyboard();
     }
 
     @Override
     public void onStartingToHide() {
-        if (mPasswordEntry.isAttachedToWindow()) {
-            mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
-        }
+        mView.hideKeyboard();
     }
 
     private void updateSwitchImeButton() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 1f0bd54..571d274 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -35,7 +35,6 @@
 import com.android.internal.widget.LockscreenCredential;
 import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingClassifier;
 import com.android.systemui.classifier.FalsingCollector;
@@ -272,16 +271,6 @@
     }
 
     @Override
-    public void reloadColors() {
-        super.reloadColors();
-        mMessageAreaController.reloadColors();
-        int textColor = Utils.getColorAttr(mLockPatternView.getContext(),
-                android.R.attr.textColorSecondary).getDefaultColor();
-        int errorColor = Utils.getColorError(mLockPatternView.getContext()).getDefaultColor();
-        mLockPatternView.setColors(textColor, textColor, errorColor);
-    }
-
-    @Override
     public void onPause() {
         super.onPause();
 
@@ -372,10 +361,13 @@
                 Map<String, Object> arguments = new HashMap<>();
                 arguments.put("count", secondsRemaining);
 
-                mMessageAreaController.setMessage(PluralsMessageFormatter.format(
-                        mView.getResources(),
-                        arguments,
-                        R.string.kg_too_many_failed_attempts_countdown));
+                mMessageAreaController.setMessage(
+                        PluralsMessageFormatter.format(
+                            mView.getResources(),
+                            arguments,
+                            R.string.kg_too_many_failed_attempts_countdown),
+                        /* animate= */ false
+                );
             }
 
             @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 7876f07..f51ac32 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -70,12 +70,6 @@
     }
 
     @Override
-    public void reloadColors() {
-        super.reloadColors();
-        mView.reloadColors();
-    }
-
-    @Override
     public boolean startDisappearAnimation(Runnable finishRunnable) {
         return mView.startDisappearAnimation(
                 mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 93ee151..8f3484a 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 =
@@ -214,10 +221,11 @@
                 public void onEnd(WindowInsetsAnimation animation) {
                     if (!mDisappearAnimRunning) {
                         endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
-                        updateChildren(0 /* translationY */, 1f /* alpha */);
                     } else {
                         endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
+                        setAlpha(0f);
                     }
+                    updateChildren(0 /* translationY */, 1f /* alpha */);
                 }
 
                 private void updateChildren(int translationY, float alpha) {
@@ -318,7 +326,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 +346,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 +373,7 @@
         }
 
         mViewMode.init(this, mGlobalSettings, mSecurityViewFlipper, mFalsingManager,
-                mUserSwitcherController);
+                mUserSwitcherController, mFalsingA11yDelegate);
     }
 
     @Mode int getMode() {
@@ -716,6 +728,11 @@
         mViewMode.reloadColors();
     }
 
+    /** Handles density or font scale changes. */
+    void onDensityOrFontScaleChanged() {
+        mViewMode.onDensityOrFontScaleChanged();
+    }
+
     /**
      * Enscapsulates the differences between bouncer modes for the container.
      */
@@ -723,7 +740,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() {};
@@ -740,6 +758,9 @@
         /** Refresh colors */
         default void reloadColors() {};
 
+        /** Handles density or font scale changes. */
+        default void onDensityOrFontScaleChanged() {}
+
         /** On a successful auth, optionally handle how the view disappears */
         default void startDisappearAnimation(SecurityMode securityMode) {};
 
@@ -828,7 +849,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 +887,7 @@
                 this::setupUserSwitcher;
 
         private UserSwitcherCallback mUserSwitcherCallback;
+        private FalsingA11yDelegate mFalsingA11yDelegate;
 
         UserSwitcherViewMode(UserSwitcherCallback userSwitcherCallback) {
             mUserSwitcherCallback = userSwitcherCallback;
@@ -874,23 +897,20 @@
         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(
-                        R.layout.keyguard_bouncer_user_switcher,
-                        mView,
-                        true);
-                mUserSwitcherViewGroup =  mView.findViewById(R.id.keyguard_bouncer_user_switcher);
+                inflateUserSwitcher();
             }
             updateSecurityViewLocation();
-            mUserSwitcher = mView.findViewById(R.id.user_switcher_header);
             setupUserSwitcher();
             mUserSwitcherController.addUserSwitchCallback(mUserSwitchCallback);
         }
@@ -921,6 +941,12 @@
         }
 
         @Override
+        public void onDensityOrFontScaleChanged() {
+            mView.removeView(mUserSwitcherViewGroup);
+            inflateUserSwitcher();
+        }
+
+        @Override
         public void onDestroy() {
             mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback);
         }
@@ -978,6 +1004,7 @@
             mUserSwitcher.setText(currentUserName);
 
             KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor);
+            anchor.setAccessibilityDelegate(mFalsingA11yDelegate);
 
             BaseUserSwitcherAdapter adapter = new BaseUserSwitcherAdapter(mUserSwitcherController) {
                 @Override
@@ -1048,7 +1075,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) -> {
@@ -1080,11 +1107,19 @@
                         new KeyguardSecurityViewTransition());
             }
             int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans);
+            int viewFlipperBottomMargin = mResources.getDimensionPixelSize(
+                    R.dimen.bouncer_user_switcher_view_mode_view_flipper_bottom_margin);
+            int userSwitcherBottomMargin = mResources.getDimensionPixelSize(
+                    R.dimen.bouncer_user_switcher_view_mode_user_switcher_bottom_margin);
             if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
                 ConstraintSet constraintSet = new ConstraintSet();
                 constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP, yTrans);
-                constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
-                constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+                constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, mViewFlipper.getId(),
+                        TOP, userSwitcherBottomMargin);
+                constraintSet.connect(mViewFlipper.getId(), TOP, mUserSwitcherViewGroup.getId(),
+                        BOTTOM);
+                constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM,
+                        viewFlipperBottomMargin);
                 constraintSet.centerHorizontally(mViewFlipper.getId(), PARENT_ID);
                 constraintSet.centerHorizontally(mUserSwitcherViewGroup.getId(), PARENT_ID);
                 constraintSet.setVerticalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD);
@@ -1120,6 +1155,15 @@
             }
         }
 
+        private void inflateUserSwitcher() {
+            LayoutInflater.from(mView.getContext()).inflate(
+                    R.layout.keyguard_bouncer_user_switcher,
+                    mView,
+                    true);
+            mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher);
+            mUserSwitcher = mView.findViewById(R.id.user_switcher_header);
+        }
+
         interface UserSwitcherCallback {
             void showUnlockToContinueMessage();
         }
@@ -1137,7 +1181,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 0b395a8..a72a484 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -58,7 +58,9 @@
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
-import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.biometrics.SideFpsController;
+import com.android.systemui.biometrics.SideFpsUiRequestSource;
+import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -99,7 +101,8 @@
     private final GlobalSettings mGlobalSettings;
     private final FeatureFlags mFeatureFlags;
     private final SessionTracker mSessionTracker;
-    private final Optional<SidefpsController> mSidefpsController;
+    private final Optional<SideFpsController> mSideFpsController;
+    private final FalsingA11yDelegate mFalsingA11yDelegate;
 
     private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
 
@@ -248,6 +251,11 @@
                 public void onUiModeChanged() {
                     reloadColors();
                 }
+
+                @Override
+                public void onDensityOrFontScaleChanged() {
+                    KeyguardSecurityContainerController.this.onDensityOrFontScaleChanged();
+                }
             };
     private boolean mBouncerVisible = false;
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
@@ -288,7 +296,8 @@
             FeatureFlags featureFlags,
             GlobalSettings globalSettings,
             SessionTracker sessionTracker,
-            Optional<SidefpsController> sidefpsController) {
+            Optional<SideFpsController> sideFpsController,
+            FalsingA11yDelegate falsingA11yDelegate) {
         super(view);
         mLockPatternUtils = lockPatternUtils;
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -308,12 +317,14 @@
         mFeatureFlags = featureFlags;
         mGlobalSettings = globalSettings;
         mSessionTracker = sessionTracker;
-        mSidefpsController = sidefpsController;
+        mSideFpsController = sideFpsController;
+        mFalsingA11yDelegate = falsingA11yDelegate;
     }
 
     @Override
     public void onInit() {
         mSecurityViewFlipperController.init();
+        configureMode();
     }
 
     @Override
@@ -346,16 +357,29 @@
     }
 
     private void updateSideFpsVisibility() {
-        if (!mSidefpsController.isPresent()) {
+        if (!mSideFpsController.isPresent()) {
             return;
         }
-        if (mBouncerVisible
-                && getResources().getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
-                && mUpdateMonitor.isFingerprintDetectionRunning()
-                && !mUpdateMonitor.userNeedsStrongAuth()) {
-            mSidefpsController.get().show();
+        final boolean sfpsEnabled = getResources().getBoolean(
+                R.bool.config_show_sidefps_hint_on_bouncer);
+        final boolean fpsDetectionRunning = mUpdateMonitor.isFingerprintDetectionRunning();
+        final boolean isUnlockingWithFpAllowed =
+                mUpdateMonitor.isUnlockingWithFingerprintAllowed();
+
+        boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning
+                && isUnlockingWithFpAllowed;
+
+        if (DEBUG) {
+            Log.d(TAG, "sideFpsToShow=" + toShow + ", "
+                    + "mBouncerVisible=" + mBouncerVisible + ", "
+                    + "configEnabled=" + sfpsEnabled + ", "
+                    + "fpsDetectionRunning=" + fpsDetectionRunning + ", "
+                    + "isUnlockingWithFpAllowed=" + isUnlockingWithFpAllowed);
+        }
+        if (toShow) {
+            mSideFpsController.get().show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
         } else {
-            mSidefpsController.get().hide();
+            mSideFpsController.get().hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
         }
     }
 
@@ -625,7 +649,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) {
@@ -706,10 +730,22 @@
     }
 
     private void reloadColors() {
-        mSecurityViewFlipperController.reloadColors();
+        resetViewFlipper();
         mView.reloadColors();
     }
 
+    /** Handles density or font scale changes. */
+    private void onDensityOrFontScaleChanged() {
+        resetViewFlipper();
+        mView.onDensityOrFontScaleChanged();
+    }
+
+    private void resetViewFlipper() {
+        mSecurityViewFlipperController.clearViews();
+        mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode,
+                mKeyguardSecurityCallback);
+    }
+
     static class Factory {
 
         private final KeyguardSecurityContainer mView;
@@ -729,7 +765,8 @@
         private final FeatureFlags mFeatureFlags;
         private final UserSwitcherController mUserSwitcherController;
         private final SessionTracker mSessionTracker;
-        private final Optional<SidefpsController> mSidefpsController;
+        private final Optional<SideFpsController> mSidefpsController;
+        private final FalsingA11yDelegate mFalsingA11yDelegate;
 
         @Inject
         Factory(KeyguardSecurityContainer view,
@@ -749,7 +786,8 @@
                 FeatureFlags featureFlags,
                 GlobalSettings globalSettings,
                 SessionTracker sessionTracker,
-                Optional<SidefpsController> sidefpsController) {
+                Optional<SideFpsController> sidefpsController,
+                FalsingA11yDelegate falsingA11yDelegate) {
             mView = view;
             mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
             mLockPatternUtils = lockPatternUtils;
@@ -767,6 +805,7 @@
             mUserSwitcherController = userSwitcherController;
             mSessionTracker = sessionTracker;
             mSidefpsController = sidefpsController;
+            mFalsingA11yDelegate = falsingA11yDelegate;
         }
 
         public KeyguardSecurityContainerController create(
@@ -777,7 +816,7 @@
                     mKeyguardStateController, securityCallback, mSecurityViewFlipperController,
                     mConfigurationController, mFalsingCollector, mFalsingManager,
                     mUserSwitcherController, mFeatureFlags, mGlobalSettings, mSessionTracker,
-                    mSidefpsController);
+                    mSidefpsController, mFalsingA11yDelegate);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index bddf4b0..a5c8c78 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -74,15 +74,13 @@
         }
     }
 
-    /**
-     * Reload colors of ui elements upon theme change.
-     */
-    public void reloadColors() {
-        for (KeyguardInputViewController<KeyguardInputView> child : mChildren) {
-            child.reloadColors();
-        }
+    /** Handles density or font scale changes. */
+    public void clearViews() {
+        mView.removeAllViews();
+        mChildren.clear();
     }
 
+
     @VisibleForTesting
     KeyguardInputViewController<KeyguardInputView> getSecurityView(SecurityMode securityMode,
             KeyguardSecurityCallback keyguardSecurityCallback) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 91bf20f..a16f3047 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -133,12 +133,6 @@
     }
 
     @Override
-    public void reloadColors() {
-        super.reloadColors();
-        mView.reloadColors();
-    }
-
-    @Override
     protected void verifyPasswordAndUnlock() {
         String entry = mPasswordEntry.getText();
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 5995e85..e9405eb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -121,12 +121,6 @@
     }
 
     @Override
-    public void reloadColors() {
-        super.reloadColors();
-        mView.reloadColors();
-    }
-
-    @Override
     protected void verifyPasswordAndUnlock() {
         mStateMachine.next();
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 83e23bd..8b9823b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -17,6 +17,7 @@
 package com.android.keyguard;
 
 import android.content.Context;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
@@ -112,4 +113,11 @@
             mKeyguardSlice.dump(pw, args);
         }
     }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Trace.beginSection("KeyguardStatusView#onMeasure");
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        Trace.endSection();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 39dc609..51ade29 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -27,14 +27,16 @@
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
 import static android.hardware.biometrics.BiometricConstants.LockoutMode;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
+import static android.hardware.biometrics.BiometricSourceType.FACE;
+import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
 import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.keyguard.FaceAuthReasonKt.apiRequestReasonToUiEvent;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED;
 import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_DREAM_STARTED;
 import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED;
 import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP;
@@ -63,6 +65,7 @@
 import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_KEYGUARD_INIT;
 import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN;
 import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED;
 import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
@@ -144,12 +147,14 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.StatusBarState;
 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;
 
@@ -166,7 +171,6 @@
 import java.util.Set;
 import java.util.TimeZone;
 import java.util.concurrent.Executor;
-import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
@@ -226,7 +230,15 @@
      * Biometric authentication: Cancelling and waiting for the relevant biometric service to
      * send us the confirmation that cancellation has happened.
      */
-    private static final int BIOMETRIC_STATE_CANCELLING = 2;
+    @VisibleForTesting
+    protected static final int BIOMETRIC_STATE_CANCELLING = 2;
+
+    /**
+     * Biometric state: During cancelling we got another request to start listening, so when we
+     * receive the cancellation done signal, we should start listening again.
+     */
+    @VisibleForTesting
+    protected static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3;
 
     /**
      * Action indicating keyguard *can* start biometric authentiation.
@@ -241,12 +253,6 @@
      */
     private static final int BIOMETRIC_ACTION_UPDATE = 2;
 
-    /**
-     * Biometric state: During cancelling we got another request to start listening, so when we
-     * receive the cancellation done signal, we should start listening again.
-     */
-    private static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3;
-
     @VisibleForTesting
     public static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
     public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2;
@@ -262,6 +268,7 @@
             "com.android.settings", "com.android.settings.FallbackHome");
 
     private final Context mContext;
+    private final UserTracker mUserTracker;
     private final KeyguardUpdateMonitorLogger mLogger;
     private final boolean mIsPrimaryUser;
     private final AuthController mAuthController;
@@ -297,8 +304,8 @@
     private boolean mCredentialAttempted;
     private boolean mKeyguardGoingAway;
     private boolean mGoingToSleep;
-    private boolean mBouncerFullyShown;
-    private boolean mBouncerIsOrWillBeShowing;
+    private boolean mPrimaryBouncerFullyShown;
+    private boolean mPrimaryBouncerIsOrWillBeShowing;
     private boolean mUdfpsBouncerShowing;
     private boolean mAuthInterruptActive;
     private boolean mNeedsSlowUnlockTransition;
@@ -321,17 +328,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;
@@ -350,7 +360,8 @@
 
     private KeyguardBypassController mKeyguardBypassController;
     private List<SubscriptionInfo> mSubscriptionInfo;
-    private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+    @VisibleForTesting
+    protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
     private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
     private boolean mIsDreaming;
     private boolean mLogoutEnabled;
@@ -380,6 +391,7 @@
     protected Handler getHandler() {
         return mHandler;
     }
+
     private final Handler mHandler;
 
     private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
@@ -465,21 +477,12 @@
                     FACE_AUTH_TRIGGERED_TRUST_DISABLED);
         }
 
-        mLogger.logTrustChanged(wasTrusted, enabled, userId);
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-            if (cb != null) {
-                cb.onTrustChanged(userId);
-                if (enabled && flags != 0) {
-                    cb.onTrustGrantedWithFlags(flags, userId);
-                }
-            }
-        }
-
-        if (KeyguardUpdateMonitor.getCurrentUser() == userId) {
-            CharSequence message = null;
-            final boolean userHasTrust = getUserHasTrust(userId);
-            if (userHasTrust && trustGrantedMessages != null) {
+        if (enabled) {
+            String message = null;
+            if (KeyguardUpdateMonitor.getCurrentUser() == userId
+                    && trustGrantedMessages != null) {
+                // Show the first non-empty string provided by a trust agent OR intentionally pass
+                // an empty string through (to prevent the default trust agent string from showing)
                 for (String msg : trustGrantedMessages) {
                     message = msg;
                     if (!TextUtils.isEmpty(message)) {
@@ -488,17 +491,38 @@
                 }
             }
 
-            if (message != null) {
-                mLogger.logShowTrustGrantedMessage(message.toString());
-            }
-            for (int i = 0; i < mCallbacks.size(); i++) {
-                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-                if (cb != null) {
-                    cb.showTrustGrantedMessage(message);
+            mLogger.logTrustGrantedWithFlags(flags, userId, message);
+            if (userId == getCurrentUser()) {
+                final TrustGrantFlags trustGrantFlags = new TrustGrantFlags(flags);
+                for (int i = 0; i < mCallbacks.size(); i++) {
+                    KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+                    if (cb != null) {
+                        cb.onTrustGrantedForCurrentUser(
+                                shouldDismissKeyguardOnTrustGrantedWithCurrentUser(trustGrantFlags),
+                                trustGrantFlags, message);
+                    }
                 }
             }
         }
 
+        mLogger.logTrustChanged(wasTrusted, enabled, userId);
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onTrustChanged(userId);
+            }
+        }
+    }
+
+    /**
+     * Whether the trust granted call with its passed flags should dismiss keyguard.
+     * It's assumed that the trust was granted for the current user.
+     */
+    private boolean shouldDismissKeyguardOnTrustGrantedWithCurrentUser(TrustGrantFlags flags) {
+        final boolean isBouncerShowing = mPrimaryBouncerIsOrWillBeShowing || mUdfpsBouncerShowing;
+        return (flags.isInitiatedByUser() || flags.dismissKeyguardRequested())
+                && (mDeviceInteractive || flags.temporaryAndRenewable())
+                && (isBouncerShowing || flags.dismissKeyguardRequested());
     }
 
     @Override
@@ -704,18 +728,20 @@
 
     /**
      * 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()}
      */
     public void requestFaceAuthOnOccludingApp(boolean request) {
         mOccludingAppRequestingFace = request;
-        updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
-                FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED);
+        int action = mOccludingAppRequestingFace ? BIOMETRIC_ACTION_UPDATE : BIOMETRIC_ACTION_STOP;
+        updateFaceListeningState(action, FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED);
     }
 
     /**
      * 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)}
@@ -769,17 +795,17 @@
                 new BiometricAuthenticated(true, isStrongBiometric));
         // Update/refresh trust state only if user can skip bouncer
         if (getUserCanSkipBouncer(userId)) {
-            mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FINGERPRINT);
+            mTrustManager.unlockedByBiometricForUser(userId, FINGERPRINT);
         }
         // Don't send cancel if authentication succeeds
         mFingerprintCancelSignal = null;
+        mLogger.logFingerprintSuccess(userId, isStrongBiometric);
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
                 FACE_AUTH_UPDATED_FP_AUTHENTICATED);
-        mLogger.d("onFingerprintAuthenticated");
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricAuthenticated(userId, BiometricSourceType.FINGERPRINT,
+                cb.onBiometricAuthenticated(userId, FINGERPRINT,
                         isStrongBiometric);
             }
         }
@@ -808,10 +834,11 @@
                     + " triggered while waiting for cancellation, removing watchdog");
             mHandler.removeCallbacks(mFpCancelNotReceived);
         }
+        mLogger.d("handleFingerprintAuthFailed");
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+                cb.onBiometricAuthFailed(FINGERPRINT);
             }
         }
         if (isUdfpsSupported()) {
@@ -836,7 +863,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricAcquired(BiometricSourceType.FINGERPRINT, acquireInfo);
+                cb.onBiometricAcquired(FINGERPRINT, acquireInfo);
             }
         }
     }
@@ -849,13 +876,7 @@
             mHandler.removeCallbacks(mFpCancelNotReceived);
         }
         try {
-            final int userId;
-            try {
-                userId = ActivityManager.getService().getCurrentUser().id;
-            } catch (RemoteException e) {
-                mLogger.logException(e, "Failed to get current user id");
-                return;
-            }
+            final int userId = mUserTracker.getUserId();
             if (userId != authUserId) {
                 mLogger.logFingerprintAuthForWrongUser(authUserId);
                 return;
@@ -876,7 +897,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FINGERPRINT);
+                cb.onBiometricHelp(msgId, helpString, FINGERPRINT);
             }
         }
     }
@@ -918,17 +939,21 @@
             setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
         }
 
-        if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
-                || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED) {
-            mLogger.logRetryAfterFpError(msgId, errString);
+        if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) {
+            mLogger.logRetryAfterFpErrorWithDelay(msgId, errString, HAL_ERROR_RETRY_TIMEOUT);
             mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
         }
 
+        if (msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED) {
+            mLogger.logRetryAfterFpErrorWithDelay(msgId, errString, 0);
+            updateFingerprintListeningState(BIOMETRIC_ACTION_START);
+        }
+
         boolean lockedOutStateChanged = false;
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
             lockedOutStateChanged = !mFingerprintLockedOutPermanent;
             mFingerprintLockedOutPermanent = true;
-            mLogger.d("Fingerprint locked out - requiring strong auth");
+            mLogger.d("Fingerprint permanently locked out - requiring stronger auth");
             mLockPatternUtils.requireStrongAuth(
                     STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser());
         }
@@ -937,21 +962,23 @@
                 || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
             lockedOutStateChanged |= !mFingerprintLockedOut;
             mFingerprintLockedOut = true;
+            mLogger.d("Fingerprint temporarily locked out - requiring stronger auth");
             if (isUdfpsEnrolled()) {
                 updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
             }
             stopListeningForFace(FACE_AUTH_STOPPED_FP_LOCKED_OUT);
         }
 
+        mLogger.logFingerprintError(msgId, errString);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT);
+                cb.onBiometricError(msgId, errString, FINGERPRINT);
             }
         }
 
         if (lockedOutStateChanged) {
-            notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+            notifyLockedOutStateChanged(FINGERPRINT);
         }
     }
 
@@ -979,7 +1006,7 @@
         }
 
         if (changed) {
-            notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+            notifyLockedOutStateChanged(FINGERPRINT);
         }
     }
 
@@ -1002,7 +1029,7 @@
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
                 cb.onBiometricRunningStateChanged(isFingerprintDetectionRunning(),
-                        BiometricSourceType.FINGERPRINT);
+                        FINGERPRINT);
             }
         }
     }
@@ -1015,7 +1042,7 @@
                 new BiometricAuthenticated(true, isStrongBiometric));
         // Update/refresh trust state only if user can skip bouncer
         if (getUserCanSkipBouncer(userId)) {
-            mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE);
+            mTrustManager.unlockedByBiometricForUser(userId, FACE);
         }
         // Don't send cancel if authentication succeeds
         mFaceCancelSignal = null;
@@ -1026,7 +1053,7 @@
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
                 cb.onBiometricAuthenticated(userId,
-                        BiometricSourceType.FACE,
+                        FACE,
                         isStrongBiometric);
             }
         }
@@ -1048,7 +1075,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricAuthFailed(BiometricSourceType.FACE);
+                cb.onBiometricAuthFailed(FACE);
             }
         }
         handleFaceHelp(BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
@@ -1061,7 +1088,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo);
+                cb.onBiometricAcquired(FACE, acquireInfo);
             }
         }
     }
@@ -1073,13 +1100,7 @@
                 mLogger.d("Aborted successful auth because device is going to sleep.");
                 return;
             }
-            final int userId;
-            try {
-                userId = ActivityManager.getService().getCurrentUser().id;
-            } catch (RemoteException e) {
-                mLogger.logException(e, "Failed to get current user id");
-                return;
-            }
+            final int userId = mUserTracker.getUserId();
             if (userId != authUserId) {
                 mLogger.logFaceAuthForWrongUser(authUserId);
                 return;
@@ -1102,7 +1123,7 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FACE);
+                cb.onBiometricHelp(msgId, helpString, FACE);
             }
         }
     }
@@ -1170,12 +1191,12 @@
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
                 cb.onBiometricError(msgId, errString,
-                        BiometricSourceType.FACE);
+                        FACE);
             }
         }
 
         if (lockedOutStateChanged) {
-            notifyLockedOutStateChanged(BiometricSourceType.FACE);
+            notifyLockedOutStateChanged(FACE);
         }
     }
 
@@ -1189,7 +1210,7 @@
                 FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET), getBiometricLockoutDelay());
 
         if (changed) {
-            notifyLockedOutStateChanged(BiometricSourceType.FACE);
+            notifyLockedOutStateChanged(FACE);
         }
     }
 
@@ -1212,7 +1233,7 @@
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
                 cb.onBiometricRunningStateChanged(isFaceDetectionRunning(),
-                        BiometricSourceType.FACE);
+                        FACE);
             }
         }
     }
@@ -1353,9 +1374,40 @@
     }
 
     public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
-        return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric);
+        // StrongAuthTracker#isUnlockingWithBiometricAllowed includes
+        // STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent;
+        // however the strong auth tracker does not include the temporary lockout
+        // mFingerprintLockedOut.
+        return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric)
+                && !mFingerprintLockedOut;
     }
 
+    /**
+     * Whether fingerprint is allowed ot be used for unlocking based on the strongAuthTracker
+     * and temporary lockout state (tracked by FingerprintManager via error codes).
+     */
+    public boolean isUnlockingWithFingerprintAllowed() {
+        return isUnlockingWithBiometricAllowed(FINGERPRINT);
+    }
+
+    /**
+     * Whether the given biometric is allowed based on strongAuth & lockout states.
+     */
+    public boolean isUnlockingWithBiometricAllowed(
+            @NonNull BiometricSourceType biometricSourceType) {
+        switch (biometricSourceType) {
+            case FINGERPRINT:
+                return isUnlockingWithBiometricAllowed(true);
+            case FACE:
+                return isUnlockingWithBiometricAllowed(false);
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Whether the user locked down the device. This doesn't include device policy manager lockdown.
+     */
     public boolean isUserInLockdown(int userId) {
         return containsFlag(mStrongAuthTracker.getStrongAuthForUser(userId),
                 STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
@@ -1375,11 +1427,6 @@
         return isEncrypted || isLockDown;
     }
 
-    public boolean userNeedsStrongAuth() {
-        return mStrongAuthTracker.getStrongAuthForUser(getCurrentUser())
-                != LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
-    }
-
     private boolean containsFlag(int haystack, int needle) {
         return (haystack & needle) != 0;
     }
@@ -1392,7 +1439,8 @@
         return mStrongAuthTracker;
     }
 
-    private void notifyStrongAuthStateChanged(int userId) {
+    @VisibleForTesting
+    void notifyStrongAuthAllowedChanged(int userId) {
         Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1400,6 +1448,15 @@
                 cb.onStrongAuthStateChanged(userId);
             }
         }
+        if (userId == getCurrentUser()) {
+            FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED.setExtraInfo(
+                    mStrongAuthTracker.getStrongAuthForUser(getCurrentUser()));
+
+            // Strong auth is only reset when primary auth is used to enter the device,
+            // so we only check whether to stop biometric listening states here
+            updateBiometricListeningState(
+                    BIOMETRIC_ACTION_STOP, FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED);
+        }
     }
 
     private void notifyLockedOutStateChanged(BiometricSourceType type) {
@@ -1411,8 +1468,8 @@
             }
         }
     }
-
-    private void notifyNonStrongBiometricStateChanged(int userId) {
+    @VisibleForTesting
+    void notifyNonStrongBiometricAllowedChanged(int userId) {
         Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1420,6 +1477,16 @@
                 cb.onNonStrongBiometricAllowedChanged(userId);
             }
         }
+        if (userId == getCurrentUser()) {
+            FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED.setExtraInfo(
+                    mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(
+                            getCurrentUser()) ? -1 : 1);
+
+            // This is only reset when primary auth is used to enter the device, so we only check
+            // whether to stop biometric listening states here
+            updateBiometricListeningState(BIOMETRIC_ACTION_STOP,
+                    FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED);
+        }
     }
 
     private void dispatchErrorMessage(CharSequence message) {
@@ -1549,12 +1616,6 @@
         }
     };
 
-    private final FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback
-            = (sensorId, userId, isStrongBiometric) -> {
-                // Trigger the fingerprint success path so the bouncer can be shown
-                handleFingerprintAuthenticated(userId, isStrongBiometric);
-            };
-
     /**
      * Propagates a pointer down event to keyguard.
      */
@@ -1641,8 +1702,9 @@
                 public void onAuthenticationFailed() {
                         String reason =
                                 mKeyguardBypassController.canBypass() ? "bypass"
-                                        : mUdfpsBouncerShowing ? "udfpsBouncer" :
-                                                mBouncerFullyShown ? "bouncer" : "udfpsFpDown";
+                                        : mUdfpsBouncerShowing ? "udfpsBouncer"
+                                                : mPrimaryBouncerFullyShown ? "bouncer"
+                                                        : "udfpsFpDown";
                         requestActiveUnlock(
                                 ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
                                 "faceFailure-" + reason);
@@ -1770,16 +1832,12 @@
         }
     }
 
-    public static class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
-        private final Consumer<Integer> mStrongAuthRequiredChangedCallback;
-        private final Consumer<Integer> mNonStrongBiometricAllowedChanged;
-
-        public StrongAuthTracker(Context context,
-                Consumer<Integer> strongAuthRequiredChangedCallback,
-                Consumer<Integer> nonStrongBiometricAllowedChanged) {
+    /**
+     * Updates callbacks when strong auth requirements change.
+     */
+    public class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+        public StrongAuthTracker(Context context) {
             super(context);
-            mStrongAuthRequiredChangedCallback = strongAuthRequiredChangedCallback;
-            mNonStrongBiometricAllowedChanged = nonStrongBiometricAllowedChanged;
         }
 
         public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
@@ -1795,7 +1853,7 @@
 
         @Override
         public void onStrongAuthRequiredChanged(int userId) {
-            mStrongAuthRequiredChangedCallback.accept(userId);
+            notifyStrongAuthAllowedChanged(userId);
         }
 
         // TODO(b/247091681): Renaming the inappropriate onIsNonStrongBiometricAllowedChanged
@@ -1803,7 +1861,7 @@
         //  Strong-Auth
         @Override
         public void onIsNonStrongBiometricAllowedChanged(int userId) {
-            mNonStrongBiometricAllowedChanged.accept(userId);
+            notifyNonStrongBiometricAllowedChanged(userId);
         }
     }
 
@@ -1928,8 +1986,10 @@
     @Inject
     protected KeyguardUpdateMonitor(
             Context context,
+            UserTracker userTracker,
             @Main Looper mainLooper,
             BroadcastDispatcher broadcastDispatcher,
+            SecureSettings secureSettings,
             DumpManager dumpManager,
             @Background Executor backgroundExecutor,
             @Main Executor mainExecutor,
@@ -1959,10 +2019,10 @@
             FaceWakeUpTriggersConfig faceWakeUpTriggersConfig) {
         mContext = context;
         mSubscriptionManager = subscriptionManager;
+        mUserTracker = userTracker;
         mTelephonyListenerManager = telephonyListenerManager;
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
-        mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged,
-                this::notifyNonStrongBiometricStateChanged);
+        mStrongAuthTracker = new StrongAuthTracker(context);
         mBackgroundExecutor = backgroundExecutor;
         mBroadcastDispatcher = broadcastDispatcher;
         mInteractionJankMonitor = interactionJankMonitor;
@@ -1972,6 +2032,7 @@
         mStatusBarState = mStatusBarStateController.getState();
         mLockPatternUtils = lockPatternUtils;
         mAuthController = authController;
+        mSecureSettings = secureSettings;
         dumpManager.registerDumpable(getClass().getName(), this);
         mSensorPrivacyManager = sensorPrivacyManager;
         mActiveUnlockConfig = activeUnlockConfiguration;
@@ -2032,7 +2093,7 @@
                         handleKeyguardReset();
                         break;
                     case MSG_KEYGUARD_BOUNCER_CHANGED:
-                        handleKeyguardBouncerChanged(msg.arg1, msg.arg2);
+                        handlePrimaryBouncerChanged(msg.arg1, msg.arg2);
                         break;
                     case MSG_REPORT_EMERGENCY_CALL_ACTION:
                         handleReportEmergencyCallAction();
@@ -2190,7 +2251,7 @@
 
         TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
         mIsPrimaryUser = mUserManager.isPrimaryUser();
-        int user = ActivityManager.getCurrentUser();
+        int user = mUserTracker.getUserId();
         mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
         mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
         updateSecondaryLockscreenRequirement(user);
@@ -2214,9 +2275,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 +2348,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() {
@@ -2460,7 +2563,7 @@
                 requestOrigin,
                 extraReason, canFaceBypass
                         || mUdfpsBouncerShowing
-                        || mBouncerFullyShown
+                        || mPrimaryBouncerFullyShown
                         || mAuthController.isUdfpsFingerDown());
     }
 
@@ -2481,7 +2584,7 @@
     private boolean shouldTriggerActiveUnlock() {
         // Triggers:
         final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
-        final boolean awakeKeyguard = mBouncerFullyShown || mUdfpsBouncerShowing
+        final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mUdfpsBouncerShowing
                 || (isKeyguardVisible() && !mGoingToSleep
                 && mStatusBarState != StatusBarState.SHADE_LOCKED);
 
@@ -2494,24 +2597,17 @@
                 || !mLockPatternUtils.isSecure(user);
 
         // Don't trigger active unlock if fp is locked out
-        final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
+        final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
 
         // Don't trigger active unlock if primary auth is required
-        final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
-        final boolean isLockDown =
-                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
-                        || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
-        final boolean isEncryptedOrTimedOut =
-                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT)
-                        || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
+        final boolean primaryAuthRequired = !isUnlockingWithBiometricAllowed(true);
 
         final boolean shouldTriggerActiveUnlock =
                 (mAuthInterruptActive || triggerActiveUnlockForAssistant || awakeKeyguard)
                         && !mSwitchingUser
                         && !userCanDismissLockScreen
-                        && !fpLockedout
-                        && !isLockDown
-                        && !isEncryptedOrTimedOut
+                        && !fpLockedOut
+                        && !primaryAuthRequired
                         && !mKeyguardGoingAway
                         && !mSecureCameraLaunched;
 
@@ -2523,9 +2619,8 @@
                         shouldTriggerActiveUnlock,
                         awakeKeyguard,
                         mAuthInterruptActive,
-                        isEncryptedOrTimedOut,
-                        fpLockedout,
-                        isLockDown,
+                        fpLockedOut,
+                        primaryAuthRequired,
                         mSwitchingUser,
                         triggerActiveUnlockForAssistant,
                         userCanDismissLockScreen));
@@ -2560,7 +2655,7 @@
         final boolean shouldListenKeyguardState =
                 isKeyguardVisible()
                         || !mDeviceInteractive
-                        || (mBouncerIsOrWillBeShowing && !mKeyguardGoingAway)
+                        || (mPrimaryBouncerIsOrWillBeShowing && !mKeyguardGoingAway)
                         || mGoingToSleep
                         || shouldListenForFingerprintAssistant
                         || (mKeyguardOccluded && mIsDreaming)
@@ -2577,32 +2672,38 @@
                         && !fingerprintDisabledForUser
                         && (!mKeyguardGoingAway || !mDeviceInteractive)
                         && mIsPrimaryUser
-                        && biometricEnabledForUser;
-
+                        && biometricEnabledForUser
+                        && !isUserInLockdown(user);
+        final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
+        final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
         final boolean shouldListenBouncerState =
-                !(mFingerprintLockedOut && mBouncerIsOrWillBeShowing && mCredentialAttempted);
+                !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
 
-        final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
         final boolean shouldListenUdfpsState = !isUdfps
                 || (!userCanSkipBouncer
-                    && !isEncryptedOrLockdownForUser
-                    && userDoesNotHaveTrust);
+                && !strongerAuthRequired
+                && userDoesNotHaveTrust);
 
-        final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
-                && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
+        boolean shouldListenSideFpsState = true;
+        if (isSideFps) {
+            shouldListenSideFpsState =
+                    mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true;
+        }
 
+        boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
+                && shouldListenBouncerState && shouldListenUdfpsState
+                && shouldListenSideFpsState;
         maybeLogListenerModelData(
                 new KeyguardFingerprintListenModel(
                     System.currentTimeMillis(),
                     user,
                     shouldListen,
                     biometricEnabledForUser,
-                    mBouncerIsOrWillBeShowing,
+                        mPrimaryBouncerIsOrWillBeShowing,
                     userCanSkipBouncer,
                     mCredentialAttempted,
                     mDeviceInteractive,
                     mIsDreaming,
-                    isEncryptedOrLockdownForUser,
                     fingerprintDisabledForUser,
                     mFingerprintLockedOut,
                     mGoingToSleep,
@@ -2611,7 +2712,9 @@
                     mKeyguardOccluded,
                     mOccludingAppRequestingFp,
                     mIsPrimaryUser,
+                    shouldListenSideFpsState,
                     shouldListenForFingerprintAssistant,
+                    strongerAuthRequired,
                     mSwitchingUser,
                     isUdfps,
                     userDoesNotHaveTrust));
@@ -2632,63 +2735,47 @@
         final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive
                 && !statusBarShadeLocked;
         final int user = getCurrentUser();
-        final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
-        final boolean isLockDown =
-                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
-                        || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
-        final boolean isEncryptedOrTimedOut =
-                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT)
-                        || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
-
-        // TODO: always disallow when fp is already locked out?
-        final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
-
+        final boolean faceAuthAllowed = isUnlockingWithBiometricAllowed(FACE);
         final boolean canBypass = mKeyguardBypassController != null
                 && mKeyguardBypassController.canBypass();
         // There's no reason to ask the HAL for authentication when the user can dismiss the
-        // bouncer, unless we're bypassing and need to auto-dismiss the lock screen even when
-        // TrustAgents or biometrics are keeping the device unlocked.
-        final boolean becauseCannotSkipBouncer = !getUserCanSkipBouncer(user) || canBypass;
+        // bouncer because the user is trusted, unless we're bypassing and need to auto-dismiss
+        // the lock screen even when TrustAgents are keeping the device unlocked.
+        final boolean userNotTrustedOrDetectionIsNeeded = !getUserHasTrust(user) || canBypass;
 
-        // Scan even when encrypted or timeout to show a preemptive bouncer when bypassing.
-        // Lock-down mode shouldn't scan, since it is more explicit.
-        boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass
-                && !mBouncerFullyShown);
+        // If the device supports face detection (without authentication), if bypass is enabled,
+        // allow face detection to happen even if stronger auth is required. When face is detected,
+        // we show the bouncer. However, if the user manually locked down the device themselves,
+        // never attempt to detect face.
+        final boolean supportsDetect = !mFaceSensorProperties.isEmpty()
+                && mFaceSensorProperties.get(0).supportsFaceDetection
+                && canBypass && !mPrimaryBouncerIsOrWillBeShowing
+                && !isUserInLockdown(user);
+        final boolean faceAuthAllowedOrDetectionIsNeeded = faceAuthAllowed || supportsDetect;
 
-        // If the device supports face detection (without authentication) and bypass is enabled,
-        // allow face scanning to happen if the device is in lockdown mode.
-        // Otherwise, prevent scanning.
-        final boolean supportsDetectOnly = !mFaceSensorProperties.isEmpty()
-                && canBypass
-                && mFaceSensorProperties.get(0).supportsFaceDetection;
-        if (isLockDown && !supportsDetectOnly) {
-            strongAuthAllowsScanning = false;
-        }
-
-        // If the face has recently been authenticated do not attempt to authenticate again.
-        final boolean faceAuthenticated = getIsFaceAuthenticated();
+        // If the face or fp has recently been authenticated do not attempt to authenticate again.
+        final boolean faceAndFpNotAuthenticated = !getUserUnlockedWithBiometric(user);
         final boolean faceDisabledForUser = isFaceDisabled(user);
         final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
         final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
-        final boolean fpOrFaceIsLockedOut = isFaceLockedOut() || fpLockedout;
+        final boolean isUdfpsFingerDown = mAuthController.isUdfpsFingerDown();
 
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
         final boolean shouldListen =
-                (mBouncerFullyShown
+                (mPrimaryBouncerFullyShown
                         || mAuthInterruptActive
                         || mOccludingAppRequestingFace
                         || awakeKeyguard
                         || shouldListenForFaceAssistant
-                        || mAuthController.isUdfpsFingerDown()
+                        || isUdfpsFingerDown
                         || mUdfpsBouncerShowing)
-                && !mSwitchingUser && !faceDisabledForUser && becauseCannotSkipBouncer
+                && !mSwitchingUser && !faceDisabledForUser && userNotTrustedOrDetectionIsNeeded
                 && !mKeyguardGoingAway && biometricEnabledForUser
-                && strongAuthAllowsScanning && mIsPrimaryUser
+                && faceAuthAllowedOrDetectionIsNeeded && mIsPrimaryUser
                 && (!mSecureCameraLaunched || mOccludingAppRequestingFace)
-                && !faceAuthenticated
-                && !mGoingToSleep
-                && !fpOrFaceIsLockedOut;
+                && faceAndFpNotAuthenticated
+                && !mGoingToSleep;
 
         // Aggregate relevant fields for debug logging.
         maybeLogListenerModelData(
@@ -2697,23 +2784,24 @@
                     user,
                     shouldListen,
                     mAuthInterruptActive,
-                    becauseCannotSkipBouncer,
                     biometricEnabledForUser,
-                    mBouncerFullyShown,
-                    faceAuthenticated,
+                    mPrimaryBouncerFullyShown,
+                    faceAndFpNotAuthenticated,
+                    faceAuthAllowed,
                     faceDisabledForUser,
                     isFaceLockedOut(),
-                    fpLockedout,
                     mGoingToSleep,
                     awakeKeyguard,
                     mKeyguardGoingAway,
                     shouldListenForFaceAssistant,
                     mOccludingAppRequestingFace,
                     mIsPrimaryUser,
-                    strongAuthAllowsScanning,
                     mSecureCameraLaunched,
+                    supportsDetect,
                     mSwitchingUser,
-                    mUdfpsBouncerShowing));
+                    mUdfpsBouncerShowing,
+                    isUdfpsFingerDown,
+                    userNotTrustedOrDetectionIsNeeded));
 
         return shouldListen;
     }
@@ -2760,15 +2848,22 @@
             // Waiting for restart via handleFingerprintError().
             return;
         }
-        mLogger.v("startListeningForFingerprint()");
 
         if (unlockPossible) {
             mFingerprintCancelSignal = new CancellationSignal();
 
-            if (isEncryptedOrLockdown(userId)) {
-                mFpm.detectFingerprint(mFingerprintCancelSignal, mFingerprintDetectionCallback,
+            if (!isUnlockingWithFingerprintAllowed()) {
+                mLogger.v("startListeningForFingerprint - detect");
+                mFpm.detectFingerprint(
+                        mFingerprintCancelSignal,
+                        (sensorId, user, isStrongBiometric) -> {
+                            mLogger.d("fingerprint detected");
+                            // Trigger the fingerprint success path so the bouncer can be shown
+                            handleFingerprintAuthenticated(user, isStrongBiometric);
+                        },
                         userId);
             } else {
+                mLogger.v("startListeningForFingerprint - authenticate");
                 mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
                         mFingerprintAuthenticationCallback, null /* handler */,
                         FingerprintManager.SENSOR_ID_ANY, userId, 0 /* flags */);
@@ -2806,9 +2901,11 @@
             // This would need to be updated for multi-sensor devices
             final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
                     && mFaceSensorProperties.get(0).supportsFaceDetection;
-            if (isEncryptedOrLockdown(userId) && supportsFaceDetection) {
+            if (!isUnlockingWithBiometricAllowed(FACE) && supportsFaceDetection) {
+                mLogger.v("startListeningForFace - detect");
                 mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
             } else {
+                mLogger.v("startListeningForFace - authenticate");
                 final boolean isBypassEnabled = mKeyguardBypassController != null
                         && mKeyguardBypassController.isBypassEnabled();
                 mFaceManager.authenticate(null /* crypto */, mFaceCancelSignal,
@@ -2985,11 +3082,15 @@
             }
         }
 
+        // Immediately stop previous biometric listening states.
+        // Resetting lockout states updates the biometric listening states.
         if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
+            stopListeningForFace(FACE_AUTH_UPDATED_USER_SWITCHING);
             handleFaceLockoutReset(mFaceManager.getLockoutModeForUser(
                     mFaceSensorProperties.get(0).sensorId, userId));
         }
         if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) {
+            stopListeningForFingerprint();
             handleFingerprintLockoutReset(mFpm.getLockoutModeForUser(
                     mFingerprintSensorProperties.get(0).sensorId, userId));
         }
@@ -3229,17 +3330,19 @@
     /**
      * Handle {@link #MSG_KEYGUARD_BOUNCER_CHANGED}
      *
-     * @see #sendKeyguardBouncerChanged(boolean, boolean)
+     * @see #sendPrimaryBouncerChanged(boolean, boolean)
      */
-    private void handleKeyguardBouncerChanged(int bouncerIsOrWillBeShowing, int bouncerFullyShown) {
+    private void handlePrimaryBouncerChanged(int primaryBouncerIsOrWillBeShowing,
+            int primaryBouncerFullyShown) {
         Assert.isMainThread();
-        final boolean wasBouncerIsOrWillBeShowing = mBouncerIsOrWillBeShowing;
-        final boolean wasBouncerFullyShown = mBouncerFullyShown;
-        mBouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing == 1;
-        mBouncerFullyShown = bouncerFullyShown == 1;
-        mLogger.logKeyguardBouncerChanged(mBouncerIsOrWillBeShowing, mBouncerFullyShown);
+        final boolean wasPrimaryBouncerIsOrWillBeShowing = mPrimaryBouncerIsOrWillBeShowing;
+        final boolean wasPrimaryBouncerFullyShown = mPrimaryBouncerFullyShown;
+        mPrimaryBouncerIsOrWillBeShowing = primaryBouncerIsOrWillBeShowing == 1;
+        mPrimaryBouncerFullyShown = primaryBouncerFullyShown == 1;
+        mLogger.logPrimaryKeyguardBouncerChanged(mPrimaryBouncerIsOrWillBeShowing,
+                mPrimaryBouncerFullyShown);
 
-        if (mBouncerFullyShown) {
+        if (mPrimaryBouncerFullyShown) {
             // If the bouncer is shown, always clear this flag. This can happen in the following
             // situations: 1) Default camera with SHOW_WHEN_LOCKED is not chosen yet. 2) Secure
             // camera requests dismiss keyguard (tapping on photos for example). When these happen,
@@ -3249,18 +3352,18 @@
             mCredentialAttempted = false;
         }
 
-        if (wasBouncerIsOrWillBeShowing != mBouncerIsOrWillBeShowing) {
+        if (wasPrimaryBouncerIsOrWillBeShowing != mPrimaryBouncerIsOrWillBeShowing) {
             for (int i = 0; i < mCallbacks.size(); i++) {
                 KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
                 if (cb != null) {
-                    cb.onKeyguardBouncerStateChanged(mBouncerIsOrWillBeShowing);
+                    cb.onKeyguardBouncerStateChanged(mPrimaryBouncerIsOrWillBeShowing);
                 }
             }
             updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         }
 
-        if (wasBouncerFullyShown != mBouncerFullyShown) {
-            if (mBouncerFullyShown) {
+        if (wasPrimaryBouncerFullyShown != mPrimaryBouncerFullyShown) {
+            if (mPrimaryBouncerFullyShown) {
                 requestActiveUnlock(
                         ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
                         "bouncerFullyShown");
@@ -3268,7 +3371,7 @@
             for (int i = 0; i < mCallbacks.size(); i++) {
                 KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
                 if (cb != null) {
-                    cb.onKeyguardBouncerFullyShowingChanged(mBouncerFullyShown);
+                    cb.onKeyguardBouncerFullyShowingChanged(mPrimaryBouncerFullyShown);
                 }
             }
             updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
@@ -3394,7 +3497,7 @@
     @AnyThread
     public void setSwitchingUser(boolean switching) {
         mSwitchingUser = switching;
-        // Since this comes in on a binder thread, we need to post if first
+        // Since this comes in on a binder thread, we need to post it first
         mHandler.post(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
                 FACE_AUTH_UPDATED_USER_SWITCHING));
     }
@@ -3419,14 +3522,15 @@
     }
 
     /**
-     * @see #handleKeyguardBouncerChanged(int, int)
+     * @see #handlePrimaryBouncerChanged(int, int)
      */
-    public void sendKeyguardBouncerChanged(boolean bouncerIsOrWillBeShowing,
-            boolean bouncerFullyShown) {
-        mLogger.logSendKeyguardBouncerChanged(bouncerIsOrWillBeShowing, bouncerFullyShown);
+    public void sendPrimaryBouncerChanged(boolean primaryBouncerIsOrWillBeShowing,
+            boolean primaryBouncerFullyShown) {
+        mLogger.logSendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
+                primaryBouncerFullyShown);
         Message message = mHandler.obtainMessage(MSG_KEYGUARD_BOUNCER_CHANGED);
-        message.arg1 = bouncerIsOrWillBeShowing ? 1 : 0;
-        message.arg2 = bouncerFullyShown ? 1 : 0;
+        message.arg1 = primaryBouncerIsOrWillBeShowing ? 1 : 0;
+        message.arg2 = primaryBouncerFullyShown ? 1 : 0;
         message.sendToTarget();
     }
 
@@ -3485,8 +3589,8 @@
         Assert.isMainThread();
         mUserFingerprintAuthenticated.clear();
         mUserFaceAuthenticated.clear();
-        mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FINGERPRINT, unlockedUser);
-        mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, unlockedUser);
+        mTrustManager.clearAllBiometricRecognized(FINGERPRINT, unlockedUser);
+        mTrustManager.clearAllBiometricRecognized(FACE, unlockedUser);
         mLogger.d("clearBiometricRecognized");
 
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -3712,6 +3816,11 @@
             mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);
         }
 
+        if (mSfpsRequireScreenOnToAuthPrefObserver != null) {
+            mContext.getContentResolver().unregisterContentObserver(
+                    mSfpsRequireScreenOnToAuthPrefObserver);
+        }
+
         try {
             ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
         } catch (RemoteException e) {
@@ -3755,7 +3864,7 @@
             pw.println("    " + subId + "=" + mServiceStates.get(subId));
         }
         if (mFpm != null && mFpm.isHardwareDetected()) {
-            final int userId = ActivityManager.getCurrentUser();
+            final int userId = mUserTracker.getUserId();
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
             BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
             pw.println("  Fingerprint state (user=" + userId + ")");
@@ -3781,13 +3890,21 @@
             if (isUdfpsSupported()) {
                 pw.println("        udfpsEnrolled=" + isUdfpsEnrolled());
                 pw.println("        shouldListenForUdfps=" + shouldListenForFingerprint(true));
-                pw.println("        mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing);
+                pw.println("        mPrimaryBouncerIsOrWillBeShowing="
+                        + mPrimaryBouncerIsOrWillBeShowing);
                 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()) {
-            final int userId = ActivityManager.getCurrentUser();
+            final int userId = mUserTracker.getUserId();
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
             BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
             pw.println("  Face authentication state (user=" + userId + ")");
@@ -3802,11 +3919,13 @@
             pw.println("    listening: actual=" + mFaceRunningState
                     + " expected=(" + (shouldListenForFace() ? 1 : 0));
             pw.println("    strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
+            pw.println("    isNonStrongBiometricAllowedAfterIdleTimeout="
+                    + mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(userId));
             pw.println("    trustManaged=" + getUserTrustIsManaged(userId));
             pw.println("    mFaceLockedOutPermanent=" + mFaceLockedOutPermanent);
             pw.println("    enabledByUser=" + mBiometricEnabledForUser.get(userId));
             pw.println("    mSecureCameraLaunched=" + mSecureCameraLaunched);
-            pw.println("    mBouncerFullyShown=" + mBouncerFullyShown);
+            pw.println("    mPrimaryBouncerFullyShown=" + mPrimaryBouncerFullyShown);
             pw.println("    mNeedsSlowUnlockTransition=" + mNeedsSlowUnlockTransition);
         }
         mListenModels.print(pw);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index c06e1dc..1d58fc9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -19,6 +19,7 @@
 import android.telephony.TelephonyManager;
 import android.view.WindowManagerPolicyConstants;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -174,14 +175,14 @@
     public void onTrustManagedChanged(int userId) { }
 
     /**
-     * Called after trust was granted with non-zero flags.
+     * Called after trust was granted.
+     * @param dismissKeyguard whether the keyguard should be dismissed as a result of the
+     *                        trustGranted
+     * @param message optional message the trust agent has provided to show that should indicate
+     *                why trust was granted.
      */
-    public void onTrustGrantedWithFlags(int flags, int userId) { }
-
-    /**
-     * Called when setting the trust granted message.
-     */
-    public void showTrustGrantedMessage(@Nullable CharSequence message) { }
+    public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+            @NonNull TrustGrantFlags flags, @Nullable String message) { }
 
     /**
      * Called when a biometric has been acquired.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 90f0446..6c3c246 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -50,16 +50,11 @@
 
     /**
      * Resets the state of Keyguard View.
-     * @param hideBouncerWhenShowing
+     * @param hideBouncerWhenShowing when true, hides the primary and alternate bouncers if showing.
      */
     void reset(boolean hideBouncerWhenShowing);
 
     /**
-     * Stop showing any alternate auth methods.
-     */
-    void resetAlternateAuth(boolean forceUpdateScrim);
-
-    /**
      * Called when the device started going to sleep.
      */
     default void onStartedGoingToSleep() {};
@@ -156,20 +151,24 @@
     void notifyKeyguardAuthenticated(boolean strongAuth);
 
     /**
-     * Shows the Bouncer.
-     *
+     * Shows the primary bouncer.
      */
-    void showBouncer(boolean scrimmed);
+    void showPrimaryBouncer(boolean scrimmed);
 
     /**
-     * Returns {@code true} when the bouncer is currently showing
+     * When the primary bouncer is fully visible or is showing but animation didn't finish yet.
+     */
+    boolean primaryBouncerIsOrWillBeShowing();
+
+    /**
+     * Returns {@code true} when the primary bouncer or alternate bouncer is currently showing
      */
     boolean isBouncerShowing();
 
     /**
-     * When bouncer is fully visible or it is showing but animation didn't finish yet.
+     * Stop showing the alternate bouncer, if showing.
      */
-    boolean bouncerIsOrWillBeShowing();
+    void hideAlternateBouncer(boolean forceUpdateScrim);
 
     // TODO: Deprecate registerStatusBar in KeyguardViewController interface. It is currently
     //  only used for testing purposes in StatusBarKeyguardViewManager, and it prevents us from
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 0a82968..34a5ef7 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -158,6 +158,10 @@
         return mLockIconCenter.y - mRadius;
     }
 
+    float getLocationBottom() {
+        return mLockIconCenter.y + mRadius;
+    }
+
     /**
      * Updates the icon its default state where no visual is shown.
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 8fbbd38..cf246b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -282,6 +282,10 @@
         return mView.getLocationTop();
     }
 
+    public float getBottom() {
+        return mView.getLocationBottom();
+    }
+
     private void updateVisibility() {
         if (mCancelDelayedUpdateVisibilityRunnable != null) {
             mCancelDelayedUpdateVisibilityRunnable.run();
@@ -697,7 +701,7 @@
                 "lock-screen-lock-icon-longpress",
                 TOUCH_VIBRATION_ATTRIBUTES);
 
-        mKeyguardViewController.showBouncer(/* scrim */ true);
+        mKeyguardViewController.showPrimaryBouncer(/* scrim */ true);
     }
 
 
diff --git a/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java b/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java
index 777bd19..3392a1c 100644
--- a/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java
+++ b/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java
@@ -23,9 +23,10 @@
     /** Set text color for the next security message. */
     default void setNextMessageColor(ColorStateList colorState) {}
 
-    void setMessage(CharSequence msg);
-
-    void setMessage(int resId);
+    /**
+     * Sets a message to the underlying text view.
+     */
+    void setMessage(CharSequence msg, boolean animate);
 
     void formatMessage(int resId, Object... formatArgs);
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java b/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java
new file mode 100644
index 0000000..d33732c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java
@@ -0,0 +1,98 @@
+/*
+ * 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.keyguard;
+
+import android.service.trust.TrustAgentService;
+
+import java.util.Objects;
+
+/**
+ * Translating {@link android.service.trust.TrustAgentService.GrantTrustFlags} to a more
+ * parsable object. These flags are requested by a TrustAgent.
+ */
+public class TrustGrantFlags {
+    final int mFlags;
+
+    public TrustGrantFlags(int flags) {
+        this.mFlags = flags;
+    }
+
+    /** {@link TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER} */
+    public boolean isInitiatedByUser() {
+        return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
+    }
+
+    /**
+     * Trust agent is requesting to dismiss the keyguard.
+     * See {@link TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD}.
+     *
+     * This does not guarantee that the keyguard is dismissed.
+     * KeyguardUpdateMonitor makes the final determination whether the keyguard should be dismissed.
+     * {@link KeyguardUpdateMonitorCallback#onTrustGrantedForCurrentUser(
+     *      boolean, TrustGrantFlags, String).
+     */
+    public boolean dismissKeyguardRequested() {
+        return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
+    }
+
+    /** {@link TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE} */
+    public boolean temporaryAndRenewable() {
+        return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0;
+    }
+
+    /** {@link TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE} */
+    public boolean displayMessage() {
+        return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof TrustGrantFlags)) {
+            return false;
+        }
+
+        return ((TrustGrantFlags) o).mFlags == this.mFlags;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(mFlags);
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        sb.append(mFlags);
+        sb.append("]=");
+
+        if (isInitiatedByUser()) {
+            sb.append("initiatedByUser|");
+        }
+        if (dismissKeyguardRequested()) {
+            sb.append("dismissKeyguard|");
+        }
+        if (temporaryAndRenewable()) {
+            sb.append("temporaryAndRenewable|");
+        }
+        if (displayMessage()) {
+            sb.append("displayMessage|");
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 9a0bfc1..122c521 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -29,24 +29,25 @@
 import android.util.DisplayMetrics;
 import android.view.LayoutInflater;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
-import androidx.lifecycle.Observer;
 
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManager.DockEventListener;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.settings.CurrentUserObservable;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.settings.UserTracker;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
 import javax.inject.Inject;
@@ -69,7 +70,8 @@
     private final ContentResolver mContentResolver;
     private final SettingsWrapper mSettingsWrapper;
     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
-    private final CurrentUserObservable mCurrentUserObservable;
+    private final UserTracker mUserTracker;
+    private final Executor mMainExecutor;
 
     /**
      * Observe settings changes to know when to switch the clock face.
@@ -80,7 +82,7 @@
                 public void onChange(boolean selfChange, Collection<Uri> uris,
                         int flags, int userId) {
                     if (Objects.equals(userId,
-                            mCurrentUserObservable.getCurrentUser().getValue())) {
+                            mUserTracker.getUserId())) {
                         reload();
                     }
                 }
@@ -89,7 +91,13 @@
     /**
      * Observe user changes and react by potentially loading the custom clock for the new user.
      */
-    private final Observer<Integer> mCurrentUserObserver = (newUserId) -> reload();
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    reload();
+                }
+            };
 
     private final PluginManager mPluginManager;
     @Nullable private final DockManager mDockManager;
@@ -129,22 +137,24 @@
     @Inject
     public ClockManager(Context context, LayoutInflater layoutInflater,
             PluginManager pluginManager, SysuiColorExtractor colorExtractor,
-            @Nullable DockManager dockManager, BroadcastDispatcher broadcastDispatcher) {
+            @Nullable DockManager dockManager, UserTracker userTracker,
+            @Main Executor mainExecutor) {
         this(context, layoutInflater, pluginManager, colorExtractor,
-                context.getContentResolver(), new CurrentUserObservable(broadcastDispatcher),
+                context.getContentResolver(), userTracker, mainExecutor,
                 new SettingsWrapper(context.getContentResolver()), dockManager);
     }
 
     @VisibleForTesting
     ClockManager(Context context, LayoutInflater layoutInflater,
             PluginManager pluginManager, SysuiColorExtractor colorExtractor,
-            ContentResolver contentResolver, CurrentUserObservable currentUserObservable,
+            ContentResolver contentResolver, UserTracker userTracker, Executor mainExecutor,
             SettingsWrapper settingsWrapper, DockManager dockManager) {
         mContext = context;
         mPluginManager = pluginManager;
         mContentResolver = contentResolver;
         mSettingsWrapper = settingsWrapper;
-        mCurrentUserObservable = currentUserObservable;
+        mUserTracker = userTracker;
+        mMainExecutor = mainExecutor;
         mDockManager = dockManager;
         mPreviewClocks = new AvailableClocks();
 
@@ -226,7 +236,7 @@
         mContentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.DOCKED_CLOCK_FACE),
                 false, mContentObserver, UserHandle.USER_ALL);
-        mCurrentUserObservable.getCurrentUser().observeForever(mCurrentUserObserver);
+        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
         if (mDockManager != null) {
             mDockManager.addListener(mDockEventListener);
         }
@@ -235,7 +245,7 @@
     private void unregister() {
         mPluginManager.removePluginListener(mPreviewClocks);
         mContentResolver.unregisterContentObserver(mContentObserver);
-        mCurrentUserObservable.getCurrentUser().removeObserver(mCurrentUserObserver);
+        mUserTracker.removeCallback(mUserChangedCallback);
         if (mDockManager != null) {
             mDockManager.removeListener(mDockEventListener);
         }
@@ -363,7 +373,7 @@
             ClockPlugin plugin = null;
             if (ClockManager.this.isDocked()) {
                 final String name = mSettingsWrapper.getDockedClockFace(
-                        mCurrentUserObservable.getCurrentUser().getValue());
+                        mUserTracker.getUserId());
                 if (name != null) {
                     plugin = mClocks.get(name);
                     if (plugin != null) {
@@ -372,7 +382,7 @@
                 }
             }
             final String name = mSettingsWrapper.getLockScreenCustomClockFace(
-                    mCurrentUserObservable.getCurrentUser().getValue());
+                    mUserTracker.getUserId());
             if (name != null) {
                 plugin = mClocks.get(name);
             }
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 9767313..676979c 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -17,17 +17,20 @@
 package com.android.keyguard.dagger;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.os.Handler;
 import android.os.UserHandle;
+import android.view.LayoutInflater;
 
+import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.shared.clocks.ClockRegistry;
 import com.android.systemui.shared.clocks.DefaultClockProvider;
-import com.android.systemui.shared.plugins.PluginManager;
 
 import dagger.Module;
 import dagger.Provides;
@@ -42,14 +45,16 @@
             @Application Context context,
             PluginManager pluginManager,
             @Main Handler handler,
-            DefaultClockProvider defaultClockProvider,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            @Main Resources resources,
+            LayoutInflater layoutInflater) {
         return new ClockRegistry(
                 context,
                 pluginManager,
                 handler,
                 featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
                 UserHandle.USER_ALL,
-                defaultClockProvider);
+                new DefaultClockProvider(context, layoutInflater, resources),
+                context.getString(R.string.lockscreen_clock_id_fallback));
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
index 49e9783..ef067b8 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
@@ -16,7 +16,7 @@
 
 package com.android.keyguard.dagger;
 
-import static com.android.systemui.biometrics.SidefpsControllerKt.hasSideFpsSensor;
+import static com.android.systemui.biometrics.SideFpsControllerKt.hasSideFpsSensor;
 
 import android.annotation.Nullable;
 import android.hardware.fingerprint.FingerprintManager;
@@ -27,7 +27,7 @@
 import com.android.keyguard.KeyguardSecurityContainer;
 import com.android.keyguard.KeyguardSecurityViewFlipper;
 import com.android.systemui.R;
-import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.biometrics.SideFpsController;
 import com.android.systemui.dagger.qualifiers.RootView;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 
@@ -70,12 +70,12 @@
         return containerView.findViewById(R.id.view_flipper);
     }
 
-    /** Provides {@link SidefpsController} if the device has the side fingerprint sensor. */
+    /** Provides {@link SideFpsController} if the device has the side fingerprint sensor. */
     @Provides
     @KeyguardBouncerScope
-    static Optional<SidefpsController> providesOptionalSidefpsController(
+    static Optional<SideFpsController> providesOptionalSidefpsController(
             @Nullable FingerprintManager fingerprintManager,
-            Provider<SidefpsController> sidefpsControllerProvider) {
+            Provider<SideFpsController> sidefpsControllerProvider) {
         if (!hasSideFpsSensor(fingerprintManager)) {
             return Optional.empty();
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
index 8fc8600..a7d4455 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
@@ -21,10 +21,7 @@
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherControllerImpl;
 
-import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
@@ -50,10 +47,4 @@
     static StatusBarUserSwitcherContainer getUserSwitcherContainer(KeyguardStatusBarView view) {
         return view.findViewById(R.id.user_switcher_container);
     }
-
-    /** */
-    @Binds
-    @KeyguardStatusBarViewScope
-    abstract StatusBarUserSwitcherController bindStatusBarUserSwitcherController(
-            StatusBarUserSwitcherControllerImpl controller);
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
index 6264ce7..2bb75aa 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
@@ -17,7 +17,7 @@
 package com.android.keyguard.logging
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.dagger.BiometricMessagesLog
+import com.android.systemui.log.dagger.BiometricLog
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel.DEBUG
 import javax.inject.Inject
@@ -26,7 +26,7 @@
 @SysUISingleton
 class FaceMessageDeferralLogger
 @Inject
-constructor(@BiometricMessagesLog private val logBuffer: LogBuffer) :
+constructor(@BiometricLog private val logBuffer: LogBuffer) :
     BiometricMessageDeferralLogger(logBuffer, "FaceMessageDeferralLogger")
 
 open class BiometricMessageDeferralLogger(
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
new file mode 100644
index 0000000..bc0bd8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.keyguard.logging
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.BiometricLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_ONLY_WAKE
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_SHOW_BOUNCER
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "BiometricUnlockLogger"
+
+/** Helper class for logging for [com.android.systemui.statusbar.phone.BiometricUnlockController] */
+@SysUISingleton
+class BiometricUnlockLogger @Inject constructor(@BiometricLog private val logBuffer: LogBuffer) {
+    fun i(@CompileTimeConstant msg: String) = log(msg, INFO)
+    fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+    fun log(@CompileTimeConstant msg: String, level: LogLevel) = logBuffer.log(TAG, level, msg)
+
+    fun logStartWakeAndUnlock(mode: Int) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { int1 = mode },
+            { "startWakeAndUnlock(${wakeAndUnlockModeToString(int1)})" }
+        )
+    }
+
+    fun logUdfpsAttemptThresholdMet(consecutiveFailedAttempts: Int) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { int1 = consecutiveFailedAttempts },
+            { "udfpsAttemptThresholdMet consecutiveFailedAttempts=$int1" }
+        )
+    }
+
+    fun logCalculateModeForFingerprintUnlockingAllowed(
+        deviceInteractive: Boolean,
+        keyguardShowing: Boolean,
+        deviceDreaming: Boolean
+    ) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                bool1 = deviceInteractive
+                bool2 = keyguardShowing
+                bool3 = deviceDreaming
+            },
+            {
+                "calculateModeForFingerprint unlockingAllowed=true" +
+                    " deviceInteractive=$bool1 isKeyguardShowing=$bool2" +
+                    " deviceDreaming=$bool3"
+            }
+        )
+    }
+
+    fun logCalculateModeForFingerprintUnlockingNotAllowed(
+        strongBiometric: Boolean,
+        strongAuthFlags: Int,
+        nonStrongBiometricAllowed: Boolean,
+        deviceInteractive: Boolean,
+        keyguardShowing: Boolean
+    ) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                int1 = strongAuthFlags
+                bool1 = strongBiometric
+                bool2 = nonStrongBiometricAllowed
+                bool3 = deviceInteractive
+                bool4 = keyguardShowing
+            },
+            {
+                "calculateModeForFingerprint unlockingAllowed=false" +
+                    " strongBiometric=$bool1 strongAuthFlags=$int1" +
+                    " nonStrongBiometricAllowed=$bool2" +
+                    " deviceInteractive=$bool3 isKeyguardShowing=$bool4"
+            }
+        )
+    }
+
+    fun logCalculateModeForPassiveAuthUnlockingAllowed(
+        deviceInteractive: Boolean,
+        keyguardShowing: Boolean,
+        deviceDreaming: Boolean,
+        bypass: Boolean
+    ) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                bool1 = deviceInteractive
+                bool2 = keyguardShowing
+                bool3 = deviceDreaming
+                bool4 = bypass
+            },
+            {
+                "calculateModeForPassiveAuth unlockingAllowed=true" +
+                    " deviceInteractive=$bool1 isKeyguardShowing=$bool2" +
+                    " deviceDreaming=$bool3 bypass=$bool4"
+            }
+        )
+    }
+
+    fun logCalculateModeForPassiveAuthUnlockingNotAllowed(
+        strongBiometric: Boolean,
+        strongAuthFlags: Int,
+        nonStrongBiometricAllowed: Boolean,
+        deviceInteractive: Boolean,
+        keyguardShowing: Boolean,
+        bypass: Boolean
+    ) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                int1 = if (strongBiometric) 1 else 0
+                int2 = strongAuthFlags
+                bool1 = nonStrongBiometricAllowed
+                bool2 = deviceInteractive
+                bool3 = keyguardShowing
+                bool4 = bypass
+            },
+            {
+                "calculateModeForPassiveAuth unlockingAllowed=false" +
+                    " strongBiometric=${int1 == 1}" +
+                    " strongAuthFlags=$int2 nonStrongBiometricAllowed=$bool1" +
+                    " deviceInteractive=$bool2 isKeyguardShowing=$bool3 bypass=$bool4"
+            }
+        )
+    }
+}
+
+private fun wakeAndUnlockModeToString(mode: Int): String {
+    return when (mode) {
+        MODE_NONE -> "MODE_NONE"
+        MODE_WAKE_AND_UNLOCK -> "MODE_WAKE_AND_UNLOCK"
+        MODE_WAKE_AND_UNLOCK_PULSING -> "MODE_WAKE_AND_UNLOCK_PULSING"
+        MODE_SHOW_BOUNCER -> "MODE_SHOW_BOUNCER"
+        MODE_ONLY_WAKE -> "MODE_ONLY_WAKE"
+        MODE_UNLOCK_COLLAPSING -> "MODE_UNLOCK_COLLAPSING"
+        MODE_WAKE_AND_UNLOCK_FROM_DREAM -> "MODE_WAKE_AND_UNLOCK_FROM_DREAM"
+        MODE_DISMISS_BOUNCER -> "MODE_DISMISS_BOUNCER"
+        else -> "UNKNOWN{$mode}"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 46f3d4e..9e58500 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -18,13 +18,11 @@
 
 import com.android.systemui.log.dagger.KeyguardLog
 import com.android.systemui.plugins.log.LogBuffer
-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
-import com.android.systemui.plugins.log.MessagePrinter
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
@@ -36,33 +34,46 @@
  * an overkill.
  */
 class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuffer) {
-    fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+    fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
 
-    fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
+    fun e(@CompileTimeConstant msg: String) = buffer.log(TAG, ERROR, msg)
 
-    fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
+    fun v(@CompileTimeConstant msg: String) = buffer.log(TAG, VERBOSE, msg)
 
-    fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
+    fun w(@CompileTimeConstant msg: String) = buffer.log(TAG, WARNING, msg)
 
-    fun log(msg: String, level: LogLevel) = buffer.log(TAG, level, msg)
+    fun logException(ex: Exception, @CompileTimeConstant logMsg: String) {
+        buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
+    }
 
-    private fun debugLog(messageInitializer: MessageInitializer, messagePrinter: MessagePrinter) {
-        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" })
+        buffer.log(TAG, DEBUG, { double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
     }
 
     // TODO: remove after b/237743330 is fixed
     fun logStatusBarExplicitAlpha(alpha: Float) {
-        debugLog({ double1 = alpha.toDouble() }, { "new mExplicitAlpha value: $double1" })
+        buffer.log(
+            TAG,
+            DEBUG,
+            { double1 = alpha.toDouble() },
+            { "new mExplicitAlpha value: $double1" }
+        )
     }
 
     // TODO: remove after b/237743330 is fixed
     fun logStatusBarAlphaVisibility(visibility: Int, alpha: Float, state: String) {
-        debugLog(
+        buffer.log(
+            TAG,
+            DEBUG,
             {
                 int1 = visibility
                 double1 = alpha.toDouble()
@@ -71,4 +82,22 @@
             { "changing visibility to $int1 with alpha $double1 in state: $str1" }
         )
     }
+
+    @JvmOverloads
+    fun logBiometricMessage(
+        @CompileTimeConstant context: String,
+        msgId: Int? = null,
+        msg: String? = null
+    ) {
+        buffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = context
+                str2 = "$msgId"
+                str3 = msg
+            },
+            { "$str1 msgId: $str2 msg: $str3" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 3308f55..b66ae28 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -25,6 +25,7 @@
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.keyguard.KeyguardListenModel
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.TrustGrantFlags
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.plugins.log.LogLevel.DEBUG
@@ -153,19 +154,36 @@
                 { "fingerprintRunningState: $int1" })
     }
 
+    fun logFingerprintSuccess(userId: Int, isStrongBiometric: Boolean) {
+        logBuffer.log(TAG, DEBUG, {
+            int1 = userId
+            bool1 = isStrongBiometric
+        }, {"Fingerprint auth successful: userId: $int1, isStrongBiometric: $bool1"})
+    }
+
+    fun logFingerprintError(msgId: Int, originalErrMsg: String) {
+        logBuffer.log(TAG, DEBUG, {
+            str1 = originalErrMsg
+            int1 = msgId
+        }, { "Fingerprint error received: $str1 msgId= $int1" })
+    }
+
     fun logInvalidSubId(subId: Int) {
         logBuffer.log(TAG, INFO,
                 { int1 = subId },
                 { "Previously active sub id $int1 is now invalid, will remove" })
     }
 
-    fun logKeyguardBouncerChanged(bouncerIsOrWillBeShowing: Boolean, bouncerFullyShown: Boolean) {
+    fun logPrimaryKeyguardBouncerChanged(
+            primaryBouncerIsOrWillBeShowing: Boolean,
+            primaryBouncerFullyShown: Boolean
+    ) {
         logBuffer.log(TAG, DEBUG, {
-            bool1 = bouncerIsOrWillBeShowing
-            bool2 = bouncerFullyShown
+            bool1 = primaryBouncerIsOrWillBeShowing
+            bool2 = primaryBouncerFullyShown
         }, {
-            "handleKeyguardBouncerChanged " +
-                    "bouncerIsOrWillBeShowing=$bool1 bouncerFullyShowing=$bool2"
+            "handlePrimaryBouncerChanged " +
+                    "primaryBouncerIsOrWillBeShowing=$bool1 primaryBouncerFullyShown=$bool2"
         })
     }
 
@@ -207,12 +225,13 @@
                 { "Retrying face after HW unavailable, attempt $int1" })
     }
 
-    fun logRetryAfterFpError(msgId: Int, errString: String?) {
+    fun logRetryAfterFpErrorWithDelay(msgId: Int, errString: String?, delay: Int) {
         logBuffer.log(TAG, DEBUG, {
             int1 = msgId
+            int2 = delay
             str1 = "$errString"
         }, {
-            "Fingerprint retrying auth due to($int1) -> $str1"
+            "Fingerprint retrying auth after $int2 ms due to($int1) -> $str1"
         })
     }
 
@@ -222,16 +241,16 @@
                 { "Retrying fingerprint attempt: $int1" })
     }
 
-    fun logSendKeyguardBouncerChanged(
-        bouncerIsOrWillBeShowing: Boolean,
-        bouncerFullyShown: Boolean,
+    fun logSendPrimaryBouncerChanged(
+        primaryBouncerIsOrWillBeShowing: Boolean,
+        primaryBouncerFullyShown: Boolean,
     ) {
         logBuffer.log(TAG, DEBUG, {
-            bool1 = bouncerIsOrWillBeShowing
-            bool2 = bouncerFullyShown
+            bool1 = primaryBouncerIsOrWillBeShowing
+            bool2 = primaryBouncerFullyShown
         }, {
-            "sendKeyguardBouncerChanged bouncerIsOrWillBeShowing=$bool1 " +
-                    "bouncerFullyShown=$bool2"
+            "sendPrimaryBouncerChanged primaryBouncerIsOrWillBeShowing=$bool1 " +
+                    "primaryBouncerFullyShown=$bool2"
         })
     }
 
@@ -358,12 +377,16 @@
                 }, { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" })
     }
 
-    fun logShowTrustGrantedMessage(
+    fun logTrustGrantedWithFlags(
+            flags: Int,
+            userId: Int,
             message: String?
     ) {
         logBuffer.log(TAG, DEBUG, {
+            int1 = flags
+            int2 = userId
             str1 = message
-        }, { "showTrustGrantedMessage message$str1" })
+        }, { "trustGrantedWithFlags[user=$int2] flags=${TrustGrantFlags(int1)} message=$str1" })
     }
 
     fun logTrustChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index 3015710..eee705d 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -26,8 +26,6 @@
 
 import kotlin.math.roundToInt
 
-const val TAG = "CameraAvailabilityListener"
-
 /**
  * Listens for usage of the Camera and controls the ScreenDecorations transition to show extra
  * protection around a display cutout based on config_frontBuiltInDisplayCutoutProtection and
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
index a89cbf5..9ac45b3 100644
--- a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
+++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
@@ -4,30 +4,32 @@
 import android.content.Context
 import android.content.pm.PackageManager
 import android.util.Log
+import com.android.internal.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.FlagListenable
 import com.android.systemui.flags.Flags
-import javax.inject.Inject
+import com.android.systemui.settings.UserTracker
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withContext
+import javax.inject.Inject
 
 @SysUISingleton
 class ChooserSelector @Inject constructor(
         private val context: Context,
+        private val userTracker: UserTracker,
         private val featureFlags: FeatureFlags,
         @Application private val coroutineScope: CoroutineScope,
-        @Background private val bgDispatcher: CoroutineDispatcher
+        @Background private val bgDispatcher: CoroutineDispatcher,
 ) : CoreStartable {
 
-    private val packageManager = context.packageManager
     private val chooserComponent = ComponentName.unflattenFromString(
-            context.resources.getString(ChooserSelectorResourceHelper.CONFIG_CHOOSER_ACTIVITY))
+            context.resources.getString(R.string.config_chooserActivity))
 
     override fun start() {
         coroutineScope.launch {
@@ -56,10 +58,17 @@
         } else {
             PackageManager.COMPONENT_ENABLED_STATE_DISABLED
         }
-        try {
-            packageManager.setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0)
-        } catch (e: IllegalArgumentException) {
-            Log.w("ChooserSelector", "Unable to set IntentResolver enabled=" + enabled, e)
+        userTracker.userProfiles.forEach {
+            try {
+                context.createContextAsUser(it.userHandle, /* flags = */ 0).packageManager
+                        .setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0)
+            } catch (e: IllegalArgumentException) {
+                Log.w(
+                        "ChooserSelector",
+                        "Unable to set IntentResolver enabled=$enabled for user ${it.id}",
+                        e,
+                )
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java b/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java
deleted file mode 100644
index 7a2de7b..0000000
--- a/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java
+++ /dev/null
@@ -1,31 +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;
-
-import androidx.annotation.StringRes;
-
-import com.android.internal.R;
-
-/** Helper class for referencing resources */
-class ChooserSelectorResourceHelper {
-
-    private ChooserSelectorResourceHelper() {
-    }
-
-    @StringRes
-    static final int CONFIG_CHOOSER_ACTIVITY = R.string.config_chooserActivity;
-}
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index 929ebea..becf5b3 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -39,10 +39,13 @@
  */
 public interface CoreStartable extends Dumpable {
 
-    /** Main entry point for implementations. Called shortly after app startup. */
+    /** Main entry point for implementations. Called shortly after SysUI startup. */
     void start();
 
-    /** */
+    /** Called when the device configuration changes. This will not be called before
+     * {@link #start()}, but it could be called before {@link #onBootCompleted()}.
+     *
+     * @see android.app.Application#onConfigurationChanged(Configuration)  */
     default void onConfigurationChanged(Configuration newConfig) {
     }
 
@@ -50,7 +53,11 @@
     default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
     }
 
-    /** Called when the device reports BOOT_COMPLETED. */
+    /** Called immediately after the system broadcasts
+     * {@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED} or during SysUI startup if the
+     * property {@code sys.boot_completed} is already set to 1. The latter typically occurs when
+     * starting a new SysUI instance, such as when starting SysUI for a secondary user.
+     * {@link #onBootCompleted()} will never be called before {@link #start()}. */
     default void onBootCompleted() {
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index a5fdc68..ef16a3a 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -61,6 +61,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.PluginDependencyProvider;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.EnhancedEstimates;
@@ -70,8 +71,8 @@
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -357,6 +358,7 @@
     @Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy;
     @Inject Lazy<SystemUIDialogManager> mSystemUIDialogManagerLazy;
     @Inject Lazy<DialogLaunchAnimator> mDialogLaunchAnimatorLazy;
+    @Inject Lazy<UserTracker> mUserTrackerLazy;
 
     @Inject
     public Dependency() {
@@ -564,6 +566,7 @@
         mProviders.put(GroupExpansionManager.class, mGroupExpansionManagerLazy::get);
         mProviders.put(SystemUIDialogManager.class, mSystemUIDialogManagerLazy::get);
         mProviders.put(DialogLaunchAnimator.class, mDialogLaunchAnimatorLazy::get);
+        mProviders.put(UserTracker.class, mUserTrackerLazy::get);
 
         Dependency.setInstance(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index 5d52056..90ecb46 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -169,7 +169,7 @@
             return
         }
         cutoutPath.reset()
-        display.getDisplayInfo(displayInfo)
+        context.display?.getDisplayInfo(displayInfo)
         displayInfo.displayCutout?.cutoutPath?.let { path -> cutoutPath.set(path) }
         invalidate()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 9a8d532..104b71f 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -17,24 +17,25 @@
 package com.android.systemui;
 
 import android.app.AlertDialog;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.UserInfo;
 import android.os.UserHandle;
-import android.util.Log;
+
+import androidx.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.QSUserSwitcherEvent;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.settings.SecureSettings;
 
+import java.util.concurrent.Executor;
+
 import javax.inject.Inject;
 
 import dagger.assisted.Assisted;
@@ -44,31 +45,66 @@
 /**
  * Manages notification when a guest session is resumed.
  */
-public class GuestResumeSessionReceiver extends BroadcastReceiver {
-
-    private static final String TAG = GuestResumeSessionReceiver.class.getSimpleName();
+public class GuestResumeSessionReceiver {
 
     @VisibleForTesting
     public static final String SETTING_GUEST_HAS_LOGGED_IN = "systemui.guest_has_logged_in";
 
     @VisibleForTesting
     public AlertDialog mNewSessionDialog;
+    private final Executor mMainExecutor;
     private final UserTracker mUserTracker;
     private final SecureSettings mSecureSettings;
-    private final BroadcastDispatcher mBroadcastDispatcher;
     private final ResetSessionDialog.Factory mResetSessionDialogFactory;
     private final GuestSessionNotification mGuestSessionNotification;
 
+    @VisibleForTesting
+    public final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    cancelDialog();
+
+                    UserInfo currentUser = mUserTracker.getUserInfo();
+                    if (!currentUser.isGuest()) {
+                        return;
+                    }
+
+                    int guestLoginState = mSecureSettings.getIntForUser(
+                            SETTING_GUEST_HAS_LOGGED_IN, 0, newUser);
+
+                    if (guestLoginState == 0) {
+                        // set 1 to indicate, 1st login
+                        guestLoginState = 1;
+                        mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState,
+                                newUser);
+                    } else if (guestLoginState == 1) {
+                        // set 2 to indicate, 2nd or later login
+                        guestLoginState = 2;
+                        mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState,
+                                newUser);
+                    }
+
+                    mGuestSessionNotification.createPersistentNotification(currentUser,
+                            (guestLoginState <= 1));
+
+                    if (guestLoginState > 1) {
+                        mNewSessionDialog = mResetSessionDialogFactory.create(newUser);
+                        mNewSessionDialog.show();
+                    }
+                }
+            };
+
     @Inject
     public GuestResumeSessionReceiver(
+            @Main Executor mainExecutor,
             UserTracker userTracker,
             SecureSettings secureSettings,
-            BroadcastDispatcher broadcastDispatcher,
             GuestSessionNotification guestSessionNotification,
             ResetSessionDialog.Factory resetSessionDialogFactory) {
+        mMainExecutor = mainExecutor;
         mUserTracker = userTracker;
         mSecureSettings = secureSettings;
-        mBroadcastDispatcher = broadcastDispatcher;
         mGuestSessionNotification = guestSessionNotification;
         mResetSessionDialogFactory = resetSessionDialogFactory;
     }
@@ -77,49 +113,7 @@
      * Register this receiver with the {@link BroadcastDispatcher}
      */
     public void register() {
-        IntentFilter f = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-        mBroadcastDispatcher.registerReceiver(this, f, null /* handler */, UserHandle.SYSTEM);
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        String action = intent.getAction();
-
-        if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-            cancelDialog();
-
-            int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-            if (userId == UserHandle.USER_NULL) {
-                Log.e(TAG, intent + " sent to " + TAG + " without EXTRA_USER_HANDLE");
-                return;
-            }
-
-            UserInfo currentUser = mUserTracker.getUserInfo();
-            if (!currentUser.isGuest()) {
-                return;
-            }
-
-            int guestLoginState = mSecureSettings.getIntForUser(
-                    SETTING_GUEST_HAS_LOGGED_IN, 0, userId);
-
-            if (guestLoginState == 0) {
-                // set 1 to indicate, 1st login
-                guestLoginState = 1;
-                mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, userId);
-            } else if (guestLoginState == 1) {
-                // set 2 to indicate, 2nd or later login
-                guestLoginState = 2;
-                mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, userId);
-            }
-
-            mGuestSessionNotification.createPersistentNotification(currentUser,
-                                                                   (guestLoginState <= 1));
-
-            if (guestLoginState > 1) {
-                mNewSessionDialog = mResetSessionDialogFactory.create(userId);
-                mNewSessionDialog.show();
-            }
-        }
+        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
     }
 
     private void cancelDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
index 95f666c..1bb0329 100644
--- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
@@ -21,8 +21,8 @@
 import android.view.View;
 
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.ViewProvider;
-import com.android.systemui.shared.plugins.PluginManager;
 
 /**
  * Define an interface or abstract class as follows that includes the
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 9aa5fae..70750a1 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -72,7 +72,8 @@
             Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP,
             Key.ACCESSIBILITY_FLOATING_MENU_POSITION,
             Key.HAS_CLICKED_NUDGE_TO_SETUP_DREAM,
-            Key.HAS_DISMISSED_NUDGE_TO_SETUP_DREAM
+            Key.HAS_DISMISSED_NUDGE_TO_SETUP_DREAM,
+            Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED
     })
     // TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them
     public @interface Key {
@@ -117,6 +118,7 @@
         String ACCESSIBILITY_FLOATING_MENU_POSITION = "AccessibilityFloatingMenuPosition";
         String HAS_CLICKED_NUDGE_TO_SETUP_DREAM = "HasClickedNudgeToSetupDream";
         String HAS_DISMISSED_NUDGE_TO_SETUP_DREAM = "HasDismissedNudgeToSetupDream";
+        String HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED = "HasAccessibilityFloatingMenuTucked";
     }
 
     public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 11d579d..e6f559b 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -26,11 +26,7 @@
 import android.annotation.IdRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -46,7 +42,6 @@
 import android.os.Handler;
 import android.os.SystemProperties;
 import android.os.Trace;
-import android.os.UserHandle;
 import android.provider.Settings.Secure;
 import android.util.DisplayUtils;
 import android.util.Log;
@@ -69,7 +64,6 @@
 
 import com.android.internal.util.Preconditions;
 import com.android.settingslib.Utils;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.decor.CutoutDecorProviderFactory;
@@ -129,7 +123,6 @@
     private DisplayManager mDisplayManager;
     @VisibleForTesting
     protected boolean mIsRegistered;
-    private final BroadcastDispatcher mBroadcastDispatcher;
     private final Context mContext;
     private final Executor mMainExecutor;
     private final TunerService mTunerService;
@@ -170,6 +163,7 @@
     private Display.Mode mDisplayMode;
     @VisibleForTesting
     protected DisplayInfo mDisplayInfo = new DisplayInfo();
+    private DisplayCutout mDisplayCutout;
 
     @VisibleForTesting
     protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
@@ -216,8 +210,10 @@
                 (FaceScanningOverlay) getOverlayView(mFaceScanningViewId);
         if (faceScanningOverlay != null) {
             faceScanningOverlay.setHideOverlayRunnable(() -> {
+                Trace.beginSection("ScreenDecorations#hideOverlayRunnable");
                 updateOverlayWindowVisibilityIfViewExists(
                         faceScanningOverlay.findViewById(mFaceScanningViewId));
+                Trace.endSection();
             });
             faceScanningOverlay.enableShowProtection(false);
         }
@@ -279,16 +275,18 @@
             if (mOverlays == null || !shouldOptimizeVisibility()) {
                 return;
             }
-
+            Trace.beginSection("ScreenDecorations#updateOverlayWindowVisibilityIfViewExists");
             for (final OverlayWindow overlay : mOverlays) {
                 if (overlay == null) {
                     continue;
                 }
                 if (overlay.getView(view.getId()) != null) {
                     overlay.getRootView().setVisibility(getWindowVisibility(overlay, true));
+                    Trace.endSection();
                     return;
                 }
             }
+            Trace.endSection();
         });
     }
 
@@ -302,7 +300,6 @@
     public ScreenDecorations(Context context,
             @Main Executor mainExecutor,
             SecureSettings secureSettings,
-            BroadcastDispatcher broadcastDispatcher,
             TunerService tunerService,
             UserTracker userTracker,
             PrivacyDotViewController dotViewController,
@@ -312,7 +309,6 @@
         mContext = context;
         mMainExecutor = mainExecutor;
         mSecureSettings = secureSettings;
-        mBroadcastDispatcher = broadcastDispatcher;
         mTunerService = tunerService;
         mUserTracker = userTracker;
         mDotViewController = dotViewController;
@@ -378,12 +374,14 @@
     }
 
     private void startOnScreenDecorationsThread() {
+        Trace.beginSection("ScreenDecorations#startOnScreenDecorationsThread");
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
         mContext.getDisplay().getDisplayInfo(mDisplayInfo);
         mRotation = mDisplayInfo.rotation;
         mDisplayMode = mDisplayInfo.getMode();
         mDisplayUniqueId = mDisplayInfo.uniqueId;
+        mDisplayCutout = mDisplayInfo.displayCutout;
         mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(),
                 mDisplayUniqueId);
         mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
@@ -479,6 +477,7 @@
 
         mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
         updateConfiguration();
+        Trace.endSection();
     }
 
     @VisibleForTesting
@@ -528,6 +527,12 @@
     }
 
     private void setupDecorations() {
+        Trace.beginSection("ScreenDecorations#setupDecorations");
+        setupDecorationsInner();
+        Trace.endSection();
+    }
+
+    private void setupDecorationsInner() {
         if (hasRoundedCorners() || shouldDrawCutout() || isPrivacyDotEnabled()
                 || mFaceScanningFactory.getHasProviders()) {
 
@@ -580,7 +585,11 @@
                 return;
             }
 
-            mMainExecutor.execute(() -> mTunerService.addTunable(this, SIZE));
+            mMainExecutor.execute(() -> {
+                Trace.beginSection("ScreenDecorations#addTunable");
+                mTunerService.addTunable(this, SIZE);
+                Trace.endSection();
+            });
 
             // Watch color inversion and invert the overlay as needed.
             if (mColorInversionSetting == null) {
@@ -597,19 +606,20 @@
             mColorInversionSetting.onChange(false);
             updateColorInversion(mColorInversionSetting.getValue());
 
-            IntentFilter filter = new IntentFilter();
-            filter.addAction(Intent.ACTION_USER_SWITCHED);
-            mBroadcastDispatcher.registerReceiver(mUserSwitchIntentReceiver, filter,
-                    mExecutor, UserHandle.ALL);
+            mUserTracker.addCallback(mUserChangedCallback, mExecutor);
             mIsRegistered = true;
         } else {
-            mMainExecutor.execute(() -> mTunerService.removeTunable(this));
+            mMainExecutor.execute(() -> {
+                Trace.beginSection("ScreenDecorations#removeTunable");
+                mTunerService.removeTunable(this);
+                Trace.endSection();
+            });
 
             if (mColorInversionSetting != null) {
                 mColorInversionSetting.setListening(false);
             }
 
-            mBroadcastDispatcher.unregisterReceiver(mUserSwitchIntentReceiver);
+            mUserTracker.removeCallback(mUserChangedCallback);
             mIsRegistered = false;
         }
     }
@@ -896,18 +906,18 @@
         }
     }
 
-    private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            int newUserId = ActivityManager.getCurrentUser();
-            if (DEBUG) {
-                Log.d(TAG, "UserSwitched newUserId=" + newUserId);
-            }
-            // update color inversion setting to the new user
-            mColorInversionSetting.setUserId(newUserId);
-            updateColorInversion(mColorInversionSetting.getValue());
-        }
-    };
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    if (DEBUG) {
+                        Log.d(TAG, "UserSwitched newUserId=" + newUser);
+                    }
+                    // update color inversion setting to the new user
+                    mColorInversionSetting.setUserId(newUser);
+                    updateColorInversion(mColorInversionSetting.getValue());
+                }
+            };
 
     private void updateColorInversion(int colorsInvertedValue) {
         mTintColor = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK;
@@ -949,6 +959,7 @@
         }
 
         mExecutor.execute(() -> {
+            Trace.beginSection("ScreenDecorations#onConfigurationChanged");
             int oldRotation = mRotation;
             mPendingConfigChange = false;
             updateConfiguration();
@@ -961,6 +972,7 @@
                 // the updated rotation).
                 updateLayoutParams();
             }
+            Trace.endSection();
         });
     }
 
@@ -1022,7 +1034,8 @@
         mRoundedCornerResDelegate.dump(pw, args);
     }
 
-    private void updateConfiguration() {
+    @VisibleForTesting
+    void updateConfiguration() {
         Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(),
                 "must call on " + mHandler.getLooper().getThread()
                         + ", but was " + Thread.currentThread());
@@ -1033,11 +1046,14 @@
             mDotViewController.setNewRotation(newRotation);
         }
         final Display.Mode newMod = mDisplayInfo.getMode();
+        final DisplayCutout newCutout = mDisplayInfo.displayCutout;
 
         if (!mPendingConfigChange
-                && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) {
+                && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod)
+                || !Objects.equals(newCutout, mDisplayCutout))) {
             mRotation = newRotation;
             mDisplayMode = newMod;
+            mDisplayCutout = newCutout;
             mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
                     getPhysicalPixelDisplaySizeRatio());
             if (mScreenDecorHwcLayer != null) {
@@ -1125,6 +1141,7 @@
             if (mOverlays == null || !SIZE.equals(key)) {
                 return;
             }
+            Trace.beginSection("ScreenDecorations#onTuningChanged");
             try {
                 final int sizeFactor = Integer.parseInt(newValue);
                 mRoundedCornerResDelegate.setTuningSizeFactor(sizeFactor);
@@ -1138,6 +1155,7 @@
                     R.id.rounded_corner_bottom_right
             });
             updateHwLayerRoundedCornerExistAndSize();
+            Trace.endSection();
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index d9f44cd..ffdd861 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;
@@ -112,7 +113,8 @@
         setTheme(R.style.Theme_SystemUI);
 
         if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
-            IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
+            IntentFilter bootCompletedFilter = new
+                    IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED);
             bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
 
             // If SF GPU context priority is set to realtime, then SysUI should run at high.
@@ -137,7 +139,7 @@
                     if (mServicesStarted) {
                         final int N = mServices.length;
                         for (int i = 0; i < N; i++) {
-                            mServices[i].onBootCompleted();
+                            notifyBootCompleted(mServices[i]);
                         }
                     }
                 }
@@ -256,7 +258,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 +269,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 +293,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 +369,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/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index a21f45f..632fcdc 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -97,7 +97,6 @@
                     .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
                     .setRecentTasks(mWMComponent.getRecentTasks())
                     .setBackAnimation(mWMComponent.getBackAnimation())
-                    .setFloatingTasks(mWMComponent.getFloatingTasks())
                     .setDesktopMode(mWMComponent.getDesktopMode());
 
             // Only initialize when not starting from tests since this currently initializes some
@@ -118,7 +117,6 @@
                     .setStartingSurface(Optional.ofNullable(null))
                     .setRecentTasks(Optional.ofNullable(null))
                     .setBackAnimation(Optional.ofNullable(null))
-                    .setFloatingTasks(Optional.ofNullable(null))
                     .setDesktopMode(Optional.ofNullable(null));
         }
         mSysUIComponent = builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
index 8920c92..8aa3040 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
@@ -17,7 +17,7 @@
 package com.android.systemui
 
 import android.content.Context
-import com.android.systemui.dagger.DaggerGlobalRootComponent
+import com.android.systemui.dagger.DaggerReferenceGlobalRootComponent
 import com.android.systemui.dagger.GlobalRootComponent
 
 /**
@@ -25,6 +25,6 @@
  */
 class SystemUIInitializerImpl(context: Context) : SystemUIInitializer(context) {
     override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder {
-        return DaggerGlobalRootComponent.builder()
+        return DaggerReferenceGlobalRootComponent.builder()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 0a2dc5b..50449b0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -52,6 +52,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -183,15 +184,18 @@
     private final AccessibilityManager mA11yManager;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private final NotificationShadeWindowController mNotificationShadeController;
+    private final ShadeController mShadeController;
     private final StatusBarWindowCallback mNotificationShadeCallback;
     private boolean mDismissNotificationShadeActionRegistered;
 
     @Inject
     public SystemActions(Context context,
             NotificationShadeWindowController notificationShadeController,
+            ShadeController shadeController,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             Optional<Recents> recentsOptional) {
         mContext = context;
+        mShadeController = shadeController;
         mRecentsOptional = recentsOptional;
         mReceiver = new SystemActionsBroadcastReceiver();
         mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
@@ -291,8 +295,11 @@
         mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
         mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
         mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
-        mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
-        mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
+        if (mCentralSurfacesOptionalLazy.get().isPresent()) {
+            // These two actions require the CentralSurfaces instance.
+            mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
+            mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
+        }
         mA11yManager.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG);
         mA11yManager.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN);
         mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
@@ -526,9 +533,7 @@
     }
 
     private void handleAccessibilityDismissNotificationShade() {
-        mCentralSurfacesOptionalLazy.get().ifPresent(
-                centralSurfaces -> centralSurfaces.animateCollapsePanels(
-                        CommandQueue.FLAG_EXCLUDE_NONE, false /* force */));
+        mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
     }
 
     private void handleDpadUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index a6e767c..ec15d1a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -158,6 +158,10 @@
     private View mTopDrag;
     private View mRightDrag;
     private View mBottomDrag;
+    private ImageView mTopLeftCornerView;
+    private ImageView mTopRightCornerView;
+    private ImageView mBottomLeftCornerView;
+    private ImageView mBottomRightCornerView;
     private final Configuration mConfiguration;
 
     @NonNull
@@ -357,13 +361,15 @@
         return false;
     }
 
-    private void changeMagnificationSize(@MagnificationSize int index) {
+    @VisibleForTesting
+    void changeMagnificationSize(@MagnificationSize int index) {
         final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
         int size = (int) (initSize * MAGNIFICATION_SCALE_OPTIONS[index]);
         setWindowSize(size, size);
     }
 
-    private void setEditMagnifierSizeMode(boolean enable) {
+    @VisibleForTesting
+    void setEditMagnifierSizeMode(boolean enable) {
         mEditSizeEnable = enable;
         applyResourcesValues();
 
@@ -639,10 +645,37 @@
         Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize,
                 mMirrorView.getWidth() - mBorderDragSize,
                 mMirrorView.getHeight() - mBorderDragSize);
+
+        Region tapExcludeRegion = new Region();
+
         Rect dragArea = new Rect();
         mDragView.getHitRect(dragArea);
 
-        regionInsideDragBorder.op(dragArea, Region.Op.DIFFERENCE);
+        Rect topLeftArea = new Rect();
+        mTopLeftCornerView.getHitRect(topLeftArea);
+
+        Rect topRightArea = new Rect();
+        mTopRightCornerView.getHitRect(topRightArea);
+
+        Rect bottomLeftArea = new Rect();
+        mBottomLeftCornerView.getHitRect(bottomLeftArea);
+
+        Rect bottomRightArea = new Rect();
+        mBottomRightCornerView.getHitRect(bottomRightArea);
+
+        Rect closeArea = new Rect();
+        mCloseView.getHitRect(closeArea);
+
+        // add tapExcludeRegion for Drag or close
+        tapExcludeRegion.op(dragArea, Region.Op.UNION);
+        tapExcludeRegion.op(topLeftArea, Region.Op.UNION);
+        tapExcludeRegion.op(topRightArea, Region.Op.UNION);
+        tapExcludeRegion.op(bottomLeftArea, Region.Op.UNION);
+        tapExcludeRegion.op(bottomRightArea, Region.Op.UNION);
+        tapExcludeRegion.op(closeArea, Region.Op.UNION);
+
+        regionInsideDragBorder.op(tapExcludeRegion, Region.Op.DIFFERENCE);
+
         return regionInsideDragBorder;
     }
 
@@ -756,6 +789,10 @@
         mRightDrag = mMirrorView.findViewById(R.id.right_handle);
         mBottomDrag = mMirrorView.findViewById(R.id.bottom_handle);
         mCloseView = mMirrorView.findViewById(R.id.close_button);
+        mTopRightCornerView = mMirrorView.findViewById(R.id.top_right_corner);
+        mTopLeftCornerView = mMirrorView.findViewById(R.id.top_left_corner);
+        mBottomRightCornerView = mMirrorView.findViewById(R.id.bottom_right_corner);
+        mBottomLeftCornerView = mMirrorView.findViewById(R.id.bottom_left_corner);
 
         mDragView.setOnTouchListener(this);
         mLeftDrag.setOnTouchListener(this);
@@ -763,6 +800,10 @@
         mRightDrag.setOnTouchListener(this);
         mBottomDrag.setOnTouchListener(this);
         mCloseView.setOnTouchListener(this);
+        mTopLeftCornerView.setOnTouchListener(this);
+        mTopRightCornerView.setOnTouchListener(this);
+        mBottomLeftCornerView.setOnTouchListener(this);
+        mBottomRightCornerView.setOnTouchListener(this);
     }
 
     /**
@@ -831,8 +872,16 @@
 
     @Override
     public boolean onTouch(View v, MotionEvent event) {
-        if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
-                || v == mBottomDrag || v == mCloseView) {
+        if (v == mDragView
+                || v == mLeftDrag
+                || v == mTopDrag
+                || v == mRightDrag
+                || v == mBottomDrag
+                || v == mTopLeftCornerView
+                || v == mTopRightCornerView
+                || v == mBottomLeftCornerView
+                || v == mBottomRightCornerView
+                || v == mCloseView) {
             return mGestureDetector.onTouch(v, event);
         }
         return false;
@@ -1195,7 +1244,7 @@
     @Override
     public boolean onDrag(View view, float offsetX, float offsetY) {
         if (mEditSizeEnable) {
-            changeWindowSize(view, offsetX, offsetY);
+            return changeWindowSize(view, offsetX, offsetY);
         } else {
             move((int) offsetX, (int) offsetY);
         }
@@ -1220,13 +1269,47 @@
         if (mEditSizeEnable) {
             mDragView.setVisibility(View.GONE);
             mCloseView.setVisibility(View.VISIBLE);
+            mTopRightCornerView.setVisibility(View.VISIBLE);
+            mTopLeftCornerView.setVisibility(View.VISIBLE);
+            mBottomRightCornerView.setVisibility(View.VISIBLE);
+            mBottomLeftCornerView.setVisibility(View.VISIBLE);
         } else {
             mDragView.setVisibility(View.VISIBLE);
             mCloseView.setVisibility(View.GONE);
+            mTopRightCornerView.setVisibility(View.GONE);
+            mTopLeftCornerView.setVisibility(View.GONE);
+            mBottomRightCornerView.setVisibility(View.GONE);
+            mBottomLeftCornerView.setVisibility(View.GONE);
         }
     }
 
-    public boolean changeWindowSize(View view, float offsetX, float offsetY) {
+    private boolean changeWindowSize(View view, float offsetX, float offsetY) {
+        if (view == mLeftDrag) {
+            changeMagnificationFrameSize(offsetX, 0, 0, 0);
+        } else if (view == mRightDrag) {
+            changeMagnificationFrameSize(0, 0, offsetX, 0);
+        } else if (view == mTopDrag) {
+            changeMagnificationFrameSize(0, offsetY, 0, 0);
+        } else if (view == mBottomDrag) {
+            changeMagnificationFrameSize(0, 0, 0, offsetY);
+        } else if (view == mTopLeftCornerView) {
+            changeMagnificationFrameSize(offsetX, offsetY, 0, 0);
+        } else if (view == mTopRightCornerView) {
+            changeMagnificationFrameSize(0, offsetY, offsetX, 0);
+        } else if (view == mBottomLeftCornerView) {
+            changeMagnificationFrameSize(offsetX, 0, 0, offsetY);
+        } else if (view == mBottomRightCornerView) {
+            changeMagnificationFrameSize(0, 0, offsetX, offsetY);
+        } else {
+            return false;
+        }
+
+        return true;
+    }
+
+    private void changeMagnificationFrameSize(
+            float leftOffset, float topOffset, float rightOffset,
+            float bottomOffset) {
         boolean bRTL = isRTL(mContext);
         final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
 
@@ -1236,54 +1319,26 @@
         Rect tempRect = new Rect();
         tempRect.set(mMagnificationFrame);
 
-        if (view == mLeftDrag) {
-            if (bRTL) {
-                tempRect.right += offsetX;
-                if (tempRect.right > mWindowBounds.width()) {
-                    return false;
-                }
-            } else {
-                tempRect.left += offsetX;
-                if (tempRect.left < 0) {
-                    return false;
-                }
-            }
-        } else if (view == mRightDrag) {
-            if (bRTL) {
-                tempRect.left += offsetX;
-                if (tempRect.left < 0) {
-                    return false;
-                }
-            } else {
-                tempRect.right += offsetX;
-                if (tempRect.right > mWindowBounds.width()) {
-                    return false;
-                }
-            }
-        } else if (view == mTopDrag) {
-            tempRect.top += offsetY;
-            if (tempRect.top < 0) {
-                return false;
-            }
-        } else if (view == mBottomDrag) {
-            tempRect.bottom += offsetY;
-            if (tempRect.bottom > mWindowBounds.height()) {
-                return false;
-            }
+        if (bRTL) {
+            tempRect.left += (int) (rightOffset);
+            tempRect.right += (int) (leftOffset);
+        } else {
+            tempRect.right += (int) (rightOffset);
+            tempRect.left += (int) (leftOffset);
         }
+        tempRect.top += (int) (topOffset);
+        tempRect.bottom += (int) (bottomOffset);
 
         if (tempRect.width() < initSize || tempRect.height() < initSize
                 || tempRect.width() > maxWidthSize || tempRect.height() > maxHeightSize) {
-            return false;
+            return;
         }
-
         mMagnificationFrame.set(tempRect);
 
         computeBounceAnimationScale();
         calculateMagnificationFrameBoundary();
 
         modifyWindowMagnification(true);
-        return true;
     }
 
     private static boolean isRTL(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 9cffd5d..069c0f6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -103,7 +103,7 @@
             MagnificationSize.LARGE,
     })
     /** Denotes the Magnification size type. */
-    @interface MagnificationSize {
+    public @interface MagnificationSize {
         int NONE = 0;
         int SMALL  = 1;
         int MEDIUM = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
index 9af8300..de351ec 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
@@ -57,7 +57,7 @@
     @FloatRange(from = 0.0, to = 1.0)
     private static final float DEFAULT_POSITION_X_PERCENT = 1.0f;
     @FloatRange(from = 0.0, to = 1.0)
-    private static final float DEFAULT_POSITION_Y_PERCENT = 0.9f;
+    private static final float DEFAULT_POSITION_Y_PERCENT = 0.77f;
 
     private final Context mContext;
     private final AccessibilityFloatingMenuView mMenuView;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 396f584..1e14763 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
-import static android.util.MathUtils.constrain;
-
 import static java.util.Objects.requireNonNull;
 
 import android.animation.ValueAnimator;
@@ -64,7 +62,6 @@
     private final MenuView mMenuView;
     private final ValueAnimator mFadeOutAnimator;
     private final Handler mHandler;
-    private boolean mIsMovedToEdge;
     private boolean mIsFadeEffectEnabled;
     private DismissAnimationController.DismissCallback mDismissCallback;
 
@@ -111,25 +108,25 @@
     }
 
     void moveToTopLeftPosition() {
-        mIsMovedToEdge = false;
+        mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
         final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
         moveAndPersistPosition(new PointF(draggableBounds.left, draggableBounds.top));
     }
 
     void moveToTopRightPosition() {
-        mIsMovedToEdge = false;
+        mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
         final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
         moveAndPersistPosition(new PointF(draggableBounds.right, draggableBounds.top));
     }
 
     void moveToBottomLeftPosition() {
-        mIsMovedToEdge = false;
+        mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
         final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
         moveAndPersistPosition(new PointF(draggableBounds.left, draggableBounds.bottom));
     }
 
     void moveToBottomRightPosition() {
-        mIsMovedToEdge = false;
+        mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
         final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
         moveAndPersistPosition(new PointF(draggableBounds.right, draggableBounds.bottom));
     }
@@ -254,6 +251,8 @@
         // If the translation x is zero, it should be at the left of the bound.
         if (currentXTranslation < draggableBounds.left
                 || currentXTranslation > draggableBounds.right) {
+            constrainPositionAndUpdate(
+                    new PointF(mMenuView.getTranslationX(), mMenuView.getTranslationY()));
             moveToEdgeAndHide();
             return true;
         }
@@ -262,37 +261,33 @@
         return false;
     }
 
-    private boolean isOnLeftSide() {
+    boolean isOnLeftSide() {
         return mMenuView.getTranslationX() < mMenuView.getMenuDraggableBounds().centerX();
     }
 
-    boolean isMovedToEdge() {
-        return mIsMovedToEdge;
+    boolean isMoveToTucked() {
+        return mMenuView.isMoveToTucked();
     }
 
     void moveToEdgeAndHide() {
-        mIsMovedToEdge = true;
+        mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true);
 
-        final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
-        final float endY = constrain(mMenuView.getTranslationY(), draggableBounds.top,
-                draggableBounds.bottom);
-        final float menuHalfWidth = mMenuView.getWidth() / 2.0f;
+        final PointF position = mMenuView.getMenuPosition();
+        final float menuHalfWidth = mMenuView.getMenuWidth() / 2.0f;
         final float endX = isOnLeftSide()
-                ? draggableBounds.left - menuHalfWidth
-                : draggableBounds.right + menuHalfWidth;
-        moveAndPersistPosition(new PointF(endX, endY));
+                ? position.x - menuHalfWidth
+                : position.x + menuHalfWidth;
+        moveToPosition(new PointF(endX, position.y));
 
         // Keep the touch region let users could click extra space to pop up the menu view
         // from the screen edge
-        mMenuView.onBoundsInParentChanged(isOnLeftSide()
-                ? draggableBounds.left
-                : draggableBounds.right, (int) mMenuView.getTranslationY());
+        mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
 
         fadeOutIfEnabled();
     }
 
     void moveOutEdgeAndShow() {
-        mIsMovedToEdge = false;
+        mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
 
         mMenuView.onPositionChanged();
         mMenuView.onEdgeChangedIfNeeded();
@@ -345,7 +340,7 @@
     }
 
     private void constrainPositionAndUpdate(PointF position) {
-        final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
+        final Rect draggableBounds = mMenuView.getMenuDraggableBoundsExcludeIme();
         // Have the space gap margin between the top bound and the menu view, so actually the
         // position y range needs to cut the margin.
         position.offset(-draggableBounds.left, -draggableBounds.top);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index 57019de..5bc7406 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -50,7 +50,8 @@
     private static final float DEFAULT_MENU_POSITION_X_PERCENT = 1.0f;
 
     @FloatRange(from = 0.0, to = 1.0)
-    private static final float DEFAULT_MENU_POSITION_Y_PERCENT = 0.9f;
+    private static final float DEFAULT_MENU_POSITION_Y_PERCENT = 0.77f;
+    private static final boolean DEFAULT_MOVE_TO_TUCKED_VALUE = false;
 
     private final Context mContext;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -92,6 +93,12 @@
         mPercentagePosition = getStartPosition();
     }
 
+    void loadMenuMoveToTucked(OnInfoReady<Boolean> callback) {
+        callback.onReady(
+                Prefs.getBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
+                        DEFAULT_MOVE_TO_TUCKED_VALUE));
+    }
+
     void loadMenuPosition(OnInfoReady<Position> callback) {
         callback.onReady(mPercentagePosition);
     }
@@ -113,6 +120,11 @@
                 getMenuOpacityFromSettings(mContext));
     }
 
+    void updateMoveToTucked(boolean isMoveToTucked) {
+        Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
+                isMoveToTucked);
+    }
+
     void updateMenuSavingPosition(Position percentagePosition) {
         mPercentagePosition = percentagePosition;
         Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
index ac5736b..14517ba 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
@@ -74,10 +74,10 @@
                                 R.string.accessibility_floating_button_action_move_bottom_right));
         info.addAction(moveBottomRight);
 
-        final int moveEdgeId = mAnimationController.isMovedToEdge()
+        final int moveEdgeId = mAnimationController.isMoveToTucked()
                 ? R.id.action_move_out_edge_and_show
                 : R.id.action_move_to_edge_and_hide;
-        final int moveEdgeTextResId = mAnimationController.isMovedToEdge()
+        final int moveEdgeTextResId = mAnimationController.isMoveToTucked()
                 ? R.string.accessibility_floating_button_action_move_out_edge_and_show
                 : R.string.accessibility_floating_button_action_move_to_edge_and_hide_to_half;
         final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveToOrOutEdge =
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 6a14af5..986aa51 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -58,12 +58,15 @@
             this::updateSystemGestureExcludeRects;
     private final Observer<MenuFadeEffectInfo> mFadeEffectInfoObserver =
             this::onMenuFadeEffectInfoChanged;
+    private final Observer<Boolean> mMoveToTuckedObserver = this::onMoveToTucked;
     private final Observer<Position> mPercentagePositionObserver = this::onPercentagePosition;
     private final Observer<Integer> mSizeTypeObserver = this::onSizeTypeChanged;
     private final Observer<List<AccessibilityTarget>> mTargetFeaturesObserver =
             this::onTargetFeaturesChanged;
     private final MenuViewAppearance mMenuViewAppearance;
 
+    private boolean mIsMoveToTucked;
+
     private OnTargetFeaturesChangeListener mFeaturesChangeListener;
 
     MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
@@ -161,6 +164,12 @@
                 mMenuViewAppearance.getMenuStrokeColor());
     }
 
+    private void onMoveToTucked(boolean isMoveToTucked) {
+        mIsMoveToTucked = isMoveToTucked;
+
+        onPositionChanged();
+    }
+
     private void onPercentagePosition(Position percentagePosition) {
         mMenuViewAppearance.setPercentagePosition(percentagePosition);
 
@@ -171,6 +180,10 @@
         final PointF position = mMenuViewAppearance.getMenuPosition();
         mMenuAnimationController.moveToPosition(position);
         onBoundsInParentChanged((int) position.x, (int) position.y);
+
+        if (isMoveToTucked()) {
+            mMenuAnimationController.moveToEdgeAndHide();
+        }
     }
 
     @SuppressLint("NotifyDataSetChanged")
@@ -219,6 +232,22 @@
         return mMenuViewAppearance.getMenuDraggableBounds();
     }
 
+    Rect getMenuDraggableBoundsExcludeIme() {
+        return mMenuViewAppearance.getMenuDraggableBoundsExcludeIme();
+    }
+
+    int getMenuHeight() {
+        return mMenuViewAppearance.getMenuHeight();
+    }
+
+    int getMenuWidth() {
+        return mMenuViewAppearance.getMenuWidth();
+    }
+
+    PointF getMenuPosition() {
+        return mMenuViewAppearance.getMenuPosition();
+    }
+
     void persistPositionAndUpdateEdge(Position percentagePosition) {
         mMenuViewModel.updateMenuSavingPosition(percentagePosition);
         mMenuViewAppearance.setPercentagePosition(percentagePosition);
@@ -226,6 +255,16 @@
         onEdgeChangedIfNeeded();
     }
 
+    boolean isMoveToTucked() {
+        return mIsMoveToTucked;
+    }
+
+    void updateMenuMoveToTucked(boolean isMoveToTucked) {
+        mIsMoveToTucked = isMoveToTucked;
+        mMenuViewModel.updateMenuMoveToTucked(isMoveToTucked);
+    }
+
+
     /**
      * Uses the touch events from the parent view to identify if users clicked the extra
      * space of the menu view. If yes, will use the percentage position and update the
@@ -241,7 +280,7 @@
     boolean maybeMoveOutEdgeAndShow(int x, int y) {
         // Utilizes the touch region of the parent view to implement that users could tap extra
         // the space region to show the menu from the edge.
-        if (!mMenuAnimationController.isMovedToEdge() || !mBoundsInParent.contains(x, y)) {
+        if (!isMoveToTucked() || !mBoundsInParent.contains(x, y)) {
             return false;
         }
 
@@ -258,6 +297,7 @@
         mMenuViewModel.getFadeEffectInfoData().observeForever(mFadeEffectInfoObserver);
         mMenuViewModel.getTargetFeaturesData().observeForever(mTargetFeaturesObserver);
         mMenuViewModel.getSizeTypeData().observeForever(mSizeTypeObserver);
+        mMenuViewModel.getMoveToTuckedData().observeForever(mMoveToTuckedObserver);
         setVisibility(VISIBLE);
         mMenuViewModel.registerContentObservers();
         getViewTreeObserver().addOnComputeInternalInsetsListener(this);
@@ -271,6 +311,7 @@
         mMenuViewModel.getFadeEffectInfoData().removeObserver(mFadeEffectInfoObserver);
         mMenuViewModel.getTargetFeaturesData().removeObserver(mTargetFeaturesObserver);
         mMenuViewModel.getSizeTypeData().removeObserver(mSizeTypeObserver);
+        mMenuViewModel.getMoveToTuckedData().removeObserver(mMoveToTuckedObserver);
         mMenuViewModel.unregisterContentObservers();
         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
         getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater);
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..a7cdeab 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -47,6 +47,9 @@
     private final Resources mRes;
     private final Position mPercentagePosition = new Position(/* percentageX= */
             0f, /* percentageY= */ 0f);
+    private boolean mIsImeShowing;
+    // Avoid the menu view overlapping on the primary action button under the bottom as possible.
+    private int mImeShiftingSpace;
     private int mTargetFeaturesSize;
     private int mSizeType;
     private int mMargin;
@@ -62,6 +65,7 @@
     private int mStrokeColor;
     private int mInset;
     private int mElevation;
+    private float mImeTop;
     private float[] mRadii;
     private Drawable mBackgroundDrawable;
     private String mContentDescription;
@@ -106,6 +110,8 @@
         mStrokeColor = mRes.getColor(R.color.accessibility_floating_menu_stroke_dark);
         mInset = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_inset);
         mElevation = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation);
+        mImeShiftingSpace = mRes.getDimensionPixelSize(
+                R.dimen.accessibility_floating_menu_ime_shifting_space);
         final Drawable drawable =
                 mRes.getDrawable(R.drawable.accessibility_floating_menu_background);
         mBackgroundDrawable = new InstantInsetLayerDrawable(new Drawable[]{drawable});
@@ -131,33 +137,56 @@
         mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(mTargetFeaturesSize));
     }
 
+    void onImeVisibilityChanged(boolean imeShowing, float imeTop) {
+        mIsImeShowing = imeShowing;
+        mImeTop = imeTop;
+    }
+
     Rect getMenuDraggableBounds() {
+        return getMenuDraggableBoundsWith(/* includeIme= */ true);
+    }
+
+    Rect getMenuDraggableBoundsExcludeIme() {
+        return getMenuDraggableBoundsWith(/* includeIme= */ false);
+    }
+
+    private Rect getMenuDraggableBoundsWith(boolean includeIme) {
         final int margin = getMenuMargin();
-        final Rect draggableBounds = getWindowAvailableBounds();
+        final Rect draggableBounds = new Rect(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();
-        draggableBounds.bottom -= Math.min(
-                getWindowAvailableBounds().height() - draggableBounds.top,
-                calculateActualMenuHeight() + margin);
+
+        if (includeIme && mIsImeShowing) {
+            final int imeHeight = (int) (draggableBounds.bottom - mImeTop);
+            draggableBounds.bottom -= (imeHeight + mImeShiftingSpace);
+        }
+        draggableBounds.bottom -= (calculateActualMenuHeight() + margin);
+        draggableBounds.bottom = Math.max(draggableBounds.top, draggableBounds.bottom);
+
         return draggableBounds;
     }
 
     PointF getMenuPosition() {
-        final Rect draggableBounds = getMenuDraggableBounds();
+        final Rect draggableBounds = getMenuDraggableBoundsExcludeIme();
+        final float x = draggableBounds.left
+                + draggableBounds.width() * mPercentagePosition.getPercentageX();
 
-        return new PointF(
-                draggableBounds.left
-                        + draggableBounds.width() * mPercentagePosition.getPercentageX(),
-                draggableBounds.top
-                        + draggableBounds.height() * mPercentagePosition.getPercentageY());
+        float y = draggableBounds.top
+                + draggableBounds.height() * mPercentagePosition.getPercentageY();
+
+        // If the bottom of the menu view and overlap on the ime, its position y will be
+        // overridden with new y.
+        final float menuBottom = y + getMenuHeight() + mMargin;
+        if (mIsImeShowing && (menuBottom >= mImeTop)) {
+            y = Math.max(draggableBounds.top,
+                    mImeTop - getMenuHeight() - mMargin - mImeShiftingSpace);
+        }
+
+        return new PointF(x, y);
     }
 
     String getContentDescription() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 33e155d..c42943c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -16,18 +16,30 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static android.view.WindowInsets.Type.ime;
+
+import static androidx.core.view.WindowInsetsCompat.Type;
+
+import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
+import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
+import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
 import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
 
+import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.IntDef;
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.PluralsMessageFormatter;
 import android.view.MotionEvent;
+import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.WindowMetrics;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 import android.widget.TextView;
@@ -57,14 +69,17 @@
 class MenuViewLayer extends FrameLayout {
     private static final int SHOW_MESSAGE_DELAY_MS = 3000;
 
+    private final WindowManager mWindowManager;
     private final MenuView mMenuView;
     private final MenuMessageView mMessageView;
     private final DismissView mDismissView;
+    private final MenuViewAppearance mMenuViewAppearance;
     private final MenuAnimationController mMenuAnimationController;
     private final AccessibilityManager mAccessibilityManager;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final IAccessibilityFloatingMenu mFloatingMenu;
     private final DismissAnimationController mDismissAnimationController;
+    private final Rect mImeInsetsRect = new Rect();
 
     @IntDef({
             LayerIndex.MENU_VIEW,
@@ -82,8 +97,22 @@
     final Runnable mDismissMenuAction = new Runnable() {
         @Override
         public void run() {
-            Settings.Secure.putString(getContext().getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "");
+            Settings.Secure.putStringForUser(getContext().getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
+                    UserHandle.USER_CURRENT);
+
+            // Should disable the corresponding service when the fragment type is
+            // INVISIBLE_TOGGLE, which will enable service when the shortcut is on.
+            final List<AccessibilityServiceInfo> serviceInfoList =
+                    mAccessibilityManager.getEnabledAccessibilityServiceList(
+                            AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+            serviceInfoList.forEach(info -> {
+                if (getAccessibilityServiceFragmentType(info) == INVISIBLE_TOGGLE) {
+                    setAccessibilityServiceState(mContext, info.getComponentName(), /* enabled= */
+                            false);
+                }
+            });
+
             mFloatingMenu.hide();
         }
     };
@@ -92,13 +121,13 @@
             AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu) {
         super(context);
 
+        mWindowManager = windowManager;
         mAccessibilityManager = accessibilityManager;
         mFloatingMenu = floatingMenu;
 
         final MenuViewModel menuViewModel = new MenuViewModel(context);
-        final MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context,
-                windowManager);
-        mMenuView = new MenuView(context, menuViewModel, menuViewAppearance);
+        mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
+        mMenuView = new MenuView(context, menuViewModel, mMenuViewAppearance);
         mMenuAnimationController = mMenuView.getMenuAnimationController();
         mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
 
@@ -181,6 +210,7 @@
         super.onAttachedToWindow();
 
         mMenuView.show();
+        setOnApplyWindowInsetsListener((view, insets) -> onWindowInsetsApplied(insets));
         mMessageView.setUndoListener(view -> undo());
         mContext.registerComponentCallbacks(mDismissAnimationController);
     }
@@ -190,10 +220,35 @@
         super.onDetachedFromWindow();
 
         mMenuView.hide();
+        setOnApplyWindowInsetsListener(null);
         mHandler.removeCallbacksAndMessages(/* token= */ null);
         mContext.unregisterComponentCallbacks(mDismissAnimationController);
     }
 
+    private WindowInsets onWindowInsetsApplied(WindowInsets insets) {
+        final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+        final WindowInsets windowInsets = windowMetrics.getWindowInsets();
+        final Rect imeInsetsRect = windowInsets.getInsets(ime()).toRect();
+        if (!imeInsetsRect.equals(mImeInsetsRect)) {
+            final Rect windowBounds = new Rect(windowMetrics.getBounds());
+            final Rect systemBarsAndDisplayCutoutInsetsRect =
+                    windowInsets.getInsetsIgnoringVisibility(
+                            Type.systemBars() | Type.displayCutout()).toRect();
+            final float imeTop =
+                    windowBounds.height() - systemBarsAndDisplayCutoutInsetsRect.top
+                            - imeInsetsRect.bottom;
+
+            mMenuViewAppearance.onImeVisibilityChanged(windowInsets.isVisible(ime()), imeTop);
+
+            mMenuView.onEdgeChanged();
+            mMenuView.onPositionChanged();
+
+            mImeInsetsRect.set(imeInsetsRect);
+        }
+
+        return insets;
+    }
+
     private void hideMenuAndShowMessage() {
         final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis(
                 SHOW_MESSAGE_DELAY_MS,
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/accessibility/floatingmenu/MenuViewModel.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
index e8a2b6e..bd41787 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
@@ -35,6 +35,7 @@
     private final MutableLiveData<Integer> mSizeTypeData = new MutableLiveData<>();
     private final MutableLiveData<MenuFadeEffectInfo> mFadeEffectInfoData =
             new MutableLiveData<>();
+    private final MutableLiveData<Boolean> mMoveToTuckedData = new MutableLiveData<>();
     private final MutableLiveData<Position> mPercentagePositionData = new MutableLiveData<>();
     private final MenuInfoRepository mInfoRepository;
 
@@ -57,10 +58,19 @@
         mFadeEffectInfoData.setValue(fadeEffectInfo);
     }
 
+    void updateMenuMoveToTucked(boolean isMoveToTucked) {
+        mInfoRepository.updateMoveToTucked(isMoveToTucked);
+    }
+
     void updateMenuSavingPosition(Position percentagePosition) {
         mInfoRepository.updateMenuSavingPosition(percentagePosition);
     }
 
+    LiveData<Boolean> getMoveToTuckedData() {
+        mInfoRepository.loadMenuMoveToTucked(mMoveToTuckedData::setValue);
+        return mMoveToTuckedData;
+    }
+
     LiveData<Position> getPercentagePositionData() {
         mInfoRepository.loadMenuPosition(mPercentagePositionData::setValue);
         return mPercentagePositionData;
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index 5616a00..621b99d 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -29,13 +29,15 @@
 import android.util.Log
 import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper
 import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper
 import com.android.systemui.people.widget.PeopleBackupHelper
 
 /**
  * Helper for backing up elements in SystemUI
  *
- * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI.
- * The helper can be used to back up any element that is stored in [Context.getFilesDir].
+ * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI. The
+ * helper can be used to back up any element that is stored in [Context.getFilesDir] or
+ * [Context.getSharedPreferences].
  *
  * After restoring is done, a [ACTION_RESTORE_FINISHED] intent will be send to SystemUI user 0,
  * indicating that restoring is finished for a given user.
@@ -47,9 +49,11 @@
         internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME
         private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite"
         private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
+        private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY =
+            "systemui.keyguard.quickaffordance.shared_preferences"
         val controlsDataLock = Any()
         const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED"
-        private const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
+        const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
     }
 
     override fun onCreate(userHandle: UserHandle, operationType: Int) {
@@ -67,17 +71,27 @@
         }
 
         val keys = PeopleBackupHelper.getFilesToBackup()
-        addHelper(PEOPLE_TILES_BACKUP_KEY, PeopleBackupHelper(
-                this, userHandle, keys.toTypedArray()))
+        addHelper(
+            PEOPLE_TILES_BACKUP_KEY,
+            PeopleBackupHelper(this, userHandle, keys.toTypedArray())
+        )
+        addHelper(
+            KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY,
+            KeyguardQuickAffordanceBackupHelper(
+                context = this,
+                userId = userHandle.identifier,
+            ),
+        )
     }
 
     override fun onRestoreFinished() {
         super.onRestoreFinished()
-        val intent = Intent(ACTION_RESTORE_FINISHED).apply {
-            `package` = packageName
-            putExtra(Intent.EXTRA_USER_ID, userId)
-            flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
-        }
+        val intent =
+            Intent(ACTION_RESTORE_FINISHED).apply {
+                `package` = packageName
+                putExtra(Intent.EXTRA_USER_ID, userId)
+                flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
+            }
         sendBroadcastAsUser(intent, UserHandle.SYSTEM, PERMISSION_SELF)
     }
 
@@ -90,7 +104,9 @@
      * @property lock a lock to hold while backing up and restoring the files.
      * @property context the context of the [BackupAgent]
      * @property fileNamesAndPostProcess a map from the filenames to back up and the post processing
+     * ```
      *                                   actions to take
+     * ```
      */
     private class NoOverwriteFileBackupHelper(
         val lock: Any,
@@ -115,23 +131,23 @@
             data: BackupDataOutput?,
             newState: ParcelFileDescriptor?
         ) {
-            synchronized(lock) {
-                super.performBackup(oldState, data, newState)
-            }
+            synchronized(lock) { super.performBackup(oldState, data, newState) }
         }
     }
 }
+
 private fun getPPControlsFile(context: Context): () -> Unit {
     return {
         val filesDir = context.filesDir
         val file = Environment.buildPath(filesDir, BackupHelper.CONTROLS)
         if (file.exists()) {
-            val dest = Environment.buildPath(filesDir,
-                AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
+            val dest =
+                Environment.buildPath(filesDir, AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
             file.copyTo(dest)
             val jobScheduler = context.getSystemService(JobScheduler::class.java)
             jobScheduler?.schedule(
-                AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context))
+                AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context)
+            )
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
new file mode 100644
index 0000000..b52ddc1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
@@ -0,0 +1,207 @@
+/*
+ * 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.battery
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.Rect
+import android.graphics.drawable.DrawableWrapper
+import android.util.PathParser
+import com.android.settingslib.graph.ThemedBatteryDrawable
+import com.android.systemui.R
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.SHIELD_LEFT_OFFSET
+import com.android.systemui.battery.BatterySpecs.SHIELD_STROKE
+import com.android.systemui.battery.BatterySpecs.SHIELD_TOP_OFFSET
+
+/**
+ * A battery drawable that accessorizes [ThemedBatteryDrawable] with additional information if
+ * necessary.
+ *
+ * For now, it adds a shield in the bottom-right corner when [displayShield] is true.
+ */
+class AccessorizedBatteryDrawable(
+    private val context: Context,
+    frameColor: Int,
+) : DrawableWrapper(ThemedBatteryDrawable(context, frameColor)) {
+    private val mainBatteryDrawable: ThemedBatteryDrawable
+        get() = drawable as ThemedBatteryDrawable
+
+    private val shieldPath = Path()
+    private val scaledShield = Path()
+    private val scaleMatrix = Matrix()
+
+    private var shieldLeftOffsetScaled = SHIELD_LEFT_OFFSET
+    private var shieldTopOffsetScaled = SHIELD_TOP_OFFSET
+
+    private var density = context.resources.displayMetrics.density
+
+    private val dualTone =
+        context.resources.getBoolean(com.android.internal.R.bool.config_batterymeterDualTone)
+
+    private val shieldTransparentOutlinePaint =
+        Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+            p.color = Color.TRANSPARENT
+            p.strokeWidth = ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
+            p.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
+            p.style = Paint.Style.FILL_AND_STROKE
+        }
+
+    private val shieldPaint =
+        Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+            p.color = Color.MAGENTA
+            p.style = Paint.Style.FILL
+            p.isDither = true
+        }
+
+    init {
+        loadPaths()
+    }
+
+    override fun onBoundsChange(bounds: Rect) {
+        super.onBoundsChange(bounds)
+        updateSizes()
+    }
+
+    var displayShield: Boolean = false
+
+    private fun updateSizes() {
+        val b = bounds
+        if (b.isEmpty) {
+            return
+        }
+
+        val mainWidth = BatterySpecs.getMainBatteryWidth(b.width().toFloat(), displayShield)
+        val mainHeight = BatterySpecs.getMainBatteryHeight(b.height().toFloat(), displayShield)
+
+        drawable?.setBounds(
+            b.left,
+            b.top,
+            /* right= */ b.left + mainWidth.toInt(),
+            /* bottom= */ b.top + mainHeight.toInt()
+        )
+
+        if (displayShield) {
+            val sx = b.right / BATTERY_WIDTH_WITH_SHIELD
+            val sy = b.bottom / BATTERY_HEIGHT_WITH_SHIELD
+            scaleMatrix.setScale(sx, sy)
+            shieldPath.transform(scaleMatrix, scaledShield)
+
+            shieldLeftOffsetScaled = sx * SHIELD_LEFT_OFFSET
+            shieldTopOffsetScaled = sy * SHIELD_TOP_OFFSET
+
+            val scaledStrokeWidth =
+                (sx * SHIELD_STROKE).coerceAtLeast(
+                    ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
+                )
+            shieldTransparentOutlinePaint.strokeWidth = scaledStrokeWidth
+        }
+    }
+
+    override fun getIntrinsicHeight(): Int {
+        val height =
+            if (displayShield) {
+                BATTERY_HEIGHT_WITH_SHIELD
+            } else {
+                BATTERY_HEIGHT
+            }
+        return (height * density).toInt()
+    }
+
+    override fun getIntrinsicWidth(): Int {
+        val width =
+            if (displayShield) {
+                BATTERY_WIDTH_WITH_SHIELD
+            } else {
+                BATTERY_WIDTH
+            }
+        return (width * density).toInt()
+    }
+
+    override fun draw(c: Canvas) {
+        c.saveLayer(null, null)
+        // Draw the main battery icon
+        super.draw(c)
+
+        if (displayShield) {
+            c.translate(shieldLeftOffsetScaled, shieldTopOffsetScaled)
+            // We need a transparent outline around the shield, so first draw the transparent-ness
+            // then draw the shield
+            c.drawPath(scaledShield, shieldTransparentOutlinePaint)
+            c.drawPath(scaledShield, shieldPaint)
+        }
+        c.restore()
+    }
+
+    override fun getOpacity(): Int {
+        return PixelFormat.OPAQUE
+    }
+
+    override fun setAlpha(p0: Int) {
+        // Unused internally -- see [ThemedBatteryDrawable.setAlpha].
+    }
+
+    override fun setColorFilter(colorfilter: ColorFilter?) {
+        super.setColorFilter(colorFilter)
+        shieldPaint.colorFilter = colorFilter
+    }
+
+    /** Sets whether the battery is currently charging. */
+    fun setCharging(charging: Boolean) {
+        mainBatteryDrawable.charging = charging
+    }
+
+    /** Sets the current level (out of 100) of the battery. */
+    fun setBatteryLevel(level: Int) {
+        mainBatteryDrawable.setBatteryLevel(level)
+    }
+
+    /** Sets whether power save is enabled. */
+    fun setPowerSaveEnabled(powerSaveEnabled: Boolean) {
+        mainBatteryDrawable.powerSaveEnabled = powerSaveEnabled
+    }
+
+    /** Returns whether power save is currently enabled. */
+    fun getPowerSaveEnabled(): Boolean {
+        return mainBatteryDrawable.powerSaveEnabled
+    }
+
+    /** Sets the colors to use for the icon. */
+    fun setColors(fgColor: Int, bgColor: Int, singleToneColor: Int) {
+        shieldPaint.color = if (dualTone) fgColor else singleToneColor
+        mainBatteryDrawable.setColors(fgColor, bgColor, singleToneColor)
+    }
+
+    /** Notifies this drawable that the density might have changed. */
+    fun notifyDensityChanged() {
+        density = context.resources.displayMetrics.density
+    }
+
+    private fun loadPaths() {
+        val shieldPathString = context.resources.getString(R.string.config_batterymeterShieldPath)
+        shieldPath.set(PathParser.createPathFromPathData(shieldPathString))
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 6a10d4a..03d999f 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -45,7 +45,6 @@
 import androidx.annotation.StyleRes;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.settingslib.graph.ThemedBatteryDrawable;
 import com.android.systemui.DualToneHandler;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
@@ -68,7 +67,7 @@
     public static final int MODE_OFF = 2;
     public static final int MODE_ESTIMATE = 3;
 
-    private final ThemedBatteryDrawable mDrawable;
+    private final AccessorizedBatteryDrawable mDrawable;
     private final ImageView mBatteryIconView;
     private TextView mBatteryPercentView;
 
@@ -77,7 +76,10 @@
     private int mLevel;
     private int mShowPercentMode = MODE_DEFAULT;
     private boolean mShowPercentAvailable;
+    private String mEstimateText = null;
     private boolean mCharging;
+    private boolean mIsOverheated;
+    private boolean mDisplayShieldEnabled;
     // Error state where we know nothing about the current battery state
     private boolean mBatteryStateUnknown;
     // Lazily-loaded since this is expected to be a rare-if-ever state
@@ -106,7 +108,7 @@
         final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
                 context.getColor(R.color.meter_background_color));
         mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);
-        mDrawable = new ThemedBatteryDrawable(context, frameColor);
+        mDrawable = new AccessorizedBatteryDrawable(context, frameColor);
         atts.recycle();
 
         mShowPercentAvailable = context.getResources().getBoolean(
@@ -170,12 +172,14 @@
         if (mode == mShowPercentMode) return;
         mShowPercentMode = mode;
         updateShowPercent();
+        updatePercentText();
     }
 
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         updatePercentView();
+        mDrawable.notifyDensityChanged();
     }
 
     public void setColorsFromContext(Context context) {
@@ -203,6 +207,17 @@
         mDrawable.setPowerSaveEnabled(isPowerSave);
     }
 
+    void onIsOverheatedChanged(boolean isOverheated) {
+        boolean valueChanged = mIsOverheated != isOverheated;
+        mIsOverheated = isOverheated;
+        if (valueChanged) {
+            updateContentDescription();
+            // The battery drawable is a different size depending on whether it's currently
+            // overheated or not, so we need to re-scale the view when overheated changes.
+            scaleBatteryMeterViews();
+        }
+    }
+
     private TextView loadPercentView() {
         return (TextView) LayoutInflater.from(getContext())
                 .inflate(R.layout.battery_percentage_view, null);
@@ -227,13 +242,17 @@
         mBatteryEstimateFetcher = fetcher;
     }
 
+    void setDisplayShieldEnabled(boolean displayShieldEnabled) {
+        mDisplayShieldEnabled = displayShieldEnabled;
+    }
+
     void updatePercentText() {
         if (mBatteryStateUnknown) {
-            setContentDescription(getContext().getString(R.string.accessibility_battery_unknown));
             return;
         }
 
         if (mBatteryEstimateFetcher == null) {
+            setPercentTextAtCurrentLevel();
             return;
         }
 
@@ -245,10 +264,9 @@
                         return;
                     }
                     if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
+                        mEstimateText = estimate;
                         mBatteryPercentView.setText(estimate);
-                        setContentDescription(getContext().getString(
-                                R.string.accessibility_battery_level_with_estimate,
-                                mLevel, estimate));
+                        updateContentDescription();
                     } else {
                         setPercentTextAtCurrentLevel();
                     }
@@ -257,28 +275,49 @@
                 setPercentTextAtCurrentLevel();
             }
         } else {
-            setContentDescription(
-                    getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
-                            : R.string.accessibility_battery_level, mLevel));
+            updateContentDescription();
         }
     }
 
     private void setPercentTextAtCurrentLevel() {
-        if (mBatteryPercentView == null) {
-            return;
+        if (mBatteryPercentView != null) {
+            mEstimateText = null;
+            String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
+            // Setting text actually triggers a layout pass (because the text view is set to
+            // wrap_content width and TextView always relayouts for this). Avoid needless
+            // relayout if the text didn't actually change.
+            if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
+                mBatteryPercentView.setText(percentText);
+            }
         }
 
-        String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
-        // Setting text actually triggers a layout pass (because the text view is set to
-        // wrap_content width and TextView always relayouts for this). Avoid needless
-        // relayout if the text didn't actually change.
-        if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
-            mBatteryPercentView.setText(percentText);
+        updateContentDescription();
+    }
+
+    private void updateContentDescription() {
+        Context context = getContext();
+
+        String contentDescription;
+        if (mBatteryStateUnknown) {
+            contentDescription = context.getString(R.string.accessibility_battery_unknown);
+        } else if (mShowPercentMode == MODE_ESTIMATE && !TextUtils.isEmpty(mEstimateText)) {
+            contentDescription = context.getString(
+                    mIsOverheated
+                            ? R.string.accessibility_battery_level_charging_paused_with_estimate
+                            : R.string.accessibility_battery_level_with_estimate,
+                    mLevel,
+                    mEstimateText);
+        } else if (mIsOverheated) {
+            contentDescription =
+                    context.getString(R.string.accessibility_battery_level_charging_paused, mLevel);
+        } else if (mCharging) {
+            contentDescription =
+                    context.getString(R.string.accessibility_battery_level_charging, mLevel);
+        } else {
+            contentDescription = context.getString(R.string.accessibility_battery_level, mLevel);
         }
 
-        setContentDescription(
-                getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
-                        : R.string.accessibility_battery_level, mLevel));
+        setContentDescription(contentDescription);
     }
 
     void updateShowPercent() {
@@ -329,6 +368,7 @@
         }
 
         mBatteryStateUnknown = isUnknown;
+        updateContentDescription();
 
         if (mBatteryStateUnknown) {
             mBatteryIconView.setImageDrawable(getUnknownStateDrawable());
@@ -349,15 +389,43 @@
         res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
         float iconScaleFactor = typedValue.getFloat();
 
-        int batteryHeight = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height);
-        int batteryWidth = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width);
+        float mainBatteryHeight =
+                res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height) * iconScaleFactor;
+        float mainBatteryWidth =
+                res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width) * iconScaleFactor;
+
+        // If the battery is marked as overheated, we should display a shield indicating that the
+        // battery is being "defended".
+        boolean displayShield = mDisplayShieldEnabled && mIsOverheated;
+        float fullBatteryIconHeight =
+                BatterySpecs.getFullBatteryHeight(mainBatteryHeight, displayShield);
+        float fullBatteryIconWidth =
+                BatterySpecs.getFullBatteryWidth(mainBatteryWidth, displayShield);
+
+        int marginTop;
+        if (displayShield) {
+            // If the shield is displayed, we need some extra marginTop so that the bottom of the
+            // main icon is still aligned with the bottom of all the other system icons.
+            int shieldHeightAddition = Math.round(fullBatteryIconHeight - mainBatteryHeight);
+            // However, the other system icons have some embedded bottom padding that the battery
+            // doesn't have, so we shouldn't move the battery icon down by the full amount.
+            // See b/258672854.
+            marginTop = shieldHeightAddition
+                    - res.getDimensionPixelSize(R.dimen.status_bar_battery_extra_vertical_spacing);
+        } else {
+            marginTop = 0;
+        }
+
         int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom);
 
         LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
-                (int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor));
-        scaledLayoutParams.setMargins(0, 0, 0, marginBottom);
+                Math.round(fullBatteryIconWidth),
+                Math.round(fullBatteryIconHeight));
+        scaledLayoutParams.setMargins(0, marginTop, 0, marginBottom);
 
+        mDrawable.setDisplayShield(displayShield);
         mBatteryIconView.setLayoutParams(scaledLayoutParams);
+        mBatteryIconView.invalidateDrawable(mDrawable);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index ae9a323..f4ec33a 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -17,19 +17,23 @@
 
 import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
 
-import android.app.ActivityManager;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.view.View;
 
-import com.android.systemui.broadcast.BroadcastDispatcher;
+import androidx.annotation.NonNull;
+
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -42,12 +46,13 @@
 public class BatteryMeterViewController extends ViewController<BatteryMeterView> {
     private final ConfigurationController mConfigurationController;
     private final TunerService mTunerService;
+    private final Handler mMainHandler;
     private final ContentResolver mContentResolver;
     private final BatteryController mBatteryController;
 
     private final String mSlotBattery;
     private final SettingObserver mSettingObserver;
-    private final CurrentUserTracker mCurrentUserTracker;
+    private final UserTracker mUserTracker;
 
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
@@ -84,6 +89,21 @@
                 public void onBatteryUnknownStateChanged(boolean isUnknown) {
                     mView.onBatteryUnknownStateChanged(isUnknown);
                 }
+
+                @Override
+                public void onIsOverheatedChanged(boolean isOverheated) {
+                    mView.onIsOverheatedChanged(isOverheated);
+                }
+            };
+
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    mContentResolver.unregisterContentObserver(mSettingObserver);
+                    registerShowBatteryPercentObserver(newUser);
+                    mView.updateShowPercent();
+                }
             };
 
     // Some places may need to show the battery conditionally, and not obey the tuner
@@ -93,30 +113,26 @@
     @Inject
     public BatteryMeterViewController(
             BatteryMeterView view,
+            UserTracker userTracker,
             ConfigurationController configurationController,
             TunerService tunerService,
-            BroadcastDispatcher broadcastDispatcher,
             @Main Handler mainHandler,
             ContentResolver contentResolver,
+            FeatureFlags featureFlags,
             BatteryController batteryController) {
         super(view);
+        mUserTracker = userTracker;
         mConfigurationController = configurationController;
         mTunerService = tunerService;
+        mMainHandler = mainHandler;
         mContentResolver = contentResolver;
         mBatteryController = batteryController;
 
         mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
+        mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON));
 
         mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
-        mSettingObserver = new SettingObserver(mainHandler);
-        mCurrentUserTracker = new CurrentUserTracker(broadcastDispatcher) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                contentResolver.unregisterContentObserver(mSettingObserver);
-                registerShowBatteryPercentObserver(newUserId);
-                mView.updateShowPercent();
-            }
-        };
+        mSettingObserver = new SettingObserver(mMainHandler);
     }
 
     @Override
@@ -125,9 +141,9 @@
         subscribeForTunerUpdates();
         mBatteryController.addCallback(mBatteryStateChangeCallback);
 
-        registerShowBatteryPercentObserver(ActivityManager.getCurrentUser());
+        registerShowBatteryPercentObserver(mUserTracker.getUserId());
         registerGlobalBatteryUpdateObserver();
-        mCurrentUserTracker.startTracking();
+        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
 
         mView.updateShowPercent();
     }
@@ -138,7 +154,7 @@
         unsubscribeFromTunerUpdates();
         mBatteryController.removeCallback(mBatteryStateChangeCallback);
 
-        mCurrentUserTracker.stopTracking();
+        mUserTracker.removeCallback(mUserChangedCallback);
         mContentResolver.unregisterContentObserver(mSettingObserver);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt
new file mode 100644
index 0000000..6455a96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt
@@ -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 com.android.systemui.battery
+
+import com.android.settingslib.graph.ThemedBatteryDrawable
+
+/** An object storing specs related to the battery icon in the status bar. */
+object BatterySpecs {
+
+    /** Width of the main battery icon, not including the shield. */
+    const val BATTERY_WIDTH = ThemedBatteryDrawable.WIDTH
+    /** Height of the main battery icon, not including the shield. */
+    const val BATTERY_HEIGHT = ThemedBatteryDrawable.HEIGHT
+
+    private const val SHIELD_WIDTH = 10f
+    private const val SHIELD_HEIGHT = 13f
+
+    /**
+     * Amount that the left side of the shield should be offset from the left side of the battery.
+     */
+    const val SHIELD_LEFT_OFFSET = 8f
+    /** Amount that the top of the shield should be offset from the top of the battery. */
+    const val SHIELD_TOP_OFFSET = 10f
+
+    const val SHIELD_STROKE = 4f
+
+    /** The full width of the battery icon, including the main battery icon *and* the shield. */
+    const val BATTERY_WIDTH_WITH_SHIELD = SHIELD_LEFT_OFFSET + SHIELD_WIDTH
+    /** The full height of the battery icon, including the main battery icon *and* the shield. */
+    const val BATTERY_HEIGHT_WITH_SHIELD = SHIELD_TOP_OFFSET + SHIELD_HEIGHT
+
+    /**
+     * Given the desired height of the main battery icon in pixels, returns the height that the full
+     * battery icon will take up in pixels.
+     *
+     * If there's no shield, this will just return [mainBatteryHeight]. Otherwise, the shield
+     * extends slightly below the bottom of the main battery icon so we need some extra height.
+     */
+    @JvmStatic
+    fun getFullBatteryHeight(mainBatteryHeight: Float, displayShield: Boolean): Float {
+        return if (!displayShield) {
+            mainBatteryHeight
+        } else {
+            val verticalScaleFactor = mainBatteryHeight / BATTERY_HEIGHT
+            verticalScaleFactor * BATTERY_HEIGHT_WITH_SHIELD
+        }
+    }
+
+    /**
+     * Given the desired width of the main battery icon in pixels, returns the width that the full
+     * battery icon will take up in pixels.
+     *
+     * If there's no shield, this will just return [mainBatteryWidth]. Otherwise, the shield extends
+     * past the right side of the main battery icon so we need some extra width.
+     */
+    @JvmStatic
+    fun getFullBatteryWidth(mainBatteryWidth: Float, displayShield: Boolean): Float {
+        return if (!displayShield) {
+            mainBatteryWidth
+        } else {
+            val horizontalScaleFactor = mainBatteryWidth / BATTERY_WIDTH
+            horizontalScaleFactor * BATTERY_WIDTH_WITH_SHIELD
+        }
+    }
+
+    /**
+     * Given the height of the full battery icon, return how tall the main battery icon should be.
+     *
+     * If there's no shield, this will just return [fullBatteryHeight]. Otherwise, the shield takes
+     * up some of the view's height so the main battery width will be just a portion of
+     * [fullBatteryHeight].
+     */
+    @JvmStatic
+    fun getMainBatteryHeight(fullBatteryHeight: Float, displayShield: Boolean): Float {
+        return if (!displayShield) {
+            fullBatteryHeight
+        } else {
+            return (BATTERY_HEIGHT / BATTERY_HEIGHT_WITH_SHIELD) * fullBatteryHeight
+        }
+    }
+
+    /**
+     * Given the width of the full battery icon, return how wide the main battery icon should be.
+     *
+     * If there's no shield, this will just return [fullBatteryWidth]. Otherwise, the shield takes
+     * up some of the view's width so the main battery width will be just a portion of
+     * [fullBatteryWidth].
+     */
+    @JvmStatic
+    fun getMainBatteryWidth(fullBatteryWidth: Float, displayShield: Boolean): Float {
+        return if (!displayShield) {
+            fullBatteryWidth
+        } else {
+            return (BATTERY_WIDTH / BATTERY_WIDTH_WITH_SHIELD) * fullBatteryWidth
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index b40b356..b962cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -18,6 +18,7 @@
 
 import android.annotation.RawRes
 import android.content.Context
+import android.content.res.Configuration
 import android.hardware.fingerprint.FingerprintManager
 import android.view.DisplayInfo
 import android.view.Surface
@@ -33,15 +34,19 @@
 import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
 import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE
 import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
+import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
+import com.android.systemui.unfold.updates.FoldProvider
 
 /** Fingerprint only icon animator for BiometricPrompt.  */
 open class AuthBiometricFingerprintIconController(
         context: Context,
         iconView: LottieAnimationView,
         protected val iconViewOverlay: LottieAnimationView
-) : AuthIconController(context, iconView) {
+) : AuthIconController(context, iconView), FoldProvider.FoldCallback {
 
+    private var isDeviceFolded: Boolean = false
     private val isSideFps: Boolean
+    private val screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
     var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
         set(value) {
             if (field == value) {
@@ -74,6 +79,8 @@
         if (isSideFps && displayInfo.rotation == Surface.ROTATION_180) {
             iconView.rotation = 180f
         }
+        screenSizeFoldProvider.registerCallback(this, context.mainExecutor)
+        screenSizeFoldProvider.onConfigurationChange(context.resources.configuration)
     }
 
     private fun updateIconSideFps(@BiometricState lastState: Int, @BiometricState newState: Int) {
@@ -100,6 +107,8 @@
         if (shouldAnimateForTransition(lastState, newState)) {
             iconView.playAnimation()
             iconViewOverlay.playAnimation()
+        } else if (lastState == STATE_IDLE && newState == STATE_AUTHENTICATING_ANIMATING_IN) {
+            iconView.playAnimation()
         }
         LottieColorUtils.applyDynamicColors(context, iconView)
         LottieColorUtils.applyDynamicColors(context, iconViewOverlay)
@@ -124,6 +133,10 @@
         LottieColorUtils.applyDynamicColors(context, iconView)
     }
 
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        screenSizeFoldProvider.onConfigurationChange(newConfig)
+    }
+
     override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
         if (isSideFps) {
             updateIconSideFps(lastState, newState)
@@ -191,11 +204,21 @@
 
     @RawRes
     private fun getSideFpsAnimationForTransition(rotation: Int): Int = when (rotation) {
-        Surface.ROTATION_0 -> R.raw.biometricprompt_landscape_base
-        Surface.ROTATION_90 -> R.raw.biometricprompt_portrait_base_topleft
-        Surface.ROTATION_180 -> R.raw.biometricprompt_landscape_base
-        Surface.ROTATION_270 -> R.raw.biometricprompt_portrait_base_bottomright
-        else -> R.raw.biometricprompt_landscape_base
+        Surface.ROTATION_90 -> if (isDeviceFolded) {
+            R.raw.biometricprompt_folded_base_topleft
+        } else {
+            R.raw.biometricprompt_portrait_base_topleft
+        }
+        Surface.ROTATION_270 -> if (isDeviceFolded) {
+            R.raw.biometricprompt_folded_base_bottomright
+        } else {
+            R.raw.biometricprompt_portrait_base_bottomright
+        }
+        else -> if (isDeviceFolded) {
+            R.raw.biometricprompt_folded_base_default
+        } else {
+            R.raw.biometricprompt_landscape_base
+        }
     }
 
     @RawRes
@@ -273,4 +296,8 @@
         }
         else -> null
     }
+
+    override fun onFoldUpdated(isFolded: Boolean) {
+        isDeviceFolded = isFolded
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
index 15f487b..b3b6fa2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
@@ -18,6 +18,7 @@
 
 import android.annotation.DrawableRes
 import android.content.Context
+import android.content.res.Configuration
 import android.graphics.drawable.Animatable2
 import android.graphics.drawable.AnimatedVectorDrawable
 import android.graphics.drawable.Drawable
@@ -91,4 +92,6 @@
 
     /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
     open fun handleAnimationEnd(drawable: Drawable) {}
+
+    open fun onConfigurationChanged(newConfig: Configuration) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 0ac71c4..e12c170 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -27,6 +27,7 @@
 import android.annotation.Nullable;
 import android.annotation.StringRes;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.hardware.biometrics.BiometricAuthenticator.Modality;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.PromptInfo;
@@ -654,6 +655,12 @@
     }
 
     @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mIconController.onConfigurationChanged(newConfig);
+    }
+
+    @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index f74c721..815ac68 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -141,8 +141,7 @@
     private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
 
     private final @Background DelayableExecutor mBackgroundExecutor;
-    private int mOrientation;
-    private boolean mSkipFirstLostFocus = false;
+    private boolean mIsOrientationChanged = false;
 
     // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
     @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason;
@@ -491,6 +490,7 @@
     @Override
     public void onOrientationChanged() {
         maybeUpdatePositionForUdfps(true /* invalidate */);
+        mIsOrientationChanged = true;
     }
 
     @Override
@@ -499,8 +499,8 @@
         if (!hasWindowFocus) {
             //it's a workaround to avoid closing BP incorrectly
             //BP gets a onWindowFocusChanged(false) and then gets a onWindowFocusChanged(true)
-            if (mSkipFirstLostFocus) {
-                mSkipFirstLostFocus = false;
+            if (mIsOrientationChanged) {
+                mIsOrientationChanged = false;
                 return;
             }
             Log.v(TAG, "Lost window focus, dismissing the dialog");
@@ -512,9 +512,6 @@
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
 
-        //save the first orientation
-        mOrientation = getResources().getConfiguration().orientation;
-
         mWakefulnessLifecycle.addObserver(this);
 
         if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
@@ -670,7 +667,7 @@
         }
 
         if (savedState != null) {
-            mSkipFirstLostFocus = savedState.getBoolean(
+            mIsOrientationChanged = savedState.getBoolean(
                     AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED);
         }
 
@@ -764,9 +761,7 @@
                 mBiometricView != null && mCredentialView == null);
         outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null);
 
-        if (mOrientation != getResources().getConfiguration().orientation) {
-            outState.putBoolean(AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED, true);
-        }
+        outState.putBoolean(AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED, mIsOrientationChanged);
 
         if (mBiometricView != null) {
             mBiometricView.onSaveState(outState);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 313ff4157..db2239b 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;
@@ -122,7 +122,7 @@
     @Nullable private final FingerprintManager mFingerprintManager;
     @Nullable private final FaceManager mFaceManager;
     private final Provider<UdfpsController> mUdfpsControllerFactory;
-    private final Provider<SidefpsController> mSidefpsControllerFactory;
+    private final Provider<SideFpsController> mSidefpsControllerFactory;
 
     // TODO: these should be migrated out once ready
     @NonNull private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
@@ -147,8 +147,8 @@
     @NonNull private final WindowManager mWindowManager;
     @NonNull private final DisplayManager mDisplayManager;
     @Nullable private UdfpsController mUdfpsController;
-    @Nullable private IUdfpsHbmListener mUdfpsHbmListener;
-    @Nullable private SidefpsController mSidefpsController;
+    @Nullable private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback;
+    @Nullable private SideFpsController mSideFpsController;
     @Nullable private IBiometricContextListener mBiometricContextListener;
     @Nullable private UdfpsLogger mUdfpsLogger;
     @VisibleForTesting IBiometricSysuiReceiver mReceiver;
@@ -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;
@@ -305,7 +306,7 @@
 
         mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null;
         if (mSidefpsProps != null) {
-            mSidefpsController = mSidefpsControllerFactory.get();
+            mSideFpsController = mSidefpsControllerFactory.get();
         }
 
         mFingerprintManager.registerBiometricStateListener(new BiometricStateListener() {
@@ -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);
         }
@@ -613,6 +623,10 @@
                     getFingerprintSensorLocationInNaturalOrientation(),
                     mCachedDisplayInfo);
         }
+
+        for (final Callback cb : mCallbacks) {
+            cb.onFingerprintLocationChanged();
+        }
     }
 
     /**
@@ -634,6 +648,10 @@
                     mCachedDisplayInfo
             );
         }
+
+        for (final Callback cb : mCallbacks) {
+            cb.onFaceSensorLocationChanged();
+        }
     }
 
     /**
@@ -692,7 +710,7 @@
             @Nullable FingerprintManager fingerprintManager,
             @Nullable FaceManager faceManager,
             Provider<UdfpsController> udfpsControllerFactory,
-            Provider<SidefpsController> sidefpsControllerFactory,
+            Provider<SideFpsController> sidefpsControllerFactory,
             @NonNull DisplayManager displayManager,
             @NonNull WakefulnessLifecycle wakefulnessLifecycle,
             @NonNull UserManager userManager,
@@ -722,6 +740,7 @@
         mWindowManager = windowManager;
         mInteractionJankMonitor = jankMonitor;
         mUdfpsEnrolledForUser = new SparseBooleanArray();
+        mSfpsEnrolledForUser = new SparseBooleanArray();
         mFaceEnrolledForUser = new SparseBooleanArray();
         mVibratorHelper = vibrator;
 
@@ -799,17 +818,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 +900,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 +993,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 +1124,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;
@@ -1288,8 +1333,24 @@
         default void onBiometricPromptDismissed() {}
 
         /**
-         * The location in pixels can change due to resolution changes.
+         * Called when the location of the fingerprint sensor changes. The location in pixels can
+         * change due to resolution changes.
+         */
+        default void onFingerprintLocationChanged() {}
+
+        /**
+         * Called when the location of the under display fingerprint sensor changes. The location in
+         * pixels can change due to resolution changes.
+         *
+         * On devices with UDFPS, this is always called alongside
+         * {@link #onFingerprintLocationChanged}.
          */
         default void onUdfpsLocationChanged() {}
+
+        /**
+         * Called when the location of the face unlock sensor (typically the front facing camera)
+         * changes. The location in pixels can change due to resolution changes.
+         */
+        default void onFaceSensorLocationChanged() {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index fc5f447..d561cd7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -29,6 +29,8 @@
 import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.animation.Interpolators
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.CircleReveal
@@ -71,7 +73,8 @@
     private val biometricUnlockController: BiometricUnlockController,
     private val udfpsControllerProvider: Provider<UdfpsController>,
     private val statusBarStateController: StatusBarStateController,
-    rippleView: AuthRippleView?
+    private val featureFlags: FeatureFlags,
+        rippleView: AuthRippleView?
 ) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback,
     WakefulnessLifecycle.Observer {
 
@@ -116,9 +119,9 @@
         notificationShadeWindowController.setForcePluginOpen(false, this)
     }
 
-    fun showUnlockRipple(biometricSourceType: BiometricSourceType?) {
+    fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
         if (!keyguardStateController.isShowing ||
-            keyguardUpdateMonitor.userNeedsStrongAuth()) {
+                !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(biometricSourceType)) {
             return
         }
 
@@ -159,12 +162,17 @@
 
     private fun showUnlockedRipple() {
         notificationShadeWindowController.setForcePluginOpen(true, this)
-        val lightRevealScrim = centralSurfaces.lightRevealScrim
-        if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
-            circleReveal?.let {
-                lightRevealScrim?.revealAmount = 0f
-                lightRevealScrim?.revealEffect = it
-                startLightRevealScrimOnKeyguardFadingAway = true
+
+        // This code path is not used if the KeyguardTransitionRepository is managing the light
+        // reveal scrim.
+        if (!featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+            val lightRevealScrim = centralSurfaces.lightRevealScrim
+            if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
+                circleReveal?.let {
+                    lightRevealScrim?.revealAmount = 0f
+                    lightRevealScrim?.revealEffect = it
+                    startLightRevealScrimOnKeyguardFadingAway = true
+                }
             }
         }
 
@@ -177,6 +185,10 @@
     }
 
     override fun onKeyguardFadingAwayChanged() {
+        if (featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+            return
+        }
+
         if (keyguardStateController.isKeyguardFadingAway) {
             val lightRevealScrim = centralSurfaces.lightRevealScrim
             if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {
@@ -246,7 +258,7 @@
         object : KeyguardUpdateMonitorCallback() {
             override fun onBiometricAuthenticated(
                 userId: Int,
-                biometricSourceType: BiometricSourceType?,
+                biometricSourceType: BiometricSourceType,
                 isStrongBiometric: Boolean
             ) {
                 if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
@@ -255,14 +267,14 @@
                 showUnlockRipple(biometricSourceType)
             }
 
-        override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
+        override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType) {
             if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
                 mView.retractDwellRipple()
             }
         }
 
         override fun onBiometricAcquired(
-            biometricSourceType: BiometricSourceType?,
+            biometricSourceType: BiometricSourceType,
             acquireInfo: Int
         ) {
             if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index c93fe6a..4b57d45 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -29,7 +29,7 @@
 import android.view.animation.PathInterpolator
 import com.android.internal.graphics.ColorUtils
 import com.android.systemui.animation.Interpolators
-import com.android.systemui.ripple.RippleShader
+import com.android.systemui.surfaceeffects.ripple.RippleShader
 
 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
new file mode 100644
index 0000000..17ebdad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -0,0 +1,401 @@
+/*
+ * 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.Animator
+import android.animation.AnimatorListenerAdapter
+import android.app.ActivityTaskManager
+import android.content.Context
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.graphics.Rect
+import android.hardware.biometrics.BiometricOverlayConstants
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.display.DisplayManager
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.hardware.fingerprint.ISidefpsController
+import android.os.Handler
+import android.util.Log
+import android.util.RotationUtils
+import android.view.Display
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.Surface
+import android.view.View
+import android.view.View.AccessibilityDelegate
+import android.view.ViewPropertyAnimator
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+import android.view.accessibility.AccessibilityEvent
+import androidx.annotation.RawRes
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.model.KeyPath
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.io.PrintWriter
+import javax.inject.Inject
+
+private const val TAG = "SideFpsController"
+
+/**
+ * Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events.
+ */
+@SysUISingleton
+class SideFpsController
+@Inject
+constructor(
+    private val context: Context,
+    private val layoutInflater: LayoutInflater,
+    fingerprintManager: FingerprintManager?,
+    private val windowManager: WindowManager,
+    private val activityTaskManager: ActivityTaskManager,
+    overviewProxyService: OverviewProxyService,
+    displayManager: DisplayManager,
+    @Main private val mainExecutor: DelayableExecutor,
+    @Main private val handler: Handler,
+    dumpManager: DumpManager
+) : Dumpable {
+    val requests: HashSet<SideFpsUiRequestSource> = HashSet()
+
+    @VisibleForTesting
+    val sensorProps: FingerprintSensorPropertiesInternal =
+        fingerprintManager?.sideFpsSensorProperties
+            ?: throw IllegalStateException("no side fingerprint sensor")
+
+    @VisibleForTesting
+    val orientationListener =
+        BiometricDisplayListener(
+            context,
+            displayManager,
+            handler,
+            BiometricDisplayListener.SensorType.SideFingerprint(sensorProps)
+        ) { onOrientationChanged() }
+
+    @VisibleForTesting
+    val overviewProxyListener =
+        object : OverviewProxyService.OverviewProxyListener {
+            override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) {
+                overlayView?.let { view ->
+                    handler.postDelayed({ updateOverlayVisibility(view) }, 500)
+                }
+            }
+        }
+
+    private val animationDuration =
+        context.resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()
+
+    private var overlayHideAnimator: ViewPropertyAnimator? = null
+
+    private var overlayView: View? = null
+        set(value) {
+            field?.let { oldView ->
+                windowManager.removeView(oldView)
+                orientationListener.disable()
+            }
+            overlayHideAnimator?.cancel()
+            overlayHideAnimator = null
+
+            field = value
+            field?.let { newView ->
+                windowManager.addView(newView, overlayViewParams)
+                updateOverlayVisibility(newView)
+                orientationListener.enable()
+            }
+        }
+    @VisibleForTesting
+    internal var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
+
+    private val overlayViewParams =
+        WindowManager.LayoutParams(
+                WindowManager.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
+                Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
+                PixelFormat.TRANSLUCENT
+            )
+            .apply {
+                title = TAG
+                fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
+                gravity = Gravity.TOP or Gravity.LEFT
+                layoutInDisplayCutoutMode =
+                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+                privateFlags = PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_NO_MOVE_ANIMATION
+            }
+
+    init {
+        fingerprintManager?.setSidefpsController(
+            object : ISidefpsController.Stub() {
+                override fun show(
+                    sensorId: Int,
+                    @BiometricOverlayConstants.ShowReason reason: Int
+                ) =
+                    if (reason.isReasonToAutoShow(activityTaskManager)) {
+                        show(SideFpsUiRequestSource.AUTO_SHOW)
+                    } else {
+                        hide(SideFpsUiRequestSource.AUTO_SHOW)
+                    }
+
+                override fun hide(sensorId: Int) = hide(SideFpsUiRequestSource.AUTO_SHOW)
+            }
+        )
+        overviewProxyService.addCallback(overviewProxyListener)
+        dumpManager.registerDumpable(this)
+    }
+
+    /** Shows the side fps overlay if not already shown. */
+    fun show(request: SideFpsUiRequestSource) {
+        requests.add(request)
+        mainExecutor.execute {
+            if (overlayView == null) {
+                createOverlayForDisplay()
+            } else {
+                Log.v(TAG, "overlay already shown")
+            }
+        }
+    }
+
+    /** Hides the fps overlay if shown. */
+    fun hide(request: SideFpsUiRequestSource) {
+        requests.remove(request)
+        mainExecutor.execute {
+            if (requests.isEmpty()) {
+                overlayView = null
+            }
+        }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("requests:")
+        for (requestSource in requests) {
+            pw.println("     $requestSource.name")
+        }
+    }
+
+    private fun onOrientationChanged() {
+        if (overlayView != null) {
+            createOverlayForDisplay()
+        }
+    }
+
+    private fun createOverlayForDisplay() {
+        val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
+        overlayView = view
+        val display = context.display!!
+        val offsets =
+            sensorProps.getLocation(display.uniqueId).let { location ->
+                if (location == null) {
+                    Log.w(TAG, "No location specified for display: ${display.uniqueId}")
+                }
+                location ?: sensorProps.location
+            }
+        overlayOffsets = offsets
+
+        val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+        view.rotation = display.asSideFpsAnimationRotation(offsets.isYAligned())
+        lottie.setAnimation(display.asSideFpsAnimation(offsets.isYAligned()))
+        lottie.addLottieOnCompositionLoadedListener {
+            // Check that view is not stale, and that overlayView has not been hidden/removed
+            if (overlayView != null && overlayView == view) {
+                updateOverlayParams(display, it.bounds)
+            }
+        }
+        lottie.addOverlayDynamicColor(context)
+
+        /**
+         * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
+         * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
+         * in focus
+         */
+        view.setAccessibilityDelegate(
+            object : AccessibilityDelegate() {
+                override fun dispatchPopulateAccessibilityEvent(
+                    host: View,
+                    event: AccessibilityEvent
+                ): Boolean {
+                    return if (
+                        event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+                    ) {
+                        true
+                    } else {
+                        super.dispatchPopulateAccessibilityEvent(host, event)
+                    }
+                }
+            }
+        )
+    }
+
+    @VisibleForTesting
+    internal fun updateOverlayParams(display: Display, bounds: Rect) {
+        val isNaturalOrientation = display.isNaturalOrientation()
+        val size = windowManager.maximumWindowMetrics.bounds
+        val displayWidth = if (isNaturalOrientation) size.width() else size.height()
+        val displayHeight = if (isNaturalOrientation) size.height() else size.width()
+        val boundsWidth = if (isNaturalOrientation) bounds.width() else bounds.height()
+        val boundsHeight = if (isNaturalOrientation) bounds.height() else bounds.width()
+        val sensorBounds =
+            if (overlayOffsets.isYAligned()) {
+                Rect(
+                    displayWidth - boundsWidth,
+                    overlayOffsets.sensorLocationY,
+                    displayWidth,
+                    overlayOffsets.sensorLocationY + boundsHeight
+                )
+            } else {
+                Rect(
+                    overlayOffsets.sensorLocationX,
+                    0,
+                    overlayOffsets.sensorLocationX + boundsWidth,
+                    boundsHeight
+                )
+            }
+
+        RotationUtils.rotateBounds(
+            sensorBounds,
+            Rect(0, 0, displayWidth, displayHeight),
+            display.rotation
+        )
+
+        overlayViewParams.x = sensorBounds.left
+        overlayViewParams.y = sensorBounds.top
+        windowManager.updateViewLayout(overlayView, overlayViewParams)
+    }
+
+    private fun updateOverlayVisibility(view: View) {
+        if (view != overlayView) {
+            return
+        }
+        // hide after a few seconds if the sensor is oriented down and there are
+        // large overlapping system bars
+        val rotation = context.display?.rotation
+        if (
+            windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar() &&
+                ((rotation == Surface.ROTATION_270 && overlayOffsets.isYAligned()) ||
+                    (rotation == Surface.ROTATION_180 && !overlayOffsets.isYAligned()))
+        ) {
+            overlayHideAnimator =
+                view
+                    .animate()
+                    .alpha(0f)
+                    .setStartDelay(3_000)
+                    .setDuration(animationDuration)
+                    .setListener(
+                        object : AnimatorListenerAdapter() {
+                            override fun onAnimationEnd(animation: Animator) {
+                                view.visibility = View.GONE
+                                overlayHideAnimator = null
+                            }
+                        }
+                    )
+        } else {
+            overlayHideAnimator?.cancel()
+            overlayHideAnimator = null
+            view.alpha = 1f
+            view.visibility = View.VISIBLE
+        }
+    }
+}
+
+private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorPropertiesInternal?
+    get() = this?.sensorPropertiesInternal?.firstOrNull { it.isAnySidefpsType }
+
+/** Returns [True] when the device has a side fingerprint sensor. */
+fun FingerprintManager?.hasSideFpsSensor(): Boolean = this?.sideFpsSensorProperties != null
+
+@BiometricOverlayConstants.ShowReason
+private fun Int.isReasonToAutoShow(activityTaskManager: ActivityTaskManager): Boolean =
+    when (this) {
+        REASON_AUTH_KEYGUARD -> false
+        REASON_AUTH_SETTINGS ->
+            when (activityTaskManager.topClass()) {
+                // TODO(b/186176653): exclude fingerprint overlays from this list view
+                "com.android.settings.biometrics.fingerprint.FingerprintSettings" -> false
+                else -> true
+            }
+        else -> true
+    }
+
+private fun ActivityTaskManager.topClass(): String =
+    getTasks(1).firstOrNull()?.topActivity?.className ?: ""
+
+@RawRes
+private fun Display.asSideFpsAnimation(yAligned: Boolean): Int =
+    when (rotation) {
+        Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+        Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+        else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
+    }
+
+private fun Display.asSideFpsAnimationRotation(yAligned: Boolean): Float =
+    when (rotation) {
+        Surface.ROTATION_90 -> if (yAligned) 0f else 180f
+        Surface.ROTATION_180 -> 180f
+        Surface.ROTATION_270 -> if (yAligned) 180f else 0f
+        else -> 0f
+    }
+
+private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
+
+private fun Display.isNaturalOrientation(): Boolean =
+    rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
+
+private fun WindowInsets.hasBigNavigationBar(): Boolean =
+    getInsets(WindowInsets.Type.navigationBars()).bottom >= 70
+
+private fun LottieAnimationView.addOverlayDynamicColor(context: Context) {
+    fun update() {
+        val c = context.getColor(R.color.biometric_dialog_accent)
+        val chevronFill = context.getColor(R.color.sfps_chevron_fill)
+        for (key in listOf(".blue600", ".blue400")) {
+            addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
+                PorterDuffColorFilter(c, PorterDuff.Mode.SRC_ATOP)
+            }
+        }
+        addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+            PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
+        }
+    }
+
+    if (composition != null) {
+        update()
+    } else {
+        addLottieOnCompositionLoadedListener { update() }
+    }
+}
+
+/**
+ * The source of a request to show the side fps visual indicator. This is distinct from
+ * [BiometricOverlayConstants] which corrresponds with the reason fingerprint authentication is
+ * requested.
+ */
+enum class SideFpsUiRequestSource {
+    /** see [isReasonToAutoShow] */
+    AUTO_SHOW,
+    /** Pin, pattern or password bouncer */
+    PRIMARY_BOUNCER,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
deleted file mode 100644
index d03106b..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
+++ /dev/null
@@ -1,337 +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 android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.app.ActivityTaskManager
-import android.content.Context
-import android.graphics.PixelFormat
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
-import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
-import android.hardware.biometrics.SensorLocationInternal
-import android.hardware.display.DisplayManager
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.hardware.fingerprint.ISidefpsController
-import android.os.Handler
-import android.util.Log
-import android.util.RotationUtils
-import android.view.Display
-import android.view.Gravity
-import android.view.LayoutInflater
-import android.view.Surface
-import android.view.View
-import android.view.View.AccessibilityDelegate
-import android.view.ViewPropertyAnimator
-import android.view.WindowInsets
-import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
-import android.view.accessibility.AccessibilityEvent
-import androidx.annotation.RawRes
-import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieProperty
-import com.airbnb.lottie.model.KeyPath
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.util.concurrency.DelayableExecutor
-import javax.inject.Inject
-
-private const val TAG = "SidefpsController"
-
-/**
- * Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events.
- */
-@SysUISingleton
-class SidefpsController @Inject constructor(
-    private val context: Context,
-    private val layoutInflater: LayoutInflater,
-    fingerprintManager: FingerprintManager?,
-    private val windowManager: WindowManager,
-    private val activityTaskManager: ActivityTaskManager,
-    overviewProxyService: OverviewProxyService,
-    displayManager: DisplayManager,
-    @Main private val mainExecutor: DelayableExecutor,
-    @Main private val handler: Handler
-) {
-    @VisibleForTesting
-    val sensorProps: FingerprintSensorPropertiesInternal = fingerprintManager
-        ?.sideFpsSensorProperties
-        ?: throw IllegalStateException("no side fingerprint sensor")
-
-    @VisibleForTesting
-    val orientationListener = BiometricDisplayListener(
-        context,
-        displayManager,
-        handler,
-        BiometricDisplayListener.SensorType.SideFingerprint(sensorProps)
-    ) { onOrientationChanged() }
-
-    @VisibleForTesting
-    val overviewProxyListener = object : OverviewProxyService.OverviewProxyListener {
-        override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) {
-            overlayView?.let { view ->
-                handler.postDelayed({ updateOverlayVisibility(view) }, 500)
-            }
-        }
-    }
-
-    private val animationDuration =
-        context.resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()
-
-    private var overlayHideAnimator: ViewPropertyAnimator? = null
-
-    private var overlayView: View? = null
-        set(value) {
-            field?.let { oldView ->
-                windowManager.removeView(oldView)
-                orientationListener.disable()
-            }
-            overlayHideAnimator?.cancel()
-            overlayHideAnimator = null
-
-            field = value
-            field?.let { newView ->
-                windowManager.addView(newView, overlayViewParams)
-                updateOverlayVisibility(newView)
-                orientationListener.enable()
-            }
-        }
-    @VisibleForTesting
-    internal var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
-
-    private val overlayViewParams = WindowManager.LayoutParams(
-        WindowManager.LayoutParams.WRAP_CONTENT,
-        WindowManager.LayoutParams.WRAP_CONTENT,
-        WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
-        Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
-        PixelFormat.TRANSLUCENT
-    ).apply {
-        title = TAG
-        fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
-        gravity = Gravity.TOP or Gravity.LEFT
-        layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-        privateFlags = PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_NO_MOVE_ANIMATION
-    }
-
-    init {
-        fingerprintManager?.setSidefpsController(
-            object : ISidefpsController.Stub() {
-                override fun show(
-                    sensorId: Int,
-                    @BiometricOverlayConstants.ShowReason reason: Int
-                ) = if (reason.isReasonToShow(activityTaskManager)) show() else hide()
-
-                override fun hide(sensorId: Int) = hide()
-            })
-        overviewProxyService.addCallback(overviewProxyListener)
-    }
-
-    /** Shows the side fps overlay if not already shown. */
-    fun show() {
-        mainExecutor.execute {
-            if (overlayView == null) {
-                createOverlayForDisplay()
-            } else {
-                Log.v(TAG, "overlay already shown")
-            }
-        }
-    }
-
-    /** Hides the fps overlay if shown. */
-    fun hide() {
-        mainExecutor.execute { overlayView = null }
-    }
-
-    private fun onOrientationChanged() {
-        if (overlayView != null) {
-            createOverlayForDisplay()
-        }
-    }
-
-    private fun createOverlayForDisplay() {
-        val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
-        overlayView = view
-        val display = context.display!!
-        val offsets = sensorProps.getLocation(display.uniqueId).let { location ->
-            if (location == null) {
-                Log.w(TAG, "No location specified for display: ${display.uniqueId}")
-            }
-            location ?: sensorProps.location
-        }
-        overlayOffsets = offsets
-
-        val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
-        view.rotation = display.asSideFpsAnimationRotation(offsets.isYAligned())
-        lottie.setAnimation(display.asSideFpsAnimation(offsets.isYAligned()))
-        lottie.addLottieOnCompositionLoadedListener {
-            // Check that view is not stale, and that overlayView has not been hidden/removed
-            if (overlayView != null && overlayView == view) {
-                updateOverlayParams(display, it.bounds)
-            }
-        }
-        lottie.addOverlayDynamicColor(context)
-
-        /**
-         * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
-         * speaking @string/accessibility_fingerprint_label twice when sensor location indicator
-         * is in focus
-         */
-        view.setAccessibilityDelegate(object : AccessibilityDelegate() {
-            override fun dispatchPopulateAccessibilityEvent(
-                host: View,
-                event: AccessibilityEvent
-            ): Boolean {
-                return if (event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
-                    true
-                } else {
-                    super.dispatchPopulateAccessibilityEvent(host, event)
-                }
-            }
-        })
-    }
-
-    @VisibleForTesting
-    internal fun updateOverlayParams(display: Display, bounds: Rect) {
-        val isNaturalOrientation = display.isNaturalOrientation()
-        val size = windowManager.maximumWindowMetrics.bounds
-        val displayWidth = if (isNaturalOrientation) size.width() else size.height()
-        val displayHeight = if (isNaturalOrientation) size.height() else size.width()
-        val boundsWidth = if (isNaturalOrientation) bounds.width() else bounds.height()
-        val boundsHeight = if (isNaturalOrientation) bounds.height() else bounds.width()
-        val sensorBounds = if (overlayOffsets.isYAligned()) {
-            Rect(
-                displayWidth - boundsWidth,
-                overlayOffsets.sensorLocationY,
-                displayWidth,
-                overlayOffsets.sensorLocationY + boundsHeight
-            )
-        } else {
-            Rect(
-                overlayOffsets.sensorLocationX,
-                0,
-                overlayOffsets.sensorLocationX + boundsWidth,
-                boundsHeight
-            )
-        }
-
-        RotationUtils.rotateBounds(
-            sensorBounds,
-            Rect(0, 0, displayWidth, displayHeight),
-            display.rotation
-        )
-
-        overlayViewParams.x = sensorBounds.left
-        overlayViewParams.y = sensorBounds.top
-        windowManager.updateViewLayout(overlayView, overlayViewParams)
-    }
-
-    private fun updateOverlayVisibility(view: View) {
-        if (view != overlayView) {
-            return
-        }
-        // hide after a few seconds if the sensor is oriented down and there are
-        // large overlapping system bars
-        val rotation = context.display?.rotation
-        if (windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar() &&
-            ((rotation == Surface.ROTATION_270 && overlayOffsets.isYAligned()) ||
-                    (rotation == Surface.ROTATION_180 && !overlayOffsets.isYAligned()))) {
-            overlayHideAnimator = view.animate()
-                .alpha(0f)
-                .setStartDelay(3_000)
-                .setDuration(animationDuration)
-                .setListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator) {
-                        view.visibility = View.GONE
-                        overlayHideAnimator = null
-                    }
-                })
-        } else {
-            overlayHideAnimator?.cancel()
-            overlayHideAnimator = null
-            view.alpha = 1f
-            view.visibility = View.VISIBLE
-        }
-    }
-}
-
-private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorPropertiesInternal?
-    get() = this?.sensorPropertiesInternal?.firstOrNull { it.isAnySidefpsType }
-
-/** Returns [True] when the device has a side fingerprint sensor. */
-fun FingerprintManager?.hasSideFpsSensor(): Boolean = this?.sideFpsSensorProperties != null
-
-@BiometricOverlayConstants.ShowReason
-private fun Int.isReasonToShow(activityTaskManager: ActivityTaskManager): Boolean = when (this) {
-    REASON_AUTH_KEYGUARD -> false
-    REASON_AUTH_SETTINGS -> when (activityTaskManager.topClass()) {
-        // TODO(b/186176653): exclude fingerprint overlays from this list view
-        "com.android.settings.biometrics.fingerprint.FingerprintSettings" -> false
-        else -> true
-    }
-    else -> true
-}
-
-private fun ActivityTaskManager.topClass(): String =
-    getTasks(1).firstOrNull()?.topActivity?.className ?: ""
-
-@RawRes
-private fun Display.asSideFpsAnimation(yAligned: Boolean): Int = when (rotation) {
-    Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
-    Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
-    else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
-}
-
-private fun Display.asSideFpsAnimationRotation(yAligned: Boolean): Float = when (rotation) {
-    Surface.ROTATION_90 -> if (yAligned) 0f else 180f
-    Surface.ROTATION_180 -> 180f
-    Surface.ROTATION_270 -> if (yAligned) 180f else 0f
-    else -> 0f
-}
-
-private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
-
-private fun Display.isNaturalOrientation(): Boolean =
-    rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
-
-private fun WindowInsets.hasBigNavigationBar(): Boolean =
-    getInsets(WindowInsets.Type.navigationBars()).bottom >= 70
-
-private fun LottieAnimationView.addOverlayDynamicColor(context: Context) {
-    fun update() {
-        val c = context.getColor(R.color.biometric_dialog_accent)
-        for (key in listOf(".blue600", ".blue400")) {
-            addValueCallback(
-                KeyPath(key, "**"),
-                LottieProperty.COLOR_FILTER
-            ) { PorterDuffColorFilter(c, PorterDuff.Mode.SRC_ATOP) }
-        }
-    }
-
-    if (composition != null) {
-        update()
-    } else {
-        addLottieOnCompositionLoadedListener { update() }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING
new file mode 100644
index 0000000..794eba4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING
@@ -0,0 +1,16 @@
+{
+  "presubmit": [
+    {
+      // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
+      "name": "SystemUIGoogleBiometricsScreenshotTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
index ad96612..bdad413 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -37,6 +37,9 @@
     private float mDialogSuggestedAlpha = 1f;
     private float mNotificationShadeExpansion = 0f;
 
+    // Used for Udfps ellipse detection when flag is true, set by AnimationViewController
+    boolean mUseExpandedOverlay = false;
+
     // mAlpha takes into consideration the status bar expansion amount and dialog suggested alpha
     private int mAlpha;
     boolean mPauseAuth;
@@ -118,6 +121,24 @@
     }
 
     /**
+     * Converts coordinates of RectF relative to the screen to coordinates relative to this view.
+     *
+     * @param bounds RectF based off screen coordinates in current orientation
+     */
+    RectF getBoundsRelativeToView(RectF bounds) {
+        int[] pos = getLocationOnScreen();
+
+        RectF output = new RectF(
+                bounds.left - pos[0],
+                bounds.top - pos[1],
+                bounds.right - pos[0],
+                bounds.bottom - pos[1]
+        );
+
+        return output;
+    }
+
+    /**
      * Set the suggested alpha based on whether a dialog was recently shown or hidden.
      * @param dialogSuggestedAlpha value from 0f to 1f.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 48c60a0..19b0548 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,12 +52,21 @@
 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.biometrics.udfps.InteractionEvent;
+import com.android.systemui.biometrics.udfps.NormalizedTouchData;
+import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
+import com.android.systemui.biometrics.udfps.TouchProcessor;
+import com.android.systemui.biometrics.udfps.TouchProcessorResult;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeReceiver;
@@ -63,6 +74,7 @@
 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.PrimaryBouncerInteractor;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -77,6 +89,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;
@@ -99,7 +113,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;
 
@@ -133,10 +147,12 @@
     @NonNull private final LatencyTracker mLatencyTracker;
     @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
     @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
+    @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+    @Nullable private final TouchProcessor mTouchProcessor;
 
     // 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;
@@ -201,6 +217,11 @@
         }
     };
 
+    @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,
@@ -219,7 +240,8 @@
                             mUnlockedScreenOffAnimationController,
                             mUdfpsDisplayMode, requestId, reason, callback,
                             (view, event, fromUdfpsView) -> onTouch(requestId, event,
-                                    fromUdfpsView), mActivityLaunchAnimator)));
+                                    fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
+                            mPrimaryBouncerInteractor)));
         }
 
         @Override
@@ -255,7 +277,7 @@
                     }
                     mAcquiredReceived = true;
                     final UdfpsView view = mOverlay.getOverlayView();
-                    if (view != null) {
+                    if (view != null && isOptical()) {
                         unconfigureDisplay(view);
                     }
                     if (acquiredGood) {
@@ -296,31 +318,56 @@
                 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");
         }
 
         if (!mOverlayParams.equals(overlayParams)) {
             mOverlayParams = overlayParams;
 
-            final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth();
+            final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateBouncer();
 
             // When the bounds change it's always necessary to re-create the overlay's window with
             // new LayoutParams. If the overlay needs to be shown, this will re-create and show the
             // overlay with the updated LayoutParams. Otherwise, the overlay will remain hidden.
             redrawOverlay();
             if (wasShowingAltAuth) {
-                mKeyguardViewManager.showGenericBouncer(true);
+                mKeyguardViewManager.showBouncer(true);
             }
         }
     }
@@ -330,7 +377,7 @@
         mAuthControllerUpdateUdfpsLocation = r;
     }
 
-    public void setUdfpsDisplayMode(UdfpsDisplayModeProvider udfpsDisplayMode) {
+    public void setUdfpsDisplayMode(@NonNull UdfpsDisplayModeProvider udfpsDisplayMode) {
         mUdfpsDisplayMode = udfpsDisplayMode;
     }
 
@@ -421,8 +468,99 @@
         return portraitTouch;
     }
 
+    private void tryDismissingKeyguard() {
+        if (!mOnFingerDown) {
+            playStartHaptic();
+        }
+        mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */);
+        mAttemptedToDismissKeyguard = true;
+    }
+
     @VisibleForTesting
     boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
+        if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+            return newOnTouch(requestId, event, fromUdfpsView);
+        } else {
+            return oldOnTouch(requestId, event, fromUdfpsView);
+        }
+    }
+
+    private boolean newOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
+        if (!fromUdfpsView) {
+            Log.e(TAG, "ignoring the touch injected from outside of UdfpsView");
+            return false;
+        }
+        if (mOverlay == null) {
+            Log.w(TAG, "ignoring onTouch with null overlay");
+            return false;
+        }
+        if (!mOverlay.matchesRequestId(requestId)) {
+            Log.w(TAG, "ignoring stale touch event: " + requestId + " current: "
+                    + mOverlay.getRequestId());
+            return false;
+        }
+
+        final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId,
+                mOverlayParams);
+        if (result instanceof TouchProcessorResult.Failure) {
+            Log.w(TAG, ((TouchProcessorResult.Failure) result).getReason());
+            return false;
+        }
+
+        final TouchProcessorResult.ProcessedTouch processedTouch =
+                (TouchProcessorResult.ProcessedTouch) result;
+        final NormalizedTouchData data = processedTouch.getTouchData();
+
+        mActivePointerId = processedTouch.getPointerOnSensorId();
+        switch (processedTouch.getEvent()) {
+            case DOWN:
+                if (shouldTryToDismissKeyguard()) {
+                    tryDismissingKeyguard();
+                }
+                onFingerDown(requestId,
+                        data.getPointerId(),
+                        data.getX(),
+                        data.getY(),
+                        data.getMinor(),
+                        data.getMajor(),
+                        data.getOrientation(),
+                        data.getTime(),
+                        data.getGestureStart(),
+                        mStatusBarStateController.isDozing());
+                break;
+
+            case UP:
+            case CANCEL:
+                if (InteractionEvent.CANCEL.equals(processedTouch.getEvent())) {
+                    Log.w(TAG, "This is a CANCEL event that's reported as an UP event!");
+                }
+                mAttemptedToDismissKeyguard = false;
+                onFingerUp(requestId,
+                        mOverlay.getOverlayView(),
+                        data.getPointerId(),
+                        data.getX(),
+                        data.getY(),
+                        data.getMinor(),
+                        data.getMajor(),
+                        data.getOrientation(),
+                        data.getTime(),
+                        data.getGestureStart(),
+                        mStatusBarStateController.isDozing());
+                mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION);
+                break;
+
+
+            default:
+                break;
+        }
+
+        // We should only consume touches that are within the sensor. By returning "false" for
+        // touches outside of the sensor, we let other UI components consume these events and act on
+        // them appropriately.
+        return processedTouch.getTouchData().isWithinSensor(mOverlayParams.getNativeSensorBounds());
+    }
+
+    private boolean oldOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
         if (mOverlay == null) {
             Log.w(TAG, "ignoring onTouch with null overlay");
             return false;
@@ -434,7 +572,6 @@
         }
 
         final UdfpsView udfpsView = mOverlay.getOverlayView();
-        final boolean isDisplayConfigured = udfpsView.isDisplayConfigured();
         boolean handled = false;
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_OUTSIDE:
@@ -453,7 +590,7 @@
                     mVelocityTracker.clear();
                 }
 
-                boolean withinSensorArea =
+                final boolean withinSensorArea =
                         isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView);
                 if (withinSensorArea) {
                     Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0);
@@ -468,11 +605,7 @@
                 }
                 if ((withinSensorArea || fromUdfpsView) && shouldTryToDismissKeyguard()) {
                     Log.v(TAG, "onTouch | dismiss keyguard ACTION_DOWN");
-                    if (!mOnFingerDown) {
-                        playStartHaptic();
-                    }
-                    mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */);
-                    mAttemptedToDismissKeyguard = true;
+                    tryDismissingKeyguard();
                 }
 
                 Trace.endSection();
@@ -485,17 +618,13 @@
                         ? event.getPointerId(0)
                         : event.findPointerIndex(mActivePointerId);
                 if (idx == event.getActionIndex()) {
-                    boolean actionMoveWithinSensorArea =
+                    final boolean actionMoveWithinSensorArea =
                             isWithinSensorArea(udfpsView, event.getX(idx), event.getY(idx),
                                     fromUdfpsView);
                     if ((fromUdfpsView || actionMoveWithinSensorArea)
                             && shouldTryToDismissKeyguard()) {
                         Log.v(TAG, "onTouch | dismiss keyguard ACTION_MOVE");
-                        if (!mOnFingerDown) {
-                            playStartHaptic();
-                        }
-                        mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */);
-                        mAttemptedToDismissKeyguard = true;
+                        tryDismissingKeyguard();
                         break;
                     }
                     // Map the touch to portrait mode if the device is in landscape mode.
@@ -518,15 +647,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;
@@ -620,7 +748,9 @@
             @NonNull LatencyTracker latencyTracker,
             @NonNull ActivityLaunchAnimator activityLaunchAnimator,
             @NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
-            @BiometricsBackground Executor biometricsExecutor) {
+            @NonNull @BiometricsBackground Executor biometricsExecutor,
+            @NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
+            @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -650,7 +780,21 @@
         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;
+        mPrimaryBouncerInteractor = primaryBouncerInteractor;
+
+        mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
+                ? singlePointerTouchProcessor : null;
+
+        mDumpManager.registerDumpable(TAG, this);
 
         mOrientationListener = new BiometricDisplayListener(
                 context,
@@ -740,8 +884,8 @@
                 onFingerUp(mOverlay.getRequestId(), oldView);
             }
             final boolean removed = mOverlay.hide();
-            if (mKeyguardViewManager.isShowingAlternateAuth()) {
-                mKeyguardViewManager.resetAlternateAuth(true);
+            if (mKeyguardViewManager.isShowingAlternateBouncer()) {
+                mKeyguardViewManager.hideAlternateBouncer(true);
             }
             Log.v(TAG, "hideUdfpsOverlay | removing window: " + removed);
         } else {
@@ -781,7 +925,7 @@
                 Log.v(TAG, "aod lock icon long-press rejected by the falsing manager.");
                 return;
             }
-            mKeyguardViewManager.showBouncer(true);
+            mKeyguardViewManager.showPrimaryBouncer(true);
 
             // play the same haptic as the LockIconViewController longpress
             mVibrator.vibrate(
@@ -847,11 +991,44 @@
         mIsAodInterruptActive = false;
     }
 
+    private boolean isOptical() {
+        return mSensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
+    }
+
     public boolean isFingerDown() {
         return mOnFingerDown;
     }
 
-    private void onFingerDown(long requestId, int x, int y, float minor, float major) {
+    private void onFingerDown(
+            long requestId,
+            int x,
+            int y,
+            float minor,
+            float major) {
+        onFingerDown(
+                requestId,
+                MotionEvent.INVALID_POINTER_ID /* pointerId */,
+                x,
+                y,
+                minor,
+                major,
+                0f /* orientation */,
+                0L /* time */,
+                0L /* gestureStart */,
+                false /* isAod */);
+    }
+
+    private void onFingerDown(
+            long requestId,
+            int pointerId,
+            float x,
+            float y,
+            float minor,
+            float major,
+            float orientation,
+            long time,
+            long gestureStart,
+            boolean isAod) {
         mExecution.assertIsMainThread();
 
         if (mOverlay == null) {
@@ -863,7 +1040,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);
@@ -878,7 +1057,7 @@
         mOnFingerDown = true;
         if (mAlternateTouchProvider != null) {
             mBiometricExecutor.execute(() -> {
-                mAlternateTouchProvider.onPointerDown(requestId, x, y, minor, major);
+                mAlternateTouchProvider.onPointerDown(requestId, (int) x, (int) y, minor, major);
             });
             mFgExecutor.execute(() -> {
                 if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
@@ -886,11 +1065,17 @@
                 }
             });
         } else {
-            mFingerprintManager.onPointerDown(requestId, mSensorId, x, y, minor, major);
+            if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+                mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y,
+                        minor, major, orientation, time, gestureStart, isAod);
+            } else {
+                mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, (int) x,
+                        (int) 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(() -> {
@@ -898,7 +1083,7 @@
                         mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
                     });
                 } else {
-                    mFingerprintManager.onUiReady(requestId, mSensorId);
+                    mFingerprintManager.onUiReady(requestId, mSensorProps.sensorId);
                     mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
                 }
             });
@@ -910,6 +1095,32 @@
     }
 
     private void onFingerUp(long requestId, @NonNull UdfpsView view) {
+        onFingerUp(
+                requestId,
+                view,
+                MotionEvent.INVALID_POINTER_ID /* pointerId */,
+                0f /* x */,
+                0f /* y */,
+                0f /* minor */,
+                0f /* major */,
+                0f /* orientation */,
+                0L /* time */,
+                0L /* gestureStart */,
+                false /* isAod */);
+    }
+
+    private void onFingerUp(
+            long requestId,
+            @NonNull UdfpsView view,
+            int pointerId,
+            float x,
+            float y,
+            float minor,
+            float major,
+            float orientation,
+            long time,
+            long gestureStart,
+            boolean isAod) {
         mExecution.assertIsMainThread();
         mActivePointerId = -1;
         mAcquiredReceived = false;
@@ -924,15 +1135,21 @@
                     }
                 });
             } else {
-                mFingerprintManager.onPointerUp(requestId, mSensorId);
+                if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+                    mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x,
+                            y, minor, major, orientation, time, gestureStart, isAod);
+                } else {
+                    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..8db4927 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -48,6 +48,9 @@
 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.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -70,35 +73,38 @@
  */
 @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 primaryBouncerInteractor: PrimaryBouncerInteractor,
+        private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
 ) {
     /** The view, when [isShowing], or null. */
     var overlayView: UdfpsView? = null
         private set
 
     private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams()
+    private var sensorBounds: Rect = Rect()
 
     private var overlayTouchListener: TouchExplorationStateChangeListener? = null
 
@@ -116,6 +122,10 @@
         privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
         // Avoid announcing window title.
         accessibilityTitle = " "
+
+        if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+            inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
+        }
     }
 
     /** A helper if the [requestReason] was due to enrollment. */
@@ -156,6 +166,7 @@
     fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean {
         if (overlayView == null) {
             overlayParams = params
+            sensorBounds = Rect(params.sensorBounds)
             try {
                 overlayView = (inflater.inflate(
                     R.layout.udfps_view, null, false
@@ -174,6 +185,7 @@
                     }
 
                     windowManager.addView(this, coreLayoutParams.updateDimensions(animation))
+                    sensorRect = sensorBounds
                     touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled
                     overlayTouchListener = TouchExplorationStateChangeListener {
                         if (accessibilityManager.isTouchExplorationEnabled) {
@@ -190,6 +202,7 @@
                         overlayTouchListener!!
                     )
                     overlayTouchListener?.onTouchExplorationStateChanged(true)
+                    useExpandedOverlay = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
                 }
             } catch (e: RuntimeException) {
                 Log.e(TAG, "showUdfpsOverlay | failed to add window", e)
@@ -221,13 +234,14 @@
             REASON_ENROLL_ENROLLING -> {
                 UdfpsEnrollViewController(
                     view.addUdfpsView(R.layout.udfps_enroll_view) {
-                        updateSensorLocation(overlayParams.sensorBounds)
+                        updateSensorLocation(sensorBounds)
                     },
                     enrollHelper ?: throw IllegalStateException("no enrollment helper"),
                     statusBarStateController,
                     shadeExpansionStateManager,
                     dialogManager,
                     dumpManager,
+                    featureFlags,
                     overlayParams.scaleFactor
                 )
             }
@@ -246,7 +260,9 @@
                     unlockedScreenOffAnimationController,
                     dialogManager,
                     controller,
-                    activityLaunchAnimator
+                    activityLaunchAnimator,
+                    featureFlags,
+                    primaryBouncerInteractor
                 )
             }
             REASON_AUTH_BP -> {
@@ -414,7 +430,12 @@
         }
 
         // Original sensorBounds assume portrait mode.
-        val rotatedSensorBounds = Rect(overlayParams.sensorBounds)
+        var rotatedBounds =
+            if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+                Rect(overlayParams.overlayBounds)
+            } else {
+                Rect(overlayParams.sensorBounds)
+            }
 
         val rot = overlayParams.rotation
         if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
@@ -428,18 +449,27 @@
             } else {
                 Log.v(TAG, "Rotate UDFPS bounds " + Surface.rotationToString(rot))
                 RotationUtils.rotateBounds(
-                    rotatedSensorBounds,
+                    rotatedBounds,
                     overlayParams.naturalDisplayWidth,
                     overlayParams.naturalDisplayHeight,
                     rot
                 )
+
+                if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+                    RotationUtils.rotateBounds(
+                            sensorBounds,
+                            overlayParams.naturalDisplayWidth,
+                            overlayParams.naturalDisplayHeight,
+                            rot
+                    )
+                }
             }
         }
 
-        x = rotatedSensorBounds.left - paddingX
-        y = rotatedSensorBounds.top - paddingY
-        height = rotatedSensorBounds.height() + 2 * paddingX
-        width = rotatedSensorBounds.width() + 2 * paddingY
+        x = rotatedBounds.left - paddingX
+        y = rotatedBounds.top - paddingY
+        height = rotatedBounds.height() + 2 * paddingX
+        width = rotatedBounds.width() + 2 * paddingY
 
         return this
     }
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/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
index 1e35958..3e1c4e5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
@@ -20,6 +20,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.PointF;
@@ -28,6 +29,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Looper;
+import android.util.AttributeSet;
 import android.view.animation.AccelerateDecelerateInterpolator;
 
 import androidx.annotation.NonNull;
@@ -68,25 +70,29 @@
     private boolean mShouldShowTipHint = false;
     private boolean mShouldShowEdgeHint = false;
 
-    UdfpsEnrollDrawable(@NonNull Context context) {
+    private int mEnrollIcon;
+    private int mMovingTargetFill;
+
+    UdfpsEnrollDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context);
 
+        loadResources(context, attrs);
         mSensorOutlinePaint = new Paint(0 /* flags */);
         mSensorOutlinePaint.setAntiAlias(true);
-        mSensorOutlinePaint.setColor(context.getColor(R.color.udfps_moving_target_fill));
+        mSensorOutlinePaint.setColor(mMovingTargetFill);
         mSensorOutlinePaint.setStyle(Paint.Style.FILL);
 
         mBlueFill = new Paint(0 /* flags */);
         mBlueFill.setAntiAlias(true);
-        mBlueFill.setColor(context.getColor(R.color.udfps_moving_target_fill));
+        mBlueFill.setColor(mMovingTargetFill);
         mBlueFill.setStyle(Paint.Style.FILL);
 
         mMovingTargetFpIcon = context.getResources()
                 .getDrawable(R.drawable.ic_kg_fingerprint, null);
-        mMovingTargetFpIcon.setTint(context.getColor(R.color.udfps_enroll_icon));
+        mMovingTargetFpIcon.setTint(mEnrollIcon);
         mMovingTargetFpIcon.mutate();
 
-        getFingerprintDrawable().setTint(context.getColor(R.color.udfps_enroll_icon));
+        getFingerprintDrawable().setTint(mEnrollIcon);
 
         mTargetAnimListener = new Animator.AnimatorListener() {
             @Override
@@ -105,6 +111,16 @@
         };
     }
 
+    void loadResources(Context context, @Nullable AttributeSet attrs) {
+        final TypedArray ta = context.obtainStyledAttributes(attrs,
+                R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
+                R.style.BiometricsEnrollStyle);
+        mEnrollIcon = ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollIcon, 0);
+        mMovingTargetFill = ta.getColor(
+                R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
+        ta.recycle();
+    }
+
     void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) {
         mEnrollHelper = helper;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index 49e378e..66a8424 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -18,6 +18,7 @@
 
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
@@ -26,6 +27,7 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.util.AttributeSet;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
@@ -93,18 +95,25 @@
     @Nullable private ValueAnimator mCheckmarkAnimator;
     @NonNull private final ValueAnimator.AnimatorUpdateListener mCheckmarkUpdateListener;
 
-    public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
+    private int mMovingTargetFill;
+    private int mMovingTargetFillError;
+    private int mEnrollProgress;
+    private int mEnrollProgressHelp;
+    private int mEnrollProgressHelpWithTalkback;
+
+    public UdfpsEnrollProgressBarDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
         mContext = context;
+
+        loadResources(context, attrs);
         mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
-        mProgressColor = context.getColor(R.color.udfps_enroll_progress);
+        mProgressColor = mEnrollProgress;
         final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
         mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
+        mOnFirstBucketFailedColor = mMovingTargetFillError;
         if (!mIsAccessibilityEnabled) {
-            mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
-            mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
+            mHelpColor = mEnrollProgressHelp;
         } else {
-            mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback);
-            mOnFirstBucketFailedColor = mHelpColor;
+            mHelpColor = mEnrollProgressHelpWithTalkback;
         }
         mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
         mCheckmarkDrawable.mutate();
@@ -112,7 +121,7 @@
 
         mBackgroundPaint = new Paint();
         mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
-        mBackgroundPaint.setColor(context.getColor(R.color.udfps_moving_target_fill));
+        mBackgroundPaint.setColor(mMovingTargetFill);
         mBackgroundPaint.setAntiAlias(true);
         mBackgroundPaint.setStyle(Paint.Style.STROKE);
         mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
@@ -148,6 +157,23 @@
         };
     }
 
+    void loadResources(Context context, @Nullable AttributeSet attrs) {
+        final TypedArray ta = context.obtainStyledAttributes(attrs,
+                R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
+                R.style.BiometricsEnrollStyle);
+        mMovingTargetFill = ta.getColor(
+                R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
+        mMovingTargetFillError = ta.getColor(
+                R.styleable.BiometricsEnrollView_biometricsMovingTargetFillError, 0);
+        mEnrollProgress = ta.getColor(
+                R.styleable.BiometricsEnrollView_biometricsEnrollProgress, 0);
+        mEnrollProgressHelp = ta.getColor(
+                R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelp, 0);
+        mEnrollProgressHelpWithTalkback = ta.getColor(
+                R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelpWithTalkback, 0);
+        ta.recycle();
+    }
+
     void onEnrollmentProgress(int remaining, int totalSteps) {
         mAfterFirstTouch = true;
         updateState(remaining, totalSteps, false /* showingHelp */);
@@ -197,6 +223,7 @@
             }
         }
 
+        mShowingHelp = showingHelp;
         mRemainingSteps = remainingSteps;
         mTotalSteps = totalSteps;
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 69c37b2..1cc4141 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
@@ -41,10 +42,13 @@
     @NonNull private ImageView mFingerprintView;
     @NonNull private ImageView mFingerprintProgressView;
 
+    private LayoutParams mProgressParams;
+    private float mProgressBarRadius;
+
     public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
-        mFingerprintDrawable = new UdfpsEnrollDrawable(mContext);
-        mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context);
+        mFingerprintDrawable = new UdfpsEnrollDrawable(mContext, attrs);
+        mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, attrs);
         mHandler = new Handler(Looper.getMainLooper());
     }
 
@@ -57,6 +61,32 @@
     }
 
     @Override
+    void onSensorRectUpdated(RectF bounds) {
+        if (mUseExpandedOverlay) {
+            RectF converted = getBoundsRelativeToView(bounds);
+
+            mProgressParams = new LayoutParams(
+                    (int) (converted.width() + mProgressBarRadius * 2),
+                    (int) (converted.height() + mProgressBarRadius * 2));
+            mProgressParams.setMargins(
+                    (int) (converted.left - mProgressBarRadius),
+                    (int) (converted.top - mProgressBarRadius),
+                    (int) (converted.right + mProgressBarRadius),
+                    (int) (converted.bottom + mProgressBarRadius)
+            );
+
+            mFingerprintProgressView.setLayoutParams(mProgressParams);
+            super.onSensorRectUpdated(converted);
+        } else {
+            super.onSensorRectUpdated(bounds);
+        }
+    }
+
+    void setProgressBarRadius(float radius) {
+        mProgressBarRadius = radius;
+    }
+
+    @Override
     public UdfpsDrawable getDrawable() {
         return mFingerprintDrawable;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index e01273f..4017665 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -21,6 +21,8 @@
 
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -57,6 +59,7 @@
             @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
             @NonNull SystemUIDialogManager systemUIDialogManager,
             @NonNull DumpManager dumpManager,
+            @NonNull FeatureFlags featureFlags,
             float scaleFactor) {
         super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
                 dumpManager);
@@ -64,6 +67,11 @@
                 R.integer.config_udfpsEnrollProgressBar));
         mEnrollHelper = enrollHelper;
         mView.setEnrollHelper(mEnrollHelper);
+        mView.setProgressBarRadius(mEnrollProgressBarRadius);
+
+        if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+            mView.mUseExpandedOverlay = true;
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index bc274a0..339b8ca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
+import android.graphics.RectF;
 import android.util.AttributeSet;
 import android.util.MathUtils;
 import android.view.View;
@@ -75,6 +76,8 @@
     private int mAnimationType = ANIMATION_NONE;
     private boolean mFullyInflated;
 
+    private LayoutParams mParams;
+
     public UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         mFingerprintDrawable = new UdfpsFpDrawable(context);
@@ -239,6 +242,22 @@
         updateAlpha();
     }
 
+    @Override
+    void onSensorRectUpdated(RectF bounds) {
+        super.onSensorRectUpdated(bounds);
+
+        if (mUseExpandedOverlay) {
+            mParams = new LayoutParams((int) bounds.width(), (int) bounds.height());
+            RectF converted = getBoundsRelativeToView(bounds);
+            mParams.setMargins(
+                    (int) converted.left,
+                    (int) converted.top,
+                    (int) converted.right,
+                    (int) converted.bottom
+            );
+        }
+    }
+
     /**
      * Animates in the bg protection circle behind the fp icon to highlight the icon.
      */
@@ -277,6 +296,7 @@
         pw.println("    mUdfpsRequested=" + mUdfpsRequested);
         pw.println("    mInterpolatedDarkAmount=" + mInterpolatedDarkAmount);
         pw.println("    mAnimationType=" + mAnimationType);
+        pw.println("    mUseExpandedOverlay=" + mUseExpandedOverlay);
     }
 
     private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener =
@@ -291,7 +311,12 @@
             updatePadding();
             updateColor();
             updateAlpha();
-            parent.addView(view);
+
+            if (mUseExpandedOverlay) {
+                parent.addView(view, mParams);
+            } else {
+                parent.addView(view);
+            }
 
             // requires call to invalidate to update the color
             mLockScreenFp.addValueCallback(
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..63144fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -0,0 +1,560 @@
+/*
+ * 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.PrimaryBouncerInteractor
+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.PrimaryBouncerExpansionCallback
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.AlternateBouncer
+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.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 primaryBouncerInteractor: PrimaryBouncerInteractor
+) :
+    UdfpsAnimationViewController<UdfpsKeyguardView>(
+        view,
+        statusBarStateController,
+        shadeExpansionStateManager,
+        systemUIDialogManager,
+        dumpManager
+    ) {
+    private val useExpandedOverlay: Boolean =
+        featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
+    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 mPrimaryBouncerExpansionCallback: PrimaryBouncerExpansionCallback =
+        object : PrimaryBouncerExpansionCallback {
+            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.isPrimaryBouncerInTransit) {
+                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
+                }
+
+                // Forwarding touches not needed with expanded overlay
+                if (useExpandedOverlay) {
+                    return
+                } else {
+                    udfpsController.onTouch(event)
+                }
+            }
+        }
+
+    private val mAlternateBouncer: AlternateBouncer =
+        object : AlternateBouncer {
+            override fun showAlternateBouncer(): Boolean {
+                return showUdfpsBouncer(true)
+            }
+
+            override fun hideAlternateBouncer(): Boolean {
+                return showUdfpsBouncer(false)
+            }
+
+            override fun isShowingAlternateBouncer(): 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.setAlternateBouncer(mAlternateBouncer)
+    }
+
+    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 {
+            primaryBouncerInteractor.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.primaryBouncer
+            bouncer?.expansion?.let {
+                mPrimaryBouncerExpansionCallback.onExpansionChanged(it)
+                bouncer.addBouncerExpansionCallback(mPrimaryBouncerExpansionCallback)
+            }
+            updateBouncerHiddenAmount()
+        }
+        configurationController.addCallback(configurationListener)
+        shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
+        updateScaleFactor()
+        view.updatePadding()
+        updateAlpha()
+        updatePauseAuth()
+        keyguardViewManager.setAlternateBouncer(mAlternateBouncer)
+        lockScreenShadeTransitionController.udfpsKeyguardViewController = this
+        activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
+        view.mUseExpandedOverlay = useExpandedOverlay
+    }
+
+    override fun onViewDetached() {
+        super.onViewDetached()
+        faceDetectRunning = false
+        keyguardStateController.removeCallback(keyguardStateControllerCallback)
+        statusBarStateController.removeCallback(stateListener)
+        keyguardViewManager.removeAlternateAuthInterceptor(mAlternateBouncer)
+        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.primaryBouncer?.removeBouncerExpansionCallback(
+                mPrimaryBouncerExpansionCallback
+            )
+        }
+    }
+
+    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.isShowingAlternateBouncer
+        }
+    }
+
+    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.showPrimaryBouncer(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.isShowingAlternateBouncer
+        if (altBouncerShowing || !keyguardViewManager.primaryBouncerIsOrWillBeShowing()) {
+            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/UdfpsOverlayParams.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt
index c23b0f0..7f3846c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt
@@ -7,17 +7,23 @@
 /**
  * Collection of parameters that define an under-display fingerprint sensor (UDFPS) overlay.
  *
- * @property sensorBounds coordinates of the bounding box around the sensor, in natural orientation,
- *     in pixels, for the current resolution.
- * @property naturalDisplayWidth width of the physical display, in natural orientation, in pixels,
- *     for the current resolution.
- * @property naturalDisplayHeight height of the physical display, in natural orientation, in pixels,
- *     for the current resolution.
- * @property scaleFactor ratio of a dimension in the current resolution to the corresponding
- *     dimension in the native resolution.
- * @property rotation current rotation of the display.
+ * [sensorBounds] coordinates of the bounding box around the sensor in natural orientation, in
+ * pixels, for the current resolution.
+ *
+ * [overlayBounds] coordinates of the UI overlay in natural orientation, in pixels, for the current
+ * resolution.
+ *
+ * [naturalDisplayWidth] width of the physical display in natural orientation, in pixels, for the
+ * current resolution.
+ *
+ * [naturalDisplayHeight] height of the physical display in natural orientation, in pixels, for the
+ * current resolution.
+ *
+ * [scaleFactor] ratio of a dimension in the current resolution to the corresponding dimension in
+ * the native resolution.
+ *
+ * [rotation] current rotation of the display.
  */
-
 data class UdfpsOverlayParams(
     val sensorBounds: Rect = Rect(),
     val overlayBounds: Rect = Rect(),
@@ -26,17 +32,21 @@
     val scaleFactor: Float = 1f,
     @Rotation val rotation: Int = Surface.ROTATION_0
 ) {
+
+    /** Same as [sensorBounds], but in native resolution. */
+    val nativeSensorBounds = Rect(sensorBounds).apply { scale(1f / scaleFactor) }
+
     /** See [android.view.DisplayInfo.logicalWidth] */
-    val logicalDisplayWidth
-        get() = if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
+    val logicalDisplayWidth =
+        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
             naturalDisplayHeight
         } else {
             naturalDisplayWidth
         }
 
     /** See [android.view.DisplayInfo.logicalHeight] */
-    val logicalDisplayHeight
-        get() = if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
+    val logicalDisplayHeight =
+        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
             naturalDisplayWidth
         } else {
             naturalDisplayHeight
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
index da50f1c..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.
@@ -67,6 +75,12 @@
             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)
         }
@@ -80,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) {
@@ -125,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/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index a15456d..4a8877e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -20,6 +20,7 @@
 import android.graphics.Color
 import android.graphics.Paint
 import android.graphics.PointF
+import android.graphics.Rect
 import android.graphics.RectF
 import android.util.AttributeSet
 import android.util.Log
@@ -38,9 +39,12 @@
     attrs: AttributeSet?
 ) : FrameLayout(context, attrs), DozeReceiver {
 
+    // Use expanded overlay when feature flag is true, set by UdfpsViewController
+    var useExpandedOverlay: Boolean = false
+
     // sensorRect may be bigger than the sensor. True sensor dimensions are defined in
     // overlayParams.sensorBounds
-    private val sensorRect = RectF()
+    var sensorRect = Rect()
     private var mUdfpsDisplayMode: UdfpsDisplayModeProvider? = null
     private val debugTextPaint = Paint().apply {
         isAntiAlias = true
@@ -92,13 +96,19 @@
         val paddingX = animationViewController?.paddingX ?: 0
         val paddingY = animationViewController?.paddingY ?: 0
 
-        sensorRect.set(
-            paddingX.toFloat(),
-            paddingY.toFloat(),
-            (overlayParams.sensorBounds.width() + paddingX).toFloat(),
-            (overlayParams.sensorBounds.height() + paddingY).toFloat()
-        )
-        animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+        // Updates sensor rect in relation to the overlay view
+        if (useExpandedOverlay) {
+            animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+        } else {
+            sensorRect.set(
+                    paddingX,
+                    paddingY,
+                    (overlayParams.sensorBounds.width() + paddingX),
+                    (overlayParams.sensorBounds.height() + paddingY)
+            )
+
+            animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+        }
     }
 
     fun onTouchOutsideView() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
new file mode 100644
index 0000000..001fed7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
@@ -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.systemui.biometrics.dagger
+
+import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector
+import com.android.systemui.biometrics.udfps.EllipseOverlapDetector
+import com.android.systemui.biometrics.udfps.OverlapDetector
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import dagger.Module
+import dagger.Provides
+
+/** Dagger module for all things UDFPS. TODO(b/260558624): Move to BiometricsModule. */
+@Module
+interface UdfpsModule {
+    companion object {
+
+        @Provides
+        @SysUISingleton
+        fun providesOverlapDetector(featureFlags: FeatureFlags): OverlapDetector {
+            return if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+                EllipseOverlapDetector()
+            } else {
+                BoundingBoxOverlapDetector()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt
new file mode 100644
index 0000000..79a0acb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.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.biometrics.udfps
+
+import android.graphics.Rect
+import com.android.systemui.dagger.SysUISingleton
+
+/** Returns whether the touch coordinates are within the sensor's bounding box. */
+@SysUISingleton
+class BoundingBoxOverlapDetector : OverlapDetector {
+    override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean =
+        touchData.isWithinSensor(nativeSensorBounds)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
new file mode 100644
index 0000000..8572242
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.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.biometrics.udfps
+
+import android.graphics.Point
+import android.graphics.Rect
+import com.android.systemui.dagger.SysUISingleton
+import kotlin.math.cos
+import kotlin.math.pow
+import kotlin.math.sin
+
+/**
+ * Approximates the touch as an ellipse and determines whether the ellipse has a sufficient overlap
+ * with the sensor.
+ */
+@SysUISingleton
+class EllipseOverlapDetector(private val neededPoints: Int = 2) : OverlapDetector {
+
+    override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean {
+        val points = calculateSensorPoints(nativeSensorBounds)
+        return points.count { checkPoint(it, touchData) } >= neededPoints
+    }
+
+    private fun checkPoint(point: Point, touchData: NormalizedTouchData): 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(touchData.orientation) * (point.x - touchData.x)
+        val b: Float = sin(touchData.orientation) * (point.y - touchData.y)
+        val c: Float = sin(touchData.orientation) * (point.x - touchData.x)
+        val d: Float = cos(touchData.orientation) * (point.y - touchData.y)
+        val result =
+            (a + b).pow(2) / (touchData.minor / 2).pow(2) +
+                (c - d).pow(2) / (touchData.major / 2).pow(2)
+
+        return result <= 1
+    }
+
+    private fun calculateSensorPoints(sensorBounds: Rect): List<Point> {
+        val sensorX = sensorBounds.centerX()
+        val sensorY = sensorBounds.centerY()
+        val cornerOffset: Int = sensorBounds.width() / 4
+        val sideOffset: Int = sensorBounds.width() / 3
+
+        return listOf(
+            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/udfps/InteractionEvent.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/InteractionEvent.kt
new file mode 100644
index 0000000..6e47dad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/InteractionEvent.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.biometrics.udfps
+
+import android.view.MotionEvent
+
+/** Interaction event between a finger and the under-display fingerprint sensor (UDFPS). */
+enum class InteractionEvent {
+    /**
+     * A finger entered the sensor area. This can originate from either [MotionEvent.ACTION_DOWN] or
+     * [MotionEvent.ACTION_MOVE].
+     */
+    DOWN,
+
+    /**
+     * A finger left the sensor area. This can originate from either [MotionEvent.ACTION_UP] or
+     * [MotionEvent.ACTION_MOVE].
+     */
+    UP,
+
+    /**
+     * The touch reporting has stopped. This corresponds to [MotionEvent.ACTION_CANCEL]. This should
+     * not be confused with [UP]. If there was a finger on the sensor, it may or may not still be on
+     * the sensor.
+     */
+    CANCEL,
+
+    /**
+     * The interaction hasn't changed since the previous event. The can originate from any of
+     * [MotionEvent.ACTION_DOWN], [MotionEvent.ACTION_MOVE], or [MotionEvent.ACTION_UP] if one of
+     * these is true:
+     * - There was previously a finger on the sensor, and that finger is still on the sensor.
+     * - There was previously no finger on the sensor, and there still isn't.
+     */
+    UNCHANGED,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt
new file mode 100644
index 0000000..62bedc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.udfps
+
+import android.graphics.Rect
+import android.view.MotionEvent
+
+/** Touch data in natural orientation and native resolution. */
+data class NormalizedTouchData(
+
+    /**
+     * Value obtained from [MotionEvent.getPointerId], or [MotionEvent.INVALID_POINTER_ID] if the ID
+     * is not available.
+     */
+    val pointerId: Int,
+
+    /** [MotionEvent.getRawX] mapped to natural orientation and native resolution. */
+    val x: Float,
+
+    /** [MotionEvent.getRawY] mapped to natural orientation and native resolution. */
+    val y: Float,
+
+    /** [MotionEvent.getTouchMinor] mapped to natural orientation and native resolution. */
+    val minor: Float,
+
+    /** [MotionEvent.getTouchMajor] mapped to natural orientation and native resolution. */
+    val major: Float,
+
+    /** [MotionEvent.getOrientation] mapped to natural orientation. */
+    val orientation: Float,
+
+    /** [MotionEvent.getEventTime]. */
+    val time: Long,
+
+    /** [MotionEvent.getDownTime]. */
+    val gestureStart: Long,
+) {
+
+    /**
+     * [nativeSensorBounds] contains the location and dimensions of the sensor area in native
+     * resolution and natural orientation.
+     *
+     * Returns whether the coordinates of the given pointer are within the sensor's bounding box.
+     */
+    fun isWithinSensor(nativeSensorBounds: Rect): Boolean {
+        return nativeSensorBounds.left <= x &&
+            nativeSensorBounds.right >= x &&
+            nativeSensorBounds.top <= y &&
+            nativeSensorBounds.bottom >= y
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt
new file mode 100644
index 0000000..0fec8ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.udfps
+
+import android.graphics.Rect
+
+/** Determines whether the touch has a sufficient overlap with the sensor. */
+interface OverlapDetector {
+    fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
new file mode 100644
index 0000000..338bf66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
@@ -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.systemui.biometrics.udfps
+
+import android.graphics.PointF
+import android.util.RotationUtils
+import android.view.MotionEvent
+import android.view.MotionEvent.INVALID_POINTER_ID
+import android.view.Surface
+import com.android.systemui.biometrics.UdfpsOverlayParams
+import com.android.systemui.biometrics.udfps.TouchProcessorResult.Failure
+import com.android.systemui.biometrics.udfps.TouchProcessorResult.ProcessedTouch
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * TODO(b/259140693): Consider using an object pool of TouchProcessorResult to avoid allocations.
+ */
+@SysUISingleton
+class SinglePointerTouchProcessor @Inject constructor(val overlapDetector: OverlapDetector) :
+    TouchProcessor {
+
+    override fun processTouch(
+        event: MotionEvent,
+        previousPointerOnSensorId: Int,
+        overlayParams: UdfpsOverlayParams,
+    ): TouchProcessorResult {
+
+        fun preprocess(): PreprocessedTouch {
+            // TODO(b/253085297): Add multitouch support. pointerIndex can be > 0 for ACTION_MOVE.
+            val pointerIndex = 0
+            val touchData = event.normalize(pointerIndex, overlayParams)
+            val isGoodOverlap =
+                overlapDetector.isGoodOverlap(touchData, overlayParams.nativeSensorBounds)
+            return PreprocessedTouch(touchData, previousPointerOnSensorId, isGoodOverlap)
+        }
+
+        return when (event.actionMasked) {
+            MotionEvent.ACTION_DOWN -> processActionDown(preprocess())
+            MotionEvent.ACTION_MOVE -> processActionMove(preprocess())
+            MotionEvent.ACTION_UP -> processActionUp(preprocess())
+            MotionEvent.ACTION_CANCEL ->
+                processActionCancel(event.normalize(pointerIndex = 0, overlayParams))
+            else ->
+                Failure("Unsupported MotionEvent." + MotionEvent.actionToString(event.actionMasked))
+        }
+    }
+}
+
+private data class PreprocessedTouch(
+    val data: NormalizedTouchData,
+    val previousPointerOnSensorId: Int,
+    val isGoodOverlap: Boolean,
+)
+
+private fun processActionDown(touch: PreprocessedTouch): TouchProcessorResult {
+    return if (touch.isGoodOverlap) {
+        ProcessedTouch(InteractionEvent.DOWN, pointerOnSensorId = touch.data.pointerId, touch.data)
+    } else {
+        val event =
+            if (touch.data.pointerId == touch.previousPointerOnSensorId) {
+                InteractionEvent.UP
+            } else {
+                InteractionEvent.UNCHANGED
+            }
+        ProcessedTouch(event, pointerOnSensorId = INVALID_POINTER_ID, touch.data)
+    }
+}
+
+private fun processActionMove(touch: PreprocessedTouch): TouchProcessorResult {
+    val hadPointerOnSensor = touch.previousPointerOnSensorId != INVALID_POINTER_ID
+    val interactionEvent =
+        when {
+            touch.isGoodOverlap && !hadPointerOnSensor -> InteractionEvent.DOWN
+            !touch.isGoodOverlap && hadPointerOnSensor -> InteractionEvent.UP
+            else -> InteractionEvent.UNCHANGED
+        }
+    val pointerOnSensorId =
+        when (interactionEvent) {
+            InteractionEvent.UNCHANGED -> touch.previousPointerOnSensorId
+            InteractionEvent.DOWN -> touch.data.pointerId
+            else -> INVALID_POINTER_ID
+        }
+    return ProcessedTouch(interactionEvent, pointerOnSensorId, touch.data)
+}
+
+private fun processActionUp(touch: PreprocessedTouch): TouchProcessorResult {
+    return if (touch.isGoodOverlap) {
+        ProcessedTouch(InteractionEvent.UP, pointerOnSensorId = INVALID_POINTER_ID, touch.data)
+    } else {
+        val event =
+            if (touch.previousPointerOnSensorId != INVALID_POINTER_ID) {
+                InteractionEvent.UP
+            } else {
+                InteractionEvent.UNCHANGED
+            }
+        ProcessedTouch(event, pointerOnSensorId = INVALID_POINTER_ID, touch.data)
+    }
+}
+
+private fun processActionCancel(data: NormalizedTouchData): TouchProcessorResult {
+    return ProcessedTouch(InteractionEvent.CANCEL, pointerOnSensorId = INVALID_POINTER_ID, data)
+}
+
+/**
+ * Returns the touch information from the given [MotionEvent] with the relevant fields mapped to
+ * natural orientation and native resolution.
+ */
+private fun MotionEvent.normalize(
+    pointerIndex: Int,
+    overlayParams: UdfpsOverlayParams
+): NormalizedTouchData {
+    val naturalTouch: PointF = rotateToNaturalOrientation(pointerIndex, overlayParams)
+    val nativeX = naturalTouch.x / overlayParams.scaleFactor
+    val nativeY = naturalTouch.y / overlayParams.scaleFactor
+    val nativeMinor: Float = getTouchMinor(pointerIndex) / overlayParams.scaleFactor
+    val nativeMajor: Float = getTouchMajor(pointerIndex) / overlayParams.scaleFactor
+    return NormalizedTouchData(
+        pointerId = getPointerId(pointerIndex),
+        x = nativeX,
+        y = nativeY,
+        minor = nativeMinor,
+        major = nativeMajor,
+        // TODO(b/259311354): touch orientation should be reported relative to Surface.ROTATION_O.
+        orientation = getOrientation(pointerIndex),
+        time = eventTime,
+        gestureStart = downTime,
+    )
+}
+
+/**
+ * Returns the [MotionEvent.getRawX] and [MotionEvent.getRawY] of the given pointer as if the device
+ * is in the [Surface.ROTATION_0] orientation.
+ */
+private fun MotionEvent.rotateToNaturalOrientation(
+    pointerIndex: Int,
+    overlayParams: UdfpsOverlayParams
+): PointF {
+    val touchPoint = PointF(getRawX(pointerIndex), getRawY(pointerIndex))
+    val rot = overlayParams.rotation
+    if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
+        RotationUtils.rotatePointF(
+            touchPoint,
+            RotationUtils.deltaRotation(rot, Surface.ROTATION_0),
+            overlayParams.logicalDisplayWidth.toFloat(),
+            overlayParams.logicalDisplayHeight.toFloat()
+        )
+    }
+    return touchPoint
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt
new file mode 100644
index 0000000..ffcebf9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.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.biometrics.udfps
+
+import android.view.MotionEvent
+import com.android.systemui.biometrics.UdfpsOverlayParams
+
+/**
+ * Determines whether a finger entered or left the area of the under-display fingerprint sensor
+ * (UDFPS). Maps the touch information from a [MotionEvent] to the orientation and scale independent
+ * [NormalizedTouchData].
+ */
+interface TouchProcessor {
+
+    /**
+     * [event] touch event to be processed.
+     *
+     * [previousPointerOnSensorId] pointerId for the finger that was on the sensor prior to this
+     * event. See [MotionEvent.getPointerId]. If there was no finger on the sensor, this should be
+     * set to [MotionEvent.INVALID_POINTER_ID].
+     *
+     * [overlayParams] contains the location and dimensions of the sensor area, as well as the scale
+     * factor and orientation of the overlay. See [UdfpsOverlayParams].
+     *
+     * Returns [TouchProcessorResult.ProcessedTouch] on success, and [TouchProcessorResult.Failure]
+     * on failure.
+     */
+    fun processTouch(
+        event: MotionEvent,
+        previousPointerOnSensorId: Int,
+        overlayParams: UdfpsOverlayParams,
+    ): TouchProcessorResult
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessorResult.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessorResult.kt
new file mode 100644
index 0000000..be75bb0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessorResult.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.udfps
+
+import android.view.MotionEvent
+
+/** Contains all the possible returns types for [TouchProcessor.processTouch] */
+sealed class TouchProcessorResult {
+
+    /**
+     * [event] whether a finger entered or left the sensor area. See [InteractionEvent].
+     *
+     * [pointerOnSensorId] pointerId fof the finger that's currently on the sensor. See
+     * [MotionEvent.getPointerId]. If there is no finger on the sensor, the value is set to
+     * [MotionEvent.INVALID_POINTER_ID].
+     *
+     * [touchData] relevant data from the MotionEvent, mapped to natural orientation and native
+     * resolution. See [NormalizedTouchData].
+     */
+    data class ProcessedTouch(
+        val event: InteractionEvent,
+        val pointerOnSensorId: Int,
+        val touchData: NormalizedTouchData
+    ) : TouchProcessorResult()
+
+    /** [reason] the reason for the failure. */
+    data class Failure(val reason: String = "") : TouchProcessorResult()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
index c619648..6fb8e34 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -6,6 +6,8 @@
 import android.view.inputmethod.InputMethodManager
 import android.widget.ImeAwareEditText
 import android.widget.TextView
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.R
@@ -13,7 +15,7 @@
 import com.android.systemui.biometrics.ui.CredentialView
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.launch
 
 /** Sub-binder for the [CredentialPasswordView]. */
@@ -24,14 +26,19 @@
         view: CredentialPasswordView,
         host: CredentialView.Host,
         viewModel: CredentialViewModel,
+        requestFocusForInput: Boolean,
     ) {
         val imeManager = view.context.getSystemService(InputMethodManager::class.java)!!
 
         val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword)
 
+        val onBackInvokedCallback = OnBackInvokedCallback { host.onCredentialAborted() }
+
         view.repeatWhenAttached {
-            passwordField.requestFocus()
-            passwordField.scheduleShowSoftInput()
+            if (requestFocusForInput) {
+                passwordField.requestFocus()
+                passwordField.scheduleShowSoftInput()
+            }
 
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 // observe credential validation attempts and submit/cancel buttons
@@ -43,9 +50,7 @@
                                 launch { viewModel.checkCredential(text, header) }
                             }
                         )
-                        passwordField.setOnKeyListener(
-                            OnBackButtonListener { host.onCredentialAborted() }
-                        )
+                        passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback))
                     }
                 }
 
@@ -66,18 +71,35 @@
                         }
                     }
                 }
+
+                val onBackInvokedDispatcher = view.findOnBackInvokedDispatcher()
+                if (onBackInvokedDispatcher != null) {
+                    launch {
+                            onBackInvokedDispatcher.registerOnBackInvokedCallback(
+                                OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                                onBackInvokedCallback
+                            )
+                            awaitCancellation()
+                        }
+                        .invokeOnCompletion {
+                            onBackInvokedDispatcher.unregisterOnBackInvokedCallback(
+                                onBackInvokedCallback
+                            )
+                        }
+                }
             }
         }
     }
 }
 
-private class OnBackButtonListener(private val onBack: () -> Unit) : View.OnKeyListener {
+private class OnBackButtonListener(private val onBackInvokedCallback: OnBackInvokedCallback) :
+    View.OnKeyListener {
     override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
         if (keyCode != KeyEvent.KEYCODE_BACK) {
             return false
         }
         if (event.action == KeyEvent.ACTION_UP) {
-            onBack()
+            onBackInvokedCallback.onBackInvoked()
         }
         return true
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
index 4765551..b692ad3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
@@ -9,7 +9,6 @@
 import com.android.systemui.biometrics.ui.CredentialView
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
 /** Sub-binder for the [CredentialPatternView]. */
@@ -30,7 +29,7 @@
                     viewModel.header.collect { header ->
                         lockPatternView.setOnPatternListener(
                             OnPatternDetectedListener { pattern ->
-                                if (pattern.isPatternLongEnough()) {
+                                if (pattern.isPatternTooShort()) {
                                     // Pattern size is less than the minimum
                                     // do not count it as a failed attempt
                                     viewModel.showPatternTooShortError()
@@ -71,5 +70,5 @@
     }
 }
 
-private fun List<LockPatternView.Cell>.isPatternLongEnough(): Boolean =
+private fun List<LockPatternView.Cell>.isPatternTooShort(): Boolean =
     size < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index fcc9487..e2d36dc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -17,7 +17,6 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
@@ -40,6 +39,7 @@
         panelViewController: AuthPanelController,
         animatePanel: Boolean,
         maxErrorDuration: Long = 3_000L,
+        requestFocusForInput: Boolean = true,
     ) {
         val titleView: TextView = view.requireViewById(R.id.title)
         val subtitleView: TextView = view.requireViewById(R.id.subtitle)
@@ -110,7 +110,8 @@
 
         // bind the auth widget
         when (view) {
-            is CredentialPasswordView -> CredentialPasswordViewBinder.bind(view, host, viewModel)
+            is CredentialPasswordView ->
+                CredentialPasswordViewBinder.bind(view, host, viewModel, requestFocusForInput)
             is CredentialPatternView -> CredentialPatternViewBinder.bind(view, host, viewModel)
             else -> throw IllegalStateException("unexpected view type: ${view.javaClass.name}")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 537cbc5..a0a892d 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -64,8 +64,9 @@
  * from SystemUI. That way the number of calls to [BroadcastReceiver.onReceive] can be reduced for
  * a given broadcast.
  *
- * Use only for IntentFilters with actions and optionally categories. It does not support,
- * permissions, schemes, data types, data authorities or priority different than 0.
+ * Use only for IntentFilters with actions and optionally categories. It does not support schemes,
+ * data types, data authorities or priority different than 0.
+ *
  * Cannot be used for getting sticky broadcasts (either as return of registering or as re-delivery).
  * Broadcast handling may be asynchronous *without* calling goAsync(), as it's running within sysui
  * and doesn't need to worry about being killed.
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..1454210 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -31,7 +31,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleView
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.policy.BatteryController
@@ -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/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index e82d0ea..3808ab7 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -30,7 +30,7 @@
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.ripple.RippleShader.RippleShape;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
 
 /**
  * A WirelessChargingAnimation is a view containing view + animation for wireless charging.
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index c0cc6b4..36103f8 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.RippleShader.RippleShape;
-import com.android.systemui.ripple.RippleView;
-import com.android.systemui.ripple.RippleViewKt;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
+import com.android.systemui.surfaceeffects.ripple.RippleView;
 
 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..e8e1f2e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -34,6 +34,8 @@
 import com.android.systemui.classifier.FalsingDataProvider.SessionListener;
 import com.android.systemui.classifier.HistoryTracker.BeliefListener;
 import com.android.systemui.dagger.qualifiers.TestHarness;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -65,6 +67,7 @@
     private static final double FALSE_BELIEF_THRESHOLD = 0.9;
 
     private final FalsingDataProvider mDataProvider;
+    private final LongTapClassifier mLongTapClassifier;
     private final SingleTapClassifier mSingleTapClassifier;
     private final DoubleTapClassifier mDoubleTapClassifier;
     private final HistoryTracker mHistoryTracker;
@@ -73,6 +76,7 @@
     private final boolean mTestHarness;
     private final MetricsLogger mMetricsLogger;
     private int mIsFalseTouchCalls;
+    private FeatureFlags mFeatureFlags;
     private static final Queue<String> RECENT_INFO_LOG =
             new ArrayDeque<>(RECENT_INFO_LOG_SIZE + 1);
     private static final Queue<DebugSwipeRecord> RECENT_SWIPES =
@@ -175,19 +179,23 @@
     public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
             MetricsLogger metricsLogger,
             @Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers,
-            SingleTapClassifier singleTapClassifier, DoubleTapClassifier doubleTapClassifier,
-            HistoryTracker historyTracker, KeyguardStateController keyguardStateController,
+            SingleTapClassifier singleTapClassifier, LongTapClassifier longTapClassifier,
+            DoubleTapClassifier doubleTapClassifier, HistoryTracker historyTracker,
+            KeyguardStateController keyguardStateController,
             AccessibilityManager accessibilityManager,
-            @TestHarness boolean testHarness) {
+            @TestHarness boolean testHarness,
+            FeatureFlags featureFlags) {
         mDataProvider = falsingDataProvider;
         mMetricsLogger = metricsLogger;
         mClassifiers = classifiers;
         mSingleTapClassifier = singleTapClassifier;
+        mLongTapClassifier = longTapClassifier;
         mDoubleTapClassifier = doubleTapClassifier;
         mHistoryTracker = historyTracker;
         mKeyguardStateController = keyguardStateController;
         mAccessibilityManager = accessibilityManager;
         mTestHarness = testHarness;
+        mFeatureFlags = featureFlags;
 
         mDataProvider.addSessionListener(mSessionListener);
         mDataProvider.addGestureCompleteListener(mGestureFinalizedListener);
@@ -223,7 +231,8 @@
 
         // check for false tap if it is a seekbar interaction
         if (interactionType == MEDIA_SEEKBAR) {
-            localResult[0] &= isFalseTap(LOW_PENALTY);
+            localResult[0] &= isFalseTap(mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+                    ? FalsingManager.MODERATE_PENALTY : FalsingManager.LOW_PENALTY);
         }
 
         logDebug("False Gesture (type: " + interactionType + "): " + localResult[0]);
@@ -313,6 +322,58 @@
     }
 
     @Override
+    public boolean isFalseLongTap(@Penalty int penalty) {
+        if (!mFeatureFlags.isEnabled(Flags.FALSING_FOR_LONG_TAPS)) {
+            return false;
+        }
+
+        checkDestroyed();
+
+        if (skipFalsing(GENERIC)) {
+            mPriorResults = getPassedResult(1);
+            logDebug("Skipped falsing");
+            return false;
+        }
+
+        double falsePenalty = 0;
+        switch(penalty) {
+            case NO_PENALTY:
+                falsePenalty = 0;
+                break;
+            case LOW_PENALTY:
+                falsePenalty = 0.1;
+                break;
+            case MODERATE_PENALTY:
+                falsePenalty = 0.3;
+                break;
+            case HIGH_PENALTY:
+                falsePenalty = 0.6;
+                break;
+        }
+
+        FalsingClassifier.Result longTapResult =
+                mLongTapClassifier.isTap(mDataProvider.getRecentMotionEvents().isEmpty()
+                        ? mDataProvider.getPriorMotionEvents()
+                        : mDataProvider.getRecentMotionEvents(), falsePenalty);
+        mPriorResults = Collections.singleton(longTapResult);
+
+        if (!longTapResult.isFalse()) {
+            if (mDataProvider.isJustUnlockedWithFace()) {
+                // Immediately pass if a face is detected.
+                mPriorResults = getPassedResult(1);
+                logDebug("False Long Tap: false (face detected)");
+            } else {
+                mPriorResults = getPassedResult(0.1);
+                logDebug("False Long Tap: false (default)");
+            }
+            return false;
+        } else {
+            logDebug("False Long Tap: " + longTapResult.isFalse() + " (simple)");
+            return longTapResult.isFalse();
+        }
+    }
+
+    @Override
     public boolean isFalseDoubleTap() {
         checkDestroyed();
 
@@ -337,7 +398,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/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index 5d04b5f..5c905df 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -29,7 +29,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.FalsingPlugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.util.DeviceConfigProxy;
 
 import java.io.PrintWriter;
@@ -139,6 +139,11 @@
     }
 
     @Override
+    public boolean isFalseLongTap(int penalty) {
+        return mInternalFalsingManager.isFalseLongTap(penalty);
+    }
+
+    @Override
     public boolean isFalseDoubleTap() {
         return mInternalFalsingManager.isFalseDoubleTap();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
index 7b7f17e..5302af9 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
@@ -40,6 +40,7 @@
 public interface FalsingModule {
     String BRIGHT_LINE_GESTURE_CLASSIFERS = "bright_line_gesture_classifiers";
     String SINGLE_TAP_TOUCH_SLOP = "falsing_single_tap_touch_slop";
+    String LONG_TAP_TOUCH_SLOP = "falsing_long_tap_slop";
     String DOUBLE_TAP_TOUCH_SLOP = "falsing_double_tap_touch_slop";
     String DOUBLE_TAP_TIMEOUT_MS = "falsing_double_tap_timeout_ms";
 
@@ -81,4 +82,11 @@
     static float providesSingleTapTouchSlop(ViewConfiguration viewConfiguration) {
         return viewConfiguration.getScaledTouchSlop();
     }
+
+    /** */
+    @Provides
+    @Named(LONG_TAP_TOUCH_SLOP)
+    static float providesLongTapTouchSlop(ViewConfiguration viewConfiguration) {
+        return viewConfiguration.getScaledTouchSlop() * 1.25f;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/LongTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/LongTapClassifier.java
new file mode 100644
index 0000000..1963e69
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/LongTapClassifier.java
@@ -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.systemui.classifier;
+
+import static com.android.systemui.classifier.FalsingModule.LONG_TAP_TOUCH_SLOP;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Falsing classifier that accepts or rejects a gesture as a long tap.
+ */
+public class LongTapClassifier extends TapClassifier{
+
+    @Inject
+    LongTapClassifier(FalsingDataProvider dataProvider,
+            @Named(LONG_TAP_TOUCH_SLOP) float touchSlop) {
+        super(dataProvider, touchSlop);
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java
index bd6fbfb..7a7401d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java
@@ -18,57 +18,17 @@
 
 import static com.android.systemui.classifier.FalsingModule.SINGLE_TAP_TOUCH_SLOP;
 
-import android.view.MotionEvent;
-
-import java.util.List;
-
 import javax.inject.Inject;
 import javax.inject.Named;
 
 /**
- * Falsing classifier that accepts or rejects a single gesture as a tap.
+ * Falsing classifier that accepts or rejects a gesture as a single tap.
  */
-public class SingleTapClassifier extends FalsingClassifier {
-    private final float mTouchSlop;
+public class SingleTapClassifier extends TapClassifier {
 
     @Inject
     SingleTapClassifier(FalsingDataProvider dataProvider,
             @Named(SINGLE_TAP_TOUCH_SLOP) float touchSlop) {
-        super(dataProvider);
-        mTouchSlop = touchSlop;
-    }
-
-    @Override
-    Result calculateFalsingResult(
-            @Classifier.InteractionType int interactionType,
-            double historyBelief, double historyConfidence) {
-        return isTap(getRecentMotionEvents(), 0.5);
-    }
-
-    /** Given a list of {@link android.view.MotionEvent}'s, returns true if the look like a tap. */
-    public Result isTap(List<MotionEvent> motionEvents, double falsePenalty) {
-        if (motionEvents.isEmpty()) {
-            return falsed(0, "no motion events");
-        }
-        float downX = motionEvents.get(0).getX();
-        float downY = motionEvents.get(0).getY();
-
-        for (MotionEvent event : motionEvents) {
-            String reason;
-            if (Math.abs(event.getX() - downX) >= mTouchSlop) {
-                reason = "dX too big for a tap: "
-                        + Math.abs(event.getX() - downX)
-                        + "vs "
-                        + mTouchSlop;
-                return falsed(falsePenalty, reason);
-            } else if (Math.abs(event.getY() - downY) >= mTouchSlop) {
-                reason = "dY too big for a tap: "
-                        + Math.abs(event.getY() - downY)
-                        + " vs "
-                        + mTouchSlop;
-                return falsed(falsePenalty, reason);
-            }
-        }
-        return Result.passed(0);
+        super(dataProvider, touchSlop);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TapClassifier.java
new file mode 100644
index 0000000..e24cfaa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TapClassifier.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.systemui.classifier;
+
+import android.view.MotionEvent;
+
+import java.util.List;
+
+/**
+ * Falsing classifier that accepts or rejects a gesture as a tap.
+ */
+public abstract class TapClassifier extends FalsingClassifier{
+    private final float mTouchSlop;
+
+    TapClassifier(FalsingDataProvider dataProvider,
+            float touchSlop) {
+        super(dataProvider);
+        mTouchSlop = touchSlop;
+    }
+
+    @Override
+    Result calculateFalsingResult(
+            @Classifier.InteractionType int interactionType,
+            double historyBelief, double historyConfidence) {
+        return isTap(getRecentMotionEvents(), 0.5);
+    }
+
+    /** Given a list of {@link android.view.MotionEvent}'s, returns true if the look like a tap. */
+    public Result isTap(List<MotionEvent> motionEvents, double falsePenalty) {
+        if (motionEvents.isEmpty()) {
+            return falsed(0, "no motion events");
+        }
+        float downX = motionEvents.get(0).getX();
+        float downY = motionEvents.get(0).getY();
+
+        for (MotionEvent event : motionEvents) {
+            String reason;
+            if (Math.abs(event.getX() - downX) >= mTouchSlop) {
+                reason = "dX too big for a tap: "
+                        + Math.abs(event.getX() - downX)
+                        + "vs "
+                        + mTouchSlop;
+                return falsed(falsePenalty, reason);
+            } else if (Math.abs(event.getY() - downY) >= mTouchSlop) {
+                reason = "dY too big for a tap: "
+                        + Math.abs(event.getY() - downY)
+                        + " vs "
+                        + mTouchSlop;
+                return falsed(falsePenalty, reason);
+            }
+        }
+        return Result.passed(0);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 9f338d1..63c2065 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);
 
 
@@ -252,16 +266,19 @@
             mExitAnimator.cancel();
         }
         reset();
+        mClipboardLogger.setClipSource(clipSource);
         String accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
 
         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));
@@ -284,10 +301,18 @@
         } else {
             mView.showDefaultTextPreview();
         }
-        maybeShowRemoteCopy(clipData);
+        if (!isRemote) {
+            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 +452,9 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
-                mTimeoutHandler.resetTimeout();
+                if (mOnUiUpdate != null) {
+                    mOnUiUpdate.run();
+                }
             }
         });
         mEnterAnimator.start();
@@ -501,21 +528,27 @@
 
     static class ClipboardLogger {
         private final UiEventLogger mUiEventLogger;
+        private String mClipSource;
         private boolean mGuarded = false;
 
         ClipboardLogger(UiEventLogger uiEventLogger) {
             mUiEventLogger = uiEventLogger;
         }
 
+        void setClipSource(String clipSource) {
+            mClipSource = clipSource;
+        }
+
         void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) {
             if (!mGuarded) {
                 mGuarded = true;
-                mUiEventLogger.log(event);
+                mUiEventLogger.log(event, 0, mClipSource);
             }
         }
 
         void reset() {
             mGuarded = false;
+            mClipSource = null;
         }
     }
 }
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..c194e66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
@@ -0,0 +1,55 @@
+/*
+ * 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 android.os.Build;
+import android.provider.DeviceConfig;
+
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+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)) {
+            if (Build.isDebuggable() && DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_SYSTEMUI,
+                    SystemUiDeviceConfigFlags.CLIPBOARD_IGNORE_REMOTE_COPY_SOURCE,
+                    false)) {
+                return true;
+            }
+            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/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index 588ef5c..66e5d7c4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -16,16 +16,125 @@
 
 package com.android.systemui.controls
 
+import android.Manifest
+import android.content.ComponentName
 import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE
+import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+import android.content.pm.ResolveInfo
 import android.content.pm.ServiceInfo
+import android.os.UserHandle
+import android.service.controls.ControlsProviderService
+import androidx.annotation.WorkerThread
 import com.android.settingslib.applications.DefaultAppInfo
+import com.android.systemui.R
+import java.util.Objects
 
 class ControlsServiceInfo(
-    context: Context,
+    private val context: Context,
     val serviceInfo: ServiceInfo
 ) : DefaultAppInfo(
     context,
     context.packageManager,
     context.userId,
     serviceInfo.componentName
-)
\ No newline at end of file
+) {
+    private val _panelActivity: ComponentName?
+
+    init {
+        val metadata = serviceInfo.metaData
+                ?.getString(ControlsProviderService.META_DATA_PANEL_ACTIVITY) ?: ""
+        val unflatenned = ComponentName.unflattenFromString(metadata)
+        if (unflatenned != null && unflatenned.packageName == componentName.packageName) {
+            _panelActivity = unflatenned
+        } else {
+            _panelActivity = null
+        }
+    }
+
+    /**
+     * Component name of an activity that will be shown embedded in the device controls space
+     * instead of using the controls rendered by SystemUI.
+     *
+     * The activity must be in the same package, exported, enabled and protected by the
+     * [Manifest.permission.BIND_CONTROLS] permission. Additionally, only packages declared in
+     * [R.array.config_controlsPreferredPackages] can declare activities for use as a panel.
+     */
+    var panelActivity: ComponentName? = null
+        private set
+
+    private var resolved: Boolean = false
+
+    @WorkerThread
+    fun resolvePanelActivity() {
+        if (resolved) return
+        resolved = true
+        val validPackages = context.resources
+                .getStringArray(R.array.config_controlsPreferredPackages)
+        if (componentName.packageName !in validPackages) return
+        panelActivity = _panelActivity?.let {
+            val resolveInfos = mPm.queryIntentActivitiesAsUser(
+                    Intent().setComponent(it),
+                    PackageManager.ResolveInfoFlags.of(
+                            MATCH_DIRECT_BOOT_AWARE.toLong() or
+                                    MATCH_DIRECT_BOOT_UNAWARE.toLong()
+                    ),
+                    UserHandle.of(userId)
+            )
+            if (resolveInfos.isNotEmpty() && verifyResolveInfo(resolveInfos[0])) {
+                it
+            } else {
+                null
+            }
+        }
+    }
+
+    /**
+     * Verifies that the panel activity is enabled, exported and protected by the correct
+     * permission. This last check is to prevent apps from forgetting to protect the activity, as
+     * they won't be able to see the panel until they do.
+     */
+    @WorkerThread
+    private fun verifyResolveInfo(resolveInfo: ResolveInfo): Boolean {
+        return resolveInfo.activityInfo?.let {
+            it.permission == Manifest.permission.BIND_CONTROLS &&
+                    it.exported && isComponentActuallyEnabled(it)
+        } ?: false
+    }
+
+    @WorkerThread
+    private fun isComponentActuallyEnabled(activityInfo: ActivityInfo): Boolean {
+        return when (mPm.getComponentEnabledSetting(activityInfo.componentName)) {
+            PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true
+            PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> false
+            PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> activityInfo.enabled
+            else -> false
+        }
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return other is ControlsServiceInfo &&
+                userId == other.userId &&
+                componentName == other.componentName &&
+                panelActivity == other.panelActivity
+    }
+
+    override fun hashCode(): Int {
+        return Objects.hash(userId, componentName, panelActivity)
+    }
+
+    fun copy(): ControlsServiceInfo {
+        return ControlsServiceInfo(context, serviceInfo).also {
+            it.panelActivity = this.panelActivity
+        }
+    }
+
+    override fun toString(): String {
+        return """
+            ControlsServiceInfo(serviceInfo=$serviceInfo, panelActivity=$panelActivity, resolved=$resolved)
+        """.trimIndent()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt
new file mode 100644
index 0000000..3d10ab9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt
@@ -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.systemui.controls
+
+import kotlinx.coroutines.flow.StateFlow
+
+/** Repository for Device controls related settings. */
+interface ControlsSettingsRepository {
+    /** Whether device controls activity can be shown above lockscreen for this user. */
+    val canShowControlsInLockscreen: StateFlow<Boolean>
+
+    /** Whether trivial controls can be actioned from the lockscreen for this user. */
+    val allowActionOnTrivialControlsInLockscreen: StateFlow<Boolean>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt
new file mode 100644
index 0000000..9dc422a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt
@@ -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.systemui.controls
+
+import android.provider.Settings
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.SettingObserver
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * This implementation uses an `@Application` [CoroutineScope] to provide hot flows for the values
+ * of the tracked settings.
+ */
+@SysUISingleton
+class ControlsSettingsRepositoryImpl
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val userRepository: UserRepository,
+    private val secureSettings: SecureSettings
+) : ControlsSettingsRepository {
+
+    override val canShowControlsInLockscreen =
+        makeFlowForSetting(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS)
+
+    override val allowActionOnTrivialControlsInLockscreen =
+        makeFlowForSetting(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private fun makeFlowForSetting(setting: String): StateFlow<Boolean> {
+        return userRepository.selectedUserInfo
+            .distinctUntilChanged()
+            .flatMapLatest { userInfo ->
+                conflatedCallbackFlow {
+                        val observer =
+                            object : SettingObserver(secureSettings, null, setting, userInfo.id) {
+                                override fun handleValueChanged(
+                                    value: Int,
+                                    observedChange: Boolean
+                                ) {
+                                    trySend(value == 1)
+                                }
+                            }
+                        observer.isListening = true
+                        trySend(observer.value == 1)
+                        awaitClose { observer.isListening = false }
+                    }
+                    .flowOn(backgroundDispatcher)
+                    .distinctUntilChanged()
+            }
+            .stateIn(
+                scope,
+                started = SharingStarted.Eagerly,
+                // When the observer starts listening, the flow will emit the current value
+                // so the initialValue here is irrelevant.
+                initialValue = false,
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index 31fadb1..2f49c3f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.util.UserAwareController
 import com.android.systemui.controls.management.ControlsFavoritingActivity
 import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.SelectedItem
 import java.util.function.Consumer
 
 /**
@@ -184,8 +185,8 @@
      */
     fun countFavoritesForComponent(componentName: ComponentName): Int
 
-    /** See [ControlsUiController.getPreferredStructure]. */
-    fun getPreferredStructure(): StructureInfo
+    /** See [ControlsUiController.getPreferredSelectedItem]. */
+    fun getPreferredSelection(): SelectedItem
 
     /**
      * Interface for structure to pass data to [ControlsFavoritingActivity].
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 9e20b46..7b1c623 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -34,11 +34,11 @@
 import com.android.internal.notification.NotificationAccessConfirmationActivityContract.EXTRA_USER_ID
 import com.android.systemui.Dumpable
 import com.android.systemui.backup.BackupHelper
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.ControlStatus
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.SelectedItem
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
@@ -61,11 +61,10 @@
     private val uiController: ControlsUiController,
     private val bindingController: ControlsBindingController,
     private val listingController: ControlsListingController,
-    private val broadcastDispatcher: BroadcastDispatcher,
     private val userFileManager: UserFileManager,
+    private val userTracker: UserTracker,
     optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
     dumpManager: DumpManager,
-    userTracker: UserTracker
 ) : Dumpable, ControlsController {
 
     companion object {
@@ -122,18 +121,15 @@
         userChanging = false
     }
 
-    private val userSwitchReceiver = object : BroadcastReceiver() {
-        override fun onReceive(context: Context, intent: Intent) {
-            if (intent.action == Intent.ACTION_USER_SWITCHED) {
-                userChanging = true
-                val newUser =
-                        UserHandle.of(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, sendingUserId))
-                if (currentUser == newUser) {
-                    userChanging = false
-                    return
-                }
-                setValuesForUser(newUser)
+    private val userTrackerCallback = object : UserTracker.Callback {
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            userChanging = true
+            val newUserHandle = UserHandle.of(newUser)
+            if (currentUser == newUserHandle) {
+                userChanging = false
+                return
             }
+            setValuesForUser(newUserHandle)
         }
     }
 
@@ -235,12 +231,7 @@
         dumpManager.registerDumpable(javaClass.name, this)
         resetFavorites()
         userChanging = false
-        broadcastDispatcher.registerReceiver(
-                userSwitchReceiver,
-                IntentFilter(Intent.ACTION_USER_SWITCHED),
-                executor,
-                UserHandle.ALL
-        )
+        userTracker.addCallback(userTrackerCallback, executor)
         context.registerReceiver(
             restoreFinishedReceiver,
             IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
@@ -252,7 +243,7 @@
     }
 
     fun destroy() {
-        broadcastDispatcher.unregisterReceiver(userSwitchReceiver)
+        userTracker.removeCallback(userTrackerCallback)
         context.unregisterReceiver(restoreFinishedReceiver)
         listingController.removeCallback(listingCallback)
     }
@@ -558,8 +549,8 @@
         )
     }
 
-    override fun getPreferredStructure(): StructureInfo {
-        return uiController.getPreferredStructure(getFavorites())
+    override fun getPreferredSelection(): SelectedItem {
+        return uiController.getPreferredSelectedItem(getFavorites())
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/StructureInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/StructureInfo.kt
index 34bfa13..c8090bf 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/StructureInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/StructureInfo.kt
@@ -31,4 +31,9 @@
     val componentName: ComponentName,
     val structure: CharSequence,
     val controls: List<ControlInfo>
-)
+) {
+    companion object {
+        val EMPTY_COMPONENT = ComponentName("", "")
+        val EMPTY_STRUCTURE = StructureInfo(EMPTY_COMPONENT, "", mutableListOf())
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 9e4a364..77d0496e4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -16,13 +16,10 @@
 
 package com.android.systemui.controls.dagger
 
-import android.content.ContentResolver
 import android.content.Context
-import android.database.ContentObserver
-import android.os.UserHandle
-import android.provider.Settings
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+import com.android.systemui.controls.ControlsSettingsRepository
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
 import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
@@ -31,12 +28,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.settings.SecureSettings
 import dagger.Lazy
+import kotlinx.coroutines.flow.StateFlow
 import java.util.Optional
 import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
 
 /**
  * Pseudo-component to inject into classes outside `com.android.systemui.controls`.
@@ -46,47 +41,26 @@
  */
 @SysUISingleton
 class ControlsComponent @Inject constructor(
-    @ControlsFeatureEnabled private val featureEnabled: Boolean,
-    private val context: Context,
-    private val lazyControlsController: Lazy<ControlsController>,
-    private val lazyControlsUiController: Lazy<ControlsUiController>,
-    private val lazyControlsListingController: Lazy<ControlsListingController>,
-    private val lockPatternUtils: LockPatternUtils,
-    private val keyguardStateController: KeyguardStateController,
-    private val userTracker: UserTracker,
-    private val secureSettings: SecureSettings,
-    private val optionalControlsTileResourceConfiguration:
-        Optional<ControlsTileResourceConfiguration>
+        @ControlsFeatureEnabled private val featureEnabled: Boolean,
+        private val context: Context,
+        private val lazyControlsController: Lazy<ControlsController>,
+        private val lazyControlsUiController: Lazy<ControlsUiController>,
+        private val lazyControlsListingController: Lazy<ControlsListingController>,
+        private val lockPatternUtils: LockPatternUtils,
+        private val keyguardStateController: KeyguardStateController,
+        private val userTracker: UserTracker,
+        controlsSettingsRepository: ControlsSettingsRepository,
+        optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration>
 ) {
-    private val contentResolver: ContentResolver
-        get() = context.contentResolver
 
-    private val _canShowWhileLockedSetting = MutableStateFlow(false)
-    val canShowWhileLockedSetting = _canShowWhileLockedSetting.asStateFlow()
+    val canShowWhileLockedSetting: StateFlow<Boolean> =
+            controlsSettingsRepository.canShowControlsInLockscreen
 
     private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
         optionalControlsTileResourceConfiguration.orElse(
             ControlsTileResourceConfigurationImpl()
         )
 
-    val showWhileLockedObserver = object : ContentObserver(null) {
-        override fun onChange(selfChange: Boolean) {
-            updateShowWhileLocked()
-        }
-    }
-
-    init {
-        if (featureEnabled) {
-            secureSettings.registerContentObserverForUser(
-                Settings.Secure.getUriFor(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS),
-                false, /* notifyForDescendants */
-                showWhileLockedObserver,
-                UserHandle.USER_ALL
-            )
-            updateShowWhileLocked()
-        }
-    }
-
     fun getControlsController(): Optional<ControlsController> {
         return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty()
     }
@@ -127,11 +101,6 @@
         return Visibility.AVAILABLE
     }
 
-    private fun updateShowWhileLocked() {
-        _canShowWhileLockedSetting.value = secureSettings.getIntForUser(
-            Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
-    }
-
     enum class Visibility {
         AVAILABLE, AVAILABLE_AFTER_UNLOCK, UNAVAILABLE
     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index 6f58abd..9ae605e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -20,6 +20,8 @@
 import android.content.pm.PackageManager
 import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.controls.ControlsMetricsLoggerImpl
+import com.android.systemui.controls.ControlsSettingsRepository
+import com.android.systemui.controls.ControlsSettingsRepositoryImpl
 import com.android.systemui.controls.controller.ControlsBindingController
 import com.android.systemui.controls.controller.ControlsBindingControllerImpl
 import com.android.systemui.controls.controller.ControlsController
@@ -83,6 +85,11 @@
     abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController
 
     @Binds
+    abstract fun provideSettingsManager(
+            manager: ControlsSettingsRepositoryImpl
+    ): ControlsSettingsRepository
+
+    @Binds
     abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
index 2389ad1..753d5ad 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -64,7 +64,8 @@
                 val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) {
                     it.loadLabel() ?: ""
                 }
-                listOfServices = serviceInfos.sortedWith(localeComparator)
+                listOfServices = serviceInfos.filter { it.panelActivity == null }
+                        .sortedWith(localeComparator)
                 uiExecutor.execute(::notifyDataSetChanged)
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index 5e8ce6d..7df0865 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -18,40 +18,47 @@
 
 import android.app.ActivityOptions
 import android.content.ComponentName
+import android.content.Context
 import android.content.Intent
 import android.os.Bundle
+import android.util.Log
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewStub
 import android.widget.Button
 import android.widget.TextView
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
 import androidx.activity.ComponentActivity
 import androidx.recyclerview.widget.GridLayoutManager
 import androidx.recyclerview.widget.ItemTouchHelper
 import androidx.recyclerview.widget.RecyclerView
 import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.controls.ui.ControlsUiController
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
 import javax.inject.Inject
 
 /**
  * Activity for rearranging and removing controls for a given structure
  */
-class ControlsEditingActivity @Inject constructor(
+open class ControlsEditingActivity @Inject constructor(
+    @Main private val mainExecutor: Executor,
     private val controller: ControlsControllerImpl,
-    private val broadcastDispatcher: BroadcastDispatcher,
+    private val userTracker: UserTracker,
     private val customIconCache: CustomIconCache,
     private val uiController: ControlsUiController
 ) : ComponentActivity() {
 
     companion object {
+        private const val DEBUG = false
         private const val TAG = "ControlsEditingActivity"
-        private const val EXTRA_STRUCTURE = ControlsFavoritingActivity.EXTRA_STRUCTURE
+        const val EXTRA_STRUCTURE = ControlsFavoritingActivity.EXTRA_STRUCTURE
         private val SUBTITLE_ID = R.string.controls_favorite_rearrange
         private val EMPTY_TEXT_ID = R.string.controls_favorite_removed
     }
@@ -62,17 +69,24 @@
     private lateinit var subtitle: TextView
     private lateinit var saveButton: View
 
-    private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+    private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
         private val startingUser = controller.currentUserId
 
-        override fun onUserSwitched(newUserId: Int) {
-            if (newUserId != startingUser) {
-                stopTracking()
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            if (newUser != startingUser) {
+                userTracker.removeCallback(this)
                 finish()
             }
         }
     }
 
+    private val mOnBackInvokedCallback = OnBackInvokedCallback {
+        if (DEBUG) {
+            Log.d(TAG, "Predictive Back dispatcher called mOnBackInvokedCallback")
+        }
+        onBackPressed()
+    }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
@@ -93,12 +107,23 @@
         super.onStart()
         setUpList()
 
-        currentUserTracker.startTracking()
+        userTracker.addCallback(userTrackerCallback, mainExecutor)
+
+        if (DEBUG) {
+            Log.d(TAG, "Registered onBackInvokedCallback")
+        }
+        onBackInvokedDispatcher.registerOnBackInvokedCallback(
+                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
     }
 
     override fun onStop() {
         super.onStop()
-        currentUserTracker.stopTracking()
+        userTracker.removeCallback(userTrackerCallback)
+
+        if (DEBUG) {
+            Log.d(TAG, "Unregistered onBackInvokedCallback")
+        }
+        onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback)
     }
 
     override fun onBackPressed() {
@@ -226,7 +251,7 @@
     }
 
     override fun onDestroy() {
-        currentUserTracker.stopTracking()
+        userTracker.removeCallback(userTrackerCallback)
         super.onDestroy()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index be572c5..3e97d31 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -20,10 +20,12 @@
 import android.animation.AnimatorListenerAdapter
 import android.app.ActivityOptions
 import android.content.ComponentName
+import android.content.Context
 import android.content.Intent
 import android.content.res.Configuration
 import android.os.Bundle
 import android.text.TextUtils
+import android.util.Log
 import android.view.Gravity
 import android.view.View
 import android.view.ViewGroup
@@ -32,11 +34,12 @@
 import android.widget.FrameLayout
 import android.widget.TextView
 import android.widget.Toast
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
 import androidx.activity.ComponentActivity
 import androidx.viewpager2.widget.ViewPager2
 import com.android.systemui.Prefs
 import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.TooltipManager
 import com.android.systemui.controls.controller.ControlsControllerImpl
@@ -44,21 +47,22 @@
 import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
 import java.text.Collator
 import java.util.concurrent.Executor
 import java.util.function.Consumer
 import javax.inject.Inject
 
-class ControlsFavoritingActivity @Inject constructor(
+open class ControlsFavoritingActivity @Inject constructor(
     @Main private val executor: Executor,
     private val controller: ControlsControllerImpl,
     private val listingController: ControlsListingController,
-    private val broadcastDispatcher: BroadcastDispatcher,
+    private val userTracker: UserTracker,
     private val uiController: ControlsUiController
 ) : ComponentActivity() {
 
     companion object {
+        private const val DEBUG = false
         private const val TAG = "ControlsFavoritingActivity"
 
         // If provided and no structure is available, use as the title
@@ -67,7 +71,7 @@
         // If provided, show this structure page first
         const val EXTRA_STRUCTURE = "extra_structure"
         const val EXTRA_SINGLE_STRUCTURE = "extra_single_structure"
-        internal const val EXTRA_FROM_PROVIDER_SELECTOR = "extra_from_provider_selector"
+        const val EXTRA_FROM_PROVIDER_SELECTOR = "extra_from_provider_selector"
         private const val TOOLTIP_PREFS_KEY = Prefs.Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT
         private const val TOOLTIP_MAX_SHOWN = 2
     }
@@ -91,17 +95,24 @@
     private var cancelLoadRunnable: Runnable? = null
     private var isPagerLoaded = false
 
-    private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+    private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
         private val startingUser = controller.currentUserId
 
-        override fun onUserSwitched(newUserId: Int) {
-            if (newUserId != startingUser) {
-                stopTracking()
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            if (newUser != startingUser) {
+                userTracker.removeCallback(this)
                 finish()
             }
         }
     }
 
+    private val mOnBackInvokedCallback = OnBackInvokedCallback {
+        if (DEBUG) {
+            Log.d(TAG, "Predictive Back dispatcher called mOnBackInvokedCallback")
+        }
+        onBackPressed()
+    }
+
     private val listingCallback = object : ControlsListingController.ControlsListingCallback {
 
         override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
@@ -346,13 +357,19 @@
     override fun onPause() {
         super.onPause()
         mTooltipManager?.hide(false)
-    }
+   }
 
     override fun onStart() {
         super.onStart()
 
         listingController.addCallback(listingCallback)
-        currentUserTracker.startTracking()
+        userTracker.addCallback(userTrackerCallback, executor)
+
+        if (DEBUG) {
+            Log.d(TAG, "Registered onBackInvokedCallback")
+        }
+        onBackInvokedDispatcher.registerOnBackInvokedCallback(
+                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
     }
 
     override fun onResume() {
@@ -365,13 +382,19 @@
             loadControls()
             isPagerLoaded = true
         }
-    }
+   }
 
     override fun onStop() {
         super.onStop()
 
         listingController.removeCallback(listingCallback)
-        currentUserTracker.stopTracking()
+        userTracker.removeCallback(userTrackerCallback)
+
+        if (DEBUG) {
+            Log.d(TAG, "Unregistered onBackInvokedCallback")
+        }
+        onBackInvokedDispatcher.unregisterOnBackInvokedCallback(
+                mOnBackInvokedCallback)
     }
 
     override fun onConfigurationChanged(newConfig: Configuration) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 2d76ff2..c6428ef 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -18,17 +18,23 @@
 
 import android.content.ComponentName
 import android.content.Context
-import android.content.pm.ServiceInfo
 import android.os.UserHandle
 import android.service.controls.ControlsProviderService
 import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
 import com.android.settingslib.applications.ServiceListing
 import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.Dumpable
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.indentIfPossible
+import java.io.PrintWriter
 import java.util.concurrent.Executor
 import java.util.concurrent.atomic.AtomicInteger
 import javax.inject.Inject
@@ -57,16 +63,19 @@
     private val context: Context,
     @Background private val backgroundExecutor: Executor,
     private val serviceListingBuilder: (Context) -> ServiceListing,
-    userTracker: UserTracker
-) : ControlsListingController {
+    private val userTracker: UserTracker,
+    dumpManager: DumpManager,
+    featureFlags: FeatureFlags
+) : ControlsListingController, Dumpable {
 
     @Inject
-    constructor(context: Context, executor: Executor, userTracker: UserTracker): this(
-            context,
-            executor,
-            ::createServiceListing,
-            userTracker
-    )
+    constructor(
+            context: Context,
+            @Background executor: Executor,
+            userTracker: UserTracker,
+            dumpManager: DumpManager,
+            featureFlags: FeatureFlags
+    ) : this(context, executor, ::createServiceListing, userTracker, dumpManager, featureFlags)
 
     private var serviceListing = serviceListingBuilder(context)
     // All operations in background thread
@@ -76,27 +85,26 @@
         private const val TAG = "ControlsListingControllerImpl"
     }
 
-    private var availableComponents = emptySet<ComponentName>()
-    private var availableServices = emptyList<ServiceInfo>()
+    private var availableServices = emptyList<ControlsServiceInfo>()
     private var userChangeInProgress = AtomicInteger(0)
 
     override var currentUserId = userTracker.userId
         private set
 
-    private val serviceListingCallback = ServiceListing.Callback {
-        val newServices = it.toList()
-        val newComponents =
-            newServices.mapTo(mutableSetOf<ComponentName>(), { s -> s.getComponentName() })
-
+    private val serviceListingCallback = ServiceListing.Callback { list ->
+        Log.d(TAG, "ServiceConfig reloaded, count: ${list.size}")
+        val newServices = list.map { ControlsServiceInfo(userTracker.userContext, it) }
+        // After here, `list` is not captured, so we don't risk modifying it outside of the callback
         backgroundExecutor.execute {
             if (userChangeInProgress.get() > 0) return@execute
-            if (!newComponents.equals(availableComponents)) {
-                Log.d(TAG, "ServiceConfig reloaded, count: ${newComponents.size}")
-                availableComponents = newComponents
+            if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
+                newServices.forEach(ControlsServiceInfo::resolvePanelActivity)
+            }
+
+            if (newServices != availableServices) {
                 availableServices = newServices
-                val currentServices = getCurrentServices()
                 callbacks.forEach {
-                    it.onServicesUpdated(currentServices)
+                    it.onServicesUpdated(getCurrentServices())
                 }
             }
         }
@@ -104,6 +112,7 @@
 
     init {
         Log.d(TAG, "Initializing")
+        dumpManager.registerDumpable(TAG, this)
         serviceListing.addCallback(serviceListingCallback)
         serviceListing.setListening(true)
         serviceListing.reload()
@@ -165,7 +174,7 @@
      *         [ControlsProviderService]
      */
     override fun getCurrentServices(): List<ControlsServiceInfo> =
-            availableServices.map { ControlsServiceInfo(context, it) }
+            availableServices.map(ControlsServiceInfo::copy)
 
     /**
      * Get the localized label for the component.
@@ -174,7 +183,15 @@
      * @return a label as returned by [CandidateInfo.loadLabel] or `null`.
      */
     override fun getAppLabel(name: ComponentName): CharSequence? {
-        return getCurrentServices().firstOrNull { it.componentName == name }
+        return availableServices.firstOrNull { it.componentName == name }
                 ?.loadLabel()
     }
+
+    override fun dump(writer: PrintWriter, args: Array<out String>) {
+        writer.println("ControlsListingController:")
+        writer.asIndenting().indentIfPossible {
+            println("Callbacks: $callbacks")
+            println("Services: ${getCurrentServices()}")
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index b26615f..90bc5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -18,58 +18,68 @@
 
 import android.app.ActivityOptions
 import android.content.ComponentName
+import android.content.Context
 import android.content.Intent
 import android.os.Bundle
+import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewStub
 import android.widget.Button
 import android.widget.TextView
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
 import androidx.activity.ComponentActivity
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
-import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
 import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
 /**
  * Activity to select an application to favorite the [Control] provided by them.
  */
-class ControlsProviderSelectorActivity @Inject constructor(
+open class ControlsProviderSelectorActivity @Inject constructor(
     @Main private val executor: Executor,
     @Background private val backExecutor: Executor,
     private val listingController: ControlsListingController,
     private val controlsController: ControlsController,
-    private val broadcastDispatcher: BroadcastDispatcher,
+    private val userTracker: UserTracker,
     private val uiController: ControlsUiController
 ) : ComponentActivity() {
 
     companion object {
+        private const val DEBUG = false
         private const val TAG = "ControlsProviderSelectorActivity"
         const val BACK_SHOULD_EXIT = "back_should_exit"
     }
     private var backShouldExit = false
     private lateinit var recyclerView: RecyclerView
-    private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+    private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
         private val startingUser = listingController.currentUserId
 
-        override fun onUserSwitched(newUserId: Int) {
-            if (newUserId != startingUser) {
-                stopTracking()
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            if (newUser != startingUser) {
+                userTracker.removeCallback(this)
                 finish()
             }
         }
     }
 
+    private val mOnBackInvokedCallback = OnBackInvokedCallback {
+        if (DEBUG) {
+            Log.d(TAG, "Predictive Back dispatcher called mOnBackInvokedCallback")
+        }
+        onBackPressed()
+    }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
@@ -119,7 +129,7 @@
 
     override fun onStart() {
         super.onStart()
-        currentUserTracker.startTracking()
+        userTracker.addCallback(userTrackerCallback, executor)
 
         recyclerView.alpha = 0.0f
         recyclerView.adapter = AppAdapter(
@@ -141,11 +151,22 @@
                 }
             })
         }
+
+        if (DEBUG) {
+            Log.d(TAG, "Registered onBackInvokedCallback")
+        }
+        onBackInvokedDispatcher.registerOnBackInvokedCallback(
+                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
     }
 
     override fun onStop() {
         super.onStop()
-        currentUserTracker.stopTracking()
+        userTracker.removeCallback(userTrackerCallback)
+
+        if (DEBUG) {
+            Log.d(TAG, "Unregistered onBackInvokedCallback")
+        }
+        onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback)
     }
 
     /**
@@ -169,7 +190,7 @@
     }
 
     override fun onDestroy() {
-        currentUserTracker.stopTracking()
+        userTracker.removeCallback(userTrackerCallback)
         super.onDestroy()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
index b376455..86bde5c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
@@ -19,6 +19,7 @@
 import android.app.AlertDialog
 import android.app.Dialog
 import android.content.ComponentName
+import android.content.Context
 import android.content.DialogInterface
 import android.content.Intent
 import android.os.Bundle
@@ -32,18 +33,20 @@
 import android.widget.TextView
 import androidx.activity.ComponentActivity
 import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.ui.RenderInfo
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import java.util.concurrent.Executor
 import javax.inject.Inject
 
 open class ControlsRequestDialog @Inject constructor(
+    @Main private val mainExecutor: Executor,
     private val controller: ControlsController,
-    private val broadcastDispatcher: BroadcastDispatcher,
+    private val userTracker: UserTracker,
     private val controlsListingController: ControlsListingController
 ) : ComponentActivity(), DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
 
@@ -58,12 +61,12 @@
         override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {}
     }
 
-    private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+    private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
         private val startingUser = controller.currentUserId
 
-        override fun onUserSwitched(newUserId: Int) {
-            if (newUserId != startingUser) {
-                stopTracking()
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            if (newUser != startingUser) {
+                userTracker.removeCallback(this)
                 finish()
             }
         }
@@ -72,7 +75,7 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
-        currentUserTracker.startTracking()
+        userTracker.addCallback(userTrackerCallback, mainExecutor)
         controlsListingController.addCallback(callback)
 
         val requestUser = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL)
@@ -118,7 +121,7 @@
 
     override fun onDestroy() {
         dialog?.dismiss()
-        currentUserTracker.stopTracking()
+        userTracker.removeCallback(userTrackerCallback)
         controlsListingController.removeCallback(callback)
         super.onDestroy()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 1f7021e..041ed1d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -25,9 +25,6 @@
 import android.content.Context
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
-import android.database.ContentObserver
-import android.net.Uri
-import android.os.Handler
 import android.os.UserHandle
 import android.os.VibrationEffect
 import android.provider.Settings.Secure
@@ -41,6 +38,7 @@
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.ControlsSettingsRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
@@ -69,17 +67,17 @@
     private val vibrator: VibratorHelper,
     private val secureSettings: SecureSettings,
     private val userContextProvider: UserContextProvider,
-    @Main mainHandler: Handler
+    private val controlsSettingsRepository: ControlsSettingsRepository,
 ) : ControlActionCoordinator {
     private var dialog: Dialog? = null
     private var pendingAction: Action? = null
     private var actionsInProgress = mutableSetOf<String>()
     private val isLocked: Boolean
         get() = !keyguardStateController.isUnlocked()
-    private var mAllowTrivialControls: Boolean = secureSettings.getIntForUser(
-            Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
-    private var mShowDeviceControlsInLockscreen: Boolean = secureSettings.getIntForUser(
-            Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
+    private val allowTrivialControls: Boolean
+        get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
+    private val showDeviceControlsInLockscreen: Boolean
+        get() = controlsSettingsRepository.canShowControlsInLockscreen.value
     override lateinit var activityContext: Context
 
     companion object {
@@ -87,38 +85,6 @@
         private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
     }
 
-    init {
-        val lockScreenShowControlsUri =
-            secureSettings.getUriFor(Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)
-        val showControlsUri =
-                secureSettings.getUriFor(Secure.LOCKSCREEN_SHOW_CONTROLS)
-        val controlsContentObserver = object : ContentObserver(mainHandler) {
-            override fun onChange(selfChange: Boolean, uri: Uri?) {
-                super.onChange(selfChange, uri)
-                when (uri) {
-                    lockScreenShowControlsUri -> {
-                        mAllowTrivialControls = secureSettings.getIntForUser(
-                                Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
-                                0, UserHandle.USER_CURRENT) != 0
-                    }
-                    showControlsUri -> {
-                        mShowDeviceControlsInLockscreen = secureSettings
-                                .getIntForUser(Secure.LOCKSCREEN_SHOW_CONTROLS,
-                                        0, UserHandle.USER_CURRENT) != 0
-                    }
-                }
-            }
-        }
-        secureSettings.registerContentObserverForUser(
-            lockScreenShowControlsUri,
-            false /* notifyForDescendants */, controlsContentObserver, UserHandle.USER_ALL
-        )
-        secureSettings.registerContentObserverForUser(
-            showControlsUri,
-            false /* notifyForDescendants */, controlsContentObserver, UserHandle.USER_ALL
-        )
-    }
-
     override fun closeDialogs() {
         val isActivityFinishing =
             (activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed }
@@ -233,7 +199,7 @@
     @AnyThread
     @VisibleForTesting
     fun bouncerOrRun(action: Action) {
-        val authRequired = action.authIsRequired || !mAllowTrivialControls
+        val authRequired = action.authIsRequired || !allowTrivialControls
 
         if (keyguardStateController.isShowing() && authRequired) {
             if (isLocked) {
@@ -291,7 +257,7 @@
                 PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
         val attempts = prefs.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)
         if (attempts >= MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG ||
-                (mShowDeviceControlsInLockscreen && mAllowTrivialControls)) {
+                (showDeviceControlsInLockscreen && allowTrivialControls)) {
             return
         }
         val builder = AlertDialog
@@ -313,7 +279,7 @@
                     true
                 }
 
-        if (mShowDeviceControlsInLockscreen) {
+        if (showDeviceControlsInLockscreen) {
             dialog = builder
                     .setTitle(R.string.controls_settings_trivial_controls_dialog_title)
                     .setMessage(R.string.controls_settings_trivial_controls_dialog_message)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index d3b5d0e..bd704c1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -27,10 +27,13 @@
 import android.view.ViewGroup
 import android.view.WindowInsets
 import android.view.WindowInsets.Type
+import android.view.WindowManager
 import androidx.activity.ComponentActivity
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.management.ControlsAnimations
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import javax.inject.Inject
 
 /**
@@ -44,6 +47,7 @@
     private val uiController: ControlsUiController,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val dreamManager: IDreamManager,
+    private val featureFlags: FeatureFlags
 ) : ComponentActivity() {
 
     private lateinit var parent: ViewGroup
@@ -52,6 +56,9 @@
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+        if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
+            window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+        }
 
         setContentView(R.layout.controls_fullscreen)
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index c1cfbcb..f5c5905 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -35,7 +35,7 @@
 
     /**
      * Returns the preferred activity to start, depending on if the user has favorited any
-     * controls.
+     * controls or whether there are any app providing panels.
      */
     fun resolveActivity(): Class<*>
 
@@ -53,9 +53,43 @@
     )
 
     /**
-     * Returns the structure that is currently preferred by the user.
+     * Returns the element that is currently preferred by the user.
      *
-     * This structure will be the one that appears when the user first opens the controls activity.
+     * This element will be the one that appears when the user first opens the controls activity.
      */
-    fun getPreferredStructure(structures: List<StructureInfo>): StructureInfo
+    fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem
 }
+
+sealed class SelectedItem {
+
+    abstract val name: CharSequence
+    abstract val hasControls: Boolean
+    abstract val componentName: ComponentName
+
+    /**
+     * Represents the currently selected item for a structure.
+     */
+    data class StructureItem(val structure: StructureInfo) : SelectedItem() {
+        override val name: CharSequence = structure.structure
+        override val hasControls: Boolean = structure.controls.isNotEmpty()
+        override val componentName: ComponentName = structure.componentName
+    }
+
+    /**
+     * Represents the currently selected item for a service that provides a panel activity.
+     *
+     * The [componentName] is that of the service, as that is the expected identifier that should
+     * not change (to always provide proper migration).
+     */
+    data class PanelItem(
+            val appName: CharSequence,
+            override val componentName:
+            ComponentName
+    ) : SelectedItem() {
+        override val name: CharSequence = appName
+        override val hasControls: Boolean = true
+    }
+    companion object {
+        val EMPTY_SELECTION: SelectedItem = StructureItem(StructureInfo.EMPTY_STRUCTURE)
+    }
+}
\ No newline at end of file
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..a07c716 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -21,13 +21,14 @@
 import android.animation.ObjectAnimator
 import android.app.Activity
 import android.app.ActivityOptions
+import android.app.PendingIntent
 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
+import android.service.controls.ControlsProviderService
 import android.util.Log
 import android.view.ContextThemeWrapper
 import android.view.LayoutInflater
@@ -37,18 +38,23 @@
 import android.view.animation.DecelerateInterpolator
 import android.widget.AdapterView
 import android.widget.ArrayAdapter
+import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
 import android.widget.ListPopupWindow
 import android.widget.Space
 import android.widget.TextView
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.ControlsSettingsRepository
 import com.android.systemui.controls.CustomIconCache
-import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.controller.StructureInfo.Companion.EMPTY_COMPONENT
+import com.android.systemui.controls.controller.StructureInfo.Companion.EMPTY_STRUCTURE
 import com.android.systemui.controls.management.ControlAdapter
 import com.android.systemui.controls.management.ControlsEditingActivity
 import com.android.systemui.controls.management.ControlsFavoritingActivity
@@ -57,13 +63,21 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.globalactions.GlobalActionsPopupMenu
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.shade.ShadeController
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.asIndenting
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.indentIfPossible
+import com.android.wm.shell.TaskViewFactory
 import dagger.Lazy
+import java.io.PrintWriter
 import java.text.Collator
+import java.util.Optional
 import java.util.function.Consumer
 import javax.inject.Inject
 
@@ -76,40 +90,45 @@
         @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
-) : ControlsUiController {
+        private val keyguardStateController: KeyguardStateController,
+        private val userFileManager: UserFileManager,
+        private val userTracker: UserTracker,
+        private val taskViewFactory: Optional<TaskViewFactory>,
+        private val controlsSettingsRepository: ControlsSettingsRepository,
+        dumpManager: DumpManager
+) : ControlsUiController, Dumpable {
 
     companion object {
         private const val PREF_COMPONENT = "controls_component"
-        private const val PREF_STRUCTURE = "controls_structure"
+        private const val PREF_STRUCTURE_OR_APP_NAME = "controls_structure"
+        private const val PREF_IS_PANEL = "controls_is_panel"
 
         private const val FADE_IN_MILLIS = 200L
-
-        private val EMPTY_COMPONENT = ComponentName("", "")
-        private val EMPTY_STRUCTURE = StructureInfo(
-            EMPTY_COMPONENT,
-            "",
-            mutableListOf<ControlInfo>()
-        )
     }
 
-    private var selectedStructure: StructureInfo = EMPTY_STRUCTURE
+    private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION
     private lateinit var allStructures: List<StructureInfo>
     private val controlsById = mutableMapOf<ControlKey, ControlWithState>()
     private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>()
     private lateinit var parent: ViewGroup
-    private lateinit var lastItems: List<SelectionItem>
     private var popup: ListPopupWindow? = null
     private var hidden = true
     private lateinit var onDismiss: Runnable
     private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
     private var retainCache = false
+    private var lastSelections = emptyList<SelectionItem>()
+    private val sharedPreferences
+        get() = userFileManager.getSharedPreferences(
+            fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+            mode = 0,
+            userId = userTracker.userId
+        )
+
+    private var taskViewController: PanelTaskViewController? = null
 
     private val collator = Collator.getInstance(context.resources.configuration.locales[0])
     private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) {
@@ -119,10 +138,12 @@
     private val onSeedingComplete = Consumer<Boolean> {
         accepted ->
             if (accepted) {
-                selectedStructure = controlsController.get().getFavorites().maxByOrNull {
+                selectedItem = controlsController.get().getFavorites().maxByOrNull {
                     it.controls.size
-                } ?: EMPTY_STRUCTURE
-                updatePreferences(selectedStructure)
+                }?.let {
+                    SelectedItem.StructureItem(it)
+                } ?: SelectedItem.EMPTY_SELECTION
+                updatePreferences(selectedItem)
             }
             reload(parent)
     }
@@ -130,6 +151,10 @@
     private lateinit var activityContext: Context
     private lateinit var listingCallback: ControlsListingController.ControlsListingCallback
 
+    init {
+        dumpManager.registerDumpable(javaClass.name, this)
+    }
+
     private fun createCallback(
         onResult: (List<SelectionItem>) -> Unit
     ): ControlsListingController.ControlsListingCallback {
@@ -137,7 +162,15 @@
             override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
                 val lastItems = serviceInfos.map {
                     val uid = it.serviceInfo.applicationInfo.uid
-                    SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName, uid)
+
+                    SelectionItem(
+                            it.loadLabel(),
+                            "",
+                            it.loadIcon(),
+                            it.componentName,
+                            uid,
+                            it.panelActivity
+                    )
                 }
                 uiExecutor.execute {
                     parent.removeAllViews()
@@ -151,11 +184,13 @@
 
     override fun resolveActivity(): Class<*> {
         val allStructures = controlsController.get().getFavorites()
-        val selectedStructure = getPreferredStructure(allStructures)
+        val selected = getPreferredSelectedItem(allStructures)
+        val anyPanels = controlsListingController.get().getCurrentServices()
+                .none { it.panelActivity != null }
 
         return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
             ControlsActivity::class.java
-        } else if (selectedStructure.controls.isEmpty() && allStructures.size <= 1) {
+        } else if (!selected.hasControls && allStructures.size <= 1 && !anyPanels) {
             ControlsProviderSelectorActivity::class.java
         } else {
             ControlsActivity::class.java
@@ -177,31 +212,49 @@
         controlActionCoordinator.activityContext = activityContext
 
         allStructures = controlsController.get().getFavorites()
-        selectedStructure = getPreferredStructure(allStructures)
+        selectedItem = getPreferredSelectedItem(allStructures)
 
         if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
             listingCallback = createCallback(::showSeedingView)
-        } else if (selectedStructure.controls.isEmpty() && allStructures.size <= 1) {
+        } else if (
+                selectedItem !is SelectedItem.PanelItem &&
+                !selectedItem.hasControls &&
+                allStructures.size <= 1
+        ) {
             // only show initial view if there are really no favorites across any structure
-            listingCallback = createCallback(::showInitialSetupView)
+            listingCallback = createCallback(::initialView)
         } else {
-            selectedStructure.controls.map {
-                ControlWithState(selectedStructure.componentName, it, null)
-            }.associateByTo(controlsById) {
-                ControlKey(selectedStructure.componentName, it.ci.controlId)
+            val selected = selectedItem
+            if (selected is SelectedItem.StructureItem) {
+                selected.structure.controls.map {
+                    ControlWithState(selected.structure.componentName, it, null)
+                }.associateByTo(controlsById) {
+                    ControlKey(selected.structure.componentName, it.ci.controlId)
+                }
+                controlsController.get().subscribeToFavorites(selected.structure)
             }
             listingCallback = createCallback(::showControlsView)
-            controlsController.get().subscribeToFavorites(selectedStructure)
         }
 
         controlsListingController.get().addCallback(listingCallback)
     }
 
+    private fun initialView(items: List<SelectionItem>) {
+        if (items.any { it.isPanel }) {
+            // We have at least a panel, so we'll end up showing that.
+            showControlsView(items)
+        } else {
+            showInitialSetupView(items)
+        }
+    }
+
     private fun reload(parent: ViewGroup) {
         if (hidden) return
 
         controlsListingController.get().removeCallback(listingCallback)
         controlsController.get().unsubscribe()
+        taskViewController?.dismiss()
+        taskViewController = null
 
         val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f)
         fadeAnim.setInterpolator(AccelerateInterpolator(1.0f))
@@ -281,27 +334,98 @@
     private fun showControlsView(items: List<SelectionItem>) {
         controlViewsById.clear()
 
-        val itemsByComponent = items.associateBy { it.componentName }
-        val itemsWithStructure = mutableListOf<SelectionItem>()
-        allStructures.mapNotNullTo(itemsWithStructure) {
+        val (panels, structures) = items.partition { it.isPanel }
+        val panelComponents = panels.map { it.componentName }.toSet()
+
+        val itemsByComponent = structures.associateBy { it.componentName }
+                .filterNot { it.key in panelComponents }
+        val panelsAndStructures = mutableListOf<SelectionItem>()
+        allStructures.mapNotNullTo(panelsAndStructures) {
             itemsByComponent.get(it.componentName)?.copy(structure = it.structure)
         }
-        itemsWithStructure.sortWith(localeComparator)
+        panelsAndStructures.addAll(panels)
 
-        val selectionItem = findSelectionItem(selectedStructure, itemsWithStructure) ?: items[0]
+        panelsAndStructures.sortWith(localeComparator)
 
-        controlsMetricsLogger.refreshBegin(selectionItem.uid, !keyguardStateController.isUnlocked())
+        lastSelections = panelsAndStructures
 
-        createListView(selectionItem)
-        createDropDown(itemsWithStructure, selectionItem)
+        val selectionItem = findSelectionItem(selectedItem, panelsAndStructures)
+                ?: if (panels.isNotEmpty()) {
+                    // If we couldn't find a good selected item, but there's at least one panel,
+                    // show a panel.
+                    panels[0]
+                } else {
+                    items[0]
+                }
+        maybeUpdateSelectedItem(selectionItem)
+
+        createControlsSpaceFrame()
+
+        if (taskViewFactory.isPresent && selectionItem.isPanel) {
+            createPanelView(selectionItem.panelComponentName!!)
+        } else if (!selectionItem.isPanel) {
+            controlsMetricsLogger
+                    .refreshBegin(selectionItem.uid, !keyguardStateController.isUnlocked())
+            createListView(selectionItem)
+        } else {
+            Log.w(ControlsUiController.TAG, "Not TaskViewFactory to display panel $selectionItem")
+        }
+
+        createDropDown(panelsAndStructures, selectionItem)
         createMenu()
     }
 
-    private fun createMenu() {
-        val items = arrayOf(
-            context.resources.getString(R.string.controls_menu_add),
-            context.resources.getString(R.string.controls_menu_edit)
+    private fun createPanelView(componentName: ComponentName) {
+        val setting = controlsSettingsRepository
+                .allowActionOnTrivialControlsInLockscreen.value
+        val pendingIntent = PendingIntent.getActivityAsUser(
+                context,
+                0,
+                Intent()
+                        .setComponent(componentName)
+                        .putExtra(
+                                ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
+                                setting
+                        ),
+                PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+                null,
+                userTracker.userHandle
         )
+
+        parent.requireViewById<View>(R.id.controls_scroll_view).visibility = View.GONE
+        val container = parent.requireViewById<FrameLayout>(R.id.controls_panel)
+        container.visibility = View.VISIBLE
+        container.post {
+            taskViewFactory.get().create(activityContext, uiExecutor) { taskView ->
+                taskViewController = PanelTaskViewController(
+                        activityContext,
+                        uiExecutor,
+                        pendingIntent,
+                        taskView,
+                        onDismiss::run
+                ).also {
+                    container.addView(taskView)
+                    it.launchTaskView()
+                }
+            }
+        }
+    }
+
+    private fun createMenu() {
+        val isPanel = selectedItem is SelectedItem.PanelItem
+        val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
+                ?: EMPTY_STRUCTURE
+
+        val items = if (isPanel) {
+            arrayOf(
+                    context.resources.getString(R.string.controls_menu_add),
+            )
+        } else {
+            arrayOf(
+                    context.resources.getString(R.string.controls_menu_add),
+                    context.resources.getString(R.string.controls_menu_edit)
+            )
+        }
         var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items)
 
         val anchor = parent.requireViewById<ImageView>(R.id.controls_more)
@@ -322,7 +446,13 @@
                         ) {
                             when (pos) {
                                 // 0: Add Control
-                                0 -> startFavoritingActivity(selectedStructure)
+                                0 -> {
+                                    if (isPanel) {
+                                        startProviderSelectorActivity()
+                                    } else {
+                                        startFavoritingActivity(selectedStructure)
+                                    }
+                                }
                                 // 1: Edit controls
                                 1 -> startEditingActivity(selectedStructure)
                             }
@@ -344,6 +474,9 @@
             addAll(items)
         }
 
+        val iconSize = context.resources
+                .getDimensionPixelSize(R.dimen.controls_header_app_icon_size)
+
         /*
          * Default spinner widget does not work with the window type required
          * for this dialog. Use a textView with the ListPopupWindow to achieve
@@ -354,14 +487,21 @@
             // override the default color on the dropdown drawable
             (getBackground() as LayerDrawable).getDrawable(0)
                 .setTint(context.resources.getColor(R.color.control_spinner_dropdown, null))
-        }
-
-        if (items.size == 1) {
-            spinner.setBackground(null)
-            return
+            selected.icon.setBounds(0, 0, iconSize, iconSize)
+            compoundDrawablePadding = (iconSize / 2.4f).toInt()
+            setCompoundDrawablesRelative(selected.icon, null, null, null)
         }
 
         val anchor = parent.requireViewById<ViewGroup>(R.id.controls_header)
+        if (items.size == 1) {
+            spinner.setBackground(null)
+            anchor.setOnClickListener(null)
+            return
+        } else {
+            spinner.background = parent.context.resources
+                    .getDrawable(R.drawable.control_spinner_background)
+        }
+
         anchor.setOnClickListener(object : View.OnClickListener {
             override fun onClick(v: View) {
                 popup = GlobalActionsPopupMenu(
@@ -389,14 +529,20 @@
         })
     }
 
-    private fun createListView(selected: SelectionItem) {
-        val inflater = LayoutInflater.from(context)
+    private fun createControlsSpaceFrame() {
+        val inflater = LayoutInflater.from(activityContext)
         inflater.inflate(R.layout.controls_with_favorites, parent, true)
 
         parent.requireViewById<ImageView>(R.id.controls_close).apply {
             setOnClickListener { _: View -> onDismiss.run() }
             visibility = View.VISIBLE
         }
+    }
+
+    private fun createListView(selected: SelectionItem) {
+        if (selectedItem !is SelectedItem.StructureItem) return
+        val selectedStructure = (selectedItem as SelectedItem.StructureItem).structure
+        val inflater = LayoutInflater.from(activityContext)
 
         val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources)
 
@@ -444,35 +590,51 @@
         }
     }
 
-    override fun getPreferredStructure(structures: List<StructureInfo>): StructureInfo {
-        if (structures.isEmpty()) return EMPTY_STRUCTURE
+    override fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem {
+        val sp = sharedPreferences
 
-        val component = sharedPreferences.getString(PREF_COMPONENT, null)?.let {
+        val component = sp.getString(PREF_COMPONENT, null)?.let {
             ComponentName.unflattenFromString(it)
         } ?: EMPTY_COMPONENT
-        val structure = sharedPreferences.getString(PREF_STRUCTURE, "")
-
-        return structures.firstOrNull {
-            component == it.componentName && structure == it.structure
-        } ?: structures.get(0)
+        val name = sp.getString(PREF_STRUCTURE_OR_APP_NAME, "")!!
+        val isPanel = sp.getBoolean(PREF_IS_PANEL, false)
+        return if (isPanel) {
+            SelectedItem.PanelItem(name, component)
+        } else {
+            if (structures.isEmpty()) return SelectedItem.EMPTY_SELECTION
+            SelectedItem.StructureItem(structures.firstOrNull {
+                component == it.componentName && name == it.structure
+            } ?: structures.get(0))
+        }
     }
 
-    private fun updatePreferences(si: StructureInfo) {
-        if (si == EMPTY_STRUCTURE) return
+    private fun updatePreferences(si: SelectedItem) {
         sharedPreferences.edit()
-            .putString(PREF_COMPONENT, si.componentName.flattenToString())
-            .putString(PREF_STRUCTURE, si.structure.toString())
-            .commit()
+                .putString(PREF_COMPONENT, si.componentName.flattenToString())
+                .putString(PREF_STRUCTURE_OR_APP_NAME, si.name.toString())
+                .putBoolean(PREF_IS_PANEL, si is SelectedItem.PanelItem)
+                .commit()
+    }
+
+    private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {
+        val newSelection = if (item.isPanel) {
+            SelectedItem.PanelItem(item.appName, item.componentName)
+        } else {
+            SelectedItem.StructureItem(allStructures.firstOrNull {
+                it.structure == item.structure && it.componentName == item.componentName
+            } ?: EMPTY_STRUCTURE)
+        }
+        return if (newSelection != selectedItem ) {
+            selectedItem = newSelection
+            updatePreferences(selectedItem)
+            true
+        } else {
+            false
+        }
     }
 
     private fun switchAppOrStructure(item: SelectionItem) {
-        val newSelection = allStructures.first {
-            it.structure == item.structure && it.componentName == item.componentName
-        }
-
-        if (newSelection != selectedStructure) {
-            selectedStructure = newSelection
-            updatePreferences(selectedStructure)
+        if (maybeUpdateSelectedItem(item)) {
             reload(parent)
         }
     }
@@ -496,6 +658,8 @@
 
         closeDialogs(true)
         controlsController.get().unsubscribe()
+        taskViewController?.dismiss()
+        taskViewController = null
 
         parent.removeAllViews()
         controlsById.clear()
@@ -536,20 +700,48 @@
         return row
     }
 
-    private fun findSelectionItem(si: StructureInfo, items: List<SelectionItem>): SelectionItem? =
-        items.firstOrNull {
-            it.componentName == si.componentName && it.structure == si.structure
+    private fun findSelectionItem(si: SelectedItem, items: List<SelectionItem>): SelectionItem? =
+        items.firstOrNull { it.matches(si) }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("ControlsUiControllerImpl:")
+        pw.asIndenting().indentIfPossible {
+            println("hidden: $hidden")
+            println("selectedItem: $selectedItem")
+            println("lastSelections: $lastSelections")
+            println("setting: ${controlsSettingsRepository
+                    .allowActionOnTrivialControlsInLockscreen.value}")
         }
+    }
 }
 
-private data class SelectionItem(
+@VisibleForTesting
+internal data class SelectionItem(
     val appName: CharSequence,
     val structure: CharSequence,
     val icon: Drawable,
     val componentName: ComponentName,
-    val uid: Int
+    val uid: Int,
+    val panelComponentName: ComponentName?
 ) {
     fun getTitle() = if (structure.isEmpty()) { appName } else { structure }
+
+    val isPanel: Boolean = panelComponentName != null
+
+    fun matches(selectedItem: SelectedItem): Boolean {
+        if (componentName != selectedItem.componentName) {
+            // Not the same component so they are not the same.
+            return false
+        }
+        if (isPanel || selectedItem is SelectedItem.PanelItem) {
+            // As they have the same component, if [this.isPanel] then we may be migrating from
+            // device controls API into panel. Want this to match, even if the selectedItem is not
+            // a panel. We don't want to match on app name because that can change with locale.
+            return true
+        }
+        // Return true if we find a structure with the correct name
+        return structure == (selectedItem as SelectedItem.StructureItem).structure.structure
+    }
 }
 
 private class ItemAdapter(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
new file mode 100644
index 0000000..7143be2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.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.controls.ui
+
+import android.app.ActivityOptions
+import android.app.ActivityTaskManager
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.util.boundsOnScreen
+import com.android.wm.shell.TaskView
+import java.util.concurrent.Executor
+
+class PanelTaskViewController(
+    private val activityContext: Context,
+    private val uiExecutor: Executor,
+    private val pendingIntent: PendingIntent,
+    private val taskView: TaskView,
+    private val hide: () -> Unit = {}
+) {
+
+    private var detailTaskId = INVALID_TASK_ID
+
+    private val fillInIntent =
+        Intent().apply {
+            // Apply flags to make behaviour match documentLaunchMode=always.
+            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+        }
+
+    private fun removeDetailTask() {
+        if (detailTaskId == INVALID_TASK_ID) return
+        ActivityTaskManager.getInstance().removeTask(detailTaskId)
+        detailTaskId = INVALID_TASK_ID
+    }
+
+    private val stateCallback =
+        object : TaskView.Listener {
+            override fun onInitialized() {
+
+                val options =
+                    ActivityOptions.makeCustomAnimation(
+                        activityContext,
+                        0 /* enterResId */,
+                        0 /* exitResId */
+                    )
+                options.taskAlwaysOnTop = true
+
+                taskView.post {
+                    taskView.startActivity(
+                        pendingIntent,
+                        fillInIntent,
+                        options,
+                        taskView.boundsOnScreen
+                    )
+                }
+            }
+
+            override fun onTaskRemovalStarted(taskId: Int) {
+                detailTaskId = INVALID_TASK_ID
+                dismiss()
+            }
+
+            override fun onTaskCreated(taskId: Int, name: ComponentName?) {
+                detailTaskId = taskId
+            }
+
+            override fun onReleased() {
+                removeDetailTask()
+            }
+
+            override fun onBackPressedOnTaskRoot(taskId: Int) {
+                dismiss()
+                hide()
+            }
+        }
+
+    fun dismiss() {
+        taskView.release()
+    }
+
+    fun launchTaskView() {
+        taskView.setListener(uiExecutor, stateCallback)
+    }
+}
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/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index d60a222..3d8e4cb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -19,7 +19,6 @@
 import android.content.BroadcastReceiver;
 
 import com.android.systemui.GuestResetOrExitSessionReceiver;
-import com.android.systemui.GuestResumeSessionReceiver;
 import com.android.systemui.media.dialog.MediaOutputDialogReceiver;
 import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver;
 import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
@@ -106,15 +105,6 @@
      */
     @Binds
     @IntoMap
-    @ClassKey(GuestResumeSessionReceiver.class)
-    public abstract BroadcastReceiver bindGuestResumeSessionReceiver(
-            GuestResumeSessionReceiver broadcastReceiver);
-
-    /**
-     *
-     */
-    @Binds
-    @IntoMap
     @ClassKey(GuestResetOrExitSessionReceiver.class)
     public abstract BroadcastReceiver bindGuestResetOrExitSessionReceiver(
             GuestResetOrExitSessionReceiver broadcastReceiver);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 139a8b7..8b4b30c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -17,9 +17,11 @@
 package com.android.systemui.dagger;
 
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 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;
@@ -30,6 +32,7 @@
 import android.app.UiModeManager;
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
+import android.app.job.JobScheduler;
 import android.app.role.RoleManager;
 import android.app.smartspace.SmartspaceManager;
 import android.app.trust.TrustManager;
@@ -54,6 +57,7 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.input.InputManager;
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.media.MediaRouter2Manager;
@@ -68,6 +72,7 @@
 import android.os.ServiceManager;
 import android.os.UserManager;
 import android.os.Vibrator;
+import android.os.storage.StorageManager;
 import android.permission.PermissionManager;
 import android.safetycenter.SafetyCenterManager;
 import android.service.dreams.DreamService;
@@ -109,6 +114,7 @@
 /**
  * Provides Non-SystemUI, Framework-Owned instances to the dependency graph.
  */
+@SuppressLint("NonInjectedService")
 @Module
 public class FrameworkServicesModule {
     @Provides
@@ -137,6 +143,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);
     }
@@ -275,12 +287,24 @@
 
     @Provides
     @Singleton
+    static JobScheduler provideJobScheduler(Context context) {
+        return context.getSystemService(JobScheduler.class);
+    }
+
+    @Provides
+    @Singleton
     static InteractionJankMonitor provideInteractionJankMonitor() {
         return InteractionJankMonitor.getInstance();
     }
 
     @Provides
     @Singleton
+    static InputManager provideInputManager(Context context) {
+        return context.getSystemService(InputManager.class);
+    }
+
+    @Provides
+    @Singleton
     static InputMethodManager provideInputMethodManager(Context context) {
         return context.getSystemService(InputMethodManager.class);
     }
@@ -462,7 +486,13 @@
 
     @Provides
     @Singleton
-    static SubscriptionManager provideSubcriptionManager(Context context) {
+    static StorageManager provideStorageManager(Context context) {
+        return context.getSystemService(StorageManager.class);
+    }
+
+    @Provides
+    @Singleton
+    static SubscriptionManager provideSubscriptionManager(Context context) {
         return context.getSystemService(SubscriptionManager.class);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index 9e33ee1..9e8c0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -21,22 +21,21 @@
 import com.android.systemui.dagger.qualifiers.InstrumentationTest;
 import com.android.systemui.util.InitializationChecker;
 
-import javax.inject.Singleton;
-
 import dagger.BindsInstance;
-import dagger.Component;
 
 /**
- * Root component for Dagger injection.
+ * Base root component for Dagger injection.
+ *
+ * This class is not actually annotated as a Dagger component, since it is not used directly as one.
+ * Doing so generates unnecessary code bloat.
+ *
+ * See {@link ReferenceGlobalRootComponent} for the one actually used by AOSP.
  */
-@Singleton
-@Component(modules = {GlobalModule.class})
 public interface GlobalRootComponent {
 
     /**
      * Builder for a GlobalRootComponent.
      */
-    @Component.Builder
     interface Builder {
         @BindsInstance
         Builder context(Context context);
@@ -51,7 +50,7 @@
     WMComponent.Builder getWMComponentBuilder();
 
     /**
-     * Builder for a {@link SysUIComponent}, which makes it a subcomponent of this class.
+     * Builder for a {@link ReferenceSysUIComponent}, which makes it a subcomponent of this class.
      */
     SysUIComponent.Builder getSysUIComponent();
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceGlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceGlobalRootComponent.java
new file mode 100644
index 0000000..be93c9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceGlobalRootComponent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dagger;
+
+import javax.inject.Singleton;
+
+import dagger.Component;
+
+/**
+ * Root component for Dagger injection used in AOSP.
+ */
+@Singleton
+@Component(modules = {GlobalModule.class})
+public interface ReferenceGlobalRootComponent extends GlobalRootComponent {
+
+    /**
+     * Builder for a ReferenceGlobalRootComponent.
+     */
+    @Component.Builder
+    interface Builder extends GlobalRootComponent.Builder {
+        ReferenceGlobalRootComponent build();
+    }
+
+    /**
+     * Builder for a {@link ReferenceSysUIComponent}, which makes it a subcomponent of this class.
+     */
+    ReferenceSysUIComponent.Builder getSysUIComponent();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
new file mode 100644
index 0000000..b30e0c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dagger;
+
+import com.android.systemui.keyguard.KeyguardQuickAffordanceProvider;
+import com.android.systemui.statusbar.NotificationInsetsModule;
+import com.android.systemui.statusbar.QsFrameTranslateModule;
+
+import dagger.Subcomponent;
+
+/**
+ * Dagger Subcomponent for Core SysUI used in AOSP.
+ */
+@SysUISingleton
+@Subcomponent(modules = {
+        DefaultComponentBinder.class,
+        DependencyProvider.class,
+        NotificationInsetsModule.class,
+        QsFrameTranslateModule.class,
+        SystemUIBinder.class,
+        SystemUIModule.class,
+        SystemUICoreStartableModule.class,
+        ReferenceSystemUIModule.class})
+public interface ReferenceSysUIComponent extends SysUIComponent {
+
+    /**
+     * Builder for a ReferenceSysUIComponent.
+     */
+    @SysUISingleton
+    @Subcomponent.Builder
+    interface Builder extends SysUIComponent.Builder {
+        ReferenceSysUIComponent build();
+    }
+
+    /**
+     * Member injection into the supplied argument.
+     */
+    void inject(KeyguardQuickAffordanceProvider keyguardQuickAffordanceProvider);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 48bef97..d0c5007 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -41,9 +41,11 @@
 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;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
@@ -93,6 +95,7 @@
         AospPolicyModule.class,
         GestureModule.class,
         MediaModule.class,
+        MultiUserUtilsModule.class,
         PowerModule.class,
         QSModule.class,
         ReferenceScreenshotModule.class,
@@ -162,7 +165,8 @@
             ConfigurationController configurationController,
             @Main Handler handler,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            ShadeExpansionStateManager shadeExpansionStateManager) {
         return new HeadsUpManagerPhone(
                 context,
                 headsUpManagerLogger,
@@ -173,7 +177,8 @@
                 configurationController,
                 handler,
                 accessibilityManagerWrapper,
-                uiEventLogger
+                uiEventLogger,
+                shadeExpansionStateManager
         );
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index d05bd51..6dc4f5c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -28,6 +28,7 @@
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.people.PeopleProvider;
+import com.android.systemui.statusbar.NotificationInsetsModule;
 import com.android.systemui.statusbar.QsFrameTranslateModule;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.unfold.FoldStateLogger;
@@ -40,7 +41,6 @@
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
-import com.android.wm.shell.floating.FloatingTasks;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.recents.RecentTasks;
@@ -58,12 +58,15 @@
 import dagger.Subcomponent;
 
 /**
- * Dagger Subcomponent for Core SysUI.
+ * An example Dagger Subcomponent for Core SysUI.
+ *
+ * See {@link ReferenceSysUIComponent} for the one actually used by AOSP.
  */
 @SysUISingleton
 @Subcomponent(modules = {
         DefaultComponentBinder.class,
         DependencyProvider.class,
+        NotificationInsetsModule.class,
         QsFrameTranslateModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
@@ -111,9 +114,6 @@
         Builder setBackAnimation(Optional<BackAnimation> b);
 
         @BindsInstance
-        Builder setFloatingTasks(Optional<FloatingTasks> f);
-
-        @BindsInstance
         Builder setDesktopMode(Optional<DesktopMode> d);
 
         SysUIComponent build();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 09743ef..85ba68c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
 import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
 import com.android.systemui.power.PowerUI
+import com.android.systemui.reardisplay.RearDisplayDialogController
 import com.android.systemui.recents.Recents
 import com.android.systemui.settings.dagger.MultiUserUtilsModule
 import com.android.systemui.shortcut.ShortcutKeyDispatcher
@@ -250,4 +251,11 @@
     @IntoMap
     @ClassKey(ChipbarCoordinator::class)
     abstract fun bindChipbarController(sysui: ChipbarCoordinator): CoreStartable
+
+
+    /** Inject into RearDisplayDialogController) */
+    @Binds
+    @IntoMap
+    @ClassKey(RearDisplayDialogController::class)
+    abstract fun bindRearDisplayDialogController(sysui: RearDisplayDialogController): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 6db56210..3a59f4b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -33,6 +33,7 @@
 import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
 import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
 import com.android.systemui.biometrics.dagger.BiometricsModule;
+import com.android.systemui.biometrics.dagger.UdfpsModule;
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
 import com.android.systemui.controls.dagger.ControlsModule;
@@ -41,12 +42,14 @@
 import com.android.systemui.doze.dagger.DozeComponent;
 import com.android.systemui.dreams.dagger.DreamModule;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.FlagsModule;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.data.BouncerViewModule;
 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 +61,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;
@@ -134,13 +136,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,
@@ -153,6 +155,7 @@
             TelephonyRepositoryModule.class,
             TemporaryDisplayModule.class,
             TunerModule.class,
+            UdfpsModule.class,
             UserModule.class,
             UtilModule.class,
             NoteTaskModule.class,
@@ -240,6 +243,7 @@
             CommonNotifCollection notifCollection,
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
+            FeatureFlags featureFlags,
             @Main Executor sysuiMainExecutor) {
         return Optional.ofNullable(BubblesManager.create(context,
                 bubblesOptional,
@@ -256,6 +260,7 @@
                 notifCollection,
                 notifPipeline,
                 sysUiState,
+                featureFlags,
                 sysuiMainExecutor));
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 096f969..d756f3a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -32,7 +32,6 @@
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
-import com.android.wm.shell.floating.FloatingTasks;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.recents.RecentTasks;
@@ -111,9 +110,6 @@
     @WMSingleton
     Optional<BackAnimation> getBackAnimation();
 
-    @WMSingleton
-    Optional<FloatingTasks> getFloatingTasks();
-
     /**
      * Optional {@link DesktopMode} component for interacting with desktop mode.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
index d537d4b..000bbe6 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
@@ -54,6 +54,9 @@
     private val receiverMap: Map<String, MutableList<DemoMode>>
 
     init {
+        // Don't persist demo mode across restarts.
+        requestFinishDemoMode()
+
         val m = mutableMapOf<String, MutableList<DemoMode>>()
         DemoMode.COMMANDS.map { command ->
             m.put(command, mutableListOf())
@@ -74,7 +77,6 @@
         // content changes to know if the setting turned on or off
         tracker.startTracking()
 
-        // TODO: We should probably exit demo mode if we booted up with it on
         isInDemoMode = tracker.isInDemoMode
 
         val demoFilter = IntentFilter()
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index b69afeb..0c14ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -133,9 +133,9 @@
     /**
      * Appends fling event to the logs
      */
-    public void traceFling(boolean expand, boolean aboveThreshold, boolean thresholdNeeded,
+    public void traceFling(boolean expand, boolean aboveThreshold,
             boolean screenOnFromTouch) {
-        mLogger.logFling(expand, aboveThreshold, thresholdNeeded, screenOnFromTouch);
+        mLogger.logFling(expand, aboveThreshold, screenOnFromTouch);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 18c8e01..b5dbe21 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -96,13 +96,11 @@
     fun logFling(
         expand: Boolean,
         aboveThreshold: Boolean,
-        thresholdNeeded: Boolean,
         screenOnFromTouch: Boolean
     ) {
         buffer.log(TAG, DEBUG, {
             bool1 = expand
             bool2 = aboveThreshold
-            bool3 = thresholdNeeded
             bool4 = screenOnFromTouch
         }, {
             "Fling expand=$bool1 aboveThreshold=$bool2 thresholdNeeded=$bool3 " +
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/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index d0258d3..f64d918 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -24,7 +24,6 @@
 import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
 
 import android.annotation.AnyThread;
-import android.app.ActivityManager;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
@@ -50,6 +49,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -99,6 +99,7 @@
     private final SecureSettings mSecureSettings;
     private final DevicePostureController mDevicePostureController;
     private final AuthController mAuthController;
+    private final UserTracker mUserTracker;
     private final boolean mScreenOffUdfpsEnabled;
 
     // Sensors
@@ -152,7 +153,8 @@
             ProximitySensor proximitySensor,
             SecureSettings secureSettings,
             AuthController authController,
-            DevicePostureController devicePostureController
+            DevicePostureController devicePostureController,
+            UserTracker userTracker
     ) {
         mSensorManager = sensorManager;
         mConfig = config;
@@ -170,6 +172,7 @@
         mDevicePostureController = devicePostureController;
         mDevicePosture = mDevicePostureController.getDevicePosture();
         mAuthController = authController;
+        mUserTracker = userTracker;
 
         mUdfpsEnrolled =
                 mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser());
@@ -441,7 +444,7 @@
     private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
         @Override
         public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) {
-            if (userId != ActivityManager.getCurrentUser()) {
+            if (userId != mUserTracker.getUserId()) {
                 return;
             }
             for (TriggerSensor s : mTriggerSensors) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index e8d7e46..f8bd1e7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -27,7 +27,7 @@
 import com.android.systemui.plugins.DozeServicePlugin;
 import com.android.systemui.plugins.DozeServicePlugin.RequestDoze;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt
new file mode 100644
index 0000000..12ceedd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.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.systemui.doze
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.CallbackController
+import javax.inject.Inject
+
+/** Receives doze transition events, and passes those events to registered callbacks. */
+@SysUISingleton
+class DozeTransitionListener @Inject constructor() :
+    DozeMachine.Part, CallbackController<DozeTransitionCallback> {
+    val callbacks = mutableSetOf<DozeTransitionCallback>()
+    var oldState = DozeMachine.State.UNINITIALIZED
+    var newState = DozeMachine.State.UNINITIALIZED
+
+    override fun transitionTo(oldState: DozeMachine.State, newState: DozeMachine.State) {
+        this.oldState = oldState
+        this.newState = newState
+        callbacks.forEach { it.onDozeTransition(oldState, newState) }
+    }
+
+    override fun addCallback(callback: DozeTransitionCallback) {
+        callbacks.add(callback)
+    }
+
+    override fun removeCallback(callback: DozeTransitionCallback) {
+        callbacks.remove(callback)
+    }
+}
+
+interface DozeTransitionCallback {
+    fun onDozeTransition(oldState: DozeMachine.State, newState: DozeMachine.State)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 32cb1c0..5daf1ce 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -29,12 +29,13 @@
 import android.content.IntentFilter;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.text.format.Formatter;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.view.Display;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEvent;
@@ -45,6 +46,7 @@
 import com.android.systemui.doze.DozeMachine.State;
 import com.android.systemui.doze.dagger.DozeScope;
 import com.android.systemui.log.SessionTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -99,6 +101,7 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final AuthController mAuthController;
     private final KeyguardStateController mKeyguardStateController;
+    private final UserTracker mUserTracker;
     private final UiEventLogger mUiEventLogger;
 
     private long mNotificationPulseTime;
@@ -109,6 +112,14 @@
     private boolean mWantTouchScreenSensors;
     private boolean mWantSensors;
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    mDozeSensors.onUserSwitched();
+                }
+            };
+
     @VisibleForTesting
     public enum DozingUpdateUiEvent implements UiEventLogger.UiEventEnum {
         @UiEvent(doc = "Dozing updated due to notification.")
@@ -188,7 +199,8 @@
             UiEventLogger uiEventLogger,
             SessionTracker sessionTracker,
             KeyguardStateController keyguardStateController,
-            DevicePostureController devicePostureController) {
+            DevicePostureController devicePostureController,
+            UserTracker userTracker) {
         mContext = context;
         mDozeHost = dozeHost;
         mConfig = config;
@@ -200,7 +212,7 @@
 
         mDozeSensors = new DozeSensors(mSensorManager, dozeParameters,
                 config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
-                secureSettings, authController, devicePostureController);
+                secureSettings, authController, devicePostureController, userTracker);
         mDockManager = dockManager;
         mProxCheck = proxCheck;
         mDozeLog = dozeLog;
@@ -208,6 +220,7 @@
         mAuthController = authController;
         mUiEventLogger = uiEventLogger;
         mKeyguardStateController = keyguardStateController;
+        mUserTracker = userTracker;
     }
 
     @Override
@@ -232,7 +245,7 @@
             return;
         }
         mNotificationPulseTime = SystemClock.elapsedRealtime();
-        if (!mConfig.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) {
+        if (!mConfig.pulseOnNotificationEnabled(mUserTracker.getUserId())) {
             runIfNotNull(onPulseSuppressedListener);
             mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled");
             return;
@@ -488,12 +501,14 @@
         mBroadcastReceiver.register(mBroadcastDispatcher);
         mDockManager.addListener(mDockEventListener);
         mDozeHost.addCallback(mHostCallback);
+        mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
     }
 
     private void unregisterCallbacks() {
         mBroadcastReceiver.unregister(mBroadcastDispatcher);
         mDozeHost.removeCallback(mHostCallback);
         mDockManager.removeListener(mDockEventListener);
+        mUserTracker.removeCallback(mUserChangedCallback);
     }
 
     private void stopListeningToAllTriggers() {
@@ -618,9 +633,6 @@
                 requestPulse(DozeLog.PULSE_REASON_INTENT, false, /* performedProxCheck */
                         null /* onPulseSuppressedListener */);
             }
-            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
-                mDozeSensors.onUserSwitched();
-            }
         }
 
         public void register(BroadcastDispatcher broadcastDispatcher) {
@@ -628,7 +640,6 @@
                 return;
             }
             IntentFilter filter = new IntentFilter(PULSE_ACTION);
-            filter.addAction(Intent.ACTION_USER_SWITCHED);
             broadcastDispatcher.registerReceiver(this, filter);
             mRegistered = true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 98cd2d7..069344f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -35,6 +35,7 @@
 import com.android.systemui.doze.DozeSensors;
 import com.android.systemui.doze.DozeSuppressor;
 import com.android.systemui.doze.DozeSuspendScreenStatePreventingAdapter;
+import com.android.systemui.doze.DozeTransitionListener;
 import com.android.systemui.doze.DozeTriggers;
 import com.android.systemui.doze.DozeUi;
 import com.android.systemui.doze.DozeWallpaperState;
@@ -83,7 +84,7 @@
             DozeUi dozeUi, DozeScreenState dozeScreenState,
             DozeScreenBrightness dozeScreenBrightness, DozeWallpaperState dozeWallpaperState,
             DozeDockHandler dozeDockHandler, DozeAuthRemover dozeAuthRemover,
-            DozeSuppressor dozeSuppressor) {
+            DozeSuppressor dozeSuppressor, DozeTransitionListener dozeTransitionListener) {
         return new DozeMachine.Part[]{
                 dozePauser,
                 dozeFalsingManagerAdapter,
@@ -94,7 +95,8 @@
                 dozeWallpaperState,
                 dozeDockHandler,
                 dozeAuthRemover,
-                dozeSuppressor
+                dozeSuppressor,
+                dozeTransitionListener
         };
     }
 
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..0087c84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -0,0 +1,280 @@
+/*
+ * 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.AnimatorSet
+import android.animation.ValueAnimator
+import android.view.View
+import android.view.animation.Interpolator
+import androidx.annotation.FloatRange
+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.complication.ComplicationLayoutParams.Position
+import com.android.systemui.dreams.dagger.DreamOverlayModule
+import com.android.systemui.statusbar.BlurUtils
+import com.android.systemui.statusbar.CrossFadeHelper
+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 mDreamInBlurAnimDurationMs: Long,
+    @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY)
+    private val mDreamInBlurAnimDelayMs: Long,
+    @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
+    private val mDreamInComplicationsAnimDurationMs: Long,
+    @Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
+    private val mDreamInTopComplicationsAnimDelayMs: Long,
+    @Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
+    private val mDreamInBottomComplicationsAnimDelayMs: Long,
+    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DISTANCE)
+    private val mDreamOutTranslationYDistance: Int,
+    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DURATION)
+    private val mDreamOutTranslationYDurationMs: Long,
+    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
+    private val mDreamOutTranslationYDelayBottomMs: Long,
+    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
+    private val mDreamOutTranslationYDelayTopMs: Long,
+    @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DURATION) private val mDreamOutAlphaDurationMs: Long,
+    @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_BOTTOM)
+    private val mDreamOutAlphaDelayBottomMs: Long,
+    @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_TOP) private val mDreamOutAlphaDelayTopMs: Long,
+    @Named(DreamOverlayModule.DREAM_OUT_BLUR_DURATION) private val mDreamOutBlurDurationMs: Long
+) {
+
+    private var mAnimator: Animator? = null
+
+    /**
+     * Store the current alphas at the various positions. This is so that we may resume an animation
+     * at the current alpha.
+     */
+    private var mCurrentAlphaAtPosition = mutableMapOf<Int, Float>()
+
+    @FloatRange(from = 0.0, to = 1.0) private var mBlurProgress: Float = 0f
+
+    /** Starts the dream content and dream overlay entry animations. */
+    @JvmOverloads
+    fun startEntryAnimations(view: View, animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
+        cancelAnimations()
+
+        mAnimator =
+            animatorBuilder().apply {
+                playTogether(
+                    blurAnimator(
+                        view = view,
+                        from = 1f,
+                        to = 0f,
+                        durationMs = mDreamInBlurAnimDurationMs,
+                        delayMs = mDreamInBlurAnimDelayMs
+                    ),
+                    alphaAnimator(
+                        from = 0f,
+                        to = 1f,
+                        durationMs = mDreamInComplicationsAnimDurationMs,
+                        delayMs = mDreamInTopComplicationsAnimDelayMs,
+                        position = ComplicationLayoutParams.POSITION_TOP
+                    ),
+                    alphaAnimator(
+                        from = 0f,
+                        to = 1f,
+                        durationMs = mDreamInComplicationsAnimDurationMs,
+                        delayMs = mDreamInBottomComplicationsAnimDelayMs,
+                        position = ComplicationLayoutParams.POSITION_BOTTOM
+                    )
+                )
+                doOnEnd {
+                    mAnimator = null
+                    mOverlayStateController.setEntryAnimationsFinished(true)
+                }
+                start()
+            }
+    }
+
+    /** Starts the dream content and dream overlay exit animations. */
+    @JvmOverloads
+    fun startExitAnimations(
+        view: View,
+        doneCallback: () -> Unit,
+        animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
+    ) {
+        cancelAnimations()
+
+        mAnimator =
+            animatorBuilder().apply {
+                playTogether(
+                    blurAnimator(
+                        view = view,
+                        // Start the blurring wherever the entry animation ended, in
+                        // case it was cancelled early.
+                        from = mBlurProgress,
+                        to = 1f,
+                        durationMs = mDreamOutBlurDurationMs
+                    ),
+                    translationYAnimator(
+                        from = 0f,
+                        to = mDreamOutTranslationYDistance.toFloat(),
+                        durationMs = mDreamOutTranslationYDurationMs,
+                        delayMs = mDreamOutTranslationYDelayBottomMs,
+                        position = ComplicationLayoutParams.POSITION_BOTTOM,
+                        animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
+                    ),
+                    translationYAnimator(
+                        from = 0f,
+                        to = mDreamOutTranslationYDistance.toFloat(),
+                        durationMs = mDreamOutTranslationYDurationMs,
+                        delayMs = mDreamOutTranslationYDelayTopMs,
+                        position = ComplicationLayoutParams.POSITION_TOP,
+                        animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
+                    ),
+                    alphaAnimator(
+                        from =
+                            mCurrentAlphaAtPosition.getOrDefault(
+                                key = ComplicationLayoutParams.POSITION_BOTTOM,
+                                defaultValue = 1f
+                            ),
+                        to = 0f,
+                        durationMs = mDreamOutAlphaDurationMs,
+                        delayMs = mDreamOutAlphaDelayBottomMs,
+                        position = ComplicationLayoutParams.POSITION_BOTTOM
+                    ),
+                    alphaAnimator(
+                        from =
+                            mCurrentAlphaAtPosition.getOrDefault(
+                                key = ComplicationLayoutParams.POSITION_TOP,
+                                defaultValue = 1f
+                            ),
+                        to = 0f,
+                        durationMs = mDreamOutAlphaDurationMs,
+                        delayMs = mDreamOutAlphaDelayTopMs,
+                        position = ComplicationLayoutParams.POSITION_TOP
+                    )
+                )
+                doOnEnd {
+                    mAnimator = null
+                    mOverlayStateController.setExitAnimationsRunning(false)
+                    doneCallback()
+                }
+                start()
+            }
+        mOverlayStateController.setExitAnimationsRunning(true)
+    }
+
+    /** Cancels the dream content and dream overlay animations, if they're currently running. */
+    fun cancelAnimations() {
+        mAnimator =
+            mAnimator?.let {
+                it.cancel()
+                null
+            }
+    }
+
+    private fun blurAnimator(
+        view: View,
+        from: Float,
+        to: Float,
+        durationMs: Long,
+        delayMs: Long = 0
+    ): Animator {
+        return ValueAnimator.ofFloat(from, to).apply {
+            duration = durationMs
+            startDelay = delayMs
+            interpolator = Interpolators.LINEAR
+            addUpdateListener { animator: ValueAnimator ->
+                mBlurProgress = animator.animatedValue as Float
+                mBlurUtils.applyBlur(
+                    viewRootImpl = view.viewRootImpl,
+                    radius = mBlurUtils.blurRadiusOfRatio(mBlurProgress).toInt(),
+                    opaque = false
+                )
+            }
+        }
+    }
+
+    private fun alphaAnimator(
+        from: Float,
+        to: Float,
+        durationMs: Long,
+        delayMs: Long,
+        @Position position: Int
+    ): Animator {
+        return ValueAnimator.ofFloat(from, to).apply {
+            duration = durationMs
+            startDelay = delayMs
+            interpolator = Interpolators.LINEAR
+            addUpdateListener { va: ValueAnimator ->
+                setElementsAlphaAtPosition(
+                    alpha = va.animatedValue as Float,
+                    position = position,
+                    fadingOut = to < from
+                )
+            }
+        }
+    }
+
+    private fun translationYAnimator(
+        from: Float,
+        to: Float,
+        durationMs: Long,
+        delayMs: Long,
+        @Position position: Int,
+        animInterpolator: Interpolator
+    ): Animator {
+        return ValueAnimator.ofFloat(from, to).apply {
+            duration = durationMs
+            startDelay = delayMs
+            interpolator = animInterpolator
+            addUpdateListener { va: ValueAnimator ->
+                setElementsTranslationYAtPosition(va.animatedValue as Float, position)
+            }
+        }
+    }
+
+    /** Sets alpha of complications at the specified position. */
+    private fun setElementsAlphaAtPosition(alpha: Float, position: Int, fadingOut: Boolean) {
+        mCurrentAlphaAtPosition[position] = alpha
+        mComplicationHostViewController.getViewsAtPosition(position).forEach { view ->
+            if (fadingOut) {
+                CrossFadeHelper.fadeOut(view, 1 - alpha, /* remap= */ false)
+            } else {
+                CrossFadeHelper.fadeIn(view, alpha, /* remap= */ false)
+            }
+        }
+        if (position == ComplicationLayoutParams.POSITION_TOP) {
+            mStatusBarViewController.setFadeAmount(alpha, fadingOut)
+        }
+    }
+
+    /** Sets y translation of complications at the specified position. */
+    private fun setElementsTranslationYAtPosition(translationY: Float, position: Int) {
+        mComplicationHostViewController.getViewsAtPosition(position).forEach { v ->
+            v.translationY = translationY
+        }
+        if (position == ComplicationLayoutParams.POSITION_TOP) {
+            mStatusBarViewController.setTranslationY(translationY)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 733a80d..9d7ad30 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -29,19 +29,22 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.complication.ComplicationHostViewController;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayModule;
-import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.util.ViewController;
 
 import java.util.Arrays;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -54,6 +57,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;
 
@@ -74,14 +79,14 @@
     // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
     private final Handler mHandler;
     private final int mDreamOverlayMaxTranslationY;
-    private final BouncerCallbackInteractor mBouncerCallbackInteractor;
+    private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
 
     private long mJitterStartTimeMillis;
 
     private boolean mBouncerAnimating;
 
-    private final KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback =
-            new KeyguardBouncer.BouncerExpansionCallback() {
+    private final KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback =
+            new KeyguardBouncer.PrimaryBouncerExpansionCallback() {
 
                 @Override
                 public void onStartingToShow() {
@@ -134,12 +139,16 @@
             @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
                     burnInProtectionUpdateInterval,
             @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter,
-            BouncerCallbackInteractor bouncerCallbackInteractor) {
+            PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor,
+            DreamOverlayAnimationsController animationsController,
+            DreamOverlayStateController stateController) {
         super(containerView);
         mDreamOverlayContentView = contentView;
         mStatusBarViewController = statusBarViewController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mBlurUtils = blurUtils;
+        mDreamOverlayAnimationsController = animationsController;
+        mStateController = stateController;
 
         mComplicationHostViewController = complicationHostViewController;
         mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
@@ -154,7 +163,7 @@
         mMaxBurnInOffset = maxBurnInOffset;
         mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
         mMillisUntilFullJitter = millisUntilFullJitter;
-        mBouncerCallbackInteractor = bouncerCallbackInteractor;
+        mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
     }
 
     @Override
@@ -167,21 +176,28 @@
     protected void onViewAttached() {
         mJitterStartTimeMillis = System.currentTimeMillis();
         mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
-        final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getBouncer();
+        final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getPrimaryBouncer();
         if (bouncer != null) {
             bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback);
         }
-        mBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
+        mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
+
+        // Start dream entry animations. Skip animations for low light clock.
+        if (!mStateController.isLowLightActive()) {
+            mDreamOverlayAnimationsController.startEntryAnimations(mView);
+        }
     }
 
     @Override
     protected void onViewDetached() {
         mHandler.removeCallbacks(this::updateBurnInOffsets);
-        final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getBouncer();
+        final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getPrimaryBouncer();
         if (bouncer != null) {
             bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback);
         }
-        mBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
+        mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
+
+        mDreamOverlayAnimationsController.cancelAnimations();
     }
 
     View getContainerView() {
@@ -238,4 +254,17 @@
                         : aboutToShowBouncerProgress(expansion + 0.03f));
         return MathUtils.lerp(-mDreamOverlayMaxTranslationY, 0, fraction);
     }
+
+    /**
+     * Handle the dream waking up and run any necessary animations.
+     *
+     * @param onAnimationEnd Callback to trigger once animations are finished.
+     * @param callbackExecutor Executor to execute the callback on.
+     */
+    public void wakeUp(@NonNull Runnable onAnimationEnd, @NonNull Executor callbackExecutor) {
+        mDreamOverlayAnimationsController.startExitAnimations(mView, () -> {
+            callbackExecutor.execute(onAnimationEnd);
+            return null;
+        });
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index d1b7368..e76d5b3 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,23 @@
         });
     }
 
+    private Lifecycle.State getCurrentStateLocked() {
+        return mLifecycleRegistry.getCurrentState();
+    }
+
+    private void setCurrentStateLocked(Lifecycle.State state) {
+        mLifecycleRegistry.setCurrentState(state);
+    }
+
+    @Override
+    public void onWakeUp(@NonNull Runnable onCompletedCallback) {
+        mExecutor.execute(() -> {
+            if (mDreamOverlayContainerViewController != null) {
+                mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
+            }
+        });
+    }
+
     /**
      * 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 +251,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 +270,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..5f942b6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -51,6 +51,8 @@
 
     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;
+    public static final int STATE_DREAM_EXIT_ANIMATIONS_RUNNING = 1 << 3;
 
     private static final int OP_CLEAR_STATE = 1;
     private static final int OP_SET_STATE = 2;
@@ -202,6 +204,22 @@
         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);
+    }
+
+    /**
+     * Returns whether the dream content and dream overlay exit animations are running.
+     * @return {@code true} if animations are running, {@code false} otherwise.
+     */
+    public boolean areExitAnimationsRunning() {
+        return containsState(STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
+    }
+
     private boolean containsState(int state) {
         return (mState & state) != 0;
     }
@@ -218,7 +236,7 @@
         }
 
         if (existingState != mState) {
-            notifyCallbacks(callback -> callback.onStateChanged());
+            notifyCallbacks(Callback::onStateChanged);
         }
     }
 
@@ -239,6 +257,24 @@
     }
 
     /**
+     * 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);
+    }
+
+    /**
+     * Sets whether dream content and dream overlay exit animations are running.
+     * @param running {@code true} if exit animations are running, {@code false} otherwise.
+     */
+    public void setExitAnimationsRunning(boolean running) {
+        modifyState(running ? OP_SET_STATE : OP_CLEAR_STATE,
+                STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
+    }
+
+    /**
      * 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..f1bb156 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;
@@ -41,6 +37,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -83,6 +80,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 +109,9 @@
             new DreamOverlayStateController.Callback() {
                 @Override
                 public void onStateChanged() {
-                    updateLowLightState();
+                    mEntryAnimationsFinished =
+                            mDreamOverlayStateController.areEntryAnimationsFinished();
+                    updateVisibility();
                 }
             };
 
@@ -195,7 +197,6 @@
         mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback);
 
         mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
-        updateLowLightState();
 
         mTouchInsetSession.addViewToTracking(mView);
     }
@@ -216,6 +217,37 @@
         mIsAttached = false;
     }
 
+    /**
+     * Sets fade of the dream overlay status bar.
+     *
+     * No-op if the dream overlay status bar should not be shown.
+     */
+    protected void setFadeAmount(float fadeAmount, boolean fadingOut) {
+        updateVisibility();
+
+        if (mView.getVisibility() != View.VISIBLE) {
+            return;
+        }
+
+        if (fadingOut) {
+            CrossFadeHelper.fadeOut(mView, 1 - fadeAmount, /* remap= */ false);
+        } else {
+            CrossFadeHelper.fadeIn(mView, fadeAmount, /* remap= */ false);
+        }
+    }
+
+    /**
+     * Sets the y translation of the dream overlay status bar.
+     */
+    public void setTranslationY(float translationY) {
+        mView.setTranslationY(translationY);
+    }
+
+    private boolean shouldShowStatusBar() {
+        return !mDreamOverlayStateController.isLowLightActive()
+                && !mStatusBarWindowStateController.windowIsShowing();
+    }
+
     private void updateWifiUnavailableStatusIcon() {
         final NetworkCapabilities capabilities =
                 mConnectivityManager.getNetworkCapabilities(
@@ -235,13 +267,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 +329,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/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 41f5578..b07efdf 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -197,11 +197,11 @@
      */
     interface VisibilityController {
         /**
-         * Called to set the visibility of all shown and future complications.
+         * Called to set the visibility of all shown and future complications. Changes in visibility
+         * will always be animated.
          * @param visibility The desired future visibility.
-         * @param animate whether the change should be animated.
          */
-        void setVisibility(@View.Visibility int visibility, boolean animate);
+        void setVisibility(@View.Visibility int visibility);
     }
 
     /**
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/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index 5694f6d..46ce7a9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -21,12 +21,9 @@
 import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_DEFAULT;
 import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
 
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.Constraints;
@@ -34,6 +31,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.touch.TouchInsetManager;
 
 import java.util.ArrayList;
@@ -213,6 +211,19 @@
                 }
             });
 
+            if (mLayoutParams.constraintSpecified()) {
+                switch (direction) {
+                    case ComplicationLayoutParams.DIRECTION_START:
+                    case ComplicationLayoutParams.DIRECTION_END:
+                        params.matchConstraintMaxWidth = mLayoutParams.getConstraint();
+                        break;
+                    case ComplicationLayoutParams.DIRECTION_UP:
+                    case ComplicationLayoutParams.DIRECTION_DOWN:
+                        params.matchConstraintMaxHeight = mLayoutParams.getConstraint();
+                        break;
+                }
+            }
+
             mView.setLayoutParams(params);
         }
 
@@ -479,7 +490,6 @@
     private final TouchInsetManager.TouchInsetSession mSession;
     private final int mFadeInDuration;
     private final int mFadeOutDuration;
-    private ViewPropertyAnimator mViewPropertyAnimator;
 
     /** */
     @Inject
@@ -496,26 +506,16 @@
     }
 
     @Override
-    public void setVisibility(int visibility, boolean animate) {
-        final boolean appearing = visibility == View.VISIBLE;
-
-        if (mViewPropertyAnimator != null) {
-            mViewPropertyAnimator.cancel();
+    public void setVisibility(int visibility) {
+        if (visibility == View.VISIBLE) {
+            CrossFadeHelper.fadeIn(mLayout, mFadeInDuration, /* delay= */ 0);
+        } else {
+            CrossFadeHelper.fadeOut(
+                    mLayout,
+                    mFadeOutDuration,
+                    /* delay= */ 0,
+                    /* endRunnable= */ null);
         }
-
-        if (appearing) {
-            mLayout.setVisibility(View.VISIBLE);
-        }
-
-        mViewPropertyAnimator = mLayout.animate()
-                .alpha(appearing ? 1f : 0f)
-                .setDuration(appearing ? mFadeInDuration : mFadeOutDuration)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mLayout.setVisibility(visibility);
-                    }
-                });
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index a21eb19..1755cb92 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -38,7 +38,7 @@
             POSITION_START,
     })
 
-    @interface Position {}
+    public @interface Position {}
     /** Align view with the top of parent or bottom of preceding {@link Complication}. */
     public static final int POSITION_TOP = 1 << 0;
     /** Align view with the bottom of parent or top of preceding {@link Complication}. */
@@ -52,6 +52,7 @@
     private static final int LAST_POSITION = POSITION_END;
 
     private static final int MARGIN_UNSPECIFIED = 0xFFFFFFFF;
+    private static final int CONSTRAINT_UNSPECIFIED = 0xFFFFFFFF;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = { "DIRECTION_" }, value = {
@@ -81,6 +82,8 @@
 
     private final int mMargin;
 
+    private final int mConstraint;
+
     private final boolean mSnapToGuide;
 
     // Do not allow specifying opposite positions
@@ -110,7 +113,8 @@
      */
     public ComplicationLayoutParams(int width, int height, @Position int position,
             @Direction int direction, int weight) {
-        this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, false);
+        this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, CONSTRAINT_UNSPECIFIED,
+                false);
     }
 
     /**
@@ -127,7 +131,27 @@
      */
     public ComplicationLayoutParams(int width, int height, @Position int position,
             @Direction int direction, int weight, int margin) {
-        this(width, height, position, direction, weight, margin, false);
+        this(width, height, position, direction, weight, margin, CONSTRAINT_UNSPECIFIED, false);
+    }
+
+    /**
+     * Constructs a {@link ComplicationLayoutParams}.
+     * @param width The width {@link android.view.View.MeasureSpec} for the view.
+     * @param height The height {@link android.view.View.MeasureSpec} for the view.
+     * @param position The place within the parent container where the view should be positioned.
+     * @param direction The direction the view should be laid out from either the parent container
+     *                  or preceding view.
+     * @param weight The weight that should be considered for this view when compared to other
+     *               views. This has an impact on the placement of the view but not the rendering of
+     *               the view.
+     * @param margin The margin to apply between complications.
+     * @param constraint The max width or height the complication is allowed to spread, depending on
+     *                   its direction. For horizontal directions, this would be applied on width,
+     *                   and for vertical directions, height.
+     */
+    public ComplicationLayoutParams(int width, int height, @Position int position,
+            @Direction int direction, int weight, int margin, int constraint) {
+        this(width, height, position, direction, weight, margin, constraint, false);
     }
 
     /**
@@ -148,7 +172,8 @@
      */
     public ComplicationLayoutParams(int width, int height, @Position int position,
             @Direction int direction, int weight, boolean snapToGuide) {
-        this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, snapToGuide);
+        this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, CONSTRAINT_UNSPECIFIED,
+                snapToGuide);
     }
 
     /**
@@ -162,6 +187,9 @@
      *               views. This has an impact on the placement of the view but not the rendering of
      *               the view.
      * @param margin The margin to apply between complications.
+     * @param constraint The max width or height the complication is allowed to spread, depending on
+     *                   its direction. For horizontal directions, this would be applied on width,
+     *                   and for vertical directions, height.
      * @param snapToGuide When set to {@code true}, the dimension perpendicular to the direction
      *                    will be automatically set to align with a predetermined guide for that
      *                    side. For example, if the complication is aligned to the top end and
@@ -169,7 +197,7 @@
      *                    from the end of the parent to the guide.
      */
     public ComplicationLayoutParams(int width, int height, @Position int position,
-            @Direction int direction, int weight, int margin, boolean snapToGuide) {
+            @Direction int direction, int weight, int margin, int constraint, boolean snapToGuide) {
         super(width, height);
 
         if (!validatePosition(position)) {
@@ -187,6 +215,8 @@
 
         mMargin = margin;
 
+        mConstraint = constraint;
+
         mSnapToGuide = snapToGuide;
     }
 
@@ -199,6 +229,7 @@
         mDirection = source.mDirection;
         mWeight = source.mWeight;
         mMargin = source.mMargin;
+        mConstraint = source.mConstraint;
         mSnapToGuide = source.mSnapToGuide;
     }
 
@@ -269,6 +300,21 @@
     }
 
     /**
+     * Returns whether the horizontal or vertical constraint has been specified.
+     */
+    public boolean constraintSpecified() {
+        return mConstraint != CONSTRAINT_UNSPECIFIED;
+    }
+
+    /**
+     * Returns the horizontal or vertical constraint of the complication, depending its direction.
+     * For horizontal directions, this is the max width, and for vertical directions, max height.
+     */
+    public int getConstraint() {
+        return mConstraint;
+    }
+
+    /**
      * Returns whether the complication's dimension perpendicular to direction should be
      * automatically set.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index cedd850a..ee00512 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -32,7 +32,9 @@
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.CoreStartable;
+import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.controls.ControlsServiceInfo;
 import com.android.systemui.controls.dagger.ControlsComponent;
 import com.android.systemui.controls.management.ControlsListingController;
 import com.android.systemui.controls.ui.ControlsActivity;
@@ -42,6 +44,8 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.util.ViewController;
 
+import java.util.List;
+
 import javax.inject.Inject;
 import javax.inject.Named;
 
@@ -76,16 +80,25 @@
         private final DreamOverlayStateController mDreamOverlayStateController;
         private final ControlsComponent mControlsComponent;
 
-        private boolean mControlServicesAvailable = false;
+        private boolean mOverlayActive = false;
 
         // Callback for when the home controls service availability changes.
         private final ControlsListingController.ControlsListingCallback mControlsCallback =
-                serviceInfos -> {
-                    boolean available = !serviceInfos.isEmpty();
+                services -> updateHomeControlsComplication();
 
-                    if (available != mControlServicesAvailable) {
-                        mControlServicesAvailable = available;
-                        updateComplicationAvailability();
+        private final DreamOverlayStateController.Callback mOverlayStateCallback =
+                new DreamOverlayStateController.Callback() {
+                    @Override
+                    public void onStateChanged() {
+                        if (mOverlayActive == mDreamOverlayStateController.isOverlayActive()) {
+                            return;
+                        }
+
+                        mOverlayActive = !mOverlayActive;
+
+                        if (mOverlayActive) {
+                            updateHomeControlsComplication();
+                        }
                     }
                 };
 
@@ -102,18 +115,29 @@
         public void start() {
             mControlsComponent.getControlsListingController().ifPresent(
                     c -> c.addCallback(mControlsCallback));
+            mDreamOverlayStateController.addCallback(mOverlayStateCallback);
         }
 
-        private void updateComplicationAvailability() {
+        private void updateHomeControlsComplication() {
+            mControlsComponent.getControlsListingController().ifPresent(c -> {
+                if (isHomeControlsAvailable(c.getCurrentServices())) {
+                    mDreamOverlayStateController.addComplication(mComplication);
+                } else {
+                    mDreamOverlayStateController.removeComplication(mComplication);
+                }
+            });
+        }
+
+        private boolean isHomeControlsAvailable(List<ControlsServiceInfo> controlsServices) {
+            if (controlsServices.isEmpty()) {
+                return false;
+            }
+
             final boolean hasFavorites = mControlsComponent.getControlsController()
                     .map(c -> !c.getFavorites().isEmpty())
                     .orElse(false);
-            if (!hasFavorites || !mControlServicesAvailable
-                    || mControlsComponent.getVisibility() == UNAVAILABLE) {
-                mDreamOverlayStateController.removeComplication(mComplication);
-            } else {
-                mDreamOverlayStateController.addComplication(mComplication);
-            }
+            final ControlsComponent.Visibility visibility = mControlsComponent.getVisibility();
+            return hasFavorites && visibility != UNAVAILABLE;
         }
     }
 
@@ -128,7 +152,7 @@
         @Inject
         DreamHomeControlsChipViewHolder(
                 DreamHomeControlsChipViewController dreamHomeControlsChipViewController,
-                @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
+                @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) View view,
                 @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams
         ) {
             mView = view;
@@ -151,7 +175,7 @@
     /**
      * Controls behavior of the dream complication.
      */
-    static class DreamHomeControlsChipViewController extends ViewController<ImageView> {
+    static class DreamHomeControlsChipViewController extends ViewController<View> {
         private static final String TAG = "DreamHomeControlsCtrl";
         private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -180,7 +204,7 @@
 
         @Inject
         DreamHomeControlsChipViewController(
-                @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
+                @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) View view,
                 ActivityStarter activityStarter,
                 Context context,
                 ControlsComponent controlsComponent,
@@ -195,9 +219,10 @@
 
         @Override
         protected void onViewAttached() {
-            mView.setImageResource(mControlsComponent.getTileImageId());
-            mView.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId()));
-            mView.setOnClickListener(this::onClickHomeControls);
+            final ImageView chip = mView.findViewById(R.id.home_controls_chip);
+            chip.setImageResource(mControlsComponent.getTileImageId());
+            chip.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId()));
+            chip.setOnClickListener(this::onClickHomeControls);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
index c9fecc9..09cc7c5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
@@ -41,6 +41,7 @@
     public static final String COMPLICATIONS_FADE_OUT_DURATION = "complications_fade_out_duration";
     public static final String COMPLICATIONS_FADE_IN_DURATION = "complications_fade_in_duration";
     public static final String COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout";
+    public static final String COMPLICATIONS_FADE_OUT_DELAY = "complication_fade_out_delay";
 
     /**
      * Generates a {@link ConstraintLayout}, which can host
@@ -75,6 +76,16 @@
     }
 
     /**
+     * Provides the delay to wait for before fading out complications.
+     */
+    @Provides
+    @Named(COMPLICATIONS_FADE_OUT_DELAY)
+    @DreamOverlayComponent.DreamOverlayScope
+    static int providesComplicationsFadeOutDelay(@Main Resources resources) {
+        return resources.getInteger(R.integer.complicationFadeOutDelayMs);
+    }
+
+    /**
      * Provides the fade in duration for complications.
      */
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
index 7d9f105..5290e44 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
@@ -45,11 +45,12 @@
     @Provides
     @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW)
     static View provideComplicationView(LayoutInflater layoutInflater) {
-        final TextClock view = Preconditions.checkNotNull((TextClock)
+        final View view = Preconditions.checkNotNull(
                         layoutInflater.inflate(R.layout.dream_overlay_complication_clock_time,
                                 null, false),
                 "R.layout.dream_overlay_complication_clock_time did not properly inflated");
-        view.setFontVariationSettings(TAG_WEIGHT + WEIGHT);
+        ((TextClock) view.findViewById(R.id.time_view)).setFontVariationSettings(
+                TAG_WEIGHT + WEIGHT);
         return view;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
index cf05d2d..a7aa97f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
@@ -19,7 +19,7 @@
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import android.view.LayoutInflater;
-import android.widget.ImageView;
+import android.view.View;
 
 import com.android.systemui.R;
 import com.android.systemui.dreams.complication.DreamHomeControlsComplication;
@@ -74,8 +74,8 @@
         @Provides
         @DreamHomeControlsComplicationScope
         @Named(DREAM_HOME_CONTROLS_CHIP_VIEW)
-        static ImageView provideHomeControlsChipView(LayoutInflater layoutInflater) {
-            return (ImageView) layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
+        static View provideHomeControlsChipView(LayoutInflater layoutInflater) {
+            return layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
                     null, false);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 7d2ce51..9b954f5f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -47,10 +47,10 @@
     String DREAM_MEDIA_ENTRY_LAYOUT_PARAMS = "media_entry_layout_params";
 
     int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1;
-    int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 0;
-    int DREAM_MEDIA_COMPLICATION_WEIGHT = -1;
-    int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 1;
-    int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 0;
+    int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 2;
+    int DREAM_MEDIA_COMPLICATION_WEIGHT = 0;
+    int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 4;
+    int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 3;
 
     /**
      * Provides layout parameters for the clock time complication.
@@ -60,10 +60,11 @@
     static ComplicationLayoutParams provideClockTimeLayoutParams() {
         return new ComplicationLayoutParams(0,
                 ViewGroup.LayoutParams.WRAP_CONTENT,
-                ComplicationLayoutParams.POSITION_TOP
+                ComplicationLayoutParams.POSITION_BOTTOM
                         | ComplicationLayoutParams.POSITION_START,
-                ComplicationLayoutParams.DIRECTION_DOWN,
-                DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
+                ComplicationLayoutParams.DIRECTION_UP,
+                DREAM_CLOCK_TIME_COMPLICATION_WEIGHT,
+                0 /*margin*/);
     }
 
     /**
@@ -71,10 +72,10 @@
      */
     @Provides
     @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS)
-    static ComplicationLayoutParams provideHomeControlsChipLayoutParams(@Main Resources res) {
+    static ComplicationLayoutParams provideHomeControlsChipLayoutParams() {
         return new ComplicationLayoutParams(
-                res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
-                res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
                 ComplicationLayoutParams.POSITION_BOTTOM
                         | ComplicationLayoutParams.POSITION_START,
                 ComplicationLayoutParams.DIRECTION_END,
@@ -101,14 +102,15 @@
      */
     @Provides
     @Named(DREAM_SMARTSPACE_LAYOUT_PARAMS)
-    static ComplicationLayoutParams provideSmartspaceLayoutParams() {
-        return new ComplicationLayoutParams(0,
+    static ComplicationLayoutParams provideSmartspaceLayoutParams(@Main Resources res) {
+        return new ComplicationLayoutParams(
                 ViewGroup.LayoutParams.WRAP_CONTENT,
-                ComplicationLayoutParams.POSITION_TOP
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ComplicationLayoutParams.POSITION_BOTTOM
                         | ComplicationLayoutParams.POSITION_START,
-                ComplicationLayoutParams.DIRECTION_DOWN,
+                ComplicationLayoutParams.DIRECTION_END,
                 DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
-                0,
-                true /*snapToGuide*/);
+                res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_padding),
+                res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_max_width));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index f9dca08..101f4a4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -44,7 +44,7 @@
             DreamOverlayComponent.class,
         })
 public interface DreamModule {
-    String DREAM_ONLY_ENABLED_FOR_SYSTEM_USER = "dream_only_enabled_for_system_user";
+    String DREAM_ONLY_ENABLED_FOR_DOCK_USER = "dream_only_enabled_for_dock_user";
 
     String DREAM_SUPPORTED = "dream_supported";
 
@@ -70,10 +70,10 @@
 
     /** */
     @Provides
-    @Named(DREAM_ONLY_ENABLED_FOR_SYSTEM_USER)
-    static boolean providesDreamOnlyEnabledForSystemUser(@Main Resources resources) {
+    @Named(DREAM_ONLY_ENABLED_FOR_DOCK_USER)
+    static boolean providesDreamOnlyEnabledForDockUser(@Main Resources resources) {
         return resources.getBoolean(
-                com.android.internal.R.bool.config_dreamsOnlyEnabledForSystemUser);
+                com.android.internal.R.bool.config_dreamsOnlyEnabledForDockUser);
     }
 
     /** */
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..ed0e1d9 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,30 @@
     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";
+    public static final String DREAM_OUT_TRANSLATION_Y_DISTANCE =
+            "dream_out_complications_translation_y";
+    public static final String DREAM_OUT_TRANSLATION_Y_DURATION =
+            "dream_out_complications_translation_y_duration";
+    public static final String DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM =
+            "dream_out_complications_translation_y_delay_bottom";
+    public static final String DREAM_OUT_TRANSLATION_Y_DELAY_TOP =
+            "dream_out_complications_translation_y_delay_top";
+    public static final String DREAM_OUT_ALPHA_DURATION =
+            "dream_out_complications_alpha_duration";
+    public static final String DREAM_OUT_ALPHA_DELAY_BOTTOM =
+            "dream_out_complications_alpha_delay_bottom";
+    public static final String DREAM_OUT_ALPHA_DELAY_TOP =
+            "dream_out_complications_alpha_delay_top";
+    public static final String DREAM_OUT_BLUR_DURATION =
+            "dream_out_blur_duration";
 
     /** */
     @Provides
@@ -114,6 +138,112 @@
         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 long providesDreamInBlurAnimationDuration(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
+    }
+
+    /**
+     * Delay in milliseconds of the dream in un-blur animation.
+     */
+    @Provides
+    @Named(DREAM_IN_BLUR_ANIMATION_DELAY)
+    static long providesDreamInBlurAnimationDelay(@Main Resources resources) {
+        return (long) 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 long providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
+        return (long) 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 long providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
+        return (long) 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 long providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
+        return (long) resources.getInteger(
+                R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+    }
+
+    /**
+     * Provides the number of pixels to translate complications when waking up from dream.
+     */
+    @Provides
+    @Named(DREAM_OUT_TRANSLATION_Y_DISTANCE)
+    @DreamOverlayComponent.DreamOverlayScope
+    static int providesDreamOutComplicationsTranslationY(@Main Resources resources) {
+        return resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset);
+    }
+
+    @Provides
+    @Named(DREAM_OUT_TRANSLATION_Y_DURATION)
+    @DreamOverlayComponent.DreamOverlayScope
+    static long providesDreamOutComplicationsTranslationYDuration(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDurationMs);
+    }
+
+    @Provides
+    @Named(DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
+    @DreamOverlayComponent.DreamOverlayScope
+    static long providesDreamOutComplicationsTranslationYDelayBottom(@Main Resources resources) {
+        return (long) resources.getInteger(
+                R.integer.config_dreamOverlayOutTranslationYDelayBottomMs);
+    }
+
+    @Provides
+    @Named(DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
+    @DreamOverlayComponent.DreamOverlayScope
+    static long providesDreamOutComplicationsTranslationYDelayTop(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDelayTopMs);
+    }
+
+    @Provides
+    @Named(DREAM_OUT_ALPHA_DURATION)
+    @DreamOverlayComponent.DreamOverlayScope
+    static long providesDreamOutComplicationsAlphaDuration(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDurationMs);
+    }
+
+    @Provides
+    @Named(DREAM_OUT_ALPHA_DELAY_BOTTOM)
+    @DreamOverlayComponent.DreamOverlayScope
+    static long providesDreamOutComplicationsAlphaDelayBottom(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayBottomMs);
+    }
+
+    @Provides
+    @Named(DREAM_OUT_ALPHA_DELAY_TOP)
+    @DreamOverlayComponent.DreamOverlayScope
+    static long providesDreamOutComplicationsAlphaDelayTop(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayTopMs);
+    }
+
+    @Provides
+    @Named(DREAM_OUT_BLUR_DURATION)
+    @DreamOverlayComponent.DreamOverlayScope
+    static long providesDreamOutBlurDuration(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayOutBlurDurationMs);
+    }
+
     @Provides
     @DreamOverlayComponent.DreamOverlayScope
     static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 0dba4ff..92cdcf9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -116,7 +116,7 @@
 
                         if (mCapture) {
                             // Since the user is dragging the bouncer up, set scrimmed to false.
-                            mStatusBarKeyguardViewManager.showBouncer(false);
+                            mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
                         }
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
index 3087cdf..e276e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
@@ -16,22 +16,26 @@
 
 package com.android.systemui.dreams.touch;
 
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DELAY;
 import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_RESTORE_TIMEOUT;
 
-import android.os.Handler;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.Complication;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.touch.TouchInsetManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
+import java.util.ArrayDeque;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -49,33 +53,58 @@
     private static final String TAG = "HideComplicationHandler";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private final Complication.VisibilityController mVisibilityController;
     private final int mRestoreTimeout;
+    private final int mFadeOutDelay;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private final Handler mHandler;
-    private final Executor mExecutor;
+    private final DelayableExecutor mExecutor;
+    private final DreamOverlayStateController mOverlayStateController;
     private final TouchInsetManager mTouchInsetManager;
+    private final Complication.VisibilityController mVisibilityController;
+    private boolean mHidden = false;
+    @Nullable
+    private Runnable mHiddenCallback;
+    private final ArrayDeque<Runnable> mCancelCallbacks = new ArrayDeque<>();
+
 
     private final Runnable mRestoreComplications = new Runnable() {
         @Override
         public void run() {
-            mVisibilityController.setVisibility(View.VISIBLE, true);
+            mVisibilityController.setVisibility(View.VISIBLE);
+            mHidden = false;
+        }
+    };
+
+    private final Runnable mHideComplications = new Runnable() {
+        @Override
+        public void run() {
+            if (mOverlayStateController.areExitAnimationsRunning()) {
+                // Avoid interfering with the exit animations.
+                return;
+            }
+            mVisibilityController.setVisibility(View.INVISIBLE);
+            mHidden = true;
+            if (mHiddenCallback != null) {
+                mHiddenCallback.run();
+                mHiddenCallback = null;
+            }
         }
     };
 
     @Inject
     HideComplicationTouchHandler(Complication.VisibilityController visibilityController,
             @Named(COMPLICATIONS_RESTORE_TIMEOUT) int restoreTimeout,
+            @Named(COMPLICATIONS_FADE_OUT_DELAY) int fadeOutDelay,
             TouchInsetManager touchInsetManager,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            @Main Executor executor,
-            @Main Handler handler) {
+            @Main DelayableExecutor executor,
+            DreamOverlayStateController overlayStateController) {
         mVisibilityController = visibilityController;
         mRestoreTimeout = restoreTimeout;
+        mFadeOutDelay = fadeOutDelay;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
-        mHandler = handler;
         mTouchInsetManager = touchInsetManager;
         mExecutor = executor;
+        mOverlayStateController = overlayStateController;
     }
 
     @Override
@@ -87,7 +116,8 @@
         final boolean bouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing();
 
         // If other sessions are interested in this touch, do not fade out elements.
-        if (session.getActiveSessionCount() > 1 || bouncerShowing) {
+        if (session.getActiveSessionCount() > 1 || bouncerShowing
+                || mOverlayStateController.areExitAnimationsRunning()) {
             if (DEBUG) {
                 Log.d(TAG, "not fading. Active session count: " + session.getActiveSessionCount()
                         + ". Bouncer showing: " + bouncerShowing);
@@ -115,8 +145,11 @@
                 touchCheck.addListener(() -> {
                     try {
                         if (!touchCheck.get()) {
-                            mHandler.removeCallbacks(mRestoreComplications);
-                            mVisibilityController.setVisibility(View.INVISIBLE, true);
+                            // Cancel all pending callbacks.
+                            while (!mCancelCallbacks.isEmpty()) mCancelCallbacks.pop().run();
+                            mCancelCallbacks.add(
+                                    mExecutor.executeDelayed(
+                                            mHideComplications, mFadeOutDelay));
                         } else {
                             // If a touch occurred inside the dream overlay touch insets, do not
                             // handle the touch.
@@ -130,7 +163,23 @@
                     || motionEvent.getAction() == MotionEvent.ACTION_UP) {
                 // End session and initiate delayed reappearance of the complications.
                 session.pop();
-                mHandler.postDelayed(mRestoreComplications, mRestoreTimeout);
+                runAfterHidden(() -> mCancelCallbacks.add(
+                        mExecutor.executeDelayed(mRestoreComplications,
+                                mRestoreTimeout)));
+            }
+        });
+    }
+
+    /**
+     * Triggers a runnable after complications have been hidden. Will override any previously set
+     * runnable currently waiting for hide to happen.
+     */
+    private void runAfterHidden(Runnable runnable) {
+        mExecutor.execute(() -> {
+            if (mHidden) {
+                runnable.run();
+            } else {
+                mHiddenCallback = runnable;
             }
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index 609bd76..a2f65ba 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.R
 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
-import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH
 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL
 import com.android.systemui.dump.nano.SystemUIProtoDump
 import com.android.systemui.plugins.log.LogBuffer
@@ -148,12 +147,12 @@
     }
 
     private fun dumpCritical(pw: PrintWriter, args: ParsedArgs) {
-        dumpManager.dumpDumpables(pw, args.rawArgs)
+        dumpManager.dumpCritical(pw, args.rawArgs)
         dumpConfig(pw)
     }
 
     private fun dumpNormal(pw: PrintWriter, args: ParsedArgs) {
-        dumpManager.dumpBuffers(pw, args.tailLength)
+        dumpManager.dumpNormal(pw, args.rawArgs, args.tailLength)
         logBufferEulogizer.readEulogyIfPresent(pw)
     }
 
@@ -349,14 +348,12 @@
     companion object {
         const val PRIORITY_ARG = "--dump-priority"
         const val PRIORITY_ARG_CRITICAL = "CRITICAL"
-        const val PRIORITY_ARG_HIGH = "HIGH"
         const val PRIORITY_ARG_NORMAL = "NORMAL"
         const val PROTO = "--proto"
     }
 }
 
-private val PRIORITY_OPTIONS =
-        arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_HIGH, PRIORITY_ARG_NORMAL)
+private val PRIORITY_OPTIONS = arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_NORMAL)
 
 private val COMMANDS = arrayOf(
         "bugreport-critical",
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index ae78089..c982131 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -40,20 +40,54 @@
     private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
 
     /**
-     * Register a dumpable to be called during a bug report. The dumpable will be called during the
-     * CRITICAL section of the bug report, so don't dump an excessive amount of stuff here.
+     * Registers a dumpable to be called during the CRITICAL section of the bug report.
+     *
+     * The CRITICAL section gets very high priority during a dump, but also a very limited amount of
+     * time to do the dumping. So, please don't dump an excessive amount of stuff using CRITICAL.
+     *
+     * See [registerDumpable].
+     */
+    fun registerCriticalDumpable(name: String, module: Dumpable) {
+        registerDumpable(name, module, DumpPriority.CRITICAL)
+    }
+
+    /**
+     * Registers a dumpable to be called during the NORMAL section of the bug report.
+     *
+     * The NORMAL section gets a lower priority during a dump, but also more time. This should be
+     * used by [LogBuffer] instances, [ProtoDumpable] instances, and any [Dumpable] instances that
+     * dump a lot of information.
+     */
+    fun registerNormalDumpable(name: String, module: Dumpable) {
+        registerDumpable(name, module, DumpPriority.NORMAL)
+    }
+
+    /**
+     * Register a dumpable to be called during a bug report.
      *
      * @param name The name to register the dumpable under. This is typically the qualified class
      * name of the thing being dumped (getClass().getName()), but can be anything as long as it
      * doesn't clash with an existing registration.
+     * @param priority the priority level of this dumpable, which affects at what point in the bug
+     * report this gets dump. By default, the dumpable will be called during the CRITICAL section of
+     * the bug report, so don't dump an excessive amount of stuff here.
+     *
+     * TODO(b/259973758): Replace all calls to this method with calls to [registerCriticalDumpable]
+     * or [registerNormalDumpable] instead.
      */
     @Synchronized
-    fun registerDumpable(name: String, module: Dumpable) {
+    @JvmOverloads
+    @Deprecated("Use registerCriticalDumpable or registerNormalDumpable instead")
+    fun registerDumpable(
+        name: String,
+        module: Dumpable,
+        priority: DumpPriority = DumpPriority.CRITICAL,
+    ) {
         if (!canAssignToNameLocked(name, module)) {
             throw IllegalArgumentException("'$name' is already registered")
         }
 
-        dumpables[name] = RegisteredDumpable(name, module)
+        dumpables[name] = RegisteredDumpable(name, module, priority)
     }
 
     /**
@@ -81,7 +115,10 @@
         if (!canAssignToNameLocked(name, buffer)) {
             throw IllegalArgumentException("'$name' is already registered")
         }
-        buffers[name] = RegisteredDumpable(name, buffer)
+
+        // All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
+        // data.
+        buffers[name] = RegisteredDumpable(name, buffer, DumpPriority.NORMAL)
     }
 
     /**
@@ -140,7 +177,35 @@
     }
 
     /**
-     * Dumps all registered dumpables to [pw]
+     * Dumps all registered dumpables with critical priority to [pw]
+     */
+    @Synchronized
+    fun dumpCritical(pw: PrintWriter, args: Array<String>) {
+        for (dumpable in dumpables.values) {
+            if (dumpable.priority == DumpPriority.CRITICAL) {
+                dumpDumpable(dumpable, pw, args)
+            }
+        }
+    }
+
+    /**
+     * To [pw], dumps (1) all registered dumpables with normal priority; and (2) all [LogBuffer]s.
+     */
+    @Synchronized
+    fun dumpNormal(pw: PrintWriter, args: Array<String>, tailLength: Int = 0) {
+        for (dumpable in dumpables.values) {
+            if (dumpable.priority == DumpPriority.NORMAL) {
+                dumpDumpable(dumpable, pw, args)
+            }
+        }
+
+        for (buffer in buffers.values) {
+            dumpBuffer(buffer, pw, tailLength)
+        }
+    }
+
+    /**
+     * Dump all the instances of [Dumpable].
      */
     @Synchronized
     fun dumpDumpables(pw: PrintWriter, args: Array<String>) {
@@ -232,7 +297,15 @@
 
 private data class RegisteredDumpable<T>(
     val name: String,
-    val dumpable: T
+    val dumpable: T,
+    val priority: DumpPriority,
 )
 
-private const val TAG = "DumpManager"
+/**
+ * The priority level for a given dumpable, which affects at what point in the bug report this gets
+ * dumped.
+ */
+enum class DumpPriority {
+    CRITICAL,
+    NORMAL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
index fb4fc92..95e7ad96 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
@@ -34,9 +34,6 @@
     fun isEnabled(flag: ResourceBooleanFlag): Boolean
 
     /** Returns a boolean value for the given flag.  */
-    fun isEnabled(flag: DeviceConfigBooleanFlag): Boolean
-
-    /** Returns a boolean value for the given flag.  */
     fun isEnabled(flag: SysPropBooleanFlag): Boolean
 
     /** Returns a string value for the given flag.  */
@@ -44,4 +41,10 @@
 
     /** Returns a string value for the given flag.  */
     fun getString(flag: ResourceStringFlag): String
+
+    /** Returns an int value for a given flag/ */
+    fun getInt(flag: IntFlag): Int
+
+    /** Returns an int value for a given flag/ */
+    fun getInt(flag: ResourceIntFlag): Int
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 1f061e9..267e036 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -40,7 +40,6 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.jetbrains.annotations.NotNull;
@@ -77,18 +76,18 @@
     private final SecureSettings mSecureSettings;
     private final Resources mResources;
     private final SystemPropertiesHelper mSystemProperties;
-    private final DeviceConfigProxy mDeviceConfigProxy;
     private final ServerFlagReader mServerFlagReader;
     private final Map<Integer, Flag<?>> mAllFlags;
     private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
     private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
+    private final Map<Integer, Integer> mIntFlagCache = new TreeMap<>();
     private final Restarter mRestarter;
 
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
             new ServerFlagReader.ChangeListener() {
                 @Override
                 public void onChange() {
-                    mRestarter.restart();
+                    mRestarter.restartSystemUI();
                 }
             };
 
@@ -99,7 +98,6 @@
             SecureSettings secureSettings,
             SystemPropertiesHelper systemProperties,
             @Main Resources resources,
-            DeviceConfigProxy deviceConfigProxy,
             ServerFlagReader serverFlagReader,
             @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
             Restarter restarter) {
@@ -108,7 +106,6 @@
         mSecureSettings = secureSettings;
         mResources = resources;
         mSystemProperties = systemProperties;
-        mDeviceConfigProxy = deviceConfigProxy;
         mServerFlagReader = serverFlagReader;
         mAllFlags = allFlags;
         mRestarter = restarter;
@@ -140,7 +137,7 @@
         int id = flag.getId();
         if (!mBooleanFlagCache.containsKey(id)) {
             mBooleanFlagCache.put(id,
-                    readFlagValue(id, flag.getDefault()));
+                    readBooleanFlagInternal(flag, flag.getDefault()));
         }
 
         return mBooleanFlagCache.get(id);
@@ -151,19 +148,7 @@
         int id = flag.getId();
         if (!mBooleanFlagCache.containsKey(id)) {
             mBooleanFlagCache.put(id,
-                    readFlagValue(id, mResources.getBoolean(flag.getResourceId())));
-        }
-
-        return mBooleanFlagCache.get(id);
-    }
-
-    @Override
-    public boolean isEnabled(@NonNull DeviceConfigBooleanFlag flag) {
-        int id = flag.getId();
-        if (!mBooleanFlagCache.containsKey(id)) {
-            boolean deviceConfigValue = mDeviceConfigProxy.getBoolean(flag.getNamespace(),
-                    flag.getName(), flag.getDefault());
-            mBooleanFlagCache.put(id, readFlagValue(id, deviceConfigValue));
+                    readBooleanFlagInternal(flag, mResources.getBoolean(flag.getResourceId())));
         }
 
         return mBooleanFlagCache.get(id);
@@ -179,7 +164,7 @@
                     id,
                     mSystemProperties.getBoolean(
                             flag.getName(),
-                            readFlagValue(id, flag.getDefault())));
+                            readBooleanFlagInternal(flag, flag.getDefault())));
         }
 
         return mBooleanFlagCache.get(id);
@@ -191,7 +176,7 @@
         int id = flag.getId();
         if (!mStringFlagCache.containsKey(id)) {
             mStringFlagCache.put(id,
-                    readFlagValue(id, flag.getDefault(), StringFlagSerializer.INSTANCE));
+                    readFlagValueInternal(id, flag.getDefault(), StringFlagSerializer.INSTANCE));
         }
 
         return mStringFlagCache.get(id);
@@ -203,27 +188,57 @@
         int id = flag.getId();
         if (!mStringFlagCache.containsKey(id)) {
             mStringFlagCache.put(id,
-                    readFlagValue(id, mResources.getString(flag.getResourceId()),
+                    readFlagValueInternal(id, mResources.getString(flag.getResourceId()),
                             StringFlagSerializer.INSTANCE));
         }
 
         return mStringFlagCache.get(id);
     }
 
-    /** 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);
+
+    @NonNull
+    @Override
+    public int getInt(@NonNull IntFlag flag) {
+        int id = flag.getId();
+        if (!mIntFlagCache.containsKey(id)) {
+            mIntFlagCache.put(id,
+                    readFlagValueInternal(id, flag.getDefault(), IntFlagSerializer.INSTANCE));
+        }
+
+        return mIntFlagCache.get(id);
+    }
+
+    @NonNull
+    @Override
+    public int getInt(@NonNull ResourceIntFlag flag) {
+        int id = flag.getId();
+        if (!mIntFlagCache.containsKey(id)) {
+            mIntFlagCache.put(id,
+                    readFlagValueInternal(id, mResources.getInteger(flag.getResourceId()),
+                            IntFlagSerializer.INSTANCE));
+        }
+
+        return mIntFlagCache.get(id);
+    }
+
+    /** Specific override for Boolean flags that checks against the teamfood list.*/
+    private boolean readBooleanFlagInternal(Flag<Boolean> flag, boolean defaultValue) {
+        Boolean result = readBooleanFlagOverride(flag.getId());
+        boolean hasServerOverride = mServerFlagReader.hasOverride(
+                flag.getNamespace(), flag.getName());
 
         // Only check for teamfood if the default is false
         // and there is no server override.
-        if (!hasServerOverride && !defaultValue && result == null && id != Flags.TEAMFOOD.getId()) {
-            if (mAllFlags.containsKey(id) && mAllFlags.get(id).getTeamfood()) {
-                return isEnabled(Flags.TEAMFOOD);
-            }
+        if (!hasServerOverride
+                && !defaultValue
+                && result == null
+                && flag.getId() != Flags.TEAMFOOD.getId()
+                && flag.getTeamfood()) {
+            return isEnabled(Flags.TEAMFOOD);
         }
 
-        return result == null ? mServerFlagReader.readServerOverride(id, defaultValue) : result;
+        return result == null ? mServerFlagReader.readServerOverride(
+                flag.getNamespace(), flag.getName(), defaultValue) : result;
     }
 
     private Boolean readBooleanFlagOverride(int id) {
@@ -231,7 +246,8 @@
     }
 
     @NonNull
-    private <T> T readFlagValue(int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
+    private <T> T readFlagValueInternal(
+            int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
         requireNonNull(defaultValue, "defaultValue");
         T result = readFlagValueInternal(id, serializer);
         return result == null ? defaultValue : result;
@@ -311,9 +327,7 @@
             Log.i(TAG, "SystemUI Restart Suppressed");
             return;
         }
-        Log.i(TAG, "Restarting SystemUI");
-        // SysUI starts back when up exited. Is there a better way to do this?
-        System.exit(0);
+        mRestarter.restartSystemUI();
     }
 
     private void restartAndroid(boolean requestSuppress) {
@@ -321,8 +335,7 @@
             Log.i(TAG, "Android Restart Suppressed");
             return;
         }
-        Log.i(TAG, "Restarting Android");
-        mRestarter.restart();
+        mRestarter.restartAndroid();
     }
 
     void setBooleanFlagInternal(Flag<?> flag, boolean value) {
@@ -330,8 +343,6 @@
             setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
         } else if (flag instanceof ResourceBooleanFlag) {
             setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
-        } else if (flag instanceof DeviceConfigBooleanFlag) {
-            setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
         } else if (flag instanceof SysPropBooleanFlag) {
             // Store SysProp flags in SystemProperties where they can read by outside parties.
             mSystemProperties.setBoolean(((SysPropBooleanFlag) flag).getName(), value);
@@ -352,6 +363,16 @@
         }
     }
 
+    void setIntFlagInternal(Flag<?> flag, int value) {
+        if (flag instanceof IntFlag) {
+            setFlagValue(flag.getId(), value, IntFlagSerializer.INSTANCE);
+        } else if (flag instanceof ResourceIntFlag) {
+            setFlagValue(flag.getId(), value, IntFlagSerializer.INSTANCE);
+        } else {
+            throw new IllegalArgumentException("Unknown flag type");
+        }
+    }
+
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -439,9 +460,6 @@
             } else if (f instanceof ResourceBooleanFlag) {
                 enabled = isEnabled((ResourceBooleanFlag) f);
                 overridden = readBooleanFlagOverride(f.getId()) != null;
-            } else if (f instanceof DeviceConfigBooleanFlag) {
-                enabled = isEnabled((DeviceConfigBooleanFlag) f);
-                overridden = false;
             } else if (f instanceof SysPropBooleanFlag) {
                 // TODO(b/223379190): Teamfood not supported for sysprop flags yet.
                 enabled = isEnabled((SysPropBooleanFlag) f);
@@ -454,9 +472,11 @@
             }
 
             if (enabled) {
-                return new ReleasedFlag(f.getId(), teamfood, overridden);
+                return new ReleasedFlag(
+                        f.getId(), f.getName(), f.getNamespace(), teamfood, overridden);
             } else {
-                return new UnreleasedFlag(f.getId(), teamfood, overridden);
+                return new UnreleasedFlag(
+                        f.getId(), f.getName(), f.getNamespace(), teamfood, overridden);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
new file mode 100644
index 0000000..069e612
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.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.flags
+
+import android.util.Log
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import javax.inject.Inject
+
+/** Restarts SystemUI when the screen is locked. */
+class FeatureFlagsDebugRestarter
+@Inject
+constructor(
+    private val wakefulnessLifecycle: WakefulnessLifecycle,
+    private val systemExitRestarter: SystemExitRestarter,
+) : Restarter {
+
+    private var androidRestartRequested = false
+
+    val observer =
+        object : WakefulnessLifecycle.Observer {
+            override fun onFinishedGoingToSleep() {
+                Log.d(FeatureFlagsDebug.TAG, "Restarting due to systemui flag change")
+                restartNow()
+            }
+        }
+
+    override fun restartSystemUI() {
+        Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting on next screen off.")
+        scheduleRestart()
+    }
+
+    override fun restartAndroid() {
+        Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting on next screen off.")
+        androidRestartRequested = true
+        scheduleRestart()
+    }
+
+    fun scheduleRestart() {
+        if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
+            restartNow()
+        } else {
+            wakefulnessLifecycle.addObserver(observer)
+        }
+    }
+
+    private fun restartNow() {
+        if (androidRestartRequested) {
+            systemExitRestarter.restartAndroid()
+        } else {
+            systemExitRestarter.restartSystemUI()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index 6271334..b94d781 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.flags
 
+import android.content.Intent
 import com.android.systemui.CoreStartable
+import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import dagger.Binds
@@ -31,11 +33,12 @@
     dumpManager: DumpManager,
     private val commandRegistry: CommandRegistry,
     private val flagCommand: FlagCommand,
-    private val featureFlags: FeatureFlagsDebug
+    private val featureFlags: FeatureFlagsDebug,
+    private val broadcastSender: BroadcastSender
 ) : CoreStartable {
 
     init {
-        dumpManager.registerDumpable(FeatureFlagsDebug.TAG) { pw, args ->
+        dumpManager.registerCriticalDumpable(FeatureFlagsDebug.TAG) { pw, args ->
             featureFlags.dump(pw, args)
         }
     }
@@ -43,6 +46,8 @@
     override fun start() {
         featureFlags.init()
         commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
+        val intent = Intent(FlagManager.ACTION_SYSUI_STARTED)
+        broadcastSender.sendBroadcast(intent)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 30cad5f..8bddacc 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -61,7 +61,7 @@
             new ServerFlagReader.ChangeListener() {
                 @Override
                 public void onChange() {
-                    mRestarter.restart();
+                    mRestarter.restartSystemUI();
                 }
             };
 
@@ -101,7 +101,7 @@
 
     @Override
     public boolean isEnabled(@NotNull ReleasedFlag flag) {
-        return mServerFlagReader.readServerOverride(flag.getId(), true);
+        return mServerFlagReader.readServerOverride(flag.getNamespace(), flag.getName(), true);
     }
 
     @Override
@@ -115,18 +115,6 @@
     }
 
     @Override
-    public boolean isEnabled(@NonNull DeviceConfigBooleanFlag flag) {
-        int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
-        if (cacheIndex < 0) {
-            boolean deviceConfigValue = mDeviceConfigProxy.getBoolean(flag.getNamespace(),
-                    flag.getName(), flag.getDefault());
-            return isEnabled(flag.getId(), deviceConfigValue);
-        }
-
-        return mBooleanCache.valueAt(cacheIndex);
-    }
-
-    @Override
     public boolean isEnabled(SysPropBooleanFlag flag) {
         int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
         if (cacheIndex < 0) {
@@ -165,13 +153,25 @@
         return defaultValue;
     }
 
+    @NonNull
+    @Override
+    public int getInt(@NonNull IntFlag flag) {
+        return flag.getDefault();
+    }
+
+    @NonNull
+    @Override
+    public int getInt(@NonNull ResourceIntFlag flag) {
+        return mResources.getInteger(flag.getResourceId());
+    }
+
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("can override: false");
-        Map<Integer, Flag<?>> knownFlags = Flags.collectFlags();
-        for (Map.Entry<Integer, Flag<?>> idToFlag : knownFlags.entrySet()) {
-            int id = idToFlag.getKey();
-            Flag<?> flag = idToFlag.getValue();
+        Map<String, Flag<?>> knownFlags = FlagsFactory.INSTANCE.getKnownFlags();
+        for (Map.Entry<String, Flag<?>> nameToFlag : knownFlags.entrySet()) {
+            Flag<?> flag = nameToFlag.getValue();
+            int id = flag.getId();
             boolean def = false;
             if (mBooleanCache.indexOfKey(flag.getId()) < 0) {
                 if (flag instanceof SysPropBooleanFlag) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
new file mode 100644
index 0000000..7ff3876
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
@@ -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.systemui.flags
+
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+/** Restarts SystemUI when the device appears idle. */
+class FeatureFlagsReleaseRestarter
+@Inject
+constructor(
+    private val wakefulnessLifecycle: WakefulnessLifecycle,
+    private val batteryController: BatteryController,
+    @Background private val bgExecutor: DelayableExecutor,
+    private val systemExitRestarter: SystemExitRestarter
+) : Restarter {
+    var listenersAdded = false
+    var pendingRestart: Runnable? = null
+    var androidRestartRequested = false
+
+    val observer =
+        object : WakefulnessLifecycle.Observer {
+            override fun onFinishedGoingToSleep() {
+                scheduleRestart()
+            }
+        }
+
+    val batteryCallback =
+        object : BatteryController.BatteryStateChangeCallback {
+            override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+                scheduleRestart()
+            }
+        }
+
+    override fun restartSystemUI() {
+        Log.d(
+            FeatureFlagsDebug.TAG,
+            "SystemUI Restart requested. Restarting when plugged in and idle."
+        )
+        scheduleRestart()
+    }
+
+    override fun restartAndroid() {
+        Log.d(
+            FeatureFlagsDebug.TAG,
+            "Android Restart requested. Restarting when plugged in and idle."
+        )
+        androidRestartRequested = true
+        scheduleRestart()
+    }
+
+    private fun scheduleRestart() {
+        // Don't bother adding listeners twice.
+        if (!listenersAdded) {
+            listenersAdded = true
+            wakefulnessLifecycle.addObserver(observer)
+            batteryController.addCallback(batteryCallback)
+        }
+        if (
+            wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
+        ) {
+            if (pendingRestart == null) {
+                pendingRestart = bgExecutor.executeDelayed(this::restartNow, 30L, TimeUnit.SECONDS)
+            }
+        } else if (pendingRestart != null) {
+            pendingRestart?.run()
+            pendingRestart = null
+        }
+    }
+
+    private fun restartNow() {
+        Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
+        if (androidRestartRequested) {
+            systemExitRestarter.restartAndroid()
+        } else {
+            systemExitRestarter.restartSystemUI()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
index e7d8cc3..d088d74 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
@@ -29,7 +29,7 @@
 constructor(dumpManager: DumpManager, featureFlags: FeatureFlags) : CoreStartable {
 
     init {
-        dumpManager.registerDumpable(FeatureFlagsRelease.TAG) { pw, args ->
+        dumpManager.registerCriticalDumpable(FeatureFlagsRelease.TAG) { pw, args ->
             featureFlags.dump(pw, args)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index 1e93c0b7..b7fc0e4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -23,7 +23,6 @@
 import com.android.systemui.statusbar.commandline.Command;
 
 import java.io.PrintWriter;
-import java.lang.reflect.Field;
 import java.util.List;
 import java.util.Map;
 
@@ -38,6 +37,7 @@
 
     private final List<String> mOnCommands = List.of("true", "on", "1", "enabled");
     private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
+    private final List<String> mSetCommands = List.of("set", "put");
     private final FeatureFlagsDebug mFeatureFlags;
     private final Map<Integer, Flag<?>> mAllFlags;
 
@@ -60,12 +60,6 @@
             return;
         }
 
-        if (args.size() > 2) {
-            pw.println("Invalid number of arguments.");
-            help(pw);
-            return;
-        }
-
         int id = 0;
         try {
             id = Integer.parseInt(args.get(0));
@@ -85,48 +79,113 @@
         Flag<?> flag = mAllFlags.get(id);
 
         String cmd = "";
-        if (args.size() == 2) {
+        if (args.size() > 1) {
             cmd = args.get(1).toLowerCase();
         }
 
         if ("erase".equals(cmd) || "reset".equals(cmd)) {
+            if (args.size() > 2) {
+                pw.println("Invalid number of arguments to reset a flag.");
+                help(pw);
+                return;
+            }
+
             mFeatureFlags.eraseFlag(flag);
             return;
         }
 
-        boolean newValue = true;
-        if (args.size() == 1 || "toggle".equals(cmd)) {
-            boolean enabled = isBooleanFlagEnabled(flag);
-
-            if (args.size() == 1) {
-                pw.println("Flag " + id + " is " + enabled);
+        boolean shouldSet = true;
+        if (args.size() == 1) {
+            shouldSet = false;
+        }
+        if (isBooleanFlag(flag)) {
+            if (args.size() > 2) {
+                pw.println("Invalid number of arguments for a boolean flag.");
+                help(pw);
                 return;
             }
-
-            newValue = !enabled;
-        } else {
-            newValue = mOnCommands.contains(cmd);
-            if (!newValue && !mOffCommands.contains(cmd)) {
+            boolean newValue = isBooleanFlagEnabled(flag);
+            if ("toggle".equals(cmd)) {
+                newValue = !newValue;
+            } else if (mOnCommands.contains(cmd)) {
+                newValue = true;
+            } else if (mOffCommands.contains(cmd)) {
+                newValue = false;
+            } else if (shouldSet) {
                 pw.println("Invalid on/off argument supplied");
                 help(pw);
                 return;
             }
-        }
 
-        pw.flush();  // Next command will restart sysui, so flush before we do so.
-        mFeatureFlags.setBooleanFlagInternal(flag, newValue);
+            pw.println("Flag " + id + " is " + newValue);
+            pw.flush();  // Next command will restart sysui, so flush before we do so.
+            if (shouldSet) {
+                mFeatureFlags.setBooleanFlagInternal(flag, newValue);
+            }
+            return;
+
+        } else if (isStringFlag(flag)) {
+            if (shouldSet) {
+                if (args.size() != 3) {
+                    pw.println("Invalid number of arguments a StringFlag.");
+                    help(pw);
+                    return;
+                } else if (!mSetCommands.contains(cmd)) {
+                    pw.println("Unknown command: " + cmd);
+                    help(pw);
+                    return;
+                }
+                String value = args.get(2);
+                pw.println("Setting Flag " + id + " to " + value);
+                pw.flush();  // Next command will restart sysui, so flush before we do so.
+                mFeatureFlags.setStringFlagInternal(flag, args.get(2));
+            } else {
+                pw.println("Flag " + id + " is " + getStringFlag(flag));
+            }
+            return;
+        } else if (isIntFlag(flag)) {
+            if (shouldSet) {
+                if (args.size() != 3) {
+                    pw.println("Invalid number of arguments for an IntFlag.");
+                    help(pw);
+                    return;
+                } else if (!mSetCommands.contains(cmd)) {
+                    pw.println("Unknown command: " + cmd);
+                    help(pw);
+                    return;
+                }
+                int value = Integer.parseInt(args.get(2));
+                pw.println("Setting Flag " + id + " to " + value);
+                pw.flush();  // Next command will restart sysui, so flush before we do so.
+                mFeatureFlags.setIntFlagInternal(flag, value);
+            } else {
+                pw.println("Flag " + id + " is " + getIntFlag(flag));
+            }
+            return;
+        }
     }
 
     @Override
     public void help(PrintWriter pw) {
-        pw.println(
-                "Usage: adb shell cmd statusbar flag <id> "
+        pw.println("Usage: adb shell cmd statusbar flag <id> [options]");
+        pw.println();
+        pw.println("  Boolean Flag Options: "
                         + "[true|false|1|0|on|off|enable|disable|toggle|erase|reset]");
+        pw.println("  String Flag Options: [set|put \"<value>\"]");
+        pw.println("  Int Flag Options: [set|put <value>]");
+        pw.println();
         pw.println("The id can either be a numeric integer or the corresponding field name");
         pw.println(
                 "If no argument is supplied after the id, the flags runtime value is output");
     }
 
+    private boolean isBooleanFlag(Flag<?> flag) {
+        return (flag instanceof BooleanFlag)
+                || (flag instanceof ResourceBooleanFlag)
+                || (flag instanceof SysPropFlag)
+                || (flag instanceof DeviceConfigBooleanFlag);
+    }
+
     private boolean isBooleanFlagEnabled(Flag<?> flag) {
         if (flag instanceof ReleasedFlag) {
             return mFeatureFlags.isEnabled((ReleasedFlag) flag);
@@ -141,34 +200,51 @@
         return false;
     }
 
+    private boolean isStringFlag(Flag<?> flag) {
+        return (flag instanceof StringFlag) || (flag instanceof ResourceStringFlag);
+    }
+
+    private String getStringFlag(Flag<?> flag) {
+        if (flag instanceof StringFlag) {
+            return mFeatureFlags.getString((StringFlag) flag);
+        } else if (flag instanceof ResourceStringFlag) {
+            return mFeatureFlags.getString((ResourceStringFlag) flag);
+        }
+
+        return "";
+    }
+
+    private boolean isIntFlag(Flag<?> flag) {
+        return (flag instanceof IntFlag) || (flag instanceof ResourceIntFlag);
+    }
+
+    private int getIntFlag(Flag<?> flag) {
+        if (flag instanceof IntFlag) {
+            return mFeatureFlags.getInt((IntFlag) flag);
+        } else if (flag instanceof ResourceIntFlag) {
+            return mFeatureFlags.getInt((ResourceIntFlag) flag);
+        }
+
+        return 0;
+    }
+
     private int flagNameToId(String flagName) {
-        List<Field> fields = Flags.getFlagFields();
-        for (Field field : fields) {
-            if (flagName.equals(field.getName())) {
-                return fieldToId(field);
+        Map<String, Flag<?>> flagFields = FlagsFactory.INSTANCE.getKnownFlags();
+        for (String fieldName : flagFields.keySet()) {
+            if (flagName.equals(fieldName)) {
+                return flagFields.get(fieldName).getId();
             }
         }
 
         return 0;
     }
 
-    private int fieldToId(Field field) {
-        try {
-            Flag<?> flag = (Flag<?>) field.get(null);
-            return flag.getId();
-        } catch (IllegalAccessException e) {
-            // no-op
-        }
-
-        return 0;
-    }
-
     private void printKnownFlags(PrintWriter pw) {
-        List<Field> fields = Flags.getFlagFields();
+        Map<String, Flag<?>> fields = FlagsFactory.INSTANCE.getKnownFlags();
 
         int longestFieldName = 0;
-        for (Field field : fields) {
-            longestFieldName = Math.max(longestFieldName, field.getName().length());
+        for (String fieldName : fields.keySet()) {
+            longestFieldName = Math.max(longestFieldName, fieldName.length());
         }
 
         pw.println("Known Flags:");
@@ -176,23 +252,32 @@
         for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) {
             pw.print(" ");
         }
-        pw.println("ID   Enabled?");
+        pw.println("ID   Value");
         for (int i = 0; i < longestFieldName; i++) {
             pw.print("=");
         }
         pw.println(" ==== ========");
-        for (Field field : fields) {
-            int id = fieldToId(field);
+        for (String fieldName : fields.keySet()) {
+            Flag<?> flag = fields.get(fieldName);
+            int id = flag.getId();
             if (id == 0 || !mAllFlags.containsKey(id)) {
                 continue;
             }
-            pw.print(field.getName());
-            int fieldWidth = field.getName().length();
+            pw.print(fieldName);
+            int fieldWidth = fieldName.length();
             for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) {
                 pw.print(" ");
             }
             pw.printf("%-4d ", id);
-            pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
+            if (isBooleanFlag(flag)) {
+                pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
+            } else if (isStringFlag(flag)) {
+                pw.println(getStringFlag(flag));
+            } else if (isIntFlag(flag)) {
+                pw.println(getIntFlag(flag));
+            } else {
+                pw.println("<unknown flag type>");
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index a4d3399e..ae2e1d1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -18,7 +18,10 @@
 import android.provider.DeviceConfig
 import com.android.internal.annotations.Keep
 import com.android.systemui.R
-import java.lang.reflect.Field
+import com.android.systemui.flags.FlagsFactory.releasedFlag
+import com.android.systemui.flags.FlagsFactory.resourceBooleanFlag
+import com.android.systemui.flags.FlagsFactory.sysPropBooleanFlag
+import com.android.systemui.flags.FlagsFactory.unreleasedFlag
 
 /**
  * List of [Flag] objects for use in SystemUI.
@@ -33,329 +36,405 @@
  * See [FeatureFlagsDebug] for instructions on flipping the flags via adb.
  */
 object Flags {
-    @JvmField val TEAMFOOD = UnreleasedFlag(1)
+    @JvmField val TEAMFOOD = unreleasedFlag(1, "teamfood")
 
     // 100 - notification
     // TODO(b/254512751): Tracking Bug
-    val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = UnreleasedFlag(103)
+    val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
+        unreleasedFlag(103, "notification_pipeline_developer_logging")
 
     // TODO(b/254512732): Tracking Bug
-    @JvmField val NSSL_DEBUG_LINES = UnreleasedFlag(105)
+    @JvmField val NSSL_DEBUG_LINES = unreleasedFlag(105, "nssl_debug_lines")
 
     // TODO(b/254512505): Tracking Bug
-    @JvmField val NSSL_DEBUG_REMOVE_ANIMATION = UnreleasedFlag(106)
+    @JvmField val NSSL_DEBUG_REMOVE_ANIMATION = unreleasedFlag(106, "nssl_debug_remove_animation")
 
     // TODO(b/254512624): Tracking Bug
     @JvmField
     val NOTIFICATION_DRAG_TO_CONTENTS =
-        ResourceBooleanFlag(108, R.bool.config_notificationToContents)
+        resourceBooleanFlag(
+            108,
+            R.bool.config_notificationToContents,
+            "notification_drag_to_contents"
+        )
 
     // TODO(b/254512517): Tracking Bug
-    val FSI_REQUIRES_KEYGUARD = UnreleasedFlag(110, teamfood = true)
+    val FSI_REQUIRES_KEYGUARD = unreleasedFlag(110, "fsi_requires_keyguard", teamfood = true)
+
+    // TODO(b/259130119): Tracking Bug
+    val FSI_ON_DND_UPDATE = unreleasedFlag(259130119, "fsi_on_dnd_update", teamfood = true)
 
     // TODO(b/254512538): Tracking Bug
-    val INSTANT_VOICE_REPLY = UnreleasedFlag(111, teamfood = true)
+    val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply", teamfood = true)
 
     // TODO(b/254512425): Tracking Bug
-    val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = true)
+    val NOTIFICATION_MEMORY_MONITOR_ENABLED =
+        releasedFlag(112, "notification_memory_monitor_enabled")
 
     // 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, teamfood = true)
-    // next id: 117
+    @JvmField
+    val NOTIFICATION_DISMISSAL_FADE =
+        unreleasedFlag(113, "notification_dismissal_fade", teamfood = true)
+
+    // TODO(b/259558771): Tracking Bug
+    val STABILITY_INDEX_FIX = releasedFlag(114, "stability_index_fix")
+
+    // TODO(b/259559750): Tracking Bug
+    val SEMI_STABLE_SORT = unreleasedFlag(115, "semi_stable_sort", teamfood = true)
+
+    @JvmField
+    val USE_ROUNDNESS_SOURCETYPES = unreleasedFlag(116, "use_roundness_sourcetype", teamfood = true)
+
+    // TODO(b/259217907)
+    @JvmField
+    val NOTIFICATION_GROUP_DISMISSAL_ANIMATION =
+        unreleasedFlag(259217907, "notification_group_dismissal_animation", teamfood = true)
+
+    // TODO(b/257506350): Tracking Bug
+    val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
+
+    @JvmField
+    val SIMPLIFIED_APPEAR_FRACTION =
+        unreleasedFlag(259395680, "simplified_appear_fraction", teamfood = true)
+
+    // TODO(b/257315550): Tracking Bug
+    val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when")
+
+    val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
+        unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
 
     // 200 - keyguard/lockscreen
     // ** Flag retired **
     // public static final BooleanFlag KEYGUARD_LAYOUT =
     //         new BooleanFlag(200, true);
     // TODO(b/254512713): Tracking Bug
-    @JvmField val LOCKSCREEN_ANIMATIONS = ReleasedFlag(201)
+    @JvmField val LOCKSCREEN_ANIMATIONS = releasedFlag(201, "lockscreen_animations")
 
     // TODO(b/254512750): Tracking Bug
-    val NEW_UNLOCK_SWIPE_ANIMATION = ReleasedFlag(202)
-    val CHARGING_RIPPLE = ResourceBooleanFlag(203, R.bool.flag_charging_ripple)
+    val NEW_UNLOCK_SWIPE_ANIMATION = releasedFlag(202, "new_unlock_swipe_animation")
+    val CHARGING_RIPPLE = resourceBooleanFlag(203, R.bool.flag_charging_ripple, "charging_ripple")
 
     // TODO(b/254512281): Tracking Bug
     @JvmField
-    val BOUNCER_USER_SWITCHER = ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher)
+    val BOUNCER_USER_SWITCHER =
+        resourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher, "bouncer_user_switcher")
 
     // TODO(b/254512676): Tracking Bug
-    @JvmField val LOCKSCREEN_CUSTOM_CLOCKS = UnreleasedFlag(207, teamfood = true)
+    @JvmField
+    val LOCKSCREEN_CUSTOM_CLOCKS = unreleasedFlag(207, "lockscreen_custom_clocks", teamfood = true)
 
     /**
      * Flag to enable the usage of the new bouncer data source. This is a refactor of and eventual
      * replacement of KeyguardBouncer.java.
      */
     // TODO(b/254512385): Tracking Bug
-    @JvmField val MODERN_BOUNCER = UnreleasedFlag(208)
-
-    /**
-     * Whether the user interactor and repository should use `UserSwitcherController`.
-     *
-     * If this is `false`, the interactor and repo skip the controller and directly access the
-     * framework APIs.
-     */
-    // TODO(b/254513286): Tracking Bug
-    val USER_INTERACTOR_AND_REPO_USE_CONTROLLER = UnreleasedFlag(210)
-
-    /**
-     * Whether `UserSwitcherController` should use the user interactor.
-     *
-     * When this is `true`, the controller does not directly access framework APIs. Instead, it goes
-     * through the interactor.
-     *
-     * Note: do not set this to true if [.USER_INTERACTOR_AND_REPO_USE_CONTROLLER] is `true` as it
-     * would created a cycle between controller -> interactor -> controller.
-     */
-    // TODO(b/254513102): Tracking Bug
-    val USER_CONTROLLER_USES_INTERACTOR = ReleasedFlag(211)
+    @JvmField val MODERN_BOUNCER = releasedFlag(208, "modern_bouncer")
 
     /**
      * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
      * the digits when the clock moves.
      */
-    @JvmField val STEP_CLOCK_ANIMATION = UnreleasedFlag(212)
+    @JvmField val STEP_CLOCK_ANIMATION = unreleasedFlag(212, "step_clock_animation")
 
     /**
      * 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, "dozing_migration_1")
 
-    @JvmField val NEW_ELLIPSE_DETECTION = UnreleasedFlag(214)
+    // TODO(b/252897742): Tracking Bug
+    @JvmField val NEW_ELLIPSE_DETECTION = unreleasedFlag(214, "new_ellipse_detection")
 
-    @JvmField val NEW_UDFPS_OVERLAY = UnreleasedFlag(215)
+    // TODO(b/252897742): Tracking Bug
+    @JvmField val NEW_UDFPS_OVERLAY = unreleasedFlag(215, "new_udfps_overlay")
+
+    /**
+     * Whether to enable the code powering customizable lock screen quick affordances.
+     *
+     * This flag enables any new prebuilt quick affordances as well.
+     */
+    // TODO(b/255618149): Tracking Bug
+    @JvmField
+    val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
+        unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = false)
+
+    /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
+    // TODO(b/256513609): Tracking Bug
+    @JvmField val ACTIVE_UNLOCK_CHIPBAR = releasedFlag(217, "active_unlock_chipbar")
+
+    /**
+     * Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the
+     * new KeyguardTransitionRepository.
+     */
+    @JvmField
+    val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = true)
 
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
-    @JvmField val POWER_MENU_LITE = ReleasedFlag(300)
+    @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
 
     // 400 - smartspace
 
     // TODO(b/254513100): Tracking Bug
-    val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED = ReleasedFlag(401)
-    val SMARTSPACE = ResourceBooleanFlag(402, R.bool.flag_smartspace)
+    val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
+        releasedFlag(401, "smartspace_shared_element_transition_enabled")
+    val SMARTSPACE = resourceBooleanFlag(402, R.bool.flag_smartspace, "smartspace")
 
     // 500 - quick settings
-    @Deprecated("Not needed anymore") val NEW_USER_SWITCHER = ReleasedFlag(500)
 
     // TODO(b/254512321): Tracking Bug
-    @JvmField val COMBINED_QS_HEADERS = UnreleasedFlag(501, teamfood = true)
-    val PEOPLE_TILE = ResourceBooleanFlag(502, R.bool.flag_conversations)
+    @JvmField val COMBINED_QS_HEADERS = releasedFlag(501, "combined_qs_headers")
+    val PEOPLE_TILE = resourceBooleanFlag(502, R.bool.flag_conversations, "people_tile")
+
     @JvmField
     val QS_USER_DETAIL_SHORTCUT =
-        ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut)
-
-    // TODO(b/254512699): Tracking Bug
-    @Deprecated("Not needed anymore") val NEW_FOOTER = ReleasedFlag(504)
-
-    // TODO(b/254512747): Tracking Bug
-    val NEW_HEADER = UnreleasedFlag(505, teamfood = true)
+        resourceBooleanFlag(
+            503,
+            R.bool.flag_lockscreen_qs_user_detail_shortcut,
+            "qs_user_detail_shortcut"
+        )
 
     // TODO(b/254512383): Tracking Bug
     @JvmField
     val FULL_SCREEN_USER_SWITCHER =
-        ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher)
+        resourceBooleanFlag(
+            506,
+            R.bool.config_enableFullscreenUserSwitcher,
+            "full_screen_user_switcher"
+        )
 
     // TODO(b/254512678): Tracking Bug
-    @JvmField val NEW_FOOTER_ACTIONS = ReleasedFlag(507)
+    @JvmField val NEW_FOOTER_ACTIONS = releasedFlag(507, "new_footer_actions")
 
     // TODO(b/244064524): Tracking Bug
-    @JvmField val QS_SECONDARY_DATA_SUB_INFO = UnreleasedFlag(508, teamfood = true)
+    @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag(508, "qs_secondary_data_sub_info")
 
     // 600- status bar
-    // TODO(b/254513246): Tracking Bug
-    val STATUS_BAR_USER_SWITCHER = ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip)
 
-    // TODO(b/254512623): Tracking Bug
-    @Deprecated("Replaced by mobile and wifi specific flags.")
-    val NEW_STATUS_BAR_PIPELINE_BACKEND = UnreleasedFlag(604, teamfood = false)
+    // TODO(b/256614753): Tracking Bug
+    val NEW_STATUS_BAR_MOBILE_ICONS = unreleasedFlag(606, "new_status_bar_mobile_icons")
 
-    // TODO(b/254512660): Tracking Bug
-    @Deprecated("Replaced by mobile and wifi specific flags.")
-    val NEW_STATUS_BAR_PIPELINE_FRONTEND = UnreleasedFlag(605, teamfood = false)
+    // TODO(b/256614210): Tracking Bug
+    val NEW_STATUS_BAR_WIFI_ICON = unreleasedFlag(607, "new_status_bar_wifi_icon")
 
-    val NEW_STATUS_BAR_MOBILE_ICONS = UnreleasedFlag(606)
+    // TODO(b/256614751): Tracking Bug
+    val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND =
+        unreleasedFlag(608, "new_status_bar_mobile_icons_backend")
 
-    val NEW_STATUS_BAR_WIFI_ICON = UnreleasedFlag(607)
+    // TODO(b/256613548): Tracking Bug
+    val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend")
+
+    // TODO(b/256623670): Tracking Bug
+    @JvmField
+    val BATTERY_SHIELD_ICON =
+        resourceBooleanFlag(610, R.bool.flag_battery_shield_icon, "battery_shield_icon")
+
+    // TODO(b/260881289): Tracking Bug
+    val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
+        unreleasedFlag(611, "new_status_bar_icons_debug_coloring")
 
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
-    val ONGOING_CALL_STATUS_BAR_CHIP = ReleasedFlag(700)
+    val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
 
     // TODO(b/254512681): Tracking Bug
-    val ONGOING_CALL_IN_IMMERSIVE = ReleasedFlag(701)
+    val ONGOING_CALL_IN_IMMERSIVE = releasedFlag(701, "ongoing_call_in_immersive")
 
     // TODO(b/254512753): Tracking Bug
-    val ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = ReleasedFlag(702)
+    val ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = releasedFlag(702, "ongoing_call_in_immersive_chip_tap")
 
     // 800 - general visual/theme
-    @JvmField val MONET = ResourceBooleanFlag(800, R.bool.flag_monet)
+    @JvmField val MONET = resourceBooleanFlag(800, R.bool.flag_monet, "monet")
 
     // 801 - region sampling
     // TODO(b/254512848): Tracking Bug
-    val REGION_SAMPLING = UnreleasedFlag(801)
+    val REGION_SAMPLING = unreleasedFlag(801, "region_sampling")
 
     // 802 - wallpaper rendering
     // TODO(b/254512923): Tracking Bug
-    @JvmField val USE_CANVAS_RENDERER = UnreleasedFlag(802, teamfood = true)
+    @JvmField val USE_CANVAS_RENDERER = unreleasedFlag(802, "use_canvas_renderer")
 
     // 803 - screen contents translation
     // TODO(b/254513187): Tracking Bug
-    val SCREEN_CONTENTS_TRANSLATION = UnreleasedFlag(803)
+    val SCREEN_CONTENTS_TRANSLATION = unreleasedFlag(803, "screen_contents_translation")
 
     // 804 - monochromatic themes
-    @JvmField val MONOCHROMATIC_THEMES = UnreleasedFlag(804)
+    @JvmField
+    val MONOCHROMATIC_THEMES =
+        sysPropBooleanFlag(804, "persist.sysui.monochromatic", default = false)
 
     // 900 - media
     // TODO(b/254512697): Tracking Bug
-    val MEDIA_TAP_TO_TRANSFER = ReleasedFlag(900)
+    val MEDIA_TAP_TO_TRANSFER = releasedFlag(900, "media_tap_to_transfer")
 
     // TODO(b/254512502): Tracking Bug
-    val MEDIA_SESSION_ACTIONS = UnreleasedFlag(901)
+    val MEDIA_SESSION_ACTIONS = unreleasedFlag(901, "media_session_actions")
 
     // TODO(b/254512726): Tracking Bug
-    val MEDIA_NEARBY_DEVICES = ReleasedFlag(903)
+    val MEDIA_NEARBY_DEVICES = releasedFlag(903, "media_nearby_devices")
 
     // TODO(b/254512695): Tracking Bug
-    val MEDIA_MUTE_AWAIT = ReleasedFlag(904)
+    val MEDIA_MUTE_AWAIT = releasedFlag(904, "media_mute_await")
 
     // TODO(b/254512654): Tracking Bug
-    @JvmField val DREAM_MEDIA_COMPLICATION = UnreleasedFlag(905)
+    @JvmField val DREAM_MEDIA_COMPLICATION = unreleasedFlag(905, "dream_media_complication")
 
     // TODO(b/254512673): Tracking Bug
-    @JvmField val DREAM_MEDIA_TAP_TO_OPEN = UnreleasedFlag(906)
+    @JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag(906, "dream_media_tap_to_open")
 
     // TODO(b/254513168): Tracking Bug
-    val UMO_SURFACE_RIPPLE = UnreleasedFlag(907)
+    @JvmField val UMO_SURFACE_RIPPLE = unreleasedFlag(907, "umo_surface_ripple")
+
+    @JvmField val MEDIA_FALSING_PENALTY = unreleasedFlag(908, "media_falsing_media")
 
     // 1000 - dock
-    val SIMULATE_DOCK_THROUGH_CHARGING = ReleasedFlag(1000)
+    val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
 
     // TODO(b/254512758): Tracking Bug
-    @JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
+    @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
 
     // 1100 - windowing
     @Keep
+    @JvmField
     val WM_ENABLE_SHELL_TRANSITIONS =
-        SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false)
-
-    /** b/170163464: animate bubbles expanded view collapse with home gesture */
-    @Keep
-    val BUBBLES_HOME_GESTURE =
-        SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true)
+        sysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", default = false)
 
     // TODO(b/254513207): Tracking Bug
-    @JvmField
     @Keep
+    @JvmField
     val WM_ENABLE_PARTIAL_SCREEN_SHARING =
-        DeviceConfigBooleanFlag(
+        unreleasedFlag(
             1102,
-            "record_task_content",
-            DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-            false,
+            name = "record_task_content",
+            namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
             teamfood = true
         )
 
     // TODO(b/254512674): Tracking Bug
+    @Keep
     @JvmField
-    @Keep
-    val HIDE_NAVBAR_WINDOW = SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false)
+    val HIDE_NAVBAR_WINDOW =
+        sysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", default = false)
 
     @Keep
-    val WM_DESKTOP_WINDOWING = SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false)
+    @JvmField
+    val WM_DESKTOP_WINDOWING =
+        sysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", default = false)
 
     @Keep
-    val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false)
+    @JvmField
+    val WM_CAPTION_ON_SHELL =
+        sysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", default = false)
 
     @Keep
-    val FLOATING_TASKS_ENABLED = SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false)
-
-    @Keep
-    val SHOW_FLOATING_TASKS_AS_BUBBLES =
-        SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false)
-
-    @Keep
+    @JvmField
     val ENABLE_FLING_TO_DISMISS_BUBBLE =
-        SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true)
+        sysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", default = true)
 
     @Keep
+    @JvmField
     val ENABLE_FLING_TO_DISMISS_PIP =
-        SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true)
+        sysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", default = true)
 
     @Keep
+    @JvmField
     val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
-        SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false)
+        sysPropBooleanFlag(
+            1110,
+            "persist.wm.debug.enable_pip_keep_clear_algorithm",
+            default = false
+        )
+
+    // TODO(b/256873975): Tracking Bug
+    @JvmField @Keep val WM_BUBBLE_BAR = unreleasedFlag(1111, "wm_bubble_bar")
+
+    // TODO(b/260271148): Tracking bug
+    @Keep
+    @JvmField
+    val WM_DESKTOP_WINDOWING_2 =
+        sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false)
 
     // 1200 - predictive back
     @Keep
+    @JvmField
     val WM_ENABLE_PREDICTIVE_BACK =
-        SysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", true)
+        sysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", default = true)
 
     @Keep
+    @JvmField
     val WM_ENABLE_PREDICTIVE_BACK_ANIM =
-        SysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", false)
+        sysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", default = false)
 
     @Keep
+    @JvmField
     val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
-        SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false)
+        sysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", default = false)
 
     // TODO(b/254512728): Tracking Bug
-    @JvmField val NEW_BACK_AFFORDANCE = UnreleasedFlag(1203, teamfood = false)
+    @JvmField
+    val NEW_BACK_AFFORDANCE = unreleasedFlag(1203, "new_back_affordance", teamfood = false)
+
+    // TODO(b/255854141): Tracking Bug
+    @JvmField
+    val WM_ENABLE_PREDICTIVE_BACK_SYSUI =
+        unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = true)
 
     // 1300 - screenshots
     // TODO(b/254512719): Tracking Bug
-    @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300)
+    @JvmField val SCREENSHOT_REQUEST_PROCESSOR = releasedFlag(1300, "screenshot_request_processor")
 
     // TODO(b/254513155): Tracking Bug
-    @JvmField val SCREENSHOT_WORK_PROFILE_POLICY = UnreleasedFlag(1301)
+    @JvmField
+    val SCREENSHOT_WORK_PROFILE_POLICY =
+        unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true)
 
     // 1400 - columbus
     // TODO(b/254512756): Tracking Bug
-    val QUICK_TAP_IN_PCC = ReleasedFlag(1400)
+    val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc")
 
     // 1500 - chooser
     // TODO(b/254512507): Tracking Bug
-    val CHOOSER_UNBUNDLED = UnreleasedFlag(1500, teamfood = true)
+    val CHOOSER_UNBUNDLED = unreleasedFlag(1500, "chooser_unbundled", teamfood = true)
 
     // 1600 - accessibility
-    @JvmField val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS = UnreleasedFlag(1600)
+    @JvmField
+    val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS =
+        unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations", teamfood = true)
 
     // 1700 - clipboard
-    @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700)
+    @JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
+    @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
 
     // 1800 - shade container
-    @JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = UnreleasedFlag(1800, teamfood = true)
+    @JvmField
+    val LEAVE_SHADE_OPEN_FOR_BUGREPORT =
+        unreleasedFlag(1800, "leave_shade_open_for_bugreport", teamfood = true)
 
-    // 1900 - note task
-    @JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks")
+    // 1900
+    @JvmField val NOTE_TASKS = unreleasedFlag(1900, "keycode_flag")
 
-    // Pay no attention to the reflection behind the curtain.
-    // ========================== Curtain ==========================
-    // |                                                           |
-    // |  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  |
-    @JvmStatic
-    fun collectFlags(): Map<Int, Flag<*>> {
-        return flagFields
-            .map { field ->
-                // field[null] returns the current value of the field.
-                // See java.lang.Field#get
-                val flag = field[null] as Flag<*>
-                flag.id to flag
-            }
-            .toMap()
-    }
+    // 2000 - device controls
+    @Keep @JvmField val USE_APP_PANELS = unreleasedFlag(2000, "use_app_panels", teamfood = true)
 
-    // |  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  |
-    @JvmStatic
-    val flagFields: List<Field>
-        get() {
-            return Flags::class.java.fields.filter { f ->
-                Flag::class.java.isAssignableFrom(f.type)
-            }
-        }
-    // |                                                           |
-    // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/
+    // 2100 - Falsing Manager
+    @JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
+
+    // 2200 - udfps
+    // TODO(b/259264861): Tracking Bug
+    @JvmField val UDFPS_NEW_TOUCH_DETECTION = unreleasedFlag(2200, "udfps_new_touch_detection")
+    @JvmField val UDFPS_ELLIPSE_DEBUG_UI = unreleasedFlag(2201, "udfps_ellipse_debug")
+    @JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection")
+
+    // 2300 - stylus
+    @JvmField val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used")
+
+    // 2400 - performance tools and debugging info
+    // TODO(b/238923086): Tracking Bug
+    @JvmField
+    val WARN_ON_BLOCKING_BINDER_TRANSACTIONS =
+        unreleasedFlag(2400, "warn_on_blocking_binder_transactions")
+
+    // TODO(b259590361): Tracking bug
+    val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index e1f4944..8442230 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -15,7 +15,6 @@
  */
 package com.android.systemui.flags
 
-import com.android.internal.statusbar.IStatusBarService
 import dagger.Module
 import dagger.Provides
 import javax.inject.Named
@@ -30,17 +29,7 @@
         @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()
-                }
-            }
+            return FlagsFactory.knownFlags.map { it.value.id to it.value }.toMap()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/OWNERS b/packages/SystemUI/src/com/android/systemui/flags/OWNERS
new file mode 100644
index 0000000..c9d2db1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/OWNERS
@@ -0,0 +1,12 @@
+set noparent
+
+# Bug component: 1203176
+
+mankoff@google.com # send reviews here
+
+pixel@google.com
+juliacr@google.com
+cinek@google.com
+alexflo@google.com
+dsandler@android.com
+adamcohen@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
index 8f095a2..ce8b821 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
@@ -16,5 +16,7 @@
 package com.android.systemui.flags
 
 interface Restarter {
-    fun restart()
-}
\ No newline at end of file
+    fun restartSystemUI()
+
+    fun restartAndroid()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index 694fa01..ae05c46 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -27,11 +27,10 @@
 
 interface ServerFlagReader {
     /** Returns true if there is a server-side setting stored. */
-    fun hasOverride(flagId: Int): Boolean
+    fun hasOverride(namespace: String, name: String): Boolean
 
     /** Returns any stored server-side setting or the default if not set. */
-    fun readServerOverride(flagId: Int, default: Boolean): Boolean
-
+    fun readServerOverride(namespace: String, name: String, default: Boolean): Boolean
     /** Register a listener for changes to any of the passed in flags. */
     fun listenForChanges(values: Collection<Flag<*>>, listener: ChangeListener)
 
@@ -68,19 +67,19 @@
         }
     }
 
-    override fun hasOverride(flagId: Int): Boolean =
-        deviceConfig.getProperty(
+    override fun hasOverride(namespace: String, name: String): Boolean =
+        !namespace.isBlank() && !name.isBlank() && deviceConfig.getProperty(
             namespace,
-            getServerOverrideName(flagId)
+            name
         ) != null
 
-    override fun readServerOverride(flagId: Int, default: Boolean): Boolean {
-        return deviceConfig.getBoolean(
+
+    override fun readServerOverride(namespace: String, name: String, default: Boolean): Boolean =
+        !namespace.isBlank() && !name.isBlank() && deviceConfig.getBoolean(
             namespace,
-            getServerOverrideName(flagId),
+            name,
             default
         )
-    }
 
     override fun listenForChanges(
         flags: Collection<Flag<*>>,
@@ -121,24 +120,24 @@
 }
 
 class ServerFlagReaderFake : ServerFlagReader {
-    private val flagMap: MutableMap<Int, Boolean> = mutableMapOf()
+    private val flagMap: MutableMap<String, Boolean> = mutableMapOf()
     private val listeners =
         mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
 
-    override fun hasOverride(flagId: Int): Boolean {
-        return flagMap.containsKey(flagId)
+    override fun hasOverride(namespace: String, name: String): Boolean {
+        return flagMap.containsKey(name)
     }
 
-    override fun readServerOverride(flagId: Int, default: Boolean): Boolean {
-        return flagMap.getOrDefault(flagId, default)
+    override fun readServerOverride(namespace: String, name: String, default: Boolean): Boolean {
+        return flagMap.getOrDefault(name, default)
     }
 
-    fun setFlagValue(flagId: Int, value: Boolean) {
-        flagMap.put(flagId, value)
+    fun setFlagValue(namespace: String, name: String, value: Boolean) {
+        flagMap.put(name, value)
 
         for ((listener, flags) in listeners) {
             flagLoop@ for (flag in flags) {
-                if (flagId == flag.id) {
+                if (name == flag.name) {
                     listener.onChange()
                     break@flagLoop
                 }
@@ -146,8 +145,8 @@
         }
     }
 
-    fun eraseFlag(flagId: Int) {
-        flagMap.remove(flagId)
+    fun eraseFlag(namespace: String, name: String) {
+        flagMap.remove(name)
     }
 
     override fun listenForChanges(
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
new file mode 100644
index 0000000..89daa64
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.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.systemui.flags
+
+import com.android.internal.statusbar.IStatusBarService
+import javax.inject.Inject
+
+class SystemExitRestarter
+@Inject
+constructor(
+    private val barService: IStatusBarService,
+) : Restarter {
+    override fun restartAndroid() {
+        barService.restart()
+    }
+
+    override fun restartSystemUI() {
+        System.exit(0)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
index 18fb423..d9bcb50 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
@@ -50,13 +50,12 @@
 
     @Override
     public void accept(T extension) {
-        try {
-            Fragment.class.cast(extension);
+        if (Fragment.class.isInstance(extension)) {
             mFragmentHostManager.getExtensionManager().setCurrentExtension(mId, mTag,
                     mOldClass, extension.getClass().getName(), mExtension.getContext());
             mOldClass = extension.getClass().getName();
-        } catch (ClassCastException e) {
-            Log.e(TAG, extension.getClass().getName() + " must be a Fragment", e);
+        } else {
+            Log.e(TAG, extension.getClass().getName() + " must be a Fragment");
         }
         mExtension.clearItem(true);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 3ef5499..db2cd91 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -125,6 +125,7 @@
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.scrim.ScrimDrawable;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -201,6 +202,7 @@
     protected final SecureSettings mSecureSettings;
     protected final Resources mResources;
     private final ConfigurationController mConfigurationController;
+    private final UserTracker mUserTracker;
     private final UserManager mUserManager;
     private final TrustManager mTrustManager;
     private final IActivityManager mIActivityManager;
@@ -339,6 +341,7 @@
             @NonNull VibratorHelper vibrator,
             @Main Resources resources,
             ConfigurationController configurationController,
+            UserTracker userTracker,
             KeyguardStateController keyguardStateController,
             UserManager userManager,
             TrustManager trustManager,
@@ -370,6 +373,7 @@
         mSecureSettings = secureSettings;
         mResources = resources;
         mConfigurationController = configurationController;
+        mUserTracker = userTracker;
         mUserManager = userManager;
         mTrustManager = trustManager;
         mIActivityManager = iActivityManager;
@@ -1198,11 +1202,7 @@
     }
 
     protected UserInfo getCurrentUser() {
-        try {
-            return mIActivityManager.getCurrentUser();
-        } catch (RemoteException re) {
-            return null;
-        }
+        return mUserTracker.getUserInfo();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java b/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java
deleted file mode 100644
index 622fa65..0000000
--- a/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.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.systemui.globalactions;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.ScrollView;
-
-/**
- * When measured, this view sets the minimum height of its first child to be equal to its own
- * target height.
- *
- * This ensures fall-through click handlers can be placed on this view's child component.
- */
-public class MinHeightScrollView extends ScrollView {
-    public MinHeightScrollView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        View firstChild = getChildAt(0);
-        if (firstChild != null) {
-            firstChild.setMinimumHeight(MeasureSpec.getSize(heightMeasureSpec));
-        }
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 1f52fc6..9b2e6b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -375,7 +375,7 @@
     public static final int INDICATION_TYPE_ALIGNMENT = 4;
     public static final int INDICATION_TYPE_TRANSIENT = 5;
     public static final int INDICATION_TYPE_TRUST = 6;
-    public static final int INDICATION_TYPE_RESTING = 7;
+    public static final int INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE = 7;
     public static final int INDICATION_TYPE_USER_LOCKED = 8;
     public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
     public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11;
@@ -390,7 +390,7 @@
             INDICATION_TYPE_ALIGNMENT,
             INDICATION_TYPE_TRANSIENT,
             INDICATION_TYPE_TRUST,
-            INDICATION_TYPE_RESTING,
+            INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
             INDICATION_TYPE_USER_LOCKED,
             INDICATION_TYPE_REVERSE_CHARGING,
             INDICATION_TYPE_BIOMETRIC_MESSAGE,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
new file mode 100644
index 0000000..4ae37c5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
@@ -0,0 +1,340 @@
+/*
+ * 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
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.content.UriMatcher
+import android.content.pm.ProviderInfo
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.net.Uri
+import android.util.Log
+import com.android.systemui.SystemUIAppComponentFactoryBase
+import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import javax.inject.Inject
+import kotlinx.coroutines.runBlocking
+
+class KeyguardQuickAffordanceProvider :
+    ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
+
+    @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
+
+    private lateinit var contextAvailableCallback: ContextAvailableCallback
+
+    private val uriMatcher =
+        UriMatcher(UriMatcher.NO_MATCH).apply {
+            addURI(
+                Contract.AUTHORITY,
+                Contract.SlotTable.TABLE_NAME,
+                MATCH_CODE_ALL_SLOTS,
+            )
+            addURI(
+                Contract.AUTHORITY,
+                Contract.AffordanceTable.TABLE_NAME,
+                MATCH_CODE_ALL_AFFORDANCES,
+            )
+            addURI(
+                Contract.AUTHORITY,
+                Contract.SelectionTable.TABLE_NAME,
+                MATCH_CODE_ALL_SELECTIONS,
+            )
+            addURI(
+                Contract.AUTHORITY,
+                Contract.FlagsTable.TABLE_NAME,
+                MATCH_CODE_ALL_FLAGS,
+            )
+        }
+
+    override fun onCreate(): Boolean {
+        return true
+    }
+
+    override fun attachInfo(context: Context?, info: ProviderInfo?) {
+        contextAvailableCallback.onContextAvailable(checkNotNull(context))
+        super.attachInfo(context, info)
+    }
+
+    override fun setContextAvailableCallback(callback: ContextAvailableCallback) {
+        contextAvailableCallback = callback
+    }
+
+    override fun getType(uri: Uri): String? {
+        val prefix =
+            when (uriMatcher.match(uri)) {
+                MATCH_CODE_ALL_SLOTS,
+                MATCH_CODE_ALL_AFFORDANCES,
+                MATCH_CODE_ALL_FLAGS,
+                MATCH_CODE_ALL_SELECTIONS -> "vnd.android.cursor.dir/vnd."
+                else -> null
+            }
+
+        val tableName =
+            when (uriMatcher.match(uri)) {
+                MATCH_CODE_ALL_SLOTS -> Contract.SlotTable.TABLE_NAME
+                MATCH_CODE_ALL_AFFORDANCES -> Contract.AffordanceTable.TABLE_NAME
+                MATCH_CODE_ALL_SELECTIONS -> Contract.SelectionTable.TABLE_NAME
+                MATCH_CODE_ALL_FLAGS -> Contract.FlagsTable.TABLE_NAME
+                else -> null
+            }
+
+        if (prefix == null || tableName == null) {
+            return null
+        }
+
+        return "$prefix${Contract.AUTHORITY}.$tableName"
+    }
+
+    override fun insert(uri: Uri, values: ContentValues?): Uri? {
+        if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) {
+            throw UnsupportedOperationException()
+        }
+
+        return insertSelection(values)
+    }
+
+    override fun query(
+        uri: Uri,
+        projection: Array<out String>?,
+        selection: String?,
+        selectionArgs: Array<out String>?,
+        sortOrder: String?,
+    ): Cursor? {
+        return when (uriMatcher.match(uri)) {
+            MATCH_CODE_ALL_AFFORDANCES -> runBlocking { queryAffordances() }
+            MATCH_CODE_ALL_SLOTS -> querySlots()
+            MATCH_CODE_ALL_SELECTIONS -> runBlocking { querySelections() }
+            MATCH_CODE_ALL_FLAGS -> queryFlags()
+            else -> null
+        }
+    }
+
+    override fun update(
+        uri: Uri,
+        values: ContentValues?,
+        selection: String?,
+        selectionArgs: Array<out String>?,
+    ): Int {
+        Log.e(TAG, "Update is not supported!")
+        return 0
+    }
+
+    override fun delete(
+        uri: Uri,
+        selection: String?,
+        selectionArgs: Array<out String>?,
+    ): Int {
+        if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) {
+            throw UnsupportedOperationException()
+        }
+
+        return deleteSelection(uri, selectionArgs)
+    }
+
+    private fun insertSelection(values: ContentValues?): Uri? {
+        if (values == null) {
+            throw IllegalArgumentException("Cannot insert selection, no values passed in!")
+        }
+
+        if (!values.containsKey(Contract.SelectionTable.Columns.SLOT_ID)) {
+            throw IllegalArgumentException(
+                "Cannot insert selection, " +
+                    "\"${Contract.SelectionTable.Columns.SLOT_ID}\" not specified!"
+            )
+        }
+
+        if (!values.containsKey(Contract.SelectionTable.Columns.AFFORDANCE_ID)) {
+            throw IllegalArgumentException(
+                "Cannot insert selection, " +
+                    "\"${Contract.SelectionTable.Columns.AFFORDANCE_ID}\" not specified!"
+            )
+        }
+
+        val slotId = values.getAsString(Contract.SelectionTable.Columns.SLOT_ID)
+        val affordanceId = values.getAsString(Contract.SelectionTable.Columns.AFFORDANCE_ID)
+
+        if (slotId.isNullOrEmpty()) {
+            throw IllegalArgumentException("Cannot insert selection, slot ID was empty!")
+        }
+
+        if (affordanceId.isNullOrEmpty()) {
+            throw IllegalArgumentException("Cannot insert selection, affordance ID was empty!")
+        }
+
+        val success =
+            interactor.select(
+                slotId = slotId,
+                affordanceId = affordanceId,
+            )
+
+        return if (success) {
+            Log.d(TAG, "Successfully selected $affordanceId for slot $slotId")
+            context?.contentResolver?.notifyChange(Contract.SelectionTable.URI, null)
+            Contract.SelectionTable.URI
+        } else {
+            Log.d(TAG, "Failed to select $affordanceId for slot $slotId")
+            null
+        }
+    }
+
+    private suspend fun querySelections(): Cursor {
+        return MatrixCursor(
+                arrayOf(
+                    Contract.SelectionTable.Columns.SLOT_ID,
+                    Contract.SelectionTable.Columns.AFFORDANCE_ID,
+                    Contract.SelectionTable.Columns.AFFORDANCE_NAME,
+                )
+            )
+            .apply {
+                val affordanceRepresentationsBySlotId = interactor.getSelections()
+                affordanceRepresentationsBySlotId.entries.forEach {
+                    (slotId, affordanceRepresentations) ->
+                    affordanceRepresentations.forEach { affordanceRepresentation ->
+                        addRow(
+                            arrayOf(
+                                slotId,
+                                affordanceRepresentation.id,
+                                affordanceRepresentation.name,
+                            )
+                        )
+                    }
+                }
+            }
+    }
+
+    private suspend fun queryAffordances(): Cursor {
+        return MatrixCursor(
+                arrayOf(
+                    Contract.AffordanceTable.Columns.ID,
+                    Contract.AffordanceTable.Columns.NAME,
+                    Contract.AffordanceTable.Columns.ICON,
+                    Contract.AffordanceTable.Columns.IS_ENABLED,
+                    Contract.AffordanceTable.Columns.ENABLEMENT_INSTRUCTIONS,
+                    Contract.AffordanceTable.Columns.ENABLEMENT_ACTION_TEXT,
+                    Contract.AffordanceTable.Columns.ENABLEMENT_COMPONENT_NAME,
+                )
+            )
+            .apply {
+                interactor.getAffordancePickerRepresentations().forEach { representation ->
+                    addRow(
+                        arrayOf(
+                            representation.id,
+                            representation.name,
+                            representation.iconResourceId,
+                            if (representation.isEnabled) 1 else 0,
+                            representation.instructions?.joinToString(
+                                Contract.AffordanceTable.ENABLEMENT_INSTRUCTIONS_DELIMITER
+                            ),
+                            representation.actionText,
+                            representation.actionComponentName,
+                        )
+                    )
+                }
+            }
+    }
+
+    private fun querySlots(): Cursor {
+        return MatrixCursor(
+                arrayOf(
+                    Contract.SlotTable.Columns.ID,
+                    Contract.SlotTable.Columns.CAPACITY,
+                )
+            )
+            .apply {
+                interactor.getSlotPickerRepresentations().forEach { representation ->
+                    addRow(
+                        arrayOf(
+                            representation.id,
+                            representation.maxSelectedAffordances,
+                        )
+                    )
+                }
+            }
+    }
+
+    private fun queryFlags(): Cursor {
+        return MatrixCursor(
+                arrayOf(
+                    Contract.FlagsTable.Columns.NAME,
+                    Contract.FlagsTable.Columns.VALUE,
+                )
+            )
+            .apply {
+                interactor.getPickerFlags().forEach { flag ->
+                    addRow(
+                        arrayOf(
+                            flag.name,
+                            if (flag.value) {
+                                1
+                            } else {
+                                0
+                            },
+                        )
+                    )
+                }
+            }
+    }
+
+    private fun deleteSelection(
+        uri: Uri,
+        selectionArgs: Array<out String>?,
+    ): Int {
+        if (selectionArgs == null) {
+            throw IllegalArgumentException(
+                "Cannot delete selection, selection arguments not included!"
+            )
+        }
+
+        val (slotId, affordanceId) =
+            when (selectionArgs.size) {
+                1 -> Pair(selectionArgs[0], null)
+                2 -> Pair(selectionArgs[0], selectionArgs[1])
+                else ->
+                    throw IllegalArgumentException(
+                        "Cannot delete selection, selection arguments has wrong size, expected to" +
+                            " have 1 or 2 arguments, had ${selectionArgs.size} instead!"
+                    )
+            }
+
+        val deleted =
+            interactor.unselect(
+                slotId = slotId,
+                affordanceId = affordanceId,
+            )
+
+        return if (deleted) {
+            Log.d(TAG, "Successfully unselected $affordanceId for slot $slotId")
+            context?.contentResolver?.notifyChange(uri, null)
+            1
+        } else {
+            Log.d(TAG, "Failed to unselect $affordanceId for slot $slotId")
+            0
+        }
+    }
+
+    companion object {
+        private const val TAG = "KeyguardQuickAffordanceProvider"
+        private const val MATCH_CODE_ALL_SLOTS = 1
+        private const val MATCH_CODE_ALL_AFFORDANCES = 2
+        private const val MATCH_CODE_ALL_SELECTIONS = 3
+        private const val MATCH_CODE_ALL_FLAGS = 4
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 0214313..e631816 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -220,6 +220,7 @@
                                 synchronized (mFinishCallbacks) {
                                     if (mFinishCallbacks.remove(transition) == null) return;
                                 }
+                                info.releaseAllSurfaces();
                                 Slog.d(TAG, "Finish IRemoteAnimationRunner.");
                                 finishCallback.onTransitionFinished(null /* wct */, null /* t */);
                             }
@@ -235,6 +236,8 @@
                     synchronized (mFinishCallbacks) {
                         origFinishCB = mFinishCallbacks.remove(transition);
                     }
+                    info.releaseAllSurfaces();
+                    t.close();
                     if (origFinishCB == null) {
                         // already finished (or not started yet), so do nothing.
                         return;
@@ -423,12 +426,15 @@
             t.apply();
             mBinder.setOccluded(true /* isOccluded */, true /* animate */);
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            info.releaseAllSurfaces();
         }
 
         @Override
         public void mergeAnimation(IBinder transition, TransitionInfo info,
                 SurfaceControl.Transaction t, IBinder mergeTarget,
                 IRemoteTransitionFinishedCallback finishCallback) {
+            t.close();
+            info.releaseAllSurfaces();
         }
     };
 
@@ -440,12 +446,15 @@
             t.apply();
             mBinder.setOccluded(false /* isOccluded */, true /* animate */);
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            info.releaseAllSurfaces();
         }
 
         @Override
         public void mergeAnimation(IBinder transition, TransitionInfo info,
                 SurfaceControl.Transaction t, IBinder mergeTarget,
                 IRemoteTransitionFinishedCallback finishCallback) {
+            t.close();
+            info.releaseAllSurfaces();
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 5d564f7..bafd2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard;
 
 import android.annotation.AnyThread;
-import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -52,6 +51,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUIAppComponentFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -140,6 +140,8 @@
     public KeyguardBypassController mKeyguardBypassController;
     @Inject
     public KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Inject
+    UserTracker mUserTracker;
     private CharSequence mMediaTitle;
     private CharSequence mMediaArtist;
     protected boolean mDozing;
@@ -355,7 +357,7 @@
         synchronized (this) {
             if (withinNHoursLocked(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) {
                 String pattern = android.text.format.DateFormat.is24HourFormat(getContext(),
-                        ActivityManager.getCurrentUser()) ? "HH:mm" : "h:mm";
+                        mUserTracker.getUserId()) ? "HH:mm" : "h:mm";
                 mNextAlarm = android.text.format.DateFormat.format(pattern,
                         mNextAlarmInfo.getTriggerTime()).toString();
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index c4eac1c..c0d6cc9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -824,7 +824,11 @@
         surfaceBehindEntryAnimator.cancel()
         surfaceBehindAlpha = 1f
         setSurfaceBehindAppearAmount(1f)
-        launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
+        try {
+            launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
+        }  catch (e: RemoteException) {
+            Log.e(TAG, "Remote exception in notifyFinishedKeyguardExitAnimation", e)
+        }
 
         // That target is no longer valid since the animation finished, null it out.
         surfaceBehindRemoteAnimationTargets = null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 41abb62..36c939d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -36,7 +36,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
-import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -124,7 +123,9 @@
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
@@ -136,6 +137,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -262,10 +264,12 @@
     private AlarmManager mAlarmManager;
     private AudioManager mAudioManager;
     private StatusBarManager mStatusBarManager;
+    private final UserTracker mUserTracker;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final Executor mUiBgExecutor;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthController;
+    private final Lazy<ShadeController> mShadeController;
 
     private boolean mSystemReady;
     private boolean mBootCompleted;
@@ -407,6 +411,11 @@
     private final int mDreamOpenAnimationDuration;
 
     /**
+     * The duration in milliseconds of the dream close animation.
+     */
+    private final int mDreamCloseAnimationDuration;
+
+    /**
      * The animation used for hiding keyguard. This is used to fetch the animation timings if
      * WindowManager is not providing us with them.
      */
@@ -713,7 +722,7 @@
 
         @Override
         public void keyguardDone(boolean strongAuth, int targetUserId) {
-            if (targetUserId != ActivityManager.getCurrentUser()) {
+            if (targetUserId != mUserTracker.getUserId()) {
                 return;
             }
             if (DEBUG) Log.d(TAG, "keyguardDone");
@@ -736,7 +745,7 @@
         public void keyguardDonePending(boolean strongAuth, int targetUserId) {
             Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
             if (DEBUG) Log.d(TAG, "keyguardDonePending");
-            if (targetUserId != ActivityManager.getCurrentUser()) {
+            if (targetUserId != mUserTracker.getUserId()) {
                 Trace.endSection();
                 return;
             }
@@ -844,6 +853,7 @@
                 @Override
                 public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
                     mOccludeAnimationPlaying = true;
+                    mScrimControllerLazy.get().setOccludeAnimationPlaying(true);
                 }
 
                 @Override
@@ -854,12 +864,13 @@
 
                     // Ensure keyguard state is set correctly if we're cancelled.
                     mCentralSurfaces.updateIsKeyguard();
+                    mScrimControllerLazy.get().setOccludeAnimationPlaying(false);
                 }
 
                 @Override
                 public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
                     if (launchIsFullScreen) {
-                        mCentralSurfaces.instantCollapseNotificationPanel();
+                        mShadeController.get().instantCollapseShade();
                     }
 
                     mOccludeAnimationPlaying = false;
@@ -867,6 +878,7 @@
                     // Hide the keyguard now that we're done launching the occluding activity over
                     // it.
                     mCentralSurfaces.updateIsKeyguard();
+                    mScrimControllerLazy.get().setOccludeAnimationPlaying(false);
 
                     mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
                 }
@@ -888,25 +900,32 @@
                 @NonNull
                 @Override
                 public LaunchAnimator.State createAnimatorState() {
-                    final int width = getLaunchContainer().getWidth();
-                    final int height = getLaunchContainer().getHeight();
-
-                    final float initialHeight = height / 3f;
-                    final float initialWidth = width / 3f;
+                    final int fullWidth = getLaunchContainer().getWidth();
+                    final int fullHeight = getLaunchContainer().getHeight();
 
                     if (mUpdateMonitor.isSecureCameraLaunchedOverKeyguard()) {
+                        final float initialHeight = fullHeight / 3f;
+                        final float initialWidth = fullWidth / 3f;
+
                         // Start the animation near the power button, at one-third size, since the
                         // camera was launched from the power button.
                         return new LaunchAnimator.State(
                                 (int) (mPowerButtonY - initialHeight / 2f) /* top */,
                                 (int) (mPowerButtonY + initialHeight / 2f) /* bottom */,
-                                (int) (width - initialWidth) /* left */,
-                                width /* right */,
+                                (int) (fullWidth - initialWidth) /* left */,
+                                fullWidth /* right */,
                                 mWindowCornerRadius, mWindowCornerRadius);
                     } else {
-                        // Start the animation in the center of the screen, scaled down.
+                        final float initialHeight = fullHeight / 2f;
+                        final float initialWidth = fullWidth / 2f;
+
+                        // Start the animation in the center of the screen, scaled down to half
+                        // size.
                         return new LaunchAnimator.State(
-                                height / 2, height / 2, width / 2, width / 2,
+                                (int) (fullHeight - initialHeight) / 2,
+                                (int) (initialHeight + (fullHeight - initialHeight) / 2),
+                                (int) (fullWidth - initialWidth) / 2,
+                                (int) (initialWidth + (fullWidth - initialWidth) / 2),
                                 mWindowCornerRadius, mWindowCornerRadius);
                     }
                 }
@@ -1053,7 +1072,8 @@
                         }
 
                         mUnoccludeAnimator = ValueAnimator.ofFloat(1f, 0f);
-                        mUnoccludeAnimator.setDuration(UNOCCLUDE_ANIMATION_DURATION);
+                        mUnoccludeAnimator.setDuration(isDream ? mDreamCloseAnimationDuration
+                                : UNOCCLUDE_ANIMATION_DURATION);
                         mUnoccludeAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE);
                         mUnoccludeAnimator.addUpdateListener(
                                 animation -> {
@@ -1123,12 +1143,14 @@
     private ScreenOnCoordinator mScreenOnCoordinator;
 
     private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+    private Lazy<ScrimController> mScrimControllerLazy;
 
     /**
      * Injected constructor. See {@link KeyguardModule}.
      */
     public KeyguardViewMediator(
             Context context,
+            UserTracker userTracker,
             FalsingCollector falsingCollector,
             LockPatternUtils lockPatternUtils,
             BroadcastDispatcher broadcastDispatcher,
@@ -1150,9 +1172,12 @@
             ScreenOnCoordinator screenOnCoordinator,
             InteractionJankMonitor interactionJankMonitor,
             DreamOverlayStateController dreamOverlayStateController,
+            Lazy<ShadeController> shadeControllerLazy,
             Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
-            Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
+            Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+            Lazy<ScrimController> scrimControllerLazy) {
         mContext = context;
+        mUserTracker = userTracker;
         mFalsingCollector = falsingCollector;
         mLockPatternUtils = lockPatternUtils;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -1165,6 +1190,7 @@
         mTrustManager = trustManager;
         mUserSwitcherController = userSwitcherController;
         mKeyguardDisplayManager = keyguardDisplayManager;
+        mShadeController = shadeControllerLazy;
         dumpManager.registerDumpable(getClass().getName(), this);
         mDeviceConfig = deviceConfig;
         mScreenOnCoordinator = screenOnCoordinator;
@@ -1194,6 +1220,7 @@
         mDreamOverlayStateController = dreamOverlayStateController;
 
         mActivityLaunchAnimator = activityLaunchAnimator;
+        mScrimControllerLazy = scrimControllerLazy;
 
         mPowerButtonY = context.getResources().getDimensionPixelSize(
                 R.dimen.physical_power_button_center_screen_location_y);
@@ -1201,6 +1228,8 @@
 
         mDreamOpenAnimationDuration = context.getResources().getInteger(
                 com.android.internal.R.integer.config_dreamOpenAnimationDuration);
+        mDreamCloseAnimationDuration = context.getResources().getInteger(
+                com.android.internal.R.integer.config_dreamCloseAnimationDuration);
     }
 
     public void userActivity() {
@@ -1230,7 +1259,7 @@
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
-        KeyguardUpdateMonitor.setCurrentUser(ActivityManager.getCurrentUser());
+        KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId());
 
         // Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard
         // is disabled.
@@ -1725,7 +1754,7 @@
                 try {
                     callback.onKeyguardExitResult(true);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
+                    Slog.w(TAG, "Failed to call onKeyguardExitResult(true)", e);
                 }
             } else {
 
@@ -2180,6 +2209,9 @@
                 case START_KEYGUARD_EXIT_ANIM:
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
+                    synchronized (KeyguardViewMediator.this) {
+                        mHiding = true;
+                    }
                     StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
                     mNotificationShadeWindowControllerLazy.get().batchApplyWindowLayoutParams(
                             () -> {
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..47ef0fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -42,15 +42,19 @@
 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;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -71,6 +75,7 @@
         KeyguardUserSwitcherComponent.class},
         includes = {
             FalsingModule.class,
+            KeyguardDataQuickAffordanceModule.class,
             KeyguardQuickAffordanceModule.class,
             KeyguardRepositoryModule.class,
             StartKeyguardTransitionModule.class,
@@ -83,6 +88,7 @@
     @SysUISingleton
     public static KeyguardViewMediator newKeyguardViewMediator(
             Context context,
+            UserTracker userTracker,
             FalsingCollector falsingCollector,
             LockPatternUtils lockPatternUtils,
             BroadcastDispatcher broadcastDispatcher,
@@ -106,10 +112,13 @@
             ScreenOnCoordinator screenOnCoordinator,
             InteractionJankMonitor interactionJankMonitor,
             DreamOverlayStateController dreamOverlayStateController,
+            Lazy<ShadeController> shadeController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController,
-            Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
+            Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+            Lazy<ScrimController> scrimControllerLazy) {
         return new KeyguardViewMediator(
                 context,
+                userTracker,
                 falsingCollector,
                 lockPatternUtils,
                 broadcastDispatcher,
@@ -133,8 +142,10 @@
                 screenOnCoordinator,
                 interactionJankMonitor,
                 dreamOverlayStateController,
+                shadeController,
                 notificationShadeWindowController,
-                activityLaunchAnimator);
+                activityLaunchAnimator,
+                scrimControllerLazy);
     }
 
     /** */
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/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index a069582..73dbeab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -24,6 +24,8 @@
  */
 object BuiltInKeyguardQuickAffordanceKeys {
     // Please keep alphabetical order of const names to simplify future maintenance.
+    const val CAMERA = "camera"
+    const val FLASHLIGHT = "flashlight"
     const val HOME_CONTROLS = "home"
     const val QR_CODE_SCANNER = "qr_code_scanner"
     const val QUICK_ACCESS_WALLET = "wallet"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
new file mode 100644
index 0000000..dbc376e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.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.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.camera.CameraGestureHelper
+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 dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+@SysUISingleton
+class CameraQuickAffordanceConfig
+@Inject
+constructor(
+    @Application private val context: Context,
+    private val cameraGestureHelper: Lazy<CameraGestureHelper>,
+) : KeyguardQuickAffordanceConfig {
+
+    override val key: String
+        get() = BuiltInKeyguardQuickAffordanceKeys.CAMERA
+
+    override val pickerName: String
+        get() = context.getString(R.string.accessibility_camera_button)
+
+    override val pickerIconResourceId: Int
+        get() = com.android.internal.R.drawable.perm_group_camera
+
+    override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
+        get() =
+            flowOf(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    icon =
+                        Icon.Resource(
+                            com.android.internal.R.drawable.perm_group_camera,
+                            ContentDescription.Resource(R.string.accessibility_camera_button)
+                        )
+                )
+            )
+
+    override fun onTriggered(
+        expandable: Expandable?
+    ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+        cameraGestureHelper
+            .get()
+            .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+        return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
new file mode 100644
index 0000000..62fe80a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.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.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
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+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.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.statusbar.policy.FlashlightController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class FlashlightQuickAffordanceConfig
+@Inject
+constructor(
+    @Application private val context: Context,
+    private val flashlightController: FlashlightController,
+) : KeyguardQuickAffordanceConfig {
+
+    private sealed class FlashlightState {
+
+        abstract fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState
+
+        object On : FlashlightState() {
+            override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    Icon.Resource(
+                        R.drawable.qs_flashlight_icon_on,
+                        ContentDescription.Resource(R.string.quick_settings_flashlight_label)
+                    ),
+                    ActivationState.Active
+                )
+        }
+
+        object OffAvailable : FlashlightState() {
+            override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    Icon.Resource(
+                        R.drawable.qs_flashlight_icon_off,
+                        ContentDescription.Resource(R.string.quick_settings_flashlight_label)
+                    ),
+                    ActivationState.Inactive
+                )
+        }
+
+        object Unavailable : FlashlightState() {
+            override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+                KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+        }
+    }
+
+    override val key: String
+        get() = BuiltInKeyguardQuickAffordanceKeys.FLASHLIGHT
+
+    override val pickerName: String
+        get() = context.getString(R.string.quick_settings_flashlight_label)
+
+    override val pickerIconResourceId: Int
+        get() = R.drawable.ic_flashlight_off
+
+    override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+        conflatedCallbackFlow {
+            val flashlightCallback =
+                object : FlashlightController.FlashlightListener {
+                    override fun onFlashlightChanged(enabled: Boolean) {
+                        trySendWithFailureLogging(
+                            if (enabled) {
+                                FlashlightState.On.toLockScreenState()
+                            } else {
+                                FlashlightState.OffAvailable.toLockScreenState()
+                            },
+                            TAG
+                        )
+                    }
+
+                    override fun onFlashlightError() {
+                        trySendWithFailureLogging(
+                            FlashlightState.OffAvailable.toLockScreenState(),
+                            TAG
+                        )
+                    }
+
+                    override fun onFlashlightAvailabilityChanged(available: Boolean) {
+                        trySendWithFailureLogging(
+                            if (!available) {
+                                FlashlightState.Unavailable.toLockScreenState()
+                            } else {
+                                if (flashlightController.isEnabled) {
+                                    FlashlightState.On.toLockScreenState()
+                                } else {
+                                    FlashlightState.OffAvailable.toLockScreenState()
+                                }
+                            },
+                            TAG
+                        )
+                    }
+                }
+
+            flashlightController.addCallback(flashlightCallback)
+
+            awaitClose { flashlightController.removeCallback(flashlightCallback) }
+        }
+
+    override fun onTriggered(
+        expandable: Expandable?
+    ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+        flashlightController.setFlashlight(
+            flashlightController.isAvailable && !flashlightController.isEnabled
+        )
+        return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+    }
+
+    override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
+        if (flashlightController.isAvailable) {
+            KeyguardQuickAffordanceConfig.PickerScreenState.Default
+        } else {
+            KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+        }
+
+    companion object {
+        private const val TAG = "FlashlightQuickAffordanceConfig"
+    }
+}
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 c600e13..2558fab 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
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.Intent
 import androidx.annotation.DrawableRes
+import com.android.systemui.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -45,7 +46,7 @@
 class HomeControlsKeyguardQuickAffordanceConfig
 @Inject
 constructor(
-    @Application context: Context,
+    @Application private val context: Context,
     private val component: ControlsComponent,
 ) : KeyguardQuickAffordanceConfig {
 
@@ -53,6 +54,10 @@
 
     override val key: String = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
 
+    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) {
@@ -62,6 +67,36 @@
             }
         }
 
+    override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+        if (!component.isEnabled()) {
+            return KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+        }
+
+        val currentServices =
+            component.getControlsListingController().getOrNull()?.getCurrentServices()
+        val hasFavorites =
+            component.getControlsController().getOrNull()?.getFavorites()?.isNotEmpty() == true
+        if (currentServices.isNullOrEmpty() || !hasFavorites) {
+            return KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
+                instructions =
+                    listOf(
+                        context.getString(
+                            R.string.keyguard_affordance_enablement_dialog_message,
+                            pickerName,
+                        ),
+                        context.getString(
+                            R.string.keyguard_affordance_enablement_dialog_home_instruction_1
+                        ),
+                        context.getString(
+                            R.string.keyguard_affordance_enablement_dialog_home_instruction_2
+                        ),
+                    ),
+            )
+        }
+
+        return KeyguardQuickAffordanceConfig.PickerScreenState.Default
+    }
+
     override fun onTriggered(
         expandable: Expandable?,
     ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
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..072cfb1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+
+@Module
+interface KeyguardDataQuickAffordanceModule {
+    @Binds
+    fun providerClientFactory(
+        impl: KeyguardQuickAffordanceProviderClientFactoryImpl,
+    ): KeyguardQuickAffordanceProviderClientFactory
+
+    companion object {
+        @Provides
+        @ElementsIntoSet
+        fun quickAffordanceConfigs(
+            flashlight: FlashlightQuickAffordanceConfig,
+            home: HomeControlsKeyguardQuickAffordanceConfig,
+            quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
+            qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+            camera: CameraQuickAffordanceConfig,
+        ): Set<KeyguardQuickAffordanceConfig> {
+            return setOf(
+                camera,
+                flashlight,
+                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 0a8090b..98b1a73 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
@@ -21,6 +21,7 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
 import kotlinx.coroutines.flow.Flow
 
 /** Defines interface that can act as data source for a single quick affordance model. */
@@ -29,6 +30,10 @@
     /** Unique identifier for this quick affordance. It must be globally unique. */
     val key: String
 
+    val pickerName: String
+
+    val pickerIconResourceId: Int
+
     /**
      * The ever-changing state of the affordance.
      *
@@ -37,6 +42,12 @@
     val lockScreenState: Flow<LockScreenState>
 
     /**
+     * Returns the [PickerScreenState] representing the affordance in the settings or selector
+     * experience.
+     */
+    suspend fun getPickerScreenState(): PickerScreenState = PickerScreenState.Default
+
+    /**
      * Notifies that the affordance was clicked by the user.
      *
      * @param expandable An [Expandable] to use when animating dialogs or activities
@@ -45,6 +56,58 @@
     fun onTriggered(expandable: Expandable?): OnTriggeredResult
 
     /**
+     * Encapsulates the state of a quick affordance within the context of the settings or selector
+     * experience.
+     */
+    sealed class PickerScreenState {
+
+        /** The picker shows the item for selecting this affordance as it normally would. */
+        object Default : PickerScreenState()
+
+        /**
+         * The picker does not show an item for selecting this affordance as it is not supported on
+         * the device at all. For example, missing hardware requirements.
+         */
+        object UnavailableOnDevice : PickerScreenState()
+
+        /**
+         * The picker shows the item for selecting this affordance as disabled. Clicking on it will
+         * show the given instructions to the user. If [actionText] and [actionComponentName] are
+         * provided (optional) a button will be shown to open an activity to help the user complete
+         * the steps described in the instructions.
+         */
+        data class Disabled(
+            /** List of human-readable instructions for setting up the quick affordance. */
+            val instructions: List<String>,
+            /**
+             * Optional text to display on a button that the user can click to start a flow to go
+             * and set up the quick affordance and make it enabled.
+             */
+            val actionText: String? = null,
+            /**
+             * Optional component name to be able to build an `Intent` that opens an `Activity` for
+             * the user to be able to set up the quick affordance and make it enabled.
+             *
+             * This is either just an action for the `Intent` or a package name and action,
+             * separated by [Contract.AffordanceTable.COMPONENT_NAME_SEPARATOR] for convenience, you
+             * can use the [componentName] function.
+             */
+            val actionComponentName: String? = null,
+        ) : PickerScreenState() {
+            init {
+                check(instructions.isNotEmpty()) { "Instructions must not be empty!" }
+                check(
+                    (actionText.isNullOrEmpty() && actionComponentName.isNullOrEmpty()) ||
+                        (!actionText.isNullOrEmpty() && !actionComponentName.isNullOrEmpty())
+                ) {
+                    "actionText and actionComponentName must either both be null/empty or both be" +
+                        " non-empty!"
+                }
+            }
+        }
+    }
+
+    /**
      * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
      * button on the lock-screen).
      */
@@ -79,4 +142,18 @@
             val canShowWhileLocked: Boolean,
         ) : OnTriggeredResult()
     }
+
+    companion object {
+        fun componentName(
+            packageName: String? = null,
+            action: String?,
+        ): String? {
+            return when {
+                action.isNullOrEmpty() -> null
+                !packageName.isNullOrEmpty() ->
+                    "$packageName${Contract.AffordanceTable.COMPONENT_NAME_SEPARATOR}$action"
+                else -> action
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt
new file mode 100644
index 0000000..72747f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt
@@ -0,0 +1,214 @@
+/*
+ * 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 android.os.UserHandle
+import android.provider.Settings
+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.KeyguardQuickAffordanceLegacySettingSyncer.Companion.BINDINGS
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Keeps quick affordance selections and legacy user settings in sync.
+ *
+ * "Legacy user settings" are user settings like: Settings > Display > Lock screen > "Show device
+ * controls" Settings > Display > Lock screen > "Show wallet"
+ *
+ * Quick affordance selections are the ones available through the new custom lock screen experience
+ * from Settings > Wallpaper & Style.
+ *
+ * This class keeps these in sync, mostly for backwards compatibility purposes and in order to not
+ * "forget" an existing legacy user setting when the device gets updated with a version of System UI
+ * that has the new customizable lock screen feature.
+ *
+ * The way it works is that, when [startSyncing] is called, the syncer starts coroutines to listen
+ * for changes in both legacy user settings and their respective affordance selections. Whenever one
+ * of each pair is changed, the other member of that pair is also updated to match. For example, if
+ * the user turns on "Show device controls", we automatically select the home controls affordance
+ * for the preferred slot. Conversely, when the home controls affordance is unselected by the user,
+ * we set the "Show device controls" setting to "off".
+ *
+ * The class can be configured by updating its list of triplets in the code under [BINDINGS].
+ */
+@SysUISingleton
+class KeyguardQuickAffordanceLegacySettingSyncer
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val secureSettings: SecureSettings,
+    private val selectionsManager: KeyguardQuickAffordanceLocalUserSelectionManager,
+) {
+    companion object {
+        private val BINDINGS =
+            listOf(
+                Binding(
+                    settingsKey = Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+                    slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                    affordanceId = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+                ),
+                Binding(
+                    settingsKey = Settings.Secure.LOCKSCREEN_SHOW_WALLET,
+                    slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                    affordanceId = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET,
+                ),
+                Binding(
+                    settingsKey = Settings.Secure.LOCK_SCREEN_SHOW_QR_CODE_SCANNER,
+                    slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                    affordanceId = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER,
+                ),
+            )
+    }
+
+    fun startSyncing(
+        bindings: List<Binding> = BINDINGS,
+    ): Job {
+        return scope.launch { bindings.forEach { binding -> startSyncing(this, binding) } }
+    }
+
+    private fun startSyncing(
+        scope: CoroutineScope,
+        binding: Binding,
+    ) {
+        secureSettings
+            .observerFlow(
+                names = arrayOf(binding.settingsKey),
+                userId = UserHandle.USER_ALL,
+            )
+            .map {
+                isSet(
+                    settingsKey = binding.settingsKey,
+                )
+            }
+            .distinctUntilChanged()
+            .onEach { isSet ->
+                if (isSelected(binding.affordanceId) != isSet) {
+                    if (isSet) {
+                        select(
+                            slotId = binding.slotId,
+                            affordanceId = binding.affordanceId,
+                        )
+                    } else {
+                        unselect(
+                            affordanceId = binding.affordanceId,
+                        )
+                    }
+                }
+            }
+            .flowOn(backgroundDispatcher)
+            .launchIn(scope)
+
+        selectionsManager.selections
+            .map { it.values.flatten().toSet() }
+            .map { it.contains(binding.affordanceId) }
+            .distinctUntilChanged()
+            .onEach { isSelected ->
+                if (isSet(binding.settingsKey) != isSelected) {
+                    set(binding.settingsKey, isSelected)
+                }
+            }
+            .flowOn(backgroundDispatcher)
+            .launchIn(scope)
+    }
+
+    private fun isSelected(
+        affordanceId: String,
+    ): Boolean {
+        return selectionsManager
+            .getSelections() // Map<String, List<String>>
+            .values // Collection<List<String>>
+            .flatten() // List<String>
+            .toSet() // Set<String>
+            .contains(affordanceId)
+    }
+
+    private fun select(
+        slotId: String,
+        affordanceId: String,
+    ) {
+        val affordanceIdsAtSlotId = selectionsManager.getSelections()[slotId] ?: emptyList()
+        selectionsManager.setSelections(
+            slotId = slotId,
+            affordanceIds = affordanceIdsAtSlotId + listOf(affordanceId),
+        )
+    }
+
+    private fun unselect(
+        affordanceId: String,
+    ) {
+        val currentSelections = selectionsManager.getSelections()
+        val slotIdsContainingAffordanceId =
+            currentSelections
+                .filter { (_, affordanceIds) -> affordanceIds.contains(affordanceId) }
+                .map { (slotId, _) -> slotId }
+
+        slotIdsContainingAffordanceId.forEach { slotId ->
+            val currentAffordanceIds = currentSelections[slotId] ?: emptyList()
+            val affordanceIdsAfterUnselecting =
+                currentAffordanceIds.toMutableList().apply { remove(affordanceId) }
+
+            selectionsManager.setSelections(
+                slotId = slotId,
+                affordanceIds = affordanceIdsAfterUnselecting,
+            )
+        }
+    }
+
+    private fun isSet(
+        settingsKey: String,
+    ): Boolean {
+        return secureSettings.getIntForUser(
+            settingsKey,
+            0,
+            UserHandle.USER_CURRENT,
+        ) != 0
+    }
+
+    private suspend fun set(
+        settingsKey: String,
+        isSet: Boolean,
+    ) {
+        withContext(backgroundDispatcher) {
+            secureSettings.putInt(
+                settingsKey,
+                if (isSet) 1 else 0,
+            )
+        }
+    }
+
+    data class Binding(
+        val settingsKey: String,
+        val slotId: String,
+        val affordanceId: String,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
new file mode 100644
index 0000000..0066785
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
@@ -0,0 +1,184 @@
+/*
+ * 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 android.content.Context
+import android.content.IntentFilter
+import android.content.SharedPreferences
+import com.android.systemui.R
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * Manages and provides access to the current "selections" of keyguard quick affordances, answering
+ * the question "which affordances should the keyguard show?" for the user associated with the
+ * System UI process.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardQuickAffordanceLocalUserSelectionManager
+@Inject
+constructor(
+    @Application context: Context,
+    private val userFileManager: UserFileManager,
+    private val userTracker: UserTracker,
+    broadcastDispatcher: BroadcastDispatcher,
+) : KeyguardQuickAffordanceSelectionManager {
+
+    private var sharedPrefs: SharedPreferences = instantiateSharedPrefs()
+
+    private val userId: Flow<Int> = conflatedCallbackFlow {
+        val callback =
+            object : UserTracker.Callback {
+                override fun onUserChanged(newUser: Int, userContext: Context) {
+                    trySendWithFailureLogging(newUser, TAG)
+                }
+            }
+
+        userTracker.addCallback(callback) { it.run() }
+        trySendWithFailureLogging(userTracker.userId, TAG)
+
+        awaitClose { userTracker.removeCallback(callback) }
+    }
+
+    private val defaults: Map<String, List<String>> by lazy {
+        context.resources
+            .getStringArray(R.array.config_keyguardQuickAffordanceDefaults)
+            .associate { item ->
+                val splitUp = item.split(SLOT_AFFORDANCES_DELIMITER)
+                check(splitUp.size == 2)
+                val slotId = splitUp[0]
+                val affordanceIds = splitUp[1].split(AFFORDANCE_DELIMITER)
+                slotId to affordanceIds
+            }
+    }
+
+    /**
+     * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an
+     * initial value.
+     */
+    private val backupRestorationEvents: Flow<Unit> =
+        broadcastDispatcher.broadcastFlow(
+            filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
+            flags = Context.RECEIVER_NOT_EXPORTED,
+            permission = BackupHelper.PERMISSION_SELF,
+        )
+
+    override val selections: Flow<Map<String, List<String>>> =
+        combine(
+                userId,
+                backupRestorationEvents.onStart {
+                    // We emit an initial event to make sure that the combine emits at least once,
+                    // even if we never get a Backup & Restore restoration event (which is the most
+                    // common case anyway as restoration really only happens on initial device
+                    // setup).
+                    emit(Unit)
+                }
+            ) { _, _ -> }
+            .flatMapLatest {
+                conflatedCallbackFlow {
+                    // We want to instantiate a new SharedPreferences instance each time either the
+                    // user ID changes or we have a backup & restore restoration event. The reason
+                    // is that our sharedPrefs instance needs to be replaced with a new one as it
+                    // depends on the user ID and when the B&R job completes, the backing file is
+                    // replaced but the existing instance still has a stale in-memory cache.
+                    sharedPrefs = instantiateSharedPrefs()
+
+                    val listener =
+                        SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
+                            trySend(getSelections())
+                        }
+
+                    sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+                    send(getSelections())
+
+                    awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+                }
+            }
+
+    override fun getSelections(): Map<String, List<String>> {
+        val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) }
+        val result =
+            slotKeys
+                .associate { key ->
+                    val slotId = key.substring(KEY_PREFIX_SLOT.length)
+                    val value = sharedPrefs.getString(key, null)
+                    val affordanceIds =
+                        if (!value.isNullOrEmpty()) {
+                            value.split(AFFORDANCE_DELIMITER)
+                        } else {
+                            emptyList()
+                        }
+                    slotId to affordanceIds
+                }
+                .toMutableMap()
+
+        // If the result map is missing keys, it means that the system has never set anything for
+        // those slots. This is where we need examine our defaults and see if there should be a
+        // default value for the affordances in the slot IDs that are missing from the result.
+        //
+        // Once the user makes any selection for a slot, even when they select "None", this class
+        // will persist a key for that slot ID. In the case of "None", it will have a value of the
+        // empty string. This is why this system works.
+        defaults.forEach { (slotId, affordanceIds) ->
+            if (!result.containsKey(slotId)) {
+                result[slotId] = affordanceIds
+            }
+        }
+
+        return result
+    }
+
+    override fun setSelections(
+        slotId: String,
+        affordanceIds: List<String>,
+    ) {
+        val key = "$KEY_PREFIX_SLOT$slotId"
+        val value = affordanceIds.joinToString(AFFORDANCE_DELIMITER)
+        sharedPrefs.edit().putString(key, value).apply()
+    }
+
+    private fun instantiateSharedPrefs(): SharedPreferences {
+        return userFileManager.getSharedPreferences(
+            FILE_NAME,
+            Context.MODE_PRIVATE,
+            userTracker.userId,
+        )
+    }
+
+    companion object {
+        private const val TAG = "KeyguardQuickAffordancePrimaryUserSelectionManager"
+        const val FILE_NAME = "quick_affordance_selections"
+        private const val KEY_PREFIX_SLOT = "slot_"
+        private const val SLOT_AFFORDANCES_DELIMITER = ":"
+        private const val AFFORDANCE_DELIMITER = ","
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt
new file mode 100644
index 0000000..727a813
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt
@@ -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.systemui.keyguard.data.quickaffordance
+
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClientImpl
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+
+interface KeyguardQuickAffordanceProviderClientFactory {
+    fun create(): KeyguardQuickAffordanceProviderClient
+}
+
+class KeyguardQuickAffordanceProviderClientFactoryImpl
+@Inject
+constructor(
+    private val userTracker: UserTracker,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : KeyguardQuickAffordanceProviderClientFactory {
+    override fun create(): KeyguardQuickAffordanceProviderClient {
+        return KeyguardQuickAffordanceProviderClientImpl(
+            context = userTracker.userContext,
+            backgroundDispatcher = backgroundDispatcher,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt
new file mode 100644
index 0000000..8ffef25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.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.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient
+import javax.inject.Inject
+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.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/**
+ * Manages and provides access to the current "selections" of keyguard quick affordances, answering
+ * the question "which affordances should the keyguard show?" for users associated with other System
+ * UI processes.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardQuickAffordanceRemoteUserSelectionManager
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val userTracker: UserTracker,
+    private val clientFactory: KeyguardQuickAffordanceProviderClientFactory,
+    private val userHandle: UserHandle,
+) : KeyguardQuickAffordanceSelectionManager {
+
+    private val userId: Flow<Int> = conflatedCallbackFlow {
+        val callback =
+            object : UserTracker.Callback {
+                override fun onUserChanged(newUser: Int, userContext: Context) {
+                    trySendWithFailureLogging(newUser, TAG)
+                }
+            }
+
+        userTracker.addCallback(callback) { it.run() }
+        trySendWithFailureLogging(userTracker.userId, TAG)
+
+        awaitClose { userTracker.removeCallback(callback) }
+    }
+
+    private val clientOrNull: StateFlow<KeyguardQuickAffordanceProviderClient?> =
+        userId
+            .distinctUntilChanged()
+            .map { selectedUserId ->
+                if (userHandle.isSystem && userHandle.identifier != selectedUserId) {
+                    clientFactory.create()
+                } else {
+                    null
+                }
+            }
+            .stateIn(
+                scope = scope,
+                started = SharingStarted.Eagerly,
+                initialValue = null,
+            )
+
+    private val _selections: StateFlow<Map<String, List<String>>> =
+        clientOrNull
+            .flatMapLatest { client ->
+                client?.observeSelections()?.map { selections ->
+                    buildMap<String, List<String>> {
+                        selections.forEach { selection ->
+                            val slotId = selection.slotId
+                            val affordanceIds = (get(slotId) ?: emptyList()).toMutableList()
+                            affordanceIds.add(selection.affordanceId)
+                            put(slotId, affordanceIds)
+                        }
+                    }
+                }
+                    ?: emptyFlow()
+            }
+            .stateIn(
+                scope = scope,
+                started = SharingStarted.Eagerly,
+                initialValue = emptyMap(),
+            )
+
+    override val selections: Flow<Map<String, List<String>>> = _selections
+
+    override fun getSelections(): Map<String, List<String>> {
+        return _selections.value
+    }
+
+    override fun setSelections(slotId: String, affordanceIds: List<String>) {
+        clientOrNull.value?.let { client ->
+            scope.launch {
+                client.deleteAllSelections(slotId = slotId)
+                affordanceIds.forEach { affordanceId ->
+                    client.insertSelection(slotId = slotId, affordanceId = affordanceId)
+                }
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "KeyguardQuickAffordanceMultiUserSelectionManager"
+    }
+}
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..21fffed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.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.keyguard.data.quickaffordance
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Defines interface for classes that manage and provide access to the current "selections" of
+ * keyguard quick affordances, answering the question "which affordances should the keyguard show?".
+ */
+interface KeyguardQuickAffordanceSelectionManager {
+
+    /** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
+    val selections: Flow<Map<String, List<String>>>
+
+    /**
+     * Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
+     * descending priority order.
+     */
+    fun getSelections(): Map<String, List<String>>
+
+    /**
+     * 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>,
+    )
+
+    companion object {
+        const val FILE_NAME = "quick_affordance_selections"
+    }
+}
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 d620b2a..a96ce77 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,11 +36,16 @@
 class QrCodeScannerKeyguardQuickAffordanceConfig
 @Inject
 constructor(
+    @Application private val context: Context,
     private val controller: QRCodeScannerController,
 ) : KeyguardQuickAffordanceConfig {
 
     override val key: String = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
 
+    override val pickerName = context.getString(R.string.qr_code_scanner_title)
+
+    override val pickerIconResourceId = R.drawable.ic_qr_code_scanner
+
     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
         conflatedCallbackFlow {
             val callback =
@@ -68,6 +75,28 @@
             }
         }
 
+    override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+        return when {
+            !controller.isAvailableOnDevice ->
+                KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+            !controller.isAbleToOpenCameraApp ->
+                KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
+                    instructions =
+                        listOf(
+                            context.getString(
+                                R.string.keyguard_affordance_enablement_dialog_message,
+                                pickerName,
+                            ),
+                            context.getString(
+                                R.string
+                                    .keyguard_affordance_enablement_dialog_qr_scanner_instruction
+                            ),
+                        ),
+                )
+            else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default
+        }
+    }
+
     override fun onTriggered(
         expandable: Expandable?,
     ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
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 be57a32..beb20ce 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,10 +17,13 @@
 
 package com.android.systemui.keyguard.data.quickaffordance
 
+import android.content.Context
+import android.content.Intent
 import android.graphics.drawable.Drawable
 import android.service.quickaccesswallet.GetWalletCardsError
 import android.service.quickaccesswallet.GetWalletCardsResponse
 import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.service.quickaccesswallet.WalletCard
 import android.util.Log
 import com.android.systemui.R
 import com.android.systemui.animation.Expandable
@@ -29,32 +32,41 @@
 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.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.Companion.componentName
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.wallet.controller.QuickAccessWalletController
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.suspendCancellableCoroutine
 
 /** Quick access wallet quick affordance data source. */
 @SysUISingleton
 class QuickAccessWalletKeyguardQuickAffordanceConfig
 @Inject
 constructor(
+    @Application private val context: Context,
     private val walletController: QuickAccessWalletController,
     private val activityStarter: ActivityStarter,
 ) : KeyguardQuickAffordanceConfig {
 
     override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
 
+    override val pickerName: String = 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?) {
+                        val hasCards = response?.walletCards?.isNotEmpty() == true
                         trySendWithFailureLogging(
                             state(
                                 isFeatureEnabled = walletController.isWalletEnabled,
-                                hasCard = response?.walletCards?.isNotEmpty() == true,
+                                hasCard = hasCards,
                                 tileIcon = walletController.walletClient.tileIcon,
                             ),
                             TAG,
@@ -86,6 +98,44 @@
             }
         }
 
+    override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+        return when {
+            !walletController.isWalletEnabled ->
+                KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+            walletController.walletClient.tileIcon == null || queryCards().isEmpty() -> {
+                val componentName =
+                    walletController.walletClient.createWalletSettingsIntent().toComponentName()
+                val actionText =
+                    if (componentName != null) {
+                        context.getString(
+                            R.string.keyguard_affordance_enablement_dialog_action_template,
+                            pickerName,
+                        )
+                    } else {
+                        null
+                    }
+                KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
+                    instructions =
+                        listOf(
+                            context.getString(
+                                R.string.keyguard_affordance_enablement_dialog_message,
+                                pickerName,
+                            ),
+                            context.getString(
+                                R.string.keyguard_affordance_enablement_dialog_wallet_instruction_1
+                            ),
+                            context.getString(
+                                R.string.keyguard_affordance_enablement_dialog_wallet_instruction_2
+                            ),
+                        ),
+                    actionText = actionText,
+                    actionComponentName = componentName,
+                )
+            }
+            else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default
+        }
+    }
+
     override fun onTriggered(
         expandable: Expandable?,
     ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
@@ -97,6 +147,24 @@
         return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
     }
 
+    private suspend fun queryCards(): List<WalletCard> {
+        return suspendCancellableCoroutine { continuation ->
+            val callback =
+                object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+                        continuation.resumeWith(
+                            Result.success(response?.walletCards ?: emptyList())
+                        )
+                    }
+
+                    override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+                        continuation.resumeWith(Result.success(emptyList()))
+                    }
+                }
+            walletController.queryWalletCards(callback)
+        }
+    }
+
     private fun state(
         isFeatureEnabled: Boolean,
         hasCard: Boolean,
@@ -118,6 +186,14 @@
         }
     }
 
+    private fun Intent?.toComponentName(): String? {
+        if (this == null) {
+            return null
+        }
+
+        return componentName(packageName = `package`, action = action)
+    }
+
     companion object {
         private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
     }
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..783f752 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
@@ -16,20 +16,17 @@
 
 package com.android.systemui.keyguard.data.repository
 
-import android.hardware.biometrics.BiometricSourceType
 import com.android.keyguard.KeyguardUpdateMonitor
-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
 
-/** Encapsulates app state for the lock screen bouncer. */
+/** Encapsulates app state for the lock screen primary and alternate bouncer. */
 @SysUISingleton
 class KeyguardBouncerRepository
 @Inject
@@ -37,95 +34,79 @@
     private val viewMediatorCallback: ViewMediatorCallback,
     keyguardUpdateMonitor: KeyguardUpdateMonitor,
 ) {
-    var bouncerPromptReason: Int? = null
-    /** 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()
-    private val _isVisible = MutableStateFlow(false)
-    val isVisible = _isVisible.asStateFlow()
-    private val _show = MutableStateFlow<KeyguardBouncerModel?>(null)
-    val show = _show.asStateFlow()
-    private val _showingSoon = MutableStateFlow(false)
-    val showingSoon = _showingSoon.asStateFlow()
-    private val _hide = MutableStateFlow(false)
-    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()
+    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
+    private val _primaryBouncerVisible = MutableStateFlow(false)
+    val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
+    private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
+    val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
+    val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+    private val _primaryBouncerHide = MutableStateFlow(false)
+    val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
+    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
+    val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
+    val primaryBouncerStartingDisappearAnimation = _primaryBouncerDisappearAnimation.asStateFlow()
+    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
+    private val _primaryBouncerScrimmed = MutableStateFlow(false)
+    val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+    /**
+     * Set how much of the notification 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 _keyguardPosition = MutableStateFlow(0f)
     val keyguardPosition = _keyguardPosition.asStateFlow()
-    private val _resourceUpdateRequests = MutableStateFlow(false)
-    val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
-    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
-    val showMessage = _showMessage.asStateFlow()
+    private val _onScreenTurnedOff = MutableStateFlow(false)
+    val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
+    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
+    val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
     private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
     /** Determines if user is already unlocked */
     val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
-    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
-    val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
-    private val _onScreenTurnedOff = MutableStateFlow(false)
-    val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
-
+    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
+    val showMessage = _showMessage.asStateFlow()
+    private val _resourceUpdateRequests = MutableStateFlow(false)
+    val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+    val bouncerPromptReason: Int
+        get() = viewMediatorCallback.bouncerPromptReason
     val bouncerErrorMessage: CharSequence?
         get() = viewMediatorCallback.consumeCustomMessage()
 
-    init {
-        val callback =
-            object : KeyguardUpdateMonitorCallback() {
-                override fun onStrongAuthStateChanged(userId: Int) {
-                    bouncerPromptReason = viewMediatorCallback.bouncerPromptReason
-                }
-
-                override fun onLockedOutStateChanged(type: BiometricSourceType) {
-                    if (type == BiometricSourceType.FINGERPRINT) {
-                        bouncerPromptReason = viewMediatorCallback.bouncerPromptReason
-                    }
-                }
-            }
-
-        keyguardUpdateMonitor.registerCallback(callback)
+    fun setPrimaryScrimmed(isScrimmed: Boolean) {
+        _primaryBouncerScrimmed.value = isScrimmed
     }
 
-    fun setScrimmed(isScrimmed: Boolean) {
-        _isScrimmed.value = isScrimmed
+    fun setPrimaryVisible(isVisible: Boolean) {
+        _primaryBouncerVisible.value = isVisible
     }
 
-    fun setExpansion(expansion: Float) {
-        _expansionAmount.value = expansion
+    fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
+        _primaryBouncerShow.value = keyguardBouncerModel
     }
 
-    fun setVisible(isVisible: Boolean) {
-        _isVisible.value = isVisible
+    fun setPrimaryShowingSoon(showingSoon: Boolean) {
+        _primaryBouncerShowingSoon.value = showingSoon
     }
 
-    fun setShow(keyguardBouncerModel: KeyguardBouncerModel?) {
-        _show.value = keyguardBouncerModel
+    fun setPrimaryHide(hide: Boolean) {
+        _primaryBouncerHide.value = hide
     }
 
-    fun setShowingSoon(showingSoon: Boolean) {
-        _showingSoon.value = showingSoon
+    fun setPrimaryStartingToHide(startingToHide: Boolean) {
+        _primaryBouncerStartingToHide.value = startingToHide
     }
 
-    fun setHide(hide: Boolean) {
-        _hide.value = hide
+    fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+        _primaryBouncerDisappearAnimation.value = runnable
     }
 
-    fun setStartingToHide(startingToHide: Boolean) {
-        _startingToHide.value = startingToHide
-    }
-
-    fun setOnDismissAction(bouncerCallbackActionsModel: BouncerCallbackActionsModel?) {
-        _onDismissAction.value = bouncerCallbackActionsModel
-    }
-
-    fun setStartDisappearAnimation(runnable: Runnable?) {
-        _disappearAnimation.value = runnable
+    fun setPanelExpansion(panelExpansion: Float) {
+        _panelExpansionAmount.value = panelExpansion
     }
 
     fun setKeyguardPosition(keyguardPosition: Float) {
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..e3f5e90
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -0,0 +1,242 @@
+/*
+ * 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.content.Context
+import android.os.UserHandle
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
+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.settings.UserTracker
+import java.io.PrintWriter
+import javax.inject.Inject
+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.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Abstracts access to application state related to keyguard quick affordances. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardQuickAffordanceRepository
+@Inject
+constructor(
+    @Application private val appContext: Context,
+    @Application private val scope: CoroutineScope,
+    private val localUserSelectionManager: KeyguardQuickAffordanceLocalUserSelectionManager,
+    private val remoteUserSelectionManager: KeyguardQuickAffordanceRemoteUserSelectionManager,
+    private val userTracker: UserTracker,
+    legacySettingSyncer: KeyguardQuickAffordanceLegacySettingSyncer,
+    private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
+    dumpManager: DumpManager,
+    userHandle: UserHandle,
+) {
+    private val userId: Flow<Int> =
+        ConflatedCallbackFlow.conflatedCallbackFlow {
+            val callback =
+                object : UserTracker.Callback {
+                    override fun onUserChanged(newUser: Int, userContext: Context) {
+                        trySendWithFailureLogging(newUser, TAG)
+                    }
+                }
+
+            userTracker.addCallback(callback) { it.run() }
+            trySendWithFailureLogging(userTracker.userId, TAG)
+
+            awaitClose { userTracker.removeCallback(callback) }
+        }
+
+    private val selectionManager: StateFlow<KeyguardQuickAffordanceSelectionManager> =
+        userId
+            .distinctUntilChanged()
+            .map { selectedUserId ->
+                if (userHandle.identifier == selectedUserId) {
+                    localUserSelectionManager
+                } else {
+                    remoteUserSelectionManager
+                }
+            }
+            .stateIn(
+                scope = scope,
+                started = SharingStarted.Eagerly,
+                initialValue = localUserSelectionManager,
+            )
+
+    /**
+     * 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
+            .flatMapLatest { selectionManager ->
+                selectionManager.selections.map { selectionsBySlotId ->
+                    selectionsBySlotId.mapValues { (_, selections) ->
+                        configs.filter { selections.contains(it.key) }
+                    }
+                }
+            }
+            .stateIn(
+                scope = scope,
+                started = SharingStarted.Eagerly,
+                initialValue = emptyMap(),
+            )
+
+    private val _slotPickerRepresentations: List<KeyguardSlotPickerRepresentation> by lazy {
+        fun parseSlot(unparsedSlot: String): Pair<String, Int> {
+            val split = unparsedSlot.split(SLOT_CONFIG_DELIMITER)
+            check(split.size == 2)
+            val slotId = split[0]
+            val slotCapacity = split[1].toInt()
+            return slotId to slotCapacity
+        }
+
+        val unparsedSlots =
+            appContext.resources.getStringArray(R.array.config_keyguardQuickAffordanceSlots)
+
+        val seenSlotIds = mutableSetOf<String>()
+        unparsedSlots.mapNotNull { unparsedSlot ->
+            val (slotId, slotCapacity) = parseSlot(unparsedSlot)
+            check(!seenSlotIds.contains(slotId)) { "Duplicate slot \"$slotId\"!" }
+            seenSlotIds.add(slotId)
+            KeyguardSlotPickerRepresentation(
+                id = slotId,
+                maxSelectedAffordances = slotCapacity,
+            )
+        }
+    }
+
+    init {
+        legacySettingSyncer.startSyncing()
+        dumpManager.registerDumpable("KeyguardQuickAffordances", Dumpster())
+    }
+
+    /**
+     * 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.
+     */
+    fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
+        val selections = selectionManager.value.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.
+     */
+    fun getSelections(): Map<String, List<String>> {
+        return selectionManager.value.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>,
+    ) {
+        selectionManager.value.setSelections(
+            slotId = slotId,
+            affordanceIds = affordanceIds,
+        )
+    }
+
+    /**
+     * Returns the list of representation objects for all known, device-available 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.
+     */
+    suspend fun getAffordancePickerRepresentations():
+        List<KeyguardQuickAffordancePickerRepresentation> {
+        return configs
+            .associateWith { config -> config.getPickerScreenState() }
+            .filterNot { (_, pickerState) ->
+                pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+            }
+            .map { (config, pickerState) ->
+                val disabledPickerState =
+                    pickerState as? KeyguardQuickAffordanceConfig.PickerScreenState.Disabled
+                KeyguardQuickAffordancePickerRepresentation(
+                    id = config.key,
+                    name = config.pickerName,
+                    iconResourceId = config.pickerIconResourceId,
+                    isEnabled =
+                        pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.Default,
+                    instructions = disabledPickerState?.instructions,
+                    actionText = disabledPickerState?.actionText,
+                    actionComponentName = disabledPickerState?.actionComponentName,
+                )
+            }
+    }
+
+    /**
+     * 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> {
+        return _slotPickerRepresentations
+    }
+
+    private inner class Dumpster : Dumpable {
+        override fun dump(pw: PrintWriter, args: Array<out String>) {
+            val slotPickerRepresentations = getSlotPickerRepresentations()
+            val selectionsBySlotId = getSelections()
+            pw.println("Slots & selections:")
+            slotPickerRepresentations.forEach { slotPickerRepresentation ->
+                val slotId = slotPickerRepresentation.id
+                val capacity = slotPickerRepresentation.maxSelectedAffordances
+                val affordanceIds = selectionsBySlotId[slotId]
+
+                val selectionText =
+                    if (!affordanceIds.isNullOrEmpty()) {
+                        ": ${affordanceIds.joinToString(", ")}"
+                    } else {
+                        " is empty"
+                    }
+
+                pw.println("    $slotId$selectionText (capacity = $capacity)")
+            }
+            pw.println("Available affordances on device:")
+            configs.forEach { config -> pw.println("    ${config.key} (\"${config.pickerName}\")") }
+        }
+    }
+
+    companion object {
+        private const val TAG = "KeyguardQuickAffordanceRepository"
+        private const val SLOT_CONFIG_DELIMITER = ":"
+    }
+}
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..148792b 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
@@ -16,13 +16,29 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import android.graphics.Point
+import android.hardware.biometrics.BiometricSourceType
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.biometrics.AuthController
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.doze.DozeHost
+import com.android.systemui.doze.DozeMachine
+import com.android.systemui.doze.DozeTransitionCallback
+import com.android.systemui.doze.DozeTransitionListener
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 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 +78,12 @@
      */
     val isKeyguardShowing: Flow<Boolean>
 
+    /** Observable for the signal that keyguard is about to go away. */
+    val isKeyguardGoingAway: Flow<Boolean>
+
+    /** Observable for whether the bouncer is showing. */
+    val isBouncerShowing: Flow<Boolean>
+
     /**
      * Observable for whether we are in doze state.
      *
@@ -69,12 +91,20 @@
      * enter to conserve battery when the device is locked and inactive.
      *
      * Note that it is possible for the system to be transitioning into doze while this flow still
-     * returns `false`. In order to account for that, observers should also use the [dozeAmount]
-     * flow to check if it's greater than `0`
+     * returns `false`. In order to account for that, observers should also use the
+     * [linearDozeAmount] flow to check if it's greater than `0`
      */
     val isDozing: Flow<Boolean>
 
     /**
+     * Observable for whether the device is dreaming.
+     *
+     * Dozing/AOD is a specific type of dream, but it is also possible for other non-systemui dreams
+     * to be active, such as screensavers.
+     */
+    val isDreaming: Flow<Boolean>
+
+    /**
      * Observable for the amount of doze we are currently in.
      *
      * While in doze state, this amount can change - driving a cycle of animations designed to avoid
@@ -84,11 +114,29 @@
      * happens during an animation/transition into doze mode. An observer would be wise to account
      * for both flows if needed.
      */
-    val dozeAmount: Flow<Float>
+    val linearDozeAmount: Flow<Float>
+
+    /** Doze state information, as it transitions */
+    val dozeTransitionModel: Flow<DozeTransitionModel>
 
     /** Observable for the [StatusBarState] */
     val statusBarState: Flow<StatusBarState>
 
+    /** Observable for device wake/sleep state */
+    val wakefulness: Flow<WakefulnessModel>
+
+    /** Observable for biometric unlock modes */
+    val biometricUnlockState: Flow<BiometricUnlockModel>
+
+    /** Approximate location on the screen of the fingerprint sensor. */
+    val fingerprintSensorLocation: Flow<Point?>
+
+    /** Approximate location on the screen of the face unlock sensor/front facing camera. */
+    val faceSensorLocation: Flow<Point?>
+
+    /** Source of the most recent biometric unlock, such as fingerprint or face. */
+    val biometricUnlockSource: Flow<BiometricUnlockSource?>
+
     /**
      * Returns `true` if the keyguard is showing; `false` otherwise.
      *
@@ -108,6 +156,11 @@
      * Sets the relative offset of the lock-screen clock from its natural position on the screen.
      */
     fun setClockPosition(x: Int, y: Int)
+
+    /**
+     * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
+     */
+    fun isUdfpsSupported(): Boolean
 }
 
 /** Encapsulates application state for the keyguard. */
@@ -116,8 +169,13 @@
 @Inject
 constructor(
     statusBarStateController: StatusBarStateController,
-    private val keyguardStateController: KeyguardStateController,
     dozeHost: DozeHost,
+    wakefulnessLifecycle: WakefulnessLifecycle,
+    biometricUnlockController: BiometricUnlockController,
+    private val keyguardStateController: KeyguardStateController,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val dozeTransitionListener: DozeTransitionListener,
+    private val authController: AuthController,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
@@ -152,6 +210,52 @@
         awaitClose { keyguardStateController.removeCallback(callback) }
     }
 
+    override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
+        val callback =
+            object : KeyguardStateController.Callback {
+                override fun onKeyguardGoingAwayChanged() {
+                    trySendWithFailureLogging(
+                        keyguardStateController.isKeyguardGoingAway,
+                        TAG,
+                        "updated isKeyguardGoingAway"
+                    )
+                }
+            }
+
+        keyguardStateController.addCallback(callback)
+        // Adding the callback does not send an initial update.
+        trySendWithFailureLogging(
+            keyguardStateController.isKeyguardGoingAway,
+            TAG,
+            "initial isKeyguardGoingAway"
+        )
+
+        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 =
@@ -171,11 +275,30 @@
             }
             .distinctUntilChanged()
 
-    override val dozeAmount: Flow<Float> = conflatedCallbackFlow {
+    override val isDreaming: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : KeyguardUpdateMonitorCallback() {
+                        override fun onDreamingStateChanged(isDreaming: Boolean) {
+                            trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
+                        }
+                    }
+                keyguardUpdateMonitor.registerCallback(callback)
+                trySendWithFailureLogging(
+                    keyguardUpdateMonitor.isDreaming,
+                    TAG,
+                    "initial isDreaming",
+                )
+
+                awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+            }
+            .distinctUntilChanged()
+
+    override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
         val callback =
             object : StatusBarStateController.StateListener {
                 override fun onDozeAmountChanged(linear: Float, eased: Float) {
-                    trySendWithFailureLogging(eased, TAG, "updated dozeAmount")
+                    trySendWithFailureLogging(linear, TAG, "updated dozeAmount")
                 }
             }
 
@@ -185,6 +308,37 @@
         awaitClose { statusBarStateController.removeCallback(callback) }
     }
 
+    override val dozeTransitionModel: Flow<DozeTransitionModel> = conflatedCallbackFlow {
+        val callback =
+            object : DozeTransitionCallback {
+                override fun onDozeTransition(
+                    oldState: DozeMachine.State,
+                    newState: DozeMachine.State
+                ) {
+                    trySendWithFailureLogging(
+                        DozeTransitionModel(
+                            from = dozeMachineStateToModel(oldState),
+                            to = dozeMachineStateToModel(newState),
+                        ),
+                        TAG,
+                        "doze transition model"
+                    )
+                }
+            }
+
+        dozeTransitionListener.addCallback(callback)
+        trySendWithFailureLogging(
+            DozeTransitionModel(
+                from = dozeMachineStateToModel(dozeTransitionListener.oldState),
+                to = dozeMachineStateToModel(dozeTransitionListener.newState),
+            ),
+            TAG,
+            "initial doze transition model"
+        )
+
+        awaitClose { dozeTransitionListener.removeCallback(callback) }
+    }
+
     override fun isKeyguardShowing(): Boolean {
         return keyguardStateController.isShowing
     }
@@ -207,6 +361,139 @@
         awaitClose { statusBarStateController.removeCallback(callback) }
     }
 
+    override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
+        fun dispatchUpdate() {
+            trySendWithFailureLogging(
+                biometricModeIntToObject(biometricUnlockController.mode),
+                TAG,
+                "biometric mode"
+            )
+        }
+
+        val callback =
+            object : BiometricUnlockController.BiometricModeListener {
+                override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
+                    dispatchUpdate()
+                }
+
+                override fun onResetMode() {
+                    dispatchUpdate()
+                }
+            }
+
+        biometricUnlockController.addBiometricModeListener(callback)
+        dispatchUpdate()
+
+        awaitClose { biometricUnlockController.removeBiometricModeListener(callback) }
+    }
+
+    override val wakefulness: Flow<WakefulnessModel> = conflatedCallbackFlow {
+        val observer =
+            object : WakefulnessLifecycle.Observer {
+                override fun onStartedWakingUp() {
+                    dispatchNewState()
+                }
+
+                override fun onFinishedWakingUp() {
+                    dispatchNewState()
+                }
+
+                override fun onPostFinishedWakingUp() {
+                    dispatchNewState()
+                }
+
+                override fun onStartedGoingToSleep() {
+                    dispatchNewState()
+                }
+
+                override fun onFinishedGoingToSleep() {
+                    dispatchNewState()
+                }
+
+                private fun dispatchNewState() {
+                    trySendWithFailureLogging(
+                        WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
+                        TAG,
+                        "updated wakefulness state"
+                    )
+                }
+            }
+
+        wakefulnessLifecycle.addObserver(observer)
+        trySendWithFailureLogging(
+            WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
+            TAG,
+            "initial wakefulness state"
+        )
+
+        awaitClose { wakefulnessLifecycle.removeObserver(observer) }
+    }
+
+    override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
+        fun sendFpLocation() {
+            trySendWithFailureLogging(
+                authController.fingerprintSensorLocation,
+                TAG,
+                "AuthController.Callback#onFingerprintLocationChanged"
+            )
+        }
+
+        val callback =
+            object : AuthController.Callback {
+                override fun onFingerprintLocationChanged() {
+                    sendFpLocation()
+                }
+            }
+
+        authController.addCallback(callback)
+        sendFpLocation()
+
+        awaitClose { authController.removeCallback(callback) }
+    }
+
+    override val faceSensorLocation: Flow<Point?> = conflatedCallbackFlow {
+        fun sendSensorLocation() {
+            trySendWithFailureLogging(
+                authController.faceSensorLocation,
+                TAG,
+                "AuthController.Callback#onFingerprintLocationChanged"
+            )
+        }
+
+        val callback =
+            object : AuthController.Callback {
+                override fun onFaceSensorLocationChanged() {
+                    sendSensorLocation()
+                }
+            }
+
+        authController.addCallback(callback)
+        sendSensorLocation()
+
+        awaitClose { authController.removeCallback(callback) }
+    }
+
+    override val biometricUnlockSource: Flow<BiometricUnlockSource?> = conflatedCallbackFlow {
+        val callback =
+            object : KeyguardUpdateMonitorCallback() {
+                override fun onBiometricAuthenticated(
+                    userId: Int,
+                    biometricSourceType: BiometricSourceType?,
+                    isStrongBiometric: Boolean
+                ) {
+                    trySendWithFailureLogging(
+                        BiometricUnlockSource.fromBiometricSourceType(biometricSourceType),
+                        TAG,
+                        "onBiometricAuthenticated"
+                    )
+                }
+            }
+
+        keyguardUpdateMonitor.registerCallback(callback)
+        trySendWithFailureLogging(null, TAG, "initial value")
+        awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+    }
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.value = animate
     }
@@ -219,6 +506,8 @@
         _clockPosition.value = Position(x, y)
     }
 
+    override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported
+
     private fun statusBarStateIntToObject(value: Int): StatusBarState {
         return when (value) {
             0 -> StatusBarState.SHADE
@@ -228,6 +517,39 @@
         }
     }
 
+    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")
+        }
+    }
+
+    private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel {
+        return when (state) {
+            DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED
+            DozeMachine.State.INITIALIZED -> DozeStateModel.INITIALIZED
+            DozeMachine.State.DOZE -> DozeStateModel.DOZE
+            DozeMachine.State.DOZE_SUSPEND_TRIGGERS -> DozeStateModel.DOZE_SUSPEND_TRIGGERS
+            DozeMachine.State.DOZE_AOD -> DozeStateModel.DOZE_AOD
+            DozeMachine.State.DOZE_REQUEST_PULSE -> DozeStateModel.DOZE_REQUEST_PULSE
+            DozeMachine.State.DOZE_PULSING -> DozeStateModel.DOZE_PULSING
+            DozeMachine.State.DOZE_PULSING_BRIGHT -> DozeStateModel.DOZE_PULSING_BRIGHT
+            DozeMachine.State.DOZE_PULSE_DONE -> DozeStateModel.DOZE_PULSE_DONE
+            DozeMachine.State.FINISH -> DozeStateModel.FINISH
+            DozeMachine.State.DOZE_AOD_PAUSED -> DozeStateModel.DOZE_AOD_PAUSED
+            DozeMachine.State.DOZE_AOD_PAUSING -> DozeStateModel.DOZE_AOD_PAUSING
+            DozeMachine.State.DOZE_AOD_DOCKED -> DozeStateModel.DOZE_AOD_DOCKED
+            else -> throw IllegalArgumentException("Invalid DozeMachine.State: state")
+        }
+    }
+
     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..26f853f 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,12 @@
 @Module
 interface KeyguardRepositoryModule {
     @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
+
+    @Binds
+    fun keyguardTransitionRepository(
+        impl: KeyguardTransitionRepositoryImpl
+    ): KeyguardTransitionRepository
+
+    @Binds
+    fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
 }
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..5bb586e 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,160 @@
         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>(
+            replay = 2,
+            extraBufferCapacity = 10,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST,
+        )
+    override val transitions = _transitions.asSharedFlow().distinctUntilChanged()
+    private var lastStep: TransitionStep = TransitionStep()
+    private var lastAnimator: ValueAnimator? = 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
+
+    init {
+        // Seed with transitions signaling a boot into lockscreen state
+        emitTransition(
+            TransitionStep(
+                KeyguardState.OFF,
+                KeyguardState.LOCKSCREEN,
+                0f,
+                TransitionState.STARTED,
+                KeyguardTransitionRepositoryImpl::class.simpleName!!,
+            )
+        )
+        emitTransition(
+            TransitionStep(
+                KeyguardState.OFF,
+                KeyguardState.LOCKSCREEN,
+                1f,
+                TransitionState.FINISHED,
+                KeyguardTransitionRepositoryImpl::class.simpleName!!,
+            )
+        )
+    }
+
+    override fun startTransition(info: TransitionInfo): UUID? {
+        if (lastStep.transitionState != TransitionState.FINISHED) {
+            Log.i(TAG, "Transition still active: $lastStep, canceling")
+        }
+
+        val startingValue = 1f - lastStep.value
+        lastAnimator?.cancel()
+        lastAnimator = info.animator
+
+        info.animator?.let { animator ->
+            // An animator was provided, so use it to run the transition
+            animator.setFloatValues(startingValue, 1f)
+            animator.duration = ((1f - startingValue) * animator.duration).toLong()
+            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, startingValue, TransitionState.STARTED))
+                    }
+                    override fun onAnimationCancel(animation: Animator) {
+                        endAnimation(animation, lastStep.value, TransitionState.CANCELED)
+                    }
+                    override fun onAnimationEnd(animation: Animator) {
+                        endAnimation(animation, 1f, TransitionState.FINISHED)
+                    }
+
+                    private fun endAnimation(
+                        animation: Animator,
+                        value: Float,
+                        state: TransitionState
+                    ) {
+                        emitTransition(TransitionStep(info, value, state))
+                        animator.removeListener(this)
+                        animator.removeUpdateListener(updateListener)
+                        lastAnimator = null
+                    }
+                }
+            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/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
new file mode 100644
index 0000000..a17481a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.data.repository
+
+import android.content.Context
+import android.graphics.Point
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.statusbar.CircleReveal
+import com.android.systemui.statusbar.LiftReveal
+import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.statusbar.PowerButtonReveal
+import javax.inject.Inject
+import kotlin.math.max
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+val DEFAULT_REVEAL_EFFECT = LiftReveal
+
+/**
+ * Encapsulates state relevant to the light reveal scrim, the view used to reveal/hide screen
+ * contents during transitions between AOD and lockscreen/unlocked.
+ */
+interface LightRevealScrimRepository {
+
+    /**
+     * The reveal effect that should be used for the next lock/unlock. We switch between either the
+     * biometric unlock effect (if wake and unlocking) or the non-biometric effect, and position it
+     * at the current screen position of the appropriate sensor.
+     */
+    val revealEffect: Flow<LightRevealEffect>
+}
+
+@SysUISingleton
+class LightRevealScrimRepositoryImpl
+@Inject
+constructor(
+    keyguardRepository: KeyguardRepository,
+    val context: Context,
+) : LightRevealScrimRepository {
+
+    /** The reveal effect used if the device was locked/unlocked via the power button. */
+    private val powerButtonReveal =
+        PowerButtonReveal(
+            context.resources
+                .getDimensionPixelSize(R.dimen.physical_power_button_center_screen_location_y)
+                .toFloat()
+        )
+
+    /**
+     * Reveal effect to use for a fingerprint unlock. This is reconstructed if the fingerprint
+     * sensor location on the screen (in pixels) changes due to configuration changes.
+     */
+    private val fingerprintRevealEffect: Flow<LightRevealEffect?> =
+        keyguardRepository.fingerprintSensorLocation.map {
+            it?.let { constructCircleRevealFromPoint(it) }
+        }
+
+    /**
+     * Reveal effect to use for a face unlock. This is reconstructed if the face sensor/front camera
+     * location on the screen (in pixels) changes due to configuration changes.
+     */
+    private val faceRevealEffect: Flow<LightRevealEffect?> =
+        keyguardRepository.faceSensorLocation.map { it?.let { constructCircleRevealFromPoint(it) } }
+
+    /**
+     * The reveal effect we'll use for the next biometric unlock animation. We switch between the
+     * fingerprint/face unlock effect flows depending on the biometric unlock source.
+     */
+    private val biometricRevealEffect: Flow<LightRevealEffect?> =
+        keyguardRepository.biometricUnlockSource.flatMapLatest { source ->
+            when (source) {
+                BiometricUnlockSource.FINGERPRINT_SENSOR -> fingerprintRevealEffect
+                BiometricUnlockSource.FACE_SENSOR -> faceRevealEffect
+                else -> flowOf(null)
+            }
+        }
+
+    /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */
+    private val nonBiometricRevealEffect: Flow<LightRevealEffect?> =
+        keyguardRepository.wakefulness.map { wakefulnessModel ->
+            val wakingUpFromPowerButton =
+                wakefulnessModel.isWakingUpOrAwake &&
+                    wakefulnessModel.lastWakeReason == WakeSleepReason.POWER_BUTTON
+            val sleepingFromPowerButton =
+                !wakefulnessModel.isWakingUpOrAwake &&
+                    wakefulnessModel.lastSleepReason == WakeSleepReason.POWER_BUTTON
+
+            if (wakingUpFromPowerButton || sleepingFromPowerButton) {
+                powerButtonReveal
+            } else {
+                LiftReveal
+            }
+        }
+
+    override val revealEffect =
+        combine(
+                keyguardRepository.biometricUnlockState,
+                biometricRevealEffect,
+                nonBiometricRevealEffect
+            ) { biometricUnlockState, biometricReveal, nonBiometricReveal ->
+
+                // Use the biometric reveal for any flavor of wake and unlocking.
+                when (biometricUnlockState) {
+                    BiometricUnlockModel.WAKE_AND_UNLOCK,
+                    BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
+                    BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM -> biometricReveal
+                    else -> nonBiometricReveal
+                }
+                    ?: DEFAULT_REVEAL_EFFECT
+            }
+            .distinctUntilChanged()
+
+    private fun constructCircleRevealFromPoint(point: Point): LightRevealEffect {
+        return with(point) {
+            CircleReveal(
+                x,
+                y,
+                startRadius = 0,
+                endRadius =
+                    max(max(x, context.display.width - x), max(y, context.display.height - y)),
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
new file mode 100644
index 0000000..0e865ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.backup
+
+import android.app.backup.SharedPreferencesBackupHelper
+import android.content.Context
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.settings.UserFileManagerImpl
+
+/** Handles backup & restore for keyguard quick affordances. */
+class KeyguardQuickAffordanceBackupHelper(
+    context: Context,
+    userId: Int,
+) :
+    SharedPreferencesBackupHelper(
+        context,
+        if (UserFileManagerImpl.isPrimaryUser(userId)) {
+            KeyguardQuickAffordanceSelectionManager.FILE_NAME
+        } else {
+            UserFileManagerImpl.secondaryUserFile(
+                    context = context,
+                    fileName = KeyguardQuickAffordanceSelectionManager.FILE_NAME,
+                    directoryName = UserFileManagerImpl.SHARED_PREFS,
+                    userId = userId,
+                )
+                .also { UserFileManagerImpl.ensureParentDirExists(it) }
+                .toString()
+        }
+    )
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..f3d2905 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
@@ -20,13 +20,13 @@
 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.DozeStateModel
 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
@@ -34,33 +34,56 @@
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
-    private val keyguardRepository: KeyguardRepository,
+    private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
-) : TransitionInteractor("AOD<->LOCKSCREEN") {
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor(AodLockscreenTransitionInteractor::class.simpleName!!) {
 
     override fun start() {
+        listenForTransitionToAodFromLockscreen()
+        listenForTransitionToLockscreenFromDozeStates()
+    }
+
+    private fun listenForTransitionToAodFromLockscreen() {
         scope.launch {
-            keyguardRepository.isDozing.collect { isDozing ->
-                if (isDozing) {
-                    keyguardTransitionRepository.startTransition(
-                        TransitionInfo(
-                            name,
-                            KeyguardState.LOCKSCREEN,
-                            KeyguardState.AOD,
-                            getAnimator(),
+            keyguardInteractor
+                .dozeTransitionTo(DozeStateModel.DOZE_AOD)
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (dozeToAod, lastStartedStep) = pair
+                    if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.LOCKSCREEN,
+                                KeyguardState.AOD,
+                                getAnimator(),
+                            )
                         )
-                    )
-                } else {
-                    keyguardTransitionRepository.startTransition(
-                        TransitionInfo(
-                            name,
-                            KeyguardState.AOD,
-                            KeyguardState.LOCKSCREEN,
-                            getAnimator(),
-                        )
-                    )
+                    }
                 }
-            }
+        }
+    }
+
+    private fun listenForTransitionToLockscreenFromDozeStates() {
+        val canGoToLockscreen = setOf(KeyguardState.AOD, KeyguardState.DOZING)
+        scope.launch {
+            keyguardInteractor
+                .dozeTransitionTo(DozeStateModel.FINISH)
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (dozeToAod, lastStartedStep) = pair
+                    if (canGoToLockscreen.contains(lastStartedStep.to)) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                lastStartedStep.to,
+                                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..dad166f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
@@ -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.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.Companion.isWakeAndUnlock
+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(AodToGoneTransitionInteractor::class.simpleName!!) {
+
+    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 && isWakeAndUnlock(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/BouncerCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt
deleted file mode 100644
index 10c7a37..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt
+++ /dev/null
@@ -1,102 +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.keyguard.domain.interactor
-
-import android.view.View
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.phone.KeyguardBouncer
-import com.android.systemui.util.ListenerSet
-import javax.inject.Inject
-
-/** Interactor to add and remove callbacks for the bouncer. */
-@SysUISingleton
-class BouncerCallbackInteractor @Inject constructor() {
-    private var resetCallbacks = ListenerSet<KeyguardBouncer.KeyguardResetCallback>()
-    private var expansionCallbacks = ArrayList<KeyguardBouncer.BouncerExpansionCallback>()
-    /** Add a KeyguardResetCallback. */
-    fun addKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) {
-        resetCallbacks.addIfAbsent(callback)
-    }
-
-    /** Remove a KeyguardResetCallback. */
-    fun removeKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) {
-        resetCallbacks.remove(callback)
-    }
-
-    /** Adds a callback to listen to bouncer expansion updates. */
-    fun addBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) {
-        if (!expansionCallbacks.contains(callback)) {
-            expansionCallbacks.add(callback)
-        }
-    }
-
-    /**
-     * Removes a previously added callback. If the callback was never added, this method does
-     * nothing.
-     */
-    fun removeBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) {
-        expansionCallbacks.remove(callback)
-    }
-
-    /** Propagate fully shown to bouncer expansion callbacks. */
-    fun dispatchFullyShown() {
-        for (callback in expansionCallbacks) {
-            callback.onFullyShown()
-        }
-    }
-
-    /** Propagate starting to hide to bouncer expansion callbacks. */
-    fun dispatchStartingToHide() {
-        for (callback in expansionCallbacks) {
-            callback.onStartingToHide()
-        }
-    }
-
-    /** Propagate starting to show to bouncer expansion callbacks. */
-    fun dispatchStartingToShow() {
-        for (callback in expansionCallbacks) {
-            callback.onStartingToShow()
-        }
-    }
-
-    /** Propagate fully hidden to bouncer expansion callbacks. */
-    fun dispatchFullyHidden() {
-        for (callback in expansionCallbacks) {
-            callback.onFullyHidden()
-        }
-    }
-
-    /** Propagate expansion changes to bouncer expansion callbacks. */
-    fun dispatchExpansionChanged(expansion: Float) {
-        for (callback in expansionCallbacks) {
-            callback.onExpansionChanged(expansion)
-        }
-    }
-    /** Propagate visibility changes to bouncer expansion callbacks. */
-    fun dispatchVisibilityChanged(visibility: Int) {
-        for (callback in expansionCallbacks) {
-            callback.onVisibilityChanged(visibility == View.VISIBLE)
-        }
-    }
-
-    /** Propagate keyguard reset. */
-    fun dispatchReset() {
-        for (callback in resetCallbacks) {
-            callback.onKeyguardReset()
-        }
-    }
-}
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
deleted file mode 100644
index 2af9318..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
+++ /dev/null
@@ -1,324 +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.keyguard.domain.interactor
-
-import android.content.res.ColorStateList
-import android.os.Handler
-import android.os.Trace
-import android.os.UserHandle
-import android.os.UserManager
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.DejankUtils
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-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
-import com.android.systemui.shared.system.SysUiStatsLog
-import com.android.systemui.statusbar.phone.KeyguardBouncer
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
-
-/** Encapsulates business logic for interacting with the lock-screen bouncer. */
-@SysUISingleton
-class BouncerInteractor
-@Inject
-constructor(
-    private val repository: KeyguardBouncerRepository,
-    private val bouncerView: BouncerView,
-    @Main private val mainHandler: Handler,
-    private val keyguardStateController: KeyguardStateController,
-    private val keyguardSecurityModel: KeyguardSecurityModel,
-    private val callbackInteractor: BouncerCallbackInteractor,
-    private val falsingCollector: FalsingCollector,
-    private val dismissCallbackRegistry: DismissCallbackRegistry,
-    keyguardBypassController: KeyguardBypassController,
-    keyguardUpdateMonitor: KeyguardUpdateMonitor,
-) {
-    /** Whether we want to wait for face auth. */
-    private val bouncerFaceDelay =
-        keyguardStateController.isFaceAuthEnabled &&
-            !keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                KeyguardUpdateMonitor.getCurrentUser()
-            ) &&
-            !needsFullscreenBouncer() &&
-            !keyguardUpdateMonitor.userNeedsStrongAuth() &&
-            !keyguardBypassController.bypassEnabled
-
-    /** Runnable to show the bouncer. */
-    val showRunnable = Runnable {
-        repository.setVisible(true)
-        repository.setShow(
-            KeyguardBouncerModel(
-                promptReason = repository.bouncerPromptReason ?: 0,
-                errorMessage = repository.bouncerErrorMessage,
-                expansionAmount = repository.expansionAmount.value
-            )
-        )
-        repository.setShowingSoon(false)
-    }
-
-    val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
-    val screenTurnedOff: Flow<Unit> = repository.onScreenTurnedOff.filter { it }.map {}
-    val show: Flow<KeyguardBouncerModel> = repository.show.filterNotNull()
-    val hide: Flow<Unit> = repository.hide.filter { it }.map {}
-    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
-
-    // TODO(b/243685699): Move isScrimmed logic to data layer.
-    // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
-    /** Show the bouncer if necessary and set the relevant states. */
-    @JvmOverloads
-    fun show(isScrimmed: Boolean) {
-        // Reset some states as we show the bouncer.
-        repository.setShowMessage(null)
-        repository.setOnScreenTurnedOff(false)
-        repository.setKeyguardAuthenticated(null)
-        repository.setHide(false)
-        repository.setStartingToHide(false)
-
-        val resumeBouncer =
-            (repository.isVisible.value || repository.showingSoon.value) && needsFullscreenBouncer()
-
-        if (!resumeBouncer && repository.show.value != null) {
-            // If bouncer is visible, the bouncer is already showing.
-            return
-        }
-
-        val keyguardUserId = KeyguardUpdateMonitor.getCurrentUser()
-        if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
-            // In split system user mode, we never unlock system user.
-            return
-        }
-
-        Trace.beginSection("KeyguardBouncer#show")
-        repository.setScrimmed(isScrimmed)
-        if (isScrimmed) {
-            setExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
-        }
-
-        if (resumeBouncer) {
-            bouncerView.delegate?.resume()
-            // Bouncer is showing the next security screen and we just need to prompt a resume.
-            return
-        }
-        if (bouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
-            // Keyguard is done.
-            return
-        }
-
-        repository.setShowingSoon(true)
-        if (bouncerFaceDelay) {
-            mainHandler.postDelayed(showRunnable, 1200L)
-        } else {
-            DejankUtils.postAfterTraversal(showRunnable)
-        }
-        keyguardStateController.notifyBouncerShowing(true)
-        callbackInteractor.dispatchStartingToShow()
-
-        Trace.endSection()
-    }
-
-    /** Sets the correct bouncer states to hide the bouncer. */
-    fun hide() {
-        Trace.beginSection("KeyguardBouncer#hide")
-        if (isFullyShowing()) {
-            SysUiStatsLog.write(
-                SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
-                SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN
-            )
-            dismissCallbackRegistry.notifyDismissCancelled()
-        }
-
-        falsingCollector.onBouncerHidden()
-        keyguardStateController.notifyBouncerShowing(false /* showing */)
-        cancelShowRunnable()
-        repository.setShowingSoon(false)
-        repository.setOnDismissAction(null)
-        repository.setVisible(false)
-        repository.setHide(true)
-        repository.setShow(null)
-        Trace.endSection()
-    }
-
-    /**
-     * Sets the panel expansion which is calculated further upstream. Expansion is from 0f to 1f
-     * where 0f => showing and 1f => hiding
-     */
-    fun setExpansion(expansion: Float) {
-        val oldExpansion = repository.expansionAmount.value
-        val expansionChanged = oldExpansion != expansion
-        if (repository.startingDisappearAnimation.value == null) {
-            repository.setExpansion(expansion)
-        }
-
-        if (
-            expansion == KeyguardBouncer.EXPANSION_VISIBLE &&
-                oldExpansion != KeyguardBouncer.EXPANSION_VISIBLE
-        ) {
-            falsingCollector.onBouncerShown()
-            callbackInteractor.dispatchFullyShown()
-        } else if (
-            expansion == KeyguardBouncer.EXPANSION_HIDDEN &&
-                oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN
-        ) {
-            repository.setVisible(false)
-            repository.setShow(null)
-            falsingCollector.onBouncerHidden()
-            DejankUtils.postAfterTraversal { callbackInteractor.dispatchReset() }
-            callbackInteractor.dispatchFullyHidden()
-        } else if (
-            expansion != KeyguardBouncer.EXPANSION_VISIBLE &&
-                oldExpansion == KeyguardBouncer.EXPANSION_VISIBLE
-        ) {
-            callbackInteractor.dispatchStartingToHide()
-            repository.setStartingToHide(true)
-        }
-        if (expansionChanged) {
-            callbackInteractor.dispatchExpansionChanged(expansion)
-        }
-    }
-
-    /** Set the initial keyguard message to show when bouncer is shown. */
-    fun showMessage(message: String?, colorStateList: ColorStateList?) {
-        repository.setShowMessage(BouncerShowMessageModel(message, colorStateList))
-    }
-
-    /**
-     * Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is
-     * unlocked, we will run the onDismissAction. If the bouncer is existed before unlocking, we
-     * call cancelAction.
-     */
-    fun setDismissAction(
-        onDismissAction: ActivityStarter.OnDismissAction?,
-        cancelAction: Runnable?
-    ) {
-        repository.setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
-    }
-
-    /** Update the resources of the views. */
-    fun updateResources() {
-        repository.setResourceUpdateRequests(true)
-    }
-
-    /** Tell the bouncer that keyguard is authenticated. */
-    fun notifyKeyguardAuthenticated(strongAuth: Boolean) {
-        repository.setKeyguardAuthenticated(strongAuth)
-    }
-
-    /** Tell the bouncer the screen has turned off. */
-    fun onScreenTurnedOff() {
-        repository.setOnScreenTurnedOff(true)
-    }
-
-    /** Update the position of the bouncer when showing. */
-    fun setKeyguardPosition(position: Float) {
-        repository.setKeyguardPosition(position)
-    }
-
-    /** Notifies that the state change was handled. */
-    fun notifyKeyguardAuthenticatedHandled() {
-        repository.setKeyguardAuthenticated(null)
-    }
-
-    /** Notify that view visibility has changed. */
-    fun notifyBouncerVisibilityHasChanged(visibility: Int) {
-        callbackInteractor.dispatchVisibilityChanged(visibility)
-    }
-
-    /** Notify that the resources have been updated */
-    fun notifyUpdatedResources() {
-        repository.setResourceUpdateRequests(false)
-    }
-
-    /** Set whether back button is enabled when on the bouncer screen. */
-    fun setBackButtonEnabled(enabled: Boolean) {
-        repository.setIsBackButtonEnabled(enabled)
-    }
-
-    /** Tell the bouncer to start the pre hide animation. */
-    fun startDisappearAnimation(runnable: Runnable) {
-        val finishRunnable = Runnable {
-            runnable.run()
-            repository.setStartDisappearAnimation(null)
-        }
-        repository.setStartDisappearAnimation(finishRunnable)
-    }
-
-    /** Returns whether bouncer is fully showing. */
-    fun isFullyShowing(): Boolean {
-        return (repository.showingSoon.value || repository.isVisible.value) &&
-            repository.expansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
-            repository.startingDisappearAnimation.value == null
-    }
-
-    /** Returns whether bouncer is scrimmed. */
-    fun isScrimmed(): Boolean {
-        return repository.isScrimmed.value
-    }
-
-    /** 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
-    }
-
-    /** Return whether bouncer is animating away. */
-    fun isAnimatingAway(): Boolean {
-        return repository.startingDisappearAnimation.value != null
-    }
-
-    /** Return whether bouncer will dismiss with actions */
-    fun willDismissWithAction(): Boolean {
-        return repository.onDismissAction.value?.onDismissAction != null
-    }
-
-    /** Returns whether the bouncer should be full screen. */
-    private fun needsFullscreenBouncer(): Boolean {
-        val mode: KeyguardSecurityModel.SecurityMode =
-            keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser())
-        return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
-            mode == KeyguardSecurityModel.SecurityMode.SimPuk
-    }
-
-    /** Remove the show runnable from the main handler queue to improve performance. */
-    private fun cancelShowRunnable() {
-        DejankUtils.removeCallbacks(showRunnable)
-        mainHandler.removeCallbacks(showRunnable)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
new file mode 100644
index 0000000..056c44d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.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.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+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.launch
+
+@SysUISingleton
+class BouncerToGoneTransitionInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val shadeRepository: ShadeRepository,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor
+) : TransitionInteractor(BouncerToGoneTransitionInteractor::class.simpleName!!) {
+
+    private var transitionId: UUID? = null
+
+    override fun start() {
+        listenForKeyguardGoingAway()
+    }
+
+    private fun listenForKeyguardGoingAway() {
+        scope.launch {
+            keyguardInteractor.isKeyguardGoingAway
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+                .collect { pair ->
+                    val (isKeyguardGoingAway, keyguardState) = pair
+                    if (isKeyguardGoingAway && keyguardState == KeyguardState.BOUNCER) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                ownerName = name,
+                                from = KeyguardState.BOUNCER,
+                                to = KeyguardState.GONE,
+                                animator = getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    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/DreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
new file mode 100644
index 0000000..b73ce9e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.Companion.isWakeAndUnlock
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
+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.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class DreamingTransitionInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor(DreamingTransitionInteractor::class.simpleName!!) {
+
+    private val canDreamFrom =
+        setOf(KeyguardState.LOCKSCREEN, KeyguardState.GONE, KeyguardState.DOZING)
+
+    override fun start() {
+        listenForEntryToDreaming()
+        listenForDreamingToLockscreen()
+        listenForDreamingToGone()
+        listenForDreamingToDozing()
+    }
+
+    private fun listenForEntryToDreaming() {
+        scope.launch {
+            keyguardInteractor.isDreaming
+                .sample(
+                    combine(
+                        keyguardInteractor.dozeTransitionModel,
+                        keyguardTransitionInteractor.finishedKeyguardState,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect { triple ->
+                    val (isDreaming, dozeTransitionModel, keyguardState) = triple
+                    // Dozing/AOD and dreaming have overlapping events. If the state remains in
+                    // FINISH, it means that doze mode is not running and DREAMING is ok to
+                    // commence.
+                    if (
+                        isDozeOff(dozeTransitionModel.to) &&
+                            isDreaming &&
+                            canDreamFrom.contains(keyguardState)
+                    ) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                keyguardState,
+                                KeyguardState.DREAMING,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun listenForDreamingToLockscreen() {
+        scope.launch {
+            keyguardInteractor.isDreaming
+                .sample(
+                    combine(
+                        keyguardInteractor.dozeTransitionModel,
+                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        ::Pair,
+                    ),
+                    ::toTriple
+                )
+                .collect { triple ->
+                    val (isDreaming, dozeTransitionModel, lastStartedTransition) = triple
+                    if (
+                        isDozeOff(dozeTransitionModel.to) &&
+                            !isDreaming &&
+                            lastStartedTransition.to == KeyguardState.DREAMING
+                    ) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.DREAMING,
+                                KeyguardState.LOCKSCREEN,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun listenForDreamingToGone() {
+        scope.launch {
+            keyguardInteractor.biometricUnlockState
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+                .collect { pair ->
+                    val (biometricUnlockState, keyguardState) = pair
+                    if (
+                        keyguardState == KeyguardState.DREAMING &&
+                            isWakeAndUnlock(biometricUnlockState)
+                    ) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.DREAMING,
+                                KeyguardState.GONE,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun listenForDreamingToDozing() {
+        scope.launch {
+            combine(
+                    keyguardInteractor.dozeTransitionModel,
+                    keyguardTransitionInteractor.finishedKeyguardState,
+                    ::Pair
+                )
+                .collect { pair ->
+                    val (dozeTransitionModel, keyguardState) = pair
+                    if (
+                        dozeTransitionModel.to == DozeStateModel.DOZE &&
+                            keyguardState == KeyguardState.DREAMING
+                    ) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.DREAMING,
+                                KeyguardState.DOZING,
+                                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/GoneAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
new file mode 100644
index 0000000..a50e759
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
@@ -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.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.WakefulnessState
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+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(GoneAodTransitionInteractor::class.simpleName!!) {
+
+    override fun start() {
+        scope.launch {
+            keyguardInteractor.wakefulnessModel
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+                .collect { pair ->
+                    val (wakefulnessState, keyguardState) = pair
+                    if (
+                        keyguardState == KeyguardState.GONE &&
+                            wakefulnessState.state == WakefulnessState.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/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
index ede50b0..d2a7486 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
@@ -48,4 +48,9 @@
     fun setAnimateDozingTransitions(animate: Boolean) {
         repository.setAnimateDozingTransitions(animate)
     }
+
+    /**
+     * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
+     */
+    fun shouldConstrainToTopOfLockIcon(): Boolean = repository.isUdfpsSupported()
 }
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..6912e1d 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
@@ -17,10 +17,17 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.graphics.Point
 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.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+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
+import kotlinx.coroutines.flow.filter
 
 /**
  * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -35,12 +42,41 @@
      * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
      * all.
      */
-    val dozeAmount: Flow<Float> = repository.dozeAmount
+    val dozeAmount: Flow<Float> = repository.linearDozeAmount
     /** Whether the system is in doze mode. */
     val isDozing: Flow<Boolean> = repository.isDozing
-    /** Whether the keyguard is showing to not. */
+    /** Doze transition information. */
+    val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
+    /**
+     * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
+     * but not vice-versa.
+     */
+    val isDreaming: Flow<Boolean> = repository.isDreaming
+    /** Whether the keyguard is showing or not. */
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+    /** Whether the keyguard is going away. */
+    val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
+    /** Whether the bouncer is showing or not. */
+    val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
+    /** The device wake/sleep state */
+    val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness
+    /** 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
 
+    /** The approximate location on the screen of the fingerprint sensor, if one is available. */
+    val fingerprintSensorLocation: Flow<Point?> = repository.fingerprintSensorLocation
+
+    /** The approximate location on the screen of the face unlock sensor, if one is available. */
+    val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
+
+    fun dozeTransitionTo(state: DozeStateModel): Flow<DozeTransitionModel> {
+        return dozeTransitionModel.filter { it.to == state }
+    }
     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 13d97aa..748c6e8 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,32 @@
 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.KeyguardPickerFlag
+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.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract
 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 +56,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
@@ -72,7 +90,19 @@
         configKey: String,
         expandable: Expandable?,
     ) {
-        @Suppress("UNCHECKED_CAST") val config = registry.get(configKey)
+        @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(
@@ -84,28 +114,142 @@
         }
     }
 
+    /**
+     * Selects an affordance with the given ID on the slot with the given ID.
+     *
+     * @return `true` if the affordance was selected successfully; `false` otherwise.
+     */
+    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).
+     */
+    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<KeyguardQuickAffordancePickerRepresentation>> {
+        val slots = repository.get().getSlotPickerRepresentations()
+        val selections = repository.get().getSelections()
+        val affordanceById =
+            getAffordancePickerRepresentations().associateBy { affordance -> affordance.id }
+        return slots.associate { slot ->
+            slot.id to
+                (selections[slot.id] ?: emptyList()).mapNotNull { affordanceId ->
+                    affordanceById[affordanceId]
+                }
+        }
+    }
+
     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.
+                // 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.LockScreenState.Visible }
+                states.indexOfFirst { state ->
+                    state is KeyguardQuickAffordanceConfig.LockScreenState.Visible
+                }
             if (index != -1) {
                 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,
                     activationState = visibleState.activationState,
                 )
@@ -145,4 +289,47 @@
             )
         }
     }
+
+    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])
+    }
+
+    suspend fun getAffordancePickerRepresentations():
+        List<KeyguardQuickAffordancePickerRepresentation> {
+        return repository.get().getAffordancePickerRepresentations()
+    }
+
+    fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
+        check(isUsingRepository)
+
+        return repository.get().getSlotPickerRepresentations()
+    }
+
+    fun getPickerFlags(): List<KeyguardPickerFlag> {
+        return listOf(
+            KeyguardPickerFlag(
+                name = KeyguardQuickAffordanceProviderContract.FlagsTable.FLAG_NAME_FEATURE_ENABLED,
+                value = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES),
+            )
+        )
+    }
+
+    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..a2661d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.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.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.wakefulnessModel.collect { logger.v("WakefulnessModel", it) }
+        }
+
+        scope.launch {
+            keyguardInteractor.isBouncerShowing.collect { logger.v("Bouncer showing", it) }
+        }
+
+        scope.launch { keyguardInteractor.isDozing.collect { logger.v("isDozing", it) } }
+
+        scope.launch { keyguardInteractor.isDreaming.collect { logger.v("isDreaming", it) } }
+
+        scope.launch {
+            interactor.finishedKeyguardTransitionStep.collect {
+                logger.i("Finished transition", it)
+            }
+        }
+
+        scope.launch {
+            interactor.canceledKeyguardTransitionStep.collect {
+                logger.i("Canceled transition", it)
+            }
+        }
+
+        scope.launch {
+            interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
+        }
+
+        scope.launch {
+            keyguardInteractor.dozeTransitionModel.collect { logger.i("Doze 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..bb8b79a 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,15 @@
                 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")
+                    is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is DreamingTransitionInteractor -> 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..54a4f49 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,20 @@
             aodToLockscreenTransition.map { step -> step.copy(value = 1f - step.value) },
             lockscreenToAodTransition,
         )
+
+    /* The last [TransitionStep] with a [TransitionState] of STARTED */
+    val startedKeyguardTransitionStep: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
+
+    /* The last [TransitionStep] with a [TransitionState] of CANCELED */
+    val canceledKeyguardTransitionStep: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.transitionState == TransitionState.CANCELED }
+
+    /* 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 }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
new file mode 100644
index 0000000..6e25200
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class LightRevealScrimInteractor
+@Inject
+constructor(
+    transitionRepository: KeyguardTransitionRepository,
+    transitionInteractor: KeyguardTransitionInteractor,
+    lightRevealScrimRepository: LightRevealScrimRepository,
+) {
+
+    /**
+     * Whenever a keyguard transition starts, sample the latest reveal effect from the repository
+     * and use that for the starting transition.
+     *
+     * We can't simply use the nextRevealEffect since the effect may change midway through a
+     * transition, but we don't want to change effects part way through. For example, if we're using
+     * a CircleReveal to animate a biometric unlock, but the biometric unlock mode changes to NONE
+     * from WAKE_AND_UNLOCK before the unlock animation ends, we don't want to end up switching to a
+     * LiftReveal.
+     */
+    val lightRevealEffect: Flow<LightRevealEffect> =
+        transitionInteractor.startedKeyguardTransitionStep.sample(
+            lightRevealScrimRepository.revealEffect
+        )
+
+    /**
+     * The reveal amount to use for the light reveal scrim, which is derived from the keyguard
+     * transition steps.
+     */
+    val revealAmount: Flow<Float> =
+        transitionRepository.transitions
+            // Only listen to transitions that change the reveal amount.
+            .filter { willTransitionAffectRevealAmount(it) }
+            // Use the transition amount as the reveal amount, inverting it if we're transitioning
+            // to a non-revealed (hidden) state.
+            .map { step -> if (willBeRevealedInState(step.to)) step.value else 1f - step.value }
+
+    companion object {
+
+        /**
+         * Whether the transition requires a change in the reveal amount of the light reveal scrim.
+         * If not, we don't care about the transition and don't need to listen to it.
+         */
+        fun willTransitionAffectRevealAmount(transition: TransitionStep): Boolean {
+            return willBeRevealedInState(transition.from) != willBeRevealedInState(transition.to)
+        }
+
+        /**
+         * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
+         * state after the transition is complete. If false, scrim will be fully hidden.
+         */
+        fun willBeRevealedInState(state: KeyguardState): Boolean {
+            return when (state) {
+                KeyguardState.OFF -> false
+                KeyguardState.DOZING -> false
+                KeyguardState.AOD -> false
+                KeyguardState.DREAMING -> true
+                KeyguardState.BOUNCER -> true
+                KeyguardState.LOCKSCREEN -> true
+                KeyguardState.GONE -> true
+            }
+        }
+    }
+}
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..3218f96 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.WakefulnessState
 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,128 @@
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
-    private val keyguardRepository: KeyguardRepository,
+    private val keyguardInteractor: KeyguardInteractor,
     private val shadeRepository: ShadeRepository,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
-) : TransitionInteractor("LOCKSCREEN<->BOUNCER") {
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor
+) : TransitionInteractor(LockscreenBouncerTransitionInteractor::class.simpleName!!) {
 
     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
-                        }
+    private fun listenForBouncerHiding() {
+        scope.launch {
+            keyguardInteractor.isBouncerShowing
+                .sample(
+                    combine(
+                        keyguardInteractor.wakefulnessModel,
+                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                    ) { wakefulnessModel, transitionStep ->
+                        Pair(wakefulnessModel, transitionStep)
+                    }
+                ) { bouncerShowing, wakefulnessAndTransition ->
+                    Triple(
+                        bouncerShowing,
+                        wakefulnessAndTransition.first,
+                        wakefulnessAndTransition.second
                     )
-                } else {
-                    // TODO (b/251849525): Remove statusbarstate check when that state is integrated
-                    // into KeyguardTransitionRepository
-                    val isOnLockscreen =
-                        transitions.transitionState == TransitionState.FINISHED &&
-                            transitions.to == KeyguardState.LOCKSCREEN
+                }
+                .collect { (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) ->
                     if (
-                        isOnLockscreen &&
-                            shadeModel.isUserDragging &&
-                            statusBarState != SHADE_LOCKED
+                        !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
                     ) {
-                        transitionId =
-                            keyguardTransitionRepository.startTransition(
-                                TransitionInfo(
-                                    ownerName = name,
-                                    from = KeyguardState.LOCKSCREEN,
-                                    to = KeyguardState.BOUNCER,
-                                    animator = null,
-                                )
+                        val to =
+                            if (
+                                wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
+                                    wakefulnessState.state == WakefulnessState.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,
+                    ) { finishedKeyguardState, statusBarState ->
+                        Pair(finishedKeyguardState, statusBarState)
+                    }
+                ) { shadeModel, keyguardStateAndStatusBarState ->
+                    Triple(
+                        shadeModel,
+                        keyguardStateAndStatusBarState.first,
+                        keyguardStateAndStatusBarState.second
+                    )
+                }
+                .collect { (shadeModel, keyguardState, statusBarState) ->
+                    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..95d9602
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.util.kotlin.sample
+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 keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+) : TransitionInteractor(LockscreenGoneTransitionInteractor::class.simpleName!!) {
+
+    override fun start() {
+        scope.launch {
+            keyguardInteractor.isKeyguardGoingAway
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (isKeyguardGoingAway, lastStartedStep) = pair
+                    if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                        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/PrimaryBouncerCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt
new file mode 100644
index 0000000..c5e49c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.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.keyguard.domain.interactor
+
+import android.view.View
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.util.ListenerSet
+import javax.inject.Inject
+
+/** Interactor to add and remove callbacks for the bouncer. */
+@SysUISingleton
+class PrimaryBouncerCallbackInteractor @Inject constructor() {
+    private var resetCallbacks = ListenerSet<KeyguardBouncer.KeyguardResetCallback>()
+    private var expansionCallbacks = ArrayList<KeyguardBouncer.PrimaryBouncerExpansionCallback>()
+    /** Add a KeyguardResetCallback. */
+    fun addKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) {
+        resetCallbacks.addIfAbsent(callback)
+    }
+
+    /** Remove a KeyguardResetCallback. */
+    fun removeKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) {
+        resetCallbacks.remove(callback)
+    }
+
+    /** Adds a callback to listen to bouncer expansion updates. */
+    fun addBouncerExpansionCallback(callback: KeyguardBouncer.PrimaryBouncerExpansionCallback) {
+        if (!expansionCallbacks.contains(callback)) {
+            expansionCallbacks.add(callback)
+        }
+    }
+
+    /**
+     * Removes a previously added callback. If the callback was never added, this method does
+     * nothing.
+     */
+    fun removeBouncerExpansionCallback(callback: KeyguardBouncer.PrimaryBouncerExpansionCallback) {
+        expansionCallbacks.remove(callback)
+    }
+
+    /** Propagate fully shown to bouncer expansion callbacks. */
+    fun dispatchFullyShown() {
+        for (callback in expansionCallbacks) {
+            callback.onFullyShown()
+        }
+    }
+
+    /** Propagate starting to hide to bouncer expansion callbacks. */
+    fun dispatchStartingToHide() {
+        for (callback in expansionCallbacks) {
+            callback.onStartingToHide()
+        }
+    }
+
+    /** Propagate starting to show to bouncer expansion callbacks. */
+    fun dispatchStartingToShow() {
+        for (callback in expansionCallbacks) {
+            callback.onStartingToShow()
+        }
+    }
+
+    /** Propagate fully hidden to bouncer expansion callbacks. */
+    fun dispatchFullyHidden() {
+        for (callback in expansionCallbacks) {
+            callback.onFullyHidden()
+        }
+    }
+
+    /** Propagate expansion changes to bouncer expansion callbacks. */
+    fun dispatchExpansionChanged(expansion: Float) {
+        for (callback in expansionCallbacks) {
+            callback.onExpansionChanged(expansion)
+        }
+    }
+    /** Propagate visibility changes to bouncer expansion callbacks. */
+    fun dispatchVisibilityChanged(visibility: Int) {
+        for (callback in expansionCallbacks) {
+            callback.onVisibilityChanged(visibility == View.VISIBLE)
+        }
+    }
+
+    /** Propagate keyguard reset. */
+    fun dispatchReset() {
+        for (callback in resetCallbacks) {
+            callback.onKeyguardReset()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
new file mode 100644
index 0000000..2cf5fb9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -0,0 +1,345 @@
+/*
+ * 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.content.res.ColorStateList
+import android.hardware.biometrics.BiometricSourceType
+import android.os.Handler
+import android.os.Trace
+import android.os.UserHandle
+import android.os.UserManager
+import android.view.View
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.DejankUtils
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+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.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+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
+
+/**
+ * Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password)
+ * bouncer.
+ */
+@SysUISingleton
+class PrimaryBouncerInteractor
+@Inject
+constructor(
+    private val repository: KeyguardBouncerRepository,
+    private val primaryBouncerView: BouncerView,
+    @Main private val mainHandler: Handler,
+    private val keyguardStateController: KeyguardStateController,
+    private val keyguardSecurityModel: KeyguardSecurityModel,
+    private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
+    private val falsingCollector: FalsingCollector,
+    private val dismissCallbackRegistry: DismissCallbackRegistry,
+    keyguardBypassController: KeyguardBypassController,
+    keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) {
+    /** Whether we want to wait for face auth. */
+    private val primaryBouncerFaceDelay =
+        keyguardStateController.isFaceAuthEnabled &&
+            !keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
+                KeyguardUpdateMonitor.getCurrentUser()
+            ) &&
+            !needsFullscreenBouncer() &&
+            keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) &&
+            !keyguardBypassController.bypassEnabled
+
+    /** Runnable to show the primary bouncer. */
+    val showRunnable = Runnable {
+        repository.setPrimaryVisible(true)
+        repository.setPrimaryShow(
+            KeyguardBouncerModel(
+                promptReason = repository.bouncerPromptReason ?: 0,
+                errorMessage = repository.bouncerErrorMessage,
+                expansionAmount = repository.panelExpansionAmount.value
+            )
+        )
+        repository.setPrimaryShowingSoon(false)
+        primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
+    }
+
+    val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
+    val screenTurnedOff: Flow<Unit> = repository.onScreenTurnedOff.filter { it }.map {}
+    val show: Flow<KeyguardBouncerModel> = repository.primaryBouncerShow.filterNotNull()
+    val hide: Flow<Unit> = repository.primaryBouncerHide.filter { it }.map {}
+    val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {}
+    val isVisible: Flow<Boolean> = repository.primaryBouncerVisible
+    val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
+    val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
+    val startingDisappearAnimation: Flow<Runnable> =
+        repository.primaryBouncerStartingDisappearAnimation.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.primaryBouncerVisible) {
+            panelExpansion,
+            primaryBouncerVisible ->
+            if (primaryBouncerVisible) {
+                1f - panelExpansion
+            } else {
+                0f
+            }
+        }
+
+    // TODO(b/243685699): Move isScrimmed logic to data layer.
+    // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
+    /** Show the bouncer if necessary and set the relevant states. */
+    @JvmOverloads
+    fun show(isScrimmed: Boolean) {
+        // Reset some states as we show the bouncer.
+        repository.setOnScreenTurnedOff(false)
+        repository.setKeyguardAuthenticated(null)
+        repository.setPrimaryHide(false)
+        repository.setPrimaryStartingToHide(false)
+
+        val resumeBouncer =
+            (repository.primaryBouncerVisible.value ||
+                repository.primaryBouncerShowingSoon.value) && needsFullscreenBouncer()
+
+        if (!resumeBouncer && repository.primaryBouncerShow.value != null) {
+            // If bouncer is visible, the bouncer is already showing.
+            return
+        }
+
+        val keyguardUserId = KeyguardUpdateMonitor.getCurrentUser()
+        if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
+            // In split system user mode, we never unlock system user.
+            return
+        }
+
+        Trace.beginSection("KeyguardBouncer#show")
+        repository.setPrimaryScrimmed(isScrimmed)
+        if (isScrimmed) {
+            setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+        }
+
+        if (resumeBouncer) {
+            primaryBouncerView.delegate?.resume()
+            // Bouncer is showing the next security screen and we just need to prompt a resume.
+            return
+        }
+        if (primaryBouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
+            // Keyguard is done.
+            return
+        }
+
+        repository.setPrimaryShowingSoon(true)
+        if (primaryBouncerFaceDelay) {
+            mainHandler.postDelayed(showRunnable, 1200L)
+        } else {
+            DejankUtils.postAfterTraversal(showRunnable)
+        }
+        keyguardStateController.notifyBouncerShowing(true)
+        primaryBouncerCallbackInteractor.dispatchStartingToShow()
+        Trace.endSection()
+    }
+
+    /** Sets the correct bouncer states to hide the bouncer. */
+    fun hide() {
+        Trace.beginSection("KeyguardBouncer#hide")
+        if (isFullyShowing()) {
+            SysUiStatsLog.write(
+                SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
+                SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN
+            )
+            dismissCallbackRegistry.notifyDismissCancelled()
+        }
+
+        falsingCollector.onBouncerHidden()
+        keyguardStateController.notifyBouncerShowing(false /* showing */)
+        cancelShowRunnable()
+        repository.setPrimaryShowingSoon(false)
+        repository.setPrimaryVisible(false)
+        repository.setPrimaryHide(true)
+        repository.setPrimaryShow(null)
+        primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
+        Trace.endSection()
+    }
+
+    /**
+     * 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 setPanelExpansion(expansion: Float) {
+        val oldExpansion = repository.panelExpansionAmount.value
+        val expansionChanged = oldExpansion != expansion
+        if (repository.primaryBouncerStartingDisappearAnimation.value == null) {
+            repository.setPanelExpansion(expansion)
+        }
+
+        if (
+            expansion == KeyguardBouncer.EXPANSION_VISIBLE &&
+                oldExpansion != KeyguardBouncer.EXPANSION_VISIBLE
+        ) {
+            falsingCollector.onBouncerShown()
+            primaryBouncerCallbackInteractor.dispatchFullyShown()
+        } else if (
+            expansion == KeyguardBouncer.EXPANSION_HIDDEN &&
+                oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN
+        ) {
+            /*
+             * There are cases where #hide() was not invoked, such as when
+             * NotificationPanelViewController controls the hide animation. Make sure the state gets
+             * updated by calling #hide() directly.
+             */
+            hide()
+            DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() }
+            primaryBouncerCallbackInteractor.dispatchFullyHidden()
+        } else if (
+            expansion != KeyguardBouncer.EXPANSION_VISIBLE &&
+                oldExpansion == KeyguardBouncer.EXPANSION_VISIBLE
+        ) {
+            primaryBouncerCallbackInteractor.dispatchStartingToHide()
+            repository.setPrimaryStartingToHide(true)
+        }
+        if (expansionChanged) {
+            primaryBouncerCallbackInteractor.dispatchExpansionChanged(expansion)
+        }
+    }
+
+    /** Set the initial keyguard message to show when bouncer is shown. */
+    fun showMessage(message: String?, colorStateList: ColorStateList?) {
+        repository.setShowMessage(BouncerShowMessageModel(message, colorStateList))
+    }
+
+    /**
+     * Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is
+     * unlocked, we will run the onDismissAction. If the bouncer is existed before unlocking, we
+     * call cancelAction.
+     */
+    fun setDismissAction(
+        onDismissAction: ActivityStarter.OnDismissAction?,
+        cancelAction: Runnable?
+    ) {
+        primaryBouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
+    }
+
+    /** Update the resources of the views. */
+    fun updateResources() {
+        repository.setResourceUpdateRequests(true)
+    }
+
+    /** Tell the bouncer that keyguard is authenticated. */
+    fun notifyKeyguardAuthenticated(strongAuth: Boolean) {
+        repository.setKeyguardAuthenticated(strongAuth)
+    }
+
+    /** Tell the bouncer the screen has turned off. */
+    fun onScreenTurnedOff() {
+        repository.setOnScreenTurnedOff(true)
+    }
+
+    /** Update the position of the bouncer when showing. */
+    fun setKeyguardPosition(position: Float) {
+        repository.setKeyguardPosition(position)
+    }
+
+    /** Notifies that the state change was handled. */
+    fun notifyKeyguardAuthenticatedHandled() {
+        repository.setKeyguardAuthenticated(null)
+    }
+
+    /** Notifies that the message was shown. */
+    fun onMessageShown() {
+        repository.setShowMessage(null)
+    }
+
+    /** Notify that the resources have been updated */
+    fun notifyUpdatedResources() {
+        repository.setResourceUpdateRequests(false)
+    }
+
+    /** Set whether back button is enabled when on the bouncer screen. */
+    fun setBackButtonEnabled(enabled: Boolean) {
+        repository.setIsBackButtonEnabled(enabled)
+    }
+
+    /** Tell the bouncer to start the pre hide animation. */
+    fun startDisappearAnimation(runnable: Runnable) {
+        val finishRunnable = Runnable {
+            runnable.run()
+            repository.setPrimaryStartDisappearAnimation(null)
+        }
+        repository.setPrimaryStartDisappearAnimation(finishRunnable)
+    }
+
+    /** Returns whether bouncer is fully showing. */
+    fun isFullyShowing(): Boolean {
+        return (repository.primaryBouncerShowingSoon.value ||
+            repository.primaryBouncerVisible.value) &&
+            repository.panelExpansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
+            repository.primaryBouncerStartingDisappearAnimation.value == null
+    }
+
+    /** Returns whether bouncer is scrimmed. */
+    fun isScrimmed(): Boolean {
+        return repository.primaryBouncerScrimmed.value
+    }
+
+    /** If bouncer expansion is between 0f and 1f non-inclusive. */
+    fun isInTransit(): Boolean {
+        return repository.primaryBouncerShowingSoon.value ||
+            repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
+                repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
+    }
+
+    /** Return whether bouncer is animating away. */
+    fun isAnimatingAway(): Boolean {
+        return repository.primaryBouncerStartingDisappearAnimation.value != null
+    }
+
+    /** Return whether bouncer will dismiss with actions */
+    fun willDismissWithAction(): Boolean {
+        return primaryBouncerView.delegate?.willDismissWithActions() == true
+    }
+
+    /** Returns whether the bouncer should be full screen. */
+    private fun needsFullscreenBouncer(): Boolean {
+        val mode: KeyguardSecurityModel.SecurityMode =
+            keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser())
+        return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
+            mode == KeyguardSecurityModel.SecurityMode.SimPuk
+    }
+
+    /** Remove the show runnable from the main handler queue to improve performance. */
+    private fun cancelShowRunnable() {
+        DejankUtils.removeCallbacks(showRunnable)
+        mainHandler.removeCallbacks(showRunnable)
+    }
+}
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..5f63ae7 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,18 @@
     @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 bouncerGone(impl: BouncerToGoneTransitionInteractor): TransitionInteractor
+
+    @Binds
+    @IntoSet
+    abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
+
+    @Binds @IntoSet abstract fun dreaming(impl: DreamingTransitionInteractor): TransitionInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index a2a46d9..08ad3d5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -29,4 +29,6 @@
 sealed class TransitionInteractor(val name: String) {
 
     abstract fun start()
+
+    fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second)
 }
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..8fe6309f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.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.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;
+
+    companion object {
+        private val wakeAndUnlockModes =
+            setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
+
+        fun isWakeAndUnlock(model: BiometricUnlockModel): Boolean {
+            return wakeAndUnlockModes.contains(model)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockSource.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockSource.kt
new file mode 100644
index 0000000..b403416
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockSource.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.shared.model
+
+import android.hardware.biometrics.BiometricSourceType
+
+/** Biometric unlock sensor sources, which we use to play sensor-specific animations. */
+enum class BiometricUnlockSource {
+    /** The unlock was initiated by a fingerprint sensor authentication. */
+    FINGERPRINT_SENSOR,
+
+    /** The unlock was initiated by the front-facing camera or a nearby sensor. */
+    FACE_SENSOR;
+
+    companion object {
+        fun fromBiometricSourceType(type: BiometricSourceType?): BiometricUnlockSource? {
+            return when (type) {
+                BiometricSourceType.FINGERPRINT -> FINGERPRINT_SENSOR
+                BiometricSourceType.FACE -> FACE_SENSOR
+                BiometricSourceType.IRIS -> FACE_SENSOR
+                else -> null
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt
new file mode 100644
index 0000000..65b7cf7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.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.systemui.keyguard.shared.model
+
+/** Model device doze states. */
+enum class DozeStateModel {
+    /** Default state. Transition to INITIALIZED to get Doze going. */
+    UNINITIALIZED,
+    /** Doze components are set up. Followed by transition to DOZE or DOZE_AOD. */
+    INITIALIZED,
+    /** Regular doze. Device is asleep and listening for pulse triggers. */
+    DOZE,
+    /** Deep doze. Device is asleep and is not listening for pulse triggers. */
+    DOZE_SUSPEND_TRIGGERS,
+    /** Always-on doze. Device is asleep, showing UI and listening for pulse triggers. */
+    DOZE_AOD,
+    /** Pulse has been requested. Device is awake and preparing UI */
+    DOZE_REQUEST_PULSE,
+    /** Pulse is showing. Device is awake and showing UI. */
+    DOZE_PULSING,
+    /** Pulse is showing with bright wallpaper. Device is awake and showing UI. */
+    DOZE_PULSING_BRIGHT,
+    /** Pulse is done showing. Followed by transition to DOZE or DOZE_AOD. */
+    DOZE_PULSE_DONE,
+    /** Doze is done. DozeService is finished. */
+    FINISH,
+    /** AOD, but the display is temporarily off. */
+    DOZE_AOD_PAUSED,
+    /** AOD, prox is near, transitions to DOZE_AOD_PAUSED after a timeout. */
+    DOZE_AOD_PAUSING,
+    /** Always-on doze. Device is awake, showing docking UI and listening for pulse triggers. */
+    DOZE_AOD_DOCKED;
+
+    companion object {
+        fun isDozeOff(model: DozeStateModel): Boolean {
+            return model == UNINITIALIZED || model == FINISH
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeTransitionModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeTransitionModel.kt
new file mode 100644
index 0000000..e96ace2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeTransitionModel.kt
@@ -0,0 +1,22 @@
+/*
+ * 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
+
+/** Doze transition information. */
+data class DozeTransitionModel(
+    val from: DozeStateModel = DozeStateModel.UNINITIALIZED,
+    val to: DozeStateModel = DozeStateModel.UNINITIALIZED,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardPickerFlag.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardPickerFlag.kt
new file mode 100644
index 0000000..a7a5957
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardPickerFlag.kt
@@ -0,0 +1,24 @@
+/*
+ * 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
+
+/** Represents a flag that's consumed by the settings or wallpaper picker app. */
+data class KeyguardPickerFlag(
+    val name: String,
+    val value: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
new file mode 100644
index 0000000..7d13359
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.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.systemui.keyguard.shared.model
+
+import androidx.annotation.DrawableRes
+
+/**
+ * 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,
+
+    /** Whether this quick affordance is enabled. */
+    val isEnabled: Boolean = true,
+
+    /** If not enabled, the list of user-visible steps to re-enable it. */
+    val instructions: List<String>? = null,
+
+    /**
+     * If not enabled, an optional label for a button that takes the user to a destination where
+     * they can re-enable it.
+     */
+    val actionText: String? = null,
+
+    /**
+     * If not enabled, an optional component name (package and action) for a button that takes the
+     * user to a destination where they can re-enable it.
+     */
+    val actionComponentName: String? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt
new file mode 100644
index 0000000..86f2756
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.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
+
+/**
+ * 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..dd908c4 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,9 +17,29 @@
 
 /** List of all possible states to transition to/from */
 enum class KeyguardState {
-    /** For initialization only */
-    NONE,
-    /* Always-on Display. The device is in a low-power mode with a minimal UI visible */
+    /*
+     * The display is completely off, as well as any sensors that would trigger the device to wake
+     * up.
+     */
+    OFF,
+    /**
+     * The device has entered a special low-power mode within SystemUI. Doze is technically a
+     * special dream service implementation. No UI is visible. In this state, a least some
+     * low-powered sensors such as lift to wake or tap to wake are enabled, or wake screen for
+     * notifications is enabled, allowing the device to quickly wake up.
+     */
+    DOZING,
+    /*
+     * A device state after the device times out, which can be from both LOCKSCREEN or GONE states.
+     * DOZING is an example of special version of this state. Dreams may be implemented by third
+     * parties to present their own UI over keyguard, like a screensaver.
+     */
+    DREAMING,
+    /**
+     * The device has entered a special low-power mode within SystemUI, also called the Always-on
+     * Display (AOD). A minimal UI is presented to show critical information. If the device is in
+     * low-power mode without a UI, then it is DOZING.
+     */
     AOD,
     /*
      * The security screen prompt UI, containing PIN, Password, Pattern, and all FPS
@@ -31,4 +51,10 @@
      * 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..38a93b5 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,8 +17,12 @@
 
 /** Possible states for a running transition between [State] */
 enum class TransitionState {
-    NONE,
+    /* Transition has begun. */
     STARTED,
+    /* Transition is actively running. */
     RUNNING,
-    FINISHED
+    /* Transition has completed successfully. */
+    FINISHED,
+    /* Transition has been interrupted, and not completed successfully. */
+    CANCELED,
 }
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..767fd58 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
@@ -17,8 +17,15 @@
 
 /** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */
 data class TransitionStep(
-    val from: KeyguardState = KeyguardState.NONE,
-    val to: KeyguardState = KeyguardState.NONE,
+    val from: KeyguardState = KeyguardState.OFF,
+    val to: KeyguardState = KeyguardState.OFF,
     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/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
new file mode 100644
index 0000000..b32597d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
@@ -0,0 +1,44 @@
+/*
+ * 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
+
+import android.os.PowerManager
+
+/** The reason we're waking up or going to sleep, such as pressing the power button. */
+enum class WakeSleepReason {
+    /** The physical power button was pressed to wake up or sleep the device. */
+    POWER_BUTTON,
+
+    /** Something else happened to wake up or sleep the device. */
+    OTHER;
+
+    companion object {
+        fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason {
+            return when (reason) {
+                PowerManager.WAKE_REASON_POWER_BUTTON -> POWER_BUTTON
+                else -> OTHER
+            }
+        }
+
+        fun fromPowerManagerSleepReason(reason: Int): WakeSleepReason {
+            return when (reason) {
+                PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON -> POWER_BUTTON
+                else -> OTHER
+            }
+        }
+    }
+}
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..03dee00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.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.systemui.keyguard.shared.model
+
+import com.android.systemui.keyguard.WakefulnessLifecycle
+
+/** Model device wakefulness states. */
+data class WakefulnessModel(
+    val state: WakefulnessState,
+    val isWakingUpOrAwake: Boolean,
+    val lastWakeReason: WakeSleepReason,
+    val lastSleepReason: WakeSleepReason,
+) {
+    companion object {
+        fun isSleepingOrStartingToSleep(model: WakefulnessModel): Boolean {
+            return model.state == WakefulnessState.ASLEEP ||
+                model.state == WakefulnessState.STARTING_TO_SLEEP
+        }
+
+        fun isWakingOrStartingToWake(model: WakefulnessModel): Boolean {
+            return model.state == WakefulnessState.AWAKE ||
+                model.state == WakefulnessState.STARTING_TO_WAKE
+        }
+
+        fun fromWakefulnessLifecycle(wakefulnessLifecycle: WakefulnessLifecycle): WakefulnessModel {
+            return WakefulnessModel(
+                WakefulnessState.fromWakefulnessLifecycleInt(wakefulnessLifecycle.wakefulness),
+                wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_WAKING ||
+                    wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE,
+                WakeSleepReason.fromPowerManagerWakeReason(wakefulnessLifecycle.lastWakeReason),
+                WakeSleepReason.fromPowerManagerSleepReason(wakefulnessLifecycle.lastSleepReason),
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessState.kt
new file mode 100644
index 0000000..6791d88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessState.kt
@@ -0,0 +1,44 @@
+/*
+ * 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
+
+import com.android.systemui.keyguard.WakefulnessLifecycle
+
+enum class WakefulnessState {
+    /** 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;
+
+    companion object {
+        fun fromWakefulnessLifecycleInt(
+            @WakefulnessLifecycle.Wakefulness value: Int
+        ): WakefulnessState {
+            return when (value) {
+                WakefulnessLifecycle.WAKEFULNESS_ASLEEP -> ASLEEP
+                WakefulnessLifecycle.WAKEFULNESS_WAKING -> STARTING_TO_WAKE
+                WakefulnessLifecycle.WAKEFULNESS_AWAKE -> AWAKE
+                WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP -> STARTING_TO_SLEEP
+                else -> throw IllegalArgumentException("Invalid Wakefulness value: $value")
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 2c99ca5..cbe512f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.binder
 
+import android.graphics.drawable.Animatable2
 import android.util.Size
 import android.util.TypedValue
 import android.view.View
@@ -31,6 +32,7 @@
 import com.android.systemui.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.Interpolators
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
@@ -69,6 +71,12 @@
 
         /** Notifies that device configuration has changed. */
         fun onConfigurationChanged()
+
+        /**
+         * Returns whether the keyguard bottom area should be constrained to the top of the lock
+         * icon
+         */
+        fun shouldConstrainToTopOfLockIcon(): Boolean
     }
 
     /** Binds the view to the view-model, continuing to update the former based on the latter. */
@@ -208,6 +216,9 @@
             override fun onConfigurationChanged() {
                 configurationBasedDimensions.value = loadFromResources(view)
             }
+
+            override fun shouldConstrainToTopOfLockIcon(): Boolean =
+                viewModel.shouldConstrainToTopOfLockIcon()
         }
     }
 
@@ -238,6 +249,27 @@
 
         IconViewBinder.bind(viewModel.icon, view)
 
+        (view.drawable as? Animatable2)?.let { animatable ->
+            (viewModel.icon as? Icon.Resource)?.res?.let { iconResourceId ->
+                // Always start the animation (we do call stop() below, if we need to skip it).
+                animatable.start()
+
+                if (view.tag != iconResourceId) {
+                    // Here when we haven't run the animation on a previous update.
+                    //
+                    // Save the resource ID for next time, so we know not to re-animate the same
+                    // animation again.
+                    view.tag = iconResourceId
+                } else {
+                    // Here when we've already done this animation on a previous update and want to
+                    // skip directly to the final frame of the animation to avoid running it.
+                    //
+                    // By calling stop after start, we go to the final frame of the animation.
+                    animatable.stop()
+                }
+            }
+        }
+
         view.isActivated = viewModel.isActivated
         view.drawable.setTint(
             Utils.getColorAttrDefaultColor(
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..f772b17 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,9 +29,9 @@
 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
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
@@ -75,13 +75,28 @@
                     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) {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
                 try {
                     viewModel.setBouncerViewDelegate(delegate)
                     launch {
                         viewModel.show.collect {
+                            hostViewController.showPromptReason(it.promptReason)
+                            it.errorMessage?.let { errorMessage ->
+                                hostViewController.showErrorMessage(errorMessage)
+                            }
                             hostViewController.showPrimarySecurityScreen()
                             hostViewController.appear(
                                 SystemBarUtils.getStatusBarHeight(view.context)
@@ -90,18 +105,6 @@
                     }
 
                     launch {
-                        viewModel.showPromptReason.collect { prompt ->
-                            hostViewController.showPromptReason(prompt)
-                        }
-                    }
-
-                    launch {
-                        viewModel.showBouncerErrorMessage.collect { errorMessage ->
-                            hostViewController.showErrorMessage(errorMessage)
-                        }
-                    }
-
-                    launch {
                         viewModel.showWithFullExpansion.collect { model ->
                             hostViewController.resetSecurityContainer()
                             hostViewController.showPromptReason(model.promptReason)
@@ -122,15 +125,6 @@
                     }
 
                     launch {
-                        viewModel.setDismissAction.collect {
-                            hostViewController.setOnDismissAction(
-                                it.onDismissAction,
-                                it.cancelAction
-                            )
-                        }
-                    }
-
-                    launch {
                         viewModel.startDisappearAnimation.collect {
                             hostViewController.startDisappearAnimation(it)
                         }
@@ -158,7 +152,6 @@
                             val visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
                             view.visibility = visibility
                             hostViewController.onBouncerVisibilityChanged(visibility)
-                            viewModel.notifyBouncerVisibilityHasChanged(visibility)
                         }
                     }
 
@@ -187,6 +180,7 @@
                     launch {
                         viewModel.bouncerShowMessage.collect {
                             hostViewController.showMessage(it.message, it.colorStateList)
+                            viewModel.onMessageShown()
                         }
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
new file mode 100644
index 0000000..f1da882
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
@@ -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.systemui.keyguard.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.LightRevealScrim
+import kotlinx.coroutines.launch
+
+object LightRevealScrimViewBinder {
+    @JvmStatic
+    fun bind(revealScrim: LightRevealScrim, viewModel: LightRevealScrimViewModel) {
+        revealScrim.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                launch {
+                    viewModel.revealAmount.collect { amount -> revealScrim.revealAmount = amount }
+                }
+
+                launch {
+                    viewModel.lightRevealEffect.collect { effect ->
+                        revealScrim.revealEffect = effect
+                    }
+                }
+            }
+        }
+    }
+}
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 b6b2304..227796f 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
@@ -90,6 +90,12 @@
             .distinctUntilChanged()
     }
 
+    /**
+     * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
+     */
+    fun shouldConstrainToTopOfLockIcon(): Boolean =
+            bottomAreaInteractor.shouldConstrainToTopOfLockIcon()
+
     private fun button(
         position: KeyguardQuickAffordancePosition
     ): Flow<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..e5d4e49 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
@@ -19,15 +19,13 @@
 import android.view.View
 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.domain.interactor.PrimaryBouncerInteractor
 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
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 
 /** Models UI state for the lock screen bouncer; handles user input. */
@@ -35,10 +33,10 @@
 @Inject
 constructor(
     private val view: BouncerView,
-    private val interactor: BouncerInteractor,
+    private val interactor: PrimaryBouncerInteractor,
 ) {
     /** 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
@@ -46,13 +44,6 @@
     /** Observe whether bouncer is showing. */
     val show: Flow<KeyguardBouncerModel> = interactor.show
 
-    /** Observe bouncer prompt when bouncer is showing. */
-    val showPromptReason: Flow<Int> = interactor.show.map { it.promptReason }
-
-    /** Observe bouncer error message when bouncer is showing. */
-    val showBouncerErrorMessage: Flow<CharSequence> =
-        interactor.show.map { it.errorMessage }.filterNotNull()
-
     /** Observe visible expansion when bouncer is showing. */
     val showWithFullExpansion: Flow<KeyguardBouncerModel> =
         interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE }
@@ -63,9 +54,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
 
@@ -84,10 +72,6 @@
     /** Observe whether screen is turned off. */
     val screenTurnedOff: Flow<Unit> = interactor.screenTurnedOff
 
-    /** Notify that view visibility has changed. */
-    fun notifyBouncerVisibilityHasChanged(visibility: Int) {
-        return interactor.notifyBouncerVisibilityHasChanged(visibility)
-    }
     /** Observe whether we want to update resources. */
     fun notifyUpdateResources() {
         interactor.notifyUpdatedResources()
@@ -98,6 +82,11 @@
         interactor.notifyKeyguardAuthenticatedHandled()
     }
 
+    /** Notifies that the message was shown. */
+    fun onMessageShown() {
+        interactor.onMessageShown()
+    }
+
     /** Observe whether back button is enabled. */
     fun observeOnIsBackButtonEnabled(systemUiVisibility: () -> Int): Flow<Int> {
         return interactor.isBackButtonEnabled.map { enabled ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
new file mode 100644
index 0000000..a46d441
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.LightRevealScrimInteractor
+import com.android.systemui.statusbar.LightRevealEffect
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Models UI state for the light reveal scrim, which is used during screen on and off animations to
+ * draw a gradient that reveals/hides the contents of the screen.
+ */
+class LightRevealScrimViewModel @Inject constructor(interactor: LightRevealScrimInteractor) {
+    val lightRevealEffect: Flow<LightRevealEffect> = interactor.lightRevealEffect
+    val revealAmount: Flow<Float> = interactor.revealAmount
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index f9e341c..d6e29e0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.log
 
-import android.app.ActivityManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogcatEchoTracker
 
@@ -29,15 +29,6 @@
     private val dumpManager: DumpManager,
     private val logcatEchoTracker: LogcatEchoTracker
 ) {
-    /* limitiometricMessageDeferralLogger the size of maxPoolSize for low ram (Go) devices */
-    private fun adjustMaxSize(requestedMaxSize: Int): Int {
-        return if (ActivityManager.isLowRamDeviceStatic()) {
-            minOf(requestedMaxSize, 20) /* low ram max log size*/
-        } else {
-            requestedMaxSize
-        }
-    }
-
     @JvmOverloads
     fun create(
         name: String,
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt
new file mode 100644
index 0000000..619eac1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.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.log
+
+import android.app.ActivityManager
+
+class LogBufferHelper {
+    companion object {
+        /** If necessary, returns a limited maximum size for low ram (Go) devices */
+        fun adjustMaxSize(requestedMaxSize: Int): Int {
+            return if (ActivityManager.isLowRamDeviceStatic()) {
+                minOf(requestedMaxSize, 20) /* low ram max log size*/
+            } else {
+                requestedMaxSize
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
index c7e4c5e..b98a92f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
@@ -49,7 +49,9 @@
 @SysUISingleton
 public class SessionTracker implements CoreStartable {
     private static final String TAG = "SessionTracker";
-    private static final boolean DEBUG = false;
+
+    // To enable logs: `adb shell setprop log.tag.SessionTracker DEBUG` & restart sysui
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     // At most 20 bits: ~1m possibilities, ~0.5% probability of collision in 100 values
     private final InstanceIdSequence mInstanceIdGenerator = new InstanceIdSequence(1 << 20);
@@ -81,8 +83,8 @@
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
         mKeyguardStateController.addCallback(mKeyguardStateCallback);
 
-        mKeyguardSessionStarted = mKeyguardStateController.isShowing();
-        if (mKeyguardSessionStarted) {
+        if (mKeyguardStateController.isShowing()) {
+            mKeyguardSessionStarted = true;
             startSession(SESSION_KEYGUARD);
         }
     }
@@ -136,12 +138,11 @@
             new KeyguardUpdateMonitorCallback() {
         @Override
         public void onStartedGoingToSleep(int why) {
-            // we need to register to the KeyguardUpdateMonitor lifecycle b/c it gets called
-            // before the WakefulnessLifecycle
             if (mKeyguardSessionStarted) {
-                return;
+                endSession(SESSION_KEYGUARD);
             }
 
+            // Start a new session whenever the device goes to sleep
             mKeyguardSessionStarted = true;
             startSession(SESSION_KEYGUARD);
         }
@@ -154,6 +155,9 @@
             boolean wasSessionStarted = mKeyguardSessionStarted;
             boolean keyguardShowing = mKeyguardStateController.isShowing();
             if (keyguardShowing && !wasSessionStarted) {
+                // the keyguard can start showing without the device going to sleep (ie: lockdown
+                // from the power button), so we start a new keyguard session when the keyguard is
+                // newly shown in addition to when the device starts going to sleep
                 mKeyguardSessionStarted = true;
                 startSession(SESSION_KEYGUARD);
             } else if (!keyguardShowing && wasSessionStarted) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java
new file mode 100644
index 0000000..4b774d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java
@@ -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.
+ */
+
+package com.android.systemui.log.dagger;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link com.android.systemui.plugins.log.LogBuffer} for BiometricMessages processing such as
+ * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}
+ */
+@Qualifier
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface BiometricLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
deleted file mode 100644
index eeadf40..0000000
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
+++ /dev/null
@@ -1,33 +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.log.dagger;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Qualifier;
-
-/**
- * A {@link com.android.systemui.plugins.log.LogBuffer} for BiometricMessages processing such as
- * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}
- */
-@Qualifier
-@Documented
-@Retention(RetentionPolicy.RUNTIME)
-public @interface BiometricMessagesLog {
-}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index ff291bf..f5a97ce 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -300,9 +300,9 @@
      */
     @Provides
     @SysUISingleton
-    @BiometricMessagesLog
-    public static LogBuffer provideBiometricMessagesLogBuffer(LogBufferFactory factory) {
-        return factory.create("BiometricMessagesLog", 150);
+    @BiometricLog
+    public static LogBuffer provideBiometricLogBuffer(LogBufferFactory factory) {
+        return factory.create("BiometricLog", 200);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
new file mode 100644
index 0000000..bb04b6b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.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.log.table
+
+import com.android.systemui.util.kotlin.pairwiseBy
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * An interface that enables logging the difference between values in table format.
+ *
+ * Many objects that we want to log are data-y objects with a collection of fields. When logging
+ * these objects, we want to log each field separately. This allows ABT (Android Bug Tool) to easily
+ * highlight changes in individual fields.
+ *
+ * See [TableLogBuffer].
+ */
+interface Diffable<T> {
+    /**
+     * Finds the differences between [prevVal] and this object and logs those diffs to [row].
+     *
+     * Each implementer should determine which individual fields have changed between [prevVal] and
+     * this object, and only log the fields that have actually changed. This helps save buffer
+     * space.
+     *
+     * For example, if:
+     * - prevVal = Object(val1=100, val2=200, val3=300)
+     * - this = Object(val1=100, val2=200, val3=333)
+     *
+     * Then only the val3 change should be logged.
+     */
+    fun logDiffs(prevVal: T, row: TableRowLogger)
+
+    /**
+     * Logs all the relevant fields of this object to [row].
+     *
+     * As opposed to [logDiffs], this method should log *all* fields.
+     *
+     * Implementation is optional. This method will only be used with [logDiffsForTable] in order to
+     * fully log the initial value of the flow.
+     */
+    fun logFull(row: TableRowLogger) {}
+}
+
+/**
+ * Each time the flow is updated with a new value, logs the differences between the previous value
+ * and the new value to the given [tableLogBuffer].
+ *
+ * The new value's [Diffable.logDiffs] method will be used to log the differences to the table.
+ *
+ * @param columnPrefix a prefix that will be applied to every column name that gets logged.
+ */
+fun <T : Diffable<T>> Flow<T>.logDiffsForTable(
+    tableLogBuffer: TableLogBuffer,
+    columnPrefix: String,
+    initialValue: T,
+): Flow<T> {
+    // Fully log the initial value to the table.
+    val getInitialValue = {
+        tableLogBuffer.logChange(columnPrefix) { row -> initialValue.logFull(row) }
+        initialValue
+    }
+    return this.pairwiseBy(getInitialValue) { prevVal: T, newVal: T ->
+        tableLogBuffer.logDiffs(columnPrefix, prevVal, newVal)
+        newVal
+    }
+}
+
+/**
+ * Each time the boolean flow is updated with a new value that's different from the previous value,
+ * logs the new value to the given [tableLogBuffer].
+ */
+fun Flow<Boolean>.logDiffsForTable(
+    tableLogBuffer: TableLogBuffer,
+    columnPrefix: String,
+    columnName: String,
+    initialValue: Boolean,
+): Flow<Boolean> {
+    val initialValueFun = {
+        tableLogBuffer.logChange(columnPrefix, columnName, initialValue)
+        initialValue
+    }
+    return this.pairwiseBy(initialValueFun) { prevVal, newVal: Boolean ->
+        if (prevVal != newVal) {
+            tableLogBuffer.logChange(columnPrefix, columnName, newVal)
+        }
+        newVal
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
new file mode 100644
index 0000000..68c297f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
@@ -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.systemui.log.table
+
+/**
+ * A object used with [TableLogBuffer] to store changes in variables over time. Is recyclable.
+ *
+ * Each message represents a change to exactly 1 type, specified by [DataType].
+ */
+data class TableChange(
+    var timestamp: Long = 0,
+    var columnPrefix: String = "",
+    var columnName: String = "",
+    var type: DataType = DataType.EMPTY,
+    var bool: Boolean = false,
+    var int: Int = 0,
+    var str: String? = null,
+) {
+    /** Resets to default values so that the object can be recycled. */
+    fun reset(timestamp: Long, columnPrefix: String, columnName: String) {
+        this.timestamp = timestamp
+        this.columnPrefix = columnPrefix
+        this.columnName = columnName
+        this.type = DataType.EMPTY
+        this.bool = false
+        this.int = 0
+        this.str = null
+    }
+
+    /** Sets this to store a string change. */
+    fun set(value: String?) {
+        type = DataType.STRING
+        str = value
+    }
+
+    /** Sets this to store a boolean change. */
+    fun set(value: Boolean) {
+        type = DataType.BOOLEAN
+        bool = value
+    }
+
+    /** Sets this to store an int change. */
+    fun set(value: Int) {
+        type = DataType.INT
+        int = value
+    }
+
+    /** Returns true if this object has a change. */
+    fun hasData(): Boolean {
+        return columnName.isNotBlank() && type != DataType.EMPTY
+    }
+
+    fun getName(): String {
+        return if (columnPrefix.isNotBlank()) {
+            "$columnPrefix.$columnName"
+        } else {
+            columnName
+        }
+    }
+
+    fun getVal(): String {
+        return when (type) {
+            DataType.EMPTY -> null
+            DataType.STRING -> str
+            DataType.INT -> int
+            DataType.BOOLEAN -> bool
+        }.toString()
+    }
+
+    enum class DataType {
+        STRING,
+        BOOLEAN,
+        INT,
+        EMPTY,
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
new file mode 100644
index 0000000..9d0b833
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -0,0 +1,230 @@
+/*
+ * 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.log.table
+
+import com.android.systemui.Dumpable
+import com.android.systemui.plugins.util.RingBuffer
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import java.text.SimpleDateFormat
+import java.util.Locale
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * A logger that logs changes in table format.
+ *
+ * Some parts of System UI maintain a lot of pieces of state at once.
+ * [com.android.systemui.plugins.log.LogBuffer] allows us to easily log change events:
+ *
+ * - 10-10 10:10:10.456: state2 updated to newVal2
+ * - 10-10 10:11:00.000: stateN updated to StateN(val1=true, val2=1)
+ * - 10-10 10:11:02.123: stateN updated to StateN(val1=true, val2=2)
+ * - 10-10 10:11:05.123: state1 updated to newVal1
+ * - 10-10 10:11:06.000: stateN updated to StateN(val1=false, val2=3)
+ *
+ * However, it can sometimes be more useful to view the state changes in table format:
+ *
+ * - timestamp--------- | state1- | state2- | ... | stateN.val1 | stateN.val2
+ * - -------------------------------------------------------------------------
+ * - 10-10 10:10:10.123 | val1--- | val2--- | ... | false------ | 0-----------
+ * - 10-10 10:10:10.456 | val1--- | newVal2 | ... | false------ | 0-----------
+ * - 10-10 10:11:00.000 | val1--- | newVal2 | ... | true------- | 1-----------
+ * - 10-10 10:11:02.123 | val1--- | newVal2 | ... | true------- | 2-----------
+ * - 10-10 10:11:05.123 | newVal1 | newVal2 | ... | true------- | 2-----------
+ * - 10-10 10:11:06.000 | newVal1 | newVal2 | ... | false------ | 3-----------
+ *
+ * This class enables easy logging of the state changes in both change event format and table
+ * format.
+ *
+ * This class also enables easy logging of states that are a collection of fields. For example,
+ * stateN in the above example consists of two fields -- val1 and val2. It's useful to put each
+ * field into its own column so that ABT (Android Bug Tool) can easily highlight changes to
+ * individual fields.
+ *
+ * How it works:
+ *
+ * 1) Create an instance of this buffer via [TableLogBufferFactory].
+ *
+ * 2) For any states being logged, implement [Diffable]. Implementing [Diffable] allows the state to
+ * only log the fields that have *changed* since the previous update, instead of always logging all
+ * fields.
+ *
+ * 3) Each time a change in a state happens, call [logDiffs]. If your state is emitted using a
+ * [Flow], you should use the [logDiffsForTable] extension function to automatically log diffs any
+ * time your flow emits a new value.
+ *
+ * When a dump occurs, there will be two dumps:
+ *
+ * 1) The change events under the dumpable name "$name-changes".
+ *
+ * 2) This class will coalesce all the diffs into a table format and log them under the dumpable
+ * name "$name-table".
+ *
+ * @param maxSize the maximum size of the buffer. Must be > 0.
+ */
+class TableLogBuffer(
+    maxSize: Int,
+    private val name: String,
+    private val systemClock: SystemClock,
+) : Dumpable {
+    init {
+        if (maxSize <= 0) {
+            throw IllegalArgumentException("maxSize must be > 0")
+        }
+    }
+
+    private val buffer = RingBuffer(maxSize) { TableChange() }
+
+    // A [TableRowLogger] object, re-used each time [logDiffs] is called.
+    // (Re-used to avoid object allocation.)
+    private val tempRow = TableRowLoggerImpl(0, columnPrefix = "", this)
+
+    /**
+     * Log the differences between [prevVal] and [newVal].
+     *
+     * The [newVal] object's method [Diffable.logDiffs] will be used to fetch the diffs.
+     *
+     * @param columnPrefix a prefix that will be applied to every column name that gets logged. This
+     * ensures that all the columns related to the same state object will be grouped together in the
+     * table.
+     *
+     * @throws IllegalArgumentException if [columnPrefix] or column name contain "|". "|" is used as
+     * the separator token for parsing, so it can't be present in any part of the column name.
+     */
+    @Synchronized
+    fun <T : Diffable<T>> logDiffs(columnPrefix: String, prevVal: T, newVal: T) {
+        val row = tempRow
+        row.timestamp = systemClock.currentTimeMillis()
+        row.columnPrefix = columnPrefix
+        newVal.logDiffs(prevVal, row)
+    }
+
+    /**
+     * Logs change(s) to the buffer using [rowInitializer].
+     *
+     * @param rowInitializer a function that will be called immediately to store relevant data on
+     * the row.
+     */
+    @Synchronized
+    fun logChange(columnPrefix: String, rowInitializer: (TableRowLogger) -> Unit) {
+        val row = tempRow
+        row.timestamp = systemClock.currentTimeMillis()
+        row.columnPrefix = columnPrefix
+        rowInitializer(row)
+    }
+
+    /** Logs a boolean change. */
+    fun logChange(prefix: String, columnName: String, value: Boolean) {
+        logChange(systemClock.currentTimeMillis(), prefix, columnName, value)
+    }
+
+    // Keep these individual [logChange] methods private (don't let clients give us their own
+    // timestamps.)
+
+    private fun logChange(timestamp: Long, prefix: String, columnName: String, value: String?) {
+        val change = obtain(timestamp, prefix, columnName)
+        change.set(value)
+    }
+
+    private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Boolean) {
+        val change = obtain(timestamp, prefix, columnName)
+        change.set(value)
+    }
+
+    private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int) {
+        val change = obtain(timestamp, prefix, columnName)
+        change.set(value)
+    }
+
+    // TODO(b/259454430): Add additional change types here.
+
+    @Synchronized
+    private fun obtain(timestamp: Long, prefix: String, columnName: String): TableChange {
+        verifyValidName(prefix, columnName)
+        val tableChange = buffer.advance()
+        tableChange.reset(timestamp, prefix, columnName)
+        return tableChange
+    }
+
+    private fun verifyValidName(prefix: String, columnName: String) {
+        if (prefix.contains(SEPARATOR)) {
+            throw IllegalArgumentException("columnPrefix cannot contain $SEPARATOR but was $prefix")
+        }
+        if (columnName.contains(SEPARATOR)) {
+            throw IllegalArgumentException(
+                "columnName cannot contain $SEPARATOR but was $columnName"
+            )
+        }
+    }
+
+    @Synchronized
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println(HEADER_PREFIX + name)
+        pw.println("version $VERSION")
+        for (i in 0 until buffer.size) {
+            buffer[i].dump(pw)
+        }
+        pw.println(FOOTER_PREFIX + name)
+    }
+
+    /** Dumps an individual [TableChange]. */
+    private fun TableChange.dump(pw: PrintWriter) {
+        if (!this.hasData()) {
+            return
+        }
+        val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(timestamp)
+        pw.print(formattedTimestamp)
+        pw.print(SEPARATOR)
+        pw.print(this.getName())
+        pw.print(SEPARATOR)
+        pw.print(this.getVal())
+        pw.println()
+    }
+
+    /**
+     * A private implementation of [TableRowLogger].
+     *
+     * Used so that external clients can't modify [timestamp].
+     */
+    private class TableRowLoggerImpl(
+        var timestamp: Long,
+        var columnPrefix: String,
+        val tableLogBuffer: TableLogBuffer,
+    ) : TableRowLogger {
+        /** Logs a change to a string value. */
+        override fun logChange(columnName: String, value: String?) {
+            tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+        }
+
+        /** Logs a change to a boolean value. */
+        override fun logChange(columnName: String, value: Boolean) {
+            tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+        }
+
+        /** Logs a change to an int value. */
+        override fun logChange(columnName: String, value: Int) {
+            tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+        }
+    }
+}
+
+val TABLE_LOG_DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
+
+private const val HEADER_PREFIX = "SystemUI StateChangeTableSection START: "
+private const val FOOTER_PREFIX = "SystemUI StateChangeTableSection END: "
+private const val SEPARATOR = "|"
+private const val VERSION = "1"
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
new file mode 100644
index 0000000..7a90a74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.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.log.table
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+
+@SysUISingleton
+class TableLogBufferFactory
+@Inject
+constructor(
+    private val dumpManager: DumpManager,
+    private val systemClock: SystemClock,
+) {
+    fun create(
+        name: String,
+        maxSize: Int,
+    ): TableLogBuffer {
+        val tableBuffer = TableLogBuffer(adjustMaxSize(maxSize), name, systemClock)
+        dumpManager.registerNormalDumpable(name, tableBuffer)
+        return tableBuffer
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt
new file mode 100644
index 0000000..a7ba13b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.log.table
+
+/**
+ * A class that logs a row to [TableLogBuffer].
+ *
+ * Objects that implement [Diffable] will receive an instance of this class, and can log any changes
+ * to individual fields using the [logChange] methods. All logged changes will be associated with
+ * the same timestamp.
+ */
+interface TableRowLogger {
+    /** Logs a change to a string value. */
+    fun logChange(columnName: String, value: String?)
+
+    /** Logs a change to a boolean value. */
+    fun logChange(columnName: String, value: Boolean)
+
+    /** Logs a change to an int value. */
+    fun logChange(columnName: String, value: Int)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
new file mode 100644
index 0000000..a88a4ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.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.systemui.logcat;
+
+import android.annotation.StyleRes;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.Html;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
+import android.util.Slog;
+import android.view.ContextThemeWrapper;
+import android.view.InflateException;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.internal.app.ILogAccessDialogCallback;
+import com.android.systemui.R;
+
+
+/**
+ * Dialog responsible for obtaining user consent per-use log access
+ */
+public class LogAccessDialogActivity extends Activity implements
+        View.OnClickListener {
+    private static final String TAG = LogAccessDialogActivity.class.getSimpleName();
+    public static final String EXTRA_CALLBACK = "EXTRA_CALLBACK";
+
+
+    private static final int DIALOG_TIME_OUT = Build.IS_DEBUGGABLE ? 60000 : 300000;
+    private static final int MSG_DISMISS_DIALOG = 0;
+
+    private String mPackageName;
+    private int mUid;
+    private ILogAccessDialogCallback mCallback;
+
+    private String mAlertTitle;
+    private String mAlertBody;
+    private String mAlertLearnMore;
+    private AlertDialog.Builder mAlertDialog;
+    private AlertDialog mAlert;
+    private View mAlertView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // retrieve Intent extra information
+        if (!readIntentInfo(getIntent())) {
+            Slog.e(TAG, "Invalid Intent extras, finishing");
+            finish();
+            return;
+        }
+
+        // retrieve the title string from passed intent extra
+        try {
+            mAlertTitle = getTitleString(this, mPackageName, mUid);
+        } catch (NameNotFoundException e) {
+            Slog.e(TAG, "Unable to fetch label of package " + mPackageName, e);
+            declineLogAccess();
+            finish();
+            return;
+        }
+
+        mAlertBody = getResources().getString(R.string.log_access_confirmation_body);
+        mAlertLearnMore = getResources().getString(R.string.log_access_confirmation_learn_more);
+
+        // create View
+        int themeId = R.style.LogAccessDialogTheme;
+        mAlertView = createView(themeId);
+
+        // create AlertDialog
+        mAlertDialog = new AlertDialog.Builder(this, themeId);
+        mAlertDialog.setView(mAlertView);
+        mAlertDialog.setOnCancelListener(dialog -> declineLogAccess());
+        mAlertDialog.setOnDismissListener(dialog -> finish());
+
+        // show Alert
+        mAlert = mAlertDialog.create();
+        mAlert.getWindow().setHideOverlayWindows(true);
+        mAlert.show();
+
+        // set Alert Timeout
+        mHandler.sendEmptyMessageDelayed(MSG_DISMISS_DIALOG, DIALOG_TIME_OUT);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (!isChangingConfigurations() && mAlert != null && mAlert.isShowing()) {
+            mAlert.dismiss();
+        }
+        mAlert = null;
+    }
+
+    private boolean readIntentInfo(Intent intent) {
+        if (intent == null) {
+            Slog.e(TAG, "Intent is null");
+            return false;
+        }
+
+        mCallback = ILogAccessDialogCallback.Stub.asInterface(
+                intent.getExtras().getBinder(EXTRA_CALLBACK));
+        if (mCallback == null) {
+            Slog.e(TAG, "Missing callback");
+            return false;
+        }
+
+        mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+        if (mPackageName == null || mPackageName.length() == 0) {
+            Slog.e(TAG, "Missing package name extra");
+            return false;
+        }
+
+        if (!intent.hasExtra(Intent.EXTRA_UID)) {
+            Slog.e(TAG, "Missing EXTRA_UID");
+            return false;
+        }
+
+        mUid = intent.getIntExtra(Intent.EXTRA_UID, 0);
+
+        return true;
+    }
+
+    private Handler mHandler = new Handler() {
+        public void handleMessage(android.os.Message msg) {
+            switch (msg.what) {
+                case MSG_DISMISS_DIALOG:
+                    if (mAlert != null) {
+                        mAlert.dismiss();
+                        mAlert = null;
+                        declineLogAccess();
+                    }
+                    break;
+
+                default:
+                    break;
+            }
+        }
+    };
+
+    private String getTitleString(Context context, String callingPackage, int uid)
+            throws NameNotFoundException {
+        PackageManager pm = context.getPackageManager();
+
+        CharSequence appLabel = pm.getApplicationInfoAsUser(callingPackage,
+                PackageManager.MATCH_DIRECT_BOOT_AUTO,
+                UserHandle.getUserId(uid)).loadLabel(pm);
+
+        String titleString = context.getString(R.string.log_access_confirmation_title, appLabel);
+
+        return titleString;
+    }
+
+    private Spannable styleFont(String text) {
+        Spannable s = (Spannable) Html.fromHtml(text);
+        for (URLSpan span : s.getSpans(0, s.length(), URLSpan.class)) {
+            TypefaceSpan typefaceSpan = new TypefaceSpan("google-sans");
+            s.setSpan(typefaceSpan, s.getSpanStart(span), s.getSpanEnd(span), 0);
+        }
+        return s;
+    }
+
+    /**
+     * Returns the dialog view.
+     * If we cannot retrieve the package name, it returns null and we decline the full device log
+     * access
+     */
+    private View createView(@StyleRes int themeId) {
+        Context themedContext = new ContextThemeWrapper(this, themeId);
+        final View view = LayoutInflater.from(themedContext).inflate(
+                R.layout.log_access_user_consent_dialog_permission, null /*root*/);
+
+        if (view == null) {
+            throw new InflateException();
+        }
+
+        ((TextView) view.findViewById(R.id.log_access_dialog_title))
+            .setText(mAlertTitle);
+
+        if (!TextUtils.isEmpty(mAlertLearnMore)) {
+            Spannable mSpannableLearnMore = styleFont(mAlertLearnMore);
+
+            ((TextView) view.findViewById(R.id.log_access_dialog_body))
+                    .setText(TextUtils.concat(mAlertBody, "\n\n", mSpannableLearnMore));
+
+            ((TextView) view.findViewById(R.id.log_access_dialog_body))
+                    .setMovementMethod(LinkMovementMethod.getInstance());
+        } else {
+            ((TextView) view.findViewById(R.id.log_access_dialog_body))
+                    .setText(mAlertBody);
+        }
+
+        Button button_allow = (Button) view.findViewById(R.id.log_access_dialog_allow_button);
+        button_allow.setOnClickListener(this);
+
+        Button button_deny = (Button) view.findViewById(R.id.log_access_dialog_deny_button);
+        button_deny.setOnClickListener(this);
+
+        return view;
+
+    }
+
+    @Override
+    public void onClick(View view) {
+        try {
+            if (view.getId() == R.id.log_access_dialog_allow_button) {
+                mCallback.approveAccessForClient(mUid, mPackageName);
+                finish();
+            } else if (view.getId() == R.id.log_access_dialog_allow_button) {
+                declineLogAccess();
+                finish();
+            }
+        } catch (RemoteException e) {
+            finish();
+        }
+    }
+
+    private void declineLogAccess() {
+        try {
+            mCallback.declineAccessForClient(mUid, mPackageName);
+        } catch (RemoteException e) {
+            finish();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/logcat/OWNERS b/packages/SystemUI/src/com/android/systemui/logcat/OWNERS
new file mode 100644
index 0000000..9c0c414
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/logcat/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1218649
+file:platform/frameworks/base:/services/core/java/com/android/server/logcat/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index be357ee..ceb4845 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -79,8 +79,7 @@
         val queryIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
         intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
 
-        // TODO(b/240939253): update copies
-        val title = getString(R.string.media_projection_dialog_service_title)
+        val title = getString(R.string.media_projection_permission_app_selector_title)
         intent.putExtra(Intent.EXTRA_TITLE, title)
         super.onCreate(bundle)
         controller.init()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index 397bffc..bfa67a8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -18,6 +18,9 @@
 
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
+import static com.android.systemui.screenrecord.ScreenShareOptionKt.ENTIRE_SCREEN;
+import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP;
+
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.DialogInterface;
@@ -44,6 +47,8 @@
 import com.android.systemui.R;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.screenrecord.MediaProjectionPermissionDialog;
+import com.android.systemui.screenrecord.ScreenShareOption;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.Utils;
 
@@ -102,6 +107,7 @@
 
         CharSequence dialogText = null;
         CharSequence dialogTitle = null;
+        String appName = null;
         if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) {
             dialogText = getString(R.string.media_projection_dialog_service_text);
             dialogTitle = getString(R.string.media_projection_dialog_service_title);
@@ -132,7 +138,7 @@
 
             String unsanitizedAppName = TextUtils.ellipsize(label,
                     paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
-            String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
+            appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
 
             String actionText = getString(R.string.media_projection_dialog_text, appName);
             SpannableString message = new SpannableString(actionText);
@@ -146,27 +152,28 @@
             dialogTitle = getString(R.string.media_projection_dialog_title, appName);
         }
 
-        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
-                R.style.Theme_SystemUI_Dialog)
-                .setTitle(dialogTitle)
-                .setIcon(R.drawable.ic_media_projection_permission)
-                .setMessage(dialogText)
-                .setPositiveButton(R.string.media_projection_action_text, this)
-                .setNeutralButton(android.R.string.cancel, this)
-                .setOnCancelListener(this);
-
         if (isPartialScreenSharingEnabled()) {
-            // This is a temporary entry point before we have a new permission dialog
-            // TODO(b/233183090): this activity should be redesigned to have a dropdown selector
-            dialogBuilder.setNegativeButton("App", this);
+            mDialog = new MediaProjectionPermissionDialog(this, () -> {
+                ScreenShareOption selectedOption =
+                        ((MediaProjectionPermissionDialog) mDialog).getSelectedScreenShareOption();
+                grantMediaProjectionPermission(selectedOption.getMode());
+            }, appName);
+        } else {
+            AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
+                    R.style.Theme_SystemUI_Dialog)
+                    .setTitle(dialogTitle)
+                    .setIcon(R.drawable.ic_media_projection_permission)
+                    .setMessage(dialogText)
+                    .setPositiveButton(R.string.media_projection_action_text, this)
+                    .setNeutralButton(android.R.string.cancel, this);
+            mDialog = dialogBuilder.create();
         }
 
-        mDialog = dialogBuilder.create();
-
         SystemUIDialog.registerDismissListener(mDialog);
         SystemUIDialog.applyFlags(mDialog);
         SystemUIDialog.setDialogSize(mDialog);
 
+        mDialog.setOnCancelListener(this);
         mDialog.create();
         mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
 
@@ -186,12 +193,17 @@
 
     @Override
     public void onClick(DialogInterface dialog, int which) {
+        if (which == AlertDialog.BUTTON_POSITIVE) {
+            grantMediaProjectionPermission(ENTIRE_SCREEN);
+        }
+    }
+
+    private void grantMediaProjectionPermission(int screenShareMode) {
         try {
-            if (which == AlertDialog.BUTTON_POSITIVE) {
+            if (screenShareMode == ENTIRE_SCREEN) {
                 setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
             }
-
-            if (isPartialScreenSharingEnabled() && which == AlertDialog.BUTTON_NEGATIVE) {
+            if (isPartialScreenSharingEnabled() && screenShareMode == SINGLE_APP) {
                 IMediaProjection projection = createProjection(mUid, mPackageName);
                 final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class);
                 intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
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..a8f39fa9a 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,8 @@
 import androidx.constraintlayout.widget.Barrier
 import com.android.systemui.R
 import com.android.systemui.media.controls.models.GutsViewHolder
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
 import com.android.systemui.util.animation.TransitionLayout
 
 private const val TAG = "MediaViewHolder"
@@ -36,6 +38,9 @@
 
     // Player information
     val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
+    val multiRippleView = itemView.requireViewById<MultiRippleView>(R.id.touch_ripple_view)
+    val turbulenceNoiseView =
+        itemView.requireViewById<TurbulenceNoiseView>(R.id.turbulence_noise_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/models/recommendation/SmartspaceMediaDataProvider.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
index a7ed69a..cacb3e2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
@@ -29,7 +29,6 @@
 
     private val smartspaceMediaTargetListeners: MutableList<SmartspaceTargetListener> =
         mutableListOf()
-    private var smartspaceMediaTargets: List<SmartspaceTarget> = listOf()
 
     override fun registerListener(smartspaceTargetListener: SmartspaceTargetListener) {
         smartspaceMediaTargetListeners.add(smartspaceTargetListener)
@@ -41,22 +40,7 @@
 
     /** Updates Smartspace data and propagates it to any listeners. */
     override fun onTargetsAvailable(targets: List<SmartspaceTarget>) {
-        // Filter out non-media targets.
-        val mediaTargets = mutableListOf<SmartspaceTarget>()
-        for (target in targets) {
-            val smartspaceTarget = target
-            if (smartspaceTarget.featureType == SmartspaceTarget.FEATURE_MEDIA) {
-                mediaTargets.add(smartspaceTarget)
-            }
-        }
-
-        if (!mediaTargets.isEmpty()) {
-            Log.d(TAG, "Forwarding Smartspace media updates $mediaTargets")
-        }
-
-        smartspaceMediaTargets = mediaTargets
-        smartspaceMediaTargetListeners.forEach {
-            it.onSmartspaceTargetsUpdated(smartspaceMediaTargets)
-        }
+        Log.d(TAG, "Forwarding Smartspace updates $targets")
+        smartspaceMediaTargetListeners.forEach { it.onSmartspaceTargetsUpdated(targets) }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index 45b319b..cf71d67 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -20,13 +20,12 @@
 import android.os.SystemProperties
 import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
 import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.util.time.SystemClock
 import java.util.SortedMap
@@ -62,14 +61,13 @@
 @Inject
 constructor(
     private val context: Context,
-    private val broadcastDispatcher: BroadcastDispatcher,
+    private val userTracker: UserTracker,
     private val broadcastSender: BroadcastSender,
     private val lockscreenUserManager: NotificationLockscreenUserManager,
     @Main private val executor: Executor,
     private val systemClock: SystemClock,
     private val logger: MediaUiEventLogger
 ) : MediaDataManager.Listener {
-    private val userTracker: CurrentUserTracker
     private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
     internal val listeners: Set<MediaDataManager.Listener>
         get() = _listeners.toSet()
@@ -81,15 +79,15 @@
     private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
     private var reactivatedKey: String? = null
 
-    init {
-        userTracker =
-            object : CurrentUserTracker(broadcastDispatcher) {
-                override fun onUserSwitched(newUserId: Int) {
-                    // Post this so we can be sure lockscreenUserManager already got the broadcast
-                    executor.execute { handleUserSwitched(newUserId) }
-                }
+    private val userTrackerCallback =
+        object : UserTracker.Callback {
+            override fun onUserChanged(newUser: Int, userContext: Context) {
+                handleUserSwitched(newUser)
             }
-        userTracker.startTracking()
+        }
+
+    init {
+        userTracker.addCallback(userTrackerCallback, executor)
     }
 
     override fun onMediaDataLoaded(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 14dd990..2dd339d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -82,7 +82,6 @@
 import java.io.IOException
 import java.io.PrintWriter
 import java.util.concurrent.Executor
-import java.util.concurrent.Executors
 import javax.inject.Inject
 
 // URI fields to try loading album art from
@@ -154,6 +153,7 @@
 class MediaDataManager(
     private val context: Context,
     @Background private val backgroundExecutor: Executor,
+    @Main private val uiExecutor: Executor,
     @Main private val foregroundExecutor: DelayableExecutor,
     private val mediaControllerFactory: MediaControllerFactory,
     private val broadcastDispatcher: BroadcastDispatcher,
@@ -171,7 +171,8 @@
     private val systemClock: SystemClock,
     private val tunerService: TunerService,
     private val mediaFlags: MediaFlags,
-    private val logger: MediaUiEventLogger
+    private val logger: MediaUiEventLogger,
+    private val smartspaceManager: SmartspaceManager,
 ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
     companion object {
@@ -218,6 +219,7 @@
     constructor(
         context: Context,
         @Background backgroundExecutor: Executor,
+        @Main uiExecutor: Executor,
         @Main foregroundExecutor: DelayableExecutor,
         mediaControllerFactory: MediaControllerFactory,
         dumpManager: DumpManager,
@@ -233,10 +235,12 @@
         clock: SystemClock,
         tunerService: TunerService,
         mediaFlags: MediaFlags,
-        logger: MediaUiEventLogger
+        logger: MediaUiEventLogger,
+        smartspaceManager: SmartspaceManager,
     ) : this(
         context,
         backgroundExecutor,
+        uiExecutor,
         foregroundExecutor,
         mediaControllerFactory,
         broadcastDispatcher,
@@ -254,7 +258,8 @@
         clock,
         tunerService,
         mediaFlags,
-        logger
+        logger,
+        smartspaceManager,
     )
 
     private val appChangeReceiver =
@@ -314,21 +319,18 @@
 
         // Register for Smartspace data updates.
         smartspaceMediaDataProvider.registerListener(this)
-        val smartspaceManager: SmartspaceManager =
-            context.getSystemService(SmartspaceManager::class.java)
         smartspaceSession =
             smartspaceManager.createSmartspaceSession(
                 SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
             )
         smartspaceSession?.let {
             it.addOnTargetsAvailableListener(
-                // Use a new thread listening to Smartspace updates instead of using the existing
-                // backgroundExecutor. SmartspaceSession has scheduled routine updates which can be
-                // unpredictable on test simulators, using the backgroundExecutor makes it's hard to
-                // test the threads numbers.
-                // Switch to use backgroundExecutor when SmartspaceSession has a good way to be
-                // mocked.
-                Executors.newCachedThreadPool(),
+                // Use a main uiExecutor thread listening to Smartspace updates instead of using
+                // the existing background executor.
+                // SmartspaceSession has scheduled routine updates which can be unpredictable on
+                // test simulators, using the backgroundExecutor makes it's hard to test the threads
+                // numbers.
+                uiExecutor,
                 SmartspaceSession.OnTargetsAvailableListener { targets ->
                     smartspaceMediaDataProvider.onTargetsAvailable(targets)
                 }
@@ -420,6 +422,7 @@
                     appUid = appUid
                 )
             mediaEntries.put(packageName, resumeData)
+            logSingleVsMultipleMediaAdded(appUid, packageName, instanceId)
             logger.logResumeMediaAdded(appUid, packageName, instanceId)
         }
         backgroundExecutor.execute {
@@ -810,6 +813,7 @@
         val appUid = appInfo?.uid ?: Process.INVALID_UID
 
         if (logEvent) {
+            logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId)
             logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
         } else if (playbackLocation != currentEntry?.playbackLocation) {
             logger.logPlaybackLocationChange(appUid, sbn.packageName, instanceId, playbackLocation)
@@ -853,6 +857,20 @@
         }
     }
 
+    private fun logSingleVsMultipleMediaAdded(
+        appUid: Int,
+        packageName: String,
+        instanceId: InstanceId
+    ) {
+        if (mediaEntries.size == 1) {
+            logger.logSingleMediaPlayerInCarousel(appUid, packageName, instanceId)
+        } else if (mediaEntries.size == 2) {
+            // Since this method is only called when there is a new media session added.
+            // logging needed once there is more than one media session in carousel.
+            logger.logMultipleMediaPlayersInCarousel(appUid, packageName, instanceId)
+        }
+    }
+
     private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
         try {
             return context.packageManager.getApplicationInfo(packageName, 0)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index 4891297..2d10b82 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -32,10 +32,12 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.Utils
 import com.android.systemui.util.time.SystemClock
@@ -55,6 +57,8 @@
 constructor(
     private val context: Context,
     private val broadcastDispatcher: BroadcastDispatcher,
+    private val userTracker: UserTracker,
+    @Main private val mainExecutor: Executor,
     @Background private val backgroundExecutor: Executor,
     private val tunerService: TunerService,
     private val mediaBrowserFactory: ResumeMediaBrowserFactory,
@@ -77,18 +81,26 @@
     private var currentUserId: Int = context.userId
 
     @VisibleForTesting
-    val userChangeReceiver =
+    val userUnlockReceiver =
         object : BroadcastReceiver() {
             override fun onReceive(context: Context, intent: Intent) {
                 if (Intent.ACTION_USER_UNLOCKED == intent.action) {
-                    loadMediaResumptionControls()
-                } else if (Intent.ACTION_USER_SWITCHED == intent.action) {
-                    currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
-                    loadSavedComponents()
+                    val userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
+                    if (userId == currentUserId) {
+                        loadMediaResumptionControls()
+                    }
                 }
             }
         }
 
+    private val userTrackerCallback =
+        object : UserTracker.Callback {
+            override fun onUserChanged(newUser: Int, userContext: Context) {
+                currentUserId = newUser
+                loadSavedComponents()
+            }
+        }
+
     private val mediaBrowserCallback =
         object : ResumeMediaBrowser.Callback() {
             override fun addTrack(
@@ -126,13 +138,13 @@
             dumpManager.registerDumpable(TAG, this)
             val unlockFilter = IntentFilter()
             unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED)
-            unlockFilter.addAction(Intent.ACTION_USER_SWITCHED)
             broadcastDispatcher.registerReceiver(
-                userChangeReceiver,
+                userUnlockReceiver,
                 unlockFilter,
                 null,
                 UserHandle.ALL
             )
+            userTracker.addCallback(userTrackerCallback, mainExecutor)
             loadSavedComponents()
         }
     }
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..93be6a7 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,8 @@
 import com.android.settingslib.Utils
 import com.android.systemui.media.controls.models.player.MediaViewHolder
 import com.android.systemui.monet.ColorScheme
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
 
 /**
  * A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
@@ -100,12 +102,22 @@
 internal constructor(
     private val context: Context,
     private val mediaViewHolder: MediaViewHolder,
+    private val multiRippleController: MultiRippleController,
+    private val turbulenceNoiseController: TurbulenceNoiseController,
     animatingColorTransitionFactory: AnimatingColorTransitionFactory
 ) {
     constructor(
         context: Context,
-        mediaViewHolder: MediaViewHolder
-    ) : this(context, mediaViewHolder, ::AnimatingColorTransition)
+        mediaViewHolder: MediaViewHolder,
+        multiRippleController: MultiRippleController,
+        turbulenceNoiseController: TurbulenceNoiseController
+    ) : this(
+        context,
+        mediaViewHolder,
+        multiRippleController,
+        turbulenceNoiseController,
+        ::AnimatingColorTransition
+    )
 
     val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
     val surfaceColor =
@@ -125,6 +137,8 @@
             val accentColorList = ColorStateList.valueOf(accentPrimary)
             mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
             mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
+            multiRippleController.updateColor(accentPrimary)
+            turbulenceNoiseController.updateNoiseColor(accentPrimary)
         }
 
     val accentSecondary =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index e38c1ba..1fdbc99 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -184,6 +184,7 @@
 
     private val configListener =
         object : ConfigurationController.ConfigurationListener {
+
             override fun onDensityOrFontScaleChanged() {
                 // System font changes should only happen when UMO is offscreen or a flicker may
                 // occur
@@ -199,6 +200,7 @@
             override fun onConfigChanged(newConfig: Configuration?) {
                 if (newConfig == null) return
                 isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL
+                updatePlayers(recreateMedia = true)
             }
 
             override fun onUiModeChanged() {
@@ -610,7 +612,12 @@
             // are
             // elements in mediaPlayers.
             if (MediaPlayerData.players().size != mediaContent.childCount) {
-                Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
+                Log.e(
+                    TAG,
+                    "Size of players list and number of views in carousel are out of sync. " +
+                        "Players size is ${MediaPlayerData.players().size}. " +
+                        "View count is ${mediaContent.childCount}."
+                )
             }
             return existingPlayer == null
         }
@@ -630,7 +637,7 @@
             val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey()
             existingSmartspaceMediaKey?.let {
                 val removedPlayer =
-                    MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey, true)
+                    removePlayer(existingSmartspaceMediaKey, dismissMediaData = false)
                 removedPlayer?.run {
                     debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
                 }
@@ -667,7 +674,12 @@
             // are
             // elements in mediaPlayers.
             if (MediaPlayerData.players().size != mediaContent.childCount) {
-                Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
+                Log.e(
+                    TAG,
+                    "Size of players list and number of views in carousel are out of sync. " +
+                        "Players size is ${MediaPlayerData.players().size}. " +
+                        "View count is ${mediaContent.childCount}."
+                )
             }
         }
 
@@ -675,7 +687,7 @@
         key: String,
         dismissMediaData: Boolean = true,
         dismissRecommendation: Boolean = true
-    ) {
+    ): MediaControlPanel? {
         if (key == MediaPlayerData.smartspaceMediaKey()) {
             MediaPlayerData.smartspaceMediaData?.let {
                 logger.logRecommendationRemoved(it.packageName, it.instanceId)
@@ -683,7 +695,7 @@
         }
         val removed =
             MediaPlayerData.removeMediaPlayer(key, dismissMediaData || dismissRecommendation)
-        removed?.apply {
+        return removed?.apply {
             mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
             mediaContent.removeView(removed.mediaViewHolder?.player)
             mediaContent.removeView(removed.recommendationViewHolder?.recommendations)
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..df8fb91 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
@@ -31,6 +31,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
+import android.graphics.BlendMode;
 import android.graphics.Color;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
@@ -64,6 +65,7 @@
 import androidx.constraintlayout.widget.ConstraintSet;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.InstanceId;
 import com.android.settingslib.widget.AdaptiveIcon;
@@ -76,6 +78,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;
@@ -98,6 +102,13 @@
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController;
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimation;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
+import com.android.systemui.surfaceeffects.ripple.RippleShader;
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig;
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController;
 import com.android.systemui.util.ColorUtilKt;
 import com.android.systemui.util.animation.TransitionLayout;
 import com.android.systemui.util.time.SystemClock;
@@ -209,6 +220,10 @@
     private boolean mIsCurrentBroadcastedApp = false;
     private boolean mShowBroadcastDialogButton = false;
     private String mSwitchBroadcastApp;
+    private MultiRippleController mMultiRippleController;
+    private TurbulenceNoiseController mTurbulenceNoiseController;
+    private FeatureFlags mFeatureFlags;
+    private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig = null;
 
     /**
      * Initialize a new control panel
@@ -236,7 +251,9 @@
             KeyguardStateController keyguardStateController,
             ActivityIntentHelper activityIntentHelper,
             NotificationLockscreenUserManager lockscreenUserManager,
-            BroadcastDialogController broadcastDialogController) {
+            BroadcastDialogController broadcastDialogController,
+            FeatureFlags featureFlags
+    ) {
         mContext = context;
         mBackgroundExecutor = backgroundExecutor;
         mMainExecutor = mainExecutor;
@@ -262,6 +279,8 @@
             logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
             return Unit.INSTANCE;
         });
+
+        mFeatureFlags = featureFlags;
     }
 
     /**
@@ -361,6 +380,7 @@
         mMediaViewController.attach(player, MediaViewController.TYPE.PLAYER);
 
         vh.getPlayer().setOnLongClickListener(v -> {
+            if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
             if (!mMediaViewController.isGutsVisible()) {
                 openGuts();
                 return true;
@@ -381,7 +401,20 @@
         AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit,
                 Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText);
 
-        mColorSchemeTransition = new ColorSchemeTransition(mContext, mMediaViewHolder);
+        MultiRippleView multiRippleView = vh.getMultiRippleView();
+        mMultiRippleController = new MultiRippleController(multiRippleView);
+        mTurbulenceNoiseController = new TurbulenceNoiseController(vh.getTurbulenceNoiseView());
+        multiRippleView.addRipplesFinishedListener(
+                () -> {
+                    if (mTurbulenceNoiseAnimationConfig == null) {
+                        mTurbulenceNoiseAnimationConfig = createLingeringNoiseAnimation();
+                    }
+                    // Color will be correctly updated in ColorSchemeTransition.
+                    mTurbulenceNoiseController.play(mTurbulenceNoiseAnimationConfig);
+                }
+        );
+        mColorSchemeTransition = new ColorSchemeTransition(
+                mContext, mMediaViewHolder, mMultiRippleController, mTurbulenceNoiseController);
         mMetadataAnimationHandler = new MetadataAnimationHandler(exit, enter);
     }
 
@@ -409,6 +442,7 @@
         mMediaViewController.attach(recommendations, MediaViewController.TYPE.RECOMMENDATION);
 
         mRecommendationViewHolder.getRecommendations().setOnLongClickListener(v -> {
+            if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
             if (!mMediaViewController.isGutsVisible()) {
                 openGuts();
                 return true;
@@ -424,7 +458,9 @@
         if (mMediaViewHolder == null) {
             return;
         }
-        Trace.beginSection("MediaControlPanel#bindPlayer<" + key + ">");
+        if (Trace.isEnabled()) {
+            Trace.traceBegin(Trace.TRACE_TAG_APP, "MediaControlPanel#bindPlayer<" + key + ">");
+        }
         mKey = key;
         mMediaData = data;
         MediaSession.Token token = data.getToken();
@@ -555,7 +591,10 @@
         seamlessView.setContentDescription(deviceString);
         seamlessView.setOnClickListener(
                 v -> {
-                    if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                    if (mFalsingManager.isFalseTap(
+                            mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+                                    ? FalsingManager.MODERATE_PENALTY :
+                                    FalsingManager.LOW_PENALTY)) {
                         return;
                     }
 
@@ -901,19 +940,9 @@
         if (mIsSeekBarEnabled) {
             return ConstraintSet.VISIBLE;
         }
-        // If disabled and "neighbours" are visible, set progress bar to INVISIBLE instead of GONE
-        // so layout weights still work.
-        return areAnyExpandedBottomActionsVisible() ? ConstraintSet.INVISIBLE : ConstraintSet.GONE;
-    }
-
-    private boolean areAnyExpandedBottomActionsVisible() {
-        ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
-        for (int id : MediaViewHolder.Companion.getExpandedBottomActionIds()) {
-            if (expandedSet.getVisibility(id) == ConstraintSet.VISIBLE) {
-                return true;
-            }
-        }
-        return false;
+        // Set progress bar to INVISIBLE to keep the positions of text and buttons similar to the
+        // original positions when seekbar is enabled.
+        return ConstraintSet.INVISIBLE;
     }
 
     private void setGenericButton(
@@ -978,10 +1007,16 @@
             } else {
                 button.setEnabled(true);
                 button.setOnClickListener(v -> {
-                    if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                    if (!mFalsingManager.isFalseTap(
+                            mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+                                    ? FalsingManager.MODERATE_PENALTY :
+                                    FalsingManager.LOW_PENALTY)) {
                         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 +1032,46 @@
         }
     }
 
+    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().getCurrentColor(),
+                        /* opacity= */ 100,
+                        /* shouldFillRipple= */ false,
+                        /* sparkleStrength= */ 0f,
+                        /* shouldDistort= */ false
+                )
+        );
+    }
+
+    private TurbulenceNoiseAnimationConfig createLingeringNoiseAnimation() {
+        return new TurbulenceNoiseAnimationConfig(
+                TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_GRID_COUNT,
+                TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
+                /* noiseMoveSpeedX= */ 0f,
+                /* noiseMoveSpeedY= */ 0f,
+                TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
+                /* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
+                // We want to add (BlendMode.PLUS) the turbulence noise on top of the album art.
+                // Thus, set the background color with alpha 0.
+                /* backgroundColor= */ ColorUtils.setAlphaComponent(Color.BLACK, 0),
+                TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY,
+                /* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
+                /* height= */ mMediaViewHolder.getMultiRippleView().getHeight(),
+                TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_DURATION_IN_MILLIS,
+                this.getContext().getResources().getDisplayMetrics().density,
+                BlendMode.PLUS,
+                /* onAnimationEnd= */ null
+        );
+    }
     private void clearButton(final ImageButton button) {
         button.setImageDrawable(null);
         button.setContentDescription(null);
@@ -1096,8 +1171,10 @@
             return;
         }
 
-        Trace.beginSection(
-                "MediaControlPanel#bindRecommendation<" + data.getPackageName() + ">");
+        if (Trace.isEnabled()) {
+            Trace.traceBegin(Trace.TRACE_TAG_APP,
+                    "MediaControlPanel#bindRecommendation<" + data.getPackageName() + ">");
+        }
 
         mRecommendationData = data;
         mSmartspaceId = SmallHash.hash(data.getTargetId());
@@ -1154,6 +1231,7 @@
             setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation, itemIndex);
             // Bubble up the long-click event to the card.
             mediaCoverContainer.setOnLongClickListener(v -> {
+                if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
                 View parent = (View) v.getParent();
                 if (parent != null) {
                     parent.performLongClick();
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 cbb670e..f7a9bc7 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
@@ -799,6 +799,16 @@
         }
 
         if (
+            desiredLocation == LOCATION_QS &&
+                previousLocation == LOCATION_LOCKSCREEN &&
+                statusbarState == StatusBarState.SHADE
+        ) {
+            // This is an invalid transition, can happen when tapping on home control and the UMO
+            // while being on landscape orientation in tablet.
+            return false
+        }
+
+        if (
             statusbarState == StatusBarState.KEYGUARD &&
                 (currentLocation == LOCATION_LOCKSCREEN || previousLocation == LOCATION_LOCKSCREEN)
         ) {
@@ -1043,18 +1053,9 @@
                     rootOverlay!!.add(mediaFrame)
                 } else {
                     val targetHost = getHost(newLocation)!!.hostView
-                    // When adding back to the host, let's make sure to reset the bounds.
-                    // Usually adding the view will trigger a layout that does this automatically,
-                    // but we sometimes suppress this.
+                    // This will either do a full layout pass and remeasure, or it will bypass
+                    // that and directly set the mediaFrame's bounds within the premeasured host.
                     targetHost.addView(mediaFrame)
-                    val left = targetHost.paddingLeft
-                    val top = targetHost.paddingTop
-                    mediaFrame.setLeftTopRightBottom(
-                        left,
-                        top,
-                        left + currentBounds.width(),
-                        top + currentBounds.height()
-                    )
 
                     if (mediaFrame.childCount > 0) {
                         val child = mediaFrame.getChildAt(0)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 4bf3031..4feb984 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -420,7 +420,9 @@
      */
     fun getMeasurementsForState(hostState: MediaHostState): MeasurementOutput? =
         traceSection("MediaViewController#getMeasurementsForState") {
-            val viewState = obtainViewState(hostState) ?: return null
+            // measurements should never factor in the squish fraction
+            val viewState =
+                obtainViewState(hostState.copy().also { it.squishFraction = 1.0f }) ?: return null
             measurement.measuredWidth = viewState.width
             measurement.measuredHeight = viewState.height
             return measurement
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
index 3ad8c21..ea943be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
@@ -213,6 +213,24 @@
             instanceId
         )
     }
+
+    fun logSingleMediaPlayerInCarousel(uid: Int, packageName: String, instanceId: InstanceId) {
+        logger.logWithInstanceId(
+            MediaUiEvent.MEDIA_CAROUSEL_SINGLE_PLAYER,
+            uid,
+            packageName,
+            instanceId
+        )
+    }
+
+    fun logMultipleMediaPlayersInCarousel(uid: Int, packageName: String, instanceId: InstanceId) {
+        logger.logWithInstanceId(
+            MediaUiEvent.MEDIA_CAROUSEL_MULTIPLE_PLAYERS,
+            uid,
+            packageName,
+            instanceId
+        )
+    }
 }
 
 enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@@ -269,7 +287,11 @@
     @UiEvent(doc = "User tapped on a media recommendation card")
     MEDIA_RECOMMENDATION_CARD_TAP(1045),
     @UiEvent(doc = "User opened the broadcast dialog from a media control")
-    MEDIA_OPEN_BROADCAST_DIALOG(1079);
+    MEDIA_OPEN_BROADCAST_DIALOG(1079),
+    @UiEvent(doc = "The media carousel contains one media player card")
+    MEDIA_CAROUSEL_SINGLE_PLAYER(1244),
+    @UiEvent(doc = "The media carousel contains multiple media player cards")
+    MEDIA_CAROUSEL_MULTIPLE_PLAYERS(1245);
 
     override fun getId() = metricId
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
index dd9d35b..55fce59 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
@@ -35,25 +35,40 @@
     private val mediaOutputBroadcastDialogFactory: MediaOutputBroadcastDialogFactory
 ) : BroadcastReceiver() {
     override fun onReceive(context: Context, intent: Intent) {
-        if (TextUtils.equals(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG,
-                        intent.action)) {
-            val packageName: String? =
-                    intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME)
-            if (!TextUtils.isEmpty(packageName)) {
-                mediaOutputDialogFactory.create(packageName!!, false)
-            } else if (DEBUG) {
-                Log.e(TAG, "Unable to launch media output dialog. Package name is empty.")
+        when {
+            TextUtils.equals(Intent.ACTION_SHOW_OUTPUT_SWITCHER, intent.action) -> {
+                val packageName: String? = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME)
+                launchMediaOutputDialogIfPossible(packageName)
             }
-        } else if (TextUtils.equals(
-                    MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG,
-                    intent.action)) {
-            val packageName: String? =
+            TextUtils.equals(
+                MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG, intent.action) -> {
+                val packageName: String? =
                     intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME)
-            if (!TextUtils.isEmpty(packageName)) {
-                mediaOutputBroadcastDialogFactory.create(packageName!!, false)
-            } else if (DEBUG) {
-                Log.e(TAG, "Unable to launch media output broadcast dialog. Package name is empty.")
+                launchMediaOutputDialogIfPossible(packageName)
             }
+            TextUtils.equals(
+                MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG,
+                intent.action) -> {
+                val packageName: String? =
+                    intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME)
+                launchMediaOutputBroadcastDialogIfPossible(packageName)
+            }
+        }
+    }
+
+    private fun launchMediaOutputDialogIfPossible(packageName: String?) {
+        if (!packageName.isNullOrEmpty()) {
+            mediaOutputDialogFactory.create(packageName, false)
+        } else if (DEBUG) {
+            Log.e(TAG, "Unable to launch media output dialog. Package name is empty.")
+        }
+    }
+
+    private fun launchMediaOutputBroadcastDialogIfPossible(packageName: String?) {
+        if (!packageName.isNullOrEmpty()) {
+            mediaOutputBroadcastDialogFactory.create(packageName, false)
+        } else if (DEBUG) {
+            Log.e(TAG, "Unable to launch media output broadcast dialog. Package name is empty.")
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index a4a96806..b10abb5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -48,52 +48,66 @@
     /** All commands for the sender device. */
     inner class SenderCommand : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
-            val commandName = args[1]
+            if (args.size < 2) {
+                help(pw)
+                return
+            }
+
+            val senderArgs = processArgs(args)
+
             @StatusBarManager.MediaTransferSenderState
             val displayState: Int?
             try {
-                displayState = ChipStateSender.getSenderStateIdFromName(commandName)
+                displayState = ChipStateSender.getSenderStateIdFromName(senderArgs.commandName)
             } catch (ex: IllegalArgumentException) {
-                pw.println("Invalid command name $commandName")
+                pw.println("Invalid command name ${senderArgs.commandName}")
                 return
             }
 
             @SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE
             val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
                     as StatusBarManager
-            val routeInfo = MediaRoute2Info.Builder("id", args[0])
+            val routeInfo = MediaRoute2Info.Builder(senderArgs.id, senderArgs.deviceName)
                     .addFeature("feature")
-            val useAppIcon = !(args.size >= 3 && args[2] == "useAppIcon=false")
-            if (useAppIcon) {
+            if (senderArgs.useAppIcon) {
                 routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
             }
 
+            var undoExecutor: Executor? = null
+            var undoRunnable: Runnable? = null
+            if (isSucceededState(displayState) && senderArgs.showUndo) {
+                undoExecutor = mainExecutor
+                undoRunnable = Runnable { Log.i(CLI_TAG, "Undo triggered for $displayState") }
+            }
+
             statusBarManager.updateMediaTapToTransferSenderDisplay(
                 displayState,
                 routeInfo.build(),
-                getUndoExecutor(displayState),
-                getUndoCallback(displayState)
+                undoExecutor,
+                undoRunnable,
             )
         }
 
-        private fun getUndoExecutor(
-            @StatusBarManager.MediaTransferSenderState displayState: Int
-        ): Executor? {
-            return if (isSucceededState(displayState)) {
-                mainExecutor
-            } else {
-                null
-            }
-        }
+        private fun processArgs(args: List<String>): SenderArgs {
+            val senderArgs = SenderArgs(
+                deviceName = args[0],
+                commandName = args[1],
+            )
 
-        private fun getUndoCallback(
-            @StatusBarManager.MediaTransferSenderState displayState: Int
-        ): Runnable? {
-            return if (isSucceededState(displayState)) {
-                Runnable { Log.i(CLI_TAG, "Undo triggered for $displayState") }
-            } else {
-                null
+            if (args.size == 2) {
+                return senderArgs
             }
+
+            // Process any optional arguments
+            args.subList(2, args.size).forEach {
+                when {
+                    it == "useAppIcon=false" -> senderArgs.useAppIcon = false
+                    it == "showUndo=false" -> senderArgs.showUndo = false
+                    it.substring(0, 3) == "id=" -> senderArgs.id = it.substring(3)
+                }
+            }
+
+            return senderArgs
         }
 
         private fun isSucceededState(
@@ -106,14 +120,31 @@
         }
 
         override fun help(pw: PrintWriter) {
-            pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND " +
-                    "<deviceName> <chipState> useAppIcon=[true|false]")
+            pw.println(
+                "Usage: adb shell cmd statusbar $SENDER_COMMAND " +
+                "<deviceName> <chipState> " +
+                "useAppIcon=[true|false] id=<id> showUndo=[true|false]"
+            )
+            pw.println("Note: useAppIcon, id, and showUndo are optional additional commands.")
         }
     }
 
+    private data class SenderArgs(
+        val deviceName: String,
+        val commandName: String,
+        var id: String = "id",
+        var useAppIcon: Boolean = true,
+        var showUndo: Boolean = true,
+    )
+
     /** All commands for the receiver device. */
     inner class ReceiverCommand : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
+            if (args.isEmpty()) {
+                help(pw)
+                return
+            }
+
             val commandName = args[0]
             @StatusBarManager.MediaTransferReceiverState
             val displayState: Int?
@@ -127,8 +158,10 @@
             @SuppressLint("WrongConstant") // sysui is allowed to call STATUS_BAR_SERVICE
             val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
                     as StatusBarManager
-            val routeInfo = MediaRoute2Info.Builder("id", "Test Name")
-                .addFeature("feature")
+            val routeInfo = MediaRoute2Info.Builder(
+                if (args.size >= 3) args[2] else "id",
+                "Test Name"
+            ).addFeature("feature")
             val useAppIcon = !(args.size >= 2 && args[1] == "useAppIcon=false")
             if (useAppIcon) {
                 routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
@@ -144,7 +177,7 @@
 
         override fun help(pw: PrintWriter) {
             pw.println("Usage: adb shell cmd statusbar $RECEIVER_COMMAND " +
-                    "<chipState> useAppIcon=[true|false]")
+                    "<chipState> useAppIcon=[true|false] <id>")
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index 120f7d6..b55bedd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -58,6 +58,27 @@
         )
     }
 
+    /**
+     * Logs an invalid sender state transition error in trying to update to [desiredState].
+     *
+     * @param currentState the previous state of the chip.
+     * @param desiredState the new state of the chip.
+     */
+    fun logInvalidStateTransitionError(
+        currentState: String,
+        desiredState: String
+    ) {
+        buffer.log(
+                tag,
+                LogLevel.ERROR,
+                {
+                    str1 = currentState
+                    str2 = desiredState
+                },
+                { "Cannot display state=$str2 after state=$str1; invalid transition" }
+        )
+    }
+
     /** Logs that we couldn't find information for [packageName]. */
     fun logPackageNotFound(packageName: String) {
         buffer.log(
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 7dd9fb4..cc5e256 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
@@ -45,6 +45,7 @@
 import com.android.systemui.util.animation.AnimationUtil.Companion.frames
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLock
 import javax.inject.Inject
 
 /**
@@ -55,7 +56,7 @@
  * TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator.
  */
 @SysUISingleton
-class MediaTttChipControllerReceiver @Inject constructor(
+open class MediaTttChipControllerReceiver @Inject constructor(
         private val commandQueue: CommandQueue,
         context: Context,
         @MediaTttReceiverLogger logger: MediaTttLogger,
@@ -68,6 +69,7 @@
         private val mediaTttFlags: MediaTttFlags,
         private val uiEventLogger: MediaTttReceiverUiEventLogger,
         private val viewUtil: ViewUtil,
+        wakeLockBuilder: WakeLock.Builder,
 ) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
         context,
         logger,
@@ -77,6 +79,7 @@
         configurationController,
         powerManager,
         R.layout.media_ttt_chip_receiver,
+        wakeLockBuilder,
 ) {
     @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
     override val windowLayoutParams = commonWindowLayoutParams.apply {
@@ -118,18 +121,32 @@
         uiEventLogger.logReceiverStateChange(chipState)
 
         if (chipState == ChipStateReceiver.FAR_FROM_SENDER) {
-            removeView(removalReason = ChipStateReceiver.FAR_FROM_SENDER.name)
+            removeView(routeInfo.id, removalReason = ChipStateReceiver.FAR_FROM_SENDER.name)
             return
         }
         if (appIcon == null) {
-            displayView(ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appName))
+            displayView(
+                ChipReceiverInfo(
+                    routeInfo,
+                    appIconDrawableOverride = null,
+                    appName,
+                    id = routeInfo.id,
+                )
+            )
             return
         }
 
         appIcon.loadDrawableAsync(
                 context,
                 Icon.OnDrawableLoadedListener { drawable ->
-                    displayView(ChipReceiverInfo(routeInfo, drawable, appName))
+                    displayView(
+                        ChipReceiverInfo(
+                            routeInfo,
+                            drawable,
+                            appName,
+                            id = routeInfo.id,
+                        )
+                    )
                 },
                 // Notify the listener on the main handler since the listener will update
                 // the UI.
@@ -166,15 +183,28 @@
         val appIconView = view.getAppIconView()
         appIconView.animate()
                 .translationYBy(-1 * getTranslationAmount().toFloat())
-                .setDuration(30.frames)
+                .setDuration(ICON_TRANSLATION_ANIM_DURATION)
                 .start()
         appIconView.animate()
                 .alpha(1f)
-                .setDuration(5.frames)
+                .setDuration(ICON_ALPHA_ANIM_DURATION)
                 .start()
         // Using withEndAction{} doesn't apply a11y focus when screen is unlocked.
         appIconView.postOnAnimation { view.requestAccessibilityFocus() }
-        startRipple(view.requireViewById(R.id.ripple))
+        expandRipple(view.requireViewById(R.id.ripple))
+    }
+
+    override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+        val appIconView = view.getAppIconView()
+        appIconView.animate()
+                .translationYBy(getTranslationAmount().toFloat())
+                .setDuration(ICON_TRANSLATION_ANIM_DURATION)
+                .start()
+        appIconView.animate()
+                .alpha(0f)
+                .setDuration(ICON_ALPHA_ANIM_DURATION)
+                .start()
+        (view.requireViewById(R.id.ripple) as ReceiverChipRippleView).collapseRipple(onAnimationEnd)
     }
 
     override fun getTouchableRegion(view: View, outRect: Rect) {
@@ -188,11 +218,22 @@
         return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
     }
 
-    private fun startRipple(rippleView: ReceiverChipRippleView) {
-        if (rippleView.rippleInProgress) {
+    private fun expandRipple(rippleView: ReceiverChipRippleView) {
+        if (rippleView.rippleInProgress()) {
             // Skip if ripple is still playing
             return
         }
+
+        // In case the device orientation changes, we need to reset the layout.
+        rippleView.addOnLayoutChangeListener (
+            View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
+                if (v == null) return@OnLayoutChangeListener
+
+                val layoutChangedRippleView = v as ReceiverChipRippleView
+                layoutRipple(layoutChangedRippleView)
+                layoutChangedRippleView.invalidate()
+            }
+        )
         rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
             override fun onViewDetachedFromWindow(view: View?) {}
 
@@ -202,7 +243,7 @@
                 }
                 val attachedRippleView = view as ReceiverChipRippleView
                 layoutRipple(attachedRippleView)
-                attachedRippleView.startRipple()
+                attachedRippleView.expandRipple()
                 attachedRippleView.removeOnAttachStateChangeListener(this)
             }
         })
@@ -225,10 +266,14 @@
     }
 }
 
+val ICON_TRANSLATION_ANIM_DURATION = 30.frames
+val ICON_ALPHA_ANIM_DURATION = 5.frames
+
 data class ChipReceiverInfo(
     val routeInfo: MediaRoute2Info,
     val appIconDrawableOverride: Drawable?,
     val appNameOverride: CharSequence?,
     override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER,
     override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER,
+    override val id: String,
 ) : TemporaryViewInfo()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index e354a03..6e9fc5c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -16,19 +16,47 @@
 
 package com.android.systemui.media.taptotransfer.receiver
 
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
 import android.content.Context
 import android.util.AttributeSet
-import com.android.systemui.ripple.RippleShader
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleShader
+import com.android.systemui.surfaceeffects.ripple.RippleView
 
 /**
  * An expanding ripple effect for the media tap-to-transfer receiver chip.
  */
 class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) {
+
+    // Indicates whether the ripple started expanding.
+    private var isStarted: Boolean
+
     init {
         setupShader(RippleShader.RippleShape.ELLIPSE)
         setRippleFill(true)
         setSparkleStrength(0f)
         duration = 3000L
+        isStarted = false
+    }
+
+    fun expandRipple(onAnimationEnd: Runnable? = null) {
+        isStarted = true
+        super.startRipple(onAnimationEnd)
+    }
+
+    /** Used to animate out the ripple. No-op if the ripple was never started via [startRipple]. */
+    fun collapseRipple(onAnimationEnd: Runnable? = null) {
+        if (!isStarted) {
+            return // Ignore if ripple is not started yet.
+        }
+        // Reset all listeners to animator.
+        animator.removeAllListeners()
+        animator.addListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator?) {
+                onAnimationEnd?.run()
+                isStarted = false
+            }
+        })
+        animator.reverse()
     }
 }
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 af7317c..1f27582 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
@@ -56,7 +56,12 @@
         R.string.media_move_closer_to_start_cast,
         transferStatus = TransferStatus.NOT_STARTED,
         endItem = null,
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+        }
+    },
 
     /**
      * A state representing that the two devices are close but not close enough to *end* a cast
@@ -70,7 +75,12 @@
         R.string.media_move_closer_to_end_cast,
         transferStatus = TransferStatus.NOT_STARTED,
         endItem = null,
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+        }
+    },
 
     /**
      * A state representing that a transfer to the receiver device has been initiated (but not
@@ -83,7 +93,13 @@
         transferStatus = TransferStatus.IN_PROGRESS,
         endItem = SenderEndItem.Loading,
         timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == TRANSFER_TO_RECEIVER_SUCCEEDED ||
+                    nextState == TRANSFER_TO_RECEIVER_FAILED
+        }
+    },
 
     /**
      * A state representing that a transfer from the receiver device and back to this device (the
@@ -96,7 +112,13 @@
         transferStatus = TransferStatus.IN_PROGRESS,
         endItem = SenderEndItem.Loading,
         timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_SUCCEEDED ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_FAILED
+        }
+    },
 
     /**
      * A state representing that a transfer to the receiver device has been successfully completed.
@@ -112,7 +134,13 @@
             newState =
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED
         ),
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == ALMOST_CLOSE_TO_START_CAST ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+        }
+    },
 
     /**
      * A state representing that a transfer back to this device has been successfully completed.
@@ -128,7 +156,13 @@
             newState =
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED
         ),
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == ALMOST_CLOSE_TO_END_CAST ||
+                    nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+        }
+    },
 
     /** A state representing that a transfer to the receiver device has failed. */
     TRANSFER_TO_RECEIVER_FAILED(
@@ -137,7 +171,13 @@
         R.string.media_transfer_failed,
         transferStatus = TransferStatus.FAILED,
         endItem = SenderEndItem.Error,
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == ALMOST_CLOSE_TO_START_CAST ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+        }
+    },
 
     /** A state representing that a transfer back to this device has failed. */
     TRANSFER_TO_THIS_DEVICE_FAILED(
@@ -146,7 +186,13 @@
         R.string.media_transfer_failed,
         transferStatus = TransferStatus.FAILED,
         endItem = SenderEndItem.Error,
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == ALMOST_CLOSE_TO_END_CAST ||
+                    nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+        }
+    },
 
     /** A state representing that this device is far away from any receiver device. */
     FAR_FROM_RECEIVER(
@@ -162,6 +208,12 @@
             throw IllegalArgumentException("FAR_FROM_RECEIVER should never be displayed, " +
                 "so its string should never be fetched")
         }
+
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState.transferStatus == TransferStatus.NOT_STARTED ||
+                    nextState.transferStatus == TransferStatus.IN_PROGRESS
+        }
     };
 
     /**
@@ -175,6 +227,8 @@
         return Text.Loaded(context.getString(stringResId!!, otherDeviceName))
     }
 
+    abstract fun isValidNextState(nextState: ChipStateSender): Boolean
+
     companion object {
         /**
          * Returns the sender state enum associated with the given [displayState] from
@@ -197,6 +251,31 @@
          */
         @StatusBarManager.MediaTransferSenderState
         fun getSenderStateIdFromName(name: String): Int = valueOf(name).stateInt
+
+        /**
+         * Validates the transition from a chip state to another.
+         *
+         * @param currentState is the current state of the chip.
+         * @param desiredState is the desired state of the chip.
+         * @return true if the transition from [currentState] to [desiredState] is valid, and false
+         * otherwise.
+         */
+        fun isValidStateTransition(
+                currentState: ChipStateSender?,
+                desiredState: ChipStateSender,
+        ): Boolean {
+            // Far from receiver is the default state.
+            if (currentState == null) {
+                return FAR_FROM_RECEIVER.isValidNextState(desiredState)
+            }
+
+            // No change in state is valid.
+            if (currentState == desiredState) {
+                return true
+            }
+
+            return currentState.isValidNextState(desiredState)
+        }
     }
 }
 
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 d1ea2d0..e34b0cb 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
@@ -52,6 +52,8 @@
 ) : CoreStartable {
 
     private var displayedState: ChipStateSender? = null
+    // A map to store current chip state per id.
+    private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf()
 
     private val commandQueueCallbacks =
         object : CommandQueue.Callbacks {
@@ -87,9 +89,22 @@
             logger.logStateChangeError(displayState)
             return
         }
+
+        val currentState = stateMap[routeInfo.id]
+        if (!ChipStateSender.isValidStateTransition(currentState, chipState)) {
+            // ChipStateSender.FAR_FROM_RECEIVER is the default state when there is no state.
+            logger.logInvalidStateTransitionError(
+                currentState = currentState?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name,
+                chipState.name
+            )
+            return
+        }
         uiEventLogger.logSenderStateChange(chipState)
 
+        stateMap.put(routeInfo.id, chipState)
         if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
+            // No need to store the state since it is the default state
+            stateMap.remove(routeInfo.id)
             // Return early if we're not displaying a chip anyway
             val currentDisplayedState = displayedState ?: return
 
@@ -108,7 +123,7 @@
             }
 
             displayedState = null
-            chipbarCoordinator.removeView(removalReason)
+            chipbarCoordinator.removeView(routeInfo.id, removalReason)
         } else {
             displayedState = chipState
             chipbarCoordinator.displayView(
@@ -119,7 +134,7 @@
                     context,
                     logger,
                 )
-            )
+            ) { stateMap.remove(routeInfo.id) }
         }
     }
 
@@ -162,6 +177,7 @@
             windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER,
             wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
             timeoutMs = chipStateSender.timeout,
+            id = routeInfo.id,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 7fd100f..6c41caa 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -19,6 +19,7 @@
 import android.app.Activity
 import android.content.ComponentName
 import android.content.Context
+import com.android.launcher3.icons.IconFactory
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.MediaProjectionAppSelectorActivity
 import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
@@ -92,6 +93,11 @@
         ): ConfigurationController = ConfigurationControllerImpl(activity)
 
         @Provides
+        fun bindIconFactory(
+            context: Context
+        ): IconFactory = IconFactory.obtain(context)
+
+        @Provides
         @MediaProjectionAppSelector
         @MediaProjectionAppSelectorScope
         fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
index 0927f3b..b85d628 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
@@ -19,13 +19,14 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.PackageManager
-import android.content.pm.PackageManager.ComponentInfoFlags
 import android.graphics.drawable.Drawable
 import android.os.UserHandle
 import com.android.launcher3.icons.BaseIconFactory.IconOptions
 import com.android.launcher3.icons.IconFactory
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.system.PackageManagerWrapper
 import javax.inject.Inject
+import javax.inject.Provider
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.withContext
 
@@ -38,14 +39,18 @@
 constructor(
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val context: Context,
-    private val packageManager: PackageManager
+    // Use wrapper to access hidden API that allows to get ActivityInfo for any user id
+    private val packageManagerWrapper: PackageManagerWrapper,
+    private val packageManager: PackageManager,
+    private val iconFactoryProvider: Provider<IconFactory>
 ) : AppIconLoader {
 
     override suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? =
         withContext(backgroundDispatcher) {
-            IconFactory.obtain(context).use<IconFactory, Drawable?> { iconFactory ->
-                val activityInfo = packageManager
-                        .getActivityInfo(component, ComponentInfoFlags.of(0))
+            iconFactoryProvider.get().use<IconFactory, Drawable?> { iconFactory ->
+                val activityInfo =
+                    packageManagerWrapper.getActivityInfo(component, userId)
+                        ?: return@withContext null
                 val icon = activityInfo.loadIcon(packageManager) ?: return@withContext null
                 val userHandler = UserHandle.of(userId)
                 val options = IconOptions().apply { setUser(userHandler) }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index e8b49cd..7a77c47 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -16,19 +16,19 @@
 
 package com.android.systemui.mediaprojection.appselector.data
 
-import android.app.ActivityManager
 import android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.wm.shell.recents.RecentTasks
 import com.android.wm.shell.util.GroupedRecentTaskInfo
 import java.util.Optional
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlin.coroutines.resume
 import kotlin.coroutines.suspendCoroutine
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.withContext
-import java.util.concurrent.Executor
 
 interface RecentTaskListProvider {
     /** Loads recent tasks, the returned task list is from the most-recent to least-recent order */
@@ -40,7 +40,8 @@
 constructor(
     @Background private val coroutineDispatcher: CoroutineDispatcher,
     @Background private val backgroundExecutor: Executor,
-    private val recentTasks: Optional<RecentTasks>
+    private val recentTasks: Optional<RecentTasks>,
+    private val userTracker: UserTracker
 ) : RecentTaskListProvider {
 
     private val recents by lazy { recentTasks.getOrNull() }
@@ -67,10 +68,8 @@
             getRecentTasks(
                 Integer.MAX_VALUE,
                 RECENT_IGNORE_UNAVAILABLE,
-                ActivityManager.getCurrentUser(),
+                userTracker.userId,
                 backgroundExecutor
-            ) { tasks ->
-                continuation.resume(tasks)
-            }
+            ) { tasks -> continuation.resume(tasks) }
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
index b682bd1..d4991f9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
@@ -148,6 +148,7 @@
 
         val currentRotation: Int = display.rotation
         val displayWidthPx = windowMetrics.bounds.width()
+        val displayHeightPx = windowMetrics.bounds.height()
         val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
         val isTablet = isTablet(context)
         val taskbarSize =
@@ -163,6 +164,7 @@
             measuredWidth,
             measuredHeight,
             displayWidthPx,
+            displayHeightPx,
             taskbarSize,
             isTablet,
             currentRotation,
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/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 33021e3..2c0745b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.navigationbar;
 
+import static android.app.StatusBarManager.WINDOW_NAVIGATION_BAR;
+import static android.app.StatusBarManager.WindowVisibleState;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
@@ -58,6 +60,7 @@
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -86,7 +89,7 @@
         AccessibilityButtonModeObserver.ModeChangedListener,
         AccessibilityButtonTargetsObserver.TargetsChangedListener,
         OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
-        Dumpable {
+        Dumpable, CommandQueue.Callbacks {
     private final AccessibilityManager mAccessibilityManager;
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
@@ -97,13 +100,18 @@
     private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
     private final List<NavbarTaskbarStateUpdater> mA11yEventListeners = new ArrayList<>();
     private final Context mContext;
-    private ContentResolver mContentResolver;
+    private final CommandQueue mCommandQueue;
+    private final ContentResolver mContentResolver;
     private boolean mAssistantAvailable;
     private boolean mLongPressHomeEnabled;
     private boolean mAssistantTouchGestureEnabled;
     private int mNavBarMode;
     private int mA11yButtonState;
 
+    // Attributes used in NavBarHelper.CurrentSysuiState
+    private int mWindowStateDisplayId;
+    private @WindowVisibleState int mWindowState;
+
     private final ContentObserver mAssistContentObserver = new ContentObserver(
             new Handler(Looper.getMainLooper())) {
         @Override
@@ -128,8 +136,10 @@
             KeyguardStateController keyguardStateController,
             NavigationModeController navigationModeController,
             UserTracker userTracker,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            CommandQueue commandQueue) {
         mContext = context;
+        mCommandQueue = commandQueue;
         mContentResolver = mContext.getContentResolver();
         mAccessibilityManager = accessibilityManager;
         mAssistManagerLazy = assistManagerLazy;
@@ -160,10 +170,13 @@
                 false, mAssistContentObserver, UserHandle.USER_ALL);
         updateAssistantAvailability();
         updateA11yState();
+        mCommandQueue.addCallback(this);
+
     }
 
     public void destroy() {
         mContentResolver.unregisterContentObserver(mAssistContentObserver);
+        mCommandQueue.removeCallback(this);
     }
 
     /**
@@ -333,6 +346,20 @@
                 || (!isKeyguardShowing && (vis & InputMethodService.IME_VISIBLE) != 0);
     }
 
+    @Override
+    public void setWindowState(int displayId, int window, int state) {
+        CommandQueue.Callbacks.super.setWindowState(displayId, window, state);
+        if (window != WINDOW_NAVIGATION_BAR) {
+            return;
+        }
+        mWindowStateDisplayId = displayId;
+        mWindowState = state;
+    }
+
+    public CurrentSysuiState getCurrentSysuiState() {
+        return new CurrentSysuiState();
+    }
+
     /**
      * Callbacks will get fired once immediately after registering via
      * {@link #registerNavTaskStateUpdater(NavbarTaskbarStateUpdater)}
@@ -342,6 +369,17 @@
         void updateAssistantAvailable(boolean available);
     }
 
+    /** Data class to help Taskbar/Navbar initiate state correctly when switching between the two.*/
+    public class CurrentSysuiState {
+        public final int mWindowStateDisplayId;
+        public final @WindowVisibleState int mWindowState;
+
+        public CurrentSysuiState() {
+            mWindowStateDisplayId = NavBarHelper.this.mWindowStateDisplayId;
+            mWindowState = NavBarHelper.this.mWindowState;
+        }
+    }
+
     static @TransitionMode int transitionMode(boolean isTransient, int appearance) {
         final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_NAVIGATION_BARS;
         if (isTransient) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 50cf63d..5202562 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -57,13 +57,12 @@
 import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay;
 
 import android.annotation.IdRes;
+import android.annotation.NonNull;
 import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
 import android.app.StatusBarManager;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
@@ -87,14 +86,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;
@@ -116,7 +119,6 @@
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -134,6 +136,7 @@
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.systemui.shared.recents.utilities.Utilities;
@@ -204,7 +207,7 @@
     private final NotificationRemoteInputManager mNotificationRemoteInputManager;
     private final OverviewProxyService mOverviewProxyService;
     private final NavigationModeController mNavigationModeController;
-    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final UserTracker mUserTracker;
     private final CommandQueue mCommandQueue;
     private final Optional<Pip> mPipOptional;
     private final Optional<Recents> mRecentsOptional;
@@ -366,15 +369,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 +481,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,
@@ -503,7 +515,7 @@
             StatusBarStateController statusBarStateController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             SysUiState sysUiFlagsContainer,
-            BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker,
             CommandQueue commandQueue,
             Optional<Pip> pipOptional,
             Optional<Recents> recentsOptional,
@@ -546,7 +558,7 @@
         mNotificationRemoteInputManager = notificationRemoteInputManager;
         mOverviewProxyService = overviewProxyService;
         mNavigationModeController = navigationModeController;
-        mBroadcastDispatcher = broadcastDispatcher;
+        mUserTracker = userTracker;
         mCommandQueue = commandQueue;
         mPipOptional = pipOptional;
         mRecentsOptional = recentsOptional;
@@ -653,6 +665,9 @@
         mDisplayId = mContext.getDisplayId();
         mIsOnDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
 
+        // Ensure we try to get currentSysuiState from navBarHelper before command queue callbacks
+        // start firing, since the latter is source of truth
+        parseCurrentSysuiState();
         mCommandQueue.addCallback(this);
         mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled();
         mNavBarHelper.init();
@@ -696,7 +711,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 +733,8 @@
 
         mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
                 mOnComputeInternalInsetsListener);
+        mView.getViewRootImpl().addSurfaceChangedCallback(mSurfaceChangedCallback);
+        notifyNavigationBarSurface();
 
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
@@ -726,9 +744,7 @@
         prepareNavigationBarView();
         checkNavBarModes();
 
-        IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-        mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
-                Handler.getMain(), UserHandle.ALL);
+        mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
         notifyNavigationBarScreenOn();
 
@@ -779,7 +795,7 @@
         mView.setUpdateActiveTouchRegionsCallback(null);
         getBarTransitions().destroy();
         mOverviewProxyService.removeCallback(mOverviewProxyListener);
-        mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
+        mUserTracker.removeCallback(mUserChangedCallback);
         mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
         if (mOrientationHandle != null) {
             resetSecondaryHandle();
@@ -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;
     }
@@ -932,6 +952,13 @@
         setOrientedHandleSamplingRegion(null);
     }
 
+    private void parseCurrentSysuiState() {
+        NavBarHelper.CurrentSysuiState state = mNavBarHelper.getCurrentSysuiState();
+        if (state.mWindowStateDisplayId == mDisplayId) {
+            mNavigationBarWindowState = state.mWindowState;
+        }
+    }
+
     private void reconfigureHomeLongClick() {
         if (mView.getHomeButton().getCurrentView() == null) {
             return;
@@ -947,6 +974,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 +1092,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 +1305,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) {
@@ -1707,21 +1740,14 @@
         }
     };
 
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            // TODO(193941146): Currently unregistering a receiver through BroadcastDispatcher is
-            // async, but we've already cleared the fields. Just return early in this case.
-            if (mView == null) {
-                return;
-            }
-            String action = intent.getAction();
-            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                // The accessibility settings may be different for the new user
-                updateAccessibilityStateFlags();
-            }
-        }
-    };
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    // The accessibility settings may be different for the new user
+                    updateAccessibilityStateFlags();
+                }
+            };
 
     @VisibleForTesting
     int getNavigationIconHints() {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 3fd1aa7..e2f55f0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -145,7 +145,7 @@
         boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources());
         boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
         // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
-        Log.d(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
+        Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
                 + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized()
                 + " willApplyConfigToNavbars=" + willApplyConfig
                 + " navBarCount=" + mNavigationBars.size());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
index 59bb2278e..2a7704f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
@@ -45,11 +45,10 @@
 import com.android.systemui.shared.system.QuickStepContract;
 
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.Objects;
 
-public class NavigationBarInflaterView extends FrameLayout
-        implements NavigationModeController.ModeChangedListener {
-
+public class NavigationBarInflaterView extends FrameLayout {
     private static final String TAG = "NavBarInflater";
 
     public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
@@ -83,6 +82,24 @@
     private static final String ABSOLUTE_SUFFIX = "A";
     private static final String ABSOLUTE_VERTICAL_CENTERED_SUFFIX = "C";
 
+    private static class Listener implements NavigationModeController.ModeChangedListener {
+        private final WeakReference<NavigationBarInflaterView> mSelf;
+
+        Listener(NavigationBarInflaterView self) {
+            mSelf = new WeakReference<>(self);
+        }
+
+        @Override
+        public void onNavigationModeChanged(int mode) {
+            NavigationBarInflaterView self = mSelf.get();
+            if (self != null) {
+                self.onNavigationModeChanged(mode);
+            }
+        }
+    }
+
+    private final Listener mListener;
+
     protected LayoutInflater mLayoutInflater;
     protected LayoutInflater mLandscapeInflater;
 
@@ -106,7 +123,8 @@
         super(context, attrs);
         createInflaters();
         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
-        mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
+        mListener = new Listener(this);
+        mNavBarMode = Dependency.get(NavigationModeController.class).addListener(mListener);
     }
 
     @VisibleForTesting
@@ -146,14 +164,13 @@
         return getContext().getString(defaultResource);
     }
 
-    @Override
-    public void onNavigationModeChanged(int mode) {
+    private void onNavigationModeChanged(int mode) {
         mNavBarMode = mode;
     }
 
     @Override
     protected void onDetachedFromWindow() {
-        Dependency.get(NavigationModeController.class).removeListener(this);
+        Dependency.get(NavigationModeController.class).removeListener(mListener);
         super.onDetachedFromWindow();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 73fc21e..ac7c70b 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;
 
@@ -214,6 +214,7 @@
             return;
         }
         mDisplayId = displayId;
+        parseCurrentSysuiState();
         mCommandQueue.addCallback(this);
         mOverviewProxyService.addCallback(this);
         mEdgeBackGestureHandler.onNavigationModeChanged(
@@ -271,6 +272,13 @@
         return mInitialized;
     }
 
+    private void parseCurrentSysuiState() {
+        NavBarHelper.CurrentSysuiState state = mNavBarHelper.getCurrentSysuiState();
+        if (state.mWindowStateDisplayId == mDisplayId) {
+            mTaskBarWindowState = state.mWindowState;
+        }
+    }
+
     private void updateSysuiFlags() {
         int a11yFlags = mNavBarHelper.getA11yButtonState();
         boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
@@ -355,7 +363,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..f97385b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -59,7 +59,6 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
@@ -70,9 +69,9 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.settings.CurrentUserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -102,8 +101,8 @@
 /**
  * Utility class to handle edge swipes for back gesture
  */
-public class EdgeBackGestureHandler extends CurrentUserTracker
-        implements PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> {
+public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBackPlugin>,
+        ProtoTraceable<SystemUiTraceProto> {
 
     private static final String TAG = "EdgeBackGestureHandler";
     private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
@@ -172,6 +171,7 @@
 
 
     private final Context mContext;
+    private final UserTracker mUserTracker;
     private final OverviewProxyService mOverviewProxyService;
     private final SysUiState mSysUiState;
     private Runnable mStateChangeCallback;
@@ -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
@@ -325,6 +321,15 @@
     private final Consumer<Boolean> mOnIsInPipStateChangedListener =
             (isInPip) -> mIsInPip = isInPip;
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    updateIsEnabled();
+                    updateCurrentUserResources();
+                }
+            };
+
     EdgeBackGestureHandler(
             Context context,
             OverviewProxyService overviewProxyService,
@@ -332,7 +337,7 @@
             PluginManager pluginManager,
             @Main Executor executor,
             @Background Executor backgroundExecutor,
-            BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker,
             ProtoTracer protoTracer,
             NavigationModeController navigationModeController,
             BackPanelController.Factory backPanelControllerFactory,
@@ -344,11 +349,11 @@
             Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider,
             Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
             FeatureFlags featureFlags) {
-        super(broadcastDispatcher);
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = executor;
         mBackgroundExecutor = backgroundExecutor;
+        mUserTracker = userTracker;
         mOverviewProxyService = overviewProxyService;
         mSysUiState = sysUiState;
         mPluginManager = pluginManager;
@@ -467,12 +472,6 @@
         }
     }
 
-    @Override
-    public void onUserSwitched(int newUserId) {
-        updateIsEnabled();
-        updateCurrentUserResources();
-    }
-
     /**
      * @see NavigationBarView#onAttachedToWindow()
      */
@@ -482,7 +481,7 @@
         mOverviewProxyService.addCallback(mQuickSwitchListener);
         mSysUiState.addCallback(mSysUiStateCallback);
         updateIsEnabled();
-        startTracking();
+        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
     }
 
     /**
@@ -494,7 +493,7 @@
         mOverviewProxyService.removeCallback(mQuickSwitchListener);
         mSysUiState.removeCallback(mSysUiStateCallback);
         updateIsEnabled();
-        stopTracking();
+        mUserTracker.removeCallback(mUserChangedCallback);
     }
 
     /**
@@ -804,9 +803,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;
@@ -981,7 +977,7 @@
         }
 
         // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
-        Log.d(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
+        Log.i(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
                 + " lastReportedConfig=" + mLastReportedConfig);
         mLastReportedConfig.updateFrom(newConfig);
         updateDisplaySize();
@@ -1100,7 +1096,7 @@
         private final PluginManager mPluginManager;
         private final Executor mExecutor;
         private final Executor mBackgroundExecutor;
-        private final BroadcastDispatcher mBroadcastDispatcher;
+        private final UserTracker mUserTracker;
         private final ProtoTracer mProtoTracer;
         private final NavigationModeController mNavigationModeController;
         private final BackPanelController.Factory mBackPanelControllerFactory;
@@ -1120,7 +1116,7 @@
                        PluginManager pluginManager,
                        @Main Executor executor,
                        @Background Executor backgroundExecutor,
-                       BroadcastDispatcher broadcastDispatcher,
+                       UserTracker userTracker,
                        ProtoTracer protoTracer,
                        NavigationModeController navigationModeController,
                        BackPanelController.Factory backPanelControllerFactory,
@@ -1138,7 +1134,7 @@
             mPluginManager = pluginManager;
             mExecutor = executor;
             mBackgroundExecutor = backgroundExecutor;
-            mBroadcastDispatcher = broadcastDispatcher;
+            mUserTracker = userTracker;
             mProtoTracer = protoTracer;
             mNavigationModeController = navigationModeController;
             mBackPanelControllerFactory = backPanelControllerFactory;
@@ -1161,7 +1157,7 @@
                     mPluginManager,
                     mExecutor,
                     mBackgroundExecutor,
-                    mBroadcastDispatcher,
+                    mUserTracker,
                     mProtoTracer,
                     mNavigationModeController,
                     mBackPanelControllerFactory,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index d247f24..6dd60d0 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -17,12 +17,14 @@
 package com.android.systemui.notetask
 
 import android.app.KeyguardManager
+import android.content.ComponentName
 import android.content.Context
+import android.content.pm.PackageManager
 import android.os.UserManager
-import android.view.KeyEvent
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.util.kotlin.getOrNull
-import com.android.wm.shell.floating.FloatingTasks
+import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
 import javax.inject.Inject
 
@@ -39,22 +41,29 @@
 constructor(
     private val context: Context,
     private val intentResolver: NoteTaskIntentResolver,
-    private val optionalFloatingTasks: Optional<FloatingTasks>,
+    private val optionalBubbles: Optional<Bubbles>,
     private val optionalKeyguardManager: Optional<KeyguardManager>,
     private val optionalUserManager: Optional<UserManager>,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
 ) {
 
-    fun handleSystemKey(keyCode: Int) {
+    /**
+     * Shows a note task. How the task is shown will depend on when the method is invoked.
+     *
+     * If in multi-window mode, notes will open as a full screen experience. That is particularly
+     * important for Large screen devices. These devices may support a taskbar that let users to
+     * drag and drop a shortcut into multi-window mode, and notes should comply with this behaviour.
+     *
+     * If the keyguard is locked, notes will open as a full screen experience. A locked device has
+     * no contextual information which let us use the whole screen space available.
+     *
+     * If no in multi-window or the keyguard is unlocked, notes will open as a floating experience.
+     * That will let users open other apps in full screen, and take contextual notes.
+     */
+    fun showNoteTask(isInMultiWindowMode: Boolean = false) {
         if (!isEnabled) return
 
-        if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) {
-            showNoteTask()
-        }
-    }
-
-    private fun showNoteTask() {
-        val floatingTasks = optionalFloatingTasks.getOrNull() ?: return
+        val bubbles = optionalBubbles.getOrNull() ?: return
         val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
         val userManager = optionalUserManager.getOrNull() ?: return
         val intent = intentResolver.resolveIntent() ?: return
@@ -62,11 +71,35 @@
         // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
         if (!userManager.isUserUnlocked) return
 
-        if (keyguardManager.isKeyguardLocked) {
+        if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
             context.startActivity(intent)
         } else {
             // TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
-            floatingTasks.showOrSetStashed(intent)
+            bubbles.showAppBubble(intent)
         }
     }
+
+    /**
+     * Set `android:enabled` property in the `AndroidManifest` associated with the Shortcut
+     * component to [value].
+     *
+     * If the shortcut entry `android:enabled` is set to `true`, the shortcut will be visible in the
+     * Widget Picker to all users.
+     */
+    fun setNoteTaskShortcutEnabled(value: Boolean) {
+        val componentName = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+
+        val enabledState =
+            if (value) {
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+            } else {
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+            }
+
+        context.packageManager.setComponentEnabledSetting(
+            componentName,
+            enabledState,
+            PackageManager.DONT_KILL_APP,
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index d84717d..d14b7a7 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -16,9 +16,10 @@
 
 package com.android.systemui.notetask
 
+import android.view.KeyEvent
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.statusbar.CommandQueue
-import com.android.wm.shell.floating.FloatingTasks
-import dagger.Lazy
+import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
 import javax.inject.Inject
 
@@ -26,22 +27,26 @@
 internal class NoteTaskInitializer
 @Inject
 constructor(
-    private val optionalFloatingTasks: Optional<FloatingTasks>,
-    private val lazyNoteTaskController: Lazy<NoteTaskController>,
+    private val optionalBubbles: Optional<Bubbles>,
+    private val noteTaskController: NoteTaskController,
     private val commandQueue: CommandQueue,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
 ) {
 
-    private val callbacks =
+    @VisibleForTesting
+    val callbacks =
         object : CommandQueue.Callbacks {
             override fun handleSystemKey(keyCode: Int) {
-                lazyNoteTaskController.get().handleSystemKey(keyCode)
+                if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) {
+                    noteTaskController.showNoteTask()
+                }
             }
         }
 
     fun initialize() {
-        if (isEnabled && optionalFloatingTasks.isPresent) {
+        if (isEnabled && optionalBubbles.isPresent) {
             commandQueue.addCallback(callbacks)
         }
+        noteTaskController.setNoteTaskShortcutEnabled(isEnabled)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index 035396a..8bdf319 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -16,32 +16,47 @@
 
 package com.android.systemui.notetask
 
+import android.app.Activity
 import android.app.KeyguardManager
 import android.content.Context
 import android.os.UserManager
 import androidx.core.content.getSystemService
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
+import dagger.Binds
 import dagger.Module
 import dagger.Provides
-import java.util.*
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import java.util.Optional
 
 /** Compose all dependencies required by Note Task feature. */
 @Module
-internal class NoteTaskModule {
+internal interface NoteTaskModule {
 
-    @[Provides NoteTaskEnabledKey]
-    fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
-        return featureFlags.isEnabled(Flags.NOTE_TASKS)
-    }
+    @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
+    fun bindNoteTaskLauncherActivity(activity: LaunchNoteTaskActivity): Activity?
 
-    @Provides
-    fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
-        return Optional.ofNullable(context.getSystemService())
-    }
+    @[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
+    fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity?
 
-    @Provides
-    fun provideOptionalUserManager(context: Context): Optional<UserManager> {
-        return Optional.ofNullable(context.getSystemService())
+    companion object {
+
+        @[Provides NoteTaskEnabledKey]
+        fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
+            return featureFlags.isEnabled(Flags.NOTE_TASKS)
+        }
+
+        @Provides
+        fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
+            return Optional.ofNullable(context.getSystemService())
+        }
+
+        @Provides
+        fun provideOptionalUserManager(context: Context): Optional<UserManager> {
+            return Optional.ofNullable(context.getSystemService())
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
new file mode 100644
index 0000000..f6a623e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.notetask.shortcut
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.annotation.DrawableRes
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.graphics.drawable.IconCompat
+import com.android.systemui.R
+import javax.inject.Inject
+
+/**
+ * Activity responsible for create a shortcut for notes action. If the shortcut is enabled, a new
+ * shortcut will appear in the widget picker. If the shortcut is selected, the Activity here will be
+ * launched, creating a new shortcut for [CreateNoteTaskShortcutActivity], and will finish.
+ *
+ * @see <a
+ * href="https://developer.android.com/develop/ui/views/launch/shortcuts/creating-shortcuts#custom-pinned">Creating
+ * a custom shortcut activity</a>
+ */
+internal class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val intent =
+            createShortcutIntent(
+                id = SHORTCUT_ID,
+                shortLabel = getString(R.string.note_task_button_label),
+                intent = LaunchNoteTaskActivity.newIntent(context = this),
+                iconResource = R.drawable.ic_note_task_button,
+            )
+        setResult(Activity.RESULT_OK, intent)
+
+        finish()
+    }
+
+    private fun createShortcutIntent(
+        id: String,
+        shortLabel: String,
+        intent: Intent,
+        @DrawableRes iconResource: Int,
+    ): Intent {
+        val shortcutInfo =
+            ShortcutInfoCompat.Builder(this, id)
+                .setIntent(intent)
+                .setShortLabel(shortLabel)
+                .setLongLived(true)
+                .setIcon(IconCompat.createWithResource(this, iconResource))
+                .build()
+
+        return ShortcutManagerCompat.createShortcutResultIntent(
+            this,
+            shortcutInfo,
+        )
+    }
+
+    private companion object {
+        private const val SHORTCUT_ID = "note-task-shortcut-id"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
new file mode 100644
index 0000000..47fe676
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.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.notetask.shortcut
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import com.android.systemui.notetask.NoteTaskController
+import com.android.systemui.notetask.NoteTaskIntentResolver
+import javax.inject.Inject
+
+/** Activity responsible for launching the note experience, and finish. */
+internal class LaunchNoteTaskActivity
+@Inject
+constructor(
+    private val noteTaskController: NoteTaskController,
+) : ComponentActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        noteTaskController.showNoteTask(isInMultiWindowMode)
+
+        finish()
+    }
+
+    companion object {
+
+        /** Creates a new [Intent] set to start [LaunchNoteTaskActivity]. */
+        fun newIntent(context: Context): Intent {
+            return Intent(context, LaunchNoteTaskActivity::class.java).apply {
+                // Intent's action must be set in shortcuts, or an exception will be thrown.
+                // TODO(b/254606432): Use Intent.ACTION_NOTES instead.
+                action = NoteTaskIntentResolver.NOTES_ACTION
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index be82b1f..67e9664 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -1096,7 +1096,7 @@
             Pair<Integer, Integer> first = emojiIndices.get(i - 1);
 
             // Check if second emoji starts right after first starts
-            if (second.first == first.second) {
+            if (Objects.equals(second.first, first.second)) {
                 // Check if emojis in sequence are the same
                 if (Objects.equals(emojiTexts.get(i), emojiTexts.get(i - 1))) {
                     if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
index 0b565ea..e6575d5a 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
@@ -18,7 +18,6 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.PluginDependency.DependencyProvider;
-import com.android.systemui.shared.plugins.PluginManager;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
index 638f81b..146633d 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
@@ -27,7 +27,6 @@
 import com.android.systemui.shared.plugins.PluginActionManager;
 import com.android.systemui.shared.plugins.PluginEnabler;
 import com.android.systemui.shared.plugins.PluginInstance;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.plugins.PluginManagerImpl;
 import com.android.systemui.shared.plugins.PluginPrefs;
 import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 1da866e..5a1ad96 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -39,6 +39,8 @@
 import android.util.Log;
 import android.util.Slog;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.fuelgauge.Estimate;
 import com.android.settingslib.utils.ThreadUtils;
@@ -47,6 +49,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
@@ -80,6 +83,7 @@
     private final PowerManager mPowerManager;
     private final WarningsUI mWarnings;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final UserTracker mUserTracker;
     private InattentiveSleepWarningView mOverlayView;
     private final Configuration mLastConfiguration = new Configuration();
     private int mPlugType = 0;
@@ -122,12 +126,21 @@
                 }
             };
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    mWarnings.userSwitched();
+                }
+            };
+
     @Inject
     public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher,
             CommandQueue commandQueue, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             WarningsUI warningsUI, EnhancedEstimates enhancedEstimates,
             WakefulnessLifecycle wakefulnessLifecycle,
-            PowerManager powerManager) {
+            PowerManager powerManager,
+            UserTracker userTracker) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
         mCommandQueue = commandQueue;
@@ -136,6 +149,7 @@
         mEnhancedEstimates = enhancedEstimates;
         mPowerManager = powerManager;
         mWakefulnessLifecycle = wakefulnessLifecycle;
+        mUserTracker = userTracker;
     }
 
     public void start() {
@@ -154,6 +168,7 @@
                 false, obs, UserHandle.USER_ALL);
         updateBatteryWarningLevels();
         mReceiver.init();
+        mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
 
         // Check to see if we need to let the user know that the phone previously shut down due
@@ -250,7 +265,6 @@
             IntentFilter filter = new IntentFilter();
             filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
             filter.addAction(Intent.ACTION_BATTERY_CHANGED);
-            filter.addAction(Intent.ACTION_USER_SWITCHED);
             mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler);
             // Force get initial values. Relying on Sticky behavior until API for getting info.
             if (!mHasReceivedBattery) {
@@ -332,8 +346,6 @@
                             plugged, bucket);
                 });
 
-            } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                mWarnings.userSwitched();
             } else {
                 Slog.w(TAG, "unknown intent: " + intent);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
index 2c20feb..fa3f878f 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
@@ -158,14 +158,18 @@
      * Returns true if lock screen entry point for QR Code Scanner is to be enabled.
      */
     public boolean isEnabledForLockScreenButton() {
-        return mQRCodeScannerEnabled && mIntent != null && mConfigEnableLockScreenButton
-                && isActivityCallable(mIntent);
+        return mQRCodeScannerEnabled && isAbleToOpenCameraApp() && isAvailableOnDevice();
+    }
+
+    /** Returns whether the feature is available on the device. */
+    public boolean isAvailableOnDevice() {
+        return mConfigEnableLockScreenButton;
     }
 
     /**
-     * Returns true if quick settings entry point for QR Code Scanner is to be enabled.
+     * Returns true if the feature can open a camera app on the device.
      */
-    public boolean isEnabledForQuickSettings() {
+    public boolean isAbleToOpenCameraApp() {
         return mIntent != null && isActivityCallable(mIntent);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index bb2b441..3c10778 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -18,6 +18,9 @@
 
 import android.app.IActivityManager
 import android.app.IForegroundServiceObserver
+import android.app.job.IUserVisibleJobObserver
+import android.app.job.JobScheduler
+import android.app.job.UserVisibleJobSummary
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
@@ -47,6 +50,7 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.Dumpable
 import com.android.systemui.R
@@ -92,6 +96,8 @@
      */
     val showFooterDot: StateFlow<Boolean>
 
+    val includesUserVisibleJobs: Boolean
+
     /**
      * Initialize this controller. This should be called once, before this controller is used for
      * the first time.
@@ -141,19 +147,21 @@
     @Background private val backgroundExecutor: Executor,
     private val systemClock: SystemClock,
     private val activityManager: IActivityManager,
+    private val jobScheduler: JobScheduler,
     private val packageManager: PackageManager,
     private val userTracker: UserTracker,
     private val deviceConfigProxy: DeviceConfigProxy,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val dumpManager: DumpManager
-) : IForegroundServiceObserver.Stub(), Dumpable, FgsManagerController {
+) : Dumpable, FgsManagerController {
 
     companion object {
         private const val INTERACTION_JANK_TAG = "active_background_apps"
         private const val DEFAULT_TASK_MANAGER_ENABLED = true
         private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false
         private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true
+        private const val DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS = false
     }
 
     override var newChangesSinceDialogWasDismissed = false
@@ -167,6 +175,11 @@
 
     private var showStopBtnForUserAllowlistedApps = false
 
+    private var showUserVisibleJobs = DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
+
+    override val includesUserVisibleJobs: Boolean
+        get() = showUserVisibleJobs
+
     override val numRunningPackages: Int
         get() {
             synchronized(lock) {
@@ -186,7 +199,7 @@
     private var currentProfileIds = mutableSetOf<Int>()
 
     @GuardedBy("lock")
-    private val runningServiceTokens = mutableMapOf<UserPackage, StartTimeAndTokens>()
+    private val runningTaskIdentifiers = mutableMapOf<UserPackage, StartTimeAndIdentifiers>()
 
     @GuardedBy("lock")
     private var dialog: SystemUIDialog? = null
@@ -210,13 +223,29 @@
         }
     }
 
+    private val foregroundServiceObserver = ForegroundServiceObserver()
+
+    private val userVisibleJobObserver = UserVisibleJobObserver()
+
     override fun init() {
         synchronized(lock) {
             if (initialized) {
                 return
             }
+
+            showUserVisibleJobs = deviceConfigProxy.getBoolean(
+                NAMESPACE_SYSTEMUI,
+                TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS)
+
             try {
-                activityManager.registerForegroundServiceObserver(this)
+                activityManager.registerForegroundServiceObserver(foregroundServiceObserver)
+                // Clumping FGS and user-visible jobs here and showing a single entry and button
+                // for them is the easiest way to get user-visible jobs showing in Task Manager.
+                // Ideally, we would have dedicated UI in task manager for the user-visible jobs.
+                // TODO(255768978): distinguish jobs from FGS and give users more control
+                if (showUserVisibleJobs) {
+                    jobScheduler.registerUserVisibleJobObserver(userVisibleJobObserver)
+                }
             } catch (e: RemoteException) {
                 e.rethrowFromSystemServer()
             }
@@ -235,6 +264,12 @@
                 showStopBtnForUserAllowlistedApps = it.getBoolean(
                     TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
                     showStopBtnForUserAllowlistedApps)
+                var wasShowingUserVisibleJobs = showUserVisibleJobs
+                showUserVisibleJobs = it.getBoolean(
+                        TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, showUserVisibleJobs)
+                if (showUserVisibleJobs != wasShowingUserVisibleJobs) {
+                    onShowUserVisibleJobsFlagChanged()
+                }
             }
 
             _isAvailable.value = deviceConfigProxy.getBoolean(
@@ -269,32 +304,6 @@
         }
     }
 
-    override fun onForegroundStateChanged(
-        token: IBinder,
-        packageName: String,
-        userId: Int,
-        isForeground: Boolean
-    ) {
-        synchronized(lock) {
-            val userPackageKey = UserPackage(userId, packageName)
-            if (isForeground) {
-                runningServiceTokens.getOrPut(userPackageKey) { StartTimeAndTokens(systemClock) }
-                    .addToken(token)
-            } else {
-                if (runningServiceTokens[userPackageKey]?.also {
-                    it.removeToken(token)
-                }?.isEmpty() == true
-                ) {
-                    runningServiceTokens.remove(userPackageKey)
-                }
-            }
-
-            updateNumberOfVisibleRunningPackagesLocked()
-
-            updateAppItemsLocked()
-        }
-    }
-
     @GuardedBy("lock")
     private val onNumberOfPackagesChangedListeners =
         mutableSetOf<FgsManagerController.OnNumberOfPackagesChangedListener>()
@@ -336,7 +345,7 @@
     }
 
     private fun getNumVisiblePackagesLocked(): Int {
-        return runningServiceTokens.keys.count {
+        return runningTaskIdentifiers.keys.count {
             it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId)
         }
     }
@@ -361,7 +370,7 @@
     }
 
     private fun getNumVisibleButtonsLocked(): Int {
-        return runningServiceTokens.keys.count {
+        return runningTaskIdentifiers.keys.count {
             it.uiControl != UIControl.HIDE_BUTTON && currentProfileIds.contains(it.userId)
         }
     }
@@ -372,7 +381,7 @@
         synchronized(lock) {
             if (dialog == null) {
 
-                runningServiceTokens.keys.forEach {
+                runningTaskIdentifiers.keys.forEach {
                     it.updateUiControl()
                 }
 
@@ -434,17 +443,17 @@
             return
         }
 
-        val addedPackages = runningServiceTokens.keys.filter {
+        val addedPackages = runningTaskIdentifiers.keys.filter {
             currentProfileIds.contains(it.userId) &&
                     it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true
         }
-        val removedPackages = runningApps.keys.filter { !runningServiceTokens.containsKey(it) }
+        val removedPackages = runningApps.keys.filter { !runningTaskIdentifiers.containsKey(it) }
 
         addedPackages.forEach {
             val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId)
             runningApps[it] = RunningApp(
                 it.userId, it.packageName,
-                runningServiceTokens[it]!!.startTime, it.uiControl,
+                runningTaskIdentifiers[it]!!.startTime, it.uiControl,
                 packageManager.getApplicationLabel(ai),
                 packageManager.getUserBadgedIcon(
                     packageManager.getApplicationIcon(ai), UserHandle.of(it.userId)
@@ -471,7 +480,41 @@
 
     private fun stopPackage(userId: Int, packageName: String, timeStarted: Long) {
         logEvent(stopped = true, packageName, userId, timeStarted)
-        activityManager.stopAppForUser(packageName, userId)
+        val userPackageKey = UserPackage(userId, packageName)
+        if (showUserVisibleJobs &&
+                runningTaskIdentifiers[userPackageKey]?.hasRunningJobs() == true) {
+            // TODO(255768978): allow fine-grained job control
+            jobScheduler.stopUserVisibleJobsForUser(packageName, userId)
+        }
+        if (runningTaskIdentifiers[userPackageKey]?.hasFgs() == true) {
+            activityManager.stopAppForUser(packageName, userId)
+        }
+    }
+
+    private fun onShowUserVisibleJobsFlagChanged() {
+        if (showUserVisibleJobs) {
+            jobScheduler.registerUserVisibleJobObserver(userVisibleJobObserver)
+        } else {
+            jobScheduler.unregisterUserVisibleJobObserver(userVisibleJobObserver)
+
+            synchronized(lock) {
+                for ((userPackage, startTimeAndIdentifiers) in runningTaskIdentifiers) {
+                    if (startTimeAndIdentifiers.hasFgs()) {
+                        // The app still has FGS running, so all we need to do is remove
+                        // the job summaries
+                        startTimeAndIdentifiers.clearJobSummaries()
+                    } else {
+                        // The app only has user-visible jobs running, so remove it from
+                        // the map altogether
+                        runningTaskIdentifiers.remove(userPackage)
+                    }
+                }
+
+                updateNumberOfVisibleRunningPackagesLocked()
+
+                updateAppItemsLocked()
+            }
+        }
     }
 
     private fun logEvent(stopped: Boolean, packageName: String, userId: Int, timeStarted: Long) {
@@ -564,6 +607,62 @@
         }
     }
 
+    private inner class ForegroundServiceObserver : IForegroundServiceObserver.Stub() {
+        override fun onForegroundStateChanged(
+                token: IBinder,
+                packageName: String,
+                userId: Int,
+                isForeground: Boolean
+        ) {
+            synchronized(lock) {
+                val userPackageKey = UserPackage(userId, packageName)
+                if (isForeground) {
+                    runningTaskIdentifiers
+                            .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) }
+                            .addFgsToken(token)
+                } else {
+                    if (runningTaskIdentifiers[userPackageKey]?.also {
+                                it.removeFgsToken(token)
+                            }?.isEmpty() == true
+                    ) {
+                        runningTaskIdentifiers.remove(userPackageKey)
+                    }
+                }
+
+                updateNumberOfVisibleRunningPackagesLocked()
+
+                updateAppItemsLocked()
+            }
+        }
+    }
+
+    private inner class UserVisibleJobObserver : IUserVisibleJobObserver.Stub() {
+        override fun onUserVisibleJobStateChanged(
+                summary: UserVisibleJobSummary,
+                isRunning: Boolean
+        ) {
+            synchronized(lock) {
+                val userPackageKey = UserPackage(summary.sourceUserId, summary.sourcePackageName)
+                if (isRunning) {
+                    runningTaskIdentifiers
+                            .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) }
+                            .addJobSummary(summary)
+                } else {
+                    if (runningTaskIdentifiers[userPackageKey]?.also {
+                                it.removeJobSummary(summary)
+                            }?.isEmpty() == true
+                    ) {
+                        runningTaskIdentifiers.remove(userPackageKey)
+                    }
+                }
+
+                updateNumberOfVisibleRunningPackagesLocked()
+
+                updateAppItemsLocked()
+            }
+        }
+    }
+
     private inner class UserPackage(
         val userId: Int,
         val packageName: String
@@ -630,37 +729,64 @@
         }
     }
 
-    private data class StartTimeAndTokens(
+    private data class StartTimeAndIdentifiers(
         val systemClock: SystemClock
     ) {
         val startTime = systemClock.elapsedRealtime()
-        val tokens = mutableSetOf<IBinder>()
+        val fgsTokens = mutableSetOf<IBinder>()
+        val jobSummaries = mutableSetOf<UserVisibleJobSummary>()
 
-        fun addToken(token: IBinder) {
-            tokens.add(token)
+        fun addJobSummary(summary: UserVisibleJobSummary) {
+            jobSummaries.add(summary)
         }
 
-        fun removeToken(token: IBinder) {
-            tokens.remove(token)
+        fun clearJobSummaries() {
+            jobSummaries.clear()
+        }
+
+        fun removeJobSummary(summary: UserVisibleJobSummary) {
+            jobSummaries.remove(summary)
+        }
+
+        fun addFgsToken(token: IBinder) {
+            fgsTokens.add(token)
+        }
+
+        fun removeFgsToken(token: IBinder) {
+            fgsTokens.remove(token)
+        }
+
+        fun hasFgs(): Boolean {
+            return !fgsTokens.isEmpty()
+        }
+
+        fun hasRunningJobs(): Boolean {
+            return !jobSummaries.isEmpty()
         }
 
         fun isEmpty(): Boolean {
-            return tokens.isEmpty()
+            return fgsTokens.isEmpty() && jobSummaries.isEmpty()
         }
 
         fun dump(pw: PrintWriter) {
-            pw.println("StartTimeAndTokens: [")
+            pw.println("StartTimeAndIdentifiers: [")
             pw.indentIfPossible {
                 pw.println(
                     "startTime=$startTime (time running =" +
                         " ${systemClock.elapsedRealtime() - startTime}ms)"
                 )
-                pw.println("tokens: [")
+                pw.println("fgs tokens: [")
                 pw.indentIfPossible {
-                    for (token in tokens) {
+                    for (token in fgsTokens) {
                         pw.println("$token")
                     }
                 }
+                pw.println("job summaries: [")
+                pw.indentIfPossible {
+                    for (summary in jobSummaries) {
+                        pw.println("$summary")
+                    }
+                }
                 pw.println("]")
             }
             pw.println("]")
@@ -724,13 +850,13 @@
         synchronized(lock) {
             pw.println("current user profiles = $currentProfileIds")
             pw.println("newChangesSinceDialogWasShown=$newChangesSinceDialogWasDismissed")
-            pw.println("Running service tokens: [")
+            pw.println("Running task identifiers: [")
             pw.indentIfPossible {
-                runningServiceTokens.forEach { (userPackage, startTimeAndTokens) ->
+                runningTaskIdentifiers.forEach { (userPackage, startTimeAndIdentifiers) ->
                     pw.println("{")
                     pw.indentIfPossible {
                         userPackage.dump(pw)
-                        startTimeAndTokens.dump(pw)
+                        startTimeAndIdentifiers.dump(pw)
                     }
                     pw.println("}")
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
index dc79f40..6f645b5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
@@ -26,6 +26,7 @@
 import javax.inject.Inject
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
 
 interface ChipVisibilityListener {
     fun onChipVisibilityRefreshed(visible: Boolean)
@@ -54,7 +55,8 @@
     private val activityStarter: ActivityStarter,
     private val appOpsController: AppOpsController,
     private val broadcastDispatcher: BroadcastDispatcher,
-    private val safetyCenterManager: SafetyCenterManager
+    private val safetyCenterManager: SafetyCenterManager,
+    private val deviceProvisionedController: DeviceProvisionedController
 ) {
 
     var chipVisibilityListener: ChipVisibilityListener? = null
@@ -134,6 +136,8 @@
 
     fun onParentVisible() {
         privacyChip.setOnClickListener {
+            // Do not expand dialog while device is not provisioned
+            if (!deviceProvisionedController.isDeviceProvisioned) return@setOnClickListener
             // If the privacy chip is visible, it means there were some indicators
             uiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK)
             if (safetyCenterEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 0697133..f92bbf7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -364,13 +364,18 @@
     private void distributeTiles() {
         emptyAndInflateOrRemovePages();
 
-        final int tileCount = mPages.get(0).maxTiles();
-        if (DEBUG) Log.d(TAG, "Distributing tiles");
+        final int tilesPerPageCount = mPages.get(0).maxTiles();
         int index = 0;
-        final int NT = mTiles.size();
-        for (int i = 0; i < NT; i++) {
+        final int totalTilesCount = mTiles.size();
+        if (DEBUG) {
+            Log.d(TAG, "Distributing tiles: "
+                    + "[tilesPerPageCount=" + tilesPerPageCount + "]"
+                    + "[totalTilesCount=" + totalTilesCount + "]"
+            );
+        }
+        for (int i = 0; i < totalTilesCount; i++) {
             TileRecord tile = mTiles.get(i);
-            if (mPages.get(index).mRecords.size() == tileCount) index++;
+            if (mPages.get(index).mRecords.size() == tilesPerPageCount) index++;
             if (DEBUG) {
                 Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
                         + index);
@@ -577,8 +582,8 @@
         });
         setOffscreenPageLimit(lastPageNumber); // Ensure the page to reveal has been inflated.
         int dx = getWidth() * lastPageNumber;
-        mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx  : dx, 0,
-            REVEAL_SCROLL_DURATION_MILLIS);
+        mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0,
+                REVEAL_SCROLL_DURATION_MILLIS);
         postInvalidateOnAnimation();
     }
 
@@ -738,6 +743,7 @@
 
     public interface PageListener {
         int INVALID_PAGE = -1;
+
         void onPageChanged(boolean isFirst, int pageNumber);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index ef87fb4..dc9dcc2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -29,6 +29,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.util.LargeScreenUtils;
 
 import java.io.PrintWriter;
 
@@ -52,6 +53,7 @@
     private boolean mQsDisabled;
     private int mContentHorizontalPadding = -1;
     private boolean mClippingEnabled;
+    private boolean mUseCombinedHeaders;
 
     public QSContainerImpl(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -66,6 +68,10 @@
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
     }
 
+    void setUseCombinedHeaders(boolean useCombinedHeaders) {
+        mUseCombinedHeaders = useCombinedHeaders;
+    }
+
     @Override
     public boolean hasOverlappingRendering() {
         return false;
@@ -143,9 +149,15 @@
 
     void updateResources(QSPanelController qsPanelController,
             QuickStatusBarHeaderController quickStatusBarHeaderController) {
+        int topPadding = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
+        if (mUseCombinedHeaders
+                && !LargeScreenUtils.shouldUseLargeScreenShadeHeader(mContext.getResources())) {
+            topPadding = mContext.getResources()
+                    .getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
+        }
         mQSPanelContainer.setPaddingRelative(
                 mQSPanelContainer.getPaddingStart(),
-                QSUtils.getQsHeaderSystemIconsAreaHeight(mContext),
+                topPadding,
                 mQSPanelContainer.getPaddingEnd(),
                 mQSPanelContainer.getPaddingBottom());
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index dea7bb5..28b4c822 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -22,6 +22,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -37,7 +39,6 @@
     private final ConfigurationController mConfigurationController;
     private final FalsingManager mFalsingManager;
     private final NonInterceptingScrollView mQSPanelContainer;
-
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
         @Override
@@ -65,13 +66,15 @@
             QSPanelController qsPanelController,
             QuickStatusBarHeaderController quickStatusBarHeaderController,
             ConfigurationController configurationController,
-            FalsingManager falsingManager) {
+            FalsingManager falsingManager,
+            FeatureFlags featureFlags) {
         super(view);
         mQsPanelController = qsPanelController;
         mQuickStatusBarHeaderController = quickStatusBarHeaderController;
         mConfigurationController = configurationController;
         mFalsingManager = falsingManager;
         mQSPanelContainer = mView.getQSPanelContainer();
+        view.setUseCombinedHeaders(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 920a108..c0533ba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -515,7 +515,13 @@
     public void setExpanded(boolean expanded) {
         if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
         mQsExpanded = expanded;
-        updateQsPanelControllerListening();
+        if (mInSplitShade && mQsExpanded) {
+            // in split shade QS is expanded immediately when shade expansion starts and then we
+            // also need to listen to changes - otherwise QS is updated only once its fully expanded
+            setListening(true);
+        } else {
+            updateQsPanelControllerListening();
+        }
         updateQsState();
     }
 
@@ -691,10 +697,12 @@
         if (mQSAnimator != null) {
             mQSAnimator.setPosition(expansion);
         }
-        if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD
+        if (!mInSplitShade
+                || mStatusBarStateController.getState() == StatusBarState.KEYGUARD
                 || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
             // At beginning, state is 0 and will apply wrong squishiness to MediaHost in lockscreen
-            // and media player expect no change by squishiness in lock screen shade
+            // and media player expect no change by squishiness in lock screen shade. Don't bother
+            // squishing mQsMediaHost when not in split shade to prevent problems with stale state.
             mQsMediaHost.setSquishFraction(1.0F);
         } else {
             mQsMediaHost.setSquishFraction(mSquishinessFraction);
@@ -751,7 +759,8 @@
         return ShadeInterpolation.getContentAlpha(progress);
     }
 
-    private void updateQsBounds() {
+    @VisibleForTesting
+    void updateQsBounds() {
         if (mLastQSExpansion == 1.0f) {
             // Fully expanded, let's set the layout bounds as clip bounds. This is necessary because
             // it's a scrollview and otherwise wouldn't be clipped. However, we set the horizontal
@@ -767,9 +776,10 @@
         mQSPanelScrollView.getLocationOnScreen(mLocationTemp);
         int left = mLocationTemp[0];
         int top = mLocationTemp[1];
-        mQsMediaHost.getCurrentClipping().set(left, top, left + getView().getMeasuredWidth(),
+        mQsMediaHost.getCurrentClipping().set(left, top,
+                left + getView().getMeasuredWidth(),
                 top + mQSPanelScrollView.getMeasuredHeight()
-                        - mQSPanelScrollView.getPaddingBottom());
+                        - mQSPanelController.getPaddingBottom());
     }
 
     private boolean headerWillBeAnimating() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index abc0ade..1827eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -237,7 +237,11 @@
      * @return if bouncer is in transit
      */
     public boolean isBouncerInTransit() {
-        return mStatusBarKeyguardViewManager.isBouncerInTransit();
+        return mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit();
+    }
+
+    public int getPaddingBottom() {
+        return mView.getPaddingBottom();
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 2a80de0..dd88c83 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -25,6 +25,7 @@
 import android.content.res.Configuration;
 import android.content.res.Configuration.Orientation;
 import android.metrics.LogMaker;
+import android.util.Log;
 import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -38,6 +39,7 @@
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileViewImpl;
 import com.android.systemui.util.LargeScreenUtils;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.animation.DisappearParameters;
@@ -237,6 +239,16 @@
     private void addTile(final QSTile tile, boolean collapsedView) {
         final TileRecord r =
                 new TileRecord(tile, mHost.createTileView(getContext(), tile, collapsedView));
+        // TODO(b/250618218): Remove the QSLogger in QSTileViewImpl once we know the root cause of
+        // b/250618218.
+        try {
+            QSTileViewImpl qsTileView = (QSTileViewImpl) (r.tileView);
+            if (qsTileView != null) {
+                qsTileView.setQsLogger(mQSLogger);
+            }
+        } catch (ClassCastException e) {
+            Log.e(TAG, "Failed to cast QSTileView to QSTileViewImpl", e);
+        }
         mView.addTile(r);
         mRecords.add(r);
         mCachedSpecs = getTilesSpecs();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
index 67bc769..5dbf0f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
@@ -247,7 +247,7 @@
 
         Icon icon;
         ContentDescription contentDescription = null;
-        if (isParentalControlsEnabled) {
+        if (isParentalControlsEnabled && securityModel.getDeviceAdminIcon() != null) {
             icon = new Icon.Loaded(securityModel.getDeviceAdminIcon(), contentDescription);
         } else if (vpnName != null || vpnNameWorkProfile != null) {
             if (securityModel.isVpnBranded()) {
@@ -476,7 +476,7 @@
     @VisibleForTesting
     View createDialogView(Context quickSettingsContext) {
         if (mSecurityController.isParentalControlsEnabled()) {
-            return createParentalControlsDialogView();
+            return createParentalControlsDialogView(quickSettingsContext);
         }
         return createOrganizationDialogView(quickSettingsContext);
     }
@@ -579,8 +579,8 @@
         return dialogView;
     }
 
-    private View createParentalControlsDialogView() {
-        View dialogView = LayoutInflater.from(mContext)
+    private View createParentalControlsDialogView(Context quickSettingsContext) {
+        View dialogView = LayoutInflater.from(quickSettingsContext)
                 .inflate(R.layout.quick_settings_footer_dialog_parental_controls, null, false);
 
         DeviceAdminInfo info = mSecurityController.getDeviceAdminInfo();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index f37d668..cad296b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -41,6 +41,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.nano.SystemUIProtoDump;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTileView;
@@ -53,7 +54,6 @@
 import com.android.systemui.qs.nano.QsTileState;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -608,7 +608,7 @@
 
         if (TextUtils.isEmpty(tileList)) {
             tileList = res.getString(R.string.quick_settings_tiles);
-            if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);
+            if (DEBUG) Log.d(TAG, "Loaded tile specs from default config: " + tileList);
         } else {
             if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 27d9da6..946fe54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -288,8 +288,15 @@
         }
 
         MarginLayoutParams qqsLP = (MarginLayoutParams) mHeaderQsPanel.getLayoutParams();
-        qqsLP.topMargin = largeScreenHeaderActive || !mUseCombinedQSHeader ? mContext.getResources()
-                .getDimensionPixelSize(R.dimen.qqs_layout_margin_top) : qsOffsetHeight;
+        if (largeScreenHeaderActive) {
+            qqsLP.topMargin = mContext.getResources()
+                    .getDimensionPixelSize(R.dimen.qqs_layout_margin_top);
+        } else if (!mUseCombinedQSHeader) {
+            qqsLP.topMargin = qsOffsetHeight;
+        } else {
+            qqsLP.topMargin = mContext.getResources()
+                    .getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height);
+        }
         mHeaderQsPanel.setLayoutParams(qqsLP);
 
         updateBatteryMode();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
index 6b0abd4..7794fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs;
 
-import android.app.ActivityManager;
 import android.database.ContentObserver;
 import android.os.Handler;
 
@@ -47,10 +46,6 @@
         this(settingsProxy, handler, settingName, userId, 0);
     }
 
-    public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName) {
-        this(settingsProxy, handler, settingName, ActivityManager.getCurrentUser());
-    }
-
     public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName,
             int userId, int defaultValue) {
         super(handler);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 3d00dd4..7ee4047 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -123,7 +123,6 @@
     public boolean updateResources() {
         final Resources res = mContext.getResources();
         mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
-        updateColumns();
         mMaxCellHeight = mContext.getResources().getDimensionPixelSize(mCellHeightResId);
         mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal);
         mSidePadding = useSidePadding() ? mCellMarginHorizontal / 2 : 0;
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/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index cf10c79..79fcc7d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -82,12 +82,12 @@
         DefaultItemAnimator animator = new DefaultItemAnimator();
         animator.setMoveDuration(TileAdapter.MOVE_DURATION);
         mRecyclerView.setItemAnimator(animator);
+
+        updateTransparentViewHeight();
     }
 
     void updateResources() {
-        LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
-        lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
-        mTransparentView.setLayoutParams(lp);
+        updateTransparentViewHeight();
         mRecyclerView.getAdapter().notifyItemChanged(0);
     }
 
@@ -236,4 +236,10 @@
     public boolean isOpening() {
         return mOpening;
     }
+
+    private void updateTransparentViewHeight() {
+        LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
+        lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
+        mTransparentView.setLayoutParams(lp);
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index 9ba3501..03bb7a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -32,8 +32,6 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.FgsManagerController
@@ -42,10 +40,9 @@
 import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
 import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
 import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
-import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.security.data.repository.SecurityRepository
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.user.UserSwitcherActivity
+import com.android.systemui.user.domain.interactor.UserInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@ -100,13 +97,12 @@
 @Inject
 constructor(
     private val activityStarter: ActivityStarter,
-    private val featureFlags: FeatureFlags,
     private val metricsLogger: MetricsLogger,
     private val uiEventLogger: UiEventLogger,
     private val deviceProvisionedController: DeviceProvisionedController,
     private val qsSecurityFooterUtils: QSSecurityFooterUtils,
     private val fgsManagerController: FgsManagerController,
-    private val userSwitchDialogController: UserSwitchDialogController,
+    private val userInteractor: UserInteractor,
     securityRepository: SecurityRepository,
     foregroundServicesRepository: ForegroundServicesRepository,
     userSwitcherRepository: UserSwitcherRepository,
@@ -182,22 +178,6 @@
     }
 
     override fun showUserSwitcher(context: Context, expandable: Expandable) {
-        if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
-            userSwitchDialogController.showDialog(context, expandable)
-            return
-        }
-
-        val intent =
-            Intent(context, UserSwitcherActivity::class.java).apply {
-                addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
-            }
-
-        activityStarter.startActivity(
-            intent,
-            true /* dismissShade */,
-            expandable.activityLaunchController(),
-            true /* showOverlockscreenwhenlocked */,
-            UserHandle.SYSTEM,
-        )
+        userInteractor.showUserSwitcher(context, expandable)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 931dc8d..9f6317f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -129,12 +129,36 @@
         })
     }
 
-    fun logInternetTileUpdate(lastType: Int, callback: String) {
+    fun logInternetTileUpdate(tileSpec: String, lastType: Int, callback: String) {
         log(VERBOSE, {
+            str1 = tileSpec
             int1 = lastType
-            str1 = callback
+            str2 = callback
         }, {
-            "mLastTileState=$int1, Callback=$str1."
+            "[$str1] mLastTileState=$int1, Callback=$str2."
+        })
+    }
+
+    // TODO(b/250618218): Remove this method once we know the root cause of b/250618218.
+    fun logTileBackgroundColorUpdateIfInternetTile(
+        tileSpec: String,
+        state: Int,
+        disabledByPolicy: Boolean,
+        color: Int
+    ) {
+        // This method is added to further debug b/250618218 which has only been observed from the
+        // InternetTile, so we are only logging the background color change for the InternetTile
+        // to avoid spamming the QSLogger.
+        if (tileSpec != "internet") {
+            return
+        }
+        log(VERBOSE, {
+            str1 = tileSpec
+            int1 = state
+            bool1 = disabledByPolicy
+            int2 = color
+        }, {
+            "[$str1] state=$int1, disabledByPolicy=$bool1, color=$int2."
         })
     }
 
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..cd69f4e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -143,13 +143,18 @@
             if (d instanceof Animatable2) {
                 Animatable2 a = (Animatable2) d;
                 a.start();
-                if (state.isTransient) {
-                    a.registerAnimationCallback(new AnimationCallback() {
-                        @Override
-                        public void onAnimationEnd(Drawable drawable) {
-                            a.start();
-                        }
-                    });
+                if (shouldAnimate) {
+                    if (state.isTransient) {
+                        a.registerAnimationCallback(new AnimationCallback() {
+                            @Override
+                            public void onAnimationEnd(Drawable drawable) {
+                                a.start();
+                            }
+                        });
+                    }
+                } else {
+                    // Sends animator to end of animation. Needs to be called after calling start.
+                    a.stop();
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 972b243..b355d4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -50,6 +50,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.qs.QSTile.BooleanState
 import com.android.systemui.plugins.qs.QSTileView
+import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
 import java.util.Objects
 
@@ -116,7 +117,7 @@
     protected lateinit var sideView: ViewGroup
     private lateinit var customDrawableView: ImageView
     private lateinit var chevronView: ImageView
-
+    private var mQsLogger: QSLogger? = null
     protected var showRippleEffect = true
 
     private lateinit var ripple: RippleDrawable
@@ -188,6 +189,10 @@
         updateHeight()
     }
 
+    fun setQsLogger(qsLogger: QSLogger) {
+        mQsLogger = qsLogger
+    }
+
     fun updateResources() {
         FontSizeUtils.updateFontSize(label, R.dimen.qs_tile_text_size)
         FontSizeUtils.updateFontSize(secondaryLabel, R.dimen.qs_tile_text_size)
@@ -493,6 +498,11 @@
         // Colors
         if (state.state != lastState || state.disabledByPolicy || lastDisabledByPolicy) {
             singleAnimator.cancel()
+            mQsLogger?.logTileBackgroundColorUpdateIfInternetTile(
+                    state.spec,
+                    state.state,
+                    state.disabledByPolicy,
+                    getBackgroundColorForState(state.state, state.disabledByPolicy))
             if (allowAnimations) {
                 singleAnimator.setValues(
                         colorValuesHolder(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 86d4fa3..033dbe0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -48,6 +48,7 @@
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.GlobalSettings;
 
 import javax.inject.Inject;
@@ -74,14 +75,16 @@
             QSLogger qsLogger,
             BroadcastDispatcher broadcastDispatcher,
             Lazy<ConnectivityManager> lazyConnectivityManager,
-            GlobalSettings globalSettings
+            GlobalSettings globalSettings,
+            UserTracker userTracker
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBroadcastDispatcher = broadcastDispatcher;
         mLazyConnectivityManager = lazyConnectivityManager;
 
-        mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON) {
+        mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON,
+                userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 // mHandler is the background handler so calling this is OK
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index c65bd9b..41d8549 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.SelectedItem
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
@@ -125,14 +126,15 @@
         state.icon = icon
         if (controlsComponent.isEnabled() && hasControlsApps.get()) {
             if (controlsComponent.getVisibility() == AVAILABLE) {
-                val structureInfo = controlsComponent
-                    .getControlsController().get().getPreferredStructure()
-                state.state = if (structureInfo.controls.isEmpty()) {
+                val selection = controlsComponent
+                    .getControlsController().get().getPreferredSelection()
+                state.state = if (selection is SelectedItem.StructureItem &&
+                        selection.structure.controls.isEmpty()) {
                     Tile.STATE_INACTIVE
                 } else {
                     Tile.STATE_ACTIVE
                 }
-                val label = structureInfo.structure
+                val label = selection.name
                 state.secondaryLabel = if (label == tileLabel) null else label
             } else {
                 state.state = Tile.STATE_INACTIVE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index bebd580..5bc209a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -70,7 +70,7 @@
     private final SettingObserver mDreamSettingObserver;
     private final UserTracker mUserTracker;
     private final boolean mDreamSupported;
-    private final boolean mDreamOnlyEnabledForSystemUser;
+    private final boolean mDreamOnlyEnabledForDockUser;
 
     private boolean mIsDocked = false;
 
@@ -100,22 +100,22 @@
             BroadcastDispatcher broadcastDispatcher,
             UserTracker userTracker,
             @Named(DreamModule.DREAM_SUPPORTED) boolean dreamSupported,
-            @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_SYSTEM_USER)
-                    boolean dreamOnlyEnabledForSystemUser
+            @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_DOCK_USER)
+                    boolean dreamOnlyEnabledForDockUser
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mDreamManager = dreamManager;
         mBroadcastDispatcher = broadcastDispatcher;
         mEnabledSettingObserver = new SettingObserver(secureSettings, mHandler,
-                Settings.Secure.SCREENSAVER_ENABLED) {
+                Settings.Secure.SCREENSAVER_ENABLED, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 refreshState();
             }
         };
         mDreamSettingObserver = new SettingObserver(secureSettings, mHandler,
-                Settings.Secure.SCREENSAVER_COMPONENTS) {
+                Settings.Secure.SCREENSAVER_COMPONENTS, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 refreshState();
@@ -123,7 +123,7 @@
         };
         mUserTracker = userTracker;
         mDreamSupported = dreamSupported;
-        mDreamOnlyEnabledForSystemUser = dreamOnlyEnabledForSystemUser;
+        mDreamOnlyEnabledForDockUser = dreamOnlyEnabledForDockUser;
     }
 
     @Override
@@ -203,7 +203,8 @@
         // For now, restrict to debug users.
         return Build.isDebuggable()
                 && mDreamSupported
-                && (!mDreamOnlyEnabledForSystemUser || mUserTracker.getUserHandle().isSystem());
+                // TODO(b/257333623): Allow the Dock User to be non-SystemUser user in HSUM.
+                && (!mDreamOnlyEnabledForDockUser || mUserTracker.getUserHandle().isSystem());
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index ae46477..28dd986 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -188,8 +188,6 @@
         int mWifiSignalIconId;
         @Nullable
         String mSsid;
-        boolean mActivityIn;
-        boolean mActivityOut;
         @Nullable
         String mWifiSignalContentDescription;
         boolean mIsTransient;
@@ -207,8 +205,6 @@
                     .append(",mConnected=").append(mConnected)
                     .append(",mWifiSignalIconId=").append(mWifiSignalIconId)
                     .append(",mSsid=").append(mSsid)
-                    .append(",mActivityIn=").append(mActivityIn)
-                    .append(",mActivityOut=").append(mActivityOut)
                     .append(",mWifiSignalContentDescription=").append(mWifiSignalContentDescription)
                     .append(",mIsTransient=").append(mIsTransient)
                     .append(",mNoDefaultNetwork=").append(mNoDefaultNetwork)
@@ -226,8 +222,6 @@
         CharSequence mDataContentDescription;
         int mMobileSignalIconId;
         int mQsTypeIcon;
-        boolean mActivityIn;
-        boolean mActivityOut;
         boolean mNoSim;
         boolean mRoaming;
         boolean mMultipleSubs;
@@ -243,8 +237,6 @@
                 .append(",mDataContentDescription=").append(mDataContentDescription)
                 .append(",mMobileSignalIconId=").append(mMobileSignalIconId)
                 .append(",mQsTypeIcon=").append(mQsTypeIcon)
-                .append(",mActivityIn=").append(mActivityIn)
-                .append(",mActivityOut=").append(mActivityOut)
                 .append(",mNoSim=").append(mNoSim)
                 .append(",mRoaming=").append(mRoaming)
                 .append(",mMultipleSubs=").append(mMultipleSubs)
@@ -275,8 +267,6 @@
             mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
             mWifiInfo.mEnabled = indicators.enabled;
             mWifiInfo.mSsid = indicators.description;
-            mWifiInfo.mActivityIn = indicators.activityIn;
-            mWifiInfo.mActivityOut = indicators.activityOut;
             mWifiInfo.mIsTransient = indicators.isTransient;
             mWifiInfo.mStatusLabel = indicators.statusLabel;
             refreshState(mWifiInfo);
@@ -297,8 +287,6 @@
                     ? indicators.typeContentDescriptionHtml : null;
             mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon;
             mCellularInfo.mQsTypeIcon = indicators.qsType;
-            mCellularInfo.mActivityIn = indicators.activityIn;
-            mCellularInfo.mActivityOut = indicators.activityOut;
             mCellularInfo.mRoaming = indicators.roaming;
             mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1;
             refreshState(mCellularInfo);
@@ -345,7 +333,14 @@
             mCellularInfo.mAirplaneModeEnabled = icon.visible;
             mWifiInfo.mAirplaneModeEnabled = icon.visible;
             if (!mSignalCallback.mEthernetInfo.mConnected) {
-                if (mWifiInfo.mEnabled && (mWifiInfo.mWifiSignalIconId > 0)
+                // Always use mWifiInfo to refresh the Internet Tile if airplane mode is enabled,
+                // because Internet Tile will show different information depending on whether WiFi
+                // is enabled or not.
+                if (mWifiInfo.mAirplaneModeEnabled) {
+                    refreshState(mWifiInfo);
+                // If airplane mode is disabled, we will use mWifiInfo to refresh the Internet Tile
+                // if WiFi is currently connected to avoid any icon flickering.
+                } else if (mWifiInfo.mEnabled && (mWifiInfo.mWifiSignalIconId > 0)
                         && (mWifiInfo.mSsid != null)) {
                     refreshState(mWifiInfo);
                 } else {
@@ -387,7 +382,8 @@
 
     @Override
     protected void handleUpdateState(SignalState state, Object arg) {
-        mQSLogger.logInternetTileUpdate(mLastTileState, arg == null ? "null" : arg.toString());
+        mQSLogger.logInternetTileUpdate(
+                getTileSpec(), mLastTileState, arg == null ? "null" : arg.toString());
         if (arg instanceof CellularCallbackInfo) {
             mLastTileState = LAST_STATE_CELLULAR;
             handleUpdateCellularState(state, arg);
@@ -427,8 +423,6 @@
         state.state = Tile.STATE_ACTIVE;
         state.dualTarget = true;
         state.value = cb.mEnabled;
-        state.activityIn = cb.mEnabled && cb.mActivityIn;
-        state.activityOut = cb.mEnabled && cb.mActivityOut;
         final StringBuffer minimalContentDescription = new StringBuffer();
         final StringBuffer minimalStateDescription = new StringBuffer();
         final Resources r = mContext.getResources();
@@ -502,8 +496,6 @@
         boolean mobileDataEnabled = mDataController.isMobileDataSupported()
                 && mDataController.isMobileDataEnabled();
         state.value = mobileDataEnabled;
-        state.activityIn = mobileDataEnabled && cb.mActivityIn;
-        state.activityOut = mobileDataEnabled && cb.mActivityOut;
         state.expandedAccessibilityClassName = Switch.class.getName();
 
         if (cb.mAirplaneModeEnabled && cb.mQsTypeIcon != TelephonyIcons.ICON_CWF) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index b415022..6d50b56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -115,8 +115,12 @@
         state.label = mContext.getString(R.string.qr_code_scanner_title);
         state.contentDescription = state.label;
         state.icon = ResourceIcon.get(R.drawable.ic_qr_code_scanner);
-        state.state = mQRCodeScannerController.isEnabledForQuickSettings() ? Tile.STATE_ACTIVE
+        state.state = mQRCodeScannerController.isAbleToOpenCameraApp() ? Tile.STATE_INACTIVE
                 : Tile.STATE_UNAVAILABLE;
+        // The assumption is that if the OEM has the QR code scanner module enabled then the scanner
+        // would go to "Unavailable" state only when GMS core is updating.
+        state.secondaryLabel = state.state == Tile.STATE_UNAVAILABLE
+                ? mContext.getString(R.string.qr_code_scanner_updating_secondary_label) : null;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index f63f044..64a8a14 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles;
 
+import android.app.Dialog;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Looper;
@@ -43,7 +44,6 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.screenrecord.RecordingController;
-import com.android.systemui.screenrecord.ScreenRecordDialog;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -170,9 +170,9 @@
             mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
             getHost().collapsePanels();
         };
-        ScreenRecordDialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
-                mDialogLaunchAnimator, mActivityStarter, onStartRecordingClicked);
 
+        Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
+                mDialogLaunchAnimator, mActivityStarter, onStartRecordingClicked);
         ActivityStarter.OnDismissAction dismissAction = () -> {
             if (shouldAnimateFromView) {
                 mDialogLaunchAnimator.showFromView(dialog, view, new DialogCuj(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 7130294..a6c7781 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -27,6 +27,7 @@
 import android.view.View;
 import android.widget.Switch;
 
+import androidx.annotation.MainThread;
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.MetricsLogger;
@@ -91,11 +92,13 @@
     }
 
     @Override
+    @MainThread
     public void onManagedProfileChanged() {
         refreshState(mProfileController.isWorkModeEnabled());
     }
 
     @Override
+    @MainThread
     public void onManagedProfileRemoved() {
         mHost.removeTile(getTileSpec());
         mHost.unmarkTileAsAutoAdded(getTileSpec());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index a895d72..9743c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -249,15 +249,7 @@
         mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
         mInternetDialogTitle.setText(getDialogTitleText());
         mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
-
-        TypedArray typedArray = mContext.obtainStyledAttributes(
-                new int[]{android.R.attr.selectableItemBackground});
-        try {
-            mBackgroundOff = typedArray.getDrawable(0 /* index */);
-        } finally {
-            typedArray.recycle();
-        }
-
+        mBackgroundOff = mContext.getDrawable(R.drawable.internet_dialog_selected_effect);
         setOnClickListener();
         mTurnWifiOnLayout.setBackground(null);
         mAirplaneModeButton.setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index aa6e678..a4ce6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -142,21 +142,28 @@
     private static final int SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE =
             R.string.all_network_unavailable;
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final TelephonyDisplayInfo DEFAULT_TELEPHONY_DISPLAY_INFO =
+            new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
 
     static final int MAX_WIFI_ENTRY_COUNT = 3;
 
     private final FeatureFlags mFeatureFlags;
 
+    @VisibleForTesting
+    /** Should be accessible only to the main thread. */
+    final Map<Integer, TelephonyDisplayInfo> mSubIdTelephonyDisplayInfoMap = new HashMap<>();
+
     private WifiManager mWifiManager;
     private Context mContext;
     private SubscriptionManager mSubscriptionManager;
+    /** Should be accessible only to the main thread. */
     private Map<Integer, TelephonyManager> mSubIdTelephonyManagerMap = new HashMap<>();
+    /** Should be accessible only to the main thread. */
+    private Map<Integer, TelephonyCallback> mSubIdTelephonyCallbackMap = new HashMap<>();
     private TelephonyManager mTelephonyManager;
     private ConnectivityManager mConnectivityManager;
     private CarrierConfigTracker mCarrierConfigTracker;
-    private TelephonyDisplayInfo mTelephonyDisplayInfo =
-            new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
     private Handler mHandler;
     private Handler mWorkerHandler;
     private MobileMappings.Config mConfig = null;
@@ -190,8 +197,6 @@
     @VisibleForTesting
     protected SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
     @VisibleForTesting
-    protected InternetTelephonyCallback mInternetTelephonyCallback;
-    @VisibleForTesting
     protected WifiUtils.InternetIconInjector mWifiIconInjector;
     @VisibleForTesting
     protected boolean mCanConfigWifi;
@@ -290,8 +295,10 @@
         mConfig = MobileMappings.Config.readConfig(mContext);
         mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
         mSubIdTelephonyManagerMap.put(mDefaultDataSubId, mTelephonyManager);
-        mInternetTelephonyCallback = new InternetTelephonyCallback();
-        mTelephonyManager.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback);
+        InternetTelephonyCallback telephonyCallback =
+                new InternetTelephonyCallback(mDefaultDataSubId);
+        mSubIdTelephonyCallbackMap.put(mDefaultDataSubId, telephonyCallback);
+        mTelephonyManager.registerTelephonyCallback(mExecutor, telephonyCallback);
         // Listen the connectivity changes
         mConnectivityManager.registerDefaultNetworkCallback(mConnectivityManagerNetworkCallback);
         mCanConfigWifi = canConfigWifi;
@@ -304,7 +311,12 @@
         }
         mBroadcastDispatcher.unregisterReceiver(mConnectionStateReceiver);
         for (TelephonyManager tm : mSubIdTelephonyManagerMap.values()) {
-            tm.unregisterTelephonyCallback(mInternetTelephonyCallback);
+            TelephonyCallback callback = mSubIdTelephonyCallbackMap.get(tm.getSubscriptionId());
+            if (callback != null) {
+                tm.unregisterTelephonyCallback(callback);
+            } else if (DEBUG) {
+                Log.e(TAG, "Unexpected null telephony call back for Sub " + tm.getSubscriptionId());
+            }
         }
         mSubscriptionManager.removeOnSubscriptionsChangedListener(
                 mOnSubscriptionsChangedListener);
@@ -623,7 +635,9 @@
             int subId = subInfo.getSubscriptionId();
             if (mSubIdTelephonyManagerMap.get(subId) == null) {
                 TelephonyManager secondaryTm = mTelephonyManager.createForSubscriptionId(subId);
-                secondaryTm.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback);
+                InternetTelephonyCallback telephonyCallback = new InternetTelephonyCallback(subId);
+                secondaryTm.registerTelephonyCallback(mExecutor, telephonyCallback);
+                mSubIdTelephonyCallbackMap.put(subId, telephonyCallback);
                 mSubIdTelephonyManagerMap.put(subId, secondaryTm);
             }
             return subId;
@@ -637,8 +651,7 @@
     }
 
     String getMobileNetworkSummary(int subId) {
-        String description = getNetworkTypeDescription(mContext, mConfig,
-                mTelephonyDisplayInfo, subId);
+        String description = getNetworkTypeDescription(mContext, mConfig, subId);
         return getMobileSummary(mContext, description, subId);
     }
 
@@ -646,7 +659,9 @@
      * Get currently description of mobile network type.
      */
     private String getNetworkTypeDescription(Context context, MobileMappings.Config config,
-            TelephonyDisplayInfo telephonyDisplayInfo, int subId) {
+            int subId) {
+        TelephonyDisplayInfo telephonyDisplayInfo =
+                mSubIdTelephonyDisplayInfoMap.getOrDefault(subId, DEFAULT_TELEPHONY_DISPLAY_INFO);
         String iconKey = getIconKey(telephonyDisplayInfo);
 
         if (mapIconSets(config) == null || mapIconSets(config).get(iconKey) == null) {
@@ -725,11 +740,10 @@
 
     Intent getSubSettingIntent(int subId) {
         final Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS);
-
         final Bundle fragmentArgs = new Bundle();
         // Special contract for Settings to highlight permission row
         fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID);
-        fragmentArgs.putInt(Settings.EXTRA_SUB_ID, subId);
+        intent.putExtra(Settings.EXTRA_SUB_ID, subId);
         intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
         return intent;
     }
@@ -1082,6 +1096,11 @@
             TelephonyCallback.SignalStrengthsListener,
             TelephonyCallback.UserMobileDataStateListener {
 
+        private final int mSubId;
+        private InternetTelephonyCallback(int subId) {
+            mSubId = subId;
+        }
+
         @Override
         public void onServiceStateChanged(@NonNull ServiceState serviceState) {
             mCallback.onServiceStateChanged(serviceState);
@@ -1099,7 +1118,7 @@
 
         @Override
         public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
-            mTelephonyDisplayInfo = telephonyDisplayInfo;
+            mSubIdTelephonyDisplayInfoMap.put(mSubId, telephonyDisplayInfo);
             mCallback.onDisplayInfoChanged(telephonyDisplayInfo);
         }
 
@@ -1224,19 +1243,30 @@
             }
             return;
         }
-
-        mDefaultDataSubId = defaultDataSubId;
         if (DEBUG) {
-            Log.d(TAG, "DDS: defaultDataSubId:" + mDefaultDataSubId);
+            Log.d(TAG, "DDS: defaultDataSubId:" + defaultDataSubId);
         }
-        if (SubscriptionManager.isUsableSubscriptionId(mDefaultDataSubId)) {
-            mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback);
-            mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
-            mSubIdTelephonyManagerMap.put(mDefaultDataSubId, mTelephonyManager);
-            mTelephonyManager.registerTelephonyCallback(mHandler::post,
-                    mInternetTelephonyCallback);
-            mCallback.onSubscriptionsChanged(mDefaultDataSubId);
+        if (SubscriptionManager.isUsableSubscriptionId(defaultDataSubId)) {
+            // clean up old defaultDataSubId
+            TelephonyCallback oldCallback = mSubIdTelephonyCallbackMap.get(mDefaultDataSubId);
+            if (oldCallback != null) {
+                mTelephonyManager.unregisterTelephonyCallback(oldCallback);
+            } else if (DEBUG) {
+                Log.e(TAG, "Unexpected null telephony call back for Sub " + mDefaultDataSubId);
+            }
+            mSubIdTelephonyCallbackMap.remove(mDefaultDataSubId);
+            mSubIdTelephonyDisplayInfoMap.remove(mDefaultDataSubId);
+            mSubIdTelephonyManagerMap.remove(mDefaultDataSubId);
+
+            // create for new defaultDataSubId
+            mTelephonyManager = mTelephonyManager.createForSubscriptionId(defaultDataSubId);
+            mSubIdTelephonyManagerMap.put(defaultDataSubId, mTelephonyManager);
+            InternetTelephonyCallback newCallback = new InternetTelephonyCallback(defaultDataSubId);
+            mSubIdTelephonyCallbackMap.put(defaultDataSubId, newCallback);
+            mTelephonyManager.registerTelephonyCallback(mHandler::post, newCallback);
+            mCallback.onSubscriptionsChanged(defaultDataSubId);
         }
+        mDefaultDataSubId = defaultDataSubId;
     }
 
     public WifiUtils.InternetIconInjector getWifiIconInjector() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index 314252b..4c9c99c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.qs.QSUserSwitcherEvent
 import com.android.systemui.qs.tiles.UserDetailView
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.user.ui.dialog.DialogShowerImpl
 import javax.inject.Inject
 import javax.inject.Provider
 
@@ -130,19 +131,6 @@
         }
     }
 
-    private class DialogShowerImpl(
-        private val animateFrom: Dialog,
-        private val dialogLaunchAnimator: DialogLaunchAnimator
-    ) : DialogInterface by animateFrom, DialogShower {
-        override fun showDialog(dialog: Dialog, cuj: DialogCuj) {
-            dialogLaunchAnimator.showFromDialog(
-                dialog,
-                animateFrom = animateFrom,
-                cuj
-            )
-        }
-    }
-
     interface DialogShower : DialogInterface {
         fun showDialog(dialog: Dialog, cuj: DialogCuj)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
new file mode 100644
index 0000000..802db7e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
@@ -0,0 +1,208 @@
+/*
+ * 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.reardisplay;
+
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManagerGlobal;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.CoreStartable;
+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;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+import com.airbnb.lottie.LottieAnimationView;
+import com.airbnb.lottie.LottieDrawable;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * Provides an educational dialog to the user alerting them to what
+ * they may need to do to enter rear display mode. This may be to open the
+ * device if it is currently folded, or to confirm that they would like
+ * the content to move to the screen on their device that is aligned with
+ * the rear camera. This includes a device animation to provide more context
+ * to the user.
+ *
+ * We are suppressing lint for the VisibleForTests check because the use of
+ * DeviceStateManagerGlobal as in this file should not be encouraged for other use-cases.
+ * The lint check will notify any other use-cases that they are possibly doing something
+ * incorrectly.
+ */
+@SuppressLint("VisibleForTests") // TODO(b/260264542) Migrate away from DeviceStateManagerGlobal
+@SysUISingleton
+public class RearDisplayDialogController implements CoreStartable, CommandQueue.Callbacks {
+
+    private int[] mFoldedStates;
+    private boolean mStartedFolded;
+    private boolean mServiceNotified = false;
+    private int mAnimationRepeatCount = LottieDrawable.INFINITE;
+
+    private DeviceStateManagerGlobal mDeviceStateManagerGlobal;
+    private DeviceStateManager.DeviceStateCallback mDeviceStateManagerCallback =
+            new DeviceStateManagerCallback();
+
+    private final Context mContext;
+    private final CommandQueue mCommandQueue;
+    private final Executor mExecutor;
+
+    @VisibleForTesting
+    SystemUIDialog mRearDisplayEducationDialog;
+
+    @Inject
+    public RearDisplayDialogController(Context context, CommandQueue commandQueue,
+            @Main Executor executor) {
+        mContext = context;
+        mCommandQueue = commandQueue;
+        mExecutor = executor;
+    }
+
+    @Override
+    public void start() {
+        mCommandQueue.addCallback(this);
+    }
+
+    @Override
+    public void showRearDisplayDialog(int currentBaseState) {
+        initializeValues(currentBaseState);
+        createAndShowDialog();
+    }
+
+    private void createAndShowDialog() {
+        mServiceNotified = false;
+        Context dialogContext = mRearDisplayEducationDialog.getContext();
+
+        View dialogView;
+        if (mStartedFolded) {
+            dialogView = View.inflate(dialogContext,
+                    R.layout.activity_rear_display_education, null);
+        } else {
+            dialogView = View.inflate(dialogContext,
+                    R.layout.activity_rear_display_education_opened, null);
+        }
+        LottieAnimationView animationView = dialogView.findViewById(
+                R.id.rear_display_folded_animation);
+        animationView.setRepeatCount(mAnimationRepeatCount);
+        mRearDisplayEducationDialog.setView(dialogView);
+
+        configureDialogButtons();
+
+        mRearDisplayEducationDialog.show();
+    }
+
+    /**
+     * Configures the buttons on the dialog depending on the starting device posture
+     */
+    private void configureDialogButtons() {
+        // If we are open, we need to provide a confirm option
+        if (!mStartedFolded) {
+            mRearDisplayEducationDialog.setPositiveButton(
+                    R.string.rear_display_bottom_sheet_confirm,
+                    (dialog, which) -> closeOverlayAndNotifyService(false), true);
+        }
+        mRearDisplayEducationDialog.setNegativeButton(R.string.rear_display_bottom_sheet_cancel,
+                (dialog, which) -> closeOverlayAndNotifyService(true), true);
+        mRearDisplayEducationDialog.setOnDismissListener(dialog -> {
+            // Dialog is being dismissed before we've notified the system server
+            if (!mServiceNotified) {
+                closeOverlayAndNotifyService(true);
+            }
+        });
+    }
+
+    /**
+     * Initializes properties and values we need when getting ready to show the dialog.
+     *
+     * Ensures we're not using old values from when the dialog may have been shown previously.
+     */
+    private void initializeValues(int startingBaseState) {
+        mRearDisplayEducationDialog = new SystemUIDialog(mContext);
+        if (mFoldedStates == null) {
+            mFoldedStates = mContext.getResources().getIntArray(
+                    com.android.internal.R.array.config_foldedDeviceStates);
+        }
+        mStartedFolded = isFoldedState(startingBaseState);
+        mDeviceStateManagerGlobal = DeviceStateManagerGlobal.getInstance();
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(mDeviceStateManagerCallback,
+                mExecutor);
+    }
+
+    private boolean isFoldedState(int state) {
+        for (int i = 0; i < mFoldedStates.length; i++) {
+            if (mFoldedStates[i] == state) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Closes the educational overlay, and notifies the system service if rear display mode
+     * should be cancelled or enabled.
+     */
+    private void closeOverlayAndNotifyService(boolean shouldCancelRequest) {
+        mServiceNotified = true;
+        mDeviceStateManagerGlobal.unregisterDeviceStateCallback(mDeviceStateManagerCallback);
+        mDeviceStateManagerGlobal.onStateRequestOverlayDismissed(shouldCancelRequest);
+    }
+
+    /**
+     * TestAPI to allow us to set the folded states array, instead of reading from resources.
+     */
+    @TestApi
+    void setFoldedStates(int[] foldedStates) {
+        mFoldedStates = foldedStates;
+    }
+
+    @TestApi
+    void setDeviceStateManagerCallback(
+            DeviceStateManager.DeviceStateCallback deviceStateManagerCallback) {
+        mDeviceStateManagerCallback = deviceStateManagerCallback;
+    }
+
+    @TestApi
+    void setAnimationRepeatCount(int repeatCount) {
+        mAnimationRepeatCount = repeatCount;
+    }
+
+    private class DeviceStateManagerCallback implements DeviceStateManager.DeviceStateCallback {
+        @Override
+        public void onBaseStateChanged(int state) {
+            if (mStartedFolded && !isFoldedState(state)) {
+                // We've opened the device, we can close the overlay
+                mRearDisplayEducationDialog.dismiss();
+                closeOverlayAndNotifyService(false);
+            } else if (!mStartedFolded && isFoldedState(state)) {
+                // We've closed the device, finish activity
+                mRearDisplayEducationDialog.dismiss();
+                closeOverlayAndNotifyService(true);
+            }
+        }
+
+        // We only care about physical device changes in this scenario
+        @Override
+        public void onStateChanged(int state) {}
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 66be00d..00d129a 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;
 
@@ -77,8 +78,8 @@
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.systemui.Dumpable;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.ScreenLifecycle;
@@ -89,12 +90,11 @@
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.buttons.KeyButtonView;
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -107,21 +107,19 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
-import java.util.function.BiConsumer;
+import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
 import javax.inject.Inject;
 
 import dagger.Lazy;
 
-
 /**
  * Class to send information from overview to launcher with a binder.
  */
 @SysUISingleton
-public class OverviewProxyService extends CurrentUserTracker implements
-        CallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener,
-        Dumpable {
+public class OverviewProxyService implements CallbackController<OverviewProxyListener>,
+        NavigationModeController.ModeChangedListener, Dumpable {
 
     private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
 
@@ -133,6 +131,7 @@
     private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
 
     private final Context mContext;
+    private final Executor mMainExecutor;
     private final ShellInterface mShellInterface;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private SysUiState mSysUiState;
@@ -145,10 +144,12 @@
     private final Intent mQuickStepIntent;
     private final ScreenshotHelper mScreenshotHelper;
     private final CommandQueue mCommandQueue;
+    private final UserTracker mUserTracker;
     private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
     private final UiEventLogger mUiEventLogger;
 
     private Region mActiveNavBarRegion;
+    private SurfaceControl mNavigationBarSurface;
 
     private IOverviewProxy mOverviewProxy;
     private int mConnectionBackoffAttempts;
@@ -190,7 +191,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 +219,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 +316,6 @@
         }
 
         @Override
-        public void notifySwipeUpGestureStarted() {
-            verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () ->
-                    notifySwipeUpGestureStartedInternal());
-        }
-
-        @Override
         public void notifyPrioritizedRotation(@Surface.Rotation int rotation) {
             verifyCallerAndClearCallingIdentityPostMain("notifyPrioritizedRotation", () ->
                     notifyPrioritizedRotationInternal(rotation));
@@ -423,7 +417,7 @@
                 return;
             }
 
-            mCurrentBoundedUserId = getCurrentUserId();
+            mCurrentBoundedUserId = mUserTracker.getUserId();
             mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
 
             Bundle params = new Bundle();
@@ -443,6 +437,7 @@
                 Log.e(TAG_OPS, "Failed to call onInitialize()", e);
             }
             dispatchNavButtonBounds();
+            dispatchNavigationBarSurface();
 
             // Force-update the systemui state flags
             updateSystemUiStateFlags();
@@ -474,8 +469,6 @@
     };
 
     private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged;
-    private final BiConsumer<Rect, Rect> mSplitScreenBoundsChangeListener =
-            this::notifySplitScreenBoundsChanged;
 
     // This is the death handler for the binder from the launcher service
     private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
@@ -505,34 +498,44 @@
         }
     };
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    mConnectionBackoffAttempts = 0;
+                    internalConnectToCurrentUser();
+                }
+            };
+
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     @Inject
     public OverviewProxyService(Context context,
+            @Main Executor mainExecutor,
             CommandQueue commandQueue,
             ShellInterface shellInterface,
             Lazy<NavigationBarController> navBarControllerLazy,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             NavigationModeController navModeController,
             NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
-            BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker,
             ScreenLifecycle screenLifecycle,
             UiEventLogger uiEventLogger,
             KeyguardUnlockAnimationController sysuiUnlockAnimationController,
             AssistUtils assistUtils,
             DumpManager dumpManager) {
-        super(broadcastDispatcher);
-
         // b/241601880: This component shouldn't be running for a non-primary user
         if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) {
             Log.e(TAG_OPS, "Unexpected initialization for non-primary user", new Throwable());
         }
 
         mContext = context;
+        mMainExecutor = mainExecutor;
         mShellInterface = shellInterface;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mHandler = new Handler();
         mNavBarControllerLazy = navBarControllerLazy;
         mStatusBarWinController = statusBarWinController;
+        mUserTracker = userTracker;
         mConnectionBackoffAttempts = 0;
         mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
                 com.android.internal.R.string.config_recentsComponentName));
@@ -562,18 +565,30 @@
         statusBarWinController.registerCallback(mStatusBarWindowCallback);
         mScreenshotHelper = new ScreenshotHelper(context);
 
-        // Listen for tracing state changes
         commandQueue.addCallback(new CommandQueue.Callbacks() {
+
+            // Listen for tracing state changes
             @Override
             public void onTracingStateChanged(boolean enabled) {
                 mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
                         .commitUpdate(mContext.getDisplayId());
             }
+
+            @Override
+            public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+                if (mOverviewProxy != null) {
+                    try {
+                        mOverviewProxy.enterStageSplitFromRunningApp(leftOrTop);
+                    } catch (RemoteException e) {
+                        Log.w(TAG_OPS, "Unable to enter stage split from the current running app");
+                    }
+                }
+            }
         });
         mCommandQueue = commandQueue;
 
         // Listen for user setup
-        startTracking();
+        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
 
         screenLifecycle.addObserver(mLifecycleObserver);
 
@@ -586,22 +601,23 @@
         assistUtils.registerVoiceInteractionSessionListener(mVoiceInteractionSessionListener);
     }
 
-    @Override
-    public void onUserSwitched(int newUserId) {
-        mConnectionBackoffAttempts = 0;
-        internalConnectToCurrentUser();
-    }
-
     public void onVoiceSessionWindowVisibilityChanged(boolean visible) {
         mSysUiState.setFlag(SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING, visible)
                 .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 +630,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);
@@ -712,7 +728,7 @@
             mBound = mContext.bindServiceAsUser(launcherServiceIntent,
                     mOverviewServiceConnection,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
-                    UserHandle.of(getCurrentUserId()));
+                    UserHandle.of(mUserTracker.getUserId()));
         } catch (SecurityException e) {
             Log.e(TAG_OPS, "Unable to bind because of security error", e);
         }
@@ -800,24 +816,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 +840,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) {
@@ -854,26 +852,6 @@
         }
     }
 
-    /**
-     * Notifies the Launcher of split screen size changes
-     *
-     * @param secondaryWindowBounds Bounds of the secondary window including the insets
-     * @param secondaryWindowInsets stable insets received by the secondary window
-     */
-    public void notifySplitScreenBoundsChanged(
-            Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
-        try {
-            if (mOverviewProxy != null) {
-                mOverviewProxy.onSplitScreenSecondaryBoundsChanged(
-                        secondaryWindowBounds, secondaryWindowInsets);
-            } else {
-                Log.e(TAG_OPS, "Failed to get overview proxy for split screen bounds.");
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG_OPS, "Failed to call onSplitScreenSecondaryBoundsChanged()", e);
-        }
-    }
-
     private final ScreenLifecycle.Observer mLifecycleObserver = new ScreenLifecycle.Observer() {
         /**
          * Notifies the Launcher that screen turned on and ready to use
@@ -979,7 +957,7 @@
     }
 
     private void updateEnabledState() {
-        final int currentUser = ActivityManagerWrapper.getInstance().getCurrentUserId();
+        final int currentUser = mUserTracker.getUserId();
         mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
                 MATCH_SYSTEM_ONLY, currentUser) != null;
     }
@@ -1005,23 +983,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/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 2ee5f05..645b125 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -51,10 +51,13 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.util.leak.RotationUtils;
@@ -76,6 +79,7 @@
     private final AccessibilityManager mAccessibilityService;
     private final WindowManager mWindowManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final UserTracker mUserTracker;
 
     private RequestWindowView mRequestWindow;
     private int mNavBarMode;
@@ -83,12 +87,21 @@
     /** ID of task to be pinned or locked. */
     private int taskId;
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    clearPrompt();
+                }
+            };
+
     @Inject
     public ScreenPinningRequest(
             Context context,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             NavigationModeController navigationModeController,
-            BroadcastDispatcher broadcastDispatcher) {
+            BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker) {
         mContext = context;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mAccessibilityService = (AccessibilityManager)
@@ -97,6 +110,7 @@
                 mContext.getSystemService(Context.WINDOW_SERVICE);
         mNavBarMode = navigationModeController.addListener(this);
         mBroadcastDispatcher = broadcastDispatcher;
+        mUserTracker = userTracker;
     }
 
     public void clearPrompt() {
@@ -228,9 +242,9 @@
             }
 
             IntentFilter filter = new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
-            filter.addAction(Intent.ACTION_USER_SWITCHED);
             filter.addAction(Intent.ACTION_SCREEN_OFF);
             mBroadcastDispatcher.registerReceiver(mReceiver, filter);
+            mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
         }
 
         private void inflateView(int rotation) {
@@ -358,6 +372,7 @@
         @Override
         public void onDetachedFromWindow() {
             mBroadcastDispatcher.unregisterReceiver(mReceiver);
+            mUserTracker.removeCallback(mUserChangedCallback);
         }
 
         protected void onConfigurationChanged() {
@@ -388,8 +403,7 @@
             public void onReceive(Context context, Intent intent) {
                 if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
                     post(mUpdateLayoutRunnable);
-                } else if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)
-                        || intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+                } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                     clearPrompt();
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
deleted file mode 100644
index d2f3a6a..0000000
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ /dev/null
@@ -1,228 +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.ripple
-
-import android.graphics.PointF
-import android.graphics.RuntimeShader
-import android.util.MathUtils
-
-/**
- * Shader class that renders an expanding ripple effect. The ripple contains three elements:
- *
- * 1. an expanding filled [RippleShape] that appears in the beginning and quickly fades away
- * 2. an expanding ring that appears throughout the effect
- * 3. an expanding ring-shaped area that reveals noise over #2.
- *
- * The ripple shader will be default to the circle shape if not specified.
- *
- * Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
- */
-class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.CIRCLE) :
-        RuntimeShader(buildShader(rippleShape)) {
-
-    /** Shapes that the [RippleShader] supports. */
-    enum class RippleShape {
-        CIRCLE,
-        ROUNDED_BOX,
-        ELLIPSE
-    }
-    //language=AGSL
-    companion object {
-        private const val SHADER_UNIFORMS = """uniform vec2 in_center;
-                uniform vec2 in_size;
-                uniform float in_progress;
-                uniform float in_cornerRadius;
-                uniform float in_thickness;
-                uniform float in_time;
-                uniform float in_distort_radial;
-                uniform float in_distort_xy;
-                uniform float in_fadeSparkle;
-                uniform float in_fadeFill;
-                uniform float in_fadeRing;
-                uniform float in_blur;
-                uniform float in_pixelDensity;
-                layout(color) uniform vec4 in_color;
-                uniform float in_sparkle_strength;"""
-
-        private const val SHADER_CIRCLE_MAIN = """vec4 main(vec2 p) {
-                vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
-                float radius = in_size.x * 0.5;
-                float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
-                float inside = soften(sdCircle(p_distorted-in_center, radius * 1.2), in_blur);
-                float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
-                    * (1.-sparkleRing) * in_fadeSparkle;
-
-                float rippleInsideAlpha = (1.-inside) * in_fadeFill;
-                float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
-                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
-                vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
-                return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
-            }
-        """
-
-        private const val SHADER_ROUNDED_BOX_MAIN = """vec4 main(vec2 p) {
-                float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
-                    in_thickness), in_blur);
-                float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
-                    in_blur);
-                float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
-                    * (1.-sparkleRing) * in_fadeSparkle;
-
-                float rippleInsideAlpha = (1.-inside) * in_fadeFill;
-                float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
-                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
-                vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
-                return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
-            }
-        """
-
-        private const val SHADER_ELLIPSE_MAIN = """vec4 main(vec2 p) {
-                vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
-
-                float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
-                float inside = soften(sdEllipse(p_distorted-in_center, in_size * 1.2), in_blur);
-                float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
-                    * (1.-sparkleRing) * in_fadeSparkle;
-
-                float rippleInsideAlpha = (1.-inside) * in_fadeFill;
-                float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
-                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
-                vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
-                return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
-            }
-        """
-
-        private const val CIRCLE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
-                SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.CIRCLE_SDF +
-                SHADER_CIRCLE_MAIN
-        private const val ROUNDED_BOX_SHADER = SHADER_UNIFORMS +
-                RippleShaderUtilLibrary.SHADER_LIB + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
-                SdfShaderLibrary.ROUNDED_BOX_SDF + SHADER_ROUNDED_BOX_MAIN
-        private const val ELLIPSE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
-                SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.ELLIPSE_SDF +
-                SHADER_ELLIPSE_MAIN
-
-        private fun buildShader(rippleShape: RippleShape): String =
-                when (rippleShape) {
-                    RippleShape.CIRCLE -> CIRCLE_SHADER
-                    RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
-                    RippleShape.ELLIPSE -> ELLIPSE_SHADER
-                }
-
-        private fun subProgress(start: Float, end: Float, progress: Float): Float {
-            val min = Math.min(start, end)
-            val max = Math.max(start, end)
-            val sub = Math.min(Math.max(progress, min), max)
-            return (sub - start) / (end - start)
-        }
-    }
-
-    /**
-     * Sets the center position of the ripple.
-     */
-    fun setCenter(x: Float, y: Float) {
-        setFloatUniform("in_center", x, y)
-    }
-
-    /** Max width of the ripple. */
-    private var maxSize: PointF = PointF()
-    fun setMaxSize(width: Float, height: Float) {
-        maxSize.x = width
-        maxSize.y = height
-    }
-
-    /**
-     * Progress of the ripple. Float value between [0, 1].
-     */
-    var progress: Float = 0.0f
-        set(value) {
-            field = value
-            setFloatUniform("in_progress", value)
-            val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
-
-            setFloatUniform("in_size", /* width= */ maxSize.x * curvedProg,
-                    /* height= */ maxSize.y * curvedProg)
-            setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
-            // radius should not exceed width and height values.
-            setFloatUniform("in_cornerRadius",
-                    Math.min(maxSize.x, maxSize.y) * curvedProg)
-
-            setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
-
-            val fadeIn = subProgress(0f, 0.1f, value)
-            val fadeOutNoise = subProgress(0.4f, 1f, value)
-            var fadeOutRipple = 0f
-            var fadeFill = 0f
-            if (!rippleFill) {
-                fadeFill = subProgress(0f, 0.6f, value)
-                fadeOutRipple = subProgress(0.3f, 1f, value)
-            }
-            setFloatUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
-            setFloatUniform("in_fadeFill", 1 - fadeFill)
-            setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
-        }
-
-    /**
-     * Play time since the start of the effect.
-     */
-    var time: Float = 0.0f
-        set(value) {
-            field = value
-            setFloatUniform("in_time", value)
-        }
-
-    /**
-     * A hex value representing the ripple color, in the format of ARGB
-     */
-    var color: Int = 0xffffff
-        set(value) {
-            field = value
-            setColorUniform("in_color", value)
-        }
-
-    /**
-     * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus
-     * with strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1
-     * it's opaque white and looks the most grainy.
-     */
-    var sparkleStrength: Float = 0.0f
-        set(value) {
-            field = value
-            setFloatUniform("in_sparkle_strength", value)
-        }
-
-    /**
-     * Distortion strength of the ripple. Expected value between[0, 1].
-     */
-    var distortionStrength: Float = 0.0f
-        set(value) {
-            field = value
-            setFloatUniform("in_distort_radial", 75 * progress * value)
-            setFloatUniform("in_distort_xy", 75 * value)
-        }
-
-    var pixelDensity: Float = 1.0f
-        set(value) {
-            field = value
-            setFloatUniform("in_pixelDensity", value)
-        }
-
-    /**
-     * True if the ripple should stayed filled in as it expands to give a filled-in circle effect.
-     * False for a ring effect.
-     */
-    var rippleFill: Boolean = false
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
deleted file mode 100644
index 6de4648..0000000
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
+++ /dev/null
@@ -1,53 +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.ripple
-
-/** A common utility functions that are used for computing [RippleShader]. */
-class RippleShaderUtilLibrary {
-    //language=AGSL
-    companion object {
-        const val SHADER_LIB = """
-            float triangleNoise(vec2 n) {
-                    n  = fract(n * vec2(5.3987, 5.4421));
-                    n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
-                    float xy = n.x * n.y;
-                    return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
-                }
-                const float PI = 3.1415926535897932384626;
-
-                float sparkles(vec2 uv, float t) {
-                    float n = triangleNoise(uv);
-                    float s = 0.0;
-                    for (float i = 0; i < 4; i += 1) {
-                        float l = i * 0.01;
-                        float h = l + 0.1;
-                        float o = smoothstep(n - l, h, n);
-                        o *= abs(sin(PI * o * (t + 0.55 * i)));
-                        s += o;
-                    }
-                    return s;
-                }
-
-                vec2 distort(vec2 p, float time, float distort_amount_radial,
-                    float distort_amount_xy) {
-                        float angle = atan(p.y, p.x);
-                          return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
-                                    cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
-                             + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
-                                    cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
-            }"""
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
deleted file mode 100644
index 1e51ffa..0000000
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ /dev/null
@@ -1,166 +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.ripple
-
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.ValueAnimator
-import android.content.Context
-import android.content.res.Configuration
-import android.graphics.Canvas
-import android.graphics.Paint
-import android.util.AttributeSet
-import android.view.View
-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.
- *
- * Set up the shader with a desired [RippleShape] using [setupShader], [setMaxSize] and [setCenter],
- * then call [startRipple] to trigger the ripple expansion.
- */
-open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
-
-    private lateinit var rippleShader: RippleShader
-    lateinit var rippleShape: RippleShape
-        private set
-
-    private val ripplePaint = Paint()
-
-    var rippleInProgress: Boolean = false
-    var duration: Long = 1750
-
-    private var maxWidth: Float = 0.0f
-    private var maxHeight: Float = 0.0f
-    fun setMaxSize(maxWidth: Float, maxHeight: Float) {
-        this.maxWidth = maxWidth
-        this.maxHeight = maxHeight
-        rippleShader.setMaxSize(maxWidth, maxHeight)
-    }
-
-    private var centerX: Float = 0.0f
-    private var centerY: Float = 0.0f
-    fun setCenter(x: Float, y: Float) {
-        this.centerX = x
-        this.centerY = y
-        rippleShader.setCenter(x, y)
-    }
-
-    override fun onConfigurationChanged(newConfig: Configuration?) {
-        rippleShader.pixelDensity = resources.displayMetrics.density
-        super.onConfigurationChanged(newConfig)
-    }
-
-    override fun onAttachedToWindow() {
-        rippleShader.pixelDensity = resources.displayMetrics.density
-        super.onAttachedToWindow()
-    }
-
-    /** Initializes the shader. Must be called before [startRipple]. */
-    fun setupShader(rippleShape: RippleShape = RippleShape.CIRCLE) {
-        this.rippleShape = rippleShape
-        rippleShader = RippleShader(rippleShape)
-
-        rippleShader.color = RIPPLE_DEFAULT_COLOR
-        rippleShader.progress = 0f
-        rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
-        rippleShader.pixelDensity = resources.displayMetrics.density
-
-        ripplePaint.shader = rippleShader
-    }
-
-    @JvmOverloads
-    fun startRipple(onAnimationEnd: Runnable? = null) {
-        if (rippleInProgress) {
-            return // Ignore if ripple effect is already playing
-        }
-        val animator = ValueAnimator.ofFloat(0f, 1f)
-        animator.duration = duration
-        animator.addUpdateListener { updateListener ->
-            val now = updateListener.currentPlayTime
-            val progress = updateListener.animatedValue as Float
-            rippleShader.progress = progress
-            rippleShader.distortionStrength = 1 - progress
-            rippleShader.time = now.toFloat()
-            invalidate()
-        }
-        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) {
-        rippleShader.color = ColorUtils.setAlphaComponent(color, alpha)
-    }
-
-    /**
-     * Set whether the ripple should remain filled as the ripple expands.
-     *
-     * See [RippleShader.rippleFill].
-     */
-    fun setRippleFill(rippleFill: Boolean) {
-        rippleShader.rippleFill = rippleFill
-    }
-
-    /**
-     * Set the intensity of the sparkles.
-     */
-    fun setSparkleStrength(strength: Float) {
-        rippleShader.sparkleStrength = strength
-    }
-
-    override fun onDraw(canvas: Canvas?) {
-        if (canvas == null || !canvas.isHardwareAccelerated) {
-            // Drawing with the ripple shader requires hardware acceleration, so skip
-            // if it's unsupported.
-            return
-        }
-        // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the
-        // active effect area. Values here should be kept in sync with the animation implementation
-        // in the ripple shader.
-        if (rippleShape == RippleShape.CIRCLE) {
-            val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
-                    (1 - rippleShader.progress)) * maxWidth
-            canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
-        } else {
-            val maskWidth = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
-                    (1 - rippleShader.progress)) * maxWidth * 2
-            val maskHeight = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
-                    (1 - rippleShader.progress)) * maxHeight * 2
-            canvas.drawRect(
-                    /* left= */ centerX - maskWidth,
-                    /* top= */ centerY - maskHeight,
-                    /* right= */ centerX + maskWidth,
-                    /* bottom= */ centerY + maskHeight,
-                    ripplePaint)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
deleted file mode 100644
index 5e256c6..0000000
--- a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
+++ /dev/null
@@ -1,112 +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.ripple
-
-/** Library class that contains 2D signed distance functions. */
-class SdfShaderLibrary {
-    //language=AGSL
-    companion object {
-        const val CIRCLE_SDF = """
-            float sdCircle(vec2 p, float r) {
-                return (length(p)-r) / r;
-            }
-
-            float circleRing(vec2 p, float radius) {
-                float thicknessHalf = radius * 0.25;
-
-                float outerCircle = sdCircle(p, radius + thicknessHalf);
-                float innerCircle = sdCircle(p, radius);
-
-                return subtract(outerCircle, innerCircle);
-            }
-        """
-
-        const val ROUNDED_BOX_SDF = """
-            float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) {
-                size *= 0.5;
-                cornerRadius *= 0.5;
-                vec2 d = abs(p)-size+cornerRadius;
-
-                float outside = length(max(d, 0.0));
-                float inside = min(max(d.x, d.y), 0.0);
-
-                return (outside+inside-cornerRadius)/size.y;
-            }
-
-            float roundedBoxRing(vec2 p, vec2 size, float cornerRadius,
-                float borderThickness) {
-                float outerRoundBox = sdRoundedBox(p, size, cornerRadius);
-                float innerRoundBox = sdRoundedBox(p, size - vec2(borderThickness),
-                    cornerRadius - borderThickness);
-                return subtract(outerRoundBox, innerRoundBox);
-            }
-        """
-
-        // Used non-trigonometry parametrization and Halley's method (iterative) for root finding.
-        // This is more expensive than the regular circle SDF, recommend to use the circle SDF if
-        // possible.
-        const val ELLIPSE_SDF = """float sdEllipse(vec2 p, vec2 wh) {
-            wh *= 0.5;
-
-            // symmetry
-            (wh.x > wh.y) ? wh = wh.yx, p = abs(p.yx) : p = abs(p);
-
-            vec2 u = wh*p, v = wh*wh;
-
-            float U1 = u.y/2.0;  float U5 = 4.0*U1;
-            float U2 = v.y-v.x;  float U6 = 6.0*U1;
-            float U3 = u.x-U2;   float U7 = 3.0*U3;
-            float U4 = u.x+U2;
-
-            float t = 0.5;
-            for (int i = 0; i < 3; i ++) {
-                float F1 = t*(t*t*(U1*t+U3)+U4)-U1;
-                float F2 = t*t*(U5*t+U7)+U4;
-                float F3 = t*(U6*t+U7);
-
-                t += (F1*F2)/(F1*F3-F2*F2);
-            }
-
-            t = clamp(t, 0.0, 1.0);
-
-            float d = distance(p, wh*vec2(1.0-t*t,2.0*t)/(t*t+1.0));
-            d /= wh.y;
-
-            return (dot(p/wh,p/wh)>1.0) ? d : -d;
-        }
-
-        float ellipseRing(vec2 p, vec2 wh) {
-            vec2 thicknessHalf = wh * 0.25;
-
-            float outerEllipse = sdEllipse(p, wh + thicknessHalf);
-            float innerEllipse = sdEllipse(p, wh);
-
-            return subtract(outerEllipse, innerEllipse);
-        }
-        """
-
-        const val SHADER_SDF_OPERATION_LIB = """
-            float soften(float d, float blur) {
-                float blurHalf = blur * 0.5;
-                return smoothstep(-blurHalf, blurHalf, d);
-            }
-
-            float subtract(float outer, float inner) {
-                return max(outer, -inner);
-            }
-        """
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
new file mode 100644
index 0000000..db0052a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
@@ -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.systemui.screenrecord
+
+import android.content.Context
+import android.os.Bundle
+import android.view.Gravity
+import android.view.View
+import android.view.ViewStub
+import android.view.WindowManager
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.ImageView
+import android.widget.Spinner
+import android.widget.TextView
+import androidx.annotation.ColorRes
+import androidx.annotation.DrawableRes
+import androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/** Base permission dialog for screen share and recording */
+open class BaseScreenSharePermissionDialog(
+    context: Context?,
+    private val screenShareOptions: List<ScreenShareOption>,
+    private val appName: String?,
+    @DrawableRes private val dialogIconDrawable: Int? = null,
+    @ColorRes private val dialogIconTint: Int? = null
+) : SystemUIDialog(context), AdapterView.OnItemSelectedListener {
+    private lateinit var dialogTitle: TextView
+    private lateinit var startButton: TextView
+    private lateinit var warning: TextView
+    private lateinit var screenShareModeSpinner: Spinner
+    var selectedScreenShareOption: ScreenShareOption = screenShareOptions.first()
+
+    public override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        window.apply {
+            addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+            setGravity(Gravity.CENTER)
+        }
+        setContentView(R.layout.screen_share_dialog)
+        dialogTitle = findViewById(R.id.screen_share_dialog_title)
+        warning = findViewById(R.id.text_warning)
+        startButton = findViewById(R.id.button_start)
+        findViewById<TextView>(R.id.button_cancel).setOnClickListener { dismiss() }
+        updateIcon()
+        initScreenShareOptions()
+        createOptionsView(getOptionsViewLayoutId())
+    }
+
+    private fun updateIcon() {
+        val icon = findViewById<ImageView>(R.id.screen_share_dialog_icon)
+        if (dialogIconTint != null) {
+            icon.setColorFilter(context.getColor(dialogIconTint))
+        }
+        if (dialogIconDrawable != null) {
+            icon.setImageDrawable(context.getDrawable(dialogIconDrawable))
+        }
+    }
+
+    protected fun initScreenShareOptions() {
+        selectedScreenShareOption = screenShareOptions.first()
+        warning.text = warningText
+        initScreenShareSpinner()
+    }
+
+    private val warningText: String
+        get() = context.getString(selectedScreenShareOption.warningText, appName)
+
+    private fun initScreenShareSpinner() {
+        val options = screenShareOptions.map { context.getString(it.spinnerText) }.toTypedArray()
+        val adapter =
+            ArrayAdapter(
+                context.applicationContext,
+                R.layout.screen_share_dialog_spinner_text,
+                options
+            )
+        adapter.setDropDownViewResource(R.layout.screen_share_dialog_spinner_item_text)
+        screenShareModeSpinner = findViewById(R.id.screen_share_mode_spinner)
+        screenShareModeSpinner.adapter = adapter
+        screenShareModeSpinner.onItemSelectedListener = this
+    }
+
+    override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) {
+        selectedScreenShareOption = screenShareOptions[pos]
+        warning.text = warningText
+    }
+
+    override fun onNothingSelected(parent: AdapterView<*>?) {}
+
+    /** Protected methods for the text updates & functionality */
+    protected fun setDialogTitle(@StringRes stringId: Int) {
+        val title = context.getString(stringId, appName)
+        dialogTitle.text = title
+    }
+
+    protected fun setStartButtonText(@StringRes stringId: Int) {
+        startButton.setText(stringId)
+    }
+
+    protected fun setStartButtonOnClickListener(listener: View.OnClickListener?) {
+        startButton.setOnClickListener(listener)
+    }
+
+    // Create additional options that is shown under the share mode spinner
+    // Eg. the audio and tap toggles in SysUI Recorder
+    @LayoutRes protected open fun getOptionsViewLayoutId(): Int? = null
+
+    private fun createOptionsView(@LayoutRes layoutId: Int?) {
+        if (layoutId == null) return
+        val stub = findViewById<View>(R.id.options_stub) as ViewStub
+        stub.layoutResource = layoutId
+        stub.inflate()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
new file mode 100644
index 0000000..c5a82ce1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
@@ -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.
+ */
+package com.android.systemui.screenrecord
+
+import android.content.Context
+import android.os.Bundle
+import com.android.systemui.R
+
+/** Dialog to select screen recording options */
+class MediaProjectionPermissionDialog(
+    context: Context?,
+    private val onStartRecordingClicked: Runnable,
+    private val appName: String?
+) : BaseScreenSharePermissionDialog(context, createOptionList(appName), appName) {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        if (appName == null) {
+            setDialogTitle(R.string.media_projection_permission_dialog_system_service_title)
+        } else {
+            setDialogTitle(R.string.media_projection_permission_dialog_title)
+        }
+        setStartButtonText(R.string.media_projection_permission_dialog_continue)
+        setStartButtonOnClickListener {
+            // Note that it is important to run this callback before dismissing, so that the
+            // callback can disable the dialog exit animation if it wants to.
+            onStartRecordingClicked.run()
+            dismiss()
+        }
+    }
+
+    companion object {
+        private fun createOptionList(appName: String?): List<ScreenShareOption> {
+            val singleAppWarningText =
+                if (appName == null) {
+                    R.string.media_projection_permission_dialog_system_service_warning_single_app
+                } else {
+                    R.string.media_projection_permission_dialog_warning_single_app
+                }
+            val entireScreenWarningText =
+                if (appName == null) {
+                    R.string.media_projection_permission_dialog_system_service_warning_entire_screen
+                } else {
+                    R.string.media_projection_permission_dialog_warning_entire_screen
+                }
+
+            return listOf(
+                ScreenShareOption(
+                    mode = ENTIRE_SCREEN,
+                    spinnerText = R.string.media_projection_permission_dialog_option_entire_screen,
+                    warningText = entireScreenWarningText
+                ),
+                ScreenShareOption(
+                    mode = SINGLE_APP,
+                    spinnerText = R.string.media_projection_permission_dialog_option_single_app,
+                    warningText = singleAppWarningText
+                )
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 1083f22..b8684ee 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.screenrecord;
 
+import android.app.Dialog;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -32,12 +33,16 @@
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 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.plugins.ActivityStarter;
 import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.CallbackController;
 
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -53,8 +58,10 @@
     private boolean mIsRecording;
     private PendingIntent mStopIntent;
     private CountDownTimer mCountDownTimer = null;
-    private BroadcastDispatcher mBroadcastDispatcher;
-    private UserContextProvider mUserContextProvider;
+    private final Executor mMainExecutor;
+    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final UserContextProvider mUserContextProvider;
+    private final UserTracker mUserTracker;
 
     protected static final String INTENT_UPDATE_STATE =
             "com.android.systemui.screenrecord.UPDATE_STATE";
@@ -64,12 +71,13 @@
             new CopyOnWriteArrayList<>();
 
     @VisibleForTesting
-    protected final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            stopRecording();
-        }
-    };
+    final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    stopRecording();
+                }
+            };
 
     @VisibleForTesting
     protected final BroadcastReceiver mStateChangeReceiver = new BroadcastReceiver() {
@@ -90,18 +98,26 @@
      * Create a new RecordingController
      */
     @Inject
-    public RecordingController(BroadcastDispatcher broadcastDispatcher,
-            UserContextProvider userContextProvider) {
+    public RecordingController(@Main Executor mainExecutor,
+            BroadcastDispatcher broadcastDispatcher,
+            UserContextProvider userContextProvider,
+            UserTracker userTracker) {
+        mMainExecutor = mainExecutor;
         mBroadcastDispatcher = broadcastDispatcher;
         mUserContextProvider = userContextProvider;
+        mUserTracker = userTracker;
     }
 
     /** Create a dialog to show screen recording options to the user. */
-    public ScreenRecordDialog createScreenRecordDialog(Context context, FeatureFlags flags,
-            DialogLaunchAnimator dialogLaunchAnimator, ActivityStarter activityStarter,
-            @Nullable Runnable onStartRecordingClicked) {
-        return new ScreenRecordDialog(context, this, activityStarter, mUserContextProvider,
-                flags, dialogLaunchAnimator, onStartRecordingClicked);
+    public Dialog createScreenRecordDialog(Context context, FeatureFlags flags,
+                                           DialogLaunchAnimator dialogLaunchAnimator,
+                                           ActivityStarter activityStarter,
+                                           @Nullable Runnable onStartRecordingClicked) {
+        return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
+                ? new ScreenRecordPermissionDialog(context, this, activityStarter,
+                        dialogLaunchAnimator, mUserContextProvider, onStartRecordingClicked)
+                : new ScreenRecordDialog(context, this, activityStarter,
+                mUserContextProvider, flags, dialogLaunchAnimator, onStartRecordingClicked);
     }
 
     /**
@@ -133,9 +149,7 @@
                 }
                 try {
                     startIntent.send();
-                    IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-                    mBroadcastDispatcher.registerReceiver(mUserChangeReceiver, userFilter, null,
-                            UserHandle.ALL);
+                    mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
 
                     IntentFilter stateFilter = new IntentFilter(INTENT_UPDATE_STATE);
                     mBroadcastDispatcher.registerReceiver(mStateChangeReceiver, stateFilter, null,
@@ -205,7 +219,7 @@
     public synchronized void updateState(boolean isRecording) {
         if (!isRecording && mIsRecording) {
             // Unregister receivers if we have stopped recording
-            mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
+            mUserTracker.removeCallback(mUserChangedCallback);
             mBroadcastDispatcher.unregisterReceiver(mStateChangeReceiver);
         }
         mIsRecording = isRecording;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
new file mode 100644
index 0000000..44b18ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.screenrecord
+
+import android.app.Activity
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.os.ResultReceiver
+import android.view.View
+import android.view.View.GONE
+import android.view.View.VISIBLE
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Spinner
+import android.widget.Switch
+import androidx.annotation.LayoutRes
+import com.android.systemui.R
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.media.MediaProjectionAppSelectorActivity
+import com.android.systemui.media.MediaProjectionCaptureTarget
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserContextProvider
+
+/** Dialog to select screen recording options */
+class ScreenRecordPermissionDialog(
+    context: Context?,
+    private val controller: RecordingController,
+    private val activityStarter: ActivityStarter,
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val userContextProvider: UserContextProvider,
+    private val onStartRecordingClicked: Runnable?
+) :
+    BaseScreenSharePermissionDialog(
+        context,
+        createOptionList(),
+        null,
+        R.drawable.ic_screenrecord,
+        R.color.screenrecord_icon_color
+    ) {
+    private lateinit var tapsSwitch: Switch
+    private lateinit var tapsView: View
+    private lateinit var audioSwitch: Switch
+    private lateinit var options: Spinner
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setDialogTitle(R.string.screenrecord_start_label)
+        setStartButtonText(R.string.screenrecord_start_recording)
+        setStartButtonOnClickListener { v: View? ->
+            onStartRecordingClicked?.run()
+            if (selectedScreenShareOption.mode == ENTIRE_SCREEN) {
+                requestScreenCapture(/* captureTarget= */ null)
+            }
+            if (selectedScreenShareOption.mode == SINGLE_APP) {
+                val intent = Intent(context, MediaProjectionAppSelectorActivity::class.java)
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+                // We can't start activity for result here so we use result receiver to get
+                // the selected target to capture
+                intent.putExtra(
+                    MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
+                    CaptureTargetResultReceiver()
+                )
+                val animationController = dialogLaunchAnimator.createActivityLaunchController(v!!)
+                if (animationController == null) {
+                    dismiss()
+                }
+                activityStarter.startActivity(intent, /* dismissShade= */ true, animationController)
+            }
+            dismiss()
+        }
+        initRecordOptionsView()
+    }
+
+    @LayoutRes override fun getOptionsViewLayoutId(): Int = R.layout.screen_record_options
+
+    private fun initRecordOptionsView() {
+        audioSwitch = findViewById(R.id.screenrecord_audio_switch)
+        tapsSwitch = findViewById(R.id.screenrecord_taps_switch)
+        tapsView = findViewById(R.id.show_taps)
+        updateTapsViewVisibility()
+        options = findViewById(R.id.screen_recording_options)
+        val a: ArrayAdapter<*> =
+            ScreenRecordingAdapter(context, android.R.layout.simple_spinner_dropdown_item, MODES)
+        a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+        options.adapter = a
+        options.setOnItemClickListenerInt { _: AdapterView<*>?, _: View?, _: Int, _: Long ->
+            audioSwitch.isChecked = true
+        }
+    }
+
+    override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) {
+        super.onItemSelected(adapterView, view, pos, id)
+        updateTapsViewVisibility()
+    }
+
+    private fun updateTapsViewVisibility() {
+        tapsView.visibility = if (selectedScreenShareOption.mode == SINGLE_APP) GONE else VISIBLE
+    }
+
+    /**
+     * Starts screen capture after some countdown
+     * @param captureTarget target to capture (could be e.g. a task) or null to record the whole
+     * screen
+     */
+    private fun requestScreenCapture(captureTarget: MediaProjectionCaptureTarget?) {
+        val userContext = userContextProvider.userContext
+        val showTaps = selectedScreenShareOption.mode != SINGLE_APP && tapsSwitch.isChecked
+        val audioMode =
+            if (audioSwitch.isChecked) options.selectedItem as ScreenRecordingAudioSource
+            else ScreenRecordingAudioSource.NONE
+        val startIntent =
+            PendingIntent.getForegroundService(
+                userContext,
+                RecordingService.REQUEST_CODE,
+                RecordingService.getStartIntent(
+                    userContext,
+                    Activity.RESULT_OK,
+                    audioMode.ordinal,
+                    showTaps,
+                    captureTarget
+                ),
+                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+            )
+        val stopIntent =
+            PendingIntent.getService(
+                userContext,
+                RecordingService.REQUEST_CODE,
+                RecordingService.getStopIntent(userContext),
+                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+            )
+        controller.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent)
+    }
+
+    private inner class CaptureTargetResultReceiver() :
+        ResultReceiver(Handler(Looper.getMainLooper())) {
+        override fun onReceiveResult(resultCode: Int, resultData: Bundle) {
+            if (resultCode == Activity.RESULT_OK) {
+                val captureTarget =
+                    resultData.getParcelable(
+                        MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET,
+                        MediaProjectionCaptureTarget::class.java
+                    )
+
+                // Start recording of the selected target
+                requestScreenCapture(captureTarget)
+            }
+        }
+    }
+
+    companion object {
+        private val MODES =
+            listOf(
+                ScreenRecordingAudioSource.INTERNAL,
+                ScreenRecordingAudioSource.MIC,
+                ScreenRecordingAudioSource.MIC_AND_INTERNAL
+            )
+        private const val DELAY_MS: Long = 3000
+        private const val INTERVAL_MS: Long = 1000
+        private fun createOptionList(): List<ScreenShareOption> {
+            return listOf(
+                ScreenShareOption(
+                    ENTIRE_SCREEN,
+                    R.string.screenrecord_option_entire_screen,
+                    R.string.screenrecord_warning_entire_screen
+                ),
+                ScreenShareOption(
+                    SINGLE_APP,
+                    R.string.screenrecord_option_single_app,
+                    R.string.screenrecord_warning_single_app
+                )
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt
new file mode 100644
index 0000000..3d39fd8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt
@@ -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.
+ */
+package com.android.systemui.screenrecord
+
+import androidx.annotation.IntDef
+import androidx.annotation.StringRes
+import kotlin.annotation.Retention
+
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(ENTIRE_SCREEN, SINGLE_APP)
+annotation class ScreenShareMode
+
+const val ENTIRE_SCREEN = 0
+const val SINGLE_APP = 1
+
+class ScreenShareOption(
+    @ScreenShareMode val mode: Int,
+    @StringRes val spinnerText: Int,
+    @StringRes val warningText: Int
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 5961635..01e32b7a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -32,7 +32,7 @@
 import com.android.internal.infra.ServiceConnector
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import javax.inject.Inject
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineDispatcher
@@ -45,7 +45,7 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Main private val mainDispatcher: CoroutineDispatcher,
     private val context: Context,
 ) {
     /**
@@ -70,23 +70,21 @@
         userId: Int,
         overrideTransition: Boolean,
     ) {
-        withContext(bgDispatcher) {
-            dismissKeyguard()
+        dismissKeyguard()
 
-            if (userId == UserHandle.myUserId()) {
-                context.startActivity(intent, bundle)
-            } else {
-                launchCrossProfileIntent(userId, intent, bundle)
-            }
+        if (userId == UserHandle.myUserId()) {
+            withContext(mainDispatcher) { context.startActivity(intent, bundle) }
+        } else {
+            launchCrossProfileIntent(userId, intent, bundle)
+        }
 
-            if (overrideTransition) {
-                val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
-                try {
-                    WindowManagerGlobal.getWindowManagerService()
-                        .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY)
-                } catch (e: Exception) {
-                    Log.e(TAG, "Error overriding screenshot app transition", e)
-                }
+        if (overrideTransition) {
+            val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
+            try {
+                WindowManagerGlobal.getWindowManagerService()
+                    .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY)
+            } catch (e: Exception) {
+                Log.e(TAG, "Error overriding screenshot app transition", e)
             }
         }
     }
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/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 8bf956b..5450db9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -46,6 +46,8 @@
 import com.android.systemui.R;
 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.screenshot.ScrollCaptureController.LongScreenshot;
 
 import com.google.common.util.concurrent.ListenableFuture;
@@ -67,6 +69,7 @@
     private static final String TAG = LogConfig.logTag(LongScreenshotActivity.class);
 
     public static final String EXTRA_CAPTURE_RESPONSE = "capture-response";
+    public static final String EXTRA_SCREENSHOT_USER_HANDLE = "screenshot-userhandle";
     private static final String KEY_SAVED_IMAGE_PATH = "saved-image-path";
 
     private final UiEventLogger mUiEventLogger;
@@ -74,6 +77,8 @@
     private final Executor mBackgroundExecutor;
     private final ImageExporter mImageExporter;
     private final LongScreenshotData mLongScreenshotHolder;
+    private final ActionIntentExecutor mActionExecutor;
+    private final FeatureFlags mFeatureFlags;
 
     private ImageView mPreview;
     private ImageView mTransitionView;
@@ -85,6 +90,7 @@
     private CropView mCropView;
     private MagnifierView mMagnifierView;
     private ScrollCaptureResponse mScrollCaptureResponse;
+    private UserHandle mScreenshotUserHandle;
     private File mSavedImagePath;
 
     private ListenableFuture<File> mCacheSaveFuture;
@@ -103,12 +109,15 @@
     @Inject
     public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter,
             @Main Executor mainExecutor, @Background Executor bgExecutor,
-            LongScreenshotData longScreenshotHolder) {
+            LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor,
+            FeatureFlags featureFlags) {
         mUiEventLogger = uiEventLogger;
         mUiExecutor = mainExecutor;
         mBackgroundExecutor = bgExecutor;
         mImageExporter = imageExporter;
         mLongScreenshotHolder = longScreenshotHolder;
+        mActionExecutor = actionExecutor;
+        mFeatureFlags = featureFlags;
     }
 
 
@@ -139,6 +148,11 @@
 
         Intent intent = getIntent();
         mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE);
+        mScreenshotUserHandle = intent.getParcelableExtra(EXTRA_SCREENSHOT_USER_HANDLE,
+                UserHandle.class);
+        if (mScreenshotUserHandle == null) {
+            mScreenshotUserHandle = Process.myUserHandle();
+        }
 
         if (savedInstanceState != null) {
             String savedImagePath = savedInstanceState.getString(KEY_SAVED_IMAGE_PATH);
@@ -318,36 +332,51 @@
     }
 
     private void doEdit(Uri uri) {
-        String editorPackage = getString(R.string.config_screenshotEditor);
-        Intent intent = new Intent(Intent.ACTION_EDIT);
-        if (!TextUtils.isEmpty(editorPackage)) {
-            intent.setComponent(ComponentName.unflattenFromString(editorPackage));
-        }
-        intent.setDataAndType(uri, "image/png");
-        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
-                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY) && mScreenshotUserHandle
+                != Process.myUserHandle()) {
+            // TODO: Fix transition for work profile. Omitting it in the meantime.
+            mActionExecutor.launchIntentAsync(
+                    ActionIntentCreator.INSTANCE.createEditIntent(uri, this),
+                    null,
+                    mScreenshotUserHandle.getIdentifier(), false);
+        } else {
+            String editorPackage = getString(R.string.config_screenshotEditor);
+            Intent intent = new Intent(Intent.ACTION_EDIT);
+            if (!TextUtils.isEmpty(editorPackage)) {
+                intent.setComponent(ComponentName.unflattenFromString(editorPackage));
+            }
+            intent.setDataAndType(uri, "image/png");
+            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
 
-        mTransitionView.setImageBitmap(mOutputBitmap);
-        mTransitionView.setVisibility(View.VISIBLE);
-        mTransitionView.setTransitionName(
-                ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
-        // TODO: listen for transition completing instead of finishing onStop
-        mTransitionStarted = true;
-        startActivity(intent,
-                ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
-                        ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle());
+            mTransitionView.setImageBitmap(mOutputBitmap);
+            mTransitionView.setVisibility(View.VISIBLE);
+            mTransitionView.setTransitionName(
+                    ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
+            // TODO: listen for transition completing instead of finishing onStop
+            mTransitionStarted = true;
+            startActivity(intent,
+                    ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
+                            ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle());
+        }
     }
 
     private void doShare(Uri uri) {
-        Intent intent = new Intent(Intent.ACTION_SEND);
-        intent.setType("image/png");
-        intent.putExtra(Intent.EXTRA_STREAM, uri);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
-                | Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        Intent sharingChooserIntent = Intent.createChooser(intent, null)
-                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+            Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri, null);
+            mActionExecutor.launchIntentAsync(shareIntent, null,
+                    mScreenshotUserHandle.getIdentifier(), false);
+        } else {
+            Intent intent = new Intent(Intent.ACTION_SEND);
+            intent.setType("image/png");
+            intent.putExtra(Intent.EXTRA_STREAM, uri);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
+                    | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            Intent sharingChooserIntent = Intent.createChooser(intent, null)
+                    .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
-        startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
+            startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
+        }
     }
 
     private void onClicked(View v) {
@@ -389,8 +418,8 @@
         mOutputBitmap = renderBitmap(drawable, bounds);
         ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
                 mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now(),
-                // TODO: Owner must match the owner of the captured window.
-                Process.myUserHandle());
+                mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)
+                        ? mScreenshotUserHandle : Process.myUserHandle());
         exportFuture.addListener(() -> onExportCompleted(action, exportFuture), mUiExecutor);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 7143ba2..b4934cf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -38,6 +38,7 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -81,7 +82,6 @@
 
     private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
     private String mScreenshotId;
-    private final boolean mSmartActionsEnabled;
     private final Random mRandom = new Random();
     private final Supplier<ActionTransition> mSharedElementTransition;
     private final ImageExporter mImageExporter;
@@ -109,8 +109,6 @@
         mParams = data;
 
         // Initialize screenshot notification smart actions provider.
-        mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true);
         mSmartActionsProvider = screenshotNotificationSmartActionsProvider;
     }
 
@@ -131,8 +129,16 @@
 
         Bitmap image = mParams.image;
         mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, requestId);
+
+        boolean savingToOtherUser = mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)
+                && (user != Process.myUserHandle());
+        // Smart actions don't yet work for cross-user saves.
+        boolean smartActionsEnabled = !savingToOtherUser
+                && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS,
+                true);
         try {
-            if (mSmartActionsEnabled && mParams.mQuickShareActionsReadyListener != null) {
+            if (smartActionsEnabled && mParams.mQuickShareActionsReadyListener != null) {
                 // Since Quick Share target recommendation does not rely on image URL, it is
                 // queried and surfaced before image compress/export. Action intent would not be
                 // used, because it does not contain image URL.
@@ -150,10 +156,9 @@
             CompletableFuture<List<Notification.Action>> smartActionsFuture =
                     mScreenshotSmartActions.getSmartActionsFuture(
                             mScreenshotId, uri, image, mSmartActionsProvider, REGULAR_SMART_ACTIONS,
-                            mSmartActionsEnabled, user);
-
+                            smartActionsEnabled, user);
             List<Notification.Action> smartActions = new ArrayList<>();
-            if (mSmartActionsEnabled) {
+            if (smartActionsEnabled) {
                 int timeoutMs = DeviceConfig.getInt(
                         DeviceConfig.NAMESPACE_SYSTEMUI,
                         SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
@@ -168,9 +173,12 @@
             mImageData.uri = uri;
             mImageData.owner = user;
             mImageData.smartActions = smartActions;
-            mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri);
-            mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri);
-            mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
+            mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri,
+                    smartActionsEnabled);
+            mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri,
+                    smartActionsEnabled);
+            mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri,
+                    smartActionsEnabled);
             mImageData.quickShareAction = createQuickShareAction(mContext,
                     mQuickShareData.quickShareAction, uri);
             mImageData.subject = getSubjectString();
@@ -228,7 +236,8 @@
      * Assumes that the action intent is sent immediately after being supplied.
      */
     @VisibleForTesting
-    Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri) {
+    Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri,
+            boolean smartActionsEnabled) {
         return () -> {
             ActionTransition transition = mSharedElementTransition.get();
 
@@ -274,7 +283,7 @@
                             .putExtra(ScreenshotController.EXTRA_DISALLOW_ENTER_PIP, true)
                             .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
                             .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
-                                    mSmartActionsEnabled)
+                                    smartActionsEnabled)
                             .setAction(Intent.ACTION_SEND)
                             .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
@@ -290,7 +299,8 @@
     }
 
     @VisibleForTesting
-    Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri) {
+    Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri,
+            boolean smartActionsEnabled) {
         return () -> {
             ActionTransition transition = mSharedElementTransition.get();
             // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
@@ -323,7 +333,7 @@
                             .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
                             .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
                             .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
-                                    mSmartActionsEnabled)
+                                    smartActionsEnabled)
                             .putExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, true)
                             .setAction(Intent.ACTION_EDIT)
                             .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
@@ -339,7 +349,8 @@
     }
 
     @VisibleForTesting
-    Notification.Action createDeleteAction(Context context, Resources r, Uri uri) {
+    Notification.Action createDeleteAction(Context context, Resources r, Uri uri,
+            boolean smartActionsEnabled) {
         // Make sure pending intents for the system user are still unique across users
         // by setting the (otherwise unused) request code to the current user id.
         int requestCode = mContext.getUserId();
@@ -350,7 +361,7 @@
                         .putExtra(ScreenshotController.SCREENSHOT_URI_ID, uri.toString())
                         .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
                         .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
-                                mSmartActionsEnabled)
+                                smartActionsEnabled)
                         .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
                 PendingIntent.FLAG_CANCEL_CURRENT
                         | PendingIntent.FLAG_ONE_SHOT
@@ -391,7 +402,7 @@
             Intent intent = new Intent(context, SmartActionsReceiver.class)
                     .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, action.actionIntent)
                     .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-            addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
+            addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
             PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
                     mRandom.nextInt(),
                     intent,
@@ -445,7 +456,9 @@
         Intent intent = new Intent(context, SmartActionsReceiver.class)
                 .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, updatedPendingIntent)
                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
+        // We only query for quick share actions when smart actions are enabled, so we can assert
+        // that it's true here.
+        addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
         PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
                 mRandom.nextInt(),
                 intent,
@@ -464,7 +477,7 @@
                 mScreenshotSmartActions.getSmartActionsFuture(
                         mScreenshotId, null, image, mSmartActionsProvider,
                         QUICK_SHARE_ACTION,
-                        mSmartActionsEnabled, user);
+                        true /* smartActionsEnabled */, user);
         int timeoutMs = DeviceConfig.getInt(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_QUICK_SHARE_ACTIONS_TIMEOUT_MS,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index d524a35..a6447a5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -63,6 +63,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -83,6 +84,8 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 import android.window.WindowContext;
 
 import androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -276,6 +279,14 @@
             mScreenshotNotificationSmartActionsProvider;
     private final TimeoutHandler mScreenshotHandler;
     private final ActionIntentExecutor mActionExecutor;
+    private final UserManager mUserManager;
+
+    private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
+        if (DEBUG_INPUT) {
+            Log.d(TAG, "Predictive Back callback dispatched");
+        }
+        respondToBack();
+    };
 
     private ScreenshotView mScreenshotView;
     private Bitmap mScreenBitmap;
@@ -314,7 +325,8 @@
             TimeoutHandler timeoutHandler,
             BroadcastSender broadcastSender,
             ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
-            ActionIntentExecutor actionExecutor
+            ActionIntentExecutor actionExecutor,
+            UserManager userManager
     ) {
         mScreenshotSmartActions = screenshotSmartActions;
         mNotificationsController = screenshotNotificationsController;
@@ -345,6 +357,7 @@
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mFlags = flags;
         mActionExecutor = actionExecutor;
+        mUserManager = userManager;
 
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
 
@@ -461,6 +474,10 @@
         }
     }
 
+    private void respondToBack() {
+        dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+    }
+
     /**
      * Update resources on configuration change. Reinflate for theme/color changes.
      */
@@ -472,6 +489,26 @@
         // Inflate the screenshot layout
         mScreenshotView = (ScreenshotView)
                 LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
+        mScreenshotView.addOnAttachStateChangeListener(
+                new View.OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(@NonNull View v) {
+                        if (DEBUG_INPUT) {
+                            Log.d(TAG, "Registering Predictive Back callback");
+                        }
+                        mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
+                    }
+
+                    @Override
+                    public void onViewDetachedFromWindow(@NonNull View v) {
+                        if (DEBUG_INPUT) {
+                            Log.d(TAG, "Unregistering Predictive Back callback");
+                        }
+                        mScreenshotView.findOnBackInvokedDispatcher()
+                                .unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+                    }
+                });
         mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() {
             @Override
             public void onUserInteraction() {
@@ -499,7 +536,7 @@
                 if (DEBUG_INPUT) {
                     Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK");
                 }
-                dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+                respondToBack();
                 return true;
             }
             return false;
@@ -542,9 +579,16 @@
 
     private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
             Insets screenInsets, ComponentName topComponent, boolean showFlash, UserHandle owner) {
-        withWindowAttached(() ->
+        withWindowAttached(() -> {
+            if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+                    && mUserManager.isManagedProfile(owner.getIdentifier())) {
+                mScreenshotView.announceForAccessibility(mContext.getResources().getString(
+                        R.string.screenshot_saving_work_profile_title));
+            } else {
                 mScreenshotView.announceForAccessibility(
-                        mContext.getResources().getString(R.string.screenshot_saving_title)));
+                        mContext.getResources().getString(R.string.screenshot_saving_title));
+            }
+        });
 
         mScreenshotView.reset();
 
@@ -587,7 +631,7 @@
         // Wait until this window is attached to request because it is
         // the reference used to locate the target window (below).
         withWindowAttached(() -> {
-            requestScrollCapture();
+            requestScrollCapture(owner);
             mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
                     new ViewRootImpl.ActivityConfigCallback() {
                         @Override
@@ -599,11 +643,11 @@
                                 mScreenshotView.hideScrollChip();
                                 // Delay scroll capture eval a bit to allow the underlying activity
                                 // to set up in the new orientation.
-                                mScreenshotHandler.postDelayed(
-                                        ScreenshotController.this::requestScrollCapture, 150);
+                                mScreenshotHandler.postDelayed(() -> {
+                                    requestScrollCapture(owner);
+                                }, 150);
                                 mScreenshotView.updateInsets(
-                                        mWindowManager.getCurrentWindowMetrics()
-                                                .getWindowInsets());
+                                        mWindowManager.getCurrentWindowMetrics().getWindowInsets());
                                 // Screenshot animation calculations won't be valid anymore,
                                 // so just end
                                 if (mScreenshotAnimation != null
@@ -651,7 +695,7 @@
         mScreenshotHandler.cancelTimeout(); // restarted after animation
     }
 
-    private void requestScrollCapture() {
+    private void requestScrollCapture(UserHandle owner) {
         if (!allowLongScreenshots()) {
             Log.d(TAG, "Long screenshots not supported on this device");
             return;
@@ -664,10 +708,11 @@
                 mScrollCaptureClient.request(DEFAULT_DISPLAY);
         mLastScrollCaptureRequest = future;
         mLastScrollCaptureRequest.addListener(() ->
-                onScrollCaptureResponseReady(future), mMainExecutor);
+                onScrollCaptureResponseReady(future, owner), mMainExecutor);
     }
 
-    private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture) {
+    private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture,
+            UserHandle owner) {
         try {
             if (mLastScrollCaptureResponse != null) {
                 mLastScrollCaptureResponse.close();
@@ -697,7 +742,7 @@
                 mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
                         mScreenshotTakenInPortrait);
                 // delay starting scroll capture to make sure the scrim is up before the app moves
-                mScreenshotView.post(() -> runBatchScrollCapture(response));
+                mScreenshotView.post(() -> runBatchScrollCapture(response, owner));
             });
         } catch (InterruptedException | ExecutionException e) {
             Log.e(TAG, "requestScrollCapture failed", e);
@@ -706,7 +751,7 @@
 
     ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture;
 
-    private void runBatchScrollCapture(ScrollCaptureResponse response) {
+    private void runBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
         // Clear the reference to prevent close() in dismissScreenshot
         mLastScrollCaptureResponse = null;
 
@@ -734,12 +779,18 @@
 
             mLongScreenshotHolder.setLongScreenshot(longScreenshot);
             mLongScreenshotHolder.setTransitionDestinationCallback(
-                    (transitionDestination, onTransitionEnd) ->
+                    (transitionDestination, onTransitionEnd) -> {
                             mScreenshotView.startLongScreenshotTransition(
                                     transitionDestination, onTransitionEnd,
-                                    longScreenshot));
+                                    longScreenshot);
+                        // TODO: Do this via ActionIntentExecutor instead.
+                        mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+                    }
+            );
 
             final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
+            intent.putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE,
+                    owner);
             intent.setFlags(
                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
 
@@ -961,13 +1012,8 @@
 
         if (imageData.uri != null) {
             if (!imageData.owner.equals(Process.myUserHandle())) {
-                // TODO: Handle non-primary user ownership (e.g. Work Profile)
-                // This image is owned by another user. Special treatment will be
-                // required in the UI (badging) as well as sending intents which can
-                // correctly forward those URIs on to be read (actions).
-
-                Log.d(TAG, "*** Screenshot saved to a non-primary user ("
-                        + imageData.owner + ") as " + imageData.uri);
+                Log.d(TAG, "Screenshot saved to user " + imageData.owner + " as "
+                        + imageData.uri);
             }
             mScreenshotHandler.post(() -> {
                 if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
@@ -975,16 +1021,25 @@
                         @Override
                         public void onAnimationEnd(Animator animation) {
                             super.onAnimationEnd(animation);
-                            mScreenshotView.setChipIntents(imageData);
+                            doPostAnimation(imageData);
                         }
                     });
                 } else {
-                    mScreenshotView.setChipIntents(imageData);
+                    doPostAnimation(imageData);
                 }
             });
         }
     }
 
+    private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
+        mScreenshotView.setChipIntents(imageData);
+        if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+                && mUserManager.isManagedProfile(imageData.owner.getIdentifier())) {
+            // TODO: Read app from configuration
+            mScreenshotView.showWorkProfileMessage("Files");
+        }
+    }
+
     /**
      * Sets up the action shade and its entrance animation, once we get the Quick Share action data.
      */
@@ -1039,12 +1094,22 @@
                     R.string.screenshot_failed_to_save_text);
         } else {
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
+            if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+                    && mUserManager.isManagedProfile(imageData.owner.getIdentifier())) {
+                mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0,
+                        mPackageName);
+            }
         }
     }
 
     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/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index 8b5a24c..c891686 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -89,7 +89,9 @@
     @UiEvent(doc = "User has saved a long screenshot to a file")
     SCREENSHOT_LONG_SCREENSHOT_SAVED(910),
     @UiEvent(doc = "User has discarded the result of a long screenshot")
-    SCREENSHOT_LONG_SCREENSHOT_EXIT(911);
+    SCREENSHOT_LONG_SCREENSHOT_EXIT(911),
+    @UiEvent(doc = "A screenshot has been taken and saved to work profile")
+    SCREENSHOT_SAVED_TO_WORK_PROFILE(1240);
 
     private final int mId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index c41e2bc..4cb91e1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -15,12 +15,17 @@
  */
 package com.android.systemui.screenshot
 
-import android.app.Service
 import android.content.Intent
 import android.os.IBinder
 import android.util.Log
+import androidx.lifecycle.LifecycleService
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.phone.CentralSurfaces
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import java.util.Optional
 import javax.inject.Inject
 
@@ -30,7 +35,8 @@
 internal class ScreenshotProxyService @Inject constructor(
     private val mExpansionMgr: ShadeExpansionStateManager,
     private val mCentralSurfacesOptional: Optional<CentralSurfaces>,
-) : Service() {
+    @Main private val mMainDispatcher: CoroutineDispatcher,
+) : LifecycleService() {
 
     private val mBinder: IBinder = object : IScreenshotProxy.Stub() {
         /**
@@ -43,20 +49,28 @@
         }
 
         override fun dismissKeyguard(callback: IOnDoneCallback) {
-            if (mCentralSurfacesOptional.isPresent) {
-                mCentralSurfacesOptional.get().executeRunnableDismissingKeyguard(
-                    Runnable {
-                        callback.onDone(true)
-                    }, null,
-                    true /* dismissShade */, true /* afterKeyguardGone */,
-                    true /* deferred */
-                )
-            } else {
-                callback.onDone(false)
+            lifecycleScope.launch {
+                executeAfterDismissing(callback)
             }
         }
     }
 
+    private suspend fun executeAfterDismissing(callback: IOnDoneCallback) =
+        withContext(mMainDispatcher) {
+            mCentralSurfacesOptional.ifPresentOrElse(
+                    {
+                        it.executeRunnableDismissingKeyguard(
+                                Runnable {
+                                    callback.onDone(true)
+                                }, null,
+                                true /* dismissShade */, true /* afterKeyguardGone */,
+                                true /* deferred */
+                        )
+                    },
+                    { callback.onDone(false) }
+            )
+        }
+
     override fun onBind(intent: Intent): IBinder? {
         Log.d(TAG, "onBind: $intent")
         return mBinder
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 27331ae..fae938d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -80,6 +80,7 @@
 import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import androidx.constraintlayout.widget.ConstraintLayout;
 
@@ -137,6 +138,8 @@
 
     private ImageView mScrollingScrim;
     private DraggableConstraintLayout mScreenshotStatic;
+    private ViewGroup mMessageContainer;
+    private TextView mMessageContent;
     private ImageView mScreenshotPreview;
     private ImageView mScreenshotBadge;
     private View mScreenshotPreviewBorder;
@@ -340,10 +343,26 @@
         }
     }
 
+    /**
+     * Show a notification under the screenshot view indicating that a work profile screenshot has
+     * been taken and which app can be used to view it.
+     *
+     * @param appName The name of the app to use to view screenshots
+     */
+    void showWorkProfileMessage(String appName) {
+        mMessageContent.setText(
+                mContext.getString(R.string.screenshot_work_profile_notification, appName));
+        mMessageContainer.setVisibility(VISIBLE);
+    }
+
     @Override // View
     protected void onFinishInflate() {
         mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
         mScreenshotStatic = requireNonNull(findViewById(R.id.screenshot_static));
+        mMessageContainer =
+                requireNonNull(mScreenshotStatic.findViewById(R.id.screenshot_message_container));
+        mMessageContent =
+                requireNonNull(mMessageContainer.findViewById(R.id.screenshot_message_content));
         mScreenshotPreview = requireNonNull(findViewById(R.id.screenshot_preview));
 
         mScreenshotPreviewBorder = requireNonNull(
@@ -806,12 +825,23 @@
             }
         });
         if (mQuickShareChip != null) {
-            mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,
-                    () -> {
-                        mUiEventLogger.log(
-                                ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED, 0, mPackageName);
-                        animateDismissal();
-                    });
+            if (imageData.quickShareAction != null) {
+                mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,
+                        () -> {
+                            mUiEventLogger.log(
+                                    ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED, 0,
+                                    mPackageName);
+                            animateDismissal();
+                        });
+            } else {
+                // hide chip and unset pending interaction if necessary, since we don't actually
+                // have a useable quick share intent
+                Log.wtf(TAG, "Showed quick share chip, but quick share intent was null");
+                if (mPendingInteraction == PendingInteraction.QUICK_SHARE) {
+                    mPendingInteraction = null;
+                }
+                mQuickShareChip.setVisibility(GONE);
+            }
         }
 
         if (mPendingInteraction != null) {
@@ -879,6 +909,7 @@
 
     void startLongScreenshotTransition(Rect destination, Runnable onTransitionEnd,
             ScrollCaptureController.LongScreenshot longScreenshot) {
+        mPendingSharedTransition = true;
         AnimatorSet animSet = new AnimatorSet();
 
         ValueAnimator scrimAnim = ValueAnimator.ofFloat(0, 1);
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index 2d1d8b7..d33d113 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -29,6 +29,8 @@
 import android.hardware.SensorPrivacyManager.Sources.DIALOG
 import android.os.Bundle
 import android.os.Handler
+import android.window.OnBackInvokedDispatcher
+import androidx.annotation.OpenForTesting
 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION
 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL
 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE
@@ -45,7 +47,8 @@
  *
  * <p>The dialog is started for the user the app is running for which might be a secondary users.
  */
-class SensorUseStartedActivity @Inject constructor(
+@OpenForTesting
+open class SensorUseStartedActivity @Inject constructor(
     private val sensorPrivacyController: IndividualSensorPrivacyController,
     private val keyguardStateController: KeyguardStateController,
     private val keyguardDismissUtil: KeyguardDismissUtil,
@@ -67,9 +70,10 @@
     private lateinit var sensorUsePackageName: String
     private var unsuppressImmediately = false
 
-    private lateinit var sensorPrivacyListener: IndividualSensorPrivacyController.Callback
+    private var sensorPrivacyListener: IndividualSensorPrivacyController.Callback? = null
 
     private var mDialog: AlertDialog? = null
+    private val mBackCallback = this::onBackInvoked
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -84,15 +88,14 @@
 
         if (intent.getBooleanExtra(EXTRA_ALL_SENSORS, false)) {
             sensor = ALL_SENSORS
-            sensorPrivacyListener =
-                    IndividualSensorPrivacyController.Callback { _, _ ->
-                        if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
-                                !sensorPrivacyController.isSensorBlocked(CAMERA)) {
-                            finish()
-                        }
-                    }
-
-            sensorPrivacyController.addCallback(sensorPrivacyListener)
+            val callback = IndividualSensorPrivacyController.Callback { _, _ ->
+                if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
+                        !sensorPrivacyController.isSensorBlocked(CAMERA)) {
+                    finish()
+                }
+            }
+            sensorPrivacyListener = callback
+            sensorPrivacyController.addCallback(callback)
             if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
                     !sensorPrivacyController.isSensorBlocked(CAMERA)) {
                 finish()
@@ -105,14 +108,14 @@
                     return
                 }
             }
-            sensorPrivacyListener =
-                    IndividualSensorPrivacyController.Callback { whichSensor: Int,
-                                                                 isBlocked: Boolean ->
-                        if (whichSensor == sensor && !isBlocked) {
-                            finish()
-                        }
-                    }
-            sensorPrivacyController.addCallback(sensorPrivacyListener)
+            val callback = IndividualSensorPrivacyController.Callback {
+                whichSensor: Int, isBlocked: Boolean ->
+                if (whichSensor == sensor && !isBlocked) {
+                    finish()
+                }
+            }
+            sensorPrivacyListener = callback
+            sensorPrivacyController.addCallback(callback)
 
             if (!sensorPrivacyController.isSensorBlocked(sensor)) {
                 finish()
@@ -122,6 +125,10 @@
 
         mDialog = SensorUseDialog(this, sensor, this, this)
         mDialog!!.show()
+
+        onBackInvokedDispatcher.registerOnBackInvokedCallback(
+                OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                mBackCallback)
     }
 
     override fun onStart() {
@@ -180,10 +187,15 @@
     override fun onDestroy() {
         super.onDestroy()
         mDialog?.dismiss()
-        sensorPrivacyController.removeCallback(sensorPrivacyListener)
+        sensorPrivacyListener?.also { sensorPrivacyController.removeCallback(it) }
+        onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mBackCallback)
     }
 
     override fun onBackPressed() {
+        onBackInvoked()
+    }
+
+    fun onBackInvoked() {
         // do not allow backing out
     }
 
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/CurrentUserObservable.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java
deleted file mode 100644
index dea8c32..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.settings;
-
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-/**
- * A class that has an observable for the current user.
- */
-public class CurrentUserObservable {
-
-    private final CurrentUserTracker mTracker;
-
-    private final MutableLiveData<Integer> mCurrentUser = new MutableLiveData<Integer>() {
-        @Override
-        protected void onActive() {
-            super.onActive();
-            mTracker.startTracking();
-        }
-
-        @Override
-        protected void onInactive() {
-            super.onInactive();
-            mTracker.stopTracking();
-        }
-    };
-
-    public CurrentUserObservable(BroadcastDispatcher broadcastDispatcher) {
-        mTracker = new CurrentUserTracker(broadcastDispatcher) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                mCurrentUser.setValue(newUserId);
-            }
-        };
-    }
-
-    /**
-     * Returns the current user that can be observed.
-     */
-    public LiveData<Integer> getCurrentUser() {
-        if (mCurrentUser.getValue() == null) {
-            mCurrentUser.setValue(mTracker.getCurrentUserId());
-        }
-        return mCurrentUser;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
deleted file mode 100644
index 9599d77..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.settings;
-
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-
-public abstract class CurrentUserTracker {
-    private final UserReceiver mUserReceiver;
-
-    private Consumer<Integer> mCallback = this::onUserSwitched;
-
-    public CurrentUserTracker(BroadcastDispatcher broadcastDispatcher) {
-        this(UserReceiver.getInstance(broadcastDispatcher));
-    }
-
-    @VisibleForTesting
-    CurrentUserTracker(UserReceiver receiver) {
-        mUserReceiver = receiver;
-    }
-
-    public int getCurrentUserId() {
-        return mUserReceiver.getCurrentUserId();
-    }
-
-    public void startTracking() {
-        mUserReceiver.addTracker(mCallback);
-    }
-
-    public void stopTracking() {
-        mUserReceiver.removeTracker(mCallback);
-    }
-
-    public abstract void onUserSwitched(int newUserId);
-
-    @VisibleForTesting
-    static class UserReceiver extends BroadcastReceiver {
-        private static UserReceiver sInstance;
-
-        private boolean mReceiverRegistered;
-        private int mCurrentUserId;
-        private final BroadcastDispatcher mBroadcastDispatcher;
-
-        private List<Consumer<Integer>> mCallbacks = new ArrayList<>();
-
-        @VisibleForTesting
-        UserReceiver(BroadcastDispatcher broadcastDispatcher) {
-            mBroadcastDispatcher = broadcastDispatcher;
-        }
-
-        static UserReceiver getInstance(BroadcastDispatcher broadcastDispatcher) {
-            if (sInstance == null) {
-                sInstance = new UserReceiver(broadcastDispatcher);
-            }
-            return sInstance;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
-                notifyUserSwitched(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
-            }
-        }
-
-        public int getCurrentUserId() {
-            return mCurrentUserId;
-        }
-
-        private void addTracker(Consumer<Integer> callback) {
-            if (!mCallbacks.contains(callback)) {
-                mCallbacks.add(callback);
-            }
-            if (!mReceiverRegistered) {
-                mCurrentUserId = ActivityManager.getCurrentUser();
-                IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-                mBroadcastDispatcher.registerReceiver(this, filter, null,
-                        UserHandle.ALL);
-                mReceiverRegistered = true;
-            }
-        }
-
-        private void removeTracker(Consumer<Integer> callback) {
-            if (mCallbacks.contains(callback)) {
-                mCallbacks.remove(callback);
-                if (mCallbacks.size() == 0 && mReceiverRegistered) {
-                    mBroadcastDispatcher.unregisterReceiver(this);
-                    mReceiverRegistered = false;
-                }
-            }
-        }
-
-        private void notifyUserSwitched(int newUserId) {
-            if (mCurrentUserId != newUserId) {
-                mCurrentUserId = newUserId;
-                List<Consumer<Integer>> callbacks = new ArrayList<>(mCallbacks);
-                for (Consumer<Integer> consumer : callbacks) {
-                    // Accepting may modify this list
-                    if (mCallbacks.contains(consumer)) {
-                        consumer.accept(newUserId);
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
index d450afa..bfba6df 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
@@ -35,12 +35,14 @@
 import javax.inject.Inject
 
 /**
- * Implementation for retrieving file paths for file storage of system and secondary users.
- * Files lie in {File Directory}/UserFileManager/{User Id} for secondary user.
- * For system user, we use the conventional {File Directory}
+ * Implementation for retrieving file paths for file storage of system and secondary users. Files
+ * lie in {File Directory}/UserFileManager/{User Id} for secondary user. For system user, we use the
+ * conventional {File Directory}
  */
 @SysUISingleton
-class UserFileManagerImpl @Inject constructor(
+class UserFileManagerImpl
+@Inject
+constructor(
     // Context of system process and system user.
     private val context: Context,
     val userManager: UserManager,
@@ -49,80 +51,114 @@
 ) : UserFileManager, CoreStartable {
     companion object {
         private const val FILES = "files"
-        @VisibleForTesting internal const val SHARED_PREFS = "shared_prefs"
+        const val SHARED_PREFS = "shared_prefs"
         @VisibleForTesting internal const val ID = "UserFileManager"
-    }
 
-   private val broadcastReceiver = object : BroadcastReceiver() {
+        /** Returns `true` if the given user ID is that for the primary/system user. */
+        fun isPrimaryUser(userId: Int): Boolean {
+            return UserHandle(userId).isSystem
+        }
+
         /**
-         * Listen to Intent.ACTION_USER_REMOVED to clear user data.
+         * Returns a [File] pointing to the correct path for a secondary user ID.
+         *
+         * Note that there is no check for the type of user. This should only be called for
+         * secondary users, never for the system user. For that, make sure to call [isPrimaryUser].
+         *
+         * Note also that there is no guarantee that the parent directory structure for the file
+         * exists on disk. For that, call [ensureParentDirExists].
+         *
+         * @param context The context
+         * @param fileName The name of the file
+         * @param directoryName The name of the directory that would contain the file
+         * @param userId The ID of the user to build a file path for
          */
-        override fun onReceive(context: Context, intent: Intent) {
-            if (intent.action == Intent.ACTION_USER_REMOVED) {
-                clearDeletedUserData()
+        fun secondaryUserFile(
+            context: Context,
+            fileName: String,
+            directoryName: String,
+            userId: Int,
+        ): File {
+            return Environment.buildPath(
+                context.filesDir,
+                ID,
+                userId.toString(),
+                directoryName,
+                fileName,
+            )
+        }
+
+        /**
+         * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
+         * recursively.
+         */
+        fun ensureParentDirExists(file: File) {
+            val parent = file.parentFile
+            if (!parent.exists()) {
+                if (!parent.mkdirs()) {
+                    Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
+                }
             }
         }
     }
 
-    /**
-     * Poll for user-specific directories to delete upon start up.
-     */
+    private val broadcastReceiver =
+        object : BroadcastReceiver() {
+            /** Listen to Intent.ACTION_USER_REMOVED to clear user data. */
+            override fun onReceive(context: Context, intent: Intent) {
+                if (intent.action == Intent.ACTION_USER_REMOVED) {
+                    clearDeletedUserData()
+                }
+            }
+        }
+
+    /** Poll for user-specific directories to delete upon start up. */
     override fun start() {
         clearDeletedUserData()
-        val filter = IntentFilter().apply {
-            addAction(Intent.ACTION_USER_REMOVED)
-        }
+        val filter = IntentFilter().apply { addAction(Intent.ACTION_USER_REMOVED) }
         broadcastDispatcher.registerReceiver(broadcastReceiver, filter, backgroundExecutor)
     }
 
-    /**
-     * Return the file based on current user.
-     */
+    /** Return the file based on current user. */
     override fun getFile(fileName: String, userId: Int): File {
-        return if (UserHandle(userId).isSystem) {
-            Environment.buildPath(
-                context.filesDir,
-                fileName
-            )
+        return if (isPrimaryUser(userId)) {
+            Environment.buildPath(context.filesDir, fileName)
         } else {
-            val secondaryFile = Environment.buildPath(
-                context.filesDir,
-                ID,
-                userId.toString(),
-                FILES,
-                fileName
-            )
+            val secondaryFile =
+                secondaryUserFile(
+                    context = context,
+                    userId = userId,
+                    directoryName = FILES,
+                    fileName = fileName,
+                )
             ensureParentDirExists(secondaryFile)
             secondaryFile
         }
     }
 
-    /**
-     * Get shared preferences from user.
-     */
+    /** Get shared preferences from user. */
     override fun getSharedPreferences(
         fileName: String,
         @Context.PreferencesMode mode: Int,
         userId: Int
     ): SharedPreferences {
-        if (UserHandle(userId).isSystem) {
+        if (isPrimaryUser(userId)) {
             return context.getSharedPreferences(fileName, mode)
         }
-        val secondaryUserDir = Environment.buildPath(
-            context.filesDir,
-            ID,
-            userId.toString(),
-            SHARED_PREFS,
-            fileName
-        )
+
+        val secondaryUserDir =
+            secondaryUserFile(
+                context = context,
+                fileName = fileName,
+                directoryName = SHARED_PREFS,
+                userId = userId,
+            )
 
         ensureParentDirExists(secondaryUserDir)
         return context.getSharedPreferences(secondaryUserDir, mode)
     }
 
-    /**
-     * Remove dirs for deleted users.
-     */
+    /** Remove dirs for deleted users. */
     @VisibleForTesting
     internal fun clearDeletedUserData() {
         backgroundExecutor.execute {
@@ -133,10 +169,11 @@
 
             dirsToDelete.forEach { dir ->
                 try {
-                    val dirToDelete = Environment.buildPath(
-                        file,
-                        dir,
-                    )
+                    val dirToDelete =
+                        Environment.buildPath(
+                            file,
+                            dir,
+                        )
                     dirToDelete.deleteRecursively()
                 } catch (e: Exception) {
                     Log.e(ID, "Deletion failed.", e)
@@ -144,18 +181,4 @@
             }
         }
     }
-
-    /**
-     * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
-     * recursively.
-     */
-    @VisibleForTesting
-    internal fun ensureParentDirExists(file: File) {
-        val parent = file.parentFile
-        if (!parent.exists()) {
-            if (!parent.mkdirs()) {
-                Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 6711734..200288b 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()
@@ -108,6 +107,7 @@
 
         val filter = IntentFilter().apply {
             addAction(Intent.ACTION_USER_SWITCHED)
+            addAction(Intent.ACTION_USER_INFO_CHANGED)
             // These get called when a managed profile goes in or out of quiet mode.
             addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
             addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
@@ -125,6 +125,7 @@
             Intent.ACTION_USER_SWITCHED -> {
                 handleSwitchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL))
             }
+            Intent.ACTION_USER_INFO_CHANGED,
             Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
             Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
             Intent.ACTION_MANAGED_PROFILE_REMOVED,
@@ -155,7 +156,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 +175,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/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 7801c68..5880003 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -21,6 +21,7 @@
 import static com.android.settingslib.display.BrightnessUtils.convertLinearToGammaFloat;
 
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -46,11 +47,13 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.RestrictedLockUtilsInternal;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 
+import java.util.concurrent.Executor;
+
 import javax.inject.Inject;
 
 public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
@@ -74,9 +77,10 @@
     private final Context mContext;
     private final ToggleSlider mControl;
     private final DisplayManager mDisplayManager;
-    private final CurrentUserTracker mUserTracker;
+    private final UserTracker mUserTracker;
     private final IVrManager mVrManager;
 
+    private final Executor mMainExecutor;
     private final Handler mBackgroundHandler;
     private final BrightnessObserver mBrightnessObserver;
 
@@ -169,7 +173,7 @@
             }
 
             mBrightnessObserver.startObserving();
-            mUserTracker.startTracking();
+            mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
 
             // Update the slider and mode before attaching the listener so we don't
             // receive the onChanged notifications for the initial values.
@@ -197,7 +201,7 @@
             }
 
             mBrightnessObserver.stopObserving();
-            mUserTracker.stopTracking();
+            mUserTracker.removeCallback(mUserChangedCallback);
 
             mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
         }
@@ -275,22 +279,27 @@
         }
     };
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    mBackgroundHandler.post(mUpdateModeRunnable);
+                    mBackgroundHandler.post(mUpdateSliderRunnable);
+                }
+            };
+
     public BrightnessController(
             Context context,
             ToggleSlider control,
-            BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker,
+            @Main Executor mainExecutor,
             @Background Handler bgHandler) {
         mContext = context;
         mControl = control;
         mControl.setMax(GAMMA_SPACE_MAX);
+        mMainExecutor = mainExecutor;
         mBackgroundHandler = bgHandler;
-        mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                mBackgroundHandler.post(mUpdateModeRunnable);
-                mBackgroundHandler.post(mUpdateSliderRunnable);
-            }
-        };
+        mUserTracker = userTracker;
         mBrightnessObserver = new BrightnessObserver(mHandler);
 
         mDisplayId = mContext.getDisplayId();
@@ -364,7 +373,7 @@
                 mControl.setEnforcedAdmin(
                         RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
                                 UserManager.DISALLOW_CONFIG_BRIGHTNESS,
-                                mUserTracker.getCurrentUserId()));
+                                mUserTracker.getUserId()));
             }
         });
     }
@@ -440,16 +449,19 @@
     /** Factory for creating a {@link BrightnessController}. */
     public static class Factory {
         private final Context mContext;
-        private final BroadcastDispatcher mBroadcastDispatcher;
+        private final UserTracker mUserTracker;
+        private final Executor mMainExecutor;
         private final Handler mBackgroundHandler;
 
         @Inject
         public Factory(
                 Context context,
-                BroadcastDispatcher broadcastDispatcher,
+                UserTracker userTracker,
+                @Main Executor mainExecutor,
                 @Background Handler bgHandler) {
             mContext = context;
-            mBroadcastDispatcher = broadcastDispatcher;
+            mUserTracker = userTracker;
+            mMainExecutor = mainExecutor;
             mBackgroundHandler = bgHandler;
         }
 
@@ -458,7 +470,8 @@
             return new BrightnessController(
                     mContext,
                     toggleSlider,
-                    mBroadcastDispatcher,
+                    mUserTracker,
+                    mMainExecutor,
                     mBackgroundHandler);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index d5a3954..e208be9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -34,10 +34,12 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
 
 import java.util.List;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -46,16 +48,19 @@
 
     private BrightnessController mBrightnessController;
     private final BrightnessSliderController.Factory mToggleSliderFactory;
-    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final UserTracker mUserTracker;
+    private final Executor mMainExecutor;
     private final Handler mBackgroundHandler;
 
     @Inject
     public BrightnessDialog(
-            BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker,
             BrightnessSliderController.Factory factory,
+            @Main Executor mainExecutor,
             @Background Handler bgHandler) {
-        mBroadcastDispatcher = broadcastDispatcher;
+        mUserTracker = userTracker;
         mToggleSliderFactory = factory;
+        mMainExecutor = mainExecutor;
         mBackgroundHandler = bgHandler;
     }
 
@@ -101,7 +106,7 @@
         frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
 
         mBrightnessController = new BrightnessController(
-                this, controller, mBroadcastDispatcher, mBackgroundHandler);
+                this, controller, mUserTracker, mMainExecutor, mBackgroundHandler);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java b/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java
new file mode 100644
index 0000000..fc61e90
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java
@@ -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.
+ */
+
+package com.android.systemui.shade;
+
+import com.android.systemui.camera.CameraGestureHelper;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+
+import javax.inject.Inject;
+
+/** Handles launching camera from Shade. */
+@SysUISingleton
+public class CameraLauncher {
+    private final CameraGestureHelper mCameraGestureHelper;
+    private final KeyguardBypassController mKeyguardBypassController;
+
+    private boolean mLaunchingAffordance;
+
+    @Inject
+    public CameraLauncher(
+            CameraGestureHelper cameraGestureHelper,
+            KeyguardBypassController keyguardBypassController
+    ) {
+        mCameraGestureHelper = cameraGestureHelper;
+        mKeyguardBypassController = keyguardBypassController;
+    }
+
+    /** Launches the camera. */
+    public void launchCamera(int source, boolean isShadeFullyCollapsed) {
+        if (!isShadeFullyCollapsed) {
+            setLaunchingAffordance(true);
+        }
+
+        mCameraGestureHelper.launchCamera(source);
+    }
+
+    /**
+     * Set whether we are currently launching an affordance. This is currently only set when
+     * launched via a camera gesture.
+     */
+    public void setLaunchingAffordance(boolean launchingAffordance) {
+        mLaunchingAffordance = launchingAffordance;
+        mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
+    }
+
+    /**
+     * Return true when a bottom affordance is launching an occluded activity with a splash screen.
+     */
+    public boolean isLaunchingAffordance() {
+        return mLaunchingAffordance;
+    }
+
+    /**
+     * Whether the camera application can be launched for the camera launch gesture.
+     */
+    public boolean canCameraGestureBeLaunched(int barState) {
+        return mCameraGestureHelper.canCameraGestureBeLaunched(barState);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
index 4063af3..5011227 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -51,6 +51,8 @@
                 connect(R.id.statusIcons, ConstraintSet.START, R.id.date, ConstraintSet.END)
                 connect(R.id.privacy_container, ConstraintSet.START, R.id.date, ConstraintSet.END)
                 constrainWidth(R.id.statusIcons, ViewGroup.LayoutParams.WRAP_CONTENT)
+                constrainedWidth(R.id.date, true)
+                constrainedWidth(R.id.statusIcons, true)
             }
         )
     }
@@ -92,7 +94,8 @@
                     centerEnd,
                     ConstraintSet.END
                 )
-                constrainWidth(R.id.statusIcons, 0)
+                constrainedWidth(R.id.date, true)
+                constrainedWidth(R.id.statusIcons, true)
             },
             qsConstraintsChanges = {
                 setGuidelineBegin(centerStart, offsetFromEdge)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
new file mode 100644
index 0000000..ae303eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
@@ -0,0 +1,142 @@
+/*
+ * 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.annotation.NonNull;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+
+import com.android.keyguard.LockIconViewController;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Drawable for NotificationPanelViewController.
+ */
+public class DebugDrawable extends Drawable {
+
+    private final NotificationPanelViewController mNotificationPanelViewController;
+    private final NotificationPanelView mView;
+    private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
+    private final LockIconViewController mLockIconViewController;
+    private final Set<Integer> mDebugTextUsedYPositions;
+    private final Paint mDebugPaint;
+
+    public DebugDrawable(
+            NotificationPanelViewController notificationPanelViewController,
+            NotificationPanelView notificationPanelView,
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController,
+            LockIconViewController lockIconViewController
+    ) {
+        mNotificationPanelViewController = notificationPanelViewController;
+        mView = notificationPanelView;
+        mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
+        mLockIconViewController = lockIconViewController;
+        mDebugTextUsedYPositions = new HashSet<>();
+        mDebugPaint = new Paint();
+    }
+
+    @Override
+    public void draw(@androidx.annotation.NonNull @NonNull Canvas canvas) {
+        mDebugTextUsedYPositions.clear();
+
+        mDebugPaint.setColor(Color.RED);
+        mDebugPaint.setStrokeWidth(2);
+        mDebugPaint.setStyle(Paint.Style.STROKE);
+        mDebugPaint.setTextSize(24);
+        String headerDebugInfo = mNotificationPanelViewController.getHeaderDebugInfo();
+        if (headerDebugInfo != null) canvas.drawText(headerDebugInfo, 50, 100, mDebugPaint);
+
+        drawDebugInfo(canvas, mNotificationPanelViewController.getMaxPanelHeight(),
+                Color.RED, "getMaxPanelHeight()");
+        drawDebugInfo(canvas, (int) mNotificationPanelViewController.getExpandedHeight(),
+                Color.BLUE, "getExpandedHeight()");
+        drawDebugInfo(canvas, mNotificationPanelViewController.calculatePanelHeightQsExpanded(),
+                Color.GREEN, "calculatePanelHeightQsExpanded()");
+        drawDebugInfo(canvas, mNotificationPanelViewController.calculatePanelHeightQsExpanded(),
+                Color.YELLOW, "calculatePanelHeightShade()");
+        drawDebugInfo(canvas,
+                (int) mNotificationPanelViewController.calculateNotificationsTopPadding(),
+                Color.MAGENTA, "calculateNotificationsTopPadding()");
+        drawDebugInfo(canvas, mNotificationPanelViewController.getClockPositionResult().clockY,
+                Color.GRAY, "mClockPositionResult.clockY");
+        drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY,
+                "mLockIconViewController.getTop()");
+
+        if (mNotificationPanelViewController.getKeyguardShowing()) {
+            // Notifications have the space between those two lines.
+            drawDebugInfo(canvas,
+                    mNotificationStackScrollLayoutController.getTop()
+                            + (int) mNotificationPanelViewController
+                            .getKeyguardNotificationTopPadding(),
+                    Color.RED, "NSSL.getTop() + mKeyguardNotificationTopPadding");
+
+            drawDebugInfo(canvas, mNotificationStackScrollLayoutController.getBottom()
+                            - (int) mNotificationPanelViewController
+                            .getKeyguardNotificationBottomPadding(),
+                    Color.RED, "NSSL.getBottom() - mKeyguardNotificationBottomPadding");
+        }
+
+        mDebugPaint.setColor(Color.CYAN);
+        canvas.drawLine(0,
+                mNotificationPanelViewController.getClockPositionResult().stackScrollerPadding,
+                mView.getWidth(), mNotificationStackScrollLayoutController.getTopPadding(),
+                mDebugPaint);
+    }
+
+    private void drawDebugInfo(Canvas canvas, int y, int color, String label) {
+        mDebugPaint.setColor(color);
+        canvas.drawLine(/* startX= */ 0, /* startY= */ y, /* stopX= */ mView.getWidth(),
+                /* stopY= */ y, mDebugPaint);
+        canvas.drawText(label + " = " + y + "px", /* x= */ 0,
+                /* y= */ computeDebugYTextPosition(y), mDebugPaint);
+    }
+
+    private int computeDebugYTextPosition(int lineY) {
+        if (lineY - mDebugPaint.getTextSize() < 0) {
+            // Avoiding drawing out of bounds
+            lineY += mDebugPaint.getTextSize();
+        }
+        int textY = lineY;
+        while (mDebugTextUsedYPositions.contains(textY)) {
+            textY = (int) (textY + mDebugPaint.getTextSize());
+        }
+        mDebugTextUsedYPositions.add(textY);
+        return textY;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.UNKNOWN;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 6b540aa..b511b54 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -19,6 +19,7 @@
 import android.annotation.IdRes
 import android.app.StatusBarManager
 import android.content.res.Configuration
+import android.os.Bundle
 import android.os.Trace
 import android.os.Trace.TRACE_TAG_APP
 import android.util.Pair
@@ -34,6 +35,8 @@
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -43,7 +46,6 @@
 import com.android.systemui.qs.carrier.QSCarrierGroupController
 import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
 import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
-import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
 import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
 import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
@@ -53,6 +55,7 @@
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
 import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
 import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER
+import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.VariableDateView
 import com.android.systemui.statusbar.policy.VariableDateViewController
@@ -69,11 +72,9 @@
  * expansion of the headers in small screen portrait.
  *
  * [header] will be a [MotionLayout] if [Flags.COMBINED_QS_HEADERS] is enabled. In this case, the
- * [MotionLayout] has 2 transitions:
+ * [MotionLayout] has one transitions:
  * * [HEADER_TRANSITION_ID]: [QQS_HEADER_CONSTRAINT] <-> [QS_HEADER_CONSTRAINT] for portrait
  *   handheld device configuration.
- * * [LARGE_SCREEN_HEADER_TRANSITION_ID]: [LARGE_SCREEN_HEADER_CONSTRAINT] (to itself) for all
- *   other configurations
  */
 @CentralSurfacesScope
 class LargeScreenShadeHeaderController @Inject constructor(
@@ -89,7 +90,8 @@
     private val dumpManager: DumpManager,
     private val featureFlags: FeatureFlags,
     private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
-    private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager
+    private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager,
+    private val demoModeController: DemoModeController
 ) : ViewController<View>(header), Dumpable {
 
     companion object {
@@ -99,8 +101,6 @@
         @VisibleForTesting
         internal val HEADER_TRANSITION_ID = R.id.header_transition
         @VisibleForTesting
-        internal val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
-        @VisibleForTesting
         internal val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
         @VisibleForTesting
         internal val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
@@ -115,10 +115,6 @@
         }
     }
 
-    init {
-        loadConstraints()
-    }
-
     private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
 
     private lateinit var iconManager: StatusBarIconController.TintedIconManager
@@ -126,7 +122,7 @@
     private lateinit var qsCarrierGroupController: QSCarrierGroupController
 
     private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
-    private val clock: TextView = header.findViewById(R.id.clock)
+    private val clock: Clock = header.findViewById(R.id.clock)
     private val date: TextView = header.findViewById(R.id.date)
     private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
     private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
@@ -212,6 +208,14 @@
         view.onApplyWindowInsets(insets)
     }
 
+    private val demoModeReceiver = object : DemoMode {
+        override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
+        override fun dispatchDemoCommand(command: String, args: Bundle) =
+            clock.dispatchDemoCommand(command, args)
+        override fun onDemoModeStarted() = clock.onDemoModeStarted()
+        override fun onDemoModeFinished() = clock.onDemoModeFinished()
+    }
+
     private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
         override fun onChipVisibilityRefreshed(visible: Boolean) {
             if (header is MotionLayout) {
@@ -246,6 +250,8 @@
             qsCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
             if (header is MotionLayout) {
                 loadConstraints()
+                header.minHeight = resources
+                        .getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height)
                 lastInsets?.let { updateConstraintsForInsets(header, it) }
             }
             updateResources()
@@ -298,6 +304,7 @@
 
         dumpManager.registerDumpable(this)
         configurationController.addCallback(configurationControllerListener)
+        demoModeController.addCallback(demoModeReceiver)
 
         updateVisibility()
         updateTransition()
@@ -307,6 +314,7 @@
         privacyIconsController.chipVisibilityListener = null
         dumpManager.unregisterDumpable(this::class.java.simpleName)
         configurationController.removeCallback(configurationControllerListener)
+        demoModeController.removeCallback(demoModeReceiver)
     }
 
     fun disable(state1: Int, state2: Int, animate: Boolean) {
@@ -328,16 +336,11 @@
         if (header is MotionLayout) {
             // Use resources.getXml instead of passing the resource id due to bug b/205018300
             header.getConstraintSet(QQS_HEADER_CONSTRAINT)
-                .load(context, resources.getXml(R.xml.qqs_header))
-            val qsConstraints = if (featureFlags.isEnabled(Flags.NEW_HEADER)) {
-                R.xml.qs_header_new
-            } else {
-                R.xml.qs_header
-            }
+                    .load(context, resources.getXml(R.xml.qqs_header))
             header.getConstraintSet(QS_HEADER_CONSTRAINT)
-                .load(context, resources.getXml(qsConstraints))
+                    .load(context, resources.getXml(R.xml.qs_header))
             header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
-                .load(context, resources.getXml(R.xml.large_screen_shade_header))
+                    .load(context, resources.getXml(R.xml.large_screen_shade_header))
         }
     }
 
@@ -426,7 +429,6 @@
         }
         header as MotionLayout
         if (largeScreenActive) {
-            header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
             header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT).applyTo(header)
         } else {
             header.setTransition(HEADER_TRANSITION_ID)
@@ -524,4 +526,7 @@
             updateConstraints(LARGE_SCREEN_HEADER_CONSTRAINT, updates.largeScreenConstraintsChanges)
         }
     }
+
+    @VisibleForTesting
+    internal fun simulateViewDetached() = this.onViewDetached()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1c0f057..507dec6 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,21 +50,16 @@
 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;
 import android.graphics.Color;
-import android.graphics.ColorFilter;
 import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.graphics.drawable.Drawable;
 import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Bundle;
@@ -104,7 +96,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;
@@ -127,17 +118,18 @@
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.DejankUtils;
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.LaunchAnimator;
 import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.camera.CameraGestureHelper;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeLog;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.DumpsysTableLogger;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -175,19 +167,17 @@
 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;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -210,8 +200,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;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -237,17 +225,15 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
-import java.util.Set;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
 
 @CentralSurfacesComponent.CentralSurfacesScope
-public final class NotificationPanelViewController {
+public final class NotificationPanelViewController implements Dumpable {
 
     public static final String TAG = NotificationPanelView.class.getSimpleName();
     public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
@@ -257,29 +243,18 @@
     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;
+    /** The delay to reset the hint text when the hint animation is finished running. */
+    private static final int HINT_RESET_DELAY_MS = 1200;
     private static final long ANIMATION_DELAY_ICON_FADE_IN =
             ActivityLaunchAnimator.TIMINGS.getTotalDuration()
                     - CollapsedStatusBarFragment.FADE_IN_DURATION
@@ -292,6 +267,23 @@
      * 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();
+    /**
+     * Duration to use for the animator when the keyguard status view alignment changes, and a
+     * custom clock animation is in use.
+     */
+    private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
+
     private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
     private final Resources mResources;
     private final KeyguardStateController mKeyguardStateController;
@@ -300,49 +292,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;
@@ -362,9 +329,7 @@
     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;
@@ -381,6 +346,12 @@
     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 final NotificationGutsManager mGutsManager;
+
     private long mDownTime;
     private boolean mTouchSlopExceededBeforeDown;
     private boolean mIsLaunchAnimationRunning;
@@ -402,13 +373,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;
@@ -421,18 +390,14 @@
     private float mQuickQsHeaderHeight;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
-
     private int mQsTrackingPointer;
     private VelocityTracker mQsVelocityTracker;
+    private TrackingStartedListener mTrackingStartedListener;
+    private OpenCloseListener mOpenCloseListener;
+    private GestureRecorder mGestureRecorder;
     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;
 
     /**
@@ -487,24 +452,24 @@
      * 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.
      */
     private boolean mQsAnimatorExpand;
-    private boolean mIsLaunchTransitionFinished;
     private ValueAnimator mQsSizeChangeAnimator;
-
     private boolean mQsScrimEnabled = true;
     private boolean mQsTouchAboveFalsingThreshold;
     private int mQsFalsingThreshold;
 
-    /** Indicates drag starting height when swiping down or up on heads-up notifications */
+    /**
+     * Indicates drag starting height when swiping down or up on heads-up notifications.
+     * This usually serves as a threshold from when shade expansion should really start. Otherwise
+     * this value would be height of shade and it will be immediately expanded to some extent.
+     */
     private int mHeadsUpStartHeight;
     private HeadsUpTouchHelper mHeadsUpTouchHelper;
     private boolean mListenForHeadsUp;
@@ -513,43 +478,30 @@
     private boolean mCollapsedOnDown;
     private boolean mClosingWithAlphaFadeOut;
     private boolean mHeadsUpAnimatingAway;
-    private boolean mLaunchingAffordance;
     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>>
@@ -579,17 +531,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.
@@ -597,94 +551,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 Rect mLastQsClipBounds = 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;
@@ -697,6 +608,7 @@
     private int mQsClipTop;
     private int mQsClipBottom;
     private boolean mQsVisible;
+
     private final ContentResolver mContentResolver;
     private float mMinFraction;
 
@@ -715,56 +627,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;
     private float mMinExpandHeight;
@@ -777,7 +640,6 @@
     private float mLastGesturedOverExpansion = -1;
     /** Whether the current animator is the spring back animation. */
     private boolean mIsSpringBackAnimation;
-    private boolean mInSplitShade;
     private float mHintDistance;
     private float mInitialOffsetOnTouch;
     private boolean mCollapsedAndHeadsUpOnDown;
@@ -813,8 +675,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) {
+            postToView(mHideExpandedRunnable);
+        }
+    };
 
     @Inject
     public NotificationPanelViewController(NotificationPanelView view,
@@ -842,6 +716,7 @@
             ConversationNotificationManager conversationNotificationManager,
             MediaHierarchyManager mediaHierarchyManager,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            NotificationGutsManager gutsManager,
             NotificationsQSContainerController notificationsQSContainerController,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
@@ -849,7 +724,6 @@
             KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
             KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
-            NotificationIconAreaController notificationIconAreaController,
             AuthController authController,
             ScrimController scrimController,
             UserManager userManager,
@@ -858,7 +732,6 @@
             AmbientState ambientState,
             LockIconViewController lockIconViewController,
             KeyguardMediaController keyguardMediaController,
-            PrivacyDotViewController privacyDotViewController,
             TapAgainViewController tapAgainViewController,
             NavigationModeController navigationModeController,
             NavigationBarController navigationBarController,
@@ -876,14 +749,15 @@
             SysUiState sysUiState,
             Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+            KeyguardIndicationController keyguardIndicationController,
             NotificationListContainer notificationListContainer,
             NotificationStackSizeCalculator notificationStackSizeCalculator,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             ShadeTransitionController shadeTransitionController,
             SystemClock systemClock,
-            CameraGestureHelper cameraGestureHelper,
             KeyguardBottomAreaViewModel keyguardBottomAreaViewModel,
-            KeyguardBottomAreaInteractor keyguardBottomAreaInteractor) {
+            KeyguardBottomAreaInteractor keyguardBottomAreaInteractor,
+            DumpManager dumpManager) {
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onKeyguardFadingAwayChanged() {
@@ -896,7 +770,7 @@
         mLockscreenGestureLogger = lockscreenGestureLogger;
         mShadeExpansionStateManager = shadeExpansionStateManager;
         mShadeLog = shadeLogger;
-        TouchHandler touchHandler = createTouchHandler();
+        mGutsManager = gutsManager;
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
@@ -904,16 +778,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();
@@ -946,7 +820,6 @@
         mInteractionJankMonitor = interactionJankMonitor;
         mSystemClock = systemClock;
         mKeyguardMediaController = keyguardMediaController;
-        mPrivacyDotViewController = privacyDotViewController;
         mMetricsLogger = metricsLogger;
         mConfigurationController = configurationController;
         mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
@@ -958,7 +831,6 @@
         mKeyguardBottomAreaViewControllerProvider = keyguardBottomAreaViewControllerProvider;
         mNotificationsQSContainerController.init();
         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
-        mNotificationIconAreaController = notificationIconAreaController;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
         mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
         mDepthController = notificationShadeDepthController;
@@ -1001,10 +873,7 @@
         mShadeTransitionController = shadeTransitionController;
         lockscreenShadeTransitionController.setNotificationPanelController(this);
         shadeTransitionController.setNotificationPanelViewController(this);
-        DynamicPrivacyControlListener
-                dynamicPrivacyControlListener =
-                new DynamicPrivacyControlListener();
-        dynamicPrivacyController.addListener(dynamicPrivacyControlListener);
+        dynamicPrivacyController.addListener(this::onDynamicPrivacyChanged);
 
         shadeExpansionStateManager.addStateListener(this::onPanelStateChanged);
 
@@ -1028,16 +897,18 @@
         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());
+            mView.getOverlay().add(new DebugDrawable(this, mView,
+                    mNotificationStackScrollLayoutController, mLockIconViewController));
         }
 
         mKeyguardUnfoldTransition = unfoldComponent.map(
@@ -1048,60 +919,71 @@
         mQsFrameTranslateController = qsFrameTranslateController;
         updateUserSwitcherFlags();
         mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel;
+        mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
         onFinishInflate();
         keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
                 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;
+        dumpManager.registerDumpable(this);
+    }
+
+    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
@@ -1140,7 +1022,7 @@
                 R.id.notification_stack_scroller);
         mNotificationStackScrollLayoutController.attach(stackScrollLayout);
         mNotificationStackScrollLayoutController.setOnHeightChangedListener(
-                mOnHeightChangedListener);
+                new NsslHeightChangedListener());
         mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
                 mOnOverscrollTopChangedListener);
         mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled);
@@ -1148,7 +1030,7 @@
         mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
                 mOnEmptySpaceClickListener);
         addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
-        mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
+        setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
 
         initBottomArea();
 
@@ -1195,7 +1077,6 @@
         mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
         mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
         mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount);
-        mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade);
         mFlingAnimationUtils = mFlingAnimationUtilsBuilder.get()
                 .setMaxLengthSeconds(0.4f).build();
         mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
@@ -1231,6 +1112,7 @@
                 mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
         mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
         mKeyguardStatusViewController.init();
+        updateClockAppearance();
 
         if (mKeyguardUserSwitcherController != null) {
             // Try to close the switcher so that callbacks are triggered if necessary.
@@ -1261,11 +1143,6 @@
         }
     }
 
-    private void setCentralSurfaces(CentralSurfaces centralSurfaces) {
-        // TODO: this can be injected.
-        mCentralSurfaces = centralSurfaces;
-    }
-
     public void updateResources() {
         mSplitShadeNotificationsScrimMarginBottom =
                 mResources.getDimensionPixelSize(
@@ -1284,8 +1161,15 @@
 
         mLargeScreenShadeHeaderHeight =
                 mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
-        mQuickQsHeaderHeight = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
-                SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
+        // TODO: When the flag is eventually removed, it means that we have a single view that is
+        // the same height in QQS and in Large Screen (large_screen_shade_header_height). Eventually
+        // the concept of largeScreenHeader or quickQsHeader will disappear outside of the class
+        // that controls the view as the offset needs to be the same regardless.
+        if (mUseLargeScreenShadeHeader || mFeatureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)) {
+            mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight;
+        } else {
+            mQuickQsHeaderHeight = SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
+        }
         int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
                 mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
         mLargeScreenShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
@@ -1351,7 +1235,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);
@@ -1397,7 +1281,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();
@@ -1430,12 +1314,21 @@
         mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView));
     }
 
+    @VisibleForTesting
+    void setQs(QS qs) {
+        mQs = qs;
+    }
+
     private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
         mKeyguardMediaController.attachSplitShadeContainer(container);
     }
 
     private void initBottomArea() {
-        mKeyguardBottomArea.init(mKeyguardBottomAreaViewModel, mFalsingManager);
+        mKeyguardBottomArea.init(
+                mKeyguardBottomAreaViewModel,
+                mFalsingManager,
+                mLockIconViewController
+        );
     }
 
     @VisibleForTesting
@@ -1444,12 +1337,7 @@
     }
 
     @VisibleForTesting
-    boolean getClosing() {
-        return mClosing;
-    }
-
-    @VisibleForTesting
-    boolean getIsFlinging() {
+    boolean isFlinging() {
         return mIsFlinging;
     }
 
@@ -1476,11 +1364,19 @@
         return mHintAnimationRunning || mUnlockedScreenOffAnimationController.isAnimationPlaying();
     }
 
-    public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
-        mKeyguardIndicationController = indicationController;
+    private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) {
+        mKeyguardBottomArea = keyguardBottomArea;
         mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
     }
 
+    void setOpenCloseListener(OpenCloseListener openCloseListener) {
+        mOpenCloseListener = openCloseListener;
+    }
+
+    void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
+        mTrackingStartedListener = trackingStartedListener;
+    }
+
     private void updateGestureExclusionRect() {
         Rect exclusionRect = calculateGestureExclusionRect();
         mView.setSystemGestureExclusionRects(exclusionRect.isEmpty() ? Collections.emptyList()
@@ -1637,6 +1533,10 @@
         updateClock();
     }
 
+    public KeyguardClockPositionAlgorithm.Result getClockPositionResult() {
+        return mClockPositionResult;
+    }
+
     @ClockSize
     private int computeDesiredClockSize() {
         if (mSplitShadeEnabled) {
@@ -1693,23 +1593,31 @@
                     // Find the clock, so we can exclude it from this transition.
                     FrameLayout clockContainerView =
                             mView.findViewById(R.id.lockscreen_clock_view_large);
-                    View clockView = clockContainerView.getChildAt(0);
 
-                    transition.excludeTarget(clockView, /* exclude= */ true);
+                    // The clock container can sometimes be null. If it is, just fall back to the
+                    // old animation rather than setting up the custom animations.
+                    if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
+                        TransitionManager.beginDelayedTransition(
+                                mNotificationContainerParent, transition);
+                    } else {
+                        View clockView = clockContainerView.getChildAt(0);
 
-                    TransitionSet set = new TransitionSet();
-                    set.addTransition(transition);
+                        transition.excludeTarget(clockView, /* exclude= */ true);
 
-                    SplitShadeTransitionAdapter adapter =
-                            new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
+                        TransitionSet set = new TransitionSet();
+                        set.addTransition(transition);
 
-                    // Use linear here, so the actual clock can pick its own interpolator.
-                    adapter.setInterpolator(Interpolators.LINEAR);
-                    adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                    adapter.addTarget(clockView);
-                    set.addTransition(adapter);
+                        SplitShadeTransitionAdapter adapter =
+                                new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
 
-                    TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+                        // Use linear here, so the actual clock can pick its own interpolator.
+                        adapter.setInterpolator(Interpolators.LINEAR);
+                        adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
+                        adapter.addTarget(clockView);
+                        set.addTransition(adapter);
+
+                        TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+                    }
                 } else {
                     TransitionManager.beginDelayedTransition(
                             mNotificationContainerParent, transition);
@@ -1884,8 +1792,7 @@
     }
 
     public void resetViews(boolean animate) {
-        mIsLaunchTransitionFinished = false;
-        mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
+        mGutsManager.closeAndSaveGuts(true /* leavebehind */, true /* force */,
                 true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
         if (animate && !isFullyCollapsed()) {
             animateCloseQs(true /* animateAway */);
@@ -1924,13 +1831,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);
@@ -1940,13 +1847,19 @@
         }
     }
 
-    private void setQsExpandImmediate(boolean expandImmediate) {
+    @VisibleForTesting
+    void setQsExpandImmediate(boolean expandImmediate) {
         if (expandImmediate != mQsExpandImmediate) {
             mQsExpandImmediate = expandImmediate;
             mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate);
         }
     }
 
+    @VisibleForTesting
+    boolean isQsExpandImmediate() {
+        return mQsExpandImmediate;
+    }
+
     private void setShowShelfOnly(boolean shelfOnly) {
         mNotificationStackScrollLayoutController.setShouldShowShelfOnly(
                 shelfOnly && !mSplitShadeEnabled);
@@ -1954,7 +1867,11 @@
 
     public void closeQs() {
         cancelQsAnimation();
-        setQsExpansion(mQsMinExpansionHeight);
+        setQsExpansionHeight(mQsMinExpansionHeight);
+        // qsExpandImmediate is a safety latch in case we're calling closeQS while we're in the
+        // middle of animation - we need to make sure that value is always false when shade if
+        // fully collapsed or expanded
+        setQsExpandImmediate(false);
     }
 
     @VisibleForTesting
@@ -1992,7 +1909,7 @@
             }
             float height = mQsExpansionHeight;
             mQsExpansionAnimator.cancel();
-            setQsExpansion(height);
+            setQsExpansionHeight(height);
         }
         flingSettings(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE);
     }
@@ -2016,7 +1933,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 {
@@ -2033,12 +1950,12 @@
         }
     }
 
-    public void fling(float vel, boolean expand) {
-        GestureRecorder gr = mCentralSurfaces.getGestureRecorder();
-        if (gr != null) {
-            gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
+    private void fling(float vel) {
+        if (mGestureRecorder != null) {
+            mGestureRecorder.tag("fling " + ((vel > 0) ? "open" : "closed"),
+                    "notifications,v=" + vel);
         }
-        fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
+        fling(vel, true, 1.0f /* collapseSpeedUpFactor */, false);
     }
 
     @VisibleForTesting
@@ -2057,7 +1974,7 @@
         // we want to perform an overshoot animation when flinging open
         final boolean addOverscroll =
                 expand
-                        && !mInSplitShade // Split shade has its own overscroll logic
+                        && !mSplitShadeEnabled // Split shade has its own overscroll logic
                         && mStatusBarStateController.getState() != KEYGUARD
                         && mOverExpansion == 0.0f
                         && vel >= 0;
@@ -2125,7 +2042,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 {
@@ -2155,7 +2072,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;
@@ -2170,6 +2087,14 @@
                 mInitialTouchX = x;
                 initVelocityTracker();
                 trackMovement(event);
+                float qsExpansionFraction = computeQsExpansionFraction();
+                // Intercept the touch if QS is between fully collapsed and fully expanded state
+                if (!mSplitShadeEnabled
+                        && qsExpansionFraction > 0.0 && qsExpansionFraction < 1.0) {
+                    mShadeLog.logMotionEvent(event,
+                            "onQsIntercept: down action, QS partially expanded/collapsed");
+                    return true;
+                }
                 if (mKeyguardShowing
                         && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
                     // Dragging down on the lockscreen statusbar should prohibit other interactions
@@ -2205,7 +2130,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 {
@@ -2216,7 +2141,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;
@@ -2275,7 +2200,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();
@@ -2325,7 +2250,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);
@@ -2364,9 +2289,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;
     }
@@ -2422,6 +2347,14 @@
         if (!isFullyCollapsed()) {
             handleQsDown(event);
         }
+        // defer touches on QQS to shade while shade is collapsing. Added margin for error
+        // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS.
+        if (!mSplitShadeEnabled
+                && computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) {
+            mShadeLog.logMotionEvent(event,
+                    "handleQsTouch: QQS touched while shade collapsing");
+            mQsTracking = false;
+        }
         if (!mQsExpandImmediate && mQsTracking) {
             onQsTouch(event);
             if (!mConflictingQsExpansionGesture && !mSplitShadeEnabled) {
@@ -2492,7 +2425,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;
@@ -2506,9 +2439,7 @@
         }
     }
 
-    /**
-     * Input focus transfer is about to happen.
-     */
+    /** Input focus transfer is about to happen. */
     public void startWaitingForOpenPanelGesture() {
         if (!isFullyCollapsed()) {
             return;
@@ -2540,7 +2471,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);
         }
@@ -2614,9 +2545,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;
                 }
@@ -2663,8 +2594,7 @@
 
         // Reset scroll position and apply that position to the expanded height.
         float height = mQsExpansionHeight;
-        setQsExpansion(height);
-        updateExpandedHeightToMaxHeight();
+        setQsExpansionHeight(height);
         mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
 
         // When expanding QS, let's authenticate the user if possible,
@@ -2691,6 +2621,9 @@
                 navigationBarView.onStatusBarPanelStateChanged();
             }
             mShadeExpansionStateManager.onQsExpansionChanged(expanded);
+            mShadeLog.logQsExpansionChanged("QS Expansion Changed.", expanded,
+                    mQsMinExpansionHeight, mQsMaxExpansionHeight, mStackScrollerOverscrolling,
+                    mDozing, mQsAnimatorExpand, mAnimatingQS);
         }
     }
 
@@ -2735,7 +2668,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;
@@ -2845,8 +2778,10 @@
      * as well based on the bounds of the shade and QS state.
      */
     private void setQSClippingBounds() {
-        final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction());
-        final boolean qsVisible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0);
+        float qsExpansionFraction = computeQsExpansionFraction();
+        final int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
+        final boolean qsVisible = (qsExpansionFraction > 0 || qsPanelBottomY > 0);
+        checkCorrectScrimVisibility(qsExpansionFraction);
 
         int top = calculateTopQsClippingBound(qsPanelBottomY);
         int bottom = calculateBottomQsClippingBound(top);
@@ -2857,6 +2792,19 @@
         applyQSClippingBounds(left, top, right, bottom, qsVisible);
     }
 
+    private void checkCorrectScrimVisibility(float expansionFraction) {
+        // issues with scrims visible on keyguard occur only in split shade
+        if (mSplitShadeEnabled) {
+            boolean keyguardViewsVisible = mBarState == KEYGUARD && mKeyguardOnlyContentAlpha == 1;
+            // expansionFraction == 1 means scrims are fully visible as their size/visibility depend
+            // on QS expansion
+            if (expansionFraction == 1 && keyguardViewsVisible) {
+                Log.wtf(TAG,
+                        "Incorrect state, scrim is visible at the same time when clock is visible");
+            }
+        }
+    }
+
     private int calculateTopQsClippingBound(int qsPanelBottomY) {
         int top;
         if (mSplitShadeEnabled) {
@@ -2897,12 +2845,12 @@
             return top + mNotificationStackScrollLayoutController.getHeight()
                     + mSplitShadeNotificationsScrimMarginBottom;
         } else {
-            return getView().getBottom();
+            return mView.getBottom();
         }
     }
 
     private int calculateLeftQsClippingBound() {
-        if (isFullWidth()) {
+        if (mIsFullWidth) {
             // left bounds can ignore insets, it should always reach the edge of the screen
             return 0;
         } else {
@@ -2911,8 +2859,8 @@
     }
 
     private int calculateRightQsClippingBound() {
-        if (isFullWidth()) {
-            return getView().getRight() + mDisplayRightInset;
+        if (mIsFullWidth) {
+            return mView.getRight() + mDisplayRightInset;
         } else {
             return mNotificationStackScrollLayoutController.getRight();
         }
@@ -2927,7 +2875,7 @@
      */
     private void applyQSClippingBounds(int left, int top, int right, int bottom,
             boolean qsVisible) {
-        if (!mAnimateNextNotificationBounds || mKeyguardStatusAreaClipBounds.isEmpty()) {
+        if (!mAnimateNextNotificationBounds || mLastQsClipBounds.isEmpty()) {
             if (mQsClippingAnimation != null) {
                 // update the end position of the animator
                 mQsClippingAnimationEndBounds.set(left, top, right, bottom);
@@ -2936,10 +2884,10 @@
             }
         } else {
             mQsClippingAnimationEndBounds.set(left, top, right, bottom);
-            final int startLeft = mKeyguardStatusAreaClipBounds.left;
-            final int startTop = mKeyguardStatusAreaClipBounds.top;
-            final int startRight = mKeyguardStatusAreaClipBounds.right;
-            final int startBottom = mKeyguardStatusAreaClipBounds.bottom;
+            final int startLeft = mLastQsClipBounds.left;
+            final int startTop = mLastQsClipBounds.top;
+            final int startRight = mLastQsClipBounds.right;
+            final int startBottom = mLastQsClipBounds.bottom;
             if (mQsClippingAnimation != null) {
                 mQsClippingAnimation.cancel();
             }
@@ -2976,12 +2924,10 @@
 
     private void applyQSClippingImmediately(int left, int top, int right, int bottom,
             boolean qsVisible) {
-        // Fancy clipping for quick settings
         int radius = mScrimCornerRadius;
         boolean clipStatusView = false;
-        if (isFullWidth()) {
-            // The padding on this area is large enough that we can use a cheaper clipping strategy
-            mKeyguardStatusAreaClipBounds.set(left, top, right, bottom);
+        mLastQsClipBounds.set(left, top, right, bottom);
+        if (mIsFullWidth) {
             clipStatusView = qsVisible;
             float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius;
             radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
@@ -3016,8 +2962,8 @@
                     radius,
                     qsVisible && !mSplitShadeEnabled);
         }
-        mKeyguardStatusViewController.setClipBounds(
-                clipStatusView ? mKeyguardStatusAreaClipBounds : null);
+        // The padding on this area is large enough that we can use a cheaper clipping strategy
+        mKeyguardStatusViewController.setClipBounds(clipStatusView ? mLastQsClipBounds : null);
         if (!qsVisible && mSplitShadeEnabled) {
             // On the lockscreen when qs isn't visible, we don't want the bounds of the shade to
             // be visible, otherwise you can see the bounds once swiping up to see bouncer
@@ -3042,11 +2988,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() {
@@ -3083,7 +3041,7 @@
         }
     }
 
-    private float calculateNotificationsTopPadding() {
+    float calculateNotificationsTopPadding() {
         if (mSplitShadeEnabled) {
             return mKeyguardShowing ? getKeyguardNotificationStaticPadding() : 0;
         }
@@ -3117,10 +3075,19 @@
         }
     }
 
-    /**
-     * @return the topPadding of notifications when on keyguard not respecting quick settings
-     * expansion
-     */
+    public boolean getKeyguardShowing() {
+        return mKeyguardShowing;
+    }
+
+    public float getKeyguardNotificationTopPadding() {
+        return mKeyguardNotificationTopPadding;
+    }
+
+    public float getKeyguardNotificationBottomPadding() {
+        return mKeyguardNotificationBottomPadding;
+    }
+
+    /** Returns the topPadding of notifications when on keyguard not respecting QS expansion. */
     private int getKeyguardNotificationStaticPadding() {
         if (!mKeyguardShowing) {
             return 0;
@@ -3152,17 +3119,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
@@ -3200,22 +3168,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;
@@ -3270,9 +3223,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 */);
     }
@@ -3329,7 +3280,7 @@
             animator.setDuration(350);
         }
         animator.addUpdateListener(
-                animation -> setQsExpansion((Float) animation.getAnimatedValue()));
+                animation -> setQsExpansionHeight((Float) animation.getAnimatedValue()));
         animator.addListener(new AnimatorListenerAdapter() {
             private boolean mIsCanceled;
 
@@ -3405,7 +3356,7 @@
         return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS);
     }
 
-    public int getMaxPanelHeight() {
+    int getMaxPanelHeight() {
         int min = mStatusBarMinHeight;
         if (!(mBarState == KEYGUARD)
                 && mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) {
@@ -3439,19 +3390,38 @@
     }
 
     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;
+        // in split shade we react when HUN is visible only if shade height is over HUN start
+        // height - which means user is swiping down. Otherwise shade QS will either not show at all
+        // with HUN movement or it will blink when touching HUN initially
+        boolean qsShouldExpandWithHeadsUp = !mSplitShadeEnabled
+                || (!mHeadsUpManager.isTrackingHeadsUp() || expandedHeight > mHeadsUpStartHeight);
+        if (goingBetweenClosedShadeAndExpandedQs && qsShouldExpandWithHeadsUp) {
             float qsExpansionFraction;
             if (mSplitShadeEnabled) {
                 qsExpansionFraction = 1;
@@ -3470,7 +3440,7 @@
             }
             float targetHeight = mQsMinExpansionHeight
                     + qsExpansionFraction * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
-            setQsExpansion(targetHeight);
+            setQsExpansionHeight(targetHeight);
         }
         updateExpandedHeight(expandedHeight);
         updateHeader();
@@ -3486,11 +3456,7 @@
         boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown;
         if (mPanelExpanded != isExpanded) {
             mPanelExpanded = isExpanded;
-
-            mHeadsUpManager.setIsPanelExpanded(isExpanded);
-            mStatusBarTouchableRegionManager.setPanelExpanded(isExpanded);
-            mCentralSurfaces.setPanelExpanded(isExpanded);
-
+            mShadeExpansionStateManager.onShadeExpansionFullyChanged(isExpanded);
             if (!isExpanded && mQs != null && mQs.isCustomizing()) {
                 mQs.closeCustomizer();
             }
@@ -3514,7 +3480,7 @@
         }
     }
 
-    private int calculatePanelHeightQsExpanded() {
+    int calculatePanelHeightQsExpanded() {
         float
                 notificationHeight =
                 mNotificationStackScrollLayoutController.getHeight()
@@ -3572,9 +3538,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();
@@ -3583,13 +3547,17 @@
     }
 
     private float getHeaderTranslation() {
+        if (mSplitShadeEnabled) {
+            // in split shade QS don't translate, just (un)squish and overshoot
+            return 0;
+        }
         if (mBarState == KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
             return -mQs.getQsMinExpansionHeight();
         }
         float appearAmount = mNotificationStackScrollLayoutController
                 .calculateAppearFraction(mExpandedHeight);
         float startHeight = -mQsExpansionHeight;
-        if (!mSplitShadeEnabled && mBarState == StatusBarState.SHADE) {
+        if (mBarState == StatusBarState.SHADE) {
             // Small parallax as we pull down and clip QS
             startHeight = -mQsExpansionHeight * QS_PARALLAX_AMOUNT;
         }
@@ -3717,7 +3685,7 @@
                                 if (mAnimateAfterExpanding) {
                                     notifyExpandingStarted();
                                     beginJankMonitoring();
-                                    fling(0, true /* expand */);
+                                    fling(0  /* expand */);
                                 } else {
                                     setExpandedFraction(1f);
                                 }
@@ -3754,11 +3722,29 @@
 
     }
 
+    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();
         mTracking = true;
-        mCentralSurfaces.onTrackingStarted();
+        mTrackingStartedListener.onTrackingStarted();
         notifyExpandingStarted();
         updatePanelExpansionAndVisibility();
         mScrimController.onTrackingStarted();
@@ -3773,7 +3759,6 @@
     private void onTrackingStopped(boolean expand) {
         mFalsingCollector.onTrackingStopped();
         mTracking = false;
-        mCentralSurfaces.onTrackingStopped(expand);
         updatePanelExpansionAndVisibility();
         if (expand) {
             mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */,
@@ -3788,7 +3773,7 @@
 
     private void updateMaxHeadsUpTranslation() {
         mNotificationStackScrollLayoutController.setHeadsUpBoundaries(
-                getHeight(), mNavigationBarBottomHeight);
+                mView.getHeight(), mNavigationBarBottomHeight);
     }
 
     @VisibleForTesting
@@ -3816,14 +3801,16 @@
 
     @VisibleForTesting
     void onUnlockHintFinished() {
-        mCentralSurfaces.onHintFinished();
+        // Delay the reset a bit so the user can read the text.
+        mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS);
         mScrimController.setExpansionAffectsAlpha(true);
         mNotificationStackScrollLayoutController.setUnlockHintRunning(false);
     }
 
     @VisibleForTesting
     void onUnlockHintStarted() {
-        mCentralSurfaces.onUnlockHintStarted();
+        mFalsingCollector.onUnlockHintStarted();
+        mKeyguardIndicationController.showActionToUnlock();
         mScrimController.setExpansionAffectsAlpha(false);
         mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
     }
@@ -3833,7 +3820,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.
@@ -3876,10 +3864,6 @@
         mQs.closeCustomizer();
     }
 
-    public boolean isLaunchTransitionFinished() {
-        return mIsLaunchTransitionFinished;
-    }
-
     public void setIsLaunchAnimationRunning(boolean running) {
         boolean wasRunning = mIsLaunchAnimationRunning;
         mIsLaunchAnimationRunning = running;
@@ -3889,10 +3873,9 @@
     }
 
     @VisibleForTesting
-    void setIsClosing(boolean isClosing) {
-        boolean wasClosing = isClosing();
-        mClosing = isClosing;
-        if (wasClosing != isClosing) {
+    void setClosing(boolean isClosing) {
+        if (mClosing != isClosing) {
+            mClosing = isClosing;
             mShadeExpansionStateManager.notifyPanelCollapsingChanged(isClosing);
         }
         mAmbientState.setIsClosing(isClosing);
@@ -3905,10 +3888,6 @@
         }
     }
 
-    public boolean isDozing() {
-        return mDozing;
-    }
-
     public void setQsScrimEnabled(boolean qsScrimEnabled) {
         boolean changed = mQsScrimEnabled != qsScrimEnabled;
         mQsScrimEnabled = qsScrimEnabled;
@@ -3921,7 +3900,7 @@
         mKeyguardStatusViewController.dozeTimeTick();
     }
 
-    private boolean onMiddleClicked() {
+    private void onMiddleClicked() {
         switch (mBarState) {
             case KEYGUARD:
                 if (!mDozingOnDown) {
@@ -3943,14 +3922,12 @@
                         startUnlockHintAnimation();
                     }
                 }
-                return true;
+                break;
             case StatusBarState.SHADE_LOCKED:
                 if (!mQsExpanded) {
                     mStatusBarStateController.setState(KEYGUARD);
                 }
-                return true;
-            default:
-                return true;
+                break;
         }
     }
 
@@ -3986,6 +3963,7 @@
 
     public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
         mHeadsUpManager = headsUpManager;
+        mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
         mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
                 mNotificationStackScrollLayoutController.getHeadsUpCallback(),
                 NotificationPanelViewController.this);
@@ -4000,7 +3978,7 @@
     }
 
     private void onClosingFinished() {
-        mCentralSurfaces.onClosingFinished();
+        mOpenCloseListener.onClosingFinished();
         setClosingWithAlphaFadeout(false);
         mMediaHierarchyManager.closeGuts();
     }
@@ -4024,17 +4002,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;
@@ -4045,14 +4015,15 @@
         }
     }
 
+    public int getBarState() {
+        return mBarState;
+    }
+
     private boolean isOnKeyguard() {
         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;
@@ -4093,42 +4064,6 @@
                 && mBarState == StatusBarState.SHADE;
     }
 
-    /** Launches the camera. */
-    public void launchCamera(int source) {
-        if (!isFullyCollapsed()) {
-            setLaunchingAffordance(true);
-        }
-
-        mCameraGestureHelper.launchCamera(source);
-    }
-
-    public void onAffordanceLaunchEnded() {
-        setLaunchingAffordance(false);
-    }
-
-    /**
-     * Set whether we are currently launching an affordance. This is currently only set when
-     * launched via a 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.
-     */
-    public boolean isLaunchingAffordanceWithPreview() {
-        return mLaunchingAffordance;
-    }
-
-    /**
-     * Whether the camera application can be launched for the camera launch gesture.
-     */
-    public boolean canCameraGestureBeLaunched() {
-        return mCameraGestureHelper.canCameraGestureBeLaunched(mBarState);
-    }
-
     public boolean hideStatusBarIconsWhenExpanded() {
         if (mIsLaunchAnimationRunning) {
             return mHideIconsDuringLaunchAnimation;
@@ -4137,22 +4072,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;
@@ -4169,7 +4101,7 @@
                         final int height = bottom - top;
                         final int oldHeight = oldBottom - oldTop;
                         if (height != oldHeight) {
-                            mHeightListener.onQsHeightChanged();
+                            onQsHeightChanged();
                         }
                     });
             mQs.setCollapsedMediaVisibilityChangedListener((visible) -> {
@@ -4182,7 +4114,7 @@
             mLockscreenShadeTransitionController.setQS(mQs);
             mShadeTransitionController.setQs(mQs);
             mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader());
-            mQs.setScrollListener(mScrollListener);
+            mQs.setScrollListener(mQsScrollListener);
             updateQsExpansion();
         }
 
@@ -4195,7 +4127,7 @@
                 mQs = null;
             }
         }
-    };
+    }
 
     private void animateNextNotificationBounds(long duration, long delay) {
         mAnimateNextNotificationBounds = true;
@@ -4285,13 +4217,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;
     }
@@ -4320,11 +4246,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,
@@ -4341,9 +4264,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();
@@ -4385,14 +4306,11 @@
                     public void onAnimationEnd(Animator animation) {
                         endAction.run();
                     }
-                }).setUpdateListener(anim -> {
-                    mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
-                }).start();
+                }).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();
@@ -4411,67 +4329,191 @@
         mBlockingExpansionForCurrentTouch = mTracking;
     }
 
+    @Override
     public void dump(PrintWriter pw, String[] args) {
-        pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
-                        + " tracking=%s timeAnim=%s%s "
-                        + "touchDisabled=%s" + "]",
-                this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(),
-                mClosing ? "T" : "f", mTracking ? "T" : "f", mHeightAnimator,
-                ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""),
-                mTouchDisabled ? "T" : "f"));
+        pw.println(TAG + ":");
         IndentingPrintWriter ipw = asIndenting(pw);
         ipw.increaseIndent();
+
+        ipw.print("mDownTime="); ipw.println(mDownTime);
+        ipw.print("mTouchSlopExceededBeforeDown="); ipw.println(mTouchSlopExceededBeforeDown);
+        ipw.print("mIsLaunchAnimationRunning="); ipw.println(mIsLaunchAnimationRunning);
+        ipw.print("mOverExpansion="); ipw.println(mOverExpansion);
+        ipw.print("mExpandedHeight="); ipw.println(mExpandedHeight);
+        ipw.print("mTracking="); ipw.println(mTracking);
+        ipw.print("mHintAnimationRunning="); ipw.println(mHintAnimationRunning);
+        ipw.print("mExpanding="); ipw.println(mExpanding);
+        ipw.print("mSplitShadeEnabled="); ipw.println(mSplitShadeEnabled);
+        ipw.print("mKeyguardNotificationBottomPadding=");
+        ipw.println(mKeyguardNotificationBottomPadding);
+        ipw.print("mKeyguardNotificationTopPadding="); ipw.println(mKeyguardNotificationTopPadding);
+        ipw.print("mMaxAllowedKeyguardNotifications=");
+        ipw.println(mMaxAllowedKeyguardNotifications);
+        ipw.print("mAnimateNextPositionUpdate="); ipw.println(mAnimateNextPositionUpdate);
+        ipw.print("mQuickQsHeaderHeight="); ipw.println(mQuickQsHeaderHeight);
+        ipw.print("mQsTrackingPointer="); ipw.println(mQsTrackingPointer);
+        ipw.print("mQsTracking="); ipw.println(mQsTracking);
+        ipw.print("mConflictingQsExpansionGesture="); ipw.println(mConflictingQsExpansionGesture);
+        ipw.print("mPanelExpanded="); ipw.println(mPanelExpanded);
+        ipw.print("mQsExpanded="); ipw.println(mQsExpanded);
+        ipw.print("mQsExpandedWhenExpandingStarted="); ipw.println(mQsExpandedWhenExpandingStarted);
+        ipw.print("mQsFullyExpanded="); ipw.println(mQsFullyExpanded);
+        ipw.print("mKeyguardShowing="); ipw.println(mKeyguardShowing);
+        ipw.print("mKeyguardQsUserSwitchEnabled="); ipw.println(mKeyguardQsUserSwitchEnabled);
+        ipw.print("mKeyguardUserSwitcherEnabled="); ipw.println(mKeyguardUserSwitcherEnabled);
+        ipw.print("mDozing="); ipw.println(mDozing);
+        ipw.print("mDozingOnDown="); ipw.println(mDozingOnDown);
+        ipw.print("mBouncerShowing="); ipw.println(mBouncerShowing);
+        ipw.print("mBarState="); ipw.println(mBarState);
+        ipw.print("mInitialHeightOnTouch="); ipw.println(mInitialHeightOnTouch);
+        ipw.print("mInitialTouchX="); ipw.println(mInitialTouchX);
+        ipw.print("mInitialTouchY="); ipw.println(mInitialTouchY);
+        ipw.print("mQsExpansionHeight="); ipw.println(mQsExpansionHeight);
+        ipw.print("mQsMinExpansionHeight="); ipw.println(mQsMinExpansionHeight);
+        ipw.print("mQsMaxExpansionHeight="); ipw.println(mQsMaxExpansionHeight);
+        ipw.print("mQsPeekHeight="); ipw.println(mQsPeekHeight);
+        ipw.print("mStackScrollerOverscrolling="); ipw.println(mStackScrollerOverscrolling);
+        ipw.print("mQsExpansionFromOverscroll="); ipw.println(mQsExpansionFromOverscroll);
+        ipw.print("mLastOverscroll="); ipw.println(mLastOverscroll);
+        ipw.print("mQsExpansionEnabledPolicy="); ipw.println(mQsExpansionEnabledPolicy);
+        ipw.print("mQsExpansionEnabledAmbient="); ipw.println(mQsExpansionEnabledAmbient);
+        ipw.print("mStatusBarMinHeight="); ipw.println(mStatusBarMinHeight);
+        ipw.print("mStatusBarHeaderHeightKeyguard="); ipw.println(mStatusBarHeaderHeightKeyguard);
+        ipw.print("mOverStretchAmount="); ipw.println(mOverStretchAmount);
+        ipw.print("mDownX="); ipw.println(mDownX);
+        ipw.print("mDownY="); ipw.println(mDownY);
+        ipw.print("mDisplayTopInset="); ipw.println(mDisplayTopInset);
+        ipw.print("mDisplayRightInset="); ipw.println(mDisplayRightInset);
+        ipw.print("mLargeScreenShadeHeaderHeight="); ipw.println(mLargeScreenShadeHeaderHeight);
+        ipw.print("mSplitShadeNotificationsScrimMarginBottom=");
+        ipw.println(mSplitShadeNotificationsScrimMarginBottom);
+        ipw.print("mIsExpanding="); ipw.println(mIsExpanding);
+        ipw.print("mQsExpandImmediate="); ipw.println(mQsExpandImmediate);
+        ipw.print("mTwoFingerQsExpandPossible="); ipw.println(mTwoFingerQsExpandPossible);
+        ipw.print("mHeaderDebugInfo="); ipw.println(mHeaderDebugInfo);
+        ipw.print("mQsAnimatorExpand="); ipw.println(mQsAnimatorExpand);
+        ipw.print("mQsScrimEnabled="); ipw.println(mQsScrimEnabled);
+        ipw.print("mQsTouchAboveFalsingThreshold="); ipw.println(mQsTouchAboveFalsingThreshold);
+        ipw.print("mQsFalsingThreshold="); ipw.println(mQsFalsingThreshold);
+        ipw.print("mHeadsUpStartHeight="); ipw.println(mHeadsUpStartHeight);
+        ipw.print("mListenForHeadsUp="); ipw.println(mListenForHeadsUp);
+        ipw.print("mNavigationBarBottomHeight="); ipw.println(mNavigationBarBottomHeight);
+        ipw.print("mExpandingFromHeadsUp="); ipw.println(mExpandingFromHeadsUp);
+        ipw.print("mCollapsedOnDown="); ipw.println(mCollapsedOnDown);
+        ipw.print("mClosingWithAlphaFadeOut="); ipw.println(mClosingWithAlphaFadeOut);
+        ipw.print("mHeadsUpAnimatingAway="); ipw.println(mHeadsUpAnimatingAway);
+        ipw.print("mShowIconsWhenExpanded="); ipw.println(mShowIconsWhenExpanded);
+        ipw.print("mIndicationBottomPadding="); ipw.println(mIndicationBottomPadding);
+        ipw.print("mAmbientIndicationBottomPadding="); ipw.println(mAmbientIndicationBottomPadding);
+        ipw.print("mIsFullWidth="); ipw.println(mIsFullWidth);
+        ipw.print("mBlockingExpansionForCurrentTouch=");
+        ipw.println(mBlockingExpansionForCurrentTouch);
+        ipw.print("mExpectingSynthesizedDown="); ipw.println(mExpectingSynthesizedDown);
+        ipw.print("mLastEventSynthesizedDown="); ipw.println(mLastEventSynthesizedDown);
+        ipw.print("mInterpolatedDarkAmount="); ipw.println(mInterpolatedDarkAmount);
+        ipw.print("mLinearDarkAmount="); ipw.println(mLinearDarkAmount);
+        ipw.print("mPulsing="); ipw.println(mPulsing);
+        ipw.print("mHideIconsDuringLaunchAnimation="); ipw.println(mHideIconsDuringLaunchAnimation);
+        ipw.print("mStackScrollerMeasuringPass="); ipw.println(mStackScrollerMeasuringPass);
+        ipw.print("mPanelAlpha="); ipw.println(mPanelAlpha);
+        ipw.print("mBottomAreaShadeAlpha="); ipw.println(mBottomAreaShadeAlpha);
+        ipw.print("mHeadsUpInset="); ipw.println(mHeadsUpInset);
+        ipw.print("mHeadsUpPinnedMode="); ipw.println(mHeadsUpPinnedMode);
+        ipw.print("mAllowExpandForSmallExpansion="); ipw.println(mAllowExpandForSmallExpansion);
+        ipw.print("mLockscreenNotificationQSPadding=");
+        ipw.println(mLockscreenNotificationQSPadding);
+        ipw.print("mTransitioningToFullShadeProgress=");
+        ipw.println(mTransitioningToFullShadeProgress);
+        ipw.print("mTransitionToFullShadeQSPosition=");
+        ipw.println(mTransitionToFullShadeQSPosition);
+        ipw.print("mDistanceForQSFullShadeTransition=");
+        ipw.println(mDistanceForQSFullShadeTransition);
+        ipw.print("mQsTranslationForFullShadeTransition=");
+        ipw.println(mQsTranslationForFullShadeTransition);
+        ipw.print("mMaxOverscrollAmountForPulse="); ipw.println(mMaxOverscrollAmountForPulse);
+        ipw.print("mAnimateNextNotificationBounds="); ipw.println(mAnimateNextNotificationBounds);
+        ipw.print("mNotificationBoundsAnimationDelay=");
+        ipw.println(mNotificationBoundsAnimationDelay);
+        ipw.print("mNotificationBoundsAnimationDuration=");
+        ipw.println(mNotificationBoundsAnimationDuration);
+        ipw.print("mIsPanelCollapseOnQQS="); ipw.println(mIsPanelCollapseOnQQS);
+        ipw.print("mAnimatingQS="); ipw.println(mAnimatingQS);
+        ipw.print("mIsQsTranslationResetAnimator="); ipw.println(mIsQsTranslationResetAnimator);
+        ipw.print("mIsPulseExpansionResetAnimator="); ipw.println(mIsPulseExpansionResetAnimator);
+        ipw.print("mKeyguardOnlyContentAlpha="); ipw.println(mKeyguardOnlyContentAlpha);
+        ipw.print("mKeyguardOnlyTransitionTranslationY=");
+        ipw.println(mKeyguardOnlyTransitionTranslationY);
+        ipw.print("mUdfpsMaxYBurnInOffset="); ipw.println(mUdfpsMaxYBurnInOffset);
+        ipw.print("mIsGestureNavigation="); ipw.println(mIsGestureNavigation);
+        ipw.print("mOldLayoutDirection="); ipw.println(mOldLayoutDirection);
+        ipw.print("mScrimCornerRadius="); ipw.println(mScrimCornerRadius);
+        ipw.print("mScreenCornerRadius="); ipw.println(mScreenCornerRadius);
+        ipw.print("mQSAnimatingHiddenFromCollapsed="); ipw.println(mQSAnimatingHiddenFromCollapsed);
+        ipw.print("mUseLargeScreenShadeHeader="); ipw.println(mUseLargeScreenShadeHeader);
+        ipw.print("mEnableQsClipping="); ipw.println(mEnableQsClipping);
+        ipw.print("mQsClipTop="); ipw.println(mQsClipTop);
+        ipw.print("mQsClipBottom="); ipw.println(mQsClipBottom);
+        ipw.print("mQsVisible="); ipw.println(mQsVisible);
+        ipw.print("mMinFraction="); ipw.println(mMinFraction);
+        ipw.print("mStatusViewCentered="); ipw.println(mStatusViewCentered);
+        ipw.print("mSplitShadeFullTransitionDistance=");
+        ipw.println(mSplitShadeFullTransitionDistance);
+        ipw.print("mSplitShadeScrimTransitionDistance=");
+        ipw.println(mSplitShadeScrimTransitionDistance);
+        ipw.print("mMinExpandHeight="); ipw.println(mMinExpandHeight);
+        ipw.print("mPanelUpdateWhenAnimatorEnds="); ipw.println(mPanelUpdateWhenAnimatorEnds);
+        ipw.print("mHasVibratedOnOpen="); ipw.println(mHasVibratedOnOpen);
+        ipw.print("mFixedDuration="); ipw.println(mFixedDuration);
+        ipw.print("mPanelFlingOvershootAmount="); ipw.println(mPanelFlingOvershootAmount);
+        ipw.print("mLastGesturedOverExpansion="); ipw.println(mLastGesturedOverExpansion);
+        ipw.print("mIsSpringBackAnimation="); ipw.println(mIsSpringBackAnimation);
+        ipw.print("mSplitShadeEnabled="); ipw.println(mSplitShadeEnabled);
+        ipw.print("mHintDistance="); ipw.println(mHintDistance);
+        ipw.print("mInitialOffsetOnTouch="); ipw.println(mInitialOffsetOnTouch);
+        ipw.print("mCollapsedAndHeadsUpOnDown="); ipw.println(mCollapsedAndHeadsUpOnDown);
+        ipw.print("mExpandedFraction="); ipw.println(mExpandedFraction);
+        ipw.print("mExpansionDragDownAmountPx="); ipw.println(mExpansionDragDownAmountPx);
+        ipw.print("mPanelClosedOnDown="); ipw.println(mPanelClosedOnDown);
+        ipw.print("mHasLayoutedSinceDown="); ipw.println(mHasLayoutedSinceDown);
+        ipw.print("mUpdateFlingVelocity="); ipw.println(mUpdateFlingVelocity);
+        ipw.print("mUpdateFlingOnLayout="); ipw.println(mUpdateFlingOnLayout);
+        ipw.print("mClosing="); ipw.println(mClosing);
+        ipw.print("mTouchSlopExceeded="); ipw.println(mTouchSlopExceeded);
+        ipw.print("mTrackingPointer="); ipw.println(mTrackingPointer);
+        ipw.print("mTouchSlop="); ipw.println(mTouchSlop);
+        ipw.print("mSlopMultiplier="); ipw.println(mSlopMultiplier);
+        ipw.print("mTouchAboveFalsingThreshold="); ipw.println(mTouchAboveFalsingThreshold);
+        ipw.print("mTouchStartedInEmptyArea="); ipw.println(mTouchStartedInEmptyArea);
+        ipw.print("mMotionAborted="); ipw.println(mMotionAborted);
+        ipw.print("mUpwardsWhenThresholdReached="); ipw.println(mUpwardsWhenThresholdReached);
+        ipw.print("mAnimatingOnDown="); ipw.println(mAnimatingOnDown);
+        ipw.print("mHandlingPointerUp="); ipw.println(mHandlingPointerUp);
+        ipw.print("mInstantExpanding="); ipw.println(mInstantExpanding);
+        ipw.print("mAnimateAfterExpanding="); ipw.println(mAnimateAfterExpanding);
+        ipw.print("mIsFlinging="); ipw.println(mIsFlinging);
+        ipw.print("mViewName="); ipw.println(mViewName);
+        ipw.print("mInitialExpandY="); ipw.println(mInitialExpandY);
+        ipw.print("mInitialExpandX="); ipw.println(mInitialExpandX);
+        ipw.print("mTouchDisabled="); ipw.println(mTouchDisabled);
+        ipw.print("mInitialTouchFromKeyguard="); ipw.println(mInitialTouchFromKeyguard);
+        ipw.print("mNextCollapseSpeedUpFactor="); ipw.println(mNextCollapseSpeedUpFactor);
+        ipw.print("mGestureWaitForTouchSlop="); ipw.println(mGestureWaitForTouchSlop);
+        ipw.print("mIgnoreXTouchSlop="); ipw.println(mIgnoreXTouchSlop);
+        ipw.print("mExpandLatencyTracking="); ipw.println(mExpandLatencyTracking);
+        ipw.print("mExpandLatencyTracking="); ipw.println(mExpandLatencyTracking);
         ipw.println("gestureExclusionRect:" + calculateGestureExclusionRect());
-        ipw.println("applyQSClippingImmediately: top(" + mQsClipTop + ") bottom(" + mQsClipBottom
-                + ")");
-        ipw.println("qsVisible:" + mQsVisible);
         new DumpsysTableLogger(
                 TAG,
                 NPVCDownEventState.TABLE_HEADERS,
                 mLastDownEvents.toList()
         ).printTableData(ipw);
-        ipw.decreaseIndent();
-        if (mKeyguardStatusBarViewController != null) {
-            mKeyguardStatusBarViewController.dump(pw, args);
-        }
     }
 
-    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();
     }
@@ -4488,16 +4530,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.
      *
@@ -4505,9 +4537,13 @@
      */
     public void initDependencies(
             CentralSurfaces centralSurfaces,
+            GestureRecorder recorder,
             Runnable hideExpandedRunnable,
             NotificationShelfController notificationShelfController) {
-        setCentralSurfaces(centralSurfaces);
+        // TODO(b/254859580): this can be injected.
+        mCentralSurfaces = centralSurfaces;
+
+        mGestureRecorder = recorder;
         mHideExpandedRunnable = hideExpandedRunnable;
         mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
         mNotificationShelfController = notificationShelfController;
@@ -4515,10 +4551,6 @@
         updateMaxDisplayedNotifications(true);
     }
 
-    public void setAlpha(float alpha) {
-        mView.setAlpha(alpha);
-    }
-
     public void resetTranslation() {
         mView.setTranslationX(0f);
     }
@@ -4537,32 +4569,24 @@
         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;
     }
 
-    public void onThemeChanged() {
-        mConfigurationListener.onThemeChanged();
+    public String getHeaderDebugInfo() {
+        return mHeaderDebugInfo;
     }
 
-    private OnLayoutChangeListener createLayoutChangeListener() {
-        return new OnLayoutChangeListener();
+    public void onThemeChanged() {
+        mConfigurationListener.onThemeChanged();
     }
 
     @VisibleForTesting
@@ -4570,59 +4594,6 @@
         return new TouchHandler();
     }
 
-    private final PhoneStatusBarView.TouchEventHandler mStatusBarViewTouchEventHandler =
-            new PhoneStatusBarView.TouchEventHandler() {
-                @Override
-                public void onInterceptTouchEvent(MotionEvent event) {
-                    mCentralSurfaces.onTouchEvent(event);
-                }
-
-                @Override
-                public boolean handleTouchEvent(MotionEvent event) {
-                    mCentralSurfaces.onTouchEvent(event);
-
-                    // TODO(b/202981994): Move the touch debugging in this method to a central
-                    //  location. (Right now, it's split between CentralSurfaces and here.)
-
-                    // If panels aren't enabled, ignore the gesture and don't pass it down to the
-                    // panel view.
-                    if (!mCommandQueue.panelsEnabled()) {
-                        if (event.getAction() == MotionEvent.ACTION_DOWN) {
-                            Log.v(
-                                    TAG,
-                                    String.format(
-                                            "onTouchForwardedFromStatusBar: "
-                                                    + "panel disabled, ignoring touch at (%d,%d)",
-                                            (int) event.getX(),
-                                            (int) event.getY()
-                                    )
-                            );
-                        }
-                        return false;
-                    }
-
-                    // If the view that would receive the touch is disabled, just have status bar
-                    // eat the gesture.
-                    if (event.getAction() == MotionEvent.ACTION_DOWN && !mView.isEnabled()) {
-                        Log.v(TAG,
-                                String.format(
-                                        "onTouchForwardedFromStatusBar: "
-                                                + "panel view disabled, eating touch at (%d,%d)",
-                                        (int) event.getX(),
-                                        (int) event.getY()
-                                )
-                        );
-                        return true;
-                    }
-
-                    return mView.dispatchTouchEvent(event);
-                }
-            };
-
-    private OnConfigurationChangedListener createOnConfigurationChangedListener() {
-        return new OnConfigurationChangedListener();
-    }
-
     public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
         return mNotificationStackScrollLayoutController;
     }
@@ -4663,13 +4634,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="
@@ -4681,8 +4646,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
@@ -4729,8 +4696,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
@@ -4748,9 +4713,8 @@
      * 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.
+     *                         instead being opened programmatically (such as by the open panel
+     *                         gesture), we always play haptic.
      */
     private void maybeVibrateOnOpening(boolean openingWithTouch) {
         if (mVibrateOnOpening) {
@@ -4825,7 +4789,6 @@
             }
 
             mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
-                    mCentralSurfaces.isFalsingThresholdNeeded(),
                     mCentralSurfaces.isWakeUpComingFromTouch());
             // Log collapse gesture if on lock screen.
             if (!expand && onKeyguard) {
@@ -4847,10 +4810,10 @@
                 mUpdateFlingVelocity = vel;
             }
         } else if (!mCentralSurfaces.isBouncerShowing()
-                && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
+                && !mStatusBarKeyguardViewManager.isShowingAlternateBouncer()
                 && !mKeyguardStateController.isKeyguardGoingAway()) {
-            boolean expands = onEmptySpaceClick();
-            onTrackingStopped(expands);
+            onEmptySpaceClick();
+            onTrackingStopped(true);
         }
         mVelocityTracker.clear();
     }
@@ -4862,7 +4825,7 @@
 
     private void endClosing() {
         if (mClosing) {
-            setIsClosing(false);
+            setClosing(false);
             onClosingFinished();
         }
     }
@@ -4874,9 +4837,6 @@
      */
     private boolean isFalseTouch(float x, float y,
             @Classifier.InteractionType int interactionType) {
-        if (!mCentralSurfaces.isFalsingThresholdNeeded()) {
-            return false;
-        }
         if (mFalsingManager.isClassifierEnabled()) {
             return mFalsingManager.isFalseTouch(interactionType);
         }
@@ -4897,7 +4857,7 @@
             boolean expandBecauseOfFalsing) {
         float target = expand ? getMaxPanelHeight() : 0;
         if (!expand) {
-            setIsClosing(true);
+            setClosing(true);
         }
         flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
     }
@@ -4932,13 +4892,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);
     }
 
@@ -4978,7 +4934,7 @@
             float maxPanelHeight = getMaxPanelTransitionDistance();
             if (mHeightAnimator == null) {
                 // Split shade has its own overscroll logic
-                if (mTracking && !mInSplitShade) {
+                if (mTracking && !mSplitShadeEnabled) {
                     float overExpansionPixels = Math.max(0, h - maxPanelHeight);
                     setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
                 }
@@ -5025,17 +4981,16 @@
         setExpandedHeight(getMaxPanelTransitionDistance() * frac);
     }
 
-    @VisibleForTesting
     float getExpandedHeight() {
         return mExpandedHeight;
     }
 
-    public float getExpandedFraction() {
+    private float getExpandedFraction() {
         return mExpandedFraction;
     }
 
     public boolean isFullyExpanded() {
-        return mExpandedHeight >= getMaxPanelHeight();
+        return mExpandedHeight >= getMaxPanelTransitionDistance();
     }
 
     public boolean isFullyCollapsed() {
@@ -5046,10 +5001,6 @@
         return mClosing || mIsLaunchAnimationRunning;
     }
 
-    public boolean isFlinging() {
-        return mIsFlinging;
-    }
-
     public boolean isTracking() {
         return mTracking;
     }
@@ -5196,8 +5147,7 @@
      */
     public void updatePanelExpansionAndVisibility() {
         mShadeExpansionStateManager.onPanelExpansionChanged(
-                mExpandedFraction, isExpanded(),
-                mTracking, mExpansionDragDownAmountPx);
+                mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
         updateVisibility();
     }
 
@@ -5210,16 +5160,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
@@ -5236,10 +5181,40 @@
 
     /** 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;
     }
 
+    /** */
+    public boolean postToView(Runnable action) {
+        return mView.post(action);
+    }
+
+    /** */
+    public boolean sendInterceptTouchEventToView(MotionEvent event) {
+        return mView.onInterceptTouchEvent(event);
+    }
+
+    /** */
+    public boolean sendTouchEventToView(MotionEvent event) {
+        return mView.dispatchTouchEvent(event);
+    }
+
+    /** */
+    public void requestLayoutOnView() {
+        mView.requestLayout();
+    }
+
+    /** */
+    public void resetViewAlphas() {
+        ViewGroupFadeHelper.reset(mView);
+    }
+
+    /** */
+    public boolean isViewEnabled() {
+        return mView.isEnabled();
+    }
+
     private void beginJankMonitoring() {
         if (mInteractionJankMonitor == null) {
             return;
@@ -5276,12 +5251,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;
             }
@@ -5305,26 +5279,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) {
@@ -5341,7 +5311,7 @@
             mQsExpansionFromOverscroll = rounded != 0f;
             mLastOverscroll = rounded;
             updateQsState();
-            setQsExpansion(mQsMinExpansionHeight + rounded);
+            setQsExpansionHeight(mQsMinExpansionHeight + rounded);
         }
 
         @Override
@@ -5358,7 +5328,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, () -> {
@@ -5368,27 +5338,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) {
@@ -5428,32 +5387,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)
@@ -5470,27 +5428,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();
@@ -5551,18 +5508,27 @@
                 }
             } 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
+                if (mSplitShadeEnabled && oldState == SHADE && statusBarState == KEYGUARD) {
+                    // user can go to keyguard from different shade states and closing animation
+                    // may not fully run - we always want to make sure we close QS when that happens
+                    // as we never need QS open in fresh keyguard state
+                    closeQs();
+                }
                 final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
                         && statusBarState == KEYGUARD
                         && mScreenOffAnimationController.isKeyguardShowDelayed();
                 if (!animatingUnlockedShadeToKeyguard) {
                     // Only make the status bar visible if we're not animating the screen off, since
                     // we only want to be showing the clock/notifications during the animation.
-                    mShadeLog.v("Updating keyguard status bar state to "
-                            + (keyguardShowing ? "visible" : "invisible"));
+                    if (keyguardShowing) {
+                        mShadeLog.v("Updating keyguard status bar state to visible");
+                    } else {
+                        mShadeLog.v("Updating keyguard status bar state to invisible");
+                    }
                     mKeyguardStatusBarViewController.updateViewState(
                             /* alpha= */ 1f,
                             keyguardShowing ? View.VISIBLE : View.INVISIBLE);
@@ -5646,21 +5612,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);
@@ -5675,16 +5639,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) {
@@ -5693,7 +5657,7 @@
             mHasLayoutedSinceDown = true;
             if (mUpdateFlingOnLayout) {
                 abortAnimations();
-                fling(mUpdateFlingVelocity, true /* expands */);
+                fling(mUpdateFlingVelocity);
                 mUpdateFlingOnLayout = false;
             }
             updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount());
@@ -5720,21 +5684,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());
             }
@@ -5760,102 +5721,17 @@
         }
     }
 
-    private class DebugDrawable extends Drawable {
+    @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;
 
-        private final Set<Integer> mDebugTextUsedYPositions = new HashSet<>();
-        private final Paint mDebugPaint = new Paint();
-
-        @Override
-        public void draw(@androidx.annotation.NonNull @NonNull Canvas canvas) {
-            mDebugTextUsedYPositions.clear();
-
-            mDebugPaint.setColor(Color.RED);
-            mDebugPaint.setStrokeWidth(2);
-            mDebugPaint.setStyle(Paint.Style.STROKE);
-            mDebugPaint.setTextSize(24);
-            if (mHeaderDebugInfo != null) canvas.drawText(mHeaderDebugInfo, 50, 100, mDebugPaint);
-
-            drawDebugInfo(canvas, getMaxPanelHeight(), Color.RED, "getMaxPanelHeight()");
-            drawDebugInfo(canvas, (int) getExpandedHeight(), Color.BLUE, "getExpandedHeight()");
-            drawDebugInfo(canvas, calculatePanelHeightQsExpanded(), Color.GREEN,
-                    "calculatePanelHeightQsExpanded()");
-            drawDebugInfo(canvas, calculatePanelHeightShade(), Color.YELLOW,
-                    "calculatePanelHeightShade()");
-            drawDebugInfo(canvas, (int) calculateNotificationsTopPadding(), Color.MAGENTA,
-                    "calculateNotificationsTopPadding()");
-            drawDebugInfo(canvas, mClockPositionResult.clockY, Color.GRAY,
-                    "mClockPositionResult.clockY");
-            drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY,
-                    "mLockIconViewController.getTop()");
-
-            if (mKeyguardShowing) {
-                // Notifications have the space between those two lines.
-                drawDebugInfo(canvas,
-                        mNotificationStackScrollLayoutController.getTop() +
-                                (int) mKeyguardNotificationTopPadding,
-                        Color.RED,
-                        "NSSL.getTop() + mKeyguardNotificationTopPadding");
-
-                drawDebugInfo(canvas, mNotificationStackScrollLayoutController.getBottom() -
-                                (int) mKeyguardNotificationBottomPadding,
-                        Color.RED,
-                        "NSSL.getBottom() - mKeyguardNotificationBottomPadding");
-            }
-
-            mDebugPaint.setColor(Color.CYAN);
-            canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(),
-                    mNotificationStackScrollLayoutController.getTopPadding(), mDebugPaint);
-        }
-
-        private void drawDebugInfo(Canvas canvas, int y, int color, String label) {
-            mDebugPaint.setColor(color);
-            canvas.drawLine(/* startX= */ 0, /* startY= */ y, /* stopX= */ mView.getWidth(),
-                    /* stopY= */ y, mDebugPaint);
-            canvas.drawText(label + " = " + y + "px", /* x= */ 0,
-                    /* y= */ computeDebugYTextPosition(y), mDebugPaint);
-        }
-
-        private int computeDebugYTextPosition(int lineY) {
-            if (lineY - mDebugPaint.getTextSize() < 0) {
-                // Avoiding drawing out of bounds
-                lineY += mDebugPaint.getTextSize();
-            }
-            int textY = lineY;
-            while (mDebugTextUsedYPositions.contains(textY)) {
-                textY = (int) (textY + mDebugPaint.getTextSize());
-            }
-            mDebugTextUsedYPositions.add(textY);
-            return textY;
-        }
-
-        @Override
-        public void setAlpha(int alpha) {
-
-        }
-
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) {
-
-        }
-
-        @Override
-        public int getOpacity() {
-            return PixelFormat.UNKNOWN;
-        }
-    }
-
-    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;
-
-            mNavigationBarBottomHeight = insets.getStableInsetBottom();
-            updateMaxHeadsUpTranslation();
-            return insets;
-        }
+        mNavigationBarBottomHeight = insets.getStableInsetBottom();
+        updateMaxHeadsUpTranslation();
+        return insets;
     }
 
     /** Removes any pending runnables that would collapse the panel. */
@@ -5863,9 +5739,6 @@
         mView.removeCallbacks(mMaybeHideExpandedRunnable);
     }
 
-    @PanelState
-    private int mCurrentPanelState = STATE_CLOSED;
-
     private void onPanelStateChanged(@PanelState int state) {
         updateQSExpansionEnabledAmbient();
 
@@ -5880,7 +5753,7 @@
             if (mSplitShadeEnabled && !isOnKeyguard()) {
                 setQsExpandImmediate(true);
             }
-            mCentralSurfaces.makeExpandedVisible(false);
+            mOpenCloseListener.onOpenStarted();
         }
         if (state == STATE_CLOSED) {
             setQsExpandImmediate(false);
@@ -5891,17 +5764,17 @@
         mCurrentPanelState = state;
     }
 
-    /** Returns the handler that the status bar should forward touches to. */
-    public PhoneStatusBarView.TouchEventHandler getStatusBarTouchEventHandler() {
-        return mStatusBarViewTouchEventHandler;
-    }
-
     @VisibleForTesting
     StatusBarStateController getStatusBarStateController() {
         return mStatusBarStateController;
     }
 
     @VisibleForTesting
+    StateListener getStatusBarStateListener() {
+        return mStatusBarStateListener;
+    }
+
+    @VisibleForTesting
     boolean isHintAnimationRunning() {
         return mHintAnimationRunning;
     }
@@ -5922,6 +5795,7 @@
 
         /** @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()
@@ -5934,6 +5808,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()
@@ -5941,15 +5817,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
@@ -5980,6 +5862,9 @@
                     if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) {
                         cancelHeightAnimator();
                         mTouchSlopExceeded = true;
+                        mShadeLog.v("NotificationPanelViewController MotionEvent intercepted:"
+                                + " mAnimatingOnDown: true, mClosing: true, mHintAnimationRunning:"
+                                + " false");
                         return true;
                     }
                     mInitialExpandY = y;
@@ -6024,6 +5909,8 @@
                                 && hAbs > Math.abs(x - mInitialExpandX)) {
                             cancelHeightAnimator();
                             startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
+                            mShadeLog.v("NotificationPanelViewController MotionEvent"
+                                    + " intercepted: startExpandMotion");
                             return true;
                         }
                     }
@@ -6152,7 +6039,6 @@
              *
              * Flinging is also enabled in order to open or close the shade.
              */
-
             int pointerIndex = event.findPointerIndex(mTrackingPointer);
             if (pointerIndex < 0) {
                 pointerIndex = 0;
@@ -6168,6 +6054,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();
@@ -6214,6 +6101,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 */);
@@ -6252,6 +6143,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.
@@ -6268,15 +6160,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 };
@@ -6326,5 +6209,40 @@
             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.showPrimaryBouncer(true);
+                return true;
+            }
+            return super.performAccessibilityAction(host, action, args);
+        }
+    }
+
+    /** Listens for when touch tracking begins. */
+    interface TrackingStartedListener {
+        void onTrackingStarted();
+    }
+
+    /** Listens for when shade begins opening of finishes closing. */
+    interface OpenCloseListener {
+        /** Called when the shade finishes closing. */
+        void onClosingFinished();
+        /** Called when the shade starts opening. */
+        void onOpenStarted();
+    }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 66a22f4..8698c04 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -44,6 +44,7 @@
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManagerGlobal;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
@@ -158,6 +159,7 @@
                         SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
         configurationController.addCallback(this);
         shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
+        shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
 
         float desiredPreferredRefreshRate = context.getResources()
                 .getInteger(R.integer.config_keyguardRefreshRate);
@@ -204,6 +206,14 @@
         }
     }
 
+    @VisibleForTesting
+    void onShadeExpansionFullyChanged(Boolean isExpanded) {
+        if (mCurrentState.mPanelExpanded != isExpanded) {
+            mCurrentState.mPanelExpanded = isExpanded;
+            apply(mCurrentState);
+        }
+    }
+
     /**
      * Register a listener to monitor scrims visibility
      * @param listener A listener to monitor scrims visibility
@@ -236,7 +246,6 @@
         mLp.token = new Binder();
         mLp.gravity = Gravity.TOP;
         mLp.setFitInsetsTypes(0 /* types */);
-        mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
         mLp.setTitle("NotificationShade");
         mLp.packageName = mContext.getPackageName();
         mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -374,8 +383,6 @@
             mLpChanged.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
             mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
         }
-
-        mLpChanged.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
     }
 
     private void applyForceShowNavigationFlag(State state) {
@@ -699,15 +706,6 @@
     }
 
     @Override
-    public void setPanelExpanded(boolean isExpanded) {
-        if (mCurrentState.mPanelExpanded == isExpanded) {
-            return;
-        }
-        mCurrentState.mPanelExpanded = isExpanded;
-        apply(mCurrentState);
-    }
-
-    @Override
     public void onRemoteInputActive(boolean remoteInputActive) {
         mCurrentState.mRemoteInputActive = remoteInputActive;
         apply(mCurrentState);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index e52170e..6acf417 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade;
 
+import static android.os.Trace.TRACE_TAG_ALWAYS;
 import static android.view.WindowInsets.Type.systemBars;
 
 import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
@@ -23,6 +24,7 @@
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
 import android.annotation.LayoutRes;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
@@ -33,7 +35,9 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Trace;
 import android.util.AttributeSet;
+import android.util.Pair;
 import android.view.ActionMode;
 import android.view.DisplayCutout;
 import android.view.InputQueue;
@@ -72,6 +76,7 @@
     private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
 
     private InteractionEventHandler mInteractionEventHandler;
+    private LayoutInsetsController mLayoutInsetProvider;
 
     public NotificationShadeWindowView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -106,12 +111,10 @@
         mLeftInset = 0;
         mRightInset = 0;
         DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
-        if (displayCutout != null) {
-            mLeftInset = displayCutout.getSafeInsetLeft();
-            mRightInset = displayCutout.getSafeInsetRight();
-        }
-        mLeftInset = Math.max(insets.left, mLeftInset);
-        mRightInset = Math.max(insets.right, mRightInset);
+        Pair<Integer, Integer> pairInsets = mLayoutInsetProvider
+                .getinsets(windowInsets, displayCutout);
+        mLeftInset = pairInsets.first;
+        mRightInset = pairInsets.second;
         applyMargins();
         return windowInsets;
     }
@@ -170,6 +173,10 @@
         mInteractionEventHandler = listener;
     }
 
+    protected void setLayoutInsetsController(LayoutInsetsController provider) {
+        mLayoutInsetProvider = provider;
+    }
+
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
@@ -299,6 +306,19 @@
         return mode;
     }
 
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Trace.beginSection("NotificationShadeWindowView#onMeasure");
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        Trace.endSection();
+    }
+
+    @Override
+    public void requestLayout() {
+        Trace.instant(TRACE_TAG_ALWAYS, "NotificationShadeWindowView#requestLayout");
+        super.requestLayout();
+    }
+
     private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
         private final ActionMode.Callback mWrapped;
 
@@ -338,6 +358,18 @@
         }
     }
 
+    /**
+     * Controller responsible for calculating insets for the shade window.
+     */
+    public interface LayoutInsetsController {
+
+        /**
+         * Update the insets and calculate them accordingly.
+         */
+        Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets,
+                @Nullable DisplayCutout displayCutout);
+    }
+
     interface InteractionEventHandler {
         /**
          * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 65bd58d..d773c01 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -42,6 +42,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationInsetsController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -76,6 +77,7 @@
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final AmbientState mAmbientState;
     private final PulsingGestureListener mPulsingGestureListener;
+    private final NotificationInsetsController mNotificationInsetsController;
 
     private GestureDetector mPulsingWakeupGestureHandler;
     private View mBrightnessMirror;
@@ -111,6 +113,7 @@
             CentralSurfaces centralSurfaces,
             NotificationShadeWindowController controller,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+            NotificationInsetsController notificationInsetsController,
             AmbientState ambientState,
             PulsingGestureListener pulsingGestureListener,
             FeatureFlags featureFlags,
@@ -134,6 +137,7 @@
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mAmbientState = ambientState;
         mPulsingGestureListener = pulsingGestureListener;
+        mNotificationInsetsController = notificationInsetsController;
 
         // This view is not part of the newly inflated expanded status bar.
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -165,6 +169,7 @@
         mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
                 mPulsingGestureListener);
 
+        mView.setLayoutInsetsController(mNotificationInsetsController);
         mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
             @Override
             public Boolean handleDispatchTouchEvent(MotionEvent ev) {
@@ -284,7 +289,7 @@
                     return true;
                 }
 
-                if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+                if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
                     // capture all touches if the alt auth bouncer is showing
                     return true;
                 }
@@ -311,7 +316,7 @@
                 MotionEvent cancellation = MotionEvent.obtain(ev);
                 cancellation.setAction(MotionEvent.ACTION_CANCEL);
                 mStackScrollLayout.onInterceptTouchEvent(cancellation);
-                mNotificationPanelViewController.getView().onInterceptTouchEvent(cancellation);
+                mNotificationPanelViewController.sendInterceptTouchEventToView(cancellation);
                 cancellation.recycle();
             }
 
@@ -322,7 +327,7 @@
                     handled = !mService.isPulsing();
                 }
 
-                if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+                if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
                     // 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/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index aa610bd..a41a15d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.shade;
 
+import android.view.MotionEvent;
+
+import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -29,31 +32,32 @@
  */
 public interface ShadeController {
 
-    /**
-     * Make our window larger and the panel expanded
-     */
-    void instantExpandNotificationsPanel();
+    /** Make our window larger and the shade expanded */
+    void instantExpandShade();
 
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
-    void animateCollapsePanels();
+    /** Collapse the shade instantly with no animation. */
+    void instantCollapseShade();
 
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
-    void animateCollapsePanels(int flags);
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShade();
+
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShade(int flags);
+
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShadeForced();
+
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShadeDelayed();
 
     /**
      * Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or
-     * dismissing {@link CentralSurfaces} when on {@link StatusBarState#SHADE}.
+     * dismissing status bar when on {@link StatusBarState#SHADE}.
      */
-    void animateCollapsePanels(int flags, boolean force);
-
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
-    void animateCollapsePanels(int flags, boolean force, boolean delayed);
-
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
     void animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor);
 
     /**
-     * If the notifications panel is not fully expanded, collapse it animated.
+     * If the shade is not fully expanded, collapse it animated.
      *
      * @return Seems to always return false
      */
@@ -77,9 +81,7 @@
      */
     void addPostCollapseAction(Runnable action);
 
-    /**
-     * Run all of the runnables added by {@link #addPostCollapseAction}.
-     */
+    /** Run all of the runnables added by {@link #addPostCollapseAction}. */
     void runPostCollapseRunnables();
 
     /**
@@ -87,13 +89,51 @@
      *
      * @return true if the shade was open, else false
      */
-    boolean collapsePanel();
+    boolean collapseShade();
 
     /**
-     * If animate is true, does the same as {@link #collapsePanel()}. Otherwise, instantly collapse
-     * the panel. Post collapse runnables will be executed
+     * If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse
+     * the shade. Post collapse runnables will be executed
      *
      * @param animate true to animate the collapse, false for instantaneous collapse
      */
-    void collapsePanel(boolean animate);
+    void collapseShade(boolean animate);
+
+    /** Makes shade expanded but not visible. */
+    void makeExpandedInvisible();
+
+    /** Makes shade expanded and visible. */
+    void makeExpandedVisible(boolean force);
+
+    /** Returns whether the shade is expanded and visible. */
+    boolean isExpandedVisible();
+
+    /** Handle status bar touch event. */
+    void onStatusBarTouch(MotionEvent event);
+
+    /** Called when the shade finishes collapsing. */
+    void onClosingFinished();
+
+    /** Sets the listener for when the visibility of the shade changes. */
+    void setVisibilityListener(ShadeVisibilityListener listener);
+
+    /** */
+    void setNotificationPresenter(NotificationPresenter presenter);
+
+    /** */
+    void setNotificationShadeWindowViewController(
+            NotificationShadeWindowViewController notificationShadeWindowViewController);
+
+    /** */
+    void setNotificationPanelViewController(
+            NotificationPanelViewController notificationPanelViewController);
+
+    /** Listens for shade visibility changes. */
+    interface ShadeVisibilityListener {
+        /** Called when the visibility of the shade changes. */
+        void visibilityChanged(boolean visible);
+
+        /** Called when shade expanded and visible state changed. */
+        void expandedVisibleChanged(boolean expandedVisible);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index f389dd9..638e748 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.shade;
 
+import android.content.ComponentCallbacks2;
 import android.util.Log;
+import android.view.MotionEvent;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
@@ -27,11 +30,12 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowController;
 
 import java.util.ArrayList;
-import java.util.Optional;
 
 import javax.inject.Inject;
 
@@ -39,68 +43,81 @@
 
 /** An implementation of {@link ShadeController}. */
 @SysUISingleton
-public class ShadeControllerImpl implements ShadeController {
+public final class ShadeControllerImpl implements ShadeController {
 
     private static final String TAG = "ShadeControllerImpl";
     private static final boolean SPEW = false;
 
-    private final CommandQueue mCommandQueue;
-    private final StatusBarStateController mStatusBarStateController;
-    protected final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final int mDisplayId;
-    protected final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+
+    private final CommandQueue mCommandQueue;
+    private final KeyguardStateController mKeyguardStateController;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final StatusBarStateController mStatusBarStateController;
+    private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private final StatusBarWindowController mStatusBarWindowController;
+
     private final Lazy<AssistManager> mAssistManagerLazy;
+    private final Lazy<NotificationGutsManager> mGutsManager;
 
     private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
+    private boolean mExpandedVisible;
+
+    private NotificationPanelViewController mNotificationPanelViewController;
+    private NotificationPresenter mPresenter;
+    private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+    private ShadeVisibilityListener mShadeVisibilityListener;
+
     @Inject
     public ShadeControllerImpl(
             CommandQueue commandQueue,
+            KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
-            NotificationShadeWindowController notificationShadeWindowController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             WindowManager windowManager,
-            Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
-            Lazy<AssistManager> assistManagerLazy
+            Lazy<AssistManager> assistManagerLazy,
+            Lazy<NotificationGutsManager> gutsManager
     ) {
         mCommandQueue = commandQueue;
         mStatusBarStateController = statusBarStateController;
+        mStatusBarWindowController = statusBarWindowController;
+        mGutsManager = gutsManager;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mDisplayId = windowManager.getDefaultDisplay().getDisplayId();
-        // TODO: Remove circular reference to CentralSurfaces when possible.
-        mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+        mKeyguardStateController = keyguardStateController;
         mAssistManagerLazy = assistManagerLazy;
     }
 
     @Override
-    public void instantExpandNotificationsPanel() {
+    public void instantExpandShade() {
         // Make our window larger and the panel expanded.
-        getCentralSurfaces().makeExpandedVisible(true /* force */);
-        getNotificationPanelViewController().expand(false /* animate */);
+        makeExpandedVisible(true /* force */);
+        mNotificationPanelViewController.expand(false /* animate */);
         mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
     }
 
     @Override
-    public void animateCollapsePanels() {
-        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+    public void animateCollapseShade() {
+        animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
     }
 
     @Override
-    public void animateCollapsePanels(int flags) {
-        animateCollapsePanels(flags, false /* force */, false /* delayed */,
-                1.0f /* speedUpFactor */);
+    public void animateCollapseShade(int flags) {
+        animateCollapsePanels(flags, false, false, 1.0f);
     }
 
     @Override
-    public void animateCollapsePanels(int flags, boolean force) {
-        animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
+    public void animateCollapseShadeForced() {
+        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
     }
 
     @Override
-    public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
-        animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */);
+    public void animateCollapseShadeDelayed() {
+        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
     }
 
     @Override
@@ -111,34 +128,26 @@
             return;
         }
         if (SPEW) {
-            Log.d(TAG, "animateCollapse():"
-                    + " mExpandedVisible=" + getCentralSurfaces().isExpandedVisible()
-                    + " flags=" + flags);
+            Log.d(TAG,
+                    "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
         }
-
-        // TODO(b/62444020): remove when this bug is fixed
-        Log.v(TAG, "NotificationShadeWindow: " + getNotificationShadeWindowView()
-                + " canPanelBeCollapsed(): "
-                + getNotificationPanelViewController().canPanelBeCollapsed());
         if (getNotificationShadeWindowView() != null
-                && getNotificationPanelViewController().canPanelBeCollapsed()
+                && mNotificationPanelViewController.canPanelBeCollapsed()
                 && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
             // release focus immediately to kick off focus change transition
             mNotificationShadeWindowController.setNotificationShadeFocusable(false);
 
-            getCentralSurfaces().getNotificationShadeWindowViewController().cancelExpandHelper();
-            getNotificationPanelViewController()
-                    .collapsePanel(true /* animate */, delayed, speedUpFactor);
+            mNotificationShadeWindowViewController.cancelExpandHelper();
+            mNotificationPanelViewController.collapsePanel(true, delayed, speedUpFactor);
         }
     }
 
-
     @Override
     public boolean closeShadeIfOpen() {
-        if (!getNotificationPanelViewController().isFullyCollapsed()) {
+        if (!mNotificationPanelViewController.isFullyCollapsed()) {
             mCommandQueue.animateCollapsePanels(
                     CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
-            getCentralSurfaces().visibilityChanged(false);
+            notifyVisibilityChanged(false);
             mAssistManagerLazy.get().hideAssist();
         }
         return false;
@@ -146,21 +155,19 @@
 
     @Override
     public boolean isShadeOpen() {
-        NotificationPanelViewController controller =
-                getNotificationPanelViewController();
-        return controller.isExpanding() || controller.isFullyExpanded();
+        return mNotificationPanelViewController.isExpanding()
+                || mNotificationPanelViewController.isFullyExpanded();
     }
 
     @Override
     public void postOnShadeExpanded(Runnable executable) {
-        getNotificationPanelViewController().addOnGlobalLayoutListener(
+        mNotificationPanelViewController.addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
                     public void onGlobalLayout() {
-                        if (getCentralSurfaces().getNotificationShadeWindowView()
-                                .isVisibleToUser()) {
-                            getNotificationPanelViewController().removeOnGlobalLayoutListener(this);
-                            getNotificationPanelViewController().getView().post(executable);
+                        if (getNotificationShadeWindowView().isVisibleToUser()) {
+                            mNotificationPanelViewController.removeOnGlobalLayoutListener(this);
+                            mNotificationPanelViewController.postToView(executable);
                         }
                     }
                 });
@@ -183,12 +190,11 @@
     }
 
     @Override
-    public boolean collapsePanel() {
-        if (!getNotificationPanelViewController().isFullyCollapsed()) {
+    public boolean collapseShade() {
+        if (!mNotificationPanelViewController.isFullyCollapsed()) {
             // close the shade if it was open
-            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                    true /* force */, true /* delayed */);
-            getCentralSurfaces().visibilityChanged(false);
+            animateCollapseShadeDelayed();
+            notifyVisibilityChanged(false);
 
             return true;
         } else {
@@ -197,33 +203,154 @@
     }
 
     @Override
-    public void collapsePanel(boolean animate) {
+    public void collapseShade(boolean animate) {
         if (animate) {
-            boolean willCollapse = collapsePanel();
+            boolean willCollapse = collapseShade();
             if (!willCollapse) {
                 runPostCollapseRunnables();
             }
-        } else if (!getPresenter().isPresenterFullyCollapsed()) {
-            getCentralSurfaces().instantCollapseNotificationPanel();
-            getCentralSurfaces().visibilityChanged(false);
+        } else if (!mPresenter.isPresenterFullyCollapsed()) {
+            instantCollapseShade();
+            notifyVisibilityChanged(false);
         } else {
             runPostCollapseRunnables();
         }
     }
 
-    private CentralSurfaces getCentralSurfaces() {
-        return mCentralSurfacesOptionalLazy.get().get();
+    @Override
+    public void onStatusBarTouch(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            if (mExpandedVisible) {
+                animateCollapseShade();
+            }
+        }
     }
 
-    private NotificationPresenter getPresenter() {
-        return getCentralSurfaces().getPresenter();
+    @Override
+    public void onClosingFinished() {
+        runPostCollapseRunnables();
+        if (!mPresenter.isPresenterFullyCollapsed()) {
+            // if we set it not to be focusable when collapsing, we have to undo it when we aborted
+            // the closing
+            mNotificationShadeWindowController.setNotificationShadeFocusable(true);
+        }
     }
 
-    protected NotificationShadeWindowView getNotificationShadeWindowView() {
-        return getCentralSurfaces().getNotificationShadeWindowView();
+    @Override
+    public void instantCollapseShade() {
+        mNotificationPanelViewController.instantCollapse();
+        runPostCollapseRunnables();
     }
 
-    private NotificationPanelViewController getNotificationPanelViewController() {
-        return getCentralSurfaces().getPanelController();
+    @Override
+    public void makeExpandedVisible(boolean force) {
+        if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
+        if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
+            return;
+        }
+
+        mExpandedVisible = true;
+
+        // Expand the window to encompass the full screen in anticipation of the drag.
+        // It's only possible to do atomically because the status bar is at the top of the screen!
+        mNotificationShadeWindowController.setPanelVisible(true);
+
+        notifyVisibilityChanged(true);
+        mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
+        notifyExpandedVisibleChanged(true);
+    }
+
+    @Override
+    public void makeExpandedInvisible() {
+        if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
+
+        if (!mExpandedVisible || getNotificationShadeWindowView() == null) {
+            return;
+        }
+
+        // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
+        mNotificationPanelViewController.collapsePanel(false, false, 1.0f);
+
+        mNotificationPanelViewController.closeQs();
+
+        mExpandedVisible = false;
+        notifyVisibilityChanged(false);
+
+        // Update the visibility of notification shade and status bar window.
+        mNotificationShadeWindowController.setPanelVisible(false);
+        mStatusBarWindowController.setForceStatusBarVisible(false);
+
+        // Close any guts that might be visible
+        mGutsManager.get().closeAndSaveGuts(
+                true /* removeLeavebehind */,
+                true /* force */,
+                true /* removeControls */,
+                -1 /* x */,
+                -1 /* y */,
+                true /* resetMenu */);
+
+        runPostCollapseRunnables();
+        notifyExpandedVisibleChanged(false);
+        mCommandQueue.recomputeDisableFlags(
+                mDisplayId,
+                mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
+
+        // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
+        // the bouncer appear animation.
+        if (!mKeyguardStateController.isShowing()) {
+            WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+        }
+    }
+
+    @Override
+    public boolean isExpandedVisible() {
+        return mExpandedVisible;
+    }
+
+    @Override
+    public void setVisibilityListener(ShadeVisibilityListener listener) {
+        mShadeVisibilityListener = listener;
+    }
+
+    private void notifyVisibilityChanged(boolean visible) {
+        mShadeVisibilityListener.visibilityChanged(visible);
+    }
+
+    private void notifyExpandedVisibleChanged(boolean expandedVisible) {
+        mShadeVisibilityListener.expandedVisibleChanged(expandedVisible);
+    }
+
+    @Override
+    public void setNotificationPresenter(NotificationPresenter presenter) {
+        mPresenter = presenter;
+    }
+
+    @Override
+    public void setNotificationShadeWindowViewController(
+            NotificationShadeWindowViewController controller) {
+        mNotificationShadeWindowViewController = controller;
+    }
+
+    private NotificationShadeWindowView getNotificationShadeWindowView() {
+        return mNotificationShadeWindowViewController.getView();
+    }
+
+    @Override
+    public void setNotificationPanelViewController(
+            NotificationPanelViewController notificationPanelViewController) {
+        mNotificationPanelViewController = notificationPanelViewController;
+        mNotificationPanelViewController.setTrackingStartedListener(this::runPostCollapseRunnables);
+        mNotificationPanelViewController.setOpenCloseListener(
+                new NotificationPanelViewController.OpenCloseListener() {
+                    @Override
+                    public void onClosingFinished() {
+                        ShadeControllerImpl.this.onClosingFinished();
+                    }
+
+                    @Override
+                    public void onOpenStarted() {
+                        makeExpandedVisible(false);
+                    }
+                });
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 667392c..a1767cc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -34,6 +34,7 @@
 class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents {
 
     private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
+    private val fullExpansionListeners = CopyOnWriteArrayList<ShadeFullExpansionListener>()
     private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
     private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
     private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>()
@@ -62,6 +63,15 @@
         expansionListeners.remove(listener)
     }
 
+    fun addFullExpansionListener(listener: ShadeFullExpansionListener) {
+        fullExpansionListeners.add(listener)
+        listener.onShadeExpansionFullyChanged(qsExpanded)
+    }
+
+    fun removeFullExpansionListener(listener: ShadeFullExpansionListener) {
+        fullExpansionListeners.remove(listener)
+    }
+
     fun addQsExpansionListener(listener: ShadeQsExpansionListener) {
         qsExpansionListeners.add(listener)
         listener.onQsExpansionChanged(qsExpanded)
@@ -156,6 +166,13 @@
         qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) }
     }
 
+    fun onShadeExpansionFullyChanged(isExpanded: Boolean) {
+        this.expanded = isExpanded
+
+        debugLog("expanded=$isExpanded")
+        fullExpansionListeners.forEach { it.onShadeExpansionFullyChanged(isExpanded) }
+    }
+
     /** Updates the panel state if necessary. */
     fun updateState(@PanelState state: Int) {
         debugLog(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt
new file mode 100644
index 0000000..6d13e19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt
@@ -0,0 +1,23 @@
+/*
+ * 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
+
+/** A listener interface to be notified of expansion events for the notification shade. */
+fun interface ShadeFullExpansionListener {
+    /** Invoked whenever the shade expansion changes, when it is fully collapsed or expanded */
+    fun onShadeExpansionFullyChanged(isExpanded: Boolean)
+}
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/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..07820ec 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
@@ -62,7 +78,7 @@
             })
         shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
         shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
-        dumpManager.registerDumpable("ShadeTransitionController") { printWriter, _ ->
+        dumpManager.registerCriticalDumpable("ShadeTransitionController") { printWriter, _ ->
             dump(printWriter)
         }
     }
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..f95125f 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
@@ -54,7 +70,7 @@
                     updateResources()
                 }
             })
-        dumpManager.registerDumpable("SplitShadeOverScroller") { printWriter, _ ->
+        dumpManager.registerCriticalDumpable("SplitShadeOverScroller") { printWriter, _ ->
             dump(printWriter)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 4ae0f6a..590a04a 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;
@@ -163,6 +163,9 @@
     private static final int MSG_REGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 66 << MSG_SHIFT;
     private static final int MSG_UNREGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 67 << MSG_SHIFT;
     private static final int MSG_TILE_SERVICE_REQUEST_LISTENING_STATE = 68 << MSG_SHIFT;
+    private static final int MSG_SHOW_REAR_DISPLAY_DIALOG = 69 << MSG_SHIFT;
+    private static final int MSG_GO_TO_FULLSCREEN_FROM_SPLIT = 70 << MSG_SHIFT;
+    private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -333,9 +336,9 @@
         }
 
         /**
-         * @see IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
+         * @see IStatusBar#setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback)
          */
-        default void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+        default void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
         }
 
         /**
@@ -356,11 +359,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) { }
 
         /**
@@ -472,6 +475,21 @@
          */
         default void unregisterNearbyMediaDevicesProvider(
                 @NonNull INearbyMediaDevicesProvider provider) {}
+
+        /**
+         * @see IStatusBar#showRearDisplayDialog
+         */
+        default void showRearDisplayDialog(int currentBaseState) {}
+
+        /**
+         * @see IStatusBar#goToFullscreenFromSplit
+         */
+        default void goToFullscreenFromSplit() {}
+
+        /**
+         * @see IStatusBar#enterStageSplitFromRunningApp
+         */
+        default void enterStageSplitFromRunningApp(boolean leftOrTop) {}
     }
 
     public CommandQueue(Context context) {
@@ -1017,9 +1035,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 +1108,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 +1117,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();
@@ -1226,6 +1244,21 @@
     }
 
     @Override
+    public void showRearDisplayDialog(int currentBaseState) {
+        synchronized (mLock) {
+            mHandler.obtainMessage(MSG_SHOW_REAR_DISPLAY_DIALOG, currentBaseState).sendToTarget();
+        }
+    }
+
+    @Override
+    public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+        synchronized (mLock) {
+            mHandler.obtainMessage(MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP,
+                    leftOrTop).sendToTarget();
+        }
+    }
+
+    @Override
     public void requestAddTile(
             @NonNull ComponentName componentName,
             @NonNull CharSequence appName,
@@ -1286,6 +1319,11 @@
                 .sendToTarget();
     }
 
+    @Override
+    public void goToFullscreenFromSplit() {
+        mHandler.obtainMessage(MSG_GO_TO_FULLSCREEN_FROM_SPLIT).sendToTarget();
+    }
+
     private final class H extends Handler {
         private H(Looper l) {
             super(l);
@@ -1546,9 +1584,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 +1620,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;
@@ -1721,6 +1759,21 @@
                         mCallbacks.get(i).requestTileServiceListeningState(component);
                     }
                     break;
+                case MSG_SHOW_REAR_DISPLAY_DIALOG:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).showRearDisplayDialog((Integer) msg.obj);
+                    }
+                    break;
+                case MSG_GO_TO_FULLSCREEN_FROM_SPLIT:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).goToFullscreenFromSplit();
+                    }
+                    break;
+                case MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).enterStageSplitFromRunningApp((Boolean) msg.obj);
+                    }
+                    break;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
index 3d161d9..24c66eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
@@ -17,9 +17,12 @@
 package com.android.systemui.statusbar;
 
 import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
 import android.annotation.StringRes;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.Configuration;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.TextView;
@@ -33,16 +36,30 @@
 public class EmptyShadeView extends StackScrollerDecorView {
 
     private TextView mEmptyText;
+    private TextView mEmptyFooterText;
+
     private @StringRes int mText = R.string.empty_shade_text;
 
+    private @DrawableRes int mFooterIcon = R.drawable.ic_friction_lock_closed;
+    private @StringRes int mFooterText = R.string.unlock_to_see_notif_text;
+    private @Visibility int mFooterVisibility = View.GONE;
+    private int mSize;
+
     public EmptyShadeView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mSize = getResources().getDimensionPixelSize(
+                R.dimen.notifications_unseen_footer_icon_size);
     }
 
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
+        mSize = getResources().getDimensionPixelSize(
+                R.dimen.notifications_unseen_footer_icon_size);
         mEmptyText.setText(mText);
+        mEmptyFooterText.setVisibility(mFooterVisibility);
+        setFooterText(mFooterText);
+        setFooterIcon(mFooterIcon);
     }
 
     @Override
@@ -52,11 +69,13 @@
 
     @Override
     protected View findSecondaryView() {
-        return null;
+        return findViewById(R.id.no_notifications_footer);
     }
 
     public void setTextColor(@ColorInt int color) {
         mEmptyText.setTextColor(color);
+        mEmptyFooterText.setTextColor(color);
+        mEmptyFooterText.setCompoundDrawableTintList(ColorStateList.valueOf(color));
     }
 
     public void setText(@StringRes int text) {
@@ -64,14 +83,53 @@
         mEmptyText.setText(mText);
     }
 
+    public void setFooterVisibility(@Visibility int visibility) {
+        mFooterVisibility = visibility;
+        setSecondaryVisible(visibility == View.VISIBLE, false);
+    }
+
+    public void setFooterText(@StringRes int text) {
+        mFooterText = text;
+        if (text != 0) {
+            mEmptyFooterText.setText(mFooterText);
+        } else {
+            mEmptyFooterText.setText(null);
+        }
+    }
+
+    public void setFooterIcon(@DrawableRes int icon) {
+        mFooterIcon = icon;
+        Drawable drawable;
+        if (icon == 0) {
+            drawable = null;
+        } else {
+            drawable = getResources().getDrawable(icon);
+            drawable.setBounds(0, 0, mSize, mSize);
+        }
+        mEmptyFooterText.setCompoundDrawablesRelative(drawable, null, null, null);
+    }
+
+    @StringRes
     public int getTextResource() {
         return mText;
     }
 
+    @StringRes
+    public int getFooterTextResource() {
+        return mFooterText;
+    }
+
+    @DrawableRes
+    public int getFooterIconResource() {
+        return mFooterIcon;
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mEmptyText = (TextView) findContentView();
+        mEmptyFooterText = (TextView) findSecondaryView();
+        mEmptyFooterText.setCompoundDrawableTintList(mEmptyFooterText.getTextColors());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 073ab8b..0f27420 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -21,6 +21,7 @@
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
 import static android.hardware.biometrics.BiometricSourceType.FACE;
+import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
@@ -35,7 +36,7 @@
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
@@ -50,12 +51,10 @@
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.Color;
-import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.BatteryManager;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -64,22 +63,24 @@
 import android.os.UserManager;
 import android.text.TextUtils;
 import android.text.format.Formatter;
-import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.ViewClippingUtil;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TrustGrantFlags;
+import com.android.keyguard.logging.KeyguardLogger;
 import com.android.settingslib.Utils;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.R;
+import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FaceHelpMessageDeferral;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
@@ -123,7 +124,6 @@
 
     private static final String TAG = "KeyguardIndication";
     private static final boolean DEBUG_CHARGING_SPEED = false;
-    private static final boolean DEBUG = Build.IS_DEBUGGABLE;
 
     private static final int MSG_HIDE_TRANSIENT = 1;
     private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2;
@@ -138,6 +138,8 @@
     private final KeyguardStateController mKeyguardStateController;
     protected final StatusBarStateController mStatusBarStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final AuthController mAuthController;
+    private final KeyguardLogger mKeyguardLogger;
     private ViewGroup mIndicationArea;
     private KeyguardIndicationTextView mTopIndicationView;
     private KeyguardIndicationTextView mLockScreenIndicationView;
@@ -154,11 +156,12 @@
     private final AccessibilityManager mAccessibilityManager;
     private final Handler mHandler;
 
-    protected KeyguardIndicationRotateTextViewController mRotateTextViewController;
+    @VisibleForTesting
+    public KeyguardIndicationRotateTextViewController mRotateTextViewController;
     private BroadcastReceiver mBroadcastReceiver;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
-    private String mRestingIndication;
+    private String mPersistentUnlockMessage;
     private String mAlignmentIndication;
     private CharSequence mTrustGrantedIndication;
     private CharSequence mTransientIndication;
@@ -188,27 +191,23 @@
     private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
 
     private boolean mDozing;
-    private final ViewClippingUtil.ClippingParameters mClippingParams =
-            new ViewClippingUtil.ClippingParameters() {
-                @Override
-                public boolean shouldFinish(View view) {
-                    return view == mIndicationArea;
-                }
-            };
-    private ScreenLifecycle mScreenLifecycle;
+    private final ScreenLifecycle mScreenLifecycle;
     private final ScreenLifecycle.Observer mScreenObserver =
             new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurnedOn() {
             mHandler.removeMessages(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON);
             if (mBiometricErrorMessageToShowOnScreenOn != null) {
-                showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn);
+                String followUpMessage = mFaceLockedOutThisAuthSession
+                        ? faceLockedOutFollowupMessage() : null;
+                showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn, followUpMessage);
                 // We want to keep this message around in case the screen was off
                 hideBiometricMessageDelayed(DEFAULT_HIDE_DELAY_MS);
                 mBiometricErrorMessageToShowOnScreenOn = null;
             }
         }
     };
+    private boolean mFaceLockedOutThisAuthSession;
 
     /**
      * Creates a new KeyguardIndicationController and registers callbacks.
@@ -229,11 +228,13 @@
             @Main DelayableExecutor executor,
             @Background DelayableExecutor bgExecutor,
             FalsingManager falsingManager,
+            AuthController authController,
             LockPatternUtils lockPatternUtils,
             ScreenLifecycle screenLifecycle,
             KeyguardBypassController keyguardBypassController,
             AccessibilityManager accessibilityManager,
-            FaceHelpMessageDeferral faceHelpMessageDeferral) {
+            FaceHelpMessageDeferral faceHelpMessageDeferral,
+            KeyguardLogger keyguardLogger) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
         mDevicePolicyManager = devicePolicyManager;
@@ -248,10 +249,12 @@
         mExecutor = executor;
         mBackgroundExecutor = bgExecutor;
         mLockPatternUtils = lockPatternUtils;
+        mAuthController = authController;
         mFalsingManager = falsingManager;
         mKeyguardBypassController = keyguardBypassController;
         mAccessibilityManager = accessibilityManager;
         mScreenLifecycle = screenLifecycle;
+        mKeyguardLogger = keyguardLogger;
         mScreenLifecycle.addObserver(mScreenObserver);
 
         mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
@@ -376,7 +379,7 @@
         updateLockScreenTrustMsg(userId, getTrustGrantedIndication(), getTrustManagedIndication());
         updateLockScreenAlignmentMsg();
         updateLockScreenLogoutView();
-        updateLockScreenRestingMsg();
+        updateLockScreenPersistentUnlockMsg();
     }
 
     private void updateOrganizedOwnedDevice() {
@@ -482,7 +485,8 @@
     }
 
     private void updateLockScreenUserLockedMsg(int userId) {
-        if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
+        if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)
+                || mKeyguardUpdateMonitor.isEncryptedOrLockdown(userId)) {
             mRotateTextViewController.updateIndication(
                     INDICATION_TYPE_USER_LOCKED,
                     new KeyguardIndication.Builder()
@@ -587,18 +591,17 @@
         }
     }
 
-    private void updateLockScreenRestingMsg() {
-        if (!TextUtils.isEmpty(mRestingIndication)
-                && !mRotateTextViewController.hasIndications()) {
+    private void updateLockScreenPersistentUnlockMsg() {
+        if (!TextUtils.isEmpty(mPersistentUnlockMessage)) {
             mRotateTextViewController.updateIndication(
-                    INDICATION_TYPE_RESTING,
+                    INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
                     new KeyguardIndication.Builder()
-                            .setMessage(mRestingIndication)
+                            .setMessage(mPersistentUnlockMessage)
                             .setTextColor(mInitialTextColorState)
                             .build(),
-                    false);
+                    true);
         } else {
-            mRotateTextViewController.hideIndication(INDICATION_TYPE_RESTING);
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE);
         }
     }
 
@@ -619,7 +622,6 @@
                                 if (mFalsingManager.isFalseTap(LOW_PENALTY)) {
                                     return;
                                 }
-                                int currentUserId = getCurrentUser();
                                 mDevicePolicyManager.logoutUser();
                             })
                             .build(),
@@ -676,17 +678,14 @@
                 hideTransientIndication();
             }
             updateDeviceEntryIndication(false);
-        } else if (!visible) {
+        } else {
             // If we unlock and return to keyguard quickly, previous error should not be shown
             hideTransientIndication();
         }
     }
 
-    /**
-     * Sets the indication that is shown if nothing else is showing.
-     */
-    public void setRestingIndication(String restingIndication) {
-        mRestingIndication = restingIndication;
+    private void setPersistentUnlockMessage(String persistentUnlockMessage) {
+        mPersistentUnlockMessage = persistentUnlockMessage;
         updateDeviceEntryIndication(false);
     }
 
@@ -764,7 +763,7 @@
      * logic.
      */
     private void showBiometricMessage(CharSequence biometricMessage,
-            CharSequence biometricMessageFollowUp) {
+            @Nullable CharSequence biometricMessageFollowUp) {
         if (TextUtils.equals(biometricMessage, mBiometricMessage)) {
             return;
         }
@@ -929,7 +928,7 @@
         }
 
         if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
-            if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
+            if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
                 return; // udfps affordance is highlighted, no need to show action to unlock
             } else if (mKeyguardUpdateMonitor.isFaceEnrolled()) {
                 String message = mContext.getString(R.string.keyguard_retry);
@@ -1028,7 +1027,7 @@
                 mChargingTimeRemaining = mPowerPluggedIn
                         ? mBatteryInfo.computeChargeTimeRemaining() : -1;
             } catch (RemoteException e) {
-                Log.e(TAG, "Error calling IBatteryStats: ", e);
+                mKeyguardLogger.logException(e, "Error calling IBatteryStats");
                 mChargingTimeRemaining = -1;
             }
             updateDeviceEntryIndication(!wasPluggedIn && mPowerPluggedInWired);
@@ -1072,17 +1071,14 @@
                     && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
             final boolean faceAuthFailed = biometricSourceType == FACE
                     && msgId == BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; // ran through matcher & failed
-            final boolean isUnlockWithFingerprintPossible =
-                    mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                            getCurrentUser());
+            final boolean isUnlockWithFingerprintPossible = canUnlockWithFingerprint();
             final boolean isCoExFaceAcquisitionMessage =
                     faceAuthSoftError && isUnlockWithFingerprintPossible;
             if (isCoExFaceAcquisitionMessage && !mCoExFaceAcquisitionMsgIdsToShow.contains(msgId)) {
-                if (DEBUG) {
-                    Log.d(TAG, "skip showing msgId=" + msgId + " helpString=" + helpString
-                            + ", due to co-ex logic");
-                }
-                return;
+                mKeyguardLogger.logBiometricMessage(
+                        "skipped showing help message due to co-ex logic",
+                        msgId,
+                        helpString);
             } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
                 mStatusBarKeyguardViewManager.setKeyguardMessage(helpString,
                         mInitialTextColorState);
@@ -1120,74 +1116,49 @@
         }
 
         @Override
-        public void onBiometricError(int msgId, String errString,
-                BiometricSourceType biometricSourceType) {
-            CharSequence deferredFaceMessage = null;
-            if (biometricSourceType == FACE) {
-                if (msgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) {
-                    deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
-                    if (DEBUG) {
-                        Log.d(TAG, "showDeferredFaceMessage msgId=" + deferredFaceMessage);
-                    }
-                }
-                mFaceAcquiredMessageDeferral.reset();
-            }
-
-            if (shouldSuppressBiometricError(msgId, biometricSourceType, mKeyguardUpdateMonitor)) {
-                if (DEBUG) {
-                    Log.d(TAG, "suppressingBiometricError msgId=" + msgId
-                            + " source=" + biometricSourceType);
-                }
-            } else if (biometricSourceType == FACE && msgId == FaceManager.FACE_ERROR_TIMEOUT) {
-                // Co-ex: show deferred message OR nothing
-                if (mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                        KeyguardUpdateMonitor.getCurrentUser())) {
-                    // if we're on the lock screen (bouncer isn't showing), show the deferred msg
-                    if (deferredFaceMessage != null
-                            && !mStatusBarKeyguardViewManager.isBouncerShowing()) {
-                        showBiometricMessage(
-                                deferredFaceMessage,
-                                mContext.getString(R.string.keyguard_suggest_fingerprint)
-                        );
-                        return;
-                    }
-
-                    // otherwise, don't show any message
-                    if (DEBUG) {
-                        Log.d(TAG, "skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
-                    }
-                    return;
-                }
-
-                // Face-only: The face timeout message is not very actionable, let's ask the user to
-                // manually retry.
-                if (deferredFaceMessage != null) {
-                    showBiometricMessage(
-                            deferredFaceMessage,
-                            mContext.getString(R.string.keyguard_unlock)
-                    );
-                } else {
-                    // suggest swiping up to unlock (try face auth again or swipe up to bouncer)
-                    showActionToUnlock();
-                }
-            } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
-                mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState);
-            } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
-                showBiometricMessage(errString);
-            } else {
-                mBiometricErrorMessageToShowOnScreenOn = errString;
+        public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) {
+            if (biometricSourceType == FACE && !mKeyguardUpdateMonitor.isFaceLockedOut()) {
+                mFaceLockedOutThisAuthSession = false;
+            } else if (biometricSourceType == FINGERPRINT) {
+                setPersistentUnlockMessage(mKeyguardUpdateMonitor.isFingerprintLockedOut()
+                        ? mContext.getString(R.string.keyguard_unlock) : "");
             }
         }
 
-        private boolean shouldSuppressBiometricError(int msgId,
-                BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor) {
-            if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
-                return shouldSuppressFingerprintError(msgId, updateMonitor);
-            }
+        @Override
+        public void onBiometricError(int msgId, String errString,
+                BiometricSourceType biometricSourceType) {
             if (biometricSourceType == FACE) {
-                return shouldSuppressFaceError(msgId, updateMonitor);
+                onFaceAuthError(msgId, errString);
+            } else if (biometricSourceType == FINGERPRINT) {
+                onFingerprintAuthError(msgId, errString);
             }
-            return false;
+        }
+
+        private void onFaceAuthError(int msgId, String errString) {
+            CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
+            mFaceAcquiredMessageDeferral.reset();
+            if (shouldSuppressFaceError(msgId, mKeyguardUpdateMonitor)) {
+                mKeyguardLogger.logBiometricMessage("suppressingFaceError", msgId, errString);
+                return;
+            }
+            if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
+                handleFaceAuthTimeoutError(deferredFaceMessage);
+            } else if (isLockoutError(msgId)) {
+                handleFaceLockoutError(errString);
+            } else {
+                showErrorMessageNowOrLater(errString, null);
+            }
+        }
+
+        private void onFingerprintAuthError(int msgId, String errString) {
+            if (shouldSuppressFingerprintError(msgId, mKeyguardUpdateMonitor)) {
+                mKeyguardLogger.logBiometricMessage("suppressingFingerprintError",
+                        msgId,
+                        errString);
+            } else {
+                showErrorMessageNowOrLater(errString, null);
+            }
         }
 
         private boolean shouldSuppressFingerprintError(int msgId,
@@ -1197,7 +1168,7 @@
             // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
             // check of whether non-strong biometric is allowed
             return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
-                    && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
+                    && !isLockoutError(msgId))
                     || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
                     || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
                     || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED);
@@ -1217,16 +1188,14 @@
 
         @Override
         public void onTrustChanged(int userId) {
-            if (getCurrentUser() != userId) {
-                return;
-            }
+            if (!isCurrentUser(userId)) return;
             updateDeviceEntryIndication(false);
         }
 
         @Override
-        public void showTrustGrantedMessage(CharSequence message) {
-            mTrustGrantedIndication = message;
-            updateDeviceEntryIndication(false);
+        public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+                @NonNull TrustGrantFlags flags, @Nullable String message) {
+            showTrustGrantedMessage(dismissKeyguard, message);
         }
 
         @Override
@@ -1286,7 +1255,92 @@
         }
     }
 
-    private StatusBarStateController.StateListener mStatusBarStateListener =
+    private boolean isCurrentUser(int userId) {
+        return getCurrentUser() == userId;
+    }
+
+    protected void showTrustGrantedMessage(boolean dismissKeyguard, @Nullable String message) {
+        mTrustGrantedIndication = message;
+        updateDeviceEntryIndication(false);
+    }
+
+    private void handleFaceLockoutError(String errString) {
+        String followupMessage = faceLockedOutFollowupMessage();
+        // Lockout error can happen multiple times in a session because we trigger face auth
+        // even when it is locked out so that the user is aware that face unlock would have
+        // triggered but didn't because it is locked out.
+
+        // On first lockout we show the error message from FaceManager, which tells the user they
+        // had too many unsuccessful attempts.
+        if (!mFaceLockedOutThisAuthSession) {
+            mFaceLockedOutThisAuthSession = true;
+            showErrorMessageNowOrLater(errString, followupMessage);
+        } else if (!mAuthController.isUdfpsFingerDown()) {
+            // On subsequent lockouts, we show a more generic locked out message.
+            showErrorMessageNowOrLater(
+                    mContext.getString(R.string.keyguard_face_unlock_unavailable),
+                    followupMessage);
+        }
+    }
+
+    private String faceLockedOutFollowupMessage() {
+        int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
+                : R.string.keyguard_unlock;
+        return mContext.getString(followupMsgId);
+    }
+
+    private static boolean isLockoutError(int msgId) {
+        return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
+                || msgId == FaceManager.FACE_ERROR_LOCKOUT;
+    }
+
+    private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) {
+        mKeyguardLogger.logBiometricMessage("deferred message after face auth timeout",
+                null, String.valueOf(deferredFaceMessage));
+        if (canUnlockWithFingerprint()) {
+            // Co-ex: show deferred message OR nothing
+            // if we're on the lock screen (bouncer isn't showing), show the deferred msg
+            if (deferredFaceMessage != null
+                    && !mStatusBarKeyguardViewManager.isBouncerShowing()) {
+                showBiometricMessage(
+                        deferredFaceMessage,
+                        mContext.getString(R.string.keyguard_suggest_fingerprint)
+                );
+            } else {
+                // otherwise, don't show any message
+                mKeyguardLogger.logBiometricMessage(
+                        "skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
+            }
+        } else if (deferredFaceMessage != null) {
+            // Face-only: The face timeout message is not very actionable, let's ask the
+            // user to manually retry.
+            showBiometricMessage(
+                    deferredFaceMessage,
+                    mContext.getString(R.string.keyguard_unlock)
+            );
+        } else {
+            // Face-only
+            // suggest swiping up to unlock (try face auth again or swipe up to bouncer)
+            showActionToUnlock();
+        }
+    }
+
+    private boolean canUnlockWithFingerprint() {
+        return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
+                KeyguardUpdateMonitor.getCurrentUser());
+    }
+
+    private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
+        if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
+            mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState);
+        } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
+            showBiometricMessage(errString, followUpMsg);
+        } else {
+            mBiometricErrorMessageToShowOnScreenOn = errString;
+        }
+    }
+
+    private final StatusBarStateController.StateListener mStatusBarStateListener =
             new StatusBarStateController.StateListener() {
         @Override
         public void onStateChanged(int newState) {
@@ -1307,7 +1361,7 @@
         }
     };
 
-    private KeyguardStateController.Callback mKeyguardStateCallback =
+    private final KeyguardStateController.Callback mKeyguardStateCallback =
             new KeyguardStateController.Callback() {
         @Override
         public void onUnlockedChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 9d2750f..2334a4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -15,9 +15,12 @@
 import android.util.AttributeSet
 import android.util.MathUtils.lerp
 import android.view.View
+import android.view.animation.PathInterpolator
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
 import com.android.systemui.util.getColorWithAlpha
+import com.android.systemui.util.leak.RotationUtils
+import com.android.systemui.util.leak.RotationUtils.Rotation
 import java.util.function.Consumer
 
 /**
@@ -67,42 +70,46 @@
     override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
         val interpolatedAmount = INTERPOLATOR.getInterpolation(amount)
         val ovalWidthIncreaseAmount =
-                getPercentPastThreshold(interpolatedAmount, WIDEN_OVAL_THRESHOLD)
+            getPercentPastThreshold(interpolatedAmount, WIDEN_OVAL_THRESHOLD)
 
         val initialWidthMultiplier = (1f - OVAL_INITIAL_WIDTH_PERCENT) / 2f
 
         with(scrim) {
-            revealGradientEndColorAlpha = 1f - getPercentPastThreshold(
-                    amount, FADE_END_COLOR_OUT_THRESHOLD)
+            revealGradientEndColorAlpha =
+                1f - getPercentPastThreshold(amount, FADE_END_COLOR_OUT_THRESHOLD)
             setRevealGradientBounds(
-                    scrim.width * initialWidthMultiplier +
-                            -scrim.width * ovalWidthIncreaseAmount,
-                    scrim.height * OVAL_INITIAL_TOP_PERCENT -
-                            scrim.height * interpolatedAmount,
-                    scrim.width * (1f - initialWidthMultiplier) +
-                            scrim.width * ovalWidthIncreaseAmount,
-                    scrim.height * OVAL_INITIAL_BOTTOM_PERCENT +
-                            scrim.height * interpolatedAmount)
+                scrim.width * initialWidthMultiplier + -scrim.width * ovalWidthIncreaseAmount,
+                scrim.height * OVAL_INITIAL_TOP_PERCENT - scrim.height * interpolatedAmount,
+                scrim.width * (1f - initialWidthMultiplier) + scrim.width * ovalWidthIncreaseAmount,
+                scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount
+            )
         }
     }
 }
 
 class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevealEffect {
 
-    private val INTERPOLATOR = Interpolators.FAST_OUT_SLOW_IN_REVERSE
+    // Interpolator that reveals >80% of the content at 0.5 progress, makes revealing faster
+    private val interpolator = PathInterpolator(/* controlX1= */ 0.4f, /* controlY1= */ 0f,
+            /* controlX2= */ 0.2f, /* controlY2= */ 1f)
 
     override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
-        val interpolatedAmount = INTERPOLATOR.getInterpolation(amount)
+        val interpolatedAmount = interpolator.getInterpolation(amount)
 
         scrim.interpolatedRevealAmount = interpolatedAmount
 
         scrim.startColorAlpha =
-            getPercentPastThreshold(1 - interpolatedAmount,
-                threshold = 1 - START_COLOR_REVEAL_PERCENTAGE)
+            getPercentPastThreshold(
+                1 - interpolatedAmount,
+                threshold = 1 - START_COLOR_REVEAL_PERCENTAGE
+            )
 
         scrim.revealGradientEndColorAlpha =
-            1f - getPercentPastThreshold(interpolatedAmount,
-                threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE)
+            1f -
+                getPercentPastThreshold(
+                    interpolatedAmount,
+                    threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+                )
 
         // Start changing gradient bounds later to avoid harsh gradient in the beginning
         val gradientBoundsAmount = lerp(GRADIENT_START_BOUNDS_PERCENTAGE, 1.0f, interpolatedAmount)
@@ -179,7 +186,7 @@
      */
     private val OFF_SCREEN_START_AMOUNT = 0.05f
 
-    private val WIDTH_INCREASE_MULTIPLIER = 1.25f
+    private val INCREASE_MULTIPLIER = 1.25f
 
     override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
         val interpolatedAmount = Interpolators.FAST_OUT_SLOW_IN_REVERSE.getInterpolation(amount)
@@ -188,15 +195,36 @@
         with(scrim) {
             revealGradientEndColorAlpha = 1f - fadeAmount
             interpolatedRevealAmount = interpolatedAmount
-            setRevealGradientBounds(
+            @Rotation val rotation = RotationUtils.getRotation(scrim.getContext())
+            if (rotation == RotationUtils.ROTATION_NONE) {
+                setRevealGradientBounds(
                     width * (1f + OFF_SCREEN_START_AMOUNT) -
-                            width * WIDTH_INCREASE_MULTIPLIER * interpolatedAmount,
-                    powerButtonY -
-                            height * interpolatedAmount,
+                        width * INCREASE_MULTIPLIER * interpolatedAmount,
+                    powerButtonY - height * interpolatedAmount,
                     width * (1f + OFF_SCREEN_START_AMOUNT) +
-                            width * WIDTH_INCREASE_MULTIPLIER * interpolatedAmount,
-                    powerButtonY +
-                            height * interpolatedAmount)
+                        width * INCREASE_MULTIPLIER * interpolatedAmount,
+                    powerButtonY + height * interpolatedAmount
+                )
+            } else if (rotation == RotationUtils.ROTATION_LANDSCAPE) {
+                setRevealGradientBounds(
+                    powerButtonY - width * interpolatedAmount,
+                    (-height * OFF_SCREEN_START_AMOUNT) -
+                        height * INCREASE_MULTIPLIER * interpolatedAmount,
+                    powerButtonY + width * interpolatedAmount,
+                    (-height * OFF_SCREEN_START_AMOUNT) +
+                        height * INCREASE_MULTIPLIER * interpolatedAmount
+                )
+            } else {
+                // RotationUtils.ROTATION_SEASCAPE
+                setRevealGradientBounds(
+                    (width - powerButtonY) - width * interpolatedAmount,
+                    height * (1f + OFF_SCREEN_START_AMOUNT) -
+                        height * INCREASE_MULTIPLIER * interpolatedAmount,
+                    (width - powerButtonY) + width * interpolatedAmount,
+                    height * (1f + OFF_SCREEN_START_AMOUNT) +
+                        height * INCREASE_MULTIPLIER * interpolatedAmount
+                )
+            }
         }
     }
 }
@@ -208,9 +236,7 @@
  */
 class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
 
-    /**
-     * Listener that is called if the scrim's opaqueness changes
-     */
+    /** Listener that is called if the scrim's opaqueness changes */
     lateinit var isScrimOpaqueChangedListener: Consumer<Boolean>
 
     /**
@@ -224,8 +250,11 @@
 
                 revealEffect.setRevealAmountOnScrim(value, this)
                 updateScrimOpaque()
-                Trace.traceCounter(Trace.TRACE_TAG_APP, "light_reveal_amount",
-                        (field * 100).toInt())
+                Trace.traceCounter(
+                    Trace.TRACE_TAG_APP,
+                    "light_reveal_amount",
+                    (field * 100).toInt()
+                )
                 invalidate()
             }
         }
@@ -250,10 +279,10 @@
 
     /**
      * Alpha of the fill that can be used in the beginning of the animation to hide the content.
-     * Normally the gradient bounds are animated from small size so the content is not visible,
-     * but if the start gradient bounds allow to see some content this could be used to make the
-     * reveal smoother. It can help to add fade in effect in the beginning of the animation.
-     * The color of the fill is determined by [revealGradientEndColor].
+     * Normally the gradient bounds are animated from small size so the content is not visible, but
+     * if the start gradient bounds allow to see some content this could be used to make the reveal
+     * smoother. It can help to add fade in effect in the beginning of the animation. The color of
+     * the fill is determined by [revealGradientEndColor].
      *
      * 0 - no fill and content is visible, 1 - the content is covered with the start color
      */
@@ -281,9 +310,7 @@
             }
         }
 
-    /**
-     * Is the scrim currently fully opaque
-     */
+    /** Is the scrim currently fully opaque */
     var isScrimOpaque = false
         private set(value) {
             if (field != value) {
@@ -318,16 +345,22 @@
      * Paint used to draw a transparent-to-white radial gradient. This will be scaled and translated
      * via local matrix in [onDraw] so we never need to construct a new shader.
      */
-    private val gradientPaint = Paint().apply {
-        shader = RadialGradient(
-                0f, 0f, 1f,
-                intArrayOf(Color.TRANSPARENT, Color.WHITE), floatArrayOf(0f, 1f),
-                Shader.TileMode.CLAMP)
+    private val gradientPaint =
+        Paint().apply {
+            shader =
+                RadialGradient(
+                    0f,
+                    0f,
+                    1f,
+                    intArrayOf(Color.TRANSPARENT, Color.WHITE),
+                    floatArrayOf(0f, 1f),
+                    Shader.TileMode.CLAMP
+                )
 
-        // SRC_OVER ensures that we draw the semitransparent pixels over other views in the same
-        // window, rather than outright replacing them.
-        xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
-    }
+            // SRC_OVER ensures that we draw the semitransparent pixels over other views in the same
+            // window, rather than outright replacing them.
+            xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
+        }
 
     /**
      * Matrix applied to [gradientPaint]'s RadialGradient shader to move the gradient to
@@ -347,8 +380,8 @@
      * simply a helper method that sets [revealGradientCenter], [revealGradientWidth], and
      * [revealGradientHeight] for you.
      *
-     * This method does not call [invalidate] - you should do so once you're done changing
-     * properties.
+     * This method does not call [invalidate]
+     * - you should do so once you're done changing properties.
      */
     fun setRevealGradientBounds(left: Float, top: Float, right: Float, bottom: Float) {
         revealGradientWidth = right - left
@@ -359,8 +392,12 @@
     }
 
     override fun onDraw(canvas: Canvas?) {
-        if (canvas == null || revealGradientWidth <= 0 || revealGradientHeight <= 0 ||
-            revealAmount == 0f) {
+        if (
+            canvas == null ||
+                revealGradientWidth <= 0 ||
+                revealGradientHeight <= 0 ||
+                revealAmount == 0f
+        ) {
             if (revealAmount < 1f) {
                 canvas?.drawColor(revealGradientEndColor)
             }
@@ -383,8 +420,10 @@
     }
 
     private fun setPaintColorFilter() {
-        gradientPaint.colorFilter = PorterDuffColorFilter(
-            getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha),
-            PorterDuff.Mode.MULTIPLY)
+        gradientPaint.colorFilter =
+            PorterDuffColorFilter(
+                getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha),
+                PorterDuff.Mode.MULTIPLY
+            )
     }
 }
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/NotificationInsetsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
new file mode 100644
index 0000000..39d7d66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+import com.android.systemui.shade.NotificationShadeWindowView;
+
+/**
+ * Calculates insets for the notification shade window view.
+ */
+public abstract class NotificationInsetsController
+        implements NotificationShadeWindowView.LayoutInsetsController {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java
new file mode 100644
index 0000000..1ed704e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java
@@ -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.statusbar;
+
+import static android.view.WindowInsets.Type.systemBars;
+
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.util.Pair;
+import android.view.DisplayCutout;
+import android.view.WindowInsets;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Default implementation of NotificationsInsetsController.
+ */
+@SysUISingleton
+public class NotificationInsetsImpl extends NotificationInsetsController {
+
+    @Inject
+    public NotificationInsetsImpl() {
+
+    }
+
+    @Override
+    public Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets,
+            @Nullable DisplayCutout displayCutout) {
+        final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
+        int leftInset = 0;
+        int rightInset = 0;
+
+        if (displayCutout != null) {
+            leftInset = displayCutout.getSafeInsetLeft();
+            rightInset = displayCutout.getSafeInsetRight();
+        }
+        leftInset = Math.max(insets.left, leftInset);
+        rightInset = Math.max(insets.right, rightInset);
+
+        return new Pair(leftInset, rightInset);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java
new file mode 100644
index 0000000..614bc0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+@Module
+public interface NotificationInsetsModule {
+
+    @Binds
+    @SysUISingleton
+    NotificationInsetsController bindNotificationInsetsController(NotificationInsetsImpl impl);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 824d3a3..56b689e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -31,7 +31,7 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 184dc25..f4cd985 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,7 +19,6 @@
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
-import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.admin.DevicePolicyManager;
@@ -31,6 +30,7 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -38,6 +38,7 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.statusbar.NotificationVisibility;
@@ -50,6 +51,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -93,6 +95,7 @@
     private final SparseBooleanArray mUsersInLockdownLatestResult = new SparseBooleanArray();
     private final SparseBooleanArray mShouldHideNotifsLatestResult = new SparseBooleanArray();
     private final UserManager mUserManager;
+    private final UserTracker mUserTracker;
     private final List<UserChangedListener> mListeners = new ArrayList<>();
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final NotificationClickNotifier mClickNotifier;
@@ -126,21 +129,6 @@
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
             switch (action) {
-                case Intent.ACTION_USER_SWITCHED:
-                    mCurrentUserId = intent.getIntExtra(
-                            Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL);
-                    updateCurrentProfilesCache();
-
-                    Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
-
-                    updateLockscreenNotificationSetting();
-                    updatePublicMode();
-                    mPresenter.onUserSwitched(mCurrentUserId);
-
-                    for (UserChangedListener listener : mListeners) {
-                        listener.onUserChanged(mCurrentUserId);
-                    }
-                    break;
                 case Intent.ACTION_USER_REMOVED:
                     int removedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                     if (removedUserId != -1) {
@@ -180,6 +168,25 @@
         }
     };
 
+    protected final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    mCurrentUserId = newUser;
+                    updateCurrentProfilesCache();
+
+                    Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
+
+                    updateLockscreenNotificationSetting();
+                    updatePublicMode();
+                    mPresenter.onUserSwitched(mCurrentUserId);
+
+                    for (UserChangedListener listener : mListeners) {
+                        listener.onUserChanged(mCurrentUserId);
+                    }
+                }
+            };
+
     protected final Context mContext;
     private final Handler mMainHandler;
     protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
@@ -195,6 +202,7 @@
             BroadcastDispatcher broadcastDispatcher,
             DevicePolicyManager devicePolicyManager,
             UserManager userManager,
+            UserTracker userTracker,
             Lazy<NotificationVisibilityProvider> visibilityProviderLazy,
             Lazy<CommonNotifCollection> commonNotifCollectionLazy,
             NotificationClickNotifier clickNotifier,
@@ -210,7 +218,8 @@
         mMainHandler = mainHandler;
         mDevicePolicyManager = devicePolicyManager;
         mUserManager = userManager;
-        mCurrentUserId = ActivityManager.getCurrentUser();
+        mUserTracker = userTracker;
+        mCurrentUserId = mUserTracker.getUserId();
         mVisibilityProviderLazy = visibilityProviderLazy;
         mCommonNotifCollectionLazy = commonNotifCollectionLazy;
         mClickNotifier = clickNotifier;
@@ -281,7 +290,6 @@
                 null /* handler */, UserHandle.ALL);
 
         IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_USER_ADDED);
         filter.addAction(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
@@ -295,7 +303,9 @@
         mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null,
                 Context.RECEIVER_EXPORTED_UNAUDITED);
 
-        mCurrentUserId = ActivityManager.getCurrentUser(); // in case we reg'd receiver too late
+        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
+
+        mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late
         updateCurrentProfilesCache();
 
         mSettingsObserver.onChange(false);  // set up
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index b5879ec..8dc7842 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -304,7 +304,7 @@
     }
 
     init {
-        dumpManager.registerDumpable(javaClass.name, this)
+        dumpManager.registerCriticalDumpable(javaClass.name, this)
         if (WAKE_UP_ANIMATION_ENABLED) {
             keyguardStateController.addCallback(keyguardStateCallback)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index e21acb7..0b1807d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -123,9 +123,6 @@
     /** Sets whether the window was collapsed by force or not. */
     default void setForceWindowCollapsed(boolean force) {}
 
-    /** Sets whether panel is expanded or not. */
-    default void setPanelExpanded(boolean isExpanded) {}
-
     /** Gets whether the panel is expanded or not. */
     default boolean getPanelExpanded() {
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 87ef92a..56c34a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -39,6 +39,7 @@
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -66,11 +67,14 @@
     // the next icon has translated out of the way, to avoid overlapping.
     private static final Interpolator ICON_ALPHA_INTERPOLATOR =
             new PathInterpolator(0.6f, 0f, 0.6f, 0f);
+    private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
+    private static final SourceType SHELF_SCROLL = SourceType.from("ShelfScroll");
 
     private NotificationIconContainer mShelfIcons;
     private int[] mTmp = new int[2];
     private boolean mHideBackground;
     private int mStatusBarHeight;
+    private boolean mEnableNotificationClipping;
     private AmbientState mAmbientState;
     private NotificationStackScrollLayoutController mHostLayoutController;
     private int mPaddingBetweenElements;
@@ -111,29 +115,37 @@
         setClipChildren(false);
         setClipToPadding(false);
         mShelfIcons.setIsStaticLayout(false);
-        requestBottomRoundness(1.0f, /* animate = */ false, SourceType.DefaultValue);
-        requestTopRoundness(1f, false, SourceType.DefaultValue);
+        requestRoundness(/* top = */ 1f, /* bottom = */ 1f, BASE_VALUE, /* animate = */ false);
 
-        // Setting this to first in section to get the clipping to the top roundness correct. This
-        // value determines the way we are clipping to the top roundness of the overall shade
-        setFirstInSection(true);
-        initDimens();
+        if (!mUseRoundnessSourceTypes) {
+            // Setting this to first in section to get the clipping to the top roundness correct.
+            // This value determines the way we are clipping to the top roundness of the overall
+            // shade
+            setFirstInSection(true);
+        }
+        updateResources();
     }
 
     public void bind(AmbientState ambientState,
-            NotificationStackScrollLayoutController hostLayoutController) {
+                     NotificationStackScrollLayoutController hostLayoutController) {
         mAmbientState = ambientState;
         mHostLayoutController = hostLayoutController;
+        hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
+            child.requestRoundnessReset(SHELF_SCROLL);
+        });
     }
 
-    private void initDimens() {
+    private void updateResources() {
         Resources res = getResources();
         mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
         mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
 
         ViewGroup.LayoutParams layoutParams = getLayoutParams();
-        layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
-        setLayoutParams(layoutParams);
+        final int newShelfHeight = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
+        if (newShelfHeight != layoutParams.height) {
+            layoutParams.height = newShelfHeight;
+            setLayoutParams(layoutParams);
+        }
 
         final int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
         mShelfIcons.setPadding(padding, 0, padding, 0);
@@ -141,6 +153,7 @@
         mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
         mCornerAnimationDistance = res.getDimensionPixelSize(
                 R.dimen.notification_corner_animation_distance);
+        mEnableNotificationClipping = res.getBoolean(R.bool.notification_enable_clipping);
 
         mShelfIcons.setInNotificationIconShelf(true);
         if (!mShowNotificationShelf) {
@@ -151,7 +164,7 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        initDimens();
+        updateResources();
     }
 
     @Override
@@ -180,9 +193,11 @@
                 + " indexOfFirstViewInShelf=" + mIndexOfFirstViewInShelf + ')';
     }
 
-    /** Update the state of the shelf. */
+    /**
+     * Update the state of the shelf.
+     */
     public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                            AmbientState ambientState) {
         ExpandableView lastView = ambientState.getLastVisibleBackgroundChild();
         ShelfState viewState = (ShelfState) getViewState();
         if (mShowNotificationShelf && lastView != null) {
@@ -241,7 +256,7 @@
 
     /**
      * @param fractionToShade Fraction of lockscreen to shade transition
-     * @param shortestWidth Shortest width to use for lockscreen shelf
+     * @param shortestWidth   Shortest width to use for lockscreen shelf
      */
     @VisibleForTesting
     public void updateActualWidth(float fractionToShade, float shortestWidth) {
@@ -276,9 +291,9 @@
 
     /**
      * @param localX Click x from left of screen
-     * @param slop Margin of error within which we count x for valid click
-     * @param left Left of shelf, from left of screen
-     * @param right Right of shelf, from left of screen
+     * @param slop   Margin of error within which we count x for valid click
+     * @param left   Left of shelf, from left of screen
+     * @param right  Right of shelf, from left of screen
      * @return Whether click x was in view
      */
     @VisibleForTesting
@@ -288,8 +303,8 @@
 
     /**
      * @param localY Click y from top of shelf
-     * @param slop Margin of error within which we count y for valid click
-     * @param top Top of shelf
+     * @param slop   Margin of error within which we count y for valid click
+     * @param top    Top of shelf
      * @param bottom Height of shelf
      * @return Whether click y was in view
      */
@@ -301,7 +316,7 @@
     /**
      * @param localX Click x
      * @param localY Click y
-     * @param slop Margin of error for valid click
+     * @param slop   Margin of error for valid click
      * @return Whether this click was on the visible (non-clipped) part of the shelf
      */
     @Override
@@ -473,13 +488,15 @@
         }
     }
 
-    private void updateCornerRoundnessOnScroll(ActivatableNotificationView anv, float viewStart,
+    private void updateCornerRoundnessOnScroll(
+            ActivatableNotificationView anv,
+            float viewStart,
             float shelfStart) {
 
         final boolean isUnlockedHeadsUp = !mAmbientState.isOnKeyguard()
                 && !mAmbientState.isShadeExpanded()
                 && anv instanceof ExpandableNotificationRow
-                && ((ExpandableNotificationRow) anv).isHeadsUp();
+                && anv.isHeadsUp();
 
         final boolean isHunGoingToShade = mAmbientState.isShadeExpanded()
                 && anv == mAmbientState.getTrackedHeadsUpRow();
@@ -496,49 +513,45 @@
             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();
         final float cornerAnimationTop = shelfStart - cornerAnimationDistance;
 
-        if (viewEnd >= cornerAnimationTop) {
-            // Round bottom corners within animation bounds
-            final float changeFraction = MathUtils.saturate(
-                    (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
-            anv.requestBottomRoundness(
-                    anv.isLastInSection() ? 1f : changeFraction,
-                    /* animate = */ false,
-                    SourceType.OnScroll);
-
-        } else if (viewEnd < cornerAnimationTop) {
-            // 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,
-                    /* animate = */ false,
-                    SourceType.OnScroll);
+        final SourceType sourceType;
+        if (mUseRoundnessSourceTypes) {
+            sourceType = SHELF_SCROLL;
+        } else {
+            sourceType = LegacySourceType.OnScroll;
         }
 
-        if (viewStart >= cornerAnimationTop) {
+        final float topValue;
+        if (!mUseRoundnessSourceTypes && anv.isFirstInSection()) {
+            topValue = 1f;
+        } else if (viewStart >= cornerAnimationTop) {
             // Round top corners within animation bounds
-            final float changeFraction = MathUtils.saturate(
+            topValue = MathUtils.saturate(
                     (viewStart - cornerAnimationTop) / cornerAnimationDistance);
-            anv.requestTopRoundness(
-                    anv.isFirstInSection() ? 1f : changeFraction,
-                    false,
-                    SourceType.OnScroll);
-
-        } else if (viewStart < cornerAnimationTop) {
+        } else {
             // 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,
-                    SourceType.OnScroll);
+            topValue = 0f;
         }
+        anv.requestTopRoundness(topValue, sourceType, /* animate = */ false);
+
+        final float bottomValue;
+        if (!mUseRoundnessSourceTypes && anv.isLastInSection()) {
+            bottomValue = 1f;
+        } else if (viewEnd >= cornerAnimationTop) {
+            // Round bottom corners within animation bounds
+            bottomValue = MathUtils.saturate(
+                    (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
+        } else {
+            // Fast scroll skips frames and leaves corners with unfinished rounding.
+            // Reset top and bottom corners outside of animation bounds.
+            bottomValue = 0f;
+        }
+        anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false);
     }
 
     /**
@@ -624,10 +637,11 @@
 
     /**
      * Update the clipping of this view.
+     *
      * @return the amount that our own top should be clipped
      */
     private int updateNotificationClipHeight(ExpandableView view,
-            float notificationClipEnd, int childIndex) {
+                                             float notificationClipEnd, int childIndex) {
         float viewEnd = view.getTranslationY() + view.getActualHeight();
         boolean isPinned = (view.isPinned() || view.isHeadsUpAnimatingAway())
                 && !mAmbientState.isDozingAndNotPulsing(view);
@@ -639,7 +653,8 @@
         }
         if (!isPinned) {
             if (viewEnd > notificationClipEnd && !shouldClipOwnTop) {
-                int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
+                int clipBottomAmount =
+                        mEnableNotificationClipping ? (int) (viewEnd - notificationClipEnd) : 0;
                 view.setClipBottomAmount(clipBottomAmount);
             } else {
                 view.setClipBottomAmount(0);
@@ -654,7 +669,7 @@
 
     @Override
     public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
-            int outlineTranslation) {
+                                       int outlineTranslation) {
         if (!mHasItemsInStableShelf) {
             shadowIntensity = 0.0f;
         }
@@ -662,18 +677,24 @@
     }
 
     /**
-     * @param i Index of the view in the host layout.
-     * @param view The current ExpandableView.
-     * @param scrollingFast Whether we are scrolling fast.
+     * @param i                 Index of the view in the host layout.
+     * @param view              The current ExpandableView.
+     * @param scrollingFast     Whether we are scrolling fast.
      * @param expandingAnimated Whether we are expanding a notification.
-     * @param isLastChild Whether this is the last view.
-     * @param shelfClipStart The point at which notifications start getting clipped by the shelf.
+     * @param isLastChild       Whether this is the last view.
+     * @param shelfClipStart    The point at which notifications start getting clipped by the shelf.
      * @return The amount how much this notification is in the shelf.
-     *         0f is not in shelf. 1f is completely in shelf.
+     * 0f is not in shelf. 1f is completely in shelf.
      */
     @VisibleForTesting
-    public float getAmountInShelf(int i, ExpandableView view, boolean scrollingFast,
-            boolean expandingAnimated, boolean isLastChild, float shelfClipStart) {
+    public float getAmountInShelf(
+            int i,
+            ExpandableView view,
+            boolean scrollingFast,
+            boolean expandingAnimated,
+            boolean isLastChild,
+            float shelfClipStart
+    ) {
 
         // Let's calculate how much the view is in the shelf
         float viewStart = view.getTranslationY();
@@ -752,8 +773,13 @@
         return start;
     }
 
-    private void updateIconPositioning(ExpandableView view, float iconTransitionAmount,
-            boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) {
+    private void updateIconPositioning(
+            ExpandableView view,
+            float iconTransitionAmount,
+            boolean scrollingFast,
+            boolean expandingAnimated,
+            boolean isLastChild
+    ) {
         StatusBarIconView icon = view.getShelfIcon();
         NotificationIconContainer.IconState iconState = getIconState(icon);
         if (iconState == null) {
@@ -808,13 +834,13 @@
         iconState.hidden = isAppearing
                 || (view instanceof ExpandableNotificationRow
                 && ((ExpandableNotificationRow) view).isLowPriority()
-                && mShelfIcons.hasMaxNumDot())
+                && mShelfIcons.areIconsOverflowing())
                 || (transitionAmount == 0.0f && !iconState.isAnimating(icon))
                 || row.isAboveShelf()
                 || row.showingPulsing()
                 || row.getTranslationZ() > mAmbientState.getBaseZHeight();
 
-        iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount;
+        iconState.iconAppearAmount = iconState.hidden ? 0f : transitionAmount;
 
         // Fade in icons at shelf start
         // This is important for conversation icons, which are badged and need x reset
@@ -844,7 +870,7 @@
     }
 
     private float getFullyClosedTranslation() {
-        return - (getIntrinsicHeight() - mStatusBarHeight) / 2;
+        return -(getIntrinsicHeight() - mStatusBarHeight) / 2;
     }
 
     @Override
@@ -901,7 +927,7 @@
 
     /**
      * @return whether the shelf has any icons in it when a potential animation has finished, i.e
-     *         if the current state would be applied right now
+     * if the current state would be applied right now
      */
     public boolean hasItemsInStableShelf() {
         return mHasItemsInStableShelf;
@@ -959,7 +985,7 @@
 
     @Override
     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
-            int oldTop, int oldRight, int oldBottom) {
+                               int oldTop, int oldRight, int oldBottom) {
         updateRelativeOffset();
     }
 
@@ -976,6 +1002,15 @@
         mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
     }
 
+    /**
+     * This method resets the OnScroll roundness of a view to 0f
+     * <p>
+     * Note: This should be the only class that handles roundness {@code SourceType.OnScroll}
+     */
+    public static void resetLegacyOnScrollRoundness(ExpandableView expandableView) {
+        expandableView.requestRoundnessReset(LegacySourceType.OnScroll);
+    }
+
     public class ShelfState extends ExpandableViewState {
         private boolean hasItemsInStableShelf;
         private ExpandableView firstViewInShelf;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
index 3b1fa17..bb84c75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
@@ -18,6 +18,8 @@
 
 import android.view.View;
 
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
@@ -42,14 +44,17 @@
     private AmbientState mAmbientState;
 
     @Inject
-    public NotificationShelfController(NotificationShelf notificationShelf,
+    public NotificationShelfController(
+            NotificationShelf notificationShelf,
             ActivatableNotificationViewController activatableNotificationViewController,
             KeyguardBypassController keyguardBypassController,
-            SysuiStatusBarStateController statusBarStateController) {
+            SysuiStatusBarStateController statusBarStateController,
+            FeatureFlags featureFlags) {
         mView = notificationShelf;
         mActivatableNotificationViewController = activatableNotificationViewController;
         mKeyguardBypassController = keyguardBypassController;
         mStatusBarStateController = statusBarStateController;
+        mView.useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES));
         mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
@@ -88,7 +93,7 @@
 
     public @View.Visibility int getVisibility() {
         return mView.getVisibility();
-    };
+    }
 
     public void setCollapsedIcons(NotificationIconContainer notificationIcons) {
         mView.setCollapsedIcons(notificationIcons);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
index 13d8adb..572c0e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
@@ -51,8 +51,8 @@
                     updateResources()
                 }
             })
-        dumpManager.registerDumpable("SplitShadeLockscreenOverScroller") { printWriter, _ ->
-            dump(printWriter)
+        dumpManager.registerCriticalDumpable("SplitShadeLockscreenOverScroller") { pw, _ ->
+            dump(pw)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index c04bc82..58ce447 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;
@@ -54,6 +53,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.policy.CallbackController;
 
@@ -153,13 +153,18 @@
     private Interpolator mDozeInterpolator = Interpolators.FAST_OUT_SLOW_IN;
 
     @Inject
-    public StatusBarStateControllerImpl(UiEventLogger uiEventLogger, DumpManager dumpManager,
-            InteractionJankMonitor interactionJankMonitor) {
+    public StatusBarStateControllerImpl(
+            UiEventLogger uiEventLogger,
+            DumpManager dumpManager,
+            InteractionJankMonitor interactionJankMonitor,
+            ShadeExpansionStateManager shadeExpansionStateManager
+    ) {
         mUiEventLogger = uiEventLogger;
         mInteractionJankMonitor = interactionJankMonitor;
         for (int i = 0; i < HISTORY_SIZE; i++) {
             mHistoricalRecords[i] = new HistoricalState();
         }
+        shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
 
         dumpManager.registerDumpable(this);
     }
@@ -263,21 +268,6 @@
     }
 
     @Override
-    public boolean setPanelExpanded(boolean expanded) {
-        if (mIsExpanded == expanded) {
-            return false;
-        }
-        mIsExpanded = expanded;
-        String tag = getClass().getSimpleName() + "#setIsExpanded";
-        DejankUtils.startDetectingBlockingIpcs(tag);
-        for (RankedListener rl : new ArrayList<>(mListeners)) {
-            rl.mListener.onExpandedChanged(mIsExpanded);
-        }
-        DejankUtils.stopDetectingBlockingIpcs(tag);
-        return true;
-    }
-
-    @Override
     public float getInterpolatedDozeAmount() {
         return mDozeInterpolator.getInterpolation(mDozeAmount);
     }
@@ -325,6 +315,18 @@
         }
     }
 
+    private void onShadeExpansionFullyChanged(Boolean isExpanded) {
+        if (mIsExpanded != isExpanded) {
+            mIsExpanded = isExpanded;
+            String tag = getClass().getSimpleName() + "#setIsExpanded";
+            DejankUtils.startDetectingBlockingIpcs(tag);
+            for (RankedListener rl : new ArrayList<>(mListeners)) {
+                rl.mListener.onExpandedChanged(mIsExpanded);
+            }
+            DejankUtils.stopDetectingBlockingIpcs(tag);
+        }
+    }
+
     private void startDozeAnimation() {
         if (mDozeAmount == 0f || mDozeAmount == 1f) {
             mDozeInterpolator = mIsDozing
@@ -497,9 +499,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 +516,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..5a392a9 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;
 
@@ -109,13 +109,6 @@
     void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated);
 
     /**
-     * Update the expanded state from {@link CentralSurfaces}'s perspective
-     * @param expanded are we expanded?
-     * @return {@code true} if the state changed, else {@code false}
-     */
-    boolean setPanelExpanded(boolean expanded);
-
-    /**
      * Sets whether to leave status bar open when hiding keyguard
      */
     void setLeaveOpenOnKeyguardHide(boolean leaveOpen);
@@ -154,7 +147,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/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index c070fcc..324e972 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -24,17 +24,21 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
 
 import org.jetbrains.annotations.NotNull;
 
 import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 
 import javax.inject.Inject;
 
 /**
- *
+ * A Helper class that offloads {@link Vibrator} calls to a different thread.
+ * {@link Vibrator} makes blocking calls that may cause SysUI to ANR.
+ * TODO(b/245528624): Use regular Vibrator instance once new APIs are available.
  */
 @SysUISingleton
 public class VibratorHelper {
@@ -53,10 +57,18 @@
     private final Executor mExecutor;
 
     /**
-     *
+     * Creates a vibrator helper on a new single threaded {@link Executor}.
      */
     @Inject
-    public VibratorHelper(@Nullable Vibrator vibrator, @Background Executor executor) {
+    public VibratorHelper(@Nullable Vibrator vibrator) {
+        this(vibrator, Executors.newSingleThreadExecutor());
+    }
+
+    /**
+     * Creates new vibrator helper on a specific {@link Executor}.
+     */
+    @VisibleForTesting
+    public VibratorHelper(@Nullable Vibrator vibrator, Executor executor) {
         mExecutor = executor;
         mVibrator = vibrator;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
index ec221b7..c523d22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
@@ -15,9 +15,7 @@
  */
 package com.android.systemui.statusbar.connectivity;
 
-import static com.android.settingslib.mobile.MobileMappings.getDefaultIcons;
-import static com.android.settingslib.mobile.MobileMappings.getIconKey;
-import static com.android.settingslib.mobile.MobileMappings.mapIconSets;
+import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
 
 import android.content.Context;
 import android.content.Intent;
@@ -46,6 +44,7 @@
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.settingslib.net.SignalStrengthUtil;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy;
 import com.android.systemui.util.CarrierConfigTracker;
 
 import java.io.PrintWriter;
@@ -63,6 +62,7 @@
     private final TelephonyManager mPhone;
     private final CarrierConfigTracker mCarrierConfigTracker;
     private final SubscriptionDefaults mDefaults;
+    private final MobileMappingsProxy mMobileMappingsProxy;
     private final String mNetworkNameDefault;
     private final String mNetworkNameSeparator;
     private final ContentObserver mObserver;
@@ -121,6 +121,7 @@
             TelephonyManager phone,
             CallbackHandler callbackHandler,
             NetworkControllerImpl networkController,
+            MobileMappingsProxy mobileMappingsProxy,
             SubscriptionInfo info,
             SubscriptionDefaults defaults,
             Looper receiverLooper,
@@ -135,13 +136,14 @@
         mPhone = phone;
         mDefaults = defaults;
         mSubscriptionInfo = info;
+        mMobileMappingsProxy = mobileMappingsProxy;
         mNetworkNameSeparator = getTextIfExists(
                 R.string.status_bar_network_name_separator).toString();
         mNetworkNameDefault = getTextIfExists(
                 com.android.internal.R.string.lockscreen_carrier_default).toString();
 
-        mNetworkToIconLookup = mapIconSets(mConfig);
-        mDefaultIcons = getDefaultIcons(mConfig);
+        mNetworkToIconLookup = mMobileMappingsProxy.mapIconSets(mConfig);
+        mDefaultIcons = mMobileMappingsProxy.getDefaultIcons(mConfig);
 
         String networkName = info.getCarrierName() != null ? info.getCarrierName().toString()
                 : mNetworkNameDefault;
@@ -161,8 +163,8 @@
     void setConfiguration(Config config) {
         mConfig = config;
         updateInflateSignalStrength();
-        mNetworkToIconLookup = mapIconSets(mConfig);
-        mDefaultIcons = getDefaultIcons(mConfig);
+        mNetworkToIconLookup = mMobileMappingsProxy.mapIconSets(mConfig);
+        mDefaultIcons = mMobileMappingsProxy.getDefaultIcons(mConfig);
         updateTelephony();
     }
 
@@ -271,8 +273,9 @@
             dataContentDescription = mContext.getString(R.string.data_connection_no_internet);
         }
 
-        final QsInfo qsInfo = getQsInfo(contentDescription, icons.dataType);
-        final SbInfo sbInfo = getSbInfo(contentDescription, icons.dataType);
+        int iconId = mCurrentState.getNetworkTypeIcon(mContext);
+        final QsInfo qsInfo = getQsInfo(contentDescription, iconId);
+        final SbInfo sbInfo = getSbInfo(contentDescription, iconId);
 
         MobileDataIndicators mobileDataIndicators = new MobileDataIndicators(
                 sbInfo.icon,
@@ -373,6 +376,10 @@
         } else if (action.equals(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
             updateDataSim();
             notifyListenersIfNecessary();
+        } else if (action.equals(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED)) {
+            int carrierId = intent.getIntExtra(
+                    TelephonyManager.EXTRA_CARRIER_ID, UNKNOWN_CARRIER_ID);
+            mCurrentState.setCarrierId(carrierId);
         }
     }
 
@@ -477,7 +484,8 @@
             mCurrentState.level = getSignalLevel(mCurrentState.signalStrength);
         }
 
-        String iconKey = getIconKey(mCurrentState.telephonyDisplayInfo);
+        mCurrentState.setCarrierId(mPhone.getSimCarrierId());
+        String iconKey = mMobileMappingsProxy.getIconKey(mCurrentState.telephonyDisplayInfo);
         if (mNetworkToIconLookup.get(iconKey) != null) {
             mCurrentState.iconGroup = mNetworkToIconLookup.get(iconKey);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
index 7938179..a323454 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
@@ -22,6 +22,7 @@
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.MobileStatusTracker
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import com.android.systemui.util.CarrierConfigTracker
 import javax.inject.Inject
 
@@ -33,6 +34,7 @@
     val context: Context,
     val callbackHandler: CallbackHandler,
     val carrierConfigTracker: CarrierConfigTracker,
+    val mobileMappings: MobileMappingsProxy,
 ) {
     fun createMobileSignalController(
         config: MobileMappings.Config,
@@ -56,6 +58,7 @@
             phone,
             callbackHandler,
             networkController,
+            mobileMappings,
             subscriptionInfo,
             subscriptionDefaults,
             receiverLooper,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
index f20d206..1fb6a98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
@@ -16,10 +16,14 @@
 
 package com.android.systemui.statusbar.connectivity
 
+import android.annotation.DrawableRes
+import android.content.Context
 import android.telephony.ServiceState
 import android.telephony.SignalStrength
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyManager
+import com.android.internal.annotations.VisibleForTesting
+import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.Utils
 import com.android.settingslib.mobile.MobileStatusTracker.MobileStatus
 import com.android.settingslib.mobile.TelephonyIcons
@@ -41,7 +45,7 @@
     @JvmField var roaming: Boolean = false,
     @JvmField var dataState: Int = TelephonyManager.DATA_DISCONNECTED,
     // Tracks the on/off state of the defaultDataSubscription
-    @JvmField var defaultDataOff: Boolean = false
+    @JvmField var defaultDataOff: Boolean = false,
 ) : ConnectivityState() {
 
     @JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
@@ -49,6 +53,11 @@
     @JvmField var serviceState: ServiceState? = null
     @JvmField var signalStrength: SignalStrength? = null
 
+    var carrierId = TelephonyManager.UNKNOWN_CARRIER_ID
+
+    @VisibleForTesting
+    var networkTypeResIdCache: NetworkTypeResIdCache = NetworkTypeResIdCache()
+
     /** @return true if this state is disabled or not default data */
     val isDataDisabledOrNotDefault: Boolean
         get() = (iconGroup === TelephonyIcons.DATA_DISABLED ||
@@ -125,6 +134,21 @@
         return serviceState != null && serviceState!!.roaming
     }
 
+    /**
+     *
+     * Load the (potentially customized) icon resource id for the current network type. Note that
+     * this operation caches the result. Note that reading the [MobileIconGroup.dataType] field
+     * directly will not yield correct results in cases where the carrierId has an associated
+     * override. This is the preferred method for getting the network type indicator.
+     *
+     * @return a drawable res id appropriate for the current (carrierId, networkType) pair
+     */
+    @DrawableRes
+    fun getNetworkTypeIcon(context: Context): Int {
+        val icon = (iconGroup as MobileIconGroup)
+        return networkTypeResIdCache.get(icon, carrierId, context)
+    }
+
     fun setFromMobileStatus(mobileStatus: MobileStatus) {
         activityIn = mobileStatus.activityIn
         activityOut = mobileStatus.activityOut
@@ -140,6 +164,7 @@
         super.toString(builder)
         builder.append(',')
         builder.append("dataSim=$dataSim,")
+        builder.append("carrierId=$carrierId")
         builder.append("networkName=$networkName,")
         builder.append("networkNameData=$networkNameData,")
         builder.append("dataConnected=$dataConnected,")
@@ -157,6 +182,8 @@
         builder.append("voiceServiceState=${getVoiceServiceState()},")
         builder.append("isInService=${isInService()},")
 
+        builder.append("networkTypeIconCache=$networkTypeResIdCache")
+
         builder.append("serviceState=${serviceState?.minLog() ?: "(null)"},")
         builder.append("signalStrength=${signalStrength?.minLog() ?: "(null)"},")
         builder.append("displayInfo=$telephonyDisplayInfo")
@@ -164,6 +191,7 @@
 
     override fun tableColumns(): List<String> {
         val columns = listOf("dataSim",
+            "carrierId",
             "networkName",
             "networkNameData",
             "dataConnected",
@@ -178,6 +206,7 @@
             "showQuickSettingsRatIcon",
             "voiceServiceState",
             "isInService",
+            "networkTypeIconCache",
             "serviceState",
             "signalStrength",
             "displayInfo")
@@ -187,6 +216,7 @@
 
     override fun tableData(): List<String> {
         val columns = listOf(dataSim,
+                carrierId,
                 networkName,
                 networkNameData,
                 dataConnected,
@@ -201,6 +231,7 @@
                 showQuickSettingsRatIcon(),
                 getVoiceServiceState(),
                 isInService(),
+                networkTypeResIdCache,
                 serviceState?.minLog() ?: "(null)",
                 signalStrength?.minLog() ?: "(null)",
                 telephonyDisplayInfo).map { it.toString() }
@@ -217,6 +248,7 @@
 
         if (networkName != other.networkName) return false
         if (networkNameData != other.networkNameData) return false
+        if (carrierId != other.carrierId) return false
         if (dataSim != other.dataSim) return false
         if (dataConnected != other.dataConnected) return false
         if (isEmergency != other.isEmergency) return false
@@ -238,6 +270,7 @@
         var result = super.hashCode()
         result = 31 * result + (networkName?.hashCode() ?: 0)
         result = 31 * result + (networkNameData?.hashCode() ?: 0)
+        result = 31 * result + (carrierId.hashCode())
         result = 31 * result + dataSim.hashCode()
         result = 31 * result + dataConnected.hashCode()
         result = 31 * result + isEmergency.hashCode()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 450b757..336356e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -22,6 +22,7 @@
 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -38,6 +39,7 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
@@ -66,7 +68,7 @@
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.LongRunning;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
@@ -75,7 +77,8 @@
 import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.DataSaverControllerImpl;
@@ -127,7 +130,7 @@
     private final boolean mHasMobileDataFeature;
     private final SubscriptionDefaults mSubDefaults;
     private final DataSaverController mDataSaverController;
-    private final CurrentUserTracker mUserTracker;
+    private final UserTracker mUserTracker;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final DemoModeController mDemoModeController;
     private final Object mLock = new Object();
@@ -138,7 +141,7 @@
     private final MobileSignalControllerFactory mMobileFactory;
 
     private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener;
-    private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private int mActiveMobileDataSubscription = INVALID_SUBSCRIPTION_ID;
 
     // Subcontrollers.
     @VisibleForTesting
@@ -190,6 +193,7 @@
     private final Executor mBgExecutor;
     // Handler that all callbacks are made on.
     private final CallbackHandler mCallbackHandler;
+    private final StatusBarPipelineFlags mStatusBarPipelineFlags;
 
     private int mEmergencySource;
     private boolean mIsEmergency;
@@ -211,23 +215,36 @@
                 }
             };
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    NetworkControllerImpl.this.onUserSwitched(newUser);
+                }
+            };
+
     /**
      * Construct this controller object and register for updates.
+     *
+     * {@code @LongRunning} looper and bgExecutor instead {@code @Background} ones are used to
+     * address the b/246456655. This can be reverted after b/240663726 is fixed.
      */
     @Inject
     public NetworkControllerImpl(
             Context context,
-            @Background Looper bgLooper,
-            @Background Executor bgExecutor,
+            @LongRunning Looper bgLooper,
+            @LongRunning Executor bgExecutor,
             SubscriptionManager subscriptionManager,
             CallbackHandler callbackHandler,
             DeviceProvisionedController deviceProvisionedController,
             BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker,
             ConnectivityManager connectivityManager,
             TelephonyManager telephonyManager,
             TelephonyListenerManager telephonyListenerManager,
             @Nullable WifiManager wifiManager,
             AccessPointControllerImpl accessPointController,
+            StatusBarPipelineFlags statusBarPipelineFlags,
             DemoModeController demoModeController,
             CarrierConfigTracker carrierConfigTracker,
             WifiStatusTrackerFactory trackerFactory,
@@ -246,10 +263,12 @@
                 bgExecutor,
                 callbackHandler,
                 accessPointController,
+                statusBarPipelineFlags,
                 new DataUsageController(context),
                 new SubscriptionDefaults(),
                 deviceProvisionedController,
                 broadcastDispatcher,
+                userTracker,
                 demoModeController,
                 carrierConfigTracker,
                 trackerFactory,
@@ -272,10 +291,12 @@
             Executor bgExecutor,
             CallbackHandler callbackHandler,
             AccessPointControllerImpl accessPointController,
+            StatusBarPipelineFlags statusBarPipelineFlags,
             DataUsageController dataUsageController,
             SubscriptionDefaults defaultsHandler,
             DeviceProvisionedController deviceProvisionedController,
             BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker,
             DemoModeController demoModeController,
             CarrierConfigTracker carrierConfigTracker,
             WifiStatusTrackerFactory trackerFactory,
@@ -292,6 +313,7 @@
         mBgLooper = bgLooper;
         mBgExecutor = bgExecutor;
         mCallbackHandler = callbackHandler;
+        mStatusBarPipelineFlags = statusBarPipelineFlags;
         mDataSaverController = new DataSaverControllerImpl(context);
         mBroadcastDispatcher = broadcastDispatcher;
         mMobileFactory = mobileFactory;
@@ -332,13 +354,9 @@
 
         // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
         updateAirplaneMode(true /* force callback */);
-        mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                NetworkControllerImpl.this.onUserSwitched(newUserId);
-            }
-        };
-        mUserTracker.startTracking();
+        mUserTracker = userTracker;
+        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
+
         deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
             @Override
             public void onUserSetupChanged() {
@@ -502,6 +520,7 @@
         filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
         filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
         filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
+        filter.addAction(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED);
         filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
         mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
         mListening = true;
@@ -792,6 +811,20 @@
                 mConfig = Config.readConfig(mContext);
                 mReceiverHandler.post(this::handleConfigurationChanged);
                 break;
+
+            case TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED: {
+                // Notify the relevant MobileSignalController of the change
+                int subId = intent.getIntExtra(
+                        TelephonyManager.EXTRA_SUBSCRIPTION_ID,
+                        INVALID_SUBSCRIPTION_ID
+                );
+                if (SubscriptionManager.isValidSubscriptionId(subId)) {
+                    if (mMobileSignalControllers.indexOfKey(subId) >= 0) {
+                        mMobileSignalControllers.get(subId).handleBroadcast(intent);
+                    }
+                }
+            }
+            break;
             case Intent.ACTION_SIM_STATE_CHANGED:
                 // Avoid rebroadcast because SysUI is direct boot aware.
                 if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
@@ -819,7 +852,7 @@
                 break;
             default:
                 int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
-                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+                        INVALID_SUBSCRIPTION_ID);
                 if (SubscriptionManager.isValidSubscriptionId(subId)) {
                     if (mMobileSignalControllers.indexOfKey(subId) >= 0) {
                         mMobileSignalControllers.get(subId).handleBroadcast(intent);
@@ -1306,7 +1339,7 @@
             mWifiSignalController.notifyListeners();
         }
         String sims = args.getString("sims");
-        if (sims != null) {
+        if (sims != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
             int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
             List<SubscriptionInfo> subs = new ArrayList<>();
             if (num != mMobileSignalControllers.size()) {
@@ -1329,12 +1362,15 @@
             mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
         }
         String mobile = args.getString("mobile");
-        if (mobile != null) {
+        if (mobile != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
             boolean show = mobile.equals("show");
             String datatype = args.getString("datatype");
             String slotString = args.getString("slot");
             int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString);
             slot = MathUtils.constrain(slot, 0, 8);
+            String carrierIdString = args.getString("carrierid");
+            int carrierId = TextUtils.isEmpty(carrierIdString) ? 0
+                    : Integer.parseInt(carrierIdString);
             // Ensure we have enough sim slots
             List<SubscriptionInfo> subs = new ArrayList<>();
             while (mMobileSignalControllers.size() <= slot) {
@@ -1346,6 +1382,9 @@
             }
             // Hack to index linearly for easy use.
             MobileSignalController controller = mMobileSignalControllers.valueAt(slot);
+            if (carrierId != 0) {
+                controller.getState().setCarrierId(carrierId);
+            }
             controller.getState().dataSim = datatype != null;
             controller.getState().isDefault = datatype != null;
             controller.getState().dataConnected = datatype != null;
@@ -1408,7 +1447,7 @@
             controller.notifyListeners();
         }
         String carrierNetworkChange = args.getString("carriernetworkchange");
-        if (carrierNetworkChange != null) {
+        if (carrierNetworkChange != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
             boolean show = carrierNetworkChange.equals("show");
             for (int i = 0; i < mMobileSignalControllers.size(); i++) {
                 MobileSignalController controller = mMobileSignalControllers.valueAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt
new file mode 100644
index 0000000..9be7ee9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt
@@ -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.
+ */
+
+package com.android.systemui.statusbar.connectivity
+
+import android.annotation.DrawableRes
+import android.content.Context
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
+import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
+
+/**
+ * Cache for network type resource IDs.
+ *
+ * The default framework behavior is to have a statically defined icon per network type. See
+ * [MobileIconGroup] for the standard mapping.
+ *
+ * For the case of carrierId-defined overrides, we want to check [MobileIconCarrierIdOverrides] for
+ * an existing icon override, and cache the result of the operation
+ */
+class NetworkTypeResIdCache(
+    private val overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
+) {
+    @DrawableRes private var cachedResId: Int = 0
+    private var lastCarrierId: Int? = null
+    private var lastIconGroup: MobileIconGroup? = null
+    private var isOverridden: Boolean = false
+
+    @DrawableRes
+    fun get(iconGroup: MobileIconGroup, carrierId: Int, context: Context): Int {
+        if (lastCarrierId != carrierId || lastIconGroup != iconGroup) {
+            lastCarrierId = carrierId
+            lastIconGroup = iconGroup
+
+            val maybeOverride = calculateOverriddenIcon(iconGroup, carrierId, context)
+            if (maybeOverride > 0) {
+                cachedResId = maybeOverride
+                isOverridden = true
+            } else {
+                cachedResId = iconGroup.dataType
+                isOverridden = false
+            }
+        }
+
+        return cachedResId
+    }
+
+    override fun toString(): String {
+        return "networkTypeResIdCache={id=$cachedResId, isOverridden=$isOverridden}"
+    }
+
+    @DrawableRes
+    private fun calculateOverriddenIcon(
+        iconGroup: MobileIconGroup,
+        carrierId: Int,
+        context: Context,
+    ): Int {
+        val name = iconGroup.name
+        if (!overrides.carrierIdEntryExists(carrierId)) {
+            return 0
+        }
+
+        return overrides.getOverrideFor(carrierId, name, context.resources)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt
index a02dd34..42b874f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt
@@ -37,6 +37,13 @@
  * own [Configuration] and track resources based on the full set of available mcc-mnc combinations.
  *
  * (for future reference: b/240555502 is the initiating bug for this)
+ *
+ * NOTE: MCC/MNC qualifiers are not sufficient to fully describe a network type icon qualified by
+ * network type + carrier ID. This class exists to keep the legacy behavior of using the MCC/MNC
+ * resource qualifiers working, but if a carrier-specific icon is requested, then the override
+ * provided by [MobileIconCarrierIdOverrides] will take precedence.
+ *
+ * TODO(b/258503704): consider removing this class in favor of the `carrierId` overrides
  */
 @SysUISingleton
 class MobileContextProvider
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index eacb18e..14d0d7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -300,7 +300,7 @@
 
             @Override
             public boolean isShowingAlternateAuthOnUnlock() {
-                return statusBarKeyguardViewManager.get().shouldShowAltAuth();
+                return statusBarKeyguardViewManager.get().canShowAlternateBouncer();
             }
         };
         return new DialogLaunchAnimator(callback, interactionJankMonitor);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 143c697..bd5b8f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -94,7 +94,11 @@
     private val views: Sequence<View>
         get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
 
-    private var showingListener: ShowingListener? = null
+    var showingListener: ShowingListener? = null
+        set(value) {
+            field = value
+        }
+        get() = field
 
     init {
         contentInsetsProvider.addCallback(object : StatusBarContentInsetsChangedListener {
@@ -147,10 +151,6 @@
         return uiExecutor
     }
 
-    fun setShowingListener(l: ShowingListener?) {
-        showingListener = l
-    }
-
     @UiThread
     fun setNewRotation(rot: Int) {
         dlog("updateRotation: $rot")
@@ -219,7 +219,7 @@
 
     // Update the gravity and margins of the privacy views
     @UiThread
-    private fun updateRotations(rotation: Int, paddingTop: Int) {
+    open fun updateRotations(rotation: Int, paddingTop: Int) {
         // To keep a view in the corner, its gravity is always the description of its current corner
         // Therefore, just figure out which view is in which corner. This turns out to be something
         // like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
@@ -250,7 +250,7 @@
     }
 
     @UiThread
-    private fun setCornerSizes(state: ViewState) {
+    open fun setCornerSizes(state: ViewState) {
         // StatusBarContentInsetsProvider can tell us the location of the privacy indicator dot
         // in every rotation. The only thing we need to check is rtl
         val rtl = state.layoutRtl
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..8bb2d46 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,28 @@
     private var showSensitiveContentForManagedUser = false
     private var managedUserHandle: UserHandle? = null
 
-    private val updateFun = object : RegionSamplingInstance.UpdateColorCallback {
-        override fun updateColors() {
-            updateTextColorFromRegionSampler()
-        }
-    }
+    // TODO(b/202758428): refactor so that we can test color updates via region samping, similar to
+    //  how we test color updates when theme changes (See testThemeChangeUpdatesTextColor).
+    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(
-                    v,
-                    uiExecutor,
-                    bgExecutor,
-                    regionSamplingEnabled,
-                    updateFun
-            )
-            initializeTextColors(regionSamplingInstance)
-            regionSamplingInstance.startRegionSampler()
-            regionSamplingInstances.put(v, regionSamplingInstance)
+            if (regionSamplingEnabled) {
+                var regionSampler = RegionSampler(
+                        v,
+                        uiExecutor,
+                        bgExecutor,
+                        regionSamplingEnabled,
+                        updateFun
+                )
+                initializeTextColors(regionSampler)
+                regionSampler.startRegionSampler()
+                regionSamplers.put(v, regionSampler)
+            }
+
             connectSession()
 
             updateTextColorFromWallpaper()
@@ -131,9 +133,11 @@
         override fun onViewDetachedFromWindow(v: View) {
             smartspaceViews.remove(v as SmartspaceView)
 
-            var regionSamplingInstance = regionSamplingInstances.getValue(v)
-            regionSamplingInstance.stopRegionSampler()
-            regionSamplingInstances.remove(v)
+            if (regionSamplingEnabled) {
+                var regionSampler = regionSamplers.getValue(v)
+                regionSampler.stopRegionSampler()
+                regionSamplers.remove(v)
+            }
 
             if (smartspaceViews.isEmpty()) {
                 disconnect()
@@ -238,19 +242,24 @@
 
         ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
             override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) {
-                activityStarter.startActivity(
-                    intent,
-                    true, /* dismissShade */
-                    null, /* launch animator - looks bad with the transparent smartspace bg */
-                    showOnLockscreen
-                )
+                if (showOnLockscreen) {
+                    activityStarter.startActivity(
+                            intent,
+                            true, /* dismissShade */
+                            // launch animator - looks bad with the transparent smartspace bg
+                            null,
+                            true
+                    )
+                } else {
+                    activityStarter.postStartActivityDismissingKeyguard(intent, 0)
+                }
             }
 
             override fun startPendingIntent(pi: PendingIntent, showOnLockscreen: Boolean) {
                 if (showOnLockscreen) {
                     pi.send()
                 } else {
-                    activityStarter.startPendingIntentDismissingKeyguard(pi)
+                    activityStarter.postStartActivityDismissingKeyguard(pi)
                 }
             }
         })
@@ -363,19 +372,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/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 2734511..39daa13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -40,4 +40,12 @@
     val isSemiStableSortEnabled: Boolean by lazy {
         featureFlags.isEnabled(Flags.SEMI_STABLE_SORT)
     }
+
+    val shouldFilterUnseenNotifsOnKeyguard: Boolean by lazy {
+        featureFlags.isEnabled(Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD)
+    }
+
+    val isNoHunForOldWhenEnabled: Boolean by lazy {
+        featureFlags.isEnabled(Flags.NO_HUN_FOR_OLD_WHEN)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index d97b712..3e2dd05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -38,6 +38,7 @@
 import javax.inject.Inject
 import kotlin.math.min
 
+
 @SysUISingleton
 class NotificationWakeUpCoordinator @Inject constructor(
     dumpManager: DumpManager,
@@ -45,7 +46,8 @@
     private val statusBarStateController: StatusBarStateController,
     private val bypassController: KeyguardBypassController,
     private val dozeParameters: DozeParameters,
-    private val screenOffAnimationController: ScreenOffAnimationController
+    private val screenOffAnimationController: ScreenOffAnimationController,
+    private val logger: NotificationWakeUpCoordinatorLogger,
 ) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener,
     Dumpable {
 
@@ -242,6 +244,7 @@
     }
 
     override fun onDozeAmountChanged(linear: Float, eased: Float) {
+        logger.logOnDozeAmountChanged(linear, eased)
         if (overrideDozeAmountIfAnimatingScreenOff(linear)) {
             return
         }
@@ -273,6 +276,7 @@
     }
 
     override fun onStateChanged(newState: Int) {
+        logger.logOnStateChanged(newState)
         if (state == StatusBarState.SHADE && newState == StatusBarState.SHADE) {
             // The SHADE -> SHADE transition is only possible as part of cancelling the screen-off
             // animation (e.g. by fingerprint unlock).  This is done because the system is in an
@@ -320,8 +324,12 @@
     private fun overrideDozeAmountIfBypass(): Boolean {
         if (bypassController.bypassEnabled) {
             if (statusBarStateController.state == StatusBarState.KEYGUARD) {
+                logger.logSetDozeAmount("1.0", "1.0",
+                        "Override: bypass (keyguard)", StatusBarState.KEYGUARD)
                 setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)")
             } else {
+                logger.logSetDozeAmount("0.0", "0.0",
+                        "Override: bypass (shade)", statusBarStateController.state)
                 setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
             }
             return true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
new file mode 100644
index 0000000..b40ce25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -0,0 +1,55 @@
+/*
+ * 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
+
+import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import javax.inject.Inject
+
+class NotificationWakeUpCoordinatorLogger
+@Inject
+constructor(@NotificationLog private val buffer: LogBuffer) {
+    fun logSetDozeAmount(linear: String, eased: String, source: String, state: Int) {
+        buffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = linear
+                str2 = eased
+                str3 = source
+                int1 = state
+            },
+            { "setDozeAmount: linear: $str1, eased: $str2, source: $str3, state: $int1" }
+        )
+    }
+
+    fun logOnDozeAmountChanged(linear: Float, eased: Float) {
+        buffer.log(
+            TAG,
+            DEBUG,
+            {
+                double1 = linear.toDouble()
+                str2 = eased.toString()
+            },
+            { "onDozeAmountChanged($double1, $str2)" }
+        )
+    }
+
+    fun logOnStateChanged(newState: Int) {
+        buffer.log(TAG, DEBUG, { int1 = newState }, { "onStateChanged($int1)" })
+    }
+}
+
+private const val TAG = "NotificationWakeUpCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index ed7f648..0eb0000 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -74,8 +74,8 @@
     @JvmDefault
     fun requestTopRoundness(
         @FloatRange(from = 0.0, to = 1.0) value: Float,
-        animate: Boolean,
         sourceType: SourceType,
+        animate: Boolean,
     ): Boolean {
         val roundnessMap = roundableState.topRoundnessMap
         val lastValue = roundnessMap.values.maxOrNull() ?: 0f
@@ -105,6 +105,30 @@
     }
 
     /**
+     * Request the top roundness [value] for a specific [sourceType]. Animate the roundness if the
+     * view is shown.
+     *
+     * The top roundness of a [Roundable] can be defined by different [sourceType]. In case more
+     * origins require different roundness, for the same property, the maximum value will always be
+     * chosen.
+     *
+     * @param value a value between 0f and 1f.
+     * @param sourceType the source from which the request for roundness comes.
+     * @return Whether the roundness was changed.
+     */
+    @JvmDefault
+    fun requestTopRoundness(
+        @FloatRange(from = 0.0, to = 1.0) value: Float,
+        sourceType: SourceType,
+    ): Boolean {
+        return requestTopRoundness(
+            value = value,
+            sourceType = sourceType,
+            animate = roundableState.targetView.isShown
+        )
+    }
+
+    /**
      * Request the bottom roundness [value] for a specific [sourceType].
      *
      * The bottom roundness of a [Roundable] can be defined by different [sourceType]. In case more
@@ -119,8 +143,8 @@
     @JvmDefault
     fun requestBottomRoundness(
         @FloatRange(from = 0.0, to = 1.0) value: Float,
-        animate: Boolean,
         sourceType: SourceType,
+        animate: Boolean,
     ): Boolean {
         val roundnessMap = roundableState.bottomRoundnessMap
         val lastValue = roundnessMap.values.maxOrNull() ?: 0f
@@ -149,9 +173,101 @@
         return false
     }
 
+    /**
+     * Request the bottom roundness [value] for a specific [sourceType]. Animate the roundness if
+     * the view is shown.
+     *
+     * The bottom roundness of a [Roundable] can be defined by different [sourceType]. In case more
+     * origins require different roundness, for the same property, the maximum value will always be
+     * chosen.
+     *
+     * @param value value between 0f and 1f.
+     * @param sourceType the source from which the request for roundness comes.
+     * @return Whether the roundness was changed.
+     */
+    @JvmDefault
+    fun requestBottomRoundness(
+        @FloatRange(from = 0.0, to = 1.0) value: Float,
+        sourceType: SourceType,
+    ): Boolean {
+        return requestBottomRoundness(
+            value = value,
+            sourceType = sourceType,
+            animate = roundableState.targetView.isShown
+        )
+    }
+
+    /**
+     * Request the roundness [value] for a specific [sourceType].
+     *
+     * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+     * more origins require different roundness, for the same property, the maximum value will
+     * always be chosen.
+     *
+     * @param top top value between 0f and 1f.
+     * @param bottom bottom value between 0f and 1f.
+     * @param sourceType the source from which the request for roundness comes.
+     * @param animate true if it should animate to that value.
+     * @return Whether the roundness was changed.
+     */
+    @JvmDefault
+    fun requestRoundness(
+        @FloatRange(from = 0.0, to = 1.0) top: Float,
+        @FloatRange(from = 0.0, to = 1.0) bottom: Float,
+        sourceType: SourceType,
+        animate: Boolean,
+    ): Boolean {
+        val hasTopChanged =
+            requestTopRoundness(value = top, sourceType = sourceType, animate = animate)
+        val hasBottomChanged =
+            requestBottomRoundness(value = bottom, sourceType = sourceType, animate = animate)
+        return hasTopChanged || hasBottomChanged
+    }
+
+    /**
+     * Request the roundness [value] for a specific [sourceType]. Animate the roundness if the view
+     * is shown.
+     *
+     * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+     * more origins require different roundness, for the same property, the maximum value will
+     * always be chosen.
+     *
+     * @param top top value between 0f and 1f.
+     * @param bottom bottom value between 0f and 1f.
+     * @param sourceType the source from which the request for roundness comes.
+     * @return Whether the roundness was changed.
+     */
+    @JvmDefault
+    fun requestRoundness(
+        @FloatRange(from = 0.0, to = 1.0) top: Float,
+        @FloatRange(from = 0.0, to = 1.0) bottom: Float,
+        sourceType: SourceType,
+    ): Boolean {
+        return requestRoundness(
+            top = top,
+            bottom = bottom,
+            sourceType = sourceType,
+            animate = roundableState.targetView.isShown,
+        )
+    }
+
+    /**
+     * Request the roundness 0f for a [SourceType]. Animate the roundness if the view is shown.
+     *
+     * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+     * more origins require different roundness, for the same property, the maximum value will
+     * always be chosen.
+     *
+     * @param sourceType the source from which the request for roundness comes.
+     */
+    @JvmDefault
+    fun requestRoundnessReset(sourceType: SourceType) {
+        requestRoundness(top = 0f, bottom = 0f, sourceType = sourceType)
+    }
+
     /** Apply the roundness changes, usually means invalidate the [RoundableState.targetView]. */
     @JvmDefault
-    fun applyRoundness() {
+    fun applyRoundnessAndInvalidate() {
         roundableState.targetView.invalidate()
     }
 
@@ -227,7 +343,7 @@
     /** Set the current top roundness */
     internal fun setTopRoundness(
         value: Float,
-        animated: Boolean = targetView.isShown,
+        animated: Boolean,
     ) {
         PropertyAnimator.setProperty(targetView, topAnimatable, value, DURATION, animated)
     }
@@ -235,11 +351,19 @@
     /** Set the current bottom roundness */
     internal fun setBottomRoundness(
         value: Float,
-        animated: Boolean = targetView.isShown,
+        animated: Boolean,
     ) {
         PropertyAnimator.setProperty(targetView, bottomAnimatable, value, DURATION, animated)
     }
 
+    fun debugString() = buildString {
+        append("TargetView: ${targetView.hashCode()} ")
+        append("Top: $topRoundness ")
+        append(topRoundnessMap.map { "${it.key} ${it.value}" })
+        append(" Bottom: $bottomRoundness ")
+        append(bottomRoundnessMap.map { "${it.key} ${it.value}" })
+    }
+
     companion object {
         private val DURATION: AnimationProperties =
             AnimationProperties()
@@ -252,7 +376,7 @@
 
                     override fun setValue(view: View, value: Float) {
                         roundable.roundableState.topRoundness = value
-                        roundable.applyRoundness()
+                        roundable.applyRoundnessAndInvalidate()
                     }
                 },
                 R.id.top_roundess_animator_tag,
@@ -267,7 +391,7 @@
 
                     override fun setValue(view: View, value: Float) {
                         roundable.roundableState.bottomRoundness = value
-                        roundable.applyRoundness()
+                        roundable.applyRoundnessAndInvalidate()
                     }
                 },
                 R.id.bottom_roundess_animator_tag,
@@ -277,7 +401,31 @@
     }
 }
 
-enum class SourceType {
+/**
+ * Interface used to define the owner of a roundness. Usually the [SourceType] is defined as a
+ * private property of a class.
+ */
+interface SourceType {
+    companion object {
+        /**
+         * This is the most convenient way to define a new [SourceType].
+         *
+         * For example:
+         *
+         * ```kotlin
+         *     private val SECTION = SourceType.from("Section")
+         * ```
+         */
+        @JvmStatic
+        fun from(name: String) =
+            object : SourceType {
+                override fun toString() = name
+            }
+    }
+}
+
+@Deprecated("Use SourceType.from() instead", ReplaceWith("SourceType.from()"))
+enum class LegacySourceType : SourceType {
     DefaultValue,
     OnDismissAnimation,
     OnScroll,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 585d871..37d82ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -676,9 +676,6 @@
         return row != null && row.areChildrenExpanded();
     }
 
-    public boolean keepInParent() {
-        return row != null && row.keepInParent();
-    }
 
     //TODO: probably less confusing to say "is group fully visible"
     public boolean isGroupNotFullyVisible() {
@@ -698,10 +695,6 @@
         return row != null && row.isSummaryWithChildren();
     }
 
-    public void setKeepInParent(boolean keep) {
-        if (row != null) row.setKeepInParent(keep);
-    }
-
     public void onDensityOrFontScaleChanged() {
         if (row != null) row.onDensityOrFontScaleChanged();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 3ae2545..65a21a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1160,12 +1160,21 @@
                 mLogger.logParentChanged(mIterationCount, prev.getParent(), curr.getParent());
             }
 
-            if (curr.getSuppressedChanges().getParent() != null) {
-                mLogger.logParentChangeSuppressed(
+            GroupEntry currSuppressedParent = curr.getSuppressedChanges().getParent();
+            GroupEntry prevSuppressedParent = prev.getSuppressedChanges().getParent();
+            if (currSuppressedParent != null && (prevSuppressedParent == null
+                    || !prevSuppressedParent.getKey().equals(currSuppressedParent.getKey()))) {
+                mLogger.logParentChangeSuppressedStarted(
                         mIterationCount,
-                        curr.getSuppressedChanges().getParent(),
+                        currSuppressedParent,
                         curr.getParent());
             }
+            if (prevSuppressedParent != null && currSuppressedParent == null) {
+                mLogger.logParentChangeSuppressedStopped(
+                        mIterationCount,
+                        prevSuppressedParent,
+                        prev.getParent());
+            }
 
             if (curr.getSuppressedChanges().getSection() != null) {
                 mLogger.logSectionChangeSuppressed(
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/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
deleted file mode 100644
index e3d71c8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
-
-import javax.inject.Inject;
-
-/**
- * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
- * headers on the lockscreen.
- */
-@CoordinatorScope
-public class KeyguardCoordinator implements Coordinator {
-    private static final String TAG = "KeyguardCoordinator";
-    private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
-    private final SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
-    private final StatusBarStateController mStatusBarStateController;
-
-    @Inject
-    public KeyguardCoordinator(
-            KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
-            SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider,
-            StatusBarStateController statusBarStateController) {
-        mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
-        mSectionHeaderVisibilityProvider = sectionHeaderVisibilityProvider;
-        mStatusBarStateController = statusBarStateController;
-    }
-
-    @Override
-    public void attach(NotifPipeline pipeline) {
-
-        setupInvalidateNotifListCallbacks();
-        // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
-        pipeline.addFinalizeFilter(mNotifFilter);
-        mKeyguardNotificationVisibilityProvider
-                .addOnStateChangedListener(this::invalidateListFromFilter);
-        updateSectionHeadersVisibility();
-    }
-
-    private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
-        @Override
-        public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) {
-            return mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry);
-        }
-    };
-
-    // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
-    // these same updates
-    private void setupInvalidateNotifListCallbacks() {
-
-    }
-
-    private void invalidateListFromFilter(String reason) {
-        updateSectionHeadersVisibility();
-        mNotifFilter.invalidateList(reason);
-    }
-
-    private void updateSectionHeadersVisibility() {
-        boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
-        boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
-        boolean showSections = !onKeyguard && !neverShowSections;
-        mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
new file mode 100644
index 0000000..9da94ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.coordinator
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl
+import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/**
+ * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
+ * headers on the lockscreen.
+ */
+@CoordinatorScope
+class KeyguardCoordinator
+@Inject
+constructor(
+    private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
+    private val keyguardRepository: KeyguardRepository,
+    private val notifPipelineFlags: NotifPipelineFlags,
+    @Application private val scope: CoroutineScope,
+    private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
+    private val seenNotifsProvider: SeenNotificationsProviderImpl,
+    private val statusBarStateController: StatusBarStateController,
+) : Coordinator {
+
+    private val unseenNotifications = mutableSetOf<NotificationEntry>()
+
+    override fun attach(pipeline: NotifPipeline) {
+        setupInvalidateNotifListCallbacks()
+        // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
+        pipeline.addFinalizeFilter(notifFilter)
+        keyguardNotificationVisibilityProvider.addOnStateChangedListener(::invalidateListFromFilter)
+        updateSectionHeadersVisibility()
+        if (notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard) {
+            attachUnseenFilter(pipeline)
+        }
+    }
+
+    private fun attachUnseenFilter(pipeline: NotifPipeline) {
+        pipeline.addFinalizeFilter(unseenNotifFilter)
+        pipeline.addCollectionListener(collectionListener)
+        scope.launch { clearUnseenWhenKeyguardIsDismissed() }
+    }
+
+    private suspend fun clearUnseenWhenKeyguardIsDismissed() {
+        // Use collectLatest so that the suspending block is cancelled if isKeyguardShowing changes
+        // during the timeout period
+        keyguardRepository.isKeyguardShowing.collectLatest { isKeyguardShowing ->
+            if (!isKeyguardShowing) {
+                unseenNotifFilter.invalidateList("keyguard no longer showing")
+                delay(SEEN_TIMEOUT)
+                unseenNotifications.clear()
+            }
+        }
+    }
+
+    private val collectionListener =
+        object : NotifCollectionListener {
+            override fun onEntryAdded(entry: NotificationEntry) {
+                if (keyguardRepository.isKeyguardShowing()) {
+                    unseenNotifications.add(entry)
+                }
+            }
+
+            override fun onEntryUpdated(entry: NotificationEntry) {
+                if (keyguardRepository.isKeyguardShowing()) {
+                    unseenNotifications.add(entry)
+                }
+            }
+
+            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+                unseenNotifications.remove(entry)
+            }
+        }
+
+    @VisibleForTesting
+    internal val unseenNotifFilter =
+        object : NotifFilter("$TAG-unseen") {
+
+            var hasFilteredAnyNotifs = false
+
+            override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
+                when {
+                    // Don't apply filter if the keyguard isn't currently showing
+                    !keyguardRepository.isKeyguardShowing() -> false
+                    // Don't apply the filter if the notification is unseen
+                    unseenNotifications.contains(entry) -> false
+                    // Don't apply the filter to (non-promoted) group summaries
+                    //  - summary will be pruned if necessary, depending on if children are filtered
+                    entry.parent?.summary == entry -> false
+                    else -> true
+                }.also { hasFiltered -> hasFilteredAnyNotifs = hasFilteredAnyNotifs || hasFiltered }
+
+            override fun onCleanup() {
+                seenNotifsProvider.hasFilteredOutSeenNotifications = hasFilteredAnyNotifs
+                hasFilteredAnyNotifs = false
+            }
+        }
+
+    private val notifFilter: NotifFilter =
+        object : NotifFilter(TAG) {
+            override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
+                keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
+        }
+
+    // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
+    //  these same updates
+    private fun setupInvalidateNotifListCallbacks() {}
+
+    private fun invalidateListFromFilter(reason: String) {
+        updateSectionHeadersVisibility()
+        notifFilter.invalidateList(reason)
+    }
+
+    private fun updateSectionHeadersVisibility() {
+        val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD
+        val neverShowSections = sectionHeaderVisibilityProvider.neverShowSectionHeaders
+        val showSections = !onKeyguard && !neverShowSections
+        sectionHeaderVisibilityProvider.sectionHeadersVisible = showSections
+    }
+
+    companion object {
+        private const val TAG = "KeyguardCoordinator"
+        private val SEEN_TIMEOUT = 5.seconds
+    }
+}
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 3002a68..a2379b2 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
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeStateEvents;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -62,6 +63,7 @@
     private final HeadsUpManager mHeadsUpManager;
     private final ShadeStateEvents mShadeStateEvents;
     private final StatusBarStateController mStatusBarStateController;
+    private final VisibilityLocationProvider mVisibilityLocationProvider;
     private final VisualStabilityProvider mVisualStabilityProvider;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
 
@@ -94,9 +96,11 @@
             HeadsUpManager headsUpManager,
             ShadeStateEvents shadeStateEvents,
             StatusBarStateController statusBarStateController,
+            VisibilityLocationProvider visibilityLocationProvider,
             VisualStabilityProvider visualStabilityProvider,
             WakefulnessLifecycle wakefulnessLifecycle) {
         mHeadsUpManager = headsUpManager;
+        mVisibilityLocationProvider = visibilityLocationProvider;
         mVisualStabilityProvider = visualStabilityProvider;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mStatusBarStateController = statusBarStateController;
@@ -123,6 +127,11 @@
     //  HUNs to the top of the shade
     private final NotifStabilityManager mNotifStabilityManager =
             new NotifStabilityManager("VisualStabilityCoordinator") {
+                private boolean canMoveForHeadsUp(NotificationEntry entry) {
+                    return entry != null && mHeadsUpManager.isAlerting(entry.getKey())
+                            && !mVisibilityLocationProvider.isInVisibleLocation(entry);
+                }
+
                 @Override
                 public void onBeginRun() {
                     mIsSuppressingPipelineRun = false;
@@ -140,7 +149,7 @@
                 @Override
                 public boolean isGroupChangeAllowed(@NonNull NotificationEntry entry) {
                     final boolean isGroupChangeAllowedForEntry =
-                            mReorderingAllowed || mHeadsUpManager.isAlerting(entry.getKey());
+                            mReorderingAllowed || canMoveForHeadsUp(entry);
                     mIsSuppressingGroupChange |= !isGroupChangeAllowedForEntry;
                     return isGroupChangeAllowedForEntry;
                 }
@@ -156,7 +165,7 @@
                 public boolean isSectionChangeAllowed(@NonNull NotificationEntry entry) {
                     final boolean isSectionChangeAllowedForEntry =
                             mReorderingAllowed
-                                    || mHeadsUpManager.isAlerting(entry.getKey())
+                                    || canMoveForHeadsUp(entry)
                                     || mEntriesThatCanChangeSection.containsKey(entry.getKey());
                     if (!isSectionChangeAllowedForEntry) {
                         mEntriesWithSuppressedSectionChange.add(entry.getKey());
@@ -165,8 +174,8 @@
                 }
 
                 @Override
-                public boolean isEntryReorderingAllowed(@NonNull ListEntry section) {
-                    return mReorderingAllowed;
+                public boolean isEntryReorderingAllowed(@NonNull ListEntry entry) {
+                    return mReorderingAllowed || canMoveForHeadsUp(entry.getRepresentativeEntry());
                 }
 
                 @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 8e052c7..4adc90a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -193,7 +193,7 @@
         })
     }
 
-    fun logParentChangeSuppressed(
+    fun logParentChangeSuppressedStarted(
         buildId: Int,
         suppressedParent: GroupEntry?,
         keepingParent: GroupEntry?
@@ -207,6 +207,21 @@
         })
     }
 
+    fun logParentChangeSuppressedStopped(
+            buildId: Int,
+            previouslySuppressedParent: GroupEntry?,
+            previouslyKeptParent: GroupEntry?
+    ) {
+        buffer.log(TAG, INFO, {
+            long1 = buildId.toLong()
+            str1 = previouslySuppressedParent?.logKey
+            str2 = previouslyKeptParent?.logKey
+        }, {
+            "(Build $long1)     Change of parent to '$str1' no longer suppressed; " +
+                    "replaced parent '$str2'"
+        })
+    }
+
     fun logGroupPruningSuppressed(
         buildId: Int,
         keepingParent: GroupEntry?
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
index 966ab4c..afdadeb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
@@ -51,7 +51,9 @@
      */
     public final void invalidateList(@Nullable String reason) {
         if (mListener != null) {
-            Trace.beginSection("Pluggable<" + mName + ">.invalidateList");
+            if (Trace.isEnabled()) {
+                Trace.traceBegin(Trace.TRACE_TAG_APP, "Pluggable<" + mName + ">.invalidateList");
+            }
             mListener.onPluggableInvalidated((This) this, reason);
             Trace.endSection();
         }
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/collection/provider/SeenNotificationsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SeenNotificationsProvider.kt
new file mode 100644
index 0000000..cff47e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SeenNotificationsProvider.kt
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+
+/** Keeps track of whether "seen" notification content has been filtered out of the shade. */
+interface SeenNotificationsProvider {
+    /** Are any already-seen notifications currently filtered out of the shade? */
+    val hasFilteredOutSeenNotifications: Boolean
+}
+
+@Module
+interface SeenNotificationsProviderModule {
+    @Binds
+    fun bindSeenNotificationsProvider(
+        impl: SeenNotificationsProviderImpl
+    ): SeenNotificationsProvider
+}
+
+@SysUISingleton
+class SeenNotificationsProviderImpl @Inject constructor() : SeenNotificationsProvider {
+    override var hasFilteredOutSeenNotifications: Boolean = false
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt
new file mode 100644
index 0000000..4bc4ecf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.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.statusbar.notification.collection.provider
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/**
+ * An injectable component which delegates the visibility location computation to a delegate which
+ * can be initialized after the initial injection, generally because it's provided by a view.
+ */
+@SysUISingleton
+class VisibilityLocationProviderDelegator @Inject constructor() : VisibilityLocationProvider {
+    private var delegate: VisibilityLocationProvider? = null
+
+    fun setDelegate(provider: VisibilityLocationProvider) {
+        delegate = provider
+    }
+
+    override fun isInVisibleLocation(entry: NotificationEntry): Boolean =
+        requireNotNull(this.delegate) { "delegate not initialized" }.isInVisibleLocation(entry)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
index f949af0..8de0381 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
@@ -55,4 +55,8 @@
 
     override val view: View
         get() = mediaContainerView!!
+
+    override fun offerToKeepInParentForAnimation(): Boolean = false
+    override fun removeFromParentIfKeptForAnimation(): Boolean = false
+    override fun resetKeepInParentForAnimation() {}
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
index 26ba12c..ae72a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
@@ -30,6 +30,7 @@
  * below.
  */
 interface NodeController {
+
     /** A string that uniquely(ish) represents the node in the tree. Used for debugging. */
     val nodeLabel: String
 
@@ -64,6 +65,27 @@
 
     /** Called when this view has been removed */
     fun onViewRemoved() {}
+
+    /**
+     * Called before removing a node from its parent
+     *
+     * If returned true, the ShadeViewDiffer won't detach this row and the view system is
+     * responsible for ensuring the row is in eventually removed from the parent.
+     *
+     * @return false to opt out from this feature
+     */
+    fun offerToKeepInParentForAnimation(): Boolean
+
+    /**
+     * Called before a node is reattached. Removes the view from its parent
+     * if it was flagged to be kept before.
+     *
+     * @return whether it did a removal
+     */
+    fun removeFromParentIfKeptForAnimation(): Boolean
+
+    /** Called when a node is being reattached */
+    fun resetKeepInParentForAnimation()
 }
 
 /**
@@ -90,7 +112,7 @@
 }
 
 private fun treeSpecToStrHelper(tree: NodeSpec, sb: StringBuilder, indent: String) {
-    sb.append("${indent}{${tree.controller.nodeLabel}}\n")
+    sb.append("$indent{${tree.controller.nodeLabel}}\n")
     if (tree.children.isNotEmpty()) {
         val childIndent = "$indent  "
         for (child in tree.children) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
index 2073e92..5ff686a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
@@ -32,6 +32,9 @@
     override val view: View
 ) : NodeController, PipelineDumpable {
     override val nodeLabel: String = "<root>"
+    override fun offerToKeepInParentForAnimation(): Boolean = false
+    override fun removeFromParentIfKeptForAnimation(): Boolean = false
+    override fun resetKeepInParentForAnimation() {}
 
     override fun getChildAt(index: Int): View? {
         return listContainer.getContainerChildAt(index)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 2c9508e..7b59266 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -100,4 +100,7 @@
 
     override val view: View
         get() = _view!!
+    override fun offerToKeepInParentForAnimation(): Boolean = false
+    override fun removeFromParentIfKeptForAnimation(): Boolean = false
+    override fun resetKeepInParentForAnimation() {}
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 9a9941e..18ee481 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -86,10 +86,10 @@
     }
 
     private fun maybeDetachChild(
-        parentNode: ShadeNode,
-        parentSpec: NodeSpec?,
-        childNode: ShadeNode,
-        childSpec: NodeSpec?
+            parentNode: ShadeNode,
+            parentSpec: NodeSpec?,
+            childNode: ShadeNode,
+            childSpec: NodeSpec?
     ) {
         val newParentNode = childSpec?.parent?.let { getNode(it) }
 
@@ -100,14 +100,27 @@
                 nodes.remove(childNode.controller)
             }
 
-            logger.logDetachingChild(
-                key = childNode.label,
-                isTransfer = !childCompletelyRemoved,
-                isParentRemoved = parentSpec == null,
-                oldParent = parentNode.label,
-                newParent = newParentNode?.label)
-            parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved)
-            childNode.parent = null
+            if (childCompletelyRemoved && parentSpec == null &&
+                    childNode.offerToKeepInParentForAnimation()) {
+                // If both the child and the parent are being removed at the same time, then
+                // keep the child attached to the parent for animation purposes
+                logger.logSkipDetachingChild(
+                        key = childNode.label,
+                        parentKey = parentNode.label,
+                        isTransfer = !childCompletelyRemoved,
+                        isParentRemoved = true
+                )
+            } else {
+                logger.logDetachingChild(
+                        key = childNode.label,
+                        isTransfer = !childCompletelyRemoved,
+                        isParentRemoved = parentSpec == null,
+                        oldParent = parentNode.label,
+                        newParent = newParentNode?.label
+                )
+                parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved)
+                childNode.parent = null
+            }
         }
     }
 
@@ -119,6 +132,16 @@
             val childNode = getNode(childSpec)
 
             if (childNode.view != currView) {
+                val removedFromParent = childNode.removeFromParentIfKeptForAnimation()
+                if (removedFromParent) {
+                    logger.logDetachingChild(
+                            key = childNode.label,
+                            isTransfer = false,
+                            isParentRemoved = true,
+                            oldParent = null,
+                            newParent = null
+                    )
+                }
 
                 when (childNode.parent) {
                     null -> {
@@ -142,6 +165,8 @@
                 }
             }
 
+            childNode.resetKeepInParentForAnimation()
+
             if (childSpec.children.isNotEmpty()) {
                 attachChildren(childNode, specMap)
             }
@@ -213,4 +238,16 @@
         controller.removeChild(child.controller, isTransfer)
         child.controller.onViewRemoved()
     }
+
+    fun offerToKeepInParentForAnimation(): Boolean {
+        return controller.offerToKeepInParentForAnimation()
+    }
+
+    fun removeFromParentIfKeptForAnimation(): Boolean {
+        return controller.removeFromParentIfKeptForAnimation()
+    }
+
+    fun resetKeepInParentForAnimation() {
+        controller.resetKeepInParentForAnimation()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index b4b9438..1e22c2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -43,6 +43,20 @@
         })
     }
 
+    fun logSkipDetachingChild(
+            key: String,
+            parentKey: String?,
+            isTransfer: Boolean,
+            isParentRemoved: Boolean
+    ) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = key
+            str2 = parentKey
+            bool1 = isTransfer
+            bool2 = isParentRemoved
+        }, { "Skip detaching $str1 from $str2 isTransfer=$bool1 isParentRemoved=$bool2" })
+    }
+
     fun logAttachingChild(key: String, parent: String, atIndex: Int) {
         buffer.log(TAG, LogLevel.DEBUG, {
             str1 = key
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 ff63891..808638a 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
@@ -34,8 +34,10 @@
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeEventsModule;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
@@ -50,6 +52,8 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl;
+import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderModule;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -93,6 +97,7 @@
 @Module(includes = {
         CoordinatorsModule.class,
         KeyguardNotificationVisibilityProviderModule.class,
+        SeenNotificationsProviderModule.class,
         ShadeEventsModule.class,
         NotifPipelineChoreographerModule.class,
         NotificationSectionHeadersModule.class,
@@ -150,6 +155,11 @@
     @Binds
     NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager);
 
+    /** Provides an instance of {@link VisibilityLocationProvider} */
+    @Binds
+    VisibilityLocationProvider bindVisibilityLocationProvider(
+            VisibilityLocationProviderDelegator visibilityLocationProviderDelegator);
+
     /** Provides an instance of {@link NotificationLogger} */
     @SysUISingleton
     @Provides
@@ -160,6 +170,7 @@
             NotificationVisibilityProvider visibilityProvider,
             NotifPipeline notifPipeline,
             StatusBarStateController statusBarStateController,
+            ShadeExpansionStateManager shadeExpansionStateManager,
             NotificationLogger.ExpansionStateLogger expansionStateLogger,
             NotificationPanelLogger notificationPanelLogger) {
         return new NotificationLogger(
@@ -169,6 +180,7 @@
                 visibilityProvider,
                 notifPipeline,
                 statusBarStateController,
+                shadeExpansionStateManager,
                 expansionStateLogger,
                 notificationPanelLogger);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index e6dbcee..7513aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -2,22 +2,20 @@
 
 import android.app.Notification
 import android.app.Notification.VISIBILITY_SECRET
-import android.content.BroadcastReceiver
 import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
 import android.database.ContentObserver
 import android.net.Uri
 import android.os.Handler
+import android.os.HandlerExecutor
 import android.os.UserHandle
 import android.provider.Settings
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.CoreStartable
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -78,7 +76,7 @@
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val highPriorityProvider: HighPriorityProvider,
     private val statusBarStateController: SysuiStatusBarStateController,
-    private val broadcastDispatcher: BroadcastDispatcher,
+    private val userTracker: UserTracker,
     private val secureSettings: SecureSettings,
     private val globalSettings: GlobalSettings
 ) : CoreStartable, KeyguardNotificationVisibilityProvider {
@@ -87,6 +85,15 @@
     private val onStateChangedListeners = ListenerSet<Consumer<String>>()
     private var hideSilentNotificationsOnLockscreen: Boolean = false
 
+    private val userTrackerCallback = object : UserTracker.Callback {
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            if (isLockedOrLocking) {
+                // maybe public mode changed
+                notifyStateChanged("onUserSwitched")
+            }
+        }
+    }
+
     override fun start() {
         readShowSilentNotificationSetting()
         keyguardStateController.addCallback(object : KeyguardStateController.Callback {
@@ -143,14 +150,7 @@
                 notifyStateChanged("onStatusBarUpcomingStateChanged")
             }
         })
-        broadcastDispatcher.registerReceiver(object : BroadcastReceiver() {
-            override fun onReceive(context: Context, intent: Intent) {
-                if (isLockedOrLocking) {
-                    // maybe public mode changed
-                    notifyStateChanged(intent.action!!)
-                }
-            }
-        }, IntentFilter(Intent.ACTION_USER_SWITCHED))
+        userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler))
     }
 
     override fun addOnStateChangedListener(listener: Consumer<String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 073b6b0..13b3aca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -106,6 +106,36 @@
         })
     }
 
+    fun logNoHeadsUpOldWhen(
+        entry: NotificationEntry,
+        notifWhen: Long,
+        notifAge: Long
+    ) {
+        buffer.log(TAG, DEBUG, {
+            str1 = entry.logKey
+            long1 = notifWhen
+            long2 = notifAge
+        }, {
+            "No heads up: old when $long1 (age=$long2 ms): $str1"
+        })
+    }
+
+    fun logMaybeHeadsUpDespiteOldWhen(
+        entry: NotificationEntry,
+        notifWhen: Long,
+        notifAge: Long,
+        reason: String
+    ) {
+        buffer.log(TAG, DEBUG, {
+            str1 = entry.logKey
+            str2 = reason
+            long1 = notifWhen
+            long2 = notifAge
+        }, {
+            "Maybe heads up: old when $long1 (age=$long2 ms) but $str2: $str1"
+        })
+    }
+
     fun logNoHeadsUpSuppressedBy(
         entry: NotificationEntry,
         suppressor: NotificationInterruptSuppressor
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index c4f5a3a..ec5bd68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -20,6 +20,7 @@
 import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD;
 import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR;
 
+import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.ContentResolver;
 import android.database.ContentObserver;
@@ -82,7 +83,10 @@
         FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(1235),
 
         @UiEvent(doc = "FSI suppressed for requiring neither HUN nor keyguard")
-        FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236);
+        FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236),
+
+        @UiEvent(doc = "HUN suppressed for old when")
+        HUN_SUPPRESSED_OLD_WHEN(1237);
 
         private final int mId;
 
@@ -346,6 +350,10 @@
             return false;
         }
 
+        if (shouldSuppressHeadsUpWhenAwakeForOldWhen(entry, log)) {
+            return false;
+        }
+
         for (int i = 0; i < mSuppressors.size(); i++) {
             if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) {
                 if (log) mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i));
@@ -470,4 +478,51 @@
     private boolean isSnoozedPackage(StatusBarNotification sbn) {
         return mHeadsUpManager.isSnoozed(sbn.getPackageName());
     }
+
+    private boolean shouldSuppressHeadsUpWhenAwakeForOldWhen(NotificationEntry entry, boolean log) {
+        if (!mFlags.isNoHunForOldWhenEnabled()) {
+            return false;
+        }
+
+        final Notification notification = entry.getSbn().getNotification();
+        if (notification == null) {
+            return false;
+        }
+
+        final long when = notification.when;
+        final long now = System.currentTimeMillis();
+        final long age = now - when;
+
+        if (age < MAX_HUN_WHEN_AGE_MS) {
+            return false;
+        }
+
+        if (when <= 0) {
+            // Some notifications (including many system notifications) are posted with the "when"
+            // field set to 0. Nothing in the Javadocs for Notification mentions a special meaning
+            // for a "when" of 0, but Android didn't even exist at the dawn of the Unix epoch.
+            // Therefore, assume that these notifications effectively don't have a "when" value,
+            // and don't suppress HUNs.
+            if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "when <= 0");
+            return false;
+        }
+
+        if (notification.fullScreenIntent != null) {
+            if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "full-screen intent");
+            return false;
+        }
+
+        if (notification.isForegroundService()) {
+            if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "foreground service");
+            return false;
+        }
+
+        if (log) mLogger.logNoHeadsUpOldWhen(entry, when, age);
+        final int uid = entry.getSbn().getUid();
+        final String packageName = entry.getSbn().getPackageName();
+        mUiEventLogger.log(NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN, uid, packageName);
+        return true;
+    }
+
+    public static final long MAX_HUN_WHEN_AGE_MS = 24 * 60 * 60 * 1000;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 6391877..5f6a5cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -36,6 +36,7 @@
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
@@ -52,6 +53,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
@@ -92,25 +94,6 @@
     private Boolean mPanelExpanded = null;  // Use null to indicate state is not yet known
     private boolean mLogging = false;
 
-    protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
-            new OnChildLocationsChangedListener() {
-                @Override
-                public void onChildLocationsChanged() {
-                    if (mHandler.hasCallbacks(mVisibilityReporter)) {
-                        // Visibilities will be reported when the existing
-                        // callback is executed.
-                        return;
-                    }
-                    // Calculate when we're allowed to run the visibility
-                    // reporter. Note that this timestamp might already have
-                    // passed. That's OK, the callback will just be executed
-                    // ASAP.
-                    long nextReportUptimeMs =
-                            mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS;
-                    mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs);
-                }
-            };
-
     // Tracks notifications currently visible in mNotificationStackScroller and
     // emits visibility events via NoMan on changes.
     protected Runnable mVisibilityReporter = new Runnable() {
@@ -219,6 +202,7 @@
             NotificationVisibilityProvider visibilityProvider,
             NotifPipeline notifPipeline,
             StatusBarStateController statusBarStateController,
+            ShadeExpansionStateManager shadeExpansionStateManager,
             ExpansionStateLogger expansionStateLogger,
             NotificationPanelLogger notificationPanelLogger) {
         mNotificationListener = notificationListener;
@@ -232,6 +216,7 @@
         mNotificationPanelLogger = notificationPanelLogger;
         // Not expected to be destroyed, don't need to unsubscribe
         statusBarStateController.addCallback(this);
+        shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
 
         registerNewPipelineListener();
     }
@@ -278,14 +263,14 @@
             if (DEBUG) {
                 Log.i(TAG, "startNotificationLogging");
             }
-            mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
+            mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
             // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
             // cause the scroller to emit child location events. Hence generate
             // one ourselves to guarantee that we're reporting visible
             // notifications.
             // (Note that in cases where the scroller does emit events, this
             // additional event doesn't break anything.)
-            mNotificationLocationsChangedListener.onChildLocationsChanged();
+            onChildLocationsChanged();
         }
     }
 
@@ -411,21 +396,6 @@
     }
 
     /**
-     * Called by CentralSurfaces to notify the logger that the panel expansion has changed.
-     * The panel may be showing any of the normal notification panel, the AOD, or the bouncer.
-     * @param isExpanded True if the panel is expanded.
-     */
-    public void onPanelExpandedChanged(boolean isExpanded) {
-        if (DEBUG) {
-            Log.i(TAG, "onPanelExpandedChanged: new=" + isExpanded);
-        }
-        mPanelExpanded = isExpanded;
-        synchronized (mDozingLock) {
-            maybeUpdateLoggingStatus();
-        }
-    }
-
-    /**
      * Called when the notification is expanded / collapsed.
      */
     public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
@@ -434,6 +404,36 @@
     }
 
     @VisibleForTesting
+    void onShadeExpansionFullyChanged(Boolean isExpanded) {
+        // mPanelExpanded is initialized as null
+        if (mPanelExpanded == null || !mPanelExpanded.equals(isExpanded)) {
+            if (DEBUG) {
+                Log.i(TAG, "onPanelExpandedChanged: new=" + isExpanded);
+            }
+            mPanelExpanded = isExpanded;
+            synchronized (mDozingLock) {
+                maybeUpdateLoggingStatus();
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void onChildLocationsChanged() {
+        if (mHandler.hasCallbacks(mVisibilityReporter)) {
+            // Visibilities will be reported when the existing
+            // callback is executed.
+            return;
+        }
+        // Calculate when we're allowed to run the visibility
+        // reporter. Note that this timestamp might already have
+        // passed. That's OK, the callback will just be executed
+        // ASAP.
+        long nextReportUptimeMs =
+                mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS;
+        mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs);
+    }
+
+    @VisibleForTesting
     public void setVisibilityReporter(Runnable visibilityReporter) {
         mVisibilityReporter = visibilityReporter;
     }
@@ -535,7 +535,7 @@
                 return;
             }
             if (loggedExpansionState != null
-                    && state.mIsExpanded == loggedExpansionState) {
+                    && Objects.equals(state.mIsExpanded, loggedExpansionState)) {
                 return;
             }
             mLoggedExpansionState.put(key, state.mIsExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index d29298a..fbe88df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -39,9 +39,13 @@
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf}
  * to implement dimming/activating on Keyguard for the double-tap gesture
@@ -91,6 +95,7 @@
             = new PathInterpolator(0.6f, 0, 0.5f, 1);
     private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
             = new PathInterpolator(0, 0, 0.5f, 1);
+    private final Set<SourceType> mOnDetachResetRoundness = new HashSet<>();
     private int mTintedRippleColor;
     private int mNormalRippleColor;
     private Gefingerpoken mTouchHandler;
@@ -134,6 +139,7 @@
     private boolean mDismissed;
     private boolean mRefocusOnDismiss;
     private AccessibilityManager mAccessibilityManager;
+    protected boolean mUseRoundnessSourceTypes;
 
     public ActivatableNotificationView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -613,9 +619,9 @@
     protected void resetAllContentAlphas() {}
 
     @Override
-    public void applyRoundness() {
-        super.applyRoundness();
+    public void applyRoundnessAndInvalidate() {
         applyBackgroundRoundness(getTopCornerRadius(), getBottomCornerRadius());
+        super.applyRoundnessAndInvalidate();
     }
 
     @Override
@@ -775,6 +781,33 @@
         mAccessibilityManager = accessibilityManager;
     }
 
+    /**
+     * Enable the support for rounded corner based on the SourceType
+     * @param enabled true if is supported
+     */
+    public void useRoundnessSourceTypes(boolean enabled) {
+        mUseRoundnessSourceTypes = enabled;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mUseRoundnessSourceTypes && !mOnDetachResetRoundness.isEmpty()) {
+            for (SourceType sourceType : mOnDetachResetRoundness) {
+                requestRoundnessReset(sourceType);
+            }
+            mOnDetachResetRoundness.clear();
+        }
+    }
+
+    /**
+     * SourceType which should be reset when this View is detached
+     * @param sourceType will be reset on View detached
+     */
+    public void addOnDetachResetRoundness(SourceType sourceType) {
+        mOnDetachResetRoundness.add(sourceType);
+    }
+
     public interface OnActivatedListener {
         void onActivated(ActivatableNotificationView view);
         void onActivationReset(ActivatableNotificationView view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9e7717c..c7c1634 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -19,6 +19,7 @@
 import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 
+import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
 
 import android.animation.Animator;
@@ -90,6 +91,7 @@
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.notification.FeedbackIcon;
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -135,11 +137,16 @@
 
     private static final String TAG = "ExpandableNotifRow";
     private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DEBUG_ONMEASURE =
+            Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
     private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
     private static final int COLORED_DIVIDER_ALPHA = 0x7B;
     private static final int MENU_VIEW_INDEX = 0;
     public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
     private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
+    private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
+    private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)");
+    private static final SourceType PINNED = SourceType.from("Pinned");
 
     // We don't correctly track dark mode until the content views are inflated, so always update
     // the background on first content update just in case it happens to be during a theme change.
@@ -147,6 +154,7 @@
     private boolean mNotificationTranslationFinished = false;
     private boolean mIsSnoozed;
     private boolean mIsFaded;
+    private boolean mAnimatePinnedRoundness = false;
 
     /**
      * Listener for when {@link ExpandableNotificationRow} is laid out.
@@ -238,7 +246,7 @@
     private NotificationContentView mPrivateLayout;
     private NotificationContentView[] mLayouts;
     private int mNotificationColor;
-    private ExpansionLogger mLogger;
+    private ExpandableNotificationRowLogger mLogger;
     private String mLoggingKey;
     private NotificationGuts mGuts;
     private NotificationEntry mEntry;
@@ -337,7 +345,7 @@
             }
         }
     };
-    private boolean mKeepInParent;
+    private boolean mKeepInParentForDismissAnimation;
     private boolean mRemoved;
     private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT =
             new FloatProperty<ExpandableNotificationRow>("translate") {
@@ -373,7 +381,7 @@
 
     private float mTopRoundnessDuringLaunchAnimation;
     private float mBottomRoundnessDuringLaunchAnimation;
-    private boolean mIsNotificationGroupCornerEnabled;
+    private float mSmallRoundness;
 
     /**
      * Returns whether the given {@code statusBarNotification} is a system notification.
@@ -823,6 +831,12 @@
         if (mChildrenContainer == null) {
             mChildrenContainerStub.inflate();
         }
+
+        if (row.keepInParentForDismissAnimation()) {
+            logSkipAttachingKeepInParentChild(row);
+            return;
+        }
+
         mChildrenContainer.addNotification(row, childIndex);
         onAttachedChildrenCountChanged();
         row.setIsChildInGroup(true, this);
@@ -831,10 +845,41 @@
     public void removeChildNotification(ExpandableNotificationRow row) {
         if (mChildrenContainer != null) {
             mChildrenContainer.removeNotification(row);
+            row.setKeepInParentForDismissAnimation(false);
         }
         onAttachedChildrenCountChanged();
         row.setIsChildInGroup(false, null);
-        row.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
+        if (!mUseRoundnessSourceTypes) {
+            row.requestBottomRoundness(0.0f, LegacySourceType.DefaultValue, /* animate = */ false);
+        }
+    }
+
+    /**
+     * Removes the children notifications which were marked to keep for the dismissal animation.
+     */
+    public void removeChildrenWithKeepInParent() {
+        if (mChildrenContainer == null) return;
+
+        List<ExpandableNotificationRow> clonedList = new ArrayList<>(
+                mChildrenContainer.getAttachedChildren());
+        boolean childCountChanged = false;
+        for (ExpandableNotificationRow child : clonedList) {
+            if (child.keepInParentForDismissAnimation()) {
+                mChildrenContainer.removeNotification(child);
+                child.setIsChildInGroup(false, null);
+                if (!mUseRoundnessSourceTypes) {
+                    LegacySourceType sourceType = LegacySourceType.DefaultValue;
+                    child.requestBottomRoundness(0f, sourceType, /* animate = */ false);
+                }
+                child.setKeepInParentForDismissAnimation(false);
+                logKeepInParentChildDetached(child);
+                childCountChanged = true;
+            }
+        }
+
+        if (childCountChanged) {
+            onAttachedChildrenCountChanged();
+        }
     }
 
     /**
@@ -880,6 +925,9 @@
             mNotificationParent.updateBackgroundForGroupState();
         }
         updateBackgroundClipping();
+        if (mUseRoundnessSourceTypes) {
+            updateBaseRoundness();
+        }
     }
 
     @Override
@@ -998,6 +1046,16 @@
         if (isAboveShelf() != wasAboveShelf) {
             mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
         }
+        if (mUseRoundnessSourceTypes) {
+            if (pinned) {
+                // Should be animated if someone explicitly set it to 0 and the row is shown.
+                boolean animated = mAnimatePinnedRoundness && isShown();
+                requestRoundness(/* top = */ 1f, /* bottom = */ 1f, PINNED, animated);
+            } else {
+                requestRoundnessReset(PINNED);
+                mAnimatePinnedRoundness = true;
+            }
+        }
     }
 
     @Override
@@ -1341,21 +1399,6 @@
         return mOnKeyguard;
     }
 
-    public void removeAllChildren() {
-        List<ExpandableNotificationRow> notificationChildren =
-                mChildrenContainer.getAttachedChildren();
-        ArrayList<ExpandableNotificationRow> clonedList = new ArrayList<>(notificationChildren);
-        for (int i = 0; i < clonedList.size(); i++) {
-            ExpandableNotificationRow row = clonedList.get(i);
-            if (row.keepInParent()) {
-                continue;
-            }
-            mChildrenContainer.removeNotification(row);
-            row.setIsChildInGroup(false, null);
-        }
-        onAttachedChildrenCountChanged();
-    }
-
     @Override
     public void dismiss(boolean refocusOnDismiss) {
         super.dismiss(refocusOnDismiss);
@@ -1374,12 +1417,20 @@
         }
     }
 
-    public boolean keepInParent() {
-        return mKeepInParent;
+    /**
+     * @return  if this entry should be kept in its parent during removal.
+     */
+    public boolean keepInParentForDismissAnimation() {
+        return mKeepInParentForDismissAnimation;
     }
 
-    public void setKeepInParent(boolean keepInParent) {
-        mKeepInParent = keepInParent;
+    public void setKeepInParentForDismissAnimation(boolean keepInParent) {
+        mKeepInParentForDismissAnimation = keepInParent;
+    }
+
+    /** @return true if the User has dismissed this notif's parent */
+    public boolean isParentDismissed() {
+        return getEntry().getDismissState() == PARENT_DISMISSED;
     }
 
     @Override
@@ -1526,7 +1577,7 @@
             l.setAlpha(alpha);
         }
         if (mChildrenContainer != null) {
-            mChildrenContainer.setAlpha(alpha);
+            mChildrenContainer.setContentAlpha(alpha);
         }
     }
 
@@ -1550,14 +1601,37 @@
         mUseIncreasedHeadsUpHeight = use;
     }
 
-    public interface ExpansionLogger {
+    /**
+     * Interface for logging {{@link ExpandableNotificationRow} events.}
+     */
+    public interface ExpandableNotificationRowLogger {
+        /**
+         * Called when the notification is expanded / collapsed.
+         */
         void logNotificationExpansion(String key, boolean userAction, boolean expanded);
+
+        /**
+         * Called when a notification which was previously kept in its parent for the
+         * dismiss animation is finally detached from its parent.
+         */
+        void logKeepInParentChildDetached(NotificationEntry child, NotificationEntry oldParent);
+
+        /**
+         * Called when we want to attach a notification to a new parent,
+         * but it still has the keepInParent flag set, so we skip it.
+         */
+        void logSkipAttachingKeepInParentChild(
+                NotificationEntry child,
+                NotificationEntry newParent
+        );
     }
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
         mImageResolver = new NotificationInlineImageResolver(context,
                 new NotificationInlineImageCache());
+        float radius = getResources().getDimension(R.dimen.notification_corner_radius_small);
+        mSmallRoundness = radius / getMaxRadius();
         initDimens();
     }
 
@@ -1569,7 +1643,7 @@
             RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
             String appName,
             String notificationKey,
-            ExpansionLogger logger,
+            ExpandableNotificationRowLogger logger,
             KeyguardBypassController bypassController,
             GroupMembershipManager groupMembershipManager,
             GroupExpansionManager groupExpansionManager,
@@ -1739,6 +1813,11 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure"));
+        if (DEBUG_ONMEASURE) {
+            Log.d(TAG, "onMeasure("
+                    + "widthMeasureSpec=" + MeasureSpec.toString(widthMeasureSpec) + ", "
+                    + "heightMeasureSpec=" + MeasureSpec.toString(heightMeasureSpec) + ")");
+        }
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         Trace.endSection();
     }
@@ -1785,7 +1864,7 @@
             mChildrenContainer.setIsLowPriority(mIsLowPriority);
             mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
             mChildrenContainer.onNotificationUpdated();
-            mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
+            mChildrenContainer.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
 
             mTranslateableViews.add(mChildrenContainer);
         });
@@ -2217,7 +2296,7 @@
 
     @Override
     public float getTopRoundness() {
-        if (mExpandAnimationRunning) {
+        if (!mUseRoundnessSourceTypes && mExpandAnimationRunning) {
             return mTopRoundnessDuringLaunchAnimation;
         }
 
@@ -2226,7 +2305,7 @@
 
     @Override
     public float getBottomRoundness() {
-        if (mExpandAnimationRunning) {
+        if (!mUseRoundnessSourceTypes && mExpandAnimationRunning) {
             return mBottomRoundnessDuringLaunchAnimation;
         }
 
@@ -3382,17 +3461,24 @@
     }
 
     @Override
-    public void applyRoundness() {
-        super.applyRoundness();
+    public void applyRoundnessAndInvalidate() {
         applyChildrenRoundness();
+        super.applyRoundnessAndInvalidate();
     }
 
     private void applyChildrenRoundness() {
         if (mIsSummaryWithChildren) {
-            mChildrenContainer.requestBottomRoundness(
-                    getBottomRoundness(),
-                    /* animate = */ false,
-                    SourceType.DefaultValue);
+            if (mUseRoundnessSourceTypes) {
+                mChildrenContainer.requestRoundness(
+                        /* top = */ getTopRoundness(),
+                        /* bottom = */ getBottomRoundness(),
+                        FROM_PARENT);
+            } else {
+                mChildrenContainer.requestBottomRoundness(
+                        getBottomRoundness(),
+                        LegacySourceType.DefaultValue,
+                        /* animate = */ false);
+            }
         }
     }
 
@@ -3551,6 +3637,7 @@
             } else {
                 pw.println("no viewState!!!");
             }
+            pw.println("Roundness: " + getRoundableState().debugString());
 
             if (mIsSummaryWithChildren) {
                 pw.println();
@@ -3575,6 +3662,18 @@
         });
     }
 
+    private void logKeepInParentChildDetached(ExpandableNotificationRow child) {
+        if (mLogger != null) {
+            mLogger.logKeepInParentChildDetached(child.getEntry(), getEntry());
+        }
+    }
+
+    private void logSkipAttachingKeepInParentChild(ExpandableNotificationRow child) {
+        if (mLogger != null) {
+            mLogger.logSkipAttachingKeepInParentChild(child.getEntry(), getEntry());
+        }
+    }
+
     private void setTargetPoint(Point p) {
         mTargetPoint = p;
     }
@@ -3583,14 +3682,38 @@
         return mTargetPoint;
     }
 
+    /** Update the minimum roundness based on current state */
+    private void updateBaseRoundness() {
+        if (isChildInGroup()) {
+            requestRoundnessReset(BASE_VALUE);
+        } else {
+            requestRoundness(mSmallRoundness, mSmallRoundness, BASE_VALUE);
+        }
+    }
+
     /**
-     * Enable the support for rounded corner in notification group
+     * Enable the support for rounded corner based on the SourceType
      * @param enabled true if is supported
      */
-    public void enableNotificationGroupCorner(boolean enabled) {
-        mIsNotificationGroupCornerEnabled = enabled;
+    @Override
+    public void useRoundnessSourceTypes(boolean enabled) {
+        super.useRoundnessSourceTypes(enabled);
         if (mChildrenContainer != null) {
-            mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
+            mChildrenContainer.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+        }
+    }
+
+    @Override
+    public String toString() {
+        String roundableStateDebug = "RoundableState = " + getRoundableState().debugString();
+        return "ExpandableNotificationRow:" + hashCode() + " { " + roundableStateDebug + " }";
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (mUseRoundnessSourceTypes) {
+            updateBaseRoundness();
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 842526e..d113860 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -33,9 +33,9 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.FeedbackIcon;
@@ -83,13 +83,11 @@
     private final GroupExpansionManager mGroupExpansionManager;
     private final RowContentBindStage mRowContentBindStage;
     private final NotificationLogger mNotificationLogger;
+    private final NotificationRowLogger mLogBufferLogger;
     private final HeadsUpManager mHeadsUpManager;
     private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;
     private final StatusBarStateController mStatusBarStateController;
     private final MetricsLogger mMetricsLogger;
-
-    private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
-            this::logNotificationExpansion;
     private final ExpandableNotificationRow.CoordinateOnClickListener mOnFeedbackClickListener;
     private final NotificationGutsManager mNotificationGutsManager;
     private final OnUserInteractionCallback mOnUserInteractionCallback;
@@ -101,8 +99,32 @@
     private final Optional<BubblesManager> mBubblesManagerOptional;
     private final SmartReplyConstants mSmartReplyConstants;
     private final SmartReplyController mSmartReplyController;
-
     private final ExpandableNotificationRowDragController mDragController;
+    private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
+            new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
+                @Override
+                public void logNotificationExpansion(String key, boolean userAction,
+                        boolean expanded) {
+                    mNotificationLogger.onExpansionChanged(key, userAction, expanded);
+                }
+
+                @Override
+                public void logKeepInParentChildDetached(
+                        NotificationEntry child,
+                        NotificationEntry oldParent
+                ) {
+                    mLogBufferLogger.logKeepInParentChildDetached(child, oldParent);
+                }
+
+                @Override
+                public void logSkipAttachingKeepInParentChild(
+                        NotificationEntry child,
+                        NotificationEntry newParent
+                ) {
+                    mLogBufferLogger.logSkipAttachingKeepInParentChild(child, newParent);
+                }
+            };
+
 
     @Inject
     public ExpandableNotificationRowController(
@@ -110,6 +132,7 @@
             ActivatableNotificationViewController activatableNotificationViewController,
             RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
             MetricsLogger metricsLogger,
+            NotificationRowLogger logBufferLogger,
             NotificationListContainer listContainer,
             NotificationMediaManager mediaManager,
             SmartReplyConstants smartReplyConstants,
@@ -163,6 +186,7 @@
         mBubblesManagerOptional = bubblesManagerOptional;
         mDragController = dragController;
         mMetricsLogger = metricsLogger;
+        mLogBufferLogger = logBufferLogger;
         mSmartReplyConstants = smartReplyConstants;
         mSmartReplyController = smartReplyController;
     }
@@ -177,7 +201,7 @@
                 mRemoteInputViewSubcomponentFactory,
                 mAppName,
                 mNotificationKey,
-                mExpansionLogger,
+                mLoggerCallback,
                 mKeyguardBypassController,
                 mGroupMembershipManager,
                 mGroupExpansionManager,
@@ -231,8 +255,8 @@
                 mStatusBarStateController.removeCallback(mStatusBarStateListener);
             }
         });
-        mView.enableNotificationGroupCorner(
-                mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER));
+        mView.useRoundnessSourceTypes(
+                mFeatureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES));
     }
 
     private final StatusBarStateController.StateListener mStatusBarStateListener =
@@ -243,10 +267,6 @@
                 }
             };
 
-    private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
-        mNotificationLogger.onExpansionChanged(key, userAction, expanded);
-    }
-
     @Override
     @NonNull
     public String getNodeLabel() {
@@ -336,4 +356,34 @@
     public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
         mView.setFeedbackIcon(icon);
     }
+
+    @Override
+    public boolean offerToKeepInParentForAnimation() {
+        //If the User dismissed the notification's parent, we want to keep it attached until the
+        //dismiss animation is ongoing. Therefore we don't want to remove it in the ShadeViewDiffer.
+        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_DISMISSAL_ANIMATION)
+                && mView.isParentDismissed()) {
+            mView.setKeepInParentForDismissAnimation(true);
+            return true;
+        }
+
+        //Otherwise the view system doesn't do the removal, so we rely on the ShadeViewDiffer
+        return false;
+    }
+
+    @Override
+    public boolean removeFromParentIfKeptForAnimation() {
+        ExpandableNotificationRow parent = mView.getNotificationParent();
+        if (mView.keepInParentForDismissAnimation() && parent != null) {
+            parent.removeChildNotification(mView);
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void resetKeepInParentForAnimation() {
+        mView.setKeepInParentForDismissAnimation(false);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 64f87ca..b56bae1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -54,8 +54,6 @@
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
-import java.util.Collections;
-
 import javax.inject.Inject;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 4fde5d0..0213b96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -84,7 +84,7 @@
         int right;
         int bottom;
         int height;
-        float topRoundness = mAlwaysRoundBothCorners ? getMaxRadius() : getTopCornerRadius();
+        float topRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getTopCornerRadius();
         if (!mCustomOutline) {
             // The outline just needs to be shifted if we're translating the contents. Otherwise
             // it's already in the right place.
@@ -97,7 +97,7 @@
             // If the top is rounded we want the bottom to be at most at the top roundness, in order
             // to avoid the shadow changing when scrolling up.
             bottom = Math.max(mMinimumHeightForClipping,
-                    Math.max(getActualHeight() - mClipBottomAmount, (int) (top + topRoundness)));
+                    Math.max(getActualHeight() - mClipBottomAmount, (int) (top + topRadius)));
         } else {
             left = mOutlineRect.left;
             top = mOutlineRect.top;
@@ -108,17 +108,17 @@
         if (height == 0) {
             return EMPTY_PATH;
         }
-        float bottomRoundness = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
-        if (topRoundness + bottomRoundness > height) {
-            float overShoot = topRoundness + bottomRoundness - height;
+        float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
+        if (topRadius + bottomRadius > height) {
+            float overShoot = topRadius + bottomRadius - height;
             float currentTopRoundness = getTopRoundness();
             float currentBottomRoundness = getBottomRoundness();
-            topRoundness -= overShoot * currentTopRoundness
+            topRadius -= overShoot * currentTopRoundness
                     / (currentTopRoundness + currentBottomRoundness);
-            bottomRoundness -= overShoot * currentBottomRoundness
+            bottomRadius -= overShoot * currentBottomRoundness
                     / (currentTopRoundness + currentBottomRoundness);
         }
-        getRoundedRectPath(left, top, right, bottom, topRoundness, bottomRoundness, mTmpPath);
+        getRoundedRectPath(left, top, right, bottom, topRadius, bottomRadius, mTmpPath);
         return mTmpPath;
     }
 
@@ -219,9 +219,9 @@
     }
 
     @Override
-    public void applyRoundness() {
+    public void applyRoundnessAndInvalidate() {
         invalidateOutline();
-        super.applyRoundness();
+        super.applyRoundnessAndInvalidate();
     }
 
     protected void setBackgroundTop(int backgroundTop) {
@@ -233,7 +233,7 @@
 
     public void onDensityOrFontScaleChanged() {
         initDimens();
-        applyRoundness();
+        applyRoundnessAndInvalidate();
     }
 
     @Override
@@ -241,7 +241,7 @@
         int previousHeight = getActualHeight();
         super.setActualHeight(actualHeight, notifyListeners);
         if (previousHeight != actualHeight) {
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
@@ -250,7 +250,7 @@
         int previousAmount = getClipTopAmount();
         super.setClipTopAmount(clipTopAmount);
         if (previousAmount != clipTopAmount) {
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
@@ -259,14 +259,14 @@
         int previousAmount = getClipBottomAmount();
         super.setClipBottomAmount(clipBottomAmount);
         if (previousAmount != clipBottomAmount) {
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
     protected void setOutlineAlpha(float alpha) {
         if (alpha != mOutlineAlpha) {
             mOutlineAlpha = alpha;
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
@@ -280,7 +280,7 @@
             setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
         } else {
             mCustomOutline = false;
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
@@ -340,7 +340,7 @@
         // Outlines need to be at least 1 dp
         mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom);
         mOutlineRect.right = (int) Math.max(left, mOutlineRect.right);
-        applyRoundness();
+        applyRoundnessAndInvalidate();
     }
 
     public Path getCustomClipPath(View child) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 0ce9656..f21db0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -154,7 +154,7 @@
         // If the user selected Priority and the previous selection was not priority, show a
         // People Tile add request.
         if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
-            mShadeController.animateCollapsePanels();
+            mShadeController.animateCollapseShade();
             mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
         }
         mGutsContainer.closeControls(v, /* save= */ true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
new file mode 100644
index 0000000..ce11be3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.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.statusbar.notification.row
+
+import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+class NotificationRowLogger @Inject constructor(@NotificationLog private val buffer: LogBuffer) {
+    fun logKeepInParentChildDetached(child: NotificationEntry, oldParent: NotificationEntry?) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = child.logKey
+                str2 = oldParent.logKey
+            },
+            { "Detach child $str1 kept in parent $str2" }
+        )
+    }
+
+    fun logSkipAttachingKeepInParentChild(child: NotificationEntry, newParent: NotificationEntry?) {
+        buffer.log(
+            TAG,
+            LogLevel.WARNING,
+            {
+                str1 = child.logKey
+                str2 = newParent.logKey
+            },
+            { "Skipping to attach $str1 to $str2, because it still flagged to keep in parent" }
+        )
+    }
+}
+
+private const val TAG = "NotifRow"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index f13e48d..1f664cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -71,6 +71,8 @@
     private View mFeedbackIcon;
     private boolean mIsLowPriority;
     private boolean mTransformLowPriorityTitle;
+    private boolean mUseRoundnessSourceTypes;
+    private RoundnessChangedListener mRoundnessChangedListener;
 
     protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
         super(ctx, view, row);
@@ -117,6 +119,20 @@
         return mRoundableState;
     }
 
+    @Override
+    public void applyRoundnessAndInvalidate() {
+        if (mUseRoundnessSourceTypes && mRoundnessChangedListener != null) {
+            // We cannot apply the rounded corner to this View, so our parents (in drawChild()) will
+            // clip our canvas. So we should invalidate our parent.
+            mRoundnessChangedListener.applyRoundnessAndInvalidate();
+        }
+        Roundable.super.applyRoundnessAndInvalidate();
+    }
+
+    public void setOnRoundnessChangedListener(RoundnessChangedListener listener) {
+        mRoundnessChangedListener = listener;
+    }
+
     protected void resolveHeaderViews() {
         mIcon = mView.findViewById(com.android.internal.R.id.icon);
         mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
@@ -343,4 +359,23 @@
             }
         }
     }
+
+    /**
+     * Enable the support for rounded corner based on the SourceType
+     *
+     * @param enabled true if is supported
+     */
+    public void useRoundnessSourceTypes(boolean enabled) {
+        mUseRoundnessSourceTypes = enabled;
+    }
+
+    /**
+     * Interface that handle the Roundness changes
+     */
+    public interface RoundnessChangedListener {
+        /**
+         * This method will be called when this class call applyRoundnessAndInvalidate()
+         */
+        void applyRoundnessAndInvalidate();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index b2628e4..6f4d6d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -719,7 +719,7 @@
      */
     public boolean isBouncerInTransit() {
         return mStatusBarKeyguardViewManager != null
-                && mStatusBarKeyguardViewManager.isBouncerInTransit();
+                && mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit();
     }
 
     /**
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..4a8e2db 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
@@ -25,6 +25,7 @@
 import android.graphics.Path;
 import android.graphics.Path.Direction;
 import android.graphics.drawable.ColorDrawable;
+import android.os.Trace;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -44,7 +45,9 @@
 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.LegacySourceType;
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.Roundable;
@@ -81,6 +84,7 @@
             return mAnimationFilter;
         }
     }.setDuration(200);
+    private static final SourceType FROM_PARENT = SourceType.from("FromParent(NCC)");
 
     private final List<View> mDividers = new ArrayList<>();
     private final List<ExpandableNotificationRow> mAttachedChildren = new ArrayList<>();
@@ -129,7 +133,7 @@
     private int mUntruncatedChildCount;
     private boolean mContainingNotificationIsFaded = false;
     private RoundableState mRoundableState;
-    private boolean mIsNotificationGroupCornerEnabled;
+    private boolean mUseRoundnessSourceTypes;
 
     public NotificationChildrenContainer(Context context) {
         this(context, null);
@@ -218,6 +222,7 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Trace.beginSection("NotificationChildrenContainer#onMeasure");
         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
         boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
         boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
@@ -266,6 +271,7 @@
         }
 
         setMeasuredDimension(width, height);
+        Trace.endSection();
     }
 
     @Override
@@ -308,12 +314,24 @@
 
         row.setContentTransformationAmount(0, false /* isLastChild */);
         row.setNotificationFaded(mContainingNotificationIsFaded);
+
+        if (!mUseRoundnessSourceTypes) {
+            // 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.resetLegacyOnScrollRoundness(row);
+        }
+
         // It doesn't make sense to keep old animations around, lets cancel them!
         ExpandableViewState viewState = row.getViewState();
         if (viewState != null) {
             viewState.cancelAnimations(row);
             row.cancelAppearDrawing();
         }
+
+        if (mUseRoundnessSourceTypes) {
+            applyRoundnessAndInvalidate();
+        }
     }
 
     private void ensureRemovedFromTransientContainer(View v) {
@@ -347,6 +365,11 @@
         if (!row.isRemoved()) {
             mGroupingUtil.restoreChildNotification(row);
         }
+
+        if (mUseRoundnessSourceTypes) {
+            row.requestRoundnessReset(FROM_PARENT);
+            applyRoundnessAndInvalidate();
+        }
     }
 
     /**
@@ -373,6 +396,10 @@
                             getContext(),
                             mNotificationHeader,
                             mContainingNotification);
+            mNotificationHeaderWrapper.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+            if (mUseRoundnessSourceTypes) {
+                mNotificationHeaderWrapper.setOnRoundnessChangedListener(this::invalidate);
+            }
             addView(mNotificationHeader, 0);
             invalidate();
         } else {
@@ -410,6 +437,12 @@
                                 getContext(),
                                 mNotificationHeaderLowPriority,
                                 mContainingNotification);
+                mNotificationHeaderWrapperLowPriority.useRoundnessSourceTypes(
+                        mUseRoundnessSourceTypes
+                );
+                if (mUseRoundnessSourceTypes) {
+                    mNotificationHeaderWrapper.setOnRoundnessChangedListener(this::invalidate);
+                }
                 addView(mNotificationHeaderLowPriority, 0);
                 invalidate();
             } else {
@@ -489,6 +522,20 @@
     }
 
     /**
+     * Sets the alpha on the content, while leaving the background of the container itself as is.
+     *
+     * @param alpha alpha value to apply to the content
+     */
+    public void setContentAlpha(float alpha) {
+        for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
+            mNotificationHeader.getChildAt(i).setAlpha(alpha);
+        }
+        for (ExpandableNotificationRow child : getAttachedChildren()) {
+            child.setContentAlpha(alpha);
+        }
+    }
+
+    /**
      * To be called any time the rows have been updated
      */
     public void updateExpansionStates() {
@@ -818,7 +865,7 @@
 
             isCanvasChanged = true;
             canvas.save();
-            if (mIsNotificationGroupCornerEnabled && translation != 0f) {
+            if (mUseRoundnessSourceTypes && translation != 0f) {
                 clipPath.offset(translation, 0f);
                 canvas.clipPath(clipPath);
                 clipPath.offset(-translation, 0f);
@@ -1369,20 +1416,28 @@
     }
 
     @Override
-    public void applyRoundness() {
-        Roundable.super.applyRoundness();
+    public void applyRoundnessAndInvalidate() {
         boolean last = true;
         for (int i = mAttachedChildren.size() - 1; i >= 0; i--) {
             ExpandableNotificationRow child = mAttachedChildren.get(i);
             if (child.getVisibility() == View.GONE) {
                 continue;
             }
-            child.requestBottomRoundness(
-                    last ? getBottomRoundness() : 0f,
-                    /* animate = */ isShown(),
-                    SourceType.DefaultValue);
+            if (mUseRoundnessSourceTypes) {
+                child.requestRoundness(
+                        /* top = */ 0f,
+                        /* bottom = */ last ? getBottomRoundness() : 0f,
+                        FROM_PARENT);
+            } else {
+                child.requestRoundness(
+                        /* top = */ 0f,
+                        /* bottom = */ last ? getBottomRoundness() : 0f,
+                        LegacySourceType.DefaultValue,
+                        /* animate = */ isShown());
+            }
             last = false;
         }
+        Roundable.super.applyRoundnessAndInvalidate();
     }
 
     public void setHeaderVisibleAmount(float headerVisibleAmount) {
@@ -1440,10 +1495,17 @@
     }
 
     /**
-     * Enable the support for rounded corner in notification group
+     * Enable the support for rounded corner based on the SourceType
+     *
      * @param enabled true if is supported
      */
-    public void enableNotificationGroupCorner(boolean enabled) {
-        mIsNotificationGroupCornerEnabled = enabled;
+    public void useRoundnessSourceTypes(boolean enabled) {
+        mUseRoundnessSourceTypes = enabled;
+    }
+
+    @Override
+    public String toString() {
+        String roundableStateDebug = "RoundableState = " + getRoundableState().debugString();
+        return "NotificationChildrenContainer:" + hashCode() + " { " + roundableStateDebug + " }";
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 6810055..fde8c4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -25,6 +25,9 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.Roundable;
 import com.android.systemui.statusbar.notification.SourceType;
@@ -45,6 +48,7 @@
 public class NotificationRoundnessManager implements Dumpable {
 
     private static final String TAG = "NotificationRoundnessManager";
+    private static final SourceType DISMISS_ANIMATION = SourceType.from("DismissAnimation");
 
     private final ExpandableView[] mFirstInSectionViews;
     private final ExpandableView[] mLastInSectionViews;
@@ -63,12 +67,14 @@
     private ExpandableView mSwipedView = null;
     private Roundable mViewBeforeSwipedView = null;
     private Roundable mViewAfterSwipedView = null;
+    private boolean mUseRoundnessSourceTypes;
 
     @Inject
     NotificationRoundnessManager(
             NotificationSectionsFeatureManager sectionsFeatureManager,
             NotificationRoundnessLogger notifLogger,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            FeatureFlags featureFlags) {
         int numberOfSections = sectionsFeatureManager.getNumberOfBuckets();
         mFirstInSectionViews = new ExpandableView[numberOfSections];
         mLastInSectionViews = new ExpandableView[numberOfSections];
@@ -76,6 +82,7 @@
         mTmpLastInSectionViews = new ExpandableView[numberOfSections];
         mNotifLogger = notifLogger;
         mDumpManager = dumpManager;
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
 
         mDumpManager.registerDumpable(TAG, this);
     }
@@ -94,6 +101,7 @@
     }
 
     public void updateView(ExpandableView view, boolean animate) {
+        if (mUseRoundnessSourceTypes) return;
         boolean changed = updateViewWithoutCallback(view, animate);
         if (changed) {
             mRoundingChangedCallback.run();
@@ -110,6 +118,7 @@
     boolean updateViewWithoutCallback(
             ExpandableView view,
             boolean animate) {
+        if (mUseRoundnessSourceTypes) return false;
         if (view == null
                 || view == mViewBeforeSwipedView
                 || view == mViewAfterSwipedView) {
@@ -118,13 +127,13 @@
 
         final boolean isTopChanged = view.requestTopRoundness(
                 getRoundnessDefaultValue(view, true /* top */),
-                animate,
-                SourceType.DefaultValue);
+                LegacySourceType.DefaultValue,
+                animate);
 
         final boolean isBottomChanged = view.requestBottomRoundness(
                 getRoundnessDefaultValue(view, /* top = */ false),
-                animate,
-                SourceType.DefaultValue);
+                LegacySourceType.DefaultValue,
+                animate);
 
         final boolean isFirstInSection = isFirstInSection(view);
         final boolean isLastInSection = isLastInSection(view);
@@ -139,6 +148,7 @@
     }
 
     private boolean isFirstInSection(ExpandableView view) {
+        if (mUseRoundnessSourceTypes) return false;
         for (int i = 0; i < mFirstInSectionViews.length; i++) {
             if (view == mFirstInSectionViews[i]) {
                 return true;
@@ -148,6 +158,7 @@
     }
 
     private boolean isLastInSection(ExpandableView view) {
+        if (mUseRoundnessSourceTypes) return false;
         for (int i = mLastInSectionViews.length - 1; i >= 0; i--) {
             if (view == mLastInSectionViews[i]) {
                 return true;
@@ -160,9 +171,6 @@
             Roundable viewBefore,
             ExpandableView viewSwiped,
             Roundable viewAfter) {
-        final boolean animate = true;
-        final SourceType source = SourceType.OnDismissAnimation;
-
         // This method requires you to change the roundness of the current View targets and reset
         // the roundness of the old View targets (if any) to 0f.
         // To avoid conflicts, it generates a set of old Views and removes the current Views
@@ -172,31 +180,34 @@
         if (mSwipedView != null) oldViews.add(mSwipedView);
         if (mViewAfterSwipedView != null) oldViews.add(mViewAfterSwipedView);
 
+        final SourceType source;
+        if (mUseRoundnessSourceTypes) {
+            source = DISMISS_ANIMATION;
+        } else {
+            source = LegacySourceType.OnDismissAnimation;
+        }
+
         mViewBeforeSwipedView = viewBefore;
         if (viewBefore != null) {
             oldViews.remove(viewBefore);
-            viewBefore.requestTopRoundness(0f, animate, source);
-            viewBefore.requestBottomRoundness(1f, animate, source);
+            viewBefore.requestRoundness(/* top = */ 0f, /* bottom = */ 1f, source);
         }
 
         mSwipedView = viewSwiped;
         if (viewSwiped != null) {
             oldViews.remove(viewSwiped);
-            viewSwiped.requestTopRoundness(1f, animate, source);
-            viewSwiped.requestBottomRoundness(1f, animate, source);
+            viewSwiped.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, source);
         }
 
         mViewAfterSwipedView = viewAfter;
         if (viewAfter != null) {
             oldViews.remove(viewAfter);
-            viewAfter.requestTopRoundness(1f, animate, source);
-            viewAfter.requestBottomRoundness(0f, animate, source);
+            viewAfter.requestRoundness(/* top = */ 1f, /* bottom = */ 0f, source);
         }
 
         // After setting the current Views, reset the views that are still present in the set.
         for (Roundable oldView : oldViews) {
-            oldView.requestTopRoundness(0f, animate, source);
-            oldView.requestBottomRoundness(0f, animate, source);
+            oldView.requestRoundnessReset(source);
         }
     }
 
@@ -204,7 +215,23 @@
         mIsClearAllInProgress = isClearingAll;
     }
 
+    /**
+     * Check if "Clear all" notifications is in progress.
+     */
+    public boolean isClearAllInProgress() {
+        return mIsClearAllInProgress;
+    }
+
+    /**
+     * Check if we can request the `Pulsing` roundness for notification.
+     */
+    public boolean shouldRoundNotificationPulsing() {
+        return mRoundForPulsingViews;
+    }
+
     private float getRoundnessDefaultValue(Roundable view, boolean top) {
+        if (mUseRoundnessSourceTypes) return 0f;
+
         if (view == null) {
             return 0f;
         }
@@ -250,6 +277,7 @@
     }
 
     public void setExpanded(float expandedHeight, float appearFraction) {
+        if (mUseRoundnessSourceTypes) return;
         mExpanded = expandedHeight != 0.0f;
         mAppearFraction = appearFraction;
         if (mTrackedHeadsUp != null) {
@@ -258,6 +286,7 @@
     }
 
     public void updateRoundedChildren(NotificationSection[] sections) {
+        if (mUseRoundnessSourceTypes) return;
         boolean anyChanged = false;
         for (int i = 0; i < sections.length; i++) {
             mTmpFirstInSectionViews[i] = mFirstInSectionViews[i];
@@ -280,6 +309,7 @@
             NotificationSection[] sections,
             ExpandableView[] oldViews,
             boolean first) {
+        if (mUseRoundnessSourceTypes) return false;
         boolean anyChanged = false;
         for (ExpandableView oldView : oldViews) {
             if (oldView != null) {
@@ -313,6 +343,7 @@
             NotificationSection[] sections,
             ExpandableView[] oldViews,
             boolean first) {
+        if (mUseRoundnessSourceTypes) return false;
         boolean anyChanged = false;
         for (NotificationSection section : sections) {
             ExpandableView newView =
@@ -339,6 +370,15 @@
         mAnimatedChildren = animatedChildren;
     }
 
+    /**
+     * Check if the view should be animated
+     * @param view target view
+     * @return true, if is in the AnimatedChildren set
+     */
+    public boolean isAnimatedChild(ExpandableView view) {
+        return mAnimatedChildren.contains(view);
+    }
+
     public void setOnRoundingChangedCallback(Runnable roundingChangedCallback) {
         mRoundingChangedCallback = roundingChangedCallback;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index a1b77ac..070b439 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -19,8 +19,11 @@
 import android.util.Log
 import android.view.View
 import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.media.controls.ui.KeyguardMediaController
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.SourceType
 import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
 import com.android.systemui.statusbar.notification.dagger.AlertingHeader
@@ -44,12 +47,16 @@
     private val keyguardMediaController: KeyguardMediaController,
     private val sectionsFeatureManager: NotificationSectionsFeatureManager,
     private val mediaContainerController: MediaContainerController,
+    private val notificationRoundnessManager: NotificationRoundnessManager,
     @IncomingHeader private val incomingHeaderController: SectionHeaderController,
     @PeopleHeader private val peopleHeaderController: SectionHeaderController,
     @AlertingHeader private val alertingHeaderController: SectionHeaderController,
-    @SilentHeader private val silentHeaderController: SectionHeaderController
+    @SilentHeader private val silentHeaderController: SectionHeaderController,
+    featureFlags: FeatureFlags
 ) : SectionProvider {
 
+    private val useRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)
+
     private val configurationListener = object : ConfigurationController.ConfigurationListener {
         override fun onLocaleListChanged() {
             reinflateViews()
@@ -177,11 +184,49 @@
                         size = sections.size,
                         operation = SectionBounds::addNotif
                 )
+
+        // Build a set of the old first/last Views of the sections
+        val oldFirstChildren = sections.mapNotNull { it.firstVisibleChild }.toSet().toMutableSet()
+        val oldLastChildren = sections.mapNotNull { it.lastVisibleChild }.toSet().toMutableSet()
+
         // Update each section with the associated boundary, tracking if there was a change
         val changed = sections.fold(false) { changed, section ->
             val bounds = sectionBounds[section.bucket] ?: SectionBounds.None
-            bounds.updateSection(section) || changed
+            val isSectionChanged = bounds.updateSection(section)
+            isSectionChanged || changed
         }
+
+        if (useRoundnessSourceTypes) {
+            val newFirstChildren = sections.mapNotNull { it.firstVisibleChild }
+            val newLastChildren = sections.mapNotNull { it.lastVisibleChild }
+
+            // Update the roundness of Views that weren't already in the first/last position
+            newFirstChildren.forEach { firstChild ->
+                val wasFirstChild = oldFirstChildren.remove(firstChild)
+                if (!wasFirstChild) {
+                    val notAnimatedChild = !notificationRoundnessManager.isAnimatedChild(firstChild)
+                    val animated = firstChild.isShown && notAnimatedChild
+                    firstChild.requestTopRoundness(1f, SECTION, animated)
+                }
+            }
+            newLastChildren.forEach { lastChild ->
+                val wasLastChild = oldLastChildren.remove(lastChild)
+                if (!wasLastChild) {
+                    val notAnimatedChild = !notificationRoundnessManager.isAnimatedChild(lastChild)
+                    val animated = lastChild.isShown && notAnimatedChild
+                    lastChild.requestBottomRoundness(1f, SECTION, animated)
+                }
+            }
+
+            // The Views left in the set are no longer in the first/last position
+            oldFirstChildren.forEach { noMoreFirstChild ->
+                noMoreFirstChild.requestTopRoundness(0f, SECTION)
+            }
+            oldLastChildren.forEach { noMoreLastChild ->
+                noMoreLastChild.requestBottomRoundness(0f, SECTION)
+            }
+        }
+
         if (DEBUG) {
             logSections(sections)
         }
@@ -215,5 +260,6 @@
     companion object {
         private const val TAG = "NotifSectionsManager"
         private const val DEBUG = false
+        private val SECTION = SourceType.from("Section")
     }
 }
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..21e2bd8 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
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import static android.os.Trace.TRACE_TAG_ALWAYS;
+
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL;
 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
@@ -30,9 +32,12 @@
 import android.animation.TimeAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -44,6 +49,7 @@
 import android.graphics.Path;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.Trace;
 import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.IndentingPrintWriter;
@@ -114,6 +120,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;
@@ -183,9 +191,13 @@
 
     private final boolean mDebugLines;
     private Paint mDebugPaint;
-    /** Used to track the Y positions that were already used to draw debug text labels. */
+    /**
+     * Used to track the Y positions that were already used to draw debug text labels.
+     */
     private Set<Integer> mDebugTextUsedYPositions;
     private final boolean mDebugRemoveAnimation;
+    private final boolean mSimplifiedAppearFraction;
+    private final boolean mUseRoundnessSourceTypes;
 
     private int mContentHeight;
     private float mIntrinsicContentHeight;
@@ -346,15 +358,14 @@
     private final Rect mQsHeaderBound = new Rect();
     private boolean mContinuousShadowUpdate;
     private boolean mContinuousBackgroundUpdate;
-    private final ViewTreeObserver.OnPreDrawListener mShadowUpdater
-            = () -> {
-                updateViewShadows();
-                return true;
-            };
+    private final ViewTreeObserver.OnPreDrawListener mShadowUpdater = () -> {
+        updateViewShadows();
+        return true;
+    };
     private final ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
-                updateBackground();
-                return true;
-            };
+        updateBackground();
+        return true;
+    };
     private final Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
         float endY = view.getTranslationY() + view.getActualHeight();
         float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
@@ -410,7 +421,8 @@
      */
     private int mMaxDisplayedNotifications = -1;
     private float mKeyguardBottomPadding = -1;
-    @VisibleForTesting int mStatusBarHeight;
+    @VisibleForTesting
+    int mStatusBarHeight;
     private int mMinInteractionHeight;
     private final Rect mClipRect = new Rect();
     private boolean mIsClipped;
@@ -559,6 +571,8 @@
 
     @Nullable
     private OnClickListener mManageButtonClickListener;
+    @Nullable
+    private OnNotificationRemovedListener mOnNotificationRemovedListener;
 
     public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
         super(context, attrs, 0, 0);
@@ -566,6 +580,8 @@
         FeatureFlags featureFlags = Dependency.get(FeatureFlags.class);
         mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
         mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
+        mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
         mScreenOffAnimationController =
                 Dependency.get(ScreenOffAnimationController.class);
@@ -731,7 +747,7 @@
     }
 
     private void logHunSkippedForUnexpectedState(ExpandableNotificationRow enr,
-            boolean expected, boolean actual) {
+                                                 boolean expected, boolean actual) {
         if (mLogger == null) return;
         mLogger.hunSkippedForUnexpectedState(enr.getEntry(), expected, actual);
     }
@@ -858,13 +874,13 @@
 
     /**
      * Draws round rects for each background section.
-     *
+     * <p>
      * We want to draw a round rect for each background section as defined by {@link #mSections}.
      * However, if two sections are directly adjacent with no gap between them (e.g. on the
      * lockscreen where the shelf can appear directly below the high priority section, or while
      * scrolling the shade so that the top of the shelf is right at the bottom of the high priority
      * section), we don't want to round the adjacent corners.
-     *
+     * <p>
      * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we
      * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect.
      * This method tracks the top of each rect we need to draw, then iterates through the visible
@@ -873,7 +889,7 @@
      * the current section.  When we're done iterating we will always have one rect left to draw.
      */
     private void drawBackgroundRects(Canvas canvas, int left, int right, int top,
-            int animationYOffset) {
+                                     int animationYOffset) {
         int backgroundRectTop = top;
         int lastSectionBottom =
                 mSections[0].getCurrentBounds().bottom + animationYOffset;
@@ -964,7 +980,7 @@
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     void initView(Context context, NotificationSwipeHelper swipeHelper,
-            NotificationStackSizeCalculator notificationStackSizeCalculator) {
+                  NotificationStackSizeCalculator notificationStackSizeCalculator) {
         mScroller = new OverScroller(getContext());
         mSwipeHelper = swipeHelper;
         mNotificationStackSizeCalculator = notificationStackSizeCalculator;
@@ -1072,6 +1088,12 @@
     @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Trace.beginSection("NotificationStackScrollLayout#onMeasure");
+        if (SPEW) {
+            Log.d(TAG, "onMeasure("
+                    + "widthMeasureSpec=" + MeasureSpec.toString(widthMeasureSpec) + ", "
+                    + "heightMeasureSpec=" + MeasureSpec.toString(heightMeasureSpec) + ")");
+        }
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
         int width = MeasureSpec.getSize(widthMeasureSpec);
@@ -1088,6 +1110,13 @@
         for (int i = 0; i < size; i++) {
             measureChild(getChildAt(i), childWidthSpec, childHeightSpec);
         }
+        Trace.endSection();
+    }
+
+    @Override
+    public void requestLayout() {
+        Trace.instant(TRACE_TAG_ALWAYS, "NotificationStackScrollLayout#requestLayout");
+        super.requestLayout();
     }
 
     @Override
@@ -1281,18 +1310,19 @@
     /**
      * @return Whether we should skip stack height updates.
      * True when
-     *      1) Unlock hint is running
-     *      2) Swiping up on lockscreen or flinging down after swipe up
+     * 1) Unlock hint is running
+     * 2) Swiping up on lockscreen or flinging down after swipe up
      */
     private boolean shouldSkipHeightUpdate() {
         return mAmbientState.isOnKeyguard()
                 && (mAmbientState.isUnlockHintRunning()
-                        || mAmbientState.isSwipingUp()
-                        || mAmbientState.isFlingingAfterSwipeUpOnLockscreen());
+                || mAmbientState.isSwipingUp()
+                || mAmbientState.isFlingingAfterSwipeUpOnLockscreen());
     }
 
     /**
      * Apply expansion fraction to the y position and height of the notifications panel.
+     *
      * @param listenerNeedsAnimation does the listener need to animate?
      */
     private void updateStackPosition(boolean listenerNeedsAnimation) {
@@ -1380,7 +1410,7 @@
             mExpandedHeight = height;
             setIsExpanded(height > 0);
             int minExpansionHeight = getMinExpansionHeight();
-            if (height < minExpansionHeight) {
+            if (height < minExpansionHeight && !mShouldUseSplitNotificationShade) {
                 mClipRect.left = 0;
                 mClipRect.right = getWidth();
                 mClipRect.top = 0;
@@ -1393,10 +1423,8 @@
         }
         int stackHeight;
         float translationY;
-        float appearEndPosition = getAppearEndPosition();
-        float appearStartPosition = getAppearStartPosition();
         float appearFraction = 1.0f;
-        boolean appearing = height < appearEndPosition;
+        boolean appearing = calculateAppearFraction(height) < 1;
         mAmbientState.setAppearing(appearing);
         if (!appearing) {
             translationY = 0;
@@ -1427,11 +1455,14 @@
             } else {
                 // This may happen when pushing up a heads up. We linearly push it up from the
                 // start
-                translationY = height - appearStartPosition + getExpandTranslationStart();
+                translationY = height - getAppearStartPosition() + getExpandTranslationStart();
             }
             stackHeight = (int) (height - translationY);
-            if (isHeadsUpTransition()) {
-                translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction);
+            if (isHeadsUpTransition() && appearFraction >= 0) {
+                int topSpacing = mShouldUseSplitNotificationShade
+                        ? mAmbientState.getStackTopMargin() : mTopPadding;
+                float startPos = mHeadsUpInset - topSpacing;
+                translationY = MathUtils.lerp(startPos, 0, appearFraction);
             }
         }
         mAmbientState.setAppearFraction(appearFraction);
@@ -1482,7 +1513,6 @@
     public void updateClipping() {
         boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
                 && !mHeadsUpAnimatingAway;
-        boolean clipToOutline = false;
         if (mIsClipped != clipped) {
             mIsClipped = clipped;
         }
@@ -1498,7 +1528,7 @@
             setClipBounds(null);
         }
 
-        setClipToOutline(clipToOutline);
+        setClipToOutline(false);
     }
 
     /**
@@ -1521,7 +1551,7 @@
             final int pinnedHeight = firstVisibleSection != null
                     ? firstVisibleSection.getFirstVisibleChild().getPinnedHeadsUpHeight()
                     : 0;
-            return mHeadsUpInset + pinnedHeight;
+            return mHeadsUpInset - mAmbientState.getStackTopMargin() + pinnedHeight;
         }
         return getMinExpansionHeight();
     }
@@ -1553,7 +1583,7 @@
      */
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private float getAppearEndPosition() {
-        int appearPosition = 0;
+        int appearPosition = mAmbientState.getStackTopMargin();
         int visibleNotifCount = mController.getVisibleNotificationCount();
         if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) {
             if (isHeadsUpTransition()
@@ -1577,18 +1607,49 @@
         return mAmbientState.getTrackedHeadsUpRow() != null;
     }
 
-    /**
-     * @param height the height of the panel
-     * @return the fraction of the appear animation that has been performed
-     */
+    // TODO(b/246353296): remove it when Flags.SIMPLIFIED_APPEAR_FRACTION is removed
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    public float calculateAppearFraction(float height) {
+    public float calculateAppearFractionOld(float height) {
         float appearEndPosition = getAppearEndPosition();
         float appearStartPosition = getAppearStartPosition();
         return (height - appearStartPosition)
                 / (appearEndPosition - appearStartPosition);
     }
 
+    /**
+     * @param height the height of the panel
+     * @return Fraction of the appear animation that has been performed. Normally follows expansion
+     * fraction so goes from 0 to 1, the only exception is HUN where it can go negative, down to -1,
+     * when HUN is swiped up.
+     */
+    @FloatRange(from = -1.0, to = 1.0)
+    public float simplifiedAppearFraction(float height) {
+        if (isHeadsUpTransition()) {
+            // HUN is a special case because fraction can go negative if swiping up. And for now
+            // it must go negative as other pieces responsible for proper translation up assume
+            // negative value for HUN going up.
+            // This can't use expansion fraction as that goes only from 0 to 1. Also when
+            // appear fraction for HUN is 0, expansion fraction will be already around 0.2-0.3
+            // and that makes translation jump immediately. Let's use old implementation for now and
+            // see if we can figure out something better
+            return MathUtils.constrain(calculateAppearFractionOld(height), -1, 1);
+        } else {
+            return mAmbientState.getExpansionFraction();
+        }
+    }
+
+    public float calculateAppearFraction(float height) {
+        if (mSimplifiedAppearFraction) {
+            return simplifiedAppearFraction(height);
+        } else if (mShouldUseSplitNotificationShade) {
+            // for split shade we want to always use the new way of calculating appear fraction
+            // because without it heads-up experience is very broken and it's less risky change
+            return simplifiedAppearFraction(height);
+        } else {
+            return calculateAppearFractionOld(height);
+        }
+    }
+
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public float getStackTranslation() {
         return mStackTranslation;
@@ -1654,7 +1715,7 @@
      */
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     ExpandableView getChildAtPosition(float touchX, float touchY,
-            boolean requireMinHeight, boolean ignoreDecors) {
+                                      boolean requireMinHeight, boolean ignoreDecors) {
         // find the view under the pointer, accounting for GONE views
         final int count = getChildCount();
         for (int childIdx = 0; childIdx < count; childIdx++) {
@@ -1746,7 +1807,8 @@
     @Override
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        mBottomInset = insets.getSystemWindowInsetBottom();
+        mBottomInset = insets.getSystemWindowInsetBottom()
+                + insets.getInsets(WindowInsets.Type.ime()).bottom;
 
         mWaterfallTopInset = 0;
         final DisplayCutout cutout = insets.getDisplayCutout();
@@ -1813,9 +1875,9 @@
     public void dismissViewAnimated(
             View child, Consumer<Boolean> endRunnable, int delay, long duration) {
         if (child instanceof SectionHeaderView) {
-             ((StackScrollerDecorView) child).setContentVisible(
-                     false /* visible */, true /* animate */, endRunnable);
-             return;
+            ((StackScrollerDecorView) child).setContentVisible(
+                    false /* visible */, true /* animate */, endRunnable);
+            return;
         }
         mSwipeHelper.dismissChild(
                 child,
@@ -1977,10 +2039,11 @@
     /**
      * Scrolls by the given delta, overscrolling if needed.  If called during a fling and the delta
      * would cause us to exceed the provided maximum overscroll, springs back instead.
-     *
+     * <p>
      * This method performs the determination of whether we're exceeding the overscroll and clamps
      * the scroll amount if so.  The actual scrolling/overscrolling happens in
      * {@link #onCustomOverScrolled(int, boolean)}
+     *
      * @param deltaY         The (signed) number of pixels to scroll.
      * @param scrollY        The current scroll position (absolute scrolling only).
      * @param scrollRangeY   The maximum allowable scroll position (absolute scrolling only).
@@ -2043,7 +2106,7 @@
      */
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
-            boolean cancelAnimators) {
+                                    boolean cancelAnimators) {
         setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
     }
 
@@ -2059,7 +2122,7 @@
      */
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
-            boolean cancelAnimators, boolean isRubberbanded) {
+                                    boolean cancelAnimators, boolean isRubberbanded) {
         if (cancelAnimators) {
             mStateAnimator.cancelOverScrollAnimators(onTop);
         }
@@ -2068,7 +2131,7 @@
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
-            boolean isRubberbanded) {
+                                             boolean isRubberbanded) {
         amount = Math.max(0, amount);
         if (animate) {
             mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
@@ -2124,7 +2187,7 @@
      * Scrolls to the given position, overscrolling if needed.  If called during a fling and the
      * position exceeds the provided maximum overscroll, springs back instead.
      *
-     * @param scrollY The target scroll position.
+     * @param scrollY  The target scroll position.
      * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
      *                 the overscroll limit.
      */
@@ -2233,8 +2296,8 @@
                 if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
                     List<ExpandableNotificationRow> notificationChildren =
                             row.getAttachedChildren();
-                    for (int childIndex = 0; childIndex < notificationChildren.size();
-                            childIndex++) {
+                    int childrenSize = notificationChildren.size();
+                    for (int childIndex = 0; childIndex < childrenSize; childIndex++) {
                         ExpandableNotificationRow rowChild = notificationChildren.get(childIndex);
                         if (rowChild.getTranslationY() + rowTranslation >= translationY) {
                             return rowChild;
@@ -2310,10 +2373,9 @@
     /**
      * Calculate the gap height between two different views
      *
-     * @param previous the previousView
-     * @param current the currentView
+     * @param previous     the previousView
+     * @param current      the currentView
      * @param visibleIndex the visible index in the list
-     *
      * @return the gap height needed before the current view
      */
     public float calculateGapHeight(
@@ -2321,7 +2383,7 @@
             ExpandableView current,
             int visibleIndex
     ) {
-       return mStackScrollAlgorithm.getGapHeightForChild(mSectionsManager, visibleIndex, current,
+        return mStackScrollAlgorithm.getGapHeightForChild(mSectionsManager, visibleIndex, current,
                 previous, mAmbientState.getFractionToShade(), mAmbientState.isOnKeyguard());
     }
 
@@ -2587,15 +2649,15 @@
         return mScrolledToTopOnFirstDown
                 && !mExpandedInThisMotion
                 && (initialVelocity > mMinimumVelocity
-                        || (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0));
+                || (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0));
     }
 
     /**
      * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
      * account.
      *
-     * @param qsHeight               the top padding imposed by the quick settings panel
-     * @param animate                whether to animate the change
+     * @param qsHeight the top padding imposed by the quick settings panel
+     * @param animate  whether to animate the change
      */
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public void updateTopPadding(float qsHeight, boolean animate) {
@@ -2669,21 +2731,34 @@
     }
 
 
-
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setChildTransferInProgress(boolean childTransferInProgress) {
         Assert.isMainThread();
         mChildTransferInProgress = childTransferInProgress;
     }
 
+    /**
+     * Set the remove notification listener
+     * @param listener callback for notification removed
+     */
+    public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
+        mOnNotificationRemovedListener = listener;
+    }
+
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     @Override
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
         // we only call our internal methods if this is actually a removal and not just a
         // notification which becomes a child notification
+        ExpandableView expandableView = (ExpandableView) child;
         if (!mChildTransferInProgress) {
-            onViewRemovedInternal((ExpandableView) child, this);
+            onViewRemovedInternal(expandableView, this);
+        }
+        if (mOnNotificationRemovedListener != null) {
+            mOnNotificationRemovedListener.onNotificationRemoved(
+                    expandableView,
+                    mChildTransferInProgress);
         }
     }
 
@@ -2711,6 +2786,10 @@
             }
         } else {
             mSwipedOutViews.remove(child);
+
+            if (child instanceof ExpandableNotificationRow) {
+                ((ExpandableNotificationRow) child).removeChildrenWithKeepInParent();
+            }
         }
         updateAnimationState(false, child);
 
@@ -2939,8 +3018,10 @@
             mAnimateNextSectionBoundsChange = false;
         }
         mAmbientState.setLastVisibleBackgroundChild(lastChild);
-        // TODO: Refactor SectionManager and put the RoundnessManager there.
-        mController.getNotificationRoundnessManager().updateRoundedChildren(mSections);
+        if (!mUseRoundnessSourceTypes) {
+            // TODO: Refactor SectionManager and put the RoundnessManager there.
+            mController.getNotificationRoundnessManager().updateRoundedChildren(mSections);
+        }
         mAnimateBottomOnLayout = false;
         invalidate();
     }
@@ -3147,7 +3228,7 @@
                     // Only animate if we still have pinned heads up, otherwise we just have the
                     // regular collapse animation of the lock screen
                     || (mKeyguardBypassEnabled && onKeyguard()
-                            && mInHeadsUpPinnedMode);
+                    && mInHeadsUpPinnedMode);
             if (performDisappearAnimation && !isHeadsUp) {
                 type = row.wasJustClicked()
                         ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
@@ -3292,7 +3373,7 @@
             AnimationEvent animEvent = duration == null
                     ? new AnimationEvent(child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)
                     : new AnimationEvent(
-                            child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION, duration);
+                    child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION, duration);
             mAnimationEvents.add(animEvent);
         }
         mChildrenChangingPositions.clear();
@@ -3383,7 +3464,9 @@
 
     @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
     protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) {
-        return new StackScrollAlgorithm(context, this);
+        StackScrollAlgorithm stackScrollAlgorithm = new StackScrollAlgorithm(context, this);
+        stackScrollAlgorithm.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+        return stackScrollAlgorithm;
     }
 
     /**
@@ -3683,6 +3766,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 +3779,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 final 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) {
@@ -3874,7 +3981,9 @@
         mOnEmptySpaceClickListener = listener;
     }
 
-    /** @hide */
+    /**
+     * @hide
+     */
     @Override
     @ShadeViewRefactor(RefactorComponent.INPUT)
     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
@@ -4144,7 +4253,7 @@
                 mShadeNeedsToClose = false;
                 postDelayed(
                         () -> {
-                            mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+                            mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
                         },
                         DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
             }
@@ -4475,7 +4584,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    void setEmptyShadeView(EmptyShadeView emptyShadeView) {
+    public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
         int index = -1;
         if (mEmptyShadeView != null) {
             index = indexOfChild(mEmptyShadeView);
@@ -4486,15 +4595,43 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
+    void updateEmptyShadeView(
+            boolean visible, boolean areNotificationsHiddenInShade, boolean areSeenNotifsFiltered) {
         mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
 
+        if (areNotificationsHiddenInShade) {
+            updateEmptyShadeView(R.string.dnd_suppressing_shade_text, 0, 0);
+        } else if (areSeenNotifsFiltered) {
+            updateEmptyShadeView(
+                    R.string.no_unseen_notif_text,
+                    R.string.unlock_to_see_notif_text,
+                    R.drawable.ic_friction_lock_closed);
+        } else {
+            updateEmptyShadeView(R.string.empty_shade_text, 0, 0);
+        }
+    }
+
+    private void updateEmptyShadeView(
+            @StringRes int newTextRes,
+            @StringRes int newFooterTextRes,
+            @DrawableRes int newFooterIconRes) {
         int oldTextRes = mEmptyShadeView.getTextResource();
-        int newTextRes = areNotificationsHiddenInShade
-                ? R.string.dnd_suppressing_shade_text : R.string.empty_shade_text;
         if (oldTextRes != newTextRes) {
             mEmptyShadeView.setText(newTextRes);
         }
+        int oldFooterTextRes = mEmptyShadeView.getFooterTextResource();
+        if (oldFooterTextRes != newFooterTextRes) {
+            mEmptyShadeView.setFooterText(newFooterTextRes);
+        }
+        int oldFooterIconRes = mEmptyShadeView.getFooterIconResource();
+        if (oldFooterIconRes != newFooterIconRes) {
+            mEmptyShadeView.setFooterIcon(newFooterIconRes);
+        }
+        if (newFooterIconRes != 0 || newFooterTextRes != 0) {
+            mEmptyShadeView.setFooterVisibility(View.VISIBLE);
+        } else {
+            mEmptyShadeView.setFooterVisibility(View.GONE);
+        }
     }
 
     public boolean isEmptyShadeViewVisible() {
@@ -4617,7 +4754,9 @@
         return touchY > mTopPadding + mStackTranslation;
     }
 
-    /** @hide */
+    /**
+     * @hide
+     */
     @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
@@ -5047,6 +5186,7 @@
             println(pw, "intrinsicPadding", mIntrinsicPadding);
             println(pw, "topPadding", mTopPadding);
             println(pw, "bottomPadding", mBottomPadding);
+            mNotificationStackSizeCalculator.dump(pw, args);
         });
         pw.println();
         pw.println("Contents:");
@@ -5241,7 +5381,9 @@
         return canChildBeCleared(row) && matchesSelection(row, selection);
     }
 
-    /** Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked. */
+    /**
+     * Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked.
+     */
     public void setManageButtonClickListener(@Nullable OnClickListener listener) {
         mManageButtonClickListener = listener;
         if (mFooterView != null) {
@@ -5266,9 +5408,9 @@
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void inflateEmptyShadeView() {
+        EmptyShadeView oldView = mEmptyShadeView;
         EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
                 R.layout.status_bar_no_notifications, this, false);
-        view.setText(R.string.empty_shade_text);
         view.setOnClickListener(v -> {
             final boolean showHistory = mController.isHistoryEnabled();
             Intent intent = showHistory
@@ -5277,6 +5419,10 @@
             mCentralSurfaces.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
         });
         setEmptyShadeView(view);
+        updateEmptyShadeView(
+                oldView == null ? R.string.empty_shade_text : oldView.getTextResource(),
+                oldView == null ? 0 : oldView.getFooterTextResource(),
+                oldView == null ? 0 : oldView.getFooterIconResource());
     }
 
     /**
@@ -5302,6 +5448,7 @@
     /**
      * Set how far the wake up is when waking up from pulsing. This is a height and will adjust the
      * notification positions accordingly.
+     *
      * @param height the new wake up height
      * @return the overflow how much the height is further than he lowest notification
      */
@@ -5533,7 +5680,7 @@
      * Set rounded rect clipping bounds on this view.
      */
     public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
-            int bottomRadius) {
+                                         int bottomRadius) {
         if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right
                 && mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top
                 && mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) {
@@ -5594,7 +5741,7 @@
         mLaunchingNotification = launching;
         mLaunchingNotificationNeedsToBeClipped = mLaunchAnimationParams != null
                 && (mLaunchAnimationParams.getStartRoundedTopClipping() > 0
-                        || mLaunchAnimationParams.getParentStartRoundedTopClipping() > 0);
+                || mLaunchAnimationParams.getParentStartRoundedTopClipping() > 0);
         if (!mLaunchingNotificationNeedsToBeClipped || !mLaunchingNotification) {
             mLaunchedNotificationClipPath.reset();
         }
@@ -5632,7 +5779,7 @@
                 mLaunchAnimationParams.getProgress(0,
                         NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING));
         int top = (int) Math.min(MathUtils.lerp(mRoundedRectClippingTop,
-                mLaunchAnimationParams.getTop(), expandProgress),
+                        mLaunchAnimationParams.getTop(), expandProgress),
                 mRoundedRectClippingTop);
         float topRadius = mLaunchAnimationParams.getTopCornerRadius();
         float bottomRadius = mLaunchAnimationParams.getBottomCornerRadius();
@@ -5756,25 +5903,25 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public interface OnOverscrollTopChangedListener {
 
-    /**
-     * Notifies a listener that the overscroll has changed.
-     *
-     * @param amount         the amount of overscroll, in pixels
-     * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
-     *                       unrubberbanded motion to directly expand overscroll view (e.g
-     *                       expand
-     *                       QS)
-     */
-    void onOverscrollTopChanged(float amount, boolean isRubberbanded);
+        /**
+         * Notifies a listener that the overscroll has changed.
+         *
+         * @param amount         the amount of overscroll, in pixels
+         * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
+         *                       unrubberbanded motion to directly expand overscroll view (e.g
+         *                       expand
+         *                       QS)
+         */
+        void onOverscrollTopChanged(float amount, boolean isRubberbanded);
 
-    /**
-     * Notify a listener that the scroller wants to escape from the scrolling motion and
-     * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
-     *
-     * @param velocity The velocity that the Scroller had when over flinging
-     * @param open     Should the fling open or close the overscroll view.
-     */
-    void flingTopOverscroll(float velocity, boolean open);
+        /**
+         * Notify a listener that the scroller wants to escape from the scrolling motion and
+         * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
+         *
+         * @param velocity The velocity that the Scroller had when over flinging
+         * @param open     Should the fling open or close the overscroll view.
+         */
+        void flingTopOverscroll(float velocity, boolean open);
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -6136,7 +6283,9 @@
         }
     };
 
-    public HeadsUpTouchHelper.Callback getHeadsUpCallback() { return mHeadsUpCallback; }
+    public HeadsUpTouchHelper.Callback getHeadsUpCallback() {
+        return mHeadsUpCallback;
+    }
 
     void onGroupExpandChanged(ExpandableNotificationRow changedRow, boolean expanded) {
         boolean animated = mAnimationsEnabled && (mIsExpanded || changedRow.isPinned());
@@ -6241,15 +6390,25 @@
         return mLastSentExpandedHeight;
     }
 
-    /** Enum for selecting some or all notification rows (does not included non-notif views). */
+    /**
+     * Enum for selecting some or all notification rows (does not included non-notif views).
+     */
     @Retention(SOURCE)
     @IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE})
-    @interface SelectedRows {}
-    /** All rows representing notifs. */
+    @interface SelectedRows {
+    }
+
+    /**
+     * All rows representing notifs.
+     */
     public static final int ROWS_ALL = 0;
-    /** Only rows where entry.isHighPriority() is true. */
+    /**
+     * Only rows where entry.isHighPriority() is true.
+     */
     public static final int ROWS_HIGH_PRIORITY = 1;
-    /** Only rows where entry.isHighPriority() is false. */
+    /**
+     * Only rows where entry.isHighPriority() is false.
+     */
     public static final int ROWS_GENTLE = 2;
 
     interface ClearAllListener {
@@ -6264,4 +6423,16 @@
         void onAnimationEnd(
                 List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows);
     }
+
+    /**
+     *
+     */
+    public interface OnNotificationRemovedListener {
+        /**
+         *
+         * @param child
+         * @param isTransferInProgress
+         */
+        void onNotificationRemoved(ExpandableView child, boolean isTransferInProgress);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index e1337826..4bcc0b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -81,6 +81,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -89,6 +90,8 @@
 import com.android.systemui.statusbar.notification.collection.PipelineDumper;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProvider;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
 import com.android.systemui.statusbar.notification.collection.render.NotifStats;
@@ -157,6 +160,7 @@
     private final NotifCollection mNotifCollection;
     private final UiEventLogger mUiEventLogger;
     private final NotificationRemoteInputManager mRemoteInputManager;
+    private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
     private final ShadeController mShadeController;
     private final KeyguardMediaController mKeyguardMediaController;
     private final SysuiStatusBarStateController mStatusBarStateController;
@@ -172,14 +176,18 @@
     private final StackStateLogger mStackStateLogger;
     private final NotificationStackScrollLogger mLogger;
     private final GroupExpansionManager mGroupExpansionManager;
+    private final NotifPipelineFlags mNotifPipelineFlags;
+    private final SeenNotificationsProvider mSeenNotificationsProvider;
 
     private NotificationStackScrollLayout mView;
     private boolean mFadeNotificationsOnDismiss;
     private NotificationSwipeHelper mSwipeHelper;
-    @Nullable private Boolean mHistoryEnabled;
+    @Nullable
+    private Boolean mHistoryEnabled;
     private int mBarState;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     private final FeatureFlags mFeatureFlags;
+    private final boolean mUseRoundnessSourceTypes;
     private final NotificationTargetsHelper mNotificationTargetsHelper;
 
     private View mLongPressedView;
@@ -385,7 +393,7 @@
                     if (item != null) {
                         Point origin = provider.getRevealAnimationOrigin();
                         mNotificationGutsManager.openGuts(row, origin.x, origin.y, item);
-                    } else  {
+                    } else {
                         Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no "
                                 + "menu item in menuItemtoExposeOnSnap. Skipping.");
                     }
@@ -414,7 +422,7 @@
 
                 @Override
                 public void onSnooze(StatusBarNotification sbn,
-                        NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
+                                     NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
                     mCentralSurfaces.setNotificationSnoozed(sbn, snoozeOption);
                 }
 
@@ -441,7 +449,11 @@
                     if (!row.isDismissed()) {
                         handleChildViewDismissed(view);
                     }
+
                     row.removeFromTransientContainer();
+                    if (row instanceof ExpandableNotificationRow) {
+                        ((ExpandableNotificationRow) row).removeChildrenWithKeepInParent();
+                    }
                 }
 
                 /**
@@ -534,7 +546,7 @@
 
                 @Override
                 public boolean updateSwipeProgress(View animView, boolean dismissable,
-                        float swipeProgress) {
+                                                   float swipeProgress) {
                     // Returning true prevents alpha fading.
                     return !mFadeNotificationsOnDismiss;
                 }
@@ -574,16 +586,22 @@
 
                 @Override
                 public void onHeadsUpPinned(NotificationEntry entry) {
-                    mNotificationRoundnessManager.updateView(entry.getRow(), false /* animate */);
+                    if (!mUseRoundnessSourceTypes) {
+                        mNotificationRoundnessManager.updateView(
+                                entry.getRow(),
+                                /* animate = */ false);
+                    }
                 }
 
                 @Override
                 public void onHeadsUpUnPinned(NotificationEntry entry) {
-                    ExpandableNotificationRow row = entry.getRow();
-                    // update the roundedness posted, because we might be animating away the
-                    // headsup soon, so no need to set the roundedness to 0 and then back to 1.
-                    row.post(() -> mNotificationRoundnessManager.updateView(row,
-                            true /* animate */));
+                    if (!mUseRoundnessSourceTypes) {
+                        ExpandableNotificationRow row = entry.getRow();
+                        // update the roundedness posted, because we might be animating away the
+                        // headsup soon, so no need to set the roundedness to 0 and then back to 1.
+                        row.post(() -> mNotificationRoundnessManager.updateView(row,
+                                true /* animate */));
+                    }
                 }
 
                 @Override
@@ -593,8 +611,10 @@
                     mView.setNumHeadsUp(numEntries);
                     mView.setTopHeadsUpEntry(topEntry);
                     generateHeadsUpAnimation(entry, isHeadsUp);
-                    ExpandableNotificationRow row = entry.getRow();
-                    mNotificationRoundnessManager.updateView(row, true /* animate */);
+                    if (!mUseRoundnessSourceTypes) {
+                        ExpandableNotificationRow row = entry.getRow();
+                        mNotificationRoundnessManager.updateView(row, true /* animate */);
+                    }
                 }
             };
 
@@ -633,11 +653,14 @@
             GroupExpansionManager groupManager,
             @SilentHeader SectionHeaderController silentHeaderController,
             NotifPipeline notifPipeline,
+            NotifPipelineFlags notifPipelineFlags,
             NotifCollection notifCollection,
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
             ShadeTransitionController shadeTransitionController,
             UiEventLogger uiEventLogger,
             NotificationRemoteInputManager remoteInputManager,
+            VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
+            SeenNotificationsProvider seenNotificationsProvider,
             ShadeController shadeController,
             InteractionJankMonitor jankMonitor,
             StackStateLogger stackLogger,
@@ -676,11 +699,15 @@
         mGroupExpansionManager = groupManager;
         mSilentHeaderController = silentHeaderController;
         mNotifPipeline = notifPipeline;
+        mNotifPipelineFlags = notifPipelineFlags;
         mNotifCollection = notifCollection;
         mUiEventLogger = uiEventLogger;
         mRemoteInputManager = remoteInputManager;
+        mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
+        mSeenNotificationsProvider = seenNotificationsProvider;
         mShadeController = shadeController;
         mFeatureFlags = featureFlags;
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
         mNotificationTargetsHelper = notificationTargetsHelper;
         updateResources();
     }
@@ -747,8 +774,12 @@
         mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
 
         mFadeNotificationsOnDismiss = mFeatureFlags.isEnabled(Flags.NOTIFICATION_DISMISSAL_FADE);
-        mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
-        mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+        if (!mUseRoundnessSourceTypes) {
+            mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
+            mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+        }
+
+        mVisibilityLocationProviderDelegator.setDelegate(this::isInVisibleLocation);
 
         mTunerService.addTunable(
                 (key, newValue) -> {
@@ -965,7 +996,7 @@
     }
 
     public boolean isAddOrRemoveAnimationPending() {
-        return mView.isAddOrRemoveAnimationPending();
+        return mView != null && mView.isAddOrRemoveAnimationPending();
     }
 
     public int getVisibleNotificationCount() {
@@ -979,9 +1010,11 @@
                 Log.wtf(TAG, "isHistoryEnabled failed to initialize its value");
                 return false;
             }
-            mHistoryEnabled = historyEnabled =
-                    Settings.Secure.getIntForUser(mView.getContext().getContentResolver(),
-                    Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
+            mHistoryEnabled = historyEnabled = Settings.Secure.getIntForUser(
+                    mView.getContext().getContentResolver(),
+                    Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+                    0,
+                    UserHandle.USER_CURRENT) == 1;
         }
         return historyEnabled;
     }
@@ -1011,7 +1044,7 @@
     }
 
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
-            boolean cancelAnimators) {
+                                    boolean cancelAnimators) {
         mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators);
     }
 
@@ -1122,7 +1155,9 @@
     }
 
     public void setAlpha(float alpha) {
-        mView.setAlpha(alpha);
+        if (mView != null) {
+            mView.setAlpha(alpha);
+        }
     }
 
     public float calculateAppearFraction(float height) {
@@ -1187,7 +1222,7 @@
 
     /**
      * Update whether we should show the empty shade view ("no notifications" in the shade).
-     *
+     * <p>
      * When in split mode, notifications are always visible regardless of the state of the
      * QuickSettings panel. That being the case, empty view is always shown if the other conditions
      * are true.
@@ -1202,14 +1237,18 @@
                 // For more details, see: b/228790482
                 && !isInTransitionToKeyguard();
 
-        mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
+        mView.updateEmptyShadeView(
+                shouldShow,
+                mZenModeController.areNotificationsHiddenInShade(),
+                mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()
+                        && mSeenNotificationsProvider.getHasFilteredOutSeenNotifications());
 
         Trace.endSection();
     }
 
     /**
      * @return true if {@link StatusBarStateController} is in transition to the KEYGUARD
-     *         and false otherwise.
+     * and false otherwise.
      */
     private boolean isInTransitionToKeyguard() {
         final int currentState = mStatusBarStateController.getState();
@@ -1241,7 +1280,9 @@
         mView.setExpandedHeight(expandedHeight);
     }
 
-    /** Sets the QS header. Used to check if a touch is within its bounds. */
+    /**
+     * Sets the QS header. Used to check if a touch is within its bounds.
+     */
     public void setQsHeader(ViewGroup view) {
         mView.setQsHeader(view);
     }
@@ -1304,7 +1345,7 @@
     public RemoteInputController.Delegate createDelegate() {
         return new RemoteInputController.Delegate() {
             public void setRemoteInputActive(NotificationEntry entry,
-                    boolean remoteInputActive) {
+                                             boolean remoteInputActive) {
                 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
                 entry.notifyHeightChanged(true /* needsAnimation */);
                 updateFooter();
@@ -1435,7 +1476,7 @@
     }
 
     private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove,
-            @SelectedRows int selectedRows) {
+                                @SelectedRows int selectedRows) {
         if (selectedRows == ROWS_ALL) {
             mNotifCollection.dismissAllNotifications(
                     mLockscreenUserManager.getCurrentUserId());
@@ -1478,8 +1519,8 @@
 
     /**
      * @return the inset during the full shade transition, that needs to be added to the position
-     *         of the quick settings edge. This is relevant for media, that is transitioning
-     *         from the keyguard host to the quick settings one.
+     * of the quick settings edge. This is relevant for media, that is transitioning
+     * from the keyguard host to the quick settings one.
      */
     public int getFullShadeTransitionInset() {
         MediaContainerView view = mKeyguardMediaController.getSinglePaneContainer();
@@ -1493,10 +1534,10 @@
     /**
      * @param fraction The fraction of lockscreen to shade transition.
      *                 0f for all other states.
-     *
-     * Once the lockscreen to shade transition completes and the shade is 100% open,
-     * LockscreenShadeTransitionController resets amount and fraction to 0, where they remain
-     * until the next lockscreen-to-shade transition.
+     *                 <p>
+     *                 Once the lockscreen to shade transition completes and the shade is 100% open,
+     *                 LockscreenShadeTransitionController resets amount and fraction to 0, where
+     *                 they remain until the next lockscreen-to-shade transition.
      */
     public void setTransitionToFullShadeAmount(float fraction) {
         mView.setFractionToShade(fraction);
@@ -1509,7 +1550,9 @@
         mView.setExtraTopInsetForFullShadeTransition(overScrollAmount);
     }
 
-    /** */
+    /**
+     *
+     */
     public void setWillExpand(boolean willExpand) {
         mView.setWillExpand(willExpand);
     }
@@ -1525,7 +1568,7 @@
      * Set rounded rect clipping bounds on this view.
      */
     public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
-            int bottomRadius) {
+                                         int bottomRadius) {
         mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius);
     }
 
@@ -1545,6 +1588,15 @@
     }
 
     /**
+     * Set the remove notification listener
+     * @param listener callback for notification removed
+     */
+    public void setOnNotificationRemovedListener(
+            NotificationStackScrollLayout.OnNotificationRemovedListener listener) {
+        mView.setOnNotificationRemovedListener(listener);
+    }
+
+    /**
      * Enum for UiEvent logged from this class
      */
     enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
@@ -1554,10 +1606,13 @@
         @UiEvent(doc = "User dismissed all silent notifications from notification panel.")
         DISMISS_SILENT_NOTIFICATIONS_PANEL(314);
         private final int mId;
+
         NotificationPanelEvent(int id) {
             mId = id;
         }
-        @Override public int getId() {
+
+        @Override
+        public int getId() {
             return mId;
         }
 
@@ -1698,8 +1753,12 @@
         @Override
         public void bindRow(ExpandableNotificationRow row) {
             row.setHeadsUpAnimatingAwayListener(animatingAway -> {
-                mNotificationRoundnessManager.updateView(row, false);
-                mHeadsUpAppearanceController.updateHeader(row.getEntry());
+                if (!mUseRoundnessSourceTypes) {
+                    mNotificationRoundnessManager.updateView(row, false);
+                }
+                NotificationEntry entry = row.getEntry();
+                mHeadsUpAppearanceController.updateHeader(entry);
+                mHeadsUpAppearanceController.updateHeadsUpAndPulsingRoundness(entry);
             });
         }
 
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/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index ae854e2..25f99c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.util.Compile
 import com.android.systemui.util.children
+import java.io.PrintWriter
 import javax.inject.Inject
 import kotlin.math.max
 import kotlin.math.min
@@ -53,6 +54,8 @@
     @Main private val resources: Resources
 ) {
 
+    private lateinit var lastComputeHeightLog : String
+
     /**
      * Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf.
      * If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space
@@ -114,7 +117,9 @@
         shelfIntrinsicHeight: Float
     ): Int {
         log { "\n" }
-        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
+
+        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
+            /* computeHeight= */ false)
 
         var maxNotifications =
             stackHeightSequence.lastIndexWhile { heightResult ->
@@ -157,18 +162,21 @@
         shelfIntrinsicHeight: Float
     ): Float {
         log { "\n" }
+        lastComputeHeightLog = ""
         val heightPerMaxNotifications =
-            computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
+            computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
+                    /* computeHeight= */ true)
 
         val (notificationsHeight, shelfHeightWithSpaceBefore) =
             heightPerMaxNotifications.elementAtOrElse(maxNotifications) {
                 heightPerMaxNotifications.last() // Height with all notifications visible.
             }
-        log {
-            "computeHeight(maxNotifications=$maxNotifications," +
+        lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," +
                 "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " +
                 "${notificationsHeight + shelfHeightWithSpaceBefore}" +
                 " = ($notificationsHeight + $shelfHeightWithSpaceBefore)"
+        log {
+            lastComputeHeightLog
         }
         return notificationsHeight + shelfHeightWithSpaceBefore
     }
@@ -184,7 +192,8 @@
 
     private fun computeHeightPerNotificationLimit(
         stack: NotificationStackScrollLayout,
-        shelfHeight: Float
+        shelfHeight: Float,
+        computeHeight: Boolean
     ): Sequence<StackHeight> = sequence {
         log { "computeHeightPerNotificationLimit" }
 
@@ -213,9 +222,14 @@
                             currentIndex = firstViewInShelfIndex)
                     spaceBeforeShelf + shelfHeight
                 }
+
+            val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " +
+                "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
+            if (computeHeight) {
+                lastComputeHeightLog += "\n" + currentLog
+            }
             log {
-                "i=$i notificationsHeight=$notifications " +
-                    "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
+                currentLog
             }
             yield(
                 StackHeight(
@@ -260,6 +274,10 @@
         return size
     }
 
+    fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog")
+    }
+
     private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
         if (visibility == GONE || hasNoContentHeight()) return false
         if (onLockscreen) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index ee57411..aaf9300 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -20,6 +20,7 @@
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.res.Resources;
 import android.graphics.Rect;
@@ -34,9 +35,11 @@
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 
@@ -49,6 +52,7 @@
     @VisibleForTesting
     protected static final long COVER_MENU_DELAY = 4000;
     private static final String TAG = "NotificationSwipeHelper";
+    private static final SourceType SWIPE_DISMISS = SourceType.from("SwipeDismiss");
     private final Runnable mFalsingCheck;
     private View mTranslatingParentView;
     private View mMenuExposedView;
@@ -64,13 +68,21 @@
     private WeakReference<NotificationMenuRowPlugin> mCurrMenuRowRef;
     private boolean mIsExpanded;
     private boolean mPulsing;
+    private final NotificationRoundnessManager mNotificationRoundnessManager;
+    private final boolean mUseRoundnessSourceTypes;
 
     NotificationSwipeHelper(
-            Resources resources, ViewConfiguration viewConfiguration,
-            FalsingManager falsingManager, FeatureFlags featureFlags,
-            int swipeDirection, NotificationCallback callback,
-            NotificationMenuRowPlugin.OnMenuEventListener menuListener) {
+            Resources resources,
+            ViewConfiguration viewConfiguration,
+            FalsingManager falsingManager,
+            FeatureFlags featureFlags,
+            int swipeDirection,
+            NotificationCallback callback,
+            NotificationMenuRowPlugin.OnMenuEventListener menuListener,
+            NotificationRoundnessManager notificationRoundnessManager) {
         super(swipeDirection, callback, resources, viewConfiguration, falsingManager, featureFlags);
+        mNotificationRoundnessManager = notificationRoundnessManager;
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
         mMenuListener = menuListener;
         mCallback = callback;
         mFalsingCheck = () -> resetExposedMenuView(true /* animate */, true /* force */);
@@ -304,6 +316,33 @@
         handleMenuCoveredOrDismissed();
     }
 
+    @Override
+    protected void prepareDismissAnimation(View view, Animator anim) {
+        super.prepareDismissAnimation(view, anim);
+
+        if (mUseRoundnessSourceTypes
+                && view instanceof ExpandableNotificationRow
+                && mNotificationRoundnessManager.isClearAllInProgress()) {
+            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    row.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, SWIPE_DISMISS);
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    row.requestRoundnessReset(SWIPE_DISMISS);
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    row.requestRoundnessReset(SWIPE_DISMISS);
+                }
+            });
+        }
+    }
+
     @VisibleForTesting
     protected void superDismissChild(final View view, float velocity, boolean useAccelerateInterpolator) {
         super.dismissChild(view, velocity, useAccelerateInterpolator);
@@ -521,14 +560,17 @@
         private int mSwipeDirection;
         private NotificationCallback mNotificationCallback;
         private NotificationMenuRowPlugin.OnMenuEventListener mOnMenuEventListener;
+        private NotificationRoundnessManager mNotificationRoundnessManager;
 
         @Inject
         Builder(@Main Resources resources, ViewConfiguration viewConfiguration,
-                FalsingManager falsingManager, FeatureFlags featureFlags) {
+                FalsingManager falsingManager, FeatureFlags featureFlags,
+                NotificationRoundnessManager notificationRoundnessManager) {
             mResources = resources;
             mViewConfiguration = viewConfiguration;
             mFalsingManager = falsingManager;
             mFeatureFlags = featureFlags;
+            mNotificationRoundnessManager = notificationRoundnessManager;
         }
 
         Builder setSwipeDirection(int swipeDirection) {
@@ -549,7 +591,8 @@
 
         NotificationSwipeHelper build() {
             return new NotificationSwipeHelper(mResources, mViewConfiguration, mFalsingManager,
-                    mFeatureFlags, mSwipeDirection, mNotificationCallback, mOnMenuEventListener);
+                    mFeatureFlags, mSwipeDirection, mNotificationCallback, mOnMenuEventListener,
+                    mNotificationRoundnessManager);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
index 991a14b..548d1a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
@@ -20,8 +20,7 @@
 constructor(
     featureFlags: FeatureFlags,
 ) {
-    private val isNotificationGroupCornerEnabled =
-        featureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER)
+    private val useRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)
 
     /**
      * This method looks for views that can be rounded (and implement [Roundable]) during a
@@ -48,7 +47,7 @@
         if (notificationParent != null && childrenContainer != null) {
             // We are inside a notification group
 
-            if (!isNotificationGroupCornerEnabled) {
+            if (!useRoundnessSourceTypes) {
                 return RoundableTargets(null, null, null)
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index eea1d911..aff7b4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -31,6 +31,7 @@
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,23 +52,27 @@
 
     private static final String TAG = "StackScrollAlgorithm";
     private static final Boolean DEBUG = false;
+    private static final SourceType STACK_SCROLL_ALGO = SourceType.from("StackScrollAlgorithm");
 
     private final ViewGroup mHostView;
     private float mPaddingBetweenElements;
     private float mGapHeight;
     private float mGapHeightOnLockscreen;
     private int mCollapsedSize;
+    private boolean mEnableNotificationClipping;
 
     private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
     private boolean mIsExpanded;
     private boolean mClipNotificationScrollToTop;
-    @VisibleForTesting float mHeadsUpInset;
+    @VisibleForTesting
+    float mHeadsUpInset;
     private int mPinnedZTranslationExtra;
     private float mNotificationScrimPadding;
     private int mMarginBottom;
     private float mQuickQsOffsetHeight;
     private float mSmallCornerRadius;
     private float mLargeCornerRadius;
+    private boolean mUseRoundnessSourceTypes;
 
     public StackScrollAlgorithm(
             Context context,
@@ -85,6 +90,7 @@
         mPaddingBetweenElements = res.getDimensionPixelSize(
                 R.dimen.notification_divider_height);
         mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
+        mEnableNotificationClipping = res.getBoolean(R.bool.notification_enable_clipping);
         mClipNotificationScrollToTop = res.getBoolean(R.bool.config_clipNotificationScrollToTop);
         int statusBarHeight = SystemBarUtils.getStatusBarHeight(context);
         mHeadsUpInset = statusBarHeight + res.getDimensionPixelSize(
@@ -126,7 +132,7 @@
     }
 
     private void updateAlphaState(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                  AmbientState ambientState) {
         for (ExpandableView view : algorithmState.visibleChildren) {
             final ViewState viewState = view.getViewState();
 
@@ -226,7 +232,7 @@
     }
 
     private void getNotificationChildrenStates(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                               AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
             ExpandableView v = algorithmState.visibleChildren.get(i);
@@ -238,7 +244,7 @@
     }
 
     private void updateSpeedBumpState(StackScrollAlgorithmState algorithmState,
-            int speedBumpIndex) {
+                                      int speedBumpIndex) {
         int childCount = algorithmState.visibleChildren.size();
         int belowSpeedBump = speedBumpIndex;
         for (int i = 0; i < childCount; i++) {
@@ -265,7 +271,7 @@
     }
 
     private void updateClipping(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                AmbientState ambientState) {
         float drawStart = ambientState.isOnKeyguard() ? 0
                 : ambientState.getStackY() - ambientState.getScrollY();
         float clipStart = 0;
@@ -289,7 +295,7 @@
                 // The bottom of this view is peeking out from under the previous view.
                 // Clip the part that is peeking out.
                 float overlapAmount = newNotificationEnd - firstHeadsUpEnd;
-                state.clipBottomAmount = (int) overlapAmount;
+                state.clipBottomAmount = mEnableNotificationClipping ? (int) overlapAmount : 0;
             } else {
                 state.clipBottomAmount = 0;
             }
@@ -311,7 +317,7 @@
      * Updates the dimmed, activated and hiding sensitive states of the children.
      */
     private void updateDimmedActivatedHideSensitive(AmbientState ambientState,
-            StackScrollAlgorithmState algorithmState) {
+                                                    StackScrollAlgorithmState algorithmState) {
         boolean dimmed = ambientState.isDimmed();
         boolean hideSensitive = ambientState.isHideSensitive();
         View activatedChild = ambientState.getActivatedChild();
@@ -405,7 +411,7 @@
     }
 
     private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
-            ExpandableView v) {
+                                   ExpandableView v) {
         ExpandableViewState viewState = v.getViewState();
         viewState.notGoneIndex = notGoneIndex;
         state.visibleChildren.add(v);
@@ -431,7 +437,7 @@
      * @param ambientState   The current ambient state
      */
     protected void updatePositionsForState(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                           AmbientState ambientState) {
         if (!ambientState.isOnKeyguard()
                 || (ambientState.isBypassEnabled() && ambientState.isPulseExpanding())) {
             algorithmState.mCurrentYPosition += mNotificationScrimPadding;
@@ -445,7 +451,7 @@
     }
 
     private void setLocation(ExpandableViewState expandableViewState, float currentYPosition,
-            int i) {
+                             int i) {
         expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
         if (currentYPosition <= 0) {
             expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
@@ -454,7 +460,7 @@
 
     /**
      * @return Fraction to apply to view height and gap between views.
-     *         Does not include shelf height even if shelf is showing.
+     * Does not include shelf height even if shelf is showing.
      */
     protected float getExpansionFractionWithoutShelf(
             StackScrollAlgorithmState algorithmState,
@@ -468,7 +474,7 @@
                 && (!ambientState.isBypassEnabled() || !ambientState.isPulseExpanding())
                 ? 0 : mNotificationScrimPadding;
 
-        final float stackHeight = ambientState.getStackHeight()  - shelfHeight - scrimPadding;
+        final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding;
         final float stackEndHeight = ambientState.getStackEndHeight() - shelfHeight - scrimPadding;
         if (stackEndHeight == 0f) {
             // This should not happen, since even when the shade is empty we show EmptyShadeView
@@ -493,22 +499,27 @@
     }
 
     @VisibleForTesting
-    void maybeUpdateHeadsUpIsVisible(ExpandableViewState viewState, boolean isShadeExpanded,
-            boolean mustStayOnScreen, boolean topVisible, float viewEnd, float hunMax) {
-
+    void maybeUpdateHeadsUpIsVisible(
+            ExpandableViewState viewState,
+            boolean isShadeExpanded,
+            boolean mustStayOnScreen,
+            boolean topVisible,
+            float viewEnd,
+            float hunMax) {
         if (isShadeExpanded && mustStayOnScreen && topVisible) {
             viewState.headsUpIsVisible = viewEnd < hunMax;
         }
     }
 
     // TODO(b/172289889) polish shade open from HUN
+
     /**
      * Populates the {@link ExpandableViewState} for a single child.
      *
-     * @param i                The index of the child in
-     * {@link StackScrollAlgorithmState#visibleChildren}.
-     * @param algorithmState   The overall output state of the algorithm.
-     * @param ambientState     The input state provided to the algorithm.
+     * @param i              The index of the child in
+     *                       {@link StackScrollAlgorithmState#visibleChildren}.
+     * @param algorithmState The overall output state of the algorithm.
+     * @param ambientState   The input state provided to the algorithm.
      */
     protected void updateChild(
             int i,
@@ -582,8 +593,8 @@
                     final float stackBottom = !ambientState.isShadeExpanded()
                             || ambientState.getDozeAmount() == 1f
                             || bypassPulseNotExpanding
-                                    ? ambientState.getInnerHeight()
-                                    : ambientState.getStackHeight();
+                            ? ambientState.getInnerHeight()
+                            : ambientState.getStackHeight();
                     final float shelfStart = stackBottom
                             - ambientState.getShelf().getIntrinsicHeight()
                             - mPaddingBetweenElements;
@@ -619,9 +630,9 @@
      * Get the gap height needed for before a view
      *
      * @param sectionProvider the sectionProvider used to understand the sections
-     * @param visibleIndex the visible index of this view in the list
-     * @param child the child asked about
-     * @param previousChild the child right before it or null if none
+     * @param visibleIndex    the visible index of this view in the list
+     * @param child           the child asked about
+     * @param previousChild   the child right before it or null if none
      * @return the size of the gap needed or 0 if none is needed
      */
     public float getGapHeightForChild(
@@ -655,9 +666,9 @@
      * Does a given child need a gap, i.e spacing before a view?
      *
      * @param sectionProvider the sectionProvider used to understand the sections
-     * @param visibleIndex the visible index of this view in the list
-     * @param child the child asked about
-     * @param previousChild the child right before it or null if none
+     * @param visibleIndex    the visible index of this view in the list
+     * @param child           the child asked about
+     * @param previousChild   the child right before it or null if none
      * @return if the child needs a gap height
      */
     private boolean childNeedsGapHeight(
@@ -672,7 +683,7 @@
     }
 
     private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                     AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
             View child = algorithmState.visibleChildren.get(i);
@@ -689,7 +700,7 @@
     }
 
     private void updateHeadsUpStates(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                     AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
 
         // Move the tracked heads up into position during the appear animation, by interpolating
@@ -773,7 +784,7 @@
      */
     @VisibleForTesting
     void clampHunToTop(float quickQsOffsetHeight, float stackTranslation, float collapsedHeight,
-            ExpandableViewState viewState) {
+                       ExpandableViewState viewState) {
 
         final float newTranslation = Math.max(quickQsOffsetHeight + stackTranslation,
                 viewState.getYTranslation());
@@ -788,7 +799,7 @@
     // Pin HUN to bottom of expanded QS
     // while the rest of notifications are scrolled offscreen.
     private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
-            ExpandableViewState childState) {
+                                          ExpandableViewState childState) {
         float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
         final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
                 + ambientState.getStackTranslation();
@@ -803,14 +814,19 @@
         // Animate pinned HUN bottom corners to and from original roundness.
         final float originalCornerRadius =
                 row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius);
-        final float roundness = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
+        final float bottomValue = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
                 ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius);
-        row.requestBottomRoundness(roundness, /* animate = */ false, SourceType.OnScroll);
+        if (mUseRoundnessSourceTypes) {
+            row.requestBottomRoundness(bottomValue, STACK_SCROLL_ALGO);
+            row.addOnDetachResetRoundness(STACK_SCROLL_ALGO);
+        } else {
+            row.requestBottomRoundness(bottomValue, LegacySourceType.OnScroll);
+        }
     }
 
     @VisibleForTesting
     float computeCornerRoundnessForPinnedHun(float hostViewHeight, float stackY,
-            float viewMaxHeight, float originalCornerRadius) {
+                                             float viewMaxHeight, float originalCornerRadius) {
 
         // Compute y where corner roundness should be in its original unpinned state.
         // We use view max height because the pinned collapsed HUN expands to max height
@@ -840,7 +856,7 @@
      * @param ambientState   The ambient state of the algorithm
      */
     private void updateZValuesForState(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                       AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         float childrenOnTop = 0.0f;
 
@@ -860,30 +876,53 @@
         }
     }
 
+    /**
+     * Calculate and update the Z positions for a given child. We currently only give shadows to
+     * HUNs to distinguish a HUN from its surroundings.
+     *
+     * @param isTopHun      Whether the child is a top HUN. A top HUN means a HUN that shows on the
+     *                      vertically top of screen. Top HUNs should have drop shadows
+     * @param childrenOnTop It is greater than 0 when there's an existing HUN that is elevated
+     * @return childrenOnTop The decimal part represents the fraction of the elevated HUN's height
+     *                      that overlaps with QQS Panel. The integer part represents the count of
+     *                      previous HUNs whose Z positions are greater than 0.
+     */
     protected float updateChildZValue(int i, float childrenOnTop,
-            StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState,
-            boolean shouldElevateHun) {
+                                      StackScrollAlgorithmState algorithmState,
+                                      AmbientState ambientState,
+                                      boolean isTopHun) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
         ExpandableViewState childViewState = child.getViewState();
-        int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
         float baseZ = ambientState.getBaseZHeight();
+
+        // Handles HUN shadow when Shade is opened
+
         if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
                 && !ambientState.isDozingAndNotPulsing(child)
                 && childViewState.getYTranslation() < ambientState.getTopPadding()
                 + ambientState.getStackTranslation()) {
+            // Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
+            // Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
+            // When scrolling down shade to make HUN back to in-position in Notification Panel,
+            // The over-lapping fraction goes to 0, and shadows hides gradually.
             if (childrenOnTop != 0.0f) {
+                // To elevate the later HUN over previous HUN
                 childrenOnTop++;
             } else {
                 float overlap = ambientState.getTopPadding()
                         + ambientState.getStackTranslation() - childViewState.getYTranslation();
-                childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
+                // To prevent over-shadow during HUN entry
+                childrenOnTop += Math.min(
+                        1.0f,
+                        overlap / childViewState.height
+                );
+                MathUtils.saturate(childrenOnTop);
             }
             childViewState.setZTranslation(baseZ
-                    + childrenOnTop * zDistanceBetweenElements);
-        } else if (shouldElevateHun) {
+                    + childrenOnTop * mPinnedZTranslationExtra);
+        } else if (isTopHun) {
             // In case this is a new view that has never been measured before, we don't want to
-            // elevate if we are currently expanded more then the notification
+            // elevate if we are currently expanded more than the notification
             int shelfHeight = ambientState.getShelf() == null ? 0 :
                     ambientState.getShelf().getIntrinsicHeight();
             float shelfStart = ambientState.getInnerHeight()
@@ -892,23 +931,28 @@
             float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight()
                     + mPaddingBetweenElements;
             if (shelfStart > notificationEnd) {
+                // When the notification doesn't overlap with Notification Shelf, there's no shadow
                 childViewState.setZTranslation(baseZ);
             } else {
+                // Give shadow to the notification if it overlaps with Notification Shelf
                 float factor = (notificationEnd - shelfStart) / shelfHeight;
                 if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
                     factor = 1.0f;
                 }
                 factor = Math.min(factor, 1.0f);
-                childViewState.setZTranslation(baseZ + factor * zDistanceBetweenElements);
+                childViewState.setZTranslation(baseZ + factor * mPinnedZTranslationExtra);
             }
         } else {
             childViewState.setZTranslation(baseZ);
         }
 
-        // We need to scrim the notification more from its surrounding content when we are pinned,
-        // and we therefore elevate it higher.
-        // We can use the headerVisibleAmount for this, since the value nicely goes from 0 to 1 when
-        // expanding after which we have a normal elevation again.
+        // Handles HUN shadow when shade is closed.
+        // While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays.
+        // During HUN-to-Shade (eg. dragging down HUN to open Shade): headerVisibleAmount goes
+        // gradually from 0 to 1, shadow hides gradually.
+        // Header visibility is a deprecated concept, we are using headerVisibleAmount only because
+        // this value nicely goes from 0 to 1 during the HUN-to-Shade process.
+
         childViewState.setZTranslation(childViewState.getZTranslation()
                 + (1.0f - child.getHeaderVisibleAmount()) * mPinnedZTranslationExtra);
         return childrenOnTop;
@@ -918,6 +962,14 @@
         this.mIsExpanded = isExpanded;
     }
 
+    /**
+     * Enable the support for rounded corner based on the SourceType
+     * @param enabled true if is supported
+     */
+    public void useRoundnessSourceTypes(boolean enabled) {
+        mUseRoundnessSourceTypes = enabled;
+    }
+
     public static class StackScrollAlgorithmState {
 
         /**
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..03057a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -29,7 +29,6 @@
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.util.Log;
 
 import androidx.annotation.Nullable;
 
@@ -41,10 +40,10 @@
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.LatencyTracker;
-import com.android.keyguard.KeyguardConstants;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.KeyguardViewController;
+import com.android.keyguard.logging.BiometricUnlockLogger;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.biometrics.AuthController;
@@ -64,8 +63,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;
 
@@ -74,9 +75,6 @@
  */
 @SysUISingleton
 public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable {
-
-    private static final String TAG = "BiometricUnlockCtrl";
-    private static final boolean DEBUG_BIO_WAKELOCK = KeyguardConstants.DEBUG_BIOMETRIC_WAKELOCK;
     private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
     private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
     private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
@@ -163,13 +161,14 @@
     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;
     private final StatusBarStateController mStatusBarStateController;
     private final LatencyTracker mLatencyTracker;
     private final VibratorHelper mVibratorHelper;
+    private final BiometricUnlockLogger mLogger;
 
     private long mLastFpFailureUptimeMillis;
     private int mNumConsecutiveFpFailures;
@@ -255,7 +254,8 @@
     private final ScreenOffAnimationController mScreenOffAnimationController;
 
     @Inject
-    public BiometricUnlockController(DozeScrimController dozeScrimController,
+    public BiometricUnlockController(
+            DozeScrimController dozeScrimController,
             KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
             NotificationShadeWindowController notificationShadeWindowController,
             KeyguardStateController keyguardStateController, Handler handler,
@@ -264,6 +264,7 @@
             KeyguardBypassController keyguardBypassController,
             MetricsLogger metricsLogger, DumpManager dumpManager,
             PowerManager powerManager,
+            BiometricUnlockLogger biometricUnlockLogger,
             NotificationMediaManager notificationMediaManager,
             WakefulnessLifecycle wakefulnessLifecycle,
             ScreenLifecycle screenLifecycle,
@@ -297,6 +298,7 @@
         mSessionTracker = sessionTracker;
         mScreenOffAnimationController = screenOffAnimationController;
         mVibratorHelper = vibrator;
+        mLogger = biometricUnlockLogger;
 
         dumpManager.registerDumpable(getClass().getName(), this);
     }
@@ -305,17 +307,20 @@
         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() {
         @Override
         public void run() {
-            if (DEBUG_BIO_WAKELOCK) {
-                Log.i(TAG, "biometric wakelock: TIMEOUT!!");
-            }
+            mLogger.i("biometric wakelock: TIMEOUT!!");
             releaseBiometricWakeLock();
         }
     };
@@ -323,9 +328,7 @@
     private void releaseBiometricWakeLock() {
         if (mWakeLock != null) {
             mHandler.removeCallbacks(mReleaseBiometricWakeLockRunnable);
-            if (DEBUG_BIO_WAKELOCK) {
-                Log.i(TAG, "releasing biometric wakelock");
-            }
+            mLogger.i("releasing biometric wakelock");
             mWakeLock.release();
             mWakeLock = null;
         }
@@ -356,9 +359,7 @@
             Trace.beginSection("acquiring wake-and-unlock");
             mWakeLock.acquire();
             Trace.endSection();
-            if (DEBUG_BIO_WAKELOCK) {
-                Log.i(TAG, "biometric acquired, grabbing biometric wakelock");
-            }
+            mLogger.i("biometric acquired, grabbing biometric wakelock");
             mHandler.postDelayed(mReleaseBiometricWakeLockRunnable,
                     BIOMETRIC_WAKELOCK_TIMEOUT_MS);
         }
@@ -395,7 +396,7 @@
             mKeyguardViewMediator.userActivity();
             startWakeAndUnlock(biometricSourceType, isStrongBiometric);
         } else {
-            Log.d(TAG, "onBiometricAuthenticated aborted by bypass controller");
+            mLogger.d("onBiometricAuthenticated aborted by bypass controller");
         }
     }
 
@@ -411,7 +412,7 @@
     }
 
     public void startWakeAndUnlock(@WakeAndUnlockMode int mode) {
-        Log.v(TAG, "startWakeAndUnlock(" + mode + ")");
+        mLogger.logStartWakeAndUnlock(mode);
         boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive();
         mMode = mode;
         mHasScreenTurnedOnSinceAuthenticating = false;
@@ -426,9 +427,7 @@
         // brightness changes due to display state transitions.
         Runnable wakeUp = ()-> {
             if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
-                if (DEBUG_BIO_WAKELOCK) {
-                    Log.i(TAG, "bio wakelock: Authenticated, waking up...");
-                }
+                mLogger.i("bio wakelock: Authenticated, waking up...");
                 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                         "android.policy:BIOMETRIC");
             }
@@ -455,7 +454,7 @@
                 break;
             case MODE_SHOW_BOUNCER:
                 Trace.beginSection("MODE_SHOW_BOUNCER");
-                mKeyguardViewController.showBouncer(true);
+                mKeyguardViewController.showPrimaryBouncer(true);
                 Trace.endSection();
                 break;
             case MODE_WAKE_AND_UNLOCK_FROM_DREAM:
@@ -481,15 +480,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);
         }
     }
 
@@ -500,7 +496,7 @@
                 && mPendingAuthenticated.userId == KeyguardUpdateMonitor.getCurrentUser();
     }
 
-    public int getMode() {
+    public @WakeAndUnlockMode int getMode() {
         return mMode;
     }
 
@@ -515,13 +511,16 @@
     }
 
     private @WakeAndUnlockMode int calculateModeForFingerprint(boolean isStrongBiometric) {
-        boolean unlockingAllowed =
+        final boolean unlockingAllowed =
                 mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric);
-        boolean deviceDreaming = mUpdateMonitor.isDreaming();
+        final boolean deviceInteractive = mUpdateMonitor.isDeviceInteractive();
+        final boolean keyguardShowing = mKeyguardStateController.isShowing();
+        final boolean deviceDreaming = mUpdateMonitor.isDreaming();
 
-        if (!mUpdateMonitor.isDeviceInteractive()) {
-            if (!mKeyguardStateController.isShowing()
-                    && !mScreenOffAnimationController.isKeyguardShowDelayed()) {
+        logCalculateModeForFingerprint(unlockingAllowed, deviceInteractive,
+                keyguardShowing, deviceDreaming, isStrongBiometric);
+        if (!deviceInteractive) {
+            if (!keyguardShowing && !mScreenOffAnimationController.isKeyguardShowDelayed()) {
                 if (mKeyguardStateController.isUnlocked()) {
                     return MODE_WAKE_AND_UNLOCK;
                 }
@@ -537,8 +536,8 @@
         if (unlockingAllowed && deviceDreaming) {
             return MODE_WAKE_AND_UNLOCK_FROM_DREAM;
         }
-        if (mKeyguardStateController.isShowing()) {
-            if (mKeyguardViewController.bouncerIsOrWillBeShowing() && unlockingAllowed) {
+        if (keyguardShowing) {
+            if (mKeyguardViewController.primaryBouncerIsOrWillBeShowing() && unlockingAllowed) {
                 return MODE_DISMISS_BOUNCER;
             } else if (unlockingAllowed) {
                 return MODE_UNLOCK_COLLAPSING;
@@ -549,14 +548,39 @@
         return MODE_NONE;
     }
 
+    private void logCalculateModeForFingerprint(boolean unlockingAllowed, boolean deviceInteractive,
+            boolean keyguardShowing, boolean deviceDreaming, boolean strongBiometric) {
+        if (unlockingAllowed) {
+            mLogger.logCalculateModeForFingerprintUnlockingAllowed(deviceInteractive,
+                    keyguardShowing, deviceDreaming);
+        } else {
+            // if unlocking isn't allowed, log more information about why unlocking may not
+            // have been allowed
+            final int strongAuthFlags = mUpdateMonitor.getStrongAuthTracker().getStrongAuthForUser(
+                    KeyguardUpdateMonitor.getCurrentUser());
+            final boolean nonStrongBiometricAllowed =
+                    mUpdateMonitor.getStrongAuthTracker()
+                            .isNonStrongBiometricAllowedAfterIdleTimeout(
+                                    KeyguardUpdateMonitor.getCurrentUser());
+
+            mLogger.logCalculateModeForFingerprintUnlockingNotAllowed(strongBiometric,
+                    strongAuthFlags, nonStrongBiometricAllowed, deviceInteractive, keyguardShowing);
+        }
+    }
+
     private @WakeAndUnlockMode int calculateModeForPassiveAuth(boolean isStrongBiometric) {
-        boolean unlockingAllowed =
+        final boolean deviceInteractive = mUpdateMonitor.isDeviceInteractive();
+        final boolean isKeyguardShowing = mKeyguardStateController.isShowing();
+        final boolean unlockingAllowed =
                 mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric);
-        boolean deviceDreaming = mUpdateMonitor.isDreaming();
-        boolean bypass = mKeyguardBypassController.getBypassEnabled()
+        final boolean deviceDreaming = mUpdateMonitor.isDreaming();
+        final boolean bypass = mKeyguardBypassController.getBypassEnabled()
                 || mAuthController.isUdfpsFingerDown();
-        if (!mUpdateMonitor.isDeviceInteractive()) {
-            if (!mKeyguardStateController.isShowing()) {
+
+        logCalculateModeForPassiveAuth(unlockingAllowed, deviceInteractive, isKeyguardShowing,
+                deviceDreaming, bypass, isStrongBiometric);
+        if (!deviceInteractive) {
+            if (!isKeyguardShowing) {
                 return bypass ? MODE_WAKE_AND_UNLOCK : MODE_ONLY_WAKE;
             } else if (!unlockingAllowed) {
                 return bypass ? MODE_SHOW_BOUNCER : MODE_NONE;
@@ -580,11 +604,11 @@
         if (unlockingAllowed && mKeyguardStateController.isOccluded()) {
             return MODE_UNLOCK_COLLAPSING;
         }
-        if (mKeyguardStateController.isShowing()) {
-            if ((mKeyguardViewController.bouncerIsOrWillBeShowing()
+        if (isKeyguardShowing) {
+            if ((mKeyguardViewController.primaryBouncerIsOrWillBeShowing()
                     || mKeyguardBypassController.getAltBouncerShowing()) && unlockingAllowed) {
                 return MODE_DISMISS_BOUNCER;
-            } else if (unlockingAllowed && (bypass || mAuthController.isUdfpsFingerDown())) {
+            } else if (unlockingAllowed && bypass) {
                 return MODE_UNLOCK_COLLAPSING;
             } else {
                 return bypass ? MODE_SHOW_BOUNCER : MODE_NONE;
@@ -593,6 +617,28 @@
         return MODE_NONE;
     }
 
+    private void logCalculateModeForPassiveAuth(boolean unlockingAllowed,
+            boolean deviceInteractive, boolean keyguardShowing, boolean deviceDreaming,
+            boolean bypass, boolean strongBiometric) {
+        if (unlockingAllowed) {
+            mLogger.logCalculateModeForPassiveAuthUnlockingAllowed(
+                    deviceInteractive, keyguardShowing, deviceDreaming, bypass);
+        } else {
+            // if unlocking isn't allowed, log more information about why unlocking may not
+            // have been allowed
+            final int strongAuthFlags = mUpdateMonitor.getStrongAuthTracker().getStrongAuthForUser(
+                    KeyguardUpdateMonitor.getCurrentUser());
+            final boolean nonStrongBiometricAllowed =
+                    mUpdateMonitor.getStrongAuthTracker()
+                            .isNonStrongBiometricAllowedAfterIdleTimeout(
+                                    KeyguardUpdateMonitor.getCurrentUser());
+
+            mLogger.logCalculateModeForPassiveAuthUnlockingNotAllowed(
+                    strongBiometric, strongAuthFlags, nonStrongBiometricAllowed,
+                    deviceInteractive, keyguardShowing, bypass);
+        }
+    }
+
     @Override
     public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
         mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
@@ -610,6 +656,7 @@
 
         if (!mVibratorHelper.hasVibrator()
                 && (!mUpdateMonitor.isDeviceInteractive() || mUpdateMonitor.isDreaming())) {
+            mLogger.d("wakeup device on authentication failure (device doesn't have a vibrator)");
             startWakeAndUnlock(MODE_ONLY_WAKE);
         } else if (biometricSourceType == BiometricSourceType.FINGERPRINT
                 && mUpdateMonitor.isUdfpsSupported()) {
@@ -622,6 +669,7 @@
             mLastFpFailureUptimeMillis = currUptimeMillis;
 
             if (mNumConsecutiveFpFailures >= UDFPS_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
+                mLogger.logUdfpsAttemptThresholdMet(mNumConsecutiveFpFailures);
                 startWakeAndUnlock(MODE_SHOW_BOUNCER);
                 UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
                 mNumConsecutiveFpFailures = 0;
@@ -652,6 +700,7 @@
                 && (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
                 || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT);
         if (fingerprintLockout) {
+            mLogger.d("fingerprint locked out");
             startWakeAndUnlock(MODE_SHOW_BOUNCER);
             UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
         }
@@ -696,9 +745,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;
@@ -742,6 +790,15 @@
     }
 
     @Override
+    public void onKeyguardBouncerStateChanged(boolean bouncerIsOrWillBeShowing) {
+        // When the bouncer is dismissed, treat this as a reset of the unlock mode. The user
+        // may have gone back instead of successfully unlocking
+        if (!bouncerIsOrWillBeShowing) {
+            resetMode();
+        }
+    }
+
+    @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println(" BiometricUnlockController:");
         pw.print("   mMode="); pw.println(mMode);
@@ -807,10 +864,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 2504fc1..c7c6441 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -28,7 +28,6 @@
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.view.KeyEvent;
-import android.view.MotionEvent;
 import android.view.RemoteAnimationAdapter;
 import android.view.View;
 import android.view.ViewGroup;
@@ -51,10 +50,8 @@
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
-import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.LightRevealScrim;
 import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 
 import java.io.PrintWriter;
 
@@ -123,6 +120,7 @@
         ActivityOptions options = getDefaultActivityOptions(animationAdapter);
         options.setLaunchDisplayId(displayId);
         options.setCallerDisplayId(displayId);
+        options.setPendingIntentBackgroundActivityLaunchAllowed(true);
         return options.toBundle();
     }
 
@@ -146,6 +144,7 @@
                 : ActivityOptions.SourceInfo.TYPE_NOTIFICATION, eventTime);
         options.setLaunchDisplayId(displayId);
         options.setCallerDisplayId(displayId);
+        options.setPendingIntentBackgroundActivityLaunchAllowed(true);
         return options.toBundle();
     }
 
@@ -192,12 +191,8 @@
 
     void animateExpandSettingsPanel(@Nullable String subpanel);
 
-    void animateCollapsePanels(int flags, boolean force);
-
     void collapsePanelOnMainThread();
 
-    void collapsePanelWithDuration(int duration);
-
     void togglePanel();
 
     void start();
@@ -256,12 +251,8 @@
 
     boolean isWakeUpComingFromTouch();
 
-    boolean isFalsingThresholdNeeded();
-
     void onKeyguardViewManagerStatesUpdated();
 
-    void setPanelExpanded(boolean isExpanded);
-
     ViewGroup getNotificationScrollLayout();
 
     boolean isPulsing();
@@ -287,17 +278,17 @@
 
     void postAnimateOpenPanels();
 
-    boolean isExpandedVisible();
-
     boolean isPanelExpanded();
 
     void onInputFocusTransfer(boolean start, boolean cancel, float velocity);
 
     void animateCollapseQuickSettings();
 
-    void onTouchEvent(MotionEvent event);
+    /** */
+    boolean getCommandQueuePanelsEnabled();
 
-    GestureRecorder getGestureRecorder();
+    /** */
+    int getStatusBarWindowState();
 
     BiometricUnlockController getBiometricUnlockController();
 
@@ -305,9 +296,6 @@
 
     void checkBarModes();
 
-    // Called by NavigationBarFragment
-    void setQsScrimEnabled(boolean scrimEnabled);
-
     void updateBubblesVisibility();
 
     void setInteracting(int barWindow, boolean interacting);
@@ -379,8 +367,6 @@
 
     void showKeyguardImpl();
 
-    boolean isInLaunchTransition();
-
     void fadeKeyguardAfterLaunchTransition(Runnable beforeFading,
             Runnable endRunnable, Runnable cancelRunnable);
 
@@ -418,16 +404,6 @@
 
     LightRevealScrim getLightRevealScrim();
 
-    void onTrackingStarted();
-
-    void onClosingFinished();
-
-    void onUnlockHintStarted();
-
-    void onHintFinished();
-
-    void onTrackingStopped(boolean expand);
-
     // TODO: Figure out way to remove these.
     NavigationBarView getNavigationBarView();
 
@@ -437,8 +413,6 @@
 
     void showPinningEscapeToast();
 
-    KeyguardBottomAreaView getKeyguardBottomAreaView();
-
     void setBouncerShowing(boolean bouncerShowing);
 
     void setBouncerShowingOverDream(boolean bouncerShowingOverDream);
@@ -465,7 +439,11 @@
 
     void setTransitionToFullShadeProgress(float transitionToFullShadeProgress);
 
-    void setBouncerHiddenFraction(float expansion);
+    /**
+     * Sets the amount of progress to the bouncer being fully hidden/visible. 1 means the bouncer
+     * is fully hidden, while 0 means the bouncer is visible.
+     */
+    void setPrimaryBouncerHiddenFraction(float expansion);
 
     @VisibleForTesting
     void updateScrimController();
@@ -505,22 +483,10 @@
 
     boolean isBouncerShowingOverDream();
 
-    void onBouncerPreHideAnimation();
-
     boolean isKeyguardSecure();
 
-    NotificationPanelViewController getPanelController();
-
-    NotificationGutsManager getGutsManager();
-
     void updateNotificationPanelTouchState();
 
-    void makeExpandedVisible(boolean force);
-
-    void instantCollapseNotificationPanel();
-
-    void visibilityChanged(boolean visible);
-
     int getDisplayId();
 
     int getRotation();
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..6b72e96 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;
 
@@ -55,6 +55,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
@@ -71,6 +72,8 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 /** */
 @CentralSurfacesComponent.CentralSurfacesScope
 public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callbacks {
@@ -99,6 +102,7 @@
     private final boolean mVibrateOnOpening;
     private final VibrationEffect mCameraLaunchGestureVibrationEffect;
     private final SystemBarAttributesListener mSystemBarAttributesListener;
+    private final Lazy<CameraLauncher> mCameraLauncherLazy;
 
     private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@@ -128,8 +132,8 @@
             Optional<Vibrator> vibratorOptional,
             DisableFlagsLogger disableFlagsLogger,
             @DisplayId int displayId,
-            SystemBarAttributesListener systemBarAttributesListener) {
-
+            SystemBarAttributesListener systemBarAttributesListener,
+            Lazy<CameraLauncher> cameraLauncherLazy) {
         mCentralSurfaces = centralSurfaces;
         mContext = context;
         mShadeController = shadeController;
@@ -152,6 +156,7 @@
         mVibratorOptional = vibratorOptional;
         mDisableFlagsLogger = disableFlagsLogger;
         mDisplayId = displayId;
+        mCameraLauncherLazy = cameraLauncherLazy;
 
         mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
         mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
@@ -204,7 +209,7 @@
     public void animateExpandNotificationsPanel() {
         if (CentralSurfaces.SPEW) {
             Log.d(CentralSurfaces.TAG,
-                    "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible());
+                    "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
         }
         if (!mCommandQueue.panelsEnabled()) {
             return;
@@ -217,7 +222,7 @@
     public void animateExpandSettingsPanel(@Nullable String subPanel) {
         if (CentralSurfaces.SPEW) {
             Log.d(CentralSurfaces.TAG,
-                    "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible());
+                    "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
         }
         if (!mCommandQueue.panelsEnabled()) {
             return;
@@ -271,7 +276,7 @@
 
         if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
             if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
-                mShadeController.animateCollapsePanels();
+                mShadeController.animateCollapseShade();
             }
         }
 
@@ -288,7 +293,7 @@
         if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
             mCentralSurfaces.updateQsExpansionEnabled();
             if ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
-                mShadeController.animateCollapsePanels();
+                mShadeController.animateCollapseShade();
             }
         }
 
@@ -346,7 +351,8 @@
             mCentralSurfaces.setLaunchCameraOnFinishedGoingToSleep(true);
             return;
         }
-        if (!mNotificationPanelViewController.canCameraGestureBeLaunched()) {
+        if (!mCameraLauncherLazy.get().canCameraGestureBeLaunched(
+                mNotificationPanelViewController.getBarState())) {
             if (CentralSurfaces.DEBUG_CAMERA_LIFT) {
                 Slog.d(CentralSurfaces.TAG, "Can't launch camera right now");
             }
@@ -383,7 +389,8 @@
                 if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
                     mStatusBarKeyguardViewManager.reset(true /* hide */);
                 }
-                mNotificationPanelViewController.launchCamera(source);
+                mCameraLauncherLazy.get().launchCamera(source,
+                        mNotificationPanelViewController.isFullyCollapsed());
                 mCentralSurfaces.updateScrimController();
             } else {
                 // We need to defer the camera launch until the screen comes on, since otherwise
@@ -458,7 +465,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 +478,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails
         );
@@ -543,7 +550,7 @@
     @Override
     public void togglePanel() {
         if (mCentralSurfaces.isPanelExpanded()) {
-            mShadeController.animateCollapsePanels();
+            mShadeController.animateCollapseShade();
         } else {
             animateExpandNotificationsPanel();
         }
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 d227ed3..b394535 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -58,7 +58,6 @@
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
-import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -94,7 +93,6 @@
 import android.view.IRemoteAnimationRunner;
 import android.view.IWindowManager;
 import android.view.KeyEvent;
-import android.view.MotionEvent;
 import android.view.ThreadedRenderer;
 import android.view.View;
 import android.view.ViewGroup;
@@ -159,6 +157,8 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.plugins.DarkIconDispatcher;
@@ -166,22 +166,22 @@
 import com.android.systemui.plugins.OverlayPlugin;
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.recents.ScreenPinningRequest;
-import com.android.systemui.ripple.RippleShader.RippleShape;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CircleReveal;
@@ -231,6 +231,7 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.WallpaperController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -240,6 +241,8 @@
 import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
 import com.android.wm.shell.startingsurface.StartingSurface;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.List;
@@ -250,8 +253,6 @@
 import javax.inject.Inject;
 import javax.inject.Named;
 
-import dagger.Lazy;
-
 /**
  * A class handling initialization and coordination between some of the key central surfaces in
  * System UI: The notification shade, the keyguard (lockscreen), and the status bar.
@@ -278,6 +279,7 @@
     // 1020-1040 reserved for BaseStatusBar
 
     /**
+     * TODO(b/249277686) delete this
      * The delay to reset the hint text when the hint animation is finished running.
      */
     private static final int HINT_RESET_DELAY_MS = 1200;
@@ -404,12 +406,6 @@
 
     /** */
     @Override
-    public void animateCollapsePanels(int flags, boolean force) {
-        mCommandQueueCallbacks.animateCollapsePanels(flags, force);
-    }
-
-    /** */
-    @Override
     public void togglePanel() {
         mCommandQueueCallbacks.togglePanel();
     }
@@ -466,8 +462,9 @@
     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 Lazy<CameraLauncher> mCameraLauncherLazy;
 
     private final PluginDependencyProvider mPluginDependencyProvider;
     private final KeyguardDismissUtil mKeyguardDismissUtil;
@@ -478,10 +475,11 @@
     private final OngoingCallController mOngoingCallController;
     private final StatusBarSignalPolicy mStatusBarSignalPolicy;
     private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
+    private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy;
 
-    // expanded notifications
-    // the sliding/resizing panel within the notification window
-    protected NotificationPanelViewController mNotificationPanelViewController;
+    /** Controller for the Shade. */
+    @VisibleForTesting
+    NotificationPanelViewController mNotificationPanelViewController;
 
     // settings
     private QSPanelController mQSPanelController;
@@ -490,8 +488,6 @@
 
     private View mReportRejectedTouch;
 
-    private boolean mExpandedVisible;
-
     private final NotificationGutsManager mGutsManager;
     private final NotificationLogger mNotificationLogger;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
@@ -600,6 +596,7 @@
 
     private Runnable mLaunchTransitionEndRunnable;
     private Runnable mLaunchTransitionCancelRunnable;
+    private boolean mLaunchingAffordance;
     private boolean mLaunchCameraWhenFinishedWaking;
     private boolean mLaunchCameraOnFinishedGoingToSleep;
     private boolean mLaunchEmergencyActionWhenFinishedWaking;
@@ -744,7 +741,9 @@
             InteractionJankMonitor jankMonitor,
             DeviceStateManager deviceStateManager,
             WiredChargingRippleController wiredChargingRippleController,
-            IDreamManager dreamManager) {
+            IDreamManager dreamManager,
+            Lazy<CameraLauncher> cameraLauncherLazy,
+            Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy) {
         mContext = context;
         mNotificationsController = notificationsController;
         mFragmentService = fragmentService;
@@ -821,6 +820,7 @@
         mMessageRouter = messageRouter;
         mWallpaperManager = wallpaperManager;
         mJankMonitor = jankMonitor;
+        mCameraLauncherLazy = cameraLauncherLazy;
 
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
         mStartingSurfaceOptional = startingSurfaceOptional;
@@ -831,6 +831,7 @@
         mScreenOffAnimationController = screenOffAnimationController;
 
         mShadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged);
+        mShadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
 
         mBubbleExpandListener = (isExpanding, key) ->
                 mContext.getMainExecutor().execute(this::updateScrimController);
@@ -856,6 +857,8 @@
         deviceStateManager.registerCallback(mMainExecutor,
                 new FoldStateListener(mContext, this::onFoldedStateChanged));
         wiredChargingRippleController.registerCallbacks();
+
+        mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy;
     }
 
     @Override
@@ -886,6 +889,8 @@
         updateDisplaySize();
         mStatusBarHideIconsForBouncerManager.setDisplayId(mDisplayId);
 
+        initShadeVisibilityListener();
+
         // start old BaseStatusBar.start().
         mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
         mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
@@ -928,7 +933,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,
@@ -970,6 +975,11 @@
         // Lastly, call to the icon policy to install/update all the icons.
         mIconPolicy.init();
 
+        // Based on teamfood flag, turn predictive back dispatch on at runtime.
+        if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
+            mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
+        }
+
         mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onUnlockedChanged() {
@@ -978,6 +988,12 @@
 
             @Override
             public void onKeyguardGoingAwayChanged() {
+                if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+                    // This code path is not used if the KeyguardTransitionRepository is managing
+                    // the lightreveal scrim.
+                    return;
+                }
+
                 // The light reveal scrim should always be fully revealed by the time the keyguard
                 // is done going away. Double check that this is true.
                 if (!mKeyguardStateController.isKeyguardGoingAway()) {
@@ -1071,6 +1087,25 @@
                                 requestTopUi, componentTag))));
     }
 
+    @VisibleForTesting
+    void initShadeVisibilityListener() {
+        mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
+            @Override
+            public void visibilityChanged(boolean visible) {
+                onShadeVisibilityChanged(visible);
+            }
+
+            @Override
+            public void expandedVisibleChanged(boolean expandedVisible) {
+                if (expandedVisible) {
+                    setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
+                } else {
+                    onExpandedInvisible();
+                }
+            }
+        });
+    }
+
     private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) {
         Trace.beginSection("CentralSurfaces#onFoldedStateChanged");
         onFoldedStateChangedInternal(isFolded, willGoToSleep);
@@ -1125,7 +1160,6 @@
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         mNotificationIconAreaController.setupShelf(mNotificationShelfController);
         mShadeExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
-        mUserSwitcherController.init(mNotificationShadeWindowView);
 
         // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
         mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
@@ -1152,7 +1186,6 @@
         initializer.initializeStatusBar(mCentralSurfacesComponent);
 
         mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
-        mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener());
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
 
         createNavigationBar(result);
@@ -1161,9 +1194,6 @@
             mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
         }
 
-        mNotificationPanelViewController.setKeyguardIndicationController(
-                mKeyguardIndicationController);
-
         mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById(
                 R.id.ambient_indication_container);
 
@@ -1200,6 +1230,12 @@
         mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront);
 
         mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
+
+        if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+            LightRevealScrimViewBinder.bind(
+                    mLightRevealScrim, mLightRevealScrimViewModelLazy.get());
+        }
+
         mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> {
             Runnable updateOpaqueness = () -> {
                 mNotificationShadeWindowController.setLightRevealScrimOpaque(
@@ -1221,7 +1257,8 @@
 
         mNotificationPanelViewController.initDependencies(
                 this,
-                this::makeExpandedInvisible,
+                mGestureRec,
+                mShadeController::makeExpandedInvisible,
                 mNotificationShelfController);
 
         BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
@@ -1363,6 +1400,7 @@
     private void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
         float fraction = event.getFraction();
         boolean tracking = event.getTracking();
+        boolean isExpanded = event.getExpanded();
         dispatchPanelExpansionForKeyguardDismiss(fraction, tracking);
 
         if (fraction == 0 || fraction == 1) {
@@ -1375,6 +1413,23 @@
         }
     }
 
+    @VisibleForTesting
+    void onShadeExpansionFullyChanged(Boolean isExpanded) {
+        if (mPanelExpanded != isExpanded) {
+            mPanelExpanded = isExpanded;
+            if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
+                if (DEBUG) {
+                    Log.v(TAG, "clearing notification effects from Height");
+                }
+                clearNotificationEffects();
+            }
+
+            if (!isExpanded) {
+                mRemoteInputManager.onPanelCollapsed();
+            }
+        }
+    }
+
     @NonNull
     @Override
     public Lifecycle getLifecycle() {
@@ -1406,6 +1461,7 @@
         mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController);
         mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
+        mShadeController.setNotificationPresenter(mPresenter);
         mNotificationsController.initialize(
                 this,
                 mPresenter,
@@ -1455,11 +1511,7 @@
         return (v, event) -> {
             mAutoHideController.checkUserAutoHide(event);
             mRemoteInputManager.checkRemoteInputOutside(event);
-            if (event.getAction() == MotionEvent.ACTION_UP) {
-                if (mExpandedVisible) {
-                    mShadeController.animateCollapsePanels();
-                }
-            }
+            mShadeController.onStatusBarTouch(event);
             return mNotificationShadeWindowView.onTouchEvent(event);
         };
     }
@@ -1481,6 +1533,9 @@
         mNotificationShadeWindowViewController.setupExpandedStatusBar();
         mNotificationPanelViewController =
                 mCentralSurfacesComponent.getNotificationPanelViewController();
+        mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+        mShadeController.setNotificationShadeWindowViewController(
+                mNotificationShadeWindowViewController);
         mCentralSurfacesComponent.getLockIconViewController().init();
         mStackScrollerController =
                 mCentralSurfacesComponent.getNotificationStackScrollLayoutController();
@@ -1514,11 +1569,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
@@ -1529,11 +1585,7 @@
                             case BiometricUnlockController.MODE_WAKE_AND_UNLOCK:
                                 setWakeAndUnlocking(true);
                         }
-                    }
-
-                    @Override
-                    public void notifyBiometricAuthModeChanged() {
-                        CentralSurfacesImpl.this.notifyBiometricAuthModeChanged();
+                        notifyBiometricAuthModeChanged();
                     }
 
                     private void setWakeAndUnlocking(boolean wakeAndUnlocking) {
@@ -1768,11 +1820,6 @@
         return mWakeUpComingFromTouch;
     }
 
-    @Override
-    public boolean isFalsingThresholdNeeded() {
-        return true;
-    }
-
     /**
      * To be called when there's a state change in StatusBarKeyguardViewManager.
      */
@@ -1782,27 +1829,6 @@
     }
 
     @Override
-    public void setPanelExpanded(boolean isExpanded) {
-        if (mPanelExpanded != isExpanded) {
-            mNotificationLogger.onPanelExpandedChanged(isExpanded);
-        }
-        mPanelExpanded = isExpanded;
-        mStatusBarHideIconsForBouncerManager.setPanelExpandedAndTriggerUpdate(isExpanded);
-        mNotificationShadeWindowController.setPanelExpanded(isExpanded);
-        mStatusBarStateController.setPanelExpanded(isExpanded);
-        if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
-            if (DEBUG) {
-                Log.v(TAG, "clearing notification effects from Height");
-            }
-            clearNotificationEffects();
-        }
-
-        if (!isExpanded) {
-            mRemoteInputManager.onPanelCollapsed();
-        }
-    }
-
-    @Override
     public ViewGroup getNotificationScrollLayout() {
         return mStackScroller;
     }
@@ -1829,9 +1855,9 @@
     public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
         if (mPresenter.isPresenterFullyCollapsed() && !mPresenter.isCollapsing()
                 && isLaunchForActivity) {
-            onClosingFinished();
+            mShadeController.onClosingFinished();
         } else {
-            mShadeController.collapsePanel(true /* animate */);
+            mShadeController.collapseShade(true /* animate */);
         }
     }
 
@@ -1839,10 +1865,10 @@
     @Override
     public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
         if (!mPresenter.isCollapsing()) {
-            onClosingFinished();
+            mShadeController.onClosingFinished();
         }
         if (launchIsFullScreen) {
-            instantCollapseNotificationPanel();
+            mShadeController.instantCollapseShade();
         }
     }
 
@@ -1932,33 +1958,13 @@
     }
 
     @Override
-    public void makeExpandedVisible(boolean force) {
-        if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
-        if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
-            return;
-        }
-
-        mExpandedVisible = true;
-
-        // Expand the window to encompass the full screen in anticipation of the drag.
-        // This is only possible to do atomically because the status bar is at the top of the screen!
-        mNotificationShadeWindowController.setPanelVisible(true);
-
-        visibilityChanged(true);
-        mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
-        setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
-    }
-
-    @Override
     public void postAnimateCollapsePanels() {
-        mMainExecutor.execute(mShadeController::animateCollapsePanels);
+        mMainExecutor.execute(mShadeController::animateCollapseShade);
     }
 
     @Override
     public void postAnimateForceCollapsePanels() {
-        mMainExecutor.execute(
-                () -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE,
-                true /* force */));
+        mMainExecutor.execute(mShadeController::animateCollapseShadeForced);
     }
 
     @Override
@@ -1967,11 +1973,6 @@
     }
 
     @Override
-    public boolean isExpandedVisible() {
-        return mExpandedVisible;
-    }
-
-    @Override
     public boolean isPanelExpanded() {
         return mPanelExpanded;
     }
@@ -2000,90 +2001,23 @@
         }
     }
 
-    void makeExpandedInvisible() {
-        if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible
-                + " mExpandedVisible=" + mExpandedVisible);
-
-        if (!mExpandedVisible || mNotificationShadeWindowView == null) {
-            return;
-        }
-
-        // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
-        mNotificationPanelViewController.collapsePanel(/*animate=*/ false, false /* delayed*/,
-                1.0f /* speedUpFactor */);
-
-        mNotificationPanelViewController.closeQs();
-
-        mExpandedVisible = false;
-        visibilityChanged(false);
-
-        // Update the visibility of notification shade and status bar window.
-        mNotificationShadeWindowController.setPanelVisible(false);
-        mStatusBarWindowController.setForceStatusBarVisible(false);
-
-        // Close any guts that might be visible
-        mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
-                true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
-
-        mShadeController.runPostCollapseRunnables();
+    private void onExpandedInvisible() {
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
         if (!mNotificationActivityStarter.isCollapsingToShowActivityOverLockscreen()) {
             showBouncerOrLockScreenIfKeyguard();
         } else if (DEBUG) {
             Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen");
         }
-        mCommandQueue.recomputeDisableFlags(
-                mDisplayId,
-                mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
-
-        // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
-        // the bouncer appear animation.
-        if (!mKeyguardStateController.isShowing()) {
-            WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
-        }
-    }
-
-    /** Called when a touch event occurred on {@link PhoneStatusBarView}. */
-    @Override
-    public void onTouchEvent(MotionEvent event) {
-        // TODO(b/202981994): Move this touch debugging to a central location. (Right now, it's
-        //   split between NotificationPanelViewController and here.)
-        if (DEBUG_GESTURES) {
-            if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
-                EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH,
-                        event.getActionMasked(), (int) event.getX(), (int) event.getY(),
-                        mDisabled1, mDisabled2);
-            }
-
-        }
-
-        if (SPEW) {
-            Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1="
-                    + mDisabled1 + " mDisabled2=" + mDisabled2);
-        } else if (CHATTY) {
-            if (event.getAction() != MotionEvent.ACTION_MOVE) {
-                Log.d(TAG, String.format(
-                            "panel: %s at (%f, %f) mDisabled1=0x%08x mDisabled2=0x%08x",
-                            MotionEvent.actionToString(event.getAction()),
-                            event.getRawX(), event.getRawY(), mDisabled1, mDisabled2));
-            }
-        }
-
-        if (DEBUG_GESTURES) {
-            mGestureRec.add(event);
-        }
-
-        if (mStatusBarWindowState == WINDOW_STATE_SHOWING) {
-            final boolean upOrCancel =
-                    event.getAction() == MotionEvent.ACTION_UP ||
-                    event.getAction() == MotionEvent.ACTION_CANCEL;
-            setInteracting(StatusBarManager.WINDOW_STATUS_BAR, !upOrCancel || mExpandedVisible);
-        }
     }
 
     @Override
-    public GestureRecorder getGestureRecorder() {
-        return mGestureRec;
+    public boolean getCommandQueuePanelsEnabled() {
+        return mCommandQueue.panelsEnabled();
+    }
+
+    @Override
+    public int getStatusBarWindowState() {
+        return mStatusBarWindowState;
     }
 
     @Override
@@ -2178,12 +2112,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() {
@@ -2232,7 +2160,7 @@
         IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
         synchronized (mQueueLock) {
             pw.println("Current Status Bar state:");
-            pw.println("  mExpandedVisible=" + mExpandedVisible);
+            pw.println("  mExpandedVisible=" + mShadeController.isExpandedVisible());
             pw.println("  mDisplayMetrics=" + mDisplayMetrics);
             pw.println("  mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller));
             pw.println("  mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller)
@@ -2261,13 +2189,6 @@
         }
 
         pw.println("  Panels: ");
-        if (mNotificationPanelViewController != null) {
-            pw.println("    mNotificationPanel="
-                    + mNotificationPanelViewController.getView() + " params="
-                    + mNotificationPanelViewController.getView().getLayoutParams().debug(""));
-            pw.print  ("      ");
-            mNotificationPanelViewController.dump(pw, args);
-        }
         pw.println("  mStackScroller: " + mStackScroller + " (dump moved)");
         pw.println("  Theme:");
         String nightMode = mUiModeManager == null ? "null" : mUiModeManager.getNightMode() + "";
@@ -2554,24 +2475,13 @@
                     }
                 }
                 if (dismissShade) {
-                    if (mExpandedVisible && !mBouncerShowing) {
-                        mShadeController.animateCollapsePanels(
-                                CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                                true /* force */, true /* delayed*/);
+                    if (mShadeController.isExpandedVisible() && !mBouncerShowing) {
+                        mShadeController.animateCollapseShadeDelayed();
                     } else {
-
                         // Do it after DismissAction has been processed to conserve the needed
                         // ordering.
                         mMainExecutor.execute(mShadeController::runPostCollapseRunnables);
                     }
-                } else if (CentralSurfacesImpl.this.isInLaunchTransition()
-                        && 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
-                    // keyguardDonePending gets called first.
-                    mMainExecutor.execute(mStatusBarKeyguardViewManager::readyForKeyguardDone);
                 }
                 return deferred;
             }
@@ -2608,7 +2518,7 @@
                             flags |= CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL;
                         }
                     }
-                    mShadeController.animateCollapsePanels(flags);
+                    mShadeController.animateCollapseShade(flags);
                 }
             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                 if (mNotificationShadeWindowController != null) {
@@ -2723,10 +2633,9 @@
                 com.android.systemui.R.dimen.physical_power_button_center_screen_location_y));
     }
 
-    // Visibility reporting
     protected void handleVisibleToUserChanged(boolean visibleToUser) {
         if (visibleToUser) {
-            handleVisibleToUserChangedImpl(visibleToUser);
+            onVisibleToUser();
             mNotificationLogger.startNotificationLogging();
 
             if (!mIsBackCallbackRegistered) {
@@ -2743,7 +2652,7 @@
             }
         } else {
             mNotificationLogger.stopNotificationLogging();
-            handleVisibleToUserChangedImpl(visibleToUser);
+            onInvisibleToUser();
 
             if (mIsBackCallbackRegistered) {
                 ViewRootImpl viewRootImpl = getViewRootImpl();
@@ -2763,41 +2672,38 @@
         }
     }
 
-    // Visibility reporting
-    void handleVisibleToUserChangedImpl(boolean visibleToUser) {
-        if (visibleToUser) {
-            /* The LEDs are turned off when the notification panel is shown, even just a little bit.
-             * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
-             * this.
-             */
-            boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
-            boolean clearNotificationEffects =
-                    !mPresenter.isPresenterFullyCollapsed() &&
-                            (mState == StatusBarState.SHADE
-                                    || mState == StatusBarState.SHADE_LOCKED);
-            int notificationLoad = mNotificationsController.getActiveNotificationsCount();
-            if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
-                notificationLoad = 1;
-            }
-            final int finalNotificationLoad = notificationLoad;
-            mUiBgExecutor.execute(() -> {
-                try {
-                    mBarService.onPanelRevealed(clearNotificationEffects,
-                            finalNotificationLoad);
-                } catch (RemoteException ex) {
-                    // Won't fail unless the world has ended.
-                }
-            });
-        } else {
-            mUiBgExecutor.execute(() -> {
-                try {
-                    mBarService.onPanelHidden();
-                } catch (RemoteException ex) {
-                    // Won't fail unless the world has ended.
-                }
-            });
+    void onVisibleToUser() {
+        /* The LEDs are turned off when the notification panel is shown, even just a little bit.
+         * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
+         * this.
+         */
+        boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
+        boolean clearNotificationEffects =
+                !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE
+                        || mState == StatusBarState.SHADE_LOCKED);
+        int notificationLoad = mNotificationsController.getActiveNotificationsCount();
+        if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
+            notificationLoad = 1;
         }
+        final int finalNotificationLoad = notificationLoad;
+        mUiBgExecutor.execute(() -> {
+            try {
+                mBarService.onPanelRevealed(clearNotificationEffects,
+                        finalNotificationLoad);
+            } catch (RemoteException ex) {
+                // Won't fail unless the world has ended.
+            }
+        });
+    }
 
+    void onInvisibleToUser() {
+        mUiBgExecutor.execute(() -> {
+            try {
+                mBarService.onPanelHidden();
+            } catch (RemoteException ex) {
+                // Won't fail unless the world has ended.
+            }
+        });
     }
 
     private void logStateToEventlog() {
@@ -2975,24 +2881,19 @@
     private void updatePanelExpansionForKeyguard() {
         if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
                 != BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) {
-            mShadeController.instantExpandNotificationsPanel();
+            mShadeController.instantExpandShade();
         }
     }
 
     private void onLaunchTransitionFadingEnded() {
         mNotificationPanelViewController.resetAlpha();
-        mNotificationPanelViewController.onAffordanceLaunchEnded();
+        mCameraLauncherLazy.get().setLaunchingAffordance(false);
         releaseGestureWakeLock();
         runLaunchTransitionEndRunnable();
         mKeyguardStateController.setLaunchTransitionFadingAway(false);
         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.
      *
@@ -3056,7 +2957,7 @@
 
     private void onLaunchTransitionTimeout() {
         Log.w(TAG, "Launch transition: Timeout!");
-        mNotificationPanelViewController.onAffordanceLaunchEnded();
+        mCameraLauncherLazy.get().setLaunchingAffordance(false);
         releaseGestureWakeLock();
         mNotificationPanelViewController.resetViews(false /* animate */);
     }
@@ -3099,7 +3000,7 @@
             // too heavy for the CPU and GPU on any device.
             mNavigationBarController.disableAnimationsDuringHide(mDisplayId, delay);
         } else if (!mNotificationPanelViewController.isCollapsing()) {
-            instantCollapseNotificationPanel();
+            mShadeController.instantCollapseShade();
         }
 
         // Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile
@@ -3109,7 +3010,7 @@
         }
         mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
         releaseGestureWakeLock();
-        mNotificationPanelViewController.onAffordanceLaunchEnded();
+        mCameraLauncherLazy.get().setLaunchingAffordance(false);
         mNotificationPanelViewController.resetAlpha();
         mNotificationPanelViewController.resetTranslation();
         mNotificationPanelViewController.resetViewGroupFade();
@@ -3257,8 +3158,7 @@
     @Override
     public boolean onMenuPressed() {
         if (shouldUnlockOnMenuPressed()) {
-            mShadeController.animateCollapsePanels(
-                    CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+            mShadeController.animateCollapseShadeForced();
             return true;
         }
         return false;
@@ -3267,7 +3167,7 @@
     @Override
     public void endAffordanceLaunch() {
         releaseGestureWakeLock();
-        mNotificationPanelViewController.onAffordanceLaunchEnded();
+        mCameraLauncherLazy.get().setLaunchingAffordance(false);
     }
 
     /**
@@ -3303,7 +3203,7 @@
         if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
                 && !isBouncerShowingOverDream()) {
             if (mNotificationPanelViewController.canPanelBeCollapsed()) {
-                mShadeController.animateCollapsePanels();
+                mShadeController.animateCollapseShade();
             }
             return true;
         }
@@ -3313,8 +3213,7 @@
     @Override
     public boolean onSpacePressed() {
         if (mDeviceInteractive && mState != StatusBarState.SHADE) {
-            mShadeController.animateCollapsePanels(
-                    CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+            mShadeController.animateCollapseShadeForced();
             return true;
         }
         return false;
@@ -3330,9 +3229,9 @@
                 // lock screen where users can use the UDFPS affordance to enter the device
                 mStatusBarKeyguardViewManager.reset(true);
             } else if (mState == StatusBarState.KEYGUARD
-                    && !mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()
+                    && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
                     && isKeyguardSecure()) {
-                mStatusBarKeyguardViewManager.showGenericBouncer(true /* scrimmed */);
+                mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
             }
         }
     }
@@ -3354,12 +3253,6 @@
         }
     }
 
-    @Override
-    public void instantCollapseNotificationPanel() {
-        mNotificationPanelViewController.instantCollapse();
-        mShadeController.runPostCollapseRunnables();
-    }
-
     /**
      * Collapse the panel directly if we are on the main thread, post the collapsing on the main
      * thread if we are not.
@@ -3367,18 +3260,12 @@
     @Override
     public void collapsePanelOnMainThread() {
         if (Looper.getMainLooper().isCurrentThread()) {
-            mShadeController.collapsePanel();
+            mShadeController.collapseShade();
         } else {
-            mContext.getMainExecutor().execute(mShadeController::collapsePanel);
+            mContext.getMainExecutor().execute(mShadeController::collapseShade);
         }
     }
 
-    /** 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).
@@ -3390,6 +3277,10 @@
             return;
         }
 
+        if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+            return;
+        }
+
         final boolean wakingUpFromPowerButton = wakingUp
                 && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)
                 && mWakefulnessLifecycle.getLastWakeReason()
@@ -3416,37 +3307,6 @@
         return mLightRevealScrim;
     }
 
-    @Override
-    public void onTrackingStarted() {
-        mShadeController.runPostCollapseRunnables();
-    }
-
-    @Override
-    public void onClosingFinished() {
-        mShadeController.runPostCollapseRunnables();
-        if (!mPresenter.isPresenterFullyCollapsed()) {
-            // if we set it not to be focusable when collapsing, we have to undo it when we aborted
-            // the closing
-            mNotificationShadeWindowController.setNotificationShadeFocusable(true);
-        }
-    }
-
-    @Override
-    public void onUnlockHintStarted() {
-        mFalsingCollector.onUnlockHintStarted();
-        mKeyguardIndicationController.showActionToUnlock();
-    }
-
-    @Override
-    public void onHintFinished() {
-        // Delay the reset a bit so the user can read the text.
-        mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS);
-    }
-
-    @Override
-    public void onTrackingStopped(boolean expand) {
-    }
-
     // TODO: Figure out way to remove these.
     @Override
     public NavigationBarView getNavigationBarView() {
@@ -3468,15 +3328,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();
@@ -3540,7 +3391,7 @@
             mNotificationShadeWindowViewController.cancelCurrentTouch();
         }
         if (mPanelExpanded && mState == StatusBarState.SHADE) {
-            mShadeController.animateCollapsePanels();
+            mShadeController.animateCollapseShade();
         }
     }
 
@@ -3555,7 +3406,7 @@
     final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
         @Override
         public void onFinishedGoingToSleep() {
-            mNotificationPanelViewController.onAffordanceLaunchEnded();
+            mCameraLauncherLazy.get().setLaunchingAffordance(false);
             releaseGestureWakeLock();
             mLaunchCameraWhenFinishedWaking = false;
             mDeviceInteractive = false;
@@ -3603,7 +3454,7 @@
             // The unlocked screen off and fold to aod animations might use our LightRevealScrim -
             // we need to be expanded for it to be visible.
             if (mDozeParameters.shouldShowLightRevealScrim()) {
-                makeExpandedVisible(true);
+                mShadeController.makeExpandedVisible(true);
             }
 
             DejankUtils.stopDetectingBlockingIpcs(tag);
@@ -3632,7 +3483,7 @@
                 // If we are waking up during the screen off animation, we should undo making the
                 // expanded visible (we did that so the LightRevealScrim would be visible).
                 if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) {
-                    makeExpandedInvisible();
+                    mShadeController.makeExpandedInvisible();
                 }
 
             });
@@ -3656,7 +3507,8 @@
                         .updateSensitivenessForOccludedWakeup();
             }
             if (mLaunchCameraWhenFinishedWaking) {
-                mNotificationPanelViewController.launchCamera(mLastCameraLaunchSource);
+                mCameraLauncherLazy.get().launchCamera(mLastCameraLaunchSource,
+                        mNotificationPanelViewController.isFullyCollapsed());
                 mLaunchCameraWhenFinishedWaking = false;
             }
             if (mLaunchEmergencyActionWhenFinishedWaking) {
@@ -3829,7 +3681,7 @@
      * is fully hidden, while 0 means the bouncer is visible.
      */
     @Override
-    public void setBouncerHiddenFraction(float expansion) {
+    public void setPrimaryBouncerHiddenFraction(float expansion) {
         mScrimController.setBouncerHiddenFraction(expansion);
     }
 
@@ -3847,11 +3699,10 @@
 
         mScrimController.setExpansionAffectsAlpha(!unlocking);
 
-        boolean launchingAffordanceWithPreview =
-                mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
+        boolean launchingAffordanceWithPreview = mLaunchingAffordance;
         mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
 
-        if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
+        if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
             if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
                     || mTransitionToFullShadeProgress > 0f) {
                 mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
@@ -3862,7 +3713,7 @@
             // Bouncer needs the front scrim when it's on top of an activity,
             // tapping on a notification, editing QS or being dismissed by
             // FLAG_DISMISS_KEYGUARD_ACTIVITY.
-            ScrimState state = mStatusBarKeyguardViewManager.bouncerNeedsScrimming()
+            ScrimState state = mStatusBarKeyguardViewManager.primaryBouncerNeedsScrimming()
                     ? ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER;
             mScrimController.transitionTo(state);
         } else if (launchingAffordanceWithPreview) {
@@ -3967,8 +3818,7 @@
                 Settings.Secure.putInt(mContext.getContentResolver(),
                         Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
                 if (BANNER_ACTION_SETUP.equals(action)) {
-                    mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                            true /* force */);
+                    mShadeController.animateCollapseShadeForced();
                     mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
                             .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 
@@ -4030,7 +3880,7 @@
                     action.run();
                 }).start();
 
-                return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard;
+                return collapsePanel ? mShadeController.collapseShade() : willAnimateOnKeyguard;
             }
 
             @Override
@@ -4125,8 +3975,7 @@
         mMainExecutor.execute(runnable);
     }
 
-    @Override
-    public void visibilityChanged(boolean visible) {
+    private void onShadeVisibilityChanged(boolean visible) {
         if (mVisible != visible) {
             mVisible = visible;
             if (!visible) {
@@ -4171,7 +4020,7 @@
      */
     @Override
     public boolean isBouncerShowingScrimmed() {
-        return isBouncerShowing() && mStatusBarKeyguardViewManager.bouncerNeedsScrimming();
+        return isBouncerShowing() && mStatusBarKeyguardViewManager.primaryBouncerNeedsScrimming();
     }
 
     @Override
@@ -4179,29 +4028,12 @@
         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
-    public NotificationGutsManager getGutsManager() {
-        return mGutsManager;
-    }
+    // End Extra BaseStatusBarMethods.
 
     boolean isTransientShown() {
         return mTransientShown;
@@ -4213,7 +4045,9 @@
             return;
         }
 
-        mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
+        if (!mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
+            mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
+        }
     }
 
     @Override
@@ -4289,7 +4123,7 @@
                 Log.wtf(TAG, "WallpaperManager not supported");
                 return;
             }
-            WallpaperInfo info = mWallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
+            WallpaperInfo info = mWallpaperManager.getWallpaperInfoForUser(UserHandle.USER_CURRENT);
             mWallpaperController.onWallpaperInfoUpdated(info);
 
             final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
@@ -4325,7 +4159,6 @@
             }
             // TODO: Bring these out of CentralSurfaces.
             mUserInfoControllerImpl.onDensityOrFontScaleChanged();
-            mUserSwitcherController.onDensityOrFontScaleChanged();
             mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
             mHeadsUpManager.onDensityOrFontScaleChanged();
         }
@@ -4395,6 +4228,7 @@
                 @Override
                 public void onDozeAmountChanged(float linear, float eased) {
                     if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
+                            && !mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)
                             && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
                         mLightRevealScrim.setRevealAmount(1f - linear);
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 34cd1ce..7dcdc0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -33,7 +33,7 @@
     private val lastConfig = Configuration()
     private var density: Int = 0
     private var smallestScreenWidth: Int = 0
-    private var maxBounds: Rect? = null
+    private var maxBounds = Rect()
     private var fontScale: Float = 0.toFloat()
     private val inCarMode: Boolean
     private var uiMode: Int = 0
@@ -47,6 +47,7 @@
         fontScale = currentConfig.fontScale
         density = currentConfig.densityDpi
         smallestScreenWidth = currentConfig.smallestScreenWidthDp
+        maxBounds.set(currentConfig.windowConfiguration.maxBounds)
         inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK ==
                 Configuration.UI_MODE_TYPE_CAR
         uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
@@ -92,7 +93,11 @@
 
         val maxBounds = newConfig.windowConfiguration.maxBounds
         if (maxBounds != this.maxBounds) {
-            this.maxBounds = maxBounds
+            // Update our internal rect to have the same bounds, instead of using
+            // `this.maxBounds = maxBounds` directly. Setting it directly means that `maxBounds`
+            // would be a direct reference to windowConfiguration.maxBounds, so the if statement
+            // above would always fail. See b/245799099 for more information.
+            this.maxBounds.set(maxBounds)
             listeners.filterForEach({ this.listeners.contains(it) }) {
                 it.onMaxBoundsChanged()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 484441a..c217ab3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -19,11 +19,14 @@
 import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.OPERATOR_NAME_FRAME_VIEW;
 
 import android.graphics.Rect;
+import android.util.MathUtils;
 import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
@@ -32,8 +35,10 @@
 import com.android.systemui.statusbar.HeadsUpStatusBarView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
 import com.android.systemui.statusbar.policy.Clock;
@@ -51,6 +56,7 @@
 
 /**
  * Controls the appearance of heads up notifications in the icon area and the header itself.
+ * It also controls the roundness of the heads up notifications and the pulsing notifications.
  */
 @StatusBarFragmentScope
 public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBarView>
@@ -59,12 +65,17 @@
         NotificationWakeUpCoordinator.WakeUpListener {
     public static final int CONTENT_FADE_DURATION = 110;
     public static final int CONTENT_FADE_DELAY = 100;
+
+    private static final SourceType HEADS_UP = SourceType.from("HeadsUp");
+    private static final SourceType PULSING = SourceType.from("Pulsing");
     private final NotificationIconAreaController mNotificationIconAreaController;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final NotificationStackScrollLayoutController mStackScrollerController;
 
     private final DarkIconDispatcher mDarkIconDispatcher;
     private final NotificationPanelViewController mNotificationPanelViewController;
+    private final NotificationRoundnessManager mNotificationRoundnessManager;
+    private final boolean mUseRoundnessSourceTypes;
     private final Consumer<ExpandableNotificationRow>
             mSetTrackingHeadsUp = this::setTrackingHeadsUp;
     private final BiConsumer<Float, Float> mSetExpandedHeight = this::setAppearFraction;
@@ -105,11 +116,15 @@
             CommandQueue commandQueue,
             NotificationStackScrollLayoutController stackScrollerController,
             NotificationPanelViewController notificationPanelViewController,
+            NotificationRoundnessManager notificationRoundnessManager,
+            FeatureFlags featureFlags,
             HeadsUpStatusBarView headsUpStatusBarView,
             Clock clockView,
             @Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
         super(headsUpStatusBarView);
         mNotificationIconAreaController = notificationIconAreaController;
+        mNotificationRoundnessManager = notificationRoundnessManager;
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
         mHeadsUpManager = headsUpManager;
 
         // We may be mid-HUN-expansion when this controller is re-created (for example, if the user
@@ -179,6 +194,7 @@
     public void onHeadsUpPinned(NotificationEntry entry) {
         updateTopEntry();
         updateHeader(entry);
+        updateHeadsUpAndPulsingRoundness(entry);
     }
 
     private void updateTopEntry() {
@@ -316,6 +332,7 @@
     public void onHeadsUpUnPinned(NotificationEntry entry) {
         updateTopEntry();
         updateHeader(entry);
+        updateHeadsUpAndPulsingRoundness(entry);
     }
 
     public void setAppearFraction(float expandedHeight, float appearFraction) {
@@ -346,7 +363,9 @@
         ExpandableNotificationRow previousTracked = mTrackedChild;
         mTrackedChild = trackedChild;
         if (previousTracked != null) {
-            updateHeader(previousTracked.getEntry());
+            NotificationEntry entry = previousTracked.getEntry();
+            updateHeader(entry);
+            updateHeadsUpAndPulsingRoundness(entry);
         }
     }
 
@@ -357,6 +376,7 @@
     private void updateHeadsUpHeaders() {
         mHeadsUpManager.getAllEntries().forEach(entry -> {
             updateHeader(entry);
+            updateHeadsUpAndPulsingRoundness(entry);
         });
     }
 
@@ -370,6 +390,31 @@
         row.setHeaderVisibleAmount(headerVisibleAmount);
     }
 
+    /**
+     * Update the HeadsUp and the Pulsing roundness based on current state
+     * @param entry target notification
+     */
+    public void updateHeadsUpAndPulsingRoundness(NotificationEntry entry) {
+        if (mUseRoundnessSourceTypes) {
+            ExpandableNotificationRow row = entry.getRow();
+            boolean isTrackedChild = row == mTrackedChild;
+            if (row.isPinned() || row.isHeadsUpAnimatingAway() || isTrackedChild) {
+                float roundness = MathUtils.saturate(1f - mAppearFraction);
+                row.requestRoundness(roundness, roundness, HEADS_UP);
+            } else {
+                row.requestRoundnessReset(HEADS_UP);
+            }
+            if (mNotificationRoundnessManager.shouldRoundNotificationPulsing()) {
+                if (row.showingPulsing()) {
+                    row.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, PULSING);
+                } else {
+                    row.requestRoundnessReset(PULSING);
+                }
+            }
+        }
+    }
+
+
     @Override
     public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
         mView.onDarkChanged(areas, darkIntensity, tint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 103e4f6..3743fff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -34,6 +34,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
@@ -111,7 +112,8 @@
             ConfigurationController configurationController,
             @Main Handler handler,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            ShadeExpansionStateManager shadeExpansionStateManager) {
         super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
         Resources resources = mContext.getResources();
         mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
@@ -132,6 +134,8 @@
                 updateResources();
             }
         });
+
+        shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
     }
 
     public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -221,13 +225,7 @@
         mTrackingHeadsUp = trackingHeadsUp;
     }
 
-    /**
-     * Notify that the status bar panel gets expanded or collapsed.
-     *
-     * @param isExpanded True to notify expanded, false to notify collapsed.
-     * TODO(b/237811427) replace with a listener
-     */
-    public void setIsPanelExpanded(boolean isExpanded) {
+    private void onShadeExpansionFullyChanged(Boolean isExpanded) {
         if (isExpanded != mIsExpanded) {
             mIsExpanded = isExpanded;
             if (isExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 4897c52..78b28d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -23,6 +23,8 @@
 import android.view.ViewPropertyAnimator
 import android.view.WindowInsets
 import android.widget.FrameLayout
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.LockIconViewController
 import com.android.systemui.R
 import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder.bind
@@ -51,13 +53,20 @@
 
     private var ambientIndicationArea: View? = null
     private lateinit var binding: KeyguardBottomAreaViewBinder.Binding
+    private lateinit var lockIconViewController: LockIconViewController
 
     /** Initializes the view. */
     fun init(
         viewModel: KeyguardBottomAreaViewModel,
         falsingManager: FalsingManager,
+        lockIconViewController: LockIconViewController,
     ) {
-        binding = bind(this, viewModel, falsingManager)
+        binding = bind(
+                this,
+                viewModel,
+                falsingManager,
+        )
+        this.lockIconViewController = lockIconViewController
     }
 
     /**
@@ -114,4 +123,29 @@
         }
         return insets
     }
+
+    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+        super.onLayout(changed, left, top, right, bottom)
+        findViewById<View>(R.id.ambient_indication_container)?.let {
+            val (ambientLeft, ambientTop) = it.locationOnScreen
+            if (binding.shouldConstrainToTopOfLockIcon()) {
+                //make top of ambient indication view the bottom of the lock icon
+                it.layout(
+                        ambientLeft,
+                        lockIconViewController.bottom.toInt(),
+                        right - ambientLeft,
+                        ambientTop + it.measuredHeight
+                )
+            } else {
+                //make bottom of ambient indication view the top of the lock icon
+                val lockLocationTop = lockIconViewController.top
+                it.layout(
+                        ambientLeft,
+                        lockLocationTop.toInt() - it.measuredHeight,
+                        right - ambientLeft,
+                        lockLocationTop.toInt()
+                )
+            }
+        }
+    }
 }
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..000fe14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -55,15 +55,20 @@
 import javax.inject.Inject;
 
 /**
- * A class which manages the bouncer on the lockscreen.
+ * A class which manages the primary (pin/pattern/password) bouncer on the lockscreen.
  * @deprecated Use KeyguardBouncerRepository
  */
 @Deprecated
 public class KeyguardBouncer {
 
-    private static final String TAG = "KeyguardBouncer";
+    private static final String TAG = "PrimaryKeyguardBouncer";
     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;
 
@@ -73,7 +78,7 @@
     private final FalsingCollector mFalsingCollector;
     private final DismissCallbackRegistry mDismissCallbackRegistry;
     private final Handler mHandler;
-    private final List<BouncerExpansionCallback> mExpansionCallbacks = new ArrayList<>();
+    private final List<PrimaryBouncerExpansionCallback> mExpansionCallbacks = new ArrayList<>();
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final KeyguardStateController mKeyguardStateController;
     private final KeyguardSecurityModel mKeyguardSecurityModel;
@@ -121,7 +126,7 @@
     private KeyguardBouncer(Context context, ViewMediatorCallback callback,
             ViewGroup container,
             DismissCallbackRegistry dismissCallbackRegistry, FalsingCollector falsingCollector,
-            BouncerExpansionCallback expansionCallback,
+            PrimaryBouncerExpansionCallback expansionCallback,
             KeyguardStateController keyguardStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             KeyguardBypassController keyguardBypassController, @Main Handler handler,
@@ -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) {
@@ -227,8 +240,8 @@
                     && !mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
                             KeyguardUpdateMonitor.getCurrentUser())
                     && !needsFullscreenBouncer()
-                    && !mKeyguardUpdateMonitor.isFaceLockedOut()
-                    && !mKeyguardUpdateMonitor.userNeedsStrongAuth()
+                    && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                            BiometricSourceType.FACE)
                     && !mKeyguardBypassController.getBypassEnabled()) {
                 mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
             } else {
@@ -266,10 +279,7 @@
      * @see #onFullyShown()
      */
     private void onFullyHidden() {
-        cancelShowRunnable();
-        setVisibility(View.INVISIBLE);
-        mFalsingCollector.onBouncerHidden();
-        DejankUtils.postAfterTraversal(mResetRunnable);
+
     }
 
     private void setVisibility(@View.Visibility int visibility) {
@@ -446,7 +456,13 @@
             onFullyShown();
             dispatchFullyShown();
         } else if (fraction == EXPANSION_HIDDEN && oldExpansion != EXPANSION_HIDDEN) {
-            onFullyHidden();
+            DejankUtils.postAfterTraversal(mResetRunnable);
+            /*
+             * There are cases where #hide() was not invoked, such as when
+             * NotificationPanelViewController controls the hide animation. Make sure the state gets
+             * updated by calling #hide() directly.
+             */
+            hide(false /* destroyView */);
             dispatchFullyHidden();
         } else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) {
             dispatchStartingToHide();
@@ -558,37 +574,37 @@
     }
 
     private void dispatchFullyShown() {
-        for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
             callback.onFullyShown();
         }
     }
 
     private void dispatchStartingToHide() {
-        for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
             callback.onStartingToHide();
         }
     }
 
     private void dispatchStartingToShow() {
-        for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
             callback.onStartingToShow();
         }
     }
 
     private void dispatchFullyHidden() {
-        for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
             callback.onFullyHidden();
         }
     }
 
     private void dispatchExpansionChanged() {
-        for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
             callback.onExpansionChanged(mExpansion);
         }
     }
 
     private void dispatchVisibilityChanged() {
-        for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
             callback.onVisibilityChanged(mContainer.getVisibility() == View.VISIBLE);
         }
     }
@@ -634,7 +650,7 @@
     /**
      * Adds a callback to listen to bouncer expansion updates.
      */
-    public void addBouncerExpansionCallback(BouncerExpansionCallback callback) {
+    public void addBouncerExpansionCallback(PrimaryBouncerExpansionCallback callback) {
         if (!mExpansionCallbacks.contains(callback)) {
             mExpansionCallbacks.add(callback);
         }
@@ -644,11 +660,14 @@
      * Removes a previously added callback. If the callback was never added, this methood
      * does nothing.
      */
-    public void removeBouncerExpansionCallback(BouncerExpansionCallback callback) {
+    public void removeBouncerExpansionCallback(PrimaryBouncerExpansionCallback callback) {
         mExpansionCallbacks.remove(callback);
     }
 
-    public interface BouncerExpansionCallback {
+    /**
+     * Callback updated when the primary bouncer's show and hide states change.
+     */
+    public interface PrimaryBouncerExpansionCallback {
         /**
          * Invoked when the bouncer expansion reaches {@link KeyguardBouncer#EXPANSION_VISIBLE}.
          * This is NOT called each time the bouncer is shown, but rather only when the fully
@@ -732,7 +751,7 @@
          * Construct a KeyguardBouncer that will exist in the given container.
          */
         public KeyguardBouncer create(ViewGroup container,
-                BouncerExpansionCallback expansionCallback) {
+                PrimaryBouncerExpansionCallback expansionCallback) {
             return new KeyguardBouncer(mContext, mCallback, container,
                     mDismissCallbackRegistry, mFalsingCollector, expansionCallback,
                     mKeyguardStateController, mKeyguardUpdateMonitor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 18877f9..13566ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -26,6 +26,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.Pair;
 import android.util.TypedValue;
@@ -47,6 +48,9 @@
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
+import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -69,7 +73,7 @@
     private ImageView mMultiUserAvatar;
     private BatteryMeterView mBatteryView;
     private StatusIconContainer mStatusIconContainer;
-    private ViewGroup mUserSwitcherContainer;
+    private StatusBarUserSwitcherContainer mUserSwitcherContainer;
 
     private boolean mKeyguardUserSwitcherEnabled;
     private boolean mKeyguardUserAvatarEnabled;
@@ -120,8 +124,12 @@
         loadDimens();
     }
 
-    public ViewGroup getUserSwitcherContainer() {
-        return mUserSwitcherContainer;
+    /**
+     * Should only be called from {@link KeyguardStatusBarViewController}
+     * @param viewModel view model for the status bar user chip
+     */
+    void init(StatusBarUserChipViewModel viewModel) {
+        StatusBarUserChipViewBinder.bind(mUserSwitcherContainer, viewModel);
     }
 
     @Override
@@ -303,10 +311,7 @@
         lp = (LayoutParams) mStatusIconArea.getLayoutParams();
         lp.removeRule(RelativeLayout.RIGHT_OF);
         lp.width = LayoutParams.WRAP_CONTENT;
-
-        LinearLayout.LayoutParams llp =
-                (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
-        llp.setMarginStart(getResources().getDimensionPixelSize(
+        lp.setMarginStart(getResources().getDimensionPixelSize(
                 R.dimen.system_icons_super_container_margin_start));
         return true;
     }
@@ -338,10 +343,7 @@
         lp = (LayoutParams) mStatusIconArea.getLayoutParams();
         lp.addRule(RelativeLayout.RIGHT_OF, R.id.cutout_space_view);
         lp.width = LayoutParams.MATCH_PARENT;
-
-        LinearLayout.LayoutParams llp =
-                (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
-        llp.setMarginStart(0);
+        lp.setMarginStart(0);
         return true;
     }
 
@@ -527,4 +529,11 @@
         mClipRect.set(0, mTopClipping, getWidth(), getHeight());
         setClipBounds(mClipRect);
     }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Trace.beginSection("KeyguardStatusBarView#onMeasure");
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        Trace.endSection();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 14cebf4..d4dc1dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -59,13 +59,11 @@
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.fragment.StatusBarIconBlocklistKt;
 import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventAnimator;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -110,9 +108,7 @@
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final StatusBarContentInsetsProvider mInsetsProvider;
     private final UserManager mUserManager;
-    private final StatusBarUserSwitcherFeatureController mFeatureController;
-    private final StatusBarUserSwitcherController mUserSwitcherController;
-    private final StatusBarUserInfoTracker mStatusBarUserInfoTracker;
+    private final StatusBarUserChipViewModel mStatusBarUserChipViewModel;
     private final SecureSettings mSecureSettings;
     private final CommandQueue mCommandQueue;
     private final Executor mMainExecutor;
@@ -276,9 +272,7 @@
             SysuiStatusBarStateController statusBarStateController,
             StatusBarContentInsetsProvider statusBarContentInsetsProvider,
             UserManager userManager,
-            StatusBarUserSwitcherFeatureController featureController,
-            StatusBarUserSwitcherController userSwitcherController,
-            StatusBarUserInfoTracker statusBarUserInfoTracker,
+            StatusBarUserChipViewModel userChipViewModel,
             SecureSettings secureSettings,
             CommandQueue commandQueue,
             @Main Executor mainExecutor,
@@ -301,9 +295,7 @@
         mStatusBarStateController = statusBarStateController;
         mInsetsProvider = statusBarContentInsetsProvider;
         mUserManager = userManager;
-        mFeatureController = featureController;
-        mUserSwitcherController = userSwitcherController;
-        mStatusBarUserInfoTracker = statusBarUserInfoTracker;
+        mStatusBarUserChipViewModel = userChipViewModel;
         mSecureSettings = secureSettings;
         mCommandQueue = commandQueue;
         mMainExecutor = mainExecutor;
@@ -328,8 +320,7 @@
                 R.dimen.header_notifications_collide_distance);
 
         mView.setKeyguardUserAvatarEnabled(
-                !mFeatureController.isStatusBarUserSwitcherFeatureEnabled());
-        mFeatureController.addCallback(enabled -> mView.setKeyguardUserAvatarEnabled(!enabled));
+                !mStatusBarUserChipViewModel.getChipEnabled());
         mSystemEventAnimator = new StatusBarSystemEventAnimator(mView, r);
 
         mDisableStateTracker = new DisableStateTracker(
@@ -344,11 +335,11 @@
         super.onInit();
         mCarrierTextController.init();
         mBatteryMeterViewController.init();
-        mUserSwitcherController.init();
     }
 
     @Override
     protected void onViewAttached() {
+        mView.init(mStatusBarUserChipViewModel);
         mConfigurationController.addCallback(mConfigurationListener);
         mAnimationScheduler.addCallback(mAnimationCallback);
         mUserInfoController.addCallback(mOnUserInfoChangedListener);
@@ -394,9 +385,6 @@
     /** Sets whether user switcher is enabled. */
     public void setKeyguardUserSwitcherEnabled(boolean enabled) {
         mView.setKeyguardUserSwitcherEnabled(enabled);
-        // We don't have a listener for when the user switcher setting changes, so this is
-        // where we re-check the state
-        mStatusBarUserInfoTracker.checkEnabled();
     }
 
     /** Sets whether this controller should listen to battery updates. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
index 4496607..3989854 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
@@ -62,7 +62,7 @@
     private var statusBarBoundsProvider: StatusBarBoundsProvider? = null
 
     override fun start() {
-        dumpManager.registerDumpable(javaClass.simpleName) { printWriter, _ -> dump(printWriter) }
+        dumpManager.registerCriticalDumpable(javaClass.simpleName) { pw, _ -> dump(pw) }
     }
 
     override fun stop() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index 16fddb42..6bf5443 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -33,6 +33,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
@@ -41,12 +42,54 @@
 /**
  * Class to control all aspects about light bar changes.
  */
-public class LightBarTransitionsController implements Dumpable, Callbacks,
-        StatusBarStateController.StateListener {
+public class LightBarTransitionsController implements Dumpable {
 
     public static final int DEFAULT_TINT_ANIMATION_DURATION = 120;
     private static final String EXTRA_DARK_INTENSITY = "dark_intensity";
 
+    private static class Callback implements Callbacks, StatusBarStateController.StateListener {
+        private final WeakReference<LightBarTransitionsController> mSelf;
+
+        Callback(LightBarTransitionsController self) {
+            mSelf = new WeakReference<>(self);
+        }
+
+        @Override
+        public void appTransitionPending(int displayId, boolean forced) {
+            LightBarTransitionsController self = mSelf.get();
+            if (self != null) {
+                self.appTransitionPending(displayId, forced);
+            }
+        }
+
+        @Override
+        public void appTransitionCancelled(int displayId) {
+            LightBarTransitionsController self = mSelf.get();
+            if (self != null) {
+                self.appTransitionCancelled(displayId);
+            }
+        }
+
+        @Override
+        public void appTransitionStarting(int displayId, long startTime, long duration,
+                boolean forced) {
+            LightBarTransitionsController self = mSelf.get();
+            if (self != null) {
+                self.appTransitionStarting(displayId, startTime, duration, forced);
+            }
+        }
+
+        @Override
+        public void onDozeAmountChanged(float linear, float eased) {
+            LightBarTransitionsController self = mSelf.get();
+            if (self != null) {
+                self.onDozeAmountChanged(linear, eased);
+            }
+        }
+    }
+
+    private final Callback mCallback;
+
     private final Handler mHandler;
     private final DarkIntensityApplier mApplier;
     private final KeyguardStateController mKeyguardStateController;
@@ -86,8 +129,9 @@
         mKeyguardStateController = keyguardStateController;
         mStatusBarStateController = statusBarStateController;
         mCommandQueue = commandQueue;
-        mCommandQueue.addCallback(this);
-        mStatusBarStateController.addCallback(this);
+        mCallback = new Callback(this);
+        mCommandQueue.addCallback(mCallback);
+        mStatusBarStateController.addCallback(mCallback);
         mDozeAmount = mStatusBarStateController.getDozeAmount();
         mContext = context;
         mDisplayId = mContext.getDisplayId();
@@ -95,8 +139,8 @@
 
     /** Call to cleanup the LightBarTransitionsController when done with it. */
     public void destroy() {
-        mCommandQueue.removeCallback(this);
-        mStatusBarStateController.removeCallback(this);
+        mCommandQueue.removeCallback(mCallback);
+        mStatusBarStateController.removeCallback(mCallback);
     }
 
     public void saveState(Bundle outState) {
@@ -110,16 +154,14 @@
         mNextDarkIntensity = mDarkIntensity;
     }
 
-    @Override
-    public void appTransitionPending(int displayId, boolean forced) {
+    private void appTransitionPending(int displayId, boolean forced) {
         if (mDisplayId != displayId || mKeyguardStateController.isKeyguardGoingAway() && !forced) {
             return;
         }
         mTransitionPending = true;
     }
 
-    @Override
-    public void appTransitionCancelled(int displayId) {
+    private void appTransitionCancelled(int displayId) {
         if (mDisplayId != displayId) {
             return;
         }
@@ -131,9 +173,7 @@
         mTransitionPending = false;
     }
 
-    @Override
-    public void appTransitionStarting(int displayId, long startTime, long duration,
-            boolean forced) {
+    private void appTransitionStarting(int displayId, long startTime, long duration, boolean forced) {
         if (mDisplayId != displayId || mKeyguardStateController.isKeyguardGoingAway() && !forced) {
             return;
         }
@@ -230,10 +270,6 @@
         pw.print(" mNextDarkIntensity="); pw.println(mNextDarkIntensity);
     }
 
-    @Override
-    public void onStateChanged(int newState) { }
-
-    @Override
     public void onDozeAmountChanged(float linear, float eased) {
         mDozeAmount = eased;
         dispatchDark();
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/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 8793a57..1d7dfe1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.app.IWallpaperManager;
 import android.app.IWallpaperManagerCallback;
 import android.app.WallpaperColors;
@@ -45,6 +44,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationMediaManager;
 
 import libcore.io.IoUtils;
@@ -82,10 +82,11 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             DumpManager dumpManager,
             NotificationMediaManager mediaManager,
-            @Main Handler mainHandler) {
+            @Main Handler mainHandler,
+            UserTracker userTracker) {
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
         mWallpaperManager = wallpaperManager;
-        mCurrentUserId = ActivityManager.getCurrentUser();
+        mCurrentUserId = userTracker.getUserId();
         mUpdateMonitor = keyguardUpdateMonitor;
         mMediaManager = mediaManager;
         mH = mainHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
index 4969a1c..6811bf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
@@ -14,6 +14,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import androidx.annotation.MainThread;
+
 import com.android.systemui.statusbar.phone.ManagedProfileController.Callback;
 import com.android.systemui.statusbar.policy.CallbackController;
 
@@ -25,8 +27,20 @@
 
     boolean isWorkModeEnabled();
 
-    public interface Callback {
+    /**
+     * Callback to get updates about work profile status.
+     */
+    interface Callback {
+        /**
+         * Called when managed profile change is detected. This always runs on the main thread.
+         */
+        @MainThread
         void onManagedProfileChanged();
+
+        /**
+         * Called when managed profile removal is detected. This always runs on the main thread.
+         */
+        @MainThread
         void onManagedProfileRemoved();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 94d1bf4..abdf827 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -14,49 +14,47 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.app.ActivityManager;
 import android.app.StatusBarManager;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.UserInfo;
 import android.os.UserHandle;
 import android.os.UserManager;
 
 import androidx.annotation.NonNull;
 
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
-/**
- */
 @SysUISingleton
 public class ManagedProfileControllerImpl implements ManagedProfileController {
 
     private final List<Callback> mCallbacks = new ArrayList<>();
-
+    private final UserTrackerCallback mUserTrackerCallback = new UserTrackerCallback();
     private final Context mContext;
+    private final Executor mMainExecutor;
     private final UserManager mUserManager;
-    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final UserTracker mUserTracker;
     private final LinkedList<UserInfo> mProfiles;
+
     private boolean mListening;
     private int mCurrentUser;
 
-    /**
-     */
     @Inject
-    public ManagedProfileControllerImpl(Context context, BroadcastDispatcher broadcastDispatcher) {
+    public ManagedProfileControllerImpl(Context context, @Main Executor mainExecutor,
+            UserTracker userTracker, UserManager userManager) {
         mContext = context;
-        mUserManager = UserManager.get(mContext);
-        mBroadcastDispatcher = broadcastDispatcher;
-        mProfiles = new LinkedList<UserInfo>();
+        mMainExecutor = mainExecutor;
+        mUserManager = userManager;
+        mUserTracker = userTracker;
+        mProfiles = new LinkedList<>();
     }
 
     @Override
@@ -90,7 +88,7 @@
     private void reloadManagedProfiles() {
         synchronized (mProfiles) {
             boolean hadProfile = mProfiles.size() > 0;
-            int user = ActivityManager.getCurrentUser();
+            int user = mUserTracker.getUserId();
             mProfiles.clear();
 
             for (UserInfo ui : mUserManager.getEnabledProfiles(user)) {
@@ -99,16 +97,22 @@
                 }
             }
             if (mProfiles.size() == 0 && hadProfile && (user == mCurrentUser)) {
-                for (Callback callback : mCallbacks) {
-                    callback.onManagedProfileRemoved();
-                }
+                mMainExecutor.execute(this::notifyManagedProfileRemoved);
             }
             mCurrentUser = user;
         }
     }
 
+    private void notifyManagedProfileRemoved() {
+        for (Callback callback : mCallbacks) {
+            callback.onManagedProfileRemoved();
+        }
+    }
+
     public boolean hasActiveProfile() {
-        if (!mListening) reloadManagedProfiles();
+        if (!mListening || mUserTracker.getUserId() != mCurrentUser) {
+            reloadManagedProfiles();
+        }
         synchronized (mProfiles) {
             return mProfiles.size() > 0;
         }
@@ -127,30 +131,34 @@
     }
 
     private void setListening(boolean listening) {
+        if (mListening == listening) {
+            return;
+        }
         mListening = listening;
         if (listening) {
             reloadManagedProfiles();
-
-            final IntentFilter filter = new IntentFilter();
-            filter.addAction(Intent.ACTION_USER_SWITCHED);
-            filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
-            filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
-            filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
-            filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
-            mBroadcastDispatcher.registerReceiver(
-                    mReceiver, filter, null /* handler */, UserHandle.ALL);
+            mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
         } else {
-            mBroadcastDispatcher.unregisterReceiver(mReceiver);
+            mUserTracker.removeCallback(mUserTrackerCallback);
         }
     }
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+    private final class UserTrackerCallback implements UserTracker.Callback {
+
         @Override
-        public void onReceive(Context context, Intent intent) {
+        public void onUserChanged(int newUser, @NonNull Context userContext) {
             reloadManagedProfiles();
             for (Callback callback : mCallbacks) {
                 callback.onManagedProfileChanged();
             }
         }
-    };
+
+        @Override
+        public void onProfilesChanged(@NonNull List<UserInfo> profiles) {
+            reloadManagedProfiles();
+            for (Callback callback : mCallbacks) {
+                callback.onManagedProfileChanged();
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index c189ace..4ee2de1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -141,7 +141,6 @@
     /* Maximum number of icons in short shelf on lockscreen when also showing overflow dot. */
     public static final int MAX_ICONS_ON_LOCKSCREEN = 3;
     public static final int MAX_STATIC_ICONS = 4;
-    private static final int MAX_DOTS = 1;
 
     private boolean mIsStaticLayout = true;
     private final HashMap<View, IconState> mIconStates = new HashMap<>();
@@ -166,8 +165,7 @@
     private IconState mLastVisibleIconState;
     private IconState mFirstVisibleIconState;
     private float mVisualOverflowStart;
-    // Keep track of overflow in range [0, 3]
-    private int mNumDots;
+    private boolean mIsShowingOverflowDot;
     private StatusBarIconView mIsolatedIcon;
     private Rect mIsolatedIconLocation;
     private int[] mAbsolutePosition = new int[2];
@@ -387,8 +385,8 @@
         }
     }
 
-    public boolean hasMaxNumDot() {
-        return mNumDots >= MAX_DOTS;
+    public boolean areIconsOverflowing() {
+        return mIsShowingOverflowDot;
     }
 
     private boolean areAnimationsEnabled(StatusBarIconView icon) {
@@ -494,7 +492,7 @@
                     : 1f;
             translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
         }
-        mNumDots = 0;
+        mIsShowingOverflowDot = false;
         if (firstOverflowIndex != -1) {
             translationX = mVisualOverflowStart;
             for (int i = firstOverflowIndex; i < childCount; i++) {
@@ -502,15 +500,14 @@
                 IconState iconState = mIconStates.get(view);
                 int dotWidth = mStaticDotDiameter + mDotPadding;
                 iconState.setXTranslation(translationX);
-                if (mNumDots < MAX_DOTS) {
-                    if (mNumDots == 0 && iconState.iconAppearAmount < 0.8f) {
+                if (!mIsShowingOverflowDot) {
+                    if (iconState.iconAppearAmount < 0.8f) {
                         iconState.visibleState = StatusBarIconView.STATE_ICON;
                     } else {
                         iconState.visibleState = StatusBarIconView.STATE_DOT;
-                        mNumDots++;
+                        mIsShowingOverflowDot = true;
                     }
-                    translationX += (mNumDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth)
-                            * iconState.iconAppearAmount;
+                    translationX += dotWidth * iconState.iconAppearAmount;
                     mLastVisibleIconState = iconState;
                 } else {
                     iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
@@ -618,10 +615,6 @@
         return Math.min(getWidth(), translation);
     }
 
-    private float getMaxOverflowStart() {
-        return getLayoutEnd() - mIconSize;
-    }
-
     public void setChangingViewPositions(boolean changingViewPositions) {
         mChangingViewPositions = changingViewPositions;
     }
@@ -645,25 +638,6 @@
         mSpeedBumpIndex = speedBumpIndex;
     }
 
-    public boolean hasOverflow() {
-        return mNumDots > 0;
-    }
-
-    // Give some extra room for btw notifications if we can
-    public int getNoOverflowExtraPadding() {
-        if (mNumDots != 0) {
-            return 0;
-        }
-
-        int collapsedPadding = mIconSize;
-
-        if (collapsedPadding + getFinalTranslationX() > getWidth()) {
-            collapsedPadding = getWidth() - getFinalTranslationX();
-        }
-
-        return collapsedPadding;
-    }
-
     public int getIconSize() {
         return mIconSize;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
index 3811689..4fe03017 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
@@ -25,7 +25,7 @@
 import com.android.systemui.plugins.NotificationListenerController;
 import com.android.systemui.plugins.NotificationListenerController.NotificationProvider;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 
 import java.util.ArrayList;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 7aeb08d..28bc64d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -38,6 +38,9 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
+import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
 import com.android.systemui.util.leak.RotationUtils;
 
 import java.util.Objects;
@@ -73,6 +76,11 @@
         mTouchEventHandler = handler;
     }
 
+    void init(StatusBarUserChipViewModel viewModel) {
+        StatusBarUserSwitcherContainer container = findViewById(R.id.user_switcher_container);
+        StatusBarUserChipViewBinder.bind(container, viewModel);
+    }
+
     @Override
     public void onFinishInflate() {
         super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index f9c4c8f..11bc490 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -15,19 +15,25 @@
  */
 package com.android.systemui.statusbar.phone
 
+import android.app.StatusBarManager.WINDOW_STATE_SHOWING
+import android.app.StatusBarManager.WINDOW_STATUS_BAR
 import android.content.res.Configuration
 import android.graphics.Point
+import android.util.Log
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
 import com.android.systemui.R
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
+import com.android.systemui.statusbar.phone.PhoneStatusBarView.TouchEventHandler
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.unfold.SysUIUnfoldComponent
 import com.android.systemui.unfold.UNFOLD_STATUS_BAR
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.systemui.util.view.ViewUtil
@@ -35,14 +41,18 @@
 import javax.inject.Inject
 import javax.inject.Named
 
+private const val TAG = "PhoneStatusBarViewController"
+
 /** Controller for [PhoneStatusBarView].  */
 class PhoneStatusBarViewController private constructor(
     view: PhoneStatusBarView,
     @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
+    private val centralSurfaces: CentralSurfaces,
+    private val shadeController: ShadeController,
+    private val shadeLogger: ShadeLogger,
     private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
-    private val userSwitcherController: StatusBarUserSwitcherController,
+    private val userChipViewModel: StatusBarUserChipViewModel,
     private val viewUtil: ViewUtil,
-    touchEventHandler: PhoneStatusBarView.TouchEventHandler,
     private val configurationController: ConfigurationController
 ) : ViewController<PhoneStatusBarView>(view) {
 
@@ -90,11 +100,11 @@
     }
 
     init {
-        mView.setTouchEventHandler(touchEventHandler)
+        mView.setTouchEventHandler(PhoneStatusBarViewTouchHandler())
+        mView.init(userChipViewModel)
     }
 
     override fun onInit() {
-        userSwitcherController.init()
     }
 
     fun setImportantForAccessibility(mode: Int) {
@@ -120,6 +130,54 @@
         return viewUtil.touchIsWithinView(mView, x, y)
     }
 
+    /** Called when a touch event occurred on {@link PhoneStatusBarView}. */
+    fun onTouchEvent(event: MotionEvent) {
+        if (centralSurfaces.statusBarWindowState == WINDOW_STATE_SHOWING) {
+            val upOrCancel =
+                    event.action == MotionEvent.ACTION_UP ||
+                    event.action == MotionEvent.ACTION_CANCEL
+            centralSurfaces.setInteracting(WINDOW_STATUS_BAR,
+                    !upOrCancel || shadeController.isExpandedVisible)
+        }
+    }
+
+    inner class PhoneStatusBarViewTouchHandler : TouchEventHandler {
+        override fun onInterceptTouchEvent(event: MotionEvent) {
+            onTouchEvent(event)
+        }
+
+        override fun handleTouchEvent(event: MotionEvent): Boolean {
+            onTouchEvent(event)
+
+            // If panels aren't enabled, ignore the gesture and don't pass it down to the
+            // panel view.
+            if (!centralSurfaces.commandQueuePanelsEnabled) {
+                if (event.action == MotionEvent.ACTION_DOWN) {
+                    Log.v(TAG, String.format("onTouchForwardedFromStatusBar: panel disabled, " +
+                            "ignoring touch at (${event.x.toInt()},${event.y.toInt()})"))
+                }
+                return false
+            }
+
+            if (event.action == MotionEvent.ACTION_DOWN) {
+                // If the view that would receive the touch is disabled, just have status
+                // bar eat the gesture.
+                if (!centralSurfaces.notificationPanelViewController.isViewEnabled) {
+                    shadeLogger.logMotionEvent(event,
+                            "onTouchForwardedFromStatusBar: panel view disabled")
+                    return true
+                }
+                if (centralSurfaces.notificationPanelViewController.isFullyCollapsed &&
+                        event.y < 1f) {
+                    // b/235889526 Eat events on the top edge of the phone when collapsed
+                    shadeLogger.logMotionEvent(event, "top edge touch ignored")
+                    return true
+                }
+            }
+            return centralSurfaces.notificationPanelViewController.sendTouchEventToView(event)
+        }
+    }
+
     class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
         override fun getViewCenter(view: View, outPoint: Point) =
             when (view.id) {
@@ -156,21 +214,25 @@
         private val unfoldComponent: Optional<SysUIUnfoldComponent>,
         @Named(UNFOLD_STATUS_BAR)
         private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
-        private val userSwitcherController: StatusBarUserSwitcherController,
+        private val userChipViewModel: StatusBarUserChipViewModel,
+        private val centralSurfaces: CentralSurfaces,
+        private val shadeController: ShadeController,
+        private val shadeLogger: ShadeLogger,
         private val viewUtil: ViewUtil,
-        private val configurationController: ConfigurationController
+        private val configurationController: ConfigurationController,
     ) {
         fun create(
-            view: PhoneStatusBarView,
-            touchEventHandler: PhoneStatusBarView.TouchEventHandler
+            view: PhoneStatusBarView
         ) =
             PhoneStatusBarViewController(
                 view,
                 progressProvider.getOrNull(),
+                centralSurfaces,
+                shadeController,
+                shadeLogger,
                 unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(),
-                userSwitcherController,
+                userChipViewModel,
                 viewUtil,
-                touchEventHandler,
                 configurationController
             )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index cf3a48c..d500f99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -53,7 +53,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -111,6 +110,12 @@
     private boolean mClipsQsScrim;
 
     /**
+     * Whether an activity is launching over the lockscreen. During the launch animation, we want to
+     * delay certain scrim changes until after the animation ends.
+     */
+    private boolean mOccludeAnimationPlaying = false;
+
+    /**
      * 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.
@@ -205,7 +210,6 @@
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private KeyguardViewMediator mKeyguardViewMediator;
 
     private GradientColors mColors;
     private boolean mNeedsDrawableColorUpdate;
@@ -275,8 +279,7 @@
             @Main Executor mainExecutor,
             ScreenOffAnimationController screenOffAnimationController,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
-            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            KeyguardViewMediator keyguardViewMediator) {
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
         mScrimStateListener = lightBarController::setScrimState;
         mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
 
@@ -315,8 +318,6 @@
             }
         });
         mColors = new GradientColors();
-
-        mKeyguardViewMediator = keyguardViewMediator;
     }
 
     /**
@@ -351,6 +352,11 @@
                 .getBoolean(R.bool.notification_scrim_transparent);
         updateScrims();
         mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
+
+        // prepare() sets proper initial values for most states
+        for (ScrimState state : ScrimState.values()) {
+            state.prepare(state);
+        }
     }
 
     /**
@@ -640,10 +646,6 @@
     private void setTransitionToFullShade(boolean transitioning) {
         if (transitioning != mTransitioningToFullShade) {
             mTransitioningToFullShade = transitioning;
-            if (transitioning) {
-                // Let's make sure the shade locked is ready
-                ScrimState.SHADE_LOCKED.prepare(mState);
-            }
         }
     }
 
@@ -738,6 +740,11 @@
         return mClipsQsScrim;
     }
 
+    public void setOccludeAnimationPlaying(boolean occludeAnimationPlaying) {
+        mOccludeAnimationPlaying = occludeAnimationPlaying;
+        applyAndDispatchState();
+    }
+
     private void setOrAdaptCurrentAnimation(@Nullable View scrim) {
         if (scrim == null) {
             return;
@@ -777,11 +784,15 @@
         }
 
         if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING) {
-            // Darken scrim as you pull down the shade when unlocked, unless the shade is expanding
-            // because we're doing the screen off animation OR the shade is collapsing because
-            // we're playing the unlock animation
+            final boolean occluding =
+                    mOccludeAnimationPlaying || mState.mLaunchingAffordanceWithPreview;
+
+            // Darken scrim as it's pulled down while unlocked. If we're unlocked but playing the
+            // screen off/occlusion animations, ignore expansion changes while those animations
+            // play.
             if (!mScreenOffAnimationController.shouldExpandNotifications()
-                    && !mAnimatingPanelExpansionOnUnlock) {
+                    && !mAnimatingPanelExpansionOnUnlock
+                    && !occluding) {
                 float behindFraction = getInterpolatedFraction();
                 behindFraction = (float) Math.pow(behindFraction, 0.8f);
                 if (mClipsQsScrim) {
@@ -812,23 +823,8 @@
                         mBehindTint,
                         interpolatedFraction);
             }
-
-            // If we're unlocked but still playing the occlude animation, remain at the keyguard
-            // alpha temporarily.
-            if (mKeyguardViewMediator.isOccludeAnimationPlaying()
-                    || mState.mLaunchingAffordanceWithPreview) {
-                mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA;
-            }
         } else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
-            float behindFraction = getInterpolatedFraction();
-            behindFraction = (float) Math.pow(behindFraction, 0.8f);
-
-            mBehindAlpha = behindFraction * mDefaultScrimAlpha;
-            mNotificationsAlpha = mBehindAlpha;
-            if (mClipsQsScrim) {
-                mBehindAlpha = 1;
-                mBehindTint = Color.BLACK;
-            }
+            mNotificationsAlpha = (float) Math.pow(getInterpolatedFraction(), 0.8f);
         } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
                 || mState == ScrimState.PULSING) {
             Pair<Integer, Float> result = calculateBackStateForState(mState);
@@ -903,7 +899,7 @@
 
         float stateBehind = mClipsQsScrim ? state.getNotifAlpha() : state.getBehindAlpha();
         float behindAlpha;
-        int behindTint;
+        int behindTint = state.getBehindTint();
         if (mDarkenWhileDragging) {
             behindAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind,
                     interpolatedFract);
@@ -911,17 +907,19 @@
             behindAlpha = MathUtils.lerp(0 /* start */, stateBehind,
                     interpolatedFract);
         }
-        if (mClipsQsScrim) {
-            behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(),
+        if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) {
+            if (mClipsQsScrim) {
+                behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(),
                     state.getNotifTint(), interpolatedFract);
-        } else {
-            behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
+            } else {
+                behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
                     state.getBehindTint(), interpolatedFract);
+            }
         }
         if (mQsExpansion > 0) {
             behindAlpha = MathUtils.lerp(behindAlpha, mDefaultScrimAlpha, mQsExpansion);
             float tintProgress = mQsExpansion;
-            if (mStatusBarKeyguardViewManager.isBouncerInTransit()) {
+            if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) {
                 // this is case of - on lockscreen - going from expanded QS to bouncer.
                 // Because mQsExpansion is already interpolated and transition between tints
                 // is too slow, we want to speed it up and make it more aligned to bouncer
@@ -1104,7 +1102,7 @@
     }
 
     private float getInterpolatedFraction() {
-        if (mStatusBarKeyguardViewManager.isBouncerInTransit()) {
+        if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) {
             return BouncerPanelExpansionCalculator
                     .aboutToShowBouncerProgress(mPanelExpansionFraction);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index b447f0d..0e9d3ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -90,11 +90,14 @@
     AUTH_SCRIMMED_SHADE {
         @Override
         public void prepare(ScrimState previousState) {
-            // notif & behind scrim alpha values are determined by ScrimController#applyState
+            // notif scrim alpha values are determined by ScrimController#applyState
             // based on the shade expansion
 
             mFrontTint = Color.BLACK;
             mFrontAlpha = .66f;
+
+            mBehindTint = Color.BLACK;
+            mBehindAlpha = 1f;
         }
     },
 
@@ -143,18 +146,12 @@
             mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
             mNotifAlpha = 1f;
             mFrontAlpha = 0f;
-            mBehindTint = Color.BLACK;
+            mBehindTint = mClipQsScrim ? Color.TRANSPARENT : Color.BLACK;
 
             if (mClipQsScrim) {
                 updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
             }
         }
-
-        // to make sure correct color is returned before "prepare" is called
-        @Override
-        public int getBehindTint() {
-            return Color.BLACK;
-        }
     },
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 5512bed..3d6bebb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -72,9 +72,9 @@
                 //resize the layout. Let's
                 // make sure that the window stays small for one frame until the
                 //touchableRegion is set.
-                mNotificationPanelViewController.getView().requestLayout();
+                mNotificationPanelViewController.requestLayoutOnView();
                 mNotificationShadeWindowController.setForceWindowCollapsed(true);
-                mNotificationPanelViewController.getView().post(() -> {
+                mNotificationPanelViewController.postToView(() -> {
                     mNotificationShadeWindowController.setForceWindowCollapsed(false);
                 });
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
index 5113191..4d9de09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
@@ -5,6 +5,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -24,10 +25,11 @@
  */
 @SysUISingleton
 class StatusBarHideIconsForBouncerManager @Inject constructor(
-    private val commandQueue: CommandQueue,
-    @Main private val mainExecutor: DelayableExecutor,
-    statusBarWindowStateController: StatusBarWindowStateController,
-    dumpManager: DumpManager
+        private val commandQueue: CommandQueue,
+        @Main private val mainExecutor: DelayableExecutor,
+        statusBarWindowStateController: StatusBarWindowStateController,
+        shadeExpansionStateManager: ShadeExpansionStateManager,
+        dumpManager: DumpManager
 ) : Dumpable {
     // State variables set by external classes.
     private var panelExpanded: Boolean = false
@@ -47,6 +49,12 @@
         statusBarWindowStateController.addListener {
                 state -> setStatusBarStateAndTriggerUpdate(state)
         }
+        shadeExpansionStateManager.addFullExpansionListener { isExpanded ->
+            if (panelExpanded != isExpanded) {
+                panelExpanded = isExpanded
+                updateHideIconsForBouncer(animate = false)
+            }
+        }
     }
 
     /** Returns true if the status bar icons should be hidden in the bouncer. */
@@ -63,11 +71,6 @@
         this.displayId = displayId
     }
 
-    fun setPanelExpandedAndTriggerUpdate(panelExpanded: Boolean) {
-        this.panelExpanded = panelExpanded
-        updateHideIconsForBouncer(animate = false)
-    }
-
     fun setIsOccludedAndTriggerUpdate(isOccluded: Boolean) {
         this.isOccluded = isOccluded
         updateHideIconsForBouncer(animate = false)
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..aafcddd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -19,8 +19,6 @@
 import static android.view.WindowInsets.Type.navigationBars;
 
 import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER;
-import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
 
@@ -60,8 +58,8 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.data.BouncerView;
-import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -77,8 +75,7 @@
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
-import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
+import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.unfold.FoldAodAnimationController;
@@ -86,8 +83,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;
 
@@ -133,69 +132,68 @@
     @Nullable
     private final FoldAodAnimationController mFoldAodAnimationController;
     private KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController;
-    private final BouncerCallbackInteractor mBouncerCallbackInteractor;
-    private final BouncerInteractor mBouncerInteractor;
-    private final BouncerView mBouncerView;
+    private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
+    private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+    private final BouncerView mPrimaryBouncerView;
     private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
 
-    private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
-        private boolean mBouncerAnimating;
+    // Local cache of expansion events, to avoid duplicates
+    private float mFraction = -1f;
+    private boolean mTracking = false;
 
-        @Override
-        public void onFullyShown() {
-            mBouncerAnimating = false;
-            updateStates();
-        }
+    private final PrimaryBouncerExpansionCallback mExpansionCallback =
+            new PrimaryBouncerExpansionCallback() {
+            private boolean mPrimaryBouncerAnimating;
 
-        @Override
-        public void onStartingToHide() {
-            mBouncerAnimating = true;
-            updateStates();
-        }
-
-        @Override
-        public void onStartingToShow() {
-            mBouncerAnimating = true;
-            updateStates();
-        }
-
-        @Override
-        public void onFullyHidden() {
-            mBouncerAnimating = false;
-        }
-
-        @Override
-        public void onExpansionChanged(float expansion) {
-            if (mAlternateAuthInterceptor != null) {
-                mAlternateAuthInterceptor.setBouncerExpansionChanged(expansion);
-            }
-            if (mBouncerAnimating) {
-                mCentralSurfaces.setBouncerHiddenFraction(expansion);
-            }
-            updateStates();
-        }
-
-        @Override
-        public void onVisibilityChanged(boolean isVisible) {
-            mCentralSurfaces
-                    .setBouncerShowingOverDream(
-                            isVisible && mDreamOverlayStateController.isOverlayActive());
-
-            if (!isVisible) {
-                mCentralSurfaces.setBouncerHiddenFraction(KeyguardBouncer.EXPANSION_HIDDEN);
-            }
-            if (mAlternateAuthInterceptor != null) {
-                mAlternateAuthInterceptor.onBouncerVisibilityChanged();
+            @Override
+            public void onFullyShown() {
+                mPrimaryBouncerAnimating = false;
+                updateStates();
             }
 
-            /* Register predictive back callback when keyguard becomes visible, and unregister
-            when it's hidden. */
-            if (isVisible) {
-                registerBackCallback();
-            } else {
-                unregisterBackCallback();
+            @Override
+            public void onStartingToHide() {
+                mPrimaryBouncerAnimating = true;
+                updateStates();
             }
-        }
+
+            @Override
+            public void onStartingToShow() {
+                mPrimaryBouncerAnimating = true;
+                updateStates();
+            }
+
+            @Override
+            public void onFullyHidden() {
+                mPrimaryBouncerAnimating = false;
+                updateStates();
+            }
+
+            @Override
+            public void onExpansionChanged(float expansion) {
+                if (mPrimaryBouncerAnimating) {
+                    mCentralSurfaces.setPrimaryBouncerHiddenFraction(expansion);
+                }
+            }
+
+            @Override
+            public void onVisibilityChanged(boolean isVisible) {
+                mCentralSurfaces.setBouncerShowingOverDream(
+                        isVisible && mDreamOverlayStateController.isOverlayActive());
+
+                if (!isVisible) {
+                    mCentralSurfaces.setPrimaryBouncerHiddenFraction(
+                            KeyguardBouncer.EXPANSION_HIDDEN);
+                }
+
+                /* Register predictive back callback when keyguard becomes visible, and unregister
+                when it's hidden. */
+                if (isVisible) {
+                    registerBackCallback();
+                } else {
+                    unregisterBackCallback();
+                }
+            }
     };
 
     private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
@@ -221,14 +219,14 @@
 
     protected LockPatternUtils mLockPatternUtils;
     protected ViewMediatorCallback mViewMediatorCallback;
-    protected CentralSurfaces mCentralSurfaces;
+    @Nullable protected CentralSurfaces mCentralSurfaces;
     private NotificationPanelViewController mNotificationPanelViewController;
     private BiometricUnlockController mBiometricUnlockController;
     private boolean mCentralSurfacesRegistered;
 
     private View mNotificationContainer;
 
-    @Nullable protected KeyguardBouncer mBouncer;
+    @Nullable protected KeyguardBouncer mPrimaryBouncer;
     protected boolean mRemoteInputActive;
     private boolean mGlobalActionsVisible = false;
     private boolean mLastGlobalActionsVisible = false;
@@ -241,8 +239,8 @@
     protected boolean mFirstUpdate = true;
     protected boolean mLastShowing;
     protected boolean mLastOccluded;
-    private boolean mLastBouncerShowing;
-    private boolean mLastBouncerIsOrWillBeShowing;
+    private boolean mLastPrimaryBouncerShowing;
+    private boolean mLastPrimaryBouncerIsOrWillBeShowing;
     private boolean mLastBouncerDismissible;
     protected boolean mLastRemoteInputActive;
     private boolean mLastDozing;
@@ -252,6 +250,7 @@
     private int mLastBiometricMode;
     private boolean mLastScreenOffAnimationPlaying;
     private float mQsExpansion;
+    final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
     private boolean mIsModernBouncerEnabled;
 
     private OnDismissAction mAfterKeyguardGoneAction;
@@ -268,8 +267,8 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateManager;
     private final LatencyTracker mLatencyTracker;
     private final KeyguardSecurityModel mKeyguardSecurityModel;
-    private KeyguardBypassController mBypassController;
-    @Nullable private AlternateAuthInterceptor mAlternateAuthInterceptor;
+    @Nullable private KeyguardBypassController mBypassController;
+    @Nullable private AlternateBouncer mAlternateBouncer;
 
     private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -304,9 +303,9 @@
             LatencyTracker latencyTracker,
             KeyguardSecurityModel keyguardSecurityModel,
             FeatureFlags featureFlags,
-            BouncerCallbackInteractor bouncerCallbackInteractor,
-            BouncerInteractor bouncerInteractor,
-            BouncerView bouncerView) {
+            PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor,
+            PrimaryBouncerInteractor primaryBouncerInteractor,
+            BouncerView primaryBouncerView) {
         mContext = context;
         mViewMediatorCallback = callback;
         mLockPatternUtils = lockPatternUtils;
@@ -324,9 +323,9 @@
         mShadeController = shadeController;
         mLatencyTracker = latencyTracker;
         mKeyguardSecurityModel = keyguardSecurityModel;
-        mBouncerCallbackInteractor = bouncerCallbackInteractor;
-        mBouncerInteractor = bouncerInteractor;
-        mBouncerView = bouncerView;
+        mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
+        mPrimaryBouncerInteractor = primaryBouncerInteractor;
+        mPrimaryBouncerView = primaryBouncerView;
         mFoldAodAnimationController = sysUIUnfoldComponent
                 .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
         mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
@@ -344,9 +343,9 @@
 
         ViewGroup container = mCentralSurfaces.getBouncerContainer();
         if (mIsModernBouncerEnabled) {
-            mBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback);
+            mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback);
         } else {
-            mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
+            mPrimaryBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
         }
         mNotificationPanelViewController = notificationPanelViewController;
         if (shadeExpansionStateManager != null) {
@@ -365,20 +364,20 @@
      * Sets the given alt auth interceptor to null if it's the current auth interceptor. Else,
      * does nothing.
      */
-    public void removeAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) {
-        if (Objects.equals(mAlternateAuthInterceptor, authInterceptor)) {
-            mAlternateAuthInterceptor = null;
-            resetAlternateAuth(true);
+    public void removeAlternateAuthInterceptor(@NonNull AlternateBouncer authInterceptor) {
+        if (Objects.equals(mAlternateBouncer, authInterceptor)) {
+            mAlternateBouncer = null;
+            hideAlternateBouncer(true);
         }
     }
 
     /**
      * Sets a new alt auth interceptor.
      */
-    public void setAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) {
-        if (!Objects.equals(mAlternateAuthInterceptor, authInterceptor)) {
-            mAlternateAuthInterceptor = authInterceptor;
-            resetAlternateAuth(false);
+    public void setAlternateBouncer(@NonNull AlternateBouncer authInterceptor) {
+        if (!Objects.equals(mAlternateBouncer, authInterceptor)) {
+            mAlternateBouncer = authInterceptor;
+            hideAlternateBouncer(false);
         }
     }
 
@@ -442,77 +441,68 @@
         hideBouncer(true /* destroyView */);
     }
 
-    @Override
-    public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
-        float fraction = event.getFraction();
-        boolean tracking = event.getTracking();
+    private boolean beginShowingBouncer(ShadeExpansionChangeEvent event) {
         // Avoid having the shade and the bouncer open at the same time over a dream.
         final boolean hideBouncerOverDream =
                 mDreamOverlayStateController.isOverlayActive()
                         && (mNotificationPanelViewController.isExpanded()
                         || mNotificationPanelViewController.isExpanding());
 
-        // We don't want to translate the bounce when:
-        // • device is dozing and not pulsing
-        // • Keyguard is occluded, because we're in a FLAG_SHOW_WHEN_LOCKED activity and need to
-        //   conserve the original animation.
-        // • The user quickly taps on the display and we show "swipe up to unlock."
-        // • Keyguard will be dismissed by an action. a.k.a: FLAG_DISMISS_KEYGUARD_ACTIVITY
-        // • Full-screen user switcher is displayed.
-        if (mDozing && !mPulsing) {
+        final boolean isUserTrackingStarted =
+                event.getFraction() != KeyguardBouncer.EXPANSION_HIDDEN && event.getTracking();
+
+        return mKeyguardStateController.isShowing()
+                && !primaryBouncerIsOrWillBeShowing()
+                && isUserTrackingStarted
+                && !hideBouncerOverDream
+                && !mKeyguardStateController.isOccluded()
+                && !mKeyguardStateController.canDismissLockScreen()
+                && !bouncerIsAnimatingAway()
+                && !mNotificationPanelViewController.isUnlockHintRunning()
+                && !(mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED);
+    }
+
+    @Override
+    public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
+        float fraction = event.getFraction();
+        boolean tracking = event.getTracking();
+
+        if (mFraction == fraction && mTracking == tracking) {
+            // Ignore duplicate events, as they will cause confusion with bouncer expansion
             return;
-        } else if (mNotificationPanelViewController.isUnlockHintRunning()) {
-            if (mBouncer != null) {
-                mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+        }
+        mFraction = fraction;
+        mTracking = tracking;
+
+        /*
+         * The bouncer may have received a call to show(), or the following will infer it from
+         * device state and touch handling. The bouncer MUST have been notified that it is about to
+         * show if any subsequent events are to be handled.
+         */
+        if (beginShowingBouncer(event)) {
+            if (mPrimaryBouncer != null) {
+                mPrimaryBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
             } else {
-                mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+                mPrimaryBouncerInteractor.show(/* isScrimmed= */false);
             }
-        } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
-            // Don't expand to the bouncer. Instead transition back to the lock screen (see
-            // CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
+        }
+
+        if (!primaryBouncerIsOrWillBeShowing()) {
             return;
-        } else if (bouncerNeedsScrimming()) {
-            if (mBouncer != null) {
-                mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+        }
+
+        if (mKeyguardStateController.isShowing()) {
+            if (mPrimaryBouncer != null) {
+                mPrimaryBouncer.setExpansion(fraction);
             } else {
-                mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+                mPrimaryBouncerInteractor.setPanelExpansion(fraction);
             }
-        } else if (mKeyguardStateController.isShowing()  && !hideBouncerOverDream) {
-            if (!isWakeAndUnlocking()
-                    && !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
-                    && !mCentralSurfaces.isInLaunchTransition()
-                    && !isUnlockCollapsing()) {
-                if (mBouncer != null) {
-                    mBouncer.setExpansion(fraction);
-                } else {
-                    mBouncerInteractor.setExpansion(fraction);
-                }
-            }
-            if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
-                    && !mKeyguardStateController.canDismissLockScreen()
-                    && !bouncerIsShowing()
-                    && !bouncerIsAnimatingAway()) {
-                if (mBouncer != null) {
-                    mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
-                } else {
-                    mBouncerInteractor.show(/* isScrimmed= */false);
-                }
-            }
-        } else if (!mKeyguardStateController.isShowing()  && isBouncerInTransit()) {
-            // Keyguard is not visible anymore, but expansion animation was still running.
-            // We need to hide the bouncer, otherwise it will be stuck in transit.
-            if (mBouncer != null) {
-                mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+        } else {
+            if (mPrimaryBouncer != null) {
+                mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             } else {
-                mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+                mPrimaryBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             }
-        } else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
-            // Panel expanded while pulsing but didn't translate the bouncer (because we are
-            // unlocked.) Let's simply wake-up to dismiss the lock screen.
-            mCentralSurfaces.wakeUpIfDozing(
-                    SystemClock.uptimeMillis(),
-                    mCentralSurfaces.getBouncerContainer(),
-                    "BOUNCER_VISIBLE");
         }
     }
 
@@ -548,17 +538,17 @@
         if (needsFullscreenBouncer() && !mDozing) {
             // The keyguard might be showing (already). So we need to hide it.
             mCentralSurfaces.hideKeyguard();
-            if (mBouncer != null) {
-                mBouncer.show(true /* resetSecuritySelection */);
+            if (mPrimaryBouncer != null) {
+                mPrimaryBouncer.show(true /* resetSecuritySelection */);
             } else {
-                mBouncerInteractor.show(true);
+                mPrimaryBouncerInteractor.show(true);
             }
         } else {
             mCentralSurfaces.showKeyguard();
             if (hideBouncerWhenShowing) {
                 hideBouncer(false /* destroyView */);
-                if (mBouncer != null) {
-                    mBouncer.prepare();
+                if (mPrimaryBouncer != null) {
+                    mPrimaryBouncer.prepare();
                 }
             }
         }
@@ -566,23 +556,25 @@
     }
 
     /**
-     * If applicable, shows the alternate authentication bouncer. Else, shows the input
-     * (pin/password/pattern) bouncer.
-     * @param scrimmed true when the input bouncer should show scrimmed, false when the user will be
-     * dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
+     *
+     * If possible, shows the alternate bouncer. Else, shows the primary (pin/pattern/password)
+     * bouncer.
+     * @param scrimmed true when the primary bouncer should show scrimmed,
+     *                 false when the user will be dragging it and translation should be deferred
+     *                 {@see KeyguardBouncer#show(boolean, boolean)}
      */
-    public void showGenericBouncer(boolean scrimmed) {
-        if (shouldShowAltAuth()) {
-            updateAlternateAuthShowing(mAlternateAuthInterceptor.showAlternateAuthBouncer());
+    public void showBouncer(boolean scrimmed) {
+        if (canShowAlternateBouncer()) {
+            updateAlternateBouncerShowing(mAlternateBouncer.showAlternateBouncer());
             return;
         }
 
-        showBouncer(scrimmed);
+        showPrimaryBouncer(scrimmed);
     }
 
-    /** Whether we should show the alternate authentication instead of the traditional bouncer. */
-    public boolean shouldShowAltAuth() {
-        return mAlternateAuthInterceptor != null
+    /** Whether we can show the alternate bouncer instead of the primary bouncer. */
+    public boolean canShowAlternateBouncer() {
+        return mAlternateBouncer != null
                 && mKeyguardUpdateManager.isUnlockingWithBiometricAllowed(true);
     }
 
@@ -591,10 +583,10 @@
      */
     @VisibleForTesting
     void hideBouncer(boolean destroyView) {
-        if (mBouncer != null) {
-            mBouncer.hide(destroyView);
+        if (mPrimaryBouncer != null) {
+            mPrimaryBouncer.hide(destroyView);
         } else {
-            mBouncerInteractor.hide();
+            mPrimaryBouncerInteractor.hide();
         }
         if (mKeyguardStateController.isShowing()) {
             // If we were showing the bouncer and then aborting, we need to also clear out any
@@ -605,19 +597,19 @@
     }
 
     /**
-     * Shows the keyguard input bouncer - the password challenge on the lock screen
+     * Shows the primary bouncer - the pin/pattern/password challenge on the lock screen.
      *
      * @param scrimmed true when the bouncer should show scrimmed, false when the user will be
      * dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
      */
-    public void showBouncer(boolean scrimmed) {
-        resetAlternateAuth(false);
+    public void showPrimaryBouncer(boolean scrimmed) {
+        hideAlternateBouncer(false);
 
         if (mKeyguardStateController.isShowing()  && !isBouncerShowing()) {
-            if (mBouncer != null) {
-                mBouncer.show(false /* resetSecuritySelection */, scrimmed);
+            if (mPrimaryBouncer != null) {
+                mPrimaryBouncer.show(false /* resetSecuritySelection */, scrimmed);
             } else {
-                mBouncerInteractor.show(scrimmed);
+                mPrimaryBouncerInteractor.show(scrimmed);
             }
         }
         updateStates();
@@ -649,42 +641,41 @@
 
                 // If there is an an alternate auth interceptor (like the UDFPS), show that one
                 // instead of the bouncer.
-                if (shouldShowAltAuth()) {
+                if (canShowAlternateBouncer()) {
                     if (!afterKeyguardGone) {
-                        if (mBouncer != null) {
-                            mBouncer.setDismissAction(mAfterKeyguardGoneAction,
+                        if (mPrimaryBouncer != null) {
+                            mPrimaryBouncer.setDismissAction(mAfterKeyguardGoneAction,
                                     mKeyguardGoneCancelAction);
                         } else {
-                            mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
+                            mPrimaryBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
                                     mKeyguardGoneCancelAction);
                         }
                         mAfterKeyguardGoneAction = null;
                         mKeyguardGoneCancelAction = null;
                     }
 
-                    updateAlternateAuthShowing(
-                            mAlternateAuthInterceptor.showAlternateAuthBouncer());
+                    updateAlternateBouncerShowing(mAlternateBouncer.showAlternateBouncer());
                     return;
                 }
 
                 if (afterKeyguardGone) {
                     // we'll handle the dismiss action after keyguard is gone, so just show the
                     // bouncer
-                    if (mBouncer != null) {
-                        mBouncer.show(false /* resetSecuritySelection */);
+                    if (mPrimaryBouncer != null) {
+                        mPrimaryBouncer.show(false /* resetSecuritySelection */);
                     } else {
-                        mBouncerInteractor.show(/* isScrimmed= */true);
+                        mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
                     }
                 } else {
                     // after authentication success, run dismiss action with the option to defer
                     // hiding the keyguard based on the return value of the OnDismissAction
-                    if (mBouncer != null) {
-                        mBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
+                    if (mPrimaryBouncer != null) {
+                        mPrimaryBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
                                 mKeyguardGoneCancelAction);
                     } else {
-                        mBouncerInteractor.setDismissAction(
+                        mPrimaryBouncerInteractor.setDismissAction(
                                 mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
-                        mBouncerInteractor.show(/* isScrimmed= */true);
+                        mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
                     }
                     // bouncer will handle the dismiss action, so we no longer need to track it here
                     mAfterKeyguardGoneAction = null;
@@ -702,11 +693,6 @@
         return mode == MODE_WAKE_AND_UNLOCK || mode == MODE_WAKE_AND_UNLOCK_PULSING;
     }
 
-    private boolean isUnlockCollapsing() {
-        int mode = mBiometricUnlockController.getMode();
-        return mode == MODE_UNLOCK_COLLAPSING;
-    }
-
     /**
      * Adds a {@param runnable} to be executed after Keyguard is gone.
      */
@@ -729,28 +715,34 @@
             } else {
                 showBouncerOrKeyguard(hideBouncerWhenShowing);
             }
-            resetAlternateAuth(false);
+            hideAlternateBouncer(false);
             mKeyguardUpdateManager.sendKeyguardReset();
             updateStates();
         }
     }
 
     @Override
-    public void resetAlternateAuth(boolean forceUpdateScrim) {
-        final boolean updateScrim = (mAlternateAuthInterceptor != null
-                && mAlternateAuthInterceptor.hideAlternateAuthBouncer())
+    public void hideAlternateBouncer(boolean forceUpdateScrim) {
+        final boolean updateScrim = (mAlternateBouncer != null
+                && mAlternateBouncer.hideAlternateBouncer())
                 || forceUpdateScrim;
-        updateAlternateAuthShowing(updateScrim);
+        updateAlternateBouncerShowing(updateScrim);
     }
 
-    private void updateAlternateAuthShowing(boolean updateScrim) {
-        final boolean isShowingAltAuth = isShowingAlternateAuth();
+    private void updateAlternateBouncerShowing(boolean updateScrim) {
+        if (!mCentralSurfacesRegistered) {
+            // if CentralSurfaces hasn't been registered yet, then the controllers below haven't
+            // been initialized yet so there's no need to attempt to forward them events.
+            return;
+        }
+
+        final boolean isShowingAlternateBouncer = isShowingAlternateBouncer();
         if (mKeyguardMessageAreaController != null) {
-            mKeyguardMessageAreaController.setIsVisible(isShowingAltAuth);
+            mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer);
             mKeyguardMessageAreaController.setMessage("");
         }
-        mBypassController.setAltBouncerShowing(isShowingAltAuth);
-        mKeyguardUpdateManager.setUdfpsBouncerShowing(isShowingAltAuth);
+        mBypassController.setAltBouncerShowing(isShowingAlternateBouncer);
+        mKeyguardUpdateManager.setUdfpsBouncerShowing(isShowingAlternateBouncer);
 
         if (updateScrim) {
             mCentralSurfaces.updateScrimController();
@@ -787,10 +779,10 @@
 
     @Override
     public void onFinishedGoingToSleep() {
-        if (mBouncer != null) {
-            mBouncer.onScreenTurnedOff();
+        if (mPrimaryBouncer != null) {
+            mPrimaryBouncer.onScreenTurnedOff();
         } else {
-            mBouncerInteractor.onScreenTurnedOff();
+            mPrimaryBouncerInteractor.onScreenTurnedOff();
         }
     }
 
@@ -849,21 +841,6 @@
         if (isShowing && isOccluding) {
             SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                     SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
-            if (mCentralSurfaces.isInLaunchTransition()) {
-                final Runnable endRunnable = new Runnable() {
-                    @Override
-                    public void run() {
-                        mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
-                        reset(true /* hideBouncerWhenShowing */);
-                    }
-                };
-                mCentralSurfaces.fadeKeyguardAfterLaunchTransition(
-                        null /* beforeFading */,
-                        endRunnable,
-                        endRunnable);
-                return;
-            }
-
             if (mCentralSurfaces.isLaunchingActivityOverLockscreen()) {
                 // When isLaunchingActivityOverLockscreen() is true, we know for sure that the post
                 // collapse runnables will be run.
@@ -890,20 +867,20 @@
             // by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
             reset(isOccluding /* hideBouncerWhenShowing*/);
         }
-        if (animate && !isOccluded && isShowing && !bouncerIsShowing()) {
+        if (animate && !isOccluded && isShowing && !primaryBouncerIsShowing()) {
             mCentralSurfaces.animateKeyguardUnoccluding();
         }
     }
 
     @Override
     public void startPreHideAnimation(Runnable finishRunnable) {
-        if (bouncerIsShowing()) {
-            if (mBouncer != null) {
-                mBouncer.startPreHideAnimation(finishRunnable);
+        if (primaryBouncerIsShowing()) {
+            if (mPrimaryBouncer != null) {
+                mPrimaryBouncer.startPreHideAnimation(finishRunnable);
             } else {
-                mBouncerInteractor.startDisappearAnimation(finishRunnable);
+                mPrimaryBouncerInteractor.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,8 +912,7 @@
         long uptimeMillis = SystemClock.uptimeMillis();
         long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);
 
-        if (mCentralSurfaces.isInLaunchTransition()
-                || mKeyguardStateController.isFlingingToDismissKeyguard()) {
+        if (mKeyguardStateController.isFlingingToDismissKeyguard()) {
             final boolean wasFlingingToDismissKeyguard =
                     mKeyguardStateController.isFlingingToDismissKeyguard();
             mCentralSurfaces.fadeKeyguardAfterLaunchTransition(new Runnable() {
@@ -1015,19 +991,19 @@
             updateResources();
             return;
         }
-        boolean wasShowing = bouncerIsShowing();
-        boolean wasScrimmed = bouncerIsScrimmed();
+        boolean wasShowing = primaryBouncerIsShowing();
+        boolean wasScrimmed = primaryBouncerIsScrimmed();
 
         hideBouncer(true /* destroyView */);
-        mBouncer.prepare();
+        mPrimaryBouncer.prepare();
 
-        if (wasShowing) showBouncer(wasScrimmed);
+        if (wasShowing) showPrimaryBouncer(wasScrimmed);
     }
 
     public void onKeyguardFadedAway() {
         mNotificationContainer.postDelayed(() -> mNotificationShadeWindowController
                         .setKeyguardFadingAway(false), 100);
-        ViewGroupFadeHelper.reset(mNotificationPanelViewController.getView());
+        mNotificationPanelViewController.resetViewAlphas();
         mCentralSurfaces.finishKeyguardFadingAway();
         mBiometricUnlockController.finishKeyguardFadingAway();
         WindowManagerGlobal.getInstance().trimMemory(
@@ -1065,8 +1041,8 @@
      * WARNING: This method might cause Binder calls.
      */
     public boolean isSecure() {
-        if (mBouncer != null) {
-            return mBouncer.isSecure();
+        if (mPrimaryBouncer != null) {
+            return mPrimaryBouncer.isSecure();
         }
 
         return mKeyguardSecurityModel.getSecurityMode(
@@ -1080,7 +1056,7 @@
      * @return whether a back press can be handled right now.
      */
     public boolean canHandleBackPressed() {
-        return bouncerIsShowing();
+        return primaryBouncerIsShowing();
     }
 
     /**
@@ -1093,7 +1069,7 @@
 
         mCentralSurfaces.endAffordanceLaunch();
         // The second condition is for SIM card locked bouncer
-        if (bouncerIsScrimmed() && !needsFullscreenBouncer()) {
+        if (primaryBouncerIsScrimmed() && !needsFullscreenBouncer()) {
             hideBouncer(false);
             updateStates();
         } else {
@@ -1114,27 +1090,27 @@
 
     @Override
     public boolean isBouncerShowing() {
-        return bouncerIsShowing() || isShowingAlternateAuth();
+        return primaryBouncerIsShowing() || isShowingAlternateBouncer();
     }
 
     @Override
-    public boolean bouncerIsOrWillBeShowing() {
-        return isBouncerShowing() || isBouncerInTransit();
+    public boolean primaryBouncerIsOrWillBeShowing() {
+        return isBouncerShowing() || isPrimaryBouncerInTransit();
     }
 
     public boolean isFullscreenBouncer() {
-        if (mBouncerView.getDelegate() != null) {
-            return mBouncerView.getDelegate().isFullScreenBouncer();
+        if (mPrimaryBouncerView.getDelegate() != null) {
+            return mPrimaryBouncerView.getDelegate().isFullScreenBouncer();
         }
-        return mBouncer != null && mBouncer.isFullscreenBouncer();
+        return mPrimaryBouncer != null && mPrimaryBouncer.isFullscreenBouncer();
     }
 
     /**
      * Clear out any potential actions that were saved to run when the device is unlocked
      */
     public void cancelPostAuthActions() {
-        if (bouncerIsOrWillBeShowing()) {
-            return; // allow bouncer to trigger saved actions
+        if (primaryBouncerIsOrWillBeShowing()) {
+            return; // allow the primary bouncer to trigger saved actions
         }
         mAfterKeyguardGoneAction = null;
         mDismissActionWillAnimateOnKeyguard = false;
@@ -1173,25 +1149,25 @@
         }
         boolean showing = mKeyguardStateController.isShowing();
         boolean occluded = mKeyguardStateController.isOccluded();
-        boolean bouncerShowing = bouncerIsShowing();
-        boolean bouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing();
-        boolean bouncerDismissible = !isFullscreenBouncer();
+        boolean primaryBouncerShowing = primaryBouncerIsShowing();
+        boolean primaryBouncerIsOrWillBeShowing = primaryBouncerIsOrWillBeShowing();
+        boolean primaryBouncerDismissible = !isFullscreenBouncer();
         boolean remoteInputActive = mRemoteInputActive;
 
-        if ((bouncerDismissible || !showing || remoteInputActive) !=
-                (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
+        if ((primaryBouncerDismissible || !showing || remoteInputActive)
+                != (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
                 || mFirstUpdate) {
-            if (bouncerDismissible || !showing || remoteInputActive) {
-                if (mBouncer != null) {
-                    mBouncer.setBackButtonEnabled(true);
+            if (primaryBouncerDismissible || !showing || remoteInputActive) {
+                if (mPrimaryBouncer != null) {
+                    mPrimaryBouncer.setBackButtonEnabled(true);
                 } else {
-                    mBouncerInteractor.setBackButtonEnabled(true);
+                    mPrimaryBouncerInteractor.setBackButtonEnabled(true);
                 }
             } else {
-                if (mBouncer != null) {
-                    mBouncer.setBackButtonEnabled(false);
+                if (mPrimaryBouncer != null) {
+                    mPrimaryBouncer.setBackButtonEnabled(false);
                 } else {
-                    mBouncerInteractor.setBackButtonEnabled(false);
+                    mPrimaryBouncerInteractor.setBackButtonEnabled(false);
                 }
             }
         }
@@ -1202,23 +1178,26 @@
             updateNavigationBarVisibility(navBarVisible);
         }
 
-        if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
-            mNotificationShadeWindowController.setBouncerShowing(bouncerShowing);
-            mCentralSurfaces.setBouncerShowing(bouncerShowing);
+        boolean isPrimaryBouncerShowingChanged =
+            primaryBouncerShowing != mLastPrimaryBouncerShowing;
+        mLastPrimaryBouncerShowing = primaryBouncerShowing;
+
+        if (isPrimaryBouncerShowingChanged || mFirstUpdate) {
+            mNotificationShadeWindowController.setBouncerShowing(primaryBouncerShowing);
+            mCentralSurfaces.setBouncerShowing(primaryBouncerShowing);
         }
-        if (bouncerIsOrWillBeShowing != mLastBouncerIsOrWillBeShowing || mFirstUpdate
-                || bouncerShowing != mLastBouncerShowing) {
-            mKeyguardUpdateManager.sendKeyguardBouncerChanged(bouncerIsOrWillBeShowing,
-                    bouncerShowing);
+        if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing || mFirstUpdate
+                || isPrimaryBouncerShowingChanged) {
+            mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
+                    primaryBouncerShowing);
         }
 
         mFirstUpdate = false;
         mLastShowing = showing;
         mLastGlobalActionsVisible = mGlobalActionsVisible;
         mLastOccluded = occluded;
-        mLastBouncerShowing = bouncerShowing;
-        mLastBouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing;
-        mLastBouncerDismissible = bouncerDismissible;
+        mLastPrimaryBouncerIsOrWillBeShowing = primaryBouncerIsOrWillBeShowing;
+        mLastBouncerDismissible = primaryBouncerDismissible;
         mLastRemoteInputActive = remoteInputActive;
         mLastDozing = mDozing;
         mLastPulsing = mPulsing;
@@ -1262,7 +1241,7 @@
                 || mPulsing && !mIsDocked)
                 && mGesturalNav;
         return (!keyguardVisible && !hideWhileDozing && !mScreenOffAnimationPlaying
-                || bouncerIsShowing()
+                || primaryBouncerIsShowing()
                 || mRemoteInputActive
                 || keyguardWithGestureNav
                 || mGlobalActionsVisible);
@@ -1278,32 +1257,32 @@
                 && !mLastScreenOffAnimationPlaying || mLastPulsing && !mLastIsDocked)
                 && mLastGesturalNav;
         return (!keyguardShowing && !hideWhileDozing && !mLastScreenOffAnimationPlaying
-                || mLastBouncerShowing || mLastRemoteInputActive || keyguardWithGestureNav
+                || mLastPrimaryBouncerShowing || mLastRemoteInputActive || keyguardWithGestureNav
                 || mLastGlobalActionsVisible);
     }
 
     public boolean shouldDismissOnMenuPressed() {
-        if (mBouncerView.getDelegate() != null) {
-            return mBouncerView.getDelegate().shouldDismissOnMenuPressed();
+        if (mPrimaryBouncerView.getDelegate() != null) {
+            return mPrimaryBouncerView.getDelegate().shouldDismissOnMenuPressed();
         }
-        return mBouncer != null && mBouncer.shouldDismissOnMenuPressed();
+        return mPrimaryBouncer != null && mPrimaryBouncer.shouldDismissOnMenuPressed();
     }
 
     public boolean interceptMediaKey(KeyEvent event) {
-        if (mBouncerView.getDelegate() != null) {
-            return mBouncerView.getDelegate().interceptMediaKey(event);
+        if (mPrimaryBouncerView.getDelegate() != null) {
+            return mPrimaryBouncerView.getDelegate().interceptMediaKey(event);
         }
-        return mBouncer != null && mBouncer.interceptMediaKey(event);
+        return mPrimaryBouncer != null && mPrimaryBouncer.interceptMediaKey(event);
     }
 
     /**
      * @return true if the pre IME back event should be handled
      */
     public boolean dispatchBackKeyEventPreIme() {
-        if (mBouncerView.getDelegate() != null) {
-            return mBouncerView.getDelegate().dispatchBackKeyEventPreIme();
+        if (mPrimaryBouncerView.getDelegate() != null) {
+            return mPrimaryBouncerView.getDelegate().dispatchBackKeyEventPreIme();
         }
-        return mBouncer != null && mBouncer.dispatchBackKeyEventPreIme();
+        return mPrimaryBouncer != null && mPrimaryBouncer.dispatchBackKeyEventPreIme();
     }
 
     public void readyForKeyguardDone() {
@@ -1312,7 +1291,7 @@
 
     @Override
     public boolean shouldDisableWindowAnimationsForUnlock() {
-        return mCentralSurfaces.isInLaunchTransition();
+        return false;
     }
 
     @Override
@@ -1349,29 +1328,29 @@
      * fingerprint.
      */
     public void notifyKeyguardAuthenticated(boolean strongAuth) {
-        if (mBouncer != null) {
-            mBouncer.notifyKeyguardAuthenticated(strongAuth);
+        if (mPrimaryBouncer != null) {
+            mPrimaryBouncer.notifyKeyguardAuthenticated(strongAuth);
         } else {
-            mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
+            mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
         }
 
-        if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) {
-            resetAlternateAuth(false);
+        if (mAlternateBouncer != null && isShowingAlternateBouncer()) {
+            hideAlternateBouncer(false);
             executeAfterKeyguardGoneAction();
         }
     }
 
     /** Display security message to relevant KeyguardMessageArea. */
     public void setKeyguardMessage(String message, ColorStateList colorState) {
-        if (isShowingAlternateAuth()) {
+        if (isShowingAlternateBouncer()) {
             if (mKeyguardMessageAreaController != null) {
                 mKeyguardMessageAreaController.setMessage(message);
             }
         } else {
-            if (mBouncer != null) {
-                mBouncer.showMessage(message, colorState);
+            if (mPrimaryBouncer != null) {
+                mPrimaryBouncer.showMessage(message, colorState);
             } else {
-                mBouncerInteractor.showMessage(message, colorState);
+                mPrimaryBouncerInteractor.showMessage(message, colorState);
             }
         }
     }
@@ -1410,12 +1389,15 @@
         }
     }
 
-    public boolean bouncerNeedsScrimming() {
+    /**
+     * Whether the primary bouncer requires scrimming.
+     */
+    public boolean primaryBouncerNeedsScrimming() {
         // When a dream overlay is active, scrimming will cause any expansion to immediately expand.
         return (mKeyguardStateController.isOccluded()
                 && !mDreamOverlayStateController.isOverlayActive())
-                || bouncerWillDismissWithAction()
-                || (bouncerIsShowing() && bouncerIsScrimmed())
+                || primaryBouncerWillDismissWithAction()
+                || (primaryBouncerIsShowing() && primaryBouncerIsScrimmed())
                 || isFullscreenBouncer();
     }
 
@@ -1425,10 +1407,10 @@
      * configuration.
      */
     public void updateResources() {
-        if (mBouncer != null) {
-            mBouncer.updateResources();
+        if (mPrimaryBouncer != null) {
+            mPrimaryBouncer.updateResources();
         } else {
-            mBouncerInteractor.updateResources();
+            mPrimaryBouncerInteractor.updateResources();
         }
     }
 
@@ -1440,15 +1422,20 @@
         pw.println("  mAfterKeyguardGoneRunnables: " + mAfterKeyguardGoneRunnables);
         pw.println("  mPendingWakeupAction: " + mPendingWakeupAction);
         pw.println("  isBouncerShowing(): " + isBouncerShowing());
-        pw.println("  bouncerIsOrWillBeShowing(): " + bouncerIsOrWillBeShowing());
-
-        if (mBouncer != null) {
-            mBouncer.dump(pw);
+        pw.println("  bouncerIsOrWillBeShowing(): " + primaryBouncerIsOrWillBeShowing());
+        pw.println("  Registered KeyguardViewManagerCallbacks:");
+        for (KeyguardViewManagerCallback callback : mCallbacks) {
+            pw.println("      " + callback);
         }
 
-        if (mAlternateAuthInterceptor != null) {
-            pw.println("AltAuthInterceptor: ");
-            mAlternateAuthInterceptor.dump(pw);
+        if (mPrimaryBouncer != null) {
+            pw.println("PrimaryBouncer:");
+            mPrimaryBouncer.dump(pw);
+        }
+
+        if (mAlternateBouncer != null) {
+            pw.println("AlternateBouncer:");
+            mAlternateBouncer.dump(pw);
         }
     }
 
@@ -1465,6 +1452,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,44 +1477,35 @@
      */
     public void setQsExpansion(float qsExpansion) {
         mQsExpansion = qsExpansion;
-        if (mAlternateAuthInterceptor != null) {
-            mAlternateAuthInterceptor.setQsExpansion(qsExpansion);
+        for (KeyguardViewManagerCallback callback : mCallbacks) {
+            callback.onQSExpansionChanged(mQsExpansion);
         }
     }
 
     @Nullable
-    public KeyguardBouncer getBouncer() {
-        return mBouncer;
+    public KeyguardBouncer getPrimaryBouncer() {
+        return mPrimaryBouncer;
     }
 
-    public boolean isShowingAlternateAuth() {
-        return mAlternateAuthInterceptor != null
-                && mAlternateAuthInterceptor.isShowingAlternateAuthBouncer();
-    }
-
-    public boolean isShowingAlternateAuthOrAnimating() {
-        return mAlternateAuthInterceptor != null
-                && (mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()
-                || mAlternateAuthInterceptor.isAnimating());
+    public boolean isShowingAlternateBouncer() {
+        return mAlternateBouncer != null && mAlternateBouncer.isShowingAlternateBouncer();
     }
 
     /**
-     * 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. */
     public void updateKeyguardPosition(float x) {
-        if (mBouncer != null) {
-            mBouncer.updateKeyguardPosition(x);
+        if (mPrimaryBouncer != null) {
+            mPrimaryBouncer.updateKeyguardPosition(x);
         } else {
-            mBouncerInteractor.setKeyguardPosition(x);
+            mPrimaryBouncerInteractor.setKeyguardPosition(x);
         }
     }
 
@@ -1545,41 +1537,41 @@
      */
     public void requestFp(boolean request, int udfpsColor) {
         mKeyguardUpdateManager.requestFingerprintAuthOnOccludingApp(request);
-        if (mAlternateAuthInterceptor != null) {
-            mAlternateAuthInterceptor.requestUdfps(request, udfpsColor);
+        if (mAlternateBouncer != null) {
+            mAlternateBouncer.requestUdfps(request, udfpsColor);
         }
     }
 
     /**
      * Returns if bouncer expansion is between 0 and 1 non-inclusive.
      */
-    public boolean isBouncerInTransit() {
-        if (mBouncer != null) {
-            return mBouncer.inTransit();
+    public boolean isPrimaryBouncerInTransit() {
+        if (mPrimaryBouncer != null) {
+            return mPrimaryBouncer.inTransit();
         } else {
-            return mBouncerInteractor.isInTransit();
+            return mPrimaryBouncerInteractor.isInTransit();
         }
     }
 
     /**
      * Returns if bouncer is showing
      */
-    public boolean bouncerIsShowing() {
-        if (mBouncer != null) {
-            return mBouncer.isShowing();
+    public boolean primaryBouncerIsShowing() {
+        if (mPrimaryBouncer != null) {
+            return mPrimaryBouncer.isShowing();
         } else {
-            return mBouncerInteractor.isFullyShowing();
+            return mPrimaryBouncerInteractor.isFullyShowing();
         }
     }
 
     /**
      * Returns if bouncer is scrimmed
      */
-    public boolean bouncerIsScrimmed() {
-        if (mBouncer != null) {
-            return mBouncer.isScrimmed();
+    public boolean primaryBouncerIsScrimmed() {
+        if (mPrimaryBouncer != null) {
+            return mPrimaryBouncer.isScrimmed();
         } else {
-            return mBouncerInteractor.isScrimmed();
+            return mPrimaryBouncerInteractor.isScrimmed();
         }
     }
 
@@ -1587,10 +1579,10 @@
      * Returns if bouncer is animating away
      */
     public boolean bouncerIsAnimatingAway() {
-        if (mBouncer != null) {
-            return mBouncer.isAnimatingAway();
+        if (mPrimaryBouncer != null) {
+            return mPrimaryBouncer.isAnimatingAway();
         } else {
-            return mBouncerInteractor.isAnimatingAway();
+            return mPrimaryBouncerInteractor.isAnimatingAway();
         }
 
     }
@@ -1598,11 +1590,11 @@
     /**
      * Returns if bouncer will dismiss with action
      */
-    public boolean bouncerWillDismissWithAction() {
-        if (mBouncer != null) {
-            return mBouncer.willDismissWithAction();
+    public boolean primaryBouncerWillDismissWithAction() {
+        if (mPrimaryBouncer != null) {
+            return mPrimaryBouncer.willDismissWithAction();
         } else {
-            return mBouncerInteractor.willDismissWithAction();
+            return mPrimaryBouncerInteractor.willDismissWithAction();
         }
     }
 
@@ -1617,58 +1609,26 @@
     }
 
     /**
-     * Delegate used to send show/reset events to an alternate authentication method instead of the
-     * regular pin/pattern/password bouncer.
+     * Delegate used to send show and hide events to an alternate authentication method instead of
+     * the regular pin/pattern/password bouncer.
      */
-    public interface AlternateAuthInterceptor {
+    public interface AlternateBouncer {
         /**
          * Show alternate authentication bouncer.
          * @return whether alternate auth method was newly shown
          */
-        boolean showAlternateAuthBouncer();
+        boolean showAlternateBouncer();
 
         /**
          * Hide alternate authentication bouncer
          * @return whether the alternate auth method was newly hidden
          */
-        boolean hideAlternateAuthBouncer();
+        boolean hideAlternateBouncer();
 
         /**
          * @return true if the alternate auth bouncer is showing
          */
-        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();
+        boolean isShowingAlternateBouncer();
 
         /**
          * Use when an app occluding the keyguard would like to give the user ability to
@@ -1679,5 +1639,25 @@
          */
         void requestUdfps(boolean requestUdfps, int color);
 
+        /**
+         * print information for the alternate bouncer 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..05bf860 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));
     }
 
     /**
@@ -267,11 +260,11 @@
 
         if (showOverLockscreen) {
             mShadeController.addPostCollapseAction(runnable);
-            mShadeController.collapsePanel(true /* animate */);
+            mShadeController.collapseShade(true /* animate */);
         } else if (mKeyguardStateController.isShowing()
                 && mCentralSurfaces.isOccluded()) {
             mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
-            mShadeController.collapsePanel();
+            mShadeController.collapseShade();
         } else {
             runnable.run();
         }
@@ -413,7 +406,7 @@
 
     private void expandBubbleStack(NotificationEntry entry) {
         mBubblesManagerOptional.get().expandStackAndSelectBubble(entry);
-        mShadeController.collapsePanel();
+        mShadeController.collapseShade();
     }
 
     private void startNotificationIntent(
@@ -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
         }
     }
 
@@ -602,17 +593,9 @@
 
     private void collapseOnMainThread() {
         if (Looper.getMainLooper().isCurrentThread()) {
-            mShadeController.collapsePanel();
+            mShadeController.collapseShade();
         } else {
-            mMainThreadHandler.post(mShadeController::collapsePanel);
+            mMainThreadHandler.post(mShadeController::collapseShade);
         }
     }
-
-    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/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 70af77e..7fe01825 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -133,7 +133,7 @@
         if (!row.isPinned()) {
             mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
         }
-        mStatusBarKeyguardViewManager.showGenericBouncer(true /* scrimmed */);
+        mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
         mPendingRemoteInputView = clicked;
     }
 
@@ -180,7 +180,7 @@
                 }
             };
             mShadeController.postOnShadeExpanded(clickPendingViewRunnable);
-            mShadeController.instantExpandNotificationsPanel();
+            mShadeController.instantExpandShade();
         }
     }
 
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/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index d9c0293..2a039da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -34,6 +34,7 @@
 import com.android.systemui.R;
 import com.android.systemui.ScreenDecorations;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -68,12 +69,15 @@
     private int mDisplayCutoutTouchableRegionSize;
     private int mStatusBarHeight;
 
+    private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener;
+
     @Inject
     public StatusBarTouchableRegionManager(
             Context context,
             NotificationShadeWindowController notificationShadeWindowController,
             ConfigurationController configurationController,
             HeadsUpManagerPhone headsUpManager,
+            ShadeExpansionStateManager shadeExpansionStateManager,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController
     ) {
         mContext = context;
@@ -101,17 +105,7 @@
                         updateTouchableRegion();
                     }
                 });
-        mHeadsUpManager.addHeadsUpPhoneListener(
-                new HeadsUpManagerPhone.OnHeadsUpPhoneListenerChange() {
-                    @Override
-                    public void onHeadsUpGoingAwayStateChanged(boolean headsUpGoingAway) {
-                        if (!headsUpGoingAway) {
-                            updateTouchableRegionAfterLayout();
-                        } else {
-                            updateTouchableRegion();
-                        }
-                    }
-                });
+        mHeadsUpManager.addHeadsUpPhoneListener(this::onHeadsUpGoingAwayStateChanged);
 
         mNotificationShadeWindowController = notificationShadeWindowController;
         mNotificationShadeWindowController.setForcePluginOpenListener((forceOpen) -> {
@@ -119,6 +113,9 @@
         });
 
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+        shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
+
+        mOnComputeInternalInsetsListener = this::onComputeInternalInsets;
     }
 
     protected void setup(
@@ -136,17 +133,11 @@
         pw.println(mTouchableRegion);
     }
 
-    /**
-     * Notify that the status bar panel gets expanded or collapsed.
-     *
-     * @param isExpanded True to notify expanded, false to notify collapsed.
-     * TODO(b/237811427) replace with a listener
-     */
-    public void setPanelExpanded(boolean isExpanded) {
+    private void onShadeExpansionFullyChanged(Boolean isExpanded) {
         if (isExpanded != mIsStatusBarExpanded) {
             mIsStatusBarExpanded = isExpanded;
             if (isExpanded) {
-                // make sure our state is sane
+                // make sure our state is sensible
                 mForceCollapsedUntilLayout = false;
             }
             updateTouchableRegion();
@@ -260,18 +251,22 @@
                 || mUnlockedScreenOffAnimationController.isAnimationPlaying();
     }
 
-    private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener =
-            new OnComputeInternalInsetsListener() {
-        @Override
-        public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
-            if (shouldMakeEntireScreenTouchable()) {
-                return;
-            }
-
-            // Update touch insets to include any area needed for touching features that live in
-            // the status bar (ie: heads up notifications)
-            info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-            info.touchableRegion.set(calculateTouchableRegion());
+    private void onHeadsUpGoingAwayStateChanged(boolean headsUpGoingAway) {
+        if (!headsUpGoingAway) {
+            updateTouchableRegionAfterLayout();
+        } else {
+            updateTouchableRegion();
         }
-    };
+    }
+
+    private void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
+        if (shouldMakeEntireScreenTouchable()) {
+            return;
+        }
+
+        // Update touch insets to include any area needed for touching features that live in
+        // the status bar (ie: heads up notifications)
+        info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+        info.touchableRegion.set(calculateTouchableRegion());
+    }
 }
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/phone/SystemUIDialogManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java
index e7d9221..678c2d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java
@@ -87,7 +87,7 @@
 
     private void updateDialogListeners() {
         if (shouldHideAffordance()) {
-            mKeyguardViewController.resetAlternateAuth(true);
+            mKeyguardViewController.hideAlternateBouncer(true);
         }
 
         for (Listener listener : mListeners) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 0369845..344d233 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -28,13 +28,13 @@
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.biometrics.AuthRippleView;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.privacy.OngoingPrivacyChip;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.CombinedShadeHeadersConstraintManager;
 import com.android.systemui.shade.CombinedShadeHeadersConstraintManagerImpl;
 import com.android.systemui.shade.NotificationPanelView;
@@ -220,20 +220,22 @@
     @Named(LARGE_SCREEN_BATTERY_CONTROLLER)
     static BatteryMeterViewController getBatteryMeterViewController(
             @Named(SPLIT_SHADE_BATTERY_VIEW) BatteryMeterView batteryMeterView,
+            UserTracker userTracker,
             ConfigurationController configurationController,
             TunerService tunerService,
-            BroadcastDispatcher broadcastDispatcher,
             @Main Handler mainHandler,
             ContentResolver contentResolver,
+            FeatureFlags featureFlags,
             BatteryController batteryController
     ) {
         return new BatteryMeterViewController(
                 batteryMeterView,
+                userTracker,
                 configurationController,
                 tunerService,
-                broadcastDispatcher,
                 mainHandler,
                 contentResolver,
+                featureFlags,
                 batteryController);
 
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 41f1f95..730ecde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -21,7 +21,6 @@
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.dagger.qualifiers.RootView;
-import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.statusbar.HeadsUpStatusBarView;
 import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
@@ -29,8 +28,6 @@
 import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherControllerImpl;
 import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 
@@ -39,7 +36,6 @@
 
 import javax.inject.Named;
 
-import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.Multibinds;
@@ -126,21 +122,13 @@
     }
 
     /** */
-    @Binds
-    @StatusBarFragmentScope
-    StatusBarUserSwitcherController bindStatusBarUserSwitcherController(
-            StatusBarUserSwitcherControllerImpl controller);
-
-    /** */
     @Provides
     @StatusBarFragmentScope
     static PhoneStatusBarViewController providePhoneStatusBarViewController(
             PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory,
-            @RootView PhoneStatusBarView phoneStatusBarView,
-            NotificationPanelViewController notificationPanelViewController) {
+            @RootView PhoneStatusBarView phoneStatusBarView) {
         return phoneStatusBarViewControllerFactory.create(
-                phoneStatusBarView,
-                notificationPanelViewController.getStatusBarTouchEventHandler());
+                phoneStatusBarView);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
deleted file mode 100644
index f6b8cb0..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
+++ /dev/null
@@ -1,134 +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.statusbar.phone.userswitcher
-
-import android.graphics.drawable.Drawable
-import android.os.UserManager
-import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.policy.CallbackController
-import com.android.systemui.statusbar.policy.UserInfoController
-import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
-import java.io.PrintWriter
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-/**
- * Since every user switcher chip will user the exact same information and logic on whether or not
- * to show, and what data to show, it makes sense to create a single tracker here
- */
-@SysUISingleton
-class StatusBarUserInfoTracker @Inject constructor(
-    private val userInfoController: UserInfoController,
-    private val userManager: UserManager,
-    private val dumpManager: DumpManager,
-    @Main private val mainExecutor: Executor,
-    @Background private val backgroundExecutor: Executor
-) : CallbackController<CurrentUserChipInfoUpdatedListener>, Dumpable {
-    var currentUserName: String? = null
-        private set
-    var currentUserAvatar: Drawable? = null
-        private set
-    var userSwitcherEnabled = false
-        private set
-    private var listening = false
-
-    private val listeners = mutableListOf<CurrentUserChipInfoUpdatedListener>()
-
-    private val userInfoChangedListener = OnUserInfoChangedListener { name, picture, _ ->
-        currentUserAvatar = picture
-        currentUserName = name
-        notifyListenersUserInfoChanged()
-    }
-
-    init {
-        dumpManager.registerDumpable(TAG, this)
-    }
-
-    override fun addCallback(listener: CurrentUserChipInfoUpdatedListener) {
-        if (listeners.isEmpty()) {
-            startListening()
-        }
-
-        if (!listeners.contains(listener)) {
-            listeners.add(listener)
-        }
-    }
-
-    override fun removeCallback(listener: CurrentUserChipInfoUpdatedListener) {
-        listeners.remove(listener)
-
-        if (listeners.isEmpty()) {
-            stopListening()
-        }
-    }
-
-    private fun notifyListenersUserInfoChanged() {
-        listeners.forEach {
-            it.onCurrentUserChipInfoUpdated()
-        }
-    }
-
-    private fun notifyListenersSettingChanged() {
-        listeners.forEach {
-            it.onStatusBarUserSwitcherSettingChanged(userSwitcherEnabled)
-        }
-    }
-
-    private fun startListening() {
-        listening = true
-        userInfoController.addCallback(userInfoChangedListener)
-    }
-
-    private fun stopListening() {
-        listening = false
-        userInfoController.removeCallback(userInfoChangedListener)
-    }
-
-    /**
-     * Force a check to [UserManager.isUserSwitcherEnabled], and update listeners if the value has
-     * changed
-     */
-    fun checkEnabled() {
-        backgroundExecutor.execute {
-            // Check on a background thread to avoid main thread Binder calls
-            val wasEnabled = userSwitcherEnabled
-            userSwitcherEnabled = userManager.isUserSwitcherEnabled
-
-            if (wasEnabled != userSwitcherEnabled) {
-                mainExecutor.execute {
-                    notifyListenersSettingChanged()
-                }
-            }
-        }
-    }
-
-    override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println("  userSwitcherEnabled=$userSwitcherEnabled")
-        pw.println("  listening=$listening")
-    }
-}
-
-interface CurrentUserChipInfoUpdatedListener {
-    fun onCurrentUserChipInfoUpdated()
-    fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {}
-}
-
-private const val TAG = "StatusBarUserInfoTracker"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
deleted file mode 100644
index e498ae4..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
+++ /dev/null
@@ -1,112 +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.statusbar.phone.userswitcher
-
-import android.content.Intent
-import android.os.UserHandle
-import android.view.View
-import com.android.systemui.animation.Expandable
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.UserSwitcherActivity
-import com.android.systemui.util.ViewController
-
-import javax.inject.Inject
-
-/**
- * ViewController for [StatusBarUserSwitcherContainer]
- */
-class StatusBarUserSwitcherControllerImpl @Inject constructor(
-    view: StatusBarUserSwitcherContainer,
-    private val tracker: StatusBarUserInfoTracker,
-    private val featureController: StatusBarUserSwitcherFeatureController,
-    private val userSwitcherDialogController: UserSwitchDialogController,
-    private val featureFlags: FeatureFlags,
-    private val activityStarter: ActivityStarter,
-    private val falsingManager: FalsingManager
-) : ViewController<StatusBarUserSwitcherContainer>(view),
-        StatusBarUserSwitcherController {
-    private val listener = object : CurrentUserChipInfoUpdatedListener {
-        override fun onCurrentUserChipInfoUpdated() {
-            updateChip()
-        }
-
-        override fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {
-            updateEnabled()
-        }
-    }
-
-    private val featureFlagListener = object : OnUserSwitcherPreferenceChangeListener {
-        override fun onUserSwitcherPreferenceChange(enabled: Boolean) {
-            updateEnabled()
-        }
-    }
-
-    public override fun onViewAttached() {
-        tracker.addCallback(listener)
-        featureController.addCallback(featureFlagListener)
-        mView.setOnClickListener { view: View ->
-            if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                return@setOnClickListener
-            }
-
-            if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
-                val intent = Intent(context, UserSwitcherActivity::class.java)
-                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
-
-                activityStarter.startActivity(intent, true /* dismissShade */,
-                        null /* ActivityLaunchAnimator.Controller */,
-                        true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM)
-            } else {
-                userSwitcherDialogController.showDialog(view.context, Expandable.fromView(view))
-            }
-        }
-
-        updateEnabled()
-    }
-
-    override fun onViewDetached() {
-        tracker.removeCallback(listener)
-        featureController.removeCallback(featureFlagListener)
-        mView.setOnClickListener(null)
-    }
-
-    private fun updateChip() {
-        mView.text.text = tracker.currentUserName
-        mView.avatar.setImageDrawable(tracker.currentUserAvatar)
-    }
-
-    private fun updateEnabled() {
-        if (featureController.isStatusBarUserSwitcherFeatureEnabled() &&
-                tracker.userSwitcherEnabled) {
-            mView.visibility = View.VISIBLE
-            updateChip()
-        } else {
-            mView.visibility = View.GONE
-        }
-    }
-}
-
-interface StatusBarUserSwitcherController {
-    fun init()
-}
-
-private const val TAG = "SbUserSwitcherController"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
deleted file mode 100644
index 7bae9ff..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
+++ /dev/null
@@ -1,63 +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.statusbar.phone.userswitcher
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.policy.CallbackController
-
-import javax.inject.Inject
-
-@SysUISingleton
-class StatusBarUserSwitcherFeatureController @Inject constructor(
-    private val flags: FeatureFlags
-) : CallbackController<OnUserSwitcherPreferenceChangeListener> {
-    private val listeners = mutableListOf<OnUserSwitcherPreferenceChangeListener>()
-
-    init {
-        flags.addListener(Flags.STATUS_BAR_USER_SWITCHER) {
-            it.requestNoRestart()
-            notifyListeners()
-        }
-    }
-
-    fun isStatusBarUserSwitcherFeatureEnabled(): Boolean {
-        return flags.isEnabled(Flags.STATUS_BAR_USER_SWITCHER)
-    }
-
-    override fun addCallback(listener: OnUserSwitcherPreferenceChangeListener) {
-        if (!listeners.contains(listener)) {
-            listeners.add(listener)
-        }
-    }
-
-    override fun removeCallback(listener: OnUserSwitcherPreferenceChangeListener) {
-        listeners.remove(listener)
-    }
-
-    private fun notifyListeners() {
-        val enabled = flags.isEnabled(Flags.STATUS_BAR_USER_SWITCHER)
-        listeners.forEach {
-            it.onUserSwitcherPreferenceChange(enabled)
-        }
-    }
-}
-
-interface OnUserSwitcherPreferenceChangeListener {
-    fun onUserSwitcherPreferenceChange(enabled: Boolean)
-}
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..4d914fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -27,16 +27,31 @@
     /** 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
      * pipeline to help with debugging.
      */
-    // For now, just always apply the debug coloring if we've enabled the new icon.
-    fun useWifiDebugColoring(): Boolean = useNewWifiIcon()
+    fun useWifiDebugColoring(): Boolean =
+        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_ICONS_DEBUG_COLORING)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
index 7aa5ee1..8ff9198 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
@@ -23,9 +23,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.qs.SettingObserver
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
+import com.android.systemui.statusbar.pipeline.dagger.AirplaneTableLog
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -58,7 +59,7 @@
 constructor(
     @Background private val bgHandler: Handler,
     private val globalSettings: GlobalSettings,
-    logger: ConnectivityPipelineLogger,
+    @AirplaneTableLog logger: TableLogBuffer,
     @Application scope: CoroutineScope,
 ) : AirplaneModeRepository {
     // TODO(b/254848912): Replace this with a generic SettingObserver coroutine once we have it.
@@ -82,7 +83,12 @@
                 awaitClose { observer.isListening = false }
             }
             .distinctUntilChanged()
-            .logInputChange(logger, "isAirplaneMode")
+            .logDiffsForTable(
+                logger,
+                columnPrefix = "",
+                columnName = "isAirplaneMode",
+                initialValue = false
+            )
             .stateIn(
                 scope,
                 started = SharingStarted.WhileSubscribed(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
index fe30c01..4a5342e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
@@ -36,16 +36,20 @@
  * [com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository] for
  * more details.
  */
+interface AirplaneModeViewModel {
+    /** True if the airplane mode icon is currently visible in the status bar. */
+    val isAirplaneModeIconVisible: StateFlow<Boolean>
+}
+
 @SysUISingleton
-class AirplaneModeViewModel
+class AirplaneModeViewModelImpl
 @Inject
 constructor(
     interactor: AirplaneModeInteractor,
     logger: ConnectivityPipelineLogger,
     @Application private val scope: CoroutineScope,
-) {
-    /** True if the airplane mode icon is currently visible in the status bar. */
-    val isAirplaneModeIconVisible: StateFlow<Boolean> =
+) : AirplaneModeViewModel {
+    override val isAirplaneModeIconVisible: StateFlow<Boolean> =
         combine(interactor.isAirplaneMode, interactor.isForceHidden) {
                 isAirplaneMode,
                 isAirplaneIconForceHidden ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/AirplaneTableLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/AirplaneTableLog.kt
new file mode 100644
index 0000000..4f70f66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/AirplaneTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.dagger
+
+import javax.inject.Qualifier
+
+/** Airplane mode logs in table format. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class AirplaneTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index fcd1b8a..c350c78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,22 +16,34 @@
 
 package com.android.systemui.statusbar.pipeline.dagger
 
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileRepositorySwitcher
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 
 @Module
 abstract class StatusBarPipelineModule {
@@ -39,22 +51,49 @@
     abstract fun airplaneModeRepository(impl: AirplaneModeRepositoryImpl): AirplaneModeRepository
 
     @Binds
-    abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
+    abstract fun airplaneModeViewModel(impl: AirplaneModeViewModelImpl): AirplaneModeViewModel
 
     @Binds
-    abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+    abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
+
+    @Binds abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+
+    @Binds
+    abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
 
     @Binds
     abstract fun mobileConnectionsRepository(
-        impl: MobileConnectionsRepositoryImpl
+        impl: MobileRepositorySwitcher
     ): MobileConnectionsRepository
 
-    @Binds
-    abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
+    @Binds abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
 
-    @Binds
-    abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
+    @Binds abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
 
     @Binds
     abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
+
+    @Binds
+    @IntoMap
+    @ClassKey(MobileUiAdapter::class)
+    abstract fun bindFeature(impl: MobileUiAdapter): CoreStartable
+
+    @Module
+    companion object {
+        @JvmStatic
+        @Provides
+        @SysUISingleton
+        @WifiTableLog
+        fun provideWifiTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
+            return factory.create("WifiTableLog", 100)
+        }
+
+        @JvmStatic
+        @Provides
+        @SysUISingleton
+        @AirplaneTableLog
+        fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
+            return factory.create("AirplaneTableLog", 30)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt
new file mode 100644
index 0000000..ac395a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.dagger
+
+import javax.inject.Qualifier
+
+/** Wifi logs in table format. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class WifiTableLog
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
index da87f73..5479b92 100644
--- 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
@@ -20,6 +20,7 @@
 import android.telephony.TelephonyManager.DATA_CONNECTING
 import android.telephony.TelephonyManager.DATA_DISCONNECTED
 import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_UNKNOWN
 import android.telephony.TelephonyManager.DataState
 
 /** Internal enum representation of the telephony data connection states */
@@ -28,6 +29,7 @@
     Connecting(DATA_CONNECTING),
     Disconnected(DATA_DISCONNECTED),
     Disconnecting(DATA_DISCONNECTING),
+    Unknown(DATA_UNKNOWN),
 }
 
 fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
@@ -36,5 +38,6 @@
         DATA_CONNECTING -> DataConnectionState.Connecting
         DATA_DISCONNECTED -> DataConnectionState.Disconnected
         DATA_DISCONNECTING -> DataConnectionState.Disconnecting
-        else -> throw IllegalArgumentException("unknown data state received")
+        DATA_UNKNOWN -> DataConnectionState.Unknown
+        else -> throw IllegalArgumentException("unknown data state received $this")
     }
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/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 06e8f46..f094563 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,36 +16,13 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
-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
 import android.telephony.TelephonyManager
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-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 java.lang.IllegalStateException
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.asExecutor
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
 
 /**
  * Every mobile line of service can be identified via a [SubscriptionInfo] object. We set up a
@@ -65,137 +42,10 @@
      */
     val subscriptionModelFlow: Flow<MobileSubscriptionModel>
     /** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
-    val dataEnabled: Flow<Boolean>
-}
-
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-class MobileConnectionRepositoryImpl(
-    private val subId: Int,
-    private val telephonyManager: TelephonyManager,
-    bgDispatcher: CoroutineDispatcher,
-    logger: ConnectivityPipelineLogger,
-    scope: CoroutineScope,
-) : MobileConnectionRepository {
-    init {
-        if (telephonyManager.subscriptionId != subId) {
-            throw IllegalStateException(
-                "TelephonyManager should be created with subId($subId). " +
-                    "Found ${telephonyManager.subscriptionId} instead."
-            )
-        }
-    }
-
-    override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
-        var state = MobileSubscriptionModel()
-        conflatedCallbackFlow {
-                // TODO (b/240569788): log all of these into the connectivity logger
-                val callback =
-                    object :
-                        TelephonyCallback(),
-                        TelephonyCallback.ServiceStateListener,
-                        TelephonyCallback.SignalStrengthsListener,
-                        TelephonyCallback.DataConnectionStateListener,
-                        TelephonyCallback.DataActivityListener,
-                        TelephonyCallback.CarrierNetworkListener,
-                        TelephonyCallback.DisplayInfoListener {
-                        override fun onServiceStateChanged(serviceState: ServiceState) {
-                            state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
-                            trySend(state)
-                        }
-
-                        override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
-                            val cdmaLevel =
-                                signalStrength
-                                    .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
-                                    .let { strengths ->
-                                        if (!strengths.isEmpty()) {
-                                            strengths[0].level
-                                        } else {
-                                            CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
-                                        }
-                                    }
-
-                            val primaryLevel = signalStrength.level
-
-                            state =
-                                state.copy(
-                                    cdmaLevel = cdmaLevel,
-                                    primaryLevel = primaryLevel,
-                                    isGsm = signalStrength.isGsm,
-                                )
-                            trySend(state)
-                        }
-
-                        override fun onDataConnectionStateChanged(
-                            dataState: Int,
-                            networkType: Int
-                        ) {
-                            state =
-                                state.copy(dataConnectionState = dataState.toDataConnectionType())
-                            trySend(state)
-                        }
-
-                        override fun onDataActivity(direction: Int) {
-                            state = state.copy(dataActivityDirection = direction)
-                            trySend(state)
-                        }
-
-                        override fun onCarrierNetworkChange(active: Boolean) {
-                            state = state.copy(carrierNetworkChangeActive = active)
-                            trySend(state)
-                        }
-
-                        override fun onDisplayInfoChanged(
-                            telephonyDisplayInfo: TelephonyDisplayInfo
-                        ) {
-                            val networkType =
-                                if (
-                                    telephonyDisplayInfo.overrideNetworkType ==
-                                        OVERRIDE_NETWORK_TYPE_NONE
-                                ) {
-                                    DefaultNetworkType(telephonyDisplayInfo.networkType)
-                                } else {
-                                    OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType)
-                                }
-                            state = state.copy(resolvedNetworkType = networkType)
-                            trySend(state)
-                        }
-                    }
-                telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
-                awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
-            }
-            .logOutputChange(logger, "MobileSubscriptionModel")
-            .stateIn(scope, SharingStarted.WhileSubscribed(), state)
-    }
-
+    val dataEnabled: StateFlow<Boolean>
     /**
-     * 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.
+     * True if this connection represents the default subscription per
+     * [SubscriptionManager.getDefaultDataSubscriptionId]
      */
-    private val telephonyPollingEvent: Flow<Unit> = subscriptionModelFlow.map {}
-
-    override val dataEnabled: Flow<Boolean> = telephonyPollingEvent.map { dataConnectionAllowed() }
-
-    private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
-
-    class Factory
-    @Inject
-    constructor(
-        private val telephonyManager: TelephonyManager,
-        private val logger: ConnectivityPipelineLogger,
-        @Background private val bgDispatcher: CoroutineDispatcher,
-        @Application private val scope: CoroutineScope,
-    ) {
-        fun build(subId: Int): MobileConnectionRepository {
-            return MobileConnectionRepositoryImpl(
-                subId,
-                telephonyManager.createForSubscriptionId(subId),
-                bgDispatcher,
-                logger,
-                scope,
-            )
-        }
-    }
+    val isDefaultDataSubscription: StateFlow<Boolean>
 }
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..14200f0 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,37 +16,14 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
-import android.content.Context
-import android.content.IntentFilter
-import android.telephony.CarrierConfigManager
+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.annotation.VisibleForTesting
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.MobileMappings.Config
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-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.shared.ConnectivityPipelineLogger
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.asExecutor
-import kotlinx.coroutines.channels.awaitClose
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
 
 /**
  * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
@@ -57,145 +34,20 @@
     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
-}
 
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class MobileConnectionsRepositoryImpl
-@Inject
-constructor(
-    private val subscriptionManager: SubscriptionManager,
-    private val telephonyManager: TelephonyManager,
-    private val logger: ConnectivityPipelineLogger,
-    broadcastDispatcher: BroadcastDispatcher,
-    private val context: Context,
-    @Background private val bgDispatcher: CoroutineDispatcher,
-    @Application private val scope: CoroutineScope,
-    private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory
-) : MobileConnectionsRepository {
-    private val subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
-
-    /**
-     * State flow that emits the set of mobile data subscriptions, each represented by its own
-     * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
-     * info object, but for now we keep track of the infos themselves.
-     */
-    override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : SubscriptionManager.OnSubscriptionsChangedListener() {
-                        override fun onSubscriptionsChanged() {
-                            trySend(Unit)
-                        }
-                    }
-
-                subscriptionManager.addOnSubscriptionsChangedListener(
-                    bgDispatcher.asExecutor(),
-                    callback,
-                )
-
-                awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
-            }
-            .mapLatest { fetchSubscriptionsList() }
-            .onEach { infos -> dropUnusedReposFromCache(infos) }
-            .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
-
-    /** StateFlow that keeps track of the current active mobile data subscription */
-    override val activeMobileDataSubscriptionId: StateFlow<Int> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
-                        override fun onActiveDataSubscriptionIdChanged(subId: Int) {
-                            trySend(subId)
-                        }
-                    }
-
-                telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
-                awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
-            }
-            .stateIn(
-                scope,
-                started = SharingStarted.WhileSubscribed(),
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID
-            )
-
-    private val defaultDataSubChangedEvent =
-        broadcastDispatcher.broadcastFlow(
-            IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
-        )
-
-    private val carrierConfigChangedEvent =
-        broadcastDispatcher.broadcastFlow(
-            IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
-        )
-
-    /**
-     * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
-     * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
-     * config, so this will apply to every icon that we care about.
-     *
-     * Relevant bits in the config are things like
-     * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
-     *
-     * 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)
-            }
-            .stateIn(
-                scope,
-                SharingStarted.WhileSubscribed(),
-                initialValue = Config.readConfig(context)
-            )
-
-    override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
-        if (!isValidSubId(subId)) {
-            throw IllegalArgumentException(
-                "subscriptionId $subId is not in the list of valid subscriptions"
-            )
-        }
-
-        return subIdRepositoryCache[subId]
-            ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
-    }
-
-    private fun isValidSubId(subId: Int): Boolean {
-        subscriptionsFlow.value.forEach {
-            if (it.subscriptionId == subId) {
-                return true
-            }
-        }
-
-        return false
-    }
-
-    @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
-
-    private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
-        return mobileConnectionRepositoryFactory.build(subId)
-    }
-
-    private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
-        // Remove any connection repository from the cache that isn't in the new set of IDs. They
-        // will get garbage collected once their subscribers go away
-        val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }
-
-        subIdRepositoryCache.keys.forEach {
-            if (!currentValidSubscriptionIds.contains(it)) {
-                subIdRepositoryCache.remove(it)
-            }
-        }
-    }
-
-    private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
-        withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+    /** Observe changes to the [Settings.Global.MOBILE_DATA] setting */
+    val globalMobileDataSettingChangedEvent: Flow<Unit>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
new file mode 100644
index 0000000..e214005
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.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.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.os.Bundle
+import android.telephony.SubscriptionInfo
+import androidx.annotation.VisibleForTesting
+import com.android.settingslib.mobile.MobileMappings
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
+import javax.inject.Inject
+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.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * A provider for the [MobileConnectionsRepository] interface that can choose between the Demo and
+ * Prod concrete implementations at runtime. It works by defining a base flow, [activeRepo], which
+ * switches based on the latest information from [DemoModeController], and switches every flow in
+ * the interface to point to the currently-active provider. This allows us to put the demo mode
+ * interface in its own repository, completely separate from the real version, while still using all
+ * of the prod implementations for the rest of the pipeline (interactors and onward). Looks
+ * something like this:
+ *
+ * ```
+ * RealRepository
+ *                 │
+ *                 ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
+ *                 │
+ * DemoRepository
+ * ```
+ *
+ * NOTE: because the UI layer for mobile icons relies on a nested-repository structure, it is likely
+ * that we will have to drain the subscription list whenever demo mode changes. Otherwise if a real
+ * subscription list [1] is replaced with a demo subscription list [1], the view models will not see
+ * a change (due to `distinctUntilChanged`) and will not refresh their data providers to the demo
+ * implementation.
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class MobileRepositorySwitcher
+@Inject
+constructor(
+    @Application scope: CoroutineScope,
+    val realRepository: MobileConnectionsRepositoryImpl,
+    val demoMobileConnectionsRepository: DemoMobileConnectionsRepository,
+    demoModeController: DemoModeController,
+) : MobileConnectionsRepository {
+
+    val isDemoMode: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : DemoMode {
+                        override fun dispatchDemoCommand(command: String?, args: Bundle?) {
+                            // Nothing, we just care about on/off
+                        }
+
+                        override fun onDemoModeStarted() {
+                            demoMobileConnectionsRepository.startProcessingCommands()
+                            trySend(true)
+                        }
+
+                        override fun onDemoModeFinished() {
+                            demoMobileConnectionsRepository.stopProcessingCommands()
+                            trySend(false)
+                        }
+                    }
+
+                demoModeController.addCallback(callback)
+                awaitClose { demoModeController.removeCallback(callback) }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), demoModeController.isInDemoMode)
+
+    // Convenient definition flow for the currently active repo (based on demo mode or not)
+    @VisibleForTesting
+    internal val activeRepo: StateFlow<MobileConnectionsRepository> =
+        isDemoMode
+            .mapLatest { demoMode ->
+                if (demoMode) {
+                    demoMobileConnectionsRepository
+                } else {
+                    realRepository
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository)
+
+    override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
+        activeRepo
+            .flatMapLatest { it.subscriptionsFlow }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                realRepository.subscriptionsFlow.value
+            )
+
+    override val activeMobileDataSubscriptionId: StateFlow<Int> =
+        activeRepo
+            .flatMapLatest { it.activeMobileDataSubscriptionId }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                realRepository.activeMobileDataSubscriptionId.value
+            )
+
+    override val defaultDataSubRatConfig: StateFlow<MobileMappings.Config> =
+        activeRepo
+            .flatMapLatest { it.defaultDataSubRatConfig }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                realRepository.defaultDataSubRatConfig.value
+            )
+
+    override val defaultDataSubId: StateFlow<Int> =
+        activeRepo
+            .flatMapLatest { it.defaultDataSubId }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.defaultDataSubId.value)
+
+    override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
+        activeRepo
+            .flatMapLatest { it.defaultMobileNetworkConnectivity }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                realRepository.defaultMobileNetworkConnectivity.value
+            )
+
+    override val globalMobileDataSettingChangedEvent: Flow<Unit> =
+        activeRepo.flatMapLatest { it.globalMobileDataSettingChangedEvent }
+
+    override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
+        if (isDemoMode.value) {
+            return demoMobileConnectionsRepository.getRepoForSubId(subId)
+        }
+        return realRepository.getRepoForSubId(subId)
+    }
+}
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/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
new file mode 100644
index 0000000..5f2feb2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -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 com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
+
+import android.content.Context
+import android.telephony.Annotation
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED
+import android.telephony.TelephonyManager.NETWORK_TYPE_GSM
+import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_NR
+import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import android.util.Log
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.dagger.qualifiers.Application
+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.MobileConnectivityModel
+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.ResolvedNetworkType
+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.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** This repository vends out data based on demo mode commands */
+@OptIn(ExperimentalCoroutinesApi::class)
+class DemoMobileConnectionsRepository
+@Inject
+constructor(
+    private val dataSource: DemoModeMobileConnectionDataSource,
+    @Application private val scope: CoroutineScope,
+    context: Context,
+) : MobileConnectionsRepository {
+
+    private var demoCommandJob: Job? = null
+
+    private val connectionRepoCache = mutableMapOf<Int, DemoMobileConnectionRepository>()
+    private val subscriptionInfoCache = mutableMapOf<Int, SubscriptionInfo>()
+    val demoModeFinishedEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+
+    private val _subscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf())
+    override val subscriptionsFlow =
+        _subscriptions
+            .onEach { infos -> dropUnusedReposFromCache(infos) }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _subscriptions.value)
+
+    private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
+        // Remove any connection repository from the cache that isn't in the new set of IDs. They
+        // will get garbage collected once their subscribers go away
+        val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }
+
+        connectionRepoCache.keys.forEach {
+            if (!currentValidSubscriptionIds.contains(it)) {
+                connectionRepoCache.remove(it)
+            }
+        }
+    }
+
+    private fun maybeCreateSubscription(subId: Int) {
+        if (!subscriptionInfoCache.containsKey(subId)) {
+            createSubscriptionForSubId(subId, subId).also { subscriptionInfoCache[subId] = it }
+
+            _subscriptions.value = subscriptionInfoCache.values.toList()
+        }
+    }
+
+    /** Mimics the old NetworkControllerImpl for now */
+    private fun createSubscriptionForSubId(subId: Int, slotIndex: Int): SubscriptionInfo {
+        return SubscriptionInfo(
+            subId,
+            "",
+            slotIndex,
+            "",
+            "",
+            0,
+            0,
+            "",
+            0,
+            null,
+            null,
+            null,
+            "",
+            false,
+            null,
+            null,
+        )
+    }
+
+    // TODO(b/261029387): add a command for this value
+    override val activeMobileDataSubscriptionId =
+        subscriptionsFlow
+            .mapLatest { infos ->
+                // For now, active is just the first in the list
+                infos.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID
+            }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                subscriptionsFlow.value.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID
+            )
+
+    /** Demo mode doesn't currently support modifications to the mobile mappings */
+    override val defaultDataSubRatConfig =
+        MutableStateFlow(MobileMappings.Config.readConfig(context))
+
+    // TODO(b/261029387): add a command for this value
+    override val defaultDataSubId =
+        activeMobileDataSubscriptionId.stateIn(
+            scope,
+            SharingStarted.WhileSubscribed(),
+            INVALID_SUBSCRIPTION_ID
+        )
+
+    // TODO(b/261029387): not yet supported
+    override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel())
+
+    override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
+        return connectionRepoCache[subId]
+            ?: DemoMobileConnectionRepository(subId).also { connectionRepoCache[subId] = it }
+    }
+
+    override val globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
+
+    fun startProcessingCommands() {
+        demoCommandJob =
+            scope.launch {
+                dataSource.mobileEvents.filterNotNull().collect { event -> processEvent(event) }
+            }
+    }
+
+    fun stopProcessingCommands() {
+        demoCommandJob?.cancel()
+        _subscriptions.value = listOf()
+        connectionRepoCache.clear()
+        subscriptionInfoCache.clear()
+    }
+
+    private fun processEvent(event: FakeNetworkEventModel) {
+        when (event) {
+            is Mobile -> {
+                processEnabledMobileState(event)
+            }
+            is MobileDisabled -> {
+                processDisabledMobileState(event)
+            }
+        }
+    }
+
+    private fun processEnabledMobileState(state: Mobile) {
+        // get or create the connection repo, and set its values
+        val subId = state.subId ?: DEFAULT_SUB_ID
+        maybeCreateSubscription(subId)
+
+        val connection = getRepoForSubId(subId)
+        // This is always true here, because we split out disabled states at the data-source level
+        connection.dataEnabled.value = true
+        connection.isDefaultDataSubscription.value = state.dataType != null
+
+        connection.subscriptionModelFlow.value = state.toMobileSubscriptionModel()
+    }
+
+    private fun processDisabledMobileState(state: MobileDisabled) {
+        if (_subscriptions.value.isEmpty()) {
+            // Nothing to do here
+            return
+        }
+
+        val subId =
+            state.subId
+                ?: run {
+                    // For sake of usability, we can allow for no subId arg if there is only one
+                    // subscription
+                    if (_subscriptions.value.size > 1) {
+                        Log.d(
+                            TAG,
+                            "processDisabledMobileState: Unable to infer subscription to " +
+                                "disable. Specify subId using '-e slot <subId>'" +
+                                "Known subIds: [${subIdsString()}]"
+                        )
+                        return
+                    }
+
+                    // Use the only existing subscription as our arg, since there is only one
+                    _subscriptions.value[0].subscriptionId
+                }
+
+        removeSubscription(subId)
+    }
+
+    private fun removeSubscription(subId: Int) {
+        val currentSubscriptions = _subscriptions.value
+        subscriptionInfoCache.remove(subId)
+        _subscriptions.value = currentSubscriptions.filter { it.subscriptionId != subId }
+    }
+
+    private fun subIdsString(): String =
+        _subscriptions.value.joinToString(",") { it.subscriptionId.toString() }
+
+    companion object {
+        private const val TAG = "DemoMobileConnectionsRepo"
+
+        private const val DEFAULT_SUB_ID = 1
+    }
+}
+
+private fun Mobile.toMobileSubscriptionModel(): MobileSubscriptionModel {
+    return MobileSubscriptionModel(
+        isEmergencyOnly = false, // TODO(b/261029387): not yet supported
+        isGsm = false, // TODO(b/261029387): not yet supported
+        cdmaLevel = level ?: 0,
+        primaryLevel = level ?: 0,
+        dataConnectionState = DataConnectionState.Connected, // TODO(b/261029387): not yet supported
+        dataActivityDirection = activity,
+        carrierNetworkChangeActive = carrierNetworkChange,
+        // TODO(b/261185097): once mobile mappings can be mocked at this layer, we can build our
+        //  own demo map
+        resolvedNetworkType = dataType.toResolvedNetworkType()
+    )
+}
+
+@Annotation.NetworkType
+private fun SignalIcon.MobileIconGroup?.toNetworkType(): Int =
+    when (this) {
+        TelephonyIcons.THREE_G -> NETWORK_TYPE_GSM
+        TelephonyIcons.LTE -> NETWORK_TYPE_LTE
+        TelephonyIcons.FOUR_G -> NETWORK_TYPE_UMTS
+        TelephonyIcons.NR_5G -> NETWORK_TYPE_NR
+        TelephonyIcons.NR_5G_PLUS -> OVERRIDE_NETWORK_TYPE_NR_ADVANCED
+        else -> NETWORK_TYPE_UNKNOWN
+    }
+
+private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType =
+    when (this) {
+        TelephonyIcons.NR_5G_PLUS -> OverrideNetworkType(toNetworkType())
+        else -> DefaultNetworkType(toNetworkType())
+    }
+
+class DemoMobileConnectionRepository(val subId: Int) : MobileConnectionRepository {
+    override val subscriptionModelFlow = MutableStateFlow(MobileSubscriptionModel())
+
+    override val dataEnabled = MutableStateFlow(true)
+
+    override val isDefaultDataSubscription = MutableStateFlow(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
new file mode 100644
index 0000000..da55787
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.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.systemui.statusbar.pipeline.mobile.data.repository.demo
+
+import android.os.Bundle
+import android.telephony.Annotation.DataActivityType
+import android.telephony.TelephonyManager.DATA_ACTIVITY_IN
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
+import android.telephony.TelephonyManager.DATA_ACTIVITY_OUT
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+
+/**
+ * Data source that can map from demo mode commands to inputs into the
+ * [DemoMobileConnectionsRepository]'s flows
+ */
+@SysUISingleton
+class DemoModeMobileConnectionDataSource
+@Inject
+constructor(
+    demoModeController: DemoModeController,
+    @Application scope: CoroutineScope,
+) {
+    private val demoCommandStream: Flow<Bundle> = conflatedCallbackFlow {
+        val callback =
+            object : DemoMode {
+                override fun demoCommands(): List<String> = listOf(COMMAND_NETWORK)
+
+                override fun dispatchDemoCommand(command: String, args: Bundle) {
+                    trySend(args)
+                }
+
+                override fun onDemoModeFinished() {
+                    // Handled elsewhere
+                }
+
+                override fun onDemoModeStarted() {
+                    // Handled elsewhere
+                }
+            }
+
+        demoModeController.addCallback(callback)
+        awaitClose { demoModeController.removeCallback(callback) }
+    }
+
+    // If the args contains "mobile", then all of the args are relevant. It's just the way demo mode
+    // commands work and it's a little silly
+    private val _mobileCommands = demoCommandStream.map { args -> args.toMobileEvent() }
+    val mobileEvents = _mobileCommands.shareIn(scope, SharingStarted.WhileSubscribed())
+
+    private fun Bundle.toMobileEvent(): FakeNetworkEventModel? {
+        val mobile = getString("mobile") ?: return null
+        return if (mobile == "show") {
+            activeMobileEvent()
+        } else {
+            MobileDisabled(subId = getString("slot")?.toInt())
+        }
+    }
+
+    /** Parse a valid mobile command string into a network event */
+    private fun Bundle.activeMobileEvent(): Mobile {
+        // There are many key/value pairs supported by mobile demo mode. Bear with me here
+        val level = getString("level")?.toInt()
+        val dataType = getString("datatype")?.toDataType()
+        val slot = getString("slot")?.toInt()
+        val carrierId = getString("carrierid")?.toInt()
+        val inflateStrength = getString("inflate")?.toBoolean()
+        val activity = getString("activity")?.toActivity()
+        val carrierNetworkChange = getString("carriernetworkchange") == "show"
+
+        return Mobile(
+            level = level,
+            dataType = dataType,
+            subId = slot,
+            carrierId = carrierId,
+            inflateStrength = inflateStrength,
+            activity = activity,
+            carrierNetworkChange = carrierNetworkChange,
+        )
+    }
+}
+
+private fun String.toDataType(): MobileIconGroup =
+    when (this) {
+        "1x" -> TelephonyIcons.ONE_X
+        "3g" -> TelephonyIcons.THREE_G
+        "4g" -> TelephonyIcons.FOUR_G
+        "4g+" -> TelephonyIcons.FOUR_G_PLUS
+        "5g" -> TelephonyIcons.NR_5G
+        "5ge" -> TelephonyIcons.LTE_CA_5G_E
+        "5g+" -> TelephonyIcons.NR_5G_PLUS
+        "e" -> TelephonyIcons.E
+        "g" -> TelephonyIcons.G
+        "h" -> TelephonyIcons.H
+        "h+" -> TelephonyIcons.H_PLUS
+        "lte" -> TelephonyIcons.LTE
+        "lte+" -> TelephonyIcons.LTE_PLUS
+        "dis" -> TelephonyIcons.DATA_DISABLED
+        "not" -> TelephonyIcons.NOT_DEFAULT_DATA
+        else -> TelephonyIcons.UNKNOWN
+    }
+
+@DataActivityType
+private fun String.toActivity(): Int =
+    when (this) {
+        "inout" -> DATA_ACTIVITY_INOUT
+        "in" -> DATA_ACTIVITY_IN
+        "out" -> DATA_ACTIVITY_OUT
+        else -> DATA_ACTIVITY_NONE
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
new file mode 100644
index 0000000..3f3acaf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
@@ -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.systemui.statusbar.pipeline.mobile.data.repository.demo.model
+
+import android.telephony.Annotation.DataActivityType
+import com.android.settingslib.SignalIcon
+
+/**
+ * Model for the demo commands, ported from [NetworkControllerImpl]
+ *
+ * Nullable fields represent optional command line arguments
+ */
+sealed interface FakeNetworkEventModel {
+    data class Mobile(
+        val level: Int?,
+        val dataType: SignalIcon.MobileIconGroup?,
+        // Null means the default (chosen by the repository)
+        val subId: Int?,
+        val carrierId: Int?,
+        val inflateStrength: Boolean?,
+        @DataActivityType val activity: Int?,
+        val carrierNetworkChange: Boolean,
+    ) : FakeNetworkEventModel
+
+    data class MobileDisabled(
+        // Null means the default (chosen by the repository)
+        val subId: Int?
+    ) : FakeNetworkEventModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
new file mode 100644
index 0000000..4c1cf4a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -0,0 +1,235 @@
+/*
+ * 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.repository.prod
+
+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.TelephonyCallback
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
+import android.telephony.TelephonyManager
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+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.mobile.data.repository.MobileConnectionRepository
+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
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class MobileConnectionRepositoryImpl(
+    private val context: Context,
+    private val subId: Int,
+    private val telephonyManager: TelephonyManager,
+    private val globalSettings: GlobalSettings,
+    defaultDataSubId: StateFlow<Int>,
+    globalMobileDataSettingChangedEvent: Flow<Unit>,
+    bgDispatcher: CoroutineDispatcher,
+    logger: ConnectivityPipelineLogger,
+    scope: CoroutineScope,
+) : MobileConnectionRepository {
+    init {
+        if (telephonyManager.subscriptionId != subId) {
+            throw IllegalStateException(
+                "TelephonyManager should be created with subId($subId). " +
+                    "Found ${telephonyManager.subscriptionId} instead."
+            )
+        }
+    }
+
+    private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+
+    override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
+        var state = MobileSubscriptionModel()
+        conflatedCallbackFlow {
+                // TODO (b/240569788): log all of these into the connectivity logger
+                val callback =
+                    object :
+                        TelephonyCallback(),
+                        TelephonyCallback.ServiceStateListener,
+                        TelephonyCallback.SignalStrengthsListener,
+                        TelephonyCallback.DataConnectionStateListener,
+                        TelephonyCallback.DataActivityListener,
+                        TelephonyCallback.CarrierNetworkListener,
+                        TelephonyCallback.DisplayInfoListener {
+                        override fun onServiceStateChanged(serviceState: ServiceState) {
+                            state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+                            trySend(state)
+                        }
+
+                        override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+                            val cdmaLevel =
+                                signalStrength
+                                    .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
+                                    .let { strengths ->
+                                        if (!strengths.isEmpty()) {
+                                            strengths[0].level
+                                        } else {
+                                            CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+                                        }
+                                    }
+
+                            val primaryLevel = signalStrength.level
+
+                            state =
+                                state.copy(
+                                    cdmaLevel = cdmaLevel,
+                                    primaryLevel = primaryLevel,
+                                    isGsm = signalStrength.isGsm,
+                                )
+                            trySend(state)
+                        }
+
+                        override fun onDataConnectionStateChanged(
+                            dataState: Int,
+                            networkType: Int
+                        ) {
+                            state =
+                                state.copy(dataConnectionState = dataState.toDataConnectionType())
+                            trySend(state)
+                        }
+
+                        override fun onDataActivity(direction: Int) {
+                            state = state.copy(dataActivityDirection = direction)
+                            trySend(state)
+                        }
+
+                        override fun onCarrierNetworkChange(active: Boolean) {
+                            state = state.copy(carrierNetworkChangeActive = active)
+                            trySend(state)
+                        }
+
+                        override fun onDisplayInfoChanged(
+                            telephonyDisplayInfo: TelephonyDisplayInfo
+                        ) {
+                            val networkType =
+                                if (
+                                    telephonyDisplayInfo.overrideNetworkType ==
+                                        OVERRIDE_NETWORK_TYPE_NONE
+                                ) {
+                                    DefaultNetworkType(telephonyDisplayInfo.networkType)
+                                } else {
+                                    OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType)
+                                }
+                            state = state.copy(resolvedNetworkType = networkType)
+                            trySend(state)
+                        }
+                    }
+                telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+                awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+            }
+            .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,
+            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/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
new file mode 100644
index 0000000..08d6010
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -0,0 +1,265 @@
+/*
+ * 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.repository.prod
+
+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.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
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+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.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+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
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.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
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+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,
+    private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory
+) : MobileConnectionsRepository {
+    private val subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
+
+    /**
+     * State flow that emits the set of mobile data subscriptions, each represented by its own
+     * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
+     * info object, but for now we keep track of the infos themselves.
+     */
+    override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : SubscriptionManager.OnSubscriptionsChangedListener() {
+                        override fun onSubscriptionsChanged() {
+                            trySend(Unit)
+                        }
+                    }
+
+                subscriptionManager.addOnSubscriptionsChangedListener(
+                    bgDispatcher.asExecutor(),
+                    callback,
+                )
+
+                awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+            }
+            .mapLatest { fetchSubscriptionsList() }
+            .onEach { infos -> dropUnusedReposFromCache(infos) }
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
+
+    /** StateFlow that keeps track of the current active mobile data subscription */
+    override val activeMobileDataSubscriptionId: StateFlow<Int> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
+                        override fun onActiveDataSubscriptionIdChanged(subId: Int) {
+                            trySend(subId)
+                        }
+                    }
+
+                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,
+                SharingStarted.WhileSubscribed(),
+                SubscriptionManager.getDefaultDataSubscriptionId()
+            )
+
+    private val carrierConfigChangedEvent =
+        broadcastDispatcher.broadcastFlow(
+            IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
+        )
+
+    /**
+     * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
+     * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
+     * config, so this will apply to every icon that we care about.
+     *
+     * Relevant bits in the config are things like
+     * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
+     *
+     * This flow will produce whenever the default data subscription or the carrier config changes.
+     */
+    override val defaultDataSubRatConfig: StateFlow<Config> =
+        merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
+            .mapLatest { Config.readConfig(context) }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                initialValue = Config.readConfig(context)
+            )
+
+    override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
+        if (!isValidSubId(subId)) {
+            throw IllegalArgumentException(
+                "subscriptionId $subId is not in the list of valid subscriptions"
+            )
+        }
+
+        return subIdRepositoryCache[subId]
+            ?: 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) {
+                return true
+            }
+        }
+
+        return false
+    }
+
+    @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
+
+    private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
+        return mobileConnectionRepositoryFactory.build(
+            subId,
+            defaultDataSubId,
+            globalMobileDataSettingChangedEvent,
+        )
+    }
+
+    private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
+        // Remove any connection repository from the cache that isn't in the new set of IDs. They
+        // will get garbage collected once their subscribers go away
+        val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }
+
+        subIdRepositoryCache.keys.forEach {
+            if (!currentValidSubscriptionIds.contains(it)) {
+                subIdRepositoryCache.remove(it)
+            }
+        }
+    }
+
+    private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
+        withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+}
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 f99d278c..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,81 +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: Flow<Boolean>
+    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: Flow<Boolean> = connectionRepository.dataEnabled
+    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 614d583..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,6 +19,7 @@
 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
@@ -35,7 +36,9 @@
 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
 
 /**
@@ -51,12 +54,16 @@
 interface MobileIconsInteractor {
     /** List of subscriptions, potentially filtered for CBRS */
     val filteredSubscriptions: Flow<List<SubscriptionInfo>>
+    /** 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: Flow<Map<String, MobileIconGroup>>
+    val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
     /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
-    val defaultMobileIconGroup: Flow<MobileIconGroup>
+    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: Flow<Boolean>
+    val isUserSetup: StateFlow<Boolean>
     /**
      * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
      * subId. Will throw if the ID is invalid
@@ -79,6 +86,22 @@
     private val 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>> =
         mobileConnectionsRepo.subscriptionsFlow
 
@@ -132,22 +155,40 @@
      */
     override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
         mobileConnectionsRepo.defaultDataSubRatConfig
-            .map { mobileMappingsProxy.mapIconSets(it) }
+            .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> =
         mobileConnectionsRepo.defaultDataSubRatConfig
-            .map { mobileMappingsProxy.getDefaultIcons(it) }
+            .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,
             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..d9487bf 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
@@ -16,10 +16,12 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui
 
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 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
@@ -28,9 +30,10 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /**
  * This class is intended to provide a context to collect on the
@@ -49,8 +52,9 @@
     interactor: MobileIconsInteractor,
     private val iconController: StatusBarIconController,
     private val iconsViewModelFactory: MobileIconsViewModel.Factory,
-    @Application scope: CoroutineScope,
-) {
+    @Application private val scope: CoroutineScope,
+    private val statusBarPipelineFlags: StatusBarPipelineFlags,
+) : CoreStartable {
     private val mobileSubIds: Flow<List<Int>> =
         interactor.filteredSubscriptions.mapLatest { infos ->
             infos.map { subscriptionInfo -> subscriptionInfo.subscriptionId }
@@ -64,12 +68,19 @@
      * NOTE: this should go away as the view presenter learns more about this data pipeline
      */
     private val mobileSubIdsState: StateFlow<List<Int>> =
-        mobileSubIds
-            .onEach {
-                // Notify the icon controller here so that it knows to add icons
-                iconController.setNewMobileIconSubIds(it)
+        mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+    override fun start() {
+        // 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()) {
+            scope.launch {
+                mobileSubIds.collectLatest { iconController.setNewMobileIconSubIds(it) }
             }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+        }
+    }
 
     /**
      * Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
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 8131739..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,10 +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.mapLatest
 
 /**
  * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
@@ -39,29 +41,38 @@
  *
  * 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?> =
-        combine(iconInteractor.networkTypeIconGroup, iconInteractor.isDataEnabled) {
-            networkTypeIconGroup,
-            isDataEnabled ->
-            if (!isDataEnabled) {
+    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 =
@@ -72,5 +83,5 @@
             }
         }
 
-    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/mobile/util/MobileMappings.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
index 60bd038..501467f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
@@ -32,6 +32,7 @@
 interface MobileMappingsProxy {
     fun mapIconSets(config: Config): Map<String, MobileIconGroup>
     fun getDefaultIcons(config: Config): MobileIconGroup
+    fun getIconKey(displayInfo: TelephonyDisplayInfo): String
     fun toIconKey(@NetworkType networkType: Int): String
     fun toIconKeyOverride(@NetworkType networkType: Int): String
 }
@@ -44,6 +45,9 @@
     override fun getDefaultIcons(config: Config): MobileIconGroup =
         MobileMappings.getDefaultIcons(config)
 
+    override fun getIconKey(displayInfo: TelephonyDisplayInfo): String =
+        MobileMappings.getIconKey(displayInfo)
+
     override fun toIconKey(@NetworkType networkType: Int): String =
         MobileMappings.toIconKey(networkType)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
index 062c3d1..a682a57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
@@ -17,12 +17,34 @@
 package com.android.systemui.statusbar.pipeline.wifi.data.model
 
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.log.table.Diffable
 
 /** Provides information about the current wifi network. */
-sealed class WifiNetworkModel {
+sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
+
     /** A model representing that we have no active wifi network. */
     object Inactive : WifiNetworkModel() {
         override fun toString() = "WifiNetwork.Inactive"
+
+        override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+            if (prevVal is Inactive) {
+                return
+            }
+
+            if (prevVal is CarrierMerged) {
+                // The only difference between CarrierMerged and Inactive is the type
+                row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE)
+                return
+            }
+
+            // When changing from Active to Inactive, we need to log diffs to all the fields.
+            logFullNonActiveNetwork(TYPE_INACTIVE, row)
+        }
+
+        override fun logFull(row: TableRowLogger) {
+            logFullNonActiveNetwork(TYPE_INACTIVE, row)
+        }
     }
 
     /**
@@ -33,6 +55,21 @@
      */
     object CarrierMerged : WifiNetworkModel() {
         override fun toString() = "WifiNetwork.CarrierMerged"
+
+        override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+            if (prevVal is CarrierMerged) {
+                return
+            }
+
+            if (prevVal is Inactive) {
+                // The only difference between CarrierMerged and Inactive is the type.
+                row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)
+                return
+            }
+
+            // When changing from Active to CarrierMerged, we need to log diffs to all the fields.
+            logFullNonActiveNetwork(TYPE_CARRIER_MERGED, row)
+        }
     }
 
     /** Provides information about an active wifi network. */
@@ -76,6 +113,44 @@
             }
         }
 
+        override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+            if (prevVal !is Active) {
+                row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE)
+            }
+
+            if (prevVal !is Active || prevVal.networkId != networkId) {
+                row.logChange(COL_NETWORK_ID, networkId)
+            }
+            if (prevVal !is Active || prevVal.isValidated != isValidated) {
+                row.logChange(COL_VALIDATED, isValidated)
+            }
+            if (prevVal !is Active || prevVal.level != level) {
+                if (level != null) {
+                    row.logChange(COL_LEVEL, level)
+                } else {
+                    row.logChange(COL_LEVEL, LEVEL_DEFAULT)
+                }
+            }
+            if (prevVal !is Active || prevVal.ssid != ssid) {
+                row.logChange(COL_SSID, ssid)
+            }
+
+            // TODO(b/238425913): The passpoint-related values are frequently never used, so it
+            //   would be great to not log them when they're not used.
+            if (prevVal !is Active || prevVal.isPasspointAccessPoint != isPasspointAccessPoint) {
+                row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint)
+            }
+            if (prevVal !is Active ||
+                prevVal.isOnlineSignUpForPasspointAccessPoint !=
+                isOnlineSignUpForPasspointAccessPoint) {
+                row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint)
+            }
+            if (prevVal !is Active ||
+                prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) {
+                row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName)
+            }
+        }
+
         override fun toString(): String {
             // Only include the passpoint-related values in the string if we have them. (Most
             // networks won't have them so they'll be mostly clutter.)
@@ -101,4 +176,31 @@
             internal const val MAX_VALID_LEVEL = 4
         }
     }
+
+    internal fun logFullNonActiveNetwork(type: String, row: TableRowLogger) {
+        row.logChange(COL_NETWORK_TYPE, type)
+        row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
+        row.logChange(COL_VALIDATED, false)
+        row.logChange(COL_LEVEL, LEVEL_DEFAULT)
+        row.logChange(COL_SSID, null)
+        row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
+        row.logChange(COL_ONLINE_SIGN_UP, false)
+        row.logChange(COL_PASSPOINT_NAME, null)
+    }
 }
+
+const val TYPE_CARRIER_MERGED = "CarrierMerged"
+const val TYPE_INACTIVE = "Inactive"
+const val TYPE_ACTIVE = "Active"
+
+const val COL_NETWORK_TYPE = "type"
+const val COL_NETWORK_ID = "networkId"
+const val COL_VALIDATED = "isValidated"
+const val COL_LEVEL = "level"
+const val COL_SSID = "ssid"
+const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint"
+const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint"
+const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName"
+
+val LEVEL_DEFAULT: String? = null
+val NETWORK_ID_DEFAULT: String? = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 93448c1d..0c9c1cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -36,10 +36,14 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.ACTIVITY_PREFIX
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -82,6 +86,7 @@
     broadcastDispatcher: BroadcastDispatcher,
     connectivityManager: ConnectivityManager,
     logger: ConnectivityPipelineLogger,
+    @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
     @Main mainExecutor: Executor,
     @Application scope: CoroutineScope,
     wifiManager: WifiManager?,
@@ -105,7 +110,12 @@
             merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
                 .mapLatest { wifiManager.isWifiEnabled }
                 .distinctUntilChanged()
-                .logInputChange(logger, "enabled")
+                .logDiffsForTable(
+                    wifiTableLogBuffer,
+                    columnPrefix = "",
+                    columnName = "isWifiEnabled",
+                    initialValue = wifiManager.isWifiEnabled,
+                )
                 .stateIn(
                     scope = scope,
                     started = SharingStarted.WhileSubscribed(),
@@ -139,7 +149,12 @@
         awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
     }
         .distinctUntilChanged()
-        .logInputChange(logger, "isWifiDefault")
+        .logDiffsForTable(
+            wifiTableLogBuffer,
+            columnPrefix = "",
+            columnName = "isWifiDefault",
+            initialValue = false,
+        )
         .stateIn(
             scope,
             started = SharingStarted.WhileSubscribed(),
@@ -199,6 +214,12 @@
 
         awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
     }
+        .distinctUntilChanged()
+        .logDiffsForTable(
+            wifiTableLogBuffer,
+            columnPrefix = "wifiNetwork",
+            initialValue = WIFI_NETWORK_DEFAULT,
+        )
         // There will be multiple wifi icons in different places that will frequently
         // subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures that
         // new subscribes will get the latest value immediately upon subscription. Otherwise, the
@@ -223,6 +244,11 @@
                     awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
                 }
             }
+                .logDiffsForTable(
+                    wifiTableLogBuffer,
+                    columnPrefix = ACTIVITY_PREFIX,
+                    initialValue = ACTIVITY_DEFAULT,
+                )
                 .stateIn(
                     scope,
                     started = SharingStarted.WhileSubscribed(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 3a3e611..ec935fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -34,16 +34,36 @@
  * This interactor processes information from our data layer into information that the UI layer can
  * use.
  */
-@SysUISingleton
-class WifiInteractor @Inject constructor(
-    connectivityRepository: ConnectivityRepository,
-    wifiRepository: WifiRepository,
-) {
+interface WifiInteractor {
     /**
      * The SSID (service set identifier) of the wifi network. Null if we don't have a network, or
      * have a network but no valid SSID.
      */
-    val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
+    val ssid: Flow<String?>
+
+    /** Our current enabled status. */
+    val isEnabled: Flow<Boolean>
+
+    /** Our current default status. */
+    val isDefault: Flow<Boolean>
+
+    /** Our current wifi network. See [WifiNetworkModel]. */
+    val wifiNetwork: Flow<WifiNetworkModel>
+
+    /** Our current wifi activity. See [WifiActivityModel]. */
+    val activity: StateFlow<WifiActivityModel>
+
+    /** True if we're configured to force-hide the wifi icon and false otherwise. */
+    val isForceHidden: Flow<Boolean>
+}
+
+@SysUISingleton
+class WifiInteractorImpl @Inject constructor(
+    connectivityRepository: ConnectivityRepository,
+    wifiRepository: WifiRepository,
+) : WifiInteractor {
+
+    override val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
         when (info) {
             is WifiNetworkModel.Inactive -> null
             is WifiNetworkModel.CarrierMerged -> null
@@ -56,20 +76,15 @@
         }
     }
 
-    /** Our current enabled status. */
-    val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
+    override val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
 
-    /** Our current default status. */
-    val isDefault: Flow<Boolean> = wifiRepository.isWifiDefault
+    override val isDefault: Flow<Boolean> = wifiRepository.isWifiDefault
 
-    /** Our current wifi network. See [WifiNetworkModel]. */
-    val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
+    override val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
 
-    /** Our current wifi activity. See [WifiActivityModel]. */
-    val activity: StateFlow<WifiActivityModel> = wifiRepository.wifiActivity
+    override val activity: StateFlow<WifiActivityModel> = wifiRepository.wifiActivity
 
-    /** True if we're configured to force-hide the wifi icon and false otherwise. */
-    val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
+    override val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
         it.contains(ConnectivitySlot.WIFI)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
index 5746106..a4ca41c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
@@ -16,10 +16,32 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.shared.model
 
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
 /** Provides information on the current wifi activity. */
 data class WifiActivityModel(
     /** True if the wifi has activity in (download). */
     val hasActivityIn: Boolean,
     /** True if the wifi has activity out (upload). */
     val hasActivityOut: Boolean,
-)
+) : Diffable<WifiActivityModel> {
+
+    override fun logDiffs(prevVal: WifiActivityModel, row: TableRowLogger) {
+        if (prevVal.hasActivityIn != hasActivityIn) {
+            row.logChange(COL_ACTIVITY_IN, hasActivityIn)
+        }
+        if (prevVal.hasActivityOut != hasActivityOut) {
+            row.logChange(COL_ACTIVITY_OUT, hasActivityOut)
+        }
+    }
+
+    override fun logFull(row: TableRowLogger) {
+        row.logChange(COL_ACTIVITY_IN, hasActivityIn)
+        row.logChange(COL_ACTIVITY_OUT, hasActivityOut)
+    }
+}
+
+const val ACTIVITY_PREFIX = "wifiActivity"
+private const val COL_ACTIVITY_IN = "in"
+private const val COL_ACTIVITY_OUT = "out"
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..5223760
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.model.WifiIcon
+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 is WifiIcon.Visible && 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..f5b5950 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,8 @@
 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.model.WifiIcon
 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 +61,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 {
@@ -111,8 +93,10 @@
 
                 launch {
                     viewModel.wifiIcon.collect { wifiIcon ->
-                        view.isVisible = wifiIcon != null
-                        wifiIcon?.let { IconViewBinder.bind(wifiIcon, iconView) }
+                        view.isVisible = wifiIcon is WifiIcon.Visible
+                        if (wifiIcon is WifiIcon.Visible) {
+                            IconViewBinder.bind(wifiIcon.icon, iconView)
+                        }
                     }
                 }
 
@@ -154,7 +138,7 @@
 
         return object : Binding {
             override fun getShouldIconBeVisible(): Boolean {
-                return viewModel.wifiIcon.value != null
+                return viewModel.wifiIcon.value is WifiIcon.Visible
             }
 
             override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
new file mode 100644
index 0000000..e491d2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.model
+
+import android.annotation.DrawableRes
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
+/** Represents the various states of the wifi icon. */
+sealed interface WifiIcon : Diffable<WifiIcon> {
+    /** Represents a wifi icon that should be hidden (not visible). */
+    object Hidden : WifiIcon {
+        override fun toString() = "hidden"
+    }
+
+    /**
+     * Represents a visible wifi icon that uses [res] as its image and [contentDescription] as its
+     * description.
+     */
+    class Visible(
+        @DrawableRes res: Int,
+        val contentDescription: ContentDescription.Loaded,
+    ) : WifiIcon {
+        val icon = Icon.Resource(res, contentDescription)
+
+        override fun toString() = contentDescription.description.toString()
+    }
+
+    override fun logDiffs(prevVal: WifiIcon, row: TableRowLogger) {
+        if (prevVal.toString() != toString()) {
+            row.logChange(COL_ICON, toString())
+        }
+    }
+
+    override fun logFull(row: TableRowLogger) {
+        row.logChange(COL_ICON, toString())
+    }
+}
+
+private const val COL_ICON = "wifiIcon"
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/HomeWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
index 95ab251..a29c9b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
@@ -28,7 +28,7 @@
  */
 class HomeWifiViewModel(
     statusBarPipelineFlags: StatusBarPipelineFlags,
-    wifiIcon: StateFlow<Icon.Resource?>,
+    wifiIcon: StateFlow<WifiIcon>,
     isActivityInViewVisible: Flow<Boolean>,
     isActivityOutViewVisible: Flow<Boolean>,
     isActivityContainerVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
index 86535d6..1e190fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
@@ -17,15 +17,15 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
 /** A view model for the wifi icon shown on keyguard (lockscreen). */
 class KeyguardWifiViewModel(
     statusBarPipelineFlags: StatusBarPipelineFlags,
-    wifiIcon: StateFlow<Icon.Resource?>,
+    wifiIcon: StateFlow<WifiIcon>,
     isActivityInViewVisible: Flow<Boolean>,
     isActivityOutViewVisible: Flow<Boolean>,
     isActivityContainerVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index 7cbdf5d..e35a8fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flowOf
@@ -33,8 +33,8 @@
     statusBarPipelineFlags: StatusBarPipelineFlags,
     debugTint: Int,
 
-    /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
-    val wifiIcon: StateFlow<Icon.Resource?>,
+    /** The wifi icon that should be displayed. */
+    val wifiIcon: StateFlow<WifiIcon>,
 
     /** True if the activity in view should be visible. */
     val isActivityInViewVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
index fd54c5f..18e62b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
@@ -17,15 +17,15 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
 /** A view model for the wifi icon shown in quick settings (when the shade is pulled down). */
 class QsWifiViewModel(
     statusBarPipelineFlags: StatusBarPipelineFlags,
-    wifiIcon: StateFlow<Icon.Resource?>,
+    wifiIcon: StateFlow<WifiIcon>,
     isActivityInViewVisible: Flow<Boolean>,
     isActivityOutViewVisible: Flow<Boolean>,
     isActivityContainerVisible: Flow<Boolean>,
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..ec7ba65 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
@@ -17,16 +17,18 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.content.Context
-import androidx.annotation.DrawableRes
 import androidx.annotation.StringRes
 import androidx.annotation.VisibleForTesting
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
 import com.android.systemui.R
 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.statusbar.pipeline.dagger.WifiTableLog
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
@@ -71,50 +73,39 @@
     connectivityConstants: ConnectivityConstants,
     private val context: Context,
     logger: ConnectivityPipelineLogger,
+    @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
     interactor: WifiInteractor,
     @Application private val scope: CoroutineScope,
     statusBarPipelineFlags: StatusBarPipelineFlags,
     wifiConstants: WifiConstants,
 ) {
-    /**
-     * Returns the drawable resource ID to use for the wifi icon based on the given network.
-     * Null if we can't compute the icon.
-     */
-    @DrawableRes
-    private fun WifiNetworkModel.iconResId(): Int? {
+    /** Returns the icon to use based on the given network. */
+    private fun WifiNetworkModel.icon(): WifiIcon {
         return when (this) {
-            is WifiNetworkModel.CarrierMerged -> null
-            is WifiNetworkModel.Inactive -> WIFI_NO_NETWORK
-            is WifiNetworkModel.Active ->
-                when {
-                    this.level == null -> null
-                    this.isValidated -> WIFI_FULL_ICONS[this.level]
-                    else -> WIFI_NO_INTERNET_ICONS[this.level]
-                }
-        }
-    }
-
-    /**
-     * Returns the content description for the wifi icon based on the given network.
-     * Null if we can't compute the content description.
-     */
-    private fun WifiNetworkModel.contentDescription(): ContentDescription? {
-        return when (this) {
-            is WifiNetworkModel.CarrierMerged -> null
-            is WifiNetworkModel.Inactive ->
+            is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden
+            is WifiNetworkModel.Inactive -> WifiIcon.Visible(
+                res = WIFI_NO_NETWORK,
                 ContentDescription.Loaded(
                     "${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
                 )
+            )
             is WifiNetworkModel.Active ->
                 when (this.level) {
-                    null -> null
+                    null -> WifiIcon.Hidden
                     else -> {
                         val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
                         when {
-                            this.isValidated -> ContentDescription.Loaded(levelDesc)
+                            this.isValidated ->
+                                WifiIcon.Visible(
+                                    WIFI_FULL_ICONS[this.level],
+                                    ContentDescription.Loaded(levelDesc)
+                                )
                             else ->
-                                ContentDescription.Loaded(
-                                    "$levelDesc,${context.getString(NO_INTERNET)}"
+                                WifiIcon.Visible(
+                                    WIFI_NO_INTERNET_ICONS[this.level],
+                                    ContentDescription.Loaded(
+                                        "$levelDesc,${context.getString(NO_INTERNET)}"
+                                    )
                                 )
                         }
                     }
@@ -122,8 +113,8 @@
         }
     }
 
-    /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
-    private val wifiIcon: StateFlow<Icon.Resource?> =
+    /** The wifi icon that should be displayed. */
+    private val wifiIcon: StateFlow<WifiIcon> =
         combine(
             interactor.isEnabled,
             interactor.isDefault,
@@ -131,21 +122,29 @@
             interactor.wifiNetwork,
         ) { isEnabled, isDefault, isForceHidden, wifiNetwork ->
             if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
-                return@combine null
+                return@combine WifiIcon.Hidden
             }
 
-            val iconResId = wifiNetwork.iconResId() ?: return@combine null
-            val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription())
+            val icon = wifiNetwork.icon()
 
             return@combine when {
                 isDefault -> icon
                 wifiConstants.alwaysShowIconIfEnabled -> icon
                 !connectivityConstants.hasDataCapabilities -> icon
                 wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
-                else -> null
+                else -> WifiIcon.Hidden
             }
         }
-        .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
+            .logDiffsForTable(
+                wifiTableLogBuffer,
+                columnPrefix = "",
+                initialValue = WifiIcon.Hidden,
+            )
+            .stateIn(
+                scope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = WifiIcon.Hidden
+            )
 
     /** 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/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index cf4106c..68d30d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -21,7 +21,6 @@
 import android.graphics.ColorMatrix
 import android.graphics.ColorMatrixColorFilter
 import android.graphics.drawable.Drawable
-import android.os.UserHandle
 import android.widget.BaseAdapter
 import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
 import com.android.systemui.user.data.source.UserRecord
@@ -84,7 +83,7 @@
     }
 
     fun refresh() {
-        controller.refreshUsers(UserHandle.USER_NULL)
+        controller.refreshUsers()
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 149ed0a..d10d7cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -155,6 +155,9 @@
 
         default void onWirelessChargingChanged(boolean isWirlessCharging) {
         }
+
+        default void onIsOverheatedChanged(boolean isOverheated) {
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index c7ad767..2ee5232 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.os.BatteryManager.BATTERY_HEALTH_OVERHEAT;
+import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
+import static android.os.BatteryManager.EXTRA_HEALTH;
 import static android.os.BatteryManager.EXTRA_PRESENT;
 
 import android.annotation.WorkerThread;
@@ -87,6 +90,7 @@
     protected boolean mPowerSave;
     private boolean mAodPowerSave;
     private boolean mWirelessCharging;
+    private boolean mIsOverheated = false;
     private boolean mTestMode = false;
     @VisibleForTesting
     boolean mHasReceivedBattery = false;
@@ -152,6 +156,7 @@
         pw.print("  mPluggedIn="); pw.println(mPluggedIn);
         pw.print("  mCharging="); pw.println(mCharging);
         pw.print("  mCharged="); pw.println(mCharged);
+        pw.print("  mIsOverheated="); pw.println(mIsOverheated);
         pw.print("  mPowerSave="); pw.println(mPowerSave);
         pw.print("  mStateUnknown="); pw.println(mStateUnknown);
     }
@@ -184,6 +189,7 @@
         cb.onPowerSaveChanged(mPowerSave);
         cb.onBatteryUnknownStateChanged(mStateUnknown);
         cb.onWirelessChargingChanged(mWirelessCharging);
+        cb.onIsOverheatedChanged(mIsOverheated);
     }
 
     @Override
@@ -222,6 +228,13 @@
                 fireBatteryUnknownStateChanged();
             }
 
+            int batteryHealth = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
+            boolean isOverheated = batteryHealth == BATTERY_HEALTH_OVERHEAT;
+            if (isOverheated != mIsOverheated) {
+                mIsOverheated = isOverheated;
+                fireIsOverheatedChanged();
+            }
+
             fireBatteryLevelChanged();
         } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
             updatePowerSave();
@@ -292,6 +305,10 @@
         return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS;
     }
 
+    public boolean isOverheated() {
+        return mIsOverheated;
+    }
+
     @Override
     public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
         // Need to fetch or refresh the estimate, but it may involve binder calls so offload the
@@ -402,6 +419,15 @@
         }
     }
 
+    private void fireIsOverheatedChanged() {
+        synchronized (mChangeCallbacks) {
+            final int n = mChangeCallbacks.size();
+            for (int i = 0; i < n; i++) {
+                mChangeCallbacks.get(i).onIsOverheatedChanged(mIsOverheated);
+            }
+        }
+    }
+
     @Override
     public void dispatchDemoCommand(String command, Bundle args) {
         if (!mDemoModeController.isInDemoMode()) {
@@ -412,6 +438,7 @@
         String plugged = args.getString("plugged");
         String powerSave = args.getString("powersave");
         String present = args.getString("present");
+        String overheated = args.getString("overheated");
         if (level != null) {
             mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
         }
@@ -426,6 +453,10 @@
             mStateUnknown = !present.equals("true");
             fireBatteryUnknownStateChanged();
         }
+        if (overheated != null) {
+            mIsOverheated = overheated.equals("true");
+            fireIsOverheatedChanged();
+        }
         fireBatteryLevelChanged();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index aae0f93..acdf0d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.policy;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
@@ -41,6 +40,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -83,6 +83,7 @@
     @Inject
     public BluetoothControllerImpl(
             Context context,
+            UserTracker userTracker,
             DumpManager dumpManager,
             BluetoothLogger logger,
             @Background Looper bgLooper,
@@ -100,7 +101,7 @@
                     mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
         }
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        mCurrentUser = ActivityManager.getCurrentUser();
+        mCurrentUser = userTracker.getUserId();
         mDumpManager.registerDumpable(TAG, this);
     }
 
@@ -145,7 +146,13 @@
     }
 
     private String getDeviceString(CachedBluetoothDevice device) {
-        return device.getName() + " " + device.getBondState() + " " + device.isConnected();
+        return device.getName()
+                + " bondState=" + device.getBondState()
+                + " connected=" + device.isConnected()
+                + " active[A2DP]=" + device.isActiveDevice(BluetoothProfile.A2DP)
+                + " active[HEADSET]=" + device.isActiveDevice(BluetoothProfile.HEADSET)
+                + " active[HEARING_AID]=" + device.isActiveDevice(BluetoothProfile.HEARING_AID)
+                + " active[LE_AUDIO]=" + device.isActiveDevice(BluetoothProfile.LE_AUDIO);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 576962d..6875b52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.annotation.NonNull;
 import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -49,7 +50,7 @@
 import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -79,7 +80,7 @@
     private static final String SHOW_SECONDS = "show_seconds";
     private static final String VISIBILITY = "visibility";
 
-    private final CurrentUserTracker mCurrentUserTracker;
+    private final UserTracker mUserTracker;
     private final CommandQueue mCommandQueue;
     private int mCurrentUserId;
 
@@ -114,6 +115,15 @@
 
     private final BroadcastDispatcher mBroadcastDispatcher;
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    mCurrentUserId = newUser;
+                    updateClock();
+                }
+            };
+
     public Clock(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -132,12 +142,7 @@
             a.recycle();
         }
         mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
-        mCurrentUserTracker = new CurrentUserTracker(mBroadcastDispatcher) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                mCurrentUserId = newUserId;
-            }
-        };
+        mUserTracker = Dependency.get(UserTracker.class);
     }
 
     @Override
@@ -186,7 +191,6 @@
             filter.addAction(Intent.ACTION_TIME_CHANGED);
             filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
             filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
-            filter.addAction(Intent.ACTION_USER_SWITCHED);
 
             // NOTE: This receiver could run before this method returns, as it's not dispatching
             // on the main thread and BroadcastDispatcher may not need to register with Context.
@@ -196,8 +200,8 @@
             Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
                     StatusBarIconController.ICON_HIDE_LIST);
             mCommandQueue.addCallback(this);
-            mCurrentUserTracker.startTracking();
-            mCurrentUserId = mCurrentUserTracker.getCurrentUserId();
+            mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+            mCurrentUserId = mUserTracker.getUserId();
         }
 
         // The time zone may have changed while the receiver wasn't registered, so update the Time
@@ -227,7 +231,7 @@
             mAttached = false;
             Dependency.get(TunerService.class).removeTunable(this);
             mCommandQueue.removeCallback(this);
-            mCurrentUserTracker.stopTracking();
+            mUserTracker.removeCallback(mUserChangedCallback);
         }
     }
 
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..6c66f0b 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
     }
@@ -140,6 +140,9 @@
                         // is out of sync, perhaps through a device restore, and update the
                         // preference
                         addPackageToSeededSet(prefs, pkg)
+                    } else if (it.panelActivity != null) {
+                        // Do not seed for packages with panels
+                        addPackageToSeededSet(prefs, pkg)
                     } else {
                         componentsToSeed.add(it.componentName)
                     }
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/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
index 4c6c7e0..3d0e69c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
@@ -22,7 +22,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
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..a4821e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -18,7 +18,6 @@
 
 import static android.net.TetheringManager.TETHERING_WIFI;
 
-import android.app.ActivityManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.TetheringManager;
@@ -33,10 +32,12 @@
 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;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -58,11 +59,13 @@
     private final WifiManager mWifiManager;
     private final Handler mMainHandler;
     private final Context mContext;
+    private final UserTracker mUserTracker;
 
     private int mHotspotState;
     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;
 
@@ -93,31 +96,39 @@
     @Inject
     public HotspotControllerImpl(
             Context context,
+            UserTracker userTracker,
             @Main Handler mainHandler,
             @Background Handler backgroundHandler,
             DumpManager dumpManager) {
         mContext = context;
+        mUserTracker = userTracker;
         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
-                && UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser());
+        return mIsTetheringSupportedConfig && mIsTetheringSupported && mHasTetherableWifiRegexs
+                && UserManager.get(mContext).isUserAdmin(mUserTracker.getUserId());
     }
 
     public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index b234e9c..63b9ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -28,11 +28,14 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -45,22 +48,34 @@
 
     private final ArrayList<NextAlarmChangeCallback> mChangeCallbacks = new ArrayList<>();
 
+    private final UserTracker mUserTracker;
     private AlarmManager mAlarmManager;
     private AlarmManager.AlarmClockInfo mNextAlarm;
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    updateNextAlarm();
+                }
+            };
+
     /**
      */
     @Inject
     public NextAlarmControllerImpl(
+            @Main Executor mainExecutor,
             AlarmManager alarmManager,
             BroadcastDispatcher broadcastDispatcher,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            UserTracker userTracker) {
         dumpManager.registerDumpable("NextAlarmController", this);
         mAlarmManager = alarmManager;
+        mUserTracker = userTracker;
         IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
         broadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
+        mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
         updateNextAlarm();
     }
 
@@ -98,14 +113,13 @@
 
     public void onReceive(Context context, Intent intent) {
         final String action = intent.getAction();
-        if (action.equals(Intent.ACTION_USER_SWITCHED)
-                || action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
+        if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
             updateNextAlarm();
         }
     }
 
     private void updateNextAlarm() {
-        mNextAlarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
+        mNextAlarm = mAlarmManager.getNextAlarmClock(mUserTracker.getUserId());
         fireNextAlarmChanged();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index cc241d9..ba94714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -16,7 +16,6 @@
 package com.android.systemui.statusbar.policy;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.app.admin.DeviceAdminInfo;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManager.DeviceOwnerType;
@@ -55,8 +54,9 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -70,7 +70,7 @@
 /**
  */
 @SysUISingleton
-public class SecurityControllerImpl extends CurrentUserTracker implements SecurityController {
+public class SecurityControllerImpl implements SecurityController {
 
     private static final String TAG = "SecurityController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -84,11 +84,13 @@
     private static final int CA_CERT_LOADING_RETRY_TIME_IN_MS = 30_000;
 
     private final Context mContext;
+    private final UserTracker mUserTracker;
     private final ConnectivityManager mConnectivityManager;
     private final VpnManager mVpnManager;
     private final DevicePolicyManager mDevicePolicyManager;
     private final PackageManager mPackageManager;
     private final UserManager mUserManager;
+    private final Executor mMainExecutor;
     private final Executor mBgExecutor;
 
     @GuardedBy("mCallbacks")
@@ -102,18 +104,28 @@
     // Needs to be cached here since the query has to be asynchronous
     private ArrayMap<Integer, Boolean> mHasCACerts = new ArrayMap<Integer, Boolean>();
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    onUserSwitched(newUser);
+                }
+            };
+
     /**
      */
     @Inject
     public SecurityControllerImpl(
             Context context,
+            UserTracker userTracker,
             @Background Handler bgHandler,
             BroadcastDispatcher broadcastDispatcher,
+            @Main Executor mainExecutor,
             @Background Executor bgExecutor,
             DumpManager dumpManager
     ) {
-        super(broadcastDispatcher);
         mContext = context;
+        mUserTracker = userTracker;
         mDevicePolicyManager = (DevicePolicyManager)
                 context.getSystemService(Context.DEVICE_POLICY_SERVICE);
         mConnectivityManager = (ConnectivityManager)
@@ -121,6 +133,7 @@
         mVpnManager = context.getSystemService(VpnManager.class);
         mPackageManager = context.getPackageManager();
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        mMainExecutor = mainExecutor;
         mBgExecutor = bgExecutor;
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -133,8 +146,8 @@
 
         // TODO: re-register network callback on user change.
         mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
-        onUserSwitched(ActivityManager.getCurrentUser());
-        startTracking();
+        onUserSwitched(mUserTracker.getUserId());
+        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
     }
 
     public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 29285f8..a593d51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -28,7 +27,6 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.ContactsContract;
@@ -40,8 +38,11 @@
 import com.android.settingslib.drawable.UserIconDrawable;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
 
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -53,6 +54,7 @@
     private static final String TAG = "UserInfoController";
 
     private final Context mContext;
+    private final UserTracker mUserTracker;
     private final ArrayList<OnUserInfoChangedListener> mCallbacks =
             new ArrayList<OnUserInfoChangedListener>();
     private AsyncTask<Void, Void, UserInfoQueryResult> mUserInfoTask;
@@ -64,11 +66,11 @@
     /**
      */
     @Inject
-    public UserInfoControllerImpl(Context context) {
+    public UserInfoControllerImpl(Context context, @Main Executor mainExecutor,
+            UserTracker userTracker) {
         mContext = context;
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mContext.registerReceiver(mReceiver, filter);
+        mUserTracker = userTracker;
+        mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
 
         IntentFilter profileFilter = new IntentFilter();
         profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
@@ -88,15 +90,13 @@
         mCallbacks.remove(callback);
     }
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                reloadUserInfo();
-            }
-        }
-    };
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    reloadUserInfo();
+                }
+            };
 
     private final BroadcastReceiver mProfileReceiver = new BroadcastReceiver() {
         @Override
@@ -104,15 +104,11 @@
             final String action = intent.getAction();
             if (ContactsContract.Intents.ACTION_PROFILE_CHANGED.equals(action) ||
                     Intent.ACTION_USER_INFO_CHANGED.equals(action)) {
-                try {
-                    final int currentUser = ActivityManager.getService().getCurrentUser().id;
-                    final int changedUser =
-                            intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
-                    if (changedUser == currentUser) {
-                        reloadUserInfo();
-                    }
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Couldn't get current user id for profile change", e);
+                final int currentUser = mUserTracker.getUserId();
+                final int changedUser =
+                        intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
+                if (changedUser == currentUser) {
+                    reloadUserInfo();
                 }
             }
         }
@@ -130,15 +126,12 @@
         Context currentUserContext;
         UserInfo userInfo;
         try {
-            userInfo = ActivityManager.getService().getCurrentUser();
+            userInfo = mUserTracker.getUserInfo();
             currentUserContext = mContext.createPackageContextAsUser("android", 0,
                     new UserHandle(userInfo.id));
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Couldn't create user context", e);
             throw new RuntimeException(e);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Couldn't get user info", e);
-            throw new RuntimeException(e);
         }
         final int userId = userInfo.id;
         final boolean isGuest = userInfo.isGuest();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
index 146b222..bdb656b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
@@ -14,35 +14,74 @@
  * limitations under the License.
  *
  */
+
 package com.android.systemui.statusbar.policy
 
-import android.annotation.UserIdInt
+import android.content.Context
 import android.content.Intent
 import android.view.View
-import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
 import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
+import dagger.Lazy
+import java.io.PrintWriter
 import java.lang.ref.WeakReference
-import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
 
-/** Defines interface for a class that provides user switching functionality and state. */
-interface UserSwitcherController : Dumpable {
+/** Access point into multi-user switching logic. */
+@Deprecated("Use UserInteractor or GuestUserInteractor instead.")
+@SysUISingleton
+class UserSwitcherController
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    private val userInteractorLazy: Lazy<UserInteractor>,
+    private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
+    private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
+    private val activityStarter: ActivityStarter,
+) {
+
+    /** Defines interface for classes that can be called back when the user is switched. */
+    fun interface UserSwitchCallback {
+        /** Notifies that the user has switched. */
+        fun onUserSwitched()
+    }
+
+    private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
+    private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
+    private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
+
+    private val callbackCompatMap = mutableMapOf<UserSwitchCallback, UserInteractor.UserCallback>()
 
     /** The current list of [UserRecord]. */
     val users: ArrayList<UserRecord>
+        get() = userInteractor.userRecords.value
 
     /** Whether the user switcher experience should use the simple experience. */
     val isSimpleUserSwitcher: Boolean
-
-    /** Require a view for jank detection */
-    fun init(view: View)
+        get() = userInteractor.isSimpleUserSwitcher
 
     /** The [UserRecord] of the current user or `null` when none. */
     val currentUserRecord: UserRecord?
+        get() = userInteractor.selectedUserRecord.value
 
     /** The name of the current user of the device or `null`, when none is selected. */
     val currentUserName: String?
+        get() =
+            currentUserRecord?.let {
+                LegacyUserUiHelper.getUserRecordName(
+                    context = applicationContext,
+                    record = it,
+                    isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
+                    isGuestUserResetting = userInteractor.isGuestUserResetting,
+                )
+            }
 
     /**
      * Notifies that a user has been selected.
@@ -55,34 +94,40 @@
      * @param userId The ID of the user to switch to.
      * @param dialogShower An optional [DialogShower] in case we need to show dialogs.
      */
-    fun onUserSelected(userId: Int, dialogShower: DialogShower?)
-
-    /** Whether it is allowed to add users while the device is locked. */
-    val isAddUsersFromLockScreenEnabled: Flow<Boolean>
+    fun onUserSelected(userId: Int, dialogShower: DialogShower?) {
+        userInteractor.selectUser(userId, dialogShower)
+    }
 
     /** Whether the guest user is configured to always be present on the device. */
     val isGuestUserAutoCreated: Boolean
+        get() = userInteractor.isGuestUserAutoCreated
 
     /** Whether the guest user is currently being reset. */
     val isGuestUserResetting: Boolean
-
-    /** Creates and switches to the guest user. */
-    fun createAndSwitchToGuestUser(dialogShower: DialogShower?)
-
-    /** Shows the add user dialog. */
-    fun showAddUserDialog(dialogShower: DialogShower?)
-
-    /** Starts an activity to add a supervised user to the device. */
-    fun startSupervisedUserActivity()
-
-    /** Notifies when the display density or font scale has changed. */
-    fun onDensityOrFontScaleChanged()
+        get() = userInteractor.isGuestUserResetting
 
     /** Registers an adapter to notify when the users change. */
-    fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>)
+    fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
+        userInteractor.addCallback(
+            object : UserInteractor.UserCallback {
+                override fun isEvictable(): Boolean {
+                    return adapter.get() == null
+                }
+
+                override fun onUserStateChanged() {
+                    adapter.get()?.notifyDataSetChanged()
+                }
+            }
+        )
+    }
 
     /** Notifies the item for a user has been clicked. */
-    fun onUserListItemClicked(record: UserRecord, dialogShower: DialogShower?)
+    fun onUserListItemClicked(
+        record: UserRecord,
+        dialogShower: DialogShower?,
+    ) {
+        userInteractor.onRecordSelected(record, dialogShower)
+    }
 
     /**
      * Removes guest user and switches to target user. The guest must be the current user and its id
@@ -103,7 +148,12 @@
      * @param targetUserId id of the user to switch to after guest is removed. If
      * `UserHandle.USER_NULL`, then switch immediately to the newly created guest user.
      */
-    fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int)
+    fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
+        userInteractor.removeGuestUser(
+            guestUserId = guestUserId,
+            targetUserId = targetUserId,
+        )
+    }
 
     /**
      * Exits guest user and switches to previous non-guest user. The guest must be the current user.
@@ -114,43 +164,58 @@
      * @param forceRemoveGuestOnExit true: remove guest before switching user, false: remove guest
      * only if its ephemeral, else keep guest
      */
-    fun exitGuestUser(
-        @UserIdInt guestUserId: Int,
-        @UserIdInt targetUserId: Int,
-        forceRemoveGuestOnExit: Boolean
-    )
+    fun exitGuestUser(guestUserId: Int, targetUserId: Int, forceRemoveGuestOnExit: Boolean) {
+        userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
+    }
 
     /**
      * Guarantee guest is present only if the device is provisioned. Otherwise, create a content
      * observer to wait until the device is provisioned, then schedule the guest creation.
      */
-    fun schedulePostBootGuestCreation()
+    fun schedulePostBootGuestCreation() {
+        guestUserInteractor.onDeviceBootCompleted()
+    }
 
     /** Whether keyguard is showing. */
     val isKeyguardShowing: Boolean
+        get() = keyguardInteractor.isKeyguardShowing()
 
     /** Starts an activity with the given [Intent]. */
-    fun startActivity(intent: Intent)
+    fun startActivity(intent: Intent) {
+        activityStarter.startActivity(intent, /* dismissShade= */ true)
+    }
 
     /**
      * Refreshes users from UserManager.
      *
      * The pictures are only loaded if they have not been loaded yet.
-     *
-     * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
      */
-    fun refreshUsers(forcePictureLoadForId: Int)
+    fun refreshUsers() {
+        userInteractor.refreshUsers()
+    }
 
     /** Adds a subscriber to when user switches. */
-    fun addUserSwitchCallback(callback: UserSwitchCallback)
+    fun addUserSwitchCallback(callback: UserSwitchCallback) {
+        val interactorCallback =
+            object : UserInteractor.UserCallback {
+                override fun onUserStateChanged() {
+                    callback.onUserSwitched()
+                }
+            }
+        callbackCompatMap[callback] = interactorCallback
+        userInteractor.addCallback(interactorCallback)
+    }
 
     /** Removes a previously-added subscriber. */
-    fun removeUserSwitchCallback(callback: UserSwitchCallback)
+    fun removeUserSwitchCallback(callback: UserSwitchCallback) {
+        val interactorCallback = callbackCompatMap.remove(callback)
+        if (interactorCallback != null) {
+            userInteractor.removeCallback(interactorCallback)
+        }
+    }
 
-    /** Defines interface for classes that can be called back when the user is switched. */
-    fun interface UserSwitchCallback {
-        /** Notifies that the user has switched. */
-        fun onUserSwitched()
+    fun dump(pw: PrintWriter, args: Array<out String>) {
+        userInteractor.dump(pw)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
deleted file mode 100644
index 935fc7f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
+++ /dev/null
@@ -1,299 +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.statusbar.policy
-
-import android.content.Context
-import android.content.Intent
-import android.view.View
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
-import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
-import dagger.Lazy
-import java.io.PrintWriter
-import java.lang.ref.WeakReference
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Implementation of [UserSwitcherController]. */
-@SysUISingleton
-class UserSwitcherControllerImpl
-@Inject
-constructor(
-    @Application private val applicationContext: Context,
-    flags: FeatureFlags,
-    @Suppress("DEPRECATION") private val oldImpl: Lazy<UserSwitcherControllerOldImpl>,
-    private val userInteractorLazy: Lazy<UserInteractor>,
-    private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
-    private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
-    private val activityStarter: ActivityStarter,
-) : UserSwitcherController {
-
-    private val useInteractor: Boolean =
-        flags.isEnabled(Flags.USER_CONTROLLER_USES_INTERACTOR) &&
-            !flags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-    private val _oldImpl: UserSwitcherControllerOldImpl
-        get() = oldImpl.get()
-    private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
-    private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
-    private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
-
-    private val callbackCompatMap =
-        mutableMapOf<UserSwitcherController.UserSwitchCallback, UserInteractor.UserCallback>()
-
-    private fun notSupported(): Nothing {
-        error("Not supported in the new implementation!")
-    }
-
-    override val users: ArrayList<UserRecord>
-        get() =
-            if (useInteractor) {
-                userInteractor.userRecords.value
-            } else {
-                _oldImpl.users
-            }
-
-    override val isSimpleUserSwitcher: Boolean
-        get() =
-            if (useInteractor) {
-                userInteractor.isSimpleUserSwitcher
-            } else {
-                _oldImpl.isSimpleUserSwitcher
-            }
-
-    override fun init(view: View) {
-        if (!useInteractor) {
-            _oldImpl.init(view)
-        }
-    }
-
-    override val currentUserRecord: UserRecord?
-        get() =
-            if (useInteractor) {
-                userInteractor.selectedUserRecord.value
-            } else {
-                _oldImpl.currentUserRecord
-            }
-
-    override val currentUserName: String?
-        get() =
-            if (useInteractor) {
-                currentUserRecord?.let {
-                    LegacyUserUiHelper.getUserRecordName(
-                        context = applicationContext,
-                        record = it,
-                        isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
-                        isGuestUserResetting = userInteractor.isGuestUserResetting,
-                    )
-                }
-            } else {
-                _oldImpl.currentUserName
-            }
-
-    override fun onUserSelected(
-        userId: Int,
-        dialogShower: UserSwitchDialogController.DialogShower?
-    ) {
-        if (useInteractor) {
-            userInteractor.selectUser(userId, dialogShower)
-        } else {
-            _oldImpl.onUserSelected(userId, dialogShower)
-        }
-    }
-
-    override val isAddUsersFromLockScreenEnabled: Flow<Boolean>
-        get() =
-            if (useInteractor) {
-                notSupported()
-            } else {
-                _oldImpl.isAddUsersFromLockScreenEnabled
-            }
-
-    override val isGuestUserAutoCreated: Boolean
-        get() =
-            if (useInteractor) {
-                userInteractor.isGuestUserAutoCreated
-            } else {
-                _oldImpl.isGuestUserAutoCreated
-            }
-
-    override val isGuestUserResetting: Boolean
-        get() =
-            if (useInteractor) {
-                userInteractor.isGuestUserResetting
-            } else {
-                _oldImpl.isGuestUserResetting
-            }
-
-    override fun createAndSwitchToGuestUser(
-        dialogShower: UserSwitchDialogController.DialogShower?,
-    ) {
-        if (useInteractor) {
-            notSupported()
-        } else {
-            _oldImpl.createAndSwitchToGuestUser(dialogShower)
-        }
-    }
-
-    override fun showAddUserDialog(dialogShower: UserSwitchDialogController.DialogShower?) {
-        if (useInteractor) {
-            notSupported()
-        } else {
-            _oldImpl.showAddUserDialog(dialogShower)
-        }
-    }
-
-    override fun startSupervisedUserActivity() {
-        if (useInteractor) {
-            notSupported()
-        } else {
-            _oldImpl.startSupervisedUserActivity()
-        }
-    }
-
-    override fun onDensityOrFontScaleChanged() {
-        if (!useInteractor) {
-            _oldImpl.onDensityOrFontScaleChanged()
-        }
-    }
-
-    override fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
-        if (useInteractor) {
-            userInteractor.addCallback(
-                object : UserInteractor.UserCallback {
-                    override fun isEvictable(): Boolean {
-                        return adapter.get() == null
-                    }
-
-                    override fun onUserStateChanged() {
-                        adapter.get()?.notifyDataSetChanged()
-                    }
-                }
-            )
-        } else {
-            _oldImpl.addAdapter(adapter)
-        }
-    }
-
-    override fun onUserListItemClicked(
-        record: UserRecord,
-        dialogShower: UserSwitchDialogController.DialogShower?,
-    ) {
-        if (useInteractor) {
-            userInteractor.onRecordSelected(record, dialogShower)
-        } else {
-            _oldImpl.onUserListItemClicked(record, dialogShower)
-        }
-    }
-
-    override fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
-        if (useInteractor) {
-            userInteractor.removeGuestUser(
-                guestUserId = guestUserId,
-                targetUserId = targetUserId,
-            )
-        } else {
-            _oldImpl.removeGuestUser(guestUserId, targetUserId)
-        }
-    }
-
-    override fun exitGuestUser(
-        guestUserId: Int,
-        targetUserId: Int,
-        forceRemoveGuestOnExit: Boolean
-    ) {
-        if (useInteractor) {
-            userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
-        } else {
-            _oldImpl.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
-        }
-    }
-
-    override fun schedulePostBootGuestCreation() {
-        if (useInteractor) {
-            guestUserInteractor.onDeviceBootCompleted()
-        } else {
-            _oldImpl.schedulePostBootGuestCreation()
-        }
-    }
-
-    override val isKeyguardShowing: Boolean
-        get() =
-            if (useInteractor) {
-                keyguardInteractor.isKeyguardShowing()
-            } else {
-                _oldImpl.isKeyguardShowing
-            }
-
-    override fun startActivity(intent: Intent) {
-        if (useInteractor) {
-            activityStarter.startActivity(intent, /* dismissShade= */ true)
-        } else {
-            _oldImpl.startActivity(intent)
-        }
-    }
-
-    override fun refreshUsers(forcePictureLoadForId: Int) {
-        if (useInteractor) {
-            userInteractor.refreshUsers()
-        } else {
-            _oldImpl.refreshUsers(forcePictureLoadForId)
-        }
-    }
-
-    override fun addUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
-        if (useInteractor) {
-            val interactorCallback =
-                object : UserInteractor.UserCallback {
-                    override fun onUserStateChanged() {
-                        callback.onUserSwitched()
-                    }
-                }
-            callbackCompatMap[callback] = interactorCallback
-            userInteractor.addCallback(interactorCallback)
-        } else {
-            _oldImpl.addUserSwitchCallback(callback)
-        }
-    }
-
-    override fun removeUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
-        if (useInteractor) {
-            val interactorCallback = callbackCompatMap.remove(callback)
-            if (interactorCallback != null) {
-                userInteractor.removeCallback(interactorCallback)
-            }
-        } else {
-            _oldImpl.removeUserSwitchCallback(callback)
-        }
-    }
-
-    override fun dump(pw: PrintWriter, args: Array<out String>) {
-        if (useInteractor) {
-            userInteractor.dump(pw)
-        } else {
-            _oldImpl.dump(pw, args)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
deleted file mode 100644
index c294c37..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
+++ /dev/null
@@ -1,1063 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.policy;
-
-import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
-
-import android.annotation.UserIdInt;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.IActivityManager;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.graphics.Bitmap;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.telephony.TelephonyCallback;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.view.View;
-import android.view.WindowManagerGlobal;
-import android.widget.Toast;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.util.LatencyTracker;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.users.UserCreatingDialog;
-import com.android.systemui.GuestResetOrExitSessionReceiver;
-import com.android.systemui.GuestResumeSessionReceiver;
-import com.android.systemui.SystemUISecondaryUserService;
-import com.android.systemui.animation.DialogCuj;
-import com.android.systemui.animation.DialogLaunchAnimator;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.LongRunning;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.qs.QSUserSwitcherEvent;
-import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.user.data.source.UserRecord;
-import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper;
-import com.android.systemui.user.shared.model.UserActionModel;
-import com.android.systemui.user.ui.dialog.AddUserDialog;
-import com.android.systemui.user.ui.dialog.ExitGuestDialog;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
-import kotlinx.coroutines.flow.Flow;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlowKt;
-
-/**
- * Old implementation. Keeps a list of all users on the device for user switching.
- *
- * @deprecated This is the old implementation. Please depend on {@link UserSwitcherController}
- * instead.
- */
-@Deprecated
-@SysUISingleton
-public class UserSwitcherControllerOldImpl implements UserSwitcherController {
-
-    private static final String TAG = "UserSwitcherController";
-    private static final boolean DEBUG = false;
-    private static final String SIMPLE_USER_SWITCHER_GLOBAL_SETTING =
-            "lockscreenSimpleUserSwitcher";
-    private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000;
-
-    private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
-    private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000L;
-
-    private static final String INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user";
-    private static final String INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode";
-
-    protected final Context mContext;
-    protected final UserTracker mUserTracker;
-    protected final UserManager mUserManager;
-    private final ContentObserver mSettingsObserver;
-    private final ArrayList<WeakReference<BaseUserSwitcherAdapter>> mAdapters = new ArrayList<>();
-    @VisibleForTesting
-    final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
-    @VisibleForTesting
-    final GuestResetOrExitSessionReceiver mGuestResetOrExitSessionReceiver;
-    private final KeyguardStateController mKeyguardStateController;
-    private final DeviceProvisionedController mDeviceProvisionedController;
-    private final DevicePolicyManager mDevicePolicyManager;
-    protected final Handler mHandler;
-    private final ActivityStarter mActivityStarter;
-    private final BroadcastDispatcher mBroadcastDispatcher;
-    private final BroadcastSender mBroadcastSender;
-    private final TelephonyListenerManager mTelephonyListenerManager;
-    private final InteractionJankMonitor mInteractionJankMonitor;
-    private final LatencyTracker mLatencyTracker;
-    private final DialogLaunchAnimator mDialogLaunchAnimator;
-
-    private ArrayList<UserRecord> mUsers = new ArrayList<>();
-    @VisibleForTesting
-    AlertDialog mExitGuestDialog;
-    @VisibleForTesting
-    Dialog mAddUserDialog;
-    private int mLastNonGuestUser = UserHandle.USER_SYSTEM;
-    private boolean mSimpleUserSwitcher;
-    // When false, there won't be any visual affordance to add a new user from the keyguard even if
-    // the user is unlocked
-    private final MutableStateFlow<Boolean> mAddUsersFromLockScreen =
-            StateFlowKt.MutableStateFlow(false);
-    private boolean mUserSwitcherEnabled;
-    @VisibleForTesting
-    boolean mPauseRefreshUsers;
-    private int mSecondaryUser = UserHandle.USER_NULL;
-    private Intent mSecondaryUserServiceIntent;
-    private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
-    private final UiEventLogger mUiEventLogger;
-    private final IActivityManager mActivityManager;
-    private final Executor mBgExecutor;
-    private final Executor mUiExecutor;
-    private final Executor mLongRunningExecutor;
-    private final boolean mGuestUserAutoCreated;
-    private final AtomicBoolean mGuestIsResetting;
-    private final AtomicBoolean mGuestCreationScheduled;
-    private FalsingManager mFalsingManager;
-    @Nullable
-    private View mView;
-    private String mCreateSupervisedUserPackage;
-    private GlobalSettings mGlobalSettings;
-    private List<UserSwitchCallback> mUserSwitchCallbacks =
-            Collections.synchronizedList(new ArrayList<>());
-
-    @Inject
-    public UserSwitcherControllerOldImpl(
-            Context context,
-            IActivityManager activityManager,
-            UserManager userManager,
-            UserTracker userTracker,
-            KeyguardStateController keyguardStateController,
-            DeviceProvisionedController deviceProvisionedController,
-            DevicePolicyManager devicePolicyManager,
-            @Main Handler handler,
-            ActivityStarter activityStarter,
-            BroadcastDispatcher broadcastDispatcher,
-            BroadcastSender broadcastSender,
-            UiEventLogger uiEventLogger,
-            FalsingManager falsingManager,
-            TelephonyListenerManager telephonyListenerManager,
-            SecureSettings secureSettings,
-            GlobalSettings globalSettings,
-            @Background Executor bgExecutor,
-            @LongRunning Executor longRunningExecutor,
-            @Main Executor uiExecutor,
-            InteractionJankMonitor interactionJankMonitor,
-            LatencyTracker latencyTracker,
-            DumpManager dumpManager,
-            DialogLaunchAnimator dialogLaunchAnimator,
-            GuestResumeSessionReceiver guestResumeSessionReceiver,
-            GuestResetOrExitSessionReceiver guestResetOrExitSessionReceiver) {
-        mContext = context;
-        mActivityManager = activityManager;
-        mUserTracker = userTracker;
-        mBroadcastDispatcher = broadcastDispatcher;
-        mBroadcastSender = broadcastSender;
-        mTelephonyListenerManager = telephonyListenerManager;
-        mUiEventLogger = uiEventLogger;
-        mFalsingManager = falsingManager;
-        mInteractionJankMonitor = interactionJankMonitor;
-        mLatencyTracker = latencyTracker;
-        mGlobalSettings = globalSettings;
-        mGuestResumeSessionReceiver = guestResumeSessionReceiver;
-        mGuestResetOrExitSessionReceiver = guestResetOrExitSessionReceiver;
-        mBgExecutor = bgExecutor;
-        mLongRunningExecutor = longRunningExecutor;
-        mUiExecutor = uiExecutor;
-        mGuestResumeSessionReceiver.register();
-        mGuestResetOrExitSessionReceiver.register();
-        mGuestUserAutoCreated = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_guestUserAutoCreated);
-        mGuestIsResetting = new AtomicBoolean();
-        mGuestCreationScheduled = new AtomicBoolean();
-        mKeyguardStateController = keyguardStateController;
-        mDeviceProvisionedController = deviceProvisionedController;
-        mDevicePolicyManager = devicePolicyManager;
-        mHandler = handler;
-        mActivityStarter = activityStarter;
-        mUserManager = userManager;
-        mDialogLaunchAnimator = dialogLaunchAnimator;
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_ADDED);
-        filter.addAction(Intent.ACTION_USER_REMOVED);
-        filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-        filter.addAction(Intent.ACTION_USER_STOPPED);
-        filter.addAction(Intent.ACTION_USER_UNLOCKED);
-        mBroadcastDispatcher.registerReceiver(
-                mReceiver, filter, null /* executor */,
-                UserHandle.SYSTEM, Context.RECEIVER_EXPORTED, null /* permission */);
-
-        mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
-
-        mSecondaryUserServiceIntent = new Intent(context, SystemUISecondaryUserService.class);
-
-        filter = new IntentFilter();
-        mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter,
-                PERMISSION_SELF, null /* scheduler */,
-                Context.RECEIVER_EXPORTED_UNAUDITED);
-
-        mSettingsObserver = new ContentObserver(mHandler) {
-            @Override
-            public void onChange(boolean selfChange) {
-                mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
-                mAddUsersFromLockScreen.setValue(
-                        mGlobalSettings.getIntForUser(
-                                Settings.Global.ADD_USERS_WHEN_LOCKED,
-                                0,
-                                UserHandle.USER_SYSTEM) != 0);
-                mUserSwitcherEnabled = mGlobalSettings.getIntForUser(
-                        Settings.Global.USER_SWITCHER_ENABLED, 0, UserHandle.USER_SYSTEM) != 0;
-                refreshUsers(UserHandle.USER_NULL);
-            };
-        };
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true,
-                mSettingsObserver);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.USER_SWITCHER_ENABLED), true,
-                mSettingsObserver);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true,
-                mSettingsObserver);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(
-                        Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED),
-                true, mSettingsObserver);
-        // Fetch initial values.
-        mSettingsObserver.onChange(false);
-
-        keyguardStateController.addCallback(mCallback);
-        listenForCallState();
-
-        mCreateSupervisedUserPackage = mContext.getString(
-                com.android.internal.R.string.config_supervisedUserCreationPackage);
-
-        dumpManager.registerDumpable(getClass().getSimpleName(), this);
-
-        refreshUsers(UserHandle.USER_NULL);
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public void refreshUsers(int forcePictureLoadForId) {
-        if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId + ")");
-        if (forcePictureLoadForId != UserHandle.USER_NULL) {
-            mForcePictureLoadForUserId.put(forcePictureLoadForId, true);
-        }
-
-        if (mPauseRefreshUsers) {
-            return;
-        }
-
-        boolean forceAllUsers = mForcePictureLoadForUserId.get(UserHandle.USER_ALL);
-        SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size());
-        final int userCount = mUsers.size();
-        for (int i = 0; i < userCount; i++) {
-            UserRecord r = mUsers.get(i);
-            if (r == null || r.picture == null || r.info == null || forceAllUsers
-                    || mForcePictureLoadForUserId.get(r.info.id)) {
-                continue;
-            }
-            bitmaps.put(r.info.id, r.picture);
-        }
-        mForcePictureLoadForUserId.clear();
-
-        mBgExecutor.execute(() ->  {
-            List<UserInfo> infos = mUserManager.getAliveUsers();
-            if (infos == null) {
-                return;
-            }
-            ArrayList<UserRecord> records = new ArrayList<>(infos.size());
-            int currentId = mUserTracker.getUserId();
-            // Check user switchability of the foreground user since SystemUI is running in
-            // User 0
-            boolean canSwitchUsers = mUserManager.getUserSwitchability(
-                    UserHandle.of(mUserTracker.getUserId())) == SWITCHABILITY_STATUS_OK;
-            UserRecord guestRecord = null;
-
-            for (UserInfo info : infos) {
-                boolean isCurrent = currentId == info.id;
-                if (!mUserSwitcherEnabled && !info.isPrimary()) {
-                    continue;
-                }
-
-                if (info.isEnabled()) {
-                    if (info.isGuest()) {
-                        // Tapping guest icon triggers remove and a user switch therefore
-                        // the icon shouldn't be enabled even if the user is current
-                        guestRecord = LegacyUserDataHelper.createRecord(
-                                mContext,
-                                mUserManager,
-                                null /* picture */,
-                                info,
-                                isCurrent,
-                                canSwitchUsers);
-                    } else if (info.supportsSwitchToByUser()) {
-                        records.add(
-                                LegacyUserDataHelper.createRecord(
-                                        mContext,
-                                        mUserManager,
-                                        bitmaps.get(info.id),
-                                        info,
-                                        isCurrent,
-                                        canSwitchUsers));
-                    }
-                }
-            }
-
-            if (guestRecord == null) {
-                if (mGuestUserAutoCreated) {
-                    // If mGuestIsResetting=true, the switch should be disabled since
-                    // we will just use it as an indicator for "Resetting guest...".
-                    // Otherwise, default to canSwitchUsers.
-                    boolean isSwitchToGuestEnabled = !mGuestIsResetting.get() && canSwitchUsers;
-                    guestRecord = LegacyUserDataHelper.createRecord(
-                            mContext,
-                            currentId,
-                            UserActionModel.ENTER_GUEST_MODE,
-                            false /* isRestricted */,
-                            isSwitchToGuestEnabled);
-                    records.add(guestRecord);
-                } else if (canCreateGuest(guestRecord != null)) {
-                    guestRecord = LegacyUserDataHelper.createRecord(
-                            mContext,
-                            currentId,
-                            UserActionModel.ENTER_GUEST_MODE,
-                            false /* isRestricted */,
-                            canSwitchUsers);
-                    records.add(guestRecord);
-                }
-            } else {
-                records.add(guestRecord);
-            }
-
-            if (canCreateUser()) {
-                final UserRecord userRecord = LegacyUserDataHelper.createRecord(
-                        mContext,
-                        currentId,
-                        UserActionModel.ADD_USER,
-                        createIsRestricted(),
-                        canSwitchUsers);
-                records.add(userRecord);
-            }
-
-            if (canCreateSupervisedUser()) {
-                final UserRecord userRecord = LegacyUserDataHelper.createRecord(
-                        mContext,
-                        currentId,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        createIsRestricted(),
-                        canSwitchUsers);
-                records.add(userRecord);
-            }
-
-            if (canManageUsers()) {
-                records.add(LegacyUserDataHelper.createRecord(
-                        mContext,
-                        KeyguardUpdateMonitor.getCurrentUser(),
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                        /* isRestricted= */ false,
-                        /* isSwitchToEnabled= */ true
-                ));
-            }
-
-            mUiExecutor.execute(() -> {
-                if (records != null) {
-                    mUsers = records;
-                    notifyAdapters();
-                }
-            });
-        });
-    }
-
-    private boolean systemCanCreateUsers() {
-        return !mUserManager.hasBaseUserRestriction(
-                UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
-    }
-
-    private boolean currentUserCanCreateUsers() {
-        UserInfo currentUser = mUserTracker.getUserInfo();
-        return currentUser != null
-                && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM)
-                && systemCanCreateUsers();
-    }
-
-    private boolean anyoneCanCreateUsers() {
-        return systemCanCreateUsers() && mAddUsersFromLockScreen.getValue();
-    }
-
-    @VisibleForTesting
-    boolean canCreateGuest(boolean hasExistingGuest) {
-        return mUserSwitcherEnabled
-                && (currentUserCanCreateUsers() || anyoneCanCreateUsers())
-                && !hasExistingGuest;
-    }
-
-    @VisibleForTesting
-    boolean canCreateUser() {
-        return mUserSwitcherEnabled
-                && (currentUserCanCreateUsers() || anyoneCanCreateUsers())
-                && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
-    }
-
-    @VisibleForTesting
-    boolean canManageUsers() {
-        UserInfo currentUser = mUserTracker.getUserInfo();
-        return mUserSwitcherEnabled
-                && ((currentUser != null && currentUser.isAdmin())
-                || mAddUsersFromLockScreen.getValue());
-    }
-
-    private boolean createIsRestricted() {
-        return !mAddUsersFromLockScreen.getValue();
-    }
-
-    @VisibleForTesting
-    boolean canCreateSupervisedUser() {
-        return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser();
-    }
-
-    private void pauseRefreshUsers() {
-        if (!mPauseRefreshUsers) {
-            mHandler.postDelayed(mUnpauseRefreshUsers, PAUSE_REFRESH_USERS_TIMEOUT_MS);
-            mPauseRefreshUsers = true;
-        }
-    }
-
-    private void notifyAdapters() {
-        for (int i = mAdapters.size() - 1; i >= 0; i--) {
-            BaseUserSwitcherAdapter adapter = mAdapters.get(i).get();
-            if (adapter != null) {
-                adapter.notifyDataSetChanged();
-            } else {
-                mAdapters.remove(i);
-            }
-        }
-    }
-
-    @Override
-    public boolean isSimpleUserSwitcher() {
-        return mSimpleUserSwitcher;
-    }
-
-    /**
-     * Returns whether the current user is a system user.
-     */
-    @VisibleForTesting
-    boolean isSystemUser() {
-        return mUserTracker.getUserId() == UserHandle.USER_SYSTEM;
-    }
-
-    @Override
-    public @Nullable UserRecord getCurrentUserRecord() {
-        for (int i = 0; i < mUsers.size(); ++i) {
-            UserRecord userRecord = mUsers.get(i);
-            if (userRecord.isCurrent) {
-                return userRecord;
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public void onUserSelected(int userId, @Nullable DialogShower dialogShower) {
-        UserRecord userRecord = mUsers.stream()
-                .filter(x -> x.resolveId() == userId)
-                .findFirst()
-                .orElse(null);
-        if (userRecord == null) {
-            return;
-        }
-
-        onUserListItemClicked(userRecord, dialogShower);
-    }
-
-    @Override
-    public Flow<Boolean> isAddUsersFromLockScreenEnabled() {
-        return mAddUsersFromLockScreen;
-    }
-
-    @Override
-    public boolean isGuestUserAutoCreated() {
-        return mGuestUserAutoCreated;
-    }
-
-    @Override
-    public boolean isGuestUserResetting() {
-        return mGuestIsResetting.get();
-    }
-
-    @Override
-    public void onUserListItemClicked(UserRecord record, DialogShower dialogShower) {
-        if (record.isGuest && record.info == null) {
-            createAndSwitchToGuestUser(dialogShower);
-        } else if (record.isAddUser) {
-            showAddUserDialog(dialogShower);
-        } else if (record.isAddSupervisedUser) {
-            startSupervisedUserActivity();
-        } else if (record.isManageUsers) {
-            startActivity(new Intent(Settings.ACTION_USER_SETTINGS));
-        } else {
-            onUserListItemClicked(record.info.id, record, dialogShower);
-        }
-    }
-
-    private void onUserListItemClicked(int id, UserRecord record, DialogShower dialogShower) {
-        int currUserId = mUserTracker.getUserId();
-        // If switching from guest and guest is ephemeral, then follow the flow
-        // of showExitGuestDialog to remove current guest,
-        // and switch to selected user
-        UserInfo currUserInfo = mUserTracker.getUserInfo();
-        if (currUserId == id) {
-            if (record.isGuest) {
-                showExitGuestDialog(id, currUserInfo.isEphemeral(), dialogShower);
-            }
-            return;
-        }
-
-        if (currUserInfo != null && currUserInfo.isGuest()) {
-            showExitGuestDialog(currUserId, currUserInfo.isEphemeral(),
-                    record.resolveId(), dialogShower);
-            return;
-        }
-
-        if (dialogShower != null) {
-            // If we haven't morphed into another dialog, it means we have just switched users.
-            // Then, dismiss the dialog.
-            dialogShower.dismiss();
-        }
-        switchToUserId(id);
-    }
-
-    private void switchToUserId(int id) {
-        try {
-            if (mView != null) {
-                mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
-                        .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
-                        .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
-            }
-            mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
-            pauseRefreshUsers();
-            mActivityManager.switchUser(id);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Couldn't switch user.", e);
-        }
-    }
-
-    private void showExitGuestDialog(int id, boolean isGuestEphemeral, DialogShower dialogShower) {
-        int newId = UserHandle.USER_SYSTEM;
-        if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
-            UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
-            if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
-                newId = info.id;
-            }
-        }
-        showExitGuestDialog(id, isGuestEphemeral, newId, dialogShower);
-    }
-
-    private void showExitGuestDialog(
-            int id,
-            boolean isGuestEphemeral,
-            int targetId,
-            DialogShower dialogShower) {
-        if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
-            mExitGuestDialog.cancel();
-        }
-        mExitGuestDialog = new ExitGuestDialog(
-                mContext,
-                id,
-                isGuestEphemeral,
-                targetId,
-                mKeyguardStateController.isShowing(),
-                mFalsingManager,
-                mDialogLaunchAnimator,
-                this::exitGuestUser);
-        if (dialogShower != null) {
-            dialogShower.showDialog(mExitGuestDialog, new DialogCuj(
-                    InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
-                    INTERACTION_JANK_EXIT_GUEST_MODE_TAG));
-        } else {
-            mExitGuestDialog.show();
-        }
-    }
-
-    @Override
-    public void createAndSwitchToGuestUser(@Nullable DialogShower dialogShower) {
-        createGuestAsync(guestId -> {
-            // guestId may be USER_NULL if we haven't reloaded the user list yet.
-            if (guestId != UserHandle.USER_NULL) {
-                mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD);
-                onUserListItemClicked(guestId, UserRecord.createForGuest(), dialogShower);
-            }
-        });
-    }
-
-    @Override
-    public void showAddUserDialog(@Nullable DialogShower dialogShower) {
-        if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
-            mAddUserDialog.cancel();
-        }
-        final UserInfo currentUser = mUserTracker.getUserInfo();
-        mAddUserDialog = new AddUserDialog(
-                mContext,
-                currentUser.getUserHandle(),
-                mKeyguardStateController.isShowing(),
-                /* showEphemeralMessage= */currentUser.isGuest() && currentUser.isEphemeral(),
-                mFalsingManager,
-                mBroadcastSender,
-                mDialogLaunchAnimator);
-        if (dialogShower != null) {
-            dialogShower.showDialog(mAddUserDialog,
-                    new DialogCuj(
-                            InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
-                            INTERACTION_JANK_ADD_NEW_USER_TAG
-                    ));
-        } else {
-            mAddUserDialog.show();
-        }
-    }
-
-    @Override
-    public void startSupervisedUserActivity() {
-        final Intent intent = new Intent()
-                .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
-                .setPackage(mCreateSupervisedUserPackage)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        mContext.startActivity(intent);
-    }
-
-    private void listenForCallState() {
-        mTelephonyListenerManager.addCallStateListener(mPhoneStateListener);
-    }
-
-    private final TelephonyCallback.CallStateListener mPhoneStateListener =
-            new TelephonyCallback.CallStateListener() {
-        private int mCallState;
-
-        @Override
-        public void onCallStateChanged(int state) {
-            if (mCallState == state) return;
-            if (DEBUG) Log.v(TAG, "Call state changed: " + state);
-            mCallState = state;
-            refreshUsers(UserHandle.USER_NULL);
-        }
-    };
-
-    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (DEBUG) {
-                Log.v(TAG, "Broadcast: a=" + intent.getAction()
-                        + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
-            }
-
-            boolean unpauseRefreshUsers = false;
-            int forcePictureLoadForId = UserHandle.USER_NULL;
-
-            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
-                if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
-                    mExitGuestDialog.cancel();
-                    mExitGuestDialog = null;
-                }
-
-                final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                final UserInfo userInfo = mUserManager.getUserInfo(currentId);
-                final int userCount = mUsers.size();
-                for (int i = 0; i < userCount; i++) {
-                    UserRecord record = mUsers.get(i);
-                    if (record.info == null) continue;
-                    boolean shouldBeCurrent = record.info.id == currentId;
-                    if (record.isCurrent != shouldBeCurrent) {
-                        mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
-                    }
-                    if (shouldBeCurrent && !record.isGuest) {
-                        mLastNonGuestUser = record.info.id;
-                    }
-                    if ((userInfo == null || !userInfo.isAdmin()) && record.isRestricted) {
-                        // Immediately remove restricted records in case the AsyncTask is too slow.
-                        mUsers.remove(i);
-                        i--;
-                    }
-                }
-                notifyUserSwitchCallbacks();
-                notifyAdapters();
-
-                // Disconnect from the old secondary user's service
-                if (mSecondaryUser != UserHandle.USER_NULL) {
-                    context.stopServiceAsUser(mSecondaryUserServiceIntent,
-                            UserHandle.of(mSecondaryUser));
-                    mSecondaryUser = UserHandle.USER_NULL;
-                }
-                // Connect to the new secondary user's service (purely to ensure that a persistent
-                // SystemUI application is created for that user)
-                if (userInfo != null && userInfo.id != UserHandle.USER_SYSTEM) {
-                    context.startServiceAsUser(mSecondaryUserServiceIntent,
-                            UserHandle.of(userInfo.id));
-                    mSecondaryUser = userInfo.id;
-                }
-                unpauseRefreshUsers = true;
-                if (mGuestUserAutoCreated) {
-                    // Guest user must be scheduled for creation AFTER switching to the target user.
-                    // This avoids lock contention which will produce UX bugs on the keyguard
-                    // (b/193933686).
-                    // TODO(b/191067027): Move guest user recreation to system_server
-                    guaranteeGuestPresent();
-                }
-            } else if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) {
-                forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
-                        UserHandle.USER_NULL);
-            } else if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
-                // Unlocking the system user may require a refresh
-                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-                if (userId != UserHandle.USER_SYSTEM) {
-                    return;
-                }
-            }
-            refreshUsers(forcePictureLoadForId);
-            if (unpauseRefreshUsers) {
-                mUnpauseRefreshUsers.run();
-            }
-        }
-    };
-
-    private final Runnable mUnpauseRefreshUsers = new Runnable() {
-        @Override
-        public void run() {
-            mHandler.removeCallbacks(this);
-            mPauseRefreshUsers = false;
-            refreshUsers(UserHandle.USER_NULL);
-        }
-    };
-
-    @Override
-    public void dump(PrintWriter pw, String[] args) {
-        pw.println("UserSwitcherController state:");
-        pw.println("  mLastNonGuestUser=" + mLastNonGuestUser);
-        pw.print("  mUsers.size="); pw.println(mUsers.size());
-        for (int i = 0; i < mUsers.size(); i++) {
-            final UserRecord u = mUsers.get(i);
-            pw.print("    "); pw.println(u.toString());
-        }
-        pw.println("mSimpleUserSwitcher=" + mSimpleUserSwitcher);
-        pw.println("mGuestUserAutoCreated=" + mGuestUserAutoCreated);
-    }
-
-    @Override
-    public String getCurrentUserName() {
-        if (mUsers.isEmpty()) return null;
-        UserRecord item = mUsers.stream().filter(x -> x.isCurrent).findFirst().orElse(null);
-        if (item == null || item.info == null) return null;
-        if (item.isGuest) return mContext.getString(com.android.internal.R.string.guest_name);
-        return item.info.name;
-    }
-
-    @Override
-    public void onDensityOrFontScaleChanged() {
-        refreshUsers(UserHandle.USER_ALL);
-    }
-
-    @Override
-    public void addAdapter(WeakReference<BaseUserSwitcherAdapter> adapter) {
-        mAdapters.add(adapter);
-    }
-
-    @Override
-    public ArrayList<UserRecord> getUsers() {
-        return mUsers;
-    }
-
-    @Override
-    public void removeGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId) {
-        UserInfo currentUser = mUserTracker.getUserInfo();
-        if (currentUser.id != guestUserId) {
-            Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
-                    + " is not current user (" + currentUser.id + ")");
-            return;
-        }
-        if (!currentUser.isGuest()) {
-            Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
-                    + " is not a guest");
-            return;
-        }
-
-        boolean marked = mUserManager.markGuestForDeletion(currentUser.id);
-        if (!marked) {
-            Log.w(TAG, "Couldn't mark the guest for deletion for user " + guestUserId);
-            return;
-        }
-
-        if (targetUserId == UserHandle.USER_NULL) {
-            // Create a new guest in the foreground, and then immediately switch to it
-            createGuestAsync(newGuestId -> {
-                if (newGuestId == UserHandle.USER_NULL) {
-                    Log.e(TAG, "Could not create new guest, switching back to system user");
-                    switchToUserId(UserHandle.USER_SYSTEM);
-                    mUserManager.removeUser(currentUser.id);
-                    try {
-                        WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Couldn't remove guest because ActivityManager "
-                                + "or WindowManager is dead");
-                    }
-                    return;
-                }
-                switchToUserId(newGuestId);
-                mUserManager.removeUser(currentUser.id);
-            });
-        } else {
-            if (mGuestUserAutoCreated) {
-                mGuestIsResetting.set(true);
-            }
-            switchToUserId(targetUserId);
-            mUserManager.removeUser(currentUser.id);
-        }
-    }
-
-    @Override
-    public void exitGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId,
-            boolean forceRemoveGuestOnExit) {
-        UserInfo currentUser = mUserTracker.getUserInfo();
-        if (currentUser.id != guestUserId) {
-            Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
-                    + " is not current user (" + currentUser.id + ")");
-            return;
-        }
-        if (!currentUser.isGuest()) {
-            Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
-                    + " is not a guest");
-            return;
-        }
-
-        int newUserId = UserHandle.USER_SYSTEM;
-        if (targetUserId == UserHandle.USER_NULL) {
-            // when target user is not specified switch to last non guest user
-            if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
-                UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
-                if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
-                    newUserId = info.id;
-                }
-            }
-        } else {
-            newUserId = targetUserId;
-        }
-
-        if (currentUser.isEphemeral() || forceRemoveGuestOnExit) {
-            mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
-            removeGuestUser(currentUser.id, newUserId);
-        } else {
-            mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH);
-            switchToUserId(newUserId);
-        }
-    }
-
-    private void scheduleGuestCreation() {
-        if (!mGuestCreationScheduled.compareAndSet(false, true)) {
-            return;
-        }
-
-        mLongRunningExecutor.execute(() -> {
-            int newGuestId = createGuest();
-            mGuestCreationScheduled.set(false);
-            mGuestIsResetting.set(false);
-            if (newGuestId == UserHandle.USER_NULL) {
-                Log.w(TAG, "Could not create new guest while exiting existing guest");
-                // Refresh users so that we still display "Guest" if
-                // config_guestUserAutoCreated=true
-                refreshUsers(UserHandle.USER_NULL);
-            }
-        });
-
-    }
-
-    @Override
-    public void schedulePostBootGuestCreation() {
-        if (isDeviceAllowedToAddGuest()) {
-            guaranteeGuestPresent();
-        } else {
-            mDeviceProvisionedController.addCallback(mGuaranteeGuestPresentAfterProvisioned);
-        }
-    }
-
-    private boolean isDeviceAllowedToAddGuest() {
-        return mDeviceProvisionedController.isDeviceProvisioned()
-                && !mDevicePolicyManager.isDeviceManaged();
-    }
-
-    /**
-     * If there is no guest on the device, schedule creation of a new guest user in the background.
-     */
-    private void guaranteeGuestPresent() {
-        if (isDeviceAllowedToAddGuest() && mUserManager.findCurrentGuestUser() == null) {
-            scheduleGuestCreation();
-        }
-    }
-
-    private void createGuestAsync(Consumer<Integer> callback) {
-        final Dialog guestCreationProgressDialog =
-                new UserCreatingDialog(mContext, /* isGuest= */true);
-        guestCreationProgressDialog.show();
-
-        // userManager.createGuest will block the thread so post is needed for the dialog to show
-        mBgExecutor.execute(() -> {
-            final int guestId = createGuest();
-            mUiExecutor.execute(() -> {
-                guestCreationProgressDialog.dismiss();
-                if (guestId == UserHandle.USER_NULL) {
-                    Toast.makeText(mContext,
-                            com.android.settingslib.R.string.add_guest_failed,
-                            Toast.LENGTH_SHORT).show();
-                }
-                callback.accept(guestId);
-            });
-        });
-    }
-
-    /**
-     * Creates a guest user and return its multi-user user ID.
-     *
-     * This method does not check if a guest already exists before it makes a call to
-     * {@link UserManager} to create a new one.
-     *
-     * @return The multi-user user ID of the newly created guest user, or
-     * {@link UserHandle#USER_NULL} if the guest couldn't be created.
-     */
-    private @UserIdInt int createGuest() {
-        UserInfo guest;
-        try {
-            guest = mUserManager.createGuest(mContext);
-        } catch (UserManager.UserOperationException e) {
-            Log.e(TAG, "Couldn't create guest user", e);
-            return UserHandle.USER_NULL;
-        }
-        if (guest == null) {
-            Log.e(TAG, "Couldn't create guest, most likely because there already exists one");
-            return UserHandle.USER_NULL;
-        }
-        return guest.id;
-    }
-
-    @Override
-    public void init(View view) {
-        mView = view;
-    }
-
-    @Override
-    public boolean isKeyguardShowing() {
-        return mKeyguardStateController.isShowing();
-    }
-
-    private boolean shouldUseSimpleUserSwitcher() {
-        int defaultSimpleUserSwitcher = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_expandLockScreenUserSwitcher) ? 1 : 0;
-        return mGlobalSettings.getIntForUser(SIMPLE_USER_SWITCHER_GLOBAL_SETTING,
-                defaultSimpleUserSwitcher, UserHandle.USER_SYSTEM) != 0;
-    }
-
-    @Override
-    public void startActivity(Intent intent) {
-        mActivityStarter.startActivity(intent, /* dismissShade= */ true);
-    }
-
-    @Override
-    public void addUserSwitchCallback(UserSwitchCallback callback) {
-        mUserSwitchCallbacks.add(callback);
-    }
-
-    @Override
-    public void removeUserSwitchCallback(UserSwitchCallback callback) {
-        mUserSwitchCallbacks.remove(callback);
-    }
-
-    /**
-     *  Notify user switch callbacks that user has switched.
-     */
-    private void notifyUserSwitchCallbacks() {
-        List<UserSwitchCallback> temp;
-        synchronized (mUserSwitchCallbacks) {
-            temp = new ArrayList<>(mUserSwitchCallbacks);
-        }
-        for (UserSwitchCallback callback : temp) {
-            callback.onUserSwitched();
-        }
-    }
-
-    private final KeyguardStateController.Callback mCallback =
-            new KeyguardStateController.Callback() {
-                @Override
-                public void onKeyguardShowingChanged() {
-
-                    // When Keyguard is going away, we don't need to update our items immediately
-                    // which
-                    // helps making the transition faster.
-                    if (!mKeyguardStateController.isShowing()) {
-                        mHandler.post(UserSwitcherControllerOldImpl.this::notifyAdapters);
-                    } else {
-                        notifyAdapters();
-                    }
-                }
-            };
-
-    private final DeviceProvisionedController.DeviceProvisionedListener
-            mGuaranteeGuestPresentAfterProvisioned =
-            new DeviceProvisionedController.DeviceProvisionedListener() {
-                @Override
-                public void onDeviceProvisionedChanged() {
-                    if (isDeviceAllowedToAddGuest()) {
-                        mBgExecutor.execute(
-                                () -> mDeviceProvisionedController.removeCallback(
-                                        mGuaranteeGuestPresentAfterProvisioned));
-                        guaranteeGuestPresent();
-                    }
-                }
-            };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 9866389..b135d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
@@ -28,6 +27,7 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings.Global;
@@ -46,7 +46,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.qs.SettingObserver;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.Utils;
 import com.android.systemui.util.settings.GlobalSettings;
 
@@ -58,14 +58,15 @@
 
 /** Platform implementation of the zen mode controller. **/
 @SysUISingleton
-public class ZenModeControllerImpl extends CurrentUserTracker
-        implements ZenModeController, Dumpable {
+public class ZenModeControllerImpl implements ZenModeController, Dumpable {
     private static final String TAG = "ZenModeController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
     private final Object mCallbacksLock = new Object();
     private final Context mContext;
+    private final UserTracker mUserTracker;
+    private final BroadcastDispatcher mBroadcastDispatcher;
     private final SettingObserver mModeSetting;
     private final SettingObserver mConfigSetting;
     private final NotificationManager mNoMan;
@@ -80,23 +81,45 @@
     private long mZenUpdateTime;
     private NotificationManager.Policy mConsolidatedNotificationPolicy;
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, Context userContext) {
+                    mUserId = newUser;
+                    if (mRegistered) {
+                        mBroadcastDispatcher.unregisterReceiver(mReceiver);
+                    }
+                    final IntentFilter filter = new IntentFilter(
+                            AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+                    filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
+                    mBroadcastDispatcher.registerReceiver(mReceiver, filter, null,
+                            UserHandle.of(mUserId));
+                    mRegistered = true;
+                    mSetupObserver.register();
+                }
+            };
+
     @Inject
     public ZenModeControllerImpl(
             Context context,
             @Main Handler handler,
             BroadcastDispatcher broadcastDispatcher,
             DumpManager dumpManager,
-            GlobalSettings globalSettings) {
-        super(broadcastDispatcher);
+            GlobalSettings globalSettings,
+            UserTracker userTracker) {
         mContext = context;
-        mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE) {
+        mBroadcastDispatcher = broadcastDispatcher;
+        mUserTracker = userTracker;
+        mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE,
+                userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 updateZenMode(value);
                 fireZenChanged(value);
             }
         };
-        mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG) {
+        mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG,
+                userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 updateZenModeConfig();
@@ -112,7 +135,7 @@
         mSetupObserver = new SetupObserver(handler);
         mSetupObserver.register();
         mUserManager = context.getSystemService(UserManager.class);
-        startTracking();
+        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(handler));
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
     }
@@ -120,7 +143,7 @@
     @Override
     public boolean isVolumeRestricted() {
         return mUserManager.hasUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME,
-                new UserHandle(mUserId));
+                UserHandle.of(mUserId));
     }
 
     @Override
@@ -183,19 +206,6 @@
     }
 
     @Override
-    public void onUserSwitched(int userId) {
-        mUserId = userId;
-        if (mRegistered) {
-            mContext.unregisterReceiver(mReceiver);
-        }
-        final IntentFilter filter = new IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
-        filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
-        mContext.registerReceiverAsUser(mReceiver, new UserHandle(mUserId), filter, null, null);
-        mRegistered = true;
-        mSetupObserver.register();
-    }
-
-    @Override
     public ComponentName getEffectsSuppressor() {
         return NotificationManager.from(mContext).getEffectsSuppressor();
     }
@@ -208,7 +218,7 @@
 
     @Override
     public int getCurrentUser() {
-        return ActivityManager.getCurrentUser();
+        return mUserTracker.getUserId();
     }
 
     private void fireNextAlarmChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index b1b45b5..1b73539 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -58,8 +58,6 @@
 import com.android.systemui.statusbar.policy.SecurityControllerImpl;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.UserSwitcherControllerImpl;
 import com.android.systemui.statusbar.policy.WalletController;
 import com.android.systemui.statusbar.policy.WalletControllerImpl;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -198,8 +196,4 @@
     static DataSaverController provideDataSaverController(NetworkController networkController) {
         return networkController.getDataSaverController();
     }
-
-    /** Binds {@link UserSwitcherController} to its implementation. */
-    @Binds
-    UserSwitcherController bindUserSwitcherController(UserSwitcherControllerImpl impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 637fac0..ea40208 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -22,7 +22,6 @@
 import android.graphics.Rect
 import android.graphics.drawable.Drawable
 import android.os.PowerManager
-import android.os.SystemClock
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -35,6 +34,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.wakelock.WakeLock
 
 /**
  * A generic controller that can temporarily display a new view in a new window.
@@ -54,6 +54,7 @@
     private val configurationController: ConfigurationController,
     private val powerManager: PowerManager,
     @LayoutRes private val viewLayoutRes: Int,
+    private val wakeLockBuilder: WakeLock.Builder,
 ) : CoreStartable {
     /**
      * Window layout params that will be used as a starting point for the [windowLayoutParams] of
@@ -84,14 +85,37 @@
     private var cancelViewTimeout: Runnable? = null
 
     /**
+     * A wakelock that is acquired when view is displayed and screen off,
+     * then released when view is removed.
+     */
+    private var wakeLock: WakeLock? = null
+
+    /** A string that keeps track of wakelock reason once it is acquired till it gets released */
+    private var wakeReasonAcquired: String? = null
+
+    /**
+     * A stack of pairs of device id and temporary view info. This is used when there may be
+     * multiple devices in range, and we want to always display the chip for the most recently
+     * active device.
+     */
+    internal val activeViews: ArrayDeque<Pair<String, T>> = ArrayDeque()
+
+    /**
      * Displays the view with the provided [newInfo].
      *
      * This method handles inflating and attaching the view, then delegates to [updateView] to
      * display the correct information in the view.
+     * @param onViewTimeout a runnable that runs after the view timeout.
      */
-    fun displayView(newInfo: T) {
+    fun displayView(newInfo: T, onViewTimeout: Runnable? = null) {
         val currentDisplayInfo = displayInfo
 
+        // Update our list of active devices by removing it if necessary, then adding back at the
+        // front of the list
+        val id = newInfo.id
+        val position = findAndRemoveFromActiveViewsList(id)
+        activeViews.addFirst(Pair(id, newInfo))
+
         if (currentDisplayInfo != null &&
             currentDisplayInfo.info.windowTitle == newInfo.windowTitle) {
             // We're already displaying information in the correctly-titled window, so we just need
@@ -103,23 +127,37 @@
                 // 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}")
+                removeView(
+                    id,
+                    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:${newInfo.wakeReason}",
-                )
+
+            wakeLock = if (!powerManager.isScreenOn) {
+                // If the screen is off, fully wake it so the user can see the view.
+                wakeLockBuilder
+                    .setTag(newInfo.windowTitle)
+                    .setLevelsAndFlags(
+                            PowerManager.FULL_WAKE_LOCK or
+                                PowerManager.ACQUIRE_CAUSES_WAKEUP
+                    )
+                    .build()
+            } else {
+                // Per b/239426653, we want the view to show over the dream state.
+                // If the screen is on, using screen bright level will leave screen on the dream
+                // state but ensure the screen will not go off before wake lock is released.
+                wakeLockBuilder
+                    .setTag(newInfo.windowTitle)
+                    .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
+                    .build()
             }
-            logger.logViewAddition(newInfo.windowTitle)
+            wakeLock?.acquire(newInfo.wakeReason)
+            wakeReasonAcquired = newInfo.wakeReason
+            logger.logViewAddition(id, newInfo.windowTitle)
             inflateAndUpdateView(newInfo)
         }
 
@@ -130,9 +168,16 @@
             // include it just to be safe.
             FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
        )
-        cancelViewTimeout?.run()
+
+        // Only cancel timeout of the most recent view displayed, as it will be reset.
+        if (position == 0) {
+            cancelViewTimeout?.run()
+        }
         cancelViewTimeout = mainExecutor.executeDelayed(
-            { removeView(REMOVAL_REASON_TIMEOUT) },
+            {
+                removeView(id, REMOVAL_REASON_TIMEOUT)
+                onViewTimeout?.run()
+            },
             timeout.toLong()
         )
     }
@@ -155,6 +200,7 @@
             it.copyFrom(windowLayoutParams)
             it.title = newInfo.windowTitle
         }
+        newView.keepScreenOn = true
         windowManager.addView(newView, paramsWithTitle)
         animateViewIn(newView)
     }
@@ -174,25 +220,67 @@
     }
 
     /**
-     * Hides the view.
+     * Hides the view given its [id].
      *
+     * @param id the id of the device responsible of displaying the temp view.
      * @param removalReason a short string describing why the view was removed (timeout, state
      *     change, etc.)
      */
-    fun removeView(removalReason: String) {
+    fun removeView(id: String, removalReason: String) {
         val currentDisplayInfo = displayInfo ?: return
 
-        val currentView = currentDisplayInfo.view
-        animateViewOut(currentView) { windowManager.removeView(currentView) }
+        val removalPosition = findAndRemoveFromActiveViewsList(id)
+        if (removalPosition == null) {
+            logger.logViewRemovalIgnored(id, "view not found in the list")
+            return
+        }
+        if (removalPosition != 0) {
+            logger.logViewRemovalIgnored(id, "most recent view is being displayed.")
+            return
+        }
+        logger.logViewRemoval(id, removalReason)
 
-        logger.logViewRemoval(removalReason)
+        val newViewToDisplay = if (activeViews.isEmpty()) {
+            null
+        } else {
+            activeViews[0].second
+        }
+
+        val currentView = currentDisplayInfo.view
+        animateViewOut(currentView) {
+            windowManager.removeView(currentView)
+            wakeLock?.release(wakeReasonAcquired)
+        }
+
         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
-        // new view appropriately.
+        // that if a new view event comes in while this view is animating out, we still display
+        // the new view appropriately.
         displayInfo = null
         // No need to time the view out since it's already gone
         cancelViewTimeout?.run()
+
+        if (newViewToDisplay != null) {
+            mainExecutor.executeDelayed({ displayView(newViewToDisplay)}, DISPLAY_VIEW_DELAY)
+        }
+    }
+
+    /**
+     * Finds and removes the active view with the given [id] from the stack, or null if there is no
+     * active view with that ID
+     *
+     * @param id that temporary view belonged to.
+     *
+     * @return index of the view in the stack , otherwise null.
+     */
+    private fun findAndRemoveFromActiveViewsList(id: String): Int? {
+        for (i in 0 until activeViews.size) {
+            if (activeViews[i].first == id) {
+                activeViews.removeAt(i)
+                return i
+            }
+        }
+        return null
     }
 
     /**
@@ -233,6 +321,7 @@
 }
 
 private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT"
+const val DISPLAY_VIEW_DELAY = 50L
 
 private data class IconInfo(
     val iconName: String,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
index cbb5002..df83960 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
@@ -37,6 +37,11 @@
      * disappears.
      */
     open val timeoutMs: Int = DEFAULT_TIMEOUT_MILLIS
+
+    /**
+     * The id of the temporary view.
+     */
+    abstract val id: String
 }
 
 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 428a104..133a384 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -24,13 +24,42 @@
     internal val buffer: LogBuffer,
     internal val tag: String,
 ) {
-    /** 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 added the view with the given [id] in a window titled [windowTitle]. */
+    fun logViewAddition(id: String, windowTitle: String) {
+        buffer.log(
+            tag,
+            LogLevel.DEBUG,
+            {
+                str1 = windowTitle
+                str2 = id
+            },
+            { "View added. window=$str1 id=$str2" }
+        )
     }
 
-    /** Logs that we removed the chip for the given [reason]. */
-    fun logViewRemoval(reason: String) {
-        buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "View removed due to: $str1" })
+    /** Logs that we removed the view with the given [id] for the given [reason]. */
+    fun logViewRemoval(id: String, reason: String) {
+        buffer.log(
+            tag,
+            LogLevel.DEBUG,
+            {
+                str1 = reason
+                str2 = id
+            },
+            { "View with id=$str2 is removed due to: $str1" }
+        )
+    }
+
+    /** Logs that we ignored removal of the view with the given [id]. */
+    fun logViewRemovalIgnored(id: String, reason: String) {
+        buffer.log(
+            tag,
+            LogLevel.DEBUG,
+            {
+                str1 = reason
+                str2 = id
+            },
+            { "Removal of view with id=$str2 is ignored because $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 87b6e8d..fb17b69 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLock
 import javax.inject.Inject
 
 /**
@@ -75,6 +76,7 @@
         private val falsingCollector: FalsingCollector,
         private val viewUtil: ViewUtil,
         private val vibratorHelper: VibratorHelper,
+        wakeLockBuilder: WakeLock.Builder,
 ) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
         context,
         logger,
@@ -84,6 +86,7 @@
         configurationController,
         powerManager,
         R.layout.chipbar,
+        wakeLockBuilder,
 ) {
 
     private lateinit var parent: ChipbarRootView
@@ -92,8 +95,6 @@
         gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
     }
 
-    override fun start() {}
-
     override fun updateView(
         newInfo: ChipbarInfo,
         currentView: ViewGroup
@@ -173,7 +174,7 @@
             chipInnerView,
             ViewHierarchyAnimator.Hotspot.TOP,
             Interpolators.EMPHASIZED_DECELERATE,
-            duration = ANIMATION_DURATION,
+            duration = ANIMATION_IN_DURATION,
             includeMargins = true,
             includeFadeIn = true,
             // We can only request focus once the animation finishes.
@@ -186,12 +187,14 @@
             view.requireViewById<ViewGroup>(R.id.chipbar_inner),
             ViewHierarchyAnimator.Hotspot.TOP,
             Interpolators.EMPHASIZED_ACCELERATE,
-            ANIMATION_DURATION,
+            ANIMATION_OUT_DURATION,
             includeMargins = true,
             onAnimationEnd,
         )
     }
 
+    override fun start() {}
+
     override fun getTouchableRegion(view: View, outRect: Rect) {
         viewUtil.setRectToViewWindowLocation(view, outRect)
     }
@@ -205,4 +208,5 @@
     }
 }
 
-private const val ANIMATION_DURATION = 500L
+private const val ANIMATION_IN_DURATION = 500L
+private const val ANIMATION_OUT_DURATION = 250L
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 6237365..b92e0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -40,6 +40,7 @@
     override val windowTitle: String,
     override val wakeReason: String,
     override val timeoutMs: Int,
+    override val id: String,
 ) : TemporaryViewInfo()
 
 /** The possible items to display at the end of the chipbar. */
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/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
index 26cc6ba3..f3b9cc1 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -25,8 +25,8 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.ToastPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
 
 import java.io.PrintWriter;
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
index fe183fc..4999515 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
@@ -38,9 +38,9 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.plugins.PluginEnablerImpl;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.shared.plugins.PluginActionManager;
 import com.android.systemui.shared.plugins.PluginEnabler;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.plugins.PluginPrefs;
 
 import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 10a09dd1..5ea4399 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -44,9 +44,11 @@
 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;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -89,6 +91,7 @@
         includes = {
                 AospPolicyModule.class,
                 GestureModule.class,
+                MultiUserUtilsModule.class,
                 PowerModule.class,
                 QSModule.class,
                 ReferenceScreenshotModule.class,
@@ -157,7 +160,8 @@
             ConfigurationController configurationController,
             @Main Handler handler,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            ShadeExpansionStateManager shadeExpansionStateManager) {
         return new HeadsUpManagerPhone(
                 context,
                 headsUpManagerLogger,
@@ -168,7 +172,8 @@
                 configurationController,
                 handler,
                 accessibilityManagerWrapper,
-                uiEventLogger
+                uiEventLogger,
+                shadeExpansionStateManager
         );
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index 0f06144..6216acd 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -92,6 +92,7 @@
         deviceStateManager.registerCallback(executor, FoldListener())
         wakefulnessLifecycle.addObserver(this)
 
+        // TODO(b/254878364): remove this call to NPVC.getView()
         centralSurfaces.notificationPanelViewController.view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) { listenForDozing(this) }
         }
@@ -157,6 +158,7 @@
             // We don't need to wait for the scrim as it is already displayed
             // but we should wait for the initial animation preparations to be drawn
             // (setting initial alpha/translation)
+            // TODO(b/254878364): remove this call to NPVC.getView()
             OneShotPreDrawListener.add(
                 centralSurfaces.notificationPanelViewController.view,
                 onReady
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 6ed3a09..b2ec27c 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -15,13 +15,15 @@
  */
 package com.android.systemui.unfold
 
-import android.animation.ValueAnimator
+import android.content.ContentResolver
 import android.content.Context
 import android.graphics.PixelFormat
 import android.hardware.devicestate.DeviceStateManager
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener
 import android.hardware.display.DisplayManager
 import android.hardware.input.InputManager
+import android.os.Handler
+import android.os.Looper
 import android.os.Trace
 import android.view.Choreographer
 import android.view.Display
@@ -32,13 +34,18 @@
 import android.view.SurfaceSession
 import android.view.WindowManager
 import android.view.WindowlessWindowManager
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.LinearLightRevealEffect
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.FOLD
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.UNFOLD
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.updates.RotationChangeProvider
+import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
+import com.android.systemui.util.Assert.isMainThread
 import com.android.systemui.util.traceSection
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper
 import java.util.Optional
@@ -52,11 +59,13 @@
 constructor(
     private val context: Context,
     private val deviceStateManager: DeviceStateManager,
+    private val contentResolver: ContentResolver,
     private val displayManager: DisplayManager,
     private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
     private val displayAreaHelper: Optional<DisplayAreaHelper>,
     @Main private val executor: Executor,
     @UiBackground private val backgroundExecutor: Executor,
+    @Background private val bgHandler: Handler,
     private val rotationChangeProvider: RotationChangeProvider,
 ) {
 
@@ -92,7 +101,7 @@
                 overlayContainer = builder.build()
 
                 SurfaceControl.Transaction()
-                    .setLayer(overlayContainer, Integer.MAX_VALUE)
+                    .setLayer(overlayContainer, UNFOLD_OVERLAY_LAYER_Z_INDEX)
                     .show(overlayContainer)
                     .apply()
 
@@ -117,12 +126,12 @@
         Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn")
         try {
             // Add the view only if we are unfolding and this is the first screen on
-            if (!isFolded && !isUnfoldHandled && ValueAnimator.areAnimatorsEnabled()) {
-                addView(onOverlayReady)
+            if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) {
+                executeInBackground { addOverlay(onOverlayReady, reason = UNFOLD) }
                 isUnfoldHandled = true
             } else {
                 // No unfold transition, immediately report that overlay is ready
-                ensureOverlayRemoved()
+                executeInBackground { ensureOverlayRemoved() }
                 onOverlayReady.run()
             }
         } finally {
@@ -130,13 +139,14 @@
         }
     }
 
-    private fun addView(onOverlayReady: Runnable? = null) {
+    private fun addOverlay(onOverlayReady: Runnable? = null, reason: AddOverlayReason) {
         if (!::wwm.isInitialized) {
             // Surface overlay is not created yet on the first SysUI launch
             onOverlayReady?.run()
             return
         }
 
+        ensureInBackground()
         ensureOverlayRemoved()
 
         val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
@@ -144,13 +154,16 @@
             LightRevealScrim(context, null).apply {
                 revealEffect = createLightRevealEffect()
                 isScrimOpaqueChangedListener = Consumer {}
-                revealAmount = 0f
+                revealAmount = when (reason) {
+                    FOLD -> TRANSPARENT
+                    UNFOLD -> BLACK
+                }
             }
 
         val params = getLayoutParams()
         newRoot.setView(newView, params)
 
-        onOverlayReady?.let { callback ->
+        if (onOverlayReady != null) {
             Trace.beginAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
 
             newRoot.relayout(params) { transaction ->
@@ -162,14 +175,13 @@
                 // blocker (turn on the brightness) only when the content is actually visible as it
                 // might be presented only in the next frame.
                 // See b/197538198
-                transaction
-                    .setFrameTimelineVsync(vsyncId)
-                    .apply()
+                transaction.setFrameTimelineVsync(vsyncId).apply()
 
-                transaction.setFrameTimelineVsync(vsyncId + 1)
+                transaction
+                    .setFrameTimelineVsync(vsyncId + 1)
                     .addTransactionCommittedListener(backgroundExecutor) {
                         Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
-                        callback.run()
+                        onOverlayReady.run()
                     }
                     .apply()
             }
@@ -212,14 +224,16 @@
     }
 
     private fun ensureOverlayRemoved() {
-        root?.release()
-        root = null
-        scrimView = null
+        ensureInBackground()
+        traceSection("ensureOverlayRemoved") {
+            root?.release()
+            root = null
+            scrimView = null
+        }
     }
 
     private fun getUnfoldedDisplayInfo(): DisplayInfo =
-        displayManager
-            .displays
+        displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
             .asSequence()
             .map { DisplayInfo().apply { it.getDisplayInfo(this) } }
             .filter { it.type == Display.TYPE_INTERNAL }
@@ -228,17 +242,17 @@
     private inner class TransitionListener : TransitionProgressListener {
 
         override fun onTransitionProgress(progress: Float) {
-            scrimView?.revealAmount = progress
+            executeInBackground { scrimView?.revealAmount = progress }
         }
 
         override fun onTransitionFinished() {
-            ensureOverlayRemoved()
+            executeInBackground { ensureOverlayRemoved() }
         }
 
         override fun onTransitionStarted() {
             // Add view for folding case (when unfolding the view is added earlier)
             if (scrimView == null) {
-                addView()
+                executeInBackground { addOverlay(reason = FOLD) }
             }
             // Disable input dispatching during transition.
             InputManager.getInstance().cancelCurrentTouch()
@@ -250,21 +264,52 @@
             traceSection("UnfoldLightRevealOverlayAnimation#onRotationChanged") {
                 if (currentRotation != newRotation) {
                     currentRotation = newRotation
-                    scrimView?.revealEffect = createLightRevealEffect()
-                    root?.relayout(getLayoutParams())
+                    executeInBackground {
+                        scrimView?.revealEffect = createLightRevealEffect()
+                        root?.relayout(getLayoutParams())
+                    }
                 }
             }
         }
     }
 
+    private fun executeInBackground(f: () -> Unit) {
+        ensureInMainThread()
+        // The UiBackground executor is not used as it doesn't have a prepared looper.
+        bgHandler.post(f)
+    }
+
+    private fun ensureInBackground() {
+        check(Looper.myLooper() == bgHandler.looper) { "Not being executed in the background!" }
+    }
+
+    private fun ensureInMainThread() {
+        isMainThread()
+    }
+
     private inner class FoldListener :
         FoldStateListener(
             context,
             Consumer { isFolded ->
                 if (isFolded) {
-                    ensureOverlayRemoved()
+                    executeInBackground { ensureOverlayRemoved() }
                     isUnfoldHandled = false
                 }
                 this.isFolded = isFolded
-            })
+            }
+        )
+
+    private enum class AddOverlayReason { FOLD, UNFOLD }
+
+    private companion object {
+        const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
+
+        // Put the unfold overlay below the rotation animation screenshot to hide the moment
+        // when it is rotated but the rotation of the other windows hasn't happen yet
+        const val UNFOLD_OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
+
+        // constants for revealAmount.
+        const val TRANSPARENT = 1f
+        const val BLACK = 0f
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index bf70673..095718b 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -46,6 +46,7 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.SystemUIApplication;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.util.NotificationChannels;
 
@@ -61,15 +62,24 @@
     private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME";
     private static final String ACTION_FINISH_WIZARD = "com.android.systemui.action.FINISH_WIZARD";
     private final Context mContext;
+    private final BroadcastDispatcher mBroadcastDispatcher;
 
     // TODO: delay some notifications to avoid bumpy fast operations
 
-    private NotificationManager mNotificationManager;
-    private StorageManager mStorageManager;
+    private final NotificationManager mNotificationManager;
+    private final StorageManager mStorageManager;
 
     @Inject
-    public StorageNotification(Context context) {
+    public StorageNotification(
+            Context context,
+            BroadcastDispatcher broadcastDispatcher,
+            NotificationManager notificationManager,
+            StorageManager storageManager
+    ) {
         mContext = context;
+        mBroadcastDispatcher = broadcastDispatcher;
+        mNotificationManager = notificationManager;
+        mStorageManager = storageManager;
     }
 
     private static class MoveInfo {
@@ -168,17 +178,22 @@
 
     @Override
     public void start() {
-        mNotificationManager = mContext.getSystemService(NotificationManager.class);
-
-        mStorageManager = mContext.getSystemService(StorageManager.class);
         mStorageManager.registerListener(mListener);
 
-        mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME),
-                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null,
-                Context.RECEIVER_EXPORTED_UNAUDITED);
-        mContext.registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_WIZARD),
-                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null,
-                Context.RECEIVER_EXPORTED_UNAUDITED);
+        mBroadcastDispatcher.registerReceiver(
+                mSnoozeReceiver,
+                new IntentFilter(ACTION_SNOOZE_VOLUME),
+                null,
+                null,
+                Context.RECEIVER_EXPORTED_UNAUDITED,
+                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+        mBroadcastDispatcher.registerReceiver(
+                mFinishReceiver,
+                new IntentFilter(ACTION_FINISH_WIZARD),
+                null,
+                null,
+                Context.RECEIVER_EXPORTED_UNAUDITED,
+                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
 
         // Kick current state into place
         final List<DiskInfo> disks = mStorageManager.getDisks();
@@ -381,7 +396,7 @@
 
         // Don't annoy when user dismissed in past.  (But make sure the disk is adoptable; we
         // used to allow snoozing non-adoptable disks too.)
-        if (rec.isSnoozed() && disk.isAdoptable()) {
+        if (rec == null || (rec.isSnoozed() && disk.isAdoptable())) {
             return null;
         }
         if (disk.isAdoptable() && !rec.isInited() && rec.getType() != VolumeInfo.TYPE_PUBLIC
diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
index f017126..b56c403 100644
--- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -25,6 +25,8 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Log;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -59,6 +61,7 @@
     private final ActivityStarter mActivityStarter;
 
     private Dialog mSetupUserDialog;
+    private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
 
     @Inject
     public CreateUserActivity(UserCreator userCreator,
@@ -82,6 +85,10 @@
 
         mSetupUserDialog = createDialog();
         mSetupUserDialog.show();
+
+        getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                        OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                        mBackCallback);
     }
 
     @Override
@@ -125,10 +132,20 @@
 
     @Override
     public void onBackPressed() {
-        super.onBackPressed();
+        onBackInvoked();
+    }
+
+    private void onBackInvoked() {
         if (mSetupUserDialog != null) {
             mSetupUserDialog.dismiss();
         }
+        finish();
+    }
+
+    @Override
+    protected void onDestroy() {
+        getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback);
+        super.onDestroy();
     }
 
     private void addUserNow(String userName, Drawable userIcon) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index 0c72b78..2b29885 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -17,6 +17,7 @@
 package com.android.systemui.user;
 
 import android.app.Activity;
+import android.os.UserHandle;
 
 import com.android.settingslib.users.EditUserInfoController;
 import com.android.systemui.user.data.repository.UserRepositoryModule;
@@ -51,4 +52,22 @@
     @IntoMap
     @ClassKey(UserSwitcherActivity.class)
     public abstract Activity provideUserSwitcherActivity(UserSwitcherActivity activity);
+
+    /**
+     * Provides the {@link UserHandle} for the user associated with this System UI process.
+     *
+     * <p>Note that this is static and unchanging for the life-time of the process we are running
+     * in. It can be <i>different</i> from the user that is the currently-selected user, which may
+     * be associated with a different System UI process.
+     *
+     * <p>For example, the System UI process which creates all the windows and renders UI is always
+     * the one associated with the primary user on the device. However, if the user is switched to
+     * another, non-primary user (for example user "X"), then a secondary System UI process will be
+     * spawned. While the original primary user process continues to be the only one rendering UI,
+     * the new system UI process may be used for things like file or content access.
+     */
+    @Provides
+    public static UserHandle provideUserHandle() {
+        return new UserHandle(UserHandle.myUserId());
+    }
 }
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..c0f0390 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
@@ -19,31 +19,19 @@
 
 import android.content.Context
 import android.content.pm.UserInfo
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.Drawable
 import android.os.UserHandle
 import android.os.UserManager
 import android.provider.Settings
 import androidx.annotation.VisibleForTesting
-import androidx.appcompat.content.res.AppCompatResources
-import com.android.internal.util.UserIcons
 import com.android.systemui.R
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 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.settings.UserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import java.util.concurrent.atomic.AtomicBoolean
@@ -55,13 +43,13 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 
 /**
@@ -71,15 +59,6 @@
  * upstream changes.
  */
 interface UserRepository {
-    /** List of all users on the device. */
-    val users: Flow<List<UserModel>>
-
-    /** The currently-selected user. */
-    val selectedUser: Flow<UserModel>
-
-    /** List of available user-related actions. */
-    val actions: Flow<List<UserActionModel>>
-
     /** User switcher related settings. */
     val userSwitcherSettings: Flow<UserSwitcherSettingsModel>
 
@@ -92,9 +71,6 @@
     /** User ID of the last non-guest selected user. */
     val lastSelectedNonGuestUserId: Int
 
-    /** Whether actions are available even when locked. */
-    val isActionableWhenLocked: Flow<Boolean>
-
     /** Whether the device is configured to always have a guest user available. */
     val isGuestUserAutoCreated: Boolean
 
@@ -104,6 +80,9 @@
     /** Whether we've scheduled the creation of a guest user. */
     val isGuestUserCreationScheduled: AtomicBoolean
 
+    /** Whether to enable the status bar user chip (which launches the user switcher) */
+    val isStatusBarUserChipEnabled: Boolean
+
     /** The user of the secondary service. */
     var secondaryUserId: Int
 
@@ -124,19 +103,14 @@
 constructor(
     @Application private val appContext: Context,
     private val manager: UserManager,
-    private val controller: UserSwitcherController,
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val globalSettings: GlobalSettings,
     private val tracker: UserTracker,
-    private val featureFlags: FeatureFlags,
 ) : UserRepository {
 
-    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()
 
@@ -149,70 +123,24 @@
     override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
         private set
 
-    private val userRecords: Flow<List<UserRecord>> = conflatedCallbackFlow {
-        fun send() {
-            trySendWithFailureLogging(
-                controller.users,
-                TAG,
-            )
-        }
-
-        val callback = UserSwitcherController.UserSwitchCallback { send() }
-
-        controller.addUserSwitchCallback(callback)
-        send()
-
-        awaitClose { controller.removeUserSwitchCallback(callback) }
-    }
-
-    override val users: Flow<List<UserModel>> =
-        userRecords.map { records -> records.filter { it.isUser() }.map { it.toUserModel() } }
-
-    override val selectedUser: Flow<UserModel> =
-        users.map { users -> users.first { user -> user.isSelected } }
-
-    override val actions: Flow<List<UserActionModel>> =
-        userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } }
-
-    override val isActionableWhenLocked: Flow<Boolean> =
-        if (isNewImpl) {
-            emptyFlow()
-        } else {
-            controller.isAddUsersFromLockScreenEnabled
-        }
-
     override val isGuestUserAutoCreated: Boolean =
-        if (isNewImpl) {
-            appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
-        } else {
-            controller.isGuestUserAutoCreated
-        }
+        appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
 
     private var _isGuestUserResetting: Boolean = false
-    override var isGuestUserResetting: Boolean =
-        if (isNewImpl) {
-            _isGuestUserResetting
-        } else {
-            controller.isGuestUserResetting
-        }
-        set(value) =
-            if (isNewImpl) {
-                _isGuestUserResetting = value
-            } else {
-                error("Not supported in the old implementation!")
-            }
+    override var isGuestUserResetting: Boolean = _isGuestUserResetting
 
     override val isGuestUserCreationScheduled = AtomicBoolean()
 
+    override val isStatusBarUserChipEnabled: Boolean =
+        appContext.resources.getBoolean(R.bool.flag_user_switcher_chip)
+
     override var secondaryUserId: Int = UserHandle.USER_NULL
 
     override var isRefreshUsersPaused: Boolean = false
 
     init {
-        if (isNewImpl) {
-            observeSelectedUser()
-            observeUserSettings()
-        }
+        observeSelectedUser()
+        observeUserSettings()
     }
 
     override fun refreshUsers() {
@@ -235,7 +163,7 @@
     }
 
     override fun isSimpleUserSwitcher(): Boolean {
-        return checkNotNull(_userSwitcherSettings.value?.isSimpleUserSwitcher)
+        return _userSwitcherSettings.value.isSimpleUserSwitcher
     }
 
     private fun observeSelectedUser() {
@@ -249,6 +177,10 @@
                         override fun onUserChanged(newUser: Int, userContext: Context) {
                             send()
                         }
+
+                        override fun onProfilesChanged(profiles: List<UserInfo>) {
+                            send()
+                        }
                     }
 
                 tracker.addCallback(callback, mainDispatcher.asExecutor())
@@ -310,7 +242,15 @@
             val isUserSwitcherEnabled =
                 globalSettings.getIntForUser(
                     Settings.Global.USER_SWITCHER_ENABLED,
-                    0,
+                    if (
+                        appContext.resources.getBoolean(
+                            com.android.internal.R.bool.config_showUserSwitcherByDefault
+                        )
+                    ) {
+                        1
+                    } else {
+                        0
+                    },
                     UserHandle.USER_SYSTEM,
                 ) != 0
 
@@ -322,64 +262,6 @@
         }
     }
 
-    private fun UserRecord.isUser(): Boolean {
-        return when {
-            isAddUser -> false
-            isAddSupervisedUser -> false
-            isManageUsers -> false
-            isGuest -> info != null
-            else -> true
-        }
-    }
-
-    private fun UserRecord.isNotUser(): Boolean {
-        return !isUser()
-    }
-
-    private fun UserRecord.toUserModel(): UserModel {
-        return UserModel(
-            id = resolveId(),
-            name = getUserName(this),
-            image = getUserImage(this),
-            isSelected = isCurrent,
-            isSelectable = isSwitchToEnabled || isGuest,
-            isGuest = isGuest,
-        )
-    }
-
-    private fun UserRecord.toActionModel(): UserActionModel {
-        return when {
-            isAddUser -> UserActionModel.ADD_USER
-            isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
-            isGuest -> UserActionModel.ENTER_GUEST_MODE
-            isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
-            else -> error("Don't know how to convert to UserActionModel: $this")
-        }
-    }
-
-    private fun getUserName(record: UserRecord): Text {
-        val resourceId: Int? = LegacyUserUiHelper.getGuestUserRecordNameResourceId(record)
-        return if (resourceId != null) {
-            Text.Resource(resourceId)
-        } else {
-            Text.Loaded(checkNotNull(record.info).name)
-        }
-    }
-
-    private fun getUserImage(record: UserRecord): Drawable {
-        if (record.isGuest) {
-            return checkNotNull(
-                AppCompatResources.getDrawable(appContext, R.drawable.ic_account_circle)
-            )
-        }
-
-        val userId = checkNotNull(record.info?.id)
-        return manager.getUserIcon(userId)?.let { userSelectedIcon ->
-            BitmapDrawable(userSelectedIcon)
-        }
-            ?: UserIcons.getDefaultUserIcon(appContext.resources, userId, /* light= */ false)
-    }
-
     companion object {
         private const val TAG = "UserRepository"
         @VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
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/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index dda78aa..512fadf 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -34,6 +34,7 @@
 import com.android.internal.util.UserIcons
 import com.android.systemui.R
 import com.android.systemui.SystemUISecondaryUserService
+import com.android.systemui.animation.Expandable
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.SysUISingleton
@@ -44,8 +45,9 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.UserSwitcherActivity
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.user.data.source.UserRecord
 import com.android.systemui.user.domain.model.ShowDialogRequestModel
@@ -64,8 +66,6 @@
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -82,7 +82,6 @@
 constructor(
     @Application private val applicationContext: Context,
     private val repository: UserRepository,
-    private val controller: UserSwitcherController,
     private val activityStarter: ActivityStarter,
     private val keyguardInteractor: KeyguardInteractor,
     private val featureFlags: FeatureFlags,
@@ -107,9 +106,6 @@
         fun onUserStateChanged()
     }
 
-    private val isNewImpl: Boolean
-        get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
     private val supervisedUserPackageName: String?
         get() =
             applicationContext.getString(
@@ -118,185 +114,161 @@
 
     private val callbackMutex = Mutex()
     private val callbacks = mutableSetOf<UserCallback>()
+    private val userInfos: Flow<List<UserInfo>> =
+        repository.userInfos.map { userInfos ->
+            userInfos.filter { it.isFull }
+        }
 
     /** List of current on-device users to select from. */
     val users: Flow<List<UserModel>>
         get() =
-            if (isNewImpl) {
-                combine(
-                    repository.userInfos,
-                    repository.selectedUserInfo,
-                    repository.userSwitcherSettings,
-                ) { userInfos, selectedUserInfo, settings ->
-                    toUserModels(
-                        userInfos = userInfos,
-                        selectedUserId = selectedUserInfo.id,
-                        isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
-                    )
-                }
-            } else {
-                repository.users
+            combine(
+                userInfos,
+                repository.selectedUserInfo,
+                repository.userSwitcherSettings,
+            ) { userInfos, selectedUserInfo, settings ->
+                toUserModels(
+                    userInfos = userInfos,
+                    selectedUserId = selectedUserInfo.id,
+                    isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
+                )
             }
 
     /** The currently-selected user. */
     val selectedUser: Flow<UserModel>
         get() =
-            if (isNewImpl) {
-                combine(
-                    repository.selectedUserInfo,
-                    repository.userSwitcherSettings,
-                ) { selectedUserInfo, settings ->
-                    val selectedUserId = selectedUserInfo.id
-                    checkNotNull(
-                        toUserModel(
-                            userInfo = selectedUserInfo,
-                            selectedUserId = selectedUserId,
-                            canSwitchUsers = canSwitchUsers(selectedUserId),
-                            isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
-                        )
-                    )
-                }
-            } else {
-                repository.selectedUser
+            repository.selectedUserInfo.map { selectedUserInfo ->
+                val selectedUserId = selectedUserInfo.id
+                toUserModel(
+                    userInfo = selectedUserInfo,
+                    selectedUserId = selectedUserId,
+                    canSwitchUsers = canSwitchUsers(selectedUserId)
+                )
             }
 
     /** List of user-switcher related actions that are available. */
     val actions: Flow<List<UserActionModel>>
         get() =
-            if (isNewImpl) {
-                combine(
-                    repository.selectedUserInfo,
-                    repository.userInfos,
-                    repository.userSwitcherSettings,
-                    keyguardInteractor.isKeyguardShowing,
-                ) { _, userInfos, settings, isDeviceLocked ->
-                    buildList {
-                        val hasGuestUser = userInfos.any { it.isGuest }
-                        if (
-                            !hasGuestUser &&
-                                (guestUserInteractor.isGuestUserAutoCreated ||
-                                    UserActionsUtil.canCreateGuest(
-                                        manager,
-                                        repository,
-                                        settings.isUserSwitcherEnabled,
-                                        settings.isAddUsersFromLockscreen,
-                                    ))
-                        ) {
-                            add(UserActionModel.ENTER_GUEST_MODE)
-                        }
+            combine(
+                repository.selectedUserInfo,
+                userInfos,
+                repository.userSwitcherSettings,
+                keyguardInteractor.isKeyguardShowing,
+            ) { _, userInfos, settings, isDeviceLocked ->
+                buildList {
+                    if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
+                        // The device is locked and our setting to allow actions that add users
+                        // from the lock-screen is not enabled. We can finish building the list
+                        // here.
+                        val isFullScreen = featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)
 
-                        if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
-                            // The device is locked and our setting to allow actions that add users
-                            // from the lock-screen is not enabled. The guest action from above is
-                            // always allowed, even when the device is locked, but the various "add
-                            // user" actions below are not. We can finish building the list here.
-
-                            val canCreateUsers =
-                                UserActionsUtil.canCreateUser(
-                                    manager,
-                                    repository,
-                                    settings.isUserSwitcherEnabled,
-                                    settings.isAddUsersFromLockscreen,
+                        val actionList: List<UserActionModel> =
+                            if (isFullScreen) {
+                                listOf(
+                                    UserActionModel.ADD_USER,
+                                    UserActionModel.ADD_SUPERVISED_USER,
+                                    UserActionModel.ENTER_GUEST_MODE,
                                 )
-
-                            if (canCreateUsers) {
-                                add(UserActionModel.ADD_USER)
+                            } else {
+                                listOf(
+                                    UserActionModel.ENTER_GUEST_MODE,
+                                    UserActionModel.ADD_USER,
+                                    UserActionModel.ADD_SUPERVISED_USER,
+                                )
                             }
+                        actionList.map {
+                            when (it) {
+                                UserActionModel.ENTER_GUEST_MODE -> {
+                                    val hasGuestUser = userInfos.any { it.isGuest }
+                                    if (!hasGuestUser && canCreateGuestUser(settings)) {
+                                        add(UserActionModel.ENTER_GUEST_MODE)
+                                    }
+                                }
+                                UserActionModel.ADD_USER -> {
+                                    val canCreateUsers =
+                                        UserActionsUtil.canCreateUser(
+                                            manager,
+                                            repository,
+                                            settings.isUserSwitcherEnabled,
+                                            settings.isAddUsersFromLockscreen,
+                                        )
 
-                            if (
-                                UserActionsUtil.canCreateSupervisedUser(
-                                    manager,
-                                    repository,
-                                    settings.isUserSwitcherEnabled,
-                                    settings.isAddUsersFromLockscreen,
-                                    supervisedUserPackageName,
-                                )
-                            ) {
-                                add(UserActionModel.ADD_SUPERVISED_USER)
+                                    if (canCreateUsers) {
+                                        add(UserActionModel.ADD_USER)
+                                    }
+                                }
+                                UserActionModel.ADD_SUPERVISED_USER -> {
+                                    if (
+                                        UserActionsUtil.canCreateSupervisedUser(
+                                            manager,
+                                            repository,
+                                            settings.isUserSwitcherEnabled,
+                                            settings.isAddUsersFromLockscreen,
+                                            supervisedUserPackageName,
+                                        )
+                                    ) {
+                                        add(UserActionModel.ADD_SUPERVISED_USER)
+                                    }
+                                }
+                                else -> Unit
                             }
                         }
-
-                        if (
-                            UserActionsUtil.canManageUsers(
-                                repository,
-                                settings.isUserSwitcherEnabled,
-                                settings.isAddUsersFromLockscreen,
-                            )
-                        ) {
-                            add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-                        }
+                    }
+                    if (
+                        UserActionsUtil.canManageUsers(
+                            repository,
+                            settings.isUserSwitcherEnabled,
+                            settings.isAddUsersFromLockscreen,
+                        )
+                    ) {
+                        add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
                     }
                 }
-            } else {
-                combine(
-                        repository.isActionableWhenLocked,
-                        keyguardInteractor.isKeyguardShowing,
-                    ) { isActionableWhenLocked, isLocked ->
-                        isActionableWhenLocked || !isLocked
-                    }
-                    .flatMapLatest { isActionable ->
-                        if (isActionable) {
-                            repository.actions
-                        } else {
-                            // If not actionable it means that we're not allowed to show actions
-                            // when
-                            // locked and we are locked. Therefore, we should show no actions.
-                            flowOf(emptyList())
-                        }
-                    }
             }
 
     val userRecords: StateFlow<ArrayList<UserRecord>> =
-        if (isNewImpl) {
-            combine(
-                    repository.userInfos,
-                    repository.selectedUserInfo,
-                    actions,
-                    repository.userSwitcherSettings,
-                ) { userInfos, selectedUserInfo, actionModels, settings ->
-                    ArrayList(
-                        userInfos.map {
+        combine(
+                userInfos,
+                repository.selectedUserInfo,
+                actions,
+                repository.userSwitcherSettings,
+            ) { userInfos, selectedUserInfo, actionModels, settings ->
+                ArrayList(
+                    userInfos.map {
+                        toRecord(
+                            userInfo = it,
+                            selectedUserId = selectedUserInfo.id,
+                        )
+                    } +
+                        actionModels.map {
                             toRecord(
-                                userInfo = it,
+                                action = it,
                                 selectedUserId = selectedUserInfo.id,
+                                isRestricted =
+                                    it != UserActionModel.ENTER_GUEST_MODE &&
+                                        it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
+                                        !settings.isAddUsersFromLockscreen,
                             )
-                        } +
-                            actionModels.map {
-                                toRecord(
-                                    action = it,
-                                    selectedUserId = selectedUserInfo.id,
-                                    isRestricted =
-                                        it != UserActionModel.ENTER_GUEST_MODE &&
-                                            it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
-                                            !settings.isAddUsersFromLockscreen,
-                                )
-                            }
-                    )
-                }
-                .onEach { notifyCallbacks() }
-                .stateIn(
-                    scope = applicationScope,
-                    started = SharingStarted.Eagerly,
-                    initialValue = ArrayList(),
+                        }
                 )
-        } else {
-            MutableStateFlow(ArrayList())
-        }
+            }
+            .onEach { notifyCallbacks() }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = ArrayList(),
+            )
 
     val selectedUserRecord: StateFlow<UserRecord?> =
-        if (isNewImpl) {
-            repository.selectedUserInfo
-                .map { selectedUserInfo ->
-                    toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
-                }
-                .stateIn(
-                    scope = applicationScope,
-                    started = SharingStarted.Eagerly,
-                    initialValue = null,
-                )
-        } else {
-            MutableStateFlow(null)
-        }
+        repository.selectedUserInfo
+            .map { selectedUserInfo ->
+                toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = null,
+            )
 
     /** Whether the device is configured to always have a guest user available. */
     val isGuestUserAutoCreated: Boolean = guestUserInteractor.isGuestUserAutoCreated
@@ -304,6 +276,9 @@
     /** Whether the guest user is currently being reset. */
     val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting
 
+    /** Whether to enable the user chip in the status bar */
+    val isStatusBarUserChipEnabled: Boolean = repository.isStatusBarUserChipEnabled
+
     private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null)
     val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow()
 
@@ -311,44 +286,37 @@
     val dialogDismissRequests: Flow<Unit?> = _dialogDismissRequests.asStateFlow()
 
     val isSimpleUserSwitcher: Boolean
-        get() =
-            if (isNewImpl) {
-                repository.isSimpleUserSwitcher()
-            } else {
-                error("Not supported in the old implementation!")
-            }
+        get() = repository.isSimpleUserSwitcher()
 
     init {
-        if (isNewImpl) {
-            refreshUsersScheduler.refreshIfNotPaused()
-            telephonyInteractor.callState
-                .distinctUntilChanged()
-                .onEach { refreshUsersScheduler.refreshIfNotPaused() }
-                .launchIn(applicationScope)
+        refreshUsersScheduler.refreshIfNotPaused()
+        telephonyInteractor.callState
+            .distinctUntilChanged()
+            .onEach { refreshUsersScheduler.refreshIfNotPaused() }
+            .launchIn(applicationScope)
 
-            combine(
-                    broadcastDispatcher.broadcastFlow(
-                        filter =
-                            IntentFilter().apply {
-                                addAction(Intent.ACTION_USER_ADDED)
-                                addAction(Intent.ACTION_USER_REMOVED)
-                                addAction(Intent.ACTION_USER_INFO_CHANGED)
-                                addAction(Intent.ACTION_USER_SWITCHED)
-                                addAction(Intent.ACTION_USER_STOPPED)
-                                addAction(Intent.ACTION_USER_UNLOCKED)
-                            },
-                        user = UserHandle.SYSTEM,
-                        map = { intent, _ -> intent },
-                    ),
-                    repository.selectedUserInfo.pairwise(null),
-                ) { intent, selectedUserChange ->
-                    Pair(intent, selectedUserChange.previousValue)
-                }
-                .onEach { (intent, previousSelectedUser) ->
-                    onBroadcastReceived(intent, previousSelectedUser)
-                }
-                .launchIn(applicationScope)
-        }
+        combine(
+                broadcastDispatcher.broadcastFlow(
+                    filter =
+                        IntentFilter().apply {
+                            addAction(Intent.ACTION_USER_ADDED)
+                            addAction(Intent.ACTION_USER_REMOVED)
+                            addAction(Intent.ACTION_USER_INFO_CHANGED)
+                            addAction(Intent.ACTION_USER_SWITCHED)
+                            addAction(Intent.ACTION_USER_STOPPED)
+                            addAction(Intent.ACTION_USER_UNLOCKED)
+                        },
+                    user = UserHandle.SYSTEM,
+                    map = { intent, _ -> intent },
+                ),
+                repository.selectedUserInfo.pairwise(null),
+            ) { intent, selectedUserChange ->
+                Pair(intent, selectedUserChange.previousValue)
+            }
+            .onEach { (intent, previousSelectedUser) ->
+                onBroadcastReceived(intent, previousSelectedUser)
+            }
+            .launchIn(applicationScope)
     }
 
     fun addCallback(callback: UserCallback) {
@@ -414,48 +382,43 @@
         newlySelectedUserId: Int,
         dialogShower: UserSwitchDialogController.DialogShower? = null,
     ) {
-        if (isNewImpl) {
-            val currentlySelectedUserInfo = repository.getSelectedUserInfo()
-            if (
-                newlySelectedUserId == currentlySelectedUserInfo.id &&
-                    currentlySelectedUserInfo.isGuest
-            ) {
-                // Here when clicking on the currently-selected guest user to leave guest mode
-                // and return to the previously-selected non-guest user.
-                showDialog(
-                    ShowDialogRequestModel.ShowExitGuestDialog(
-                        guestUserId = currentlySelectedUserInfo.id,
-                        targetUserId = repository.lastSelectedNonGuestUserId,
-                        isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
-                        isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
-                        onExitGuestUser = this::exitGuestUser,
-                        dialogShower = dialogShower,
-                    )
+        val currentlySelectedUserInfo = repository.getSelectedUserInfo()
+        if (
+            newlySelectedUserId == currentlySelectedUserInfo.id && currentlySelectedUserInfo.isGuest
+        ) {
+            // Here when clicking on the currently-selected guest user to leave guest mode
+            // and return to the previously-selected non-guest user.
+            showDialog(
+                ShowDialogRequestModel.ShowExitGuestDialog(
+                    guestUserId = currentlySelectedUserInfo.id,
+                    targetUserId = repository.lastSelectedNonGuestUserId,
+                    isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+                    isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                    onExitGuestUser = this::exitGuestUser,
+                    dialogShower = dialogShower,
                 )
-                return
-            }
-
-            if (currentlySelectedUserInfo.isGuest) {
-                // Here when switching from guest to a non-guest user.
-                showDialog(
-                    ShowDialogRequestModel.ShowExitGuestDialog(
-                        guestUserId = currentlySelectedUserInfo.id,
-                        targetUserId = newlySelectedUserId,
-                        isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
-                        isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
-                        onExitGuestUser = this::exitGuestUser,
-                        dialogShower = dialogShower,
-                    )
-                )
-                return
-            }
-
-            dialogShower?.dismiss()
-
-            switchUser(newlySelectedUserId)
-        } else {
-            controller.onUserSelected(newlySelectedUserId, dialogShower)
+            )
+            return
         }
+
+        if (currentlySelectedUserInfo.isGuest) {
+            // Here when switching from guest to a non-guest user.
+            showDialog(
+                ShowDialogRequestModel.ShowExitGuestDialog(
+                    guestUserId = currentlySelectedUserInfo.id,
+                    targetUserId = newlySelectedUserId,
+                    isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+                    isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                    onExitGuestUser = this::exitGuestUser,
+                    dialogShower = dialogShower,
+                )
+            )
+            return
+        }
+
+        dialogShower?.dismiss()
+
+        switchUser(newlySelectedUserId)
     }
 
     /** Executes the given action. */
@@ -463,51 +426,38 @@
         action: UserActionModel,
         dialogShower: UserSwitchDialogController.DialogShower? = null,
     ) {
-        if (isNewImpl) {
-            when (action) {
-                UserActionModel.ENTER_GUEST_MODE ->
-                    guestUserInteractor.createAndSwitchTo(
-                        this::showDialog,
-                        this::dismissDialog,
-                    ) { userId ->
-                        selectUser(userId, dialogShower)
-                    }
-                UserActionModel.ADD_USER -> {
-                    val currentUser = repository.getSelectedUserInfo()
-                    showDialog(
-                        ShowDialogRequestModel.ShowAddUserDialog(
-                            userHandle = currentUser.userHandle,
-                            isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
-                            showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
-                            dialogShower = dialogShower,
-                        )
-                    )
+        when (action) {
+            UserActionModel.ENTER_GUEST_MODE ->
+                guestUserInteractor.createAndSwitchTo(
+                    this::showDialog,
+                    this::dismissDialog,
+                ) { userId ->
+                    selectUser(userId, dialogShower)
                 }
-                UserActionModel.ADD_SUPERVISED_USER ->
-                    activityStarter.startActivity(
-                        Intent()
-                            .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
-                            .setPackage(supervisedUserPackageName)
-                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
-                        /* dismissShade= */ true,
+            UserActionModel.ADD_USER -> {
+                val currentUser = repository.getSelectedUserInfo()
+                showDialog(
+                    ShowDialogRequestModel.ShowAddUserDialog(
+                        userHandle = currentUser.userHandle,
+                        isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                        showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
+                        dialogShower = dialogShower,
                     )
-                UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
-                    activityStarter.startActivity(
-                        Intent(Settings.ACTION_USER_SETTINGS),
-                        /* dismissShade= */ true,
-                    )
+                )
             }
-        } else {
-            when (action) {
-                UserActionModel.ENTER_GUEST_MODE -> controller.createAndSwitchToGuestUser(null)
-                UserActionModel.ADD_USER -> controller.showAddUserDialog(null)
-                UserActionModel.ADD_SUPERVISED_USER -> controller.startSupervisedUserActivity()
-                UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
-                    activityStarter.startActivity(
-                        Intent(Settings.ACTION_USER_SETTINGS),
-                        /* dismissShade= */ false,
-                    )
-            }
+            UserActionModel.ADD_SUPERVISED_USER ->
+                activityStarter.startActivity(
+                    Intent()
+                        .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
+                        .setPackage(supervisedUserPackageName)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+                    /* dismissShade= */ true,
+                )
+            UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
+                activityStarter.startActivity(
+                    Intent(Settings.ACTION_USER_SETTINGS),
+                    /* dismissShade= */ true,
+                )
         }
     }
 
@@ -541,6 +491,26 @@
         }
     }
 
+    fun showUserSwitcher(context: Context, expandable: Expandable) {
+        if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+            showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable))
+            return
+        }
+
+        val intent =
+            Intent(context, UserSwitcherActivity::class.java).apply {
+                addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+            }
+
+        activityStarter.startActivity(
+            intent,
+            true /* dismissShade */,
+            expandable.activityLaunchController(),
+            true /* showOverlockscreenwhenlocked */,
+            UserHandle.SYSTEM,
+        )
+    }
+
     private fun showDialog(request: ShowDialogRequestModel) {
         _dialogShowRequests.value = request
     }
@@ -674,7 +644,7 @@
             // The guest user should go in the last position.
             .sortedBy { it.isGuest }
             .mapNotNull { userInfo ->
-                toUserModel(
+                filterAndMapToUserModel(
                     userInfo = userInfo,
                     selectedUserId = selectedUserId,
                     canSwitchUsers = canSwitchUsers,
@@ -683,51 +653,65 @@
             }
     }
 
-    private suspend fun toUserModel(
+    /**
+     * Maps UserInfo to UserModel based on some parameters and return null under certain conditions
+     * to be filtered out.
+     */
+    private suspend fun filterAndMapToUserModel(
         userInfo: UserInfo,
         selectedUserId: Int,
         canSwitchUsers: Boolean,
         isUserSwitcherEnabled: Boolean,
     ): UserModel? {
-        val userId = userInfo.id
-        val isSelected = userId == selectedUserId
-
         return when {
             // When the user switcher is not enabled in settings, we only show the primary user.
             !isUserSwitcherEnabled && !userInfo.isPrimary -> null
-
             // We avoid showing disabled users.
             !userInfo.isEnabled -> null
-            userInfo.isGuest ->
-                UserModel(
-                    id = userId,
-                    name = Text.Loaded(userInfo.name),
-                    image =
-                        getUserImage(
-                            isGuest = true,
-                            userId = userId,
-                        ),
-                    isSelected = isSelected,
-                    isSelectable = canSwitchUsers,
-                    isGuest = true,
-                )
-            userInfo.supportsSwitchToByUser() ->
-                UserModel(
-                    id = userId,
-                    name = Text.Loaded(userInfo.name),
-                    image =
-                        getUserImage(
-                            isGuest = false,
-                            userId = userId,
-                        ),
-                    isSelected = isSelected,
-                    isSelectable = canSwitchUsers || isSelected,
-                    isGuest = false,
-                )
+            // We meet the conditions to return the UserModel.
+            userInfo.isGuest || userInfo.supportsSwitchToByUser() ->
+                toUserModel(userInfo, selectedUserId, canSwitchUsers)
             else -> null
         }
     }
 
+    /** Maps UserInfo to UserModel based on some parameters. */
+    private suspend fun toUserModel(
+        userInfo: UserInfo,
+        selectedUserId: Int,
+        canSwitchUsers: Boolean
+    ): UserModel {
+        val userId = userInfo.id
+        val isSelected = userId == selectedUserId
+        return if (userInfo.isGuest) {
+            UserModel(
+                id = userId,
+                name = Text.Loaded(userInfo.name),
+                image =
+                    getUserImage(
+                        isGuest = true,
+                        userId = userId,
+                    ),
+                isSelected = isSelected,
+                isSelectable = canSwitchUsers,
+                isGuest = true,
+            )
+        } else {
+            UserModel(
+                id = userId,
+                name = Text.Loaded(userInfo.name),
+                image =
+                    getUserImage(
+                        isGuest = false,
+                        userId = userId,
+                    ),
+                isSelected = isSelected,
+                isSelectable = canSwitchUsers || isSelected,
+                isGuest = false,
+            )
+        }
+    }
+
     private suspend fun canSwitchUsers(selectedUserId: Int): Boolean {
         return withContext(backgroundDispatcher) {
             manager.getUserSwitchability(UserHandle.of(selectedUserId))
@@ -757,6 +741,16 @@
         )
     }
 
+    private fun canCreateGuestUser(settings: UserSwitcherSettingsModel): Boolean {
+        return guestUserInteractor.isGuestUserAutoCreated ||
+            UserActionsUtil.canCreateGuest(
+                manager,
+                repository,
+                settings.isUserSwitcherEnabled,
+                settings.isAddUsersFromLockscreen,
+            )
+    }
+
     companion object {
         private const val TAG = "UserInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 177356e..14cc3e7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -18,11 +18,13 @@
 package com.android.systemui.user.domain.model
 
 import android.os.UserHandle
+import com.android.systemui.animation.Expandable
 import com.android.systemui.qs.user.UserSwitchDialogController
 
 /** Encapsulates a request to show a dialog. */
 sealed class ShowDialogRequestModel(
     open val dialogShower: UserSwitchDialogController.DialogShower? = null,
+    open val expandable: Expandable? = null,
 ) {
     data class ShowAddUserDialog(
         val userHandle: UserHandle,
@@ -43,4 +45,9 @@
         val onExitGuestUser: (guestId: Int, targetId: Int, forceRemoveGuest: Boolean) -> Unit,
         override val dialogShower: UserSwitchDialogController.DialogShower?,
     ) : ShowDialogRequestModel(dialogShower)
+
+    /** Show the user switcher dialog */
+    data class ShowUserSwitcherDialog(
+        override val expandable: Expandable?,
+    ) : ShowDialogRequestModel()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt
new file mode 100644
index 0000000..8e40f68
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.user.ui.binder
+
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@OptIn(InternalCoroutinesApi::class)
+object StatusBarUserChipViewBinder {
+    /** Binds the status bar user chip view model to the given view */
+    @JvmStatic
+    fun bind(
+        view: StatusBarUserSwitcherContainer,
+        viewModel: StatusBarUserChipViewModel,
+    ) {
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.isChipVisible.collect { isVisible -> view.isVisible = isVisible }
+                }
+
+                launch {
+                    viewModel.userName.collect { name -> TextViewBinder.bind(view.text, name) }
+                }
+
+                launch {
+                    viewModel.userAvatar.collect { avatar -> view.avatar.setImageDrawable(avatar) }
+                }
+
+                bindButton(view, viewModel)
+            }
+        }
+    }
+
+    private fun bindButton(
+        view: StatusBarUserSwitcherContainer,
+        viewModel: StatusBarUserChipViewModel,
+    ) {
+        view.setOnClickListener { viewModel.onClick(Expandable.fromView(view)) }
+    }
+}
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..e137107 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)
@@ -132,9 +133,11 @@
                 launch {
                     viewModel.users.collect { users ->
                         val viewPool =
-                            view.children.filter { it.tag == USER_VIEW_TAG }.toMutableList()
+                            gridContainerView.children
+                                .filter { it.tag == USER_VIEW_TAG }
+                                .toMutableList()
                         viewPool.forEach {
-                            view.removeView(it)
+                            gridContainerView.removeView(it)
                             flowWidget.removeView(it)
                         }
                         users.forEach { userViewModel ->
@@ -152,7 +155,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/user/ui/dialog/DialogShowerImpl.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/DialogShowerImpl.kt
new file mode 100644
index 0000000..3fe2a7b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/DialogShowerImpl.kt
@@ -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.
+ */
+
+package com.android.systemui.user.ui.dialog
+
+import android.app.Dialog
+import android.content.DialogInterface
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
+
+/** Extracted from [UserSwitchDialogController] */
+class DialogShowerImpl(
+    private val animateFrom: Dialog,
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+) : DialogInterface by animateFrom, DialogShower {
+    override fun showDialog(dialog: Dialog, cuj: DialogCuj) {
+        dialogLaunchAnimator.showFromDialog(dialog, animateFrom = animateFrom, cuj)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
new file mode 100644
index 0000000..b8ae257
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
@@ -0,0 +1,69 @@
+package com.android.systemui.user.ui.dialog
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.view.LayoutInflater
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.QSUserSwitcherEvent
+import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/**
+ * Extracted from the old UserSwitchDialogController. This is the dialog version of the full-screen
+ * user switcher. See config_enableFullscreenUserSwitcher
+ */
+class UserSwitchDialog(
+    context: Context,
+    adapter: UserDetailView.Adapter,
+    uiEventLogger: UiEventLogger,
+    falsingManager: FalsingManager,
+    activityStarter: ActivityStarter,
+    dialogLaunchAnimator: DialogLaunchAnimator,
+) : SystemUIDialog(context) {
+    init {
+        setShowForAllUsers(true)
+        setCanceledOnTouchOutside(true)
+        setTitle(R.string.qs_user_switch_dialog_title)
+        setPositiveButton(R.string.quick_settings_done) { _, _ ->
+            uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE)
+        }
+        setNeutralButton(
+            R.string.quick_settings_more_user_settings,
+            { _, _ ->
+                if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                    uiEventLogger.log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS)
+                    val controller =
+                        dialogLaunchAnimator.createActivityLaunchController(
+                            getButton(BUTTON_NEUTRAL)
+                        )
+
+                    if (controller == null) {
+                        dismiss()
+                    }
+
+                    activityStarter.postStartActivityDismissingKeyguard(
+                        USER_SETTINGS_INTENT,
+                        0,
+                        controller
+                    )
+                }
+            },
+            false /* dismissOnClick */
+        )
+        val gridFrame =
+            LayoutInflater.from(this.context).inflate(R.layout.qs_user_dialog_content, null)
+        setView(gridFrame)
+
+        adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
+        adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator))
+    }
+
+    companion object {
+        private val USER_SETTINGS_INTENT = Intent(Settings.ACTION_USER_SETTINGS)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index e921720..d451230 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -20,6 +20,7 @@
 import android.app.Dialog
 import android.content.Context
 import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.users.UserCreatingDialog
 import com.android.systemui.CoreStartable
 import com.android.systemui.animation.DialogCuj
@@ -27,15 +28,15 @@
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.tiles.UserDetailView
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.domain.model.ShowDialogRequestModel
 import dagger.Lazy
 import javax.inject.Inject
+import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.launch
 
@@ -50,16 +51,14 @@
     private val broadcastSender: Lazy<BroadcastSender>,
     private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
     private val interactor: Lazy<UserInteractor>,
-    private val featureFlags: Lazy<FeatureFlags>,
+    private val userDetailAdapterProvider: Provider<UserDetailView.Adapter>,
+    private val eventLogger: Lazy<UiEventLogger>,
+    private val activityStarter: Lazy<ActivityStarter>,
 ) : CoreStartable {
 
     private var currentDialog: Dialog? = null
 
     override fun start() {
-        if (featureFlags.get().isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
-            return
-        }
-
         startHandlingDialogShowRequests()
         startHandlingDialogDismissRequests()
     }
@@ -116,10 +115,28 @@
                                     INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
                                 ),
                             )
+                        is ShowDialogRequestModel.ShowUserSwitcherDialog ->
+                            Pair(
+                                UserSwitchDialog(
+                                    context = context.get(),
+                                    adapter = userDetailAdapterProvider.get(),
+                                    uiEventLogger = eventLogger.get(),
+                                    falsingManager = falsingManager.get(),
+                                    activityStarter = activityStarter.get(),
+                                    dialogLaunchAnimator = dialogLaunchAnimator.get(),
+                                ),
+                                DialogCuj(
+                                    InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
+                                    INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
+                                ),
+                            )
                     }
                 currentDialog = dialog
 
-                if (request.dialogShower != null && dialogCuj != null) {
+                val controller = request.expandable?.dialogLaunchController(dialogCuj)
+                if (controller != null) {
+                    dialogLaunchAnimator.get().show(dialog, controller)
+                } else if (request.dialogShower != null && dialogCuj != null) {
                     request.dialogShower?.showDialog(dialog, dialogCuj)
                 } else {
                     dialog.show()
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
new file mode 100644
index 0000000..3300e8e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.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.systemui.user.ui.viewmodel
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.domain.interactor.UserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class StatusBarUserChipViewModel
+@Inject
+constructor(
+    @Application private val context: Context,
+    interactor: UserInteractor,
+) {
+    /** Whether the status bar chip ui should be available */
+    val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled
+
+    /** Whether or not the chip should be showing, based on the number of users */
+    val isChipVisible: Flow<Boolean> =
+        if (!chipEnabled) {
+            flowOf(false)
+        } else {
+            interactor.users.mapLatest { users -> users.size > 1 }
+        }
+
+    /** The display name of the current user */
+    val userName: Flow<Text> = interactor.selectedUser.mapLatest { userModel -> userModel.name }
+
+    /** Avatar for the current user */
+    val userAvatar: Flow<Drawable> =
+        interactor.selectedUser.mapLatest { userModel -> userModel.image }
+
+    /** Action to execute on click. Should launch the user switcher */
+    val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(context, it) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index d857e85..37115ad 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -19,9 +19,9 @@
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.ui.drawable.CircularDrawable
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.UserInteractor
@@ -41,12 +41,8 @@
     private val userInteractor: UserInteractor,
     private val guestUserInteractor: GuestUserInteractor,
     private val powerInteractor: PowerInteractor,
-    private val featureFlags: FeatureFlags,
 ) : ViewModel() {
 
-    private val isNewImpl: Boolean
-        get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
     /** On-device users. */
     val users: Flow<List<UserViewModel>> =
         userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
@@ -150,7 +146,12 @@
     ): UserViewModel {
         return UserViewModel(
             viewKey = model.id,
-            name = model.name,
+            name =
+                if (model.isGuest && model.isSelected) {
+                    Text.Resource(R.string.guest_exit_quick_settings_button)
+                } else {
+                    model.name
+                },
             image = CircularDrawable(model.image),
             isSelectionMarkerVisible = model.isSelected,
             alpha =
@@ -216,7 +217,6 @@
         private val userInteractor: UserInteractor,
         private val guestUserInteractor: GuestUserInteractor,
         private val powerInteractor: PowerInteractor,
-        private val featureFlags: FeatureFlags,
     ) : ViewModelProvider.Factory {
         override fun <T : ViewModel> create(modelClass: Class<T>): T {
             @Suppress("UNCHECKED_CAST")
@@ -224,7 +224,6 @@
                 userInteractor = userInteractor,
                 guestUserInteractor = guestUserInteractor,
                 powerInteractor = powerInteractor,
-                featureFlags = featureFlags,
             )
                 as T
         }
diff --git a/packages/SystemUI/src/com/android/systemui/util/BrightnessProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/BrightnessProgressDrawable.kt
new file mode 100644
index 0000000..12a0c03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/BrightnessProgressDrawable.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.util
+
+import android.content.pm.ActivityInfo
+import android.content.res.Resources
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.DrawableWrapper
+import android.graphics.drawable.InsetDrawable
+
+/**
+ * [DrawableWrapper] to use in the progress of brightness slider.
+ *
+ * This drawable is used to change the bounds of the enclosed drawable depending on the level to
+ * simulate a sliding progress, instead of using clipping or scaling. That way, the shape of the
+ * edges is maintained.
+ *
+ * Meant to be used with a rounded ends background, it will also prevent deformation when the slider
+ * is meant to be smaller than the rounded corner. The background should have rounded corners that
+ * are half of the height.
+ *
+ * This class also assumes that a "thumb" icon exists within the end's edge of the progress
+ * drawable, and the slider's width, when interacted on, if offset by half the size of the thumb
+ * icon which puts the icon directly underneath the user's finger.
+ */
+class BrightnessProgressDrawable @JvmOverloads constructor(drawable: Drawable? = null) :
+    InsetDrawable(drawable, 0) {
+
+    companion object {
+        private const val MAX_LEVEL = 10000 // Taken from Drawable
+    }
+
+    override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
+        onLevelChange(level)
+        return super.onLayoutDirectionChanged(layoutDirection)
+    }
+
+    override fun onBoundsChange(bounds: Rect) {
+        super.onBoundsChange(bounds)
+        onLevelChange(level)
+    }
+
+    override fun onLevelChange(level: Int): Boolean {
+        val db = drawable?.bounds!!
+
+        // The thumb offset shifts the sun icon directly under the user's thumb
+        val thumbOffset = bounds.height() / 2
+        val width = bounds.width() * level / MAX_LEVEL + thumbOffset
+
+        // On 0, the width is bounds.height (a circle), and on MAX_LEVEL, the width is bounds.width
+        drawable?.setBounds(
+            bounds.left,
+            db.top,
+            width.coerceAtMost(bounds.width()).coerceAtLeast(bounds.height()),
+            db.bottom
+        )
+        return super.onLevelChange(level)
+    }
+
+    override fun getConstantState(): ConstantState {
+        // This should not be null as it was created with a state in the constructor.
+        return RoundedCornerState(super.getConstantState()!!)
+    }
+
+    override fun getChangingConfigurations(): Int {
+        return super.getChangingConfigurations() or ActivityInfo.CONFIG_DENSITY
+    }
+
+    override fun canApplyTheme(): Boolean {
+        return (drawable?.canApplyTheme() ?: false) || super.canApplyTheme()
+    }
+
+    private class RoundedCornerState(private val wrappedState: ConstantState) : ConstantState() {
+        override fun newDrawable(): Drawable {
+            return newDrawable(null, null)
+        }
+
+        override fun newDrawable(res: Resources?, theme: Resources.Theme?): Drawable {
+            val wrapper = wrappedState.newDrawable(res, theme) as DrawableWrapper
+            return BrightnessProgressDrawable(wrapper.drawable)
+        }
+
+        override fun getChangingConfigurations(): Int {
+            return wrappedState.changingConfigurations
+        }
+
+        override fun canApplyTheme(): Boolean {
+            return true
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
index 6b5556b..bff6132d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
@@ -19,7 +19,6 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.Context;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 
@@ -51,13 +50,6 @@
     }
 
     /**
-     * Wrapped version of {@link DeviceConfig#enforceReadPermission}.
-     */
-    public void enforceReadPermission(Context context, String namespace) {
-        DeviceConfig.enforceReadPermission(context, namespace);
-    }
-
-    /**
      * Wrapped version of {@link DeviceConfig#getBoolean}.
      */
     public boolean getBoolean(
diff --git a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
index 99eb03b..1059d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
@@ -33,11 +33,6 @@
  * Meant to be used with a rounded ends background, it will also prevent deformation when the slider
  * is meant to be smaller than the rounded corner. The background should have rounded corners that
  * are half of the height.
- *
- * This class also assumes that a "thumb" icon exists within the end's edge of the progress
- * drawable, and the slider's width, when interacted on, if offset by half the size of the thumb
- * icon which puts the icon directly underneath the user's finger.
- *
  */
 class RoundedCornerProgressDrawable @JvmOverloads constructor(
     drawable: Drawable? = null
@@ -59,16 +54,9 @@
 
     override fun onLevelChange(level: Int): Boolean {
         val db = drawable?.bounds!!
-
-        // The thumb offset shifts the sun icon directly under the user's thumb
-        val thumbOffset = bounds.height() / 2
-        val width = bounds.width() * level / MAX_LEVEL + thumbOffset
-
         // On 0, the width is bounds.height (a circle), and on MAX_LEVEL, the width is bounds.width
-        drawable?.setBounds(
-            bounds.left, db.top,
-            width.coerceAtMost(bounds.width()).coerceAtLeast(bounds.height()), db.bottom
-        )
+        val width = bounds.height() + (bounds.width() - bounds.height()) * level / MAX_LEVEL
+        drawable?.setBounds(bounds.left, db.top, bounds.left + width, db.bottom)
         return super.onLevelChange(level)
     }
 
@@ -103,4 +91,4 @@
             return true
         }
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
index 5b16ae9..b311318 100644
--- a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
@@ -22,11 +22,22 @@
  * Run a block within a [Trace] section.
  * Calls [Trace.beginSection] before and [Trace.endSection] after the passed block.
  */
-inline fun <T> traceSection(tag: String, block: () -> T): T {
-    Trace.beginSection(tag)
-    try {
-        return block()
-    } finally {
-        Trace.endSection()
+inline fun <T> traceSection(tag: String, block: () -> T): T =
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+            Trace.traceBegin(Trace.TRACE_TAG_APP, tag)
+            try {
+                block()
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_APP)
+            }
+        } else {
+            block()
+        }
+
+class TraceUtils {
+    companion object {
+        inline fun traceRunnable(tag: String, crossinline block: () -> Unit): Runnable {
+            return Runnable { traceSection(tag) { block() } }
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt b/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt
new file mode 100644
index 0000000..da81d54
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.util.condition
+
+/**
+ * A higher order [Condition] which combines multiple conditions with a specified
+ * [Evaluator.ConditionOperand].
+ */
+internal class CombinedCondition
+constructor(
+    private val conditions: Collection<Condition>,
+    @Evaluator.ConditionOperand private val operand: Int
+) : Condition(null, false), Condition.Callback {
+
+    override fun start() {
+        onConditionChanged(this)
+        conditions.forEach { it.addCallback(this) }
+    }
+
+    override fun onConditionChanged(condition: Condition) {
+        Evaluator.evaluate(conditions, operand)?.also { value -> updateCondition(value) }
+            ?: clearCondition()
+    }
+
+    override fun stop() {
+        conditions.forEach { it.removeCallback(this) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
index 2c317dd..b39adef 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
@@ -24,7 +24,10 @@
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Iterator;
+import java.util.List;
 
 /**
  * Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform
@@ -181,6 +184,42 @@
     }
 
     /**
+     * Creates a new condition which will only be true when both this condition and all the provided
+     * conditions are true.
+     */
+    public Condition and(Collection<Condition> others) {
+        final List<Condition> conditions = new ArrayList<>(others);
+        conditions.add(this);
+        return new CombinedCondition(conditions, Evaluator.OP_AND);
+    }
+
+    /**
+     * Creates a new condition which will only be true when both this condition and the provided
+     * condition is true.
+     */
+    public Condition and(Condition other) {
+        return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_AND);
+    }
+
+    /**
+     * Creates a new condition which will only be true when either this condition or any of the
+     * provided conditions are true.
+     */
+    public Condition or(Collection<Condition> others) {
+        final List<Condition> conditions = new ArrayList<>(others);
+        conditions.add(this);
+        return new CombinedCondition(conditions, Evaluator.OP_OR);
+    }
+
+    /**
+     * Creates a new condition which will only be true when either this condition or the provided
+     * condition is true.
+     */
+    public Condition or(Condition other) {
+        return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_OR);
+    }
+
+    /**
      * Callback that receives updates about whether the condition has been fulfilled.
      */
     public interface Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt b/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt
new file mode 100644
index 0000000..cf44e84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt
@@ -0,0 +1,92 @@
+package com.android.systemui.util.condition
+
+import android.annotation.IntDef
+
+/**
+ * Helper for evaluating a collection of [Condition] objects with a given
+ * [Evaluator.ConditionOperand]
+ */
+internal object Evaluator {
+    /** Operands for combining multiple conditions together */
+    @Retention(AnnotationRetention.SOURCE)
+    @IntDef(value = [OP_AND, OP_OR])
+    annotation class ConditionOperand
+
+    /**
+     * 3-valued logical AND operand, with handling for unknown values (represented as null)
+     *
+     * ```
+     * +-----+----+---+---+
+     * | AND | T  | F | U |
+     * +-----+----+---+---+
+     * | T   | T  | F | U |
+     * | F   | F  | F | F |
+     * | U   | U  | F | U |
+     * +-----+----+---+---+
+     * ```
+     */
+    const val OP_AND = 0
+
+    /**
+     * 3-valued logical OR operand, with handling for unknown values (represented as null)
+     *
+     * ```
+     * +-----+----+---+---+
+     * | OR  | T  | F | U |
+     * +-----+----+---+---+
+     * | T   | T  | T | T |
+     * | F   | T  | F | U |
+     * | U   | T  | U | U |
+     * +-----+----+---+---+
+     * ```
+     */
+    const val OP_OR = 1
+
+    /**
+     * Evaluates a set of conditions with a given operand
+     *
+     * If overriding conditions are present, they take precedence over normal conditions if set.
+     *
+     * @param conditions The collection of conditions to evaluate. If empty, null is returned.
+     * @param operand The operand to use when evaluating.
+     * @return Either true or false if the value is known, or null if value is unknown
+     */
+    fun evaluate(conditions: Collection<Condition>, @ConditionOperand operand: Int): Boolean? {
+        if (conditions.isEmpty()) return null
+        // If there are overriding conditions with values set, they take precedence.
+        val targetConditions =
+            conditions
+                .filter { it.isConditionSet && it.isOverridingCondition }
+                .ifEmpty { conditions }
+        return when (operand) {
+            OP_AND ->
+                threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = false)
+            OP_OR ->
+                threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = true)
+            else -> null
+        }
+    }
+
+    /**
+     * Helper for evaluating 3-valued logical AND/OR.
+     *
+     * @param returnValueIfAnyMatches AND returns false if any value is false. OR returns true if
+     * any value is true.
+     */
+    private fun threeValuedAndOrOr(
+        conditions: Collection<Condition>,
+        returnValueIfAnyMatches: Boolean
+    ): Boolean? {
+        var hasUnknown = false
+        for (condition in conditions) {
+            if (!condition.isConditionSet) {
+                hasUnknown = true
+                continue
+            }
+            if (condition.isConditionMet == returnValueIfAnyMatches) {
+                return returnValueIfAnyMatches
+            }
+        }
+        return if (hasUnknown) null else !returnValueIfAnyMatches
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index cb430ba..24bc907 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -24,12 +24,10 @@
 import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Set;
 import java.util.concurrent.Executor;
-import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
@@ -57,21 +55,10 @@
         }
 
         public void update() {
-            // Only consider set conditions.
-            final Collection<Condition> setConditions = mSubscription.mConditions.stream()
-                    .filter(Condition::isConditionSet).collect(Collectors.toSet());
-
-            // Overriding conditions do not override each other
-            final Collection<Condition> overridingConditions = setConditions.stream()
-                    .filter(Condition::isOverridingCondition).collect(Collectors.toSet());
-
-            final Collection<Condition> targetCollection = overridingConditions.isEmpty()
-                    ? setConditions : overridingConditions;
-
-            final boolean newAllConditionsMet = targetCollection.isEmpty() ? true : targetCollection
-                    .stream()
-                    .map(Condition::isConditionMet)
-                    .allMatch(conditionMet -> conditionMet);
+            final Boolean result = Evaluator.INSTANCE.evaluate(mSubscription.mConditions,
+                    Evaluator.OP_AND);
+            // Consider unknown (null) as true
+            final boolean newAllConditionsMet = result == null || result;
 
             if (mAllConditionsMet != null && newAllConditionsMet == mAllConditionsMet) {
                 return;
@@ -109,6 +96,7 @@
 
     /**
      * Registers a callback and the set of conditions to trigger it.
+     *
      * @param subscription A {@link Subscription} detailing the desired conditions and callback.
      * @return A {@link Subscription.Token} that can be used to remove the subscription.
      */
@@ -139,6 +127,7 @@
 
     /**
      * Removes a subscription from participating in future callbacks.
+     *
      * @param token The {@link Subscription.Token} returned when the {@link Subscription} was
      *              originally added.
      */
@@ -179,7 +168,9 @@
         private final Set<Condition> mConditions;
         private final Callback mCallback;
 
-        /** */
+        /**
+         *
+         */
         public Subscription(Set<Condition> conditions, Callback callback) {
             this.mConditions = Collections.unmodifiableSet(conditions);
             this.mCallback = callback;
@@ -209,7 +200,6 @@
 
             /**
              * Default constructor specifying the {@link Callback} for the {@link Subscription}.
-             * @param callback
              */
             public Builder(Callback callback) {
                 mCallback = callback;
@@ -218,7 +208,7 @@
 
             /**
              * Adds a {@link Condition} to be associated with the {@link Subscription}.
-             * @param condition
+             *
              * @return The updated {@link Builder}.
              */
             public Builder addCondition(Condition condition) {
@@ -228,7 +218,7 @@
 
             /**
              * Adds a set of {@link Condition} to be associated with the {@link Subscription}.
-             * @param condition
+             *
              * @return The updated {@link Builder}.
              */
             public Builder addConditions(Set<Condition> condition) {
@@ -238,6 +228,7 @@
 
             /**
              * Builds the {@link Subscription}.
+             *
              * @return The resulting {@link Subscription}.
              */
             public Subscription build() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index f71d596..b61b2e6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -20,7 +20,6 @@
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.onStart
@@ -58,6 +57,22 @@
     onStart { emit(initialValue) }.pairwiseBy(transform)
 
 /**
+ * Returns a new [Flow] that combines the two most recent emissions from [this] using [transform].
+ *
+ *
+ * The output of [getInitialValue] will be used as the "old" value for the first emission. As
+ * opposed to the initial value in the above [pairwiseBy], [getInitialValue] can do some work before
+ * returning the initial value.
+ *
+ * Useful for code that needs to compare the current value to the previous value.
+ */
+fun <T, R> Flow<T>.pairwiseBy(
+    getInitialValue: suspend () -> T,
+    transform: suspend (previousValue: T, newValue: T) -> R,
+): Flow<R> =
+    onStart { emit(getInitialValue()) }.pairwiseBy(transform)
+
+/**
  * Returns a new [Flow] that produces the two most recent emissions from [this]. Note that the new
  * Flow will not start emitting until it has received two emissions from the upstream Flow.
  *
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index 4875982..9b06a37 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -31,8 +31,8 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.util.concurrency.ThreadFactory;
 
 import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java b/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
index d7c4e93..3c57081 100644
--- a/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
@@ -16,10 +16,11 @@
 
 package com.android.systemui.util.time;
 
-import android.app.ActivityManager;
 import android.content.Context;
 import android.text.format.DateFormat;
 
+import com.android.systemui.settings.UserTracker;
+
 import javax.inject.Inject;
 
 /**
@@ -27,14 +28,16 @@
  */
 public class DateFormatUtil {
     private final Context mContext;
+    private final UserTracker mUserTracker;
 
     @Inject
-    public DateFormatUtil(Context context) {
+    public DateFormatUtil(Context context, UserTracker userTracker) {
         mContext = context;
+        mUserTracker = userTracker;
     }
 
     /** Returns true if the phone is in 24 hour format. */
     public boolean is24HourFormat() {
-        return DateFormat.is24HourFormat(mContext, ActivityManager.getCurrentUser());
+        return DateFormat.is24HourFormat(mContext, mUserTracker.getUserId());
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
index 8d77c4a..f320d07 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
@@ -38,6 +38,11 @@
     long DEFAULT_MAX_TIMEOUT = 20000;
 
     /**
+     * Default wake-lock levels and flags.
+     */
+    int DEFAULT_LEVELS_AND_FLAGS = PowerManager.PARTIAL_WAKE_LOCK;
+
+    /**
      * @param why A tag that will be saved for sysui dumps.
      * @see android.os.PowerManager.WakeLock#acquire()
      **/
@@ -60,13 +65,21 @@
      * Creates a {@link WakeLock} that has a default release timeout.
      * @see android.os.PowerManager.WakeLock#acquire(long) */
     static WakeLock createPartial(Context context, String tag, long maxTimeout) {
-        return wrap(createPartialInner(context, tag), maxTimeout);
+        return wrap(createWakeLockInner(context, tag, DEFAULT_LEVELS_AND_FLAGS), maxTimeout);
+    }
+
+    /**
+     * Creates a {@link WakeLock} that has a default release timeout and flags.
+     */
+    static WakeLock createWakeLock(Context context, String tag, int flags, long maxTimeout) {
+        return wrap(createWakeLockInner(context, tag, flags), maxTimeout);
     }
 
     @VisibleForTesting
-    static PowerManager.WakeLock createPartialInner(Context context, String tag) {
+    static PowerManager.WakeLock createWakeLockInner(
+            Context context, String tag, int levelsAndFlags) {
         return context.getSystemService(PowerManager.class)
-                    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag);
+                    .newWakeLock(levelsAndFlags, tag);
     }
 
     static Runnable wrapImpl(WakeLock w, Runnable r) {
@@ -131,6 +144,7 @@
     class Builder {
         private final Context mContext;
         private String mTag;
+        private int mLevelsAndFlags = DEFAULT_LEVELS_AND_FLAGS;
         private long mMaxTimeout = DEFAULT_MAX_TIMEOUT;
 
         @Inject
@@ -143,13 +157,18 @@
             return this;
         }
 
+        public Builder setLevelsAndFlags(int levelsAndFlags) {
+            this.mLevelsAndFlags = levelsAndFlags;
+            return this;
+        }
+
         public Builder setMaxTimeout(long maxTimeout) {
             this.mMaxTimeout = maxTimeout;
             return this;
         }
 
         public WakeLock build() {
-            return WakeLock.createPartial(mContext, mTag, mMaxTimeout);
+            return WakeLock.createWakeLock(mContext, mTag, mLevelsAndFlags, mMaxTimeout);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 33c00fb..9349966 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -432,6 +432,11 @@
                             AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
                             AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0;
             changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
+        } else if (stream == AudioManager.STREAM_VOICE_CALL) {
+            final boolean routedToBluetooth =
+                    (mAudio.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)
+                            & AudioManager.DEVICE_OUT_BLE_HEADSET) != 0;
+            changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
         }
         return changed;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 903aba1..1bc0d08 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -72,6 +72,7 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.VibrationEffect;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.text.InputFilter;
@@ -108,6 +109,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.view.RotationPolicy;
@@ -125,11 +128,15 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.AlphaTintDrawableWrapper;
+import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.RoundedCornerProgressDrawable;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -186,6 +193,9 @@
     private ViewGroup mDialogRowsView;
     private ViewGroup mRinger;
 
+    private DeviceConfigProxy mDeviceConfigProxy;
+    private Executor mExecutor;
+
     /**
      * Container for the top part of the dialog, which contains the ringer, the ringer drawer, the
      * volume rows, and the ellipsis button. This does not include the live caption button.
@@ -274,6 +284,13 @@
     private BackgroundBlurDrawable mDialogRowsViewBackground;
     private final InteractionJankMonitor mInteractionJankMonitor;
 
+    private boolean mSeparateNotification;
+
+    @VisibleForTesting
+    int mVolumeRingerIconDrawableId;
+    @VisibleForTesting
+    int mVolumeRingerMuteIconDrawableId;
+
     public VolumeDialogImpl(
             Context context,
             VolumeDialogController volumeDialogController,
@@ -283,7 +300,9 @@
             MediaOutputDialogFactory mediaOutputDialogFactory,
             VolumePanelFactory volumePanelFactory,
             ActivityStarter activityStarter,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            DeviceConfigProxy deviceConfigProxy,
+            Executor executor) {
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
         mController = volumeDialogController;
@@ -323,6 +342,50 @@
         }
 
         initDimens();
+
+        mDeviceConfigProxy = deviceConfigProxy;
+        mExecutor = executor;
+        mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+        updateRingerModeIconSet();
+    }
+
+    /**
+     * If ringer and notification are the same stream (T and earlier), use notification-like bell
+     * icon set.
+     * If ringer and notification are separated, then use generic speaker icons.
+     */
+    private void updateRingerModeIconSet() {
+        if (mSeparateNotification) {
+            mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on;
+            mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute;
+        } else {
+            mVolumeRingerIconDrawableId = R.drawable.ic_volume_ringer;
+            mVolumeRingerMuteIconDrawableId = R.drawable.ic_volume_ringer_mute;
+        }
+
+        if (mRingerDrawerMuteIcon != null) {
+            mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+        }
+        if (mRingerDrawerNormalIcon != null) {
+            mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId);
+        }
+    }
+
+    /**
+     * Change icon for ring stream (not ringer mode icon)
+     */
+    private void updateRingRowIcon() {
+        Optional<VolumeRow> volumeRow = mRows.stream().filter(row -> row.stream == STREAM_RING)
+                .findFirst();
+        if (volumeRow.isPresent()) {
+            VolumeRow volRow = volumeRow.get();
+            volRow.iconRes = mSeparateNotification ? R.drawable.ic_ring_volume
+                    : R.drawable.ic_volume_ringer;
+            volRow.iconMuteRes = mSeparateNotification ? R.drawable.ic_ring_volume_off
+                    : R.drawable.ic_volume_ringer_mute;
+            volRow.setIcon(volRow.iconRes, mContext.getTheme());
+        }
     }
 
     @Override
@@ -339,6 +402,9 @@
         mController.getState();
 
         mConfigurationController.addCallback(this);
+
+        mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                mExecutor, this::onDeviceConfigChange);
     }
 
     @Override
@@ -346,6 +412,24 @@
         mController.removeCallback(mControllerCallbackH);
         mHandler.removeCallbacksAndMessages(null);
         mConfigurationController.removeCallback(this);
+        mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
+    }
+
+    /**
+     * Update ringer mode icon based on the config
+     */
+    private void onDeviceConfigChange(DeviceConfig.Properties properties) {
+        Set<String> changeSet = properties.getKeyset();
+        if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
+            boolean newVal = properties.getBoolean(
+                    SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+            if (newVal != mSeparateNotification) {
+                mSeparateNotification = newVal;
+                updateRingerModeIconSet();
+                updateRingRowIcon();
+
+            }
+        }
     }
 
     @Override
@@ -554,6 +638,8 @@
         mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon);
         mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background);
 
+        updateRingerModeIconSet();
+
         setupRingerDrawer();
 
         mODICaptionsView = mDialog.findViewById(R.id.odi_captions);
@@ -577,8 +663,14 @@
             addRow(AudioManager.STREAM_MUSIC,
                     R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true);
             if (!AudioSystem.isSingleVolume(mContext)) {
-                addRow(AudioManager.STREAM_RING,
-                        R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false);
+                if (mSeparateNotification) {
+                    addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume,
+                            R.drawable.ic_ring_volume_off, true, false);
+                } else {
+                    addRow(AudioManager.STREAM_RING, R.drawable.ic_volume_ringer,
+                            R.drawable.ic_volume_ringer, true, false);
+                }
+
                 addRow(STREAM_ALARM,
                         R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false);
                 addRow(AudioManager.STREAM_VOICE_CALL,
@@ -1230,6 +1322,9 @@
                 effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
                 break;
             case RINGER_MODE_VIBRATE:
+                // Feedback handled by onStateChange, for feedback both when user toggles
+                // directly in volume dialog, or drags slider to a value of 0 in settings.
+                break;
             default:
                 effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
         }
@@ -1308,7 +1403,7 @@
 
     private void showH(int reason, boolean keyguardLocked, int lockTaskModeState) {
         Trace.beginSection("VolumeDialogImpl#showH");
-        if (D.BUG) Log.d(TAG, "showH r=" + Events.SHOW_REASONS[reason]);
+        Log.i(TAG, "showH r=" + Events.SHOW_REASONS[reason]);
         mHandler.removeMessages(H.SHOW);
         mHandler.removeMessages(H.DISMISS);
         rescheduleTimeoutH();
@@ -1336,7 +1431,7 @@
         final int timeout = computeTimeoutH();
         mHandler.sendMessageDelayed(mHandler
                 .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
-        if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
+        Log.i(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
         mController.userActivity();
     }
 
@@ -1363,10 +1458,10 @@
 
     protected void dismissH(int reason) {
         Trace.beginSection("VolumeDialogImpl#dismissH");
-        if (D.BUG) {
-            Log.d(TAG, "mDialog.dismiss() reason: " + Events.DISMISS_REASONS[reason]
-                    + " from: " + Debug.getCaller());
-        }
+
+        Log.i(TAG, "mDialog.dismiss() reason: " + Events.DISMISS_REASONS[reason]
+                + " from: " + Debug.getCaller());
+
         mHandler.removeMessages(H.DISMISS);
         mHandler.removeMessages(H.SHOW);
         if (mIsAnimatingDismiss) {
@@ -1531,8 +1626,8 @@
                     mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
                     break;
                 case AudioManager.RINGER_MODE_SILENT:
-                    mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
-                    mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+                    mRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+                    mSelectedRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
                     mRingerIcon.setTag(Events.ICON_STATE_MUTE);
                     addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT,
                             mContext.getString(R.string.volume_ringer_hint_unmute));
@@ -1541,14 +1636,14 @@
                 default:
                     boolean muted = (mAutomute && ss.level == 0) || ss.muted;
                     if (!isZenMuted && muted) {
-                        mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
-                        mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+                        mRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+                        mSelectedRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
                         addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
                                 mContext.getString(R.string.volume_ringer_hint_unmute));
                         mRingerIcon.setTag(Events.ICON_STATE_MUTE);
                     } else {
-                        mRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
-                        mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
+                        mRingerIcon.setImageResource(mVolumeRingerIconDrawableId);
+                        mSelectedRingerIcon.setImageResource(mVolumeRingerIconDrawableId);
                         if (mController.hasVibrator()) {
                             addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
                                     mContext.getString(R.string.volume_ringer_hint_vibrate));
@@ -1630,9 +1725,8 @@
                 && mState.ringerModeInternal != -1
                 && mState.ringerModeInternal != state.ringerModeInternal
                 && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
-            mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK));
+            mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
         }
-
         mState = state;
         mDynamic.clear();
         // add any new dynamic rows
@@ -1677,6 +1771,7 @@
         if (ss.level == row.requestedLevel) {
             row.requestedLevel = -1;
         }
+        final boolean isVoiceCallStream = row.stream == AudioManager.STREAM_VOICE_CALL;
         final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY;
         final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
         final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
@@ -1721,8 +1816,12 @@
         } else if (isRingSilent || zenMuted) {
             iconRes = row.iconMuteRes;
         } else if (ss.routedToBluetooth) {
-            iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
-                                        : R.drawable.ic_volume_media_bt;
+            if (isVoiceCallStream) {
+                iconRes = R.drawable.ic_volume_bt_sco;
+            } else {
+                iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
+                                            : R.drawable.ic_volume_media_bt;
+            }
         } else if (isStreamMuted(ss)) {
             iconRes = ss.muted ? R.drawable.ic_volume_media_off : row.iconMuteRes;
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index c5792b9..8f10fa6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -20,6 +20,7 @@
 import android.media.AudioManager;
 
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialog;
@@ -27,11 +28,14 @@
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
 import com.android.systemui.volume.VolumeDialogImpl;
 import com.android.systemui.volume.VolumePanelFactory;
 
+import java.util.concurrent.Executor;
+
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
@@ -55,7 +59,9 @@
             MediaOutputDialogFactory mediaOutputDialogFactory,
             VolumePanelFactory volumePanelFactory,
             ActivityStarter activityStarter,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            DeviceConfigProxy deviceConfigProxy,
+            @Main Executor executor) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
                 volumeDialogController,
@@ -65,7 +71,9 @@
                 mediaOutputDialogFactory,
                 volumePanelFactory,
                 activityStarter,
-                interactionJankMonitor);
+                interactionJankMonitor,
+                deviceConfigProxy,
+                executor);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
         impl.setSilentMode(false);
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 44f6d03..5df4a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -29,8 +29,10 @@
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
 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;
@@ -101,6 +103,15 @@
     }
 
     @Override
+    public Looper onProvideEngineLooper() {
+        // Receive messages on mWorker thread instead of SystemUI's main handler.
+        // All other wallpapers have their own process, and they can receive messages on their own
+        // main handler without any delay. But since ImageWallpaper lives in SystemUI, performance
+        // of the image wallpaper could be negatively affected when SystemUI's main handler is busy.
+        return mWorker != null ? mWorker.getLooper() : super.onProvideEngineLooper();
+    }
+
+    @Override
     public void onCreate() {
         super.onCreate();
         mWorker = new HandlerThread(TAG);
@@ -598,7 +609,6 @@
             getDisplayContext().getSystemService(DisplayManager.class)
                     .unregisterDisplayListener(this);
             mWallpaperLocalColorExtractor.cleanUp();
-            unloadBitmap();
         }
 
         @Override
@@ -676,9 +686,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 +724,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 +742,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 +761,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 +774,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/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 4e77514..7033ccd 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -25,6 +25,7 @@
 import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
+import static com.android.systemui.flags.Flags.WM_BUBBLE_BAR;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
@@ -51,6 +52,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -129,6 +131,7 @@
             CommonNotifCollection notifCollection,
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
+            FeatureFlags featureFlags,
             Executor sysuiMainExecutor) {
         if (bubblesOptional.isPresent()) {
             return new BubblesManager(context,
@@ -146,6 +149,7 @@
                     notifCollection,
                     notifPipeline,
                     sysUiState,
+                    featureFlags,
                     sysuiMainExecutor);
         } else {
             return null;
@@ -168,6 +172,7 @@
             CommonNotifCollection notifCollection,
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
+            FeatureFlags featureFlags,
             Executor sysuiMainExecutor) {
         mContext = context;
         mBubbles = bubbles;
@@ -352,6 +357,7 @@
                 });
             }
         };
+        mBubbles.setBubbleBarEnabled(featureFlags.isEnabled(WM_BUBBLE_BAR));
         mBubbles.setSysuiProxy(mSysuiProxy);
     }
 
@@ -543,7 +549,7 @@
         } catch (RemoteException e) {
             Log.e(TAG, e.getMessage());
         }
-        mShadeController.collapsePanel(true);
+        mShadeController.collapseShade(true);
         if (entry.getRow() != null) {
             entry.getRow().updateBubbleButton();
         }
@@ -591,7 +597,7 @@
         }
 
         if (shouldBubble) {
-            mShadeController.collapsePanel(true);
+            mShadeController.collapseShade(true);
             if (entry.getRow() != null) {
                 entry.getRow().updateBubbleButton();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 02738d5..8ef98f0 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -253,6 +253,12 @@
                 splitScreen.onFinishedWakingUp();
             }
         });
+        mCommandQueue.addCallback(new CommandQueue.Callbacks() {
+            @Override
+            public void goToFullscreenFromSplit() {
+                splitScreen.goToFullscreenFromSplit();
+            }
+        });
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index c55ee61..2d257b9 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -93,6 +93,21 @@
             android:excludeFromRecents="true"
             />
 
+        <activity android:name="com.android.systemui.controls.management.ControlsEditingActivityTest$TestableControlsEditingActivity"
+            android:exported="false"
+            android:excludeFromRecents="true"
+            />
+
+        <activity android:name="com.android.systemui.controls.management.ControlsFavoritingActivityTest$TestableControlsFavoritingActivity"
+            android:exported="false"
+            android:excludeFromRecents="true"
+            />
+
+        <activity android:name="com.android.systemui.controls.management.ControlsProviderSelectorActivityTest$TestableControlsProviderSelectorActivity"
+            android:exported="false"
+            android:excludeFromRecents="true"
+            />
+
         <activity android:name="com.android.systemui.screenshot.ScrollViewActivity"
                   android:exported="false" />
 
@@ -106,6 +121,18 @@
                   android:finishOnCloseSystemDialogs="true"
                   android:excludeFromRecents="true" />
 
+        <activity android:name=".user.CreateUserActivityTest$CreateUserActivityTestable"
+            android:exported="false"
+            android:theme="@style/Theme.SystemUI.Dialog.Alert"
+            android:finishOnCloseSystemDialogs="true"
+            android:excludeFromRecents="true" />
+
+        <activity android:name=".sensorprivacy.SensorUseStartedActivityTest$SensorUseStartedActivityTestable"
+                  android:exported="false"
+                  android:theme="@style/Theme.SystemUI.Dialog.Alert"
+                  android:finishOnCloseSystemDialogs="true"
+                  android:excludeFromRecents="true" />
+
         <provider
             android:name="androidx.startup.InitializationProvider"
             tools:replace="android:authorities"
@@ -119,6 +146,12 @@
             tools:replace="android:authorities"
             tools:node="remove" />
 
+        <provider android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
+            android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled"
+            android:enabled="false"
+            tools:replace="android:authorities"
+            tools:node="remove" />
+
         <provider
             android:name="androidx.core.content.FileProvider"
             android:authorities="com.android.systemui.test.fileprovider"
diff --git a/packages/SystemUI/tests/robolectric/config/robolectric.properties b/packages/SystemUI/tests/robolectric/config/robolectric.properties
new file mode 100644
index 0000000..2a75bd9
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/config/robolectric.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java
new file mode 100644
index 0000000..188dff2
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.robotests;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import static com.google.common.truth.Truth.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SysuiResourceLoadingTest extends SysuiRoboBase {
+    @Test
+    public void testResources() {
+        assertThat(getContext().getString(com.android.systemui.R.string.app_label))
+                .isEqualTo("System UI");
+        assertThat(getContext().getString(com.android.systemui.tests.R.string.test_content))
+                .isNotEmpty();
+    }
+}
diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java
new file mode 100644
index 0000000..d9686bb
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java
@@ -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.robotests;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+
+public class SysuiRoboBase {
+    public Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
index 0a9c745..ffedb30 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
@@ -46,7 +46,7 @@
     @Test
     public void testShowsTextField() {
         mKeyguardMessageArea.setVisibility(View.INVISIBLE);
-        mKeyguardMessageArea.setMessage("oobleck");
+        mKeyguardMessageArea.setMessage("oobleck", /* animate= */ true);
         assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.VISIBLE);
         assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck");
     }
@@ -55,7 +55,7 @@
     public void testHiddenWhenBouncerHidden() {
         mKeyguardMessageArea.setIsVisible(false);
         mKeyguardMessageArea.setVisibility(View.INVISIBLE);
-        mKeyguardMessageArea.setMessage("oobleck");
+        mKeyguardMessageArea.setMessage("oobleck", /* animate= */ true);
         assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE);
         assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck");
     }
@@ -63,7 +63,7 @@
     @Test
     public void testClearsTextField() {
         mKeyguardMessageArea.setVisibility(View.VISIBLE);
-        mKeyguardMessageArea.setMessage("");
+        mKeyguardMessageArea.setMessage("", /* animate= */ true);
         assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE);
         assertThat(mKeyguardMessageArea.getText()).isEqualTo("");
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
index 7b9b39f..ba46a87 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
@@ -49,30 +49,30 @@
     @Test
     fun testSetSameMessage() {
         val underTestSpy = spy(underTest)
-        underTestSpy.setMessage("abc")
-        underTestSpy.setMessage("abc")
+        underTestSpy.setMessage("abc", animate = true)
+        underTestSpy.setMessage("abc", animate = true)
         verify(underTestSpy, times(1)).text = "abc"
     }
 
     @Test
     fun testSetDifferentMessage() {
-        underTest.setMessage("abc")
-        underTest.setMessage("def")
+        underTest.setMessage("abc", animate = true)
+        underTest.setMessage("def", animate = true)
         assertThat(underTest.text).isEqualTo("def")
     }
 
     @Test
     fun testSetNullMessage() {
-        underTest.setMessage(null)
+        underTest.setMessage(null, animate = true)
         assertThat(underTest.text).isEqualTo("")
     }
 
     @Test
     fun testSetNullClearsPreviousMessage() {
-        underTest.setMessage("something not null")
+        underTest.setMessage("something not null", animate = true)
         assertThat(underTest.text).isEqualTo("something not null")
 
-        underTest.setMessage(null)
+        underTest.setMessage(null, animate = true)
         assertThat(underTest.text).isEqualTo("")
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 1c3656d..e8f8e25 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
@@ -156,7 +155,8 @@
         verify(configurationController).addCallback(capture(captor))
         captor.value.onDensityOrFontScaleChanged()
 
-        verify(events).onFontSettingChanged()
+        verify(smallClockEvents, times(2)).onFontSettingChanged(anyFloat())
+        verify(largeClockEvents, times(2)).onFontSettingChanged(anyFloat())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index aca60c0..fa8c8982 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -63,7 +63,6 @@
     credentialAttempted = false,
     deviceInteractive = false,
     dreaming = false,
-    encryptedOrLockdown = false,
     fingerprintDisabled = false,
     fingerprintLockedOut = false,
     goingToSleep = false,
@@ -72,7 +71,9 @@
     keyguardOccluded = false,
     occludingAppRequestingFp = false,
     primaryUser = false,
+    shouldListenSfpsState = false,
     shouldListenForFingerprintAssistant = false,
+    strongerAuthRequired = false,
     switchingUser = false,
     udfps = false,
     userDoesNotHaveTrust = false
@@ -83,21 +84,22 @@
     userId = user,
     listening = false,
     authInterruptActive = false,
-    becauseCannotSkipBouncer = false,
     biometricSettingEnabledForUser = false,
     bouncerFullyShown = false,
-    faceAuthenticated = false,
+    faceAndFpNotAuthenticated = false,
+    faceAuthAllowed = true,
     faceDisabled = false,
     faceLockedOut = false,
-    fpLockedOut = false,
     goingToSleep = false,
     keyguardAwake = false,
     keyguardGoingAway = false,
     listeningForFaceAssistant = false,
     occludingAppRequestingFaceAuth = false,
     primaryUser = false,
-    scanningAllowedByStrongAuth = false,
     secureCameraLaunched = false,
+    supportsDetect = true,
     switchingUser = false,
     udfpsBouncerShowing = false,
+    udfpsFingerDown = false,
+    userNotTrustedOrDetectionIsNeeded = false
 )
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 5d2b0ca..0e837d2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -16,8 +16,11 @@
 
 package com.android.keyguard;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -82,7 +85,7 @@
     @Test
     public void testClearsTextField() {
         mMessageAreaController.setMessage("");
-        verify(mKeyguardMessageArea).setMessage("");
+        verify(mKeyguardMessageArea).setMessage("", /* animate= */ true);
     }
 
     @Test
@@ -90,4 +93,11 @@
         mMessageAreaController.setIsVisible(true);
         verify(mKeyguardMessageArea).setIsVisible(true);
     }
+
+    @Test
+    public void testGetMessage() {
+        String msg = "abc";
+        when(mKeyguardMessageArea.getText()).thenReturn(msg);
+        assertThat(mMessageAreaController.getMessage()).isEqualTo(msg);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index b369098..d20be56 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -19,6 +19,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
@@ -31,6 +32,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.`when`
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -42,6 +44,8 @@
     @Mock
     private lateinit var keyguardPasswordView: KeyguardPasswordView
     @Mock
+    private lateinit var passwordEntry: EditText
+    @Mock
     lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock
     lateinit var securityMode: KeyguardSecurityModel.SecurityMode
@@ -80,6 +84,9 @@
         ).thenReturn(mKeyguardMessageArea)
         Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
             .thenReturn(mKeyguardMessageAreaController)
+        Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
+        Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry)
+        ).thenReturn(passwordEntry)
         keyguardPasswordViewController = KeyguardPasswordViewController(
             keyguardPasswordView,
             keyguardUpdateMonitor,
@@ -102,7 +109,10 @@
         Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
         Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
         keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
-        keyguardPasswordView.post { verify(keyguardPasswordView).requestFocus() }
+        keyguardPasswordView.post {
+            verify(keyguardPasswordView).requestFocus()
+            verify(keyguardPasswordView).showKeyboard()
+        }
     }
 
     @Test
@@ -114,8 +124,27 @@
     }
 
     @Test
+    fun testHideKeyboardWhenOnPause() {
+        keyguardPasswordViewController.onPause()
+        keyguardPasswordView.post {
+            verify(keyguardPasswordView).clearFocus()
+            verify(keyguardPasswordView).hideKeyboard()
+        }
+    }
+
+    @Test
     fun startAppearAnimation() {
         keyguardPasswordViewController.startAppearAnimation()
         verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
     }
+
+    @Test
+    fun startAppearAnimation_withExistingMessage() {
+        `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+        keyguardPasswordViewController.startAppearAnimation()
+        verify(
+            mKeyguardMessageAreaController,
+            never()
+        ).setMessage(R.string.keyguard_enter_your_password)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 9eff704..b3d1c8f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -33,6 +33,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -112,4 +113,14 @@
         mKeyguardPatternViewController.startAppearAnimation()
         verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
     }
+
+    @Test
+    fun startAppearAnimation_withExistingMessage() {
+        `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+        mKeyguardPatternViewController.startAppearAnimation()
+        verify(
+            mKeyguardMessageAreaController,
+            never()
+        ).setMessage(R.string.keyguard_enter_your_password)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index d9efdea..8bcfe6f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -100,4 +100,12 @@
         pinViewController.startAppearAnimation()
         verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin)
     }
+
+    @Test
+    fun startAppearAnimation_withExistingMessage() {
+        Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+        pinViewController.startAppearAnimation()
+        verify(keyguardMessageAreaController, Mockito.never())
+            .setMessage(R.string.keyguard_enter_your_password)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index f9bec65..84f6d91 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -54,7 +54,9 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.biometrics.SideFpsController;
+import com.android.systemui.biometrics.SideFpsUiRequestSource;
+import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.log.SessionTracker;
@@ -140,9 +142,11 @@
     @Mock
     private KeyguardViewController mKeyguardViewController;
     @Mock
-    private SidefpsController mSidefpsController;
+    private SideFpsController mSideFpsController;
     @Mock
     private KeyguardPasswordViewController mKeyguardPasswordViewControllerMock;
+    @Mock
+    private FalsingA11yDelegate mFalsingA11yDelegate;
 
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
@@ -186,7 +190,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 +239,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 +248,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 +285,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 +299,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 +311,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 +326,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);
@@ -326,48 +346,48 @@
     @Test
     public void onBouncerVisibilityChanged_allConditionsGood_sideFpsHintShown() {
         setupConditionsToEnableSideFpsHint();
-        reset(mSidefpsController);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
 
-        verify(mSidefpsController).show();
-        verify(mSidefpsController, never()).hide();
+        verify(mSideFpsController).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).hide(any());
     }
 
     @Test
     public void onBouncerVisibilityChanged_fpsSensorNotRunning_sideFpsHintHidden() {
         setupConditionsToEnableSideFpsHint();
         setFingerprintDetectionRunning(false);
-        reset(mSidefpsController);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
 
-        verify(mSidefpsController).hide();
-        verify(mSidefpsController, never()).show();
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).show(any());
     }
 
     @Test
     public void onBouncerVisibilityChanged_withoutSidedSecurity_sideFpsHintHidden() {
         setupConditionsToEnableSideFpsHint();
         setSideFpsHintEnabledFromResources(false);
-        reset(mSidefpsController);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
 
-        verify(mSidefpsController).hide();
-        verify(mSidefpsController, never()).show();
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).show(any());
     }
 
     @Test
-    public void onBouncerVisibilityChanged_needsStrongAuth_sideFpsHintHidden() {
+    public void onBouncerVisibilityChanged_unlockingWithFingerprintNotAllowed_sideFpsHintHidden() {
         setupConditionsToEnableSideFpsHint();
-        setNeedsStrongAuth(true);
-        reset(mSidefpsController);
+        setUnlockingWithFingerprintAllowed(false);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
 
-        verify(mSidefpsController).hide();
-        verify(mSidefpsController, never()).show();
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).show(any());
     }
 
     @Test
@@ -375,13 +395,13 @@
         setupGetSecurityView();
         setupConditionsToEnableSideFpsHint();
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        verify(mSidefpsController, atLeastOnce()).show();
-        reset(mSidefpsController);
+        verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.INVISIBLE);
 
-        verify(mSidefpsController).hide();
-        verify(mSidefpsController, never()).show();
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).show(any());
     }
 
     @Test
@@ -389,13 +409,13 @@
         setupGetSecurityView();
         setupConditionsToEnableSideFpsHint();
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        verify(mSidefpsController, atLeastOnce()).show();
-        reset(mSidefpsController);
+        verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onStartingToHide();
 
-        verify(mSidefpsController).hide();
-        verify(mSidefpsController, never()).show();
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).show(any());
     }
 
     @Test
@@ -403,13 +423,13 @@
         setupGetSecurityView();
         setupConditionsToEnableSideFpsHint();
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        verify(mSidefpsController, atLeastOnce()).show();
-        reset(mSidefpsController);
+        verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onPause();
 
-        verify(mSidefpsController).hide();
-        verify(mSidefpsController, never()).show();
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).show(any());
     }
 
     @Test
@@ -417,12 +437,12 @@
         setupGetSecurityView();
         setupConditionsToEnableSideFpsHint();
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        reset(mSidefpsController);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onResume(0);
 
-        verify(mSidefpsController).show();
-        verify(mSidefpsController, never()).hide();
+        verify(mSideFpsController).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).hide(any());
     }
 
     @Test
@@ -431,12 +451,12 @@
         setupConditionsToEnableSideFpsHint();
         setSideFpsHintEnabledFromResources(false);
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        reset(mSidefpsController);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onResume(0);
 
-        verify(mSidefpsController).hide();
-        verify(mSidefpsController, never()).show();
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).show(any());
     }
 
     @Test
@@ -528,6 +548,51 @@
         verify(mKeyguardPasswordViewControllerMock, never()).showMessage(null, null);
     }
 
+    @Test
+    public void onDensityorFontScaleChanged() {
+        ArgumentCaptor<ConfigurationController.ConfigurationListener>
+                configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
+                ConfigurationController.ConfigurationListener.class);
+        mKeyguardSecurityContainerController.onViewAttached();
+        verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
+        configurationListenerArgumentCaptor.getValue().onDensityOrFontScaleChanged();
+
+        verify(mView).onDensityOrFontScaleChanged();
+        verify(mKeyguardSecurityViewFlipperController).clearViews();
+        verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
+                any(KeyguardSecurityCallback.class));
+    }
+
+    @Test
+    public void onThemeChanged() {
+        ArgumentCaptor<ConfigurationController.ConfigurationListener>
+                configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
+                ConfigurationController.ConfigurationListener.class);
+        mKeyguardSecurityContainerController.onViewAttached();
+        verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
+        configurationListenerArgumentCaptor.getValue().onThemeChanged();
+
+        verify(mView).reloadColors();
+        verify(mKeyguardSecurityViewFlipperController).clearViews();
+        verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
+                any(KeyguardSecurityCallback.class));
+    }
+
+    @Test
+    public void onUiModeChanged() {
+        ArgumentCaptor<ConfigurationController.ConfigurationListener>
+                configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
+                ConfigurationController.ConfigurationListener.class);
+        mKeyguardSecurityContainerController.onViewAttached();
+        verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
+        configurationListenerArgumentCaptor.getValue().onUiModeChanged();
+
+        verify(mView).reloadColors();
+        verify(mKeyguardSecurityViewFlipperController).clearViews();
+        verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
+                any(KeyguardSecurityCallback.class));
+    }
+
     private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
         mKeyguardSecurityContainerController.onViewAttached();
         verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
@@ -538,7 +603,7 @@
         attachView();
         setSideFpsHintEnabledFromResources(true);
         setFingerprintDetectionRunning(true);
-        setNeedsStrongAuth(false);
+        setUnlockingWithFingerprintAllowed(true);
     }
 
     private void attachView() {
@@ -557,9 +622,8 @@
                 enabled);
     }
 
-    private void setNeedsStrongAuth(boolean needed) {
-        when(mKeyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(needed);
-        mKeyguardUpdateMonitorCallback.getValue().onStrongAuthStateChanged(/* userId= */ 0);
+    private void setUnlockingWithFingerprintAllowed(boolean allowed) {
+        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(allowed);
     }
 
     private void setupGetSecurityView() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 82d3ca7..36ed669 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);
@@ -263,9 +262,12 @@
         ConstraintSet.Constraint userSwitcherConstraint =
                 getViewConstraint(R.id.keyguard_bouncer_user_switcher);
 
-        assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.topToBottom).isEqualTo(
+                R.id.keyguard_bouncer_user_switcher);
         assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
         assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+        assertThat(userSwitcherConstraint.layout.bottomToTop).isEqualTo(
+                mSecurityViewFlipper.getId());
         assertThat(userSwitcherConstraint.layout.topMargin).isEqualTo(
                 getContext().getResources().getDimensionPixelSize(
                         R.dimen.bouncer_user_switcher_y_trans));
@@ -309,6 +311,17 @@
     }
 
     @Test
+    public void testOnDensityOrFontScaleChanged() {
+        setupUserSwitcher();
+        View oldUserSwitcher = mKeyguardSecurityContainer.findViewById(
+                R.id.keyguard_bouncer_user_switcher);
+        mKeyguardSecurityContainer.onDensityOrFontScaleChanged();
+        View newUserSwitcher = mKeyguardSecurityContainer.findViewById(
+                R.id.keyguard_bouncer_user_switcher);
+        assertThat(oldUserSwitcher).isNotEqualTo(newUserSwitcher);
+    }
+
+    @Test
     public void testTouchesAreRecognizedAsBeingOnTheOtherSideOfSecurity() {
         setupUserSwitcher();
         setViewWidth(VIEW_WIDTH);
@@ -377,8 +390,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 +408,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 +417,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/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index 9296d3d..1614b57 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -106,4 +106,10 @@
             }
         }
     }
+
+    @Test
+    public void onDensityOrFontScaleChanged() {
+        mKeyguardSecurityViewFlipperController.clearViews();
+        verify(mView).removeAllViews();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 680c3b8..002da55 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -21,13 +21,17 @@
 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;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
 import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
+import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -35,10 +39,12 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.ArgumentMatchers.anyString;
 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 +60,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 +68,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;
@@ -83,6 +93,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.service.dreams.IDreamManager;
+import android.service.trust.TrustAgentService;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -91,6 +102,8 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.annotation.NonNull;
+
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.InstanceId;
@@ -107,10 +120,12 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.StatusBarState;
 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;
@@ -150,6 +165,8 @@
     private static final int FINGERPRINT_SENSOR_ID = 1;
 
     @Mock
+    private UserTracker mUserTracker;
+    @Mock
     private DumpManager mDumpManager;
     @Mock
     private KeyguardUpdateMonitor.StrongAuthTracker mStrongAuthTracker;
@@ -182,6 +199,8 @@
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
+    private SecureSettings mSecureSettings;
+    @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
     private SensorPrivacyManager mSensorPrivacyManager;
@@ -215,6 +234,7 @@
     private GlobalSettings mGlobalSettings;
     private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
 
+
     private final int mCurrentUserId = 100;
     private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
 
@@ -224,6 +244,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;
@@ -247,21 +270,8 @@
 
         // IBiometricsFace@1.0 does not support detection, only authentication.
         when(mFaceSensorProperties.isEmpty()).thenReturn(false);
-
-        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-        componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
-                "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
-                "00000001" /* serialNumber */, "" /* softwareVersion */));
-        componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
-                "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
-                "vendor/version/revision" /* softwareVersion */));
-
-        when(mFaceSensorProperties.get(anyInt())).thenReturn(new FaceSensorPropertiesInternal(
-                0 /* id */,
-                FaceSensorProperties.STRENGTH_STRONG, 1 /* maxTemplatesAllowed */,
-                componentInfo, FaceSensorProperties.TYPE_UNKNOWN,
-                false /* supportsFaceDetection */, true /* supportsSelfIllumination */,
-                false /* resetLockoutRequiresChallenge */));
+        when(mFaceSensorProperties.get(anyInt())).thenReturn(
+                createFaceSensorProperties(/* supportsFaceDetection = */ false));
 
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
@@ -293,8 +303,7 @@
         ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                 .when(SubscriptionManager::getDefaultSubscriptionId);
         KeyguardUpdateMonitor.setCurrentUser(mCurrentUserId);
-        ExtendedMockito.doReturn(KeyguardUpdateMonitor.getCurrentUser())
-                .when(ActivityManager::getCurrentUser);
+        when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
         ExtendedMockito.doReturn(mActivityService).when(ActivityManager::getService);
 
         mFaceWakeUpTriggersConfig = new FaceWakeUpTriggersConfig(
@@ -305,6 +314,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)
@@ -327,9 +345,33 @@
         when(mAuthController.areAllFingerprintAuthenticatorsRegistered()).thenReturn(true);
     }
 
+    @NonNull
+    private FaceSensorPropertiesInternal createFaceSensorProperties(boolean supportsFaceDetection) {
+        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+        componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
+                "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+                "00000001" /* serialNumber */, "" /* softwareVersion */));
+        componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+                "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+                "vendor/version/revision" /* softwareVersion */));
+
+
+        return new FaceSensorPropertiesInternal(
+                0 /* id */,
+                FaceSensorProperties.STRENGTH_STRONG,
+                1 /* maxTemplatesAllowed */,
+                componentInfo,
+                FaceSensorProperties.TYPE_UNKNOWN,
+                supportsFaceDetection /* supportsFaceDetection */,
+                true /* supportsSelfIllumination */,
+                false /* resetLockoutRequiresChallenge */);
+    }
+
     @After
     public void tearDown() {
-        mMockitoSession.finishMocking();
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
         cleanupKeyguardUpdateMonitor();
     }
 
@@ -565,30 +607,13 @@
     }
 
     @Test
-    public void testFingerprintDoesNotAuth_whenEncrypted() {
-        testFingerprintWhenStrongAuth(
-                STRONG_AUTH_REQUIRED_AFTER_BOOT);
-    }
-
-    @Test
-    public void testFingerprintDoesNotAuth_whenDpmLocked() {
-        testFingerprintWhenStrongAuth(
-                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW);
-    }
-
-    @Test
-    public void testFingerprintDoesNotAuth_whenUserLockdown() {
-        testFingerprintWhenStrongAuth(
-                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
-    }
-
-    private void testFingerprintWhenStrongAuth(int strongAuth) {
+    public void testOnlyDetectFingerprint_whenFingerprintUnlockNotAllowed() {
         // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
         // will trigger updateBiometricListeningState();
         clearInvocations(mFingerprintManager);
         mKeyguardUpdateMonitor.resetBiometricListeningState();
 
-        when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
         mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
         mTestableLooper.processAllMessages();
 
@@ -597,6 +622,64 @@
     }
 
     @Test
+    public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() {
+        // GIVEN unlocking with biometric is allowed
+        strongAuthNotRequired();
+
+        // THEN unlocking with face and fp is allowed
+        Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                BiometricSourceType.FACE));
+        Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                BiometricSourceType.FINGERPRINT));
+    }
+
+    @Test
+    public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricNotAllowed() {
+        // GIVEN unlocking with biometric is not allowed
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+
+        // THEN unlocking with face is not allowed
+        Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                BiometricSourceType.FACE));
+    }
+
+    @Test
+    public void testUnlockingWithFaceAllowed_fingerprintLockout() {
+        // GIVEN unlocking with biometric is allowed
+        strongAuthNotRequired();
+
+        // WHEN fingerprint is locked out
+        fingerprintErrorLockedOut();
+
+        // THEN unlocking with face is not allowed
+        Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                BiometricSourceType.FACE));
+    }
+
+    @Test
+    public void testUnlockingWithFpAllowed_strongAuthTrackerUnlockingWithBiometricNotAllowed() {
+        // GIVEN unlocking with biometric is not allowed
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+
+        // THEN unlocking with fingerprint is not allowed
+        Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                BiometricSourceType.FINGERPRINT));
+    }
+
+    @Test
+    public void testUnlockingWithFpAllowed_fingerprintLockout() {
+        // GIVEN unlocking with biometric is allowed
+        strongAuthNotRequired();
+
+        // WHEN fingerprint is locked out
+        fingerprintErrorLockedOut();
+
+        // THEN unlocking with fingeprint is not allowed
+        Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                BiometricSourceType.FINGERPRINT));
+    }
+
+    @Test
     public void testTriesToAuthenticate_whenBouncer() {
         setKeyguardBouncerVisibility(true);
 
@@ -607,7 +690,7 @@
 
     @Test
     public void testNoStartAuthenticate_whenAboutToShowBouncer() {
-        mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(
+        mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(
                 /* bouncerIsOrWillBeShowing */ true, /* bouncerFullyShown */ false);
 
         verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
@@ -640,10 +723,9 @@
     }
 
     @Test
-    public void skipsAuthentication_whenEncryptedKeyguard() {
-        when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
-                STRONG_AUTH_REQUIRED_AFTER_BOOT);
-        mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
+    public void skipsAuthentication_whenStrongAuthRequired_nonBypass() {
+        lockscreenBypassIsNotAllowed();
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
 
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mTestableLooper.processAllMessages();
@@ -653,15 +735,48 @@
     }
 
     @Test
-    public void requiresAuthentication_whenEncryptedKeyguard_andBypass() {
-        testStrongAuthExceptOnBouncer(
-                STRONG_AUTH_REQUIRED_AFTER_BOOT);
+    public void faceDetect_whenStrongAuthRequiredAndBypass() {
+        // GIVEN bypass is enabled, face detection is supported and strong auth is required
+        lockscreenBypassIsAllowed();
+        supportsFaceDetection();
+        strongAuthRequiredEncrypted();
+        keyguardIsVisible();
+
+        // WHEN the device wakes up
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mTestableLooper.processAllMessages();
+
+        // FACE detect is triggered, not authenticate
+        verify(mFaceManager).detectFace(any(), any(), anyInt());
+        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+                anyBoolean());
+
+        // WHEN bouncer becomes visible
+        setKeyguardBouncerVisibility(true);
+        clearInvocations(mFaceManager);
+
+        // THEN face scanning is not run
+        mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+                anyBoolean());
+        verify(mFaceManager, never()).detectFace(any(), any(), anyInt());
     }
 
     @Test
-    public void requiresAuthentication_whenTimeoutKeyguard_andBypass() {
-        testStrongAuthExceptOnBouncer(
-                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
+    public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() {
+        // GIVEN bypass is enabled, face detection is NOT supported and strong auth is required
+        lockscreenBypassIsAllowed();
+        strongAuthRequiredEncrypted();
+        keyguardIsVisible();
+
+        // WHEN the device wakes up
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mTestableLooper.processAllMessages();
+
+        // FACE detect and authenticate are NOT triggered
+        verify(mFaceManager, never()).detectFace(any(), any(), anyInt());
+        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+                anyBoolean());
     }
 
     @Test
@@ -694,24 +809,6 @@
         assertThat(didFaceAuthRun).isFalse();
     }
 
-    private void testStrongAuthExceptOnBouncer(int strongAuth) {
-        when(mKeyguardBypassController.canBypass()).thenReturn(true);
-        mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
-        when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
-
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-        keyguardIsVisible();
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
-
-        // Stop scanning when bouncer becomes visible
-        setKeyguardBouncerVisibility(true);
-        clearInvocations(mFaceManager);
-        mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
-                anyBoolean());
-    }
-
     @Test
     public void testTriesToAuthenticate_whenAssistant() {
         mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
@@ -722,10 +819,9 @@
 
     @Test
     public void testTriesToAuthenticate_whenTrustOnAgentKeyguard_ifBypass() {
-        mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mTestableLooper.processAllMessages();
-        when(mKeyguardBypassController.canBypass()).thenReturn(true);
+        lockscreenBypassIsAllowed();
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
                 KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
                 new ArrayList<>());
@@ -745,26 +841,26 @@
     }
 
     @Test
-    public void testIgnoresAuth_whenLockdown() {
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-        when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
-                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+    public void testNoFaceAuth_whenLockDown() {
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+        userDeviceLockDown();
 
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         keyguardIsVisible();
+        mTestableLooper.processAllMessages();
+
         verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
                 anyBoolean());
+        verify(mFaceManager, never()).detectFace(any(), any(), anyInt());
     }
 
     @Test
-    public void testTriesToAuthenticate_whenLockout() {
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-        when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
-                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+    public void testFingerprintPowerPressed_restartsFingerprintListeningStateImmediately() {
+        mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
+                .onAuthenticationError(FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, "");
 
-        keyguardIsVisible();
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+        verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
+                anyInt());
     }
 
     @Test
@@ -899,10 +995,6 @@
                 faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
         final boolean fpLocked =
                 fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
-        when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
-                .thenReturn(fingerprintLockoutMode);
-        when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
-                .thenReturn(faceLockoutMode);
 
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mTestableLooper.processAllMessages();
@@ -912,6 +1004,10 @@
         verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
                 anyInt());
 
+        when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
+                .thenReturn(fingerprintLockoutMode);
+        when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
+                .thenReturn(faceLockoutMode);
         final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
         final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
         mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
@@ -922,14 +1018,22 @@
         mKeyguardUpdateMonitor.handleUserSwitchComplete(newUser);
         mTestableLooper.processAllMessages();
 
-        verify(faceCancel, faceLocked ? times(1) : never()).cancel();
-        verify(fpCancel, fpLocked ? times(1) : never()).cancel();
-        verify(callback, faceLocked ? times(1) : never()).onBiometricRunningStateChanged(
+        // THEN face and fingerprint listening are always cancelled immediately
+        verify(faceCancel).cancel();
+        verify(callback).onBiometricRunningStateChanged(
                 eq(false), eq(BiometricSourceType.FACE));
-        verify(callback, fpLocked ? times(1) : never()).onBiometricRunningStateChanged(
+        verify(fpCancel).cancel();
+        verify(callback).onBiometricRunningStateChanged(
                 eq(false), eq(BiometricSourceType.FINGERPRINT));
+
+        // THEN locked out states are updated
         assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked);
         assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked);
+
+        // Fingerprint should be restarted once its cancelled bc on lockout, the device
+        // can still detectFingerprint (and if it's not locked out, fingerprint can listen)
+        assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
+                .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING);
     }
 
     @Test
@@ -1010,6 +1114,7 @@
     @Test
     public void testSecondaryLockscreenRequirement() {
         KeyguardUpdateMonitor.setCurrentUser(UserHandle.myUserId());
+        when(mUserTracker.getUserId()).thenReturn(UserHandle.myUserId());
         int user = KeyguardUpdateMonitor.getCurrentUser();
         String packageName = "fake.test.package";
         String cls = "FakeService";
@@ -1114,9 +1219,8 @@
         // GIVEN status bar state is on the keyguard
         mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
 
-        // WHEN user hasn't authenticated since last boot
-        when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
-                .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
+        // WHEN user hasn't authenticated since last boot, cannot unlock with FP
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
 
         // THEN we shouldn't listen for udfps
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1136,6 +1240,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);
@@ -1171,8 +1332,7 @@
         when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
 
         // WHEN device in lock down
-        when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
-                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
 
         // THEN we shouldn't listen for udfps
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1185,7 +1345,7 @@
         mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED);
         setKeyguardBouncerVisibility(false /* isVisible */);
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        when(mKeyguardBypassController.canBypass()).thenReturn(true);
+        lockscreenBypassIsAllowed();
         keyguardIsVisible();
 
         // WHEN status bar state reports a change to the keyguard that would normally indicate to
@@ -1206,6 +1366,24 @@
     }
 
     @Test
+    public void testRequestFaceAuthFromOccludingApp_whenInvoked_startsFaceAuth() {
+        mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
+
+        assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isTrue();
+    }
+
+    @Test
+    public void testRequestFaceAuthFromOccludingApp_whenInvoked_stopsFaceAuth() {
+        mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
+
+        assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isTrue();
+
+        mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
+
+        assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isFalse();
+    }
+
+    @Test
     public void testRequireUnlockForNfc_Broadcast() {
         KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
         mKeyguardUpdateMonitor.registerCallback(callback);
@@ -1234,7 +1412,10 @@
                 Arrays.asList("Unlocked by wearable"));
 
         // THEN the showTrustGrantedMessage should be called with the first message
-        verify(mTestCallback).showTrustGrantedMessage("Unlocked by wearable");
+        verify(mTestCallback).onTrustGrantedForCurrentUser(
+                anyBoolean(),
+                eq(new TrustGrantFlags(0)),
+                eq("Unlocked by wearable"));
     }
 
     @Test
@@ -1291,6 +1472,29 @@
     }
 
     @Test
+    public void testShouldListenForFace_whenFpIsAlreadyAuthenticated_returnsFalse()
+            throws RemoteException {
+        // Face auth should run when the following is true.
+        bouncerFullyVisibleAndNotGoingToSleep();
+        keyguardNotGoingAway();
+        currentUserIsPrimary();
+        strongAuthNotRequired();
+        biometricsEnabledForCurrentUser();
+        currentUserDoesNotHaveTrust();
+        biometricsNotDisabledThroughDevicePolicyManager();
+        userNotCurrentlySwitching();
+
+        mTestableLooper.processAllMessages();
+
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+        successfulFingerprintAuth();
+        mTestableLooper.processAllMessages();
+
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+    }
+
+    @Test
     public void testShouldListenForFace_whenUserIsNotPrimary_returnsFalse() throws RemoteException {
         cleanupKeyguardUpdateMonitor();
         // This disables face auth
@@ -1325,11 +1529,9 @@
         userNotCurrentlySwitching();
 
         // This disables face auth
-        when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
-                .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
         mTestableLooper.processAllMessages();
 
-
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
     }
 
@@ -1541,7 +1743,7 @@
     }
 
     @Test
-    public void testShouldListenForFace_whenFaceIsLockedOut_returnsFalse()
+    public void testShouldListenForFace_whenFaceIsLockedOut_returnsTrue()
             throws RemoteException {
         // Preconditions for face auth to run
         keyguardNotGoingAway();
@@ -1558,7 +1760,9 @@
         faceAuthLockedOut();
         mTestableLooper.processAllMessages();
 
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+        // This is needed beccause we want to show face locked out error message whenever face auth
+        // is supposed to run.
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
     }
 
     @Test
@@ -1642,6 +1846,258 @@
         verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
     }
 
+
+    @Test
+    public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_deviceInteractive() {
+        // GIVEN device is interactive
+        deviceIsInteractive();
+
+        // GIVEN callback is registered
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD flag
+        mKeyguardUpdateMonitor.onTrustChanged(
+                true /* enabled */,
+                getCurrentUser() /* userId */,
+                TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
+                null /* trustGrantedMessages */);
+
+        // THEN onTrustGrantedForCurrentUser callback called
+        verify(callback).onTrustGrantedForCurrentUser(
+                eq(true) /* dismissKeyguard */,
+                eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)),
+                eq(null) /* message */
+        );
+    }
+
+    @Test
+    public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_doesNotDismiss() {
+        // GIVEN device is NOT interactive
+
+        // GIVEN callback is registered
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD flag
+        mKeyguardUpdateMonitor.onTrustChanged(
+                true /* enabled */,
+                getCurrentUser() /* userId */,
+                TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
+                null /* trustGrantedMessages */);
+
+        // THEN onTrustGrantedForCurrentUser callback called
+        verify(callback).onTrustGrantedForCurrentUser(
+                eq(false) /* dismissKeyguard */,
+                eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)),
+                eq(null) /* message */
+        );
+    }
+
+    @Test
+    public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_temporaryAndRenewable() {
+        // GIVEN device is interactive
+        deviceIsInteractive();
+
+        // GIVEN callback is registered
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN onTrustChanged for a different user
+        mKeyguardUpdateMonitor.onTrustChanged(
+                true /* enabled */,
+                546 /* userId, not the current userId */,
+                0 /* flags */,
+                null /* trustGrantedMessages */);
+
+        // THEN onTrustGrantedForCurrentUser callback called
+        verify(callback, never()).onTrustGrantedForCurrentUser(
+                anyBoolean() /* dismissKeyguard */,
+                anyObject() /* flags */,
+                anyString() /* message */
+        );
+    }
+
+    @Test
+    public void testOnTrustGranted_differentUser_noCallback() {
+        // GIVEN device is interactive
+
+        // GIVEN callback is registered
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD AND TRUST_TEMPORARY_AND_RENEWABLE
+        // flags (temporary & rewable is active unlock)
+        mKeyguardUpdateMonitor.onTrustChanged(
+                true /* enabled */,
+                getCurrentUser() /* userId */,
+                TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
+                        | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
+                null /* trustGrantedMessages */);
+
+        // THEN onTrustGrantedForCurrentUser callback called
+        verify(callback).onTrustGrantedForCurrentUser(
+                eq(true) /* dismissKeyguard */,
+                eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
+                        | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)),
+                eq(null) /* message */
+        );
+    }
+
+    @Test
+    public void testOnTrustGrantedForCurrentUser_bouncerShowing_initiatedByUser() {
+        // GIVEN device is interactive & bouncer is showing
+        deviceIsInteractive();
+        bouncerFullyVisible();
+
+        // GIVEN callback is registered
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN onTrustChanged with INITIATED_BY_USER flag
+        mKeyguardUpdateMonitor.onTrustChanged(
+                true /* enabled */,
+                getCurrentUser() /* userId, not the current userId */,
+                TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER /* flags */,
+                null /* trustGrantedMessages */);
+
+        // THEN onTrustGrantedForCurrentUser callback called
+        verify(callback, never()).onTrustGrantedForCurrentUser(
+                eq(true) /* dismissKeyguard */,
+                eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER)),
+                anyString() /* message */
+        );
+    }
+
+    @Test
+    public void testOnTrustGrantedForCurrentUser_bouncerShowing_temporaryRenewable() {
+        // GIVEN device is NOT interactive & bouncer is showing
+        bouncerFullyVisible();
+
+        // GIVEN callback is registered
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN onTrustChanged with INITIATED_BY_USER flag
+        mKeyguardUpdateMonitor.onTrustChanged(
+                true /* enabled */,
+                getCurrentUser() /* userId, not the current userId */,
+                TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER
+                        | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
+                null /* trustGrantedMessages */);
+
+        // THEN onTrustGrantedForCurrentUser callback called
+        verify(callback, never()).onTrustGrantedForCurrentUser(
+                eq(true) /* dismissKeyguard */,
+                eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER
+                        | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)),
+                anyString() /* message */
+        );
+    }
+
+    @Test
+    public void testStrongAuthChange_lockDown_stopsFpAndFaceListeningState() {
+        // GIVEN device is listening for face and fingerprint
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mTestableLooper.processAllMessages();
+        keyguardIsVisible();
+
+        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+        verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
+                anyInt());
+
+        final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
+        final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
+        mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
+        mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel;
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN strong auth changes and device is in user lockdown
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+        userDeviceLockDown();
+        mKeyguardUpdateMonitor.notifyStrongAuthAllowedChanged(getCurrentUser());
+        mTestableLooper.processAllMessages();
+
+        // THEN face and fingerprint listening are cancelled
+        verify(faceCancel).cancel();
+        verify(callback).onBiometricRunningStateChanged(
+                eq(false), eq(BiometricSourceType.FACE));
+        verify(fpCancel).cancel();
+        verify(callback).onBiometricRunningStateChanged(
+                eq(false), eq(BiometricSourceType.FINGERPRINT));
+    }
+
+    @Test
+    public void testNonStrongBiometricAllowedChanged_stopsFaceListeningState() {
+        // GIVEN device is listening for face and fingerprint
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mTestableLooper.processAllMessages();
+        keyguardIsVisible();
+
+        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+
+        final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
+        mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN non-strong biometric allowed changes
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+        mKeyguardUpdateMonitor.notifyNonStrongBiometricAllowedChanged(getCurrentUser());
+        mTestableLooper.processAllMessages();
+
+        // THEN face and fingerprint listening are cancelled
+        verify(faceCancel).cancel();
+        verify(callback).onBiometricRunningStateChanged(
+                eq(false), eq(BiometricSourceType.FACE));
+    }
+
+    @Test
+    public void testShouldListenForFace_withLockedDown_returnsFalse()
+            throws RemoteException {
+        keyguardNotGoingAway();
+        bouncerFullyVisibleAndNotGoingToSleep();
+        currentUserIsPrimary();
+        currentUserDoesNotHaveTrust();
+        biometricsNotDisabledThroughDevicePolicyManager();
+        biometricsEnabledForCurrentUser();
+        userNotCurrentlySwitching();
+        supportsFaceDetection();
+        mTestableLooper.processAllMessages();
+
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+        userDeviceLockDown();
+
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+    }
+
+    private void userDeviceLockDown() {
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+        when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId))
+                .thenReturn(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+    }
+
+    private void supportsFaceDetection() {
+        when(mFaceSensorProperties.get(anyInt()))
+                .thenReturn(createFaceSensorProperties(
+                        /* supportsFaceDetection = */ true));
+    }
+
+    private void lockscreenBypassIsAllowed() {
+        mockCanBypassLockscreen(true);
+    }
+
+    private void mockCanBypassLockscreen(boolean canBypass) {
+        mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
+        when(mKeyguardBypassController.canBypass()).thenReturn(canBypass);
+    }
+
+    private void lockscreenBypassIsNotAllowed() {
+        mockCanBypassLockscreen(false);
+    }
+
     private void cleanupKeyguardUpdateMonitor() {
         if (mKeyguardUpdateMonitor != null) {
             mKeyguardUpdateMonitor.removeCallback(mTestCallback);
@@ -1693,6 +2149,15 @@
                 .onAuthenticationAcquired(FINGERPRINT_ACQUIRED_START);
     }
 
+    private void successfulFingerprintAuth() {
+        mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
+                .onAuthenticationSucceeded(
+                        new FingerprintManager.AuthenticationResult(null,
+                                null,
+                                mCurrentUserId,
+                                true));
+    }
+
     private void triggerSuccessfulFaceAuth() {
         mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
         verify(mFaceManager).authenticate(any(),
@@ -1726,9 +2191,16 @@
         );
     }
 
+    private void strongAuthRequiredEncrypted() {
+        when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+                .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+    }
+
     private void strongAuthNotRequired() {
         when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
                 .thenReturn(0);
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
     }
 
     private void currentUserDoesNotHaveTrust() {
@@ -1769,8 +2241,12 @@
         setKeyguardBouncerVisibility(true);
     }
 
+    private void bouncerNotVisible() {
+        setKeyguardBouncerVisibility(false);
+    }
+
     private void setKeyguardBouncerVisibility(boolean isVisible) {
-        mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible, isVisible);
+        mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(isVisible, isVisible);
         mTestableLooper.processAllMessages();
     }
 
@@ -1802,9 +2278,9 @@
         AtomicBoolean mSimStateChanged = new AtomicBoolean(false);
 
         protected TestableKeyguardUpdateMonitor(Context context) {
-            super(context,
+            super(context, mUserTracker,
                     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/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
index ff4412e9..7a5b772 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
@@ -17,6 +17,7 @@
 
 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.reset;
 import static org.mockito.Mockito.verify;
@@ -30,15 +31,15 @@
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
 
-import androidx.lifecycle.MutableLiveData;
-
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerFake;
 import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.settings.CurrentUserObservable;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.After;
 import org.junit.Before;
@@ -52,8 +53,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-// Need to run tests on main looper because LiveData operations such as setData, observe,
-// removeObserver cannot be invoked on a background thread.
+// Need to run tests on main looper to allow for onClockChanged operation to happen synchronously.
 @RunWithLooper(setAsMainLooper = true)
 public final class ClockManagerTest extends SysuiTestCase {
 
@@ -63,14 +63,16 @@
     private static final int SECONDARY_USER_ID = 11;
     private static final Uri SETTINGS_URI = null;
 
+    private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
     private ClockManager mClockManager;
     private ContentObserver mContentObserver;
     private DockManagerFake mFakeDockManager;
-    private MutableLiveData<Integer> mCurrentUser;
+    private ArgumentCaptor<UserTracker.Callback> mUserTrackerCallbackCaptor;
     @Mock PluginManager mMockPluginManager;
     @Mock SysuiColorExtractor mMockColorExtractor;
     @Mock ContentResolver mMockContentResolver;
-    @Mock CurrentUserObservable mMockCurrentUserObserable;
+    @Mock UserTracker mUserTracker;
     @Mock SettingsWrapper mMockSettingsWrapper;
     @Mock ClockManager.ClockChangedListener mMockListener1;
     @Mock ClockManager.ClockChangedListener mMockListener2;
@@ -83,18 +85,18 @@
 
         mFakeDockManager = new DockManagerFake();
 
-        mCurrentUser = new MutableLiveData<>();
-        mCurrentUser.setValue(MAIN_USER_ID);
-        when(mMockCurrentUserObserable.getCurrentUser()).thenReturn(mCurrentUser);
+        when(mUserTracker.getUserId()).thenReturn(MAIN_USER_ID);
+        mUserTrackerCallbackCaptor = ArgumentCaptor.forClass(UserTracker.Callback.class);
 
         mClockManager = new ClockManager(getContext(), inflater,
                 mMockPluginManager, mMockColorExtractor, mMockContentResolver,
-                mMockCurrentUserObserable, mMockSettingsWrapper, mFakeDockManager);
+                mUserTracker, mMainExecutor, mMockSettingsWrapper, mFakeDockManager);
 
         mClockManager.addBuiltinClock(() -> new BubbleClockController(
                 getContext().getResources(), inflater, mMockColorExtractor));
         mClockManager.addOnClockChangedListener(mMockListener1);
         mClockManager.addOnClockChangedListener(mMockListener2);
+        verify(mUserTracker).addCallback(mUserTrackerCallbackCaptor.capture(), any());
         reset(mMockListener1, mMockListener2);
 
         mContentObserver = mClockManager.getContentObserver();
@@ -221,7 +223,7 @@
     @Test
     public void onUserChanged_defaultClock() {
         // WHEN the user changes
-        mCurrentUser.setValue(SECONDARY_USER_ID);
+        switchUser(SECONDARY_USER_ID);
         // THEN the plugin is null for the default clock face
         assertThat(mClockManager.getCurrentClock()).isNull();
     }
@@ -232,7 +234,7 @@
         when(mMockSettingsWrapper.getLockScreenCustomClockFace(SECONDARY_USER_ID)).thenReturn(
                 BUBBLE_CLOCK);
         // WHEN the user changes
-        mCurrentUser.setValue(SECONDARY_USER_ID);
+        switchUser(SECONDARY_USER_ID);
         // THEN the plugin is the bubble clock face.
         assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
     }
@@ -244,8 +246,13 @@
         // AND the second user as selected the bubble clock for the dock
         when(mMockSettingsWrapper.getDockedClockFace(SECONDARY_USER_ID)).thenReturn(BUBBLE_CLOCK);
         // WHEN the user changes
-        mCurrentUser.setValue(SECONDARY_USER_ID);
+        switchUser(SECONDARY_USER_ID);
         // THEN the plugin is the bubble clock face.
         assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
     }
+
+    private void switchUser(int newUser) {
+        when(mUserTracker.getUserId()).thenReturn(newUser);
+        mUserTrackerCallbackCaptor.getValue().onUserChanged(newUser, mContext);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
index 6b1ef38..81d0034 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
@@ -3,6 +3,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.PackageManager
+import android.content.pm.UserInfo
 import android.content.res.Resources
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
@@ -11,9 +12,11 @@
 import com.android.systemui.flags.FlagListenable
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancel
@@ -26,9 +29,9 @@
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -44,6 +47,8 @@
     private lateinit var chooserSelector: ChooserSelector
 
     @Mock private lateinit var mockContext: Context
+    @Mock private lateinit var mockProfileContext: Context
+    @Mock private lateinit var mockUserTracker: UserTracker
     @Mock private lateinit var mockPackageManager: PackageManager
     @Mock private lateinit var mockResources: Resources
     @Mock private lateinit var mockFeatureFlags: FeatureFlags
@@ -52,12 +57,20 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
 
-        `when`(mockContext.packageManager).thenReturn(mockPackageManager)
-        `when`(mockContext.resources).thenReturn(mockResources)
-        `when`(mockResources.getString(anyInt())).thenReturn(
+        whenever(mockContext.createContextAsUser(any(), anyInt())).thenReturn(mockProfileContext)
+        whenever(mockContext.resources).thenReturn(mockResources)
+        whenever(mockProfileContext.packageManager).thenReturn(mockPackageManager)
+        whenever(mockResources.getString(anyInt())).thenReturn(
                 ComponentName("TestPackage", "TestClass").flattenToString())
+        whenever(mockUserTracker.userProfiles).thenReturn(listOf(UserInfo(), UserInfo()))
 
-        chooserSelector = ChooserSelector(mockContext, mockFeatureFlags, testScope, testDispatcher)
+        chooserSelector = ChooserSelector(
+                mockContext,
+                mockUserTracker,
+                mockFeatureFlags,
+                testScope,
+                testDispatcher,
+        )
     }
 
     @After
@@ -74,7 +87,9 @@
 
         // Assert
         verify(mockFeatureFlags).addListener(
-                eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+                eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
+                flagListener.capture(),
+        )
         verify(mockFeatureFlags, never()).removeListener(any())
 
         // Act
@@ -87,86 +102,102 @@
     @Test
     fun initialize_enablesUnbundledChooser_whenFlagEnabled() {
         // Arrange
-        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+        whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
 
         // Act
         chooserSelector.start()
 
         // Assert
-        verify(mockPackageManager).setComponentEnabledSetting(
+        verify(mockPackageManager, times(2)).setComponentEnabledSetting(
                 eq(ComponentName("TestPackage", "TestClass")),
                 eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
-                anyInt())
+                anyInt(),
+        )
     }
 
     @Test
     fun initialize_disablesUnbundledChooser_whenFlagDisabled() {
         // Arrange
-        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+        whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
 
         // Act
         chooserSelector.start()
 
         // Assert
-        verify(mockPackageManager).setComponentEnabledSetting(
+        verify(mockPackageManager, times(2)).setComponentEnabledSetting(
                 eq(ComponentName("TestPackage", "TestClass")),
                 eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
-                anyInt())
+                anyInt(),
+        )
     }
 
     @Test
     fun enablesUnbundledChooser_whenFlagBecomesEnabled() {
         // Arrange
-        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+        whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
         chooserSelector.start()
         verify(mockFeatureFlags).addListener(
-                eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+                eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
+                flagListener.capture(),
+        )
         verify(mockPackageManager, never()).setComponentEnabledSetting(
-                any(), eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED), anyInt())
+                any(),
+                eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
+                anyInt(),
+        )
 
         // Act
-        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+        whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
         flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
 
         // Assert
-        verify(mockPackageManager).setComponentEnabledSetting(
+        verify(mockPackageManager, times(2)).setComponentEnabledSetting(
                 eq(ComponentName("TestPackage", "TestClass")),
                 eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
-                anyInt())
+                anyInt(),
+        )
     }
 
     @Test
     fun disablesUnbundledChooser_whenFlagBecomesDisabled() {
         // Arrange
-        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+        whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
         chooserSelector.start()
         verify(mockFeatureFlags).addListener(
-                eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+                eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
+                flagListener.capture(),
+        )
         verify(mockPackageManager, never()).setComponentEnabledSetting(
-                any(), eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED), anyInt())
+                any(),
+                eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+                anyInt(),
+        )
 
         // Act
-        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+        whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
         flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
 
         // Assert
-        verify(mockPackageManager).setComponentEnabledSetting(
+        verify(mockPackageManager, times(2)).setComponentEnabledSetting(
                 eq(ComponentName("TestPackage", "TestClass")),
                 eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
-                anyInt())
+                anyInt(),
+        )
     }
 
     @Test
     fun doesNothing_whenAnotherFlagChanges() {
         // Arrange
-        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+        whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
         chooserSelector.start()
         verify(mockFeatureFlags).addListener(
-                eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
+                eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
+                flagListener.capture(),
+        )
         clearInvocations(mockPackageManager)
 
         // Act
-        `when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+        whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
         flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id + 1))
 
         // Assert
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt
index a4e0825..5886206 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui
 
+import android.content.Context
 import android.graphics.Canvas
 import android.graphics.Insets
 import android.graphics.Path
@@ -48,6 +49,7 @@
     @Mock private lateinit var mockCanvas: Canvas
     @Mock private lateinit var mockRootView: View
     @Mock private lateinit var mockDisplay: Display
+    @Mock private lateinit var mockContext: Context
 
     private lateinit var cutoutBaseView: DisplayCutoutBaseView
     private val cutout: DisplayCutout = DisplayCutout.Builder()
@@ -168,7 +170,9 @@
                 R.bool.config_fillMainBuiltInDisplayCutout, fillCutout)
 
         cutoutBaseView = spy(DisplayCutoutBaseView(mContext))
-        whenever(cutoutBaseView.display).thenReturn(mockDisplay)
+
+        whenever(cutoutBaseView.context).thenReturn(mockContext)
+        whenever(mockContext.display).thenReturn(mockDisplay)
         whenever(mockDisplay.uniqueId).thenReturn("mockDisplayUniqueId")
         whenever(cutoutBaseView.rootView).thenReturn(mockRootView)
         whenever(cutoutBaseView.getPhysicalPixelDisplaySizeRatio()).thenReturn(1f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
index 054650b..8207fa6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui
 
+import android.content.Context
 import android.graphics.Insets
 import android.graphics.PixelFormat
 import android.graphics.Rect
@@ -44,6 +45,7 @@
 
     @Mock private lateinit var mockDisplay: Display
     @Mock private lateinit var mockRootView: View
+    @Mock private lateinit var mockContext: Context
 
     private val displayWidth = 100
     private val displayHeight = 200
@@ -75,7 +77,8 @@
         decorHwcLayer = Mockito.spy(ScreenDecorHwcLayer(mContext, decorationSupport))
         whenever(decorHwcLayer.width).thenReturn(displayWidth)
         whenever(decorHwcLayer.height).thenReturn(displayHeight)
-        whenever(decorHwcLayer.display).thenReturn(mockDisplay)
+        whenever(decorHwcLayer.context).thenReturn(mockContext)
+        whenever(mockContext.display).thenReturn(mockDisplay)
         whenever(decorHwcLayer.rootView).thenReturn(mockRootView)
         whenever(mockRootView.left).thenReturn(0)
         whenever(mockRootView.top).thenReturn(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 181839a..0627fc6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -77,7 +77,6 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.decor.CornerDecorProvider;
 import com.android.systemui.decor.CutoutDecorProviderFactory;
 import com.android.systemui.decor.CutoutDecorProviderImpl;
@@ -132,8 +131,6 @@
     @Mock
     private TunerService mTunerService;
     @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
     private UserTracker mUserTracker;
     @Mock
     private PrivacyDotViewController mDotViewController;
@@ -223,8 +220,8 @@
                 mExecutor));
 
         mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
-                mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController,
-                mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory) {
+                mTunerService, mUserTracker, mDotViewController, mThreadFactory,
+                mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory) {
             @Override
             public void start() {
                 super.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 69ccc8b..9d39a8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -34,7 +34,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -88,6 +87,8 @@
 import com.android.systemui.util.leak.ReferenceTestUtils;
 import com.android.systemui.utils.os.FakeHandler;
 
+import com.google.common.util.concurrent.AtomicDouble;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -710,7 +711,7 @@
     }
 
     @Test
-    public void onSingleTap_enabled_scaleIsChanged() {
+    public void onSingleTap_enabled_scaleAnimates() {
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
@@ -721,14 +722,28 @@
         });
 
         final View mirrorView = mWindowManager.getAttachedView();
+
         final long timeout = SystemClock.uptimeMillis() + 1000;
-        while (SystemClock.uptimeMillis() < timeout) {
-            SystemClock.sleep(10);
-            if (Float.compare(1.0f, mirrorView.getScaleX()) < 0) {
-                return;
+        final AtomicDouble maxScaleX = new AtomicDouble();
+        final Runnable onAnimationFrame = new Runnable() {
+            @Override
+            public void run() {
+                // For some reason the fancy way doesn't compile...
+//                maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
+                final double oldMax = maxScaleX.get();
+                final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
+                assertTrue(maxScaleX.compareAndSet(oldMax, newMax));
+
+                if (SystemClock.uptimeMillis() < timeout) {
+                    mirrorView.postOnAnimation(this);
+                }
             }
-        }
-        fail("MirrorView scale is not changed");
+        };
+        mirrorView.postOnAnimation(onAnimationFrame);
+
+        waitForIdleSync();
+
+        ReferenceTestUtils.waitForCondition(() -> maxScaleX.get() > 1.0);
     }
 
     @Test
@@ -903,6 +918,97 @@
     }
 
     @Test
+    public void changeMagnificationSize_expectedWindowSize() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+        final float magnificationScaleLarge = 2.5f;
+        final int initSize = Math.min(bounds.width(), bounds.height()) / 3;
+        final int magnificationSize = (int) (initSize * magnificationScaleLarge);
+
+        final int expectedWindowHeight = magnificationSize;
+        final int expectedWindowWidth = magnificationSize;
+
+        mInstrumentation.runOnMainSync(
+                () ->
+                        mWindowMagnificationController.enableWindowMagnificationInternal(
+                                Float.NaN, Float.NaN, Float.NaN));
+
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.changeMagnificationSize(
+                            WindowMagnificationSettings.MagnificationSize.LARGE);
+                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                });
+
+        assertEquals(expectedWindowHeight, actualWindowHeight.get());
+        assertEquals(expectedWindowWidth, actualWindowWidth.get());
+    }
+
+    @Test
+    public void editModeOnDragCorner_resizesWindow() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+        final int startingSize = (int) (bounds.width() / 2);
+
+        mInstrumentation.runOnMainSync(
+                () ->
+                        mWindowMagnificationController.enableWindowMagnificationInternal(
+                                Float.NaN, Float.NaN, Float.NaN));
+
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+                    mWindowMagnificationController.setEditMagnifierSizeMode(true);
+                });
+
+        waitForIdleSync();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController
+                            .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f);
+                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                });
+
+        assertEquals(startingSize + 1, actualWindowHeight.get());
+        assertEquals(startingSize + 2, actualWindowWidth.get());
+    }
+
+    @Test
+    public void editModeOnDragEdge_resizesWindowInOnlyOneDirection() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+        final int startingSize = (int) (bounds.width() / 2f);
+
+        mInstrumentation.runOnMainSync(
+                () ->
+                        mWindowMagnificationController.enableWindowMagnificationInternal(
+                                Float.NaN, Float.NaN, Float.NaN));
+
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+                    mWindowMagnificationController.setEditMagnifierSizeMode(true);
+                    mWindowMagnificationController
+                            .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f);
+                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                });
+        assertEquals(startingSize + 1, actualWindowHeight.get());
+        assertEquals(startingSize, actualWindowWidth.get());
+    }
+
+    @Test
     public void setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen() {
 
         final int minimumWindowSize = mResources.getDimensionPixelSize(
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/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index d0bd4f7..b2c5266 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -32,8 +32,10 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Prefs;
 import com.android.systemui.SysuiTestCase;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,6 +46,7 @@
 @SmallTest
 public class MenuAnimationControllerTest extends SysuiTestCase {
 
+    private boolean mLastIsMoveToTucked;
     private ViewPropertyAnimator mViewPropertyAnimator;
     private MenuView mMenuView;
     private MenuAnimationController mMenuAnimationController;
@@ -60,6 +63,14 @@
         doReturn(mViewPropertyAnimator).when(mMenuView).animate();
 
         mMenuAnimationController = new MenuAnimationController(mMenuView);
+        mLastIsMoveToTucked = Prefs.getBoolean(mContext,
+                Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* defaultValue= */ false);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
+                mLastIsMoveToTucked);
     }
 
     @Test
@@ -81,10 +92,34 @@
 
     @Test
     public void startGrowAnimation_menuCompletelyOpaque() {
-        mMenuAnimationController.startShrinkAnimation(null);
+        mMenuAnimationController.startShrinkAnimation(/* endAction= */ null);
 
         mMenuAnimationController.startGrowAnimation();
 
         assertThat(mMenuView.getAlpha()).isEqualTo(/* completelyOpaque */ 1.0f);
     }
+
+    @Test
+    public void moveToEdgeAndHide_untucked_expectedSharedPreferenceValue() {
+        Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* value= */
+                false);
+
+        mMenuAnimationController.moveToEdgeAndHide();
+        final boolean isMoveToTucked = Prefs.getBoolean(mContext,
+                Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* defaultValue= */ false);
+
+        assertThat(isMoveToTucked).isTrue();
+    }
+
+    @Test
+    public void moveOutEdgeAndShow_tucked_expectedSharedPreferenceValue() {
+        Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* value= */
+                true);
+
+        mMenuAnimationController.moveOutEdgeAndShow();
+        final boolean isMoveToTucked = Prefs.getBoolean(mContext,
+                Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* defaultValue= */ true);
+
+        assertThat(isMoveToTucked).isFalse();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index d20eeaf..428a00a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -18,23 +18,44 @@
 
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.systemBars;
 
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
 import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.graphics.Insets;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
+import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.WindowMetrics;
 import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -43,12 +64,34 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** Tests for {@link MenuViewLayer}. */
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class MenuViewLayerTest extends SysuiTestCase {
+    private static final String SELECT_TO_SPEAK_PACKAGE_NAME = "com.google.android.marvin.talkback";
+    private static final String SELECT_TO_SPEAK_SERVICE_NAME =
+            "com.google.android.accessibility.selecttospeak.SelectToSpeakService";
+    private static final ComponentName TEST_SELECT_TO_SPEAK_COMPONENT_NAME = new ComponentName(
+            SELECT_TO_SPEAK_PACKAGE_NAME, SELECT_TO_SPEAK_SERVICE_NAME);
+
+    private static final int DISPLAY_WINDOW_WIDTH = 1080;
+    private static final int DISPLAY_WINDOW_HEIGHT = 2340;
+    private static final int STATUS_BAR_HEIGHT = 75;
+    private static final int NAVIGATION_BAR_HEIGHT = 125;
+    private static final int IME_HEIGHT = 350;
+    private static final int IME_TOP =
+            DISPLAY_WINDOW_HEIGHT - STATUS_BAR_HEIGHT - NAVIGATION_BAR_HEIGHT - IME_HEIGHT;
+
     private MenuViewLayer mMenuViewLayer;
+    private String mLastAccessibilityButtonTargets;
+    private String mLastEnabledAccessibilityServices;
+    private WindowMetrics mWindowMetrics;
+    private MenuView mMenuView;
+    private MenuAnimationController mMenuAnimationController;
 
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
@@ -56,13 +99,50 @@
     @Mock
     private IAccessibilityFloatingMenu mFloatingMenu;
 
+    @Mock
+    private WindowManager mStubWindowManager;
+
+    @Mock
+    private AccessibilityManager mStubAccessibilityManager;
+
     @Before
     public void setUp() throws Exception {
-        final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
-        final AccessibilityManager stubAccessibilityManager = mContext.getSystemService(
-                AccessibilityManager.class);
-        mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager, stubAccessibilityManager,
+        final Rect mDisplayBounds = new Rect();
+        mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH,
+                DISPLAY_WINDOW_HEIGHT);
+        mWindowMetrics = spy(new WindowMetrics(mDisplayBounds, fakeDisplayInsets()));
+        doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics();
+
+        mMenuViewLayer = new MenuViewLayer(mContext, mStubWindowManager, mStubAccessibilityManager,
                 mFloatingMenu);
+        mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
+        mMenuAnimationController = mMenuView.getMenuAnimationController();
+
+        mLastAccessibilityButtonTargets =
+                Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
+        mLastEnabledAccessibilityServices =
+                Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, UserHandle.USER_CURRENT);
+
+        mMenuViewLayer.onAttachedToWindow();
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", UserHandle.USER_CURRENT);
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "", UserHandle.USER_CURRENT);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastAccessibilityButtonTargets,
+                UserHandle.USER_CURRENT);
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, mLastEnabledAccessibilityServices,
+                UserHandle.USER_CURRENT);
+
+        mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
+        mMenuViewLayer.onDetachedFromWindow();
     }
 
     @Test
@@ -87,4 +167,110 @@
 
         verify(mFloatingMenu).hide();
     }
+
+    @Test
+    public void tiggerDismissMenuAction_matchA11yButtonTargetsResult() {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+                MAGNIFICATION_COMPONENT_NAME.flattenToString(), UserHandle.USER_CURRENT);
+
+        mMenuViewLayer.mDismissMenuAction.run();
+        final String value =
+                Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
+
+        assertThat(value).isEqualTo("");
+    }
+
+    @Test
+    public void tiggerDismissMenuAction_matchEnabledA11yServicesResult() {
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
+        final ResolveInfo resolveInfo = new ResolveInfo();
+        final ServiceInfo serviceInfo = new ServiceInfo();
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        resolveInfo.serviceInfo = serviceInfo;
+        serviceInfo.applicationInfo = applicationInfo;
+        applicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
+        final AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
+        accessibilityServiceInfo.setResolveInfo(resolveInfo);
+        accessibilityServiceInfo.flags = AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+        final List<AccessibilityServiceInfo> serviceInfoList = new ArrayList<>();
+        accessibilityServiceInfo.setComponentName(TEST_SELECT_TO_SPEAK_COMPONENT_NAME);
+        serviceInfoList.add(accessibilityServiceInfo);
+        when(mStubAccessibilityManager.getEnabledAccessibilityServiceList(
+                AccessibilityServiceInfo.FEEDBACK_ALL_MASK)).thenReturn(serviceInfoList);
+
+        mMenuViewLayer.mDismissMenuAction.run();
+        final String value = Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+
+        assertThat(value).isEqualTo("");
+    }
+
+    @Test
+    public void showingImeInsetsChange_notOverlapOnIme_menuKeepOriginalPosition() {
+        final float menuTop = STATUS_BAR_HEIGHT + 100;
+        mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
+
+        dispatchShowingImeInsets();
+
+        assertThat(mMenuView.getTranslationX()).isEqualTo(0);
+        assertThat(mMenuView.getTranslationY()).isEqualTo(menuTop);
+    }
+
+    @Test
+    public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
+        final float menuTop = IME_TOP + 100;
+        mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
+
+        dispatchShowingImeInsets();
+
+        final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
+        assertThat(mMenuView.getTranslationX()).isEqualTo(0);
+        assertThat(menuBottom).isLessThan(IME_TOP);
+    }
+
+    @Test
+    public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() {
+        final float menuTop = IME_TOP + 200;
+        mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
+        dispatchShowingImeInsets();
+
+        dispatchHidingImeInsets();
+
+        assertThat(mMenuView.getTranslationX()).isEqualTo(0);
+        assertThat(mMenuView.getTranslationY()).isEqualTo(menuTop);
+    }
+
+    private void dispatchShowingImeInsets() {
+        final WindowInsets fakeShowingImeInsets = fakeImeInsets(/* isImeVisible= */ true);
+        doReturn(fakeShowingImeInsets).when(mWindowMetrics).getWindowInsets();
+        mMenuViewLayer.dispatchApplyWindowInsets(fakeShowingImeInsets);
+    }
+
+    private void dispatchHidingImeInsets() {
+        final WindowInsets fakeHidingImeInsets = fakeImeInsets(/* isImeVisible= */ false);
+        doReturn(fakeHidingImeInsets).when(mWindowMetrics).getWindowInsets();
+        mMenuViewLayer.dispatchApplyWindowInsets(fakeHidingImeInsets);
+    }
+
+    private WindowInsets fakeDisplayInsets() {
+        return new WindowInsets.Builder()
+                .setVisible(systemBars() | displayCutout(), /* visible= */ true)
+                .setInsets(systemBars() | displayCutout(),
+                        Insets.of(/* left= */ 0, STATUS_BAR_HEIGHT, /* right= */ 0,
+                                NAVIGATION_BAR_HEIGHT))
+                .build();
+    }
+
+    private WindowInsets fakeImeInsets(boolean isImeVisible) {
+        final int bottom = isImeVisible ? (IME_HEIGHT + NAVIGATION_BAR_HEIGHT) : 0;
+        return new WindowInsets.Builder()
+                .setVisible(ime(), isImeVisible)
+                .setInsets(ime(),
+                        Insets.of(/* left= */ 0, /* top= */ 0, /* right= */ 0, bottom))
+                .build();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
new file mode 100644
index 0000000..2c680be
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.animation
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import java.lang.reflect.Modifier
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class InterpolatorsAndroidXTest : SysuiTestCase() {
+
+    @Test
+    fun testInterpolatorsAndInterpolatorsAndroidXPublicMethodsAreEqual() {
+        assertEquals(
+            Interpolators::class.java.getPublicMethods(),
+            InterpolatorsAndroidX::class.java.getPublicMethods()
+        )
+    }
+
+    @Test
+    fun testInterpolatorsAndInterpolatorsAndroidXPublicFieldsAreEqual() {
+        assertEquals(
+            Interpolators::class.java.getPublicFields(),
+            InterpolatorsAndroidX::class.java.getPublicFields()
+        )
+    }
+
+    private fun <T> Class<T>.getPublicMethods() =
+        declaredMethods
+            .filter { Modifier.isPublic(it.modifiers) }
+            .map { it.toString().replace(name, "") }
+            .toSet()
+
+    private fun <T> Class<T>.getPublicFields() =
+        fields.filter { Modifier.isPublic(it.modifiers) }.map { it.name }.toSet()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
new file mode 100644
index 0000000..982f033
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.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.systemui.battery
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class AccessorizedBatteryDrawableTest : SysuiTestCase() {
+    @Test
+    fun intrinsicSize_shieldFalse_isBatterySize() {
+        val drawable = AccessorizedBatteryDrawable(context, frameColor = 0)
+        drawable.displayShield = false
+
+        val density = context.resources.displayMetrics.density
+        assertThat(drawable.intrinsicHeight).isEqualTo((BATTERY_HEIGHT * density).toInt())
+        assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH * density).toInt())
+    }
+
+    @Test
+    fun intrinsicSize_shieldTrue_isBatteryPlusShieldSize() {
+        val drawable = AccessorizedBatteryDrawable(context, frameColor = 0)
+        drawable.displayShield = true
+
+        val density = context.resources.displayMetrics.density
+        assertThat(drawable.intrinsicHeight)
+            .isEqualTo((BATTERY_HEIGHT_WITH_SHIELD * density).toInt())
+        assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH_WITH_SHIELD * density).toInt())
+    }
+
+    // TODO(b/255625888): Screenshot tests for this drawable would be amazing!
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index 1d038a4..1482f29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -34,7 +34,9 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
@@ -50,15 +52,16 @@
     private BatteryMeterView mBatteryMeterView;
 
     @Mock
+    private UserTracker mUserTracker;
+    @Mock
     private ConfigurationController mConfigurationController;
     @Mock
     private TunerService mTunerService;
     @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
     private Handler mHandler;
     @Mock
     private ContentResolver mContentResolver;
+    private FakeFeatureFlags mFeatureFlags;
     @Mock
     private BatteryController mBatteryController;
 
@@ -71,19 +74,13 @@
         when(mBatteryMeterView.getContext()).thenReturn(mContext);
         when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources());
 
-        mController = new BatteryMeterViewController(
-                mBatteryMeterView,
-                mConfigurationController,
-                mTunerService,
-                mBroadcastDispatcher,
-                mHandler,
-                mContentResolver,
-                mBatteryController
-        );
+        mFeatureFlags = new FakeFeatureFlags();
+        mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
     }
 
     @Test
     public void onViewAttached_callbacksRegistered() {
+        initController();
         mController.onViewAttached();
 
         verify(mConfigurationController).addCallback(any());
@@ -101,6 +98,7 @@
 
     @Test
     public void onViewDetached_callbacksUnregistered() {
+        initController();
         // Set everything up first.
         mController.onViewAttached();
 
@@ -114,6 +112,7 @@
 
     @Test
     public void ignoreTunerUpdates_afterOnViewAttached_callbackUnregistered() {
+        initController();
         // Start out receiving tuner updates
         mController.onViewAttached();
 
@@ -124,10 +123,43 @@
 
     @Test
     public void ignoreTunerUpdates_beforeOnViewAttached_callbackNeverRegistered() {
+        initController();
+
         mController.ignoreTunerUpdates();
 
         mController.onViewAttached();
 
         verify(mTunerService, never()).addTunable(any(), any());
     }
+
+    @Test
+    public void shieldFlagDisabled_viewNotified() {
+        mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
+
+        initController();
+
+        verify(mBatteryMeterView).setDisplayShieldEnabled(false);
+    }
+
+    @Test
+    public void shieldFlagEnabled_viewNotified() {
+        mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, true);
+
+        initController();
+
+        verify(mBatteryMeterView).setDisplayShieldEnabled(true);
+    }
+
+    private void initController() {
+        mController = new BatteryMeterViewController(
+                mBatteryMeterView,
+                mUserTracker,
+                mConfigurationController,
+                mTunerService,
+                mHandler,
+                mContentResolver,
+                mFeatureFlags,
+                mBatteryController
+        );
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index b4ff2a5..eb7d9c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -17,7 +17,9 @@
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import android.widget.ImageView
 import androidx.test.filters.SmallTest
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher
 import com.android.systemui.statusbar.policy.BatteryController.EstimateFetchCompletion
@@ -58,6 +60,182 @@
         // No assert needed
     }
 
+    @Test
+    fun contentDescription_unknown() {
+        mBatteryMeterView.onBatteryUnknownStateChanged(true)
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_unknown)
+        )
+    }
+
+    @Test
+    fun contentDescription_estimate() {
+        mBatteryMeterView.onBatteryLevelChanged(15, false)
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+        mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+        mBatteryMeterView.updatePercentText()
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(
+                        R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+                )
+        )
+    }
+
+    @Test
+    fun contentDescription_estimateAndOverheated() {
+        mBatteryMeterView.onBatteryLevelChanged(17, false)
+        mBatteryMeterView.onIsOverheatedChanged(true)
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+        mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+        mBatteryMeterView.updatePercentText()
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(
+                        R.string.accessibility_battery_level_charging_paused_with_estimate,
+                        17,
+                        ESTIMATE,
+                )
+        )
+    }
+
+    @Test
+    fun contentDescription_overheated() {
+        mBatteryMeterView.onBatteryLevelChanged(90, false)
+        mBatteryMeterView.onIsOverheatedChanged(true)
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level_charging_paused, 90)
+        )
+    }
+
+    @Test
+    fun contentDescription_charging() {
+        mBatteryMeterView.onBatteryLevelChanged(45, true)
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level_charging, 45)
+        )
+    }
+
+    @Test
+    fun contentDescription_notCharging() {
+        mBatteryMeterView.onBatteryLevelChanged(45, false)
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level, 45)
+        )
+    }
+
+    @Test
+    fun changesFromEstimateToPercent_textAndContentDescriptionChanges() {
+        mBatteryMeterView.onBatteryLevelChanged(15, false)
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+        mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+        mBatteryMeterView.updatePercentText()
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(
+                        R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+                )
+        )
+
+        // Update the show mode from estimate to percent
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+
+        assertThat(mBatteryMeterView.batteryPercentViewText).isEqualTo("15%")
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level, 15)
+        )
+    }
+
+    @Test
+    fun contentDescription_manyUpdates_alwaysUpdated() {
+        // Overheated
+        mBatteryMeterView.onBatteryLevelChanged(90, false)
+        mBatteryMeterView.onIsOverheatedChanged(true)
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level_charging_paused, 90)
+        )
+
+        // Overheated & estimate
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+        mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+        mBatteryMeterView.updatePercentText()
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(
+                        R.string.accessibility_battery_level_charging_paused_with_estimate,
+                        90,
+                        ESTIMATE,
+                )
+        )
+
+        // Just estimate
+        mBatteryMeterView.onIsOverheatedChanged(false)
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(
+                        R.string.accessibility_battery_level_with_estimate,
+                        90,
+                        ESTIMATE,
+                )
+        )
+
+        // Just percent
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level, 90)
+        )
+
+        // Charging
+        mBatteryMeterView.onBatteryLevelChanged(90, true)
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level_charging, 90)
+        )
+    }
+
+    @Test
+    fun isOverheatedChanged_true_drawableGetsTrue() {
+        mBatteryMeterView.setDisplayShieldEnabled(true)
+        val drawable = getBatteryDrawable()
+
+        mBatteryMeterView.onIsOverheatedChanged(true)
+
+        assertThat(drawable.displayShield).isTrue()
+    }
+
+    @Test
+    fun isOverheatedChanged_false_drawableGetsFalse() {
+        mBatteryMeterView.setDisplayShieldEnabled(true)
+        val drawable = getBatteryDrawable()
+
+        // Start as true
+        mBatteryMeterView.onIsOverheatedChanged(true)
+
+        // Update to false
+        mBatteryMeterView.onIsOverheatedChanged(false)
+
+        assertThat(drawable.displayShield).isFalse()
+    }
+
+    @Test
+    fun isOverheatedChanged_true_featureflagOff_drawableGetsFalse() {
+        mBatteryMeterView.setDisplayShieldEnabled(false)
+        val drawable = getBatteryDrawable()
+
+        mBatteryMeterView.onIsOverheatedChanged(true)
+
+        assertThat(drawable.displayShield).isFalse()
+    }
+
+    private fun getBatteryDrawable(): AccessorizedBatteryDrawable {
+        return (mBatteryMeterView.getChildAt(0) as ImageView)
+                .drawable as AccessorizedBatteryDrawable
+    }
+
     private class Fetcher : BatteryEstimateFetcher {
         override fun fetchBatteryTimeRemainingEstimate(
                 completion: EstimateFetchCompletion) {
@@ -68,4 +246,4 @@
     private companion object {
         const val ESTIMATE = "2 hours 2 minutes"
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
new file mode 100644
index 0000000..39cb047
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.battery
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class BatterySpecsTest : SysuiTestCase() {
+    @Test
+    fun getFullBatteryHeight_shieldFalse_returnsMainHeight() {
+        val fullHeight = BatterySpecs.getFullBatteryHeight(56f, displayShield = false)
+
+        assertThat(fullHeight).isEqualTo(56f)
+    }
+
+    @Test
+    fun getFullBatteryHeight_shieldTrue_returnsMainHeightPlusShield() {
+        val mainHeight = BATTERY_HEIGHT * 5
+        val fullHeight = BatterySpecs.getFullBatteryHeight(mainHeight, displayShield = true)
+
+        // Since the main battery was scaled 5x, the output height should also be scaled 5x
+        val expectedFullHeight = BATTERY_HEIGHT_WITH_SHIELD * 5
+
+        assertThat(fullHeight).isWithin(.0001f).of(expectedFullHeight)
+    }
+
+    @Test
+    fun getFullBatteryWidth_shieldFalse_returnsMainWidth() {
+        val fullWidth = BatterySpecs.getFullBatteryWidth(33f, displayShield = false)
+
+        assertThat(fullWidth).isEqualTo(33f)
+    }
+
+    @Test
+    fun getFullBatteryWidth_shieldTrue_returnsMainWidthPlusShield() {
+        val mainWidth = BATTERY_WIDTH * 3.3f
+
+        val fullWidth = BatterySpecs.getFullBatteryWidth(mainWidth, displayShield = true)
+
+        // Since the main battery was scaled 3.3x, the output width should also be scaled 5x
+        val expectedFullWidth = BATTERY_WIDTH_WITH_SHIELD * 3.3f
+        assertThat(fullWidth).isWithin(.0001f).of(expectedFullWidth)
+    }
+
+    @Test
+    fun getMainBatteryHeight_shieldFalse_returnsFullHeight() {
+        val mainHeight = BatterySpecs.getMainBatteryHeight(89f, displayShield = false)
+
+        assertThat(mainHeight).isEqualTo(89f)
+    }
+
+    @Test
+    fun getMainBatteryHeight_shieldTrue_returnsNotFullHeight() {
+        val fullHeight = BATTERY_HEIGHT_WITH_SHIELD * 7.7f
+
+        val mainHeight = BatterySpecs.getMainBatteryHeight(fullHeight, displayShield = true)
+
+        // Since the full height was scaled 7.7x, the main height should also be scaled 7.7x.
+        val expectedHeight = BATTERY_HEIGHT * 7.7f
+        assertThat(mainHeight).isWithin(.0001f).of(expectedHeight)
+    }
+
+    @Test
+    fun getMainBatteryWidth_shieldFalse_returnsFullWidth() {
+        val mainWidth = BatterySpecs.getMainBatteryWidth(2345f, displayShield = false)
+
+        assertThat(mainWidth).isEqualTo(2345f)
+    }
+
+    @Test
+    fun getMainBatteryWidth_shieldTrue_returnsNotFullWidth() {
+        val fullWidth = BATTERY_WIDTH_WITH_SHIELD * 0.6f
+
+        val mainWidth = BatterySpecs.getMainBatteryWidth(fullWidth, displayShield = true)
+
+        // Since the full width was scaled 0.6x, the main height should also be scaled 0.6x.
+        val expectedWidth = BATTERY_WIDTH * 0.6f
+        assertThat(mainWidth).isWithin(.0001f).of(expectedWidth)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 45b8ce1..898f370 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -125,6 +125,21 @@
     }
 
     @Test
+    fun testCredentialPasswordDismissesOnBack() {
+        val container = initializeCredentialPasswordContainer(addToView = true)
+        assertThat(container.parent).isNotNull()
+        val root = container.rootView
+
+        // Simulate back invocation
+        container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK))
+        container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK))
+        waitForIdleSync()
+
+        assertThat(container.parent).isNull()
+        assertThat(root.isAttachedToWindow).isFalse()
+    }
+
+    @Test
     fun testIgnoresAnimatedInWhenDismissed() {
         val container = initializeFingerprintContainer(addToView = false)
         container.dismissFromSystemServer()
@@ -175,6 +190,25 @@
     }
 
     @Test
+    fun testFocusLossAfterRotating() {
+        val container = initializeFingerprintContainer()
+        waitForIdleSync()
+
+        val requestID = authContainer?.requestId ?: 0L
+
+        verify(callback).onDialogAnimatedIn(requestID)
+        container.onOrientationChanged()
+        container.onWindowFocusChanged(false)
+        waitForIdleSync()
+
+        verify(callback, never()).onDismissed(
+                eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+                eq<ByteArray?>(null), /* credentialAttestation */
+                eq(requestID)
+        )
+    }
+
+    @Test
     fun testDismissesOnFocusLoss_hidesKeyboardWhenVisible() {
         val container = initializeFingerprintContainer(
             authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
@@ -350,20 +384,7 @@
 
     @Test
     fun testCredentialUI_disablesClickingOnBackground() {
-        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
-        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
-            DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
-        )
-
-        // In the credential view, clicking on the background (to cancel authentication) is not
-        // valid. Thus, the listener should be null, and it should not be in the accessibility
-        // hierarchy.
-        val container = initializeFingerprintContainer(
-            authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
-        )
-        waitForIdleSync()
-
-        assertThat(container.hasCredentialPasswordView()).isTrue()
+        val container = initializeCredentialPasswordContainer()
         assertThat(container.hasBiometricPrompt()).isFalse()
         assertThat(
             container.findViewById<View>(R.id.background)?.isImportantForAccessibility
@@ -423,6 +444,27 @@
         verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L)
     }
 
+    private fun initializeCredentialPasswordContainer(
+            addToView: Boolean = true,
+    ): TestAuthContainerView {
+        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
+        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
+            DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+        )
+
+        // In the credential view, clicking on the background (to cancel authentication) is not
+        // valid. Thus, the listener should be null, and it should not be in the accessibility
+        // hierarchy.
+        val container = initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
+                addToView = addToView,
+        )
+        waitForIdleSync()
+
+        assertThat(container.hasCredentialPasswordView()).isTrue()
+        return container
+    }
+
     private fun initializeFingerprintContainer(
         authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
         addToView: Boolean = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 4dd46ed..40b2cdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -152,7 +152,7 @@
     @Mock
     private UdfpsController mUdfpsController;
     @Mock
-    private SidefpsController mSidefpsController;
+    private SideFpsController mSideFpsController;
     @Mock
     private DisplayManager mDisplayManager;
     @Mock
@@ -255,7 +255,7 @@
 
         mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
                 mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
-                () -> mUdfpsController, () -> mSidefpsController, mStatusBarStateController,
+                () -> mUdfpsController, () -> mSideFpsController, mStatusBarStateController,
                 mVibratorHelper);
 
         mAuthController.start();
@@ -288,7 +288,7 @@
         // This test requires an uninitialized AuthController.
         AuthController authController = new TestableAuthController(mContextSpy, mExecution,
                 mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
-                mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
+                mFaceManager, () -> mUdfpsController, () -> mSideFpsController,
                 mStatusBarStateController, mVibratorHelper);
         authController.start();
 
@@ -318,7 +318,7 @@
         // This test requires an uninitialized AuthController.
         AuthController authController = new TestableAuthController(mContextSpy, mExecution,
                 mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
-                mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
+                mFaceManager, () -> mUdfpsController, () -> mSideFpsController,
                 mStatusBarStateController, mVibratorHelper);
         authController.start();
 
@@ -1009,7 +1009,7 @@
                 FingerprintManager fingerprintManager,
                 FaceManager faceManager,
                 Provider<UdfpsController> udfpsControllerFactory,
-                Provider<SidefpsController> sidefpsControllerFactory,
+                Provider<SideFpsController> sidefpsControllerFactory,
                 StatusBarStateController statusBarStateController,
                 VibratorHelper vibratorHelper) {
             super(context, execution, commandQueue, activityTaskManager, windowManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 0b528a5..b765ab3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -26,6 +26,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.LightRevealScrim
@@ -37,7 +38,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.leak.RotationUtils
-import javax.inject.Provider
+import com.android.systemui.util.mockito.any
 import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -46,15 +47,16 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
-import org.mockito.Mockito.any
+import org.mockito.Mockito.`when`
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 import org.mockito.MockitoSession
 import org.mockito.quality.Strictness
+import javax.inject.Provider
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -76,6 +78,7 @@
     @Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController>
     @Mock private lateinit var udfpsController: UdfpsController
     @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var featureFlags: FeatureFlags
     @Mock private lateinit var lightRevealScrim: LightRevealScrim
     @Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
 
@@ -105,6 +108,7 @@
             biometricUnlockController,
             udfpsControllerProvider,
             statusBarStateController,
+            featureFlags,
             rippleView
         )
         controller.init()
@@ -118,12 +122,13 @@
 
     @Test
     fun testFingerprintTrigger_KeyguardShowing_Ripple() {
-        // GIVEN fp exists, keyguard is showing, user doesn't need strong auth
+        // GIVEN fp exists, keyguard is showing, unlocking with fp allowed
         val fpsLocation = Point(5, 5)
         `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
         `when`(keyguardStateController.isShowing).thenReturn(true)
-        `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
 
         // WHEN fingerprint authenticated
         val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
@@ -140,11 +145,12 @@
 
     @Test
     fun testFingerprintTrigger_KeyguardNotShowing_NoRipple() {
-        // GIVEN fp exists & user doesn't need strong auth
+        // GIVEN fp exists & unlocking with fp allowed
         val fpsLocation = Point(5, 5)
         `when`(authController.udfpsLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
-        `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
 
         // WHEN keyguard is NOT showing & fingerprint authenticated
         `when`(keyguardStateController.isShowing).thenReturn(false)
@@ -160,15 +166,16 @@
     }
 
     @Test
-    fun testFingerprintTrigger_StrongAuthRequired_NoRipple() {
+    fun testFingerprintTrigger_biometricUnlockNotAllowed_NoRipple() {
         // GIVEN fp exists & keyguard is showing
         val fpsLocation = Point(5, 5)
         `when`(authController.udfpsLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
         `when`(keyguardStateController.isShowing).thenReturn(true)
 
-        // WHEN user needs strong auth & fingerprint authenticated
-        `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(true)
+        // WHEN unlocking with fingerprint is NOT allowed & fingerprint authenticated
+        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                eq(BiometricSourceType.FINGERPRINT))).thenReturn(false)
         val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
         verify(keyguardUpdateMonitor).registerCallback(captor.capture())
         captor.value.onBiometricAuthenticated(
@@ -182,13 +189,14 @@
 
     @Test
     fun testFaceTriggerBypassEnabled_Ripple() {
-        // GIVEN face auth sensor exists, keyguard is showing & strong auth isn't required
+        // GIVEN face auth sensor exists, keyguard is showing & unlocking with face is allowed
         val faceLocation = Point(5, 5)
         `when`(authController.faceSensorLocation).thenReturn(faceLocation)
         controller.onViewAttached()
 
         `when`(keyguardStateController.isShowing).thenReturn(true)
-        `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                BiometricSourceType.FACE)).thenReturn(true)
 
         // WHEN bypass is enabled & face authenticated
         `when`(bypassController.canBypass()).thenReturn(true)
@@ -275,6 +283,8 @@
         `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
         `when`(keyguardStateController.isShowing).thenReturn(true)
+        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                BiometricSourceType.FINGERPRINT)).thenReturn(true)
         `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
 
         controller.showUnlockRipple(BiometricSourceType.FINGERPRINT)
@@ -295,6 +305,8 @@
         `when`(keyguardStateController.isShowing).thenReturn(true)
         `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
         `when`(authController.isUdfpsFingerDown).thenReturn(true)
+        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+                eq(BiometricSourceType.FACE))).thenReturn(true)
 
         controller.showUnlockRipple(BiometricSourceType.FACE)
         assertTrue("reveal didn't start on keyguardFadingAway",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
new file mode 100644
index 0000000..3c40835
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -0,0 +1,488 @@
+/*
+ * 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.Animator
+import android.app.ActivityManager
+import android.app.ActivityTaskManager
+import android.content.ComponentName
+import android.graphics.Insets
+import android.graphics.Rect
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManagerGlobal
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.hardware.fingerprint.ISidefpsController
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+import android.view.DisplayInfo
+import android.view.LayoutInflater
+import android.view.Surface
+import android.view.View
+import android.view.ViewPropertyAnimator
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.airbnb.lottie.LottieAnimationView
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.recents.OverviewProxyService
+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.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+import org.mockito.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.junit.MockitoJUnit
+
+private const val DISPLAY_ID = 2
+private const val SENSOR_ID = 1
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class SideFpsControllerTest : SysuiTestCase() {
+
+    @JvmField @Rule var rule = MockitoJUnit.rule()
+
+    @Mock lateinit var layoutInflater: LayoutInflater
+    @Mock lateinit var fingerprintManager: FingerprintManager
+    @Mock lateinit var windowManager: WindowManager
+    @Mock lateinit var activityTaskManager: ActivityTaskManager
+    @Mock lateinit var sideFpsView: View
+    @Mock lateinit var displayManager: DisplayManager
+    @Mock lateinit var overviewProxyService: OverviewProxyService
+    @Mock lateinit var handler: Handler
+    @Mock lateinit var dumpManager: DumpManager
+    @Captor lateinit var overlayCaptor: ArgumentCaptor<View>
+    @Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
+
+    private val executor = FakeExecutor(FakeSystemClock())
+    private lateinit var overlayController: ISidefpsController
+    private lateinit var sideFpsController: SideFpsController
+
+    enum class DeviceConfig {
+        X_ALIGNED,
+        Y_ALIGNED_UNFOLDED,
+        Y_ALIGNED_FOLDED
+    }
+
+    private lateinit var deviceConfig: DeviceConfig
+    private lateinit var indicatorBounds: Rect
+    private lateinit var displayBounds: Rect
+    private lateinit var sensorLocation: SensorLocationInternal
+    private var displayWidth: Int = 0
+    private var displayHeight: Int = 0
+    private var boundsWidth: Int = 0
+    private var boundsHeight: Int = 0
+
+    @Before
+    fun setup() {
+        context.addMockSystemService(DisplayManager::class.java, displayManager)
+        context.addMockSystemService(WindowManager::class.java, windowManager)
+
+        whenEver(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView)
+        whenEver(sideFpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
+            .thenReturn(mock(LottieAnimationView::class.java))
+        with(mock(ViewPropertyAnimator::class.java)) {
+            whenEver(sideFpsView.animate()).thenReturn(this)
+            whenEver(alpha(anyFloat())).thenReturn(this)
+            whenEver(setStartDelay(anyLong())).thenReturn(this)
+            whenEver(setDuration(anyLong())).thenReturn(this)
+            whenEver(setListener(any())).thenAnswer {
+                (it.arguments[0] as Animator.AnimatorListener).onAnimationEnd(
+                    mock(Animator::class.java)
+                )
+                this
+            }
+        }
+    }
+
+    private fun testWithDisplay(
+        deviceConfig: DeviceConfig = DeviceConfig.X_ALIGNED,
+        initInfo: DisplayInfo.() -> Unit = {},
+        windowInsets: WindowInsets = insetsForSmallNavbar(),
+        block: () -> Unit
+    ) {
+        this.deviceConfig = deviceConfig
+
+        when (deviceConfig) {
+            DeviceConfig.X_ALIGNED -> {
+                displayWidth = 2560
+                displayHeight = 1600
+                sensorLocation = SensorLocationInternal("", 2325, 0, 0)
+                boundsWidth = 160
+                boundsHeight = 84
+            }
+            DeviceConfig.Y_ALIGNED_UNFOLDED -> {
+                displayWidth = 2208
+                displayHeight = 1840
+                sensorLocation = SensorLocationInternal("", 0, 510, 0)
+                boundsWidth = 110
+                boundsHeight = 210
+            }
+            DeviceConfig.Y_ALIGNED_FOLDED -> {
+                displayWidth = 1080
+                displayHeight = 2100
+                sensorLocation = SensorLocationInternal("", 0, 590, 0)
+                boundsWidth = 110
+                boundsHeight = 210
+            }
+        }
+        indicatorBounds = Rect(0, 0, boundsWidth, boundsHeight)
+        displayBounds = Rect(0, 0, displayWidth, displayHeight)
+        var locations = listOf(sensorLocation)
+
+        whenEver(fingerprintManager.sensorPropertiesInternal)
+            .thenReturn(
+                listOf(
+                    FingerprintSensorPropertiesInternal(
+                        SENSOR_ID,
+                        SensorProperties.STRENGTH_STRONG,
+                        5 /* maxEnrollmentsPerUser */,
+                        listOf() /* componentInfo */,
+                        FingerprintSensorProperties.TYPE_POWER_BUTTON,
+                        true /* halControlsIllumination */,
+                        true /* resetLockoutRequiresHardwareAuthToken */,
+                        locations
+                    )
+                )
+            )
+
+        val displayInfo = DisplayInfo()
+        displayInfo.initInfo()
+        val dmGlobal = mock(DisplayManagerGlobal::class.java)
+        val display = Display(dmGlobal, DISPLAY_ID, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS)
+        whenEver(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
+        whenEver(windowManager.defaultDisplay).thenReturn(display)
+        whenEver(windowManager.maximumWindowMetrics)
+            .thenReturn(WindowMetrics(displayBounds, WindowInsets.CONSUMED))
+        whenEver(windowManager.currentWindowMetrics)
+            .thenReturn(WindowMetrics(displayBounds, windowInsets))
+
+        sideFpsController =
+            SideFpsController(
+                context.createDisplayContext(display),
+                layoutInflater,
+                fingerprintManager,
+                windowManager,
+                activityTaskManager,
+                overviewProxyService,
+                displayManager,
+                executor,
+                handler,
+                dumpManager
+            )
+
+        overlayController =
+            ArgumentCaptor.forClass(ISidefpsController::class.java)
+                .apply { verify(fingerprintManager).setSidefpsController(capture()) }
+                .value
+
+        block()
+    }
+
+    @Test
+    fun testSubscribesToOrientationChangesWhenShowingOverlay() = testWithDisplay {
+        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+        executor.runAllReady()
+
+        verify(displayManager).registerDisplayListener(any(), eq(handler), anyLong())
+
+        overlayController.hide(SENSOR_ID)
+        executor.runAllReady()
+        verify(displayManager).unregisterDisplayListener(any())
+    }
+
+    @Test
+    fun testShowsAndHides() = testWithDisplay {
+        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+        executor.runAllReady()
+
+        verify(windowManager).addView(overlayCaptor.capture(), any())
+
+        reset(windowManager)
+        overlayController.hide(SENSOR_ID)
+        executor.runAllReady()
+
+        verify(windowManager, never()).addView(any(), any())
+        verify(windowManager).removeView(eq(overlayCaptor.value))
+    }
+
+    @Test
+    fun testShowsOnce() = testWithDisplay {
+        repeat(5) {
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+        }
+
+        verify(windowManager).addView(any(), any())
+        verify(windowManager, never()).removeView(any())
+    }
+
+    @Test
+    fun testHidesOnce() = testWithDisplay {
+        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+        executor.runAllReady()
+
+        repeat(5) {
+            overlayController.hide(SENSOR_ID)
+            executor.runAllReady()
+        }
+
+        verify(windowManager).addView(any(), any())
+        verify(windowManager).removeView(any())
+    }
+
+    @Test fun testIgnoredForKeyguard() = testWithDisplay { testIgnoredFor(REASON_AUTH_KEYGUARD) }
+
+    @Test
+    fun testShowsForMostSettings() = testWithDisplay {
+        whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpEnrollTask()))
+        testIgnoredFor(REASON_AUTH_SETTINGS, ignored = false)
+    }
+
+    @Test
+    fun testIgnoredForVerySpecificSettings() = testWithDisplay {
+        whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpSettingsTask()))
+        testIgnoredFor(REASON_AUTH_SETTINGS)
+    }
+
+    private fun testIgnoredFor(reason: Int, ignored: Boolean = true) {
+        overlayController.show(SENSOR_ID, reason)
+        executor.runAllReady()
+
+        verify(windowManager, if (ignored) never() else times(1)).addView(any(), any())
+    }
+
+    @Test
+    fun showsWithTaskbar() =
+        testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED, { rotation = Surface.ROTATION_0 }) {
+            hidesWithTaskbar(visible = true)
+        }
+
+    @Test
+    fun showsWithTaskbarOnY() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            { rotation = Surface.ROTATION_0 }
+        ) { hidesWithTaskbar(visible = true) }
+
+    @Test
+    fun showsWithTaskbar90() =
+        testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED, { rotation = Surface.ROTATION_90 }) {
+            hidesWithTaskbar(visible = true)
+        }
+
+    @Test
+    fun showsWithTaskbar90OnY() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            { rotation = Surface.ROTATION_90 }
+        ) { hidesWithTaskbar(visible = true) }
+
+    @Test
+    fun showsWithTaskbar180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            { rotation = Surface.ROTATION_180 }
+        ) { hidesWithTaskbar(visible = true) }
+
+    @Test
+    fun showsWithTaskbar270OnY() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            { rotation = Surface.ROTATION_270 }
+        ) { hidesWithTaskbar(visible = true) }
+
+    @Test
+    fun showsWithTaskbarCollapsedDown() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            { rotation = Surface.ROTATION_270 },
+            windowInsets = insetsForSmallNavbar()
+        ) { hidesWithTaskbar(visible = true) }
+
+    @Test
+    fun showsWithTaskbarCollapsedDownOnY() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            { rotation = Surface.ROTATION_180 },
+            windowInsets = insetsForSmallNavbar()
+        ) { hidesWithTaskbar(visible = true) }
+
+    @Test
+    fun hidesWithTaskbarDown() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            { rotation = Surface.ROTATION_180 },
+            windowInsets = insetsForLargeNavbar()
+        ) { hidesWithTaskbar(visible = false) }
+
+    @Test
+    fun hidesWithTaskbarDownOnY() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            { rotation = Surface.ROTATION_270 },
+            windowInsets = insetsForLargeNavbar()
+        ) { hidesWithTaskbar(visible = true) }
+
+    private fun hidesWithTaskbar(visible: Boolean) {
+        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+        executor.runAllReady()
+
+        sideFpsController.overviewProxyListener.onTaskbarStatusUpdated(visible, false)
+        executor.runAllReady()
+
+        verify(windowManager).addView(any(), any())
+        verify(windowManager, never()).removeView(any())
+        verify(sideFpsView).visibility = if (visible) View.VISIBLE else View.GONE
+    }
+
+    @Test
+    fun testIndicatorPlacementForXAlignedSensor() =
+        testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED) {
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            sideFpsController.overlayOffsets = sensorLocation
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
+            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
+        }
+
+    @Test
+    fun testIndicatorPlacementForYAlignedSensor() =
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+            sideFpsController.overlayOffsets = sensorLocation
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
+            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
+        }
+
+    @Test
+    fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay {
+        // By default all those tests assume the side fps sensor is available.
+
+        assertThat(fingerprintManager.hasSideFpsSensor()).isTrue()
+    }
+
+    @Test
+    fun hasSideFpsSensor_withoutSensorProps_returnsFalse() {
+        whenEver(fingerprintManager.sensorPropertiesInternal).thenReturn(null)
+
+        assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
+    }
+
+    @Test
+    fun testLayoutParams_isKeyguardDialogType() =
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+            sideFpsController.overlayOffsets = sensorLocation
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+            val lpType = overlayViewParamsCaptor.value.type
+
+            assertThat((lpType and TYPE_KEYGUARD_DIALOG) != 0).isTrue()
+        }
+
+    @Test
+    fun testLayoutParams_hasNoMoveAnimationWindowFlag() =
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+            sideFpsController.overlayOffsets = sensorLocation
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+            val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+            assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
+        }
+
+    @Test
+    fun testLayoutParams_hasTrustedOverlayWindowFlag() =
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+            sideFpsController.overlayOffsets = sensorLocation
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+            val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+            assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
+        }
+}
+
+private fun insetsForSmallNavbar() = insetsWithBottom(60)
+
+private fun insetsForLargeNavbar() = insetsWithBottom(100)
+
+private fun insetsWithBottom(bottom: Int) =
+    WindowInsets.Builder()
+        .setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, bottom))
+        .build()
+
+private fun fpEnrollTask() = settingsTask(".biometrics.fingerprint.FingerprintEnrollEnrolling")
+
+private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings")
+
+private fun settingsTask(cls: String) =
+    ActivityManager.RunningTaskInfo().apply {
+        topActivity = ComponentName.createRelative("com.android.settings", cls)
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
deleted file mode 100644
index 8d969d0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ /dev/null
@@ -1,493 +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 android.animation.Animator
-import android.app.ActivityManager
-import android.app.ActivityTaskManager
-import android.content.ComponentName
-import android.graphics.Insets
-import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
-import android.hardware.biometrics.SensorLocationInternal
-import android.hardware.biometrics.SensorProperties
-import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManagerGlobal
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorProperties
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.hardware.fingerprint.ISidefpsController
-import android.os.Handler
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.Display
-import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
-import android.view.DisplayInfo
-import android.view.LayoutInflater
-import android.view.Surface
-import android.view.View
-import android.view.ViewPropertyAnimator
-import android.view.WindowInsets
-import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
-import android.view.WindowMetrics
-import androidx.test.filters.SmallTest
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.recents.OverviewProxyService
-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.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyFloat
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyLong
-import org.mockito.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.junit.MockitoJUnit
-
-private const val DISPLAY_ID = 2
-private const val SENSOR_ID = 1
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class SidefpsControllerTest : SysuiTestCase() {
-
-    @JvmField @Rule
-    var rule = MockitoJUnit.rule()
-
-    @Mock
-    lateinit var layoutInflater: LayoutInflater
-    @Mock
-    lateinit var fingerprintManager: FingerprintManager
-    @Mock
-    lateinit var windowManager: WindowManager
-    @Mock
-    lateinit var activityTaskManager: ActivityTaskManager
-    @Mock
-    lateinit var sidefpsView: View
-    @Mock
-    lateinit var displayManager: DisplayManager
-    @Mock
-    lateinit var overviewProxyService: OverviewProxyService
-    @Mock
-    lateinit var handler: Handler
-    @Captor
-    lateinit var overlayCaptor: ArgumentCaptor<View>
-    @Captor
-    lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
-
-    private val executor = FakeExecutor(FakeSystemClock())
-    private lateinit var overlayController: ISidefpsController
-    private lateinit var sideFpsController: SidefpsController
-
-    enum class DeviceConfig { X_ALIGNED, Y_ALIGNED_UNFOLDED, Y_ALIGNED_FOLDED }
-
-    private lateinit var deviceConfig: DeviceConfig
-    private lateinit var indicatorBounds: Rect
-    private lateinit var displayBounds: Rect
-    private lateinit var sensorLocation: SensorLocationInternal
-    private var displayWidth: Int = 0
-    private var displayHeight: Int = 0
-    private var boundsWidth: Int = 0
-    private var boundsHeight: Int = 0
-
-    @Before
-    fun setup() {
-        context.addMockSystemService(DisplayManager::class.java, displayManager)
-        context.addMockSystemService(WindowManager::class.java, windowManager)
-
-        whenEver(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sidefpsView)
-        whenEver(sidefpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
-            .thenReturn(mock(LottieAnimationView::class.java))
-        with(mock(ViewPropertyAnimator::class.java)) {
-            whenEver(sidefpsView.animate()).thenReturn(this)
-            whenEver(alpha(anyFloat())).thenReturn(this)
-            whenEver(setStartDelay(anyLong())).thenReturn(this)
-            whenEver(setDuration(anyLong())).thenReturn(this)
-            whenEver(setListener(any())).thenAnswer {
-                (it.arguments[0] as Animator.AnimatorListener)
-                    .onAnimationEnd(mock(Animator::class.java))
-                this
-            }
-        }
-    }
-
-    private fun testWithDisplay(
-        deviceConfig: DeviceConfig = DeviceConfig.X_ALIGNED,
-        initInfo: DisplayInfo.() -> Unit = {},
-        windowInsets: WindowInsets = insetsForSmallNavbar(),
-        block: () -> Unit
-    ) {
-        this.deviceConfig = deviceConfig
-
-        when (deviceConfig) {
-            DeviceConfig.X_ALIGNED -> {
-                displayWidth = 2560
-                displayHeight = 1600
-                sensorLocation = SensorLocationInternal("", 2325, 0, 0)
-                boundsWidth = 160
-                boundsHeight = 84
-            }
-            DeviceConfig.Y_ALIGNED_UNFOLDED -> {
-                displayWidth = 2208
-                displayHeight = 1840
-                sensorLocation = SensorLocationInternal("", 0, 510, 0)
-                boundsWidth = 110
-                boundsHeight = 210
-            }
-            DeviceConfig.Y_ALIGNED_FOLDED -> {
-                displayWidth = 1080
-                displayHeight = 2100
-                sensorLocation = SensorLocationInternal("", 0, 590, 0)
-                boundsWidth = 110
-                boundsHeight = 210
-            }
-        }
-        indicatorBounds = Rect(0, 0, boundsWidth, boundsHeight)
-        displayBounds = Rect(0, 0, displayWidth, displayHeight)
-        var locations = listOf(sensorLocation)
-
-        whenEver(fingerprintManager.sensorPropertiesInternal).thenReturn(
-            listOf(
-                FingerprintSensorPropertiesInternal(
-                    SENSOR_ID,
-                    SensorProperties.STRENGTH_STRONG,
-                    5 /* maxEnrollmentsPerUser */,
-                    listOf() /* componentInfo */,
-                    FingerprintSensorProperties.TYPE_POWER_BUTTON,
-                    true /* halControlsIllumination */,
-                    true /* resetLockoutRequiresHardwareAuthToken */,
-                    locations
-                )
-            )
-        )
-
-        val displayInfo = DisplayInfo()
-        displayInfo.initInfo()
-        val dmGlobal = mock(DisplayManagerGlobal::class.java)
-        val display = Display(dmGlobal, DISPLAY_ID, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS)
-        whenEver(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
-        whenEver(windowManager.defaultDisplay).thenReturn(display)
-        whenEver(windowManager.maximumWindowMetrics).thenReturn(
-                WindowMetrics(displayBounds, WindowInsets.CONSUMED)
-        )
-        whenEver(windowManager.currentWindowMetrics).thenReturn(
-            WindowMetrics(displayBounds, windowInsets)
-        )
-
-        sideFpsController = SidefpsController(
-            context.createDisplayContext(display), layoutInflater, fingerprintManager,
-            windowManager, activityTaskManager, overviewProxyService, displayManager, executor,
-            handler
-        )
-
-        overlayController = ArgumentCaptor.forClass(ISidefpsController::class.java).apply {
-            verify(fingerprintManager).setSidefpsController(capture())
-        }.value
-
-        block()
-    }
-
-    @Test
-    fun testSubscribesToOrientationChangesWhenShowingOverlay() = testWithDisplay {
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        verify(displayManager).registerDisplayListener(any(), eq(handler), anyLong())
-
-        overlayController.hide(SENSOR_ID)
-        executor.runAllReady()
-        verify(displayManager).unregisterDisplayListener(any())
-    }
-
-    @Test
-    fun testShowsAndHides() = testWithDisplay {
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        verify(windowManager).addView(overlayCaptor.capture(), any())
-
-        reset(windowManager)
-        overlayController.hide(SENSOR_ID)
-        executor.runAllReady()
-
-        verify(windowManager, never()).addView(any(), any())
-        verify(windowManager).removeView(eq(overlayCaptor.value))
-    }
-
-    @Test
-    fun testShowsOnce() = testWithDisplay {
-        repeat(5) {
-            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-            executor.runAllReady()
-        }
-
-        verify(windowManager).addView(any(), any())
-        verify(windowManager, never()).removeView(any())
-    }
-
-    @Test
-    fun testHidesOnce() = testWithDisplay {
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        repeat(5) {
-            overlayController.hide(SENSOR_ID)
-            executor.runAllReady()
-        }
-
-        verify(windowManager).addView(any(), any())
-        verify(windowManager).removeView(any())
-    }
-
-    @Test
-    fun testIgnoredForKeyguard() = testWithDisplay {
-        testIgnoredFor(REASON_AUTH_KEYGUARD)
-    }
-
-    @Test
-    fun testShowsForMostSettings() = testWithDisplay {
-        whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpEnrollTask()))
-        testIgnoredFor(REASON_AUTH_SETTINGS, ignored = false)
-    }
-
-    @Test
-    fun testIgnoredForVerySpecificSettings() = testWithDisplay {
-        whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpSettingsTask()))
-        testIgnoredFor(REASON_AUTH_SETTINGS)
-    }
-
-    private fun testIgnoredFor(reason: Int, ignored: Boolean = true) {
-        overlayController.show(SENSOR_ID, reason)
-        executor.runAllReady()
-
-        verify(windowManager, if (ignored) never() else times(1)).addView(any(), any())
-    }
-
-    @Test
-    fun showsWithTaskbar() = testWithDisplay(
-        deviceConfig = DeviceConfig.X_ALIGNED,
-        { rotation = Surface.ROTATION_0 }
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun showsWithTaskbarOnY() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
-        { rotation = Surface.ROTATION_0 }
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun showsWithTaskbar90() = testWithDisplay(
-        deviceConfig = DeviceConfig.X_ALIGNED,
-        { rotation = Surface.ROTATION_90 }
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun showsWithTaskbar90OnY() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
-        { rotation = Surface.ROTATION_90 }
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun showsWithTaskbar180() = testWithDisplay(
-        deviceConfig = DeviceConfig.X_ALIGNED,
-        { rotation = Surface.ROTATION_180 }
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun showsWithTaskbar270OnY() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
-        { rotation = Surface.ROTATION_270 }
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun showsWithTaskbarCollapsedDown() = testWithDisplay(
-        deviceConfig = DeviceConfig.X_ALIGNED,
-        { rotation = Surface.ROTATION_270 },
-        windowInsets = insetsForSmallNavbar()
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun showsWithTaskbarCollapsedDownOnY() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
-        { rotation = Surface.ROTATION_180 },
-        windowInsets = insetsForSmallNavbar()
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun hidesWithTaskbarDown() = testWithDisplay(
-        deviceConfig = DeviceConfig.X_ALIGNED,
-        { rotation = Surface.ROTATION_180 },
-        windowInsets = insetsForLargeNavbar()
-    ) {
-        hidesWithTaskbar(visible = false)
-    }
-
-    @Test
-    fun hidesWithTaskbarDownOnY() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
-        { rotation = Surface.ROTATION_270 },
-        windowInsets = insetsForLargeNavbar()
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    private fun hidesWithTaskbar(visible: Boolean) {
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        sideFpsController.overviewProxyListener.onTaskbarStatusUpdated(visible, false)
-        executor.runAllReady()
-
-        verify(windowManager).addView(any(), any())
-        verify(windowManager, never()).removeView(any())
-        verify(sidefpsView).visibility = if (visible) View.VISIBLE else View.GONE
-    }
-
-    @Test
-    fun testIndicatorPlacementForXAlignedSensor() = testWithDisplay(
-        deviceConfig = DeviceConfig.X_ALIGNED
-    ) {
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        sideFpsController.overlayOffsets = sensorLocation
-        sideFpsController.updateOverlayParams(
-            windowManager.defaultDisplay,
-            indicatorBounds
-        )
-        executor.runAllReady()
-
-        verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
-        assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
-        assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
-    }
-
-    @Test
-    fun testIndicatorPlacementForYAlignedSensor() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
-    ) {
-        sideFpsController.overlayOffsets = sensorLocation
-        sideFpsController.updateOverlayParams(
-            windowManager.defaultDisplay,
-            indicatorBounds
-        )
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-        assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
-        assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
-    }
-
-    @Test
-    fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay {
-        // By default all those tests assume the side fps sensor is available.
-
-        assertThat(fingerprintManager.hasSideFpsSensor()).isTrue()
-    }
-
-    @Test
-    fun hasSideFpsSensor_withoutSensorProps_returnsFalse() {
-        whenEver(fingerprintManager.sensorPropertiesInternal).thenReturn(null)
-
-        assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
-    }
-
-    @Test
-    fun testLayoutParams_hasNoMoveAnimationWindowFlag() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
-    ) {
-        sideFpsController.overlayOffsets = sensorLocation
-        sideFpsController.updateOverlayParams(
-            windowManager.defaultDisplay,
-            indicatorBounds
-        )
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
-        val lpFlags = overlayViewParamsCaptor.value.privateFlags
-
-        assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
-    }
-
-    @Test
-    fun testLayoutParams_hasTrustedOverlayWindowFlag() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
-    ) {
-        sideFpsController.overlayOffsets = sensorLocation
-        sideFpsController.updateOverlayParams(
-            windowManager.defaultDisplay,
-            indicatorBounds
-        )
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
-        val lpFlags = overlayViewParamsCaptor.value.privateFlags
-
-        assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
-    }
-}
-
-private fun insetsForSmallNavbar() = insetsWithBottom(60)
-private fun insetsForLargeNavbar() = insetsWithBottom(100)
-private fun insetsWithBottom(bottom: Int) = WindowInsets.Builder()
-    .setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, bottom))
-    .build()
-
-private fun fpEnrollTask() = settingsTask(".biometrics.fingerprint.FingerprintEnrollEnrolling")
-private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings")
-private fun settingsTask(cls: String) = ActivityManager.RunningTaskInfo().apply {
-    topActivity = ComponentName.createRelative("com.android.settings", cls)
-}
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..4b459c0 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.PrimaryBouncerInteractor
 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 mPrimaryBouncerInteractor: PrimaryBouncerInteractor
     @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,
+            mPrimaryBouncerInteractor, 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 ed957db..b267a5c 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;
@@ -68,9 +70,15 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.biometrics.udfps.InteractionEvent;
+import com.android.systemui.biometrics.udfps.NormalizedTouchData;
+import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
+import com.android.systemui.biometrics.udfps.TouchProcessorResult;
 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.PrimaryBouncerInteractor;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -107,20 +115,14 @@
 @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
     public MockitoRule rule = MockitoJUnit.rule();
-
     // Unit under test
     private UdfpsController mUdfpsController;
-
     // Dependencies
     private FakeExecutor mBiometricsExecutor;
-    private Execution mExecution;
     @Mock
     private LayoutInflater mLayoutInflater;
     @Mock
@@ -172,7 +174,6 @@
     private UdfpsDisplayMode mUdfpsDisplayMode;
     @Mock
     private FeatureFlags mFeatureFlags;
-
     // Stuff for configuring mocks
     @Mock
     private UdfpsView mUdfpsView;
@@ -192,19 +193,30 @@
     private ActivityLaunchAnimator mActivityLaunchAnimator;
     @Mock
     private AlternateUdfpsTouchProvider mAlternateTouchProvider;
+    @Mock
+    private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+    @Mock
+    private SinglePointerTouchProcessor mSinglePointerTouchProcessor;
 
     // 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);
@@ -217,9 +229,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 */,
@@ -229,61 +239,62 @@
                 "" /* 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 */));
-        when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
+                true /* resetLockoutRequiresHardwareAuthToken */);
+
+        mUltrasonicProps = new FingerprintSensorPropertiesInternal(2 /* sensorId */,
+                SensorProperties.STRENGTH_STRONG,
+                5 /* maxEnrollmentsPerUser */,
+                componentInfo,
+                FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
+                true /* resetLockoutRequiresHardwareAuthToken */);
+
         mFgExecutor = new FakeExecutor(new FakeSystemClock());
 
         // Create a fake background executor.
         mBiometricsExecutor = new FakeExecutor(new FakeSystemClock());
 
-        mUdfpsController = new UdfpsController(
-                mContext,
-                mExecution,
-                mLayoutInflater,
-                mFingerprintManager,
-                mWindowManager,
-                mStatusBarStateController,
-                mFgExecutor,
-                new ShadeExpansionStateManager(),
-                mStatusBarKeyguardViewManager,
-                mDumpManager,
-                mKeyguardUpdateMonitor,
-                mFeatureFlags,
-                mFalsingManager,
-                mPowerManager,
-                mAccessibilityManager,
-                mLockscreenShadeTransitionController,
-                mScreenLifecycle,
-                mVibrator,
-                mUdfpsHapticsSimulator,
-                mUdfpsShell,
-                mKeyguardStateController,
-                mDisplayManager,
-                mHandler,
-                mConfigurationController,
-                mSystemClock,
-                mUnlockedScreenOffAnimationController,
-                mSystemUIDialogManager,
-                mLatencyTracker,
-                mActivityLaunchAnimator,
-                Optional.of(mAlternateTouchProvider),
-                mBiometricsExecutor);
+        initUdfpsController(true /* hasAlternateTouchProvider */);
+    }
+
+    private void initUdfpsController(boolean hasAlternateTouchProvider) {
+        initUdfpsController(mOpticalProps, hasAlternateTouchProvider);
+    }
+
+    private void initUdfpsController(FingerprintSensorPropertiesInternal sensorProps,
+            boolean hasAlternateTouchProvider) {
+        reset(mFingerprintManager);
+        reset(mScreenLifecycle);
+
+        final Optional<AlternateUdfpsTouchProvider> alternateTouchProvider =
+                hasAlternateTouchProvider ? Optional.of(mAlternateTouchProvider) : Optional.empty();
+
+        mUdfpsController = new UdfpsController(mContext, new FakeExecution(), mLayoutInflater,
+                mFingerprintManager, mWindowManager, mStatusBarStateController, mFgExecutor,
+                new ShadeExpansionStateManager(), mStatusBarKeyguardViewManager, mDumpManager,
+                mKeyguardUpdateMonitor, mFeatureFlags, mFalsingManager, mPowerManager,
+                mAccessibilityManager, mLockscreenShadeTransitionController, mScreenLifecycle,
+                mVibrator, mUdfpsHapticsSimulator, mUdfpsShell, mKeyguardStateController,
+                mDisplayManager, mHandler, mConfigurationController, mSystemClock,
+                mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
+                mActivityLaunchAnimator, alternateTouchProvider, mBiometricsExecutor,
+                mPrimaryBouncerInteractor, mSinglePointerTouchProcessor);
         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(sensorProps, 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();
@@ -298,7 +309,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();
 
@@ -314,8 +325,7 @@
     }
 
     @Test
-    public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice()
-            throws RemoteException {
+    public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice() throws RemoteException {
         onActionMoveTouch_whenCanDismissLockScreen_entersDevice(false /* stale */);
     }
 
@@ -333,7 +343,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();
 
@@ -341,7 +351,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);
@@ -361,7 +371,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();
 
@@ -384,27 +394,27 @@
     @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(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).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
-        verify(mStatusBarKeyguardViewManager).resetAlternateAuth(eq(true));
+        verify(mStatusBarKeyguardViewManager).hideAlternateBouncer(eq(true));
     }
 
     @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());
@@ -434,12 +444,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();
@@ -447,7 +457,7 @@
 
                             // Update overlay parameters.
                             reset(mWindowManager);
-                            mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, newParams);
+                            mUdfpsController.updateOverlayParams(mOpticalProps, newParams);
                             mFgExecutor.runAllReady();
 
                             // Ensure the overlay was recreated.
@@ -469,18 +479,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();
@@ -502,8 +512,37 @@
                 new MotionEvent.PointerCoords[]{pc}, 0, 0, 1f, 1f, 0, 0, 0, 0);
     }
 
+    private static class TestParams {
+        public final FingerprintSensorPropertiesInternal sensorProps;
+        public final boolean hasAlternateTouchProvider;
+
+        TestParams(FingerprintSensorPropertiesInternal sensorProps,
+                boolean hasAlternateTouchProvider) {
+            this.sensorProps = sensorProps;
+            this.hasAlternateTouchProvider = hasAlternateTouchProvider;
+        }
+    }
+
+    private void runWithAllParams(ThrowingConsumer<TestParams> testParamsConsumer) {
+        for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps,
+                mUltrasonicProps)) {
+            for (boolean hasAlternateTouchProvider : new boolean[]{false, true}) {
+                initUdfpsController(sensorProps, hasAlternateTouchProvider);
+                testParamsConsumer.accept(new TestParams(sensorProps, hasAlternateTouchProvider));
+            }
+        }
+    }
+
     @Test
-    public void onTouch_propagatesTouchInNativeOrientationAndResolution() throws RemoteException {
+    public void onTouch_propagatesTouchInNativeOrientationAndResolution() {
+        runWithAllParams(
+                this::onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized);
+    }
+
+    private void onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized(
+            TestParams testParams) throws RemoteException {
+        reset(mUdfpsView);
+
         final Rect sensorBounds = new Rect(1000, 1900, 1080, 1920); // Bottom right corner.
         final int displayWidth = 1080;
         final int displayHeight = 1920;
@@ -522,13 +561,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, testParams.sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
         verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
 
         // Test ROTATION_0
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        mUdfpsController.updateOverlayParams(testParams.sensorProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, Surface.ROTATION_0));
         MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor,
@@ -540,12 +579,19 @@
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricsExecutor.runAllReady();
         event.recycle();
-        verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
-                eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+        if (testParams.hasAlternateTouchProvider) {
+            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
+                    eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+        } else {
+            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+                    eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
+                    eq(expectedMinor), eq(expectedMajor));
+        }
 
         // Test ROTATION_90
         reset(mAlternateTouchProvider);
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        reset(mFingerprintManager);
+        mUdfpsController.updateOverlayParams(testParams.sensorProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, Surface.ROTATION_90));
         event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor);
@@ -556,12 +602,19 @@
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricsExecutor.runAllReady();
         event.recycle();
-        verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
-                eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+        if (testParams.hasAlternateTouchProvider) {
+            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
+                    eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+        } else {
+            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+                    eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
+                    eq(expectedMinor), eq(expectedMajor));
+        }
 
         // Test ROTATION_270
         reset(mAlternateTouchProvider);
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        reset(mFingerprintManager);
+        mUdfpsController.updateOverlayParams(testParams.sensorProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, Surface.ROTATION_270));
         event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor);
@@ -572,12 +625,19 @@
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricsExecutor.runAllReady();
         event.recycle();
-        verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
-                eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+        if (testParams.hasAlternateTouchProvider) {
+            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
+                    eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+        } else {
+            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+                    eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
+                    eq(expectedMinor), eq(expectedMajor));
+        }
 
         // Test ROTATION_180
         reset(mAlternateTouchProvider);
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        reset(mFingerprintManager);
+        mUdfpsController.updateOverlayParams(testParams.sensorProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, Surface.ROTATION_180));
         // ROTATION_180 is not supported. It should be treated like ROTATION_0.
@@ -589,124 +649,228 @@
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricsExecutor.runAllReady();
         event.recycle();
-        verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
-                eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+        if (testParams.hasAlternateTouchProvider) {
+            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
+                    eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+        } else {
+            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+                    eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
+                    eq(expectedMinor), eq(expectedMajor));
+        }
     }
 
     @Test
-    public void fingerDown() throws RemoteException {
+    public void fingerDown() {
+        runWithAllParams(this::fingerDownParameterized);
+    }
+
+    private void fingerDownParameterized(TestParams testParams) 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, testParams.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));
+
+        // THEN the touch provider is notified about onPointerDown.
+        if (testParams.hasAlternateTouchProvider) {
+            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(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
+        } else {
+            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+                    eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(0f), eq(0f));
+            verify(mAlternateTouchProvider, never()).onPointerDown(anyInt(), anyInt(), anyInt(),
+                    anyFloat(), anyFloat());
+        }
+
         // AND display configuration begins
-        verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+        if (testParams.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 (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // AND onDisplayConfigured notifies FingerprintManager about onUiReady
+            mOnDisplayConfiguredCaptor.getValue().run();
+            mBiometricsExecutor.runAllReady();
+            if (testParams.hasAlternateTouchProvider) {
+                InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
+                inOrder.verify(mAlternateTouchProvider).onUiReady();
+                inOrder.verify(mLatencyTracker).onActionEnd(
+                        eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+                verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt());
+            } else {
+                InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker);
+                inOrder.verify(mFingerprintManager).onUiReady(eq(TEST_REQUEST_ID),
+                        eq(testParams.sensorProps.sensorId));
+                inOrder.verify(mLatencyTracker).onActionEnd(
+                        eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+                verify(mAlternateTouchProvider, never()).onUiReady();
+            }
+        } else {
+            verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt());
+            verify(mAlternateTouchProvider, never()).onUiReady();
+            verify(mLatencyTracker, never()).onActionEnd(
+                    eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+        }
     }
 
     @Test
-    public void aodInterrupt() throws RemoteException {
+    public void aodInterrupt() {
+        runWithAllParams(this::aodInterruptParameterized);
+    }
+
+    private void aodInterruptParameterized(TestParams testParams) 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, testParams.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 (testParams.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 */);
-        verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
-                anyFloat(), anyFloat());
-        verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
+
+        if (testParams.hasAlternateTouchProvider) {
+            verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0),
+                    eq(3f) /* minor */, eq(2f) /* major */);
+            verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
+                    anyInt(), anyFloat(), anyFloat());
+            verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
+        } else {
+            verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+                    eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(3f) /* minor */,
+                    eq(2f) /* major */);
+            verify(mAlternateTouchProvider, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
+                    anyFloat(), anyFloat());
+        }
     }
 
     @Test
-    public void cancelAodInterrupt() throws RemoteException {
+    public void cancelAodInterrupt() {
+        runWithAllParams(this::cancelAodInterruptParameterized);
+    }
+
+    private void cancelAodInterruptParameterized(TestParams testParams) throws RemoteException {
+        reset(mUdfpsView);
+
         // GIVEN AOD interrupt
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.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 (testParams.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() {
+        runWithAllParams(this::aodInterruptTimeoutParameterized);
+    }
+
+    private void aodInterruptTimeoutParameterized(TestParams testParams) throws RemoteException {
+        reset(mUdfpsView);
+
         // GIVEN AOD interrupt
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.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 (testParams.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 (testParams.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() {
+        runWithAllParams(this::aodInterruptCancelTimeoutActionOnFingerUpParameterized);
+    }
+
+    private void aodInterruptCancelTimeoutActionOnFingerUpParameterized(TestParams testParams)
+            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, testParams.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 (testParams.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());
@@ -731,37 +895,54 @@
         moveEvent.recycle();
         mFgExecutor.runAllReady();
 
-        // Configure UdfpsView to accept the finger up event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        if (testParams.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 (testParams.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() {
+        runWithAllParams(this::aodInterruptCancelTimeoutActionOnAcquiredParameterized);
+    }
+
+    private void aodInterruptCancelTimeoutActionOnAcquiredParameterized(TestParams testParams)
+            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, testParams.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 (testParams.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(testParams.sensorProps.sensorId,
                 BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD);
 
         // Configure UdfpsView to accept the ACTION_DOWN event
@@ -781,29 +962,42 @@
         moveEvent.recycle();
         mFgExecutor.runAllReady();
 
-        // Configure UdfpsView to accept the finger up event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        if (testParams.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 (testParams.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() {
+        runWithAllParams(this::aodInterruptScreenOffParameterized);
+    }
+
+    private void aodInterruptScreenOffParameterized(TestParams testParams) throws RemoteException {
+        reset(mUdfpsView);
+
         // GIVEN screen off
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.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
@@ -811,11 +1005,17 @@
     }
 
     @Test
-    public void aodInterrupt_fingerprintNotRunning() throws RemoteException {
+    public void aodInterrupt_fingerprintNotRunning() {
+        runWithAllParams(this::aodInterrupt_fingerprintNotRunningParameterized);
+    }
+
+    private void aodInterrupt_fingerprintNotRunningParameterized(TestParams testParams)
+            throws RemoteException {
+        reset(mUdfpsView);
+
         // GIVEN showing overlay
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
-                mUdfpsOverlayControllerCallback);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
 
@@ -835,7 +1035,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();
 
@@ -870,7 +1070,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();
 
@@ -893,4 +1093,100 @@
                 anyString(),
                 any());
     }
+
+    @Test
+    public void onTouch_withoutNewTouchDetection_shouldCallOldFingerprintManagerPath()
+            throws RemoteException {
+        // Disable new touch detection.
+        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(false);
+
+        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+        // Configure UdfpsView to accept the ACTION_DOWN event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        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();
+
+        // AND ACTION_MOVE is received
+        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+        mBiometricsExecutor.runAllReady();
+        moveEvent.recycle();
+
+        // AND ACTION_UP is received
+        MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
+        mBiometricsExecutor.runAllReady();
+        upEvent.recycle();
+
+        // THEN the old FingerprintManager path is invoked.
+        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
+                anyFloat(), anyFloat());
+        verify(mFingerprintManager).onPointerUp(anyLong(), anyInt());
+    }
+
+    @Test
+    public void onTouch_withNewTouchDetection_shouldCallOldFingerprintManagerPath()
+            throws RemoteException {
+        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+                0L);
+        final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch(
+                InteractionEvent.DOWN, 1 /* pointerId */, touchData);
+        final TouchProcessorResult processorResultUp = new TouchProcessorResult.ProcessedTouch(
+                InteractionEvent.UP, 1 /* pointerId */, touchData);
+
+        // Enable new touch detection.
+        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+
+        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+        // Configure UdfpsView to accept the ACTION_DOWN event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+        // WHEN ACTION_DOWN is received
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultDown);
+        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        mBiometricsExecutor.runAllReady();
+        downEvent.recycle();
+
+        // AND ACTION_UP is received
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultUp);
+        MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
+        mBiometricsExecutor.runAllReady();
+        upEvent.recycle();
+
+        // THEN the new FingerprintManager path is invoked.
+        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
+        verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
+    }
 }
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/UdfpsEnrollViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java
new file mode 100644
index 0000000..60a0258
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.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.systemui.biometrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class UdfpsEnrollViewTest extends SysuiTestCase {
+
+    private static String ENROLL_PROGRESS_COLOR_LIGHT = "#699FF3";
+    private static String ENROLL_PROGRESS_COLOR_DARK = "#7DA7F1";
+
+    @Test
+    public void fingerprintUdfpsEnroll_usesCorrectThemeCheckmarkFillColor() {
+        final Configuration config = mContext.getResources().getConfiguration();
+        final boolean isDarkThemeOn = (config.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                == Configuration.UI_MODE_NIGHT_YES;
+        final int currentColor = mContext.getColor(R.color.udfps_enroll_progress);
+
+        assertThat(currentColor).isEqualTo(Color.parseColor(isDarkThemeOn
+                ? ENROLL_PROGRESS_COLOR_DARK : ENROLL_PROGRESS_COLOR_LIGHT));
+    }
+}
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..3c61382
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.PrimaryBouncerInteractor;
+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 PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+
+    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.AlternateBouncer>
+            mAlternateBouncerCaptor;
+    protected StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
+
+    private @Captor ArgumentCaptor<KeyguardStateController.Callback>
+            mKeyguardStateControllerCallbackCaptor;
+    protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
+
+    private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.KeyguardViewManagerCallback>
+            mKeyguardViewManagerCallbackArgumentCaptor;
+    protected StatusBarKeyguardViewManager.KeyguardViewManagerCallback mKeyguardViewManagerCallback;
+
+
+    @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).setAlternateBouncer(
+                mAlternateBouncerCaptor.capture());
+        mAlternateBouncer = mAlternateBouncerCaptor.getValue();
+    }
+
+    protected void captureKeyguardStateControllerCallback() {
+        verify(mKeyguardStateController).addCallback(
+                mKeyguardStateControllerCallbackCaptor.capture());
+        mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+    }
+
+    public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
+        return createUdfpsKeyguardViewController(false, false);
+    }
+
+    public void captureKeyGuardViewManagerCallback() {
+        verify(mStatusBarKeyguardViewManager).addCallback(
+                mKeyguardViewManagerCallbackArgumentCaptor.capture());
+        mKeyguardViewManagerCallback = mKeyguardViewManagerCallbackArgumentCaptor.getValue();
+    }
+
+    protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
+            boolean useModernBouncer, boolean useExpandedOverlay) {
+        mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
+        mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay);
+        when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(
+                useModernBouncer ? null : mBouncer);
+        UdfpsKeyguardViewController controller = new UdfpsKeyguardViewController(
+                mView,
+                mStatusBarStateController,
+                mShadeExpansionStateManager,
+                mStatusBarKeyguardViewManager,
+                mKeyguardUpdateMonitor,
+                mDumpManager,
+                mLockscreenShadeTransitionController,
+                mConfigurationController,
+                mSystemClock,
+                mKeyguardStateController,
+                mUnlockedScreenOffAnimationController,
+                mDialogManager,
+                mUdfpsController,
+                mActivityLaunchAnimator,
+                mFeatureFlags,
+                mPrimaryBouncerInteractor);
+        return controller;
+    }
+}
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..babe533 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeast;
@@ -25,126 +26,55 @@
 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 android.view.MotionEvent;
 
 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.PrimaryBouncerExpansionCallback>
+            mBouncerExpansionCallbackCaptor;
+    private KeyguardBouncer.PrimaryBouncerExpansionCallback 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,
+                /* useExpandedOverlay */ false);
     }
 
     @Test
+    public void testShouldPauseAuth_bouncerShowing() {
+        mController.onViewAttached();
+        captureStatusBarStateListeners();
+        sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+        captureBouncerExpansionCallback();
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+
+        assertTrue(mController.shouldPauseAuth());
+    }
+
+
+
+    @Test
     public void testRegistersExpansionChangedListenerOnAttached() {
         mController.onViewAttached();
         captureStatusBarExpansionListeners();
@@ -202,20 +132,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();
@@ -326,13 +242,13 @@
         sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
         assertTrue(mController.shouldPauseAuth());
 
-        mAltAuthInterceptor.showAlternateAuthBouncer(); // force show
+        mAlternateBouncer.showAlternateBouncer(); // force show
         assertFalse(mController.shouldPauseAuth());
-        assertTrue(mAltAuthInterceptor.isShowingAlternateAuthBouncer());
+        assertTrue(mAlternateBouncer.isShowingAlternateBouncer());
 
-        mAltAuthInterceptor.hideAlternateAuthBouncer(); // stop force show
+        mAlternateBouncer.hideAlternateBouncer(); // stop force show
         assertTrue(mController.shouldPauseAuth());
-        assertFalse(mAltAuthInterceptor.isShowingAlternateAuthBouncer());
+        assertFalse(mAlternateBouncer.isShowingAlternateBouncer());
     }
 
     @Test
@@ -345,7 +261,7 @@
         mController.onViewDetached();
 
         // THEN remove alternate auth interceptor
-        verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAltAuthInterceptor);
+        verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAlternateBouncer);
     }
 
     @Test
@@ -355,14 +271,15 @@
         captureAltAuthInterceptor();
 
         // GIVEN udfps bouncer isn't showing
-        mAltAuthInterceptor.hideAlternateAuthBouncer();
+        mAlternateBouncer.hideAlternateBouncer();
 
         // WHEN touch is observed outside the view
         mController.onTouchOutsideView();
 
         // THEN bouncer / alt auth methods are never called
+        verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
-        verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
+        verify(mStatusBarKeyguardViewManager, never()).hideAlternateBouncer(anyBoolean());
     }
 
     @Test
@@ -372,32 +289,33 @@
         captureAltAuthInterceptor();
 
         // GIVEN udfps bouncer is showing
-        mAltAuthInterceptor.showAlternateAuthBouncer();
+        mAlternateBouncer.showAlternateBouncer();
 
         // WHEN touch is observed outside the view 200ms later (just within threshold)
         mSystemClock.advanceTime(200);
         mController.onTouchOutsideView();
 
         // THEN bouncer / alt auth methods are never called because not enough time has passed
+        verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
-        verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
+        verify(mStatusBarKeyguardViewManager, never()).hideAlternateBouncer(anyBoolean());
     }
 
     @Test
-    public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showInputBouncer() {
+    public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showPrimaryBouncer() {
         // GIVEN view is attached
         mController.onViewAttached();
         captureAltAuthInterceptor();
 
         // GIVEN udfps bouncer is showing
-        mAltAuthInterceptor.showAlternateAuthBouncer();
+        mAlternateBouncer.showAlternateBouncer();
 
         // WHEN touch is observed outside the view 205ms later
         mSystemClock.advanceTime(205);
         mController.onTouchOutsideView();
 
         // THEN show the bouncer
-        verify(mStatusBarKeyguardViewManager).showBouncer(eq(true));
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(eq(true));
     }
 
     @Test
@@ -428,7 +346,7 @@
         when(mResourceContext.getString(anyInt())).thenReturn("test string");
 
         // WHEN status bar expansion is 0 but udfps bouncer is requested
-        mAltAuthInterceptor.showAlternateAuthBouncer();
+        mAlternateBouncer.showAlternateBouncer();
 
         // THEN alpha is 255
         verify(mView).setUnpausedAlpha(255);
@@ -459,7 +377,7 @@
         captureKeyguardStateControllerCallback();
         captureAltAuthInterceptor();
         updateStatusBarExpansion(1f, true);
-        mAltAuthInterceptor.showAlternateAuthBouncer();
+        mAlternateBouncer.showAlternateBouncer();
         reset(mView);
 
         // WHEN we're transitioning to the full shade
@@ -503,41 +421,41 @@
         verify(mView, atLeastOnce()).setPauseAuth(false);
     }
 
-    private void sendStatusBarStateChanged(int statusBarState) {
-        mStatusBarStateListener.onStateChanged(statusBarState);
+    private void captureBouncerExpansionCallback() {
+        verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture());
+        mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue();
     }
 
-    private void captureStatusBarStateListeners() {
-        verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
-        mStatusBarStateListener = mStateListenerCaptor.getValue();
+    @Test
+    // TODO(b/259264861): Tracking Bug
+    public void testUdfpsExpandedOverlayOn() {
+        // GIVEN view is attached and useExpandedOverlay is true
+        mController = createUdfpsKeyguardViewController(false, true);
+        mController.onViewAttached();
+        captureKeyGuardViewManagerCallback();
+
+        // WHEN a touch is received
+        mKeyguardViewManagerCallback.onTouch(
+                MotionEvent.obtain(0, 0, 0, 0, 0, 0));
+
+        // THEN udfpsController onTouch is not called
+        assertTrue(mView.mUseExpandedOverlay);
+        verify(mUdfpsController, never()).onTouch(any());
     }
 
-    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();
-    }
+    @Test
+    // TODO(b/259264861): Tracking Bug
+    public void testUdfpsExpandedOverlayOff() {
+        // GIVEN view is attached and useExpandedOverlay is false
+        mController.onViewAttached();
+        captureKeyGuardViewManagerCallback();
 
-    private void updateStatusBarExpansion(float fraction, boolean expanded) {
-        ShadeExpansionChangeEvent event =
-                new ShadeExpansionChangeEvent(
-                        fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
-        for (ShadeExpansionListener listener : mExpansionListeners) {
-            listener.onPanelExpansionChanged(event);
-        }
-    }
+        // WHEN a touch is received
+        mKeyguardViewManagerCallback.onTouch(
+                MotionEvent.obtain(0, 0, 0, 0, 0, 0));
 
-    private void captureAltAuthInterceptor() {
-        verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
-                mAltAuthInterceptorCaptor.capture());
-        mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
-    }
-
-    private void captureKeyguardStateControllerCallback() {
-        verify(mKeyguardStateController).addCallback(
-                mKeyguardStateControllerCallbackCaptor.capture());
-        mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+        // THEN udfpsController onTouch is called
+        assertFalse(mView.mUseExpandedOverlay);
+        verify(mUdfpsController).onTouch(any());
     }
 }
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..517e27a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -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 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.PrimaryBouncerCallbackInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+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? {
+        mPrimaryBouncerInteractor =
+            PrimaryBouncerInteractor(
+                keyguardBouncerRepository,
+                mock(BouncerView::class.java),
+                mock(Handler::class.java),
+                mKeyguardStateController,
+                mock(KeyguardSecurityModel::class.java),
+                mock(PrimaryBouncerCallbackInteractor::class.java),
+                mock(FalsingCollector::class.java),
+                mock(DismissCallbackRegistry::class.java),
+                mock(KeyguardBypassController::class.java),
+                mKeyguardUpdateMonitor
+            )
+        return createUdfpsKeyguardViewController(
+            /* useModernBouncer */ true, /* useExpandedOverlay */
+            false
+        )
+    }
+
+    /** 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.setPrimaryVisible(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/udfps/BoundingBoxOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
new file mode 100644
index 0000000..4f89b69
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
@@ -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 com.android.systemui.biometrics.udfps
+
+import android.graphics.Rect
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@SmallTest
+@RunWith(Parameterized::class)
+class BoundingBoxOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() {
+    val underTest = BoundingBoxOverlapDetector()
+
+    @Test
+    fun isGoodOverlap() {
+        val touchData = TOUCH_DATA.copy(x = testCase.x.toFloat(), y = testCase.y.toFloat())
+        val actual = underTest.isGoodOverlap(touchData, SENSOR)
+
+        assertThat(actual).isEqualTo(testCase.expected)
+    }
+
+    data class TestCase(val x: Int, val y: Int, val expected: Boolean)
+
+    companion object {
+        @Parameters(name = "{0}")
+        @JvmStatic
+        fun data(): List<TestCase> =
+            listOf(
+                    genPositiveTestCases(
+                        validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()),
+                        validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY())
+                    ),
+                    genNegativeTestCases(
+                        invalidXs = listOf(SENSOR.left - 1, SENSOR.right + 1),
+                        invalidYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1),
+                        validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()),
+                        validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY())
+                    )
+                )
+                .flatten()
+    }
+}
+
+/* Placeholder touch parameters. */
+private const val POINTER_ID = 42
+private const val NATIVE_MINOR = 2.71828f
+private const val NATIVE_MAJOR = 3.14f
+private const val ORIENTATION = 1.23f
+private const val TIME = 12345699L
+private const val GESTURE_START = 12345600L
+
+/* Template [NormalizedTouchData]. */
+private val TOUCH_DATA =
+    NormalizedTouchData(
+        POINTER_ID,
+        x = 0f,
+        y = 0f,
+        NATIVE_MINOR,
+        NATIVE_MAJOR,
+        ORIENTATION,
+        TIME,
+        GESTURE_START
+    )
+
+private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 500 /* bottom */)
+
+private fun genTestCases(
+    xs: List<Int>,
+    ys: List<Int>,
+    expected: Boolean
+): List<BoundingBoxOverlapDetectorTest.TestCase> {
+    return xs.flatMap { x ->
+        ys.map { y -> BoundingBoxOverlapDetectorTest.TestCase(x, y, expected) }
+    }
+}
+
+private fun genPositiveTestCases(
+    validXs: List<Int>,
+    validYs: List<Int>,
+) = genTestCases(validXs, validYs, expected = true)
+
+private fun genNegativeTestCases(
+    invalidXs: List<Int>,
+    invalidYs: List<Int>,
+    validXs: List<Int>,
+    validYs: List<Int>,
+): List<BoundingBoxOverlapDetectorTest.TestCase> {
+    return genTestCases(invalidXs, validYs, expected = false) +
+        genTestCases(validXs, invalidYs, expected = false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
new file mode 100644
index 0000000..834d0a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
@@ -0,0 +1,90 @@
+package com.android.systemui.biometrics.udfps
+
+import android.graphics.Rect
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@SmallTest
+@RunWith(Parameterized::class)
+class NormalizedTouchDataTest(val testCase: TestCase) : SysuiTestCase() {
+
+    @Test
+    fun isWithinSensor() {
+        val touchData = TOUCH_DATA.copy(x = testCase.x.toFloat(), y = testCase.y.toFloat())
+        val actual = touchData.isWithinSensor(SENSOR)
+
+        assertThat(actual).isEqualTo(testCase.expected)
+    }
+
+    data class TestCase(val x: Int, val y: Int, val expected: Boolean)
+
+    companion object {
+        @Parameters(name = "{0}")
+        @JvmStatic
+        fun data(): List<TestCase> =
+            listOf(
+                    genPositiveTestCases(
+                        validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()),
+                        validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY())
+                    ),
+                    genNegativeTestCases(
+                        invalidXs = listOf(SENSOR.left - 1, SENSOR.right + 1),
+                        invalidYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1),
+                        validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()),
+                        validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY())
+                    )
+                )
+                .flatten()
+    }
+}
+
+/* Placeholder touch parameters. */
+private const val POINTER_ID = 42
+private const val NATIVE_MINOR = 2.71828f
+private const val NATIVE_MAJOR = 3.14f
+private const val ORIENTATION = 1.23f
+private const val TIME = 12345699L
+private const val GESTURE_START = 12345600L
+
+/* Template [NormalizedTouchData]. */
+private val TOUCH_DATA =
+    NormalizedTouchData(
+        POINTER_ID,
+        x = 0f,
+        y = 0f,
+        NATIVE_MINOR,
+        NATIVE_MAJOR,
+        ORIENTATION,
+        TIME,
+        GESTURE_START
+    )
+
+private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 500 /* bottom */)
+
+private fun genTestCases(
+    xs: List<Int>,
+    ys: List<Int>,
+    expected: Boolean
+): List<NormalizedTouchDataTest.TestCase> {
+    return xs.flatMap { x -> ys.map { y -> NormalizedTouchDataTest.TestCase(x, y, expected) } }
+}
+
+private fun genPositiveTestCases(
+    validXs: List<Int>,
+    validYs: List<Int>,
+) = genTestCases(validXs, validYs, expected = true)
+
+private fun genNegativeTestCases(
+    invalidXs: List<Int>,
+    invalidYs: List<Int>,
+    validXs: List<Int>,
+    validYs: List<Int>,
+): List<NormalizedTouchDataTest.TestCase> {
+    return genTestCases(invalidXs, validYs, expected = false) +
+        genTestCases(validXs, invalidYs, expected = false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
new file mode 100644
index 0000000..95c53b4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
@@ -0,0 +1,506 @@
+/*
+ * 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.udfps
+
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.MotionEvent.INVALID_POINTER_ID
+import android.view.MotionEvent.PointerProperties
+import android.view.Surface
+import android.view.Surface.Rotation
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.UdfpsOverlayParams
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@SmallTest
+@RunWith(Parameterized::class)
+class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() {
+    private val overlapDetector = FakeOverlapDetector()
+    private val underTest = SinglePointerTouchProcessor(overlapDetector)
+
+    @Test
+    fun processTouch() {
+        overlapDetector.shouldReturn = testCase.isGoodOverlap
+
+        val actual =
+            underTest.processTouch(
+                testCase.event,
+                testCase.previousPointerOnSensorId,
+                testCase.overlayParams,
+            )
+
+        assertThat(actual).isInstanceOf(testCase.expected.javaClass)
+        if (actual is TouchProcessorResult.ProcessedTouch) {
+            assertThat(actual).isEqualTo(testCase.expected)
+        }
+    }
+
+    data class TestCase(
+        val event: MotionEvent,
+        val isGoodOverlap: Boolean,
+        val previousPointerOnSensorId: Int,
+        val overlayParams: UdfpsOverlayParams,
+        val expected: TouchProcessorResult,
+    ) {
+        override fun toString(): String {
+            val expectedOutput =
+                if (expected is TouchProcessorResult.ProcessedTouch) {
+                    expected.event.toString() +
+                        ", (x: ${expected.touchData.x}, y: ${expected.touchData.y})" +
+                        ", pointerOnSensorId: ${expected.pointerOnSensorId}" +
+                        ", ..."
+                } else {
+                    TouchProcessorResult.Failure().toString()
+                }
+            return "{" +
+                MotionEvent.actionToString(event.action) +
+                ", (x: ${event.x}, y: ${event.y})" +
+                ", scale: ${overlayParams.scaleFactor}" +
+                ", rotation: " +
+                Surface.rotationToString(overlayParams.rotation) +
+                ", previousPointerOnSensorId: $previousPointerOnSensorId" +
+                ", ...} expected: {$expectedOutput}"
+        }
+    }
+
+    companion object {
+        @Parameters(name = "{0}")
+        @JvmStatic
+        fun data(): List<TestCase> =
+            listOf(
+                    // MotionEvent.ACTION_DOWN
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_DOWN,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        isGoodOverlap = true,
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_DOWN,
+                        previousPointerOnSensorId = POINTER_ID,
+                        isGoodOverlap = true,
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_DOWN,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        isGoodOverlap = false,
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_DOWN,
+                        previousPointerOnSensorId = POINTER_ID,
+                        isGoodOverlap = false,
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    // MotionEvent.ACTION_MOVE
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_MOVE,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        isGoodOverlap = true,
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_MOVE,
+                        previousPointerOnSensorId = POINTER_ID,
+                        isGoodOverlap = true,
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_MOVE,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        isGoodOverlap = false,
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_MOVE,
+                        previousPointerOnSensorId = POINTER_ID,
+                        isGoodOverlap = false,
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    // MotionEvent.ACTION_UP
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_UP,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        isGoodOverlap = true,
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_UP,
+                        previousPointerOnSensorId = POINTER_ID,
+                        isGoodOverlap = true,
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_UP,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        isGoodOverlap = false,
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_UP,
+                        previousPointerOnSensorId = POINTER_ID,
+                        isGoodOverlap = false,
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    // MotionEvent.ACTION_CANCEL
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_CANCEL,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        isGoodOverlap = true,
+                        expectedInteractionEvent = InteractionEvent.CANCEL,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_CANCEL,
+                        previousPointerOnSensorId = POINTER_ID,
+                        isGoodOverlap = true,
+                        expectedInteractionEvent = InteractionEvent.CANCEL,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_CANCEL,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        isGoodOverlap = false,
+                        expectedInteractionEvent = InteractionEvent.CANCEL,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_CANCEL,
+                        previousPointerOnSensorId = POINTER_ID,
+                        isGoodOverlap = false,
+                        expectedInteractionEvent = InteractionEvent.CANCEL,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                )
+                .flatten() +
+                listOf(
+                        // Unsupported MotionEvent actions.
+                        genTestCasesForUnsupportedAction(MotionEvent.ACTION_POINTER_DOWN),
+                        genTestCasesForUnsupportedAction(MotionEvent.ACTION_POINTER_UP),
+                        genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_ENTER),
+                        genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_MOVE),
+                        genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_EXIT),
+                    )
+                    .flatten()
+    }
+}
+
+/* Display dimensions in native resolution and natural orientation. */
+private const val ROTATION_0_NATIVE_DISPLAY_WIDTH = 400
+private const val ROTATION_0_NATIVE_DISPLAY_HEIGHT = 600
+
+/*
+ * ROTATION_0 map:
+ * _ _ _ _
+ * _ _ O _
+ * _ _ _ _
+ * _ S _ _
+ * _ S _ _
+ * _ _ _ _
+ *
+ * (_) empty space
+ * (S) sensor
+ * (O) touch outside of the sensor
+ */
+private val ROTATION_0_NATIVE_SENSOR_BOUNDS =
+    Rect(
+        100, /* left */
+        300, /* top */
+        200, /* right */
+        500, /* bottom */
+    )
+private val ROTATION_0_INPUTS =
+    OrientationBasedInputs(
+        rotation = Surface.ROTATION_0,
+        nativeXWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterX(),
+        nativeYWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterY(),
+        nativeXOutsideSensor = 250f,
+        nativeYOutsideSensor = 150f,
+    )
+
+/*
+ * ROTATION_90 map:
+ * _ _ _ _ _ _
+ * _ O _ _ _ _
+ * _ _ _ S S _
+ * _ _ _ _ _ _
+ *
+ * (_) empty space
+ * (S) sensor
+ * (O) touch outside of the sensor
+ */
+private val ROTATION_90_NATIVE_SENSOR_BOUNDS =
+    Rect(
+        300, /* left */
+        200, /* top */
+        500, /* right */
+        300, /* bottom */
+    )
+private val ROTATION_90_INPUTS =
+    OrientationBasedInputs(
+        rotation = Surface.ROTATION_90,
+        nativeXWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterX(),
+        nativeYWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterY(),
+        nativeXOutsideSensor = 150f,
+        nativeYOutsideSensor = 150f,
+    )
+
+/* ROTATION_180 is not supported. It's treated the same as ROTATION_0. */
+private val ROTATION_180_INPUTS =
+    ROTATION_0_INPUTS.copy(
+        rotation = Surface.ROTATION_180,
+    )
+
+/*
+ * ROTATION_270 map:
+ * _ _ _ _ _ _
+ * _ S S _ _ _
+ * _ _ _ _ O _
+ * _ _ _ _ _ _
+ *
+ * (_) empty space
+ * (S) sensor
+ * (O) touch outside of the sensor
+ */
+private val ROTATION_270_NATIVE_SENSOR_BOUNDS =
+    Rect(
+        100, /* left */
+        100, /* top */
+        300, /* right */
+        200, /* bottom */
+    )
+private val ROTATION_270_INPUTS =
+    OrientationBasedInputs(
+        rotation = Surface.ROTATION_270,
+        nativeXWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterX(),
+        nativeYWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterY(),
+        nativeXOutsideSensor = 450f,
+        nativeYOutsideSensor = 250f,
+    )
+
+/* Placeholder touch parameters. */
+private const val POINTER_ID = 42
+private const val NATIVE_MINOR = 2.71828f
+private const val NATIVE_MAJOR = 3.14f
+private const val ORIENTATION = 1.23f
+private const val TIME = 12345699L
+private const val GESTURE_START = 12345600L
+
+/* Template [MotionEvent]. */
+private val MOTION_EVENT =
+    obtainMotionEvent(
+        action = 0,
+        pointerId = POINTER_ID,
+        x = 0f,
+        y = 0f,
+        minor = 0f,
+        major = 0f,
+        orientation = ORIENTATION,
+        time = TIME,
+        gestureStart = GESTURE_START,
+    )
+
+/* Template [NormalizedTouchData]. */
+private val NORMALIZED_TOUCH_DATA =
+    NormalizedTouchData(
+        POINTER_ID,
+        x = 0f,
+        y = 0f,
+        NATIVE_MINOR,
+        NATIVE_MAJOR,
+        ORIENTATION,
+        TIME,
+        GESTURE_START
+    )
+
+/*
+ * Contains test inputs that are tied to a particular device orientation.
+ *
+ * "native" means in native resolution (not scaled).
+ */
+private data class OrientationBasedInputs(
+    @Rotation val rotation: Int,
+    val nativeXWithinSensor: Float,
+    val nativeYWithinSensor: Float,
+    val nativeXOutsideSensor: Float,
+    val nativeYOutsideSensor: Float,
+) {
+
+    fun toOverlayParams(scaleFactor: Float): UdfpsOverlayParams =
+        UdfpsOverlayParams(
+            sensorBounds = ROTATION_0_NATIVE_SENSOR_BOUNDS.scaled(scaleFactor),
+            overlayBounds = ROTATION_0_NATIVE_SENSOR_BOUNDS.scaled(scaleFactor),
+            naturalDisplayHeight = (ROTATION_0_NATIVE_DISPLAY_HEIGHT * scaleFactor).toInt(),
+            naturalDisplayWidth = (ROTATION_0_NATIVE_DISPLAY_WIDTH * scaleFactor).toInt(),
+            scaleFactor = scaleFactor,
+            rotation = rotation
+        )
+
+    fun getNativeX(isWithinSensor: Boolean): Float {
+        return if (isWithinSensor) nativeXWithinSensor else nativeXOutsideSensor
+    }
+
+    fun getNativeY(isWithinSensor: Boolean): Float {
+        return if (isWithinSensor) nativeYWithinSensor else nativeYOutsideSensor
+    }
+}
+
+private fun genPositiveTestCases(
+    motionEventAction: Int,
+    previousPointerOnSensorId: Int,
+    isGoodOverlap: Boolean,
+    expectedInteractionEvent: InteractionEvent,
+    expectedPointerOnSensorId: Int
+): List<SinglePointerTouchProcessorTest.TestCase> {
+    val scaleFactors = listOf(0.75f, 1f, 1.5f)
+    val orientations =
+        listOf(
+            ROTATION_0_INPUTS,
+            ROTATION_90_INPUTS,
+            ROTATION_180_INPUTS,
+            ROTATION_270_INPUTS,
+        )
+    return scaleFactors.flatMap { scaleFactor ->
+        orientations.map { orientation ->
+            val overlayParams = orientation.toOverlayParams(scaleFactor)
+            val nativeX = orientation.getNativeX(isGoodOverlap)
+            val nativeY = orientation.getNativeY(isGoodOverlap)
+            val event =
+                MOTION_EVENT.copy(
+                    action = motionEventAction,
+                    x = nativeX * scaleFactor,
+                    y = nativeY * scaleFactor,
+                    minor = NATIVE_MINOR * scaleFactor,
+                    major = NATIVE_MAJOR * scaleFactor,
+                )
+            val expectedTouchData =
+                NORMALIZED_TOUCH_DATA.copy(
+                    x = ROTATION_0_INPUTS.getNativeX(isGoodOverlap),
+                    y = ROTATION_0_INPUTS.getNativeY(isGoodOverlap),
+                )
+            val expected =
+                TouchProcessorResult.ProcessedTouch(
+                    event = expectedInteractionEvent,
+                    pointerOnSensorId = expectedPointerOnSensorId,
+                    touchData = expectedTouchData,
+                )
+            SinglePointerTouchProcessorTest.TestCase(
+                event = event,
+                isGoodOverlap = isGoodOverlap,
+                previousPointerOnSensorId = previousPointerOnSensorId,
+                overlayParams = overlayParams,
+                expected = expected,
+            )
+        }
+    }
+}
+
+private fun genTestCasesForUnsupportedAction(
+    motionEventAction: Int
+): List<SinglePointerTouchProcessorTest.TestCase> {
+    val isGoodOverlap = true
+    val previousPointerOnSensorIds = listOf(INVALID_POINTER_ID, POINTER_ID)
+    return previousPointerOnSensorIds.map { previousPointerOnSensorId ->
+        val overlayParams = ROTATION_0_INPUTS.toOverlayParams(scaleFactor = 1f)
+        val nativeX = ROTATION_0_INPUTS.getNativeX(isGoodOverlap)
+        val nativeY = ROTATION_0_INPUTS.getNativeY(isGoodOverlap)
+        val event =
+            MOTION_EVENT.copy(
+                action = motionEventAction,
+                x = nativeX,
+                y = nativeY,
+                minor = NATIVE_MINOR,
+                major = NATIVE_MAJOR,
+            )
+        SinglePointerTouchProcessorTest.TestCase(
+            event = event,
+            isGoodOverlap = isGoodOverlap,
+            previousPointerOnSensorId = previousPointerOnSensorId,
+            overlayParams = overlayParams,
+            expected = TouchProcessorResult.Failure(),
+        )
+    }
+}
+
+private fun obtainMotionEvent(
+    action: Int,
+    pointerId: Int,
+    x: Float,
+    y: Float,
+    minor: Float,
+    major: Float,
+    orientation: Float,
+    time: Long,
+    gestureStart: Long,
+): MotionEvent {
+    val pp = PointerProperties()
+    pp.id = pointerId
+    val pc = MotionEvent.PointerCoords()
+    pc.x = x
+    pc.y = y
+    pc.touchMinor = minor
+    pc.touchMajor = major
+    pc.orientation = orientation
+    return MotionEvent.obtain(
+        gestureStart /* downTime */,
+        time /* eventTime */,
+        action /* action */,
+        1 /* pointerCount */,
+        arrayOf(pp) /* pointerProperties */,
+        arrayOf(pc) /* pointerCoords */,
+        0 /* metaState */,
+        0 /* buttonState */,
+        1f /* xPrecision */,
+        1f /* yPrecision */,
+        0 /* deviceId */,
+        0 /* edgeFlags */,
+        0 /* source */,
+        0 /* flags */
+    )
+}
+
+private fun MotionEvent.copy(
+    action: Int = this.action,
+    pointerId: Int = this.getPointerId(0),
+    x: Float = this.rawX,
+    y: Float = this.rawY,
+    minor: Float = this.touchMinor,
+    major: Float = this.touchMajor,
+    orientation: Float = this.orientation,
+    time: Long = this.eventTime,
+    gestureStart: Long = this.downTime,
+) = obtainMotionEvent(action, pointerId, x, y, minor, major, orientation, time, gestureStart)
+
+private fun Rect.scaled(scaleFactor: Float) = Rect(this).apply { scale(scaleFactor) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index 2af0557..d159714 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleView
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index 6bc7308..0fadc13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -39,6 +39,8 @@
 import com.android.internal.logging.testing.FakeMetricsLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingDataProvider.GestureFinalizedListener;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -66,6 +68,8 @@
     @Mock
     private SingleTapClassifier mSingleTapClassfier;
     @Mock
+    private LongTapClassifier mLongTapClassifier;
+    @Mock
     private DoubleTapClassifier mDoubleTapClassifier;
     @Mock
     private FalsingClassifier mClassifierA;
@@ -80,6 +84,7 @@
     private AccessibilityManager mAccessibilityManager;
 
     private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+    private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
 
     private final FalsingClassifier.Result mFalsedResult =
             FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), "");
@@ -94,6 +99,7 @@
         when(mClassifierB.classifyGesture(anyInt(), anyDouble(), anyDouble()))
                 .thenReturn(mPassedResult);
         when(mSingleTapClassfier.isTap(any(List.class), anyDouble())).thenReturn(mPassedResult);
+        when(mLongTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
         when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
                 .thenReturn(mPassedResult);
         mClassifiers.add(mClassifierA);
@@ -101,9 +107,9 @@
         when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
-                mMetricsLogger, mClassifiers, mSingleTapClassfier, mDoubleTapClassifier,
-                mHistoryTracker, mKeyguardStateController, mAccessibilityManager,
-                false);
+                mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier,
+                mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
+                mAccessibilityManager, false, mFakeFeatureFlags);
 
 
         ArgumentCaptor<GestureFinalizedListener> gestureCompleteListenerCaptor =
@@ -113,6 +119,8 @@
                 gestureCompleteListenerCaptor.capture());
 
         mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
+        mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
+        mFakeFeatureFlags.set(Flags.MEDIA_FALSING_PENALTY, true);
     }
 
     @Test
@@ -212,7 +220,7 @@
     }
 
     @Test
-    public void testIsFalseTap_EmptyRecentEvents() {
+    public void testIsFalseSingleTap_EmptyRecentEvents() {
         // Ensure we look at prior events if recent events has already been emptied.
         when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(new ArrayList<>());
         when(mFalsingDataProvider.getPriorMotionEvents()).thenReturn(mMotionEventList);
@@ -223,7 +231,7 @@
 
 
     @Test
-    public void testIsFalseTap_RobustCheck_NoFaceAuth() {
+    public void testIsFalseSingleTap_RobustCheck_NoFaceAuth() {
         when(mSingleTapClassfier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
         when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
                 .thenReturn(mFalsedResult);
@@ -233,13 +241,50 @@
     }
 
     @Test
-    public void testIsFalseTap_RobustCheck_FaceAuth() {
+    public void testIsFalseSingleTap_RobustCheck_FaceAuth() {
         when(mSingleTapClassfier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
         when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(true);
         assertThat(mBrightLineFalsingManager.isFalseTap(NO_PENALTY)).isFalse();
     }
 
     @Test
+    public void testIsFalseLongTap_EmptyRecentEvents() {
+        // Ensure we look at prior events if recent events has already been emptied.
+        when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(new ArrayList<>());
+        when(mFalsingDataProvider.getPriorMotionEvents()).thenReturn(mMotionEventList);
+
+        mBrightLineFalsingManager.isFalseLongTap(0);
+        verify(mLongTapClassifier).isTap(mMotionEventList, 0);
+    }
+
+    @Test
+    public void testIsFalseLongTap_FalseLongTap_NotFlagged() {
+        mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, false);
+        when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult);
+        assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse();
+    }
+
+    @Test
+    public void testIsFalseLongTap_FalseLongTap() {
+        when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult);
+        assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isTrue();
+    }
+
+    @Test
+    public void testIsFalseLongTap_RobustCheck_NoFaceAuth() {
+        when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
+        when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(false);
+        assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse();
+    }
+
+    @Test
+    public void testIsFalseLongTap_RobustCheck_FaceAuth() {
+        when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
+        when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(true);
+        assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse();
+    }
+
+    @Test
     public void testIsFalseDoubleTap() {
         when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
                 .thenReturn(mPassedResult);
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..4281ee0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -32,6 +32,8 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.testing.FakeMetricsLogger;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -57,6 +59,8 @@
     @Mock
     private SingleTapClassifier mSingleTapClassifier;
     @Mock
+    private LongTapClassifier mLongTapClassifier;
+    @Mock
     private DoubleTapClassifier mDoubleTapClassifier;
     @Mock
     private FalsingClassifier mClassifierA;
@@ -71,6 +75,7 @@
     private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
     private final FalsingClassifier.Result mFalsedResult =
             FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), "");
+    private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
 
     @Before
     public void setup() {
@@ -78,15 +83,17 @@
         when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
                 .thenReturn(mFalsedResult);
         when(mSingleTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
+        when(mLongTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
         when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
                 .thenReturn(mFalsedResult);
         mClassifiers.add(mClassifierA);
         when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
-                mMetricsLogger, mClassifiers, mSingleTapClassifier, mDoubleTapClassifier,
-                mHistoryTracker, mKeyguardStateController, mAccessibilityManager,
-                false);
+                mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier,
+                mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
+                mAccessibilityManager, false, mFakeFeatureFlags);
+        mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
     }
 
     @Test
@@ -96,7 +103,6 @@
         assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse();
     }
 
-
     @Test
     public void testA11yDisablesTap() {
         assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
@@ -106,6 +112,13 @@
 
 
     @Test
+    public void testA11yDisablesLongTap() {
+        assertThat(mBrightLineFalsingManager.isFalseLongTap(1)).isTrue();
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
+        assertThat(mBrightLineFalsingManager.isFalseLongTap(1)).isFalse();
+    }
+
+    @Test
     public void testA11yDisablesDoubleTap() {
         assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isTrue();
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
@@ -159,4 +172,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..d6e621f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -19,10 +19,13 @@
 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;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.animation.Animator;
@@ -37,6 +40,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 +66,10 @@
     @Mock
     private TimeoutHandler mTimeoutHandler;
     @Mock
+    private ClipboardOverlayUtils mClipboardUtils;
+    @Mock
     private UiEventLogger mUiEventLogger;
+    private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
     @Mock
     private Animator mAnimator;
@@ -73,7 +80,6 @@
     private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor;
     private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks;
 
-
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -84,6 +90,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 +99,8 @@
                 getFakeBroadcastDispatcher(),
                 mBroadcastSender,
                 mTimeoutHandler,
+                mFeatureFlags,
+                mClipboardUtils,
                 mUiEventLogger);
         verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
         mCallbacks = mOverlayCallbacksCaptor.getValue();
@@ -162,7 +172,7 @@
 
         mCallbacks.onShareButtonTapped();
 
-        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED);
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "");
         verify(mClipboardOverlayView, times(1)).getExitAnimation();
     }
 
@@ -172,7 +182,7 @@
 
         mCallbacks.onDismissButtonTapped();
 
-        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "");
         verify(mClipboardOverlayView, times(1)).getExitAnimation();
     }
 
@@ -183,7 +193,48 @@
         mCallbacks.onSwipeDismissInitiated(mAnimator);
         mCallbacks.onDismissButtonTapped();
 
-        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED, 0, null);
         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();
+    }
+
+    @Test
+    public void test_logsUseLastClipSource() {
+        mOverlayController.setClipData(mSampleClipData, "first.package");
+        mCallbacks.onDismissButtonTapped();
+        mOverlayController.setClipData(mSampleClipData, "second.package");
+        mCallbacks.onDismissButtonTapped();
+
+        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "first.package");
+        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package");
+        verifyNoMoreInteractions(mUiEventLogger);
+    }
 }
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/ControlsSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt
new file mode 100644
index 0000000..4b88b44
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt
@@ -0,0 +1,187 @@
+/*
+ * 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
+
+import android.content.pm.UserInfo
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class ControlsSettingsRepositoryImplTest : SysuiTestCase() {
+
+    companion object {
+        private const val LOCKSCREEN_SHOW = Settings.Secure.LOCKSCREEN_SHOW_CONTROLS
+        private const val LOCKSCREEN_ACTION = Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS
+
+        private fun createUser(id: Int): UserInfo {
+            return UserInfo(id, "user_$id", 0)
+        }
+
+        private val ALL_USERS = (0..1).map { it to createUser(it) }.toMap()
+    }
+
+    private lateinit var underTest: ControlsSettingsRepository
+
+    private lateinit var testScope: TestScope
+    private lateinit var secureSettings: FakeSettings
+    private lateinit var userRepository: FakeUserRepository
+
+    @Before
+    fun setUp() {
+        secureSettings = FakeSettings()
+        userRepository = FakeUserRepository()
+        userRepository.setUserInfos(ALL_USERS.values.toList())
+
+        val coroutineDispatcher = UnconfinedTestDispatcher()
+        testScope = TestScope(coroutineDispatcher)
+
+        underTest =
+            ControlsSettingsRepositoryImpl(
+                scope = testScope.backgroundScope,
+                backgroundDispatcher = coroutineDispatcher,
+                userRepository = userRepository,
+                secureSettings = secureSettings,
+            )
+    }
+
+    @Test
+    fun showInLockScreen() =
+        testScope.runTest {
+            setUser(0)
+            val values = mutableListOf<Boolean>()
+            val job =
+                launch(UnconfinedTestDispatcher()) {
+                    underTest.canShowControlsInLockscreen.toList(values)
+                }
+            assertThat(values.last()).isFalse()
+
+            secureSettings.putBool(LOCKSCREEN_SHOW, true)
+            assertThat(values.last()).isTrue()
+
+            secureSettings.putBool(LOCKSCREEN_SHOW, false)
+            assertThat(values.last()).isFalse()
+
+            secureSettings.putBoolForUser(LOCKSCREEN_SHOW, true, 1)
+            assertThat(values.last()).isFalse()
+
+            setUser(1)
+            assertThat(values.last()).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun showInLockScreen_changesInOtherUsersAreNotQueued() =
+        testScope.runTest {
+            setUser(0)
+
+            val values = mutableListOf<Boolean>()
+            val job =
+                launch(UnconfinedTestDispatcher()) {
+                    underTest.canShowControlsInLockscreen.toList(values)
+                }
+
+            secureSettings.putBoolForUser(LOCKSCREEN_SHOW, true, 1)
+            secureSettings.putBoolForUser(LOCKSCREEN_SHOW, false, 1)
+
+            setUser(1)
+            assertThat(values.last()).isFalse()
+            assertThat(values).containsNoneIn(listOf(true))
+
+            job.cancel()
+        }
+
+    @Test
+    fun actionInLockScreen() =
+        testScope.runTest {
+            setUser(0)
+            val values = mutableListOf<Boolean>()
+            val job =
+                launch(UnconfinedTestDispatcher()) {
+                    underTest.allowActionOnTrivialControlsInLockscreen.toList(values)
+                }
+            assertThat(values.last()).isFalse()
+
+            secureSettings.putBool(LOCKSCREEN_ACTION, true)
+            assertThat(values.last()).isTrue()
+
+            secureSettings.putBool(LOCKSCREEN_ACTION, false)
+            assertThat(values.last()).isFalse()
+
+            secureSettings.putBoolForUser(LOCKSCREEN_ACTION, true, 1)
+            assertThat(values.last()).isFalse()
+
+            setUser(1)
+            assertThat(values.last()).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun actionInLockScreen_changesInOtherUsersAreNotQueued() =
+        testScope.runTest {
+            setUser(0)
+
+            val values = mutableListOf<Boolean>()
+            val job =
+                launch(UnconfinedTestDispatcher()) {
+                    underTest.allowActionOnTrivialControlsInLockscreen.toList(values)
+                }
+
+            secureSettings.putBoolForUser(LOCKSCREEN_ACTION, true, 1)
+            secureSettings.putBoolForUser(LOCKSCREEN_ACTION, false, 1)
+
+            setUser(1)
+            assertThat(values.last()).isFalse()
+            assertThat(values).containsNoneIn(listOf(true))
+
+            job.cancel()
+        }
+
+    @Test
+    fun valueIsUpdatedWhenNotSubscribed() =
+        testScope.runTest {
+            setUser(0)
+            assertThat(underTest.canShowControlsInLockscreen.value).isFalse()
+
+            secureSettings.putBool(LOCKSCREEN_SHOW, true)
+
+            assertThat(underTest.canShowControlsInLockscreen.value).isTrue()
+        }
+
+    private suspend fun setUser(id: Int) {
+        secureSettings.userId = id
+        userRepository.setSelectedUserInfo(ALL_USERS[id]!!)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt
new file mode 100644
index 0000000..8a1bed2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.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.controls
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeControlsSettingsRepository : ControlsSettingsRepository {
+    private val _canShowControlsInLockscreen = MutableStateFlow(false)
+    override val canShowControlsInLockscreen = _canShowControlsInLockscreen.asStateFlow()
+    private val _allowActionOnTrivialControlsInLockscreen = MutableStateFlow(false)
+    override val allowActionOnTrivialControlsInLockscreen =
+        _allowActionOnTrivialControlsInLockscreen.asStateFlow()
+
+    fun setCanShowControlsInLockscreen(value: Boolean) {
+        _canShowControlsInLockscreen.value = value
+    }
+
+    fun setAllowActionOnTrivialControlsInLockscreen(value: Boolean) {
+        _allowActionOnTrivialControlsInLockscreen.value = value
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 4ed5649c..1d00d6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -18,30 +18,24 @@
 
 import android.content.Context
 import android.content.SharedPreferences
-import android.database.ContentObserver
-import android.net.Uri
-import android.os.Handler
-import android.os.UserHandle
-import android.provider.Settings
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.FakeControlsSettingsRepository
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.settings.SecureSettings
 import com.android.wm.shell.TaskViewFactory
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Answers
-import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.`when`
@@ -79,8 +73,6 @@
     @Mock
     private lateinit var secureSettings: SecureSettings
     @Mock
-    private lateinit var mainHandler: Handler
-    @Mock
     private lateinit var userContextProvider: UserContextProvider
 
     companion object {
@@ -91,17 +83,15 @@
 
     private lateinit var coordinator: ControlActionCoordinatorImpl
     private lateinit var action: ControlActionCoordinatorImpl.Action
+    private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        `when`(secureSettings.getUriFor(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS))
-                .thenReturn(Settings.Secure
-                        .getUriFor(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS))
-        `when`(secureSettings.getIntForUser(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
-                0, UserHandle.USER_CURRENT))
-                .thenReturn(1)
+        controlsSettingsRepository = FakeControlsSettingsRepository()
+        controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+        controlsSettingsRepository.setCanShowControlsInLockscreen(true)
 
         coordinator = spy(ControlActionCoordinatorImpl(
                 mContext,
@@ -115,7 +105,7 @@
                 vibratorHelper,
                 secureSettings,
                 userContextProvider,
-                mainHandler
+                controlsSettingsRepository
         ))
 
         val userContext = mock(Context::class.java)
@@ -128,9 +118,6 @@
         `when`(pref.getInt(DeviceControlsControllerImpl.PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
                 .thenReturn(2)
 
-        verify(secureSettings).registerContentObserverForUser(any(Uri::class.java),
-                anyBoolean(), any(ContentObserver::class.java), anyInt())
-
         `when`(cvh.cws.ci.controlId).thenReturn(ID)
         `when`(cvh.cws.control?.isAuthRequired()).thenReturn(true)
         action = spy(coordinator.Action(ID, {}, false, true))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index c31fd82..1b34706 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.controls.controller
 
 import android.app.PendingIntent
-import android.content.BroadcastReceiver
 import android.content.ComponentName
 import android.content.Context
 import android.content.ContextWrapper
@@ -31,7 +30,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.backup.BackupHelper
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.ControlStatus
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.management.ControlsListingController
@@ -85,10 +83,8 @@
     @Mock
     private lateinit var auxiliaryPersistenceWrapper: AuxiliaryPersistenceWrapper
     @Mock
-    private lateinit var broadcastDispatcher: BroadcastDispatcher
-    @Mock
     private lateinit var listingController: ControlsListingController
-    @Mock(stubOnly = true)
+    @Mock
     private lateinit var userTracker: UserTracker
     @Mock
     private lateinit var userFileManager: UserFileManager
@@ -104,7 +100,7 @@
             ArgumentCaptor<ControlsBindingController.LoadCallback>
 
     @Captor
-    private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
+    private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
     @Captor
     private lateinit var listingCallbackCaptor:
             ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@@ -170,16 +166,15 @@
                 uiController,
                 bindingController,
                 listingController,
-                broadcastDispatcher,
                 userFileManager,
+                userTracker,
                 Optional.of(persistenceWrapper),
-                mock(DumpManager::class.java),
-                userTracker
+                mock(DumpManager::class.java)
         )
         controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper
 
-        verify(broadcastDispatcher).registerReceiver(
-            capture(broadcastReceiverCaptor), any(), any(), eq(UserHandle.ALL), anyInt(), any()
+        verify(userTracker).addCallback(
+            capture(userTrackerCallbackCaptor), any()
         )
 
         verify(listingController).addCallback(capture(listingCallbackCaptor))
@@ -227,11 +222,10 @@
                 uiController,
                 bindingController,
                 listingController,
-                broadcastDispatcher,
                 userFileManager,
+                userTracker,
                 Optional.of(persistenceWrapper),
-                mock(DumpManager::class.java),
-                userTracker
+                mock(DumpManager::class.java)
         )
         assertEquals(listOf(TEST_STRUCTURE_INFO), controller_other.getFavorites())
     }
@@ -518,14 +512,8 @@
         delayableExecutor.runAllReady()
 
         reset(persistenceWrapper)
-        val intent = Intent(Intent.ACTION_USER_SWITCHED).apply {
-            putExtra(Intent.EXTRA_USER_HANDLE, otherUser)
-        }
-        val pendingResult = mock(BroadcastReceiver.PendingResult::class.java)
-        `when`(pendingResult.sendingUserId).thenReturn(otherUser)
-        broadcastReceiverCaptor.value.pendingResult = pendingResult
 
-        broadcastReceiverCaptor.value.onReceive(mContext, intent)
+        userTrackerCallbackCaptor.value.onUserChanged(otherUser, mContext)
 
         verify(persistenceWrapper).changeFileAndBackupManager(any(), any())
         verify(persistenceWrapper).readFavorites()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
index 77f451f..48fc46b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
@@ -17,19 +17,18 @@
 package com.android.systemui.controls.dagger
 
 import android.testing.AndroidTestingRunner
-import android.provider.Settings
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.FakeControlsSettingsRepository
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.settings.SecureSettings
 import dagger.Lazy
 import java.util.Optional
 import org.junit.Assert.assertEquals
@@ -63,13 +62,13 @@
     @Mock
     private lateinit var lockPatternUtils: LockPatternUtils
     @Mock
-    private lateinit var secureSettings: SecureSettings
-    @Mock
     private lateinit var optionalControlsTileResourceConfiguration:
             Optional<ControlsTileResourceConfiguration>
     @Mock
     private lateinit var controlsTileResourceConfiguration: ControlsTileResourceConfiguration
 
+    private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
+
     companion object {
         fun <T> eq(value: T): T = Mockito.eq(value) ?: value
     }
@@ -78,6 +77,8 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        controlsSettingsRepository = FakeControlsSettingsRepository()
+
         `when`(userTracker.userHandle.identifier).thenReturn(0)
         `when`(optionalControlsTileResourceConfiguration.orElse(any()))
             .thenReturn(controlsTileResourceConfiguration)
@@ -125,8 +126,7 @@
         `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
             .thenReturn(STRONG_AUTH_NOT_REQUIRED)
         `when`(keyguardStateController.isUnlocked()).thenReturn(false)
-        `when`(secureSettings.getInt(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS), anyInt()))
-            .thenReturn(0)
+        controlsSettingsRepository.setCanShowControlsInLockscreen(false)
         val component = setupComponent(true)
 
         assertEquals(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK, component.getVisibility())
@@ -137,9 +137,7 @@
         `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
             .thenReturn(STRONG_AUTH_NOT_REQUIRED)
         `when`(keyguardStateController.isUnlocked()).thenReturn(false)
-        `when`(secureSettings.getIntForUser(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS),
-                anyInt(), anyInt()))
-            .thenReturn(1)
+        controlsSettingsRepository.setCanShowControlsInLockscreen(true)
         val component = setupComponent(true)
 
         assertEquals(ControlsComponent.Visibility.AVAILABLE, component.getVisibility())
@@ -147,8 +145,7 @@
 
     @Test
     fun testFeatureEnabledAndCanShowWhileUnlockedVisibility() {
-        `when`(secureSettings.getInt(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS), anyInt()))
-            .thenReturn(0)
+        controlsSettingsRepository.setCanShowControlsInLockscreen(false)
         `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
             .thenReturn(STRONG_AUTH_NOT_REQUIRED)
         `when`(keyguardStateController.isUnlocked()).thenReturn(true)
@@ -187,7 +184,7 @@
             lockPatternUtils,
             keyguardStateController,
             userTracker,
-            secureSettings,
+            controlsSettingsRepository,
             optionalControlsTileResourceConfiguration
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
index 1e4a9e4..765c4c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -34,9 +35,8 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -70,7 +70,7 @@
     fun testOnServicesUpdated_nullLoadLabel() {
         val captor = ArgumentCaptor
             .forClass(ControlsListingController.ControlsListingCallback::class.java)
-        val controlsServiceInfo = mock(ControlsServiceInfo::class.java)
+        val controlsServiceInfo = mock<ControlsServiceInfo>()
         val serviceInfo = listOf(controlsServiceInfo)
         `when`(controlsServiceInfo.loadLabel()).thenReturn(null)
         verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
@@ -81,4 +81,32 @@
 
         assertThat(adapter.itemCount).isEqualTo(serviceInfo.size)
     }
+
+    @Test
+    fun testOnServicesUpdatedDoesntHavePanels() {
+        val captor = ArgumentCaptor
+                .forClass(ControlsListingController.ControlsListingCallback::class.java)
+        val serviceInfo = listOf(
+                ControlsServiceInfo("no panel", null),
+                ControlsServiceInfo("panel", mock())
+        )
+        verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
+
+        captor.value.onServicesUpdated(serviceInfo)
+        backgroundExecutor.runAllReady()
+        uiExecutor.runAllReady()
+
+        assertThat(adapter.itemCount).isEqualTo(1)
+    }
+
+    fun ControlsServiceInfo(
+        label: CharSequence,
+        panelComponentName: ComponentName? = null
+    ): ControlsServiceInfo {
+        return mock {
+            `when`(this.loadLabel()).thenReturn(label)
+            `when`(this.panelActivity).thenReturn(panelComponentName)
+            `when`(this.loadIcon()).thenReturn(mock())
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
new file mode 100644
index 0000000..3b6f7d1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -0,0 +1,118 @@
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.CustomIconCache
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import java.util.concurrent.CountDownLatch
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsEditingActivityTest : SysuiTestCase() {
+    private val uiExecutor = FakeExecutor(FakeSystemClock())
+
+    @Mock lateinit var controller: ControlsControllerImpl
+
+    @Mock lateinit var userTracker: UserTracker
+
+    @Mock lateinit var customIconCache: CustomIconCache
+
+    @Mock lateinit var uiController: ControlsUiController
+
+    private lateinit var controlsEditingActivity: ControlsEditingActivity_Factory
+    private var latch: CountDownLatch = CountDownLatch(1)
+
+    @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
+    @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
+
+    @Rule
+    @JvmField
+    var activityRule =
+        ActivityTestRule(
+            object :
+                SingleActivityFactory<TestableControlsEditingActivity>(
+                    TestableControlsEditingActivity::class.java
+                ) {
+                override fun create(intent: Intent?): TestableControlsEditingActivity {
+                    return TestableControlsEditingActivity(
+                        uiExecutor,
+                        controller,
+                        userTracker,
+                        customIconCache,
+                        uiController,
+                        mockDispatcher,
+                        latch
+                    )
+                }
+            },
+            false,
+            false
+        )
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        val intent = Intent()
+        intent.putExtra(ControlsEditingActivity.EXTRA_STRUCTURE, "TestTitle")
+        val cname = ComponentName("TestPackageName", "TestClassName")
+        intent.putExtra(Intent.EXTRA_COMPONENT_NAME, cname)
+        activityRule.launchActivity(intent)
+    }
+
+    @Test
+    fun testBackCallbackRegistrationAndUnregistration() {
+        // 1. ensure that launching the activity results in it registering a callback
+        verify(mockDispatcher)
+            .registerOnBackInvokedCallback(
+                ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+                captureCallback.capture()
+            )
+        activityRule.finishActivity()
+        latch.await() // ensure activity is finished
+        // 2. ensure that when the activity is finished, it unregisters the same callback
+        verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
+    }
+
+    public class TestableControlsEditingActivity(
+        private val executor: FakeExecutor,
+        private val controller: ControlsControllerImpl,
+        private val userTracker: UserTracker,
+        private val customIconCache: CustomIconCache,
+        private val uiController: ControlsUiController,
+        private val mockDispatcher: OnBackInvokedDispatcher,
+        private val latch: CountDownLatch
+    ) : ControlsEditingActivity(executor, controller, userTracker, customIconCache, uiController) {
+        override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
+            return mockDispatcher
+        }
+
+        override fun onStop() {
+            super.onStop()
+            // ensures that test runner thread does not proceed until ui thread is done
+            latch.countDown()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
new file mode 100644
index 0000000..3655232
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -0,0 +1,125 @@
+package com.android.systemui.controls.management
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsFavoritingActivityTest : SysuiTestCase() {
+    @Main private val executor: Executor = MoreExecutors.directExecutor()
+
+    @Mock lateinit var controller: ControlsControllerImpl
+
+    @Mock lateinit var listingController: ControlsListingController
+
+    @Mock lateinit var userTracker: UserTracker
+
+    @Mock lateinit var uiController: ControlsUiController
+
+    private lateinit var controlsFavoritingActivity: ControlsFavoritingActivity_Factory
+    private var latch: CountDownLatch = CountDownLatch(1)
+
+    @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
+    @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
+
+    @Rule
+    @JvmField
+    var activityRule =
+        ActivityTestRule(
+            object :
+                SingleActivityFactory<TestableControlsFavoritingActivity>(
+                    TestableControlsFavoritingActivity::class.java
+                ) {
+                override fun create(intent: Intent?): TestableControlsFavoritingActivity {
+                    return TestableControlsFavoritingActivity(
+                        executor,
+                        controller,
+                        listingController,
+                        userTracker,
+                        uiController,
+                        mockDispatcher,
+                        latch
+                    )
+                }
+            },
+            false,
+            false
+        )
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        val intent = Intent()
+        intent.putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true)
+        activityRule.launchActivity(intent)
+    }
+
+    // b/259549854 to root-cause and fix
+    @FlakyTest
+    @Test
+    fun testBackCallbackRegistrationAndUnregistration() {
+        // 1. ensure that launching the activity results in it registering a callback
+        verify(mockDispatcher)
+            .registerOnBackInvokedCallback(
+                ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+                captureCallback.capture()
+            )
+        activityRule.finishActivity()
+        latch.await() // ensure activity is finished
+        // 2. ensure that when the activity is finished, it unregisters the same callback
+        verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
+    }
+
+    public class TestableControlsFavoritingActivity(
+        executor: Executor,
+        controller: ControlsControllerImpl,
+        listingController: ControlsListingController,
+        userTracker: UserTracker,
+        uiController: ControlsUiController,
+        private val mockDispatcher: OnBackInvokedDispatcher,
+        private val latch: CountDownLatch
+    ) :
+        ControlsFavoritingActivity(
+            executor,
+            controller,
+            listingController,
+            userTracker,
+            uiController
+        ) {
+        override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
+            return mockDispatcher
+        }
+
+        override fun onStop() {
+            super.onStop()
+            // ensures that test runner thread does not proceed until ui thread is done
+            latch.countDown()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index db41d8d..c677f19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -16,27 +16,44 @@
 
 package com.android.systemui.controls.management
 
+import android.Manifest
 import android.content.ComponentName
 import android.content.Context
 import android.content.ContextWrapper
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
 import android.content.pm.ServiceInfo
+import android.os.Bundle
 import android.os.UserHandle
+import android.service.controls.ControlsProviderService
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.settingslib.applications.ServiceListing
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.USE_APP_PANELS
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.After
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatcher
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.mock
@@ -51,10 +68,8 @@
 class ControlsListingControllerImplTest : SysuiTestCase() {
 
     companion object {
-        private const val TEST_LABEL = "TEST_LABEL"
-        private const val TEST_PERMISSION = "permission"
-        fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
-        fun <T> any(): T = Mockito.any<T>()
+        private const val FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
+                PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
     }
 
     @Mock
@@ -63,15 +78,17 @@
     private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
     @Mock
     private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
-    @Mock
-    private lateinit var serviceInfo: ServiceInfo
-    @Mock
-    private lateinit var serviceInfo2: ServiceInfo
     @Mock(stubOnly = true)
     private lateinit var userTracker: UserTracker
+    @Mock(stubOnly = true)
+    private lateinit var dumpManager: DumpManager
+    @Mock
+    private lateinit var packageManager: PackageManager
+    @Mock
+    private lateinit var featureFlags: FeatureFlags
 
-    private var componentName = ComponentName("pkg1", "class1")
-    private var componentName2 = ComponentName("pkg2", "class2")
+    private var componentName = ComponentName("pkg", "class1")
+    private var activityName = ComponentName("pkg", "activity")
 
     private val executor = FakeExecutor(FakeSystemClock())
 
@@ -87,9 +104,21 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        `when`(serviceInfo.componentName).thenReturn(componentName)
-        `when`(serviceInfo2.componentName).thenReturn(componentName2)
         `when`(userTracker.userId).thenReturn(user)
+        `when`(userTracker.userContext).thenReturn(context)
+        // Return disabled by default
+        `when`(packageManager.getComponentEnabledSetting(any()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
+        mContext.setMockPackageManager(packageManager)
+
+        mContext.orCreateTestableResources
+                .addOverride(
+                        R.array.config_controlsPreferredPackages,
+                        arrayOf(componentName.packageName)
+                )
+
+        // Return true by default, we'll test the false path
+        `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(true)
 
         val wrapper = object : ContextWrapper(mContext) {
             override fun createContextAsUser(user: UserHandle, flags: Int): Context {
@@ -97,7 +126,14 @@
             }
         }
 
-        controller = ControlsListingControllerImpl(wrapper, executor, { mockSL }, userTracker)
+        controller = ControlsListingControllerImpl(
+                wrapper,
+                executor,
+                { mockSL },
+                userTracker,
+                dumpManager,
+                featureFlags
+        )
         verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
     }
 
@@ -123,9 +159,16 @@
             Unit
         }
         `when`(mockServiceListing.reload()).then {
-            callback?.onServicesReloaded(listOf(serviceInfo))
+            callback?.onServicesReloaded(listOf(ServiceInfo(componentName)))
         }
-        ControlsListingControllerImpl(mContext, exec, { mockServiceListing }, userTracker)
+        ControlsListingControllerImpl(
+                mContext,
+                exec,
+                { mockServiceListing },
+                userTracker,
+                dumpManager,
+                featureFlags
+        )
     }
 
     @Test
@@ -148,7 +191,7 @@
 
     @Test
     fun testCallbackGetsList() {
-        val list = listOf(serviceInfo)
+        val list = listOf(ServiceInfo(componentName))
         controller.addCallback(mockCallback)
         controller.addCallback(mockCallbackOther)
 
@@ -188,6 +231,8 @@
 
     @Test
     fun testChangeUserSendsCorrectServiceUpdate() {
+        val serviceInfo = ServiceInfo(componentName)
+
         val list = listOf(serviceInfo)
         controller.addCallback(mockCallback)
 
@@ -223,4 +268,326 @@
         verify(mockCallback).onServicesUpdated(capture(captor))
         assertEquals(0, captor.value.size)
     }
+
+    @Test
+    fun test_nullPanelActivity() {
+        val list = listOf(ServiceInfo(componentName))
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+
+        assertNull(controller.getCurrentServices()[0].panelActivity)
+    }
+
+    @Test
+    fun testNoActivity_nullPanel() {
+        val serviceInfo = ServiceInfo(
+                componentName,
+                activityName
+        )
+
+        val list = listOf(serviceInfo)
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+
+        assertNull(controller.getCurrentServices()[0].panelActivity)
+    }
+
+    @Test
+    fun testActivityWithoutPermission_nullPanel() {
+        val serviceInfo = ServiceInfo(
+                componentName,
+                activityName
+        )
+
+        setUpQueryResult(listOf(ActivityInfo(activityName)))
+
+        val list = listOf(serviceInfo)
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+
+        assertNull(controller.getCurrentServices()[0].panelActivity)
+    }
+
+    @Test
+    fun testActivityPermissionNotExported_nullPanel() {
+        val serviceInfo = ServiceInfo(
+                componentName,
+                activityName
+        )
+
+        setUpQueryResult(listOf(
+                ActivityInfo(activityName, permission = Manifest.permission.BIND_CONTROLS)
+        ))
+
+        val list = listOf(serviceInfo)
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+
+        assertNull(controller.getCurrentServices()[0].panelActivity)
+    }
+
+    @Test
+    fun testActivityDisabled_nullPanel() {
+        val serviceInfo = ServiceInfo(
+                componentName,
+                activityName
+        )
+
+        setUpQueryResult(listOf(
+                ActivityInfo(
+                        activityName,
+                        exported = true,
+                        permission = Manifest.permission.BIND_CONTROLS
+                )
+        ))
+
+        val list = listOf(serviceInfo)
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+
+        assertNull(controller.getCurrentServices()[0].panelActivity)
+    }
+
+    @Test
+    fun testActivityEnabled_correctPanel() {
+        val serviceInfo = ServiceInfo(
+                componentName,
+                activityName
+        )
+
+        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+
+        setUpQueryResult(listOf(
+                ActivityInfo(
+                        activityName,
+                        exported = true,
+                        permission = Manifest.permission.BIND_CONTROLS
+                )
+        ))
+
+        val list = listOf(serviceInfo)
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+
+        assertEquals(activityName, controller.getCurrentServices()[0].panelActivity)
+    }
+
+    @Test
+    fun testActivityDefaultEnabled_correctPanel() {
+        val serviceInfo = ServiceInfo(
+                componentName,
+                activityName
+        )
+
+        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+        setUpQueryResult(listOf(
+                ActivityInfo(
+                        activityName,
+                        enabled = true,
+                        exported = true,
+                        permission = Manifest.permission.BIND_CONTROLS
+                )
+        ))
+
+        val list = listOf(serviceInfo)
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+
+        assertEquals(activityName, controller.getCurrentServices()[0].panelActivity)
+    }
+
+    @Test
+    fun testActivityDefaultDisabled_nullPanel() {
+        val serviceInfo = ServiceInfo(
+                componentName,
+                activityName
+        )
+
+        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+        setUpQueryResult(listOf(
+                ActivityInfo(
+                        activityName,
+                        enabled = false,
+                        exported = true,
+                        permission = Manifest.permission.BIND_CONTROLS
+                )
+        ))
+
+        val list = listOf(serviceInfo)
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+
+        assertNull(controller.getCurrentServices()[0].panelActivity)
+    }
+
+    @Test
+    fun testActivityDefaultEnabled_flagDisabled_nullPanel() {
+        `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(false)
+        val serviceInfo = ServiceInfo(
+                componentName,
+                activityName,
+        )
+
+        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+        setUpQueryResult(listOf(
+                ActivityInfo(
+                        activityName,
+                        enabled = true,
+                        exported = true,
+                        permission = Manifest.permission.BIND_CONTROLS
+                )
+        ))
+
+        val list = listOf(serviceInfo)
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+
+        assertNull(controller.getCurrentServices()[0].panelActivity)
+    }
+
+    @Test
+    fun testActivityDifferentPackage_nullPanel() {
+        val serviceInfo = ServiceInfo(
+                componentName,
+                ComponentName("other_package", "cls")
+        )
+
+        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+        setUpQueryResult(listOf(
+                ActivityInfo(
+                        activityName,
+                        enabled = true,
+                        exported = true,
+                        permission = Manifest.permission.BIND_CONTROLS
+                )
+        ))
+
+        val list = listOf(serviceInfo)
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+
+        assertNull(controller.getCurrentServices()[0].panelActivity)
+    }
+
+    @Test
+    fun testPackageNotPreferred_nullPanel() {
+        mContext.orCreateTestableResources
+                .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
+
+        val serviceInfo = ServiceInfo(
+                componentName,
+                activityName
+        )
+
+        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+
+        setUpQueryResult(listOf(
+                ActivityInfo(
+                        activityName,
+                        exported = true,
+                        permission = Manifest.permission.BIND_CONTROLS
+                )
+        ))
+
+        val list = listOf(serviceInfo)
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+
+        assertNull(controller.getCurrentServices()[0].panelActivity)
+    }
+
+    @Test
+    fun testListingsNotModifiedByCallback() {
+        // This test checks that if the list passed to the callback is modified, it has no effect
+        // in the resulting services
+        val list = mutableListOf<ServiceInfo>()
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        list.add(ServiceInfo(ComponentName("a", "b")))
+        executor.runAllReady()
+
+        assertTrue(controller.getCurrentServices().isEmpty())
+    }
+
+    private fun ServiceInfo(
+            componentName: ComponentName,
+            panelActivityComponentName: ComponentName? = null
+    ): ServiceInfo {
+        return ServiceInfo().apply {
+            packageName = componentName.packageName
+            name = componentName.className
+            panelActivityComponentName?.let {
+                metaData = Bundle().apply {
+                    putString(
+                            ControlsProviderService.META_DATA_PANEL_ACTIVITY,
+                            it.flattenToShortString()
+                    )
+                }
+            }
+        }
+    }
+
+    private fun ActivityInfo(
+        componentName: ComponentName,
+        exported: Boolean = false,
+        enabled: Boolean = true,
+        permission: String? = null
+    ): ActivityInfo {
+        return ActivityInfo().apply {
+            packageName = componentName.packageName
+            name = componentName.className
+            this.permission = permission
+            this.exported = exported
+            this.enabled = enabled
+        }
+    }
+
+    private fun setUpQueryResult(infos: List<ActivityInfo>) {
+        `when`(
+                packageManager.queryIntentActivitiesAsUser(
+                        argThat(IntentMatcher(activityName)),
+                        argThat(FlagsMatcher(FLAGS)),
+                        eq(UserHandle.of(user))
+                )
+        ).thenReturn(infos.map {
+            ResolveInfo().apply { activityInfo = it }
+        })
+    }
+
+    private class IntentMatcher(
+            private val componentName: ComponentName
+    ) : ArgumentMatcher<Intent> {
+        override fun matches(argument: Intent?): Boolean {
+            return argument?.component == componentName
+        }
+    }
+
+    private class FlagsMatcher(
+            private val flags: Long
+    ) : ArgumentMatcher<PackageManager.ResolveInfoFlags> {
+        override fun matches(argument: PackageManager.ResolveInfoFlags?): Boolean {
+            return flags == argument?.value
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
new file mode 100644
index 0000000..56c3efe
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.management
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsProviderSelectorActivityTest : SysuiTestCase() {
+    @Main private val executor: Executor = MoreExecutors.directExecutor()
+
+    @Background private val backExecutor: Executor = MoreExecutors.directExecutor()
+
+    @Mock lateinit var listingController: ControlsListingController
+
+    @Mock lateinit var controlsController: ControlsController
+
+    @Mock lateinit var userTracker: UserTracker
+
+    @Mock lateinit var uiController: ControlsUiController
+
+    private lateinit var controlsProviderSelectorActivity: ControlsProviderSelectorActivity_Factory
+    private var latch: CountDownLatch = CountDownLatch(1)
+
+    @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
+    @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
+
+    @Rule
+    @JvmField
+    var activityRule =
+        ActivityTestRule(
+            object :
+                SingleActivityFactory<TestableControlsProviderSelectorActivity>(
+                    TestableControlsProviderSelectorActivity::class.java
+                ) {
+                override fun create(intent: Intent?): TestableControlsProviderSelectorActivity {
+                    return TestableControlsProviderSelectorActivity(
+                        executor,
+                        backExecutor,
+                        listingController,
+                        controlsController,
+                        userTracker,
+                        uiController,
+                        mockDispatcher,
+                        latch
+                    )
+                }
+            },
+            false,
+            false
+        )
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        val intent = Intent()
+        intent.putExtra(ControlsProviderSelectorActivity.BACK_SHOULD_EXIT, true)
+        activityRule.launchActivity(intent)
+    }
+
+    @Test
+    fun testBackCallbackRegistrationAndUnregistration() {
+        // 1. ensure that launching the activity results in it registering a callback
+        verify(mockDispatcher)
+            .registerOnBackInvokedCallback(
+                ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+                captureCallback.capture()
+            )
+        activityRule.finishActivity()
+        latch.await() // ensure activity is finished
+        // 2. ensure that when the activity is finished, it unregisters the same callback
+        verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
+    }
+
+    public class TestableControlsProviderSelectorActivity(
+        executor: Executor,
+        backExecutor: Executor,
+        listingController: ControlsListingController,
+        controlsController: ControlsController,
+        userTracker: UserTracker,
+        uiController: ControlsUiController,
+        private val mockDispatcher: OnBackInvokedDispatcher,
+        private val latch: CountDownLatch
+    ) :
+        ControlsProviderSelectorActivity(
+            executor,
+            backExecutor,
+            listingController,
+            controlsController,
+            userTracker,
+            uiController
+        ) {
+        override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
+            return mockDispatcher
+        }
+
+        override fun onStop() {
+            super.onStop()
+            // ensures that test runner thread does not proceed until ui thread is done
+            latch.countDown()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
index efb3db7..314b176 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import org.junit.After
@@ -46,9 +47,10 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
 
 @MediumTest
 @RunWith(AndroidTestingRunner::class)
@@ -67,6 +69,10 @@
     private lateinit var controller: ControlsController
 
     @Mock
+    private lateinit var mainExecutor: Executor
+    @Mock
+    private lateinit var userTracker: UserTracker
+    @Mock
     private lateinit var listingController: ControlsListingController
     @Mock
     private lateinit var iIntentSender: IIntentSender
@@ -81,8 +87,9 @@
             ) {
                     override fun create(intent: Intent?): TestControlsRequestDialog {
                         return TestControlsRequestDialog(
+                                mainExecutor,
                                 controller,
-                                fakeBroadcastDispatcher,
+                                userTracker,
                                 listingController
                         )
                     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
index 3f6308b..ec239f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.controls.management
 
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
 
 class TestControlsRequestDialog(
+    mainExecutor: Executor,
     controller: ControlsController,
-    dispatcher: BroadcastDispatcher,
+    userTracker: UserTracker,
     listingController: ControlsListingController
-) : ControlsRequestDialog(controller, dispatcher, listingController)
\ No newline at end of file
+) : ControlsRequestDialog(mainExecutor, controller, userTracker, listingController)
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..d965e33
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -0,0 +1,414 @@
+/*
+ * 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.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.ServiceInfo
+import android.os.UserHandle
+import android.service.controls.ControlsProviderService
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.CustomIconCache
+import com.android.systemui.controls.FakeControlsSettingsRepository
+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.dump.DumpManager
+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.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.TaskView
+import com.android.wm.shell.TaskViewFactory
+import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import java.util.Optional
+import java.util.function.Consumer
+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.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+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
+    @Mock lateinit var taskViewFactory: TaskViewFactory
+    @Mock lateinit var dumpManager: DumpManager
+    val sharedPreferences = FakeSharedPreferences()
+    lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
+
+    var uiExecutor = FakeExecutor(FakeSystemClock())
+    var bgExecutor = FakeExecutor(FakeSystemClock())
+    lateinit var underTest: ControlsUiControllerImpl
+    lateinit var parent: FrameLayout
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        controlsSettingsRepository = FakeControlsSettingsRepository()
+
+        // This way, it won't be cloned every time `LayoutInflater.fromContext` is called, but we
+        // need to clone it once so we don't modify the original one.
+        mContext.addMockSystemService(
+            Context.LAYOUT_INFLATER_SERVICE,
+            mContext.baseContext
+                .getSystemService(LayoutInflater::class.java)!!
+                .cloneInContext(mContext)
+        )
+
+        parent = FrameLayout(mContext)
+
+        underTest =
+            ControlsUiControllerImpl(
+                Lazy { controlsController },
+                context,
+                uiExecutor,
+                bgExecutor,
+                Lazy { controlsListingController },
+                controlActionCoordinator,
+                activityStarter,
+                iconCache,
+                controlsMetricsLogger,
+                keyguardStateController,
+                userFileManager,
+                userTracker,
+                Optional.of(taskViewFactory),
+                controlsSettingsRepository,
+                dumpManager
+            )
+        `when`(
+                userFileManager.getSharedPreferences(
+                    DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+                    0,
+                    0
+                )
+            )
+            .thenReturn(sharedPreferences)
+        `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
+            .thenReturn(sharedPreferences)
+        `when`(userTracker.userId).thenReturn(0)
+        `when`(userTracker.userHandle).thenReturn(UserHandle.of(0))
+    }
+
+    @Test
+    fun testGetPreferredStructure() {
+        val structureInfo = mock<StructureInfo>()
+        underTest.getPreferredSelectedItem(listOf(structureInfo))
+        verify(userFileManager)
+            .getSharedPreferences(
+                fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+                mode = 0,
+                userId = 0
+            )
+    }
+
+    @Test
+    fun testGetPreferredStructure_differentUserId() {
+        val selectedItems =
+            listOf(
+                SelectedItem.StructureItem(
+                    StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
+                ),
+                SelectedItem.StructureItem(
+                    StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList())
+                ),
+            )
+        val structures = selectedItems.map { it.structure }
+        sharedPreferences
+            .edit()
+            .putString("controls_component", selectedItems[0].componentName.flattenToString())
+            .putString("controls_structure", selectedItems[0].name.toString())
+            .commit()
+
+        val differentSharedPreferences = FakeSharedPreferences()
+        differentSharedPreferences
+            .edit()
+            .putString("controls_component", selectedItems[1].componentName.flattenToString())
+            .putString("controls_structure", selectedItems[1].name.toString())
+            .commit()
+
+        val previousPreferredStructure = underTest.getPreferredSelectedItem(structures)
+
+        `when`(
+                userFileManager.getSharedPreferences(
+                    DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+                    0,
+                    1
+                )
+            )
+            .thenReturn(differentSharedPreferences)
+        `when`(userTracker.userId).thenReturn(1)
+
+        val currentPreferredStructure = underTest.getPreferredSelectedItem(structures)
+
+        assertThat(previousPreferredStructure).isEqualTo(selectedItems[0])
+        assertThat(currentPreferredStructure).isEqualTo(selectedItems[1])
+        assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
+    }
+
+    @Test
+    fun testGetPreferredPanel() {
+        val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+        sharedPreferences
+            .edit()
+            .putString("controls_component", panel.componentName.flattenToString())
+            .putString("controls_structure", panel.appName.toString())
+            .putBoolean("controls_is_panel", true)
+            .commit()
+
+        val selected = underTest.getPreferredSelectedItem(emptyList())
+
+        assertThat(selected).isEqualTo(panel)
+    }
+
+    @Test
+    fun testPanelDoesNotRefreshControls() {
+        val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+        setUpPanel(panel)
+
+        underTest.show(parent, {}, context)
+        verify(controlsController, never()).refreshStatus(any(), any())
+    }
+
+    @Test
+    fun testPanelCallsTaskViewFactoryCreate() {
+        mockLayoutInflater()
+        val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+        val serviceInfo = setUpPanel(panel)
+
+        underTest.show(parent, {}, context)
+
+        val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+        verify(controlsListingController).addCallback(capture(captor))
+
+        captor.value.onServicesUpdated(listOf(serviceInfo))
+        FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+        verify(taskViewFactory).create(eq(context), eq(uiExecutor), any())
+    }
+
+    @Test
+    fun testPanelControllerStartActivityWithCorrectArguments() {
+        mockLayoutInflater()
+        controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+
+        val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+        val serviceInfo = setUpPanel(panel)
+
+        underTest.show(parent, {}, context)
+
+        val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+        verify(controlsListingController).addCallback(capture(captor))
+
+        captor.value.onServicesUpdated(listOf(serviceInfo))
+        FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+        val pendingIntent = verifyPanelCreatedAndStartTaskView()
+
+        with(pendingIntent) {
+            assertThat(isActivity).isTrue()
+            assertThat(intent.component).isEqualTo(serviceInfo.panelActivity)
+            assertThat(
+                    intent.getBooleanExtra(
+                        ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
+                        false
+                    )
+                )
+                .isTrue()
+        }
+    }
+
+    @Test
+    fun testPendingIntentExtrasAreModified() {
+        mockLayoutInflater()
+        controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+
+        val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+        val serviceInfo = setUpPanel(panel)
+
+        underTest.show(parent, {}, context)
+
+        val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+        verify(controlsListingController).addCallback(capture(captor))
+
+        captor.value.onServicesUpdated(listOf(serviceInfo))
+        FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+        val pendingIntent = verifyPanelCreatedAndStartTaskView()
+        assertThat(
+                pendingIntent.intent.getBooleanExtra(
+                    ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
+                    false
+                )
+            )
+            .isTrue()
+
+        underTest.hide()
+
+        clearInvocations(controlsListingController, taskViewFactory)
+        controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(false)
+        underTest.show(parent, {}, context)
+
+        verify(controlsListingController).addCallback(capture(captor))
+        captor.value.onServicesUpdated(listOf(serviceInfo))
+        FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+        val newPendingIntent = verifyPanelCreatedAndStartTaskView()
+        assertThat(
+                newPendingIntent.intent.getBooleanExtra(
+                    ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
+                    false
+                )
+            )
+            .isFalse()
+    }
+
+    private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
+        val activity = ComponentName("pkg", "activity")
+        sharedPreferences
+            .edit()
+            .putString("controls_component", panel.componentName.flattenToString())
+            .putString("controls_structure", panel.appName.toString())
+            .putBoolean("controls_is_panel", true)
+            .commit()
+        return ControlsServiceInfo(panel.componentName, panel.appName, activity)
+    }
+
+    private fun verifyPanelCreatedAndStartTaskView(): PendingIntent {
+        val taskViewConsumerCaptor = argumentCaptor<Consumer<TaskView>>()
+        verify(taskViewFactory).create(eq(context), eq(uiExecutor), capture(taskViewConsumerCaptor))
+
+        val taskView: TaskView = mock {
+            `when`(this.post(any())).thenAnswer {
+                uiExecutor.execute(it.arguments[0] as Runnable)
+                true
+            }
+        }
+        // calls PanelTaskViewController#launchTaskView
+        taskViewConsumerCaptor.value.accept(taskView)
+        val listenerCaptor = argumentCaptor<TaskView.Listener>()
+        verify(taskView).setListener(any(), capture(listenerCaptor))
+        listenerCaptor.value.onInitialized()
+        FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+        val pendingIntentCaptor = argumentCaptor<PendingIntent>()
+        verify(taskView).startActivity(capture(pendingIntentCaptor), any(), any(), any())
+        return pendingIntentCaptor.value
+    }
+
+    private fun ControlsServiceInfo(
+        componentName: ComponentName,
+        label: CharSequence,
+        panelComponentName: ComponentName? = null
+    ): ControlsServiceInfo {
+        val serviceInfo =
+            ServiceInfo().apply {
+                applicationInfo = ApplicationInfo()
+                packageName = componentName.packageName
+                name = componentName.className
+            }
+        return spy(ControlsServiceInfo(mContext, serviceInfo)).apply {
+            `when`(loadLabel()).thenReturn(label)
+            `when`(loadIcon()).thenReturn(mock())
+            `when`(panelActivity).thenReturn(panelComponentName)
+        }
+    }
+
+    private fun mockLayoutInflater() {
+        LayoutInflater.from(context)
+            .setPrivateFactory(
+                object : LayoutInflater.Factory2 {
+                    override fun onCreateView(
+                        view: View?,
+                        name: String,
+                        context: Context,
+                        attrs: AttributeSet
+                    ): View? {
+                        return onCreateView(name, context, attrs)
+                    }
+
+                    override fun onCreateView(
+                        name: String,
+                        context: Context,
+                        attrs: AttributeSet
+                    ): View? {
+                        if (FrameLayout::class.java.simpleName.equals(name)) {
+                            val mock: FrameLayout = mock {
+                                `when`(this.context).thenReturn(context)
+                                `when`(this.id).thenReturn(R.id.controls_panel)
+                                `when`(this.requireViewById<View>(any())).thenCallRealMethod()
+                                `when`(this.findViewById<View>(R.id.controls_panel))
+                                    .thenReturn(this)
+                                `when`(this.post(any())).thenAnswer {
+                                    uiExecutor.execute(it.arguments[0] as Runnable)
+                                    true
+                                }
+                            }
+                            return mock
+                        } else {
+                            return null
+                        }
+                    }
+                }
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
new file mode 100644
index 0000000..5cd2ace
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.app.ActivityOptions
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.boundsOnScreen
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.TaskView
+import com.google.common.truth.Truth.assertThat
+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.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class PanelTaskViewControllerTest : SysuiTestCase() {
+
+    companion object {
+        val FAKE_BOUNDS = Rect(10, 20, 30, 40)
+    }
+
+    @Mock private lateinit var activityContext: Context
+    @Mock private lateinit var taskView: TaskView
+    @Mock private lateinit var pendingIntent: PendingIntent
+    @Mock private lateinit var hideRunnable: () -> Unit
+
+    @Captor private lateinit var listenerCaptor: ArgumentCaptor<TaskView.Listener>
+
+    private lateinit var uiExecutor: FakeExecutor
+    private lateinit var underTest: PanelTaskViewController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(taskView.boundsOnScreen).thenAnswer { (it.arguments[0] as Rect).set(FAKE_BOUNDS) }
+        whenever(taskView.post(any())).thenAnswer {
+            uiExecutor.execute(it.arguments[0] as Runnable)
+            true
+        }
+
+        uiExecutor = FakeExecutor(FakeSystemClock())
+
+        underTest =
+            PanelTaskViewController(
+                activityContext,
+                uiExecutor,
+                pendingIntent,
+                taskView,
+                hideRunnable
+            )
+    }
+
+    @Test
+    fun testLaunchTaskViewAttachedListener() {
+        underTest.launchTaskView()
+        verify(taskView).setListener(eq(uiExecutor), any())
+    }
+
+    @Test
+    fun testTaskViewOnInitializeStartsActivity() {
+        underTest.launchTaskView()
+        verify(taskView).setListener(any(), capture(listenerCaptor))
+
+        listenerCaptor.value.onInitialized()
+        uiExecutor.runAllReady()
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val optionsCaptor = argumentCaptor<ActivityOptions>()
+
+        verify(taskView)
+            .startActivity(
+                eq(pendingIntent),
+                /* fillInIntent */ capture(intentCaptor),
+                capture(optionsCaptor),
+                eq(FAKE_BOUNDS)
+            )
+
+        assertThat(intentCaptor.value.flags)
+            .isEqualTo(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_MULTIPLE_TASK)
+        assertThat(optionsCaptor.value.taskAlwaysOnTop).isTrue()
+    }
+
+    @Test
+    fun testHideRunnableCalledWhenBackOnRoot() {
+        underTest.launchTaskView()
+        verify(taskView).setListener(any(), capture(listenerCaptor))
+
+        listenerCaptor.value.onBackPressedOnTaskRoot(0)
+
+        verify(hideRunnable).invoke()
+    }
+
+    @Test
+    fun testTaskViewReleasedOnDismiss() {
+        underTest.dismiss()
+        verify(taskView).release()
+    }
+
+    @Test
+    fun testTaskViewReleasedOnBackOnRoot() {
+        underTest.launchTaskView()
+        verify(taskView).setListener(any(), capture(listenerCaptor))
+
+        listenerCaptor.value.onBackPressedOnTaskRoot(0)
+        verify(taskView).release()
+    }
+
+    @Test
+    fun testOnTaskRemovalStarted() {
+        underTest.launchTaskView()
+        verify(taskView).setListener(any(), capture(listenerCaptor))
+
+        listenerCaptor.value.onTaskRemovalStarted(0)
+        verify(taskView).release()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/SelectionItemTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/SelectionItemTest.kt
new file mode 100644
index 0000000..57176f0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/SelectionItemTest.kt
@@ -0,0 +1,112 @@
+package com.android.systemui.controls.ui
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SelectionItemTest : SysuiTestCase() {
+
+    @Test
+    fun testMatchBadComponentName_false() {
+        val selectionItem =
+            SelectionItem(
+                appName = "app",
+                structure = "structure",
+                icon = mock(),
+                componentName = ComponentName("pkg", "cls"),
+                uid = 0,
+                panelComponentName = null
+            )
+
+        assertThat(
+                selectionItem.matches(
+                    SelectedItem.StructureItem(
+                        StructureInfo(ComponentName("", ""), "s", emptyList())
+                    )
+                )
+            )
+            .isFalse()
+        assertThat(selectionItem.matches(SelectedItem.PanelItem("name", ComponentName("", ""))))
+            .isFalse()
+    }
+
+    @Test
+    fun testMatchSameComponentName_panelSelected_true() {
+        val componentName = ComponentName("pkg", "cls")
+
+        val selectionItem =
+            SelectionItem(
+                appName = "app",
+                structure = "structure",
+                icon = mock(),
+                componentName = componentName,
+                uid = 0,
+                panelComponentName = null
+            )
+        assertThat(selectionItem.matches(SelectedItem.PanelItem("name", componentName))).isTrue()
+    }
+
+    @Test
+    fun testMatchSameComponentName_panelSelection_true() {
+        val componentName = ComponentName("pkg", "cls")
+
+        val selectionItem =
+            SelectionItem(
+                appName = "app",
+                structure = "structure",
+                icon = mock(),
+                componentName = componentName,
+                uid = 0,
+                panelComponentName = ComponentName("pkg", "panel")
+            )
+        assertThat(selectionItem.matches(SelectedItem.PanelItem("name", componentName))).isTrue()
+    }
+
+    @Test
+    fun testMatchSameComponentSameStructure_true() {
+        val componentName = ComponentName("pkg", "cls")
+        val structureName = "structure"
+
+        val structureItem =
+            SelectedItem.StructureItem(StructureInfo(componentName, structureName, emptyList()))
+
+        val selectionItem =
+            SelectionItem(
+                appName = "app",
+                structure = structureName,
+                icon = mock(),
+                componentName = componentName,
+                uid = 0,
+                panelComponentName = null
+            )
+        assertThat(selectionItem.matches(structureItem)).isTrue()
+    }
+
+    @Test
+    fun testMatchSameComponentDifferentStructure_false() {
+        val componentName = ComponentName("pkg", "cls")
+        val structureName = "structure"
+
+        val structureItem =
+            SelectedItem.StructureItem(StructureInfo(componentName, structureName, emptyList()))
+
+        val selectionItem =
+            SelectionItem(
+                appName = "app",
+                structure = "other",
+                icon = mock(),
+                componentName = componentName,
+                uid = 0,
+                panelComponentName = null
+            )
+        assertThat(selectionItem.matches(structureItem)).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 2f206ad..07d7e79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -51,6 +51,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.doze.DozeSensors.TriggerSensor;
 import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -99,6 +100,8 @@
     @Mock
     private DevicePostureController mDevicePostureController;
     @Mock
+    private UserTracker mUserTracker;
+    @Mock
     private ProximitySensor mProximitySensor;
 
     // Capture listeners so that they can be used to send events
@@ -428,7 +431,7 @@
         DozeSensors dozeSensors = new DozeSensors(mSensorManager, mDozeParameters,
                 mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                 mProximitySensor, mFakeSettings, mAuthController,
-                mDevicePostureController);
+                mDevicePostureController, mUserTracker);
 
         for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
             assertFalse(sensor.mIgnoresSetting);
@@ -440,7 +443,7 @@
             super(mSensorManager, mDozeParameters,
                     mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                     mProximitySensor, mFakeSettings, mAuthController,
-                    mDevicePostureController);
+                    mDevicePostureController, mUserTracker);
             for (TriggerSensor sensor : mTriggerSensors) {
                 if (sensor instanceof PluginSensor
                         && ((PluginSensor) sensor).mPluginSensor.getType()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 6091d3a..82432ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -49,6 +49,7 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent;
 import com.android.systemui.log.SessionTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -98,6 +99,8 @@
     @Mock
     private DevicePostureController mDevicePostureController;
     @Mock
+    private UserTracker mUserTracker;
+    @Mock
     private SessionTracker mSessionTracker;
 
     private DozeTriggers mTriggers;
@@ -131,7 +134,7 @@
                 asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
                 mProximityCheck, mDozeLog, mBroadcastDispatcher, new FakeSettings(),
                 mAuthController, mUiEventLogger, mSessionTracker, mKeyguardStateController,
-                mDevicePostureController);
+                mDevicePostureController, mUserTracker);
         mTriggers.setDozeMachine(mMachine);
         waitForSensorManager();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
new file mode 100644
index 0000000..99406ed
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -0,0 +1,125 @@
+package com.android.systemui.dreams
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.statusbar.BlurUtils
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+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.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
+
+    companion object {
+        private const val DREAM_IN_BLUR_ANIMATION_DURATION = 1L
+        private const val DREAM_IN_BLUR_ANIMATION_DELAY = 2L
+        private const val DREAM_IN_COMPLICATIONS_ANIMATION_DURATION = 3L
+        private const val DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY = 4L
+        private const val DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY = 5L
+        private const val DREAM_OUT_TRANSLATION_Y_DISTANCE = 6
+        private const val DREAM_OUT_TRANSLATION_Y_DURATION = 7L
+        private const val DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM = 8L
+        private const val DREAM_OUT_TRANSLATION_Y_DELAY_TOP = 9L
+        private const val DREAM_OUT_ALPHA_DURATION = 10L
+        private const val DREAM_OUT_ALPHA_DELAY_BOTTOM = 11L
+        private const val DREAM_OUT_ALPHA_DELAY_TOP = 12L
+        private const val DREAM_OUT_BLUR_DURATION = 13L
+    }
+
+    @Mock private lateinit var mockAnimator: AnimatorSet
+    @Mock private lateinit var blurUtils: BlurUtils
+    @Mock private lateinit var hostViewController: ComplicationHostViewController
+    @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
+    @Mock private lateinit var stateController: DreamOverlayStateController
+    private lateinit var controller: DreamOverlayAnimationsController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        controller =
+            DreamOverlayAnimationsController(
+                blurUtils,
+                hostViewController,
+                statusBarViewController,
+                stateController,
+                DREAM_IN_BLUR_ANIMATION_DURATION,
+                DREAM_IN_BLUR_ANIMATION_DELAY,
+                DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
+                DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY,
+                DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY,
+                DREAM_OUT_TRANSLATION_Y_DISTANCE,
+                DREAM_OUT_TRANSLATION_Y_DURATION,
+                DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM,
+                DREAM_OUT_TRANSLATION_Y_DELAY_TOP,
+                DREAM_OUT_ALPHA_DURATION,
+                DREAM_OUT_ALPHA_DELAY_BOTTOM,
+                DREAM_OUT_ALPHA_DELAY_TOP,
+                DREAM_OUT_BLUR_DURATION
+            )
+    }
+
+    @Test
+    fun testExitAnimationOnEnd() {
+        val mockCallback: () -> Unit = mock()
+
+        controller.startExitAnimations(
+            view = mock(),
+            doneCallback = mockCallback,
+            animatorBuilder = { mockAnimator }
+        )
+
+        val captor = argumentCaptor<Animator.AnimatorListener>()
+        verify(mockAnimator).addListener(captor.capture())
+        val listener = captor.value
+
+        verify(mockCallback, never()).invoke()
+        listener.onAnimationEnd(mockAnimator)
+        verify(mockCallback, times(1)).invoke()
+    }
+
+    @Test
+    fun testCancellation() {
+        controller.startExitAnimations(
+            view = mock(),
+            doneCallback = mock(),
+            animatorBuilder = { mockAnimator }
+        )
+
+        verify(mockAnimator, never()).cancel()
+        controller.cancelAnimations()
+        verify(mockAnimator, times(1)).cancel()
+    }
+
+    @Test
+    fun testExitAfterStartWillCancel() {
+        val mockStartAnimator: AnimatorSet = mock()
+        val mockExitAnimator: AnimatorSet = mock()
+
+        controller.startEntryAnimations(view = mock(), animatorBuilder = { mockStartAnimator })
+
+        verify(mockStartAnimator, never()).cancel()
+
+        controller.startExitAnimations(
+            view = mock(),
+            doneCallback = mock(),
+            animatorBuilder = { mockExitAnimator }
+        )
+
+        // Verify that we cancelled the start animator in favor of the exit
+        // animator.
+        verify(mockStartAnimator, times(1)).cancel()
+        verify(mockExitAnimator, never()).cancel()
+    }
+}
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..73c226d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -36,10 +36,10 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dreams.complication.ComplicationHostViewController;
-import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
+import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 
 import org.junit.Before;
@@ -90,7 +90,13 @@
     ViewRootImpl mViewRoot;
 
     @Mock
-    BouncerCallbackInteractor mBouncerCallbackInteractor;
+    PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
+
+    @Mock
+    DreamOverlayAnimationsController mAnimationsController;
+
+    @Mock
+    DreamOverlayStateController mStateController;
 
     DreamOverlayContainerViewController mController;
 
@@ -100,7 +106,7 @@
 
         when(mDreamOverlayContainerView.getResources()).thenReturn(mResources);
         when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
-        when(mStatusBarKeyguardViewManager.getBouncer()).thenReturn(mBouncer);
+        when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(mBouncer);
         when(mDreamOverlayContainerView.getViewRootImpl()).thenReturn(mViewRoot);
 
         mController = new DreamOverlayContainerViewController(
@@ -115,7 +121,9 @@
                 MAX_BURN_IN_OFFSET,
                 BURN_IN_PROTECTION_UPDATE_INTERVAL,
                 MILLIS_UNTIL_FULL_JITTER,
-                mBouncerCallbackInteractor);
+                mPrimaryBouncerCallbackInteractor,
+                mAnimationsController,
+                mStateController);
     }
 
     @Test
@@ -159,8 +167,8 @@
 
     @Test
     public void testBouncerAnimation_doesNotApply() {
-        final ArgumentCaptor<BouncerExpansionCallback> bouncerExpansionCaptor =
-                ArgumentCaptor.forClass(BouncerExpansionCallback.class);
+        final ArgumentCaptor<PrimaryBouncerExpansionCallback> bouncerExpansionCaptor =
+                ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
         mController.onViewAttached();
         verify(mBouncer).addBouncerExpansionCallback(bouncerExpansionCaptor.capture());
 
@@ -170,8 +178,8 @@
 
     @Test
     public void testBouncerAnimation_updateBlur() {
-        final ArgumentCaptor<BouncerExpansionCallback> bouncerExpansionCaptor =
-                ArgumentCaptor.forClass(BouncerExpansionCallback.class);
+        final ArgumentCaptor<PrimaryBouncerExpansionCallback> bouncerExpansionCaptor =
+                ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
         mController.onViewAttached();
         verify(mBouncer).addBouncerExpansionCallback(bouncerExpansionCaptor.capture());
 
@@ -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()).cancelAnimations();
+    }
+
+    @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).cancelAnimations();
+    }
 }
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..ffb8342 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -253,6 +254,7 @@
         verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
         verify(mStateController).setOverlayActive(false);
         verify(mStateController).setLowLightActive(false);
+        verify(mStateController).setEntryAnimationsFinished(false);
     }
 
     @Test
@@ -273,27 +275,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);
@@ -332,4 +338,28 @@
         verify(mDreamOverlayComponent).getDreamOverlayContainerViewController();
         verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor();
     }
+
+    @Test
+    public void testWakeUp() throws RemoteException {
+        final IBinder proxy = mService.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+        // Inform the overlay service of dream starting.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                true /*shouldShowComplication*/);
+        mMainExecutor.runAllReady();
+
+        final Runnable callback = mock(Runnable.class);
+        mService.onWakeUp(callback);
+        mMainExecutor.runAllReady();
+        verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor);
+    }
+
+    @Test
+    public void testWakeUpBeforeStartDoesNothing() {
+        final Runnable callback = mock(Runnable.class);
+        mService.onWakeUp(callback);
+        mMainExecutor.runAllReady();
+        verify(mDreamOverlayContainerViewController, never()).wakeUp(callback, mMainExecutor);
+    }
 }
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/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
index 849ac5e..06a944e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -347,21 +347,21 @@
 
         addComplication(engine, thirdViewInfo);
 
-        // The first added view should now be underneath the second view.
+        // The first added view should now be underneath the third view.
         verifyChange(firstViewInfo, false, lp -> {
             assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue();
             assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
             assertThat(lp.topMargin).isEqualTo(margin);
         });
 
-        // The second view should be in underneath the third view.
+        // The second view should be to the start of the third view.
         verifyChange(secondViewInfo, false, lp -> {
             assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue();
             assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
             assertThat(lp.getMarginEnd()).isEqualTo(margin);
         });
 
-        // The third view should be in at the top.
+        // The third view should be at the top end corner. No margin should be applied.
         verifyChange(thirdViewInfo, true, lp -> {
             assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
             assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
@@ -425,14 +425,14 @@
 
         addComplication(engine, thirdViewInfo);
 
-        // The first added view should now be underneath the second view.
+        // The first added view should now be underneath the third view.
         verifyChange(firstViewInfo, false, lp -> {
             assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue();
             assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
             assertThat(lp.topMargin).isEqualTo(complicationMargin);
         });
 
-        // The second view should be in underneath the third view.
+        // The second view should be to the start of the third view.
         verifyChange(secondViewInfo, false, lp -> {
             assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue();
             assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
@@ -441,6 +441,133 @@
     }
 
     /**
+     * Ensures layout sets correct max width constraint.
+     */
+    @Test
+    public void testWidthConstraint() {
+        final int maxWidth = 20;
+        final ComplicationLayoutEngine engine =
+                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+
+        final ViewInfo viewStartDirection = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_START,
+                        0,
+                        5,
+                        maxWidth),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+        final ViewInfo viewEndDirection = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_START,
+                        ComplicationLayoutParams.DIRECTION_END,
+                        0,
+                        5,
+                        maxWidth),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        addComplication(engine, viewStartDirection);
+        addComplication(engine, viewEndDirection);
+
+        // Verify both horizontal direction views have max width set correctly, and max height is
+        // not set.
+        verifyChange(viewStartDirection, false, lp -> {
+            assertThat(lp.matchConstraintMaxWidth).isEqualTo(maxWidth);
+            assertThat(lp.matchConstraintMaxHeight).isEqualTo(0);
+        });
+        verifyChange(viewEndDirection, false, lp -> {
+            assertThat(lp.matchConstraintMaxWidth).isEqualTo(maxWidth);
+            assertThat(lp.matchConstraintMaxHeight).isEqualTo(0);
+        });
+    }
+
+    /**
+     * Ensures layout sets correct max height constraint.
+     */
+    @Test
+    public void testHeightConstraint() {
+        final int maxHeight = 20;
+        final ComplicationLayoutEngine engine =
+                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+
+        final ViewInfo viewUpDirection = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_BOTTOM
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_UP,
+                        0,
+                        5,
+                        maxHeight),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+        final ViewInfo viewDownDirection = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0,
+                        5,
+                        maxHeight),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        addComplication(engine, viewUpDirection);
+        addComplication(engine, viewDownDirection);
+
+        // Verify both vertical direction views have max height set correctly, and max width is
+        // not set.
+        verifyChange(viewUpDirection, false, lp -> {
+            assertThat(lp.matchConstraintMaxHeight).isEqualTo(maxHeight);
+            assertThat(lp.matchConstraintMaxWidth).isEqualTo(0);
+        });
+        verifyChange(viewDownDirection, false, lp -> {
+            assertThat(lp.matchConstraintMaxHeight).isEqualTo(maxHeight);
+            assertThat(lp.matchConstraintMaxWidth).isEqualTo(0);
+        });
+    }
+
+    /**
+     * Ensures layout does not set any constraint if not specified.
+     */
+    @Test
+    public void testConstraintNotSetWhenNotSpecified() {
+        final ComplicationLayoutEngine engine =
+                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+
+        final ViewInfo view = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0,
+                        5),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        addComplication(engine, view);
+
+        // Verify neither max height nor max width set.
+        verifyChange(view, false, lp -> {
+            assertThat(lp.matchConstraintMaxHeight).isEqualTo(0);
+            assertThat(lp.matchConstraintMaxWidth).isEqualTo(0);
+        });
+    }
+
+    /**
      * Ensures layout in a particular position updates.
      */
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
index cb7e47b..fdb4cc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
@@ -100,7 +100,7 @@
      * Ensures unspecified margin uses default.
      */
     @Test
-    public void testUnspecifiedMarginUsesDefault() {
+    public void testDefaultMargin() {
         final ComplicationLayoutParams params = new ComplicationLayoutParams(
                 100,
                 100,
@@ -136,13 +136,15 @@
                 ComplicationLayoutParams.POSITION_TOP,
                 ComplicationLayoutParams.DIRECTION_DOWN,
                 3,
-                10);
+                10,
+                20);
         final ComplicationLayoutParams copy = new ComplicationLayoutParams(params);
 
         assertThat(copy.getDirection() == params.getDirection()).isTrue();
         assertThat(copy.getPosition() == params.getPosition()).isTrue();
         assertThat(copy.getWeight() == params.getWeight()).isTrue();
         assertThat(copy.getMargin(0) == params.getMargin(1)).isTrue();
+        assertThat(copy.getConstraint() == params.getConstraint()).isTrue();
         assertThat(copy.height == params.height).isTrue();
         assertThat(copy.width == params.width).isTrue();
     }
@@ -168,4 +170,31 @@
         assertThat(copy.height == params.height).isTrue();
         assertThat(copy.width == params.width).isTrue();
     }
+
+    /**
+     * Ensures that constraint is set correctly.
+     */
+    @Test
+    public void testConstraint() {
+        final ComplicationLayoutParams paramsWithoutConstraint = new ComplicationLayoutParams(
+                100,
+                100,
+                ComplicationLayoutParams.POSITION_TOP,
+                ComplicationLayoutParams.DIRECTION_DOWN,
+                3,
+                10);
+        assertThat(paramsWithoutConstraint.constraintSpecified()).isFalse();
+
+        final int constraint = 10;
+        final ComplicationLayoutParams paramsWithConstraint = new ComplicationLayoutParams(
+                100,
+                100,
+                ComplicationLayoutParams.POSITION_TOP,
+                ComplicationLayoutParams.DIRECTION_DOWN,
+                3,
+                10,
+                constraint);
+        assertThat(paramsWithConstraint.constraintSpecified()).isTrue();
+        assertThat(paramsWithConstraint.getConstraint()).isEqualTo(constraint);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index aa8c93e..e6d3a69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -35,6 +35,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.controls.ControlsServiceInfo;
 import com.android.systemui.controls.controller.ControlsController;
@@ -84,13 +85,19 @@
     private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor;
 
     @Mock
-    private ImageView mView;
+    private View mView;
+
+    @Mock
+    private ImageView mHomeControlsView;
 
     @Mock
     private ActivityStarter mActivityStarter;
 
     @Mock
-    UiEventLogger mUiEventLogger;
+    private UiEventLogger mUiEventLogger;
+
+    @Captor
+    private ArgumentCaptor<DreamOverlayStateController.Callback> mStateCallbackCaptor;
 
     @Before
     public void setup() {
@@ -102,6 +109,7 @@
         when(mControlsComponent.getControlsListingController()).thenReturn(
                 Optional.of(mControlsListingController));
         when(mControlsComponent.getVisibility()).thenReturn(AVAILABLE);
+        when(mView.findViewById(R.id.home_controls_chip)).thenReturn(mHomeControlsView);
     }
 
     @Test
@@ -164,6 +172,29 @@
         verify(mDreamOverlayStateController).addComplication(mComplication);
     }
 
+    @Test
+    public void complicationAvailability_checkAvailabilityWhenDreamOverlayBecomesActive() {
+        final DreamHomeControlsComplication.Registrant registrant =
+                new DreamHomeControlsComplication.Registrant(mComplication,
+                        mDreamOverlayStateController, mControlsComponent);
+        registrant.start();
+
+        setServiceAvailable(true);
+        setHaveFavorites(false);
+
+        // Complication not available on start.
+        verify(mDreamOverlayStateController, never()).addComplication(mComplication);
+
+        // Favorite controls added, complication should be available now.
+        setHaveFavorites(true);
+
+        // Dream overlay becomes active.
+        setDreamOverlayActive(true);
+
+        // Verify complication is added.
+        verify(mDreamOverlayStateController).addComplication(mComplication);
+    }
+
     /**
      * Ensures clicking home controls chip logs UiEvent.
      */
@@ -180,9 +211,9 @@
 
         final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
                 ArgumentCaptor.forClass(View.OnClickListener.class);
-        verify(mView).setOnClickListener(clickListenerCaptor.capture());
+        verify(mHomeControlsView).setOnClickListener(clickListenerCaptor.capture());
 
-        clickListenerCaptor.getValue().onClick(mView);
+        clickListenerCaptor.getValue().onClick(mHomeControlsView);
         verify(mUiEventLogger).log(
                 DreamHomeControlsComplication.DreamHomeControlsChipViewController
                         .DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
@@ -196,10 +227,17 @@
 
     private void setServiceAvailable(boolean value) {
         final List<ControlsServiceInfo> serviceInfos = mock(List.class);
+        when(mControlsListingController.getCurrentServices()).thenReturn(serviceInfos);
         when(serviceInfos.isEmpty()).thenReturn(!value);
         triggerControlsListingCallback(serviceInfos);
     }
 
+    private void setDreamOverlayActive(boolean value) {
+        when(mDreamOverlayStateController.isOverlayActive()).thenReturn(value);
+        verify(mDreamOverlayStateController).addCallback(mStateCallbackCaptor.capture());
+        mStateCallbackCaptor.getValue().onStateChanged();
+    }
+
     private void triggerControlsListingCallback(List<ControlsServiceInfo> serviceInfos) {
         verify(mControlsListingController).addCallback(mCallbackCaptor.capture());
         mCallbackCaptor.getValue().onServicesUpdated(serviceInfos);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
index 14a5702..4e3aca7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.dreams.touch;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
@@ -33,6 +31,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.Complication;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -52,6 +51,7 @@
 @RunWith(AndroidTestingRunner.class)
 public class HideComplicationTouchHandlerTest extends SysuiTestCase {
     private static final int RESTORE_TIMEOUT = 1000;
+    private static final int HIDE_DELAY = 500;
 
     @Mock
     Complication.VisibilityController mVisibilityController;
@@ -71,11 +71,18 @@
     @Mock
     DreamTouchHandler.TouchSession mSession;
 
-    FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+    @Mock
+    DreamOverlayStateController mStateController;
+
+    FakeSystemClock mClock;
+
+    FakeExecutor mFakeExecutor;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mClock = new FakeSystemClock();
+        mFakeExecutor = new FakeExecutor(mClock);
     }
 
     /**
@@ -86,10 +93,11 @@
         final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
                 mVisibilityController,
                 RESTORE_TIMEOUT,
+                HIDE_DELAY,
                 mTouchInsetManager,
                 mStatusBarKeyguardViewManager,
                 mFakeExecutor,
-                mHandler);
+                mStateController);
 
         // Report multiple active sessions.
         when(mSession.getActiveSessionCount()).thenReturn(2);
@@ -103,8 +111,10 @@
         // Verify session end.
         verify(mSession).pop();
 
+        mClock.advanceTime(HIDE_DELAY);
+
         // Verify no interaction with visibility controller.
-        verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+        verify(mVisibilityController, never()).setVisibility(anyInt());
     }
 
     /**
@@ -115,10 +125,11 @@
         final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
                 mVisibilityController,
                 RESTORE_TIMEOUT,
+                HIDE_DELAY,
                 mTouchInsetManager,
                 mStatusBarKeyguardViewManager,
                 mFakeExecutor,
-                mHandler);
+                mStateController);
 
         // Report one session.
         when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -132,8 +143,10 @@
         // Verify session end.
         verify(mSession).pop();
 
+        mClock.advanceTime(HIDE_DELAY);
+
         // Verify no interaction with visibility controller.
-        verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+        verify(mVisibilityController, never()).setVisibility(anyInt());
     }
 
     /**
@@ -144,10 +157,11 @@
         final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
                 mVisibilityController,
                 RESTORE_TIMEOUT,
+                HIDE_DELAY,
                 mTouchInsetManager,
                 mStatusBarKeyguardViewManager,
                 mFakeExecutor,
-                mHandler);
+                mStateController);
 
         // Report one session
         when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -177,8 +191,10 @@
         // Verify session ended.
         verify(mSession).pop();
 
+        mClock.advanceTime(HIDE_DELAY);
+
         // Verify no interaction with visibility controller.
-        verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+        verify(mVisibilityController, never()).setVisibility(anyInt());
     }
 
     /**
@@ -189,10 +205,11 @@
         final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
                 mVisibilityController,
                 RESTORE_TIMEOUT,
+                HIDE_DELAY,
                 mTouchInsetManager,
                 mStatusBarKeyguardViewManager,
                 mFakeExecutor,
-                mHandler);
+                mStateController);
 
         // Report one session
         when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -221,11 +238,11 @@
         inputEventListenerCaptor.getValue().onInputEvent(mMotionEvent);
         mFakeExecutor.runAllReady();
 
-        // Verify callback to restore visibility cancelled.
-        verify(mHandler).removeCallbacks(any());
-
+        // Verify visibility controller doesn't hide until after timeout
+        verify(mVisibilityController, never()).setVisibility(eq(View.INVISIBLE));
+        mClock.advanceTime(HIDE_DELAY);
         // Verify visibility controller told to hide complications.
-        verify(mVisibilityController).setVisibility(eq(View.INVISIBLE), anyBoolean());
+        verify(mVisibilityController).setVisibility(eq(View.INVISIBLE));
 
         Mockito.clearInvocations(mVisibilityController, mHandler);
 
@@ -235,11 +252,8 @@
         mFakeExecutor.runAllReady();
 
         // Verify visibility controller told to show complications.
-        ArgumentCaptor<Runnable> delayRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
-        verify(mHandler).postDelayed(delayRunnableCaptor.capture(),
-                eq(Long.valueOf(RESTORE_TIMEOUT)));
-        delayRunnableCaptor.getValue().run();
-        verify(mVisibilityController).setVisibility(eq(View.VISIBLE), anyBoolean());
+        mClock.advanceTime(RESTORE_TIMEOUT);
+        verify(mVisibilityController).setVisibility(eq(View.VISIBLE));
 
         // Verify session ended.
         verify(mSession).pop();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
index 65ae90b..19135d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -103,8 +103,8 @@
         // THEN only the requested ones have their dump() method called
         verify(dumpable1).dump(pw, args)
         verify(dumpable2, never()).dump(
-                any(PrintWriter::class.java),
-                any(Array<String>::class.java))
+            any(PrintWriter::class.java),
+            any(Array<String>::class.java))
         verify(dumpable3).dump(pw, args)
         verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
         verify(buffer2).dump(pw, 0)
@@ -126,9 +126,9 @@
     @Test
     fun testCriticalDump() {
         // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerDumpable("dumpable1", dumpable1)
-        dumpManager.registerDumpable("dumpable2", dumpable2)
-        dumpManager.registerDumpable("dumpable3", dumpable3)
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
         dumpManager.registerBuffer("buffer1", buffer1)
         dumpManager.registerBuffer("buffer2", buffer2)
 
@@ -136,10 +136,12 @@
         val args = arrayOf("--dump-priority", "CRITICAL")
         dumpHandler.dump(fd, pw, args)
 
-        // THEN all modules are dumped (but no buffers)
+        // THEN only critical modules are dumped (and no buffers)
         verify(dumpable1).dump(pw, args)
         verify(dumpable2).dump(pw, args)
-        verify(dumpable3).dump(pw, args)
+        verify(dumpable3, never()).dump(
+            any(PrintWriter::class.java),
+            any(Array<String>::class.java))
         verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
         verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
     }
@@ -147,9 +149,9 @@
     @Test
     fun testNormalDump() {
         // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerDumpable("dumpable1", dumpable1)
-        dumpManager.registerDumpable("dumpable2", dumpable2)
-        dumpManager.registerDumpable("dumpable3", dumpable3)
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
         dumpManager.registerBuffer("buffer1", buffer1)
         dumpManager.registerBuffer("buffer2", buffer2)
 
@@ -157,16 +159,14 @@
         val args = arrayOf("--dump-priority", "NORMAL")
         dumpHandler.dump(fd, pw, args)
 
-        // THEN all buffers are dumped (but no modules)
+        // THEN the normal module and all buffers are dumped
         verify(dumpable1, never()).dump(
                 any(PrintWriter::class.java),
                 any(Array<String>::class.java))
         verify(dumpable2, never()).dump(
                 any(PrintWriter::class.java),
                 any(Array<String>::class.java))
-        verify(dumpable3, never()).dump(
-                any(PrintWriter::class.java),
-                any(Array<String>::class.java))
+        verify(dumpable3).dump(pw, args)
         verify(buffer1).dump(pw, 0)
         verify(buffer2).dump(pw, 0)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
new file mode 100644
index 0000000..0c5a74c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
@@ -0,0 +1,221 @@
+/*
+ * 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.dump
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.Dumpable
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.util.mockito.any
+import java.io.PrintWriter
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class DumpManagerTest : SysuiTestCase() {
+
+    @Mock private lateinit var pw: PrintWriter
+
+    @Mock private lateinit var dumpable1: Dumpable
+    @Mock private lateinit var dumpable2: Dumpable
+    @Mock private lateinit var dumpable3: Dumpable
+
+    @Mock private lateinit var buffer1: LogBuffer
+    @Mock private lateinit var buffer2: LogBuffer
+
+    private val dumpManager = DumpManager()
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun testDumpTarget_dumpable() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+
+        // WHEN a dumpable is dumped explicitly
+        val args = arrayOf<String>()
+        dumpManager.dumpTarget("dumpable2", pw, arrayOf(), tailLength = 0)
+
+        // THEN only the requested one has their dump() method called
+        verify(dumpable1, never())
+            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(dumpable2).dump(pw, args)
+        verify(dumpable3, never())
+            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
+        verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+    }
+
+    @Test
+    fun testDumpTarget_buffer() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+
+        // WHEN a buffer is dumped explicitly
+        dumpManager.dumpTarget("buffer1", pw, arrayOf(), tailLength = 14)
+
+        // THEN only the requested one has their dump() method called
+        verify(dumpable1, never())
+            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(dumpable2, never())
+            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(dumpable2, never())
+            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(buffer1).dump(pw, tailLength = 14)
+        verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+    }
+
+    @Test
+    fun testDumpableMatchingIsBasedOnEndOfTag() {
+        // GIVEN a dumpable registered to the manager
+        dumpManager.registerCriticalDumpable("com.android.foo.bar.dumpable1", dumpable1)
+
+        // WHEN that module is dumped
+        val args = arrayOf<String>()
+        dumpManager.dumpTarget("dumpable1", pw, arrayOf(), tailLength = 14)
+
+        // THEN its dump() method is called
+        verify(dumpable1).dump(pw, args)
+    }
+
+    @Test
+    fun testDumpDumpables() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+
+        // WHEN a dumpable dump is requested
+        val args = arrayOf<String>()
+        dumpManager.dumpDumpables(pw, args)
+
+        // THEN all dumpables are dumped (both critical and normal) (and no dumpables)
+        verify(dumpable1).dump(pw, args)
+        verify(dumpable2).dump(pw, args)
+        verify(dumpable3).dump(pw, args)
+        verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
+        verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+    }
+
+    @Test
+    fun testDumpBuffers() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+
+        // WHEN a buffer dump is requested
+        dumpManager.dumpBuffers(pw, tailLength = 1)
+
+        // THEN all buffers are dumped (and no dumpables)
+        verify(dumpable1, never())
+            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(dumpable2, never())
+            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(dumpable3, never())
+            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(buffer1).dump(pw, tailLength = 1)
+        verify(buffer2).dump(pw, tailLength = 1)
+    }
+
+    @Test
+    fun testCriticalDump() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+
+        // WHEN a critical dump is requested
+        val args = arrayOf<String>()
+        dumpManager.dumpCritical(pw, args)
+
+        // THEN only critical modules are dumped (and no buffers)
+        verify(dumpable1).dump(pw, args)
+        verify(dumpable2).dump(pw, args)
+        verify(dumpable3, never())
+            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
+        verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+    }
+
+    @Test
+    fun testNormalDump() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+
+        // WHEN a normal dump is requested
+        val args = arrayOf<String>()
+        dumpManager.dumpNormal(pw, args, tailLength = 2)
+
+        // THEN the normal module and all buffers are dumped
+        verify(dumpable1, never())
+            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(dumpable2, never())
+            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(dumpable3).dump(pw, args)
+        verify(buffer1).dump(pw, tailLength = 2)
+        verify(buffer2).dump(pw, tailLength = 2)
+    }
+
+    @Test
+    fun testUnregister() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+
+        dumpManager.unregisterDumpable("dumpable2")
+        dumpManager.unregisterDumpable("dumpable3")
+
+        // WHEN a dumpables dump is requested
+        val args = arrayOf<String>()
+        dumpManager.dumpDumpables(pw, args)
+
+        // THEN the unregistered dumpables (both normal and critical) are not dumped
+        verify(dumpable1).dump(pw, args)
+        verify(dumpable2, never())
+            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(dumpable3, never())
+            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
index 318f2bc..170a70f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -20,7 +20,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
-import java.lang.IllegalStateException
 import org.junit.Assert.fail
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -29,12 +28,12 @@
 @RunWith(AndroidTestingRunner::class)
 class FakeFeatureFlagsTest : SysuiTestCase() {
 
-    private val unreleasedFlag = UnreleasedFlag(-1000)
-    private val releasedFlag = ReleasedFlag(-1001)
-    private val stringFlag = StringFlag(-1002)
-    private val resourceBooleanFlag = ResourceBooleanFlag(-1003, resourceId = -1)
-    private val resourceStringFlag = ResourceStringFlag(-1004, resourceId = -1)
-    private val sysPropBooleanFlag = SysPropBooleanFlag(-1005, name = "test")
+    private val unreleasedFlag = UnreleasedFlag(-1000, "-1000", "test")
+    private val releasedFlag = ReleasedFlag(-1001, "-1001", "test")
+    private val stringFlag = StringFlag(-1002, "-1002", "test")
+    private val resourceBooleanFlag = ResourceBooleanFlag(-1003, "-1003", "test", resourceId = -1)
+    private val resourceStringFlag = ResourceStringFlag(-1004, "-1004", "test", resourceId = -1)
+    private val sysPropBooleanFlag = SysPropBooleanFlag(-1005, "test", "test")
 
     /**
      * FakeFeatureFlags does not honor any default values. All flags which are accessed must be
@@ -47,7 +46,7 @@
             assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("TEAMFOOD")
+            assertThat(ex.message).contains("id=1")
         }
         try {
             assertThat(flags.isEnabled(unreleasedFlag)).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
new file mode 100644
index 0000000..ed16721
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class FeatureFlagsDebugRestarterTest : SysuiTestCase() {
+    private lateinit var restarter: FeatureFlagsDebugRestarter
+
+    @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        restarter = FeatureFlagsDebugRestarter(wakefulnessLifecycle, systemExitRestarter)
+    }
+
+    @Test
+    fun testRestart_ImmediateWhenAsleep() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+        restarter.restartSystemUI()
+        verify(systemExitRestarter).restartSystemUI()
+    }
+
+    @Test
+    fun testRestart_WaitsForSceenOff() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+
+        restarter.restartSystemUI()
+        verify(systemExitRestarter, never()).restartSystemUI()
+
+        val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
+        verify(wakefulnessLifecycle).addObserver(captor.capture())
+
+        captor.value.onFinishedGoingToSleep()
+
+        verify(systemExitRestarter).restartSystemUI()
+    }
+}
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 4b3b70e..7592cc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -20,6 +20,7 @@
 import android.content.Intent
 import android.content.pm.PackageManager.NameNotFoundException
 import android.content.res.Resources
+import android.content.res.Resources.NotFoundException
 import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -30,10 +31,6 @@
 import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
-import java.io.PrintWriter
-import java.io.Serializable
-import java.io.StringWriter
-import java.util.function.Consumer
 import org.junit.Assert
 import org.junit.Before
 import org.junit.Test
@@ -45,8 +42,12 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import java.io.PrintWriter
+import java.io.Serializable
+import java.io.StringWriter
+import java.util.function.Consumer
+import org.mockito.Mockito.`when` as whenever
 
 /**
  * NOTE: This test is for the version of FeatureFlagManager in src-debug, which allows overriding
@@ -56,21 +57,32 @@
 class FeatureFlagsDebugTest : SysuiTestCase() {
     private lateinit var mFeatureFlagsDebug: FeatureFlagsDebug
 
-    @Mock private lateinit var flagManager: FlagManager
-    @Mock private lateinit var mockContext: Context
-    @Mock private lateinit var secureSettings: SecureSettings
-    @Mock private lateinit var systemProperties: SystemPropertiesHelper
-    @Mock private lateinit var resources: Resources
-    @Mock private lateinit var commandRegistry: CommandRegistry
-    @Mock private lateinit var restarter: Restarter
+    @Mock
+    private lateinit var flagManager: FlagManager
+    @Mock
+    private lateinit var mockContext: Context
+    @Mock
+    private lateinit var secureSettings: SecureSettings
+    @Mock
+    private lateinit var systemProperties: SystemPropertiesHelper
+    @Mock
+    private lateinit var resources: Resources
+    @Mock
+    private lateinit var commandRegistry: CommandRegistry
+    @Mock
+    private lateinit var restarter: Restarter
     private val flagMap = mutableMapOf<Int, Flag<*>>()
     private lateinit var broadcastReceiver: BroadcastReceiver
     private lateinit var clearCacheAction: Consumer<Int>
     private val serverFlagReader = ServerFlagReaderFake()
 
     private val deviceConfig = DeviceConfigProxyFake()
-    private val teamfoodableFlagA = UnreleasedFlag(500, true)
-    private val teamfoodableFlagB = ReleasedFlag(501, true)
+    private val teamfoodableFlagA = UnreleasedFlag(
+        500, name = "a", namespace = "test", teamfood = true
+    )
+    private val teamfoodableFlagB = ReleasedFlag(
+        501, name = "b", namespace = "test", teamfood = true
+    )
 
     @Before
     fun setup() {
@@ -83,7 +95,6 @@
             secureSettings,
             systemProperties,
             resources,
-            deviceConfig,
             serverFlagReader,
             flagMap,
             restarter
@@ -91,8 +102,10 @@
         mFeatureFlagsDebug.init()
         verify(flagManager).onSettingsChangedAction = any()
         broadcastReceiver = withArgCaptor {
-            verify(mockContext).registerReceiver(capture(), any(), nullable(), nullable(),
-                any())
+            verify(mockContext).registerReceiver(
+                capture(), any(), nullable(), nullable(),
+                any()
+            )
         }
         clearCacheAction = withArgCaptor {
             verify(flagManager).clearCacheAction = capture()
@@ -106,10 +119,42 @@
         whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
         whenever(flagManager.readFlagValue<Boolean>(eq(4), any())).thenReturn(false)
 
-        assertThat(mFeatureFlagsDebug.isEnabled(ReleasedFlag(2))).isTrue()
-        assertThat(mFeatureFlagsDebug.isEnabled(UnreleasedFlag(3))).isTrue()
-        assertThat(mFeatureFlagsDebug.isEnabled(ReleasedFlag(4))).isFalse()
-        assertThat(mFeatureFlagsDebug.isEnabled(UnreleasedFlag(5))).isFalse()
+        assertThat(
+            mFeatureFlagsDebug.isEnabled(
+                ReleasedFlag(
+                    2,
+                    name = "2",
+                    namespace = "test"
+                )
+            )
+        ).isTrue()
+        assertThat(
+            mFeatureFlagsDebug.isEnabled(
+                UnreleasedFlag(
+                    3,
+                    name = "3",
+                    namespace = "test"
+                )
+            )
+        ).isTrue()
+        assertThat(
+            mFeatureFlagsDebug.isEnabled(
+                ReleasedFlag(
+                    4,
+                    name = "3",
+                    namespace = "test"
+                )
+            )
+        ).isFalse()
+        assertThat(
+            mFeatureFlagsDebug.isEnabled(
+                UnreleasedFlag(
+                    5,
+                    name = "4",
+                    namespace = "test"
+                )
+            )
+        ).isFalse()
     }
 
     @Test
@@ -137,9 +182,9 @@
     @Test
     fun teamFoodFlag_Overridden() {
         whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.id), any()))
-                .thenReturn(true)
+            .thenReturn(true)
         whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.id), any()))
-                .thenReturn(false)
+            .thenReturn(false)
         whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true)
         assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
         assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse()
@@ -160,17 +205,26 @@
         whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
         whenever(flagManager.readFlagValue<Boolean>(eq(5), any())).thenReturn(false)
 
-        assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(1, 1001))).isFalse()
-        assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(2, 1002))).isTrue()
-        assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(3, 1003))).isTrue()
+        assertThat(
+            mFeatureFlagsDebug.isEnabled(
+                ResourceBooleanFlag(
+                    1,
+                    "1",
+                    "test",
+                    1001
+                )
+            )
+        ).isFalse()
+        assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(2, "2", "test", 1002))).isTrue()
+        assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(3, "3", "test", 1003))).isTrue()
 
         Assert.assertThrows(NameNotFoundException::class.java) {
-            mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(4, 1004))
+            mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(4, "4", "test", 1004))
         }
         // Test that resource is loaded (and validated) even when the setting is set.
         //  This prevents developers from not noticing when they reference an invalid resource.
         Assert.assertThrows(NameNotFoundException::class.java) {
-            mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(5, 1005))
+            mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(5, "5", "test", 1005))
         }
     }
 
@@ -183,36 +237,30 @@
             return@thenAnswer it.getArgument(1)
         }
 
-        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(1, "a"))).isFalse()
-        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(2, "b"))).isTrue()
-        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(3, "c", true))).isTrue()
-        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(4, "d", false))).isFalse()
-        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e"))).isFalse()
-    }
-
-    @Test
-    fun readDeviceConfigBooleanFlag() {
-        val namespace = "test_namespace"
-        deviceConfig.setProperty(namespace, "a", "true", false)
-        deviceConfig.setProperty(namespace, "b", "false", false)
-        deviceConfig.setProperty(namespace, "c", null, false)
-
-        assertThat(mFeatureFlagsDebug.isEnabled(DeviceConfigBooleanFlag(1, "a", namespace)))
-            .isTrue()
-        assertThat(mFeatureFlagsDebug.isEnabled(DeviceConfigBooleanFlag(2, "b", namespace)))
-            .isFalse()
-        assertThat(mFeatureFlagsDebug.isEnabled(DeviceConfigBooleanFlag(3, "c", namespace)))
-            .isFalse()
+        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(1, "a", "test"))).isFalse()
+        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(2, "b", "test"))).isTrue()
+        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(3, "c", "test", true))).isTrue()
+        assertThat(
+            mFeatureFlagsDebug.isEnabled(
+                SysPropBooleanFlag(
+                    4,
+                    "d",
+                    "test",
+                    false
+                )
+            )
+        ).isFalse()
+        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e", "test"))).isFalse()
     }
 
     @Test
     fun readStringFlag() {
         whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo")
         whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("bar")
-        assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "biz"))).isEqualTo("biz")
-        assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "baz"))).isEqualTo("baz")
-        assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "buz"))).isEqualTo("foo")
-        assertThat(mFeatureFlagsDebug.getString(StringFlag(4, "buz"))).isEqualTo("bar")
+        assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz")
+        assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz")
+        assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo")
+        assertThat(mFeatureFlagsDebug.getString(StringFlag(4, "4", "test", "buz"))).isEqualTo("bar")
     }
 
     @Test
@@ -228,29 +276,93 @@
         whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("override4")
         whenever(flagManager.readFlagValue<String>(eq(6), any())).thenReturn("override6")
 
-        assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(1, 1001))).isEqualTo("")
-        assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(2, 1002))).isEqualTo("resource2")
-        assertThat(mFeatureFlagsDebug.getString(ResourceStringFlag(3, 1003))).isEqualTo("override3")
+        assertThat(
+            mFeatureFlagsDebug.getString(
+                ResourceStringFlag(
+                    1,
+                    "1",
+                    "test",
+                    1001
+                )
+            )
+        ).isEqualTo("")
+        assertThat(
+            mFeatureFlagsDebug.getString(
+                ResourceStringFlag(
+                    2,
+                    "2",
+                    "test",
+                    1002
+                )
+            )
+        ).isEqualTo("resource2")
+        assertThat(
+            mFeatureFlagsDebug.getString(
+                ResourceStringFlag(
+                    3,
+                    "3",
+                    "test",
+                    1003
+                )
+            )
+        ).isEqualTo("override3")
 
         Assert.assertThrows(NullPointerException::class.java) {
-            mFeatureFlagsDebug.getString(ResourceStringFlag(4, 1004))
+            mFeatureFlagsDebug.getString(ResourceStringFlag(4, "4", "test", 1004))
         }
         Assert.assertThrows(NameNotFoundException::class.java) {
-            mFeatureFlagsDebug.getString(ResourceStringFlag(5, 1005))
+            mFeatureFlagsDebug.getString(ResourceStringFlag(5, "5", "test", 1005))
         }
         // Test that resource is loaded (and validated) even when the setting is set.
         //  This prevents developers from not noticing when they reference an invalid resource.
         Assert.assertThrows(NameNotFoundException::class.java) {
-            mFeatureFlagsDebug.getString(ResourceStringFlag(6, 1005))
+            mFeatureFlagsDebug.getString(ResourceStringFlag(6, "6", "test", 1005))
+        }
+    }
+
+    @Test
+    fun readIntFlag() {
+        whenever(flagManager.readFlagValue<Int>(eq(3), any())).thenReturn(22)
+        whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(48)
+        assertThat(mFeatureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12)
+        assertThat(mFeatureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93)
+        assertThat(mFeatureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22)
+        assertThat(mFeatureFlagsDebug.getInt(IntFlag(4, "4", "test", 234))).isEqualTo(48)
+    }
+
+    @Test
+    fun readResourceIntFlag() {
+        whenever(resources.getInteger(1001)).thenReturn(88)
+        whenever(resources.getInteger(1002)).thenReturn(61)
+        whenever(resources.getInteger(1003)).thenReturn(9342)
+        whenever(resources.getInteger(1004)).thenThrow(NotFoundException("unknown resource"))
+        whenever(resources.getInteger(1005)).thenThrow(NotFoundException("unknown resource"))
+        whenever(resources.getInteger(1006)).thenThrow(NotFoundException("unknown resource"))
+
+        whenever(flagManager.readFlagValue<Int>(eq(3), any())).thenReturn(20)
+        whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(500)
+        whenever(flagManager.readFlagValue<Int>(eq(5), any())).thenReturn(9519)
+
+        assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(1, "1", "test", 1001))).isEqualTo(88)
+        assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(2, "2", "test", 1002))).isEqualTo(61)
+        assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(3, "3", "test", 1003))).isEqualTo(20)
+
+        Assert.assertThrows(NotFoundException::class.java) {
+            mFeatureFlagsDebug.getInt(ResourceIntFlag(4, "4", "test", 1004))
+        }
+        // Test that resource is loaded (and validated) even when the setting is set.
+        //  This prevents developers from not noticing when they reference an invalid resource.
+        Assert.assertThrows(NotFoundException::class.java) {
+            mFeatureFlagsDebug.getInt(ResourceIntFlag(5, "5", "test", 1005))
         }
     }
 
     @Test
     fun broadcastReceiver_IgnoresInvalidData() {
-        addFlag(UnreleasedFlag(1))
-        addFlag(ResourceBooleanFlag(2, 1002))
-        addFlag(StringFlag(3, "flag3"))
-        addFlag(ResourceStringFlag(4, 1004))
+        addFlag(UnreleasedFlag(1, "1", "test"))
+        addFlag(ResourceBooleanFlag(2, "2", "test", 1002))
+        addFlag(StringFlag(3, "3", "test", "flag3"))
+        addFlag(ResourceStringFlag(4, "4", "test", 1004))
 
         broadcastReceiver.onReceive(mockContext, null)
         broadcastReceiver.onReceive(mockContext, Intent())
@@ -266,7 +378,7 @@
 
     @Test
     fun intentWithId_NoValueKeyClears() {
-        addFlag(UnreleasedFlag(1))
+        addFlag(UnreleasedFlag(1, name = "1", namespace = "test"))
 
         // trying to erase an id not in the map does nothing
         broadcastReceiver.onReceive(
@@ -285,10 +397,10 @@
 
     @Test
     fun setBooleanFlag() {
-        addFlag(UnreleasedFlag(1))
-        addFlag(UnreleasedFlag(2))
-        addFlag(ResourceBooleanFlag(3, 1003))
-        addFlag(ResourceBooleanFlag(4, 1004))
+        addFlag(UnreleasedFlag(1, "1", "test"))
+        addFlag(UnreleasedFlag(2, "2", "test"))
+        addFlag(ResourceBooleanFlag(3, "3", "test", 1003))
+        addFlag(ResourceBooleanFlag(4, "4", "test", 1004))
 
         setByBroadcast(1, false)
         verifyPutData(1, "{\"type\":\"boolean\",\"value\":false}")
@@ -305,8 +417,8 @@
 
     @Test
     fun setStringFlag() {
-        addFlag(StringFlag(1, "flag1"))
-        addFlag(ResourceStringFlag(2, 1002))
+        addFlag(StringFlag(1, "flag1", "1", "test"))
+        addFlag(ResourceStringFlag(2, "2", "test", 1002))
 
         setByBroadcast(1, "override1")
         verifyPutData(1, "{\"type\":\"string\",\"value\":\"override1\"}")
@@ -317,7 +429,7 @@
 
     @Test
     fun setFlag_ClearsCache() {
-        val flag1 = addFlag(StringFlag(1, "flag1"))
+        val flag1 = addFlag(StringFlag(1, "1", "test", "flag1"))
         whenever(flagManager.readFlagValue<String>(eq(1), any())).thenReturn("original")
 
         // gets the flag & cache it
@@ -339,31 +451,31 @@
 
     @Test
     fun serverSide_Overrides_MakesFalse() {
-        val flag = ReleasedFlag(100)
+        val flag = ReleasedFlag(100, "100", "test")
 
-        serverFlagReader.setFlagValue(flag.id, false)
+        serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
 
         assertThat(mFeatureFlagsDebug.isEnabled(flag)).isFalse()
     }
 
     @Test
     fun serverSide_Overrides_MakesTrue() {
-        val flag = UnreleasedFlag(100)
+        val flag = UnreleasedFlag(100, name = "100", namespace = "test")
 
-        serverFlagReader.setFlagValue(flag.id, true)
+        serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
 
         assertThat(mFeatureFlagsDebug.isEnabled(flag)).isTrue()
     }
 
     @Test
     fun dumpFormat() {
-        val flag1 = ReleasedFlag(1)
-        val flag2 = ResourceBooleanFlag(2, 1002)
-        val flag3 = UnreleasedFlag(3)
-        val flag4 = StringFlag(4, "")
-        val flag5 = StringFlag(5, "flag5default")
-        val flag6 = ResourceStringFlag(6, 1006)
-        val flag7 = ResourceStringFlag(7, 1007)
+        val flag1 = ReleasedFlag(1, "1", "test")
+        val flag2 = ResourceBooleanFlag(2, "2", "test", 1002)
+        val flag3 = UnreleasedFlag(3, "3", "test")
+        val flag4 = StringFlag(4, "4", "test", "")
+        val flag5 = StringFlag(5, "5", "test", "flag5default")
+        val flag6 = ResourceStringFlag(6, "6", "test", 1006)
+        val flag7 = ResourceStringFlag(7, "7", "test", 1007)
 
         whenever(resources.getBoolean(1002)).thenReturn(true)
         whenever(resources.getString(1006)).thenReturn("resource1006")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
new file mode 100644
index 0000000..7d807e2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import com.android.systemui.statusbar.policy.BatteryController
+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.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
+    private lateinit var restarter: FeatureFlagsReleaseRestarter
+
+    @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock private lateinit var batteryController: BatteryController
+    @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+    private val executor = FakeExecutor(FakeSystemClock())
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        restarter =
+            FeatureFlagsReleaseRestarter(
+                wakefulnessLifecycle,
+                batteryController,
+                executor,
+                systemExitRestarter
+            )
+    }
+
+    @Test
+    fun testRestart_ScheduledWhenReady() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+
+        assertThat(executor.numPending()).isEqualTo(0)
+        restarter.restartSystemUI()
+        assertThat(executor.numPending()).isEqualTo(1)
+    }
+
+    @Test
+    fun testRestart_RestartsWhenIdle() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+
+        restarter.restartSystemUI()
+        verify(systemExitRestarter, never()).restartSystemUI()
+        executor.advanceClockToLast()
+        executor.runAllReady()
+        verify(systemExitRestarter).restartSystemUI()
+    }
+
+    @Test
+    fun testRestart_NotScheduledWhenAwake() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+
+        assertThat(executor.numPending()).isEqualTo(0)
+        restarter.restartSystemUI()
+        assertThat(executor.numPending()).isEqualTo(0)
+    }
+
+    @Test
+    fun testRestart_NotScheduledWhenNotPluggedIn() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+        whenever(batteryController.isPluggedIn).thenReturn(false)
+
+        assertThat(executor.numPending()).isEqualTo(0)
+        restarter.restartSystemUI()
+        assertThat(executor.numPending()).isEqualTo(0)
+    }
+
+    @Test
+    fun testRestart_NotDoubleSheduled() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+
+        assertThat(executor.numPending()).isEqualTo(0)
+        restarter.restartSystemUI()
+        restarter.restartSystemUI()
+        assertThat(executor.numPending()).isEqualTo(1)
+    }
+
+    @Test
+    fun testWakefulnessLifecycle_CanRestart() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+        assertThat(executor.numPending()).isEqualTo(0)
+        restarter.restartSystemUI()
+
+        val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
+        verify(wakefulnessLifecycle).addObserver(captor.capture())
+
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+
+        captor.value.onFinishedGoingToSleep()
+        assertThat(executor.numPending()).isEqualTo(1)
+    }
+
+    @Test
+    fun testBatteryController_CanRestart() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+        whenever(batteryController.isPluggedIn).thenReturn(false)
+        assertThat(executor.numPending()).isEqualTo(0)
+        restarter.restartSystemUI()
+
+        val captor =
+            ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
+        verify(batteryController).addCallback(captor.capture())
+
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+
+        captor.value.onBatteryLevelChanged(0, true, true)
+        assertThat(executor.numPending()).isEqualTo(1)
+    }
+}
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 b2dd60c..d5b5a4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -25,8 +25,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 /**
  * NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
@@ -59,7 +59,9 @@
     fun testBooleanResourceFlag() {
         val flagId = 213
         val flagResourceId = 3
-        val flag = ResourceBooleanFlag(flagId, flagResourceId)
+        val flagName = "213"
+        val flagNamespace = "test"
+        val flag = ResourceBooleanFlag(flagId, flagName, flagNamespace, flagResourceId)
         whenever(mResources.getBoolean(flagResourceId)).thenReturn(true)
         assertThat(mFeatureFlagsRelease.isEnabled(flag)).isTrue()
     }
@@ -71,57 +73,45 @@
         whenever(mResources.getString(1003)).thenReturn(null)
         whenever(mResources.getString(1004)).thenAnswer { throw NameNotFoundException() }
 
-        assertThat(mFeatureFlagsRelease.getString(ResourceStringFlag(1, 1001))).isEqualTo("")
-        assertThat(mFeatureFlagsRelease.getString(ResourceStringFlag(2, 1002))).isEqualTo("res2")
+        assertThat(mFeatureFlagsRelease.getString(
+            ResourceStringFlag(1, "1", "test", 1001))).isEqualTo("")
+        assertThat(mFeatureFlagsRelease.getString(
+            ResourceStringFlag(2, "2", "test", 1002))).isEqualTo("res2")
 
         assertThrows(NullPointerException::class.java) {
-            mFeatureFlagsRelease.getString(ResourceStringFlag(3, 1003))
+            mFeatureFlagsRelease.getString(ResourceStringFlag(3, "3", "test", 1003))
         }
         assertThrows(NameNotFoundException::class.java) {
-            mFeatureFlagsRelease.getString(ResourceStringFlag(4, 1004))
+            mFeatureFlagsRelease.getString(ResourceStringFlag(4, "4", "test", 1004))
         }
     }
 
     @Test
-    fun testReadDeviceConfigBooleanFlag() {
-        val namespace = "test_namespace"
-        deviceConfig.setProperty(namespace, "a", "true", false)
-        deviceConfig.setProperty(namespace, "b", "false", false)
-        deviceConfig.setProperty(namespace, "c", null, false)
-
-        assertThat(mFeatureFlagsRelease.isEnabled(DeviceConfigBooleanFlag(1, "a", namespace)))
-            .isTrue()
-        assertThat(mFeatureFlagsRelease.isEnabled(DeviceConfigBooleanFlag(2, "b", namespace)))
-            .isFalse()
-        assertThat(mFeatureFlagsRelease.isEnabled(DeviceConfigBooleanFlag(3, "c", namespace)))
-            .isFalse()
-    }
-
-    @Test
     fun testSysPropBooleanFlag() {
         val flagId = 213
         val flagName = "sys_prop_flag"
+        val flagNamespace = "test"
         val flagDefault = true
 
-        val flag = SysPropBooleanFlag(flagId, flagName, flagDefault)
+        val flag = SysPropBooleanFlag(flagId, flagName, flagNamespace, flagDefault)
         whenever(mSystemProperties.getBoolean(flagName, flagDefault)).thenReturn(flagDefault)
         assertThat(mFeatureFlagsRelease.isEnabled(flag)).isEqualTo(flagDefault)
     }
 
     @Test
     fun serverSide_OverridesReleased_MakesFalse() {
-        val flag = ReleasedFlag(100)
+        val flag = ReleasedFlag(100, "100", "test")
 
-        serverFlagReader.setFlagValue(flag.id, false)
+        serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
 
         assertThat(mFeatureFlagsRelease.isEnabled(flag)).isFalse()
     }
 
     @Test
     fun serverSide_OverridesUnreleased_Ignored() {
-        val flag = UnreleasedFlag(100)
+        val flag = UnreleasedFlag(100, "100", "test")
 
-        serverFlagReader.setFlagValue(flag.id, true)
+        serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
 
         assertThat(mFeatureFlagsRelease.isEnabled(flag)).isFalse()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index 9628ee9..fea91c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -33,8 +33,10 @@
     @Mock private lateinit var featureFlags: FeatureFlagsDebug
     @Mock private lateinit var pw: PrintWriter
     private val flagMap = mutableMapOf<Int, Flag<*>>()
-    private val flagA = UnreleasedFlag(500)
-    private val flagB = ReleasedFlag(501)
+    private val flagA = UnreleasedFlag(500, "500", "test")
+    private val flagB = ReleasedFlag(501, "501", "test")
+    private val stringFlag = StringFlag(502, "502", "test", "abracadabra")
+    private val intFlag = IntFlag(503, "503", "test", 12)
 
     private lateinit var cmd: FlagCommand
 
@@ -44,26 +46,59 @@
 
         whenever(featureFlags.isEnabled(any(UnreleasedFlag::class.java))).thenReturn(false)
         whenever(featureFlags.isEnabled(any(ReleasedFlag::class.java))).thenReturn(true)
+        whenever(featureFlags.getString(any(StringFlag::class.java))).thenAnswer { invocation ->
+            (invocation.getArgument(0) as StringFlag).default
+        }
+        whenever(featureFlags.getInt(any(IntFlag::class.java))).thenAnswer { invocation ->
+            (invocation.getArgument(0) as IntFlag).default
+        }
+
         flagMap.put(flagA.id, flagA)
         flagMap.put(flagB.id, flagB)
+        flagMap.put(stringFlag.id, stringFlag)
+        flagMap.put(intFlag.id, intFlag)
 
         cmd = FlagCommand(featureFlags, flagMap)
     }
 
     @Test
-    fun readFlagCommand() {
+    fun readBooleanFlagCommand() {
         cmd.execute(pw, listOf(flagA.id.toString()))
         Mockito.verify(featureFlags).isEnabled(flagA)
     }
 
     @Test
-    fun setFlagCommand() {
+    fun readStringFlagCommand() {
+        cmd.execute(pw, listOf(stringFlag.id.toString()))
+        Mockito.verify(featureFlags).getString(stringFlag)
+    }
+
+    @Test
+    fun readIntFlag() {
+        cmd.execute(pw, listOf(intFlag.id.toString()))
+        Mockito.verify(featureFlags).getInt(intFlag)
+    }
+
+    @Test
+    fun setBooleanFlagCommand() {
         cmd.execute(pw, listOf(flagB.id.toString(), "on"))
         Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, true)
     }
 
     @Test
-    fun toggleFlagCommand() {
+    fun setStringFlagCommand() {
+        cmd.execute(pw, listOf(stringFlag.id.toString(), "set", "foobar"))
+        Mockito.verify(featureFlags).setStringFlagInternal(stringFlag, "foobar")
+    }
+
+    @Test
+    fun setIntFlag() {
+        cmd.execute(pw, listOf(intFlag.id.toString(), "put", "123"))
+        Mockito.verify(featureFlags).setIntFlagInternal(intFlag, 123)
+    }
+
+    @Test
+    fun toggleBooleanFlagCommand() {
         cmd.execute(pw, listOf(flagB.id.toString(), "toggle"))
         Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, false)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
index 17324a0..fca7e96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
@@ -64,14 +64,14 @@
         verifyNoMoreInteractions(mFlagSettingsHelper)
 
         // adding the first listener registers the observer
-        mFlagManager.addListener(ReleasedFlag(1), listener1)
+        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
         val observer = withArgCaptor<ContentObserver> {
             verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
         }
         verifyNoMoreInteractions(mFlagSettingsHelper)
 
         // adding another listener does nothing
-        mFlagManager.addListener(ReleasedFlag(2), listener2)
+        mFlagManager.addListener(ReleasedFlag(2, "2", "test"), listener2)
         verifyNoMoreInteractions(mFlagSettingsHelper)
 
         // removing the original listener does nothing with second one still present
@@ -89,7 +89,7 @@
         val listener = mock<FlagListenable.Listener>()
         val clearCacheAction = mock<Consumer<Int>>()
         mFlagManager.clearCacheAction = clearCacheAction
-        mFlagManager.addListener(ReleasedFlag(1), listener)
+        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener)
         val observer = withArgCaptor<ContentObserver> {
             verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
         }
@@ -101,8 +101,8 @@
     fun testObserverInvokesListeners() {
         val listener1 = mock<FlagListenable.Listener>()
         val listener10 = mock<FlagListenable.Listener>()
-        mFlagManager.addListener(ReleasedFlag(1), listener1)
-        mFlagManager.addListener(ReleasedFlag(10), listener10)
+        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
+        mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener10)
         val observer = withArgCaptor<ContentObserver> {
             verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
         }
@@ -127,8 +127,8 @@
     fun testOnlySpecificFlagListenerIsInvoked() {
         val listener1 = mock<FlagListenable.Listener>()
         val listener10 = mock<FlagListenable.Listener>()
-        mFlagManager.addListener(ReleasedFlag(1), listener1)
-        mFlagManager.addListener(ReleasedFlag(10), listener10)
+        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
+        mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener10)
 
         mFlagManager.dispatchListenersAndMaybeRestart(1, null)
         val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
@@ -148,8 +148,8 @@
     @Test
     fun testSameListenerCanBeUsedForMultipleFlags() {
         val listener = mock<FlagListenable.Listener>()
-        mFlagManager.addListener(ReleasedFlag(1), listener)
-        mFlagManager.addListener(ReleasedFlag(10), listener)
+        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener)
+        mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener)
 
         mFlagManager.dispatchListenersAndMaybeRestart(1, null)
         val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
@@ -177,7 +177,7 @@
     @Test
     fun testListenerCanSuppressRestart() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.addListener(ReleasedFlag(1)) { event ->
+        mFlagManager.addListener(ReleasedFlag(1, "1", "test")) { event ->
             event.requestNoRestart()
         }
         mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
@@ -188,7 +188,7 @@
     @Test
     fun testListenerOnlySuppressesRestartForOwnFlag() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.addListener(ReleasedFlag(10)) { event ->
+        mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { event ->
             event.requestNoRestart()
         }
         mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
@@ -199,10 +199,10 @@
     @Test
     fun testRestartWhenNotAllListenersRequestSuppress() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.addListener(ReleasedFlag(10)) { event ->
+        mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { event ->
             event.requestNoRestart()
         }
-        mFlagManager.addListener(ReleasedFlag(10)) {
+        mFlagManager.addListener(ReleasedFlag(10, "10", "test")) {
             // do not request
         }
         mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java
deleted file mode 100644
index 250cc48..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java
+++ /dev/null
@@ -1,119 +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.flags;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.util.Pair;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Test;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-@SmallTest
-public class FlagsTest extends SysuiTestCase {
-
-    @Test
-    public void testDuplicateFlagIdCheckWorks() {
-        List<Pair<String, Flag<?>>> flags = collectFlags(DuplicateFlagContainer.class);
-        Map<Integer, List<String>> duplicates = groupDuplicateFlags(flags);
-
-        assertWithMessage(generateAssertionMessage(duplicates))
-                .that(duplicates.size()).isEqualTo(2);
-    }
-
-    @Test
-    public void testNoDuplicateFlagIds() {
-        List<Pair<String, Flag<?>>> flags = collectFlags(Flags.class);
-        Map<Integer, List<String>> duplicates = groupDuplicateFlags(flags);
-
-        assertWithMessage(generateAssertionMessage(duplicates))
-                .that(duplicates.size()).isEqualTo(0);
-    }
-
-    private String generateAssertionMessage(Map<Integer, List<String>> duplicates) {
-        StringBuilder stringBuilder = new StringBuilder();
-        stringBuilder.append("Duplicate flag keys found: {");
-        for (int id : duplicates.keySet()) {
-            stringBuilder
-                    .append(" ")
-                    .append(id)
-                    .append(": [")
-                    .append(String.join(", ", duplicates.get(id)))
-                    .append("]");
-        }
-        stringBuilder.append(" }");
-
-        return stringBuilder.toString();
-    }
-
-    private List<Pair<String, Flag<?>>> collectFlags(Class<?> clz) {
-        List<Pair<String, Flag<?>>> flags = new ArrayList<>();
-
-        Field[] fields = clz.getFields();
-
-        for (Field field : fields) {
-            Class<?> t = field.getType();
-            if (Flag.class.isAssignableFrom(t)) {
-                try {
-                    flags.add(Pair.create(field.getName(), (Flag<?>) field.get(null)));
-                } catch (IllegalAccessException e) {
-                    // no-op
-                }
-            }
-        }
-
-        return flags;
-    }
-
-    private Map<Integer, List<String>> groupDuplicateFlags(List<Pair<String, Flag<?>>> flags) {
-        Map<Integer, List<String>> grouping = new HashMap<>();
-
-        for (Pair<String, Flag<?>> flag : flags) {
-            grouping.putIfAbsent(flag.second.getId(), new ArrayList<>());
-            grouping.get(flag.second.getId()).add(flag.first);
-        }
-
-        Map<Integer, List<String>> result = new HashMap<>();
-        for (Integer id : grouping.keySet()) {
-            if (grouping.get(id).size() > 1) {
-                result.put(id, grouping.get(id));
-            }
-        }
-
-        return result;
-    }
-
-    private static class DuplicateFlagContainer {
-        public static final BooleanFlag A_FLAG = new UnreleasedFlag(0);
-        public static final BooleanFlag B_FLAG = new UnreleasedFlag(0);
-        public static final StringFlag C_FLAG = new StringFlag(0);
-
-        public static final BooleanFlag D_FLAG = new UnreleasedFlag(1);
-
-        public static final DoubleFlag E_FLAG = new DoubleFlag(3);
-        public static final DoubleFlag F_FLAG = new DoubleFlag(3);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
index 6f5f460..1633912 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -50,7 +50,7 @@
 
     @Test
     fun testChange_alertsListener() {
-        val flag = ReleasedFlag(1)
+        val flag = ReleasedFlag(1, "1", "test")
         serverFlagReader.listenForChanges(listOf(flag), changeListener)
 
         deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 8b1554c..d52616b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -63,6 +63,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -103,6 +104,7 @@
     @Mock private SecureSettings mSecureSettings;
     @Mock private Resources mResources;
     @Mock private ConfigurationController mConfigurationController;
+    @Mock private UserTracker mUserTracker;
     @Mock private KeyguardStateController mKeyguardStateController;
     @Mock private UserManager mUserManager;
     @Mock private TrustManager mTrustManager;
@@ -152,6 +154,7 @@
                 mVibratorHelper,
                 mResources,
                 mConfigurationController,
+                mUserTracker,
                 mKeyguardStateController,
                 mUserManager,
                 mTrustManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
new file mode 100644
index 0000000..cef452b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -0,0 +1,460 @@
+/*
+ * 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
+
+import android.content.ContentValues
+import android.content.pm.PackageManager
+import android.content.pm.ProviderInfo
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SystemUIAppComponentFactoryBase
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
+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
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
+
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var activityStarter: ActivityStarter
+
+    private lateinit var underTest: KeyguardQuickAffordanceProvider
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = KeyguardQuickAffordanceProvider()
+        val scope = CoroutineScope(IMMEDIATE)
+        val localUserSelectionManager =
+            KeyguardQuickAffordanceLocalUserSelectionManager(
+                context = context,
+                userFileManager =
+                    mock<UserFileManager>().apply {
+                        whenever(
+                                getSharedPreferences(
+                                    anyString(),
+                                    anyInt(),
+                                    anyInt(),
+                                )
+                            )
+                            .thenReturn(FakeSharedPreferences())
+                    },
+                userTracker = userTracker,
+                broadcastDispatcher = fakeBroadcastDispatcher,
+            )
+        val remoteUserSelectionManager =
+            KeyguardQuickAffordanceRemoteUserSelectionManager(
+                scope = scope,
+                userTracker = userTracker,
+                clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+                userHandle = UserHandle.SYSTEM,
+            )
+        val quickAffordanceRepository =
+            KeyguardQuickAffordanceRepository(
+                appContext = context,
+                scope = scope,
+                localUserSelectionManager = localUserSelectionManager,
+                remoteUserSelectionManager = remoteUserSelectionManager,
+                userTracker = userTracker,
+                configs =
+                    setOf(
+                        FakeKeyguardQuickAffordanceConfig(
+                            key = AFFORDANCE_1,
+                            pickerName = AFFORDANCE_1_NAME,
+                            pickerIconResourceId = 1,
+                        ),
+                        FakeKeyguardQuickAffordanceConfig(
+                            key = AFFORDANCE_2,
+                            pickerName = AFFORDANCE_2_NAME,
+                            pickerIconResourceId = 2,
+                        ),
+                    ),
+                legacySettingSyncer =
+                    KeyguardQuickAffordanceLegacySettingSyncer(
+                        scope = scope,
+                        backgroundDispatcher = IMMEDIATE,
+                        secureSettings = FakeSettings(),
+                        selectionsManager = localUserSelectionManager,
+                    ),
+                dumpManager = mock(),
+                userHandle = UserHandle.SYSTEM,
+            )
+        underTest.interactor =
+            KeyguardQuickAffordanceInteractor(
+                keyguardInteractor =
+                    KeyguardInteractor(
+                        repository = FakeKeyguardRepository(),
+                    ),
+                registry = mock(),
+                lockPatternUtils = lockPatternUtils,
+                keyguardStateController = keyguardStateController,
+                userTracker = userTracker,
+                activityStarter = activityStarter,
+                featureFlags =
+                    FakeFeatureFlags().apply {
+                        set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+                    },
+                repository = { quickAffordanceRepository },
+            )
+
+        underTest.attachInfoForTesting(
+            context,
+            ProviderInfo().apply { authority = Contract.AUTHORITY },
+        )
+        context.contentResolver.addProvider(Contract.AUTHORITY, underTest)
+        context.testablePermissions.setPermission(
+            Contract.PERMISSION,
+            PackageManager.PERMISSION_GRANTED,
+        )
+    }
+
+    @Test
+    fun `onAttachInfo - reportsContext`() {
+        val callback: SystemUIAppComponentFactoryBase.ContextAvailableCallback = mock()
+        underTest.setContextAvailableCallback(callback)
+
+        underTest.attachInfo(context, null)
+
+        verify(callback).onContextAvailable(context)
+    }
+
+    @Test
+    fun getType() {
+        assertThat(underTest.getType(Contract.AffordanceTable.URI))
+            .isEqualTo(
+                "vnd.android.cursor.dir/vnd." +
+                    "${Contract.AUTHORITY}.${Contract.AffordanceTable.TABLE_NAME}"
+            )
+        assertThat(underTest.getType(Contract.SlotTable.URI))
+            .isEqualTo(
+                "vnd.android.cursor.dir/vnd.${Contract.AUTHORITY}.${Contract.SlotTable.TABLE_NAME}"
+            )
+        assertThat(underTest.getType(Contract.SelectionTable.URI))
+            .isEqualTo(
+                "vnd.android.cursor.dir/vnd." +
+                    "${Contract.AUTHORITY}.${Contract.SelectionTable.TABLE_NAME}"
+            )
+    }
+
+    @Test
+    fun `insert and query selection`() =
+        runBlocking(IMMEDIATE) {
+            val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+            val affordanceId = AFFORDANCE_2
+            val affordanceName = AFFORDANCE_2_NAME
+
+            insertSelection(
+                slotId = slotId,
+                affordanceId = affordanceId,
+            )
+
+            assertThat(querySelections())
+                .isEqualTo(
+                    listOf(
+                        Selection(
+                            slotId = slotId,
+                            affordanceId = affordanceId,
+                            affordanceName = affordanceName,
+                        )
+                    )
+                )
+        }
+
+    @Test
+    fun `query slots`() =
+        runBlocking(IMMEDIATE) {
+            assertThat(querySlots())
+                .isEqualTo(
+                    listOf(
+                        Slot(
+                            id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                            capacity = 1,
+                        ),
+                        Slot(
+                            id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                            capacity = 1,
+                        ),
+                    )
+                )
+        }
+
+    @Test
+    fun `query affordances`() =
+        runBlocking(IMMEDIATE) {
+            assertThat(queryAffordances())
+                .isEqualTo(
+                    listOf(
+                        Affordance(
+                            id = AFFORDANCE_1,
+                            name = AFFORDANCE_1_NAME,
+                            iconResourceId = 1,
+                        ),
+                        Affordance(
+                            id = AFFORDANCE_2,
+                            name = AFFORDANCE_2_NAME,
+                            iconResourceId = 2,
+                        ),
+                    )
+                )
+        }
+
+    @Test
+    fun `delete and query selection`() =
+        runBlocking(IMMEDIATE) {
+            insertSelection(
+                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                affordanceId = AFFORDANCE_1,
+            )
+            insertSelection(
+                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                affordanceId = AFFORDANCE_2,
+            )
+
+            context.contentResolver.delete(
+                Contract.SelectionTable.URI,
+                "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
+                    " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
+                arrayOf(
+                    KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                    AFFORDANCE_2,
+                ),
+            )
+
+            assertThat(querySelections())
+                .isEqualTo(
+                    listOf(
+                        Selection(
+                            slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                            affordanceId = AFFORDANCE_1,
+                            affordanceName = AFFORDANCE_1_NAME,
+                        )
+                    )
+                )
+        }
+
+    @Test
+    fun `delete all selections in a slot`() =
+        runBlocking(IMMEDIATE) {
+            insertSelection(
+                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                affordanceId = AFFORDANCE_1,
+            )
+            insertSelection(
+                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                affordanceId = AFFORDANCE_2,
+            )
+
+            context.contentResolver.delete(
+                Contract.SelectionTable.URI,
+                Contract.SelectionTable.Columns.SLOT_ID,
+                arrayOf(
+                    KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                ),
+            )
+
+            assertThat(querySelections())
+                .isEqualTo(
+                    listOf(
+                        Selection(
+                            slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                            affordanceId = AFFORDANCE_1,
+                            affordanceName = AFFORDANCE_1_NAME,
+                        )
+                    )
+                )
+        }
+
+    private fun insertSelection(
+        slotId: String,
+        affordanceId: String,
+    ) {
+        context.contentResolver.insert(
+            Contract.SelectionTable.URI,
+            ContentValues().apply {
+                put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
+                put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
+            }
+        )
+    }
+
+    private fun querySelections(): List<Selection> {
+        return context.contentResolver
+            .query(
+                Contract.SelectionTable.URI,
+                null,
+                null,
+                null,
+                null,
+            )
+            ?.use { cursor ->
+                buildList {
+                    val slotIdColumnIndex =
+                        cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
+                    val affordanceIdColumnIndex =
+                        cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
+                    val affordanceNameColumnIndex =
+                        cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME)
+                    if (
+                        slotIdColumnIndex == -1 ||
+                            affordanceIdColumnIndex == -1 ||
+                            affordanceNameColumnIndex == -1
+                    ) {
+                        return@buildList
+                    }
+
+                    while (cursor.moveToNext()) {
+                        add(
+                            Selection(
+                                slotId = cursor.getString(slotIdColumnIndex),
+                                affordanceId = cursor.getString(affordanceIdColumnIndex),
+                                affordanceName = cursor.getString(affordanceNameColumnIndex),
+                            )
+                        )
+                    }
+                }
+            }
+            ?: emptyList()
+    }
+
+    private fun querySlots(): List<Slot> {
+        return context.contentResolver
+            .query(
+                Contract.SlotTable.URI,
+                null,
+                null,
+                null,
+                null,
+            )
+            ?.use { cursor ->
+                buildList {
+                    val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
+                    val capacityColumnIndex =
+                        cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
+                    if (idColumnIndex == -1 || capacityColumnIndex == -1) {
+                        return@buildList
+                    }
+
+                    while (cursor.moveToNext()) {
+                        add(
+                            Slot(
+                                id = cursor.getString(idColumnIndex),
+                                capacity = cursor.getInt(capacityColumnIndex),
+                            )
+                        )
+                    }
+                }
+            }
+            ?: emptyList()
+    }
+
+    private fun queryAffordances(): List<Affordance> {
+        return context.contentResolver
+            .query(
+                Contract.AffordanceTable.URI,
+                null,
+                null,
+                null,
+                null,
+            )
+            ?.use { cursor ->
+                buildList {
+                    val idColumnIndex = cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
+                    val nameColumnIndex =
+                        cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
+                    val iconColumnIndex =
+                        cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
+                    if (idColumnIndex == -1 || nameColumnIndex == -1 || iconColumnIndex == -1) {
+                        return@buildList
+                    }
+
+                    while (cursor.moveToNext()) {
+                        add(
+                            Affordance(
+                                id = cursor.getString(idColumnIndex),
+                                name = cursor.getString(nameColumnIndex),
+                                iconResourceId = cursor.getInt(iconColumnIndex),
+                            )
+                        )
+                    }
+                }
+            }
+            ?: emptyList()
+    }
+
+    data class Slot(
+        val id: String,
+        val capacity: Int,
+    )
+
+    data class Affordance(
+        val id: String,
+        val name: String,
+        val iconResourceId: Int,
+    )
+
+    data class Selection(
+        val slotId: String,
+        val affordanceId: String,
+        val affordanceName: String,
+    )
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+        private const val AFFORDANCE_1 = "affordance_1"
+        private const val AFFORDANCE_2 = "affordance_2"
+        private const val AFFORDANCE_1_NAME = "affordance_1_name"
+        private const val AFFORDANCE_2_NAME = "affordance_2_name"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 23516c9..729a1cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -48,6 +48,7 @@
 import com.android.systemui.SystemUIInitializerImpl;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -93,6 +94,8 @@
     private NextAlarmController mNextAlarmController;
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock
+    private UserTracker mUserTracker;
     private TestableKeyguardSliceProvider mProvider;
     private boolean mIsZenMode;
 
@@ -105,6 +108,7 @@
         mProvider.attachInfo(getContext(), null);
         reset(mContentResolver);
         SliceProvider.setSpecs(new HashSet<>(Arrays.asList(SliceSpecs.LIST)));
+        when(mUserTracker.getUserId()).thenReturn(100);
     }
 
     @After
@@ -267,6 +271,7 @@
             mKeyguardBypassController = KeyguardSliceProviderTest.this.mKeyguardBypassController;
             mMediaManager = KeyguardSliceProviderTest.this.mNotificationMediaManager;
             mKeyguardUpdateMonitor = KeyguardSliceProviderTest.this.mKeyguardUpdateMonitor;
+            mUserTracker = KeyguardSliceProviderTest.this.mUserTracker;
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 2c3ddd5..798839d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard;
 
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
@@ -34,6 +35,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.os.PowerManager;
@@ -41,6 +43,11 @@
 import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.RemoteAnimationTarget;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -52,18 +59,27 @@
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollectorFake;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -77,14 +93,13 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import dagger.Lazy;
-
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
 public class KeyguardViewMediatorTest extends SysuiTestCase {
     private KeyguardViewMediator mViewMediator;
 
+    private @Mock UserTracker mUserTracker;
     private @Mock DevicePolicyManager mDevicePolicyManager;
     private @Mock LockPatternUtils mLockPatternUtils;
     private @Mock KeyguardUpdateMonitor mUpdateMonitor;
@@ -92,11 +107,15 @@
     private @Mock BroadcastDispatcher mBroadcastDispatcher;
     private @Mock DismissCallbackRegistry mDismissCallbackRegistry;
     private @Mock DumpManager mDumpManager;
+    private @Mock WindowManager mWindowManager;
+    private @Mock IActivityManager mActivityManager;
+    private @Mock ConfigurationController mConfigurationController;
     private @Mock PowerManager mPowerManager;
     private @Mock TrustManager mTrustManager;
     private @Mock UserSwitcherController mUserSwitcherController;
     private @Mock NavigationModeController mNavigationModeController;
     private @Mock KeyguardDisplayManager mKeyguardDisplayManager;
+    private @Mock KeyguardBypassController mKeyguardBypassController;
     private @Mock DozeParameters mDozeParameters;
     private @Mock SysuiStatusBarStateController mStatusBarStateController;
     private @Mock KeyguardStateController mKeyguardStateController;
@@ -105,9 +124,14 @@
     private @Mock ScreenOffAnimationController mScreenOffAnimationController;
     private @Mock InteractionJankMonitor mInteractionJankMonitor;
     private @Mock ScreenOnCoordinator mScreenOnCoordinator;
-    private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
+    private @Mock ShadeController mShadeController;
+    private NotificationShadeWindowController mNotificationShadeWindowController;
     private @Mock DreamOverlayStateController mDreamOverlayStateController;
     private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+    private @Mock ScrimController mScrimController;
+    private @Mock SysuiColorExtractor mColorExtractor;
+    private @Mock AuthController mAuthController;
+    private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -124,6 +148,14 @@
         when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
         when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
         when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
+        final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
+        when(testViewRoot.getView()).thenReturn(mock(View.class));
+        when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot);
+        mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
+                mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
+                mConfigurationController, mViewMediator, mKeyguardBypassController,
+                mColorExtractor, mDumpManager, mKeyguardStateController,
+                mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager);
 
         createAndStartViewMediator();
     }
@@ -281,9 +313,27 @@
         verify(mCentralSurfaces).updateIsKeyguard();
     }
 
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void testStartKeyguardExitAnimation_expectSurfaceBehindRemoteAnimation() {
+        RemoteAnimationTarget[] apps = new RemoteAnimationTarget[]{
+                mock(RemoteAnimationTarget.class)
+        };
+        RemoteAnimationTarget[] wallpapers = new RemoteAnimationTarget[]{
+                mock(RemoteAnimationTarget.class)
+        };
+        IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class);
+
+        mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers,
+                null, callback);
+        TestableLooper.get(this).processAllMessages();
+        assertTrue(mViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
+    }
+
     private void createAndStartViewMediator() {
         mViewMediator = new KeyguardViewMediator(
                 mContext,
+                mUserTracker,
                 mFalsingCollector,
                 mLockPatternUtils,
                 mBroadcastDispatcher,
@@ -307,8 +357,10 @@
                 mScreenOnCoordinator,
                 mInteractionJankMonitor,
                 mDreamOverlayStateController,
-                mNotificationShadeWindowControllerLazy,
-                () -> mActivityLaunchAnimator);
+                () -> mShadeController,
+                () -> mNotificationShadeWindowController,
+                () -> mActivityLaunchAnimator,
+                () -> mScrimController);
         mViewMediator.start();
 
         mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
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/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..7205f30
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -0,0 +1,65 @@
+/*
+ *  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 android.app.StatusBarManager
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.CameraGestureHelper
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class CameraQuickAffordanceConfigTest : SysuiTestCase() {
+
+    @Mock private lateinit var cameraGestureHelper: CameraGestureHelper
+    @Mock private lateinit var context: Context
+
+    private lateinit var underTest: CameraQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            CameraQuickAffordanceConfig(
+                context,
+            ) {
+                cameraGestureHelper
+            }
+    }
+
+    @Test
+    fun `affordance triggered -- camera launch called`() {
+        // When
+        val result = underTest.onTriggered(null)
+
+        // Then
+        verify(cameraGestureHelper)
+            .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+        assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+    }
+}
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 f18acba..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
@@ -23,14 +23,11 @@
 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 onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..9fa7db1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
@@ -0,0 +1,215 @@
+/*
+ *  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 android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.statusbar.policy.FlashlightController
+import com.android.systemui.utils.leaks.FakeFlashlightController
+import com.android.systemui.utils.leaks.LeakCheckedTest
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
+
+    @Mock private lateinit var context: Context
+    private lateinit var flashlightController: FakeFlashlightController
+    private lateinit var underTest: FlashlightQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        injectLeakCheckedDependency(FlashlightController::class.java)
+        MockitoAnnotations.initMocks(this)
+
+        flashlightController =
+            SysuiLeakCheck().getLeakChecker(FlashlightController::class.java)
+                as FakeFlashlightController
+        underTest = FlashlightQuickAffordanceConfig(context, flashlightController)
+    }
+
+    @Test
+    fun `flashlight is off -- triggered -- icon is on and active`() = runTest {
+        // given
+        flashlightController.isEnabled = false
+        flashlightController.isAvailable = true
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
+
+        // when
+        underTest.onTriggered(null)
+        val lastValue = values.last()
+
+        // then
+        assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+        assertEquals(
+            R.drawable.qs_flashlight_icon_on,
+            ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
+                    as? Icon.Resource)
+                ?.res
+        )
+        job.cancel()
+    }
+
+    @Test
+    fun `flashlight is on -- triggered -- icon is off and inactive`() = runTest {
+        // given
+        flashlightController.isEnabled = true
+        flashlightController.isAvailable = true
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
+
+        // when
+        underTest.onTriggered(null)
+        val lastValue = values.last()
+
+        // then
+        assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+        assertEquals(
+            R.drawable.qs_flashlight_icon_off,
+            ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
+                    as? Icon.Resource)
+                ?.res
+        )
+        job.cancel()
+    }
+
+    @Test
+    fun `flashlight is on -- receives error -- icon is off and inactive`() = runTest {
+        // given
+        flashlightController.isEnabled = true
+        flashlightController.isAvailable = false
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
+
+        // when
+        flashlightController.onFlashlightError()
+        val lastValue = values.last()
+
+        // then
+        assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+        assertEquals(
+            R.drawable.qs_flashlight_icon_off,
+            ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
+                    as? Icon.Resource)
+                ?.res
+        )
+        job.cancel()
+    }
+
+    @Test
+    fun `flashlight availability now off -- hidden`() = runTest {
+        // given
+        flashlightController.isEnabled = true
+        flashlightController.isAvailable = false
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
+
+        // when
+        flashlightController.onFlashlightAvailabilityChanged(false)
+        val lastValue = values.last()
+
+        // then
+        assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+        job.cancel()
+    }
+
+    @Test
+    fun `flashlight availability now on -- flashlight on -- inactive and icon off`() = runTest {
+        // given
+        flashlightController.isEnabled = true
+        flashlightController.isAvailable = false
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
+
+        // when
+        flashlightController.onFlashlightAvailabilityChanged(true)
+        val lastValue = values.last()
+
+        // then
+        assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+        assertTrue(
+            (lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState
+                is ActivationState.Active
+        )
+        assertEquals(R.drawable.qs_flashlight_icon_on, (lastValue.icon as? Icon.Resource)?.res)
+        job.cancel()
+    }
+
+    @Test
+    fun `flashlight availability now on -- flashlight off -- inactive and icon off`() = runTest {
+        // given
+        flashlightController.isEnabled = false
+        flashlightController.isAvailable = false
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
+
+        // when
+        flashlightController.onFlashlightAvailabilityChanged(true)
+        val lastValue = values.last()
+
+        // then
+        assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+        assertTrue(
+            (lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState
+                is ActivationState.Inactive
+        )
+        assertEquals(R.drawable.qs_flashlight_icon_off, (lastValue.icon as? Icon.Resource)?.res)
+        job.cancel()
+    }
+
+    @Test
+    fun `flashlight available -- picker state default`() = runTest {
+        // given
+        flashlightController.isAvailable = true
+
+        // when
+        val result = underTest.getPickerScreenState()
+
+        // then
+        assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.Default)
+    }
+
+    @Test
+    fun `flashlight not available -- picker state unavailable`() = runTest {
+        // given
+        flashlightController.isAvailable = false
+
+        // when
+        val result = underTest.getPickerScreenState()
+
+        // then
+        assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+    }
+}
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 c94cec6..322014a 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
@@ -24,8 +24,9 @@
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import java.util.Optional
+import java.util.*
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -40,7 +41,6 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -93,6 +93,14 @@
         whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
         whenever(component.getControlsListingController())
             .thenReturn(Optional.of(controlsListingController))
+        whenever(controlsListingController.getCurrentServices())
+            .thenReturn(
+                if (hasServiceInfos) {
+                    listOf(mock(), mock())
+                } else {
+                    emptyList()
+                }
+            )
         whenever(component.canShowWhileLockedSetting)
             .thenReturn(MutableStateFlow(canShowWhileLocked))
         whenever(component.getVisibility())
@@ -144,6 +152,17 @@
                     KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java
                 }
             )
+        assertThat(underTest.getPickerScreenState())
+            .isInstanceOf(
+                when {
+                    !isFeatureEnabled ->
+                        KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice::class
+                            .java
+                    hasServiceInfos && hasFavorites ->
+                        KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java
+                    else -> KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java
+                }
+            )
         job.cancel()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
new file mode 100644
index 0000000..3b0169d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -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.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.content.res.Resources
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.any
+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.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() {
+
+    @Mock private lateinit var sharedPrefs: FakeSharedPreferences
+
+    private lateinit var underTest: KeyguardQuickAffordanceLegacySettingSyncer
+
+    private lateinit var testScope: TestScope
+    private lateinit var testDispatcher: TestDispatcher
+    private lateinit var selectionManager: KeyguardQuickAffordanceLocalUserSelectionManager
+    private lateinit var settings: FakeSettings
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        val context: Context = mock()
+        sharedPrefs = FakeSharedPreferences()
+        whenever(context.getSharedPreferences(anyString(), any())).thenReturn(sharedPrefs)
+        val resources: Resources = mock()
+        whenever(resources.getStringArray(R.array.config_keyguardQuickAffordanceDefaults))
+            .thenReturn(emptyArray())
+        whenever(context.resources).thenReturn(resources)
+
+        testDispatcher = UnconfinedTestDispatcher()
+        testScope = TestScope(testDispatcher)
+        selectionManager =
+            KeyguardQuickAffordanceLocalUserSelectionManager(
+                context = context,
+                userFileManager =
+                    mock {
+                        whenever(
+                                getSharedPreferences(
+                                    anyString(),
+                                    anyInt(),
+                                    anyInt(),
+                                )
+                            )
+                            .thenReturn(FakeSharedPreferences())
+                    },
+                userTracker = FakeUserTracker(),
+                broadcastDispatcher = fakeBroadcastDispatcher,
+            )
+        settings = FakeSettings()
+        settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0)
+        settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_WALLET, 0)
+        settings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0)
+
+        underTest =
+            KeyguardQuickAffordanceLegacySettingSyncer(
+                scope = testScope,
+                backgroundDispatcher = testDispatcher,
+                secureSettings = settings,
+                selectionsManager = selectionManager,
+            )
+    }
+
+    @Test
+    fun `Setting a setting selects the affordance`() =
+        testScope.runTest {
+            val job = underTest.startSyncing()
+
+            settings.putInt(
+                Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+                1,
+            )
+
+            assertThat(
+                    selectionManager
+                        .getSelections()
+                        .getOrDefault(
+                            KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                            emptyList()
+                        )
+                )
+                .contains(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `Clearing a setting selects the affordance`() =
+        testScope.runTest {
+            val job = underTest.startSyncing()
+
+            settings.putInt(
+                Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+                1,
+            )
+            settings.putInt(
+                Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+                0,
+            )
+
+            assertThat(
+                    selectionManager
+                        .getSelections()
+                        .getOrDefault(
+                            KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                            emptyList()
+                        )
+                )
+                .doesNotContain(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `Selecting an affordance sets its setting`() =
+        testScope.runTest {
+            val job = underTest.startSyncing()
+
+            selectionManager.setSelections(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                listOf(BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET)
+            )
+
+            advanceUntilIdle()
+            assertThat(settings.getInt(Settings.Secure.LOCKSCREEN_SHOW_WALLET)).isEqualTo(1)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `Unselecting an affordance clears its setting`() =
+        testScope.runTest {
+            val job = underTest.startSyncing()
+
+            selectionManager.setSelections(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                listOf(BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET)
+            )
+            selectionManager.setSelections(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                emptyList()
+            )
+
+            assertThat(settings.getInt(Settings.Secure.LOCKSCREEN_SHOW_WALLET)).isEqualTo(0)
+
+            job.cancel()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
new file mode 100644
index 0000000..67091a9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -0,0 +1,362 @@
+/*
+ * 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 android.content.Intent
+import android.content.SharedPreferences
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() {
+
+    @Mock private lateinit var userFileManager: UserFileManager
+
+    private lateinit var underTest: KeyguardQuickAffordanceLocalUserSelectionManager
+
+    private lateinit var userTracker: FakeUserTracker
+    private lateinit var sharedPrefs: MutableMap<Int, SharedPreferences>
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        sharedPrefs = mutableMapOf()
+        whenever(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())).thenAnswer {
+            val userId = it.arguments[2] as Int
+            sharedPrefs.getOrPut(userId) { FakeSharedPreferences() }
+        }
+        userTracker = FakeUserTracker()
+        val dispatcher = UnconfinedTestDispatcher()
+        Dispatchers.setMain(dispatcher)
+
+        underTest =
+            KeyguardQuickAffordanceLocalUserSelectionManager(
+                context = context,
+                userFileManager = userFileManager,
+                userTracker = userTracker,
+                broadcastDispatcher = fakeBroadcastDispatcher,
+            )
+    }
+
+    @After
+    fun tearDown() {
+        Dispatchers.resetMain()
+    }
+
+    @Test
+    fun setSelections() = runTest {
+        overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
+        val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+        val job =
+            launch(UnconfinedTestDispatcher()) {
+                underTest.selections.toList(affordanceIdsBySlotId)
+            }
+        val slotId1 = "slot1"
+        val slotId2 = "slot2"
+        val affordanceId1 = "affordance1"
+        val affordanceId2 = "affordance2"
+        val affordanceId3 = "affordance3"
+
+        underTest.setSelections(
+            slotId = slotId1,
+            affordanceIds = listOf(affordanceId1),
+        )
+        assertSelections(
+            affordanceIdsBySlotId.last(),
+            mapOf(
+                slotId1 to listOf(affordanceId1),
+            ),
+        )
+
+        underTest.setSelections(
+            slotId = slotId2,
+            affordanceIds = listOf(affordanceId2),
+        )
+        assertSelections(
+            affordanceIdsBySlotId.last(),
+            mapOf(
+                slotId1 to listOf(affordanceId1),
+                slotId2 to listOf(affordanceId2),
+            )
+        )
+
+        underTest.setSelections(
+            slotId = slotId1,
+            affordanceIds = listOf(affordanceId1, affordanceId3),
+        )
+        assertSelections(
+            affordanceIdsBySlotId.last(),
+            mapOf(
+                slotId1 to listOf(affordanceId1, affordanceId3),
+                slotId2 to listOf(affordanceId2),
+            )
+        )
+
+        underTest.setSelections(
+            slotId = slotId1,
+            affordanceIds = listOf(affordanceId3),
+        )
+        assertSelections(
+            affordanceIdsBySlotId.last(),
+            mapOf(
+                slotId1 to listOf(affordanceId3),
+                slotId2 to listOf(affordanceId2),
+            )
+        )
+
+        underTest.setSelections(
+            slotId = slotId2,
+            affordanceIds = listOf(),
+        )
+        assertSelections(
+            affordanceIdsBySlotId.last(),
+            mapOf(
+                slotId1 to listOf(affordanceId3),
+                slotId2 to listOf(),
+            )
+        )
+
+        job.cancel()
+    }
+
+    @Test
+    fun `remembers selections by user`() = runTest {
+        val slot1 = "slot_1"
+        val slot2 = "slot_2"
+        val affordance1 = "affordance_1"
+        val affordance2 = "affordance_2"
+        val affordance3 = "affordance_3"
+
+        val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+        val job =
+            launch(UnconfinedTestDispatcher()) {
+                underTest.selections.toList(affordanceIdsBySlotId)
+            }
+
+        val userInfos =
+            listOf(
+                UserInfo(/* id= */ 0, "zero", /* flags= */ 0),
+                UserInfo(/* id= */ 1, "one", /* flags= */ 0),
+            )
+        userTracker.set(
+            userInfos = userInfos,
+            selectedUserIndex = 0,
+        )
+        underTest.setSelections(
+            slotId = slot1,
+            affordanceIds = listOf(affordance1),
+        )
+        underTest.setSelections(
+            slotId = slot2,
+            affordanceIds = listOf(affordance2),
+        )
+
+        // Switch to user 1
+        userTracker.set(
+            userInfos = userInfos,
+            selectedUserIndex = 1,
+        )
+        // We never set selections on user 1, so it should be empty.
+        assertSelections(
+            observed = affordanceIdsBySlotId.last(),
+            expected = emptyMap(),
+        )
+        // Now, let's set selections on user 1.
+        underTest.setSelections(
+            slotId = slot1,
+            affordanceIds = listOf(affordance2),
+        )
+        underTest.setSelections(
+            slotId = slot2,
+            affordanceIds = listOf(affordance3),
+        )
+        assertSelections(
+            observed = affordanceIdsBySlotId.last(),
+            expected =
+                mapOf(
+                    slot1 to listOf(affordance2),
+                    slot2 to listOf(affordance3),
+                ),
+        )
+
+        // Switch back to user 0.
+        userTracker.set(
+            userInfos = userInfos,
+            selectedUserIndex = 0,
+        )
+        // Assert that we still remember the old selections for user 0.
+        assertSelections(
+            observed = affordanceIdsBySlotId.last(),
+            expected =
+                mapOf(
+                    slot1 to listOf(affordance1),
+                    slot2 to listOf(affordance2),
+                ),
+        )
+
+        job.cancel()
+    }
+
+    @Test
+    fun `selections respects defaults`() = runTest {
+        val slotId1 = "slot1"
+        val slotId2 = "slot2"
+        val affordanceId1 = "affordance1"
+        val affordanceId2 = "affordance2"
+        val affordanceId3 = "affordance3"
+        overrideResource(
+            R.array.config_keyguardQuickAffordanceDefaults,
+            arrayOf(
+                "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}",
+                "$slotId2:${listOf(affordanceId2).joinToString(",")}",
+            ),
+        )
+        val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+        val job =
+            launch(UnconfinedTestDispatcher()) {
+                underTest.selections.toList(affordanceIdsBySlotId)
+            }
+
+        assertSelections(
+            affordanceIdsBySlotId.last(),
+            mapOf(
+                slotId1 to listOf(affordanceId1, affordanceId3),
+                slotId2 to listOf(affordanceId2),
+            ),
+        )
+
+        job.cancel()
+    }
+
+    @Test
+    fun `selections ignores defaults after selecting an affordance`() = runTest {
+        val slotId1 = "slot1"
+        val slotId2 = "slot2"
+        val affordanceId1 = "affordance1"
+        val affordanceId2 = "affordance2"
+        val affordanceId3 = "affordance3"
+        overrideResource(
+            R.array.config_keyguardQuickAffordanceDefaults,
+            arrayOf(
+                "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}",
+                "$slotId2:${listOf(affordanceId2).joinToString(",")}",
+            ),
+        )
+        val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+        val job =
+            launch(UnconfinedTestDispatcher()) {
+                underTest.selections.toList(affordanceIdsBySlotId)
+            }
+
+        underTest.setSelections(slotId1, listOf(affordanceId2))
+        assertSelections(
+            affordanceIdsBySlotId.last(),
+            mapOf(
+                slotId1 to listOf(affordanceId2),
+                slotId2 to listOf(affordanceId2),
+            ),
+        )
+
+        job.cancel()
+    }
+
+    @Test
+    fun `selections ignores defaults after clearing a slot`() = runTest {
+        val slotId1 = "slot1"
+        val slotId2 = "slot2"
+        val affordanceId1 = "affordance1"
+        val affordanceId2 = "affordance2"
+        val affordanceId3 = "affordance3"
+        overrideResource(
+            R.array.config_keyguardQuickAffordanceDefaults,
+            arrayOf(
+                "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}",
+                "$slotId2:${listOf(affordanceId2).joinToString(",")}",
+            ),
+        )
+        val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+        val job =
+            launch(UnconfinedTestDispatcher()) {
+                underTest.selections.toList(affordanceIdsBySlotId)
+            }
+
+        underTest.setSelections(slotId1, listOf())
+        assertSelections(
+            affordanceIdsBySlotId.last(),
+            mapOf(
+                slotId1 to listOf(),
+                slotId2 to listOf(affordanceId2),
+            ),
+        )
+
+        job.cancel()
+    }
+
+    @Test
+    fun `responds to backup and restore by reloading the selections from disk`() = runTest {
+        overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
+        val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+        val job =
+            launch(UnconfinedTestDispatcher()) {
+                underTest.selections.toList(affordanceIdsBySlotId)
+            }
+        clearInvocations(userFileManager)
+
+        fakeBroadcastDispatcher.registeredReceivers.firstOrNull()?.onReceive(context, Intent())
+
+        verify(userFileManager, atLeastOnce()).getSharedPreferences(anyString(), anyInt(), anyInt())
+        job.cancel()
+    }
+
+    private fun assertSelections(
+        observed: Map<String, List<String>>?,
+        expected: Map<String, List<String>>,
+    ) {
+        assertThat(underTest.getSelections()).isEqualTo(expected)
+        assertThat(observed).isEqualTo(expected)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
new file mode 100644
index 0000000..d7e9cf1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
@@ -0,0 +1,219 @@
+/*
+ * 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 android.content.pm.UserInfo
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() {
+
+    @Mock private lateinit var userHandle: UserHandle
+
+    private lateinit var underTest: KeyguardQuickAffordanceRemoteUserSelectionManager
+
+    private lateinit var clientFactory: FakeKeyguardQuickAffordanceProviderClientFactory
+    private lateinit var testScope: TestScope
+    private lateinit var testDispatcher: TestDispatcher
+    private lateinit var userTracker: FakeUserTracker
+    private lateinit var client1: FakeKeyguardQuickAffordanceProviderClient
+    private lateinit var client2: FakeKeyguardQuickAffordanceProviderClient
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(userHandle.identifier).thenReturn(UserHandle.USER_SYSTEM)
+        whenever(userHandle.isSystem).thenReturn(true)
+        client1 = FakeKeyguardQuickAffordanceProviderClient()
+        client2 = FakeKeyguardQuickAffordanceProviderClient()
+
+        userTracker = FakeUserTracker()
+        userTracker.set(
+            userInfos =
+                listOf(
+                    UserInfo(
+                        UserHandle.USER_SYSTEM,
+                        "Primary",
+                        /* flags= */ 0,
+                    ),
+                    UserInfo(
+                        OTHER_USER_ID_1,
+                        "Secondary 1",
+                        /* flags= */ 0,
+                    ),
+                    UserInfo(
+                        OTHER_USER_ID_2,
+                        "Secondary 2",
+                        /* flags= */ 0,
+                    ),
+                ),
+            selectedUserIndex = 0,
+        )
+
+        clientFactory =
+            FakeKeyguardQuickAffordanceProviderClientFactory(
+                userTracker,
+            ) { selectedUserId ->
+                when (selectedUserId) {
+                    OTHER_USER_ID_1 -> client1
+                    OTHER_USER_ID_2 -> client2
+                    else -> error("No client set-up for user $selectedUserId!")
+                }
+            }
+
+        testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
+
+        underTest =
+            KeyguardQuickAffordanceRemoteUserSelectionManager(
+                scope = testScope.backgroundScope,
+                userTracker = userTracker,
+                clientFactory = clientFactory,
+                userHandle = userHandle,
+            )
+    }
+
+    @Test
+    fun `selections - primary user process`() =
+        testScope.runTest {
+            val values = mutableListOf<Map<String, List<String>>>()
+            val job = launch { underTest.selections.toList(values) }
+
+            runCurrent()
+            assertThat(values.last()).isEmpty()
+
+            client1.insertSelection(
+                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+            )
+            client2.insertSelection(
+                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2,
+            )
+
+            userTracker.set(
+                userInfos = userTracker.userProfiles,
+                selectedUserIndex = 1,
+            )
+            runCurrent()
+            assertThat(values.last())
+                .isEqualTo(
+                    mapOf(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+                            listOf(
+                                FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+                            ),
+                    )
+                )
+
+            userTracker.set(
+                userInfos = userTracker.userProfiles,
+                selectedUserIndex = 2,
+            )
+            runCurrent()
+            assertThat(values.last())
+                .isEqualTo(
+                    mapOf(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+                            listOf(
+                                FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2,
+                            ),
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `selections - secondary user process - always empty`() =
+        testScope.runTest {
+            whenever(userHandle.isSystem).thenReturn(false)
+            val values = mutableListOf<Map<String, List<String>>>()
+            val job = launch { underTest.selections.toList(values) }
+
+            runCurrent()
+            assertThat(values.last()).isEmpty()
+
+            client1.insertSelection(
+                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+            )
+            userTracker.set(
+                userInfos = userTracker.userProfiles,
+                selectedUserIndex = 1,
+            )
+            runCurrent()
+            assertThat(values.last()).isEmpty()
+
+            job.cancel()
+        }
+
+    @Test
+    fun setSelections() =
+        testScope.runTest {
+            userTracker.set(
+                userInfos = userTracker.userProfiles,
+                selectedUserIndex = 1,
+            )
+            runCurrent()
+
+            underTest.setSelections(
+                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                affordanceIds = listOf(FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1),
+            )
+            runCurrent()
+
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+                            listOf(
+                                FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+                            ),
+                    )
+                )
+        }
+
+    companion object {
+        private const val OTHER_USER_ID_1 = UserHandle.MIN_SECONDARY_USER_ID + 1
+        private const val OTHER_USER_ID_2 = UserHandle.MIN_SECONDARY_USER_ID + 2
+    }
+}
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 61a3f9f..6255980 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
@@ -24,17 +24,18 @@
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -50,7 +51,7 @@
         MockitoAnnotations.initMocks(this)
         whenever(controller.intent).thenReturn(INTENT_1)
 
-        underTest = QrCodeScannerKeyguardQuickAffordanceConfig(controller)
+        underTest = QrCodeScannerKeyguardQuickAffordanceConfig(mock(), controller)
     }
 
     @Test
@@ -134,6 +135,33 @@
             )
     }
 
+    @Test
+    fun `getPickerScreenState - enabled if configured on device - can open camera`() = runTest {
+        whenever(controller.isAvailableOnDevice).thenReturn(true)
+        whenever(controller.isAbleToOpenCameraApp).thenReturn(true)
+
+        assertThat(underTest.getPickerScreenState())
+            .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default)
+    }
+
+    @Test
+    fun `getPickerScreenState - disabled if configured on device - cannot open camera`() = runTest {
+        whenever(controller.isAvailableOnDevice).thenReturn(true)
+        whenever(controller.isAbleToOpenCameraApp).thenReturn(false)
+
+        assertThat(underTest.getPickerScreenState())
+            .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
+    }
+
+    @Test
+    fun `getPickerScreenState - unavailable if not configured on device`() = runTest {
+        whenever(controller.isAvailableOnDevice).thenReturn(false)
+        whenever(controller.isAbleToOpenCameraApp).thenReturn(true)
+
+        assertThat(underTest.getPickerScreenState())
+            .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+    }
+
     private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.LockScreenState?) {
         assertThat(latest)
             .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
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 c05beef..d875dd9 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
@@ -33,9 +33,11 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.wallet.controller.QuickAccessWalletController
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,6 +46,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@@ -59,6 +62,7 @@
 
         underTest =
             QuickAccessWalletKeyguardQuickAffordanceConfig(
+                context,
                 walletController,
                 activityStarter,
             )
@@ -150,6 +154,44 @@
             )
     }
 
+    @Test
+    fun `getPickerScreenState - default`() = runTest {
+        setUpState()
+
+        assertThat(underTest.getPickerScreenState())
+            .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default)
+    }
+
+    @Test
+    fun `getPickerScreenState - unavailable`() = runTest {
+        setUpState(
+            isWalletEnabled = false,
+        )
+
+        assertThat(underTest.getPickerScreenState())
+            .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+    }
+
+    @Test
+    fun `getPickerScreenState - disabled when there is no icon`() = runTest {
+        setUpState(
+            hasWalletIcon = false,
+        )
+
+        assertThat(underTest.getPickerScreenState())
+            .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
+    }
+
+    @Test
+    fun `getPickerScreenState - disabled when there is no card`() = runTest {
+        setUpState(
+            hasSelectedCard = false,
+        )
+
+        assertThat(underTest.getPickerScreenState())
+            .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
+    }
+
     private fun setUpState(
         isWalletEnabled: Boolean = true,
         isWalletQuerySuccessful: Boolean = true,
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..c40488a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -0,0 +1,291 @@
+/*
+ * 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.content.pm.UserInfo
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient
+import com.android.systemui.util.FakeSharedPreferences
+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
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+
+@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
+    private lateinit var userTracker: FakeUserTracker
+    private lateinit var client1: FakeKeyguardQuickAffordanceProviderClient
+    private lateinit var client2: FakeKeyguardQuickAffordanceProviderClient
+
+    @Before
+    fun setUp() {
+        config1 =
+            FakeKeyguardQuickAffordanceConfig(
+                FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1
+            )
+        config2 =
+            FakeKeyguardQuickAffordanceConfig(
+                FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2
+            )
+        val scope = CoroutineScope(IMMEDIATE)
+        userTracker = FakeUserTracker()
+        val localUserSelectionManager =
+            KeyguardQuickAffordanceLocalUserSelectionManager(
+                context = context,
+                userFileManager =
+                    mock<UserFileManager>().apply {
+                        whenever(
+                                getSharedPreferences(
+                                    anyString(),
+                                    anyInt(),
+                                    anyInt(),
+                                )
+                            )
+                            .thenReturn(FakeSharedPreferences())
+                    },
+                userTracker = userTracker,
+                broadcastDispatcher = fakeBroadcastDispatcher,
+            )
+        client1 = FakeKeyguardQuickAffordanceProviderClient()
+        client2 = FakeKeyguardQuickAffordanceProviderClient()
+        val remoteUserSelectionManager =
+            KeyguardQuickAffordanceRemoteUserSelectionManager(
+                scope = scope,
+                userTracker = userTracker,
+                clientFactory =
+                    FakeKeyguardQuickAffordanceProviderClientFactory(
+                        userTracker,
+                    ) { selectedUserId ->
+                        when (selectedUserId) {
+                            SECONDARY_USER_1 -> client1
+                            SECONDARY_USER_2 -> client2
+                            else -> error("No set-up client for user $selectedUserId!")
+                        }
+                    },
+                userHandle = UserHandle.SYSTEM,
+            )
+
+        underTest =
+            KeyguardQuickAffordanceRepository(
+                appContext = context,
+                scope = scope,
+                localUserSelectionManager = localUserSelectionManager,
+                remoteUserSelectionManager = remoteUserSelectionManager,
+                userTracker = userTracker,
+                legacySettingSyncer =
+                    KeyguardQuickAffordanceLegacySettingSyncer(
+                        scope = scope,
+                        backgroundDispatcher = IMMEDIATE,
+                        secureSettings = FakeSettings(),
+                        selectionsManager = localUserSelectionManager,
+                    ),
+                configs = setOf(config1, config2),
+                dumpManager = mock(),
+                userHandle = UserHandle.SYSTEM,
+            )
+    }
+
+    @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() =
+        runBlocking(IMMEDIATE) {
+            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() {
+        val slot1 = "slot1"
+        val slot2 = "slot2"
+        val slot3 = "slot3"
+        context.orCreateTestableResources.addOverride(
+            R.array.config_keyguardQuickAffordanceSlots,
+            arrayOf(
+                "$slot1:2",
+                "$slot2:4",
+                "$slot3:5",
+            ),
+        )
+
+        assertThat(underTest.getSlotPickerRepresentations())
+            .isEqualTo(
+                listOf(
+                    KeyguardSlotPickerRepresentation(
+                        id = slot1,
+                        maxSelectedAffordances = 2,
+                    ),
+                    KeyguardSlotPickerRepresentation(
+                        id = slot2,
+                        maxSelectedAffordances = 4,
+                    ),
+                    KeyguardSlotPickerRepresentation(
+                        id = slot3,
+                        maxSelectedAffordances = 5,
+                    ),
+                )
+            )
+    }
+
+    @Test
+    fun `selections for secondary user`() =
+        runBlocking(IMMEDIATE) {
+            userTracker.set(
+                userInfos =
+                    listOf(
+                        UserInfo(
+                            UserHandle.USER_SYSTEM,
+                            "Primary",
+                            /* flags= */ 0,
+                        ),
+                        UserInfo(
+                            SECONDARY_USER_1,
+                            "Secondary 1",
+                            /* flags= */ 0,
+                        ),
+                        UserInfo(
+                            SECONDARY_USER_2,
+                            "Secondary 2",
+                            /* flags= */ 0,
+                        ),
+                    ),
+                selectedUserIndex = 2,
+            )
+            client2.insertSelection(
+                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2,
+            )
+            val observed = mutableListOf<Map<String, List<KeyguardQuickAffordanceConfig>>>()
+            val job = underTest.selections.onEach { observed.add(it) }.launchIn(this)
+            yield()
+
+            assertSelections(
+                observed = observed.last(),
+                expected =
+                    mapOf(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+                            listOf(
+                                config2,
+                            ),
+                    )
+            )
+
+            job.cancel()
+        }
+
+    private 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
+        private const val SECONDARY_USER_1 = UserHandle.MIN_SECONDARY_USER_ID + 1
+        private const val SECONDARY_USER_2 = UserHandle.MIN_SECONDARY_USER_ID + 2
+    }
+}
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..5deac19 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
@@ -16,18 +16,36 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import android.graphics.Point
+import android.hardware.biometrics.BiometricSourceType
 import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.doze.DozeHost
+import com.android.systemui.doze.DozeMachine
+import com.android.systemui.doze.DozeTransitionCallback
+import com.android.systemui.doze.DozeTransitionListener
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
 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
+import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -43,6 +61,11 @@
     @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
+    @Mock private lateinit var dozeTransitionListener: DozeTransitionListener
+    @Mock private lateinit var authController: AuthController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     private lateinit var underTest: KeyguardRepositoryImpl
 
@@ -53,135 +76,472 @@
         underTest =
             KeyguardRepositoryImpl(
                 statusBarStateController,
-                keyguardStateController,
                 dozeHost,
+                wakefulnessLifecycle,
+                biometricUnlockController,
+                keyguardStateController,
+                keyguardUpdateMonitor,
+                dozeTransitionListener,
+                authController,
             )
     }
 
     @Test
-    fun animateBottomAreaDozingTransitions() = runBlockingTest {
-        assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
+    fun animateBottomAreaDozingTransitions() =
+        runTest(UnconfinedTestDispatcher()) {
+            assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
 
-        underTest.setAnimateDozingTransitions(true)
-        assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+            underTest.setAnimateDozingTransitions(true)
+            assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
 
-        underTest.setAnimateDozingTransitions(false)
-        assertThat(underTest.animateBottomAreaDozingTransitions.value).isFalse()
+            underTest.setAnimateDozingTransitions(false)
+            assertThat(underTest.animateBottomAreaDozingTransitions.value).isFalse()
 
-        underTest.setAnimateDozingTransitions(true)
-        assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
-    }
+            underTest.setAnimateDozingTransitions(true)
+            assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+        }
 
     @Test
-    fun bottomAreaAlpha() = runBlockingTest {
-        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+    fun bottomAreaAlpha() =
+        runTest(UnconfinedTestDispatcher()) {
+            assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
 
-        underTest.setBottomAreaAlpha(0.1f)
-        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
+            underTest.setBottomAreaAlpha(0.1f)
+            assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
 
-        underTest.setBottomAreaAlpha(0.2f)
-        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
+            underTest.setBottomAreaAlpha(0.2f)
+            assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
 
-        underTest.setBottomAreaAlpha(0.3f)
-        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
+            underTest.setBottomAreaAlpha(0.3f)
+            assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
 
-        underTest.setBottomAreaAlpha(0.5f)
-        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
+            underTest.setBottomAreaAlpha(0.5f)
+            assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
 
-        underTest.setBottomAreaAlpha(1.0f)
-        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
-    }
+            underTest.setBottomAreaAlpha(1.0f)
+            assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+        }
 
     @Test
-    fun clockPosition() = runBlockingTest {
-        assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
+    fun clockPosition() =
+        runTest(UnconfinedTestDispatcher()) {
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
 
-        underTest.setClockPosition(0, 1)
-        assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
+            underTest.setClockPosition(0, 1)
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
 
-        underTest.setClockPosition(1, 9)
-        assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
+            underTest.setClockPosition(1, 9)
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
 
-        underTest.setClockPosition(1, 0)
-        assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
+            underTest.setClockPosition(1, 0)
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
 
-        underTest.setClockPosition(3, 1)
-        assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
-    }
+            underTest.setClockPosition(3, 1)
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
+        }
 
     @Test
-    fun isKeyguardShowing() = runBlockingTest {
-        whenever(keyguardStateController.isShowing).thenReturn(false)
-        var latest: Boolean? = null
-        val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
+    fun isKeyguardShowing() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(keyguardStateController.isShowing).thenReturn(false)
+            var latest: Boolean? = null
+            val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
 
-        assertThat(latest).isFalse()
-        assertThat(underTest.isKeyguardShowing()).isFalse()
+            assertThat(latest).isFalse()
+            assertThat(underTest.isKeyguardShowing()).isFalse()
 
-        val captor = argumentCaptor<KeyguardStateController.Callback>()
-        verify(keyguardStateController).addCallback(captor.capture())
+            val captor = argumentCaptor<KeyguardStateController.Callback>()
+            verify(keyguardStateController).addCallback(captor.capture())
 
-        whenever(keyguardStateController.isShowing).thenReturn(true)
-        captor.value.onKeyguardShowingChanged()
-        assertThat(latest).isTrue()
-        assertThat(underTest.isKeyguardShowing()).isTrue()
+            whenever(keyguardStateController.isShowing).thenReturn(true)
+            captor.value.onKeyguardShowingChanged()
+            assertThat(latest).isTrue()
+            assertThat(underTest.isKeyguardShowing()).isTrue()
 
-        whenever(keyguardStateController.isShowing).thenReturn(false)
-        captor.value.onKeyguardShowingChanged()
-        assertThat(latest).isFalse()
-        assertThat(underTest.isKeyguardShowing()).isFalse()
+            whenever(keyguardStateController.isShowing).thenReturn(false)
+            captor.value.onKeyguardShowingChanged()
+            assertThat(latest).isFalse()
+            assertThat(underTest.isKeyguardShowing()).isFalse()
 
-        job.cancel()
-    }
+            job.cancel()
+        }
 
     @Test
-    fun isDozing() = runBlockingTest {
-        var latest: Boolean? = null
-        val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+    fun isDozing() =
+        runTest(UnconfinedTestDispatcher()) {
+            var latest: Boolean? = null
+            val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
 
-        val captor = argumentCaptor<DozeHost.Callback>()
-        verify(dozeHost).addCallback(captor.capture())
+            val captor = argumentCaptor<DozeHost.Callback>()
+            verify(dozeHost).addCallback(captor.capture())
 
-        captor.value.onDozingChanged(true)
-        assertThat(latest).isTrue()
+            captor.value.onDozingChanged(true)
+            assertThat(latest).isTrue()
 
-        captor.value.onDozingChanged(false)
-        assertThat(latest).isFalse()
+            captor.value.onDozingChanged(false)
+            assertThat(latest).isFalse()
 
-        job.cancel()
-        verify(dozeHost).removeCallback(captor.value)
-    }
+            job.cancel()
+            verify(dozeHost).removeCallback(captor.value)
+        }
 
     @Test
-    fun `isDozing - starts with correct initial value for isDozing`() = runBlockingTest {
-        var latest: Boolean? = null
+    fun `isDozing - starts with correct initial value for isDozing`() =
+        runTest(UnconfinedTestDispatcher()) {
+            var latest: Boolean? = null
 
-        whenever(statusBarStateController.isDozing).thenReturn(true)
-        var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
-        assertThat(latest).isTrue()
-        job.cancel()
+            whenever(statusBarStateController.isDozing).thenReturn(true)
+            var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+            assertThat(latest).isTrue()
+            job.cancel()
 
-        whenever(statusBarStateController.isDozing).thenReturn(false)
-        job = underTest.isDozing.onEach { latest = it }.launchIn(this)
-        assertThat(latest).isFalse()
-        job.cancel()
-    }
+            whenever(statusBarStateController.isDozing).thenReturn(false)
+            job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+            assertThat(latest).isFalse()
+            job.cancel()
+        }
 
     @Test
-    fun dozeAmount() = runBlockingTest {
-        val values = mutableListOf<Float>()
-        val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
+    fun dozeAmount() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Float>()
+            val job = underTest.linearDozeAmount.onEach(values::add).launchIn(this)
 
-        val captor = argumentCaptor<StatusBarStateController.StateListener>()
-        verify(statusBarStateController).addCallback(captor.capture())
+            val captor = argumentCaptor<StatusBarStateController.StateListener>()
+            verify(statusBarStateController).addCallback(captor.capture())
 
-        captor.value.onDozeAmountChanged(0.433f, 0.4f)
-        captor.value.onDozeAmountChanged(0.498f, 0.5f)
-        captor.value.onDozeAmountChanged(0.661f, 0.65f)
+            captor.value.onDozeAmountChanged(0.433f, 0.4f)
+            captor.value.onDozeAmountChanged(0.498f, 0.5f)
+            captor.value.onDozeAmountChanged(0.661f, 0.65f)
 
-        assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f))
+            assertThat(values).isEqualTo(listOf(0f, 0.433f, 0.498f, 0.661f))
 
-        job.cancel()
-        verify(statusBarStateController).removeCallback(captor.value)
-    }
+            job.cancel()
+            verify(statusBarStateController).removeCallback(captor.value)
+        }
+
+    @Test
+    fun wakefulness() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<WakefulnessModel>()
+            val job = underTest.wakefulness.onEach(values::add).launchIn(this)
+
+            val captor = argumentCaptor<WakefulnessLifecycle.Observer>()
+            verify(wakefulnessLifecycle).addObserver(captor.capture())
+
+            whenever(wakefulnessLifecycle.wakefulness)
+                .thenReturn(WakefulnessLifecycle.WAKEFULNESS_WAKING)
+            captor.value.onStartedWakingUp()
+
+            whenever(wakefulnessLifecycle.wakefulness)
+                .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE)
+            captor.value.onFinishedWakingUp()
+
+            whenever(wakefulnessLifecycle.wakefulness)
+                .thenReturn(WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP)
+            captor.value.onStartedGoingToSleep()
+
+            whenever(wakefulnessLifecycle.wakefulness)
+                .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP)
+            captor.value.onFinishedGoingToSleep()
+
+            assertThat(values.map { it.state })
+                .isEqualTo(
+                    listOf(
+                        // Initial value will be ASLEEP
+                        WakefulnessState.ASLEEP,
+                        WakefulnessState.STARTING_TO_WAKE,
+                        WakefulnessState.AWAKE,
+                        WakefulnessState.STARTING_TO_SLEEP,
+                        WakefulnessState.ASLEEP,
+                    )
+                )
+
+            job.cancel()
+            verify(wakefulnessLifecycle).removeObserver(captor.value)
+        }
+
+    @Test
+    fun isUdfpsSupported() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
+            assertThat(underTest.isUdfpsSupported()).isTrue()
+
+            whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(false)
+            assertThat(underTest.isUdfpsSupported()).isFalse()
+        }
+
+    @Test
+    fun isBouncerShowing() =
+        runTest(UnconfinedTestDispatcher()) {
+            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 isKeyguardGoingAway() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
+            var latest: Boolean? = null
+            val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            val captor = argumentCaptor<KeyguardStateController.Callback>()
+            verify(keyguardStateController).addCallback(captor.capture())
+
+            whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
+            captor.value.onKeyguardGoingAwayChanged()
+            assertThat(latest).isTrue()
+
+            whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
+            captor.value.onKeyguardGoingAwayChanged()
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isDreaming() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
+            var latest: Boolean? = null
+            val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+            verify(keyguardUpdateMonitor).registerCallback(captor.capture())
+
+            captor.value.onDreamingStateChanged(true)
+            assertThat(latest).isTrue()
+
+            captor.value.onDreamingStateChanged(false)
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun biometricUnlockState() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<BiometricUnlockModel>()
+            val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
+
+            val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
+            verify(biometricUnlockController).addBiometricModeListener(captor.capture())
+
+            listOf(
+                    BiometricUnlockController.MODE_NONE,
+                    BiometricUnlockController.MODE_WAKE_AND_UNLOCK,
+                    BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING,
+                    BiometricUnlockController.MODE_SHOW_BOUNCER,
+                    BiometricUnlockController.MODE_ONLY_WAKE,
+                    BiometricUnlockController.MODE_UNLOCK_COLLAPSING,
+                    BiometricUnlockController.MODE_DISMISS_BOUNCER,
+                    BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM,
+                )
+                .forEach {
+                    whenever(biometricUnlockController.mode).thenReturn(it)
+                    captor.value.onModeChanged(it)
+                }
+
+            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)
+        }
+
+    @Test
+    fun dozeTransitionModel() =
+        runTest(UnconfinedTestDispatcher()) {
+            // For the initial state
+            whenever(dozeTransitionListener.oldState).thenReturn(DozeMachine.State.UNINITIALIZED)
+            whenever(dozeTransitionListener.newState).thenReturn(DozeMachine.State.UNINITIALIZED)
+
+            val values = mutableListOf<DozeTransitionModel>()
+            val job = underTest.dozeTransitionModel.onEach(values::add).launchIn(this)
+
+            val listener =
+                withArgCaptor<DozeTransitionCallback> {
+                    verify(dozeTransitionListener).addCallback(capture())
+                }
+
+            // These don't have to reflect real transitions from the DozeMachine. Only that the
+            // transitions are properly emitted
+            listener.onDozeTransition(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE)
+            listener.onDozeTransition(DozeMachine.State.DOZE, DozeMachine.State.DOZE_AOD)
+            listener.onDozeTransition(DozeMachine.State.DOZE_AOD_DOCKED, DozeMachine.State.FINISH)
+            listener.onDozeTransition(
+                DozeMachine.State.DOZE_REQUEST_PULSE,
+                DozeMachine.State.DOZE_PULSING
+            )
+            listener.onDozeTransition(
+                DozeMachine.State.DOZE_SUSPEND_TRIGGERS,
+                DozeMachine.State.DOZE_PULSE_DONE
+            )
+            listener.onDozeTransition(
+                DozeMachine.State.DOZE_AOD_PAUSING,
+                DozeMachine.State.DOZE_AOD_PAUSED
+            )
+
+            assertThat(values)
+                .isEqualTo(
+                    listOf(
+                        // Initial value will be UNINITIALIZED
+                        DozeTransitionModel(
+                            DozeStateModel.UNINITIALIZED,
+                            DozeStateModel.UNINITIALIZED
+                        ),
+                        DozeTransitionModel(DozeStateModel.INITIALIZED, DozeStateModel.DOZE),
+                        DozeTransitionModel(DozeStateModel.DOZE, DozeStateModel.DOZE_AOD),
+                        DozeTransitionModel(DozeStateModel.DOZE_AOD_DOCKED, DozeStateModel.FINISH),
+                        DozeTransitionModel(
+                            DozeStateModel.DOZE_REQUEST_PULSE,
+                            DozeStateModel.DOZE_PULSING
+                        ),
+                        DozeTransitionModel(
+                            DozeStateModel.DOZE_SUSPEND_TRIGGERS,
+                            DozeStateModel.DOZE_PULSE_DONE
+                        ),
+                        DozeTransitionModel(
+                            DozeStateModel.DOZE_AOD_PAUSING,
+                            DozeStateModel.DOZE_AOD_PAUSED
+                        ),
+                    )
+                )
+
+            job.cancel()
+            verify(dozeTransitionListener).removeCallback(listener)
+        }
+
+    @Test
+    fun fingerprintSensorLocation() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Point?>()
+            val job = underTest.fingerprintSensorLocation.onEach(values::add).launchIn(this)
+
+            val captor = argumentCaptor<AuthController.Callback>()
+            verify(authController).addCallback(captor.capture())
+
+            // An initial, null value should be initially emitted so that flows combined with this
+            // one
+            // emit values immediately. The sensor location is expected to be nullable, so anyone
+            // consuming it should handle that properly.
+            assertThat(values).isEqualTo(listOf(null))
+
+            listOf(Point(500, 500), Point(0, 0), null, Point(250, 250))
+                .onEach {
+                    whenever(authController.fingerprintSensorLocation).thenReturn(it)
+                    captor.value.onFingerprintLocationChanged()
+                }
+                .also { dispatchedSensorLocations ->
+                    assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations)
+                }
+
+            job.cancel()
+        }
+
+    @Test
+    fun faceSensorLocation() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Point?>()
+            val job = underTest.faceSensorLocation.onEach(values::add).launchIn(this)
+
+            val captor = argumentCaptor<AuthController.Callback>()
+            verify(authController).addCallback(captor.capture())
+
+            // An initial, null value should be initially emitted so that flows combined with this
+            // one
+            // emit values immediately. The sensor location is expected to be nullable, so anyone
+            // consuming it should handle that properly.
+            assertThat(values).isEqualTo(listOf(null))
+
+            listOf(
+                    Point(500, 500),
+                    Point(0, 0),
+                    null,
+                    Point(250, 250),
+                )
+                .onEach {
+                    whenever(authController.faceSensorLocation).thenReturn(it)
+                    captor.value.onFaceSensorLocationChanged()
+                }
+                .also { dispatchedSensorLocations ->
+                    assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations)
+                }
+
+            job.cancel()
+        }
+
+    @Test
+    fun biometricUnlockSource() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<BiometricUnlockSource?>()
+            val job = underTest.biometricUnlockSource.onEach(values::add).launchIn(this)
+
+            val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+            verify(keyguardUpdateMonitor).registerCallback(captor.capture())
+
+            // An initial, null value should be initially emitted so that flows combined with this
+            // one
+            // emit values immediately. The biometric unlock source is expected to be nullable, so
+            // anyone consuming it should handle that properly.
+            assertThat(values).isEqualTo(listOf(null))
+
+            listOf(
+                    BiometricSourceType.FINGERPRINT,
+                    BiometricSourceType.IRIS,
+                    null,
+                    BiometricSourceType.FACE,
+                    BiometricSourceType.FINGERPRINT,
+                )
+                .onEach { biometricSourceType ->
+                    captor.value.onBiometricAuthenticated(0, biometricSourceType, false)
+                }
+
+            assertThat(values)
+                .isEqualTo(
+                    listOf(
+                        null,
+                        BiometricUnlockSource.FINGERPRINT_SENSOR,
+                        BiometricUnlockSource.FACE_SENSOR,
+                        null,
+                        BiometricUnlockSource.FACE_SENSOR,
+                        BiometricUnlockSource.FINGERPRINT_SENSOR,
+                    )
+                )
+
+            job.cancel()
+        }
 }
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..ce9c1da 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
@@ -22,11 +22,12 @@
 import android.util.Log.TerribleFailure
 import android.util.Log.TerribleFailureHandler
 import android.view.Choreographer.FrameCallback
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Interpolators
+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.BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -38,7 +39,6 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.launchIn
@@ -63,7 +63,7 @@
 
     @Before
     fun setUp() {
-        underTest = KeyguardTransitionRepository()
+        underTest = KeyguardTransitionRepositoryImpl()
         wtfHandler = WtfHandler()
         oldWtfHandler = Log.setWtfHandler(wtfHandler)
     }
@@ -91,18 +91,52 @@
                 }
             }
 
-            assertSteps(steps, listWithStep(BigDecimal(.1)))
+            assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN)
 
             job.cancel()
             provider.stop()
         }
 
     @Test
-    fun `startTransition called during another transition fails`() {
-        underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, null))
-        underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, BOUNCER, null))
+    @FlakyTest(bugId = 260213291)
+    fun `starting second transition will cancel the first transition`() {
+        runBlocking(IMMEDIATE) {
+            val (animator, provider) = setupAnimator(this)
 
-        assertThat(wtfHandler.failed).isTrue()
+            val steps = mutableListOf<TransitionStep>()
+            val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+
+            underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator))
+            // 3 yields(), alternating with the animator, results in a value 0.1, which can be
+            // canceled and tested against
+            yield()
+            yield()
+            yield()
+
+            // Now start 2nd transition, which will interrupt the first
+            val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+            val (animator2, provider2) = setupAnimator(this)
+            underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, animator2))
+
+            val startTime = System.currentTimeMillis()
+            while (animator2.isRunning()) {
+                yield()
+                if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
+                    fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
+                }
+            }
+
+            val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+            assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+            val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.9))
+            assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+            job.cancel()
+            job2.cancel()
+            provider.stop()
+            provider2.stop()
+        }
     }
 
     @Test
@@ -128,11 +162,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()
         }
 
@@ -161,11 +199,15 @@
         assertThat(wtfHandler.failed).isTrue()
     }
 
-    private fun listWithStep(step: BigDecimal): List<BigDecimal> {
+    private fun listWithStep(
+        step: BigDecimal,
+        start: BigDecimal = BigDecimal.ZERO,
+        stop: BigDecimal = BigDecimal.ONE,
+    ): List<BigDecimal> {
         val steps = mutableListOf<BigDecimal>()
 
-        var i = BigDecimal.ZERO
-        while (i.compareTo(BigDecimal.ONE) <= 0) {
+        var i = start
+        while (i.compareTo(stop) <= 0) {
             steps.add(i)
             i = (i + step).setScale(2, RoundingMode.HALF_UP)
         }
@@ -173,19 +215,43 @@
         return steps
     }
 
-    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))
+    private fun assertSteps(
+        steps: List<TransitionStep>,
+        fractions: List<BigDecimal>,
+        from: KeyguardState,
+        to: KeyguardState,
+    ) {
+        assertThat(steps[0])
+            .isEqualTo(
+                TransitionStep(
+                    from,
+                    to,
+                    fractions[0].toFloat(),
+                    TransitionState.STARTED,
+                    OWNER_NAME
+                )
+            )
         fractions.forEachIndexed { index, fraction ->
             assertThat(steps[index + 1])
                 .isEqualTo(
-                    TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING)
+                    TransitionStep(
+                        from,
+                        to,
+                        fraction.toFloat(),
+                        TransitionState.RUNNING,
+                        OWNER_NAME
+                    )
                 )
         }
+        val lastValue = fractions[fractions.size - 1].toFloat()
+        val status =
+            if (lastValue < 1f) {
+                TransitionState.CANCELED
+            } else {
+                TransitionState.FINISHED
+            }
         assertThat(steps[steps.size - 1])
-            .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+            .isEqualTo(TransitionStep(from, to, lastValue, status, OWNER_NAME))
 
         assertThat(wtfHandler.failed).isFalse()
     }
@@ -222,7 +288,7 @@
                 scope.launch {
                     frames.collect {
                         // Delay is required for AnimationHandler to properly register a callback
-                        delay(1)
+                        yield()
                         val (frameNumber, callback) = it
                         callback?.doFrame(frameNumber)
                     }
@@ -235,7 +301,7 @@
         }
 
         override fun postFrameCallback(cb: FrameCallback) {
-            frames.value = Pair(++frameCount, cb)
+            frames.value = Pair(frameCount++, cb)
         }
         override fun postCommitCallback(runnable: Runnable) {}
         override fun getFrameTime() = frameCount
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
new file mode 100644
index 0000000..d2db910
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -0,0 +1,167 @@
+/*
+ * 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.graphics.Point
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.statusbar.CircleReveal
+import com.android.systemui.statusbar.LightRevealEffect
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class LightRevealScrimRepositoryTest : SysuiTestCase() {
+    private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
+    private lateinit var underTest: LightRevealScrimRepositoryImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        fakeKeyguardRepository = FakeKeyguardRepository()
+        underTest = LightRevealScrimRepositoryImpl(fakeKeyguardRepository, context)
+    }
+
+    @Test
+    fun `nextRevealEffect - effect switches between default and biometric with no dupes`() =
+        runTest {
+            val values = mutableListOf<LightRevealEffect>()
+            val job = launch { underTest.revealEffect.collect { values.add(it) } }
+
+            // We should initially emit the default reveal effect.
+            runCurrent()
+            values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT })
+
+            // The source and sensor locations are still null, so we should still be using the
+            // default reveal despite a biometric unlock.
+            fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+
+            runCurrent()
+            values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },)
+
+            // We got a source but still have no sensor locations, so should be sticking with
+            // the default effect.
+            fakeKeyguardRepository.setBiometricUnlockSource(
+                BiometricUnlockSource.FINGERPRINT_SENSOR
+            )
+
+            runCurrent()
+            values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },)
+
+            // We got a location for the face sensor, but we unlocked with fingerprint.
+            val faceLocation = Point(250, 0)
+            fakeKeyguardRepository.setFaceSensorLocation(faceLocation)
+
+            runCurrent()
+            values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },)
+
+            // Now we have fingerprint sensor locations, and wake and unlock via fingerprint.
+            val fingerprintLocation = Point(500, 500)
+            fakeKeyguardRepository.setFingerprintSensorLocation(fingerprintLocation)
+            fakeKeyguardRepository.setBiometricUnlockSource(
+                BiometricUnlockSource.FINGERPRINT_SENSOR
+            )
+            fakeKeyguardRepository.setBiometricUnlockState(
+                BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+            )
+
+            // We should now have switched to the circle reveal, at the fingerprint location.
+            runCurrent()
+            values.assertEffectsMatchPredicates(
+                { it == DEFAULT_REVEAL_EFFECT },
+                {
+                    it is CircleReveal &&
+                        it.centerX == fingerprintLocation.x &&
+                        it.centerY == fingerprintLocation.y
+                },
+            )
+
+            // Subsequent wake and unlocks should not emit duplicate, identical CircleReveals.
+            val valuesPrevSize = values.size
+            fakeKeyguardRepository.setBiometricUnlockState(
+                BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+            )
+            fakeKeyguardRepository.setBiometricUnlockState(
+                BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+            )
+            assertEquals(valuesPrevSize, values.size)
+
+            // Non-biometric unlock, we should return to the default reveal.
+            fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+
+            runCurrent()
+            values.assertEffectsMatchPredicates(
+                { it == DEFAULT_REVEAL_EFFECT },
+                {
+                    it is CircleReveal &&
+                        it.centerX == fingerprintLocation.x &&
+                        it.centerY == fingerprintLocation.y
+                },
+                { it == DEFAULT_REVEAL_EFFECT },
+            )
+
+            // We already have a face location, so switching to face source should update the
+            // CircleReveal.
+            fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
+            runCurrent()
+            fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            runCurrent()
+
+            values.assertEffectsMatchPredicates(
+                { it == DEFAULT_REVEAL_EFFECT },
+                {
+                    it is CircleReveal &&
+                        it.centerX == fingerprintLocation.x &&
+                        it.centerY == fingerprintLocation.y
+                },
+                { it == DEFAULT_REVEAL_EFFECT },
+                {
+                    it is CircleReveal &&
+                        it.centerX == faceLocation.x &&
+                        it.centerY == faceLocation.y
+                },
+            )
+
+            job.cancel()
+        }
+
+    /**
+     * Asserts that the list of LightRevealEffects satisfies the list of predicates, in order, with
+     * no leftover elements.
+     */
+    private fun List<LightRevealEffect>.assertEffectsMatchPredicates(
+        vararg predicates: (LightRevealEffect) -> Boolean
+    ) {
+        println(this)
+        assertEquals(predicates.size, this.size)
+
+        assertFalse(
+            zip(predicates) { effect, predicate -> predicate(effect) }.any { matched -> !matched }
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt
deleted file mode 100644
index 3a61c57..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt
+++ /dev/null
@@ -1,86 +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.keyguard.domain.interactor
-
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.phone.KeyguardBouncer
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class BouncerCallbackInteractorTest : SysuiTestCase() {
-    private val bouncerCallbackInteractor = BouncerCallbackInteractor()
-    @Mock private lateinit var bouncerExpansionCallback: KeyguardBouncer.BouncerExpansionCallback
-    @Mock private lateinit var keyguardResetCallback: KeyguardBouncer.KeyguardResetCallback
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        bouncerCallbackInteractor.addBouncerExpansionCallback(bouncerExpansionCallback)
-        bouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback)
-    }
-
-    @Test
-    fun testOnFullyShown() {
-        bouncerCallbackInteractor.dispatchFullyShown()
-        verify(bouncerExpansionCallback).onFullyShown()
-    }
-
-    @Test
-    fun testOnFullyHidden() {
-        bouncerCallbackInteractor.dispatchFullyHidden()
-        verify(bouncerExpansionCallback).onFullyHidden()
-    }
-
-    @Test
-    fun testOnExpansionChanged() {
-        bouncerCallbackInteractor.dispatchExpansionChanged(5f)
-        verify(bouncerExpansionCallback).onExpansionChanged(5f)
-    }
-
-    @Test
-    fun testOnVisibilityChanged() {
-        bouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
-        verify(bouncerExpansionCallback).onVisibilityChanged(false)
-    }
-
-    @Test
-    fun testOnStartingToHide() {
-        bouncerCallbackInteractor.dispatchStartingToHide()
-        verify(bouncerExpansionCallback).onStartingToHide()
-    }
-
-    @Test
-    fun testOnStartingToShow() {
-        bouncerCallbackInteractor.dispatchStartingToShow()
-        verify(bouncerExpansionCallback).onStartingToShow()
-    }
-
-    @Test
-    fun testOnKeyguardReset() {
-        bouncerCallbackInteractor.dispatchReset()
-        verify(keyguardResetCallback).onKeyguardReset()
-    }
-}
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
deleted file mode 100644
index e6c8dd8..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
+++ /dev/null
@@ -1,278 +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.keyguard.domain.interactor
-
-import android.os.Looper
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.DejankUtils
-import com.android.systemui.SysuiTestCase
-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.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
-import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
-import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.utils.os.FakeHandler
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Answers
-import org.mockito.Mock
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
-class BouncerInteractorTest : SysuiTestCase() {
-    @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 keyguardStateController: KeyguardStateController
-    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
-    @Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor
-    @Mock private lateinit var falsingCollector: FalsingCollector
-    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
-    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    private val mainHandler = FakeHandler(Looper.getMainLooper())
-    private lateinit var bouncerInteractor: BouncerInteractor
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        DejankUtils.setImmediate(true)
-        bouncerInteractor =
-            BouncerInteractor(
-                repository,
-                bouncerView,
-                mainHandler,
-                keyguardStateController,
-                keyguardSecurityModel,
-                bouncerCallbackInteractor,
-                falsingCollector,
-                dismissCallbackRegistry,
-                keyguardBypassController,
-                keyguardUpdateMonitor,
-            )
-        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
-        `when`(repository.show.value).thenReturn(null)
-    }
-
-    @Test
-    fun testShow_isScrimmed() {
-        bouncerInteractor.show(true)
-        verify(repository).setShowMessage(null)
-        verify(repository).setOnScreenTurnedOff(false)
-        verify(repository).setKeyguardAuthenticated(null)
-        verify(repository).setHide(false)
-        verify(repository).setStartingToHide(false)
-        verify(repository).setScrimmed(true)
-        verify(repository).setExpansion(EXPANSION_VISIBLE)
-        verify(repository).setShowingSoon(true)
-        verify(keyguardStateController).notifyBouncerShowing(true)
-        verify(bouncerCallbackInteractor).dispatchStartingToShow()
-        verify(repository).setVisible(true)
-        verify(repository).setShow(any(KeyguardBouncerModel::class.java))
-        verify(repository).setShowingSoon(false)
-    }
-
-    @Test
-    fun testShow_isNotScrimmed() {
-        verify(repository, never()).setExpansion(EXPANSION_VISIBLE)
-    }
-
-    @Test
-    fun testShow_keyguardIsDone() {
-        `when`(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
-        verify(keyguardStateController, never()).notifyBouncerShowing(true)
-        verify(bouncerCallbackInteractor, never()).dispatchStartingToShow()
-    }
-
-    @Test
-    fun testHide() {
-        bouncerInteractor.hide()
-        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)
-    }
-
-    @Test
-    fun testExpansion() {
-        `when`(repository.expansionAmount.value).thenReturn(0.5f)
-        bouncerInteractor.setExpansion(0.6f)
-        verify(repository).setExpansion(0.6f)
-        verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
-    }
-
-    @Test
-    fun testExpansion_fullyShown() {
-        `when`(repository.expansionAmount.value).thenReturn(0.5f)
-        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
-        bouncerInteractor.setExpansion(EXPANSION_VISIBLE)
-        verify(falsingCollector).onBouncerShown()
-        verify(bouncerCallbackInteractor).dispatchFullyShown()
-    }
-
-    @Test
-    fun testExpansion_fullyHidden() {
-        `when`(repository.expansionAmount.value).thenReturn(0.5f)
-        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
-        bouncerInteractor.setExpansion(EXPANSION_HIDDEN)
-        verify(repository).setVisible(false)
-        verify(repository).setShow(null)
-        verify(falsingCollector).onBouncerHidden()
-        verify(bouncerCallbackInteractor).dispatchReset()
-        verify(bouncerCallbackInteractor).dispatchFullyHidden()
-    }
-
-    @Test
-    fun testExpansion_startingToHide() {
-        `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        bouncerInteractor.setExpansion(0.1f)
-        verify(repository).setStartingToHide(true)
-        verify(bouncerCallbackInteractor).dispatchStartingToHide()
-    }
-
-    @Test
-    fun testShowMessage() {
-        bouncerInteractor.showMessage("abc", null)
-        verify(repository).setShowMessage(BouncerShowMessageModel("abc", null))
-    }
-
-    @Test
-    fun testDismissAction() {
-        val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
-        val cancelAction = mock(Runnable::class.java)
-        bouncerInteractor.setDismissAction(onDismissAction, cancelAction)
-        verify(repository)
-            .setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
-    }
-
-    @Test
-    fun testUpdateResources() {
-        bouncerInteractor.updateResources()
-        verify(repository).setResourceUpdateRequests(true)
-    }
-
-    @Test
-    fun testNotifyKeyguardAuthenticated() {
-        bouncerInteractor.notifyKeyguardAuthenticated(true)
-        verify(repository).setKeyguardAuthenticated(true)
-    }
-
-    @Test
-    fun testOnScreenTurnedOff() {
-        bouncerInteractor.onScreenTurnedOff()
-        verify(repository).setOnScreenTurnedOff(true)
-    }
-
-    @Test
-    fun testSetKeyguardPosition() {
-        bouncerInteractor.setKeyguardPosition(0f)
-        verify(repository).setKeyguardPosition(0f)
-    }
-
-    @Test
-    fun testNotifyKeyguardAuthenticatedHandled() {
-        bouncerInteractor.notifyKeyguardAuthenticatedHandled()
-        verify(repository).setKeyguardAuthenticated(null)
-    }
-
-    @Test
-    fun testNotifyUpdatedResources() {
-        bouncerInteractor.notifyUpdatedResources()
-        verify(repository).setResourceUpdateRequests(false)
-    }
-
-    @Test
-    fun testSetBackButtonEnabled() {
-        bouncerInteractor.setBackButtonEnabled(true)
-        verify(repository).setIsBackButtonEnabled(true)
-    }
-
-    @Test
-    fun testStartDisappearAnimation() {
-        val runnable = mock(Runnable::class.java)
-        bouncerInteractor.startDisappearAnimation(runnable)
-        verify(repository).setStartDisappearAnimation(any(Runnable::class.java))
-    }
-
-    @Test
-    fun testIsFullShowing() {
-        `when`(repository.isVisible.value).thenReturn(true)
-        `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
-        assertThat(bouncerInteractor.isFullyShowing()).isTrue()
-        `when`(repository.isVisible.value).thenReturn(false)
-        assertThat(bouncerInteractor.isFullyShowing()).isFalse()
-    }
-
-    @Test
-    fun testIsScrimmed() {
-        `when`(repository.isScrimmed.value).thenReturn(true)
-        assertThat(bouncerInteractor.isScrimmed()).isTrue()
-        `when`(repository.isScrimmed.value).thenReturn(false)
-        assertThat(bouncerInteractor.isScrimmed()).isFalse()
-    }
-
-    @Test
-    fun testIsInTransit() {
-        `when`(repository.showingSoon.value).thenReturn(true)
-        assertThat(bouncerInteractor.isInTransit()).isTrue()
-        `when`(repository.showingSoon.value).thenReturn(false)
-        assertThat(bouncerInteractor.isInTransit()).isFalse()
-        `when`(repository.expansionAmount.value).thenReturn(0.5f)
-        assertThat(bouncerInteractor.isInTransit()).isTrue()
-    }
-
-    @Test
-    fun testIsAnimatingAway() {
-        `when`(repository.startingDisappearAnimation.value).thenReturn(Runnable {})
-        assertThat(bouncerInteractor.isAnimatingAway()).isTrue()
-        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
-        assertThat(bouncerInteractor.isAnimatingAway()).isFalse()
-    }
-
-    @Test
-    fun testWillDismissWithAction() {
-        `when`(repository.onDismissAction.value?.onDismissAction)
-            .thenReturn(mock(ActivityStarter.OnDismissAction::class.java))
-        assertThat(bouncerInteractor.willDismissWithAction()).isTrue()
-        `when`(repository.onDismissAction.value?.onDismissAction).thenReturn(null)
-        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 7116cc1..1c1f039 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
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.Intent
+import android.os.UserHandle
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
@@ -25,18 +26,31 @@
 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.FakeKeyguardQuickAffordanceProviderClientFactory
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
 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
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.test.runBlockingTest
 import org.junit.Before
 import org.junit.Test
@@ -44,6 +58,8 @@
 import org.junit.runners.Parameterized
 import org.junit.runners.Parameterized.Parameter
 import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.ArgumentMatchers.same
 import org.mockito.Mock
@@ -189,11 +205,12 @@
                     /* startActivity= */ true,
                 ),
             )
+
+        private val IMMEDIATE = Dispatchers.Main.immediate
     }
 
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
     @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
     @Mock private lateinit var expandable: Expandable
@@ -206,17 +223,65 @@
     @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
     @JvmField @Parameter(4) var startActivity: Boolean = false
     private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+    private lateinit var userTracker: UserTracker
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(expandable.activityLaunchController()).thenReturn(animationController)
 
+        userTracker = FakeUserTracker()
         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 scope = CoroutineScope(IMMEDIATE)
+        val localUserSelectionManager =
+            KeyguardQuickAffordanceLocalUserSelectionManager(
+                context = context,
+                userFileManager =
+                    mock<UserFileManager>().apply {
+                        whenever(
+                                getSharedPreferences(
+                                    anyString(),
+                                    anyInt(),
+                                    anyInt(),
+                                )
+                            )
+                            .thenReturn(FakeSharedPreferences())
+                    },
+                userTracker = userTracker,
+                broadcastDispatcher = fakeBroadcastDispatcher,
+            )
+        val remoteUserSelectionManager =
+            KeyguardQuickAffordanceRemoteUserSelectionManager(
+                scope = scope,
+                userTracker = userTracker,
+                clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+                userHandle = UserHandle.SYSTEM,
+            )
+        val quickAffordanceRepository =
+            KeyguardQuickAffordanceRepository(
+                appContext = context,
+                scope = scope,
+                localUserSelectionManager = localUserSelectionManager,
+                remoteUserSelectionManager = remoteUserSelectionManager,
+                userTracker = userTracker,
+                legacySettingSyncer =
+                    KeyguardQuickAffordanceLegacySettingSyncer(
+                        scope = scope,
+                        backgroundDispatcher = IMMEDIATE,
+                        secureSettings = FakeSettings(),
+                        selectionsManager = localUserSelectionManager,
+                    ),
+                configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+                dumpManager = mock(),
+                userHandle = UserHandle.SYSTEM,
+            )
         underTest =
             KeyguardQuickAffordanceInteractor(
                 keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
@@ -229,14 +294,8 @@
                                 ),
                             KeyguardQuickAffordancePosition.BOTTOM_END to
                                 listOf(
-                                    object :
-                                        FakeKeyguardQuickAffordanceConfig(
-                                            BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
-                                        ) {},
-                                    object :
-                                        FakeKeyguardQuickAffordanceConfig(
-                                            BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
-                                        ) {},
+                                    quickAccessWallet,
+                                    qrCodeScanner,
                                 ),
                         ),
                     ),
@@ -244,6 +303,11 @@
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
                 activityStarter = activityStarter,
+                featureFlags =
+                    FakeFeatureFlags().apply {
+                        set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+                    },
+                repository = { quickAffordanceRepository },
             )
     }
 
@@ -301,7 +365,6 @@
         needStrongAuthAfterBoot: Boolean = true,
         keyguardIsUnlocked: Boolean = false,
     ) {
-        whenever(userTracker.userHandle).thenReturn(mock())
         whenever(lockPatternUtils.getStrongAuthForUser(any()))
             .thenReturn(
                 if (needStrongAuthAfterBoot) {
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 ae32ba6..11fe905 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
@@ -17,36 +17,56 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.os.UserHandle
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 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.FakeKeyguardQuickAffordanceProviderClientFactory
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
 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.model.KeyguardQuickAffordancePickerRepresentation
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
 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.FakeSharedPreferences
 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
+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
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@@ -62,6 +82,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 +92,61 @@
         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 scope = CoroutineScope(IMMEDIATE)
+
+        val localUserSelectionManager =
+            KeyguardQuickAffordanceLocalUserSelectionManager(
+                context = context,
+                userFileManager =
+                    mock<UserFileManager>().apply {
+                        whenever(
+                                getSharedPreferences(
+                                    anyString(),
+                                    anyInt(),
+                                    anyInt(),
+                                )
+                            )
+                            .thenReturn(FakeSharedPreferences())
+                    },
+                userTracker = userTracker,
+                broadcastDispatcher = fakeBroadcastDispatcher,
+            )
+        val remoteUserSelectionManager =
+            KeyguardQuickAffordanceRemoteUserSelectionManager(
+                scope = scope,
+                userTracker = userTracker,
+                clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+                userHandle = UserHandle.SYSTEM,
+            )
+        val quickAffordanceRepository =
+            KeyguardQuickAffordanceRepository(
+                appContext = context,
+                scope = scope,
+                localUserSelectionManager = localUserSelectionManager,
+                remoteUserSelectionManager = remoteUserSelectionManager,
+                userTracker = userTracker,
+                legacySettingSyncer =
+                    KeyguardQuickAffordanceLegacySettingSyncer(
+                        scope = scope,
+                        backgroundDispatcher = IMMEDIATE,
+                        secureSettings = FakeSettings(),
+                        selectionsManager = localUserSelectionManager,
+                    ),
+                configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+                dumpManager = mock(),
+                userHandle = UserHandle.SYSTEM,
+            )
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+            }
 
         underTest =
             KeyguardQuickAffordanceInteractor(
@@ -107,6 +169,8 @@
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
                 activityStarter = activityStarter,
+                featureFlags = featureFlags,
+                repository = { quickAffordanceRepository },
             )
     }
 
@@ -210,6 +274,306 @@
             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(
+                                KeyguardQuickAffordancePickerRepresentation(
+                                    id = homeControls.key,
+                                    name = homeControls.pickerName,
+                                    iconResourceId = homeControls.pickerIconResourceId,
+                                ),
+                            ),
+                        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(
+                                KeyguardQuickAffordancePickerRepresentation(
+                                    id = quickAccessWallet.key,
+                                    name = quickAccessWallet.pickerName,
+                                    iconResourceId = quickAccessWallet.pickerIconResourceId,
+                                ),
+                            ),
+                        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(
+                                KeyguardQuickAffordancePickerRepresentation(
+                                    id = quickAccessWallet.key,
+                                    name = quickAccessWallet.pickerName,
+                                    iconResourceId = quickAccessWallet.pickerIconResourceId,
+                                ),
+                            ),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+                            listOf(
+                                KeyguardQuickAffordancePickerRepresentation(
+                                    id = qrCodeScanner.key,
+                                    name = qrCodeScanner.pickerName,
+                                    iconResourceId = qrCodeScanner.pickerIconResourceId,
+                                ),
+                            ),
+                    )
+                )
+
+            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(
+                                KeyguardQuickAffordancePickerRepresentation(
+                                    id = quickAccessWallet.key,
+                                    name = quickAccessWallet.pickerName,
+                                    iconResourceId = quickAccessWallet.pickerIconResourceId,
+                                ),
+                            ),
+                    )
+                )
+
+            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(
+                                KeyguardQuickAffordancePickerRepresentation(
+                                    id = quickAccessWallet.key,
+                                    name = quickAccessWallet.pickerName,
+                                    iconResourceId = quickAccessWallet.pickerIconResourceId,
+                                ),
+                            ),
+                    )
+                )
+
+            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 +584,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/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
new file mode 100644
index 0000000..3166214
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.data.repository.FakeLightRevealScrimRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.statusbar.LightRevealScrim
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class LightRevealScrimInteractorTest : SysuiTestCase() {
+    private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
+    private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
+
+    private val keyguardTransitionInteractor =
+        KeyguardTransitionInteractor(fakeKeyguardTransitionRepository)
+
+    private lateinit var underTest: LightRevealScrimInteractor
+
+    private val reveal1 =
+        object : LightRevealEffect {
+            override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {}
+        }
+
+    private val reveal2 =
+        object : LightRevealEffect {
+            override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {}
+        }
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            LightRevealScrimInteractor(
+                fakeKeyguardTransitionRepository,
+                keyguardTransitionInteractor,
+                fakeLightRevealScrimRepository
+            )
+    }
+
+    @Test
+    fun `lightRevealEffect - does not change during keyguard transition`() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<LightRevealEffect>()
+            val job = underTest.lightRevealEffect.onEach(values::add).launchIn(this)
+
+            fakeLightRevealScrimRepository.setRevealEffect(reveal1)
+
+            // The reveal effect shouldn't emit anything until a keyguard transition starts.
+            assertEquals(values.size, 0)
+
+            // Once it starts, it should emit reveal1.
+            fakeKeyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(transitionState = TransitionState.STARTED)
+            )
+            assertEquals(values, listOf(reveal1))
+
+            // Until the next transition starts, reveal2 should not be emitted.
+            fakeLightRevealScrimRepository.setRevealEffect(reveal2)
+            fakeKeyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(transitionState = TransitionState.RUNNING)
+            )
+            fakeKeyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(transitionState = TransitionState.FINISHED)
+            )
+            assertEquals(values, listOf(reveal1))
+            fakeKeyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(transitionState = TransitionState.STARTED)
+            )
+            assertEquals(values, listOf(reveal1, reveal2))
+
+            job.cancel()
+        }
+
+    @Test
+    fun `revealAmount - inverted when appropriate`() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Float>()
+            val job = underTest.revealAmount.onEach(values::add).launchIn(this)
+
+            fakeKeyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = 0.3f
+                )
+            )
+
+            assertEquals(values, listOf(0.3f))
+
+            fakeKeyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 0.3f
+                )
+            )
+
+            assertEquals(values, listOf(0.3f, 0.7f))
+
+            job.cancel()
+        }
+
+    @Test
+    fun `revealAmount - ignores transitions that do not affect reveal amount`() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Float>()
+            val job = underTest.revealAmount.onEach(values::add).launchIn(this)
+
+            fakeKeyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.DOZING, to = KeyguardState.AOD, value = 0.3f)
+            )
+
+            assertEquals(values, emptyList<Float>())
+
+            fakeKeyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.AOD, to = KeyguardState.DOZING, value = 0.3f)
+            )
+
+            assertEquals(values, emptyList<Float>())
+
+            job.cancel()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
new file mode 100644
index 0000000..db9c4e7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
@@ -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.systemui.keyguard.domain.interactor
+
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PrimaryBouncerCallbackInteractorTest : SysuiTestCase() {
+    private val mPrimaryBouncerCallbackInteractor = PrimaryBouncerCallbackInteractor()
+    @Mock
+    private lateinit var mPrimaryBouncerExpansionCallback:
+        KeyguardBouncer.PrimaryBouncerExpansionCallback
+    @Mock private lateinit var keyguardResetCallback: KeyguardBouncer.KeyguardResetCallback
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(
+            mPrimaryBouncerExpansionCallback
+        )
+        mPrimaryBouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback)
+    }
+
+    @Test
+    fun testOnFullyShown() {
+        mPrimaryBouncerCallbackInteractor.dispatchFullyShown()
+        verify(mPrimaryBouncerExpansionCallback).onFullyShown()
+    }
+
+    @Test
+    fun testOnFullyHidden() {
+        mPrimaryBouncerCallbackInteractor.dispatchFullyHidden()
+        verify(mPrimaryBouncerExpansionCallback).onFullyHidden()
+    }
+
+    @Test
+    fun testOnExpansionChanged() {
+        mPrimaryBouncerCallbackInteractor.dispatchExpansionChanged(5f)
+        verify(mPrimaryBouncerExpansionCallback).onExpansionChanged(5f)
+    }
+
+    @Test
+    fun testOnVisibilityChanged() {
+        mPrimaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
+        verify(mPrimaryBouncerExpansionCallback).onVisibilityChanged(false)
+    }
+
+    @Test
+    fun testOnStartingToHide() {
+        mPrimaryBouncerCallbackInteractor.dispatchStartingToHide()
+        verify(mPrimaryBouncerExpansionCallback).onStartingToHide()
+    }
+
+    @Test
+    fun testOnStartingToShow() {
+        mPrimaryBouncerCallbackInteractor.dispatchStartingToShow()
+        verify(mPrimaryBouncerExpansionCallback).onStartingToShow()
+    }
+
+    @Test
+    fun testOnKeyguardReset() {
+        mPrimaryBouncerCallbackInteractor.dispatchReset()
+        verify(keyguardResetCallback).onKeyguardReset()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
new file mode 100644
index 0000000..a6fc13b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -0,0 +1,289 @@
+/*
+ * 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.os.Looper
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.DejankUtils
+import com.android.systemui.SysuiTestCase
+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.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+class PrimaryBouncerInteractorTest : SysuiTestCase() {
+    @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 mPrimaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    private val mainHandler = FakeHandler(Looper.getMainLooper())
+    private lateinit var mPrimaryBouncerInteractor: PrimaryBouncerInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        DejankUtils.setImmediate(true)
+        mPrimaryBouncerInteractor =
+            PrimaryBouncerInteractor(
+                repository,
+                bouncerView,
+                mainHandler,
+                keyguardStateController,
+                keyguardSecurityModel,
+                mPrimaryBouncerCallbackInteractor,
+                falsingCollector,
+                dismissCallbackRegistry,
+                keyguardBypassController,
+                keyguardUpdateMonitor,
+            )
+        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        `when`(repository.primaryBouncerShow.value).thenReturn(null)
+        `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
+    }
+
+    @Test
+    fun testShow_isScrimmed() {
+        mPrimaryBouncerInteractor.show(true)
+        verify(repository).setOnScreenTurnedOff(false)
+        verify(repository).setKeyguardAuthenticated(null)
+        verify(repository).setPrimaryHide(false)
+        verify(repository).setPrimaryStartingToHide(false)
+        verify(repository).setPrimaryScrimmed(true)
+        verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
+        verify(repository).setPrimaryShowingSoon(true)
+        verify(keyguardStateController).notifyBouncerShowing(true)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToShow()
+        verify(repository).setPrimaryVisible(true)
+        verify(repository).setPrimaryShow(any(KeyguardBouncerModel::class.java))
+        verify(repository).setPrimaryShowingSoon(false)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.VISIBLE)
+    }
+
+    @Test
+    fun testShow_isNotScrimmed() {
+        verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
+    }
+
+    @Test
+    fun testShow_keyguardIsDone() {
+        `when`(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
+        verify(keyguardStateController, never()).notifyBouncerShowing(true)
+        verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
+    }
+
+    @Test
+    fun testHide() {
+        mPrimaryBouncerInteractor.hide()
+        verify(falsingCollector).onBouncerHidden()
+        verify(keyguardStateController).notifyBouncerShowing(false)
+        verify(repository).setPrimaryShowingSoon(false)
+        verify(repository).setPrimaryVisible(false)
+        verify(repository).setPrimaryHide(true)
+        verify(repository).setPrimaryShow(null)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE)
+    }
+
+    @Test
+    fun testExpansion() {
+        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        mPrimaryBouncerInteractor.setPanelExpansion(0.6f)
+        verify(repository).setPanelExpansion(0.6f)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
+    }
+
+    @Test
+    fun testExpansion_fullyShown() {
+        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
+        verify(falsingCollector).onBouncerShown()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
+    }
+
+    @Test
+    fun testExpansion_fullyHidden() {
+        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
+        verify(repository).setPrimaryVisible(false)
+        verify(repository).setPrimaryShow(null)
+        verify(repository).setPrimaryHide(true)
+        verify(falsingCollector).onBouncerHidden()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchReset()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden()
+    }
+
+    @Test
+    fun testExpansion_startingToHide() {
+        `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        mPrimaryBouncerInteractor.setPanelExpansion(0.1f)
+        verify(repository).setPrimaryStartingToHide(true)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
+    }
+
+    @Test
+    fun testShowMessage() {
+        val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
+        mPrimaryBouncerInteractor.showMessage("abc", null)
+        verify(repository).setShowMessage(argCaptor.capture())
+        assertThat(argCaptor.value.message).isEqualTo("abc")
+    }
+
+    @Test
+    fun testDismissAction() {
+        val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
+        val cancelAction = mock(Runnable::class.java)
+        mPrimaryBouncerInteractor.setDismissAction(onDismissAction, cancelAction)
+        verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
+    }
+
+    @Test
+    fun testUpdateResources() {
+        mPrimaryBouncerInteractor.updateResources()
+        verify(repository).setResourceUpdateRequests(true)
+    }
+
+    @Test
+    fun testNotifyKeyguardAuthenticated() {
+        mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(true)
+        verify(repository).setKeyguardAuthenticated(true)
+    }
+
+    @Test
+    fun testNotifyShowedMessage() {
+        mPrimaryBouncerInteractor.onMessageShown()
+        verify(repository).setShowMessage(null)
+    }
+
+    @Test
+    fun testOnScreenTurnedOff() {
+        mPrimaryBouncerInteractor.onScreenTurnedOff()
+        verify(repository).setOnScreenTurnedOff(true)
+    }
+
+    @Test
+    fun testSetKeyguardPosition() {
+        mPrimaryBouncerInteractor.setKeyguardPosition(0f)
+        verify(repository).setKeyguardPosition(0f)
+    }
+
+    @Test
+    fun testNotifyKeyguardAuthenticatedHandled() {
+        mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedHandled()
+        verify(repository).setKeyguardAuthenticated(null)
+    }
+
+    @Test
+    fun testNotifyUpdatedResources() {
+        mPrimaryBouncerInteractor.notifyUpdatedResources()
+        verify(repository).setResourceUpdateRequests(false)
+    }
+
+    @Test
+    fun testSetBackButtonEnabled() {
+        mPrimaryBouncerInteractor.setBackButtonEnabled(true)
+        verify(repository).setIsBackButtonEnabled(true)
+    }
+
+    @Test
+    fun testStartDisappearAnimation() {
+        val runnable = mock(Runnable::class.java)
+        mPrimaryBouncerInteractor.startDisappearAnimation(runnable)
+        verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
+    }
+
+    @Test
+    fun testIsFullShowing() {
+        `when`(repository.primaryBouncerVisible.value).thenReturn(true)
+        `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isTrue()
+        `when`(repository.primaryBouncerVisible.value).thenReturn(false)
+        assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isFalse()
+    }
+
+    @Test
+    fun testIsScrimmed() {
+        `when`(repository.primaryBouncerScrimmed.value).thenReturn(true)
+        assertThat(mPrimaryBouncerInteractor.isScrimmed()).isTrue()
+        `when`(repository.primaryBouncerScrimmed.value).thenReturn(false)
+        assertThat(mPrimaryBouncerInteractor.isScrimmed()).isFalse()
+    }
+
+    @Test
+    fun testIsInTransit() {
+        `when`(repository.primaryBouncerShowingSoon.value).thenReturn(true)
+        assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
+        `when`(repository.primaryBouncerShowingSoon.value).thenReturn(false)
+        assertThat(mPrimaryBouncerInteractor.isInTransit()).isFalse()
+        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
+    }
+
+    @Test
+    fun testIsAnimatingAway() {
+        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
+        assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isTrue()
+        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isFalse()
+    }
+
+    @Test
+    fun testWillDismissWithAction() {
+        `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
+        assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isTrue()
+        `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
+        assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isFalse()
+    }
+}
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 f73d1ec..83a5d0e 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
@@ -17,16 +17,24 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.content.Intent
+import android.os.UserHandle
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 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.FakeKeyguardQuickAffordanceProviderClientFactory
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
 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
@@ -34,13 +42,18 @@
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
 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
@@ -50,6 +63,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.verifyZeroInteractions
@@ -82,20 +96,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 +123,54 @@
         whenever(userTracker.userHandle).thenReturn(mock())
         whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
             .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
+        val scope = CoroutineScope(IMMEDIATE)
+        val localUserSelectionManager =
+            KeyguardQuickAffordanceLocalUserSelectionManager(
+                context = context,
+                userFileManager =
+                    mock<UserFileManager>().apply {
+                        whenever(
+                                getSharedPreferences(
+                                    anyString(),
+                                    anyInt(),
+                                    anyInt(),
+                                )
+                            )
+                            .thenReturn(FakeSharedPreferences())
+                    },
+                userTracker = userTracker,
+                broadcastDispatcher = fakeBroadcastDispatcher,
+            )
+        val remoteUserSelectionManager =
+            KeyguardQuickAffordanceRemoteUserSelectionManager(
+                scope = scope,
+                userTracker = userTracker,
+                clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+                userHandle = UserHandle.SYSTEM,
+            )
+        val quickAffordanceRepository =
+            KeyguardQuickAffordanceRepository(
+                appContext = context,
+                scope = scope,
+                localUserSelectionManager = localUserSelectionManager,
+                remoteUserSelectionManager = remoteUserSelectionManager,
+                userTracker = userTracker,
+                legacySettingSyncer =
+                    KeyguardQuickAffordanceLegacySettingSyncer(
+                        scope = scope,
+                        backgroundDispatcher = IMMEDIATE,
+                        secureSettings = FakeSettings(),
+                        selectionsManager = localUserSelectionManager,
+                    ),
+                configs =
+                    setOf(
+                        homeControlsQuickAffordanceConfig,
+                        quickAccessWalletAffordanceConfig,
+                        qrCodeScannerAffordanceConfig,
+                    ),
+                dumpManager = mock(),
+                userHandle = UserHandle.SYSTEM,
+            )
         underTest =
             KeyguardBottomAreaViewModel(
                 keyguardInteractor = keyguardInteractor,
@@ -127,6 +182,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,
@@ -576,5 +636,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/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
new file mode 100644
index 0000000..3727134
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.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.systemui.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+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
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardBouncerViewModelTest : SysuiTestCase() {
+    lateinit var underTest: KeyguardBouncerViewModel
+    @Mock lateinit var bouncerView: BouncerView
+    @Mock lateinit var bouncerInteractor: PrimaryBouncerInteractor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
+    }
+
+    @Test
+    fun setMessage() =
+        runBlocking(Dispatchers.Main.immediate) {
+            val flow = MutableStateFlow<BouncerShowMessageModel?>(null)
+            var message: BouncerShowMessageModel? = null
+            Mockito.`when`(bouncerInteractor.showMessage)
+                .thenReturn(flow as Flow<BouncerShowMessageModel>)
+            // Reinitialize the view model.
+            underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
+
+            flow.value = BouncerShowMessageModel(message = "abc", colorStateList = null)
+
+            val job = underTest.bouncerShowMessage.onEach { message = it }.launchIn(this)
+            assertThat(message?.message).isEqualTo("abc")
+            job.cancel()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
index dc5522e..aa54a1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -23,8 +23,10 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 
+import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -171,6 +173,34 @@
     }
 
     @Test
+    public void testKeyguardSessionOnDeviceStartsSleepingTwiceInARow_startsNewKeyguardSession()
+            throws RemoteException {
+        // GIVEN session tracker started w/o any sessions
+        mSessionTracker.start();
+        captureKeyguardUpdateMonitorCallback();
+
+        // WHEN device starts going to sleep
+        mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
+
+        // THEN the keyguard session has a session id
+        final InstanceId firstSessionId = mSessionTracker.getSessionId(SESSION_KEYGUARD);
+        assertNotNull(firstSessionId);
+
+        // WHEN device starts going to sleep a second time
+        mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
+
+        // THEN there's a new keyguard session with a unique session id
+        final InstanceId secondSessionId = mSessionTracker.getSessionId(SESSION_KEYGUARD);
+        assertNotNull(secondSessionId);
+        assertNotEquals(firstSessionId, secondSessionId);
+
+        // THEN session start event gets sent to status bar service twice (once per going to
+        // sleep signal)
+        verify(mStatusBarService, times(2)).onSessionStarted(
+                eq(SESSION_KEYGUARD), any(InstanceId.class));
+    }
+
+    @Test
     public void testKeyguardSessionOnKeyguardShowingChange() throws RemoteException {
         // GIVEN session tracker started w/o any sessions
         mSessionTracker.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
new file mode 100644
index 0000000..432764a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.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.log.table
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class TableChangeTest : SysuiTestCase() {
+
+    @Test
+    fun setString_isString() {
+        val underTest = TableChange()
+
+        underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+        underTest.set("fakeValue")
+
+        assertThat(underTest.hasData()).isTrue()
+        assertThat(underTest.getVal()).isEqualTo("fakeValue")
+    }
+
+    @Test
+    fun setBoolean_isBoolean() {
+        val underTest = TableChange()
+
+        underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+        underTest.set(true)
+
+        assertThat(underTest.hasData()).isTrue()
+        assertThat(underTest.getVal()).isEqualTo("true")
+    }
+
+    @Test
+    fun setInt_isInt() {
+        val underTest = TableChange()
+
+        underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+        underTest.set(8900)
+
+        assertThat(underTest.hasData()).isTrue()
+        assertThat(underTest.getVal()).isEqualTo("8900")
+    }
+
+    @Test
+    fun setThenReset_isEmpty() {
+        val underTest = TableChange()
+
+        underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+        underTest.set(8900)
+        underTest.reset(timestamp = 0, columnPrefix = "prefix", columnName = "name")
+
+        assertThat(underTest.hasData()).isFalse()
+        assertThat(underTest.getVal()).isEqualTo("null")
+    }
+
+    @Test
+    fun getName_hasPrefix() {
+        val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+
+        assertThat(underTest.getName()).contains("fakePrefix")
+        assertThat(underTest.getName()).contains("fakeName")
+    }
+
+    @Test
+    fun getName_noPrefix() {
+        val underTest = TableChange(columnPrefix = "", columnName = "fakeName")
+
+        assertThat(underTest.getName()).contains("fakeName")
+    }
+
+    @Test
+    fun resetThenSet_hasNewValue() {
+        val underTest = TableChange()
+
+        underTest.reset(timestamp = 100, columnPrefix = "prefix", columnName = "original")
+        underTest.set("fakeValue")
+        underTest.reset(timestamp = 0, columnPrefix = "", columnName = "updated")
+        underTest.set(8900)
+
+        assertThat(underTest.hasData()).isTrue()
+        assertThat(underTest.getName()).contains("updated")
+        assertThat(underTest.getName()).doesNotContain("prefix")
+        assertThat(underTest.getName()).doesNotContain("original")
+        assertThat(underTest.getVal()).isEqualTo("8900")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
new file mode 100644
index 0000000..2c8d7ab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
@@ -0,0 +1,464 @@
+/*
+ * 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.log.table
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class TableLogBufferTest : SysuiTestCase() {
+    private lateinit var underTest: TableLogBuffer
+
+    private lateinit var systemClock: FakeSystemClock
+    private lateinit var outputWriter: StringWriter
+
+    @Before
+    fun setup() {
+        systemClock = FakeSystemClock()
+        outputWriter = StringWriter()
+
+        underTest = TableLogBuffer(MAX_SIZE, NAME, systemClock)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun maxSizeZero_throwsException() {
+        TableLogBuffer(maxSize = 0, "name", systemClock)
+    }
+
+    @Test
+    fun dumpChanges_hasHeader() {
+        val dumpedString = dumpChanges()
+
+        assertThat(logLines(dumpedString)[0]).isEqualTo(HEADER_PREFIX + NAME)
+    }
+
+    @Test
+    fun dumpChanges_hasVersion() {
+        val dumpedString = dumpChanges()
+
+        assertThat(logLines(dumpedString)[1]).isEqualTo("version $VERSION")
+    }
+
+    @Test
+    fun dumpChanges_hasFooter() {
+        val dumpedString = dumpChanges()
+
+        assertThat(logLines(dumpedString).last()).isEqualTo(FOOTER_PREFIX + NAME)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun dumpChanges_str_separatorNotAllowedInPrefix() {
+        val next =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("columnName", "stringValue")
+                }
+            }
+        underTest.logDiffs("some${SEPARATOR}thing", TestDiffable(), next)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun dumpChanges_bool_separatorNotAllowedInPrefix() {
+        val next =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("columnName", true)
+                }
+            }
+        underTest.logDiffs("some${SEPARATOR}thing", TestDiffable(), next)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun dumpChanges_int_separatorNotAllowedInPrefix() {
+        val next =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("columnName", 567)
+                }
+            }
+        underTest.logDiffs("some${SEPARATOR}thing", TestDiffable(), next)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun dumpChanges_str_separatorNotAllowedInColumnName() {
+        val next =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("column${SEPARATOR}Name", "stringValue")
+                }
+            }
+        underTest.logDiffs("prefix", TestDiffable(), next)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun dumpChanges_bool_separatorNotAllowedInColumnName() {
+        val next =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("column${SEPARATOR}Name", true)
+                }
+            }
+        underTest.logDiffs("prefix", TestDiffable(), next)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun dumpChanges_int_separatorNotAllowedInColumnName() {
+        val next =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("column${SEPARATOR}Name", 456)
+                }
+            }
+        underTest.logDiffs("prefix", TestDiffable(), next)
+    }
+
+    @Test
+    fun logChange_bool_dumpsCorrectly() {
+        systemClock.setCurrentTimeMillis(4000L)
+
+        underTest.logChange("prefix", "columnName", true)
+
+        val dumpedString = dumpChanges()
+        val expected =
+            TABLE_LOG_DATE_FORMAT.format(4000L) +
+                SEPARATOR +
+                "prefix.columnName" +
+                SEPARATOR +
+                "true"
+        assertThat(dumpedString).contains(expected)
+    }
+
+    @Test
+    fun dumpChanges_strChange_logsFromNext() {
+        systemClock.setCurrentTimeMillis(100L)
+
+        val prevDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("stringValChange", "prevStringVal")
+                }
+            }
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("stringValChange", "newStringVal")
+                }
+            }
+
+        underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        val expected =
+            TABLE_LOG_DATE_FORMAT.format(100L) +
+                SEPARATOR +
+                "prefix.stringValChange" +
+                SEPARATOR +
+                "newStringVal"
+        assertThat(dumpedString).contains(expected)
+        assertThat(dumpedString).doesNotContain("prevStringVal")
+    }
+
+    @Test
+    fun dumpChanges_boolChange_logsFromNext() {
+        systemClock.setCurrentTimeMillis(100L)
+
+        val prevDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("booleanValChange", false)
+                }
+            }
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("booleanValChange", true)
+                }
+            }
+
+        underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        val expected =
+            TABLE_LOG_DATE_FORMAT.format(100L) +
+                SEPARATOR +
+                "prefix.booleanValChange" +
+                SEPARATOR +
+                "true"
+        assertThat(dumpedString).contains(expected)
+        assertThat(dumpedString).doesNotContain("false")
+    }
+
+    @Test
+    fun dumpChanges_intChange_logsFromNext() {
+        systemClock.setCurrentTimeMillis(100L)
+
+        val prevDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("intValChange", 12345)
+                }
+            }
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("intValChange", 67890)
+                }
+            }
+
+        underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        val expected =
+            TABLE_LOG_DATE_FORMAT.format(100L) +
+                SEPARATOR +
+                "prefix.intValChange" +
+                SEPARATOR +
+                "67890"
+        assertThat(dumpedString).contains(expected)
+        assertThat(dumpedString).doesNotContain("12345")
+    }
+
+    @Test
+    fun dumpChanges_noPrefix() {
+        systemClock.setCurrentTimeMillis(100L)
+
+        val prevDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("booleanValChange", false)
+                }
+            }
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("booleanValChange", true)
+                }
+            }
+
+        // WHEN there's a blank prefix
+        underTest.logDiffs("", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        // THEN the dump still works
+        val expected =
+            TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + "booleanValChange" + SEPARATOR + "true"
+        assertThat(dumpedString).contains(expected)
+    }
+
+    @Test
+    fun dumpChanges_multipleChangesForSameColumn_logs() {
+        lateinit var valToDump: String
+
+        val diffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("valChange", valToDump)
+                }
+            }
+
+        systemClock.setCurrentTimeMillis(12000L)
+        valToDump = "stateValue12"
+        underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+        systemClock.setCurrentTimeMillis(20000L)
+        valToDump = "stateValue20"
+        underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+        systemClock.setCurrentTimeMillis(40000L)
+        valToDump = "stateValue40"
+        underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+        systemClock.setCurrentTimeMillis(45000L)
+        valToDump = "stateValue45"
+        underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+        val dumpedString = dumpChanges()
+
+        val expected1 =
+            TABLE_LOG_DATE_FORMAT.format(12000L) +
+                SEPARATOR +
+                "valChange" +
+                SEPARATOR +
+                "stateValue12"
+        val expected2 =
+            TABLE_LOG_DATE_FORMAT.format(20000L) +
+                SEPARATOR +
+                "valChange" +
+                SEPARATOR +
+                "stateValue20"
+        val expected3 =
+            TABLE_LOG_DATE_FORMAT.format(40000L) +
+                SEPARATOR +
+                "valChange" +
+                SEPARATOR +
+                "stateValue40"
+        val expected4 =
+            TABLE_LOG_DATE_FORMAT.format(45000L) +
+                SEPARATOR +
+                "valChange" +
+                SEPARATOR +
+                "stateValue45"
+        assertThat(dumpedString).contains(expected1)
+        assertThat(dumpedString).contains(expected2)
+        assertThat(dumpedString).contains(expected3)
+        assertThat(dumpedString).contains(expected4)
+    }
+
+    @Test
+    fun dumpChanges_multipleChangesAtOnce_logs() {
+        systemClock.setCurrentTimeMillis(100L)
+
+        val prevDiffable = object : TestDiffable() {}
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("status", "in progress")
+                    row.logChange("connected", false)
+                }
+            }
+
+        underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        val timestamp = TABLE_LOG_DATE_FORMAT.format(100L)
+        val expected1 = timestamp + SEPARATOR + "status" + SEPARATOR + "in progress"
+        val expected2 = timestamp + SEPARATOR + "connected" + SEPARATOR + "false"
+        assertThat(dumpedString).contains(expected1)
+        assertThat(dumpedString).contains(expected2)
+    }
+
+    @Test
+    fun logChange_rowInitializer_dumpsCorrectly() {
+        systemClock.setCurrentTimeMillis(100L)
+
+        underTest.logChange("") { row ->
+            row.logChange("column1", "val1")
+            row.logChange("column2", 2)
+            row.logChange("column3", true)
+        }
+
+        val dumpedString = dumpChanges()
+
+        val timestamp = TABLE_LOG_DATE_FORMAT.format(100L)
+        val expected1 = timestamp + SEPARATOR + "column1" + SEPARATOR + "val1"
+        val expected2 = timestamp + SEPARATOR + "column2" + SEPARATOR + "2"
+        val expected3 = timestamp + SEPARATOR + "column3" + SEPARATOR + "true"
+        assertThat(dumpedString).contains(expected1)
+        assertThat(dumpedString).contains(expected2)
+        assertThat(dumpedString).contains(expected3)
+    }
+
+    @Test
+    fun logChangeAndLogDiffs_bothLogged() {
+        systemClock.setCurrentTimeMillis(100L)
+
+        underTest.logChange("") { row ->
+            row.logChange("column1", "val1")
+            row.logChange("column2", 2)
+            row.logChange("column3", true)
+        }
+
+        systemClock.setCurrentTimeMillis(200L)
+        val prevDiffable = object : TestDiffable() {}
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("column1", "newVal1")
+                    row.logChange("column2", 222)
+                    row.logChange("column3", false)
+                }
+            }
+
+        underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        val timestamp1 = TABLE_LOG_DATE_FORMAT.format(100L)
+        val expected1 = timestamp1 + SEPARATOR + "column1" + SEPARATOR + "val1"
+        val expected2 = timestamp1 + SEPARATOR + "column2" + SEPARATOR + "2"
+        val expected3 = timestamp1 + SEPARATOR + "column3" + SEPARATOR + "true"
+        val timestamp2 = TABLE_LOG_DATE_FORMAT.format(200L)
+        val expected4 = timestamp2 + SEPARATOR + "column1" + SEPARATOR + "newVal1"
+        val expected5 = timestamp2 + SEPARATOR + "column2" + SEPARATOR + "222"
+        val expected6 = timestamp2 + SEPARATOR + "column3" + SEPARATOR + "false"
+        assertThat(dumpedString).contains(expected1)
+        assertThat(dumpedString).contains(expected2)
+        assertThat(dumpedString).contains(expected3)
+        assertThat(dumpedString).contains(expected4)
+        assertThat(dumpedString).contains(expected5)
+        assertThat(dumpedString).contains(expected6)
+    }
+
+    @Test
+    fun dumpChanges_rotatesIfBufferIsFull() {
+        lateinit var valToDump: String
+
+        val prevDiffable = object : TestDiffable() {}
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("status", valToDump)
+                }
+            }
+
+        for (i in 0 until MAX_SIZE + 3) {
+            valToDump = "testString[$i]"
+            underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable)
+        }
+
+        val dumpedString = dumpChanges()
+
+        assertThat(dumpedString).doesNotContain("testString[0]")
+        assertThat(dumpedString).doesNotContain("testString[1]")
+        assertThat(dumpedString).doesNotContain("testString[2]")
+        assertThat(dumpedString).contains("testString[3]")
+        assertThat(dumpedString).contains("testString[${MAX_SIZE + 2}]")
+    }
+
+    private fun dumpChanges(): String {
+        underTest.dump(PrintWriter(outputWriter), arrayOf())
+        return outputWriter.toString()
+    }
+
+    private fun logLines(string: String): List<String> {
+        return string.split("\n").filter { it.isNotBlank() }
+    }
+
+    private open class TestDiffable : Diffable<TestDiffable> {
+        override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {}
+    }
+}
+
+private const val NAME = "TestTableBuffer"
+private const val MAX_SIZE = 10
+
+// Copying these here from [TableLogBuffer] so that we catch any accidental versioning change
+private const val HEADER_PREFIX = "SystemUI StateChangeTableSection START: "
+private const val FOOTER_PREFIX = "SystemUI StateChangeTableSection END: "
+private const val SEPARATOR = "|" // TBD
+private const val VERSION = "1"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
index 7cd8e74..56c91bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
@@ -42,6 +42,7 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
@@ -464,7 +465,7 @@
     fun onFalseTapOrTouch() {
         whenever(mockController.getTransportControls()).thenReturn(mockTransport)
         whenever(falsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).thenReturn(true)
-        whenever(falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
+        whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
         viewModel.updateController(mockController)
         val pos = 169
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
index 575b1c6..9d33e6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
@@ -22,13 +22,13 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.media.controls.MediaTestUtils
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
 import com.android.systemui.media.controls.ui.MediaPlayerData
 import com.android.systemui.media.controls.util.MediaUiEventLogger
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -64,7 +64,7 @@
 class MediaDataFilterTest : SysuiTestCase() {
 
     @Mock private lateinit var listener: MediaDataManager.Listener
-    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var broadcastSender: BroadcastSender
     @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
@@ -85,7 +85,7 @@
         mediaDataFilter =
             MediaDataFilter(
                 context,
-                broadcastDispatcher,
+                userTracker,
                 broadcastSender,
                 lockscreenUserManager,
                 executor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 11eb26b..52b694f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -20,6 +20,8 @@
 import android.app.Notification.MediaStyle
 import android.app.PendingIntent
 import android.app.smartspace.SmartspaceAction
+import android.app.smartspace.SmartspaceConfig
+import android.app.smartspace.SmartspaceManager
 import android.app.smartspace.SmartspaceTarget
 import android.content.Intent
 import android.graphics.Bitmap
@@ -60,7 +62,6 @@
 import com.google.common.truth.Truth.assertThat
 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
@@ -106,6 +107,7 @@
     lateinit var metadataBuilder: MediaMetadata.Builder
     lateinit var backgroundExecutor: FakeExecutor
     lateinit var foregroundExecutor: FakeExecutor
+    lateinit var uiExecutor: FakeExecutor
     @Mock lateinit var dumpManager: DumpManager
     @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
@@ -117,6 +119,7 @@
     @Mock lateinit var listener: MediaDataManager.Listener
     @Mock lateinit var pendingIntent: PendingIntent
     @Mock lateinit var activityStarter: ActivityStarter
+    @Mock lateinit var smartspaceManager: SmartspaceManager
     lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
     @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
     @Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@@ -131,6 +134,7 @@
     @Mock private lateinit var tunerService: TunerService
     @Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
     @Captor lateinit var callbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
+    @Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
 
     private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
 
@@ -145,6 +149,7 @@
     fun setup() {
         foregroundExecutor = FakeExecutor(clock)
         backgroundExecutor = FakeExecutor(clock)
+        uiExecutor = FakeExecutor(clock)
         smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
         Settings.Secure.putInt(
             context.contentResolver,
@@ -155,6 +160,7 @@
             MediaDataManager(
                 context = context,
                 backgroundExecutor = backgroundExecutor,
+                uiExecutor = uiExecutor,
                 foregroundExecutor = foregroundExecutor,
                 mediaControllerFactory = mediaControllerFactory,
                 broadcastDispatcher = broadcastDispatcher,
@@ -172,7 +178,8 @@
                 systemClock = clock,
                 tunerService = tunerService,
                 mediaFlags = mediaFlags,
-                logger = logger
+                logger = logger,
+                smartspaceManager = smartspaceManager,
             )
         verify(tunerService)
             .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -191,6 +198,7 @@
                 putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
                 putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
             }
+        verify(smartspaceManager).createSmartspaceSession(capture(smartSpaceConfigBuilderCaptor))
         whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
         whenever(controller.transportControls).thenReturn(transportControls)
         whenever(controller.playbackInfo).thenReturn(playbackInfo)
@@ -767,15 +775,14 @@
             .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
     }
 
-    @Ignore("b/233283726")
     @Test
     fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
         verify(logger).getNewInstanceId()
 
         smartspaceMediaDataProvider.onTargetsAvailable(listOf())
-        foregroundExecutor.advanceClockToLast()
-        foregroundExecutor.runAllReady()
+        uiExecutor.advanceClockToLast()
+        uiExecutor.runAllReady()
 
         verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
         verifyNoMoreInteractions(logger)
@@ -798,7 +805,6 @@
             .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
     }
 
-    @Ignore("b/229838140")
     @Test
     fun testMediaRecommendationDisabled_removesSmartspaceData() {
         // GIVEN a media recommendation card is present
@@ -815,7 +821,9 @@
         tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
 
         // THEN listeners are notified
+        uiExecutor.advanceClockToLast()
         foregroundExecutor.advanceClockToLast()
+        uiExecutor.runAllReady()
         foregroundExecutor.runAllReady()
         verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
index 84fdfd7..136ace1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.media.controls.models.player.MediaDeviceData
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
@@ -79,6 +80,7 @@
 class MediaResumeListenerTest : SysuiTestCase() {
 
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var device: MediaDeviceData
     @Mock private lateinit var token: MediaSession.Token
@@ -131,12 +133,15 @@
         whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor)
         whenever(mockContext.packageManager).thenReturn(context.packageManager)
         whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
+        whenever(mockContext.userId).thenReturn(context.userId)
 
         executor = FakeExecutor(clock)
         resumeListener =
             MediaResumeListener(
                 mockContext,
                 broadcastDispatcher,
+                userTracker,
+                executor,
                 executor,
                 tunerService,
                 resumeBrowserFactory,
@@ -177,6 +182,8 @@
             MediaResumeListener(
                 context,
                 broadcastDispatcher,
+                userTracker,
+                executor,
                 executor,
                 tunerService,
                 resumeBrowserFactory,
@@ -185,7 +192,7 @@
             )
         listener.setManager(mediaDataManager)
         verify(broadcastDispatcher, never())
-            .registerReceiver(eq(listener.userChangeReceiver), any(), any(), any(), anyInt(), any())
+            .registerReceiver(eq(listener.userUnlockReceiver), any(), any(), any(), anyInt(), any())
 
         // When data is loaded, we do NOT execute or update anything
         listener.onMediaDataLoaded(KEY, OLD_KEY, data)
@@ -289,7 +296,7 @@
         resumeListener.setManager(mediaDataManager)
         verify(broadcastDispatcher)
             .registerReceiver(
-                eq(resumeListener.userChangeReceiver),
+                eq(resumeListener.userUnlockReceiver),
                 any(),
                 any(),
                 any(),
@@ -299,7 +306,8 @@
 
         // When we get an unlock event
         val intent = Intent(Intent.ACTION_USER_UNLOCKED)
-        resumeListener.userChangeReceiver.onReceive(context, intent)
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
+        resumeListener.userUnlockReceiver.onReceive(context, intent)
 
         // Then we should attempt to find recent media for each saved component
         verify(resumeBrowser, times(3)).findRecentMedia()
@@ -375,6 +383,8 @@
             MediaResumeListener(
                 mockContext,
                 broadcastDispatcher,
+                userTracker,
+                executor,
                 executor,
                 tunerService,
                 resumeBrowserFactory,
@@ -386,7 +396,8 @@
 
         // When we load a component that was played recently
         val intent = Intent(Intent.ACTION_USER_UNLOCKED)
-        resumeListener.userChangeReceiver.onReceive(mockContext, intent)
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
+        resumeListener.userUnlockReceiver.onReceive(mockContext, intent)
 
         // We add its resume controls
         verify(resumeBrowser, times(1)).findRecentMedia()
@@ -404,6 +415,8 @@
             MediaResumeListener(
                 mockContext,
                 broadcastDispatcher,
+                userTracker,
+                executor,
                 executor,
                 tunerService,
                 resumeBrowserFactory,
@@ -415,7 +428,8 @@
 
         // When we load a component that is not recent
         val intent = Intent(Intent.ACTION_USER_UNLOCKED)
-        resumeListener.userChangeReceiver.onReceive(mockContext, intent)
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
+        resumeListener.userUnlockReceiver.onReceive(mockContext, intent)
 
         // We do not try to add resume controls
         verify(resumeBrowser, times(0)).findRecentMedia()
@@ -443,6 +457,8 @@
             MediaResumeListener(
                 mockContext,
                 broadcastDispatcher,
+                userTracker,
+                executor,
                 executor,
                 tunerService,
                 resumeBrowserFactory,
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..a943746 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,8 @@
 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.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
 import junit.framework.Assert.assertEquals
 import org.junit.After
 import org.junit.Before
@@ -60,6 +62,8 @@
     private lateinit var animatingColorTransitionFactory: AnimatingColorTransitionFactory
     @Mock private lateinit var mediaViewHolder: MediaViewHolder
     @Mock private lateinit var gutsViewHolder: GutsViewHolder
+    @Mock private lateinit var multiRippleController: MultiRippleController
+    @Mock private lateinit var turbulenceNoiseController: TurbulenceNoiseController
 
     @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
 
@@ -70,7 +74,13 @@
         whenever(extractColor.invoke(colorScheme)).thenReturn(TARGET_COLOR)
 
         colorSchemeTransition =
-            ColorSchemeTransition(context, mediaViewHolder, animatingColorTransitionFactory)
+            ColorSchemeTransition(
+                context,
+                mediaViewHolder,
+                multiRippleController,
+                turbulenceNoiseController,
+                animatingColorTransitionFactory
+            )
 
         colorTransition =
             object : AnimatingColorTransition(DEFAULT_COLOR, extractColor, applyColor) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index c8e8943..6ca34e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -49,6 +49,7 @@
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertTrue
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -119,6 +120,7 @@
         MediaPlayerData.clear()
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testPlayerOrdering() {
         // Test values: key, data, last active time
@@ -295,6 +297,7 @@
         }
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testOrderWithSmartspace_prioritized() {
         testPlayerOrdering()
@@ -312,6 +315,7 @@
         assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() {
         testPlayerOrdering()
@@ -328,6 +332,7 @@
         assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testOrderWithSmartspace_notPrioritized() {
         testPlayerOrdering()
@@ -346,6 +351,7 @@
         assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() {
         testPlayerOrdering()
@@ -382,6 +388,8 @@
             MediaPlayerData.playerKeys().elementAt(0)
         )
     }
+
+    @Ignore("b/253229241")
     @Test
     fun testSwipeDismiss_logged() {
         mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke()
@@ -389,6 +397,7 @@
         verify(logger).logSwipeDismiss()
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testSettingsButton_logged() {
         mediaCarouselController.settingsButton.callOnClick()
@@ -396,6 +405,7 @@
         verify(logger).logCarouselSettings()
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testLocationChangeQs_logged() {
         mediaCarouselController.onDesiredLocationChanged(
@@ -406,6 +416,7 @@
         verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testLocationChangeQqs_logged() {
         mediaCarouselController.onDesiredLocationChanged(
@@ -416,6 +427,7 @@
         verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testLocationChangeLockscreen_logged() {
         mediaCarouselController.onDesiredLocationChanged(
@@ -426,6 +438,7 @@
         verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testLocationChangeDream_logged() {
         mediaCarouselController.onDesiredLocationChanged(
@@ -436,6 +449,7 @@
         verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testRecommendationRemoved_logged() {
         val packageName = "smartspace package"
@@ -449,6 +463,7 @@
         verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testMediaLoaded_ScrollToActivePlayer() {
         listener.value.onMediaDataLoaded(
@@ -506,6 +521,7 @@
         )
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
         listener.value.onSmartspaceMediaDataLoaded(
@@ -549,6 +565,7 @@
         assertEquals(playerIndex, 0)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
         var result = false
@@ -560,6 +577,7 @@
         assertEquals(true, result)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
         var result = false
@@ -573,6 +591,7 @@
         assertEquals(true, result)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testGetCurrentVisibleMediaContentIntent() {
         val clickIntent1 = mock(PendingIntent::class.java)
@@ -619,6 +638,7 @@
         assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
         val delta = 0.0001F
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..fdef344 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
@@ -78,6 +80,8 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
@@ -174,6 +178,8 @@
     private lateinit var cancelText: TextView
     private lateinit var dismiss: FrameLayout
     private lateinit var dismissText: TextView
+    private lateinit var multiRippleView: MultiRippleView
+    private lateinit var turbulenceNoiseView: TurbulenceNoiseView
 
     private lateinit var session: MediaSession
     private lateinit var device: MediaDeviceData
@@ -205,6 +211,11 @@
     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)
+            this.set(Flags.MEDIA_FALSING_PENALTY, true)
+        }
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
@@ -244,7 +255,8 @@
                     keyguardStateController,
                     activityIntentHelper,
                     lockscreenUserManager,
-                    broadcastDialogController
+                    broadcastDialogController,
+                    fakeFeatureFlag
                 ) {
                 override fun loadAnimator(
                     animId: Int,
@@ -374,6 +386,9 @@
                     )
             }
 
+        multiRippleView = MultiRippleView(context, null)
+        turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
         whenever(viewHolder.player).thenReturn(view)
         whenever(viewHolder.appIcon).thenReturn(appIcon)
         whenever(viewHolder.albumView).thenReturn(albumView)
@@ -414,6 +429,9 @@
         whenever(viewHolder.getAction(R.id.action4)).thenReturn(action4)
 
         whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
+
+        whenever(viewHolder.multiRippleView).thenReturn(multiRippleView)
+        whenever(viewHolder.turbulenceNoiseView).thenReturn(turbulenceNoiseView)
     }
 
     /** Initialize elements for the recommendation view holder */
@@ -684,7 +702,7 @@
     }
 
     @Test
-    fun bind_seekBarDisabled_noActions_seekBarVisibilityIsSetToGone() {
+    fun bind_seekBarDisabled_noActions_seekBarVisibilityIsSetToInvisible() {
         useRealConstraintSets()
 
         val state = mediaData.copy(semanticActions = MediaButton())
@@ -693,7 +711,7 @@
 
         player.bindPlayer(state, PACKAGE)
 
-        assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.GONE)
+        assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.INVISIBLE)
     }
 
     @Test
@@ -723,7 +741,7 @@
     }
 
     @Test
-    fun seekBarChangesToDisabledAfterBind_noActions_seekBarChangesToGone() {
+    fun seekBarChangesToDisabledAfterBind_noActions_seekBarChangesToInvisible() {
         useRealConstraintSets()
 
         val state = mediaData.copy(semanticActions = MediaButton())
@@ -734,7 +752,7 @@
 
         getEnabledChangeListener().onEnabledChanged(enabled = false)
 
-        assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.GONE)
+        assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.INVISIBLE)
     }
 
     @Test
@@ -1126,6 +1144,19 @@
     /* ***** Guts tests for the player ***** */
 
     @Test
+    fun player_longClick_isFalse() {
+        whenever(falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
+        player.attachPlayer(viewHolder)
+
+        val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+        verify(viewHolder.player).onLongClickListener = captor.capture()
+
+        captor.value.onLongClick(viewHolder.player)
+        verify(mediaViewController, never()).openGuts()
+        verify(mediaViewController, never()).closeGuts()
+    }
+
+    @Test
     fun player_longClickWhenGutsClosed_gutsOpens() {
         player.attachPlayer(viewHolder)
         player.bindPlayer(mediaData, KEY)
@@ -1305,6 +1336,20 @@
     /* ***** Guts tests for the recommendations ***** */
 
     @Test
+    fun recommendations_longClick_isFalse() {
+        whenever(falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
+        player.attachRecommendation(recommendationViewHolder)
+        player.bindRecommendation(smartspaceData)
+
+        val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+        verify(viewHolder.player).onLongClickListener = captor.capture()
+
+        captor.value.onLongClick(viewHolder.player)
+        verify(mediaViewController, never()).openGuts()
+        verify(mediaViewController, never()).closeGuts()
+    }
+
+    @Test
     fun recommendations_longClickWhenGutsClosed_gutsOpens() {
         player.attachRecommendation(recommendationViewHolder)
         player.bindRecommendation(smartspaceData)
@@ -1973,6 +2018,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/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
new file mode 100644
index 0000000..771b986
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.media.dialog;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.media.MediaOutputConstants;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MediaOutputDialogReceiverTest extends SysuiTestCase {
+
+    private MediaOutputDialogReceiver mMediaOutputDialogReceiver;
+
+    private final MediaOutputDialogFactory mMockMediaOutputDialogFactory =
+            mock(MediaOutputDialogFactory.class);
+
+    private final MediaOutputBroadcastDialogFactory mMockMediaOutputBroadcastDialogFactory =
+            mock(MediaOutputBroadcastDialogFactory.class);
+
+    @Before
+    public void setup() {
+        mMediaOutputDialogReceiver = new MediaOutputDialogReceiver(mMockMediaOutputDialogFactory,
+                mMockMediaOutputBroadcastDialogFactory);
+    }
+
+    @Test
+    public void showOutputSwitcher_ExtraPackageName_DialogFactoryCalled() {
+        Intent intent = new Intent(Intent.ACTION_SHOW_OUTPUT_SWITCHER);
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, getContext().getPackageName());
+        mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+        verify(mMockMediaOutputDialogFactory, times(1))
+                .create(getContext().getPackageName(), false, null);
+        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+    }
+
+    @Test
+    public void showOutputSwitcher_WrongExtraKey_DialogFactoryNotCalled() {
+        Intent intent = new Intent(Intent.ACTION_SHOW_OUTPUT_SWITCHER);
+        intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
+        mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+    }
+
+    @Test
+    public void showOutputSwitcher_NoExtra_DialogFactoryNotCalled() {
+        Intent intent = new Intent(Intent.ACTION_SHOW_OUTPUT_SWITCHER);
+        mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+    }
+
+    @Test
+    public void launchMediaOutputDialog_ExtraPackageName_DialogFactoryCalled() {
+        Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
+        intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
+        mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+        verify(mMockMediaOutputDialogFactory, times(1))
+                .create(getContext().getPackageName(), false, null);
+        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+    }
+
+    @Test
+    public void launchMediaOutputDialog_WrongExtraKey_DialogFactoryNotCalled() {
+        Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
+        intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
+        mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+    }
+
+    @Test
+    public void launchMediaOutputDialog_NoExtra_DialogFactoryNotCalled() {
+        Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
+        mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+    }
+
+    @Test
+    public void launchMediaOutputBroadcastDialog_ExtraPackageName_BroadcastDialogFactoryCalled() {
+        Intent intent = new Intent(
+                MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
+        intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
+        mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogFactory, times(1))
+                .create(getContext().getPackageName(), false, null);
+    }
+
+    @Test
+    public void launchMediaOutputBroadcastDialog_WrongExtraKey_DialogBroadcastFactoryNotCalled() {
+        Intent intent = new Intent(
+                MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
+        intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
+        mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+    }
+
+    @Test
+    public void launchMediaOutputBroadcastDialog_NoExtra_BroadcastDialogFactoryNotCalled() {
+        Intent intent = new Intent(
+                MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
+        mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+    }
+
+    @Test
+    public void unKnownAction_ExtraPackageName_FactoriesNotCalled() {
+        Intent intent = new Intent("UnKnown Action");
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, getContext().getPackageName());
+        intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
+        mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+    }
+
+    @Test
+    public void unKnownActionAnd_NoExtra_FactoriesNotCalled() {
+        Intent intent = new Intent("UnKnown Action");
+        mMediaOutputDialogReceiver.onReceive(getContext(), intent);
+
+        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
+        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
new file mode 100644
index 0000000..4aa982e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.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.media.taptotransfer.receiver
+
+import android.content.Context
+import android.os.Handler
+import android.os.PowerManager
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import com.android.systemui.media.taptotransfer.MediaTttFlags
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLock
+
+class FakeMediaTttChipControllerReceiver(
+    commandQueue: CommandQueue,
+    context: Context,
+    logger: MediaTttLogger,
+    windowManager: WindowManager,
+    mainExecutor: DelayableExecutor,
+    accessibilityManager: AccessibilityManager,
+    configurationController: ConfigurationController,
+    powerManager: PowerManager,
+    mainHandler: Handler,
+    mediaTttFlags: MediaTttFlags,
+    uiEventLogger: MediaTttReceiverUiEventLogger,
+    viewUtil: ViewUtil,
+    wakeLockBuilder: WakeLock.Builder,
+) :
+    MediaTttChipControllerReceiver(
+        commandQueue,
+        context,
+        logger,
+        windowManager,
+        mainExecutor,
+        accessibilityManager,
+        configurationController,
+        powerManager,
+        mainHandler,
+        mediaTttFlags,
+        uiEventLogger,
+        viewUtil,
+        wakeLockBuilder,
+    ) {
+    override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+        // Just bypass the animation in tests
+        onAnimationEnd.run()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 8c3ae3d..23f7cdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLockFake
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -85,6 +86,10 @@
     private lateinit var fakeAppIconDrawable: Drawable
     private lateinit var uiEventLoggerFake: UiEventLoggerFake
     private lateinit var receiverUiEventLogger: MediaTttReceiverUiEventLogger
+    private lateinit var fakeClock: FakeSystemClock
+    private lateinit var fakeExecutor: FakeExecutor
+    private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+    private lateinit var fakeWakeLock: WakeLockFake
 
     @Before
     fun setUp() {
@@ -99,15 +104,22 @@
         )).thenReturn(applicationInfo)
         context.setMockPackageManager(packageManager)
 
+        fakeClock = FakeSystemClock()
+        fakeExecutor = FakeExecutor(fakeClock)
+
         uiEventLoggerFake = UiEventLoggerFake()
         receiverUiEventLogger = MediaTttReceiverUiEventLogger(uiEventLoggerFake)
 
-        controllerReceiver = MediaTttChipControllerReceiver(
+        fakeWakeLock = WakeLockFake()
+        fakeWakeLockBuilder = WakeLockFake.Builder(context)
+        fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
+        controllerReceiver = FakeMediaTttChipControllerReceiver(
             commandQueue,
             context,
             logger,
             windowManager,
-            FakeExecutor(FakeSystemClock()),
+            fakeExecutor,
             accessibilityManager,
             configurationController,
             powerManager,
@@ -115,6 +127,7 @@
             mediaTttFlags,
             receiverUiEventLogger,
             viewUtil,
+            fakeWakeLockBuilder,
         )
         controllerReceiver.start()
 
@@ -141,6 +154,7 @@
             mediaTttFlags,
             receiverUiEventLogger,
             viewUtil,
+            fakeWakeLockBuilder,
         )
         controllerReceiver.start()
 
@@ -200,6 +214,39 @@
     }
 
     @Test
+    fun commandQueueCallback_closeThenFar_wakeLockAcquiredThenReleased() {
+        commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+                StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
+                routeInfo,
+                null,
+                null
+        )
+
+        assertThat(fakeWakeLock.isHeld).isTrue()
+
+        commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+                StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
+                routeInfo,
+                null,
+                null
+        )
+
+        assertThat(fakeWakeLock.isHeld).isFalse()
+    }
+
+    @Test
+    fun commandQueueCallback_closeThenFar_wakeLockNeverAcquired() {
+        commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+                StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
+                routeInfo,
+                null,
+                null
+        )
+
+        assertThat(fakeWakeLock.isHeld).isFalse()
+    }
+
+    @Test
     fun receivesNewStateFromCommandQueue_isLogged() {
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
@@ -214,7 +261,12 @@
     @Test
     fun updateView_noOverrides_usesInfoFromAppIcon() {
         controllerReceiver.displayView(
-            ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride = null)
+            ChipReceiverInfo(
+                routeInfo,
+                appIconDrawableOverride = null,
+                appNameOverride = null,
+                id = "id",
+            )
         )
 
         val view = getChipView()
@@ -227,7 +279,12 @@
         val drawableOverride = context.getDrawable(R.drawable.ic_celebration)!!
 
         controllerReceiver.displayView(
-            ChipReceiverInfo(routeInfo, drawableOverride, appNameOverride = null)
+            ChipReceiverInfo(
+                routeInfo,
+                drawableOverride,
+                appNameOverride = null,
+                id = "id",
+            )
         )
 
         val view = getChipView()
@@ -239,7 +296,12 @@
         val appNameOverride = "Sweet New App"
 
         controllerReceiver.displayView(
-            ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride)
+            ChipReceiverInfo(
+                routeInfo,
+                appIconDrawableOverride = null,
+                appNameOverride,
+                id = "id",
+            )
         )
 
         val view = getChipView()
@@ -293,7 +355,7 @@
             .addFeature("feature")
             .setClientPackageName(packageName)
             .build()
-        return ChipReceiverInfo(routeInfo, null, null)
+        return ChipReceiverInfo(routeInfo, null, null, id = "id")
     }
 
     private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
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 ad19bc2..311740e 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
@@ -52,6 +52,7 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLockFake
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -89,6 +90,8 @@
     @Mock private lateinit var viewUtil: ViewUtil
     @Mock private lateinit var windowManager: WindowManager
     @Mock private lateinit var vibratorHelper: VibratorHelper
+    private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+    private lateinit var fakeWakeLock: WakeLockFake
     private lateinit var chipbarCoordinator: ChipbarCoordinator
     private lateinit var commandQueueCallback: CommandQueue.Callbacks
     private lateinit var fakeAppIconDrawable: Drawable
@@ -118,6 +121,10 @@
         fakeClock = FakeSystemClock()
         fakeExecutor = FakeExecutor(fakeClock)
 
+        fakeWakeLock = WakeLockFake()
+        fakeWakeLockBuilder = WakeLockFake.Builder(context)
+        fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
         uiEventLoggerFake = UiEventLoggerFake()
         uiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
 
@@ -134,6 +141,7 @@
                 falsingCollector,
                 viewUtil,
                 vibratorHelper,
+                fakeWakeLockBuilder,
             )
         chipbarCoordinator.start()
 
@@ -257,6 +265,8 @@
 
     @Test
     fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
+        displayReceiverTriggered()
+        reset(vibratorHelper)
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -270,13 +280,15 @@
             .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
-        assertThat(uiEventLoggerFake.eventId(0))
+        // Event index 1 since initially displaying the triggered chip would also log an event.
+        assertThat(uiEventLoggerFake.eventId(1))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
         verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
     }
 
     @Test
     fun transferToReceiverSucceeded_nullUndoCallback_noUndo() {
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -289,6 +301,7 @@
 
     @Test
     fun transferToReceiverSucceeded_withUndoRunnable_undoVisible() {
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -305,6 +318,7 @@
     @Test
     fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
         var undoCallbackCalled = false
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -317,8 +331,9 @@
 
         getChipbarView().getUndoButton().performClick()
 
-        // Event index 1 since initially displaying the succeeded chip would also log an event
-        assertThat(uiEventLoggerFake.eventId(1))
+        // Event index 2 since initially displaying the triggered and succeeded chip would also log
+        // events.
+        assertThat(uiEventLoggerFake.eventId(2))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id)
         assertThat(undoCallbackCalled).isTrue()
         assertThat(getChipbarView().getChipText())
@@ -327,6 +342,8 @@
 
     @Test
     fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
+        displayThisDeviceTriggered()
+        reset(vibratorHelper)
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -340,13 +357,15 @@
             .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
-        assertThat(uiEventLoggerFake.eventId(0))
+        // Event index 1 since initially displaying the triggered chip would also log an event.
+        assertThat(uiEventLoggerFake.eventId(1))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id)
         verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
     }
 
     @Test
     fun transferToThisDeviceSucceeded_nullUndoCallback_noUndo() {
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -359,6 +378,7 @@
 
     @Test
     fun transferToThisDeviceSucceeded_withUndoRunnable_undoVisible() {
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -375,6 +395,7 @@
     @Test
     fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
         var undoCallbackCalled = false
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -387,8 +408,9 @@
 
         getChipbarView().getUndoButton().performClick()
 
-        // Event index 1 since initially displaying the succeeded chip would also log an event
-        assertThat(uiEventLoggerFake.eventId(1))
+        // Event index 2 since initially displaying the triggered and succeeded chip would also log
+        // events.
+        assertThat(uiEventLoggerFake.eventId(2))
             .isEqualTo(
                 MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
             )
@@ -399,6 +421,8 @@
 
     @Test
     fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
+        displayReceiverTriggered()
+        reset(vibratorHelper)
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
             routeInfo,
@@ -413,7 +437,8 @@
         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
-        assertThat(uiEventLoggerFake.eventId(0))
+        // Event index 1 since initially displaying the triggered chip would also log an event.
+        assertThat(uiEventLoggerFake.eventId(1))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id)
         verify(vibratorHelper).vibrate(any<VibrationEffect>())
     }
@@ -421,6 +446,12 @@
     @Test
     fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null
+        )
+        reset(vibratorHelper)
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
             routeInfo,
             null
@@ -434,7 +465,8 @@
         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
-        assertThat(uiEventLoggerFake.eventId(0))
+        // Event index 1 since initially displaying the triggered chip would also log an event.
+        assertThat(uiEventLoggerFake.eventId(1))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id)
         verify(vibratorHelper).vibrate(any<VibrationEffect>())
     }
@@ -472,6 +504,36 @@
     }
 
     @Test
+    fun commandQueueCallback_almostCloseThenFarFromReceiver_wakeLockAcquiredThenReleased() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+
+        assertThat(fakeWakeLock.isHeld).isTrue()
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null
+        )
+
+        assertThat(fakeWakeLock.isHeld).isFalse()
+    }
+
+    @Test
+    fun commandQueueCallback_FarFromReceiver_wakeLockNeverReleased() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null
+        )
+
+        assertThat(fakeWakeLock.isHeld).isFalse()
+    }
+
+    @Test
     fun commandQueueCallback_invalidStateParam_noChipShown() {
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(100, routeInfo, null)
 
@@ -479,6 +541,166 @@
     }
 
     @Test
+    fun commandQueueCallback_receiverTriggeredThenAlmostStart_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_thisDeviceTriggeredThenAlmostEnd_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_receiverSucceededThenReceiverTriggered_invalidTransitionLogged() {
+        displayReceiverTriggered()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            null
+        )
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_thisDeviceSucceededThenThisDeviceTriggered_invalidTransitionLogged() {
+        displayThisDeviceTriggered()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            null
+        )
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_almostStartThenReceiverSucceeded_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_almostEndThenThisDeviceSucceeded_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_AlmostStartThenReceiverFailed_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_almostEndThenThisDeviceFailed_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
     fun receivesNewStateFromCommandQueue_isLogged() {
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
@@ -537,6 +759,7 @@
 
     @Test
     fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -560,6 +783,7 @@
 
     @Test
     fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -583,6 +807,7 @@
 
     @Test
     fun transferToReceiverSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -622,6 +847,7 @@
 
     @Test
     fun transferToThisDeviceSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -679,6 +905,26 @@
     private fun ChipStateSender.getExpectedStateText(): String? {
         return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context)
     }
+
+    // display receiver triggered state helper method to make sure we start from a valid state
+    // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_RECEIVER_TRIGGERED).
+    private fun displayReceiverTriggered() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            routeInfo,
+            null
+        )
+    }
+
+    // display this device triggered state helper method to make sure we start from a valid state
+    // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_THIS_DEVICE_TRIGGERED).
+    private fun displayThisDeviceTriggered() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null
+        )
+    }
 }
 
 private const val APP_NAME = "Fake app name"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt
new file mode 100644
index 0000000..9b346d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.mediaprojection.appselector.data
+
+import android.content.ComponentName
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import androidx.test.filters.SmallTest
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.FastBitmapDrawable
+import com.android.launcher3.icons.IconFactory
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.system.PackageManagerWrapper
+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.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class IconLoaderLibAppIconLoaderTest : SysuiTestCase() {
+
+    private val iconFactory: IconFactory = mock()
+    private val packageManagerWrapper: PackageManagerWrapper = mock()
+    private val packageManager: PackageManager = mock()
+    private val dispatcher = Dispatchers.Unconfined
+
+    private val appIconLoader =
+        IconLoaderLibAppIconLoader(
+            backgroundDispatcher = dispatcher,
+            context = context,
+            packageManagerWrapper = packageManagerWrapper,
+            packageManager = packageManager,
+            iconFactoryProvider = { iconFactory }
+        )
+
+    @Test
+    fun loadIcon_loadsIconUsingTheSameUserId() {
+        val icon = createIcon()
+        val component = ComponentName("com.test", "TestApplication")
+        givenIcon(component, userId = 123, icon = icon)
+
+        val loadedIcon = runBlocking { appIconLoader.loadIcon(userId = 123, component = component) }
+
+        assertThat(loadedIcon).isEqualTo(icon)
+    }
+
+    private fun givenIcon(component: ComponentName, userId: Int, icon: FastBitmapDrawable) {
+        val activityInfo = mock<ActivityInfo>()
+        whenever(packageManagerWrapper.getActivityInfo(component, userId)).thenReturn(activityInfo)
+        val rawIcon = mock<Drawable>()
+        whenever(activityInfo.loadIcon(packageManager)).thenReturn(rawIcon)
+
+        val bitmapInfo = mock<BitmapInfo>()
+        whenever(iconFactory.createBadgedIconBitmap(eq(rawIcon), any())).thenReturn(bitmapInfo)
+        whenever(bitmapInfo.newIcon(context)).thenReturn(icon)
+    }
+
+    private fun createIcon(): FastBitmapDrawable =
+        FastBitmapDrawable(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index 939af16..d35a212 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -4,6 +4,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -11,11 +12,11 @@
 import com.android.wm.shell.util.GroupedRecentTaskInfo
 import com.google.common.truth.Truth.assertThat
 import java.util.*
+import java.util.function.Consumer
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.util.function.Consumer
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -23,8 +24,14 @@
 
     private val dispatcher = Dispatchers.Unconfined
     private val recentTasks: RecentTasks = mock()
+    private val userTracker: UserTracker = mock()
     private val recentTaskListProvider =
-        ShellRecentTaskListProvider(dispatcher, Runnable::run, Optional.of(recentTasks))
+        ShellRecentTaskListProvider(
+            dispatcher,
+            Runnable::run,
+            Optional.of(recentTasks),
+            userTracker
+        )
 
     @Test
     fun loadRecentTasks_oneTask_returnsTheSameTask() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 6c03730..1865ef6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.navigationbar;
 
+import static android.app.StatusBarManager.WINDOW_NAVIGATION_BAR;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
 
@@ -47,6 +48,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -69,6 +71,10 @@
 @SmallTest
 public class NavBarHelperTest extends SysuiTestCase {
 
+    private static final int DISPLAY_ID = 0;
+    private static final int WINDOW = WINDOW_NAVIGATION_BAR;
+    private static final int STATE_ID = 0;
+
     @Mock
     AccessibilityManager mAccessibilityManager;
     @Mock
@@ -93,6 +99,8 @@
     DumpManager mDumpManager;
     @Mock
     NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater;
+    @Mock
+    CommandQueue mCommandQueue;
     private AccessibilityManager.AccessibilityServicesStateChangeListener
             mAccessibilityServicesStateChangeListener;
 
@@ -114,7 +122,7 @@
                 mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver,
                 mSystemActions, mOverviewProxyService, mAssistManagerLazy,
                 () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
-                mNavigationModeController, mUserTracker, mDumpManager);
+                mNavigationModeController, mUserTracker, mDumpManager, mCommandQueue);
 
     }
 
@@ -241,6 +249,45 @@
                 ACCESSIBILITY_BUTTON_CLICKABLE_STATE);
     }
 
+    @Test
+    public void registerCommandQueueCallbacks() {
+        mNavBarHelper.init();
+        verify(mCommandQueue, times(1)).addCallback(any());
+    }
+
+    @Test
+    public void saveMostRecentSysuiState() {
+        mNavBarHelper.init();
+        mNavBarHelper.setWindowState(DISPLAY_ID, WINDOW, STATE_ID);
+        NavBarHelper.CurrentSysuiState state1 = mNavBarHelper.getCurrentSysuiState();
+
+        // Update window state
+        int newState = STATE_ID + 1;
+        mNavBarHelper.setWindowState(DISPLAY_ID, WINDOW, newState);
+        NavBarHelper.CurrentSysuiState state2 = mNavBarHelper.getCurrentSysuiState();
+
+        // Ensure we get most recent state back
+        assertThat(state1.mWindowState).isNotEqualTo(state2.mWindowState);
+        assertThat(state1.mWindowStateDisplayId).isEqualTo(state2.mWindowStateDisplayId);
+        assertThat(state2.mWindowState).isEqualTo(newState);
+    }
+
+    @Test
+    public void ignoreNonNavbarSysuiState() {
+        mNavBarHelper.init();
+        mNavBarHelper.setWindowState(DISPLAY_ID, WINDOW, STATE_ID);
+        NavBarHelper.CurrentSysuiState state1 = mNavBarHelper.getCurrentSysuiState();
+
+        // Update window state for other window type
+        int newState = STATE_ID + 1;
+        mNavBarHelper.setWindowState(DISPLAY_ID, WINDOW + 1, newState);
+        NavBarHelper.CurrentSysuiState state2 = mNavBarHelper.getCurrentSysuiState();
+
+        // Ensure we get first state back
+        assertThat(state2.mWindowState).isEqualTo(state1.mWindowState);
+        assertThat(state2.mWindowState).isNotEqualTo(newState);
+    }
+
     private List<String> createFakeShortcutTargets() {
         return new ArrayList<>(List.of("a", "b", "c", "d"));
     }
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..80adbf0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -44,14 +44,11 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Handler;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.telecom.TelecomManager;
 import android.testing.AndroidTestingRunner;
@@ -61,6 +58,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;
@@ -78,7 +76,6 @@
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
 import com.android.systemui.accessibility.SystemActions;
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
@@ -118,6 +115,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
@@ -165,7 +163,7 @@
     @Mock
     private Handler mHandler;
     @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
+    private UserTracker mUserTracker;
     @Mock
     private UiEventLogger mUiEventLogger;
     @Mock
@@ -201,6 +199,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 +227,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.
@@ -243,7 +244,7 @@
                     mSystemActions, mOverviewProxyService,
                     () -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces),
                     mKeyguardStateController, mock(NavigationModeController.class),
-                    mock(UserTracker.class), mock(DumpManager.class)));
+                    mock(UserTracker.class), mock(DumpManager.class), mock(CommandQueue.class)));
             mNavigationBar = createNavBar(mContext);
             mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
         });
@@ -311,14 +312,10 @@
     }
 
     @Test
-    public void testRegisteredWithDispatcher() {
+    public void testRegisteredWithUserTracker() {
         mNavigationBar.init();
         mNavigationBar.onViewAttached();
-        verify(mBroadcastDispatcher).registerReceiverWithHandler(
-                any(BroadcastReceiver.class),
-                any(IntentFilter.class),
-                any(Handler.class),
-                any(UserHandle.class));
+        verify(mUserTracker).addCallback(any(UserTracker.Callback.class), any(Executor.class));
     }
 
     @Test
@@ -434,6 +431,12 @@
         verify(mNavigationBarView).setVisibility(View.INVISIBLE);
     }
 
+    @Test
+    public void testOnInit_readCurrentSysuiState() {
+        mNavigationBar.init();
+        verify(mNavBarHelper, times(1)).getCurrentSysuiState();
+    }
+
     private NavigationBar createNavBar(Context context) {
         DeviceProvisionedController deviceProvisionedController =
                 mock(DeviceProvisionedController.class);
@@ -453,7 +456,7 @@
                 mStatusBarStateController,
                 mStatusBarKeyguardViewManager,
                 mMockSysUiState,
-                mBroadcastDispatcher,
+                mUserTracker,
                 mCommandQueue,
                 Optional.of(mock(Pip.class)),
                 Optional.of(mock(Recents.class)),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index f20c6a2..4a9c750 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -16,17 +16,22 @@
 package com.android.systemui.notetask
 
 import android.app.KeyguardManager
+import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
+import android.content.pm.PackageManager
 import android.os.UserManager
 import android.test.suitebuilder.annotation.SmallTest
-import android.view.KeyEvent
 import androidx.test.runner.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
-import com.android.wm.shell.floating.FloatingTasks
-import java.util.*
+import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -48,9 +53,10 @@
     private val notesIntent = Intent(NOTES_ACTION)
 
     @Mock lateinit var context: Context
+    @Mock lateinit var packageManager: PackageManager
     @Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver
-    @Mock lateinit var floatingTasks: FloatingTasks
-    @Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks>
+    @Mock lateinit var bubbles: Bubbles
+    @Mock lateinit var optionalBubbles: Optional<Bubbles>
     @Mock lateinit var keyguardManager: KeyguardManager
     @Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager>
     @Mock lateinit var optionalUserManager: Optional<UserManager>
@@ -60,8 +66,9 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        whenever(context.packageManager).thenReturn(packageManager)
         whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent)
-        whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks)
+        whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
         whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
         whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
         whenever(userManager.isUserUnlocked).thenReturn(true)
@@ -71,96 +78,132 @@
         return NoteTaskController(
             context = context,
             intentResolver = noteTaskIntentResolver,
-            optionalFloatingTasks = optionalFloatingTasks,
+            optionalBubbles = optionalBubbles,
             optionalKeyguardManager = optionalKeyguardManager,
             optionalUserManager = optionalUserManager,
             isEnabled = isEnabled,
         )
     }
 
+    // region showNoteTask
     @Test
-    fun handleSystemKey_keyguardIsLocked_shouldStartActivity() {
+    fun showNoteTask_keyguardIsLocked_shouldStartActivity() {
         whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_keyguardIsUnlocked_shouldStartFloatingTask() {
+    fun showNoteTask_keyguardIsUnlocked_shouldStartBubbles() {
         whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
-        verify(floatingTasks).showOrSetStashed(notesIntent)
+        verify(bubbles).showAppBubble(notesIntent)
         verify(context, never()).startActivity(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+    fun showNoteTask_isInMultiWindowMode_shouldStartActivity() {
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
 
-        verify(context, never()).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = true)
+
+        verify(context).startActivity(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_floatingTasksIsNull_shouldDoNothing() {
-        whenever(optionalFloatingTasks.orElse(null)).thenReturn(null)
+    fun showNoteTask_bubblesIsNull_shouldDoNothing() {
+        whenever(optionalBubbles.orElse(null)).thenReturn(null)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_keyguardManagerIsNull_shouldDoNothing() {
+    fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() {
         whenever(optionalKeyguardManager.orElse(null)).thenReturn(null)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_userManagerIsNull_shouldDoNothing() {
+    fun showNoteTask_userManagerIsNull_shouldDoNothing() {
         whenever(optionalUserManager.orElse(null)).thenReturn(null)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_intentResolverReturnsNull_shouldDoNothing() {
+    fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() {
         whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(null)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_flagDisabled_shouldDoNothing() {
-        createNoteTaskController(isEnabled = false).handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+    fun showNoteTask_flagDisabled_shouldDoNothing() {
+        createNoteTaskController(isEnabled = false).showNoteTask()
 
         verify(context, never()).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_userIsLocked_shouldDoNothing() {
+    fun showNoteTask_userIsLocked_shouldDoNothing() {
         whenever(userManager.isUserUnlocked).thenReturn(false)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
+    // endregion
+
+    // region setNoteTaskShortcutEnabled
+    @Test
+    fun setNoteTaskShortcutEnabled_setTrue() {
+        createNoteTaskController().setNoteTaskShortcutEnabled(value = true)
+
+        val argument = argumentCaptor<ComponentName>()
+        verify(context.packageManager)
+            .setComponentEnabledSetting(
+                argument.capture(),
+                eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
+                eq(PackageManager.DONT_KILL_APP),
+            )
+        val expected = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+        assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
+    }
+
+    @Test
+    fun setNoteTaskShortcutEnabled_setFalse() {
+        createNoteTaskController().setNoteTaskShortcutEnabled(value = false)
+
+        val argument = argumentCaptor<ComponentName>()
+        verify(context.packageManager)
+            .setComponentEnabledSetting(
+                argument.capture(),
+                eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+                eq(PackageManager.DONT_KILL_APP),
+            )
+        val expected = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+        assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
+    }
+    // endregion
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index f344c8d..538131a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -16,13 +16,13 @@
 package com.android.systemui.notetask
 
 import android.test.suitebuilder.annotation.SmallTest
+import android.view.KeyEvent
 import androidx.test.runner.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.wm.shell.floating.FloatingTasks
-import java.util.*
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -43,26 +43,28 @@
 internal class NoteTaskInitializerTest : SysuiTestCase() {
 
     @Mock lateinit var commandQueue: CommandQueue
-    @Mock lateinit var floatingTasks: FloatingTasks
-    @Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks>
+    @Mock lateinit var bubbles: Bubbles
+    @Mock lateinit var optionalBubbles: Optional<Bubbles>
+    @Mock lateinit var noteTaskController: NoteTaskController
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        whenever(optionalFloatingTasks.isPresent).thenReturn(true)
-        whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks)
+        whenever(optionalBubbles.isPresent).thenReturn(true)
+        whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
     }
 
     private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer {
         return NoteTaskInitializer(
-            optionalFloatingTasks = optionalFloatingTasks,
-            lazyNoteTaskController = mock(),
+            optionalBubbles = optionalBubbles,
+            noteTaskController = noteTaskController,
             commandQueue = commandQueue,
             isEnabled = isEnabled,
         )
     }
 
+    // region initializer
     @Test
     fun initialize_shouldAddCallbacks() {
         createNoteTaskInitializer().initialize()
@@ -78,11 +80,42 @@
     }
 
     @Test
-    fun initialize_floatingTasksNotPresent_shouldDoNothing() {
-        whenever(optionalFloatingTasks.isPresent).thenReturn(false)
+    fun initialize_bubblesNotPresent_shouldDoNothing() {
+        whenever(optionalBubbles.isPresent).thenReturn(false)
 
         createNoteTaskInitializer().initialize()
 
         verify(commandQueue, never()).addCallback(any())
     }
+
+    @Test
+    fun initialize_flagEnabled_shouldEnableShortcut() {
+        createNoteTaskInitializer().initialize()
+
+        verify(noteTaskController).setNoteTaskShortcutEnabled(true)
+    }
+
+    @Test
+    fun initialize_flagDisabled_shouldDisableShortcut() {
+        createNoteTaskInitializer(isEnabled = false).initialize()
+
+        verify(noteTaskController).setNoteTaskShortcutEnabled(false)
+    }
+    // endregion
+
+    // region handleSystemKey
+    @Test
+    fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
+        createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+        verify(noteTaskController).showNoteTask()
+    }
+
+    @Test
+    fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
+        createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+
+        verify(noteTaskController, never()).showNoteTask()
+    }
+    // endregion
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index c377c37..338182a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -48,6 +48,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.power.PowerUI.WarningsUI;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
@@ -85,6 +86,7 @@
     private PowerUI mPowerUI;
     @Mock private EnhancedEstimates mEnhancedEstimates;
     @Mock private PowerManager mPowerManager;
+    @Mock private UserTracker mUserTracker;
     @Mock private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock private IThermalService mThermalServiceMock;
     private IThermalEventListener mUsbThermalEventListener;
@@ -682,7 +684,8 @@
     private void createPowerUi() {
         mPowerUI = new PowerUI(
                 mContext, mBroadcastDispatcher, mCommandQueue, mCentralSurfacesOptionalLazy,
-                mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager);
+                mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager,
+                mUserTracker);
         mPowerUI.mThermalService = mThermalServiceMock;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
index 346d1e6..65210d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
@@ -31,7 +31,6 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -133,7 +132,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isEnabledForQuickSettings()).isFalse();
+        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
     }
 
     @Test
@@ -152,7 +151,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
     }
 
     @Test
@@ -162,7 +161,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
     }
 
     @Test
@@ -172,7 +171,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
     }
 
     @Test
@@ -182,7 +181,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/abc.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
     }
 
     @Test
@@ -192,7 +191,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isEnabledForQuickSettings()).isFalse();
+        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
     }
 
     @Test
@@ -202,21 +201,21 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
 
         mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
                 "def/.ijk", false);
         verifyActivityDetails("def/.ijk");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
 
         mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
                 null, false);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
 
         // Once from setup + twice from this function
         verify(mCallback, times(3)).onQRCodeScannerActivityChanged();
@@ -229,7 +228,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isEnabledForQuickSettings()).isFalse();
+        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
 
         mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
@@ -237,14 +236,14 @@
 
         verifyActivityDetails("def/.ijk");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
 
         mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
                 null, false);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isEnabledForQuickSettings()).isFalse();
+        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
         verify(mCallback, times(2)).onQRCodeScannerActivityChanged();
     }
 
@@ -296,19 +295,19 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
 
         mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
                 UserHandle.USER_CURRENT);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
 
         mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1",
                 UserHandle.USER_CURRENT);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
         // Once from setup + twice from this function
         verify(mCallback, times(3)).onQRCodeScannerPreferenceChanged();
     }
@@ -320,13 +319,13 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
 
         mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
                 QR_CODE_SCANNER_PREFERENCE_CHANGE);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isEnabledForQuickSettings()).isFalse();
+        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
 
         // Unregister once again and make sure it affects the next register event
         mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
@@ -335,7 +334,7 @@
                 QR_CODE_SCANNER_PREFERENCE_CHANGE);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
     }
 
     @Test
@@ -345,7 +344,7 @@
                 /* enableOnLockScreen */ false);
         assertThat(mController.getIntent()).isNotNull();
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
         assertThat(getSettingsQRCodeDefaultComponent()).isNull();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 7bae115..17eb6e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
@@ -29,6 +30,9 @@
 
 import android.app.IActivityManager;
 import android.app.IForegroundServiceObserver;
+import android.app.job.IUserVisibleJobObserver;
+import android.app.job.JobScheduler;
+import android.app.job.UserVisibleJobSummary;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -59,6 +63,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -79,6 +84,8 @@
     @Mock
     IActivityManager mIActivityManager;
     @Mock
+    JobScheduler mJobScheduler;
+    @Mock
     PackageManager mPackageManager;
     @Mock
     UserTracker mUserTracker;
@@ -92,8 +99,10 @@
     private FgsManagerController mFmc;
 
     private IForegroundServiceObserver mIForegroundServiceObserver;
+    private IUserVisibleJobObserver mIUserVisibleJobObserver;
     private UserTracker.Callback mUserTrackerCallback;
     private BroadcastReceiver mShowFgsManagerReceiver;
+    private InOrder mJobSchedulerInOrder;
 
     private List<UserInfo> mUserProfiles;
 
@@ -111,6 +120,8 @@
         mUserProfiles = new ArrayList<>();
         Mockito.doReturn(mUserProfiles).when(mUserTracker).getUserProfiles();
 
+        mJobSchedulerInOrder = Mockito.inOrder(mJobScheduler);
+
         mFmc = createFgsManagerController();
     }
 
@@ -132,6 +143,52 @@
     }
 
     @Test
+    public void testNumPackages_jobs() throws RemoteException {
+        setUserProfiles(0);
+        setShowUserVisibleJobs(true);
+
+        UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
+        UserVisibleJobSummary j2 = new UserVisibleJobSummary(1, 0, "pkg2", 1);
+        Assert.assertEquals(0, mFmc.getNumRunningPackages());
+        mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, true);
+        Assert.assertEquals(1, mFmc.getNumRunningPackages());
+        mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j2, true);
+        Assert.assertEquals(2, mFmc.getNumRunningPackages());
+        mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, false);
+        Assert.assertEquals(1, mFmc.getNumRunningPackages());
+        mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j2, false);
+        Assert.assertEquals(0, mFmc.getNumRunningPackages());
+    }
+
+    @Test
+    public void testNumPackages_FgsAndJobs() throws RemoteException {
+        setUserProfiles(0);
+        setShowUserVisibleJobs(true);
+
+        Binder b1 = new Binder();
+        Binder b2 = new Binder();
+        UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
+        UserVisibleJobSummary j3 = new UserVisibleJobSummary(1, 0, "pkg3", 1);
+        Assert.assertEquals(0, mFmc.getNumRunningPackages());
+        mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, true);
+        Assert.assertEquals(1, mFmc.getNumRunningPackages());
+        mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, true);
+        Assert.assertEquals(2, mFmc.getNumRunningPackages());
+        mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, true);
+        Assert.assertEquals(2, mFmc.getNumRunningPackages());
+        mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j3, true);
+        Assert.assertEquals(3, mFmc.getNumRunningPackages());
+        mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, false);
+        Assert.assertEquals(2, mFmc.getNumRunningPackages());
+        mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j3, false);
+        Assert.assertEquals(1, mFmc.getNumRunningPackages());
+        mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, false);
+        Assert.assertEquals(1, mFmc.getNumRunningPackages());
+        mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, false);
+        Assert.assertEquals(0, mFmc.getNumRunningPackages());
+    }
+
+    @Test
     public void testNumPackagesDoesNotChangeWhenSecondFgsIsStarted() throws RemoteException {
         setUserProfiles(0);
 
@@ -243,6 +300,91 @@
         Assert.assertEquals(0, mFmc.visibleButtonsCount());
     }
 
+    @Test
+    public void testShowUserVisibleJobsOnCreation() {
+        // Test when the default is on.
+        mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
+                "true", false);
+        FgsManagerController fmc = new FgsManagerControllerImpl(
+                mContext,
+                mMainExecutor,
+                mBackgroundExecutor,
+                mSystemClock,
+                mIActivityManager,
+                mJobScheduler,
+                mPackageManager,
+                mUserTracker,
+                mDeviceConfigProxyFake,
+                mDialogLaunchAnimator,
+                mBroadcastDispatcher,
+                mDumpManager
+        );
+        fmc.init();
+        Assert.assertTrue(fmc.getIncludesUserVisibleJobs());
+        ArgumentCaptor<IUserVisibleJobObserver> iUserVisibleJobObserverArgumentCaptor =
+                ArgumentCaptor.forClass(IUserVisibleJobObserver.class);
+        mJobSchedulerInOrder.verify(mJobScheduler)
+                .registerUserVisibleJobObserver(iUserVisibleJobObserverArgumentCaptor.capture());
+        Assert.assertNotNull(iUserVisibleJobObserverArgumentCaptor.getValue());
+
+        // Test when the default is off.
+        mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
+                "false", false);
+        fmc = new FgsManagerControllerImpl(
+                mContext,
+                mMainExecutor,
+                mBackgroundExecutor,
+                mSystemClock,
+                mIActivityManager,
+                mJobScheduler,
+                mPackageManager,
+                mUserTracker,
+                mDeviceConfigProxyFake,
+                mDialogLaunchAnimator,
+                mBroadcastDispatcher,
+                mDumpManager
+        );
+        fmc.init();
+        Assert.assertFalse(fmc.getIncludesUserVisibleJobs());
+        mJobSchedulerInOrder.verify(mJobScheduler, never()).registerUserVisibleJobObserver(any());
+    }
+
+    @Test
+    public void testShowUserVisibleJobsToggling() throws Exception {
+        setUserProfiles(0);
+        setShowUserVisibleJobs(true);
+
+        // pkg1 has only job
+        // pkg2 has both job and fgs
+        // pkg3 has only fgs
+        UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
+        UserVisibleJobSummary j2 = new UserVisibleJobSummary(1, 0, "pkg2", 1);
+        Binder b2 = new Binder();
+        Binder b3 = new Binder();
+
+        Assert.assertEquals(0, mFmc.getNumRunningPackages());
+        mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, true);
+        mIForegroundServiceObserver.onForegroundStateChanged(b3, "pkg3", 0, true);
+        Assert.assertEquals(2, mFmc.getNumRunningPackages());
+        mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, true);
+        mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j2, true);
+        Assert.assertEquals(3, mFmc.getNumRunningPackages());
+
+        // Turn off the flag, confirm the number of packages is updated properly.
+        setShowUserVisibleJobs(false);
+        // Only pkg1 should be removed since the other two have fgs
+        Assert.assertEquals(2, mFmc.getNumRunningPackages());
+
+        setShowUserVisibleJobs(true);
+
+        Assert.assertEquals(2, mFmc.getNumRunningPackages());
+        mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, true);
+        mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j2, true);
+        Assert.assertEquals(3, mFmc.getNumRunningPackages());
+    }
+
     private void setShowStopButtonForUserAllowlistedApps(boolean enable) {
         mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
@@ -251,6 +393,33 @@
         mBackgroundExecutor.runAllReady();
     }
 
+    private void setShowUserVisibleJobs(boolean enable) {
+        if (mFmc.getIncludesUserVisibleJobs() == enable) {
+            // No change.
+            return;
+        }
+
+        mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
+                enable ? "true" : "false", false);
+        mBackgroundExecutor.advanceClockToLast();
+        mBackgroundExecutor.runAllReady();
+
+        ArgumentCaptor<IUserVisibleJobObserver> iUserVisibleJobObserverArgumentCaptor =
+                ArgumentCaptor.forClass(IUserVisibleJobObserver.class);
+        if (enable) {
+            mJobSchedulerInOrder.verify(mJobScheduler).registerUserVisibleJobObserver(
+                    iUserVisibleJobObserverArgumentCaptor.capture()
+            );
+            mIUserVisibleJobObserver = iUserVisibleJobObserverArgumentCaptor.getValue();
+        } else {
+            mJobSchedulerInOrder.verify(mJobScheduler).unregisterUserVisibleJobObserver(
+                    eq(mIUserVisibleJobObserver)
+            );
+            mIUserVisibleJobObserver = null;
+        }
+    }
+
     private void setBackgroundRestrictionExemptionReason(String pkgName, int uid, int reason)
             throws Exception {
         Mockito.doReturn(uid)
@@ -275,6 +444,7 @@
                 mBackgroundExecutor,
                 mSystemClock,
                 mIActivityManager,
+                mJobScheduler,
                 mPackageManager,
                 mUserTracker,
                 mDeviceConfigProxyFake,
@@ -304,6 +474,15 @@
         mUserTrackerCallback = userTrackerCallbackArgumentCaptor.getValue();
         mShowFgsManagerReceiver = showFgsManagerReceiverArgumentCaptor.getValue();
 
+        if (result.getIncludesUserVisibleJobs()) {
+            ArgumentCaptor<IUserVisibleJobObserver> iUserVisibleJobObserverArgumentCaptor =
+                    ArgumentCaptor.forClass(IUserVisibleJobObserver.class);
+            mJobSchedulerInOrder.verify(mJobScheduler).registerUserVisibleJobObserver(
+                    iUserVisibleJobObserverArgumentCaptor.capture()
+            );
+            mIUserVisibleJobObserver = iUserVisibleJobObserverArgumentCaptor.getValue();
+        }
+
         return result;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
index 4c72406..3620233 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.privacy.PrivacyItemController
 import com.android.systemui.privacy.logging.PrivacyLogger
 import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -66,6 +67,8 @@
     private lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock
     private lateinit var safetyCenterManager: SafetyCenterManager
+    @Mock
+    private lateinit var deviceProvisionedController: DeviceProvisionedController
 
     private val uiExecutor = FakeExecutor(FakeSystemClock())
     private val backgroundExecutor = FakeExecutor(FakeSystemClock())
@@ -80,6 +83,7 @@
         whenever(privacyChip.context).thenReturn(context)
         whenever(privacyChip.resources).thenReturn(context.resources)
         whenever(privacyChip.isAttachedToWindow).thenReturn(true)
+        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
 
         cameraSlotName = context.getString(com.android.internal.R.string.status_bar_camera)
         microphoneSlotName = context.getString(com.android.internal.R.string.status_bar_microphone)
@@ -98,7 +102,8 @@
                 activityStarter,
                 appOpsController,
                 broadcastDispatcher,
-                safetyCenterManager
+                safetyCenterManager,
+                deviceProvisionedController
         )
 
         backgroundExecutor.runAllReady()
@@ -199,6 +204,18 @@
         )
     }
 
+    @Test
+    fun testNoDialogWhenDeviceNotProvisioned() {
+        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
+        controller.onParentVisible()
+
+        val captor = argumentCaptor<View.OnClickListener>()
+        verify(privacyChip).setOnClickListener(capture(captor))
+
+        captor.value.onClick(privacyChip)
+        verify(privacyDialogController, never()).showDialog(any(Context::class.java))
+    }
+
     private fun setPrivacyController(micCamera: Boolean, location: Boolean) {
         whenever(privacyItemController.micCameraAvailable).thenReturn(micCamera)
         whenever(privacyItemController.locationAvailable).thenReturn(location)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index cd7a949..aedb935 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -19,6 +19,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -439,6 +440,34 @@
         verify(mQSPanelController).setExpanded(false);
     }
 
+    @Test
+    public void startsListeningAfterStateChangeToExpanded_inSplitShade() {
+        QSFragment fragment = resumeAndGetFragment();
+        enableSplitShade();
+        fragment.setQsVisible(true);
+        clearInvocations(mQSPanelController);
+
+        fragment.setExpanded(true);
+        verify(mQSPanelController).setListening(true, true);
+    }
+
+    @Test
+    public void testUpdateQSBounds_setMediaClipCorrectly() {
+        QSFragment fragment = resumeAndGetFragment();
+        disableSplitShade();
+
+        Rect mediaHostClip = new Rect();
+        when(mQSPanelController.getPaddingBottom()).thenReturn(50);
+        setLocationOnScreen(mQSPanelScrollView, 25);
+        when(mQSPanelScrollView.getMeasuredHeight()).thenReturn(200);
+        when(mQSMediaHost.getCurrentClipping()).thenReturn(mediaHostClip);
+
+        fragment.updateQsBounds();
+
+        assertEquals(25, mediaHostClip.top);
+        assertEquals(175, mediaHostClip.bottom);
+    }
+
     @Override
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         MockitoAnnotations.initMocks(this);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 3c867ab..9f28708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -64,7 +64,7 @@
         whenever(brightnessSliderFactory.create(any(), any())).thenReturn(brightnessSlider)
         whenever(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
         whenever(qsPanel.resources).thenReturn(mContext.orCreateTestableResources.resources)
-        whenever(statusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false)
+        whenever(statusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false)
         whenever(qsPanel.setListening(anyBoolean())).then {
             whenever(qsPanel.isListening).thenReturn(it.getArgument(0))
         }
@@ -116,9 +116,9 @@
 
     @Test
     fun testIsBouncerInTransit() {
-        whenever(statusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true)
+        whenever(statusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true)
         assertThat(controller.isBouncerInTransit()).isEqualTo(true)
-        whenever(statusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false)
+        whenever(statusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false)
         assertThat(controller.isBouncerInTransit()).isEqualTo(false)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 5e9c1aa..906c20b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -703,28 +703,32 @@
     public void testParentalControls() {
         // Make sure the security footer is visible, so that the images are updated.
         when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
-
         when(mSecurityController.isParentalControlsEnabled()).thenReturn(true);
 
+        // We use the default icon when there is no admin icon.
+        when(mSecurityController.getIcon(any())).thenReturn(null);
+        mFooter.refreshState();
+        TestableLooper.get(this).processAllMessages();
+        assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls),
+                mFooterText.getText());
+        assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+
         Drawable testDrawable = new VectorDrawable();
         when(mSecurityController.getIcon(any())).thenReturn(testDrawable);
         assertNotNull(mSecurityController.getIcon(null));
 
         mFooter.refreshState();
-
         TestableLooper.get(this).processAllMessages();
 
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls),
                 mFooterText.getText());
         assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
-
         assertEquals(testDrawable, mPrimaryFooterIcon.getDrawable());
 
         // Ensure the primary icon is back to default after parental controls are gone
         when(mSecurityController.isParentalControlsEnabled()).thenReturn(false);
         mFooter.refreshState();
         TestableLooper.get(this).processAllMessages();
-
         assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index c452872..fb1a720 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -54,6 +54,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.nano.SystemUIProtoDump;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -66,7 +67,6 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 5abc0e1..35c8cc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -27,6 +27,8 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
+import android.content.res.Resources;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -42,16 +44,22 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class TileLayoutTest extends SysuiTestCase {
-    private TileLayout mTileLayout;
+    private Resources mResources;
     private int mLayoutSizeForOneTile;
+    private TileLayout mTileLayout; // under test
 
     @Before
     public void setUp() throws Exception {
-        mTileLayout = new TileLayout(mContext);
+        Context context = Mockito.spy(mContext);
+        mResources = Mockito.spy(context.getResources());
+        Mockito.when(mContext.getResources()).thenReturn(mResources);
+
+        mTileLayout = new TileLayout(context);
         // Layout needs to leave space for the tile margins. Three times the margin size is
         // sufficient for any number of columns.
         mLayoutSizeForOneTile =
@@ -203,4 +211,21 @@
         verify(tileRecord1.tileView).setPosition(0);
         verify(tileRecord2.tileView).setPosition(1);
     }
+
+    @Test
+    public void resourcesChanged_updateResources_returnsTrue() {
+        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+        mTileLayout.updateResources(); // setup with 1
+        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2);
+
+        assertEquals(true, mTileLayout.updateResources());
+    }
+
+    @Test
+    public void resourcesSame_updateResources_returnsFalse() {
+        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+        mTileLayout.updateResources(); // setup with 1
+
+        assertEquals(false, mTileLayout.updateResources());
+    }
 }
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/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 213eca8..25c95ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -42,12 +42,12 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
index 2c2ddbb..23466cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
@@ -16,10 +16,8 @@
 
 package com.android.systemui.qs.footer.domain.interactor
 
-import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
-import android.os.UserHandle
 import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
@@ -30,23 +28,20 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.Expandable
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.QSSecurityFooterUtils
 import com.android.systemui.qs.footer.FooterActionsTestUtils
-import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.truth.correspondence.FakeUiEvent
 import com.android.systemui.truth.correspondence.LogMaker
-import com.android.systemui.user.UserSwitcherActivity
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.nullable
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestCoroutineScheduler
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -61,7 +56,7 @@
 
     @Before
     fun setUp() {
-        utils = FooterActionsTestUtils(context, TestableLooper.get(this))
+        utils = FooterActionsTestUtils(context, TestableLooper.get(this), TestCoroutineScheduler())
     }
 
     @Test
@@ -156,54 +151,4 @@
         // We only unlock the device.
         verify(activityStarter).postQSRunnableDismissingKeyguard(any())
     }
-
-    @Test
-    fun showUserSwitcher_fullScreenDisabled() {
-        val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
-        val userSwitchDialogController = mock<UserSwitchDialogController>()
-        val underTest =
-            utils.footerActionsInteractor(
-                featureFlags = featureFlags,
-                userSwitchDialogController = userSwitchDialogController,
-            )
-
-        val expandable = mock<Expandable>()
-        underTest.showUserSwitcher(context, expandable)
-
-        // Dialog is shown.
-        verify(userSwitchDialogController).showDialog(context, expandable)
-    }
-
-    @Test
-    fun showUserSwitcher_fullScreenEnabled() {
-        val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
-        val activityStarter = mock<ActivityStarter>()
-        val underTest =
-            utils.footerActionsInteractor(
-                featureFlags = featureFlags,
-                activityStarter = activityStarter,
-            )
-
-        // The clicked expandable.
-        val expandable = mock<Expandable>()
-        underTest.showUserSwitcher(context, expandable)
-
-        // Dialog is shown.
-        val intentCaptor = argumentCaptor<Intent>()
-        verify(activityStarter)
-            .startActivity(
-                intentCaptor.capture(),
-                /* dismissShade= */ eq(true),
-                /* ActivityLaunchAnimator.Controller= */ nullable(),
-                /* showOverLockscreenWhenLocked= */ eq(true),
-                eq(UserHandle.SYSTEM),
-            )
-        assertThat(intentCaptor.value.component)
-            .isEqualTo(
-                ComponentName(
-                    context,
-                    UserSwitcherActivity::class.java,
-                )
-            )
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 081a218..47afa70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.FakeFgsManagerController
 import com.android.systemui.qs.QSSecurityFooterUtils
 import com.android.systemui.qs.footer.FooterActionsTestUtils
@@ -44,12 +45,9 @@
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestCoroutineScheduler
 import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -62,16 +60,20 @@
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
 class FooterActionsViewModelTest : SysuiTestCase() {
+    private val testScope = TestScope()
     private lateinit var utils: FooterActionsTestUtils
-    private val testDispatcher = UnconfinedTestDispatcher(TestCoroutineScheduler())
 
     @Before
     fun setUp() {
-        utils = FooterActionsTestUtils(context, TestableLooper.get(this))
+        utils = FooterActionsTestUtils(context, TestableLooper.get(this), testScope.testScheduler)
+    }
+
+    private fun runTest(block: suspend TestScope.() -> Unit) {
+        testScope.runTest(testBody = block)
     }
 
     @Test
-    fun settingsButton() = runBlockingTest {
+    fun settingsButton() = runTest {
         val underTest = utils.footerActionsViewModel(showPowerButton = false)
         val settings = underTest.settings
 
@@ -87,7 +89,7 @@
     }
 
     @Test
-    fun powerButton() = runBlockingTest {
+    fun powerButton() = runTest {
         // Without power button.
         val underTestWithoutPower = utils.footerActionsViewModel(showPowerButton = false)
         assertThat(underTestWithoutPower.power).isNull()
@@ -114,7 +116,7 @@
     }
 
     @Test
-    fun userSwitcher() = runBlockingTest {
+    fun userSwitcher() = runTest {
         val picture: Drawable = mock()
         val userInfoController = FakeUserInfoController(FakeInfo(picture = picture))
         val settings = FakeSettings()
@@ -135,7 +137,6 @@
                 showPowerButton = false,
                 footerActionsInteractor =
                     utils.footerActionsInteractor(
-                        bgDispatcher = testDispatcher,
                         userSwitcherRepository =
                             utils.userSwitcherRepository(
                                 userTracker = userTracker,
@@ -143,22 +144,12 @@
                                 userManager = userManager,
                                 userInfoController = userInfoController,
                                 userSwitcherController = userSwitcherControllerWrapper.controller,
-                                bgDispatcher = testDispatcher,
                             ),
                     )
             )
 
         // Collect the user switcher into currentUserSwitcher.
-        var currentUserSwitcher: FooterActionsButtonViewModel? = null
-        val job = launch { underTest.userSwitcher.collect { currentUserSwitcher = it } }
-        fun currentUserSwitcher(): FooterActionsButtonViewModel? {
-            // Make sure we finish collecting the current user switcher. This is necessary because
-            // combined flows launch multiple coroutines in the current scope so we need to make
-            // sure we process all coroutines triggered by our flow collection before we make
-            // assertions on the current buttons.
-            advanceUntilIdle()
-            return currentUserSwitcher
-        }
+        val currentUserSwitcher = collectLastValue(underTest.userSwitcher)
 
         // The user switcher is disabled.
         assertThat(currentUserSwitcher()).isNull()
@@ -203,12 +194,10 @@
         // in guest mode.
         userInfoController.updateInfo { this.picture = mock<UserIconDrawable>() }
         assertThat(iconTint()).isNull()
-
-        job.cancel()
     }
 
     @Test
-    fun security() = runBlockingTest {
+    fun security() = runTest {
         val securityController = FakeSecurityController()
         val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
 
@@ -224,22 +213,15 @@
                 footerActionsInteractor =
                     utils.footerActionsInteractor(
                         qsSecurityFooterUtils = qsSecurityFooterUtils,
-                        bgDispatcher = testDispatcher,
                         securityRepository =
                             utils.securityRepository(
                                 securityController = securityController,
-                                bgDispatcher = testDispatcher,
                             ),
                     ),
             )
 
         // Collect the security model into currentSecurity.
-        var currentSecurity: FooterActionsSecurityButtonViewModel? = null
-        val job = launch { underTest.security.collect { currentSecurity = it } }
-        fun currentSecurity(): FooterActionsSecurityButtonViewModel? {
-            advanceUntilIdle()
-            return currentSecurity
-        }
+        val currentSecurity = collectLastValue(underTest.security)
 
         // By default, we always return a null SecurityButtonConfig.
         assertThat(currentSecurity()).isNull()
@@ -270,12 +252,10 @@
         security = currentSecurity()
         assertThat(security).isNotNull()
         assertThat(security!!.onClick).isNull()
-
-        job.cancel()
     }
 
     @Test
-    fun foregroundServices() = runBlockingTest {
+    fun foregroundServices() = runTest {
         val securityController = FakeSecurityController()
         val fgsManagerController =
             FakeFgsManagerController(
@@ -300,21 +280,14 @@
                         securityRepository =
                             utils.securityRepository(
                                 securityController,
-                                bgDispatcher = testDispatcher,
                             ),
                         foregroundServicesRepository =
                             utils.foregroundServicesRepository(fgsManagerController),
-                        bgDispatcher = testDispatcher,
                     ),
             )
 
         // Collect the security model into currentSecurity.
-        var currentForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null
-        val job = launch { underTest.foregroundServices.collect { currentForegroundServices = it } }
-        fun currentForegroundServices(): FooterActionsForegroundServicesButtonViewModel? {
-            advanceUntilIdle()
-            return currentForegroundServices
-        }
+        val currentForegroundServices = collectLastValue(underTest.foregroundServices)
 
         // We don't show the foreground services button if the number of running packages is not
         // > 1.
@@ -356,12 +329,10 @@
         }
         securityController.updateState {}
         assertThat(currentForegroundServices()?.displayText).isFalse()
-
-        job.cancel()
     }
 
     @Test
-    fun observeDeviceMonitoringDialogRequests() = runBlockingTest {
+    fun observeDeviceMonitoringDialogRequests() = runTest {
         val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
         val broadcastDispatcher = mock<BroadcastDispatcher>()
 
@@ -390,7 +361,6 @@
                     utils.footerActionsInteractor(
                         qsSecurityFooterUtils = qsSecurityFooterUtils,
                         broadcastDispatcher = broadcastDispatcher,
-                        bgDispatcher = testDispatcher,
                     ),
             )
 
@@ -415,7 +385,4 @@
         underTest.onVisibilityChangeRequested(visible = true)
         assertThat(underTest.isVisible.value).isTrue()
     }
-
-    private fun runBlockingTest(block: suspend TestScope.() -> Unit) =
-        runTest(testDispatcher) { block() }
 }
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..f55d262 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;
@@ -39,6 +40,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
 
 @RunWith(AndroidTestingRunner.class)
 @UiThreadTest
@@ -136,6 +139,22 @@
         assertEquals(mIconView.getColor(s1), mIconView.getColor(s2));
     }
 
+    @Test
+    public void testIconStartedAndStoppedWhenAllowAnimationsFalse() {
+        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);
+
+        InOrder inOrder = Mockito.inOrder(d);
+        inOrder.verify(d).start();
+        inOrder.verify(d).stop();
+    }
+
     private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
         return new Drawable.ConstantState() {
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index 73a0cbc..030c59f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.settings.GlobalSettings
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
@@ -64,6 +65,8 @@
     private lateinit var mConnectivityManager: Lazy<ConnectivityManager>
     @Mock
     private lateinit var mGlobalSettings: GlobalSettings
+    @Mock
+    private lateinit var mUserTracker: UserTracker
     private lateinit var mTestableLooper: TestableLooper
     private lateinit var mTile: AirplaneModeTile
 
@@ -87,7 +90,8 @@
             mQsLogger,
             mBroadcastDispatcher,
             mConnectivityManager,
-            mGlobalSettings)
+            mGlobalSettings,
+            mUserTracker)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index f7b9438e..e0b3125 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.SelectedItem
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
@@ -118,8 +119,9 @@
         `when`(qsHost.context).thenReturn(spiedContext)
         `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
         `when`(controlsComponent.isEnabled()).thenReturn(true)
-        `when`(controlsController.getPreferredStructure())
-                .thenReturn(StructureInfo(ComponentName("pkg", "cls"), "structure", listOf()))
+        `when`(controlsController.getPreferredSelection())
+                .thenReturn(SelectedItem.StructureItem(
+                        StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())))
         secureSettings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 1)
 
         setupControlsComponent()
@@ -226,12 +228,12 @@
                 capture(listingCallbackCaptor)
         )
         `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
-        `when`(controlsController.getPreferredStructure()).thenReturn(
-            StructureInfo(
+        `when`(controlsController.getPreferredSelection()).thenReturn(
+            SelectedItem.StructureItem(StructureInfo(
                 ComponentName("pkg", "cls"),
                 "structure",
                 listOf(ControlInfo("id", "title", "subtitle", 1))
-            )
+            ))
         )
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
@@ -247,8 +249,9 @@
                 capture(listingCallbackCaptor)
         )
         `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
-        `when`(controlsController.getPreferredStructure())
-                .thenReturn(StructureInfo(ComponentName("pkg", "cls"), "structure", listOf()))
+        `when`(controlsController.getPreferredSelection())
+                .thenReturn(SelectedItem.StructureItem(
+                        StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())))
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
         testableLooper.processAllMessages()
@@ -257,6 +260,22 @@
     }
 
     @Test
+    fun testStateActiveIfPreferredIsPanel() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+        `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
+        `when`(controlsController.getPreferredSelection())
+                .thenReturn(SelectedItem.PanelItem("appName", ComponentName("pkg", "cls")))
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_ACTIVE)
+    }
+
+    @Test
     fun testStateInactiveIfLocked() {
         verify(controlsListingController).observe(
             any(LifecycleOwner::class.java),
@@ -303,12 +322,12 @@
         )
         `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
         `when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
-        `when`(controlsController.getPreferredStructure()).thenReturn(
-            StructureInfo(
-                ComponentName("pkg", "cls"),
-                "structure",
-                listOf(ControlInfo("id", "title", "subtitle", 1))
-            )
+        `when`(controlsController.getPreferredSelection()).thenReturn(
+            SelectedItem.StructureItem(StructureInfo(
+                    ComponentName("pkg", "cls"),
+                    "structure",
+                    listOf(ControlInfo("id", "title", "subtitle", 1))
+            ))
         )
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
@@ -334,12 +353,12 @@
         `when`(controlsComponent.getVisibility())
             .thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
         `when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
-        `when`(controlsController.getPreferredStructure()).thenReturn(
-            StructureInfo(
+        `when`(controlsController.getPreferredSelection()).thenReturn(
+            SelectedItem.StructureItem(StructureInfo(
                 ComponentName("pkg", "cls"),
                 "structure",
                 listOf(ControlInfo("id", "title", "subtitle", 1))
-            )
+            ))
         )
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index d91baa5..80c39cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -23,6 +23,7 @@
 
 
 import android.os.Handler;
+import android.service.quicksettings.Tile;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -38,6 +39,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.statusbar.connectivity.AccessPointController;
+import com.android.systemui.statusbar.connectivity.IconState;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 
 import org.junit.Before;
@@ -113,4 +115,24 @@
             .isNotEqualTo(mContext.getString(R.string.quick_settings_networks_available));
         assertThat(mTile.getLastTileState()).isEqualTo(-1);
     }
+
+    @Test
+    public void setIsAirplaneMode_APM_enabled_wifi_disabled() {
+        IconState state = new IconState(true, 0, "");
+        mTile.mSignalCallback.setIsAirplaneMode(state);
+        mTestableLooper.processAllMessages();
+        assertThat(mTile.getState().state).isEqualTo(Tile.STATE_INACTIVE);
+        assertThat(mTile.getState().secondaryLabel)
+            .isEqualTo(mContext.getString(R.string.status_bar_airplane));
+    }
+
+    @Test
+    public void setIsAirplaneMode_APM_enabled_wifi_enabled() {
+        IconState state = new IconState(false, 0, "");
+        mTile.mSignalCallback.setIsAirplaneMode(state);
+        mTestableLooper.processAllMessages();
+        assertThat(mTile.getState().state).isEqualTo(Tile.STATE_ACTIVE);
+        assertThat(mTile.getState().secondaryLabel)
+            .isNotEqualTo(mContext.getString(R.string.status_bar_airplane));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index b652aee..a1be2f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -18,6 +18,7 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
 
 import static org.mockito.Mockito.when;
 
@@ -108,17 +109,20 @@
 
     @Test
     public void testQRCodeTileUnavailable() {
-        when(mController.isEnabledForQuickSettings()).thenReturn(false);
+        when(mController.isAbleToOpenCameraApp()).thenReturn(false);
         QSTile.State state = new QSTile.State();
         mTile.handleUpdateState(state, null);
         assertEquals(state.state, Tile.STATE_UNAVAILABLE);
+        assertEquals(state.secondaryLabel.toString(),
+                     mContext.getString(R.string.qr_code_scanner_updating_secondary_label));
     }
 
     @Test
     public void testQRCodeTileAvailable() {
-        when(mController.isEnabledForQuickSettings()).thenReturn(true);
+        when(mController.isAbleToOpenCameraApp()).thenReturn(true);
         QSTile.State state = new QSTile.State();
         mTile.handleUpdateState(state, null);
-        assertEquals(state.state, Tile.STATE_ACTIVE);
+        assertEquals(state.state, Tile.STATE_INACTIVE);
+        assertNull(state.secondaryLabel);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index 3131f60..08a90b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -42,27 +42,21 @@
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class UserDetailViewAdapterTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var mUserSwitcherController: UserSwitcherController
-    @Mock
-    private lateinit var mParent: ViewGroup
-    @Mock
-    private lateinit var mUserDetailItemView: UserDetailItemView
-    @Mock
-    private lateinit var mOtherView: View
-    @Mock
-    private lateinit var mInflatedUserDetailItemView: UserDetailItemView
-    @Mock
-    private lateinit var mLayoutInflater: LayoutInflater
+    @Mock private lateinit var mUserSwitcherController: UserSwitcherController
+    @Mock private lateinit var mParent: ViewGroup
+    @Mock private lateinit var mUserDetailItemView: UserDetailItemView
+    @Mock private lateinit var mOtherView: View
+    @Mock private lateinit var mInflatedUserDetailItemView: UserDetailItemView
+    @Mock private lateinit var mLayoutInflater: LayoutInflater
     private var falsingManagerFake: FalsingManagerFake = FalsingManagerFake()
     private lateinit var adapter: UserDetailView.Adapter
     private lateinit var uiEventLogger: UiEventLoggerFake
@@ -77,10 +71,13 @@
         `when`(mLayoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
             .thenReturn(mInflatedUserDetailItemView)
         `when`(mParent.context).thenReturn(mContext)
-        adapter = UserDetailView.Adapter(
-            mContext, mUserSwitcherController, uiEventLogger,
-            falsingManagerFake
-        )
+        adapter =
+            UserDetailView.Adapter(
+                mContext,
+                mUserSwitcherController,
+                uiEventLogger,
+                falsingManagerFake
+            )
         mPicture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 2ef7312..62404cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -31,6 +31,7 @@
 
 import android.animation.Animator;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
 import android.net.ConnectivityManager;
@@ -42,6 +43,7 @@
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -60,8 +62,8 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.UnreleasedFlag;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.connectivity.AccessPointController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -87,6 +89,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -169,8 +172,8 @@
     private WifiStateWorker mWifiStateWorker;
     @Mock
     private SignalStrength mSignalStrength;
-    @Mock
-    private FeatureFlags mFlags;
+
+    private FakeFeatureFlags mFlags = new FakeFeatureFlags();
 
     private TestableResources mTestableResources;
     private InternetDialogController mInternetDialogController;
@@ -221,6 +224,7 @@
         mInternetDialogController.onAccessPointsChanged(mAccessPoints);
         mInternetDialogController.mActivityStarter = mActivityStarter;
         mInternetDialogController.mWifiIconInjector = mWifiIconInjector;
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, false);
     }
 
     @After
@@ -410,7 +414,7 @@
 
     @Test
     public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
-        when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
         InternetDialogController spyController = spy(mInternetDialogController);
         fakeAirplaneModeEnabled(false);
         when(mWifiStateWorker.isWifiEnabled()).thenReturn(true);
@@ -767,7 +771,7 @@
 
     @Test
     public void getSignalStrengthIcon_differentSubId() {
-        when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
         InternetDialogController spyController = spy(mInternetDialogController);
         Drawable icons = spyController.getSignalStrengthIcon(SUB_ID, mContext, 1, 1, 0, false);
         Drawable icons2 = spyController.getSignalStrengthIcon(SUB_ID2, mContext, 1, 1, 0, false);
@@ -777,7 +781,7 @@
 
     @Test
     public void getActiveAutoSwitchNonDdsSubId() {
-        when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
         // active on non-DDS
         SubscriptionInfo info = mock(SubscriptionInfo.class);
         doReturn(SUB_ID2).when(info).getSubscriptionId();
@@ -813,16 +817,35 @@
 
     @Test
     public void getMobileNetworkSummary() {
-        when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
+        Resources res1 = mock(Resources.class);
+        doReturn("EDGE").when(res1).getString(anyInt());
+        Resources res2 = mock(Resources.class);
+        doReturn("LTE").when(res2).getString(anyInt());
+        when(SubscriptionManager.getResourcesForSubId(any(), eq(SUB_ID))).thenReturn(res1);
+        when(SubscriptionManager.getResourcesForSubId(any(), eq(SUB_ID2))).thenReturn(res2);
+
         InternetDialogController spyController = spy(mInternetDialogController);
+        Map<Integer, TelephonyDisplayInfo> mSubIdTelephonyDisplayInfoMap =
+                spyController.mSubIdTelephonyDisplayInfoMap;
+        TelephonyDisplayInfo info1 = new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_EDGE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+        TelephonyDisplayInfo info2 = new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+
+        mSubIdTelephonyDisplayInfoMap.put(SUB_ID, info1);
+        mSubIdTelephonyDisplayInfoMap.put(SUB_ID2, info2);
+
         doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
         doReturn(true).when(spyController).isMobileDataEnabled();
         doReturn(true).when(spyController).activeNetworkIsCellular();
         String dds = spyController.getMobileNetworkSummary(SUB_ID);
         String nonDds = spyController.getMobileNetworkSummary(SUB_ID2);
 
+        String ddsNetworkType = dds.split("/")[1];
+        String nonDdsNetworkType = nonDds.split("/")[1];
         assertThat(dds).contains(mContext.getString(R.string.mobile_data_poor_connection));
-        assertThat(dds).isNotEqualTo(nonDds);
+        assertThat(ddsNetworkType).isNotEqualTo(nonDdsNetworkType);
     }
 
     @Test
@@ -837,7 +860,7 @@
 
     @Test
     public void launchMobileNetworkSettings_validSubId() {
-        when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
         InternetDialogController spyController = spy(mInternetDialogController);
         doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
         spyController.launchMobileNetworkSettings(mDialogLaunchView);
@@ -848,7 +871,7 @@
 
     @Test
     public void launchMobileNetworkSettings_invalidSubId() {
-        when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
         InternetDialogController spyController = spy(mInternetDialogController);
         doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                 .when(spyController).getActiveAutoSwitchNonDdsSubId();
@@ -860,7 +883,7 @@
 
     @Test
     public void setAutoDataSwitchMobileDataPolicy() {
-        when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+        mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
         mInternetDialogController.setAutoDataSwitchMobileDataPolicy(SUB_ID, true);
 
         verify(mTelephonyManager).setMobileDataPolicyEnabled(eq(
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/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
new file mode 100644
index 0000000..ea0e454
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.reardisplay;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import android.hardware.devicestate.DeviceStateManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class RearDisplayDialogControllerTest extends SysuiTestCase {
+
+    @Mock
+    private CommandQueue mCommandQueue;
+
+    private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+
+
+    private static final int CLOSED_BASE_STATE = 0;
+    private static final int OPEN_BASE_STATE = 1;
+
+    @Test
+    public void testClosedDialogIsShown() {
+        RearDisplayDialogController controller = new RearDisplayDialogController(mContext,
+                mCommandQueue, mFakeExecutor);
+        controller.setDeviceStateManagerCallback(new TestDeviceStateManagerCallback());
+        controller.setFoldedStates(new int[]{0});
+        controller.setAnimationRepeatCount(0);
+
+        controller.showRearDisplayDialog(CLOSED_BASE_STATE);
+        assertTrue(controller.mRearDisplayEducationDialog.isShowing());
+        View deviceOpenedWarningTextView = controller.mRearDisplayEducationDialog.findViewById(
+                R.id.rear_display_warning_text_view);
+        assertNull(deviceOpenedWarningTextView);
+    }
+
+    @Test
+    public void testOpenDialogIsShown() {
+        RearDisplayDialogController controller = new RearDisplayDialogController(mContext,
+                mCommandQueue, mFakeExecutor);
+        controller.setDeviceStateManagerCallback(new TestDeviceStateManagerCallback());
+        controller.setFoldedStates(new int[]{0});
+        controller.setAnimationRepeatCount(0);
+
+        controller.showRearDisplayDialog(OPEN_BASE_STATE);
+
+        assertTrue(controller.mRearDisplayEducationDialog.isShowing());
+        View deviceOpenedWarningTextView = controller.mRearDisplayEducationDialog.findViewById(
+                R.id.rear_display_warning_text_view);
+        assertNotNull(deviceOpenedWarningTextView);
+    }
+
+    /**
+     * Empty device state manager callbacks, so we can verify that the correct
+     * dialogs are being created regardless of device state of the test device.
+     */
+    private static class TestDeviceStateManagerCallback implements
+            DeviceStateManager.DeviceStateCallback {
+
+        @Override
+        public void onStateChanged(int state) { }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
deleted file mode 100644
index 2d2f4cc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
+++ /dev/null
@@ -1,51 +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.ripple
-
-import android.testing.AndroidTestingRunner
-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
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class RippleViewTest : SysuiTestCase() {
-    @Mock
-    private lateinit var rippleView: RippleView
-
-    @Before
-    fun setup() {
-        rippleView = RippleView(context, null)
-    }
-
-    @Test
-    fun testSetupShader_compilesCircle() {
-        rippleView.setupShader(RippleShader.RippleShape.CIRCLE)
-    }
-
-    @Test
-    fun testSetupShader_compilesRoundedBox() {
-        rippleView.setupShader(RippleShader.RippleShape.ROUNDED_BOX)
-    }
-
-    @Test
-    fun testSetupShader_compilesEllipse() {
-        rippleView.setupShader(RippleShader.RippleShape.ELLIPSE)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 013e58e..69f3e987 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -33,6 +33,9 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -49,12 +52,16 @@
  */
 public class RecordingControllerTest extends SysuiTestCase {
 
+    private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
     @Mock
     private RecordingController.RecordingStateChangeCallback mCallback;
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
     private UserContextProvider mUserContextProvider;
+    @Mock
+    private UserTracker mUserTracker;
 
     private RecordingController mController;
 
@@ -63,7 +70,8 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mController = new RecordingController(mBroadcastDispatcher, mUserContextProvider);
+        mController = new RecordingController(mMainExecutor, mBroadcastDispatcher,
+                mUserContextProvider, mUserTracker);
         mController.addCallback(mCallback);
     }
 
@@ -176,9 +184,7 @@
         mController.updateState(true);
 
         // and user is changed
-        Intent intent = new Intent(Intent.ACTION_USER_SWITCHED)
-                .putExtra(Intent.EXTRA_USER_HANDLE, USER_ID);
-        mController.mUserChangeReceiver.onReceive(mContext, intent);
+        mController.mUserChangedCallback.onUserChanged(USER_ID, mContext);
 
         // Ensure that the recording was stopped
         verify(mCallback).onRecordingEnd();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
new file mode 100644
index 0000000..0aa3621
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -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 com.android.systemui.screenrecord
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.widget.Spinner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class ScreenRecordPermissionDialogTest : SysuiTestCase() {
+
+    @Mock private lateinit var starter: ActivityStarter
+    @Mock private lateinit var controller: RecordingController
+    @Mock private lateinit var userContextProvider: UserContextProvider
+    @Mock private lateinit var flags: FeatureFlags
+    @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+    @Mock private lateinit var onStartRecordingClicked: Runnable
+
+    private lateinit var dialog: ScreenRecordPermissionDialog
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        dialog =
+            ScreenRecordPermissionDialog(
+                context,
+                controller,
+                starter,
+                dialogLaunchAnimator,
+                userContextProvider,
+                onStartRecordingClicked
+            )
+        dialog.onCreate(null)
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+    }
+
+    @After
+    fun teardown() {
+        if (::dialog.isInitialized) {
+            dialog.dismiss()
+        }
+    }
+
+    @Test
+    fun testShowDialog_partialScreenSharingEnabled_optionsSpinnerIsVisible() {
+        dialog.show()
+
+        val visibility = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner).visibility
+        assertThat(visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    fun testShowDialog_singleAppSelected_showTapsIsGone() {
+        dialog.show()
+        onSpinnerItemSelected(SINGLE_APP)
+
+        val visibility = dialog.requireViewById<View>(R.id.show_taps).visibility
+        assertThat(visibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun testShowDialog_entireScreenSelected_showTapsIsVisible() {
+        dialog.show()
+        onSpinnerItemSelected(ENTIRE_SCREEN)
+
+        val visibility = dialog.requireViewById<View>(R.id.show_taps).visibility
+        assertThat(visibility).isEqualTo(View.VISIBLE)
+    }
+
+    private fun onSpinnerItemSelected(position: Int) {
+        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+        spinner.onItemSelectedListener.onItemSelected(spinner, mock(), position, /* id= */ 0)
+    }
+}
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..df3a62f 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,13 +70,21 @@
 @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);
 
     private static final ZonedDateTime CAPTURE_TIME =
-            ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 13, 15), ZoneId.of("EST"));
+            ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 13, 15), ZoneId.of("America/New_York"));
+
+    private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+    @Mock
+    private ContentResolver mMockContentResolver;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
 
     @Test
     public void testImageFilename() {
@@ -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/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 8c9404e..85c8ba7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -184,7 +184,7 @@
                         ActionTransition::new, mSmartActionsProvider);
 
         Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
-                Uri.parse("Screenshot_123.png")).get().action;
+                Uri.parse("Screenshot_123.png"), true).get().action;
 
         Intent intent = shareAction.actionIntent.getIntent();
         assertNotNull(intent);
@@ -212,7 +212,7 @@
                         ActionTransition::new, mSmartActionsProvider);
 
         Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
-                Uri.parse("Screenshot_123.png")).get().action;
+                Uri.parse("Screenshot_123.png"), true).get().action;
 
         Intent intent = editAction.actionIntent.getIntent();
         assertNotNull(intent);
@@ -241,7 +241,7 @@
 
         Notification.Action deleteAction = task.createDeleteAction(mContext,
                 mContext.getResources(),
-                Uri.parse("Screenshot_123.png"));
+                Uri.parse("Screenshot_123.png"), true);
 
         Intent intent = deleteAction.actionIntent.getIntent();
         assertNotNull(intent);
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/sensorprivacy/SensorUseStartedActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt
new file mode 100644
index 0000000..333e634
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt
@@ -0,0 +1,38 @@
+package com.android.systemui.sensorprivacy
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class SensorUseStartedActivityTest : SysuiTestCase() {
+    open class SensorUseStartedActivityTestable :
+        SensorUseStartedActivity(
+            sensorPrivacyController = mock(),
+            keyguardStateController = mock(),
+            keyguardDismissUtil = mock(),
+            bgHandler = mock(),
+        )
+
+    @get:Rule val activityRule = ActivityScenarioRule(SensorUseStartedActivityTestable::class.java)
+
+    @Test
+    fun onBackPressed_doNothing() {
+        activityRule.scenario.onActivity { activity ->
+            assertThat(activity.isFinishing).isFalse()
+
+            activity.onBackPressed()
+
+            assertThat(activity.isFinishing).isFalse()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java
deleted file mode 100644
index 1b515c6..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java
+++ /dev/null
@@ -1,74 +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.settings;
-
-import android.content.Intent;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Testing functionality of the current user tracker
- */
-@SmallTest
-public class CurrentUserTrackerTest extends SysuiTestCase {
-
-    private CurrentUserTracker mTracker;
-    private CurrentUserTracker.UserReceiver mReceiver;
-    @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mReceiver = new CurrentUserTracker.UserReceiver(mBroadcastDispatcher);
-        mTracker = new CurrentUserTracker(mReceiver) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                stopTracking();
-            }
-        };
-    }
-
-    @Test
-    public void testBroadCastDoesntCrashOnConcurrentModification() {
-        mTracker.startTracking();
-        CurrentUserTracker secondTracker = new CurrentUserTracker(mReceiver) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                stopTracking();
-            }
-        };
-        secondTracker.startTracking();
-        triggerUserSwitch();
-    }
-    /**
-     * Simulates a user switch event.
-     */
-    private void triggerUserSwitch() {
-        Intent intent = new Intent(Intent.ACTION_USER_SWITCHED);
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, 1);
-        mReceiver.onReceive(getContext(), intent);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
index 6d9b01e..020a866 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
@@ -50,24 +50,20 @@
 
     lateinit var userFileManager: UserFileManagerImpl
     lateinit var backgroundExecutor: FakeExecutor
-    @Mock
-    lateinit var userManager: UserManager
-    @Mock
-    lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock lateinit var userManager: UserManager
+    @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         backgroundExecutor = FakeExecutor(FakeSystemClock())
-        userFileManager = UserFileManagerImpl(context, userManager,
-            broadcastDispatcher, backgroundExecutor)
+        userFileManager =
+            UserFileManagerImpl(context, userManager, broadcastDispatcher, backgroundExecutor)
     }
 
     @After
     fun end() {
-        val dir = Environment.buildPath(
-            context.filesDir,
-            UserFileManagerImpl.ID)
+        val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID)
         dir.deleteRecursively()
     }
 
@@ -82,13 +78,14 @@
     @Test
     fun testGetSharedPreferences() {
         val secondarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11)
-        val secondaryUserDir = Environment.buildPath(
-            context.filesDir,
-            UserFileManagerImpl.ID,
-            "11",
-            UserFileManagerImpl.SHARED_PREFS,
-            TEST_FILE_NAME
-        )
+        val secondaryUserDir =
+            Environment.buildPath(
+                context.filesDir,
+                UserFileManagerImpl.ID,
+                "11",
+                UserFileManagerImpl.SHARED_PREFS,
+                TEST_FILE_NAME
+            )
 
         assertThat(secondarySharedPref).isNotNull()
         assertThat(secondaryUserDir.exists())
@@ -101,32 +98,35 @@
         val userFileManager = spy(userFileManager)
         userFileManager.start()
         verify(userFileManager).clearDeletedUserData()
-        verify(broadcastDispatcher).registerReceiver(any(BroadcastReceiver::class.java),
-            any(IntentFilter::class.java),
-            any(Executor::class.java), isNull(), eq(Context.RECEIVER_EXPORTED), isNull())
+        verify(broadcastDispatcher)
+            .registerReceiver(
+                any(BroadcastReceiver::class.java),
+                any(IntentFilter::class.java),
+                any(Executor::class.java),
+                isNull(),
+                eq(Context.RECEIVER_EXPORTED),
+                isNull()
+            )
     }
 
     @Test
     fun testClearDeletedUserData() {
-        val dir = Environment.buildPath(
-            context.filesDir,
-            UserFileManagerImpl.ID,
-            "11",
-            "files"
-        )
+        val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID, "11", "files")
         dir.mkdirs()
-        val file = Environment.buildPath(
-            context.filesDir,
-            UserFileManagerImpl.ID,
-            "11",
-            "files",
-            TEST_FILE_NAME
-        )
-        val secondaryUserDir = Environment.buildPath(
-            context.filesDir,
-            UserFileManagerImpl.ID,
-            "11",
-        )
+        val file =
+            Environment.buildPath(
+                context.filesDir,
+                UserFileManagerImpl.ID,
+                "11",
+                "files",
+                TEST_FILE_NAME
+            )
+        val secondaryUserDir =
+            Environment.buildPath(
+                context.filesDir,
+                UserFileManagerImpl.ID,
+                "11",
+            )
         file.createNewFile()
         assertThat(secondaryUserDir.exists()).isTrue()
         assertThat(file.exists()).isTrue()
@@ -139,15 +139,16 @@
 
     @Test
     fun testEnsureParentDirExists() {
-        val file = Environment.buildPath(
-            context.filesDir,
-            UserFileManagerImpl.ID,
-            "11",
-            "files",
-            TEST_FILE_NAME
-        )
+        val file =
+            Environment.buildPath(
+                context.filesDir,
+                UserFileManagerImpl.ID,
+                "11",
+                "files",
+                TEST_FILE_NAME
+            )
         assertThat(file.parentFile.exists()).isFalse()
-        userFileManager.ensureParentDirExists(file)
+        UserFileManagerImpl.ensureParentDirExists(file)
         assertThat(file.parentFile.exists()).isTrue()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index bd4b94e..52462c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -311,6 +311,37 @@
     }
 
     @Test
+    fun testCallbackCalledOnUserInfoChanged() {
+        tracker.initialize(0)
+        val callback = TestCallback()
+        tracker.addCallback(callback, executor)
+        val profileID = tracker.userId + 10
+
+        `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+            val id = invocation.getArgument<Int>(0)
+            val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+            val infoProfile = UserInfo(
+                id + 10,
+                "",
+                "",
+                UserInfo.FLAG_MANAGED_PROFILE,
+                UserManager.USER_TYPE_PROFILE_MANAGED
+            )
+            infoProfile.profileGroupId = id
+            listOf(info, infoProfile)
+        }
+
+        val intent = Intent(Intent.ACTION_USER_INFO_CHANGED)
+            .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+
+        tracker.onReceive(context, intent)
+
+        assertThat(callback.calledOnUserChanged).isEqualTo(0)
+        assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
+        assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID)
+    }
+
+    @Test
     fun testCallbackRemoved() {
         tracker.initialize(0)
         val newID = 5
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 1130bda..9d1802a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -28,9 +28,10 @@
 import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -45,7 +46,9 @@
 @TestableLooper.RunWithLooper
 class BrightnessDialogTest : SysuiTestCase() {
 
+    @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory
+    @Mock private lateinit var mainExecutor: Executor
     @Mock private lateinit var backgroundHandler: Handler
     @Mock private lateinit var brightnessSliderController: BrightnessSliderController
 
@@ -56,8 +59,9 @@
             object : SingleActivityFactory<TestDialog>(TestDialog::class.java) {
                 override fun create(intent: Intent?): TestDialog {
                     return TestDialog(
-                        fakeBroadcastDispatcher,
+                        userTracker,
                         brightnessSliderControllerFactory,
+                        mainExecutor,
                         backgroundHandler
                     )
                 }
@@ -100,8 +104,15 @@
     }
 
     class TestDialog(
-        broadcastDispatcher: BroadcastDispatcher,
+        userTracker: UserTracker,
         brightnessSliderControllerFactory: BrightnessSliderController.Factory,
+        mainExecutor: Executor,
         backgroundHandler: Handler
-    ) : BrightnessDialog(broadcastDispatcher, brightnessSliderControllerFactory, backgroundHandler)
+    ) :
+        BrightnessDialog(
+            userTracker,
+            brightnessSliderControllerFactory,
+            mainExecutor,
+            backgroundHandler
+        )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index 0ce9056..88651c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -23,8 +23,11 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Expect
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -36,13 +39,16 @@
     private lateinit var qsConstraint: ConstraintSet
     private lateinit var largeScreenConstraint: ConstraintSet
 
+    @get:Rule
+    val expect: Expect = Expect.create()
+
     @Before
     fun setUp() {
         qqsConstraint = ConstraintSet().apply {
             load(context, context.resources.getXml(R.xml.qqs_header))
         }
         qsConstraint = ConstraintSet().apply {
-            load(context, context.resources.getXml(R.xml.qs_header_new))
+            load(context, context.resources.getXml(R.xml.qs_header))
         }
         largeScreenConstraint = ConstraintSet().apply {
             load(context, context.resources.getXml(R.xml.large_screen_shade_header))
@@ -320,6 +326,70 @@
         assertThat(changes.largeScreenConstraintsChanges).isNull()
     }
 
+    @Test
+    fun testRelevantViewsAreNotMatchConstraints() {
+        val views = mapOf(
+                R.id.clock to "clock",
+                R.id.date to "date",
+                R.id.statusIcons to "icons",
+                R.id.privacy_container to "privacy",
+                R.id.carrier_group to "carriers",
+                R.id.batteryRemainingIcon to "battery",
+        )
+        views.forEach { (id, name) ->
+            assertWithMessage("$name has 0 height in qqs")
+                    .that(qqsConstraint.getConstraint(id).layout.mHeight).isNotEqualTo(0)
+            assertWithMessage("$name has 0 width in qqs")
+                    .that(qqsConstraint.getConstraint(id).layout.mWidth).isNotEqualTo(0)
+            assertWithMessage("$name has 0 height in qs")
+                    .that(qsConstraint.getConstraint(id).layout.mHeight).isNotEqualTo(0)
+            assertWithMessage("$name has 0 width in qs")
+                    .that(qsConstraint.getConstraint(id).layout.mWidth).isNotEqualTo(0)
+        }
+    }
+
+    @Test
+    fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() {
+        val views = mapOf(
+                R.id.clock to "clock",
+                R.id.date to "date",
+                R.id.statusIcons to "icons",
+                R.id.privacy_container to "privacy",
+                R.id.carrier_group to "carriers",
+                R.id.batteryRemainingIcon to "battery",
+        )
+        views.forEach { (id, name) ->
+            expect.withMessage("$name changes height")
+                    .that(qqsConstraint.getConstraint(id).layout.mHeight.fromConstraint())
+                    .isEqualTo(qsConstraint.getConstraint(id).layout.mHeight.fromConstraint())
+            expect.withMessage("$name changes width")
+                    .that(qqsConstraint.getConstraint(id).layout.mWidth.fromConstraint())
+                    .isEqualTo(qsConstraint.getConstraint(id).layout.mWidth.fromConstraint())
+        }
+    }
+
+    private fun Int.fromConstraint() = when (this) {
+        -1 -> "MATCH_PARENT"
+        -2 -> "WRAP_CONTENT"
+        else -> toString()
+    }
+
+    @Test
+    fun testEmptyCutoutDateIconsAreConstrainedWidth() {
+        CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()()
+
+        assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
+        assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+    }
+
+    @Test
+    fun testCenterCutoutDateIconsAreConstrainedWidth() {
+        CombinedShadeHeadersConstraintManagerImpl.centerCutoutConstraints(false, 10)()
+
+        assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
+        assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+    }
+
     private operator fun ConstraintsChanges.invoke() {
         qqsConstraintsChanges?.invoke(qqsConstraint)
         qsConstraintsChanges?.invoke(qsConstraint)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index 14a3bc1..1d30ad9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -35,6 +35,8 @@
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -44,16 +46,17 @@
 import com.android.systemui.qs.carrier.QSCarrierGroupController
 import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
 import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
-import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
 import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
 import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.statusbar.policy.VariableDateView
 import com.android.systemui.statusbar.policy.VariableDateViewController
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
@@ -73,6 +76,7 @@
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
@@ -104,7 +108,7 @@
     @Mock
     private lateinit var featureFlags: FeatureFlags
     @Mock
-    private lateinit var clock: TextView
+    private lateinit var clock: Clock
     @Mock
     private lateinit var date: VariableDateView
     @Mock
@@ -138,6 +142,7 @@
     private lateinit var qsConstraints: ConstraintSet
     @Mock
     private lateinit var largeScreenConstraints: ConstraintSet
+    @Mock private lateinit var demoModeController: DemoModeController
 
     @JvmField @Rule
     val mockitoRule = MockitoJUnit.rule()
@@ -146,10 +151,12 @@
     private lateinit var controller: LargeScreenShadeHeaderController
     private lateinit var carrierIconSlots: List<String>
     private val configurationController = FakeConfigurationController()
+    private lateinit var demoModeControllerCapture: ArgumentCaptor<DemoMode>
 
     @Before
     fun setUp() {
-        whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
+        demoModeControllerCapture = argumentCaptor<DemoMode>()
+        whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
         whenever(clock.context).thenReturn(mockedContext)
 
         whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
@@ -179,7 +186,6 @@
         whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
 
         whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
-        whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
 
         setUpDefaultInsets()
         setUpMotionLayout(view)
@@ -196,7 +202,8 @@
             dumpManager,
             featureFlags,
             qsCarrierGroupControllerBuilder,
-            combinedShadeHeadersConstraintManager
+            combinedShadeHeadersConstraintManager,
+            demoModeController
         )
         whenever(view.isAttachedToWindow).thenReturn(true)
         controller.init()
@@ -205,20 +212,6 @@
     }
 
     @Test
-    fun testCorrectConstraints() {
-        val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java)
-
-        verify(qqsConstraints).load(eq(context), capture(captor))
-        assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
-
-        verify(qsConstraints).load(eq(context), capture(captor))
-        assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header_new)
-
-        verify(largeScreenConstraints).load(eq(context), capture(captor))
-        assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
-    }
-
-    @Test
     fun testControllersCreatedAndInitialized() {
         verify(variableDateViewController).init()
 
@@ -280,16 +273,6 @@
     }
 
     @Test
-    fun testLargeScreenActive_true() {
-        controller.largeScreenActive = false // Make sure there's a change
-        clearInvocations(view)
-
-        controller.largeScreenActive = true
-
-        verify(view).setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
-    }
-
-    @Test
     fun testLargeScreenActive_false() {
         controller.largeScreenActive = true // Make sure there's a change
         clearInvocations(view)
@@ -618,6 +601,21 @@
     }
 
     @Test
+    fun demoMode_attachDemoMode() {
+        verify(demoModeController).addCallback(capture(demoModeControllerCapture))
+        demoModeControllerCapture.value.onDemoModeStarted()
+        verify(clock).onDemoModeStarted()
+    }
+
+    @Test
+    fun demoMode_detachDemoMode() {
+        controller.simulateViewDetached()
+        verify(demoModeController).removeCallback(capture(demoModeControllerCapture))
+        demoModeControllerCapture.value.onDemoModeFinished()
+        verify(clock).onDemoModeFinished()
+    }
+
+    @Test
     fun animateOutOnStartCustomizing() {
         val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
         val duration = 1000L
@@ -674,6 +672,25 @@
         verify(clock).pivotY = height.toFloat() / 2
     }
 
+    @Test
+    fun onDensityOrFontScaleChanged_reloadConstraints() {
+        // After density or font scale change, constraints need to be reloaded to reflect new
+        // dimensions.
+        reset(qqsConstraints)
+        reset(qsConstraints)
+        reset(largeScreenConstraints)
+
+        configurationController.notifyDensityOrFontScaleChanged()
+
+        val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java)
+        verify(qqsConstraints).load(eq(context), capture(captor))
+        assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
+        verify(qsConstraints).load(eq(context), capture(captor))
+        assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header)
+        verify(largeScreenConstraints).load(eq(context), capture(captor))
+        assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
+    }
+
     private fun View.executeLayoutChange(
             left: Int,
             top: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 90ae693..b4c8f98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -13,6 +13,8 @@
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -22,9 +24,12 @@
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.statusbar.policy.VariableDateViewController
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -52,7 +57,7 @@
     @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController
     @Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
     @Mock private lateinit var featureFlags: FeatureFlags
-    @Mock private lateinit var clock: TextView
+    @Mock private lateinit var clock: Clock
     @Mock private lateinit var date: TextView
     @Mock private lateinit var carrierGroup: QSCarrierGroup
     @Mock private lateinit var batteryMeterView: BatteryMeterView
@@ -66,6 +71,7 @@
         CombinedShadeHeadersConstraintManager
 
     @Mock private lateinit var mockedContext: Context
+    @Mock private lateinit var demoModeController: DemoModeController
 
     @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
     var viewVisibility = View.GONE
@@ -76,7 +82,7 @@
 
     @Before
     fun setup() {
-        whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
+        whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
         whenever(clock.context).thenReturn(mockedContext)
         whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
         whenever(date.context).thenReturn(mockedContext)
@@ -111,8 +117,9 @@
                 dumpManager,
                 featureFlags,
                 qsCarrierGroupControllerBuilder,
-                combinedShadeHeadersConstraintManager
-        )
+                combinedShadeHeadersConstraintManager,
+                demoModeController
+                )
         whenever(view.isAttachedToWindow).thenReturn(true)
         mLargeScreenShadeHeaderController.init()
         carrierIconSlots = listOf(
@@ -230,4 +237,21 @@
         verify(animator).setInterpolator(Interpolators.ALPHA_IN)
         verify(animator).start()
     }
+
+    @Test
+    fun demoMode_attachDemoMode() {
+        val cb = argumentCaptor<DemoMode>()
+        verify(demoModeController).addCallback(capture(cb))
+        cb.value.onDemoModeStarted()
+        verify(clock).onDemoModeStarted()
+    }
+
+    @Test
+    fun demoMode_detachDemoMode() {
+        mLargeScreenShadeHeaderController.simulateViewDetached()
+        val cb = argumentCaptor<DemoMode>()
+        verify(demoModeController).removeCallback(capture(cb))
+        cb.value.onDemoModeFinished()
+        verify(clock).onDemoModeFinished()
+    }
 }
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 ac4dd49..56a840c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -43,6 +43,7 @@
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 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;
@@ -94,7 +95,6 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.camera.CameraGestureHelper;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.doze.DozeLog;
@@ -129,12 +129,13 @@
 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;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
@@ -153,7 +154,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,9 +199,9 @@
     @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 NotificationGutsManager mGutsManager;
     @Mock private KeyguardStatusBarView mKeyguardStatusBar;
     @Mock private KeyguardUserSwitcherView mUserSwitcherView;
     @Mock private ViewStub mUserSwitcherStubView;
@@ -227,7 +227,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 +254,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 +293,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;
@@ -312,7 +311,7 @@
         MockitoAnnotations.initMocks(this);
         SystemClock systemClock = new FakeSystemClock();
         mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
-                mInteractionJankMonitor);
+                mInteractionJankMonitor, mShadeExpansionStateManager);
 
         KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
         keyguardStatusView.setId(R.id.keyguard_status_view);
@@ -383,10 +382,11 @@
                         mDumpManager,
                         mock(HeadsUpManagerPhone.class),
                         new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager,
-                                mInteractionJankMonitor),
+                                mInteractionJankMonitor, mShadeExpansionStateManager),
                         mKeyguardBypassController,
                         mDozeParameters,
-                        mScreenOffAnimationController);
+                        mScreenOffAnimationController,
+                        mock(NotificationWakeUpCoordinatorLogger.class));
         mConfigurationController = new ConfigurationControllerImpl(mContext);
         PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
                 mContext,
@@ -456,8 +456,9 @@
                 mShadeLog,
                 mConfigurationController,
                 () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
-                mConversationNotificationManager, mMediaHiearchyManager,
+                mConversationNotificationManager, mMediaHierarchyManager,
                 mStatusBarKeyguardViewManager,
+                mGutsManager,
                 mNotificationsQSContainerController,
                 mNotificationStackScrollLayoutController,
                 mKeyguardStatusViewComponentFactory,
@@ -465,7 +466,6 @@
                 mKeyguardUserSwitcherComponentFactory,
                 mKeyguardStatusBarViewComponentFactory,
                 mLockscreenShadeTransitionController,
-                mNotificationAreaController,
                 mAuthController,
                 mScrimController,
                 mUserManager,
@@ -474,7 +474,6 @@
                 mAmbientState,
                 mLockIconViewController,
                 mKeyguardMediaController,
-                mPrivacyDotViewController,
                 mTapAgainViewController,
                 mNavigationModeController,
                 mNavigationBarController,
@@ -492,21 +491,30 @@
                 mSysUiState,
                 () -> mKeyguardBottomAreaViewController,
                 mKeyguardUnlockAnimationController,
+                mKeyguardIndicationController,
                 mNotificationListContainer,
                 mNotificationStackSizeCalculator,
                 mUnlockedScreenOffAnimationController,
                 mShadeTransitionController,
                 systemClock,
-                mock(CameraGestureHelper.class),
                 mKeyguardBottomAreaViewModel,
-                mKeyguardBottomAreaInteractor);
+                mKeyguardBottomAreaInteractor,
+                mDumpManager);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
+                null,
                 () -> {},
                 mNotificationShelfController);
+        mNotificationPanelViewController.setTrackingStartedListener(() -> {});
+        mNotificationPanelViewController.setOpenCloseListener(
+                new NotificationPanelViewController.OpenCloseListener() {
+                    @Override
+                    public void onClosingFinished() {}
+
+                    @Override
+                    public void onOpenStarted() {}
+                });
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
-        mNotificationPanelViewController.setKeyguardIndicationController(
-                mKeyguardIndicationController);
         ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
         verify(mView, atLeast(1)).addOnAttachStateChangeListener(
@@ -516,13 +524,15 @@
         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)
                 .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture());
+        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
+        reset(mKeyguardStatusViewController);
     }
 
     @After
@@ -602,7 +612,7 @@
 
     @Test
     public void getVerticalSpaceForLockscreenNotifications_useLockIconBottomPadding_returnsSpaceAvailable() {
-        setBottomPadding(/* stackScrollLayoutBottom= */ 100,
+        setBottomPadding(/* stackScrollLayoutBottom= */ 180,
                 /* lockIconPadding= */ 20,
                 /* indicationPadding= */ 0,
                 /* ambientPadding= */ 0);
@@ -613,7 +623,7 @@
 
     @Test
     public void getVerticalSpaceForLockscreenNotifications_useIndicationBottomPadding_returnsSpaceAvailable() {
-        setBottomPadding(/* stackScrollLayoutBottom= */ 100,
+        setBottomPadding(/* stackScrollLayoutBottom= */ 180,
                 /* lockIconPadding= */ 0,
                 /* indicationPadding= */ 30,
                 /* ambientPadding= */ 0);
@@ -624,7 +634,7 @@
 
     @Test
     public void getVerticalSpaceForLockscreenNotifications_useAmbientBottomPadding_returnsSpaceAvailable() {
-        setBottomPadding(/* stackScrollLayoutBottom= */ 100,
+        setBottomPadding(/* stackScrollLayoutBottom= */ 180,
                 /* lockIconPadding= */ 0,
                 /* indicationPadding= */ 0,
                 /* ambientPadding= */ 40);
@@ -762,6 +772,8 @@
 
     @Test
     public void testOnTouchEvent_expansionResumesAfterBriefTouch() {
+        mFalsingManager.setIsClassifierEnabled(true);
+        mFalsingManager.setIsFalseTouch(false);
         // Start shade collapse with swipe up
         onTouchEvent(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
@@ -773,8 +785,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 */,
@@ -788,63 +800,14 @@
                 0 /* metaState */));
 
         // fling should still be called after a touch that does not exceed touch slop
-        assertThat(mNotificationPanelViewController.getClosing()).isTrue();
-        assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue();
-    }
-
-    @Test
-    public void handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
-        when(mCommandQueue.panelsEnabled()).thenReturn(false);
-
-        boolean returnVal = mNotificationPanelViewController
-                .getStatusBarTouchEventHandler()
-                .handleTouchEvent(
-                        MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
-
-        assertThat(returnVal).isFalse();
-        verify(mView, never()).dispatchTouchEvent(any());
-    }
-
-    @Test
-    public void handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() {
-        when(mCommandQueue.panelsEnabled()).thenReturn(true);
-        when(mView.isEnabled()).thenReturn(false);
-
-        boolean returnVal = mNotificationPanelViewController
-                .getStatusBarTouchEventHandler()
-                .handleTouchEvent(
-                        MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
-
-        assertThat(returnVal).isTrue();
-        verify(mView, never()).dispatchTouchEvent(any());
-    }
-
-    @Test
-    public void handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
-        when(mCommandQueue.panelsEnabled()).thenReturn(true);
-        when(mView.isEnabled()).thenReturn(false);
-        MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0);
-
-        mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
-
-        verify(mView).dispatchTouchEvent(event);
-    }
-
-    @Test
-    public void handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
-        when(mCommandQueue.panelsEnabled()).thenReturn(true);
-        when(mView.isEnabled()).thenReturn(true);
-        MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0);
-
-        mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
-
-        verify(mView).dispatchTouchEvent(event);
+        assertThat(mNotificationPanelViewController.isClosing()).isTrue();
+        assertThat(mNotificationPanelViewController.isFlinging()).isTrue();
     }
 
     @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(
@@ -856,22 +819,22 @@
 
     @Test
     public void testA11y_scrollForward() {
-        mAccessibiltyDelegate.performAccessibilityAction(
+        mAccessibilityDelegate.performAccessibilityAction(
                 mView,
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId(),
                 null);
 
-        verify(mStatusBarKeyguardViewManager).showBouncer(true);
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
     }
 
     @Test
     public void testA11y_scrollUp() {
-        mAccessibiltyDelegate.performAccessibilityAction(
+        mAccessibilityDelegate.performAccessibilityAction(
                 mView,
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId(),
                 null);
 
-        verify(mStatusBarKeyguardViewManager).showBouncer(true);
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
     }
 
     @Test
@@ -994,7 +957,7 @@
     }
 
     @Test
-    public void testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView() {
+    public void testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView_initClock() {
         givenViewAttached();
         when(mResources.getBoolean(
                 com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true);
@@ -1005,6 +968,7 @@
         mNotificationPanelViewController.onFinishInflate();
 
         verify(mUserSwitcherStubView, never()).inflate();
+        verify(mKeyguardStatusViewController, times(3)).displayClock(LARGE, /* animate */ true);
     }
 
     @Test
@@ -1127,6 +1091,19 @@
     }
 
     @Test
+    public void testUnlockedSplitShadeTransitioningToKeyguard_closesQS() {
+        enableSplitShade(true);
+        mStatusBarStateController.setState(SHADE);
+        mNotificationPanelViewController.setQsExpanded(true);
+
+        mStatusBarStateController.setState(KEYGUARD);
+
+
+        assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
+        assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
+    }
+
+    @Test
     public void testSwitchesToCorrectClockInSinglePaneShade() {
         mStatusBarStateController.setState(KEYGUARD);
 
@@ -1282,7 +1259,7 @@
         mNotificationPanelViewController.expandWithQs();
 
         verify(mLockscreenShadeTransitionController).goToLockedShade(
-                /* expandedView= */null, /* needsQSAnimation= */false);
+                /* expandedView= */null, /* needsQSAnimation= */true);
     }
 
     @Test
@@ -1329,11 +1306,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
@@ -1345,18 +1322,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
@@ -1399,7 +1376,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);
@@ -1419,7 +1396,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);
@@ -1495,7 +1472,7 @@
 
     @Test
     public void onLayoutChange_fullWidth_updatesQSWithFullWithTrue() {
-        mNotificationPanelViewController.mQs = mQs;
+        mNotificationPanelViewController.setQs(mQs);
 
         setIsFullWidth(true);
 
@@ -1504,7 +1481,7 @@
 
     @Test
     public void onLayoutChange_notFullWidth_updatesQSWithFullWithFalse() {
-        mNotificationPanelViewController.mQs = mQs;
+        mNotificationPanelViewController.setQs(mQs);
 
         setIsFullWidth(false);
 
@@ -1513,7 +1490,7 @@
 
     @Test
     public void onLayoutChange_qsNotSet_doesNotCrash() {
-        mNotificationPanelViewController.mQs = null;
+        mNotificationPanelViewController.setQs(null);
 
         triggerLayoutChange();
     }
@@ -1539,7 +1516,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())
@@ -1552,7 +1529,7 @@
                 /* delay= */ 0
         );
 
-        mNotificationPanelViewController.setQsExpansion(/* height= */ 123);
+        mNotificationPanelViewController.setQsExpansionHeight(/* height= */ 123);
 
         // First for setTransitionToFullShadeAmount and then setQsExpansion
         verify(mQs, times(2)).setQsExpansion(
@@ -1567,13 +1544,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(),
@@ -1586,7 +1563,7 @@
     @Test
     public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() {
         StatusBarStateController.StateListener statusBarStateListener =
-                mNotificationPanelViewController.mStatusBarStateListener;
+                mNotificationPanelViewController.getStatusBarStateListener();
         statusBarStateListener.onStateChanged(KEYGUARD);
         mNotificationPanelViewController.setDozing(false, false);
 
@@ -1601,7 +1578,7 @@
     @Test
     public void onEmptySpaceClicked_notDozingAndFaceDetectionIsNotRunning_startsUnlockAnimation() {
         StatusBarStateController.StateListener statusBarStateListener =
-                mNotificationPanelViewController.mStatusBarStateListener;
+                mNotificationPanelViewController.getStatusBarStateListener();
         statusBarStateListener.onStateChanged(KEYGUARD);
         mNotificationPanelViewController.setDozing(false, false);
         when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
@@ -1616,7 +1593,7 @@
     @Test
     public void onEmptySpaceClicked_notDozingAndFaceDetectionIsRunning_doesNotStartUnlockHint() {
         StatusBarStateController.StateListener statusBarStateListener =
-                mNotificationPanelViewController.mStatusBarStateListener;
+                mNotificationPanelViewController.getStatusBarStateListener();
         statusBarStateListener.onStateChanged(KEYGUARD);
         mNotificationPanelViewController.setDozing(false, false);
         when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
@@ -1631,7 +1608,7 @@
     @Test
     public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() {
         StatusBarStateController.StateListener statusBarStateListener =
-                mNotificationPanelViewController.mStatusBarStateListener;
+                mNotificationPanelViewController.getStatusBarStateListener();
         statusBarStateListener.onStateChanged(KEYGUARD);
         mNotificationPanelViewController.setDozing(true, false);
 
@@ -1645,7 +1622,7 @@
     @Test
     public void onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth() {
         StatusBarStateController.StateListener statusBarStateListener =
-                mNotificationPanelViewController.mStatusBarStateListener;
+                mNotificationPanelViewController.getStatusBarStateListener();
         statusBarStateListener.onStateChanged(SHADE_LOCKED);
 
         mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
@@ -1664,11 +1641,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
@@ -1680,6 +1657,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/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 95cf9d6..d7d17b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -239,9 +239,9 @@
 
     @Test
     public void setPanelExpanded_notFocusable_altFocusable_whenPanelIsOpen() {
-        mNotificationShadeWindowController.setPanelExpanded(true);
+        mNotificationShadeWindowController.onShadeExpansionFullyChanged(true);
         clearInvocations(mWindowManager);
-        mNotificationShadeWindowController.setPanelExpanded(true);
+        mNotificationShadeWindowController.onShadeExpansionFullyChanged(true);
         verifyNoMoreInteractions(mWindowManager);
         mNotificationShadeWindowController.setNotificationShadeFocusable(true);
 
@@ -313,7 +313,7 @@
         verifyNoMoreInteractions(mWindowManager);
 
         clearInvocations(mWindowManager);
-        mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+        mNotificationShadeWindowController.batchApplyWindowLayoutParams(() -> {
             mNotificationShadeWindowController.setForceDozeBrightness(false);
             verify(mWindowManager, never()).updateViewLayout(any(), any());
         });
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index db7e017..c3207c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.NotificationInsetsController
 import com.android.systemui.statusbar.NotificationShadeDepthController
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -94,6 +95,8 @@
     private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
     @Mock
     private lateinit var pulsingGestureListener: PulsingGestureListener
+    @Mock
+    private lateinit var notificationInsetsController: NotificationInsetsController
     @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
     @Mock lateinit var keyguardBouncerContainer: ViewGroup
     @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -124,6 +127,7 @@
             centralSurfaces,
             notificationShadeWindowController,
             keyguardUnlockAnimationController,
+            notificationInsetsController,
             ambientState,
             pulsingGestureListener,
             featureFlags,
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..4bf00c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationInsetsController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -91,6 +92,7 @@
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
     @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
+    @Mock private NotificationInsetsController mNotificationInsetsController;
 
     @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
             mInteractionEventHandlerCaptor;
@@ -125,6 +127,7 @@
                 mCentralSurfaces,
                 mNotificationShadeWindowController,
                 mKeyguardUnlockAnimationController,
+                mNotificationInsetsController,
                 mAmbientState,
                 mPulsingGestureListener,
                 mFeatureFlags,
@@ -152,7 +155,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.isShowingAlternateBouncer()).thenReturn(true);
         when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
 
         // THEN we should intercept touch
@@ -165,7 +168,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.isShowingAlternateBouncer()).thenReturn(false);
         when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
 
         // THEN we shouldn't intercept touch
@@ -178,7 +181,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.isShowingAlternateBouncer()).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/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 70cbc64..4d7741ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -28,11 +28,12 @@
 import com.android.systemui.plugins.ClockMetadata
 import com.android.systemui.plugins.ClockProviderPlugin
 import com.android.systemui.plugins.PluginListener
-import com.android.systemui.shared.plugins.PluginManager
+import com.android.systemui.plugins.PluginManager
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.fail
+import org.json.JSONException
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -238,4 +239,52 @@
         pluginListener.onPluginDisconnected(plugin2)
         assertEquals(1, changeCallCount)
     }
+
+    @Test
+    fun jsonDeserialization_gotExpectedObject() {
+        val expected = ClockRegistry.ClockSetting("ID", 500)
+        val actual = ClockRegistry.ClockSetting.deserialize("""{
+            "clockId":"ID",
+            "_applied_timestamp":500
+        }""")
+        assertEquals(expected, actual)
+    }
+
+    @Test
+    fun jsonDeserialization_noTimestamp_gotExpectedObject() {
+        val expected = ClockRegistry.ClockSetting("ID", null)
+        val actual = ClockRegistry.ClockSetting.deserialize("{\"clockId\":\"ID\"}")
+        assertEquals(expected, actual)
+    }
+
+    @Test
+    fun jsonDeserialization_nullTimestamp_gotExpectedObject() {
+        val expected = ClockRegistry.ClockSetting("ID", null)
+        val actual = ClockRegistry.ClockSetting.deserialize("""{
+            "clockId":"ID",
+            "_applied_timestamp":null
+        }""")
+        assertEquals(expected, actual)
+    }
+
+    @Test(expected = JSONException::class)
+    fun jsonDeserialization_noId_threwException() {
+        val expected = ClockRegistry.ClockSetting("ID", 500)
+        val actual = ClockRegistry.ClockSetting.deserialize("{\"_applied_timestamp\":500}")
+        assertEquals(expected, actual)
+    }
+
+    @Test
+    fun jsonSerialization_gotExpectedString() {
+        val expected = "{\"clockId\":\"ID\",\"_applied_timestamp\":500}"
+        val actual = ClockRegistry.ClockSetting.serialize( ClockRegistry.ClockSetting("ID", 500))
+        assertEquals(expected, actual)
+    }
+
+    @Test
+    fun jsonSerialization_noTimestamp_gotExpectedString() {
+        val expected = "{\"clockId\":\"ID\"}"
+        val actual = ClockRegistry.ClockSetting.serialize( ClockRegistry.ClockSetting("ID", null))
+        assertEquals(expected, actual)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 539a54b..a7588dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -43,6 +43,7 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.notNull
 import org.mockito.Mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
@@ -139,12 +140,19 @@
     }
 
     @Test
-    fun defaultClock_events_onFontSettingChanged() {
+    fun defaultSmallClock_events_onFontSettingChanged() {
         val clock = provider.createClock(DEFAULT_CLOCK_ID)
-        clock.events.onFontSettingChanged()
+        clock.smallClock.events.onFontSettingChanged(100f)
 
-        verify(mockSmallClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat())
-        verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat())
+        verify(mockSmallClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(100f))
+    }
+
+    @Test
+    fun defaultLargeClock_events_onFontSettingChanged() {
+        val clock = provider.createClock(DEFAULT_CLOCK_ID)
+        clock.largeClock.events.onFontSettingChanged(200f)
+
+        verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(200f))
         verify(mockLargeClockView).setLayoutParams(any())
     }
 
@@ -171,4 +179,12 @@
         verify(mockSmallClockView, times(2)).refreshFormat()
         verify(mockLargeClockView, times(2)).refreshFormat()
     }
+
+    @Test
+    fun test_aodClock_always_whiteColor() {
+        val clock = provider.createClock(DEFAULT_CLOCK_ID)
+        clock.animations.doze(0.9f) // set AOD mode to active
+        clock.smallClock.events.onRegionDarknessChanged(true)
+        verify((clock.smallClock.view as AnimatableClockView), never()).animateAppearOnLockscreen()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
new file mode 100644
index 0000000..5a62cc1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
@@ -0,0 +1,102 @@
+package com.android.systemui.shared.regionsampling
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.navigationbar.RegionSamplingHelper
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+import org.junit.Assert
+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.clearInvocations
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class RegionSamplerTest : SysuiTestCase() {
+
+    @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 pw: PrintWriter
+    @Mock private lateinit var callback: RegionSamplingHelper.SamplingCallback
+
+    private lateinit var mRegionSampler: RegionSampler
+    private var updateFun: UpdateColorCallback = {}
+
+    @Before
+    fun setUp() {
+        whenever(sampledView.isAttachedToWindow).thenReturn(true)
+        whenever(regionSampler.callback).thenReturn(this@RegionSamplerTest.callback)
+
+        mRegionSampler =
+            object : RegionSampler(sampledView, mainExecutor, bgExecutor, true, updateFun) {
+                override fun createRegionSamplingHelper(
+                    sampledView: View,
+                    callback: RegionSamplingHelper.SamplingCallback,
+                    mainExecutor: Executor?,
+                    bgExecutor: Executor?
+                ): RegionSamplingHelper {
+                    return this@RegionSamplerTest.regionSampler
+                }
+            }
+    }
+
+    @Test
+    fun testStartRegionSampler() {
+        mRegionSampler.startRegionSampler()
+
+        verify(regionSampler).start(Rect(0, 0, 0, 0))
+    }
+
+    @Test
+    fun testStopRegionSampler() {
+        mRegionSampler.stopRegionSampler()
+
+        verify(regionSampler).stop()
+    }
+
+    @Test
+    fun testDump() {
+        mRegionSampler.dump(pw)
+
+        verify(regionSampler).dump(pw)
+    }
+
+    @Test
+    fun testUpdateColorCallback() {
+        regionSampler.callback.onRegionDarknessChanged(false)
+        verify(regionSampler.callback).onRegionDarknessChanged(false)
+        clearInvocations(regionSampler.callback)
+        regionSampler.callback.onRegionDarknessChanged(true)
+        verify(regionSampler.callback).onRegionDarknessChanged(true)
+    }
+
+    @Test
+    fun testFlagFalse() {
+        mRegionSampler =
+            object : RegionSampler(sampledView, mainExecutor, bgExecutor, false, updateFun) {
+                override fun createRegionSamplingHelper(
+                    sampledView: View,
+                    callback: RegionSamplingHelper.SamplingCallback,
+                    mainExecutor: Executor?,
+                    bgExecutor: Executor?
+                ): RegionSamplingHelper {
+                    return this@RegionSamplerTest.regionSampler
+                }
+            }
+
+        Assert.assertEquals(mRegionSampler.regionSampler, null)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt
deleted file mode 100644
index 09d51f6..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt
+++ /dev/null
@@ -1,113 +0,0 @@
-package com.android.systemui.shared.regionsampling
-
-import android.graphics.Rect
-import android.testing.AndroidTestingRunner
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper
-import java.io.PrintWriter
-import java.util.concurrent.Executor
-import org.junit.Assert
-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.clearInvocations
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.junit.MockitoJUnit
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class RegionSamplingInstanceTest : SysuiTestCase() {
-
-    @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
-
-    @Before
-    fun setUp() {
-        whenever(sampledView.isAttachedToWindow).thenReturn(true)
-        whenever(regionSampler.callback).thenReturn(this@RegionSamplingInstanceTest.callback)
-
-        regionSamplingInstance = object : RegionSamplingInstance(
-                sampledView,
-                mainExecutor,
-                bgExecutor,
-                true,
-                updateFun
-        ) {
-            override fun createRegionSamplingHelper(
-                    sampledView: View,
-                    callback: RegionSamplingHelper.SamplingCallback,
-                    mainExecutor: Executor?,
-                    bgExecutor: Executor?
-            ): RegionSamplingHelper {
-                return this@RegionSamplingInstanceTest.regionSampler
-            }
-        }
-    }
-
-    @Test
-    fun testStartRegionSampler() {
-        regionSamplingInstance.startRegionSampler()
-
-        verify(regionSampler).start(Rect(0, 0, 0, 0))
-    }
-
-    @Test
-    fun testStopRegionSampler() {
-        regionSamplingInstance.stopRegionSampler()
-
-        verify(regionSampler).stop()
-    }
-
-    @Test
-    fun testDump() {
-        regionSamplingInstance.dump(pw)
-
-        verify(regionSampler).dump(pw)
-    }
-
-    @Test
-    fun testUpdateColorCallback() {
-        regionSampler.callback.onRegionDarknessChanged(false)
-        verify(regionSampler.callback).onRegionDarknessChanged(false)
-        clearInvocations(regionSampler.callback)
-        regionSampler.callback.onRegionDarknessChanged(true)
-        verify(regionSampler.callback).onRegionDarknessChanged(true)
-    }
-
-    @Test
-    fun testFlagFalse() {
-        regionSamplingInstance = object : RegionSamplingInstance(
-                sampledView,
-                mainExecutor,
-                bgExecutor,
-                false,
-                updateFun
-        ) {
-            override fun createRegionSamplingHelper(
-                    sampledView: View,
-                    callback: RegionSamplingHelper.SamplingCallback,
-                    mainExecutor: Executor?,
-                    bgExecutor: Executor?
-            ): RegionSamplingHelper {
-                return this@RegionSamplingInstanceTest.regionSampler
-            }
-        }
-
-        Assert.assertEquals(regionSamplingInstance.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..0000c32 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;
 
@@ -117,13 +118,6 @@
     }
 
     @Test
-    public void testCollapsePanels() {
-        mCommandQueue.animateCollapsePanels();
-        waitForIdleSync();
-        verify(mCallbacks).animateCollapsePanels(eq(0), eq(false));
-    }
-
-    @Test
     public void testExpandSettings() {
         String panel = "some_panel";
         mCommandQueue.animateExpandSettingsPanel(panel);
@@ -135,27 +129,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 +488,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
@@ -519,4 +516,12 @@
         waitForIdleSync();
         verify(mCallbacks).setNavigationBarLumaSamplingEnabled(eq(1), eq(true));
     }
+
+    @Test
+    public void testShowRearDisplayDialog() {
+        final int currentBaseState = 1;
+        mCommandQueue.showRearDisplayDialog(currentBaseState);
+        waitForIdleSync();
+        verify(mCallbacks).showRearDisplayDialog(eq(currentBaseState));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index ec5d089..c8a392b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -20,6 +20,7 @@
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
 import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
 
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
@@ -31,12 +32,12 @@
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_OFF;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
+import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_TURNING_ON;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -54,6 +55,7 @@
 import static org.mockito.Mockito.reset;
 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.app.Instrumentation;
@@ -85,9 +87,12 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TrustGrantFlags;
+import com.android.keyguard.logging.KeyguardLogger;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FaceHelpMessageDeferral;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dock.DockManager;
@@ -173,6 +178,8 @@
     private FaceHelpMessageDeferral mFaceHelpMessageDeferral;
     @Mock
     private ScreenLifecycle mScreenLifecycle;
+    @Mock
+    private AuthController mAuthController;
     @Captor
     private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
     @Captor
@@ -263,9 +270,10 @@
                 mWakeLockBuilder,
                 mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
                 mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
-                mUserManager, mExecutor, mExecutor,  mFalsingManager, mLockPatternUtils,
-                mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager,
-                mFaceHelpMessageDeferral);
+                mUserManager, mExecutor, mExecutor, mFalsingManager,
+                mAuthController, mLockPatternUtils, mScreenLifecycle,
+                mKeyguardBypassController, mAccessibilityManager,
+                mFaceHelpMessageDeferral, mock(KeyguardLogger.class));
         mController.init();
         mController.setIndicationArea(mIndicationArea);
         verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
@@ -822,31 +830,6 @@
     }
 
     @Test
-    public void updateMonitor_listenerUpdatesIndication() {
-        createController();
-        String restingIndication = "Resting indication";
-        reset(mKeyguardUpdateMonitor);
-
-        mController.setVisible(true);
-        verifyIndicationMessage(INDICATION_TYPE_USER_LOCKED,
-                mContext.getString(com.android.internal.R.string.lockscreen_storage_locked));
-
-        reset(mRotateTextViewController);
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
-        when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
-        mController.setRestingIndication(restingIndication);
-        verifyHideIndication(INDICATION_TYPE_USER_LOCKED);
-        verifyIndicationMessage(INDICATION_TYPE_RESTING, restingIndication);
-
-        reset(mRotateTextViewController);
-        reset(mKeyguardUpdateMonitor);
-        when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
-        mKeyguardStateControllerCallback.onUnlockedChanged();
-        verifyIndicationMessage(INDICATION_TYPE_RESTING, restingIndication);
-    }
-
-    @Test
     public void onRefreshBatteryInfo_computesChargingTime() throws RemoteException {
         createController();
         BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
@@ -1061,7 +1044,8 @@
 
         // GIVEN a trust granted message but trust isn't granted
         final String trustGrantedMsg = "testing trust granted message";
-        mController.getKeyguardCallback().showTrustGrantedMessage(trustGrantedMsg);
+        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+                false, new TrustGrantFlags(0), trustGrantedMsg);
 
         verifyHideIndication(INDICATION_TYPE_TRUST);
 
@@ -1085,7 +1069,8 @@
 
         // WHEN the showTrustGranted method is called
         final String trustGrantedMsg = "testing trust granted message";
-        mController.getKeyguardCallback().showTrustGrantedMessage(trustGrantedMsg);
+        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+                false, new TrustGrantFlags(0), trustGrantedMsg);
 
         // THEN verify the trust granted message shows
         verifyIndicationMessage(
@@ -1102,7 +1087,8 @@
         when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
 
         // WHEN the showTrustGranted method is called with a null message
-        mController.getKeyguardCallback().showTrustGrantedMessage(null);
+        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+                false, new TrustGrantFlags(0), null);
 
         // THEN verify the default trust granted message shows
         verifyIndicationMessage(
@@ -1119,7 +1105,8 @@
         when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
 
         // WHEN the showTrustGranted method is called with an EMPTY string
-        mController.getKeyguardCallback().showTrustGrantedMessage("");
+        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+                false, new TrustGrantFlags(0), "");
 
         // THEN verify NO trust message is shown
         verifyNoMessage(INDICATION_TYPE_TRUST);
@@ -1374,6 +1361,201 @@
     }
 
 
+    @Test
+    public void onBiometricError_faceLockedOutFirstTime_showsThePassedInMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "first lockout");
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() {
+        createController();
+        fingerprintUnlockIsPossible();
+        onFaceLockoutError("first lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_suggest_fingerprint));
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutFirstTimeAndFpNotAllowed_showsDefaultFollowup() {
+        createController();
+        fingerprintUnlockIsNotPossible();
+        onFaceLockoutError("first lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutSecondTimeInSession_showsUnavailableMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                mContext.getString(R.string.keyguard_face_unlock_unavailable));
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutSecondTimeOnBouncer_showsUnavailableMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+
+        onFaceLockoutError("second lockout");
+
+        verify(mStatusBarKeyguardViewManager)
+                .setKeyguardMessage(
+                        eq(mContext.getString(R.string.keyguard_face_unlock_unavailable)),
+                        any());
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutSecondTimeButUdfpsActive_showsNoMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+
+        when(mAuthController.isUdfpsFingerDown()).thenReturn(true);
+        onFaceLockoutError("second lockout");
+
+        verifyNoMoreInteractions(mRotateTextViewController);
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() {
+        createController();
+        fingerprintUnlockIsPossible();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_suggest_fingerprint));
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutAgainAndFpNotAllowed_showsDefaultFollowup() {
+        createController();
+        fingerprintUnlockIsNotPossible();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onBiometricError_whenFaceLockoutReset_onLockOutError_showsPassedInMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+        when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(false);
+        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "second lockout");
+    }
+
+    @Test
+    public void onFpLockoutStateChanged_whenFpIsLockedOut_showsPersistentMessage() {
+        createController();
+        mController.setVisible(true);
+        when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(true);
+
+        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+
+        verifyIndicationShown(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onFpLockoutStateChanged_whenFpIsNotLockedOut_showsPersistentMessage() {
+        createController();
+        mController.setVisible(true);
+        clearInvocations(mRotateTextViewController);
+        when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(false);
+
+        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+
+        verifyHideIndication(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE);
+    }
+
+    @Test
+    public void onVisibilityChange_showsPersistentMessage_ifFpIsLockedOut() {
+        createController();
+        mController.setVisible(false);
+        when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(true);
+        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+        clearInvocations(mRotateTextViewController);
+
+        mController.setVisible(true);
+
+        verifyIndicationShown(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onBiometricError_whenFaceIsLocked_onMultipleLockOutErrors_showUnavailableMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+        when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true);
+        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                mContext.getString(R.string.keyguard_face_unlock_unavailable));
+    }
+
+    @Test
+    public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsNotAvailable_showsMessage() {
+        createController();
+        screenIsTurningOn();
+        fingerprintUnlockIsNotPossible();
+
+        onFaceLockoutError("lockout error");
+        verifyNoMoreInteractions(mRotateTextViewController);
+
+        mScreenObserver.onScreenTurnedOn();
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                "lockout error");
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
+        createController();
+        screenIsTurningOn();
+        fingerprintUnlockIsPossible();
+
+        onFaceLockoutError("lockout error");
+        verifyNoMoreInteractions(mRotateTextViewController);
+
+        mScreenObserver.onScreenTurnedOn();
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                "lockout error");
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_suggest_fingerprint));
+    }
+
+    private void screenIsTurningOn() {
+        when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_TURNING_ON);
+    }
 
     private void sendUpdateDisclosureBroadcast() {
         mBroadcastReceiver.onReceive(mContext, new Intent());
@@ -1422,4 +1604,33 @@
                     anyObject(), anyBoolean());
         }
     }
+
+    private void verifyIndicationShown(int indicationType, String message) {
+        verify(mRotateTextViewController)
+                .updateIndication(eq(indicationType),
+                        mKeyguardIndicationCaptor.capture(),
+                        eq(true));
+        assertThat(mKeyguardIndicationCaptor.getValue().getMessage().toString())
+                .isEqualTo(message);
+    }
+
+    private void fingerprintUnlockIsNotPossible() {
+        setupFingerprintUnlockPossible(false);
+    }
+
+    private void fingerprintUnlockIsPossible() {
+        setupFingerprintUnlockPossible(true);
+    }
+
+    private void setupFingerprintUnlockPossible(boolean possible) {
+        when(mKeyguardUpdateMonitor
+                .getCachedIsUnlockWithFingerprintPossible(KeyguardUpdateMonitor.getCurrentUser()))
+                .thenReturn(possible);
+    }
+
+    private void onFaceLockoutError(String errMsg) {
+        mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_LOCKOUT_PERMANENT,
+                errMsg,
+                BiometricSourceType.FACE);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 5e11858..3412679 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -37,7 +37,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index bdafa48..452606d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar;
 
-import static android.content.Intent.ACTION_USER_SWITCHED;
-
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
@@ -34,7 +32,6 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.os.Handler;
@@ -53,6 +50,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -78,6 +76,8 @@
     private NotificationPresenter mPresenter;
     @Mock
     private UserManager mUserManager;
+    @Mock
+    private UserTracker mUserTracker;
 
     // Dependency mocks:
     @Mock
@@ -115,6 +115,7 @@
         MockitoAnnotations.initMocks(this);
 
         int currentUserId = ActivityManager.getCurrentUser();
+        when(mUserTracker.getUserId()).thenReturn(currentUserId);
         mSettings = new FakeSettings();
         mSettings.setUserId(ActivityManager.getCurrentUser());
         mCurrentUser = new UserInfo(currentUserId, "", 0);
@@ -289,11 +290,9 @@
     }
 
     @Test
-    public void testActionUserSwitchedCallsOnUserSwitched() {
-        Intent intent = new Intent()
-                .setAction(ACTION_USER_SWITCHED)
-                .putExtra(Intent.EXTRA_USER_HANDLE, mSecondaryUser.id);
-        mLockscreenUserManager.getBaseBroadcastReceiverForTest().onReceive(mContext, intent);
+    public void testUserSwitchedCallsOnUserSwitched() {
+        mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanged(mSecondaryUser.id,
+                mContext);
         verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
     }
 
@@ -344,6 +343,7 @@
                     mBroadcastDispatcher,
                     mDevicePolicyManager,
                     mUserManager,
+                    mUserTracker,
                     (() -> mVisibilityProvider),
                     (() -> mNotifCollection),
                     mClickNotifier,
@@ -361,6 +361,10 @@
             return mBaseBroadcastReceiver;
         }
 
+        public UserTracker.Callback getUserTrackerCallbackForTest() {
+            return mUserChangedCallback;
+        }
+
         public ContentObserver getLockscreenSettingsObserverForTest() {
             return mLockscreenSettingsObserver;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 77b1e37..beaf300 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -131,7 +131,9 @@
 
     @Test
     fun setupListeners() {
-        verify(dumpManager).registerDumpable(anyString(), eq(notificationShadeDepthController))
+        verify(dumpManager).registerCriticalDumpable(
+            anyString(), eq(notificationShadeDepthController)
+        )
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 1d8e5de..5124eb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -48,6 +49,7 @@
 
     @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
     @Mock private lateinit var mockDarkAnimator: ObjectAnimator
+    @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
 
     private lateinit var controller: StatusBarStateControllerImpl
     private lateinit var uiEventLogger: UiEventLoggerFake
@@ -62,7 +64,7 @@
         controller = object : StatusBarStateControllerImpl(
             uiEventLogger,
             mock(DumpManager::class.java),
-            interactionJankMonitor
+            interactionJankMonitor, shadeExpansionStateManager
         ) {
             override fun createDarkAnimator(): ObjectAnimator { return mockDarkAnimator }
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt
new file mode 100644
index 0000000..62b4e7b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.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.systemui.statusbar.connectivity
+
+import android.content.res.Resources
+import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
+
+typealias CarrierId = Int
+
+typealias NetworkType = String
+
+typealias ResId = Int
+
+class MobileIconCarrierIdOverridesFake : MobileIconCarrierIdOverrides {
+    /** Backing for [carrierIdEntryExists] */
+    var overriddenIds = mutableSetOf<Int>()
+
+    /** Backing for [getOverrideFor]. Map should be Map< CarrierId < NetworkType, ResId>> */
+    var overridesByCarrierId = mutableMapOf<CarrierId, Map<NetworkType, ResId>>()
+
+    override fun getOverrideFor(
+        carrierId: CarrierId,
+        networkType: NetworkType,
+        resources: Resources
+    ): ResId {
+        if (!overriddenIds.contains(carrierId)) return 0
+
+        return overridesByCarrierId[carrierId]?.get(networkType) ?: 0
+    }
+
+    override fun carrierIdEntryExists(carrierId: Int): Boolean {
+        return overriddenIds.contains(carrierId)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java
deleted file mode 100644
index 7ddfde3..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java
+++ /dev/null
@@ -1,137 +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.statusbar.connectivity;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-
-import com.android.settingslib.mobile.TelephonyIcons;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class MobileStateTest extends SysuiTestCase {
-
-    private final MobileState mState = new MobileState();
-
-    @Before
-    public void setUp() {
-    }
-
-    @Test
-    public void testIsDataDisabledOrNotDefault_dataDisabled() {
-        mState.iconGroup = TelephonyIcons.DATA_DISABLED;
-        mState.userSetup = true;
-
-        assertTrue(mState.isDataDisabledOrNotDefault());
-    }
-
-    @Test
-    public void testIsDataDisabledOrNotDefault_notDefaultData() {
-        mState.iconGroup = TelephonyIcons.NOT_DEFAULT_DATA;
-        mState.userSetup = true;
-
-        assertTrue(mState.isDataDisabledOrNotDefault());
-    }
-
-    @Test
-    public void testIsDataDisabledOrNotDefault_notDisabled() {
-        mState.iconGroup = TelephonyIcons.G;
-        mState.userSetup = true;
-
-        assertFalse(mState.isDataDisabledOrNotDefault());
-    }
-
-    @Test
-    public void testHasActivityIn_noData_noActivity() {
-        mState.dataConnected = false;
-        mState.carrierNetworkChangeMode = false;
-        mState.activityIn = false;
-
-        assertFalse(mState.hasActivityIn());
-    }
-
-    @Test
-    public void testHasActivityIn_noData_activityIn() {
-        mState.dataConnected = false;
-        mState.carrierNetworkChangeMode = false;
-        mState.activityIn = true;
-
-        assertFalse(mState.hasActivityIn());
-    }
-
-    @Test
-    public void testHasActivityIn_dataConnected_activityIn() {
-        mState.dataConnected = true;
-        mState.carrierNetworkChangeMode = false;
-        mState.activityIn = true;
-
-        assertTrue(mState.hasActivityIn());
-    }
-
-    @Test
-    public void testHasActivityIn_carrierNetworkChange() {
-        mState.dataConnected = true;
-        mState.carrierNetworkChangeMode = true;
-        mState.activityIn = true;
-
-        assertFalse(mState.hasActivityIn());
-    }
-
-    @Test
-    public void testHasActivityOut_noData_noActivity() {
-        mState.dataConnected = false;
-        mState.carrierNetworkChangeMode = false;
-        mState.activityOut = false;
-
-        assertFalse(mState.hasActivityOut());
-    }
-
-    @Test
-    public void testHasActivityOut_noData_activityOut() {
-        mState.dataConnected = false;
-        mState.carrierNetworkChangeMode = false;
-        mState.activityOut = true;
-
-        assertFalse(mState.hasActivityOut());
-    }
-
-    @Test
-    public void testHasActivityOut_dataConnected_activityOut() {
-        mState.dataConnected = true;
-        mState.carrierNetworkChangeMode = false;
-        mState.activityOut = true;
-
-        assertTrue(mState.hasActivityOut());
-    }
-
-    @Test
-    public void testHasActivityOut_carrierNetworkChange() {
-        mState.dataConnected = true;
-        mState.carrierNetworkChangeMode = true;
-        mState.activityOut = true;
-
-        assertFalse(mState.hasActivityOut());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
new file mode 100644
index 0000000..a226ded
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.connectivity
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MobileStateTest : SysuiTestCase() {
+
+    private val state = MobileState()
+    @Before fun setUp() {}
+
+    @Test
+    fun testIsDataDisabledOrNotDefault_dataDisabled() {
+        state.iconGroup = TelephonyIcons.DATA_DISABLED
+        state.userSetup = true
+        assertTrue(state.isDataDisabledOrNotDefault)
+    }
+
+    @Test
+    fun testIsDataDisabledOrNotDefault_notDefaultData() {
+        state.iconGroup = TelephonyIcons.NOT_DEFAULT_DATA
+        state.userSetup = true
+        assertTrue(state.isDataDisabledOrNotDefault)
+    }
+
+    @Test
+    fun testIsDataDisabledOrNotDefault_notDisabled() {
+        state.iconGroup = TelephonyIcons.G
+        state.userSetup = true
+        assertFalse(state.isDataDisabledOrNotDefault)
+    }
+
+    @Test
+    fun testHasActivityIn_noData_noActivity() {
+        state.dataConnected = false
+        state.carrierNetworkChangeMode = false
+        state.activityIn = false
+        assertFalse(state.hasActivityIn())
+    }
+
+    @Test
+    fun testHasActivityIn_noData_activityIn() {
+        state.dataConnected = false
+        state.carrierNetworkChangeMode = false
+        state.activityIn = true
+        assertFalse(state.hasActivityIn())
+    }
+
+    @Test
+    fun testHasActivityIn_dataConnected_activityIn() {
+        state.dataConnected = true
+        state.carrierNetworkChangeMode = false
+        state.activityIn = true
+        assertTrue(state.hasActivityIn())
+    }
+
+    @Test
+    fun testHasActivityIn_carrierNetworkChange() {
+        state.dataConnected = true
+        state.carrierNetworkChangeMode = true
+        state.activityIn = true
+        assertFalse(state.hasActivityIn())
+    }
+
+    @Test
+    fun testHasActivityOut_noData_noActivity() {
+        state.dataConnected = false
+        state.carrierNetworkChangeMode = false
+        state.activityOut = false
+        assertFalse(state.hasActivityOut())
+    }
+
+    @Test
+    fun testHasActivityOut_noData_activityOut() {
+        state.dataConnected = false
+        state.carrierNetworkChangeMode = false
+        state.activityOut = true
+        assertFalse(state.hasActivityOut())
+    }
+
+    @Test
+    fun testHasActivityOut_dataConnected_activityOut() {
+        state.dataConnected = true
+        state.carrierNetworkChangeMode = false
+        state.activityOut = true
+        assertTrue(state.hasActivityOut())
+    }
+
+    @Test
+    fun testHasActivityOut_carrierNetworkChange() {
+        state.dataConnected = true
+        state.carrierNetworkChangeMode = true
+        state.activityOut = true
+        assertFalse(state.hasActivityOut())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 9c65fac..5431eba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -71,6 +71,9 @@
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -115,6 +118,7 @@
     protected TelephonyManager mMockTm;
     protected TelephonyListenerManager mTelephonyListenerManager;
     protected BroadcastDispatcher mMockBd;
+    protected UserTracker mUserTracker;
     protected Config mConfig;
     protected CallbackHandler mCallbackHandler;
     protected SubscriptionDefaults mMockSubDefaults;
@@ -125,6 +129,8 @@
     protected CarrierConfigTracker mCarrierConfigTracker;
     protected FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     protected Handler mMainHandler;
+    // Use a real mobile mappings object since lots of tests rely on it
+    protected FakeMobileMappingsProxy mMobileMappingsProxy = new FakeMobileMappingsProxy();
     protected WifiStatusTrackerFactory mWifiStatusTrackerFactory;
     protected MobileSignalControllerFactory mMobileFactory;
 
@@ -169,6 +175,7 @@
         mMockSm = mock(SubscriptionManager.class);
         mMockCm = mock(ConnectivityManager.class);
         mMockBd = mock(BroadcastDispatcher.class);
+        mUserTracker = mock(UserTracker.class);
         mMockNsm = mock(NetworkScoreManager.class);
         mMockSubDefaults = mock(SubscriptionDefaults.class);
         mCarrierConfigTracker = mock(CarrierConfigTracker.class);
@@ -219,10 +226,13 @@
 
         mWifiStatusTrackerFactory = new WifiStatusTrackerFactory(
                 mContext, mMockWm, mMockNsm, mMockCm, mMainHandler);
+        // Most of these tests rely on the actual MobileMappings behavior
+        mMobileMappingsProxy.setUseRealImpl(true);
         mMobileFactory = new MobileSignalControllerFactory(
                 mContext,
                 mCallbackHandler,
-                mCarrierConfigTracker
+                mCarrierConfigTracker,
+                mMobileMappingsProxy
         );
 
         mNetworkController = new NetworkControllerImpl(mContext,
@@ -236,10 +246,12 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mMockProvisionController,
                 mMockBd,
+                mUserTracker,
                 mDemoModeController,
                 mCarrierConfigTracker,
                 mWifiStatusTrackerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index 4bed4a1..9441d49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -18,12 +18,21 @@
 
 import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
 import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+import static android.telephony.TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED;
+import static android.telephony.TelephonyManager.EXTRA_CARRIER_ID;
+import static android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID;
 
+import static com.android.settingslib.mobile.TelephonyIcons.NR_5G_PLUS;
+
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.content.Intent;
 import android.net.NetworkCapabilities;
 import android.os.Handler;
 import android.os.Looper;
@@ -35,16 +44,20 @@
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
+import com.android.settingslib.SignalIcon.MobileIconGroup;
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.CarrierConfigTracker;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashMap;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -138,10 +151,12 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
                 mMockBd,
+                mUserTracker,
                 mDemoModeController,
                 mock(CarrierConfigTracker.class),
                 mWifiStatusTrackerFactory,
@@ -329,6 +344,57 @@
         assertFalse(mNetworkController.isMobileDataNetworkInService());
     }
 
+    @Test
+    public void mobileSignalController_getsCarrierId() {
+        when(mMockTm.getSimCarrierId()).thenReturn(1);
+        setupDefaultSignal();
+
+        assertEquals(1, mMobileSignalController.getState().getCarrierId());
+    }
+
+    @Test
+    public void mobileSignalController_updatesCarrierId_onChange() {
+        when(mMockTm.getSimCarrierId()).thenReturn(1);
+        setupDefaultSignal();
+
+        // Updates are sent down through this broadcast, we can send the intent directly
+        Intent intent = new Intent(ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED);
+        intent.putExtra(EXTRA_SUBSCRIPTION_ID, mSubId);
+        intent.putExtra(EXTRA_CARRIER_ID, 2);
+
+        mMobileSignalController.handleBroadcast(intent);
+
+        assertEquals(2, mMobileSignalController.getState().getCarrierId());
+    }
+
+    @Test
+    public void networkTypeIcon_hasCarrierIdOverride() {
+        int fakeCarrier = 1;
+        int fakeIconOverride = 12345;
+        int testDataNetType = 100;
+        String testDataString = "100";
+        HashMap<String, MobileIconGroup> testMap = new HashMap<>();
+        testMap.put(testDataString, NR_5G_PLUS);
+
+        // Pretend that there is an override for this icon, and this carrier ID
+        NetworkTypeResIdCache mockCache = mock(NetworkTypeResIdCache.class);
+        when(mockCache.get(eq(NR_5G_PLUS), eq(fakeCarrier), any())).thenReturn(fakeIconOverride);
+
+        // Turn off the default mobile mapping, so we can override
+        mMobileMappingsProxy.setUseRealImpl(false);
+        mMobileMappingsProxy.setIconMap(testMap);
+        // Use the mocked cache
+        mMobileSignalController.mCurrentState.setNetworkTypeResIdCache(mockCache);
+        // Rebuild the network map
+        mMobileSignalController.setConfiguration(mConfig);
+        when(mMockTm.getSimCarrierId()).thenReturn(fakeCarrier);
+
+        setupDefaultSignal();
+        updateDataConnectionState(TelephonyManager.DATA_CONNECTED, testDataNetType);
+
+        verifyDataIndicators(fakeIconOverride);
+    }
+
     private void testDataActivity(int direction, boolean in, boolean out) {
         updateDataActivity(direction);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index d5f5105..4c1f0a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -44,6 +44,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.CarrierConfigTracker;
 
@@ -78,10 +79,12 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mMockProvisionController,
                 mMockBd,
+                mUserTracker,
                 mDemoModeController,
                 mCarrierConfigTracker,
                 mWifiStatusTrackerFactory,
@@ -114,10 +117,12 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mMockProvisionController,
                 mMockBd,
+                mUserTracker,
                 mDemoModeController,
                 mCarrierConfigTracker,
                 mWifiStatusTrackerFactory,
@@ -148,10 +153,12 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
                 mMockBd,
+                mUserTracker,
                 mDemoModeController,
                 mock(CarrierConfigTracker.class),
                 mWifiStatusTrackerFactory,
@@ -185,10 +192,12 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
                 mMockBd,
+                mUserTracker,
                 mDemoModeController,
                 mock(CarrierConfigTracker.class),
                 mWifiStatusTrackerFactory,
@@ -270,10 +279,12 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
                 mMockBd,
+                mUserTracker,
                 mDemoModeController,
                 mock(CarrierConfigTracker.class),
                 mWifiStatusTrackerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
new file mode 100644
index 0000000..9e73487
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.connectivity
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.systemui.SysuiTestCase
+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 NetworkTypeResIdCacheTest : SysuiTestCase() {
+    private lateinit var cache: NetworkTypeResIdCache
+    private var overrides = MobileIconCarrierIdOverridesFake()
+
+    @Before
+    fun setUp() {
+        cache = NetworkTypeResIdCache(overrides)
+    }
+
+    @Test
+    fun carrier1_noOverride_usesDefault() {
+        assertThat(cache.get(group1, CARRIER_1, context)).isEqualTo(iconDefault1)
+    }
+
+    @Test
+    fun carrier1_overridden_usesOverride() {
+        overrides.overriddenIds.add(CARRIER_1)
+        overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1)
+
+        assertThat(cache.get(group1, CARRIER_1, context)).isEqualTo(iconOverride1)
+    }
+
+    @Test
+    fun carrier1_override_carrier2UsesDefault() {
+        overrides.overriddenIds.add(CARRIER_1)
+        overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1)
+
+        assertThat(cache.get(group1, CARRIER_2, context)).isEqualTo(iconDefault1)
+    }
+
+    @Test
+    fun carrier1_overrideType1_type2UsesDefault() {
+        overrides.overriddenIds.add(CARRIER_1)
+        overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1)
+
+        assertThat(cache.get(group2, CARRIER_1, context)).isEqualTo(iconDefault2)
+    }
+
+    companion object {
+        // Simplified icon overrides here
+        const val CARRIER_1 = 1
+        const val CARRIER_2 = 2
+
+        const val NET_TYPE_1 = "one"
+        const val iconDefault1 = 123
+        const val iconOverride1 = 321
+        val group1 = MobileIconGroup(NET_TYPE_1, /* dataContentDesc */ 0, iconDefault1)
+
+        const val NET_TYPE_2 = "two"
+        const val iconDefault2 = 234
+
+        val group2 = MobileIconGroup(NET_TYPE_2, /* dataContentDesc*/ 0, iconDefault2)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
new file mode 100644
index 0000000..89faa239
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
@@ -0,0 +1,164 @@
+package com.android.systemui.statusbar.notification
+
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(JUnit4::class)
+class RoundableTest : SysuiTestCase() {
+    val targetView: View = mock()
+    val roundable = FakeRoundable(targetView)
+
+    @Test
+    fun defaultConfig_shouldNotHaveRoundedCorner() {
+        // the expected default value for the roundness is top = 0f, bottom = 0f
+        assertEquals(0f, roundable.roundableState.topRoundness)
+        assertEquals(0f, roundable.roundableState.bottomRoundness)
+        assertEquals(false, roundable.hasRoundedCorner())
+    }
+
+    @Test
+    fun applyRoundnessAndInvalidate_should_invalidate_targetView() {
+        roundable.applyRoundnessAndInvalidate()
+
+        verify(targetView, times(1)).invalidate()
+    }
+
+    @Test
+    fun requestTopRoundness_update_and_invalidate_targetView() {
+        roundable.requestTopRoundness(value = 1f, sourceType = SOURCE1)
+
+        assertEquals(1f, roundable.roundableState.topRoundness)
+        verify(targetView, times(1)).invalidate()
+    }
+
+    @Test
+    fun requestBottomRoundness_update_and_invalidate_targetView() {
+        roundable.requestBottomRoundness(value = 1f, sourceType = SOURCE1)
+
+        assertEquals(1f, roundable.roundableState.bottomRoundness)
+        verify(targetView, times(1)).invalidate()
+    }
+
+    @Test
+    fun requestRoundness_update_and_invalidate_targetView() {
+        roundable.requestRoundness(top = 1f, bottom = 1f, sourceType = SOURCE1)
+
+        assertEquals(1f, roundable.roundableState.topRoundness)
+        assertEquals(1f, roundable.roundableState.bottomRoundness)
+        verify(targetView, atLeastOnce()).invalidate()
+    }
+
+    @Test
+    fun requestRoundnessReset_update_and_invalidate_targetView() {
+        roundable.requestRoundness(1f, 1f, SOURCE1)
+        assertEquals(1f, roundable.roundableState.topRoundness)
+        assertEquals(1f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundnessReset(sourceType = SOURCE1)
+
+        assertEquals(0f, roundable.roundableState.topRoundness)
+        assertEquals(0f, roundable.roundableState.bottomRoundness)
+        verify(targetView, atLeastOnce()).invalidate()
+    }
+
+    @Test
+    fun hasRoundedCorner_return_true_ifRoundnessIsGreaterThenZero() {
+        roundable.requestRoundness(top = 1f, bottom = 1f, sourceType = SOURCE1)
+        assertEquals(true, roundable.hasRoundedCorner())
+
+        roundable.requestRoundness(top = 1f, bottom = 0f, sourceType = SOURCE1)
+        assertEquals(true, roundable.hasRoundedCorner())
+
+        roundable.requestRoundness(top = 0f, bottom = 1f, sourceType = SOURCE1)
+        assertEquals(true, roundable.hasRoundedCorner())
+
+        roundable.requestRoundness(top = 0f, bottom = 0f, sourceType = SOURCE1)
+        assertEquals(false, roundable.hasRoundedCorner())
+    }
+
+    @Test
+    fun roundness_take_maxValue_onMultipleSources_first_lower() {
+        roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+        assertEquals(0.1f, roundable.roundableState.topRoundness)
+        assertEquals(0.1f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+        // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+        assertEquals(0.2f, roundable.roundableState.topRoundness)
+        assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+    }
+
+    @Test
+    fun roundness_take_maxValue_onMultipleSources_first_higher() {
+        roundable.requestRoundness(0.5f, 0.5f, SOURCE1)
+        assertEquals(0.5f, roundable.roundableState.topRoundness)
+        assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.1f, 0.1f, SOURCE2)
+        // SOURCE1 has 0.5f - SOURCE2 has 0.1f
+        assertEquals(0.5f, roundable.roundableState.topRoundness)
+        assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+    }
+
+    @Test
+    fun roundness_take_maxValue_onMultipleSources_first_higher_second_step() {
+        roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+        assertEquals(0.1f, roundable.roundableState.topRoundness)
+        assertEquals(0.1f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+        // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+        assertEquals(0.2f, roundable.roundableState.topRoundness)
+        assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.3f, 0.3f, SOURCE1)
+        // SOURCE1 has 0.3f - SOURCE2 has 0.2f
+        assertEquals(0.3f, roundable.roundableState.topRoundness)
+        assertEquals(0.3f, roundable.roundableState.bottomRoundness)
+    }
+
+    @Test
+    fun roundness_take_maxValue_onMultipleSources_first_lower_second_step() {
+        roundable.requestRoundness(0.5f, 0.5f, SOURCE1)
+        assertEquals(0.5f, roundable.roundableState.topRoundness)
+        assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+        // SOURCE1 has 0.5f - SOURCE2 has 0.2f
+        assertEquals(0.5f, roundable.roundableState.topRoundness)
+        assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+        // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+        assertEquals(0.2f, roundable.roundableState.topRoundness)
+        assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+    }
+
+    class FakeRoundable(
+        targetView: View,
+        radius: Float = MAX_RADIUS,
+    ) : Roundable {
+        override val roundableState =
+            RoundableState(
+                targetView = targetView,
+                roundable = this,
+                maxRadius = radius,
+            )
+    }
+
+    companion object {
+        private const val MAX_RADIUS = 10f
+        private val SOURCE1 = SourceType.from("Source1")
+        private val SOURCE2 = SourceType.from("Source2")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 3b05321..94e3e6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -92,6 +92,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -741,22 +742,24 @@
     @Test
     public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() {
         // GIVEN a collection with two grouped notifs in it
-        CollectionEvent notif0 = postNotif(
+        CollectionEvent groupNotif = postNotif(
                 buildNotif(TEST_PACKAGE, 0)
                         .setGroup(mContext, GROUP_1)
                         .setGroupSummary(mContext, true));
-        CollectionEvent notif1 = postNotif(
+        CollectionEvent childNotif = postNotif(
                 buildNotif(TEST_PACKAGE, 1)
                         .setGroup(mContext, GROUP_1));
-        NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
-        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry groupEntry = mCollectionListener.getEntry(groupNotif.key);
+        NotificationEntry childEntry = mCollectionListener.getEntry(childNotif.key);
+        ExpandableNotificationRow childRow = mock(ExpandableNotificationRow.class);
+        childEntry.setRow(childRow);
 
         // WHEN the summary is dismissed
-        mCollection.dismissNotification(entry0, defaultStats(entry0));
+        mCollection.dismissNotification(groupEntry, defaultStats(groupEntry));
 
         // THEN all members of the group are marked as dismissed locally
-        assertEquals(DISMISSED, entry0.getDismissState());
-        assertEquals(PARENT_DISMISSED, entry1.getDismissState());
+        assertEquals(DISMISSED, groupEntry.getDismissState());
+        assertEquals(PARENT_DISMISSED, childEntry.getDismissState());
     }
 
     @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/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 7e2e6f6..7f73856 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -13,57 +13,57 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProvider
+import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.withArgCaptor
-import java.util.function.Consumer
-import org.junit.Before
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.verify
+import java.util.function.Consumer
+import kotlin.time.Duration.Companion.seconds
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class KeyguardCoordinatorTest : SysuiTestCase() {
-    private val notifPipeline: NotifPipeline = mock()
+
     private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val notifPipelineFlags: NotifPipelineFlags = mock()
+    private val notifPipeline: NotifPipeline = mock()
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
     private val statusBarStateController: StatusBarStateController = mock()
 
-    private lateinit var onStateChangeListener: Consumer<String>
-    private lateinit var keyguardFilter: NotifFilter
-
-    @Before
-    fun setup() {
-        val keyguardCoordinator = KeyguardCoordinator(
-            keyguardNotifVisibilityProvider,
-            sectionHeaderVisibilityProvider,
-            statusBarStateController
-        )
-        keyguardCoordinator.attach(notifPipeline)
-        onStateChangeListener = withArgCaptor {
-            verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
-        }
-        keyguardFilter = withArgCaptor {
-            verify(notifPipeline).addFinalizeFilter(capture())
-        }
-    }
-
     @Test
-    fun testSetSectionHeadersVisibleInShade() {
+    fun testSetSectionHeadersVisibleInShade() = runKeyguardCoordinatorTest {
         clearInvocations(sectionHeaderVisibilityProvider)
         whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
         onStateChangeListener.accept("state change")
@@ -71,10 +71,204 @@
     }
 
     @Test
-    fun testSetSectionHeadersNotVisibleOnKeyguard() {
+    fun testSetSectionHeadersNotVisibleOnKeyguard() = runKeyguardCoordinatorTest {
         clearInvocations(sectionHeaderVisibilityProvider)
         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
         onStateChangeListener.accept("state change")
         verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(false)
     }
+
+    @Test
+    fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() {
+        whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+        // GIVEN: Keyguard is not showing, and a notification is present
+        keyguardRepository.setKeyguardShowing(false)
+        runKeyguardCoordinatorTest {
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: The keyguard is now showing
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is recognized as "seen" and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+            // WHEN: The keyguard goes away
+            keyguardRepository.setKeyguardShowing(false)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is shown regardless
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenFilterUpdatesSeenProviderWhenSuppressing() {
+        whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+        // GIVEN: Keyguard is not showing, and a notification is present
+        keyguardRepository.setKeyguardShowing(false)
+        runKeyguardCoordinatorTest {
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: The keyguard is now showing
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is recognized as "seen" and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+            // WHEN: The filter is cleaned up
+            unseenFilter.onCleanup()
+
+            // THEN: The SeenNotificationProvider has been updated to reflect the suppression
+            assertThat(seenNotificationsProvider.hasFilteredOutSeenNotifications).isTrue()
+        }
+    }
+
+    @Test
+    fun unseenFilterAllowsNewNotif() {
+        whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+        // GIVEN: Keyguard is showing, no notifications present
+        keyguardRepository.setKeyguardShowing(true)
+        runKeyguardCoordinatorTest {
+            // WHEN: A new notification is posted
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // THEN: The notification is recognized as "unseen" and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenFilterSeenGroupSummaryWithUnseenChild() {
+        whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+        // GIVEN: Keyguard is not showing, and a notification is present
+        keyguardRepository.setKeyguardShowing(false)
+        runKeyguardCoordinatorTest {
+            // WHEN: A new notification is posted
+            val fakeSummary = NotificationEntryBuilder().build()
+            val fakeChild = NotificationEntryBuilder()
+                    .setGroup(context, "group")
+                    .setGroupSummary(context, false)
+                    .build()
+            GroupEntryBuilder()
+                    .setSummary(fakeSummary)
+                    .addChild(fakeChild)
+                    .build()
+
+            collectionListener.onEntryAdded(fakeSummary)
+            collectionListener.onEntryAdded(fakeChild)
+
+            // WHEN: Keyguard is now showing, both notifications are marked as seen
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // WHEN: The child notification is now unseen
+            collectionListener.onEntryUpdated(fakeChild)
+
+            // THEN: The summary is not filtered out, because the child is unseen
+            assertThat(unseenFilter.shouldFilterOut(fakeSummary, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() {
+        whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+        // GIVEN: Keyguard is showing, unseen notification is present
+        keyguardRepository.setKeyguardShowing(true)
+        runKeyguardCoordinatorTest {
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: Keyguard is no longer showing for 5 seconds
+            keyguardRepository.setKeyguardShowing(false)
+            testScheduler.runCurrent()
+            testScheduler.advanceTimeBy(5.seconds.inWholeMilliseconds)
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is now recognized as "seen" and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+        }
+    }
+
+    @Test
+    fun unseenNotificationIsNotMarkedAsSeenIfTimeThresholdNotMet() {
+        whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+        // GIVEN: Keyguard is showing, unseen notification is present
+        keyguardRepository.setKeyguardShowing(true)
+        runKeyguardCoordinatorTest {
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: Keyguard is no longer showing for <5 seconds
+            keyguardRepository.setKeyguardShowing(false)
+            testScheduler.runCurrent()
+            testScheduler.advanceTimeBy(1.seconds.inWholeMilliseconds)
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is not recognized as "seen" and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+    }
+
+    private fun runKeyguardCoordinatorTest(
+        testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
+    ) {
+        val testScope = TestScope(UnconfinedTestDispatcher())
+        val seenNotificationsProvider = SeenNotificationsProviderImpl()
+        val keyguardCoordinator =
+            KeyguardCoordinator(
+                keyguardNotifVisibilityProvider,
+                keyguardRepository,
+                notifPipelineFlags,
+                testScope.backgroundScope,
+                sectionHeaderVisibilityProvider,
+                seenNotificationsProvider,
+                statusBarStateController,
+            )
+        keyguardCoordinator.attach(notifPipeline)
+        testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
+            KeyguardCoordinatorTestScope(keyguardCoordinator, testScope, seenNotificationsProvider)
+                .testBlock()
+        }
+    }
+
+    private inner class KeyguardCoordinatorTestScope(
+        private val keyguardCoordinator: KeyguardCoordinator,
+        private val scope: TestScope,
+        val seenNotificationsProvider: SeenNotificationsProvider,
+    ) : CoroutineScope by scope {
+        val testScheduler: TestCoroutineScheduler
+            get() = scope.testScheduler
+
+        val onStateChangeListener: Consumer<String> =
+            withArgCaptor {
+                verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+            }
+
+        val unseenFilter: NotifFilter
+            get() = keyguardCoordinator.unseenNotifFilter
+
+        // TODO(254647461): Remove lazy once Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD is enabled and
+        //  removed
+        val collectionListener: NotifCollectionListener by lazy {
+            withArgCaptor { verify(notifPipeline).addCollectionListener(capture()) }
+        }
+    }
 }
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 b4a5f5c..e488f39 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
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 
 import static org.junit.Assert.assertTrue;
@@ -38,6 +40,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeStateEvents;
 import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -73,6 +76,7 @@
     @Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
     @Mock private HeadsUpManager mHeadsUpManager;
     @Mock private ShadeStateEvents mShadeStateEvents;
+    @Mock private VisibilityLocationProvider mVisibilityLocationProvider;
     @Mock private VisualStabilityProvider mVisualStabilityProvider;
 
     @Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
@@ -100,6 +104,7 @@
                 mHeadsUpManager,
                 mShadeStateEvents,
                 mStatusBarStateController,
+                mVisibilityLocationProvider,
                 mVisualStabilityProvider,
                 mWakefulnessLifecycle);
 
@@ -355,6 +360,38 @@
     }
 
     @Test
+    public void testMovingVisibleHeadsUpNotAllowed() {
+        // GIVEN stability enforcing conditions
+        setPanelExpanded(true);
+        setSleepy(false);
+
+        // WHEN a notification is alerting and visible
+        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+        when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
+                .thenReturn(true);
+
+        // VERIFY the notification cannot be reordered
+        assertThat(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)).isFalse();
+        assertThat(mNotifStabilityManager.isSectionChangeAllowed(mEntry)).isFalse();
+    }
+
+    @Test
+    public void testMovingInvisibleHeadsUpAllowed() {
+        // GIVEN stability enforcing conditions
+        setPanelExpanded(true);
+        setSleepy(false);
+
+        // WHEN a notification is alerting but not visible
+        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+        when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
+                .thenReturn(false);
+
+        // VERIFY the notification can be reordered
+        assertThat(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)).isTrue();
+        assertThat(mNotifStabilityManager.isSectionChangeAllowed(mEntry)).isTrue();
+    }
+
+    @Test
     public void testNeverSuppressedChanges_noInvalidationCalled() {
         // GIVEN no notifications are currently being suppressed from grouping nor being sorted
 
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
index 15cf17d..6167b46 100644
--- 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
@@ -18,6 +18,7 @@
 import android.content.Context
 import android.testing.AndroidTestingRunner
 import android.view.View
+import android.view.ViewGroup
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -26,6 +27,10 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.matches
+import org.mockito.Mockito.verify
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -124,6 +129,64 @@
         Assert.assertNull(controller3.view.parent)
         Assert.assertNull(controller4.view.parent)
         Assert.assertNull(controller5.view.parent)
+        verifyDetachingChildLogged(controller3, oldParent = controller2)
+        verifyDetachingChildLogged(controller4, oldParent = controller2)
+        verifyDetachingChildLogged(controller5, oldParent = controller2)
+    }
+
+    @Test
+    fun testRemovedGroupsWithKeepInParentAreKeptTogether() {
+        // GIVEN a preexisting tree with a group
+        // AND the group children supports keepInParent
+        applySpecAndCheck(
+            node(controller1),
+            node(controller2, node(controller3), node(controller4), node(controller5))
+        )
+        controller3.supportsKeepInParent = true
+        controller4.supportsKeepInParent = true
+        controller5.supportsKeepInParent = true
+
+        // WHEN the new spec removes the entire group
+        applySpecAndCheck(node(controller1))
+
+        // THEN the group children are still attached to their parent
+        Assert.assertEquals(controller2.view, controller3.view.parent)
+        Assert.assertEquals(controller2.view, controller4.view.parent)
+        Assert.assertEquals(controller2.view, controller5.view.parent)
+        verifySkipDetachingChildLogged(controller3, parent = controller2)
+        verifySkipDetachingChildLogged(controller4, parent = controller2)
+        verifySkipDetachingChildLogged(controller5, parent = controller2)
+    }
+
+    @Test
+    fun testReuseRemovedGroupsWithKeepInParent() {
+        // GIVEN a preexisting tree with a dismissed group
+        // AND the group children supports keepInParent
+        controller3.supportsKeepInParent = true
+        controller4.supportsKeepInParent = true
+        controller5.supportsKeepInParent = true
+        applySpecAndCheck(
+            node(controller1),
+            node(controller2, node(controller3), node(controller4), node(controller5))
+        )
+        applySpecAndCheck(node(controller1))
+
+        // WHEN a new spec is applied which reuses the dismissed views
+        applySpecAndCheck(
+            node(controller1),
+            node(controller2),
+            node(controller3),
+            node(controller4),
+            node(controller5)
+        )
+
+        // THEN the dismissed views can be reused
+        Assert.assertEquals(rootController.view, controller3.view.parent)
+        Assert.assertEquals(rootController.view, controller4.view.parent)
+        Assert.assertEquals(rootController.view, controller5.view.parent)
+        verifyDetachingChildLogged(controller3, oldParent = null)
+        verifyDetachingChildLogged(controller4, oldParent = null)
+        verifyDetachingChildLogged(controller5, oldParent = null)
     }
 
     @Test
@@ -184,7 +247,30 @@
         }
     }
 
+    private fun verifySkipDetachingChildLogged(child: NodeController, parent: NodeController) {
+        verify(logger)
+            .logSkipDetachingChild(
+                key = matches(child.nodeLabel),
+                parentKey = matches(parent.nodeLabel),
+                anyBoolean(),
+                anyBoolean()
+            )
+    }
+
+    private fun verifyDetachingChildLogged(child: NodeController, oldParent: NodeController?) {
+        verify(logger)
+            .logDetachingChild(
+                key = matches(child.nodeLabel),
+                isTransfer = anyBoolean(),
+                isParentRemoved = anyBoolean(),
+                oldParent = oldParent?.let { matches(it.nodeLabel) } ?: isNull(),
+                newParent = isNull()
+            )
+    }
+
     private class FakeController(context: Context, label: String) : NodeController {
+        var supportsKeepInParent: Boolean = false
+
         override val view: FrameLayout = FrameLayout(context)
         override val nodeLabel: String = label
         override fun getChildCount(): Int = view.childCount
@@ -209,6 +295,22 @@
         override fun onViewAdded() {}
         override fun onViewMoved() {}
         override fun onViewRemoved() {}
+        override fun offerToKeepInParentForAnimation(): Boolean {
+            return supportsKeepInParent
+        }
+
+        override fun removeFromParentIfKeptForAnimation(): Boolean {
+            if (supportsKeepInParent) {
+                (view.parent as? ViewGroup)?.removeView(view)
+                return true
+            }
+
+            return false
+        }
+
+        override fun resetKeepInParentForAnimation() {
+            supportsKeepInParent = false
+        }
     }
 
     private class SpecBuilder(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index 8b7b4de..6bd3f7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -26,22 +26,17 @@
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.collection.EntryUtilKt.modifyEntry;
-import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.argThat;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -54,10 +49,10 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -97,7 +92,7 @@
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private HighPriorityProvider mHighPriorityProvider;
     @Mock private SysuiStatusBarStateController mStatusBarStateController;
-    @Mock private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock private UserTracker mUserTracker;
     private final FakeSettings mFakeSettings = new FakeSettings();
 
     private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
@@ -117,7 +112,7 @@
                                 mKeyguardUpdateMonitor,
                                 mHighPriorityProvider,
                                 mStatusBarStateController,
-                                mBroadcastDispatcher,
+                                mUserTracker,
                                 mFakeSettings,
                                 mFakeSettings);
         mKeyguardNotificationVisibilityProvider = component.getProvider();
@@ -205,23 +200,19 @@
     }
 
     @Test
-    public void notifyListeners_onReceiveUserSwitchBroadcast() {
-        ArgumentCaptor<BroadcastReceiver> callbackCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mBroadcastDispatcher).registerReceiver(
+    public void notifyListeners_onReceiveUserSwitchCallback() {
+        ArgumentCaptor<UserTracker.Callback> callbackCaptor =
+                ArgumentCaptor.forClass(UserTracker.Callback.class);
+        verify(mUserTracker).addCallback(
                 callbackCaptor.capture(),
-                argThat(intentFilter -> intentFilter.hasAction(Intent.ACTION_USER_SWITCHED)),
-                isNull(),
-                isNull(),
-                eq(Context.RECEIVER_EXPORTED),
-                isNull());
-        BroadcastReceiver callback = callbackCaptor.getValue();
+                any());
+        UserTracker.Callback callback = callbackCaptor.getValue();
 
         Consumer<String> listener = mock(Consumer.class);
         mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
 
         when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
-        callback.onReceive(mContext, new Intent(Intent.ACTION_USER_SWITCHED));
+        callback.onUserChanged(CURR_USER_ID, mContext);
 
         verify(listener).accept(anyString());
     }
@@ -619,7 +610,7 @@
                     @BindsInstance KeyguardUpdateMonitor keyguardUpdateMonitor,
                     @BindsInstance HighPriorityProvider highPriorityProvider,
                     @BindsInstance SysuiStatusBarStateController statusBarStateController,
-                    @BindsInstance BroadcastDispatcher broadcastDispatcher,
+                    @BindsInstance UserTracker userTracker,
                     @BindsInstance SecureSettings secureSettings,
                     @BindsInstance GlobalSettings globalSettings
             );
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index ea311da..21aae00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -17,6 +17,7 @@
 
 
 import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.Notification.GROUP_ALERT_SUMMARY;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -33,6 +34,8 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -390,6 +393,127 @@
         assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
     }
 
+    private long makeWhenHoursAgo(long hoursAgo) {
+        return System.currentTimeMillis() - (1000 * 60 * 60 * hoursAgo);
+    }
+
+    @Test
+    public void testShouldHeadsUp_oldWhen_flagDisabled() throws Exception {
+        ensureStateForHeadsUpWhenAwake();
+        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(false);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        entry.getSbn().getNotification().when = makeWhenHoursAgo(25);
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+        verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+        verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
+    }
+
+    @Test
+    public void testShouldHeadsUp_oldWhen_whenNow() throws Exception {
+        ensureStateForHeadsUpWhenAwake();
+        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+        verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+        verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
+    }
+
+    @Test
+    public void testShouldHeadsUp_oldWhen_whenRecent() throws Exception {
+        ensureStateForHeadsUpWhenAwake();
+        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        entry.getSbn().getNotification().when = makeWhenHoursAgo(13);
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+        verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+        verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
+    }
+
+    @Test
+    public void testShouldHeadsUp_oldWhen_whenZero() throws Exception {
+        ensureStateForHeadsUpWhenAwake();
+        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        entry.getSbn().getNotification().when = 0L;
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+        verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+        verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(0L), anyLong(),
+                eq("when <= 0"));
+    }
+
+    @Test
+    public void testShouldHeadsUp_oldWhen_whenNegative() throws Exception {
+        ensureStateForHeadsUpWhenAwake();
+        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        entry.getSbn().getNotification().when = -1L;
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+        verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+        verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(-1L), anyLong(),
+                eq("when <= 0"));
+    }
+
+    @Test
+    public void testShouldHeadsUp_oldWhen_hasFullScreenIntent() throws Exception {
+        ensureStateForHeadsUpWhenAwake();
+        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+        long when = makeWhenHoursAgo(25);
+
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silent= */ false);
+        entry.getSbn().getNotification().when = when;
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+        verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+        verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(when), anyLong(),
+                eq("full-screen intent"));
+    }
+
+    @Test
+    public void testShouldHeadsUp_oldWhen_isForegroundService() throws Exception {
+        ensureStateForHeadsUpWhenAwake();
+        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+        long when = makeWhenHoursAgo(25);
+
+        NotificationEntry entry = createFgsNotification(IMPORTANCE_HIGH);
+        entry.getSbn().getNotification().when = when;
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+        verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+        verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(when), anyLong(),
+                eq("foreground service"));
+    }
+
+    @Test
+    public void testShouldNotHeadsUp_oldWhen() throws Exception {
+        ensureStateForHeadsUpWhenAwake();
+        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+        long when = makeWhenHoursAgo(25);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        entry.getSbn().getNotification().when = when;
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+
+        verify(mLogger).logNoHeadsUpOldWhen(eq(entry), eq(when), anyLong());
+        verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
+    }
+
     @Test
     public void testShouldNotFullScreen_notPendingIntent_withStrictFlag() throws Exception {
         when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
@@ -763,6 +887,16 @@
         return createNotification(importance, n);
     }
 
+    private NotificationEntry createFgsNotification(int importance) {
+        Notification n = new Notification.Builder(getContext(), "a")
+                .setContentTitle("title")
+                .setContentText("content text")
+                .setFlag(FLAG_FOREGROUND_SERVICE, true)
+                .build();
+
+        return createNotification(importance, n);
+    }
+
     private final NotificationInterruptSuppressor
             mSuppressAwakeHeadsUp =
             new NotificationInterruptSuppressor() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index b2dc842..7117c23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -41,6 +41,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
@@ -88,6 +89,7 @@
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private NotifPipeline mNotifPipeline;
     @Mock private NotificationListener mListener;
+    @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
 
     private NotificationEntry mEntry;
     private TestableNotificationLogger mLogger;
@@ -118,6 +120,7 @@
                 mVisibilityProvider,
                 mNotifPipeline,
                 mock(StatusBarStateControllerImpl.class),
+                mShadeExpansionStateManager,
                 mBarService,
                 mExpansionStateLogger
         );
@@ -152,7 +155,7 @@
 
         when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
         when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
-        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
+        mLogger.onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
         mUiBgExecutor.runAllReady();
 
@@ -162,7 +165,7 @@
 
         // |mEntry| won't change visibility, so it shouldn't be reported again:
         Mockito.reset(mBarService);
-        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
+        mLogger.onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
         mUiBgExecutor.runAllReady();
 
@@ -174,7 +177,7 @@
             throws Exception {
         when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
         when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
-        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
+        mLogger.onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
         mUiBgExecutor.runAllReady();
         Mockito.reset(mBarService);
@@ -189,13 +192,13 @@
     }
 
     private void setStateAsleep() {
-        mLogger.onPanelExpandedChanged(true);
+        mLogger.onShadeExpansionFullyChanged(true);
         mLogger.onDozingChanged(true);
         mLogger.onStateChanged(StatusBarState.KEYGUARD);
     }
 
     private void setStateAwake() {
-        mLogger.onPanelExpandedChanged(false);
+        mLogger.onShadeExpansionFullyChanged(false);
         mLogger.onDozingChanged(false);
         mLogger.onStateChanged(StatusBarState.SHADE);
     }
@@ -221,7 +224,7 @@
         when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
         setStateAwake();
         // Now expand panel
-        mLogger.onPanelExpandedChanged(true);
+        mLogger.onShadeExpansionFullyChanged(true);
         assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
         assertFalse(mNotificationPanelLoggerFake.get(0).isLockscreen);
         assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
@@ -263,6 +266,7 @@
                 NotificationVisibilityProvider visibilityProvider,
                 NotifPipeline notifPipeline,
                 StatusBarStateControllerImpl statusBarStateController,
+                ShadeExpansionStateManager shadeExpansionStateManager,
                 IStatusBarService barService,
                 ExpansionStateLogger expansionStateLogger) {
             super(
@@ -272,6 +276,7 @@
                     visibilityProvider,
                     notifPipeline,
                     statusBarStateController,
+                    shadeExpansionStateManager,
                     expansionStateLogger,
                     mNotificationPanelLoggerFake
             );
@@ -280,9 +285,5 @@
             // Make this on the current thread so we can wait for it during tests.
             mHandler = Handler.createAsync(Looper.myLooper());
         }
-
-        OnChildLocationsChangedListener getChildLocationsChangedListenerForTest() {
-            return mNotificationLocationsChangedListener;
-        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
new file mode 100644
index 0000000..2d23f3c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -0,0 +1,173 @@
+/*
+ * 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.row
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.PluginManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.NotificationMediaManager
+import com.android.systemui.statusbar.SmartReplyController
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.logging.NotificationLogger
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.SmartReplyConstants
+import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.wmshell.BubblesManager
+import java.util.Optional
+import junit.framework.Assert
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.never
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ExpandableNotificationRowControllerTest : SysuiTestCase() {
+
+    private val appName = "MyApp"
+    private val notifKey = "MyNotifKey"
+
+    private val view: ExpandableNotificationRow = mock()
+    private val activableNotificationViewController: ActivatableNotificationViewController = mock()
+    private val rivSubComponentFactory: RemoteInputViewSubcomponent.Factory = mock()
+    private val metricsLogger: MetricsLogger = mock()
+    private val logBufferLogger: NotificationRowLogger = mock()
+    private val listContainer: NotificationListContainer = mock()
+    private val mediaManager: NotificationMediaManager = mock()
+    private val smartReplyConstants: SmartReplyConstants = mock()
+    private val smartReplyController: SmartReplyController = mock()
+    private val pluginManager: PluginManager = mock()
+    private val systemClock: SystemClock = mock()
+    private val keyguardBypassController: KeyguardBypassController = mock()
+    private val groupMembershipManager: GroupMembershipManager = mock()
+    private val groupExpansionManager: GroupExpansionManager = mock()
+    private val rowContentBindStage: RowContentBindStage = mock()
+    private val notifLogger: NotificationLogger = mock()
+    private val headsUpManager: HeadsUpManager = mock()
+    private val onExpandClickListener: ExpandableNotificationRow.OnExpandClickListener = mock()
+    private val statusBarStateController: StatusBarStateController = mock()
+    private val gutsManager: NotificationGutsManager = mock()
+    private val onUserInteractionCallback: OnUserInteractionCallback = mock()
+    private val falsingManager: FalsingManager = mock()
+    private val falsingCollector: FalsingCollector = mock()
+    private val featureFlags: FeatureFlags = mock()
+    private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock()
+    private val bubblesManager: BubblesManager = mock()
+    private val dragController: ExpandableNotificationRowDragController = mock()
+    private lateinit var controller: ExpandableNotificationRowController
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        controller =
+            ExpandableNotificationRowController(
+                view,
+                activableNotificationViewController,
+                rivSubComponentFactory,
+                metricsLogger,
+                logBufferLogger,
+                listContainer,
+                mediaManager,
+                smartReplyConstants,
+                smartReplyController,
+                pluginManager,
+                systemClock,
+                appName,
+                notifKey,
+                keyguardBypassController,
+                groupMembershipManager,
+                groupExpansionManager,
+                rowContentBindStage,
+                notifLogger,
+                headsUpManager,
+                onExpandClickListener,
+                statusBarStateController,
+                gutsManager,
+                /*allowLongPress=*/ false,
+                onUserInteractionCallback,
+                falsingManager,
+                falsingCollector,
+                featureFlags,
+                peopleNotificationIdentifier,
+                Optional.of(bubblesManager),
+                dragController
+            )
+    }
+
+    @After
+    fun tearDown() {
+        disallowTestableLooperAsMainThread()
+    }
+
+    @Test
+    fun offerKeepInParent_parentDismissed() {
+        whenever(featureFlags.isEnabled(Flags.NOTIFICATION_GROUP_DISMISSAL_ANIMATION))
+            .thenReturn(true)
+        whenever(view.isParentDismissed).thenReturn(true)
+
+        Assert.assertTrue(controller.offerToKeepInParentForAnimation())
+        Mockito.verify(view).setKeepInParentForDismissAnimation(true)
+    }
+
+    @Test
+    fun offerKeepInParent_parentNotDismissed() {
+        whenever(featureFlags.isEnabled(Flags.NOTIFICATION_GROUP_DISMISSAL_ANIMATION))
+            .thenReturn(true)
+
+        Assert.assertFalse(controller.offerToKeepInParentForAnimation())
+        Mockito.verify(view, never()).setKeepInParentForDismissAnimation(anyBoolean())
+    }
+
+    @Test
+    fun removeFromParent_keptForAnimation() {
+        val parentView: ExpandableNotificationRow = mock()
+        whenever(view.notificationParent).thenReturn(parentView)
+        whenever(view.keepInParentForDismissAnimation()).thenReturn(true)
+
+        Assert.assertTrue(controller.removeFromParentIfKeptForAnimation())
+        Mockito.verify(parentView).removeChildNotification(view)
+    }
+
+    @Test
+    fun removeFromParent_notKeptForAnimation() {
+        val parentView: ExpandableNotificationRow = mock()
+        whenever(view.notificationParent).thenReturn(parentView)
+
+        Assert.assertFalse(controller.removeFromParentIfKeptForAnimation())
+        Mockito.verifyNoMoreInteractions(parentView)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index ed2afe7..915924f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -41,7 +41,6 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
-import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 137842e..ee8db18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -38,6 +38,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
@@ -232,7 +233,6 @@
     @Test
     public void testUserLockedResetEvenWhenNoChildren() {
         mGroupRow.setUserLocked(true);
-        mGroupRow.removeAllChildren();
         mGroupRow.setUserLocked(false);
         assertFalse("The childrencontainer should not be userlocked but is, the state "
                 + "seems out of sync.", mGroupRow.getChildrenContainer().isUserLocked());
@@ -240,12 +240,11 @@
 
     @Test
     public void testReinflatedOnDensityChange() {
-        mGroupRow.setUserLocked(true);
-        mGroupRow.removeAllChildren();
-        mGroupRow.setUserLocked(false);
         NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
-        mGroupRow.setChildrenContainer(mockContainer);
-        mGroupRow.onDensityOrFontScaleChanged();
+        mNotifRow.setChildrenContainer(mockContainer);
+
+        mNotifRow.onDensityOrFontScaleChanged();
+
         verify(mockContainer).reInflateViews(any(), any());
     }
 
@@ -460,4 +459,79 @@
         verify(mNotificationTestHelper.mOnUserInteractionCallback, never())
                 .registerFutureDismissal(any(), anyInt());
     }
+
+    @Test
+    public void testAddChildNotification() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup(0);
+        ExpandableNotificationRow child = mNotificationTestHelper.createRow();
+
+        group.addChildNotification(child);
+
+        Assert.assertEquals(child, group.getChildNotificationAt(0));
+        Assert.assertEquals(group, child.getNotificationParent());
+        Assert.assertTrue(child.isChildInGroup());
+    }
+
+    @Test
+    public void testAddChildNotification_childSkipped() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup(0);
+        ExpandableNotificationRow child = mNotificationTestHelper.createRow();
+        child.setKeepInParentForDismissAnimation(true);
+
+        group.addChildNotification(child);
+
+        Assert.assertTrue(group.getAttachedChildren().isEmpty());
+        Assert.assertNotEquals(group, child.getNotificationParent());
+        verify(mNotificationTestHelper.getMockLogger()).logSkipAttachingKeepInParentChild(
+                /*child=*/ child.getEntry(),
+                /*newParent=*/ group.getEntry()
+        );
+    }
+
+    @Test
+    public void testRemoveChildNotification() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+        ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+        child.setKeepInParentForDismissAnimation(true);
+
+        group.removeChildNotification(child);
+
+        Assert.assertNull(child.getParent());
+        Assert.assertNull(child.getNotificationParent());
+        Assert.assertFalse(child.keepInParentForDismissAnimation());
+        verifyNoMoreInteractions(mNotificationTestHelper.getMockLogger());
+    }
+
+    @Test
+    public void testRemoveChildrenWithKeepInParent_removesChildWithKeepInParent() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+        ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+        child.setKeepInParentForDismissAnimation(true);
+
+        group.removeChildrenWithKeepInParent();
+
+        Assert.assertNull(child.getParent());
+        Assert.assertNull(child.getNotificationParent());
+        Assert.assertFalse(child.keepInParentForDismissAnimation());
+        verify(mNotificationTestHelper.getMockLogger()).logKeepInParentChildDetached(
+                /*child=*/ child.getEntry(),
+                /*oldParent=*/ group.getEntry()
+        );
+    }
+
+    @Test
+    public void testRemoveChildrenWithKeepInParent_skipsChildrenWithoutKeepInParent()
+            throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+        ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+
+        group.removeChildrenWithKeepInParent();
+
+        Assert.assertEquals(group, child.getNotificationParent());
+        Assert.assertFalse(child.keepInParentForDismissAnimation());
+        verify(mNotificationTestHelper.getMockLogger(), never()).logKeepInParentChildDetached(
+                /*child=*/ any(),
+                /*oldParent=*/ any()
+        );
+    }
 }
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..59d4720 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;
@@ -55,11 +56,13 @@
 import com.android.systemui.media.controls.util.MediaFeatureFlag;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 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;
@@ -70,7 +73,7 @@
 import com.android.systemui.statusbar.notification.icon.IconBuilder;
 import com.android.systemui.statusbar.notification.icon.IconManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -113,6 +116,7 @@
     private final Context mContext;
     private final TestableLooper mTestLooper;
     private int mId;
+    private final ExpandableNotificationRowLogger mMockLogger;
     private final GroupMembershipManager mGroupMembershipManager;
     private final GroupExpansionManager mGroupExpansionManager;
     private ExpandableNotificationRow mRow;
@@ -136,6 +140,7 @@
         dependency.injectMockDependency(NotificationMediaManager.class);
         dependency.injectMockDependency(NotificationShadeWindowController.class);
         dependency.injectMockDependency(MediaOutputDialogFactory.class);
+        mMockLogger = mock(ExpandableNotificationRowLogger.class);
         mStatusBarStateController = mock(StatusBarStateController.class);
         mGroupMembershipManager = mock(GroupMembershipManager.class);
         mGroupExpansionManager = mock(GroupExpansionManager.class);
@@ -149,7 +154,8 @@
                 mock(ConfigurationControllerImpl.class),
                 new Handler(mTestLooper.getLooper()),
                 mock(AccessibilityManagerWrapper.class),
-                mock(UiEventLogger.class)
+                mock(UiEventLogger.class),
+                mock(ShadeExpansionStateManager.class)
         );
         mIconManager = new IconManager(
                 mock(CommonNotifCollection.class),
@@ -191,6 +197,28 @@
         mDefaultInflationFlags = defaultInflationFlags;
     }
 
+    public ExpandableNotificationRowLogger getMockLogger() {
+        return mMockLogger;
+    }
+
+    /**
+     * 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.requestRoundness(topRoundness, bottomRoundness, sourceType, /*animate = */ false);
+        assertEquals(topRoundness, row.getTopRoundness(), /* delta = */ 0f);
+        assertEquals(bottomRoundness, row.getBottomRoundness(), /* delta = */ 0f);
+        return row;
+    }
+
     /**
      * Creates a generic row.
      *
@@ -270,7 +298,8 @@
     public ExpandableNotificationRow createBubble()
             throws Exception {
         Notification n = createNotification(false /* isGroupSummary */,
-                null /* groupKey */, makeBubbleMetadata(null));
+                null /* groupKey */,
+                makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */));
         n.flags |= FLAG_BUBBLE;
         ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE,
                 mDefaultInflationFlags, IMPORTANCE_HIGH);
@@ -303,7 +332,8 @@
     public ExpandableNotificationRow createBubbleInGroup()
             throws Exception {
         Notification n = createNotification(false /* isGroupSummary */,
-                GROUP_KEY /* groupKey */, makeBubbleMetadata(null));
+                GROUP_KEY /* groupKey */,
+                makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */));
         n.flags |= FLAG_BUBBLE;
         ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE,
                 mDefaultInflationFlags, IMPORTANCE_HIGH);
@@ -319,7 +349,7 @@
      * @param deleteIntent the intent to assign to {@link BubbleMetadata#deleteIntent}
      */
     public NotificationEntry createBubble(@Nullable PendingIntent deleteIntent) {
-        return createBubble(makeBubbleMetadata(deleteIntent), USER_HANDLE);
+        return createBubble(makeBubbleMetadata(deleteIntent, false /* autoExpand */), USER_HANDLE);
     }
 
     /**
@@ -328,7 +358,16 @@
      * @param handle the user to associate with this bubble.
      */
     public NotificationEntry createBubble(UserHandle handle) {
-        return createBubble(makeBubbleMetadata(null), handle);
+        return createBubble(makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */),
+                handle);
+    }
+
+    /**
+     * Returns an {@link NotificationEntry} that should be shown as a auto-expanded bubble.
+     */
+    public NotificationEntry createAutoExpandedBubble() {
+        return createBubble(makeBubbleMetadata(null /* deleteIntent */, true /* autoExpand */),
+                USER_HANDLE);
     }
 
     /**
@@ -502,7 +541,7 @@
                 mock(RemoteInputViewSubcomponent.Factory.class),
                 APP_NAME,
                 entry.getKey(),
-                mock(ExpansionLogger.class),
+                mMockLogger,
                 mock(KeyguardBypassController.class),
                 mGroupMembershipManager,
                 mGroupExpansionManager,
@@ -536,7 +575,7 @@
         assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
     }
 
-    private BubbleMetadata makeBubbleMetadata(PendingIntent deleteIntent) {
+    private BubbleMetadata makeBubbleMetadata(PendingIntent deleteIntent, boolean autoExpand) {
         Intent target = new Intent(mContext, BubblesTestActivity.class);
         PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target,
                 PendingIntent.FLAG_MUTABLE);
@@ -545,6 +584,7 @@
                         Icon.createWithResource(mContext, R.drawable.android))
                 .setDeleteIntent(deleteIntent)
                 .setDesiredHeight(314)
+                .setAutoExpandBubble(autoExpand)
                 .build();
     }
 
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..fd1944e 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.LegacySourceType;
 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 = */ LegacySourceType.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 = */ LegacySourceType.DefaultValue);
+        ExpandableNotificationRow row2 = mNotificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ LegacySourceType.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/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 8c8b644..bd0a556 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger;
@@ -73,7 +74,8 @@
         mRoundnessManager = new NotificationRoundnessManager(
                 new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext),
                 mLogger,
-                mock(DumpManager.class));
+                mock(DumpManager.class),
+                mock(FeatureFlags.class));
         allowTestableLooperAsMainThread();
         NotificationTestHelper testHelper = new NotificationTestHelper(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index ecc0224..30da08e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -30,6 +30,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -59,10 +60,12 @@
     @Mock private KeyguardMediaController mKeyguardMediaController;
     @Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
     @Mock private MediaContainerController mMediaContainerController;
+    @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
     @Mock private SectionHeaderController mIncomingHeaderController;
     @Mock private SectionHeaderController mPeopleHeaderController;
     @Mock private SectionHeaderController mAlertingHeaderController;
     @Mock private SectionHeaderController mSilentHeaderController;
+    @Mock private FeatureFlags mFeatureFlag;
 
     private NotificationSectionsManager mSectionsManager;
 
@@ -89,10 +92,12 @@
                         mKeyguardMediaController,
                         mSectionsFeatureManager,
                         mMediaContainerController,
+                        mNotificationRoundnessManager,
                         mIncomingHeaderController,
                         mPeopleHeaderController,
                         mAlertingHeaderController,
-                        mSilentHeaderController
+                        mSilentHeaderController,
+                        mFeatureFlag
                 );
         // Required in order for the header inflation to work properly
         when(mNssl.generateLayoutParams(any(AttributeSet.class)))
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..9d759c4 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.LegacySourceType
 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 = */ LegacySourceType.OnScroll)
+
+        NotificationShelf.resetLegacyOnScrollRoundness(row)
+
+        assertEquals(0f, row.topRoundness)
+        assertEquals(0f, row.bottomRoundness)
+    }
+
+    @Test
+    fun resetOnScrollRoundness_shouldNotResetOtherRoundness() {
+        val row1: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ LegacySourceType.DefaultValue)
+        val row2: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ LegacySourceType.OnDismissAnimation)
+
+        NotificationShelf.resetLegacyOnScrollRoundness(row1)
+        NotificationShelf.resetLegacyOnScrollRoundness(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/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 90061b0..645052f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -59,8 +59,11 @@
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
@@ -118,10 +121,12 @@
     @Mock private GroupExpansionManager mGroupExpansionManager;
     @Mock private SectionHeaderController mSilentHeaderController;
     @Mock private NotifPipeline mNotifPipeline;
+    @Mock private NotifPipelineFlags mNotifPipelineFlags;
     @Mock private NotifCollection mNotifCollection;
     @Mock private UiEventLogger mUiEventLogger;
     @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
+    @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
     @Mock private ShadeController mShadeController;
     @Mock private InteractionJankMonitor mJankMonitor;
     @Mock private StackStateLogger mStackLogger;
@@ -168,11 +173,14 @@
                 mGroupExpansionManager,
                 mSilentHeaderController,
                 mNotifPipeline,
+                mNotifPipelineFlags,
                 mNotifCollection,
                 mLockscreenShadeTransitionController,
                 mShadeTransitionController,
                 mUiEventLogger,
                 mRemoteInputManager,
+                mVisibilityLocationProviderDelegator,
+                new SeenNotificationsProviderImpl(),
                 mShadeController,
                 mJankMonitor,
                 mStackLogger,
@@ -225,14 +233,16 @@
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ true,
-                /* notifVisibleInShade= */ true);
+                /* notifVisibleInShade= */ true,
+                /* areSeenNotifsFiltered= */false);
 
         setupShowEmptyShadeViewState(false);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ false,
-                /* notifVisibleInShade= */ true);
+                /* notifVisibleInShade= */ true,
+                /* areSeenNotifsFiltered= */false);
     }
 
     @Test
@@ -245,14 +255,16 @@
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ true,
-                /* notifVisibleInShade= */ false);
+                /* notifVisibleInShade= */ false,
+                /* areSeenNotifsFiltered= */false);
 
         setupShowEmptyShadeViewState(false);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ false,
-                /* notifVisibleInShade= */ false);
+                /* notifVisibleInShade= */ false,
+                /* areSeenNotifsFiltered= */false);
     }
 
     @Test
@@ -271,14 +283,16 @@
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ true,
-                /* notifVisibleInShade= */ false);
+                /* notifVisibleInShade= */ false,
+                /* areSeenNotifsFiltered= */false);
 
         mController.setQsFullScreen(true);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ true,
-                /* notifVisibleInShade= */ false);
+                /* notifVisibleInShade= */ false,
+                /* areSeenNotifsFiltered= */false);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index dceb4ff..07ea630 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -324,7 +324,7 @@
     public void updateEmptyView_dndSuppressing() {
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
 
-        mStackScroller.updateEmptyShadeView(true, true);
+        mStackScroller.updateEmptyShadeView(true, true, false);
 
         verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
     }
@@ -334,7 +334,7 @@
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
 
-        mStackScroller.updateEmptyShadeView(true, false);
+        mStackScroller.updateEmptyShadeView(true, false, false);
 
         verify(mEmptyShadeView).setText(R.string.empty_shade_text);
     }
@@ -343,10 +343,10 @@
     public void updateEmptyView_noNotificationsToDndSuppressing() {
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
-        mStackScroller.updateEmptyShadeView(true, false);
+        mStackScroller.updateEmptyShadeView(true, false, false);
         verify(mEmptyShadeView).setText(R.string.empty_shade_text);
 
-        mStackScroller.updateEmptyShadeView(true, true);
+        mStackScroller.updateEmptyShadeView(true, true, false);
         verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 4ea1c71..680a323 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -74,6 +74,7 @@
     private NotificationSwipeHelper mSwipeHelper;
     private NotificationSwipeHelper.NotificationCallback mCallback;
     private NotificationMenuRowPlugin.OnMenuEventListener mListener;
+    private NotificationRoundnessManager mNotificationRoundnessManager;
     private View mView;
     private MotionEvent mEvent;
     private NotificationMenuRowPlugin mMenuRow;
@@ -92,10 +93,17 @@
     public void setUp() throws Exception {
         mCallback = mock(NotificationSwipeHelper.NotificationCallback.class);
         mListener = mock(NotificationMenuRowPlugin.OnMenuEventListener.class);
+        mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
         mFeatureFlags = mock(FeatureFlags.class);
         mSwipeHelper = spy(new NotificationSwipeHelper(
-                mContext.getResources(), ViewConfiguration.get(mContext),
-                new FalsingManagerFake(), mFeatureFlags, SwipeHelper.X, mCallback, mListener));
+                mContext.getResources(),
+                ViewConfiguration.get(mContext),
+                new FalsingManagerFake(),
+                mFeatureFlags,
+                SwipeHelper.X,
+                mCallback,
+                mListener,
+                mNotificationRoundnessManager));
         mView = mock(View.class);
         mEvent = mock(MotionEvent.class);
         mMenuRow = mock(NotificationMenuRowPlugin.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
index a2e9230..81a3f12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
@@ -35,7 +35,7 @@
     ) =
         NotificationTargetsHelper(
             FakeFeatureFlags().apply {
-                set(Flags.NOTIFICATION_GROUP_CORNER, notificationGroupCorner)
+                set(Flags.USE_ROUNDNESS_SOURCETYPES, notificationGroupCorner)
             }
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 40aec82..4d9db8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -14,6 +14,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
@@ -31,10 +32,10 @@
 
     private val hostView = FrameLayout(context)
     private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
-    private val notificationRow = mock(ExpandableNotificationRow::class.java)
-    private val dumpManager = mock(DumpManager::class.java)
-    private val mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager::class.java)
-    private val notificationShelf = mock(NotificationShelf::class.java)
+    private val notificationRow = mock<ExpandableNotificationRow>()
+    private val dumpManager = mock<DumpManager>()
+    private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
+    private val notificationShelf = mock<NotificationShelf>()
     private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
         layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
     }
@@ -46,7 +47,7 @@
             mStatusBarKeyguardViewManager
     )
 
-    private val testableResources = mContext.orCreateTestableResources
+    private val testableResources = mContext.getOrCreateTestableResources()
 
     private fun px(@DimenRes id: Int): Float =
             testableResources.resources.getDimensionPixelSize(id).toFloat()
@@ -98,7 +99,7 @@
         stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
 
         val marginBottom =
-            context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
+                context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
         val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY
         val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f
         assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY)
@@ -118,7 +119,7 @@
 
     @Test
     fun resetViewStates_expansionChanging_notificationBecomesTransparent() {
-        whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(false)
+        whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
         resetViewStates_expansionChanging_notificationAlphaUpdated(
                 expansionFraction = 0.25f,
                 expectedAlpha = 0.0f
@@ -127,7 +128,7 @@
 
     @Test
     fun resetViewStates_expansionChangingWhileBouncerInTransit_viewBecomesTransparent() {
-        whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(true)
+        whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
         resetViewStates_expansionChanging_notificationAlphaUpdated(
                 expansionFraction = 0.85f,
                 expectedAlpha = 0.0f
@@ -136,7 +137,7 @@
 
     @Test
     fun resetViewStates_expansionChanging_notificationAlphaUpdated() {
-        whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(false)
+        whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
         resetViewStates_expansionChanging_notificationAlphaUpdated(
                 expansionFraction = 0.6f,
                 expectedAlpha = getContentAlpha(0.6f)
@@ -145,7 +146,7 @@
 
     @Test
     fun resetViewStates_expansionChangingWhileBouncerInTransit_notificationAlphaUpdated() {
-        whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(true)
+        whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
         resetViewStates_expansionChanging_notificationAlphaUpdated(
                 expansionFraction = 0.95f,
                 expectedAlpha = aboutToShowBouncerProgress(0.95f)
@@ -507,6 +508,192 @@
         assertEquals(1f, currentRoundness)
     }
 
+    @Test
+    fun shadeOpened_hunFullyOverlapsQqsPanel_hunShouldHaveFullShadow() {
+        // Given: shade is opened, yTranslation of HUN is 0,
+        // the height of HUN equals to the height of QQS Panel,
+        // and HUN fully overlaps with QQS Panel
+        ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
+                px(R.dimen.qqs_layout_padding_bottom)
+        val childHunView = createHunViewMock(
+                isShadeOpen = true,
+                fullyVisible = false,
+                headerVisibleAmount = 1f
+        )
+        val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+        algorithmState.visibleChildren.add(childHunView)
+
+        // When: updateChildZValue() is called for the top HUN
+        stackScrollAlgorithm.updateChildZValue(
+                /* i= */ 0,
+                /* childrenOnTop= */ 0.0f,
+                /* StackScrollAlgorithmState= */ algorithmState,
+                /* ambientState= */ ambientState,
+                /* shouldElevateHun= */ true
+        )
+
+        // Then: full shadow would be applied
+        assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation)
+    }
+
+    @Test
+    fun shadeOpened_hunPartiallyOverlapsQQS_hunShouldHavePartialShadow() {
+        // Given: shade is opened, yTranslation of HUN is greater than 0,
+        // the height of HUN is equal to the height of QQS Panel,
+        // and HUN partially overlaps with QQS Panel
+        ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
+                px(R.dimen.qqs_layout_padding_bottom)
+        val childHunView = createHunViewMock(
+                isShadeOpen = true,
+                fullyVisible = false,
+                headerVisibleAmount = 1f
+        )
+        // Use half of the HUN's height as overlap
+        childHunView.viewState.yTranslation = (childHunView.viewState.height + 1 shr 1).toFloat()
+        val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+        algorithmState.visibleChildren.add(childHunView)
+
+        // When: updateChildZValue() is called for the top HUN
+        stackScrollAlgorithm.updateChildZValue(
+                /* i= */ 0,
+                /* childrenOnTop= */ 0.0f,
+                /* StackScrollAlgorithmState= */ algorithmState,
+                /* ambientState= */ ambientState,
+                /* shouldElevateHun= */ true
+        )
+
+        // Then: HUN should have shadow, but not as full size
+        assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
+        assertThat(childHunView.viewState.zTranslation)
+                .isLessThan(px(R.dimen.heads_up_pinned_elevation))
+    }
+
+    @Test
+    fun shadeOpened_hunDoesNotOverlapQQS_hunShouldHaveNoShadow() {
+        // Given: shade is opened, yTranslation of HUN is equal to QQS Panel's height,
+        // the height of HUN is equal to the height of QQS Panel,
+        // and HUN doesn't overlap with QQS Panel
+        ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
+                px(R.dimen.qqs_layout_padding_bottom)
+        // Mock the height of shade
+        ambientState.setLayoutMinHeight(1000)
+        val childHunView = createHunViewMock(
+                isShadeOpen = true,
+                fullyVisible = true,
+                headerVisibleAmount = 1f
+        )
+        // HUN doesn't overlap with QQS Panel
+        childHunView.viewState.yTranslation = ambientState.topPadding +
+                ambientState.stackTranslation
+        val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+        algorithmState.visibleChildren.add(childHunView)
+
+        // When: updateChildZValue() is called for the top HUN
+        stackScrollAlgorithm.updateChildZValue(
+                /* i= */ 0,
+                /* childrenOnTop= */ 0.0f,
+                /* StackScrollAlgorithmState= */ algorithmState,
+                /* ambientState= */ ambientState,
+                /* shouldElevateHun= */ true
+        )
+
+        // Then: HUN should not have shadow
+        assertEquals(0f, childHunView.viewState.zTranslation)
+    }
+
+    @Test
+    fun shadeClosed_hunShouldHaveFullShadow() {
+        // Given: shade is closed, ambientState.stackTranslation == -ambientState.topPadding,
+        // the height of HUN is equal to the height of QQS Panel,
+        ambientState.stackTranslation = -ambientState.topPadding
+        // Mock the height of shade
+        ambientState.setLayoutMinHeight(1000)
+        val childHunView = createHunViewMock(
+                isShadeOpen = false,
+                fullyVisible = false,
+                headerVisibleAmount = 0f
+        )
+        childHunView.viewState.yTranslation = 0f
+        // Shade is closed, thus childHunView's headerVisibleAmount is 0
+        childHunView.headerVisibleAmount = 0f
+        val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+        algorithmState.visibleChildren.add(childHunView)
+
+        // When: updateChildZValue() is called for the top HUN
+        stackScrollAlgorithm.updateChildZValue(
+                /* i= */ 0,
+                /* childrenOnTop= */ 0.0f,
+                /* StackScrollAlgorithmState= */ algorithmState,
+                /* ambientState= */ ambientState,
+                /* shouldElevateHun= */ true
+        )
+
+        // Then: HUN should have full shadow
+        assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation)
+    }
+
+    @Test
+    fun draggingHunToOpenShade_hunShouldHavePartialShadow() {
+        // Given: shade is closed when HUN pops up,
+        // now drags down the HUN to open shade
+        ambientState.stackTranslation = -ambientState.topPadding
+        // Mock the height of shade
+        ambientState.setLayoutMinHeight(1000)
+        val childHunView = createHunViewMock(
+                isShadeOpen = false,
+                fullyVisible = false,
+                headerVisibleAmount = 0.5f
+        )
+        childHunView.viewState.yTranslation = 0f
+        // Shade is being opened, thus childHunView's headerVisibleAmount is between 0 and 1
+        // use 0.5 as headerVisibleAmount here
+        childHunView.headerVisibleAmount = 0.5f
+        val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+        algorithmState.visibleChildren.add(childHunView)
+
+        // When: updateChildZValue() is called for the top HUN
+        stackScrollAlgorithm.updateChildZValue(
+                /* i= */ 0,
+                /* childrenOnTop= */ 0.0f,
+                /* StackScrollAlgorithmState= */ algorithmState,
+                /* ambientState= */ ambientState,
+                /* shouldElevateHun= */ true
+        )
+
+        // Then: HUN should have shadow, but not as full size
+        assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
+        assertThat(childHunView.viewState.zTranslation)
+                .isLessThan(px(R.dimen.heads_up_pinned_elevation))
+    }
+
+    private fun createHunViewMock(
+            isShadeOpen: Boolean,
+            fullyVisible: Boolean,
+            headerVisibleAmount: Float
+    ) =
+            mock<ExpandableNotificationRow>().apply {
+                val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible)
+                whenever(this.viewState).thenReturn(childViewStateMock)
+
+                whenever(this.mustStayOnScreen()).thenReturn(true)
+                whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount)
+            }
+
+
+    private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) =
+            ExpandableViewState().apply {
+                // Mock the HUN's height with ambientState.topPadding +
+                // ambientState.stackTranslation
+                height = (ambientState.topPadding + ambientState.stackTranslation).toInt()
+                if (isShadeOpen && fullyVisible) {
+                    yTranslation =
+                            ambientState.topPadding + ambientState.stackTranslation
+                } else {
+                    yTranslation = 0f
+                }
+                headsUpIsVisible = fullyVisible
+            }
+
     private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
             expansionFraction: Float,
             expectedAlpha: Float
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..868ae2b 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
@@ -41,6 +41,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.logging.BiometricUnlockLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dump.DumpManager;
@@ -75,6 +76,8 @@
     @Mock
     private KeyguardUpdateMonitor mUpdateMonitor;
     @Mock
+    private KeyguardUpdateMonitor.StrongAuthTracker mStrongAuthTracker;
+    @Mock
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock
     private NotificationShadeWindowController mNotificationShadeWindowController;
@@ -112,6 +115,8 @@
     private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock
     private VibratorHelper mVibratorHelper;
+    @Mock
+    private BiometricUnlockLogger mLogger;
     private BiometricUnlockController mBiometricUnlockController;
 
     @Before
@@ -131,33 +136,34 @@
                 mKeyguardViewMediator, mScrimController,
                 mNotificationShadeWindowController, mKeyguardStateController, mHandler,
                 mUpdateMonitor, res.getResources(), mKeyguardBypassController,
-                mMetricsLogger, mDumpManager, mPowerManager,
+                mMetricsLogger, mDumpManager, mPowerManager, mLogger,
                 mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
                 mAuthController, mStatusBarStateController,
                 mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
-        mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
+        mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener);
+        when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker);
     }
 
     @Test
-    public void onBiometricAuthenticated_whenFingerprintAndBiometricsDisallowed_showBouncer() {
+    public void onBiometricAuthenticated_fingerprintAndBiometricsDisallowed_showPrimaryBouncer() {
         when(mUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */))
                 .thenReturn(false);
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
-        verify(mStatusBarKeyguardViewManager).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
         verify(mStatusBarKeyguardViewManager, never()).notifyKeyguardAuthenticated(anyBoolean());
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
     }
 
     @Test
-    public void onBiometricAuthenticated_whenFingerprint_nonStrongBioDisallowed_showBouncer() {
+    public void onBiometricAuthenticated_fingerprint_nonStrongBioDisallowed_showPrimaryBouncer() {
         when(mUpdateMonitor.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
                 .thenReturn(false);
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FINGERPRINT, false /* isStrongBiometric */);
-        verify(mStatusBarKeyguardViewManager).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
         assertThat(mBiometricUnlockController.getBiometricType())
@@ -207,7 +213,7 @@
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
 
-        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(BiometricUnlockController.MODE_UNLOCK_COLLAPSING);
@@ -216,7 +222,7 @@
     @Test
     public void onBiometricAuthenticated_whenFingerprintOnBouncer_dismissBouncer() {
         when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
-        when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
         // the value of isStrongBiometric doesn't matter here since we only care about the returned
         // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
@@ -274,8 +280,9 @@
     }
 
     @Test
-    public void onBiometricAuthenticated_whenFace_andBypass_encrypted_showBouncer() {
+    public void onBiometricAuthenticated_whenFace_andBypass_encrypted_showPrimaryBouncer() {
         reset(mUpdateMonitor);
+        when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker);
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
 
@@ -285,7 +292,7 @@
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FACE, true /* isStrongBiometric */);
 
-        verify(mStatusBarKeyguardViewManager).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
     }
@@ -306,6 +313,7 @@
     @Test
     public void onBiometricAuthenticated_whenFace_noBypass_encrypted_doNothing() {
         reset(mUpdateMonitor);
+        when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
 
         when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
@@ -314,7 +322,7 @@
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FACE, true /* isStrongBiometric */);
 
-        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(BiometricUnlockController.MODE_NONE);
     }
@@ -322,7 +330,7 @@
     @Test
     public void onBiometricAuthenticated_whenFaceOnBouncer_dismissBouncer() {
         when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
-        when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
         // the value of isStrongBiometric doesn't matter here since we only care about the returned
         // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
@@ -342,7 +350,7 @@
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
         when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean()))
                 .thenReturn(true);
-        when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
         // the value of isStrongBiometric doesn't matter here since we only care about the returned
         // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
@@ -369,23 +377,23 @@
     }
 
     @Test
-    public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() {
+    public void onUdfpsConsecutivelyFailedThreeTimes_showPrimaryBouncer() {
         // GIVEN UDFPS is supported
         when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true);
 
         // WHEN udfps fails once - then don't show the bouncer yet
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
 
         // WHEN udfps fails the second time - then don't show the bouncer yet
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
 
         // WHEN udpfs fails the third time
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
 
         // THEN show the bouncer
-        verify(mStatusBarKeyguardViewManager).showBouncer(true);
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
     }
 
     @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..c17c5b0 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;
 
@@ -41,6 +41,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
@@ -60,6 +61,8 @@
 
 import java.util.Optional;
 
+import dagger.Lazy;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
@@ -84,6 +87,7 @@
     @Mock private Vibrator mVibrator;
     @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     @Mock private SystemBarAttributesListener mSystemBarAttributesListener;
+    @Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
 
     CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
 
@@ -115,7 +119,8 @@
                 Optional.of(mVibrator),
                 new DisableFlagsLogger(),
                 DEFAULT_DISPLAY,
-                mSystemBarAttributesListener);
+                mSystemBarAttributesListener,
+                mCameraLauncherLazy);
 
         when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
         when(mRemoteInputQuickSettingsDisabler.adjustDisableFlags(anyInt()))
@@ -131,7 +136,7 @@
                 StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
 
         verify(mCentralSurfaces).updateQsExpansionEnabled();
-        verify(mShadeController).animateCollapsePanels();
+        verify(mShadeController).animateCollapseShade();
 
         // Trying to open it does nothing.
         mSbcqCallbacks.animateExpandNotificationsPanel();
@@ -149,7 +154,7 @@
         mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
                 StatusBarManager.DISABLE2_NONE, false);
         verify(mCentralSurfaces).updateQsExpansionEnabled();
-        verify(mShadeController, never()).animateCollapsePanels();
+        verify(mShadeController, never()).animateCollapseShade();
 
         // Can now be opened.
         mSbcqCallbacks.animateExpandNotificationsPanel();
@@ -177,7 +182,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 +192,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails);
 
@@ -197,7 +202,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails
         );
@@ -209,7 +214,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 +224,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..521e518 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
@@ -106,12 +106,15 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.PluginDependencyProvider;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.NotificationPanelView;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowView;
@@ -119,7 +122,6 @@
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeControllerImpl;
 import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -211,6 +213,7 @@
     @Mock private NotificationPanelView mNotificationPanelView;
     @Mock private IStatusBarService mBarService;
     @Mock private IDreamManager mDreamManager;
+    @Mock private LightRevealScrimViewModel mLightRevealScrimViewModel;
     @Mock private ScrimController mScrimController;
     @Mock private DozeScrimController mDozeScrimController;
     @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
@@ -220,6 +223,7 @@
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
     @Mock private StatusBarStateControllerImpl mStatusBarStateController;
+    @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
     @Mock private BatteryController mBatteryController;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private StatusBarNotificationPresenter mNotificationPresenter;
@@ -287,6 +291,8 @@
     @Mock private InteractionJankMonitor mJankMonitor;
     @Mock private DeviceStateManager mDeviceStateManager;
     @Mock private WiredChargingRippleController mWiredChargingRippleController;
+    @Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
+    @Mock private CameraLauncher mCameraLauncher;
     /**
      * The process of registering/unregistering a predictive back callback requires a
      * ViewRootImpl, which is present IRL, but may be missing during a Mockito unit test.
@@ -339,6 +345,7 @@
                 mVisibilityProvider,
                 mock(NotifPipeline.class),
                 mStatusBarStateController,
+                mShadeExpansionStateManager,
                 mExpansionStateLogger,
                 new NotificationPanelLoggerFake()
         );
@@ -350,7 +357,6 @@
 
         when(mStackScrollerController.getView()).thenReturn(mStackScroller);
         when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
-        when(mNotificationPanelViewController.getView()).thenReturn(mNotificationPanelView);
         when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
         when(powerManagerService.isInteractive()).thenReturn(true);
         when(mStackScroller.getActivatedChild()).thenReturn(null);
@@ -378,6 +384,7 @@
 
         when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
         when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
+        when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher);
 
         when(mStatusBarComponentFactory.create()).thenReturn(mCentralSurfacesComponent);
         when(mCentralSurfacesComponent.getNotificationShadeWindowViewController()).thenReturn(
@@ -387,10 +394,21 @@
             return null;
         }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
 
-        mShadeController = spy(new ShadeControllerImpl(mCommandQueue,
-                mStatusBarStateController, mNotificationShadeWindowController,
-                mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
-                () -> Optional.of(mCentralSurfaces), () -> mAssistManager));
+        mShadeController = spy(new ShadeControllerImpl(
+                mCommandQueue,
+                mKeyguardStateController,
+                mStatusBarStateController,
+                mStatusBarKeyguardViewManager,
+                mStatusBarWindowController,
+                mNotificationShadeWindowController,
+                mContext.getSystemService(WindowManager.class),
+                () -> mAssistManager,
+                () -> mNotificationGutsManager
+        ));
+        mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+        mShadeController.setNotificationShadeWindowViewController(
+                mNotificationShadeWindowViewController);
+        mShadeController.setNotificationPresenter(mNotificationPresenter);
 
         when(mOperatorNameViewControllerFactory.create(any()))
                 .thenReturn(mOperatorNameViewController);
@@ -479,12 +497,16 @@
                 mActivityLaunchAnimator,
                 mJankMonitor,
                 mDeviceStateManager,
-                mWiredChargingRippleController, mDreamManager) {
+                mWiredChargingRippleController,
+                mDreamManager,
+                mCameraLauncherLazy,
+                () -> mLightRevealScrimViewModel) {
             @Override
             protected ViewRootImpl getViewRootImpl() {
                 return mViewRootImpl;
             }
         };
+        mCentralSurfaces.initShadeVisibilityListener();
         when(mViewRootImpl.getOnBackInvokedDispatcher())
                 .thenReturn(mOnBackInvokedDispatcher);
         when(mKeyguardViewMediator.registerCentralSurfaces(
@@ -500,7 +522,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;
@@ -800,7 +822,7 @@
 
         when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
         mOnBackInvokedCallback.getValue().onBackInvoked();
-        verify(mShadeController).animateCollapsePanels();
+        verify(mShadeController).animateCollapseShade();
     }
 
     @Test
@@ -891,7 +913,7 @@
         mCentralSurfaces.showKeyguardImpl();
 
         // Starting a pulse should change the scrim controller to the pulsing state
-        when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(true);
+        when(mCameraLauncher.isLaunchingAffordance()).thenReturn(true);
         mCentralSurfaces.updateScrimController();
         verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
     }
@@ -927,7 +949,7 @@
         mCentralSurfaces.showKeyguardImpl();
 
         // Starting a pulse should change the scrim controller to the pulsing state
-        when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(false);
+        when(mCameraLauncher.isLaunchingAffordance()).thenReturn(false);
         mCentralSurfaces.updateScrimController();
         verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
     }
@@ -1023,57 +1045,57 @@
     }
 
     @Test
-    public void collapseShade_callsAnimateCollapsePanels_whenExpanded() {
+    public void collapseShade_callsanimateCollapseShade_whenExpanded() {
         // GIVEN the shade is expanded
-        mCentralSurfaces.setPanelExpanded(true);
+        mCentralSurfaces.onShadeExpansionFullyChanged(true);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
 
         // WHEN collapseShade is called
         mCentralSurfaces.collapseShade();
 
-        // VERIFY that animateCollapsePanels is called
-        verify(mShadeController).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is called
+        verify(mShadeController).animateCollapseShade();
     }
 
     @Test
-    public void collapseShade_doesNotCallAnimateCollapsePanels_whenCollapsed() {
+    public void collapseShade_doesNotCallanimateCollapseShade_whenCollapsed() {
         // GIVEN the shade is collapsed
-        mCentralSurfaces.setPanelExpanded(false);
+        mCentralSurfaces.onShadeExpansionFullyChanged(false);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
 
         // WHEN collapseShade is called
         mCentralSurfaces.collapseShade();
 
-        // VERIFY that animateCollapsePanels is NOT called
-        verify(mShadeController, never()).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is NOT called
+        verify(mShadeController, never()).animateCollapseShade();
     }
 
     @Test
-    public void collapseShadeForBugReport_callsAnimateCollapsePanels_whenFlagDisabled() {
+    public void collapseShadeForBugReport_callsanimateCollapseShade_whenFlagDisabled() {
         // GIVEN the shade is expanded & flag enabled
-        mCentralSurfaces.setPanelExpanded(true);
+        mCentralSurfaces.onShadeExpansionFullyChanged(true);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
         mFeatureFlags.set(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, false);
 
         // WHEN collapseShadeForBugreport is called
         mCentralSurfaces.collapseShadeForBugreport();
 
-        // VERIFY that animateCollapsePanels is called
-        verify(mShadeController).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is called
+        verify(mShadeController).animateCollapseShade();
     }
 
     @Test
-    public void collapseShadeForBugReport_doesNotCallAnimateCollapsePanels_whenFlagEnabled() {
+    public void collapseShadeForBugReport_doesNotCallanimateCollapseShade_whenFlagEnabled() {
         // GIVEN the shade is expanded & flag enabled
-        mCentralSurfaces.setPanelExpanded(true);
+        mCentralSurfaces.onShadeExpansionFullyChanged(true);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
         mFeatureFlags.set(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, true);
 
         // WHEN collapseShadeForBugreport is called
         mCentralSurfaces.collapseShadeForBugreport();
 
-        // VERIFY that animateCollapsePanels is called
-        verify(mShadeController, never()).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is called
+        verify(mShadeController, never()).animateCollapseShade();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index fee3ccb..6155e3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -14,23 +14,38 @@
 
 package com.android.systemui.statusbar.phone
 
-import androidx.test.filters.SmallTest
+import android.content.res.Configuration
+import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_LTR
+import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_RTL
+import android.content.res.Configuration.UI_MODE_NIGHT_NO
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.content.res.Configuration.UI_MODE_TYPE_CAR
+import android.os.LocaleList
 import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import java.util.Locale
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class ConfigurationControllerImplTest : SysuiTestCase() {
 
-    private val mConfigurationController =
-            com.android.systemui.statusbar.phone.ConfigurationControllerImpl(mContext)
+    private lateinit var mConfigurationController: ConfigurationControllerImpl
+
+    @Before
+    fun setUp() {
+        mConfigurationController = ConfigurationControllerImpl(mContext)
+    }
 
     @Test
     fun testThemeChange() {
@@ -57,4 +72,304 @@
         verify(listener).onThemeChanged()
         verify(listener2, never()).onThemeChanged()
     }
+
+    @Test
+    fun configChanged_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.densityDpi = 12
+        config.smallestScreenWidthDp = 240
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the config is updated
+        config.densityDpi = 20
+        config.smallestScreenWidthDp = 300
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.changedConfig?.densityDpi).isEqualTo(20)
+        assertThat(listener.changedConfig?.smallestScreenWidthDp).isEqualTo(300)
+    }
+
+    @Test
+    fun densityChanged_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.densityDpi = 12
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the density is updated
+        config.densityDpi = 20
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.densityOrFontScaleChanged).isTrue()
+    }
+
+    @Test
+    fun fontChanged_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.fontScale = 1.5f
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the font is updated
+        config.fontScale = 1.4f
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.densityOrFontScaleChanged).isTrue()
+    }
+
+    @Test
+    fun isCarAndUiModeChanged_densityListenerNotified() {
+        val config = mContext.resources.configuration
+        config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_YES
+        // Re-create the controller since we calculate car mode on creation
+        mConfigurationController = ConfigurationControllerImpl(mContext)
+
+        val listener = createAndAddListener()
+
+        // WHEN the ui mode is updated
+        config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_NO
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.densityOrFontScaleChanged).isTrue()
+    }
+
+    @Test
+    fun isNotCarAndUiModeChanged_densityListenerNotNotified() {
+        val config = mContext.resources.configuration
+        config.uiMode = UI_MODE_NIGHT_YES
+        // Re-create the controller since we calculate car mode on creation
+        mConfigurationController = ConfigurationControllerImpl(mContext)
+
+        val listener = createAndAddListener()
+
+        // WHEN the ui mode is updated
+        config.uiMode = UI_MODE_NIGHT_NO
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is not notified because it's not car mode
+        assertThat(listener.densityOrFontScaleChanged).isFalse()
+    }
+
+    @Test
+    fun smallestScreenWidthChanged_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.smallestScreenWidthDp = 240
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the width is updated
+        config.smallestScreenWidthDp = 300
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.smallestScreenWidthChanged).isTrue()
+    }
+
+    @Test
+    fun maxBoundsChange_newConfigObject_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.windowConfiguration.setMaxBounds(0, 0, 200, 200)
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN a new configuration object with new bounds is sent
+        val newConfig = Configuration()
+        newConfig.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+        mConfigurationController.onConfigurationChanged(newConfig)
+
+        // THEN the listener is notified
+        assertThat(listener.maxBoundsChanged).isTrue()
+    }
+
+    // Regression test for b/245799099
+    @Test
+    fun maxBoundsChange_sameObject_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.windowConfiguration.setMaxBounds(0, 0, 200, 200)
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the existing config is updated with new bounds
+        config.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.maxBoundsChanged).isTrue()
+    }
+
+
+    @Test
+    fun localeListChanged_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.locales = LocaleList(Locale.CANADA, Locale.GERMANY)
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the locales are updated
+        config.locales = LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE)
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.localeListChanged).isTrue()
+    }
+
+    @Test
+    fun uiModeChanged_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.uiMode = UI_MODE_NIGHT_YES
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the ui mode is updated
+        config.uiMode = UI_MODE_NIGHT_NO
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.uiModeChanged).isTrue()
+    }
+
+    @Test
+    fun layoutDirectionUpdated_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.screenLayout = SCREENLAYOUT_LAYOUTDIR_LTR
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the layout is updated
+        config.screenLayout = SCREENLAYOUT_LAYOUTDIR_RTL
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.layoutDirectionChanged).isTrue()
+    }
+
+    @Test
+    fun assetPathsUpdated_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.assetsSeq = 45
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the assets sequence is updated
+        config.assetsSeq = 46
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.themeChanged).isTrue()
+    }
+
+    @Test
+    fun multipleUpdates_listenerNotifiedOfAll() {
+        val config = mContext.resources.configuration
+        config.densityDpi = 14
+        config.windowConfiguration.setMaxBounds(0, 0, 2, 2)
+        config.uiMode = UI_MODE_NIGHT_YES
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN multiple fields are updated
+        config.densityDpi = 20
+        config.windowConfiguration.setMaxBounds(0, 0, 3, 3)
+        config.uiMode = UI_MODE_NIGHT_NO
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified of all of them
+        assertThat(listener.densityOrFontScaleChanged).isTrue()
+        assertThat(listener.maxBoundsChanged).isTrue()
+        assertThat(listener.uiModeChanged).isTrue()
+    }
+
+    @Test
+    @Ignore("b/261408895")
+    fun equivalentConfigObject_listenerNotNotified() {
+        val config = mContext.resources.configuration
+        val listener = createAndAddListener()
+
+        // WHEN we update with the new object that has all the same fields
+        mConfigurationController.onConfigurationChanged(Configuration(config))
+
+        listener.assertNoMethodsCalled()
+    }
+
+    private fun createAndAddListener(): TestListener {
+        val listener = TestListener()
+        mConfigurationController.addCallback(listener)
+        // Adding a listener can trigger some callbacks, so we want to reset the values right
+        // after the listener is added
+        listener.reset()
+        return listener
+    }
+
+    private class TestListener : ConfigurationListener {
+        var changedConfig: Configuration? = null
+        var densityOrFontScaleChanged = false
+        var smallestScreenWidthChanged = false
+        var maxBoundsChanged = false
+        var uiModeChanged = false
+        var themeChanged = false
+        var localeListChanged = false
+        var layoutDirectionChanged = false
+
+        override fun onConfigChanged(newConfig: Configuration?) {
+            changedConfig = newConfig
+        }
+        override fun onDensityOrFontScaleChanged() {
+            densityOrFontScaleChanged = true
+        }
+        override fun onSmallestScreenWidthChanged() {
+            smallestScreenWidthChanged = true
+        }
+        override fun onMaxBoundsChanged() {
+            maxBoundsChanged = true
+        }
+        override fun onUiModeChanged() {
+            uiModeChanged = true
+        }
+        override fun onThemeChanged() {
+            themeChanged = true
+        }
+        override fun onLocaleListChanged() {
+            localeListChanged = true
+        }
+        override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
+            layoutDirectionChanged = true
+        }
+
+        fun assertNoMethodsCalled() {
+            assertThat(densityOrFontScaleChanged).isFalse()
+            assertThat(smallestScreenWidthChanged).isFalse()
+            assertThat(maxBoundsChanged).isFalse()
+            assertThat(uiModeChanged).isFalse()
+            assertThat(themeChanged).isFalse()
+            assertThat(localeListChanged).isFalse()
+            assertThat(layoutDirectionChanged).isFalse()
+        }
+
+        fun reset() {
+            changedConfig = null
+            densityOrFontScaleChanged = false
+            smallestScreenWidthChanged = false
+            maxBoundsChanged = false
+            uiModeChanged = false
+            themeChanged = false
+            localeListChanged = false
+            layoutDirectionChanged = false
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 103b7b42..9727b6c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -32,6 +32,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
@@ -40,6 +41,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -71,6 +73,8 @@
     private NotificationWakeUpCoordinator mWakeUpCoordinator;
     private KeyguardStateController mKeyguardStateController;
     private CommandQueue mCommandQueue;
+    private NotificationRoundnessManager mNotificationRoundnessManager;
+    private FeatureFlags mFeatureFlag;
 
     @Before
     public void setUp() throws Exception {
@@ -89,6 +93,8 @@
         mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
         mKeyguardStateController = mock(KeyguardStateController.class);
         mCommandQueue = mock(CommandQueue.class);
+        mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
+        mFeatureFlag = mock(FeatureFlags.class);
         mHeadsUpAppearanceController = new HeadsUpAppearanceController(
                 mock(NotificationIconAreaController.class),
                 mHeadsUpManager,
@@ -100,6 +106,8 @@
                 mCommandQueue,
                 mStackScrollerController,
                 mPanelView,
+                mNotificationRoundnessManager,
+                mFeatureFlag,
                 mHeadsUpStatusBarView,
                 new Clock(mContext, null),
                 Optional.of(mOperatorNameView));
@@ -182,6 +190,8 @@
                 mCommandQueue,
                 mStackScrollerController,
                 mPanelView,
+                mNotificationRoundnessManager,
+                mFeatureFlag,
                 mHeadsUpStatusBarView,
                 new Clock(mContext, null),
                 Optional.empty());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index e252401..780e0c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -31,6 +31,7 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -67,6 +68,7 @@
     @Mock private KeyguardBypassController mBypassController;
     @Mock private ConfigurationControllerImpl mConfigurationController;
     @Mock private AccessibilityManagerWrapper mAccessibilityManagerWrapper;
+    @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
     @Mock private UiEventLogger mUiEventLogger;
     private boolean mLivesPastNormalTime;
 
@@ -81,7 +83,8 @@
                 ConfigurationController configurationController,
                 Handler handler,
                 AccessibilityManagerWrapper accessibilityManagerWrapper,
-                UiEventLogger uiEventLogger
+                UiEventLogger uiEventLogger,
+                ShadeExpansionStateManager shadeExpansionStateManager
         ) {
             super(
                     context,
@@ -93,7 +96,8 @@
                     configurationController,
                     handler,
                     accessibilityManagerWrapper,
-                    uiEventLogger
+                    uiEventLogger,
+                    shadeExpansionStateManager
             );
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
@@ -125,7 +129,8 @@
                 mConfigurationController,
                 mTestHandler,
                 mAccessibilityManagerWrapper,
-                mUiEventLogger
+                mUiEventLogger,
+                mShadeExpansionStateManager
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index ab209d1..df7ee43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -39,6 +39,7 @@
 
 import android.content.res.ColorStateList;
 import android.graphics.Color;
+import android.hardware.biometrics.BiometricSourceType;
 import android.os.Handler;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -58,7 +59,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
+import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Assert;
@@ -86,7 +87,7 @@
     @Mock
     private KeyguardHostViewController mKeyguardHostViewController;
     @Mock
-    private BouncerExpansionCallback mExpansionCallback;
+    private KeyguardBouncer.PrimaryBouncerExpansionCallback mExpansionCallback;
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
@@ -398,6 +399,8 @@
 
     @Test
     public void testShow_delaysIfFaceAuthIsRunning() {
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+                .thenReturn(true);
         when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
         mBouncer.show(true /* reset */);
 
@@ -410,9 +413,10 @@
     }
 
     @Test
-    public void testShow_doesNotDelaysIfFaceAuthIsLockedOut() {
+    public void testShow_doesNotDelaysIfFaceAuthIsNotAllowed() {
         when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+                .thenReturn(false);
         mBouncer.show(true /* reset */);
 
         verify(mHandler, never()).postDelayed(any(), anyLong());
@@ -476,7 +480,8 @@
         mBouncer.ensureView();
         mBouncer.setExpansion(0.5f);
 
-        final BouncerExpansionCallback callback = mock(BouncerExpansionCallback.class);
+        final PrimaryBouncerExpansionCallback callback =
+                mock(PrimaryBouncerExpansionCallback.class);
         mBouncer.addBouncerExpansionCallback(callback);
 
         mBouncer.setExpansion(EXPANSION_HIDDEN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 6ec5cf8..eb0b9b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -56,14 +56,12 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -112,16 +110,12 @@
     private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider;
     @Mock
     private UserManager mUserManager;
+    @Mock
+    private StatusBarUserChipViewModel mStatusBarUserChipViewModel;
     @Captor
     private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor;
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
-    @Mock
-    private StatusBarUserSwitcherFeatureController mStatusBarUserSwitcherFeatureController;
-    @Mock
-    private StatusBarUserSwitcherController mStatusBarUserSwitcherController;
-    @Mock
-    private StatusBarUserInfoTracker mStatusBarUserInfoTracker;
     @Mock private SecureSettings mSecureSettings;
     @Mock private CommandQueue mCommandQueue;
     @Mock private KeyguardLogger mLogger;
@@ -169,9 +163,7 @@
                 mStatusBarStateController,
                 mStatusBarContentInsetsProvider,
                 mUserManager,
-                mStatusBarUserSwitcherFeatureController,
-                mStatusBarUserSwitcherController,
-                mStatusBarUserInfoTracker,
+                mStatusBarUserChipViewModel,
                 mSecureSettings,
                 mCommandQueue,
                 mFakeExecutor,
@@ -479,8 +471,7 @@
     @Test
     public void testNewUserSwitcherDisablesAvatar_newUiOn() {
         // GIVEN the status bar user switcher chip is enabled
-        when(mStatusBarUserSwitcherFeatureController.isStatusBarUserSwitcherFeatureEnabled())
-                .thenReturn(true);
+        when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(true);
 
         // WHEN the controller is created
         mController = createController();
@@ -492,8 +483,7 @@
     @Test
     public void testNewUserSwitcherDisablesAvatar_newUiOff() {
         // GIVEN the status bar user switcher chip is disabled
-        when(mStatusBarUserSwitcherFeatureController.isStatusBarUserSwitcherFeatureEnabled())
-                .thenReturn(false);
+        when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(false);
 
         // WHEN the controller is created
         mController = createController();
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/ManagedProfileControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
new file mode 100644
index 0000000..7eba3b46
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.phone
+
+import android.content.pm.UserInfo
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import junit.framework.Assert
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class ManagedProfileControllerImplTest : SysuiTestCase() {
+
+    private val mainExecutor: FakeExecutor = FakeExecutor(FakeSystemClock())
+
+    private lateinit var controller: ManagedProfileControllerImpl
+
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var userManager: UserManager
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        controller = ManagedProfileControllerImpl(context, mainExecutor, userTracker, userManager)
+    }
+
+    @Test
+    fun hasWorkingProfile_isWorkModeEnabled_returnsTrue() {
+        `when`(userTracker.userId).thenReturn(1)
+        setupWorkingProfile(1)
+
+        Assert.assertEquals(true, controller.hasActiveProfile())
+    }
+
+    @Test
+    fun noWorkingProfile_isWorkModeEnabled_returnsFalse() {
+        `when`(userTracker.userId).thenReturn(1)
+
+        Assert.assertEquals(false, controller.hasActiveProfile())
+    }
+
+    @Test
+    fun listeningUserChanges_isWorkModeEnabled_returnsTrue() {
+        `when`(userTracker.userId).thenReturn(1)
+        controller.addCallback(TestCallback)
+        `when`(userTracker.userId).thenReturn(2)
+        setupWorkingProfile(2)
+
+        Assert.assertEquals(true, controller.hasActiveProfile())
+    }
+
+    private fun setupWorkingProfile(userId: Int) {
+        `when`(userManager.getEnabledProfiles(userId))
+            .thenReturn(
+                listOf(UserInfo(userId, "test_user", "", 0, UserManager.USER_TYPE_PROFILE_MANAGED))
+            )
+    }
+
+    private object TestCallback : ManagedProfileController.Callback {
+
+        override fun onManagedProfileChanged() = Unit
+
+        override fun onManagedProfileRemoved() = Unit
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 086e5df..b80b825 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -92,7 +92,7 @@
 
         iconContainer.calculateIconXTranslations()
         assertEquals(10f, iconState.xTranslation)
-        assertFalse(iconContainer.hasOverflow())
+        assertFalse(iconContainer.areIconsOverflowing())
     }
 
     @Test
@@ -121,7 +121,7 @@
         assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation)
         assertEquals(40f, iconContainer.getIconState(iconFour).xTranslation)
 
-        assertFalse(iconContainer.hasOverflow())
+        assertFalse(iconContainer.areIconsOverflowing())
     }
 
     @Test
@@ -150,7 +150,7 @@
         assertEquals(10f, iconContainer.getIconState(iconOne).xTranslation)
         assertEquals(20f, iconContainer.getIconState(iconTwo).xTranslation)
         assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation)
-        assertTrue(iconContainer.hasOverflow())
+        assertTrue(iconContainer.areIconsOverflowing())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index a61fba5..14d239a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -27,11 +27,13 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.shade.NotificationPanelViewController
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
+import com.android.systemui.shade.ShadeControllerImpl
+import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.unfold.SysUIUnfoldComponent
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.view.ViewUtil
 import com.google.common.truth.Truth.assertThat
@@ -41,6 +43,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -49,8 +52,6 @@
 @SmallTest
 class PhoneStatusBarViewControllerTest : SysuiTestCase() {
 
-    private val touchEventHandler = TestTouchEventHandler()
-
     @Mock
     private lateinit var notificationPanelViewController: NotificationPanelViewController
     @Mock
@@ -64,7 +65,13 @@
     @Mock
     private lateinit var configurationController: ConfigurationController
     @Mock
-    private lateinit var userSwitcherController: StatusBarUserSwitcherController
+    private lateinit var userChipViewModel: StatusBarUserChipViewModel
+    @Mock
+    private lateinit var centralSurfacesImpl: CentralSurfacesImpl
+    @Mock
+    private lateinit var shadeControllerImpl: ShadeControllerImpl
+    @Mock
+    private lateinit var shadeLogger: ShadeLogger
     @Mock
     private lateinit var viewUtil: ViewUtil
 
@@ -76,29 +83,15 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        `when`(notificationPanelViewController.view).thenReturn(panelView)
         `when`(sysuiUnfoldComponent.getStatusBarMoveFromCenterAnimationController())
             .thenReturn(moveFromCenterAnimation)
-        // create the view on main thread as it requires main looper
+        // create the view and controller on main thread as it requires main looper
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
             val parent = FrameLayout(mContext) // add parent to keep layout params
             view = LayoutInflater.from(mContext)
                 .inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
+            controller = createAndInitController(view)
         }
-
-        controller = createAndInitController(view)
-    }
-
-    @Test
-    fun constructor_setsTouchHandlerOnView() {
-        val interceptEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 10f, 10f, 0)
-        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
-
-        view.onInterceptTouchEvent(interceptEvent)
-        view.onTouchEvent(event)
-
-        assertThat(touchEventHandler.lastInterceptEvent).isEqualTo(interceptEvent)
-        assertThat(touchEventHandler.lastEvent).isEqualTo(event)
     }
 
     @Test
@@ -106,7 +99,10 @@
         val view = createViewMock()
         val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
         unfoldConfig.isEnabled = true
-        controller = createAndInitController(view)
+        // create the controller on main thread as it requires main looper
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            controller = createAndInitController(view)
+        }
 
         verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
         argumentCaptor.value.onPreDraw()
@@ -114,6 +110,66 @@
         verify(moveFromCenterAnimation).onViewsReady(any())
     }
 
+    @Test
+    fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
+        `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false)
+        val returnVal = view.onTouchEvent(
+                        MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+        assertThat(returnVal).isFalse()
+        verify(notificationPanelViewController, never()).sendTouchEventToView(any())
+    }
+
+    @Test
+    fun handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() {
+        `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
+        `when`(centralSurfacesImpl.notificationPanelViewController)
+                .thenReturn(notificationPanelViewController)
+        `when`(notificationPanelViewController.isViewEnabled).thenReturn(false)
+        val returnVal = view.onTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+        assertThat(returnVal).isTrue()
+        verify(notificationPanelViewController, never()).sendTouchEventToView(any())
+    }
+
+    @Test
+    fun handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
+        `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
+        `when`(centralSurfacesImpl.notificationPanelViewController)
+                .thenReturn(notificationPanelViewController)
+        `when`(notificationPanelViewController.isViewEnabled).thenReturn(false)
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+
+        view.onTouchEvent(event)
+
+        verify(notificationPanelViewController).sendTouchEventToView(event)
+    }
+
+    @Test
+    fun handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
+        `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
+        `when`(centralSurfacesImpl.notificationPanelViewController)
+                .thenReturn(notificationPanelViewController)
+        `when`(notificationPanelViewController.isViewEnabled).thenReturn(true)
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)
+
+        view.onTouchEvent(event)
+
+        verify(notificationPanelViewController).sendTouchEventToView(event)
+    }
+
+    @Test
+    fun handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() {
+        `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
+        `when`(centralSurfacesImpl.notificationPanelViewController)
+                .thenReturn(notificationPanelViewController)
+        `when`(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+
+        view.onTouchEvent(event)
+
+        verify(notificationPanelViewController, never()).sendTouchEventToView(any())
+    }
+
     private fun createViewMock(): PhoneStatusBarView {
         val view = spy(view)
         val viewTreeObserver = mock(ViewTreeObserver::class.java)
@@ -126,10 +182,13 @@
         return PhoneStatusBarViewController.Factory(
             Optional.of(sysuiUnfoldComponent),
             Optional.of(progressProvider),
-            userSwitcherController,
+            userChipViewModel,
+            centralSurfacesImpl,
+            shadeControllerImpl,
+            shadeLogger,
             viewUtil,
             configurationController
-        ).create(view, touchEventHandler).also {
+        ).create(view).also {
             it.init()
         }
     }
@@ -139,17 +198,4 @@
         override var isHingeAngleEnabled: Boolean = false
         override val halfFoldedTimeoutMillis: Int = 0
     }
-
-    private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler {
-        var lastEvent: MotionEvent? = null
-        var lastInterceptEvent: MotionEvent? = null
-
-        override fun onInterceptTouchEvent(event: MotionEvent?) {
-            lastInterceptEvent = event
-        }
-        override fun handleTouchEvent(event: MotionEvent?): Boolean {
-            lastEvent = event
-            return false
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 5aa7f92..27b1da0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -25,7 +25,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -41,9 +40,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        // TODO(b/197137564): Setting up a panel view and its controller feels unnecessary when
-        //   testing just [PhoneStatusBarView].
-        `when`(notificationPanelViewController.view).thenReturn(panelView)
 
         view = PhoneStatusBarView(mContext, null)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index a5deaa4..e475905 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.statusbar.phone.ScrimController.KEYGUARD_SCRIM_ALPHA;
 import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
 import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
@@ -59,7 +58,6 @@
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.statusbar.policy.FakeConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -119,7 +117,6 @@
     // TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
     //   event-dispatch-on-registration pattern caused some of these unit tests to fail.)
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock private KeyguardViewMediator mKeyguardViewMediator;
 
     private static class AnimatorListener implements Animator.AnimatorListener {
         private int mNumStarts;
@@ -233,8 +230,7 @@
                 mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
                 mScreenOffAnimationController,
                 mKeyguardUnlockAnimationController,
-                mStatusBarKeyguardViewManager,
-                mKeyguardViewMediator);
+                mStatusBarKeyguardViewManager);
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
@@ -243,8 +239,6 @@
         mScrimController.setWallpaperSupportsAmbientMode(false);
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         finishAnimationsImmediately();
-
-        mScrimController.setLaunchingAffordanceWithPreview(false);
     }
 
     @After
@@ -858,8 +852,7 @@
                 mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
                 mScreenOffAnimationController,
                 mKeyguardUnlockAnimationController,
-                mStatusBarKeyguardViewManager,
-                mKeyguardViewMediator);
+                mStatusBarKeyguardViewManager);
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
@@ -1150,7 +1143,7 @@
     }
 
     @Test
-    public void testAuthScrim_notifScrimOpaque_whenShadeFullyExpanded() {
+    public void testAuthScrim_setClipQSScrimTrue_notifScrimOpaque_whenShadeFullyExpanded() {
         // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
         // with the camera app occluding the keyguard)
         mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -1176,6 +1169,34 @@
         ));
     }
 
+
+    @Test
+    public void testAuthScrim_setClipQSScrimFalse_notifScrimOpaque_whenShadeFullyExpanded() {
+        // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
+        // with the camera app occluding the keyguard)
+        mScrimController.transitionTo(ScrimState.UNLOCKED);
+        mScrimController.setClipsQsScrim(false);
+        mScrimController.setRawPanelExpansionFraction(1);
+        // notifications scrim alpha change require calling setQsPosition
+        mScrimController.setQsPosition(0, 300);
+        finishAnimationsImmediately();
+
+        // WHEN the user triggers the auth bouncer
+        mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
+        finishAnimationsImmediately();
+
+        assertEquals("Behind scrim should be opaque",
+                mScrimBehind.getViewAlpha(), 1, 0.0);
+        assertEquals("Notifications scrim should be opaque",
+                mNotificationsScrim.getViewAlpha(), 1, 0.0);
+
+        assertScrimTinted(Map.of(
+                mScrimInFront, true,
+                mScrimBehind, true,
+                mNotificationsScrim, false
+        ));
+    }
+
     @Test
     public void testAuthScrimKeyguard() {
         // GIVEN device is on the keyguard
@@ -1280,7 +1301,7 @@
 
     @Test
     public void qsExpansion_BehindTint_shadeLocked_bouncerActive_usesBouncerProgress() {
-        when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
         // clipping doesn't change tested logic but allows to assert scrims more in line with
         // their expected large screen behaviour
         mScrimController.setClipsQsScrim(false);
@@ -1296,7 +1317,7 @@
 
     @Test
     public void expansionNotificationAlpha_shadeLocked_bouncerActive_usesBouncerInterpolator() {
-        when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
 
         mScrimController.transitionTo(SHADE_LOCKED);
 
@@ -1312,7 +1333,7 @@
 
     @Test
     public void expansionNotificationAlpha_shadeLocked_bouncerNotActive_usesShadeInterpolator() {
-        when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
 
         mScrimController.transitionTo(SHADE_LOCKED);
 
@@ -1327,7 +1348,7 @@
 
     @Test
     public void notificationAlpha_unnocclusionAnimating_bouncerActive_usesKeyguardNotifAlpha() {
-        when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
         mScrimController.setClipsQsScrim(true);
 
         mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -1349,7 +1370,7 @@
 
     @Test
     public void notificationAlpha_unnocclusionAnimating_bouncerNotActive_usesKeyguardNotifAlpha() {
-        when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
 
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         mScrimController.setUnocclusionAnimationRunning(true);
@@ -1370,7 +1391,7 @@
 
     @Test
     public void notificationAlpha_inKeyguardState_bouncerActive_usesInvertedBouncerInterpolator() {
-        when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
         mScrimController.setClipsQsScrim(true);
 
         mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -1390,7 +1411,7 @@
 
     @Test
     public void notificationAlpha_inKeyguardState_bouncerNotActive_usesInvertedShadeInterpolator() {
-        when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
         mScrimController.setClipsQsScrim(true);
 
         mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -1409,17 +1430,42 @@
     }
 
     @Test
+    public void behindTint_inKeyguardState_bouncerNotActive_usesKeyguardBehindTint() {
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
+        mScrimController.setClipsQsScrim(false);
+
+        mScrimController.transitionTo(ScrimState.KEYGUARD);
+        finishAnimationsImmediately();
+        assertThat(mScrimBehind.getTint())
+                .isEqualTo(ScrimState.KEYGUARD.getBehindTint());
+    }
+
+    @Test
     public void testNotificationTransparency_followsTransitionToFullShade() {
+        mScrimController.setClipsQsScrim(true);
+
         mScrimController.transitionTo(SHADE_LOCKED);
         mScrimController.setRawPanelExpansionFraction(1.0f);
         finishAnimationsImmediately();
+
+        assertScrimTinted(Map.of(
+                mScrimInFront, false,
+                mScrimBehind, true,
+                mNotificationsScrim, false
+        ));
+
         float shadeLockedAlpha = mNotificationsScrim.getViewAlpha();
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         mScrimController.setRawPanelExpansionFraction(1.0f);
         finishAnimationsImmediately();
         float keyguardAlpha = mNotificationsScrim.getViewAlpha();
 
-        mScrimController.setClipsQsScrim(true);
+        assertScrimTinted(Map.of(
+                mScrimInFront, true,
+                mScrimBehind, true,
+                mNotificationsScrim, true
+        ));
+
         float progress = 0.5f;
         float lsNotifProgress = 0.3f;
         mScrimController.setTransitionToFullShadeProgress(progress, lsNotifProgress);
@@ -1599,30 +1645,6 @@
         assertScrimAlpha(mScrimBehind, 0);
     }
 
-    @Test
-    public void keyguardAlpha_whenUnlockedForOcclusion_ifPlayingOcclusionAnimation() {
-        mScrimController.transitionTo(ScrimState.KEYGUARD);
-
-        when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
-
-        mScrimController.transitionTo(ScrimState.UNLOCKED);
-        finishAnimationsImmediately();
-
-        assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
-    }
-
-    @Test
-    public void keyguardAlpha_whenUnlockedForLaunch_ifLaunchingAffordance() {
-        mScrimController.transitionTo(ScrimState.KEYGUARD);
-        when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
-        mScrimController.setLaunchingAffordanceWithPreview(true);
-
-        mScrimController.transitionTo(ScrimState.UNLOCKED);
-        finishAnimationsImmediately();
-
-        assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
-    }
-
     private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
         mScrimController.setRawPanelExpansionFraction(expansion);
         finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index e86676b..1759fb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -19,9 +19,9 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.graphics.Rect
-import android.test.suitebuilder.annotation.SmallTest
 import android.view.Display
 import android.view.DisplayCutout
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -463,16 +463,10 @@
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
             mock(DumpManager::class.java))
 
-        givenDisplay(
-            screenBounds = Rect(0, 0, 1080, 2160),
-            displayUniqueId = "1"
-        )
+        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
         val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
-        givenDisplay(
-            screenBounds = Rect(0, 0, 800, 600),
-            displayUniqueId = "2"
-        )
-        configurationController.onConfigurationChanged(configuration)
+
+        configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
 
         // WHEN: get insets on the second display
         val secondDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -487,23 +481,15 @@
         // get insets and switch back
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
             mock(DumpManager::class.java))
-        givenDisplay(
-            screenBounds = Rect(0, 0, 1080, 2160),
-            displayUniqueId = "1"
-        )
+
+        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
         val firstDisplayInsetsFirstCall = provider
             .getStatusBarContentAreaForRotation(ROTATION_NONE)
-        givenDisplay(
-            screenBounds = Rect(0, 0, 800, 600),
-            displayUniqueId = "2"
-        )
-        configurationController.onConfigurationChanged(configuration)
+
+        configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
         provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
-        givenDisplay(
-            screenBounds = Rect(0, 0, 1080, 2160),
-            displayUniqueId = "1"
-        )
-        configurationController.onConfigurationChanged(configuration)
+
+        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
 
         // WHEN: get insets on the first display again
         val firstDisplayInsetsSecondCall = provider
@@ -513,9 +499,70 @@
         assertThat(firstDisplayInsetsFirstCall).isEqualTo(firstDisplayInsetsSecondCall)
     }
 
-    private fun givenDisplay(screenBounds: Rect, displayUniqueId: String) {
-        `when`(display.uniqueId).thenReturn(displayUniqueId)
-        configuration.windowConfiguration.maxBounds = screenBounds
+    // Regression test for b/245799099
+    @Test
+    fun onMaxBoundsChanged_listenerNotified() {
+        // Start out with an existing configuration with bounds
+        configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+        configurationController.onConfigurationChanged(configuration)
+        val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+                mock(DumpManager::class.java))
+        val listener = object : StatusBarContentInsetsChangedListener {
+            var triggered = false
+
+            override fun onStatusBarContentInsetsChanged() {
+                triggered = true
+            }
+        }
+        provider.addCallback(listener)
+
+        // WHEN the config is updated with new bounds
+        configuration.windowConfiguration.setMaxBounds(0, 0, 456, 789)
+        configurationController.onConfigurationChanged(configuration)
+
+        // THEN the listener is notified
+        assertThat(listener.triggered).isTrue()
+    }
+
+    @Test
+    fun onDensityOrFontScaleChanged_listenerNotified() {
+        configuration.densityDpi = 12
+        val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+                mock(DumpManager::class.java))
+        val listener = object : StatusBarContentInsetsChangedListener {
+            var triggered = false
+
+            override fun onStatusBarContentInsetsChanged() {
+                triggered = true
+            }
+        }
+        provider.addCallback(listener)
+
+        // WHEN the config is updated
+        configuration.densityDpi = 20
+        configurationController.onConfigurationChanged(configuration)
+
+        // THEN the listener is notified
+        assertThat(listener.triggered).isTrue()
+    }
+
+    @Test
+    fun onThemeChanged_listenerNotified() {
+        val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+                mock(DumpManager::class.java))
+        val listener = object : StatusBarContentInsetsChangedListener {
+            var triggered = false
+
+            override fun onStatusBarContentInsetsChanged() {
+                triggered = true
+            }
+        }
+        provider.addCallback(listener)
+
+        configurationController.notifyThemeChanged()
+
+        // THEN the listener is notified
+        assertThat(listener.triggered).isTrue()
     }
 
     private fun assertRects(
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..14a319b 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 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,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT 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.statusbar.phone;
@@ -56,8 +56,8 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.data.BouncerView;
 import com.android.systemui.keyguard.data.BouncerViewDelegate;
-import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.shade.NotificationPanelViewController;
@@ -105,8 +105,7 @@
     @Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
     @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
     @Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
-    @Mock private KeyguardBouncer mBouncer;
-    @Mock private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor;
+    @Mock private StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
     @Mock private KeyguardMessageArea mKeyguardMessageArea;
     @Mock private ShadeController mShadeController;
     @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
@@ -114,13 +113,13 @@
     @Mock private LatencyTracker mLatencyTracker;
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private KeyguardSecurityModel mKeyguardSecurityModel;
-    @Mock private BouncerCallbackInteractor mBouncerCallbackInteractor;
-    @Mock private BouncerInteractor mBouncerInteractor;
+    @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
+    @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     @Mock private BouncerView mBouncerView;
     @Mock private BouncerViewDelegate mBouncerViewDelegate;
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
+    private KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback;
     private FakeKeyguardStateController mKeyguardStateController =
             spy(new FakeKeyguardStateController());
 
@@ -133,15 +132,14 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        when(mKeyguardBouncerFactory.create(
-                        any(ViewGroup.class), any(KeyguardBouncer.BouncerExpansionCallback.class)))
-                .thenReturn(mBouncer);
         when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer);
         when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
         when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
                 .thenReturn(mKeyguardMessageAreaController);
         when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
 
+        when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(true);
+
         mStatusBarKeyguardViewManager =
                 new StatusBarKeyguardViewManager(
                         getContext(),
@@ -163,8 +161,8 @@
                         mLatencyTracker,
                         mKeyguardSecurityModel,
                         mFeatureFlags,
-                        mBouncerCallbackInteractor,
-                        mBouncerInteractor,
+                        mPrimaryBouncerCallbackInteractor,
+                        mPrimaryBouncerInteractor,
                         mBouncerView) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
@@ -181,9 +179,9 @@
                 mNotificationContainer,
                 mBypassController);
         mStatusBarKeyguardViewManager.show(null);
-        ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback> callbackArgumentCaptor =
-                ArgumentCaptor.forClass(KeyguardBouncer.BouncerExpansionCallback.class);
-        verify(mKeyguardBouncerFactory).create(any(ViewGroup.class),
+        ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
+                ArgumentCaptor.forClass(KeyguardBouncer.PrimaryBouncerExpansionCallback.class);
+        verify(mPrimaryBouncerCallbackInteractor).addBouncerExpansionCallback(
                 callbackArgumentCaptor.capture());
         mBouncerExpansionCallback = callbackArgumentCaptor.getValue();
     }
@@ -194,86 +192,87 @@
         Runnable cancelAction = () -> {};
         mStatusBarKeyguardViewManager.dismissWithAction(
                 action, cancelAction, false /* afterKeyguardGone */);
-        verify(mBouncer).showWithDismissAction(eq(action), eq(cancelAction));
+        verify(mPrimaryBouncerInteractor).setDismissAction(eq(action), eq(cancelAction));
+        verify(mPrimaryBouncerInteractor).show(eq(true));
     }
 
     @Test
     public void showBouncer_onlyWhenShowing() {
         mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
-        mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
-        verify(mBouncer, never()).show(anyBoolean(), anyBoolean());
-        verify(mBouncer, never()).show(anyBoolean());
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+        verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
     }
 
     @Test
     public void showBouncer_notWhenBouncerAlreadyShowing() {
         mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
-        when(mBouncer.isSecure()).thenReturn(true);
-        mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
-        verify(mBouncer, never()).show(anyBoolean(), anyBoolean());
-        verify(mBouncer, never()).show(anyBoolean());
+        when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+                KeyguardSecurityModel.SecurityMode.Password);
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+        verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
     }
 
     @Test
     public void showBouncer_showsTheBouncer() {
-        mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
-        verify(mBouncer).show(anyBoolean(), eq(true));
-    }
-
-    @Test
-    public void onPanelExpansionChanged_neverHidesScrimmedBouncer() {
-        when(mBouncer.isShowing()).thenReturn(true);
-        when(mBouncer.isScrimmed()).thenReturn(true);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+        verify(mPrimaryBouncerInteractor).show(eq(true));
     }
 
     @Test
     public void onPanelExpansionChanged_neverShowsDuringHintAnimation() {
         when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
     }
 
     @Test
-    public void onPanelExpansionChanged_propagatesToBouncer() {
+    public void onPanelExpansionChanged_propagatesToBouncerOnlyIfShowing() {
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer).setExpansion(eq(0.5f));
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(eq(0.5f));
+
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(/* fraction= */ 0.6f, /* expanded= */ false, /* tracking= */ true));
+        verify(mPrimaryBouncerInteractor).setPanelExpansion(eq(0.6f));
+    }
+
+    @Test
+    public void onPanelExpansionChanged_duplicateEventsAreIgnored() {
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncerInteractor).setPanelExpansion(eq(0.5f));
+
+        reset(mPrimaryBouncerInteractor);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(eq(0.5f));
     }
 
     @Test
     public void onPanelExpansionChanged_hideBouncer_afterKeyguardHidden() {
         mStatusBarKeyguardViewManager.hide(0, 0);
-        when(mBouncer.inTransit()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
 
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
+        verify(mPrimaryBouncerInteractor).setPanelExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
     }
 
     @Test
     public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
         mKeyguardStateController.setCanDismissLockScreen(false);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer).show(eq(false), eq(false));
+        verify(mPrimaryBouncerInteractor).show(eq(false));
 
         // But not when it's already visible
-        reset(mBouncer);
-        when(mBouncer.isShowing()).thenReturn(true);
+        reset(mPrimaryBouncerInteractor);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer, never()).show(eq(false), eq(false));
+        verify(mPrimaryBouncerInteractor, never()).show(eq(false));
 
         // Or animating away
-        reset(mBouncer);
-        when(mBouncer.isAnimatingAway()).thenReturn(true);
+        reset(mPrimaryBouncerInteractor);
+        when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer, never()).show(eq(false), eq(false));
-    }
-
-    @Test
-    public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
-        mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animate */);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer, never()).setExpansion(eq(0.5f));
+        verify(mPrimaryBouncerInteractor, never()).show(eq(false));
     }
 
     @Test
@@ -285,7 +284,7 @@
                         /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
                         /* expanded= */ true,
                         /* tracking= */ false));
-        verify(mBouncer, never()).setExpansion(anyFloat());
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
     }
 
     @Test
@@ -302,18 +301,35 @@
                         /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
                         /* expanded= */ true,
                         /* tracking= */ false));
-        verify(mBouncer, never()).setExpansion(anyFloat());
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
     }
 
     @Test
-    public void onPanelExpansionChanged_neverTranslatesBouncerWhenLaunchingApp() {
-        when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(
                 expansionEvent(
                         /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
                         /* expanded= */ true,
                         /* tracking= */ false));
-        verify(mBouncer, never()).setExpansion(anyFloat());
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
+    }
+
+    @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
+        // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
+        // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
+        // which would mistakenly cause the bouncer to show briefly before its visibility
+        // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
+        // bouncer if the bouncer is dismissing as a result of a biometric unlock.
+        when(mBiometricUnlockController.getMode())
+                .thenReturn(BiometricUnlockController.MODE_SHOW_BOUNCER);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
     }
 
     @Test
@@ -324,7 +340,7 @@
                         /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
                         /* expanded= */ true,
                         /* tracking= */ false));
-        verify(mBouncer, never()).setExpansion(anyFloat());
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
     }
 
     @Test
@@ -332,7 +348,7 @@
         mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
         verify(mCentralSurfaces).animateKeyguardUnoccluding();
 
-        when(mBouncer.isShowing()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
         clearInvocations(mCentralSurfaces);
         mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
         verify(mCentralSurfaces, never()).animateKeyguardUnoccluding();
@@ -361,7 +377,6 @@
 
     @Test
     public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() {
-        when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
         mStatusBarKeyguardViewManager.show(null);
 
         mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
@@ -384,7 +399,7 @@
         mStatusBarKeyguardViewManager.dismissWithAction(
                 action, cancelAction, true /* afterKeyguardGone */);
 
-        when(mBouncer.isShowing()).thenReturn(false);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
         mStatusBarKeyguardViewManager.hideBouncer(true);
         mStatusBarKeyguardViewManager.hide(0, 30);
         verify(action, never()).onDismiss();
@@ -398,7 +413,7 @@
         mStatusBarKeyguardViewManager.dismissWithAction(
                 action, cancelAction, true /* afterKeyguardGone */);
 
-        when(mBouncer.isShowing()).thenReturn(false);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
         mStatusBarKeyguardViewManager.hideBouncer(true);
 
         verify(action, never()).onDismiss();
@@ -419,9 +434,9 @@
 
     @Test
     public void testShowing_whenAlternateAuthShowing() {
-        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
-        when(mBouncer.isShowing()).thenReturn(false);
-        when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
+        when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
         assertTrue(
                 "Is showing not accurate when alternative auth showing",
                 mStatusBarKeyguardViewManager.isBouncerShowing());
@@ -429,93 +444,91 @@
 
     @Test
     public void testWillBeShowing_whenAlternateAuthShowing() {
-        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
-        when(mBouncer.isShowing()).thenReturn(false);
-        when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
+        when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
         assertTrue(
                 "Is or will be showing not accurate when alternative auth showing",
-                mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing());
+                mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
     }
 
     @Test
-    public void testHideAltAuth_onShowBouncer() {
+    public void testHideAlternateBouncer_onShowBouncer() {
         // GIVEN alt auth is showing
-        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
-        when(mBouncer.isShowing()).thenReturn(false);
-        when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
-        reset(mAlternateAuthInterceptor);
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
+        when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+        reset(mAlternateBouncer);
 
         // WHEN showBouncer is called
-        mStatusBarKeyguardViewManager.showBouncer(true);
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
 
         // THEN alt bouncer should be hidden
-        verify(mAlternateAuthInterceptor).hideAlternateAuthBouncer();
+        verify(mAlternateBouncer).hideAlternateBouncer();
     }
 
     @Test
     public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() {
-        when(mBouncer.isShowing()).thenReturn(false);
-        when(mBouncer.inTransit()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
+        when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
 
         assertTrue(
                 "Is or will be showing should be true when bouncer is in transit",
-                mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing());
+                mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
     }
 
     @Test
     public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
         // GIVEN alt auth exists, unlocking with biometric isn't allowed
-        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
-        when(mBouncer.isShowing()).thenReturn(false);
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
         when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
                 .thenReturn(false);
 
         // WHEN showGenericBouncer is called
         final boolean scrimmed = true;
-        mStatusBarKeyguardViewManager.showGenericBouncer(scrimmed);
+        mStatusBarKeyguardViewManager.showBouncer(scrimmed);
 
         // THEN regular bouncer is shown
-        verify(mBouncer).show(anyBoolean(), eq(scrimmed));
-        verify(mAlternateAuthInterceptor, never()).showAlternateAuthBouncer();
+        verify(mPrimaryBouncerInteractor).show(eq(scrimmed));
+        verify(mAlternateBouncer, never()).showAlternateBouncer();
     }
 
     @Test
-    public void testShowAltAuth_unlockingWithBiometricAllowed() {
+    public void testShowAlternateBouncer_unlockingWithBiometricAllowed() {
         // GIVEN alt auth exists, unlocking with biometric is allowed
-        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
-        when(mBouncer.isShowing()).thenReturn(false);
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
         when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
 
         // WHEN showGenericBouncer is called
-        mStatusBarKeyguardViewManager.showGenericBouncer(true);
+        mStatusBarKeyguardViewManager.showBouncer(true);
 
         // THEN alt auth bouncer is shown
-        verify(mAlternateAuthInterceptor).showAlternateAuthBouncer();
-        verify(mBouncer, never()).show(anyBoolean(), anyBoolean());
+        verify(mAlternateBouncer).showAlternateBouncer();
+        verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
     }
 
     @Test
     public void testUpdateResources_delegatesToBouncer() {
         mStatusBarKeyguardViewManager.updateResources();
 
-        verify(mBouncer).updateResources();
+        verify(mPrimaryBouncerInteractor).updateResources();
     }
 
     @Test
     public void updateKeyguardPosition_delegatesToBouncer() {
         mStatusBarKeyguardViewManager.updateKeyguardPosition(1.0f);
 
-        verify(mBouncer).updateKeyguardPosition(1.0f);
+        verify(mPrimaryBouncerInteractor).setKeyguardPosition(1.0f);
     }
 
     @Test
     public void testIsBouncerInTransit() {
-        when(mBouncer.inTransit()).thenReturn(true);
-        Truth.assertThat(mStatusBarKeyguardViewManager.isBouncerInTransit()).isTrue();
-        when(mBouncer.inTransit()).thenReturn(false);
-        Truth.assertThat(mStatusBarKeyguardViewManager.isBouncerInTransit()).isFalse();
-        mBouncer = null;
-        Truth.assertThat(mStatusBarKeyguardViewManager.isBouncerInTransit()).isFalse();
+        when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
+        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isTrue();
+        when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(false);
+        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
     }
 
     private static ShadeExpansionChangeEvent expansionEvent(
@@ -546,7 +559,7 @@
                 eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
                 mOnBackInvokedCallback.capture());
 
-        when(mBouncer.isShowing()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
         when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
         /* invoke the back callback directly */
         mOnBackInvokedCallback.getValue().onBackInvoked();
@@ -576,9 +589,38 @@
     }
 
     @Test
-    public void flag_off_DoesNotCallBouncerInteractor() {
-        when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
-        mStatusBarKeyguardViewManager.hideBouncer(false);
-        verify(mBouncerInteractor, never()).hide();
+    public void hideAlternateBouncer_beforeCentralSurfacesRegistered() {
+        mStatusBarKeyguardViewManager =
+                new StatusBarKeyguardViewManager(
+                        getContext(),
+                        mViewMediatorCallback,
+                        mLockPatternUtils,
+                        mStatusBarStateController,
+                        mock(ConfigurationController.class),
+                        mKeyguardUpdateMonitor,
+                        mDreamOverlayStateController,
+                        mock(NavigationModeController.class),
+                        mock(DockManager.class),
+                        mock(NotificationShadeWindowController.class),
+                        mKeyguardStateController,
+                        mock(NotificationMediaManager.class),
+                        mKeyguardBouncerFactory,
+                        mKeyguardMessageAreaFactory,
+                        Optional.of(mSysUiUnfoldComponent),
+                        () -> mShadeController,
+                        mLatencyTracker,
+                        mKeyguardSecurityModel,
+                        mFeatureFlags,
+                        mPrimaryBouncerCallbackInteractor,
+                        mPrimaryBouncerInteractor,
+                        mBouncerView) {
+                    @Override
+                    public ViewRootImpl getViewRootImpl() {
+                        return mViewRootImpl;
+                    }
+                };
+
+        // the following call before registering centralSurfaces should NOT throw a NPE:
+        mStatusBarKeyguardViewManager.hideAlternateBouncer(true);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
new file mode 100644
index 0000000..96fba39
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
@@ -0,0 +1,641 @@
+/*
+ * 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.statusbar.phone;
+
+import static com.android.systemui.flags.Flags.MODERN_BOUNCER;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewRootImpl;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
+import android.window.WindowOnBackInvokedDispatcher;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardMessageArea;
+import com.android.keyguard.KeyguardMessageAreaController;
+import com.android.keyguard.KeyguardSecurityModel;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.data.BouncerView;
+import com.android.systemui.keyguard.data.BouncerViewDelegate;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
+
+import com.google.common.truth.Truth;
+
+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.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+/**
+ * StatusBarKeyguardViewManager Test with deprecated KeyguardBouncer.java.
+ * TODO: Delete when deleting {@link KeyguardBouncer}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase {
+    private static final ShadeExpansionChangeEvent EXPANSION_EVENT =
+            expansionEvent(/* fraction= */ 0.5f, /* expanded= */ false, /* tracking= */ true);
+
+    @Mock private ViewMediatorCallback mViewMediatorCallback;
+    @Mock private LockPatternUtils mLockPatternUtils;
+    @Mock private CentralSurfaces mCentralSurfaces;
+    @Mock private ViewGroup mContainer;
+    @Mock private NotificationPanelViewController mNotificationPanelView;
+    @Mock private BiometricUnlockController mBiometricUnlockController;
+    @Mock private SysuiStatusBarStateController mStatusBarStateController;
+    @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock private View mNotificationContainer;
+    @Mock private KeyguardBypassController mBypassController;
+    @Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
+    @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
+    @Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
+    @Mock private KeyguardBouncer mPrimaryBouncer;
+    @Mock private StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
+    @Mock private KeyguardMessageArea mKeyguardMessageArea;
+    @Mock private ShadeController mShadeController;
+    @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
+    @Mock private DreamOverlayStateController mDreamOverlayStateController;
+    @Mock private LatencyTracker mLatencyTracker;
+    @Mock private FeatureFlags mFeatureFlags;
+    @Mock private KeyguardSecurityModel mKeyguardSecurityModel;
+    @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
+    @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+    @Mock private BouncerView mBouncerView;
+    @Mock private BouncerViewDelegate mBouncerViewDelegate;
+
+    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback;
+    private FakeKeyguardStateController mKeyguardStateController =
+            spy(new FakeKeyguardStateController());
+
+    @Mock private ViewRootImpl mViewRootImpl;
+    @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
+    @Captor
+    private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
+
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mKeyguardBouncerFactory.create(
+                any(ViewGroup.class),
+                any(KeyguardBouncer.PrimaryBouncerExpansionCallback.class)))
+                .thenReturn(mPrimaryBouncer);
+        when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer);
+        when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
+        when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
+                .thenReturn(mKeyguardMessageAreaController);
+        when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
+
+        mStatusBarKeyguardViewManager =
+                new StatusBarKeyguardViewManager(
+                        getContext(),
+                        mViewMediatorCallback,
+                        mLockPatternUtils,
+                        mStatusBarStateController,
+                        mock(ConfigurationController.class),
+                        mKeyguardUpdateMonitor,
+                        mDreamOverlayStateController,
+                        mock(NavigationModeController.class),
+                        mock(DockManager.class),
+                        mock(NotificationShadeWindowController.class),
+                        mKeyguardStateController,
+                        mock(NotificationMediaManager.class),
+                        mKeyguardBouncerFactory,
+                        mKeyguardMessageAreaFactory,
+                        Optional.of(mSysUiUnfoldComponent),
+                        () -> mShadeController,
+                        mLatencyTracker,
+                        mKeyguardSecurityModel,
+                        mFeatureFlags,
+                        mPrimaryBouncerCallbackInteractor,
+                        mPrimaryBouncerInteractor,
+                        mBouncerView) {
+                    @Override
+                    public ViewRootImpl getViewRootImpl() {
+                        return mViewRootImpl;
+                    }
+                };
+        when(mViewRootImpl.getOnBackInvokedDispatcher())
+                .thenReturn(mOnBackInvokedDispatcher);
+        mStatusBarKeyguardViewManager.registerCentralSurfaces(
+                mCentralSurfaces,
+                mNotificationPanelView,
+                new ShadeExpansionStateManager(),
+                mBiometricUnlockController,
+                mNotificationContainer,
+                mBypassController);
+        mStatusBarKeyguardViewManager.show(null);
+        ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
+                ArgumentCaptor.forClass(KeyguardBouncer.PrimaryBouncerExpansionCallback.class);
+        verify(mKeyguardBouncerFactory).create(any(ViewGroup.class),
+                callbackArgumentCaptor.capture());
+        mBouncerExpansionCallback = callbackArgumentCaptor.getValue();
+    }
+
+    @Test
+    public void dismissWithAction_AfterKeyguardGoneSetToFalse() {
+        OnDismissAction action = () -> false;
+        Runnable cancelAction = () -> {};
+        mStatusBarKeyguardViewManager.dismissWithAction(
+                action, cancelAction, false /* afterKeyguardGone */);
+        verify(mPrimaryBouncer).showWithDismissAction(eq(action), eq(cancelAction));
+    }
+
+    @Test
+    public void showBouncer_onlyWhenShowing() {
+        mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+        verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+        verify(mPrimaryBouncer, never()).show(anyBoolean());
+    }
+
+    @Test
+    public void showBouncer_notWhenBouncerAlreadyShowing() {
+        mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
+        when(mPrimaryBouncer.isSecure()).thenReturn(true);
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+        verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+        verify(mPrimaryBouncer, never()).show(anyBoolean());
+    }
+
+    @Test
+    public void showBouncer_showsTheBouncer() {
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+        verify(mPrimaryBouncer).show(anyBoolean(), eq(true));
+    }
+
+    @Test
+    public void onPanelExpansionChanged_neverShowsDuringHintAnimation() {
+        when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
+    public void onPanelExpansionChanged_propagatesToBouncerOnlyIfShowing() {
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
+
+        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(/* fraction= */ 0.6f, /* expanded= */ false, /* tracking= */ true));
+        verify(mPrimaryBouncer).setExpansion(eq(0.6f));
+    }
+
+    @Test
+    public void onPanelExpansionChanged_duplicateEventsAreIgnored() {
+        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer).setExpansion(eq(0.5f));
+
+        reset(mPrimaryBouncer);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
+    }
+
+    @Test
+    public void onPanelExpansionChanged_hideBouncer_afterKeyguardHidden() {
+        mStatusBarKeyguardViewManager.hide(0, 0);
+        when(mPrimaryBouncer.inTransit()).thenReturn(true);
+
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
+    }
+
+    @Test
+    public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
+        mKeyguardStateController.setCanDismissLockScreen(false);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer).show(eq(false), eq(false));
+
+        // But not when it's already visible
+        reset(mPrimaryBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
+
+        // Or animating away
+        reset(mPrimaryBouncer);
+        when(mPrimaryBouncer.isAnimatingAway()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
+    }
+
+    @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() {
+        when(mBiometricUnlockController.getMode())
+                .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenDismissBouncer() {
+        // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
+        // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
+        // which would mistakenly cause the bouncer to show briefly before its visibility
+        // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
+        // bouncer if the bouncer is dismissing as a result of a biometric unlock.
+        when(mBiometricUnlockController.getMode())
+                .thenReturn(BiometricUnlockController.MODE_DISMISS_BOUNCER);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
+        // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
+        // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
+        // which would mistakenly cause the bouncer to show briefly before its visibility
+        // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
+        // bouncer if the bouncer is dismissing as a result of a biometric unlock.
+        when(mBiometricUnlockController.getMode())
+                .thenReturn(BiometricUnlockController.MODE_SHOW_BOUNCER);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenShadeLocked() {
+        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
+    public void setOccluded_animatesPanelExpansion_onlyIfBouncerHidden() {
+        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
+        verify(mCentralSurfaces).animateKeyguardUnoccluding();
+
+        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        clearInvocations(mCentralSurfaces);
+        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
+        verify(mCentralSurfaces, never()).animateKeyguardUnoccluding();
+    }
+
+    @Test
+    public void setOccluded_onKeyguardOccludedChangedCalled() {
+        clearInvocations(mKeyguardStateController);
+        clearInvocations(mKeyguardUpdateMonitor);
+
+        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
+        verify(mKeyguardStateController).notifyKeyguardState(true, false);
+
+        clearInvocations(mKeyguardUpdateMonitor);
+        clearInvocations(mKeyguardStateController);
+
+        mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
+        verify(mKeyguardStateController).notifyKeyguardState(true, true);
+
+        clearInvocations(mKeyguardUpdateMonitor);
+        clearInvocations(mKeyguardStateController);
+
+        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
+        verify(mKeyguardStateController).notifyKeyguardState(true, false);
+    }
+
+    @Test
+    public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() {
+        mStatusBarKeyguardViewManager.show(null);
+
+        mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
+        verify(mKeyguardStateController).notifyKeyguardState(true, true);
+    }
+
+    @Test
+    public void setOccluded_isLaunchingActivityOverLockscreen_onKeyguardOccludedChangedCalled() {
+        when(mCentralSurfaces.isLaunchingActivityOverLockscreen()).thenReturn(true);
+        mStatusBarKeyguardViewManager.show(null);
+
+        mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
+        verify(mKeyguardStateController).notifyKeyguardState(true, true);
+    }
+
+    @Test
+    public void testHiding_cancelsGoneRunnable() {
+        OnDismissAction action = mock(OnDismissAction.class);
+        Runnable cancelAction = mock(Runnable.class);
+        mStatusBarKeyguardViewManager.dismissWithAction(
+                action, cancelAction, true /* afterKeyguardGone */);
+
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        mStatusBarKeyguardViewManager.hideBouncer(true);
+        mStatusBarKeyguardViewManager.hide(0, 30);
+        verify(action, never()).onDismiss();
+        verify(cancelAction).run();
+    }
+
+    @Test
+    public void testHidingBouncer_cancelsGoneRunnable() {
+        OnDismissAction action = mock(OnDismissAction.class);
+        Runnable cancelAction = mock(Runnable.class);
+        mStatusBarKeyguardViewManager.dismissWithAction(
+                action, cancelAction, true /* afterKeyguardGone */);
+
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        mStatusBarKeyguardViewManager.hideBouncer(true);
+
+        verify(action, never()).onDismiss();
+        verify(cancelAction).run();
+    }
+
+    @Test
+    public void testHiding_doesntCancelWhenShowing() {
+        OnDismissAction action = mock(OnDismissAction.class);
+        Runnable cancelAction = mock(Runnable.class);
+        mStatusBarKeyguardViewManager.dismissWithAction(
+                action, cancelAction, true /* afterKeyguardGone */);
+
+        mStatusBarKeyguardViewManager.hide(0, 30);
+        verify(action).onDismiss();
+        verify(cancelAction, never()).run();
+    }
+
+    @Test
+    public void testShowing_whenAlternateAuthShowing() {
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+        assertTrue(
+                "Is showing not accurate when alternative auth showing",
+                mStatusBarKeyguardViewManager.isBouncerShowing());
+    }
+
+    @Test
+    public void testWillBeShowing_whenAlternateAuthShowing() {
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+        assertTrue(
+                "Is or will be showing not accurate when alternative auth showing",
+                mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
+    }
+
+    @Test
+    public void testHideAlternateBouncer_onShowBouncer() {
+        // GIVEN alt auth is showing
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+        reset(mAlternateBouncer);
+
+        // WHEN showBouncer is called
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
+
+        // THEN alt bouncer should be hidden
+        verify(mAlternateBouncer).hideAlternateBouncer();
+    }
+
+    @Test
+    public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() {
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mPrimaryBouncer.inTransit()).thenReturn(true);
+
+        assertTrue(
+                "Is or will be showing should be true when bouncer is in transit",
+                mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
+    }
+
+    @Test
+    public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
+        // GIVEN alt auth exists, unlocking with biometric isn't allowed
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+                .thenReturn(false);
+
+        // WHEN showGenericBouncer is called
+        final boolean scrimmed = true;
+        mStatusBarKeyguardViewManager.showBouncer(scrimmed);
+
+        // THEN regular bouncer is shown
+        verify(mPrimaryBouncer).show(anyBoolean(), eq(scrimmed));
+        verify(mAlternateBouncer, never()).showAlternateBouncer();
+    }
+
+    @Test
+    public void testShowAlternateBouncer_unlockingWithBiometricAllowed() {
+        // GIVEN alt auth exists, unlocking with biometric is allowed
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+
+        // WHEN showGenericBouncer is called
+        mStatusBarKeyguardViewManager.showBouncer(true);
+
+        // THEN alt auth bouncer is shown
+        verify(mAlternateBouncer).showAlternateBouncer();
+        verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+    }
+
+    @Test
+    public void testUpdateResources_delegatesToBouncer() {
+        mStatusBarKeyguardViewManager.updateResources();
+
+        verify(mPrimaryBouncer).updateResources();
+    }
+
+    @Test
+    public void updateKeyguardPosition_delegatesToBouncer() {
+        mStatusBarKeyguardViewManager.updateKeyguardPosition(1.0f);
+
+        verify(mPrimaryBouncer).updateKeyguardPosition(1.0f);
+    }
+
+    @Test
+    public void testIsBouncerInTransit() {
+        when(mPrimaryBouncer.inTransit()).thenReturn(true);
+        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isTrue();
+        when(mPrimaryBouncer.inTransit()).thenReturn(false);
+        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
+        mPrimaryBouncer = null;
+        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
+    }
+
+    private static ShadeExpansionChangeEvent expansionEvent(
+            float fraction, boolean expanded, boolean tracking) {
+        return new ShadeExpansionChangeEvent(
+                fraction, expanded, tracking, /* dragDownPxAmount= */ 0f);
+    }
+
+    @Test
+    public void testPredictiveBackCallback_registration() {
+        /* verify that a predictive back callback is registered when the bouncer becomes visible */
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+                mOnBackInvokedCallback.capture());
+
+        /* verify that the same callback is unregistered when the bouncer becomes invisible */
+        mBouncerExpansionCallback.onVisibilityChanged(false);
+        verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(
+                eq(mOnBackInvokedCallback.getValue()));
+    }
+
+    @Test
+    public void testPredictiveBackCallback_invocationHidesBouncer() {
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+        /* capture the predictive back callback during registration */
+        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+                mOnBackInvokedCallback.capture());
+
+        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
+        /* invoke the back callback directly */
+        mOnBackInvokedCallback.getValue().onBackInvoked();
+
+        /* verify that the bouncer will be hidden as a result of the invocation */
+        verify(mCentralSurfaces).setBouncerShowing(eq(false));
+    }
+
+    @Test
+    public void testReportBouncerOnDreamWhenVisible() {
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+        Mockito.clearInvocations(mCentralSurfaces);
+        when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+        verify(mCentralSurfaces).setBouncerShowingOverDream(true);
+    }
+
+    @Test
+    public void testReportBouncerOnDreamWhenNotVisible() {
+        mBouncerExpansionCallback.onVisibilityChanged(false);
+        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+        Mockito.clearInvocations(mCentralSurfaces);
+        when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+        mBouncerExpansionCallback.onVisibilityChanged(false);
+        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+    }
+
+    @Test
+    public void flag_off_DoesNotCallBouncerInteractor() {
+        when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
+        mStatusBarKeyguardViewManager.hideBouncer(false);
+        verify(mPrimaryBouncerInteractor, never()).hide();
+    }
+
+    @Test
+    public void hideAlternateBouncer_beforeCentralSurfacesRegistered() {
+        mStatusBarKeyguardViewManager =
+                new StatusBarKeyguardViewManager(
+                        getContext(),
+                        mViewMediatorCallback,
+                        mLockPatternUtils,
+                        mStatusBarStateController,
+                        mock(ConfigurationController.class),
+                        mKeyguardUpdateMonitor,
+                        mDreamOverlayStateController,
+                        mock(NavigationModeController.class),
+                        mock(DockManager.class),
+                        mock(NotificationShadeWindowController.class),
+                        mKeyguardStateController,
+                        mock(NotificationMediaManager.class),
+                        mKeyguardBouncerFactory,
+                        mKeyguardMessageAreaFactory,
+                        Optional.of(mSysUiUnfoldComponent),
+                        () -> mShadeController,
+                        mLatencyTracker,
+                        mKeyguardSecurityModel,
+                        mFeatureFlags,
+                        mPrimaryBouncerCallbackInteractor,
+                        mPrimaryBouncerInteractor,
+                        mBouncerView) {
+                    @Override
+                    public ViewRootImpl getViewRootImpl() {
+                        return mViewRootImpl;
+                    }
+                };
+
+        // the following call before registering centralSurfaces should NOT throw a NPE:
+        mStatusBarKeyguardViewManager.hideAlternateBouncer(true);
+    }
+}
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..cae414a 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
@@ -265,7 +263,7 @@
         while (!runnables.isEmpty()) runnables.remove(0).run();
 
         // Then
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(any(),
                 eq(false) /* animate */, any(), any());
@@ -298,7 +296,7 @@
         verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
 
         // This is called regardless, and simply short circuits when there is nothing to do.
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mAssistManager).hideAssist();
 
@@ -331,7 +329,7 @@
         // Then
         verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
 
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mAssistManager).hideAssist();
 
@@ -359,7 +357,7 @@
         // Then
         verify(mBubblesManager).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
 
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mAssistManager).hideAssist();
 
@@ -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/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index c3a7e65..613238f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -99,6 +99,6 @@
         mRemoteInputCallback.onLockedRemoteInput(
                 mock(ExpandableNotificationRow.class), mock(View.class));
 
-        verify(mStatusBarKeyguardViewManager).showGenericBouncer(true);
+        verify(mStatusBarKeyguardViewManager).showBouncer(true);
     }
 }
\ No newline at end of file
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/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
deleted file mode 100644
index eba3b04..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
+++ /dev/null
@@ -1,102 +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.statusbar.phone.userswitcher
-
-import android.content.Intent
-import android.os.UserHandle
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-@SmallTest
-class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() {
-    @Mock
-    private lateinit var tracker: StatusBarUserInfoTracker
-
-    @Mock
-    private lateinit var featureController: StatusBarUserSwitcherFeatureController
-
-    @Mock
-    private lateinit var userSwitcherDialogController: UserSwitchDialogController
-
-    @Mock
-    private lateinit var featureFlags: FeatureFlags
-
-    @Mock
-    private lateinit var activityStarter: ActivityStarter
-
-    @Mock
-    private lateinit var falsingManager: FalsingManager
-
-    private lateinit var statusBarUserSwitcherContainer: StatusBarUserSwitcherContainer
-    private lateinit var controller: StatusBarUserSwitcherControllerImpl
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        statusBarUserSwitcherContainer = StatusBarUserSwitcherContainer(mContext, null)
-        statusBarUserSwitcherContainer
-        controller = StatusBarUserSwitcherControllerImpl(
-                statusBarUserSwitcherContainer,
-                tracker,
-                featureController,
-                userSwitcherDialogController,
-                featureFlags,
-                activityStarter,
-                falsingManager
-        )
-        controller.init()
-        controller.onViewAttached()
-    }
-
-    @Test
-    fun testFalsingManager() {
-        statusBarUserSwitcherContainer.callOnClick()
-        verify(falsingManager).isFalseTap(FalsingManager.LOW_PENALTY)
-    }
-
-    @Test
-    fun testStartActivity() {
-        `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(false)
-        statusBarUserSwitcherContainer.callOnClick()
-        verify(userSwitcherDialogController).showDialog(any(), any())
-        `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(true)
-        statusBarUserSwitcherContainer.callOnClick()
-        verify(activityStarter).startActivity(any(Intent::class.java),
-                eq(true) /* dismissShade */,
-                eq(null) /* animationController */,
-                eq(true) /* showOverLockscreenWhenLocked */,
-                eq(UserHandle.SYSTEM))
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
index b7a6c01..d35ce76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
@@ -22,7 +22,7 @@
 import android.provider.Settings.Global
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
@@ -45,7 +45,7 @@
 
     private lateinit var underTest: AirplaneModeRepositoryImpl
 
-    @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var logger: TableLogBuffer
     private lateinit var bgHandler: Handler
     private lateinit var scope: CoroutineScope
     private lateinit var settings: FakeSettings
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
new file mode 100644
index 0000000..5a6bb30
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.airplane.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+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.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+class AirplaneModeViewModelImplTest : SysuiTestCase() {
+
+    private lateinit var underTest: AirplaneModeViewModelImpl
+
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+    private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+    private lateinit var connectivityRepository: FakeConnectivityRepository
+    private lateinit var interactor: AirplaneModeInteractor
+    private lateinit var scope: CoroutineScope
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        airplaneModeRepository = FakeAirplaneModeRepository()
+        connectivityRepository = FakeConnectivityRepository()
+        interactor = AirplaneModeInteractor(airplaneModeRepository, connectivityRepository)
+        scope = CoroutineScope(IMMEDIATE)
+
+        underTest =
+            AirplaneModeViewModelImpl(
+                interactor,
+                logger,
+                scope,
+            )
+    }
+
+    @Test
+    fun isAirplaneModeIconVisible_notAirplaneMode_outputsFalse() =
+        runBlocking(IMMEDIATE) {
+            connectivityRepository.setForceHiddenIcons(setOf())
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            var latest: Boolean? = null
+            val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isAirplaneModeIconVisible_forceHidden_outputsFalse() =
+        runBlocking(IMMEDIATE) {
+            connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
+            airplaneModeRepository.setIsAirplaneMode(true)
+
+            var latest: Boolean? = null
+            val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isAirplaneModeIconVisible_isAirplaneModeAndNotForceHidden_outputsTrue() =
+        runBlocking(IMMEDIATE) {
+            connectivityRepository.setForceHiddenIcons(setOf())
+            airplaneModeRepository.setIsAirplaneMode(true)
+
+            var latest: Boolean? = null
+            val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt
deleted file mode 100644
index 76016a1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt
+++ /dev/null
@@ -1,110 +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.statusbar.pipeline.airplane.ui.viewmodel
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
-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.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-class AirplaneModeViewModelTest : SysuiTestCase() {
-
-    private lateinit var underTest: AirplaneModeViewModel
-
-    @Mock private lateinit var logger: ConnectivityPipelineLogger
-    private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
-    private lateinit var connectivityRepository: FakeConnectivityRepository
-    private lateinit var interactor: AirplaneModeInteractor
-    private lateinit var scope: CoroutineScope
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        airplaneModeRepository = FakeAirplaneModeRepository()
-        connectivityRepository = FakeConnectivityRepository()
-        interactor = AirplaneModeInteractor(airplaneModeRepository, connectivityRepository)
-        scope = CoroutineScope(IMMEDIATE)
-
-        underTest =
-            AirplaneModeViewModel(
-                interactor,
-                logger,
-                scope,
-            )
-    }
-
-    @Test
-    fun isAirplaneModeIconVisible_notAirplaneMode_outputsFalse() =
-        runBlocking(IMMEDIATE) {
-            connectivityRepository.setForceHiddenIcons(setOf())
-            airplaneModeRepository.setIsAirplaneMode(false)
-
-            var latest: Boolean? = null
-            val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
-
-            assertThat(latest).isFalse()
-
-            job.cancel()
-        }
-
-    @Test
-    fun isAirplaneModeIconVisible_forceHidden_outputsFalse() =
-        runBlocking(IMMEDIATE) {
-            connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
-            airplaneModeRepository.setIsAirplaneMode(true)
-
-            var latest: Boolean? = null
-            val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
-
-            assertThat(latest).isFalse()
-
-            job.cancel()
-        }
-
-    @Test
-    fun isAirplaneModeIconVisible_isAirplaneModeAndNotForceHidden_outputsTrue() =
-        runBlocking(IMMEDIATE) {
-            connectivityRepository.setForceHiddenIcons(setOf())
-            airplaneModeRepository.setIsAirplaneMode(true)
-
-            var latest: Boolean? = null
-            val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
-
-            assertThat(latest).isTrue()
-
-            job.cancel()
-        }
-}
-
-private val IMMEDIATE = Dispatchers.Main.immediate
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 de1fec8..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,16 +17,18 @@
 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
     }
@@ -34,4 +36,8 @@
     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 813e750..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,6 +55,18 @@
         _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
     }
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
deleted file mode 100644
index 0939364..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
+++ /dev/null
@@ -1,349 +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.statusbar.pipeline.mobile.data.repository
-
-import android.telephony.CellSignalStrengthCdma
-import android.telephony.ServiceState
-import android.telephony.SignalStrength
-import android.telephony.SubscriptionInfo
-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
-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.whenever
-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.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.MockitoAnnotations
-
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-class MobileConnectionRepositoryTest : SysuiTestCase() {
-    private lateinit var underTest: MobileConnectionRepositoryImpl
-
-    @Mock private lateinit var telephonyManager: TelephonyManager
-    @Mock private lateinit var logger: ConnectivityPipelineLogger
-
-    private val scope = CoroutineScope(IMMEDIATE)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
-
-        underTest =
-            MobileConnectionRepositoryImpl(
-                SUB_1_ID,
-                telephonyManager,
-                IMMEDIATE,
-                logger,
-                scope,
-            )
-    }
-
-    @After
-    fun tearDown() {
-        scope.cancel()
-    }
-
-    @Test
-    fun testFlowForSubId_default() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
-
-            assertThat(latest).isEqualTo(MobileSubscriptionModel())
-
-            job.cancel()
-        }
-
-    @Test
-    fun testFlowForSubId_emergencyOnly() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
-
-            val serviceState = ServiceState()
-            serviceState.isEmergencyOnly = true
-
-            getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
-
-            assertThat(latest?.isEmergencyOnly).isEqualTo(true)
-
-            job.cancel()
-        }
-
-    @Test
-    fun testFlowForSubId_emergencyOnly_toggles() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
-
-            val callback = getTelephonyCallbackForType<ServiceStateListener>()
-            val serviceState = ServiceState()
-            serviceState.isEmergencyOnly = true
-            callback.onServiceStateChanged(serviceState)
-            serviceState.isEmergencyOnly = false
-            callback.onServiceStateChanged(serviceState)
-
-            assertThat(latest?.isEmergencyOnly).isEqualTo(false)
-
-            job.cancel()
-        }
-
-    @Test
-    fun testFlowForSubId_signalStrengths_levelsUpdate() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
-
-            val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
-            val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
-            callback.onSignalStrengthsChanged(strength)
-
-            assertThat(latest?.isGsm).isEqualTo(true)
-            assertThat(latest?.primaryLevel).isEqualTo(1)
-            assertThat(latest?.cdmaLevel).isEqualTo(2)
-
-            job.cancel()
-        }
-
-    @Test
-    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(DATA_CONNECTED, 200 /* unused */)
-
-            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()
-        }
-
-    @Test
-    fun testFlowForSubId_dataActivity() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
-
-            val callback = getTelephonyCallbackForType<TelephonyCallback.DataActivityListener>()
-            callback.onDataActivity(3)
-
-            assertThat(latest?.dataActivityDirection).isEqualTo(3)
-
-            job.cancel()
-        }
-
-    @Test
-    fun testFlowForSubId_carrierNetworkChange() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
-
-            val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
-            callback.onCarrierNetworkChange(true)
-
-            assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
-
-            job.cancel()
-        }
-
-    @Test
-    fun subscriptionFlow_networkType_default() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
-
-            val type = NETWORK_TYPE_UNKNOWN
-            val expected = DefaultNetworkType(type)
-
-            assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
-
-            job.cancel()
-        }
-
-    @Test
-    fun subscriptionFlow_networkType_updatesUsingDefault() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
-
-            val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
-            val type = NETWORK_TYPE_LTE
-            val expected = DefaultNetworkType(type)
-            val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
-            callback.onDisplayInfoChanged(ti)
-
-            assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
-
-            job.cancel()
-        }
-
-    @Test
-    fun subscriptionFlow_networkType_updatesUsingOverride() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
-
-            val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
-            val type = OVERRIDE_NETWORK_TYPE_LTE_CA
-            val expected = OverrideNetworkType(type)
-            val ti =
-                mock<TelephonyDisplayInfo>().also {
-                    whenever(it.overrideNetworkType).thenReturn(type)
-                }
-            callback.onDisplayInfoChanged(ti)
-
-            assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
-
-            job.cancel()
-        }
-
-    @Test
-    fun dataEnabled_isEnabled() =
-        runBlocking(IMMEDIATE) {
-            whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
-
-            var latest: Boolean? = null
-            val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
-
-            assertThat(latest).isTrue()
-
-            job.cancel()
-        }
-
-    @Test
-    fun dataEnabled_isDisabled() =
-        runBlocking(IMMEDIATE) {
-            whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
-
-            var latest: Boolean? = null
-            val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
-
-            assertThat(latest).isFalse()
-
-            job.cancel()
-        }
-
-    private fun getTelephonyCallbacks(): List<TelephonyCallback> {
-        val callbackCaptor = argumentCaptor<TelephonyCallback>()
-        Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
-        return callbackCaptor.allValues
-    }
-
-    private inline fun <reified T> getTelephonyCallbackForType(): T {
-        val cbs = getTelephonyCallbacks().filterIsInstance<T>()
-        assertThat(cbs.size).isEqualTo(1)
-        return cbs[0]
-    }
-
-    /** Convenience constructor for SignalStrength */
-    private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
-        val signalStrength = mock<SignalStrength>()
-        whenever(signalStrength.isGsm).thenReturn(isGsm)
-        whenever(signalStrength.level).thenReturn(gsmLevel)
-        val cdmaStrength =
-            mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
-        whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
-            .thenReturn(listOf(cdmaStrength))
-
-        return signalStrength
-    }
-
-    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) }
-    }
-}
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
deleted file mode 100644
index 326e0d281..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
+++ /dev/null
@@ -1,246 +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.statusbar.pipeline.mobile.data.repository
-
-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.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
-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.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
-import org.junit.After
-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
-
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-class MobileConnectionsRepositoryTest : SysuiTestCase() {
-    private lateinit var underTest: MobileConnectionsRepositoryImpl
-
-    @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)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        whenever(
-                broadcastDispatcher.broadcastFlow(
-                    any(),
-                    nullable(),
-                    ArgumentMatchers.anyInt(),
-                    nullable(),
-                )
-            )
-            .thenReturn(flowOf(Unit))
-
-        underTest =
-            MobileConnectionsRepositoryImpl(
-                subscriptionManager,
-                telephonyManager,
-                logger,
-                broadcastDispatcher,
-                context,
-                IMMEDIATE,
-                scope,
-                mock(),
-            )
-    }
-
-    @After
-    fun tearDown() {
-        scope.cancel()
-    }
-
-    @Test
-    fun testSubscriptions_initiallyEmpty() =
-        runBlocking(IMMEDIATE) {
-            assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
-        }
-
-    @Test
-    fun testSubscriptions_listUpdates() =
-        runBlocking(IMMEDIATE) {
-            var latest: List<SubscriptionInfo>? = null
-
-            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
-
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1, SUB_2))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
-
-            job.cancel()
-        }
-
-    @Test
-    fun testSubscriptions_removingSub_updatesList() =
-        runBlocking(IMMEDIATE) {
-            var latest: List<SubscriptionInfo>? = null
-
-            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
-
-            // WHEN 2 networks show up
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1, SUB_2))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            // WHEN one network is removed
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_2))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            // THEN the subscriptions list represents the newest change
-            assertThat(latest).isEqualTo(listOf(SUB_2))
-
-            job.cancel()
-        }
-
-    @Test
-    fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
-        runBlocking(IMMEDIATE) {
-            assertThat(underTest.activeMobileDataSubscriptionId.value)
-                .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
-        }
-
-    @Test
-    fun testActiveDataSubscriptionId_updates() =
-        runBlocking(IMMEDIATE) {
-            var active: Int? = null
-
-            val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
-
-            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
-                .onActiveDataSubscriptionIdChanged(SUB_2_ID)
-
-            assertThat(active).isEqualTo(SUB_2_ID)
-
-            job.cancel()
-        }
-
-    @Test
-    fun testConnectionRepository_validSubId_isCached() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptionsFlow.launchIn(this)
-
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
-            val repo2 = underTest.getRepoForSubId(SUB_1_ID)
-
-            assertThat(repo1).isSameInstanceAs(repo2)
-
-            job.cancel()
-        }
-
-    @Test
-    fun testConnectionCache_clearsInvalidSubscriptions() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptionsFlow.launchIn(this)
-
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1, SUB_2))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            // Get repos to trigger caching
-            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
-            val repo2 = underTest.getRepoForSubId(SUB_2_ID)
-
-            assertThat(underTest.getSubIdRepoCache())
-                .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
-
-            // SUB_2 disappears
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
-
-            job.cancel()
-        }
-
-    @Test
-    fun testConnectionRepository_invalidSubId_throws() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptionsFlow.launchIn(this)
-
-            assertThrows(IllegalArgumentException::class.java) {
-                underTest.getRepoForSubId(SUB_1_ID)
-            }
-
-            job.cancel()
-        }
-
-    private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
-        val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
-        verify(subscriptionManager)
-            .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
-        return callbackCaptor.value!!
-    }
-
-    private fun getTelephonyCallbacks(): List<TelephonyCallback> {
-        val callbackCaptor = argumentCaptor<TelephonyCallback>()
-        verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
-        return callbackCaptor.allValues
-    }
-
-    private inline fun <reified T> getTelephonyCallbackForType(): T {
-        val cbs = getTelephonyCallbacks().filterIsInstance<T>()
-        assertThat(cbs.size).isEqualTo(1)
-        return cbs[0]
-    }
-
-    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 const val SUB_2_ID = 2
-        private val SUB_2 =
-            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
new file mode 100644
index 0000000..96a280a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -0,0 +1,219 @@
+/*
+ * 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.repository
+
+import android.net.ConnectivityManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+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
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * The switcher acts as a dispatcher to either the `prod` or `demo` versions of the repository
+ * interface it's switching on. These tests just need to verify that the entire interface properly
+ * switches over when the value of `demoMode` changes
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MobileRepositorySwitcherTest : SysuiTestCase() {
+    private lateinit var underTest: MobileRepositorySwitcher
+    private lateinit var realRepo: MobileConnectionsRepositoryImpl
+    private lateinit var demoRepo: DemoMobileConnectionsRepository
+    private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+
+    @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 demoModeController: DemoModeController
+
+    private val globalSettings = FakeSettings()
+    private val fakeNetworkEventsFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
+
+    private val scope = CoroutineScope(IMMEDIATE)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        // Never start in demo mode
+        whenever(demoModeController.isInDemoMode).thenReturn(false)
+
+        mockDataSource =
+            mock<DemoModeMobileConnectionDataSource>().also {
+                whenever(it.mobileEvents).thenReturn(fakeNetworkEventsFlow)
+            }
+
+        realRepo =
+            MobileConnectionsRepositoryImpl(
+                connectivityManager,
+                subscriptionManager,
+                telephonyManager,
+                logger,
+                fakeBroadcastDispatcher,
+                globalSettings,
+                context,
+                IMMEDIATE,
+                scope,
+                mock(),
+            )
+
+        demoRepo =
+            DemoMobileConnectionsRepository(
+                dataSource = mockDataSource,
+                scope = scope,
+                context = context,
+            )
+
+        underTest =
+            MobileRepositorySwitcher(
+                scope = scope,
+                realRepository = realRepo,
+                demoMobileConnectionsRepository = demoRepo,
+                demoModeController = demoModeController,
+            )
+    }
+
+    @After
+    fun tearDown() {
+        scope.cancel()
+    }
+
+    @Test
+    fun `active repo matches demo mode setting`() =
+        runBlocking(IMMEDIATE) {
+            whenever(demoModeController.isInDemoMode).thenReturn(false)
+
+            var latest: MobileConnectionsRepository? = null
+            val job = underTest.activeRepo.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(realRepo)
+
+            startDemoMode()
+
+            assertThat(latest).isEqualTo(demoRepo)
+
+            finishDemoMode()
+
+            assertThat(latest).isEqualTo(realRepo)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `subscription list updates when demo mode changes`() =
+        runBlocking(IMMEDIATE) {
+            whenever(demoModeController.isInDemoMode).thenReturn(false)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+            // The real subscriptions has 2 subs
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+            // Demo mode turns on, and we should see only the demo subscriptions
+            startDemoMode()
+            fakeNetworkEventsFlow.value = validMobileEvent(subId = 3)
+
+            // Demo mobile connections repository makes arbitrarily-formed subscription info
+            // objects, so just validate the data we care about
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].subscriptionId).isEqualTo(3)
+
+            finishDemoMode()
+
+            assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+            job.cancel()
+        }
+
+    private fun startDemoMode() {
+        whenever(demoModeController.isInDemoMode).thenReturn(true)
+        getDemoModeCallback().onDemoModeStarted()
+    }
+
+    private fun finishDemoMode() {
+        whenever(demoModeController.isInDemoMode).thenReturn(false)
+        getDemoModeCallback().onDemoModeFinished()
+    }
+
+    private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
+        val callbackCaptor =
+            kotlinArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+        verify(subscriptionManager)
+            .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
+        return callbackCaptor.value
+    }
+
+    private fun getDemoModeCallback(): DemoMode {
+        val captor = kotlinArgumentCaptor<DemoMode>()
+        verify(demoModeController).addCallback(captor.capture())
+        return captor.value
+    }
+
+    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 const val SUB_2_ID = 2
+        private val SUB_2 =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
new file mode 100644
index 0000000..bf5ecd8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -0,0 +1,249 @@
+/*
+ * 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.repository.demo
+
+import android.telephony.Annotation
+import android.telephony.TelephonyManager
+import androidx.test.filters.SmallTest
+import com.android.settingslib.SignalIcon
+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.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+/**
+ * Parameterized test for all of the common values of [FakeNetworkEventModel]. This test simply
+ * verifies that passing the given model to [DemoMobileConnectionsRepository] results in the correct
+ * flows emitting from the given connection.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(Parameterized::class)
+internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) :
+    SysuiTestCase() {
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
+
+    private lateinit var connectionsRepo: DemoMobileConnectionsRepository
+    private lateinit var underTest: DemoMobileConnectionRepository
+    private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+
+    @Before
+    fun setUp() {
+        // The data source only provides one API, so we can mock it with a flow here for convenience
+        mockDataSource =
+            mock<DemoModeMobileConnectionDataSource>().also {
+                whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow)
+            }
+
+        connectionsRepo =
+            DemoMobileConnectionsRepository(
+                dataSource = mockDataSource,
+                scope = testScope.backgroundScope,
+                context = context,
+            )
+
+        connectionsRepo.startProcessingCommands()
+    }
+
+    @After
+    fun tearDown() {
+        testScope.cancel()
+    }
+
+    @Test
+    fun demoNetworkData() =
+        testScope.runTest {
+            val networkModel =
+                FakeNetworkEventModel.Mobile(
+                    level = testCase.level,
+                    dataType = testCase.dataType,
+                    subId = testCase.subId,
+                    carrierId = testCase.carrierId,
+                    inflateStrength = testCase.inflateStrength,
+                    activity = testCase.activity,
+                    carrierNetworkChange = testCase.carrierNetworkChange,
+                )
+
+            fakeNetworkEventFlow.value = networkModel
+            underTest = connectionsRepo.getRepoForSubId(subId)
+
+            assertConnection(underTest, networkModel)
+        }
+
+    private fun assertConnection(
+        conn: DemoMobileConnectionRepository,
+        model: FakeNetworkEventModel
+    ) {
+        when (model) {
+            is FakeNetworkEventModel.Mobile -> {
+                val subscriptionModel: MobileSubscriptionModel = conn.subscriptionModelFlow.value
+                assertThat(conn.subId).isEqualTo(model.subId)
+                assertThat(subscriptionModel.cdmaLevel).isEqualTo(model.level)
+                assertThat(subscriptionModel.primaryLevel).isEqualTo(model.level)
+                assertThat(subscriptionModel.dataActivityDirection).isEqualTo(model.activity)
+                assertThat(subscriptionModel.carrierNetworkChangeActive)
+                    .isEqualTo(model.carrierNetworkChange)
+
+                // TODO(b/261029387): check these once we start handling them
+                assertThat(subscriptionModel.isEmergencyOnly).isFalse()
+                assertThat(subscriptionModel.isGsm).isFalse()
+                assertThat(subscriptionModel.dataConnectionState)
+                    .isEqualTo(DataConnectionState.Connected)
+            }
+            // MobileDisabled isn't combinatorial in nature, and is tested in
+            // DemoMobileConnectionsRepositoryTest.kt
+            else -> {}
+        }
+    }
+
+    /** Matches [FakeNetworkEventModel] */
+    internal data class TestCase(
+        val level: Int,
+        val dataType: SignalIcon.MobileIconGroup,
+        val subId: Int,
+        val carrierId: Int,
+        val inflateStrength: Boolean,
+        @Annotation.DataActivityType val activity: Int,
+        val carrierNetworkChange: Boolean,
+    ) {
+        override fun toString(): String {
+            return "INPUT(level=$level, " +
+                "dataType=${dataType.name}, " +
+                "subId=$subId, " +
+                "carrierId=$carrierId, " +
+                "inflateStrength=$inflateStrength, " +
+                "activity=$activity, " +
+                "carrierNetworkChange=$carrierNetworkChange)"
+        }
+
+        // Convenience for iterating test data and creating new cases
+        fun modifiedBy(
+            level: Int? = null,
+            dataType: SignalIcon.MobileIconGroup? = null,
+            subId: Int? = null,
+            carrierId: Int? = null,
+            inflateStrength: Boolean? = null,
+            @Annotation.DataActivityType activity: Int? = null,
+            carrierNetworkChange: Boolean? = null,
+        ): TestCase =
+            TestCase(
+                level = level ?: this.level,
+                dataType = dataType ?: this.dataType,
+                subId = subId ?: this.subId,
+                carrierId = carrierId ?: this.carrierId,
+                inflateStrength = inflateStrength ?: this.inflateStrength,
+                activity = activity ?: this.activity,
+                carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange
+            )
+    }
+
+    companion object {
+        private val subId = 1
+
+        private val booleanList = listOf(true, false)
+        private val levels = listOf(0, 1, 2, 3)
+        private val dataTypes =
+            listOf(
+                TelephonyIcons.THREE_G,
+                TelephonyIcons.LTE,
+                TelephonyIcons.FOUR_G,
+                TelephonyIcons.NR_5G,
+                TelephonyIcons.NR_5G_PLUS,
+            )
+        private val carrierIds = listOf(1, 10, 100)
+        private val inflateStrength = booleanList
+        private val activity =
+            listOf(
+                TelephonyManager.DATA_ACTIVITY_NONE,
+                TelephonyManager.DATA_ACTIVITY_IN,
+                TelephonyManager.DATA_ACTIVITY_OUT,
+                TelephonyManager.DATA_ACTIVITY_INOUT
+            )
+        private val carrierNetworkChange = booleanList
+
+        @Parameters(name = "{0}") @JvmStatic fun data() = testData()
+
+        /**
+         * Generate some test data. For the sake of convenience, we'll parameterize only non-null
+         * network event data. So given the lists of test data:
+         * ```
+         *    list1 = [1, 2, 3]
+         *    list2 = [false, true]
+         *    list3 = [a, b, c]
+         * ```
+         * We'll generate test cases for:
+         *
+         * Test (1, false, a) Test (2, false, a) Test (3, false, a) Test (1, true, a) Test (1,
+         * false, b) Test (1, false, c)
+         *
+         * NOTE: this is not a combinatorial product of all of the possible sets of parameters.
+         * Since this test is built to exercise demo mode, the general approach is to define a
+         * fully-formed "base case", and from there to make sure to use every valid parameter once,
+         * by defining the rest of the test cases against the base case. Specific use-cases can be
+         * added to the non-parameterized test, or manually below the generated test cases.
+         */
+        private fun testData(): List<TestCase> {
+            val testSet = mutableSetOf<TestCase>()
+
+            val baseCase =
+                TestCase(
+                    levels.first(),
+                    dataTypes.first(),
+                    subId,
+                    carrierIds.first(),
+                    inflateStrength.first(),
+                    activity.first(),
+                    carrierNetworkChange.first()
+                )
+
+            val tail =
+                sequenceOf(
+                        levels.map { baseCase.modifiedBy(level = it) },
+                        dataTypes.map { baseCase.modifiedBy(dataType = it) },
+                        carrierIds.map { baseCase.modifiedBy(carrierId = it) },
+                        inflateStrength.map { baseCase.modifiedBy(inflateStrength = it) },
+                        activity.map { baseCase.modifiedBy(activity = it) },
+                        carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
+                    )
+                    .flatten()
+
+            testSet.add(baseCase)
+            tail.toCollection(testSet)
+
+            return testSet.toList()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
new file mode 100644
index 0000000..a8f6993
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -0,0 +1,306 @@
+/*
+ * 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.repository.demo
+
+import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
+import androidx.test.filters.SmallTest
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons.THREE_G
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
+
+    private lateinit var underTest: DemoMobileConnectionsRepository
+    private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+
+    @Before
+    fun setUp() {
+        // The data source only provides one API, so we can mock it with a flow here for convenience
+        mockDataSource =
+            mock<DemoModeMobileConnectionDataSource>().also {
+                whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow)
+            }
+
+        underTest =
+            DemoMobileConnectionsRepository(
+                dataSource = mockDataSource,
+                scope = testScope.backgroundScope,
+                context = context,
+            )
+
+        underTest.startProcessingCommands()
+    }
+
+    @Test
+    fun `network event - create new subscription`() =
+        testScope.runTest {
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEmpty()
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1)
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].subscriptionId).isEqualTo(1)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `network event - reuses subscription when same Id`() =
+        testScope.runTest {
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEmpty()
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].subscriptionId).isEqualTo(1)
+
+            // Second network event comes in with the same subId, does not create a new subscription
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 2)
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].subscriptionId).isEqualTo(1)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `multiple subscriptions`() =
+        testScope.runTest {
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1)
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 2)
+
+            assertThat(latest).hasSize(2)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `mobile disabled event - disables connection - subId specified - single conn`() =
+        testScope.runTest {
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+
+            fakeNetworkEventFlow.value = MobileDisabled(subId = 1)
+
+            assertThat(latest).hasSize(0)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `mobile disabled event - disables connection - subId not specified - single conn`() =
+        testScope.runTest {
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+
+            fakeNetworkEventFlow.value = MobileDisabled(subId = null)
+
+            assertThat(latest).hasSize(0)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `mobile disabled event - disables connection - subId specified - multiple conn`() =
+        testScope.runTest {
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1)
+
+            fakeNetworkEventFlow.value = MobileDisabled(subId = 2)
+
+            assertThat(latest).hasSize(1)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `mobile disabled event - subId not specified - multiple conn - ignores command`() =
+        testScope.runTest {
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1)
+
+            fakeNetworkEventFlow.value = MobileDisabled(subId = null)
+
+            assertThat(latest).hasSize(2)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `demo connection - single subscription`() =
+        testScope.runTest {
+            var currentEvent: FakeNetworkEventModel = validMobileEvent(subId = 1)
+            var connections: List<DemoMobileConnectionRepository>? = null
+            val job =
+                underTest.subscriptionsFlow
+                    .onEach { infos ->
+                        connections =
+                            infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) }
+                    }
+                    .launchIn(this)
+
+            fakeNetworkEventFlow.value = currentEvent
+
+            assertThat(connections).hasSize(1)
+            val connection1 = connections!![0]
+
+            assertConnection(connection1, currentEvent)
+
+            // Exercise the whole api
+
+            currentEvent = validMobileEvent(subId = 1, level = 2)
+            fakeNetworkEventFlow.value = currentEvent
+            assertConnection(connection1, currentEvent)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `demo connection - two connections - update second - no affect on first`() =
+        testScope.runTest {
+            var currentEvent1 = validMobileEvent(subId = 1)
+            var connection1: DemoMobileConnectionRepository? = null
+            var currentEvent2 = validMobileEvent(subId = 2)
+            var connection2: DemoMobileConnectionRepository? = null
+            var connections: List<DemoMobileConnectionRepository>? = null
+            val job =
+                underTest.subscriptionsFlow
+                    .onEach { infos ->
+                        connections =
+                            infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) }
+                    }
+                    .launchIn(this)
+
+            fakeNetworkEventFlow.value = currentEvent1
+            fakeNetworkEventFlow.value = currentEvent2
+            assertThat(connections).hasSize(2)
+            connections!!.forEach {
+                if (it.subId == 1) {
+                    connection1 = it
+                } else if (it.subId == 2) {
+                    connection2 = it
+                } else {
+                    Assert.fail("Unexpected subscription")
+                }
+            }
+
+            assertConnection(connection1!!, currentEvent1)
+            assertConnection(connection2!!, currentEvent2)
+
+            // WHEN the event changes for connection 2, it updates, and connection 1 stays the same
+            currentEvent2 = validMobileEvent(subId = 2, activity = DATA_ACTIVITY_INOUT)
+            fakeNetworkEventFlow.value = currentEvent2
+            assertConnection(connection1!!, currentEvent1)
+            assertConnection(connection2!!, currentEvent2)
+
+            // and vice versa
+            currentEvent1 = validMobileEvent(subId = 1, inflateStrength = true)
+            fakeNetworkEventFlow.value = currentEvent1
+            assertConnection(connection1!!, currentEvent1)
+            assertConnection(connection2!!, currentEvent2)
+
+            job.cancel()
+        }
+
+    private fun assertConnection(
+        conn: DemoMobileConnectionRepository,
+        model: FakeNetworkEventModel
+    ) {
+        when (model) {
+            is FakeNetworkEventModel.Mobile -> {
+                val subscriptionModel: MobileSubscriptionModel = conn.subscriptionModelFlow.value
+                assertThat(conn.subId).isEqualTo(model.subId)
+                assertThat(subscriptionModel.cdmaLevel).isEqualTo(model.level)
+                assertThat(subscriptionModel.primaryLevel).isEqualTo(model.level)
+                assertThat(subscriptionModel.dataActivityDirection).isEqualTo(model.activity)
+                assertThat(subscriptionModel.carrierNetworkChangeActive)
+                    .isEqualTo(model.carrierNetworkChange)
+
+                // TODO(b/261029387) check these once we start handling them
+                assertThat(subscriptionModel.isEmergencyOnly).isFalse()
+                assertThat(subscriptionModel.isGsm).isFalse()
+                assertThat(subscriptionModel.dataConnectionState)
+                    .isEqualTo(DataConnectionState.Connected)
+            }
+            else -> {}
+        }
+    }
+}
+
+/** Convenience to create a valid fake network event with minimal params */
+fun validMobileEvent(
+    level: Int? = 1,
+    dataType: SignalIcon.MobileIconGroup? = THREE_G,
+    subId: Int? = 1,
+    carrierId: Int? = UNKNOWN_CARRIER_ID,
+    inflateStrength: Boolean? = false,
+    activity: Int? = null,
+    carrierNetworkChange: Boolean = false,
+): FakeNetworkEventModel =
+    FakeNetworkEventModel.Mobile(
+        level = level,
+        dataType = dataType,
+        subId = subId,
+        carrierId = carrierId,
+        inflateStrength = inflateStrength,
+        activity = activity,
+        carrierNetworkChange = carrierNetworkChange,
+    )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
new file mode 100644
index 0000000..c8df5ac
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -0,0 +1,431 @@
+/*
+ * 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.repository.prod
+
+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.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.DATA_UNKNOWN
+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
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+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.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.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileConnectionRepositoryTest : SysuiTestCase() {
+    private lateinit var underTest: MobileConnectionRepositoryImpl
+
+    @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,
+            )
+    }
+
+    @After
+    fun tearDown() {
+        scope.cancel()
+    }
+
+    @Test
+    fun testFlowForSubId_default() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(MobileSubscriptionModel())
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_emergencyOnly() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+            val serviceState = ServiceState()
+            serviceState.isEmergencyOnly = true
+
+            getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+            assertThat(latest?.isEmergencyOnly).isEqualTo(true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_emergencyOnly_toggles() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<ServiceStateListener>()
+            val serviceState = ServiceState()
+            serviceState.isEmergencyOnly = true
+            callback.onServiceStateChanged(serviceState)
+            serviceState.isEmergencyOnly = false
+            callback.onServiceStateChanged(serviceState)
+
+            assertThat(latest?.isEmergencyOnly).isEqualTo(false)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_signalStrengths_levelsUpdate() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+            val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+            callback.onSignalStrengthsChanged(strength)
+
+            assertThat(latest?.isGsm).isEqualTo(true)
+            assertThat(latest?.primaryLevel).isEqualTo(1)
+            assertThat(latest?.cdmaLevel).isEqualTo(2)
+
+            job.cancel()
+        }
+
+    @Test
+    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(DATA_CONNECTED, 200 /* unused */)
+
+            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()
+        }
+
+    @Test
+    fun testFlowForSubId_dataConnectionState_unknown() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+            val callback =
+                getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+            callback.onDataConnectionStateChanged(DATA_UNKNOWN, 200 /* unused */)
+
+            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Unknown)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_dataActivity() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<TelephonyCallback.DataActivityListener>()
+            callback.onDataActivity(3)
+
+            assertThat(latest?.dataActivityDirection).isEqualTo(3)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_carrierNetworkChange() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
+            callback.onCarrierNetworkChange(true)
+
+            assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun subscriptionFlow_networkType_default() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+            val type = NETWORK_TYPE_UNKNOWN
+            val expected = DefaultNetworkType(type)
+
+            assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+
+            job.cancel()
+        }
+
+    @Test
+    fun subscriptionFlow_networkType_updatesUsingDefault() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+            val type = NETWORK_TYPE_LTE
+            val expected = DefaultNetworkType(type)
+            val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+            callback.onDisplayInfoChanged(ti)
+
+            assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+
+            job.cancel()
+        }
+
+    @Test
+    fun subscriptionFlow_networkType_updatesUsingOverride() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+            val type = OVERRIDE_NETWORK_TYPE_LTE_CA
+            val expected = OverrideNetworkType(type)
+            val ti =
+                mock<TelephonyDisplayInfo>().also {
+                    whenever(it.overrideNetworkType).thenReturn(type)
+                }
+            callback.onDisplayInfoChanged(ti)
+
+            assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+
+            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())
+        return callbackCaptor.allValues
+    }
+
+    private inline fun <reified T> getTelephonyCallbackForType(): T {
+        val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+        assertThat(cbs.size).isEqualTo(1)
+        return cbs[0]
+    }
+
+    /** Convenience constructor for SignalStrength */
+    private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
+        val signalStrength = mock<SignalStrength>()
+        whenever(signalStrength.isGsm).thenReturn(isGsm)
+        whenever(signalStrength.level).thenReturn(gsmLevel)
+        val cdmaStrength =
+            mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
+        whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
+            .thenReturn(listOf(cdmaStrength))
+
+        return signalStrength
+    }
+
+    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) }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
new file mode 100644
index 0000000..359ea18
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -0,0 +1,382 @@
+/*
+ * 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.repository.prod
+
+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.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.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.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+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
+
+    private val scope = CoroutineScope(IMMEDIATE)
+    private val globalSettings = FakeSettings()
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            MobileConnectionsRepositoryImpl(
+                connectivityManager,
+                subscriptionManager,
+                telephonyManager,
+                logger,
+                fakeBroadcastDispatcher,
+                globalSettings,
+                context,
+                IMMEDIATE,
+                scope,
+                mock(),
+            )
+    }
+
+    @After
+    fun tearDown() {
+        scope.cancel()
+    }
+
+    @Test
+    fun testSubscriptions_initiallyEmpty() =
+        runBlocking(IMMEDIATE) {
+            assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
+        }
+
+    @Test
+    fun testSubscriptions_listUpdates() =
+        runBlocking(IMMEDIATE) {
+            var latest: List<SubscriptionInfo>? = null
+
+            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+            job.cancel()
+        }
+
+    @Test
+    fun testSubscriptions_removingSub_updatesList() =
+        runBlocking(IMMEDIATE) {
+            var latest: List<SubscriptionInfo>? = null
+
+            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+            // WHEN 2 networks show up
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // WHEN one network is removed
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // THEN the subscriptions list represents the newest change
+            assertThat(latest).isEqualTo(listOf(SUB_2))
+
+            job.cancel()
+        }
+
+    @Test
+    fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
+        runBlocking(IMMEDIATE) {
+            assertThat(underTest.activeMobileDataSubscriptionId.value)
+                .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+        }
+
+    @Test
+    fun testActiveDataSubscriptionId_updates() =
+        runBlocking(IMMEDIATE) {
+            var active: Int? = null
+
+            val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
+
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+            assertThat(active).isEqualTo(SUB_2_ID)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testConnectionRepository_validSubId_isCached() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.subscriptionsFlow.launchIn(this)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+            val repo2 = underTest.getRepoForSubId(SUB_1_ID)
+
+            assertThat(repo1).isSameInstanceAs(repo2)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testConnectionCache_clearsInvalidSubscriptions() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.subscriptionsFlow.launchIn(this)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // Get repos to trigger caching
+            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+            val repo2 = underTest.getRepoForSubId(SUB_2_ID)
+
+            assertThat(underTest.getSubIdRepoCache())
+                .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
+
+            // SUB_2 disappears
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testConnectionRepository_invalidSubId_throws() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.subscriptionsFlow.launchIn(this)
+
+            assertThrows(IllegalArgumentException::class.java) {
+                underTest.getRepoForSubId(SUB_1_ID)
+            }
+
+            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)
+            .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
+        return callbackCaptor.value!!
+    }
+
+    private fun getTelephonyCallbacks(): List<TelephonyCallback> {
+        val callbackCaptor = argumentCaptor<TelephonyCallback>()
+        verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+        return callbackCaptor.allValues
+    }
+
+    private inline fun <reified T> getTelephonyCallbackForType(): T {
+        val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+        assertThat(cbs.size).isEqualTo(1)
+        return cbs[0]
+    }
+
+    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 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 5611c44..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
@@ -28,18 +28,23 @@
     private val _isEmergencyOnly = MutableStateFlow(false)
     override val isEmergencyOnly = _isEmergencyOnly
 
+    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(4)
     override val numberOfLevels = _numberOfLevels
 
-    private val _cutOut = MutableStateFlow(false)
-    override val cutOut = _cutOut
-
     fun setIconGroup(group: SignalIcon.MobileIconGroup) {
         _iconGroup.value = group
     }
@@ -52,6 +57,14 @@
         _isDataEnabled.value = enabled
     }
 
+    fun setIsDefaultDataEnabled(disabled: Boolean) {
+        _isDefaultDataEnabled.value = disabled
+    }
+
+    fun setIsFailedConnection(failed: Boolean) {
+        _isFailedConnection.value = failed
+    }
+
     fun setLevel(level: Int) {
         _level.value = level
     }
@@ -59,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 877ce0e..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,10 @@
 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
@@ -32,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
@@ -168,6 +171,92 @@
             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
 
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 ce0f33f..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
@@ -46,10 +46,12 @@
         MockitoAnnotations.initMocks(this)
         interactor.apply {
             setLevel(1)
-            setCutOut(false)
+            setIsDefaultDataEnabled(true)
+            setIsFailedConnection(false)
             setIconGroup(THREE_G)
             setIsEmergencyOnly(false)
             setNumberOfLevels(4)
+            isDataConnected.value = true
         }
         underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
     }
@@ -59,8 +61,23 @@
         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()
         }
@@ -97,6 +114,44 @@
         }
 
     @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 =
@@ -119,6 +174,14 @@
             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/mobile/util/FakeMobileMappingsProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
index 6d8d902..a052008 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
@@ -16,31 +16,59 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.util
 
+import android.telephony.TelephonyDisplayInfo
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.MobileMappings.Config
 import com.android.settingslib.mobile.TelephonyIcons
 
 class FakeMobileMappingsProxy : MobileMappingsProxy {
+    // The old [NetworkControllerDataTest] infra requires us to be able to use the real
+    // impl sometimes
+    var useRealImpl = false
+
+    private var realImpl = MobileMappingsProxyImpl()
     private var iconMap = mapOf<String, MobileIconGroup>()
     private var defaultIcons = TelephonyIcons.THREE_G
 
     fun setIconMap(map: Map<String, MobileIconGroup>) {
         iconMap = map
     }
-    override fun mapIconSets(config: Config): Map<String, MobileIconGroup> = iconMap
+    override fun mapIconSets(config: Config): Map<String, MobileIconGroup> {
+        if (useRealImpl) {
+            return realImpl.mapIconSets(config)
+        }
+        return iconMap
+    }
     fun getIconMap() = iconMap
 
     fun setDefaultIcons(group: MobileIconGroup) {
         defaultIcons = group
     }
-    override fun getDefaultIcons(config: Config): MobileIconGroup = defaultIcons
+    override fun getDefaultIcons(config: Config): MobileIconGroup {
+        if (useRealImpl) {
+            return realImpl.getDefaultIcons(config)
+        }
+        return defaultIcons
+    }
+
+    /** This is only used in the old pipeline, use the real impl always */
+    override fun getIconKey(displayInfo: TelephonyDisplayInfo): String {
+        return realImpl.getIconKey(displayInfo)
+    }
+
     fun getDefaultIcons(): MobileIconGroup = defaultIcons
 
     override fun toIconKey(networkType: Int): String {
+        if (useRealImpl) {
+            return realImpl.toIconKeyOverride(networkType)
+        }
         return networkType.toString()
     }
 
     override fun toIconKeyOverride(networkType: Int): String {
+        if (useRealImpl) {
+            return realImpl.toIconKeyOverride(networkType)
+        }
         return toIconKey(networkType) + "_override"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
index 3d29d2b..30fd308 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
@@ -18,8 +18,10 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableRowLogger
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MIN_VALID_LEVEL
+import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 
 @SmallTest
@@ -48,6 +50,125 @@
         WifiNetworkModel.Active(NETWORK_ID, level = MAX_VALID_LEVEL + 1)
     }
 
+    // Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken
+
+    @Test
+    fun logDiffs_inactiveToActive_logsAllActiveFields() {
+        val logger = TestLogger()
+        val activeNetwork =
+            WifiNetworkModel.Active(
+                networkId = 5,
+                isValidated = true,
+                level = 3,
+                ssid = "Test SSID"
+            )
+
+        activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger)
+
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5"))
+        assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
+        assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
+        assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+    }
+    @Test
+    fun logDiffs_activeToInactive_resetsAllActiveFields() {
+        val logger = TestLogger()
+        val activeNetwork =
+            WifiNetworkModel.Active(
+                networkId = 5,
+                isValidated = true,
+                level = 3,
+                ssid = "Test SSID"
+            )
+
+        WifiNetworkModel.Inactive.logDiffs(prevVal = activeNetwork, logger)
+
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE))
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
+        assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
+        assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
+        assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+    }
+
+    @Test
+    fun logDiffs_carrierMergedToActive_logsAllActiveFields() {
+        val logger = TestLogger()
+        val activeNetwork =
+            WifiNetworkModel.Active(
+                networkId = 5,
+                isValidated = true,
+                level = 3,
+                ssid = "Test SSID"
+            )
+
+        activeNetwork.logDiffs(prevVal = WifiNetworkModel.CarrierMerged, logger)
+
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5"))
+        assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
+        assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
+        assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+    }
+    @Test
+    fun logDiffs_activeToCarrierMerged_resetsAllActiveFields() {
+        val logger = TestLogger()
+        val activeNetwork =
+            WifiNetworkModel.Active(
+                networkId = 5,
+                isValidated = true,
+                level = 3,
+                ssid = "Test SSID"
+            )
+
+        WifiNetworkModel.CarrierMerged.logDiffs(prevVal = activeNetwork, logger)
+
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED))
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
+        assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
+        assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
+        assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+    }
+
+    @Test
+    fun logDiffs_activeChangesLevel_onlyLevelLogged() {
+        val logger = TestLogger()
+        val prevActiveNetwork =
+            WifiNetworkModel.Active(
+                networkId = 5,
+                isValidated = true,
+                level = 3,
+                ssid = "Test SSID"
+            )
+        val newActiveNetwork =
+            WifiNetworkModel.Active(
+                networkId = 5,
+                isValidated = true,
+                level = 2,
+                ssid = "Test SSID"
+            )
+
+        newActiveNetwork.logDiffs(prevActiveNetwork, logger)
+
+        assertThat(logger.changes).isEqualTo(listOf(Pair(COL_LEVEL, "2")))
+    }
+
+    private class TestLogger : TableRowLogger {
+        val changes = mutableListOf<Pair<String, String>>()
+
+        override fun logChange(columnName: String, value: String?) {
+            changes.add(Pair(columnName, value.toString()))
+        }
+
+        override fun logChange(columnName: String, value: Int) {
+            changes.add(Pair(columnName, value.toString()))
+        }
+
+        override fun logChange(columnName: String, value: Boolean) {
+            changes.add(Pair(columnName, value.toString()))
+        }
+    }
+
     companion object {
         private const val NETWORK_ID = 2
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index a64a4bd..800f3c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -29,6 +29,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
@@ -69,6 +70,7 @@
 
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogger: TableLogBuffer
     @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var wifiManager: WifiManager
     private lateinit var executor: Executor
@@ -804,6 +806,7 @@
             broadcastDispatcher,
             connectivityManager,
             logger,
+            tableLogger,
             executor,
             scope,
             wifiManagerToUse,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
new file mode 100644
index 0000000..b38497a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -0,0 +1,283 @@
+/*
+ * 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.domain.interactor
+
+import android.net.wifi.WifiManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
+import com.google.common.truth.Truth.assertThat
+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.yield
+import org.junit.Before
+import org.junit.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@SmallTest
+class WifiInteractorImplTest : SysuiTestCase() {
+
+    private lateinit var underTest: WifiInteractor
+
+    private lateinit var connectivityRepository: FakeConnectivityRepository
+    private lateinit var wifiRepository: FakeWifiRepository
+
+    @Before
+    fun setUp() {
+        connectivityRepository = FakeConnectivityRepository()
+        wifiRepository = FakeWifiRepository()
+        underTest = WifiInteractorImpl(connectivityRepository, wifiRepository)
+    }
+
+    @Test
+    fun ssid_inactiveNetwork_outputsNull() = runBlocking(IMMEDIATE) {
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+
+        var latest: String? = "default"
+        val job = underTest
+            .ssid
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isNull()
+
+        job.cancel()
+    }
+
+    @Test
+    fun ssid_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
+        wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
+
+        var latest: String? = "default"
+        val job = underTest
+            .ssid
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isNull()
+
+        job.cancel()
+    }
+
+    @Test
+    fun ssid_isPasspointAccessPoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+            networkId = 1,
+            isPasspointAccessPoint = true,
+            passpointProviderFriendlyName = "friendly",
+        ))
+
+        var latest: String? = null
+        val job = underTest
+            .ssid
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isEqualTo("friendly")
+
+        job.cancel()
+    }
+
+    @Test
+    fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+            networkId = 1,
+            isOnlineSignUpForPasspointAccessPoint = true,
+            passpointProviderFriendlyName = "friendly",
+        ))
+
+        var latest: String? = null
+        val job = underTest
+            .ssid
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isEqualTo("friendly")
+
+        job.cancel()
+    }
+
+    @Test
+    fun ssid_unknownSsid_outputsNull() = runBlocking(IMMEDIATE) {
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+            networkId = 1,
+            ssid = WifiManager.UNKNOWN_SSID,
+        ))
+
+        var latest: String? = "default"
+        val job = underTest
+            .ssid
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isNull()
+
+        job.cancel()
+    }
+
+    @Test
+    fun ssid_validSsid_outputsSsid() = runBlocking(IMMEDIATE) {
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+            networkId = 1,
+            ssid = "MyAwesomeWifiNetwork",
+        ))
+
+        var latest: String? = null
+        val job = underTest
+            .ssid
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isEqualTo("MyAwesomeWifiNetwork")
+
+        job.cancel()
+    }
+
+    @Test
+    fun isEnabled_matchesRepoIsEnabled() = runBlocking(IMMEDIATE) {
+        var latest: Boolean? = null
+        val job = underTest
+            .isEnabled
+            .onEach { latest = it }
+            .launchIn(this)
+
+        wifiRepository.setIsWifiEnabled(true)
+        yield()
+        assertThat(latest).isTrue()
+
+        wifiRepository.setIsWifiEnabled(false)
+        yield()
+        assertThat(latest).isFalse()
+
+        wifiRepository.setIsWifiEnabled(true)
+        yield()
+        assertThat(latest).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun isDefault_matchesRepoIsDefault() = runBlocking(IMMEDIATE) {
+        var latest: Boolean? = null
+        val job = underTest
+            .isDefault
+            .onEach { latest = it }
+            .launchIn(this)
+
+        wifiRepository.setIsWifiDefault(true)
+        yield()
+        assertThat(latest).isTrue()
+
+        wifiRepository.setIsWifiDefault(false)
+        yield()
+        assertThat(latest).isFalse()
+
+        wifiRepository.setIsWifiDefault(true)
+        yield()
+        assertThat(latest).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_matchesRepoWifiNetwork() = runBlocking(IMMEDIATE) {
+        val wifiNetwork = WifiNetworkModel.Active(
+            networkId = 45,
+            isValidated = true,
+            level = 3,
+            ssid = "AB",
+            passpointProviderFriendlyName = "friendly"
+        )
+        wifiRepository.setWifiNetwork(wifiNetwork)
+
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isEqualTo(wifiNetwork)
+
+        job.cancel()
+    }
+
+    @Test
+    fun activity_matchesRepoWifiActivity() = runBlocking(IMMEDIATE) {
+        var latest: WifiActivityModel? = null
+        val job = underTest
+            .activity
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val activity1 = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        wifiRepository.setWifiActivity(activity1)
+        yield()
+        assertThat(latest).isEqualTo(activity1)
+
+        val activity2 = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+        wifiRepository.setWifiActivity(activity2)
+        yield()
+        assertThat(latest).isEqualTo(activity2)
+
+        val activity3 = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        wifiRepository.setWifiActivity(activity3)
+        yield()
+        assertThat(latest).isEqualTo(activity3)
+
+        job.cancel()
+    }
+
+    @Test
+    fun isForceHidden_repoHasWifiHidden_outputsTrue() = runBlocking(IMMEDIATE) {
+        connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
+
+        var latest: Boolean? = null
+        val job = underTest
+            .isForceHidden
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun isForceHidden_repoDoesNotHaveWifiHidden_outputsFalse() = runBlocking(IMMEDIATE) {
+        connectivityRepository.setForceHiddenIcons(setOf())
+
+        var latest: Boolean? = null
+        val job = underTest
+            .isForceHidden
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
deleted file mode 100644
index 71b8bab..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
+++ /dev/null
@@ -1,282 +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.statusbar.pipeline.wifi.domain.interactor
-
-import android.net.wifi.WifiManager
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
-import com.google.common.truth.Truth.assertThat
-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.yield
-import org.junit.Before
-import org.junit.Test
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-class WifiInteractorTest : SysuiTestCase() {
-
-    private lateinit var underTest: WifiInteractor
-
-    private lateinit var connectivityRepository: FakeConnectivityRepository
-    private lateinit var wifiRepository: FakeWifiRepository
-
-    @Before
-    fun setUp() {
-        connectivityRepository = FakeConnectivityRepository()
-        wifiRepository = FakeWifiRepository()
-        underTest = WifiInteractor(connectivityRepository, wifiRepository)
-    }
-
-    @Test
-    fun ssid_inactiveNetwork_outputsNull() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
-
-        var latest: String? = "default"
-        val job = underTest
-            .ssid
-            .onEach { latest = it }
-            .launchIn(this)
-
-        assertThat(latest).isNull()
-
-        job.cancel()
-    }
-
-    @Test
-    fun ssid_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
-
-        var latest: String? = "default"
-        val job = underTest
-            .ssid
-            .onEach { latest = it }
-            .launchIn(this)
-
-        assertThat(latest).isNull()
-
-        job.cancel()
-    }
-
-    @Test
-    fun ssid_isPasspointAccessPoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
-            networkId = 1,
-            isPasspointAccessPoint = true,
-            passpointProviderFriendlyName = "friendly",
-        ))
-
-        var latest: String? = null
-        val job = underTest
-            .ssid
-            .onEach { latest = it }
-            .launchIn(this)
-
-        assertThat(latest).isEqualTo("friendly")
-
-        job.cancel()
-    }
-
-    @Test
-    fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
-            networkId = 1,
-            isOnlineSignUpForPasspointAccessPoint = true,
-            passpointProviderFriendlyName = "friendly",
-        ))
-
-        var latest: String? = null
-        val job = underTest
-            .ssid
-            .onEach { latest = it }
-            .launchIn(this)
-
-        assertThat(latest).isEqualTo("friendly")
-
-        job.cancel()
-    }
-
-    @Test
-    fun ssid_unknownSsid_outputsNull() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
-            networkId = 1,
-            ssid = WifiManager.UNKNOWN_SSID,
-        ))
-
-        var latest: String? = "default"
-        val job = underTest
-            .ssid
-            .onEach { latest = it }
-            .launchIn(this)
-
-        assertThat(latest).isNull()
-
-        job.cancel()
-    }
-
-    @Test
-    fun ssid_validSsid_outputsSsid() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
-            networkId = 1,
-            ssid = "MyAwesomeWifiNetwork",
-        ))
-
-        var latest: String? = null
-        val job = underTest
-            .ssid
-            .onEach { latest = it }
-            .launchIn(this)
-
-        assertThat(latest).isEqualTo("MyAwesomeWifiNetwork")
-
-        job.cancel()
-    }
-
-    @Test
-    fun isEnabled_matchesRepoIsEnabled() = runBlocking(IMMEDIATE) {
-        var latest: Boolean? = null
-        val job = underTest
-            .isEnabled
-            .onEach { latest = it }
-            .launchIn(this)
-
-        wifiRepository.setIsWifiEnabled(true)
-        yield()
-        assertThat(latest).isTrue()
-
-        wifiRepository.setIsWifiEnabled(false)
-        yield()
-        assertThat(latest).isFalse()
-
-        wifiRepository.setIsWifiEnabled(true)
-        yield()
-        assertThat(latest).isTrue()
-
-        job.cancel()
-    }
-
-    @Test
-    fun isDefault_matchesRepoIsDefault() = runBlocking(IMMEDIATE) {
-        var latest: Boolean? = null
-        val job = underTest
-            .isDefault
-            .onEach { latest = it }
-            .launchIn(this)
-
-        wifiRepository.setIsWifiDefault(true)
-        yield()
-        assertThat(latest).isTrue()
-
-        wifiRepository.setIsWifiDefault(false)
-        yield()
-        assertThat(latest).isFalse()
-
-        wifiRepository.setIsWifiDefault(true)
-        yield()
-        assertThat(latest).isTrue()
-
-        job.cancel()
-    }
-
-    @Test
-    fun wifiNetwork_matchesRepoWifiNetwork() = runBlocking(IMMEDIATE) {
-        val wifiNetwork = WifiNetworkModel.Active(
-            networkId = 45,
-            isValidated = true,
-            level = 3,
-            ssid = "AB",
-            passpointProviderFriendlyName = "friendly"
-        )
-        wifiRepository.setWifiNetwork(wifiNetwork)
-
-        var latest: WifiNetworkModel? = null
-        val job = underTest
-            .wifiNetwork
-            .onEach { latest = it }
-            .launchIn(this)
-
-        assertThat(latest).isEqualTo(wifiNetwork)
-
-        job.cancel()
-    }
-
-    @Test
-    fun activity_matchesRepoWifiActivity() = runBlocking(IMMEDIATE) {
-        var latest: WifiActivityModel? = null
-        val job = underTest
-            .activity
-            .onEach { latest = it }
-            .launchIn(this)
-
-        val activity1 = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
-        wifiRepository.setWifiActivity(activity1)
-        yield()
-        assertThat(latest).isEqualTo(activity1)
-
-        val activity2 = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
-        wifiRepository.setWifiActivity(activity2)
-        yield()
-        assertThat(latest).isEqualTo(activity2)
-
-        val activity3 = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
-        wifiRepository.setWifiActivity(activity3)
-        yield()
-        assertThat(latest).isEqualTo(activity3)
-
-        job.cancel()
-    }
-
-    @Test
-    fun isForceHidden_repoHasWifiHidden_outputsTrue() = runBlocking(IMMEDIATE) {
-        connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
-
-        var latest: Boolean? = null
-        val job = underTest
-            .isForceHidden
-            .onEach { latest = it }
-            .launchIn(this)
-
-        assertThat(latest).isTrue()
-
-        job.cancel()
-    }
-
-    @Test
-    fun isForceHidden_repoDoesNotHaveWifiHidden_outputsFalse() = runBlocking(IMMEDIATE) {
-        connectivityRepository.setForceHiddenIcons(setOf())
-
-        var latest: Boolean? = null
-        val job = underTest
-            .isForceHidden
-            .onEach { latest = it }
-            .launchIn(this)
-
-        assertThat(latest).isFalse()
-
-        job.cancel()
-    }
-}
-
-private val IMMEDIATE = Dispatchers.Main.immediate
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..3d9fd96 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
@@ -25,21 +25,24 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.lifecycle.InstantTaskExecutorRule
+import com.android.systemui.log.table.TableLogBuffer
 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
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 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.domain.interactor.WifiInteractorImpl
 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
@@ -62,6 +65,7 @@
     private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock
     private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock
     private lateinit var connectivityConstants: ConnectivityConstants
     @Mock
@@ -70,7 +74,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
 
@@ -86,9 +90,9 @@
         connectivityRepository = FakeConnectivityRepository()
         wifiRepository = FakeWifiRepository()
         wifiRepository.setIsWifiEnabled(true)
-        interactor = WifiInteractor(connectivityRepository, wifiRepository)
+        interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
         scope = CoroutineScope(Dispatchers.Unconfined)
-        airplaneModeViewModel = AirplaneModeViewModel(
+        airplaneModeViewModel = AirplaneModeViewModelImpl(
             AirplaneModeInteractor(
                 airplaneModeRepository,
                 connectivityRepository,
@@ -101,27 +105,24 @@
             connectivityConstants,
             context,
             logger,
+            tableLogBuffer,
             interactor,
             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 +131,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 +140,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 +152,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 +167,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 +182,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 +202,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 +219,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/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index a1afcd7..12b93819 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
@@ -30,6 +31,7 @@
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
@@ -37,7 +39,9 @@
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 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.domain.interactor.WifiInteractorImpl
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
@@ -65,6 +69,7 @@
 
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
     private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -81,10 +86,10 @@
         connectivityRepository = FakeConnectivityRepository()
         wifiRepository = FakeWifiRepository()
         wifiRepository.setIsWifiEnabled(true)
-        interactor = WifiInteractor(connectivityRepository, wifiRepository)
+        interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
         scope = CoroutineScope(IMMEDIATE)
         airplaneModeViewModel =
-            AirplaneModeViewModel(
+            AirplaneModeViewModelImpl(
                 AirplaneModeInteractor(
                     airplaneModeRepository,
                     connectivityRepository,
@@ -121,6 +126,7 @@
                     connectivityConstants,
                     context,
                     logger,
+                    tableLogBuffer,
                     interactor,
                     scope,
                     statusBarPipelineFlags,
@@ -135,15 +141,21 @@
             yield()
 
             // THEN we get the expected icon
-            assertThat(iconFlow.value?.res).isEqualTo(testCase.expected?.iconResource)
-            val expectedContentDescription =
-                if (testCase.expected == null) {
-                    null
-                } else {
-                    testCase.expected.contentDescription.invoke(context)
+            val actualIcon = iconFlow.value
+            when (testCase.expected) {
+                null -> {
+                    assertThat(actualIcon).isInstanceOf(WifiIcon.Hidden::class.java)
                 }
-            assertThat(iconFlow.value?.contentDescription?.loadContentDescription(context))
-                .isEqualTo(expectedContentDescription)
+                else -> {
+                    assertThat(actualIcon).isInstanceOf(WifiIcon.Visible::class.java)
+                    val actualIconVisible = actualIcon as WifiIcon.Visible
+                    assertThat(actualIconVisible.icon.res).isEqualTo(testCase.expected.iconResource)
+                    val expectedContentDescription =
+                        testCase.expected.contentDescription.invoke(context)
+                    assertThat(actualIconVisible.contentDescription.loadContentDescription(context))
+                        .isEqualTo(expectedContentDescription)
+                }
+            }
 
             job.cancel()
         }
@@ -172,7 +184,7 @@
         val isDefault: Boolean = false,
         val network: WifiNetworkModel,
 
-        /** The expected output. Null if we expect the output to be null. */
+        /** The expected output. Null if we expect the output to be hidden. */
         val expected: Expected?
     ) {
         override fun toString(): String {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 7d2c560..7502020 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -18,11 +18,12 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.TableLogBuffer
 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
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
@@ -30,8 +31,10 @@
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 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.domain.interactor.WifiInteractorImpl
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -57,6 +60,7 @@
 
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
     private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -73,9 +77,9 @@
         connectivityRepository = FakeConnectivityRepository()
         wifiRepository = FakeWifiRepository()
         wifiRepository.setIsWifiEnabled(true)
-        interactor = WifiInteractor(connectivityRepository, wifiRepository)
+        interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
         scope = CoroutineScope(IMMEDIATE)
-        airplaneModeViewModel = AirplaneModeViewModel(
+        airplaneModeViewModel = AirplaneModeViewModelImpl(
             AirplaneModeInteractor(
                 airplaneModeRepository,
                 connectivityRepository,
@@ -101,21 +105,21 @@
 
     @Test
     fun wifiIcon_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
-        var latestHome: Icon? = null
+        var latestHome: WifiIcon? = null
         val jobHome = underTest
             .home
             .wifiIcon
             .onEach { latestHome = it }
             .launchIn(this)
 
-        var latestKeyguard: Icon? = null
+        var latestKeyguard: WifiIcon? = null
         val jobKeyguard = underTest
             .keyguard
             .wifiIcon
             .onEach { latestKeyguard = it }
             .launchIn(this)
 
-        var latestQs: Icon? = null
+        var latestQs: WifiIcon? = null
         val jobQs = underTest
             .qs
             .wifiIcon
@@ -131,7 +135,7 @@
         )
         yield()
 
-        assertThat(latestHome).isInstanceOf(Icon.Resource::class.java)
+        assertThat(latestHome).isInstanceOf(WifiIcon.Visible::class.java)
         assertThat(latestHome).isEqualTo(latestKeyguard)
         assertThat(latestKeyguard).isEqualTo(latestQs)
 
@@ -539,6 +543,7 @@
             connectivityConstants,
             context,
             logger,
+            tableLogBuffer,
             interactor,
             scope,
             statusBarPipelineFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
index f304647..0a3da0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
@@ -237,7 +237,7 @@
     fun refresh() {
         underTest.refresh()
 
-        verify(controller).refreshUsers(UserHandle.USER_NULL)
+        verify(controller).refreshUsers()
     }
 
     private fun createUserRecord(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 43d0fe9..1eee08c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -221,4 +221,33 @@
 
         Assert.assertFalse(mBatteryController.isChargingSourceDock());
     }
+
+    @Test
+    public void batteryStateChanged_healthNotOverheated_outputsFalse() {
+        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+        intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_GOOD);
+
+        mBatteryController.onReceive(getContext(), intent);
+
+        Assert.assertFalse(mBatteryController.isOverheated());
+    }
+
+    @Test
+    public void batteryStateChanged_healthOverheated_outputsTrue() {
+        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+        intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT);
+
+        mBatteryController.onReceive(getContext(), intent);
+
+        Assert.assertTrue(mBatteryController.isOverheated());
+    }
+
+    @Test
+    public void batteryStateChanged_noHealthGiven_outputsFalse() {
+        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+
+        mBatteryController.onReceive(getContext(), intent);
+
+        Assert.assertFalse(mBatteryController.isOverheated());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index d0391ac..833cabb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bluetooth.BluetoothLogger;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -56,6 +57,7 @@
 @SmallTest
 public class BluetoothControllerImplTest extends SysuiTestCase {
 
+    private UserTracker mUserTracker;
     private LocalBluetoothManager mMockBluetoothManager;
     private CachedBluetoothDeviceManager mMockDeviceManager;
     private LocalBluetoothAdapter mMockAdapter;
@@ -70,6 +72,7 @@
         mTestableLooper = TestableLooper.get(this);
         mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class);
         mDevices = new ArrayList<>();
+        mUserTracker = mock(UserTracker.class);
         mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);
         when(mMockDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
         when(mMockBluetoothManager.getCachedDeviceManager()).thenReturn(mMockDeviceManager);
@@ -81,6 +84,7 @@
         mMockDumpManager = mock(DumpManager.class);
 
         mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
+                mUserTracker,
                 mMockDumpManager,
                 mock(BluetoothLogger.class),
                 mTestableLooper.getLooper(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
index 14cc032..71ac7c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
@@ -33,7 +33,7 @@
 import com.android.systemui.plugins.OverlayPlugin;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.ExtensionController.Extension;
 import com.android.systemui.statusbar.policy.ExtensionController.TunerFactory;
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..dc08aba 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,8 +38,10 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -58,6 +62,8 @@
 public class HotspotControllerImplTest extends SysuiTestCase {
 
     @Mock
+    private UserTracker mUserTracker;
+    @Mock
     private DumpManager mDumpManager;
     @Mock
     private TetheringManager mTetheringManager;
@@ -96,9 +102,13 @@
         }).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);
+        mController = new HotspotControllerImpl(mContext, mUserTracker, handler, handler,
+                mDumpManager);
         verify(mTetheringManager)
                 .registerTetheringEventCallback(any(), mTetheringCallbackCaptor.capture());
     }
@@ -176,4 +186,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, mUserTracker, handler, handler, mDumpManager);
+
+        verifyNoMoreInteractions(mTetheringManager);
+        assertFalse(controller.isHotspotSupported());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index d44cdb2..15235b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -50,6 +50,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -72,10 +73,12 @@
     private final DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class);
     private final IKeyChainService.Stub mKeyChainService = mock(IKeyChainService.Stub.class);
     private final UserManager mUserManager = mock(UserManager.class);
+    private final UserTracker mUserTracker = mock(UserTracker.class);
     private final BroadcastDispatcher mBroadcastDispatcher = mock(BroadcastDispatcher.class);
     private final Handler mHandler = mock(Handler.class);
     private SecurityControllerImpl mSecurityController;
     private ConnectivityManager mConnectivityManager = mock(ConnectivityManager.class);
+    private FakeExecutor mMainExecutor;
     private FakeExecutor mBgExecutor;
     private BroadcastReceiver mBroadcastReceiver;
 
@@ -102,11 +105,14 @@
         ArgumentCaptor<BroadcastReceiver> brCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
 
+        mMainExecutor = new FakeExecutor(new FakeSystemClock());
         mBgExecutor = new FakeExecutor(new FakeSystemClock());
         mSecurityController = new SecurityControllerImpl(
                 mContext,
+                mUserTracker,
                 mHandler,
                 mBroadcastDispatcher,
+                mMainExecutor,
                 mBgExecutor,
                 Mockito.mock(DumpManager.class));
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
deleted file mode 100644
index 169f4fb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
+++ /dev/null
@@ -1,727 +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.statusbar.policy
-
-import android.app.IActivityManager
-import android.app.NotificationManager
-import android.app.admin.DevicePolicyManager
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.DialogInterface
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.hardware.face.FaceManager
-import android.hardware.fingerprint.FingerprintManager
-import android.os.Handler
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.ThreadedRenderer
-import androidx.test.filters.SmallTest
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.util.LatencyTracker
-import com.android.internal.util.UserIcons
-import com.android.systemui.GuestResetOrExitSessionReceiver
-import com.android.systemui.GuestResumeSessionReceiver
-import com.android.systemui.GuestSessionNotification
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogCuj
-import com.android.systemui.animation.DialogLaunchAnimator
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.QSUserSwitcherEvent
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shade.NotificationShadeWindowView
-import com.android.systemui.telephony.TelephonyListenerManager
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.nullable
-import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@SmallTest
-class UserSwitcherControllerOldImplTest : SysuiTestCase() {
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var activityManager: IActivityManager
-    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
-    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
-    @Mock private lateinit var handler: Handler
-    @Mock private lateinit var userTracker: UserTracker
-    @Mock private lateinit var userManager: UserManager
-    @Mock private lateinit var activityStarter: ActivityStarter
-    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
-    @Mock private lateinit var broadcastSender: BroadcastSender
-    @Mock private lateinit var telephonyListenerManager: TelephonyListenerManager
-    @Mock private lateinit var secureSettings: SecureSettings
-    @Mock private lateinit var falsingManager: FalsingManager
-    @Mock private lateinit var dumpManager: DumpManager
-    @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
-    @Mock private lateinit var latencyTracker: LatencyTracker
-    @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
-    @Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView
-    @Mock private lateinit var threadedRenderer: ThreadedRenderer
-    @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
-    @Mock private lateinit var globalSettings: GlobalSettings
-    @Mock private lateinit var guestSessionNotification: GuestSessionNotification
-    @Mock private lateinit var guestResetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
-    private lateinit var resetSessionDialogFactory:
-                            GuestResumeSessionReceiver.ResetSessionDialog.Factory
-    private lateinit var guestResumeSessionReceiver: GuestResumeSessionReceiver
-    private lateinit var testableLooper: TestableLooper
-    private lateinit var bgExecutor: FakeExecutor
-    private lateinit var longRunningExecutor: FakeExecutor
-    private lateinit var uiExecutor: FakeExecutor
-    private lateinit var uiEventLogger: UiEventLoggerFake
-    private lateinit var userSwitcherController: UserSwitcherControllerOldImpl
-    private lateinit var picture: Bitmap
-    private val ownerId = UserHandle.USER_SYSTEM
-    private val ownerInfo = UserInfo(ownerId, "Owner", null,
-            UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL or UserInfo.FLAG_INITIALIZED or
-                    UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM or UserInfo.FLAG_ADMIN,
-            UserManager.USER_TYPE_FULL_SYSTEM)
-    private val guestId = 1234
-    private val guestInfo = UserInfo(guestId, "Guest", null,
-            UserInfo.FLAG_FULL or UserInfo.FLAG_GUEST, UserManager.USER_TYPE_FULL_GUEST)
-    private val secondaryUser =
-            UserInfo(10, "Secondary", null, 0, UserManager.USER_TYPE_FULL_SECONDARY)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        testableLooper = TestableLooper.get(this)
-        bgExecutor = FakeExecutor(FakeSystemClock())
-        longRunningExecutor = FakeExecutor(FakeSystemClock())
-        uiExecutor = FakeExecutor(FakeSystemClock())
-        uiEventLogger = UiEventLoggerFake()
-
-        mContext.orCreateTestableResources.addOverride(
-                com.android.internal.R.bool.config_guestUserAutoCreated, false)
-
-        mContext.addMockSystemService(Context.FACE_SERVICE, mock(FaceManager::class.java))
-        mContext.addMockSystemService(Context.NOTIFICATION_SERVICE,
-                mock(NotificationManager::class.java))
-        mContext.addMockSystemService(Context.FINGERPRINT_SERVICE,
-                mock(FingerprintManager::class.java))
-
-        resetSessionDialogFactory = object : GuestResumeSessionReceiver.ResetSessionDialog.Factory {
-                override fun create(userId: Int): GuestResumeSessionReceiver.ResetSessionDialog {
-                    return GuestResumeSessionReceiver.ResetSessionDialog(
-                                mContext,
-                                mock(UserSwitcherController::class.java),
-                                uiEventLogger,
-                                userId
-                            )
-                }
-            }
-
-        guestResumeSessionReceiver = GuestResumeSessionReceiver(userTracker,
-                                        secureSettings,
-                                        broadcastDispatcher,
-                                        guestSessionNotification,
-                                        resetSessionDialogFactory)
-
-        `when`(userManager.canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY)))
-                .thenReturn(true)
-        `when`(notificationShadeWindowView.context).thenReturn(context)
-
-        // Since userSwitcherController involves InteractionJankMonitor.
-        // Let's fulfill the dependencies.
-        val mockedContext = mock(Context::class.java)
-        doReturn(mockedContext).`when`(notificationShadeWindowView).context
-        doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow
-        doNothing().`when`(threadedRenderer).addObserver(any())
-        doNothing().`when`(threadedRenderer).removeObserver(any())
-        doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer
-
-        picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
-
-        // Create defaults for the current user
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-
-        setupController()
-    }
-
-    private fun setupController() {
-        userSwitcherController =
-            UserSwitcherControllerOldImpl(
-                mContext,
-                activityManager,
-                userManager,
-                userTracker,
-                keyguardStateController,
-                deviceProvisionedController,
-                devicePolicyManager,
-                handler,
-                activityStarter,
-                broadcastDispatcher,
-                broadcastSender,
-                uiEventLogger,
-                falsingManager,
-                telephonyListenerManager,
-                secureSettings,
-                globalSettings,
-                bgExecutor,
-                longRunningExecutor,
-                uiExecutor,
-                interactionJankMonitor,
-                latencyTracker,
-                dumpManager,
-                dialogLaunchAnimator,
-                guestResumeSessionReceiver,
-                guestResetOrExitSessionReceiver
-            )
-        userSwitcherController.init(notificationShadeWindowView)
-    }
-
-    @Test
-    fun testSwitchUser_parentDialogDismissed() {
-        val otherUserRecord = UserRecord(
-            secondaryUser,
-            picture,
-            false /* guest */,
-            false /* current */,
-            false /* isAddUser */,
-            false /* isRestricted */,
-            true /* isSwitchToEnabled */,
-            false /* isAddSupervisedUser */
-        )
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        userSwitcherController.onUserListItemClicked(otherUserRecord, dialogShower)
-        testableLooper.processAllMessages()
-
-        verify(dialogShower).dismiss()
-    }
-
-    @Test
-    fun testAddGuest_okButtonPressed() {
-        val emptyGuestUserRecord =
-            UserRecord(
-                null,
-                null,
-                true /* guest */,
-                false /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        `when`(userManager.createGuest(any())).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, null)
-        bgExecutor.runAllReady()
-        uiExecutor.runAllReady()
-        testableLooper.processAllMessages()
-        verify(interactionJankMonitor).begin(any())
-        verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
-        verify(activityManager).switchUser(guestInfo.id)
-        assertEquals(1, uiEventLogger.numLogs())
-        assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_ADD.id, uiEventLogger.eventId(0))
-    }
-
-    @Test
-    fun testAddGuest_parentDialogDismissed() {
-        val emptyGuestUserRecord =
-            UserRecord(
-                null,
-                null,
-                true /* guest */,
-                false /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        `when`(userManager.createGuest(any())).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, dialogShower)
-        bgExecutor.runAllReady()
-        uiExecutor.runAllReady()
-        testableLooper.processAllMessages()
-        verify(dialogShower).dismiss()
-    }
-
-    @Test
-    fun testRemoveGuest_removeButtonPressed_isLogged() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                true /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestInfo.id)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-        assertNotNull(userSwitcherController.mExitGuestDialog)
-        userSwitcherController.mExitGuestDialog
-                .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
-        testableLooper.processAllMessages()
-        assertEquals(1, uiEventLogger.numLogs())
-        assertTrue(
-            QSUserSwitcherEvent.QS_USER_GUEST_REMOVE.id == uiEventLogger.eventId(0) ||
-            QSUserSwitcherEvent.QS_USER_SWITCH.id == uiEventLogger.eventId(0)
-        )
-    }
-
-    @Test
-    fun testRemoveGuest_removeButtonPressed_dialogDismissed() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                true /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestInfo.id)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-        assertNotNull(userSwitcherController.mExitGuestDialog)
-        userSwitcherController.mExitGuestDialog
-                .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
-        testableLooper.processAllMessages()
-        assertFalse(userSwitcherController.mExitGuestDialog.isShowing)
-    }
-
-    @Test
-    fun testRemoveGuest_dialogShowerUsed() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                true /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestInfo.id)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, dialogShower)
-        assertNotNull(userSwitcherController.mExitGuestDialog)
-        testableLooper.processAllMessages()
-        verify(dialogShower)
-            .showDialog(
-                userSwitcherController.mExitGuestDialog,
-                DialogCuj(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN, "exit_guest_mode"))
-    }
-
-    @Test
-    fun testRemoveGuest_cancelButtonPressed_isNotLogged() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                true /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestId)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-        assertNotNull(userSwitcherController.mExitGuestDialog)
-        userSwitcherController.mExitGuestDialog
-                .getButton(DialogInterface.BUTTON_NEUTRAL).performClick()
-        testableLooper.processAllMessages()
-        assertEquals(0, uiEventLogger.numLogs())
-    }
-
-    @Test
-    fun testWipeGuest_startOverButtonPressed_isLogged() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                false /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestId)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        // Simulate that guest user has already logged in
-        `when`(secureSettings.getIntForUser(
-                eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
-                .thenReturn(1)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-
-        // Simulate a user switch event
-        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-
-        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver)
-        userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent)
-
-        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog)
-        userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog
-                .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_WIPE).performClick()
-        testableLooper.processAllMessages()
-        assertEquals(1, uiEventLogger.numLogs())
-        assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_WIPE.id, uiEventLogger.eventId(0))
-    }
-
-    @Test
-    fun testWipeGuest_continueButtonPressed_isLogged() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                false /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestId)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        // Simulate that guest user has already logged in
-        `when`(secureSettings.getIntForUser(
-                eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
-                .thenReturn(1)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-
-        // Simulate a user switch event
-        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-
-        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver)
-        userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent)
-
-        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog)
-        userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog
-                .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_DONTWIPE)
-                .performClick()
-        testableLooper.processAllMessages()
-        assertEquals(1, uiEventLogger.numLogs())
-        assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_CONTINUE.id, uiEventLogger.eventId(0))
-    }
-
-    @Test
-    fun test_getCurrentUserName_shouldReturnNameOfTheCurrentUser() {
-        fun addUser(id: Int, name: String, isCurrent: Boolean) {
-            userSwitcherController.users.add(
-                UserRecord(
-                    UserInfo(id, name, 0),
-                    null, false, isCurrent, false,
-                    false, false, false
-                )
-            )
-        }
-        val bgUserName = "background_user"
-        val fgUserName = "foreground_user"
-
-        addUser(1, bgUserName, false)
-        addUser(2, fgUserName, true)
-
-        assertEquals(fgUserName, userSwitcherController.currentUserName)
-    }
-
-    @Test
-    fun isSystemUser_currentUserIsSystemUser_shouldReturnTrue() {
-        `when`(userTracker.userId).thenReturn(UserHandle.USER_SYSTEM)
-        assertEquals(true, userSwitcherController.isSystemUser)
-    }
-
-    @Test
-    fun isSystemUser_currentUserIsNotSystemUser_shouldReturnFalse() {
-        `when`(userTracker.userId).thenReturn(1)
-        assertEquals(false, userSwitcherController.isSystemUser)
-    }
-
-    @Test
-    fun testCanCreateSupervisedUserWithConfiguredPackage() {
-        // GIVEN the supervised user creation package is configured
-        `when`(context.getString(
-            com.android.internal.R.string.config_supervisedUserCreationPackage))
-            .thenReturn("some_pkg")
-
-        // AND the current user is allowed to create new users
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        // WHEN the controller is started with the above config
-        setupController()
-        testableLooper.processAllMessages()
-
-        // THEN a supervised user can be constructed
-        assertTrue(userSwitcherController.canCreateSupervisedUser())
-    }
-
-    @Test
-    fun testCannotCreateSupervisedUserWithConfiguredPackage() {
-        // GIVEN the supervised user creation package is NOT configured
-        `when`(context.getString(
-            com.android.internal.R.string.config_supervisedUserCreationPackage))
-            .thenReturn(null)
-
-        // AND the current user is allowed to create new users
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        // WHEN the controller is started with the above config
-        setupController()
-        testableLooper.processAllMessages()
-
-        // THEN a supervised user can NOT be constructed
-        assertFalse(userSwitcherController.canCreateSupervisedUser())
-    }
-
-    @Test
-    fun testCannotCreateUserWhenUserSwitcherDisabled() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-        setupController()
-        assertFalse(userSwitcherController.canCreateUser())
-    }
-
-    @Test
-    fun testCannotCreateGuestUserWhenUserSwitcherDisabled() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-        setupController()
-        assertFalse(userSwitcherController.canCreateGuest(false))
-    }
-
-    @Test
-    fun testCannotCreateSupervisedUserWhenUserSwitcherDisabled() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-        setupController()
-        assertFalse(userSwitcherController.canCreateSupervisedUser())
-    }
-
-    @Test
-    fun testCanManageUser_userSwitcherEnabled_addUserWhenLocked() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-        setupController()
-        assertTrue(userSwitcherController.canManageUsers())
-    }
-
-    @Test
-    fun testCanManageUser_userSwitcherDisabled_addUserWhenLocked() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-        setupController()
-        assertFalse(userSwitcherController.canManageUsers())
-    }
-
-    @Test
-    fun testCanManageUser_userSwitcherEnabled_isAdmin() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-
-        setupController()
-        assertTrue(userSwitcherController.canManageUsers())
-    }
-
-    @Test
-    fun testCanManageUser_userSwitcherDisabled_isAdmin() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-
-        setupController()
-        assertFalse(userSwitcherController.canManageUsers())
-    }
-
-    @Test
-    fun addUserSwitchCallback() {
-        val broadcastReceiverCaptor = argumentCaptor<BroadcastReceiver>()
-        verify(broadcastDispatcher).registerReceiver(
-                capture(broadcastReceiverCaptor),
-                any(),
-                nullable(), nullable(), anyInt(), nullable())
-
-        val cb = mock(UserSwitcherController.UserSwitchCallback::class.java)
-        userSwitcherController.addUserSwitchCallback(cb)
-
-        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-        broadcastReceiverCaptor.value.onReceive(context, intent)
-        verify(cb).onUserSwitched()
-    }
-
-    @Test
-    fun onUserItemClicked_guest_runsOnBgThread() {
-        val dialogShower = mock(UserSwitchDialogController.DialogShower::class.java)
-        val guestUserRecord = UserRecord(
-            null,
-            picture,
-            true /* guest */,
-            false /* current */,
-            false /* isAddUser */,
-            false /* isRestricted */,
-            true /* isSwitchToEnabled */,
-            false /* isAddSupervisedUser */
-        )
-
-        userSwitcherController.onUserListItemClicked(guestUserRecord, dialogShower)
-        assertTrue(bgExecutor.numPending() > 0)
-        verify(userManager, never()).createGuest(context)
-        bgExecutor.runAllReady()
-        verify(userManager).createGuest(context)
-    }
-
-    @Test
-    fun onUserItemClicked_manageUsers() {
-        val manageUserRecord = LegacyUserDataHelper.createRecord(
-            mContext,
-            ownerId,
-            UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-            isRestricted = false,
-            isSwitchToEnabled = true
-        )
-
-        userSwitcherController.onUserListItemClicked(manageUserRecord, null)
-        val intentCaptor = kotlinArgumentCaptor<Intent>()
-        verify(activityStarter).startActivity(intentCaptor.capture(),
-            eq(true)
-        )
-        Truth.assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index 3fe1a9f..c06dbdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.ZenModeController.Callback;
 import com.android.systemui.util.settings.FakeSettings;
 
@@ -58,6 +59,8 @@
     BroadcastDispatcher mBroadcastDispatcher;
     @Mock
     DumpManager mDumpManager;
+    @Mock
+    UserTracker mUserTracker;
 
     private ZenModeControllerImpl mController;
 
@@ -72,7 +75,8 @@
                 Handler.createAsync(Looper.myLooper()),
                 mBroadcastDispatcher,
                 mDumpManager,
-                new FakeSettings());
+                new FakeSettings(),
+                mUserTracker);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
new file mode 100644
index 0000000..0d19ab1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/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.surfaceeffects.ripple
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.surfaceeffects.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/surfaceeffects/ripple/MultiRippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt
new file mode 100644
index 0000000..2024d53
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.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.surfaceeffects.ripple
+
+import android.testing.AndroidTestingRunner
+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 MultiRippleViewTest : SysuiTestCase() {
+    private val fakeSystemClock = FakeSystemClock()
+    // FakeExecutor is needed to run animator.
+    private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+    @Test
+    fun onRippleFinishes_triggersRippleFinished() {
+        val multiRippleView = MultiRippleView(context, null)
+        val multiRippleController = MultiRippleController(multiRippleView)
+        val rippleAnimationConfig = RippleAnimationConfig(duration = 1000L)
+
+        var isTriggered = false
+        val listener =
+            object : MultiRippleView.Companion.RipplesFinishedListener {
+                override fun onRipplesFinish() {
+                    isTriggered = true
+                }
+            }
+        multiRippleView.addRipplesFinishedListener(listener)
+
+        fakeExecutor.execute {
+            val rippleAnimation = RippleAnimation(rippleAnimationConfig)
+            multiRippleController.play(rippleAnimation)
+
+            fakeSystemClock.advanceTime(rippleAnimationConfig.duration)
+
+            assertThat(isTriggered).isTrue()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
new file mode 100644
index 0000000..756397a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/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.surfaceeffects.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/surfaceeffects/ripple/RippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
new file mode 100644
index 0000000..1e5ab7e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.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.surfaceeffects.ripple
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RippleViewTest : SysuiTestCase() {
+    private lateinit var rippleView: RippleView
+
+    @Before
+    fun setup() {
+        rippleView = RippleView(context, null)
+    }
+
+    @Test
+    fun testSetupShader_compilesCircle() {
+        rippleView.setupShader(RippleShader.RippleShape.CIRCLE)
+    }
+
+    @Test
+    fun testSetupShader_compilesRoundedBox() {
+        rippleView.setupShader(RippleShader.RippleShape.ROUNDED_BOX)
+    }
+
+    @Test
+    fun testSetupShader_compilesEllipse() {
+        rippleView.setupShader(RippleShader.RippleShape.ELLIPSE)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
new file mode 100644
index 0000000..d25c8c1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.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.surfaceeffects.turbulencenoise
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+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 TurbulenceNoiseControllerTest : SysuiTestCase() {
+    private val fakeSystemClock = FakeSystemClock()
+    // FakeExecutor is needed to run animator.
+    private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+    @Test
+    fun play_playsTurbulenceNoise() {
+        val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+        val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+        fakeExecutor.execute {
+            turbulenceNoiseController.play(config)
+
+            assertThat(turbulenceNoiseView.isPlaying).isTrue()
+
+            fakeSystemClock.advanceTime(config.duration.toLong())
+
+            assertThat(turbulenceNoiseView.isPlaying).isFalse()
+        }
+    }
+
+    @Test
+    fun updateColor_updatesCorrectColor() {
+        val config = TurbulenceNoiseAnimationConfig(duration = 1000f, color = Color.WHITE)
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+        val expectedColor = Color.RED
+
+        val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+        fakeExecutor.execute {
+            turbulenceNoiseController.play(config)
+
+            turbulenceNoiseView.updateColor(expectedColor)
+
+            fakeSystemClock.advanceTime(config.duration.toLong())
+
+            assertThat(config.color).isEqualTo(expectedColor)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
new file mode 100644
index 0000000..633aac0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.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.surfaceeffects.turbulencenoise
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+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 TurbulenceNoiseViewTest : SysuiTestCase() {
+
+    private val fakeSystemClock = FakeSystemClock()
+    // FakeExecutor is needed to run animator.
+    private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+    @Test
+    fun play_viewHasCorrectVisibility() {
+        val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+        assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
+
+        fakeExecutor.execute {
+            turbulenceNoiseView.play(config)
+
+            assertThat(turbulenceNoiseView.visibility).isEqualTo(View.VISIBLE)
+
+            fakeSystemClock.advanceTime(config.duration.toLong())
+
+            assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
+        }
+    }
+
+    @Test
+    fun play_playsAnimation() {
+        val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+        fakeExecutor.execute {
+            turbulenceNoiseView.play(config)
+
+            assertThat(turbulenceNoiseView.isPlaying).isTrue()
+        }
+    }
+
+    @Test
+    fun play_onEnd_triggersOnAnimationEnd() {
+        var animationEnd = false
+        val config =
+            TurbulenceNoiseAnimationConfig(
+                duration = 1000f,
+                onAnimationEnd = { animationEnd = true }
+            )
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+        fakeExecutor.execute {
+            turbulenceNoiseView.play(config)
+
+            assertThat(turbulenceNoiseView.isPlaying).isTrue()
+
+            fakeSystemClock.advanceTime(config.duration.toLong())
+
+            assertThat(animationEnd).isTrue()
+        }
+    }
+}
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 91b5c35..09f0d4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -35,6 +35,8 @@
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.wakelock.WakeLock
+import com.android.systemui.util.wakelock.WakeLockFake
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -53,6 +55,9 @@
     private lateinit var fakeClock: FakeSystemClock
     private lateinit var fakeExecutor: FakeExecutor
 
+    private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+    private lateinit var fakeWakeLock: WakeLockFake
+
     @Mock
     private lateinit var logger: TemporaryViewLogger
     @Mock
@@ -74,6 +79,10 @@
         fakeClock = FakeSystemClock()
         fakeExecutor = FakeExecutor(fakeClock)
 
+        fakeWakeLock = WakeLockFake()
+        fakeWakeLockBuilder = WakeLockFake.Builder(context)
+        fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
         underTest = TestController(
                 context,
                 logger,
@@ -82,7 +91,9 @@
                 accessibilityManager,
                 configurationController,
                 powerManager,
+                fakeWakeLockBuilder,
         )
+        underTest.start()
     }
 
     @Test
@@ -108,29 +119,47 @@
             )
         )
 
-        verify(logger).logViewAddition("Fake Window Title")
+        verify(logger).logViewAddition("id", "Fake Window Title")
     }
 
     @Test
-    fun displayView_screenOff_screenWakes() {
-        whenever(powerManager.isScreenOn).thenReturn(false)
-
+    fun displayView_wakeLockAcquired() {
         underTest.displayView(getState())
 
-        verify(powerManager).wakeUp(any(), any(), any())
+        assertThat(fakeWakeLock.isHeld).isTrue()
     }
 
     @Test
-    fun displayView_screenAlreadyOn_screenNotWoken() {
+    fun displayView_screenAlreadyOn_wakeLockAcquired() {
         whenever(powerManager.isScreenOn).thenReturn(true)
 
         underTest.displayView(getState())
 
-        verify(powerManager, never()).wakeUp(any(), any(), any())
+        assertThat(fakeWakeLock.isHeld).isTrue()
     }
 
     @Test
-    fun displayView_twiceWithSameWindowTitle_viewNotAddedTwice() {
+    fun displayView_wakeLockCanBeReleasedAfterTimeOut() {
+        underTest.displayView(getState())
+        assertThat(fakeWakeLock.isHeld).isTrue()
+
+        fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+        assertThat(fakeWakeLock.isHeld).isFalse()
+    }
+
+    @Test
+    fun displayView_removeView_wakeLockCanBeReleased() {
+        underTest.displayView(getState())
+        assertThat(fakeWakeLock.isHeld).isTrue()
+
+        underTest.removeView("id", "test reason")
+
+        assertThat(fakeWakeLock.isHeld).isFalse()
+    }
+
+    @Test
+    fun displayView_twice_viewNotAddedTwice() {
         underTest.displayView(getState())
         reset(windowManager)
 
@@ -234,21 +263,143 @@
     }
 
     @Test
+    fun multipleViewsWithDifferentIds_recentActiveViewIsDisplayed() {
+        underTest.displayView(ViewInfo("First name", id = "id1"))
+
+        verify(windowManager).addView(any(), any())
+
+        reset(windowManager)
+        underTest.displayView(ViewInfo("Second name", id = "id2"))
+        underTest.removeView("id2", "test reason")
+
+        verify(windowManager).removeView(any())
+
+        fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
+
+        assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
+        assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
+
+        reset(windowManager)
+        fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+        verify(windowManager).removeView(any())
+        assertThat(underTest.activeViews.size).isEqualTo(0)
+    }
+
+    @Test
+    fun multipleViewsWithDifferentIds_oldViewRemoved_recentViewIsDisplayed() {
+        underTest.displayView(ViewInfo("First name", id = "id1"))
+
+        verify(windowManager).addView(any(), any())
+
+        reset(windowManager)
+        underTest.displayView(ViewInfo("Second name", id = "id2"))
+        underTest.removeView("id1", "test reason")
+
+        verify(windowManager, never()).removeView(any())
+        assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
+        assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
+
+        fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+        verify(windowManager).removeView(any())
+        assertThat(underTest.activeViews.size).isEqualTo(0)
+    }
+
+    @Test
+    fun multipleViewsWithDifferentIds_threeDifferentViews_recentActiveViewIsDisplayed() {
+        underTest.displayView(ViewInfo("First name", id = "id1"))
+        underTest.displayView(ViewInfo("Second name", id = "id2"))
+        underTest.displayView(ViewInfo("Third name", id = "id3"))
+
+        verify(windowManager).addView(any(), any())
+
+        reset(windowManager)
+        underTest.removeView("id3", "test reason")
+
+        verify(windowManager).removeView(any())
+
+        fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
+
+        assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
+        assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
+
+        reset(windowManager)
+        underTest.removeView("id2", "test reason")
+
+        verify(windowManager).removeView(any())
+
+        fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
+
+        assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
+        assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
+
+        reset(windowManager)
+        fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+        verify(windowManager).removeView(any())
+        assertThat(underTest.activeViews.size).isEqualTo(0)
+    }
+
+    @Test
+    fun multipleViewsWithDifferentIds_oneViewStateChanged_stackHasRecentState() {
+        underTest.displayView(ViewInfo("First name", id = "id1"))
+        underTest.displayView(ViewInfo("New name", id = "id1"))
+
+        verify(windowManager).addView(any(), any())
+
+        reset(windowManager)
+        underTest.displayView(ViewInfo("Second name", id = "id2"))
+        underTest.removeView("id2", "test reason")
+
+        verify(windowManager).removeView(any())
+
+        fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
+
+        assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
+        assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("New name")
+        assertThat(underTest.activeViews[0].second.name).isEqualTo("New name")
+
+        reset(windowManager)
+        fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+        verify(windowManager).removeView(any())
+        assertThat(underTest.activeViews.size).isEqualTo(0)
+    }
+
+    @Test
+    fun multipleViewsWithDifferentIds_viewsTimeouts_noViewLeftToDisplay() {
+        underTest.displayView(ViewInfo("First name", id = "id1"))
+        fakeClock.advanceTime(TIMEOUT_MS / 3)
+        underTest.displayView(ViewInfo("Second name", id = "id2"))
+        fakeClock.advanceTime(TIMEOUT_MS / 3)
+        underTest.displayView(ViewInfo("Third name", id = "id3"))
+
+        reset(windowManager)
+        fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+        verify(windowManager).removeView(any())
+        verify(windowManager, never()).addView(any(), any())
+        assertThat(underTest.activeViews.size).isEqualTo(0)
+    }
+
+    @Test
     fun removeView_viewRemovedAndRemovalLogged() {
         // First, add the view
         underTest.displayView(getState())
 
         // Then, remove it
         val reason = "test reason"
-        underTest.removeView(reason)
+        val deviceId = "id"
+        underTest.removeView(deviceId, reason)
 
         verify(windowManager).removeView(any())
-        verify(logger).logViewRemoval(reason)
+        verify(logger).logViewRemoval(deviceId, reason)
     }
 
     @Test
     fun removeView_noAdd_viewNotRemoved() {
-        underTest.removeView("reason")
+        underTest.removeView("id", "reason")
 
         verify(windowManager, never()).removeView(any())
     }
@@ -269,6 +420,7 @@
         accessibilityManager: AccessibilityManager,
         configurationController: ConfigurationController,
         powerManager: PowerManager,
+        wakeLockBuilder: WakeLock.Builder,
     ) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger>(
         context,
         logger,
@@ -278,13 +430,12 @@
         configurationController,
         powerManager,
         R.layout.chipbar,
+        wakeLockBuilder,
     ) {
         var mostRecentViewInfo: ViewInfo? = null
 
         override val windowLayoutParams = commonWindowLayoutParams
 
-        override fun start() {}
-
         override fun updateView(newInfo: ViewInfo, currentView: ViewGroup) {
             mostRecentViewInfo = newInfo
         }
@@ -292,13 +443,16 @@
         override fun getTouchableRegion(view: View, outRect: Rect) {
             outRect.setEmpty()
         }
+
+        override fun start() {}
     }
 
     inner class ViewInfo(
         val name: String,
         override val windowTitle: String = "Window Title",
         override val wakeReason: String = "WAKE_REASON",
-        override val timeoutMs: Int = 1
+        override val timeoutMs: Int = 1,
+        override val id: String = "id",
     ) : TemporaryViewInfo()
 }
 
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 d155050..116b8fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -44,7 +44,7 @@
 
     @Test
     fun logViewAddition_bufferHasLog() {
-        logger.logViewAddition("Test Window Title")
+        logger.logViewAddition("test id", "Test Window Title")
 
         val stringWriter = StringWriter()
         buffer.dump(PrintWriter(stringWriter), tailLength = 0)
@@ -57,7 +57,8 @@
     @Test
     fun logViewRemoval_bufferHasTagAndReason() {
         val reason = "test reason"
-        logger.logViewRemoval(reason)
+        val deviceId = "test id"
+        logger.logViewRemoval(deviceId, reason)
 
         val stringWriter = StringWriter()
         buffer.dump(PrintWriter(stringWriter), tailLength = 0)
@@ -65,6 +66,7 @@
 
         assertThat(actualString).contains(TAG)
         assertThat(actualString).contains(reason)
+        assertThat(actualString).contains(deviceId)
     }
 }
 
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 f643973..47c84ab 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
@@ -43,6 +43,7 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLockFake
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -69,6 +70,8 @@
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var viewUtil: ViewUtil
     @Mock private lateinit var vibratorHelper: VibratorHelper
+    private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+    private lateinit var fakeWakeLock: WakeLockFake
     private lateinit var fakeClock: FakeSystemClock
     private lateinit var fakeExecutor: FakeExecutor
     private lateinit var uiEventLoggerFake: UiEventLoggerFake
@@ -81,6 +84,10 @@
         fakeClock = FakeSystemClock()
         fakeExecutor = FakeExecutor(fakeClock)
 
+        fakeWakeLock = WakeLockFake()
+        fakeWakeLockBuilder = WakeLockFake.Builder(context)
+        fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
         uiEventLoggerFake = UiEventLoggerFake()
 
         underTest =
@@ -96,6 +103,7 @@
                 falsingCollector,
                 viewUtil,
                 vibratorHelper,
+                fakeWakeLockBuilder,
             )
         underTest.start()
     }
@@ -369,6 +377,7 @@
             windowTitle = WINDOW_TITLE,
             wakeReason = WAKE_REASON,
             timeoutMs = TIMEOUT,
+            id = DEVICE_ID,
         )
     }
 
@@ -393,3 +402,4 @@
 private const val TIMEOUT = 10000
 private const val WINDOW_TITLE = "Test Chipbar Window Title"
 private const val WAKE_REASON = "TEST_CHIPBAR_WAKE_REASON"
+private const val DEVICE_ID = "id"
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 574f70e..beedf9f 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
@@ -27,6 +27,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLock
 
 /** A fake implementation of [ChipbarCoordinator] for testing. */
 class FakeChipbarCoordinator(
@@ -41,6 +42,7 @@
     falsingCollector: FalsingCollector,
     viewUtil: ViewUtil,
     vibratorHelper: VibratorHelper,
+    wakeLockBuilder: WakeLock.Builder,
 ) :
     ChipbarCoordinator(
         context,
@@ -54,6 +56,7 @@
         falsingCollector,
         viewUtil,
         vibratorHelper,
+        wakeLockBuilder,
     ) {
     override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
         // Just bypass the animation in tests
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 797f86a..27957ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -62,7 +62,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
index 8645298..89402de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -88,6 +88,7 @@
 
         deviceStates = FoldableTestUtils.findDeviceStates(context)
 
+        // TODO(b/254878364): remove this call to NPVC.getView()
         whenever(notificationPanelViewController.view).thenReturn(viewGroup)
         whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver)
         whenever(wakefulnessLifecycle.lastSleepReason)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
index 5509a6ca..03fd624 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
@@ -124,8 +124,11 @@
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
             { foldStateProvider.sendHingeAngleUpdate(90f) },
-            { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
-            { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_CLOSING) },
+            {
+                foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+                // Start closing immediately after we opened, before the animation ended
+                foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_CLOSING)
+            },
             { foldStateProvider.sendHingeAngleUpdate(60f) },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
index fc2a78a..e1e54a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -15,14 +15,13 @@
  */
 package com.android.systemui.unfold.util
 
-import android.animation.ValueAnimator
 import android.content.ContentResolver
 import android.database.ContentObserver
+import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.unfold.TestUnfoldTransitionProvider
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.util.mockito.any
 import org.junit.Before
@@ -30,6 +29,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
@@ -38,30 +38,25 @@
 @SmallTest
 class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
 
-    @Mock
-    lateinit var contentResolver: ContentResolver
-
-    @Mock
-    lateinit var sinkProvider: TransitionProgressListener
+    @Mock lateinit var sinkProvider: TransitionProgressListener
 
     private val sourceProvider = TestUnfoldTransitionProvider()
 
-    lateinit var progressProvider: ScaleAwareTransitionProgressProvider
+    private lateinit var contentResolver: ContentResolver
+    private lateinit var progressProvider: ScaleAwareTransitionProgressProvider
 
     private val animatorDurationScaleListenerCaptor =
-            ArgumentCaptor.forClass(ContentObserver::class.java)
+        ArgumentCaptor.forClass(ContentObserver::class.java)
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        contentResolver = spy(context.contentResolver)
 
-        progressProvider = ScaleAwareTransitionProgressProvider(
-                sourceProvider,
-                contentResolver
-        )
+        progressProvider = ScaleAwareTransitionProgressProvider(sourceProvider, contentResolver)
 
-        verify(contentResolver).registerContentObserver(any(), any(),
-                animatorDurationScaleListenerCaptor.capture())
+        verify(contentResolver)
+            .registerContentObserver(any(), any(), animatorDurationScaleListenerCaptor.capture())
 
         progressProvider.addCallback(sinkProvider)
     }
@@ -121,12 +116,20 @@
     }
 
     private fun setAnimationsEnabled(enabled: Boolean) {
-        val durationScale = if (enabled) {
-            1f
-        } else {
-            0f
-        }
-        ValueAnimator.setDurationScale(durationScale)
+        val durationScale =
+            if (enabled) {
+                1f
+            } else {
+                0f
+            }
+
+        // It uses [TestableSettingsProvider] and it will be cleared after the test
+        Settings.Global.putString(
+            contentResolver,
+            Settings.Global.ANIMATOR_DURATION_SCALE,
+            durationScale.toString()
+        )
+
         animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
new file mode 100644
index 0000000..51afbcb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
@@ -0,0 +1,55 @@
+package com.android.systemui.user
+
+import android.app.Dialog
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class CreateUserActivityTest : SysuiTestCase() {
+    open class CreateUserActivityTestable :
+        CreateUserActivity(
+            /* userCreator = */ mock(),
+            /* editUserInfoController = */ mock {
+                val dialog: Dialog = mock()
+                whenever(
+                        createDialog(
+                            /* activity = */ nullable(),
+                            /* activityStarter = */ nullable(),
+                            /* oldUserIcon = */ nullable(),
+                            /* defaultUserName = */ nullable(),
+                            /* title = */ nullable(),
+                            /* successCallback = */ nullable(),
+                            /* cancelCallback = */ nullable()
+                        )
+                    )
+                    .thenReturn(dialog)
+            },
+            /* activityManager = */ mock(),
+            /* activityStarter = */ mock(),
+        )
+
+    @get:Rule val activityRule = ActivityScenarioRule(CreateUserActivityTestable::class.java)
+
+    @Test
+    fun onBackPressed_finishActivity() {
+        activityRule.scenario.onActivity { activity ->
+            assertThat(activity.isFinishing).isFalse()
+
+            activity.onBackPressed()
+
+            assertThat(activity.isFinishing).isTrue()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
deleted file mode 100644
index 525d837..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
+++ /dev/null
@@ -1,229 +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.user.data.repository
-
-import android.content.pm.UserInfo
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import androidx.test.filters.SmallTest
-import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
-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
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
-
-    @Before
-    fun setUp() {
-        super.setUp(isRefactored = true)
-    }
-
-    @Test
-    fun userSwitcherSettings() = runSelfCancelingTest {
-        setUpGlobalSettings(
-            isSimpleUserSwitcher = true,
-            isAddUsersFromLockscreen = true,
-            isUserSwitcherEnabled = true,
-        )
-        underTest = create(this)
-
-        var value: UserSwitcherSettingsModel? = null
-        underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
-
-        assertUserSwitcherSettings(
-            model = value,
-            expectedSimpleUserSwitcher = true,
-            expectedAddUsersFromLockscreen = true,
-            expectedUserSwitcherEnabled = true,
-        )
-
-        setUpGlobalSettings(
-            isSimpleUserSwitcher = false,
-            isAddUsersFromLockscreen = true,
-            isUserSwitcherEnabled = true,
-        )
-        assertUserSwitcherSettings(
-            model = value,
-            expectedSimpleUserSwitcher = false,
-            expectedAddUsersFromLockscreen = true,
-            expectedUserSwitcherEnabled = true,
-        )
-    }
-
-    @Test
-    fun refreshUsers() = runSelfCancelingTest {
-        underTest = create(this)
-        val initialExpectedValue =
-            setUpUsers(
-                count = 3,
-                selectedIndex = 0,
-            )
-        var userInfos: List<UserInfo>? = null
-        var selectedUserInfo: UserInfo? = null
-        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
-        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
-
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(initialExpectedValue)
-        assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
-        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
-
-        val secondExpectedValue =
-            setUpUsers(
-                count = 4,
-                selectedIndex = 1,
-            )
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(secondExpectedValue)
-        assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
-        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
-
-        val selectedNonGuestUserId = selectedUserInfo?.id
-        val thirdExpectedValue =
-            setUpUsers(
-                count = 2,
-                isLastGuestUser = true,
-                selectedIndex = 1,
-            )
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(thirdExpectedValue)
-        assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
-        assertThat(selectedUserInfo?.isGuest).isTrue()
-        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
-    }
-
-    @Test
-    fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
-        underTest = create(this)
-        val unsortedUsers =
-            setUpUsers(
-                count = 3,
-                selectedIndex = 0,
-                isLastGuestUser = true,
-            )
-        unsortedUsers[0].creationTime = 999
-        unsortedUsers[1].creationTime = 900
-        unsortedUsers[2].creationTime = 950
-        val expectedUsers =
-            listOf(
-                unsortedUsers[1],
-                unsortedUsers[0],
-                unsortedUsers[2], // last because this is the guest
-            )
-        var userInfos: List<UserInfo>? = null
-        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
-
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(expectedUsers)
-    }
-
-    private fun setUpUsers(
-        count: Int,
-        isLastGuestUser: Boolean = false,
-        selectedIndex: Int = 0,
-    ): List<UserInfo> {
-        val userInfos =
-            (0 until count).map { index ->
-                createUserInfo(
-                    index,
-                    isGuest = isLastGuestUser && index == count - 1,
-                )
-            }
-        whenever(manager.aliveUsers).thenReturn(userInfos)
-        tracker.set(userInfos, selectedIndex)
-        return userInfos
-    }
-
-    private fun createUserInfo(
-        id: Int,
-        isGuest: Boolean,
-    ): UserInfo {
-        val flags = 0
-        return UserInfo(
-            id,
-            "user_$id",
-            /* iconPath= */ "",
-            flags,
-            if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
-        )
-    }
-
-    private fun setUpGlobalSettings(
-        isSimpleUserSwitcher: Boolean = false,
-        isAddUsersFromLockscreen: Boolean = false,
-        isUserSwitcherEnabled: Boolean = true,
-    ) {
-        context.orCreateTestableResources.addOverride(
-            com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
-            true,
-        )
-        globalSettings.putIntForUser(
-            UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
-            if (isSimpleUserSwitcher) 1 else 0,
-            UserHandle.USER_SYSTEM,
-        )
-        globalSettings.putIntForUser(
-            Settings.Global.ADD_USERS_WHEN_LOCKED,
-            if (isAddUsersFromLockscreen) 1 else 0,
-            UserHandle.USER_SYSTEM,
-        )
-        globalSettings.putIntForUser(
-            Settings.Global.USER_SWITCHER_ENABLED,
-            if (isUserSwitcherEnabled) 1 else 0,
-            UserHandle.USER_SYSTEM,
-        )
-    }
-
-    private fun assertUserSwitcherSettings(
-        model: UserSwitcherSettingsModel?,
-        expectedSimpleUserSwitcher: Boolean,
-        expectedAddUsersFromLockscreen: Boolean,
-        expectedUserSwitcherEnabled: Boolean,
-    ) {
-        checkNotNull(model)
-        assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
-        assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
-        assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
-    }
-
-    /**
-     * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
-     * is then automatically canceled and cleaned-up.
-     */
-    private fun runSelfCancelingTest(
-        block: suspend CoroutineScope.() -> Unit,
-    ) =
-        runBlocking(Dispatchers.Main.immediate) {
-            val scope = CoroutineScope(coroutineContext + Job())
-            block(scope)
-            scope.cancel()
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index dcea83a..034c618 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,54 +17,281 @@
 
 package com.android.systemui.user.data.repository
 
+import android.content.pm.UserInfo
+import android.os.UserHandle
 import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 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.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestCoroutineScope
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
 import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
-abstract class UserRepositoryImplTest : SysuiTestCase() {
+@SmallTest
+@RunWith(JUnit4::class)
+class UserRepositoryImplTest : SysuiTestCase() {
 
-    @Mock protected lateinit var manager: UserManager
-    @Mock protected lateinit var controller: UserSwitcherController
+    @Mock private lateinit var manager: UserManager
 
-    protected lateinit var underTest: UserRepositoryImpl
+    private lateinit var underTest: UserRepositoryImpl
 
-    protected lateinit var globalSettings: FakeSettings
-    protected lateinit var tracker: FakeUserTracker
-    protected lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var globalSettings: FakeSettings
+    private lateinit var tracker: FakeUserTracker
 
-    protected fun setUp(isRefactored: Boolean) {
+    @Before
+    fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         globalSettings = FakeSettings()
         tracker = FakeUserTracker()
-        featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored)
     }
 
-    protected fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+    @Test
+    fun userSwitcherSettings() = runSelfCancelingTest {
+        setUpGlobalSettings(
+            isSimpleUserSwitcher = true,
+            isAddUsersFromLockscreen = true,
+            isUserSwitcherEnabled = true,
+        )
+        underTest = create(this)
+
+        var value: UserSwitcherSettingsModel? = null
+        underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
+
+        assertUserSwitcherSettings(
+            model = value,
+            expectedSimpleUserSwitcher = true,
+            expectedAddUsersFromLockscreen = true,
+            expectedUserSwitcherEnabled = true,
+        )
+
+        setUpGlobalSettings(
+            isSimpleUserSwitcher = false,
+            isAddUsersFromLockscreen = true,
+            isUserSwitcherEnabled = true,
+        )
+        assertUserSwitcherSettings(
+            model = value,
+            expectedSimpleUserSwitcher = false,
+            expectedAddUsersFromLockscreen = true,
+            expectedUserSwitcherEnabled = true,
+        )
+    }
+
+    @Test
+    fun userSwitcherSettings_isUserSwitcherEnabled_notInitialized() = runSelfCancelingTest {
+        underTest = create(this)
+
+        var value: UserSwitcherSettingsModel? = null
+        underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
+
+        assertUserSwitcherSettings(
+            model = value,
+            expectedSimpleUserSwitcher = false,
+            expectedAddUsersFromLockscreen = false,
+            expectedUserSwitcherEnabled =
+                context.resources.getBoolean(
+                    com.android.internal.R.bool.config_showUserSwitcherByDefault
+                ),
+        )
+    }
+
+    @Test
+    fun refreshUsers() = runSelfCancelingTest {
+        underTest = create(this)
+        val initialExpectedValue =
+            setUpUsers(
+                count = 3,
+                selectedIndex = 0,
+            )
+        var userInfos: List<UserInfo>? = null
+        var selectedUserInfo: UserInfo? = null
+        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(initialExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+        val secondExpectedValue =
+            setUpUsers(
+                count = 4,
+                selectedIndex = 1,
+            )
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(secondExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+        val selectedNonGuestUserId = selectedUserInfo?.id
+        val thirdExpectedValue =
+            setUpUsers(
+                count = 2,
+                isLastGuestUser = true,
+                selectedIndex = 1,
+            )
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(thirdExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
+        assertThat(selectedUserInfo?.isGuest).isTrue()
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
+    }
+
+    @Test
+    fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
+        underTest = create(this)
+        val unsortedUsers =
+            setUpUsers(
+                count = 3,
+                selectedIndex = 0,
+                isLastGuestUser = true,
+            )
+        unsortedUsers[0].creationTime = 999
+        unsortedUsers[1].creationTime = 900
+        unsortedUsers[2].creationTime = 950
+        val expectedUsers =
+            listOf(
+                unsortedUsers[1],
+                unsortedUsers[0],
+                unsortedUsers[2], // last because this is the guest
+            )
+        var userInfos: List<UserInfo>? = null
+        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(expectedUsers)
+    }
+
+    private fun setUpUsers(
+        count: Int,
+        isLastGuestUser: Boolean = false,
+        selectedIndex: Int = 0,
+    ): List<UserInfo> {
+        val userInfos =
+            (0 until count).map { index ->
+                createUserInfo(
+                    index,
+                    isGuest = isLastGuestUser && index == count - 1,
+                )
+            }
+        whenever(manager.aliveUsers).thenReturn(userInfos)
+        tracker.set(userInfos, selectedIndex)
+        return userInfos
+    }
+    @Test
+    fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest {
+        underTest = create(this)
+        var selectedUserInfo: UserInfo? = null
+        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+        setUpUsers(
+            count = 2,
+            selectedIndex = 0,
+        )
+        tracker.onProfileChanged()
+        assertThat(selectedUserInfo?.id).isEqualTo(0)
+        setUpUsers(
+            count = 2,
+            selectedIndex = 1,
+        )
+        tracker.onProfileChanged()
+        assertThat(selectedUserInfo?.id).isEqualTo(1)
+    }
+
+    private fun createUserInfo(
+        id: Int,
+        isGuest: Boolean,
+    ): UserInfo {
+        val flags = 0
+        return UserInfo(
+            id,
+            "user_$id",
+            /* iconPath= */ "",
+            flags,
+            if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
+        )
+    }
+
+    private fun setUpGlobalSettings(
+        isSimpleUserSwitcher: Boolean = false,
+        isAddUsersFromLockscreen: Boolean = false,
+        isUserSwitcherEnabled: Boolean = true,
+    ) {
+        context.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
+            true,
+        )
+        globalSettings.putIntForUser(
+            UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
+            if (isSimpleUserSwitcher) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+        globalSettings.putIntForUser(
+            Settings.Global.ADD_USERS_WHEN_LOCKED,
+            if (isAddUsersFromLockscreen) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+        globalSettings.putIntForUser(
+            Settings.Global.USER_SWITCHER_ENABLED,
+            if (isUserSwitcherEnabled) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+    }
+
+    private fun assertUserSwitcherSettings(
+        model: UserSwitcherSettingsModel?,
+        expectedSimpleUserSwitcher: Boolean,
+        expectedAddUsersFromLockscreen: Boolean,
+        expectedUserSwitcherEnabled: Boolean,
+    ) {
+        checkNotNull(model)
+        assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
+        assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
+        assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
+    }
+
+    /**
+     * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
+     * is then automatically canceled and cleaned-up.
+     */
+    private fun runSelfCancelingTest(
+        block: suspend CoroutineScope.() -> Unit,
+    ) =
+        runBlocking(Dispatchers.Main.immediate) {
+            val scope = CoroutineScope(coroutineContext + Job())
+            block(scope)
+            scope.cancel()
+        }
+
+    private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
         return UserRepositoryImpl(
             appContext = context,
             manager = manager,
-            controller = controller,
             applicationScope = scope,
             mainDispatcher = IMMEDIATE,
             backgroundDispatcher = IMMEDIATE,
             globalSettings = globalSettings,
             tracker = tracker,
-            featureFlags = featureFlags,
         )
     }
 
     companion object {
-        @JvmStatic protected val IMMEDIATE = Dispatchers.Main.immediate
+        @JvmStatic private val IMMEDIATE = Dispatchers.Main.immediate
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
deleted file mode 100644
index a363a03..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
+++ /dev/null
@@ -1,209 +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.user.data.repository
-
-import android.content.pm.UserInfo
-import androidx.test.filters.SmallTest
-import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-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
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-    }
-
-    @Captor
-    private lateinit var userSwitchCallbackCaptor:
-        ArgumentCaptor<UserSwitcherController.UserSwitchCallback>
-
-    @Before
-    fun setUp() {
-        super.setUp(isRefactored = false)
-
-        whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false))
-        whenever(controller.isGuestUserAutoCreated).thenReturn(false)
-        whenever(controller.isGuestUserResetting).thenReturn(false)
-
-        underTest = create()
-    }
-
-    @Test
-    fun `users - registers for updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.users.onEach {}.launchIn(this)
-
-            verify(controller).addUserSwitchCallback(any())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `users - unregisters from updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.users.onEach {}.launchIn(this)
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
-            job.cancel()
-
-            verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
-        }
-
-    @Test
-    fun `users - does not include actions`() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createActionRecord(UserActionModel.ADD_USER),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                        createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
-                        createActionRecord(UserActionModel.ENTER_GUEST_MODE),
-                        createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
-                    )
-                )
-            var models: List<UserModel>? = null
-            val job = underTest.users.onEach { models = it }.launchIn(this)
-
-            assertThat(models).hasSize(3)
-            assertThat(models?.get(0)?.id).isEqualTo(0)
-            assertThat(models?.get(0)?.isSelected).isTrue()
-            assertThat(models?.get(1)?.id).isEqualTo(1)
-            assertThat(models?.get(1)?.isSelected).isFalse()
-            assertThat(models?.get(2)?.id).isEqualTo(2)
-            assertThat(models?.get(2)?.isSelected).isFalse()
-            job.cancel()
-        }
-
-    @Test
-    fun selectedUser() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                    )
-                )
-            var id: Int? = null
-            val job = underTest.selectedUser.map { it.id }.onEach { id = it }.launchIn(this)
-
-            assertThat(id).isEqualTo(0)
-
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0),
-                        createUserRecord(1),
-                        createUserRecord(2, isSelected = true),
-                    )
-                )
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-            userSwitchCallbackCaptor.value.onUserSwitched()
-            assertThat(id).isEqualTo(2)
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - unregisters from updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.actions.onEach {}.launchIn(this)
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
-            job.cancel()
-
-            verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
-        }
-
-    @Test
-    fun `actions - registers for updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.actions.onEach {}.launchIn(this)
-
-            verify(controller).addUserSwitchCallback(any())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - does not include users`() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createActionRecord(UserActionModel.ADD_USER),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                        createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
-                        createActionRecord(UserActionModel.ENTER_GUEST_MODE),
-                        createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
-                    )
-                )
-            var models: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { models = it }.launchIn(this)
-
-            assertThat(models).hasSize(4)
-            assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
-            assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
-            assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
-            assertThat(models?.get(3)).isEqualTo(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-            job.cancel()
-        }
-
-    private fun createUserRecord(id: Int, isSelected: Boolean = false): UserRecord {
-        return UserRecord(
-            info = UserInfo(id, "name$id", 0),
-            isCurrent = isSelected,
-        )
-    }
-
-    private fun createActionRecord(action: UserActionModel): UserRecord {
-        return UserRecord(
-            isAddUser = action == UserActionModel.ADD_USER,
-            isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
-            isGuest = action == UserActionModel.ENTER_GUEST_MODE,
-            isManageUsers = action == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-        )
-    }
-}
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/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
deleted file mode 100644
index f682e31..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
+++ /dev/null
@@ -1,740 +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.user.domain.interactor
-
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.graphics.drawable.Drawable
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import androidx.test.filters.SmallTest
-import com.android.internal.R.drawable.ic_account_circle
-import com.android.systemui.R
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.domain.model.ShowDialogRequestModel
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-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 kotlinx.coroutines.test.advanceUntilIdle
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserInteractorRefactoredTest : UserInteractorTest() {
-
-    override fun isRefactored(): Boolean {
-        return true
-    }
-
-    @Before
-    override fun setUp() {
-        super.setUp()
-
-        overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
-        overrideResource(R.dimen.max_avatar_size, 10)
-        overrideResource(
-            com.android.internal.R.string.config_supervisedUserCreationPackage,
-            SUPERVISED_USER_CREATION_APP_PACKAGE,
-        )
-        whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
-        whenever(manager.canAddMoreUsers(any())).thenReturn(true)
-    }
-
-    @Test
-    fun `onRecordSelected - user`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
-
-            verify(dialogShower).dismiss()
-            verify(activityManager).switchUser(userInfos[1].id)
-            Unit
-        }
-
-    @Test
-    fun `onRecordSelected - switch to guest user`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = true)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            underTest.onRecordSelected(UserRecord(info = userInfos.last()))
-
-            verify(activityManager).switchUser(userInfos.last().id)
-            Unit
-        }
-
-    @Test
-    fun `onRecordSelected - enter guest mode`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
-            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
-
-            underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
-
-            verify(dialogShower).dismiss()
-            verify(manager).createGuest(any())
-            Unit
-        }
-
-    @Test
-    fun `onRecordSelected - action`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = true)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
-
-            verify(dialogShower, never()).dismiss()
-            verify(activityStarter).startActivity(any(), anyBoolean())
-        }
-
-    @Test
-    fun `users - switcher enabled`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = true)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            var value: List<UserModel>? = null
-            val job = underTest.users.onEach { value = it }.launchIn(this)
-            assertUsers(models = value, count = 3, includeGuest = true)
-
-            job.cancel()
-        }
-
-    @Test
-    fun `users - switches to second user`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            var value: List<UserModel>? = null
-            val job = underTest.users.onEach { value = it }.launchIn(this)
-            userRepository.setSelectedUserInfo(userInfos[1])
-
-            assertUsers(models = value, count = 2, selectedIndex = 1)
-            job.cancel()
-        }
-
-    @Test
-    fun `users - switcher not enabled`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
-
-            var value: List<UserModel>? = null
-            val job = underTest.users.onEach { value = it }.launchIn(this)
-            assertUsers(models = value, count = 1)
-
-            job.cancel()
-        }
-
-    @Test
-    fun selectedUser() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            var value: UserModel? = null
-            val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
-            assertUser(value, id = 0, isSelected = true)
-
-            userRepository.setSelectedUserInfo(userInfos[1])
-            assertUser(value, id = 1, isSelected = true)
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device unlocked`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            keyguardRepository.setKeyguardShowing(false)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device unlocked user not primary - empty list`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[1])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            keyguardRepository.setKeyguardShowing(false)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device unlocked user is guest - empty list`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = true)
-            assertThat(userInfos[1].isGuest).isTrue()
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[1])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            keyguardRepository.setKeyguardShowing(false)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device locked add from lockscreen set - full list`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(
-                UserSwitcherSettingsModel(
-                    isUserSwitcherEnabled = true,
-                    isAddUsersFromLockscreen = true,
-                )
-            )
-            keyguardRepository.setKeyguardShowing(false)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device locked - only guest action and manage user is shown`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            keyguardRepository.setKeyguardShowing(true)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
-                    )
-                )
-
-            job.cancel()
-        }
-
-    @Test
-    fun `executeAction - add user - dialog shown`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            keyguardRepository.setKeyguardShowing(false)
-            var dialogRequest: ShowDialogRequestModel? = null
-            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-            val dialogShower: UserSwitchDialogController.DialogShower = mock()
-
-            underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
-            assertThat(dialogRequest)
-                .isEqualTo(
-                    ShowDialogRequestModel.ShowAddUserDialog(
-                        userHandle = userInfos[0].userHandle,
-                        isKeyguardShowing = false,
-                        showEphemeralMessage = false,
-                        dialogShower = dialogShower,
-                    )
-                )
-
-            underTest.onDialogShown()
-            assertThat(dialogRequest).isNull()
-
-            job.cancel()
-        }
-
-    @Test
-    fun `executeAction - add supervised user - starts activity`() =
-        runBlocking(IMMEDIATE) {
-            underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
-            val intentCaptor = kotlinArgumentCaptor<Intent>()
-            verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
-            assertThat(intentCaptor.value.action)
-                .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
-            assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
-        }
-
-    @Test
-    fun `executeAction - navigate to manage users`() =
-        runBlocking(IMMEDIATE) {
-            underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
-            val intentCaptor = kotlinArgumentCaptor<Intent>()
-            verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
-            assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
-        }
-
-    @Test
-    fun `executeAction - guest mode`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
-            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
-            val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
-            val showDialogsJob =
-                underTest.dialogShowRequests
-                    .onEach {
-                        dialogRequests.add(it)
-                        if (it != null) {
-                            underTest.onDialogShown()
-                        }
-                    }
-                    .launchIn(this)
-            val dismissDialogsJob =
-                underTest.dialogDismissRequests
-                    .onEach {
-                        if (it != null) {
-                            underTest.onDialogDismissed()
-                        }
-                    }
-                    .launchIn(this)
-
-            underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
-            assertThat(dialogRequests)
-                .contains(
-                    ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
-                )
-            verify(activityManager).switchUser(guestUserInfo.id)
-
-            showDialogsJob.cancel()
-            dismissDialogsJob.cancel()
-        }
-
-    @Test
-    fun `selectUser - already selected guest re-selected - exit guest dialog`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = true)
-            val guestUserInfo = userInfos[1]
-            assertThat(guestUserInfo.isGuest).isTrue()
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(guestUserInfo)
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            var dialogRequest: ShowDialogRequestModel? = null
-            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
-            underTest.selectUser(
-                newlySelectedUserId = guestUserInfo.id,
-                dialogShower = dialogShower,
-            )
-
-            assertThat(dialogRequest)
-                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
-            verify(dialogShower, never()).dismiss()
-            job.cancel()
-        }
-
-    @Test
-    fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = true)
-            val guestUserInfo = userInfos[1]
-            assertThat(guestUserInfo.isGuest).isTrue()
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(guestUserInfo)
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            var dialogRequest: ShowDialogRequestModel? = null
-            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
-            underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
-
-            assertThat(dialogRequest)
-                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
-            verify(dialogShower, never()).dismiss()
-            job.cancel()
-        }
-
-    @Test
-    fun `selectUser - not currently guest - switches users`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            var dialogRequest: ShowDialogRequestModel? = null
-            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
-            underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
-
-            assertThat(dialogRequest).isNull()
-            verify(activityManager).switchUser(userInfos[1].id)
-            verify(dialogShower).dismiss()
-            job.cancel()
-        }
-
-    @Test
-    fun `Telephony call state changes - refreshes users`() =
-        runBlocking(IMMEDIATE) {
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            telephonyRepository.setCallState(1)
-
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
-        }
-
-    @Test
-    fun `User switched broadcast`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            val callback1: UserInteractor.UserCallback = mock()
-            val callback2: UserInteractor.UserCallback = mock()
-            underTest.addCallback(callback1)
-            underTest.addCallback(callback2)
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            userRepository.setSelectedUserInfo(userInfos[1])
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_SWITCHED)
-                        .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
-                )
-            }
-
-            verify(callback1).onUserStateChanged()
-            verify(callback2).onUserStateChanged()
-            assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
-        }
-
-    @Test
-    fun `User info changed broadcast`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_INFO_CHANGED),
-                )
-            }
-
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
-        }
-
-    @Test
-    fun `System user unlocked broadcast - refresh users`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_UNLOCKED)
-                        .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
-                )
-            }
-
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
-        }
-
-    @Test
-    fun `Non-system user unlocked broadcast - do not refresh users`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
-                )
-            }
-
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
-        }
-
-    @Test
-    fun userRecords() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = false)
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            keyguardRepository.setKeyguardShowing(false)
-
-            testCoroutineScope.advanceUntilIdle()
-
-            assertRecords(
-                records = underTest.userRecords.value,
-                userIds = listOf(0, 1, 2),
-                selectedUserIndex = 0,
-                includeGuest = false,
-                expectedActions =
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    ),
-            )
-        }
-
-    @Test
-    fun selectedUserRecord() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = true)
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            keyguardRepository.setKeyguardShowing(false)
-
-            assertRecordForUser(
-                record = underTest.selectedUserRecord.value,
-                id = 0,
-                hasPicture = true,
-                isCurrent = true,
-                isSwitchToEnabled = true,
-            )
-        }
-
-    private fun assertUsers(
-        models: List<UserModel>?,
-        count: Int,
-        selectedIndex: Int = 0,
-        includeGuest: Boolean = false,
-    ) {
-        checkNotNull(models)
-        assertThat(models.size).isEqualTo(count)
-        models.forEachIndexed { index, model ->
-            assertUser(
-                model = model,
-                id = index,
-                isSelected = index == selectedIndex,
-                isGuest = includeGuest && index == count - 1
-            )
-        }
-    }
-
-    private fun assertUser(
-        model: UserModel?,
-        id: Int,
-        isSelected: Boolean = false,
-        isGuest: Boolean = false,
-    ) {
-        checkNotNull(model)
-        assertThat(model.id).isEqualTo(id)
-        assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
-        assertThat(model.isSelected).isEqualTo(isSelected)
-        assertThat(model.isSelectable).isTrue()
-        assertThat(model.isGuest).isEqualTo(isGuest)
-    }
-
-    private fun assertRecords(
-        records: List<UserRecord>,
-        userIds: List<Int>,
-        selectedUserIndex: Int = 0,
-        includeGuest: Boolean = false,
-        expectedActions: List<UserActionModel> = emptyList(),
-    ) {
-        assertThat(records.size >= userIds.size).isTrue()
-        userIds.indices.forEach { userIndex ->
-            val record = records[userIndex]
-            assertThat(record.info).isNotNull()
-            val isGuest = includeGuest && userIndex == userIds.size - 1
-            assertRecordForUser(
-                record = record,
-                id = userIds[userIndex],
-                hasPicture = !isGuest,
-                isCurrent = userIndex == selectedUserIndex,
-                isGuest = isGuest,
-                isSwitchToEnabled = true,
-            )
-        }
-
-        assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
-        (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
-            val record = records[actionIndex]
-            assertThat(record.info).isNull()
-            assertRecordForAction(
-                record = record,
-                type = expectedActions[actionIndex - userIds.size],
-            )
-        }
-    }
-
-    private fun assertRecordForUser(
-        record: UserRecord?,
-        id: Int? = null,
-        hasPicture: Boolean = false,
-        isCurrent: Boolean = false,
-        isGuest: Boolean = false,
-        isSwitchToEnabled: Boolean = false,
-    ) {
-        checkNotNull(record)
-        assertThat(record.info?.id).isEqualTo(id)
-        assertThat(record.picture != null).isEqualTo(hasPicture)
-        assertThat(record.isCurrent).isEqualTo(isCurrent)
-        assertThat(record.isGuest).isEqualTo(isGuest)
-        assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
-    }
-
-    private fun assertRecordForAction(
-        record: UserRecord,
-        type: UserActionModel,
-    ) {
-        assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
-        assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
-        assertThat(record.isAddSupervisedUser)
-            .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
-    }
-
-    private fun createUserInfos(
-        count: Int,
-        includeGuest: Boolean,
-    ): List<UserInfo> {
-        return (0 until count).map { index ->
-            val isGuest = includeGuest && index == count - 1
-            createUserInfo(
-                id = index,
-                name =
-                    if (isGuest) {
-                        "guest"
-                    } else {
-                        "user_$index"
-                    },
-                isPrimary = !isGuest && index == 0,
-                isGuest = isGuest,
-            )
-        }
-    }
-
-    private fun createUserInfo(
-        id: Int,
-        name: String,
-        isPrimary: Boolean = false,
-        isGuest: Boolean = false,
-    ): UserInfo {
-        return UserInfo(
-            id,
-            name,
-            /* iconPath= */ "",
-            /* flags= */ if (isPrimary) {
-                UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
-            } else {
-                0
-            },
-            if (isGuest) {
-                UserManager.USER_TYPE_FULL_GUEST
-            } else {
-                UserManager.USER_TYPE_FULL_SYSTEM
-            },
-        )
-    }
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-        private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
-        private val GUEST_ICON: Drawable = mock()
-        private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
-    }
-}
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..5beb2b3 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
@@ -19,9 +19,23 @@
 
 import android.app.ActivityManager
 import android.app.admin.DevicePolicyManager
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
 import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.internal.R.drawable.ic_account_circle
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Text
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -29,38 +43,77 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.UserSwitcherActivity
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+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 kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-abstract class UserInteractorTest : SysuiTestCase() {
+@SmallTest
+@RunWith(JUnit4::class)
+class UserInteractorTest : SysuiTestCase() {
 
-    @Mock protected lateinit var controller: UserSwitcherController
-    @Mock protected lateinit var activityStarter: ActivityStarter
-    @Mock protected lateinit var manager: UserManager
-    @Mock protected lateinit var activityManager: ActivityManager
-    @Mock protected lateinit var deviceProvisionedController: DeviceProvisionedController
-    @Mock protected lateinit var devicePolicyManager: DevicePolicyManager
-    @Mock protected lateinit var uiEventLogger: UiEventLogger
-    @Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var manager: UserManager
+    @Mock private lateinit var activityManager: ActivityManager
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
+    @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+    @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
 
-    protected lateinit var underTest: UserInteractor
+    private lateinit var underTest: UserInteractor
 
-    protected lateinit var testCoroutineScope: TestCoroutineScope
-    protected lateinit var userRepository: FakeUserRepository
-    protected lateinit var keyguardRepository: FakeKeyguardRepository
-    protected lateinit var telephonyRepository: FakeTelephonyRepository
+    private lateinit var testCoroutineScope: TestCoroutineScope
+    private lateinit var userRepository: FakeUserRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var telephonyRepository: FakeTelephonyRepository
+    private lateinit var featureFlags: FakeFeatureFlags
 
-    abstract fun isRefactored(): Boolean
-
-    open fun setUp() {
+    @Before
+    fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
+        whenever(manager.canAddMoreUsers(any())).thenReturn(true)
 
+        overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
+        overrideResource(R.dimen.max_avatar_size, 10)
+        overrideResource(
+            com.android.internal.R.string.config_supervisedUserCreationPackage,
+            SUPERVISED_USER_CREATION_APP_PACKAGE,
+        )
+
+        featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
         userRepository = FakeUserRepository()
         keyguardRepository = FakeKeyguardRepository()
         telephonyRepository = FakeTelephonyRepository()
@@ -75,16 +128,11 @@
             UserInteractor(
                 applicationContext = context,
                 repository = userRepository,
-                controller = controller,
                 activityStarter = activityStarter,
                 keyguardInteractor =
                     KeyguardInteractor(
                         repository = keyguardRepository,
                     ),
-                featureFlags =
-                    FakeFeatureFlags().apply {
-                        set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored())
-                    },
                 manager = manager,
                 applicationScope = testCoroutineScope,
                 telephonyInteractor =
@@ -107,11 +155,872 @@
                         devicePolicyManager = devicePolicyManager,
                         refreshUsersScheduler = refreshUsersScheduler,
                         uiEventLogger = uiEventLogger,
-                    )
+                        resumeSessionReceiver = resumeSessionReceiver,
+                        resetOrExitSessionReceiver = resetOrExitSessionReceiver,
+                    ),
+                featureFlags = featureFlags,
             )
     }
 
+    @Test
+    fun `onRecordSelected - user`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
+
+            verify(dialogShower).dismiss()
+            verify(activityManager).switchUser(userInfos[1].id)
+            Unit
+        }
+
+    @Test
+    fun `onRecordSelected - switch to guest user`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            underTest.onRecordSelected(UserRecord(info = userInfos.last()))
+
+            verify(activityManager).switchUser(userInfos.last().id)
+            Unit
+        }
+
+    @Test
+    fun `onRecordSelected - enter guest mode`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+
+            underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
+
+            verify(dialogShower).dismiss()
+            verify(manager).createGuest(any())
+            Unit
+        }
+
+    @Test
+    fun `onRecordSelected - action`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
+
+            verify(dialogShower, never()).dismiss()
+            verify(activityStarter).startActivity(any(), anyBoolean())
+        }
+
+    @Test
+    fun `users - switcher enabled`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            assertUsers(models = value, count = 3, includeGuest = true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `users - switches to second user`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            userRepository.setSelectedUserInfo(userInfos[1])
+
+            assertUsers(models = value, count = 2, selectedIndex = 1)
+            job.cancel()
+        }
+
+    @Test
+    fun `users - switcher not enabled`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            assertUsers(models = value, count = 1)
+
+            job.cancel()
+        }
+
+    @Test
+    fun selectedUser() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: UserModel? = null
+            val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
+            assertUser(value, id = 0, isSelected = true)
+
+            userRepository.setSelectedUserInfo(userInfos[1])
+            assertUser(value, id = 1, isSelected = true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked - full screen`() =
+        runBlocking(IMMEDIATE) {
+            featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked user not primary - empty list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked user is guest - empty list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            assertThat(userInfos[1].isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device locked add from lockscreen set - full list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(
+                UserSwitcherSettingsModel(
+                    isUserSwitcherEnabled = true,
+                    isAddUsersFromLockscreen = true,
+                )
+            )
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device locked add from lockscreen set - full list - full screen`() =
+        runBlocking(IMMEDIATE) {
+            featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(
+                UserSwitcherSettingsModel(
+                    isUserSwitcherEnabled = true,
+                    isAddUsersFromLockscreen = true,
+                )
+            )
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device locked - only  manage user is shown`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(true)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value).isEqualTo(listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT))
+
+            job.cancel()
+        }
+
+    @Test
+    fun `executeAction - add user - dialog shown`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+            val dialogShower: UserSwitchDialogController.DialogShower = mock()
+
+            underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
+            assertThat(dialogRequest)
+                .isEqualTo(
+                    ShowDialogRequestModel.ShowAddUserDialog(
+                        userHandle = userInfos[0].userHandle,
+                        isKeyguardShowing = false,
+                        showEphemeralMessage = false,
+                        dialogShower = dialogShower,
+                    )
+                )
+
+            underTest.onDialogShown()
+            assertThat(dialogRequest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `executeAction - add supervised user - starts activity`() =
+        runBlocking(IMMEDIATE) {
+            underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
+
+            val intentCaptor = kotlinArgumentCaptor<Intent>()
+            verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
+            assertThat(intentCaptor.value.action)
+                .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
+            assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
+        }
+
+    @Test
+    fun `executeAction - navigate to manage users`() =
+        runBlocking(IMMEDIATE) {
+            underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+            val intentCaptor = kotlinArgumentCaptor<Intent>()
+            verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
+            assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
+        }
+
+    @Test
+    fun `executeAction - guest mode`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+            val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
+            val showDialogsJob =
+                underTest.dialogShowRequests
+                    .onEach {
+                        dialogRequests.add(it)
+                        if (it != null) {
+                            underTest.onDialogShown()
+                        }
+                    }
+                    .launchIn(this)
+            val dismissDialogsJob =
+                underTest.dialogDismissRequests
+                    .onEach {
+                        if (it != null) {
+                            underTest.onDialogDismissed()
+                        }
+                    }
+                    .launchIn(this)
+
+            underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
+
+            assertThat(dialogRequests)
+                .contains(
+                    ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
+                )
+            verify(activityManager).switchUser(guestUserInfo.id)
+
+            showDialogsJob.cancel()
+            dismissDialogsJob.cancel()
+        }
+
+    @Test
+    fun `selectUser - already selected guest re-selected - exit guest dialog`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            val guestUserInfo = userInfos[1]
+            assertThat(guestUserInfo.isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(guestUserInfo)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(
+                newlySelectedUserId = guestUserInfo.id,
+                dialogShower = dialogShower,
+            )
+
+            assertThat(dialogRequest)
+                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+            verify(dialogShower, never()).dismiss()
+            job.cancel()
+        }
+
+    @Test
+    fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            val guestUserInfo = userInfos[1]
+            assertThat(guestUserInfo.isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(guestUserInfo)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
+
+            assertThat(dialogRequest)
+                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+            verify(dialogShower, never()).dismiss()
+            job.cancel()
+        }
+
+    @Test
+    fun `selectUser - not currently guest - switches users`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
+
+            assertThat(dialogRequest).isNull()
+            verify(activityManager).switchUser(userInfos[1].id)
+            verify(dialogShower).dismiss()
+            job.cancel()
+        }
+
+    @Test
+    fun `Telephony call state changes - refreshes users`() =
+        runBlocking(IMMEDIATE) {
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            telephonyRepository.setCallState(1)
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `User switched broadcast`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            val callback1: UserInteractor.UserCallback = mock()
+            val callback2: UserInteractor.UserCallback = mock()
+            underTest.addCallback(callback1)
+            underTest.addCallback(callback2)
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            userRepository.setSelectedUserInfo(userInfos[1])
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_SWITCHED)
+                        .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
+                )
+            }
+
+            verify(callback1).onUserStateChanged()
+            verify(callback2).onUserStateChanged()
+            assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `User info changed broadcast`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_INFO_CHANGED),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `System user unlocked broadcast - refresh users`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_UNLOCKED)
+                        .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `Non-system user unlocked broadcast - do not refresh users`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
+        }
+
+    @Test
+    fun userRecords() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = false)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+
+            testCoroutineScope.advanceUntilIdle()
+
+            assertRecords(
+                records = underTest.userRecords.value,
+                userIds = listOf(0, 1, 2),
+                selectedUserIndex = 0,
+                includeGuest = false,
+                expectedActions =
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    ),
+            )
+        }
+
+    @Test
+    fun userRecordsFullScreen() =
+        runBlocking(IMMEDIATE) {
+            featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            val userInfos = createUserInfos(count = 3, includeGuest = false)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+
+            testCoroutineScope.advanceUntilIdle()
+
+            assertRecords(
+                records = underTest.userRecords.value,
+                userIds = listOf(0, 1, 2),
+                selectedUserIndex = 0,
+                includeGuest = false,
+                expectedActions =
+                    listOf(
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    ),
+            )
+        }
+
+    @Test
+    fun selectedUserRecord() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+
+            assertRecordForUser(
+                record = underTest.selectedUserRecord.value,
+                id = 0,
+                hasPicture = true,
+                isCurrent = true,
+                isSwitchToEnabled = true,
+            )
+        }
+
+    @Test
+    fun `users - secondary user - guest user can be switched to`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var res: List<UserModel>? = null
+            val job = underTest.users.onEach { res = it }.launchIn(this)
+            assertThat(res?.size == 3).isTrue()
+            assertThat(res?.find { it.isGuest }).isNotNull()
+            job.cancel()
+        }
+
+    @Test
+    fun `users - secondary user - no guest action`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var res: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { res = it }.launchIn(this)
+            assertThat(res?.find { it == UserActionModel.ENTER_GUEST_MODE }).isNull()
+            job.cancel()
+        }
+
+    @Test
+    fun `users - secondary user - no guest user record`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var res: List<UserRecord>? = null
+            val job = underTest.userRecords.onEach { res = it }.launchIn(this)
+            assertThat(res?.find { it.isGuest }).isNull()
+            job.cancel()
+        }
+
+    @Test
+    fun `show user switcher - full screen disabled - shows dialog switcher`() =
+        runBlocking(IMMEDIATE) {
+            var dialogRequest: ShowDialogRequestModel? = null
+            val expandable = mock<Expandable>()
+            underTest.showUserSwitcher(context, expandable)
+
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            // Dialog is shown.
+            assertThat(dialogRequest)
+                .isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable))
+
+            underTest.onDialogShown()
+            assertThat(dialogRequest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `show user switcher - full screen enabled - launches activity`() {
+        featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+
+        val expandable = mock<Expandable>()
+        underTest.showUserSwitcher(context, expandable)
+
+        // Dialog is shown.
+        val intentCaptor = argumentCaptor<Intent>()
+        verify(activityStarter)
+            .startActivity(
+                intentCaptor.capture(),
+                /* dismissShade= */ eq(true),
+                /* ActivityLaunchAnimator.Controller= */ nullable(),
+                /* showOverLockscreenWhenLocked= */ eq(true),
+                eq(UserHandle.SYSTEM),
+            )
+        assertThat(intentCaptor.value.component)
+            .isEqualTo(
+                ComponentName(
+                    context,
+                    UserSwitcherActivity::class.java,
+                )
+            )
+    }
+
+    @Test
+    fun `users - secondary user - managed profile is not included`() =
+        runBlocking(IMMEDIATE) {
+            var userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList()
+            userInfos.add(
+                UserInfo(
+                    50,
+                    "Work Profile",
+                    /* iconPath= */ "",
+                    /* flags= */ UserInfo.FLAG_MANAGED_PROFILE
+                )
+            )
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var res: List<UserModel>? = null
+            val job = underTest.users.onEach { res = it }.launchIn(this)
+            assertThat(res?.size == 3).isTrue()
+            job.cancel()
+        }
+
+    @Test
+    fun `current user is not primary and user switcher is disabled`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+            var selectedUser: UserModel? = null
+            val job = underTest.selectedUser.onEach { selectedUser = it }.launchIn(this)
+            assertThat(selectedUser).isNotNull()
+            job.cancel()
+        }
+
+    private fun assertUsers(
+        models: List<UserModel>?,
+        count: Int,
+        selectedIndex: Int = 0,
+        includeGuest: Boolean = false,
+    ) {
+        checkNotNull(models)
+        assertThat(models.size).isEqualTo(count)
+        models.forEachIndexed { index, model ->
+            assertUser(
+                model = model,
+                id = index,
+                isSelected = index == selectedIndex,
+                isGuest = includeGuest && index == count - 1
+            )
+        }
+    }
+
+    private fun assertUser(
+        model: UserModel?,
+        id: Int,
+        isSelected: Boolean = false,
+        isGuest: Boolean = false,
+    ) {
+        checkNotNull(model)
+        assertThat(model.id).isEqualTo(id)
+        assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
+        assertThat(model.isSelected).isEqualTo(isSelected)
+        assertThat(model.isSelectable).isTrue()
+        assertThat(model.isGuest).isEqualTo(isGuest)
+    }
+
+    private fun assertRecords(
+        records: List<UserRecord>,
+        userIds: List<Int>,
+        selectedUserIndex: Int = 0,
+        includeGuest: Boolean = false,
+        expectedActions: List<UserActionModel> = emptyList(),
+    ) {
+        assertThat(records.size >= userIds.size).isTrue()
+        userIds.indices.forEach { userIndex ->
+            val record = records[userIndex]
+            assertThat(record.info).isNotNull()
+            val isGuest = includeGuest && userIndex == userIds.size - 1
+            assertRecordForUser(
+                record = record,
+                id = userIds[userIndex],
+                hasPicture = !isGuest,
+                isCurrent = userIndex == selectedUserIndex,
+                isGuest = isGuest,
+                isSwitchToEnabled = true,
+            )
+        }
+
+        assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
+        (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
+            val record = records[actionIndex]
+            assertThat(record.info).isNull()
+            assertRecordForAction(
+                record = record,
+                type = expectedActions[actionIndex - userIds.size],
+            )
+        }
+    }
+
+    private fun assertRecordForUser(
+        record: UserRecord?,
+        id: Int? = null,
+        hasPicture: Boolean = false,
+        isCurrent: Boolean = false,
+        isGuest: Boolean = false,
+        isSwitchToEnabled: Boolean = false,
+    ) {
+        checkNotNull(record)
+        assertThat(record.info?.id).isEqualTo(id)
+        assertThat(record.picture != null).isEqualTo(hasPicture)
+        assertThat(record.isCurrent).isEqualTo(isCurrent)
+        assertThat(record.isGuest).isEqualTo(isGuest)
+        assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
+    }
+
+    private fun assertRecordForAction(
+        record: UserRecord,
+        type: UserActionModel,
+    ) {
+        assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
+        assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
+        assertThat(record.isAddSupervisedUser)
+            .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
+    }
+
+    private fun createUserInfos(
+        count: Int,
+        includeGuest: Boolean,
+    ): List<UserInfo> {
+        return (0 until count).map { index ->
+            val isGuest = includeGuest && index == count - 1
+            createUserInfo(
+                id = index,
+                name =
+                    if (isGuest) {
+                        "guest"
+                    } else {
+                        "user_$index"
+                    },
+                isPrimary = !isGuest && index == 0,
+                isGuest = isGuest,
+            )
+        }
+    }
+
+    private fun createUserInfo(
+        id: Int,
+        name: String,
+        isPrimary: Boolean = false,
+        isGuest: Boolean = false,
+    ): UserInfo {
+        return UserInfo(
+            id,
+            name,
+            /* iconPath= */ "",
+            /* flags= */ if (isPrimary) {
+                UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL
+            } else {
+                UserInfo.FLAG_FULL
+            },
+            if (isGuest) {
+                UserManager.USER_TYPE_FULL_GUEST
+            } else {
+                UserManager.USER_TYPE_FULL_SYSTEM
+            },
+        )
+    }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
+        private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        private val GUEST_ICON: Drawable = mock()
+        private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
deleted file mode 100644
index 6a17c8d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
+++ /dev/null
@@ -1,174 +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.user.domain.interactor
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
-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
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.verify
-
-@SmallTest
-@RunWith(JUnit4::class)
-open class UserInteractorUnrefactoredTest : UserInteractorTest() {
-
-    override fun isRefactored(): Boolean {
-        return false
-    }
-
-    @Before
-    override fun setUp() {
-        super.setUp()
-    }
-
-    @Test
-    fun `actions - not actionable when locked and locked - no actions`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(UserActionModel.values().toList())
-            userRepository.setActionableWhenLocked(false)
-            keyguardRepository.setKeyguardShowing(true)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions).isEmpty()
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - not actionable when locked and not locked`() =
-        runBlocking(IMMEDIATE) {
-            setActions()
-            userRepository.setActionableWhenLocked(false)
-            keyguardRepository.setKeyguardShowing(false)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - actionable when locked and not locked`() =
-        runBlocking(IMMEDIATE) {
-            setActions()
-            userRepository.setActionableWhenLocked(true)
-            keyguardRepository.setKeyguardShowing(false)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - actionable when locked and locked`() =
-        runBlocking(IMMEDIATE) {
-            setActions()
-            userRepository.setActionableWhenLocked(true)
-            keyguardRepository.setKeyguardShowing(true)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun selectUser() {
-        val userId = 3
-
-        underTest.selectUser(userId)
-
-        verify(controller).onUserSelected(eq(userId), nullable())
-    }
-
-    @Test
-    fun `executeAction - guest`() {
-        underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
-        verify(controller).createAndSwitchToGuestUser(nullable())
-    }
-
-    @Test
-    fun `executeAction - add user`() {
-        underTest.executeAction(UserActionModel.ADD_USER)
-
-        verify(controller).showAddUserDialog(nullable())
-    }
-
-    @Test
-    fun `executeAction - add supervised user`() {
-        underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
-        verify(controller).startSupervisedUserActivity()
-    }
-
-    @Test
-    fun `executeAction - manage users`() {
-        underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
-        verify(activityStarter).startActivity(any(), anyBoolean())
-    }
-
-    private fun setActions() {
-        userRepository.setActions(UserActionModel.values().toList())
-    }
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
new file mode 100644
index 0000000..108fa62
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -0,0 +1,308 @@
+/*
+ * 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.user.ui.viewmodel
+
+import android.app.ActivityManager
+import android.app.admin.DevicePolicyManager
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+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
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.doAnswer
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class StatusBarUserChipViewModelTest : SysuiTestCase() {
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var activityManager: ActivityManager
+    @Mock private lateinit var manager: UserManager
+    @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: StatusBarUserChipViewModel
+
+    private val userRepository = FakeUserRepository()
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val featureFlags = FakeFeatureFlags()
+    private lateinit var guestUserInteractor: GuestUserInteractor
+    private lateinit var refreshUsersScheduler: RefreshUsersScheduler
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        doAnswer { invocation ->
+                val userId = invocation.arguments[0] as Int
+                when (userId) {
+                    USER_ID_0 -> return@doAnswer USER_IMAGE_0
+                    USER_ID_1 -> return@doAnswer USER_IMAGE_1
+                    USER_ID_2 -> return@doAnswer USER_IMAGE_2
+                    else -> return@doAnswer mock<Bitmap>()
+                }
+            }
+            .`when`(manager)
+            .getUserIcon(anyInt())
+
+        userRepository.isStatusBarUserChipEnabled = true
+
+        refreshUsersScheduler =
+            RefreshUsersScheduler(
+                applicationScope = testScope.backgroundScope,
+                mainDispatcher = testDispatcher,
+                repository = userRepository,
+            )
+        guestUserInteractor =
+            GuestUserInteractor(
+                applicationContext = context,
+                applicationScope = testScope.backgroundScope,
+                mainDispatcher = testDispatcher,
+                backgroundDispatcher = testDispatcher,
+                manager = manager,
+                repository = userRepository,
+                deviceProvisionedController = deviceProvisionedController,
+                devicePolicyManager = devicePolicyManager,
+                refreshUsersScheduler = refreshUsersScheduler,
+                uiEventLogger = uiEventLogger,
+                resumeSessionReceiver = resumeSessionReceiver,
+                resetOrExitSessionReceiver = resetOrExitSessionReceiver,
+            )
+
+        underTest = viewModel()
+    }
+
+    @Test
+    fun `config is false - chip is disabled`() {
+        // the enabled bit is set at SystemUI startup, so recreate the view model here
+        userRepository.isStatusBarUserChipEnabled = false
+        underTest = viewModel()
+
+        assertThat(underTest.chipEnabled).isFalse()
+    }
+
+    @Test
+    fun `config is true - chip is enabled`() {
+        // the enabled bit is set at SystemUI startup, so recreate the view model here
+        userRepository.isStatusBarUserChipEnabled = true
+        underTest = viewModel()
+
+        assertThat(underTest.chipEnabled).isTrue()
+    }
+
+    @Test
+    fun `should show chip criteria - single user`() =
+        testScope.runTest {
+            userRepository.setUserInfos(listOf(USER_0))
+            userRepository.setSelectedUserInfo(USER_0)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            val values = mutableListOf<Boolean>()
+
+            val job = launch { underTest.isChipVisible.toList(values) }
+            advanceUntilIdle()
+
+            assertThat(values).containsExactly(false)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `should show chip criteria - multiple users`() =
+        testScope.runTest {
+            setMultipleUsers()
+
+            var latest: Boolean? = null
+            val job = underTest.isChipVisible.onEach { latest = it }.launchIn(this)
+            yield()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `user chip name - shows selected user info`() =
+        testScope.runTest {
+            setMultipleUsers()
+
+            var latest: Text? = null
+            val job = underTest.userName.onEach { latest = it }.launchIn(this)
+
+            userRepository.setSelectedUserInfo(USER_0)
+            assertThat(latest).isEqualTo(USER_NAME_0)
+
+            userRepository.setSelectedUserInfo(USER_1)
+            assertThat(latest).isEqualTo(USER_NAME_1)
+
+            userRepository.setSelectedUserInfo(USER_2)
+            assertThat(latest).isEqualTo(USER_NAME_2)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `user chip avatar - shows selected user info`() =
+        testScope.runTest {
+            setMultipleUsers()
+
+            // A little hacky. System server passes us bitmaps and we wrap them in the interactor.
+            // Unwrap them to make sure we're always tracking the current user's bitmap
+            var latest: Bitmap? = null
+            val job =
+                underTest.userAvatar
+                    .onEach {
+                        if (it !is BitmapDrawable) {
+                            latest = null
+                        }
+
+                        latest = (it as BitmapDrawable).bitmap
+                    }
+                    .launchIn(this)
+
+            userRepository.setSelectedUserInfo(USER_0)
+            assertThat(latest).isEqualTo(USER_IMAGE_0)
+
+            userRepository.setSelectedUserInfo(USER_1)
+            assertThat(latest).isEqualTo(USER_IMAGE_1)
+
+            userRepository.setSelectedUserInfo(USER_2)
+            assertThat(latest).isEqualTo(USER_IMAGE_2)
+
+            job.cancel()
+        }
+
+    private fun viewModel(): StatusBarUserChipViewModel {
+        return StatusBarUserChipViewModel(
+            context = context,
+            interactor =
+                UserInteractor(
+                    applicationContext = context,
+                    repository = userRepository,
+                    activityStarter = activityStarter,
+                    keyguardInteractor =
+                        KeyguardInteractor(
+                            repository = keyguardRepository,
+                        ),
+                    featureFlags =
+                        FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) },
+                    manager = manager,
+                    applicationScope = testScope.backgroundScope,
+                    telephonyInteractor =
+                        TelephonyInteractor(
+                            repository = FakeTelephonyRepository(),
+                        ),
+                    broadcastDispatcher = fakeBroadcastDispatcher,
+                    backgroundDispatcher = testDispatcher,
+                    activityManager = activityManager,
+                    refreshUsersScheduler = refreshUsersScheduler,
+                    guestUserInteractor = guestUserInteractor,
+                )
+        )
+    }
+
+    private suspend fun setMultipleUsers() {
+        userRepository.setUserInfos(listOf(USER_0, USER_1, USER_2))
+        userRepository.setSelectedUserInfo(USER_0)
+        userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+    }
+
+    companion object {
+        private const val USER_ID_0 = 0
+        private val USER_IMAGE_0 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        private val USER_NAME_0 = Text.Loaded("zero")
+
+        private const val USER_ID_1 = 1
+        private val USER_IMAGE_1 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        private val USER_NAME_1 = Text.Loaded("one")
+
+        private const val USER_ID_2 = 2
+        private val USER_IMAGE_2 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        private val USER_NAME_2 = Text.Loaded("two")
+
+        private val USER_0 =
+            UserInfo(
+                USER_ID_0,
+                USER_NAME_0.text!!,
+                /* iconPath */ "",
+                /* flags */ UserInfo.FLAG_FULL,
+                /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+            )
+
+        private val USER_1 =
+            UserInfo(
+                USER_ID_1,
+                USER_NAME_1.text!!,
+                /* iconPath */ "",
+                /* flags */ UserInfo.FLAG_FULL,
+                /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+            )
+
+        private val USER_2 =
+            UserInfo(
+                USER_ID_2,
+                USER_NAME_2.text!!,
+                /* iconPath */ "",
+                /* flags */ UserInfo.FLAG_FULL,
+                /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+            )
+    }
+}
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..784a26b 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
@@ -19,10 +19,12 @@
 
 import android.app.ActivityManager
 import android.app.admin.DevicePolicyManager
-import android.graphics.drawable.Drawable
+import android.content.pm.UserInfo
 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
@@ -33,24 +35,27 @@
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -58,17 +63,19 @@
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class UserSwitcherViewModelTest : SysuiTestCase() {
 
-    @Mock private lateinit var controller: UserSwitcherController
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var activityManager: ActivityManager
     @Mock private lateinit var manager: UserManager
     @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
 
@@ -76,34 +83,53 @@
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var powerRepository: FakePowerRepository
 
+    private lateinit var testDispatcher: TestDispatcher
+    private lateinit var testScope: TestScope
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+        whenever(manager.getUserSwitchability(any()))
+            .thenReturn(UserManager.SWITCHABILITY_STATUS_OK)
+        overrideResource(
+            com.android.internal.R.string.config_supervisedUserCreationPackage,
+            SUPERVISED_USER_CREATION_PACKAGE,
+        )
 
+        testDispatcher = UnconfinedTestDispatcher()
+        testScope = TestScope(testDispatcher)
         userRepository = FakeUserRepository()
+        runBlocking {
+            userRepository.setSettings(
+                UserSwitcherSettingsModel(
+                    isUserSwitcherEnabled = true,
+                )
+            )
+        }
+
         keyguardRepository = FakeKeyguardRepository()
         powerRepository = FakePowerRepository()
-        val featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, true)
-        val scope = TestCoroutineScope()
         val refreshUsersScheduler =
             RefreshUsersScheduler(
-                applicationScope = scope,
-                mainDispatcher = IMMEDIATE,
+                applicationScope = testScope.backgroundScope,
+                mainDispatcher = testDispatcher,
                 repository = userRepository,
             )
         val guestUserInteractor =
             GuestUserInteractor(
                 applicationContext = context,
-                applicationScope = scope,
-                mainDispatcher = IMMEDIATE,
-                backgroundDispatcher = IMMEDIATE,
+                applicationScope = testScope.backgroundScope,
+                mainDispatcher = testDispatcher,
+                backgroundDispatcher = testDispatcher,
                 manager = manager,
                 repository = userRepository,
                 deviceProvisionedController = deviceProvisionedController,
                 devicePolicyManager = devicePolicyManager,
                 refreshUsersScheduler = refreshUsersScheduler,
                 uiEventLogger = uiEventLogger,
+                resumeSessionReceiver = resumeSessionReceiver,
+                resetOrExitSessionReceiver = resetOrExitSessionReceiver,
             )
 
         underTest =
@@ -112,21 +138,23 @@
                         UserInteractor(
                             applicationContext = context,
                             repository = userRepository,
-                            controller = controller,
                             activityStarter = activityStarter,
                             keyguardInteractor =
                                 KeyguardInteractor(
                                     repository = keyguardRepository,
                                 ),
-                            featureFlags = featureFlags,
+                            featureFlags =
+                                FakeFeatureFlags().apply {
+                                    set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+                                },
                             manager = manager,
-                            applicationScope = scope,
+                            applicationScope = testScope.backgroundScope,
                             telephonyInteractor =
                                 TelephonyInteractor(
                                     repository = FakeTelephonyRepository(),
                                 ),
                             broadcastDispatcher = fakeBroadcastDispatcher,
-                            backgroundDispatcher = IMMEDIATE,
+                            backgroundDispatcher = testDispatcher,
                             activityManager = activityManager,
                             refreshUsersScheduler = refreshUsersScheduler,
                             guestUserInteractor = guestUserInteractor,
@@ -135,242 +163,321 @@
                         PowerInteractor(
                             repository = powerRepository,
                         ),
-                    featureFlags = featureFlags,
                     guestUserInteractor = guestUserInteractor,
                 )
                 .create(UserSwitcherViewModel::class.java)
     }
 
     @Test
-    fun users() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setUsers(
+    fun users() = testScope.runTest {
+        val userInfos =
+            listOf(
+                UserInfo(
+                    /* id= */ 0,
+                    /* name= */ "zero",
+                    /* iconPath= */ "",
+                    /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
+                    UserManager.USER_TYPE_FULL_SYSTEM,
+                ),
+                UserInfo(
+                    /* id= */ 1,
+                    /* name= */ "one",
+                    /* iconPath= */ "",
+                    /* flags= */ UserInfo.FLAG_FULL,
+                    UserManager.USER_TYPE_FULL_SYSTEM,
+                ),
+                UserInfo(
+                    /* id= */ 2,
+                    /* name= */ "two",
+                    /* iconPath= */ "",
+                    /* flags= */ UserInfo.FLAG_FULL,
+                    UserManager.USER_TYPE_FULL_SYSTEM,
+                ),
+            )
+        userRepository.setUserInfos(userInfos)
+        userRepository.setSelectedUserInfo(userInfos[0])
+
+        val userViewModels = mutableListOf<List<UserViewModel>>()
+        val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
+
+        assertThat(userViewModels.last()).hasSize(3)
+        assertUserViewModel(
+            viewModel = userViewModels.last()[0],
+            viewKey = 0,
+            name = Text.Loaded("zero"),
+            isSelectionMarkerVisible = true,
+        )
+        assertUserViewModel(
+            viewModel = userViewModels.last()[1],
+            viewKey = 1,
+            name = Text.Loaded("one"),
+            isSelectionMarkerVisible = false,
+        )
+        assertUserViewModel(
+            viewModel = userViewModels.last()[2],
+            viewKey = 2,
+            name = Text.Loaded("two"),
+            isSelectionMarkerVisible = false,
+        )
+        job.cancel()
+    }
+
+    @Test
+    fun `maximumUserColumns - few users`() = testScope.runTest {
+        setUsers(count = 2)
+        val values = mutableListOf<Int>()
+        val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
+
+        assertThat(values.last()).isEqualTo(4)
+
+        job.cancel()
+    }
+
+    @Test
+    fun `maximumUserColumns - many users`() = testScope.runTest {
+        setUsers(count = 5)
+        val values = mutableListOf<Int>()
+        val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
+
+        assertThat(values.last()).isEqualTo(3)
+        job.cancel()
+    }
+
+    @Test
+    fun `isOpenMenuButtonVisible - has actions - true`() = testScope.runTest {
+        setUsers(2)
+
+        val isVisible = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
+
+        assertThat(isVisible.last()).isTrue()
+        job.cancel()
+    }
+
+    @Test
+    fun `isOpenMenuButtonVisible - no actions - false`() = testScope.runTest {
+        val userInfos = setUsers(2)
+        userRepository.setSelectedUserInfo(userInfos[1])
+        keyguardRepository.setKeyguardShowing(true)
+        whenever(manager.canAddMoreUsers(any())).thenReturn(false)
+
+        val isVisible = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
+
+        assertThat(isVisible.last()).isFalse()
+        job.cancel()
+    }
+
+    @Test
+    fun menu() = testScope.runTest {
+        val isMenuVisible = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) }
+        assertThat(isMenuVisible.last()).isFalse()
+
+        underTest.onOpenMenuButtonClicked()
+        assertThat(isMenuVisible.last()).isTrue()
+
+        underTest.onMenuClosed()
+        assertThat(isMenuVisible.last()).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun `menu actions`() = testScope.runTest {
+        setUsers(2)
+        val actions = mutableListOf<List<UserActionViewModel>>()
+        val job = launch(testDispatcher) { underTest.menu.toList(actions) }
+
+        assertThat(actions.last().map { it.viewKey })
+            .isEqualTo(
                 listOf(
-                    UserModel(
-                        id = 0,
-                        name = Text.Loaded("zero"),
-                        image = USER_IMAGE,
-                        isSelected = true,
-                        isSelectable = true,
-                        isGuest = false,
-                    ),
-                    UserModel(
-                        id = 1,
-                        name = Text.Loaded("one"),
-                        image = USER_IMAGE,
-                        isSelected = false,
-                        isSelectable = true,
-                        isGuest = false,
-                    ),
-                    UserModel(
-                        id = 2,
-                        name = Text.Loaded("two"),
-                        image = USER_IMAGE,
-                        isSelected = false,
-                        isSelectable = false,
-                        isGuest = false,
-                    ),
+                    UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
+                    UserActionModel.ADD_USER.ordinal.toLong(),
+                    UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
+                    UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
                 )
             )
 
-            var userViewModels: List<UserViewModel>? = null
-            val job = underTest.users.onEach { userViewModels = it }.launchIn(this)
+        job.cancel()
+    }
 
-            assertThat(userViewModels).hasSize(3)
-            assertUserViewModel(
-                viewModel = userViewModels?.get(0),
+    @Test
+    fun `isFinishRequested - finishes when user is switched`() = testScope.runTest {
+        val userInfos = setUsers(count = 2)
+        val isFinishRequested = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+        assertThat(isFinishRequested.last()).isFalse()
+
+        userRepository.setSelectedUserInfo(userInfos[1])
+
+        assertThat(isFinishRequested.last()).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun `isFinishRequested - finishes when the screen turns off`() = testScope.runTest {
+        setUsers(count = 2)
+        powerRepository.setInteractive(true)
+        val isFinishRequested = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+        assertThat(isFinishRequested.last()).isFalse()
+
+        powerRepository.setInteractive(false)
+
+        assertThat(isFinishRequested.last()).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun `isFinishRequested - finishes when cancel button is clicked`() = testScope.runTest {
+        setUsers(count = 2)
+        powerRepository.setInteractive(true)
+        val isFinishRequested = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+        assertThat(isFinishRequested.last()).isFalse()
+
+        underTest.onCancelButtonClicked()
+
+        assertThat(isFinishRequested.last()).isTrue()
+
+        underTest.onFinished()
+
+        assertThat(isFinishRequested.last()).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun `guest selected -- name is exit guest`() = testScope.runTest {
+        val userInfos =
+                listOf(
+                        UserInfo(
+                                /* id= */ 0,
+                                /* name= */ "zero",
+                                /* iconPath= */ "",
+                                /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
+                                UserManager.USER_TYPE_FULL_SYSTEM,
+                        ),
+                        UserInfo(
+                                /* id= */ 1,
+                                /* name= */ "one",
+                                /* iconPath= */ "",
+                                /* flags= */ UserInfo.FLAG_FULL,
+                                UserManager.USER_TYPE_FULL_GUEST,
+                        ),
+                )
+
+        userRepository.setUserInfos(userInfos)
+        userRepository.setSelectedUserInfo(userInfos[1])
+
+        val userViewModels = mutableListOf<List<UserViewModel>>()
+        val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
+
+        assertThat(userViewModels.last()).hasSize(2)
+        assertUserViewModel(
+                viewModel = userViewModels.last()[0],
                 viewKey = 0,
-                name = "zero",
-                isSelectionMarkerVisible = true,
-                alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA,
-                isClickable = true,
-            )
-            assertUserViewModel(
-                viewModel = userViewModels?.get(1),
+                name = Text.Loaded("zero"),
+                isSelectionMarkerVisible = false,
+        )
+        assertUserViewModel(
+                viewModel = userViewModels.last()[1],
                 viewKey = 1,
-                name = "one",
-                isSelectionMarkerVisible = false,
-                alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA,
-                isClickable = true,
-            )
-            assertUserViewModel(
-                viewModel = userViewModels?.get(2),
-                viewKey = 2,
-                name = "two",
-                isSelectionMarkerVisible = false,
-                alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA,
-                isClickable = false,
-            )
-            job.cancel()
-        }
+                name = Text.Resource(
+                    com.android.settingslib.R.string.guest_exit_quick_settings_button
+                ),
+                isSelectionMarkerVisible = true,
+        )
+        job.cancel()
+    }
 
     @Test
-    fun `maximumUserColumns - few users`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 2)
-            var value: Int? = null
-            val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this)
-
-            assertThat(value).isEqualTo(4)
-            job.cancel()
-        }
-
-    @Test
-    fun `maximumUserColumns - many users`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 5)
-            var value: Int? = null
-            val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this)
-
-            assertThat(value).isEqualTo(3)
-            job.cancel()
-        }
-
-    @Test
-    fun `isOpenMenuButtonVisible - has actions - true`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(UserActionModel.values().toList())
-
-            var isVisible: Boolean? = null
-            val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this)
-
-            assertThat(isVisible).isTrue()
-            job.cancel()
-        }
-
-    @Test
-    fun `isOpenMenuButtonVisible - no actions - false`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(emptyList())
-
-            var isVisible: Boolean? = null
-            val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this)
-
-            assertThat(isVisible).isFalse()
-            job.cancel()
-        }
-
-    @Test
-    fun menu() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(UserActionModel.values().toList())
-            var isMenuVisible: Boolean? = null
-            val job = underTest.isMenuVisible.onEach { isMenuVisible = it }.launchIn(this)
-            assertThat(isMenuVisible).isFalse()
-
-            underTest.onOpenMenuButtonClicked()
-            assertThat(isMenuVisible).isTrue()
-
-            underTest.onMenuClosed()
-            assertThat(isMenuVisible).isFalse()
-
-            job.cancel()
-        }
-
-    @Test
-    fun `menu actions`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(UserActionModel.values().toList())
-            var actions: List<UserActionViewModel>? = null
-            val job = underTest.menu.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions?.map { it.viewKey })
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
-                        UserActionModel.ADD_USER.ordinal.toLong(),
-                        UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
-                    )
+    fun `guest not selected -- name is guest`() = testScope.runTest {
+        val userInfos =
+                listOf(
+                        UserInfo(
+                                /* id= */ 0,
+                                /* name= */ "zero",
+                                /* iconPath= */ "",
+                                /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
+                                UserManager.USER_TYPE_FULL_SYSTEM,
+                        ),
+                        UserInfo(
+                                /* id= */ 1,
+                                /* name= */ "one",
+                                /* iconPath= */ "",
+                                /* flags= */ UserInfo.FLAG_FULL,
+                                UserManager.USER_TYPE_FULL_GUEST,
+                        ),
                 )
 
-            job.cancel()
-        }
+        userRepository.setUserInfos(userInfos)
+        userRepository.setSelectedUserInfo(userInfos[0])
+        runCurrent()
 
-    @Test
-    fun `isFinishRequested - finishes when user is switched`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 2)
-            var isFinishRequested: Boolean? = null
-            val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
-            assertThat(isFinishRequested).isFalse()
+        val userViewModels = mutableListOf<List<UserViewModel>>()
+        val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
 
-            userRepository.setSelectedUser(1)
-            yield()
-            assertThat(isFinishRequested).isTrue()
+        assertThat(userViewModels.last()).hasSize(2)
+        assertUserViewModel(
+                viewModel = userViewModels.last()[0],
+                viewKey = 0,
+                name = Text.Loaded("zero"),
+                isSelectionMarkerVisible = true,
+        )
+        assertUserViewModel(
+                viewModel = userViewModels.last()[1],
+                viewKey = 1,
+                name = Text.Loaded("one"),
+                isSelectionMarkerVisible = false,
+        )
+        job.cancel()
+    }
 
-            job.cancel()
-        }
-
-    @Test
-    fun `isFinishRequested - finishes when the screen turns off`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 2)
-            powerRepository.setInteractive(true)
-            var isFinishRequested: Boolean? = null
-            val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
-            assertThat(isFinishRequested).isFalse()
-
-            powerRepository.setInteractive(false)
-            yield()
-            assertThat(isFinishRequested).isTrue()
-
-            job.cancel()
-        }
-
-    @Test
-    fun `isFinishRequested - finishes when cancel button is clicked`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 2)
-            powerRepository.setInteractive(true)
-            var isFinishRequested: Boolean? = null
-            val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
-            assertThat(isFinishRequested).isFalse()
-
-            underTest.onCancelButtonClicked()
-            yield()
-            assertThat(isFinishRequested).isTrue()
-
-            underTest.onFinished()
-            yield()
-            assertThat(isFinishRequested).isFalse()
-
-            job.cancel()
-        }
-
-    private suspend fun setUsers(count: Int) {
-        userRepository.setUsers(
+    private suspend fun setUsers(count: Int): List<UserInfo> {
+        val userInfos =
             (0 until count).map { index ->
-                UserModel(
-                    id = index,
-                    name = Text.Loaded("$index"),
-                    image = USER_IMAGE,
-                    isSelected = index == 0,
-                    isSelectable = true,
-                    isGuest = false,
+                UserInfo(
+                    /* id= */ index,
+                    /* name= */ "$index",
+                    /* iconPath= */ "",
+                    /* flags= */ if (index == 0) {
+                        // This is the primary user.
+                        UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL
+                    } else {
+                        // This isn't the primary user.
+                        UserInfo.FLAG_FULL
+                    },
+                    UserManager.USER_TYPE_FULL_SYSTEM,
                 )
             }
-        )
+        userRepository.setUserInfos(userInfos)
+
+        if (userInfos.isNotEmpty()) {
+            userRepository.setSelectedUserInfo(userInfos[0])
+        }
+        return userInfos
     }
 
     private fun assertUserViewModel(
         viewModel: UserViewModel?,
         viewKey: Int,
-        name: String,
+        name: Text,
         isSelectionMarkerVisible: Boolean,
-        alpha: Float,
-        isClickable: Boolean,
     ) {
         checkNotNull(viewModel)
         assertThat(viewModel.viewKey).isEqualTo(viewKey)
-        assertThat(viewModel.name).isEqualTo(Text.Loaded(name))
+        assertThat(viewModel.name).isEqualTo(name)
         assertThat(viewModel.isSelectionMarkerVisible).isEqualTo(isSelectionMarkerVisible)
-        assertThat(viewModel.alpha).isEqualTo(alpha)
-        assertThat(viewModel.onClicked != null).isEqualTo(isClickable)
+        assertThat(viewModel.alpha)
+            .isEqualTo(LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA)
+        assertThat(viewModel.onClicked).isNotNull()
     }
 
     companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-        private val USER_IMAGE = mock<Drawable>()
+        private const val SUPERVISED_USER_CREATION_PACKAGE = "com.some.package"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt
new file mode 100644
index 0000000..01dd60a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt
@@ -0,0 +1,333 @@
+/*
+ * 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.util
+
+import android.content.DialogInterface
+import android.content.DialogInterface.BUTTON_NEGATIVE
+import android.content.DialogInterface.BUTTON_NEUTRAL
+import android.content.DialogInterface.BUTTON_POSITIVE
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class TestableAlertDialogTest : SysuiTestCase() {
+
+    @Test
+    fun dialogNotShowingWhenCreated() {
+        val dialog = TestableAlertDialog(context)
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+
+    @Test
+    fun dialogShownDoesntCrash() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+    }
+
+    @Test
+    fun dialogShowing() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+
+        assertThat(dialog.isShowing).isTrue()
+    }
+
+    @Test
+    fun showListenerCalled() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnShowListener = mock()
+        dialog.setOnShowListener(listener)
+
+        dialog.show()
+
+        verify(listener).onShow(dialog)
+    }
+
+    @Test
+    fun showListenerRemoved() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnShowListener = mock()
+        dialog.setOnShowListener(listener)
+        dialog.setOnShowListener(null)
+
+        dialog.show()
+
+        verify(listener, never()).onShow(any())
+    }
+
+    @Test
+    fun dialogHiddenNotShowing() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+        dialog.hide()
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+
+    @Test
+    fun dialogDismissNotShowing() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+        dialog.dismiss()
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+
+    @Test
+    fun dismissListenerCalled_ifShowing() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnDismissListener = mock()
+        dialog.setOnDismissListener(listener)
+
+        dialog.show()
+        dialog.dismiss()
+
+        verify(listener).onDismiss(dialog)
+    }
+
+    @Test
+    fun dismissListenerNotCalled_ifNotShowing() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnDismissListener = mock()
+        dialog.setOnDismissListener(listener)
+
+        dialog.dismiss()
+
+        verify(listener, never()).onDismiss(any())
+    }
+
+    @Test
+    fun dismissListenerRemoved() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnDismissListener = mock()
+        dialog.setOnDismissListener(listener)
+        dialog.setOnDismissListener(null)
+
+        dialog.show()
+        dialog.dismiss()
+
+        verify(listener, never()).onDismiss(any())
+    }
+
+    @Test
+    fun cancelListenerCalled_showing() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnCancelListener = mock()
+        dialog.setOnCancelListener(listener)
+
+        dialog.show()
+        dialog.cancel()
+
+        verify(listener).onCancel(dialog)
+    }
+
+    @Test
+    fun cancelListenerCalled_notShowing() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnCancelListener = mock()
+        dialog.setOnCancelListener(listener)
+
+        dialog.cancel()
+
+        verify(listener).onCancel(dialog)
+    }
+
+    @Test
+    fun dismissCalledOnCancel_showing() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnDismissListener = mock()
+        dialog.setOnDismissListener(listener)
+
+        dialog.show()
+        dialog.cancel()
+
+        verify(listener).onDismiss(dialog)
+    }
+
+    @Test
+    fun dialogCancelNotShowing() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+        dialog.cancel()
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+
+    @Test
+    fun cancelListenerRemoved() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnCancelListener = mock()
+        dialog.setOnCancelListener(listener)
+        dialog.setOnCancelListener(null)
+
+        dialog.show()
+        dialog.cancel()
+
+        verify(listener, never()).onCancel(any())
+    }
+
+    @Test
+    fun positiveButtonClick() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnClickListener = mock()
+        dialog.setButton(BUTTON_POSITIVE, "", listener)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_POSITIVE)
+
+        verify(listener).onClick(dialog, BUTTON_POSITIVE)
+    }
+
+    @Test
+    fun positiveButtonListener_noCalledWhenClickOtherButtons() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnClickListener = mock()
+        dialog.setButton(BUTTON_POSITIVE, "", listener)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_NEUTRAL)
+        dialog.clickButton(BUTTON_NEGATIVE)
+
+        verify(listener, never()).onClick(any(), anyInt())
+    }
+
+    @Test
+    fun negativeButtonClick() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnClickListener = mock()
+        dialog.setButton(BUTTON_NEGATIVE, "", listener)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_NEGATIVE)
+
+        verify(listener).onClick(dialog, DialogInterface.BUTTON_NEGATIVE)
+    }
+
+    @Test
+    fun negativeButtonListener_noCalledWhenClickOtherButtons() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnClickListener = mock()
+        dialog.setButton(BUTTON_NEGATIVE, "", listener)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_NEUTRAL)
+        dialog.clickButton(BUTTON_POSITIVE)
+
+        verify(listener, never()).onClick(any(), anyInt())
+    }
+
+    @Test
+    fun neutralButtonClick() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnClickListener = mock()
+        dialog.setButton(BUTTON_NEUTRAL, "", listener)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_NEUTRAL)
+
+        verify(listener).onClick(dialog, BUTTON_NEUTRAL)
+    }
+
+    @Test
+    fun neutralButtonListener_noCalledWhenClickOtherButtons() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnClickListener = mock()
+        dialog.setButton(BUTTON_NEUTRAL, "", listener)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_POSITIVE)
+        dialog.clickButton(BUTTON_NEGATIVE)
+
+        verify(listener, never()).onClick(any(), anyInt())
+    }
+
+    @Test
+    fun sameClickListenerCalledCorrectly() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnClickListener = mock()
+        dialog.setButton(BUTTON_POSITIVE, "", listener)
+        dialog.setButton(BUTTON_NEUTRAL, "", listener)
+        dialog.setButton(BUTTON_NEGATIVE, "", listener)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_POSITIVE)
+        dialog.clickButton(BUTTON_NEGATIVE)
+        dialog.clickButton(BUTTON_NEUTRAL)
+
+        val inOrder = inOrder(listener)
+        inOrder.verify(listener).onClick(dialog, BUTTON_POSITIVE)
+        inOrder.verify(listener).onClick(dialog, BUTTON_NEGATIVE)
+        inOrder.verify(listener).onClick(dialog, BUTTON_NEUTRAL)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun clickBadButton() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.clickButton(10000)
+    }
+
+    @Test
+    fun clickButtonDismisses_positive() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_POSITIVE)
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+
+    @Test
+    fun clickButtonDismisses_negative() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_NEGATIVE)
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+
+    @Test
+    fun clickButtonDismisses_neutral() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_NEUTRAL)
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
index 0b53133..2878864 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
@@ -141,4 +141,158 @@
         mCondition.clearCondition();
         assertThat(mCondition.isConditionSet()).isFalse();
     }
+
+    @Test
+    public void combineConditionsWithOr_allFalse_reportsNotMet() {
+        mCondition.fakeUpdateCondition(false);
+
+        final Condition combinedCondition = mCondition.or(
+                new FakeCondition(/* initialValue= */ false));
+
+        final Condition.Callback callback = mock(Condition.Callback.class);
+        combinedCondition.addCallback(callback);
+
+        assertThat(combinedCondition.isConditionSet()).isTrue();
+        assertThat(combinedCondition.isConditionMet()).isFalse();
+        verify(callback, times(1)).onConditionChanged(combinedCondition);
+    }
+
+    @Test
+    public void combineConditionsWithOr_allTrue_reportsMet() {
+        mCondition.fakeUpdateCondition(true);
+
+        final Condition combinedCondition = mCondition.or(
+                new FakeCondition(/* initialValue= */ true));
+
+        final Condition.Callback callback = mock(Condition.Callback.class);
+        combinedCondition.addCallback(callback);
+
+        assertThat(combinedCondition.isConditionSet()).isTrue();
+        assertThat(combinedCondition.isConditionMet()).isTrue();
+        verify(callback, times(1)).onConditionChanged(combinedCondition);
+    }
+
+    @Test
+    public void combineConditionsWithOr_singleTrue_reportsMet() {
+        mCondition.fakeUpdateCondition(false);
+
+        final Condition combinedCondition = mCondition.or(
+                new FakeCondition(/* initialValue= */ true));
+
+        final Condition.Callback callback = mock(Condition.Callback.class);
+        combinedCondition.addCallback(callback);
+
+        assertThat(combinedCondition.isConditionSet()).isTrue();
+        assertThat(combinedCondition.isConditionMet()).isTrue();
+        verify(callback, times(1)).onConditionChanged(combinedCondition);
+    }
+
+    @Test
+    public void combineConditionsWithOr_unknownAndTrue_reportsMet() {
+        mCondition.fakeUpdateCondition(true);
+
+        // Combine with an unset condition.
+        final Condition combinedCondition = mCondition.or(
+                new FakeCondition(/* initialValue= */ null));
+
+        final Condition.Callback callback = mock(Condition.Callback.class);
+        combinedCondition.addCallback(callback);
+
+        assertThat(combinedCondition.isConditionSet()).isTrue();
+        assertThat(combinedCondition.isConditionMet()).isTrue();
+        verify(callback, times(1)).onConditionChanged(combinedCondition);
+    }
+
+    @Test
+    public void combineConditionsWithOr_unknownAndFalse_reportsNotMet() {
+        mCondition.fakeUpdateCondition(false);
+
+        // Combine with an unset condition.
+        final Condition combinedCondition = mCondition.or(
+                new FakeCondition(/* initialValue= */ null));
+
+        final Condition.Callback callback = mock(Condition.Callback.class);
+        combinedCondition.addCallback(callback);
+
+        assertThat(combinedCondition.isConditionSet()).isFalse();
+        assertThat(combinedCondition.isConditionMet()).isFalse();
+        verify(callback, never()).onConditionChanged(combinedCondition);
+    }
+
+    @Test
+    public void combineConditionsWithAnd_allFalse_reportsNotMet() {
+        mCondition.fakeUpdateCondition(false);
+
+        final Condition combinedCondition = mCondition.and(
+                new FakeCondition(/* initialValue= */ false));
+
+        final Condition.Callback callback = mock(Condition.Callback.class);
+        combinedCondition.addCallback(callback);
+
+        assertThat(combinedCondition.isConditionSet()).isTrue();
+        assertThat(combinedCondition.isConditionMet()).isFalse();
+        verify(callback, times(1)).onConditionChanged(combinedCondition);
+    }
+
+    @Test
+    public void combineConditionsWithAnd_allTrue_reportsMet() {
+        mCondition.fakeUpdateCondition(true);
+
+        final Condition combinedCondition = mCondition.and(
+                new FakeCondition(/* initialValue= */ true));
+
+        final Condition.Callback callback = mock(Condition.Callback.class);
+        combinedCondition.addCallback(callback);
+
+        assertThat(combinedCondition.isConditionSet()).isTrue();
+        assertThat(combinedCondition.isConditionMet()).isTrue();
+        verify(callback, times(1)).onConditionChanged(combinedCondition);
+    }
+
+    @Test
+    public void combineConditionsWithAnd_singleTrue_reportsNotMet() {
+        mCondition.fakeUpdateCondition(true);
+
+        final Condition combinedCondition = mCondition.and(
+                new FakeCondition(/* initialValue= */ false));
+
+        final Condition.Callback callback = mock(Condition.Callback.class);
+        combinedCondition.addCallback(callback);
+
+        assertThat(combinedCondition.isConditionSet()).isTrue();
+        assertThat(combinedCondition.isConditionMet()).isFalse();
+        verify(callback, times(1)).onConditionChanged(combinedCondition);
+    }
+
+    @Test
+    public void combineConditionsWithAnd_unknownAndTrue_reportsNotMet() {
+        mCondition.fakeUpdateCondition(true);
+
+        // Combine with an unset condition.
+        final Condition combinedCondition = mCondition.and(
+                new FakeCondition(/* initialValue= */ null));
+
+        final Condition.Callback callback = mock(Condition.Callback.class);
+        combinedCondition.addCallback(callback);
+
+        assertThat(combinedCondition.isConditionSet()).isFalse();
+        assertThat(combinedCondition.isConditionMet()).isFalse();
+        verify(callback, never()).onConditionChanged(combinedCondition);
+    }
+
+    @Test
+    public void combineConditionsWithAnd_unknownAndFalse_reportsMet() {
+        mCondition.fakeUpdateCondition(false);
+
+        // Combine with an unset condition.
+        final Condition combinedCondition = mCondition.and(
+                new FakeCondition(/* initialValue= */ null));
+
+        final Condition.Callback callback = mock(Condition.Callback.class);
+        combinedCondition.addCallback(callback);
+
+        assertThat(combinedCondition.isConditionSet()).isTrue();
+        assertThat(combinedCondition.isConditionMet()).isFalse();
+        verify(callback, times(1)).onConditionChanged(combinedCondition);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
index 7df7077..6bfc2f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
@@ -51,15 +51,11 @@
             )
     }
 
-    @Test
-    fun notEnough() = runBlocking {
-        assertThatFlow(flowOf(1).pairwise()).emitsNothing()
-    }
+    @Test fun notEnough() = runBlocking { assertThatFlow(flowOf(1).pairwise()).emitsNothing() }
 
     @Test
     fun withInit() = runBlocking {
-        assertThatFlow(flowOf(2).pairwise(initialValue = 1))
-            .emitsExactly(WithPrev(1, 2))
+        assertThatFlow(flowOf(2).pairwise(initialValue = 1)).emitsExactly(WithPrev(1, 2))
     }
 
     @Test
@@ -68,25 +64,78 @@
     }
 
     @Test
-    fun withStateFlow() = runBlocking(Dispatchers.Main.immediate) {
-        val state = MutableStateFlow(1)
-        val stop = MutableSharedFlow<Unit>()
-
-        val stoppable = merge(state, stop)
-            .takeWhile { it is Int }
-            .filterIsInstance<Int>()
-
-        val job1 = launch {
-            assertThatFlow(stoppable.pairwise()).emitsExactly(WithPrev(1, 2))
-        }
-        state.value = 2
-        val job2 = launch { assertThatFlow(stoppable.pairwise()).emitsNothing() }
-
-        stop.emit(Unit)
-
-        assertThatJob(job1).isCompleted()
-        assertThatJob(job2).isCompleted()
+    fun withTransform() = runBlocking {
+        assertThatFlow(
+                flowOf("val1", "val2", "val3").pairwiseBy { prev: String, next: String ->
+                    "$prev|$next"
+                }
+            )
+            .emitsExactly("val1|val2", "val2|val3")
     }
+
+    @Test
+    fun withGetInit() = runBlocking {
+        var initRun = false
+        assertThatFlow(
+                flowOf("val1", "val2").pairwiseBy(
+                    getInitialValue = {
+                        initRun = true
+                        "initial"
+                    }
+                ) { prev: String, next: String -> "$prev|$next" }
+            )
+            .emitsExactly("initial|val1", "val1|val2")
+        assertThat(initRun).isTrue()
+    }
+
+    @Test
+    fun notEnoughWithGetInit() = runBlocking {
+        var initRun = false
+        assertThatFlow(
+                emptyFlow<String>().pairwiseBy(
+                    getInitialValue = {
+                        initRun = true
+                        "initial"
+                    }
+                ) { prev: String, next: String -> "$prev|$next" }
+            )
+            .emitsNothing()
+        // Even though the flow will not emit anything, the initial value function should still get
+        // run.
+        assertThat(initRun).isTrue()
+    }
+
+    @Test
+    fun getInitNotRunWhenFlowNotCollected() = runBlocking {
+        var initRun = false
+        flowOf("val1", "val2").pairwiseBy(
+            getInitialValue = {
+                initRun = true
+                "initial"
+            }
+        ) { prev: String, next: String -> "$prev|$next" }
+
+        // Since the flow isn't collected, ensure [initialValueFun] isn't run.
+        assertThat(initRun).isFalse()
+    }
+
+    @Test
+    fun withStateFlow() =
+        runBlocking(Dispatchers.Main.immediate) {
+            val state = MutableStateFlow(1)
+            val stop = MutableSharedFlow<Unit>()
+
+            val stoppable = merge(state, stop).takeWhile { it is Int }.filterIsInstance<Int>()
+
+            val job1 = launch { assertThatFlow(stoppable.pairwise()).emitsExactly(WithPrev(1, 2)) }
+            state.value = 2
+            val job2 = launch { assertThatFlow(stoppable.pairwise()).emitsNothing() }
+
+            stop.emit(Unit)
+
+            assertThatJob(job1).isCompleted()
+            assertThatJob(job2).isCompleted()
+        }
 }
 
 @SmallTest
@@ -94,18 +143,17 @@
 class SetChangesFlowTest : SysuiTestCase() {
     @Test
     fun simple() = runBlocking {
-        assertThatFlow(
-            flowOf(setOf(1, 2, 3), setOf(2, 3, 4)).setChanges()
-        ).emitsExactly(
-            SetChanges(
-                added = setOf(1, 2, 3),
-                removed = emptySet(),
-            ),
-            SetChanges(
-                added = setOf(4),
-                removed = setOf(1),
-            ),
-        )
+        assertThatFlow(flowOf(setOf(1, 2, 3), setOf(2, 3, 4)).setChanges())
+            .emitsExactly(
+                SetChanges(
+                    added = setOf(1, 2, 3),
+                    removed = emptySet(),
+                ),
+                SetChanges(
+                    added = setOf(4),
+                    removed = setOf(1),
+                ),
+            )
     }
 
     @Test
@@ -147,14 +195,19 @@
 class SampleFlowTest : SysuiTestCase() {
     @Test
     fun simple() = runBlocking {
-        assertThatFlow(flow { yield(); emit(1) }.sample(flowOf(2)) { a, b -> a to b })
+        assertThatFlow(
+                flow {
+                        yield()
+                        emit(1)
+                    }
+                    .sample(flowOf(2)) { a, b -> a to b }
+            )
             .emitsExactly(1 to 2)
     }
 
     @Test
     fun otherFlowNoValueYet() = runBlocking {
-        assertThatFlow(flowOf(1).sample(emptyFlow<Unit>()))
-            .emitsNothing()
+        assertThatFlow(flowOf(1).sample(emptyFlow<Unit>())).emitsNothing()
     }
 
     @Test
@@ -178,13 +231,14 @@
     }
 }
 
-private fun <T> assertThatFlow(flow: Flow<T>) = object {
-    suspend fun emitsExactly(vararg emissions: T) =
-        assertThat(flow.toList()).containsExactly(*emissions).inOrder()
-    suspend fun emitsNothing() =
-        assertThat(flow.toList()).isEmpty()
-}
+private fun <T> assertThatFlow(flow: Flow<T>) =
+    object {
+        suspend fun emitsExactly(vararg emissions: T) =
+            assertThat(flow.toList()).containsExactly(*emissions).inOrder()
+        suspend fun emitsNothing() = assertThat(flow.toList()).isEmpty()
+    }
 
-private fun assertThatJob(job: Job) = object {
-    fun isCompleted() = assertThat(job.isCompleted).isTrue()
-}
+private fun assertThatJob(job: Job) =
+    object {
+        fun isCompleted() = assertThat(job.isCompleted).isTrue()
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
index 0d8dd2c..df08efa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
@@ -28,8 +28,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.FakeThreadFactory;
 import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
index fe01f84..6e109ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
@@ -42,7 +42,9 @@
 
     @Before
     public void setUp() {
-        mInner = WakeLock.createPartialInner(mContext, WakeLockTest.class.getName());
+        mInner = WakeLock.createWakeLockInner(mContext,
+                WakeLockTest.class.getName(),
+                PowerManager.PARTIAL_WAKE_LOCK);
         mWakeLock = WakeLock.wrap(mInner, 20000);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 3769f52..915ea1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -170,6 +170,34 @@
     }
 
     @Test
+    public void testVolumeChangeW_deviceOutFromBLEHeadset_doStateChanged() {
+        mVolumeController.setDeviceInteractive(false);
+        when(mWakefullnessLifcycle.getWakefulness()).thenReturn(
+                WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+        when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn(
+                AudioManager.DEVICE_OUT_BLE_HEADSET);
+
+        mVolumeController.onVolumeChangedW(
+                AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI);
+
+        verify(mCallback, times(1)).onStateChanged(any());
+    }
+
+    @Test
+    public void testVolumeChangeW_deviceOutFromA2DP_doStateChanged() {
+        mVolumeController.setDeviceInteractive(false);
+        when(mWakefullnessLifcycle.getWakefulness()).thenReturn(
+                WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+        when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn(
+                AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+
+        mVolumeController.onVolumeChangedW(
+                AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI);
+
+        verify(mCallback, never()).onStateChanged(any());
+    }
+
+    @Test
     public void testOnRemoteVolumeChanged_newStream_noNullPointer() {
         MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
         mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 2e74bf5..a0b4eab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -28,6 +29,7 @@
 import android.app.KeyguardManager;
 import android.media.AudioManager;
 import android.os.SystemClock;
+import android.provider.DeviceConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.InputDevice;
@@ -38,6 +40,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
@@ -49,6 +52,9 @@
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+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;
@@ -71,6 +77,8 @@
     View mDrawerVibrate;
     View mDrawerMute;
     View mDrawerNormal;
+    private DeviceConfigProxyFake mDeviceConfigProxy;
+    private FakeExecutor mExecutor;
 
     @Mock
     VolumeDialogController mVolumeDialogController;
@@ -97,6 +105,9 @@
 
         getContext().addMockSystemService(KeyguardManager.class, mKeyguard);
 
+        mDeviceConfigProxy = new DeviceConfigProxyFake();
+        mExecutor = new FakeExecutor(new FakeSystemClock());
+
         mDialog = new VolumeDialogImpl(
                 getContext(),
                 mVolumeDialogController,
@@ -106,7 +117,9 @@
                 mMediaOutputDialogFactory,
                 mVolumePanelFactory,
                 mActivityStarter,
-                mInteractionJankMonitor);
+                mInteractionJankMonitor,
+                mDeviceConfigProxy,
+                mExecutor);
         mDialog.init(0, null);
         State state = createShellState();
         mDialog.onStateChangedH(state);
@@ -123,6 +136,9 @@
                 VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1);
 
         Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
+
+        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
     }
 
     private State createShellState() {
@@ -292,6 +308,35 @@
                 AudioManager.RINGER_MODE_NORMAL, false);
     }
 
+    /**
+     * Ideally we would look at the ringer ImageView and check its assigned drawable id, but that
+     * API does not exist. So we do the next best thing; we check the cached icon id.
+     */
+    @Test
+    public void notificationVolumeSeparated_theRingerIconChanges() {
+        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
+
+        mExecutor.runAllReady(); // for the config change to take effect
+
+        // assert icon is new based on res id
+        assertEquals(mDialog.mVolumeRingerIconDrawableId,
+                R.drawable.ic_speaker_on);
+        assertEquals(mDialog.mVolumeRingerMuteIconDrawableId,
+                R.drawable.ic_speaker_mute);
+    }
+
+    @Test
+    public void notificationVolumeNotSeparated_theRingerIconRemainsTheSame() {
+        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
+
+        mExecutor.runAllReady();
+
+        assertEquals(mDialog.mVolumeRingerIconDrawableId, R.drawable.ic_volume_ringer);
+        assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, R.drawable.ic_volume_ringer_mute);
+    }
+
 /*
     @Test
     public void testContentDescriptions() {
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/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index fa7ebf6a..d31f0bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -77,6 +77,7 @@
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor;
@@ -86,6 +87,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -156,6 +158,7 @@
 import java.util.List;
 import java.util.Optional;
 
+@FlakyTest
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -394,6 +397,7 @@
                 mCommonNotifCollection,
                 mNotifPipeline,
                 mSysUiState,
+                mock(FeatureFlags.class),
                 syncExecutor);
         mBubblesManager.addNotifCallback(mNotifCallback);
 
@@ -1327,7 +1331,7 @@
         spyOn(mContext);
         mBubbleController.updateBubble(mBubbleEntry);
         verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
+                mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
         assertThat(mFilterArgumentCaptor.getValue().getAction(0)).isEqualTo(
                 Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         assertThat(mFilterArgumentCaptor.getValue().getAction(1)).isEqualTo(
@@ -1347,7 +1351,7 @@
         mBubbleController.updateBubble(mBubbleEntry);
         mBubbleData.setExpanded(true);
         verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
+                mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
         Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
 
@@ -1361,7 +1365,7 @@
         mBubbleData.setExpanded(true);
 
         verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
+                mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
         Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         i.putExtra("reason", "gestureNav");
         mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
@@ -1375,7 +1379,7 @@
         mBubbleData.setExpanded(true);
 
         verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
+                mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
 
         Intent i = new Intent(Intent.ACTION_SCREEN_OFF);
         mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
@@ -1419,13 +1423,43 @@
         assertStackCollapsed();
         // Post status bar state change update with the same value
         mBubbleController.onStatusBarStateChanged(false);
-        // Stack should remain collapsedb
+        // Stack should remain collapsed
         assertStackCollapsed();
         // Post status bar state change which should trigger bubble to expand
         mBubbleController.onStatusBarStateChanged(true);
         assertStackExpanded();
     }
 
+    /**
+     * Test to verify behavior for the following scenario:
+     * <ol>
+     *     <li>device is locked with keyguard on, status bar shade state updates to
+     *     <code>false</code></li>
+     *     <li>notification entry is marked to be a bubble and it is set to auto-expand</li>
+     *     <li>device unlock starts, status bar shade state receives another update to
+     *     <code>false</code></li>
+     *     <li>device is unlocked and status bar shade state is set to <code>true</code></li>
+     *     <li>bubble should be expanded</li>
+     * </ol>
+     */
+    @Test
+    public void testOnStatusBarStateChanged_newAutoExpandedBubbleRemainsExpanded() {
+        // Set device as locked
+        mBubbleController.onStatusBarStateChanged(false);
+
+        // Create a auto-expanded bubble
+        NotificationEntry entry = mNotificationTestHelper.createAutoExpandedBubble();
+        mEntryListener.onEntryAdded(entry);
+
+        // When unlocking, we may receive duplicate updates with shade=false, ensure they don't
+        // clear the expanded state
+        mBubbleController.onStatusBarStateChanged(false);
+        mBubbleController.onStatusBarStateChanged(true);
+
+        // After unlocking, stack should be expanded
+        assertStackExpanded();
+    }
+
     @Test
     public void testSetShouldAutoExpand_notifiesFlagChanged() {
         mBubbleController.updateBubble(mBubbleEntry);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
new file mode 100644
index 0000000..3767fbe
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+import android.os.Debug;
+import android.util.Log;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Convenience class for grabbing a heap dump after a test class is run.
+ *
+ * To use:
+ * - locally edit your test class to inherit from MemoryTrackingTestCase instead of SysuiTestCase
+ * - Watch the logcat with tag MEMORY to see the path to the .ahprof file
+ * - adb pull /path/to/something.ahprof
+ * - Download ahat from https://sites.google.com/corp/google.com/ahat/home
+ * - java -jar ~/Downloads/ahat-1.7.2.jar something.hprof
+ * - Watch output for next steps
+ * - Profit and fix leaks!
+ */
+public class MemoryTrackingTestCase extends SysuiTestCase {
+    private static File sFilesDir = null;
+    private static String sLatestTestClassName = null;
+
+    @Before public void grabFilesDir() {
+        if (sFilesDir == null) {
+            sFilesDir = mContext.getFilesDir();
+        }
+        sLatestTestClassName = getClass().getName();
+    }
+
+    @AfterClass
+    public static void dumpHeap() throws IOException {
+        if (sFilesDir == null) {
+            Log.e("MEMORY", "Somehow no test cases??");
+            return;
+        }
+        mockitoTearDown();
+        Log.w("MEMORY", "about to dump heap");
+        File path = new File(sFilesDir, sLatestTestClassName + ".ahprof");
+        Debug.dumpHprofData(path.getPath());
+        Log.w("MEMORY", "did it!  Location: " + path);
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index fa3cc99..bf2235a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -136,6 +136,8 @@
                 InstrumentationRegistry.getArguments());
         if (TestableLooper.get(this) != null) {
             TestableLooper.get(this).processAllMessages();
+            // Must remove static reference to this test object to prevent leak (b/261039202)
+            TestableLooper.remove(this);
         }
         disallowTestableLooperAsMainThread();
         mContext.cleanUpReceivers(this.getClass().getSimpleName());
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt
new file mode 100644
index 0000000..8176dd0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.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.biometrics.udfps
+
+import android.graphics.Rect
+
+class FakeOverlapDetector : OverlapDetector {
+    var shouldReturn: Boolean = false
+
+    override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean {
+        return shouldReturn
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index 52e0c98..ad086ff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -22,12 +22,12 @@
 import android.os.Handler
 import android.os.Looper
 import android.os.UserHandle
-import android.util.ArraySet
 import android.util.Log
 import com.android.systemui.SysuiTestableContext
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.settings.UserTracker
+import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.Executor
 
 class FakeBroadcastDispatcher(
@@ -50,7 +50,7 @@
         PendingRemovalStore(logger)
     ) {
 
-    val registeredReceivers = ArraySet<BroadcastReceiver>()
+    val registeredReceivers: MutableSet<BroadcastReceiver> = ConcurrentHashMap.newKeySet()
 
     override fun registerReceiverWithHandler(
         receiver: BroadcastReceiver,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
index 34c83bd..d47e88f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -39,6 +39,7 @@
     private boolean mShouldEnforceBouncer;
     private boolean mIsReportingEnabled;
     private boolean mIsFalseRobustTap;
+    private boolean mIsFalseLongTap;
     private boolean mDestroyed;
     private boolean mIsProximityNear;
 
@@ -87,6 +88,10 @@
         mIsProximityNear = proxNear;
     }
 
+    public void setFalseLongTap(boolean falseLongTap) {
+        mIsFalseLongTap = falseLongTap;
+    }
+
     @Override
     public boolean isSimpleTap() {
         checkDestroyed();
@@ -100,6 +105,12 @@
     }
 
     @Override
+    public boolean isFalseLongTap(int penalty) {
+        checkDestroyed();
+        return mIsFalseLongTap;
+    }
+
+    @Override
     public boolean isFalseDoubleTap() {
         checkDestroyed();
         return mIsFalseDoubleTap;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
new file mode 100644
index 0000000..b7a8d2e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.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.coroutines
+
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+
+/** Collect [flow] in a new [Job] and return a getter for the last collected value. */
+fun <T> TestScope.collectLastValue(
+    flow: Flow<T>,
+    context: CoroutineContext = EmptyCoroutineContext,
+    start: CoroutineStart = CoroutineStart.DEFAULT,
+): () -> T? {
+    var lastValue: T? = null
+    backgroundScope.launch(context, start) { flow.collect { lastValue = it } }
+    return {
+        runCurrent()
+        lastValue
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index a60b773..6c82cef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -21,14 +21,14 @@
 class FakeFeatureFlags : FeatureFlags {
     private val booleanFlags = mutableMapOf<Int, Boolean>()
     private val stringFlags = mutableMapOf<Int, String>()
+    private val intFlags = mutableMapOf<Int, Int>()
     private val knownFlagNames = mutableMapOf<Int, String>()
     private val flagListeners = mutableMapOf<Int, MutableSet<FlagListenable.Listener>>()
     private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>()
 
     init {
-        Flags.flagFields.forEach { field ->
-            val flag: Flag<*> = field.get(null) as Flag<*>
-            knownFlagNames[flag.id] = field.name
+        FlagsFactory.knownFlags.forEach { entry: Map.Entry<String, Flag<*>> ->
+            knownFlagNames[entry.value.id] = entry.key
         }
     }
 
@@ -87,14 +87,16 @@
 
     override fun isEnabled(flag: ResourceBooleanFlag): Boolean = requireBooleanValue(flag.id)
 
-    override fun isEnabled(flag: DeviceConfigBooleanFlag): Boolean = requireBooleanValue(flag.id)
-
     override fun isEnabled(flag: SysPropBooleanFlag): Boolean = requireBooleanValue(flag.id)
 
     override fun getString(flag: StringFlag): String = requireStringValue(flag.id)
 
     override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.id)
 
+    override fun getInt(flag: IntFlag): Int = requireIntValue(flag.id)
+
+    override fun getInt(flag: ResourceIntFlag): Int = requireIntValue(flag.id)
+
     override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {
         flagListeners.getOrPut(flag.id) { mutableSetOf() }.add(listener)
         listenerFlagIds.getOrPut(listener) { mutableSetOf() }.add(flag.id)
@@ -118,11 +120,16 @@
 
     private fun requireBooleanValue(flagId: Int): Boolean {
         return booleanFlags[flagId]
-            ?: error("Flag ${flagName(flagId)} was accessed but not specified.")
+            ?: error("Flag ${flagName(flagId)} was accessed as boolean but not specified.")
     }
 
     private fun requireStringValue(flagId: Int): String {
         return stringFlags[flagId]
-            ?: error("Flag ${flagName(flagId)} was accessed but not specified.")
+            ?: error("Flag ${flagName(flagId)} was accessed as string but not specified.")
+    }
+
+    private fun requireIntValue(flagId: Int): Int {
+        return intFlags[flagId]
+            ?: error("Flag ${flagName(flagId)} was accessed as int but not specified.")
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt
new file mode 100644
index 0000000..d85dd2e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.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.systemui.keyguard.data.quickaffordance
+
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient
+
+class FakeKeyguardQuickAffordanceProviderClientFactory(
+    private val userTracker: UserTracker,
+    private val callback: (Int) -> KeyguardQuickAffordanceProviderClient = {
+        FakeKeyguardQuickAffordanceProviderClient()
+    },
+) : KeyguardQuickAffordanceProviderClientFactory {
+
+    override fun create(): KeyguardQuickAffordanceProviderClient {
+        return callback(userTracker.userId)
+    }
+}
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..5c2a915 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
@@ -17,8 +17,15 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import android.graphics.Point
 import com.android.systemui.common.shared.model.Position
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -42,12 +49,49 @@
     private val _isDozing = MutableStateFlow(false)
     override val isDozing: Flow<Boolean> = _isDozing
 
+    private val _isDreaming = MutableStateFlow(false)
+    override val isDreaming: Flow<Boolean> = _isDreaming
+
     private val _dozeAmount = MutableStateFlow(0f)
-    override val dozeAmount: Flow<Float> = _dozeAmount
+    override val linearDozeAmount: Flow<Float> = _dozeAmount
 
     private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
     override val statusBarState: Flow<StatusBarState> = _statusBarState
 
+    private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel())
+    override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel
+
+    private val _wakefulnessModel =
+        MutableStateFlow(
+            WakefulnessModel(
+                WakefulnessState.ASLEEP,
+                false,
+                WakeSleepReason.OTHER,
+                WakeSleepReason.OTHER
+            )
+        )
+    override val wakefulness: Flow<WakefulnessModel> = _wakefulnessModel
+
+    private val _isUdfpsSupported = MutableStateFlow(false)
+
+    private val _isBouncerShowing = MutableStateFlow(false)
+    override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing
+
+    private val _isKeyguardGoingAway = MutableStateFlow(false)
+    override val isKeyguardGoingAway: Flow<Boolean> = _isKeyguardGoingAway
+
+    private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
+    override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
+
+    private val _fingerprintSensorLocation = MutableStateFlow<Point?>(null)
+    override val fingerprintSensorLocation: Flow<Point?> = _fingerprintSensorLocation
+
+    private val _faceSensorLocation = MutableStateFlow<Point?>(null)
+    override val faceSensorLocation: Flow<Point?> = _faceSensorLocation
+
+    private val _biometricUnlockSource = MutableStateFlow<BiometricUnlockSource?>(null)
+    override val biometricUnlockSource: Flow<BiometricUnlockSource?> = _biometricUnlockSource
+
     override fun isKeyguardShowing(): Boolean {
         return _isKeyguardShowing.value
     }
@@ -75,4 +119,24 @@
     fun setDozeAmount(dozeAmount: Float) {
         _dozeAmount.value = dozeAmount
     }
+
+    fun setBiometricUnlockState(state: BiometricUnlockModel) {
+        _biometricUnlockState.tryEmit(state)
+    }
+
+    fun setBiometricUnlockSource(source: BiometricUnlockSource?) {
+        _biometricUnlockSource.tryEmit(source)
+    }
+
+    fun setFaceSensorLocation(location: Point?) {
+        _faceSensorLocation.tryEmit(location)
+    }
+
+    fun setFingerprintSensorLocation(location: Point?) {
+        _fingerprintSensorLocation.tryEmit(location)
+    }
+
+    override fun isUdfpsSupported(): Boolean {
+        return _isUdfpsSupported.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/keyguard/data/repository/FakeLightRevealScrimRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
new file mode 100644
index 0000000..7c22604
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
@@ -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.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.statusbar.LightRevealEffect
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Fake implementation of [LightRevealScrimRepository] */
+class FakeLightRevealScrimRepository : LightRevealScrimRepository {
+
+    private val _revealEffect: MutableStateFlow<LightRevealEffect> =
+        MutableStateFlow(DEFAULT_REVEAL_EFFECT)
+    override val revealEffect = _revealEffect
+
+    fun setRevealEffect(effect: LightRevealEffect) {
+        _revealEffect.tryEmit(effect)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
index c33ce5d..b31f119 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
@@ -43,6 +43,9 @@
 
     override val showFooterDot: MutableStateFlow<Boolean> = MutableStateFlow(showFooterDot)
 
+    override var includesUserVisibleJobs = false
+        private set
+
     private val numRunningPackagesListeners = LinkedHashSet<OnNumberOfPackagesChangedListener>()
     private val dialogDismissedListeners = LinkedHashSet<OnDialogDismissedListener>()
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 325da4e..1a893f8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -28,8 +28,6 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
@@ -43,7 +41,6 @@
 import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
 import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.security.data.repository.SecurityRepository
 import com.android.systemui.security.data.repository.SecurityRepositoryImpl
 import com.android.systemui.settings.FakeUserTracker
@@ -54,11 +51,13 @@
 import com.android.systemui.statusbar.policy.SecurityController
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.GlobalSettings
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
 
 /**
  * Util class to create real implementations of the FooterActions repositories, viewModel and
@@ -67,6 +66,7 @@
 class FooterActionsTestUtils(
     private val context: Context,
     private val testableLooper: TestableLooper,
+    private val scheduler: TestCoroutineScheduler,
 ) {
     /** Enable or disable the user switcher in the settings. */
     fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) {
@@ -97,28 +97,26 @@
     /** Create a [FooterActionsInteractor] to be used in tests. */
     fun footerActionsInteractor(
         activityStarter: ActivityStarter = mock(),
-        featureFlags: FeatureFlags = FakeFeatureFlags(),
         metricsLogger: MetricsLogger = FakeMetricsLogger(),
         uiEventLogger: UiEventLogger = UiEventLoggerFake(),
         deviceProvisionedController: DeviceProvisionedController = mock(),
         qsSecurityFooterUtils: QSSecurityFooterUtils = mock(),
         fgsManagerController: FgsManagerController = mock(),
-        userSwitchDialogController: UserSwitchDialogController = mock(),
+        userInteractor: UserInteractor = mock(),
         securityRepository: SecurityRepository = securityRepository(),
         foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(),
         userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(),
         broadcastDispatcher: BroadcastDispatcher = mock(),
-        bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+        bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
     ): FooterActionsInteractor {
         return FooterActionsInteractorImpl(
             activityStarter,
-            featureFlags,
             metricsLogger,
             uiEventLogger,
             deviceProvisionedController,
             qsSecurityFooterUtils,
             fgsManagerController,
-            userSwitchDialogController,
+            userInteractor,
             securityRepository,
             foregroundServicesRepository,
             userSwitcherRepository,
@@ -130,7 +128,7 @@
     /** Create a [SecurityRepository] to be used in tests. */
     fun securityRepository(
         securityController: SecurityController = FakeSecurityController(),
-        bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+        bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
     ): SecurityRepository {
         return SecurityRepositoryImpl(
             securityController,
@@ -149,7 +147,7 @@
     fun userSwitcherRepository(
         @Application context: Context = this.context.applicationContext,
         bgHandler: Handler = Handler(testableLooper.looper),
-        bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+        bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
         userManager: UserManager = mock(),
         userTracker: UserTracker = FakeUserTracker(),
         userSwitcherController: UserSwitcherController = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index 9726bf8..0dd1fc7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -66,6 +66,11 @@
         _userId = _userInfo.id
         _userHandle = UserHandle.of(_userId)
 
-        callbacks.forEach { it.onUserChanged(_userId, userContext) }
+        val copy = callbacks.toList()
+        copy.forEach { it.onUserChanged(_userId, userContext) }
+    }
+
+    fun onProfileChanged() {
+        callbacks.forEach { it.onProfilesChanged(_userProfiles) }
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 4df8aa4..ea5a302 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -20,26 +20,15 @@
 import android.content.pm.UserInfo
 import android.os.UserHandle
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.yield
 
 class FakeUserRepository : UserRepository {
 
-    private val _users = MutableStateFlow<List<UserModel>>(emptyList())
-    override val users: Flow<List<UserModel>> = _users.asStateFlow()
-    override val selectedUser: Flow<UserModel> =
-        users.map { models -> models.first { model -> model.isSelected } }
-
-    private val _actions = MutableStateFlow<List<UserActionModel>>(emptyList())
-    override val actions: Flow<List<UserActionModel>> = _actions.asStateFlow()
-
     private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
     override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
         _userSwitcherSettings.asStateFlow()
@@ -52,9 +41,6 @@
 
     override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
 
-    private val _isActionableWhenLocked = MutableStateFlow(false)
-    override val isActionableWhenLocked: Flow<Boolean> = _isActionableWhenLocked.asStateFlow()
-
     private var _isGuestUserAutoCreated: Boolean = false
     override val isGuestUserAutoCreated: Boolean
         get() = _isGuestUserAutoCreated
@@ -63,6 +49,8 @@
 
     override val isGuestUserCreationScheduled = AtomicBoolean()
 
+    override var isStatusBarUserChipEnabled: Boolean = false
+
     override var secondaryUserId: Int = UserHandle.USER_NULL
 
     override var isRefreshUsersPaused: Boolean = false
@@ -100,35 +88,6 @@
         yield()
     }
 
-    fun setUsers(models: List<UserModel>) {
-        _users.value = models
-    }
-
-    suspend fun setSelectedUser(userId: Int) {
-        check(_users.value.find { it.id == userId } != null) {
-            "Cannot select a user with ID $userId - no user with that ID found!"
-        }
-
-        setUsers(
-            _users.value.map { model ->
-                when {
-                    model.isSelected && model.id != userId -> model.copy(isSelected = false)
-                    !model.isSelected && model.id == userId -> model.copy(isSelected = true)
-                    else -> model
-                }
-            }
-        )
-        yield()
-    }
-
-    fun setActions(models: List<UserActionModel>) {
-        _actions.value = models
-    }
-
-    fun setActionableWhenLocked(value: Boolean) {
-        _isActionableWhenLocked.value = value
-    }
-
     fun setGuestUserAutoCreated(value: Boolean) {
         _isGuestUserAutoCreated = value
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
index 33ece00..8a10bf06 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.util;
 
-import android.content.Context;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.OnPropertiesChangedListener;
 import android.provider.DeviceConfig.Properties;
@@ -82,11 +81,6 @@
         properties.get(namespace).put(name, value);
     }
 
-    @Override
-    public void enforceReadPermission(Context context, String namespace) {
-        // no-op
-    }
-
     private Properties propsForNamespaceAndName(String namespace, String name) {
         if (mProperties.containsKey(namespace) && mProperties.get(namespace).containsKey(name)) {
             return new Properties.Builder(namespace)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/TestableAlertDialog.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/TestableAlertDialog.kt
new file mode 100644
index 0000000..4d79554
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/TestableAlertDialog.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.systemui.util
+
+import android.app.AlertDialog
+import android.content.Context
+import android.content.DialogInterface
+import java.lang.IllegalArgumentException
+
+/**
+ * [AlertDialog] that is easier to test. Due to [AlertDialog] being a class and not an interface,
+ * there are some things that cannot be avoided, like the creation of a [Handler] on the main thread
+ * (and therefore needing a prepared [Looper] in the test).
+ *
+ * It bypasses calls to show, clicks on buttons, cancel and dismiss so it all can happen bounded in
+ * the test. It tries to be as close in behavior as a real [AlertDialog].
+ *
+ * It will only call [onCreate] as part of its lifecycle, but not any of the other lifecycle methods
+ * in [Dialog].
+ *
+ * In order to test clicking on buttons, use [clickButton] instead of calling [View.callOnClick] on
+ * the view returned by [getButton] to bypass the internal [Handler].
+ */
+class TestableAlertDialog(context: Context) : AlertDialog(context) {
+
+    private var _onDismissListener: DialogInterface.OnDismissListener? = null
+    private var _onCancelListener: DialogInterface.OnCancelListener? = null
+    private var _positiveButtonClickListener: DialogInterface.OnClickListener? = null
+    private var _negativeButtonClickListener: DialogInterface.OnClickListener? = null
+    private var _neutralButtonClickListener: DialogInterface.OnClickListener? = null
+    private var _onShowListener: DialogInterface.OnShowListener? = null
+    private var _dismissOverride: Runnable? = null
+
+    private var showing = false
+    private var visible = false
+    private var created = false
+
+    override fun show() {
+        if (!created) {
+            created = true
+            onCreate(null)
+        }
+        if (isShowing) return
+        showing = true
+        visible = true
+        _onShowListener?.onShow(this)
+    }
+
+    override fun hide() {
+        visible = false
+    }
+
+    override fun isShowing(): Boolean {
+        return visible && showing
+    }
+
+    override fun dismiss() {
+        if (!showing) {
+            return
+        }
+        if (_dismissOverride != null) {
+            _dismissOverride?.run()
+            return
+        }
+        _onDismissListener?.onDismiss(this)
+        showing = false
+    }
+
+    override fun cancel() {
+        _onCancelListener?.onCancel(this)
+        dismiss()
+    }
+
+    override fun setOnDismissListener(listener: DialogInterface.OnDismissListener?) {
+        _onDismissListener = listener
+    }
+
+    override fun setOnCancelListener(listener: DialogInterface.OnCancelListener?) {
+        _onCancelListener = listener
+    }
+
+    override fun setOnShowListener(listener: DialogInterface.OnShowListener?) {
+        _onShowListener = listener
+    }
+
+    override fun takeCancelAndDismissListeners(
+        msg: String?,
+        cancel: DialogInterface.OnCancelListener?,
+        dismiss: DialogInterface.OnDismissListener?
+    ): Boolean {
+        _onCancelListener = cancel
+        _onDismissListener = dismiss
+        return true
+    }
+
+    override fun setButton(
+        whichButton: Int,
+        text: CharSequence?,
+        listener: DialogInterface.OnClickListener?
+    ) {
+        super.setButton(whichButton, text, listener)
+        when (whichButton) {
+            DialogInterface.BUTTON_POSITIVE -> _positiveButtonClickListener = listener
+            DialogInterface.BUTTON_NEGATIVE -> _negativeButtonClickListener = listener
+            DialogInterface.BUTTON_NEUTRAL -> _neutralButtonClickListener = listener
+            else -> Unit
+        }
+    }
+
+    /**
+     * Click one of the buttons in the [AlertDialog] and call the corresponding listener.
+     *
+     * Button ids are from [DialogInterface].
+     */
+    fun clickButton(whichButton: Int) {
+        val listener =
+            when (whichButton) {
+                DialogInterface.BUTTON_POSITIVE -> _positiveButtonClickListener
+                DialogInterface.BUTTON_NEGATIVE -> _negativeButtonClickListener
+                DialogInterface.BUTTON_NEUTRAL -> _neutralButtonClickListener
+                else -> throw IllegalArgumentException("Wrong button $whichButton")
+            }
+        listener?.onClick(this, whichButton)
+        dismiss()
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
index 1353ad2..07ed110 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
@@ -25,15 +25,21 @@
         super();
     }
 
-    FakeCondition(Boolean initialValue, Boolean overriding) {
+    FakeCondition(Boolean initialValue) {
+        super(initialValue, false);
+    }
+
+    FakeCondition(Boolean initialValue, boolean overriding) {
         super(initialValue, overriding);
     }
 
     @Override
-    public void start() {}
+    public void start() {
+    }
 
     @Override
-    public void stop() {}
+    public void stop() {
+    }
 
     public void fakeUpdateCondition(boolean isConditionMet) {
         updateCondition(isConditionMet);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
index f6fd2cb..f68baf5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
@@ -16,32 +16,71 @@
 
 import android.testing.LeakCheck;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.systemui.statusbar.policy.FlashlightController;
 import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener>
         implements FlashlightController {
+
+    private final List<FlashlightListener> callbacks = new ArrayList<>();
+
+    @VisibleForTesting
+    public boolean isAvailable;
+    @VisibleForTesting
+    public boolean isEnabled;
+    @VisibleForTesting
+    public boolean hasFlashlight;
+
     public FakeFlashlightController(LeakCheck test) {
         super(test, "flashlight");
     }
 
+    @VisibleForTesting
+    public void onFlashlightAvailabilityChanged(boolean newValue) {
+        callbacks.forEach(
+                flashlightListener -> flashlightListener.onFlashlightAvailabilityChanged(newValue)
+        );
+    }
+
+    @VisibleForTesting
+    public void onFlashlightError() {
+        callbacks.forEach(FlashlightListener::onFlashlightError);
+    }
+
     @Override
     public boolean hasFlashlight() {
-        return false;
+        return hasFlashlight;
     }
 
     @Override
     public void setFlashlight(boolean newState) {
-
+        callbacks.forEach(flashlightListener -> flashlightListener.onFlashlightChanged(newState));
     }
 
     @Override
     public boolean isAvailable() {
-        return false;
+        return isAvailable;
     }
 
     @Override
     public boolean isEnabled() {
-        return false;
+        return isEnabled;
+    }
+
+    @Override
+    public void addCallback(FlashlightListener listener) {
+        super.addCallback(listener);
+        callbacks.add(listener);
+    }
+
+    @Override
+    public void removeCallback(FlashlightListener listener) {
+        super.removeCallback(listener);
+        callbacks.remove(listener);
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
index d245c72..63756c6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
@@ -18,7 +18,7 @@
 
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 
 public class FakePluginManager implements PluginManager {
 
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/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
index dc6a8fb..ec1f352 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
@@ -18,7 +18,7 @@
 import android.util.ArrayMap;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
index 5c92b34..06ca153 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
@@ -14,7 +14,6 @@
  */
 package com.android.systemui.unfold.util
 
-import android.animation.ValueAnimator
 import android.content.ContentResolver
 import android.database.ContentObserver
 import android.provider.Settings
@@ -46,13 +45,15 @@
         contentResolver.registerContentObserver(
             Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
             /* notifyForDescendants= */ false,
-            animatorDurationScaleObserver)
+            animatorDurationScaleObserver
+        )
         onAnimatorScaleChanged()
     }
 
     private fun onAnimatorScaleChanged() {
-        val animationsEnabled = ValueAnimator.areAnimatorsEnabled()
-        scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(animationsEnabled)
+        scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(
+            contentResolver.areAnimationsEnabled()
+        )
     }
 
     override fun addCallback(listener: TransitionProgressListener) {
@@ -74,4 +75,18 @@
             progressProvider: UnfoldTransitionProgressProvider
         ): ScaleAwareTransitionProgressProvider
     }
+
+    companion object {
+        fun ContentResolver.areAnimationsEnabled(): Boolean {
+            val animationScale =
+                Settings.Global.getStringForUser(
+                        this,
+                        Settings.Global.ANIMATOR_DURATION_SCALE,
+                        this.userId
+                    )
+                    ?.toFloatOrNull()
+                    ?: 1f
+            return animationScale != 0f
+        }
+    }
 }
diff --git a/packages/VpnDialogs/Android.bp b/packages/VpnDialogs/Android.bp
index 05135b2..e4f80e2 100644
--- a/packages/VpnDialogs/Android.bp
+++ b/packages/VpnDialogs/Android.bp
@@ -23,10 +23,15 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+android_library {
+    name: "VpnDialogsLib",
+    srcs: ["src/**/*.java"],
+}
+
 android_app {
     name: "VpnDialogs",
     certificate: "platform",
     privileged: true,
-    srcs: ["src/**/*.java"],
+    static_libs: ["VpnDialogsLib"],
     platform_apis: true,
 }
diff --git a/packages/VpnDialogs/res/values-af/strings.xml b/packages/VpnDialogs/res/values-af/strings.xml
index 88ccbd9..db3c355 100644
--- a/packages/VpnDialogs/res/values-af/strings.xml
+++ b/packages/VpnDialogs/res/values-af/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Ontkoppel"</string>
     <string name="open_app" msgid="3717639178595958667">"Maak program oop"</string>
     <string name="dismiss" msgid="6192859333764711227">"Maak toe"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g> … ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-am/strings.xml b/packages/VpnDialogs/res/values-am/strings.xml
index 9fc5ff4..d86e043 100644
--- a/packages/VpnDialogs/res/values-am/strings.xml
+++ b/packages/VpnDialogs/res/values-am/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"ግንኙነት አቋርጥ"</string>
     <string name="open_app" msgid="3717639178595958667">"መተግበሪያን ክፈት"</string>
     <string name="dismiss" msgid="6192859333764711227">"አሰናብት"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-ar/strings.xml b/packages/VpnDialogs/res/values-ar/strings.xml
index 33be6a3..9307a74 100644
--- a/packages/VpnDialogs/res/values-ar/strings.xml
+++ b/packages/VpnDialogs/res/values-ar/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"قطع الاتصال"</string>
     <string name="open_app" msgid="3717639178595958667">"فتح التطبيق"</string>
     <string name="dismiss" msgid="6192859333764711227">"تجاهل"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-as/strings.xml b/packages/VpnDialogs/res/values-as/strings.xml
index 3f2e234..736bb83 100644
--- a/packages/VpnDialogs/res/values-as/strings.xml
+++ b/packages/VpnDialogs/res/values-as/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"সংযোগ বিচ্ছিন্ন কৰক"</string>
     <string name="open_app" msgid="3717639178595958667">"এপ্ খোলক"</string>
     <string name="dismiss" msgid="6192859333764711227">"অগ্ৰাহ্য কৰক"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-az/strings.xml b/packages/VpnDialogs/res/values-az/strings.xml
index d878835..9b69e3e5 100644
--- a/packages/VpnDialogs/res/values-az/strings.xml
+++ b/packages/VpnDialogs/res/values-az/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Əlaqəni kəs"</string>
     <string name="open_app" msgid="3717639178595958667">"Tətbiqi açın"</string>
     <string name="dismiss" msgid="6192859333764711227">"İmtina edin"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-b+sr+Latn/strings.xml b/packages/VpnDialogs/res/values-b+sr+Latn/strings.xml
index a1075d2..3270744 100644
--- a/packages/VpnDialogs/res/values-b+sr+Latn/strings.xml
+++ b/packages/VpnDialogs/res/values-b+sr+Latn/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Prekini vezu"</string>
     <string name="open_app" msgid="3717639178595958667">"Otvori aplikaciju"</string>
     <string name="dismiss" msgid="6192859333764711227">"Odbaci"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-be/strings.xml b/packages/VpnDialogs/res/values-be/strings.xml
index fc3f878..54908f6 100644
--- a/packages/VpnDialogs/res/values-be/strings.xml
+++ b/packages/VpnDialogs/res/values-be/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Адключыцца"</string>
     <string name="open_app" msgid="3717639178595958667">"Адкрыць праграму"</string>
     <string name="dismiss" msgid="6192859333764711227">"Адхіліць"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-bg/strings.xml b/packages/VpnDialogs/res/values-bg/strings.xml
index 6345f1d..734888f 100644
--- a/packages/VpnDialogs/res/values-bg/strings.xml
+++ b/packages/VpnDialogs/res/values-bg/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Изключване"</string>
     <string name="open_app" msgid="3717639178595958667">"Към приложението"</string>
     <string name="dismiss" msgid="6192859333764711227">"Отхвърляне"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-bn/strings.xml b/packages/VpnDialogs/res/values-bn/strings.xml
index 352b786..f176b244 100644
--- a/packages/VpnDialogs/res/values-bn/strings.xml
+++ b/packages/VpnDialogs/res/values-bn/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"সংযোগ বিচ্ছিন্ন করুন"</string>
     <string name="open_app" msgid="3717639178595958667">"অ্যাপটি খুলুন"</string>
     <string name="dismiss" msgid="6192859333764711227">"খারিজ করুন"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-bs/strings.xml b/packages/VpnDialogs/res/values-bs/strings.xml
index fa5f4ea..b2f40e2 100644
--- a/packages/VpnDialogs/res/values-bs/strings.xml
+++ b/packages/VpnDialogs/res/values-bs/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Prekini vezu"</string>
     <string name="open_app" msgid="3717639178595958667">"Otvori aplikaciju"</string>
     <string name="dismiss" msgid="6192859333764711227">"Odbaci"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-ca/strings.xml b/packages/VpnDialogs/res/values-ca/strings.xml
index cdb7547..aa64abd 100644
--- a/packages/VpnDialogs/res/values-ca/strings.xml
+++ b/packages/VpnDialogs/res/values-ca/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Desconnecta"</string>
     <string name="open_app" msgid="3717639178595958667">"Obre l\'aplicació"</string>
     <string name="dismiss" msgid="6192859333764711227">"Ignora"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-cs/strings.xml b/packages/VpnDialogs/res/values-cs/strings.xml
index c06f6ff..0658930 100644
--- a/packages/VpnDialogs/res/values-cs/strings.xml
+++ b/packages/VpnDialogs/res/values-cs/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Odpojit"</string>
     <string name="open_app" msgid="3717639178595958667">"Do aplikace"</string>
     <string name="dismiss" msgid="6192859333764711227">"Zavřít"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-da/strings.xml b/packages/VpnDialogs/res/values-da/strings.xml
index a4ddc19..63a32f9 100644
--- a/packages/VpnDialogs/res/values-da/strings.xml
+++ b/packages/VpnDialogs/res/values-da/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Fjern tilknytning"</string>
     <string name="open_app" msgid="3717639178595958667">"Åbn app"</string>
     <string name="dismiss" msgid="6192859333764711227">"Luk"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-de/strings.xml b/packages/VpnDialogs/res/values-de/strings.xml
index f38e395..7397ddb 100644
--- a/packages/VpnDialogs/res/values-de/strings.xml
+++ b/packages/VpnDialogs/res/values-de/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">"Verbindungsanfrage"</string>
-    <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> möchte eine VPN-Verbindung herstellen, über die der Netzwerkverkehr überwacht werden kann. Lass die Verbindung nur zu, wenn die App vertrauenswürdig ist. Wenn VPN aktiv ist, wird oben im Display &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; angezeigt."</string>
+    <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> möchte eine VPN-Verbindung herstellen, über die der Netzwerkverkehr überwacht werden kann. Lass die Verbindung nur zu, wenn die App vertrauenswürdig ist. &lt;br /&gt; &lt;br /&gt; Wenn das VPN aktiv ist, wird oben im Display &lt;img src=vpn_icon /&gt; angezeigt."</string>
     <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> möchte eine VPN-Verbindung herstellen, über die der Netzwerkverkehr überwacht werden kann. Lass die Verbindung nur zu, wenn die App vertrauenswürdig ist. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; wird auf dem Display angezeigt, wenn VPN aktiv ist."</string>
     <string name="legacy_title" msgid="192936250066580964">"VPN ist verbunden"</string>
     <string name="session" msgid="6470628549473641030">"Sitzung:"</string>
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Verbindung trennen"</string>
     <string name="open_app" msgid="3717639178595958667">"App öffnen"</string>
     <string name="dismiss" msgid="6192859333764711227">"Schließen"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-el/strings.xml b/packages/VpnDialogs/res/values-el/strings.xml
index e3eb460..3d14099 100644
--- a/packages/VpnDialogs/res/values-el/strings.xml
+++ b/packages/VpnDialogs/res/values-el/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Αποσύνδεση"</string>
     <string name="open_app" msgid="3717639178595958667">"Άνοιγμα εφαρμογής"</string>
     <string name="dismiss" msgid="6192859333764711227">"Παράβλεψη"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-en-rAU/strings.xml b/packages/VpnDialogs/res/values-en-rAU/strings.xml
index cb8b79d..7855b24 100644
--- a/packages/VpnDialogs/res/values-en-rAU/strings.xml
+++ b/packages/VpnDialogs/res/values-en-rAU/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Disconnect"</string>
     <string name="open_app" msgid="3717639178595958667">"Open app"</string>
     <string name="dismiss" msgid="6192859333764711227">"Dismiss"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-en-rCA/strings.xml b/packages/VpnDialogs/res/values-en-rCA/strings.xml
index cb8b79d..eca5db0 100644
--- a/packages/VpnDialogs/res/values-en-rCA/strings.xml
+++ b/packages/VpnDialogs/res/values-en-rCA/strings.xml
@@ -26,12 +26,14 @@
     <string name="data_received" msgid="4062776929376067820">"Received:"</string>
     <string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> bytes / <xliff:g id="NUMBER_1">%2$s</xliff:g> packets"</string>
     <string name="always_on_disconnected_title" msgid="1906740176262776166">"Can\'t connect to always-on VPN"</string>
-    <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> is set up to stay connected all the time, but it can\'t connect at the moment. Your phone will use a public network until it can reconnect to <xliff:g id="VPN_APP_1">%1$s</xliff:g>."</string>
-    <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> is set up to stay connected all the time, but it can\'t connect at the moment. You won\'t have a connection until the VPN can reconnect."</string>
+    <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> is set up to stay connected all the time, but it can\'t connect right now. Your phone will use a public network until it can reconnect to <xliff:g id="VPN_APP_1">%1$s</xliff:g>."</string>
+    <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> is set up to stay connected all the time, but it can\'t connect right now. You won\'t have a connection until the VPN can reconnect."</string>
     <string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
     <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"Change VPN settings"</string>
     <string name="configure" msgid="4905518375574791375">"Configure"</string>
     <string name="disconnect" msgid="971412338304200056">"Disconnect"</string>
     <string name="open_app" msgid="3717639178595958667">"Open app"</string>
     <string name="dismiss" msgid="6192859333764711227">"Dismiss"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-en-rGB/strings.xml b/packages/VpnDialogs/res/values-en-rGB/strings.xml
index cb8b79d..7855b24 100644
--- a/packages/VpnDialogs/res/values-en-rGB/strings.xml
+++ b/packages/VpnDialogs/res/values-en-rGB/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Disconnect"</string>
     <string name="open_app" msgid="3717639178595958667">"Open app"</string>
     <string name="dismiss" msgid="6192859333764711227">"Dismiss"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-en-rIN/strings.xml b/packages/VpnDialogs/res/values-en-rIN/strings.xml
index cb8b79d..7855b24 100644
--- a/packages/VpnDialogs/res/values-en-rIN/strings.xml
+++ b/packages/VpnDialogs/res/values-en-rIN/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Disconnect"</string>
     <string name="open_app" msgid="3717639178595958667">"Open app"</string>
     <string name="dismiss" msgid="6192859333764711227">"Dismiss"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-en-rXC/strings.xml b/packages/VpnDialogs/res/values-en-rXC/strings.xml
index f5e2deb..06d1421 100644
--- a/packages/VpnDialogs/res/values-en-rXC/strings.xml
+++ b/packages/VpnDialogs/res/values-en-rXC/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‏‏‎‏‎‏‏‎‏‎‏‏‏‏‎‎‎‎Disconnect‎‏‎‎‏‎"</string>
     <string name="open_app" msgid="3717639178595958667">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‎‏‎‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‏‎‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎Open app‎‏‎‎‏‎"</string>
     <string name="dismiss" msgid="6192859333764711227">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‎‏‎‎‏‏‎‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‎‎‎‎‏‏‎‎‏‏‏‎‏‏‎Dismiss‎‏‎‎‏‎"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‎‎‏‎‏‏‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎… ( ‎‏‎‎‏‏‎<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>‎‏‎‎‏‏‏‎)‎‏‎‎‏‎"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‎‏‏‎‏‏‏‏‎‏‎‏‏‎‎‎‎‎‎‏‎‎‏‎‏‏‎‎‏‏‎‏‎‎‏‎‎‏‎‏‏‏‎‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎ ( ‎‏‎‎‏‏‎<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>‎‏‎‎‏‏‏‎)‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-es-rUS/strings.xml b/packages/VpnDialogs/res/values-es-rUS/strings.xml
index 108a24e..3a82cb5 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>
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Desconectar"</string>
     <string name="open_app" msgid="3717639178595958667">"Abrir app"</string>
     <string name="dismiss" msgid="6192859333764711227">"Descartar"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-es/strings.xml b/packages/VpnDialogs/res/values-es/strings.xml
index 9bf86f5..336ac05 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>
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Desconectar"</string>
     <string name="open_app" msgid="3717639178595958667">"Abrir aplicación"</string>
     <string name="dismiss" msgid="6192859333764711227">"Cerrar"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-et/strings.xml b/packages/VpnDialogs/res/values-et/strings.xml
index 140c183..864b9a4 100644
--- a/packages/VpnDialogs/res/values-et/strings.xml
+++ b/packages/VpnDialogs/res/values-et/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Katkesta ühendus"</string>
     <string name="open_app" msgid="3717639178595958667">"Ava rakendus"</string>
     <string name="dismiss" msgid="6192859333764711227">"Loobu"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-eu/strings.xml b/packages/VpnDialogs/res/values-eu/strings.xml
index a27a66a..01d80eb 100644
--- a/packages/VpnDialogs/res/values-eu/strings.xml
+++ b/packages/VpnDialogs/res/values-eu/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Deskonektatu"</string>
     <string name="open_app" msgid="3717639178595958667">"Ireki aplikazioa"</string>
     <string name="dismiss" msgid="6192859333764711227">"Baztertu"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-fa/strings.xml b/packages/VpnDialogs/res/values-fa/strings.xml
index 6fb5a00..47b8735 100644
--- a/packages/VpnDialogs/res/values-fa/strings.xml
+++ b/packages/VpnDialogs/res/values-fa/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"قطع اتصال"</string>
     <string name="open_app" msgid="3717639178595958667">"باز کردن برنامه"</string>
     <string name="dismiss" msgid="6192859333764711227">"رد کردن"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-fi/strings.xml b/packages/VpnDialogs/res/values-fi/strings.xml
index 8abca06..a08d66c 100644
--- a/packages/VpnDialogs/res/values-fi/strings.xml
+++ b/packages/VpnDialogs/res/values-fi/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Katkaise yhteys"</string>
     <string name="open_app" msgid="3717639178595958667">"Avaa sovellus"</string>
     <string name="dismiss" msgid="6192859333764711227">"Hylkää"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-fr-rCA/strings.xml b/packages/VpnDialogs/res/values-fr-rCA/strings.xml
index 876111c..ad450b4 100644
--- a/packages/VpnDialogs/res/values-fr-rCA/strings.xml
+++ b/packages/VpnDialogs/res/values-fr-rCA/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Déconnecter"</string>
     <string name="open_app" msgid="3717639178595958667">"Ouvrir l\'application"</string>
     <string name="dismiss" msgid="6192859333764711227">"Ignorer"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-fr/strings.xml b/packages/VpnDialogs/res/values-fr/strings.xml
index 27ebfb0..cdec614 100644
--- a/packages/VpnDialogs/res/values-fr/strings.xml
+++ b/packages/VpnDialogs/res/values-fr/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Déconnecter"</string>
     <string name="open_app" msgid="3717639178595958667">"Ouvrir l\'application"</string>
     <string name="dismiss" msgid="6192859333764711227">"Ignorer"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-gl/strings.xml b/packages/VpnDialogs/res/values-gl/strings.xml
index 08ab9ae..5595e15 100644
--- a/packages/VpnDialogs/res/values-gl/strings.xml
+++ b/packages/VpnDialogs/res/values-gl/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Desconectar"</string>
     <string name="open_app" msgid="3717639178595958667">"Abrir aplicación"</string>
     <string name="dismiss" msgid="6192859333764711227">"Pechar"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-gu/strings.xml b/packages/VpnDialogs/res/values-gu/strings.xml
index 5ffdcb1..516d51c 100644
--- a/packages/VpnDialogs/res/values-gu/strings.xml
+++ b/packages/VpnDialogs/res/values-gu/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"ડિસ્કનેક્ટ કરો"</string>
     <string name="open_app" msgid="3717639178595958667">"ઍપ ખોલો"</string>
     <string name="dismiss" msgid="6192859333764711227">"છોડી દો"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-hi/strings.xml b/packages/VpnDialogs/res/values-hi/strings.xml
index c9c65d5..ad0cc0b 100644
--- a/packages/VpnDialogs/res/values-hi/strings.xml
+++ b/packages/VpnDialogs/res/values-hi/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"डिसकनेक्ट करें"</string>
     <string name="open_app" msgid="3717639178595958667">"ऐप खोलें"</string>
     <string name="dismiss" msgid="6192859333764711227">"खारिज करें"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-hr/strings.xml b/packages/VpnDialogs/res/values-hr/strings.xml
index 576d997..ec18688 100644
--- a/packages/VpnDialogs/res/values-hr/strings.xml
+++ b/packages/VpnDialogs/res/values-hr/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Prekini vezu"</string>
     <string name="open_app" msgid="3717639178595958667">"Otvori aplikaciju"</string>
     <string name="dismiss" msgid="6192859333764711227">"Odbaci"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-hu/strings.xml b/packages/VpnDialogs/res/values-hu/strings.xml
index 69b999f..0ce41ce 100644
--- a/packages/VpnDialogs/res/values-hu/strings.xml
+++ b/packages/VpnDialogs/res/values-hu/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Kapcsolat bontása"</string>
     <string name="open_app" msgid="3717639178595958667">"Alkalmazás indítása"</string>
     <string name="dismiss" msgid="6192859333764711227">"Bezárás"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-hy/strings.xml b/packages/VpnDialogs/res/values-hy/strings.xml
index d2a6d42..b699902 100644
--- a/packages/VpnDialogs/res/values-hy/strings.xml
+++ b/packages/VpnDialogs/res/values-hy/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Անջատել"</string>
     <string name="open_app" msgid="3717639178595958667">"Բացել հավելվածը"</string>
     <string name="dismiss" msgid="6192859333764711227">"Փակել"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-in/strings.xml b/packages/VpnDialogs/res/values-in/strings.xml
index 88a588c..342f403 100644
--- a/packages/VpnDialogs/res/values-in/strings.xml
+++ b/packages/VpnDialogs/res/values-in/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Putuskan koneksi"</string>
     <string name="open_app" msgid="3717639178595958667">"Buka aplikasi"</string>
     <string name="dismiss" msgid="6192859333764711227">"Tutup"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-is/strings.xml b/packages/VpnDialogs/res/values-is/strings.xml
index a75371d..a52292c 100644
--- a/packages/VpnDialogs/res/values-is/strings.xml
+++ b/packages/VpnDialogs/res/values-is/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Aftengja"</string>
     <string name="open_app" msgid="3717639178595958667">"Opna forrit"</string>
     <string name="dismiss" msgid="6192859333764711227">"Hunsa"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-it/strings.xml b/packages/VpnDialogs/res/values-it/strings.xml
index c443c51..7773c9e 100644
--- a/packages/VpnDialogs/res/values-it/strings.xml
+++ b/packages/VpnDialogs/res/values-it/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">"Richiesta di connessione"</string>
-    <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> vuole impostare una connessione VPN che le consenta di monitorare il traffico di rete. Accetta soltanto se ritieni la fonte attendibile. Quando la connessione VPN è attiva, nella parte superiore dello schermo viene visualizzata l\'icona &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt;."</string>
+    <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> vuole impostare una connessione VPN per monitorare il traffico di rete. Accetta soltanto se ritieni la fonte attendibile. Quando la connessione VPN è attiva, in alto sullo schermo appare l\'icona &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt;."</string>
     <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> vuole configurare una connessione VPN che le consenta di monitorare il traffico di rete. Accetta soltanto se ritieni la fonte attendibile. Quando la connessione VPN è attiva, sullo schermo viene visualizzata l\'icona &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt;."</string>
     <string name="legacy_title" msgid="192936250066580964">"VPN connessa"</string>
     <string name="session" msgid="6470628549473641030">"Sessione:"</string>
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Disconnetti"</string>
     <string name="open_app" msgid="3717639178595958667">"Apri app"</string>
     <string name="dismiss" msgid="6192859333764711227">"Ignora"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-iw/strings.xml b/packages/VpnDialogs/res/values-iw/strings.xml
index 56d8105..3d4e0f0 100644
--- a/packages/VpnDialogs/res/values-iw/strings.xml
+++ b/packages/VpnDialogs/res/values-iw/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"נתק"</string>
     <string name="open_app" msgid="3717639178595958667">"פתיחת האפליקציה"</string>
     <string name="dismiss" msgid="6192859333764711227">"סגירה"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-ja/strings.xml b/packages/VpnDialogs/res/values-ja/strings.xml
index e03e9d3..658569f 100644
--- a/packages/VpnDialogs/res/values-ja/strings.xml
+++ b/packages/VpnDialogs/res/values-ja/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"切断"</string>
     <string name="open_app" msgid="3717639178595958667">"アプリを開く"</string>
     <string name="dismiss" msgid="6192859333764711227">"閉じる"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>…(<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g>(<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-ka/strings.xml b/packages/VpnDialogs/res/values-ka/strings.xml
index 9c4388e..5c4c815 100644
--- a/packages/VpnDialogs/res/values-ka/strings.xml
+++ b/packages/VpnDialogs/res/values-ka/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"კავშირის გაწყვეტა"</string>
     <string name="open_app" msgid="3717639178595958667">"გახსენით აპი"</string>
     <string name="dismiss" msgid="6192859333764711227">"დახურვა"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-kk/strings.xml b/packages/VpnDialogs/res/values-kk/strings.xml
index 9a499d3..a519e4c 100644
--- a/packages/VpnDialogs/res/values-kk/strings.xml
+++ b/packages/VpnDialogs/res/values-kk/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Ажырату"</string>
     <string name="open_app" msgid="3717639178595958667">"Қолданбаны ашу"</string>
     <string name="dismiss" msgid="6192859333764711227">"Жабу"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-km/strings.xml b/packages/VpnDialogs/res/values-km/strings.xml
index de18aba..d93c694 100644
--- a/packages/VpnDialogs/res/values-km/strings.xml
+++ b/packages/VpnDialogs/res/values-km/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"ផ្ដាច់"</string>
     <string name="open_app" msgid="3717639178595958667">"បើកកម្មវិធី"</string>
     <string name="dismiss" msgid="6192859333764711227">"ច្រានចោល"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-kn/strings.xml b/packages/VpnDialogs/res/values-kn/strings.xml
index 6308f18..4f8d90b 100644
--- a/packages/VpnDialogs/res/values-kn/strings.xml
+++ b/packages/VpnDialogs/res/values-kn/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸು"</string>
     <string name="open_app" msgid="3717639178595958667">"ಅಪ್ಲಿಕೇಶನ್ ತೆರೆಯಿರಿ"</string>
     <string name="dismiss" msgid="6192859333764711227">"ವಜಾಗೊಳಿಸಿ"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-ko/strings.xml b/packages/VpnDialogs/res/values-ko/strings.xml
index 6e179bb..ebadad7 100644
--- a/packages/VpnDialogs/res/values-ko/strings.xml
+++ b/packages/VpnDialogs/res/values-ko/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"연결 끊기"</string>
     <string name="open_app" msgid="3717639178595958667">"앱 열기"</string>
     <string name="dismiss" msgid="6192859333764711227">"닫기"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>…(<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g>(<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-ky/strings.xml b/packages/VpnDialogs/res/values-ky/strings.xml
index 31f9e2d..2087b62 100644
--- a/packages/VpnDialogs/res/values-ky/strings.xml
+++ b/packages/VpnDialogs/res/values-ky/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Ажыратуу"</string>
     <string name="open_app" msgid="3717639178595958667">"Колдонмону ачуу"</string>
     <string name="dismiss" msgid="6192859333764711227">"Четке кагуу"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-lo/strings.xml b/packages/VpnDialogs/res/values-lo/strings.xml
index cec69f0..4c36b71 100644
--- a/packages/VpnDialogs/res/values-lo/strings.xml
+++ b/packages/VpnDialogs/res/values-lo/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"ຕັດການເຊື່ອມຕໍ່"</string>
     <string name="open_app" msgid="3717639178595958667">"ເປີດແອັບ"</string>
     <string name="dismiss" msgid="6192859333764711227">"ປິດໄວ້"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-lt/strings.xml b/packages/VpnDialogs/res/values-lt/strings.xml
index 97abd0d..d8783d2 100644
--- a/packages/VpnDialogs/res/values-lt/strings.xml
+++ b/packages/VpnDialogs/res/values-lt/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Atsijungti"</string>
     <string name="open_app" msgid="3717639178595958667">"Atidaryti programą"</string>
     <string name="dismiss" msgid="6192859333764711227">"Atsisakyti"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-lv/strings.xml b/packages/VpnDialogs/res/values-lv/strings.xml
index 6341fbd..7e8ecc1 100644
--- a/packages/VpnDialogs/res/values-lv/strings.xml
+++ b/packages/VpnDialogs/res/values-lv/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Pārtraukt savienojumu"</string>
     <string name="open_app" msgid="3717639178595958667">"Atvērt lietotni"</string>
     <string name="dismiss" msgid="6192859333764711227">"Nerādīt"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-mk/strings.xml b/packages/VpnDialogs/res/values-mk/strings.xml
index 689d028..ec692ab 100644
--- a/packages/VpnDialogs/res/values-mk/strings.xml
+++ b/packages/VpnDialogs/res/values-mk/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Прекини врска"</string>
     <string name="open_app" msgid="3717639178595958667">"Отвори ја апликацијата"</string>
     <string name="dismiss" msgid="6192859333764711227">"Отфрли"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-ml/strings.xml b/packages/VpnDialogs/res/values-ml/strings.xml
index 8284a78..a98bcdc 100644
--- a/packages/VpnDialogs/res/values-ml/strings.xml
+++ b/packages/VpnDialogs/res/values-ml/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"വിച്ഛേദിക്കുക"</string>
     <string name="open_app" msgid="3717639178595958667">"ആപ്പ് തുറക്കുക"</string>
     <string name="dismiss" msgid="6192859333764711227">"നിരസിക്കുക"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-mn/strings.xml b/packages/VpnDialogs/res/values-mn/strings.xml
index 1dd4c15..8eb3289 100644
--- a/packages/VpnDialogs/res/values-mn/strings.xml
+++ b/packages/VpnDialogs/res/values-mn/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Салгах"</string>
     <string name="open_app" msgid="3717639178595958667">"Апп нээх"</string>
     <string name="dismiss" msgid="6192859333764711227">"Хаах"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-mr/strings.xml b/packages/VpnDialogs/res/values-mr/strings.xml
index 22fb502..cccf369 100644
--- a/packages/VpnDialogs/res/values-mr/strings.xml
+++ b/packages/VpnDialogs/res/values-mr/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"‍डिस्कनेक्ट करा"</string>
     <string name="open_app" msgid="3717639178595958667">"अ‍ॅप उघडा"</string>
     <string name="dismiss" msgid="6192859333764711227">"डिसमिस करा"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-ms/strings.xml b/packages/VpnDialogs/res/values-ms/strings.xml
index c9961d2..ad42abb 100644
--- a/packages/VpnDialogs/res/values-ms/strings.xml
+++ b/packages/VpnDialogs/res/values-ms/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Putuskan sambungan"</string>
     <string name="open_app" msgid="3717639178595958667">"Buka apl"</string>
     <string name="dismiss" msgid="6192859333764711227">"Ketepikan"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-my/strings.xml b/packages/VpnDialogs/res/values-my/strings.xml
index 36348c8..bc212a2 100644
--- a/packages/VpnDialogs/res/values-my/strings.xml
+++ b/packages/VpnDialogs/res/values-my/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"ချိတ်ဆက်မှုဖြုတ်ရန်"</string>
     <string name="open_app" msgid="3717639178595958667">"အက်ပ်ကို ဖွင့်ရန်"</string>
     <string name="dismiss" msgid="6192859333764711227">"ပယ်ရန်"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-nb/strings.xml b/packages/VpnDialogs/res/values-nb/strings.xml
index 14c84d7..bca01d0 100644
--- a/packages/VpnDialogs/res/values-nb/strings.xml
+++ b/packages/VpnDialogs/res/values-nb/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Koble fra"</string>
     <string name="open_app" msgid="3717639178595958667">"Åpne appen"</string>
     <string name="dismiss" msgid="6192859333764711227">"Lukk"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-ne/strings.xml b/packages/VpnDialogs/res/values-ne/strings.xml
index 2a5648d..675a76d 100644
--- a/packages/VpnDialogs/res/values-ne/strings.xml
+++ b/packages/VpnDialogs/res/values-ne/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"डिस्कनेक्ट गर्नुहोस्"</string>
     <string name="open_app" msgid="3717639178595958667">"एप खोल्नुहोस्"</string>
     <string name="dismiss" msgid="6192859333764711227">"खारेज गर्नुहोस्"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-nl/strings.xml b/packages/VpnDialogs/res/values-nl/strings.xml
index 33f8a89..80e7f1b 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>
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Verbinding verbreken"</string>
     <string name="open_app" msgid="3717639178595958667">"App openen"</string>
     <string name="dismiss" msgid="6192859333764711227">"Sluiten"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-or/strings.xml b/packages/VpnDialogs/res/values-or/strings.xml
index 4c5c259..2f5a3dd 100644
--- a/packages/VpnDialogs/res/values-or/strings.xml
+++ b/packages/VpnDialogs/res/values-or/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"ବିଚ୍ଛିନ୍ନ କରନ୍ତୁ"</string>
     <string name="open_app" msgid="3717639178595958667">"ଆପ୍‌ ଖୋଲନ୍ତୁ"</string>
     <string name="dismiss" msgid="6192859333764711227">"ଖାରଜ କରନ୍ତୁ"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-pa/strings.xml b/packages/VpnDialogs/res/values-pa/strings.xml
index d2eba0f..427cf86 100644
--- a/packages/VpnDialogs/res/values-pa/strings.xml
+++ b/packages/VpnDialogs/res/values-pa/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
     <string name="open_app" msgid="3717639178595958667">"ਐਪ ਖੋਲ੍ਹੋ"</string>
     <string name="dismiss" msgid="6192859333764711227">"ਖਾਰਜ ਕਰੋ"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-pl/strings.xml b/packages/VpnDialogs/res/values-pl/strings.xml
index 82161d3..1bd89bb 100644
--- a/packages/VpnDialogs/res/values-pl/strings.xml
+++ b/packages/VpnDialogs/res/values-pl/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Rozłącz"</string>
     <string name="open_app" msgid="3717639178595958667">"Otwórz aplikację"</string>
     <string name="dismiss" msgid="6192859333764711227">"Zamknij"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-pt-rBR/strings.xml b/packages/VpnDialogs/res/values-pt-rBR/strings.xml
index 0d6dd0b..53d65af 100644
--- a/packages/VpnDialogs/res/values-pt-rBR/strings.xml
+++ b/packages/VpnDialogs/res/values-pt-rBR/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Desconectar"</string>
     <string name="open_app" msgid="3717639178595958667">"Abrir app"</string>
     <string name="dismiss" msgid="6192859333764711227">"Dispensar"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-pt-rPT/strings.xml b/packages/VpnDialogs/res/values-pt-rPT/strings.xml
index a310104..95f7c1a 100644
--- a/packages/VpnDialogs/res/values-pt-rPT/strings.xml
+++ b/packages/VpnDialogs/res/values-pt-rPT/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Desligar"</string>
     <string name="open_app" msgid="3717639178595958667">"Abrir app"</string>
     <string name="dismiss" msgid="6192859333764711227">"Ignorar"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-pt/strings.xml b/packages/VpnDialogs/res/values-pt/strings.xml
index 0d6dd0b..53d65af 100644
--- a/packages/VpnDialogs/res/values-pt/strings.xml
+++ b/packages/VpnDialogs/res/values-pt/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Desconectar"</string>
     <string name="open_app" msgid="3717639178595958667">"Abrir app"</string>
     <string name="dismiss" msgid="6192859333764711227">"Dispensar"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-ro/strings.xml b/packages/VpnDialogs/res/values-ro/strings.xml
index f86a5d6..f45609b 100644
--- a/packages/VpnDialogs/res/values-ro/strings.xml
+++ b/packages/VpnDialogs/res/values-ro/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Deconectează"</string>
     <string name="open_app" msgid="3717639178595958667">"Deschide aplicația"</string>
     <string name="dismiss" msgid="6192859333764711227">"Închide"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-ru/strings.xml b/packages/VpnDialogs/res/values-ru/strings.xml
index ce099562..2e346d3 100644
--- a/packages/VpnDialogs/res/values-ru/strings.xml
+++ b/packages/VpnDialogs/res/values-ru/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Разъединить"</string>
     <string name="open_app" msgid="3717639178595958667">"Открыть приложение"</string>
     <string name="dismiss" msgid="6192859333764711227">"Закрыть"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-si/strings.xml b/packages/VpnDialogs/res/values-si/strings.xml
index a836bae..fa5a70f 100644
--- a/packages/VpnDialogs/res/values-si/strings.xml
+++ b/packages/VpnDialogs/res/values-si/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"විසන්ධි කරන්න"</string>
     <string name="open_app" msgid="3717639178595958667">"යෙදුම විවෘත කරන්න"</string>
     <string name="dismiss" msgid="6192859333764711227">"ඉවතලන්න"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-sk/strings.xml b/packages/VpnDialogs/res/values-sk/strings.xml
index 766c139..755abb2 100644
--- a/packages/VpnDialogs/res/values-sk/strings.xml
+++ b/packages/VpnDialogs/res/values-sk/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Odpojiť"</string>
     <string name="open_app" msgid="3717639178595958667">"Otvoriť aplikáciu"</string>
     <string name="dismiss" msgid="6192859333764711227">"Zavrieť"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-sl/strings.xml b/packages/VpnDialogs/res/values-sl/strings.xml
index 361a5fa..b473ce0 100644
--- a/packages/VpnDialogs/res/values-sl/strings.xml
+++ b/packages/VpnDialogs/res/values-sl/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Prekini povezavo"</string>
     <string name="open_app" msgid="3717639178595958667">"Odpri aplikacijo"</string>
     <string name="dismiss" msgid="6192859333764711227">"Opusti"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g> … (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-sq/strings.xml b/packages/VpnDialogs/res/values-sq/strings.xml
index eb73baa..ad9f66e 100644
--- a/packages/VpnDialogs/res/values-sq/strings.xml
+++ b/packages/VpnDialogs/res/values-sq/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Shkëputu"</string>
     <string name="open_app" msgid="3717639178595958667">"Hap aplikacionin"</string>
     <string name="dismiss" msgid="6192859333764711227">"Hiq"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-sr/strings.xml b/packages/VpnDialogs/res/values-sr/strings.xml
index 01bd4df..eaa0aef 100644
--- a/packages/VpnDialogs/res/values-sr/strings.xml
+++ b/packages/VpnDialogs/res/values-sr/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Прекини везу"</string>
     <string name="open_app" msgid="3717639178595958667">"Отвори апликацију"</string>
     <string name="dismiss" msgid="6192859333764711227">"Одбаци"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-sv/strings.xml b/packages/VpnDialogs/res/values-sv/strings.xml
index 60ed752..175ebba 100644
--- a/packages/VpnDialogs/res/values-sv/strings.xml
+++ b/packages/VpnDialogs/res/values-sv/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Koppla från"</string>
     <string name="open_app" msgid="3717639178595958667">"Öppna appen"</string>
     <string name="dismiss" msgid="6192859333764711227">"Ignorera"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-sw/strings.xml b/packages/VpnDialogs/res/values-sw/strings.xml
index c4f4662..66c2899 100644
--- a/packages/VpnDialogs/res/values-sw/strings.xml
+++ b/packages/VpnDialogs/res/values-sw/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Tenganisha"</string>
     <string name="open_app" msgid="3717639178595958667">"Fungua programu"</string>
     <string name="dismiss" msgid="6192859333764711227">"Ondoa"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-ta/strings.xml b/packages/VpnDialogs/res/values-ta/strings.xml
index 1385bdc..31602a6 100644
--- a/packages/VpnDialogs/res/values-ta/strings.xml
+++ b/packages/VpnDialogs/res/values-ta/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"தொடர்பைத் துண்டி"</string>
     <string name="open_app" msgid="3717639178595958667">"பயன்பாட்டைத் திற"</string>
     <string name="dismiss" msgid="6192859333764711227">"நிராகரி"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-te/strings.xml b/packages/VpnDialogs/res/values-te/strings.xml
index 7884336..685dd26 100644
--- a/packages/VpnDialogs/res/values-te/strings.xml
+++ b/packages/VpnDialogs/res/values-te/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"డిస్‌కనెక్ట్ చేయి"</string>
     <string name="open_app" msgid="3717639178595958667">"యాప్‌ని తెరవండి"</string>
     <string name="dismiss" msgid="6192859333764711227">"తీసివేయండి"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-th/strings.xml b/packages/VpnDialogs/res/values-th/strings.xml
index 2e174cd..3e15d4b 100644
--- a/packages/VpnDialogs/res/values-th/strings.xml
+++ b/packages/VpnDialogs/res/values-th/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"ยกเลิกการเชื่อมต่อ"</string>
     <string name="open_app" msgid="3717639178595958667">"เปิดแอป"</string>
     <string name="dismiss" msgid="6192859333764711227">"ปิด"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-tl/strings.xml b/packages/VpnDialogs/res/values-tl/strings.xml
index ea69fba..0b4a106 100644
--- a/packages/VpnDialogs/res/values-tl/strings.xml
+++ b/packages/VpnDialogs/res/values-tl/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Idiskonekta"</string>
     <string name="open_app" msgid="3717639178595958667">"Buksan ang app"</string>
     <string name="dismiss" msgid="6192859333764711227">"I-dismiss"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-tr/strings.xml b/packages/VpnDialogs/res/values-tr/strings.xml
index 7ffa4bc..e3606ef 100644
--- a/packages/VpnDialogs/res/values-tr/strings.xml
+++ b/packages/VpnDialogs/res/values-tr/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Bağlantıyı kes"</string>
     <string name="open_app" msgid="3717639178595958667">"Uygulamayı aç"</string>
     <string name="dismiss" msgid="6192859333764711227">"Kapat"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-uk/strings.xml b/packages/VpnDialogs/res/values-uk/strings.xml
index 6411d7c..1dd0e8a 100644
--- a/packages/VpnDialogs/res/values-uk/strings.xml
+++ b/packages/VpnDialogs/res/values-uk/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Від’єднати"</string>
     <string name="open_app" msgid="3717639178595958667">"Відкрити додаток"</string>
     <string name="dismiss" msgid="6192859333764711227">"Закрити"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-ur/strings.xml b/packages/VpnDialogs/res/values-ur/strings.xml
index 3a23e94..803f042 100644
--- a/packages/VpnDialogs/res/values-ur/strings.xml
+++ b/packages/VpnDialogs/res/values-ur/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"منقطع کریں"</string>
     <string name="open_app" msgid="3717639178595958667">"ایپ کھولیں"</string>
     <string name="dismiss" msgid="6192859333764711227">"برخاست کریں"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-uz/strings.xml b/packages/VpnDialogs/res/values-uz/strings.xml
index a3256e7..a54fa08 100644
--- a/packages/VpnDialogs/res/values-uz/strings.xml
+++ b/packages/VpnDialogs/res/values-uz/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Aloqani uzish"</string>
     <string name="open_app" msgid="3717639178595958667">"Ilovani ochish"</string>
     <string name="dismiss" msgid="6192859333764711227">"Yopish"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-vi/strings.xml b/packages/VpnDialogs/res/values-vi/strings.xml
index 184d08d..6ce9f39 100644
--- a/packages/VpnDialogs/res/values-vi/strings.xml
+++ b/packages/VpnDialogs/res/values-vi/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Ngắt kết nối"</string>
     <string name="open_app" msgid="3717639178595958667">"Mở ứng dụng"</string>
     <string name="dismiss" msgid="6192859333764711227">"Loại bỏ"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-zh-rCN/strings.xml b/packages/VpnDialogs/res/values-zh-rCN/strings.xml
index a7262be..38a2e8d 100644
--- a/packages/VpnDialogs/res/values-zh-rCN/strings.xml
+++ b/packages/VpnDialogs/res/values-zh-rCN/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"断开连接"</string>
     <string name="open_app" msgid="3717639178595958667">"打开应用"</string>
     <string name="dismiss" msgid="6192859333764711227">"关闭"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>…(<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-zh-rHK/strings.xml b/packages/VpnDialogs/res/values-zh-rHK/strings.xml
index e4e6432..f3abf3c 100644
--- a/packages/VpnDialogs/res/values-zh-rHK/strings.xml
+++ b/packages/VpnDialogs/res/values-zh-rHK/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"中斷連線"</string>
     <string name="open_app" msgid="3717639178595958667">"開啟應用程式"</string>
     <string name="dismiss" msgid="6192859333764711227">"關閉"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-zh-rTW/strings.xml b/packages/VpnDialogs/res/values-zh-rTW/strings.xml
index f54ca4a..3f1336b 100644
--- a/packages/VpnDialogs/res/values-zh-rTW/strings.xml
+++ b/packages/VpnDialogs/res/values-zh-rTW/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"中斷連線"</string>
     <string name="open_app" msgid="3717639178595958667">"開啟應用程式"</string>
     <string name="dismiss" msgid="6192859333764711227">"關閉"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… (<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-zu/strings.xml b/packages/VpnDialogs/res/values-zu/strings.xml
index c224b13..563ed0f 100644
--- a/packages/VpnDialogs/res/values-zu/strings.xml
+++ b/packages/VpnDialogs/res/values-zu/strings.xml
@@ -34,4 +34,6 @@
     <string name="disconnect" msgid="971412338304200056">"Ayixhumekile kwi-inthanethi"</string>
     <string name="open_app" msgid="3717639178595958667">"Vula uhlelo lokusebenza"</string>
     <string name="dismiss" msgid="6192859333764711227">"Cashisa"</string>
+    <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
+    <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml
index f971a09..28e7272 100644
--- a/packages/VpnDialogs/res/values/strings.xml
+++ b/packages/VpnDialogs/res/values/strings.xml
@@ -100,4 +100,33 @@
          without any consequences. [CHAR LIMIT=20] -->
     <string name="dismiss">Dismiss</string>
 
+    <!-- Malicious VPN apps may provide very long labels or cunning HTML to trick the system dialogs
+         into displaying what they want. The system will attempt to sanitize the label, and if the
+         label is deemed dangerous, then this string is used instead. The first argument is the
+         first 30 characters of the label, and the second argument is the package name of the app.
+         Example : Normally a VPN app may be called "My VPN app" in which case the dialog will read
+         "My VPN app wants to set up a VPN connection...". If the label is very long, then, this
+         will be used to show "VerylongVPNlabel… (com.my.vpn.app) wants to set up a VPN
+         connection...". For this case, the code will refer to sanitized_vpn_label_with_ellipsis.
+    -->
+    <string name="sanitized_vpn_label_with_ellipsis">
+        <xliff:g id="sanitized_vpn_label_with_ellipsis" example="My VPN app">%1$s</xliff:g>… (
+        <xliff:g id="sanitized_vpn_label_with_ellipsis" example="com.my.vpn.app">%2$s</xliff:g>)
+    </string>
+
+    <!-- Malicious VPN apps may provide very long labels or cunning HTML to trick the system dialogs
+         into displaying what they want. The system will attempt to sanitize the label, and if the
+         label is deemed dangerous, then this string is used instead. The first argument is the
+         label, and the second argument is the package name of the app.
+         Example : Normally a VPN app may be called "My VPN app" in which case the dialog will read
+         "My VPN app wants to set up a VPN connection...". If the VPN label contains HTML tag but
+         the length is not very long, the dialog will show "VpnLabelWith&lt;br&gt;HtmlTag
+         (com.my.vpn.app) wants to set up a VPN connection...". For this case, the code will refer
+         to sanitized_vpn_label.
+    -->
+    <string name="sanitized_vpn_label">
+        <xliff:g id="sanitized_vpn_label" example="My VPN app">%1$s</xliff:g> (
+        <xliff:g id="sanitized_vpn_label" example="com.my.vpn.app">%2$s</xliff:g>)
+    </string>
+
 </resources>
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
index fb23678..a98d6d8 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
@@ -33,6 +33,7 @@
 import android.widget.Button;
 import android.widget.TextView;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.AlertActivity;
 import com.android.internal.net.VpnConfig;
 
@@ -40,12 +41,19 @@
         implements DialogInterface.OnClickListener, ImageGetter {
     private static final String TAG = "VpnConfirm";
 
+    // Usually the label represents the app name, 150 code points might be enough to display the app
+    // name, and 150 code points won't cover the warning message from VpnDialog.
+    @VisibleForTesting
+    static final int MAX_VPN_LABEL_LENGTH = 150;
+
     @VpnManager.VpnType private final int mVpnType;
 
     private String mPackage;
 
     private VpnManager mVm;
 
+    private View mView;
+
     public ConfirmDialog() {
         this(VpnManager.TYPE_VPN_SERVICE);
     }
@@ -54,6 +62,43 @@
         mVpnType = vpnType;
     }
 
+    /**
+     * This function will use the string resource to combine the VPN label and the package name.
+     *
+     * If the VPN label violates the length restriction, the first 30 code points of VPN label and
+     * the package name will be returned. Or return the VPN label and the package name directly if
+     * the VPN label doesn't violate the length restriction.
+     *
+     * The result will be something like,
+     * - ThisIsAVeryLongVpnAppNameWhich... (com.vpn.app)
+     *   if the VPN label violates the length restriction.
+     * or
+     * - VpnLabelWith&lt;br&gt;HtmlTag (com.vpn.app)
+     *   if the VPN label doesn't violate the length restriction.
+     *
+     */
+    private String getSimplifiedLabel(String vpnLabel, String packageName) {
+        if (vpnLabel.codePointCount(0, vpnLabel.length()) > 30) {
+            return getString(R.string.sanitized_vpn_label_with_ellipsis,
+                    vpnLabel.substring(0, vpnLabel.offsetByCodePoints(0, 30)),
+                            packageName);
+        }
+
+        return getString(R.string.sanitized_vpn_label, vpnLabel, packageName);
+    }
+
+    @VisibleForTesting
+    protected String getSanitizedVpnLabel(String vpnLabel, String packageName) {
+        final String sanitizedVpnLabel = Html.escapeHtml(vpnLabel);
+        final boolean exceedMaxVpnLabelLength = sanitizedVpnLabel.codePointCount(0,
+                sanitizedVpnLabel.length()) > MAX_VPN_LABEL_LENGTH;
+        if (exceedMaxVpnLabelLength || !vpnLabel.equals(sanitizedVpnLabel)) {
+            return getSimplifiedLabel(sanitizedVpnLabel, packageName);
+        }
+
+        return sanitizedVpnLabel;
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -75,15 +120,16 @@
             finish();
             return;
         }
-        View view = View.inflate(this, R.layout.confirm, null);
-        ((TextView) view.findViewById(R.id.warning)).setText(
-                Html.fromHtml(getString(R.string.warning, getVpnLabel()),
-                        this, null /* tagHandler */));
+        mView = View.inflate(this, R.layout.confirm, null);
+        ((TextView) mView.findViewById(R.id.warning)).setText(
+                Html.fromHtml(getString(R.string.warning, getSanitizedVpnLabel(
+                        getVpnLabel().toString(), mPackage)),
+                        this /* imageGetter */, null /* tagHandler */));
         mAlertParams.mTitle = getText(R.string.prompt);
         mAlertParams.mPositiveButtonText = getText(android.R.string.ok);
         mAlertParams.mPositiveButtonListener = this;
         mAlertParams.mNegativeButtonText = getText(android.R.string.cancel);
-        mAlertParams.mView = view;
+        mAlertParams.mView = mView;
         setupAlert();
 
         getWindow().setCloseOnTouchOutside(false);
@@ -92,6 +138,11 @@
         button.setFilterTouchesWhenObscured(true);
     }
 
+    @VisibleForTesting
+    public CharSequence getWarningText() {
+        return ((TextView) mView.findViewById(R.id.warning)).getText();
+    }
+
     private CharSequence getVpnLabel() {
         try {
             return VpnConfig.getVpnLabel(this, mPackage);
diff --git a/packages/VpnDialogs/tests/Android.bp b/packages/VpnDialogs/tests/Android.bp
new file mode 100644
index 0000000..68639bd
--- /dev/null
+++ b/packages/VpnDialogs/tests/Android.bp
@@ -0,0 +1,36 @@
+// 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "VpnDialogsTests",
+    // Use platform certificate because the test will invoke a hidden API.
+    // (e.g. VpnManager#prepareVpn()).
+    certificate: "platform",
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "mockito-target-minus-junit4",
+        "VpnDialogsLib",
+    ],
+    srcs: ["src/**/*.java"],
+}
diff --git a/packages/VpnDialogs/tests/AndroidManifest.xml b/packages/VpnDialogs/tests/AndroidManifest.xml
new file mode 100644
index 0000000..f26c1fe
--- /dev/null
+++ b/packages/VpnDialogs/tests/AndroidManifest.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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.android.vpndialogs.tests">
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="com.android.vpndialogs.VpnDialogTest$InstrumentedConfirmDialog"/>
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.vpndialogs.tests"
+                     android:label="Vpn dialog tests">
+    </instrumentation>
+</manifest>
diff --git a/packages/VpnDialogs/tests/src/com/android/vpndialogs/VpnDialogTest.java b/packages/VpnDialogs/tests/src/com/android/vpndialogs/VpnDialogTest.java
new file mode 100644
index 0000000..7cfa466
--- /dev/null
+++ b/packages/VpnDialogs/tests/src/com/android/vpndialogs/VpnDialogTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.vpndialogs;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.VpnManager;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class VpnDialogTest {
+    private ActivityScenario<ConfirmDialog> mActivityScenario;
+
+    @SuppressWarnings("StaticMockMember")
+    @Mock
+    private static PackageManager sPm;
+
+    @SuppressWarnings("StaticMockMember")
+    @Mock
+    private static VpnManager sVm;
+
+    @Mock
+    private ApplicationInfo mAi;
+
+    private static final String VPN_APP_NAME = "VpnApp";
+    private static final String VPN_APP_PACKAGE_NAME = "com.android.vpndialogs.VpnDialogTest";
+    private static final String VPN_LABEL_CONTAINS_HTML_TAG =
+            "<b><a href=\"https://www.malicious.vpn.app.com\">Google Play</a>";
+    private static final String VPN_LABEL_CONTAINS_HTML_TAG_AND_VIOLATE_LENGTH_RESTRICTION =
+            "<b><a href=\"https://www.malicious.vpn.app.com\">Google Play</a></b>"
+            + " Wants to connect the network. <br></br><br></br><br></br><br></br><br></br>"
+            + " <br></br><br></br><br></br><br></br><br></br><br></br><br></br><br></br> Deny it?";
+    private static final String VPN_LABEL_VIOLATES_LENGTH_RESTRICTION = "This is a VPN label"
+            + " which violates the length restriction. The length restriction here are 150 code"
+            + " points. So the VPN label should be sanitized, and shows the package name to the"
+            + " user.";
+
+    public static class InstrumentedConfirmDialog extends ConfirmDialog {
+        @Override
+        public PackageManager getPackageManager() {
+            return sPm;
+        }
+
+        @Override
+        public @Nullable Object getSystemService(@ServiceName @NonNull String name) {
+            switch (name) {
+                case Context.VPN_MANAGEMENT_SERVICE:
+                    return sVm;
+                default:
+                    return super.getSystemService(name);
+            }
+        }
+
+        @Override
+        public String getCallingPackage() {
+            return VPN_APP_PACKAGE_NAME;
+        }
+    }
+
+    private void launchActivity() {
+        final Context context = getInstrumentation().getContext();
+        mActivityScenario = ActivityScenario.launch(
+                new Intent(context, InstrumentedConfirmDialog.class));
+    }
+
+    @Test
+    public void testGetSanitizedVpnLabel_withNormalCase() throws Exception {
+        // Test the normal case that the VPN label showed in the VpnDialog is the app name.
+        doReturn(VPN_APP_NAME).when(mAi).loadLabel(sPm);
+        launchActivity();
+        mActivityScenario.onActivity(activity -> {
+            assertTrue(activity.getWarningText().toString().contains(VPN_APP_NAME));
+        });
+    }
+
+    private void verifySanitizedVpnLabel(String originalLabel) {
+        doReturn(originalLabel).when(mAi).loadLabel(sPm);
+        launchActivity();
+        mActivityScenario.onActivity(activity -> {
+            // The VPN label was sanitized because violating length restriction or having a html
+            // tag, so the warning message will contain the package name.
+            assertTrue(activity.getWarningText().toString().contains(activity.getCallingPackage()));
+            // Also, the length of sanitized VPN label shouldn't longer than MAX_VPN_LABEL_LENGTH
+            // and it shouldn't contain html tag.
+            final String sanitizedVpnLabel =
+                    activity.getSanitizedVpnLabel(originalLabel, VPN_APP_PACKAGE_NAME);
+            assertTrue(sanitizedVpnLabel.codePointCount(0, sanitizedVpnLabel.length())
+                    < ConfirmDialog.MAX_VPN_LABEL_LENGTH);
+            assertFalse(sanitizedVpnLabel.contains("<b>"));
+        });
+    }
+
+    @Test
+    public void testGetSanitizedVpnLabel_withHtmlTag() throws Exception {
+        // Test the case that the VPN label was sanitized because there is a html tag.
+        verifySanitizedVpnLabel(VPN_LABEL_CONTAINS_HTML_TAG);
+    }
+
+    @Test
+    public void testGetSanitizedVpnLabel_withHtmlTagAndViolateLengthRestriction() throws Exception {
+        // Test the case that the VPN label was sanitized because there is a html tag.
+        verifySanitizedVpnLabel(VPN_LABEL_CONTAINS_HTML_TAG_AND_VIOLATE_LENGTH_RESTRICTION);
+    }
+
+    @Test
+    public void testGetSanitizedVpnLabel_withLengthRestriction() throws Exception {
+        // Test the case that the VPN label was sanitized because hitting the length restriction.
+        verifySanitizedVpnLabel(VPN_LABEL_VIOLATES_LENGTH_RESTRICTION);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doReturn(false).when(sVm).prepareVpn(anyString(), anyString(), anyInt());
+        doReturn(null).when(sPm).queryIntentServices(any(), anyInt());
+        doReturn(mAi).when(sPm).getApplicationInfo(anyString(), anyInt());
+    }
+}
diff --git a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml
index 9db960f..e7ec332 100644
--- a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="display_cutout_emulation_overlay" msgid="7305489596221077240">"Punch hole cutout"</string>
+    <string name="display_cutout_emulation_overlay" msgid="7305489596221077240">"Punch Hole cutout"</string>
 </resources>
diff --git a/packages/overlays/NavigationBarMode3ButtonOverlay/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarMode3ButtonOverlay/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..8e466e0
--- /dev/null
+++ b/packages/overlays/NavigationBarMode3ButtonOverlay/res/values-sw600dp/config.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.
+ */
+-->
+<resources>
+    <!-- Controls whether seamless rotation should be allowed even though the navbar can move
+         (which normally prevents seamless rotation). Allow seamless rotation because the bar
+         is relatively small in large screen and its appearance is similar to gestural mode
+         even if it jumps to another side for display orientation change. -->
+    <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
+</resources>
+
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 9897a07..2ad2119 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -104,6 +104,7 @@
 import androidx.camera.extensions.impl.advanced.ImageReaderOutputConfigImpl;
 import androidx.camera.extensions.impl.advanced.MultiResolutionImageReaderOutputConfigImpl;
 import androidx.camera.extensions.impl.advanced.NightAdvancedExtenderImpl;
+import androidx.camera.extensions.impl.advanced.OutputSurfaceConfigurationImpl;
 import androidx.camera.extensions.impl.advanced.OutputSurfaceImpl;
 import androidx.camera.extensions.impl.advanced.RequestProcessorImpl;
 import androidx.camera.extensions.impl.advanced.SessionProcessorImpl;
@@ -829,6 +830,15 @@
 
             return null;
         }
+
+        @Override
+        public boolean isCaptureProcessProgressAvailable() {
+            if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+                return mAdvancedExtender.isCaptureProcessProgressAvailable();
+            }
+
+            return false;
+        }
     }
 
     private class CaptureCallbackStub implements SessionProcessorImpl.CaptureCallback {
@@ -922,6 +932,15 @@
                 Log.e(TAG, "Failed to notify capture complete due to remote exception!");
             }
         }
+
+        @Override
+        public void onCaptureProcessProgressed(int progress) {
+            try {
+                mCaptureCallback.onCaptureProcessProgressed(progress);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Remote client doesn't respond to capture progress callbacks!");
+            }
+        }
     }
 
     private class RequestCallbackStub extends IRequestCallback.Stub {
@@ -1147,15 +1166,27 @@
 
         @Override
         public CameraSessionConfig initSession(String cameraId, OutputSurface previewSurface,
-                OutputSurface burstSurface) {
+                OutputSurface imageCaptureSurface) {
             OutputSurfaceImplStub outputPreviewSurfaceImpl =
                     new OutputSurfaceImplStub(previewSurface);
-            OutputSurfaceImplStub outputBurstSurfaceImpl =
-                    new OutputSurfaceImplStub(burstSurface);
+            OutputSurfaceImplStub outputImageCaptureSurfaceImpl =
+                    new OutputSurfaceImplStub(imageCaptureSurface);
 
-            Camera2SessionConfigImpl sessionConfig = mSessionProcessor.initSession(cameraId,
-                    mCharacteristicsHashMap, getApplicationContext(), outputPreviewSurfaceImpl,
-                    outputBurstSurfaceImpl, null /*imageAnalysisSurfaceConfig*/);
+            Camera2SessionConfigImpl sessionConfig;
+
+            if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+                OutputSurfaceConfigurationImplStub outputSurfaceConfigs =
+                        new OutputSurfaceConfigurationImplStub(outputPreviewSurfaceImpl,
+                        // Image Analysis Output is currently only supported in CameraX
+                        outputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/);
+
+                sessionConfig = mSessionProcessor.initSession(cameraId,
+                        mCharacteristicsHashMap, getApplicationContext(), outputSurfaceConfigs);
+            } else {
+                sessionConfig = mSessionProcessor.initSession(cameraId,
+                        mCharacteristicsHashMap, getApplicationContext(), outputPreviewSurfaceImpl,
+                        outputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/);
+            }
 
             List<Camera2OutputConfigImpl> outputConfigs = sessionConfig.getOutputConfigs();
             CameraSessionConfig ret = new CameraSessionConfig();
@@ -1237,6 +1268,34 @@
         }
     }
 
+    private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl {
+        private OutputSurfaceImpl mOutputPreviewSurfaceImpl;
+        private OutputSurfaceImpl mOutputImageCaptureSurfaceImpl;
+        private OutputSurfaceImpl mOutputImageAnalysisSurfaceImpl;
+
+        public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput,
+                OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput) {
+            mOutputPreviewSurfaceImpl = previewOutput;
+            mOutputImageCaptureSurfaceImpl = imageCaptureOutput;
+            mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput;
+        }
+
+        @Override
+        public OutputSurfaceImpl getPreviewOutputSurface() {
+            return mOutputPreviewSurfaceImpl;
+        }
+
+        @Override
+        public OutputSurfaceImpl getImageCaptureOutputSurface() {
+            return mOutputImageCaptureSurfaceImpl;
+        }
+
+        @Override
+        public OutputSurfaceImpl getImageAnalysisOutputSurface() {
+            return mOutputImageAnalysisSurfaceImpl;
+        }
+    }
+
     private class OutputSurfaceImplStub implements OutputSurfaceImpl {
         private final Surface mSurface;
         private final Size mSize;
@@ -1414,6 +1473,15 @@
         }
 
         @Override
+        public boolean isCaptureProcessProgressAvailable() {
+            if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+                return mImageExtender.isCaptureProcessProgressAvailable();
+            }
+
+            return false;
+        }
+
+        @Override
         public CaptureStageImpl onEnableSession() {
             return initializeParcelable(mImageExtender.onEnableSession(), mCameraId);
         }
@@ -1570,6 +1638,15 @@
         }
 
         @Override
+        public void onCaptureProcessProgressed(int progress) {
+            try {
+                mProcessResult.onCaptureProcessProgressed(progress);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Remote client doesn't respond to capture progress callbacks!");
+            }
+        }
+
+        @Override
         public void onCaptureCompleted(long shutterTimestamp,
                 List<Pair<CaptureResult.Key, Object>> result) {
             if (result == null) {
diff --git a/proto/src/OWNERS b/proto/src/OWNERS
index abd08de..ccff624 100644
--- a/proto/src/OWNERS
+++ b/proto/src/OWNERS
@@ -2,3 +2,4 @@
 per-file wifi.proto = file:/wifi/OWNERS
 per-file camera.proto = file:/services/core/java/com/android/server/camera/OWNERS
 per-file system_messages.proto = file:/core/res/OWNERS
+per-file altitude.proto = file:/location/OWNERS
diff --git a/proto/src/altitude.proto b/proto/src/altitude.proto
new file mode 100644
index 0000000..1010f67
--- /dev/null
+++ b/proto/src/altitude.proto
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.internal.location.altitude;
+
+option java_package = "com.android.internal.location.altitude";
+option java_multiple_files = true;
+
+// Defines parameters for a spherically projected geoid map and corresponding
+// tile management.
+message MapParamsProto {
+  // Defines the resolution of the map in terms of an S2 level.
+  optional int32 map_s2_level = 1;
+  // Defines the resolution of the tiles in cache in terms of an S2 level.
+  optional int32 cache_tile_s2_level = 2;
+  // Defines the resolution of the tiles on disk in terms of an S2 level.
+  optional int32 disk_tile_s2_level = 3;
+  // Defines the `a` coefficient in the expression `a * map_value + b` used to
+  // calculate a geoid height in meters.
+  optional double model_a_meters = 4;
+  // Defines the `b` coefficient in the expression `a * map_value + b` used to
+  // calculate a geoid height in meters.
+  optional double model_b_meters = 5;
+  // Defines the root mean square error in meters of the geoid height.
+  optional double model_rmse_meters = 6;
+}
+
+// A single tile associating values in the unit interval [0, 1] to map cells.
+message S2TileProto {
+  // The S2 token associated with the common parent of all map cells in this
+  // tile.
+  optional string tile_key = 1;
+
+  // Encoded data that merge into a value in the unit interval [0, 1] for each
+  // map cell in this tile.
+  optional bytes byte_buffer = 2;
+  optional bytes byte_jpeg = 3;
+  optional bytes byte_png = 4;
+}
diff --git a/services/Android.bp b/services/Android.bp
index 76a1484..f6570e9 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -60,9 +60,17 @@
                 ignore_warnings: false,
                 proguard_flags_files: ["proguard.flags"],
             },
-            // Note: Optimizations are disabled by default if unspecified in
-            // the java_library rule.
-            conditions_default: {},
+            conditions_default: {
+                optimize: {
+                    enabled: true,
+                    optimize: false,
+                    shrink: true,
+                    ignore_warnings: false,
+                    // Note that this proguard config is very conservative, only shrinking the
+                    // permission subpackage to prune unused jarjar'ed Kotlin dependencies.
+                    proguard_flags_files: ["proguard_permission.flags"],
+                },
+            },
         },
     },
 }
@@ -97,6 +105,7 @@
         ":services.midi-sources",
         ":services.musicsearch-sources",
         ":services.net-sources",
+        ":services.permission-sources",
         ":services.print-sources",
         ":services.profcollect-sources",
         ":services.restrictions-sources",
@@ -131,6 +140,7 @@
         app_image: true,
         profile: "art-profile",
     },
+    exclude_kotlinc_generated_files: true,
 
     srcs: [":services-main-sources"],
 
@@ -152,6 +162,7 @@
         "services.musicsearch",
         "services.net",
         "services.people",
+        "services.permission",
         "services.print",
         "services.profcollect",
         "services.restrictions",
diff --git a/services/OWNERS b/services/OWNERS
index 495c0737..eace906 100644
--- a/services/OWNERS
+++ b/services/OWNERS
@@ -3,7 +3,7 @@
 # art-team@ manages the system server profile
 per-file art-profile* = calin@google.com, ngeoffray@google.com, vmarko@google.com
 
-per-file java/com/android/server/* = toddke@google.com,patb@google.com
+per-file java/com/android/server/* = patb@google.com #{LAST_RESORT_SUGGESTION}
 per-file tests/servicestests/src/com/android/server/systemconfig/* = patb@google.com
 
 per-file proguard.flags = jdduke@google.com
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index f35de17..e92150b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityService.ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS;
 import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE;
 import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER;
 import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_STATUS;
@@ -73,8 +74,11 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
+import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MagnificationSpec;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowInfo;
 import android.view.accessibility.AccessibilityCache;
@@ -83,6 +87,7 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 import android.view.inputmethod.EditorInfo;
+import android.window.ScreenCapture;
 import android.window.ScreenCapture.ScreenshotHardwareBuffer;
 
 import com.android.internal.annotations.GuardedBy;
@@ -197,6 +202,8 @@
 
     final ComponentName mComponentName;
 
+    int mGenericMotionEventSources;
+
     // the events pending events to be dispatched to this service
     final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>();
 
@@ -211,6 +218,11 @@
 
     /** The timestamp of requesting to take screenshot in milliseconds */
     private long mRequestTakeScreenshotTimestampMs;
+    /**
+     * The timestamps of requesting to take a window screenshot in milliseconds,
+     * mapping from accessibility window id -> timestamp.
+     */
+    private SparseArray<Long> mRequestTakeScreenshotOfWindowTimestampMs = new SparseArray<>();
 
     public interface SystemSupport {
         /**
@@ -282,6 +294,8 @@
         void requestImeLocked(AbstractAccessibilityServiceConnection connection);
 
         void unbindImeLocked(AbstractAccessibilityServiceConnection connection);
+
+        void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc);
     }
 
     public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
@@ -352,6 +366,7 @@
         }
         mNotificationTimeout = info.notificationTimeout;
         mIsDefault = (info.flags & DEFAULT) != 0;
+        mGenericMotionEventSources = info.getMotionEventSources();
 
         if (supportsFlagForNotImportantViews(info)) {
             if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
@@ -1252,6 +1267,51 @@
     }
 
     @Override
+    public void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+            ScreenCapture.ScreenCaptureListener listener,
+            IAccessibilityInteractionConnectionCallback callback) throws RemoteException {
+        final long currentTimestamp = SystemClock.uptimeMillis();
+        if ((currentTimestamp
+                - mRequestTakeScreenshotOfWindowTimestampMs.get(accessibilityWindowId, 0L))
+                <= ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS) {
+            callback.sendTakeScreenshotOfWindowError(
+                    AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT, interactionId);
+            return;
+        }
+        mRequestTakeScreenshotOfWindowTimestampMs.put(accessibilityWindowId, currentTimestamp);
+
+        synchronized (mLock) {
+            if (!hasRightsToCurrentUserLocked()) {
+                callback.sendTakeScreenshotOfWindowError(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+                return;
+            }
+            if (!mSecurityPolicy.canTakeScreenshotLocked(this)) {
+                callback.sendTakeScreenshotOfWindowError(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
+                        interactionId);
+                return;
+            }
+        }
+        if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+            callback.sendTakeScreenshotOfWindowError(
+                    AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
+                    interactionId);
+            return;
+        }
+
+        RemoteAccessibilityConnection connection = mA11yWindowManager.getConnectionLocked(
+                mSystemSupport.getCurrentUserIdLocked(),
+                resolveAccessibilityWindowIdLocked(accessibilityWindowId));
+        if (connection == null) {
+            callback.sendTakeScreenshotOfWindowError(
+                    AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_WINDOW, interactionId);
+            return;
+        }
+        connection.getRemote().takeScreenshotOfWindow(interactionId, listener, callback);
+    }
+
+    @Override
     public void takeScreenshot(int displayId, RemoteCallback callback) {
         if (svcConnTracingEnabled()) {
             logTraceSvcConn("takeScreenshot", "displayId=" + displayId + ";callback=" + callback);
@@ -1696,6 +1756,11 @@
         return mSystemSupport.getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
     }
 
+    public boolean wantsGenericMotionEvent(MotionEvent event) {
+        final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
+        return (mGenericMotionEventSources & eventSourceWithoutClass) != 0;
+    }
+
     /**
      * Called by the invocation handler to notify the service that the
      * state of magnification has changed.
@@ -2434,4 +2499,9 @@
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
-}
\ No newline at end of file
+
+    @Override
+    public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {
+        mSystemSupport.attachAccessibilityOverlayToDisplay(displayId, sc);
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index d80117d..c87d1c8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -141,6 +141,9 @@
      */
     static final int FLAG_SEND_MOTION_EVENTS = 0x00000400;
 
+    /** Flag for intercepting generic motion events. */
+    static final int FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS = 0x00000800;
+
     static final int FEATURES_AFFECTING_MOTION_EVENTS =
             FLAG_FEATURE_INJECT_MOTION_EVENTS
                     | FLAG_FEATURE_AUTOCLICK
@@ -149,7 +152,8 @@
                     | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
                     | FLAG_SERVICE_HANDLES_DOUBLE_TAP
                     | FLAG_REQUEST_MULTI_FINGER_GESTURES
-                    | FLAG_REQUEST_2_FINGER_PASSTHROUGH;
+                    | FLAG_REQUEST_2_FINGER_PASSTHROUGH
+                    | FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS;
 
     private final Context mContext;
 
@@ -182,6 +186,10 @@
 
     private final SparseArray<EventStreamState> mTouchScreenStreamStates = new SparseArray<>(0);
 
+    // State tracking for generic MotionEvents is display-agnostic so we only need one.
+    private GenericMotionEventStreamState mGenericMotionEventStreamState;
+    private int mCombinedGenericMotionEventSources = 0;
+
     private EventStreamState mKeyboardStreamState;
 
     AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
@@ -205,6 +213,7 @@
         mInstalled = true;
         disableFeatures();
         enableFeatures();
+        mAms.onInputFilterInstalled(true);
         super.onInstalled();
     }
 
@@ -215,6 +224,7 @@
         }
         mInstalled = false;
         disableFeatures();
+        mAms.onInputFilterInstalled(false);
         super.onUninstalled();
     }
 
@@ -296,7 +306,13 @@
     private EventStreamState getEventStreamState(InputEvent event) {
         if (event instanceof MotionEvent) {
             final int displayId = event.getDisplayId();
+            if (mGenericMotionEventStreamState == null) {
+                mGenericMotionEventStreamState = new GenericMotionEventStreamState();
+            }
 
+            if (mGenericMotionEventStreamState.shouldProcessMotionEvent((MotionEvent) event)) {
+                return mGenericMotionEventStreamState;
+            }
             if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
                 EventStreamState touchScreenStreamState = mTouchScreenStreamStates.get(displayId);
                 if (touchScreenStreamState == null) {
@@ -362,8 +378,11 @@
         mPm.userActivity(event.getEventTime(), false);
         MotionEvent transformedEvent = MotionEvent.obtain(event);
         final int displayId = event.getDisplayId();
-        mEventHandler.get(isDisplayIdValid(displayId) ? displayId : Display.DEFAULT_DISPLAY)
-                .onMotionEvent(transformedEvent, event, policyFlags);
+        EventStreamTransformation eventStreamTransformation = mEventHandler.get(
+                isDisplayIdValid(displayId) ? displayId : Display.DEFAULT_DISPLAY);
+        if (eventStreamTransformation != null) {
+            eventStreamTransformation.onMotionEvent(transformedEvent, event, policyFlags);
+        }
         transformedEvent.recycle();
     }
 
@@ -490,6 +509,19 @@
             mTouchExplorer.put(displayId, explorer);
         }
 
+        if ((mEnabledFeatures & FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS) != 0) {
+            addFirstEventHandler(displayId, new BaseEventStreamTransformation() {
+                @Override
+                public void onMotionEvent(MotionEvent event, MotionEvent rawEvent,
+                        int policyFlags) {
+                    if (!anyServiceWantsGenericMotionEvent(rawEvent)
+                            || !mAms.sendMotionEventToListeningServices(rawEvent)) {
+                        super.onMotionEvent(event, rawEvent, policyFlags);
+                    }
+                }
+            });
+        }
+
         if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
                 || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
                 || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
@@ -842,6 +874,32 @@
         }
     }
 
+    private class GenericMotionEventStreamState extends EventStreamState {
+        @Override
+        public boolean shouldProcessMotionEvent(MotionEvent event) {
+            return anyServiceWantsGenericMotionEvent(event);
+        }
+        @Override
+        public boolean shouldProcessScroll() {
+            return true;
+        }
+    }
+
+    private boolean anyServiceWantsGenericMotionEvent(MotionEvent event) {
+        // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing
+        // touch exploration.
+        if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)
+                && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
+            return false;
+        }
+        final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
+        return (mCombinedGenericMotionEventSources & eventSourceWithoutClass) != 0;
+    }
+
+    public void setCombinedGenericMotionEventSources(int sources) {
+        mCombinedGenericMotionEventSources = sources;
+    }
+
     /**
      * Keeps state of streams of events from all keyboard devices.
      */
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 47b4156..3145139 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -106,9 +106,12 @@
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.IWindow;
+import android.view.InputDevice;
+import android.view.InputEvent;
 import android.view.KeyEvent;
 import android.view.MagnificationSpec;
 import android.view.MotionEvent;
+import android.view.SurfaceControl;
 import android.view.WindowInfo;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
@@ -146,6 +149,7 @@
 import com.android.server.accessibility.magnification.WindowMagnificationManager;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -189,7 +193,7 @@
 
     // TODO: Restructure service initialization so services aren't connected before all of
     //       their capabilities are ready.
-    private static final int WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS = 1000;
+    private static final int WAIT_INPUT_FILTER_INSTALL_TIMEOUT_MS = 1000;
 
 
     // This postpones state changes events when a window doesn't exist with the expectation that
@@ -257,6 +261,8 @@
 
     private boolean mHasInputFilter;
 
+    private boolean mInputFilterInstalled;
+
     private KeyEventDispatcher mKeyEventDispatcher;
 
     private SparseArray<MotionEventInjector> mMotionEventInjectors;
@@ -291,12 +297,12 @@
 
     private Point mTempPoint = new Point();
     private boolean mIsAccessibilityButtonShown;
-
     private boolean mInputBound;
     IRemoteAccessibilityInputConnection mRemoteInputConnection;
     EditorInfo mEditorInfo;
     boolean mRestarting;
     boolean mInputSessionRequested;
+    private SparseArray<SurfaceControl> mA11yOverlayLayers = new SparseArray<>();
 
     private AccessibilityUserState getCurrentUserStateLocked() {
         return getUserStateLocked(mCurrentUserId);
@@ -516,6 +522,16 @@
         return mFingerprintGestureDispatcher;
     }
 
+    /**
+     * Called by the {@link AccessibilityInputFilter} when the filter install state changes.
+     */
+    public void onInputFilterInstalled(boolean installed) {
+        synchronized (mLock) {
+            mInputFilterInstalled = installed;
+            mLock.notifyAll();
+        }
+    }
+
     private void onBootPhase(int phase) {
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) {
@@ -859,7 +875,8 @@
                     Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
                 }
                 return IntPair.of(
-                        getClientStateLocked(userState),
+                        combineUserStateAndProxyState(getClientStateLocked(userState),
+                                mProxyManager.getStateLocked()),
                         client.mLastSentRelevantEventTypes);
             } else {
                 userState.mUserClients.register(callback, client);
@@ -871,7 +888,9 @@
                             + " and userId:" + mCurrentUserId);
                 }
                 return IntPair.of(
-                        (resolvedUserId == mCurrentUserId) ? getClientStateLocked(userState) : 0,
+                        (resolvedUserId == mCurrentUserId) ? combineUserStateAndProxyState(
+                                getClientStateLocked(userState), mProxyManager.getStateLocked())
+                                : 0,
                         client.mLastSentRelevantEventTypes);
             }
         }
@@ -1002,6 +1021,7 @@
         notifyAccessibilityServicesDelayedLocked(event, false);
         notifyAccessibilityServicesDelayedLocked(event, true);
         mUiAutomationManager.sendAccessibilityEventLocked(event);
+        mProxyManager.sendAccessibilityEvent(event);
     }
 
     private void sendAccessibilityEventToInputFilter(AccessibilityEvent event) {
@@ -1142,9 +1162,9 @@
             }
             List<AccessibilityServiceConnection> services =
                     getUserStateLocked(resolvedUserId).mBoundServices;
-            int numServices = services.size();
+            int numServices = services.size() + mProxyManager.getNumProxys();
             interfacesToInterrupt = new ArrayList<>(numServices);
-            for (int i = 0; i < numServices; i++) {
+            for (int i = 0; i < services.size(); i++) {
                 AccessibilityServiceConnection service = services.get(i);
                 IBinder a11yServiceBinder = service.mService;
                 IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
@@ -1152,6 +1172,7 @@
                     interfacesToInterrupt.add(a11yServiceInterface);
                 }
             }
+            mProxyManager.addServiceInterfaces(interfacesToInterrupt);
         }
         for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
             try {
@@ -1344,7 +1365,7 @@
         }
     }
 
-    /** Send a motion event to the service to allow it to perform gesture detection. */
+    /** Send a motion event to the services. */
     public boolean sendMotionEventToListeningServices(MotionEvent event) {
         boolean result;
         event = MotionEvent.obtain(event);
@@ -1447,7 +1468,7 @@
 
     @Override
     public @Nullable MotionEventInjector getMotionEventInjectorForDisplayLocked(int displayId) {
-        final long endMillis = SystemClock.uptimeMillis() + WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS;
+        final long endMillis = SystemClock.uptimeMillis() + WAIT_INPUT_FILTER_INSTALL_TIMEOUT_MS;
         MotionEventInjector motionEventInjector = null;
         while ((mMotionEventInjectors == null) && (SystemClock.uptimeMillis() < endMillis)) {
             try {
@@ -1701,7 +1722,9 @@
             AccessibilityUserState state = getCurrentUserStateLocked();
             for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
                 AccessibilityServiceConnection service = state.mBoundServices.get(i);
-                if (service.isServiceDetectsGesturesEnabled(displayId)) {
+                if (service.wantsGenericMotionEvent(event)
+                        || (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)
+                        && service.isServiceDetectsGesturesEnabled(displayId))) {
                     service.notifyMotionEvent(event);
                     result = true;
                 }
@@ -1940,6 +1963,7 @@
                 mUiAutomationManager.getServiceInfo(), client)
                 ? mUiAutomationManager.getRelevantEventTypes()
                 : 0;
+        relevantEventTypes |= mProxyManager.getRelevantEventTypes();
         return relevantEventTypes;
     }
 
@@ -2177,21 +2201,25 @@
         updateAccessibilityEnabledSettingLocked(userState);
     }
 
-    void scheduleUpdateClientsIfNeeded(AccessibilityUserState userState) {
-        synchronized (mLock) {
-            scheduleUpdateClientsIfNeededLocked(userState);
-        }
+    private int combineUserStateAndProxyState(int userState, int proxyState) {
+        return userState | proxyState;
     }
 
     void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
         final int clientState = getClientStateLocked(userState);
-        if (userState.getLastSentClientStateLocked() != clientState
+        final int proxyState = mProxyManager.getStateLocked();
+        if ((userState.getLastSentClientStateLocked() != clientState
+                || mProxyManager.getLastSentStateLocked() != proxyState)
                 && (mGlobalClients.getRegisteredCallbackCount() > 0
-                        || userState.mUserClients.getRegisteredCallbackCount() > 0)) {
+                || userState.mUserClients.getRegisteredCallbackCount() > 0)) {
             userState.setLastSentClientStateLocked(clientState);
+            mProxyManager.setLastStateLocked(proxyState);
+            // Send both the user and proxy state to the app for now.
+            // TODO(b/250929565): Send proxy state to proxy clients
             mMainHandler.sendMessage(obtainMessage(
                     AccessibilityManagerService::sendStateToAllClients,
-                    this, clientState, userState.mUserId));
+                    this, combineUserStateAndProxyState(clientState, proxyState),
+                    userState.mUserId));
         }
     }
 
@@ -2291,6 +2319,13 @@
             if (userState.isPerformGesturesEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
             }
+            int combinedGenericMotionEventSources = 0;
+            for (AccessibilityServiceConnection connection : userState.mBoundServices) {
+                combinedGenericMotionEventSources |= connection.mGenericMotionEventSources;
+            }
+            if (combinedGenericMotionEventSources != 0) {
+                flags |= AccessibilityInputFilter.FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS;
+            }
             if (flags != 0) {
                 if (!mHasInputFilter) {
                     mHasInputFilter = true;
@@ -2303,6 +2338,8 @@
                     setInputFilter = true;
                 }
                 mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags);
+                mInputFilter.setCombinedGenericMotionEventSources(
+                        combinedGenericMotionEventSources);
             } else {
                 if (mHasInputFilter) {
                     mHasInputFilter = false;
@@ -2433,7 +2470,8 @@
         // binding we do an update pass after each bind event, so we run this
         // code and register the callback if needed.
 
-        boolean observingWindows = mUiAutomationManager.canRetrieveInteractiveWindowsLocked();
+        boolean observingWindows = mUiAutomationManager.canRetrieveInteractiveWindowsLocked()
+                || mProxyManager.canRetrieveInteractiveWindowsLocked();
         List<AccessibilityServiceConnection> boundServices = userState.mBoundServices;
         final int boundServiceCount = boundServices.size();
         for (int i = 0; !observingWindows && (i < boundServiceCount); i++) {
@@ -3651,8 +3689,14 @@
             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);
+        mProxyManager.registerProxy(client, displayId, mContext,
+                sIdCounter++, mMainHandler, mSecurityPolicy, this, getTraceManager(),
+                mWindowManagerService, mA11yWindowManager);
         return true;
     }
 
@@ -3963,6 +4007,8 @@
 
             synchronized (mLock) {
                 mDisplaysList.add(display);
+                mA11yOverlayLayers.put(
+                        displayId, mWindowManagerService.getA11yOverlayLayer(displayId));
                 if (mInputFilter != null) {
                     mInputFilter.onDisplayAdded(display);
                 }
@@ -3986,6 +4032,7 @@
                 if (!removeDisplayFromList(displayId)) {
                     return;
                 }
+                mA11yOverlayLayers.remove(displayId);
                 if (mInputFilter != null) {
                     mInputFilter.onDisplayRemoved(displayId);
                 }
@@ -4625,6 +4672,29 @@
         }
     }
 
+    @Override
+    public void injectInputEventToInputFilter(InputEvent event) {
+        synchronized (mLock) {
+            final long endMillis =
+                    SystemClock.uptimeMillis() + WAIT_INPUT_FILTER_INSTALL_TIMEOUT_MS;
+            while (!mInputFilterInstalled && (SystemClock.uptimeMillis() < endMillis)) {
+                try {
+                    mLock.wait(endMillis - SystemClock.uptimeMillis());
+                } catch (InterruptedException ie) {
+                    /* ignore */
+                }
+            }
+        }
+
+        if (mInputFilterInstalled && mInputFilter != null) {
+            mInputFilter.onInputEvent(event,
+                    WindowManagerPolicy.FLAG_PASS_TO_USER | WindowManagerPolicy.FLAG_INJECTED);
+        } else {
+            Slog.w(LOG_TAG, "Cannot injectInputEventToInputFilter because the "
+                    + "AccessibilityInputFilter is not installed.");
+        }
+    }
+
     private final class SendWindowStateChangedEventRunnable implements Runnable {
 
         private final AccessibilityEvent mPendingEvent;
@@ -4687,4 +4757,30 @@
             return true;
         }
     }
+
+    @Override
+    public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {
+        mMainHandler.sendMessage(
+                obtainMessage(
+                        AccessibilityManagerService::attachAccessibilityOverlayToDisplayInternal,
+                        this,
+                        displayId,
+                        sc));
+    }
+
+    void attachAccessibilityOverlayToDisplayInternal(int displayId, SurfaceControl sc) {
+        if (!mA11yOverlayLayers.contains(displayId)) {
+            mA11yOverlayLayers.put(displayId, mWindowManagerService.getA11yOverlayLayer(displayId));
+        }
+        SurfaceControl parent = mA11yOverlayLayers.get(displayId);
+        if (parent == null) {
+            Slog.e(LOG_TAG, "Unable to get accessibility overlay SurfaceControl.");
+            mA11yOverlayLayers.remove(displayId);
+            return;
+        }
+        SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+        transaction.reparent(sc, parent);
+        transaction.apply();
+        transaction.close();
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index e5e1d02..ce269f5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -696,7 +696,7 @@
         final ResolveInfo resolveInfo = service.getServiceInfo().getResolveInfo();
 
         if (resolveInfo == null) {
-            // For InteractionBridge and UiAutomation
+            // For InteractionBridge, UiAutomation, and Proxy.
             return true;
         }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index 6958b66..c08b6ab 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -167,6 +167,12 @@
         mServiceCallback.setPerformAccessibilityActionResult(succeeded, interactionId);
     }
 
+    @Override
+    public void sendTakeScreenshotOfWindowError(int errorCode, int interactionId)
+            throws RemoteException {
+        mServiceCallback.sendTakeScreenshotOfWindowError(errorCode, interactionId);
+    }
+
     private void replaceInfoActionsAndCallService() {
         final AccessibilityNodeInfo nodeToReturn;
         boolean doCallback = false;
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index 934b665..d7f9c12 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -16,8 +16,12 @@
 
 package com.android.server.accessibility;
 
+import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_CLASS_NAME;
+import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_PACKAGE_NAME;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.AccessibilityTrace;
+import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.MagnificationConfig;
 import android.annotation.NonNull;
 import android.content.ComponentName;
@@ -31,7 +35,9 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteCallback;
+import android.os.RemoteException;
 import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityDisplayProxy;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.Nullable;
@@ -44,7 +50,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.
@@ -52,10 +58,6 @@
  * TODO(241429275): Initialize this when a proxy is registered.
  */
 public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection {
-    // Names used to populate ComponentName and ResolveInfo
-    private static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage";
-    private static final String PROXY_COMPONENT_CLASS_NAME = "ProxyClass";
-
     private int mDisplayId;
     private List<AccessibilityServiceInfo> mInstalledAndEnabledServices;
 
@@ -75,6 +77,16 @@
     }
 
     /**
+     * Called when the proxy is registered.
+     */
+    void initializeServiceInterface(IAccessibilityServiceClient serviceInterface)
+            throws RemoteException {
+        mServiceInterface = serviceInterface;
+        mService = serviceInterface.asBinder();
+        mServiceInterface.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId));
+    }
+
+    /**
      * Keeps mAccessibilityServiceInfo in sync with the proxy's list of AccessibilityServiceInfos.
      *
      * <p>This also sets the properties that are assumed to be populated by installed packages.
@@ -88,7 +100,7 @@
             synchronized (mLock) {
                 mInstalledAndEnabledServices = infos;
                 final AccessibilityServiceInfo proxyInfo = mAccessibilityServiceInfo;
-                // Reset values.
+                // Reset values. mAccessibilityServiceInfo is not completely reset since it is final
                 proxyInfo.flags = 0;
                 proxyInfo.eventTypes = 0;
                 proxyInfo.notificationTimeout = 0;
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index fb0b8f3..2184878 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -14,7 +14,21 @@
  * limitations under the License.
  */
 package com.android.server.accessibility;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.SparseArray;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.server.wm.WindowManagerInternal;
+
+import java.util.List;
 
 /**
  * Manages proxy connections.
@@ -25,22 +39,185 @@
  * TODO(241117292): Remove or cut down during simultaneous user refactoring.
  */
 public class ProxyManager {
+    // Names used to populate ComponentName and ResolveInfo in connection.mA11yServiceInfo and in
+    // the infos of connection.setInstalledAndEnabledServices
+    static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage";
+    static final String PROXY_COMPONENT_CLASS_NAME = "ProxyClass";
+
     private final Object mLock;
 
+    // Used to determine if we should notify AccessibilityManager clients of updates.
+    // TODO(254545943): Separate this so each display id has its own state. Currently there is no
+    // way to identify from AccessibilityManager which proxy state should be returned.
+    private int mLastState = -1;
+
+    private SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections =
+            new SparseArray<>();
+
     ProxyManager(Object lock) {
         mLock = lock;
     }
 
     /**
-     * TODO: Create the proxy service connection.
+     * Creates the service connection.
      */
-    public void registerProxy(IAccessibilityServiceClient client, int displayId) {
+    public void registerProxy(IAccessibilityServiceClient client, int displayId,
+            Context context,
+            int id, Handler mainHandler,
+            AccessibilitySecurityPolicy securityPolicy,
+            AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
+            AccessibilityTrace trace,
+            WindowManagerInternal windowManagerInternal,
+            AccessibilityWindowManager awm) throws RemoteException {
+
+        // Set a default AccessibilityServiceInfo that is used before the proxy's info is
+        // populated. A proxy has the touch exploration and window capabilities.
+        AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+        info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+                | AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
+        final String componentClassDisplayName = PROXY_COMPONENT_CLASS_NAME + displayId;
+        info.setComponentName(new ComponentName(PROXY_COMPONENT_PACKAGE_NAME,
+                componentClassDisplayName));
+        ProxyAccessibilityServiceConnection connection =
+                new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info,
+                        id, mainHandler, mLock, securityPolicy, systemSupport, trace,
+                        windowManagerInternal,
+                        awm, displayId);
+
+        mProxyA11yServiceConnections.put(displayId, connection);
+
+        // If the client dies, make sure to remove the connection.
+        IBinder.DeathRecipient deathRecipient =
+                new IBinder.DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        client.asBinder().unlinkToDeath(this, 0);
+                        clearConnection(displayId);
+                    }
+                };
+        client.asBinder().linkToDeath(deathRecipient, 0);
+        // Notify apps that the service state has changed.
+        // A11yManager#A11yServicesStateChangeListener
+        connection.mSystemSupport.onClientChangeLocked(true);
+
+        connection.initializeServiceInterface(client);
     }
 
     /**
-     * TODO: Unregister the proxy service connection based on display id.
+     * Unregister the proxy based on display id.
      */
     public boolean unregisterProxy(int displayId) {
-        return true;
+        return clearConnection(displayId);
     }
-}
+
+    private boolean clearConnection(int displayId) {
+        if (mProxyA11yServiceConnections.contains(displayId)) {
+            mProxyA11yServiceConnections.remove(displayId);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Checks if a display id is being proxy-ed.
+     */
+    public boolean isProxyed(int displayId) {
+        return mProxyA11yServiceConnections.contains(displayId);
+    }
+
+    /**
+     * Sends AccessibilityEvents to all proxies.
+     * {@link android.view.accessibility.AccessibilityDisplayProxy} will filter based on display.
+     * TODO(b/250929565): Filtering should happen in the system, not in the proxy.
+     */
+    public void sendAccessibilityEvent(AccessibilityEvent event) {
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            proxy.notifyAccessibilityEvent(event);
+        }
+    }
+
+    /**
+     * Returns {@code true} if any proxy can retrieve windows.
+     * TODO(b/250929565): Retrieve per connection/user state.
+     */
+    public boolean canRetrieveInteractiveWindowsLocked() {
+        boolean observingWindows = false;
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            final ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            if (proxy.mRetrieveInteractiveWindows) {
+                observingWindows = true;
+                break;
+            }
+        }
+        return observingWindows;
+    }
+
+    /**
+     * If there is at least one proxy, accessibility is enabled.
+     */
+    public int getStateLocked() {
+        int clientState = 0;
+        final boolean a11yEnabled = mProxyA11yServiceConnections.size() > 0;
+        if (a11yEnabled) {
+            clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
+        }
+        return clientState;
+        // TODO(b/254545943): When A11yManager is separated, include support for other properties
+        // like isTouchExplorationEnabled.
+    }
+
+    /**
+     * Gets the last state.
+     */
+    public int getLastSentStateLocked() {
+        return mLastState;
+    }
+
+    /**
+     * Sets the last state.
+     */
+    public void setLastStateLocked(int proxyState) {
+        mLastState = proxyState;
+    }
+
+    /**
+     * Returns the relevant event types of every proxy.
+     * TODO(254545943): When A11yManager is separated, return based on the A11yManager display.
+     */
+    public int getRelevantEventTypes() {
+        int relevantEventTypes = 0;
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            relevantEventTypes |= proxy.getRelevantEventTypes();
+        }
+        return relevantEventTypes;
+    }
+
+    /**
+     * Gets the number of current proxy connections.
+     * @return
+     */
+    public int getNumProxys() {
+        return mProxyA11yServiceConnections.size();
+    }
+
+    /**
+     * Adds the service interfaces to a list.
+     * @param interfaces
+     */
+    public void addServiceInterfaces(List<IAccessibilityServiceClient> interfaces) {
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            final ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            final IBinder proxyBinder = proxy.mService;
+            final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
+            if ((proxyBinder != null) && (proxyInterface != null)) {
+                interfaces.add(proxyInterface);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/api/current.txt b/services/api/current.txt
index 42ae10e..da5b1fc 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -57,8 +57,8 @@
 
   public static interface PackageManagerLocal.FilteredSnapshot extends java.lang.AutoCloseable {
     method public void close();
-    method public void forAllPackageStates(@NonNull java.util.function.Consumer<com.android.server.pm.pkg.PackageState>);
     method @Nullable public com.android.server.pm.pkg.PackageState getPackageState(@NonNull String);
+    method @NonNull public java.util.Map<java.lang.String,com.android.server.pm.pkg.PackageState> getPackageStates();
   }
 
   public static interface PackageManagerLocal.UnfilteredSnapshot extends java.lang.AutoCloseable {
@@ -76,6 +76,7 @@
     method @Nullable public String getSdkLibraryName();
     method @NonNull public java.util.List<com.android.server.pm.pkg.AndroidPackageSplit> getSplits();
     method @Nullable public String getStaticSharedLibraryName();
+    method @NonNull public java.util.UUID getStorageUuid();
     method public int getTargetSdkVersion();
     method public boolean isDebuggable();
     method public boolean isIsolatedSplitLoading();
@@ -97,8 +98,10 @@
   public interface PackageState {
     method @Nullable public com.android.server.pm.pkg.AndroidPackage getAndroidPackage();
     method public int getAppId();
+    method public int getHiddenApiEnforcementPolicy();
     method @NonNull public String getPackageName();
     method @Nullable public String getPrimaryCpuAbi();
+    method @Nullable public String getSeInfo();
     method @Nullable public String getSecondaryCpuAbi();
     method @NonNull public com.android.server.pm.pkg.PackageUserState getStateForUser(@NonNull android.os.UserHandle);
     method @NonNull public java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries();
@@ -126,13 +129,6 @@
 
 }
 
-package com.android.server.pm.snapshot {
-
-  public interface PackageDataSnapshot {
-  }
-
-}
-
 package com.android.server.role {
 
   public interface RoleServicePlatformHelper {
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 3cfae60..8baae53a 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -35,6 +35,7 @@
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.AppOpsManagerInternal;
+import android.app.BroadcastOptions;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.app.KeyguardManager;
@@ -257,6 +258,9 @@
     private boolean mIsProviderInfoPersisted;
     private boolean mIsCombinedBroadcastEnabled;
 
+    // Mark widget lifecycle broadcasts as 'interactive'
+    private Bundle mInteractiveBroadcast;
+
     AppWidgetServiceImpl(Context context) {
         mContext = context;
     }
@@ -286,6 +290,11 @@
             Slog.d(TAG, "App widget provider info will not be persisted on this device");
         }
 
+        BroadcastOptions opts = BroadcastOptions.makeBasic();
+        opts.setBackgroundActivityStartsAllowed(false);
+        opts.setInteractive(true);
+        mInteractiveBroadcast = opts.toBundle();
+
         computeMaximumWidgetBitmapMemory();
         registerBroadcastReceiver();
         registerOnCrossProfileProvidersChangedListener();
@@ -2379,33 +2388,40 @@
         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE);
         intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
         intent.setComponent(p.id.componentName);
-        sendBroadcastAsUser(intent, p.id.getProfile());
+        // Placing a widget is something users expect to be UX-responsive, so mark this
+        // broadcast as interactive
+        sendBroadcastAsUser(intent, p.id.getProfile(), true);
     }
 
     private void sendEnableIntentLocked(Provider p) {
         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
         intent.setComponent(p.id.componentName);
-        sendBroadcastAsUser(intent, p.id.getProfile());
+        // Enabling the widget is something users expect to be UX-responsive, so mark this
+        // broadcast as interactive
+        sendBroadcastAsUser(intent, p.id.getProfile(), true);
     }
 
     private void sendUpdateIntentLocked(Provider provider, int[] appWidgetIds) {
         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
         intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
         intent.setComponent(provider.id.componentName);
-        sendBroadcastAsUser(intent, provider.id.getProfile());
+        // Periodic background widget update heartbeats are not an interactive use case
+        sendBroadcastAsUser(intent, provider.id.getProfile(), false);
     }
 
     private void sendDeletedIntentLocked(Widget widget) {
         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
         intent.setComponent(widget.provider.id.componentName);
         intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId);
-        sendBroadcastAsUser(intent, widget.provider.id.getProfile());
+        // Cleanup after deletion isn't an interactive UX case
+        sendBroadcastAsUser(intent, widget.provider.id.getProfile(), false);
     }
 
     private void sendDisabledIntentLocked(Provider provider) {
         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
         intent.setComponent(provider.id.componentName);
-        sendBroadcastAsUser(intent, provider.id.getProfile());
+        // Cleanup after disable isn't an interactive UX case
+        sendBroadcastAsUser(intent, provider.id.getProfile(), false);
     }
 
     public void sendOptionsChangedIntentLocked(Widget widget) {
@@ -2413,7 +2429,9 @@
         intent.setComponent(widget.provider.id.componentName);
         intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId);
         intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, widget.options);
-        sendBroadcastAsUser(intent, widget.provider.id.getProfile());
+        // The user's changed the options, so seeing them take effect promptly is
+        // an interactive UX expectation
+        sendBroadcastAsUser(intent, widget.provider.id.getProfile(), true);
     }
 
     @GuardedBy("mLock")
@@ -3666,10 +3684,17 @@
         return null;
     }
 
-    private void sendBroadcastAsUser(Intent intent, UserHandle userHandle) {
+    /**
+     * Sends a widget lifecycle broadcast within the specified user.  If {@code isInteractive}
+     * is specified as {@code true}, the broadcast dispatch mechanism will be told that it
+     * is related to a UX flow with user-visible expectations about timely dispatch.  This
+     * should only be used for broadcast flows that do have such expectations.
+     */
+    private void sendBroadcastAsUser(Intent intent, UserHandle userHandle, boolean isInteractive) {
         final long identity = Binder.clearCallingIdentity();
         try {
-            mContext.sendBroadcastAsUser(intent, userHandle);
+            mContext.sendBroadcastAsUser(intent, userHandle, null,
+                    isInteractive ? mInteractiveBroadcast : null);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -5008,18 +5033,20 @@
 
         private void sendWidgetRestoreBroadcastLocked(String action, Provider provider,
                 Host host, int[] oldIds, int[] newIds, UserHandle userHandle) {
+            // Users expect restore to emplace widgets properly ASAP, so flag these as
+            // being interactive broadcast dispatches
             Intent intent = new Intent(action);
             intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS, oldIds);
             intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, newIds);
             if (provider != null) {
                 intent.setComponent(provider.id.componentName);
-                sendBroadcastAsUser(intent, userHandle);
+                sendBroadcastAsUser(intent, userHandle, true);
             }
             if (host != null) {
                 intent.setComponent(null);
                 intent.setPackage(host.id.packageName);
                 intent.putExtra(AppWidgetManager.EXTRA_HOST_ID, host.id.hostId);
-                sendBroadcastAsUser(intent, userHandle);
+                sendBroadcastAsUser(intent, userHandle, true);
             }
         }
 
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
index 3ea1bcb..7d8bb51 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
@@ -114,7 +114,7 @@
         info.minWidth = parser.getAttributeInt(null, ATTR_MIN_WIDTH, 0);
         info.minHeight = parser.getAttributeInt(null, ATTR_MIN_HEIGHT, 0);
         info.minResizeWidth = parser.getAttributeInt(null, ATTR_MIN_RESIZE_WIDTH, 0);
-        info.minResizeWidth = parser.getAttributeInt(null, ATTR_MIN_RESIZE_HEIGHT, 0);
+        info.minResizeHeight = parser.getAttributeInt(null, ATTR_MIN_RESIZE_HEIGHT, 0);
         info.maxResizeWidth = parser.getAttributeInt(null, ATTR_MAX_RESIZE_WIDTH, 0);
         info.maxResizeHeight = parser.getAttributeInt(null, ATTR_MAX_RESIZE_HEIGHT, 0);
         info.targetCellWidth = parser.getAttributeInt(null, ATTR_TARGET_CELL_WIDTH, 0);
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/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 8525e36..592045c 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -245,6 +245,7 @@
         });
     }
 
+    @SuppressWarnings("ReturnValueIgnored")
     private void maybeRequestShowInlineSuggestions(int sessionId,
             @Nullable InlineSuggestionsRequest request,
             @Nullable List<Dataset> inlineSuggestionsData, @Nullable Bundle clientState,
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/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
new file mode 100644
index 0000000..042bcbd
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
@@ -0,0 +1,55 @@
+/*
+ * 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.backup;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.provider.DeviceConfig;
+
+/**
+ * Retrieves values of feature flags.
+ *
+ * <p>These flags are intended to be configured server-side and their values should be set in {@link
+ * DeviceConfig} by a service that periodically syncs with the server.
+ *
+ * <p>This class must ensure that the namespace, flag name, and default value passed into {@link
+ * DeviceConfig} matches what's declared on the server. The namespace is shared for all backup and
+ * restore flags.
+ */
+public class BackupAndRestoreFeatureFlags {
+    private static final String NAMESPACE = "backup_and_restore";
+
+    private BackupAndRestoreFeatureFlags() {}
+
+    /** Retrieves the value of the flag "backup_transport_future_timeout_millis". */
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+    public static long getBackupTransportFutureTimeoutMillis() {
+        return DeviceConfig.getLong(
+                NAMESPACE,
+                /* name= */ "backup_transport_future_timeout_millis",
+                /* defaultValue= */ 600000); // 10 minutes
+    }
+
+    /** Retrieves the value of the flag "backup_transport_callback_timeout_millis". */
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+    public static long getBackupTransportCallbackTimeoutMillis() {
+        return DeviceConfig.getLong(
+                NAMESPACE,
+                /* name= */ "backup_transport_callback_timeout_millis",
+                /* defaultValue= */ 300000); // 5 minutes
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index 0fe90b1..f5d6836 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -9,7 +9,7 @@
 
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
-import android.app.backup.BackupManager;
+import android.app.backup.BackupAnnotations;
 import android.app.backup.FullBackup;
 import android.app.backup.FullBackupDataOutput;
 import android.app.backup.IBackupCallback;
@@ -148,7 +148,7 @@
         try {
             return mBackupManagerService.bindToAgentSynchronous(targetApp,
                     ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL,
-                    BackupManager.OperationType.BACKUP);
+                    BackupAnnotations.BackupDestination.CLOUD);
         } catch (SecurityException e) {
             Slog.e(TAG, "error in binding to agent for package " + targetApp.packageName
                     + ". " + e);
diff --git a/services/backup/java/com/android/server/backup/OperationStorage.java b/services/backup/java/com/android/server/backup/OperationStorage.java
index 466f647..8f73436 100644
--- a/services/backup/java/com/android/server/backup/OperationStorage.java
+++ b/services/backup/java/com/android/server/backup/OperationStorage.java
@@ -153,4 +153,4 @@
      * @return a set of operation tokens for operations in that state.
      */
     Set<Integer> operationTokensForOpState(@OpState int state);
-};
+}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 4cf63b3..ce3e628 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -46,8 +46,8 @@
 import android.app.IBackupAgent;
 import android.app.PendingIntent;
 import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.BackupManager;
-import android.app.backup.BackupManager.OperationType;
 import android.app.backup.BackupManagerMonitor;
 import android.app.backup.FullBackup;
 import android.app.backup.IBackupManager;
@@ -405,7 +405,7 @@
     private long mAncestralToken = 0;
     private long mCurrentToken = 0;
     @Nullable private File mAncestralSerialNumberFile;
-    @OperationType private volatile long mAncestralOperationType;
+    @BackupDestination private volatile long mAncestralBackupDestination;
 
     private final ContentObserver mSetupObserver;
     private final BroadcastReceiver mRunInitReceiver;
@@ -550,7 +550,7 @@
         mActivityManager = ActivityManager.getService();
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mScheduledBackupEligibility = getEligibilityRules(mPackageManager, userId,
-                OperationType.BACKUP);
+                BackupDestination.CLOUD);
 
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -844,8 +844,8 @@
         mAncestralToken = ancestralToken;
     }
 
-    public void setAncestralOperationType(@OperationType int operationType) {
-        mAncestralOperationType = operationType;
+    public void setAncestralBackupDestination(@BackupDestination int backupDestination) {
+        mAncestralBackupDestination = backupDestination;
     }
 
     public long getCurrentToken() {
@@ -1619,14 +1619,14 @@
     /** Fires off a backup agent, blocking until it attaches or times out. */
     @Nullable
     public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode,
-            @OperationType int operationType) {
+            @BackupDestination int backupDestination) {
         IBackupAgent agent = null;
         synchronized (mAgentConnectLock) {
             mConnecting = true;
             mConnectedAgent = null;
             try {
                 if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId,
-                        operationType)) {
+                        backupDestination)) {
                     Slog.d(TAG, addUserIdToLogMessage(mUserId, "awaiting agent for " + app));
 
                     // success; wait for the agent to arrive
@@ -1776,8 +1776,9 @@
     }
 
     private BackupEligibilityRules getEligibilityRulesForRestoreAtInstall(long restoreToken) {
-        if (mAncestralOperationType == OperationType.MIGRATION && restoreToken == mAncestralToken) {
-            return getEligibilityRulesForOperation(OperationType.MIGRATION);
+        if (mAncestralBackupDestination == BackupDestination.DEVICE_TRANSFER
+                && restoreToken == mAncestralToken) {
+            return getEligibilityRulesForOperation(BackupDestination.DEVICE_TRANSFER);
         } else {
             // If we're not using the ancestral data set, it means we're restoring from a backup
             // that happened on this device.
@@ -1856,14 +1857,14 @@
 
         final TransportConnection transportConnection;
         final String transportDirName;
-        int operationType;
+        int backupDestination;
         try {
             transportDirName =
                     mTransportManager.getTransportDirName(
                             mTransportManager.getCurrentTransportName());
             transportConnection =
                     mTransportManager.getCurrentTransportClientOrThrow("BMS.requestBackup()");
-            operationType = getOperationTypeFromTransport(transportConnection);
+            backupDestination = getBackupDestinationFromTransport(transportConnection);
         } catch (TransportNotRegisteredException | TransportNotAvailableException
                 | RemoteException e) {
             BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
@@ -1876,7 +1877,7 @@
         OnTaskFinishedListener listener =
                 caller -> mTransportManager.disposeOfTransportClient(transportConnection, caller);
         BackupEligibilityRules backupEligibilityRules = getEligibilityRulesForOperation(
-                operationType);
+                backupDestination);
 
         Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
         msg.obj = getRequestBackupParams(packages, observer, monitor, flags, backupEligibilityRules,
@@ -2373,7 +2374,7 @@
                             /* monitor */ null,
                             /* userInitiated */ false,
                             "BMS.beginFullBackup()",
-                            getEligibilityRulesForOperation(OperationType.BACKUP));
+                            getEligibilityRulesForOperation(BackupDestination.CLOUD));
                 } catch (IllegalStateException e) {
                     Slog.w(TAG, "Failed to start backup", e);
                     runBackup = false;
@@ -2835,7 +2836,7 @@
             Slog.i(TAG, addUserIdToLogMessage(mUserId, "Beginning adb backup..."));
 
             BackupEligibilityRules eligibilityRules = getEligibilityRulesForOperation(
-                    OperationType.ADB_BACKUP);
+                    BackupDestination.ADB_BACKUP);
             AdbBackupParams params = new AdbBackupParams(fd, includeApks, includeObbs,
                     includeShared, doWidgets, doAllApps, includeSystem, compress, doKeyValue,
                     pkgList, eligibilityRules);
@@ -2924,7 +2925,7 @@
                         /* monitor */ null,
                         /* userInitiated */ false,
                         "BMS.fullTransportBackup()",
-                        getEligibilityRulesForOperation(OperationType.BACKUP));
+                        getEligibilityRulesForOperation(BackupDestination.CLOUD));
                 // Acquiring wakelock for PerformFullTransportBackupTask before its start.
                 mWakelock.acquire();
                 (new Thread(task, "full-transport-master")).start();
@@ -3917,12 +3918,12 @@
             }
         }
 
-        int operationType;
+        int backupDestination;
         TransportConnection transportConnection = null;
         try {
             transportConnection = mTransportManager.getTransportClientOrThrow(
                     transport, /* caller */"BMS.beginRestoreSession");
-            operationType = getOperationTypeFromTransport(transportConnection);
+            backupDestination = getBackupDestinationFromTransport(transportConnection);
         } catch (TransportNotAvailableException | TransportNotRegisteredException
                 | RemoteException e) {
             Slog.w(TAG, "Failed to get operation type from transport: " + e);
@@ -3951,7 +3952,7 @@
                 return null;
             }
             mActiveRestoreSession = new ActiveRestoreSession(this, packageName, transport,
-                    getEligibilityRulesForOperation(operationType));
+                    getEligibilityRulesForOperation(backupDestination));
             mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT,
                     mAgentTimeoutParameters.getRestoreSessionTimeoutMillis());
         }
@@ -4037,14 +4038,14 @@
     }
 
     public BackupEligibilityRules getEligibilityRulesForOperation(
-            @OperationType int operationType) {
-        return getEligibilityRules(mPackageManager, mUserId, operationType);
+            @BackupDestination int backupDestination) {
+        return getEligibilityRules(mPackageManager, mUserId, backupDestination);
     }
 
     private static BackupEligibilityRules getEligibilityRules(PackageManager packageManager,
-            int userId, @OperationType int operationType) {
+            int userId, @BackupDestination int backupDestination) {
         return new BackupEligibilityRules(packageManager,
-                LocalServices.getService(PackageManagerInternal.class), userId, operationType);
+                LocalServices.getService(PackageManagerInternal.class), userId, backupDestination);
     }
 
     /** Prints service state for 'dumpsys backup'. */
@@ -4200,21 +4201,22 @@
     }
 
     @VisibleForTesting
-    @OperationType int getOperationTypeFromTransport(TransportConnection transportConnection)
+    @BackupDestination int getBackupDestinationFromTransport(
+            TransportConnection transportConnection)
             throws TransportNotAvailableException, RemoteException {
         if (!shouldUseNewBackupEligibilityRules()) {
             // Return the default to stick to the legacy behaviour.
-            return OperationType.BACKUP;
+            return BackupDestination.CLOUD;
         }
 
         final long oldCallingId = Binder.clearCallingIdentity();
         try {
             BackupTransportClient transport = transportConnection.connectOrThrow(
-                    /* caller */ "BMS.getOperationTypeFromTransport");
+                    /* caller */ "BMS.getBackupDestinationFromTransport");
             if ((transport.getTransportFlags() & BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER) != 0) {
-                return OperationType.MIGRATION;
+                return BackupDestination.DEVICE_TRANSFER;
             } else {
-                return OperationType.BACKUP;
+                return BackupDestination.CLOUD;
             }
         } finally {
             Binder.restoreCallingIdentity(oldCallingId);
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index 1e1ca95..65682f4 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -23,11 +23,13 @@
 import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
 import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
 
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
 import android.app.backup.BackupTransport;
 import android.app.backup.FullBackupDataOutput;
+import android.app.backup.IBackupManagerMonitor;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -42,6 +44,7 @@
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.remote.RemoteCall;
 import com.android.server.backup.utils.BackupEligibilityRules;
+import com.android.server.backup.utils.BackupManagerMonitorUtils;
 import com.android.server.backup.utils.FullBackupUtils;
 
 import java.io.File;
@@ -60,12 +63,13 @@
     private BackupRestoreTask mTimeoutMonitor;
     private IBackupAgent mAgent;
     private boolean mIncludeApks;
-    private PackageInfo mPkg;
+    private final PackageInfo mPkg;
     private final long mQuota;
     private final int mOpToken;
     private final int mTransportFlags;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
     private final BackupEligibilityRules mBackupEligibilityRules;
+    @Nullable private final IBackupManagerMonitor mMonitor;
 
     class FullBackupRunner implements Runnable {
         private final @UserIdInt int mUserId;
@@ -193,7 +197,8 @@
             long quota,
             int opToken,
             int transportFlags,
-            BackupEligibilityRules backupEligibilityRules) {
+            BackupEligibilityRules backupEligibilityRules,
+            IBackupManagerMonitor monitor) {
         this.backupManagerService = backupManagerService;
         mOutput = output;
         mPreflightHook = preflightHook;
@@ -208,6 +213,7 @@
                         backupManagerService.getAgentTimeoutParameters(),
                         "Timeout parameters cannot be null");
         mBackupEligibilityRules = backupEligibilityRules;
+        mMonitor = monitor;
     }
 
     public int preflightCheck() throws RemoteException {
@@ -260,6 +266,8 @@
                     }
                     result = BackupTransport.TRANSPORT_OK;
                 }
+
+                BackupManagerMonitorUtils.monitorAgentLoggingResults(mMonitor, mPkg, mAgent);
             } catch (IOException e) {
                 Slog.e(TAG, "Error backing up " + mPkg.packageName + ": " + e.getMessage());
                 result = BackupTransport.AGENT_ERROR;
@@ -307,7 +315,7 @@
             mAgent =
                     backupManagerService.bindToAgentSynchronous(
                             mPkg.applicationInfo, ApplicationThreadConstants.BACKUP_MODE_FULL,
-                            mBackupEligibilityRules.getOperationType());
+                            mBackupEligibilityRules.getBackupDestination());
         }
         return mAgent != null;
     }
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index ec58e17..cba1e29 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -420,7 +420,8 @@
                                 Long.MAX_VALUE,
                                 mCurrentOpToken,
                                 /*transportFlags=*/ 0,
-                                mBackupEligibilityRules);
+                                mBackupEligibilityRules,
+                                /* monitor= */ null);
                 sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
 
                 // Don't need to check preflight result as there is no preflight hook.
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index f0492a8..78df304 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -882,7 +882,8 @@
                             mQuota,
                             mCurrentOpToken,
                             mTransportFlags,
-                            mBackupEligibilityRules);
+                            mBackupEligibilityRules,
+                            mMonitor);
             try {
                 try {
                     if (!mIsCancelled) {
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 95cc289..3ff6ba7 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -20,7 +20,7 @@
 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
 import static com.android.server.backup.BackupManagerService.TAG;
 
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.RestoreSet;
 import android.os.Handler;
@@ -240,7 +240,7 @@
                                 /* userInitiated */ false,
                                 /* nonIncremental */ false,
                                 backupManagerService.getEligibilityRulesForOperation(
-                                        OperationType.BACKUP));
+                                        BackupDestination.CLOUD));
                     } catch (Exception e) {
                         // unable to ask the transport its dir name -- transient failure, since
                         // the above check succeeded.  Try again next time.
diff --git a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
index 6908c60..a94167e 100644
--- a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
+++ b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
@@ -353,4 +353,4 @@
             op.callback.handleCancel(cancelAll);
         }
     }
-};
+}
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 16aa4eb..ca92b69 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -68,6 +68,7 @@
 import com.android.server.backup.transport.TransportConnection;
 import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.utils.BackupEligibilityRules;
+import com.android.server.backup.utils.BackupManagerMonitorUtils;
 
 import libcore.io.IoUtils;
 
@@ -697,6 +698,8 @@
 
         try {
             extractAgentData(mCurrentPackage);
+            BackupManagerMonitorUtils.monitorAgentLoggingResults(
+                    mReporter.getMonitor(), mCurrentPackage, mAgent);
             int status = sendDataToTransport(mCurrentPackage);
             cleanUpAgentForTransportStatus(status);
         } catch (AgentException | TaskException e) {
@@ -738,7 +741,7 @@
             agent =
                     mBackupManagerService.bindToAgentSynchronous(
                             packageInfo.applicationInfo, BACKUP_MODE_INCREMENTAL,
-                            mBackupEligibilityRules.getOperationType());
+                            mBackupEligibilityRules.getBackupDestination());
             if (agent == null) {
                 mReporter.onAgentError(packageName);
                 throw AgentException.transitory();
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index 8b1d561..d3e4f13 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -16,8 +16,6 @@
 
 package com.android.server.backup.restore;
 
-import static android.app.backup.BackupManager.OperationType;
-
 import static com.android.server.backup.BackupManagerService.DEBUG;
 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
 import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSION_TIMEOUT;
@@ -26,6 +24,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IRestoreObserver;
 import android.app.backup.IRestoreSession;
@@ -299,9 +298,9 @@
     private BackupEligibilityRules getBackupEligibilityRules(RestoreSet restoreSet) {
         // TODO(b/182986784): Remove device name comparison once a designated field for operation
         //  type is added to RestoreSet object.
-        int operationType = DEVICE_NAME_FOR_D2D_SET.equals(restoreSet.device)
-                ? OperationType.MIGRATION : OperationType.BACKUP;
-        return mBackupManagerService.getEligibilityRulesForOperation(operationType);
+        int backupDestination = DEVICE_NAME_FOR_D2D_SET.equals(restoreSet.device)
+                ? BackupDestination.DEVICE_TRANSFER : BackupDestination.CLOUD;
+        return mBackupManagerService.getEligibilityRulesForOperation(backupDestination);
     }
 
     public synchronized int restorePackage(String packageName, IRestoreObserver observer,
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index e78c8d1..b042c30 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -28,6 +28,7 @@
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
 import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations;
 import android.app.backup.BackupManager;
 import android.app.backup.FullBackup;
 import android.app.backup.IBackupManagerMonitor;
@@ -398,7 +399,7 @@
                                     FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)
                                             ? ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
                                             : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL,
-                                    mBackupEligibilityRules.getOperationType());
+                                    mBackupEligibilityRules.getBackupDestination());
                             mAgentPackage = pkg;
                         } catch (IOException | NameNotFoundException e) {
                             // fall through to error handling
@@ -707,7 +708,8 @@
     }
 
     private boolean isRestorableFile(FileMetadata info) {
-        if (mBackupEligibilityRules.getOperationType() == BackupManager.OperationType.MIGRATION) {
+        if (mBackupEligibilityRules.getBackupDestination()
+                == BackupAnnotations.BackupDestination.DEVICE_TRANSFER) {
             // Everything is eligible for device-to-device migration.
             return true;
         }
diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
index 22af19e..515a172 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -24,7 +24,7 @@
 import static com.android.server.backup.UserBackupManagerService.BACKUP_FILE_HEADER_MAGIC;
 import static com.android.server.backup.UserBackupManagerService.BACKUP_FILE_VERSION;
 
-import android.app.backup.BackupManager;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.IFullBackupRestoreObserver;
 import android.content.pm.PackageManagerInternal;
 import android.os.ParcelFileDescriptor;
@@ -112,7 +112,7 @@
             BackupEligibilityRules eligibilityRules = new BackupEligibilityRules(
                     mBackupManagerService.getPackageManager(),
                     LocalServices.getService(PackageManagerInternal.class),
-                    mBackupManagerService.getUserId(), BackupManager.OperationType.ADB_BACKUP);
+                    mBackupManagerService.getUserId(), BackupDestination.ADB_BACKUP);
             FullRestoreEngine mEngine = new FullRestoreEngine(mBackupManagerService,
                     mOperationStorage, null, mObserver, null, null,
                     true, 0 /*unused*/, true, eligibilityRules);
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index b48367d..18e28de 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -405,6 +405,12 @@
             BackupTransportClient transport =
                     mTransportConnection.connectOrThrow("PerformUnifiedRestoreTask.startRestore()");
 
+            // If the requester of the restore has not passed in a monitor, we ask the transport
+            // for one.
+            if (mMonitor == null) {
+                mMonitor = transport.getBackupManagerMonitor();
+            }
+
             mStatus = transport.startRestore(mToken, packages);
             if (mStatus != BackupTransport.TRANSPORT_OK) {
                 Slog.e(TAG, "Transport error " + mStatus + "; no restore possible");
@@ -669,7 +675,7 @@
         mAgent = backupManagerService.bindToAgentSynchronous(
                 mCurrentPackage.applicationInfo,
                 ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL,
-                mBackupEligibilityRules.getOperationType());
+                mBackupEligibilityRules.getBackupDestination());
         if (mAgent == null) {
             Slog.w(TAG, "Can't find backup agent for " + packageName);
             mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
@@ -885,6 +891,10 @@
                             OpType.RESTORE_WAIT);
             mAgent.doRestoreFinished(mEphemeralOpToken,
                     backupManagerService.getBackupManagerBinder());
+
+            // Ask the agent for logs after doRestoreFinished() to allow it to finalize its logs.
+            BackupManagerMonitorUtils.monitorAgentLoggingResults(mMonitor, mCurrentPackage, mAgent);
+
             // If we get this far, the callback or timeout will schedule the
             // next restore state, so we're done
         } catch (Exception e) {
@@ -1150,8 +1160,8 @@
         if (mIsSystemRestore && mPmAgent != null) {
             backupManagerService.setAncestralPackages(mPmAgent.getRestoredPackages());
             backupManagerService.setAncestralToken(mToken);
-            backupManagerService.setAncestralOperationType(
-                    mBackupEligibilityRules.getOperationType());
+            backupManagerService.setAncestralBackupDestination(
+                    mBackupEligibilityRules.getBackupDestination());
             backupManagerService.writeRestoreTokens();
         }
 
diff --git a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
index 40d7cad..21005bb 100644
--- a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -30,6 +30,7 @@
 
 import com.android.internal.backup.IBackupTransport;
 import com.android.internal.infra.AndroidFuture;
+import com.android.server.backup.BackupAndRestoreFeatureFlags;
 
 import java.util.ArrayDeque;
 import java.util.HashSet;
@@ -385,7 +386,8 @@
 
     private <T> T getFutureResult(AndroidFuture<T> future) {
         try {
-            return future.get(600, TimeUnit.SECONDS);
+            return future.get(BackupAndRestoreFeatureFlags.getBackupTransportFutureTimeoutMillis(),
+                    TimeUnit.MILLISECONDS);
         } catch (InterruptedException | ExecutionException | TimeoutException
                  | CancellationException e) {
             Slog.w(TAG, "Failed to get result from transport:", e);
diff --git a/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java b/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java
index fb98825..deaa86c 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java
@@ -23,13 +23,13 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.backup.ITransportStatusCallback;
+import com.android.server.backup.BackupAndRestoreFeatureFlags;
 
 public class TransportStatusCallback extends ITransportStatusCallback.Stub {
     private static final String TAG = "TransportStatusCallback";
-    private static final int TIMEOUT_MILLIS = 300 * 1000; // 5 minutes.
     private static final int OPERATION_STATUS_DEFAULT = 0;
 
-    private final int mOperationTimeout;
+    private final long mOperationTimeout;
 
     @GuardedBy("this")
     private int mOperationStatus = OPERATION_STATUS_DEFAULT;
@@ -37,7 +37,7 @@
     private boolean mHasCompletedOperation = false;
 
     public TransportStatusCallback() {
-        mOperationTimeout = TIMEOUT_MILLIS;
+        mOperationTimeout = BackupAndRestoreFeatureFlags.getBackupTransportCallbackTimeoutMillis();
     }
 
     @VisibleForTesting
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index d0300ff..7f0b56f 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -23,7 +23,7 @@
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import android.annotation.Nullable;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.BackupTransport;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
@@ -61,7 +61,7 @@
     private final PackageManager mPackageManager;
     private final PackageManagerInternal mPackageManagerInternal;
     private final int mUserId;
-    @OperationType  private final int mOperationType;
+    @BackupDestination  private final int mBackupDestination;
 
     /**
      * When  this change is enabled, {@code adb backup}  is automatically turned on for apps
@@ -85,17 +85,17 @@
             PackageManagerInternal packageManagerInternal,
             int userId) {
         return new BackupEligibilityRules(packageManager, packageManagerInternal, userId,
-                OperationType.BACKUP);
+                BackupDestination.CLOUD);
     }
 
     public BackupEligibilityRules(PackageManager packageManager,
             PackageManagerInternal packageManagerInternal,
             int userId,
-            @OperationType int operationType) {
+            @BackupDestination int backupDestination) {
         mPackageManager = packageManager;
         mPackageManagerInternal = packageManagerInternal;
         mUserId = userId;
-        mOperationType = operationType;
+        mBackupDestination = backupDestination;
     }
 
     /**
@@ -111,7 +111,7 @@
      * </ol>
      *
      * However, the above eligibility rules are ignored for non-system apps in in case of
-     * device-to-device migration, see {@link OperationType}.
+     * device-to-device migration, see {@link BackupDestination}.
      */
     @VisibleForTesting
     public boolean appIsEligibleForBackup(ApplicationInfo app) {
@@ -152,22 +152,22 @@
     /**
     * Check if this app allows backup. Apps can opt out of backup by stating
     * android:allowBackup="false" in their manifest. However, this flag is ignored for non-system
-    * apps during device-to-device migrations, see {@link OperationType}.
+    * apps during device-to-device migrations, see {@link BackupDestination}.
     *
     * @param app The app under check.
     * @return boolean indicating whether backup is allowed.
     */
     public boolean isAppBackupAllowed(ApplicationInfo app) {
         boolean allowBackup = (app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
-        switch (mOperationType) {
-            case OperationType.MIGRATION:
+        switch (mBackupDestination) {
+            case BackupDestination.DEVICE_TRANSFER:
                 // Backup / restore of all non-system apps is force allowed during
                 // device-to-device migration.
                 boolean isSystemApp = (app.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
                 boolean ignoreAllowBackup = !isSystemApp && CompatChanges.isChangeEnabled(
                         IGNORE_ALLOW_BACKUP_IN_D2D, app.packageName, UserHandle.of(mUserId));
                 return ignoreAllowBackup || allowBackup;
-            case OperationType.ADB_BACKUP:
+            case BackupDestination.ADB_BACKUP:
                 String packageName = app.packageName;
                 if (packageName == null) {
                     Slog.w(TAG, "Invalid ApplicationInfo object");
@@ -207,10 +207,10 @@
                     // All other apps can use adb backup only when running in debuggable mode.
                     return isDebuggable;
                 }
-            case OperationType.BACKUP:
+            case BackupDestination.CLOUD:
                 return allowBackup;
             default:
-                Slog.w(TAG, "Unknown operation type:" + mOperationType);
+                Slog.w(TAG, "Unknown operation type:" + mBackupDestination);
                 return false;
         }
     }
@@ -398,7 +398,7 @@
         }
     }
 
-    public int getOperationType() {
-        return mOperationType;
+    public int getBackupDestination() {
+        return mBackupDestination;
     }
 }
diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
index 6f08376..8eda5b9 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
@@ -16,24 +16,43 @@
 
 package com.android.server.backup.utils;
 
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS;
 import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS;
 
 import static com.android.server.backup.BackupManagerService.DEBUG;
 import static com.android.server.backup.BackupManagerService.TAG;
 
 import android.annotation.Nullable;
+import android.app.IBackupAgent;
 import android.app.backup.BackupManagerMonitor;
+import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.IBackupManagerMonitor;
 import android.content.pm.PackageInfo;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.internal.infra.AndroidFuture;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
 /**
  * Utility methods to communicate with BackupManagerMonitor.
  */
 public class BackupManagerMonitorUtils {
     /**
+     * Timeout for how long we wait before we give up on getting logs from a {@link IBackupAgent}.
+     * We expect this to be very fast since the agent immediately returns whatever logs have been
+     * accumulated. The timeout adds a bit more security and ensures we don't hang the B&R waiting
+     * for non-essential logs.
+     */
+    private static final int AGENT_LOGGER_RESULTS_TIMEOUT_MILLIS = 500;
+
+    /**
      * Notifies monitor about the event.
      *
      * Calls {@link IBackupManagerMonitor#onEvent(Bundle)} with a bundle representing current event.
@@ -80,6 +99,48 @@
     }
 
     /**
+     * Extracts logging results from the provided {@code agent} and notifies the {@code monitor}
+     * about them.
+     *
+     * <p>Note that this method does two separate binder calls (one to the agent and one to the
+     * monitor).
+     *
+     * @param monitor - implementation of {@link IBackupManagerMonitor} to notify.
+     * @param pkg - package the {@code agent} belongs to.
+     * @param agent - the {@link IBackupAgent} to retrieve logs from.
+     * @return {@code null} if the monitor is null. {@code monitor} if we fail to retrieve the logs
+     *     from the {@code agent}. Otherwise, the result of {@link
+     *     #monitorEvent(IBackupManagerMonitor, int, PackageInfo, int, Bundle)}.
+     */
+    public static IBackupManagerMonitor monitorAgentLoggingResults(
+            @Nullable IBackupManagerMonitor monitor, PackageInfo pkg, IBackupAgent agent) {
+        if (monitor == null) {
+            return null;
+        }
+
+        try {
+            AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> resultsFuture =
+                    new AndroidFuture<>();
+            agent.getLoggerResults(resultsFuture);
+            Bundle loggerResultsBundle = new Bundle();
+            loggerResultsBundle.putParcelableList(
+                    EXTRA_LOG_AGENT_LOGGING_RESULTS,
+                    resultsFuture.get(AGENT_LOGGER_RESULTS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
+            return BackupManagerMonitorUtils.monitorEvent(
+                    monitor,
+                    LOG_EVENT_ID_AGENT_LOGGING_RESULTS,
+                    pkg,
+                    LOG_EVENT_CATEGORY_AGENT,
+                    loggerResultsBundle);
+        } catch (TimeoutException e) {
+            Slog.w(TAG, "Timeout while waiting to retrieve logging results from agent", e);
+        } catch (Exception e) {
+            Slog.w(TAG, "Failed to retrieve logging results from agent", e);
+        }
+        return monitor;
+    }
+
+    /**
      * Adds given key-value pair in the bundle and returns the bundle. If bundle was null it will
      * be created.
      *
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index a614b72..b04f3c5 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -20,6 +20,8 @@
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
 import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME;
+import static android.companion.CompanionDeviceManager.REASON_INTERNAL_ERROR;
+import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
 import static android.content.ComponentName.createRelative;
 
 import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
@@ -40,7 +42,6 @@
 import android.companion.AssociatedDevice;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
-import android.companion.CompanionDeviceManager;
 import android.companion.IAssociationRequestCallback;
 import android.content.ComponentName;
 import android.content.Context;
@@ -348,8 +349,7 @@
             // Send the association back via the app's callback
             if (callback != null) {
                 try {
-                    // TODO: update to INTERNAL_ERROR once it's added.
-                    callback.onFailure(CompanionDeviceManager.REASON_CANCELED);
+                    callback.onFailure(REASON_INTERNAL_ERROR);
                 } catch (RemoteException ignore) {
                 }
             }
@@ -358,7 +358,7 @@
             // back to the app via Activity.setResult().
             if (resultReceiver != null) {
                 final Bundle data = new Bundle();
-                resultReceiver.send(CompanionDeviceManager.RESULT_INTERNAL_ERROR, data);
+                resultReceiver.send(RESULT_INTERNAL_ERROR, data);
             }
         }
     }
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 000bafe..5e68d52 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -16,8 +16,7 @@
 
 package com.android.server.companion.virtual;
 
-import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.companion.virtual.VirtualDeviceParams.RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS;
 import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -26,10 +25,10 @@
 import android.annotation.Nullable;
 import android.app.WindowConfiguration;
 import android.app.compat.CompatChanges;
-import android.companion.AssociationRequest;
 import android.companion.virtual.VirtualDeviceManager.ActivityListener;
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.VirtualDeviceParams.ActivityPolicy;
+import android.companion.virtual.VirtualDeviceParams.RecentsPolicy;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.ComponentName;
@@ -86,6 +85,15 @@
     }
 
     /**
+     * For communicating when activities are blocked from entering PIP on the display by this
+     * policy controller.
+     */
+    public interface PipBlockedCallback {
+        /** Called when an activity is blocked from entering PIP. */
+        void onEnteringPipBlocked(int uid);
+    }
+
+    /**
      * If required, allow the secure activity to display on remote device since
      * {@link android.os.Build.VERSION_CODES#TIRAMISU}.
      */
@@ -112,14 +120,16 @@
     @GuardedBy("mGenericWindowPolicyControllerLock")
     final ArraySet<Integer> mRunningUids = new ArraySet<>();
     @Nullable private final ActivityListener mActivityListener;
+    @Nullable private final PipBlockedCallback mPipBlockedCallback;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     @NonNull
     @GuardedBy("mGenericWindowPolicyControllerLock")
     private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListeners =
             new ArraySet<>();
-    @Nullable
-    private final @AssociationRequest.DeviceProfile String mDeviceProfile;
     @Nullable private final SecureWindowCallback mSecureWindowCallback;
+    @Nullable private final List<String> mDisplayCategories;
+    @RecentsPolicy
+    private final int mDefaultRecentsPolicy;
 
     /**
      * Creates a window policy controller that is generic to the different use cases of virtual
@@ -145,7 +155,7 @@
      *   launching.
      * @param secureWindowCallback Callback that is called when a secure window shows on the
      *   virtual display.
-     * @param deviceProfile The {@link AssociationRequest.DeviceProfile} of this virtual device.
+     * @param defaultRecentsPolicy a policy to indicate how to handle activities in recents.
      */
     public GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
             @NonNull ArraySet<UserHandle> allowedUsers,
@@ -155,9 +165,11 @@
             @NonNull Set<ComponentName> blockedActivities,
             @ActivityPolicy int defaultActivityPolicy,
             @NonNull ActivityListener activityListener,
+            @NonNull PipBlockedCallback pipBlockedCallback,
             @NonNull ActivityBlockedCallback activityBlockedCallback,
             @NonNull SecureWindowCallback secureWindowCallback,
-            @AssociationRequest.DeviceProfile String deviceProfile) {
+            @NonNull List<String> displayCategories,
+            @RecentsPolicy int defaultRecentsPolicy) {
         super();
         mAllowedUsers = allowedUsers;
         mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations);
@@ -168,8 +180,10 @@
         mActivityBlockedCallback = activityBlockedCallback;
         setInterestedWindowFlags(windowFlags, systemWindowFlags);
         mActivityListener = activityListener;
-        mDeviceProfile = deviceProfile;
+        mPipBlockedCallback = pipBlockedCallback;
         mSecureWindowCallback = secureWindowCallback;
+        mDisplayCategories = displayCategories;
+        mDefaultRecentsPolicy = defaultRecentsPolicy;
     }
 
     /**
@@ -303,18 +317,19 @@
     }
 
     @Override
-    public boolean canShowTasksInRecents() {
-        if (mDeviceProfile == null) {
+    public boolean canShowTasksInHostDeviceRecents() {
+        return (mDefaultRecentsPolicy & RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS) != 0;
+    }
+
+    @Override
+    public boolean isEnteringPipAllowed(int uid) {
+        if (super.isEnteringPipAllowed(uid)) {
             return true;
         }
-       // TODO(b/234075973) : Remove this once proper API is ready.
-        switch (mDeviceProfile) {
-            case DEVICE_PROFILE_AUTOMOTIVE_PROJECTION:
-                return false;
-            case DEVICE_PROFILE_APP_STREAMING:
-            default:
-                return true;
-        }
+        mHandler.post(() -> {
+            mPipBlockedCallback.onEnteringPipBlocked(uid);
+        });
+        return false;
     }
 
     /**
@@ -327,6 +342,15 @@
         }
     }
 
+    private boolean activityMatchesDisplayCategory(ActivityInfo activityInfo) {
+        if (mDisplayCategories.isEmpty()) {
+            return activityInfo.requiredDisplayCategory == null;
+        }
+        return activityInfo.requiredDisplayCategory != null
+                    && mDisplayCategories.contains(activityInfo.requiredDisplayCategory);
+
+    }
+
     private boolean canContainActivity(ActivityInfo activityInfo, int windowFlags,
             int systemWindowFlags) {
         if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
@@ -334,9 +358,17 @@
         }
         ComponentName activityComponent = activityInfo.getComponentName();
         if (BLOCKED_APP_STREAMING_COMPONENT.equals(activityComponent)) {
-            // The error dialog alerting users that streaming is blocked is always allowed.
+            // The error dialog alerting users that streaming is blocked is always allowed. Need to
+            // run before the clauses below to ensure error dialog always shows up.
             return true;
         }
+        if (!activityMatchesDisplayCategory(activityInfo)) {
+            Slog.d(TAG, String.format(
+                    "The activity's required display category: %s is not found on virtual display"
+                            + " with the following categories: %s",
+                    activityInfo.requiredDisplayCategory, mDisplayCategories.toString()));
+            return false;
+        }
         final UserHandle activityUser =
                 UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid);
         if (!mAllowedUsers.contains(activityUser)) {
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 02053cc..0cea3d0 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.StringDef;
-import android.graphics.Point;
 import android.graphics.PointF;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputDeviceIdentifier;
@@ -79,9 +78,8 @@
     final Object mLock;
 
     /* Token -> file descriptor associations. */
-    @VisibleForTesting
     @GuardedBy("mLock")
-    final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
+    private final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
 
     private final Handler mHandler;
     private final NativeWrapper mNativeWrapper;
@@ -178,13 +176,14 @@
             int productId,
             @NonNull IBinder deviceToken,
             int displayId,
-            @NonNull Point screenSize) {
+            int height,
+            int width) {
         final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
         try {
             createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId,
                     productId, deviceToken, displayId, phys,
                     () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
-                            phys, screenSize.y, screenSize.x));
+                            phys, height, width));
         } catch (DeviceCreationException e) {
             throw new RuntimeException(
                     "Failed to create virtual touchscreen device '" + deviceName + "'.", e);
@@ -414,17 +413,25 @@
     }
 
     @VisibleForTesting
-    void addDeviceForTesting(IBinder deviceToken, int fd, int type, int displayId,
-            String phys, int inputDeviceId) {
+    void addDeviceForTesting(IBinder deviceToken, int fd, int type, int displayId, String phys,
+            int inputDeviceId) {
         synchronized (mLock) {
-            mInputDeviceDescriptors.put(deviceToken,
-                    new InputDeviceDescriptor(fd, () -> {}, type, displayId, phys,
-                            inputDeviceId));
+            mInputDeviceDescriptors.put(deviceToken, new InputDeviceDescriptor(fd, () -> {
+            }, type, displayId, phys, inputDeviceId));
         }
     }
 
-    private static native int nativeOpenUinputDpad(String deviceName, int vendorId,
-            int productId, String phys);
+    @VisibleForTesting
+    Map<IBinder, InputDeviceDescriptor> getInputDeviceDescriptors() {
+        final Map<IBinder, InputDeviceDescriptor> inputDeviceDescriptors = new ArrayMap<>();
+        synchronized (mLock) {
+            inputDeviceDescriptors.putAll(mInputDeviceDescriptors);
+        }
+        return inputDeviceDescriptors;
+    }
+
+    private static native int nativeOpenUinputDpad(String deviceName, int vendorId, int productId,
+            String phys);
     private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId,
             int productId, String phys);
     private static native int nativeOpenUinputMouse(String deviceName, int vendorId, int productId,
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
new file mode 100644
index 0000000..ec7e993
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -0,0 +1,235 @@
+/*
+ * 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.companion.virtual;
+
+import android.annotation.NonNull;
+import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.sensors.SensorManagerInternal;
+
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+
+/** Controls virtual sensors, including their lifecycle and sensor event dispatch. */
+public class SensorController {
+
+    private static final String TAG = "SensorController";
+
+    private final Object mLock;
+    private final int mVirtualDeviceId;
+    @GuardedBy("mLock")
+    private final Map<IBinder, SensorDescriptor> mSensorDescriptors = new ArrayMap<>();
+
+    private final SensorManagerInternal mSensorManagerInternal;
+
+    public SensorController(@NonNull Object lock, int virtualDeviceId) {
+        mLock = lock;
+        mVirtualDeviceId = virtualDeviceId;
+        mSensorManagerInternal = LocalServices.getService(SensorManagerInternal.class);
+    }
+
+    void close() {
+        synchronized (mLock) {
+            final Iterator<Map.Entry<IBinder, SensorDescriptor>> iterator =
+                    mSensorDescriptors.entrySet().iterator();
+            if (iterator.hasNext()) {
+                final Map.Entry<IBinder, SensorDescriptor> entry = iterator.next();
+                final IBinder token = entry.getKey();
+                final SensorDescriptor sensorDescriptor = entry.getValue();
+                iterator.remove();
+                closeSensorDescriptorLocked(token, sensorDescriptor);
+            }
+        }
+    }
+
+    void createSensor(@NonNull IBinder deviceToken, @NonNull VirtualSensorConfig config) {
+        Objects.requireNonNull(deviceToken);
+        Objects.requireNonNull(config);
+        try {
+            createSensorInternal(deviceToken, config);
+        } catch (SensorCreationException e) {
+            throw new RuntimeException(
+                    "Failed to create virtual sensor '" + config.getName() + "'.", e);
+        }
+    }
+
+    private void createSensorInternal(IBinder deviceToken, VirtualSensorConfig config)
+            throws SensorCreationException {
+        final SensorManagerInternal.RuntimeSensorStateChangeCallback runtimeSensorCallback =
+                (enabled, samplingPeriodMicros, batchReportLatencyMicros) -> {
+                    IVirtualSensorStateChangeCallback callback = config.getStateChangeCallback();
+                    if (callback != null) {
+                        try {
+                            callback.onStateChanged(
+                                    enabled, samplingPeriodMicros, batchReportLatencyMicros);
+                        } catch (RemoteException e) {
+                            throw new RuntimeException("Failed to call sensor callback.", e);
+                        }
+                    }
+                };
+
+        final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId,
+                config.getType(), config.getName(),
+                config.getVendor() == null ? "" : config.getVendor(),
+                runtimeSensorCallback);
+        if (handle <= 0) {
+            throw new SensorCreationException("Received an invalid virtual sensor handle.");
+        }
+
+        // The handle is valid from here, so ensure that all failures clean it up.
+        final BinderDeathRecipient binderDeathRecipient;
+        try {
+            binderDeathRecipient = new BinderDeathRecipient(deviceToken);
+            deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
+        } catch (RemoteException e) {
+            mSensorManagerInternal.removeRuntimeSensor(handle);
+            throw new SensorCreationException("Client died before sensor could be created.", e);
+        }
+
+        synchronized (mLock) {
+            SensorDescriptor sensorDescriptor = new SensorDescriptor(
+                    handle, config.getType(), config.getName(), binderDeathRecipient);
+            mSensorDescriptors.put(deviceToken, sensorDescriptor);
+        }
+    }
+
+    boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
+        Objects.requireNonNull(token);
+        Objects.requireNonNull(event);
+        synchronized (mLock) {
+            final SensorDescriptor sensorDescriptor = mSensorDescriptors.get(token);
+            if (sensorDescriptor == null) {
+                throw new IllegalArgumentException("Could not send sensor event for given token");
+            }
+            return mSensorManagerInternal.sendSensorEvent(
+                    sensorDescriptor.getHandle(), sensorDescriptor.getType(),
+                    event.getTimestampNanos(), event.getValues());
+        }
+    }
+
+    void unregisterSensor(@NonNull IBinder token) {
+        Objects.requireNonNull(token);
+        synchronized (mLock) {
+            final SensorDescriptor sensorDescriptor = mSensorDescriptors.remove(token);
+            if (sensorDescriptor == null) {
+                throw new IllegalArgumentException("Could not unregister sensor for given token");
+            }
+            closeSensorDescriptorLocked(token, sensorDescriptor);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void closeSensorDescriptorLocked(IBinder token, SensorDescriptor sensorDescriptor) {
+        token.unlinkToDeath(sensorDescriptor.getDeathRecipient(), /* flags= */ 0);
+        final int handle = sensorDescriptor.getHandle();
+        mSensorManagerInternal.removeRuntimeSensor(handle);
+    }
+
+
+    void dump(@NonNull PrintWriter fout) {
+        fout.println("    SensorController: ");
+        synchronized (mLock) {
+            fout.println("      Active descriptors: ");
+            for (SensorDescriptor sensorDescriptor : mSensorDescriptors.values()) {
+                fout.println("        handle: " + sensorDescriptor.getHandle());
+                fout.println("          type: " + sensorDescriptor.getType());
+                fout.println("          name: " + sensorDescriptor.getName());
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void addSensorForTesting(IBinder deviceToken, int handle, int type, String name) {
+        synchronized (mLock) {
+            mSensorDescriptors.put(deviceToken,
+                    new SensorDescriptor(handle, type, name, () -> {}));
+        }
+    }
+
+    @VisibleForTesting
+    Map<IBinder, SensorDescriptor> getSensorDescriptors() {
+        synchronized (mLock) {
+            return mSensorDescriptors;
+        }
+    }
+
+    @VisibleForTesting
+    static final class SensorDescriptor {
+
+        private final int mHandle;
+        private final IBinder.DeathRecipient mDeathRecipient;
+        private final int mType;
+        private final String mName;
+
+        SensorDescriptor(int handle, int type, String name, IBinder.DeathRecipient deathRecipient) {
+            mHandle = handle;
+            mDeathRecipient = deathRecipient;
+            mType = type;
+            mName = name;
+        }
+        public int getHandle() {
+            return mHandle;
+        }
+        public int getType() {
+            return mType;
+        }
+        public String getName() {
+            return mName;
+        }
+        public IBinder.DeathRecipient getDeathRecipient() {
+            return mDeathRecipient;
+        }
+    }
+
+    private final class BinderDeathRecipient implements IBinder.DeathRecipient {
+        private final IBinder mDeviceToken;
+
+        BinderDeathRecipient(IBinder deviceToken) {
+            mDeviceToken = deviceToken;
+        }
+
+        @Override
+        public void binderDied() {
+            // All callers are expected to call {@link VirtualDevice#unregisterSensor} before
+            // quitting, which removes this death recipient. If this is invoked, the remote end
+            // died, or they disposed of the object without properly unregistering.
+            Slog.e(TAG, "Virtual sensor controller binder died");
+            unregisterSensor(mDeviceToken);
+        }
+    }
+
+    /** An internal exception that is thrown to indicate an error when opening a virtual sensor. */
+    private static class SensorCreationException extends Exception {
+        SensorCreationException(String message) {
+            super(message);
+        }
+        SensorCreationException(String message, Exception cause) {
+            super(message, cause);
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 5ebbf07..5819861 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -38,18 +38,23 @@
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
-import android.graphics.Point;
 import android.graphics.PointF;
 import android.hardware.display.DisplayManager;
+import android.hardware.input.VirtualDpadConfig;
 import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualKeyboardConfig;
 import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseConfig;
 import android.hardware.input.VirtualMouseRelativeEvent;
 import android.hardware.input.VirtualMouseScrollEvent;
 import android.hardware.input.VirtualTouchEvent;
+import android.hardware.input.VirtualTouchscreenConfig;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Looper;
@@ -74,7 +79,9 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -97,6 +104,7 @@
     private final int mOwnerUid;
     private final int mDeviceId;
     private final InputController mInputController;
+    private final SensorController mSensorController;
     private VirtualAudioController mVirtualAudioController;
     @VisibleForTesting
     final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
@@ -159,6 +167,7 @@
                 ownerUid,
                 deviceId,
                 /* inputController= */ null,
+                /* sensorController= */ null,
                 listener,
                 pendingTrampolineCallback,
                 activityListener,
@@ -174,6 +183,7 @@
             int ownerUid,
             int deviceId,
             InputController inputController,
+            SensorController sensorController,
             OnDeviceCloseListener listener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
@@ -197,6 +207,11 @@
         } else {
             mInputController = inputController;
         }
+        if (sensorController == null) {
+            mSensorController = new SensorController(mVirtualDeviceLock, mDeviceId);
+        } else {
+            mSensorController = sensorController;
+        }
         mListener = listener;
         try {
             token.linkToDeath(this, 0);
@@ -227,6 +242,12 @@
         return mParams.getName();
     }
 
+    /** Returns the policy specified for this policy type */
+    public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
+            @VirtualDeviceParams.PolicyType int policyType) {
+        return mParams.getDevicePolicy(policyType);
+    }
+
     /** Returns the unique device ID of this device. */
     @Override // Binder call
     public int getDeviceId() {
@@ -312,11 +333,12 @@
         mListener.onClose(mAssociationInfo.getId());
         mAppToken.unlinkToDeath(this, 0);
 
-        final long token = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.close();
+            mSensorController.close();
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -378,108 +400,93 @@
         }
     }
 
-    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @Override // Binder call
-    public void createVirtualDpad(
-            int displayId,
-            @NonNull String deviceName,
-            int vendorId,
-            int productId,
-            @NonNull IBinder deviceToken) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+    public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                 "Permission required to create a virtual dpad");
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(displayId)) {
+            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual dpad for a display not associated with "
                                 + "this virtual device");
             }
         }
-        final long token = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
-            mInputController.createDpad(deviceName, vendorId, productId, deviceToken,
-                    displayId);
+            mInputController.createDpad(config.getInputDeviceName(), config.getVendorId(),
+                    config.getProductId(), deviceToken, config.getAssociatedDisplayId());
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
-    public void createVirtualKeyboard(
-            int displayId,
-            @NonNull String deviceName,
-            int vendorId,
-            int productId,
-            @NonNull IBinder deviceToken) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+    public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                 "Permission required to create a virtual keyboard");
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(displayId)) {
+            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual keyboard for a display not associated with "
                                 + "this virtual device");
             }
         }
-        final long token = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
-            mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken,
-                    displayId);
+            mInputController.createKeyboard(config.getInputDeviceName(), config.getVendorId(),
+                    config.getProductId(), deviceToken, config.getAssociatedDisplayId());
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
-    public void createVirtualMouse(
-            int displayId,
-            @NonNull String deviceName,
-            int vendorId,
-            int productId,
-            @NonNull IBinder deviceToken) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+    public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                 "Permission required to create a virtual mouse");
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(displayId)) {
+            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual mouse for a display not associated with this "
                                 + "virtual device");
             }
         }
-        final long token = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
-            mInputController.createMouse(deviceName, vendorId, productId, deviceToken, displayId);
+            mInputController.createMouse(config.getInputDeviceName(), config.getVendorId(),
+                    config.getProductId(), deviceToken, config.getAssociatedDisplayId());
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
-    public void createVirtualTouchscreen(
-            int displayId,
-            @NonNull String deviceName,
-            int vendorId,
-            int productId,
-            @NonNull IBinder deviceToken,
-            @NonNull Point screenSize) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+    public void createVirtualTouchscreen(VirtualTouchscreenConfig config,
+            @NonNull IBinder deviceToken) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                 "Permission required to create a virtual touchscreen");
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(displayId)) {
+            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual touchscreen for a display not associated with "
                                 + "this virtual device");
             }
         }
-        final long token = Binder.clearCallingIdentity();
+        int screenHeightPixels = config.getHeightInPixels();
+        int screenWidthPixels = config.getWidthInPixels();
+        if (screenHeightPixels <= 0 || screenWidthPixels <= 0) {
+            throw new IllegalArgumentException(
+                    "Cannot create a virtual touchscreen, screen dimensions must be positive. Got: "
+                            + "(" + screenWidthPixels + ", " + screenHeightPixels + ")");
+        }
+
+        final long ident = Binder.clearCallingIdentity();
         try {
-            mInputController.createTouchscreen(deviceName, vendorId, productId,
-                    deviceToken, displayId, screenSize);
+            mInputController.createTouchscreen(config.getInputDeviceName(), config.getVendorId(),
+                    config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
+                    screenHeightPixels, screenWidthPixels);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -489,92 +496,92 @@
                 android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                 "Permission required to unregister this input device");
 
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.unregisterInputDevice(token);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public int getInputDeviceId(IBinder token) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.getInputDeviceId(token);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
 
     @Override // Binder call
     public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendDpadKeyEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendKeyEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendButtonEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendTouchEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendRelativeEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendScrollEvent(token, event);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override // Binder call
     public PointF getCursorPosition(IBinder token) {
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.getCursorPosition(token);
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -584,7 +591,7 @@
                 android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                 "Permission required to unregister this input device");
 
-        final long binderToken = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mVirtualDeviceLock) {
                 mDefaultShowPointerIcon = showPointerIcon;
@@ -593,7 +600,50 @@
                 }
             }
         } finally {
-            Binder.restoreCallingIdentity(binderToken);
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public void createVirtualSensor(
+            @NonNull IBinder deviceToken,
+            @NonNull VirtualSensorConfig config) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to create a virtual sensor");
+        Objects.requireNonNull(config);
+        Objects.requireNonNull(deviceToken);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mSensorController.createSensor(deviceToken, config);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public void unregisterSensor(@NonNull IBinder token) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to unregister a virtual sensor");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mSensorController.unregisterSensor(token);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to send a virtual sensor event");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mSensorController.sendSensorEvent(token, event);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -610,9 +660,11 @@
             fout.println("    mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
         }
         mInputController.dump(fout);
+        mSensorController.dump(fout);
     }
 
-    GenericWindowPolicyController createWindowPolicyController() {
+    GenericWindowPolicyController createWindowPolicyController(
+            @NonNull List<String> displayCategories) {
         synchronized (mVirtualDeviceLock) {
             final GenericWindowPolicyController gwpc =
                     new GenericWindowPolicyController(FLAG_SECURE,
@@ -624,9 +676,11 @@
                             mParams.getBlockedActivities(),
                             mParams.getDefaultActivityPolicy(),
                             createListenerAdapter(),
+                            this::onEnteringPipBlocked,
                             this::onActivityBlocked,
                             this::onSecureWindowShown,
-                            mAssociationInfo.getDeviceProfile());
+                            displayCategories,
+                            mParams.getDefaultRecentsPolicy());
             gwpc.registerRunningAppsChangedListener(/* listener= */ this);
             return gwpc;
         }
@@ -779,6 +833,11 @@
         return mVirtualDisplayIds.contains(displayId);
     }
 
+    void onEnteringPipBlocked(int uid) {
+        showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_pip_blocked,
+                Toast.LENGTH_LONG, mContext.getMainLooper());
+    }
+
     interface OnDeviceCloseListener {
         void onClose(int associationId);
     }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index c400a74..2b62f69 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -32,6 +32,7 @@
 import android.companion.virtual.VirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceParams;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
@@ -233,6 +234,13 @@
         mLocalService.onAppsOnVirtualDeviceChanged();
     }
 
+    @VisibleForTesting
+    void addVirtualDevice(VirtualDeviceImpl virtualDevice) {
+        synchronized (mVirtualDeviceManagerLock) {
+            mVirtualDevices.put(virtualDevice.getAssociationId(), virtualDevice);
+        }
+    }
+
     class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
             VirtualDeviceImpl.PendingTrampolineCallback {
 
@@ -273,7 +281,22 @@
                             @Override
                             public void onClose(int associationId) {
                                 synchronized (mVirtualDeviceManagerLock) {
-                                    mVirtualDevices.remove(associationId);
+                                    VirtualDeviceImpl removedDevice =
+                                            mVirtualDevices.removeReturnOld(associationId);
+                                    if (removedDevice != null) {
+                                        Intent i = new Intent(
+                                                VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+                                        i.putExtra(
+                                                VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID,
+                                                removedDevice.getDeviceId());
+                                        i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                                        final long identity = Binder.clearCallingIdentity();
+                                        try {
+                                            getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+                                        } finally {
+                                            Binder.restoreCallingIdentity(identity);
+                                        }
+                                    }
                                     mAppsOnVirtualDevices.remove(associationId);
                                     if (cameraAccessController != null) {
                                         cameraAccessController.stopObservingIfNeeded();
@@ -325,7 +348,8 @@
             GenericWindowPolicyController gwpc;
             final long token = Binder.clearCallingIdentity();
             try {
-                gwpc = virtualDeviceImpl.createWindowPolicyController();
+                gwpc = virtualDeviceImpl.createWindowPolicyController(
+                    virtualDisplayConfig.getDisplayCategories());
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -358,6 +382,12 @@
             return virtualDevices;
         }
 
+        @Override // BinderCall
+        @VirtualDeviceParams.DevicePolicy
+        public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
+            return mLocalService.getDevicePolicy(deviceId, policyType);
+        }
+
         @Nullable
         private AssociationInfo getAssociationInfo(String packageName, int associationId) {
             final int callingUserId = getCallingUserHandle().getIdentifier();
@@ -439,6 +469,20 @@
         }
 
         @Override
+        @VirtualDeviceParams.DevicePolicy
+        public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
+            synchronized (mVirtualDeviceManagerLock) {
+                for (int i = 0; i < mVirtualDevices.size(); i++) {
+                    final VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
+                    if (device.getDeviceId() == deviceId) {
+                        return device.getDevicePolicy(policyType);
+                    }
+                }
+            }
+            return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+        }
+
+        @Override
         public void onVirtualDisplayCreated(int displayId) {
             final VirtualDisplayListener[] listeners;
             synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 0f101b0..08ee6d7 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -658,7 +658,6 @@
                 int sessionId, int flags, @NonNull IResultReceiver result) {
             Objects.requireNonNull(activityToken);
             Objects.requireNonNull(shareableActivityToken);
-            Objects.requireNonNull(sessionId);
             final int userId = UserHandle.getCallingUserId();
 
             final ActivityPresentationInfo activityPresentationInfo = getAmInternal()
@@ -677,7 +676,6 @@
 
         @Override
         public void finishSession(int sessionId) {
-            Objects.requireNonNull(sessionId);
             final int userId = UserHandle.getCallingUserId();
 
             synchronized (mLock) {
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/Android.bp b/services/core/Android.bp
index 553146d..b6f0237 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -100,7 +100,7 @@
     name: "services.core.unboosted",
     defaults: ["platform_service_defaults"],
     srcs: [
-        ":android.hardware.biometrics.face-V2-java-source",
+        ":android.hardware.biometrics.face-V3-java-source",
         ":statslog-art-java-gen",
         ":statslog-contexthub-java-gen",
         ":services.core-sources",
@@ -144,6 +144,7 @@
 
     static_libs: [
         "android.hardware.authsecret-V1.0-java",
+        "android.hardware.authsecret-V1-java",
         "android.hardware.boot-V1.0-java",
         "android.hardware.boot-V1.1-java",
         "android.hardware.boot-V1.2-java",
@@ -161,14 +162,14 @@
         "android.hardware.weaver-V1.0-java",
         "android.hardware.biometrics.face-V1.0-java",
         "android.hardware.biometrics.fingerprint-V2.3-java",
-        "android.hardware.biometrics.fingerprint-V2-java",
         "android.hardware.oemlock-V1.0-java",
+        "android.hardware.oemlock-V1-java",
         "android.hardware.configstore-V1.1-java",
         "android.hardware.ir-V1-java",
         "android.hardware.rebootescrow-V1-java",
         "android.hardware.soundtrigger-V2.3-java",
         "android.hardware.power.stats-V1-java",
-        "android.hardware.power-V3-java",
+        "android.hardware.power-V4-java",
         "android.hidl.manager-V1.2-java",
         "capture_state_listener-aidl-java",
         "icu4j_calendar_astronomer",
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/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 4278b3e..71a4c73 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1306,6 +1306,13 @@
         }
 
         @Override
+        public int getBatteryHealth() {
+            synchronized (mLock) {
+                return mHealthInfo.batteryHealth;
+            }
+        }
+
+        @Override
         public boolean getBatteryLevelLow() {
             synchronized (mLock) {
                 return mBatteryLevelLow;
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index fa52ac9..6cd7ce8 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -26,10 +26,21 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IBackgroundInstallControlService;
+import android.content.pm.InstallSourceInfo;
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningInfo;
+import android.content.pm.parsing.result.ParseInput;
+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.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -37,21 +48,32 @@
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.text.TextUtils;
 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.File;
 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.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @hide
@@ -70,10 +92,39 @@
     @VisibleForTesting
     static final String BINARY_HASH_ERROR = "SHA256HashError";
 
+    static final int MEASURE_APEX_AND_MODULES = 1;
+    static final int MEASURE_PRELOADS = 2;
+    static final int MEASURE_NEW_MBAS = 3;
+
+    static final long RECORD_MEASUREMENTS_COOLDOWN_MS = 24 * 60 * 60 * 1000;
+
+    @VisibleForTesting
+    static final String BUNDLE_PACKAGE_INFO = "package-info";
+    @VisibleForTesting
+    static final String BUNDLE_CONTENT_DIGEST_ALGORITHM = "content-digest-algo";
+    @VisibleForTesting
+    static final String BUNDLE_CONTENT_DIGEST = "content-digest";
+
+    static final String APEX_PRELOAD_LOCATION = "/system/apex/";
+    static final String APEX_PRELOAD_LOCATION_ERROR = "could-not-be-determined";
+
+    // used for indicating any type of error during MBA measurement
+    static final int MBA_STATUS_ERROR = 0;
+    // used for indicating factory condition preloads
+    static final int MBA_STATUS_PRELOADED = 1;
+    // used for indicating preloaded apps that are updated
+    static final int MBA_STATUS_UPDATED_PRELOAD = 2;
+    // used for indicating newly installed MBAs
+    static final int MBA_STATUS_NEW_INSTALL = 3;
+    // used for indicating newly installed MBAs that are updated (but unused currently)
+    static final int MBA_STATUS_UPDATED_NEW_INSTALL = 4;
+
+    private static final boolean DEBUG = true;     // set this to false upon submission
+
     private final Context mContext;
     private String mVbmetaDigest;
-    private HashMap<String, String> mBinaryHashes;
-    private HashMap<String, Long> mBinaryLastUpdateTimes;
+    // the system time (in ms) the last measurement was taken
+    private long mMeasurementsLastRecordedMs;
 
     final class BinaryTransparencyServiceImpl extends IBinaryTransparencyService.Stub {
 
@@ -83,25 +134,298 @@
         }
 
         @Override
-        public Map getApexInfo() {
-            HashMap results = new HashMap();
-            if (!updateBinaryMeasurements()) {
-                Slog.e(TAG, "Error refreshing APEX measurements.");
-                return results;
-            }
-            PackageManager pm = mContext.getPackageManager();
-            if (pm == null) {
-                Slog.e(TAG, "Error obtaining an instance of PackageManager.");
-                return results;
-            }
+        public List getApexInfo() {
+            List<Bundle> results = new ArrayList<>();
 
-            for (PackageInfo packageInfo : getInstalledApexs()) {
-                results.put(packageInfo, mBinaryHashes.get(packageInfo.packageName));
+            for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
+                Bundle apexMeasurement = measurePackage(packageInfo);
+                results.add(apexMeasurement);
             }
 
             return results;
         }
 
+        /**
+         * A helper function to compute the SHA256 digest of APK package signer.
+         * @param signingInfo The signingInfo of a package, usually {@link PackageInfo#signingInfo}.
+         * @return an array of {@code String} representing hex encoded string of the
+         *         SHA256 digest of APK signer(s). The number of signers will be reflected by the
+         *         size of the array.
+         *         However, {@code null} is returned if there is any error.
+         */
+        private String[] computePackageSignerSha256Digests(@Nullable SigningInfo signingInfo) {
+            if (signingInfo == null) {
+                Slog.e(TAG, "signingInfo is null");
+                return null;
+            }
+
+            Signature[] packageSigners = signingInfo.getApkContentsSigners();
+            List<String> resultList = new ArrayList<>();
+            for (Signature packageSigner : packageSigners) {
+                byte[] digest = PackageUtils.computeSha256DigestBytes(packageSigner.toByteArray());
+                String digestHexString = HexEncoding.encodeToString(digest, false);
+                resultList.add(digestHexString);
+            }
+            return resultList.toArray(new String[1]);
+        }
+
+        /**
+         * Perform basic measurement (i.e. content digest) on a given package.
+         * @param packageInfo The package to be measured.
+         * @return a {@link android.os.Bundle} that packs the measurement result with the following
+         *         keys: {@link #BUNDLE_PACKAGE_INFO},
+         *               {@link #BUNDLE_CONTENT_DIGEST_ALGORITHM}
+         *               {@link #BUNDLE_CONTENT_DIGEST}
+         */
+        private @NonNull Bundle measurePackage(PackageInfo packageInfo) {
+            Bundle result = new Bundle();
+
+            // compute content digest
+            if (DEBUG) {
+                Slog.d(TAG, "Computing content digest for " + packageInfo.packageName + " at "
+                        + packageInfo.applicationInfo.sourceDir);
+            }
+            Map<Integer, byte[]> contentDigests = computeApkContentDigest(
+                    packageInfo.applicationInfo.sourceDir);
+            result.putParcelable(BUNDLE_PACKAGE_INFO, packageInfo);
+            if (contentDigests == null) {
+                Slog.d(TAG, "Failed to compute content digest for "
+                        + packageInfo.applicationInfo.sourceDir);
+                result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0);
+                result.putByteArray(BUNDLE_CONTENT_DIGEST, null);
+                return result;
+            }
+
+            // in this iteration, we'll be supporting only 2 types of digests:
+            // CHUNKED_SHA256 and CHUNKED_SHA512.
+            // And only one of them will be available per package.
+            if (contentDigests.containsKey(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256)) {
+                Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
+                result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId);
+                result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId));
+            } else if (contentDigests.containsKey(
+                    ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512)) {
+                Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
+                result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId);
+                result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId));
+            } else {
+                // TODO(b/259423111): considering putting the raw values for the algorithm & digest
+                //  into the bundle to track potential other digest algorithms that may be in use
+                result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0);
+                result.putByteArray(BUNDLE_CONTENT_DIGEST, null);
+            }
+
+            return result;
+        }
+
+
+        /**
+         * Measures and records digests for *all* covered binaries/packages.
+         *
+         * This method will be called in a Job scheduled to take measurements periodically.
+         *
+         * Packages that are covered so far are:
+         * - all APEXs (introduced in Android T)
+         * - all mainline modules (introduced in Android T)
+         * - all preloaded apps and their update(s) (new in Android U)
+         * - dynamically installed mobile bundled apps (MBAs) (new in Android U)
+         *
+         * @return a {@code List<Bundle>}. Each Bundle item contains values as
+         *          defined by the return value of {@link #measurePackage(PackageInfo)}.
+         */
+        public List getMeasurementsForAllPackages() {
+            List<Bundle> results = new ArrayList<>();
+            PackageManager pm = mContext.getPackageManager();
+            Set<String> packagesMeasured = new HashSet<>();
+
+            // check if we should record the resulting measurements
+            long currentTimeMs = System.currentTimeMillis();
+            boolean record = false;
+            if ((currentTimeMs - mMeasurementsLastRecordedMs) >= RECORD_MEASUREMENTS_COOLDOWN_MS) {
+                Slog.d(TAG, "Measurement was last taken at " + mMeasurementsLastRecordedMs
+                        + " and is now updated to: " + currentTimeMs);
+                mMeasurementsLastRecordedMs = currentTimeMs;
+                record = true;
+            }
+
+            // measure all APEXs first
+            if (DEBUG) {
+                Slog.d(TAG, "Measuring APEXs...");
+            }
+            for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
+                packagesMeasured.add(packageInfo.packageName);
+
+                Bundle apexMeasurement = measurePackage(packageInfo);
+                results.add(apexMeasurement);
+
+                if (record) {
+                    // compute digests of signing info
+                    String[] signerDigestHexStrings = computePackageSignerSha256Digests(
+                            packageInfo.signingInfo);
+
+                    // log to Westworld
+                    FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED,
+                                            packageInfo.packageName,
+                                            packageInfo.getLongVersionCode(),
+                                            HexEncoding.encodeToString(apexMeasurement.getByteArray(
+                                                    BUNDLE_CONTENT_DIGEST), false),
+                                            apexMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
+                                            signerDigestHexStrings);
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Measured " + packagesMeasured.size()
+                        + " packages after considering APEXs.");
+            }
+
+            // proceed with all preloaded apps
+            for (PackageInfo packageInfo : pm.getInstalledPackages(
+                    PackageManager.PackageInfoFlags.of(PackageManager.MATCH_FACTORY_ONLY
+                            | PackageManager.GET_SIGNING_CERTIFICATES))) {
+                if (packagesMeasured.contains(packageInfo.packageName)) {
+                    continue;
+                }
+                packagesMeasured.add(packageInfo.packageName);
+
+                int mba_status = MBA_STATUS_PRELOADED;
+                if (packageInfo.signingInfo == null) {
+                    Slog.d(TAG, "Preload " + packageInfo.packageName  + " at "
+                            + packageInfo.applicationInfo.sourceDir + " has likely been updated.");
+                    mba_status = MBA_STATUS_UPDATED_PRELOAD;
+
+                    PackageInfo origPackageInfo = packageInfo;
+                    try {
+                        packageInfo = pm.getPackageInfo(packageInfo.packageName,
+                                PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL
+                                        | PackageManager.GET_SIGNING_CERTIFICATES));
+                    } catch (PackageManager.NameNotFoundException e) {
+                        Slog.e(TAG, "Failed to obtain an updated PackageInfo of "
+                                + origPackageInfo.packageName, e);
+                        packageInfo = origPackageInfo;
+                        mba_status = MBA_STATUS_ERROR;
+                    }
+                }
+
+
+                Bundle packageMeasurement = measurePackage(packageInfo);
+                results.add(packageMeasurement);
+
+                if (record && (mba_status == MBA_STATUS_UPDATED_PRELOAD)) {
+                    // compute digests of signing info
+                    String[] signerDigestHexStrings = computePackageSignerSha256Digests(
+                            packageInfo.signingInfo);
+
+                    // now we should have all the bits for the atom
+                    byte[] cDigest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
+                    FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
+                            packageInfo.packageName,
+                            packageInfo.getLongVersionCode(),
+                            (cDigest != null) ? HexEncoding.encodeToString(
+                                    packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST),
+                                    false) : null,
+                            packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
+                            signerDigestHexStrings, // signer_cert_digest
+                            mba_status,             // mba_status
+                            null,                   // initiator
+                            null,                   // initiator_signer_digest
+                            null,                   // installer
+                            null                    // originator
+                    );
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Measured " + packagesMeasured.size()
+                        + " packages after considering preloads");
+            }
+
+            // lastly measure all newly installed MBAs
+            for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
+                if (packagesMeasured.contains(packageInfo.packageName)) {
+                    continue;
+                }
+                packagesMeasured.add(packageInfo.packageName);
+
+                Bundle packageMeasurement = measurePackage(packageInfo);
+                results.add(packageMeasurement);
+
+                if (record) {
+                    // compute digests of signing info
+                    String[] signerDigestHexStrings = computePackageSignerSha256Digests(
+                            packageInfo.signingInfo);
+
+                    // then extract package's InstallSourceInfo
+                    if (DEBUG) {
+                        Slog.d(TAG, "Extracting InstallSourceInfo for " + packageInfo.packageName);
+                    }
+                    InstallSourceInfo installSourceInfo = getInstallSourceInfo(
+                            packageInfo.packageName);
+                    String initiator = null;
+                    SigningInfo initiatorSignerInfo = null;
+                    String[] initiatorSignerInfoDigest = null;
+                    String installer = null;
+                    String originator = null;
+
+                    if (installSourceInfo != null) {
+                        initiator = installSourceInfo.getInitiatingPackageName();
+                        initiatorSignerInfo = installSourceInfo.getInitiatingPackageSigningInfo();
+                        if (initiatorSignerInfo != null) {
+                            initiatorSignerInfoDigest = computePackageSignerSha256Digests(
+                                    initiatorSignerInfo);
+                        }
+                        installer = installSourceInfo.getInstallingPackageName();
+                        originator = installSourceInfo.getOriginatingPackageName();
+                    }
+
+                    // we should now have all the info needed for the atom
+                    byte[] cDigest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
+                    FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
+                            packageInfo.packageName,
+                            packageInfo.getLongVersionCode(),
+                            (cDigest != null) ? HexEncoding.encodeToString(
+                                    packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST),
+                                    false) : null,
+                            packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
+                            signerDigestHexStrings,
+                            MBA_STATUS_NEW_INSTALL,   // mba_status
+                            initiator,
+                            initiatorSignerInfoDigest,
+                            installer,
+                            originator
+                    );
+                }
+            }
+            if (DEBUG) {
+                long timeSpentMeasuring = System.currentTimeMillis() - currentTimeMs;
+                Slog.d(TAG, "Measured " + packagesMeasured.size()
+                        + " packages altogether in " + timeSpentMeasuring + "ms");
+            }
+
+            return results;
+        }
+
+        /**
+         * A wrapper around
+         * {@link ApkSignatureVerifier#verifySignaturesInternal(ParseInput, String, int, boolean)}.
+         * @param pathToApk The APK's installation path
+         * @return a {@code Map<Integer, byte[]>} with algorithm type as the key and content
+         *         digest as the value.
+         *         a {@code null} is returned upon encountering any error.
+         */
+        private Map<Integer, byte[]> computeApkContentDigest(String pathToApk) {
+            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+            ParseResult<ApkSignatureVerifier.SigningDetailsWithDigests> parseResult =
+                    ApkSignatureVerifier.verifySignaturesInternal(input,
+                            pathToApk,
+                            SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2, false);
+            if (parseResult.isError()) {
+                Slog.e(TAG, "Failed to compute content digest for "
+                        + pathToApk + " due to: "
+                        + parseResult.getErrorMessage());
+                return null;
+            }
+            return parseResult.getResult().contentDigests;
+        }
+
         @Override
         public void onShellCommand(@Nullable FileDescriptor in,
                                    @Nullable FileDescriptor out,
@@ -151,6 +475,113 @@
                     return 0;
                 }
 
+                private void printPackageMeasurements(PackageInfo packageInfo,
+                                                      final PrintWriter pw) {
+                    Map<Integer, byte[]> contentDigests = computeApkContentDigest(
+                            packageInfo.applicationInfo.sourceDir);
+                    if (contentDigests == null) {
+                        pw.println("ERROR: Failed to compute package content digest for "
+                                + packageInfo.applicationInfo.sourceDir);
+                        return;
+                    }
+
+                    for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
+                        Integer algorithmId = entry.getKey();
+                        byte[] contentDigest = entry.getValue();
+
+                        pw.print(translateContentDigestAlgorithmIdToString(algorithmId));
+                        pw.print(":");
+                        pw.print(HexEncoding.encodeToString(contentDigest, false));
+                        pw.print("\n");
+                    }
+                }
+
+                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 = getOriginalApexPreinstalledLocation(
+                                packageInfo.packageName, packageInfo.applicationInfo.sourceDir);
+                        pw.println("|--> Pre-installed package install location: "
+                                + origPackageFilepath);
+
+                        // TODO(b/259347186): revive this with the proper cmd options.
+                        /*
+                        String digest = PackageUtils.computeSha256DigestForLargeFile(
+                        origPackageFilepath, PackageUtils.createLargeFileBuffer());
+                         */
+
+                        Map<Integer, byte[]> contentDigests = computeApkContentDigest(
+                                origPackageFilepath);
+                        if (contentDigests == null) {
+                            pw.println("ERROR: Failed to compute package content digest for "
+                                    + origPackageFilepath);
+                        } else {
+                            for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
+                                Integer algorithmId = entry.getKey();
+                                byte[] contentDigest = entry.getValue();
+                                pw.println("|--> Pre-installed package content digest: "
+                                        + HexEncoding.encodeToString(contentDigest, false));
+                                pw.println("|--> Pre-installed package content digest algorithm: "
+                                        + translateContentDigestAlgorithmIdToString(algorithmId));
+                            }
+                        }
+                    }
+                    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());
@@ -158,21 +589,90 @@
                             + (moduleInfo.isHidden() ? "hidden" : "visible"));
                 }
 
+                private void printAppDetails(PackageInfo packageInfo,
+                                             boolean printLibraries,
+                                             final PrintWriter pw) {
+                    pw.println("--- App Details ---");
+                    pw.println("Name: " + packageInfo.applicationInfo.name);
+                    pw.println("Label: " + mContext.getPackageManager().getApplicationLabel(
+                            packageInfo.applicationInfo));
+                    pw.println("Description: " + packageInfo.applicationInfo.loadDescription(
+                            mContext.getPackageManager()));
+                    pw.println("Has code: " + packageInfo.applicationInfo.hasCode());
+                    pw.println("Is enabled: " + packageInfo.applicationInfo.enabled);
+                    pw.println("Is suspended: " + ((packageInfo.applicationInfo.flags
+                                                    & ApplicationInfo.FLAG_SUSPENDED) != 0));
+
+                    pw.println("Compile SDK version: " + packageInfo.compileSdkVersion);
+                    pw.println("Target SDK version: "
+                            + packageInfo.applicationInfo.targetSdkVersion);
+
+                    pw.println("Is privileged: "
+                            + packageInfo.applicationInfo.isPrivilegedApp());
+                    pw.println("Is a stub: " + packageInfo.isStub);
+                    pw.println("Is a core app: " + packageInfo.coreApp);
+                    pw.println("SEInfo: " + packageInfo.applicationInfo.seInfo);
+                    pw.println("Component factory: "
+                            + packageInfo.applicationInfo.appComponentFactory);
+                    pw.println("Process name: " + packageInfo.applicationInfo.processName);
+                    pw.println("Task affinity : " + packageInfo.applicationInfo.taskAffinity);
+                    pw.println("UID: " + packageInfo.applicationInfo.uid);
+                    pw.println("Shared UID: " + packageInfo.sharedUserId);
+
+                    if (printLibraries) {
+                        pw.println("== App's Shared Libraries ==");
+                        List<SharedLibraryInfo> sharedLibraryInfos =
+                                packageInfo.applicationInfo.getSharedLibraryInfos();
+                        if (sharedLibraryInfos == null || sharedLibraryInfos.isEmpty()) {
+                            pw.println("<none>");
+                        }
+
+                        for (int i = 0; i < sharedLibraryInfos.size(); i++) {
+                            SharedLibraryInfo sharedLibraryInfo = sharedLibraryInfos.get(i);
+                            pw.println("  ++ Library #" + (i + 1) + " ++");
+                            pw.println("  Lib name: " + sharedLibraryInfo.getName());
+                            long libVersion = sharedLibraryInfo.getLongVersion();
+                            pw.print("  Lib version: ");
+                            if (libVersion == SharedLibraryInfo.VERSION_UNDEFINED) {
+                                pw.print("undefined");
+                            } else {
+                                pw.print(libVersion);
+                            }
+                            pw.print("\n");
+
+                            pw.println("  Lib package name (if available): "
+                                    + sharedLibraryInfo.getPackageName());
+                            pw.println("  Lib path: " + sharedLibraryInfo.getPath());
+                            pw.print("  Lib type: ");
+                            switch (sharedLibraryInfo.getType()) {
+                                case SharedLibraryInfo.TYPE_BUILTIN:
+                                    pw.print("built-in");
+                                    break;
+                                case SharedLibraryInfo.TYPE_DYNAMIC:
+                                    pw.print("dynamic");
+                                    break;
+                                case SharedLibraryInfo.TYPE_STATIC:
+                                    pw.print("static");
+                                    break;
+                                case SharedLibraryInfo.TYPE_SDK_PACKAGE:
+                                    pw.print("SDK");
+                                    break;
+                                case SharedLibraryInfo.VERSION_UNDEFINED:
+                                default:
+                                    pw.print("undefined");
+                                    break;
+                            }
+                            pw.print("\n");
+                            pw.println("  Is a native lib: " + sharedLibraryInfo.isNative());
+                        }
+                    }
+
+                }
+
                 private int printAllApexs() {
                     final PrintWriter pw = getOutPrintWriter();
                     boolean verbose = false;
                     String opt;
-
-                    // refresh cache to make sure info is most up-to-date
-                    if (!updateBinaryMeasurements()) {
-                        pw.println("ERROR: Failed to refresh info for APEXs.");
-                        return -1;
-                    }
-                    if (mBinaryHashes == null || (mBinaryHashes.size() == 0)) {
-                        pw.println("ERROR: Unable to obtain apex_info at this time.");
-                        return -1;
-                    }
-
                     while ((opt = getNextOption()) != null) {
                         switch (opt) {
                             case "-v":
@@ -190,28 +690,36 @@
                         return -1;
                     }
 
-                    pw.println("APEX Info:");
-                    for (PackageInfo packageInfo : getInstalledApexs()) {
+                    if (!verbose) {
+                        pw.println("APEX Info [Format: package_name,package_version,"
+                                // TODO(b/259347186): revive via special cmd line option
+                                //+ "package_sha256_digest,"
+                                + "content_digest_algorithm:content_digest]:");
+                    }
+                    for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
+                        if (verbose) {
+                            pw.println("APEX Info [Format: package_name,package_version,"
+                                    // TODO(b/259347186): revive via special cmd line option
+                                    //+ "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);
 
                         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("");
                         }
                     }
@@ -222,17 +730,6 @@
                     final PrintWriter pw = getOutPrintWriter();
                     boolean verbose = false;
                     String opt;
-
-                    // refresh cache to make sure info is most up-to-date
-                    if (!updateBinaryMeasurements()) {
-                        pw.println("ERROR: Failed to refresh info for Modules.");
-                        return -1;
-                    }
-                    if (mBinaryHashes == null || (mBinaryHashes.size() == 0)) {
-                        pw.println("ERROR: Unable to obtain module_info at this time.");
-                        return -1;
-                    }
-
                     while ((opt = getNextOption()) != null) {
                         switch (opt) {
                             case "-v":
@@ -250,25 +747,38 @@
                         return -1;
                     }
 
-                    pw.println("Module Info:");
+                    if (!verbose) {
+                        pw.println("Module Info [Format: package_name,package_version,"
+                                // TODO(b/259347186): revive via special cmd line option
+                                //+ "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,"
+                                    // TODO(b/259347186): revive via special cmd line option
+                                    //+ "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);
 
                             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("");
@@ -279,6 +789,71 @@
                     return 0;
                 }
 
+                private int printAllMbas() {
+                    final PrintWriter pw = getOutPrintWriter();
+                    boolean verbose = false;
+                    boolean printLibraries = false;
+                    String opt;
+                    while ((opt = getNextOption()) != null) {
+                        switch (opt) {
+                            case "-v":
+                                verbose = true;
+                                break;
+                            case "-l":
+                                printLibraries = true;
+                                break;
+                            default:
+                                pw.println("ERROR: Unknown option: " + opt);
+                                return 1;
+                        }
+                    }
+
+                    if (!verbose) {
+                        pw.println("MBA Info [Format: package_name,package_version,"
+                                // TODO(b/259347186): revive via special cmd line option
+                                //+ "package_sha256_digest,"
+                                + "content_digest_algorithm:content_digest]:");
+                    }
+                    for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
+                        if (verbose) {
+                            pw.println("MBA Info [Format: package_name,package_version,"
+                                    // TODO(b/259347186): revive via special cmd line option
+                                    //+ "package_sha256_digest,"
+                                    + "content_digest_algorithm:content_digest]:");
+                        }
+                        pw.print(packageInfo.packageName + ",");
+                        pw.print(packageInfo.getLongVersionCode() + ",");
+                        printPackageMeasurements(packageInfo, pw);
+
+                        if (verbose) {
+                            printAppDetails(packageInfo, printLibraries, pw);
+                            printPackageInstallationInfo(packageInfo, pw);
+                            printPackageSignerDetails(packageInfo.signingInfo, pw);
+                            pw.println("");
+                        }
+                    }
+                    return 0;
+                }
+
+                // TODO(b/259347186): add option handling full file-based SHA256 digest
+                private int printAllPreloads() {
+                    final PrintWriter pw = getOutPrintWriter();
+
+                    PackageManager pm = mContext.getPackageManager();
+                    if (pm == null) {
+                        Slog.e(TAG, "Failed to obtain PackageManager.");
+                        return -1;
+                    }
+                    List<PackageInfo> factoryApps = pm.getInstalledPackages(
+                            PackageManager.PackageInfoFlags.of(PackageManager.MATCH_FACTORY_ONLY));
+
+                    pw.println("Preload Info [Format: package_name]");
+                    for (PackageInfo packageInfo : factoryApps) {
+                        pw.println(packageInfo.packageName);
+                    }
+                    return 0;
+                }
+
                 @Override
                 public int onCommand(String cmd) {
                     if (cmd == null) {
@@ -301,6 +876,10 @@
                                     return printAllApexs();
                                 case "module_info":
                                     return printAllModules();
+                                case "mba_info":
+                                    return printAllMbas();
+                                case "preload_info":
+                                    return printAllPreloads();
                                 default:
                                     pw.println(String.format("ERROR: Unknown info type '%s'",
                                             infoType));
@@ -324,11 +903,18 @@
                     pw.println("");
                     pw.println("    get apex_info [-v]");
                     pw.println("        Print information about installed APEXs on device.");
-                    pw.println("            -v: lists more verbose information about each APEX");
+                    pw.println("            -v: lists more verbose information about each APEX.");
                     pw.println("");
                     pw.println("    get module_info [-v]");
                     pw.println("        Print information about installed modules on device.");
-                    pw.println("            -v: lists more verbose information about each module");
+                    pw.println("            -v: lists more verbose information about each module.");
+                    pw.println("");
+                    pw.println("    get mba_info [-v] [-l]");
+                    pw.println("        Print information about installed mobile bundle apps "
+                               + "(MBAs on device).");
+                    pw.println("            -v: lists more verbose information about each app.");
+                    pw.println("            -l: lists shared library info. This will only be "
+                               + "listed with -v");
                     pw.println("");
                 }
 
@@ -346,8 +932,7 @@
         mContext = context;
         mServiceImpl = new BinaryTransparencyServiceImpl();
         mVbmetaDigest = VBMETA_DIGEST_UNINITIALIZED;
-        mBinaryHashes = new HashMap<>();
-        mBinaryLastUpdateTimes = new HashMap<>();
+        mMeasurementsLastRecordedMs = 0;
     }
 
     /**
@@ -378,44 +963,43 @@
             Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
             getVBMetaDigestInformation();
 
-            // due to potentially long computation that holds up boot time, computations for
-            // SHA256 digests of APEX and Module packages are scheduled here,
-            // but only executed when device is idle.
-            Slog.i(TAG, "Scheduling APEX and Module measurements to be updated.");
+            // to avoid the risk of holding up boot time, computations to measure APEX, Module, and
+            // MBA digests are scheduled here, but only executed when the device is idle and plugged
+            // in.
+            Slog.i(TAG, "Scheduling measurements to be taken.");
             UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
                     BinaryTransparencyService.this);
         }
     }
 
     /**
-     * JobService to update binary measurements and update internal cache.
+     * JobService to measure all covered binaries and record result to Westworld.
      */
     public static class UpdateMeasurementsJobService extends JobService {
-        private static final int COMPUTE_APEX_MODULE_SHA256_JOB_ID =
-                BinaryTransparencyService.UpdateMeasurementsJobService.class.hashCode();
+        private static final int DO_BINARY_MEASUREMENTS_JOB_ID =
+                UpdateMeasurementsJobService.class.hashCode();
 
         @Override
         public boolean onStartJob(JobParameters params) {
             Slog.d(TAG, "Job to update binary measurements started.");
-            if (params.getJobId() != COMPUTE_APEX_MODULE_SHA256_JOB_ID) {
+            if (params.getJobId() != DO_BINARY_MEASUREMENTS_JOB_ID) {
                 return false;
             }
 
-            // we'll still update the measurements via threads to be mindful of low-end devices
+            // we'll perform binary measurements via threads to be mindful of low-end devices
             // where this operation might take longer than expected, and so that we don't block
             // system_server's main thread.
             Executors.defaultThreadFactory().newThread(() -> {
-                // since we can't call updateBinaryMeasurements() directly, calling
-                // getApexInfo() achieves the same effect, and we simply discard the return
-                // value
-
+                // we discard the return value of getMeasurementsForAllPackages() as the
+                // results of the measurements will be recorded, and that is what we're aiming
+                // for with this job.
                 IBinder b = ServiceManager.getService(Context.BINARY_TRANSPARENCY_SERVICE);
                 IBinaryTransparencyService iBtsService =
                         IBinaryTransparencyService.Stub.asInterface(b);
                 try {
-                    iBtsService.getApexInfo();
+                    iBtsService.getMeasurementsForAllPackages();
                 } catch (RemoteException e) {
-                    Slog.e(TAG, "Updating binary measurements was interrupted.", e);
+                    Slog.e(TAG, "Taking binary measurements was interrupted.", e);
                     return;
                 }
                 jobFinished(params, false);
@@ -431,25 +1015,26 @@
 
         @SuppressLint("DefaultLocale")
         static void scheduleBinaryMeasurements(Context context, BinaryTransparencyService service) {
-            Slog.i(TAG, "Scheduling APEX & Module SHA256 digest computation job");
+            Slog.i(TAG, "Scheduling binary content-digest computation job");
             final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
             if (jobScheduler == null) {
                 Slog.e(TAG, "Failed to obtain an instance of JobScheduler.");
                 return;
             }
 
-            final JobInfo jobInfo = new JobInfo.Builder(COMPUTE_APEX_MODULE_SHA256_JOB_ID,
+            final JobInfo jobInfo = new JobInfo.Builder(DO_BINARY_MEASUREMENTS_JOB_ID,
                     new ComponentName(context, UpdateMeasurementsJobService.class))
                     .setRequiresDeviceIdle(true)
                     .setRequiresCharging(true)
+                    .setPeriodic(RECORD_MEASUREMENTS_COOLDOWN_MS)
                     .build();
             if (jobScheduler.schedule(jobInfo) != JobScheduler.RESULT_SUCCESS) {
-                Slog.e(TAG, "Failed to schedule job to update binary measurements.");
+                Slog.e(TAG, "Failed to schedule job to measure binaries.");
                 return;
             }
-            Slog.d(TAG, String.format(
-                    "Job %d to update binary measurements scheduled successfully.",
-                    COMPUTE_APEX_MODULE_SHA256_JOB_ID));
+            Slog.d(TAG, TextUtils.formatSimple(
+                    "Job %d to measure binaries was scheduled successfully.",
+                    DO_BINARY_MEASUREMENTS_JOB_ID));
         }
     }
 
@@ -459,8 +1044,23 @@
         FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest);
     }
 
+    private String translateContentDigestAlgorithmIdToString(int algorithmId) {
+        switch (algorithmId) {
+            case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
+                return "CHUNKED_SHA256";
+            case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
+                return "CHUNKED_SHA512";
+            case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+                return "VERITY_CHUNKED_SHA256";
+            case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
+                return "SHA256";
+            default:
+                return "UNKNOWN_ALGO_ID(" + algorithmId + ")";
+        }
+    }
+
     @NonNull
-    private List<PackageInfo> getInstalledApexs() {
+    private List<PackageInfo> getCurrentInstalledApexs() {
         List<PackageInfo> results = new ArrayList<>();
         PackageManager pm = mContext.getPackageManager();
         if (pm == null) {
@@ -468,7 +1068,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,133 +1079,71 @@
         return results;
     }
 
-
-    /**
-     * Updates the internal data structure with the most current APEX measurements.
-     * @return true if update is successful; false otherwise.
-     */
-    private boolean updateBinaryMeasurements() {
-        if (mBinaryHashes.size() == 0) {
-            Slog.d(TAG, "No apex in cache yet.");
-            doFreshBinaryMeasurements();
-            return true;
-        }
-
+    @Nullable
+    private InstallSourceInfo getInstallSourceInfo(String packageName) {
         PackageManager pm = mContext.getPackageManager();
         if (pm == null) {
-            Slog.e(TAG, "Failed to obtain a valid PackageManager instance.");
-            return false;
+            Slog.e(TAG, "Error obtaining an instance of PackageManager.");
+            return null;
         }
-
-        // We're assuming updates to existing modules and APEXs can happen, but not brand new
-        // ones appearing out of the blue. Thus, we're going to only go through our cache to check
-        // for changes, rather than freshly invoking `getInstalledPackages()` and
-        // `getInstalledModules()`
-        byte[] largeFileBuffer = PackageUtils.createLargeFileBuffer();
-        for (Map.Entry<String, Long> entry : mBinaryLastUpdateTimes.entrySet()) {
-            String packageName = entry.getKey();
-            try {
-                PackageInfo packageInfo = pm.getPackageInfo(packageName,
-                        PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
-                long cachedUpdateTime = entry.getValue();
-
-                if (packageInfo.lastUpdateTime > cachedUpdateTime) {
-                    Slog.d(TAG, packageName + " has been updated!");
-                    entry.setValue(packageInfo.lastUpdateTime);
-
-                    // compute the digest for the updated package
-                    String sha256digest = PackageUtils.computeSha256DigestForLargeFile(
-                            packageInfo.applicationInfo.sourceDir, largeFileBuffer);
-                    if (sha256digest == null) {
-                        Slog.e(TAG, "Failed to compute SHA256sum for file at "
-                                + packageInfo.applicationInfo.sourceDir);
-                        mBinaryHashes.put(packageName, BINARY_HASH_ERROR);
-                    } else {
-                        mBinaryHashes.put(packageName, sha256digest);
-                    }
-
-                    if (packageInfo.isApex) {
-                        FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED,
-                                packageInfo.packageName,
-                                packageInfo.getLongVersionCode(),
-                                mBinaryHashes.get(packageInfo.packageName));
-                    }
-                }
-            } catch (PackageManager.NameNotFoundException e) {
-                Slog.e(TAG, "Could not find package with name " + packageName);
-                continue;
-            }
-        }
-
-        return true;
-    }
-
-    private void doFreshBinaryMeasurements() {
-        PackageManager pm = mContext.getPackageManager();
-        Slog.d(TAG, "Obtained package manager");
-
-        // In general, we care about all APEXs, *and* all Modules, which may include some APKs.
-
-        // First, we deal with all installed APEXs.
-        byte[] largeFileBuffer = PackageUtils.createLargeFileBuffer();
-        for (PackageInfo packageInfo : getInstalledApexs()) {
-            ApplicationInfo appInfo = packageInfo.applicationInfo;
-
-            // compute SHA256 for these APEXs
-            String sha256digest = PackageUtils.computeSha256DigestForLargeFile(appInfo.sourceDir,
-                    largeFileBuffer);
-            if (sha256digest == null) {
-                Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s",
-                        packageInfo.packageName));
-                mBinaryHashes.put(packageInfo.packageName, BINARY_HASH_ERROR);
-            } else {
-                mBinaryHashes.put(packageInfo.packageName, sha256digest);
-            }
-            FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED, packageInfo.packageName,
-                    packageInfo.getLongVersionCode(), mBinaryHashes.get(packageInfo.packageName));
-            Slog.d(TAG, String.format("Last update time for %s: %d", packageInfo.packageName,
-                    packageInfo.lastUpdateTime));
-            mBinaryLastUpdateTimes.put(packageInfo.packageName, packageInfo.lastUpdateTime);
-        }
-
-        // Next, get all installed modules from PackageManager - skip over those APEXs we've
-        // processed above
-        for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
-            String packageName = module.getPackageName();
-            if (packageName == null) {
-                Slog.e(TAG, "ERROR: Encountered null package name for module "
-                        + module.getApexModuleName());
-                continue;
-            }
-            if (mBinaryHashes.containsKey(module.getPackageName())) {
-                continue;
-            }
-
-            // get PackageInfo for this module
-            try {
-                PackageInfo packageInfo = pm.getPackageInfo(packageName,
-                        PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
-                ApplicationInfo appInfo = packageInfo.applicationInfo;
-
-                // compute SHA256 digest for these modules
-                String sha256digest = PackageUtils.computeSha256DigestForLargeFile(
-                        appInfo.sourceDir, largeFileBuffer);
-                if (sha256digest == null) {
-                    Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s",
-                            packageName));
-                    mBinaryHashes.put(packageName, BINARY_HASH_ERROR);
-                } else {
-                    mBinaryHashes.put(packageName, sha256digest);
-                }
-                Slog.d(TAG, String.format("Last update time for %s: %d", packageName,
-                        packageInfo.lastUpdateTime));
-                mBinaryLastUpdateTimes.put(packageName, packageInfo.lastUpdateTime);
-            } catch (PackageManager.NameNotFoundException e) {
-                Slog.e(TAG, "ERROR: Could not obtain PackageInfo for package name: "
-                        + packageName);
-                continue;
-            }
+        try {
+            return pm.getInstallSourceInfo(packageName);
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return null;
         }
     }
 
+    @NonNull
+    private String getOriginalApexPreinstalledLocation(String packageName,
+                                                   String currentInstalledLocation) {
+        // get a listing of all apex files in /system/apex/
+        Set<String> originalApexs = Stream.of(new File(APEX_PRELOAD_LOCATION).listFiles())
+                                        .filter(f -> !f.isDirectory())
+                                        .map(File::getName)
+                                        .collect(Collectors.toSet());
+
+        for (String originalApex : originalApexs) {
+            if (originalApex.startsWith(packageName)) {
+                return APEX_PRELOAD_LOCATION + originalApex;
+            }
+        }
+
+        return APEX_PRELOAD_LOCATION_ERROR;
+    }
+
+    /**
+     * Wrapper method to call into IBICS to get a list of all newly installed MBAs.
+     *
+     * We expect IBICS to maintain an accurate list of installed MBAs, and we merely make use of
+     * the results within this service. This means we do not further check whether the
+     * apps in the returned slice is still installed or not, esp. considering that preloaded apps
+     * could be updated, or post-setup installed apps *might* be deleted in real time.
+     *
+     * Note that we do *not* cache the results from IBICS because of the more dynamic nature of
+     * MBAs v.s. other binaries that we measure.
+     *
+     * @return a list of preloaded apps + dynamically installed apps that fit the definition of MBA.
+     */
+    @NonNull
+    private List<PackageInfo> getNewlyInstalledMbas() {
+        List<PackageInfo> result = new ArrayList<>();
+        IBackgroundInstallControlService iBics = IBackgroundInstallControlService.Stub.asInterface(
+                ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
+        if (iBics == null) {
+            Slog.e(TAG,
+                    "Failed to obtain an IBinder instance of IBackgroundInstallControlService");
+            return result;
+        }
+        ParceledListSlice<PackageInfo> slice;
+        try {
+            slice = iBics.getBackgroundInstalledPackages(
+                    PackageManager.MATCH_ALL | PackageManager.GET_SIGNING_CERTIFICATES,
+                    UserHandle.USER_SYSTEM);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get a list of MBAs.", e);
+            return result;
+        }
+        return slice.getList();
+    }
 }
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 551ffff..84c033c 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -341,7 +341,8 @@
         // non-proto tombstones, even though proto tombstones do not support including the counter
         // of events dropped since rate limiting activated yet.
         DropboxRateLimiter.RateLimitResult rateLimitResult =
-                sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE, processName);
+                sDropboxRateLimiter.shouldRateLimit(
+                       proto ? TAG_TOMBSTONE_PROTO : TAG_TOMBSTONE, processName);
         if (rateLimitResult.shouldRateLimit()) return;
 
         HashMap<String, Long> timestamps = readTimestamps();
diff --git a/services/core/java/com/android/server/ConsumerIrService.java b/services/core/java/com/android/server/ConsumerIrService.java
index a9bdf06..ee6d808 100644
--- a/services/core/java/com/android/server/ConsumerIrService.java
+++ b/services/core/java/com/android/server/ConsumerIrService.java
@@ -92,6 +92,8 @@
     @Override
     @EnforcePermission(TRANSMIT_IR)
     public void transmit(String packageName, int carrierFrequency, int[] pattern) {
+        super.transmit_enforcePermission();
+
         long totalXmitTime = 0;
 
         for (int slice : pattern) {
@@ -128,6 +130,8 @@
     @Override
     @EnforcePermission(TRANSMIT_IR)
     public int[] getCarrierFrequencies() {
+        super.getCarrierFrequencies_enforcePermission();
+
         throwIfNoIrEmitter();
 
         synchronized(mHalLock) {
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 104d10d..3487613 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -19,6 +19,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.database.ContentObserver;
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
@@ -73,6 +74,7 @@
     private final boolean mAllowTheaterModeWakeFromDock;
 
     private final List<ExtconStateConfig> mExtconStateConfigs;
+    private DeviceProvisionedObserver mDeviceProvisionedObserver;
 
     static final class ExtconStateProvider {
         private final Map<String, String> mState;
@@ -110,7 +112,7 @@
                 Slog.w(TAG, "No state file found at: " + stateFilePath);
                 return new ExtconStateProvider(new HashMap<>());
             } catch (Exception e) {
-                Slog.e(TAG, "" , e);
+                Slog.e(TAG, "", e);
                 return new ExtconStateProvider(new HashMap<>());
             }
         }
@@ -136,7 +138,7 @@
 
     private static List<ExtconStateConfig> loadExtconStateConfigs(Context context) {
         String[] rows = context.getResources().getStringArray(
-            com.android.internal.R.array.config_dockExtconStateMapping);
+                com.android.internal.R.array.config_dockExtconStateMapping);
         try {
             ArrayList<ExtconStateConfig> configs = new ArrayList<>();
             for (String row : rows) {
@@ -167,6 +169,7 @@
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromDock);
         mKeepDreamingWhenUndocking = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_keepDreamingWhenUndocking);
+        mDeviceProvisionedObserver = new DeviceProvisionedObserver(mHandler);
 
         mExtconStateConfigs = loadExtconStateConfigs(context);
 
@@ -199,15 +202,19 @@
         if (phase == PHASE_ACTIVITY_MANAGER_READY) {
             synchronized (mLock) {
                 mSystemReady = true;
-
-                // don't bother broadcasting undocked here
-                if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
-                    updateLocked();
-                }
+                mDeviceProvisionedObserver.onSystemReady();
+                updateIfDockedLocked();
             }
         }
     }
 
+    private void updateIfDockedLocked() {
+        // don't bother broadcasting undocked here
+        if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+            updateLocked();
+        }
+    }
+
     private void setActualDockStateLocked(int newState) {
         mActualDockState = newState;
         if (!mUpdatesStopped) {
@@ -252,8 +259,7 @@
 
             // Skip the dock intent if not yet provisioned.
             final ContentResolver cr = getContext().getContentResolver();
-            if (Settings.Global.getInt(cr,
-                    Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
+            if (!mDeviceProvisionedObserver.isDeviceProvisioned()) {
                 Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
                 return;
             }
@@ -302,6 +308,7 @@
                                     getContext(), soundUri);
                             if (sfx != null) {
                                 sfx.setStreamType(AudioManager.STREAM_SYSTEM);
+                                sfx.preferBuiltinDevice(true);
                                 sfx.play();
                             }
                         }
@@ -418,4 +425,48 @@
             }
         }
     }
+
+    private final class DeviceProvisionedObserver extends ContentObserver {
+        private boolean mRegistered;
+
+        public DeviceProvisionedObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            synchronized (mLock) {
+                updateRegistration();
+                if (isDeviceProvisioned()) {
+                    // Send the dock broadcast if device is docked after provisioning.
+                    updateIfDockedLocked();
+                }
+            }
+        }
+
+        void onSystemReady() {
+            updateRegistration();
+        }
+
+        private void updateRegistration() {
+            boolean register = !isDeviceProvisioned();
+            if (register == mRegistered) {
+                return;
+            }
+            final ContentResolver resolver = getContext().getContentResolver();
+            if (register) {
+                resolver.registerContentObserver(
+                        Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+                        false, this);
+            } else {
+                resolver.unregisterContentObserver(this);
+            }
+            mRegistered = register;
+        }
+
+        boolean isDeviceProvisioned() {
+            return Settings.Global.getInt(getContext().getContentResolver(),
+                    Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 02c6ca2..27ee627 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -91,7 +91,7 @@
     private static final int DEFAULT_MAX_FILES_LOWRAM = 300;
     private static final int DEFAULT_QUOTA_KB = 10 * 1024;
     private static final int DEFAULT_QUOTA_PERCENT = 10;
-    private static final int DEFAULT_RESERVE_PERCENT = 10;
+    private static final int DEFAULT_RESERVE_PERCENT = 0;
     private static final int QUOTA_RESCAN_MILLIS = 5000;
 
     private static final boolean PROFILE_DUMP = false;
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index ce0e69c..27215b2 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -77,6 +77,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean startInstallation(String dsuSlot) throws RemoteException {
+        super.startInstallation_enforcePermission();
+
         IGsiService service = getGsiService();
         mGsiService = service;
         // priority from high to low: sysprop -> sdcard -> /data
@@ -124,6 +126,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public int createPartition(String name, long size, boolean readOnly) throws RemoteException {
+        super.createPartition_enforcePermission();
+
         IGsiService service = getGsiService();
         int status = service.createPartition(name, size, readOnly);
         if (status != IGsiService.INSTALL_OK) {
@@ -135,6 +139,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean closePartition() throws RemoteException {
+        super.closePartition_enforcePermission();
+
         IGsiService service = getGsiService();
         if (service.closePartition() != 0) {
             Slog.i(TAG, "Partition installation completes with error");
@@ -146,6 +152,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean finishInstallation() throws RemoteException {
+        super.finishInstallation_enforcePermission();
+
         IGsiService service = getGsiService();
         if (service.closeInstall() != 0) {
             Slog.i(TAG, "Failed to finish installation");
@@ -157,12 +165,16 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public GsiProgress getInstallationProgress() throws RemoteException {
+        super.getInstallationProgress_enforcePermission();
+
         return getGsiService().getInstallProgress();
     }
 
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean abort() throws RemoteException {
+        super.abort_enforcePermission();
+
         return getGsiService().cancelGsiInstall();
     }
 
@@ -183,12 +195,16 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean isEnabled() throws RemoteException {
+        super.isEnabled_enforcePermission();
+
         return getGsiService().isGsiEnabled();
     }
 
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean remove() throws RemoteException {
+        super.remove_enforcePermission();
+
         try {
             GsiServiceCallback callback = new GsiServiceCallback();
             synchronized (callback) {
@@ -205,6 +221,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean setEnable(boolean enable, boolean oneShot) throws RemoteException {
+        super.setEnable_enforcePermission();
+
         IGsiService gsiService = getGsiService();
         if (enable) {
             try {
@@ -229,6 +247,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean setAshmem(ParcelFileDescriptor ashmem, long size) {
+        super.setAshmem_enforcePermission();
+
         try {
             return getGsiService().setGsiAshmem(ashmem, size);
         } catch (RemoteException e) {
@@ -239,6 +259,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean submitFromAshmem(long size) {
+        super.submitFromAshmem_enforcePermission();
+
         try {
             return getGsiService().commitGsiChunkFromAshmem(size);
         } catch (RemoteException e) {
@@ -249,6 +271,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean getAvbPublicKey(AvbPublicKey dst) {
+        super.getAvbPublicKey_enforcePermission();
+
         try {
             return getGsiService().getAvbPublicKey(dst) == 0;
         } catch (RemoteException e) {
@@ -259,6 +283,8 @@
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public long suggestScratchSize() throws RemoteException {
+        super.suggestScratchSize_enforcePermission();
+
         return getGsiService().suggestScratchSize();
     }
 }
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index e529010..7d2e276 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -466,7 +466,8 @@
     public static boolean isEmergencyGestureSettingEnabled(Context context, int userId) {
         return isEmergencyGestureEnabled(context.getResources())
                 && Settings.Secure.getIntForUser(context.getContentResolver(),
-                Settings.Secure.EMERGENCY_GESTURE_ENABLED, 1, userId) != 0;
+                Settings.Secure.EMERGENCY_GESTURE_ENABLED,
+                isDefaultEmergencyGestureEnabled(context.getResources()) ? 1 : 0, userId) != 0;
     }
 
     /**
@@ -513,6 +514,11 @@
         return resources.getBoolean(com.android.internal.R.bool.config_emergencyGestureEnabled);
     }
 
+    private static boolean isDefaultEmergencyGestureEnabled(Resources resources) {
+        return resources.getBoolean(
+                com.android.internal.R.bool.config_defaultEmergencyGestureEnabled);
+    }
+
     /**
      * Whether GestureLauncherService should be enabled according to system properties.
      */
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index d29e25c..5d54b6c 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -867,6 +867,8 @@
     public void shutdown() {
         // TODO: remove from aidl if nobody calls externally
 
+        super.shutdown_enforcePermission();
+
         Slog.i(TAG, "Shutting down");
     }
 
@@ -1207,6 +1209,8 @@
     @Override
     public boolean setDataSaverModeEnabled(boolean enable) {
 
+        super.setDataSaverModeEnabled_enforcePermission();
+
         if (DBG) Log.d(TAG, "setDataSaverMode: " + enable);
         synchronized (mQuotaLock) {
             if (mDataSaverMode == enable) {
@@ -1744,6 +1748,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY)
     @Override
     public boolean isNetworkRestricted(int uid) {
+        super.isNetworkRestricted_enforcePermission();
+
         return isNetworkRestrictedInternal(uid);
     }
 
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c1c9fbb..b4ab254 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.");
@@ -442,6 +447,13 @@
                 thread.start();
                 break;
             case LEVEL_FACTORY_RESET:
+                // Before the completion of Reboot, if any crash happens then PackageWatchdog
+                // escalates to next level i.e. factory reset, as they happen in separate threads.
+                // Adding a check to prevent factory reset to execute before above reboot completes.
+                // Note: this reboot property is not persistent resets after reboot is completed.
+                if (isRebootPropertySet()) {
+                    break;
+                }
                 SystemProperties.set(PROP_ATTEMPTING_FACTORY_RESET, "true");
                 runnable = new Runnable() {
                     @Override
diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java
index e915fa1..ff903a0 100644
--- a/services/core/java/com/android/server/SerialService.java
+++ b/services/core/java/com/android/server/SerialService.java
@@ -37,6 +37,8 @@
 
     @EnforcePermission(android.Manifest.permission.SERIAL_PORT)
     public String[] getSerialPorts() {
+        super.getSerialPorts_enforcePermission();
+
         ArrayList<String> ports = new ArrayList<String>();
         for (int i = 0; i < mSerialPorts.length; i++) {
             String path = mSerialPorts[i];
@@ -51,6 +53,8 @@
 
     @EnforcePermission(android.Manifest.permission.SERIAL_PORT)
     public ParcelFileDescriptor openSerialPort(String path) {
+        super.openSerialPort_enforcePermission();
+
         for (int i = 0; i < mSerialPorts.length; i++) {
             if (mSerialPorts[i].equals(path)) {
                 return native_open(path);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 72876f6..f88f99b 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -672,6 +672,7 @@
     private static final int H_COMPLETE_UNLOCK_USER = 14;
     private static final int H_VOLUME_STATE_CHANGED = 15;
     private static final int H_CLOUD_MEDIA_PROVIDER_CHANGED = 16;
+    private static final int H_SECURE_KEYGUARD_STATE_CHANGED = 17;
 
     class StorageManagerServiceHandler extends Handler {
         public StorageManagerServiceHandler(Looper looper) {
@@ -802,12 +803,28 @@
                     break;
                 }
                 case H_CLOUD_MEDIA_PROVIDER_CHANGED: {
-                    final Object listener = msg.obj;
-                    if (listener instanceof StorageManagerInternal.CloudProviderChangeListener) {
-                        notifyCloudMediaProviderChangedAsync(
-                                (StorageManagerInternal.CloudProviderChangeListener) listener);
+                    // We send this message in two cases:
+                    // 1. After the cloud provider has been set/updated for a user.
+                    //    In this case Message's #arg1 is set to UserId, and #obj is set to the
+                    //    authority of the new cloud provider.
+                    // 2. After a new CloudProviderChangeListener is registered.
+                    //    In this case Message's #obj is set to the CloudProviderChangeListener.
+                    if (msg.obj instanceof StorageManagerInternal.CloudProviderChangeListener) {
+                        final StorageManagerInternal.CloudProviderChangeListener listener =
+                                (StorageManagerInternal.CloudProviderChangeListener) msg.obj;
+                        notifyCloudMediaProviderChangedAsync(listener);
                     } else {
-                        onCloudMediaProviderChangedAsync(msg.arg1);
+                        final int userId = msg.arg1;
+                        final String authority = (String) msg.obj;
+                        onCloudMediaProviderChangedAsync(userId, authority);
+                    }
+                    break;
+                }
+                case H_SECURE_KEYGUARD_STATE_CHANGED: {
+                    try {
+                        mVold.onSecureKeyguardStateChanged((boolean) msg.obj);
+                    } catch (Exception e) {
+                        Slog.wtf(TAG, e);
                     }
                     break;
                 }
@@ -1234,12 +1251,12 @@
     public void onKeyguardStateChanged(boolean isShowing) {
         // Push down current secure keyguard status so that we ignore malicious
         // USB devices while locked.
-        mSecureKeyguardShowing = isShowing
+        boolean isSecureKeyguardShowing = isShowing
                 && mContext.getSystemService(KeyguardManager.class).isDeviceSecure(mCurrentUserId);
-        try {
-            mVold.onSecureKeyguardStateChanged(mSecureKeyguardShowing);
-        } catch (Exception e) {
-            Slog.wtf(TAG, e);
+        if (mSecureKeyguardShowing != isSecureKeyguardShowing) {
+            mSecureKeyguardShowing = isSecureKeyguardShowing;
+            mHandler.obtainMessage(H_SECURE_KEYGUARD_STATE_CHANGED, mSecureKeyguardShowing)
+                    .sendToTarget();
         }
     }
 
@@ -1251,6 +1268,8 @@
     // Binder entry point for kicking off an immediate fstrim
     @Override
     public void runMaintenance() {
+        super.runMaintenance_enforcePermission();
+
         runIdleMaintenance(null);
     }
 
@@ -1684,17 +1703,15 @@
             @NonNull StorageManagerInternal.CloudProviderChangeListener listener) {
         synchronized (mCloudMediaProviders) {
             for (int i = mCloudMediaProviders.size() - 1; i >= 0; --i) {
-                listener.onCloudProviderChanged(
-                        mCloudMediaProviders.keyAt(i), mCloudMediaProviders.valueAt(i));
+                final int userId = mCloudMediaProviders.keyAt(i);
+                final String authority = mCloudMediaProviders.valueAt(i);
+                listener.onCloudProviderChanged(userId, authority);
             }
         }
     }
 
-    private void onCloudMediaProviderChangedAsync(int userId) {
-        final String authority;
-        synchronized (mCloudMediaProviders) {
-            authority = mCloudMediaProviders.get(userId);
-        }
+    private void onCloudMediaProviderChangedAsync(
+            @UserIdInt int userId, @Nullable String authority) {
         for (StorageManagerInternal.CloudProviderChangeListener listener :
                 mStorageManagerInternal.mCloudProviderChangeListeners) {
             listener.onCloudProviderChanged(userId, authority);
@@ -2167,6 +2184,8 @@
     @Override
     public void shutdown(final IStorageShutdownObserver observer) {
 
+        super.shutdown_enforcePermission();
+
         Slog.i(TAG, "Shutting down");
         mHandler.obtainMessage(H_SHUTDOWN, observer).sendToTarget();
     }
@@ -2175,6 +2194,8 @@
     @Override
     public void mount(String volId) {
 
+        super.mount_enforcePermission();
+
         final VolumeInfo vol = findVolumeByIdOrThrow(volId);
         if (isMountDisallowed(vol)) {
             throw new SecurityException("Mounting " + volId + " restricted by policy");
@@ -2243,6 +2264,8 @@
     @Override
     public void unmount(String volId) {
 
+        super.unmount_enforcePermission();
+
         final VolumeInfo vol = findVolumeByIdOrThrow(volId);
         unmount(vol);
     }
@@ -2267,6 +2290,8 @@
     @Override
     public void format(String volId) {
 
+        super.format_enforcePermission();
+
         final VolumeInfo vol = findVolumeByIdOrThrow(volId);
         final String fsUuid = vol.fsUuid;
         try {
@@ -2286,6 +2311,8 @@
     @Override
     public void benchmark(String volId, IVoldTaskListener listener) {
 
+        super.benchmark_enforcePermission();
+
         try {
             mVold.benchmark(volId, new IVoldTaskListener.Stub() {
                 @Override
@@ -2325,6 +2352,8 @@
     @Override
     public void partitionPublic(String diskId) {
 
+        super.partitionPublic_enforcePermission();
+
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
@@ -2337,6 +2366,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
     @Override
     public void partitionPrivate(String diskId) {
+        super.partitionPrivate_enforcePermission();
+
         enforceAdminUser();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
@@ -2351,6 +2382,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
     @Override
     public void partitionMixed(String diskId, int ratio) {
+        super.partitionMixed_enforcePermission();
+
         enforceAdminUser();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
@@ -2366,6 +2399,8 @@
     @Override
     public void setVolumeNickname(String fsUuid, String nickname) {
 
+        super.setVolumeNickname_enforcePermission();
+
         Objects.requireNonNull(fsUuid);
         synchronized (mLock) {
             final VolumeRecord rec = mRecords.get(fsUuid);
@@ -2379,6 +2414,8 @@
     @Override
     public void setVolumeUserFlags(String fsUuid, int flags, int mask) {
 
+        super.setVolumeUserFlags_enforcePermission();
+
         Objects.requireNonNull(fsUuid);
         synchronized (mLock) {
             final VolumeRecord rec = mRecords.get(fsUuid);
@@ -2392,6 +2429,8 @@
     @Override
     public void forgetVolume(String fsUuid) {
 
+        super.forgetVolume_enforcePermission();
+
         Objects.requireNonNull(fsUuid);
 
         synchronized (mLock) {
@@ -2416,6 +2455,8 @@
     @Override
     public void forgetAllVolumes() {
 
+        super.forgetAllVolumes_enforcePermission();
+
         synchronized (mLock) {
             for (int i = 0; i < mRecords.size(); i++) {
                 final String fsUuid = mRecords.keyAt(i);
@@ -2448,6 +2489,8 @@
     @Override
     public void fstrim(int flags, IVoldTaskListener listener) {
 
+        super.fstrim_enforcePermission();
+
         try {
             // Block based checkpoint process runs fstrim. So, if checkpoint is in progress
             // (first boot after OTA), We skip idle maintenance and make sure the last
@@ -2742,6 +2785,8 @@
     @Override
     public void setDebugFlags(int flags, int mask) {
 
+        super.setDebugFlags_enforcePermission();
+
         if ((mask & (StorageManager.DEBUG_ADOPTABLE_FORCE_ON
                 | StorageManager.DEBUG_ADOPTABLE_FORCE_OFF)) != 0) {
             final String value;
@@ -2812,6 +2857,8 @@
     @Override
     public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {
 
+        super.setPrimaryStorageUuid_enforcePermission();
+
         final VolumeInfo from;
         final VolumeInfo to;
 
@@ -3020,6 +3067,8 @@
      */
     @Override
     public boolean needsCheckpoint() throws RemoteException {
+        super.needsCheckpoint_enforcePermission();
+
         return mVold.needsCheckpoint();
     }
 
@@ -3040,6 +3089,8 @@
     @Override
     public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
 
+        super.createUserKey_enforcePermission();
+
         try {
             mVold.createUserKey(userId, serialNumber, ephemeral);
             // New keys are always unlocked.
@@ -3055,6 +3106,8 @@
     @Override
     public void destroyUserKey(int userId) {
 
+        super.destroyUserKey_enforcePermission();
+
         try {
             mVold.destroyUserKey(userId);
             // Destroying a key also locks it.
@@ -3070,6 +3123,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
     public void setUserKeyProtection(@UserIdInt int userId, byte[] secret) throws RemoteException {
+        super.setUserKeyProtection_enforcePermission();
+
         mVold.setUserKeyProtection(userId, HexDump.toHexString(secret));
     }
 
@@ -3078,6 +3133,8 @@
     @Override
     public void unlockUserKey(@UserIdInt int userId, int serialNumber, byte[] secret)
         throws RemoteException {
+        super.unlockUserKey_enforcePermission();
+
         if (StorageManager.isFileEncrypted()) {
             mVold.unlockUserKey(userId, serialNumber, HexDump.toHexString(secret));
         }
@@ -3090,6 +3147,8 @@
     @Override
     public void lockUserKey(int userId) {
         //  Do not lock user 0 data for headless system user
+        super.lockUserKey_enforcePermission();
+
         if (userId == UserHandle.USER_SYSTEM
                 && UserManager.isHeadlessSystemUserMode()) {
             throw new IllegalArgumentException("Headless system user data cannot be locked..");
@@ -3153,6 +3212,8 @@
     @Override
     public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
 
+        super.prepareUserStorage_enforcePermission();
+
         try {
             prepareUserStorageInternal(volumeUuid, userId, serialNumber, flags);
         } catch (Exception e) {
@@ -3196,6 +3257,8 @@
     @Override
     public void destroyUserStorage(String volumeUuid, int userId, int flags) {
 
+        super.destroyUserStorage_enforcePermission();
+
         try {
             mVold.destroyUserStorage(volumeUuid, userId, flags);
         } catch (Exception e) {
@@ -4247,6 +4310,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
     @Override
     public int getExternalStorageMountMode(int uid, String packageName) {
+        super.getExternalStorageMountMode_enforcePermission();
+
         return mStorageManagerInternal.getExternalStorageMountMode(uid, packageName);
     }
 
@@ -4781,7 +4846,7 @@
         public void registerCloudProviderChangeListener(
                 @NonNull StorageManagerInternal.CloudProviderChangeListener listener) {
             mCloudProviderChangeListeners.add(listener);
-            mHandler.obtainMessage(H_CLOUD_MEDIA_PROVIDER_CHANGED, listener);
+            mHandler.obtainMessage(H_CLOUD_MEDIA_PROVIDER_CHANGED, listener).sendToTarget();
         }
     }
 }
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/SystemService.java b/services/core/java/com/android/server/SystemService.java
index e40f001..933d259 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -473,18 +473,6 @@
     }
 
     /**
-     * The {@link UserManager#isUserVisible() user visibility} changed.
-     *
-     * <p>This callback is called before the user starts or is switched to (or after it stops), when
-     * its visibility changed because of that action.
-     *
-     * @hide
-     */
-    // NOTE: change visible to int if this method becomes a @SystemApi
-    public void onUserVisibilityChanged(@NonNull TargetUser user, boolean visible) {
-    }
-
-    /**
      * Called when an existing user is stopping, for system services to finalize any per-user
      * state they maintain for running users.  This is called prior to sending the SHUTDOWN
      * broadcast to the user; it is a good place to stop making use of any resources of that
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 83d86cd..a05b84b 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -82,10 +82,6 @@
     private static final String USER_STOPPING = "Stop"; // Logged as onUserStopping()
     private static final String USER_STOPPED = "Cleanup"; // Logged as onUserStopped()
     private static final String USER_COMPLETED_EVENT = "CompletedEvent"; // onUserCompletedEvent()
-    private static final String USER_VISIBLE = "Visible"; // Logged on onUserVisible() and
-                                                          // onUserStarting() (when visible is true)
-    private static final String USER_INVISIBLE = "Invisible"; // Logged on onUserStopping()
-                                                              // (when visibilityChanged is true)
 
     // The default number of threads to use if lifecycle thread pool is enabled.
     private static final int DEFAULT_MAX_USER_POOL_THREADS = 3;
@@ -354,41 +350,29 @@
     /**
      * Starts the given user.
      */
-    public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId,
-            boolean visible) {
-        EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId, visible ? 1 : 0);
-
+    public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
         final TargetUser targetUser = newTargetUser(userId);
         synchronized (mTargetUsers) {
+            // On Automotive / Headless System User Mode, the system user will be started twice:
+            // - Once by some external or local service that switches the system user to
+            //   the background.
+            // - Once by the ActivityManagerService, when the system is marked ready.
+            // These two events are not synchronized and the order of execution is
+            // non-deterministic. To avoid starting the system user twice, verify whether
+            // the system user has already been started by checking the mTargetUsers.
+            // TODO(b/242195409): this workaround shouldn't be necessary once we move
+            // the headless-user start logic to UserManager-land.
+            if (userId == UserHandle.USER_SYSTEM && mTargetUsers.contains(userId)) {
+                Slog.e(TAG, "Skipping starting system user twice");
+                return;
+            }
             mTargetUsers.put(userId, targetUser);
         }
-
-        if (visible) {
-            // Must send the user visiiblity change first, for 2 reasons:
-            // 1. Automotive need to update the user-zone mapping ASAP and it's one of the few
-            // services listening to this event (OTOH, there  are manyy listeners to USER_STARTING
-            // and some can take a while to process it)
-            // 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
-            onUser(t, USER_VISIBLE, /* prevUser= */ null, targetUser);
-        }
+        EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
         onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
     }
 
     /**
-     * Updates the user visibility.
-     *
-     * <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
-     */
-    public void onUserVisible(@UserIdInt int userId) {
-        EventLog.writeEvent(EventLogTags.SSM_USER_VISIBLE, userId);
-        onUser(USER_VISIBLE, userId);
-    }
-
-    /**
      * Unlocks the given user.
      */
     public void onUserUnlocking(@UserIdInt int userId) {
@@ -435,12 +419,9 @@
     /**
      * Stops the given user.
      */
-    public void onUserStopping(@UserIdInt int userId, boolean visibilityChanged) {
-        EventLog.writeEvent(EventLogTags.SSM_USER_STOPPING, userId, visibilityChanged ? 1 : 0);
+    public void onUserStopping(@UserIdInt int userId) {
+        EventLog.writeEvent(EventLogTags.SSM_USER_STOPPING, userId);
         onUser(USER_STOPPING, userId);
-        if (visibilityChanged) {
-            onUser(USER_INVISIBLE, userId);
-        }
     }
 
     /**
@@ -563,12 +544,6 @@
                         threadPool.submit(getOnUserCompletedEventRunnable(
                                 t, service, serviceName, curUser, completedEventType));
                         break;
-                    case USER_VISIBLE:
-                        service.onUserVisibilityChanged(curUser, /* visible= */ true);
-                        break;
-                    case USER_INVISIBLE:
-                        service.onUserVisibilityChanged(curUser, /* visible= */ false);
-                        break;
                     default:
                         throw new IllegalArgumentException(onWhat + " what?");
                 }
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 2652ebe..32afcca 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -52,8 +52,8 @@
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SrvccState;
 import android.telephony.BarringInfo;
-import android.telephony.CallAttributes;
 import android.telephony.CallQuality;
+import android.telephony.CallState;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
 import android.telephony.CellSignalStrength;
@@ -82,6 +82,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -349,9 +350,9 @@
 
     private CallQuality[] mCallQuality;
 
-    private CallAttributes[] mCallAttributes;
+    private ArrayList<List<CallState>> mCallStateLists;
 
-    // network type of the call associated with the mCallAttributes and mCallQuality
+    // network type of the call associated with the mCallStateLists and mCallQuality
     private int[] mCallNetworkType;
 
     private int[] mSrvccState;
@@ -558,11 +559,10 @@
                     if (VDBG) log("MSG_USER_SWITCHED userId=" + msg.arg1);
                     int numPhones = getTelephonyManager().getActiveModemCount();
                     for (int phoneId = 0; phoneId < numPhones; phoneId++) {
-                        int[] subIds = SubscriptionManager.getSubId(phoneId);
-                        int subId =
-                                (subIds != null) && (subIds.length > 0)
-                                        ? subIds[0]
-                                        : SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+                        int subId = SubscriptionManager.getSubscriptionId(phoneId);
+                        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+                            subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+                        }
                         TelephonyRegistry.this.notifyCellLocationForSubscriber(
                                 subId, mCellIdentity[phoneId], true /* hasUserSwitched */);
                     }
@@ -688,7 +688,6 @@
             mCallPreciseDisconnectCause = copyOf(mCallPreciseDisconnectCause, mNumPhones);
             mCallQuality = copyOf(mCallQuality, mNumPhones);
             mCallNetworkType = copyOf(mCallNetworkType, mNumPhones);
-            mCallAttributes = copyOf(mCallAttributes, mNumPhones);
             mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
             mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
             mTelephonyDisplayInfos = copyOf(mTelephonyDisplayInfos, mNumPhones);
@@ -708,6 +707,7 @@
                 cutListToSize(mLinkCapacityEstimateLists, mNumPhones);
                 cutListToSize(mCarrierPrivilegeStates, mNumPhones);
                 cutListToSize(mCarrierServiceStates, mNumPhones);
+                cutListToSize(mCallStateLists, mNumPhones);
                 return;
             }
 
@@ -731,8 +731,7 @@
                 mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
                 mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
                 mCallQuality[i] = createCallQuality();
-                mCallAttributes[i] = new CallAttributes(createPreciseCallState(),
-                        TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality());
+                mCallStateLists.add(i, new ArrayList<>());
                 mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
                 mPreciseCallState[i] = createPreciseCallState();
                 mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
@@ -800,7 +799,7 @@
         mCallPreciseDisconnectCause = new int[numPhones];
         mCallQuality = new CallQuality[numPhones];
         mCallNetworkType = new int[numPhones];
-        mCallAttributes = new CallAttributes[numPhones];
+        mCallStateLists = new ArrayList<>();
         mPreciseDataConnectionStates = new ArrayList<>();
         mCellInfo = new ArrayList<>(numPhones);
         mImsReasonInfo = new ArrayList<>();
@@ -838,8 +837,7 @@
             mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
             mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
             mCallQuality[i] = createCallQuality();
-            mCallAttributes[i] = new CallAttributes(createPreciseCallState(),
-                    TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality());
+            mCallStateLists.add(i, new ArrayList<>());
             mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
             mPreciseCallState[i] = createPreciseCallState();
             mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
@@ -1043,7 +1041,7 @@
             String callingFeatureId, IPhoneStateListener callback,
             int[] events, boolean notifyNow) {
         Set<Integer> eventList = Arrays.stream(events).boxed().collect(Collectors.toSet());
-        listen(renounceFineLocationAccess, renounceFineLocationAccess, callingPackage,
+        listen(renounceFineLocationAccess, renounceCoarseLocationAccess, callingPackage,
                 callingFeatureId, callback, eventList, notifyNow, subId);
     }
 
@@ -1337,7 +1335,7 @@
                 }
                 if (events.contains(TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)) {
                     try {
-                        r.callback.onCallAttributesChanged(mCallAttributes[r.phoneId]);
+                        r.callback.onCallStatesChanged(mCallStateLists.get(r.phoneId));
                     } catch (RemoteException ex) {
                         remove(r.binder);
                     }
@@ -1608,7 +1606,7 @@
                             if (DBG) {
                                 log("notifyServiceStateForSubscriber: callback.onSSC r=" + r
                                         + " subId=" + subId + " phoneId=" + phoneId
-                                        + " state=" + state);
+                                        + " state=" + stateToSend);
                             }
                             r.callback.onServiceStateChanged(stateToSend);
                         } catch (RemoteException ex) {
@@ -2172,11 +2170,30 @@
         }
     }
 
-    public void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
-                                       int foregroundCallState, int backgroundCallState) {
+    /**
+     * Send a notification to registrants that the precise call state has changed.
+     *
+     * @param phoneId the phoneId carrying the data connection
+     * @param subId the subscriptionId for the data connection
+     * @param callStates Array of PreciseCallState of foreground, background & ringing calls.
+     * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId()} for
+     *                   ringing, foreground & background calls.
+     * @param imsServiceTypes Array of IMS call service type for ringing, foreground &
+     *                        background calls.
+     * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
+     */
+    public void notifyPreciseCallState(int phoneId, int subId,
+            @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
+            @Annotation.ImsCallServiceType int[] imsServiceTypes,
+            @Annotation.ImsCallType int[] imsCallTypes) {
         if (!checkNotifyPermission("notifyPreciseCallState()")) {
             return;
         }
+
+        int ringingCallState = callStates[CallState.CALL_CLASSIFICATION_RINGING];
+        int foregroundCallState = callStates[CallState.CALL_CLASSIFICATION_FOREGROUND];
+        int backgroundCallState = callStates[CallState.CALL_CLASSIFICATION_BACKGROUND];
+
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 mRingingCallState[phoneId] = ringingCallState;
@@ -2187,11 +2204,11 @@
                         backgroundCallState,
                         DisconnectCause.NOT_VALID,
                         PreciseDisconnectCause.NOT_VALID);
-                boolean notifyCallAttributes = true;
+                boolean notifyCallState = true;
                 if (mCallQuality == null) {
                     log("notifyPreciseCallState: mCallQuality is null, "
                             + "skipping call attributes");
-                    notifyCallAttributes = false;
+                    notifyCallState = false;
                 } else {
                     // If the precise call state is no longer active, reset the call network type
                     // and call quality.
@@ -2200,8 +2217,65 @@
                         mCallNetworkType[phoneId] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
                         mCallQuality[phoneId] = createCallQuality();
                     }
-                    mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
-                            mCallNetworkType[phoneId], mCallQuality[phoneId]);
+                    mCallStateLists.get(phoneId).clear();
+                    if (foregroundCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID
+                            && foregroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
+                        CallQuality callQuality = mCallQuality[phoneId];
+                        CallState.Builder builder = new CallState.Builder(
+                                callStates[CallState.CALL_CLASSIFICATION_FOREGROUND])
+                                .setNetworkType(mCallNetworkType[phoneId])
+                                .setCallQuality(callQuality)
+                                .setCallClassification(
+                                        CallState.CALL_CLASSIFICATION_FOREGROUND);
+                        if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) {
+                            builder = builder
+                                    .setImsCallSessionId(imsCallIds[
+                                            CallState.CALL_CLASSIFICATION_FOREGROUND])
+                                    .setImsCallServiceType(imsServiceTypes[
+                                            CallState.CALL_CLASSIFICATION_FOREGROUND])
+                                    .setImsCallType(imsCallTypes[
+                                            CallState.CALL_CLASSIFICATION_FOREGROUND]);
+                        }
+                        mCallStateLists.get(phoneId).add(builder.build());
+                    }
+                    if (backgroundCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID
+                            && backgroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
+                        CallState.Builder builder = new CallState.Builder(
+                                callStates[CallState.CALL_CLASSIFICATION_BACKGROUND])
+                                .setNetworkType(mCallNetworkType[phoneId])
+                                .setCallQuality(createCallQuality())
+                                .setCallClassification(
+                                        CallState.CALL_CLASSIFICATION_BACKGROUND);
+                        if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) {
+                            builder = builder
+                                    .setImsCallSessionId(imsCallIds[
+                                            CallState.CALL_CLASSIFICATION_BACKGROUND])
+                                    .setImsCallServiceType(imsServiceTypes[
+                                            CallState.CALL_CLASSIFICATION_BACKGROUND])
+                                    .setImsCallType(imsCallTypes[
+                                            CallState.CALL_CLASSIFICATION_BACKGROUND]);
+                        }
+                        mCallStateLists.get(phoneId).add(builder.build());
+                    }
+                    if (ringingCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID
+                            && ringingCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
+                        CallState.Builder builder = new CallState.Builder(
+                                callStates[CallState.CALL_CLASSIFICATION_RINGING])
+                                .setNetworkType(mCallNetworkType[phoneId])
+                                .setCallQuality(createCallQuality())
+                                .setCallClassification(
+                                        CallState.CALL_CLASSIFICATION_RINGING);
+                        if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) {
+                            builder = builder
+                                    .setImsCallSessionId(imsCallIds[
+                                            CallState.CALL_CLASSIFICATION_RINGING])
+                                    .setImsCallServiceType(imsServiceTypes[
+                                            CallState.CALL_CLASSIFICATION_RINGING])
+                                    .setImsCallType(imsCallTypes[
+                                            CallState.CALL_CLASSIFICATION_RINGING]);
+                        }
+                        mCallStateLists.get(phoneId).add(builder.build());
+                    }
                 }
 
                 for (Record r : mRecords) {
@@ -2214,11 +2288,11 @@
                             mRemoveList.add(r.binder);
                         }
                     }
-                    if (notifyCallAttributes && r.matchTelephonyCallbackEvent(
+                    if (notifyCallState && r.matchTelephonyCallbackEvent(
                             TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
                             && idMatch(r, subId, phoneId)) {
                         try {
-                            r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
+                            r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
                         } catch (RemoteException ex) {
                             mRemoveList.add(r.binder);
                         }
@@ -2516,15 +2590,29 @@
                 // merge CallQuality with PreciseCallState and network type
                 mCallQuality[phoneId] = callQuality;
                 mCallNetworkType[phoneId] = callNetworkType;
-                mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
-                        callNetworkType, callQuality);
+                if (mCallStateLists.get(phoneId).size() > 0
+                        && mCallStateLists.get(phoneId).get(0).getCallState()
+                        == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
+                    CallState prev = mCallStateLists.get(phoneId).remove(0);
+                    mCallStateLists.get(phoneId).add(
+                            0, new CallState.Builder(prev.getCallState())
+                                    .setNetworkType(callNetworkType)
+                                    .setCallQuality(callQuality)
+                                    .setCallClassification(prev.getCallClassification())
+                                    .setImsCallSessionId(prev.getImsCallSessionId())
+                                    .setImsCallServiceType(prev.getImsCallServiceType())
+                                    .setImsCallType(prev.getImsCallType()).build());
+                } else {
+                    log("There is no active call to report CallQaulity");
+                    return;
+                }
 
                 for (Record r : mRecords) {
                     if (r.matchTelephonyCallbackEvent(
                             TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
                             && idMatch(r, subId, phoneId)) {
                         try {
-                            r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
+                            r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
                         } catch (RemoteException ex) {
                             mRemoveList.add(r.binder);
                         }
@@ -2992,7 +3080,6 @@
                 pw.println("mSrvccState=" + mSrvccState[i]);
                 pw.println("mCallPreciseDisconnectCause=" + mCallPreciseDisconnectCause[i]);
                 pw.println("mCallQuality=" + mCallQuality[i]);
-                pw.println("mCallAttributes=" + mCallAttributes[i]);
                 pw.println("mCallNetworkType=" + mCallNetworkType[i]);
                 pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i));
                 pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 76cac93..61f7f30 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -18,8 +18,11 @@
 
 import static android.Manifest.permission.DUMP;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnGatewayConnectionConfig.ALLOWED_CAPABILITIES;
+import static android.net.vcn.VcnManager.VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED;
@@ -53,6 +56,7 @@
 import android.net.vcn.VcnUnderlyingNetworkPolicy;
 import android.net.wifi.WifiInfo;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -68,6 +72,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.Slog;
@@ -83,6 +88,7 @@
 import com.android.server.vcn.VcnContext;
 import com.android.server.vcn.VcnNetworkProvider;
 import com.android.server.vcn.util.PersistableBundleUtils;
+import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -159,6 +165,9 @@
     private static final long DUMP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(5);
     private static final int LOCAL_LOG_LINE_COUNT = 512;
 
+    private static final Set<Integer> RESTRICTED_TRANSPORTS_DEFAULT =
+            Collections.singleton(TRANSPORT_WIFI);
+
     // Public for use in all other VCN classes
     @NonNull public static final LocalLog LOCAL_LOG = new LocalLog(LOCAL_LOG_LINE_COUNT);
 
@@ -361,6 +370,34 @@
         public LocationPermissionChecker newLocationPermissionChecker(@NonNull Context context) {
             return new LocationPermissionChecker(context);
         }
+
+        /** Gets the transports that need to be marked as restricted by the VCN */
+        public Set<Integer> getRestrictedTransports(
+                ParcelUuid subGrp, TelephonySubscriptionSnapshot lastSnapshot) {
+            if (!Build.IS_ENG && !Build.IS_USERDEBUG) {
+                return RESTRICTED_TRANSPORTS_DEFAULT;
+            }
+
+            final PersistableBundleWrapper carrierConfig =
+                    lastSnapshot.getCarrierConfigForSubGrp(subGrp);
+            if (carrierConfig == null) {
+                return RESTRICTED_TRANSPORTS_DEFAULT;
+            }
+
+            final int[] defaultValue =
+                    RESTRICTED_TRANSPORTS_DEFAULT.stream().mapToInt(i -> i).toArray();
+            final int[] restrictedTransportsArray =
+                    carrierConfig.getIntArray(
+                            VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
+                            defaultValue);
+
+            // Convert to a boxed set
+            final Set<Integer> restrictedTransports = new ArraySet<>();
+            for (int transport : restrictedTransportsArray) {
+                restrictedTransports.add(transport);
+            }
+            return restrictedTransports;
+        }
     }
 
     /** Notifies the VcnManagementService that external dependencies can be set up. */
@@ -517,6 +554,7 @@
                     }
                 }
 
+                boolean needNotifyAllPolicyListeners = false;
                 // Schedule teardown of any VCN instances that have lost carrier privileges (after a
                 // delay)
                 for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
@@ -564,6 +602,10 @@
                     } else {
                         // If this VCN's status has not changed, update it with the new snapshot
                         entry.getValue().updateSubscriptionSnapshot(mLastSnapshot);
+                        needNotifyAllPolicyListeners |=
+                                !Objects.equals(
+                                        oldSnapshot.getCarrierConfigForSubGrp(subGrp),
+                                        mLastSnapshot.getCarrierConfigForSubGrp(subGrp));
                     }
                 }
 
@@ -573,6 +615,10 @@
                         getSubGroupToSubIdMappings(mLastSnapshot);
                 if (!currSubGrpMappings.equals(oldSubGrpMappings)) {
                     garbageCollectAndWriteVcnConfigsLocked();
+                    needNotifyAllPolicyListeners = true;
+                }
+
+                if (needNotifyAllPolicyListeners) {
                     notifyAllPolicyListenersLocked();
                 }
             }
@@ -917,6 +963,14 @@
         });
     }
 
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void addVcnUnderlyingNetworkPolicyListenerForTest(
+            @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
+        synchronized (mLock) {
+            addVcnUnderlyingNetworkPolicyListener(listener);
+        }
+    }
+
     /** Removes the provided listener from receiving VcnUnderlyingNetworkPolicy updates. */
     @GuardedBy("mLock")
     @Override
@@ -1000,7 +1054,7 @@
 
             final ParcelUuid subGrp = getSubGroupForNetworkCapabilities(ncCopy);
             boolean isVcnManagedNetwork = false;
-            boolean isRestrictedCarrierWifi = false;
+            boolean isRestricted = false;
             synchronized (mLock) {
                 final Vcn vcn = mVcns.get(subGrp);
                 if (vcn != null) {
@@ -1008,9 +1062,19 @@
                         isVcnManagedNetwork = true;
                     }
 
-                    if (ncCopy.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
-                        // Carrier WiFi always restricted if VCN exists (even in safe mode).
-                        isRestrictedCarrierWifi = true;
+                    final Set<Integer> restrictedTransports =
+                            mDeps.getRestrictedTransports(subGrp, mLastSnapshot);
+                    for (int restrictedTransport : restrictedTransports) {
+                        if (ncCopy.hasTransport(restrictedTransport)) {
+                            if (restrictedTransport == TRANSPORT_CELLULAR) {
+                                // Only make a cell network as restricted when the VCN is in
+                                // active mode.
+                                isRestricted |= (vcn.getStatus() == VCN_STATUS_CODE_ACTIVE);
+                            } else {
+                                isRestricted = true;
+                                break;
+                            }
+                        }
                     }
                 }
             }
@@ -1024,14 +1088,16 @@
                 ncBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
             }
 
-            if (isRestrictedCarrierWifi) {
+            if (isRestricted) {
                 ncBuilder.removeCapability(
                         NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
             }
 
             final NetworkCapabilities result = ncBuilder.build();
             final VcnUnderlyingNetworkPolicy policy = new VcnUnderlyingNetworkPolicy(
-                    mTrackingNetworkCallback.requiresRestartForCarrierWifi(result), result);
+                    mTrackingNetworkCallback
+                            .requiresRestartForImmutableCapabilityChanges(result),
+                    result);
 
             logVdbg("getUnderlyingNetworkPolicy() called for caps: " + networkCapabilities
                         + "; and lp: " + linkProperties + "; result = " + policy);
@@ -1296,15 +1362,38 @@
             }
         }
 
-        private boolean requiresRestartForCarrierWifi(NetworkCapabilities caps) {
-            if (!caps.hasTransport(TRANSPORT_WIFI) || caps.getSubscriptionIds() == null) {
+        private Set<Integer> getNonTestTransportTypes(NetworkCapabilities caps) {
+            final Set<Integer> transportTypes = new ArraySet<>();
+            for (int t : caps.getTransportTypes()) {
+                transportTypes.add(t);
+            }
+            return transportTypes;
+        }
+
+        private boolean hasSameTransportsAndCapabilities(
+                NetworkCapabilities caps, NetworkCapabilities capsOther) {
+            if (!Objects.equals(
+                    getNonTestTransportTypes(caps), getNonTestTransportTypes(capsOther))) {
+                return false;
+            }
+
+            for (int capability : ALLOWED_CAPABILITIES) {
+                if (caps.hasCapability(capability) != capsOther.hasCapability(capability)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private boolean requiresRestartForImmutableCapabilityChanges(NetworkCapabilities caps) {
+            if (caps.getSubscriptionIds() == null) {
                 return false;
             }
 
             synchronized (mCaps) {
                 for (NetworkCapabilities existing : mCaps.values()) {
-                    if (existing.hasTransport(TRANSPORT_WIFI)
-                            && caps.getSubscriptionIds().equals(existing.getSubscriptionIds())) {
+                    if (caps.getSubscriptionIds().equals(existing.getSubscriptionIds())
+                            && hasSameTransportsAndCapabilities(caps, existing)) {
                         // Restart if any immutable capabilities have changed
                         return existing.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
                                 != caps.hasCapability(NET_CAPABILITY_NOT_RESTRICTED);
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index 3f1d1fe..ae50b23 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -186,6 +186,10 @@
         synchronized (mVpns) {
             for (int i = 0; i < mVpns.size(); i++) {
                 pw.println(mVpns.keyAt(i) + ": " + mVpns.valueAt(i).getPackage());
+                pw.increaseIndent();
+                mVpns.valueAt(i).dump(pw);
+                pw.decreaseIndent();
+                pw.println();
             }
             pw.decreaseIndent();
         }
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/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 672ee0e..a2755be 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -45,6 +45,9 @@
 import android.app.admin.DevicePolicyEventLogger;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.BroadcastReceiver;
 import android.content.ClipData;
 import android.content.ComponentName;
@@ -70,6 +73,7 @@
 import android.database.sqlite.SQLiteFullException;
 import android.database.sqlite.SQLiteStatement;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
@@ -196,6 +200,14 @@
     private static final int SIGNATURE_CHECK_MATCH = 1;
     private static final int SIGNATURE_CHECK_UID_MATCH = 2;
 
+    /**
+     * Apps targeting Android U and above need to declare the package visibility needs in the
+     * manifest to access the AccountManager APIs.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    private static final long ENFORCE_PACKAGE_VISIBILITY_FILTERING = 154726397;
+
     static {
         ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
         ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
@@ -527,8 +539,8 @@
      */
     private Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName,
             List<String> accountTypes, Integer callingUid, UserAccounts accounts) {
-        if (!packageExistsForUser(packageName, accounts.userId)) {
-            Log.d(TAG, "Package not found " + packageName);
+        if (!canCallerAccessPackage(packageName, callingUid, accounts.userId)) {
+            Log.w(TAG, "getAccountsAndVisibilityForPackage#Package not found " + packageName);
             return new LinkedHashMap<>();
         }
 
@@ -629,6 +641,9 @@
                    return AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE;
                 }
             }
+            if (!canCallerAccessPackage(packageName, callingUid, accounts.userId)) {
+                return AccountManager.VISIBILITY_NOT_VISIBLE;
+            }
             return resolveAccountVisibility(account, packageName, accounts);
         } finally {
             restoreCallingIdentity(identityToken);
@@ -677,7 +692,7 @@
                 restoreCallingIdentity(identityToken);
             }
         } catch (NameNotFoundException e) {
-            Log.d(TAG, "Package not found " + e.getMessage());
+            Log.w(TAG, "resolveAccountVisibility#Package not found " + e.getMessage());
             return AccountManager.VISIBILITY_NOT_VISIBLE;
         }
 
@@ -756,7 +771,7 @@
             }
             return true;
         } catch (NameNotFoundException e) {
-            Log.d(TAG, "Package not found " + e.getMessage());
+            Log.w(TAG, "isPreOApplication#Package not found " + e.getMessage());
             return true;
         }
     }
@@ -779,7 +794,7 @@
         try {
             UserAccounts accounts = getUserAccounts(userId);
             return setAccountVisibility(account, packageName, newVisibility, true /* notify */,
-                accounts);
+                    accounts, callingUid);
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -798,11 +813,12 @@
      * @param newVisibility New visibility calue
      * @param notify if the flag is set applications will get notification about visibility change
      * @param accounts UserAccount that currently hosts the account and application
+     * @param callingUid The caller's uid.
      *
      * @return True if account visibility was changed.
      */
     private boolean setAccountVisibility(Account account, String packageName, int newVisibility,
-            boolean notify, UserAccounts accounts) {
+            boolean notify, UserAccounts accounts, int callingUid) {
         synchronized (accounts.dbLock) {
             synchronized (accounts.cacheLock) {
                 Map<String, Integer> packagesToVisibility;
@@ -813,8 +829,8 @@
                                 getRequestingPackages(account, accounts);
                         accountRemovedReceivers = getAccountRemovedReceivers(account, accounts);
                     } else {
-                        if (!packageExistsForUser(packageName, accounts.userId)) {
-                            return false; // package is not installed.
+                        if (!canCallerAccessPackage(packageName, callingUid, accounts.userId)) {
+                            return false; // package is not installed or not visible.
                         }
                         packagesToVisibility = new HashMap<>();
                         packagesToVisibility.put(packageName,
@@ -826,8 +842,8 @@
                     }
                 } else {
                     // Notifications will not be send - only used during add account.
-                    if (!isSpecialPackageKey(packageName) &&
-                            !packageExistsForUser(packageName, accounts.userId)) {
+                    if (!isSpecialPackageKey(packageName)
+                            && !canCallerAccessPackage(packageName, callingUid, accounts.userId)) {
                         // package is not installed and not meta value.
                         return false;
                     }
@@ -840,6 +856,7 @@
                 }
 
                 if (notify) {
+                    Log.i(TAG, "Notifying visibility changed for package=" + packageName);
                     for (Entry<String, Integer> packageToVisibility : packagesToVisibility
                             .entrySet()) {
                         int oldVisibility = packageToVisibility.getValue();
@@ -850,9 +867,14 @@
                         }
                     }
                     for (String packageNameToNotify : accountRemovedReceivers) {
-                        sendAccountRemovedBroadcast(account, packageNameToNotify, accounts.userId);
+                        sendAccountRemovedBroadcast(
+                                account,
+                                packageNameToNotify,
+                                accounts.userId,
+                                /*useCase=*/"setAccountVisibility");
                     }
-                    sendAccountsChangedBroadcast(accounts.userId);
+                    sendAccountsChangedBroadcast(
+                            accounts.userId, account.type, /*useCase=*/"setAccountVisibility");
                 }
                 return true;
             }
@@ -973,6 +995,8 @@
      * @param accounts UserAccount that currently hosts the account
      */
     private void notifyPackage(String packageName, UserAccounts accounts) {
+        Log.i(TAG, "notifying package=" + packageName + " for userId=" + accounts.userId
+                +", sending broadcast of " + AccountManager.ACTION_VISIBLE_ACCOUNTS_CHANGED);
         Intent intent = new Intent(AccountManager.ACTION_VISIBLE_ACCOUNTS_CHANGED);
         intent.setPackage(packageName);
         intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -1037,20 +1061,6 @@
         return (receivers != null && receivers.size() > 0);
     }
 
-    private boolean packageExistsForUser(String packageName, int userId) {
-        try {
-            final long identityToken = clearCallingIdentity();
-            try {
-                mPackageManager.getPackageUidAsUser(packageName, userId);
-                return true;
-            } finally {
-                restoreCallingIdentity(identityToken);
-            }
-        } catch (NameNotFoundException e) {
-            return false;
-        }
-    }
-
     /**
      * Returns true if packageName is one of special values.
      */
@@ -1059,13 +1069,21 @@
                 || AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE.equals(packageName));
     }
 
-    private void sendAccountsChangedBroadcast(int userId) {
-        Log.i(TAG, "the accounts changed, sending broadcast of "
-                + ACCOUNTS_CHANGED_INTENT.getAction());
+    private void sendAccountsChangedBroadcast(
+            int userId, String accountType, @NonNull String useCase) {
+        Objects.requireNonNull(useCase, "useCase can't be null");
+        Log.i(TAG, "the accountType= " + (accountType == null ? "" : accountType)
+                + " changed with useCase=" + useCase + " for userId=" + userId
+                + ", sending broadcast of " + ACCOUNTS_CHANGED_INTENT.getAction());
         mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId));
     }
 
-    private void sendAccountRemovedBroadcast(Account account, String packageName, int userId) {
+    private void sendAccountRemovedBroadcast(
+            Account account, String packageName, int userId, @NonNull String useCase) {
+        Objects.requireNonNull(useCase, "useCase can't be null");
+        Log.i(TAG, "the account with type=" + account.type + " removed while useCase="
+                + useCase + " for userId=" + userId + ", sending broadcast of "
+                + AccountManager.ACTION_ACCOUNT_REMOVED);
         Intent intent = new Intent(AccountManager.ACTION_ACCOUNT_REMOVED);
         intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         intent.setPackage(packageName);
@@ -1212,6 +1230,8 @@
                                 accountsDb.endTransaction();
                             }
                             accountDeleted = true;
+                            Log.i(TAG, "validateAccountsInternal#Deleted UserId="
+                                    + accounts.userId + ", AccountId=" + accountId);
 
                             logRecord(AccountsDb.DEBUG_ACTION_AUTHENTICATOR_REMOVE,
                                     AccountsDb.TABLE_ACCOUNTS, accountId, accounts);
@@ -1228,7 +1248,11 @@
                                 }
                             }
                             for (String packageName : accountRemovedReceivers) {
-                                sendAccountRemovedBroadcast(account, packageName, accounts.userId);
+                                sendAccountRemovedBroadcast(
+                                        account,
+                                        packageName,
+                                        accounts.userId,
+                                        /*useCase=*/"validateAccounts");
                             }
                         } else {
                             ArrayList<String> accountNames = accountNamesByType.get(account.type);
@@ -1253,7 +1277,10 @@
                     AccountManager.invalidateLocalAccountsDataCaches();
                 } finally {
                     if (accountDeleted) {
-                        sendAccountsChangedBroadcast(accounts.userId);
+                        sendAccountsChangedBroadcast(
+                                accounts.userId,
+                                /*accountType=*/"ambiguous",
+                                /*useCase=*/"validateAccounts");
                     }
                 }
             }
@@ -1622,7 +1649,7 @@
 
         final long identityToken = clearCallingIdentity();
         try {
-            return getAuthenticatorTypesInternal(userId);
+            return getAuthenticatorTypesInternal(userId, callingUid);
 
         } finally {
             restoreCallingIdentity(identityToken);
@@ -1632,19 +1659,19 @@
     /**
      * Should only be called inside of a clearCallingIdentity block.
      */
-    private AuthenticatorDescription[] getAuthenticatorTypesInternal(int userId) {
+    private AuthenticatorDescription[] getAuthenticatorTypesInternal(int userId, int callingUid) {
         mAuthenticatorCache.updateServices(userId);
         Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>>
                 authenticatorCollection = mAuthenticatorCache.getAllServices(userId);
-        AuthenticatorDescription[] types =
-                new AuthenticatorDescription[authenticatorCollection.size()];
-        int i = 0;
+        final List<AuthenticatorDescription> types =
+                new ArrayList<>(authenticatorCollection.size());
         for (AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticator
                 : authenticatorCollection) {
-            types[i] = authenticator.type;
-            i++;
+            if (canCallerAccessPackage(authenticator.type.packageName, callingUid, userId)) {
+                types.add(authenticator.type);
+            }
         }
-        return types;
+        return types.toArray(new AuthenticatorDescription[types.size()]);
     }
 
     private boolean isCrossUser(int callingUid, int userId) {
@@ -1845,7 +1872,7 @@
                     }
                     if (accounts.accountsDb.findAllDeAccounts().size() > 100) {
                         Log.w(TAG, "insertAccountIntoDatabase: " + account.toSafeString()
-                                + ", skipping since more than 50 accounts on device exist");
+                                + ", skipping since more than 100 accounts on device exist");
                         return false;
                     }
                     long accountId = accounts.accountsDb.insertCeAccount(account, password);
@@ -1878,7 +1905,7 @@
                         for (Entry<String, Integer> entry : packageToVisibility.entrySet()) {
                             setAccountVisibility(account, entry.getKey() /* package */,
                                     entry.getValue() /* visibility */, false /* notify */,
-                                    accounts);
+                                    accounts, callingUid);
                         }
                     }
                     accounts.accountsDb.setTransactionSuccessful();
@@ -1899,7 +1926,9 @@
 
         sendNotificationAccountUpdated(account, accounts);
         // Only send LOGIN_ACCOUNTS_CHANGED when the database changed.
-        sendAccountsChangedBroadcast(accounts.userId);
+        Log.i(TAG, "callingUid=" + callingUid + ", userId=" + accounts.userId
+                + " added account");
+        sendAccountsChangedBroadcast(accounts.userId, account.type, /*useCase=*/"addAccount");
 
         logAddAccountExplicitlyMetrics(opPackageName, account.type, packageToVisibility);
         return true;
@@ -2089,6 +2118,8 @@
         final long identityToken = clearCallingIdentity();
         try {
             UserAccounts accounts = getUserAccounts(userId);
+            Log.i(TAG, "callingUid=" + callingUid + ", userId=" + accounts.userId
+                    + " performing rename account");
             Account resultingAccount = renameAccountInternal(accounts, accountToRename, newName);
             Bundle result = new Bundle();
             result.putString(AccountManager.KEY_ACCOUNT_NAME, resultingAccount.name);
@@ -2199,9 +2230,14 @@
                 }
 
                 sendNotificationAccountUpdated(resultAccount, accounts);
-                sendAccountsChangedBroadcast(accounts.userId);
+                sendAccountsChangedBroadcast(
+                        accounts.userId, accountToRename.type, /*useCase=*/"renameAccount");
                 for (String packageName : accountRemovedReceivers) {
-                    sendAccountRemovedBroadcast(accountToRename, packageName, accounts.userId);
+                    sendAccountRemovedBroadcast(
+                            accountToRename,
+                            packageName,
+                            accounts.userId,
+                            /*useCase=*/"renameAccount");
                 }
 
                 AccountManager.invalidateLocalAccountsDataCaches();
@@ -2433,9 +2469,13 @@
                     }
 
                     // Only broadcast LOGIN_ACCOUNTS_CHANGED if a change occurred.
-                    sendAccountsChangedBroadcast(accounts.userId);
+                    Log.i(TAG, "callingUid=" + callingUid + ", userId=" + accounts.userId
+                            + " removed account");
+                    sendAccountsChangedBroadcast(
+                            accounts.userId, account.type, /*useCase=*/"removeAccount");
                     for (String packageName : accountRemovedReceivers) {
-                        sendAccountRemovedBroadcast(account, packageName, accounts.userId);
+                        sendAccountRemovedBroadcast(
+                                account, packageName, accounts.userId, /*useCase=*/"removeAccount");
                     }
                     String action = userUnlocked ? AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE
                             : AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE_DE;
@@ -2709,7 +2749,9 @@
                     if (isChanged) {
                         // Send LOGIN_ACCOUNTS_CHANGED only if the something changed.
                         sendNotificationAccountUpdated(account, accounts);
-                        sendAccountsChangedBroadcast(accounts.userId);
+                        Log.i(TAG, "callingUid=" + callingUid + " changed password");
+                        sendAccountsChangedBroadcast(
+                                accounts.userId, account.type, /*useCase=*/"setPassword");
                     }
                 }
             }
@@ -3480,10 +3522,10 @@
 
                 @Override
                 protected String toDebugString(long now) {
-                    String requiredFeaturesStr = TextUtils.join(",", requiredFeatures);
                     return super.toDebugString(now) + ", startAddAccountSession" + ", accountType "
                             + accountType + ", requiredFeatures "
-                            + (requiredFeatures != null ? requiredFeaturesStr : null);
+                            + (requiredFeatures != null
+                                ? TextUtils.join(",", requiredFeatures) : "null");
                 }
             }.bind();
         } finally {
@@ -4063,7 +4105,7 @@
             int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
             return hasAccountAccess(account, packageName, uid);
         } catch (NameNotFoundException e) {
-            Log.d(TAG, "Package not found " + e.getMessage());
+            Log.w(TAG, "hasAccountAccess#Package not found " + e.getMessage());
             return false;
         }
     }
@@ -4195,7 +4237,7 @@
         }
         final long token = Binder.clearCallingIdentity();
         try {
-            AccountAndUser[] allAccounts = getAllAccounts();
+            AccountAndUser[] allAccounts = getAllAccountsForSystemProcess();
             for (int i = allAccounts.length - 1; i >= 0; i--) {
                 if (allAccounts[i].account.equals(account)) {
                     return true;
@@ -4345,10 +4387,11 @@
     /**
      * Returns accounts for all running users, ignores visibility values.
      *
+     * Should only be called by System process.
      * @hide
      */
     @NonNull
-    public AccountAndUser[] getRunningAccounts() {
+    public AccountAndUser[] getRunningAccountsForSystem() {
         final int[] runningUserIds;
         try {
             runningUserIds = ActivityManager.getService().getRunningUserIds();
@@ -4356,26 +4399,34 @@
             // Running in system_server; should never happen
             throw new RuntimeException(e);
         }
-        return getAccounts(runningUserIds);
+        return getAccountsForSystem(runningUserIds);
     }
 
     /**
      * Returns accounts for all users, ignores visibility values.
      *
+     * Should only be called by system process
+     *
      * @hide
      */
     @NonNull
-    public AccountAndUser[] getAllAccounts() {
+    public AccountAndUser[] getAllAccountsForSystemProcess() {
         final List<UserInfo> users = getUserManager().getAliveUsers();
         final int[] userIds = new int[users.size()];
         for (int i = 0; i < userIds.length; i++) {
             userIds[i] = users.get(i).id;
         }
-        return getAccounts(userIds);
+        return getAccountsForSystem(userIds);
     }
 
+    /**
+     * Returns all accounts for the given user, ignores all visibility checks.
+     * This should only be called by system process.
+     *
+     * @hide
+     */
     @NonNull
-    private AccountAndUser[] getAccounts(int[] userIds) {
+    private AccountAndUser[] getAccountsForSystem(int[] userIds) {
         final ArrayList<AccountAndUser> runningAccounts = Lists.newArrayList();
         for (int userId : userIds) {
             UserAccounts userAccounts = getUserAccounts(userId);
@@ -4384,7 +4435,7 @@
                     userAccounts,
                     null /* type */,
                     Binder.getCallingUid(),
-                    null /* packageName */,
+                    "android"/* packageName */,
                     false /* include managed not visible*/);
             for (Account account : accounts) {
                 runningAccounts.add(new AccountAndUser(account, userId));
@@ -4795,6 +4846,7 @@
 
     private abstract class Session extends IAccountAuthenticatorResponse.Stub
             implements IBinder.DeathRecipient, ServiceConnection {
+        private final Object mSessionLock = new Object();
         IAccountManagerResponse mResponse;
         final String mAccountType;
         final boolean mExpectActivityLaunch;
@@ -4985,9 +5037,11 @@
         }
 
         private void unbind() {
-            if (mAuthenticator != null) {
-                mAuthenticator = null;
-                mContext.unbindService(this);
+            synchronized (mSessionLock) {
+                if (mAuthenticator != null) {
+                    mAuthenticator = null;
+                    mContext.unbindService(this);
+                }
             }
         }
 
@@ -4997,12 +5051,14 @@
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
-            mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
-            try {
-                run();
-            } catch (RemoteException e) {
-                onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
-                        "remote exception");
+            synchronized (mSessionLock) {
+                mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
+                try {
+                    run();
+                } catch (RemoteException e) {
+                    onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+                            "remote exception");
+                }
             }
         }
 
@@ -5355,7 +5411,7 @@
             }
         } else {
             Account[] accounts = getAccountsFromCache(userAccounts, null /* type */,
-                    Process.SYSTEM_UID, null /* packageName */, false);
+                    Process.SYSTEM_UID, "android" /* packageName */, false);
             fout.println("Accounts: " + accounts.length);
             for (Account account : accounts) {
                 fout.println("  " + account.toString());
@@ -5550,7 +5606,7 @@
                         return true;
                     }
                 } catch (PackageManager.NameNotFoundException e) {
-                    Log.d(TAG, "Package not found " + e.getMessage());
+                    Log.w(TAG, "isPrivileged#Package not found " + e.getMessage());
                 }
             }
         } finally {
@@ -5861,6 +5917,32 @@
         return (dpmi != null) && (dpmi.isActiveProfileOwner(uid) || dpmi.isActiveDeviceOwner(uid));
     }
 
+    /**
+     * Filter the access to the target package by rules of the package visibility if the caller
+     * targeting API level U and above. Otherwise, returns true if the package is installed on
+     * the device.
+     *
+     * @param targetPkgName The package name to check.
+     * @param callingUid The caller that is going to access the package.
+     * @param userId The user ID where the target package resides.
+     * @return true if the caller is able to access the package.
+     */
+    private boolean canCallerAccessPackage(@NonNull String targetPkgName, int callingUid,
+            int userId) {
+        final PackageManagerInternal pmInternal =
+                LocalServices.getService(PackageManagerInternal.class);
+        if (!CompatChanges.isChangeEnabled(ENFORCE_PACKAGE_VISIBILITY_FILTERING, callingUid)) {
+            return pmInternal.getPackageUid(
+                    targetPkgName, 0 /* flags */, userId) != Process.INVALID_UID;
+        }
+        final boolean canAccess = !pmInternal.filterAppAccess(targetPkgName, callingUid, userId);
+        if (!canAccess && Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Package " + targetPkgName + " is not visible to caller " + callingUid
+                    + " for user " + userId);
+        }
+        return canAccess;
+    }
+
     @Override
     public void updateAppPermission(Account account, String authTokenType, int uid, boolean value)
             throws RemoteException {
@@ -6074,7 +6156,7 @@
                     }
                 }
             } catch (NameNotFoundException e) {
-                Log.d(TAG, "Package not found " + e.getMessage());
+                Log.w(TAG, "filterSharedAccounts#Package not found " + e.getMessage());
             }
             Map<Account, Integer> filtered = new LinkedHashMap<>();
             for (Map.Entry<Account, Integer> entry : unfiltered.entrySet()) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c677edc..35b3db8 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -23,18 +23,31 @@
 import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
 import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DEPRECATED;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DISABLED;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_OK;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_UNKNOWN;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+import static android.os.PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN;
 import static android.os.PowerExemptionManager.REASON_ACTIVITY_STARTER;
 import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
 import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
 import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
 import static android.os.PowerExemptionManager.REASON_BACKGROUND_FGS_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_CARRIER_PRIVILEGED_APP;
 import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
 import static android.os.PowerExemptionManager.REASON_CURRENT_INPUT_METHOD;
 import static android.os.PowerExemptionManager.REASON_DENIED;
 import static android.os.PowerExemptionManager.REASON_DEVICE_DEMO_MODE;
 import static android.os.PowerExemptionManager.REASON_DEVICE_OWNER;
+import static android.os.PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL;
+import static android.os.PowerExemptionManager.REASON_DPO_PROTECTED_APP;
 import static android.os.PowerExemptionManager.REASON_FGS_BINDING;
 import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
 import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_FGS_PERMISSION;
@@ -45,10 +58,12 @@
 import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
 import static android.os.PowerExemptionManager.REASON_PROC_STATE_TOP;
 import static android.os.PowerExemptionManager.REASON_PROFILE_OWNER;
+import static android.os.PowerExemptionManager.REASON_ROLE_EMERGENCY;
 import static android.os.PowerExemptionManager.REASON_SERVICE_LAUNCH;
 import static android.os.PowerExemptionManager.REASON_START_ACTIVITY_FLAG;
 import static android.os.PowerExemptionManager.REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
 import static android.os.PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED;
+import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE;
 import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
 import static android.os.PowerExemptionManager.REASON_TEMP_ALLOWED_WHILE_IN_USE;
 import static android.os.PowerExemptionManager.REASON_UID_VISIBLE;
@@ -63,6 +78,9 @@
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
 
 import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICE_BG_LAUNCH;
+import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED;
+import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER;
+import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT;
 import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED;
 import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
 import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_HOT;
@@ -94,9 +112,15 @@
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.ForegroundServiceStartNotAllowedException;
+import android.app.ForegroundServiceTypePolicy;
+import android.app.ForegroundServiceTypePolicy.ForegroundServicePolicyCheckCode;
+import android.app.ForegroundServiceTypePolicy.ForegroundServiceTypePermission;
+import android.app.ForegroundServiceTypePolicy.ForegroundServiceTypePolicyInfo;
 import android.app.IApplicationThread;
 import android.app.IForegroundServiceObserver;
 import android.app.IServiceConnection;
+import android.app.InvalidForegroundServiceTypeException;
+import android.app.MissingForegroundServiceTypeException;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -108,6 +132,7 @@
 import android.app.usage.UsageEvents;
 import android.appwidget.AppWidgetManagerInternal;
 import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.Overridable;
 import android.content.ComponentName;
@@ -116,15 +141,17 @@
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.net.Uri;
+import android.content.pm.ServiceInfo.ForegroundServiceType;
 import android.os.Binder;
 import android.os.Build;
+import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.DeadObjectException;
 import android.os.Handler;
@@ -163,7 +190,6 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.procstats.ServiceState;
-import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.TimeoutRecord;
@@ -204,9 +230,10 @@
     private static final boolean DEBUG_DELAYED_SERVICE = DEBUG_SERVICE;
     private static final boolean DEBUG_DELAYED_STARTS = DEBUG_DELAYED_SERVICE;
 
-    private static final boolean LOG_SERVICE_START_STOP = false;
+    // STOPSHIP(b/260012573) turn it off.
+    private static final boolean DEBUG_SHORT_SERVICE = true; // DEBUG_SERVICE;
 
-    private static final boolean SHOW_DUNGEON_NOTIFICATION = false;
+    private static final boolean LOG_SERVICE_START_STOP = DEBUG_SERVICE;
 
     // How long we wait for a service to finish executing.
     static final int SERVICE_TIMEOUT = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
@@ -287,6 +314,12 @@
     final ArrayList<ServiceRecord> mPendingFgsNotifications = new ArrayList<>();
 
     /**
+     * Map of ForegroundServiceDelegation to the delegation ServiceRecord. The delegation
+     * ServiceRecord has flag isFgsDelegate set to true.
+     */
+    final ArrayMap<ForegroundServiceDelegation, ServiceRecord> mFgsDelegations = new ArrayMap<>();
+
+    /**
      * Whether there is a rate limit that suppresses immediate re-deferral of new FGS
      * notifications from each app.  On by default, disabled only by shell command for
      * test-suite purposes.  To disable the behavior more generally, use the usual
@@ -359,6 +392,14 @@
     @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
     static final long FGS_START_EXCEPTION_CHANGE_ID = 174041399L;
 
+    /**
+     * If enabled, the FGS type check against the manifest FSG type will be enabled for
+     * instant apps too. Before U, this check was only done for non-instant apps.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = VERSION_CODES.TIRAMISU)
+    static final long FGS_TYPE_CHECK_FOR_INSTANT_APPS = 261055255L;
+
     final Runnable mLastAnrDumpClearer = new Runnable() {
         @Override public void run() {
             synchronized (mAm) {
@@ -539,7 +580,7 @@
                     try {
                         final ServiceRecord.StartItem si = r.pendingStarts.get(0);
                         startServiceInnerLocked(this, si.intent, r, false, true, si.callingId,
-                                r.startRequested);
+                                si.mCallingProcessName, r.startRequested);
                     } catch (TransactionTooLargeException e) {
                         // Ignore, nobody upstack cares.
                     }
@@ -576,6 +617,7 @@
         getAppStateTracker().addBackgroundRestrictedAppListener(new BackgroundRestrictedListener());
         mAppWidgetManagerInternal = LocalServices.getService(AppWidgetManagerInternal.class);
         setAllowListWhileInUsePermissionInFgs();
+        initSystemExemptedFgsTypePermission();
     }
 
     private AppStateTracker getAppStateTracker() {
@@ -630,25 +672,6 @@
         return false;
     }
 
-    void stopForegroundServicesForChannelLocked(String pkg, int userId, String channelId) {
-        final ServiceMap smap = mServiceMap.get(userId);
-        if (smap != null) {
-            for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) {
-                final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i);
-                if (sr.appInfo.packageName.equals(pkg) && sr.isForeground) {
-                    if (Objects.equals(sr.foregroundNoti.getChannelId(), channelId)) {
-                        if (DEBUG_FOREGROUND_SERVICE) {
-                            Slog.d(TAG_SERVICE, "Stopping FGS u" + userId + "/pkg=" + pkg
-                                    + "/channelId=" + channelId
-                                    + " for conversation channel clear");
-                        }
-                        stopServiceLocked(sr, false);
-                    }
-                }
-            }
-        }
-    }
-
     private ServiceMap getServiceMapLocked(int callingUser) {
         ServiceMap smap = mServiceMap.get(callingUser);
         if (smap == null) {
@@ -722,9 +745,15 @@
                     ? res.permission : "private to package");
         }
 
+
+        // TODO(short-service): This is inside startService() / startForegroundService().
+        // Consider if there's anything special we have to do if these are called on an already-
+        // running short-FGS... But given these APIs shouldn't change the FGS type, we likely
+        // don't need to do anything. (If they would change the FGS type, we'd have to stop
+        // the timeout)
         ServiceRecord r = res.record;
         setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r, userId,
-                allowBackgroundActivityStarts);
+                allowBackgroundActivityStarts, false /* isBindService */);
 
         if (!mAm.mUserController.exists(r.userId)) {
             Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
@@ -757,8 +786,8 @@
                 Slog.w(TAG, msg);
                 showFgsBgRestrictedNotificationLocked(r);
                 logFGSStateChangeLocked(r,
-                        FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
-                        0, FGS_STOP_REASON_UNKNOWN);
+                        FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
+                        0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
                 if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, callingUid)) {
                     throw new ForegroundServiceStartNotAllowedException(msg);
                 }
@@ -861,8 +890,10 @@
         // alias component name to the client, not the "target" component name, which is
         // what realResult contains.
         final ComponentName realResult =
-                startServiceInnerLocked(r, service, callingUid, callingPid, fgRequired, callerFg,
-                allowBackgroundActivityStarts, backgroundActivityStartsToken);
+                startServiceInnerLocked(r, service, callingUid, callingPid,
+                        getCallingProcessNameLocked(callingUid, callingPid, callingPackage),
+                        fgRequired, callerFg, allowBackgroundActivityStarts,
+                        backgroundActivityStartsToken);
         if (res.aliasComponent != null
                 && !realResult.getPackageName().startsWith("!")
                 && !realResult.getPackageName().startsWith("?")) {
@@ -872,10 +903,18 @@
         }
     }
 
+    private String getCallingProcessNameLocked(int callingUid, int callingPid,
+            String callingPackage) {
+        synchronized (mAm.mPidsSelfLocked) {
+            final ProcessRecord callingApp = mAm.mPidsSelfLocked.get(callingPid);
+            return callingApp != null ? callingApp.processName : callingPackage;
+        }
+    }
+
     private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
-            int callingUid, int callingPid, boolean fgRequired, boolean callerFg,
-            boolean allowBackgroundActivityStarts, @Nullable IBinder backgroundActivityStartsToken)
-            throws TransactionTooLargeException {
+            int callingUid, int callingPid, String callingProcessName, boolean fgRequired,
+            boolean callerFg, boolean allowBackgroundActivityStarts,
+            @Nullable IBinder backgroundActivityStartsToken) throws TransactionTooLargeException {
         NeededUriGrants neededGrants = mAm.mUgmInternal.checkGrantUriPermissionFromIntent(
                 service, callingUid, r.packageName, r.userId);
         if (unscheduleServiceRestartLocked(r, callingUid, false)) {
@@ -887,7 +926,7 @@
         r.delayedStop = false;
         r.fgRequired = fgRequired;
         r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
-                service, neededGrants, callingUid));
+                service, neededGrants, callingUid, callingProcessName));
 
         if (fgRequired) {
             // We are now effectively running a foreground service.
@@ -972,7 +1011,7 @@
             r.allowBgActivityStartsOnServiceStart(backgroundActivityStartsToken);
         }
         ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting,
-                callingUid, wasStartRequested);
+                callingUid, callingProcessName, wasStartRequested);
         return cmp;
     }
 
@@ -1090,6 +1129,8 @@
             curPendingBringups = new ArrayList<>();
             mPendingBringups.put(s, curPendingBringups);
         }
+        final String callingProcessName = getCallingProcessNameLocked(
+                callingUid, callingPid, callingPackage);
         curPendingBringups.add(new Runnable() {
             @Override
             public void run() {
@@ -1122,8 +1163,8 @@
                     } else { // Starting a service
                         try {
                             startServiceInnerLocked(s, serviceIntent, callingUid, callingPid,
-                                    fgRequired, callerFg, allowBackgroundActivityStarts,
-                                    backgroundActivityStartsToken);
+                                    callingProcessName, fgRequired, callerFg,
+                                    allowBackgroundActivityStarts, backgroundActivityStartsToken);
                         } catch (TransactionTooLargeException e) {
                             /* ignore - local call */
                         }
@@ -1168,8 +1209,8 @@
     }
 
     ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
-            boolean callerFg, boolean addToStarting, int callingUid, boolean wasStartRequested)
-            throws TransactionTooLargeException {
+            boolean callerFg, boolean addToStarting, int callingUid, String callingProcessName,
+            boolean wasStartRequested) throws TransactionTooLargeException {
         synchronized (mAm.mProcessStats.mLock) {
             final ServiceState stracker = r.getTracker();
             if (stracker != null) {
@@ -1197,13 +1238,14 @@
         }
 
         FrameworkStatsLog.write(SERVICE_REQUEST_EVENT_REPORTED, uid, callingUid,
-                ActivityManagerService.getShortAction(service.getAction()),
+                service.getAction(),
                 SERVICE_REQUEST_EVENT_REPORTED__REQUEST_TYPE__START, false,
                 r.app == null || r.app.getThread() == null
                 ? SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD
                 : (wasStartRequested || !r.getConnections().isEmpty()
                 ? SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_HOT
-                : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM));
+                : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM),
+                getShortProcessNameForStats(callingUid, callingProcessName));
 
         if (r.startRequested && addToStarting) {
             boolean first = smap.mStartingBackground.size() == 0;
@@ -1226,6 +1268,22 @@
         return r.name;
     }
 
+    private @Nullable String getShortProcessNameForStats(int uid, String processName) {
+        final String[] packages = mAm.mContext.getPackageManager().getPackagesForUid(uid);
+        if (packages != null && packages.length == 1) {
+            // Not the shared UID case, let's see if the package name equals to the process name.
+            if (TextUtils.equals(packages[0], processName)) {
+                // same name, just return null here.
+                return null;
+            } else if (processName != null && processName.startsWith(packages[0])) {
+                // return the suffix of the process name
+                return processName.substring(packages[0].length());
+            }
+        }
+        // return the full process name.
+        return processName;
+    }
+
     private void stopServiceLocked(ServiceRecord service, boolean enqueueOomAdj) {
         try {
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "stopServiceLocked()");
@@ -1240,6 +1298,8 @@
                 return;
             }
 
+            maybeStopShortFgsTimeoutLocked(service);
+
             final int uid = service.appInfo.uid;
             final String packageName = service.name.getPackageName();
             final String serviceName = service.name.getClassName();
@@ -1255,7 +1315,8 @@
             }
             service.callStart = false;
 
-            bringDownServiceIfNeededLocked(service, false, false, enqueueOomAdj);
+            bringDownServiceIfNeededLocked(service, false, false, enqueueOomAdj,
+                    "stopService");
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         }
@@ -1422,6 +1483,8 @@
                 }
             }
 
+            maybeStopShortFgsTimeoutLocked(r);
+
             final int uid = r.appInfo.uid;
             final String packageName = r.name.getPackageName();
             final String serviceName = r.name.getClassName();
@@ -1437,7 +1500,7 @@
             }
             r.callStart = false;
             final long origId = Binder.clearCallingIdentity();
-            bringDownServiceIfNeededLocked(r, false, false, false);
+            bringDownServiceIfNeededLocked(r, false, false, false, "stopServiceToken");
             Binder.restoreCallingIdentity(origId);
             return true;
         }
@@ -1530,9 +1593,11 @@
         return canRemove;
     }
 
+    /**
+     * Stop FGSs owned by non-top, BG-restricted apps.
+     */
     void updateForegroundApps(ServiceMap smap) {
         // This is called from the handler without the lock held.
-        ArrayList<ActiveForegroundApp> active = null;
         synchronized (mAm) {
             final long now = SystemClock.elapsedRealtime();
             long nextUpdateTime = Long.MAX_VALUE;
@@ -1558,12 +1623,8 @@
                         // it loses the fg service state now.
                         if (isForegroundServiceAllowedInBackgroundRestricted(
                                 aa.mUid, aa.mPackageName)) {
-                            if (active == null) {
-                                active = new ArrayList<>();
-                            }
                             if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Adding active: pkg="
                                     + aa.mPackageName + ", uid=" + aa.mUid);
-                            active.add(aa);
                         } else {
                             if (DEBUG_FOREGROUND_SERVICE) {
                                 Slog.d(TAG, "bg-restricted app "
@@ -1583,89 +1644,8 @@
                             + SystemClock.uptimeMillis() - SystemClock.elapsedRealtime());
                 }
             }
-            if (!smap.mActiveForegroundAppsChanged) {
-                return;
-            }
             smap.mActiveForegroundAppsChanged = false;
         }
-
-        if (!SHOW_DUNGEON_NOTIFICATION) {
-            return;
-        }
-
-        final NotificationManager nm = (NotificationManager) mAm.mContext.getSystemService(
-                Context.NOTIFICATION_SERVICE);
-        final Context context = mAm.mContext;
-
-        if (active != null) {
-            for (int i = 0; i < active.size(); i++) {
-                ActiveForegroundApp aa = active.get(i);
-                if (aa.mLabel == null) {
-                    PackageManager pm = context.getPackageManager();
-                    try {
-                        ApplicationInfo ai = pm.getApplicationInfoAsUser(aa.mPackageName,
-                                PackageManager.MATCH_KNOWN_PACKAGES, smap.mUserId);
-                        aa.mLabel = ai.loadLabel(pm);
-                    } catch (PackageManager.NameNotFoundException e) {
-                        aa.mLabel = aa.mPackageName;
-                    }
-                }
-            }
-
-            Intent intent;
-            String title;
-            String msg;
-            String[] pkgs;
-            final long nowElapsed = SystemClock.elapsedRealtime();
-            long oldestStartTime = nowElapsed;
-            if (active.size() == 1) {
-                intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
-                intent.setData(Uri.fromParts("package", active.get(0).mPackageName, null));
-                title = context.getString(
-                        R.string.foreground_service_app_in_background, active.get(0).mLabel);
-                msg = context.getString(R.string.foreground_service_tap_for_details);
-                pkgs = new String[] { active.get(0).mPackageName };
-                oldestStartTime = active.get(0).mStartTime;
-            } else {
-                intent = new Intent(Settings.ACTION_FOREGROUND_SERVICES_SETTINGS);
-                pkgs = new String[active.size()];
-                for (int i = 0; i < active.size(); i++) {
-                    pkgs[i] = active.get(i).mPackageName;
-                    oldestStartTime = Math.min(oldestStartTime, active.get(i).mStartTime);
-                }
-                intent.putExtra("packages", pkgs);
-                title = context.getString(
-                        R.string.foreground_service_apps_in_background, active.size());
-                msg = active.get(0).mLabel.toString();
-                for (int i = 1; i < active.size(); i++) {
-                    msg = context.getString(R.string.foreground_service_multiple_separator,
-                            msg, active.get(i).mLabel);
-                }
-            }
-            Bundle notificationBundle = new Bundle();
-            notificationBundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, pkgs);
-            Notification.Builder n =
-                    new Notification.Builder(context,
-                            SystemNotificationChannels.FOREGROUND_SERVICE)
-                            .addExtras(notificationBundle)
-                            .setSmallIcon(R.drawable.stat_sys_vitals)
-                            .setOngoing(true)
-                            .setShowWhen(oldestStartTime < nowElapsed)
-                            .setWhen(System.currentTimeMillis() - (nowElapsed - oldestStartTime))
-                            .setColor(context.getColor(
-                                    com.android.internal.R.color.system_notification_accent_color))
-                            .setContentTitle(title)
-                            .setContentText(msg)
-                            .setContentIntent(
-                                    PendingIntent.getActivityAsUser(context, 0, intent,
-                                            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED,
-                                            null, new UserHandle(smap.mUserId)));
-            nm.notifyAsUser(null, SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
-                    n.build(), new UserHandle(smap.mUserId));
-        } else {
-            nm.cancelAsUser(null, SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
-                    new UserHandle(smap.mUserId));
-        }
     }
 
     private void requestUpdateActiveForegroundAppsLocked(ServiceMap smap, long timeElapsed) {
@@ -1819,6 +1799,7 @@
             if (notification == null) {
                 throw new IllegalArgumentException("null notification");
             }
+            final int foregroundServiceStartType = foregroundServiceType;
             // Instant apps need permission to create foreground services.
             if (r.appInfo.isInstantApp()) {
                 final int mode = mAm.getAppOpsManager().checkOpNoThrow(
@@ -1847,23 +1828,44 @@
                             android.Manifest.permission.FOREGROUND_SERVICE,
                             r.app.getPid(), r.appInfo.uid, "startForeground");
                 }
+            }
+            final int manifestType = r.serviceInfo.getForegroundServiceType();
+            // If passed in foreground service type is FOREGROUND_SERVICE_TYPE_MANIFEST,
+            // consider it is the same as manifest foreground service type.
+            if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_MANIFEST) {
+                foregroundServiceType = manifestType;
+            }
 
-                int manifestType = r.serviceInfo.getForegroundServiceType();
-                // If passed in foreground service type is FOREGROUND_SERVICE_TYPE_MANIFEST,
-                // consider it is the same as manifest foreground service type.
-                if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_MANIFEST) {
-                    foregroundServiceType = manifestType;
-                }
-                // Check the passed in foreground service type flags is a subset of manifest
-                // foreground service type flags.
-                if ((foregroundServiceType & manifestType) != foregroundServiceType) {
-                    throw new IllegalArgumentException("foregroundServiceType "
+            // Check the passed in foreground service type flags is a subset of manifest
+            // foreground service type flags.
+            final String prop = "debug.skip_fgs_manifest_type_check";
+            if (((foregroundServiceType & manifestType) != foregroundServiceType)
+                    // When building a test app on Studio, the SDK may not have all the
+                    // FGS types yet. This debug flag will allow using FGS types that are
+                    // not set in the manifest.
+                    && !SystemProperties.getBoolean(prop, false)) {
+                final String message = "foregroundServiceType "
                         + String.format("0x%08X", foregroundServiceType)
                         + " is not a subset of foregroundServiceType attribute "
-                        +  String.format("0x%08X", manifestType)
-                        + " in service element of manifest file");
+                        + String.format("0x%08X", manifestType)
+                        + " in service element of manifest file";
+                if (!r.appInfo.isInstantApp()
+                        || CompatChanges.isChangeEnabled(FGS_TYPE_CHECK_FOR_INSTANT_APPS,
+                        r.appInfo.uid)) {
+                    throw new IllegalArgumentException(message);
+                } else {
+                    Slog.w(TAG, message + "\n"
+                            + "This will be an exception once the target SDK level is UDC");
                 }
             }
+            if ((foregroundServiceType & FOREGROUND_SERVICE_TYPE_SHORT_SERVICE) != 0
+                    && foregroundServiceType != FOREGROUND_SERVICE_TYPE_SHORT_SERVICE) {
+                Slog.w(TAG_SERVICE, "startForeground(): FOREGROUND_SERVICE_TYPE_SHORT_SERVICE"
+                        + " is combined with other types. SHORT_SERVICE will be ignored.");
+                // In this case, the service will be handled as a non-short, regular FGS
+                // anyway, so we just remove the SHORT_SERVICE type.
+                foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+            }
 
             boolean alreadyStartedOp = false;
             boolean stopProcStatsOp = false;
@@ -1911,7 +1913,55 @@
                     ignoreForeground = true;
                 }
 
+                int fgsTypeCheckCode = FGS_TYPE_POLICY_CHECK_UNKNOWN;
                 if (!ignoreForeground) {
+                    if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE
+                            && !r.startRequested) {
+                        // There's a long standing bug that allows a bound service to become
+                        // a foreground service *even when it's not started*.
+                        // Unfortunately, there are apps relying on this behavior, so we can't just
+                        // suddenly disallow it.
+                        // However, this would be very problematic if used with a short-FGS, so we
+                        // explicitly disallow this combination.
+                        // TODO(short-service): Change to another exception type?
+                        throw new IllegalStateException(
+                                "startForeground(SHORT_SERVICE) called on a service that's not"
+                                + " started.");
+                    }
+                    // If the service is already an FGS, and the type is changing, then we
+                    // may need to do some extra work here.
+                    if (r.isForeground && (r.foregroundServiceType != foregroundServiceType)) {
+                        // TODO(short-service): Consider transitions:
+                        //   A. Short -> other types:
+                        //     Apply the BG restriction again. Don't just allow it.
+                        //     i.e. unless the app is in a situation where it's allowed to start
+                        //     a FGS, this transition shouldn't be allowed.
+                        //     ... But think about it more, there may be a case this should be
+                        //     allowed.
+                        //
+                        //     If the transition is allowed, stop the timeout.
+                        //     If the transition is _not_ allowed... keep the timeout?
+                        //
+                        //   B. Short -> Short:
+                        //     Allowed, but the timeout won't reset. The original timeout is used.
+                        //   C. Other -> short:
+                        //     This should always be allowed.
+                        //     A timeout should start.
+
+                        // For now, let's just disallow transition from / to SHORT_SERVICE.
+                        final boolean isNewTypeShortFgs =
+                                foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+                        if (r.isShortFgs() != isNewTypeShortFgs) {
+                            // TODO(short-service): We should (probably) allow it.
+                            throw new IllegalArgumentException(
+                                    "setForeground(): Changing foreground service type from / to "
+                                    + " SHORT_SERVICE is now allowed");
+                        }
+                    }
+
+                    // If a valid short-service (which has to be "started"), happens to
+                    // also be bound, then we still _will_ apply a timeout, because it still has
+                    // to be stopped.
                     if (r.mStartForegroundCount == 0) {
                         /*
                         If the service was started with startService(), not
@@ -1931,7 +1981,9 @@
                             if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) {
                                 resetFgsRestrictionLocked(r);
                                 setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
-                                        r.appInfo.uid, r.intent.getIntent(), r, r.userId, false);
+                                        r.appInfo.uid, r.intent.getIntent(), r, r.userId,
+                                        false /* allowBackgroundActivityStarts */,
+                                        false /* isBindService */);
                                 final String temp = "startForegroundDelayMs:" + delayMs;
                                 if (r.mInfoAllowStartForeground != null) {
                                     r.mInfoAllowStartForeground += "; " + temp;
@@ -1942,10 +1994,16 @@
                             }
                         }
                     } else if (r.mStartForegroundCount >= 1) {
+                        // We get here if startForeground() is called multiple times
+                        // on the same sarvice after it's created, regardless of whether
+                        // stopForeground() has been called or not.
+
                         // The second or later time startForeground() is called after service is
                         // started. Check for app state again.
                         setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
-                                r.appInfo.uid, r.intent.getIntent(), r, r.userId, false);
+                                r.appInfo.uid, r.intent.getIntent(), r, r.userId,
+                                false /* allowBackgroundActivityStarts */,
+                                false /* isBindService */);
                     }
                     // If the foreground service is not started from TOP process, do not allow it to
                     // have while-in-use location/camera/microphone access.
@@ -1965,13 +2023,50 @@
                         updateServiceForegroundLocked(psr, true);
                         ignoreForeground = true;
                         logFGSStateChangeLocked(r,
-                                FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
-                                0, FGS_STOP_REASON_UNKNOWN);
+                                FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
+                                0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
                         if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID,
                                 r.appInfo.uid)) {
                             throw new ForegroundServiceStartNotAllowedException(msg);
                         }
                     }
+
+                    if (!ignoreForeground) {
+                        Pair<Integer, RuntimeException> fgsTypeResult = null;
+                        if (foregroundServiceType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+                            fgsTypeResult = validateForegroundServiceType(r,
+                                    foregroundServiceType,
+                                    ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE,
+                                    foregroundServiceStartType);
+                        } else {
+                            int fgsTypes = foregroundServiceType;
+                            // If the service has declared some unknown types which might be coming
+                            // from future releases, and if it also comes with the "specialUse",
+                            // then it'll be deemed as the "specialUse" and we ignore this
+                            // unknown type. Otherwise, it'll be treated as an invalid type.
+                            int defaultFgsTypes = (foregroundServiceType
+                                    & ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE) != 0
+                                    ? ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
+                                    : ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+                            for (int serviceType = Integer.highestOneBit(fgsTypes);
+                                    serviceType != 0;
+                                    serviceType = Integer.highestOneBit(fgsTypes)) {
+                                fgsTypeResult = validateForegroundServiceType(r,
+                                        serviceType, defaultFgsTypes, foregroundServiceStartType);
+                                fgsTypes &= ~serviceType;
+                                if (fgsTypeResult.first != FGS_TYPE_POLICY_CHECK_OK) {
+                                    break;
+                                }
+                            }
+                        }
+                        fgsTypeCheckCode = fgsTypeResult.first;
+                        if (fgsTypeResult.second != null) {
+                            logFGSStateChangeLocked(r,
+                                    FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
+                                    0, FGS_STOP_REASON_UNKNOWN, fgsTypeResult.first);
+                            throw fgsTypeResult.second;
+                        }
+                    }
                 }
 
                 // Apps under strict background restrictions simply don't get to have foreground
@@ -1983,6 +2078,10 @@
                         cancelForegroundNotificationLocked(r);
                         r.foregroundId = id;
                     }
+
+                    // TODO(short-service): Stop the short service timeout, if the type is changing
+                    // from short to non-short. (should we do it earlier?)
+
                     notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
                     r.foregroundNoti = notification;
                     r.foregroundServiceType = foregroundServiceType;
@@ -2040,8 +2139,8 @@
                         registerAppOpCallbackLocked(r);
                         mAm.updateForegroundServiceUsageStats(r.name, r.userId, true);
                         logFGSStateChangeLocked(r,
-                                FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
-                                0, FGS_STOP_REASON_UNKNOWN);
+                                FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
+                                0, FGS_STOP_REASON_UNKNOWN, fgsTypeCheckCode);
                         updateNumForegroundServicesLocked();
                     }
                     // Even if the service is already a FGS, we need to update the notification,
@@ -2054,6 +2153,12 @@
                     getServiceMapLocked(r.userId).ensureNotStartingBackgroundLocked(r);
                     mAm.notifyPackageUse(r.serviceInfo.packageName,
                             PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
+
+                    // Note, we'll get here if setForeground(SHORT_SERVICE) is called on a
+                    // already short-fgs.
+                    // In that case, because ShortFgsInfo is already set, this method
+                    // will be noop.
+                    maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(r);
                 } else {
                     if (DEBUG_FOREGROUND_SERVICE) {
                         Slog.d(TAG, "Suppressing startForeground() for FAS " + r);
@@ -2087,6 +2192,8 @@
                     decActiveForegroundAppLocked(smap, r);
                 }
 
+                maybeStopShortFgsTimeoutLocked(r);
+
                 // Adjust notification handling before setting isForeground to false, because
                 // that state is relevant to the notification policy side.
                 // Leave the time-to-display as already set: re-entering foreground mode will
@@ -2122,10 +2229,15 @@
                         AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
                 unregisterAppOpCallbackLocked(r);
                 logFGSStateChangeLocked(r,
-                        FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
+                        FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
                         r.mFgsExitTime > r.mFgsEnterTime
                                 ? (int) (r.mFgsExitTime - r.mFgsEnterTime) : 0,
-                        FGS_STOP_REASON_STOP_FOREGROUND);
+                        FGS_STOP_REASON_STOP_FOREGROUND,
+                        FGS_TYPE_POLICY_CHECK_UNKNOWN);
+
+                // foregroundServiceType is used in logFGSStateChangeLocked(), so we can't clear it
+                // earlier.
+                r.foregroundServiceType = 0;
                 r.mFgsNotificationWasDeferred = false;
                 signalForegroundServiceObserversLocked(r);
                 resetFgsRestrictionLocked(r);
@@ -2161,6 +2273,127 @@
         return now < eligible;
     }
 
+    /**
+     * Validate if the given service can start a foreground service with given type.
+     *
+     * @return A pair, where the first parameter is the result code and second is the exception
+     *         object if it fails to start a foreground service with given type.
+     */
+    @NonNull
+    private Pair<Integer, RuntimeException> validateForegroundServiceType(ServiceRecord r,
+            @ForegroundServiceType int type,
+            @ForegroundServiceType int defaultToType,
+            @ForegroundServiceType int startType) {
+        final ForegroundServiceTypePolicy policy = ForegroundServiceTypePolicy.getDefaultPolicy();
+        final ForegroundServiceTypePolicyInfo policyInfo =
+                policy.getForegroundServiceTypePolicyInfo(type, defaultToType);
+        final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy(
+                mAm.mContext, r.packageName, r.app.uid, r.app.getPid(),
+                r.mAllowWhileInUsePermissionInFgs, policyInfo);
+        RuntimeException exception = null;
+        switch (code) {
+            case FGS_TYPE_POLICY_CHECK_DEPRECATED: {
+                final String msg = "Starting FGS with type "
+                        + ServiceInfo.foregroundServiceTypeToLabel(type)
+                        + " code=" + code
+                        + " callerApp=" + r.app
+                        + " targetSDK=" + r.app.info.targetSdkVersion;
+                Slog.wtfQuiet(TAG, msg);
+                Slog.w(TAG, msg);
+            } break;
+            case FGS_TYPE_POLICY_CHECK_DISABLED: {
+                if (startType == FOREGROUND_SERVICE_TYPE_MANIFEST
+                        && type == FOREGROUND_SERVICE_TYPE_NONE) {
+                    exception = new MissingForegroundServiceTypeException(
+                            "Starting FGS without a type "
+                            + " callerApp=" + r.app
+                            + " targetSDK=" + r.app.info.targetSdkVersion);
+                } else {
+                    exception = new InvalidForegroundServiceTypeException(
+                            "Starting FGS with type "
+                            + ServiceInfo.foregroundServiceTypeToLabel(type)
+                            + " callerApp=" + r.app
+                            + " targetSDK=" + r.app.info.targetSdkVersion
+                            + " has been prohibited");
+                }
+            } break;
+            case FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE: {
+                final String msg = "Starting FGS with type "
+                        + ServiceInfo.foregroundServiceTypeToLabel(type)
+                        + " code=" + code
+                        + " callerApp=" + r.app
+                        + " targetSDK=" + r.app.info.targetSdkVersion
+                        + " requiredPermissions=" + policyInfo.toPermissionString();
+                Slog.wtfQuiet(TAG, msg);
+                Slog.w(TAG, msg);
+            } break;
+            case FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED: {
+                exception = new SecurityException("Starting FGS with type "
+                        + ServiceInfo.foregroundServiceTypeToLabel(type)
+                        + " callerApp=" + r.app
+                        + " targetSDK=" + r.app.info.targetSdkVersion
+                        + " requires permissions: "
+                        + policyInfo.toPermissionString());
+            } break;
+            case FGS_TYPE_POLICY_CHECK_OK:
+            default:
+                break;
+        }
+        return Pair.create(code, exception);
+    }
+
+    private class SystemExemptedFgsTypePermission extends ForegroundServiceTypePermission {
+        SystemExemptedFgsTypePermission() {
+            super("System exempted");
+        }
+
+        @Override
+        public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+                @NonNull String packageName, boolean allowWhileInUse) {
+            final AppRestrictionController appRestrictionController = mAm.mAppRestrictionController;
+            @ReasonCode int reason = appRestrictionController
+                    .getPotentialSystemExemptionReason(callerUid);
+            if (reason == REASON_DENIED) {
+                reason = appRestrictionController
+                        .getPotentialSystemExemptionReason(callerUid, packageName);
+                if (reason == REASON_DENIED) {
+                    reason = appRestrictionController
+                            .getPotentialUserAllowedExemptionReason(callerUid, packageName);
+                }
+            }
+            switch (reason) {
+                case REASON_SYSTEM_UID:
+                case REASON_SYSTEM_ALLOW_LISTED:
+                case REASON_DEVICE_DEMO_MODE:
+                case REASON_DISALLOW_APPS_CONTROL:
+                case REASON_DEVICE_OWNER:
+                case REASON_PROFILE_OWNER:
+                case REASON_PROC_STATE_PERSISTENT:
+                case REASON_PROC_STATE_PERSISTENT_UI:
+                case REASON_SYSTEM_MODULE:
+                case REASON_CARRIER_PRIVILEGED_APP:
+                case REASON_DPO_PROTECTED_APP:
+                case REASON_ACTIVE_DEVICE_ADMIN:
+                case REASON_ROLE_EMERGENCY:
+                case REASON_ALLOWLISTED_PACKAGE:
+                    return PERMISSION_GRANTED;
+                default:
+                    return PERMISSION_DENIED;
+            }
+        }
+    }
+
+    private void initSystemExemptedFgsTypePermission() {
+        final ForegroundServiceTypePolicy policy = ForegroundServiceTypePolicy.getDefaultPolicy();
+        final ForegroundServiceTypePolicyInfo policyInfo =
+                policy.getForegroundServiceTypePolicyInfo(
+                       ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+                       ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);
+        if (policyInfo != null) {
+            policyInfo.setCustomPermission(new SystemExemptedFgsTypePermission());
+        }
+    }
+
     ServiceNotificationPolicy applyForegroundServiceNotificationLocked(Notification notification,
             final String tag, final int id, final String pkg, final int userId) {
         // By nature of the FGS API, all FGS notifications have a null tag
@@ -2301,7 +2534,9 @@
         final int uid = r.appInfo.uid;
 
         // schedule the actual notification post
-        long when = now + mAm.mConstants.mFgsNotificationDeferralInterval;
+        long when = now
+                + (r.isShortFgs() ? mAm.mConstants.mFgsNotificationDeferralIntervalForShort
+                : mAm.mConstants.mFgsNotificationDeferralInterval);
         // If there are already deferred FGS notifications for this app,
         // inherit that deferred-show timestamp
         for (int i = 0; i < mPendingFgsNotifications.size(); i++) {
@@ -2320,7 +2555,9 @@
         }
 
         if (mFgsDeferralRateLimited) {
-            final long nextEligible = when + mAm.mConstants.mFgsNotificationDeferralExclusionTime;
+            final long nextEligible = when
+                    + (r.isShortFgs() ? mAm.mConstants.mFgsNotificationDeferralExclusionTimeForShort
+                    : mAm.mConstants.mFgsNotificationDeferralExclusionTime);
             mFgsDeferralEligible.put(uid, nextEligible);
         }
         r.fgDisplayTime = when;
@@ -2684,17 +2921,125 @@
     private void updateServiceForegroundLocked(ProcessServiceRecord psr, boolean oomAdj) {
         boolean anyForeground = false;
         int fgServiceTypes = 0;
+        boolean hasTypeNone = false;
         for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
             ServiceRecord sr = psr.getRunningServiceAt(i);
             if (sr.isForeground || sr.fgRequired) {
                 anyForeground = true;
                 fgServiceTypes |= sr.foregroundServiceType;
+                if (sr.foregroundServiceType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+                    hasTypeNone = true;
+                }
             }
         }
-        mAm.updateProcessForegroundLocked(psr.mApp, anyForeground, fgServiceTypes, oomAdj);
+        mAm.updateProcessForegroundLocked(psr.mApp, anyForeground,
+                fgServiceTypes, hasTypeNone, oomAdj);
         psr.setHasReportedForegroundServices(anyForeground);
     }
 
+    void unscheduleShortFgsTimeoutLocked(ServiceRecord sr) {
+        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
+        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
+    }
+
+    /**
+     * If {@code sr} is of a short-fgs, start a short-FGS timeout.
+     */
+    private void maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(ServiceRecord sr) {
+        if (!sr.isShortFgs()) {
+            return;
+        }
+        if (DEBUG_SHORT_SERVICE) {
+            Slog.i(TAG_SERVICE, "Short FGS started: " + sr);
+        }
+        if (sr.hasShortFgsInfo()) {
+            sr.getShortFgsInfo().update();
+        } else {
+            sr.setShortFgsInfo(SystemClock.uptimeMillis());
+        }
+        unscheduleShortFgsTimeoutLocked(sr); // Do it just in case
+
+        final Message msg = mAm.mHandler.obtainMessage(
+                ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
+        mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getTimeoutTime());
+    }
+
+    /**
+     * Stop the timeout for a ServiceRecord, if it's of a short-FGS.
+     */
+    private void maybeStopShortFgsTimeoutLocked(ServiceRecord sr) {
+        if (!sr.isShortFgs()) {
+            return;
+        }
+        if (DEBUG_SHORT_SERVICE) {
+            Slog.i(TAG_SERVICE, "Stop short FGS timeout: " + sr);
+        }
+        sr.clearShortFgsInfo();
+        unscheduleShortFgsTimeoutLocked(sr);
+    }
+
+    void onShortFgsTimeout(ServiceRecord sr) {
+        synchronized (mAm) {
+            if (!sr.shouldTriggerShortFgsTimeout()) {
+                if (DEBUG_SHORT_SERVICE) {
+                    Slog.d(TAG_SERVICE, "[STALE] Short FGS timed out: " + sr);
+                }
+                return;
+            }
+            Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr);
+            try {
+                sr.app.getThread().scheduleTimeoutService(sr, sr.getShortFgsInfo().getStartId());
+            } catch (RemoteException e) {
+                // TODO(short-service): Anything to do here?
+            }
+            // Schedule the ANR timeout.
+            final Message msg = mAm.mHandler.obtainMessage(
+                    ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
+            mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getAnrTime());
+        }
+    }
+
+    boolean shouldServiceTimeOutLocked(ComponentName className, IBinder token) {
+        final int userId = UserHandle.getCallingUserId();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            ServiceRecord sr = findServiceLocked(className, token, userId);
+            if (sr == null) {
+                return false;
+            }
+            return sr.shouldTriggerShortFgsTimeout();
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    void onShortFgsAnrTimeout(ServiceRecord sr) {
+        final String reason = "A foreground service of FOREGROUND_SERVICE_TYPE_SHORT_SERVICE"
+                + " did not stop within a timeout: " + sr.getComponentName();
+
+        final TimeoutRecord tr = TimeoutRecord.forShortFgsTimeout(reason);
+
+        tr.mLatencyTracker.waitingOnAMSLockStarted();
+        synchronized (mAm) {
+            tr.mLatencyTracker.waitingOnAMSLockEnded();
+
+            if (!sr.shouldTriggerShortFgsAnr()) {
+                if (DEBUG_SHORT_SERVICE) {
+                    Slog.d(TAG_SERVICE, "[STALE] Short FGS ANR'ed: " + sr);
+                }
+                return;
+            }
+
+            final String message = "Short FGS ANR'ed: " + sr;
+            if (DEBUG_SHORT_SERVICE) {
+                Slog.wtf(TAG_SERVICE, message);
+            } else {
+                Slog.e(TAG_SERVICE, message);
+            }
+            mAm.appNotResponding(sr.app, tr);
+        }
+    }
+
     private void updateAllowlistManagerLocked(ProcessServiceRecord psr) {
         psr.mAllowlistManager = false;
         for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
@@ -2706,6 +3051,7 @@
         }
     }
 
+    // TODO(short-service): Hmm what is it? Should we stop the timeout here?
     private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) {
         final ProcessServiceRecord psr = service.app.mServices;
         psr.stopService(service);
@@ -2866,7 +3212,7 @@
         ServiceLookupResult res = retrieveServiceLocked(service, instanceName,
                 isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
                 resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg,
-                isBindExternal, allowInstant);
+                isBindExternal, allowInstant, null /* fgsDelegateOptions */);
         if (res == null) {
             return 0;
         }
@@ -2985,7 +3331,7 @@
                 }
             }
             setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId,
-                    false);
+                    false /* allowBackgroundActivityStarts */, true /* isBindService */);
 
             if (s.app != null) {
                 ProcessServiceRecord servicePsr = s.app.mServices;
@@ -3015,7 +3361,8 @@
                     ? SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD
                     : (wasStartRequested || hadConnections
                     ? SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_HOT
-                    : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM));
+                    : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM),
+                    getShortProcessNameForStats(callingUid, callerApp.processName));
 
             if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bind " + s + " with " + b
                     + ": received=" + b.intent.received
@@ -3324,7 +3671,7 @@
             boolean allowInstant) {
         return retrieveServiceLocked(service, instanceName, false, 0, null, resolvedType,
                 callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg,
-                isBindExternal, allowInstant);
+                isBindExternal, allowInstant, null /* fgsDelegateOptions */);
     }
 
     private ServiceLookupResult retrieveServiceLocked(Intent service,
@@ -3332,7 +3679,7 @@
             String sdkSandboxClientAppPackage, String resolvedType,
             String callingPackage, int callingPid, int callingUid, int userId,
             boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
-            boolean allowInstant) {
+            boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions) {
         if (isSdkSandboxService && instanceName == null) {
             throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");
         }
@@ -3395,6 +3742,53 @@
                 }
             }
         }
+
+        if (r == null && fgsDelegateOptions != null) {
+            // Create a ServiceRecord for FGS delegate.
+            final ServiceInfo sInfo = new ServiceInfo();
+            ApplicationInfo aInfo = null;
+            try {
+                aInfo = AppGlobals.getPackageManager().getApplicationInfo(
+                        fgsDelegateOptions.mClientPackageName,
+                        ActivityManagerService.STOCK_PM_FLAGS,
+                        userId);
+            } catch (RemoteException ex) {
+            // pm is in same process, this will never happen.
+            }
+            if (aInfo == null) {
+                throw new SecurityException("startForegroundServiceDelegate failed, "
+                        + "could not resolve client package " + callingPackage);
+            }
+            if (aInfo.uid != fgsDelegateOptions.mClientUid) {
+                throw new SecurityException("startForegroundServiceDelegate failed, "
+                        + "uid:" + aInfo.uid
+                        + " does not match clientUid:" + fgsDelegateOptions.mClientUid);
+            }
+            sInfo.applicationInfo = aInfo;
+            sInfo.packageName = aInfo.packageName;
+            sInfo.mForegroundServiceType = fgsDelegateOptions.mForegroundServiceTypes;
+            sInfo.processName = aInfo.processName;
+            final ComponentName cn = service.getComponent();
+            sInfo.name = cn.getClassName();
+            if (createIfNeeded) {
+                final Intent.FilterComparison filter =
+                        new Intent.FilterComparison(service.cloneFilter());
+                final ServiceRestarter res = new ServiceRestarter();
+                r = new ServiceRecord(mAm, cn /* name */, cn /* instanceName */,
+                        sInfo.applicationInfo.packageName, sInfo.applicationInfo.uid, filter, sInfo,
+                        callingFromFg, res, null /* sdkSandboxProcessName */,
+                        INVALID_UID /* sdkSandboxClientAppUid */,
+                        null /* sdkSandboxClientAppPackage */);
+                res.setService(r);
+                smap.mServicesByInstanceName.put(cn, r);
+                smap.mServicesByIntent.put(filter, r);
+                if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Retrieve created new service: " + r);
+                r.mRecentCallingPackage = callingPackage;
+                r.mRecentCallingUid = callingUid;
+            }
+            return new ServiceLookupResult(r, resolution.getAlias());
+        }
+
         if (r == null) {
             try {
                 int flags = ActivityManagerService.STOCK_PM_FLAGS
@@ -3449,6 +3843,11 @@
                             throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
                                     + className + " is not an isolatedProcess");
                         }
+                        if (AppGlobals.getPackageManager().getPackageUid(callingPackage,
+                                0, userId) != callingUid) {
+                            throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
+                                    + "calling package not owned by calling UID ");
+                        }
                         // Run the service under the calling package's application.
                         ApplicationInfo aInfo = AppGlobals.getPackageManager().getApplicationInfo(
                                 callingPackage, ActivityManagerService.STOCK_PM_FLAGS, userId);
@@ -3939,7 +4338,7 @@
     /**
      * Reschedule service restarts based on if the extra delays are enabled or not.
      *
-     * @param prevEnable The previous state of whether or not it's enabled.
+     * @param prevEnabled The previous state of whether or not it's enabled.
      * @param curEnabled The current state of whether or not it's enabled.
      * @param now The uptimeMillis
      */
@@ -4484,7 +4883,7 @@
         // be called.
         if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
             r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
-                    null, null, 0));
+                    null, null, 0, null));
         }
 
         sendServiceArgsLocked(r, execInFg, true);
@@ -4618,10 +5017,11 @@
         return false;
     }
 
-    private final void bringDownServiceIfNeededLocked(ServiceRecord r, boolean knowConn,
-            boolean hasConn, boolean enqueueOomAdj) {
-        //Slog.i(TAG, "Bring down service:");
-        //r.dump("  ");
+    private void bringDownServiceIfNeededLocked(ServiceRecord r, boolean knowConn,
+            boolean hasConn, boolean enqueueOomAdj, String debugReason) {
+        if (DEBUG_SERVICE) {
+            Slog.i(TAG, "Bring down service for " + debugReason + " :" + r.toString());
+        }
 
         if (isServiceNeededLocked(r, knowConn, hasConn)) {
             return;
@@ -4639,6 +5039,13 @@
         //Slog.i(TAG, "Bring down service:");
         //r.dump("  ");
 
+        if (r.isShortFgs()) {
+            // FGS can be stopped without the app calling stopService() or stopSelf(),
+            // due to force-app-standby, or from Task Manager.
+            Slog.w(TAG_SERVICE, "Short FGS brought down without stopping: " + r);
+            maybeStopShortFgsTimeoutLocked(r);
+        }
+
         // Report to all of the connections that the service is no longer
         // available.
         ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections();
@@ -4689,6 +5096,8 @@
 
         // Check to see if the service had been started as foreground, but being
         // brought down before actually showing a notification.  That is not allowed.
+        // TODO(short-service): This is unlikely related to short-FGS, but I'm curious why it's
+        // not allowed. Look into it.
         if (r.fgRequired) {
             Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
                     + r);
@@ -4773,11 +5182,14 @@
             unregisterAppOpCallbackLocked(r);
             r.mFgsExitTime = SystemClock.uptimeMillis();
             logFGSStateChangeLocked(r,
-                    FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
+                    FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
                     r.mFgsExitTime > r.mFgsEnterTime
                             ? (int) (r.mFgsExitTime - r.mFgsEnterTime) : 0,
-                    FGS_STOP_REASON_STOP_SERVICE);
+                    FGS_STOP_REASON_STOP_SERVICE,
+                    FGS_TYPE_POLICY_CHECK_UNKNOWN);
             mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
+
+            // TODO(short-service): Make sure we stop the timeout by here.
         }
 
         r.isForeground = false;
@@ -4805,16 +5217,31 @@
                 // Bump the process to the top of LRU list
                 mAm.updateLruProcessLocked(r.app, false, null);
                 updateServiceForegroundLocked(r.app.mServices, false);
-                try {
-                    oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
-                            oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
-                    mDestroyingServices.add(r);
-                    r.destroying = true;
-                    r.app.getThread().scheduleStopService(r);
-                } catch (Exception e) {
-                    Slog.w(TAG, "Exception when destroying service "
-                            + r.shortInstanceName, e);
-                    serviceProcessGoneLocked(r, enqueueOomAdj);
+                if (r.mIsFgsDelegate) {
+                    if (r.mFgsDelegation.mConnection != null) {
+                        mAm.mHandler.post(() -> {
+                            r.mFgsDelegation.mConnection.onServiceDisconnected(
+                                    r.mFgsDelegation.mOptions.getComponentName());
+                        });
+                    }
+                    for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+                        if (mFgsDelegations.valueAt(i) == r) {
+                            mFgsDelegations.removeAt(i);
+                            break;
+                        }
+                    }
+                } else {
+                    try {
+                        oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
+                                oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                        mDestroyingServices.add(r);
+                        r.destroying = true;
+                        r.app.getThread().scheduleStopService(r);
+                    } catch (Exception e) {
+                        Slog.w(TAG, "Exception when destroying service "
+                                + r.shortInstanceName, e);
+                        serviceProcessGoneLocked(r, enqueueOomAdj);
+                    }
                 }
             } else {
                 if (DEBUG_SERVICE) Slog.v(
@@ -4990,7 +5417,8 @@
                         }
                     }
                 }
-                bringDownServiceIfNeededLocked(s, true, hasAutoCreate, enqueueOomAdj);
+                bringDownServiceIfNeededLocked(s, true, hasAutoCreate, enqueueOomAdj,
+                        "removeConnection");
             }
         }
     }
@@ -5003,6 +5431,13 @@
                 // This is a call from a service start...  take care of
                 // book-keeping.
                 r.callStart = true;
+
+                // Set the result to startCommandResult.
+                // START_TASK_REMOVED_COMPLETE is _not_ a result from onStartCommand(), so
+                // let's ignore.
+                if (res != Service.START_TASK_REMOVED_COMPLETE) {
+                    r.startCommandResult = res;
+                }
                 switch (res) {
                     case Service.START_STICKY_COMPATIBILITY:
                     case Service.START_STICKY: {
@@ -5442,7 +5877,7 @@
                     stopServiceLocked(sr, true);
                 } else {
                     sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
-                            sr.getLastStartId(), baseIntent, null, 0));
+                            sr.getLastStartId(), baseIntent, null, 0, null));
                     if (sr.app != null && sr.app.getThread() != null) {
                         // We always run in the foreground, since this is called as
                         // part of the "remove task" UI operation.
@@ -5857,7 +6292,7 @@
             TimeoutRecord timeoutRecord = TimeoutRecord.forServiceStartWithEndTime(annotation,
                     SystemClock.uptimeMillis());
 
-            timeoutRecord.mLatencyTracker.waitingOnAMSLockEnded();
+            timeoutRecord.mLatencyTracker.waitingOnAMSLockStarted();
             synchronized (mAm) {
                 timeoutRecord.mLatencyTracker.waitingOnAMSLockEnded();
                 if (!r.fgRequired || !r.fgWaiting || r.destroying) {
@@ -6500,7 +6935,7 @@
      */
     private void setFgsRestrictionLocked(String callingPackage,
             int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
-            boolean allowBackgroundActivityStarts) {
+            boolean allowBackgroundActivityStarts, boolean isBindService) {
         r.mLastSetFgsRestrictionTime = SystemClock.elapsedRealtime();
         // Check DeviceConfig flag.
         if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
@@ -6510,14 +6945,15 @@
         if (!r.mAllowWhileInUsePermissionInFgs
                 || (r.mAllowStartForeground == REASON_DENIED)) {
             final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
-                    callingPackage, callingPid, callingUid, r, allowBackgroundActivityStarts);
+                    callingPackage, callingPid, callingUid, r, allowBackgroundActivityStarts,
+                    isBindService);
             if (!r.mAllowWhileInUsePermissionInFgs) {
                 r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
             }
             if (r.mAllowStartForeground == REASON_DENIED) {
                 r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked(
                         allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
-                        userId);
+                        userId, isBindService);
             }
         }
     }
@@ -6537,9 +6973,10 @@
         }
         final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
                 callingPackage, callingPid, callingUid, null /* serviceRecord */,
-                false /* allowBackgroundActivityStarts */);
+                false /* allowBackgroundActivityStarts */, false);
         @ReasonCode int allowStartFgs = shouldAllowFgsStartForegroundNoBindingCheckLocked(
-                allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */);
+                allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */,
+                false /* isBindService */);
 
         if (allowStartFgs == REASON_DENIED) {
             if (canBindingClientStartFgsLocked(callingUid) != null) {
@@ -6559,7 +6996,7 @@
      */
     private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
             int callingPid, int callingUid, @Nullable ServiceRecord targetService,
-            boolean allowBackgroundActivityStarts) {
+            boolean allowBackgroundActivityStarts, boolean isBindService) {
         int ret = REASON_DENIED;
 
         final int uidState = mAm.getUidStateLocked(callingUid);
@@ -6713,12 +7150,12 @@
                                         shouldAllowFgsWhileInUsePermissionLocked(
                                                 clientPackageName,
                                                 clientPid, clientUid, null /* serviceRecord */,
-                                                false /* allowBackgroundActivityStarts */);
+                                                false /* allowBackgroundActivityStarts */, false);
                                 final @ReasonCode int allowStartFgs =
                                         shouldAllowFgsStartForegroundNoBindingCheckLocked(
                                                 allowWhileInUse2,
                                                 clientPid, clientUid, clientPackageName,
-                                                null /* targetService */);
+                                                null /* targetService */, false);
                                 if (allowStartFgs != REASON_DENIED) {
                                     return new Pair<>(allowStartFgs, clientPackageName);
                                 } else {
@@ -6750,11 +7187,11 @@
      */
     private @ReasonCode int shouldAllowFgsStartForegroundWithBindingCheckLocked(
             @ReasonCode int allowWhileInUse, String callingPackage, int callingPid,
-            int callingUid, Intent intent, ServiceRecord r, int userId) {
+            int callingUid, Intent intent, ServiceRecord r, int userId, boolean isBindService) {
         ActivityManagerService.FgsTempAllowListItem tempAllowListReason =
                 r.mInfoTempFgsAllowListReason = mAm.isAllowlistedForFgsStartLOSP(callingUid);
         int ret = shouldAllowFgsStartForegroundNoBindingCheckLocked(allowWhileInUse, callingPid,
-                callingUid, callingPackage, r);
+                callingUid, callingPackage, r, isBindService);
 
         String bindFromPackage = null;
         if (ret == REASON_DENIED) {
@@ -6789,6 +7226,7 @@
                         + "; callerTargetSdkVersion:" + callerTargetSdkVersion
                         + "; startForegroundCount:" + r.mStartForegroundCount
                         + "; bindFromPackage:" + bindFromPackage
+                        + ": isBindService:" + isBindService
                         + "]";
         if (!debugInfo.equals(r.mInfoAllowStartForeground)) {
             r.mLoggedInfoAllowStartForeground = false;
@@ -6799,7 +7237,7 @@
 
     private @ReasonCode int shouldAllowFgsStartForegroundNoBindingCheckLocked(
             @ReasonCode int allowWhileInUse, int callingPid, int callingUid, String callingPackage,
-            @Nullable ServiceRecord targetService) {
+            @Nullable ServiceRecord targetService, boolean isBindService) {
         int ret = allowWhileInUse;
 
         if (ret == REASON_DENIED) {
@@ -6814,7 +7252,7 @@
             final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, app -> {
                 if (app.uid == callingUid) {
                     final ProcessStateRecord state = app.mState;
-                    if (state.isAllowedStartFgs()) {
+                    if (state.isAllowedStartFgs()) { // Procstate <= BFGS?
                         return getReasonCodeFromProcState(state.getCurProcState());
                     } else {
                         final ActiveInstrumentation instr = app.getActiveInstrumentation();
@@ -6981,10 +7419,12 @@
     }
 
     private void logFgsBackgroundStart(ServiceRecord r) {
+        /*
         // Only log if FGS is started from background.
         if (!isFgsBgStart(r.mAllowStartForeground)) {
             return;
         }
+        */
         if (!r.mLoggedInfoAllowStartForeground) {
             final String msg = "Background started FGS: "
                     + ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ")
@@ -6996,10 +7436,10 @@
                 }
                 Slog.i(TAG, msg);
             } else {
-                if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
-                        mAm.mConstants.mFgsStartDeniedLogSampleRate)) {
+                //if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
+                //        mAm.mConstants.mFgsStartDeniedLogSampleRate)) {
                     Slog.wtfQuiet(TAG, msg);
-                }
+                //}
                 Slog.w(TAG, msg);
             }
             r.mLoggedInfoAllowStartForeground = true;
@@ -7011,17 +7451,20 @@
      * @param r ServiceRecord
      * @param state one of ENTER/EXIT/DENIED event.
      * @param durationMs Only meaningful for EXIT event, the duration from ENTER and EXIT state.
+     * @param fgsStopReason why was this FGS stopped.
+     * @param fgsTypeCheckCode The FGS type policy check result.
      */
     private void logFGSStateChangeLocked(ServiceRecord r, int state, int durationMs,
-            @FgsStopReason int fgsStopReason) {
+            @FgsStopReason int fgsStopReason,
+            @ForegroundServicePolicyCheckCode int fgsTypeCheckCode) {
         if (!ActivityManagerUtils.shouldSamplePackageForAtom(
                 r.packageName, mAm.mConstants.mFgsAtomSampleRate)) {
             return;
         }
         boolean allowWhileInUsePermissionInFgs;
         @PowerExemptionManager.ReasonCode int fgsStartReasonCode;
-        if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER
-                || state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
+        if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER
+                || state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
             allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
             fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
         } else {
@@ -7047,14 +7490,20 @@
                 r.mStartForegroundCount,
                 ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
                 r.mFgsHasNotificationPermission,
-                r.foregroundServiceType);
+                r.foregroundServiceType,
+                fgsTypeCheckCode,
+                r.mIsFgsDelegate,
+                r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mClientUid : INVALID_UID,
+                r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mDelegationService
+                        : ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT
+        );
 
         int event = 0;
-        if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
+        if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
             event = EventLogTags.AM_FOREGROUND_SERVICE_START;
-        } else if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
+        } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
             event = EventLogTags.AM_FOREGROUND_SERVICE_STOP;
-        } else if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED) {
+        } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED) {
             event = EventLogTags.AM_FOREGROUND_SERVICE_DENIED;
         } else {
             // Unknown event.
@@ -7082,7 +7531,7 @@
             String callingPackage) {
         return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
                 /* targetService */ null,
-                /* allowBackgroundActivityStarts */ false)
+                /* allowBackgroundActivityStarts */ false, false)
                 != REASON_DENIED;
     }
 
@@ -7111,4 +7560,194 @@
                 return "UNKNOWN";
         }
     }
+
+    /**
+     * Start a foreground service delegate. The delegate is not an actual service component, it is
+     * merely a delegate that promotes the client process into foreground service process state.
+     *
+     * @param options an ForegroundServiceDelegationOptions object.
+     * @param connection callback if the delegate is started successfully.
+     * @return true if delegate is started, false otherwise.
+     * @throw SecurityException if PackageManaager can not resolve
+     *        {@link ForegroundServiceDelegationOptions#mClientPackageName} or the resolved
+     *        package's UID is not same as {@link ForegroundServiceDelegationOptions#mClientUid}
+     */
+    boolean startForegroundServiceDelegateLocked(
+            @NonNull ForegroundServiceDelegationOptions options,
+            @Nullable ServiceConnection connection) {
+        Slog.v(TAG, "startForegroundServiceDelegateLocked " + options.getDescription());
+        final ComponentName cn = options.getComponentName();
+        for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+            ForegroundServiceDelegation delegation = mFgsDelegations.keyAt(i);
+            if (delegation.mOptions.isSameDelegate(options)) {
+                Slog.e(TAG, "startForegroundServiceDelegate " + options.getDescription()
+                        + " already exists, multiple connections are not allowed");
+                return false;
+            }
+        }
+        final int callingPid = options.mClientPid;
+        final int callingUid = options.mClientUid;
+        final int userId = UserHandle.getUserId(callingUid);
+        final String callingPackage = options.mClientPackageName;
+
+        if (!canStartForegroundServiceLocked(callingPid, callingUid, callingPackage)) {
+            Slog.d(TAG, "startForegroundServiceDelegateLocked aborted,"
+                    + " app is in the background");
+            return false;
+        }
+
+        IApplicationThread caller = options.mClientAppThread;
+        ProcessRecord callerApp;
+        if (caller != null) {
+            callerApp = mAm.getRecordForAppLOSP(caller);
+        } else {
+            synchronized (mAm.mPidsSelfLocked) {
+                callerApp = mAm.mPidsSelfLocked.get(callingPid);
+                caller = callerApp.getThread();
+            }
+        }
+        if (callerApp == null) {
+            throw new SecurityException(
+                    "Unable to find app for caller " + caller
+                            + " (pid=" + callingPid
+                            + ") when startForegroundServiceDelegateLocked " + cn);
+        }
+
+        Intent intent = new Intent();
+        intent.setComponent(cn);
+        ServiceLookupResult res = retrieveServiceLocked(intent, null /*instanceName */,
+                false /* isSdkSandboxService */, INVALID_UID /* sdkSandboxClientAppUid */,
+                null /* sdkSandboxClientAppPackage */, null /* resolvedType */, callingPackage,
+                callingPid, callingUid, userId, true /* createIfNeeded */,
+                false /* callingFromFg */, false /* isBindExternal */, false /* allowInstant */ ,
+                options);
+        if (res == null || res.record == null) {
+            Slog.d(TAG,
+                    "startForegroundServiceDelegateLocked retrieveServiceLocked returns null");
+            return false;
+        }
+
+        final ServiceRecord r = res.record;
+        r.setProcess(callerApp, caller, callingPid, null);
+        r.mIsFgsDelegate = true;
+        final ForegroundServiceDelegation delegation =
+                new ForegroundServiceDelegation(options, connection);
+        r.mFgsDelegation = delegation;
+        mFgsDelegations.put(delegation, r);
+        r.isForeground = true;
+        r.mFgsEnterTime = SystemClock.uptimeMillis();
+        r.foregroundServiceType = options.mForegroundServiceTypes;
+        setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId,
+                false, false);
+        final ProcessServiceRecord psr = callerApp.mServices;
+        final boolean newService = psr.startService(r);
+        // updateOomAdj.
+        updateServiceForegroundLocked(psr, /* oomAdj= */ true);
+
+        synchronized (mAm.mProcessStats.mLock) {
+            final ServiceState stracker = r.getTracker();
+            if (stracker != null) {
+                stracker.setForeground(true,
+                        mAm.mProcessStats.getMemFactorLocked(),
+                        SystemClock.uptimeMillis());
+            }
+        }
+
+        mAm.mBatteryStatsService.noteServiceStartRunning(callingUid, callingPackage,
+                cn.getClassName());
+        mAm.mAppOpsService.startOperation(AppOpsManager.getToken(mAm.mAppOpsService),
+                AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null,
+                true, false, null, false,
+                AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+        registerAppOpCallbackLocked(r);
+        logFGSStateChangeLocked(r,
+                FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
+                0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
+        // Notify the caller.
+        if (connection != null) {
+            mAm.mHandler.post(() -> {
+                connection.onServiceConnected(cn, delegation.mBinder);
+            });
+        }
+        signalForegroundServiceObserversLocked(r);
+        return true;
+    }
+
+    /**
+     * Stop the foreground service delegate. This removes the process out of foreground service
+     * process state.
+     *
+     * @param options an ForegroundServiceDelegationOptions object.
+     */
+    void stopForegroundServiceDelegateLocked(@NonNull ForegroundServiceDelegationOptions options) {
+        ServiceRecord r = null;
+        for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+            if (mFgsDelegations.keyAt(i).mOptions.isSameDelegate(options)) {
+                Slog.d(TAG, "stopForegroundServiceDelegateLocked " + options.getDescription());
+                r = mFgsDelegations.valueAt(i);
+                break;
+            }
+        }
+        if (r != null) {
+            bringDownServiceLocked(r, false);
+        } else {
+            Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist "
+                    + options.getDescription());
+        }
+    }
+
+    /**
+     * Stop the foreground service delegate by its ServiceConnection.
+     * This removes the process out of foreground service process state.
+     *
+     * @param connection an ServiceConnection object.
+     */
+    void stopForegroundServiceDelegateLocked(@NonNull ServiceConnection connection) {
+        ServiceRecord r = null;
+        for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+            final ForegroundServiceDelegation d = mFgsDelegations.keyAt(i);
+            if (d.mConnection == connection) {
+                Slog.d(TAG, "stopForegroundServiceDelegateLocked "
+                        + d.mOptions.getDescription());
+                r = mFgsDelegations.valueAt(i);
+                break;
+            }
+        }
+        if (r != null) {
+            bringDownServiceLocked(r, false);
+        } else {
+            Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist");
+        }
+    }
+
+    private static void getClientPackages(ServiceRecord sr, ArraySet<String> output) {
+        var connections = sr.getConnections();
+        for (int conni = connections.size() - 1; conni >= 0; conni--) {
+            var connl = connections.valueAt(conni);
+            for (int i = 0, size = connl.size(); i < size; i++) {
+                var conn = connl.get(i);
+                if (conn.binding.client != null) {
+                    output.add(conn.binding.client.info.packageName);
+                }
+            }
+        }
+    }
+
+    /**
+     * Return all client package names of a service.
+     */
+    ArraySet<String> getClientPackagesLocked(@NonNull String servicePackageName) {
+        var results = new ArraySet<String>();
+        int[] users = mAm.mUserController.getUsers();
+        for (int ui = 0; ui < users.length; ui++) {
+            ArrayMap<ComponentName, ServiceRecord> alls = getServicesLocked(users[ui]);
+            for (int i = 0, size = alls.size(); i < size; i++) {
+                ServiceRecord sr = alls.valueAt(i);
+                if (sr.name.getPackageName().equals(servicePackageName)) {
+                    getClientPackages(sr, results);
+                }
+            }
+        }
+        return results;
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 16fe121..2d69667 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -249,6 +249,18 @@
     private static final String KEY_MAX_PHANTOM_PROCESSES = "max_phantom_processes";
 
     /**
+     * Enables proactive killing of cached apps
+     */
+    private static final String KEY_PROACTIVE_KILLS_ENABLED = "proactive_kills_enabled";
+
+    /**
+      * Trim LRU cached app when swap falls below this minimum percentage.
+      *
+      * Depends on KEY_PROACTIVE_KILLS_ENABLED
+      */
+    private static final String KEY_LOW_SWAP_THRESHOLD_PERCENT = "low_swap_threshold_percent";
+
+    /**
      * Default value for mFlagBackgroundActivityStartsEnabled if not explicitly set in
      * Settings.Global. This allows it to be set experimentally unless it has been
      * enabled/disabled in developer options. Defaults to false.
@@ -306,6 +318,12 @@
             "deferred_fgs_notification_interval";
 
     /**
+     * Same as {@link #KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL} but for "short FGS".
+     */
+    private static final String KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL_FOR_SHORT =
+            "deferred_fgs_notification_interval_for_short";
+
+    /**
      * Time in milliseconds; once an FGS notification for a given uid has been
      * deferred, no subsequent FGS notification from that uid will be deferred
      * until this amount of time has passed.  Default is two minutes
@@ -315,6 +333,12 @@
             "deferred_fgs_notification_exclusion_time";
 
     /**
+     * Same as {@link #KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME} but for "short FGS".
+     */
+    private static final String KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME_FOR_SHORT =
+            "deferred_fgs_notification_exclusion_time_for_short";
+
+    /**
      * Default value for mPushMessagingOverQuotaBehavior if not explicitly set in
      * Settings.Global.
      */
@@ -571,11 +595,22 @@
     // the foreground state.
     volatile long mFgsNotificationDeferralInterval = 10_000;
 
+    /**
+     * Same as {@link #mFgsNotificationDeferralInterval} but used for "short FGS".
+     */
+    volatile long mFgsNotificationDeferralIntervalForShort = mFgsNotificationDeferralInterval;
+
     // Rate limit: minimum time after an app's FGS notification is deferred
     // before another FGS notification from that app can be deferred.
     volatile long mFgsNotificationDeferralExclusionTime = 2 * 60 * 1000L;
 
     /**
+     * Same as {@link #mFgsNotificationDeferralExclusionTime} but used for "short FGS".
+     */
+    volatile long mFgsNotificationDeferralExclusionTimeForShort =
+            mFgsNotificationDeferralExclusionTime;
+
+    /**
      * When server pushing message is over the quote, select one of the temp allow list type as
      * defined in {@link PowerExemptionManager.TempAllowListType}
      */
@@ -874,6 +909,10 @@
      */
     private static final long DEFAULT_MIN_ASSOC_LOG_DURATION = 5 * 60 * 1000; // 5 mins
 
+    private static final boolean DEFAULT_PROACTIVE_KILLS_ENABLED = false;
+
+    private static final float DEFAULT_LOW_SWAP_THRESHOLD_PERCENT = 0.10f;
+
     private static final String KEY_MIN_ASSOC_LOG_DURATION = "min_assoc_log_duration";
 
     public static long MIN_ASSOC_LOG_DURATION = DEFAULT_MIN_ASSOC_LOG_DURATION;
@@ -904,6 +943,48 @@
     public static boolean BINDER_HEAVY_HITTER_AUTO_SAMPLER_ENABLED;
     public static int BINDER_HEAVY_HITTER_AUTO_SAMPLER_BATCHSIZE;
     public static float BINDER_HEAVY_HITTER_AUTO_SAMPLER_THRESHOLD;
+    public static boolean PROACTIVE_KILLS_ENABLED = DEFAULT_PROACTIVE_KILLS_ENABLED;
+    public static float LOW_SWAP_THRESHOLD_PERCENT = DEFAULT_LOW_SWAP_THRESHOLD_PERCENT;
+
+    /** Timeout for a "short service" FGS, in milliseconds. */
+    private static final String KEY_SHORT_FGS_TIMEOUT_DURATION =
+            "short_fgs_timeout_duration";
+
+    /** @see #KEY_SHORT_FGS_TIMEOUT_DURATION */
+    static final long DEFAULT_SHORT_FGS_TIMEOUT_DURATION = 60_000;
+
+    /** @see #KEY_SHORT_FGS_TIMEOUT_DURATION */
+    public volatile long mShortFgsTimeoutDuration = DEFAULT_SHORT_FGS_TIMEOUT_DURATION;
+
+    /**
+     * If a "short service" doesn't finish within this after the timeout (
+     * {@link #KEY_SHORT_FGS_TIMEOUT_DURATION}), then we'll lower the procstate.
+     */
+    private static final String KEY_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION =
+            "short_fgs_proc_state_extra_wait_duration";
+
+    /** @see #KEY_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION */
+    static final long DEFAULT_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION = 5_000;
+
+    /** @see #KEY_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION */
+    public volatile long mShortFgsProcStateExtraWaitDuration =
+            DEFAULT_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION;
+
+    /**
+     * If a "short service" doesn't finish within this after the timeout (
+     * {@link #KEY_SHORT_FGS_TIMEOUT_DURATION}), then we'll declare an ANR.
+     * i.e. if the timeout is 60 seconds, and this ANR extra duration is 5 seconds, then
+     * the app will be ANR'ed in 65 seconds after a short service starts and it's not stopped.
+     */
+    private static final String KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION =
+            "short_fgs_anr_extra_wait_duration";
+
+    /** @see #KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION */
+    static final long DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION = 10_000;
+
+    /** @see #KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION */
+    public volatile long mShortFgsAnrExtraWaitDuration =
+            DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION;
 
     private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
             new OnPropertiesChangedListener() {
@@ -944,6 +1025,12 @@
                             case KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME:
                                 updateFgsNotificationDeferralExclusionTime();
                                 break;
+                            case KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL_FOR_SHORT:
+                                updateFgsNotificationDeferralIntervalForShort();
+                                break;
+                            case KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME_FOR_SHORT:
+                                updateFgsNotificationDeferralExclusionTimeForShort();
+                                break;
                             case KEY_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR:
                                 updatePushMessagingOverQuotaBehavior();
                                 break;
@@ -1040,6 +1127,21 @@
                             case KEY_MAX_SERVICE_CONNECTIONS_PER_PROCESS:
                                 updateMaxServiceConnectionsPerProcess();
                                 break;
+                            case KEY_SHORT_FGS_TIMEOUT_DURATION:
+                                updateShortFgsTimeoutDuration();
+                                break;
+                            case KEY_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION:
+                                updateShortFgsProcStateExtraWaitDuration();
+                                break;
+                            case KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION:
+                                updateShortFgsAnrExtraWaitDuration();
+                                break;
+                            case KEY_PROACTIVE_KILLS_ENABLED:
+                                updateProactiveKillsEnabled();
+                                break;
+                            case KEY_LOW_SWAP_THRESHOLD_PERCENT:
+                                updateLowSwapThresholdPercent();
+                                break;
                             default:
                                 break;
                         }
@@ -1350,6 +1452,13 @@
                 /*default value*/ 10_000L);
     }
 
+    private void updateFgsNotificationDeferralIntervalForShort() {
+        mFgsNotificationDeferralIntervalForShort = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL_FOR_SHORT,
+                /*default value*/ 10_000L);
+    }
+
     private void updateFgsNotificationDeferralExclusionTime() {
         mFgsNotificationDeferralExclusionTime = DeviceConfig.getLong(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1357,6 +1466,13 @@
                 /*default value*/ 2 * 60 * 1000L);
     }
 
+    private void updateFgsNotificationDeferralExclusionTimeForShort() {
+        mFgsNotificationDeferralExclusionTimeForShort = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME_FOR_SHORT,
+                /*default value*/ 2 * 60 * 1000L);
+    }
+
     private void updatePushMessagingOverQuotaBehavior() {
         mPushMessagingOverQuotaBehavior = DeviceConfig.getInt(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1660,6 +1776,20 @@
         CUR_TRIM_CACHED_PROCESSES = (MAX_CACHED_PROCESSES-rawMaxEmptyProcesses)/3;
     }
 
+    private void updateProactiveKillsEnabled() {
+        PROACTIVE_KILLS_ENABLED = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_PROACTIVE_KILLS_ENABLED,
+                DEFAULT_PROACTIVE_KILLS_ENABLED);
+    }
+
+    private void updateLowSwapThresholdPercent() {
+        LOW_SWAP_THRESHOLD_PERCENT = DeviceConfig.getFloat(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_LOW_SWAP_THRESHOLD_PERCENT,
+                DEFAULT_LOW_SWAP_THRESHOLD_PERCENT);
+    }
+
     private void updateMinAssocLogDuration() {
         MIN_ASSOC_LOG_DURATION = DeviceConfig.getLong(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MIN_ASSOC_LOG_DURATION,
@@ -1708,6 +1838,27 @@
                 DEFAULT_MAX_SERVICE_CONNECTIONS_PER_PROCESS);
     }
 
+    private void updateShortFgsTimeoutDuration() {
+        mShortFgsTimeoutDuration = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_SHORT_FGS_TIMEOUT_DURATION,
+                DEFAULT_SHORT_FGS_TIMEOUT_DURATION);
+    }
+
+    private void updateShortFgsProcStateExtraWaitDuration() {
+        mShortFgsProcStateExtraWaitDuration = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION,
+                DEFAULT_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION);
+    }
+
+    private void updateShortFgsAnrExtraWaitDuration() {
+        mShortFgsAnrExtraWaitDuration = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION,
+                DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
+    }
+
     @NeverCompile // Avoid size overhead of debugging code.
     void dump(PrintWriter pw) {
         pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
@@ -1860,6 +2011,32 @@
         pw.print("="); pw.println(mNetworkAccessTimeoutMs);
         pw.print("  "); pw.print(KEY_MAX_SERVICE_CONNECTIONS_PER_PROCESS);
         pw.print("="); pw.println(mMaxServiceConnectionsPerProcess);
+        pw.print("  "); pw.print(KEY_PROACTIVE_KILLS_ENABLED);
+        pw.print("="); pw.println(PROACTIVE_KILLS_ENABLED);
+        pw.print("  "); pw.print(KEY_LOW_SWAP_THRESHOLD_PERCENT);
+        pw.print("="); pw.println(LOW_SWAP_THRESHOLD_PERCENT);
+
+        pw.print("  "); pw.print(KEY_DEFERRED_FGS_NOTIFICATIONS_ENABLED);
+        pw.print("="); pw.println(mFlagFgsNotificationDeferralEnabled);
+        pw.print("  "); pw.print(KEY_DEFERRED_FGS_NOTIFICATIONS_API_GATED);
+        pw.print("="); pw.println(mFlagFgsNotificationDeferralApiGated);
+
+        pw.print("  "); pw.print(KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL);
+        pw.print("="); pw.println(mFgsNotificationDeferralInterval);
+        pw.print("  "); pw.print(KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL_FOR_SHORT);
+        pw.print("="); pw.println(mFgsNotificationDeferralIntervalForShort);
+
+        pw.print("  "); pw.print(KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME);
+        pw.print("="); pw.println(mFgsNotificationDeferralExclusionTime);
+        pw.print("  "); pw.print(KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME_FOR_SHORT);
+        pw.print("="); pw.println(mFgsNotificationDeferralExclusionTimeForShort);
+
+        pw.print("  "); pw.print(KEY_SHORT_FGS_TIMEOUT_DURATION);
+        pw.print("="); pw.println(mShortFgsTimeoutDuration);
+        pw.print("  "); pw.print(KEY_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION);
+        pw.print("="); pw.println(mShortFgsProcStateExtraWaitDuration);
+        pw.print("  "); pw.print(KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
+        pw.print("="); pw.println(mShortFgsAnrExtraWaitDuration);
 
         pw.println();
         if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 1d2c36b..9f2cc7f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.content.Context;
@@ -92,4 +93,28 @@
             int clientAppUid, @NonNull String clientAppPackage, @NonNull String processName,
             @Context.BindServiceFlags int flags)
             throws RemoteException;
+
+    /**
+     * Start a foreground service delegate.
+     * @param options foreground service delegate options.
+     * @param connection a service connection served as callback to caller.
+     * @return true if delegate is started successfully, false otherwise.
+     * @hide
+     */
+    boolean startForegroundServiceDelegate(@NonNull ForegroundServiceDelegationOptions options,
+            @Nullable ServiceConnection connection);
+
+    /**
+     * Stop a foreground service delegate.
+     * @param options the foreground service delegate options.
+     * @hide
+     */
+    void stopForegroundServiceDelegate(@NonNull ForegroundServiceDelegationOptions options);
+
+    /**
+     * Stop a foreground service delegate by service connection.
+     * @param connection service connection used to start delegate previously.
+     * @hide
+     */
+    void stopForegroundServiceDelegate(@NonNull ServiceConnection connection);
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ea66884..b8a982a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -87,6 +87,7 @@
 import static android.os.Process.isSdkSandboxUid;
 import static android.os.Process.isThreadInProcess;
 import static android.os.Process.killProcess;
+import static android.os.Process.killProcessGroup;
 import static android.os.Process.killProcessQuiet;
 import static android.os.Process.myPid;
 import static android.os.Process.myUid;
@@ -181,6 +182,7 @@
 import android.app.ApplicationExitInfo;
 import android.app.ApplicationThreadConstants;
 import android.app.BroadcastOptions;
+import android.app.ComponentOptions;
 import android.app.ContentProviderHolder;
 import android.app.IActivityController;
 import android.app.IActivityManager;
@@ -205,7 +207,7 @@
 import android.app.SyncNotedAppOp;
 import android.app.WaitResult;
 import android.app.assist.ActivityId;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.IBackupManager;
 import android.app.compat.CompatChanges;
 import android.app.job.JobParameters;
@@ -332,6 +334,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;
@@ -377,12 +380,10 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.MemInfoReader;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.DecFunction;
 import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriFunction;
 import com.android.internal.util.function.UndecFunction;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.BootReceiver;
@@ -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 {
@@ -947,13 +953,6 @@
             }
             return false;
         }
-
-        boolean doRemoveIfNoThreadInternal(int pid, ProcessRecord app) {
-            if (app == null || app.getThread() != null) {
-                return false;
-            }
-            return doRemoveInternal(pid, app);
-        }
     }
 
     private final PendingStartActivityUids mPendingStartActivityUids;
@@ -985,7 +984,7 @@
      * method.
      */
     @GuardedBy("this")
-    void removePidLocked(int pid, ProcessRecord app) {
+    boolean removePidLocked(int pid, ProcessRecord app) {
         final boolean removed;
         synchronized (mPidsSelfLocked) {
             removed = mPidsSelfLocked.doRemoveInternal(pid, app);
@@ -996,26 +995,6 @@
             }
             mAtmInternal.onProcessUnMapped(pid);
         }
-    }
-
-    /**
-     * Removes the process record from the map if it doesn't have a thread.
-     * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
-     * method.
-     */
-    @GuardedBy("this")
-    private boolean removePidIfNoThreadLocked(ProcessRecord app) {
-        final boolean removed;
-        final int pid = app.getPid();
-        synchronized (mPidsSelfLocked) {
-            removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(pid, app);
-        }
-        if (removed) {
-            synchronized (sActiveProcessInfoSelfLocked) {
-                sActiveProcessInfoSelfLocked.remove(pid);
-            }
-            mAtmInternal.onProcessUnMapped(pid);
-        }
         return removed;
     }
 
@@ -1558,6 +1537,8 @@
     static final int WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG = 73;
     static final int DISPATCH_SENDING_BROADCAST_EVENT = 74;
     static final int DISPATCH_BINDING_SERVICE_EVENT = 75;
+    static final int SERVICE_SHORT_FGS_TIMEOUT_MSG = 76;
+    static final int SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG = 77;
 
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
 
@@ -1892,6 +1873,12 @@
                     mBindServiceEventListeners.forEach(l ->
                             l.onBindingService((String) msg.obj, msg.arg1));
                 } break;
+                case SERVICE_SHORT_FGS_TIMEOUT_MSG: {
+                    mServices.onShortFgsTimeout((ServiceRecord) msg.obj);
+                } break;
+                case SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG: {
+                    mServices.onShortFgsAnrTimeout((ServiceRecord) msg.obj);
+                } break;
             }
         }
     }
@@ -2351,7 +2338,7 @@
         mAppErrors = null;
         mPackageWatchdog = null;
         mAppOpsService = mInjector.getAppOpsService(null /* file */, null /* handler */);
-        mBatteryStatsService = null;
+        mBatteryStatsService = mInjector.getBatteryStatsService();
         mHandler = new MainHandler(handlerThread.getLooper());
         mHandlerThread = handlerThread;
         mConstants = new ActivityManagerConstants(mContext, this, mHandler);
@@ -2366,7 +2353,7 @@
         mIntentFirewall = null;
         mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
         mCpHelper = new ContentProviderHelper(this, false);
-        mServices = null;
+        mServices = mInjector.getActiveServices(this);
         mSystemThread = null;
         mUiHandler = injector.getUiHandler(null /* service */);
         mUidObserverController = new UidObserverController(mUiHandler);
@@ -3426,14 +3413,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,70 +3432,47 @@
      * @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);
-            // Each set of ANR traces is written to a separate file and dumpstate will process
-            // all such files and add them to a captured bug report if they're recent enough.
-            maybePruneOldTraces(tracesDir);
 
             // NOTE: We should consider creating the file in native code atomically once we've
             // gotten rid of the old scheme of dumping and lot of the code that deals with paths
@@ -3534,16 +3499,15 @@
                         + (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);
             }
+            // Each set of ANR traces is written to a separate file and dumpstate will process
+            // all such files and add them to a captured bug report if they're recent enough.
+            maybePruneOldTraces(tracesDir);
 
             return tracesFile;
         } finally {
@@ -3558,6 +3522,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 +3661,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 +3679,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 +3686,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 +3707,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 +3728,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 +3754,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 +3767,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 +3797,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 +3812,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
@@ -4715,7 +4745,7 @@
     @GuardedBy("this")
     void handleProcessStartOrKillTimeoutLocked(ProcessRecord app, boolean isKillTimeout) {
         final int pid = app.getPid();
-        boolean gone = isKillTimeout || removePidIfNoThreadLocked(app);
+        boolean gone = isKillTimeout || removePidLocked(pid, app);
 
         if (gone) {
             if (isKillTimeout) {
@@ -4796,7 +4826,7 @@
     }
 
     @GuardedBy("this")
-    private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
+    private void attachApplicationLocked(@NonNull IApplicationThread thread,
             int pid, int callingUid, long startSeq) {
 
         // Find the application record that is being attached...  either via
@@ -4861,7 +4891,7 @@
                     // Ignore exceptions.
                 }
             }
-            return false;
+            return;
         }
 
         // If this application record is still attached to a previous
@@ -4886,7 +4916,7 @@
             mProcessList.startProcessLocked(app,
                     new HostingRecord(HostingRecord.HOSTING_TYPE_LINK_FAIL, processName),
                     ZYGOTE_POLICY_FLAG_EMPTY);
-            return false;
+            return;
         }
 
         EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
@@ -4897,7 +4927,7 @@
             app.mState.setVerifiedAdj(ProcessList.INVALID_ADJ);
             mOomAdjuster.setAttachingSchedGroupLSP(app);
             app.mState.setForcingToImportant(null);
-            updateProcessForegroundLocked(app, false, 0, false);
+            clearProcessForegroundLocked(app);
             app.mState.setHasShownUi(false);
             app.mState.setCached(false);
             app.setDebugging(false);
@@ -4909,8 +4939,6 @@
             app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId));
         }
 
-        mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
-
         boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
         List<ProviderInfo> providers = normalMode
                                             ? mCpHelper.generateApplicationProvidersLocked(app)
@@ -5076,7 +5104,7 @@
             app.killLocked("error during bind", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
                     true);
             handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
-            return false;
+            return;
         }
 
         // Remove this record from the list of starting applications.
@@ -5084,90 +5112,6 @@
         if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
                 "Attach application locked removing on hold: " + app);
         mProcessesOnHold.remove(app);
-
-        boolean badApp = false;
-        boolean didSomething = false;
-
-        // See if the top visible activity is waiting to run in this process...
-        if (normalMode) {
-            try {
-                didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
-            } catch (Exception e) {
-                Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
-                badApp = true;
-            }
-        }
-
-        // Find any services that should be running in this process...
-        if (!badApp) {
-            try {
-                didSomething |= mServices.attachApplicationLocked(app, processName);
-                checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");
-            } catch (Exception e) {
-                Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
-                badApp = true;
-            }
-        }
-
-        // Check if a next-broadcast receiver is in this process...
-        if (!badApp) {
-            try {
-                for (BroadcastQueue queue : mBroadcastQueues) {
-                    didSomething |= queue.onApplicationAttachedLocked(app);
-                }
-                checkTime(startTime, "attachApplicationLocked: after dispatching broadcasts");
-            } catch (Exception e) {
-                // If the app died trying to launch the receiver we declare it 'bad'
-                Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
-                badApp = true;
-            }
-        }
-
-        // Check whether the next backup agent is in this process...
-        if (!badApp && backupTarget != null && backupTarget.app == app) {
-            if (DEBUG_BACKUP) Slog.v(TAG_BACKUP,
-                    "New app is backup target, launching agent for " + app);
-            notifyPackageUse(backupTarget.appInfo.packageName,
-                             PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
-            try {
-                thread.scheduleCreateBackupAgent(backupTarget.appInfo,
-                        backupTarget.backupMode, backupTarget.userId, backupTarget.operationType);
-            } catch (Exception e) {
-                Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
-                badApp = true;
-            }
-        }
-
-        if (badApp) {
-            app.killLocked("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
-                    true);
-            handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
-            return false;
-        }
-
-        if (!didSomething) {
-            updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
-            checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked");
-        }
-
-
-        final HostingRecord hostingRecord = app.getHostingRecord();
-        String shortAction = getShortAction(hostingRecord.getAction());
-        FrameworkStatsLog.write(
-                FrameworkStatsLog.PROCESS_START_TIME,
-                app.info.uid,
-                pid,
-                app.info.packageName,
-                FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
-                app.getStartElapsedTime(),
-                (int) (bindApplicationTimeMillis - app.getStartUptime()),
-                (int) (SystemClock.uptimeMillis() - app.getStartUptime()),
-                hostingRecord.getType(),
-                hostingRecord.getName(),
-                shortAction,
-                HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
-                HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
-        return true;
     }
 
     @Override
@@ -5184,6 +5128,145 @@
         }
     }
 
+    private void finishAttachApplicationInner(long startSeq, int uid, int pid) {
+        final long startTime = SystemClock.uptimeMillis();
+        // Find the application record that is being attached...  either via
+        // the pid if we are running in multiple processes, or just pull the
+        // next app record if we are emulating process with anonymous threads.
+        final ProcessRecord app;
+        synchronized (mPidsSelfLocked) {
+            app = mPidsSelfLocked.get(pid);
+        }
+
+        if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
+            mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+        } else {
+            Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
+                    + ". Uid: " + uid);
+            killProcess(pid);
+            killProcessGroup(uid, pid);
+            mProcessList.noteAppKill(pid, uid,
+                    ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
+                    ApplicationExitInfo.SUBREASON_UNKNOWN,
+                    "wrong startSeq");
+            synchronized (this) {
+                app.killLocked("unexpected process record",
+                        ApplicationExitInfo.REASON_OTHER, true);
+            }
+            return;
+        }
+
+        synchronized (this) {
+            final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
+            final String processName = app.processName;
+            boolean badApp = false;
+            boolean didSomething = false;
+
+            // See if the top visible activity is waiting to run in this process...
+            if (normalMode) {
+                try {
+                    didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
+                } catch (Exception e) {
+                    Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
+                    badApp = true;
+                }
+            }
+
+            // Find any services that should be running in this process...
+            if (!badApp) {
+                try {
+                    didSomething |= mServices.attachApplicationLocked(app, processName);
+                    checkTime(startTime, "finishAttachApplicationInner: "
+                            + "after mServices.attachApplicationLocked");
+                } catch (Exception e) {
+                    Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
+                    badApp = true;
+                }
+            }
+
+            // Check if a next-broadcast receiver is in this process...
+            if (!badApp) {
+                try {
+                    for (BroadcastQueue queue : mBroadcastQueues) {
+                        didSomething |= queue.onApplicationAttachedLocked(app);
+                    }
+                    checkTime(startTime, "finishAttachApplicationInner: "
+                            + "after dispatching broadcasts");
+                } catch (Exception e) {
+                    // If the app died trying to launch the receiver we declare it 'bad'
+                    Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
+                    badApp = true;
+                }
+            }
+
+            // Check whether the next backup agent is in this process...
+            final BackupRecord backupTarget = mBackupTargets.get(app.userId);
+            if (!badApp && backupTarget != null && backupTarget.app == app) {
+                if (DEBUG_BACKUP) {
+                    Slog.v(TAG_BACKUP,
+                            "New app is backup target, launching agent for " + app);
+                }
+
+                notifyPackageUse(backupTarget.appInfo.packageName,
+                        PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
+                try {
+                    app.getThread().scheduleCreateBackupAgent(backupTarget.appInfo,
+                            backupTarget.backupMode, backupTarget.userId,
+                            backupTarget.backupDestination);
+                } catch (Exception e) {
+                    Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
+                    badApp = true;
+                }
+            }
+
+            if (badApp) {
+                app.killLocked("error during init",
+                        ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
+                handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
+                return;
+            }
+
+            if (!didSomething) {
+                updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+                checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
+            }
+
+            final HostingRecord hostingRecord = app.getHostingRecord();
+            final String shortAction = getShortAction(hostingRecord.getAction());
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.PROCESS_START_TIME,
+                    app.info.uid,
+                    pid,
+                    app.info.packageName,
+                    FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
+                    app.getStartElapsedTime(),
+                    (int) (app.getBindApplicationTime() - app.getStartUptime()),
+                    (int) (SystemClock.uptimeMillis() - app.getStartUptime()),
+                    hostingRecord.getType(),
+                    hostingRecord.getName(),
+                    shortAction,
+                    HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
+                    HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
+        }
+    }
+
+    @Override
+    public final void finishAttachApplication(long startSeq) {
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+
+        if (pid == MY_PID && uid == SYSTEM_UID) {
+            return;
+        }
+
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            finishAttachApplicationInner(startSeq, uid, pid);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
     /**
      * @return The last part of the string of an intent's action.
      */
@@ -5757,7 +5840,7 @@
                     return;
                 }
                 pr.mState.setForcingToImportant(null);
-                updateProcessForegroundLocked(pr, false, 0, false);
+                clearProcessForegroundLocked(pr);
             }
             updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
         }
@@ -6093,6 +6176,7 @@
     /**
      * This can be called with or without the global lock held.
      */
+    @PermissionMethod(anyOf = true)
     private void enforceCallingHasAtLeastOnePermission(String func, String... permissions) {
         for (String permission : permissions) {
             if (checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
@@ -8353,13 +8437,10 @@
 
         // On Automotive / Headless System User Mode, at this point the system user has already been
         // started and unlocked, and some of the tasks we do here have already been done. So skip
-        // those in that case.
+        // those in that case. The duplicate system user start is guarded in SystemServiceManager.
         // TODO(b/242195409): this workaround shouldn't be necessary once we move the headless-user
-        // start logic to UserManager-land
-        final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
-        if (bootingSystemUser) {
-            mUserController.onSystemUserStarting();
-        }
+        // start logic to UserManager-land.
+        mUserController.onSystemUserStarting();
 
         synchronized (this) {
             // Only start up encryption-aware persistent apps; once user is
@@ -8389,7 +8470,15 @@
                 t.traceEnd();
             }
 
-            if (bootingSystemUser) {
+            // Some systems - like automotive - will explicitly unlock system user then switch
+            // to a secondary user. Hence, we don't want to send duplicate broadcasts for
+            // the system user here.
+            // TODO(b/242195409): this workaround shouldn't be necessary once we move
+            // the headless-user start logic to UserManager-land.
+            final boolean isBootingSystemUser = (currentUserId == UserHandle.USER_SYSTEM)
+                    && !UserManager.isHeadlessSystemUserMode();
+
+            if (isBootingSystemUser) {
                 t.traceBegin("startHomeOnAllDisplays");
                 mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
                 t.traceEnd();
@@ -8400,7 +8489,7 @@
             t.traceEnd();
 
 
-            if (bootingSystemUser) {
+            if (isBootingSystemUser) {
                 t.traceBegin("sendUserStartBroadcast");
                 final int callingUid = Binder.getCallingUid();
                 final int callingPid = Binder.getCallingPid();
@@ -8441,7 +8530,7 @@
             mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
             t.traceEnd();
 
-            if (bootingSystemUser) {
+            if (isBootingSystemUser) {
                 t.traceBegin("sendUserSwitchBroadcasts");
                 mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
                 t.traceEnd();
@@ -11062,11 +11151,13 @@
         final long pss;
         final long swapPss;
         final long mRss;
-        final int id;
+        final int id; // pid
+        final int userId;
         final boolean hasActivities;
         ArrayList<MemItem> subitems;
 
         MemItem(String label, String shortLabel, long pss, long swapPss, long rss, int id,
+                @UserIdInt int userId,
                 boolean hasActivities) {
             this.isProc = true;
             this.label = label;
@@ -11075,6 +11166,7 @@
             this.swapPss = swapPss;
             this.mRss = rss;
             this.id = id;
+            this.userId = userId;
             this.hasActivities = hasActivities;
         }
 
@@ -11086,6 +11178,7 @@
             this.swapPss = swapPss;
             this.mRss = rss;
             this.id = id;
+            this.userId = UserHandle.USER_SYSTEM;
             this.hasActivities = false;
         }
     }
@@ -11120,8 +11213,9 @@
                     pw.printf("%s%s: %-60s (%s in swap)\n", prefix, stringifyKBSize(mi.pss),
                             mi.label, stringifyKBSize(mi.swapPss));
                 } else {
-                    pw.printf("%s%s: %s\n", prefix, stringifyKBSize(dumpPss ? mi.pss : mi.mRss),
-                            mi.label);
+                    pw.printf("%s%s: %s%s\n", prefix, stringifyKBSize(dumpPss ? mi.pss : mi.mRss),
+                            mi.label,
+                            mi.userId != UserHandle.USER_SYSTEM ? " (user " + mi.userId + ")" : "");
                 }
             } else if (mi.isProc) {
                 pw.print("proc,"); pw.print(tag); pw.print(","); pw.print(mi.shortLabel);
@@ -11613,7 +11707,7 @@
                     ss[INDEX_TOTAL_MEMTRACK_GL] += memtrackGl;
                     MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
                             (hasActivities ? " / activities)" : ")"), r.processName, myTotalPss,
-                            myTotalSwapPss, myTotalRss, pid, hasActivities);
+                            myTotalSwapPss, myTotalRss, pid, r.userId, hasActivities);
                     procMems.add(pssItem);
                     procMemsMap.put(pid, pssItem);
 
@@ -11710,7 +11804,7 @@
 
                     MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
                             st.name, myTotalPss, info.getSummaryTotalSwapPss(), myTotalRss,
-                            st.pid, false);
+                            st.pid, UserHandle.getUserId(st.uid), false);
                     procMems.add(pssItem);
 
                     ss[INDEX_NATIVE_PSS] += info.nativePss;
@@ -12256,7 +12350,7 @@
                 ss[INDEX_TOTAL_RSS] += myTotalRss;
                 MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
                         (hasActivities ? " / activities)" : ")"), r.processName, myTotalPss,
-                        myTotalSwapPss, myTotalRss, pid, hasActivities);
+                        myTotalSwapPss, myTotalRss, pid, r.userId, hasActivities);
                 procMems.add(pssItem);
                 procMemsMap.put(pid, pssItem);
 
@@ -12344,7 +12438,7 @@
 
                     MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
                             st.name, myTotalPss, info.getSummaryTotalSwapPss(), myTotalRss,
-                            st.pid, false);
+                            st.pid, UserHandle.getUserId(st.uid), false);
                     procMems.add(pssItem);
 
                     ss[INDEX_NATIVE_PSS] += info.nativePss;
@@ -12648,7 +12742,7 @@
         for (BroadcastQueue queue : mBroadcastQueues) {
             queue.onApplicationCleanupLocked(app);
         }
-        updateProcessForegroundLocked(app, false, 0, false);
+        clearProcessForegroundLocked(app);
         mServices.killServicesLocked(app, allowRestart);
         mPhantomProcessList.onAppDied(pid);
 
@@ -12889,6 +12983,13 @@
     }
 
     @Override
+    public boolean shouldServiceTimeOut(ComponentName className, IBinder token) {
+        synchronized (this) {
+            return mServices.shouldServiceTimeOutLocked(className, token);
+        }
+    }
+
+    @Override
     public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
             boolean requireFull, String name, String callerPackage) {
         return mUserController.handleIncomingUser(callingPid, callingUid, userId, allowAll,
@@ -13071,7 +13172,7 @@
     // instantiated.  The backup agent will invoke backupAgentCreated() on the
     // activity manager to announce its creation.
     public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId,
-            @OperationType int operationType) {
+            @BackupDestination int backupDestination) {
         if (DEBUG_BACKUP) {
             Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode
                     + " targetUserId=" + targetUserId + " callingUid = " + Binder.getCallingUid()
@@ -13138,7 +13239,7 @@
                         + app.packageName + ": " + e);
             }
 
-            BackupRecord r = new BackupRecord(app, backupMode, targetUserId, operationType);
+            BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination);
             ComponentName hostingName =
                     (backupMode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL)
                             ? new ComponentName(app.packageName, app.backupAgentName)
@@ -13180,7 +13281,7 @@
                 if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "Agent proc already running: " + proc);
                 try {
                     thread.scheduleCreateBackupAgent(app, backupMode, targetUserId,
-                            operationType);
+                            backupDestination);
                 } catch (RemoteException e) {
                     // Will time out on the backup manager side
                 }
@@ -13452,9 +13553,19 @@
             // Don't enforce the flag check if we're EITHER registering for only protected
             // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
             // not be used generally, so we will be marking them as exported by default
-            final boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
+            boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
                     DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid)
                     && mConstants.mEnforceReceiverExportedFlagRequirement;
+            // STOPSHIP(b/259139792): Allow apps that are currently targeting U and in process of
+            // updating their receivers to be exempt from this requirement until their receivers
+            // are flagged.
+            if (requireExplicitFlagForDynamicReceivers) {
+                if ("com.google.android.apps.messaging".equals(callerPackage)) {
+                    // Note, a versionCode check for this package is not performed because it could
+                    // cause breakage with a subsequent update outside the system image.
+                    requireExplicitFlagForDynamicReceivers = false;
+                }
+            }
             if (!onlyProtectedBroadcasts) {
                 if (receiver == null && !explicitExportStateDefined) {
                     // sticky broadcast, no flag specified (flag isn't required)
@@ -13836,6 +13947,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(ComponentOptions.KEY_INTERACTIVE)) {
+                enforceCallingPermission(
+                        android.Manifest.permission.COMPONENT_OPTION_INTERACTIVE,
+                        "setInteractive");
+            }
+        }
+    }
+
     @GuardedBy("this")
     final int broadcastIntentLocked(ProcessRecord callerApp,
             String callerPackage, String callerFeatureId, Intent intent, String resolvedType,
@@ -13865,6 +13995,29 @@
             @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();
 
@@ -14416,6 +14569,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...
@@ -14446,6 +14600,7 @@
                         resolvedType, false /*defaultOnly*/, userId);
             }
         }
+        BroadcastQueue.traceEnd(cookie);
 
         final boolean replacePending =
                 (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
@@ -14630,6 +14785,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) {
@@ -14703,19 +14867,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 {
@@ -15720,12 +15873,18 @@
     }
 
     @GuardedBy("this")
+    final void clearProcessForegroundLocked(ProcessRecord proc) {
+        updateProcessForegroundLocked(proc, /* isForeground =*/ false,
+                /* fgsTypes =*/0, /* hasTypeNoneFgs =*/false, /* oomAdj= */ false);
+    }
+
+    @GuardedBy("this")
     final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
-            int fgServiceTypes, boolean oomAdj) {
+            int fgServiceTypes, boolean hasTypeNoneFgs, boolean oomAdj) {
         final ProcessServiceRecord psr = proc.mServices;
         final boolean foregroundStateChanged = isForeground != psr.hasForegroundServices();
         if (foregroundStateChanged
-                || psr.getForegroundServiceTypes() != fgServiceTypes) {
+                || !psr.areForegroundServiceTypesSame(fgServiceTypes, hasTypeNoneFgs)) {
             if (foregroundStateChanged) {
                 // Notify internal listeners.
                 for (int i = mForegroundServiceStateListeners.size() - 1; i >= 0; i--) {
@@ -15733,7 +15892,7 @@
                             proc.info.packageName, proc.info.uid, proc.getPid(), isForeground);
                 }
             }
-            psr.setHasForegroundServices(isForeground, fgServiceTypes);
+            psr.setHasForegroundServices(isForeground, fgServiceTypes, hasTypeNoneFgs);
             ArrayList<ProcessRecord> curProcs = mForegroundPackages.get(proc.info.packageName,
                     proc.info.uid);
             if (isForeground) {
@@ -16909,6 +17068,11 @@
             return mEnableModernQueue;
         }
 
+        @Override
+        public void enforceBroadcastOptionsPermissions(Bundle options, int callingUid) {
+            enforceBroadcastOptionPermissionsInternal(options, callingUid);
+        }
+
         /**
          * Returns package name by pid.
          */
@@ -17330,6 +17494,21 @@
         }
 
         @Override
+        public int broadcastIntentWithCallback(Intent intent,
+                IIntentReceiver resultTo,
+                String[] requiredPermissions,
+                int userId, int[] appIdAllowList,
+                @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+                @Nullable Bundle bOptions) {
+            // Sending broadcasts with a finish callback without the need for the broadcasts
+            // delivery to be serialized is only supported by modern queue. So, when modern
+            // queue is disabled, we continue to send broadcasts in a serialized fashion.
+            final boolean serialized = !isModernQueueEnabled();
+            return broadcastIntent(intent, resultTo, requiredPermissions, serialized, userId,
+                    appIdAllowList, filterExtrasForReceiver, bOptions);
+        }
+
+        @Override
         public ComponentName startServiceInPackage(int uid, Intent service, String resolvedType,
                 boolean fgRequired, String callingPackage, @Nullable String callingFeatureId,
                 int userId, boolean allowBackgroundActivityStarts,
@@ -17632,7 +17811,7 @@
                         return null;
                     }
 
-                    if ((app.mServices.getForegroundServiceTypes() & foregroundServicetype) != 0) {
+                    if ((app.mServices.containsAnyForegroundServiceTypes(foregroundServicetype))) {
                         return Boolean.TRUE;
                     }
                     return null;
@@ -17672,14 +17851,6 @@
         }
 
         @Override
-        public void stopForegroundServicesForChannel(String pkg, int userId,
-                String channelId) {
-            synchronized (ActivityManagerService.this) {
-                mServices.stopForegroundServicesForChannelLocked(pkg, userId, channelId);
-            }
-        }
-
-        @Override
         public void registerProcessObserver(IProcessObserver processObserver) {
             ActivityManagerService.this.registerProcessObserver(processObserver);
         }
@@ -17996,6 +18167,37 @@
             mUidObserverController.register(observer, which, cutpoint, callingPackage,
                     Binder.getCallingUid());
         }
+
+        @Override
+        public boolean startForegroundServiceDelegate(
+                @NonNull ForegroundServiceDelegationOptions options,
+                @Nullable ServiceConnection connection) {
+            synchronized (ActivityManagerService.this) {
+                return mServices.startForegroundServiceDelegateLocked(options, connection);
+            }
+        }
+
+        @Override
+        public void stopForegroundServiceDelegate(
+                @NonNull ForegroundServiceDelegationOptions options) {
+            synchronized (ActivityManagerService.this) {
+                mServices.stopForegroundServiceDelegateLocked(options);
+            }
+        }
+
+        @Override
+        public void stopForegroundServiceDelegate(@NonNull ServiceConnection connection) {
+            synchronized (ActivityManagerService.this) {
+                mServices.stopForegroundServiceDelegateLocked(connection);
+            }
+        }
+
+        @Override
+        public ArraySet<String> getClientPackages(String servicePackageName) {
+            synchronized (ActivityManagerService.this) {
+                return mServices.getClientPackagesLocked(servicePackageName);
+            }
+        }
     }
 
     long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
@@ -18127,9 +18329,11 @@
         }
     }
 
-    public void waitForBroadcastBarrier(@Nullable PrintWriter pw) {
+    public void waitForBroadcastBarrier(@Nullable PrintWriter pw, boolean flushBroadcastLoopers) {
         enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
-        BroadcastLoopers.waitForIdle(pw);
+        if (flushBroadcastLoopers) {
+            BroadcastLoopers.waitForBarrier(pw);
+        }
         for (BroadcastQueue queue : mBroadcastQueues) {
             queue.waitForBarrier(pw);
         }
@@ -18185,6 +18389,59 @@
     }
 
     /**
+     * Start/stop foreground service delegate on a app's process.
+     * This interface is intended for the shell command to use.
+     */
+    void setForegroundServiceDelegate(String packageName, int uid, boolean isStart,
+            @ForegroundServiceDelegationOptions.DelegationService int delegateService,
+            String clientInstanceName) {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != SYSTEM_UID && callingUid != ROOT_UID && callingUid != SHELL_UID) {
+            throw new SecurityException(
+                    "No permission to start/stop foreground service delegate");
+        }
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            boolean foundPid = false;
+            synchronized (this) {
+                ArrayList<ForegroundServiceDelegationOptions> delegates = new ArrayList<>();
+                synchronized (mPidsSelfLocked) {
+                    for (int i = 0; i < mPidsSelfLocked.size(); i++) {
+                        final ProcessRecord p = mPidsSelfLocked.valueAt(i);
+                        final IApplicationThread thread = p.getThread();
+                        if (p.uid == uid && thread != null) {
+                            foundPid = true;
+                            int pid = mPidsSelfLocked.keyAt(i);
+                            ForegroundServiceDelegationOptions options =
+                                    new ForegroundServiceDelegationOptions(pid, uid, packageName,
+                                            null /* clientAppThread */,
+                                            false /* isSticky */,
+                                            clientInstanceName, 0 /* foregroundServiceType */,
+                                            delegateService);
+                            delegates.add(options);
+                        }
+                    }
+                }
+                for (int i = delegates.size() - 1; i >= 0; i--) {
+                    final ForegroundServiceDelegationOptions options = delegates.get(i);
+                    if (isStart) {
+                        ((ActivityManagerLocal) mInternal).startForegroundServiceDelegate(options,
+                                null /* connection */);
+                    } else {
+                        ((ActivityManagerLocal) mInternal).stopForegroundServiceDelegate(options);
+                    }
+                }
+            }
+            if (!foundPid) {
+                Slog.e(TAG, "setForegroundServiceDelegate can not find process for packageName:"
+                        + packageName + " uid:" + uid);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    /**
      * Force the settings cache to be loaded
      */
     void refreshSettingsCache() {
@@ -18590,6 +18847,21 @@
             return new ProcessList();
         }
 
+        /**
+         * Returns the {@link BatteryStatsService} instance
+         */
+        public BatteryStatsService getBatteryStatsService() {
+            return new BatteryStatsService(mContext, SystemServiceManager.ensureSystemDir(),
+                BackgroundThread.get().getHandler());
+        }
+
+        /**
+         * Returns the {@link ActiveServices} instance
+         */
+        public ActiveServices getActiveServices(ActivityManagerService service) {
+            return new ActiveServices(service);
+        }
+
         private boolean ensureHasNetworkManagementInternal() {
             if (mNmi == null) {
                 mNmi = LocalServices.getService(NetworkManagementInternal.class);
@@ -18788,19 +19060,20 @@
         }
 
         @Override
-        public SyncNotedAppOp startProxyOperation(int code,
+        public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
                 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
                 @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
-                @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean,
-                        Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
+                @NonNull UndecFunction<IBinder, Integer, AttributionSource,
+                        Boolean, Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
+                        SyncNotedAppOp> superImpl) {
             if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(
                         attributionSource.getUid()), Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    return superImpl.apply(code, new AttributionSource(shellUid,
+                    return superImpl.apply(clientId, code, new AttributionSource(shellUid,
                             "com.android.shell", attributionSource.getAttributionTag(),
                             attributionSource.getToken(), attributionSource.getNext()),
                             startIfModeDefault, shouldCollectAsyncNotedOp, message,
@@ -18810,21 +19083,22 @@
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            return superImpl.apply(code, attributionSource, startIfModeDefault,
+            return superImpl.apply(clientId, code, attributionSource, startIfModeDefault,
                     shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
                     proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
         }
 
         @Override
-        public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
-                boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource,
-                        Boolean, Void> superImpl) {
+        public void finishProxyOperation(@NonNull IBinder clientId, int code,
+                @NonNull AttributionSource attributionSource, boolean skipProxyOperation,
+                @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean,
+                        Void> superImpl) {
             if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(
                         attributionSource.getUid()), Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    superImpl.apply(code, new AttributionSource(shellUid,
+                    superImpl.apply(clientId, code, new AttributionSource(shellUid,
                             "com.android.shell", attributionSource.getAttributionTag(),
                             attributionSource.getToken(), attributionSource.getNext()),
                             skipProxyOperation);
@@ -18832,7 +19106,7 @@
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            superImpl.apply(code, attributionSource, skipProxyOperation);
+            superImpl.apply(clientId, code, attributionSource, skipProxyOperation);
         }
 
         private boolean isTargetOp(int code) {
@@ -18934,6 +19208,10 @@
         return mOomAdjuster.mCachedAppOptimizer.useFreezer();
     }
 
+    public boolean isAppFreezerExemptInstPkg() {
+        return mOomAdjuster.mCachedAppOptimizer.freezerExemptInstPkg();
+    }
+
     /**
      * Resets the state of the {@link com.android.server.am.AppErrors} instance.
      * This is intended for testing within the CTS only and is protected by
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index e4f947d..be18c08 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -257,6 +257,8 @@
                     return runForceStop(pw);
                 case "stop-app":
                     return runStopApp(pw);
+                case "clear-recent-apps":
+                    return runClearRecentApps(pw);
                 case "fgs-notification-rate-limit":
                     return runFgsNotificationRateLimit(pw);
                 case "crash":
@@ -369,6 +371,8 @@
                     return runResetDropboxRateLimiter();
                 case "list-secondary-displays-for-starting-users":
                     return runListSecondaryDisplaysForStartingUsers(pw);
+                case "set-foreground-service-delegate":
+                    return runSetForegroundServiceDelegate(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -902,7 +906,8 @@
         }
     }
 
-    // TODO(b/239982558): might need to support --displayId as well
+    // NOTE: current profiles can only be started on default display (even on automotive builds with
+    // passenger displays), so there's no need to pass a display-id
     private int runProfile(PrintWriter pw) throws RemoteException {
         final PrintWriter err = getErrPrintWriter();
         String profileFile = null;
@@ -1237,6 +1242,11 @@
         return 0;
     }
 
+    int runClearRecentApps(PrintWriter pw) throws RemoteException {
+        mTaskInterface.removeAllVisibleRecentTasks();
+        return 0;
+    }
+
     int runFgsNotificationRateLimit(PrintWriter pw) throws RemoteException {
         final String toggleValue = getNextArgRequired();
         final boolean enable;
@@ -3121,7 +3131,17 @@
     }
 
     int runWaitForBroadcastBarrier(PrintWriter pw) throws RemoteException {
-        mInternal.waitForBroadcastBarrier(pw);
+        boolean flushBroadcastLoopers = false;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--flush-broadcast-loopers")) {
+                flushBroadcastLoopers = true;
+            } else {
+                getErrPrintWriter().println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+        mInternal.waitForBroadcastBarrier(pw, flushBroadcastLoopers);
         return 0;
     }
 
@@ -3592,6 +3612,45 @@
         return 0;
     }
 
+    int runSetForegroundServiceDelegate(PrintWriter pw) throws RemoteException {
+        int userId = UserHandle.USER_CURRENT;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+        final String packageName = getNextArgRequired();
+        final String action = getNextArgRequired();
+        boolean isStart = true;
+        if ("start".equals(action)) {
+            isStart = true;
+        } else if ("stop".equals(action)) {
+            isStart = false;
+        } else {
+            pw.println("Error: action is either start or stop");
+            return -1;
+        }
+
+        int uid = INVALID_UID;
+        try {
+            final PackageManager pm = mInternal.mContext.getPackageManager();
+            uid = pm.getPackageUidAsUser(packageName,
+                    PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            pw.println("Error: userId:" + userId + " package:" + packageName + " is not found");
+            return -1;
+        }
+        mInternal.setForegroundServiceDelegate(packageName, uid, isStart,
+                ForegroundServiceDelegationOptions.DELEGATION_SERVICE_SPECIAL_USE,
+                "FgsDelegate");
+        return 0;
+    }
+
     int runResetDropboxRateLimiter() throws RemoteException {
         mInternal.resetDropboxRateLimiter();
         return 0;
@@ -3968,6 +4027,8 @@
             pw.println("  list-secondary-displays-for-starting-users");
             pw.println("         Lists the id of displays that can be used to start users on "
                     + "background.");
+            pw.println("  set-foreground-service-delegate [--user <USER_ID>] <PACKAGE> start|stop");
+            pw.println("         Start/stop an app's foreground service delegate.");
             pw.println();
             Intent.printIntentArgsHelp(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/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
index 50515cd..1f98aba 100644
--- a/services/core/java/com/android/server/am/AppFGSTracker.java
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -16,10 +16,10 @@
 
 package com.android.server.am;
 
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPES_MAX_INDEX;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
-import static android.content.pm.ServiceInfo.NUM_OF_FOREGROUND_SERVICE_TYPES;
 import static android.content.pm.ServiceInfo.foregroundServiceTypeToLabel;
 import static android.os.PowerExemptionManager.REASON_DENIED;
 
@@ -645,7 +645,7 @@
 
         PackageDurations(int uid, String packageName,
                 MaxTrackingDurationConfig maxTrackingDurationConfig, AppFGSTracker tracker) {
-            super(uid, packageName, NUM_OF_FOREGROUND_SERVICE_TYPES + 1, TAG,
+            super(uid, packageName, FOREGROUND_SERVICE_TYPES_MAX_INDEX + 1, TAG,
                     maxTrackingDurationConfig);
             mEvents[DEFAULT_INDEX] = new LinkedList<>();
             mTracker = tracker;
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index b7de57f8..45b11e1 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -506,10 +506,14 @@
             }
             if (profile != null) {
                 long startTime = SystemClock.currentThreadTimeMillis();
-                // skip background PSS calculation of apps that are capturing
-                // camera imagery
-                final boolean usingCamera = mService.isCameraActiveForUid(profile.mApp.uid);
-                long pss = usingCamera ? 0 : Debug.getPss(pid, tmp, null);
+                // skip background PSS calculation under the following situations:
+                //  - app is capturing camera imagery
+                //  - app is frozen and we have already collected PSS once.
+                final boolean skipPSSCollection =
+                        (profile.mApp.mOptRecord != null
+                         && profile.mApp.mOptRecord.skipPSSCollectionBecauseFrozen())
+                        || mService.isCameraActiveForUid(profile.mApp.uid);
+                long pss = skipPSSCollection ? 0 : Debug.getPss(pid, tmp, null);
                 long endTime = SystemClock.currentThreadTimeMillis();
                 synchronized (mProfilerLock) {
                     if (pss != 0 && profile.getThread() != null
@@ -524,7 +528,7 @@
                         if (DEBUG_PSS) {
                             Slog.d(TAG_PSS, "Skipped pss collection of " + pid
                                     + ": " + (profile.getThread() == null ? "NO_THREAD " : "")
-                                    + (usingCamera ? "CAMERA " : "")
+                                    + (skipPSSCollection ? "SKIP_PSS_COLLECTION " : "")
                                     + (profile.getPid() != pid ? "PID_CHANGED " : "")
                                     + " initState=" + procState + " curState="
                                     + profile.getSetProcState() + " "
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index ba1c3b3..6abf6d8 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -2784,6 +2784,37 @@
      */
     @ReasonCode
     int getBackgroundRestrictionExemptionReason(int uid) {
+        @ReasonCode int reason = getPotentialSystemExemptionReason(uid);
+        if (reason != REASON_DENIED) {
+            return reason;
+        }
+        final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
+        if (packages != null) {
+            // Check each packages to see if any of them is in the "fixed" exemption cases.
+            for (String pkg : packages) {
+                reason = getPotentialSystemExemptionReason(uid, pkg);
+                if (reason != REASON_DENIED) {
+                    return reason;
+                }
+            }
+            // Loop the packages again, and check the user-configurable exemptions.
+            for (String pkg : packages) {
+                reason = getPotentialUserAllowedExemptionReason(uid, pkg);
+                if (reason != REASON_DENIED) {
+                    return reason;
+                }
+            }
+        }
+        return REASON_DENIED;
+    }
+
+    /**
+     * @param uid The uid to check.
+     * @return The potential exemption reason of the given uid. The caller must decide
+     * whether or not it should be exempted.
+     */
+    @ReasonCode
+    int getPotentialSystemExemptionReason(int uid) {
         if (UserHandle.isCore(uid)) {
             return REASON_SYSTEM_UID;
         }
@@ -2811,37 +2842,51 @@
         } else if (uidProcState <= PROCESS_STATE_PERSISTENT_UI) {
             return REASON_PROC_STATE_PERSISTENT_UI;
         }
-        final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
-        if (packages != null) {
-            final AppOpsManager appOpsManager = mInjector.getAppOpsManager();
-            final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
-            final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
-            // Check each packages to see if any of them is in the "fixed" exemption cases.
-            for (String pkg : packages) {
-                if (isSystemModule(pkg)) {
-                    return REASON_SYSTEM_MODULE;
-                } else if (isCarrierApp(pkg)) {
-                    return REASON_CARRIER_PRIVILEGED_APP;
-                } else if (isExemptedFromSysConfig(pkg)) {
-                    return REASON_SYSTEM_ALLOW_LISTED;
-                } else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) {
-                    return REASON_SYSTEM_ALLOW_LISTED;
-                } else if (pm.isPackageStateProtected(pkg, userId)) {
-                    return REASON_DPO_PROTECTED_APP;
-                } else if (appStandbyInternal.isActiveDeviceAdmin(pkg, userId)) {
-                    return REASON_ACTIVE_DEVICE_ADMIN;
-                }
-            }
-            // Loop the packages again, and check the user-configurable exemptions.
-            for (String pkg : packages) {
-                if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
-                        uid, pkg) == AppOpsManager.MODE_ALLOWED) {
-                    return REASON_OP_ACTIVATE_VPN;
-                } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
-                        uid, pkg) == AppOpsManager.MODE_ALLOWED) {
-                    return REASON_OP_ACTIVATE_PLATFORM_VPN;
-                }
-            }
+        return REASON_DENIED;
+    }
+
+    /**
+     * @param uid The uid to check.
+     * @param pkgName The package name to check.
+     * @return The potential system-fixed exemption reason of the given uid/package. The caller
+     * must decide whether or not it should be exempted.
+     */
+    @ReasonCode
+    int getPotentialSystemExemptionReason(int uid, String pkg) {
+        final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
+        final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+        final int userId = UserHandle.getUserId(uid);
+        if (isSystemModule(pkg)) {
+            return REASON_SYSTEM_MODULE;
+        } else if (isCarrierApp(pkg)) {
+            return REASON_CARRIER_PRIVILEGED_APP;
+        } else if (isExemptedFromSysConfig(pkg)) {
+            return REASON_SYSTEM_ALLOW_LISTED;
+        } else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) {
+            return REASON_SYSTEM_ALLOW_LISTED;
+        } else if (pm.isPackageStateProtected(pkg, userId)) {
+            return REASON_DPO_PROTECTED_APP;
+        } else if (appStandbyInternal.isActiveDeviceAdmin(pkg, userId)) {
+            return REASON_ACTIVE_DEVICE_ADMIN;
+        }
+        return REASON_DENIED;
+    }
+
+    /**
+     * @param uid The uid to check.
+     * @param pkgName The package name to check.
+     * @return The potential user-allowed exemption reason of the given uid/package. The caller
+     * must decide whether or not it should be exempted.
+     */
+    @ReasonCode
+    int getPotentialUserAllowedExemptionReason(int uid, String pkg) {
+        final AppOpsManager appOpsManager = mInjector.getAppOpsManager();
+        if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
+                uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+            return REASON_OP_ACTIVATE_VPN;
+        } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
+                uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+            return REASON_OP_ACTIVATE_PLATFORM_VPN;
         }
         if (isRoleHeldByUid(RoleManager.ROLE_DIALER, uid)) {
             return REASON_ROLE_DIALER;
@@ -2852,6 +2897,7 @@
         if (isOnDeviceIdleAllowlist(uid)) {
             return REASON_ALLOWLISTED_PACKAGE;
         }
+        final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
         if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) {
             return REASON_COMPANION_DEVICE_MANAGER;
         }
diff --git a/services/core/java/com/android/server/am/BackupRecord.java b/services/core/java/com/android/server/am/BackupRecord.java
index d419856..0b056d7 100644
--- a/services/core/java/com/android/server/am/BackupRecord.java
+++ b/services/core/java/com/android/server/am/BackupRecord.java
@@ -16,8 +16,7 @@
 
 package com.android.server.am;
 
-import android.app.backup.BackupManager;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.content.pm.ApplicationInfo;
 
 /** @hide */
@@ -32,16 +31,16 @@
     final ApplicationInfo appInfo;         // information about BackupAgent's app
     final int userId;                      // user for which backup is performed
     final int backupMode;                  // full backup / incremental / restore
-    @OperationType  final int operationType; // see BackupManager#OperationType
+    @BackupDestination final int backupDestination; // see BackupAnnotations#BackupDestination
     ProcessRecord app;                     // where this agent is running or null
 
     // ----- Implementation -----
 
-    BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _operationType) {
+    BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination) {
         appInfo = _appInfo;
         backupMode = _backupMode;
         userId = _userId;
-        operationType = _operationType;
+        backupDestination = _backupDestination;
     }
 
     public String toString() {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 75d1f68..d1bcf87 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -731,6 +731,8 @@
     @Override
     @EnforcePermission(BATTERY_STATS)
     public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
+        super.getBatteryUsageStats_enforcePermission();
+
         awaitCompletion();
 
         if (mBatteryUsageStatsProvider.shouldUpdateStats(queries,
@@ -846,6 +848,8 @@
     @Override
     @EnforcePermission(BATTERY_STATS)
     public long computeBatteryScreenOffRealtimeMs() {
+        super.computeBatteryScreenOffRealtimeMs_enforcePermission();
+
         synchronized (mStats) {
             final long curTimeUs = SystemClock.elapsedRealtimeNanos() / 1000;
             long timeUs = mStats.computeBatteryScreenOffRealtime(curTimeUs,
@@ -857,6 +861,8 @@
     @Override
     @EnforcePermission(BATTERY_STATS)
     public long getScreenOffDischargeMah() {
+        super.getScreenOffDischargeMah_enforcePermission();
+
         synchronized (mStats) {
             long dischargeUah = mStats.getUahDischargeScreenOff(BatteryStats.STATS_SINCE_CHARGED);
             return dischargeUah / 1000;
@@ -866,6 +872,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteEvent(final int code, final String name, final int uid) {
+        super.noteEvent_enforcePermission();
+
         if (name == null) {
             // TODO(b/194733136): Replace with an IllegalArgumentException throw.
             Slog.wtfStack(TAG, "noteEvent called with null name. code = " + code);
@@ -886,6 +894,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteSyncStart(final String name, final int uid) {
+        super.noteSyncStart_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -902,6 +912,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteSyncFinish(final String name, final int uid) {
+        super.noteSyncFinish_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -919,6 +931,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteJobStart(final String name, final int uid) {
+        super.noteJobStart_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -934,6 +948,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteJobFinish(final String name, final int uid, final int stopReason) {
+        super.noteJobFinish_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1007,6 +1023,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStartWakelock(final int uid, final int pid, final String name,
             final String historyName, final int type, final boolean unimportantForLogging) {
+        super.noteStartWakelock_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1023,6 +1041,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStopWakelock(final int uid, final int pid, final String name,
             final String historyName, final int type) {
+        super.noteStopWakelock_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1039,6 +1059,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStartWakelockFromSource(final WorkSource ws, final int pid, final String name,
             final String historyName, final int type, final boolean unimportantForLogging) {
+        super.noteStartWakelockFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1058,6 +1080,8 @@
             final String historyName, final int type, final WorkSource newWs, final int newPid,
             final String newName, final String newHistoryName, final int newType,
             final boolean newUnimportantForLogging) {
+        super.noteChangeWakelockFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         final WorkSource localNewWs = newWs != null ? new WorkSource(newWs) : null;
         synchronized (mLock) {
@@ -1077,6 +1101,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStopWakelockFromSource(final WorkSource ws, final int pid, final String name,
             final String historyName, final int type) {
+        super.noteStopWakelockFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1094,6 +1120,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteLongPartialWakelockStart(final String name, final String historyName,
             final int uid) {
+        super.noteLongPartialWakelockStart_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1110,6 +1138,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteLongPartialWakelockStartFromSource(final String name, final String historyName,
             final WorkSource workSource) {
+        super.noteLongPartialWakelockStartFromSource_enforcePermission();
+
         final WorkSource localWs = workSource != null ? new WorkSource(workSource) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1127,6 +1157,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteLongPartialWakelockFinish(final String name, final String historyName,
             final int uid) {
+        super.noteLongPartialWakelockFinish_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1143,6 +1175,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteLongPartialWakelockFinishFromSource(final String name, final String historyName,
             final WorkSource workSource) {
+        super.noteLongPartialWakelockFinishFromSource_enforcePermission();
+
         final WorkSource localWs = workSource != null ? new WorkSource(workSource) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1159,6 +1193,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStartSensor(final int uid, final int sensor) {
+        super.noteStartSensor_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1175,6 +1211,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStopSensor(final int uid, final int sensor) {
+        super.noteStopSensor_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1191,6 +1229,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteVibratorOn(final int uid, final long durationMillis) {
+        super.noteVibratorOn_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1205,6 +1245,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteVibratorOff(final int uid) {
+        super.noteVibratorOff_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1219,6 +1261,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteGpsChanged(final WorkSource oldWs, final WorkSource newWs) {
+        super.noteGpsChanged_enforcePermission();
+
         final WorkSource localOldWs = oldWs != null ? new WorkSource(oldWs) : null;
         final WorkSource localNewWs = newWs != null ? new WorkSource(newWs) : null;
         synchronized (mLock) {
@@ -1235,6 +1279,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteGpsSignalQuality(final int signalLevel) {
+        super.noteGpsSignalQuality_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1249,6 +1295,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteScreenState(final int state) {
+        super.noteScreenState_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1267,6 +1315,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteScreenBrightness(final int brightness) {
+        super.noteScreenBrightness_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1282,6 +1332,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteUserActivity(final int uid, final int event) {
+        super.noteUserActivity_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1296,6 +1348,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWakeUp(final String reason, final int reasonUid) {
+        super.noteWakeUp_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1310,6 +1364,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteInteractive(final boolean interactive) {
+        super.noteInteractive_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             mHandler.post(() -> {
@@ -1323,6 +1379,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteConnectivityChanged(final int type, final String extra) {
+        super.noteConnectivityChanged_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1338,6 +1396,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteMobileRadioPowerState(final int powerState, final long timestampNs,
             final int uid) {
+        super.noteMobileRadioPowerState_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1364,6 +1424,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void notePhoneOn() {
+        super.notePhoneOn_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1378,6 +1440,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void notePhoneOff() {
+        super.notePhoneOff_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1392,6 +1456,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void notePhoneSignalStrength(final SignalStrength signalStrength) {
+        super.notePhoneSignalStrength_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1407,6 +1473,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void notePhoneDataConnectionState(final int dataType, final boolean hasData,
             final int serviceType, final int nrFrequency) {
+        super.notePhoneDataConnectionState_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1422,6 +1490,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void notePhoneState(final int state) {
+        super.notePhoneState_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1437,6 +1507,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiOn() {
+        super.noteWifiOn_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1453,6 +1525,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiOff() {
+        super.noteWifiOff_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1469,6 +1543,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStartAudio(final int uid) {
+        super.noteStartAudio_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1485,6 +1561,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStopAudio(final int uid) {
+        super.noteStopAudio_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1501,6 +1579,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStartVideo(final int uid) {
+        super.noteStartVideo_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1517,6 +1597,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStopVideo(final int uid) {
+        super.noteStopVideo_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1533,6 +1615,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteResetAudio() {
+        super.noteResetAudio_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1549,6 +1633,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteResetVideo() {
+        super.noteResetVideo_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1565,6 +1651,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteFlashlightOn(final int uid) {
+        super.noteFlashlightOn_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1581,6 +1669,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteFlashlightOff(final int uid) {
+        super.noteFlashlightOff_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1597,6 +1687,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStartCamera(final int uid) {
+        super.noteStartCamera_enforcePermission();
+
         if (DBG) Slog.d(TAG, "begin noteStartCamera");
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1615,6 +1707,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStopCamera(final int uid) {
+        super.noteStopCamera_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1631,6 +1725,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteResetCamera() {
+        super.noteResetCamera_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1647,6 +1743,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteResetFlashlight() {
+        super.noteResetFlashlight_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1663,6 +1761,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiRadioPowerState(final int powerState, final long tsNanos, final int uid) {
+        super.noteWifiRadioPowerState_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1694,6 +1794,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiRunning(final WorkSource ws) {
+        super.noteWifiRunning_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1712,6 +1814,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiRunningChanged(final WorkSource oldWs, final WorkSource newWs) {
+        super.noteWifiRunningChanged_enforcePermission();
+
         final WorkSource localOldWs = oldWs != null ? new WorkSource(oldWs) : null;
         final WorkSource localNewWs = newWs != null ? new WorkSource(newWs) : null;
         synchronized (mLock) {
@@ -1733,6 +1837,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiStopped(final WorkSource ws) {
+        super.noteWifiStopped_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : ws;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1750,6 +1856,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiState(final int wifiState, final String accessPoint) {
+        super.noteWifiState_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             mHandler.post(() -> {
@@ -1763,6 +1871,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiSupplicantStateChanged(final int supplState, final boolean failedAuth) {
+        super.noteWifiSupplicantStateChanged_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1778,6 +1888,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiRssiChanged(final int newRssi) {
+        super.noteWifiRssiChanged_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1792,6 +1904,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteFullWifiLockAcquired(final int uid) {
+        super.noteFullWifiLockAcquired_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1806,6 +1920,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteFullWifiLockReleased(final int uid) {
+        super.noteFullWifiLockReleased_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1820,6 +1936,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiScanStarted(final int uid) {
+        super.noteWifiScanStarted_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1834,6 +1952,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiScanStopped(final int uid) {
+        super.noteWifiScanStopped_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1848,6 +1968,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiMulticastEnabled(final int uid) {
+        super.noteWifiMulticastEnabled_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1862,6 +1984,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiMulticastDisabled(final int uid) {
+        super.noteWifiMulticastDisabled_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1876,6 +2000,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteFullWifiLockAcquiredFromSource(final WorkSource ws) {
+        super.noteFullWifiLockAcquiredFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1892,6 +2018,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteFullWifiLockReleasedFromSource(final WorkSource ws) {
+        super.noteFullWifiLockReleasedFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1908,6 +2036,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiScanStartedFromSource(final WorkSource ws) {
+        super.noteWifiScanStartedFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1923,6 +2053,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiScanStoppedFromSource(final WorkSource ws) {
+        super.noteWifiScanStoppedFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1938,6 +2070,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiBatchedScanStartedFromSource(final WorkSource ws, final int csph) {
+        super.noteWifiBatchedScanStartedFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1954,6 +2088,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiBatchedScanStoppedFromSource(final WorkSource ws) {
+        super.noteWifiBatchedScanStoppedFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1970,6 +2106,8 @@
     @Override
     @EnforcePermission(anyOf = {NETWORK_STACK, PERMISSION_MAINLINE_NETWORK_STACK})
     public void noteNetworkInterfaceForTransports(final String iface, int[] transportTypes) {
+        super.noteNetworkInterfaceForTransports_enforcePermission();
+
         synchronized (mLock) {
             mHandler.post(() -> {
                 mStats.noteNetworkInterfaceForTransports(iface, transportTypes);
@@ -1983,6 +2121,8 @@
         // During device boot, qtaguid isn't enabled until after the inital
         // loading of battery stats. Now that they're enabled, take our initial
         // snapshot for future delta calculation.
+        super.noteNetworkStatsEnabled_enforcePermission();
+
         synchronized (mLock) {
             // Still schedule it on the handler to make sure we have existing pending works done
             mHandler.post(() -> {
@@ -1996,6 +2136,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteDeviceIdleMode(final int mode, final String activeReason, final int activeUid) {
+        super.noteDeviceIdleMode_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -2039,6 +2181,8 @@
     @Override
     @EnforcePermission(BLUETOOTH_CONNECT)
     public void noteBluetoothOn(int uid, int reason, String packageName) {
+        super.noteBluetoothOn_enforcePermission();
+
         FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
                 Binder.getCallingUid(), null,
                 FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED,
@@ -2051,6 +2195,8 @@
     @Override
     @EnforcePermission(BLUETOOTH_CONNECT)
     public void noteBluetoothOff(int uid, int reason, String packageName) {
+        super.noteBluetoothOff_enforcePermission();
+
         FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
                 Binder.getCallingUid(), null,
                 FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED,
@@ -2060,6 +2206,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteBleScanStarted(final WorkSource ws, final boolean isUnoptimized) {
+        super.noteBleScanStarted_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -2076,6 +2224,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteBleScanStopped(final WorkSource ws, final boolean isUnoptimized) {
+        super.noteBleScanStopped_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -2092,6 +2242,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteBleScanReset() {
+        super.noteBleScanReset_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -2106,6 +2258,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteBleScanResults(final WorkSource ws, final int numNewResults) {
+        super.noteBleScanResults_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -2122,6 +2276,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiControllerActivity(final WifiActivityEnergyInfo info) {
+        super.noteWifiControllerActivity_enforcePermission();
+
         if (info == null || !info.isValid()) {
             Slog.e(TAG, "invalid wifi data given: " + info);
             return;
@@ -2142,6 +2298,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteBluetoothControllerActivity(final BluetoothActivityEnergyInfo info) {
+        super.noteBluetoothControllerActivity_enforcePermission();
+
         if (info == null || !info.isValid()) {
             Slog.e(TAG, "invalid bluetooth data given: " + info);
             return;
@@ -2162,6 +2320,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteModemControllerActivity(final ModemActivityInfo info) {
+        super.noteModemControllerActivity_enforcePermission();
+
         if (info == null) {
             Slog.e(TAG, "invalid modem data given: " + info);
             return;
@@ -2188,6 +2348,8 @@
     public void setBatteryState(final int status, final int health, final int plugType,
             final int level, final int temp, final int volt, final int chargeUAh,
             final int chargeFullUAh, final long chargeTimeToFullSeconds) {
+        super.setBatteryState_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -2230,12 +2392,16 @@
     @Override
     @EnforcePermission(BATTERY_STATS)
     public long getAwakeTimeBattery() {
+        super.getAwakeTimeBattery_enforcePermission();
+
         return mStats.getAwakeTimeBattery();
     }
 
     @Override
     @EnforcePermission(BATTERY_STATS)
     public long getAwakeTimePlugged() {
+        super.getAwakeTimePlugged_enforcePermission();
+
         return mStats.getAwakeTimePlugged();
     }
 
@@ -2738,6 +2904,8 @@
     @EnforcePermission(anyOf = {UPDATE_DEVICE_STATS, BATTERY_STATS})
     public CellularBatteryStats getCellularBatteryStats() {
         // Wait for the completion of pending works if there is any
+        super.getCellularBatteryStats_enforcePermission();
+
         awaitCompletion();
         synchronized (mStats) {
             return mStats.getCellularBatteryStats();
@@ -2752,6 +2920,8 @@
     @EnforcePermission(anyOf = {UPDATE_DEVICE_STATS, BATTERY_STATS})
     public WifiBatteryStats getWifiBatteryStats() {
         // Wait for the completion of pending works if there is any
+        super.getWifiBatteryStats_enforcePermission();
+
         awaitCompletion();
         synchronized (mStats) {
             return mStats.getWifiBatteryStats();
@@ -2766,6 +2936,8 @@
     @EnforcePermission(BATTERY_STATS)
     public GpsBatteryStats getGpsBatteryStats() {
         // Wait for the completion of pending works if there is any
+        super.getGpsBatteryStats_enforcePermission();
+
         awaitCompletion();
         synchronized (mStats) {
             return mStats.getGpsBatteryStats();
@@ -2780,6 +2952,8 @@
     @EnforcePermission(BATTERY_STATS)
     public WakeLockStats getWakeLockStats() {
         // Wait for the completion of pending works if there is any
+        super.getWakeLockStats_enforcePermission();
+
         awaitCompletion();
         synchronized (mStats) {
             return mStats.getWakeLockStats();
@@ -2794,6 +2968,8 @@
     @EnforcePermission(BATTERY_STATS)
     public BluetoothBatteryStats getBluetoothBatteryStats() {
         // Wait for the completion of pending works if there is any
+        super.getBluetoothBatteryStats_enforcePermission();
+
         awaitCompletion();
         synchronized (mStats) {
             return mStats.getBluetoothBatteryStats();
@@ -2901,6 +3077,8 @@
      */
     @EnforcePermission(POWER_SAVER)
     public boolean setChargingStateUpdateDelayMillis(int delayMillis) {
+        super.setChargingStateUpdateDelayMillis_enforcePermission();
+
         final long ident = Binder.clearCallingIdentity();
 
         try {
@@ -3051,6 +3229,8 @@
     @Override
     @EnforcePermission(DEVICE_POWER)
     public void setChargerAcOnline(boolean online, boolean forceUpdate) {
+        super.setChargerAcOnline_enforcePermission();
+
         mBatteryManagerInternal.setChargerAcOnline(online, forceUpdate);
     }
 
@@ -3060,6 +3240,8 @@
     @Override
     @EnforcePermission(DEVICE_POWER)
     public void setBatteryLevel(int level, boolean forceUpdate) {
+        super.setBatteryLevel_enforcePermission();
+
         mBatteryManagerInternal.setBatteryLevel(level, forceUpdate);
     }
 
@@ -3069,6 +3251,8 @@
     @Override
     @EnforcePermission(DEVICE_POWER)
     public void unplugBattery(boolean forceUpdate) {
+        super.unplugBattery_enforcePermission();
+
         mBatteryManagerInternal.unplugBattery(forceUpdate);
     }
 
@@ -3078,6 +3262,8 @@
     @Override
     @EnforcePermission(DEVICE_POWER)
     public void resetBattery(boolean forceUpdate) {
+        super.resetBattery_enforcePermission();
+
         mBatteryManagerInternal.resetBattery(forceUpdate);
     }
 
@@ -3087,6 +3273,8 @@
     @Override
     @EnforcePermission(DEVICE_POWER)
     public void suspendBatteryInput() {
+        super.suspendBatteryInput_enforcePermission();
+
         mBatteryManagerInternal.suspendBatteryInput();
     }
 }
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 417a0e5..f5d1c10 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -136,12 +136,42 @@
     private static final boolean DEFAULT_MODERN_QUEUE_ENABLED = true;
 
     /**
-     * For {@link BroadcastQueueModernImpl}: Maximum number of process queues to
-     * dispatch broadcasts to simultaneously.
+     * For {@link BroadcastQueueModernImpl}: Maximum dispatch parallelism
+     * that we'll tolerate for ordinary broadcast dispatch.
      */
     public int MAX_RUNNING_PROCESS_QUEUES = DEFAULT_MAX_RUNNING_PROCESS_QUEUES;
     private static final String KEY_MAX_RUNNING_PROCESS_QUEUES = "bcast_max_running_process_queues";
-    private static final int DEFAULT_MAX_RUNNING_PROCESS_QUEUES = 4;
+    private static final int DEFAULT_MAX_RUNNING_PROCESS_QUEUES =
+            ActivityManager.isLowRamDeviceStatic() ? 2 : 4;
+
+    /**
+     * For {@link BroadcastQueueModernImpl}: Additional running process queue parallelism beyond
+     * {@link #MAX_RUNNING_PROCESS_QUEUES} for dispatch of "urgent" broadcasts.
+     */
+    public int EXTRA_RUNNING_URGENT_PROCESS_QUEUES = DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES;
+    private static final String KEY_EXTRA_RUNNING_URGENT_PROCESS_QUEUES =
+            "bcast_extra_running_urgent_process_queues";
+    private static final int DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = 1;
+
+    /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of consecutive urgent
+     * broadcast dispatches allowed before letting broadcasts in lower priority queue
+     * to be scheduled in order to avoid starvation.
+     */
+    public int MAX_CONSECUTIVE_URGENT_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES;
+    private static final String KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES =
+            "bcast_max_consecutive_urgent_dispatches";
+    private static final int DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES = 3;
+
+    /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of consecutive normal
+     * broadcast dispatches allowed before letting broadcasts in lower priority queue
+     * to be scheduled in order to avoid starvation.
+     */
+    public int MAX_CONSECUTIVE_NORMAL_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES;
+    private static final String KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES =
+            "bcast_max_consecutive_normal_dispatches";
+    private static final int DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES = 10;
 
     /**
      * For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts
@@ -150,7 +180,8 @@
      */
     public int MAX_RUNNING_ACTIVE_BROADCASTS = DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS;
     private static final String KEY_MAX_RUNNING_ACTIVE_BROADCASTS = "bcast_max_running_active_broadcasts";
-    private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS = 16;
+    private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS =
+            ActivityManager.isLowRamDeviceStatic() ? 8 : 16;
 
     /**
      * For {@link BroadcastQueueModernImpl}: Maximum number of pending
@@ -159,7 +190,8 @@
      */
     public int MAX_PENDING_BROADCASTS = DEFAULT_MAX_PENDING_BROADCASTS;
     private static final String KEY_MAX_PENDING_BROADCASTS = "bcast_max_pending_broadcasts";
-    private static final int DEFAULT_MAX_PENDING_BROADCASTS = 256;
+    private static final int DEFAULT_MAX_PENDING_BROADCASTS =
+            ActivityManager.isLowRamDeviceStatic() ? 128 : 256;
 
     /**
      * For {@link BroadcastQueueModernImpl}: Delay to apply to normal
@@ -167,7 +199,7 @@
      */
     public long DELAY_NORMAL_MILLIS = DEFAULT_DELAY_NORMAL_MILLIS;
     private static final String KEY_DELAY_NORMAL_MILLIS = "bcast_delay_normal_millis";
-    private static final long DEFAULT_DELAY_NORMAL_MILLIS = 0;
+    private static final long DEFAULT_DELAY_NORMAL_MILLIS = +500;
 
     /**
      * For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts
@@ -175,7 +207,7 @@
      */
     public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS;
     private static final String KEY_DELAY_CACHED_MILLIS = "bcast_delay_cached_millis";
-    private static final long DEFAULT_DELAY_CACHED_MILLIS = 0;
+    private static final long DEFAULT_DELAY_CACHED_MILLIS = +120_000;
 
     /**
      * For {@link BroadcastQueueModernImpl}: Delay to apply to urgent
@@ -247,6 +279,10 @@
         updateDeviceConfigConstants();
     }
 
+    public int getMaxRunningQueues() {
+        return MAX_RUNNING_PROCESS_QUEUES + EXTRA_RUNNING_URGENT_PROCESS_QUEUES;
+    }
+
     private void updateSettingsConstants() {
         synchronized (this) {
             try {
@@ -314,6 +350,15 @@
                     DEFAULT_MODERN_QUEUE_ENABLED);
             MAX_RUNNING_PROCESS_QUEUES = getDeviceConfigInt(KEY_MAX_RUNNING_PROCESS_QUEUES,
                     DEFAULT_MAX_RUNNING_PROCESS_QUEUES);
+            EXTRA_RUNNING_URGENT_PROCESS_QUEUES = getDeviceConfigInt(
+                    KEY_EXTRA_RUNNING_URGENT_PROCESS_QUEUES,
+                    DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES);
+            MAX_CONSECUTIVE_URGENT_DISPATCHES = getDeviceConfigInt(
+                    KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
+                    DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES);
+            MAX_CONSECUTIVE_NORMAL_DISPATCHES = getDeviceConfigInt(
+                    KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+                    DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES);
             MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS,
                     DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
             MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS,
@@ -369,6 +414,10 @@
                     TimeUtils.formatDuration(DELAY_URGENT_MILLIS)).println();
             pw.print(KEY_MAX_HISTORY_COMPLETE_SIZE, MAX_HISTORY_COMPLETE_SIZE).println();
             pw.print(KEY_MAX_HISTORY_SUMMARY_SIZE, MAX_HISTORY_SUMMARY_SIZE).println();
+            pw.print(KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
+                    MAX_CONSECUTIVE_URGENT_DISPATCHES).println();
+            pw.print(KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+                    MAX_CONSECUTIVE_NORMAL_DISPATCHES).println();
             pw.decreaseIndent();
             pw.println();
         }
diff --git a/services/core/java/com/android/server/am/BroadcastLoopers.java b/services/core/java/com/android/server/am/BroadcastLoopers.java
index bebb484..a5535cb 100644
--- a/services/core/java/com/android/server/am/BroadcastLoopers.java
+++ b/services/core/java/com/android/server/am/BroadcastLoopers.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
@@ -25,9 +26,12 @@
 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;
+import java.util.function.BiConsumer;
 
 /**
  * Collection of {@link Looper} that are known to be used for broadcast dispatch
@@ -37,6 +41,7 @@
 public class BroadcastLoopers {
     private static final String TAG = "BroadcastLoopers";
 
+    @GuardedBy("sLoopers")
     private static final ArraySet<Looper> sLoopers = new ArraySet<>();
 
     /**
@@ -70,19 +75,44 @@
      * still in the future are ignored for the purposes of the idle test.
      */
     public static void waitForIdle(@Nullable PrintWriter pw) {
+        waitForCondition(pw, (looper, latch) -> {
+            final MessageQueue queue = looper.getQueue();
+            queue.addIdleHandler(() -> {
+                latch.countDown();
+                return false;
+            });
+        });
+    }
+
+    /**
+     * Wait for all registered {@link Looper} instances to handle currently waiting messages.
+     * Note that {@link Message#when} still in the future are ignored for the purposes
+     * of the idle test.
+     */
+    public static void waitForBarrier(@Nullable PrintWriter pw) {
+        waitForCondition(pw, (looper, latch) -> {
+            (new Handler(looper)).post(() -> {
+                latch.countDown();
+            });
+        });
+    }
+
+    /**
+     * Wait for all registered {@link Looper} instances to meet a certain condition.
+     */
+    private static void waitForCondition(@Nullable PrintWriter pw,
+            @NonNull BiConsumer<Looper, CountDownLatch> condition) {
         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();
+                final Looper looper = sLoopers.valueAt(i);
+                final MessageQueue queue = looper.getQueue();
                 if (queue.isIdle()) {
                     latch.countDown();
                 } else {
-                    queue.addIdleHandler(() -> {
-                        latch.countDown();
-                        return false;
-                    });
+                    condition.accept(looper, latch);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index f7d24e9..15d2fa3 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,19 @@
     private boolean mActiveViaColdStart;
 
     /**
-     * Count of {@link #mPending} and {@link #mPendingUrgent} broadcasts of
-     * these various flavors.
+     * Number of consecutive urgent broadcasts that have been dispatched
+     * since the last non-urgent dispatch.
+     */
+    private int mActiveCountConsecutiveUrgent;
+
+    /**
+     * Number of consecutive normal broadcasts that have been dispatched
+     * since the last offload dispatch.
+     */
+    private int mActiveCountConsecutiveNormal;
+
+    /**
+     * Count of pending broadcasts of these various flavors.
      */
     private int mCountForeground;
     private int mCountOrdered;
@@ -160,12 +170,15 @@
     private int mCountInstrumented;
     private int mCountManifest;
 
+    private boolean mPrioritizeEarliest;
+
     private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE;
     private @Reason int mRunnableAtReason = REASON_EMPTY;
     private boolean mRunnableAtInvalidated;
 
     private boolean mProcessCached;
     private boolean mProcessInstrumented;
+    private boolean mProcessPersistent;
 
     private String mCachedToString;
     private String mCachedToShortString;
@@ -177,6 +190,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 +213,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 +228,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 +246,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 +261,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 +297,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;
     }
 
@@ -317,8 +338,10 @@
         this.app = app;
         if (app != null) {
             setProcessInstrumented(app.getActiveInstrumentation() != null);
+            setProcessPersistent(app.isPersistent());
         } else {
             setProcessInstrumented(false);
+            setProcessPersistent(false);
         }
     }
 
@@ -346,6 +369,17 @@
     }
 
     /**
+     * Update if this process is in the "persistent" state, which signals broadcast dispatch should
+     * bypass all pauses or delays to prevent the system from becoming out of sync with itself.
+     */
+    public void setProcessPersistent(boolean persistent) {
+        if (mProcessPersistent != persistent) {
+            mProcessPersistent = persistent;
+            invalidateRunnableAt();
+        }
+    }
+
+    /**
      * Return if we know of an actively running "warm" process for this queue.
      */
     public boolean isProcessWarm() {
@@ -391,7 +425,6 @@
         final SomeArgs next = removeNextBroadcast();
         mActive = (BroadcastRecord) next.arg1;
         mActiveIndex = next.argi1;
-        mActiveBlockedUntilTerminalCount = next.argi2;
         mActiveCountSinceIdle++;
         mActiveViaColdStart = false;
         next.recycle();
@@ -404,7 +437,6 @@
     public void makeActiveIdle() {
         mActive = null;
         mActiveIndex = 0;
-        mActiveBlockedUntilTerminalCount = -1;
         mActiveCountSinceIdle = 0;
         mActiveViaColdStart = false;
         invalidateRunnableAt();
@@ -516,7 +548,7 @@
     }
 
     public boolean isEmpty() {
-        return mPending.isEmpty() && mPendingUrgent.isEmpty();
+        return mPending.isEmpty() && mPendingUrgent.isEmpty() && mPendingOffload.isEmpty();
     }
 
     public boolean isActive() {
@@ -527,18 +559,75 @@
      * Will thrown an exception if there are no pending broadcasts; relies on
      * {@link #isEmpty()} being false.
      */
-    SomeArgs removeNextBroadcast() {
-        ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
-        return queue.removeFirst();
+    private @Nullable SomeArgs removeNextBroadcast() {
+        final ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
+        if (queue == mPendingUrgent) {
+            mActiveCountConsecutiveUrgent++;
+        } else if (queue == mPending) {
+            mActiveCountConsecutiveUrgent = 0;
+            mActiveCountConsecutiveNormal++;
+        } else if (queue == mPendingOffload) {
+            mActiveCountConsecutiveUrgent = 0;
+            mActiveCountConsecutiveNormal = 0;
+        }
+        return !isQueueEmpty(queue) ? queue.removeFirst() : null;
     }
 
     @Nullable ArrayDeque<SomeArgs> queueForNextBroadcast() {
-        if (!mPendingUrgent.isEmpty()) {
-            return mPendingUrgent;
-        } else if (!mPending.isEmpty()) {
-            return mPending;
+        final ArrayDeque<SomeArgs> nextNormal = queueForNextBroadcast(
+                mPending, mPendingOffload,
+                mActiveCountConsecutiveNormal, constants.MAX_CONSECUTIVE_NORMAL_DISPATCHES);
+        final ArrayDeque<SomeArgs> nextBroadcastQueue = queueForNextBroadcast(
+                mPendingUrgent, nextNormal,
+                mActiveCountConsecutiveUrgent, constants.MAX_CONSECUTIVE_URGENT_DISPATCHES);
+        return nextBroadcastQueue;
+    }
+
+    private @Nullable ArrayDeque<SomeArgs> queueForNextBroadcast(
+            @Nullable ArrayDeque<SomeArgs> highPriorityQueue,
+            @Nullable ArrayDeque<SomeArgs> lowPriorityQueue,
+            int consecutiveHighPriorityCount,
+            int maxHighPriorityDispatchLimit) {
+        // nothing high priority pending, no further decisionmaking
+        if (isQueueEmpty(highPriorityQueue)) {
+            return lowPriorityQueue;
         }
-        return null;
+        // nothing but high priority pending, also no further decisionmaking
+        if (isQueueEmpty(lowPriorityQueue)) {
+            return highPriorityQueue;
+        }
+
+        // Starvation mitigation: although we prioritize high priority queues by default,
+        // we allow low priority queues to make steady progress even if broadcasts in
+        // high priority queue are arriving faster than they can be dispatched.
+        //
+        // We do not try to defer to the next broadcast in low priority queues if that broadcast
+        // is ordered and still blocked on delivery to other recipients.
+        final SomeArgs nextLPArgs = lowPriorityQueue.peekFirst();
+        final BroadcastRecord nextLPRecord = (BroadcastRecord) nextLPArgs.arg1;
+        final int nextLPRecordIndex = nextLPArgs.argi1;
+        final BroadcastRecord nextHPRecord = (BroadcastRecord) highPriorityQueue.peekFirst().arg1;
+        final boolean shouldConsiderLPQueue = (mPrioritizeEarliest
+                || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit);
+        final boolean isLPQueueEligible = shouldConsiderLPQueue
+                && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
+                && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex);
+        return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue;
+    }
+
+    private static boolean isQueueEmpty(@Nullable ArrayDeque<SomeArgs> queue) {
+        return (queue == null || queue.isEmpty());
+    }
+
+    /**
+     * When {@code prioritizeEarliest} is set to {@code true}, then earliest enqueued
+     * broadcasts would be prioritized for dispatching, even if there are urgent broadcasts
+     * waiting. This is typically used in case there are callers waiting for "barrier" to be
+     * reached.
+     */
+    @VisibleForTesting
+    void setPrioritizeEarliest(boolean prioritizeEarliest) {
+        mPrioritizeEarliest = prioritizeEarliest;
     }
 
     /**
@@ -546,13 +635,13 @@
      */
     @Nullable SomeArgs peekNextBroadcast() {
         ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
-        return (queue != null) ? queue.peekFirst() : null;
+        return !isQueueEmpty(queue) ? queue.peekFirst() : null;
     }
 
     @VisibleForTesting
     @Nullable BroadcastRecord peekNextBroadcastRecord() {
         ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
-        return (queue != null) ? (BroadcastRecord) queue.peekFirst().arg1 : null;
+        return !isQueueEmpty(queue) ? (BroadcastRecord) queue.peekFirst().arg1 : null;
     }
 
     /**
@@ -564,6 +653,14 @@
     }
 
     /**
+     * Report whether this queue is currently handling an urgent broadcast.
+     */
+    public boolean isPendingUrgent() {
+        BroadcastRecord next = peekNextBroadcastRecord();
+        return (next != null) ? next.isUrgent() : false;
+    }
+
+    /**
      * Quickly determine if this queue has broadcasts that are still waiting to
      * be delivered at some point in the future.
      */
@@ -576,17 +673,21 @@
      * barrier timestamp that are still waiting to be delivered.
      */
     public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) {
-        if (mActive != null) {
-            return mActive.enqueueTime > barrierTime;
-        }
         final SomeArgs next = mPending.peekFirst();
         final SomeArgs nextUrgent = mPendingUrgent.peekFirst();
-        // Empty queue is past any barrier
-        final boolean nextLater = next == null
+        final SomeArgs nextOffload = mPendingOffload.peekFirst();
+
+        // Empty records are always past any barrier
+        final boolean activeBeyond = (mActive == null)
+                || mActive.enqueueTime > barrierTime;
+        final boolean nextBeyond = (next == null)
                 || ((BroadcastRecord) next.arg1).enqueueTime > barrierTime;
-        final boolean nextUrgentLater = nextUrgent == null
+        final boolean nextUrgentBeyond = (nextUrgent == null)
                 || ((BroadcastRecord) nextUrgent.arg1).enqueueTime > barrierTime;
-        return nextLater && nextUrgentLater;
+        final boolean nextOffloadBeyond = (nextOffload == null)
+                || ((BroadcastRecord) nextOffload.arg1).enqueueTime > barrierTime;
+
+        return activeBeyond && nextBeyond && nextUrgentBeyond && nextOffloadBeyond;
     }
 
     public boolean isRunnable() {
@@ -627,6 +728,7 @@
     static final int REASON_MAX_PENDING = 3;
     static final int REASON_BLOCKED = 4;
     static final int REASON_INSTRUMENTED = 5;
+    static final int REASON_PERSISTENT = 6;
     static final int REASON_CONTAINS_FOREGROUND = 10;
     static final int REASON_CONTAINS_ORDERED = 11;
     static final int REASON_CONTAINS_ALARM = 12;
@@ -634,6 +736,7 @@
     static final int REASON_CONTAINS_INTERACTIVE = 14;
     static final int REASON_CONTAINS_RESULT_TO = 15;
     static final int REASON_CONTAINS_INSTRUMENTED = 16;
+    static final int REASON_CONTAINS_MANIFEST = 17;
 
     @IntDef(flag = false, prefix = { "REASON_" }, value = {
             REASON_EMPTY,
@@ -642,6 +745,7 @@
             REASON_MAX_PENDING,
             REASON_BLOCKED,
             REASON_INSTRUMENTED,
+            REASON_PERSISTENT,
             REASON_CONTAINS_FOREGROUND,
             REASON_CONTAINS_ORDERED,
             REASON_CONTAINS_ALARM,
@@ -649,6 +753,7 @@
             REASON_CONTAINS_INTERACTIVE,
             REASON_CONTAINS_RESULT_TO,
             REASON_CONTAINS_INSTRUMENTED,
+            REASON_CONTAINS_MANIFEST,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Reason {}
@@ -661,6 +766,7 @@
             case REASON_MAX_PENDING: return "MAX_PENDING";
             case REASON_BLOCKED: return "BLOCKED";
             case REASON_INSTRUMENTED: return "INSTRUMENTED";
+            case REASON_PERSISTENT: return "PERSISTENT";
             case REASON_CONTAINS_FOREGROUND: return "CONTAINS_FOREGROUND";
             case REASON_CONTAINS_ORDERED: return "CONTAINS_ORDERED";
             case REASON_CONTAINS_ALARM: return "CONTAINS_ALARM";
@@ -668,10 +774,23 @@
             case REASON_CONTAINS_INTERACTIVE: return "CONTAINS_INTERACTIVE";
             case REASON_CONTAINS_RESULT_TO: return "CONTAINS_RESULT_TO";
             case REASON_CONTAINS_INSTRUMENTED: return "CONTAINS_INSTRUMENTED";
+            case REASON_CONTAINS_MANIFEST: return "CONTAINS_MANIFEST";
             default: return Integer.toString(reason);
         }
     }
 
+    private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) {
+        final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
+
+        // We might be blocked waiting for other receivers to finish,
+        // typically for an ordered broadcast or priority traunches
+        if (r.terminalCount < blockedUntilTerminalCount
+                && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Update {@link #getRunnableAt()} if it's currently invalidated.
      */
@@ -680,13 +799,11 @@
         if (next != null) {
             final BroadcastRecord r = (BroadcastRecord) next.arg1;
             final int index = next.argi1;
-            final int blockedUntilTerminalCount = next.argi2;
             final long runnableAt = r.enqueueTime;
 
-            // We might be blocked waiting for other receivers to finish,
-            // typically for an ordered broadcast or priority traunches
-            if (r.terminalCount < blockedUntilTerminalCount
-                    && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
+            // If we're specifically queued behind other ordered dispatch activity,
+            // we aren't runnable yet
+            if (blockedOnOrderedDispatch(r, index)) {
                 mRunnableAt = Long.MAX_VALUE;
                 mRunnableAtReason = REASON_BLOCKED;
                 return;
@@ -716,6 +833,12 @@
             } else if (mCountResultTo > 0) {
                 mRunnableAt = runnableAt;
                 mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
+            } else if (mCountManifest > 0) {
+                mRunnableAt = runnableAt;
+                mRunnableAtReason = REASON_CONTAINS_MANIFEST;
+            } else if (mProcessPersistent) {
+                mRunnableAt = runnableAt;
+                mRunnableAtReason = REASON_PERSISTENT;
             } else if (mProcessCached) {
                 mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
                 mRunnableAtReason = REASON_CACHED;
@@ -726,8 +849,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 +969,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 +1000,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 +1015,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..1a72fef 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -42,16 +42,15 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.ApplicationExitInfo;
 import android.app.BroadcastOptions;
 import android.app.IApplicationThread;
-import android.app.RemoteServiceException.CannotDeliverBroadcastException;
 import android.app.usage.UsageEvents.Event;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
@@ -729,19 +728,14 @@
                     thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
                             data, extras, ordered, sticky, sendingUser,
                             app.mState.getReportedProcState());
-                // TODO: Uncomment this when (b/28322359) is fixed and we aren't getting
-                // DeadObjectException when the process isn't actually dead.
-                //} catch (DeadObjectException ex) {
-                // Failed to call into the process.  It's dying so just let it die and move on.
-                //    throw ex;
                 } catch (RemoteException ex) {
                     // Failed to call into the process. It's either dying or wedged. Kill it gently.
                     synchronized (mService) {
                         final String msg = "Failed to schedule " + intent + " to " + receiver
                                 + " via " + app + ": " + ex;
                         Slog.w(TAG, msg);
-                        app.scheduleCrashLocked(msg,
-                                CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
+                        app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
+                                ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
                     }
                     throw ex;
                 }
@@ -1394,8 +1388,8 @@
                 final String msg = "Failed to schedule " + r.intent + " to " + info
                         + " via " + app + ": " + e;
                 Slog.w(TAG, msg);
-                app.scheduleCrashLocked(msg,
-                        CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
+                app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
             } catch (RuntimeException e) {
                 Slog.wtf(TAG, "Failed sending broadcast to "
                         + r.curComponent + " with " + r.intent, e);
@@ -1639,9 +1633,8 @@
             }
             Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
             logBroadcastReceiverDiscardLocked(r);
-            String anrMessage =
-                    "Broadcast of " + r.intent.toString() + ", waited " + timeoutDurationMs + "ms";
-            TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(anrMessage);
+            TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(r.intent,
+                    timeoutDurationMs);
             if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
                 BroadcastFilter bf = (BroadcastFilter) curReceiver;
                 if (bf.receiverList.pid != 0
@@ -1690,13 +1683,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..6ba1b1f 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;
@@ -44,9 +42,9 @@
 import android.annotation.UptimeMillisLong;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.ApplicationExitInfo;
 import android.app.BroadcastOptions;
 import android.app.IApplicationThread;
-import android.app.RemoteServiceException.CannotDeliverBroadcastException;
 import android.app.UidObserver;
 import android.app.usage.UsageEvents.Event;
 import android.content.ComponentName;
@@ -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;
@@ -94,6 +91,7 @@
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -141,17 +139,9 @@
 
         // We configure runnable size only once at boot; it'd be too complex to
         // try resizing dynamically at runtime
-        mRunning = new BroadcastProcessQueue[mConstants.MAX_RUNNING_PROCESS_QUEUES];
+        mRunning = new BroadcastProcessQueue[mConstants.getMaxRunningQueues()];
     }
 
-    // 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 +212,31 @@
     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) {
+        enqueueFinishReceiver(queue, queue.getActive(), queue.getActiveIndex(),
+                deliveryState, reason);
+    }
+
+    private void enqueueFinishReceiver(@NonNull BroadcastProcessQueue queue,
+            @NonNull BroadcastRecord r, int index,
+            @DeliveryState int deliveryState, @NonNull String reason) {
+        final SomeArgs args = SomeArgs.obtain();
+        args.arg1 = queue;
+        args.argi1 = deliveryState;
+        args.arg2 = reason;
+        args.arg3 = r;
+        args.argi2 = index;
+        mLocalHandler.sendMessage(Message.obtain(mLocalHandler, MSG_FINISH_RECEIVER, args));
+    }
+
     private final Handler mLocalHandler;
 
     private final Handler.Callback mLocalCallback = (msg) -> {
@@ -240,7 +249,7 @@
             }
             case MSG_DELIVERY_TIMEOUT_SOFT: {
                 synchronized (mService) {
-                    deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj);
+                    deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1);
                 }
                 return true;
             }
@@ -266,6 +275,19 @@
                 }
                 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;
+                    final BroadcastRecord r = (BroadcastRecord) args.arg3;
+                    final int index = args.argi2;
+                    args.recycle();
+                    finishReceiverLocked(queue, deliveryState, reason, r, index);
+                }
+                return true;
+            }
         }
         return false;
     };
@@ -283,6 +305,19 @@
     }
 
     /**
+     * Return the number of active queues that are delivering "urgent" broadcasts
+     */
+    private int getRunningUrgentCount() {
+        int count = 0;
+        for (int i = 0; i < mRunning.length; i++) {
+            if (mRunning[i] != null && mRunning[i].getActive().isUrgent()) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
      * Return the first index of the given value contained inside
      * {@link #mRunning}, otherwise {@code -1}.
      */
@@ -346,10 +381,18 @@
      */
     @GuardedBy("mService")
     private void updateRunningListLocked() {
-        int avail = mRunning.length - getRunningSize();
+        // Allocated size here implicitly includes the extra reservation for urgent
+        // dispatches beyond the MAX_RUNNING_QUEUES soft limit for normal
+        // parallelism.  If we're already dispatching some urgent broadcasts,
+        // count that against the extra first - its role is to permit progress of
+        // urgent broadcast traffic when the normal reservation is fully occupied
+        // with less-urgent dispatches, not to generally expand parallelism.
+        final int usedExtra = Math.min(getRunningUrgentCount(),
+                mConstants.EXTRA_RUNNING_URGENT_PROCESS_QUEUES);
+        int avail = mRunning.length - getRunningSize() - usedExtra;
         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
@@ -372,6 +415,15 @@
                 continue;
             }
 
+            // If we've hit the soft limit for non-urgent dispatch parallelism,
+            // only consider delivering from queues whose ready broadcast is urgent
+            if (getRunningSize() >= mConstants.MAX_RUNNING_PROCESS_QUEUES) {
+                if (!queue.isPendingUrgent()) {
+                    queue = nextQueue;
+                    continue;
+                }
+            }
+
             // If queues beyond this point aren't ready to run yet, schedule
             // another pass when they'll be runnable
             if (runnableAt > now && !waitingFor) {
@@ -412,21 +464,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 +501,7 @@
             });
         }
 
-        traceEnd(TAG, cookie);
+        traceEnd(cookie);
     }
 
     @Override
@@ -464,9 +519,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 +568,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 +597,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 +629,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();
         }
@@ -605,7 +641,10 @@
         // If nothing to dispatch, send any pending result immediately
         if (r.receivers.isEmpty()) {
             scheduleResultTo(r);
+            notifyFinishBroadcast(r);
         }
+
+        traceEnd(cookie);
     }
 
     private void applyDeliveryGroupPolicy(@NonNull BroadcastRecord r) {
@@ -661,7 +700,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 +723,8 @@
                 hostingRecord, zygotePolicyFlags, allowWhileBooting, false);
         if (queue.app == null) {
             mRunningColdStart = null;
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE,
+                    "startProcessLocked failed");
             return;
         }
     }
@@ -700,10 +741,8 @@
     private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
         checkState(queue.isActive(), "isActive");
 
-        final ProcessRecord app = queue.app;
         final BroadcastRecord r = queue.getActive();
         final int index = queue.getActiveIndex();
-        final Object receiver = r.receivers.get(index);
 
         if (r.terminalCount == 0) {
             r.dispatchTime = SystemClock.uptimeMillis();
@@ -711,41 +750,77 @@
             r.dispatchClockTime = System.currentTimeMillis();
         }
 
-        // If someone already finished this broadcast, finish immediately
-        final int oldDeliveryState = getDeliveryState(r, index);
-        if (isDeliveryStateTerminal(oldDeliveryState)) {
-            finishReceiverLocked(queue, oldDeliveryState);
+        if (maybeSkipReceiver(queue, r, index)) {
             return;
         }
+        dispatchReceivers(queue, r, index);
+    }
+
+    /**
+     * Examine a receiver and possibly skip it.  The method returns true if the receiver is
+     * skipped (and therefore no more work is required).
+     */
+    private boolean maybeSkipReceiver(BroadcastProcessQueue queue, BroadcastRecord r, int index) {
+        final int oldDeliveryState = getDeliveryState(r, index);
+        final ProcessRecord app = queue.app;
+        final Object receiver = r.receivers.get(index);
+
+        // If someone already finished this broadcast, finish immediately
+        if (isDeliveryStateTerminal(oldDeliveryState)) {
+            enqueueFinishReceiver(queue, oldDeliveryState, "already terminal state");
+            return true;
+        }
 
         // Consider additional cases where we'd want to finish immediately
         if (app.isInFullBackup()) {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
-            return;
+            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
+            return true;
         }
         if (mSkipPolicy.shouldSkip(r, receiver)) {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
-            return;
+            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy");
+            return true;
         }
         final Intent receiverIntent = r.getReceiverIntent(receiver);
         if (receiverIntent == null) {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
-            return;
+            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent");
+            return true;
         }
 
         // Ignore registered receivers from a previous PID
         if ((receiver instanceof BroadcastFilter)
                 && ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
-            return;
+            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED,
+                    "BroadcastFilter for mismatched PID");
+            return true;
         }
+        // The receiver was not handled in this method.
+        return false;
+    }
 
-        if (mService.mProcessesReady && !r.timeoutExempt) {
+    /**
+     * Return true if this receiver should be assumed to have been delivered.
+     */
+    private boolean isAssumedDelivered(BroadcastRecord r, int index) {
+        return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered;
+    }
+
+    /**
+     * A receiver is about to be dispatched.  Start ANR timers, if necessary.
+     */
+    private void dispatchReceivers(BroadcastProcessQueue queue, BroadcastRecord r, int index) {
+        final ProcessRecord app = queue.app;
+        final Object receiver = r.receivers.get(index);
+
+        // Skip ANR tracking early during boot, when requested, or when we
+        // immediately assume delivery success
+        final boolean assumeDelivered = isAssumedDelivered(r, index);
+        if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
             queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
 
-            final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
-            mLocalHandler.sendMessageDelayed(
-                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_SOFT, queue), timeout);
+            final int softTimeoutMillis = (int) (r.isForeground() ? mFgConstants.TIMEOUT
+                    : mBgConstants.TIMEOUT);
+            mLocalHandler.sendMessageDelayed(Message.obtain(mLocalHandler,
+                    MSG_DELIVERY_TIMEOUT_SOFT, softTimeoutMillis, 0, queue), softTimeoutMillis);
         }
 
         if (r.allowBackgroundActivityStarts) {
@@ -768,8 +843,10 @@
         }
 
         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 Intent receiverIntent = r.getReceiverIntent(receiver);
         final IApplicationThread thread = app.getOnewayThread();
         if (thread != null) {
             try {
@@ -782,8 +859,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);
@@ -795,12 +873,13 @@
                 final String msg = "Failed to schedule " + r + " to " + receiver
                         + " via " + app + ": " + e;
                 logw(msg);
-                app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
-                app.setKilled(true);
-                finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+                app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
+                enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app");
             }
         } else {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE,
+                    "missing IApplicationThread");
         }
     }
 
@@ -809,9 +888,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);
@@ -822,29 +901,33 @@
             } catch (RemoteException e) {
                 final String msg = "Failed to schedule result of " + r + " via " + app + ": " + e;
                 logw(msg);
-                app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
+                app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
             }
         }
         // Clear so both local and remote references can be GC'ed
         r.resultTo = null;
     }
 
-    private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue) {
+    private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue,
+            int softTimeoutMillis) {
         if (queue.app != null) {
             // Instead of immediately triggering an ANR, extend the timeout by
             // the amount of time the process was runnable-but-waiting; we're
             // only willing to do this once before triggering an hard ANR
             final long cpuDelayTime = queue.app.getCpuDelayTime() - queue.lastCpuDelayTime;
-            final long timeout = MathUtils.constrain(cpuDelayTime, 0, mConstants.TIMEOUT);
+            final long hardTimeoutMillis = MathUtils.constrain(cpuDelayTime, 0, softTimeoutMillis);
             mLocalHandler.sendMessageDelayed(
-                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue), timeout);
+                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue),
+                    hardTimeoutMillis);
         } else {
             deliveryTimeoutHardLocked(queue);
         }
     }
 
     private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
-        finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT);
+        finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
+                "deliveryTimeoutHardLocked");
     }
 
     @Override
@@ -871,47 +954,68 @@
             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");
-
-        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);
-
-        if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
-            r.anrCount++;
-            if (app != null && !app.isDebugging()) {
-                mService.appNotResponding(queue.app, TimeoutRecord
-                        .forBroadcastReceiver("Broadcast of " + r.toShortString()));
-            }
-        } else {
-            mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
-            mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
-        }
-
+    /**
+     * Return true if there are more broadcasts in the queue and the queue is runnable.
+     */
+    private boolean shouldContinueScheduling(@NonNull BroadcastProcessQueue queue) {
         // If we've made reasonable progress, periodically retire ourselves to
         // avoid starvation of other processes and stack overflow when a
         // broadcast is immediately finished without waiting
         final boolean shouldRetire =
                 (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
 
-        if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) {
+        return queue.isRunnable() && queue.isProcessWarm() && !shouldRetire;
+    }
+
+    private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
+            @DeliveryState int deliveryState, @NonNull String reason) {
+        if (!queue.isActive()) {
+            logw("Ignoring finish; no active broadcast for " + queue);
+            return false;
+        }
+
+        final BroadcastRecord r = queue.getActive();
+        final int index = queue.getActiveIndex();
+        return finishReceiverLocked(queue, deliveryState, reason, r, index);
+    }
+
+    private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
+            @DeliveryState int deliveryState, @NonNull String reason,
+            BroadcastRecord r, int index) {
+        if (!queue.isActive()) {
+            logw("Ignoring finish; no active broadcast for " + queue);
+            return false;
+        }
+
+        final int cookie = traceBegin("finishReceiver");
+        final ProcessRecord app = queue.app;
+        final Object receiver = r.receivers.get(index);
+
+        setDeliveryState(queue, app, r, index, receiver, deliveryState, reason);
+
+        if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
+            r.anrCount++;
+            if (app != null && !app.isDebugging()) {
+                mService.appNotResponding(queue.app, TimeoutRecord.forBroadcastReceiver(r.intent));
+            }
+        } else {
+            mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
+            mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
+        }
+
+        final boolean res = shouldContinueScheduling(queue);
+        if (res) {
             // We're on a roll; move onto the next broadcast for this process
             queue.makeActiveNextPending();
             scheduleReceiverWarmLocked(queue);
-            return true;
         } else {
             // We've drained running broadcasts; maybe move back to runnable
             queue.makeActiveIdle();
@@ -925,8 +1029,9 @@
             // Tell other OS components that app is not actively running, giving
             // a chance to update OOM adjustment
             notifyStoppedRunning(queue);
-            return false;
         }
+        traceEnd(cookie);
+        return res;
     }
 
     /**
@@ -935,7 +1040,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 +1069,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 +1099,8 @@
                 enqueueUpdateRunningList();
             }
         }
+
+        traceEnd(cookie);
     }
 
     private @DeliveryState int getDeliveryState(@NonNull BroadcastRecord r, int index) {
@@ -1053,7 +1161,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 +1170,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;
@@ -1115,6 +1225,17 @@
         return didSomething;
     }
 
+    private void forEachQueue(@NonNull Consumer<BroadcastProcessQueue> consumer) {
+        for (int i = 0; i < mProcessQueues.size(); ++i) {
+            BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
+            while (leaf != null) {
+                consumer.accept(leaf);
+                updateRunnableList(leaf);
+                leaf = leaf.processNameNext;
+            }
+        }
+    }
+
     @Override
     public void start(@NonNull ContentResolver resolver) {
         mFgConstants.startObserving(mHandler, resolver);
@@ -1173,12 +1294,19 @@
         final CountDownLatch latch = new CountDownLatch(1);
         synchronized (mService) {
             mWaitingFor.add(Pair.create(condition, latch));
+            forEachQueue(q -> q.setPrioritizeEarliest(true));
         }
         enqueueUpdateRunningList();
         try {
             latch.await();
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
+        } finally {
+            synchronized (mService) {
+                if (mWaitingFor.isEmpty()) {
+                    forEachQueue(q -> q.setPrioritizeEarliest(false));
+                }
+            }
         }
     }
 
@@ -1253,18 +1381,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,29 +1512,34 @@
 
         final boolean recordFinished = (r.terminalCount == r.receivers.size());
         if (recordFinished) {
-            mHistory.addBroadcastToHistoryLocked(r);
+            notifyFinishBroadcast(r);
+        }
+    }
 
-            r.finishTime = SystemClock.uptimeMillis();
-            r.nextReceiver = r.receivers.size();
-            BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+    private void notifyFinishBroadcast(@NonNull BroadcastRecord r) {
+        mService.notifyBroadcastFinishedLocked(r);
+        r.finishTime = SystemClock.uptimeMillis();
+        r.nextReceiver = r.receivers.size();
+        mHistory.addBroadcastToHistoryLocked(r);
 
-            if (r.intent.getComponent() == null && r.intent.getPackage() == null
-                    && (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
-                int manifestCount = 0;
-                int manifestSkipCount = 0;
-                for (int i = 0; i < r.receivers.size(); i++) {
-                    if (r.receivers.get(i) instanceof ResolveInfo) {
-                        manifestCount++;
-                        if (r.delivery[i] == BroadcastRecord.DELIVERY_SKIPPED) {
-                            manifestSkipCount++;
-                        }
+        BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+
+        if (r.intent.getComponent() == null && r.intent.getPackage() == null
+                && (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+            int manifestCount = 0;
+            int manifestSkipCount = 0;
+            for (int i = 0; i < r.receivers.size(); i++) {
+                if (r.receivers.get(i) instanceof ResolveInfo) {
+                    manifestCount++;
+                    if (r.delivery[i] == BroadcastRecord.DELIVERY_SKIPPED) {
+                        manifestSkipCount++;
                     }
                 }
-
-                final long dispatchTime = SystemClock.uptimeMillis() - r.enqueueTime;
-                mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
-                        manifestCount, manifestSkipCount, dispatchTime);
             }
+
+            final long dispatchTime = SystemClock.uptimeMillis() - r.enqueueTime;
+            mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
+                    manifestCount, manifestSkipCount, dispatchTime);
         }
     }
 
@@ -1441,7 +1562,7 @@
         }
 
         BroadcastProcessQueue created = new BroadcastProcessQueue(mConstants, processName, uid);
-        created.app = mService.getProcessRecordLocked(processName, uid);
+        created.setProcess(mService.getProcessRecordLocked(processName, uid));
 
         if (leaf == null) {
             mProcessQueues.put(uid, created);
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 2a3c897..100b2db 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -37,6 +37,7 @@
 import android.content.ComponentName;
 import android.content.IIntentReceiver;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
@@ -96,6 +97,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 +377,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 +388,7 @@
         ordered = _serialized;
         sticky = _sticky;
         initialSticky = _initialSticky;
-        prioritized = isPrioritized(receivers);
+        prioritized = isPrioritized(blockedUntilTerminalCount, _serialized);
         userId = _userId;
         nextReceiver = 0;
         state = IDLE;
@@ -395,7 +398,7 @@
         alarm = options != null && options.isAlarmBroadcast();
         pushMessage = options != null && options.isPushMessagingBroadcast();
         pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
-        interactive = options != null && options.isInteractiveBroadcast();
+        interactive = options != null && options.isInteractive();
         this.filterExtrasForReceiver = filterExtrasForReceiver;
     }
 
@@ -427,6 +430,7 @@
         options = from.options;
         receivers = from.receivers;
         delivery = from.delivery;
+        blockedUntilTerminalCount = from.blockedUntilTerminalCount;
         scheduledTime = from.scheduledTime;
         terminalTime = from.terminalTime;
         resultToApp = from.resultToApp;
@@ -617,6 +621,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 +694,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) {
@@ -825,14 +871,34 @@
         }
     }
 
-    public boolean matchesDeliveryGroup(@NonNull BroadcastRecord other) {
-        final String key = (options != null) ? options.getDeliveryGroupKey() : null;
-        final String otherKey = (other.options != null)
-                ? other.options.getDeliveryGroupKey() : null;
-        if (key == null && otherKey == null) {
-            return intent.filterEquals(other.intent);
+    boolean matchesDeliveryGroup(@NonNull BroadcastRecord other) {
+        return matchesDeliveryGroup(this, other);
+    }
+
+    private static boolean matchesDeliveryGroup(@NonNull BroadcastRecord newRecord,
+            @NonNull BroadcastRecord oldRecord) {
+        final String newMatchingKey = getDeliveryGroupMatchingKey(newRecord);
+        final String oldMatchingKey = getDeliveryGroupMatchingKey(oldRecord);
+        final IntentFilter newMatchingFilter = getDeliveryGroupMatchingFilter(newRecord);
+        // If neither delivery group key nor matching filter is specified, then use
+        // Intent.filterEquals() to identify the delivery group.
+        if (newMatchingKey == null && oldMatchingKey == null && newMatchingFilter == null) {
+            return newRecord.intent.filterEquals(oldRecord.intent);
         }
-        return Objects.equals(key, otherKey);
+        if (newMatchingFilter != null && !newMatchingFilter.asPredicate().test(oldRecord.intent)) {
+            return false;
+        }
+        return Objects.equals(newMatchingKey, oldMatchingKey);
+    }
+
+    @Nullable
+    private static String getDeliveryGroupMatchingKey(@NonNull BroadcastRecord record) {
+        return record.options == null ? null : record.options.getDeliveryGroupMatchingKey();
+    }
+
+    @Nullable
+    private static IntentFilter getDeliveryGroupMatchingFilter(@NonNull BroadcastRecord record) {
+        return record.options == null ? null : record.options.getDeliveryGroupMatchingFilter();
     }
 
     @Override
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/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index cbf0aae..4c10d58b 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -37,6 +37,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.EventLog;
+import android.util.IntArray;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -89,6 +90,8 @@
             "compact_proc_state_throttle";
     @VisibleForTesting static final String KEY_FREEZER_DEBOUNCE_TIMEOUT =
             "freeze_debounce_timeout";
+    @VisibleForTesting static final String KEY_FREEZER_EXEMPT_INST_PKG =
+            "freeze_exempt_inst_pkg";
 
     // RSS Indices
     private static final int RSS_TOTAL_INDEX = 0;
@@ -137,6 +140,7 @@
     @VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
             String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
     @VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 600_000L;
+    @VisibleForTesting static final Boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = true;
 
     @VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor(
                 Settings.Global.CACHED_APPS_FREEZER_ENABLED);
@@ -277,6 +281,8 @@
                         for (String name : properties.getKeyset()) {
                             if (KEY_FREEZER_DEBOUNCE_TIMEOUT.equals(name)) {
                                 updateFreezerDebounceTimeout();
+                            } else if (KEY_FREEZER_EXEMPT_INST_PKG.equals(name)) {
+                                updateFreezerExemptInstPkg();
                             }
                         }
                     }
@@ -357,6 +363,7 @@
     private boolean mFreezerOverride = false;
 
     @VisibleForTesting volatile long mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
+    @VisibleForTesting volatile boolean mFreezerExemptInstPkg = DEFAULT_FREEZER_EXEMPT_INST_PKG;
 
     // Maps process ID to last compaction statistics for processes that we've fully compacted. Used
     // when evaluating throttles that we only consider for "full" compaction, so we don't store
@@ -566,6 +573,15 @@
         }
     }
 
+    /**
+     * Returns whether freezer exempts INSTALL_PACKAGES.
+     */
+    public boolean freezerExemptInstPkg() {
+        synchronized (mPhenotypeFlagLock) {
+            return mUseFreezer && mFreezerExemptInstPkg;
+        }
+    }
+
     @GuardedBy("mProcLock")
     void dump(PrintWriter pw) {
         pw.println("CachedAppOptimizer settings");
@@ -647,6 +663,7 @@
             pw.println("  " + KEY_USE_FREEZER + "=" + mUseFreezer);
             pw.println("  " + KEY_FREEZER_STATSD_SAMPLE_RATE + "=" + mFreezerStatsdSampleRate);
             pw.println("  " + KEY_FREEZER_DEBOUNCE_TIMEOUT + "=" + mFreezerDebounceTimeout);
+            pw.println("  " + KEY_FREEZER_EXEMPT_INST_PKG + "=" + mFreezerExemptInstPkg);
             synchronized (mProcLock) {
                 int size = mFrozenProcesses.size();
                 pw.println("  Apps frozen: " + size);
@@ -829,7 +846,7 @@
     /**
      * Retrieves the free swap percentage.
      */
-    static private native double getFreeSwapPercent();
+    static native double getFreeSwapPercent();
 
     /**
      * Retrieves the total used physical ZRAM
@@ -1007,6 +1024,7 @@
                     KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) {
             mUseFreezer = isFreezerSupported();
             updateFreezerDebounceTimeout();
+            updateFreezerExemptInstPkg();
         } else {
             mUseFreezer = false;
         }
@@ -1194,6 +1212,15 @@
         if (mFreezerDebounceTimeout < 0) {
             mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
         }
+        Slog.d(TAG_AM, "Freezer timeout set to " + mFreezerDebounceTimeout);
+    }
+
+    @GuardedBy("mPhenotypeFlagLock")
+    private void updateFreezerExemptInstPkg() {
+        mFreezerExemptInstPkg = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+                KEY_FREEZER_EXEMPT_INST_PKG, DEFAULT_FREEZER_EXEMPT_INST_PKG);
+        Slog.d(TAG_AM, "Freezer exemption set to " + mFreezerExemptInstPkg);
     }
 
     private boolean parseProcStateThrottle(String procStateThrottleString) {
@@ -1977,6 +2004,7 @@
 
                     opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
                     opt.setFrozen(true);
+                    opt.setHasCollectedFrozenPSS(false);
                     mFrozenProcesses.put(pid, proc);
                 } catch (Exception e) {
                     Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);
@@ -2084,15 +2112,28 @@
 
         @GuardedBy({"mAm"})
         @Override
-        public void onBlockingFileLock(int pid) {
+        public void onBlockingFileLock(IntArray pids) {
             if (DEBUG_FREEZER) {
-                Slog.d(TAG_AM, "Process (pid=" + pid + ") holds blocking file lock");
+                Slog.d(TAG_AM, "Blocking file lock found: " + pids);
             }
             synchronized (mProcLock) {
+                int pid = pids.get(0);
                 ProcessRecord app = mFrozenProcesses.get(pid);
+                ProcessRecord pr;
                 if (app != null) {
-                    Slog.i(TAG_AM, app.processName + " (" + pid + ") holds blocking file lock");
-                    unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+                    for (int i = 1; i < pids.size(); i++) {
+                        int blocked = pids.get(i);
+                        synchronized (mAm.mPidsSelfLocked) {
+                            pr = mAm.mPidsSelfLocked.get(blocked);
+                        }
+                        if (pr != null && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
+                            Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
+                                    + pr.processName + " (" + blocked + ")");
+                            // Found at least one blocked non-cached process
+                            unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+                            break;
+                        }
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index a97173d..16055b9 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -81,6 +81,7 @@
 import com.android.server.LocalServices;
 import com.android.server.RescueParty;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerService;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import java.io.FileDescriptor;
@@ -163,7 +164,7 @@
     private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
             String name, IBinder token, int callingUid, String callingPackage, String callingTag,
             boolean stable, int userId) {
-        ContentProviderRecord cpr;
+        ContentProviderRecord cpr = null;
         ContentProviderConnection conn = null;
         ProviderInfo cpi = null;
         boolean providerRunning = false;
@@ -185,8 +186,21 @@
 
             checkTime(startTime, "getContentProviderImpl: getProviderByName");
 
-            // First check if this content provider has been published...
-            cpr = mProviderMap.getProviderByName(name, userId);
+            UserManagerService userManagerService = UserManagerService.getInstance();
+
+            /*
+             For clone user profile and allowed authority, skipping finding provider and redirecting
+             it to owner profile. Ideally clone profile should not have MediaProvider instance
+             installed and mProviderMap would not have entry for clone user. This is just fallback
+             check to ensure even if MediaProvider is installed in Clone Profile, it should not be
+             used and redirect to owner user's MediaProvider.
+             */
+            //todo(b/236121588) MediaProvider should not be installed in clone profile.
+            if (!isAuthorityRedirectedForCloneProfile(name)
+                    || !userManagerService.isMediaSharedWithParent(userId)) {
+                // First check if this content provider has been published...
+                cpr = mProviderMap.getProviderByName(name, userId);
+            }
             // If that didn't work, check if it exists for user 0 and then
             // verify that it's a singleton provider before using it.
             if (cpr == null && userId != UserHandle.USER_SYSTEM) {
@@ -201,11 +215,9 @@
                         userId = UserHandle.USER_SYSTEM;
                         checkCrossUser = false;
                     } else if (isAuthorityRedirectedForCloneProfile(name)) {
-                        UserManagerInternal umInternal = LocalServices.getService(
-                                UserManagerInternal.class);
-                        UserInfo userInfo = umInternal.getUserInfo(userId);
-
-                        if (userInfo != null && userInfo.isCloneProfile()) {
+                        if (userManagerService.isMediaSharedWithParent(userId)) {
+                            UserManagerInternal umInternal = LocalServices.getService(
+                                    UserManagerInternal.class);
                             userId = umInternal.getProfileParentId(userId);
                             checkCrossUser = false;
                         }
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index dec8b62..ea3c8dc 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -107,21 +107,21 @@
 30079 uc_dispatch_user_switch (oldUserId|1|5),(newUserId|1|5)
 30080 uc_continue_user_switch (oldUserId|1|5),(newUserId|1|5)
 30081 uc_send_user_broadcast (userId|1|5),(IntentAction|3)
+
 # Tags below are used by SystemServiceManager - although it's technically part of am, these are
 # also user switch events and useful to be analyzed together with events above.
-30082 ssm_user_starting (userId|1|5),(visible|1)
+30082 ssm_user_starting (userId|1|5)
 30083 ssm_user_switching (oldUserId|1|5),(newUserId|1|5)
 30084 ssm_user_unlocking (userId|1|5)
 30085 ssm_user_unlocked (userId|1|5)
-30086 ssm_user_stopping (userId|1|5),(visibilityChanged|1)
+30086 ssm_user_stopping (userId|1|5)
 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)
+
+# Similarly, tags below are used by UserManagerService
+30091 um_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)
 30101 am_foreground_service_denied (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)
 30102 am_foreground_service_stop (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/ForegroundServiceDelegation.java b/services/core/java/com/android/server/am/ForegroundServiceDelegation.java
new file mode 100644
index 0000000..a051d17
--- /dev/null
+++ b/services/core/java/com/android/server/am/ForegroundServiceDelegation.java
@@ -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.server.am;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
+
+/**
+ * A foreground service delegate which has client options and connection callback.
+ */
+public class ForegroundServiceDelegation {
+    public final IBinder mBinder = new Binder();
+    @NonNull
+    public final ForegroundServiceDelegationOptions mOptions;
+    @Nullable
+    public final ServiceConnection mConnection;
+
+    public ForegroundServiceDelegation(@NonNull ForegroundServiceDelegationOptions options,
+            @Nullable ServiceConnection connection) {
+        mOptions = options;
+        mConnection = connection;
+    }
+}
diff --git a/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java b/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java
new file mode 100644
index 0000000..5eb5a55
--- /dev/null
+++ b/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java
@@ -0,0 +1,262 @@
+/*
+ * 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.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.IApplicationThread;
+import android.content.ComponentName;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A service module such as MediaSessionService, VOIP, Camera, Microphone, Location can ask
+ * ActivityManagerService to start a foreground service delegate on behalf of the actual app,
+ * by which the client app's process state can be promoted to FOREGROUND_SERVICE process state which
+ * is higher than the app's actual process state if the app is in the background. This can help to
+ * keep the app in the memory and extra run-time.
+ * The app does not need to define an actual service component nor add it into manifest file.
+ */
+public class ForegroundServiceDelegationOptions {
+
+    public static final int DELEGATION_SERVICE_DEFAULT = 0;
+    public static final int DELEGATION_SERVICE_DATA_SYNC = 1;
+    public static final int DELEGATION_SERVICE_MEDIA_PLAYBACK = 2;
+    public static final int DELEGATION_SERVICE_PHONE_CALL = 3;
+    public static final int DELEGATION_SERVICE_LOCATION = 4;
+    public static final int DELEGATION_SERVICE_CONNECTED_DEVICE = 5;
+    public static final int DELEGATION_SERVICE_MEDIA_PROJECTION = 6;
+    public static final int DELEGATION_SERVICE_CAMERA = 7;
+    public static final int DELEGATION_SERVICE_MICROPHONE = 8;
+    public static final int DELEGATION_SERVICE_HEALTH = 9;
+    public static final int DELEGATION_SERVICE_REMOTE_MESSAGING = 10;
+    public static final int DELEGATION_SERVICE_SYSTEM_EXEMPTED = 11;
+    public static final int DELEGATION_SERVICE_SPECIAL_USE = 12;
+
+    @IntDef(flag = false, prefix = { "DELEGATION_SERVICE_" }, value = {
+            DELEGATION_SERVICE_DEFAULT,
+            DELEGATION_SERVICE_DATA_SYNC,
+            DELEGATION_SERVICE_MEDIA_PLAYBACK,
+            DELEGATION_SERVICE_PHONE_CALL,
+            DELEGATION_SERVICE_LOCATION,
+            DELEGATION_SERVICE_CONNECTED_DEVICE,
+            DELEGATION_SERVICE_MEDIA_PROJECTION,
+            DELEGATION_SERVICE_CAMERA,
+            DELEGATION_SERVICE_MICROPHONE,
+            DELEGATION_SERVICE_HEALTH,
+            DELEGATION_SERVICE_REMOTE_MESSAGING,
+            DELEGATION_SERVICE_SYSTEM_EXEMPTED,
+            DELEGATION_SERVICE_SPECIAL_USE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DelegationService {}
+
+    // The actual app's PID
+    public final int mClientPid;
+    // The actual app's UID
+    public final int mClientUid;
+    // The actual app's package name
+    @NonNull
+    public final String mClientPackageName;
+    // The actual app's app thread
+    @Nullable
+    public final IApplicationThread mClientAppThread;
+    public final boolean mSticky; // Is it a sticky service
+
+    // The delegation service's instance name which is to identify the delegate.
+    @NonNull
+    public String mClientInstanceName;
+    // The foreground service types it consists of.
+    public final int mForegroundServiceTypes;
+    /**
+     * The service's name such as MediaSessionService, VOIP, Camera, Microphone, Location. This is
+     * the internal module's name which actually starts the FGS delegate on behalf of the client
+     * app.
+     */
+    public final @DelegationService int mDelegationService;
+
+    public ForegroundServiceDelegationOptions(int clientPid,
+            int clientUid,
+            @NonNull String clientPackageName,
+            @NonNull IApplicationThread clientAppThread,
+            boolean isSticky,
+            @NonNull String clientInstanceName,
+            int foregroundServiceTypes,
+            @DelegationService int delegationService) {
+        mClientPid = clientPid;
+        mClientUid = clientUid;
+        mClientPackageName = clientPackageName;
+        mClientAppThread = clientAppThread;
+        mSticky = isSticky;
+        mClientInstanceName = clientInstanceName;
+        mForegroundServiceTypes = foregroundServiceTypes;
+        mDelegationService = delegationService;
+    }
+
+    /**
+     * A service delegates a foreground service state to a clientUID using a instanceName.
+     * This delegation is uniquely identified by
+     * mDelegationService/mClientUid/mClientPid/mClientInstanceName
+     */
+    public boolean isSameDelegate(ForegroundServiceDelegationOptions that) {
+        return this.mDelegationService == that.mDelegationService
+                && this.mClientUid == that.mClientUid
+                && this.mClientPid == that.mClientPid
+                && this.mClientInstanceName.equals(that.mClientInstanceName);
+    }
+
+    /**
+     * Construct a component name for this delegate.
+     */
+    public ComponentName getComponentName() {
+        return new ComponentName(mClientPackageName, serviceCodeToString(mDelegationService)
+                + ":" + mClientInstanceName);
+    }
+
+    /**
+     * Get string description of this delegate options.
+     */
+    public String getDescription() {
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("ForegroundServiceDelegate{")
+                .append("package:")
+                .append(mClientPackageName)
+                .append(",")
+                .append("service:")
+                .append(serviceCodeToString(mDelegationService))
+                .append(",")
+                .append("uid:")
+                .append(mClientUid)
+                .append(",")
+                .append("pid:")
+                .append(mClientPid)
+                .append(",")
+                .append("instance:")
+                .append(mClientInstanceName)
+                .append("}");
+        return sb.toString();
+    }
+
+    /**
+     * Map the integer service code to string name.
+     * @param serviceCode
+     * @return
+     */
+    public static String serviceCodeToString(@DelegationService int serviceCode) {
+        switch (serviceCode) {
+            case DELEGATION_SERVICE_DEFAULT:
+                return "DEFAULT";
+            case DELEGATION_SERVICE_DATA_SYNC:
+                return "DATA_SYNC";
+            case DELEGATION_SERVICE_MEDIA_PLAYBACK:
+                return "MEDIA_PLAYBACK";
+            case DELEGATION_SERVICE_PHONE_CALL:
+                return "PHONE_CALL";
+            case DELEGATION_SERVICE_LOCATION:
+                return "LOCATION";
+            case DELEGATION_SERVICE_CONNECTED_DEVICE:
+                return "CONNECTED_DEVICE";
+            case DELEGATION_SERVICE_MEDIA_PROJECTION:
+                return "MEDIA_PROJECTION";
+            case DELEGATION_SERVICE_CAMERA:
+                return "CAMERA";
+            case DELEGATION_SERVICE_MICROPHONE:
+                return "MICROPHONE";
+            case DELEGATION_SERVICE_HEALTH:
+                return "HEALTH";
+            case DELEGATION_SERVICE_REMOTE_MESSAGING:
+                return "REMOTE_MESSAGING";
+            case DELEGATION_SERVICE_SYSTEM_EXEMPTED:
+                return "SYSTEM_EXEMPTED";
+            case DELEGATION_SERVICE_SPECIAL_USE:
+                return "SPECIAL_USE";
+            default:
+                return "(unknown:" + serviceCode + ")";
+        }
+    }
+
+    public static class Builder {
+        int mClientPid; // The actual app PID
+        int mClientUid; // The actual app UID
+        String mClientPackageName; // The actual app's package name
+        int mClientNotificationId; // The actual app's notification
+        IApplicationThread mClientAppThread; // The actual app's app thread
+        boolean mSticky; // Is it a sticky service
+        String mClientInstanceName; // The delegation service instance name
+        int mForegroundServiceTypes; // The foreground service types it consists of
+        @DelegationService int mDelegationService; // The internal service's name, i.e. VOIP
+
+        public Builder setClientPid(int clientPid) {
+            mClientPid = clientPid;
+            return this;
+        }
+
+        public Builder setClientUid(int clientUid) {
+            mClientUid = clientUid;
+            return this;
+        }
+
+        public Builder setClientPackageName(@NonNull String clientPackageName) {
+            mClientPackageName = clientPackageName;
+            return this;
+        }
+
+        public Builder setClientNotificationId(int clientNotificationId) {
+            mClientNotificationId = clientNotificationId;
+            return this;
+        }
+
+        public Builder setClientAppThread(@NonNull IApplicationThread clientAppThread) {
+            mClientAppThread = clientAppThread;
+            return this;
+        }
+
+        public Builder setClientInstanceName(@NonNull String clientInstanceName) {
+            mClientInstanceName = clientInstanceName;
+            return this;
+        }
+
+        public Builder setSticky(boolean isSticky) {
+            mSticky = isSticky;
+            return this;
+        }
+
+        public Builder setForegroundServiceTypes(int foregroundServiceTypes) {
+            mForegroundServiceTypes = foregroundServiceTypes;
+            return this;
+        }
+
+        public Builder setDelegationService(@DelegationService int delegationService) {
+            mDelegationService = delegationService;
+            return this;
+        }
+
+        public ForegroundServiceDelegationOptions build() {
+            return new ForegroundServiceDelegationOptions(mClientPid,
+                mClientUid,
+                mClientPackageName,
+                mClientAppThread,
+                mSticky,
+                mClientInstanceName,
+                mForegroundServiceTypes,
+                mDelegationService
+            );
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 68e5a5d..66a8bab 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -73,7 +73,30 @@
 import static com.android.server.am.PlatformCompatCache.CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY;
 import static com.android.server.am.PlatformCompatCache.CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY;
 import static com.android.server.am.PlatformCompatCache.CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME;
+import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
+import static com.android.server.am.ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ;
+import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
+import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ;
+import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ;
+import static com.android.server.am.ProcessList.HOME_APP_ADJ;
+import static com.android.server.am.ProcessList.INVALID_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
+import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
+import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
+import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED;
+import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP;
+import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND;
+import static com.android.server.am.ProcessList.SERVICE_ADJ;
+import static com.android.server.am.ProcessList.SERVICE_B_ADJ;
 import static com.android.server.am.ProcessList.TAG_PROCESS_OBSERVERS;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
+import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 
 import android.annotation.IntDef;
@@ -392,8 +415,8 @@
         });
         mTmpUidRecords = new ActiveUids(service, false);
         mTmpQueue = new ArrayDeque<ProcessRecord>(mConstants.CUR_MAX_CACHED_PROCESSES << 1);
-        mNumSlots = ((ProcessList.CACHED_APP_MAX_ADJ - ProcessList.CACHED_APP_MIN_ADJ + 1) >> 1)
-                / ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+        mNumSlots = ((CACHED_APP_MAX_ADJ - CACHED_APP_MIN_ADJ + 1) >> 1)
+                / CACHED_APP_IMPORTANCE_LEVELS;
     }
 
     void initSettings() {
@@ -579,8 +602,8 @@
         final ProcessStateRecord state = app.mState;
         final boolean wasCached = state.isCached();
         final int oldAdj = state.getCurRawAdj();
-        final int cachedAdj = oldAdj >= ProcessList.CACHED_APP_MIN_ADJ
-                ? oldAdj : ProcessList.UNKNOWN_ADJ;
+        final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ
+                ? oldAdj : UNKNOWN_ADJ;
         final boolean wasBackground = ActivityManager.isProcStateBackground(
                 state.getSetProcState());
         final int oldCap = state.getSetCapability();
@@ -595,7 +618,7 @@
                 SystemClock.uptimeMillis(), oomAdjReason);
         // The 'app' here itself might or might not be in the cycle, for example,
         // the case A <=> B vs. A -> B <=> C; anyway, if we spot a cycle here, re-compute them.
-        if (!success || (wasCached == state.isCached() && oldAdj != ProcessList.INVALID_ADJ
+        if (!success || (wasCached == state.isCached() && oldAdj != INVALID_ADJ
                 && mProcessesInCycle.isEmpty() /* Force re-compute if there is a cycle */
                 && oldCap == state.getCurCapability()
                 && wasBackground == ActivityManager.isProcStateBackground(
@@ -639,7 +662,7 @@
             mAdjSeq--;
             // Update these reachable processes
             updateOomAdjInnerLSP(oomAdjReason, topApp, processes, uids, containsCycle, false);
-        } else if (state.getCurRawAdj() == ProcessList.UNKNOWN_ADJ) {
+        } else if (state.getCurRawAdj() == UNKNOWN_ADJ) {
             // In case the app goes from non-cached to cached but it doesn't have other reachable
             // processes, its adj could be still unknown as of now, assign one.
             processes.add(app);
@@ -877,7 +900,7 @@
             if (state.getAdjSeq() != mAdjSeq) {
                 state.setContainsCycle(false);
                 state.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
-                state.setCurRawAdj(ProcessList.UNKNOWN_ADJ);
+                state.setCurRawAdj(UNKNOWN_ADJ);
                 state.setSetCapability(PROCESS_CAPABILITY_NONE);
                 state.resetCachedInfo();
                 state.setCurBoundByNonBgRestrictedApp(false);
@@ -890,7 +913,7 @@
             if (!app.isKilledByAm() && app.getThread() != null) {
                 state.setProcStateChanged(false);
                 app.mOptRecord.setLastOomAdjChangeReason(oomAdjReason);
-                computeOomAdjLSP(app, ProcessList.UNKNOWN_ADJ, topApp, fullUpdate, now, false,
+                computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, fullUpdate, now, false,
                         computeClients); // It won't enter cycle if not computing clients.
                 // if any app encountered a cycle, we need to perform an additional loop later
                 retryCycles |= state.containsCycle();
@@ -990,11 +1013,11 @@
 
         // First update the OOM adjustment for each of the
         // application processes based on their current state.
-        int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;
-        int nextCachedAdj = curCachedAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);
+        int curCachedAdj = CACHED_APP_MIN_ADJ;
+        int nextCachedAdj = curCachedAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
         int curCachedImpAdj = 0;
-        int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
-        int nextEmptyAdj = curEmptyAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);
+        int curEmptyAdj = CACHED_APP_MIN_ADJ + CACHED_APP_IMPORTANCE_LEVELS;
+        int nextEmptyAdj = curEmptyAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
 
         final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
         final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES
@@ -1032,7 +1055,7 @@
             // If we haven't yet assigned the final cached adj
             // to the process, do that now.
             if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
-                    >= ProcessList.UNKNOWN_ADJ) {
+                    >= UNKNOWN_ADJ) {
                 final ProcessServiceRecord psr = app.mServices;
                 switch (state.getCurProcState()) {
                     case PROCESS_STATE_CACHED_ACTIVITY:
@@ -1050,7 +1073,7 @@
                                 if (connectionImportance > lastCachedGroupImportance) {
                                     lastCachedGroupImportance = connectionImportance;
                                     if (curCachedAdj < nextCachedAdj
-                                            && curCachedAdj < ProcessList.CACHED_APP_MAX_ADJ) {
+                                            && curCachedAdj < CACHED_APP_MAX_ADJ) {
                                         curCachedImpAdj++;
                                     }
                                 }
@@ -1067,9 +1090,9 @@
                             if (stepCached >= cachedFactor) {
                                 stepCached = 0;
                                 curCachedAdj = nextCachedAdj;
-                                nextCachedAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
-                                if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
-                                    nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
+                                nextCachedAdj += CACHED_APP_IMPORTANCE_LEVELS * 2;
+                                if (nextCachedAdj > CACHED_APP_MAX_ADJ) {
+                                    nextCachedAdj = CACHED_APP_MAX_ADJ;
                                 }
                             }
                         }
@@ -1092,9 +1115,9 @@
                             if (stepEmpty >= emptyFactor) {
                                 stepEmpty = 0;
                                 curEmptyAdj = nextEmptyAdj;
-                                nextEmptyAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
-                                if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) {
-                                    nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ;
+                                nextEmptyAdj += CACHED_APP_IMPORTANCE_LEVELS * 2;
+                                if (nextEmptyAdj > CACHED_APP_MAX_ADJ) {
+                                    nextEmptyAdj = CACHED_APP_MAX_ADJ;
                                 }
                             }
                         }
@@ -1118,6 +1141,12 @@
 
     private long mNextNoKillDebugMessageTime;
 
+    private double mLastFreeSwapPercent = 1.00;
+
+    private static double getFreeSwapPercent() {
+        return CachedAppOptimizer.getFreeSwapPercent();
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
             final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason) {
@@ -1142,6 +1171,11 @@
         int numEmpty = 0;
         int numTrimming = 0;
 
+        boolean proactiveKillsEnabled = mConstants.PROACTIVE_KILLS_ENABLED;
+        double lowSwapThresholdPercent = mConstants.LOW_SWAP_THRESHOLD_PERCENT;
+        double freeSwapPercent =  proactiveKillsEnabled ? getFreeSwapPercent() : 1.00;
+        ProcessRecord lruCachedApp = null;
+
         for (int i = numLru - 1; i >= 0; i--) {
             ProcessRecord app = lruList.get(i);
             final ProcessStateRecord state = app.mState;
@@ -1179,6 +1213,8 @@
                                     ApplicationExitInfo.REASON_OTHER,
                                     ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
                                     true);
+                        } else if (proactiveKillsEnabled) {
+                            lruCachedApp = app;
                         }
                         break;
                     case PROCESS_STATE_CACHED_EMPTY:
@@ -1198,6 +1234,8 @@
                                         ApplicationExitInfo.REASON_OTHER,
                                         ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY,
                                         true);
+                            } else if (proactiveKillsEnabled) {
+                                lruCachedApp = app;
                             }
                         }
                         break;
@@ -1229,6 +1267,20 @@
             }
         }
 
+        if (proactiveKillsEnabled                               // Proactive kills enabled?
+                && doKillExcessiveProcesses                     // Should kill excessive processes?
+                && freeSwapPercent < lowSwapThresholdPercent    // Swap below threshold?
+                && lruCachedApp != null                         // If no cached app, let LMKD decide
+                // If swap is non-decreasing, give reclaim a chance to catch up
+                && freeSwapPercent < mLastFreeSwapPercent) {
+            lruCachedApp.killLocked("swap low and too many cached",
+                    ApplicationExitInfo.REASON_OTHER,
+                    ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
+                    true);
+        }
+
+        mLastFreeSwapPercent = freeSwapPercent;
+
         return mService.mAppProfiler.updateLowMemStateLSP(numCached, numEmpty, numTrimming);
     }
 
@@ -1432,8 +1484,8 @@
         @Override
         public void onVisibleActivity() {
             // App has a visible activity; only upgrade adjustment.
-            if (adj > ProcessList.VISIBLE_APP_ADJ) {
-                adj = ProcessList.VISIBLE_APP_ADJ;
+            if (adj > VISIBLE_APP_ADJ) {
+                adj = VISIBLE_APP_ADJ;
                 mState.setAdjType("vis-activity");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to vis-activity: " + app);
@@ -1447,8 +1499,8 @@
                             "Raise procstate to vis-activity (top): " + app);
                 }
             }
-            if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
-                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+            if (schedGroup < SCHED_GROUP_DEFAULT) {
+                schedGroup = SCHED_GROUP_DEFAULT;
             }
             mState.setCached(false);
             mState.setEmpty(false);
@@ -1458,8 +1510,8 @@
 
         @Override
         public void onPausedActivity() {
-            if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
-                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+            if (adj > PERCEPTIBLE_APP_ADJ) {
+                adj = PERCEPTIBLE_APP_ADJ;
                 mState.setAdjType("pause-activity");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to pause-activity: "  + app);
@@ -1473,8 +1525,8 @@
                             "Raise procstate to pause-activity (top): "  + app);
                 }
             }
-            if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
-                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+            if (schedGroup < SCHED_GROUP_DEFAULT) {
+                schedGroup = SCHED_GROUP_DEFAULT;
             }
             mState.setCached(false);
             mState.setEmpty(false);
@@ -1484,8 +1536,8 @@
 
         @Override
         public void onStoppingActivity(boolean finishing) {
-            if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
-                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+            if (adj > PERCEPTIBLE_APP_ADJ) {
+                adj = PERCEPTIBLE_APP_ADJ;
                 mState.setAdjType("stop-activity");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ,
@@ -1550,10 +1602,10 @@
 
         if (app.getThread() == null) {
             state.setAdjSeq(mAdjSeq);
-            state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_BACKGROUND);
+            state.setCurrentSchedulingGroup(SCHED_GROUP_BACKGROUND);
             state.setCurProcState(PROCESS_STATE_CACHED_EMPTY);
-            state.setCurAdj(ProcessList.CACHED_APP_MAX_ADJ);
-            state.setCurRawAdj(ProcessList.CACHED_APP_MAX_ADJ);
+            state.setCurAdj(CACHED_APP_MAX_ADJ);
+            state.setCurRawAdj(CACHED_APP_MAX_ADJ);
             state.setCompletedAdjSeq(state.getAdjSeq());
             state.setCurCapability(PROCESS_CAPABILITY_NONE);
             return false;
@@ -1580,7 +1632,7 @@
         int prevCapability = state.getCurCapability();
         final ProcessServiceRecord psr = app.mServices;
 
-        if (state.getMaxAdj() <= ProcessList.FOREGROUND_APP_ADJ) {
+        if (state.getMaxAdj() <= FOREGROUND_APP_ADJ) {
             // The max adjustment doesn't allow this app to be anything
             // below foreground, so it is not worth doing work for it.
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1590,7 +1642,7 @@
             state.setAdjSeq(mAdjSeq);
             state.setCurRawAdj(state.getMaxAdj());
             state.setHasForegroundActivities(false);
-            state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
+            state.setCurrentSchedulingGroup(SCHED_GROUP_DEFAULT);
             state.setCurCapability(PROCESS_CAPABILITY_ALL);
             state.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT);
             // System processes can do UI, and when they do we want to have
@@ -1600,7 +1652,7 @@
             state.setSystemNoUi(true);
             if (app == topApp) {
                 state.setSystemNoUi(false);
-                state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
+                state.setCurrentSchedulingGroup(SCHED_GROUP_TOP_APP);
                 state.setAdjType("pers-top-activity");
             } else if (state.hasTopUi()) {
                 // sched group/proc state adjustment is below
@@ -1614,11 +1666,11 @@
                         || state.isRunningRemoteAnimation()) {
                     // screen on or animating, promote UI
                     state.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
-                    state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
+                    state.setCurrentSchedulingGroup(SCHED_GROUP_TOP_APP);
                 } else {
                     // screen off, restrict UI scheduling
                     state.setCurProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
-                    state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED);
+                    state.setCurrentSchedulingGroup(SCHED_GROUP_RESTRICTED);
                 }
             }
             state.setCurRawProcState(state.getCurProcState());
@@ -1643,14 +1695,14 @@
         boolean hasVisibleActivities = false;
         if (app == topApp && PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP) {
             // The last app on the list is the foreground app.
-            adj = ProcessList.FOREGROUND_APP_ADJ;
+            adj = FOREGROUND_APP_ADJ;
             if (mService.mAtmInternal.useTopSchedGroupForTopProcess()) {
-                schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
+                schedGroup = SCHED_GROUP_TOP_APP;
                 state.setAdjType("top-activity");
             } else {
                 // Demote the scheduling group to avoid CPU contention if there is another more
                 // important process which also uses top-app, such as if SystemUI is animating.
-                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                schedGroup = SCHED_GROUP_DEFAULT;
                 state.setAdjType("intermediate-top-activity");
             }
             foregroundActivities = true;
@@ -1660,8 +1712,8 @@
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top: " + app);
             }
         } else if (state.isRunningRemoteAnimation()) {
-            adj = ProcessList.VISIBLE_APP_ADJ;
-            schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
+            adj = VISIBLE_APP_ADJ;
+            schedGroup = SCHED_GROUP_TOP_APP;
             state.setAdjType("running-remote-anim");
             procState = PROCESS_STATE_CUR_TOP;
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1669,8 +1721,8 @@
             }
         } else if (app.getActiveInstrumentation() != null) {
             // Don't want to kill running instrumentation.
-            adj = ProcessList.FOREGROUND_APP_ADJ;
-            schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+            adj = FOREGROUND_APP_ADJ;
+            schedGroup = SCHED_GROUP_DEFAULT;
             state.setAdjType("instrumentation");
             procState = PROCESS_STATE_FOREGROUND_SERVICE;
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1681,7 +1733,7 @@
             // counts as being in the foreground for OOM killer purposes.
             // It's placed in a sched group based on the nature of the
             // broadcast as reflected by which queue it's active in.
-            adj = ProcessList.FOREGROUND_APP_ADJ;
+            adj = FOREGROUND_APP_ADJ;
             schedGroup = mTmpSchedGroup[0];
             state.setAdjType("broadcast");
             procState = ActivityManager.PROCESS_STATE_RECEIVER;
@@ -1691,17 +1743,17 @@
         } else if (psr.numberOfExecutingServices() > 0) {
             // An app that is currently executing a service callback also
             // counts as being in the foreground.
-            adj = ProcessList.FOREGROUND_APP_ADJ;
+            adj = FOREGROUND_APP_ADJ;
             schedGroup = psr.shouldExecServicesFg()
-                    ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
+                    ? SCHED_GROUP_DEFAULT : SCHED_GROUP_BACKGROUND;
             state.setAdjType("exec-service");
             procState = PROCESS_STATE_SERVICE;
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making exec-service: " + app);
             }
         } else if (app == topApp) {
-            adj = ProcessList.FOREGROUND_APP_ADJ;
-            schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+            adj = FOREGROUND_APP_ADJ;
+            schedGroup = SCHED_GROUP_BACKGROUND;
             state.setAdjType("top-sleeping");
             foregroundActivities = true;
             procState = PROCESS_STATE_CUR_TOP;
@@ -1710,7 +1762,7 @@
             }
         } else {
             // As far as we know the process is empty.  We may change our mind later.
-            schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+            schedGroup = SCHED_GROUP_BACKGROUND;
             // At this point we don't actually know the adjustment.  Use the cached adj
             // value that the caller wants us to.
             adj = cachedAdj;
@@ -1746,28 +1798,63 @@
             }
         }
 
-        if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
+        int capabilityFromFGS = 0; // capability from foreground service.
+
+        // Adjust for FGS or "has-overlay-ui".
+        if (adj > PERCEPTIBLE_APP_ADJ
                 || procState > PROCESS_STATE_FOREGROUND_SERVICE) {
-            if (psr.hasForegroundServices()) {
-                // The user is aware of this app, so make it visible.
-                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                procState = PROCESS_STATE_FOREGROUND_SERVICE;
-                state.setAdjType("fg-service");
-                state.setCached(false);
-                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + state.getAdjType() + ": "
-                            + app + " ");
-                }
+            String adjType = null;
+            int newAdj = 0;
+            int newProcState = 0;
+
+            if (psr.hasForegroundServices() && psr.hasNonShortForegroundServices()) {
+                // For regular (non-short) FGS.
+                adjType = "fg-service";
+                newAdj = PERCEPTIBLE_APP_ADJ;
+                newProcState = PROCESS_STATE_FOREGROUND_SERVICE;
+
             } else if (state.hasOverlayUi()) {
-                // The process is display an overlay UI.
-                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                procState = PROCESS_STATE_IMPORTANT_FOREGROUND;
+                adjType = "has-overlay-ui";
+                newAdj = PERCEPTIBLE_APP_ADJ;
+                newProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
+
+            } else if (psr.hasForegroundServices()) {
+                // If we get here, hasNonShortForegroundServices() must be false.
+
+                // TODO(short-service): Proactively run OomAjudster when the grace period finish.
+                if (psr.areAllShortForegroundServicesProcstateTimedOut(now)) {
+                    // All the short-FGSes within this process are timed out. Don't promote to FGS.
+                    // TODO(short-service): Should we set some unique oom-adj to make it detectable,
+                    // in a long trace?
+                } else {
+                    // For short FGS.
+                    adjType = "fg-service-short";
+                    // We use MEDIUM_APP_ADJ + 1 so we can tell apart EJ
+                    // (which uses MEDIUM_APP_ADJ + 1)
+                    // from short-FGS.
+                    // (We use +1 and +2, not +0 and +1, to be consistent with the following
+                    // RECENT_FOREGROUND_APP_ADJ tweak)
+                    newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 1;
+
+                    // Short-FGS gets a below-BFGS procstate, so it can't start another FGS from it.
+                    newProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
+
+                    // Same as EJ, we explicitly grant network access to short FGS,
+                    // even when battery saver or data saver is enabled.
+                    capabilityFromFGS |= PROCESS_CAPABILITY_NETWORK;
+                }
+            }
+
+            if (adjType != null) {
+                adj = newAdj;
+                procState = newProcState;
+                state.setAdjType(adjType);
                 state.setCached(false);
-                state.setAdjType("has-overlay-ui");
-                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                schedGroup = SCHED_GROUP_DEFAULT;
+
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to overlay ui: " + app);
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType + ": "
+                            + app + " ");
                 }
             }
         }
@@ -1775,11 +1862,18 @@
         // If the app was recently in the foreground and moved to a foreground service status,
         // allow it to get a higher rank in memory for some time, compared to other foreground
         // services so that it can finish performing any persistence/processing of in-memory state.
-        if (psr.hasForegroundServices() && adj > ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ
+        if (psr.hasForegroundServices() && adj > PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ
                 && (state.getLastTopTime() + mConstants.TOP_TO_FGS_GRACE_DURATION > now
                 || state.getSetProcState() <= PROCESS_STATE_TOP)) {
-            adj = ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
-            state.setAdjType("fg-service-act");
+            if (psr.hasNonShortForegroundServices()) {
+                adj = PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
+                state.setAdjType("fg-service-act");
+            } else {
+                // For short-service FGS, we +1 the value, so we'll be able to detect it in
+                // various dashboards.
+                adj = PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 1;
+                state.setAdjType("fg-service-short-act");
+            }
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to recent fg: " + app);
             }
@@ -1790,11 +1884,13 @@
         // foreground services so that it can finish performing any persistence/processing of
         // in-memory state.
         if (psr.hasTopStartedAlmostPerceptibleServices()
-                && adj > ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ
+                && (adj > PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2)
                 && (state.getLastTopTime()
                         + mConstants.TOP_TO_ALMOST_PERCEPTIBLE_GRACE_DURATION > now
                 || state.getSetProcState() <= PROCESS_STATE_TOP)) {
-            adj = ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
+            // For EJ, we +2 the value, so we'll be able to detect it in
+            // various dashboards.
+            adj = PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2;
             // This shall henceforth be called the "EJ" exemption, despite utilizing the
             // ALMOST_PERCEPTIBLE flag to work.
             state.setAdjType("top-ej-act");
@@ -1803,18 +1899,18 @@
             }
         }
 
-        if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
+        if (adj > PERCEPTIBLE_APP_ADJ
                 || procState > PROCESS_STATE_TRANSIENT_BACKGROUND) {
             if (state.getForcingToImportant() != null) {
                 // This is currently used for toasts...  they are not interactive, and
                 // we don't want them to cause the app to become fully foreground (and
                 // thus out of background check), so we yes the best background level we can.
-                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+                adj = PERCEPTIBLE_APP_ADJ;
                 procState = PROCESS_STATE_TRANSIENT_BACKGROUND;
                 state.setCached(false);
                 state.setAdjType("force-imp");
                 state.setAdjSource(state.getForcingToImportant());
-                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                schedGroup = SCHED_GROUP_DEFAULT;
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to force imp: " + app);
                 }
@@ -1822,10 +1918,10 @@
         }
 
         if (state.getCachedIsHeavyWeight()) {
-            if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) {
+            if (adj > HEAVY_WEIGHT_APP_ADJ) {
                 // We don't want to kill the current heavy-weight process.
-                adj = ProcessList.HEAVY_WEIGHT_APP_ADJ;
-                schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+                adj = HEAVY_WEIGHT_APP_ADJ;
+                schedGroup = SCHED_GROUP_BACKGROUND;
                 state.setCached(false);
                 state.setAdjType("heavy");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1842,11 +1938,11 @@
         }
 
         if (state.getCachedIsHomeProcess()) {
-            if (adj > ProcessList.HOME_APP_ADJ) {
+            if (adj > HOME_APP_ADJ) {
                 // This process is hosting what we currently consider to be the
                 // home app, so we don't want to let it go into the background.
-                adj = ProcessList.HOME_APP_ADJ;
-                schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+                adj = HOME_APP_ADJ;
+                schedGroup = SCHED_GROUP_BACKGROUND;
                 state.setCached(false);
                 state.setAdjType("home");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1863,12 +1959,12 @@
         }
 
         if (state.getCachedIsPreviousProcess() && state.getCachedHasActivities()) {
-            if (adj > ProcessList.PREVIOUS_APP_ADJ) {
+            if (adj > PREVIOUS_APP_ADJ) {
                 // This was the previous process that showed UI to the user.
                 // We want to try to keep it around more aggressively, to give
                 // a good experience around switching between two apps.
-                adj = ProcessList.PREVIOUS_APP_ADJ;
-                schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+                adj = PREVIOUS_APP_ADJ;
+                schedGroup = SCHED_GROUP_BACKGROUND;
                 state.setCached(false);
                 state.setAdjType("previous");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1906,9 +2002,9 @@
         final BackupRecord backupTarget = mService.mBackupTargets.get(app.userId);
         if (backupTarget != null && app == backupTarget.app) {
             // If possible we want to avoid killing apps while they're being backed up
-            if (adj > ProcessList.BACKUP_APP_ADJ) {
+            if (adj > BACKUP_APP_ADJ) {
                 if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app);
-                adj = ProcessList.BACKUP_APP_ADJ;
+                adj = BACKUP_APP_ADJ;
                 if (procState > PROCESS_STATE_TRANSIENT_BACKGROUND) {
                     procState = PROCESS_STATE_TRANSIENT_BACKGROUND;
                 }
@@ -1927,12 +2023,11 @@
             }
         }
 
-        int capabilityFromFGS = 0; // capability from foreground service.
         boolean boundByNonBgRestricted = state.isCurBoundByNonBgRestrictedApp();
         boolean scheduleLikeTopApp = false;
         for (int is = psr.numberOfRunningServices() - 1;
-                is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
-                        || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+                is >= 0 && (adj > FOREGROUND_APP_ADJ
+                        || schedGroup == SCHED_GROUP_BACKGROUND
                         || procState > PROCESS_STATE_TOP);
                 is--) {
             ServiceRecord s = psr.getRunningServiceAt(is);
@@ -1951,7 +2046,7 @@
                     // go to the LRU list because it may be pretty heavy with
                     // UI stuff.  We'll tag it with a label just to help
                     // debug and understand what is going on.
-                    if (adj > ProcessList.SERVICE_ADJ) {
+                    if (adj > SERVICE_ADJ) {
                         state.setAdjType("cch-started-ui-services");
                     }
                 } else {
@@ -1960,8 +2055,8 @@
                         // This service has seen some activity within
                         // recent memory, so we will keep its process ahead
                         // of the background processes.
-                        if (adj > ProcessList.SERVICE_ADJ) {
-                            adj = ProcessList.SERVICE_ADJ;
+                        if (adj > SERVICE_ADJ) {
+                            adj = SERVICE_ADJ;
                             state.setAdjType("started-services");
                             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                                 reportOomAdjMessageLocked(TAG_OOM_ADJ,
@@ -1973,12 +2068,14 @@
                     // If we have let the service slide into the background
                     // state, still have some text describing what it is doing
                     // even though the service no longer has an impact.
-                    if (adj > ProcessList.SERVICE_ADJ) {
+                    if (adj > SERVICE_ADJ) {
                         state.setAdjType("cch-started-services");
                     }
                 }
             }
 
+            // TODO(short-service): While-in-user permissions. Do we need any change here for
+            // short-FGS? (Likely not)
             if (s.isForeground) {
                 final int fgsType = s.foregroundServiceType;
                 if (s.mAllowWhileInUsePermissionInFgs) {
@@ -2004,14 +2101,14 @@
 
             ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = s.getConnections();
             for (int conni = serviceConnections.size() - 1;
-                    conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
-                            || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+                    conni >= 0 && (adj > FOREGROUND_APP_ADJ
+                            || schedGroup == SCHED_GROUP_BACKGROUND
                             || procState > PROCESS_STATE_TOP);
                     conni--) {
                 ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni);
                 for (int i = 0;
-                        i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ
-                                || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+                        i < clist.size() && (adj > FOREGROUND_APP_ADJ
+                                || schedGroup == SCHED_GROUP_BACKGROUND
                                 || procState > PROCESS_STATE_TOP);
                         i++) {
                     // XXX should compute this based on the max of
@@ -2061,6 +2158,7 @@
                         // in this case unless they explicitly request it.
                         if ((cstate.getCurCapability() & PROCESS_CAPABILITY_NETWORK) != 0) {
                             if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+                                // This is used to grant network access to Expedited Jobs.
                                 if ((cr.flags & Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS)
                                         != 0) {
                                     capability |= PROCESS_CAPABILITY_NETWORK;
@@ -2083,7 +2181,7 @@
                         String adjType = null;
                         if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
                             // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen.
-                            if (clientAdj < ProcessList.CACHED_APP_MIN_ADJ) {
+                            if (clientAdj < CACHED_APP_MIN_ADJ) {
                                 app.mOptRecord.setShouldNotFreeze(true);
                             }
                             // Not doing bind OOM management, so treat
@@ -2123,46 +2221,50 @@
                             // list to be killed and restarted if needed for
                             // memory.
                             if (state.hasShownUi() && !state.getCachedIsHomeProcess()
-                                    && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
-                                if (adj >= ProcessList.CACHED_APP_MIN_ADJ) {
+                                    && clientAdj > PERCEPTIBLE_APP_ADJ) {
+                                if (adj >= CACHED_APP_MIN_ADJ) {
                                     adjType = "cch-bound-ui-services";
                                 }
                             } else {
                                 int newAdj;
+                                int lbAdj = VISIBLE_APP_ADJ; // lower bound of adj.
                                 if ((cr.flags&(Context.BIND_ABOVE_CLIENT
                                         |Context.BIND_IMPORTANT)) != 0) {
-                                    if (clientAdj >= ProcessList.PERSISTENT_SERVICE_ADJ) {
+                                    if (clientAdj >= PERSISTENT_SERVICE_ADJ) {
                                         newAdj = clientAdj;
                                     } else {
                                         // make this service persistent
-                                        newAdj = ProcessList.PERSISTENT_SERVICE_ADJ;
-                                        schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                                        newAdj = PERSISTENT_SERVICE_ADJ;
+                                        schedGroup = SCHED_GROUP_DEFAULT;
                                         procState = ActivityManager.PROCESS_STATE_PERSISTENT;
                                         cr.trackProcState(procState, mAdjSeq);
                                         trackedProcState = true;
                                     }
                                 } else if ((cr.flags & Context.BIND_NOT_PERCEPTIBLE) != 0
-                                        && clientAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
-                                        && adj >= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
-                                    newAdj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
+                                        && clientAdj <= PERCEPTIBLE_APP_ADJ
+                                        && adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) {
+                                    newAdj = PERCEPTIBLE_LOW_APP_ADJ;
                                 } else if ((cr.flags & Context.BIND_ALMOST_PERCEPTIBLE) != 0
-                                        && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
-                                        && adj >= ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ) {
-                                    newAdj = ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ;
+                                        && clientAdj < PERCEPTIBLE_APP_ADJ
+                                        && adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) {
+                                    // This is for expedited jobs.
+                                    // We use MEDIUM_APP_ADJ + 2 here, so we can tell apart
+                                    // EJ and short-FGS.
+                                    newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 2;
                                 } else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0
-                                        && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
-                                        && adj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
-                                    newAdj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                                } else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
+                                        && clientAdj < PERCEPTIBLE_APP_ADJ
+                                        && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
+                                    newAdj = PERCEPTIBLE_APP_ADJ;
+                                } else if (clientAdj >= PERCEPTIBLE_APP_ADJ) {
                                     newAdj = clientAdj;
                                 } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)
-                                        && clientAdj <= ProcessList.VISIBLE_APP_ADJ
-                                        && adj > ProcessList.VISIBLE_APP_ADJ) {
-                                    newAdj = ProcessList.VISIBLE_APP_ADJ;
+                                        && clientAdj <= VISIBLE_APP_ADJ
+                                        && adj > VISIBLE_APP_ADJ) {
+                                    newAdj = VISIBLE_APP_ADJ;
                                 } else {
-                                    if (adj > ProcessList.VISIBLE_APP_ADJ) {
+                                    if (adj > VISIBLE_APP_ADJ) {
                                         // TODO: Is this too limiting for apps bound from TOP?
-                                        newAdj = Math.max(clientAdj, ProcessList.VISIBLE_APP_ADJ);
+                                        newAdj = Math.max(clientAdj, lbAdj);
                                     } else {
                                         newAdj = adj;
                                     }
@@ -2187,7 +2289,7 @@
                                 if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
                                     schedGroup = curSchedGroup;
                                 } else {
-                                    schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                                    schedGroup = SCHED_GROUP_DEFAULT;
                                 }
                             }
                             if (clientProcState < PROCESS_STATE_TOP) {
@@ -2240,10 +2342,10 @@
                             }
                         }
 
-                        if (schedGroup < ProcessList.SCHED_GROUP_TOP_APP
+                        if (schedGroup < SCHED_GROUP_TOP_APP
                                 && (cr.flags & Context.BIND_SCHEDULE_LIKE_TOP_APP) != 0
                                 && clientIsSystem) {
-                            schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
+                            schedGroup = SCHED_GROUP_TOP_APP;
                             scheduleLikeTopApp = true;
                         }
 
@@ -2286,7 +2388,7 @@
                         // pings a frozen process. Accordingly, any cached app that is
                         // bound by an unfrozen app via a WPRI binding has to remain
                         // unfrozen.
-                        if (clientAdj < ProcessList.CACHED_APP_MIN_ADJ) {
+                        if (clientAdj < CACHED_APP_MIN_ADJ) {
                             app.mOptRecord.setShouldNotFreeze(true);
                         }
                     }
@@ -2295,15 +2397,15 @@
                     }
                     final ActivityServiceConnectionsHolder a = cr.activity;
                     if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
-                        if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ
+                        if (a != null && adj > FOREGROUND_APP_ADJ
                                 && a.isActivityVisible()) {
-                            adj = ProcessList.FOREGROUND_APP_ADJ;
+                            adj = FOREGROUND_APP_ADJ;
                             state.setCurRawAdj(adj);
                             if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
                                 if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
-                                    schedGroup = ProcessList.SCHED_GROUP_TOP_APP_BOUND;
+                                    schedGroup = SCHED_GROUP_TOP_APP_BOUND;
                                 } else {
-                                    schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                                    schedGroup = SCHED_GROUP_DEFAULT;
                                 }
                             }
                             state.setCached(false);
@@ -2325,14 +2427,14 @@
 
         final ProcessProviderRecord ppr = app.mProviders;
         for (int provi = ppr.numberOfProviders() - 1;
-                provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
-                        || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+                provi >= 0 && (adj > FOREGROUND_APP_ADJ
+                        || schedGroup == SCHED_GROUP_BACKGROUND
                         || procState > PROCESS_STATE_TOP);
                 provi--) {
             ContentProviderRecord cpr = ppr.getProviderAt(provi);
             for (int i = cpr.connections.size() - 1;
-                    i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
-                            || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+                    i >= 0 && (adj > FOREGROUND_APP_ADJ
+                            || schedGroup == SCHED_GROUP_BACKGROUND
                             || procState > PROCESS_STATE_TOP);
                     i--) {
                 ContentProviderConnection conn = cpr.connections.get(i);
@@ -2374,11 +2476,10 @@
                 String adjType = null;
                 if (adj > clientAdj) {
                     if (state.hasShownUi() && !state.getCachedIsHomeProcess()
-                            && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+                            && clientAdj > PERCEPTIBLE_APP_ADJ) {
                         adjType = "cch-ui-provider";
                     } else {
-                        adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ
-                                ? clientAdj : ProcessList.FOREGROUND_APP_ADJ;
+                        adj = Math.max(clientAdj, FOREGROUND_APP_ADJ);
                         state.setCurRawAdj(adj);
                         adjType = "provider";
                     }
@@ -2402,7 +2503,7 @@
                     state.setCurRawProcState(procState);
                 }
                 if (cstate.getCurrentSchedulingGroup() > schedGroup) {
-                    schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                    schedGroup = SCHED_GROUP_DEFAULT;
                 }
                 if (adjType != null) {
                     state.setAdjType(adjType);
@@ -2423,10 +2524,10 @@
             // dependencies, ensure that its adjustment is at least
             // FOREGROUND_APP_ADJ.
             if (cpr.hasExternalProcessHandles()) {
-                if (adj > ProcessList.FOREGROUND_APP_ADJ) {
-                    adj = ProcessList.FOREGROUND_APP_ADJ;
+                if (adj > FOREGROUND_APP_ADJ) {
+                    adj = FOREGROUND_APP_ADJ;
                     state.setCurRawAdj(adj);
-                    schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                    schedGroup = SCHED_GROUP_DEFAULT;
                     state.setCached(false);
                     state.setAdjType("ext-provider");
                     state.setAdjTarget(cpr.name);
@@ -2448,9 +2549,9 @@
 
         if (ppr.getLastProviderTime() > 0
                 && (ppr.getLastProviderTime() + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
-            if (adj > ProcessList.PREVIOUS_APP_ADJ) {
-                adj = ProcessList.PREVIOUS_APP_ADJ;
-                schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+            if (adj > PREVIOUS_APP_ADJ) {
+                adj = PREVIOUS_APP_ADJ;
+                schedGroup = SCHED_GROUP_BACKGROUND;
                 state.setCached(false);
                 state.setAdjType("recent-provider");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -2481,7 +2582,7 @@
             }
         }
 
-        if (adj == ProcessList.SERVICE_ADJ) {
+        if (adj == SERVICE_ADJ) {
             if (doingAll && !cycleReEval) {
                 state.setServiceB(mNewNumAServiceProcs > (mNumServiceProcs / 3));
                 mNewNumServiceProcs++;
@@ -2505,7 +2606,7 @@
                 }
             }
             if (state.isServiceB()) {
-                adj = ProcessList.SERVICE_B_ADJ;
+                adj = SERVICE_B_ADJ;
             }
         }
 
@@ -2513,8 +2614,8 @@
 
         if (adj > state.getMaxAdj()) {
             adj = state.getMaxAdj();
-            if (adj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
-                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+            if (adj <= PERCEPTIBLE_LOW_APP_ADJ) {
+                schedGroup = SCHED_GROUP_DEFAULT;
             }
         }
 
@@ -2523,8 +2624,8 @@
         if (procState >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE
                 && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
                 && !scheduleLikeTopApp) {
-            if (schedGroup > ProcessList.SCHED_GROUP_RESTRICTED) {
-                schedGroup = ProcessList.SCHED_GROUP_RESTRICTED;
+            if (schedGroup > SCHED_GROUP_RESTRICTED) {
+                schedGroup = SCHED_GROUP_RESTRICTED;
             }
         }
 
@@ -2656,7 +2757,7 @@
                 mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app);
             } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE) {
                 // See if we can compact persistent and bfgs services now that screen is off
-                if (state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ
+                if (state.getSetAdj() < FOREGROUND_APP_ADJ
                         && !state.isRunningRemoteAnimation()
                         // Because these can fire independent of oom_adj/procstate changes, we need
                         // to throttle the actual dispatch of these requests in addition to the
@@ -2685,7 +2786,7 @@
             if (uidRec != null) {
                 uidRec.noteProcAdjChanged();
             }
-            state.setVerifiedAdj(ProcessList.INVALID_ADJ);
+            state.setVerifiedAdj(INVALID_ADJ);
         }
 
         final int curSchedGroup = state.getCurrentSchedulingGroup();
@@ -2705,14 +2806,14 @@
             } else {
                 int processGroup;
                 switch (curSchedGroup) {
-                    case ProcessList.SCHED_GROUP_BACKGROUND:
+                    case SCHED_GROUP_BACKGROUND:
                         processGroup = THREAD_GROUP_BACKGROUND;
                         break;
-                    case ProcessList.SCHED_GROUP_TOP_APP:
-                    case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
+                    case SCHED_GROUP_TOP_APP:
+                    case SCHED_GROUP_TOP_APP_BOUND:
                         processGroup = THREAD_GROUP_TOP_APP;
                         break;
-                    case ProcessList.SCHED_GROUP_RESTRICTED:
+                    case SCHED_GROUP_RESTRICTED:
                         processGroup = THREAD_GROUP_RESTRICTED;
                         break;
                     default:
@@ -2723,9 +2824,9 @@
                         0 /* unused */, app.getPid(), processGroup, app.processName));
                 try {
                     final int renderThreadTid = app.getRenderThreadTid();
-                    if (curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
+                    if (curSchedGroup == SCHED_GROUP_TOP_APP) {
                         // do nothing if we already switched to RT
-                        if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
+                        if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
                             app.getWindowProcessController().onTopProcChanged();
                             if (mService.mUseFifoUiScheduling) {
                                 // Switch UI pipeline for app to SCHED_FIFO
@@ -2756,8 +2857,8 @@
                                 }
                             }
                         }
-                    } else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
-                            curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
+                    } else if (oldSchedGroup == SCHED_GROUP_TOP_APP
+                            && curSchedGroup != SCHED_GROUP_TOP_APP) {
                         app.getWindowProcessController().onTopProcChanged();
                         if (mService.mUseFifoUiScheduling) {
                             try {
@@ -2943,18 +3044,18 @@
 
     @GuardedBy({"mService", "mProcLock"})
     void setAttachingSchedGroupLSP(ProcessRecord app) {
-        int initialSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+        int initialSchedGroup = SCHED_GROUP_DEFAULT;
         final ProcessStateRecord state = app.mState;
         // If the process has been marked as foreground, it is starting as the top app (with
         // Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread.
         if (state.hasForegroundActivities()) {
             try {
                 // The priority must be the same as how does {@link #applyOomAdjLSP} set for
-                // {@link ProcessList.SCHED_GROUP_TOP_APP}. We don't check render thread because it
+                // {@link SCHED_GROUP_TOP_APP}. We don't check render thread because it
                 // is not ready when attaching.
                 app.getWindowProcessController().onTopProcChanged();
                 setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
-                initialSchedGroup = ProcessList.SCHED_GROUP_TOP_APP;
+                initialSchedGroup = SCHED_GROUP_TOP_APP;
             } catch (Exception e) {
                 Slog.w(TAG, "Failed to pre-set top priority to " + app + " " + e);
             }
@@ -3175,10 +3276,10 @@
 
         final ProcessStateRecord state = app.mState;
         // Use current adjustment when freezing, set adjustment when unfreezing.
-        if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ && !opt.isFrozen()
+        if (state.getCurAdj() >= CACHED_APP_MIN_ADJ && !opt.isFrozen()
                 && !opt.shouldNotFreeze()) {
             mCachedAppOptimizer.freezeAppAsyncLSP(app);
-        } else if (state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
+        } else if (state.getSetAdj() < CACHED_APP_MIN_ADJ) {
             mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason);
         }
     }
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 740efbc..fed0b11 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;
@@ -398,11 +396,16 @@
                 resolvedType = key.requestResolvedType;
             }
 
-            // Apply any launch flags from the ActivityOptions. This is to ensure that the caller
-            // can specify a consistent launch mode even if the PendingIntent is immutable
+            // Apply any launch flags from the ActivityOptions. This is used only by SystemUI
+            // to ensure that we can launch the pending intent with a consistent launch mode even
+            // if the provided PendingIntent is immutable (ie. to force an activity to launch into
+            // a new task, or to launch multiple instances if supported by the app)
             final ActivityOptions opts = ActivityOptions.fromBundle(options);
             if (opts != null) {
-                finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
+                // TODO(b/254490217): Move this check into SafeActivityOptions
+                if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) {
+                    finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
+                }
             }
 
             // Extract options before clearing calling identity
@@ -441,17 +444,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 +484,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/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index fb41a39..24cc533 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -73,6 +73,12 @@
     private boolean mFrozen;
 
     /**
+     * Set to false after the process has been frozen.
+     * Set to true after we have collected PSS for the frozen process.
+     */
+    private boolean mHasCollectedFrozenPSS;
+
+    /**
      * An override on the freeze state is in progress.
      */
     @GuardedBy("mProcLock")
@@ -188,6 +194,25 @@
         mFrozen = frozen;
     }
 
+    boolean skipPSSCollectionBecauseFrozen() {
+        boolean collected = mHasCollectedFrozenPSS;
+
+        // This check is racy but it isn't critical to PSS collection that we have the most up to
+        // date idea of whether a task is frozen.
+        if (!mFrozen) {
+            // not frozen == always ask to collect PSS
+            return false;
+        }
+
+        // We don't want to count PSS for a frozen process more than once.
+        mHasCollectedFrozenPSS = true;
+        return collected;
+    }
+
+    void setHasCollectedFrozenPSS(boolean collected) {
+        mHasCollectedFrozenPSS = collected;
+    }
+
     @GuardedBy("mProcLock")
     boolean hasFreezerOverride() {
         return mFreezerOverride;
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/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 42bfc4c..4d559b0 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1693,7 +1693,8 @@
                             app.info.packageName);
                     externalStorageAccess = storageManagerInternal.hasExternalStorageAccess(uid,
                             app.info.packageName);
-                    if (pm.checkPermission(Manifest.permission.INSTALL_PACKAGES,
+                    if (mService.isAppFreezerExemptInstPkg()
+                            && pm.checkPermission(Manifest.permission.INSTALL_PACKAGES,
                             app.info.packageName, userId)
                             == PackageManager.PERMISSION_GRANTED) {
                         Slog.i(TAG, app.info.packageName + " is exempt from freezer");
@@ -2507,7 +2508,7 @@
     }
 
     @GuardedBy("mService")
-    private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
+    String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
         StringBuilder sb = null;
         if (app.isKilledByAm()) {
             if (sb == null) sb = new StringBuilder();
@@ -4204,7 +4205,7 @@
                     total - mLruProcessServiceStart);
             writeProcessOomListToProto(proto,
                     ActivityManagerServiceDumpProcessesProto.LruProcesses.LIST, mService,
-                    mLruProcesses, false, dumpPackage);
+                    mLruProcesses, true, dumpPackage);
             proto.end(lruToken);
         }
 
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 0a8c640..4706c268 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -200,6 +200,11 @@
     private volatile long mStartElapsedTime;
 
     /**
+     * When the process was sent the bindApplication request
+     */
+    private volatile long mBindApplicationTime;
+
+    /**
      * This will be same as {@link #uid} usually except for some apps used during factory testing.
      */
     private volatile int mStartUid;
@@ -739,6 +744,10 @@
         return mStartElapsedTime;
     }
 
+    long getBindApplicationTime() {
+        return mBindApplicationTime;
+    }
+
     int getStartUid() {
         return mStartUid;
     }
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 67eb675..df442e8 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -21,6 +21,7 @@
 
 import android.app.ActivityManager;
 import android.content.Context;
+import android.content.pm.ServiceInfo;
 import android.os.IBinder;
 import android.os.SystemClock;
 import android.util.ArrayMap;
@@ -78,11 +79,20 @@
     private int mConnectionImportance;
 
     /**
-     * Type of foreground service, if there is a foreground service.
+     * The OR'ed foreground service types that are running on this process.
+     * Note, because TYPE_NONE (==0) is also a valid type for pre-U apps, this field doesn't tell
+     * if the process has any TYPE_NONE FGS or not, but {@link #mHasTypeNoneFgs} will be set
+     * in that case.
      */
     private int mFgServiceTypes;
 
     /**
+     * Whether the process has any foreground services of TYPE_NONE running.
+     * @see #mFgServiceTypes
+     */
+    private boolean mHasTypeNoneFgs;
+
+    /**
      * Last reported foreground service types.
      */
     private int mRepFgServiceTypes;
@@ -145,9 +155,18 @@
         return mHasClientActivities;
     }
 
-    void setHasForegroundServices(boolean hasForegroundServices, int fgServiceTypes) {
+    void setHasForegroundServices(boolean hasForegroundServices, int fgServiceTypes,
+            boolean hasTypeNoneFgs) {
+        // hasForegroundServices should be the same as "either it has any FGS types, or none types".
+        // We still take this as a parameter because it's used in the callsite...
+        if (ActivityManagerDebugConfig.DEBUG_SERVICE
+                && hasForegroundServices != ((fgServiceTypes != 0) || hasTypeNoneFgs)) {
+            throw new IllegalStateException("hasForegroundServices mismatch");
+        }
+
         mHasForegroundServices = hasForegroundServices;
         mFgServiceTypes = fgServiceTypes;
+        mHasTypeNoneFgs = hasTypeNoneFgs;
         mApp.getWindowProcessController().setHasForegroundServices(hasForegroundServices);
         if (hasForegroundServices) {
             mApp.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE);
@@ -156,6 +175,9 @@
         }
     }
 
+    /**
+     * @return true if this process has any foreground services (even timed-out short-FGS)
+     */
     boolean hasForegroundServices() {
         return mHasForegroundServices;
     }
@@ -168,10 +190,70 @@
         return mRepHasForegroundServices;
     }
 
-    int getForegroundServiceTypes() {
+    /**
+     * Returns the FGS typps, but it doesn't tell if the types include "NONE" or not, so
+     * do not use it outside of this class.
+     */
+    private int getForegroundServiceTypes() {
         return mHasForegroundServices ? mFgServiceTypes : 0;
     }
 
+    boolean areForegroundServiceTypesSame(@ServiceInfo.ForegroundServiceType int types,
+            boolean hasTypeNoneFgs) {
+        return ((getForegroundServiceTypes() & types) == types)
+                && (mHasTypeNoneFgs == hasTypeNoneFgs);
+    }
+
+    /**
+     * @return true if the fgs types includes any of the given types.
+     * (wouldn't work for TYPE_NONE, which is 0)
+     */
+    boolean containsAnyForegroundServiceTypes(@ServiceInfo.ForegroundServiceType int types) {
+        return (getForegroundServiceTypes() & types) != 0;
+    }
+
+    /**
+     * @return true if the process has any FGS that are _not_ a "short" FGS.
+     */
+    boolean hasNonShortForegroundServices() {
+        if (!mHasForegroundServices) {
+            return false; // Process has no FGS running.
+        }
+        // Does the process has any FGS of TYPE_NONE?
+        if (mHasTypeNoneFgs) {
+            return true;
+        }
+        // If not, we can just check mFgServiceTypes.
+        return mFgServiceTypes != ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+    }
+
+    /**
+     * @return if this process:
+     * - has at least one short-FGS
+     * - has no other types of FGS
+     * - and all the short-FGSes are procstate-timed out.
+     */
+    boolean areAllShortForegroundServicesProcstateTimedOut(long nowUptime) {
+        if (!mHasForegroundServices) { // Process has no FGS?
+            return false;
+        }
+        if (hasNonShortForegroundServices()) {  // Any non-short FGS running?
+            return false;
+        }
+        // Now we need to look at all short-FGS within the process and see if all of them are
+        // procstate-timed-out or not.
+        for (int i = mServices.size() - 1; i >= 0; i--) {
+            final ServiceRecord sr = mServices.valueAt(i);
+            if (!sr.isShortFgs() || !sr.hasShortFgsInfo()) {
+                continue;
+            }
+            if (sr.getShortFgsInfo().getProcStateDemoteTime() >= nowUptime) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     int getReportedForegroundServiceTypes() {
         return mRepFgServiceTypes;
     }
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index d2ef479..2ad2077 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -30,6 +30,7 @@
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.util.Slog;
 import android.util.TimeUtils;
 
@@ -43,6 +44,9 @@
  * The state info of the process, including proc state, oom adj score, et al.
  */
 final class ProcessStateRecord {
+    // Enable this to trace all OomAdjuster state transitions
+    private static final boolean TRACE_OOM_ADJ = false;
+
     private final ProcessRecord mApp;
     private final ActivityManagerService mService;
     private final ActivityManagerGlobalLock mProcLock;
@@ -916,6 +920,12 @@
 
     @GuardedBy("mService")
     void setAdjType(String adjType) {
+        if (TRACE_OOM_ADJ) {
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    "oom:" + mApp.processName + "/u" + mApp.uid, 0);
+            Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    "oom:" + mApp.processName + "/u" + mApp.uid, adjType, 0);
+        }
         mAdjType = adjType;
     }
 
@@ -1153,6 +1163,10 @@
 
     @GuardedBy({"mService", "mProcLock"})
     void onCleanupApplicationRecordLSP() {
+        if (TRACE_OOM_ADJ) {
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    "oom:" + mApp.processName + "/u" + mApp.uid, 0);
+        }
         setHasForegroundActivities(false);
         mHasShownUi = false;
         mForcingToImportant = null;
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 33e4070..438a2d43 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -567,6 +567,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
     @Override
     public byte[] getCurrentStats(List<ParcelFileDescriptor> historic) {
+        super.getCurrentStats_enforcePermission();
+
         Parcel current = Parcel.obtain();
         synchronized (mLock) {
             long now = SystemClock.uptimeMillis();
@@ -623,6 +625,8 @@
     public long getCommittedStatsMerged(long highWaterMarkMs, int section, boolean doAggregate,
             List<ParcelFileDescriptor> committedStats, ProcessStats mergedStats) {
 
+        super.getCommittedStatsMerged_enforcePermission();
+
         long newHighWaterMark = highWaterMarkMs;
         mFileLock.lock();
         try {
@@ -709,6 +713,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
     @Override
     public ParcelFileDescriptor getStatsOverTime(long minTime) {
+        super.getStatsOverTime_enforcePermission();
+
         Parcel current = Parcel.obtain();
         long curTime;
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/am/SameProcessApplicationThread.java b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
index a3c0111..62fd6e9 100644
--- a/services/core/java/com/android/server/am/SameProcessApplicationThread.java
+++ b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.app.IApplicationThread;
+import android.app.ReceiverInfo;
 import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -26,6 +27,7 @@
 import android.os.Handler;
 import android.os.RemoteException;
 
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -70,4 +72,20 @@
             }
         });
     }
+
+    @Override
+    public void scheduleReceiverList(List<ReceiverInfo> info) {
+        for (int i = 0; i < info.size(); i++) {
+            ReceiverInfo r = info.get(i);
+            if (r.registered) {
+                scheduleRegisteredReceiver(r.receiver, r.intent,
+                        r.resultCode, r.data, r.extras, r.ordered, r.sticky,
+                        r.sendingUser, r.processState);
+            } else {
+                scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
+                        r.resultCode, r.data, r.extras, r.sync,
+                        r.sendingUser, r.processState);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 4b82ad8..8ac10b8 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -128,6 +128,7 @@
     boolean delayedStop;    // service has been stopped but is in a delayed start?
     boolean stopIfKilled;   // last onStart() said to stop if service killed?
     boolean callStart;      // last onStart() has asked to always be called on restart.
+    int startCommandResult; // last result from onStartCommand(), only for dumpsys.
     int executeNesting;     // number of outstanding operations keeping foreground.
     boolean executeFg;      // should we be executing in the foreground?
     long executingStart;    // start time of last execute request.
@@ -201,11 +202,17 @@
     ActivityManagerService.FgsTempAllowListItem mInfoTempFgsAllowListReason;
     // Is the same mInfoAllowStartForeground string has been logged before? Used for dedup.
     boolean mLoggedInfoAllowStartForeground;
-    // The number of times Service.startForeground() is called;
+    // The number of times Service.startForeground() is called, after this service record is
+    // created. (i.e. due to "bound" or "start".) It never decreases, even when stopForeground()
+    // is called.
     int mStartForegroundCount;
     // Last time mAllowWhileInUsePermissionInFgs or mAllowStartForeground is set.
     long mLastSetFgsRestrictionTime;
 
+    // This is a service record of a FGS delegate (not a service record of a real service)
+    boolean mIsFgsDelegate;
+    @Nullable ForegroundServiceDelegation mFgsDelegation;
+
     String stringName;      // caching of toString
 
     private int lastStartId;    // identifier of most recent start request.
@@ -233,6 +240,7 @@
         final boolean taskRemoved;
         final int id;
         final int callingId;
+        final String mCallingProcessName;
         final Intent intent;
         final NeededUriGrants neededGrants;
         long deliveredTime;
@@ -242,14 +250,16 @@
 
         String stringName;      // caching of toString
 
-        StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id, Intent _intent,
-                NeededUriGrants _neededGrants, int _callingId) {
+        StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id,
+                Intent _intent, NeededUriGrants _neededGrants, int _callingId,
+                String callingProcessName) {
             sr = _sr;
             taskRemoved = _taskRemoved;
             id = _id;
             intent = _intent;
             neededGrants = _neededGrants;
             callingId = _callingId;
+            mCallingProcessName = callingProcessName;
         }
 
         UriPermissionOwner getUriPermissionsLocked() {
@@ -306,6 +316,84 @@
     final ArrayList<StartItem> pendingStarts = new ArrayList<StartItem>();
                             // start() arguments that haven't yet been delivered.
 
+    /**
+     * Information specific to "SHORT_SERVICE" FGS.
+     */
+    class ShortFgsInfo {
+        /** Time FGS started */
+        private final long mStartTime;
+
+        /**
+         * Copied from {@link #mStartForegroundCount}. If this is different from the parent's,
+         * that means this instance is stale.
+         */
+        private int mStartForegroundCount;
+
+        /** Service's "start ID" when this short-service started. */
+        private int mStartId;
+
+        ShortFgsInfo(long startTime) {
+            mStartTime = startTime;
+            update();
+        }
+
+        /**
+         * Update {@link #mStartForegroundCount} and {@link #mStartId}.
+         * (but not {@link #mStartTime})
+         */
+        public void update() {
+            this.mStartForegroundCount = ServiceRecord.this.mStartForegroundCount;
+            this.mStartId = getLastStartId();
+        }
+
+        long getStartTime() {
+            return mStartTime;
+        }
+
+        int getStartForegroundCount() {
+            return mStartForegroundCount;
+        }
+
+        int getStartId() {
+            return mStartId;
+        }
+
+        /**
+         * @return whether this {@link ShortFgsInfo} is still "current" or not -- i.e.
+         * it's "start foreground count" is the same as that of the ServiceRecord's.
+         *
+         * Note, we do _not_ check the "start id" here, because the start id increments if the
+         * app calls startService() or startForegroundService() on the same service,
+         * but that will _not_ update the ShortFgsInfo, and will not extend the timeout.
+         */
+        boolean isCurrent() {
+            return this.mStartForegroundCount == ServiceRecord.this.mStartForegroundCount;
+        }
+
+        /** Time when Service.onTimeout() should be called */
+        long getTimeoutTime() {
+            return mStartTime + ams.mConstants.mShortFgsTimeoutDuration;
+        }
+
+        /** Time when the procstate should be lowered. */
+        long getProcStateDemoteTime() {
+            return mStartTime + ams.mConstants.mShortFgsTimeoutDuration
+                    + ams.mConstants.mShortFgsProcStateExtraWaitDuration;
+        }
+
+        /** Time when the app should be declared ANR. */
+        long getAnrTime() {
+            return mStartTime + ams.mConstants.mShortFgsTimeoutDuration
+                    + ams.mConstants.mShortFgsAnrExtraWaitDuration;
+        }
+    }
+
+    /**
+     * Keep track of short-fgs specific information. This field gets cleared when the timeout
+     * stops.
+     */
+    private ShortFgsInfo mShortFgsInfo;
+
     void dumpStartList(PrintWriter pw, String prefix, List<StartItem> list, long now) {
         final int N = list.size();
         for (int i=0; i<N; i++) {
@@ -391,6 +479,7 @@
             proto.write(ServiceRecordProto.Start.DELAYED_STOP, delayedStop);
             proto.write(ServiceRecordProto.Start.STOP_IF_KILLED, stopIfKilled);
             proto.write(ServiceRecordProto.Start.LAST_START_ID, lastStartId);
+            proto.write(ServiceRecordProto.Start.START_COMMAND_RESULT, startCommandResult);
             proto.end(startToken);
         }
 
@@ -446,6 +535,21 @@
                 }
             }
         }
+        if (mShortFgsInfo != null && mShortFgsInfo.isCurrent()) {
+            final long shortFgsToken = proto.start(ServiceRecordProto.SHORT_FGS_INFO);
+            proto.write(ServiceRecordProto.ShortFgsInfo.START_TIME,
+                    mShortFgsInfo.getStartTime());
+            proto.write(ServiceRecordProto.ShortFgsInfo.START_ID,
+                    mShortFgsInfo.getStartId());
+            proto.write(ServiceRecordProto.ShortFgsInfo.TIMEOUT_TIME,
+                    mShortFgsInfo.getTimeoutTime());
+            proto.write(ServiceRecordProto.ShortFgsInfo.PROC_STATE_DEMOTE_TIME,
+                    mShortFgsInfo.getProcStateDemoteTime());
+            proto.write(ServiceRecordProto.ShortFgsInfo.ANR_TIME,
+                    mShortFgsInfo.getAnrTime());
+            proto.end(shortFgsToken);
+        }
+
         proto.end(token);
     }
 
@@ -499,8 +603,28 @@
         }
         if (isForeground || foregroundId != 0) {
             pw.print(prefix); pw.print("isForeground="); pw.print(isForeground);
-                    pw.print(" foregroundId="); pw.print(foregroundId);
-                    pw.print(" foregroundNoti="); pw.println(foregroundNoti);
+            pw.print(" foregroundId="); pw.print(foregroundId);
+            pw.printf(" types=%08X", foregroundServiceType);
+            pw.print(" foregroundNoti="); pw.println(foregroundNoti);
+
+            if (isShortFgs() && mShortFgsInfo != null) {
+                pw.print(prefix); pw.print("isShortFgs=true");
+                pw.print(" startId="); pw.print(mShortFgsInfo.getStartId());
+                pw.print(" startForegroundCount=");
+                pw.print(mShortFgsInfo.getStartForegroundCount());
+                pw.print(" startTime=");
+                TimeUtils.formatDuration(mShortFgsInfo.getStartTime(), now, pw);
+                pw.print(" timeout=");
+                TimeUtils.formatDuration(mShortFgsInfo.getTimeoutTime(), now, pw);
+                pw.print(" demoteTime=");
+                TimeUtils.formatDuration(mShortFgsInfo.getProcStateDemoteTime(), now, pw);
+                pw.print(" anrTime=");
+                TimeUtils.formatDuration(mShortFgsInfo.getAnrTime(), now, pw);
+                pw.println();
+            }
+        }
+        if (mIsFgsDelegate) {
+            pw.print(prefix); pw.print("isFgsDelegate="); pw.println(mIsFgsDelegate);
         }
         pw.print(prefix); pw.print("createTime=");
                 TimeUtils.formatDuration(createRealTime, nowReal, pw);
@@ -523,6 +647,7 @@
                     pw.print(" stopIfKilled="); pw.print(stopIfKilled);
                     pw.print(" callStart="); pw.print(callStart);
                     pw.print(" lastStartId="); pw.println(lastStartId);
+                    pw.print(" startCommandResult="); pw.println(startCommandResult);
         }
         if (executeNesting != 0) {
             pw.print(prefix); pw.print("executeNesting="); pw.print(executeNesting);
@@ -578,6 +703,32 @@
         }
     }
 
+    /** Used only for tests */
+    private ServiceRecord(ActivityManagerService ams) {
+        this.ams = ams;
+        name = null;
+        instanceName = null;
+        shortInstanceName = null;
+        definingPackageName = null;
+        definingUid = 0;
+        intent = null;
+        serviceInfo = null;
+        userId = 0;
+        packageName = null;
+        processName = null;
+        permission = null;
+        exported = false;
+        restarter = null;
+        createRealTime = 0;
+        isSdkSandbox = false;
+        sdkSandboxClientAppUid = 0;
+        sdkSandboxClientAppPackage = null;
+    }
+
+    public static ServiceRecord newEmptyInstanceForTest(ActivityManagerService ams) {
+        return new ServiceRecord(ams);
+    }
+
     ServiceRecord(ActivityManagerService ams, ComponentName name,
             ComponentName instanceName, String definingPackageName, int definingUid,
             Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
@@ -634,7 +785,9 @@
                     serviceInfo.applicationInfo.uid,
                     serviceInfo.applicationInfo.longVersionCode,
                     serviceInfo.processName, serviceInfo.name);
-            tracker.applyNewOwner(this);
+            if (tracker != null) {
+                tracker.applyNewOwner(this);
+            }
         }
         return tracker;
     }
@@ -757,6 +910,9 @@
      *         has no reason to start again. Note this condition doesn't consider the bindings.
      */
     boolean canStopIfKilled(boolean isStartCanceled) {
+        if (isShortFgs()) { // Short-FGS should always stop if killed.
+            return true;
+        }
         return startRequested && (stopIfKilled || isStartCanceled) && pendingStarts.isEmpty();
     }
 
@@ -1211,4 +1367,80 @@
     public ComponentName getComponentName() {
         return name;
     }
+
+    /**
+     * @return true if it's a foreground service of the "short service" type and don't have
+     * other fgs type bits set.
+     */
+    public boolean isShortFgs() {
+        // Note if the type contains FOREGROUND_SERVICE_TYPE_SHORT_SERVICE but also other bits
+        // set, it's _not_ considered be a short service. (because we shouldn't apply
+        // the short-service restrictions)
+        // (But we should be preventing mixture of FOREGROUND_SERVICE_TYPE_SHORT_SERVICE
+        // and other types in Service.startForeground().)
+        return startRequested && isForeground
+                && (foregroundServiceType == ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE);
+    }
+
+    public ShortFgsInfo getShortFgsInfo() {
+        return isShortFgs() ? mShortFgsInfo : null;
+    }
+
+    /**
+     * Call it when a short FGS starts.
+     */
+    public void setShortFgsInfo(long uptimeNow) {
+        this.mShortFgsInfo = new ShortFgsInfo(uptimeNow);
+    }
+
+    /** @return whether {@link #mShortFgsInfo} is set or not. */
+    public boolean hasShortFgsInfo() {
+        return mShortFgsInfo != null;
+    }
+
+    /**
+     * Call it when a short FGS stops.
+     */
+    public void clearShortFgsInfo() {
+        this.mShortFgsInfo = null;
+    }
+
+    /**
+     * @return true if it's a short FGS that's still up and running, and should be timed out.
+     */
+    public boolean shouldTriggerShortFgsTimeout() {
+        if (!isAppAlive()) {
+            return false;
+        }
+        if (!this.startRequested || !isShortFgs() || mShortFgsInfo == null
+                || !mShortFgsInfo.isCurrent()) {
+            return false;
+        }
+        return mShortFgsInfo.getTimeoutTime() <= SystemClock.uptimeMillis();
+    }
+
+    /**
+     * @return true if it's a short FGS that's still up and running, and should be declared
+     * an ANR.
+     */
+    public boolean shouldTriggerShortFgsAnr() {
+        if (!isAppAlive()) {
+            return false;
+        }
+        if (!this.startRequested || !isShortFgs() || mShortFgsInfo == null
+                || !mShortFgsInfo.isCurrent()) {
+            return false;
+        }
+        return mShortFgsInfo.getAnrTime() <= SystemClock.uptimeMillis();
+    }
+
+    private boolean isAppAlive() {
+        if (app == null) {
+            return false;
+        }
+        if (app.getThread() == null || app.isKilled() || app.isKilledByAm()) {
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index f16347f..f22624c 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -104,6 +104,7 @@
         DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
         DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE,
+        DeviceConfig.NAMESPACE_HDMI_CONTROL
     };
 
     private final String[] mGlobalSettings;
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index 060e3ee..2a69363 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -18,6 +18,34 @@
       ]
     },
     {
+      "name": "CtsAppFgsTestCases",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsShortFgsTestCases",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
       "name": "FrameworksServicesTests",
       "options": [
         {
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index 51568d8..b617582 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -238,11 +238,13 @@
         mEphemeral = ephemeral;
     }
 
+    /** Returns whether the UID has any FGS of any type or not (including "short fgs") */
     @GuardedBy(anyOf = {"mService", "mProcLock"})
     boolean hasForegroundServices() {
         return mForegroundServices;
     }
 
+    /** Sets whether the UID has any FGS of any type or not (including "short fgs") */
     @GuardedBy({"mService", "mProcLock"})
     void setForegroundServices(boolean foregroundServices) {
         mForegroundServices = foregroundServices;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 82d239f..aefa2f5 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -80,7 +80,6 @@
 import android.os.IProgressListener;
 import android.os.IRemoteCallback;
 import android.os.IUserManager;
-import android.os.Looper;
 import android.os.Message;
 import android.os.PowerWhitelistManager;
 import android.os.Process;
@@ -100,7 +99,6 @@
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
@@ -134,6 +132,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -176,7 +175,6 @@
     static final int START_USER_SWITCH_FG_MSG = 120;
     static final int COMPLETE_USER_SWITCH_MSG = 130;
     static final int USER_COMPLETED_EVENT_MSG = 140;
-    static final int USER_VISIBLE_MSG = 150;
 
     private static final int NO_ARG2 = 0;
 
@@ -200,6 +198,14 @@
     private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;
 
     /**
+     * Amount of time waited for {@link WindowManagerService#dismissKeyguard} callbacks to be
+     * called after dismissing the keyguard.
+     * Otherwise, we should move on to unfreeze the screen {@link #unfreezeScreen}
+     * and report user switch is complete {@link #REPORT_USER_SWITCH_COMPLETE_MSG}.
+     */
+    private static final int DISMISS_KEYGUARD_TIMEOUT_MS = 2 * 1000;
+
+    /**
      * Time after last scheduleOnUserCompletedEvent() call at which USER_COMPLETED_EVENT_MSG will be
      * scheduled (although it may fire sooner instead).
      * When it fires, {@link #reportOnUserCompletedEvent} will be processed.
@@ -431,15 +437,10 @@
     private volatile long mLastUserUnlockingUptime = 0;
 
     /**
-     * List of visible users (as defined by {@link UserManager#isUserVisible()}).
-     *
-     * <p>It's only used to call {@link SystemServiceManager} when the visibility is changed upon
-     * the user starting or stopping.
-     *
-     * <p>Note: only the key is used, not the value.
+     * Pending user starts waiting for shutdown step to complete.
      */
     @GuardedBy("mLock")
-    private final SparseBooleanArray mVisibleUsers = new SparseBooleanArray();
+    private final List<PendingUserStart> mPendingUserStarts = new ArrayList<>();
 
     private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() {
         @Override
@@ -948,9 +949,8 @@
     int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
             final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
         checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser");
-        if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
-            throw new IllegalArgumentException("Can't stop system user " + userId);
-        }
+        Preconditions.checkArgument(userId >= 0, "Invalid user id %d", userId);
+
         enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
         synchronized (mLock) {
             return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
@@ -1077,32 +1077,13 @@
             uss.setState(UserState.STATE_STOPPING);
             UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
             userManagerInternal.setUserState(userId, uss.state);
-            // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
-            // we'll need to remove this call and handle that as part of the user state workflow
-            // instead.
-            userManagerInternal.unassignUserFromDisplay(userId);
-
-            final boolean visibilityChanged;
-            boolean visibleBefore;
-            synchronized (mLock) {
-                visibleBefore = mVisibleUsers.get(userId);
-                if (visibleBefore) {
-                    if (DEBUG_MU) {
-                        Slogf.d(TAG, "Removing %d from mVisibleUsers", userId);
-                    }
-                    mVisibleUsers.delete(userId);
-                    visibilityChanged = true;
-                } else {
-                    visibilityChanged = false;
-                }
-            }
+            userManagerInternal.unassignUserFromDisplayOnStop(userId);
 
             updateStartedUserArrayLU();
 
             final boolean allowDelayedLockingCopied = allowDelayedLocking;
             Runnable finishUserStoppingAsync = () ->
-                    mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied,
-                            visibilityChanged));
+                    mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied));
 
             if (mInjector.getUserManager().isPreCreated(userId)) {
                 finishUserStoppingAsync.run();
@@ -1140,7 +1121,7 @@
     }
 
     private void finishUserStopping(final int userId, final UserState uss,
-            final boolean allowDelayedLocking, final boolean visibilityChanged) {
+            final boolean allowDelayedLocking) {
         EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPING, userId);
         synchronized (mLock) {
             if (uss.state != UserState.STATE_STOPPING) {
@@ -1157,7 +1138,7 @@
         mInjector.batteryStatsServiceNoteEvent(
                 BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
                 Integer.toString(userId), userId);
-        mInjector.getSystemServiceManager().onUserStopping(userId, visibilityChanged);
+        mInjector.getSystemServiceManager().onUserStopping(userId);
 
         Runnable finishUserStoppedAsync = () ->
                 mHandler.post(() -> finishUserStopped(uss, allowDelayedLocking));
@@ -1207,9 +1188,13 @@
             } else {
                 stopped = true;
                 // User can no longer run.
+                Slogf.i(TAG, "Removing user state from UserController.mStartedUsers for user #"
+                        + userId + " as a result of user being stopped");
                 mStartedUsers.remove(userId);
+
                 mUserLru.remove(Integer.valueOf(userId));
                 updateStartedUserArrayLU();
+
                 if (allowDelayedLocking && !keyEvictedCallbacks.isEmpty()) {
                     Slogf.wtf(TAG,
                             "Delayed locking enabled while KeyEvictedCallbacks not empty, userId:"
@@ -1223,7 +1208,10 @@
             }
         }
         if (stopped) {
+            Slogf.i(TAG, "Removing user state from UserManager.mUserStates for user #" + userId
+                    + " as a result of user being stopped");
             mInjector.getUserManagerInternal().removeUserState(userId);
+
             mInjector.activityManagerOnUserStopped(userId);
             // Clean up all state and processes associated with the user.
             // Kill all the processes for the user.
@@ -1251,10 +1239,13 @@
                     USER_LIFECYCLE_EVENT_STATE_FINISH);
             clearSessionId(userId);
 
-            if (!lockUser) {
-                return;
+            if (lockUser) {
+                dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
             }
-            dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
+
+            // Resume any existing pending user start,
+            // which was paused while the SHUTDOWN flow of the user was in progress.
+            resumePendingUserStarts(userId);
         } else {
             logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
                     USER_LIFECYCLE_EVENT_STATE_NONE);
@@ -1262,6 +1253,31 @@
         }
     }
 
+    /**
+     * Resume any existing pending user start for the specified userId which was paused
+     * while the shutdown flow of the user was in progress.
+     * Remove all the handled user starts from mPendingUserStarts.
+     * @param userId the id of the user
+     */
+    private void resumePendingUserStarts(@UserIdInt int userId) {
+        synchronized (mLock) {
+            final List<PendingUserStart> handledUserStarts = new ArrayList<>();
+
+            for (PendingUserStart userStart: mPendingUserStarts) {
+                if (userStart.userId == userId) {
+                    Slogf.i(TAG, "resumePendingUserStart for" + userStart);
+                    mHandler.post(() -> startUser(userStart.userId,
+                            userStart.isForeground, userStart.unlockListener));
+
+                    handledUserStarts.add(userStart);
+                }
+            }
+            // remove all the pending user starts which are now handled
+            mPendingUserStarts.removeAll(handledUserStarts);
+        }
+    }
+
+
     private void dispatchUserLocking(@UserIdInt int userId,
             @Nullable List<KeyEvictedCallback> keyEvictedCallbacks) {
         // Evict the user's credential encryption key. Performed on FgThread to make it
@@ -1275,6 +1291,7 @@
                 }
             }
             try {
+                Slogf.i(TAG, "Locking CE storage for user #" + userId);
                 mInjector.getStorageManager().lockUserKey(userId);
             } catch (RemoteException re) {
                 throw re.rethrowAsRuntimeException();
@@ -1525,13 +1542,32 @@
         return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, foreground, unlockListener);
     }
 
-    // TODO(b/239982558): add javadoc (need to wait until the intents / SystemService callbacks are
-    // defined
+    /**
+     * Starts a user in background and make it visible in the given display.
+     *
+     * <p>This call will trigger the usual "user started" lifecycle events (i.e., `SystemService`
+     * callbacks and app intents), plus a call to
+     * {@link UserManagerInternal.UserVisibilityListener#onUserVisibilityChanged(int, boolean)} if
+     * the user visibility changed. Notice that the visibility change is independent of the user
+     * workflow state, and they can mismatch in some corner events (for example, if the user was
+     * already running in the background but not associated with a display, this call for that user
+     * would not trigger any lifecycle event but would trigger {@code onUserVisibilityChanged}).
+     *
+     * <p>See {@link ActivityManager#startUserInBackgroundOnSecondaryDisplay(int, int)} for more
+     * semantics.
+     *
+     * @param userId user to be started
+     * @param displayId display where the user will be visible
+     *
+     * @return whether the user was started
+     */
     boolean startUserOnSecondaryDisplay(@UserIdInt int userId, int displayId) {
         checkCallingHasOneOfThosePermissions("startUserOnSecondaryDisplay",
                 MANAGE_USERS, INTERACT_ACROSS_USERS);
 
         // DEFAULT_DISPLAY is used for the current foreground user only
+        // TODO(b/245939659): might need to move this check to UserVisibilityMediator to support
+        // passenger-only screens
         Preconditions.checkArgument(displayId != Display.DEFAULT_DISPLAY,
                 "Cannot use DEFAULT_DISPLAY");
 
@@ -1539,7 +1575,7 @@
             return startUserNoChecks(userId, displayId, /* foreground= */ false,
                     /* unlockListener= */ null);
         } catch (RuntimeException e) {
-            Slogf.w(TAG, "startUserOnSecondaryDisplay(%d, %d) failed: %s", userId, displayId, e);
+            Slogf.e(TAG, "startUserOnSecondaryDisplay(%d, %d) failed: %s", userId, displayId, e);
             return false;
         }
     }
@@ -1626,9 +1662,18 @@
                 return false;
             }
 
-            mInjector.getUserManagerInternal().assignUserToDisplay(userId, displayId);
+            t.traceBegin("assignUserToDisplayOnStart");
+            int result = mInjector.getUserManagerInternal().assignUserToDisplayOnStart(userId,
+                    userInfo.profileGroupId, foreground, displayId);
+            t.traceEnd();
 
-            // TODO(b/239982558): might need something similar for bg users on secondary display
+            if (result == UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE) {
+                Slogf.e(TAG, "%s user(%d) / display (%d) assignment failed: %s",
+                        (foreground ? "fg" : "bg"), userId, displayId,
+                        UserManagerInternal.userAssignmentResultToString(result));
+                return false;
+            }
+
             if (foreground && isUserSwitchUiEnabled()) {
                 t.traceBegin("startFreezingScreen");
                 mInjector.getWindowManager().startFreezingScreen(
@@ -1652,10 +1697,11 @@
                     updateStartedUserArrayLU();
                     needStart = true;
                     updateUmState = true;
-                } else if (uss.state == UserState.STATE_SHUTDOWN && !isCallingOnHandlerThread()) {
+                } else if (uss.state == UserState.STATE_SHUTDOWN) {
                     Slogf.i(TAG, "User #" + userId
-                            + " is shutting down - will start after full stop");
-                    mHandler.post(() -> startUser(userId, foreground, unlockListener));
+                            + " is shutting down - will start after full shutdown");
+                    mPendingUserStarts.add(new PendingUserStart(userId,
+                            foreground, unlockListener));
                     t.traceEnd(); // updateStartedUserArrayStarting
                     return true;
                 }
@@ -1684,6 +1730,9 @@
                     userSwitchUiEnabled = mUserSwitchUiEnabled;
                 }
                 mInjector.updateUserConfiguration();
+                // NOTE: updateProfileRelatedCaches() is called on both if and else parts, ideally
+                // it should be moved outside, but for now it's not as there are many calls to
+                // external components here afterwards
                 updateProfileRelatedCaches();
                 mInjector.getWindowManager().setCurrentUser(userId);
                 mInjector.reportCurWakefulnessUsageEvent();
@@ -1696,6 +1745,7 @@
                         mInjector.getWindowManager().lockNow(null);
                     }
                 }
+
             } else {
                 final Integer currentUserIdInt = mCurrentUserId;
                 updateProfileRelatedCaches();
@@ -1706,28 +1756,6 @@
             }
             t.traceEnd();
 
-            // Need to call UM when user is on background, as there are some cases where the user
-            // cannot be started in background on a secondary display (for example, if user is a
-            // profile).
-            // TODO(b/253103846): it's also explicitly checking if the user is the USER_SYSTEM, as
-            // the UM call would return true during boot (when CarService / BootUserInitializer
-            // calls AM.startUserInBackground() because the system user is still the current user.
-            // TODO(b/244644281): another fragility of this check is that it must wait to call
-            // UMI.isUserVisible() until the user state is check, as that method checks if the
-            // profile of the current user is started. We should fix that dependency so the logic
-            // belongs to just one place (like UserDisplayAssigner)
-            boolean visible = foreground
-                    || userId != UserHandle.USER_SYSTEM
-                            && mInjector.getUserManagerInternal().isUserVisible(userId);
-            if (visible) {
-                synchronized (mLock) {
-                    if (DEBUG_MU) {
-                        Slogf.d(TAG, "Adding %d to mVisibleUsers", userId);
-                    }
-                    mVisibleUsers.put(userId, true);
-                }
-            }
-
             // Make sure user is in the started state.  If it is currently
             // stopping, we need to knock that off.
             if (uss.state == UserState.STATE_STOPPING) {
@@ -1764,15 +1792,8 @@
                 // Booting up a new user, need to tell system services about it.
                 // Note that this is on the same handler as scheduling of broadcasts,
                 // which is important because it needs to go first.
-                mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId,
-                        visible ? 1 : 0));
+                mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, NO_ARG2));
                 t.traceEnd();
-            } else if (visible) {
-                // User was already running and became visible (for example, when switching to a
-                // user that was started in the background before), so it's necessary to explicitly
-                // notify the services (while when the user starts from BOOTING, USER_START_MSG
-                // takes care of that.
-                mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBLE_MSG, userId, NO_ARG2));
             }
 
             t.traceBegin("sendMessages");
@@ -1804,7 +1825,7 @@
 
             if (foreground) {
                 t.traceBegin("moveUserToForeground");
-                moveUserToForeground(uss, oldUserId, userId);
+                moveUserToForeground(uss, userId);
                 t.traceEnd();
             } else {
                 t.traceBegin("finishUserBoot");
@@ -1838,10 +1859,6 @@
         return true;
     }
 
-    private boolean isCallingOnHandlerThread() {
-        return Looper.myLooper() == mHandler.getLooper();
-    }
-
     /**
      * Start user, if its not already running, and bring it to foreground.
      */
@@ -2018,21 +2035,25 @@
 
     /** Called on handler thread */
     @VisibleForTesting
-    void dispatchUserSwitchComplete(@UserIdInt int userId) {
+    void dispatchUserSwitchComplete(@UserIdInt int oldUserId, @UserIdInt int newUserId) {
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
-        t.traceBegin("dispatchUserSwitchComplete-" + userId);
+        t.traceBegin("dispatchUserSwitchComplete-" + newUserId);
         mInjector.getWindowManager().setSwitchingUser(false);
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         for (int i = 0; i < observerCount; i++) {
             try {
-                t.traceBegin("onUserSwitchComplete-" + userId + " #" + i + " "
+                t.traceBegin("onUserSwitchComplete-" + newUserId + " #" + i + " "
                         + mUserSwitchObservers.getBroadcastCookie(i));
-                mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(userId);
+                mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(newUserId);
                 t.traceEnd();
             } catch (RemoteException e) {
+                // Ignore
             }
         }
         mUserSwitchObservers.finishBroadcast();
+        t.traceBegin("sendUserSwitchBroadcasts-" + oldUserId + "-" + newUserId);
+        sendUserSwitchBroadcasts(oldUserId, newUserId);
+        t.traceEnd();
         t.traceEnd();
     }
 
@@ -2075,6 +2096,8 @@
     }
 
     private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
+        TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+        t.traceBegin("timeoutUserSwitch-" + oldUserId + "-to-" + newUserId);
         synchronized (mLock) {
             Slogf.e(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
             mTimeoutUserSwitchCallbacks = mCurWaitingUserSwitchCallbacks;
@@ -2084,6 +2107,7 @@
             mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_CALLBACKS_TIMEOUT_MSG,
                     oldUserId, newUserId), USER_SWITCH_CALLBACKS_TIMEOUT_MS);
         }
+        t.traceEnd();
     }
 
     private void timeoutUserSwitchCallbacks(int oldUserId, int newUserId) {
@@ -2141,6 +2165,8 @@
                                             + " ms after dispatchUserSwitch.");
                                 }
 
+                                TimingsTraceAndSlog t2 = new TimingsTraceAndSlog(TAG);
+                                t2.traceBegin("onUserSwitchingReply-" + name);
                                 curWaitingUserSwitchCallbacks.remove(name);
                                 // Continue switching if all callbacks have been notified and
                                 // user switching session is still valid
@@ -2149,11 +2175,15 @@
                                         == mCurWaitingUserSwitchCallbacks)) {
                                     sendContinueUserSwitchLU(uss, oldUserId, newUserId);
                                 }
+                                t2.traceEnd();
                             }
                         }
                     };
+                    t.traceBegin("onUserSwitching-" + name);
                     mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(newUserId, callback);
+                    t.traceEnd();
                 } catch (RemoteException e) {
+                    // Ignore
                 }
             }
         } else {
@@ -2167,10 +2197,13 @@
 
     @GuardedBy("mLock")
     private void sendContinueUserSwitchLU(UserState uss, int oldUserId, int newUserId) {
+        TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+        t.traceBegin("sendContinueUserSwitchLU-" + oldUserId + "-to-" + newUserId);
         mCurWaitingUserSwitchCallbacks = null;
         mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
         mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
                 oldUserId, newUserId, uss));
+        t.traceEnd();
     }
 
     @VisibleForTesting
@@ -2182,39 +2215,34 @@
 
         // Do the keyguard dismiss and unfreeze later
         mHandler.removeMessages(COMPLETE_USER_SWITCH_MSG);
-        mHandler.sendMessage(mHandler.obtainMessage(COMPLETE_USER_SWITCH_MSG, newUserId, 0));
+        mHandler.sendMessage(mHandler.obtainMessage(
+                COMPLETE_USER_SWITCH_MSG, oldUserId, newUserId));
 
         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);
-        }
 
         t.traceEnd(); // end continueUserSwitch
     }
 
     @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 {
+    void completeUserSwitch(int oldUserId, int newUserId) {
+        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, oldUserId, newUserId));
+        };
+
+        // 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();
         }
     }
 
@@ -2231,7 +2259,7 @@
         t.traceEnd();
     }
 
-    private void moveUserToForeground(UserState uss, int oldUserId, int newUserId) {
+    private void moveUserToForeground(UserState uss, int newUserId) {
         boolean homeInFront = mInjector.taskSupervisorSwitchUser(newUserId, uss);
         if (homeInFront) {
             mInjector.startHomeActivity(newUserId, "moveUserToForeground");
@@ -2239,7 +2267,6 @@
             mInjector.taskSupervisorResumeFocusedStackTopActivity();
         }
         EventLogTags.writeAmSwitchUser(newUserId);
-        sendUserSwitchBroadcasts(oldUserId, newUserId);
     }
 
     void sendUserSwitchBroadcasts(int oldUserId, int newUserId) {
@@ -2550,22 +2577,12 @@
 
     // TODO(b/242195409): remove this method if initial system user boot logic is refactored?
     void onSystemUserStarting() {
-        updateSystemUserVisibility(/* visible= */ !UserManager.isHeadlessSystemUserMode());
-    }
-
-    private void updateSystemUserVisibility(boolean visible) {
-        if (DEBUG_MU) {
-            Slogf.d(TAG, "updateSystemUserVisibility(): visible=%b", visible);
+        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);
+            mInjector.onSystemUserVisibilityChanged(/* visible= */ true);
         }
-        int userId = UserHandle.USER_SYSTEM;
-        synchronized (mLock) {
-            if (visible) {
-                mVisibleUsers.put(userId, true);
-            } else {
-                mVisibleUsers.delete(userId);
-            }
-        }
-        mInjector.onUserStarting(userId, visible);
     }
 
     /**
@@ -2965,9 +2982,6 @@
                     proto.end(uToken);
                 }
             }
-            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]);
@@ -3027,7 +3041,6 @@
                 pw.println("  mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage);
             }
             pw.println("  mLastUserUnlockingUptime: " + mLastUserUnlockingUptime);
-            pw.println("  mVisibleUsers: " + mVisibleUsers);
         }
     }
 
@@ -3064,7 +3077,7 @@
                 logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
                         USER_LIFECYCLE_EVENT_STATE_BEGIN);
 
-                mInjector.onUserStarting(/* userId= */ msg.arg1, /* visible= */ msg.arg2 == 1);
+                mInjector.onUserStarting(/* userId= */ msg.arg1);
                 scheduleOnUserCompletedEvent(msg.arg1,
                         UserCompletedEventType.EVENT_TYPE_USER_STARTING,
                         USER_COMPLETED_EVENT_DELAY_MS);
@@ -3123,9 +3136,8 @@
                 dispatchForegroundProfileChanged(msg.arg1);
                 break;
             case REPORT_USER_SWITCH_COMPLETE_MSG:
-                dispatchUserSwitchComplete(msg.arg1);
-
-                logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_SWITCH_USER,
+                dispatchUserSwitchComplete(msg.arg1, msg.arg2);
+                logUserLifecycleEvent(msg.arg2, USER_LIFECYCLE_EVENT_SWITCH_USER,
                         USER_LIFECYCLE_EVENT_STATE_FINISH);
                 break;
             case REPORT_LOCKED_BOOT_COMPLETE_MSG:
@@ -3143,10 +3155,7 @@
                 logAndClearSessionId(msg.arg1);
                 break;
             case COMPLETE_USER_SWITCH_MSG:
-                completeUserSwitch(msg.arg1);
-                break;
-            case USER_VISIBLE_MSG:
-                mInjector.getSystemServiceManager().onUserVisible(/* userId= */ msg.arg1);
+                completeUserSwitch(msg.arg1, msg.arg2);
                 break;
         }
         return false;
@@ -3418,6 +3427,32 @@
         }
     }
 
+    /**
+     * Helper class for keeping track of user starts which are paused while user's
+     * shutdown is taking place.
+     */
+    private static class PendingUserStart {
+        public final @UserIdInt int userId;
+        public final boolean isForeground;
+        public final IProgressListener unlockListener;
+
+        PendingUserStart(int userId, boolean foreground,
+                IProgressListener unlockListener) {
+            this.userId = userId;
+            this.isForeground = foreground;
+            this.unlockListener = unlockListener;
+        }
+
+        @Override
+        public String toString() {
+            return "PendingUserStart{"
+                    + "userId=" + userId
+                    + ", isForeground=" + isForeground
+                    + ", unlockListener=" + unlockListener
+                    + '}';
+        }
+    }
+
     @VisibleForTesting
     static class Injector {
         private final ActivityManagerService mService;
@@ -3648,20 +3683,28 @@
         }
 
         protected void dismissKeyguard(Runnable runnable, String reason) {
+            final AtomicBoolean isFirst = new AtomicBoolean(true);
+            final Runnable runOnce = () -> {
+                if (isFirst.getAndSet(false)) {
+                    runnable.run();
+                }
+            };
+
+            mHandler.postDelayed(runOnce, DISMISS_KEYGUARD_TIMEOUT_MS);
             getWindowManager().dismissKeyguard(new IKeyguardDismissCallback.Stub() {
                 @Override
                 public void onDismissError() throws RemoteException {
-                    mHandler.post(runnable);
+                    mHandler.post(runOnce);
                 }
 
                 @Override
                 public void onDismissSucceeded() throws RemoteException {
-                    mHandler.post(runnable);
+                    mHandler.post(runOnce);
                 }
 
                 @Override
                 public void onDismissCancelled() throws RemoteException {
-                    mHandler.post(runnable);
+                    mHandler.post(runOnce);
                 }
             }, reason);
         }
@@ -3670,9 +3713,12 @@
             return UserManager.isUsersOnSecondaryDisplaysEnabled();
         }
 
-        void onUserStarting(int userId, boolean visible) {
-            getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId,
-                    visible);
+        void onUserStarting(@UserIdInt int userId) {
+            getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId);
+        }
+
+        void onSystemUserVisibilityChanged(boolean visible) {
+            getUserManagerInternal().onSystemUserVisibilityChanged(visible);
         }
     }
 }
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index efa2f25..46d3ff1 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -35,9 +35,12 @@
 import android.app.GameManager;
 import android.app.GameManager.GameMode;
 import android.app.GameManagerInternal;
+import android.app.GameModeConfiguration;
 import android.app.GameModeInfo;
 import android.app.GameState;
 import android.app.IGameManagerService;
+import android.app.IGameModeListener;
+import android.app.StatsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -57,10 +60,12 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManagerInternal;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.UserManager;
@@ -72,6 +77,7 @@
 import android.util.AttributeSet;
 import android.util.KeyValueListParser;
 import android.util.Slog;
+import android.util.StatsEvent;
 import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
@@ -100,6 +106,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Service to manage game related features.
@@ -111,6 +118,14 @@
  */
 public final class GameManagerService extends IGameManagerService.Stub {
     public static final String TAG = "GameManagerService";
+    // event strings used for logging
+    private static final String EVENT_SET_GAME_MODE = "SET_GAME_MODE";
+    private static final String EVENT_UPDATE_CUSTOM_GAME_MODE_CONFIG =
+            "UPDATE_CUSTOM_GAME_MODE_CONFIG";
+    private static final String EVENT_RECEIVE_SHUTDOWN_INDENT = "RECEIVE_SHUTDOWN_INDENT";
+    private static final String EVENT_ON_USER_STARTING = "ON_USER_STARTING";
+    private static final String EVENT_ON_USER_SWITCHING = "ON_USER_SWITCHING";
+    private static final String EVENT_ON_USER_STOPPING = "ON_USER_STOPPING";
 
     private static final boolean DEBUG = false;
 
@@ -131,6 +146,7 @@
     private final Context mContext;
     private final Object mLock = new Object();
     private final Object mDeviceConfigLock = new Object();
+    private final Object mGameModeListenerLock = new Object();
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     final Handler mHandler;
     private final PackageManager mPackageManager;
@@ -144,6 +160,9 @@
     private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
     @GuardedBy("mDeviceConfigLock")
     private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>();
+    // listener to caller uid map
+    @GuardedBy("mGameModeListenerLock")
+    private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>();
     @Nullable
     private final GameServiceController mGameServiceController;
 
@@ -330,7 +349,18 @@
                                     + " and userId " + userId);
                             break;
                         }
+                        if (mHandler.hasMessages(CANCEL_GAME_LOADING_MODE)) {
+                            mHandler.removeMessages(CANCEL_GAME_LOADING_MODE);
+                        }
                         mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, isLoading);
+                        if (isLoading) {
+                            int loadingBoostDuration = getLoadingBoostDuration(packageName, userId);
+                            loadingBoostDuration = loadingBoostDuration > 0 ? loadingBoostDuration
+                                    : LOADING_BOOST_MAX_DURATION;
+                            mHandler.sendMessageDelayed(
+                                    mHandler.obtainMessage(CANCEL_GAME_LOADING_MODE),
+                                    loadingBoostDuration);
+                        }
                     }
                     break;
                 }
@@ -399,6 +429,7 @@
     // Turn the raw string to the corresponding fps int.
     // Return 0 when disabling, -1 for invalid fps.
     static int getFpsInt(String raw) {
+        // TODO(b/243448953): make sure this translates to proper values based on current display
         switch (raw) {
             case "30":
                 return FrameRate.FPS_30.fps;
@@ -429,6 +460,7 @@
     public void setGameState(String packageName, @NonNull GameState gameState,
             @UserIdInt int userId) {
         if (!isPackageGame(packageName, userId)) {
+            Slog.d(TAG, "No-op for attempt to set game state for non-game app: " + packageName);
             // Restrict to games only.
             return;
         }
@@ -492,8 +524,8 @@
         private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs = new ArrayMap<>();
         // if adding new properties or make any of the below overridable, the method
         // copyAndApplyOverride should be updated accordingly
-        private boolean mPerfModeOptedIn = false;
-        private boolean mBatteryModeOptedIn = false;
+        private boolean mPerfModeOverridden = false;
+        private boolean mBatteryModeOverridden = false;
         private boolean mAllowDownscale = true;
         private boolean mAllowAngle = true;
         private boolean mAllowFpsOverride = true;
@@ -510,8 +542,8 @@
                         PackageManager.GET_META_DATA, userId);
                 if (!parseInterventionFromXml(packageManager, ai, packageName)
                             && ai.metaData != null) {
-                    mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
-                    mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
+                    mPerfModeOverridden = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
+                    mBatteryModeOverridden = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
                     mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
                     mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
                 }
@@ -563,9 +595,9 @@
                     } else {
                         final TypedArray array = resources.obtainAttributes(attributeSet,
                                 com.android.internal.R.styleable.GameModeConfig);
-                        mPerfModeOptedIn = array.getBoolean(
+                        mPerfModeOverridden = array.getBoolean(
                                 GameModeConfig_supportsPerformanceGameMode, false);
-                        mBatteryModeOptedIn = array.getBoolean(
+                        mBatteryModeOverridden = array.getBoolean(
                                 GameModeConfig_supportsBatteryGameMode,
                                 false);
                         mAllowDownscale = array.getBoolean(GameModeConfig_allowGameDownscaling,
@@ -578,8 +610,8 @@
                 }
             } catch (NameNotFoundException | XmlPullParserException | IOException ex) {
                 // set flag back to default values when parsing fails
-                mPerfModeOptedIn = false;
-                mBatteryModeOptedIn = false;
+                mPerfModeOverridden = false;
+                mBatteryModeOverridden = false;
                 mAllowDownscale = true;
                 mAllowAngle = true;
                 mAllowFpsOverride = true;
@@ -596,6 +628,14 @@
             }
         }
 
+        // used to check if the override package config has any game mode config, if not, it's
+        // considered empty and safe to delete from settings
+        boolean hasActiveGameModeConfig() {
+            synchronized (mModeConfigLock) {
+                return !mModeConfigs.isEmpty();
+            }
+        }
+
         /**
          * GameModeConfiguration contains all the values for all the interventions associated with
          * a game mode.
@@ -627,8 +667,8 @@
 
             GameModeConfiguration(KeyValueListParser parser) {
                 mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED);
-                // isGameModeOptedIn() returns if an app will handle all of the changes necessary
-                // for a particular game mode. If so, the Android framework (i.e.
+                // willGamePerformOptimizations() returns if an app will handle all of the changes
+                // necessary for a particular game mode. If so, the Android framework (i.e.
                 // GameManagerService) will not do anything for the app (like window scaling or
                 // using ANGLE).
                 mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
@@ -691,10 +731,27 @@
             public boolean isActive() {
                 return (mGameMode == GameManager.GAME_MODE_STANDARD
                         || mGameMode == GameManager.GAME_MODE_PERFORMANCE
-                        || mGameMode == GameManager.GAME_MODE_BATTERY)
+                        || mGameMode == GameManager.GAME_MODE_BATTERY
+                        || mGameMode == GameManager.GAME_MODE_CUSTOM)
                         && !willGamePerformOptimizations(mGameMode);
             }
 
+            android.app.GameModeConfiguration toPublicGameModeConfig() {
+                int fpsOverride = getFpsInt(mFps);
+                // TODO(b/243448953): match to proper value in case of display change?
+                fpsOverride = fpsOverride > 0 ? fpsOverride
+                        : android.app.GameModeConfiguration.FPS_OVERRIDE_NONE;
+                final float scaling = mScaling == DEFAULT_SCALING ? 1.0f : mScaling;
+                return new android.app.GameModeConfiguration.Builder()
+                        .setScalingFactor(scaling)
+                        .setFpsOverride(fpsOverride).build();
+            }
+
+            void updateFromPublicGameModeConfig(android.app.GameModeConfiguration config) {
+                mScaling = config.getScalingFactor();
+                mFps = String.valueOf(config.getFpsOverride());
+            }
+
             /**
              * @hide
              */
@@ -718,30 +775,24 @@
          * "com.android.app.gamemode.battery.enabled" with a value of "true"
          */
         public boolean willGamePerformOptimizations(@GameMode int gameMode) {
-            return (mBatteryModeOptedIn && gameMode == GameManager.GAME_MODE_BATTERY)
-                    || (mPerfModeOptedIn && gameMode == GameManager.GAME_MODE_PERFORMANCE);
+            return (mBatteryModeOverridden && gameMode == GameManager.GAME_MODE_BATTERY)
+                    || (mPerfModeOverridden && gameMode == GameManager.GAME_MODE_PERFORMANCE);
         }
 
         private int getAvailableGameModesBitfield() {
-            int field = 0;
+            int field = modeToBitmask(GameManager.GAME_MODE_CUSTOM)
+                    | modeToBitmask(GameManager.GAME_MODE_STANDARD);
             synchronized (mModeConfigLock) {
                 for (final int mode : mModeConfigs.keySet()) {
                     field |= modeToBitmask(mode);
                 }
             }
-            if (mBatteryModeOptedIn) {
+            if (mBatteryModeOverridden) {
                 field |= modeToBitmask(GameManager.GAME_MODE_BATTERY);
             }
-            if (mPerfModeOptedIn) {
+            if (mPerfModeOverridden) {
                 field |= modeToBitmask(GameManager.GAME_MODE_PERFORMANCE);
             }
-            // The lowest bit is reserved for UNSUPPORTED, STANDARD is supported if we support any
-            // other mode.
-            if (field > 1) {
-                field |= modeToBitmask(GameManager.GAME_MODE_STANDARD);
-            } else {
-                field |= modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED);
-            }
             return field;
         }
 
@@ -763,6 +814,21 @@
         }
 
         /**
+         * Get an array of a package's overridden game modes.
+         */
+        public @GameMode int[] getOverriddenGameModes() {
+            if (mBatteryModeOverridden && mPerfModeOverridden) {
+                return new int[]{GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE};
+            } else if (mBatteryModeOverridden) {
+                return new int[]{GameManager.GAME_MODE_BATTERY};
+            } else if (mPerfModeOverridden) {
+                return new int[]{GameManager.GAME_MODE_PERFORMANCE};
+            } else {
+                return new int[]{};
+            }
+        }
+
+        /**
          * Get a GameModeConfiguration for a given game mode.
          *
          * @return The package's GameModeConfiguration for the provided mode or null if absent
@@ -798,18 +864,18 @@
 
         public boolean isActive() {
             synchronized (mModeConfigLock) {
-                return mModeConfigs.size() > 0 || mBatteryModeOptedIn || mPerfModeOptedIn;
+                return mModeConfigs.size() > 0 || mBatteryModeOverridden || mPerfModeOverridden;
             }
         }
 
         GamePackageConfiguration copyAndApplyOverride(GamePackageConfiguration overrideConfig) {
             GamePackageConfiguration copy = new GamePackageConfiguration(mPackageName);
             // if a game mode is overridden, we treat it with the highest priority and reset any
-            // opt-in game modes so that interventions are always executed.
-            copy.mPerfModeOptedIn = mPerfModeOptedIn && !(overrideConfig != null
+            // overridden game modes so that interventions are always executed.
+            copy.mPerfModeOverridden = mPerfModeOverridden && !(overrideConfig != null
                     && overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE)
                     != null);
-            copy.mBatteryModeOptedIn = mBatteryModeOptedIn && !(overrideConfig != null
+            copy.mBatteryModeOverridden = mBatteryModeOverridden && !(overrideConfig != null
                     && overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY)
                     != null);
 
@@ -848,7 +914,7 @@
     private final class LocalService extends GameManagerInternal {
         @Override
         public float getResolutionScalingFactor(String packageName, int userId) {
-            final int gameMode = getGameModeFromSettings(packageName, userId);
+            final int gameMode = getGameModeFromSettingsUnchecked(packageName, userId);
             return getResolutionScalingFactorInternal(packageName, gameMode, userId);
         }
     }
@@ -878,11 +944,13 @@
         public void onBootPhase(int phase) {
             if (phase == PHASE_BOOT_COMPLETED) {
                 mService.onBootCompleted();
+                mService.registerStatsCallbacks();
             }
         }
 
         @Override
         public void onUserStarting(@NonNull TargetUser user) {
+            Slog.d(TAG, "Starting user " + user.getUserIdentifier());
             mService.onUserStarting(user,
                     Environment.getDataSystemDeDirectory(user.getUserIdentifier()));
         }
@@ -920,13 +988,10 @@
         }
     }
 
-    private @GameMode int[] getAvailableGameModesUnchecked(String packageName) {
-        final GamePackageConfiguration config;
-        synchronized (mDeviceConfigLock) {
-            config = mConfigs.get(packageName);
-        }
+    private @GameMode int[] getAvailableGameModesUnchecked(String packageName, int userId) {
+        final GamePackageConfiguration config = getConfig(packageName, userId);
         if (config == null) {
-            return new int[]{};
+            return new int[]{GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM};
         }
         return config.getAvailableGameModes();
     }
@@ -947,17 +1012,22 @@
      */
     @Override
     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
-    public @GameMode int[] getAvailableGameModes(String packageName) throws SecurityException {
+    public @GameMode int[] getAvailableGameModes(String packageName, int userId)
+            throws SecurityException {
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
-        return getAvailableGameModesUnchecked(packageName);
+        if (!isPackageGame(packageName, userId)) {
+            return new int[]{};
+        }
+        return getAvailableGameModesUnchecked(packageName, userId);
     }
 
-    private @GameMode int getGameModeFromSettings(String packageName, @UserIdInt int userId) {
+    private @GameMode int getGameModeFromSettingsUnchecked(String packageName,
+            @UserIdInt int userId) {
         synchronized (mLock) {
             if (!mSettings.containsKey(userId)) {
                 Slog.d(TAG, "User ID '" + userId + "' does not have a Game Mode"
-                            + " selected for package: '" + packageName + "'");
-                return GameManager.GAME_MODE_UNSUPPORTED;
+                        + " selected for package: '" + packageName + "'");
+                return GameManager.GAME_MODE_STANDARD;
             }
 
             return mSettings.get(userId).getGameModeLocked(packageName);
@@ -990,12 +1060,12 @@
         // return a value if the package name is valid. Next, check if the caller has the necessary
         // permission and return a value. Do this check last, since it can throw an exception.
         if (isValidPackageName(packageName, userId)) {
-            return getGameModeFromSettings(packageName, userId);
+            return getGameModeFromSettingsUnchecked(packageName, userId);
         }
 
         // Since the package name doesn't match, check the caller has the necessary permission.
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
-        return getGameModeFromSettings(packageName, userId);
+        return getGameModeFromSettingsUnchecked(packageName, userId);
     }
 
     /**
@@ -1015,15 +1085,38 @@
         // Check the caller has the necessary permission.
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
 
-        // Restrict to games only.
         if (!isPackageGame(packageName, userId)) {
             return null;
         }
 
-        final @GameMode int activeGameMode = getGameModeFromSettings(packageName, userId);
-        final @GameMode int[] availableGameModes = getAvailableGameModesUnchecked(packageName);
-
-        return new GameModeInfo(activeGameMode, availableGameModes);
+        final @GameMode int activeGameMode = getGameModeFromSettingsUnchecked(packageName, userId);
+        final GamePackageConfiguration config = getConfig(packageName, userId);
+        if (config != null) {
+            final @GameMode int[] overriddenGameModes = config.getOverriddenGameModes();
+            final @GameMode int[] availableGameModes = config.getAvailableGameModes();
+            GameModeInfo.Builder gameModeInfoBuilder = new GameModeInfo.Builder()
+                    .setActiveGameMode(activeGameMode)
+                    .setAvailableGameModes(availableGameModes)
+                    .setOverriddenGameModes(overriddenGameModes)
+                    .setDownscalingAllowed(config.mAllowDownscale)
+                    .setFpsOverrideAllowed(config.mAllowFpsOverride);
+            for (int gameMode : availableGameModes) {
+                if (!config.willGamePerformOptimizations(gameMode)) {
+                    GamePackageConfiguration.GameModeConfiguration gameModeConfig =
+                            config.getGameModeConfiguration(gameMode);
+                    if (gameModeConfig != null) {
+                        gameModeInfoBuilder.setGameModeConfiguration(gameMode,
+                                gameModeConfig.toPublicGameModeConfig());
+                    }
+                }
+            }
+            return gameModeInfoBuilder.build();
+        } else {
+            return new GameModeInfo.Builder()
+                    .setActiveGameMode(activeGameMode)
+                    .setAvailableGameModes(getAvailableGameModesUnchecked(packageName, userId))
+                    .build();
+        }
     }
 
     /**
@@ -1035,27 +1128,52 @@
     public void setGameMode(String packageName, @GameMode int gameMode, int userId)
             throws SecurityException {
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
-
-        if (!isPackageGame(packageName, userId)) {
-            // Restrict to games only.
+        if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+            Slog.d(TAG, "No-op for attempt to set UNSUPPORTED mode for app: " + packageName);
+            return;
+        } else if (!isPackageGame(packageName, userId)) {
+            Slog.d(TAG, "No-op for attempt to set game mode for non-game app: " + packageName);
             return;
         }
-
+        int fromGameMode;
         synchronized (mLock) {
             userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, false, true, "setGameMode",
                     "com.android.server.app.GameManagerService");
 
             if (!mSettings.containsKey(userId)) {
+                Slog.d(TAG, "Failed to set game mode for package " + packageName
+                        + " as user " + userId + " is not started");
                 return;
             }
             GameManagerSettings userSettings = mSettings.get(userId);
+            fromGameMode = userSettings.getGameModeLocked(packageName);
             userSettings.setGameModeLocked(packageName, gameMode);
         }
         updateInterventions(packageName, gameMode, userId);
-        sendUserMessage(userId, WRITE_SETTINGS, "SET_GAME_MODE", WRITE_DELAY_MILLIS);
+        synchronized (mGameModeListenerLock) {
+            for (IGameModeListener listener : mGameModeListeners.keySet()) {
+                Binder.allowBlocking(listener.asBinder());
+                try {
+                    listener.onGameModeChanged(packageName, fromGameMode, gameMode, userId);
+                } catch (RemoteException ex) {
+                    Slog.w(TAG, "Cannot notify game mode change for listener added by "
+                            + mGameModeListeners.get(listener));
+                }
+            }
+        }
+        sendUserMessage(userId, WRITE_SETTINGS, EVENT_SET_GAME_MODE, WRITE_DELAY_MILLIS);
         sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
-                "SET_GAME_MODE", 0 /*delayMillis*/);
+                EVENT_SET_GAME_MODE, 0 /*delayMillis*/);
+        int gameUid = -1;
+        try {
+            gameUid = mPackageManager.getPackageUidAsUser(packageName, userId);
+        } catch (NameNotFoundException ex) {
+            Slog.d(TAG, "Cannot find the UID for package " + packageName + " under user " + userId);
+        }
+        FrameworkStatsLog.write(FrameworkStatsLog.GAME_MODE_CHANGED, gameUid,
+                Binder.getCallingUid(), gameModeToStatsdGameMode(fromGameMode),
+                gameModeToStatsdGameMode(gameMode));
     }
 
     /**
@@ -1122,17 +1240,16 @@
                 Binder.getCallingUid(), userId, false, true, "notifyGraphicsEnvironmentSetup",
                 "com.android.server.app.GameManagerService");
 
-        // Restrict to games only.
-        if (!isPackageGame(packageName, userId)) {
-            return;
-        }
-
         if (!isValidPackageName(packageName, userId)) {
+            Slog.d(TAG, "No-op for attempt to notify graphics env setup for different package"
+                    + "than caller with uid: " + Binder.getCallingUid());
             return;
         }
 
         final int gameMode = getGameMode(packageName, userId);
         if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+            Slog.d(TAG, "No-op for attempt to notify graphics env setup for non-game app: "
+                    + packageName);
             return;
         }
         int loadingBoostDuration = getLoadingBoostDuration(packageName, userId);
@@ -1234,6 +1351,121 @@
     }
 
     /**
+     * Updates the config for the game's {@link GameManager#GAME_MODE_CUSTOM} mode.
+     *
+     * @throws SecurityException        if caller doesn't have
+     *                                  {@link android.Manifest.permission#MANAGE_GAME_MODE}
+     *                                  permission.
+     * @throws IllegalArgumentException if the user ID provided doesn't exist.
+     */
+    @Override
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+    public void updateCustomGameModeConfiguration(String packageName,
+            GameModeConfiguration gameModeConfig, int userId)
+            throws SecurityException, IllegalArgumentException {
+        checkPermission(Manifest.permission.MANAGE_GAME_MODE);
+        if (!isPackageGame(packageName, userId)) {
+            Slog.d(TAG, "No-op for attempt to update custom game mode for non-game app: "
+                    + packageName);
+            return;
+        }
+        synchronized (mLock) {
+            if (!mSettings.containsKey(userId)) {
+                throw new IllegalArgumentException("User " + userId + " wasn't started");
+            }
+        }
+        // TODO(b/243448953): add validation on gameModeConfig provided
+        // Adding game mode config override of the given package name
+        GamePackageConfiguration configOverride;
+        synchronized (mLock) {
+            if (!mSettings.containsKey(userId)) {
+                return;
+            }
+            final GameManagerSettings settings = mSettings.get(userId);
+            // look for the existing GamePackageConfiguration override
+            configOverride = settings.getConfigOverride(packageName);
+            if (configOverride == null) {
+                configOverride = new GamePackageConfiguration(packageName);
+                settings.setConfigOverride(packageName, configOverride);
+            }
+
+        }
+        GamePackageConfiguration.GameModeConfiguration internalConfig =
+                configOverride.getOrAddDefaultGameModeConfiguration(GameManager.GAME_MODE_CUSTOM);
+        int gameUid = -1;
+        try {
+            gameUid = mPackageManager.getPackageUidAsUser(packageName, userId);
+        } catch (NameNotFoundException ex) {
+            Slog.d(TAG, "Cannot find the UID for package " + packageName + " under user " + userId);
+        }
+        FrameworkStatsLog.write(FrameworkStatsLog.GAME_MODE_CONFIGURATION_CHANGED, gameUid,
+                Binder.getCallingUid(), gameModeToStatsdGameMode(GameManager.GAME_MODE_CUSTOM),
+                internalConfig.getScaling(), gameModeConfig.getScalingFactor(),
+                internalConfig.getFps(), gameModeConfig.getFpsOverride());
+        internalConfig.updateFromPublicGameModeConfig(gameModeConfig);
+
+        Slog.i(TAG, "Updated custom game mode config for package: " + packageName
+                + " with FPS=" + internalConfig.getFps() + ";Scaling="
+                + internalConfig.getScaling() + " under user " + userId);
+
+        sendUserMessage(userId, WRITE_SETTINGS, EVENT_UPDATE_CUSTOM_GAME_MODE_CONFIG,
+                WRITE_DELAY_MILLIS);
+        sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
+                EVENT_UPDATE_CUSTOM_GAME_MODE_CONFIG, WRITE_DELAY_MILLIS /*delayMillis*/);
+    }
+
+    /**
+     * Adds a game mode listener.
+     *
+     * @throws SecurityException if caller doesn't have
+     *                           {@link android.Manifest.permission#MANAGE_GAME_MODE}
+     *                           permission.
+     */
+    @Override
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+    public void addGameModeListener(@NonNull IGameModeListener listener) {
+        checkPermission(Manifest.permission.MANAGE_GAME_MODE);
+        try {
+            final IBinder listenerBinder = listener.asBinder();
+            listenerBinder.linkToDeath(new DeathRecipient() {
+                @Override public void binderDied() {
+                    // TODO(b/258851194): add traces on binder death based listener removal
+                    removeGameModeListenerUnchecked(listener);
+                    listenerBinder.unlinkToDeath(this, 0 /*flags*/);
+                }
+            }, 0 /*flags*/);
+            synchronized (mGameModeListenerLock) {
+                mGameModeListeners.put(listener, Binder.getCallingUid());
+            }
+        } catch (RemoteException ex) {
+            Slog.e(TAG,
+                    "Failed to link death recipient for IGameModeListener from caller "
+                            + Binder.getCallingUid() + ", abandoned its listener registration", ex);
+        }
+    }
+
+    /**
+     * Removes a game mode listener.
+     *
+     * @throws SecurityException if caller doesn't have
+     *                           {@link android.Manifest.permission#MANAGE_GAME_MODE}
+     *                           permission.
+     */
+    @Override
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+    public void removeGameModeListener(@NonNull IGameModeListener listener) {
+        // TODO(b/258851194): add traces on manual listener removal
+        checkPermission(Manifest.permission.MANAGE_GAME_MODE);
+        removeGameModeListenerUnchecked(listener);
+    }
+
+    private void removeGameModeListenerUnchecked(IGameModeListener listener) {
+        synchronized (mGameModeListenerLock) {
+            mGameModeListeners.remove(listener);
+        }
+    }
+
+    /**
      * Notified when boot is completed.
      */
     @VisibleForTesting
@@ -1254,9 +1486,10 @@
                         for (Map.Entry<Integer, GameManagerSettings> entry : mSettings.entrySet()) {
                             final int userId = entry.getKey();
                             sendUserMessage(userId, WRITE_SETTINGS,
-                                    Intent.ACTION_SHUTDOWN, 0 /*delayMillis*/);
+                                    EVENT_RECEIVE_SHUTDOWN_INDENT, 0 /*delayMillis*/);
                             sendUserMessage(userId,
-                                    WRITE_GAME_MODE_INTERVENTION_LIST_FILE, Intent.ACTION_SHUTDOWN,
+                                    WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
+                                    EVENT_RECEIVE_SHUTDOWN_INDENT,
                                     0 /*delayMillis*/);
                         }
                     }
@@ -1281,7 +1514,8 @@
                 userSettings.readPersistentDataLocked();
             }
         }
-        sendUserMessage(userId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_STARTING", 0 /*delayMillis*/);
+        sendUserMessage(userId, POPULATE_GAME_MODE_SETTINGS, EVENT_ON_USER_STARTING,
+                0 /*delayMillis*/);
 
         if (mGameServiceController != null) {
             mGameServiceController.notifyUserStarted(user);
@@ -1301,7 +1535,7 @@
             if (!mSettings.containsKey(userId)) {
                 return;
             }
-            sendUserMessage(userId, REMOVE_SETTINGS, "ON_USER_STOPPING", 0 /*delayMillis*/);
+            sendUserMessage(userId, REMOVE_SETTINGS, EVENT_ON_USER_STOPPING, 0 /*delayMillis*/);
         }
 
         if (mGameServiceController != null) {
@@ -1311,17 +1545,10 @@
 
     void onUserSwitching(TargetUser from, TargetUser to) {
         final int toUserId = to.getUserIdentifier();
-        if (from != null) {
-            synchronized (mLock) {
-                final int fromUserId = from.getUserIdentifier();
-                if (mSettings.containsKey(fromUserId)) {
-                    sendUserMessage(fromUserId, REMOVE_SETTINGS, "ON_USER_SWITCHING",
-                            0 /*delayMillis*/);
-                }
-            }
-        }
-
-        sendUserMessage(toUserId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_SWITCHING",
+        // we want to re-populate the setting when switching user as the device config may have
+        // changed, which will only update for the previous user, see
+        // DeviceConfigListener#onPropertiesChanged.
+        sendUserMessage(toUserId, POPULATE_GAME_MODE_SETTINGS, EVENT_ON_USER_SWITCHING,
                 0 /*delayMillis*/);
 
         if (mGameServiceController != null) {
@@ -1380,7 +1607,8 @@
         final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
         if (gameMode == GameManager.GAME_MODE_STANDARD
                 || gameMode == GameManager.GAME_MODE_UNSUPPORTED || packageConfig == null
-                || packageConfig.willGamePerformOptimizations(gameMode)) {
+                || packageConfig.willGamePerformOptimizations(gameMode)
+                || packageConfig.getGameModeConfiguration(gameMode) == null) {
             resetFps(packageName, userId);
             // resolution scaling does not need to be reset as it's now read dynamically on game
             // restart, see #getResolutionScalingFactor and CompatModePackages#getCompatScale.
@@ -1389,8 +1617,9 @@
                 Slog.v(TAG, "Package configuration not found for " + packageName);
                 return;
             }
+        } else {
+            updateFps(packageConfig, packageName, gameMode, userId);
         }
-        updateFps(packageConfig, packageName, gameMode, userId);
         updateUseAngle(packageName, gameMode);
     }
 
@@ -1403,6 +1632,34 @@
     public void setGameModeConfigOverride(String packageName, @UserIdInt int userId,
             @GameMode int gameMode, String fpsStr, String scaling) throws SecurityException {
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
+        int gameUid = -1;
+        try {
+            gameUid = mPackageManager.getPackageUidAsUser(packageName, userId);
+        } catch (NameNotFoundException ex) {
+            Slog.d(TAG, "Cannot find the UID for package " + packageName + " under user " + userId);
+        }
+        GamePackageConfiguration pkgConfig = getConfig(packageName, userId);
+        if (pkgConfig != null && pkgConfig.getGameModeConfiguration(gameMode) != null) {
+            final GamePackageConfiguration.GameModeConfiguration currentModeConfig =
+                    pkgConfig.getGameModeConfiguration(gameMode);
+            FrameworkStatsLog.write(FrameworkStatsLog.GAME_MODE_CONFIGURATION_CHANGED, gameUid,
+                    Binder.getCallingUid(), gameModeToStatsdGameMode(gameMode),
+                    currentModeConfig.getScaling() /* fromScaling */,
+                    scaling == null ? currentModeConfig.getScaling()
+                            : Float.parseFloat(scaling) /* toScaling */,
+                    currentModeConfig.getFps() /* fromFps */,
+                    fpsStr == null ? currentModeConfig.getFps()
+                            : Integer.parseInt(fpsStr)) /* toFps */;
+        } else {
+            FrameworkStatsLog.write(FrameworkStatsLog.GAME_MODE_CONFIGURATION_CHANGED, gameUid,
+                    Binder.getCallingUid(), gameModeToStatsdGameMode(gameMode),
+                    GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING /* fromScaling*/,
+                    scaling == null ? GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING
+                            : Float.parseFloat(scaling) /* toScaling */,
+                    0 /* fromFps */,
+                    fpsStr == null ? 0 : Integer.parseInt(fpsStr) /* toFps */);
+        }
+
         // Adding game mode config override of the given package name
         GamePackageConfiguration configOverride;
         synchronized (mLock) {
@@ -1445,11 +1702,6 @@
     public void resetGameModeConfigOverride(String packageName, @UserIdInt int userId,
             @GameMode int gameModeToReset) throws SecurityException {
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
-        final GamePackageConfiguration deviceConfig;
-        synchronized (mDeviceConfigLock) {
-            deviceConfig = mConfigs.get(packageName);
-        }
-
         // resets GamePackageConfiguration of a given packageName.
         // If a gameMode is specified, only reset the GameModeConfiguration of the gameMode.
         synchronized (mLock) {
@@ -1467,13 +1719,9 @@
                 if (!bitFieldContainsModeBitmask(modesBitfield, gameModeToReset)) {
                     return;
                 }
-                // if the game mode to reset is the only mode other than standard mode or there
-                // is device config, the entire package config override is removed.
-                if (Integer.bitCount(modesBitfield) <= 2 || deviceConfig == null) {
+                configOverride.removeModeConfig(gameModeToReset);
+                if (!configOverride.hasActiveGameModeConfig()) {
                     settings.removeConfigOverride(packageName);
-                } else {
-                    // otherwise we reset the mode by removing the game mode config override
-                    configOverride.removeModeConfig(gameModeToReset);
                 }
             } else {
                 settings.removeConfigOverride(packageName);
@@ -1501,20 +1749,12 @@
             // want to check if we support selectable game modes
             modesBitfield &= ~modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED);
             if (!bitFieldContainsModeBitmask(modesBitfield, gameMode)) {
-                if (bitFieldContainsModeBitmask(modesBitfield,
-                        GameManager.GAME_MODE_STANDARD)) {
-                    // If the current set mode isn't supported,
-                    // but we support STANDARD, then set the mode to STANDARD.
-                    newGameMode = GameManager.GAME_MODE_STANDARD;
-                } else {
-                    // If we don't support any game modes, then set to UNSUPPORTED
-                    newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
-                }
+                // always default to STANDARD if there is no mode config
+                newGameMode = GameManager.GAME_MODE_STANDARD;
             }
-        } else if (gameMode != GameManager.GAME_MODE_UNSUPPORTED) {
-            // If we have no config for the package, but the configured mode is not
-            // UNSUPPORTED, then set to UNSUPPORTED
-            newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
+        } else {
+            // always default to STANDARD if there is no package config
+            newGameMode = GameManager.GAME_MODE_STANDARD;
         }
         return newGameMode;
     }
@@ -1777,13 +2017,99 @@
         LocalServices.addService(GameManagerInternal.class, new LocalService());
     }
 
-    private String dumpDeviceConfigs() {
-        StringBuilder out = new StringBuilder();
-        for (String key : mConfigs.keySet()) {
-            out.append("[\nName: ").append(key)
-                    .append("\nConfig: ").append(mConfigs.get(key).toString()).append("\n]");
+    private void registerStatsCallbacks() {
+        final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
+        statsManager.setPullAtomCallback(
+                FrameworkStatsLog.GAME_MODE_INFO,
+                null, // use default PullAtomMetadata values
+                BackgroundThread.getExecutor(),
+                this::onPullAtom);
+        statsManager.setPullAtomCallback(
+                FrameworkStatsLog.GAME_MODE_CONFIGURATION,
+                null, // use default PullAtomMetadata values
+                BackgroundThread.getExecutor(),
+                this::onPullAtom);
+        statsManager.setPullAtomCallback(
+                FrameworkStatsLog.GAME_MODE_LISTENER,
+                null, // use default PullAtomMetadata values
+                BackgroundThread.getExecutor(),
+                this::onPullAtom);
+    }
+
+    private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
+        if (atomTag == FrameworkStatsLog.GAME_MODE_INFO
+                || atomTag == FrameworkStatsLog.GAME_MODE_CONFIGURATION) {
+            int userId = ActivityManager.getCurrentUser();
+            Set<String> packages;
+            synchronized (mDeviceConfigLock) {
+                packages = mConfigs.keySet();
+            }
+            for (String p : packages) {
+                GamePackageConfiguration config = getConfig(p, userId);
+                if (config == null) {
+                    continue;
+                }
+                int uid = -1;
+                try {
+                    uid = mPackageManager.getPackageUidAsUser(p, userId);
+                } catch (NameNotFoundException ex) {
+                    Slog.d(TAG,
+                            "Cannot find UID for package " + p + " under user handle id " + userId);
+                }
+                if (atomTag == FrameworkStatsLog.GAME_MODE_INFO) {
+                    data.add(
+                            FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.GAME_MODE_INFO, uid,
+                                    gameModesToStatsdGameModes(config.getOverriddenGameModes()),
+                                    gameModesToStatsdGameModes(config.getAvailableGameModes())));
+                } else if (atomTag == FrameworkStatsLog.GAME_MODE_CONFIGURATION) {
+                    for (int gameMode : config.getAvailableGameModes()) {
+                        GamePackageConfiguration.GameModeConfiguration modeConfig =
+                                config.getGameModeConfiguration(gameMode);
+                        if (modeConfig != null) {
+                            data.add(FrameworkStatsLog.buildStatsEvent(
+                                    FrameworkStatsLog.GAME_MODE_CONFIGURATION, uid,
+                                    gameModeToStatsdGameMode(gameMode), modeConfig.getFps(),
+                                    modeConfig.getScaling()));
+                        }
+                    }
+                }
+            }
+        } else if (atomTag == FrameworkStatsLog.GAME_MODE_LISTENER) {
+            synchronized (mGameModeListenerLock) {
+                data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.GAME_MODE_LISTENER,
+                        mGameModeListeners.size()));
+            }
         }
-        return out.toString();
+        return android.app.StatsManager.PULL_SUCCESS;
+    }
+
+    private static int[] gameModesToStatsdGameModes(int[] modes) {
+        if (modes == null) {
+            return null;
+        }
+        int[] statsdModes = new int[modes.length];
+        int i = 0;
+        for (int mode : modes) {
+            statsdModes[i++] = gameModeToStatsdGameMode(mode);
+        }
+        return statsdModes;
+    }
+
+    private static int gameModeToStatsdGameMode(int mode) {
+        switch (mode) {
+            case GameManager.GAME_MODE_BATTERY:
+                return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_BATTERY;
+            case GameManager.GAME_MODE_PERFORMANCE:
+                return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_PERFORMANCE;
+            case GameManager.GAME_MODE_CUSTOM:
+                return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_CUSTOM;
+            case GameManager.GAME_MODE_STANDARD:
+                return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_STANDARD;
+            case GameManager.GAME_MODE_UNSUPPORTED:
+                return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_UNSUPPORTED;
+            default:
+                return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_UNSPECIFIED;
+        }
     }
 
     private static int gameStateModeToStatsdGameState(int mode) {
diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
index 1e68837..5189017 100644
--- a/services/core/java/com/android/server/app/GameManagerSettings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -19,6 +19,7 @@
 import android.app.GameManager;
 import android.os.FileUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
@@ -37,7 +38,6 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.util.Map;
 
 /**
  * Persists all GameService related settings.
@@ -49,7 +49,11 @@
     // The XML file follows the below format:
     // <?xml>
     // <packages>
-    //     <package></package>
+    //     <package name="" gameMode="">
+    //       <gameModeConfig gameMode="" fps="" scaling="" useAngle="" loadingBoost="">
+    //       </gameModeConfig>
+    //       ...
+    //     </package>
     //     ...
     // </packages>
     private static final String GAME_SERVICE_FILE_NAME = "game-manager-service.xml";
@@ -90,9 +94,14 @@
      */
     int getGameModeLocked(String packageName) {
         if (mGameModes.containsKey(packageName)) {
-            return mGameModes.get(packageName);
+            final int gameMode = mGameModes.get(packageName);
+            if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+                // force replace cached UNSUPPORTED mode with STANDARD starting in U
+                return GameManager.GAME_MODE_STANDARD;
+            }
+            return gameMode;
         }
-        return GameManager.GAME_MODE_UNSUPPORTED;
+        return GameManager.GAME_MODE_STANDARD;
     }
 
     /**
@@ -150,11 +159,14 @@
             serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
 
             serializer.startTag(null, TAG_PACKAGES);
-            for (Map.Entry<String, Integer> entry : mGameModes.entrySet()) {
-                String packageName = entry.getKey();
+            final ArraySet<String> packageNames = new ArraySet<>(mGameModes.keySet());
+            packageNames.addAll(mConfigOverrides.keySet());
+            for (String packageName : packageNames) {
                 serializer.startTag(null, TAG_PACKAGE);
                 serializer.attribute(null, ATTR_NAME, packageName);
-                serializer.attributeInt(null, ATTR_GAME_MODE, entry.getValue());
+                if (mGameModes.containsKey(packageName)) {
+                    serializer.attributeInt(null, ATTR_GAME_MODE, mGameModes.get(packageName));
+                }
                 writeGameModeConfigTags(serializer, mConfigOverrides.get(packageName));
                 serializer.endTag(null, TAG_PACKAGE);
             }
@@ -219,7 +231,7 @@
                 // Do nothing
             }
             if (type != XmlPullParser.START_TAG) {
-                Slog.wtf(TAG, "No start tag found in package manager settings");
+                Slog.wtf(TAG, "No start tag found in game manager settings");
                 return false;
             }
 
@@ -240,7 +252,7 @@
                 }
             }
         } catch (XmlPullParserException | java.io.IOException e) {
-            Slog.wtf(TAG, "Error reading package manager settings", e);
+            Slog.wtf(TAG, "Error reading game manager settings", e);
             return false;
         }
         return true;
@@ -255,15 +267,12 @@
             XmlUtils.skipCurrentTag(parser);
             return;
         }
-        int gameMode = GameManager.GAME_MODE_UNSUPPORTED;
         try {
-            gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
+            final int gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
+            mGameModes.put(name, gameMode);
         } catch (XmlPullParserException e) {
-            Slog.wtf(TAG, "Invalid game mode in package tag: "
-                    + parser.getAttributeValue(null, ATTR_GAME_MODE), e);
-            return;
+            Slog.v(TAG, "No game mode selected by user for package" + name);
         }
-        mGameModes.put(name, gameMode);
         final int packageTagDepth = parser.getDepth();
         int type;
         final GamePackageConfiguration config = new GamePackageConfiguration(name);
@@ -282,7 +291,7 @@
                         + type);
             }
         }
-        if (config.getAvailableGameModes().length > 1) {
+        if (config.hasActiveGameModeConfig()) {
             mConfigOverrides.put(name, config);
         }
     }
diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java
index cdbffbe..abab0e7 100644
--- a/services/core/java/com/android/server/app/GameManagerShellCommand.java
+++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java
@@ -27,6 +27,7 @@
 
 import java.io.PrintWriter;
 import java.util.Locale;
+import java.util.StringJoiner;
 
 /**
  * ShellCommands for GameManagerService.
@@ -34,8 +35,20 @@
  * Use with {@code adb shell cmd game ...}.
  */
 public class GameManagerShellCommand extends ShellCommand {
+    private static final String STANDARD_MODE_STR = "standard";
+    private static final String STANDARD_MODE_NUM = "1";
+    private static final String PERFORMANCE_MODE_STR = "performance";
+    private static final String PERFORMANCE_MODE_NUM = "2";
+    private static final String BATTERY_MODE_STR = "battery";
+    private static final String BATTERY_MODE_NUM = "3";
+    private static final String CUSTOM_MODE_STR = "custom";
+    private static final String CUSTOM_MODE_NUM = "4";
+    private static final String UNSUPPORTED_MODE_STR = "unsupported";
+    private static final String UNSUPPORTED_MODE_NUM = String.valueOf(
+            GameManager.GAME_MODE_UNSUPPORTED);
 
-    public GameManagerShellCommand() {}
+    public GameManagerShellCommand() {
+    }
 
     @Override
     public int onCommand(String cmd) {
@@ -46,10 +59,10 @@
         try {
             switch (cmd) {
                 case "set": {
-                    return runSetGameMode(pw);
+                    return runSetGameModeConfig(pw);
                 }
                 case "reset": {
-                    return runResetGameMode(pw);
+                    return runResetGameModeConfig(pw);
                 }
                 case "mode": {
                     /** The "mode" command allows setting a package's current game mode outside of
@@ -61,10 +74,13 @@
                      *          <PACKAGE_NAME> <CONFIG_STRING>`
                      * see: {@link GameManagerServiceTests#mockDeviceConfigAll()}
                      */
-                    return runGameMode(pw);
+                    return runSetGameMode(pw);
                 }
-                case "list": {
-                    return runGameList(pw);
+                case "list-modes": {
+                    return runListGameModes(pw);
+                }
+                case "list-configs": {
+                    return runListGameModeConfigs(pw);
                 }
                 default:
                     return handleDefaultCommands(cmd);
@@ -75,7 +91,21 @@
         return -1;
     }
 
-    private int runGameList(PrintWriter pw) throws ServiceNotFoundException, RemoteException {
+    private int runListGameModes(PrintWriter pw) throws ServiceNotFoundException, RemoteException {
+        final String packageName = getNextArgRequired();
+        final GameManagerService gameManagerService = (GameManagerService)
+                ServiceManager.getService(Context.GAME_SERVICE);
+        final StringJoiner sj = new StringJoiner(",");
+        for (int mode : gameManagerService.getAvailableGameModes(packageName,
+                ActivityManager.getCurrentUser())) {
+            sj.add(gameModeIntToString(mode));
+        }
+        pw.println(packageName + " has available game modes: [" + sj + "]");
+        return 0;
+    }
+
+    private int runListGameModeConfigs(PrintWriter pw)
+            throws ServiceNotFoundException, RemoteException {
         final String packageName = getNextArgRequired();
 
         final GameManagerService gameManagerService = (GameManagerService)
@@ -92,7 +122,7 @@
         return 0;
     }
 
-    private int runGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException {
+    private int runSetGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException {
         final String option = getNextOption();
         String userIdStr = null;
         if (option != null && option.equals("--user")) {
@@ -105,7 +135,9 @@
                 ServiceManager.getServiceOrThrow(Context.GAME_SERVICE));
         boolean batteryModeSupported = false;
         boolean perfModeSupported = false;
-        int[] modes = service.getAvailableGameModes(packageName);
+        int userId = userIdStr != null ? Integer.parseInt(userIdStr)
+                : ActivityManager.getCurrentUser();
+        int[] modes = service.getAvailableGameModes(packageName, userId);
         for (int mode : modes) {
             if (mode == GameManager.GAME_MODE_PERFORMANCE) {
                 perfModeSupported = true;
@@ -113,37 +145,47 @@
                 batteryModeSupported = true;
             }
         }
-        int userId = userIdStr != null ? Integer.parseInt(userIdStr)
-                : ActivityManager.getCurrentUser();
         switch (gameMode.toLowerCase()) {
-            case "1":
-            case "standard":
+            case STANDARD_MODE_NUM:
+            case STANDARD_MODE_STR:
                 // Standard mode can be used to specify loading ANGLE as the default OpenGL ES
                 // driver, so it should always be available.
                 service.setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId);
+                pw.println("Set game mode to `STANDARD` for user `" + userId + "` in game `"
+                        + packageName + "`");
                 break;
-            case "2":
-            case "performance":
+            case PERFORMANCE_MODE_NUM:
+            case PERFORMANCE_MODE_STR:
                 if (perfModeSupported) {
                     service.setGameMode(packageName, GameManager.GAME_MODE_PERFORMANCE,
                             userId);
+                    pw.println("Set game mode to `PERFORMANCE` for user `" + userId + "` in game `"
+                            + packageName + "`");
                 } else {
                     pw.println("Game mode: " + gameMode + " not supported by "
                             + packageName);
                     return -1;
                 }
                 break;
-            case "3":
-            case "battery":
+            case BATTERY_MODE_NUM:
+            case BATTERY_MODE_STR:
                 if (batteryModeSupported) {
                     service.setGameMode(packageName, GameManager.GAME_MODE_BATTERY,
                             userId);
+                    pw.println("Set game mode to `BATTERY` for user `" + userId + "` in game `"
+                            + packageName + "`");
                 } else {
                     pw.println("Game mode: " + gameMode + " not supported by "
                             + packageName);
                     return -1;
                 }
                 break;
+            case CUSTOM_MODE_NUM:
+            case CUSTOM_MODE_STR:
+                service.setGameMode(packageName, GameManager.GAME_MODE_CUSTOM, userId);
+                pw.println("Set game mode to `CUSTOM` for user `" + userId + "` in game `"
+                        + packageName + "`");
+                break;
             default:
                 pw.println("Invalid game mode: " + gameMode);
                 return -1;
@@ -151,15 +193,9 @@
         return 0;
     }
 
-    private int runSetGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException {
-        String option = getNextArgRequired();
-        if (!option.equals("--mode")) {
-            pw.println("Invalid option '" + option + "'");
-            return -1;
-        }
-
-        final String gameMode = getNextArgRequired();
-
+    private int runSetGameModeConfig(PrintWriter pw)
+            throws ServiceNotFoundException, RemoteException {
+        String option;
         /**
          * handling optional input
          * "--user", "--downscale" and "--fps" can come in any order
@@ -167,8 +203,12 @@
         String userIdStr = null;
         String fpsStr = null;
         String downscaleRatio = null;
+        int gameMode = GameManager.GAME_MODE_CUSTOM;
         while ((option = getNextOption()) != null) {
             switch (option) {
+                case "--mode":
+                    gameMode = Integer.parseInt(getNextArgRequired());
+                    break;
                 case "--user":
                     if (userIdStr == null) {
                         userIdStr = getNextArgRequired();
@@ -220,50 +260,21 @@
 
         final GameManagerService gameManagerService = (GameManagerService)
                 ServiceManager.getService(Context.GAME_SERVICE);
-
-        boolean batteryModeSupported = false;
-        boolean perfModeSupported = false;
-        int [] modes = gameManagerService.getAvailableGameModes(packageName);
-
-        for (int mode : modes) {
-            if (mode == GameManager.GAME_MODE_PERFORMANCE) {
-                perfModeSupported = true;
-            } else if (mode == GameManager.GAME_MODE_BATTERY) {
-                batteryModeSupported = true;
-            }
+        if (gameManagerService == null) {
+            pw.println("Failed to find GameManagerService on device");
+            return -1;
         }
-
-        switch (gameMode.toLowerCase(Locale.getDefault())) {
-            case "2":
-            case "performance":
-                if (perfModeSupported) {
-                    gameManagerService.setGameModeConfigOverride(packageName, userId,
-                            GameManager.GAME_MODE_PERFORMANCE, fpsStr, downscaleRatio);
-                } else {
-                    pw.println("Game mode: " + gameMode + " not supported by "
-                            + packageName);
-                    return -1;
-                }
-                break;
-            case "3":
-            case "battery":
-                if (batteryModeSupported) {
-                    gameManagerService.setGameModeConfigOverride(packageName, userId,
-                            GameManager.GAME_MODE_BATTERY, fpsStr, downscaleRatio);
-                } else {
-                    pw.println("Game mode: " + gameMode + " not supported by "
-                            + packageName);
-                    return -1;
-                }
-                break;
-            default:
-                pw.println("Invalid game mode: " + gameMode);
-                return -1;
-        }
+        gameManagerService.setGameModeConfigOverride(packageName, userId, gameMode,
+                fpsStr, downscaleRatio);
+        pw.println("Set custom mode intervention config for user `" + userId + "` in game `"
+                + packageName + "` as: `"
+                + "downscaling-ratio: " + downscaleRatio + ";"
+                + "fps-override: " + fpsStr + "`");
         return 0;
     }
 
-    private int runResetGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException {
+    private int runResetGameModeConfig(PrintWriter pw)
+            throws ServiceNotFoundException, RemoteException {
         String option = null;
         String gameMode = null;
         String userIdStr = null;
@@ -305,13 +316,13 @@
         }
 
         switch (gameMode.toLowerCase(Locale.getDefault())) {
-            case "2":
-            case "performance":
+            case PERFORMANCE_MODE_NUM:
+            case PERFORMANCE_MODE_STR:
                 gameManagerService.resetGameModeConfigOverride(packageName, userId,
                         GameManager.GAME_MODE_PERFORMANCE);
                 break;
-            case "3":
-            case "battery":
+            case BATTERY_MODE_NUM:
+            case BATTERY_MODE_STR:
                 gameManagerService.resetGameModeConfigOverride(packageName, userId,
                         GameManager.GAME_MODE_BATTERY);
                 break;
@@ -322,6 +333,22 @@
         return 0;
     }
 
+    private static String gameModeIntToString(@GameManager.GameMode int gameMode) {
+        switch (gameMode) {
+            case GameManager.GAME_MODE_BATTERY:
+                return BATTERY_MODE_STR;
+            case GameManager.GAME_MODE_PERFORMANCE:
+                return PERFORMANCE_MODE_STR;
+            case GameManager.GAME_MODE_CUSTOM:
+                return CUSTOM_MODE_STR;
+            case GameManager.GAME_MODE_STANDARD:
+                return STANDARD_MODE_STR;
+            case GameManager.GAME_MODE_UNSUPPORTED:
+                return UNSUPPORTED_MODE_STR;
+        }
+        return "";
+    }
+
     @Override
     public void onHelp() {
         PrintWriter pw = getOutPrintWriter();
@@ -329,21 +356,28 @@
         pw.println("  help");
         pw.println("      Print this help text.");
         pw.println("  downscale");
-        pw.println("      Deprecated. Please use `set` command.");
-        pw.println("  mode [--user <USER_ID>] [1|2|3|standard|performance|battery] <PACKAGE_NAME>");
+        pw.println("      Deprecated. Please use `custom` command.");
+        pw.println("  list-configs <PACKAGE_NAME>");
+        pw.println("      Lists the current intervention configs of an app.");
+        pw.println("  list-modes <PACKAGE_NAME>");
+        pw.println("      Lists the current available game modes of an app.");
+        pw.println("  mode [--user <USER_ID>] [1|2|3|4|standard|performance|battery|custom] "
+                + "<PACKAGE_NAME>");
         pw.println("      Set app to run in the specified game mode, if supported.");
         pw.println("      --user <USER_ID>: apply for the given user,");
         pw.println("                        the current user is used when unspecified.");
-        pw.println("  set --mode [2|3|performance|battery] [intervention configs] <PACKAGE_NAME>");
-        pw.println("      Set app to run at given game mode with configs, if supported.");
+        pw.println("  set [intervention configs] <PACKAGE_NAME>");
+        pw.println("      Set app to run at custom mode using provided intervention configs");
         pw.println("      Intervention configs consists of:");
         pw.println("      --downscale [0.3|0.35|0.4|0.45|0.5|0.55|0.6|0.65");
-        pw.println("                  |0.7|0.75|0.8|0.85|0.9|disable]");
-        pw.println("      Set app to run at the specified scaling ratio.");
-        pw.println("      --fps [30|45|60|90|120|disable]");
-        pw.println("      Set app to run at the specified fps, if supported.");
+        pw.println("                  |0.7|0.75|0.8|0.85|0.9|disable]: Set app to run at the");
+        pw.println("                                                   specified scaling ratio.");
+        pw.println("      --fps [30|45|60|90|120|disable]: Set app to run at the specified fps,");
+        pw.println("                                       if supported.");
         pw.println("  reset [--mode [2|3|performance|battery] --user <USER_ID>] <PACKAGE_NAME>");
         pw.println("      Resets the game mode of the app to device configuration.");
+        pw.println("      This should only be used to reset any override to non custom game mode");
+        pw.println("      applied using the deprecated `set` command");
         pw.println("      --mode [2|3|performance|battery]: apply for the given mode,");
         pw.println("                                        resets all modes when unspecified.");
         pw.println("      --user <USER_ID>: apply for the given user,");
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 4aaf1ab..908cb3f 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -185,6 +185,8 @@
                 @Override
                 @EnforcePermission(MANAGE_GAME_ACTIVITY)
                 public void createGameSession(int taskId) {
+                    super.createGameSession_enforcePermission();
+
                     mBackgroundExecutor.execute(() -> {
                         GameServiceProviderInstanceImpl.this.createGameSession(taskId);
                     });
@@ -197,6 +199,8 @@
                 @EnforcePermission(MANAGE_GAME_ACTIVITY)
                 public void takeScreenshot(int taskId,
                         @NonNull AndroidFuture gameScreenshotResultFuture) {
+                    super.takeScreenshot_enforcePermission();
+
                     mBackgroundExecutor.execute(() -> {
                         GameServiceProviderInstanceImpl.this.takeScreenshot(taskId,
                                 gameScreenshotResultFuture);
@@ -206,6 +210,8 @@
                 @Override
                 @EnforcePermission(MANAGE_GAME_ACTIVITY)
                 public void restartGame(int taskId) {
+                    super.restartGame_enforcePermission();
+
                     mBackgroundExecutor.execute(() -> {
                         GameServiceProviderInstanceImpl.this.restartGame(taskId);
                     });
diff --git a/services/core/java/com/android/server/app/TEST_MAPPING b/services/core/java/com/android/server/app/TEST_MAPPING
index 0ba4d8c..82840ee 100644
--- a/services/core/java/com/android/server/app/TEST_MAPPING
+++ b/services/core/java/com/android/server/app/TEST_MAPPING
@@ -9,6 +9,23 @@
       ]
     },
     {
+      "name": "CtsStatsdAtomHostTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "include-filter": "android.cts.statsdatom.gamemanager"
+        }
+      ],
+      "file_patterns": [
+        "(/|^)GameManagerService.java"
+      ]
+    },
+    {
       "name": "FrameworksMockingServicesTests",
       "options": [
         {
@@ -18,6 +35,23 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "FrameworksCoreGameManagerTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "include-filter": "android.app"
+        }
+      ],
+      "file_patterns": [
+        "(/|^)GameManagerService.java", "(/|^)GameManagerSettings.java"
+      ]
     }
   ]
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
new file mode 100644
index 0000000..587fb04
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -0,0 +1,602 @@
+/*
+ * 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.appop;
+
+import static android.app.AppOpsManager.OP_NONE;
+import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
+import static android.app.AppOpsManager.opRestrictsRead;
+
+import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.Mode;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import libcore.util.EmptyArray;
+
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Objects;
+
+
+/**
+ * Legacy implementation for App-ops service's app-op mode (uid and package) storage and access.
+ * In the future this class will also include mode callbacks and op restrictions.
+ */
+public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface {
+
+    static final String TAG = "LegacyAppOpsServiceInterfaceImpl";
+
+    // Must be the same object that the AppOpsService is using for locking.
+    final Object mLock;
+    final Handler mHandler;
+    final Context mContext;
+    final SparseArray<int[]> mSwitchedOps;
+
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    final SparseArray<SparseIntArray> mUidModes = new SparseArray<>();
+
+    @GuardedBy("mLock")
+    final SparseArray<ArrayMap<String, SparseIntArray>> mUserPackageModes = new SparseArray<>();
+
+    final SparseArray<ArraySet<OnOpModeChangedListener>> mOpModeWatchers = new SparseArray<>();
+    final ArrayMap<String, ArraySet<OnOpModeChangedListener>> mPackageModeWatchers =
+            new ArrayMap<>();
+
+    final PersistenceScheduler mPersistenceScheduler;
+
+
+    // Constant meaning that any UID should be matched when dispatching callbacks
+    private static final int UID_ANY = -2;
+
+
+    AppOpsCheckingServiceImpl(PersistenceScheduler persistenceScheduler,
+                              @NonNull Object lock, Handler handler, Context context,
+                              SparseArray<int[]> switchedOps) {
+        this.mPersistenceScheduler = persistenceScheduler;
+        this.mLock = lock;
+        this.mHandler = handler;
+        this.mContext = context;
+        this.mSwitchedOps = switchedOps;
+    }
+
+    @Override
+    public SparseIntArray getNonDefaultUidModes(int uid) {
+        synchronized (mLock) {
+            SparseIntArray opModes = mUidModes.get(uid, null);
+            if (opModes == null) {
+                return new SparseIntArray();
+            }
+            return opModes.clone();
+        }
+    }
+
+    @Override
+    public int getUidMode(int uid, int op) {
+        synchronized (mLock) {
+            SparseIntArray opModes = mUidModes.get(uid, null);
+            if (opModes == null) {
+                return AppOpsManager.opToDefaultMode(op);
+            }
+            return opModes.get(op, AppOpsManager.opToDefaultMode(op));
+        }
+    }
+
+    @Override
+    public boolean setUidMode(int uid, int op, int mode) {
+        final int defaultMode = AppOpsManager.opToDefaultMode(op);
+        synchronized (mLock) {
+            SparseIntArray opModes = mUidModes.get(uid, null);
+            if (opModes == null) {
+                if (mode != defaultMode) {
+                    opModes = new SparseIntArray();
+                    mUidModes.put(uid, opModes);
+                    opModes.put(op, mode);
+                    mPersistenceScheduler.scheduleWriteLocked();
+                }
+            } else {
+                if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) {
+                    return false;
+                }
+                if (mode == defaultMode) {
+                    opModes.delete(op);
+                    if (opModes.size() <= 0) {
+                        opModes = null;
+                        mUidModes.delete(uid);
+                    }
+                } else {
+                    opModes.put(op, mode);
+                }
+                mPersistenceScheduler.scheduleWriteLocked();
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int getPackageMode(String packageName, int op, @UserIdInt int userId) {
+        synchronized (mLock) {
+            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
+            if (packageModes == null) {
+                return AppOpsManager.opToDefaultMode(op);
+            }
+            SparseIntArray opModes = packageModes.getOrDefault(packageName, null);
+            if (opModes == null) {
+                return AppOpsManager.opToDefaultMode(op);
+            }
+            return opModes.get(op, AppOpsManager.opToDefaultMode(op));
+        }
+    }
+
+    @Override
+    public void setPackageMode(String packageName, int op, @Mode int mode, @UserIdInt int userId) {
+        final int defaultMode = AppOpsManager.opToDefaultMode(op);
+        synchronized (mLock) {
+            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
+            if (packageModes == null) {
+                packageModes = new ArrayMap<>();
+                mUserPackageModes.put(userId, packageModes);
+            }
+            SparseIntArray opModes = packageModes.get(packageName);
+            if (opModes == null) {
+                if (mode != defaultMode) {
+                    opModes = new SparseIntArray();
+                    packageModes.put(packageName, opModes);
+                    opModes.put(op, mode);
+                    mPersistenceScheduler.scheduleWriteLocked();
+                }
+            } else {
+                if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) {
+                    return;
+                }
+                if (mode == defaultMode) {
+                    opModes.delete(op);
+                    if (opModes.size() <= 0) {
+                        opModes = null;
+                        packageModes.remove(packageName);
+                    }
+                } else {
+                    opModes.put(op, mode);
+                }
+                mPersistenceScheduler.scheduleWriteLocked();
+            }
+        }
+    }
+
+    @Override
+    public void removeUid(int uid) {
+        synchronized (mLock) {
+            SparseIntArray opModes = mUidModes.get(uid);
+            if (opModes == null) {
+                return;
+            }
+            mUidModes.remove(uid);
+            mPersistenceScheduler.scheduleFastWriteLocked();
+        }
+    }
+
+    @Override
+    public boolean areUidModesDefault(int uid) {
+        synchronized (mLock) {
+            SparseIntArray opModes = mUidModes.get(uid);
+            return (opModes == null || opModes.size() <= 0);
+        }
+    }
+
+    @Override
+    public boolean arePackageModesDefault(@NonNull String packageMode, @UserIdInt int userId) {
+        synchronized (mLock) {
+            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
+            if (packageModes == null) {
+                return true;
+            }
+            SparseIntArray opModes = packageModes.get(packageMode);
+            return (opModes == null || opModes.size() <= 0);
+        }
+    }
+
+    @Override
+    public boolean removePackage(String packageName, @UserIdInt int userId) {
+        synchronized (mLock) {
+            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
+            if (packageModes == null) {
+                return false;
+            }
+            SparseIntArray ops = packageModes.remove(packageName);
+            if (ops != null) {
+                mPersistenceScheduler.scheduleFastWriteLocked();
+                return true;
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public void clearAllModes() {
+        synchronized (mLock) {
+            mUidModes.clear();
+            mUserPackageModes.clear();
+        }
+    }
+
+    @Override
+    public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener,
+            int op) {
+        Objects.requireNonNull(changedListener);
+        synchronized (mLock) {
+            ArraySet<OnOpModeChangedListener> modeWatcherSet = mOpModeWatchers.get(op);
+            if (modeWatcherSet == null) {
+                modeWatcherSet = new ArraySet<>();
+                mOpModeWatchers.put(op, modeWatcherSet);
+            }
+            modeWatcherSet.add(changedListener);
+        }
+    }
+
+    @Override
+    public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
+            @NonNull String packageName) {
+        Objects.requireNonNull(changedListener);
+        Objects.requireNonNull(packageName);
+        synchronized (mLock) {
+            ArraySet<OnOpModeChangedListener> modeWatcherSet =
+                    mPackageModeWatchers.get(packageName);
+            if (modeWatcherSet == null) {
+                modeWatcherSet = new ArraySet<>();
+                mPackageModeWatchers.put(packageName, modeWatcherSet);
+            }
+            modeWatcherSet.add(changedListener);
+        }
+    }
+
+    @Override
+    public void removeListener(@NonNull OnOpModeChangedListener changedListener) {
+        Objects.requireNonNull(changedListener);
+
+        synchronized (mLock) {
+            for (int i = mOpModeWatchers.size() - 1; i >= 0; i--) {
+                ArraySet<OnOpModeChangedListener> cbs = mOpModeWatchers.valueAt(i);
+                cbs.remove(changedListener);
+                if (cbs.size() <= 0) {
+                    mOpModeWatchers.removeAt(i);
+                }
+            }
+
+            for (int i = mPackageModeWatchers.size() - 1; i >= 0; i--) {
+                ArraySet<OnOpModeChangedListener> cbs = mPackageModeWatchers.valueAt(i);
+                cbs.remove(changedListener);
+                if (cbs.size() <= 0) {
+                    mPackageModeWatchers.removeAt(i);
+                }
+            }
+        }
+    }
+
+    @Override
+    public ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op) {
+        synchronized (mLock) {
+            ArraySet<OnOpModeChangedListener> modeChangedListenersSet = mOpModeWatchers.get(op);
+            if (modeChangedListenersSet == null) {
+                return new ArraySet<>();
+            }
+            return new ArraySet<>(modeChangedListenersSet);
+        }
+    }
+
+    @Override
+    public ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(
+            @NonNull String packageName) {
+        Objects.requireNonNull(packageName);
+
+        synchronized (mLock) {
+            ArraySet<OnOpModeChangedListener> modeChangedListenersSet =
+                    mPackageModeWatchers.get(packageName);
+            if (modeChangedListenersSet == null) {
+                return new ArraySet<>();
+            }
+            return new ArraySet<>(modeChangedListenersSet);
+        }
+    }
+
+    @Override
+    public void notifyWatchersOfChange(int code, int uid) {
+        ArraySet<OnOpModeChangedListener> listenerSet = getOpModeChangedListeners(code);
+        if (listenerSet == null) {
+            return;
+        }
+        for (int i = 0; i < listenerSet.size(); i++) {
+            final OnOpModeChangedListener listener = listenerSet.valueAt(i);
+            notifyOpChanged(listener, code, uid, null);
+        }
+    }
+
+    @Override
+    public void notifyOpChanged(@NonNull OnOpModeChangedListener onModeChangedListener, int code,
+            int uid, @Nullable String packageName) {
+        Objects.requireNonNull(onModeChangedListener);
+
+        if (uid != UID_ANY && onModeChangedListener.getWatchingUid() >= 0
+                && onModeChangedListener.getWatchingUid() != uid) {
+            return;
+        }
+
+        // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
+        int[] switchedCodes;
+        if (onModeChangedListener.getWatchedOpCode() == ALL_OPS) {
+            switchedCodes = mSwitchedOps.get(code);
+        } else if (onModeChangedListener.getWatchedOpCode() == OP_NONE) {
+            switchedCodes = new int[]{code};
+        } else {
+            switchedCodes = new int[]{onModeChangedListener.getWatchedOpCode()};
+        }
+
+        for (int switchedCode : switchedCodes) {
+            // There are features watching for mode changes such as window manager
+            // and location manager which are in our process. The callbacks in these
+            // features may require permissions our remote caller does not have.
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                if (shouldIgnoreCallback(switchedCode, onModeChangedListener.getCallingPid(),
+                        onModeChangedListener.getCallingUid())) {
+                    continue;
+                }
+                onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName);
+            } catch (RemoteException e) {
+                /* ignore */
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
+        // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
+        // as watcher should not use this to signal if the value is changed.
+        return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+                watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
+    }
+
+    @Override
+    public void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
+            @Nullable OnOpModeChangedListener callbackToIgnore) {
+        String[] uidPackageNames = getPackagesForUid(uid);
+        ArrayMap<OnOpModeChangedListener, ArraySet<String>> callbackSpecs = null;
+
+        synchronized (mLock) {
+            ArraySet<OnOpModeChangedListener> callbacks = mOpModeWatchers.get(code);
+            if (callbacks != null) {
+                final int callbackCount = callbacks.size();
+                for (int i = 0; i < callbackCount; i++) {
+                    OnOpModeChangedListener callback = callbacks.valueAt(i);
+
+                    if (onlyForeground && (callback.getFlags()
+                            & WATCH_FOREGROUND_CHANGES) == 0) {
+                        continue;
+                    }
+
+                    ArraySet<String> changedPackages = new ArraySet<>();
+                    Collections.addAll(changedPackages, uidPackageNames);
+                    if (callbackSpecs == null) {
+                        callbackSpecs = new ArrayMap<>();
+                    }
+                    callbackSpecs.put(callback, changedPackages);
+                }
+            }
+
+            for (String uidPackageName : uidPackageNames) {
+                callbacks = mPackageModeWatchers.get(uidPackageName);
+                if (callbacks != null) {
+                    if (callbackSpecs == null) {
+                        callbackSpecs = new ArrayMap<>();
+                    }
+                    final int callbackCount = callbacks.size();
+                    for (int i = 0; i < callbackCount; i++) {
+                        OnOpModeChangedListener callback = callbacks.valueAt(i);
+
+                        if (onlyForeground && (callback.getFlags()
+                                & WATCH_FOREGROUND_CHANGES) == 0) {
+                            continue;
+                        }
+
+                        ArraySet<String> changedPackages = callbackSpecs.get(callback);
+                        if (changedPackages == null) {
+                            changedPackages = new ArraySet<>();
+                            callbackSpecs.put(callback, changedPackages);
+                        }
+                        changedPackages.add(uidPackageName);
+                    }
+                }
+            }
+
+            if (callbackSpecs != null && callbackToIgnore != null) {
+                callbackSpecs.remove(callbackToIgnore);
+            }
+        }
+
+        if (callbackSpecs == null) {
+            return;
+        }
+
+        for (int i = 0; i < callbackSpecs.size(); i++) {
+            final OnOpModeChangedListener callback = callbackSpecs.keyAt(i);
+            final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
+            if (reportedPackageNames == null) {
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsCheckingServiceImpl::notifyOpChanged,
+                        this, callback, code, uid, (String) null));
+
+            } else {
+                final int reportedPackageCount = reportedPackageNames.size();
+                for (int j = 0; j < reportedPackageCount; j++) {
+                    final String reportedPackageName = reportedPackageNames.valueAt(j);
+                    mHandler.sendMessage(PooledLambda.obtainMessage(
+                            AppOpsCheckingServiceImpl::notifyOpChanged,
+                            this, callback, code, uid, reportedPackageName));
+                }
+            }
+        }
+    }
+
+    private static String[] getPackagesForUid(int uid) {
+        String[] packageNames = null;
+
+        // Very early during boot the package manager is not yet or not yet fully started. At this
+        // time there are no packages yet.
+        if (AppGlobals.getPackageManager() != null) {
+            try {
+                packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
+            } catch (RemoteException e) {
+                /* ignore - local call */
+            }
+        }
+        if (packageNames == null) {
+            return EmptyArray.STRING;
+        }
+        return packageNames;
+    }
+
+    @Override
+    public SparseBooleanArray evalForegroundUidOps(int uid,
+            @Nullable SparseBooleanArray foregroundOps) {
+        synchronized (mLock) {
+            return evalForegroundOps(mUidModes.get(uid), foregroundOps);
+        }
+    }
+
+    @Override
+    public SparseBooleanArray evalForegroundPackageOps(@NonNull String packageName,
+            @Nullable SparseBooleanArray foregroundOps, @UserIdInt int userId) {
+        synchronized (mLock) {
+            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
+            return evalForegroundOps(packageModes == null ? null : packageModes.get(packageName),
+                    foregroundOps);
+        }
+    }
+
+    private SparseBooleanArray evalForegroundOps(SparseIntArray opModes,
+            SparseBooleanArray foregroundOps) {
+        SparseBooleanArray tempForegroundOps = foregroundOps;
+        if (opModes != null) {
+            for (int i = opModes.size() - 1; i >= 0; i--) {
+                if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) {
+                    if (tempForegroundOps == null) {
+                        tempForegroundOps = new SparseBooleanArray();
+                    }
+                    evalForegroundWatchers(opModes.keyAt(i), tempForegroundOps);
+                }
+            }
+        }
+        return tempForegroundOps;
+    }
+
+    private void evalForegroundWatchers(int op, SparseBooleanArray foregroundOps) {
+        boolean curValue = foregroundOps.get(op, false);
+        ArraySet<OnOpModeChangedListener> listenerSet = mOpModeWatchers.get(op);
+        if (listenerSet != null) {
+            for (int cbi = listenerSet.size() - 1; !curValue && cbi >= 0; cbi--) {
+                if ((listenerSet.valueAt(cbi).getFlags()
+                        & AppOpsManager.WATCH_FOREGROUND_CHANGES) != 0) {
+                    curValue = true;
+                }
+            }
+        }
+        foregroundOps.put(op, curValue);
+    }
+
+    @Override
+    public boolean dumpListeners(int dumpOp, int dumpUid, @Nullable String dumpPackage,
+            @NonNull PrintWriter printWriter) {
+        boolean needSep = false;
+        if (mOpModeWatchers.size() > 0) {
+            boolean printedHeader = false;
+            for (int i = 0; i < mOpModeWatchers.size(); i++) {
+                if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) {
+                    continue;
+                }
+                boolean printedOpHeader = false;
+                ArraySet<OnOpModeChangedListener> modeChangedListenerSet =
+                        mOpModeWatchers.valueAt(i);
+                for (int j = 0; j < modeChangedListenerSet.size(); j++) {
+                    final OnOpModeChangedListener listener = modeChangedListenerSet.valueAt(j);
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(listener.getWatchingUid())) {
+                        continue;
+                    }
+                    needSep = true;
+                    if (!printedHeader) {
+                        printWriter.println("  Op mode watchers:");
+                        printedHeader = true;
+                    }
+                    if (!printedOpHeader) {
+                        printWriter.print("    Op ");
+                        printWriter.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
+                        printWriter.println(":");
+                        printedOpHeader = true;
+                    }
+                    printWriter.print("      #"); printWriter.print(j); printWriter.print(": ");
+                    printWriter.println(listener.toString());
+                }
+            }
+        }
+
+        if (mPackageModeWatchers.size() > 0 && dumpOp < 0) {
+            boolean printedHeader = false;
+            for (int i = 0; i < mPackageModeWatchers.size(); i++) {
+                if (dumpPackage != null
+                        && !dumpPackage.equals(mPackageModeWatchers.keyAt(i))) {
+                    continue;
+                }
+                needSep = true;
+                if (!printedHeader) {
+                    printWriter.println("  Package mode watchers:");
+                    printedHeader = true;
+                }
+                printWriter.print("    Pkg "); printWriter.print(mPackageModeWatchers.keyAt(i));
+                printWriter.println(":");
+                ArraySet<OnOpModeChangedListener> modeChangedListenerSet =
+                        mPackageModeWatchers.valueAt(i);
+
+                for (int j = 0; j < modeChangedListenerSet.size(); j++) {
+                    printWriter.print("      #"); printWriter.print(j); printWriter.print(": ");
+                    printWriter.println(modeChangedListenerSet.valueAt(j).toString());
+                }
+            }
+        }
+        return needSep;
+    }
+
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
new file mode 100644
index 0000000..ef3e368
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -0,0 +1,210 @@
+/*
+ * 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.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppOpsManager.Mode;
+import android.util.ArraySet;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface for accessing and modifying modes for app-ops i.e. package and uid modes.
+ * This interface also includes functions for added and removing op mode watchers.
+ * In the future this interface will also include op restrictions.
+ */
+public interface AppOpsCheckingServiceInterface {
+    /**
+     * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid.
+     * Returns an empty SparseIntArray if nothing is set.
+     * @param uid for which we need the app-ops and their modes.
+     */
+    SparseIntArray getNonDefaultUidModes(int uid);
+
+    /**
+     * Returns the app-op mode for a particular app-op of a uid.
+     * Returns default op mode if the op mode for particular uid and op is not set.
+     * @param uid user id for which we need the mode.
+     * @param op app-op for which we need the mode.
+     * @return mode of the app-op.
+     */
+    int getUidMode(int uid, int op);
+
+    /**
+     * Set the app-op mode for a particular uid and op.
+     * The mode is not set if the mode is the same as the default mode for the op.
+     * @param uid user id for which we want to set the mode.
+     * @param op app-op for which we want to set the mode.
+     * @param mode mode for the app-op.
+     * @return true if op mode is changed.
+     */
+    boolean setUidMode(int uid, int op, @Mode int mode);
+
+    /**
+     * Gets the app-op mode for a particular package.
+     * Returns default op mode if the op mode for the particular package is not set.
+     * @param packageName package name for which we need the op mode.
+     * @param op app-op for which we need the mode.
+     * @param userId user id associated with the package.
+     * @return the mode of the app-op.
+     */
+    int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId);
+
+    /**
+     * Sets the app-op mode for a particular package.
+     * @param packageName package name for which we need to set the op mode.
+     * @param op app-op for which we need to set the mode.
+     * @param mode the mode of the app-op.
+     * @param userId user id associated with the package.
+     *
+     */
+    void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId);
+
+    /**
+     * Stop tracking any app-op modes for a package.
+     * @param packageName Name of the package for which we want to remove all mode tracking.
+     * @param userId user id associated with the package.
+     */
+    boolean removePackage(@NonNull String packageName,  @UserIdInt int userId);
+
+    /**
+     * Stop tracking any app-op modes for this uid.
+     * @param uid user id for which we want to remove all tracking.
+     */
+    void removeUid(int uid);
+
+    /**
+     * Returns true if all uid modes for this uid are
+     * in default state.
+     * @param uid user id
+     */
+    boolean areUidModesDefault(int uid);
+
+    /**
+     * Returns true if all package modes for this package name are
+     * in default state.
+     * @param packageName package name.
+     * @param userId user id associated with the package.
+     */
+    boolean arePackageModesDefault(@NonNull String packageName, @UserIdInt int userId);
+
+    /**
+     * Stop tracking app-op modes for all uid and packages.
+     */
+    void clearAllModes();
+
+    /**
+     * Registers changedListener to listen to op's mode change.
+     * @param changedListener the listener that must be trigger on the op's mode change.
+     * @param op op representing the app-op whose mode change needs to be listened to.
+     */
+    void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op);
+
+    /**
+     * Registers changedListener to listen to package's app-op's mode change.
+     * @param changedListener the listener that must be trigger on the mode change.
+     * @param packageName of the package whose app-op's mode change needs to be listened to.
+     */
+    void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
+            @NonNull String packageName);
+
+    /**
+     * Stop the changedListener from triggering on any mode change.
+     * @param changedListener the listener that needs to be removed.
+     */
+    void removeListener(@NonNull OnOpModeChangedListener changedListener);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Returns a set of OnOpModeChangedListener that are listening for op's mode changes.
+     * @param op app-op whose mode change is being listened to.
+     */
+    ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes.
+     * @param packageName of package whose app-op's mode change is being listened to.
+     */
+    ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Notify that the app-op's mode is changed by triggering the change listener.
+     * @param op App-op whose mode has changed
+     * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users)
+     */
+    void notifyWatchersOfChange(int op, int uid);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Notify that the app-op's mode is changed by triggering the change listener.
+     * @param changedListener the change listener.
+     * @param op App-op whose mode has changed
+     * @param uid user id associated with the app-op
+     * @param packageName package name that is associated with the app-op
+     */
+    void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
+            @Nullable String packageName);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Notify that the app-op's mode is changed to all packages associated with the uid by
+     * triggering the appropriate change listener.
+     * @param op App-op whose mode has changed
+     * @param uid user id associated with the app-op
+     * @param onlyForeground true if only watchers that
+     * @param callbackToIgnore callback that should be ignored.
+     */
+    void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
+            @Nullable OnOpModeChangedListener callbackToIgnore);
+
+    /**
+     * TODO: Move hasForegroundWatchers and foregroundOps into this.
+     * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in
+     * foregroundOps.
+     * @param uid for which the app-op's mode needs to be marked.
+     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
+     * @return  foregroundOps.
+     */
+    SparseBooleanArray evalForegroundUidOps(int uid, @Nullable SparseBooleanArray foregroundOps);
+
+    /**
+     * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
+     * foregroundOps.
+     * @param packageName for which the app-op's mode needs to be marked.
+     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
+     * @param userId user id associated with the package.
+     * @return foregroundOps.
+     */
+    SparseBooleanArray evalForegroundPackageOps(@NonNull String packageName,
+            @Nullable SparseBooleanArray foregroundOps, @UserIdInt int userId);
+
+    /**
+     * Dump op mode and package mode listeners and their details.
+     * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an
+     *               app-op, only the watchers for that app-op are dumped.
+     * @param dumpUid uid for which we want to dump op mode watchers.
+     * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
+     * @param printWriter writer to dump to.
+     */
+    boolean dumpListeners(int dumpOp, int dumpUid, @Nullable String dumpPackage,
+            @NonNull PrintWriter printWriter);
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
index adfd2af..af5b07e 100644
--- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -42,7 +42,7 @@
 
     private Context mContext;
     private Handler mHandler;
-    private AppOpsServiceInterface mAppOpsServiceInterface;
+    private AppOpsCheckingServiceInterface mAppOpsServiceInterface;
 
     // Map from (Object token) to (int code) to (boolean restricted)
     private final ArrayMap<Object, SparseBooleanArray> mGlobalRestrictions = new ArrayMap<>();
@@ -56,7 +56,7 @@
             mUserRestrictionExcludedPackageTags = new ArrayMap<>();
 
     public AppOpsRestrictionsImpl(Context context, Handler handler,
-            AppOpsServiceInterface appOpsServiceInterface) {
+            AppOpsCheckingServiceInterface appOpsServiceInterface) {
         mContext = context;
         mHandler = handler;
         mAppOpsServiceInterface = appOpsServiceInterface;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a58583c..39338c6 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -18,55 +18,22 @@
 
 import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
 import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED;
-import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
-import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
-import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
-import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
-import static android.app.AppOpsManager.FILTER_BY_UID;
-import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
-import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
-import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_ERRORED;
-import static android.app.AppOpsManager.MODE_FOREGROUND;
-import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
 import static android.app.AppOpsManager.OP_FLAG_SELF;
 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
 import static android.app.AppOpsManager.OP_NONE;
-import static android.app.AppOpsManager.OP_PLAY_AUDIO;
-import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
-import static android.app.AppOpsManager.OP_VIBRATE;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
-import static android.app.AppOpsManager.OpEventProxyInfo;
-import static android.app.AppOpsManager.RestrictionBypass;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS;
-import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
 import static android.app.AppOpsManager._NUM_OP;
-import static android.app.AppOpsManager.extractFlagsFromKey;
-import static android.app.AppOpsManager.extractUidStateFromKey;
-import static android.app.AppOpsManager.modeToName;
-import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
 import static android.app.AppOpsManager.opRestrictsRead;
-import static android.app.AppOpsManager.opToName;
 import static android.app.AppOpsManager.opToPublicName;
-import static android.content.Intent.ACTION_PACKAGE_REMOVED;
-import static android.content.Intent.EXTRA_REPLACING;
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
 
-import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
-
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -75,21 +42,16 @@
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
-import android.app.AppOpsManager.AttributedOpEntry;
 import android.app.AppOpsManager.AttributionFlags;
 import android.app.AppOpsManager.HistoricalOps;
-import android.app.AppOpsManager.Mode;
-import android.app.AppOpsManager.OpEntry;
 import android.app.AppOpsManager.OpFlags;
 import android.app.AppOpsManagerInternal;
 import android.app.AppOpsManagerInternal.CheckOpsDelegate;
 import android.app.AsyncNotedAppOp;
 import android.app.RuntimeAppOpAccessMessage;
 import android.app.SyncNotedAppOp;
-import android.app.admin.DevicePolicyManagerInternal;
 import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -97,15 +59,11 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PermissionInfo;
-import android.database.ContentObserver;
 import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
-import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.PackageTagsList;
 import android.os.Process;
@@ -116,22 +74,14 @@
 import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
-import android.os.SystemClock;
 import android.os.UserHandle;
-import android.os.storage.StorageManagerInternal;
-import android.permission.PermissionManager;
-import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.KeyValueListParser;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
-import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.Immutable;
@@ -143,59 +93,37 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IAppOpsStartedCallback;
 import com.android.internal.app.MessageSamplingConfig;
-import com.android.internal.compat.IPlatformCompat;
-import com.android.internal.os.Clock;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
-import com.android.server.LockGuard;
-import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemServiceManager;
 import com.android.server.pm.PackageList;
-import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedAttribution;
 import com.android.server.policy.AppOpsPolicy;
 
-import dalvik.annotation.optimization.NeverCompile;
-
-import libcore.util.EmptyArray;
-
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
 import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Scanner;
-import java.util.Set;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.function.Consumer;
 
-public class AppOpsService extends IAppOpsService.Stub implements PersistenceScheduler {
+/**
+ * The system service component to {@link AppOpsManager}.
+ */
+public class AppOpsService extends IAppOpsService.Stub {
+
+    private final AppOpsServiceInterface mAppOpsService;
+
     static final String TAG = "AppOps";
     static final boolean DEBUG = false;
 
@@ -204,57 +132,19 @@
      */
     private final ArraySet<NoteOpTrace> mNoteOpCallerStacktraces = new ArraySet<>();
 
-    private static final int NO_VERSION = -1;
-    /** Increment by one every time and add the corresponding upgrade logic in
-     *  {@link #upgradeLocked(int)} below. The first version was 1 */
-    private static final int CURRENT_VERSION = 1;
-
-    // Write at most every 30 minutes.
-    static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
-
     // Constant meaning that any UID should be matched when dispatching callbacks
     private static final int UID_ANY = -2;
 
-    private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
-            OP_PLAY_AUDIO,
-            OP_RECORD_AUDIO,
-            OP_CAMERA,
-            OP_VIBRATE,
-    };
-
     private static final int MAX_UNFORWARDED_OPS = 10;
-    private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
+
     private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000;
 
     final Context mContext;
-    final AtomicFile mFile;
     private final @Nullable File mNoteOpCallerStacktracesFile;
     final Handler mHandler;
 
-    /**
-     * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
-     * objects
-     */
-    @GuardedBy("this")
-    final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
-            new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
-
-    /**
-     * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
-     * new objects
-     */
-    @GuardedBy("this")
-    final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
-            new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
-                    MAX_UNUSED_POOLED_OBJECTS);
-
     private final AppOpsManagerInternalImpl mAppOpsManagerInternal
             = new AppOpsManagerInternalImpl();
-    @Nullable private final DevicePolicyManagerInternal dpmi =
-            LocalServices.getService(DevicePolicyManagerInternal.class);
-
-    private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
-            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
 
     /**
      * Registered callbacks, called from {@link #collectAsyncNotedOp}.
@@ -281,54 +171,9 @@
 
     boolean mWriteNoteOpsScheduled;
 
-    boolean mWriteScheduled;
-    boolean mFastWriteScheduled;
-    final Runnable mWriteRunner = new Runnable() {
-        public void run() {
-            synchronized (AppOpsService.this) {
-                mWriteScheduled = false;
-                mFastWriteScheduled = false;
-                AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
-                    @Override protected Void doInBackground(Void... params) {
-                        writeState();
-                        return null;
-                    }
-                };
-                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
-            }
-        }
-    };
-
-    @GuardedBy("this")
-    @VisibleForTesting
-    final SparseArray<UidState> mUidStates = new SparseArray<>();
-
-    volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
-
-    /*
-     * These are app op restrictions imposed per user from various parties.
-     */
-    private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
-            new ArrayMap<>();
-
-    /*
-     * These are app op restrictions imposed globally from various parties within the system.
-     */
-    private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
-            new ArrayMap<>();
-
-    SparseIntArray mProfileOwners;
-
     private volatile CheckOpsDelegateDispatcher mCheckOpsDelegateDispatcher =
             new CheckOpsDelegateDispatcher(/*policy*/ null, /*delegate*/ null);
 
-    /**
-      * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
-      * changed
-      */
-    private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
-
-    private ActivityManagerInternal mActivityManagerInternal;
 
     /** Package sampled for message collection in the current session */
     @GuardedBy("this")
@@ -362,545 +207,8 @@
     /** Package Manager internal. Access via {@link #getPackageManagerInternal()} */
     private @Nullable PackageManagerInternal mPackageManagerInternal;
 
-    /** Interface for app-op modes.*/
-    @VisibleForTesting AppOpsServiceInterface mAppOpsServiceInterface;
-
-    /** Interface for app-op restrictions.*/
-    @VisibleForTesting AppOpsRestrictions mAppOpsRestrictions;
-
-    private AppOpsUidStateTracker mUidStateTracker;
-
-    /** Hands the definition of foreground and uid states */
-    @GuardedBy("this")
-    public AppOpsUidStateTracker getUidStateTracker() {
-        if (mUidStateTracker == null) {
-            mUidStateTracker = new AppOpsUidStateTrackerImpl(
-                    LocalServices.getService(ActivityManagerInternal.class),
-                    mHandler,
-                    r -> {
-                        synchronized (AppOpsService.this) {
-                            r.run();
-                        }
-                    },
-                    Clock.SYSTEM_CLOCK, mConstants);
-
-            mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
-                    this::onUidStateChanged);
-        }
-        return mUidStateTracker;
-    }
-
-    /**
-     * All times are in milliseconds. These constants are kept synchronized with the system
-     * global Settings. Any access to this class or its fields should be done while
-     * holding the AppOpsService lock.
-     */
-    final class Constants extends ContentObserver {
-
-        /**
-         * How long we want for a drop in uid state from top to settle before applying it.
-         * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
-         */
-        public long TOP_STATE_SETTLE_TIME;
-
-        /**
-         * How long we want for a drop in uid state from foreground to settle before applying it.
-         * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
-         */
-        public long FG_SERVICE_STATE_SETTLE_TIME;
-
-        /**
-         * How long we want for a drop in uid state from background to settle before applying it.
-         * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
-         */
-        public long BG_STATE_SETTLE_TIME;
-
-        private final KeyValueListParser mParser = new KeyValueListParser(',');
-        private ContentResolver mResolver;
-
-        public Constants(Handler handler) {
-            super(handler);
-            updateConstants();
-        }
-
-        public void startMonitoring(ContentResolver resolver) {
-            mResolver = resolver;
-            mResolver.registerContentObserver(
-                    Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
-                    false, this);
-            updateConstants();
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            updateConstants();
-        }
-
-        private void updateConstants() {
-            String value = mResolver != null ? Settings.Global.getString(mResolver,
-                    Settings.Global.APP_OPS_CONSTANTS) : "";
-
-            synchronized (AppOpsService.this) {
-                try {
-                    mParser.setString(value);
-                } catch (IllegalArgumentException e) {
-                    // Failed to parse the settings string, log this and move on
-                    // with defaults.
-                    Slog.e(TAG, "Bad app ops settings", e);
-                }
-                TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
-                FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
-                BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
-                        KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
-            }
-        }
-
-        void dump(PrintWriter pw) {
-            pw.println("  Settings:");
-
-            pw.print("    "); pw.print(KEY_TOP_STATE_SETTLE_TIME); pw.print("=");
-            TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
-            pw.println();
-            pw.print("    "); pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME); pw.print("=");
-            TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
-            pw.println();
-            pw.print("    "); pw.print(KEY_BG_STATE_SETTLE_TIME); pw.print("=");
-            TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
-            pw.println();
-        }
-    }
-
-    @VisibleForTesting
-    final Constants mConstants;
-
-    @VisibleForTesting
-    final class UidState {
-        public final int uid;
-
-        public ArrayMap<String, Ops> pkgOps;
-
-        // true indicates there is an interested observer, false there isn't but it has such an op
-        //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
-        public SparseBooleanArray foregroundOps;
-        public boolean hasForegroundWatchers;
-
-        public UidState(int uid) {
-            this.uid = uid;
-        }
-
-        public void clear() {
-            mAppOpsServiceInterface.removeUid(uid);
-            if (pkgOps != null) {
-                for (String packageName : pkgOps.keySet()) {
-                    mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
-                }
-            }
-            pkgOps = null;
-        }
-
-        public boolean isDefault() {
-            boolean areAllPackageModesDefault = true;
-            if (pkgOps != null) {
-                for (String packageName : pkgOps.keySet()) {
-                    if (!mAppOpsServiceInterface.arePackageModesDefault(packageName,
-                            UserHandle.getUserId(uid))) {
-                        areAllPackageModesDefault = false;
-                        break;
-                    }
-                }
-            }
-            return (pkgOps == null || pkgOps.isEmpty())
-                    && mAppOpsServiceInterface.areUidModesDefault(uid)
-                    && areAllPackageModesDefault;
-        }
-
-        // Functions for uid mode access and manipulation.
-        public SparseIntArray getNonDefaultUidModes() {
-            return mAppOpsServiceInterface.getNonDefaultUidModes(uid);
-        }
-
-        public int getUidMode(int op) {
-            return mAppOpsServiceInterface.getUidMode(uid, op);
-        }
-
-        public boolean setUidMode(int op, int mode) {
-            return mAppOpsServiceInterface.setUidMode(uid, op, mode);
-        }
-
-        @SuppressWarnings("GuardedBy")
-        int evalMode(int op, int mode) {
-            return getUidStateTracker().evalMode(uid, op, mode);
-        }
-
-        public void evalForegroundOps() {
-            foregroundOps = null;
-            foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
-            if (pkgOps != null) {
-                for (int i = pkgOps.size() - 1; i >= 0; i--) {
-                    foregroundOps = mAppOpsServiceInterface
-                            .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps,
-                                    UserHandle.getUserId(uid));
-                }
-            }
-            hasForegroundWatchers = false;
-            if (foregroundOps != null) {
-                for (int i = 0;  i < foregroundOps.size(); i++) {
-                    if (foregroundOps.valueAt(i)) {
-                        hasForegroundWatchers = true;
-                        break;
-                    }
-                }
-            }
-        }
-
-        @SuppressWarnings("GuardedBy")
-        public int getState() {
-            return getUidStateTracker().getUidState(uid);
-        }
-
-        @SuppressWarnings("GuardedBy")
-        public void dump(PrintWriter pw, long nowElapsed) {
-            getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
-        }
-    }
-
-    final static class Ops extends SparseArray<Op> {
-        final String packageName;
-        final UidState uidState;
-
-        /**
-         * The restriction properties of the package. If {@code null} it could not have been read
-         * yet and has to be refreshed.
-         */
-        @Nullable RestrictionBypass bypass;
-
-        /** Lazily populated cache of attributionTags of this package */
-        final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
-
-        /**
-         * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
-         * than or equal to {@link #knownAttributionTags}.
-         */
-        final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
-
-        Ops(String _packageName, UidState _uidState) {
-            packageName = _packageName;
-            uidState = _uidState;
-        }
-    }
-
-    /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */
-    private static final class PackageVerificationResult {
-
-        final RestrictionBypass bypass;
-        final boolean isAttributionTagValid;
-
-        PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
-            this.bypass = bypass;
-            this.isAttributionTagValid = isAttributionTagValid;
-        }
-    }
-
-    final class Op {
-        int op;
-        int uid;
-        final UidState uidState;
-        final @NonNull String packageName;
-
-        /** attributionTag -> AttributedOp */
-        final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
-
-        Op(UidState uidState, String packageName, int op, int uid) {
-            this.op = op;
-            this.uid = uid;
-            this.uidState = uidState;
-            this.packageName = packageName;
-        }
-
-        @Mode int getMode() {
-            return mAppOpsServiceInterface.getPackageMode(packageName, this.op,
-                    UserHandle.getUserId(this.uid));
-        }
-        void setMode(@Mode int mode) {
-            mAppOpsServiceInterface.setPackageMode(packageName, this.op, mode,
-                    UserHandle.getUserId(this.uid));
-        }
-
-        void removeAttributionsWithNoTime() {
-            for (int i = mAttributions.size() - 1; i >= 0; i--) {
-                if (!mAttributions.valueAt(i).hasAnyTime()) {
-                    mAttributions.removeAt(i);
-                }
-            }
-        }
-
-        private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
-                @Nullable String attributionTag) {
-            AttributedOp attributedOp;
-
-            attributedOp = mAttributions.get(attributionTag);
-            if (attributedOp == null) {
-                attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent);
-                mAttributions.put(attributionTag, attributedOp);
-            }
-
-            return attributedOp;
-        }
-
-        @NonNull OpEntry createEntryLocked() {
-            final int numAttributions = mAttributions.size();
-
-            final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
-                    new ArrayMap<>(numAttributions);
-            for (int i = 0; i < numAttributions; i++) {
-                attributionEntries.put(mAttributions.keyAt(i),
-                        mAttributions.valueAt(i).createAttributedOpEntryLocked());
-            }
-
-            return new OpEntry(op, getMode(), attributionEntries);
-        }
-
-        @NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
-            final int numAttributions = mAttributions.size();
-
-            final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
-            for (int i = 0; i < numAttributions; i++) {
-                if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
-                    attributionEntries.put(mAttributions.keyAt(i),
-                            mAttributions.valueAt(i).createAttributedOpEntryLocked());
-                    break;
-                }
-            }
-
-            return new OpEntry(op, getMode(), attributionEntries);
-        }
-
-        boolean isRunning() {
-            final int numAttributions = mAttributions.size();
-            for (int i = 0; i < numAttributions; i++) {
-                if (mAttributions.valueAt(i).isRunning()) {
-                    return true;
-                }
-            }
-
-            return false;
-        }
-    }
-
-    final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
-    final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
-    final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
-    final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
     final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
 
-    final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient  {
-        /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
-        public static final int ALL_OPS = -2;
-
-        // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
-        // Otherwise we can just use the IBinder object.
-        private final IAppOpsCallback mCallback;
-
-        ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
-                int callingUid, int callingPid) {
-            super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
-            this.mCallback = callback;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("ModeCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, getWatchingUid());
-            sb.append(" flags=0x");
-            sb.append(Integer.toHexString(getFlags()));
-            switch (getWatchedOpCode()) {
-                case OP_NONE:
-                    break;
-                case ALL_OPS:
-                    sb.append(" op=(all)");
-                    break;
-                default:
-                    sb.append(" op=");
-                    sb.append(opToName(getWatchedOpCode()));
-                    break;
-            }
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, getCallingUid());
-            sb.append(" pid=");
-            sb.append(getCallingPid());
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void unlinkToDeath() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingMode(mCallback);
-        }
-
-        @Override
-        public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
-            mCallback.opChanged(op, uid, packageName);
-        }
-    }
-
-    final class ActiveCallback implements DeathRecipient {
-        final IAppOpsActiveCallback mCallback;
-        final int mWatchingUid;
-        final int mCallingUid;
-        final int mCallingPid;
-
-        ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
-                int callingPid) {
-            mCallback = callback;
-            mWatchingUid = watchingUid;
-            mCallingUid = callingUid;
-            mCallingPid = callingPid;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("ActiveCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, mWatchingUid);
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, mCallingUid);
-            sb.append(" pid=");
-            sb.append(mCallingPid);
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void destroy() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingActive(mCallback);
-        }
-    }
-
-    final class StartedCallback implements DeathRecipient {
-        final IAppOpsStartedCallback mCallback;
-        final int mWatchingUid;
-        final int mCallingUid;
-        final int mCallingPid;
-
-        StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
-                int callingPid) {
-            mCallback = callback;
-            mWatchingUid = watchingUid;
-            mCallingUid = callingUid;
-            mCallingPid = callingPid;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("StartedCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, mWatchingUid);
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, mCallingUid);
-            sb.append(" pid=");
-            sb.append(mCallingPid);
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void destroy() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingStarted(mCallback);
-        }
-    }
-
-    final class NotedCallback implements DeathRecipient {
-        final IAppOpsNotedCallback mCallback;
-        final int mWatchingUid;
-        final int mCallingUid;
-        final int mCallingPid;
-
-        NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
-                int callingPid) {
-            mCallback = callback;
-            mWatchingUid = watchingUid;
-            mCallingUid = callingUid;
-            mCallingPid = callingPid;
-            try {
-                mCallback.asBinder().linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                /*ignored*/
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("NotedCallback{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, mWatchingUid);
-            sb.append(" from uid=");
-            UserHandle.formatUid(sb, mCallingUid);
-            sb.append(" pid=");
-            sb.append(mCallingPid);
-            sb.append('}');
-            return sb.toString();
-        }
-
-        void destroy() {
-            mCallback.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void binderDied() {
-            stopWatchingNoted(mCallback);
-        }
-    }
-
-    /**
-     * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
-     */
-    static void onClientDeath(@NonNull AttributedOp attributedOp,
-            @NonNull IBinder clientId) {
-        attributedOp.onClientDeath(clientId);
-    }
-
-
     /**
      * Loads the OpsValidation file results into a hashmap {@link #mNoteOpCallerStacktraces}
      * so that we do not log the same operation twice between instances
@@ -925,20 +233,12 @@
     }
 
     public AppOpsService(File storagePath, Handler handler, Context context) {
-        mContext = context;
+        this(handler, context, new AppOpsServiceImpl(storagePath, handler, context));
+    }
 
-        for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
-            int switchCode = AppOpsManager.opToSwitch(switchedCode);
-            mSwitchedOps.put(switchCode,
-                    ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
-        }
-        mAppOpsServiceInterface =
-                new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps);
-        mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
-                mAppOpsServiceInterface);
-
-        LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
-        mFile = new AtomicFile(storagePath, "appops");
+    @VisibleForTesting
+    public AppOpsService(Handler handler, Context context,
+            AppOpsServiceInterface appOpsServiceInterface) {
         if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
             mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(),
                     "noteOpStackTraces.json");
@@ -946,185 +246,25 @@
         } else {
             mNoteOpCallerStacktracesFile = null;
         }
+
+        mAppOpsService = appOpsServiceInterface;
+        mContext = context;
         mHandler = handler;
-        mConstants = new Constants(mHandler);
-        readState();
     }
 
+    /**
+     * Publishes binder and local service.
+     */
     public void publish() {
         ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
         LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal);
     }
 
-    /** Handler for work when packages are removed or updated */
-    private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            String pkgName = intent.getData().getEncodedSchemeSpecificPart();
-            int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
-
-            if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
-                synchronized (AppOpsService.this) {
-                    UidState uidState = mUidStates.get(uid);
-                    if (uidState == null || uidState.pkgOps == null) {
-                        return;
-                    }
-                    mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid));
-                    Ops removedOps = uidState.pkgOps.remove(pkgName);
-                    if (removedOps != null) {
-                        scheduleFastWriteLocked();
-                    }
-                }
-            } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
-                AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
-                if (pkg == null) {
-                    return;
-                }
-
-                ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
-                ArraySet<String> attributionTags = new ArraySet<>();
-                attributionTags.add(null);
-                if (pkg.getAttributions() != null) {
-                    int numAttributions = pkg.getAttributions().size();
-                    for (int attributionNum = 0; attributionNum < numAttributions;
-                            attributionNum++) {
-                        ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
-                        attributionTags.add(attribution.getTag());
-
-                        int numInheritFrom = attribution.getInheritFrom().size();
-                        for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
-                                inheritFromNum++) {
-                            dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
-                                    attribution.getTag());
-                        }
-                    }
-                }
-
-                synchronized (AppOpsService.this) {
-                    UidState uidState = mUidStates.get(uid);
-                    if (uidState == null || uidState.pkgOps == null) {
-                        return;
-                    }
-
-                    Ops ops = uidState.pkgOps.get(pkgName);
-                    if (ops == null) {
-                        return;
-                    }
-
-                    // Reset cached package properties to re-initialize when needed
-                    ops.bypass = null;
-                    ops.knownAttributionTags.clear();
-
-                    // Merge data collected for removed attributions into their successor
-                    // attributions
-                    int numOps = ops.size();
-                    for (int opNum = 0; opNum < numOps; opNum++) {
-                        Op op = ops.valueAt(opNum);
-
-                        int numAttributions = op.mAttributions.size();
-                        for (int attributionNum = numAttributions - 1; attributionNum >= 0;
-                                attributionNum--) {
-                            String attributionTag = op.mAttributions.keyAt(attributionNum);
-
-                            if (attributionTags.contains(attributionTag)) {
-                                // attribution still exist after upgrade
-                                continue;
-                            }
-
-                            String newAttributionTag = dstAttributionTags.get(attributionTag);
-
-                            AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
-                                    newAttributionTag);
-                            newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
-                            op.mAttributions.removeAt(attributionNum);
-
-                            scheduleFastWriteLocked();
-                        }
-                    }
-                }
-            }
-        }
-    };
-
+    /**
+     * Finishes boot sequence.
+     */
     public void systemReady() {
-        mConstants.startMonitoring(mContext.getContentResolver());
-        mHistoricalRegistry.systemReady(mContext.getContentResolver());
-
-        IntentFilter packageUpdateFilter = new IntentFilter();
-        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-        packageUpdateFilter.addDataScheme("package");
-
-        mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
-                packageUpdateFilter, null, null);
-
-        synchronized (this) {
-            for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
-                int uid = mUidStates.keyAt(uidNum);
-                UidState uidState = mUidStates.valueAt(uidNum);
-
-                String[] pkgsInUid = getPackagesForUid(uidState.uid);
-                if (ArrayUtils.isEmpty(pkgsInUid)) {
-                    uidState.clear();
-                    mUidStates.removeAt(uidNum);
-                    scheduleFastWriteLocked();
-                    continue;
-                }
-
-                ArrayMap<String, Ops> pkgs = uidState.pkgOps;
-                if (pkgs == null) {
-                    continue;
-                }
-
-                int numPkgs = pkgs.size();
-                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
-                    String pkg = pkgs.keyAt(pkgNum);
-
-                    String action;
-                    if (!ArrayUtils.contains(pkgsInUid, pkg)) {
-                        action = Intent.ACTION_PACKAGE_REMOVED;
-                    } else {
-                        action = Intent.ACTION_PACKAGE_REPLACED;
-                    }
-
-                    SystemServerInitThreadPool.submit(
-                            () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
-                                    .setData(Uri.fromParts("package", pkg, null))
-                                    .putExtra(Intent.EXTRA_UID, uid)),
-                            "Update app-ops uidState in case package " + pkg + " changed");
-                }
-            }
-        }
-
-        final IntentFilter packageSuspendFilter = new IntentFilter();
-        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
-        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
-        mContext.registerReceiverAsUser(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
-                final String[] changedPkgs = intent.getStringArrayExtra(
-                        Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                for (int code : OPS_RESTRICTED_ON_SUSPEND) {
-                    ArraySet<OnOpModeChangedListener> onModeChangedListeners;
-                    synchronized (AppOpsService.this) {
-                        onModeChangedListeners =
-                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
-                        if (onModeChangedListeners == null) {
-                            continue;
-                        }
-                    }
-                    for (int i = 0; i < changedUids.length; i++) {
-                        final int changedUid = changedUids[i];
-                        final String changedPkg = changedPkgs[i];
-                        // We trust packagemanager to insert matching uid and packageNames in the
-                        // extras
-                        notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
-                    }
-                }
-            }
-        }, UserHandle.ALL, packageSuspendFilter, null, null);
+        mAppOpsService.systemReady();
 
         final IntentFilter packageAddedFilter = new IntentFilter();
         packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
@@ -1132,9 +272,8 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                final Uri data = intent.getData();
 
-                final String packageName = data.getSchemeSpecificPart();
+                final String packageName = intent.getData().getSchemeSpecificPart();
                 PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
                         PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
                 if (isSamplingTarget(pi)) {
@@ -1169,8 +308,6 @@
                         }
                     }
                 });
-
-        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
     }
 
     /**
@@ -1185,132 +322,18 @@
         mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(policy, delegate);
     }
 
+    /**
+     * Notify when a package is removed
+     */
     public void packageRemoved(int uid, String packageName) {
-        synchronized (this) {
-            UidState uidState = mUidStates.get(uid);
-            if (uidState == null) {
-                return;
-            }
-
-            Ops removedOps = null;
-
-            // Remove any package state if such.
-            if (uidState.pkgOps != null) {
-                removedOps = uidState.pkgOps.remove(packageName);
-                mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
-            }
-
-            // If we just nuked the last package state check if the UID is valid.
-            if (removedOps != null && uidState.pkgOps.isEmpty()
-                    && getPackagesForUid(uid).length <= 0) {
-                uidState.clear();
-                mUidStates.remove(uid);
-            }
-
-            if (removedOps != null) {
-                scheduleFastWriteLocked();
-
-                final int numOps = removedOps.size();
-                for (int opNum = 0; opNum < numOps; opNum++) {
-                    final Op op = removedOps.valueAt(opNum);
-
-                    final int numAttributions = op.mAttributions.size();
-                    for (int attributionNum = 0; attributionNum < numAttributions;
-                            attributionNum++) {
-                        AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
-
-                        while (attributedOp.isRunning()) {
-                            attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
-                        }
-                        while (attributedOp.isPaused()) {
-                            attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
-                        }
-                    }
-                }
-            }
-        }
-
-        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
-                    mHistoricalRegistry, uid, packageName));
+        mAppOpsService.packageRemoved(uid, packageName);
     }
 
+    /**
+     * Notify when a uid is removed.
+     */
     public void uidRemoved(int uid) {
-        synchronized (this) {
-            if (mUidStates.indexOfKey(uid) >= 0) {
-                mUidStates.get(uid).clear();
-                mUidStates.remove(uid);
-                scheduleFastWriteLocked();
-            }
-        }
-    }
-
-    // The callback method from ForegroundPolicyInterface
-    private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
-        synchronized (this) {
-            UidState uidState = getUidStateLocked(uid, true);
-
-            if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
-                for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
-                    if (!uidState.foregroundOps.valueAt(fgi)) {
-                        continue;
-                    }
-                    final int code = uidState.foregroundOps.keyAt(fgi);
-
-                    if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
-                            && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
-                        mHandler.sendMessage(PooledLambda.obtainMessage(
-                                AppOpsService::notifyOpChangedForAllPkgsInUid,
-                                this, code, uidState.uid, true, null));
-                    } else if (uidState.pkgOps != null) {
-                        final ArraySet<OnOpModeChangedListener> listenerSet =
-                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
-                        if (listenerSet != null) {
-                            for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
-                                final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
-                                if ((listener.getFlags()
-                                        & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
-                                        || !listener.isWatchingUid(uidState.uid)) {
-                                    continue;
-                                }
-                                for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
-                                    final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
-                                    if (op == null) {
-                                        continue;
-                                    }
-                                    if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
-                                        mHandler.sendMessage(PooledLambda.obtainMessage(
-                                                AppOpsService::notifyOpChanged,
-                                                this, listenerSet.valueAt(cbi), code, uidState.uid,
-                                                uidState.pkgOps.keyAt(pkgi)));
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-
-            if (uidState != null && uidState.pkgOps != null) {
-                int numPkgs = uidState.pkgOps.size();
-                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
-                    Ops ops = uidState.pkgOps.valueAt(pkgNum);
-
-                    int numOps = ops.size();
-                    for (int opNum = 0; opNum < numOps; opNum++) {
-                        Op op = ops.valueAt(opNum);
-
-                        int numAttributions = op.mAttributions.size();
-                        for (int attributionNum = 0; attributionNum < numAttributions;
-                                attributionNum++) {
-                            AttributedOp attributedOp = op.mAttributions.valueAt(
-                                    attributionNum);
-
-                            attributedOp.onUidStateChanged(state);
-                        }
-                    }
-                }
-            }
-        }
+        mAppOpsService.uidRemoved(uid);
     }
 
     /**
@@ -1318,542 +341,60 @@
      */
     public void updateUidProcState(int uid, int procState,
             @ActivityManager.ProcessCapability int capability) {
-        synchronized (this) {
-            getUidStateTracker().updateUidProcState(uid, procState, capability);
-            if (!mUidStates.contains(uid)) {
-                UidState uidState = new UidState(uid);
-                mUidStates.put(uid, uidState);
-                onUidStateChanged(uid,
-                        AppOpsUidStateTracker.processStateToUidState(procState), false);
-            }
-        }
+        mAppOpsService.updateUidProcState(uid, procState, capability);
     }
 
+    /**
+     * Initiates shutdown.
+     */
     public void shutdown() {
-        Slog.w(TAG, "Writing app ops before shutdown...");
-        boolean doWrite = false;
-        synchronized (this) {
-            if (mWriteScheduled) {
-                mWriteScheduled = false;
-                mFastWriteScheduled = false;
-                mHandler.removeCallbacks(mWriteRunner);
-                doWrite = true;
-            }
-        }
-        if (doWrite) {
-            writeState();
-        }
+        mAppOpsService.shutdown();
+
         if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) {
             writeNoteOps();
         }
-
-        mHistoricalRegistry.shutdown();
-    }
-
-    private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
-        ArrayList<AppOpsManager.OpEntry> resOps = null;
-        if (ops == null) {
-            resOps = new ArrayList<>();
-            for (int j=0; j<pkgOps.size(); j++) {
-                Op curOp = pkgOps.valueAt(j);
-                resOps.add(getOpEntryForResult(curOp));
-            }
-        } else {
-            for (int j=0; j<ops.length; j++) {
-                Op curOp = pkgOps.get(ops[j]);
-                if (curOp != null) {
-                    if (resOps == null) {
-                        resOps = new ArrayList<>();
-                    }
-                    resOps.add(getOpEntryForResult(curOp));
-                }
-            }
-        }
-        return resOps;
-    }
-
-    @Nullable
-    private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
-            @Nullable int[] ops) {
-        final SparseIntArray opModes = uidState.getNonDefaultUidModes();
-        if (opModes == null) {
-            return null;
-        }
-
-        int opModeCount = opModes.size();
-        if (opModeCount == 0) {
-            return null;
-        }
-        ArrayList<AppOpsManager.OpEntry> resOps = null;
-        if (ops == null) {
-            resOps = new ArrayList<>();
-            for (int i = 0; i < opModeCount; i++) {
-                int code = opModes.keyAt(i);
-                resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
-            }
-        } else {
-            for (int j=0; j<ops.length; j++) {
-                int code = ops[j];
-                if (opModes.indexOfKey(code) >= 0) {
-                    if (resOps == null) {
-                        resOps = new ArrayList<>();
-                    }
-                    resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
-                }
-            }
-        }
-        return resOps;
-    }
-
-    private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
-        return op.createEntryLocked();
     }
 
     @Override
     public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
-        final int callingUid = Binder.getCallingUid();
-        final boolean hasAllPackageAccess = mContext.checkPermission(
-                Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
-                Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
-        ArrayList<AppOpsManager.PackageOps> res = null;
-        synchronized (this) {
-            final int uidStateCount = mUidStates.size();
-            for (int i = 0; i < uidStateCount; i++) {
-                UidState uidState = mUidStates.valueAt(i);
-                if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
-                    continue;
-                }
-                ArrayMap<String, Ops> packages = uidState.pkgOps;
-                final int packageCount = packages.size();
-                for (int j = 0; j < packageCount; j++) {
-                    Ops pkgOps = packages.valueAt(j);
-                    ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
-                    if (resOps != null) {
-                        if (res == null) {
-                            res = new ArrayList<>();
-                        }
-                        AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
-                                pkgOps.packageName, pkgOps.uidState.uid, resOps);
-                        // Caller can always see their packages and with a permission all.
-                        if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
-                            res.add(resPackage);
-                        }
-                    }
-                }
-            }
-        }
-        return res;
+        return mAppOpsService.getPackagesForOps(ops);
     }
 
     @Override
     public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
             int[] ops) {
-        enforceGetAppOpsStatsPermissionIfNeeded(uid,packageName);
-        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return Collections.emptyList();
-        }
-        synchronized (this) {
-            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
-                    /* edit */ false);
-            if (pkgOps == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
-            if (resOps == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
-            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
-                    pkgOps.packageName, pkgOps.uidState.uid, resOps);
-            res.add(resPackage);
-            return res;
-        }
-    }
-
-    private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
-        final int callingUid = Binder.getCallingUid();
-        // We get to access everything
-        if (callingUid == Process.myPid()) {
-            return;
-        }
-        // Apps can access their own data
-        if (uid == callingUid && packageName != null
-                && checkPackage(uid, packageName) == MODE_ALLOWED) {
-            return;
-        }
-        // Otherwise, you need a permission...
-        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                Binder.getCallingPid(), callingUid, null);
-    }
-
-    /**
-     * Verify that historical appop request arguments are valid.
-     */
-    private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
-            String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
-            long endTimeMillis, int flags) {
-        if ((filter & FILTER_BY_UID) != 0) {
-            Preconditions.checkArgument(uid != Process.INVALID_UID);
-        } else {
-            Preconditions.checkArgument(uid == Process.INVALID_UID);
-        }
-
-        if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
-            Objects.requireNonNull(packageName);
-        } else {
-            Preconditions.checkArgument(packageName == null);
-        }
-
-        if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
-            Preconditions.checkArgument(attributionTag == null);
-        }
-
-        if ((filter & FILTER_BY_OP_NAMES) != 0) {
-            Objects.requireNonNull(opNames);
-        } else {
-            Preconditions.checkArgument(opNames == null);
-        }
-
-        Preconditions.checkFlagsArgument(filter,
-                FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
-                        | FILTER_BY_OP_NAMES);
-        Preconditions.checkArgumentNonnegative(beginTimeMillis);
-        Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
-        Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
+        return mAppOpsService.getOpsForPackage(uid, packageName, ops);
     }
 
     @Override
     public void getHistoricalOps(int uid, String packageName, String attributionTag,
             List<String> opNames, int dataType, int filter, long beginTimeMillis,
             long endTimeMillis, int flags, RemoteCallback callback) {
-        PackageManager pm = mContext.getPackageManager();
-
-        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
-                beginTimeMillis, endTimeMillis, flags);
-        Objects.requireNonNull(callback, "callback cannot be null");
-        ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
-        boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
-        if (!isSelfRequest) {
-            boolean isCallerInstrumented =
-                    ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
-            boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
-            boolean isCallerPermissionController;
-            try {
-                isCallerPermissionController = pm.getPackageUidAsUser(
-                        mContext.getPackageManager().getPermissionControllerPackageName(), 0,
-                        UserHandle.getUserId(Binder.getCallingUid()))
-                        == Binder.getCallingUid();
-            } catch (PackageManager.NameNotFoundException doesNotHappen) {
-                return;
-            }
-
-            boolean doesCallerHavePermission = mContext.checkPermission(
-                    android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
-                    Binder.getCallingPid(), Binder.getCallingUid())
-                    == PackageManager.PERMISSION_GRANTED;
-
-            if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
-                    && !doesCallerHavePermission) {
-                mHandler.post(() -> callback.sendResult(new Bundle()));
-                return;
-            }
-
-            mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                    Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
-        }
-
-        final String[] opNamesArray = (opNames != null)
-                ? opNames.toArray(new String[opNames.size()]) : null;
-
-        Set<String> attributionChainExemptPackages = null;
-        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
-            attributionChainExemptPackages =
-                    PermissionManager.getIndicatorExemptedPackages(mContext);
-        }
-
-        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
-                ? attributionChainExemptPackages.toArray(
-                        new String[attributionChainExemptPackages.size()]) : null;
-
-        // Must not hold the appops lock
-        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
-                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
-                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
-                callback).recycleOnUse());
+        mAppOpsService.getHistoricalOps(uid, packageName, attributionTag, opNames,
+                dataType, filter, beginTimeMillis, endTimeMillis, flags, callback);
     }
 
     @Override
     public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
             List<String> opNames, int dataType, int filter, long beginTimeMillis,
             long endTimeMillis, int flags, RemoteCallback callback) {
-        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
-                beginTimeMillis, endTimeMillis, flags);
-        Objects.requireNonNull(callback, "callback cannot be null");
-
-        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
-                Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
-
-        final String[] opNamesArray = (opNames != null)
-                ? opNames.toArray(new String[opNames.size()]) : null;
-
-        Set<String> attributionChainExemptPackages = null;
-        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
-            attributionChainExemptPackages =
-                    PermissionManager.getIndicatorExemptedPackages(mContext);
-        }
-
-        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
-                ? attributionChainExemptPackages.toArray(
-                new String[attributionChainExemptPackages.size()]) : null;
-
-        // Must not hold the appops lock
-        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
-                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
-                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
-                callback).recycleOnUse());
+        mAppOpsService.getHistoricalOpsFromDiskRaw(uid, packageName, attributionTag,
+                opNames, dataType, filter, beginTimeMillis, endTimeMillis, flags, callback);
     }
 
     @Override
     public void reloadNonHistoricalState() {
-        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
-                Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
-        writeState();
-        readState();
+        mAppOpsService.reloadNonHistoricalState();
     }
 
     @Override
     public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
-        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                Binder.getCallingPid(), Binder.getCallingUid(), null);
-        synchronized (this) {
-            UidState uidState = getUidStateLocked(uid, false);
-            if (uidState == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
-            if (resOps == null) {
-                return null;
-            }
-            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
-            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
-                    null, uidState.uid, resOps);
-            res.add(resPackage);
-            return res;
-        }
-    }
-
-    private void pruneOpLocked(Op op, int uid, String packageName) {
-        op.removeAttributionsWithNoTime();
-
-        if (op.mAttributions.isEmpty()) {
-            Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
-            if (ops != null) {
-                ops.remove(op.op);
-                op.setMode(AppOpsManager.opToDefaultMode(op.op));
-                if (ops.size() <= 0) {
-                    UidState uidState = ops.uidState;
-                    ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
-                    if (pkgOps != null) {
-                        pkgOps.remove(ops.packageName);
-                        mAppOpsServiceInterface.removePackage(ops.packageName,
-                                UserHandle.getUserId(uidState.uid));
-                        if (pkgOps.isEmpty()) {
-                            uidState.pkgOps = null;
-                        }
-                        if (uidState.isDefault()) {
-                            uidState.clear();
-                            mUidStates.remove(uid);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
-        if (callingPid == Process.myPid()) {
-            return;
-        }
-        final int callingUser = UserHandle.getUserId(callingUid);
-        synchronized (this) {
-            if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
-                if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
-                    // Profile owners are allowed to change modes but only for apps
-                    // within their user.
-                    return;
-                }
-            }
-        }
-        mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
-                Binder.getCallingPid(), Binder.getCallingUid(), null);
+        return mAppOpsService.getUidOps(uid, ops);
     }
 
     @Override
     public void setUidMode(int code, int uid, int mode) {
-        setUidMode(code, uid, mode, null);
-    }
-
-    private void setUidMode(int code, int uid, int mode,
-            @Nullable IAppOpsCallback permissionPolicyCallback) {
-        if (DEBUG) {
-            Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
-                    + " by uid " + Binder.getCallingUid());
-        }
-
-        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
-        verifyIncomingOp(code);
-        code = AppOpsManager.opToSwitch(code);
-
-        if (permissionPolicyCallback == null) {
-            updatePermissionRevokedCompat(uid, code, mode);
-        }
-
-        int previousMode;
-        synchronized (this) {
-            final int defaultMode = AppOpsManager.opToDefaultMode(code);
-
-            UidState uidState = getUidStateLocked(uid, false);
-            if (uidState == null) {
-                if (mode == defaultMode) {
-                    return;
-                }
-                uidState = new UidState(uid);
-                mUidStates.put(uid, uidState);
-            }
-            if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
-                previousMode = uidState.getUidMode(code);
-            } else {
-                // doesn't look right but is legacy behavior.
-                previousMode = MODE_DEFAULT;
-            }
-
-            if (!uidState.setUidMode(code, mode)) {
-                return;
-            }
-            uidState.evalForegroundOps();
-            if (mode != MODE_ERRORED && mode != previousMode) {
-                updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
-            }
-        }
-
-        notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
-        notifyOpChangedSync(code, uid, null, mode, previousMode);
-    }
-
-    /**
-     * Notify that an op changed for all packages in an uid.
-     *
-     * @param code The op that changed
-     * @param uid The uid the op was changed for
-     * @param onlyForeground Only notify watchers that watch for foreground changes
-     */
-    private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
-            @Nullable IAppOpsCallback callbackToIgnore) {
-        ModeCallback listenerToIgnore = callbackToIgnore != null
-                ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
-        mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
-                listenerToIgnore);
-    }
-
-    private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
-        PackageManager packageManager = mContext.getPackageManager();
-        if (packageManager == null) {
-            // This can only happen during early boot. At this time the permission state and appop
-            // state are in sync
-            return;
-        }
-
-        String[] packageNames = packageManager.getPackagesForUid(uid);
-        if (ArrayUtils.isEmpty(packageNames)) {
-            return;
-        }
-        String packageName = packageNames[0];
-
-        int[] ops = mSwitchedOps.get(switchCode);
-        for (int code : ops) {
-            String permissionName = AppOpsManager.opToPermission(code);
-            if (permissionName == null) {
-                continue;
-            }
-
-            if (packageManager.checkPermission(permissionName, packageName)
-                    != PackageManager.PERMISSION_GRANTED) {
-                continue;
-            }
-
-            PermissionInfo permissionInfo;
-            try {
-                permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
-            } catch (PackageManager.NameNotFoundException e) {
-                e.printStackTrace();
-                continue;
-            }
-
-            if (!permissionInfo.isRuntime()) {
-                continue;
-            }
-
-            boolean supportsRuntimePermissions = getPackageManagerInternal()
-                    .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
-
-            UserHandle user = UserHandle.getUserHandleForUid(uid);
-            boolean isRevokedCompat;
-            if (permissionInfo.backgroundPermission != null) {
-                if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
-                        == PackageManager.PERMISSION_GRANTED) {
-                    boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
-
-                    if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
-                        Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
-                                + " permission state, this is discouraged and you should revoke the"
-                                + " runtime permission instead: uid=" + uid + ", switchCode="
-                                + switchCode + ", mode=" + mode + ", permission="
-                                + permissionInfo.backgroundPermission);
-                    }
-
-                    final long identity = Binder.clearCallingIdentity();
-                    try {
-                        packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
-                                packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
-                                isBackgroundRevokedCompat
-                                        ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
-                    } finally {
-                        Binder.restoreCallingIdentity(identity);
-                    }
-                }
-
-                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
-                        && mode != AppOpsManager.MODE_FOREGROUND;
-            } else {
-                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
-            }
-
-            if (isRevokedCompat && supportsRuntimePermissions) {
-                Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
-                        + " permission state, this is discouraged and you should revoke the"
-                        + " runtime permission instead: uid=" + uid + ", switchCode="
-                        + switchCode + ", mode=" + mode + ", permission=" + permissionName);
-            }
-
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                packageManager.updatePermissionFlags(permissionName, packageName,
-                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
-                                ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-    }
-
-    private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
-            int previousMode) {
-        final StorageManagerInternal storageManagerInternal =
-                LocalServices.getService(StorageManagerInternal.class);
-        if (storageManagerInternal != null) {
-            storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
-        }
+        mAppOpsService.setUidMode(code, uid, mode, null);
     }
 
     /**
@@ -1866,309 +407,12 @@
      */
     @Override
     public void setMode(int code, int uid, @NonNull String packageName, int mode) {
-        setMode(code, uid, packageName, mode, null);
-    }
-
-    private void setMode(int code, int uid, @NonNull String packageName, int mode,
-            @Nullable IAppOpsCallback permissionPolicyCallback) {
-        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return;
-        }
-
-        ArraySet<OnOpModeChangedListener> repCbs = null;
-        code = AppOpsManager.opToSwitch(code);
-
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, null);
-        } catch (SecurityException e) {
-            Slog.e(TAG, "Cannot setMode", e);
-            return;
-        }
-
-        int previousMode = MODE_DEFAULT;
-        synchronized (this) {
-            UidState uidState = getUidStateLocked(uid, false);
-            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
-            if (op != null) {
-                if (op.getMode() != mode) {
-                    previousMode = op.getMode();
-                    op.setMode(mode);
-
-                    if (uidState != null) {
-                        uidState.evalForegroundOps();
-                    }
-                    ArraySet<OnOpModeChangedListener> cbs =
-                            mAppOpsServiceInterface.getOpModeChangedListeners(code);
-                    if (cbs != null) {
-                        if (repCbs == null) {
-                            repCbs = new ArraySet<>();
-                        }
-                        repCbs.addAll(cbs);
-                    }
-                    cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName);
-                    if (cbs != null) {
-                        if (repCbs == null) {
-                            repCbs = new ArraySet<>();
-                        }
-                        repCbs.addAll(cbs);
-                    }
-                    if (repCbs != null && permissionPolicyCallback != null) {
-                        repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
-                    }
-                    if (mode == AppOpsManager.opToDefaultMode(op.op)) {
-                        // If going into the default mode, prune this op
-                        // if there is nothing else interesting in it.
-                        pruneOpLocked(op, uid, packageName);
-                    }
-                    scheduleFastWriteLocked();
-                    if (mode != MODE_ERRORED) {
-                        updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
-                    }
-                }
-            }
-        }
-        if (repCbs != null) {
-            mHandler.sendMessage(PooledLambda.obtainMessage(
-                    AppOpsService::notifyOpChanged,
-                    this, repCbs, code, uid, packageName));
-        }
-
-        notifyOpChangedSync(code, uid, packageName, mode, previousMode);
-    }
-
-    private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
-            int uid, String packageName) {
-        for (int i = 0; i < callbacks.size(); i++) {
-            final OnOpModeChangedListener callback = callbacks.valueAt(i);
-            notifyOpChanged(callback, code, uid, packageName);
-        }
-    }
-
-    private void notifyOpChanged(OnOpModeChangedListener callback, int code,
-            int uid, String packageName) {
-        mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName);
-    }
-
-    private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
-            int op, int uid, String packageName, int previousMode) {
-        boolean duplicate = false;
-        if (reports == null) {
-            reports = new ArrayList<>();
-        } else {
-            final int reportCount = reports.size();
-            for (int j = 0; j < reportCount; j++) {
-                ChangeRec report = reports.get(j);
-                if (report.op == op && report.pkg.equals(packageName)) {
-                    duplicate = true;
-                    break;
-                }
-            }
-        }
-        if (!duplicate) {
-            reports.add(new ChangeRec(op, uid, packageName, previousMode));
-        }
-
-        return reports;
-    }
-
-    private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
-            HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
-            int op, int uid, String packageName, int previousMode,
-            ArraySet<OnOpModeChangedListener> cbs) {
-        if (cbs == null) {
-            return callbacks;
-        }
-        if (callbacks == null) {
-            callbacks = new HashMap<>();
-        }
-        final int N = cbs.size();
-        for (int i=0; i<N; i++) {
-            OnOpModeChangedListener cb = cbs.valueAt(i);
-            ArrayList<ChangeRec> reports = callbacks.get(cb);
-            ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
-            if (changed != reports) {
-                callbacks.put(cb, changed);
-            }
-        }
-        return callbacks;
-    }
-
-    static final class ChangeRec {
-        final int op;
-        final int uid;
-        final String pkg;
-        final int previous_mode;
-
-        ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
-            op = _op;
-            uid = _uid;
-            pkg = _pkg;
-            previous_mode = _previous_mode;
-        }
+        mAppOpsService.setMode(code, uid, packageName, mode, null);
     }
 
     @Override
     public void resetAllModes(int reqUserId, String reqPackageName) {
-        final int callingPid = Binder.getCallingPid();
-        final int callingUid = Binder.getCallingUid();
-        reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
-                true, true, "resetAllModes", null);
-
-        int reqUid = -1;
-        if (reqPackageName != null) {
-            try {
-                reqUid = AppGlobals.getPackageManager().getPackageUid(
-                        reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
-            } catch (RemoteException e) {
-                /* ignore - local call */
-            }
-        }
-
-        enforceManageAppOpsModes(callingPid, callingUid, reqUid);
-
-        HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
-        ArrayList<ChangeRec> allChanges = new ArrayList<>();
-        synchronized (this) {
-            boolean changed = false;
-            for (int i = mUidStates.size() - 1; i >= 0; i--) {
-                UidState uidState = mUidStates.valueAt(i);
-
-                SparseIntArray opModes = uidState.getNonDefaultUidModes();
-                if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
-                    final int uidOpCount = opModes.size();
-                    for (int j = uidOpCount - 1; j >= 0; j--) {
-                        final int code = opModes.keyAt(j);
-                        if (AppOpsManager.opAllowsReset(code)) {
-                            int previousMode = opModes.valueAt(j);
-                            uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
-                            for (String packageName : getPackagesForUid(uidState.uid)) {
-                                callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
-                                        previousMode,
-                                        mAppOpsServiceInterface.getOpModeChangedListeners(code));
-                                callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
-                                        previousMode, mAppOpsServiceInterface
-                                                .getPackageModeChangedListeners(packageName));
-
-                                allChanges = addChange(allChanges, code, uidState.uid,
-                                        packageName, previousMode);
-                            }
-                        }
-                    }
-                }
-
-                if (uidState.pkgOps == null) {
-                    continue;
-                }
-
-                if (reqUserId != UserHandle.USER_ALL
-                        && reqUserId != UserHandle.getUserId(uidState.uid)) {
-                    // Skip any ops for a different user
-                    continue;
-                }
-
-                Map<String, Ops> packages = uidState.pkgOps;
-                Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
-                boolean uidChanged = false;
-                while (it.hasNext()) {
-                    Map.Entry<String, Ops> ent = it.next();
-                    String packageName = ent.getKey();
-                    if (reqPackageName != null && !reqPackageName.equals(packageName)) {
-                        // Skip any ops for a different package
-                        continue;
-                    }
-                    Ops pkgOps = ent.getValue();
-                    for (int j=pkgOps.size()-1; j>=0; j--) {
-                        Op curOp = pkgOps.valueAt(j);
-                        if (shouldDeferResetOpToDpm(curOp.op)) {
-                            deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
-                            continue;
-                        }
-                        if (AppOpsManager.opAllowsReset(curOp.op)
-                                && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
-                            int previousMode = curOp.getMode();
-                            curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
-                            changed = true;
-                            uidChanged = true;
-                            final int uid = curOp.uidState.uid;
-                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
-                                    previousMode,
-                                    mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
-                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
-                                    previousMode, mAppOpsServiceInterface
-                                            .getPackageModeChangedListeners(packageName));
-
-                            allChanges = addChange(allChanges, curOp.op, uid, packageName,
-                                    previousMode);
-                            curOp.removeAttributionsWithNoTime();
-                            if (curOp.mAttributions.isEmpty()) {
-                                pkgOps.removeAt(j);
-                            }
-                        }
-                    }
-                    if (pkgOps.size() == 0) {
-                        it.remove();
-                        mAppOpsServiceInterface.removePackage(packageName,
-                                UserHandle.getUserId(uidState.uid));
-                    }
-                }
-                if (uidState.isDefault()) {
-                    uidState.clear();
-                    mUidStates.remove(uidState.uid);
-                }
-                if (uidChanged) {
-                    uidState.evalForegroundOps();
-                }
-            }
-
-            if (changed) {
-                scheduleFastWriteLocked();
-            }
-        }
-        if (callbacks != null) {
-            for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
-                    : callbacks.entrySet()) {
-                OnOpModeChangedListener cb = ent.getKey();
-                ArrayList<ChangeRec> reports = ent.getValue();
-                for (int i=0; i<reports.size(); i++) {
-                    ChangeRec rep = reports.get(i);
-                    mHandler.sendMessage(PooledLambda.obtainMessage(
-                            AppOpsService::notifyOpChanged,
-                            this, cb, rep.op, rep.uid, rep.pkg));
-                }
-            }
-        }
-
-        int numChanges = allChanges.size();
-        for (int i = 0; i < numChanges; i++) {
-            ChangeRec change = allChanges.get(i);
-            notifyOpChangedSync(change.op, change.uid, change.pkg,
-                    AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
-        }
-    }
-
-    private boolean shouldDeferResetOpToDpm(int op) {
-        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
-        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
-        return dpmi != null && dpmi.supportsResetOp(op);
-    }
-
-    /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
-    private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
-        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
-        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
-        dpmi.resetOp(op, packageName, userId);
-    }
-
-    private void evalAllForegroundOpsLocked() {
-        for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
-            final UidState uidState = mUidStates.valueAt(uidi);
-            if (uidState.foregroundOps != null) {
-                uidState.evalForegroundOps();
-            }
-        }
+        mAppOpsService.resetAllModes(reqUserId, reqPackageName);
     }
 
     @Override
@@ -2179,66 +423,17 @@
     @Override
     public void startWatchingModeWithFlags(int op, String packageName, int flags,
             IAppOpsCallback callback) {
-        int watchedUid = -1;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        // TODO: should have a privileged permission to protect this.
-        // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
-        // the USAGE_STATS permission since this can provide information about when an
-        // app is in the foreground?
-        Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
-                AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
-        if (callback == null) {
-            return;
-        }
-        final boolean mayWatchPackageName = packageName != null
-                && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
-        synchronized (this) {
-            int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
-
-            int notifiedOps;
-            if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
-                if (op == OP_NONE) {
-                    notifiedOps = ALL_OPS;
-                } else {
-                    notifiedOps = op;
-                }
-            } else {
-                notifiedOps = switchOp;
-            }
-
-            ModeCallback cb = mModeWatchers.get(callback.asBinder());
-            if (cb == null) {
-                cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
-                        callingPid);
-                mModeWatchers.put(callback.asBinder(), cb);
-            }
-            if (switchOp != AppOpsManager.OP_NONE) {
-                mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
-            }
-            if (mayWatchPackageName) {
-                mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName);
-            }
-            evalAllForegroundOpsLocked();
-        }
+        mAppOpsService.startWatchingModeWithFlags(op, packageName, flags, callback);
     }
 
     @Override
     public void stopWatchingMode(IAppOpsCallback callback) {
-        if (callback == null) {
-            return;
-        }
-        synchronized (this) {
-            ModeCallback cb = mModeWatchers.remove(callback.asBinder());
-            if (cb != null) {
-                cb.unlinkToDeath();
-                mAppOpsServiceInterface.removeListener(cb);
-            }
-
-            evalAllForegroundOpsLocked();
-        }
+        mAppOpsService.stopWatchingMode(callback);
     }
 
+    /**
+     * @return the current {@link CheckOpsDelegate}.
+     */
     public CheckOpsDelegate getAppOpsServiceDelegate() {
         synchronized (AppOpsService.this) {
             final CheckOpsDelegateDispatcher dispatcher = mCheckOpsDelegateDispatcher;
@@ -2246,6 +441,9 @@
         }
     }
 
+    /**
+     * Sets the appops {@link CheckOpsDelegate}
+     */
     public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) {
         synchronized (AppOpsService.this) {
             final CheckOpsDelegateDispatcher oldDispatcher = mCheckOpsDelegateDispatcher;
@@ -2269,58 +467,7 @@
 
     private int checkOperationImpl(int code, int uid, String packageName,
             @Nullable String attributionTag, boolean raw) {
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return AppOpsManager.opToDefaultMode(code);
-        }
-
-        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return AppOpsManager.MODE_IGNORED;
-        }
-        return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
-    }
-
-    /**
-     * Get the mode of an app-op.
-     *
-     * @param code The code of the op
-     * @param uid The uid of the package the op belongs to
-     * @param packageName The package the op belongs to
-     * @param raw If the raw state of eval-ed state should be checked.
-     *
-     * @return The mode of the op
-     */
-    private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, boolean raw) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, null);
-        } catch (SecurityException e) {
-            Slog.e(TAG, "checkOperation", e);
-            return AppOpsManager.opToDefaultMode(code);
-        }
-
-        if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
-            return AppOpsManager.MODE_IGNORED;
-        }
-        synchronized (this) {
-            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
-                return AppOpsManager.MODE_IGNORED;
-            }
-            code = AppOpsManager.opToSwitch(code);
-            UidState uidState = getUidStateLocked(uid, false);
-            if (uidState != null
-                    && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
-                final int rawMode = uidState.getUidMode(code);
-                return raw ? rawMode : uidState.evalMode(code, rawMode);
-            }
-            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
-            if (op == null) {
-                return AppOpsManager.opToDefaultMode(code);
-            }
-            return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
-        }
+        return mAppOpsService.checkOperation(code, uid, packageName, attributionTag, raw);
     }
 
     @Override
@@ -2340,7 +487,8 @@
     @Override
     public void setAudioRestriction(int code, int usage, int uid, int mode,
             String[] exceptionPackages) {
-        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+        mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(),
+                Binder.getCallingUid(), uid);
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
 
@@ -2348,58 +496,35 @@
                 code, usage, uid, mode, exceptionPackages);
 
         mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
+                AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService, code,
+                UID_ANY));
     }
 
 
     @Override
     public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) {
-        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), -1);
+        mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(),
+                Binder.getCallingUid(), -1);
 
         mAudioRestrictionManager.setCameraAudioRestriction(mode);
 
         mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyWatchersOfChange, this,
+                AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService,
                 AppOpsManager.OP_PLAY_AUDIO, UID_ANY));
         mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyWatchersOfChange, this,
+                AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService,
                 AppOpsManager.OP_VIBRATE, UID_ANY));
     }
 
     @Override
     public int checkPackage(int uid, String packageName) {
-        Objects.requireNonNull(packageName);
-        try {
-            verifyAndGetBypass(uid, packageName, null);
-            // When the caller is the system, it's possible that the packageName is the special
-            // one (e.g., "root") which isn't actually existed.
-            if (resolveUid(packageName) == uid
-                    || (isPackageExisted(packageName)
-                            && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
-                return AppOpsManager.MODE_ALLOWED;
-            }
-            return AppOpsManager.MODE_ERRORED;
-        } catch (SecurityException ignored) {
-            return AppOpsManager.MODE_ERRORED;
-        }
+        return mAppOpsService.checkPackage(uid, packageName);
     }
 
     private boolean isPackageExisted(String packageName) {
         return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
     }
 
-    /**
-     * This method will check with PackageManager to determine if the package provided should
-     * be visible to the {@link Binder#getCallingUid()}.
-     *
-     * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
-     */
-    private boolean filterAppAccessUnlocked(String packageName, int userId) {
-        final int callingUid = Binder.getCallingUid();
-        return LocalServices.getService(PackageManagerInternal.class)
-                .filterAppAccess(packageName, callingUid, userId);
-    }
-
     @Override
     public SyncNotedAppOp noteProxyOperation(int code, AttributionSource attributionSource,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
@@ -2445,13 +570,20 @@
             final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
                     : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
 
-            final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
+            final int proxyReturn = mAppOpsService.noteOperationUnchecked(code, proxyUid,
                     resolveProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
-                    proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage);
-            if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
-                return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
+                    proxyFlags);
+            if (proxyReturn != AppOpsManager.MODE_ALLOWED) {
+                return new SyncNotedAppOp(proxyReturn, code, proxiedAttributionTag,
                         proxiedPackageName);
             }
+            if (shouldCollectAsyncNotedOp) {
+                boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid,
+                        resolveProxyPackageName, proxyAttributionTag, null);
+                collectAsyncNotedOp(proxyUid, resolveProxyPackageName, code,
+                        isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags,
+                        message, shouldCollectMessage);
+            }
         }
 
         String resolveProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -2463,9 +595,32 @@
 
         final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
                 : AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
-        return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
-                proxiedAttributionTag, proxyUid, resolveProxyPackageName, proxyAttributionTag,
-                proxiedFlags, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+        final int result = mAppOpsService.noteOperationUnchecked(code, proxiedUid,
+                resolveProxiedPackageName, proxiedAttributionTag, proxyUid, resolveProxyPackageName,
+                proxyAttributionTag, proxiedFlags);
+
+        boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid,
+                resolveProxiedPackageName, proxiedAttributionTag, resolveProxyPackageName);
+        if (shouldCollectAsyncNotedOp && result == AppOpsManager.MODE_ALLOWED) {
+            collectAsyncNotedOp(proxiedUid, resolveProxiedPackageName, code,
+                    isProxiedAttributionTagValid ? proxiedAttributionTag : null, proxiedFlags,
+                    message, shouldCollectMessage);
+        }
+
+
+        return new SyncNotedAppOp(result, code,
+                isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+                resolveProxiedPackageName);
+    }
+
+    private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
+        if (attributionSource.getUid() != Binder.getCallingUid()
+                && attributionSource.isTrusted(mContext)) {
+            return true;
+        }
+        return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null)
+                == PackageManager.PERMISSION_GRANTED;
     }
 
     @Override
@@ -2479,258 +634,58 @@
     private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName,
             @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp,
             @Nullable String message, boolean shouldCollectMessage) {
-        verifyIncomingUid(uid);
-        verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
 
+        int result = mAppOpsService.noteOperation(code, uid, packageName,
+                attributionTag, message);
+
         String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
-                    packageName);
-        }
-        return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
-                Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF,
-                shouldCollectAsyncNotedOp, message, shouldCollectMessage);
-    }
 
-    private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
-            @Nullable String proxyAttributionTag, @OpFlags int flags,
-            boolean shouldCollectAsyncNotedOp, @Nullable String message,
-            boolean shouldCollectMessage) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
-            boolean wasNull = attributionTag == null;
-            if (!pvr.isAttributionTagValid) {
-                attributionTag = null;
-            }
-        } catch (SecurityException e) {
-            Slog.e(TAG, "noteOperation", e);
-            return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
-                    packageName);
+        boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid,
+                    resolvedPackageName, attributionTag, null);
+
+        if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) {
+            collectAsyncNotedOp(uid, resolvedPackageName, code,
+                    isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF,
+                    message, shouldCollectMessage);
         }
 
-        synchronized (this) {
-            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
-                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
-            if (ops == null) {
-                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        AppOpsManager.MODE_IGNORED);
-                if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
-                        + " package " + packageName + "flags: " +
-                        AppOpsManager.flagsToString(flags));
-                return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
-                        packageName);
-            }
-            final Op op = getOpLocked(ops, code, uid, true);
-            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
-            if (attributedOp.isRunning()) {
-                Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
-                        + code + " startTime of in progress event="
-                        + attributedOp.mInProgressEvents.valueAt(0).getStartTime());
-            }
-
-            final int switchCode = AppOpsManager.opToSwitch(code);
-            final UidState uidState = ops.uidState;
-            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
-                attributedOp.rejected(uidState.getState(), flags);
-                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        AppOpsManager.MODE_IGNORED);
-                return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
-                        packageName);
-            }
-            // If there is a non-default per UID policy (we set UID op mode only if
-            // non-default) it takes over, otherwise use the per package policy.
-            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
-                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
-                if (uidMode != AppOpsManager.MODE_ALLOWED) {
-                    if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
-                            + switchCode + " (" + code + ") uid " + uid + " package "
-                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    attributedOp.rejected(uidState.getState(), flags);
-                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                            uidMode);
-                    return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
-                }
-            } else {
-                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
-                        : op;
-                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
-                if (mode != AppOpsManager.MODE_ALLOWED) {
-                    if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
-                            + switchCode + " (" + code + ") uid " + uid + " package "
-                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    attributedOp.rejected(uidState.getState(), flags);
-                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                            mode);
-                    return new SyncNotedAppOp(mode, code, attributionTag, packageName);
-                }
-            }
-            if (DEBUG) {
-                Slog.d(TAG,
-                        "noteOperation: allowing code " + code + " uid " + uid + " package "
-                                + packageName + (attributionTag == null ? ""
-                                : "." + attributionTag) + " flags: "
-                                + AppOpsManager.flagsToString(flags));
-            }
-            scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                    AppOpsManager.MODE_ALLOWED);
-            attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
-                    uidState.getState(),
-                    flags);
-
-            if (shouldCollectAsyncNotedOp) {
-                collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
-                        shouldCollectMessage);
-            }
-
-            return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
-                    packageName);
-        }
+        return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null,
+                resolvedPackageName);
     }
 
     // TODO moltmann: Allow watching for attribution ops
     @Override
     public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
-        int watchedUid = Process.INVALID_UID;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                != PackageManager.PERMISSION_GRANTED) {
-            watchedUid = callingUid;
-        }
-        if (ops != null) {
-            Preconditions.checkArrayElementsInRange(ops, 0,
-                    AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
-        }
-        if (callback == null) {
-            return;
-        }
-        synchronized (this) {
-            SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
-            if (callbacks == null) {
-                callbacks = new SparseArray<>();
-                mActiveWatchers.put(callback.asBinder(), callbacks);
-            }
-            final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
-                    callingUid, callingPid);
-            for (int op : ops) {
-                callbacks.put(op, activeCallback);
-            }
-        }
+        mAppOpsService.startWatchingActive(ops, callback);
     }
 
     @Override
     public void stopWatchingActive(IAppOpsActiveCallback callback) {
-        if (callback == null) {
-            return;
-        }
-        synchronized (this) {
-            final SparseArray<ActiveCallback> activeCallbacks =
-                    mActiveWatchers.remove(callback.asBinder());
-            if (activeCallbacks == null) {
-                return;
-            }
-            final int callbackCount = activeCallbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                activeCallbacks.valueAt(i).destroy();
-            }
-        }
+        mAppOpsService.stopWatchingActive(callback);
     }
 
     @Override
     public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
-        int watchedUid = Process.INVALID_UID;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                != PackageManager.PERMISSION_GRANTED) {
-            watchedUid = callingUid;
-        }
-
-        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
-        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
-                "Invalid op code in: " + Arrays.toString(ops));
-        Objects.requireNonNull(callback, "Callback cannot be null");
-
-        synchronized (this) {
-            SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
-            if (callbacks == null) {
-                callbacks = new SparseArray<>();
-                mStartedWatchers.put(callback.asBinder(), callbacks);
-            }
-
-            final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
-                    callingUid, callingPid);
-            for (int op : ops) {
-                callbacks.put(op, startedCallback);
-            }
-        }
+        mAppOpsService.startWatchingStarted(ops, callback);
     }
 
     @Override
     public void stopWatchingStarted(IAppOpsStartedCallback callback) {
-        Objects.requireNonNull(callback, "Callback cannot be null");
-
-        synchronized (this) {
-            final SparseArray<StartedCallback> startedCallbacks =
-                    mStartedWatchers.remove(callback.asBinder());
-            if (startedCallbacks == null) {
-                return;
-            }
-
-            final int callbackCount = startedCallbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                startedCallbacks.valueAt(i).destroy();
-            }
-        }
+        mAppOpsService.stopWatchingStarted(callback);
     }
 
     @Override
     public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
-        int watchedUid = Process.INVALID_UID;
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                != PackageManager.PERMISSION_GRANTED) {
-            watchedUid = callingUid;
-        }
-        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
-        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
-                "Invalid op code in: " + Arrays.toString(ops));
-        Objects.requireNonNull(callback, "Callback cannot be null");
-        synchronized (this) {
-            SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
-            if (callbacks == null) {
-                callbacks = new SparseArray<>();
-                mNotedWatchers.put(callback.asBinder(), callbacks);
-            }
-            final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
-                    callingUid, callingPid);
-            for (int op : ops) {
-                callbacks.put(op, notedCallback);
-            }
-        }
+        mAppOpsService.startWatchingNoted(ops, callback);
     }
 
     @Override
     public void stopWatchingNoted(IAppOpsNotedCallback callback) {
-        Objects.requireNonNull(callback, "Callback cannot be null");
-        synchronized (this) {
-            final SparseArray<NotedCallback> notedCallbacks =
-                    mNotedWatchers.remove(callback.asBinder());
-            if (notedCallbacks == null) {
-                return;
-            }
-            final int callbackCount = notedCallbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                notedCallbacks.valueAt(i).destroy();
-            }
-        }
+        mAppOpsService.stopWatchingNoted(callback);
     }
 
     /**
@@ -2817,7 +772,7 @@
         int uid = Binder.getCallingUid();
         Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
 
-        verifyAndGetBypass(uid, packageName, null);
+        mAppOpsService.verifyPackage(uid, packageName);
 
         synchronized (this) {
             RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -2847,7 +802,7 @@
         int uid = Binder.getCallingUid();
         Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
 
-        verifyAndGetBypass(uid, packageName, null);
+        mAppOpsService.verifyPackage(uid, packageName);
 
         synchronized (this) {
             RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -2866,7 +821,7 @@
 
         int uid = Binder.getCallingUid();
 
-        verifyAndGetBypass(uid, packageName, null);
+        mAppOpsService.verifyPackage(uid, packageName);
 
         synchronized (this) {
             return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid));
@@ -2889,62 +844,55 @@
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message,
             boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
             int attributionChainId) {
-        verifyIncomingUid(uid);
-        verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
 
+        int result = mAppOpsService.startOperation(clientId, code, uid, packageName,
+                attributionTag, startIfModeDefault, message,
+                attributionFlags, attributionChainId);
+
         String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
-                    packageName);
+
+        boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid,
+                resolvedPackageName, attributionTag, null);
+
+        if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) {
+            collectAsyncNotedOp(uid, resolvedPackageName, code,
+                    isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF,
+                    message, shouldCollectMessage);
         }
 
-        // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
-        // purposes and not as a check, also make sure that the caller is allowed to access
-        // the data gated by OP_RECORD_AUDIO.
-        //
-        // TODO: Revert this change before Android 12.
-        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
-            int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
-            if (result != AppOpsManager.MODE_ALLOWED) {
-                return new SyncNotedAppOp(result, code, attributionTag, packageName);
-            }
-        }
-        return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
-                Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
-                shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
-                attributionChainId, /*dryRun*/ false);
+        return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null,
+                resolvedPackageName);
     }
 
     @Override
-    public SyncNotedAppOp startProxyOperation(int code,
+    public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
             @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
             boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
             @AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
-        return mCheckOpsDelegateDispatcher.startProxyOperation(code, attributionSource,
-                startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
-                attributionChainId);
+        return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code,
+                attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+                proxiedAttributionFlags, attributionChainId);
     }
 
-    private SyncNotedAppOp startProxyOperationImpl(int code,
+    private SyncNotedAppOp startProxyOperationImpl(IBinder clientId, int code,
             @NonNull AttributionSource attributionSource,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
             boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
             int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
             int attributionChainId) {
+
         final int proxyUid = attributionSource.getUid();
         final String proxyPackageName = attributionSource.getPackageName();
         final String proxyAttributionTag = attributionSource.getAttributionTag();
-        final IBinder proxyToken = attributionSource.getToken();
         final int proxiedUid = attributionSource.getNextUid();
         final String proxiedPackageName = attributionSource.getNextPackageName();
         final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
-        final IBinder proxiedToken = attributionSource.getNextToken();
 
         verifyIncomingProxyUid(attributionSource);
         verifyIncomingOp(code);
@@ -2986,147 +934,68 @@
 
         if (!skipProxyOperation) {
             // Test if the proxied operation will succeed before starting the proxy operation
-            final SyncNotedAppOp testProxiedOp = startOperationUnchecked(proxiedToken, code,
+            final int testProxiedOp = mAppOpsService.startOperationUnchecked(clientId, code,
                     proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
-                    shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                     proxiedAttributionFlags, attributionChainId, /*dryRun*/ true);
-            if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
-                return testProxiedOp;
+
+            boolean isTestProxiedAttributionTagValid =
+                    mAppOpsService.isAttributionTagValid(proxiedUid, resolvedProxiedPackageName,
+                            proxiedAttributionTag, resolvedProxyPackageName);
+
+            if (!shouldStartForMode(testProxiedOp, startIfModeDefault)) {
+                return new SyncNotedAppOp(testProxiedOp, code,
+                        isTestProxiedAttributionTagValid ? proxiedAttributionTag : null,
+                        resolvedProxiedPackageName);
             }
 
             final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
                     : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
 
-            final SyncNotedAppOp proxyAppOp = startOperationUnchecked(proxyToken, code, proxyUid,
+            final int proxyAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
-                    proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
-                    shouldCollectMessage, proxyAttributionFlags, attributionChainId,
+                    proxyFlags, startIfModeDefault, proxyAttributionFlags, attributionChainId,
                     /*dryRun*/ false);
-            if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
-                return proxyAppOp;
+
+            boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid,
+                    resolvedProxyPackageName, proxyAttributionTag, null);
+
+            if (!shouldStartForMode(proxyAppOp, startIfModeDefault)) {
+                return new SyncNotedAppOp(proxyAppOp, code,
+                        isProxyAttributionTagValid ? proxyAttributionTag : null,
+                        resolvedProxyPackageName);
+            }
+
+            if (shouldCollectAsyncNotedOp) {
+                collectAsyncNotedOp(proxyUid, resolvedProxyPackageName, code,
+                        isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags,
+                        message, shouldCollectMessage);
             }
         }
 
-        return startOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName,
-                proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
-                proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
-                /*dryRun*/ false);
+        final int proxiedAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxiedUid,
+                resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
+                resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
+                proxiedAttributionFlags, attributionChainId,/*dryRun*/ false);
+
+        boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid,
+                resolvedProxiedPackageName, proxiedAttributionTag, resolvedProxyPackageName);
+
+        if (shouldCollectAsyncNotedOp && proxiedAppOp == MODE_ALLOWED) {
+            collectAsyncNotedOp(proxyUid, resolvedProxiedPackageName, code,
+                    isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+                    proxiedAttributionFlags, message, shouldCollectMessage);
+        }
+
+        return new SyncNotedAppOp(proxiedAppOp, code,
+                isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+                resolvedProxiedPackageName);
     }
 
     private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
         return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
     }
 
-    private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
-            @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
-            String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
-            boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
-            boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
-            int attributionChainId, boolean dryRun) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
-            if (!pvr.isAttributionTagValid) {
-                attributionTag = null;
-            }
-        } catch (SecurityException e) {
-            Slog.e(TAG, "startOperation", e);
-            return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
-                    packageName);
-        }
-
-        boolean isRestricted = false;
-        int startType = START_TYPE_FAILED;
-        synchronized (this) {
-            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
-                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
-            if (ops == null) {
-                if (!dryRun) {
-                    scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                            flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
-                            attributionChainId);
-                }
-                if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
-                        + " package " + packageName + " flags: "
-                        + AppOpsManager.flagsToString(flags));
-                return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
-                        packageName);
-            }
-            final Op op = getOpLocked(ops, code, uid, true);
-            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
-            final UidState uidState = ops.uidState;
-            isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
-                    false);
-            final int switchCode = AppOpsManager.opToSwitch(code);
-            // If there is a non-default per UID policy (we set UID op mode only if
-            // non-default) it takes over, otherwise use the per package policy.
-            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
-                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
-                if (!shouldStartForMode(uidMode, startIfModeDefault)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
-                                + switchCode + " (" + code + ") uid " + uid + " package "
-                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    }
-                    if (!dryRun) {
-                        attributedOp.rejected(uidState.getState(), flags);
-                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                                flags, uidMode, startType, attributionFlags, attributionChainId);
-                    }
-                    return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
-                }
-            } else {
-                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
-                        : op;
-                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
-                if (mode != AppOpsManager.MODE_ALLOWED
-                        && (!startIfModeDefault || mode != MODE_DEFAULT)) {
-                    if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
-                            + switchCode + " (" + code + ") uid " + uid + " package "
-                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    if (!dryRun) {
-                        attributedOp.rejected(uidState.getState(), flags);
-                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                                flags, mode, startType, attributionFlags, attributionChainId);
-                    }
-                    return new SyncNotedAppOp(mode, code, attributionTag, packageName);
-                }
-            }
-            if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
-                    + " package " + packageName + " restricted: " + isRestricted
-                    + " flags: " + AppOpsManager.flagsToString(flags));
-            if (!dryRun) {
-                try {
-                    if (isRestricted) {
-                        attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
-                                proxyAttributionTag, uidState.getState(), flags,
-                                attributionFlags, attributionChainId);
-                    } else {
-                        attributedOp.started(clientId, proxyUid, proxyPackageName,
-                                proxyAttributionTag, uidState.getState(), flags,
-                                attributionFlags, attributionChainId);
-                        startType = START_TYPE_STARTED;
-                    }
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                }
-                scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
-                        attributionChainId);
-            }
-        }
-
-        if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) {
-            collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
-                    message, shouldCollectMessage);
-        }
-
-        return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
-                packageName);
-    }
-
     @Override
     public void finishOperation(IBinder clientId, int code, int uid, String packageName,
             String attributionTag) {
@@ -3136,37 +1005,24 @@
 
     private void finishOperationImpl(IBinder clientId, int code, int uid, String packageName,
             String attributionTag) {
-        verifyIncomingUid(uid);
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return;
-        }
-
-        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return;
-        }
-
-        finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
+        mAppOpsService.finishOperation(clientId, code, uid, packageName, attributionTag);
     }
 
     @Override
-    public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
-            boolean skipProxyOperation) {
-        mCheckOpsDelegateDispatcher.finishProxyOperation(code, attributionSource,
+    public void finishProxyOperation(IBinder clientId, int code,
+            @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
+        mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource,
                 skipProxyOperation);
     }
 
-    private Void finishProxyOperationImpl(int code, @NonNull AttributionSource attributionSource,
-            boolean skipProxyOperation) {
+    private Void finishProxyOperationImpl(IBinder clientId, int code,
+            @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
         final int proxyUid = attributionSource.getUid();
         final String proxyPackageName = attributionSource.getPackageName();
         final String proxyAttributionTag = attributionSource.getAttributionTag();
-        final IBinder proxyToken = attributionSource.getToken();
         final int proxiedUid = attributionSource.getNextUid();
         final String proxiedPackageName = attributionSource.getNextPackageName();
         final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
-        final IBinder proxiedToken = attributionSource.getNextToken();
 
         skipProxyOperation = skipProxyOperation
                 && isCallerAndAttributionTrusted(attributionSource);
@@ -3185,8 +1041,8 @@
         }
 
         if (!skipProxyOperation) {
-            finishOperationUnchecked(proxyToken, code, proxyUid, resolvedProxyPackageName,
-                    proxyAttributionTag);
+            mAppOpsService.finishOperationUnchecked(clientId, code, proxyUid,
+                    resolvedProxyPackageName, proxyAttributionTag);
         }
 
         String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -3195,209 +1051,12 @@
             return null;
         }
 
-        finishOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName,
-                proxiedAttributionTag);
+        mAppOpsService.finishOperationUnchecked(clientId, code, proxiedUid,
+                resolvedProxiedPackageName, proxiedAttributionTag);
 
         return null;
     }
 
-    private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
-            String attributionTag) {
-        PackageVerificationResult pvr;
-        try {
-            pvr = verifyAndGetBypass(uid, packageName, attributionTag);
-            if (!pvr.isAttributionTagValid) {
-                attributionTag = null;
-            }
-        } catch (SecurityException e) {
-            Slog.e(TAG, "Cannot finishOperation", e);
-            return;
-        }
-
-        synchronized (this) {
-            Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
-                    pvr.bypass, /* edit */ true);
-            if (op == null) {
-                Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
-                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
-                return;
-            }
-            final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
-            if (attributedOp == null) {
-                Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
-                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
-                return;
-            }
-
-            if (attributedOp.isRunning() || attributedOp.isPaused()) {
-                attributedOp.finished(clientId);
-            } else {
-                Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
-                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
-            }
-        }
-    }
-
-    void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
-            String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
-            int attributionFlags, int attributionChainId) {
-        ArraySet<ActiveCallback> dispatchedCallbacks = null;
-        final int callbackListCount = mActiveWatchers.size();
-        for (int i = 0; i < callbackListCount; i++) {
-            final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
-            ActiveCallback callback = callbacks.get(code);
-            if (callback != null) {
-                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
-                    continue;
-                }
-                if (dispatchedCallbacks == null) {
-                    dispatchedCallbacks = new ArraySet<>();
-                }
-                dispatchedCallbacks.add(callback);
-            }
-        }
-        if (dispatchedCallbacks == null) {
-            return;
-        }
-        mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyOpActiveChanged,
-                this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
-                attributionFlags, attributionChainId));
-    }
-
-    private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
-            int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
-            boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
-        // There are features watching for mode changes such as window manager
-        // and location manager which are in our process. The callbacks in these
-        // features may require permissions our remote caller does not have.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final int callbackCount = callbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                final ActiveCallback callback = callbacks.valueAt(i);
-                try {
-                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
-                        continue;
-                    }
-                    callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
-                            active, attributionFlags, attributionChainId);
-                } catch (RemoteException e) {
-                    /* do nothing */
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
-            String attributionTag, @OpFlags int flags, @Mode int result,
-            @AppOpsManager.OnOpStartedListener.StartedType int startedType,
-            @AttributionFlags int attributionFlags, int attributionChainId) {
-        ArraySet<StartedCallback> dispatchedCallbacks = null;
-        final int callbackListCount = mStartedWatchers.size();
-        for (int i = 0; i < callbackListCount; i++) {
-            final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
-
-            StartedCallback callback = callbacks.get(code);
-            if (callback != null) {
-                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
-                    continue;
-                }
-
-                if (dispatchedCallbacks == null) {
-                    dispatchedCallbacks = new ArraySet<>();
-                }
-                dispatchedCallbacks.add(callback);
-            }
-        }
-
-        if (dispatchedCallbacks == null) {
-            return;
-        }
-
-        mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyOpStarted,
-                this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
-                result, startedType, attributionFlags, attributionChainId));
-    }
-
-    private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
-            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
-            @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
-            @AttributionFlags int attributionFlags, int attributionChainId) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final int callbackCount = callbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                final StartedCallback callback = callbacks.valueAt(i);
-                try {
-                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
-                        continue;
-                    }
-                    callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
-                            result, startedType, attributionFlags, attributionChainId);
-                } catch (RemoteException e) {
-                    /* do nothing */
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
-            String attributionTag, @OpFlags int flags, @Mode int result) {
-        ArraySet<NotedCallback> dispatchedCallbacks = null;
-        final int callbackListCount = mNotedWatchers.size();
-        for (int i = 0; i < callbackListCount; i++) {
-            final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
-            final NotedCallback callback = callbacks.get(code);
-            if (callback != null) {
-                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
-                    continue;
-                }
-                if (dispatchedCallbacks == null) {
-                    dispatchedCallbacks = new ArraySet<>();
-                }
-                dispatchedCallbacks.add(callback);
-            }
-        }
-        if (dispatchedCallbacks == null) {
-            return;
-        }
-        mHandler.sendMessage(PooledLambda.obtainMessage(
-                AppOpsService::notifyOpChecked,
-                this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
-                result));
-    }
-
-    private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
-            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
-            @Mode int result) {
-        // There are features watching for checks in our process. The callbacks in
-        // these features may require permissions our remote caller does not have.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final int callbackCount = callbacks.size();
-            for (int i = 0; i < callbackCount; i++) {
-                final NotedCallback callback = callbacks.valueAt(i);
-                try {
-                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
-                        continue;
-                    }
-                    callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
-                            result);
-                } catch (RemoteException e) {
-                    /* do nothing */
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
     @Override
     public int permissionToOpCode(String permission) {
         if (permission == null) {
@@ -3455,13 +1114,6 @@
                 Binder.getCallingPid(), Binder.getCallingUid(), null);
     }
 
-    private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
-        // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
-        // as watcher should not use this to signal if the value is changed.
-        return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
-                watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
-    }
-
     private void verifyIncomingOp(int op) {
         if (op >= 0 && op < AppOpsManager._NUM_OP) {
             // Enforce manage appops permission if it's a restricted read op.
@@ -3502,35 +1154,6 @@
                 || resolveUid(resolvedPackage) != Process.INVALID_UID;
     }
 
-    private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
-        if (attributionSource.getUid() != Binder.getCallingUid()
-                && attributionSource.isTrusted(mContext)) {
-            return true;
-        }
-        return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
-                Binder.getCallingPid(), Binder.getCallingUid(), null)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
-        UidState uidState = mUidStates.get(uid);
-        if (uidState == null) {
-            if (!edit) {
-                return null;
-            }
-            uidState = new UidState(uid);
-            mUidStates.put(uid, uidState);
-        }
-
-        return uidState;
-    }
-
-    private void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
-        synchronized (this) {
-            getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
-        }
-    }
-
     /**
      * @return {@link PackageManagerInternal}
      */
@@ -3542,764 +1165,6 @@
         return mPackageManagerInternal;
     }
 
-    /**
-     * Create a restriction description matching the properties of the package.
-     *
-     * @param pkg The package to create the restriction description for
-     *
-     * @return The restriction matching the package
-     */
-    private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
-        return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
-                mContext.checkPermission(android.Manifest.permission
-                        .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
-                == PackageManager.PERMISSION_GRANTED);
-    }
-
-    /**
-     * @see #verifyAndGetBypass(int, String, String, String)
-     */
-    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
-            @Nullable String attributionTag) {
-        return verifyAndGetBypass(uid, packageName, attributionTag, null);
-    }
-
-    /**
-     * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
-     * description} for the package, along with a boolean indicating whether the attribution tag is
-     * valid.
-     *
-     * @param uid The uid the package belongs to
-     * @param packageName The package the might belong to the uid
-     * @param attributionTag attribution tag or {@code null} if no need to verify
-     * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
-     *
-     * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
-     *         attribution tag is valid
-     */
-    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
-            @Nullable String attributionTag, @Nullable String proxyPackageName) {
-        if (uid == Process.ROOT_UID) {
-            // For backwards compatibility, don't check package name for root UID.
-            return new PackageVerificationResult(null,
-                    /* isAttributionTagValid */ true);
-        }
-        if (Process.isSdkSandboxUid(uid)) {
-            // SDK sandbox processes run in their own UID range, but their associated
-            // UID for checks should always be the UID of the package implementing SDK sandbox
-            // service.
-            // TODO: We will need to modify the callers of this function instead, so
-            // modifications and checks against the app ops state are done with the
-            // correct UID.
-            try {
-                final PackageManager pm = mContext.getPackageManager();
-                final String supplementalPackageName = pm.getSdkSandboxPackageName();
-                if (Objects.equals(packageName, supplementalPackageName)) {
-                    uid = pm.getPackageUidAsUser(supplementalPackageName,
-                            PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
-                }
-            } catch (PackageManager.NameNotFoundException e) {
-                // Shouldn't happen for the supplemental package
-                e.printStackTrace();
-            }
-        }
-
-
-        // Do not check if uid/packageName/attributionTag is already known.
-        synchronized (this) {
-            UidState uidState = mUidStates.get(uid);
-            if (uidState != null && uidState.pkgOps != null) {
-                Ops ops = uidState.pkgOps.get(packageName);
-
-                if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
-                        attributionTag)) && ops.bypass != null) {
-                    return new PackageVerificationResult(ops.bypass,
-                            ops.validAttributionTags.contains(attributionTag));
-                }
-            }
-        }
-
-        int callingUid = Binder.getCallingUid();
-
-        // Allow any attribution tag for resolvable uids
-        int pkgUid;
-        if (Objects.equals(packageName, "com.android.shell")) {
-            // Special case for the shell which is a package but should be able
-            // to bypass app attribution tag restrictions.
-            pkgUid = Process.SHELL_UID;
-        } else {
-            pkgUid = resolveUid(packageName);
-        }
-        if (pkgUid != Process.INVALID_UID) {
-            if (pkgUid != UserHandle.getAppId(uid)) {
-                Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
-                        + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
-                String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
-                throw new SecurityException("Specified package \"" + packageName + "\" under uid "
-                        +  UserHandle.getAppId(uid) + otherUidMessage);
-            }
-            return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
-                    /* isAttributionTagValid */ true);
-        }
-
-        int userId = UserHandle.getUserId(uid);
-        RestrictionBypass bypass = null;
-        boolean isAttributionTagValid = false;
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
-            AndroidPackage pkg = pmInt.getPackage(packageName);
-            if (pkg != null) {
-                isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
-                pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
-                bypass = getBypassforPackage(pkg);
-            }
-            if (!isAttributionTagValid) {
-                AndroidPackage proxyPkg = proxyPackageName != null
-                        ? pmInt.getPackage(proxyPackageName) : null;
-                // Re-check in proxy.
-                isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
-                String msg;
-                if (pkg != null && isAttributionTagValid) {
-                    msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
-                            + " package " + proxyPackageName + ", this is not advised";
-                } else if (pkg != null) {
-                    msg = "attributionTag " + attributionTag + " not declared in manifest of "
-                            + packageName;
-                } else {
-                    msg = "package " + packageName + " not found, can't check for "
-                            + "attributionTag " + attributionTag;
-                }
-
-                try {
-                    if (!mPlatformCompat.isChangeEnabledByPackageName(
-                            SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
-                            userId) || !mPlatformCompat.isChangeEnabledByUid(
-                                    SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
-                            callingUid)) {
-                        // Do not override tags if overriding is not enabled for this package
-                        isAttributionTagValid = true;
-                    }
-                    Slog.e(TAG, msg);
-                } catch (RemoteException neverHappens) {
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-
-        if (pkgUid != uid) {
-            Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
-                    + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
-            String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
-            throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
-                    + otherUidMessage);
-        }
-
-        return new PackageVerificationResult(bypass, isAttributionTagValid);
-    }
-
-    private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
-            @Nullable String attributionTag) {
-        if (pkg == null) {
-            return false;
-        } else if (attributionTag == null) {
-            return true;
-        }
-        if (pkg.getAttributions() != null) {
-            int numAttributions = pkg.getAttributions().size();
-            for (int i = 0; i < numAttributions; i++) {
-                if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Get (and potentially create) ops.
-     *
-     * @param uid The uid the package belongs to
-     * @param packageName The name of the package
-     * @param attributionTag attribution tag
-     * @param isAttributionTagValid whether the given attribution tag is valid
-     * @param bypass When to bypass certain op restrictions (can be null if edit == false)
-     * @param edit If an ops does not exist, create the ops?
-
-     * @return The ops
-     */
-    private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
-            boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
-        UidState uidState = getUidStateLocked(uid, edit);
-        if (uidState == null) {
-            return null;
-        }
-
-        if (uidState.pkgOps == null) {
-            if (!edit) {
-                return null;
-            }
-            uidState.pkgOps = new ArrayMap<>();
-        }
-
-        Ops ops = uidState.pkgOps.get(packageName);
-        if (ops == null) {
-            if (!edit) {
-                return null;
-            }
-            ops = new Ops(packageName, uidState);
-            uidState.pkgOps.put(packageName, ops);
-        }
-
-        if (edit) {
-            if (bypass != null) {
-                ops.bypass = bypass;
-            }
-
-            if (attributionTag != null) {
-                ops.knownAttributionTags.add(attributionTag);
-                if (isAttributionTagValid) {
-                    ops.validAttributionTags.add(attributionTag);
-                } else {
-                    ops.validAttributionTags.remove(attributionTag);
-                }
-            }
-        }
-
-        return ops;
-    }
-
-    @Override
-    public void scheduleWriteLocked() {
-        if (!mWriteScheduled) {
-            mWriteScheduled = true;
-            mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
-        }
-    }
-
-    @Override
-    public void scheduleFastWriteLocked() {
-        if (!mFastWriteScheduled) {
-            mWriteScheduled = true;
-            mFastWriteScheduled = true;
-            mHandler.removeCallbacks(mWriteRunner);
-            mHandler.postDelayed(mWriteRunner, 10*1000);
-        }
-    }
-
-    /**
-     * Get the state of an op for a uid.
-     *
-     * @param code The code of the op
-     * @param uid The uid the of the package
-     * @param packageName The package name for which to get the state for
-     * @param attributionTag The attribution tag
-     * @param isAttributionTagValid Whether the given attribution tag is valid
-     * @param bypass When to bypass certain op restrictions (can be null if edit == false)
-     * @param edit Iff {@code true} create the {@link Op} object if not yet created
-     *
-     * @return The {@link Op state} of the op
-     */
-    private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, boolean isAttributionTagValid,
-            @Nullable RestrictionBypass bypass, boolean edit) {
-        Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
-                edit);
-        if (ops == null) {
-            return null;
-        }
-        return getOpLocked(ops, code, uid, edit);
-    }
-
-    private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
-        Op op = ops.get(code);
-        if (op == null) {
-            if (!edit) {
-                return null;
-            }
-            op = new Op(ops.uidState, ops.packageName, code, uid);
-            ops.put(code, op);
-        }
-        if (edit) {
-            scheduleWriteLocked();
-        }
-        return op;
-    }
-
-    private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
-        if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
-            return false;
-        }
-        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
-        return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
-    }
-
-    private boolean isOpRestrictedLocked(int uid, int code, String packageName,
-            String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
-        int restrictionSetCount = mOpGlobalRestrictions.size();
-
-        for (int i = 0; i < restrictionSetCount; i++) {
-            ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
-            if (restrictionState.hasRestriction(code)) {
-                return true;
-            }
-        }
-
-        int userHandle = UserHandle.getUserId(uid);
-        restrictionSetCount = mOpUserRestrictions.size();
-
-        for (int i = 0; i < restrictionSetCount; i++) {
-            // For each client, check that the given op is not restricted, or that the given
-            // package is exempt from the restriction.
-            ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
-            if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
-                    isCheckOp)) {
-                RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
-                if (opBypass != null) {
-                    // If we are the system, bypass user restrictions for certain codes
-                    synchronized (this) {
-                        if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
-                            return false;
-                        }
-                        if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
-                            return false;
-                        }
-                        if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
-                                && appBypass.isRecordAudioRestrictionExcept) {
-                            return false;
-                        }
-                    }
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    void readState() {
-        int oldVersion = NO_VERSION;
-        synchronized (mFile) {
-            synchronized (this) {
-                FileInputStream stream;
-                try {
-                    stream = mFile.openRead();
-                } catch (FileNotFoundException e) {
-                    Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
-                    return;
-                }
-                boolean success = false;
-                mUidStates.clear();
-                mAppOpsServiceInterface.clearAllModes();
-                try {
-                    TypedXmlPullParser parser = Xml.resolvePullParser(stream);
-                    int type;
-                    while ((type = parser.next()) != XmlPullParser.START_TAG
-                            && type != XmlPullParser.END_DOCUMENT) {
-                        ;
-                    }
-
-                    if (type != XmlPullParser.START_TAG) {
-                        throw new IllegalStateException("no start tag found");
-                    }
-
-                    oldVersion = parser.getAttributeInt(null, "v", NO_VERSION);
-
-                    int outerDepth = parser.getDepth();
-                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                            continue;
-                        }
-
-                        String tagName = parser.getName();
-                        if (tagName.equals("pkg")) {
-                            readPackage(parser);
-                        } else if (tagName.equals("uid")) {
-                            readUidOps(parser);
-                        } else {
-                            Slog.w(TAG, "Unknown element under <app-ops>: "
-                                    + parser.getName());
-                            XmlUtils.skipCurrentTag(parser);
-                        }
-                    }
-                    success = true;
-                } catch (IllegalStateException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (NullPointerException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (NumberFormatException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (XmlPullParserException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (IOException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } catch (IndexOutOfBoundsException e) {
-                    Slog.w(TAG, "Failed parsing " + e);
-                } finally {
-                    if (!success) {
-                        mUidStates.clear();
-                        mAppOpsServiceInterface.clearAllModes();
-                    }
-                    try {
-                        stream.close();
-                    } catch (IOException e) {
-                    }
-                }
-            }
-        }
-        synchronized (this) {
-            upgradeLocked(oldVersion);
-        }
-    }
-
-    private void upgradeRunAnyInBackgroundLocked() {
-        for (int i = 0; i < mUidStates.size(); i++) {
-            final UidState uidState = mUidStates.valueAt(i);
-            if (uidState == null) {
-                continue;
-            }
-            SparseIntArray opModes = uidState.getNonDefaultUidModes();
-            if (opModes != null) {
-                final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
-                if (idx >= 0) {
-                    uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
-                            opModes.valueAt(idx));
-                }
-            }
-            if (uidState.pkgOps == null) {
-                continue;
-            }
-            boolean changed = false;
-            for (int j = 0; j < uidState.pkgOps.size(); j++) {
-                Ops ops = uidState.pkgOps.valueAt(j);
-                if (ops != null) {
-                    final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
-                    if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
-                        final Op copy = new Op(op.uidState, op.packageName,
-                                AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
-                        copy.setMode(op.getMode());
-                        ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
-                        changed = true;
-                    }
-                }
-            }
-            if (changed) {
-                uidState.evalForegroundOps();
-            }
-        }
-    }
-
-    private void upgradeLocked(int oldVersion) {
-        if (oldVersion >= CURRENT_VERSION) {
-            return;
-        }
-        Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
-        switch (oldVersion) {
-            case NO_VERSION:
-                upgradeRunAnyInBackgroundLocked();
-                // fall through
-            case 1:
-                // for future upgrades
-        }
-        scheduleFastWriteLocked();
-    }
-
-    private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
-            XmlPullParserException, IOException {
-        final int uid = parser.getAttributeInt(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                final int code = parser.getAttributeInt(null, "n");
-                final int mode = parser.getAttributeInt(null, "m");
-                setUidMode(code, uid, mode);
-            } else {
-                Slog.w(TAG, "Unknown element under <uid-ops>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    private void readPackage(TypedXmlPullParser parser)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        String pkgName = parser.getAttributeValue(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("uid")) {
-                readUid(parser, pkgName);
-            } else {
-                Slog.w(TAG, "Unknown element under <pkg>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    private void readUid(TypedXmlPullParser parser, String pkgName)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        int uid = parser.getAttributeInt(null, "n");
-        final UidState uidState = getUidStateLocked(uid, true);
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                readOp(parser, uidState, pkgName);
-            } else {
-                Slog.w(TAG, "Unknown element under <pkg>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-        uidState.evalForegroundOps();
-    }
-
-    private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
-            @Nullable String attribution)
-            throws NumberFormatException, IOException, XmlPullParserException {
-        final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
-
-        final long key = parser.getAttributeLong(null, "n");
-        final int uidState = extractUidStateFromKey(key);
-        final int opFlags = extractFlagsFromKey(key);
-
-        final long accessTime = parser.getAttributeLong(null, "t", 0);
-        final long rejectTime = parser.getAttributeLong(null, "r", 0);
-        final long accessDuration = parser.getAttributeLong(null, "d", -1);
-        final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
-        final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
-        final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
-
-        if (accessTime > 0) {
-            attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
-                    proxyAttributionTag, uidState, opFlags);
-        }
-        if (rejectTime > 0) {
-            attributedOp.rejected(rejectTime, uidState, opFlags);
-        }
-    }
-
-    private void readOp(TypedXmlPullParser parser,
-            @NonNull UidState uidState, @NonNull String pkgName)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        int opCode = parser.getAttributeInt(null, "n");
-        Op op = new Op(uidState, pkgName, opCode, uidState.uid);
-
-        final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
-        op.setMode(mode);
-
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            String tagName = parser.getName();
-            if (tagName.equals("st")) {
-                readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
-            } else {
-                Slog.w(TAG, "Unknown element under <op>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-
-        if (uidState.pkgOps == null) {
-            uidState.pkgOps = new ArrayMap<>();
-        }
-        Ops ops = uidState.pkgOps.get(pkgName);
-        if (ops == null) {
-            ops = new Ops(pkgName, uidState);
-            uidState.pkgOps.put(pkgName, ops);
-        }
-        ops.put(op.op, op);
-    }
-
-    void writeState() {
-        synchronized (mFile) {
-            FileOutputStream stream;
-            try {
-                stream = mFile.startWrite();
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to write state: " + e);
-                return;
-            }
-
-            List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
-
-            try {
-                TypedXmlSerializer out = Xml.resolveSerializer(stream);
-                out.startDocument(null, true);
-                out.startTag(null, "app-ops");
-                out.attributeInt(null, "v", CURRENT_VERSION);
-
-                SparseArray<SparseIntArray> uidStatesClone;
-                synchronized (this) {
-                    uidStatesClone = new SparseArray<>(mUidStates.size());
-
-                    final int uidStateCount = mUidStates.size();
-                    for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
-                        UidState uidState = mUidStates.valueAt(uidStateNum);
-                        int uid = mUidStates.keyAt(uidStateNum);
-
-                        SparseIntArray opModes = uidState.getNonDefaultUidModes();
-                        if (opModes != null && opModes.size() > 0) {
-                            uidStatesClone.put(uid, opModes);
-                        }
-                    }
-                }
-
-                final int uidStateCount = uidStatesClone.size();
-                for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
-                    SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
-                    if (opModes != null && opModes.size() > 0) {
-                        out.startTag(null, "uid");
-                        out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
-                        final int opCount = opModes.size();
-                        for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
-                            final int op = opModes.keyAt(opCountNum);
-                            final int mode = opModes.valueAt(opCountNum);
-                            out.startTag(null, "op");
-                            out.attributeInt(null, "n", op);
-                            out.attributeInt(null, "m", mode);
-                            out.endTag(null, "op");
-                        }
-                        out.endTag(null, "uid");
-                    }
-                }
-
-                if (allOps != null) {
-                    String lastPkg = null;
-                    for (int i=0; i<allOps.size(); i++) {
-                        AppOpsManager.PackageOps pkg = allOps.get(i);
-                        if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
-                            if (lastPkg != null) {
-                                out.endTag(null, "pkg");
-                            }
-                            lastPkg = pkg.getPackageName();
-                            if (lastPkg != null) {
-                                out.startTag(null, "pkg");
-                                out.attribute(null, "n", lastPkg);
-                            }
-                        }
-                        out.startTag(null, "uid");
-                        out.attributeInt(null, "n", pkg.getUid());
-                        List<AppOpsManager.OpEntry> ops = pkg.getOps();
-                        for (int j=0; j<ops.size(); j++) {
-                            AppOpsManager.OpEntry op = ops.get(j);
-                            out.startTag(null, "op");
-                            out.attributeInt(null, "n", op.getOp());
-                            if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
-                                out.attributeInt(null, "m", op.getMode());
-                            }
-
-                            for (String attributionTag : op.getAttributedOpEntries().keySet()) {
-                                final AttributedOpEntry attribution =
-                                        op.getAttributedOpEntries().get(attributionTag);
-
-                                final ArraySet<Long> keys = attribution.collectKeys();
-
-                                final int keyCount = keys.size();
-                                for (int k = 0; k < keyCount; k++) {
-                                    final long key = keys.valueAt(k);
-
-                                    final int uidState = AppOpsManager.extractUidStateFromKey(key);
-                                    final int flags = AppOpsManager.extractFlagsFromKey(key);
-
-                                    final long accessTime = attribution.getLastAccessTime(uidState,
-                                            uidState, flags);
-                                    final long rejectTime = attribution.getLastRejectTime(uidState,
-                                            uidState, flags);
-                                    final long accessDuration = attribution.getLastDuration(
-                                            uidState, uidState, flags);
-                                    // Proxy information for rejections is not backed up
-                                    final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
-                                            uidState, uidState, flags);
-
-                                    if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
-                                            && proxy == null) {
-                                        continue;
-                                    }
-
-                                    String proxyPkg = null;
-                                    String proxyAttributionTag = null;
-                                    int proxyUid = Process.INVALID_UID;
-                                    if (proxy != null) {
-                                        proxyPkg = proxy.getPackageName();
-                                        proxyAttributionTag = proxy.getAttributionTag();
-                                        proxyUid = proxy.getUid();
-                                    }
-
-                                    out.startTag(null, "st");
-                                    if (attributionTag != null) {
-                                        out.attribute(null, "id", attributionTag);
-                                    }
-                                    out.attributeLong(null, "n", key);
-                                    if (accessTime > 0) {
-                                        out.attributeLong(null, "t", accessTime);
-                                    }
-                                    if (rejectTime > 0) {
-                                        out.attributeLong(null, "r", rejectTime);
-                                    }
-                                    if (accessDuration > 0) {
-                                        out.attributeLong(null, "d", accessDuration);
-                                    }
-                                    if (proxyPkg != null) {
-                                        out.attribute(null, "pp", proxyPkg);
-                                    }
-                                    if (proxyAttributionTag != null) {
-                                        out.attribute(null, "pc", proxyAttributionTag);
-                                    }
-                                    if (proxyUid >= 0) {
-                                        out.attributeInt(null, "pu", proxyUid);
-                                    }
-                                    out.endTag(null, "st");
-                                }
-                            }
-
-                            out.endTag(null, "op");
-                        }
-                        out.endTag(null, "uid");
-                    }
-                    if (lastPkg != null) {
-                        out.endTag(null, "pkg");
-                    }
-                }
-
-                out.endTag(null, "app-ops");
-                out.endDocument();
-                mFile.finishWrite(stream);
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to write state, restoring backup.", e);
-                mFile.failWrite(stream);
-            }
-        }
-        mHistoricalRegistry.writeAndClearDiscreteHistory();
-    }
-
     static class Shell extends ShellCommand {
         final IAppOpsService mInterface;
         final AppOpsService mInternal;
@@ -4313,7 +1178,6 @@
         int mode;
         int packageUid;
         int nonpackageUid;
-        final static Binder sBinder = new Binder();
         IBinder mToken;
         boolean targetsUid;
 
@@ -4334,7 +1198,7 @@
             dumpCommandHelp(pw);
         }
 
-        static private int strOpToOp(String op, PrintWriter err) {
+        static int strOpToOp(String op, PrintWriter err) {
             try {
                 return AppOpsManager.strOpToOp(op);
             } catch (IllegalArgumentException e) {
@@ -4531,6 +1395,24 @@
         pw.println("              not specified, the current user is assumed.");
     }
 
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mAppOpsService.dump(fd, pw, args);
+
+        pw.println();
+        if (mCheckOpsDelegateDispatcher.mPolicy != null
+                && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
+            AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
+            policy.dumpTags(pw);
+        } else {
+            pw.println("  AppOps policy not set.");
+        }
+
+        if (mAudioRestrictionManager.hasActiveRestrictions()) {
+            pw.println();
+            mAudioRestrictionManager.dump(pw);
+        }
+    }
     static int onShellCommand(Shell shell, String cmd) {
         if (cmd == null) {
             return shell.handleDefaultCommands(cmd);
@@ -4734,14 +1616,12 @@
                     return 0;
                 }
                 case "write-settings": {
-                    shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
+                    shell.mInternal.mAppOpsService
+                            .enforceManageAppOpsModes(Binder.getCallingPid(),
                             Binder.getCallingUid(), -1);
                     final long token = Binder.clearCallingIdentity();
                     try {
-                        synchronized (shell.mInternal) {
-                            shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner);
-                        }
-                        shell.mInternal.writeState();
+                        shell.mInternal.mAppOpsService.writeState();
                         pw.println("Current settings written.");
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -4749,11 +1629,12 @@
                     return 0;
                 }
                 case "read-settings": {
-                    shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
-                            Binder.getCallingUid(), -1);
+                    shell.mInternal.mAppOpsService
+                            .enforceManageAppOpsModes(Binder.getCallingPid(),
+                                    Binder.getCallingUid(), -1);
                     final long token = Binder.clearCallingIdentity();
                     try {
-                        shell.mInternal.readState();
+                        shell.mInternal.mAppOpsService.readState();
                         pw.println("Last settings read.");
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -4799,877 +1680,70 @@
         return -1;
     }
 
-    private void dumpHelp(PrintWriter pw) {
-        pw.println("AppOps service (appops) dump options:");
-        pw.println("  -h");
-        pw.println("    Print this help text.");
-        pw.println("  --op [OP]");
-        pw.println("    Limit output to data associated with the given app op code.");
-        pw.println("  --mode [MODE]");
-        pw.println("    Limit output to data associated with the given app op mode.");
-        pw.println("  --package [PACKAGE]");
-        pw.println("    Limit output to data associated with the given package name.");
-        pw.println("  --attributionTag [attributionTag]");
-        pw.println("    Limit output to data associated with the given attribution tag.");
-        pw.println("  --include-discrete [n]");
-        pw.println("    Include discrete ops limited to n per dimension. Use zero for no limit.");
-        pw.println("  --watchers");
-        pw.println("    Only output the watcher sections.");
-        pw.println("  --history");
-        pw.println("    Only output history.");
-        pw.println("  --uid-state-changes");
-        pw.println("    Include logs about uid state changes.");
-    }
-
-    private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
-            @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
-            @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
-        final int numAttributions = op.mAttributions.size();
-        for (int i = 0; i < numAttributions; i++) {
-            if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
-                    op.mAttributions.keyAt(i), filterAttributionTag)) {
-                continue;
-            }
-
-            pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
-            dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
-                    prefix + "  ");
-            pw.print(prefix + "]\n");
-        }
-    }
-
-    private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
-            @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
-            @NonNull Date date, @NonNull String prefix) {
-
-        final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
-                attributionTag).getAttributedOpEntries().get(attributionTag);
-
-        final ArraySet<Long> keys = entry.collectKeys();
-
-        final int keyCount = keys.size();
-        for (int k = 0; k < keyCount; k++) {
-            final long key = keys.valueAt(k);
-
-            final int uidState = AppOpsManager.extractUidStateFromKey(key);
-            final int flags = AppOpsManager.extractFlagsFromKey(key);
-
-            final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
-            final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
-            final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
-            final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
-
-            String proxyPkg = null;
-            String proxyAttributionTag = null;
-            int proxyUid = Process.INVALID_UID;
-            if (proxy != null) {
-                proxyPkg = proxy.getPackageName();
-                proxyAttributionTag = proxy.getAttributionTag();
-                proxyUid = proxy.getUid();
-            }
-
-            if (accessTime > 0) {
-                pw.print(prefix);
-                pw.print("Access: ");
-                pw.print(AppOpsManager.keyToString(key));
-                pw.print(" ");
-                date.setTime(accessTime);
-                pw.print(sdf.format(date));
-                pw.print(" (");
-                TimeUtils.formatDuration(accessTime - now, pw);
-                pw.print(")");
-                if (accessDuration > 0) {
-                    pw.print(" duration=");
-                    TimeUtils.formatDuration(accessDuration, pw);
-                }
-                if (proxyUid >= 0) {
-                    pw.print(" proxy[");
-                    pw.print("uid=");
-                    pw.print(proxyUid);
-                    pw.print(", pkg=");
-                    pw.print(proxyPkg);
-                    pw.print(", attributionTag=");
-                    pw.print(proxyAttributionTag);
-                    pw.print("]");
-                }
-                pw.println();
-            }
-
-            if (rejectTime > 0) {
-                pw.print(prefix);
-                pw.print("Reject: ");
-                pw.print(AppOpsManager.keyToString(key));
-                date.setTime(rejectTime);
-                pw.print(sdf.format(date));
-                pw.print(" (");
-                TimeUtils.formatDuration(rejectTime - now, pw);
-                pw.print(")");
-                if (proxyUid >= 0) {
-                    pw.print(" proxy[");
-                    pw.print("uid=");
-                    pw.print(proxyUid);
-                    pw.print(", pkg=");
-                    pw.print(proxyPkg);
-                    pw.print(", attributionTag=");
-                    pw.print(proxyAttributionTag);
-                    pw.print("]");
-                }
-                pw.println();
-            }
-        }
-
-        final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
-        if (attributedOp.isRunning()) {
-            long earliestElapsedTime = Long.MAX_VALUE;
-            long maxNumStarts = 0;
-            int numInProgressEvents = attributedOp.mInProgressEvents.size();
-            for (int i = 0; i < numInProgressEvents; i++) {
-                AttributedOp.InProgressStartOpEvent event =
-                        attributedOp.mInProgressEvents.valueAt(i);
-
-                earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
-                maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
-            }
-
-            pw.print(prefix + "Running start at: ");
-            TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
-            pw.println();
-
-            if (maxNumStarts > 1) {
-                pw.print(prefix + "startNesting=");
-                pw.println(maxNumStarts);
-            }
-        }
-    }
-
-    @NeverCompile // Avoid size overhead of debugging code.
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
-
-        int dumpOp = OP_NONE;
-        String dumpPackage = null;
-        String dumpAttributionTag = null;
-        int dumpUid = Process.INVALID_UID;
-        int dumpMode = -1;
-        boolean dumpWatchers = false;
-        // TODO ntmyren: Remove the dumpHistory and dumpFilter
-        boolean dumpHistory = false;
-        boolean includeDiscreteOps = false;
-        boolean dumpUidStateChangeLogs = false;
-        int nDiscreteOps = 10;
-        @HistoricalOpsRequestFilter int dumpFilter = 0;
-        boolean dumpAll = false;
-
-        if (args != null) {
-            for (int i = 0; i < args.length; i++) {
-                String arg = args[i];
-                if ("-h".equals(arg)) {
-                    dumpHelp(pw);
-                    return;
-                } else if ("-a".equals(arg)) {
-                    // dump all data
-                    dumpAll = true;
-                } else if ("--op".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --op option");
-                        return;
-                    }
-                    dumpOp = Shell.strOpToOp(args[i], pw);
-                    dumpFilter |= FILTER_BY_OP_NAMES;
-                    if (dumpOp < 0) {
-                        return;
-                    }
-                } else if ("--package".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --package option");
-                        return;
-                    }
-                    dumpPackage = args[i];
-                    dumpFilter |= FILTER_BY_PACKAGE_NAME;
-                    try {
-                        dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
-                                PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
-                                0);
-                    } catch (RemoteException e) {
-                    }
-                    if (dumpUid < 0) {
-                        pw.println("Unknown package: " + dumpPackage);
-                        return;
-                    }
-                    dumpUid = UserHandle.getAppId(dumpUid);
-                    dumpFilter |= FILTER_BY_UID;
-                } else if ("--attributionTag".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --attributionTag option");
-                        return;
-                    }
-                    dumpAttributionTag = args[i];
-                    dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
-                } else if ("--mode".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --mode option");
-                        return;
-                    }
-                    dumpMode = Shell.strModeToMode(args[i], pw);
-                    if (dumpMode < 0) {
-                        return;
-                    }
-                } else if ("--watchers".equals(arg)) {
-                    dumpWatchers = true;
-                } else if ("--include-discrete".equals(arg)) {
-                    i++;
-                    if (i >= args.length) {
-                        pw.println("No argument for --include-discrete option");
-                        return;
-                    }
-                    try {
-                        nDiscreteOps = Integer.valueOf(args[i]);
-                    } catch (NumberFormatException e) {
-                        pw.println("Wrong parameter: " + args[i]);
-                        return;
-                    }
-                    includeDiscreteOps = true;
-                } else if ("--history".equals(arg)) {
-                    dumpHistory = true;
-                } else if (arg.length() > 0 && arg.charAt(0) == '-') {
-                    pw.println("Unknown option: " + arg);
-                    return;
-                } else if ("--uid-state-changes".equals(arg)) {
-                    dumpUidStateChangeLogs = true;
-                } else {
-                    pw.println("Unknown command: " + arg);
-                    return;
-                }
-            }
-        }
-
-        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-        final Date date = new Date();
-        synchronized (this) {
-            pw.println("Current AppOps Service state:");
-            if (!dumpHistory && !dumpWatchers) {
-                mConstants.dump(pw);
-            }
-            pw.println();
-            final long now = System.currentTimeMillis();
-            final long nowElapsed = SystemClock.elapsedRealtime();
-            final long nowUptime = SystemClock.uptimeMillis();
-            boolean needSep = false;
-            if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
-                    && !dumpHistory) {
-                pw.println("  Profile owners:");
-                for (int poi = 0; poi < mProfileOwners.size(); poi++) {
-                    pw.print("    User #");
-                    pw.print(mProfileOwners.keyAt(poi));
-                    pw.print(": ");
-                    UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
-                    pw.println();
-                }
-                pw.println();
-            }
-
-            if (!dumpHistory) {
-                needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
-            }
-
-            if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
-                boolean printedHeader = false;
-                for (int i = 0; i < mModeWatchers.size(); i++) {
-                    final ModeCallback cb = mModeWatchers.valueAt(i);
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
-                        continue;
-                    }
-                    needSep = true;
-                    if (!printedHeader) {
-                        pw.println("  All op mode watchers:");
-                        printedHeader = true;
-                    }
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
-                    pw.print(": "); pw.println(cb);
-                }
-            }
-            if (mActiveWatchers.size() > 0 && dumpMode < 0) {
-                needSep = true;
-                boolean printedHeader = false;
-                for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
-                    final SparseArray<ActiveCallback> activeWatchers =
-                            mActiveWatchers.valueAt(watcherNum);
-                    if (activeWatchers.size() <= 0) {
-                        continue;
-                    }
-                    final ActiveCallback cb = activeWatchers.valueAt(0);
-                    if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
-                        continue;
-                    }
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
-                        continue;
-                    }
-                    if (!printedHeader) {
-                        pw.println("  All op active watchers:");
-                        printedHeader = true;
-                    }
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(
-                            mActiveWatchers.keyAt(watcherNum))));
-                    pw.println(" ->");
-                    pw.print("        [");
-                    final int opCount = activeWatchers.size();
-                    for (int opNum = 0; opNum < opCount; opNum++) {
-                        if (opNum > 0) {
-                            pw.print(' ');
-                        }
-                        pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
-                        if (opNum < opCount - 1) {
-                            pw.print(',');
-                        }
-                    }
-                    pw.println("]");
-                    pw.print("        ");
-                    pw.println(cb);
-                }
-            }
-            if (mStartedWatchers.size() > 0 && dumpMode < 0) {
-                needSep = true;
-                boolean printedHeader = false;
-
-                final int watchersSize = mStartedWatchers.size();
-                for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
-                    final SparseArray<StartedCallback> startedWatchers =
-                            mStartedWatchers.valueAt(watcherNum);
-                    if (startedWatchers.size() <= 0) {
-                        continue;
-                    }
-
-                    final StartedCallback cb = startedWatchers.valueAt(0);
-                    if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
-                        continue;
-                    }
-
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
-                        continue;
-                    }
-
-                    if (!printedHeader) {
-                        pw.println("  All op started watchers:");
-                        printedHeader = true;
-                    }
-
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(
-                            mStartedWatchers.keyAt(watcherNum))));
-                    pw.println(" ->");
-
-                    pw.print("        [");
-                    final int opCount = startedWatchers.size();
-                    for (int opNum = 0; opNum < opCount; opNum++) {
-                        if (opNum > 0) {
-                            pw.print(' ');
-                        }
-
-                        pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
-                        if (opNum < opCount - 1) {
-                            pw.print(',');
-                        }
-                    }
-                    pw.println("]");
-
-                    pw.print("        ");
-                    pw.println(cb);
-                }
-            }
-            if (mNotedWatchers.size() > 0 && dumpMode < 0) {
-                needSep = true;
-                boolean printedHeader = false;
-                for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
-                    final SparseArray<NotedCallback> notedWatchers =
-                            mNotedWatchers.valueAt(watcherNum);
-                    if (notedWatchers.size() <= 0) {
-                        continue;
-                    }
-                    final NotedCallback cb = notedWatchers.valueAt(0);
-                    if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
-                        continue;
-                    }
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
-                        continue;
-                    }
-                    if (!printedHeader) {
-                        pw.println("  All op noted watchers:");
-                        printedHeader = true;
-                    }
-                    pw.print("    ");
-                    pw.print(Integer.toHexString(System.identityHashCode(
-                            mNotedWatchers.keyAt(watcherNum))));
-                    pw.println(" ->");
-                    pw.print("        [");
-                    final int opCount = notedWatchers.size();
-                    for (int opNum = 0; opNum < opCount; opNum++) {
-                        if (opNum > 0) {
-                            pw.print(' ');
-                        }
-                        pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
-                        if (opNum < opCount - 1) {
-                            pw.print(',');
-                        }
-                    }
-                    pw.println("]");
-                    pw.print("        ");
-                    pw.println(cb);
-                }
-            }
-            if (mAudioRestrictionManager.hasActiveRestrictions() && dumpOp < 0
-                    && dumpPackage != null && dumpMode < 0 && !dumpWatchers) {
-                needSep = mAudioRestrictionManager.dump(pw) || needSep;
-            }
-            if (needSep) {
-                pw.println();
-            }
-            for (int i=0; i<mUidStates.size(); i++) {
-                UidState uidState = mUidStates.valueAt(i);
-                final SparseIntArray opModes = uidState.getNonDefaultUidModes();
-                final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
-
-                if (dumpWatchers || dumpHistory) {
-                    continue;
-                }
-                if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
-                    boolean hasOp = dumpOp < 0 || (opModes != null
-                            && opModes.indexOfKey(dumpOp) >= 0);
-                    boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
-                    boolean hasMode = dumpMode < 0;
-                    if (!hasMode && opModes != null) {
-                        for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
-                            if (opModes.valueAt(opi) == dumpMode) {
-                                hasMode = true;
-                            }
-                        }
-                    }
-                    if (pkgOps != null) {
-                        for (int pkgi = 0;
-                                 (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
-                                 pkgi++) {
-                            Ops ops = pkgOps.valueAt(pkgi);
-                            if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
-                                hasOp = true;
-                            }
-                            if (!hasMode) {
-                                for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
-                                    if (ops.valueAt(opi).getMode() == dumpMode) {
-                                        hasMode = true;
-                                    }
-                                }
-                            }
-                            if (!hasPackage && dumpPackage.equals(ops.packageName)) {
-                                hasPackage = true;
-                            }
-                        }
-                    }
-                    if (uidState.foregroundOps != null && !hasOp) {
-                        if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
-                            hasOp = true;
-                        }
-                    }
-                    if (!hasOp || !hasPackage || !hasMode) {
-                        continue;
-                    }
-                }
-
-                pw.print("  Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":");
-                uidState.dump(pw, nowElapsed);
-                if (uidState.foregroundOps != null && (dumpMode < 0
-                        || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
-                    pw.println("    foregroundOps:");
-                    for (int j = 0; j < uidState.foregroundOps.size(); j++) {
-                        if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
-                            continue;
-                        }
-                        pw.print("      ");
-                        pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
-                        pw.print(": ");
-                        pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
-                    }
-                    pw.print("    hasForegroundWatchers=");
-                    pw.println(uidState.hasForegroundWatchers);
-                }
-                needSep = true;
-
-                if (opModes != null) {
-                    final int opModeCount = opModes.size();
-                    for (int j = 0; j < opModeCount; j++) {
-                        final int code = opModes.keyAt(j);
-                        final int mode = opModes.valueAt(j);
-                        if (dumpOp >= 0 && dumpOp != code) {
-                            continue;
-                        }
-                        if (dumpMode >= 0 && dumpMode != mode) {
-                            continue;
-                        }
-                        pw.print("      "); pw.print(AppOpsManager.opToName(code));
-                        pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
-                    }
-                }
-
-                if (pkgOps == null) {
-                    continue;
-                }
-
-                for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
-                    final Ops ops = pkgOps.valueAt(pkgi);
-                    if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
-                        continue;
-                    }
-                    boolean printedPackage = false;
-                    for (int j=0; j<ops.size(); j++) {
-                        final Op op = ops.valueAt(j);
-                        final int opCode = op.op;
-                        if (dumpOp >= 0 && dumpOp != opCode) {
-                            continue;
-                        }
-                        if (dumpMode >= 0 && dumpMode != op.getMode()) {
-                            continue;
-                        }
-                        if (!printedPackage) {
-                            pw.print("    Package "); pw.print(ops.packageName); pw.println(":");
-                            printedPackage = true;
-                        }
-                        pw.print("      "); pw.print(AppOpsManager.opToName(opCode));
-                        pw.print(" ("); pw.print(AppOpsManager.modeToName(op.getMode()));
-                        final int switchOp = AppOpsManager.opToSwitch(opCode);
-                        if (switchOp != opCode) {
-                            pw.print(" / switch ");
-                            pw.print(AppOpsManager.opToName(switchOp));
-                            final Op switchObj = ops.get(switchOp);
-                            int mode = switchObj == null
-                                    ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
-                            pw.print("="); pw.print(AppOpsManager.modeToName(mode));
-                        }
-                        pw.println("): ");
-                        dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
-                                sdf, date, "        ");
-                    }
-                }
-            }
-            if (needSep) {
-                pw.println();
-            }
-
-            boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
-            mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
-
-            if (!dumpHistory && !dumpWatchers) {
-                pw.println();
-                if (mCheckOpsDelegateDispatcher.mPolicy != null
-                        && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
-                    AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
-                    policy.dumpTags(pw);
-                } else {
-                    pw.println("  AppOps policy not set.");
-                }
-            }
-
-            if (dumpAll || dumpUidStateChangeLogs) {
-                pw.println();
-                pw.println("Uid State Changes Event Log:");
-                getUidStateTracker().dumpEvents(pw);
-            }
-        }
-
-        // Must not hold the appops lock
-        if (dumpHistory && !dumpWatchers) {
-            mHistoricalRegistry.dump("  ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
-                    dumpFilter);
-        }
-        if (includeDiscreteOps) {
-            pw.println("Discrete accesses: ");
-            mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
-                    dumpFilter, dumpOp, sdf, date, "  ", nDiscreteOps);
-        }
-    }
-
     @Override
     public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
-        checkSystemUid("setUserRestrictions");
-        Objects.requireNonNull(restrictions);
-        Objects.requireNonNull(token);
-        for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
-            String restriction = AppOpsManager.opToRestriction(i);
-            if (restriction != null) {
-                setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
-                        userHandle, null);
-            }
-        }
+        mAppOpsService.setUserRestrictions(restrictions, token, userHandle);
     }
 
     @Override
     public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
             PackageTagsList excludedPackageTags) {
-        if (Binder.getCallingPid() != Process.myPid()) {
-            mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
-                    Binder.getCallingPid(), Binder.getCallingUid(), null);
-        }
-        if (userHandle != UserHandle.getCallingUserId()) {
-            if (mContext.checkCallingOrSelfPermission(Manifest.permission
-                    .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
-                && mContext.checkCallingOrSelfPermission(Manifest.permission
-                    .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
-                        + " INTERACT_ACROSS_USERS to interact cross user ");
-            }
-        }
-        verifyIncomingOp(code);
-        Objects.requireNonNull(token);
-        setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
-    }
-
-    private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
-            int userHandle, PackageTagsList excludedPackageTags) {
-        synchronized (AppOpsService.this) {
-            ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
-
-            if (restrictionState == null) {
-                try {
-                    restrictionState = new ClientUserRestrictionState(token);
-                } catch (RemoteException e) {
-                    return;
-                }
-                mOpUserRestrictions.put(token, restrictionState);
-            }
-
-            if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
-                    userHandle)) {
-                mHandler.sendMessage(PooledLambda.obtainMessage(
-                        AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
-                mHandler.sendMessage(PooledLambda.obtainMessage(
-                        AppOpsService::updateStartedOpModeForUser, this, code, restricted,
-                        userHandle));
-            }
-
-            if (restrictionState.isDefault()) {
-                mOpUserRestrictions.remove(token);
-                restrictionState.destroy();
-            }
-        }
-    }
-
-    private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
-        synchronized (AppOpsService.this) {
-            int numUids = mUidStates.size();
-            for (int uidNum = 0; uidNum < numUids; uidNum++) {
-                int uid = mUidStates.keyAt(uidNum);
-                if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
-                    continue;
-                }
-                updateStartedOpModeForUidLocked(code, restricted, uid);
-            }
-        }
-    }
-
-    private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
-        UidState uidState = mUidStates.get(uid);
-        if (uidState == null || uidState.pkgOps == null) {
-            return;
-        }
-
-        int numPkgOps = uidState.pkgOps.size();
-        for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
-            Ops ops = uidState.pkgOps.valueAt(pkgNum);
-            Op op = ops != null ? ops.get(code) : null;
-            if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
-                continue;
-            }
-            int numAttrTags = op.mAttributions.size();
-            for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
-                AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
-                if (restricted && attrOp.isRunning()) {
-                    attrOp.pause();
-                } else if (attrOp.isPaused()) {
-                    attrOp.resume();
-                }
-            }
-        }
-    }
-
-    private void notifyWatchersOfChange(int code, int uid) {
-        final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
-        synchronized (this) {
-            modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code);
-            if (modeChangedListenerSet == null) {
-                return;
-            }
-        }
-
-        notifyOpChanged(modeChangedListenerSet,  code, uid, null);
+        mAppOpsService.setUserRestriction(code, restricted, token, userHandle,
+                excludedPackageTags);
     }
 
     @Override
     public void removeUser(int userHandle) throws RemoteException {
-        checkSystemUid("removeUser");
-        synchronized (AppOpsService.this) {
-            final int tokenCount = mOpUserRestrictions.size();
-            for (int i = tokenCount - 1; i >= 0; i--) {
-                ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
-                opRestrictions.removeUser(userHandle);
-            }
-            removeUidsForUserLocked(userHandle);
-        }
+        mAppOpsService.removeUser(userHandle);
     }
 
     @Override
     public boolean isOperationActive(int code, int uid, String packageName) {
-        if (Binder.getCallingUid() != uid) {
-            if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
-                    != PackageManager.PERMISSION_GRANTED) {
-                return false;
-            }
-        }
-        verifyIncomingOp(code);
-        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            return false;
-        }
-
-        final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return false;
-        }
-        // TODO moltmann: Allow to check for attribution op activeness
-        synchronized (AppOpsService.this) {
-            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
-            if (pkgOps == null) {
-                return false;
-            }
-
-            Op op = pkgOps.get(code);
-            if (op == null) {
-                return false;
-            }
-
-            return op.isRunning();
-        }
+        return mAppOpsService.isOperationActive(code, uid, packageName);
     }
 
     @Override
     public boolean isProxying(int op, @NonNull String proxyPackageName,
             @NonNull String proxyAttributionTag, int proxiedUid,
             @NonNull String proxiedPackageName) {
-        Objects.requireNonNull(proxyPackageName);
-        Objects.requireNonNull(proxiedPackageName);
-        final long callingUid = Binder.getCallingUid();
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
-                    proxiedPackageName, new int[] {op});
-            if (packageOps == null || packageOps.isEmpty()) {
-                return false;
-            }
-            final List<OpEntry> opEntries = packageOps.get(0).getOps();
-            if (opEntries.isEmpty()) {
-                return false;
-            }
-            final OpEntry opEntry = opEntries.get(0);
-            if (!opEntry.isRunning()) {
-                return false;
-            }
-            final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
-                    OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
-            return proxyInfo != null && callingUid == proxyInfo.getUid()
-                    && proxyPackageName.equals(proxyInfo.getPackageName())
-                    && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        return mAppOpsService.isProxying(op, proxyPackageName, proxyAttributionTag,
+                proxiedUid, proxiedPackageName);
     }
 
     @Override
     public void resetPackageOpsNoHistory(@NonNull String packageName) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "resetPackageOpsNoHistory");
-        synchronized (AppOpsService.this) {
-            final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
-                    UserHandle.getCallingUserId());
-            if (uid == Process.INVALID_UID) {
-                return;
-            }
-            UidState uidState = mUidStates.get(uid);
-            if (uidState == null || uidState.pkgOps == null) {
-                return;
-            }
-            Ops removedOps = uidState.pkgOps.remove(packageName);
-            mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
-            if (removedOps != null) {
-                scheduleFastWriteLocked();
-            }
-        }
+        mAppOpsService.resetPackageOpsNoHistory(packageName);
     }
 
     @Override
     public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
             long baseSnapshotInterval, int compressionStep) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "setHistoryParameters");
-        // Must not hold the appops lock
-        mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+        mAppOpsService.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
     }
 
     @Override
     public void offsetHistory(long offsetMillis) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "offsetHistory");
-        // Must not hold the appops lock
-        mHistoricalRegistry.offsetHistory(offsetMillis);
-        mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
+        mAppOpsService.offsetHistory(offsetMillis);
     }
 
     @Override
     public void addHistoricalOps(HistoricalOps ops) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "addHistoricalOps");
-        // Must not hold the appops lock
-        mHistoricalRegistry.addHistoricalOps(ops);
+        mAppOpsService.addHistoricalOps(ops);
     }
 
     @Override
     public void resetHistoryParameters() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "resetHistoryParameters");
-        // Must not hold the appops lock
-        mHistoricalRegistry.resetHistoryParameters();
+        mAppOpsService.resetHistoryParameters();
     }
 
     @Override
     public void clearHistory() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "clearHistory");
-        // Must not hold the appops lock
-        mHistoricalRegistry.clearAllHistory();
+        mAppOpsService.clearHistory();
     }
 
     @Override
     public void rebootHistory(long offlineDurationMillis) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "rebootHistory");
-
-        Preconditions.checkArgument(offlineDurationMillis >= 0);
-
-        // Must not hold the appops lock
-        mHistoricalRegistry.shutdown();
-
-        if (offlineDurationMillis > 0) {
-            SystemClock.sleep(offlineDurationMillis);
-        }
-
-        mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
-        mHistoricalRegistry.systemReady(mContext.getContentResolver());
-        mHistoricalRegistry.persistPendingHistory();
+        mAppOpsService.rebootHistory(offlineDurationMillis);
     }
 
     /**
@@ -5924,24 +1998,6 @@
         return false;
     }
 
-    @GuardedBy("this")
-    private void removeUidsForUserLocked(int userHandle) {
-        for (int i = mUidStates.size() - 1; i >= 0; --i) {
-            final int uid = mUidStates.keyAt(i);
-            if (UserHandle.getUserId(uid) == userHandle) {
-                mUidStates.valueAt(i).clear();
-                mUidStates.removeAt(i);
-            }
-        }
-    }
-
-    private void checkSystemUid(String function) {
-        int uid = Binder.getCallingUid();
-        if (uid != Process.SYSTEM_UID) {
-            throw new SecurityException(function + " must by called by the system");
-        }
-    }
-
     private static int resolveUid(String packageName)  {
         if (packageName == null) {
             return Process.INVALID_UID;
@@ -5962,184 +2018,43 @@
         return Process.INVALID_UID;
     }
 
-    private static String[] getPackagesForUid(int uid) {
-        String[] packageNames = null;
-
-        // Very early during boot the package manager is not yet or not yet fully started. At this
-        // time there are no packages yet.
-        if (AppGlobals.getPackageManager() != null) {
-            try {
-                packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
-            } catch (RemoteException e) {
-                /* ignore - local call */
-            }
-        }
-        if (packageNames == null) {
-            return EmptyArray.STRING;
-        }
-        return packageNames;
-    }
-
-    private final class ClientUserRestrictionState implements DeathRecipient {
-        private final IBinder token;
-
-        ClientUserRestrictionState(IBinder token)
-                throws RemoteException {
-            token.linkToDeath(this, 0);
-            this.token = token;
-        }
-
-        public boolean setRestriction(int code, boolean restricted,
-                PackageTagsList excludedPackageTags, int userId) {
-            return mAppOpsRestrictions.setUserRestriction(token, userId, code,
-                    restricted, excludedPackageTags);
-        }
-
-        public boolean hasRestriction(int code, String packageName, String attributionTag,
-                int userId, boolean isCheckOp) {
-            return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName,
-                    attributionTag, isCheckOp);
-        }
-
-        public void removeUser(int userId) {
-            mAppOpsRestrictions.clearUserRestrictions(token, userId);
-        }
-
-        public boolean isDefault() {
-            return !mAppOpsRestrictions.hasUserRestrictions(token);
-        }
-
-        @Override
-        public void binderDied() {
-            synchronized (AppOpsService.this) {
-                mAppOpsRestrictions.clearUserRestrictions(token);
-                mOpUserRestrictions.remove(token);
-                destroy();
-            }
-        }
-
-        public void destroy() {
-            token.unlinkToDeath(this, 0);
-        }
-    }
-
-    private final class ClientGlobalRestrictionState implements DeathRecipient {
-        final IBinder mToken;
-
-        ClientGlobalRestrictionState(IBinder token)
-                throws RemoteException {
-            token.linkToDeath(this, 0);
-            this.mToken = token;
-        }
-
-        boolean setRestriction(int code, boolean restricted) {
-            return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
-        }
-
-        boolean hasRestriction(int code) {
-            return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
-        }
-
-        boolean isDefault() {
-            return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
-        }
-
-        @Override
-        public void binderDied() {
-            mAppOpsRestrictions.clearGlobalRestrictions(mToken);
-            mOpGlobalRestrictions.remove(mToken);
-            destroy();
-        }
-
-        void destroy() {
-            mToken.unlinkToDeath(this, 0);
-        }
-    }
-
     private final class AppOpsManagerInternalImpl extends AppOpsManagerInternal {
         @Override public void setDeviceAndProfileOwners(SparseIntArray owners) {
-            synchronized (AppOpsService.this) {
-                mProfileOwners = owners;
-            }
+            AppOpsService.this.mAppOpsService.setDeviceAndProfileOwners(owners);
         }
 
         @Override
         public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames,
                 boolean visible) {
-            AppOpsService.this.updateAppWidgetVisibility(uidPackageNames, visible);
+            AppOpsService.this.mAppOpsService
+                    .updateAppWidgetVisibility(uidPackageNames, visible);
         }
 
         @Override
         public void setUidModeFromPermissionPolicy(int code, int uid, int mode,
                 @Nullable IAppOpsCallback callback) {
-            setUidMode(code, uid, mode, callback);
+            AppOpsService.this.mAppOpsService.setUidMode(code, uid, mode, callback);
         }
 
         @Override
         public void setModeFromPermissionPolicy(int code, int uid, @NonNull String packageName,
                 int mode, @Nullable IAppOpsCallback callback) {
-            setMode(code, uid, packageName, mode, callback);
+            AppOpsService.this.mAppOpsService
+                    .setMode(code, uid, packageName, mode, callback);
         }
 
 
         @Override
         public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
-            if (Binder.getCallingPid() != Process.myPid()) {
-                // TODO instead of this enforcement put in AppOpsManagerInternal
-                throw new SecurityException("Only the system can set global restrictions");
-            }
-
-            synchronized (AppOpsService.this) {
-                ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
-
-                if (restrictionState == null) {
-                    try {
-                        restrictionState = new ClientGlobalRestrictionState(token);
-                    } catch (RemoteException  e) {
-                        return;
-                    }
-                    mOpGlobalRestrictions.put(token, restrictionState);
-                }
-
-                if (restrictionState.setRestriction(code, restricted)) {
-                    mHandler.sendMessage(PooledLambda.obtainMessage(
-                            AppOpsService::notifyWatchersOfChange, AppOpsService.this, code,
-                            UID_ANY));
-                    mHandler.sendMessage(PooledLambda.obtainMessage(
-                            AppOpsService::updateStartedOpModeForUser, AppOpsService.this,
-                            code, restricted, UserHandle.USER_ALL));
-                }
-
-                if (restrictionState.isDefault()) {
-                    mOpGlobalRestrictions.remove(token);
-                    restrictionState.destroy();
-                }
-            }
+            AppOpsService.this.mAppOpsService
+                    .setGlobalRestriction(code, restricted, token);
         }
 
         @Override
         public int getOpRestrictionCount(int code, UserHandle user, String pkg,
                 String attributionTag) {
-            int number = 0;
-            synchronized (AppOpsService.this) {
-                int numRestrictions = mOpUserRestrictions.size();
-                for (int i = 0; i < numRestrictions; i++) {
-                    if (mOpUserRestrictions.valueAt(i)
-                            .hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
-                                    false)) {
-                        number++;
-                    }
-                }
-
-                numRestrictions = mOpGlobalRestrictions.size();
-                for (int i = 0; i < numRestrictions; i++) {
-                    if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
-                        number++;
-                    }
-                }
-            }
-
-            return number;
+            return AppOpsService.this.mAppOpsService
+                    .getOpRestrictionCount(code, user, pkg, attributionTag);
         }
     }
 
@@ -6262,7 +2177,6 @@
         Objects.requireNonNull(stackTrace);
         Preconditions.checkArgument(op >= 0);
         Preconditions.checkArgument(op < AppOpsManager._NUM_OP);
-        Objects.requireNonNull(version);
 
         NoteOpTrace noteOpTrace = new NoteOpTrace(stackTrace, op, packageName, version);
 
@@ -6436,42 +2350,42 @@
                     attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
         }
 
-        public SyncNotedAppOp startProxyOperation(int code,
+        public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
                 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
                 @AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
-                    return mPolicy.startProxyOperation(code, attributionSource,
+                    return mPolicy.startProxyOperation(clientId, code, attributionSource,
                             startIfModeDefault, shouldCollectAsyncNotedOp, message,
                             shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
                             proxiedAttributionFlags, attributionChainId,
                             this::startDelegateProxyOperationImpl);
                 } else {
-                    return mPolicy.startProxyOperation(code, attributionSource,
+                    return mPolicy.startProxyOperation(clientId, code, attributionSource,
                             startIfModeDefault, shouldCollectAsyncNotedOp, message,
                             shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
                             proxiedAttributionFlags, attributionChainId,
                             AppOpsService.this::startProxyOperationImpl);
                 }
             } else if (mCheckOpsDelegate != null) {
-                return startDelegateProxyOperationImpl(code, attributionSource,
+                return startDelegateProxyOperationImpl(clientId, code, attributionSource,
                         startIfModeDefault, shouldCollectAsyncNotedOp, message,
                         shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
                         proxiedAttributionFlags, attributionChainId);
             }
-            return startProxyOperationImpl(code, attributionSource, startIfModeDefault,
+            return startProxyOperationImpl(clientId, code, attributionSource, startIfModeDefault,
                     shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
                     proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
         }
 
-        private SyncNotedAppOp startDelegateProxyOperationImpl(int code,
+        private SyncNotedAppOp startDelegateProxyOperationImpl(IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
                 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
                 @AttributionFlags int proxiedAttributionFlsgs, int attributionChainId) {
-            return mCheckOpsDelegate.startProxyOperation(code, attributionSource,
+            return mCheckOpsDelegate.startProxyOperation(clientId, code, attributionSource,
                     startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                     skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlsgs,
                     attributionChainId, AppOpsService.this::startProxyOperationImpl);
@@ -6500,27 +2414,28 @@
                     AppOpsService.this::finishOperationImpl);
         }
 
-        public void finishProxyOperation(int code,
+        public void finishProxyOperation(IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
-                    mPolicy.finishProxyOperation(code, attributionSource,
+                    mPolicy.finishProxyOperation(clientId, code, attributionSource,
                             skipProxyOperation, this::finishDelegateProxyOperationImpl);
                 } else {
-                    mPolicy.finishProxyOperation(code, attributionSource,
+                    mPolicy.finishProxyOperation(clientId, code, attributionSource,
                             skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
                 }
             } else if (mCheckOpsDelegate != null) {
-                finishDelegateProxyOperationImpl(code, attributionSource, skipProxyOperation);
+                finishDelegateProxyOperationImpl(clientId, code, attributionSource,
+                        skipProxyOperation);
             } else {
-                finishProxyOperationImpl(code, attributionSource, skipProxyOperation);
+                finishProxyOperationImpl(clientId, code, attributionSource, skipProxyOperation);
             }
         }
 
-        private Void finishDelegateProxyOperationImpl(int code,
+        private Void finishDelegateProxyOperationImpl(IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
-            mCheckOpsDelegate.finishProxyOperation(code, attributionSource, skipProxyOperation,
-                    AppOpsService.this::finishProxyOperationImpl);
+            mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource,
+                    skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
             return null;
         }
     }
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
new file mode 100644
index 0000000..70f3bcc
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
@@ -0,0 +1,4679 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appop;
+
+import static android.app.AppOpsManager.AttributedOpEntry;
+import static android.app.AppOpsManager.AttributionFlags;
+import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
+import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
+import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
+import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
+import static android.app.AppOpsManager.HistoricalOps;
+import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
+import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.Mode;
+import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_FLAGS_ALL;
+import static android.app.AppOpsManager.OP_FLAG_SELF;
+import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.app.AppOpsManager.OP_PLAY_AUDIO;
+import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_VIBRATE;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
+import static android.app.AppOpsManager.OpEntry;
+import static android.app.AppOpsManager.OpEventProxyInfo;
+import static android.app.AppOpsManager.OpFlags;
+import static android.app.AppOpsManager.RestrictionBypass;
+import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
+import static android.app.AppOpsManager._NUM_OP;
+import static android.app.AppOpsManager.extractFlagsFromKey;
+import static android.app.AppOpsManager.extractUidStateFromKey;
+import static android.app.AppOpsManager.modeToName;
+import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
+import static android.app.AppOpsManager.opRestrictsRead;
+import static android.app.AppOpsManager.opToName;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_REPLACING;
+
+import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.PackageTagsList;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.storage.StorageManagerInternal;
+import android.permission.PermissionManager;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsNotedCallback;
+import com.android.internal.app.IAppOpsStartedCallback;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.os.Clock;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.LocalServices;
+import com.android.server.LockGuard;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedAttribution;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import libcore.util.EmptyArray;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+class AppOpsServiceImpl implements AppOpsServiceInterface {
+    static final String TAG = "AppOps";
+    static final boolean DEBUG = false;
+
+    private static final int NO_VERSION = -1;
+    /**
+     * Increment by one every time and add the corresponding upgrade logic in
+     * {@link #upgradeLocked(int)} below. The first version was 1
+     */
+    private static final int CURRENT_VERSION = 1;
+
+    // Write at most every 30 minutes.
+    static final long WRITE_DELAY = DEBUG ? 1000 : 30 * 60 * 1000;
+
+    // Constant meaning that any UID should be matched when dispatching callbacks
+    private static final int UID_ANY = -2;
+
+    private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
+            OP_PLAY_AUDIO,
+            OP_RECORD_AUDIO,
+            OP_CAMERA,
+            OP_VIBRATE,
+    };
+    private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
+
+    final Context mContext;
+    final AtomicFile mFile;
+    final Handler mHandler;
+
+    /**
+     * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
+     * objects
+     */
+    @GuardedBy("this")
+    final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
+            new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
+
+    /**
+     * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
+     * new objects
+     */
+    @GuardedBy("this")
+    final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
+            new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
+                    MAX_UNUSED_POOLED_OBJECTS);
+    @Nullable
+    private final DevicePolicyManagerInternal dpmi =
+            LocalServices.getService(DevicePolicyManagerInternal.class);
+
+    private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+
+    boolean mWriteScheduled;
+    boolean mFastWriteScheduled;
+    final Runnable mWriteRunner = new Runnable() {
+        public void run() {
+            synchronized (AppOpsServiceImpl.this) {
+                mWriteScheduled = false;
+                mFastWriteScheduled = false;
+                AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+                    @Override
+                    protected Void doInBackground(Void... params) {
+                        writeState();
+                        return null;
+                    }
+                };
+                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+            }
+        }
+    };
+
+    @GuardedBy("this")
+    @VisibleForTesting
+    final SparseArray<UidState> mUidStates = new SparseArray<>();
+
+    volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
+
+    /*
+     * These are app op restrictions imposed per user from various parties.
+     */
+    private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
+            new ArrayMap<>();
+
+    /*
+     * These are app op restrictions imposed globally from various parties within the system.
+     */
+    private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
+            new ArrayMap<>();
+
+    SparseIntArray mProfileOwners;
+
+    /**
+     * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
+     * changed
+     */
+    private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
+
+    /**
+     * Package Manager internal. Access via {@link #getPackageManagerInternal()}
+     */
+    private @Nullable PackageManagerInternal mPackageManagerInternal;
+
+    /**
+     * Interface for app-op modes.
+     */
+    @VisibleForTesting
+    AppOpsCheckingServiceInterface mAppOpsServiceInterface;
+
+    /**
+     * Interface for app-op restrictions.
+     */
+    @VisibleForTesting
+    AppOpsRestrictions mAppOpsRestrictions;
+
+    private AppOpsUidStateTracker mUidStateTracker;
+
+    /**
+     * Hands the definition of foreground and uid states
+     */
+    @GuardedBy("this")
+    public AppOpsUidStateTracker getUidStateTracker() {
+        if (mUidStateTracker == null) {
+            mUidStateTracker = new AppOpsUidStateTrackerImpl(
+                    LocalServices.getService(ActivityManagerInternal.class),
+                    mHandler,
+                    r -> {
+                        synchronized (AppOpsServiceImpl.this) {
+                            r.run();
+                        }
+                    },
+                    Clock.SYSTEM_CLOCK, mConstants);
+
+            mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
+                    this::onUidStateChanged);
+        }
+        return mUidStateTracker;
+    }
+
+    /**
+     * All times are in milliseconds. These constants are kept synchronized with the system
+     * global Settings. Any access to this class or its fields should be done while
+     * holding the AppOpsService lock.
+     */
+    final class Constants extends ContentObserver {
+
+        /**
+         * How long we want for a drop in uid state from top to settle before applying it.
+         *
+         * @see Settings.Global#APP_OPS_CONSTANTS
+         * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
+         */
+        public long TOP_STATE_SETTLE_TIME;
+
+        /**
+         * How long we want for a drop in uid state from foreground to settle before applying it.
+         *
+         * @see Settings.Global#APP_OPS_CONSTANTS
+         * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
+         */
+        public long FG_SERVICE_STATE_SETTLE_TIME;
+
+        /**
+         * How long we want for a drop in uid state from background to settle before applying it.
+         *
+         * @see Settings.Global#APP_OPS_CONSTANTS
+         * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
+         */
+        public long BG_STATE_SETTLE_TIME;
+
+        private final KeyValueListParser mParser = new KeyValueListParser(',');
+        private ContentResolver mResolver;
+
+        Constants(Handler handler) {
+            super(handler);
+            updateConstants();
+        }
+
+        public void startMonitoring(ContentResolver resolver) {
+            mResolver = resolver;
+            mResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
+                    false, this);
+            updateConstants();
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            updateConstants();
+        }
+
+        private void updateConstants() {
+            String value = mResolver != null ? Settings.Global.getString(mResolver,
+                    Settings.Global.APP_OPS_CONSTANTS) : "";
+
+            synchronized (AppOpsServiceImpl.this) {
+                try {
+                    mParser.setString(value);
+                } catch (IllegalArgumentException e) {
+                    // Failed to parse the settings string, log this and move on
+                    // with defaults.
+                    Slog.e(TAG, "Bad app ops settings", e);
+                }
+                TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
+                        KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
+                FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
+                        KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
+                BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
+                        KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
+            }
+        }
+
+        void dump(PrintWriter pw) {
+            pw.println("  Settings:");
+
+            pw.print("    ");
+            pw.print(KEY_TOP_STATE_SETTLE_TIME);
+            pw.print("=");
+            TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
+            pw.println();
+            pw.print("    ");
+            pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME);
+            pw.print("=");
+            TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
+            pw.println();
+            pw.print("    ");
+            pw.print(KEY_BG_STATE_SETTLE_TIME);
+            pw.print("=");
+            TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
+            pw.println();
+        }
+    }
+
+    @VisibleForTesting
+    final Constants mConstants;
+
+    @VisibleForTesting
+    final class UidState {
+        public final int uid;
+
+        public ArrayMap<String, Ops> pkgOps;
+
+        // true indicates there is an interested observer, false there isn't but it has such an op
+        //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
+        public SparseBooleanArray foregroundOps;
+        public boolean hasForegroundWatchers;
+
+        public UidState(int uid) {
+            this.uid = uid;
+        }
+
+        public void clear() {
+            mAppOpsServiceInterface.removeUid(uid);
+            if (pkgOps != null) {
+                for (String packageName : pkgOps.keySet()) {
+                    mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+                }
+            }
+            pkgOps = null;
+        }
+
+        public boolean isDefault() {
+            boolean areAllPackageModesDefault = true;
+            if (pkgOps != null) {
+                for (String packageName : pkgOps.keySet()) {
+                    if (!mAppOpsServiceInterface.arePackageModesDefault(packageName,
+                            UserHandle.getUserId(uid))) {
+                        areAllPackageModesDefault = false;
+                        break;
+                    }
+                }
+            }
+            return (pkgOps == null || pkgOps.isEmpty())
+                    && mAppOpsServiceInterface.areUidModesDefault(uid)
+                    && areAllPackageModesDefault;
+        }
+
+        // Functions for uid mode access and manipulation.
+        public SparseIntArray getNonDefaultUidModes() {
+            return mAppOpsServiceInterface.getNonDefaultUidModes(uid);
+        }
+
+        public int getUidMode(int op) {
+            return mAppOpsServiceInterface.getUidMode(uid, op);
+        }
+
+        public boolean setUidMode(int op, int mode) {
+            return mAppOpsServiceInterface.setUidMode(uid, op, mode);
+        }
+
+        @SuppressWarnings("GuardedBy")
+        int evalMode(int op, int mode) {
+            return getUidStateTracker().evalMode(uid, op, mode);
+        }
+
+        public void evalForegroundOps() {
+            foregroundOps = null;
+            foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
+            if (pkgOps != null) {
+                for (int i = pkgOps.size() - 1; i >= 0; i--) {
+                    foregroundOps = mAppOpsServiceInterface
+                            .evalForegroundPackageOps(pkgOps.valueAt(i).packageName,
+                                    foregroundOps,
+                                    UserHandle.getUserId(uid));
+                }
+            }
+            hasForegroundWatchers = false;
+            if (foregroundOps != null) {
+                for (int i = 0; i < foregroundOps.size(); i++) {
+                    if (foregroundOps.valueAt(i)) {
+                        hasForegroundWatchers = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        @SuppressWarnings("GuardedBy")
+        public int getState() {
+            return getUidStateTracker().getUidState(uid);
+        }
+
+        @SuppressWarnings("GuardedBy")
+        public void dump(PrintWriter pw, long nowElapsed) {
+            getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
+        }
+    }
+
+    static final class Ops extends SparseArray<Op> {
+        final String packageName;
+        final UidState uidState;
+
+        /**
+         * The restriction properties of the package. If {@code null} it could not have been read
+         * yet and has to be refreshed.
+         */
+        @Nullable RestrictionBypass bypass;
+
+        /** Lazily populated cache of attributionTags of this package */
+        final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
+
+        /**
+         * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
+         * than or equal to {@link #knownAttributionTags}.
+         */
+        final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
+
+        Ops(String _packageName, UidState _uidState) {
+            packageName = _packageName;
+            uidState = _uidState;
+        }
+    }
+
+    /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */
+    private static final class PackageVerificationResult {
+
+        final RestrictionBypass bypass;
+        final boolean isAttributionTagValid;
+
+        PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
+            this.bypass = bypass;
+            this.isAttributionTagValid = isAttributionTagValid;
+        }
+    }
+
+    final class Op {
+        int op;
+        int uid;
+        final UidState uidState;
+        final @NonNull String packageName;
+
+        /** attributionTag -> AttributedOp */
+        final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
+
+        Op(UidState uidState, String packageName, int op, int uid) {
+            this.op = op;
+            this.uid = uid;
+            this.uidState = uidState;
+            this.packageName = packageName;
+        }
+
+        @Mode int getMode() {
+            return mAppOpsServiceInterface.getPackageMode(packageName, this.op,
+                    UserHandle.getUserId(this.uid));
+        }
+
+        void setMode(@Mode int mode) {
+            mAppOpsServiceInterface.setPackageMode(packageName, this.op, mode,
+                    UserHandle.getUserId(this.uid));
+        }
+
+        void removeAttributionsWithNoTime() {
+            for (int i = mAttributions.size() - 1; i >= 0; i--) {
+                if (!mAttributions.valueAt(i).hasAnyTime()) {
+                    mAttributions.removeAt(i);
+                }
+            }
+        }
+
+        private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
+                @Nullable String attributionTag) {
+            AttributedOp attributedOp;
+
+            attributedOp = mAttributions.get(attributionTag);
+            if (attributedOp == null) {
+                attributedOp = new AttributedOp(AppOpsServiceImpl.this, attributionTag,
+                        parent);
+                mAttributions.put(attributionTag, attributedOp);
+            }
+
+            return attributedOp;
+        }
+
+        @NonNull
+        OpEntry createEntryLocked() {
+            final int numAttributions = mAttributions.size();
+
+            final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
+                    new ArrayMap<>(numAttributions);
+            for (int i = 0; i < numAttributions; i++) {
+                attributionEntries.put(mAttributions.keyAt(i),
+                        mAttributions.valueAt(i).createAttributedOpEntryLocked());
+            }
+
+            return new OpEntry(op, getMode(), attributionEntries);
+        }
+
+        @NonNull
+        OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
+            final int numAttributions = mAttributions.size();
+
+            final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
+            for (int i = 0; i < numAttributions; i++) {
+                if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
+                    attributionEntries.put(mAttributions.keyAt(i),
+                            mAttributions.valueAt(i).createAttributedOpEntryLocked());
+                    break;
+                }
+            }
+
+            return new OpEntry(op, getMode(), attributionEntries);
+        }
+
+        boolean isRunning() {
+            final int numAttributions = mAttributions.size();
+            for (int i = 0; i < numAttributions; i++) {
+                if (mAttributions.valueAt(i).isRunning()) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+
+    final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
+    final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
+    final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
+    final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
+
+    final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient  {
+        /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
+        public static final int ALL_OPS = -2;
+
+        // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
+        // Otherwise we can just use the IBinder object.
+        private final IAppOpsCallback mCallback;
+
+        ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
+                int callingUid, int callingPid) {
+            super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
+            this.mCallback = callback;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("ModeCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, getWatchingUid());
+            sb.append(" flags=0x");
+            sb.append(Integer.toHexString(getFlags()));
+            switch (getWatchedOpCode()) {
+                case OP_NONE:
+                    break;
+                case ALL_OPS:
+                    sb.append(" op=(all)");
+                    break;
+                default:
+                    sb.append(" op=");
+                    sb.append(opToName(getWatchedOpCode()));
+                    break;
+            }
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, getCallingUid());
+            sb.append(" pid=");
+            sb.append(getCallingPid());
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void unlinkToDeath() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingMode(mCallback);
+        }
+
+        @Override
+        public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
+            mCallback.opChanged(op, uid, packageName);
+        }
+    }
+
+    final class ActiveCallback implements DeathRecipient {
+        final IAppOpsActiveCallback mCallback;
+        final int mWatchingUid;
+        final int mCallingUid;
+        final int mCallingPid;
+
+        ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
+                int callingPid) {
+            mCallback = callback;
+            mWatchingUid = watchingUid;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("ActiveCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, mWatchingUid);
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, mCallingUid);
+            sb.append(" pid=");
+            sb.append(mCallingPid);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void destroy() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingActive(mCallback);
+        }
+    }
+
+    final class StartedCallback implements DeathRecipient {
+        final IAppOpsStartedCallback mCallback;
+        final int mWatchingUid;
+        final int mCallingUid;
+        final int mCallingPid;
+
+        StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
+                int callingPid) {
+            mCallback = callback;
+            mWatchingUid = watchingUid;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("StartedCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, mWatchingUid);
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, mCallingUid);
+            sb.append(" pid=");
+            sb.append(mCallingPid);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void destroy() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingStarted(mCallback);
+        }
+    }
+
+    final class NotedCallback implements DeathRecipient {
+        final IAppOpsNotedCallback mCallback;
+        final int mWatchingUid;
+        final int mCallingUid;
+        final int mCallingPid;
+
+        NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
+                int callingPid) {
+            mCallback = callback;
+            mWatchingUid = watchingUid;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("NotedCallback{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" watchinguid=");
+            UserHandle.formatUid(sb, mWatchingUid);
+            sb.append(" from uid=");
+            UserHandle.formatUid(sb, mCallingUid);
+            sb.append(" pid=");
+            sb.append(mCallingPid);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        void destroy() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingNoted(mCallback);
+        }
+    }
+
+    /**
+     * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
+     */
+    static void onClientDeath(@NonNull AttributedOp attributedOp,
+            @NonNull IBinder clientId) {
+        attributedOp.onClientDeath(clientId);
+    }
+
+    AppOpsServiceImpl(File storagePath, Handler handler, Context context) {
+        mContext = context;
+
+        for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
+            int switchCode = AppOpsManager.opToSwitch(switchedCode);
+            mSwitchedOps.put(switchCode,
+                    ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
+        }
+        mAppOpsServiceInterface =
+                new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps);
+        mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
+                mAppOpsServiceInterface);
+
+        LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
+        mFile = new AtomicFile(storagePath, "appops");
+
+        mHandler = handler;
+        mConstants = new Constants(mHandler);
+        readState();
+    }
+
+    /**
+     * Handler for work when packages are removed or updated
+     */
+    private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+            int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
+
+            if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
+                synchronized (AppOpsServiceImpl.this) {
+                    UidState uidState = mUidStates.get(uid);
+                    if (uidState == null || uidState.pkgOps == null) {
+                        return;
+                    }
+                    mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid));
+                    Ops removedOps = uidState.pkgOps.remove(pkgName);
+                    if (removedOps != null) {
+                        scheduleFastWriteLocked();
+                    }
+                }
+            } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
+                AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
+                if (pkg == null) {
+                    return;
+                }
+
+                ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
+                ArraySet<String> attributionTags = new ArraySet<>();
+                attributionTags.add(null);
+                if (pkg.getAttributions() != null) {
+                    int numAttributions = pkg.getAttributions().size();
+                    for (int attributionNum = 0; attributionNum < numAttributions;
+                            attributionNum++) {
+                        ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
+                        attributionTags.add(attribution.getTag());
+
+                        int numInheritFrom = attribution.getInheritFrom().size();
+                        for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
+                                inheritFromNum++) {
+                            dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
+                                    attribution.getTag());
+                        }
+                    }
+                }
+
+                synchronized (AppOpsServiceImpl.this) {
+                    UidState uidState = mUidStates.get(uid);
+                    if (uidState == null || uidState.pkgOps == null) {
+                        return;
+                    }
+
+                    Ops ops = uidState.pkgOps.get(pkgName);
+                    if (ops == null) {
+                        return;
+                    }
+
+                    // Reset cached package properties to re-initialize when needed
+                    ops.bypass = null;
+                    ops.knownAttributionTags.clear();
+
+                    // Merge data collected for removed attributions into their successor
+                    // attributions
+                    int numOps = ops.size();
+                    for (int opNum = 0; opNum < numOps; opNum++) {
+                        Op op = ops.valueAt(opNum);
+
+                        int numAttributions = op.mAttributions.size();
+                        for (int attributionNum = numAttributions - 1; attributionNum >= 0;
+                                attributionNum--) {
+                            String attributionTag = op.mAttributions.keyAt(attributionNum);
+
+                            if (attributionTags.contains(attributionTag)) {
+                                // attribution still exist after upgrade
+                                continue;
+                            }
+
+                            String newAttributionTag = dstAttributionTags.get(attributionTag);
+
+                            AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
+                                    newAttributionTag);
+                            newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
+                            op.mAttributions.removeAt(attributionNum);
+
+                            scheduleFastWriteLocked();
+                        }
+                    }
+                }
+            }
+        }
+    };
+
+    @Override
+    public void systemReady() {
+        mConstants.startMonitoring(mContext.getContentResolver());
+        mHistoricalRegistry.systemReady(mContext.getContentResolver());
+
+        IntentFilter packageUpdateFilter = new IntentFilter();
+        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        packageUpdateFilter.addDataScheme("package");
+
+        mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
+                packageUpdateFilter, null, null);
+
+        synchronized (this) {
+            for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
+                int uid = mUidStates.keyAt(uidNum);
+                UidState uidState = mUidStates.valueAt(uidNum);
+
+                String[] pkgsInUid = getPackagesForUid(uidState.uid);
+                if (ArrayUtils.isEmpty(pkgsInUid)) {
+                    uidState.clear();
+                    mUidStates.removeAt(uidNum);
+                    scheduleFastWriteLocked();
+                    continue;
+                }
+
+                ArrayMap<String, Ops> pkgs = uidState.pkgOps;
+                if (pkgs == null) {
+                    continue;
+                }
+
+                int numPkgs = pkgs.size();
+                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+                    String pkg = pkgs.keyAt(pkgNum);
+
+                    String action;
+                    if (!ArrayUtils.contains(pkgsInUid, pkg)) {
+                        action = Intent.ACTION_PACKAGE_REMOVED;
+                    } else {
+                        action = Intent.ACTION_PACKAGE_REPLACED;
+                    }
+
+                    SystemServerInitThreadPool.submit(
+                            () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
+                                    .setData(Uri.fromParts("package", pkg, null))
+                                    .putExtra(Intent.EXTRA_UID, uid)),
+                            "Update app-ops uidState in case package " + pkg + " changed");
+                }
+            }
+        }
+
+        final IntentFilter packageSuspendFilter = new IntentFilter();
+        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+                final String[] changedPkgs = intent.getStringArrayExtra(
+                        Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                for (int code : OPS_RESTRICTED_ON_SUSPEND) {
+                    ArraySet<OnOpModeChangedListener> onModeChangedListeners;
+                    synchronized (AppOpsServiceImpl.this) {
+                        onModeChangedListeners =
+                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
+                        if (onModeChangedListeners == null) {
+                            continue;
+                        }
+                    }
+                    for (int i = 0; i < changedUids.length; i++) {
+                        final int changedUid = changedUids[i];
+                        final String changedPkg = changedPkgs[i];
+                        // We trust packagemanager to insert matching uid and packageNames in the
+                        // extras
+                        notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
+                    }
+                }
+            }
+        }, UserHandle.ALL, packageSuspendFilter, null, null);
+    }
+
+    @Override
+    public void packageRemoved(int uid, String packageName) {
+        synchronized (this) {
+            UidState uidState = mUidStates.get(uid);
+            if (uidState == null) {
+                return;
+            }
+
+            Ops removedOps = null;
+
+            // Remove any package state if such.
+            if (uidState.pkgOps != null) {
+                removedOps = uidState.pkgOps.remove(packageName);
+                mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+            }
+
+            // If we just nuked the last package state check if the UID is valid.
+            if (removedOps != null && uidState.pkgOps.isEmpty()
+                    && getPackagesForUid(uid).length <= 0) {
+                uidState.clear();
+                mUidStates.remove(uid);
+            }
+
+            if (removedOps != null) {
+                scheduleFastWriteLocked();
+
+                final int numOps = removedOps.size();
+                for (int opNum = 0; opNum < numOps; opNum++) {
+                    final Op op = removedOps.valueAt(opNum);
+
+                    final int numAttributions = op.mAttributions.size();
+                    for (int attributionNum = 0; attributionNum < numAttributions;
+                            attributionNum++) {
+                        AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
+
+                        while (attributedOp.isRunning()) {
+                            attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
+                        }
+                        while (attributedOp.isPaused()) {
+                            attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
+                        }
+                    }
+                }
+            }
+        }
+
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
+                mHistoricalRegistry, uid, packageName));
+    }
+
+    @Override
+    public void uidRemoved(int uid) {
+        synchronized (this) {
+            if (mUidStates.indexOfKey(uid) >= 0) {
+                mUidStates.get(uid).clear();
+                mUidStates.remove(uid);
+                scheduleFastWriteLocked();
+            }
+        }
+    }
+
+    // The callback method from ForegroundPolicyInterface
+    private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
+        synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, true);
+
+            if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
+                for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
+                    if (!uidState.foregroundOps.valueAt(fgi)) {
+                        continue;
+                    }
+                    final int code = uidState.foregroundOps.keyAt(fgi);
+
+                    if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
+                            && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
+                        mHandler.sendMessage(PooledLambda.obtainMessage(
+                                AppOpsServiceImpl::notifyOpChangedForAllPkgsInUid,
+                                this, code, uidState.uid, true, null));
+                    } else if (uidState.pkgOps != null) {
+                        final ArraySet<OnOpModeChangedListener> listenerSet =
+                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
+                        if (listenerSet != null) {
+                            for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
+                                final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
+                                if ((listener.getFlags()
+                                        & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
+                                        || !listener.isWatchingUid(uidState.uid)) {
+                                    continue;
+                                }
+                                for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
+                                    final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
+                                    if (op == null) {
+                                        continue;
+                                    }
+                                    if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
+                                        mHandler.sendMessage(PooledLambda.obtainMessage(
+                                                AppOpsServiceImpl::notifyOpChanged,
+                                                this, listenerSet.valueAt(cbi), code, uidState.uid,
+                                                uidState.pkgOps.keyAt(pkgi)));
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (uidState != null && uidState.pkgOps != null) {
+                int numPkgs = uidState.pkgOps.size();
+                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+                    Ops ops = uidState.pkgOps.valueAt(pkgNum);
+
+                    int numOps = ops.size();
+                    for (int opNum = 0; opNum < numOps; opNum++) {
+                        Op op = ops.valueAt(opNum);
+
+                        int numAttributions = op.mAttributions.size();
+                        for (int attributionNum = 0; attributionNum < numAttributions;
+                                attributionNum++) {
+                            AttributedOp attributedOp = op.mAttributions.valueAt(
+                                    attributionNum);
+
+                            attributedOp.onUidStateChanged(state);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Notify the proc state or capability has changed for a certain UID.
+     */
+    @Override
+    public void updateUidProcState(int uid, int procState,
+            @ActivityManager.ProcessCapability int capability) {
+        synchronized (this) {
+            getUidStateTracker().updateUidProcState(uid, procState, capability);
+            if (!mUidStates.contains(uid)) {
+                UidState uidState = new UidState(uid);
+                mUidStates.put(uid, uidState);
+                onUidStateChanged(uid,
+                        AppOpsUidStateTracker.processStateToUidState(procState), false);
+            }
+        }
+    }
+
+    @Override
+    public void shutdown() {
+        Slog.w(TAG, "Writing app ops before shutdown...");
+        boolean doWrite = false;
+        synchronized (this) {
+            if (mWriteScheduled) {
+                mWriteScheduled = false;
+                mFastWriteScheduled = false;
+                mHandler.removeCallbacks(mWriteRunner);
+                doWrite = true;
+            }
+        }
+        if (doWrite) {
+            writeState();
+        }
+
+        mHistoricalRegistry.shutdown();
+    }
+
+    private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
+        ArrayList<AppOpsManager.OpEntry> resOps = null;
+        if (ops == null) {
+            resOps = new ArrayList<>();
+            for (int j = 0; j < pkgOps.size(); j++) {
+                Op curOp = pkgOps.valueAt(j);
+                resOps.add(getOpEntryForResult(curOp));
+            }
+        } else {
+            for (int j = 0; j < ops.length; j++) {
+                Op curOp = pkgOps.get(ops[j]);
+                if (curOp != null) {
+                    if (resOps == null) {
+                        resOps = new ArrayList<>();
+                    }
+                    resOps.add(getOpEntryForResult(curOp));
+                }
+            }
+        }
+        return resOps;
+    }
+
+    @Nullable
+    private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
+            @Nullable int[] ops) {
+        final SparseIntArray opModes = uidState.getNonDefaultUidModes();
+        if (opModes == null) {
+            return null;
+        }
+
+        int opModeCount = opModes.size();
+        if (opModeCount == 0) {
+            return null;
+        }
+        ArrayList<AppOpsManager.OpEntry> resOps = null;
+        if (ops == null) {
+            resOps = new ArrayList<>();
+            for (int i = 0; i < opModeCount; i++) {
+                int code = opModes.keyAt(i);
+                resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
+            }
+        } else {
+            for (int j = 0; j < ops.length; j++) {
+                int code = ops[j];
+                if (opModes.indexOfKey(code) >= 0) {
+                    if (resOps == null) {
+                        resOps = new ArrayList<>();
+                    }
+                    resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
+                }
+            }
+        }
+        return resOps;
+    }
+
+    private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
+        return op.createEntryLocked();
+    }
+
+    @Override
+    public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
+        final int callingUid = Binder.getCallingUid();
+        final boolean hasAllPackageAccess = mContext.checkPermission(
+                Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
+                Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
+        ArrayList<AppOpsManager.PackageOps> res = null;
+        synchronized (this) {
+            final int uidStateCount = mUidStates.size();
+            for (int i = 0; i < uidStateCount; i++) {
+                UidState uidState = mUidStates.valueAt(i);
+                if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
+                    continue;
+                }
+                ArrayMap<String, Ops> packages = uidState.pkgOps;
+                final int packageCount = packages.size();
+                for (int j = 0; j < packageCount; j++) {
+                    Ops pkgOps = packages.valueAt(j);
+                    ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+                    if (resOps != null) {
+                        if (res == null) {
+                            res = new ArrayList<>();
+                        }
+                        AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+                                pkgOps.packageName, pkgOps.uidState.uid, resOps);
+                        // Caller can always see their packages and with a permission all.
+                        if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
+                            res.add(resPackage);
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+
+    @Override
+    public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
+            int[] ops) {
+        enforceGetAppOpsStatsPermissionIfNeeded(uid, packageName);
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return Collections.emptyList();
+        }
+        synchronized (this) {
+            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
+                    /* edit */ false);
+            if (pkgOps == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+            if (resOps == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<>();
+            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+                    pkgOps.packageName, pkgOps.uidState.uid, resOps);
+            res.add(resPackage);
+            return res;
+        }
+    }
+
+    private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
+        final int callingUid = Binder.getCallingUid();
+        // We get to access everything
+        if (callingUid == Process.myPid()) {
+            return;
+        }
+        // Apps can access their own data
+        if (uid == callingUid && packageName != null
+                && checkPackage(uid, packageName) == MODE_ALLOWED) {
+            return;
+        }
+        // Otherwise, you need a permission...
+        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                Binder.getCallingPid(), callingUid, null);
+    }
+
+    /**
+     * Verify that historical appop request arguments are valid.
+     */
+    private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
+            String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags) {
+        if ((filter & FILTER_BY_UID) != 0) {
+            Preconditions.checkArgument(uid != Process.INVALID_UID);
+        } else {
+            Preconditions.checkArgument(uid == Process.INVALID_UID);
+        }
+
+        if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
+            Objects.requireNonNull(packageName);
+        } else {
+            Preconditions.checkArgument(packageName == null);
+        }
+
+        if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
+            Preconditions.checkArgument(attributionTag == null);
+        }
+
+        if ((filter & FILTER_BY_OP_NAMES) != 0) {
+            Objects.requireNonNull(opNames);
+        } else {
+            Preconditions.checkArgument(opNames == null);
+        }
+
+        Preconditions.checkFlagsArgument(filter,
+                FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
+                        | FILTER_BY_OP_NAMES);
+        Preconditions.checkArgumentNonnegative(beginTimeMillis);
+        Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
+        Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
+    }
+
+    @Override
+    public void getHistoricalOps(int uid, String packageName, String attributionTag,
+            List<String> opNames, int dataType, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, RemoteCallback callback) {
+        PackageManager pm = mContext.getPackageManager();
+
+        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
+                beginTimeMillis, endTimeMillis, flags);
+        Objects.requireNonNull(callback, "callback cannot be null");
+        ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
+        boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
+        if (!isSelfRequest) {
+            boolean isCallerInstrumented =
+                    ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
+            boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
+            boolean isCallerPermissionController;
+            try {
+                isCallerPermissionController = pm.getPackageUidAsUser(
+                        mContext.getPackageManager().getPermissionControllerPackageName(), 0,
+                        UserHandle.getUserId(Binder.getCallingUid()))
+                        == Binder.getCallingUid();
+            } catch (PackageManager.NameNotFoundException doesNotHappen) {
+                return;
+            }
+
+            boolean doesCallerHavePermission = mContext.checkPermission(
+                    android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
+                    Binder.getCallingPid(), Binder.getCallingUid())
+                    == PackageManager.PERMISSION_GRANTED;
+
+            if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
+                    && !doesCallerHavePermission) {
+                mHandler.post(() -> callback.sendResult(new Bundle()));
+                return;
+            }
+
+            mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                    Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+        }
+
+        final String[] opNamesArray = (opNames != null)
+                ? opNames.toArray(new String[opNames.size()]) : null;
+
+        Set<String> attributionChainExemptPackages = null;
+        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
+            attributionChainExemptPackages =
+                    PermissionManager.getIndicatorExemptedPackages(mContext);
+        }
+
+        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
+                ? attributionChainExemptPackages.toArray(
+                new String[attributionChainExemptPackages.size()]) : null;
+
+        // Must not hold the appops lock
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
+                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
+                callback).recycleOnUse());
+    }
+
+    @Override
+    public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
+            List<String> opNames, int dataType, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, RemoteCallback callback) {
+        ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
+                beginTimeMillis, endTimeMillis, flags);
+        Objects.requireNonNull(callback, "callback cannot be null");
+
+        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+                Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+
+        final String[] opNamesArray = (opNames != null)
+                ? opNames.toArray(new String[opNames.size()]) : null;
+
+        Set<String> attributionChainExemptPackages = null;
+        if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
+            attributionChainExemptPackages =
+                    PermissionManager.getIndicatorExemptedPackages(mContext);
+        }
+
+        final String[] chainExemptPkgArray = attributionChainExemptPackages != null
+                ? attributionChainExemptPackages.toArray(
+                new String[attributionChainExemptPackages.size()]) : null;
+
+        // Must not hold the appops lock
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
+                mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+                filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
+                callback).recycleOnUse());
+    }
+
+    @Override
+    public void reloadNonHistoricalState() {
+        mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+                Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
+        writeState();
+        readState();
+    }
+
+    @Override
+    public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
+        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+        synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
+            if (resOps == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+                    null, uidState.uid, resOps);
+            res.add(resPackage);
+            return res;
+        }
+    }
+
+    private void pruneOpLocked(Op op, int uid, String packageName) {
+        op.removeAttributionsWithNoTime();
+
+        if (op.mAttributions.isEmpty()) {
+            Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
+            if (ops != null) {
+                ops.remove(op.op);
+                op.setMode(AppOpsManager.opToDefaultMode(op.op));
+                if (ops.size() <= 0) {
+                    UidState uidState = ops.uidState;
+                    ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+                    if (pkgOps != null) {
+                        pkgOps.remove(ops.packageName);
+                        mAppOpsServiceInterface.removePackage(ops.packageName,
+                                UserHandle.getUserId(uidState.uid));
+                        if (pkgOps.isEmpty()) {
+                            uidState.pkgOps = null;
+                        }
+                        if (uidState.isDefault()) {
+                            uidState.clear();
+                            mUidStates.remove(uid);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
+        if (callingPid == Process.myPid()) {
+            return;
+        }
+        final int callingUser = UserHandle.getUserId(callingUid);
+        synchronized (this) {
+            if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
+                if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
+                    // Profile owners are allowed to change modes but only for apps
+                    // within their user.
+                    return;
+                }
+            }
+        }
+        mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+    }
+
+    @Override
+    public void setUidMode(int code, int uid, int mode,
+            @Nullable IAppOpsCallback permissionPolicyCallback) {
+        if (DEBUG) {
+            Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
+                    + " by uid " + Binder.getCallingUid());
+        }
+
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+        verifyIncomingOp(code);
+        code = AppOpsManager.opToSwitch(code);
+
+        if (permissionPolicyCallback == null) {
+            updatePermissionRevokedCompat(uid, code, mode);
+        }
+
+        int previousMode;
+        synchronized (this) {
+            final int defaultMode = AppOpsManager.opToDefaultMode(code);
+
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState == null) {
+                if (mode == defaultMode) {
+                    return;
+                }
+                uidState = new UidState(uid);
+                mUidStates.put(uid, uidState);
+            }
+            if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
+                previousMode = uidState.getUidMode(code);
+            } else {
+                // doesn't look right but is legacy behavior.
+                previousMode = MODE_DEFAULT;
+            }
+
+            if (!uidState.setUidMode(code, mode)) {
+                return;
+            }
+            uidState.evalForegroundOps();
+            if (mode != MODE_ERRORED && mode != previousMode) {
+                updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+            }
+        }
+
+        notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
+        notifyOpChangedSync(code, uid, null, mode, previousMode);
+    }
+
+    /**
+     * Notify that an op changed for all packages in an uid.
+     *
+     * @param code           The op that changed
+     * @param uid            The uid the op was changed for
+     * @param onlyForeground Only notify watchers that watch for foreground changes
+     */
+    private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
+            @Nullable IAppOpsCallback callbackToIgnore) {
+        ModeCallback listenerToIgnore = callbackToIgnore != null
+                ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
+        mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
+                listenerToIgnore);
+    }
+
+    private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager == null) {
+            // This can only happen during early boot. At this time the permission state and appop
+            // state are in sync
+            return;
+        }
+
+        String[] packageNames = packageManager.getPackagesForUid(uid);
+        if (ArrayUtils.isEmpty(packageNames)) {
+            return;
+        }
+        String packageName = packageNames[0];
+
+        int[] ops = mSwitchedOps.get(switchCode);
+        for (int code : ops) {
+            String permissionName = AppOpsManager.opToPermission(code);
+            if (permissionName == null) {
+                continue;
+            }
+
+            if (packageManager.checkPermission(permissionName, packageName)
+                    != PackageManager.PERMISSION_GRANTED) {
+                continue;
+            }
+
+            PermissionInfo permissionInfo;
+            try {
+                permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                e.printStackTrace();
+                continue;
+            }
+
+            if (!permissionInfo.isRuntime()) {
+                continue;
+            }
+
+            boolean supportsRuntimePermissions = getPackageManagerInternal()
+                    .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
+
+            UserHandle user = UserHandle.getUserHandleForUid(uid);
+            boolean isRevokedCompat;
+            if (permissionInfo.backgroundPermission != null) {
+                if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+
+                    if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
+                        Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+                                + " permission state, this is discouraged and you should revoke the"
+                                + " runtime permission instead: uid=" + uid + ", switchCode="
+                                + switchCode + ", mode=" + mode + ", permission="
+                                + permissionInfo.backgroundPermission);
+                    }
+
+                    final long identity = Binder.clearCallingIdentity();
+                    try {
+                        packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
+                                packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+                                isBackgroundRevokedCompat
+                                        ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
+                }
+
+                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
+                        && mode != AppOpsManager.MODE_FOREGROUND;
+            } else {
+                isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+            }
+
+            if (isRevokedCompat && supportsRuntimePermissions) {
+                Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+                        + " permission state, this is discouraged and you should revoke the"
+                        + " runtime permission instead: uid=" + uid + ", switchCode="
+                        + switchCode + ", mode=" + mode + ", permission=" + permissionName);
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                packageManager.updatePermissionFlags(permissionName, packageName,
+                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
+                                ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
+            int previousMode) {
+        final StorageManagerInternal storageManagerInternal =
+                LocalServices.getService(StorageManagerInternal.class);
+        if (storageManagerInternal != null) {
+            storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
+        }
+    }
+
+    @Override
+    public void setMode(int code, int uid, @NonNull String packageName, int mode,
+            @Nullable IAppOpsCallback permissionPolicyCallback) {
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return;
+        }
+
+        ArraySet<OnOpModeChangedListener> repCbs = null;
+        code = AppOpsManager.opToSwitch(code);
+
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, null);
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Cannot setMode", e);
+            return;
+        }
+
+        int previousMode = MODE_DEFAULT;
+        synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, false);
+            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
+            if (op != null) {
+                if (op.getMode() != mode) {
+                    previousMode = op.getMode();
+                    op.setMode(mode);
+
+                    if (uidState != null) {
+                        uidState.evalForegroundOps();
+                    }
+                    ArraySet<OnOpModeChangedListener> cbs =
+                            mAppOpsServiceInterface.getOpModeChangedListeners(code);
+                    if (cbs != null) {
+                        if (repCbs == null) {
+                            repCbs = new ArraySet<>();
+                        }
+                        repCbs.addAll(cbs);
+                    }
+                    cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName);
+                    if (cbs != null) {
+                        if (repCbs == null) {
+                            repCbs = new ArraySet<>();
+                        }
+                        repCbs.addAll(cbs);
+                    }
+                    if (repCbs != null && permissionPolicyCallback != null) {
+                        repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
+                    }
+                    if (mode == AppOpsManager.opToDefaultMode(op.op)) {
+                        // If going into the default mode, prune this op
+                        // if there is nothing else interesting in it.
+                        pruneOpLocked(op, uid, packageName);
+                    }
+                    scheduleFastWriteLocked();
+                    if (mode != MODE_ERRORED) {
+                        updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+                    }
+                }
+            }
+        }
+        if (repCbs != null) {
+            mHandler.sendMessage(PooledLambda.obtainMessage(
+                    AppOpsServiceImpl::notifyOpChanged,
+                    this, repCbs, code, uid, packageName));
+        }
+
+        notifyOpChangedSync(code, uid, packageName, mode, previousMode);
+    }
+
+    private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
+            int uid, String packageName) {
+        for (int i = 0; i < callbacks.size(); i++) {
+            final OnOpModeChangedListener callback = callbacks.valueAt(i);
+            notifyOpChanged(callback, code, uid, packageName);
+        }
+    }
+
+    private void notifyOpChanged(OnOpModeChangedListener callback, int code,
+            int uid, String packageName) {
+        mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName);
+    }
+
+    private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
+            int op, int uid, String packageName, int previousMode) {
+        boolean duplicate = false;
+        if (reports == null) {
+            reports = new ArrayList<>();
+        } else {
+            final int reportCount = reports.size();
+            for (int j = 0; j < reportCount; j++) {
+                ChangeRec report = reports.get(j);
+                if (report.op == op && report.pkg.equals(packageName)) {
+                    duplicate = true;
+                    break;
+                }
+            }
+        }
+        if (!duplicate) {
+            reports.add(new ChangeRec(op, uid, packageName, previousMode));
+        }
+
+        return reports;
+    }
+
+    private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
+            HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
+            int op, int uid, String packageName, int previousMode,
+            ArraySet<OnOpModeChangedListener> cbs) {
+        if (cbs == null) {
+            return callbacks;
+        }
+        if (callbacks == null) {
+            callbacks = new HashMap<>();
+        }
+        final int N = cbs.size();
+        for (int i=0; i<N; i++) {
+            OnOpModeChangedListener cb = cbs.valueAt(i);
+            ArrayList<ChangeRec> reports = callbacks.get(cb);
+            ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
+            if (changed != reports) {
+                callbacks.put(cb, changed);
+            }
+        }
+        return callbacks;
+    }
+
+    static final class ChangeRec {
+        final int op;
+        final int uid;
+        final String pkg;
+        final int previous_mode;
+
+        ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
+            op = _op;
+            uid = _uid;
+            pkg = _pkg;
+            previous_mode = _previous_mode;
+        }
+    }
+
+    @Override
+    public void resetAllModes(int reqUserId, String reqPackageName) {
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
+        reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
+                true, true, "resetAllModes", null);
+
+        int reqUid = -1;
+        if (reqPackageName != null) {
+            try {
+                reqUid = AppGlobals.getPackageManager().getPackageUid(
+                        reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
+            } catch (RemoteException e) {
+                /* ignore - local call */
+            }
+        }
+
+        enforceManageAppOpsModes(callingPid, callingUid, reqUid);
+
+        HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
+        ArrayList<ChangeRec> allChanges = new ArrayList<>();
+        synchronized (this) {
+            boolean changed = false;
+            for (int i = mUidStates.size() - 1; i >= 0; i--) {
+                UidState uidState = mUidStates.valueAt(i);
+
+                SparseIntArray opModes = uidState.getNonDefaultUidModes();
+                if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
+                    final int uidOpCount = opModes.size();
+                    for (int j = uidOpCount - 1; j >= 0; j--) {
+                        final int code = opModes.keyAt(j);
+                        if (AppOpsManager.opAllowsReset(code)) {
+                            int previousMode = opModes.valueAt(j);
+                            uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
+                            for (String packageName : getPackagesForUid(uidState.uid)) {
+                                callbacks = addCallbacks(callbacks, code, uidState.uid,
+                                        packageName, previousMode,
+                                        mAppOpsServiceInterface.getOpModeChangedListeners(code));
+                                callbacks = addCallbacks(callbacks, code, uidState.uid,
+                                        packageName, previousMode, mAppOpsServiceInterface
+                                                .getPackageModeChangedListeners(packageName));
+
+                                allChanges = addChange(allChanges, code, uidState.uid,
+                                        packageName, previousMode);
+                            }
+                        }
+                    }
+                }
+
+                if (uidState.pkgOps == null) {
+                    continue;
+                }
+
+                if (reqUserId != UserHandle.USER_ALL
+                        && reqUserId != UserHandle.getUserId(uidState.uid)) {
+                    // Skip any ops for a different user
+                    continue;
+                }
+
+                Map<String, Ops> packages = uidState.pkgOps;
+                Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
+                boolean uidChanged = false;
+                while (it.hasNext()) {
+                    Map.Entry<String, Ops> ent = it.next();
+                    String packageName = ent.getKey();
+                    if (reqPackageName != null && !reqPackageName.equals(packageName)) {
+                        // Skip any ops for a different package
+                        continue;
+                    }
+                    Ops pkgOps = ent.getValue();
+                    for (int j=pkgOps.size()-1; j>=0; j--) {
+                        Op curOp = pkgOps.valueAt(j);
+                        if (shouldDeferResetOpToDpm(curOp.op)) {
+                            deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
+                            continue;
+                        }
+                        if (AppOpsManager.opAllowsReset(curOp.op)
+                                && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
+                            int previousMode = curOp.getMode();
+                            curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
+                            changed = true;
+                            uidChanged = true;
+                            final int uid = curOp.uidState.uid;
+                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+                                    previousMode,
+                                    mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
+                            callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+                                    previousMode, mAppOpsServiceInterface
+                                            .getPackageModeChangedListeners(packageName));
+
+                            allChanges = addChange(allChanges, curOp.op, uid, packageName,
+                                    previousMode);
+                            curOp.removeAttributionsWithNoTime();
+                            if (curOp.mAttributions.isEmpty()) {
+                                pkgOps.removeAt(j);
+                            }
+                        }
+                    }
+                    if (pkgOps.size() == 0) {
+                        it.remove();
+                        mAppOpsServiceInterface.removePackage(packageName,
+                                UserHandle.getUserId(uidState.uid));
+                    }
+                }
+                if (uidState.isDefault()) {
+                    uidState.clear();
+                    mUidStates.remove(uidState.uid);
+                }
+                if (uidChanged) {
+                    uidState.evalForegroundOps();
+                }
+            }
+
+            if (changed) {
+                scheduleFastWriteLocked();
+            }
+        }
+        if (callbacks != null) {
+            for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
+                    : callbacks.entrySet()) {
+                OnOpModeChangedListener cb = ent.getKey();
+                ArrayList<ChangeRec> reports = ent.getValue();
+                for (int i=0; i<reports.size(); i++) {
+                    ChangeRec rep = reports.get(i);
+                    mHandler.sendMessage(PooledLambda.obtainMessage(
+                            AppOpsServiceImpl::notifyOpChanged,
+                            this, cb, rep.op, rep.uid, rep.pkg));
+                }
+            }
+        }
+
+        int numChanges = allChanges.size();
+        for (int i = 0; i < numChanges; i++) {
+            ChangeRec change = allChanges.get(i);
+            notifyOpChangedSync(change.op, change.uid, change.pkg,
+                    AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
+        }
+    }
+
+    private boolean shouldDeferResetOpToDpm(int op) {
+        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
+        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
+        return dpmi != null && dpmi.supportsResetOp(op);
+    }
+
+    /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
+    private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
+        // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
+        //  pre-grants to a role-based mechanism or another general-purpose mechanism.
+        dpmi.resetOp(op, packageName, userId);
+    }
+
+    private void evalAllForegroundOpsLocked() {
+        for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
+            final UidState uidState = mUidStates.valueAt(uidi);
+            if (uidState.foregroundOps != null) {
+                uidState.evalForegroundOps();
+            }
+        }
+    }
+
+    @Override
+    public void startWatchingModeWithFlags(int op, String packageName, int flags,
+            IAppOpsCallback callback) {
+        int watchedUid = -1;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        // TODO: should have a privileged permission to protect this.
+        // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
+        // the USAGE_STATS permission since this can provide information about when an
+        // app is in the foreground?
+        Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
+                AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
+        if (callback == null) {
+            return;
+        }
+        final boolean mayWatchPackageName = packageName != null
+                && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
+        synchronized (this) {
+            int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
+
+            int notifiedOps;
+            if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
+                if (op == OP_NONE) {
+                    notifiedOps = ALL_OPS;
+                } else {
+                    notifiedOps = op;
+                }
+            } else {
+                notifiedOps = switchOp;
+            }
+
+            ModeCallback cb = mModeWatchers.get(callback.asBinder());
+            if (cb == null) {
+                cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
+                        callingPid);
+                mModeWatchers.put(callback.asBinder(), cb);
+            }
+            if (switchOp != AppOpsManager.OP_NONE) {
+                mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
+            }
+            if (mayWatchPackageName) {
+                mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName);
+            }
+            evalAllForegroundOpsLocked();
+        }
+    }
+
+    @Override
+    public void stopWatchingMode(IAppOpsCallback callback) {
+        if (callback == null) {
+            return;
+        }
+        synchronized (this) {
+            ModeCallback cb = mModeWatchers.remove(callback.asBinder());
+            if (cb != null) {
+                cb.unlinkToDeath();
+                mAppOpsServiceInterface.removeListener(cb);
+            }
+
+            evalAllForegroundOpsLocked();
+        }
+    }
+
+    @Override
+    public int checkOperation(int code, int uid, String packageName,
+            @Nullable String attributionTag, boolean raw) {
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return AppOpsManager.opToDefaultMode(code);
+        }
+
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+        return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
+    }
+
+    /**
+     * Get the mode of an app-op.
+     *
+     * @param code        The code of the op
+     * @param uid         The uid of the package the op belongs to
+     * @param packageName The package the op belongs to
+     * @param raw         If the raw state of eval-ed state should be checked.
+     * @return The mode of the op
+     */
+    private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, boolean raw) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, null);
+        } catch (SecurityException e) {
+            Slog.e(TAG, "checkOperation", e);
+            return AppOpsManager.opToDefaultMode(code);
+        }
+
+        if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+        synchronized (this) {
+            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
+                return AppOpsManager.MODE_IGNORED;
+            }
+            code = AppOpsManager.opToSwitch(code);
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState != null
+                    && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
+                final int rawMode = uidState.getUidMode(code);
+                return raw ? rawMode : uidState.evalMode(code, rawMode);
+            }
+            Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
+            if (op == null) {
+                return AppOpsManager.opToDefaultMode(code);
+            }
+            return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
+        }
+    }
+
+    @Override
+    public int checkPackage(int uid, String packageName) {
+        Objects.requireNonNull(packageName);
+        try {
+            verifyAndGetBypass(uid, packageName, null);
+            // When the caller is the system, it's possible that the packageName is the special
+            // one (e.g., "root") which isn't actually existed.
+            if (resolveUid(packageName) == uid
+                    || (isPackageExisted(packageName)
+                            && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
+                return AppOpsManager.MODE_ALLOWED;
+            }
+            return AppOpsManager.MODE_ERRORED;
+        } catch (SecurityException ignored) {
+            return AppOpsManager.MODE_ERRORED;
+        }
+    }
+
+    private boolean isPackageExisted(String packageName) {
+        return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
+    }
+
+    /**
+     * This method will check with PackageManager to determine if the package provided should
+     * be visible to the {@link Binder#getCallingUid()}.
+     *
+     * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
+     */
+    private boolean filterAppAccessUnlocked(String packageName, int userId) {
+        final int callingUid = Binder.getCallingUid();
+        return LocalServices.getService(PackageManagerInternal.class)
+                .filterAppAccess(packageName, callingUid, userId);
+    }
+
+    @Override
+    public int noteOperation(int code, int uid, @Nullable String packageName,
+            @Nullable String attributionTag, @Nullable String message) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return AppOpsManager.MODE_ERRORED;
+        }
+
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+        return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
+                Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+    }
+
+    @Override
+    public int noteOperationUnchecked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+            @Nullable String proxyAttributionTag, @OpFlags int flags) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+            if (!pvr.isAttributionTagValid) {
+                attributionTag = null;
+            }
+        } catch (SecurityException e) {
+            Slog.e(TAG, "noteOperation", e);
+            return AppOpsManager.MODE_ERRORED;
+        }
+
+        synchronized (this) {
+            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+            if (ops == null) {
+                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                        AppOpsManager.MODE_IGNORED);
+                if (DEBUG) {
+                    Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+                            + " package " + packageName + "flags: "
+                            + AppOpsManager.flagsToString(flags));
+                }
+                return AppOpsManager.MODE_ERRORED;
+            }
+            final Op op = getOpLocked(ops, code, uid, true);
+            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+            if (attributedOp.isRunning()) {
+                Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
+                        + code + " startTime of in progress event="
+                        + attributedOp.mInProgressEvents.valueAt(0).getStartTime());
+            }
+
+            final int switchCode = AppOpsManager.opToSwitch(code);
+            final UidState uidState = ops.uidState;
+            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
+                attributedOp.rejected(uidState.getState(), flags);
+                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                        AppOpsManager.MODE_IGNORED);
+                return AppOpsManager.MODE_IGNORED;
+            }
+            // If there is a non-default per UID policy (we set UID op mode only if
+            // non-default) it takes over, otherwise use the per package policy.
+            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+                if (uidMode != AppOpsManager.MODE_ALLOWED) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    attributedOp.rejected(uidState.getState(), flags);
+                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                            uidMode);
+                    return uidMode;
+                }
+            } else {
+                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+                        : op;
+                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+                if (mode != AppOpsManager.MODE_ALLOWED) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    attributedOp.rejected(uidState.getState(), flags);
+                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                            mode);
+                    return mode;
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "noteOperation: allowing code " + code + " uid " + uid + " package "
+                                + packageName + (attributionTag == null ? ""
+                                : "." + attributionTag) + " flags: "
+                                + AppOpsManager.flagsToString(flags));
+            }
+            scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                    AppOpsManager.MODE_ALLOWED);
+            attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
+                    uidState.getState(),
+                    flags);
+
+            return AppOpsManager.MODE_ALLOWED;
+        }
+    }
+
+    @Override
+    public boolean isAttributionTagValid(int uid, @NonNull String packageName,
+            @Nullable String attributionTag,
+            @Nullable String proxyPackageName) {
+        try {
+            return verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName)
+                    .isAttributionTagValid;
+        } catch (SecurityException ignored) {
+            // We don't want to throw, this exception will be handled in the (c/n/s)Operation calls
+            // when they need the bypass object.
+            return false;
+        }
+    }
+
+    // TODO moltmann: Allow watching for attribution ops
+    @Override
+    public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
+        int watchedUid = Process.INVALID_UID;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                != PackageManager.PERMISSION_GRANTED) {
+            watchedUid = callingUid;
+        }
+        if (ops != null) {
+            Preconditions.checkArrayElementsInRange(ops, 0,
+                    AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
+        }
+        if (callback == null) {
+            return;
+        }
+        synchronized (this) {
+            SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
+            if (callbacks == null) {
+                callbacks = new SparseArray<>();
+                mActiveWatchers.put(callback.asBinder(), callbacks);
+            }
+            final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
+                    callingUid, callingPid);
+            for (int op : ops) {
+                callbacks.put(op, activeCallback);
+            }
+        }
+    }
+
+    @Override
+    public void stopWatchingActive(IAppOpsActiveCallback callback) {
+        if (callback == null) {
+            return;
+        }
+        synchronized (this) {
+            final SparseArray<ActiveCallback> activeCallbacks =
+                    mActiveWatchers.remove(callback.asBinder());
+            if (activeCallbacks == null) {
+                return;
+            }
+            final int callbackCount = activeCallbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                activeCallbacks.valueAt(i).destroy();
+            }
+        }
+    }
+
+    @Override
+    public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
+        int watchedUid = Process.INVALID_UID;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                != PackageManager.PERMISSION_GRANTED) {
+            watchedUid = callingUid;
+        }
+
+        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
+        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
+                "Invalid op code in: " + Arrays.toString(ops));
+        Objects.requireNonNull(callback, "Callback cannot be null");
+
+        synchronized (this) {
+            SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
+            if (callbacks == null) {
+                callbacks = new SparseArray<>();
+                mStartedWatchers.put(callback.asBinder(), callbacks);
+            }
+
+            final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
+                    callingUid, callingPid);
+            for (int op : ops) {
+                callbacks.put(op, startedCallback);
+            }
+        }
+    }
+
+    @Override
+    public void stopWatchingStarted(IAppOpsStartedCallback callback) {
+        Objects.requireNonNull(callback, "Callback cannot be null");
+
+        synchronized (this) {
+            final SparseArray<StartedCallback> startedCallbacks =
+                    mStartedWatchers.remove(callback.asBinder());
+            if (startedCallbacks == null) {
+                return;
+            }
+
+            final int callbackCount = startedCallbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                startedCallbacks.valueAt(i).destroy();
+            }
+        }
+    }
+
+    @Override
+    public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
+        int watchedUid = Process.INVALID_UID;
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                != PackageManager.PERMISSION_GRANTED) {
+            watchedUid = callingUid;
+        }
+        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
+        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
+                "Invalid op code in: " + Arrays.toString(ops));
+        Objects.requireNonNull(callback, "Callback cannot be null");
+        synchronized (this) {
+            SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
+            if (callbacks == null) {
+                callbacks = new SparseArray<>();
+                mNotedWatchers.put(callback.asBinder(), callbacks);
+            }
+            final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
+                    callingUid, callingPid);
+            for (int op : ops) {
+                callbacks.put(op, notedCallback);
+            }
+        }
+    }
+
+    @Override
+    public void stopWatchingNoted(IAppOpsNotedCallback callback) {
+        Objects.requireNonNull(callback, "Callback cannot be null");
+        synchronized (this) {
+            final SparseArray<NotedCallback> notedCallbacks =
+                    mNotedWatchers.remove(callback.asBinder());
+            if (notedCallbacks == null) {
+                return;
+            }
+            final int callbackCount = notedCallbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                notedCallbacks.valueAt(i).destroy();
+            }
+        }
+    }
+
+    @Override
+    public int startOperation(@NonNull IBinder clientId, int code, int uid,
+            @Nullable String packageName, @Nullable String attributionTag,
+            boolean startIfModeDefault, @NonNull String message,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return AppOpsManager.MODE_ERRORED;
+        }
+
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+
+        // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
+        // purposes and not as a check, also make sure that the caller is allowed to access
+        // the data gated by OP_RECORD_AUDIO.
+        //
+        // TODO: Revert this change before Android 12.
+        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
+            int result = checkOperation(OP_RECORD_AUDIO, uid, packageName, null, false);
+            if (result != AppOpsManager.MODE_ALLOWED) {
+                return result;
+            }
+        }
+        return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
+                Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
+                attributionFlags, attributionChainId, /*dryRun*/ false);
+    }
+
+    private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
+        return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
+    }
+
+    @Override
+    public int startOperationUnchecked(IBinder clientId, int code, int uid,
+            @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
+            String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
+            boolean startIfModeDefault, @AttributionFlags int attributionFlags,
+            int attributionChainId, boolean dryRun) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+            if (!pvr.isAttributionTagValid) {
+                attributionTag = null;
+            }
+        } catch (SecurityException e) {
+            Slog.e(TAG, "startOperation", e);
+            return AppOpsManager.MODE_ERRORED;
+        }
+
+        boolean isRestricted;
+        int startType = START_TYPE_FAILED;
+        synchronized (this) {
+            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+            if (ops == null) {
+                if (!dryRun) {
+                    scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                            flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
+                            attributionChainId);
+                }
+                if (DEBUG) {
+                    Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+                            + " package " + packageName + " flags: "
+                            + AppOpsManager.flagsToString(flags));
+                }
+                return AppOpsManager.MODE_ERRORED;
+            }
+            final Op op = getOpLocked(ops, code, uid, true);
+            final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+            final UidState uidState = ops.uidState;
+            isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
+                    false);
+            final int switchCode = AppOpsManager.opToSwitch(code);
+            // If there is a non-default per UID policy (we set UID op mode only if
+            // non-default) it takes over, otherwise use the per package policy.
+            if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+                if (!shouldStartForMode(uidMode, startIfModeDefault)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    if (!dryRun) {
+                        attributedOp.rejected(uidState.getState(), flags);
+                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                                flags, uidMode, startType, attributionFlags, attributionChainId);
+                    }
+                    return uidMode;
+                }
+            } else {
+                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+                        : op;
+                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+                if (!shouldStartForMode(mode, startIfModeDefault)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    if (!dryRun) {
+                        attributedOp.rejected(uidState.getState(), flags);
+                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                                flags, mode, startType, attributionFlags, attributionChainId);
+                    }
+                    return mode;
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+                        + " package " + packageName + " restricted: " + isRestricted
+                        + " flags: " + AppOpsManager.flagsToString(flags));
+            }
+            if (!dryRun) {
+                try {
+                    if (isRestricted) {
+                        attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
+                                proxyAttributionTag, uidState.getState(), flags,
+                                attributionFlags, attributionChainId);
+                    } else {
+                        attributedOp.started(clientId, proxyUid, proxyPackageName,
+                                proxyAttributionTag, uidState.getState(), flags,
+                                attributionFlags, attributionChainId);
+                        startType = START_TYPE_STARTED;
+                    }
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+                scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                        isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+                        attributionChainId);
+            }
+        }
+
+        // Possible bug? The raw mode could have been MODE_DEFAULT to reach here.
+        return isRestricted ? MODE_IGNORED : MODE_ALLOWED;
+    }
+
+    @Override
+    public void finishOperation(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return;
+        }
+
+        String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return;
+        }
+
+        finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
+    }
+
+    @Override
+    public void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag);
+            if (!pvr.isAttributionTagValid) {
+                attributionTag = null;
+            }
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Cannot finishOperation", e);
+            return;
+        }
+
+        synchronized (this) {
+            Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
+                    pvr.bypass, /* edit */ true);
+            if (op == null) {
+                Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
+                return;
+            }
+            final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+            if (attributedOp == null) {
+                Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
+                return;
+            }
+
+            if (attributedOp.isRunning() || attributedOp.isPaused()) {
+                attributedOp.finished(clientId);
+            } else {
+                Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
+            }
+        }
+    }
+
+    void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, boolean active,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        ArraySet<ActiveCallback> dispatchedCallbacks = null;
+        final int callbackListCount = mActiveWatchers.size();
+        for (int i = 0; i < callbackListCount; i++) {
+            final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
+            ActiveCallback callback = callbacks.get(code);
+            if (callback != null) {
+                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+                    continue;
+                }
+                if (dispatchedCallbacks == null) {
+                    dispatchedCallbacks = new ArraySet<>();
+                }
+                dispatchedCallbacks.add(callback);
+            }
+        }
+        if (dispatchedCallbacks == null) {
+            return;
+        }
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsServiceImpl::notifyOpActiveChanged,
+                this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
+                attributionFlags, attributionChainId));
+    }
+
+    private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
+            int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
+            boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
+        // There are features watching for mode changes such as window manager
+        // and location manager which are in our process. The callbacks in these
+        // features may require permissions our remote caller does not have.
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final int callbackCount = callbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                final ActiveCallback callback = callbacks.valueAt(i);
+                try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
+                    callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
+                            active, attributionFlags, attributionChainId);
+                } catch (RemoteException e) {
+                    /* do nothing */
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
+            String attributionTag, @OpFlags int flags, @Mode int result,
+            @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        ArraySet<StartedCallback> dispatchedCallbacks = null;
+        final int callbackListCount = mStartedWatchers.size();
+        for (int i = 0; i < callbackListCount; i++) {
+            final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
+
+            StartedCallback callback = callbacks.get(code);
+            if (callback != null) {
+                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+                    continue;
+                }
+
+                if (dispatchedCallbacks == null) {
+                    dispatchedCallbacks = new ArraySet<>();
+                }
+                dispatchedCallbacks.add(callback);
+            }
+        }
+
+        if (dispatchedCallbacks == null) {
+            return;
+        }
+
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsServiceImpl::notifyOpStarted,
+                this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
+                result, startedType, attributionFlags, attributionChainId));
+    }
+
+    private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
+            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+            @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final int callbackCount = callbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                final StartedCallback callback = callbacks.valueAt(i);
+                try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
+                    callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
+                            result, startedType, attributionFlags, attributionChainId);
+                } catch (RemoteException e) {
+                    /* do nothing */
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
+            String attributionTag, @OpFlags int flags, @Mode int result) {
+        ArraySet<NotedCallback> dispatchedCallbacks = null;
+        final int callbackListCount = mNotedWatchers.size();
+        for (int i = 0; i < callbackListCount; i++) {
+            final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
+            final NotedCallback callback = callbacks.get(code);
+            if (callback != null) {
+                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+                    continue;
+                }
+                if (dispatchedCallbacks == null) {
+                    dispatchedCallbacks = new ArraySet<>();
+                }
+                dispatchedCallbacks.add(callback);
+            }
+        }
+        if (dispatchedCallbacks == null) {
+            return;
+        }
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsServiceImpl::notifyOpChecked,
+                this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
+                result));
+    }
+
+    private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
+            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+            @Mode int result) {
+        // There are features watching for checks in our process. The callbacks in
+        // these features may require permissions our remote caller does not have.
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final int callbackCount = callbacks.size();
+            for (int i = 0; i < callbackCount; i++) {
+                final NotedCallback callback = callbacks.valueAt(i);
+                try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
+                    callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
+                            result);
+                } catch (RemoteException e) {
+                    /* do nothing */
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private void verifyIncomingUid(int uid) {
+        if (uid == Binder.getCallingUid()) {
+            return;
+        }
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+    }
+
+    private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
+        // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
+        // as watcher should not use this to signal if the value is changed.
+        return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+                watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
+    }
+
+    private void verifyIncomingOp(int op) {
+        if (op >= 0 && op < AppOpsManager._NUM_OP) {
+            // Enforce manage appops permission if it's a restricted read op.
+            if (opRestrictsRead(op)) {
+                mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+                        Binder.getCallingPid(), Binder.getCallingUid(), "verifyIncomingOp");
+            }
+            return;
+        }
+        throw new IllegalArgumentException("Bad operation #" + op);
+    }
+
+    private boolean isIncomingPackageValid(@Nullable String packageName, @UserIdInt int userId) {
+        final int callingUid = Binder.getCallingUid();
+        // Handle the special UIDs that don't have actual packages (audioserver, cameraserver, etc).
+        if (packageName == null || isSpecialPackage(callingUid, packageName)) {
+            return true;
+        }
+
+        // If the package doesn't exist, #verifyAndGetBypass would throw a SecurityException in
+        // the end. Although that exception would be caught and return, we could make it return
+        // early.
+        if (!isPackageExisted(packageName)) {
+            return false;
+        }
+
+        if (getPackageManagerInternal().filterAppAccess(packageName, callingUid, userId)) {
+            Slog.w(TAG, packageName + " not found from " + callingUid);
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean isSpecialPackage(int callingUid, @Nullable String packageName) {
+        final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, packageName);
+        return callingUid == Process.SYSTEM_UID
+                || resolveUid(resolvedPackage) != Process.INVALID_UID;
+    }
+
+    private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
+        UidState uidState = mUidStates.get(uid);
+        if (uidState == null) {
+            if (!edit) {
+                return null;
+            }
+            uidState = new UidState(uid);
+            mUidStates.put(uid, uidState);
+        }
+
+        return uidState;
+    }
+
+    @Override
+    public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
+        synchronized (this) {
+            getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
+        }
+    }
+
+    /**
+     * @return {@link PackageManagerInternal}
+     */
+    private @NonNull PackageManagerInternal getPackageManagerInternal() {
+        if (mPackageManagerInternal == null) {
+            mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+        }
+
+        return mPackageManagerInternal;
+    }
+
+    @Override
+    public void verifyPackage(int uid, String packageName) {
+        verifyAndGetBypass(uid, packageName, null);
+    }
+
+    /**
+     * Create a restriction description matching the properties of the package.
+     *
+     * @param pkg The package to create the restriction description for
+     * @return The restriction matching the package
+     */
+    private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
+        return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
+                mContext.checkPermission(android.Manifest.permission
+                        .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
+                        == PackageManager.PERMISSION_GRANTED);
+    }
+
+    /**
+     * @see #verifyAndGetBypass(int, String, String, String)
+     */
+    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+            @Nullable String attributionTag) {
+        return verifyAndGetBypass(uid, packageName, attributionTag, null);
+    }
+
+    /**
+     * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
+     * description} for the package, along with a boolean indicating whether the attribution tag is
+     * valid.
+     *
+     * @param uid              The uid the package belongs to
+     * @param packageName      The package the might belong to the uid
+     * @param attributionTag   attribution tag or {@code null} if no need to verify
+     * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
+     * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
+     * attribution tag is valid
+     */
+    private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+            @Nullable String attributionTag, @Nullable String proxyPackageName) {
+        if (uid == Process.ROOT_UID) {
+            // For backwards compatibility, don't check package name for root UID.
+            return new PackageVerificationResult(null,
+                    /* isAttributionTagValid */ true);
+        }
+        if (Process.isSdkSandboxUid(uid)) {
+            // SDK sandbox processes run in their own UID range, but their associated
+            // UID for checks should always be the UID of the package implementing SDK sandbox
+            // service.
+            // TODO: We will need to modify the callers of this function instead, so
+            // modifications and checks against the app ops state are done with the
+            // correct UID.
+            try {
+                final PackageManager pm = mContext.getPackageManager();
+                final String supplementalPackageName = pm.getSdkSandboxPackageName();
+                if (Objects.equals(packageName, supplementalPackageName)) {
+                    uid = pm.getPackageUidAsUser(supplementalPackageName,
+                            PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // Shouldn't happen for the supplemental package
+                e.printStackTrace();
+            }
+        }
+
+
+        // Do not check if uid/packageName/attributionTag is already known.
+        synchronized (this) {
+            UidState uidState = mUidStates.get(uid);
+            if (uidState != null && uidState.pkgOps != null) {
+                Ops ops = uidState.pkgOps.get(packageName);
+
+                if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
+                        attributionTag)) && ops.bypass != null) {
+                    return new PackageVerificationResult(ops.bypass,
+                            ops.validAttributionTags.contains(attributionTag));
+                }
+            }
+        }
+
+        int callingUid = Binder.getCallingUid();
+
+        // Allow any attribution tag for resolvable uids
+        int pkgUid;
+        if (Objects.equals(packageName, "com.android.shell")) {
+            // Special case for the shell which is a package but should be able
+            // to bypass app attribution tag restrictions.
+            pkgUid = Process.SHELL_UID;
+        } else {
+            pkgUid = resolveUid(packageName);
+        }
+        if (pkgUid != Process.INVALID_UID) {
+            if (pkgUid != UserHandle.getAppId(uid)) {
+                Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+                        + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
+                String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
+                throw new SecurityException("Specified package \"" + packageName + "\" under uid "
+                        + UserHandle.getAppId(uid) + otherUidMessage);
+            }
+            return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
+                    /* isAttributionTagValid */ true);
+        }
+
+        int userId = UserHandle.getUserId(uid);
+        RestrictionBypass bypass = null;
+        boolean isAttributionTagValid = false;
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+            AndroidPackage pkg = pmInt.getPackage(packageName);
+            if (pkg != null) {
+                isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
+                pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
+                bypass = getBypassforPackage(pkg);
+            }
+            if (!isAttributionTagValid) {
+                AndroidPackage proxyPkg = proxyPackageName != null
+                        ? pmInt.getPackage(proxyPackageName) : null;
+                // Re-check in proxy.
+                isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
+                String msg;
+                if (pkg != null && isAttributionTagValid) {
+                    msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
+                            + " package " + proxyPackageName + ", this is not advised";
+                } else if (pkg != null) {
+                    msg = "attributionTag " + attributionTag + " not declared in manifest of "
+                            + packageName;
+                } else {
+                    msg = "package " + packageName + " not found, can't check for "
+                            + "attributionTag " + attributionTag;
+                }
+
+                try {
+                    if (!mPlatformCompat.isChangeEnabledByPackageName(
+                            SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
+                            userId) || !mPlatformCompat.isChangeEnabledByUid(
+                            SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
+                            callingUid)) {
+                        // Do not override tags if overriding is not enabled for this package
+                        isAttributionTagValid = true;
+                    }
+                    Slog.e(TAG, msg);
+                } catch (RemoteException neverHappens) {
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        if (pkgUid != uid) {
+            Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+                    + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
+            String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
+            throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
+                    + otherUidMessage);
+        }
+
+        return new PackageVerificationResult(bypass, isAttributionTagValid);
+    }
+
+    private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
+            @Nullable String attributionTag) {
+        if (pkg == null) {
+            return false;
+        } else if (attributionTag == null) {
+            return true;
+        }
+        if (pkg.getAttributions() != null) {
+            int numAttributions = pkg.getAttributions().size();
+            for (int i = 0; i < numAttributions; i++) {
+                if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Get (and potentially create) ops.
+     *
+     * @param uid                   The uid the package belongs to
+     * @param packageName           The name of the package
+     * @param attributionTag        attribution tag
+     * @param isAttributionTagValid whether the given attribution tag is valid
+     * @param bypass                When to bypass certain op restrictions (can be null if edit
+        *                              == false)
+     * @param edit                  If an ops does not exist, create the ops?
+     * @return The ops
+     */
+    private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
+            boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
+        UidState uidState = getUidStateLocked(uid, edit);
+        if (uidState == null) {
+            return null;
+        }
+
+        if (uidState.pkgOps == null) {
+            if (!edit) {
+                return null;
+            }
+            uidState.pkgOps = new ArrayMap<>();
+        }
+
+        Ops ops = uidState.pkgOps.get(packageName);
+        if (ops == null) {
+            if (!edit) {
+                return null;
+            }
+            ops = new Ops(packageName, uidState);
+            uidState.pkgOps.put(packageName, ops);
+        }
+
+        if (edit) {
+            if (bypass != null) {
+                ops.bypass = bypass;
+            }
+
+            if (attributionTag != null) {
+                ops.knownAttributionTags.add(attributionTag);
+                if (isAttributionTagValid) {
+                    ops.validAttributionTags.add(attributionTag);
+                } else {
+                    ops.validAttributionTags.remove(attributionTag);
+                }
+            }
+        }
+
+        return ops;
+    }
+
+    @Override
+    public void scheduleWriteLocked() {
+        if (!mWriteScheduled) {
+            mWriteScheduled = true;
+            mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+        }
+    }
+
+    @Override
+    public void scheduleFastWriteLocked() {
+        if (!mFastWriteScheduled) {
+            mWriteScheduled = true;
+            mFastWriteScheduled = true;
+            mHandler.removeCallbacks(mWriteRunner);
+            mHandler.postDelayed(mWriteRunner, 10 * 1000);
+        }
+    }
+
+    /**
+     * Get the state of an op for a uid.
+     *
+     * @param code                  The code of the op
+     * @param uid                   The uid the of the package
+     * @param packageName           The package name for which to get the state for
+     * @param attributionTag        The attribution tag
+     * @param isAttributionTagValid Whether the given attribution tag is valid
+     * @param bypass                When to bypass certain op restrictions (can be null if edit
+     *                              == false)
+     * @param edit                  Iff {@code true} create the {@link Op} object if not yet created
+     * @return The {@link Op state} of the op
+     */
+    private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, boolean isAttributionTagValid,
+            @Nullable RestrictionBypass bypass, boolean edit) {
+        Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
+                edit);
+        if (ops == null) {
+            return null;
+        }
+        return getOpLocked(ops, code, uid, edit);
+    }
+
+    private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
+        Op op = ops.get(code);
+        if (op == null) {
+            if (!edit) {
+                return null;
+            }
+            op = new Op(ops.uidState, ops.packageName, code, uid);
+            ops.put(code, op);
+        }
+        if (edit) {
+            scheduleWriteLocked();
+        }
+        return op;
+    }
+
+    private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
+        if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
+            return false;
+        }
+        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+        return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
+    }
+
+    private boolean isOpRestrictedLocked(int uid, int code, String packageName,
+            String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
+        int restrictionSetCount = mOpGlobalRestrictions.size();
+
+        for (int i = 0; i < restrictionSetCount; i++) {
+            ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
+            if (restrictionState.hasRestriction(code)) {
+                return true;
+            }
+        }
+
+        int userHandle = UserHandle.getUserId(uid);
+        restrictionSetCount = mOpUserRestrictions.size();
+
+        for (int i = 0; i < restrictionSetCount; i++) {
+            // For each client, check that the given op is not restricted, or that the given
+            // package is exempt from the restriction.
+            ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
+            if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
+                    isCheckOp)) {
+                RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
+                if (opBypass != null) {
+                    // If we are the system, bypass user restrictions for certain codes
+                    synchronized (this) {
+                        if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
+                            return false;
+                        }
+                        if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
+                            return false;
+                        }
+                        if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
+                                && appBypass.isRecordAudioRestrictionExcept) {
+                            return false;
+                        }
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void readState() {
+        int oldVersion = NO_VERSION;
+        synchronized (mFile) {
+            synchronized (this) {
+                FileInputStream stream;
+                try {
+                    stream = mFile.openRead();
+                } catch (FileNotFoundException e) {
+                    Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
+                    return;
+                }
+                boolean success = false;
+                mUidStates.clear();
+                mAppOpsServiceInterface.clearAllModes();
+                try {
+                    TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+                    int type;
+                    while ((type = parser.next()) != XmlPullParser.START_TAG
+                            && type != XmlPullParser.END_DOCUMENT) {
+                        // Parse next until we reach the start or end
+                    }
+
+                    if (type != XmlPullParser.START_TAG) {
+                        throw new IllegalStateException("no start tag found");
+                    }
+
+                    oldVersion = parser.getAttributeInt(null, "v", NO_VERSION);
+
+                    int outerDepth = parser.getDepth();
+                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                            continue;
+                        }
+
+                        String tagName = parser.getName();
+                        if (tagName.equals("pkg")) {
+                            readPackage(parser);
+                        } else if (tagName.equals("uid")) {
+                            readUidOps(parser);
+                        } else {
+                            Slog.w(TAG, "Unknown element under <app-ops>: "
+                                    + parser.getName());
+                            XmlUtils.skipCurrentTag(parser);
+                        }
+                    }
+                    success = true;
+                } catch (IllegalStateException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (NullPointerException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (NumberFormatException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (XmlPullParserException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (IOException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (IndexOutOfBoundsException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } finally {
+                    if (!success) {
+                        mUidStates.clear();
+                        mAppOpsServiceInterface.clearAllModes();
+                    }
+                    try {
+                        stream.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+        synchronized (this) {
+            upgradeLocked(oldVersion);
+        }
+    }
+
+    private void upgradeRunAnyInBackgroundLocked() {
+        for (int i = 0; i < mUidStates.size(); i++) {
+            final UidState uidState = mUidStates.valueAt(i);
+            if (uidState == null) {
+                continue;
+            }
+            SparseIntArray opModes = uidState.getNonDefaultUidModes();
+            if (opModes != null) {
+                final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
+                if (idx >= 0) {
+                    uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
+                            opModes.valueAt(idx));
+                }
+            }
+            if (uidState.pkgOps == null) {
+                continue;
+            }
+            boolean changed = false;
+            for (int j = 0; j < uidState.pkgOps.size(); j++) {
+                Ops ops = uidState.pkgOps.valueAt(j);
+                if (ops != null) {
+                    final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
+                    if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
+                        final Op copy = new Op(op.uidState, op.packageName,
+                                AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
+                        copy.setMode(op.getMode());
+                        ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
+                        changed = true;
+                    }
+                }
+            }
+            if (changed) {
+                uidState.evalForegroundOps();
+            }
+        }
+    }
+
+    private void upgradeLocked(int oldVersion) {
+        if (oldVersion >= CURRENT_VERSION) {
+            return;
+        }
+        Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
+        switch (oldVersion) {
+            case NO_VERSION:
+                upgradeRunAnyInBackgroundLocked();
+                // fall through
+            case 1:
+                // for future upgrades
+        }
+        scheduleFastWriteLocked();
+    }
+
+    private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
+            XmlPullParserException, IOException {
+        final int uid = parser.getAttributeInt(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                final int code = parser.getAttributeInt(null, "n");
+                final int mode = parser.getAttributeInt(null, "m");
+                setUidMode(code, uid, mode, null);
+            } else {
+                Slog.w(TAG, "Unknown element under <uid-ops>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readPackage(TypedXmlPullParser parser)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        String pkgName = parser.getAttributeValue(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("uid")) {
+                readUid(parser, pkgName);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readUid(TypedXmlPullParser parser, String pkgName)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        int uid = parser.getAttributeInt(null, "n");
+        final UidState uidState = getUidStateLocked(uid, true);
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                readOp(parser, uidState, pkgName);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+        uidState.evalForegroundOps();
+    }
+
+    private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
+            @Nullable String attribution)
+            throws NumberFormatException, IOException, XmlPullParserException {
+        final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
+
+        final long key = parser.getAttributeLong(null, "n");
+        final int uidState = extractUidStateFromKey(key);
+        final int opFlags = extractFlagsFromKey(key);
+
+        final long accessTime = parser.getAttributeLong(null, "t", 0);
+        final long rejectTime = parser.getAttributeLong(null, "r", 0);
+        final long accessDuration = parser.getAttributeLong(null, "d", -1);
+        final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
+        final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
+        final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
+
+        if (accessTime > 0) {
+            attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
+                    proxyAttributionTag, uidState, opFlags);
+        }
+        if (rejectTime > 0) {
+            attributedOp.rejected(rejectTime, uidState, opFlags);
+        }
+    }
+
+    private void readOp(TypedXmlPullParser parser,
+            @NonNull UidState uidState, @NonNull String pkgName)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        int opCode = parser.getAttributeInt(null, "n");
+        Op op = new Op(uidState, pkgName, opCode, uidState.uid);
+
+        final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
+        op.setMode(mode);
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            String tagName = parser.getName();
+            if (tagName.equals("st")) {
+                readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
+            } else {
+                Slog.w(TAG, "Unknown element under <op>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+
+        if (uidState.pkgOps == null) {
+            uidState.pkgOps = new ArrayMap<>();
+        }
+        Ops ops = uidState.pkgOps.get(pkgName);
+        if (ops == null) {
+            ops = new Ops(pkgName, uidState);
+            uidState.pkgOps.put(pkgName, ops);
+        }
+        ops.put(op.op, op);
+    }
+
+    @Override
+    public void writeState() {
+        synchronized (mFile) {
+            FileOutputStream stream;
+            try {
+                stream = mFile.startWrite();
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to write state: " + e);
+                return;
+            }
+
+            List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
+
+            try {
+                TypedXmlSerializer out = Xml.resolveSerializer(stream);
+                out.startDocument(null, true);
+                out.startTag(null, "app-ops");
+                out.attributeInt(null, "v", CURRENT_VERSION);
+
+                SparseArray<SparseIntArray> uidStatesClone;
+                synchronized (this) {
+                    uidStatesClone = new SparseArray<>(mUidStates.size());
+
+                    final int uidStateCount = mUidStates.size();
+                    for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+                        UidState uidState = mUidStates.valueAt(uidStateNum);
+                        int uid = mUidStates.keyAt(uidStateNum);
+
+                        SparseIntArray opModes = uidState.getNonDefaultUidModes();
+                        if (opModes != null && opModes.size() > 0) {
+                            uidStatesClone.put(uid, opModes);
+                        }
+                    }
+                }
+
+                final int uidStateCount = uidStatesClone.size();
+                for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+                    SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
+                    if (opModes != null && opModes.size() > 0) {
+                        out.startTag(null, "uid");
+                        out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
+                        final int opCount = opModes.size();
+                        for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
+                            final int op = opModes.keyAt(opCountNum);
+                            final int mode = opModes.valueAt(opCountNum);
+                            out.startTag(null, "op");
+                            out.attributeInt(null, "n", op);
+                            out.attributeInt(null, "m", mode);
+                            out.endTag(null, "op");
+                        }
+                        out.endTag(null, "uid");
+                    }
+                }
+
+                if (allOps != null) {
+                    String lastPkg = null;
+                    for (int i = 0; i < allOps.size(); i++) {
+                        AppOpsManager.PackageOps pkg = allOps.get(i);
+                        if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
+                            if (lastPkg != null) {
+                                out.endTag(null, "pkg");
+                            }
+                            lastPkg = pkg.getPackageName();
+                            if (lastPkg != null) {
+                                out.startTag(null, "pkg");
+                                out.attribute(null, "n", lastPkg);
+                            }
+                        }
+                        out.startTag(null, "uid");
+                        out.attributeInt(null, "n", pkg.getUid());
+                        List<AppOpsManager.OpEntry> ops = pkg.getOps();
+                        for (int j = 0; j < ops.size(); j++) {
+                            AppOpsManager.OpEntry op = ops.get(j);
+                            out.startTag(null, "op");
+                            out.attributeInt(null, "n", op.getOp());
+                            if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
+                                out.attributeInt(null, "m", op.getMode());
+                            }
+
+                            for (String attributionTag : op.getAttributedOpEntries().keySet()) {
+                                final AttributedOpEntry attribution =
+                                        op.getAttributedOpEntries().get(attributionTag);
+
+                                final ArraySet<Long> keys = attribution.collectKeys();
+
+                                final int keyCount = keys.size();
+                                for (int k = 0; k < keyCount; k++) {
+                                    final long key = keys.valueAt(k);
+
+                                    final int uidState = AppOpsManager.extractUidStateFromKey(key);
+                                    final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+                                    final long accessTime = attribution.getLastAccessTime(uidState,
+                                            uidState, flags);
+                                    final long rejectTime = attribution.getLastRejectTime(uidState,
+                                            uidState, flags);
+                                    final long accessDuration = attribution.getLastDuration(
+                                            uidState, uidState, flags);
+                                    // Proxy information for rejections is not backed up
+                                    final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
+                                            uidState, uidState, flags);
+
+                                    if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
+                                            && proxy == null) {
+                                        continue;
+                                    }
+
+                                    String proxyPkg = null;
+                                    String proxyAttributionTag = null;
+                                    int proxyUid = Process.INVALID_UID;
+                                    if (proxy != null) {
+                                        proxyPkg = proxy.getPackageName();
+                                        proxyAttributionTag = proxy.getAttributionTag();
+                                        proxyUid = proxy.getUid();
+                                    }
+
+                                    out.startTag(null, "st");
+                                    if (attributionTag != null) {
+                                        out.attribute(null, "id", attributionTag);
+                                    }
+                                    out.attributeLong(null, "n", key);
+                                    if (accessTime > 0) {
+                                        out.attributeLong(null, "t", accessTime);
+                                    }
+                                    if (rejectTime > 0) {
+                                        out.attributeLong(null, "r", rejectTime);
+                                    }
+                                    if (accessDuration > 0) {
+                                        out.attributeLong(null, "d", accessDuration);
+                                    }
+                                    if (proxyPkg != null) {
+                                        out.attribute(null, "pp", proxyPkg);
+                                    }
+                                    if (proxyAttributionTag != null) {
+                                        out.attribute(null, "pc", proxyAttributionTag);
+                                    }
+                                    if (proxyUid >= 0) {
+                                        out.attributeInt(null, "pu", proxyUid);
+                                    }
+                                    out.endTag(null, "st");
+                                }
+                            }
+
+                            out.endTag(null, "op");
+                        }
+                        out.endTag(null, "uid");
+                    }
+                    if (lastPkg != null) {
+                        out.endTag(null, "pkg");
+                    }
+                }
+
+                out.endTag(null, "app-ops");
+                out.endDocument();
+                mFile.finishWrite(stream);
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to write state, restoring backup.", e);
+                mFile.failWrite(stream);
+            }
+        }
+        mHistoricalRegistry.writeAndClearDiscreteHistory();
+    }
+
+    private void dumpHelp(PrintWriter pw) {
+        pw.println("AppOps service (appops) dump options:");
+        pw.println("  -h");
+        pw.println("    Print this help text.");
+        pw.println("  --op [OP]");
+        pw.println("    Limit output to data associated with the given app op code.");
+        pw.println("  --mode [MODE]");
+        pw.println("    Limit output to data associated with the given app op mode.");
+        pw.println("  --package [PACKAGE]");
+        pw.println("    Limit output to data associated with the given package name.");
+        pw.println("  --attributionTag [attributionTag]");
+        pw.println("    Limit output to data associated with the given attribution tag.");
+        pw.println("  --include-discrete [n]");
+        pw.println("    Include discrete ops limited to n per dimension. Use zero for no limit.");
+        pw.println("  --watchers");
+        pw.println("    Only output the watcher sections.");
+        pw.println("  --history");
+        pw.println("    Only output history.");
+        pw.println("  --uid-state-changes");
+        pw.println("    Include logs about uid state changes.");
+    }
+
+    private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
+            @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
+            @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
+        final int numAttributions = op.mAttributions.size();
+        for (int i = 0; i < numAttributions; i++) {
+            if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
+                    op.mAttributions.keyAt(i), filterAttributionTag)) {
+                continue;
+            }
+
+            pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
+            dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
+                    prefix + "  ");
+            pw.print(prefix + "]\n");
+        }
+    }
+
+    private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
+            @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
+            @NonNull Date date, @NonNull String prefix) {
+
+        final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
+                attributionTag).getAttributedOpEntries().get(attributionTag);
+
+        final ArraySet<Long> keys = entry.collectKeys();
+
+        final int keyCount = keys.size();
+        for (int k = 0; k < keyCount; k++) {
+            final long key = keys.valueAt(k);
+
+            final int uidState = AppOpsManager.extractUidStateFromKey(key);
+            final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+            final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
+            final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
+            final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
+            final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
+
+            String proxyPkg = null;
+            String proxyAttributionTag = null;
+            int proxyUid = Process.INVALID_UID;
+            if (proxy != null) {
+                proxyPkg = proxy.getPackageName();
+                proxyAttributionTag = proxy.getAttributionTag();
+                proxyUid = proxy.getUid();
+            }
+
+            if (accessTime > 0) {
+                pw.print(prefix);
+                pw.print("Access: ");
+                pw.print(AppOpsManager.keyToString(key));
+                pw.print(" ");
+                date.setTime(accessTime);
+                pw.print(sdf.format(date));
+                pw.print(" (");
+                TimeUtils.formatDuration(accessTime - now, pw);
+                pw.print(")");
+                if (accessDuration > 0) {
+                    pw.print(" duration=");
+                    TimeUtils.formatDuration(accessDuration, pw);
+                }
+                if (proxyUid >= 0) {
+                    pw.print(" proxy[");
+                    pw.print("uid=");
+                    pw.print(proxyUid);
+                    pw.print(", pkg=");
+                    pw.print(proxyPkg);
+                    pw.print(", attributionTag=");
+                    pw.print(proxyAttributionTag);
+                    pw.print("]");
+                }
+                pw.println();
+            }
+
+            if (rejectTime > 0) {
+                pw.print(prefix);
+                pw.print("Reject: ");
+                pw.print(AppOpsManager.keyToString(key));
+                date.setTime(rejectTime);
+                pw.print(sdf.format(date));
+                pw.print(" (");
+                TimeUtils.formatDuration(rejectTime - now, pw);
+                pw.print(")");
+                if (proxyUid >= 0) {
+                    pw.print(" proxy[");
+                    pw.print("uid=");
+                    pw.print(proxyUid);
+                    pw.print(", pkg=");
+                    pw.print(proxyPkg);
+                    pw.print(", attributionTag=");
+                    pw.print(proxyAttributionTag);
+                    pw.print("]");
+                }
+                pw.println();
+            }
+        }
+
+        final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+        if (attributedOp.isRunning()) {
+            long earliestElapsedTime = Long.MAX_VALUE;
+            long maxNumStarts = 0;
+            int numInProgressEvents = attributedOp.mInProgressEvents.size();
+            for (int i = 0; i < numInProgressEvents; i++) {
+                AttributedOp.InProgressStartOpEvent event =
+                        attributedOp.mInProgressEvents.valueAt(i);
+
+                earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
+                maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
+            }
+
+            pw.print(prefix + "Running start at: ");
+            TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
+            pw.println();
+
+            if (maxNumStarts > 1) {
+                pw.print(prefix + "startNesting=");
+                pw.println(maxNumStarts);
+            }
+        }
+    }
+
+    @NeverCompile // Avoid size overhead of debugging code.
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
+
+        int dumpOp = OP_NONE;
+        String dumpPackage = null;
+        String dumpAttributionTag = null;
+        int dumpUid = Process.INVALID_UID;
+        int dumpMode = -1;
+        boolean dumpWatchers = false;
+        // TODO ntmyren: Remove the dumpHistory and dumpFilter
+        boolean dumpHistory = false;
+        boolean includeDiscreteOps = false;
+        boolean dumpUidStateChangeLogs = false;
+        int nDiscreteOps = 10;
+        @HistoricalOpsRequestFilter int dumpFilter = 0;
+        boolean dumpAll = false;
+
+        if (args != null) {
+            for (int i = 0; i < args.length; i++) {
+                String arg = args[i];
+                if ("-h".equals(arg)) {
+                    dumpHelp(pw);
+                    return;
+                } else if ("-a".equals(arg)) {
+                    // dump all data
+                    dumpAll = true;
+                } else if ("--op".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --op option");
+                        return;
+                    }
+                    dumpOp = AppOpsService.Shell.strOpToOp(args[i], pw);
+                    dumpFilter |= FILTER_BY_OP_NAMES;
+                    if (dumpOp < 0) {
+                        return;
+                    }
+                } else if ("--package".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --package option");
+                        return;
+                    }
+                    dumpPackage = args[i];
+                    dumpFilter |= FILTER_BY_PACKAGE_NAME;
+                    try {
+                        dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
+                                PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
+                                0);
+                    } catch (RemoteException e) {
+                    }
+                    if (dumpUid < 0) {
+                        pw.println("Unknown package: " + dumpPackage);
+                        return;
+                    }
+                    dumpUid = UserHandle.getAppId(dumpUid);
+                    dumpFilter |= FILTER_BY_UID;
+                } else if ("--attributionTag".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --attributionTag option");
+                        return;
+                    }
+                    dumpAttributionTag = args[i];
+                    dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
+                } else if ("--mode".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --mode option");
+                        return;
+                    }
+                    dumpMode = AppOpsService.Shell.strModeToMode(args[i], pw);
+                    if (dumpMode < 0) {
+                        return;
+                    }
+                } else if ("--watchers".equals(arg)) {
+                    dumpWatchers = true;
+                } else if ("--include-discrete".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --include-discrete option");
+                        return;
+                    }
+                    try {
+                        nDiscreteOps = Integer.valueOf(args[i]);
+                    } catch (NumberFormatException e) {
+                        pw.println("Wrong parameter: " + args[i]);
+                        return;
+                    }
+                    includeDiscreteOps = true;
+                } else if ("--history".equals(arg)) {
+                    dumpHistory = true;
+                } else if (arg.length() > 0 && arg.charAt(0) == '-') {
+                    pw.println("Unknown option: " + arg);
+                    return;
+                } else if ("--uid-state-changes".equals(arg)) {
+                    dumpUidStateChangeLogs = true;
+                } else {
+                    pw.println("Unknown command: " + arg);
+                    return;
+                }
+            }
+        }
+
+        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+        final Date date = new Date();
+        synchronized (this) {
+            pw.println("Current AppOps Service state:");
+            if (!dumpHistory && !dumpWatchers) {
+                mConstants.dump(pw);
+            }
+            pw.println();
+            final long now = System.currentTimeMillis();
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            boolean needSep = false;
+            if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
+                    && !dumpHistory) {
+                pw.println("  Profile owners:");
+                for (int poi = 0; poi < mProfileOwners.size(); poi++) {
+                    pw.print("    User #");
+                    pw.print(mProfileOwners.keyAt(poi));
+                    pw.print(": ");
+                    UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
+                    pw.println();
+                }
+                pw.println();
+            }
+
+            if (!dumpHistory) {
+                needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
+            }
+
+            if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
+                boolean printedHeader = false;
+                for (int i = 0; i < mModeWatchers.size(); i++) {
+                    final ModeCallback cb = mModeWatchers.valueAt(i);
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
+                        continue;
+                    }
+                    needSep = true;
+                    if (!printedHeader) {
+                        pw.println("  All op mode watchers:");
+                        printedHeader = true;
+                    }
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
+                    pw.print(": ");
+                    pw.println(cb);
+                }
+            }
+            if (mActiveWatchers.size() > 0 && dumpMode < 0) {
+                needSep = true;
+                boolean printedHeader = false;
+                for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
+                    final SparseArray<ActiveCallback> activeWatchers =
+                            mActiveWatchers.valueAt(watcherNum);
+                    if (activeWatchers.size() <= 0) {
+                        continue;
+                    }
+                    final ActiveCallback cb = activeWatchers.valueAt(0);
+                    if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
+                        continue;
+                    }
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+                        continue;
+                    }
+                    if (!printedHeader) {
+                        pw.println("  All op active watchers:");
+                        printedHeader = true;
+                    }
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(
+                            mActiveWatchers.keyAt(watcherNum))));
+                    pw.println(" ->");
+                    pw.print("        [");
+                    final int opCount = activeWatchers.size();
+                    for (int opNum = 0; opNum < opCount; opNum++) {
+                        if (opNum > 0) {
+                            pw.print(' ');
+                        }
+                        pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
+                        if (opNum < opCount - 1) {
+                            pw.print(',');
+                        }
+                    }
+                    pw.println("]");
+                    pw.print("        ");
+                    pw.println(cb);
+                }
+            }
+            if (mStartedWatchers.size() > 0 && dumpMode < 0) {
+                needSep = true;
+                boolean printedHeader = false;
+
+                final int watchersSize = mStartedWatchers.size();
+                for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
+                    final SparseArray<StartedCallback> startedWatchers =
+                            mStartedWatchers.valueAt(watcherNum);
+                    if (startedWatchers.size() <= 0) {
+                        continue;
+                    }
+
+                    final StartedCallback cb = startedWatchers.valueAt(0);
+                    if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
+                        continue;
+                    }
+
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+                        continue;
+                    }
+
+                    if (!printedHeader) {
+                        pw.println("  All op started watchers:");
+                        printedHeader = true;
+                    }
+
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(
+                            mStartedWatchers.keyAt(watcherNum))));
+                    pw.println(" ->");
+
+                    pw.print("        [");
+                    final int opCount = startedWatchers.size();
+                    for (int opNum = 0; opNum < opCount; opNum++) {
+                        if (opNum > 0) {
+                            pw.print(' ');
+                        }
+
+                        pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
+                        if (opNum < opCount - 1) {
+                            pw.print(',');
+                        }
+                    }
+                    pw.println("]");
+
+                    pw.print("        ");
+                    pw.println(cb);
+                }
+            }
+            if (mNotedWatchers.size() > 0 && dumpMode < 0) {
+                needSep = true;
+                boolean printedHeader = false;
+                for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
+                    final SparseArray<NotedCallback> notedWatchers =
+                            mNotedWatchers.valueAt(watcherNum);
+                    if (notedWatchers.size() <= 0) {
+                        continue;
+                    }
+                    final NotedCallback cb = notedWatchers.valueAt(0);
+                    if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
+                        continue;
+                    }
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+                        continue;
+                    }
+                    if (!printedHeader) {
+                        pw.println("  All op noted watchers:");
+                        printedHeader = true;
+                    }
+                    pw.print("    ");
+                    pw.print(Integer.toHexString(System.identityHashCode(
+                            mNotedWatchers.keyAt(watcherNum))));
+                    pw.println(" ->");
+                    pw.print("        [");
+                    final int opCount = notedWatchers.size();
+                    for (int opNum = 0; opNum < opCount; opNum++) {
+                        if (opNum > 0) {
+                            pw.print(' ');
+                        }
+                        pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
+                        if (opNum < opCount - 1) {
+                            pw.print(',');
+                        }
+                    }
+                    pw.println("]");
+                    pw.print("        ");
+                    pw.println(cb);
+                }
+            }
+            if (needSep) {
+                pw.println();
+            }
+            for (int i = 0; i < mUidStates.size(); i++) {
+                UidState uidState = mUidStates.valueAt(i);
+                final SparseIntArray opModes = uidState.getNonDefaultUidModes();
+                final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+
+                if (dumpWatchers || dumpHistory) {
+                    continue;
+                }
+                if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
+                    boolean hasOp = dumpOp < 0 || (opModes != null
+                            && opModes.indexOfKey(dumpOp) >= 0);
+                    boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
+                    boolean hasMode = dumpMode < 0;
+                    if (!hasMode && opModes != null) {
+                        for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
+                            if (opModes.valueAt(opi) == dumpMode) {
+                                hasMode = true;
+                            }
+                        }
+                    }
+                    if (pkgOps != null) {
+                        for (int pkgi = 0;
+                                (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
+                                pkgi++) {
+                            Ops ops = pkgOps.valueAt(pkgi);
+                            if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
+                                hasOp = true;
+                            }
+                            if (!hasMode) {
+                                for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
+                                    if (ops.valueAt(opi).getMode() == dumpMode) {
+                                        hasMode = true;
+                                    }
+                                }
+                            }
+                            if (!hasPackage && dumpPackage.equals(ops.packageName)) {
+                                hasPackage = true;
+                            }
+                        }
+                    }
+                    if (uidState.foregroundOps != null && !hasOp) {
+                        if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
+                            hasOp = true;
+                        }
+                    }
+                    if (!hasOp || !hasPackage || !hasMode) {
+                        continue;
+                    }
+                }
+
+                pw.print("  Uid ");
+                UserHandle.formatUid(pw, uidState.uid);
+                pw.println(":");
+                uidState.dump(pw, nowElapsed);
+                if (uidState.foregroundOps != null && (dumpMode < 0
+                        || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
+                    pw.println("    foregroundOps:");
+                    for (int j = 0; j < uidState.foregroundOps.size(); j++) {
+                        if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
+                            continue;
+                        }
+                        pw.print("      ");
+                        pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
+                        pw.print(": ");
+                        pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
+                    }
+                    pw.print("    hasForegroundWatchers=");
+                    pw.println(uidState.hasForegroundWatchers);
+                }
+                needSep = true;
+
+                if (opModes != null) {
+                    final int opModeCount = opModes.size();
+                    for (int j = 0; j < opModeCount; j++) {
+                        final int code = opModes.keyAt(j);
+                        final int mode = opModes.valueAt(j);
+                        if (dumpOp >= 0 && dumpOp != code) {
+                            continue;
+                        }
+                        if (dumpMode >= 0 && dumpMode != mode) {
+                            continue;
+                        }
+                        pw.print("      ");
+                        pw.print(AppOpsManager.opToName(code));
+                        pw.print(": mode=");
+                        pw.println(AppOpsManager.modeToName(mode));
+                    }
+                }
+
+                if (pkgOps == null) {
+                    continue;
+                }
+
+                for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
+                    final Ops ops = pkgOps.valueAt(pkgi);
+                    if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
+                        continue;
+                    }
+                    boolean printedPackage = false;
+                    for (int j = 0; j < ops.size(); j++) {
+                        final Op op = ops.valueAt(j);
+                        final int opCode = op.op;
+                        if (dumpOp >= 0 && dumpOp != opCode) {
+                            continue;
+                        }
+                        if (dumpMode >= 0 && dumpMode != op.getMode()) {
+                            continue;
+                        }
+                        if (!printedPackage) {
+                            pw.print("    Package ");
+                            pw.print(ops.packageName);
+                            pw.println(":");
+                            printedPackage = true;
+                        }
+                        pw.print("      ");
+                        pw.print(AppOpsManager.opToName(opCode));
+                        pw.print(" (");
+                        pw.print(AppOpsManager.modeToName(op.getMode()));
+                        final int switchOp = AppOpsManager.opToSwitch(opCode);
+                        if (switchOp != opCode) {
+                            pw.print(" / switch ");
+                            pw.print(AppOpsManager.opToName(switchOp));
+                            final Op switchObj = ops.get(switchOp);
+                            int mode = switchObj == null
+                                    ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
+                            pw.print("=");
+                            pw.print(AppOpsManager.modeToName(mode));
+                        }
+                        pw.println("): ");
+                        dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
+                                sdf, date, "        ");
+                    }
+                }
+            }
+            if (needSep) {
+                pw.println();
+            }
+
+            boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
+            mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
+
+            if (dumpAll || dumpUidStateChangeLogs) {
+                pw.println();
+                pw.println("Uid State Changes Event Log:");
+                getUidStateTracker().dumpEvents(pw);
+            }
+        }
+
+        // Must not hold the appops lock
+        if (dumpHistory && !dumpWatchers) {
+            mHistoricalRegistry.dump("  ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
+                    dumpFilter);
+        }
+        if (includeDiscreteOps) {
+            pw.println("Discrete accesses: ");
+            mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
+                    dumpFilter, dumpOp, sdf, date, "  ", nDiscreteOps);
+        }
+    }
+
+    @Override
+    public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
+        checkSystemUid("setUserRestrictions");
+        Objects.requireNonNull(restrictions);
+        Objects.requireNonNull(token);
+        for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
+            String restriction = AppOpsManager.opToRestriction(i);
+            if (restriction != null) {
+                setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
+                        userHandle, null);
+            }
+        }
+    }
+
+    @Override
+    public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
+            PackageTagsList excludedPackageTags) {
+        if (Binder.getCallingPid() != Process.myPid()) {
+            mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
+                    Binder.getCallingPid(), Binder.getCallingUid(), null);
+        }
+        if (userHandle != UserHandle.getCallingUserId()) {
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission
+                    .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
+                    && mContext.checkCallingOrSelfPermission(Manifest.permission
+                    .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
+                        + " INTERACT_ACROSS_USERS to interact cross user ");
+            }
+        }
+        verifyIncomingOp(code);
+        Objects.requireNonNull(token);
+        setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
+    }
+
+    private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
+            int userHandle, PackageTagsList excludedPackageTags) {
+        synchronized (AppOpsServiceImpl.this) {
+            ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
+
+            if (restrictionState == null) {
+                try {
+                    restrictionState = new ClientUserRestrictionState(token);
+                } catch (RemoteException e) {
+                    return;
+                }
+                mOpUserRestrictions.put(token, restrictionState);
+            }
+
+            if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
+                    userHandle)) {
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsServiceImpl::updateStartedOpModeForUser, this, code,
+                        restricted, userHandle));
+            }
+
+            if (restrictionState.isDefault()) {
+                mOpUserRestrictions.remove(token);
+                restrictionState.destroy();
+            }
+        }
+    }
+
+    @Override
+    public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
+        if (Binder.getCallingPid() != Process.myPid()) {
+            throw new SecurityException("Only the system can set global restrictions");
+        }
+
+        synchronized (this) {
+            ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
+
+            if (restrictionState == null) {
+                try {
+                    restrictionState = new ClientGlobalRestrictionState(token);
+                } catch (RemoteException e) {
+                    return;
+                }
+                mOpGlobalRestrictions.put(token, restrictionState);
+            }
+
+            if (restrictionState.setRestriction(code, restricted)) {
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsServiceImpl::updateStartedOpModeForUser, this, code,
+                        restricted, UserHandle.USER_ALL));
+            }
+
+            if (restrictionState.isDefault()) {
+                mOpGlobalRestrictions.remove(token);
+                restrictionState.destroy();
+            }
+        }
+    }
+
+    @Override
+    public int getOpRestrictionCount(int code, UserHandle user, String pkg,
+            String attributionTag) {
+        int number = 0;
+        synchronized (this) {
+            int numRestrictions = mOpUserRestrictions.size();
+            for (int i = 0; i < numRestrictions; i++) {
+                if (mOpUserRestrictions.valueAt(i)
+                        .hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
+                                false)) {
+                    number++;
+                }
+            }
+
+            numRestrictions = mOpGlobalRestrictions.size();
+            for (int i = 0; i < numRestrictions; i++) {
+                if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
+                    number++;
+                }
+            }
+        }
+
+        return number;
+    }
+
+    private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
+        synchronized (AppOpsServiceImpl.this) {
+            int numUids = mUidStates.size();
+            for (int uidNum = 0; uidNum < numUids; uidNum++) {
+                int uid = mUidStates.keyAt(uidNum);
+                if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
+                    continue;
+                }
+                updateStartedOpModeForUidLocked(code, restricted, uid);
+            }
+        }
+    }
+
+    private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
+        UidState uidState = mUidStates.get(uid);
+        if (uidState == null || uidState.pkgOps == null) {
+            return;
+        }
+
+        int numPkgOps = uidState.pkgOps.size();
+        for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
+            Ops ops = uidState.pkgOps.valueAt(pkgNum);
+            Op op = ops != null ? ops.get(code) : null;
+            if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
+                continue;
+            }
+            int numAttrTags = op.mAttributions.size();
+            for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
+                AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
+                if (restricted && attrOp.isRunning()) {
+                    attrOp.pause();
+                } else if (attrOp.isPaused()) {
+                    attrOp.resume();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void notifyWatchersOfChange(int code, int uid) {
+        final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
+        synchronized (this) {
+            modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code);
+            if (modeChangedListenerSet == null) {
+                return;
+            }
+        }
+
+        notifyOpChanged(modeChangedListenerSet, code, uid, null);
+    }
+
+    @Override
+    public void removeUser(int userHandle) throws RemoteException {
+        checkSystemUid("removeUser");
+        synchronized (AppOpsServiceImpl.this) {
+            final int tokenCount = mOpUserRestrictions.size();
+            for (int i = tokenCount - 1; i >= 0; i--) {
+                ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
+                opRestrictions.removeUser(userHandle);
+            }
+            removeUidsForUserLocked(userHandle);
+        }
+    }
+
+    @Override
+    public boolean isOperationActive(int code, int uid, String packageName) {
+        if (Binder.getCallingUid() != uid) {
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                return false;
+            }
+        }
+        verifyIncomingOp(code);
+        if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            return false;
+        }
+
+        final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+        if (resolvedPackageName == null) {
+            return false;
+        }
+        // TODO moltmann: Allow to check for attribution op activeness
+        synchronized (AppOpsServiceImpl.this) {
+            Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
+            if (pkgOps == null) {
+                return false;
+            }
+
+            Op op = pkgOps.get(code);
+            if (op == null) {
+                return false;
+            }
+
+            return op.isRunning();
+        }
+    }
+
+    @Override
+    public boolean isProxying(int op, @NonNull String proxyPackageName,
+            @NonNull String proxyAttributionTag, int proxiedUid,
+            @NonNull String proxiedPackageName) {
+        Objects.requireNonNull(proxyPackageName);
+        Objects.requireNonNull(proxiedPackageName);
+        final long callingUid = Binder.getCallingUid();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
+                    proxiedPackageName, new int[]{op});
+            if (packageOps == null || packageOps.isEmpty()) {
+                return false;
+            }
+            final List<OpEntry> opEntries = packageOps.get(0).getOps();
+            if (opEntries.isEmpty()) {
+                return false;
+            }
+            final OpEntry opEntry = opEntries.get(0);
+            if (!opEntry.isRunning()) {
+                return false;
+            }
+            final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
+                    OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
+            return proxyInfo != null && callingUid == proxyInfo.getUid()
+                    && proxyPackageName.equals(proxyInfo.getPackageName())
+                    && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void resetPackageOpsNoHistory(@NonNull String packageName) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "resetPackageOpsNoHistory");
+        synchronized (AppOpsServiceImpl.this) {
+            final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
+                    UserHandle.getCallingUserId());
+            if (uid == Process.INVALID_UID) {
+                return;
+            }
+            UidState uidState = mUidStates.get(uid);
+            if (uidState == null || uidState.pkgOps == null) {
+                return;
+            }
+            Ops removedOps = uidState.pkgOps.remove(packageName);
+            mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+            if (removedOps != null) {
+                scheduleFastWriteLocked();
+            }
+        }
+    }
+
+    @Override
+    public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
+            long baseSnapshotInterval, int compressionStep) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "setHistoryParameters");
+        // Must not hold the appops lock
+        mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+    }
+
+    @Override
+    public void offsetHistory(long offsetMillis) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "offsetHistory");
+        // Must not hold the appops lock
+        mHistoricalRegistry.offsetHistory(offsetMillis);
+        mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
+    }
+
+    @Override
+    public void addHistoricalOps(HistoricalOps ops) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "addHistoricalOps");
+        // Must not hold the appops lock
+        mHistoricalRegistry.addHistoricalOps(ops);
+    }
+
+    @Override
+    public void resetHistoryParameters() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "resetHistoryParameters");
+        // Must not hold the appops lock
+        mHistoricalRegistry.resetHistoryParameters();
+    }
+
+    @Override
+    public void clearHistory() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "clearHistory");
+        // Must not hold the appops lock
+        mHistoricalRegistry.clearAllHistory();
+    }
+
+    @Override
+    public void rebootHistory(long offlineDurationMillis) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "rebootHistory");
+
+        Preconditions.checkArgument(offlineDurationMillis >= 0);
+
+        // Must not hold the appops lock
+        mHistoricalRegistry.shutdown();
+
+        if (offlineDurationMillis > 0) {
+            SystemClock.sleep(offlineDurationMillis);
+        }
+
+        mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
+        mHistoricalRegistry.systemReady(mContext.getContentResolver());
+        mHistoricalRegistry.persistPendingHistory();
+    }
+
+    @GuardedBy("this")
+    private void removeUidsForUserLocked(int userHandle) {
+        for (int i = mUidStates.size() - 1; i >= 0; --i) {
+            final int uid = mUidStates.keyAt(i);
+            if (UserHandle.getUserId(uid) == userHandle) {
+                mUidStates.valueAt(i).clear();
+                mUidStates.removeAt(i);
+            }
+        }
+    }
+
+    private void checkSystemUid(String function) {
+        int uid = Binder.getCallingUid();
+        if (uid != Process.SYSTEM_UID) {
+            throw new SecurityException(function + " must by called by the system");
+        }
+    }
+
+    private static int resolveUid(String packageName) {
+        if (packageName == null) {
+            return Process.INVALID_UID;
+        }
+        switch (packageName) {
+            case "root":
+                return Process.ROOT_UID;
+            case "shell":
+            case "dumpstate":
+                return Process.SHELL_UID;
+            case "media":
+                return Process.MEDIA_UID;
+            case "audioserver":
+                return Process.AUDIOSERVER_UID;
+            case "cameraserver":
+                return Process.CAMERASERVER_UID;
+        }
+        return Process.INVALID_UID;
+    }
+
+    private static String[] getPackagesForUid(int uid) {
+        String[] packageNames = null;
+
+        // Very early during boot the package manager is not yet or not yet fully started. At this
+        // time there are no packages yet.
+        if (AppGlobals.getPackageManager() != null) {
+            try {
+                packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
+            } catch (RemoteException e) {
+                /* ignore - local call */
+            }
+        }
+        if (packageNames == null) {
+            return EmptyArray.STRING;
+        }
+        return packageNames;
+    }
+
+    private final class ClientUserRestrictionState implements DeathRecipient {
+        private final IBinder mToken;
+
+        ClientUserRestrictionState(IBinder token)
+                throws RemoteException {
+            token.linkToDeath(this, 0);
+            this.mToken = token;
+        }
+
+        public boolean setRestriction(int code, boolean restricted,
+                PackageTagsList excludedPackageTags, int userId) {
+            return mAppOpsRestrictions.setUserRestriction(mToken, userId, code,
+                    restricted, excludedPackageTags);
+        }
+
+        public boolean hasRestriction(int code, String packageName, String attributionTag,
+                int userId, boolean isCheckOp) {
+            return mAppOpsRestrictions.getUserRestriction(mToken, userId, code, packageName,
+                    attributionTag, isCheckOp);
+        }
+
+        public void removeUser(int userId) {
+            mAppOpsRestrictions.clearUserRestrictions(mToken, userId);
+        }
+
+        public boolean isDefault() {
+            return !mAppOpsRestrictions.hasUserRestrictions(mToken);
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (AppOpsServiceImpl.this) {
+                mAppOpsRestrictions.clearUserRestrictions(mToken);
+                mOpUserRestrictions.remove(mToken);
+                destroy();
+            }
+        }
+
+        public void destroy() {
+            mToken.unlinkToDeath(this, 0);
+        }
+    }
+
+    private final class ClientGlobalRestrictionState implements DeathRecipient {
+        final IBinder mToken;
+
+        ClientGlobalRestrictionState(IBinder token)
+                throws RemoteException {
+            token.linkToDeath(this, 0);
+            this.mToken = token;
+        }
+
+        boolean setRestriction(int code, boolean restricted) {
+            return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
+        }
+
+        boolean hasRestriction(int code) {
+            return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
+        }
+
+        boolean isDefault() {
+            return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
+        }
+
+        @Override
+        public void binderDied() {
+            mAppOpsRestrictions.clearGlobalRestrictions(mToken);
+            mOpGlobalRestrictions.remove(mToken);
+            destroy();
+        }
+
+        void destroy() {
+            mToken.unlinkToDeath(this, 0);
+        }
+    }
+
+    @Override
+    public void setDeviceAndProfileOwners(SparseIntArray owners) {
+        synchronized (this) {
+            mProfileOwners = owners;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
index 18f659e..8420fcb 100644
--- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
@@ -13,197 +13,482 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.server.appop;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppOpsManager.Mode;
-import android.util.ArraySet;
-import android.util.SparseBooleanArray;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.AttributionSource;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PackageTagsList;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 
+import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsNotedCallback;
+import com.android.internal.app.IAppOpsStartedCallback;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.List;
 
 /**
- * Interface for accessing and modifying modes for app-ops i.e. package and uid modes.
- * This interface also includes functions for added and removing op mode watchers.
- * In the future this interface will also include op restrictions.
+ *
  */
-public interface AppOpsServiceInterface {
-    /**
-     * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid.
-     * Returns an empty SparseIntArray if nothing is set.
-     * @param uid for which we need the app-ops and their modes.
-     */
-    SparseIntArray getNonDefaultUidModes(int uid);
+public interface AppOpsServiceInterface extends PersistenceScheduler {
 
     /**
-     * Returns the app-op mode for a particular app-op of a uid.
-     * Returns default op mode if the op mode for particular uid and op is not set.
-     * @param uid user id for which we need the mode.
-     * @param op app-op for which we need the mode.
-     * @return mode of the app-op.
-     */
-    int getUidMode(int uid, int op);
-
-    /**
-     * Set the app-op mode for a particular uid and op.
-     * The mode is not set if the mode is the same as the default mode for the op.
-     * @param uid user id for which we want to set the mode.
-     * @param op app-op for which we want to set the mode.
-     * @param mode mode for the app-op.
-     * @return true if op mode is changed.
-     */
-    boolean setUidMode(int uid, int op, @Mode int mode);
-
-    /**
-     * Gets the app-op mode for a particular package.
-     * Returns default op mode if the op mode for the particular package is not set.
-     * @param packageName package name for which we need the op mode.
-     * @param op app-op for which we need the mode.
-     * @param userId user id associated with the package.
-     * @return the mode of the app-op.
-     */
-    int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId);
-
-    /**
-     * Sets the app-op mode for a particular package.
-     * @param packageName package name for which we need to set the op mode.
-     * @param op app-op for which we need to set the mode.
-     * @param mode the mode of the app-op.
-     * @param userId user id associated with the package.
      *
      */
-    void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId);
+    void systemReady();
 
     /**
-     * Stop tracking any app-op modes for a package.
-     * @param packageName Name of the package for which we want to remove all mode tracking.
-     * @param userId user id associated with the package.
+     *
      */
-    boolean removePackage(@NonNull String packageName,  @UserIdInt int userId);
+    void shutdown();
 
     /**
-     * Stop tracking any app-op modes for this uid.
-     * @param uid user id for which we want to remove all tracking.
+     *
+     * @param uid
+     * @param packageName
      */
-    void removeUid(int uid);
+    void verifyPackage(int uid, String packageName);
 
     /**
-     * Returns true if all uid modes for this uid are
-     * in default state.
-     * @param uid user id
+     *
+     * @param op
+     * @param packageName
+     * @param flags
+     * @param callback
      */
-    boolean areUidModesDefault(int uid);
+    void startWatchingModeWithFlags(int op, String packageName, int flags,
+            IAppOpsCallback callback);
 
     /**
-     * Returns true if all package modes for this package name are
-     * in default state.
-     * @param packageName package name.
-     * @param userId user id associated with the package.
+     *
+     * @param callback
      */
-    boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
+    void stopWatchingMode(IAppOpsCallback callback);
 
     /**
-     * Stop tracking app-op modes for all uid and packages.
+     *
+     * @param ops
+     * @param callback
      */
-    void clearAllModes();
+    void startWatchingActive(int[] ops, IAppOpsActiveCallback callback);
 
     /**
-     * Registers changedListener to listen to op's mode change.
-     * @param changedListener the listener that must be trigger on the op's mode change.
-     * @param op op representing the app-op whose mode change needs to be listened to.
+     *
+     * @param callback
      */
-    void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op);
+    void stopWatchingActive(IAppOpsActiveCallback callback);
 
     /**
-     * Registers changedListener to listen to package's app-op's mode change.
-     * @param changedListener the listener that must be trigger on the mode change.
-     * @param packageName of the package whose app-op's mode change needs to be listened to.
+     *
+     * @param ops
+     * @param callback
      */
-    void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
-            @NonNull String packageName);
+    void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback);
 
     /**
-     * Stop the changedListener from triggering on any mode change.
-     * @param changedListener the listener that needs to be removed.
+     *
+     * @param callback
      */
-    void removeListener(@NonNull OnOpModeChangedListener changedListener);
+    void stopWatchingStarted(IAppOpsStartedCallback callback);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Returns a set of OnOpModeChangedListener that are listening for op's mode changes.
-     * @param op app-op whose mode change is being listened to.
+     *
+     * @param ops
+     * @param callback
      */
-    ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op);
+    void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes.
-     * @param packageName of package whose app-op's mode change is being listened to.
+     *
+     * @param callback
      */
-    ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName);
+    void stopWatchingNoted(IAppOpsNotedCallback callback);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Notify that the app-op's mode is changed by triggering the change listener.
-     * @param op App-op whose mode has changed
-     * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users)
+     * @param clientId
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param startIfModeDefault
+     * @param message
+     * @param attributionFlags
+     * @param attributionChainId
+     * @return
      */
-    void notifyWatchersOfChange(int op, int uid);
+    int startOperation(@NonNull IBinder clientId, int code, int uid,
+            @Nullable String packageName, @Nullable String attributionTag,
+            boolean startIfModeDefault, @NonNull String message,
+            @AppOpsManager.AttributionFlags int attributionFlags,
+            int attributionChainId);
+
+
+    int startOperationUnchecked(IBinder clientId, int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+            @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags,
+            boolean startIfModeDefault, @AppOpsManager.AttributionFlags int attributionFlags,
+            int attributionChainId, boolean dryRun);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Notify that the app-op's mode is changed by triggering the change listener.
-     * @param changedListener the change listener.
-     * @param op App-op whose mode has changed
-     * @param uid user id associated with the app-op
-     * @param packageName package name that is associated with the app-op
+     *
+     * @param clientId
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
      */
-    void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
-            @Nullable String packageName);
+    void finishOperation(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Notify that the app-op's mode is changed to all packages associated with the uid by
-     * triggering the appropriate change listener.
-     * @param op App-op whose mode has changed
-     * @param uid user id associated with the app-op
-     * @param onlyForeground true if only watchers that
-     * @param callbackToIgnore callback that should be ignored.
+     *
+     * @param clientId
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
      */
-    void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
-            @Nullable OnOpModeChangedListener callbackToIgnore);
+    void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag);
 
     /**
-     * TODO: Move hasForegroundWatchers and foregroundOps into this.
-     * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in
-     * foregroundOps.
-     * @param uid for which the app-op's mode needs to be marked.
-     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
-     * @return  foregroundOps.
+     *
+     * @param uidPackageNames
+     * @param visible
      */
-    SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+    void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible);
 
     /**
-     * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
-     * foregroundOps.
-     * @param packageName for which the app-op's mode needs to be marked.
-     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
-     * @param userId user id associated with the package.
-     * @return foregroundOps.
+     *
      */
-    SparseBooleanArray evalForegroundPackageOps(String packageName,
-            SparseBooleanArray foregroundOps, @UserIdInt int userId);
+    void readState();
 
     /**
-     * Dump op mode and package mode listeners and their details.
-     * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an
-     *               app-op, only the watchers for that app-op are dumped.
-     * @param dumpUid uid for which we want to dump op mode watchers.
-     * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
-     * @param printWriter writer to dump to.
+     *
      */
-    boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+    void writeState();
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     */
+    void packageRemoved(int uid, String packageName);
+
+    /**
+     *
+     * @param uid
+     */
+    void uidRemoved(int uid);
+
+    /**
+     *
+     * @param uid
+     * @param procState
+     * @param capability
+     */
+    void updateUidProcState(int uid, int procState,
+            @ActivityManager.ProcessCapability int capability);
+
+    /**
+     *
+     * @param ops
+     * @return
+     */
+    List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops);
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     * @param ops
+     * @return
+     */
+    List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
+            int[] ops);
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param opNames
+     * @param dataType
+     * @param filter
+     * @param beginTimeMillis
+     * @param endTimeMillis
+     * @param flags
+     * @param callback
+     */
+    void getHistoricalOps(int uid, String packageName, String attributionTag,
+            List<String> opNames, int dataType, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, RemoteCallback callback);
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param opNames
+     * @param dataType
+     * @param filter
+     * @param beginTimeMillis
+     * @param endTimeMillis
+     * @param flags
+     * @param callback
+     */
+    void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
+            List<String> opNames, int dataType, int filter, long beginTimeMillis,
+            long endTimeMillis, int flags, RemoteCallback callback);
+
+    /**
+     *
+     */
+    void reloadNonHistoricalState();
+
+    /**
+     *
+     * @param uid
+     * @param ops
+     * @return
+     */
+    List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops);
+
+    /**
+     *
+     * @param owners
+     */
+    void setDeviceAndProfileOwners(SparseIntArray owners);
+
+    // used in audio restriction calls, might just copy the logic to avoid having this call.
+    /**
+     *
+     * @param callingPid
+     * @param callingUid
+     * @param targetUid
+     */
+    void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param mode
+     * @param permissionPolicyCallback
+     */
+    void setUidMode(int code, int uid, int mode,
+            @Nullable IAppOpsCallback permissionPolicyCallback);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param mode
+     * @param permissionPolicyCallback
+     */
+    void setMode(int code, int uid, @NonNull String packageName, int mode,
+            @Nullable IAppOpsCallback permissionPolicyCallback);
+
+    /**
+     *
+     * @param reqUserId
+     * @param reqPackageName
+     */
+    void resetAllModes(int reqUserId, String reqPackageName);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param raw
+     * @return
+     */
+    int checkOperation(int code, int uid, String packageName,
+            @Nullable String attributionTag, boolean raw);
+
+    /**
+     *
+     * @param uid
+     * @param packageName
+     * @return
+     */
+    int checkPackage(int uid, String packageName);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param message
+     * @return
+     */
+    int noteOperation(int code, int uid, @Nullable String packageName,
+            @Nullable String attributionTag, @Nullable String message);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @param attributionTag
+     * @param proxyUid
+     * @param proxyPackageName
+     * @param proxyAttributionTag
+     * @param flags
+     * @return
+     */
+    @AppOpsManager.Mode
+    int noteOperationUnchecked(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+            @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags);
+
+    boolean isAttributionTagValid(int uid, @NonNull String packageName,
+            @Nullable String attributionTag, @Nullable String proxyPackageName);
+
+    /**
+     *
+     * @param fd
+     * @param pw
+     * @param args
+     */
+    @NeverCompile
+        // Avoid size overhead of debugging code.
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+
+    /**
+     *
+     * @param restrictions
+     * @param token
+     * @param userHandle
+     */
+    void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle);
+
+    /**
+     *
+     * @param code
+     * @param restricted
+     * @param token
+     * @param userHandle
+     * @param excludedPackageTags
+     */
+    void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
+            PackageTagsList excludedPackageTags);
+
+    /**
+     *
+     * @param code
+     * @param restricted
+     * @param token
+     */
+    void setGlobalRestriction(int code, boolean restricted, IBinder token);
+
+    /**
+     *
+     * @param code
+     * @param user
+     * @param pkg
+     * @param attributionTag
+     * @return
+     */
+    int getOpRestrictionCount(int code, UserHandle user, String pkg,
+            String attributionTag);
+
+    /**
+     *
+     * @param code
+     * @param uid
+     */
+    // added to interface for audio restriction stuff
+    void notifyWatchersOfChange(int code, int uid);
+
+    /**
+     *
+     * @param userHandle
+     * @throws RemoteException
+     */
+    void removeUser(int userHandle) throws RemoteException;
+
+    /**
+     *
+     * @param code
+     * @param uid
+     * @param packageName
+     * @return
+     */
+    boolean isOperationActive(int code, int uid, String packageName);
+
+    /**
+     *
+     * @param op
+     * @param proxyPackageName
+     * @param proxyAttributionTag
+     * @param proxiedUid
+     * @param proxiedPackageName
+     * @return
+     */
+    // TODO this one might not need to be in the interface
+    boolean isProxying(int op, @NonNull String proxyPackageName,
+            @NonNull String proxyAttributionTag, int proxiedUid,
+            @NonNull String proxiedPackageName);
+
+    /**
+     *
+     * @param packageName
+     */
+    void resetPackageOpsNoHistory(@NonNull String packageName);
+
+    /**
+     *
+     * @param mode
+     * @param baseSnapshotInterval
+     * @param compressionStep
+     */
+    void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
+            long baseSnapshotInterval, int compressionStep);
+
+    /**
+     *
+     * @param offsetMillis
+     */
+    void offsetHistory(long offsetMillis);
+
+    /**
+     *
+     * @param ops
+     */
+    void addHistoricalOps(AppOpsManager.HistoricalOps ops);
+
+    /**
+     *
+     */
+    void resetHistoryParameters();
+
+    /**
+     *
+     */
+    void clearHistory();
+
+    /**
+     *
+     * @param offlineDurationMillis
+     */
+    void rebootHistory(long offlineDurationMillis);
 }
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index 3c281d1..c1434e4 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -26,6 +26,7 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
 import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
@@ -58,7 +59,7 @@
     private final DelayableExecutor mExecutor;
     private final Clock mClock;
     private ActivityManagerInternal mActivityManagerInternal;
-    private AppOpsService.Constants mConstants;
+    private AppOpsServiceImpl.Constants mConstants;
 
     private SparseIntArray mUidStates = new SparseIntArray();
     private SparseIntArray mPendingUidStates = new SparseIntArray();
@@ -84,7 +85,7 @@
 
     AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
             Handler handler, Executor lockingExecutor, Clock clock,
-            AppOpsService.Constants constants) {
+            AppOpsServiceImpl.Constants constants) {
 
         this(activityManagerInternal, new DelayableExecutor() {
             @Override
@@ -101,7 +102,7 @@
 
     @VisibleForTesting
     AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
-            DelayableExecutor executor, Clock clock, AppOpsService.Constants constants,
+            DelayableExecutor executor, Clock clock, AppOpsServiceImpl.Constants constants,
             Thread executorThread) {
         mActivityManagerInternal = activityManagerInternal;
         mExecutor = executor;
@@ -171,6 +172,7 @@
                     return MODE_ALLOWED;
                 }
             case OP_RECORD_AUDIO:
+            case OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO:
                 if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) == 0) {
                     return MODE_IGNORED;
                 } else {
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index dcc36bc..7970269 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -40,9 +40,9 @@
 import java.util.NoSuchElementException;
 
 final class AttributedOp {
-    private final @NonNull AppOpsService mAppOpsService;
+    private final @NonNull AppOpsServiceImpl mAppOpsService;
     public final @Nullable String tag;
-    public final @NonNull AppOpsService.Op parent;
+    public final @NonNull AppOpsServiceImpl.Op parent;
 
     /**
      * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination
@@ -80,8 +80,8 @@
     // @GuardedBy("mAppOpsService")
     @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
 
-    AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag,
-            @NonNull AppOpsService.Op parent) {
+    AttributedOp(@NonNull AppOpsServiceImpl appOpsService, @Nullable String tag,
+                 @NonNull AppOpsServiceImpl.Op parent) {
         mAppOpsService = appOpsService;
         this.tag = tag;
         this.parent = parent;
@@ -131,8 +131,8 @@
 
         AppOpsManager.OpEventProxyInfo proxyInfo = null;
         if (proxyUid != Process.INVALID_UID) {
-            proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
-                    proxyAttributionTag);
+            proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid,
+                    proxyPackageName, proxyAttributionTag);
         }
 
         AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -238,7 +238,7 @@
         if (event == null) {
             event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
                     SystemClock.elapsedRealtime(), clientId, tag,
-                    PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
+                    PooledLambda.obtainRunnable(AppOpsServiceImpl::onClientDeath, this, clientId),
                     proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
                     attributionFlags, attributionChainId);
             events.put(clientId, event);
@@ -251,9 +251,9 @@
         event.mNumUnfinishedStarts++;
 
         if (isStarted) {
-            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
-                    parent.packageName, tag, uidState, flags, startTime, attributionFlags,
-                    attributionChainId);
+            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op,
+                    parent.uid, parent.packageName, tag, uidState, flags, startTime,
+                    attributionFlags, attributionChainId);
         }
     }
 
@@ -309,8 +309,8 @@
             mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
                     finishedEvent);
 
-            mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
-                    parent.packageName, tag, event.getUidState(),
+            mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op,
+                    parent.uid, parent.packageName, tag, event.getUidState(),
                     event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
                     event.getAttributionFlags(), event.getAttributionChainId());
 
@@ -334,13 +334,13 @@
     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
     private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
         if (!isPaused()) {
-            Slog.wtf(AppOpsService.TAG, "No ops running or paused");
+            Slog.wtf(AppOpsServiceImpl.TAG, "No ops running or paused");
             return;
         }
 
         int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId);
         if (indexOfToken < 0) {
-            Slog.wtf(AppOpsService.TAG, "No op running or paused for the client");
+            Slog.wtf(AppOpsServiceImpl.TAG, "No op running or paused for the client");
             return;
         } else if (isPausing) {
             // already paused
@@ -416,9 +416,9 @@
             mInProgressEvents.put(event.getClientId(), event);
             event.setStartElapsedTime(SystemClock.elapsedRealtime());
             event.setStartTime(startTime);
-            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
-                    parent.packageName, tag, event.getUidState(), event.getFlags(), startTime,
-                    event.getAttributionFlags(), event.getAttributionChainId());
+            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op,
+                    parent.uid, parent.packageName, tag, event.getUidState(), event.getFlags(),
+                    startTime, event.getAttributionFlags(), event.getAttributionChainId());
             if (shouldSendActive) {
                 mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
                         parent.packageName, tag, true, event.getAttributionFlags(),
@@ -503,8 +503,8 @@
                         newEvent.mNumUnfinishedStarts += numPreviousUnfinishedStarts - 1;
                     }
                 } catch (RemoteException e) {
-                    if (AppOpsService.DEBUG) {
-                        Slog.e(AppOpsService.TAG,
+                    if (AppOpsServiceImpl.DEBUG) {
+                        Slog.e(AppOpsServiceImpl.TAG,
                                 "Cannot switch to new uidState " + newState);
                     }
                 }
@@ -555,8 +555,8 @@
             ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents =
                     opToAdd.isRunning()
                             ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents;
-            Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: "
-                    + opToAdd.isRunning());
+            Slog.w(AppOpsServiceImpl.TAG, "Ignoring " + ignoredEvents.size()
+                    + " app-ops, running: " + opToAdd.isRunning());
 
             int numInProgressEvents = ignoredEvents.size();
             for (int i = 0; i < numInProgressEvents; i++) {
@@ -668,16 +668,22 @@
         /**
          * Create a new {@link InProgressStartOpEvent}.
          *
-         * @param startTime          The time {@link #startOperation} was called
-         * @param startElapsedTime   The elapsed time when {@link #startOperation} was called
-         * @param clientId           The client id of the caller of {@link #startOperation}
+         * @param startTime          The time {@link AppOpCheckingServiceInterface#startOperation}
+         *                          was called
+         * @param startElapsedTime   The elapsed time whe
+         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
+         * @param clientId           The client id of the caller of
+         *                          {@link AppOpCheckingServiceInterface#startOperation}
          * @param attributionTag     The attribution tag for the operation.
          * @param onDeath            The code to execute on client death
-         * @param uidState           The uidstate of the app {@link #startOperation} was called for
+         * @param uidState           The uidstate of the app
+         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
+         *                          for
          * @param attributionFlags   the attribution flags for this operation.
          * @param attributionChainId the unique id of the attribution chain this op is a part of.
-         * @param proxy              The proxy information, if {@link #startProxyOperation} was
-         *                           called
+         * @param proxy              The proxy information, if
+         *                          {@link AppOpCheckingServiceInterface#startProxyOperation} was
+         *                          called
          * @param flags              The trusted/nontrusted/self flags.
          * @throws RemoteException If the client is dying
          */
@@ -718,15 +724,21 @@
         /**
          * Reinit existing object with new state.
          *
-         * @param startTime          The time {@link #startOperation} was called
-         * @param startElapsedTime   The elapsed time when {@link #startOperation} was called
-         * @param clientId           The client id of the caller of {@link #startOperation}
+         * @param startTime          The time {@link AppOpCheckingServiceInterface#startOperation}
+         *                          was called
+         * @param startElapsedTime   The elapsed time when
+         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
+         * @param clientId           The client id of the caller of
+         *                          {@link AppOpCheckingServiceInterface#startOperation}
          * @param attributionTag     The attribution tag for this operation.
          * @param onDeath            The code to execute on client death
-         * @param uidState           The uidstate of the app {@link #startOperation} was called for
+         * @param uidState           The uidstate of the app
+         *                          {@link AppOpCheckingServiceInterface#startOperation} was called
+         *                          for
          * @param flags              The flags relating to the proxy
-         * @param proxy              The proxy information, if {@link #startProxyOperation}
-         *                           was called
+         * @param proxy              The proxy information, if
+         *                          {@link AppOpCheckingServiceInterface#startProxyOperation was
+         *                          called
          * @param attributionFlags   the attribution flags for this operation.
          * @param attributionChainId the unique id of the attribution chain this op is a part of.
          * @param proxyPool          The pool to release
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
deleted file mode 100644
index f6fff35..0000000
--- a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
+++ /dev/null
@@ -1,601 +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.appop;
-
-import static android.app.AppOpsManager.OP_NONE;
-import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
-import static android.app.AppOpsManager.opRestrictsRead;
-
-import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppGlobals;
-import android.app.AppOpsManager;
-import android.app.AppOpsManager.Mode;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.function.pooled.PooledLambda;
-
-import libcore.util.EmptyArray;
-
-import java.io.PrintWriter;
-import java.util.Collections;
-import java.util.Objects;
-
-
-/**
- * Legacy implementation for App-ops service's app-op mode (uid and package) storage and access.
- * In the future this class will also include mode callbacks and op restrictions.
- */
-public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface {
-
-    static final String TAG = "LegacyAppOpsServiceInterfaceImpl";
-
-    // Must be the same object that the AppOpsService is using for locking.
-    final Object mLock;
-    final Handler mHandler;
-    final Context mContext;
-    final SparseArray<int[]> mSwitchedOps;
-
-    @GuardedBy("mLock")
-    @VisibleForTesting
-    final SparseArray<SparseIntArray> mUidModes = new SparseArray<>();
-
-    @GuardedBy("mLock")
-    final SparseArray<ArrayMap<String, SparseIntArray>> mUserPackageModes = new SparseArray<>();
-
-    final SparseArray<ArraySet<OnOpModeChangedListener>> mOpModeWatchers = new SparseArray<>();
-    final ArrayMap<String, ArraySet<OnOpModeChangedListener>> mPackageModeWatchers =
-            new ArrayMap<>();
-
-    final PersistenceScheduler mPersistenceScheduler;
-
-
-    // Constant meaning that any UID should be matched when dispatching callbacks
-    private static final int UID_ANY = -2;
-
-
-    LegacyAppOpsServiceInterfaceImpl(PersistenceScheduler persistenceScheduler,
-            @NonNull Object lock, Handler handler, Context context,
-            SparseArray<int[]> switchedOps) {
-        this.mPersistenceScheduler = persistenceScheduler;
-        this.mLock = lock;
-        this.mHandler = handler;
-        this.mContext = context;
-        this.mSwitchedOps = switchedOps;
-    }
-
-    @Override
-    public SparseIntArray getNonDefaultUidModes(int uid) {
-        synchronized (mLock) {
-            SparseIntArray opModes = mUidModes.get(uid, null);
-            if (opModes == null) {
-                return new SparseIntArray();
-            }
-            return opModes.clone();
-        }
-    }
-
-    @Override
-    public int getUidMode(int uid, int op) {
-        synchronized (mLock) {
-            SparseIntArray opModes = mUidModes.get(uid, null);
-            if (opModes == null) {
-                return AppOpsManager.opToDefaultMode(op);
-            }
-            return opModes.get(op, AppOpsManager.opToDefaultMode(op));
-        }
-    }
-
-    @Override
-    public boolean setUidMode(int uid, int op, int mode) {
-        final int defaultMode = AppOpsManager.opToDefaultMode(op);
-        synchronized (mLock) {
-            SparseIntArray opModes = mUidModes.get(uid, null);
-            if (opModes == null) {
-                if (mode != defaultMode) {
-                    opModes = new SparseIntArray();
-                    mUidModes.put(uid, opModes);
-                    opModes.put(op, mode);
-                    mPersistenceScheduler.scheduleWriteLocked();
-                }
-            } else {
-                if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) {
-                    return false;
-                }
-                if (mode == defaultMode) {
-                    opModes.delete(op);
-                    if (opModes.size() <= 0) {
-                        opModes = null;
-                        mUidModes.delete(uid);
-                    }
-                } else {
-                    opModes.put(op, mode);
-                }
-                mPersistenceScheduler.scheduleWriteLocked();
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public int getPackageMode(String packageName, int op, @UserIdInt int userId) {
-        synchronized (mLock) {
-            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
-            if (packageModes == null) {
-                return AppOpsManager.opToDefaultMode(op);
-            }
-            SparseIntArray opModes = packageModes.getOrDefault(packageName, null);
-            if (opModes == null) {
-                return AppOpsManager.opToDefaultMode(op);
-            }
-            return opModes.get(op, AppOpsManager.opToDefaultMode(op));
-        }
-    }
-
-    @Override
-    public void setPackageMode(String packageName, int op, @Mode int mode, @UserIdInt int userId) {
-        final int defaultMode = AppOpsManager.opToDefaultMode(op);
-        synchronized (mLock) {
-            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
-            if (packageModes == null) {
-                packageModes = new ArrayMap<>();
-                mUserPackageModes.put(userId, packageModes);
-            }
-            SparseIntArray opModes = packageModes.get(packageName);
-            if (opModes == null) {
-                if (mode != defaultMode) {
-                    opModes = new SparseIntArray();
-                    packageModes.put(packageName, opModes);
-                    opModes.put(op, mode);
-                    mPersistenceScheduler.scheduleWriteLocked();
-                }
-            } else {
-                if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) {
-                    return;
-                }
-                if (mode == defaultMode) {
-                    opModes.delete(op);
-                    if (opModes.size() <= 0) {
-                        opModes = null;
-                        packageModes.remove(packageName);
-                    }
-                } else {
-                    opModes.put(op, mode);
-                }
-                mPersistenceScheduler.scheduleWriteLocked();
-            }
-        }
-    }
-
-    @Override
-    public void removeUid(int uid) {
-        synchronized (mLock) {
-            SparseIntArray opModes = mUidModes.get(uid);
-            if (opModes == null) {
-                return;
-            }
-            mUidModes.remove(uid);
-            mPersistenceScheduler.scheduleFastWriteLocked();
-        }
-    }
-
-    @Override
-    public boolean areUidModesDefault(int uid) {
-        synchronized (mLock) {
-            SparseIntArray opModes = mUidModes.get(uid);
-            return (opModes == null || opModes.size() <= 0);
-        }
-    }
-
-    @Override
-    public boolean arePackageModesDefault(String packageMode, @UserIdInt int userId) {
-        synchronized (mLock) {
-            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
-            if (packageModes == null) {
-                return true;
-            }
-            SparseIntArray opModes = packageModes.get(packageMode);
-            return (opModes == null || opModes.size() <= 0);
-        }
-    }
-
-    @Override
-    public boolean removePackage(String packageName, @UserIdInt int userId) {
-        synchronized (mLock) {
-            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
-            if (packageModes == null) {
-                return false;
-            }
-            SparseIntArray ops = packageModes.remove(packageName);
-            if (ops != null) {
-                mPersistenceScheduler.scheduleFastWriteLocked();
-                return true;
-            }
-            return false;
-        }
-    }
-
-    @Override
-    public void clearAllModes() {
-        synchronized (mLock) {
-            mUidModes.clear();
-            mUserPackageModes.clear();
-        }
-    }
-
-    @Override
-    public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener,
-            int op) {
-        Objects.requireNonNull(changedListener);
-        synchronized (mLock) {
-            ArraySet<OnOpModeChangedListener> modeWatcherSet = mOpModeWatchers.get(op);
-            if (modeWatcherSet == null) {
-                modeWatcherSet = new ArraySet<>();
-                mOpModeWatchers.put(op, modeWatcherSet);
-            }
-            modeWatcherSet.add(changedListener);
-        }
-    }
-
-    @Override
-    public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
-            @NonNull String packageName) {
-        Objects.requireNonNull(changedListener);
-        Objects.requireNonNull(packageName);
-        synchronized (mLock) {
-            ArraySet<OnOpModeChangedListener> modeWatcherSet =
-                    mPackageModeWatchers.get(packageName);
-            if (modeWatcherSet == null) {
-                modeWatcherSet = new ArraySet<>();
-                mPackageModeWatchers.put(packageName, modeWatcherSet);
-            }
-            modeWatcherSet.add(changedListener);
-        }
-    }
-
-    @Override
-    public void removeListener(@NonNull OnOpModeChangedListener changedListener) {
-        Objects.requireNonNull(changedListener);
-
-        synchronized (mLock) {
-            for (int i = mOpModeWatchers.size() - 1; i >= 0; i--) {
-                ArraySet<OnOpModeChangedListener> cbs = mOpModeWatchers.valueAt(i);
-                cbs.remove(changedListener);
-                if (cbs.size() <= 0) {
-                    mOpModeWatchers.removeAt(i);
-                }
-            }
-
-            for (int i = mPackageModeWatchers.size() - 1; i >= 0; i--) {
-                ArraySet<OnOpModeChangedListener> cbs = mPackageModeWatchers.valueAt(i);
-                cbs.remove(changedListener);
-                if (cbs.size() <= 0) {
-                    mPackageModeWatchers.removeAt(i);
-                }
-            }
-        }
-    }
-
-    @Override
-    public ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op) {
-        synchronized (mLock) {
-            ArraySet<OnOpModeChangedListener> modeChangedListenersSet = mOpModeWatchers.get(op);
-            if (modeChangedListenersSet == null) {
-                return new ArraySet<>();
-            }
-            return new ArraySet<>(modeChangedListenersSet);
-        }
-    }
-
-    @Override
-    public ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(
-            @NonNull String packageName) {
-        Objects.requireNonNull(packageName);
-
-        synchronized (mLock) {
-            ArraySet<OnOpModeChangedListener> modeChangedListenersSet =
-                    mPackageModeWatchers.get(packageName);
-            if (modeChangedListenersSet == null) {
-                return new ArraySet<>();
-            }
-            return new ArraySet<>(modeChangedListenersSet);
-        }
-    }
-
-    @Override
-    public void notifyWatchersOfChange(int code, int uid) {
-        ArraySet<OnOpModeChangedListener> listenerSet = getOpModeChangedListeners(code);
-        if (listenerSet == null) {
-            return;
-        }
-        for (int i = 0; i < listenerSet.size(); i++) {
-            final OnOpModeChangedListener listener = listenerSet.valueAt(i);
-            notifyOpChanged(listener, code, uid, null);
-        }
-    }
-
-    @Override
-    public void notifyOpChanged(@NonNull OnOpModeChangedListener onModeChangedListener, int code,
-            int uid, @Nullable String packageName) {
-        Objects.requireNonNull(onModeChangedListener);
-
-        if (uid != UID_ANY && onModeChangedListener.getWatchingUid() >= 0
-                && onModeChangedListener.getWatchingUid() != uid) {
-            return;
-        }
-
-        // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
-        int[] switchedCodes;
-        if (onModeChangedListener.getWatchedOpCode() == ALL_OPS) {
-            switchedCodes = mSwitchedOps.get(code);
-        } else if (onModeChangedListener.getWatchedOpCode() == OP_NONE) {
-            switchedCodes = new int[]{code};
-        } else {
-            switchedCodes = new int[]{onModeChangedListener.getWatchedOpCode()};
-        }
-
-        for (int switchedCode : switchedCodes) {
-            // There are features watching for mode changes such as window manager
-            // and location manager which are in our process. The callbacks in these
-            // features may require permissions our remote caller does not have.
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                if (shouldIgnoreCallback(switchedCode, onModeChangedListener.getCallingPid(),
-                        onModeChangedListener.getCallingUid())) {
-                    continue;
-                }
-                onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName);
-            } catch (RemoteException e) {
-                /* ignore */
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-    }
-
-    private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
-        // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
-        // as watcher should not use this to signal if the value is changed.
-        return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
-                watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
-    }
-
-    @Override
-    public void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
-            @Nullable OnOpModeChangedListener callbackToIgnore) {
-        String[] uidPackageNames = getPackagesForUid(uid);
-        ArrayMap<OnOpModeChangedListener, ArraySet<String>> callbackSpecs = null;
-
-        synchronized (mLock) {
-            ArraySet<OnOpModeChangedListener> callbacks = mOpModeWatchers.get(code);
-            if (callbacks != null) {
-                final int callbackCount = callbacks.size();
-                for (int i = 0; i < callbackCount; i++) {
-                    OnOpModeChangedListener callback = callbacks.valueAt(i);
-
-                    if (onlyForeground && (callback.getFlags()
-                            & WATCH_FOREGROUND_CHANGES) == 0) {
-                        continue;
-                    }
-
-                    ArraySet<String> changedPackages = new ArraySet<>();
-                    Collections.addAll(changedPackages, uidPackageNames);
-                    if (callbackSpecs == null) {
-                        callbackSpecs = new ArrayMap<>();
-                    }
-                    callbackSpecs.put(callback, changedPackages);
-                }
-            }
-
-            for (String uidPackageName : uidPackageNames) {
-                callbacks = mPackageModeWatchers.get(uidPackageName);
-                if (callbacks != null) {
-                    if (callbackSpecs == null) {
-                        callbackSpecs = new ArrayMap<>();
-                    }
-                    final int callbackCount = callbacks.size();
-                    for (int i = 0; i < callbackCount; i++) {
-                        OnOpModeChangedListener callback = callbacks.valueAt(i);
-
-                        if (onlyForeground && (callback.getFlags()
-                                & WATCH_FOREGROUND_CHANGES) == 0) {
-                            continue;
-                        }
-
-                        ArraySet<String> changedPackages = callbackSpecs.get(callback);
-                        if (changedPackages == null) {
-                            changedPackages = new ArraySet<>();
-                            callbackSpecs.put(callback, changedPackages);
-                        }
-                        changedPackages.add(uidPackageName);
-                    }
-                }
-            }
-
-            if (callbackSpecs != null && callbackToIgnore != null) {
-                callbackSpecs.remove(callbackToIgnore);
-            }
-        }
-
-        if (callbackSpecs == null) {
-            return;
-        }
-
-        for (int i = 0; i < callbackSpecs.size(); i++) {
-            final OnOpModeChangedListener callback = callbackSpecs.keyAt(i);
-            final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
-            if (reportedPackageNames == null) {
-                mHandler.sendMessage(PooledLambda.obtainMessage(
-                        LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
-                        this, callback, code, uid, (String) null));
-
-            } else {
-                final int reportedPackageCount = reportedPackageNames.size();
-                for (int j = 0; j < reportedPackageCount; j++) {
-                    final String reportedPackageName = reportedPackageNames.valueAt(j);
-                    mHandler.sendMessage(PooledLambda.obtainMessage(
-                            LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
-                            this, callback, code, uid, reportedPackageName));
-                }
-            }
-        }
-    }
-
-    private static String[] getPackagesForUid(int uid) {
-        String[] packageNames = null;
-
-        // Very early during boot the package manager is not yet or not yet fully started. At this
-        // time there are no packages yet.
-        if (AppGlobals.getPackageManager() != null) {
-            try {
-                packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
-            } catch (RemoteException e) {
-                /* ignore - local call */
-            }
-        }
-        if (packageNames == null) {
-            return EmptyArray.STRING;
-        }
-        return packageNames;
-    }
-
-    @Override
-    public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
-        synchronized (mLock) {
-            return evalForegroundOps(mUidModes.get(uid), foregroundOps);
-        }
-    }
-
-    @Override
-    public SparseBooleanArray evalForegroundPackageOps(String packageName,
-            SparseBooleanArray foregroundOps, @UserIdInt int userId) {
-        synchronized (mLock) {
-            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
-            return evalForegroundOps(packageModes == null ? null : packageModes.get(packageName),
-                    foregroundOps);
-        }
-    }
-
-    private SparseBooleanArray evalForegroundOps(SparseIntArray opModes,
-            SparseBooleanArray foregroundOps) {
-        SparseBooleanArray tempForegroundOps = foregroundOps;
-        if (opModes != null) {
-            for (int i = opModes.size() - 1; i >= 0; i--) {
-                if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) {
-                    if (tempForegroundOps == null) {
-                        tempForegroundOps = new SparseBooleanArray();
-                    }
-                    evalForegroundWatchers(opModes.keyAt(i), tempForegroundOps);
-                }
-            }
-        }
-        return tempForegroundOps;
-    }
-
-    private void evalForegroundWatchers(int op, SparseBooleanArray foregroundOps) {
-        boolean curValue = foregroundOps.get(op, false);
-        ArraySet<OnOpModeChangedListener> listenerSet = mOpModeWatchers.get(op);
-        if (listenerSet != null) {
-            for (int cbi = listenerSet.size() - 1; !curValue && cbi >= 0; cbi--) {
-                if ((listenerSet.valueAt(cbi).getFlags()
-                        & AppOpsManager.WATCH_FOREGROUND_CHANGES) != 0) {
-                    curValue = true;
-                }
-            }
-        }
-        foregroundOps.put(op, curValue);
-    }
-
-    @Override
-    public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
-            PrintWriter printWriter) {
-        boolean needSep = false;
-        if (mOpModeWatchers.size() > 0) {
-            boolean printedHeader = false;
-            for (int i = 0; i < mOpModeWatchers.size(); i++) {
-                if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) {
-                    continue;
-                }
-                boolean printedOpHeader = false;
-                ArraySet<OnOpModeChangedListener> modeChangedListenerSet =
-                        mOpModeWatchers.valueAt(i);
-                for (int j = 0; j < modeChangedListenerSet.size(); j++) {
-                    final OnOpModeChangedListener listener = modeChangedListenerSet.valueAt(j);
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(listener.getWatchingUid())) {
-                        continue;
-                    }
-                    needSep = true;
-                    if (!printedHeader) {
-                        printWriter.println("  Op mode watchers:");
-                        printedHeader = true;
-                    }
-                    if (!printedOpHeader) {
-                        printWriter.print("    Op ");
-                        printWriter.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
-                        printWriter.println(":");
-                        printedOpHeader = true;
-                    }
-                    printWriter.print("      #"); printWriter.print(j); printWriter.print(": ");
-                    printWriter.println(listener.toString());
-                }
-            }
-        }
-
-        if (mPackageModeWatchers.size() > 0 && dumpOp < 0) {
-            boolean printedHeader = false;
-            for (int i = 0; i < mPackageModeWatchers.size(); i++) {
-                if (dumpPackage != null
-                        && !dumpPackage.equals(mPackageModeWatchers.keyAt(i))) {
-                    continue;
-                }
-                needSep = true;
-                if (!printedHeader) {
-                    printWriter.println("  Package mode watchers:");
-                    printedHeader = true;
-                }
-                printWriter.print("    Pkg "); printWriter.print(mPackageModeWatchers.keyAt(i));
-                printWriter.println(":");
-                ArraySet<OnOpModeChangedListener> modeChangedListenerSet =
-                        mPackageModeWatchers.valueAt(i);
-
-                for (int j = 0; j < modeChangedListenerSet.size(); j++) {
-                    printWriter.print("      #"); printWriter.print(j); printWriter.print(": ");
-                    printWriter.println(modeChangedListenerSet.valueAt(j).toString());
-                }
-            }
-        }
-        return needSep;
-    }
-
-}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
index 5ebe811..1d1a9e7 100644
--- a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
+++ b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
@@ -22,7 +22,7 @@
  * Listener for mode changes, encapsulates methods that should be triggered in the event of a mode
  * change.
  */
-abstract class OnOpModeChangedListener {
+public abstract class OnOpModeChangedListener {
 
     // Constant meaning that any UID should be matched when dispatching callbacks
     private static final int UID_ANY = -2;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index bbffc89..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;
     }
 
@@ -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;
         }
     }
 
@@ -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) {
@@ -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.enqueue((new EventLogger.StringEvent(
-                "onUpdateCommunicationRoute, preferredCommunicationDevice: "
+                "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/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9d6fa9e..58cf7ef 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;
@@ -85,6 +86,7 @@
 import android.media.AudioFocusInfo;
 import android.media.AudioFocusRequest;
 import android.media.AudioFormat;
+import android.media.AudioHalVersionInfo;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioPlaybackConfiguration;
@@ -153,6 +155,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 +177,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 +237,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;
@@ -253,6 +258,9 @@
     /** Debug communication route */
     protected static final boolean DEBUG_COMM_RTE = false;
 
+    /** Debug log sound fx (touchsounds...) in dumpsys */
+    protected static final boolean DEBUG_LOG_SOUND_FX = false;
+
     /** How long to delay before persisting a change in volume/ringer mode. */
     private static final int PERSIST_DELAY = 500;
 
@@ -374,6 +382,7 @@
     private static final int MSG_ROTATION_UPDATE = 48;
     private static final int MSG_FOLD_UPDATE = 49;
     private static final int MSG_RESET_SPATIALIZER = 50;
+    private static final int MSG_NO_LOG_FOR_PLAYER_I = 51;
 
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -987,6 +996,7 @@
      * @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) {
@@ -1011,7 +1021,7 @@
         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent");
 
-        mSfxHelper = new SoundEffectsHelper(mContext);
+        mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase));
 
         final boolean headTrackingDefault = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default);
@@ -1026,8 +1036,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 +1260,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() {
@@ -1475,6 +1505,18 @@
     }
 
     //-----------------------------------------------------------------
+    // Communicate to PlayackActivityMonitor whether to log or not
+    // the sound FX activity (useful for removing touch sounds in the activity logs)
+    void ignorePlayerLogs(@NonNull PlayerBase playerToIgnore) {
+        if (DEBUG_LOG_SOUND_FX) {
+            return;
+        }
+        sendMsg(mAudioHandler, MSG_NO_LOG_FOR_PLAYER_I, SENDMSG_REPLACE,
+                /*arg1, piid of the player*/ playerToIgnore.getPlayerIId(),
+                /*arg2 ignored*/ 0, /*obj ignored*/ null, /*delay*/ 0);
+    }
+
+    //-----------------------------------------------------------------
     // monitoring requests for volume range initialization
     @Override // AudioSystemAdapter.OnVolRangeInitRequestListener
     public void onVolumeRangeInitRequestFromNative() {
@@ -1859,6 +1901,8 @@
      * @see AudioManager#setSupportedSystemUsages(int[])
      */
     public void setSupportedSystemUsages(@NonNull @AttributeSystemUsage int[] systemUsages) {
+        super.setSupportedSystemUsages_enforcePermission();
+
         verifySystemUsages(systemUsages);
 
         synchronized (mSupportedSystemUsagesLock) {
@@ -1872,6 +1916,8 @@
      * @see AudioManager#getSupportedSystemUsages()
      */
     public @NonNull @AttributeSystemUsage int[] getSupportedSystemUsages() {
+        super.getSupportedSystemUsages_enforcePermission();
+
         synchronized (mSupportedSystemUsagesLock) {
             return Arrays.copyOf(mSupportedSystemUsages, mSupportedSystemUsages.length);
         }
@@ -1893,6 +1939,8 @@
     @NonNull
     public List<AudioProductStrategy> getAudioProductStrategies() {
         // verify permissions
+        super.getAudioProductStrategies_enforcePermission();
+
         return AudioProductStrategy.getAudioProductStrategies();
     }
 
@@ -1904,6 +1952,8 @@
     @NonNull
     public List<AudioVolumeGroup> getAudioVolumeGroups() {
         // verify permissions
+        super.getAudioVolumeGroups_enforcePermission();
+
         return AudioVolumeGroup.getAudioVolumeGroups();
     }
 
@@ -2782,6 +2832,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /** @see AudioManager#removePreferredDeviceForStrategy(AudioProductStrategy) */
     public int removePreferredDevicesForStrategy(int strategy) {
+        super.removePreferredDevicesForStrategy_enforcePermission();
+
         final String logString =
                 String.format("removePreferredDeviceForStrategy strat:%d", strategy);
         sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
@@ -2799,6 +2851,8 @@
      * @see AudioManager#getPreferredDevicesForStrategy(AudioProductStrategy)
      */
     public List<AudioDeviceAttributes> getPreferredDevicesForStrategy(int strategy) {
+        super.getPreferredDevicesForStrategy_enforcePermission();
+
         List<AudioDeviceAttributes> devices = new ArrayList<>();
         final long identity = Binder.clearCallingIdentity();
         final int status = AudioSystem.getDevicesForRoleAndStrategy(
@@ -2869,6 +2923,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /** @see AudioManager#clearPreferredDevicesForCapturePreset(int) */
     public int clearPreferredDevicesForCapturePreset(int capturePreset) {
+        super.clearPreferredDevicesForCapturePreset_enforcePermission();
+
         final String logString = String.format(
                 "removePreferredDeviceForCapturePreset source:%d", capturePreset);
         sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
@@ -2885,6 +2941,8 @@
      * @see AudioManager#getPreferredDevicesForCapturePreset(int)
      */
     public List<AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int capturePreset) {
+        super.getPreferredDevicesForCapturePreset_enforcePermission();
+
         List<AudioDeviceAttributes> devices = new ArrayList<>();
         final long identity = Binder.clearCallingIdentity();
         final int status = AudioSystem.getDevicesForRoleAndCapturePreset(
@@ -3581,6 +3639,18 @@
         }
     }
 
+    // TODO enforce MODIFY_AUDIO_SYSTEM_SETTINGS when defined
+    private void enforceModifyAudioRoutingOrSystemSettingsPermission() {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+                != PackageManager.PERMISSION_GRANTED
+                /*&& mContext.checkCallingOrSelfPermission(
+                        android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+                            != PackageManager.PERMISSION_DENIED*/) {
+            throw new SecurityException(
+                    "Missing MODIFY_AUDIO_ROUTING or MODIFY_AUDIO_SYSTEM_SETTINGS permission");
+        }
+    }
+
     private void enforceAccessUltrasoundPermission() {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_ULTRASOUND)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -3618,6 +3688,8 @@
     /** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */
     public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags,
             String callingPackage, String attributionTag) {
+        super.setVolumeIndexForAttributes_enforcePermission();
+
         Objects.requireNonNull(attr, "attr must not be null");
         final int volumeGroup = getVolumeGroupIdForAttributes(attr);
         if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
@@ -3661,6 +3733,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /** @see AudioManager#getVolumeIndexForAttributes(attr) */
     public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
+        super.getVolumeIndexForAttributes_enforcePermission();
+
         Objects.requireNonNull(attr, "attr must not be null");
         final int volumeGroup = getVolumeGroupIdForAttributes(attr);
         if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
@@ -3673,6 +3747,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */
     public int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
+        super.getMaxVolumeIndexForAttributes_enforcePermission();
+
         Objects.requireNonNull(attr, "attr must not be null");
         return AudioSystem.getMaxVolumeIndexForAttributes(attr);
     }
@@ -3680,27 +3756,47 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /** @see AudioManager#getMinVolumeIndexForAttributes(attr) */
     public int getMinVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
+        super.getMinVolumeIndexForAttributes_enforcePermission();
+
         Objects.requireNonNull(attr, "attr must not be null");
         return AudioSystem.getMinVolumeIndexForAttributes(attr);
     }
 
     /** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes)
-     * Part of service interface, check permissions and parameters here */
+     * Part of service interface, check permissions and parameters here
+     * Note calling package is for logging purposes only, not to be trusted
+     */
     public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada,
-            @NonNull String callingPackage, @Nullable String attributionTag) {
-        enforceModifyAudioRoutingPermission();
+            @NonNull String callingPackage) {
+        enforceModifyAudioRoutingOrSystemSettingsPermission();
         Objects.requireNonNull(vi);
         Objects.requireNonNull(ada);
         Objects.requireNonNull(callingPackage);
+
         if (!vi.hasStreamType()) {
             Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception());
             return;
         }
         int index = vi.getVolumeIndex();
-        if (index == VolumeInfo.INDEX_NOT_SET) {
-            throw new IllegalArgumentException("changing device volume requires a volume index");
+        if (index == VolumeInfo.INDEX_NOT_SET && !vi.hasMuteCommand()) {
+            throw new IllegalArgumentException(
+                    "changing device volume requires a volume index or mute command");
         }
 
+        // TODO handle unmuting if current audio device
+        // if a stream is not muted but the VolumeInfo is for muting, set the volume index
+        // for the device to min volume
+        if (vi.hasMuteCommand() && vi.isMuted() && !isStreamMute(vi.getStreamType())) {
+            setStreamVolumeWithAttributionInt(vi.getStreamType(),
+                    mStreamStates[vi.getStreamType()].getMinIndex(),
+                    /*flags*/ 0,
+                    ada, callingPackage, null);
+            return;
+        }
+
+        AudioService.sVolumeLogger.enqueueAndLog("setDeviceVolume" + " from:" + callingPackage
+                + " " + vi + " " + ada, EventLogger.Event.ALOGI, TAG);
+
         if (vi.getMinVolumeIndex() == VolumeInfo.INDEX_NOT_SET
                 || vi.getMaxVolumeIndex() == VolumeInfo.INDEX_NOT_SET) {
             // assume index meant to be in stream type range, validate
@@ -3720,7 +3816,7 @@
             }
         }
         setStreamVolumeWithAttributionInt(vi.getStreamType(), index, /*flags*/ 0,
-                ada, callingPackage, attributionTag);
+                ada, callingPackage, null);
     }
 
     /** Retain API for unsupported app usage */
@@ -3786,6 +3882,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_ULTRASOUND)
     /** @see AudioManager#isUltrasoundSupported() */
     public boolean isUltrasoundSupported() {
+        super.isUltrasoundSupported_enforcePermission();
+
         return AudioSystem.isUltrasoundSupported();
     }
 
@@ -4603,6 +4701,8 @@
     /** @see AudioManager#setMasterMute(boolean, int) */
     public void setMasterMute(boolean mute, int flags, String callingPackage, int userId,
             String attributionTag) {
+        super.setMasterMute_enforcePermission();
+
         setMasterMuteInternal(mute, flags, callingPackage,
                 Binder.getCallingUid(), userId, Binder.getCallingPid(), attributionTag);
     }
@@ -4626,6 +4726,36 @@
         }
     }
 
+    /**
+     * @see AudioDeviceVolumeManager#getDeviceVolume(VolumeInfo, AudioDeviceAttributes)
+     */
+    public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi,
+            @NonNull AudioDeviceAttributes ada, @NonNull String callingPackage) {
+        enforceModifyAudioRoutingOrSystemSettingsPermission();
+        Objects.requireNonNull(vi);
+        Objects.requireNonNull(ada);
+        Objects.requireNonNull(callingPackage);
+        if (!vi.hasStreamType()) {
+            Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception());
+            return getDefaultVolumeInfo();
+        }
+
+        int streamType = vi.getStreamType();
+        final VolumeInfo.Builder vib = new VolumeInfo.Builder(vi);
+        vib.setMinVolumeIndex(mStreamStates[streamType].mIndexMin);
+        vib.setMaxVolumeIndex(mStreamStates[streamType].mIndexMax);
+        synchronized (VolumeStreamState.class) {
+            final int index;
+            if (isFixedVolumeDevice(ada.getInternalType())) {
+                index = (mStreamStates[streamType].mIndexMax + 5) / 10;
+            } else {
+                index = (mStreamStates[streamType].getIndex(ada.getInternalType()) + 5) / 10;
+            }
+            vib.setVolumeIndex(index);
+            return vib.setMuted(mStreamStates[streamType].mIsMuted).build();
+        }
+    }
+
     /** @see AudioManager#getStreamMaxVolume(int) */
     public int getStreamMaxVolume(int streamType) {
         ensureValidStreamType(streamType);
@@ -4647,6 +4777,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.QUERY_AUDIO_STATE)
     /** Get last audible volume before stream was muted. */
     public int getLastAudibleStreamVolume(int streamType) {
+        super.getLastAudibleStreamVolume_enforcePermission();
+
         ensureValidStreamType(streamType);
         int device = getDeviceForStream(streamType);
         return (mStreamStates[streamType].getIndex(device) + 5) / 10;
@@ -4664,7 +4796,6 @@
             sDefaultVolumeInfo = new VolumeInfo.Builder(AudioSystem.STREAM_MUSIC)
                     .setMinVolumeIndex(getStreamMinVolume(AudioSystem.STREAM_MUSIC))
                     .setMaxVolumeIndex(getStreamMaxVolume(AudioSystem.STREAM_MUSIC))
-                    .setMuted(false)
                     .build();
         }
         return sDefaultVolumeInfo;
@@ -5500,6 +5631,8 @@
     /** @see AudioManager#isPstnCallAudioInterceptable() */
     public boolean isPstnCallAudioInterceptable() {
 
+        super.isPstnCallAudioInterceptable_enforcePermission();
+
         boolean uplinkDeviceFound = false;
         boolean downlinkDeviceFound = false;
         AudioDeviceInfo[] devices = AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_ALL);
@@ -5747,6 +5880,9 @@
     };
 
     private boolean isValidCommunicationDevice(AudioDeviceInfo device) {
+        if (!device.isSink()) {
+            return false;
+        }
         for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
             if (device.getType() == type) {
                 return true;
@@ -5781,7 +5917,11 @@
                 throw new IllegalArgumentException("invalid portID " + portId);
             }
             if (!isValidCommunicationDevice(device)) {
-                throw new IllegalArgumentException("invalid device type " + device.getType());
+                if (!device.isSink()) {
+                    throw new IllegalArgumentException("device must have sink role");
+                } else {
+                    throw new IllegalArgumentException("invalid device type: " + device.getType());
+                }
             }
         }
         final String eventSource = new StringBuilder()
@@ -6499,9 +6639,13 @@
                     return AudioSystem.STREAM_RING;
                 } else if (wasStreamActiveRecently(
                         AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
-                    if (DEBUG_VOL)
-                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION stream active");
-                    return AudioSystem.STREAM_NOTIFICATION;
+                        if (DEBUG_VOL) {
+                            Log.v(
+                                    TAG,
+                                    "getActiveStreamType: Forcing STREAM_NOTIFICATION stream"
+                                            + " active");
+                        }
+                        return AudioSystem.STREAM_NOTIFICATION;
                 } else {
                     if (DEBUG_VOL) {
                         Log.v(TAG, "getActiveStreamType: Forcing DEFAULT_VOL_STREAM_NO_PLAYBACK("
@@ -6891,6 +7035,8 @@
             @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @Nullable String pkgName) {
         // verify permissions
         // verify arguments
+        super.setDeviceVolumeBehavior_enforcePermission();
+
         Objects.requireNonNull(device);
         AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
         sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
@@ -6974,9 +7120,10 @@
 
     private @AudioManager.DeviceVolumeBehavior
             int getDeviceVolumeBehaviorInt(@NonNull AudioDeviceAttributes device) {
-        // translate Java device type to native device type (for the devices masks for full / fixed)
-        final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
-                device.getType());
+        // Get the internal type set by the AudioDeviceAttributes constructor which is always more
+        // exact (avoids double conversions) than a conversion from SDK type via
+        // AudioDeviceInfo.convertDeviceTypeToInternalDevice()
+        final int audioSystemDeviceOut = device.getInternalType();
 
         int setDeviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut);
         if (setDeviceVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) {
@@ -7046,6 +7193,8 @@
      */
     public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes,
             @ConnectionState int state, String caller) {
+        super.setWiredDeviceConnectionState_enforcePermission();
+
         if (state != CONNECTION_STATE_CONNECTED
                 && state != CONNECTION_STATE_DISCONNECTED) {
             throw new IllegalArgumentException("Invalid state " + state);
@@ -7060,6 +7209,24 @@
                         state == CONNECTION_STATE_CONNECTED ? "connected" : "disconnected")
                 .record();
         mDeviceBroker.setWiredDeviceConnectionState(attributes, state, caller);
+        // The Dynamic Soundbar mode feature introduces dynamic presence for an HDMI Audio System
+        // Client. For example, the device can start with the Audio System Client unavailable.
+        // When the feature is activated the client becomes available, therefore Audio Service
+        // requests a new HDMI Audio System Client instance when the ARC status is changed.
+        if (attributes.getInternalType() == AudioSystem.DEVICE_IN_HDMI_ARC) {
+            updateHdmiAudioSystemClient();
+        }
+    }
+
+    /**
+     * Replace the current HDMI Audio System Client.
+     * See {@link #setWiredDeviceConnectionState(AudioDeviceAttributes, int, String)}.
+     */
+    private void updateHdmiAudioSystemClient() {
+        Slog.d(TAG, "Hdmi Audio System Client is updated");
+        synchronized (mHdmiClientLock) {
+            mHdmiAudioSystemClient = mHdmiManager.getAudioSystemClient();
+        }
     }
 
     /** @see AudioManager#setTestDeviceConnectionState(AudioDeviceAttributes, boolean) */
@@ -7821,6 +7988,7 @@
                 boolean hasModifyAudioSettings) {
             boolean changed;
             int oldIndex;
+            final boolean isCurrentDevice;
             synchronized (mSettingsLock) {
                 synchronized (VolumeStreamState.class) {
                     oldIndex = getIndex(device);
@@ -7836,7 +8004,7 @@
                     // - there is no volume index stored for this device on alias stream.
                     // If changing volume of current device, also change volume of current
                     // device on aliased stream
-                    final boolean isCurrentDevice = (device == getDeviceForStream(mStreamType));
+                    isCurrentDevice = (device == getDeviceForStream(mStreamType));
                     final int numStreamTypes = AudioSystem.getNumStreamTypes();
                     for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
                         final VolumeStreamState aliasStreamState = mStreamStates[streamType];
@@ -7876,8 +8044,9 @@
                     EventLogTags.writeVolumeChanged(mStreamType, oldIndex, index, mIndexMax / 10,
                             caller);
                 }
-                // fire changed intents for all streams
-                if (index != oldIndex) {
+                // fire changed intents for all streams, but only when the device it changed on
+                //  is the current device
+                if ((index != oldIndex) && isCurrentDevice) {
                     mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
                     mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
                     mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
@@ -7899,6 +8068,23 @@
             }
         }
 
+        public @NonNull VolumeInfo getVolumeInfo(int device) {
+            synchronized (VolumeStreamState.class) {
+                int index = mIndexMap.get(device, -1);
+                if (index == -1) {
+                    // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
+                    index = mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
+                }
+                final VolumeInfo vi = new VolumeInfo.Builder(mStreamType)
+                        .setMinVolumeIndex(mIndexMin)
+                        .setMaxVolumeIndex(mIndexMax)
+                        .setVolumeIndex(index)
+                        .setMuted(isFullyMuted())
+                        .build();
+                return vi;
+            }
+        }
+
         public boolean hasIndexForDevice(int device) {
             synchronized (VolumeStreamState.class) {
                 return (mIndexMap.get(device, -1) != -1);
@@ -8556,6 +8742,10 @@
                     // fold parameter format: "device_folded=x" where x is one of on, off
                     mAudioSystem.setParameters((String) msg.obj);
                     break;
+
+                case MSG_NO_LOG_FOR_PLAYER_I:
+                    mPlaybackMonitor.ignorePlayerIId(msg.arg1);
+                    break;
             }
         }
     }
@@ -9178,24 +9368,32 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#isAvailableForDevice(AudioDeviceAttributes) */
     public boolean isSpatializerAvailableForDevice(@NonNull AudioDeviceAttributes device)  {
+        super.isSpatializerAvailableForDevice_enforcePermission();
+
         return mSpatializerHelper.isAvailableForDevice(Objects.requireNonNull(device));
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#hasHeadTracker(AudioDeviceAttributes) */
     public boolean hasHeadTracker(@NonNull AudioDeviceAttributes device) {
+        super.hasHeadTracker_enforcePermission();
+
         return mSpatializerHelper.hasHeadTracker(Objects.requireNonNull(device));
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#setHeadTrackerEnabled(boolean, AudioDeviceAttributes) */
     public void setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes device) {
+        super.setHeadTrackerEnabled_enforcePermission();
+
         mSpatializerHelper.setHeadTrackerEnabled(enabled, Objects.requireNonNull(device));
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#isHeadTrackerEnabled(AudioDeviceAttributes) */
     public boolean isHeadTrackerEnabled(@NonNull AudioDeviceAttributes device) {
+        super.isHeadTrackerEnabled_enforcePermission();
+
         return mSpatializerHelper.isHeadTrackerEnabled(Objects.requireNonNull(device));
     }
 
@@ -9207,6 +9405,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#setSpatializerEnabled(boolean) */
     public void setSpatializerEnabled(boolean enabled) {
+        super.setSpatializerEnabled_enforcePermission();
+
         mSpatializerHelper.setFeatureEnabled(enabled);
     }
 
@@ -9236,6 +9436,8 @@
     /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */
     public void registerSpatializerHeadTrackingCallback(
             @NonNull ISpatializerHeadTrackingModeCallback cb) {
+        super.registerSpatializerHeadTrackingCallback_enforcePermission();
+
         Objects.requireNonNull(cb);
         mSpatializerHelper.registerHeadTrackingModeCallback(cb);
     }
@@ -9244,6 +9446,8 @@
     /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */
     public void unregisterSpatializerHeadTrackingCallback(
             @NonNull ISpatializerHeadTrackingModeCallback cb) {
+        super.unregisterSpatializerHeadTrackingCallback_enforcePermission();
+
         Objects.requireNonNull(cb);
         mSpatializerHelper.unregisterHeadTrackingModeCallback(cb);
     }
@@ -9259,6 +9463,8 @@
     /** @see Spatializer#setOnHeadToSoundstagePoseUpdatedListener */
     public void registerHeadToSoundstagePoseCallback(
             @NonNull ISpatializerHeadToSoundStagePoseCallback cb) {
+        super.registerHeadToSoundstagePoseCallback_enforcePermission();
+
         Objects.requireNonNull(cb);
         mSpatializerHelper.registerHeadToSoundstagePoseCallback(cb);
     }
@@ -9267,6 +9473,8 @@
     /** @see Spatializer#clearOnHeadToSoundstagePoseUpdatedListener */
     public void unregisterHeadToSoundstagePoseCallback(
             @NonNull ISpatializerHeadToSoundStagePoseCallback cb) {
+        super.unregisterHeadToSoundstagePoseCallback_enforcePermission();
+
         Objects.requireNonNull(cb);
         mSpatializerHelper.unregisterHeadToSoundstagePoseCallback(cb);
     }
@@ -9274,12 +9482,16 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#getSpatializerCompatibleAudioDevices() */
     public @NonNull List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices() {
+        super.getSpatializerCompatibleAudioDevices_enforcePermission();
+
         return mSpatializerHelper.getCompatibleAudioDevices();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#addSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */
     public void addSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
+        super.addSpatializerCompatibleAudioDevice_enforcePermission();
+
         Objects.requireNonNull(ada);
         mSpatializerHelper.addCompatibleAudioDevice(ada);
     }
@@ -9287,6 +9499,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#removeSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */
     public void removeSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
+        super.removeSpatializerCompatibleAudioDevice_enforcePermission();
+
         Objects.requireNonNull(ada);
         mSpatializerHelper.removeCompatibleAudioDevice(ada);
     }
@@ -9294,24 +9508,32 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#getSupportedHeadTrackingModes() */
     public int[] getSupportedHeadTrackingModes() {
+        super.getSupportedHeadTrackingModes_enforcePermission();
+
         return mSpatializerHelper.getSupportedHeadTrackingModes();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#getHeadTrackingMode() */
     public int getActualHeadTrackingMode() {
+        super.getActualHeadTrackingMode_enforcePermission();
+
         return mSpatializerHelper.getActualHeadTrackingMode();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#getDesiredHeadTrackingMode() */
     public int getDesiredHeadTrackingMode() {
+        super.getDesiredHeadTrackingMode_enforcePermission();
+
         return mSpatializerHelper.getDesiredHeadTrackingMode();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#setGlobalTransform */
     public void setSpatializerGlobalTransform(@NonNull float[] transform) {
+        super.setSpatializerGlobalTransform_enforcePermission();
+
         Objects.requireNonNull(transform);
         mSpatializerHelper.setGlobalTransform(transform);
     }
@@ -9319,12 +9541,16 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#recenterHeadTracker() */
     public void recenterHeadTracker() {
+        super.recenterHeadTracker_enforcePermission();
+
         mSpatializerHelper.recenterHeadTracker();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#setDesiredHeadTrackingMode */
     public void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) {
+        super.setDesiredHeadTrackingMode_enforcePermission();
+
         switch(mode) {
             case Spatializer.HEAD_TRACKING_MODE_DISABLED:
             case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
@@ -9339,6 +9565,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#setEffectParameter */
     public void setSpatializerParameter(int key, @NonNull byte[] value) {
+        super.setSpatializerParameter_enforcePermission();
+
         Objects.requireNonNull(value);
         mSpatializerHelper.setEffectParameter(key, value);
     }
@@ -9346,6 +9574,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#getEffectParameter */
     public void getSpatializerParameter(int key, @NonNull byte[] value) {
+        super.getSpatializerParameter_enforcePermission();
+
         Objects.requireNonNull(value);
         mSpatializerHelper.getEffectParameter(key, value);
     }
@@ -9353,12 +9583,16 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#getOutput */
     public int getSpatializerOutput() {
+        super.getSpatializerOutput_enforcePermission();
+
         return mSpatializerHelper.getOutput();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#setOnSpatializerOutputChangedListener */
     public void registerSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+        super.registerSpatializerOutputCallback_enforcePermission();
+
         Objects.requireNonNull(cb);
         mSpatializerHelper.registerSpatializerOutputCallback(cb);
     }
@@ -9366,6 +9600,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     /** @see Spatializer#clearOnSpatializerOutputChangedListener */
     public void unregisterSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+        super.unregisterSpatializerOutputCallback_enforcePermission();
+
         Objects.requireNonNull(cb);
         mSpatializerHelper.unregisterSpatializerOutputCallback(cb);
     }
@@ -9483,6 +9719,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /** @see AudioManager#getMutingExpectedDevice */
     public @Nullable AudioDeviceAttributes getMutingExpectedDevice() {
+        super.getMutingExpectedDevice_enforcePermission();
+
         synchronized (mMuteAwaitConnectionLock) {
             return mMutingExpectedDevice;
         }
@@ -9524,6 +9762,8 @@
     /** @see AudioManager#registerMuteAwaitConnectionCallback */
     public void registerMuteAwaitConnectionDispatcher(@NonNull IMuteAwaitConnectionCallback cb,
             boolean register) {
+        super.registerMuteAwaitConnectionDispatcher_enforcePermission();
+
         if (register) {
             mMuteAwaitConnectionDispatchers.register(cb);
         } else {
@@ -10698,6 +10938,21 @@
     }
 
     /**
+     * Called by an AudioPolicyProxy when the client dies.
+     * Checks if an active playback for media use case is currently routed to one of the
+     * remote submix devices owned by this dynamic policy and broadcasts a becoming noisy
+     * intend in this case.
+     * @param addresses list of remote submix device addresses to check.
+     */
+    private void onPolicyClientDeath(List<String> addresses) {
+        for (String address : addresses) {
+            if (mPlaybackMonitor.hasActiveMediaPlaybackOnSubmixWithAddress(address)) {
+                mDeviceBroker.postBroadcastBecomingNoisy();
+                return;
+            }
+        }
+    }
+    /**
      * Apps with MODIFY_AUDIO_ROUTING can register any policy.
      * Apps with an audio capable MediaProjection are allowed to register a RENDER|LOOPBACK policy
      * as those policy do not modify the audio routing.
@@ -11049,6 +11304,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     /** @see AudioPolicy#getFocusStack() */
     public List<AudioFocusInfo> getFocusStack() {
+        super.getFocusStack_enforcePermission();
+
         return mMediaFocusControl.getFocusStack();
     }
 
@@ -11067,15 +11324,16 @@
         return mMediaFocusControl.sendFocusLoss(focusLoser);
     }
 
-    private static final String[] HAL_VERSIONS =
-            new String[] {"7.1", "7.0", "6.0", "5.0", "4.0", "2.0"};
-
-    /** @see AudioManager#getHalVersion */
-    public @Nullable String getHalVersion() {
-        for (String version : HAL_VERSIONS) {
+    /**
+     * @see AudioManager#getHalVersion
+     */
+    public @Nullable AudioHalVersionInfo getHalVersion() {
+        for (AudioHalVersionInfo version : AudioHalVersionInfo.VERSIONS) {
             try {
+                // TODO: check AIDL service.
+                String versionStr = version.getMajorVersion() + "." + version.getMinorVersion();
                 HwBinder.getService(
-                        String.format("android.hardware.audio@%s::IDevicesFactory", version),
+                        String.format("android.hardware.audio@%s::IDevicesFactory", versionStr),
                         "default");
                 return version;
             } catch (NoSuchElementException e) {
@@ -11169,8 +11427,8 @@
     }
 
     public List<AudioRecordingConfiguration> getActiveRecordingConfigurations() {
-        final boolean isPrivileged =
-                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
+        final boolean isPrivileged = Binder.getCallingUid() == Process.SYSTEM_UID
+                || (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
                         android.Manifest.permission.MODIFY_AUDIO_ROUTING));
         return mRecordMonitor.getActiveRecordingConfigurations(isPrivileged);
     }
@@ -11405,6 +11663,13 @@
         public void binderDied() {
             mDynPolicyLogger.enqueue((new EventLogger.StringEvent("AudioPolicy "
                     + mPolicyCallback.asBinder() + " died").printLog(TAG)));
+
+            List<String> addresses = new ArrayList<>();
+            for (AudioMix mix : mMixes) {
+                addresses.add(mix.getRegistration());
+            }
+            onPolicyClientDeath(addresses);
+
             release();
         }
 
@@ -11826,6 +12091,8 @@
     // Multi Audio Focus
     //======================
     public void setMultiAudioFocusEnabled(boolean enabled) {
+        super.setMultiAudioFocusEnabled_enforcePermission();
+
         if (mMediaFocusControl != null) {
             boolean mafEnabled = mMediaFocusControl.getMultiAudioFocusEnabled();
             if (mafEnabled != enabled) {
@@ -11928,6 +12195,8 @@
     /** @see AudioManager#addAssistantServicesUids(int []) */
     @Override
     public void addAssistantServicesUids(int [] assistantUids) {
+        super.addAssistantServicesUids_enforcePermission();
+
         Objects.requireNonNull(assistantUids);
 
         synchronized (mSettingsLock) {
@@ -11939,6 +12208,8 @@
     /** @see AudioManager#removeAssistantServicesUids(int []) */
     @Override
     public void removeAssistantServicesUids(int [] assistantUids) {
+        super.removeAssistantServicesUids_enforcePermission();
+
         Objects.requireNonNull(assistantUids);
         synchronized (mSettingsLock) {
             removeAssistantServiceUidsLocked(assistantUids);
@@ -11949,6 +12220,8 @@
     /** @see AudioManager#getAssistantServicesUids() */
     @Override
     public int[] getAssistantServicesUids() {
+        super.getAssistantServicesUids_enforcePermission();
+
         int [] assistantUids;
         synchronized (mSettingsLock) {
             assistantUids = mAssistantUids.stream().mapToInt(Integer::intValue).toArray();
@@ -11960,6 +12233,8 @@
     /** @see AudioManager#setActiveAssistantServiceUids(int []) */
     @Override
     public void setActiveAssistantServiceUids(int [] activeAssistantUids) {
+        super.setActiveAssistantServiceUids_enforcePermission();
+
         Objects.requireNonNull(activeAssistantUids);
         synchronized (mSettingsLock) {
             mActiveAssistantServiceUids = activeAssistantUids;
@@ -11971,6 +12246,8 @@
     /** @see AudioManager#getActiveAssistantServiceUids() */
     @Override
     public int[] getActiveAssistantServiceUids() {
+        super.getActiveAssistantServiceUids_enforcePermission();
+
         int [] activeAssistantUids;
         synchronized (mSettingsLock) {
             activeAssistantUids = mActiveAssistantServiceUids.clone();
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 74bfa80..f35931ca 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;
 
@@ -32,6 +32,7 @@
 import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioPlaybackConfiguration.PlayerMuteEvent;
@@ -191,6 +192,18 @@
     }
 
     //=================================================================
+    // Player to ignore (only handling single player, designed for ignoring
+    // in the logs one specific player such as the touch sounds player)
+    @GuardedBy("mPlayerLock")
+    private ArrayList<Integer> mDoNotLogPiidList = new ArrayList<>();
+
+    /*package*/ void ignorePlayerIId(int doNotLogPiid) {
+        synchronized (mPlayerLock) {
+            mDoNotLogPiidList.add(doNotLogPiid);
+        }
+    }
+
+    //=================================================================
     // Track players and their states
     // methods playerAttributes, playerEvent, releasePlayer are all oneway calls
     //  into AudioService. They trigger synchronous dispatchPlaybackChange() which updates
@@ -314,14 +327,18 @@
             Log.v(TAG, TextUtils.formatSimple("playerEvent(piid=%d, event=%s, eventValue=%d)",
                     piid, AudioPlaybackConfiguration.playerStateToString(event), eventValue));
         }
-
-        final boolean change;
+        boolean change;
         synchronized(mPlayerLock) {
             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
             if (apc == null) {
                 return;
             }
 
+            final boolean doNotLog = mDoNotLogPiidList.contains(piid);
+            if (doNotLog && event != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
+                // do not log nor dispatch events for "ignored" players other than the release
+                return;
+            }
             sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue));
 
             if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
@@ -338,7 +355,8 @@
                     }
                 }
             }
-            if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+            if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL
+                    && event != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
                 // FIXME SoundPool not ready for state reporting
                 return;
             }
@@ -350,9 +368,15 @@
                 Log.e(TAG, "Error handling event " + event);
                 change = false;
             }
-            if (change && event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                mDuckingManager.checkDuck(apc);
-                mFadingManager.checkFade(apc);
+            if (change) {
+                if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                    mDuckingManager.checkDuck(apc);
+                    mFadingManager.checkFade(apc);
+                }
+                if (doNotLog) {
+                    // do not dispatch events for "ignored" players
+                    change = false;
+                }
             }
         }
         if (change) {
@@ -435,6 +459,10 @@
                 mEventHandler.sendMessage(
                         mEventHandler.obtainMessage(MSG_L_CLEAR_PORTS_FOR_PIID, piid, /*arg2=*/0));
 
+                if (change && mDoNotLogPiidList.contains(piid)) {
+                    // do not dispatch a change for a "do not log" player
+                    change = false;
+                }
             }
         }
         if (change) {
@@ -542,6 +570,26 @@
         return false;
     }
 
+    /**
+     * Return true if an active playback for media use case is currently routed to
+     * a remote submix device with the supplied address.
+     * @param address
+     */
+    public boolean hasActiveMediaPlaybackOnSubmixWithAddress(@NonNull String address) {
+        synchronized (mPlayerLock) {
+            for (AudioPlaybackConfiguration apc : mPlayers.values()) {
+                AudioDeviceInfo device = apc.getAudioDeviceInfo();
+                if (apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_MEDIA
+                        && apc.isActive() && device != null
+                        && device.getInternalType() == AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
+                        && address.equals(device.getAddress())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     protected void dump(PrintWriter pw) {
         // players
         pw.println("\nPlaybackActivityMonitor dump time: "
@@ -560,6 +608,9 @@
             for (Integer piidInt : piidIntList) {
                 final AudioPlaybackConfiguration apc = mPlayers.get(piidInt);
                 if (apc != null) {
+                    if (mDoNotLogPiidList.contains(apc.getPlayerInterfaceId())) {
+                        pw.print("(not logged)");
+                    }
                     apc.dump(pw);
                 }
             }
@@ -1155,22 +1206,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 ");
                         }
                     }
diff --git a/services/core/java/com/android/server/audio/SoundEffectsHelper.java b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
index 79b54eb..8c4efba 100644
--- a/services/core/java/com/android/server/audio/SoundEffectsHelper.java
+++ b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
@@ -25,6 +25,7 @@
 import android.media.MediaPlayer;
 import android.media.MediaPlayer.OnCompletionListener;
 import android.media.MediaPlayer.OnErrorListener;
+import android.media.PlayerBase;
 import android.media.SoundPool;
 import android.os.Environment;
 import android.os.Handler;
@@ -47,6 +48,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 
 /**
  * A helper class for managing sound effects loading / unloading
@@ -109,11 +111,14 @@
     private final int[] mEffects = new int[AudioManager.NUM_SOUND_EFFECTS]; // indexes in mResources
     private SoundPool mSoundPool;
     private SoundPoolLoader mSoundPoolLoader;
+    /** callback to provide handle to the player of the sound effects */
+    private final Consumer<PlayerBase> mPlayerAvailableCb;
 
-    SoundEffectsHelper(Context context) {
+    SoundEffectsHelper(Context context, Consumer<PlayerBase> playerAvailableCb) {
         mContext = context;
         mSfxAttenuationDb = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_soundEffectVolumeDb);
+        mPlayerAvailableCb = playerAvailableCb;
         startWorker();
     }
 
@@ -189,6 +194,7 @@
                         .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                         .build())
                 .build();
+        mPlayerAvailableCb.accept(mSoundPool);
         loadSoundAssets();
 
         mSoundPoolLoader = new SoundPoolLoader();
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index d39d2d1..1b20e43 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -18,9 +18,9 @@
 
 import android.app.IWallpaperManager;
 import android.app.backup.BackupAgentHelper;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupHelper;
-import android.app.backup.BackupManager;
 import android.app.backup.FullBackup;
 import android.app.backup.FullBackupDataOutput;
 import android.app.backup.WallpaperBackupHelper;
@@ -89,8 +89,8 @@
     private int mUserId = UserHandle.USER_SYSTEM;
 
     @Override
-    public void onCreate(UserHandle user, @BackupManager.OperationType int operationType) {
-        super.onCreate(user, operationType);
+    public void onCreate(UserHandle user, @BackupDestination int backupDestination) {
+        super.onCreate(user, backupDestination);
 
         mUserId = user.getIdentifier();
 
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index d2016c47..acfc2a7 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -178,6 +178,8 @@
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
                 @NonNull String opPackageName) throws RemoteException {
 
+            super.createTestSession_enforcePermission();
+
             final long identity = Binder.clearCallingIdentity();
             try {
                 return mInjector.getBiometricService()
@@ -192,6 +194,8 @@
         public List<SensorPropertiesInternal> getSensorProperties(String opPackageName)
                 throws RemoteException {
 
+            super.getSensorProperties_enforcePermission();
+
             final long identity = Binder.clearCallingIdentity();
             try {
                 // Get the result from BiometricService, since it is the source of truth for all
@@ -206,6 +210,8 @@
         @Override
         public String getUiPackage() {
 
+            super.getUiPackage_enforcePermission();
+
             return getContext().getResources()
                     .getString(R.string.config_biometric_prompt_ui_package);
         }
@@ -404,6 +410,17 @@
         }
 
         @Override
+        public void resetLockout(int userId, byte[] hardwareAuthToken) throws RemoteException {
+            checkInternalPermission();
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mBiometricService.resetLockout(userId, hardwareAuthToken);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public CharSequence getButtonLabel(
                 int userId,
                 String opPackageName,
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index c29755a..d971953 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -72,6 +72,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.DumpUtils;
 import com.android.server.SystemService;
+import com.android.server.biometrics.log.BiometricContext;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -100,6 +101,7 @@
     private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
     private final Random mRandom = new Random();
     @NonNull private final Supplier<Long> mRequestCounter;
+    @NonNull private final BiometricContext mBiometricContext;
 
     @VisibleForTesting
     IStatusBarService mStatusBarService;
@@ -495,6 +497,8 @@
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
                 @NonNull String opPackageName) throws RemoteException {
 
+            super.createTestSession_enforcePermission();
+
             for (BiometricSensor sensor : mSensors) {
                 if (sensor.id == sensorId) {
                     return sensor.impl.createTestSession(callback, opPackageName);
@@ -510,6 +514,8 @@
         public List<SensorPropertiesInternal> getSensorProperties(String opPackageName)
                 throws RemoteException {
 
+            super.getSensorProperties_enforcePermission();
+
             final List<SensorPropertiesInternal> sensors = new ArrayList<>();
             for (BiometricSensor sensor : mSensors) {
                 // Explicitly re-create as the super class, since AIDL doesn't play nicely with
@@ -526,6 +532,8 @@
         @Override // Binder call
         public void onReadyForAuthentication(long requestId, int cookie) {
 
+            super.onReadyForAuthentication_enforcePermission();
+
             mHandler.post(() -> handleOnReadyForAuthentication(requestId, cookie));
         }
 
@@ -534,6 +542,8 @@
         public long authenticate(IBinder token, long operationId, int userId,
                 IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo) {
 
+            super.authenticate_enforcePermission();
+
             if (token == null || receiver == null || opPackageName == null || promptInfo == null) {
                 Slog.e(TAG, "Unable to authenticate, one or more null arguments");
                 return -1;
@@ -564,6 +574,8 @@
         @Override // Binder call
         public void cancelAuthentication(IBinder token, String opPackageName, long requestId) {
 
+            super.cancelAuthentication_enforcePermission();
+
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = token;
             args.arg2 = opPackageName;
@@ -577,6 +589,8 @@
         public int canAuthenticate(String opPackageName, int userId, int callingUserId,
                 @Authenticators.Types int authenticators) {
 
+            super.canAuthenticate_enforcePermission();
+
             Slog.d(TAG, "canAuthenticate: User=" + userId
                     + ", Caller=" + callingUserId
                     + ", Authenticators=" + authenticators);
@@ -599,6 +613,8 @@
         @Override
         public boolean hasEnrolledBiometrics(int userId, String opPackageName) {
 
+            super.hasEnrolledBiometrics_enforcePermission();
+
             try {
                 for (BiometricSensor sensor : mSensors) {
                     if (sensor.impl.hasEnrolledTemplates(userId, opPackageName)) {
@@ -618,6 +634,8 @@
                 @Authenticators.Types int strength,
                 @NonNull IBiometricAuthenticator authenticator) {
 
+            super.registerAuthenticator_enforcePermission();
+
             Slog.d(TAG, "Registering ID: " + id
                     + " Modality: " + modality
                     + " Strength: " + strength);
@@ -664,6 +682,8 @@
         public void registerEnabledOnKeyguardCallback(
                 IBiometricEnabledOnKeyguardCallback callback, int callingUserId) {
 
+            super.registerEnabledOnKeyguardCallback_enforcePermission();
+
             mEnabledOnKeyguardCallbacks.add(new EnabledOnKeyguardCallback(callback));
             try {
                 callback.onChanged(mSettingObserver.getEnabledOnKeyguard(callingUserId),
@@ -678,6 +698,8 @@
         public void invalidateAuthenticatorIds(int userId, int fromSensorId,
                 IInvalidationCallback callback) {
 
+            super.invalidateAuthenticatorIds_enforcePermission();
+
             InvalidationTracker.start(getContext(), mSensors, userId, fromSensorId, callback);
         }
 
@@ -685,6 +707,8 @@
         @Override // Binder call
         public long[] getAuthenticatorIds(int callingUserId) {
 
+            super.getAuthenticatorIds_enforcePermission();
+
             final List<Long> authenticatorIds = new ArrayList<>();
             for (BiometricSensor sensor : mSensors) {
                 try {
@@ -717,6 +741,8 @@
                 int userId, byte[] hardwareAuthToken) {
 
             // Check originating strength
+            super.resetLockoutTimeBound_enforcePermission();
+
             if (!Utils.isAtLeastStrength(getSensorForId(fromSensorId).getCurrentStrength(),
                     Authenticators.BIOMETRIC_STRONG)) {
                 Slog.w(TAG, "Sensor: " + fromSensorId + " is does not meet the required strength to"
@@ -752,8 +778,22 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
+        public void resetLockout(
+                int userId, byte[] hardwareAuthToken) {
+            super.resetLockout_enforcePermission();
+
+            Slog.d(TAG, "resetLockout(userId=" + userId
+                    + ", hat=" + (hardwareAuthToken == null ? "null " : "present") + ")");
+            mBiometricContext.getAuthSessionCoordinator()
+                    .resetLockoutFor(userId, Authenticators.BIOMETRIC_STRONG, -1);
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override // Binder call
         public int getCurrentStrength(int sensorId) {
 
+            super.getCurrentStrength_enforcePermission();
+
             for (BiometricSensor sensor : mSensors) {
                 if (sensor.id == sensorId) {
                     return sensor.getCurrentStrength();
@@ -772,6 +812,8 @@
                 @Authenticators.Types int authenticators) {
 
 
+            super.getCurrentModality_enforcePermission();
+
             Slog.d(TAG, "getCurrentModality: User=" + userId
                     + ", Caller=" + callingUserId
                     + ", Authenticators=" + authenticators);
@@ -794,6 +836,8 @@
         @Override // Binder call
         public int getSupportedModalities(@Authenticators.Types int authenticators) {
 
+            super.getSupportedModalities_enforcePermission();
+
             Slog.d(TAG, "getSupportedModalities: Authenticators=" + authenticators);
 
             if (!Utils.isValidAuthenticatorConfig(authenticators)) {
@@ -954,6 +998,10 @@
             final AtomicLong generator = new AtomicLong(0);
             return () -> generator.incrementAndGet();
         }
+
+        public BiometricContext getBiometricContext(Context context) {
+            return BiometricContext.getInstance(context);
+        }
     }
 
     /**
@@ -980,6 +1028,7 @@
         mSettingObserver = mInjector.getSettingObserver(context, mHandler,
                 mEnabledOnKeyguardCallbacks);
         mRequestCounter = mInjector.getRequestGenerator();
+        mBiometricContext = injector.getBiometricContext(context);
 
         try {
             injector.getActivityManagerService().registerUserSwitchObserver(
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index aec98f0..3813fd1 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -48,8 +48,6 @@
  * the PreAuthInfo should not change any sensor state.
  */
 class PreAuthInfo {
-    private static final String TAG = "BiometricService/PreAuthInfo";
-
     static final int AUTHENTICATOR_OK = 1;
     static final int BIOMETRIC_NO_HARDWARE = 2;
     static final int BIOMETRIC_DISABLED_BY_DEVICE_POLICY = 3;
@@ -62,24 +60,7 @@
     static final int BIOMETRIC_LOCKOUT_TIMED = 10;
     static final int BIOMETRIC_LOCKOUT_PERMANENT = 11;
     static final int BIOMETRIC_SENSOR_PRIVACY_ENABLED = 12;
-    @IntDef({AUTHENTICATOR_OK,
-            BIOMETRIC_NO_HARDWARE,
-            BIOMETRIC_DISABLED_BY_DEVICE_POLICY,
-            BIOMETRIC_INSUFFICIENT_STRENGTH,
-            BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE,
-            BIOMETRIC_HARDWARE_NOT_DETECTED,
-            BIOMETRIC_NOT_ENROLLED,
-            BIOMETRIC_NOT_ENABLED_FOR_APPS,
-            CREDENTIAL_NOT_ENROLLED,
-            BIOMETRIC_LOCKOUT_TIMED,
-            BIOMETRIC_LOCKOUT_PERMANENT,
-            BIOMETRIC_SENSOR_PRIVACY_ENABLED})
-    @Retention(RetentionPolicy.SOURCE)
-    @interface AuthenticatorStatus {}
-
-    private final boolean mBiometricRequested;
-    private final int mBiometricStrengthRequested;
-
+    private static final String TAG = "BiometricService/PreAuthInfo";
     final boolean credentialRequested;
     // Sensors that can be used for this request (e.g. strong enough, enrolled, enabled).
     final List<BiometricSensor> eligibleSensors;
@@ -90,6 +71,25 @@
     final boolean ignoreEnrollmentState;
     final int userId;
     final Context context;
+    private final boolean mBiometricRequested;
+    private final int mBiometricStrengthRequested;
+    private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
+            boolean credentialRequested, List<BiometricSensor> eligibleSensors,
+            List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
+            boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
+            Context context) {
+        mBiometricRequested = biometricRequested;
+        mBiometricStrengthRequested = biometricStrengthRequested;
+        this.credentialRequested = credentialRequested;
+
+        this.eligibleSensors = eligibleSensors;
+        this.ineligibleSensors = ineligibleSensors;
+        this.credentialAvailable = credentialAvailable;
+        this.confirmationRequested = confirmationRequested;
+        this.ignoreEnrollmentState = ignoreEnrollmentState;
+        this.userId = userId;
+        this.context = context;
+    }
 
     static PreAuthInfo create(ITrustManager trustManager,
             DevicePolicyManager devicePolicyManager,
@@ -158,7 +158,8 @@
      *
      * @return @AuthenticatorStatus
      */
-    private static @AuthenticatorStatus int getStatusForBiometricAuthenticator(
+    private static @AuthenticatorStatus
+    int getStatusForBiometricAuthenticator(
             DevicePolicyManager devicePolicyManager,
             BiometricService.SettingObserver settingObserver,
             BiometricSensor sensor, int userId, String opPackageName,
@@ -200,7 +201,6 @@
                 }
             }
 
-
             final @LockoutTracker.LockoutMode int lockoutMode =
                     sensor.impl.getLockoutModeForUser(userId);
             if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
@@ -248,8 +248,8 @@
 
     /**
      * @param modality one of {@link BiometricAuthenticator#TYPE_FINGERPRINT},
-     * {@link BiometricAuthenticator#TYPE_IRIS} or {@link BiometricAuthenticator#TYPE_FACE}
-     * @return
+     *                 {@link BiometricAuthenticator#TYPE_IRIS} or
+     *                 {@link BiometricAuthenticator#TYPE_FACE}
      */
     private static int mapModalityToDevicePolicyType(int modality) {
         switch (modality) {
@@ -265,24 +265,6 @@
         }
     }
 
-    private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
-            boolean credentialRequested, List<BiometricSensor> eligibleSensors,
-            List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
-            boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
-            Context context) {
-        mBiometricRequested = biometricRequested;
-        mBiometricStrengthRequested = biometricStrengthRequested;
-        this.credentialRequested = credentialRequested;
-
-        this.eligibleSensors = eligibleSensors;
-        this.ineligibleSensors = ineligibleSensors;
-        this.credentialAvailable = credentialAvailable;
-        this.confirmationRequested = confirmationRequested;
-        this.ignoreEnrollmentState = ignoreEnrollmentState;
-        this.userId = userId;
-        this.context = context;
-    }
-
     private Pair<BiometricSensor, Integer> calculateErrorByPriority() {
         // If the caller requested STRONG, and the device contains both STRONG and non-STRONG
         // sensors, prioritize BIOMETRIC_NOT_ENROLLED over the weak sensor's
@@ -303,6 +285,7 @@
      * surface, combined with the actual sensor/credential and user/system settings, calculate the
      * internal {@link AuthenticatorStatus} that should be returned to the client. Note that this
      * will need to be converted into the public API constant.
+     *
      * @return Pair<Modality, Error> with error being the internal {@link AuthenticatorStatus} code
      */
     private Pair<Integer, Integer> getInternalStatus() {
@@ -391,7 +374,8 @@
     /**
      * @return public BiometricManager result for the current request.
      */
-    @BiometricManager.BiometricError int getCanAuthenticateResult() {
+    @BiometricManager.BiometricError
+    int getCanAuthenticateResult() {
         // TODO: Convert this directly
         return Utils.biometricConstantsToBiometricManager(
                 Utils.authenticatorStatusToBiometricConstant(
@@ -401,6 +385,7 @@
     /**
      * For the given request, generate the appropriate reason why authentication cannot be started.
      * Note that for some errors, modality is intentionally cleared.
+     *
      * @return Pair<Modality, Error> with modality being filtered if necessary, and error
      * being one of the public {@link android.hardware.biometrics.BiometricConstants} codes.
      */
@@ -443,7 +428,8 @@
      * @return bitmask representing the modalities that are running or could be running for the
      * current session.
      */
-    @BiometricAuthenticator.Modality int getEligibleModalities() {
+    @BiometricAuthenticator.Modality
+    int getEligibleModalities() {
         @BiometricAuthenticator.Modality int modalities = 0;
         for (BiometricSensor sensor : eligibleSensors) {
             modalities |= sensor.modality;
@@ -474,7 +460,7 @@
                         + ", StrengthRequested: " + mBiometricStrengthRequested
                         + ", CredentialRequested: " + credentialRequested);
         string.append(", Eligible:{");
-        for (BiometricSensor sensor: eligibleSensors) {
+        for (BiometricSensor sensor : eligibleSensors) {
             string.append(sensor.id).append(" ");
         }
         string.append("}");
@@ -489,4 +475,20 @@
         string.append(", ");
         return string.toString();
     }
+
+    @IntDef({AUTHENTICATOR_OK,
+            BIOMETRIC_NO_HARDWARE,
+            BIOMETRIC_DISABLED_BY_DEVICE_POLICY,
+            BIOMETRIC_INSUFFICIENT_STRENGTH,
+            BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE,
+            BIOMETRIC_HARDWARE_NOT_DETECTED,
+            BIOMETRIC_NOT_ENROLLED,
+            BIOMETRIC_NOT_ENABLED_FOR_APPS,
+            CREDENTIAL_NOT_ENROLLED,
+            BIOMETRIC_LOCKOUT_TIMED,
+            BIOMETRIC_LOCKOUT_PERMANENT,
+            BIOMETRIC_SENSOR_PRIVACY_ENABLED})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface AuthenticatorStatus {
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/TEST_MAPPING b/services/core/java/com/android/server/biometrics/TEST_MAPPING
index 36acc3c..8b80674 100644
--- a/services/core/java/com/android/server/biometrics/TEST_MAPPING
+++ b/services/core/java/com/android/server/biometrics/TEST_MAPPING
@@ -2,6 +2,9 @@
     "presubmit": [
         {
             "name": "CtsBiometricsTestCases"
+        },
+        {
+            "name": "CtsBiometricsHostTestCases"
         }
     ]
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index da43618..d584c99 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -120,7 +120,7 @@
 
         // if a final consumer is set it will call destroy/disable on the next value if requested
         if (!mDestroyed && mNextConsumer == null) {
-            disableLightSensorLoggingLocked();
+            disableLightSensorLoggingLocked(false /* destroying */);
         }
     }
 
@@ -130,7 +130,7 @@
 
         // if a final consumer is set it will call destroy/disable on the next value if requested
         if (!mDestroyed && mNextConsumer == null) {
-            disableLightSensorLoggingLocked();
+            disableLightSensorLoggingLocked(true /* destroying */);
             mDestroyed = true;
         }
     }
@@ -177,11 +177,10 @@
         final float current = mLastAmbientLux;
         if (current > -1f) {
             nextConsumer.consume(current);
-        } else if (mDestroyed) {
-            nextConsumer.consume(-1f);
         } else if (mNextConsumer != null) {
             mNextConsumer.add(nextConsumer);
         } else {
+            mDestroyed = false;
             mNextConsumer = nextConsumer;
             enableLightSensorLoggingLocked();
         }
@@ -199,12 +198,14 @@
         resetTimerLocked(true /* start */);
     }
 
-    private void disableLightSensorLoggingLocked() {
+    private void disableLightSensorLoggingLocked(boolean destroying) {
         resetTimerLocked(false /* start */);
 
         if (mEnabled) {
             mEnabled = false;
-            mLastAmbientLux = -1;
+            if (!destroying) {
+                mLastAmbientLux = -1;
+            }
             mSensorManager.unregisterListener(mLightSensorListener);
             Slog.v(TAG, "Disable ALS: " + mLightSensorListener.hashCode());
         }
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index 23b2714..d456736 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -44,7 +44,7 @@
 /**
  * A default provider for {@link BiometricContext}.
  */
-final class BiometricContextProvider implements BiometricContext {
+public final class BiometricContextProvider implements BiometricContext {
 
     private static final String TAG = "BiometricContextProvider";
 
@@ -83,7 +83,8 @@
     private boolean mIsAwake = false;
 
     @VisibleForTesting
-    BiometricContextProvider(@NonNull AmbientDisplayConfiguration ambientDisplayConfiguration,
+    public BiometricContextProvider(
+            @NonNull AmbientDisplayConfiguration ambientDisplayConfiguration,
             @NonNull IStatusBarService service, @Nullable Handler handler,
             AuthSessionCoordinator authSessionCoordinator) {
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
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/AuthResultCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java
index bdae5f3..a48a9d1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java
@@ -75,7 +75,10 @@
      * Adds auth success for a given strength to the current operation list.
      */
     void authenticatedFor(@Authenticators.Types int strength) {
-        updateState(strength, (old) -> AUTHENTICATOR_UNLOCKED | old);
+        // Only strong unlocks matter.
+        if (strength == Authenticators.BIOMETRIC_STRONG) {
+            updateState(strength, (old) -> AUTHENTICATOR_UNLOCKED | old);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java
index 5bc9d23..1aee5d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java
@@ -75,6 +75,7 @@
         mUserId = userId;
         mIsAuthenticating = true;
         mAuthOperations.clear();
+        mTimedLockouts.clear();
         mAuthResultCoordinator = new AuthResultCoordinator();
         mRingBuffer.addApiCall("internal : onAuthSessionStarted(" + userId + ")");
     }
@@ -88,7 +89,6 @@
      */
     void endAuthSession() {
         if (mIsAuthenticating) {
-            mAuthOperations.clear();
             final long currentTime = mClock.millis();
             for (Pair<Integer, Long> timedLockouts : mTimedLockouts) {
                 mMultiBiometricLockoutState.increaseLockoutTime(mUserId, timedLockouts.first,
@@ -109,16 +109,24 @@
                 }
 
             }
+
             mRingBuffer.addApiCall("internal : onAuthSessionEnded(" + mUserId + ")");
-            mIsAuthenticating = false;
+            clearSession();
         }
     }
 
+    private void clearSession() {
+        mIsAuthenticating = false;
+        mTimedLockouts.clear();
+        mAuthOperations.clear();
+    }
+
     /**
-     * @return true if a user can authenticate with a given strength.
+     * Returns the current lockout state for a given user/strength.
      */
-    public boolean getCanAuthFor(int userId, @Authenticators.Types int strength) {
-        return mMultiBiometricLockoutState.canUserAuthenticate(userId, strength);
+    @LockoutTracker.LockoutMode
+    public int getLockoutStateFor(int userId, @Authenticators.Types int strength) {
+        return mMultiBiometricLockoutState.getLockoutState(userId, strength);
     }
 
     @Override
@@ -145,19 +153,8 @@
     }
 
     @Override
-    public void authenticatedFor(int userId, @Authenticators.Types int biometricStrength,
-            int sensorId, long requestId) {
-        final String authStr =
-                "authenticatedFor(userId=" + userId + ", strength=" + biometricStrength
-                        + " , sensorId=" + sensorId + ", requestId= " + requestId + ")";
-        mRingBuffer.addApiCall(authStr);
-        mAuthResultCoordinator.authenticatedFor(biometricStrength);
-        attemptToFinish(userId, sensorId, authStr);
-    }
-
-    @Override
-    public void lockedOutFor(int userId, @Authenticators.Types int biometricStrength,
-            int sensorId, long requestId) {
+    public void lockedOutFor(int userId, @Authenticators.Types int biometricStrength, int sensorId,
+            long requestId) {
         final String lockedOutStr =
                 "lockOutFor(userId=" + userId + ", biometricStrength=" + biometricStrength
                         + ", sensorId=" + sensorId + ", requestId=" + requestId + ")";
@@ -179,12 +176,16 @@
     }
 
     @Override
-    public void authEndedFor(int userId, @Authenticators.Types int biometricStrength,
-            int sensorId, long requestId) {
+    public void authEndedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId,
+            long requestId, boolean wasSuccessful) {
         final String authEndedStr =
                 "authEndedFor(userId=" + userId + " ,biometricStrength=" + biometricStrength
-                        + ", sensorId=" + sensorId + ", requestId=" + requestId + ")";
+                        + ", sensorId=" + sensorId + ", requestId=" + requestId + ", wasSuccessful="
+                        + wasSuccessful + ")";
         mRingBuffer.addApiCall(authEndedStr);
+        if (wasSuccessful) {
+            mAuthResultCoordinator.authenticatedFor(biometricStrength);
+        }
         attemptToFinish(userId, sensorId, authEndedStr);
     }
 
@@ -195,6 +196,12 @@
                 "resetLockoutFor(userId=" + userId + " ,biometricStrength=" + biometricStrength
                         + ", requestId=" + requestId + ")";
         mRingBuffer.addApiCall(resetLockStr);
+        if (biometricStrength == Authenticators.BIOMETRIC_STRONG) {
+            clearSession();
+        } else {
+            // Lockouts cannot be reset by non-strong biometrics
+            return;
+        }
         mMultiBiometricLockoutState.setAuthenticatorTo(userId, biometricStrength,
                 true /*canAuthenticate */);
         mMultiBiometricLockoutState.clearLockoutTime(userId, biometricStrength);
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java b/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java
index d97f793..6bddf14 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java
@@ -28,16 +28,10 @@
     void authStartedFor(int userId, int sensorId, long requestId);
 
     /**
-     * Indicates a successful authentication occurred for a sensor of a given strength.
-     */
-    void authenticatedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId,
-            long requestId);
-
-    /**
      * Indicates authentication ended for a sensor of a given strength.
      */
     void authEndedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId,
-            long requestId);
+            long requestId, boolean wasSuccessful);
 
     /**
      * Indicates a lockout occurred for a sensor of a given strength.
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 8a24ff6..57d28f9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -81,9 +81,12 @@
     @State
     protected int mState = STATE_NEW;
     private long mStartTimeMs;
-
     private boolean mAuthAttempted;
     private boolean mAuthSuccess = false;
+    private final int mSensorStrength;
+    // This is used to determine if we should use the old lockout counter (HIDL) or the new lockout
+    // counter implementation (AIDL)
+    private final boolean mShouldUseLockoutTracker;
 
     public AuthenticationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
@@ -92,7 +95,7 @@
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener,
             @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
-            boolean shouldVibrate, boolean isKeyguardBypassEnabled) {
+            boolean shouldVibrate, boolean isKeyguardBypassEnabled, int sensorStrength) {
         super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId,
                 shouldVibrate, biometricLogger, biometricContext);
         mIsStrongBiometric = isStrongBiometric;
@@ -105,22 +108,13 @@
         mIsRestricted = restricted;
         mAllowBackgroundAuthentication = allowBackgroundAuthentication;
         mIsKeyguardBypassEnabled = isKeyguardBypassEnabled;
+        mShouldUseLockoutTracker = lockoutTracker != null;
+        mSensorStrength = sensorStrength;
     }
 
     @LockoutTracker.LockoutMode
     public int handleFailedAttempt(int userId) {
-        @LockoutTracker.LockoutMode final int lockoutMode =
-                mLockoutTracker.getLockoutModeForUser(userId);
-        final PerformanceTracker performanceTracker =
-                PerformanceTracker.getInstanceForSensorId(getSensorId());
-
-        if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
-            performanceTracker.incrementPermanentLockoutForUser(userId);
-        } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
-            performanceTracker.incrementTimedLockoutForUser(userId);
-        }
-
-        return lockoutMode;
+        return LockoutTracker.LOCKOUT_NONE;
     }
 
     protected long getStartTimeMs() {
@@ -273,10 +267,12 @@
                 cancel();
             } else {
                 // Allow system-defined limit of number of attempts before giving up
-                @LockoutTracker.LockoutMode final int lockoutMode =
-                        handleFailedAttempt(getTargetUserId());
-                if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
-                    markAlreadyDone();
+                if (mShouldUseLockoutTracker) {
+                    @LockoutTracker.LockoutMode final int lockoutMode =
+                            handleFailedAttempt(getTargetUserId());
+                    if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
+                        markAlreadyDone();
+                    }
                 }
 
                 try {
@@ -309,13 +305,6 @@
     @Override
     public void onAcquired(int acquiredInfo, int vendorCode) {
         super.onAcquired(acquiredInfo, vendorCode);
-
-        @LockoutTracker.LockoutMode final int lockoutMode =
-                mLockoutTracker.getLockoutModeForUser(getTargetUserId());
-        if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
-            PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
-            pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
-        }
     }
 
     @Override
@@ -331,8 +320,14 @@
     public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
-        @LockoutTracker.LockoutMode final int lockoutMode =
-                mLockoutTracker.getLockoutModeForUser(getTargetUserId());
+        final @LockoutTracker.LockoutMode int lockoutMode;
+        if (mShouldUseLockoutTracker) {
+            lockoutMode = mLockoutTracker.getLockoutModeForUser(getTargetUserId());
+        } else {
+            lockoutMode = getBiometricContext().getAuthSessionCoordinator()
+                    .getLockoutStateFor(getTargetUserId(), mSensorStrength);
+        }
+
         if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
             Slog.v(TAG, "In lockout mode(" + lockoutMode + ") ; disallowing authentication");
             int errorCode = lockoutMode == LockoutTracker.LOCKOUT_TIMED
@@ -406,6 +401,14 @@
         return mAuthSuccess;
     }
 
+    protected int getSensorStrength() {
+        return mSensorStrength;
+    }
+
+    protected LockoutTracker getLockoutTracker() {
+        return mLockoutTracker;
+    }
+
     protected int getShowOverlayReason() {
         if (isKeyguard()) {
             return BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
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/MultiBiometricLockoutState.java b/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java
index 6605d49..c24a989 100644
--- a/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java
+++ b/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java
@@ -75,6 +75,9 @@
                 // fall through
             case Authenticators.BIOMETRIC_CONVENIENCE:
                 authMap.get(BIOMETRIC_CONVENIENCE).mPermanentlyLockedOut = !canAuth;
+                return;
+            default:
+                Slog.e(TAG, "increaseLockoutTime called for invalid strength : "  + strength);
         }
     }
 
@@ -89,6 +92,9 @@
                 // fall through
             case Authenticators.BIOMETRIC_CONVENIENCE:
                 authMap.get(BIOMETRIC_CONVENIENCE).increaseLockoutTo(duration);
+                return;
+            default:
+                Slog.e(TAG, "increaseLockoutTime called for invalid strength : "  + strength);
         }
     }
 
@@ -103,22 +109,34 @@
                 // fall through
             case Authenticators.BIOMETRIC_CONVENIENCE:
                 authMap.get(BIOMETRIC_CONVENIENCE).setTimedLockout(0);
+                return;
+            default:
+                Slog.e(TAG, "clearLockoutTime called for invalid strength : "  + strength);
         }
     }
 
     /**
-     * Indicates if a user can perform an authentication operation with a given
-     * {@link Authenticators.Types}
+     * Retrieves the lockout state for a user of a specified strength.
      *
      * @param userId   The user.
      * @param strength The strength of biometric that is requested to authenticate.
-     * @return If a user can authenticate with a given biometric of this strength.
      */
-    boolean canUserAuthenticate(int userId, @Authenticators.Types int strength) {
-        final boolean canAuthenticate = getAuthMapForUser(userId).get(strength).canAuthenticate();
-        Slog.d(TAG, "canUserAuthenticate(userId=" + userId + ", strength=" + strength + ") ="
-                + canAuthenticate);
-        return canAuthenticate;
+    @LockoutTracker.LockoutMode
+    int getLockoutState(int userId, @Authenticators.Types int strength) {
+        final Map<Integer, AuthenticatorState> authMap = getAuthMapForUser(userId);
+        if (!authMap.containsKey(strength)) {
+            Slog.e(TAG, "Error, getLockoutState for unknown strength: " + strength
+                    + " returning LOCKOUT_NONE");
+            return LockoutTracker.LOCKOUT_NONE;
+        }
+        final AuthenticatorState state = authMap.get(strength);
+        if (state.mPermanentlyLockedOut) {
+            return LockoutTracker.LOCKOUT_PERMANENT;
+        } else if (state.isTimedLockout()) {
+            return LockoutTracker.LOCKOUT_TIMED;
+        } else {
+            return LockoutTracker.LOCKOUT_NONE;
+        }
     }
 
     @Override
@@ -152,7 +170,15 @@
         }
 
         boolean canAuthenticate() {
-            return !mPermanentlyLockedOut && mClock.millis() - mTimedLockout >= 0;
+            return !mPermanentlyLockedOut && !isTimedLockout();
+        }
+
+        boolean isTimedLockout() {
+            return mClock.millis() - mTimedLockout < 0;
+        }
+
+        void setTimedLockout(long duration) {
+            mTimedLockout = duration;
         }
 
         /**
@@ -162,10 +188,6 @@
             mTimedLockout = Math.max(mTimedLockout, duration);
         }
 
-        void setTimedLockout(long duration) {
-            mTimedLockout = duration;
-        }
-
         String toString(long currentTime) {
             final String duration =
                     mTimedLockout - currentTime > 0 ? (mTimedLockout - currentTime) + "ms" : "none";
diff --git a/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java b/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
index 42b22b0..eed2bdd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
@@ -85,7 +85,7 @@
         }
     }
 
-    void incrementAcquireForUser(int userId, boolean isCrypto) {
+    public void incrementAcquireForUser(int userId, boolean isCrypto) {
         createUserEntryIfNecessary(userId);
 
         if (isCrypto) {
@@ -95,13 +95,13 @@
         }
     }
 
-    void incrementTimedLockoutForUser(int userId) {
+    public void incrementTimedLockoutForUser(int userId) {
         createUserEntryIfNecessary(userId);
 
         mAllUsersInfo.get(userId).mTimedLockout++;
     }
 
-    void incrementPermanentLockoutForUser(int userId) {
+    public void incrementPermanentLockoutForUser(int userId) {
         createUserEntryIfNecessary(userId);
 
         mAllUsersInfo.get(userId).mPermanentLockout++;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 7a5b584..cb409fe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -99,6 +99,8 @@
         @Override
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
                 @NonNull String opPackageName) {
+            super.createTestSession_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
 
             if (provider == null) {
@@ -112,6 +114,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
+            super.dumpSensorServiceStateProto_enforcePermission();
+
             final ProtoOutputStream proto = new ProtoOutputStream();
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider != null) {
@@ -125,6 +129,8 @@
         @Override // Binder call
         public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
                 String opPackageName) {
+            super.getSensorPropertiesInternal_enforcePermission();
+
             return mRegistry.getAllProperties();
         }
 
@@ -132,6 +138,8 @@
         @Override // Binder call
         public FaceSensorPropertiesInternal getSensorProperties(int sensorId,
                 @NonNull String opPackageName) {
+            super.getSensorProperties_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
@@ -146,6 +154,8 @@
         @Override // Binder call
         public void generateChallenge(IBinder token, int sensorId, int userId,
                 IFaceServiceReceiver receiver, String opPackageName) {
+            super.generateChallenge_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
@@ -159,6 +169,8 @@
         @Override // Binder call
         public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
                 long challenge) {
+            super.revokeChallenge_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
@@ -173,6 +185,8 @@
         public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
+            super.enroll_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for enroll");
@@ -186,6 +200,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void scheduleWatchdog() {
+            super.scheduleWatchdog_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for scheduling watchdog");
@@ -201,12 +217,16 @@
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 final int[] disabledFeatures) {
             // TODO(b/145027036): Implement this.
+            super.enrollRemotely_enforcePermission();
+
             return -1;
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
         @Override // Binder call
         public void cancelEnrollment(final IBinder token, long requestId) {
+            super.cancelEnrollment_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelEnrollment");
@@ -224,6 +244,8 @@
             // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
             //  lockdown, something wrong happened. See similar path in FingerprintService.
 
+            super.authenticate_enforcePermission();
+
             final boolean restricted = false; // Face APIs are private
             final int statsClient = Utils.isKeyguard(getContext(), opPackageName)
                     ? BiometricsProtoEnums.CLIENT_KEYGUARD
@@ -249,18 +271,13 @@
         @Override // Binder call
         public long detectFace(final IBinder token, final int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName) {
+            super.detectFace_enforcePermission();
+
             if (!Utils.isKeyguard(getContext(), opPackageName)) {
                 Slog.w(TAG, "detectFace called from non-sysui package: " + opPackageName);
                 return -1;
             }
 
-            if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
-                // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
-                // ever be invoked when the user is encrypted or lockdown.
-                Slog.e(TAG, "detectFace invoked when user is not encrypted or lockdown");
-                return -1;
-            }
-
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for detectFace");
@@ -278,6 +295,8 @@
                 IBinder token, long operationId, int userId,
                 IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
                 int cookie, boolean allowBackgroundAuthentication) {
+            super.prepareForAuthentication_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for prepareForAuthentication");
@@ -295,6 +314,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public void startPreparedClient(int sensorId, int cookie) {
+            super.startPreparedClient_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for startPreparedClient");
@@ -308,6 +329,8 @@
         @Override // Binder call
         public void cancelAuthentication(final IBinder token, final String opPackageName,
                 final long requestId) {
+            super.cancelAuthentication_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelAuthentication");
@@ -321,6 +344,8 @@
         @Override // Binder call
         public void cancelFaceDetect(final IBinder token, final String opPackageName,
                 final long requestId) {
+            super.cancelFaceDetect_enforcePermission();
+
             if (!Utils.isKeyguard(getContext(), opPackageName)) {
                 Slog.w(TAG, "cancelFaceDetect called from non-sysui package: "
                         + opPackageName);
@@ -340,6 +365,8 @@
         @Override // Binder call
         public void cancelAuthenticationFromService(int sensorId, final IBinder token,
                 final String opPackageName, final long requestId) {
+            super.cancelAuthenticationFromService_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
@@ -353,6 +380,8 @@
         @Override // Binder call
         public void remove(final IBinder token, final int faceId, final int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName) {
+            super.remove_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for remove");
@@ -367,6 +396,8 @@
         @Override // Binder call
         public void removeAll(final IBinder token, final int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName) {
+            super.removeAll_enforcePermission();
+
             final FaceServiceReceiver internalReceiver = new FaceServiceReceiver() {
                 int sensorsFinishedRemoving = 0;
                 final int numSensors = getSensorPropertiesInternal(
@@ -399,6 +430,8 @@
         @Override // Binder call
         public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
                 final String opPackageName) {
+            super.addLockoutResetCallback_enforcePermission();
+
             mLockoutResetDispatcher.addCallback(callback, opPackageName);
         }
 
@@ -458,6 +491,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public boolean isHardwareDetected(int sensorId, String opPackageName) {
+            super.isHardwareDetected_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
                 final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
@@ -474,6 +509,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public List<Face> getEnrolledFaces(int sensorId, int userId, String opPackageName) {
+            super.getEnrolledFaces_enforcePermission();
+
             if (userId != UserHandle.getCallingUserId()) {
                 Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
             }
@@ -490,6 +527,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) {
+            super.hasEnrolledFaces_enforcePermission();
+
             if (userId != UserHandle.getCallingUserId()) {
                 Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
             }
@@ -506,6 +545,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
+            super.getLockoutModeForUser_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getLockoutModeForUser");
@@ -519,6 +560,8 @@
         @Override
         public void invalidateAuthenticatorId(int sensorId, int userId,
                 IInvalidationCallback callback) {
+            super.invalidateAuthenticatorId_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
@@ -531,6 +574,8 @@
         @Override // Binder call
         public long getAuthenticatorId(int sensorId, int userId) {
 
+            super.getAuthenticatorId_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getAuthenticatorId");
@@ -544,6 +589,8 @@
         @Override // Binder call
         public void resetLockout(IBinder token, int sensorId, int userId, byte[] hardwareAuthToken,
                 String opPackageName) {
+            super.resetLockout_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
@@ -558,6 +605,8 @@
         public void setFeature(final IBinder token, int userId, int feature, boolean enabled,
                 final byte[] hardwareAuthToken, IFaceServiceReceiver receiver,
                 final String opPackageName) {
+            super.setFeature_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for setFeature");
@@ -572,6 +621,8 @@
         @Override
         public void getFeature(final IBinder token, int userId, int feature,
                 IFaceServiceReceiver receiver, final String opPackageName) {
+            super.getFeature_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getFeature");
@@ -615,6 +666,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         public void registerAuthenticators(
                 @NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
+            super.registerAuthenticators_enforcePermission();
+
             mRegistry.registerAll(() -> {
                 final List<ServiceProvider> providers = new ArrayList<>();
                 for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index cfbb5dc..d11f099 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -22,6 +22,7 @@
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.face.AuthenticationFrame;
 import android.hardware.biometrics.face.BaseFrame;
+import android.hardware.biometrics.face.EnrollmentFrame;
 import android.hardware.face.Face;
 import android.hardware.face.FaceAuthenticationFrame;
 import android.hardware.face.FaceEnrollFrame;
@@ -33,6 +34,7 @@
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.EnrollClient;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
 import java.util.HashSet;
@@ -139,6 +141,8 @@
     @Override
     public void setTestHalEnabled(boolean enabled) {
 
+        super.setTestHalEnabled_enforcePermission();
+
         mProvider.setTestHalEnabled(enabled);
         mSensor.setTestHalEnabled(enabled);
     }
@@ -147,6 +151,8 @@
     @Override
     public void startEnroll(int userId) {
 
+        super.startEnroll_enforcePermission();
+
         mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
                 mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
                 null /* previewSurface */, false /* debugConsent */);
@@ -156,6 +162,8 @@
     @Override
     public void finishEnroll(int userId) {
 
+        super.finishEnroll_enforcePermission();
+
         int nextRandomId = mRandom.nextInt();
         while (mEnrollmentIds.contains(nextRandomId)) {
             nextRandomId = mRandom.nextInt();
@@ -171,6 +179,8 @@
     public void acceptAuthentication(int userId)  {
 
         // Fake authentication with any of the existing faces
+        super.acceptAuthentication_enforcePermission();
+
         List<Face> faces = FaceUtils.getInstance(mSensorId)
                 .getBiometricsForUser(mContext, userId);
         if (faces.isEmpty()) {
@@ -186,30 +196,38 @@
     @Override
     public void rejectAuthentication(int userId)  {
 
+        super.rejectAuthentication_enforcePermission();
+
         mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    // TODO(b/178414967): replace with notifyAuthenticationFrame and notifyEnrollmentFrame.
     @Override
     public void notifyAcquired(int userId, int acquireInfo) {
+        super.notifyAcquired_enforcePermission();
 
         BaseFrame data = new BaseFrame();
         data.acquiredInfo = (byte) acquireInfo;
 
-        AuthenticationFrame authenticationFrame = new AuthenticationFrame();
-        authenticationFrame.data = data;
-
-        // TODO(b/178414967): Currently onAuthenticationFrame and onEnrollmentFrame are the same.
-        // This will need to call the correct callback once the onAcquired callback is removed.
-        mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFrame(
-                authenticationFrame);
+        if (mSensor.getScheduler().getCurrentClient() instanceof EnrollClient) {
+            final EnrollmentFrame frame = new EnrollmentFrame();
+            frame.data = data;
+            mSensor.getSessionForUser(userId).getHalSessionCallback()
+                    .onEnrollmentFrame(frame);
+        } else {
+            final AuthenticationFrame frame = new AuthenticationFrame();
+            frame.data = data;
+            mSensor.getSessionForUser(userId).getHalSessionCallback()
+                    .onAuthenticationFrame(frame);
+        }
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
     @Override
     public void notifyError(int userId, int errorCode)  {
 
+        super.notifyError_enforcePermission();
+
         mSensor.getSessionForUser(userId).getHalSessionCallback().onError((byte) errorCode,
                 0 /* vendorCode */);
     }
@@ -218,6 +236,8 @@
     @Override
     public void cleanupInternalState(int userId)  {
 
+        super.cleanupInternalState_enforcePermission();
+
         Slog.d(TAG, "cleanupInternalState: " + userId);
         mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
             @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index c27d71f..b1cb257 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -47,7 +47,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
-import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.face.UsageStats;
 
 import java.util.ArrayList;
@@ -63,8 +63,6 @@
     @NonNull
     private final UsageStats mUsageStats;
     @NonNull
-    private final LockoutCache mLockoutCache;
-    @NonNull
     private final AuthSessionCoordinator mAuthSessionCoordinator;
     @Nullable
     private final NotificationManager mNotificationManager;
@@ -72,7 +70,6 @@
     private final int[] mBiometricPromptIgnoreListVendor;
     private final int[] mKeyguardIgnoreList;
     private final int[] mKeyguardIgnoreListVendor;
-    private final int mBiometricStrength;
     @Nullable
     private ICancellationSignal mCancellationSignal;
     @Nullable
@@ -88,12 +85,12 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
             @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
-            boolean isKeyguardBypassEnabled, @Authenticators.Types int biometricStrength) {
+            boolean isKeyguardBypassEnabled, @Authenticators.Types int sensorStrength) {
         this(context, lazyDaemon, token, requestId, listener, targetUserId, operationId,
                 restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
-                isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
-                isKeyguardBypassEnabled, context.getSystemService(SensorPrivacyManager.class),
-                biometricStrength);
+                isStrongBiometric, usageStats, lockoutCache /* lockoutCache */,
+                allowBackgroundAuthentication, isKeyguardBypassEnabled,
+                context.getSystemService(SensorPrivacyManager.class), sensorStrength);
     }
 
     @VisibleForTesting
@@ -109,13 +106,11 @@
             @Authenticators.Types int biometricStrength) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
                 owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
-                isStrongBiometric, null /* taskStackListener */, lockoutCache,
-                allowBackgroundAuthentication,
-                false /* shouldVibrate */,
-                isKeyguardBypassEnabled);
+                isStrongBiometric, null /* taskStackListener */, null /* lockoutCache */,
+                allowBackgroundAuthentication, false /* shouldVibrate */,
+                isKeyguardBypassEnabled, biometricStrength);
         setRequestId(requestId);
         mUsageStats = usageStats;
-        mLockoutCache = lockoutCache;
         mNotificationManager = context.getSystemService(NotificationManager.class);
         mSensorPrivacyManager = sensorPrivacyManager;
         mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
@@ -129,14 +124,12 @@
                 R.array.config_face_acquire_keyguard_ignorelist);
         mKeyguardIgnoreListVendor = resources.getIntArray(
                 R.array.config_face_acquire_vendor_keyguard_ignorelist);
-        mBiometricStrength = biometricStrength;
     }
 
     @Override
     public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         mState = STATE_STARTED;
-        mAuthSessionCoordinator.authStartedFor(getTargetUserId(), getSensorId(), getRequestId());
     }
 
     @NonNull
@@ -221,9 +214,6 @@
                 0 /* error */,
                 0 /* vendorError */,
                 getTargetUserId()));
-        mAuthSessionCoordinator
-                .authenticatedFor(getTargetUserId(), mBiometricStrength, getSensorId(),
-                        getRequestId());
     }
 
     @Override
@@ -239,8 +229,6 @@
         if (error == BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL) {
             BiometricNotificationUtils.showReEnrollmentNotification(getContext());
         }
-        mAuthSessionCoordinator.authEndedFor(getTargetUserId(), mBiometricStrength, getSensorId(),
-                getRequestId());
         super.onError(error, vendorCode);
     }
 
@@ -263,6 +251,8 @@
         mLastAcquire = acquireInfo;
         final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
         onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
+        PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
+        pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
     }
 
     /**
@@ -290,35 +280,39 @@
 
     @Override
     public void onLockoutTimed(long durationMillis) {
-        mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
+        mAuthSessionCoordinator.lockOutTimed(getTargetUserId(), getSensorStrength(), getSensorId(),
+                durationMillis, getRequestId());
         // Lockout metrics are logged as an error code.
         final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT;
         getLogger().logOnError(getContext(), getOperationContext(),
                 error, 0 /* vendorCode */, getTargetUserId());
 
+        PerformanceTracker.getInstanceForSensorId(getSensorId())
+                .incrementTimedLockoutForUser(getTargetUserId());
+
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
-        mAuthSessionCoordinator.lockOutTimed(getTargetUserId(), mBiometricStrength, getSensorId(),
-                durationMillis, getRequestId());
     }
 
     @Override
     public void onLockoutPermanent() {
-        mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
+        mAuthSessionCoordinator.lockedOutFor(getTargetUserId(), getSensorStrength(), getSensorId(),
+                getRequestId());
         // Lockout metrics are logged as an error code.
         final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
         getLogger().logOnError(getContext(), getOperationContext(),
                 error, 0 /* vendorCode */, getTargetUserId());
 
+        PerformanceTracker.getInstanceForSensorId(getSensorId())
+                .incrementPermanentLockoutForUser(getTargetUserId());
+
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
-        mAuthSessionCoordinator.lockedOutFor(getTargetUserId(), mBiometricStrength, getSensorId(),
-                getRequestId());
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 6488185..89852a1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -50,10 +50,11 @@
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
@@ -82,20 +83,34 @@
 
     private boolean mTestHalEnabled;
 
-    @NonNull private final Context mContext;
-    @NonNull private final BiometricStateCallback mBiometricStateCallback;
-    @NonNull private final String mHalInstanceName;
-    @NonNull @VisibleForTesting
+    @NonNull
+    @VisibleForTesting
     final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
-    @NonNull private final Handler mHandler;
-    @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
-    @NonNull private final UsageStats mUsageStats;
-    @NonNull private final ActivityTaskManager mActivityTaskManager;
-    @NonNull private final BiometricTaskStackListener mTaskStackListener;
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final BiometricStateCallback mBiometricStateCallback;
+    @NonNull
+    private final String mHalInstanceName;
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
+    @NonNull
+    private final UsageStats mUsageStats;
+    @NonNull
+    private final ActivityTaskManager mActivityTaskManager;
+    @NonNull
+    private final BiometricTaskStackListener mTaskStackListener;
     // for requests that do not use biometric prompt
-    @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
-    @NonNull private final BiometricContext mBiometricContext;
-    @Nullable private IFace mDaemon;
+    @NonNull
+    private final AtomicLong mRequestCounter = new AtomicLong(0);
+    @NonNull
+    private final BiometricContext mBiometricContext;
+    @NonNull
+    private final AuthSessionCoordinator mAuthSessionCoordinator;
+    @Nullable
+    private IFace mDaemon;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
         @Override
@@ -141,6 +156,7 @@
         mActivityTaskManager = ActivityTaskManager.getInstance();
         mTaskStackListener = new BiometricTaskStackListener();
         mBiometricContext = biometricContext;
+        mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
 
         for (SensorProps prop : props) {
             final int sensorId = prop.commonProps.sensorId;
@@ -312,7 +328,8 @@
 
     @Override
     public int getLockoutModeForUser(int sensorId, int userId) {
-        return mSensors.get(sensorId).getLockoutCache().getLockoutModeForUser(userId);
+        return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+                Utils.getCurrentStrength(sensorId));
     }
 
     @Override
@@ -423,7 +440,6 @@
             boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
         mHandler.post(() -> {
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
-            final int biometricStrength = Utils.getCurrentStrength(sensorId);
             final FaceAuthenticationClient client = new FaceAuthenticationClient(
                     mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
                     userId, operationId, restricted, opPackageName, cookie,
@@ -431,8 +447,24 @@
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
                     mUsageStats, mSensors.get(sensorId).getLockoutCache(),
-                    allowBackgroundAuthentication, isKeyguardBypassEnabled, biometricStrength);
-            scheduleForSensor(sensorId, client, mBiometricStateCallback);
+                    allowBackgroundAuthentication, isKeyguardBypassEnabled,
+                    Utils.getCurrentStrength(sensorId)
+                    );
+            scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
+                @Override
+                public void onClientStarted(
+                         BaseClientMonitor clientMonitor) {
+                    mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+                }
+
+                @Override
+                public void onClientFinished(
+                        BaseClientMonitor clientMonitor,
+                        boolean success) {
+                    mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
+                            sensorId, requestId, success);
+                }
+            });
         });
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index 32bed48..759c52a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -89,9 +89,9 @@
     void onLockoutCleared() {
         resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache,
                 mLockoutResetDispatcher);
+        mCallback.onClientFinished(this, true /* success */);
         getBiometricContext().getAuthSessionCoordinator()
                 .resetLockoutFor(getTargetUserId(), mBiometricStrength, getRequestId());
-        mCallback.onClientFinished(this, true /* success */);
     }
 
     public boolean interruptsPrecedingClients() {
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..0d30ddd 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 != null && 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/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
index 7a6a274f..151ffaa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
@@ -130,6 +130,8 @@
     @Override
     public void setTestHalEnabled(boolean enabled) {
 
+        super.setTestHalEnabled_enforcePermission();
+
         mFace10.setTestHalEnabled(enabled);
     }
 
@@ -137,6 +139,8 @@
     @Override
     public void startEnroll(int userId) {
 
+        super.startEnroll_enforcePermission();
+
         mFace10.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
                 mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
                 null /* previewSurface */, false /* debugConsent */);
@@ -146,6 +150,8 @@
     @Override
     public void finishEnroll(int userId) {
 
+        super.finishEnroll_enforcePermission();
+
         int nextRandomId = mRandom.nextInt();
         while (mEnrollmentIds.contains(nextRandomId)) {
             nextRandomId = mRandom.nextInt();
@@ -161,6 +167,8 @@
     public void acceptAuthentication(int userId) {
 
         // Fake authentication with any of the existing fingers
+        super.acceptAuthentication_enforcePermission();
+
         List<Face> faces = FaceUtils.getLegacyInstance(mSensorId)
                 .getBiometricsForUser(mContext, userId);
         if (faces.isEmpty()) {
@@ -176,6 +184,8 @@
     @Override
     public void rejectAuthentication(int userId) {
 
+        super.rejectAuthentication_enforcePermission();
+
         mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* faceId */, userId, null);
     }
 
@@ -183,6 +193,8 @@
     @Override
     public void notifyAcquired(int userId, int acquireInfo) {
 
+        super.notifyAcquired_enforcePermission();
+
         mHalResultController.onAcquired(0 /* deviceId */, userId, acquireInfo, 0 /* vendorCode */);
     }
 
@@ -190,6 +202,8 @@
     @Override
     public void notifyError(int userId, int errorCode) {
 
+        super.notifyError_enforcePermission();
+
         mHalResultController.onError(0 /* deviceId */, userId, errorCode, 0 /* vendorCode */);
     }
 
@@ -197,6 +211,8 @@
     @Override
     public void cleanupInternalState(int userId) {
 
+        super.cleanupInternalState_enforcePermission();
+
         mFace10.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
             @Override
             public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 0e0ee19..1adc5e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -677,8 +677,9 @@
                     opPackageName, cookie, false /* requireConfirmation */, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric, mLockoutTracker,
-                    mUsageStats, allowBackgroundAuthentication, isKeyguardBypassEnabled);
-            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+                    mUsageStats, allowBackgroundAuthentication, isKeyguardBypassEnabled,
+                    Utils.getCurrentStrength(mSensorId));
+            mScheduler.scheduleClientMonitor(client);
         });
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 91eec7d..d4a7f08 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -23,6 +23,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
@@ -39,6 +40,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.face.UsageStats;
 
 import java.util.ArrayList;
@@ -70,12 +72,12 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
             @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
-            boolean isKeyguardBypassEnabled) {
+            boolean isKeyguardBypassEnabled, @Authenticators.Types int sensorStrength) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
                 owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
                 isStrongBiometric, null /* taskStackListener */,
                 lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */,
-                isKeyguardBypassEnabled);
+                isKeyguardBypassEnabled, sensorStrength);
         setRequestId(requestId);
         mUsageStats = usageStats;
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
@@ -154,6 +156,21 @@
     }
 
     @Override
+    public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
+        @LockoutTracker.LockoutMode final int lockoutMode =
+                getLockoutTracker().getLockoutModeForUser(userId);
+        final PerformanceTracker performanceTracker =
+                PerformanceTracker.getInstanceForSensorId(getSensorId());
+        if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
+            performanceTracker.incrementPermanentLockoutForUser(userId);
+        } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
+            performanceTracker.incrementTimedLockoutForUser(userId);
+        }
+
+        return lockoutMode;
+    }
+
+    @Override
     public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
             boolean authenticated, ArrayList<Byte> token) {
         super.onAuthenticated(identifier, authenticated, token);
@@ -204,6 +221,12 @@
         if (acquireInfo == FaceManager.FACE_ACQUIRED_RECALIBRATE) {
             BiometricNotificationUtils.showReEnrollmentNotification(getContext());
         }
+        @LockoutTracker.LockoutMode final int lockoutMode =
+                getLockoutTracker().getLockoutModeForUser(getTargetUserId());
+        if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
+            PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
+            pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
+        }
 
         final boolean shouldSend = shouldSend(acquireInfo, vendorCode);
         onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
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 156e6bb..229393d 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
@@ -43,6 +43,7 @@
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -133,6 +134,8 @@
         @Override
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
                 @NonNull String opPackageName) {
+            super.createTestSession_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
 
             if (provider == null) {
@@ -146,6 +149,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
+            super.dumpSensorServiceStateProto_enforcePermission();
+
             final ProtoOutputStream proto = new ProtoOutputStream();
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider != null) {
@@ -169,6 +174,8 @@
         @Override
         public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId,
                 @NonNull String opPackageName) {
+            super.getSensorProperties_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
@@ -182,6 +189,8 @@
         @Override // Binder call
         public void generateChallenge(IBinder token, int sensorId, int userId,
                 IFingerprintServiceReceiver receiver, String opPackageName) {
+            super.generateChallenge_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
@@ -195,6 +204,8 @@
         @Override // Binder call
         public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
                 long challenge) {
+            super.revokeChallenge_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
@@ -210,6 +221,8 @@
         public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
                 final int userId, final IFingerprintServiceReceiver receiver,
                 final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
+            super.enroll_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for enroll");
@@ -223,6 +236,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
         @Override // Binder call
         public void cancelEnrollment(final IBinder token, long requestId) {
+            super.cancelEnrollment_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelEnrollment");
@@ -399,18 +414,13 @@
         @Override
         public long detectFingerprint(final IBinder token, final int userId,
                 final IFingerprintServiceReceiver receiver, final String opPackageName) {
+            super.detectFingerprint_enforcePermission();
+
             if (!Utils.isKeyguard(getContext(), opPackageName)) {
                 Slog.w(TAG, "detectFingerprint called from non-sysui package: " + opPackageName);
                 return -1;
             }
 
-            if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
-                // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
-                // ever be invoked when the user is encrypted or lockdown.
-                Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
-                return -1;
-            }
-
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for detectFingerprint");
@@ -427,6 +437,8 @@
         public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
                 int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
                 long requestId, int cookie, boolean allowBackgroundAuthentication) {
+            super.prepareForAuthentication_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for prepareForAuthentication");
@@ -443,6 +455,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
         @Override // Binder call
         public void startPreparedClient(int sensorId, int cookie) {
+            super.startPreparedClient_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for startPreparedClient");
@@ -486,6 +500,8 @@
         @Override // Binder call
         public void cancelFingerprintDetect(final IBinder token, final String opPackageName,
                 final long requestId) {
+            super.cancelFingerprintDetect_enforcePermission();
+
             if (!Utils.isKeyguard(getContext(), opPackageName)) {
                 Slog.w(TAG, "cancelFingerprintDetect called from non-sysui package: "
                         + opPackageName);
@@ -507,6 +523,8 @@
         @Override // Binder call
         public void cancelAuthenticationFromService(final int sensorId, final IBinder token,
                 final String opPackageName, final long requestId) {
+            super.cancelAuthenticationFromService_enforcePermission();
+
             Slog.d(TAG, "cancelAuthenticationFromService, sensorId: " + sensorId);
 
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
@@ -522,6 +540,8 @@
         @Override // Binder call
         public void remove(final IBinder token, final int fingerId, final int userId,
                 final IFingerprintServiceReceiver receiver, final String opPackageName) {
+            super.remove_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for remove");
@@ -536,6 +556,8 @@
         public void removeAll(final IBinder token, final int userId,
                 final IFingerprintServiceReceiver receiver, final String opPackageName) {
 
+            super.removeAll_enforcePermission();
+
             final FingerprintServiceReceiver internalReceiver = new FingerprintServiceReceiver() {
                 int sensorsFinishedRemoving = 0;
                 final int numSensors = getSensorPropertiesInternal(
@@ -568,6 +590,8 @@
         @Override // Binder call
         public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
                 final String opPackageName) {
+            super.addLockoutResetCallback_enforcePermission();
+
             mLockoutResetDispatcher.addCallback(callback, opPackageName);
         }
 
@@ -651,6 +675,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public boolean isHardwareDetected(int sensorId, String opPackageName) {
+            super.isHardwareDetected_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
@@ -663,6 +689,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
         @Override // Binder call
         public void rename(final int fingerId, final int userId, final String name) {
+            super.rename_enforcePermission();
+
             if (!Utils.isCurrentUserOrProfile(getContext(), userId)) {
                 return;
             }
@@ -718,6 +746,8 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) {
+            super.hasEnrolledFingerprints_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for hasEnrolledFingerprints, caller: " + opPackageName);
@@ -730,6 +760,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
+            super.getLockoutModeForUser_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getLockoutModeForUser");
@@ -742,6 +774,8 @@
         @Override
         public void invalidateAuthenticatorId(int sensorId, int userId,
                 IInvalidationCallback callback) {
+            super.invalidateAuthenticatorId_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
@@ -753,6 +787,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public long getAuthenticatorId(int sensorId, int userId) {
+            super.getAuthenticatorId_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getAuthenticatorId");
@@ -765,6 +801,8 @@
         @Override // Binder call
         public void resetLockout(IBinder token, int sensorId, int userId,
                 @Nullable byte[] hardwareAuthToken, String opPackageName) {
+            super.resetLockout_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
@@ -777,18 +815,24 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
         @Override
         public boolean isClientActive() {
+            super.isClientActive_enforcePermission();
+
             return mGestureAvailabilityDispatcher.isAnySensorActive();
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
         @Override
         public void addClientActiveCallback(IFingerprintClientActiveCallback callback) {
+            super.addClientActiveCallback_enforcePermission();
+
             mGestureAvailabilityDispatcher.registerCallback(callback);
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
         @Override
         public void removeClientActiveCallback(IFingerprintClientActiveCallback callback) {
+            super.removeClientActiveCallback_enforcePermission();
+
             mGestureAvailabilityDispatcher.removeCallback(callback);
         }
 
@@ -796,6 +840,8 @@
         @Override // Binder call
         public void registerAuthenticators(
                 @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
+            super.registerAuthenticators_enforcePermission();
+
             mRegistry.registerAll(() -> {
                 final List<ServiceProvider> providers = new ArrayList<>();
                 providers.addAll(getHidlProviders(hidlSensors));
@@ -814,41 +860,49 @@
         @Override
         public void addAuthenticatorsRegisteredCallback(
                 IFingerprintAuthenticatorsRegisteredCallback callback) {
+            super.addAuthenticatorsRegisteredCallback_enforcePermission();
+
             mRegistry.addAllRegisteredCallback(callback);
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+            super.registerBiometricStateListener_enforcePermission();
+
             mBiometricStateCallback.registerBiometricStateListener(listener);
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
-        public void onPointerDown(long requestId, int sensorId, int x, int y,
-                float minor, float major) {
+        public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
+            super.onPointerDown_enforcePermission();
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching provider for onFingerDown, sensorId: " + sensorId);
                 return;
             }
-            provider.onPointerDown(requestId, sensorId, x, y, minor, major);
+            provider.onPointerDown(requestId, sensorId, pc);
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
-        public void onPointerUp(long requestId, int sensorId) {
+
+        public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
+            super.onPointerUp_enforcePermission();
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching provider for onFingerUp, sensorId: " + sensorId);
                 return;
             }
-            provider.onPointerUp(requestId, sensorId);
+            provider.onPointerUp(requestId, sensorId, pc);
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void onUiReady(long requestId, int sensorId) {
+            super.onUiReady_enforcePermission();
+
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId);
@@ -860,6 +914,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
+            super.setUdfpsOverlayController_enforcePermission();
+
             for (ServiceProvider provider : mRegistry.getProviders()) {
                 provider.setUdfpsOverlayController(controller);
             }
@@ -868,6 +924,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void setSidefpsController(@NonNull ISidefpsController controller) {
+            super.setSidefpsController_enforcePermission();
+
             for (ServiceProvider provider : mRegistry.getProviders()) {
                 provider.setSidefpsController(controller);
             }
@@ -876,6 +934,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
+            super.setUdfpsOverlay_enforcePermission();
+
             for (ServiceProvider provider : mRegistry.getProviders()) {
                 provider.setUdfpsOverlay(controller);
             }
@@ -884,6 +944,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void onPowerPressed() {
+            super.onPowerPressed_enforcePermission();
+
             for (ServiceProvider provider : mRegistry.getProviders()) {
                 provider.onPowerPressed();
             }
@@ -892,6 +954,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void scheduleWatchdog() {
+            super.scheduleWatchdog_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for scheduling watchdog");
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 05c2e29..5b6f14d 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
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -122,9 +123,9 @@
             @NonNull IInvalidationCallback callback);
 
 
-    void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major);
+    void onPointerDown(long requestId, int sensorId, PointerContext pc);
 
-    void onPointerUp(long requestId, int sensorId);
+    void onPointerUp(long requestId, int sensorId, PointerContext pc);
 
     void onUiReady(long requestId, int sensorId);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
index a2c0751..da7163a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint;
 
+import android.hardware.biometrics.fingerprint.PointerContext;
+
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 
 /**
@@ -24,8 +26,8 @@
  * finger position (e.g. enroll, authenticate) should implement this.
  */
 public interface Udfps {
-    void onPointerDown(int x, int y, float minor, float major);
-    void onPointerUp();
+    void onPointerDown(PointerContext pc);
+    void onPointerUp(PointerContext pc);
     void onUiReady();
     boolean isPointerDown();
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 4181b99..135eccf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -135,6 +135,8 @@
     @Override
     public void setTestHalEnabled(boolean enabled) {
 
+        super.setTestHalEnabled_enforcePermission();
+
         mProvider.setTestHalEnabled(enabled);
         mSensor.setTestHalEnabled(enabled);
     }
@@ -143,6 +145,8 @@
     @Override
     public void startEnroll(int userId) {
 
+        super.startEnroll_enforcePermission();
+
         mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
                 mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
     }
@@ -151,6 +155,8 @@
     @Override
     public void finishEnroll(int userId) {
 
+        super.finishEnroll_enforcePermission();
+
         int nextRandomId = mRandom.nextInt();
         while (mEnrollmentIds.contains(nextRandomId)) {
             nextRandomId = mRandom.nextInt();
@@ -166,6 +172,8 @@
     public void acceptAuthentication(int userId)  {
 
         // Fake authentication with any of the existing fingers
+        super.acceptAuthentication_enforcePermission();
+
         List<Fingerprint> fingerprints = FingerprintUtils.getInstance(mSensorId)
                 .getBiometricsForUser(mContext, userId);
         if (fingerprints.isEmpty()) {
@@ -181,6 +189,8 @@
     @Override
     public void rejectAuthentication(int userId)  {
 
+        super.rejectAuthentication_enforcePermission();
+
         mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
     }
 
@@ -188,6 +198,8 @@
     @Override
     public void notifyAcquired(int userId, int acquireInfo)  {
 
+        super.notifyAcquired_enforcePermission();
+
         mSensor.getSessionForUser(userId).getHalSessionCallback()
                 .onAcquired((byte) acquireInfo, 0 /* vendorCode */);
     }
@@ -196,6 +208,8 @@
     @Override
     public void notifyError(int userId, int errorCode)  {
 
+        super.notifyError_enforcePermission();
+
         mSensor.getSessionForUser(userId).getHalSessionCallback().onError((byte) errorCode,
                 0 /* vendorCode */);
     }
@@ -204,6 +218,8 @@
     @Override
     public void cleanupInternalState(int userId)  {
 
+        super.cleanupInternalState_enforcePermission();
+
         Slog.d(TAG, "cleanupInternalState: " + userId);
         mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
             @Override
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 7f1fb1c..11f4517 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
@@ -56,7 +56,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
-import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.SensorOverlays;
 import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
@@ -72,12 +72,9 @@
 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
-    private final LockoutCache mLockoutCache;
-    @NonNull
     private final SensorOverlays mSensorOverlays;
     @NonNull
     private final FingerprintSensorPropertiesInternal mSensorProps;
@@ -86,7 +83,6 @@
     private final Handler mHandler;
     private final int mSkipWaitForPowerAcquireMessage;
     private final int mSkipWaitForPowerVendorAcquireMessage;
-    private final int mBiometricStrength;
     private final long mFingerUpIgnoresPower = 500;
     private final AuthSessionCoordinator mAuthSessionCoordinator;
     @Nullable
@@ -98,6 +94,7 @@
     private long mSideFpsLastAcquireStartTime;
     private Runnable mAuthSuccessRunnable;
     private final Clock mClock;
+    private boolean mDidFinishSfps;
 
     FingerprintAuthenticationClient(
             @NonNull Context context,
@@ -141,12 +138,12 @@
                 biometricContext,
                 isStrongBiometric,
                 taskStackListener,
-                lockoutCache,
+                null /* lockoutCache */,
                 allowBackgroundAuthentication,
                 false /* shouldVibrate */,
-                false /* isKeyguardBypassEnabled */);
+                false /* isKeyguardBypassEnabled */,
+                biometricStrength);
         setRequestId(requestId);
-        mLockoutCache = lockoutCache;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController,
                 sidefpsController, udfpsOverlay);
         mSensorProps = sensorProps;
@@ -167,7 +164,6 @@
         mSkipWaitForPowerVendorAcquireMessage =
                 context.getResources().getInteger(
                         R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage);
-        mBiometricStrength = biometricStrength;
         mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
         mSideFpsLastAcquireStartTime = -1;
         mClock = clock;
@@ -197,8 +193,6 @@
         } else {
             mState = STATE_STARTED;
         }
-        mAuthSessionCoordinator.authStartedFor(getTargetUserId(), getSensorId(),
-                getRequestId());
     }
 
     @NonNull
@@ -210,10 +204,9 @@
 
     @Override
     protected void handleLifecycleAfterAuth(boolean authenticated) {
-        if (authenticated) {
+        if (authenticated && !mDidFinishSfps) {
             mCallback.onClientFinished(this, true /* success */);
-            mAuthSessionCoordinator.authenticatedFor(
-                    getTargetUserId(), mBiometricStrength, getSensorId(), getRequestId());
+            mDidFinishSfps = true;
         }
     }
 
@@ -249,12 +242,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) {
@@ -311,6 +298,8 @@
                 });
             }
         }
+        PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
+        pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
 
     }
 
@@ -323,8 +312,6 @@
         }
 
         mSensorOverlays.hide(getSensorId());
-        mAuthSessionCoordinator.authEndedFor(getTargetUserId(), mBiometricStrength, getSensorId(),
-                getRequestId());
     }
 
     @Override
@@ -396,23 +383,17 @@
     }
 
     @Override
-    public void onPointerDown(int x, int y, float minor, float major) {
+    public void onPointerDown(PointerContext pc) {
         try {
             mIsPointerDown = true;
             mState = STATE_STARTED;
 
             final AidlSession session = getFreshDaemon();
             if (session.hasContextMethods()) {
-                final PointerContext context = new PointerContext();
-                context.pointerId = 0;
-                context.x = x;
-                context.y = y;
-                context.minor = minor;
-                context.major = major;
-                context.isAod = getBiometricContext().isAod();
-                session.getSession().onPointerDownWithContext(context);
+                session.getSession().onPointerDownWithContext(pc);
             } else {
-                session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
+                session.getSession().onPointerDown(pc.pointerId, (int) pc.x, (int) pc.y, pc.minor,
+                        pc.major);
             }
 
             if (getListener() != null) {
@@ -424,18 +405,16 @@
     }
 
     @Override
-    public void onPointerUp() {
+    public void onPointerUp(PointerContext pc) {
         try {
             mIsPointerDown = false;
             mState = STATE_STARTED_PAUSED_ATTEMPTED;
 
             final AidlSession session = getFreshDaemon();
             if (session.hasContextMethods()) {
-                final PointerContext context = new PointerContext();
-                context.pointerId = 0;
-                session.getSession().onPointerUpWithContext(context);
+                session.getSession().onPointerUpWithContext(pc);
             } else {
-                session.getSession().onPointerUp(0 /* pointerId */);
+                session.getSession().onPointerUp(pc.pointerId);
             }
 
             if (getListener() != null) {
@@ -462,7 +441,8 @@
 
     @Override
     public void onLockoutTimed(long durationMillis) {
-        mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
+        mAuthSessionCoordinator.lockOutTimed(getTargetUserId(), getSensorStrength(), getSensorId(),
+                durationMillis, getRequestId());
         // Lockout metrics are logged as an error code.
         final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
         getLogger()
@@ -473,6 +453,9 @@
                         0 /* vendorCode */,
                         getTargetUserId());
 
+        PerformanceTracker.getInstanceForSensorId(getSensorId())
+                .incrementTimedLockoutForUser(getTargetUserId());
+
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
         } catch (RemoteException e) {
@@ -481,13 +464,12 @@
 
         mSensorOverlays.hide(getSensorId());
         mCallback.onClientFinished(this, false /* success */);
-        mAuthSessionCoordinator.lockOutTimed(getTargetUserId(), mBiometricStrength, getSensorId(),
-                durationMillis, getRequestId());
     }
 
     @Override
     public void onLockoutPermanent() {
-        mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
+        mAuthSessionCoordinator.lockedOutFor(getTargetUserId(), getSensorStrength(), getSensorId(),
+                getRequestId());
         // Lockout metrics are logged as an error code.
         final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
         getLogger()
@@ -498,6 +480,9 @@
                         0 /* vendorCode */,
                         getTargetUserId());
 
+        PerformanceTracker.getInstanceForSensorId(getSensorId())
+                .incrementPermanentLockoutForUser(getTargetUserId());
+
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
         } catch (RemoteException e) {
@@ -506,8 +491,6 @@
 
         mSensorOverlays.hide(getSensorId());
         mCallback.onClientFinished(this, false /* success */);
-        mAuthSessionCoordinator.lockedOutFor(getTargetUserId(), mBiometricStrength, getSensorId(),
-                getRequestId());
     }
 
     @Override
@@ -515,18 +498,17 @@
         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());
+                if (mDidFinishSfps) {
+                    return;
                 }
-                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.
+                mDidFinishSfps = true;
+                onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
+                stopHalOperation();
+                mSensorOverlays.hide(getSensorId());
             });
         }
     }
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 7e5d39f..fa54983 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
@@ -219,22 +219,16 @@
     }
 
     @Override
-    public void onPointerDown(int x, int y, float minor, float major) {
+    public void onPointerDown(PointerContext pc) {
         try {
             mIsPointerDown = true;
 
             final AidlSession session = getFreshDaemon();
             if (session.hasContextMethods()) {
-                final PointerContext context = new PointerContext();
-                context.pointerId = 0;
-                context.x = x;
-                context.y = y;
-                context.minor = minor;
-                context.major = major;
-                context.isAod = getBiometricContext().isAod();
-                session.getSession().onPointerDownWithContext(context);
+                session.getSession().onPointerDownWithContext(pc);
             } else {
-                session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
+                session.getSession().onPointerDown(pc.pointerId, (int) pc.x, (int) pc.y, pc.minor,
+                        pc.major);
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to send pointer down", e);
@@ -242,17 +236,15 @@
     }
 
     @Override
-    public void onPointerUp() {
+    public void onPointerUp(PointerContext pc) {
         try {
             mIsPointerDown = false;
 
             final AidlSession session = getFreshDaemon();
             if (session.hasContextMethods()) {
-                final PointerContext context = new PointerContext();
-                context.pointerId = 0;
-                session.getSession().onPointerUpWithContext(context);
+                session.getSession().onPointerUpWithContext(pc);
             } else {
-                session.getSession().onPointerUp(0 /* pointerId */);
+                session.getSession().onPointerUp(pc.pointerId);
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to send pointer up", e);
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 a42ff9a..776d331 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
@@ -34,6 +34,7 @@
 import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.biometrics.common.ComponentInfo;
 import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
@@ -58,6 +59,7 @@
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -94,15 +96,23 @@
 
     private boolean mTestHalEnabled;
 
-    @NonNull private final Context mContext;
-    @NonNull private final BiometricStateCallback mBiometricStateCallback;
-    @NonNull private final String mHalInstanceName;
-    @NonNull @VisibleForTesting
+    @NonNull
+    @VisibleForTesting
     final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
-    @NonNull private final Handler mHandler;
-    @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
-    @NonNull private final ActivityTaskManager mActivityTaskManager;
-    @NonNull private final BiometricTaskStackListener mTaskStackListener;
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final BiometricStateCallback mBiometricStateCallback;
+    @NonNull
+    private final String mHalInstanceName;
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
+    @NonNull
+    private final ActivityTaskManager mActivityTaskManager;
+    @NonNull
+    private final BiometricTaskStackListener mTaskStackListener;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     @NonNull private final BiometricContext mBiometricContext;
@@ -110,6 +120,7 @@
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
     @Nullable private IUdfpsOverlay mUdfpsOverlay;
+    private AuthSessionCoordinator mAuthSessionCoordinator;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
         @Override
@@ -154,6 +165,7 @@
         mActivityTaskManager = ActivityTaskManager.getInstance();
         mTaskStackListener = new BiometricTaskStackListener();
         mBiometricContext = biometricContext;
+        mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
 
         final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
 
@@ -179,11 +191,11 @@
                             true /* resetLockoutRequiresHardwareAuthToken */,
                             !workaroundLocations.isEmpty() ? workaroundLocations :
                                     Arrays.stream(prop.sensorLocations).map(location ->
-                                            new SensorLocationInternal(
-                                                    location.display,
-                                                    location.sensorLocationX,
-                                                    location.sensorLocationY,
-                                                    location.sensorRadius))
+                                                    new SensorLocationInternal(
+                                                            location.display,
+                                                            location.sensorLocationX,
+                                                            location.sensorLocationY,
+                                                            location.sensorRadius))
                                             .collect(Collectors.toList()));
             final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
                     internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher,
@@ -347,7 +359,7 @@
                             mSensors.get(sensorId).getLazySession(), token,
                             new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
                             sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
                             mBiometricContext);
             scheduleForSensor(sensorId, client);
         });
@@ -445,8 +457,30 @@
                     mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
                     allowBackgroundAuthentication,
                     mSensors.get(sensorId).getSensorProperties(), mHandler,
-                    Utils.getCurrentStrength(sensorId), SystemClock.elapsedRealtimeClock());
-            scheduleForSensor(sensorId, client, mBiometricStateCallback);
+                    Utils.getCurrentStrength(sensorId),
+                    SystemClock.elapsedRealtimeClock());
+            scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
+
+                @Override
+                public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                    mBiometricStateCallback.onClientStarted(clientMonitor);
+                    mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+                }
+
+                @Override
+                public void onBiometricAction(int action) {
+                    mBiometricStateCallback.onBiometricAction(action);
+                }
+
+                @Override
+                public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                        boolean success) {
+                    mBiometricStateCallback.onClientFinished(clientMonitor, success);
+                    mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
+                            sensorId, requestId, success);
+                }
+            });
+
         });
     }
 
@@ -584,7 +618,8 @@
 
     @Override
     public int getLockoutModeForUser(int sensorId, int userId) {
-        return mSensors.get(sensorId).getLockoutCache().getLockoutModeForUser(userId);
+        return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+                Utils.getCurrentStrength(sensorId));
     }
 
     @Override
@@ -593,25 +628,24 @@
     }
 
     @Override
-    public void onPointerDown(long requestId, int sensorId, int x, int y,
-            float minor, float major) {
+    public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
         mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> {
             if (!(client instanceof Udfps)) {
                 Slog.e(getTag(), "onPointerDown received during client: " + client);
                 return;
             }
-            ((Udfps) client).onPointerDown(x, y, minor, major);
+            ((Udfps) client).onPointerDown(pc);
         });
     }
 
     @Override
-    public void onPointerUp(long requestId, int sensorId) {
+    public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
         mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> {
             if (!(client instanceof Udfps)) {
                 Slog.e(getTag(), "onPointerUp received during client: " + client);
                 return;
             }
-            ((Udfps) client).onPointerUp();
+            ((Udfps) client).onPointerUp(pc);
         });
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index 22f504c..0b2421b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -93,9 +93,9 @@
     void onLockoutCleared() {
         resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache,
                 mLockoutResetDispatcher);
+        mCallback.onClientFinished(this, true /* success */);
         getBiometricContext().getAuthSessionCoordinator()
                 .resetLockoutFor(getTargetUserId(), mBiometricStrength, getRequestId());
-        mCallback.onClientFinished(this, true /* success */);
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index 682c005..86a9f79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -136,6 +136,8 @@
     @Override
     public void setTestHalEnabled(boolean enabled) {
 
+        super.setTestHalEnabled_enforcePermission();
+
         mFingerprint21.setTestHalEnabled(enabled);
     }
 
@@ -143,6 +145,8 @@
     @Override
     public void startEnroll(int userId) {
 
+        super.startEnroll_enforcePermission();
+
         mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
                 mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
     }
@@ -151,6 +155,8 @@
     @Override
     public void finishEnroll(int userId) {
 
+        super.finishEnroll_enforcePermission();
+
         int nextRandomId = mRandom.nextInt();
         while (mEnrollmentIds.contains(nextRandomId)) {
             nextRandomId = mRandom.nextInt();
@@ -166,6 +172,8 @@
     public void acceptAuthentication(int userId)  {
 
         // Fake authentication with any of the existing fingers
+        super.acceptAuthentication_enforcePermission();
+
         List<Fingerprint> fingerprints = FingerprintUtils.getLegacyInstance(mSensorId)
                 .getBiometricsForUser(mContext, userId);
         if (fingerprints.isEmpty()) {
@@ -181,6 +189,8 @@
     @Override
     public void rejectAuthentication(int userId)  {
 
+        super.rejectAuthentication_enforcePermission();
+
         mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* fingerId */, userId, null);
     }
 
@@ -188,6 +198,8 @@
     @Override
     public void notifyAcquired(int userId, int acquireInfo)  {
 
+        super.notifyAcquired_enforcePermission();
+
         mHalResultController.onAcquired(0 /* deviceId */, acquireInfo, 0 /* vendorCode */);
     }
 
@@ -195,6 +207,8 @@
     @Override
     public void notifyError(int userId, int errorCode)  {
 
+        super.notifyError_enforcePermission();
+
         mHalResultController.onError(0 /* deviceId */, errorCode, 0 /* vendorCode */);
     }
 
@@ -202,6 +216,8 @@
     @Override
     public void cleanupInternalState(int userId)  {
 
+        super.cleanupInternalState_enforcePermission();
+
         mFingerprint21.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
             @Override
             public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
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 dbc96df..4567addc 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
@@ -30,6 +30,7 @@
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
 import android.hardware.fingerprint.Fingerprint;
@@ -667,7 +668,8 @@
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mLockoutTracker,
                     mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
-                    allowBackgroundAuthentication, mSensorProperties);
+                    allowBackgroundAuthentication, mSensorProperties,
+                    Utils.getCurrentStrength(mSensorId));
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
     }
@@ -806,25 +808,24 @@
     }
 
     @Override
-    public void onPointerDown(long requestId, int sensorId, int x, int y,
-            float minor, float major) {
+    public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
         mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
             if (!(client instanceof Udfps)) {
                 Slog.w(TAG, "onFingerDown received during client: " + client);
                 return;
             }
-            ((Udfps) client).onPointerDown(x, y, minor, major);
+            ((Udfps) client).onPointerDown(pc);
         });
     }
 
     @Override
-    public void onPointerUp(long requestId, int sensorId) {
+    public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
         mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
             if (!(client instanceof Udfps)) {
                 Slog.w(TAG, "onFingerDown received during client: " + client);
                 return;
             }
-            ((Udfps) client).onPointerUp();
+            ((Udfps) client).onPointerUp(pc);
         });
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index bea0f4f..34c6265 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -21,6 +21,7 @@
 import android.app.trust.TrustManager;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
@@ -441,8 +442,7 @@
     }
 
     @Override
-    public void onPointerDown(long requestId, int sensorId, int x, int y, float minor,
-            float major) {
+    public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
         mHandler.post(() -> {
             Slog.d(TAG, "onFingerDown");
             final AuthenticationConsumer lastAuthenticatedConsumer =
@@ -489,7 +489,7 @@
     }
 
     @Override
-    public void onPointerUp(long requestId, int sensorId) {
+    public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
         mHandler.post(() -> {
             Slog.d(TAG, "onFingerUp");
 
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 56fa36e..a9cc897 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
@@ -23,6 +23,8 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
@@ -42,6 +44,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.SensorOverlays;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
@@ -79,12 +82,13 @@
             @Nullable ISidefpsController sidefpsController,
             @Nullable IUdfpsOverlay udfpsOverlay,
             boolean allowBackgroundAuthentication,
-            @NonNull FingerprintSensorPropertiesInternal sensorProps) {
+            @NonNull FingerprintSensorPropertiesInternal sensorProps,
+            @Authenticators.Types int sensorStrength) {
         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 */,
+                sensorStrength);
         setRequestId(requestId);
         mLockoutFrameworkImpl = lockoutTracker;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController,
@@ -167,6 +171,18 @@
     }
 
     @Override
+    public void onAcquired(int acquiredInfo, int vendorCode) {
+        super.onAcquired(acquiredInfo, vendorCode);
+
+        @LockoutTracker.LockoutMode final int lockoutMode =
+                getLockoutTracker().getLockoutModeForUser(getTargetUserId());
+        if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
+            PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
+            pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
+        }
+    }
+
+    @Override
     public boolean wasUserDetected() {
         // TODO: Update if it needs to be used for fingerprint, i.e. success/reject, error_timeout
         return false;
@@ -175,7 +191,17 @@
     @Override
     public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
         mLockoutFrameworkImpl.addFailedAttemptForUser(userId);
-        return super.handleFailedAttempt(userId);
+        @LockoutTracker.LockoutMode final int lockoutMode =
+                getLockoutTracker().getLockoutModeForUser(userId);
+        final PerformanceTracker performanceTracker =
+                PerformanceTracker.getInstanceForSensorId(getSensorId());
+        if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
+            performanceTracker.incrementPermanentLockoutForUser(userId);
+        } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
+            performanceTracker.incrementTimedLockoutForUser(userId);
+        }
+
+        return lockoutMode;
     }
 
     @Override
@@ -209,11 +235,11 @@
     }
 
     @Override
-    public void onPointerDown(int x, int y, float minor, float major) {
+    public void onPointerDown(PointerContext pc) {
         mIsPointerDown = true;
         mState = STATE_STARTED;
         mALSProbeCallback.getProbe().enable();
-        UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major);
+        UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
 
         if (getListener() != null) {
             try {
@@ -225,7 +251,7 @@
     }
 
     @Override
-    public void onPointerUp() {
+    public void onPointerUp(PointerContext pc) {
         mIsPointerDown = false;
         mState = STATE_STARTED_PAUSED_ATTEMPTED;
         mALSProbeCallback.getProbe().disable();
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 3e9b8ef..cfa9fb4 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
@@ -22,6 +22,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -110,13 +111,13 @@
     }
 
     @Override
-    public void onPointerDown(int x, int y, float minor, float major) {
+    public void onPointerDown(PointerContext pc) {
         mIsPointerDown = true;
-        UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major);
+        UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
     }
 
     @Override
-    public void onPointerUp() {
+    public void onPointerUp(PointerContext pc) {
         mIsPointerDown = false;
         UdfpsHelper.onFingerUp(getFreshDaemon());
     }
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 3371cec..78039ef 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
@@ -22,6 +22,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricStateListener;
+import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
@@ -168,13 +169,13 @@
     }
 
     @Override
-    public void onPointerDown(int x, int y, float minor, float major) {
+    public void onPointerDown(PointerContext pc) {
         mIsPointerDown = true;
-        UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major);
+        UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
     }
 
     @Override
-    public void onPointerUp() {
+    public void onPointerUp(PointerContext pc) {
         mIsPointerDown = false;
         UdfpsHelper.onFingerUp(getFreshDaemon());
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
index ff1e762..35ea36c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
@@ -63,6 +63,8 @@
             // to wait, and some of the operations below might take a significant amount of time to
             // complete (calls to the HALs). To avoid blocking the rest of system server we put
             // this on a background thread.
+            super.registerAuthenticators_enforcePermission();
+
             final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
                     true /* allowIo */);
             thread.start();
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
index a8e4034..408fba1 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
@@ -27,6 +27,7 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.broadcastradio.hal2.AnnouncementAggregator;
 
@@ -51,15 +52,17 @@
     private final Object mLock = new Object();
 
     private final BroadcastRadioService mService;
+
+    @GuardedBy("mLock")
     private final List<RadioManager.ModuleProperties> mV1Modules;
 
     IRadioServiceHidlImpl(BroadcastRadioService service) {
         mService = Objects.requireNonNull(service, "broadcast radio service cannot be null");
-        mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService(mLock);
+        mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService();
         mV1Modules = mHal1.loadModules();
         OptionalInt max = mV1Modules.stream().mapToInt(RadioManager.ModuleProperties::getId).max();
         mHal2 = new com.android.server.broadcastradio.hal2.BroadcastRadioService(
-                max.isPresent() ? max.getAsInt() + 1 : 0, mLock);
+                max.isPresent() ? max.getAsInt() + 1 : 0);
     }
 
     @VisibleForTesting
@@ -78,9 +81,11 @@
     public List<RadioManager.ModuleProperties> listModules() {
         mService.enforcePolicyAccess();
         Collection<RadioManager.ModuleProperties> v2Modules = mHal2.listModules();
-        List<RadioManager.ModuleProperties> modules = new ArrayList<>(
-                mV1Modules.size() + v2Modules.size());
-        modules.addAll(mV1Modules);
+        List<RadioManager.ModuleProperties> modules;
+        synchronized (mLock) {
+            modules = new ArrayList<>(mV1Modules.size() + v2Modules.size());
+            modules.addAll(mV1Modules);
+        }
         modules.addAll(v2Modules);
         return modules;
     }
@@ -131,7 +136,9 @@
         radioPw.printf("HAL1: %s\n", mHal1);
 
         radioPw.increaseIndent();
-        radioPw.printf("Modules of HAL1: %s\n", mV1Modules);
+        synchronized (mLock) {
+            radioPw.printf("Modules of HAL1: %s\n", mV1Modules);
+        }
         radioPw.decreaseIndent();
 
         radioPw.printf("HAL2:\n");
diff --git a/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java b/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java
new file mode 100644
index 0000000..38b3233
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java
@@ -0,0 +1,56 @@
+/**
+ * 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;
+
+import android.app.ActivityManager;
+import android.os.Binder;
+import android.os.UserHandle;
+
+/**
+ * Controller to handle users in {@link com.android.server.broadcastradio.BroadcastRadioService}
+ */
+public final class RadioServiceUserController {
+
+    private RadioServiceUserController() {
+        throw new UnsupportedOperationException(
+                "RadioServiceUserController class is noninstantiable");
+    }
+
+    /**
+     * Check if the user calling the method in Broadcast Radio Service is the current user or the
+     * system user.
+     *
+     * @return {@code true} if the user calling this method is the current user of system user,
+     * {@code false} otherwise.
+     */
+    public static boolean isCurrentOrSystemUser() {
+        int callingUser = Binder.getCallingUserHandle().getIdentifier();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            int currentUser = ActivityManager.getCurrentUser();
+            if (callingUser != currentUser && callingUser != UserHandle.USER_SYSTEM) {
+                return false;
+            }
+            return true;
+        } catch (RuntimeException e) {
+            // Activity manager not running, nothing we can do assume user 0.
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index 71ba296..03acf72 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -34,6 +34,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.broadcastradio.RadioServiceUserController;
 import com.android.server.utils.Slogf;
 
 import java.util.ArrayList;
@@ -76,7 +77,7 @@
                 }
 
                 RadioModule radioModule =
-                        RadioModule.tryLoadingModule(moduleId, name, newBinder, mLock);
+                        RadioModule.tryLoadingModule(moduleId, name, newBinder);
                 if (radioModule == null) {
                     Slogf.w(TAG, "No module %s with id %d (HAL AIDL)", name, moduleId);
                     return;
@@ -149,8 +150,7 @@
     public BroadcastRadioServiceImpl(ArrayList<String> serviceNameList) {
         mNextModuleId = 0;
         if (DEBUG) {
-            Slogf.d(TAG, "Initializing BroadcastRadioServiceImpl %s",
-                    IBroadcastRadio.DESCRIPTOR);
+            Slogf.d(TAG, "Initializing BroadcastRadioServiceImpl %s", IBroadcastRadio.DESCRIPTOR);
         }
         for (int i = 0; i < serviceNameList.size(); i++) {
             try {
@@ -169,7 +169,7 @@
         synchronized (mLock) {
             List<RadioManager.ModuleProperties> moduleList = new ArrayList<>(mModules.size());
             for (int i = 0; i < mModules.size(); i++) {
-                moduleList.add(mModules.valueAt(i).mProperties);
+                moduleList.add(mModules.valueAt(i).getProperties());
             }
             return moduleList;
         }
@@ -203,6 +203,10 @@
         if (DEBUG) {
             Slogf.d(TAG, "Open AIDL radio session");
         }
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.e(TAG, "Cannot open tuner on AIDL HAL client for non-current user");
+            throw new IllegalStateException("Cannot open session for non-current user");
+        }
         Objects.requireNonNull(callback);
 
         if (!withAudio) {
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index c6dc431..e956a9c 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -54,11 +54,11 @@
     private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25;
 
     private final IBroadcastRadio mService;
-    public final RadioManager.ModuleProperties mProperties;
 
-    private final Object mLock;
+    private final Object mLock = new Object();
     private final Handler mHandler;
     private final RadioLogger mLogger;
+    private final RadioManager.ModuleProperties mProperties;
 
     /**
      * Tracks antenna state reported by HAL (if any).
@@ -165,18 +165,15 @@
     };
 
     @VisibleForTesting
-    RadioModule(IBroadcastRadio service,
-            RadioManager.ModuleProperties properties, Object lock) {
+    RadioModule(IBroadcastRadio service, RadioManager.ModuleProperties properties) {
         mProperties = Objects.requireNonNull(properties, "properties cannot be null");
         mService = Objects.requireNonNull(service, "service cannot be null");
-        mLock = Objects.requireNonNull(lock, "lock cannot be null");
         mHandler = new Handler(Looper.getMainLooper());
         mLogger = new RadioLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE);
     }
 
     @Nullable
-    public static RadioModule tryLoadingModule(int moduleId, String moduleName,
-            IBinder serviceBinder, Object lock) {
+    static RadioModule tryLoadingModule(int moduleId, String moduleName, IBinder serviceBinder) {
         try {
             Slogf.i(TAG, "Try loading module for module id = %d, module name = %s",
                     moduleId, moduleName);
@@ -206,31 +203,33 @@
             RadioManager.ModuleProperties prop = ConversionUtils.propertiesFromHalProperties(
                     moduleId, moduleName, service.getProperties(), amfmConfig, dabConfig);
 
-            return new RadioModule(service, prop, lock);
+            return new RadioModule(service, prop);
         } catch (RemoteException ex) {
             Slogf.e(TAG, ex, "Failed to load module %s", moduleName);
             return null;
         }
     }
 
-    public IBroadcastRadio getService() {
+    IBroadcastRadio getService() {
         return mService;
     }
 
-    void setInternalHalCallback() throws RemoteException {
-        synchronized (mLock) {
-            mService.setTunerCallback(mHalTunerCallback);
-        }
+    RadioManager.ModuleProperties getProperties() {
+        return mProperties;
     }
 
-    public TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
+    void setInternalHalCallback() throws RemoteException {
+        mService.setTunerCallback(mHalTunerCallback);
+    }
+
+    TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
             throws RemoteException {
         mLogger.logRadioEvent("Open TunerSession");
         TunerSession tunerSession;
         Boolean antennaConnected;
         RadioManager.ProgramInfo currentProgramInfo;
         synchronized (mLock) {
-            tunerSession = new TunerSession(this, mService, userCb, mLock);
+            tunerSession = new TunerSession(this, mService, userCb);
             mAidlTunerSessions.add(tunerSession);
             antennaConnected = mAntennaConnected;
             currentProgramInfo = mCurrentProgramInfo;
@@ -248,7 +247,7 @@
         return tunerSession;
     }
 
-    public void closeSessions(int error) {
+    void closeSessions(int error) {
         mLogger.logRadioEvent("Close TunerSessions %d", error);
         // TunerSession.close() must be called without mAidlTunerSessions locked because
         // it can call onTunerSessionClosed(). Therefore, the contents of mAidlTunerSessions
@@ -271,8 +270,7 @@
 
     @GuardedBy("mLock")
     @Nullable
-    private android.hardware.radio.ProgramList.Filter
-            buildUnionOfTunerSessionFiltersLocked() {
+    private android.hardware.radio.ProgramList.Filter buildUnionOfTunerSessionFiltersLocked() {
         Set<Integer> idTypes = null;
         Set<android.hardware.radio.ProgramSelector.Identifier> ids = null;
         boolean includeCategories = false;
@@ -353,14 +351,14 @@
             // Otherwise, update the HAL's filter, and AIDL clients will be updated when
             // mHalTunerCallback.onProgramListUpdated() is called.
             mUnionOfAidlProgramFilters = newFilter;
-            try {
-                mService.startProgramListUpdates(
-                        ConversionUtils.filterToHalProgramFilter(newFilter));
-            } catch (RuntimeException ex) {
-                throw ConversionUtils.throwOnError(ex, /* action= */ "Start Program ListUpdates");
-            } catch (RemoteException ex) {
-                Slogf.e(TAG, ex, "mHalTunerSession.startProgramListUpdates() failed");
-            }
+        }
+        try {
+            mService.startProgramListUpdates(
+                    ConversionUtils.filterToHalProgramFilter(newFilter));
+        } catch (RuntimeException ex) {
+            throw ConversionUtils.throwOnError(ex, /* action= */ "Start Program ListUpdates");
+        } catch (RemoteException ex) {
+            Slogf.e(TAG, ex, "mHalTunerSession.startProgramListUpdates() failed");
         }
     }
 
@@ -419,7 +417,7 @@
         }
     }
 
-    public android.hardware.radio.ICloseHandle addAnnouncementListener(
+    android.hardware.radio.ICloseHandle addAnnouncementListener(
             android.hardware.radio.IAnnouncementListener listener,
             int[] enabledTypes) throws RemoteException {
         mLogger.logRadioEvent("Add AnnouncementListener");
@@ -450,12 +448,10 @@
             }
         };
 
-        synchronized (mLock) {
-            try {
-                hwCloseHandle[0] = mService.registerAnnouncementListener(hwListener, enabledList);
-            } catch (RuntimeException ex) {
-                throw ConversionUtils.throwOnError(ex, /* action= */ "AnnouncementListener");
-            }
+        try {
+            hwCloseHandle[0] = mService.registerAnnouncementListener(hwListener, enabledList);
+        } catch (RuntimeException ex) {
+            throw ConversionUtils.throwOnError(ex, /* action= */ "AnnouncementListener");
         }
 
         return new android.hardware.radio.ICloseHandle.Stub() {
@@ -475,12 +471,10 @@
         if (id == 0) throw new IllegalArgumentException("Image ID is missing");
 
         byte[] rawImage;
-        synchronized (mLock) {
-            try {
-                rawImage = mService.getImage(id);
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
+        try {
+            rawImage = mService.getImage(id);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
 
         if (rawImage == null || rawImage.length == 0) return null;
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
index 7c26a87..1ce4044 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
@@ -26,10 +26,12 @@
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.broadcastradio.RadioServiceUserController;
 import com.android.server.utils.Slogf;
 
 import java.util.List;
@@ -40,7 +42,7 @@
     private static final String TAG = "BcRadioAidlSrv.session";
     private static final int TUNER_EVENT_LOGGER_QUEUE_SIZE = 25;
 
-    private final Object mLock;
+    private final Object mLock = new Object();
 
     private final RadioLogger mLogger;
     private final RadioModule mModule;
@@ -59,18 +61,16 @@
     private RadioManager.BandConfig mPlaceHolderConfig;
 
     TunerSession(RadioModule radioModule, IBroadcastRadio service,
-            android.hardware.radio.ITunerCallback callback,
-            Object lock) {
+            android.hardware.radio.ITunerCallback callback) {
         mModule = Objects.requireNonNull(radioModule, "radioModule cannot be null");
         mService = Objects.requireNonNull(service, "service cannot be null");
         mCallback = Objects.requireNonNull(callback, "callback cannot be null");
-        mLock = Objects.requireNonNull(lock, "lock cannot be null");
         mLogger = new RadioLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
     }
 
     @Override
     public void close() {
-        mLogger.logRadioEvent("Close tuner session");
+        mLogger.logRadioEvent("Close tuner");
         close(null);
     }
 
@@ -89,17 +89,19 @@
             mLogger.logRadioEvent("Close tuner session on error %d", error);
         }
         synchronized (mLock) {
-            if (mIsClosed) return;
-            if (error != null) {
-                try {
-                    mCallback.onError(error);
-                } catch (RemoteException ex) {
-                    Slogf.w(TAG, ex, "mCallback.onError(%s) failed", error);
-                }
+            if (mIsClosed) {
+                return;
             }
             mIsClosed = true;
-            mModule.onTunerSessionClosed(this);
         }
+        if (error != null) {
+            try {
+                mCallback.onError(error);
+            } catch (RemoteException ex) {
+                Slogf.w(TAG, ex, "mCallback.onError(%s) failed", error);
+            }
+        }
+        mModule.onTunerSessionClosed(this);
     }
 
     @Override
@@ -118,6 +120,10 @@
 
     @Override
     public void setConfiguration(RadioManager.BandConfig config) {
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot set configuration for AIDL HAL client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             mPlaceHolderConfig = Objects.requireNonNull(config, "config cannot be null");
@@ -157,6 +163,10 @@
     public void step(boolean directionDown, boolean skipSubChannel) throws RemoteException {
         mLogger.logRadioEvent("Step with direction %s, skipSubChannel?  %s",
                 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot step on AIDL HAL client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             try {
@@ -171,6 +181,10 @@
     public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
         mLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s",
                 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot scan on AIDL HAL client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             try {
@@ -184,6 +198,10 @@
     @Override
     public void tune(ProgramSelector selector) throws RemoteException {
         mLogger.logRadioEvent("Tune with selector %s", selector);
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot tune on AIDL HAL client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             try {
@@ -197,6 +215,10 @@
     @Override
     public void cancel() {
         Slogf.i(TAG, "Cancel");
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot cancel on AIDL HAL client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             try {
@@ -223,6 +245,10 @@
     @Override
     public boolean startBackgroundScan() {
         Slogf.i(TAG, "Explicit background scan trigger is not supported with HAL AIDL");
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot start background scan on AIDL HAL client from non-current user");
+            return false;
+        }
         mModule.fanoutAidlCallback(ITunerCallback::onBackgroundScanComplete);
         return true;
     }
@@ -230,6 +256,11 @@
     @Override
     public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {
         mLogger.logRadioEvent("Start programList updates %s", filter);
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG,
+                    "Cannot start program list updates on AIDL HAL client from non-current user");
+            return;
+        }
         // If the AIDL client provides a null filter, it wants all updates, so use the most broad
         // filter.
         if (filter == null) {
@@ -291,6 +322,11 @@
     @Override
     public void stopProgramListUpdates() throws RemoteException {
         mLogger.logRadioEvent("Stop programList updates");
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG,
+                    "Cannot stop program list updates on AIDL HAL client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             mProgramInfoCache = null;
@@ -331,6 +367,10 @@
     public void setConfigFlag(int flag, boolean value) throws RemoteException {
         mLogger.logRadioEvent("set ConfigFlag %s to %b ",
                 ConfigFlag.$.toString(flag), value);
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot set config flag for AIDL HAL client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             try {
@@ -344,6 +384,10 @@
     @Override
     public Map<String, String> setParameters(Map<String, String> parameters) {
         mLogger.logRadioEvent("Set parameters ");
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot set parameters for AIDL HAL client from non-current user");
+            return new ArrayMap<>();
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             try {
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java
index 5da6032..fb42c94 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java
@@ -21,16 +21,22 @@
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
 
+import com.android.server.broadcastradio.RadioServiceUserController;
+import com.android.server.utils.Slogf;
+
 import java.util.List;
 import java.util.Objects;
 
 public class BroadcastRadioService {
+
+    private static final String TAG = "BcRadio1Srv";
+
     /**
      * This field is used by native code, do not access or modify.
      */
     private final long mNativeContext = nativeInit();
 
-    private final Object mLock;
+    private final Object mLock = new Object();
 
     @Override
     protected void finalize() throws Throwable {
@@ -44,14 +50,6 @@
     private native Tuner nativeOpenTuner(long nativeContext, int moduleId,
             RadioManager.BandConfig config, boolean withAudio, ITunerCallback callback);
 
-    /**
-     * Constructor. should pass
-     * {@code com.android.server.broadcastradio.BroadcastRadioService#mLock} for lock.
-     */
-    public BroadcastRadioService(@NonNull Object lock) {
-        mLock = lock;
-    }
-
     public @NonNull List<RadioManager.ModuleProperties> loadModules() {
         synchronized (mLock) {
             return Objects.requireNonNull(nativeLoadModules(mNativeContext));
@@ -59,7 +57,11 @@
     }
 
     public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
-            boolean withAudio, @NonNull ITunerCallback callback) {
+            boolean withAudio, ITunerCallback callback) {
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.e(TAG, "Cannot open tuner on HAL 1.x client for non-current user");
+            throw new IllegalStateException("Cannot open tuner for non-current user");
+        }
         synchronized (mLock) {
             return nativeOpenTuner(mNativeContext, moduleId, bandConfig, withAudio, callback);
         }
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Convert.java b/services/core/java/com/android/server/broadcastradio/hal1/Convert.java
index 80c7762..219ee4c 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/Convert.java
@@ -24,7 +24,8 @@
 import java.util.Set;
 
 class Convert {
-    private static final String TAG = "BroadcastRadioService.Convert";
+
+    private static final String TAG = "BcRadio1Srv.Convert";
 
     /**
      * Converts string map to an array that's easily accessible by native code.
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
index e7118ad..ed8a37a 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
@@ -28,11 +28,15 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.broadcastradio.RadioServiceUserController;
+import com.android.server.utils.Slogf;
+
 import java.util.List;
 import java.util.Map;
 
 class Tuner extends ITuner.Stub {
-    private static final String TAG = "BroadcastRadioService.Tuner";
+
+    private static final String TAG = "BcRadio1Srv.Tuner";
 
     /**
      * This field is used by native code, do not access or modify.
@@ -124,6 +128,10 @@
 
     @Override
     public void setConfiguration(RadioManager.BandConfig config) {
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot set configuration for HAL 1.x client from non-current user");
+            return;
+        }
         if (config == null) {
             throw new IllegalArgumentException("The argument must not be a null pointer");
         }
@@ -169,6 +177,10 @@
 
     @Override
     public void step(boolean directionDown, boolean skipSubChannel) {
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot step on HAL 1.x client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             if (!checkConfiguredLocked()) return;
@@ -178,6 +190,10 @@
 
     @Override
     public void scan(boolean directionDown, boolean skipSubChannel) {
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot scan on HAL 1.x client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             if (!checkConfiguredLocked()) return;
@@ -187,6 +203,10 @@
 
     @Override
     public void tune(ProgramSelector selector) {
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot tune on HAL 1.x client from non-current user");
+            return;
+        }
         if (selector == null) {
             throw new IllegalArgumentException("The argument must not be a null pointer");
         }
@@ -200,6 +220,10 @@
 
     @Override
     public void cancel() {
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot cancel on HAL 1.x client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             nativeCancel(mNativeContext);
@@ -208,6 +232,10 @@
 
     @Override
     public void cancelAnnouncement() {
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot cancel announcement on HAL 1.x client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             nativeCancelAnnouncement(mNativeContext);
@@ -233,6 +261,11 @@
 
     @Override
     public boolean startBackgroundScan() {
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG,
+                    "Cannot start background scan on HAL 1.x client from non-current user");
+            return false;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             return nativeStartBackgroundScan(mNativeContext);
@@ -253,11 +286,21 @@
 
     @Override
     public void startProgramListUpdates(ProgramList.Filter filter) {
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG,
+                    "Cannot start program list updates on HAL 1.x client from non-current user");
+            return;
+        }
         mTunerCallback.startProgramListUpdates(filter);
     }
 
     @Override
     public void stopProgramListUpdates() {
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG,
+                    "Cannot stop program list updates on HAL 1.x client from non-current user");
+            return;
+        }
         mTunerCallback.stopProgramListUpdates();
     }
 
@@ -279,6 +322,10 @@
 
     @Override
     public void setConfigFlag(int flag, boolean value) {
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot set config flag for HAL 1.x client from non-current user");
+            return;
+        }
         if (flag == RadioManager.CONFIG_FORCE_ANALOG) {
             synchronized (mLock) {
                 checkNotClosedLocked();
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
index 867d5b4..0cc3833 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
@@ -34,7 +34,8 @@
 import java.util.stream.Collectors;
 
 class TunerCallback implements ITunerCallback {
-    private static final String TAG = "BroadcastRadioService.TunerCallback";
+
+    private static final String TAG = "BcRadio1Srv.TunerCallback";
 
     /**
      * This field is used by native code, do not access or modify.
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 5605737..984bf51 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -33,6 +33,9 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.broadcastradio.RadioServiceUserController;
+import com.android.server.utils.Slogf;
 
 import java.util.Collection;
 import java.util.HashMap;
@@ -40,13 +43,16 @@
 import java.util.Objects;
 import java.util.stream.Collectors;
 
-public class BroadcastRadioService {
+/**
+ * Broadcast radio service using BroadcastRadio HIDL 2.0 HAL
+ */
+public final class BroadcastRadioService {
     private static final String TAG = "BcRadio2Srv";
 
-    private final Object mLock;
+    private final Object mLock = new Object();
 
     @GuardedBy("mLock")
-    private int mNextModuleId = 0;
+    private int mNextModuleId;
 
     @GuardedBy("mLock")
     private final Map<String, Integer> mServiceNameToModuleIdMap = new HashMap<>();
@@ -69,7 +75,7 @@
                     moduleId = mNextModuleId;
                 }
 
-                RadioModule module = RadioModule.tryLoadingModule(moduleId, serviceName, mLock);
+                RadioModule module = RadioModule.tryLoadingModule(moduleId, serviceName);
                 if (module == null) {
                     return;
                 }
@@ -117,9 +123,8 @@
         }
     };
 
-    public BroadcastRadioService(int nextModuleId, Object lock) {
+    public BroadcastRadioService(int nextModuleId) {
         mNextModuleId = nextModuleId;
-        mLock = lock;
         try {
             IServiceManager manager = IServiceManager.getService();
             if (manager == null) {
@@ -132,10 +137,21 @@
         }
     }
 
+    @VisibleForTesting
+    BroadcastRadioService(int nextModuleId, IServiceManager manager) {
+        mNextModuleId = nextModuleId;
+        Objects.requireNonNull(manager, "Service manager cannot be null");
+        try {
+            manager.registerForNotifications(IBroadcastRadio.kInterfaceName, "", mServiceListener);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "Failed to register for service notifications: ", ex);
+        }
+    }
+
     public @NonNull Collection<RadioManager.ModuleProperties> listModules() {
         Slog.v(TAG, "List HIDL 2.0 modules");
         synchronized (mLock) {
-            return mModules.values().stream().map(module -> module.mProperties)
+            return mModules.values().stream().map(module -> module.getProperties())
                     .collect(Collectors.toList());
         }
     }
@@ -154,14 +170,18 @@
 
     public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
         boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
-        Slog.v(TAG, "Open HIDL 2.0 session");
+        Slog.v(TAG, "Open HIDL 2.0 session with module id " + moduleId);
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.e(TAG, "Cannot open tuner on HAL 2.0 client for non-current user");
+            throw new IllegalStateException("Cannot open session for non-current user");
+        }
         Objects.requireNonNull(callback);
 
         if (!withAudio) {
             throw new IllegalArgumentException("Non-audio sessions not supported with HAL 2.0");
         }
 
-        RadioModule module = null;
+        RadioModule module;
         synchronized (mLock) {
             module = mModules.get(moduleId);
             if (module == null) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index 726cdc3..3daf1db 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -52,8 +52,13 @@
 import java.util.stream.Collectors;
 
 class Convert {
+
     private static final String TAG = "BcRadio2Srv.convert";
 
+    private Convert() {
+        throw new UnsupportedOperationException("Convert class is noninstantiable");
+    }
+
     static void throwOnError(String action, int result) {
         switch (result) {
             case Result.OK:
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
index a9d8054..a6cf72c 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
@@ -34,13 +34,4 @@
     public Mutable() {
         value = null;
     }
-
-    /**
-     * Initialize value with specific value.
-     *
-     * @param value initial value.
-     */
-    public Mutable(E value) {
-        this.value = value;
-    }
 }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java b/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java
index 6654c0c..9831af6 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java
@@ -33,7 +33,7 @@
 import java.util.Map;
 import java.util.Set;
 
-class ProgramInfoCache {
+final class ProgramInfoCache {
     // Maximum number of RadioManager.ProgramInfo elements that will be put into a
     // ProgramList.Chunk.mModified array. Used to try to ensure a single ProgramList.Chunk stays
     // within the AIDL data size limit.
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 0a23e38..0ea5f0f 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -53,14 +53,14 @@
 import java.util.Set;
 import java.util.stream.Collectors;
 
-class RadioModule {
+final class RadioModule {
     private static final String TAG = "BcRadio2Srv.module";
     private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25;
 
     @NonNull private final IBroadcastRadio mService;
-    @NonNull public final RadioManager.ModuleProperties mProperties;
+    @NonNull private final RadioManager.ModuleProperties mProperties;
 
-    private final Object mLock;
+    private final Object mLock = new Object();
     @NonNull private final Handler mHandler;
     @NonNull private final RadioEventLogger mEventLogger;
 
@@ -75,7 +75,7 @@
     private RadioManager.ProgramInfo mCurrentProgramInfo = null;
 
     @GuardedBy("mLock")
-    private final ProgramInfoCache mProgramInfoCache = new ProgramInfoCache(null);
+    private final ProgramInfoCache mProgramInfoCache = new ProgramInfoCache(/* filter= */ null);
 
     @GuardedBy("mLock")
     private android.hardware.radio.ProgramList.Filter mUnionOfAidlProgramFilters = null;
@@ -84,47 +84,59 @@
     private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() {
         @Override
         public void onTuneFailed(int result, ProgramSelector programSelector) {
-            lockAndFireLater(() -> {
+            fireLater(() -> {
                 android.hardware.radio.ProgramSelector csel =
                         Convert.programSelectorFromHal(programSelector);
-                fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel));
+                synchronized (mLock) {
+                    fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel));
+                }
             });
         }
 
         @Override
         public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) {
-            lockAndFireLater(() -> {
-                mCurrentProgramInfo = Convert.programInfoFromHal(halProgramInfo);
-                fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(mCurrentProgramInfo));
+            fireLater(() -> {
+                synchronized (mLock) {
+                    mCurrentProgramInfo = Convert.programInfoFromHal(halProgramInfo);
+                    RadioManager.ProgramInfo currentProgramInfo = mCurrentProgramInfo;
+                    fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(
+                            currentProgramInfo));
+                }
             });
         }
 
         @Override
         public void onProgramListUpdated(ProgramListChunk programListChunk) {
-            lockAndFireLater(() -> {
+            fireLater(() -> {
                 android.hardware.radio.ProgramList.Chunk chunk =
                         Convert.programListChunkFromHal(programListChunk);
-                mProgramInfoCache.filterAndApplyChunk(chunk);
+                synchronized (mLock) {
+                    mProgramInfoCache.filterAndApplyChunk(chunk);
 
-                for (TunerSession tunerSession : mAidlTunerSessions) {
-                    tunerSession.onMergedProgramListUpdateFromHal(chunk);
+                    for (TunerSession tunerSession : mAidlTunerSessions) {
+                        tunerSession.onMergedProgramListUpdateFromHal(chunk);
+                    }
                 }
             });
         }
 
         @Override
         public void onAntennaStateChange(boolean connected) {
-            lockAndFireLater(() -> {
-                mAntennaConnected = connected;
-                fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected));
+            fireLater(() -> {
+                synchronized (mLock) {
+                    mAntennaConnected = connected;
+                    fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected));
+                }
             });
         }
 
         @Override
         public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {
-            lockAndFireLater(() -> {
+            fireLater(() -> {
                 Map<String, String> cparam = Convert.vendorInfoFromHal(parameters);
-                fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam));
+                synchronized (mLock) {
+                    fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam));
+                }
             });
         }
     };
@@ -135,16 +147,15 @@
 
     @VisibleForTesting
     RadioModule(@NonNull IBroadcastRadio service,
-            @NonNull RadioManager.ModuleProperties properties, @NonNull Object lock) {
+            @NonNull RadioManager.ModuleProperties properties) {
         mProperties = Objects.requireNonNull(properties);
         mService = Objects.requireNonNull(service);
-        mLock = Objects.requireNonNull(lock);
         mHandler = new Handler(Looper.getMainLooper());
         mEventLogger = new RadioEventLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE);
     }
 
-    public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName,
-            Object lock) {
+    @Nullable
+    static RadioModule tryLoadingModule(int idx, @NonNull String fqName) {
         try {
             Slog.i(TAG, "Try loading module for idx " + idx + ", fqName " + fqName);
             IBroadcastRadio service = IBroadcastRadio.getService(fqName);
@@ -166,18 +177,23 @@
             RadioManager.ModuleProperties prop = Convert.propertiesFromHal(idx, fqName,
                     service.getProperties(), amfmConfig.value, dabConfig.value);
 
-            return new RadioModule(service, prop, lock);
+            return new RadioModule(service, prop);
         } catch (RemoteException ex) {
             Slog.e(TAG, "Failed to load module " + fqName, ex);
             return null;
         }
     }
 
-    public @NonNull IBroadcastRadio getService() {
+    @NonNull
+    IBroadcastRadio getService() {
         return mService;
     }
 
-    public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
+    public RadioManager.ModuleProperties getProperties() {
+        return mProperties;
+    }
+
+    TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
             throws RemoteException {
         mEventLogger.logRadioEvent("Open TunerSession");
         synchronized (mLock) {
@@ -190,8 +206,7 @@
                 });
                 mHalTunerSession = Objects.requireNonNull(hwSession.value);
             }
-            TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb,
-                    mLock);
+            TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb);
             mAidlTunerSessions.add(tunerSession);
 
             // Propagate state to new client. Note: These callbacks are invoked while holding mLock
@@ -207,7 +222,7 @@
         }
     }
 
-    public void closeSessions(Integer error) {
+    void closeSessions(Integer error) {
         // Copy the contents of mAidlTunerSessions into a local array because TunerSession.close()
         // must be called without mAidlTunerSessions locked because it can call
         // onTunerSessionClosed().
@@ -223,7 +238,9 @@
         }
     }
 
-    private @Nullable android.hardware.radio.ProgramList.Filter
+    @GuardedBy("mLock")
+    @Nullable
+    private android.hardware.radio.ProgramList.Filter
             buildUnionOfTunerSessionFiltersLocked() {
         Set<Integer> idTypes = null;
         Set<android.hardware.radio.ProgramSelector.Identifier> ids = null;
@@ -274,6 +291,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void onTunerSessionProgramListFilterChangedLocked(@Nullable TunerSession session) {
         android.hardware.radio.ProgramList.Filter newFilter =
                 buildUnionOfTunerSessionFiltersLocked();
@@ -318,6 +336,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void onTunerSessionsClosedLocked(TunerSession... tunerSessions) {
         for (TunerSession tunerSession : tunerSessions) {
             mAidlTunerSessions.remove(tunerSession);
@@ -335,12 +354,8 @@
     }
 
     // add to mHandler queue, but ensure the runnable holds mLock when it gets executed
-    private void lockAndFireLater(Runnable r) {
-        mHandler.post(() -> {
-            synchronized (mLock) {
-                r.run();
-            }
-        });
+    private void fireLater(Runnable r) {
+        mHandler.post(() -> r.run());
     }
 
     interface AidlCallbackRunnable {
@@ -349,9 +364,14 @@
 
     // Invokes runnable with each TunerSession currently open.
     void fanoutAidlCallback(AidlCallbackRunnable runnable) {
-        lockAndFireLater(() -> fanoutAidlCallbackLocked(runnable));
+        fireLater(() -> {
+            synchronized (mLock) {
+                fanoutAidlCallbackLocked(runnable);
+            }
+        });
     }
 
+    @GuardedBy("mLock")
     private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) {
         List<TunerSession> deadSessions = null;
         for (TunerSession tunerSession : mAidlTunerSessions) {
@@ -374,8 +394,8 @@
         }
     }
 
-    public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
-            @NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException {
+    android.hardware.radio.ICloseHandle addAnnouncementListener(int[] enabledTypes,
+            android.hardware.radio.IAnnouncementListener listener) throws RemoteException {
         mEventLogger.logRadioEvent("Add AnnouncementListener");
         ArrayList<Byte> enabledList = new ArrayList<>();
         for (int type : enabledTypes) {
@@ -392,12 +412,10 @@
             }
         };
 
-        synchronized (mLock) {
-            mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHnd) -> {
-                halResult.value = result;
-                hwCloseHandle.value = closeHnd;
-            });
-        }
+        mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHandle) -> {
+            halResult.value = result;
+            hwCloseHandle.value = closeHandle;
+        });
         Convert.throwOnError("addAnnouncementListener", halResult.value);
 
         return new android.hardware.radio.ICloseHandle.Stub() {
@@ -417,12 +435,10 @@
         if (id == 0) throw new IllegalArgumentException("Image ID is missing");
 
         byte[] rawImage;
-        synchronized (mLock) {
-            List<Byte> rawList = Utils.maybeRethrow(() -> mService.getImage(id));
-            rawImage = new byte[rawList.size()];
-            for (int i = 0; i < rawList.size(); i++) {
-                rawImage[i] = rawList.get(i);
-            }
+        List<Byte> rawList = Utils.maybeRethrow(() -> mService.getImage(id));
+        rawImage = new byte[rawList.size()];
+        for (int i = 0; i < rawList.size(); i++) {
+            rawImage[i] = rawList.get(i);
         }
 
         if (rawImage == null || rawImage.length == 0) return null;
@@ -433,17 +449,17 @@
     void dumpInfo(IndentingPrintWriter pw) {
         pw.printf("RadioModule\n");
         pw.increaseIndent();
+        pw.printf("BroadcastRadioService: %s\n", mService);
+        pw.printf("Properties: %s\n", mProperties);
         synchronized (mLock) {
-            pw.printf("BroadcastRadioService: %s\n", mService);
-            pw.printf("Properties: %s\n", mProperties);
-            pw.printf("HIDL2.0 HAL TunerSession: %s\n", mHalTunerSession);
+            pw.printf("HIDL 2.0 HAL TunerSession: %s\n", mHalTunerSession);
             pw.printf("Is antenna connected? ");
             if (mAntennaConnected == null) {
                 pw.printf("null\n");
             } else {
                 pw.printf("%s\n", mAntennaConnected ? "Yes" : "No");
             }
-            pw.printf("current ProgramInfo: %s\n", mCurrentProgramInfo);
+            pw.printf("Current ProgramInfo: %s\n", mCurrentProgramInfo);
             pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache);
             pw.printf("Union of AIDL ProgramFilters: %s\n", mUnionOfAidlProgramFilters);
             pw.printf("AIDL TunerSessions:\n");
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 918dc98..7afee27 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -27,11 +27,16 @@
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.MutableBoolean;
 import android.util.MutableInt;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.broadcastradio.RadioServiceUserController;
+import com.android.server.utils.Slogf;
+
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -42,26 +47,28 @@
     private static final String kAudioDeviceName = "Radio tuner source";
     private static final int TUNER_EVENT_LOGGER_QUEUE_SIZE = 25;
 
-    private final Object mLock;
+    private final Object mLock = new Object();
     @NonNull private final RadioEventLogger mEventLogger;
 
     private final RadioModule mModule;
     private final ITunerSession mHwSession;
     final android.hardware.radio.ITunerCallback mCallback;
+
+    @GuardedBy("mLock")
     private boolean mIsClosed = false;
+    @GuardedBy("mLock")
     private boolean mIsMuted = false;
+    @GuardedBy("mLock")
     private ProgramInfoCache mProgramInfoCache = null;
 
     // necessary only for older APIs compatibility
     private RadioManager.BandConfig mDummyConfig = null;
 
     TunerSession(@NonNull RadioModule module, @NonNull ITunerSession hwSession,
-            @NonNull android.hardware.radio.ITunerCallback callback,
-            @NonNull Object lock) {
+            @NonNull android.hardware.radio.ITunerCallback callback) {
         mModule = Objects.requireNonNull(module);
         mHwSession = Objects.requireNonNull(hwSession);
         mCallback = Objects.requireNonNull(callback);
-        mLock = Objects.requireNonNull(lock);
         mEventLogger = new RadioEventLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
     }
 
@@ -82,23 +89,26 @@
         mEventLogger.logRadioEvent("Close on error %d", error);
         synchronized (mLock) {
             if (mIsClosed) return;
-            if (error != null) {
-                try {
-                    mCallback.onError(error);
-                } catch (RemoteException ex) {
-                    Slog.w(TAG, "mCallback.onError() failed: ", ex);
-                }
-            }
             mIsClosed = true;
-            mModule.onTunerSessionClosed(this);
         }
+        if (error != null) {
+            try {
+                mCallback.onError(error);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "mCallback.onError() failed: ", ex);
+            }
+        }
+        mModule.onTunerSessionClosed(this);
     }
 
     @Override
     public boolean isClosed() {
-        return mIsClosed;
+        synchronized (mLock) {
+            return mIsClosed;
+        }
     }
 
+    @GuardedBy("mLock")
     private void checkNotClosedLocked() {
         if (mIsClosed) {
             throw new IllegalStateException("Tuner is closed, no further operations are allowed");
@@ -107,12 +117,16 @@
 
     @Override
     public void setConfiguration(RadioManager.BandConfig config) {
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot set configuration for HAL 2.0 client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             mDummyConfig = Objects.requireNonNull(config);
-            Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.0");
-            mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config));
         }
+        Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.0");
+        mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config));
     }
 
     @Override
@@ -129,8 +143,8 @@
             checkNotClosedLocked();
             if (mIsMuted == mute) return;
             mIsMuted = mute;
-            Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app");
         }
+        Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app");
     }
 
     @Override
@@ -145,6 +159,10 @@
     public void step(boolean directionDown, boolean skipSubChannel) throws RemoteException {
         mEventLogger.logRadioEvent("Step with direction %s, skipSubChannel?  %s",
                 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot step on HAL 2.0 client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             int halResult = mHwSession.step(!directionDown);
@@ -156,6 +174,10 @@
     public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
         mEventLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s",
                 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot scan on HAL 2.0 client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             int halResult = mHwSession.scan(!directionDown, skipSubChannel);
@@ -166,6 +188,10 @@
     @Override
     public void tune(ProgramSelector selector) throws RemoteException {
         mEventLogger.logRadioEvent("Tune with selector %s", selector);
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot tune on HAL 2.0 client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             int halResult = mHwSession.tune(Convert.programSelectorToHal(selector));
@@ -176,6 +202,10 @@
     @Override
     public void cancel() {
         Slog.i(TAG, "Cancel");
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot cancel on HAL 2.0 client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             Utils.maybeRethrow(mHwSession::cancel);
@@ -196,6 +226,11 @@
     @Override
     public boolean startBackgroundScan() {
         Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG,
+                    "Cannot start background scan on HAL 2.0 client from non-current user");
+            return false;
+        }
         mModule.fanoutAidlCallback(cb -> cb.onBackgroundScanComplete());
         return true;
     }
@@ -203,6 +238,11 @@
     @Override
     public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {
         mEventLogger.logRadioEvent("start programList updates %s", filter);
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG,
+                    "Cannot start program list updates on HAL 2.0 client from non-current user");
+            return;
+        }
         // If the AIDL client provides a null filter, it wants all updates, so use the most broad
         // filter.
         if (filter == null) {
@@ -262,6 +302,11 @@
     @Override
     public void stopProgramListUpdates() throws RemoteException {
         mEventLogger.logRadioEvent("Stop programList updates");
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG,
+                    "Cannot stop program list updates on HAL 2.0 client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             mProgramInfoCache = null;
@@ -308,6 +353,10 @@
     @Override
     public void setConfigFlag(int flag, boolean value) throws RemoteException {
         mEventLogger.logRadioEvent("Set ConfigFlag  %s = %b", ConfigFlag.toString(flag), value);
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot set config flag for HAL 2.0 client from non-current user");
+            return;
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             int halResult = mHwSession.setConfigFlag(flag, value);
@@ -317,6 +366,10 @@
 
     @Override
     public Map<String, String> setParameters(Map<String, String> parameters) {
+        if (!RadioServiceUserController.isCurrentOrSystemUser()) {
+            Slogf.w(TAG, "Cannot set parameters for HAL 2.0 client from non-current user");
+            return new ArrayMap<>();
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             return Convert.vendorInfoFromHal(Utils.maybeRethrow(
@@ -336,8 +389,8 @@
     void dumpInfo(IndentingPrintWriter pw) {
         pw.printf("TunerSession\n");
         pw.increaseIndent();
+        pw.printf("HIDL HAL Session: %s\n", mHwSession);
         synchronized (mLock) {
-            pw.printf("HIDL HAL Session: %s\n", mHwSession);
             pw.printf("Is session closed? %s\n", mIsClosed ? "Yes" : "No");
             pw.printf("Is muted? %s\n", mIsMuted ? "Yes" : "No");
             pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache);
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Utils.java b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
index 384c9ba..188c25d 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
@@ -25,9 +25,14 @@
     AM_LW,
     AM_MW,
     AM_SW,
-};
+}
 
 class Utils {
+
+    private Utils() {
+        throw new UnsupportedOperationException("Utils class is noninstantiable");
+    }
+
     private static final String TAG = "BcRadio2Srv.utils";
 
     static FrequencyBand getBand(int freq) {
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 11eb782..e16ca0b 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -551,6 +551,15 @@
                     lensFacing, ignoreResizableAndSdkCheck);
         }
 
+        /**
+         * Placeholder method to fetch the system state for autoframing.
+         * TODO: b/260617354
+         */
+        @Override
+        public int getAutoframingOverride(String packageName) {
+            return CaptureRequest.CONTROL_AUTOFRAMING_OFF;
+        }
+
         @Override
         public void pingForUserUpdate() {
             if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
@@ -841,6 +850,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 +873,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/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 81b56a3..d2e572f 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
 
 import java.util.Set;
 
@@ -109,4 +110,14 @@
      * Returns true if the {@code displayId} is owned by any virtual device
      */
     public abstract boolean isDisplayOwnedByAnyVirtualDevice(int displayId);
+
+    /**
+     * Returns the device policy for the given virtual device and policy type.
+     *
+     * <p>In case the virtual device identifier is not valid, or there's no explicitly specified
+     * policy for that device and policy type, then
+     * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned.
+     */
+    public abstract @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
+            int deviceId, @VirtualDeviceParams.PolicyType int policyType);
 }
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 387e00f..2c83c6f 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -95,6 +95,8 @@
     @Override
     @EnforcePermission(LOG_COMPAT_CHANGE)
     public void reportChange(long changeId, ApplicationInfo appInfo) {
+        super.reportChange_enforcePermission();
+
         reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED);
     }
 
@@ -102,6 +104,8 @@
     @EnforcePermission(LOG_COMPAT_CHANGE)
     public void reportChangeByPackageName(long changeId, String packageName,
             @UserIdInt int userId) {
+        super.reportChangeByPackageName_enforcePermission();
+
         ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
         if (appInfo != null) {
             reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED);
@@ -111,6 +115,8 @@
     @Override
     @EnforcePermission(LOG_COMPAT_CHANGE)
     public void reportChangeByUid(long changeId, int uid) {
+        super.reportChangeByUid_enforcePermission();
+
         reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED);
     }
 
@@ -121,6 +127,8 @@
     @Override
     @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
     public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
+        super.isChangeEnabled_enforcePermission();
+
         return isChangeEnabledInternal(changeId, appInfo);
     }
 
@@ -128,6 +136,8 @@
     @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
     public boolean isChangeEnabledByPackageName(long changeId, String packageName,
             @UserIdInt int userId) {
+        super.isChangeEnabledByPackageName_enforcePermission();
+
         ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
         if (appInfo == null) {
             return mCompatConfig.willChangeBeEnabled(changeId, packageName);
@@ -138,6 +148,8 @@
     @Override
     @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
     public boolean isChangeEnabledByUid(long changeId, int uid) {
+        super.isChangeEnabledByUid_enforcePermission();
+
         String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
         if (packages == null || packages.length == 0) {
             return mCompatConfig.defaultChangeIdValue(changeId);
@@ -199,6 +211,8 @@
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
+        super.setOverrides_enforcePermission();
+
         Map<Long, PackageOverride> overridesMap = new HashMap<>();
         for (long change : overrides.enabledChanges()) {
             overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build());
@@ -215,6 +229,8 @@
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) {
+        super.setOverridesForTest_enforcePermission();
+
         Map<Long, PackageOverride> overridesMap = new HashMap<>();
         for (long change : overrides.enabledChanges()) {
             overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build());
@@ -231,6 +247,8 @@
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
     public void putAllOverridesOnReleaseBuilds(
             CompatibilityOverridesByPackageConfig overridesByPackage) {
+        super.putAllOverridesOnReleaseBuilds_enforcePermission();
+
         for (CompatibilityOverrideConfig overrides :
                 overridesByPackage.packageNameToOverrides.values()) {
             checkAllCompatOverridesAreOverridable(overrides.overrides.keySet());
@@ -242,6 +260,8 @@
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
     public void putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides,
             String packageName) {
+        super.putOverridesOnReleaseBuilds_enforcePermission();
+
         checkAllCompatOverridesAreOverridable(overrides.overrides.keySet());
         mCompatConfig.addPackageOverrides(overrides, packageName, /* skipUnknownChangeIds= */ true);
     }
@@ -249,6 +269,8 @@
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public int enableTargetSdkChanges(String packageName, int targetSdkVersion) {
+        super.enableTargetSdkChanges_enforcePermission();
+
         int numChanges =
                 mCompatConfig.enableTargetSdkChangesForPackage(packageName, targetSdkVersion);
         killPackage(packageName);
@@ -258,6 +280,8 @@
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public int disableTargetSdkChanges(String packageName, int targetSdkVersion) {
+        super.disableTargetSdkChanges_enforcePermission();
+
         int numChanges =
                 mCompatConfig.disableTargetSdkChangesForPackage(packageName, targetSdkVersion);
         killPackage(packageName);
@@ -267,6 +291,8 @@
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public void clearOverrides(String packageName) {
+        super.clearOverrides_enforcePermission();
+
         mCompatConfig.removePackageOverrides(packageName);
         killPackage(packageName);
     }
@@ -274,12 +300,16 @@
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public void clearOverridesForTest(String packageName) {
+        super.clearOverridesForTest_enforcePermission();
+
         mCompatConfig.removePackageOverrides(packageName);
     }
 
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public boolean clearOverride(long changeId, String packageName) {
+        super.clearOverride_enforcePermission();
+
         boolean existed = mCompatConfig.removeOverride(changeId, packageName);
         killPackage(packageName);
         return existed;
@@ -288,6 +318,8 @@
     @Override
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
     public boolean clearOverrideForTest(long changeId, String packageName) {
+        super.clearOverrideForTest_enforcePermission();
+
         return mCompatConfig.removeOverride(changeId, packageName);
     }
 
@@ -295,6 +327,8 @@
     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
     public void removeAllOverridesOnReleaseBuilds(
             CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage) {
+        super.removeAllOverridesOnReleaseBuilds_enforcePermission();
+
         for (CompatibilityOverridesToRemoveConfig overridesToRemove :
                 overridesToRemoveByPackage.packageNameToOverridesToRemove.values()) {
             checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds);
@@ -307,6 +341,8 @@
     public void removeOverridesOnReleaseBuilds(
             CompatibilityOverridesToRemoveConfig overridesToRemove,
             String packageName) {
+        super.removeOverridesOnReleaseBuilds_enforcePermission();
+
         checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds);
         mCompatConfig.removePackageOverrides(overridesToRemove, packageName);
     }
@@ -314,12 +350,16 @@
     @Override
     @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
     public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) {
+        super.getAppConfig_enforcePermission();
+
         return mCompatConfig.getAppConfig(appInfo);
     }
 
     @Override
     @EnforcePermission(READ_COMPAT_CHANGE_CONFIG)
     public CompatibilityChangeInfo[] listAllChanges() {
+        super.listAllChanges_enforcePermission();
+
         return mCompatConfig.dumpChanges();
     }
 
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 45b0f0a6..4fcde97 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;
@@ -124,6 +127,8 @@
 import android.system.keystore2.KeyPermission;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
 import android.util.Log;
 import android.util.Range;
 
@@ -140,6 +145,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 +158,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 +173,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;
@@ -289,6 +298,10 @@
         return mVpnProfileStore;
     }
 
+    private static final int MAX_EVENTS_LOGS = 20;
+    private final LocalLog mUnderlyNetworkChanges = new LocalLog(MAX_EVENTS_LOGS);
+    private final LocalLog mVpnManagerEvents = new LocalLog(MAX_EVENTS_LOGS);
+
     /**
      * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
      * only applies to {@link VpnService} connections.
@@ -551,6 +564,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
@@ -614,7 +645,8 @@
                 .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
-                .setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE, null))
+                .setTransportInfo(new VpnTransportInfo(
+                        VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
                 .build();
 
         loadAlwaysOnPackage();
@@ -678,7 +710,8 @@
     private void resetNetworkCapabilities() {
         mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
                 .setUids(null)
-                .setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE, null))
+                .setTransportInfo(new VpnTransportInfo(
+                        VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
                 .build();
     }
 
@@ -816,6 +849,9 @@
             int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
             @NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
             @Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
+        mVpnManagerEvents.log("Event class=" + getVpnManagerEventClassName(errorClass)
+                + ", err=" + getVpnManagerEventErrorName(errorCode) + " for " + packageName
+                + " on session " + sessionKey);
         final Intent intent = buildVpnManagerEventIntent(category, errorClass, errorCode,
                 packageName, sessionKey, profileState, underlyingNetwork, nc, lp);
         return sendEventToVpnManagerApp(intent, packageName);
@@ -1397,6 +1433,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 +1447,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 +1456,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 +1469,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 +1484,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));
@@ -1525,7 +1569,8 @@
         capsBuilder.setUids(createUserAndRestrictedProfilesRanges(mUserId,
                 mConfig.allowedApplications, mConfig.disallowedApplications));
 
-        capsBuilder.setTransportInfo(new VpnTransportInfo(getActiveVpnType(), mConfig.session));
+        capsBuilder.setTransportInfo(
+                new VpnTransportInfo(getActiveVpnType(), mConfig.session, mConfig.allowBypass));
 
         // Only apps targeting Q and above can explicitly declare themselves as metered.
         // These VPNs are assumed metered unless they state otherwise.
@@ -1539,6 +1584,7 @@
                 ? Arrays.asList(mConfig.underlyingNetworks) : null);
 
         mNetworkCapabilities = capsBuilder.build();
+        logUnderlyNetworkChanges(mNetworkCapabilities.getUnderlyingNetworks());
         mNetworkAgent = mDeps.newNetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
                 mNetworkCapabilities, lp,
                 new NetworkScore.Builder().setLegacyInt(VPN_DEFAULT_SCORE).build(),
@@ -1566,6 +1612,11 @@
         }
     }
 
+    private void logUnderlyNetworkChanges(List<Network> networks) {
+        mUnderlyNetworkChanges.log("Switch to "
+                + ((networks != null) ? TextUtils.join(", ", networks) : "null"));
+    }
+
     private void agentDisconnect(NetworkAgent networkAgent) {
         if (networkAgent != null) {
             networkAgent.unregister();
@@ -1576,6 +1627,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 +1728,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 +2765,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 +2946,6 @@
                     ikeConfiguration.isIkeExtensionEnabled(
                             IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE);
             onIkeConnectionInfoChanged(token, ikeConfiguration.getIkeSessionConnectionInfo());
-            mRetryCount = 0;
         }
 
         /**
@@ -2924,15 +2988,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 +3026,7 @@
                     if (mVpnRunner != this) return;
 
                     mInterface = interfaceName;
-                    mConfig.mtu = maxMtu;
+                    mConfig.mtu = vpnMtu;
                     mConfig.interfaze = mInterface;
 
                     mConfig.addresses.clear();
@@ -2989,6 +3065,7 @@
                 }
 
                 doSendLinkProperties(networkAgent, lp);
+                mRetryCount = 0;
             } catch (Exception e) {
                 Log.d(TAG, "Error in ChildOpened for token " + token, e);
                 onSessionLost(token, e);
@@ -3053,12 +3130,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 +3227,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 +3331,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 +3348,8 @@
                 mSession =
                         mIkev2SessionCreator.createIkeSession(
                                 mContext,
-                                ikeSessionParams,
-                                childSessionParams,
+                                getIkeSessionParams(underlyingNetwork),
+                                getChildSessionParams(),
                                 mExecutor,
                                 new VpnIkev2Utils.IkeSessionCallbackImpl(
                                         TAG, IkeV2VpnRunner.this, token),
@@ -3208,6 +3363,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
@@ -3316,6 +3475,8 @@
                 return;
             } else {
                 mActiveNetwork = null;
+                mUnderlyingNetworkCapabilities = null;
+                mUnderlyingLinkProperties = null;
             }
 
             if (mScheduledHandleNetworkLostFuture != null) {
@@ -3505,9 +3666,6 @@
                 scheduleRetryNewIkeSession();
             }
 
-            mUnderlyingNetworkCapabilities = null;
-            mUnderlyingLinkProperties = null;
-
             // Close all obsolete state, but keep VPN alive incase a usable network comes up.
             // (Mirrors VpnService behavior)
             Log.d(TAG, "Resetting state for token: " + mCurrentToken);
@@ -4231,6 +4389,7 @@
         // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
         //  ConnectivityServiceTest.
         if (SdkLevel.isAtLeastT()) {
+            mVpnManagerEvents.log(packageName + " stopped");
             sendEventToVpnManagerApp(intent, packageName);
         }
     }
@@ -4398,8 +4557,10 @@
     /** Proxy to allow different testing setups */
     // TODO: b/240492694 Remove VpnNetworkAgentWrapper and this method when
     // NetworkAgent#setUnderlyingNetworks can be un-finalized.
-    private static void doSetUnderlyingNetworks(
+    private void doSetUnderlyingNetworks(
             @NonNull NetworkAgent agent, @NonNull List<Network> networks) {
+        logUnderlyNetworkChanges(networks);
+
         if (agent instanceof VpnNetworkAgentWrapper) {
             ((VpnNetworkAgentWrapper) agent).doSetUnderlyingNetworks(networks);
         } else {
@@ -4518,4 +4679,57 @@
     static Range<Integer> createUidRangeForUser(int userId) {
         return new Range<Integer>(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1);
     }
+
+    private String getVpnManagerEventClassName(int code) {
+        switch (code) {
+            case VpnManager.ERROR_CLASS_NOT_RECOVERABLE:
+                return "ERROR_CLASS_NOT_RECOVERABLE";
+            case VpnManager.ERROR_CLASS_RECOVERABLE:
+                return "ERROR_CLASS_RECOVERABLE";
+            default:
+                return "UNKNOWN_CLASS";
+        }
+    }
+
+    private String getVpnManagerEventErrorName(int code) {
+        switch (code) {
+            case VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST:
+                return "ERROR_CODE_NETWORK_UNKNOWN_HOST";
+            case VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT:
+                return "ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT";
+            case VpnManager.ERROR_CODE_NETWORK_IO:
+                return "ERROR_CODE_NETWORK_IO";
+            case VpnManager.ERROR_CODE_NETWORK_LOST:
+                return "ERROR_CODE_NETWORK_LOST";
+            default:
+                return "UNKNOWN_ERROR";
+        }
+    }
+
+    /** Dumps VPN state. */
+    public void dump(IndentingPrintWriter pw) {
+        synchronized (Vpn.this) {
+            pw.println("Active package name: " + mPackage);
+            pw.println("Active vpn type: " + getActiveVpnType());
+            pw.println("NetworkCapabilities: " + mNetworkCapabilities);
+            if (isIkev2VpnRunner()) {
+                final IkeV2VpnRunner runner = ((IkeV2VpnRunner) mVpnRunner);
+                pw.println("Token: " + runner.mSessionKey);
+                pw.println("MOBIKE " + (runner.mMobikeEnabled ? "enabled" : "disabled"));
+                if (mDataStallSuspected) pw.println("Data stall suspected");
+                if (runner.mScheduledHandleDataStallFuture != null) {
+                    pw.println("Reset session scheduled");
+                }
+            }
+            pw.println("mUnderlyNetworkChanges (most recent first):");
+            pw.increaseIndent();
+            mUnderlyNetworkChanges.reverseDump(pw);
+            pw.decreaseIndent();
+
+            pw.println("mVpnManagerEvent (most recent first):");
+            pw.increaseIndent();
+            mVpnManagerEvents.reverseDump(pw);
+            pw.decreaseIndent();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/content/SyncAdapterStateFetcher.java b/services/core/java/com/android/server/content/SyncAdapterStateFetcher.java
index ffaf364..108cddc 100644
--- a/services/core/java/com/android/server/content/SyncAdapterStateFetcher.java
+++ b/services/core/java/com/android/server/content/SyncAdapterStateFetcher.java
@@ -17,8 +17,8 @@
 
 import android.app.ActivityManagerInternal;
 import android.app.usage.UsageStatsManagerInternal;
+import android.content.pm.UserPackage;
 import android.os.SystemClock;
-import android.util.Pair;
 
 import com.android.server.LocalServices;
 
@@ -26,8 +26,7 @@
 
 class SyncAdapterStateFetcher {
 
-    private final HashMap<Pair<Integer, String>, Integer> mBucketCache =
-            new HashMap<>();
+    private final HashMap<UserPackage, Integer> mBucketCache = new HashMap<>();
 
     public SyncAdapterStateFetcher() {
     }
@@ -36,7 +35,7 @@
      * Return sync adapter state with a cache.
      */
     public int getStandbyBucket(int userId, String packageName) {
-        final Pair<Integer, String> key = Pair.create(userId, packageName);
+        final UserPackage key = UserPackage.of(userId, packageName);
         final Integer cached = mBucketCache.get(key);
         if (cached != null) {
             return cached;
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 73afa60..dcc98e1 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -27,6 +27,7 @@
 import android.accounts.AccountManagerInternal;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
@@ -65,6 +66,7 @@
 import android.content.pm.RegisteredServicesCacheListener;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
 import android.database.ContentObserver;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
@@ -88,6 +90,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.WorkSource;
+import android.provider.ContactsContract;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.format.TimeMigrationUtils;
@@ -99,6 +102,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
@@ -498,7 +502,7 @@
             }
             mJobScheduler = (JobScheduler) mContext.getSystemService(
                     Context.JOB_SCHEDULER_SERVICE);
-            mJobSchedulerInternal = LocalServices.getService(JobSchedulerInternal.class);
+            mJobSchedulerInternal = getJobSchedulerInternal();
             // Get all persisted syncs from JobScheduler
             List<JobInfo> pendingJobs = mJobScheduler.getAllPendingJobs();
 
@@ -536,6 +540,11 @@
         }
     }
 
+    @VisibleForTesting
+    protected JobSchedulerInternal getJobSchedulerInternal() {
+        return LocalServices.getService(JobSchedulerInternal.class);
+    }
+
     /**
      * @return whether the device most likely has some periodic syncs.
      */
@@ -645,7 +654,7 @@
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mAccountManager = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE);
-        mAccountManagerInternal = LocalServices.getService(AccountManagerInternal.class);
+        mAccountManagerInternal = getAccountManagerInternal();
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mAmi = LocalServices.getService(ActivityManagerInternal.class);
 
@@ -719,6 +728,11 @@
         mLogger.log("Sync manager initialized: " + Build.FINGERPRINT);
     }
 
+    @VisibleForTesting
+    protected AccountManagerInternal getAccountManagerInternal() {
+        return LocalServices.getService(AccountManagerInternal.class);
+    }
+
     public void onStartUser(int userId) {
         // Log on the handler to avoid slowing down device boot.
         mSyncHandler.post(() -> mLogger.log("onStartUser: user=", userId));
@@ -800,9 +814,44 @@
         return mSyncStorageEngine;
     }
 
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private boolean areContactWritesEnabledForUser(UserInfo userInfo) {
+        final UserManager um = UserManager.get(mContext);
+        try {
+            final UserProperties userProperties = um.getUserProperties(userInfo.getUserHandle());
+            return !userProperties.getUseParentsContacts();
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "Trying to fetch user properties for non-existing/partial user "
+                    + userInfo.getUserHandle());
+            return false;
+        }
+    }
+
+    /**
+     * Check if account sync should be disabled for the given user and provider.
+     * @param userInfo
+     * @param providerName
+     * @return true if sync for the account corresponding to the given user and provider should be
+     * disabled, false otherwise. Also returns false if either of the inputs are null.
+     */
+    @VisibleForTesting
+    protected boolean shouldDisableSyncForUser(UserInfo userInfo, String providerName) {
+        if (userInfo == null || providerName == null) return false;
+        return providerName.equals(ContactsContract.AUTHORITY)
+                && !areContactWritesEnabledForUser(userInfo);
+    }
+
     private int getIsSyncable(Account account, int userId, String providerName) {
         int isSyncable = mSyncStorageEngine.getIsSyncable(account, userId, providerName);
-        UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId);
+        final UserManager um = UserManager.get(mContext);
+        UserInfo userInfo = um.getUserInfo(userId);
+
+        // Check if the provider is allowed to sync data from linked accounts for the user
+        if (shouldDisableSyncForUser(userInfo, providerName)) {
+            Log.w(TAG, "Account sync is disabled for account: " + account
+                    + " userId: " + userId + " provider: " + providerName);
+            return AuthorityInfo.NOT_SYNCABLE;
+        }
 
         // If it's not a restricted user, return isSyncable.
         if (userInfo == null || !userInfo.isRestricted()) return isSyncable;
@@ -2215,7 +2264,8 @@
         pw.print("Storage low: "); pw.println(storageLowIntent != null);
         pw.print("Clock valid: "); pw.println(mSyncStorageEngine.isClockValid());
 
-        final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
+        final AccountAndUser[] accounts =
+                AccountManagerService.getSingleton().getAllAccountsForSystemProcess();
 
         pw.print("Accounts: ");
         if (accounts != INITIAL_ACCOUNTS_ARRAY) {
@@ -3274,7 +3324,8 @@
         private void updateRunningAccountsH(EndPoint syncTargets) {
             synchronized (mAccountsLock) {
                 AccountAndUser[] oldAccounts = mRunningAccounts;
-                mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
+                mRunningAccounts =
+                        AccountManagerService.getSingleton().getRunningAccountsForSystem();
                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
                     Slog.v(TAG, "Accounts list: ");
                     for (AccountAndUser acc : mRunningAccounts) {
@@ -3316,7 +3367,8 @@
             }
 
             // Cancel all jobs from non-existent accounts.
-            AccountAndUser[] allAccounts = AccountManagerService.getSingleton().getAllAccounts();
+            AccountAndUser[] allAccounts =
+                    AccountManagerService.getSingleton().getAllAccountsForSystemProcess();
             List<SyncOperation> ops = getAllPendingSyncs();
             for (int i = 0, opsSize = ops.size(); i < opsSize; i++) {
                 SyncOperation op = ops.get(i);
diff --git a/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java b/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
new file mode 100644
index 0000000..06b45bf
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
@@ -0,0 +1,63 @@
+/*
+ * 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.cpu;
+
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL;
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND;
+
+import com.android.internal.util.Preconditions;
+
+/** CPU availability information. */
+public final class CpuAvailabilityInfo {
+    /** Constant to indicate missing CPU availability percent. */
+    public static final int MISSING_CPU_AVAILABILITY_PERCENT = -1;
+
+    /**
+     * The CPUSET whose availability info is recorded in this object.
+     *
+     * <p>The contained value is one of the CPUSET_* constants from the
+     * {@link CpuAvailabilityMonitoringConfig}.
+     */
+    @CpuAvailabilityMonitoringConfig.Cpuset
+    public final int cpuset;
+
+    /** The latest average CPU availability percent. */
+    public final int latestAvgAvailabilityPercent;
+
+    /** The past N-second average CPU availability percent. */
+    public final int pastNSecAvgAvailabilityPercent;
+
+    /** The duration over which the {@link pastNSecAvgAvailabilityPercent} was calculated. */
+    public final int avgAvailabilityDurationSec;
+
+    @Override
+    public String toString() {
+        return "CpuAvailabilityInfo{" + "cpuset=" + cpuset + ", latestAvgAvailabilityPercent="
+                + latestAvgAvailabilityPercent + ", pastNSecAvgAvailabilityPercent="
+                + pastNSecAvgAvailabilityPercent + ", avgAvailabilityDurationSec="
+                + avgAvailabilityDurationSec + '}';
+    }
+
+    CpuAvailabilityInfo(int cpuset, int latestAvgAvailabilityPercent,
+            int pastNSecAvgAvailabilityPercent, int avgAvailabilityDurationSec) {
+        this.cpuset = Preconditions.checkArgumentInRange(cpuset, CPUSET_ALL, CPUSET_BACKGROUND,
+                "cpuset");
+        this.latestAvgAvailabilityPercent = latestAvgAvailabilityPercent;
+        this.pastNSecAvgAvailabilityPercent = pastNSecAvgAvailabilityPercent;
+        this.avgAvailabilityDurationSec = avgAvailabilityDurationSec;
+    }
+}
diff --git a/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java b/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java
new file mode 100644
index 0000000..a3c4c9e
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.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 com.android.server.cpu;
+
+import android.annotation.IntDef;
+import android.util.IntArray;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** CPU availability monitoring config. */
+public final class CpuAvailabilityMonitoringConfig {
+    /** Constant to monitor all cpusets. */
+    public static final int CPUSET_ALL = 1;
+
+    /** Constant to monitor background cpusets. */
+    public static final int CPUSET_BACKGROUND = 2;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CPUSET_"}, value = {
+            CPUSET_ALL,
+            CPUSET_BACKGROUND
+    })
+    public @interface Cpuset {
+    }
+
+    /**
+     * The CPUSET to monitor.
+     *
+     * <p>The value must be one of the {@code CPUSET_*} constants.
+     */
+    @Cpuset
+    public final int cpuset;
+
+    /**
+     * CPU availability percent thresholds.
+     *
+     * <p>CPU availability change notifications are sent when the latest or last N seconds average
+     * CPU availability percent crosses any of these thresholds since the last notification.
+     */
+    private final IntArray mThresholds;
+
+    public IntArray getThresholds() {
+        return mThresholds;
+    }
+
+    /**
+     * Builder for the construction of {@link CpuAvailabilityMonitoringConfig} objects.
+     *
+     * <p>The builder must contain at least one threshold before calling {@link build}.
+     */
+    public static final class Builder {
+        private final int mCpuset;
+        private final IntArray mThresholds = new IntArray();
+
+        public Builder(int cpuset, int... thresholds) {
+            mCpuset = cpuset;
+            for (int threshold : thresholds) {
+                addThreshold(threshold);
+            }
+        }
+
+        /** Adds the given threshold to the builder object. */
+        public Builder addThreshold(int threshold) {
+            if (mThresholds.indexOf(threshold) == -1) {
+                mThresholds.add(threshold);
+            }
+            return this;
+        }
+
+        /** Returns the {@link CpuAvailabilityMonitoringConfig} object. */
+        public CpuAvailabilityMonitoringConfig build() {
+            return new CpuAvailabilityMonitoringConfig(this);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "CpuAvailabilityMonitoringConfig{cpuset=" + cpuset + ", mThresholds=" + mThresholds
+                + ')';
+    }
+
+    private CpuAvailabilityMonitoringConfig(Builder builder) {
+        if (builder.mCpuset != CPUSET_ALL && builder.mCpuset != CPUSET_BACKGROUND) {
+            throw new IllegalStateException("Cpuset must be either CPUSET_ALL (" + CPUSET_ALL
+                    + ") or CPUSET_BACKGROUND (" + CPUSET_BACKGROUND + "). Builder contains "
+                    + builder.mCpuset);
+        }
+        if (builder.mThresholds.size() == 0) {
+            throw new IllegalStateException("Must provide at least one threshold");
+        }
+        this.cpuset = builder.mCpuset;
+        this.mThresholds = builder.mThresholds.clone();
+    }
+}
diff --git a/services/core/java/com/android/server/cpu/CpuInfoReader.java b/services/core/java/com/android/server/cpu/CpuInfoReader.java
new file mode 100644
index 0000000..680829d
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/CpuInfoReader.java
@@ -0,0 +1,453 @@
+/*
+ * 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.cpu;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
+
+import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Reader to read CPU information from proc and sys fs files exposed by the Kernel. */
+public final class CpuInfoReader {
+    static final String TAG = CpuInfoReader.class.getSimpleName();
+    static final int FLAG_CPUSET_CATEGORY_TOP_APP = 1 << 0;
+    static final int FLAG_CPUSET_CATEGORY_BACKGROUND = 1 << 1;
+
+    private static final String CPUFREQ_DIR_PATH = "/sys/devices/system/cpu/cpufreq";
+    private static final String POLICY_DIR_PREFIX = "policy";
+    private static final String RELATED_CPUS_FILE = "related_cpus";
+    private static final String MAX_CPUFREQ_FILE = "cpuinfo_max_freq";
+    private static final String MAX_SCALING_FREQ_FILE = "scaling_max_freq";
+    private static final String CPUSET_DIR_PATH = "/dev/cpuset";
+    private static final String CPUSET_TOP_APP_DIR = "top-app";
+    private static final String CPUSET_BACKGROUND_DIR = "background";
+    private static final String CPUS_FILE = "cpus";
+    private static final String PROC_STAT_FILE_PATH = "/proc/stat";
+    private static final Pattern PROC_STAT_PATTERN =
+            Pattern.compile("cpu(?<core>[0-9]+)\\s(?<userClockTicks>[0-9]+)\\s"
+                    + "(?<niceClockTicks>[0-9]+)\\s(?<sysClockTicks>[0-9]+)\\s"
+                    + "(?<idleClockTicks>[0-9]+)\\s(?<iowaitClockTicks>[0-9]+)\\s"
+                    + "(?<irqClockTicks>[0-9]+)\\s(?<softirqClockTicks>[0-9]+)\\s"
+                    + "(?<stealClockTicks>[0-9]+)\\s(?<guestClockTicks>[0-9]+)\\s"
+                    + "(?<guestNiceClockTicks>[0-9]+)");
+    private static final long MILLIS_PER_JIFFY = 1000L / Os.sysconf(OsConstants._SC_CLK_TCK);
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"FLAG_CPUSET_CATEGORY_"}, flag = true, value = {
+            FLAG_CPUSET_CATEGORY_TOP_APP,
+            FLAG_CPUSET_CATEGORY_BACKGROUND
+    })
+    private @interface CpusetCategory{}
+
+    private final File mCpusetDir;
+    private final File mCpuFreqDir;
+    private final File mProcStatFile;
+    private final SparseIntArray mCpusetCategoriesByCpus = new SparseIntArray();
+    private final SparseArray<Long> mMaxCpuFrequenciesByCpus = new SparseArray<>();
+
+    private File[] mCpuFreqPolicyDirs;
+    private SparseArray<CpuUsageStats> mCumulativeCpuUsageStats = new SparseArray<>();
+    private boolean mIsEnabled;
+
+    public CpuInfoReader() {
+        this(new File(CPUSET_DIR_PATH), new File(CPUFREQ_DIR_PATH), new File(PROC_STAT_FILE_PATH));
+    }
+
+    @VisibleForTesting
+    CpuInfoReader(File cpusetDir, File cpuFreqDir, File procStatFile) {
+        mCpusetDir = cpusetDir;
+        mCpuFreqDir = cpuFreqDir;
+        mProcStatFile = procStatFile;
+    }
+
+    /** Inits CpuInfoReader and returns a boolean to indicate whether the reader is enabled. */
+    public boolean init() {
+        mCpuFreqPolicyDirs = mCpuFreqDir.listFiles(
+                file -> file.isDirectory() && file.getName().startsWith(POLICY_DIR_PREFIX));
+        if (mCpuFreqPolicyDirs == null || mCpuFreqPolicyDirs.length == 0) {
+            Slogf.w(TAG, "Missing CPU frequency policy directories at %s",
+                    mCpuFreqDir.getAbsolutePath());
+            return false;
+        }
+        if (!mProcStatFile.exists()) {
+            Slogf.e(TAG, "Missing proc stat file at %s", mProcStatFile.getAbsolutePath());
+            return false;
+        }
+        readCpusetCategories();
+        if (mCpusetCategoriesByCpus.size() == 0) {
+            Slogf.e(TAG, "Failed to read cpuset information read from %s",
+                    mCpusetDir.getAbsolutePath());
+            return false;
+        }
+        readMaxCpuFrequencies();
+        if (mMaxCpuFrequenciesByCpus.size() == 0) {
+            Slogf.e(TAG, "Failed to read max CPU frequencies from policy directories at %s",
+                    mCpuFreqDir.getAbsolutePath());
+            return false;
+        }
+        mIsEnabled = true;
+        return true;
+    }
+
+    /** Reads CPU information from proc and sys fs files exposed by the Kernel. */
+    public List<CpuInfo> readCpuInfos() {
+        if (!mIsEnabled) {
+            return Collections.emptyList();
+        }
+        SparseArray<CpuUsageStats> latestCpuUsageStats = readLatestCpuUsageStats();
+        if (latestCpuUsageStats == null) {
+            Slogf.e(TAG, "Failed to read latest CPU usage stats");
+            return Collections.emptyList();
+        }
+        // TODO(b/217422127): Read current CPU frequencies and populate the CpuInfo.
+        return Collections.emptyList();
+    }
+
+    private void readCpusetCategories() {
+        File[] cpusetDirs = mCpusetDir.listFiles(File::isDirectory);
+        if (cpusetDirs == null) {
+            Slogf.e(TAG, "Missing cpuset directories at %s", mCpusetDir.getAbsolutePath());
+            return;
+        }
+        for (int i = 0; i < cpusetDirs.length; i++) {
+            File dir = cpusetDirs[i];
+            @CpusetCategory int cpusetCategory;
+            switch (dir.getName()) {
+                case CPUSET_TOP_APP_DIR:
+                    cpusetCategory = FLAG_CPUSET_CATEGORY_TOP_APP;
+                    break;
+                case CPUSET_BACKGROUND_DIR:
+                    cpusetCategory = FLAG_CPUSET_CATEGORY_BACKGROUND;
+                    break;
+                default:
+                    continue;
+            }
+            File cpuCoresFile = new File(dir.getPath(), CPUS_FILE);
+            List<Integer> cpuCores = readCpuCores(cpuCoresFile);
+            if (cpuCores.isEmpty()) {
+                Slogf.e(TAG, "Failed to read CPU cores from %s", cpuCoresFile.getAbsolutePath());
+                continue;
+            }
+            for (int j = 0; j < cpuCores.size(); j++) {
+                int categories = mCpusetCategoriesByCpus.get(cpuCores.get(j));
+                categories |= cpusetCategory;
+                mCpusetCategoriesByCpus.append(cpuCores.get(j), categories);
+            }
+        }
+    }
+
+    private void readMaxCpuFrequencies() {
+        for (int i = 0; i < mCpuFreqPolicyDirs.length; i++) {
+            File policyDir = mCpuFreqPolicyDirs[i];
+            long maxCpuFreqKHz = readMaxCpuFrequency(policyDir);
+            if (maxCpuFreqKHz == 0) {
+                Slogf.w(TAG, "Invalid max CPU frequency read from %s", policyDir.getAbsolutePath());
+                continue;
+            }
+            File cpuCoresFile = new File(policyDir, RELATED_CPUS_FILE);
+            List<Integer> cpuCores = readCpuCores(cpuCoresFile);
+            if (cpuCores.isEmpty()) {
+                Slogf.e(TAG, "Failed to read CPU cores from %s", cpuCoresFile.getAbsolutePath());
+                continue;
+            }
+            for (int j = 0; j < cpuCores.size(); j++) {
+                mMaxCpuFrequenciesByCpus.append(cpuCores.get(j), maxCpuFreqKHz);
+            }
+        }
+    }
+
+    private long readMaxCpuFrequency(File policyDir) {
+        long curCpuFreqKHz = readCpuFreqKHz(new File(policyDir, MAX_CPUFREQ_FILE));
+        return curCpuFreqKHz > 0 ? curCpuFreqKHz
+                : readCpuFreqKHz(new File(policyDir, MAX_SCALING_FREQ_FILE));
+    }
+
+    private static long readCpuFreqKHz(File file) {
+        if (!file.exists()) {
+            Slogf.e(TAG, "CPU frequency file %s doesn't exist", file.getAbsolutePath());
+            return 0;
+        }
+        try {
+            List<String> lines = Files.readAllLines(file.toPath());
+            if (!lines.isEmpty()) {
+                long frequency = Long.parseLong(lines.get(0).trim());
+                return frequency > 0 ? frequency : 0;
+            }
+        } catch (Exception e) {
+            Slogf.e(TAG, e, "Failed to read integer content from file: %s", file.getAbsolutePath());
+        }
+        return 0;
+    }
+
+    /**
+     * Reads the list of CPU cores from the given file.
+     *
+     * Reads CPU cores represented in one of the below formats.
+     * <ul>
+     * <li> Single core id. Eg: 1
+     * <li> Core id range. Eg: 1-4
+     * <li> Comma separated values. Eg: 1, 3-5, 7
+     * </ul>
+     */
+    private static List<Integer> readCpuCores(File file) {
+        if (!file.exists()) {
+            Slogf.e(TAG, "Failed to read CPU cores as the file '%s' doesn't exist",
+                    file.getAbsolutePath());
+            return Collections.emptyList();
+        }
+        try {
+            List<String> lines = Files.readAllLines(file.toPath());
+            List<Integer> cpuCores = new ArrayList<>();
+            for (int i = 0; i < lines.size(); i++) {
+                String[] pairs = lines.get(i).trim().split(",");
+                for (int j = 0; j < pairs.length; j++) {
+                    String[] minMaxPairs = pairs[j].split("-");
+                    if (minMaxPairs.length >= 2) {
+                        int min = Integer.parseInt(minMaxPairs[0]);
+                        int max = Integer.parseInt(minMaxPairs[1]);
+                        if (min > max) {
+                            continue;
+                        }
+                        for (int id = min; id <= max; id++) {
+                            cpuCores.add(id);
+                        }
+                    } else if (minMaxPairs.length == 1) {
+                        cpuCores.add(Integer.parseInt(minMaxPairs[0]));
+                    } else {
+                        Slogf.w(TAG, "Invalid CPU core range format %s", pairs[j]);
+                    }
+                }
+            }
+            return cpuCores;
+        } catch (Exception e) {
+            Slogf.e(TAG, e, "Failed to read CPU cores from %s", file.getAbsolutePath());
+        }
+        return Collections.emptyList();
+    }
+
+    @Nullable
+    private SparseArray<CpuUsageStats> readLatestCpuUsageStats() {
+        SparseArray<CpuUsageStats> cumulativeCpuUsageStats = readCumulativeCpuUsageStats();
+        if (cumulativeCpuUsageStats.size() == 0) {
+            Slogf.e(TAG, "Failed to read cumulative CPU usage stats");
+            return null;
+        }
+        SparseArray<CpuUsageStats> deltaCpuUsageStats = new SparseArray();
+        for (int i = 0; i < cumulativeCpuUsageStats.size(); i++) {
+            int cpu = cumulativeCpuUsageStats.keyAt(i);
+            CpuUsageStats newStats = cumulativeCpuUsageStats.valueAt(i);
+            CpuUsageStats oldStats = mCumulativeCpuUsageStats.get(cpu);
+            deltaCpuUsageStats.append(cpu, oldStats == null ? newStats : newStats.delta(oldStats));
+        }
+        mCumulativeCpuUsageStats = cumulativeCpuUsageStats;
+        return deltaCpuUsageStats;
+    }
+
+    private SparseArray<CpuUsageStats> readCumulativeCpuUsageStats() {
+        SparseArray<CpuUsageStats> cpuUsageStats = new SparseArray<>();
+        try {
+            List<String> lines = Files.readAllLines(mProcStatFile.toPath());
+            for (int i = 0; i < lines.size(); i++) {
+                Matcher m = PROC_STAT_PATTERN.matcher(lines.get(i).trim());
+                if (!m.find()) {
+                    continue;
+                }
+                cpuUsageStats.append(Integer.parseInt(Objects.requireNonNull(m.group("core"))),
+                        new CpuUsageStats(jiffyStrToMillis(m.group("userClockTicks")),
+                                jiffyStrToMillis(m.group("niceClockTicks")),
+                                jiffyStrToMillis(m.group("sysClockTicks")),
+                                jiffyStrToMillis(m.group("idleClockTicks")),
+                                jiffyStrToMillis(m.group("iowaitClockTicks")),
+                                jiffyStrToMillis(m.group("irqClockTicks")),
+                                jiffyStrToMillis(m.group("softirqClockTicks")),
+                                jiffyStrToMillis(m.group("stealClockTicks")),
+                                jiffyStrToMillis(m.group("guestClockTicks")),
+                                jiffyStrToMillis(m.group("guestNiceClockTicks"))));
+            }
+        } catch (Exception e) {
+            Slogf.e(TAG, e, "Failed to read cpu usage stats from %s",
+                    mProcStatFile.getAbsolutePath());
+        }
+        return cpuUsageStats;
+    }
+
+    private static long jiffyStrToMillis(String jiffyStr) {
+        return Long.parseLong(Objects.requireNonNull(jiffyStr)) * MILLIS_PER_JIFFY;
+    }
+
+    /** Contains information for each CPU core on the system. */
+    public static final class CpuInfo {
+        public final int cpuCore;
+        public final @CpusetCategory int cpusetCategories;
+        public final long curCpuFreqKHz;
+        public final long maxCpuFreqKHz;
+        public final CpuUsageStats latestCpuUsageStats;
+
+        CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, long curCpuFreqKHz,
+                long maxCpuFreqKHz, CpuUsageStats latestCpuUsageStats) {
+            this.cpuCore = cpuCore;
+            this.cpusetCategories = cpusetCategories;
+            this.curCpuFreqKHz = curCpuFreqKHz;
+            this.maxCpuFreqKHz = maxCpuFreqKHz;
+            this.latestCpuUsageStats = latestCpuUsageStats;
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder("CpuInfo{ cpuCore = ").append(cpuCore)
+                    .append(", cpusetCategories = ").append(cpusetCategories)
+                    .append(", curCpuFreqKHz = ").append(curCpuFreqKHz)
+                    .append(", maxCpuFreqKHz = ").append(maxCpuFreqKHz)
+                    .append(", latestCpuUsageStats = ").append(latestCpuUsageStats)
+                    .append(" }").toString();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof CpuInfo)) {
+                return false;
+            }
+            CpuInfo other = (CpuInfo) obj;
+            return cpuCore == other.cpuCore && cpusetCategories == other.cpusetCategories
+                    && curCpuFreqKHz == other.curCpuFreqKHz
+                    && maxCpuFreqKHz == other.maxCpuFreqKHz
+                    && latestCpuUsageStats.equals(other.latestCpuUsageStats);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(cpuCore, cpusetCategories, curCpuFreqKHz, maxCpuFreqKHz,
+                    latestCpuUsageStats);
+        }
+    }
+
+    /** CPU time spent in different modes. */
+    public static final class CpuUsageStats {
+        public final long userTimeMillis;
+        public final long niceTimeMillis;
+        public final long systemTimeMillis;
+        public final long idleTimeMillis;
+        public final long iowaitTimeMillis;
+        public final long irqTimeMillis;
+        public final long softirqTimeMillis;
+        public final long stealTimeMillis;
+        public final long guestTimeMillis;
+        public final long guestNiceTimeMillis;
+
+        public CpuUsageStats(long userTimeMillis, long niceTimeMillis, long systemTimeMillis,
+                long idleTimeMillis, long iowaitTimeMillis, long irqTimeMillis,
+                long softirqTimeMillis, long stealTimeMillis, long guestTimeMillis,
+                long guestNiceTimeMillis) {
+            this.userTimeMillis = userTimeMillis;
+            this.niceTimeMillis = niceTimeMillis;
+            this.systemTimeMillis = systemTimeMillis;
+            this.idleTimeMillis = idleTimeMillis;
+            this.iowaitTimeMillis = iowaitTimeMillis;
+            this.irqTimeMillis = irqTimeMillis;
+            this.softirqTimeMillis = softirqTimeMillis;
+            this.stealTimeMillis = stealTimeMillis;
+            this.guestTimeMillis = guestTimeMillis;
+            this.guestNiceTimeMillis = guestNiceTimeMillis;
+        }
+
+        public long getTotalTime() {
+            return userTimeMillis + niceTimeMillis + systemTimeMillis + idleTimeMillis
+                    + iowaitTimeMillis + irqTimeMillis + softirqTimeMillis + stealTimeMillis
+                    + guestTimeMillis + guestNiceTimeMillis;
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder("CpuUsageStats{ userTimeMillis = ")
+                    .append(userTimeMillis)
+                    .append(", niceTimeMillis = ").append(niceTimeMillis)
+                    .append(", systemTimeMillis = ").append(systemTimeMillis)
+                    .append(", idleTimeMillis = ").append(idleTimeMillis)
+                    .append(", iowaitTimeMillis = ").append(iowaitTimeMillis)
+                    .append(", irqTimeMillis = ").append(irqTimeMillis)
+                    .append(", softirqTimeMillis = ").append(softirqTimeMillis)
+                    .append(", stealTimeMillis = ").append(stealTimeMillis)
+                    .append(", guestTimeMillis = ").append(guestTimeMillis)
+                    .append(", guestNiceTimeMillis = ").append(guestNiceTimeMillis)
+                    .append(" }").toString();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof CpuUsageStats)) {
+                return false;
+            }
+            CpuUsageStats other = (CpuUsageStats) obj;
+            return userTimeMillis == other.userTimeMillis && niceTimeMillis == other.niceTimeMillis
+                    && systemTimeMillis == other.systemTimeMillis
+                    && idleTimeMillis == other.idleTimeMillis
+                    && iowaitTimeMillis == other.iowaitTimeMillis
+                    && irqTimeMillis == other.irqTimeMillis
+                    && softirqTimeMillis == other.softirqTimeMillis
+                    && stealTimeMillis == other.stealTimeMillis
+                    && guestTimeMillis == other.guestTimeMillis
+                    && guestNiceTimeMillis == other.guestNiceTimeMillis;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(userTimeMillis, niceTimeMillis, systemTimeMillis, idleTimeMillis,
+                    iowaitTimeMillis, irqTimeMillis, softirqTimeMillis, stealTimeMillis,
+                    guestTimeMillis,
+                    guestNiceTimeMillis);
+        }
+
+        CpuUsageStats delta(CpuUsageStats rhs) {
+            return new CpuUsageStats(diff(userTimeMillis, rhs.userTimeMillis),
+                    diff(niceTimeMillis, rhs.niceTimeMillis),
+                    diff(systemTimeMillis, rhs.systemTimeMillis),
+                    diff(idleTimeMillis, rhs.idleTimeMillis),
+                    diff(iowaitTimeMillis, rhs.iowaitTimeMillis),
+                    diff(irqTimeMillis, rhs.irqTimeMillis),
+                    diff(softirqTimeMillis, rhs.softirqTimeMillis),
+                    diff(stealTimeMillis, rhs.stealTimeMillis),
+                    diff(guestTimeMillis, rhs.guestTimeMillis),
+                    diff(guestNiceTimeMillis, rhs.guestNiceTimeMillis));
+        }
+
+        private static long diff(long lhs, long rhs) {
+            return lhs > rhs ? lhs - rhs : 0;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorInternal.java b/services/core/java/com/android/server/cpu/CpuMonitorInternal.java
new file mode 100644
index 0000000..849a20b
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/CpuMonitorInternal.java
@@ -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.server.cpu;
+
+import android.annotation.CallbackExecutor;
+
+import java.util.concurrent.Executor;
+
+/** CpuMonitorInternal hosts internal APIs to monitor CPU. */
+public abstract class CpuMonitorInternal {
+    /** Callback to get CPU availability change notifications. */
+    public interface CpuAvailabilityCallback {
+        /**
+         * Called when the CPU availability crosses the provided thresholds.
+         *
+         * <p>Called when the latest or past N-second (which will be specified in the
+         * {@link CpuAvailabilityInfo}) average CPU availability percent has crossed
+         * (either goes above or drop below) the {@link CpuAvailabilityMonitoringConfig#thresholds}
+         * since the last notification. Also called when a callback is added to the service.
+         *
+         * <p>The callback is called at the executor which is specified in
+         * {@link addCpuAvailabilityCallback} or at the service handler thread.
+         *
+         * @param info CPU availability information.
+         */
+        void onAvailabilityChanged(CpuAvailabilityInfo info);
+
+        /**
+         * Called when the CPU monitoring interval changes.
+         *
+         * <p>Also called when a callback is added to the service.
+         *
+         * @param intervalMilliseconds CPU monitoring interval in milliseconds.
+         */
+        void onMonitoringIntervalChanged(long intervalMilliseconds);
+    }
+
+    /**
+     * Adds the {@link CpuAvailabilityCallback} for the caller.
+     *
+     * <p>When the callback is added, the callback will be called to notify the current CPU
+     * availability and monitoring interval.
+     *
+     * <p>When the client needs to update the {@link config} for a previously added callback,
+     * the client has to remove the callback and add the callback with a new {@link config}.
+     *
+     * @param executor Executor to execute the callback. If an executor is not provided,
+     *                 the callback will be executed on the service handler thread.
+     * @param config CPU availability monitoring config.
+     * @param callback Callback implementing {@link CpuAvailabilityCallback}
+     * interface.
+     *
+     * @throws IllegalStateException if {@code callback} is already added.
+     */
+    public abstract void addCpuAvailabilityCallback(@CallbackExecutor Executor executor,
+            CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback);
+
+    /**
+     * Removes the {@link CpuAvailabilityCallback} for the caller.
+     *
+     * @param callback Callback implementing {@link CpuAvailabilityCallback}
+     * interface.
+     *
+     * @throws IllegalArgumentException if {@code callback} is not previously added.
+     */
+    public abstract void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback);
+}
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java
new file mode 100644
index 0000000..b0dfb84
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java
@@ -0,0 +1,173 @@
+/*
+ * 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.cpu;
+
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+
+import android.content.Context;
+import android.os.Binder;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.DumpUtils;
+import com.android.server.SystemService;
+import com.android.server.utils.PriorityDump;
+import com.android.server.utils.Slogf;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/** Service to monitor CPU availability and usage. */
+public final class CpuMonitorService extends SystemService {
+    static final String TAG = CpuMonitorService.class.getSimpleName();
+    static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG);
+    // TODO(b/242722241): Make this a resource overlay property.
+    //  Maintain 3 monitoring intervals:
+    //  * One to poll very frequently when mCpuAvailabilityCallbackInfoByCallbacks are available and
+    //    CPU availability is above a threshold (such as at least 10% of CPU is available).
+    //  * One to poll less frequently when mCpuAvailabilityCallbackInfoByCallbacks are available
+    //    and CPU availability is below a threshold (such as less than 10% of CPU is available).
+    //  * One to poll very less frequently when no callbacks are available and the build is either
+    //    user-debug or eng. This will be useful for debugging in development environment.
+    static final int DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS = 5_000;
+
+    private final Context mContext;
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final ArrayMap<CpuMonitorInternal.CpuAvailabilityCallback, CpuAvailabilityCallbackInfo>
+            mCpuAvailabilityCallbackInfoByCallbacks = new ArrayMap<>();
+    @GuardedBy("mLock")
+    private long mMonitoringIntervalMilliseconds = DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS;
+
+    private final CpuMonitorInternal mLocalService = new CpuMonitorInternal() {
+        @Override
+        public void addCpuAvailabilityCallback(Executor executor,
+                CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback) {
+            Objects.requireNonNull(callback, "Callback must be non-null");
+            Objects.requireNonNull(config, "Config must be non-null");
+            synchronized (mLock) {
+                if (mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) {
+                    Slogf.i(TAG, "Overwriting the existing CpuAvailabilityCallback %s",
+                            mCpuAvailabilityCallbackInfoByCallbacks.get(callback));
+                    // TODO(b/242722241): Overwrite any internal cache (will be added in future CLs)
+                    //  that maps callbacks based on the CPU availability thresholds.
+                }
+                CpuAvailabilityCallbackInfo info = new CpuAvailabilityCallbackInfo(config,
+                        executor);
+                mCpuAvailabilityCallbackInfoByCallbacks.put(callback, info);
+                if (DEBUG) {
+                    Slogf.d(TAG, "Added a CPU availability callback: %s", info);
+                }
+            }
+            // TODO(b/242722241):
+            //  * On the executor or on the handler thread, call the callback with the latest CPU
+            //    availability info and monitoring interval.
+            //  * Monitor the CPU stats more frequently when the first callback is added.
+        }
+
+        @Override
+        public void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback) {
+            synchronized (mLock) {
+                if (!mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) {
+                    Slogf.i(TAG, "CpuAvailabilityCallback was not previously added."
+                            + " Ignoring the remove request");
+                    return;
+                }
+                CpuAvailabilityCallbackInfo info =
+                        mCpuAvailabilityCallbackInfoByCallbacks.remove(callback);
+                if (DEBUG) {
+                    Slogf.d(TAG, "Removed a CPU availability callback: %s", info);
+                }
+            }
+            // TODO(b/242722241): Increase CPU monitoring interval when all callbacks are removed.
+        }
+    };
+
+    public CpuMonitorService(Context context) {
+        super(context);
+        mContext = context;
+    }
+
+    @Override
+    public void onStart() {
+        publishLocalService(CpuMonitorInternal.class, mLocalService);
+        publishBinderService("cpu_monitor", new CpuMonitorBinder(), /* allowIsolated= */ false,
+                DUMP_FLAG_PRIORITY_CRITICAL);
+    }
+
+    private void doDump(IndentingPrintWriter writer) {
+        writer.printf("*%s*\n", getClass().getSimpleName());
+        writer.increaseIndent();
+        synchronized (mLock) {
+            writer.printf("CPU monitoring interval: %d ms\n", mMonitoringIntervalMilliseconds);
+            if (!mCpuAvailabilityCallbackInfoByCallbacks.isEmpty()) {
+                writer.println("CPU availability change callbacks:");
+                writer.increaseIndent();
+                for (int i = 0; i < mCpuAvailabilityCallbackInfoByCallbacks.size(); i++) {
+                    writer.printf("%s: %s\n", mCpuAvailabilityCallbackInfoByCallbacks.keyAt(i),
+                            mCpuAvailabilityCallbackInfoByCallbacks.valueAt(i));
+                }
+                writer.decreaseIndent();
+            }
+        }
+        // TODO(b/242722241): Print the recent past CPU stats.
+        writer.decreaseIndent();
+    }
+
+    private static final class CpuAvailabilityCallbackInfo {
+        public final CpuAvailabilityMonitoringConfig config;
+        public final Executor executor;
+
+        CpuAvailabilityCallbackInfo(CpuAvailabilityMonitoringConfig config,
+                Executor executor) {
+            this.config = config;
+            this.executor = executor;
+        }
+
+        @Override
+        public String toString() {
+            return "CpuAvailabilityCallbackInfo{" + "config=" + config + ", mExecutor=" + executor
+                    + '}';
+        }
+    }
+
+    private final class CpuMonitorBinder extends Binder {
+        private final PriorityDump.PriorityDumper mPriorityDumper =
+                new PriorityDump.PriorityDumper() {
+                    @Override
+                    public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+                            boolean asProto) {
+                        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)
+                                || asProto) {
+                            return;
+                        }
+                        try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw)) {
+                            doDump(ipw);
+                        }
+                    }
+                };
+
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            PriorityDump.dump(mPriorityDumper, fd, pw, args);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/cpu/OWNERS b/services/core/java/com/android/server/cpu/OWNERS
new file mode 100644
index 0000000..2f42363
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 608533
+
+include platform/packages/services/Car:/OWNERS
+lakshmana@google.com
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index f8d4b8f..42fe9d8 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -18,11 +18,11 @@
 
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
-import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.hardware.devicestate.DeviceStateManager;
 
 import com.android.internal.util.Preconditions;
 
@@ -55,6 +55,15 @@
      */
     public static final int FLAG_APP_INACCESSIBLE = 1 << 1;
 
+    /**
+     * Some device states can be both entered through a physical configuration as well as emulation
+     * through {@link DeviceStateManager#requestState}, while some states can only be entered
+     * through emulation and have no physical configuration to match.
+     *
+     * This flag indicates that the corresponding state can only be entered through emulation.
+     */
+    public static final int FLAG_EMULATED_ONLY = 1 << 2;
+
     /** @hide */
     @IntDef(prefix = {"FLAG_"}, flag = true, value = {
             FLAG_CANCEL_OVERRIDE_REQUESTS,
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 44c8e18..c856cab 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -17,6 +17,8 @@
 package com.android.server.devicestate;
 
 import static android.Manifest.permission.CONTROL_DEVICE_STATE;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
 
@@ -56,6 +58,7 @@
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowProcessController;
 
@@ -157,6 +160,15 @@
 
     private Set<Integer> mDeviceStatesAvailableForAppRequests;
 
+    private Set<Integer> mFoldedDeviceStates;
+
+    @Nullable
+    private DeviceState mRearDisplayState;
+
+    // TODO(259328837) Generalize for all pending feature requests in the future
+    @Nullable
+    private OverrideRequest mRearDisplayPendingOverrideRequest;
+
     @VisibleForTesting
     interface SystemPropertySetter {
         void setDebugTracingDeviceStateProperty(String value);
@@ -201,6 +213,7 @@
 
         synchronized (mLock) {
             readStatesAvailableForRequestFromApps();
+            mFoldedDeviceStates = readFoldedStates();
         }
     }
 
@@ -350,6 +363,8 @@
             mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers);
             updatePendingStateLocked();
 
+            setRearDisplayStateLocked();
+
             if (!mPendingState.isPresent()) {
                 // If the change in the supported states didn't result in a change of the pending
                 // state commitPendingState() will never be called and the callbacks will never be
@@ -361,6 +376,15 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void setRearDisplayStateLocked() {
+        int rearDisplayIdentifier = getContext().getResources().getInteger(
+                R.integer.config_deviceStateRearDisplay);
+        if (rearDisplayIdentifier != INVALID_DEVICE_STATE) {
+            mRearDisplayState = mDeviceStates.get(rearDisplayIdentifier);
+        }
+    }
+
     /**
      * Returns {@code true} if the provided state is supported. Requires that
      * {@link #mDeviceStates} is sorted prior to calling.
@@ -398,6 +422,10 @@
                 // Base state hasn't changed. Nothing to do.
                 return;
             }
+            // There is a pending rear display request, so we check if the overlay should be closed
+            if (mRearDisplayPendingOverrideRequest != null) {
+                handleRearDisplayBaseStateChangedLocked(identifier);
+            }
             mBaseState = Optional.of(baseState);
 
             if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
@@ -663,7 +691,7 @@
     }
 
     private void requestStateInternal(int state, int flags, int callingPid,
-            @NonNull IBinder token) {
+            @NonNull IBinder token, boolean hasControlDeviceStatePermission) {
         synchronized (mLock) {
             final ProcessRecord processRecord = mProcessRecords.get(callingPid);
             if (processRecord == null) {
@@ -685,7 +713,30 @@
 
             OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
                     OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
-            mOverrideRequestController.addRequest(request);
+
+            // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
+            if (!hasControlDeviceStatePermission && mRearDisplayState != null
+                    && state == mRearDisplayState.getIdentifier()) {
+                showRearDisplayEducationalOverlayLocked(request);
+            } else {
+                mOverrideRequestController.addRequest(request);
+            }
+        }
+    }
+
+    /**
+     * If we get a request to enter rear display  mode, we need to display an educational
+     * overlay to let the user know what will happen. This calls into the
+     * {@link StatusBarManagerInternal} to notify SystemUI to display the educational dialog.
+     */
+    @GuardedBy("mLock")
+    private void showRearDisplayEducationalOverlayLocked(OverrideRequest request) {
+        mRearDisplayPendingOverrideRequest = request;
+
+        StatusBarManagerInternal statusBar =
+                LocalServices.getService(StatusBarManagerInternal.class);
+        if (statusBar != null) {
+            statusBar.showRearDisplayDialog(mBaseState.get().getIdentifier());
         }
     }
 
@@ -738,6 +789,27 @@
         }
     }
 
+    /**
+     * Adds the rear display state request to the {@link OverrideRequestController} if the
+     * educational overlay was closed in a way that should enable the feature, and cancels the
+     * request if it was dismissed in a way that should cancel the feature.
+     */
+    private void onStateRequestOverlayDismissedInternal(boolean shouldCancelRequest) {
+        if (mRearDisplayPendingOverrideRequest != null) {
+            synchronized (mLock) {
+                if (shouldCancelRequest) {
+                    ProcessRecord processRecord = mProcessRecords.get(
+                            mRearDisplayPendingOverrideRequest.getPid());
+                    processRecord.notifyRequestCanceledAsync(
+                            mRearDisplayPendingOverrideRequest.getToken());
+                } else {
+                    mOverrideRequestController.addRequest(mRearDisplayPendingOverrideRequest);
+                }
+                mRearDisplayPendingOverrideRequest = null;
+            }
+        }
+    }
+
     private void dumpInternal(PrintWriter pw) {
         pw.println("DEVICE STATE MANAGER (dumpsys device_state)");
 
@@ -823,6 +895,16 @@
         }
     }
 
+    private Set<Integer> readFoldedStates() {
+        Set<Integer> foldedStates = new HashSet();
+        int[] mFoldedStatesArray = getContext().getResources().getIntArray(
+                com.android.internal.R.array.config_foldedDeviceStates);
+        for (int i = 0; i < mFoldedStatesArray.length; i++) {
+            foldedStates.add(mFoldedStatesArray[i]);
+        }
+        return foldedStates;
+    }
+
     @GuardedBy("mLock")
     private boolean isValidState(int state) {
         for (int i = 0; i < mDeviceStates.size(); i++) {
@@ -833,6 +915,28 @@
         return false;
     }
 
+    /**
+     * If the device is being opened, in response to the rear display educational overlay, we should
+     * dismiss the overlay and enter the mode.
+     */
+    @GuardedBy("mLock")
+    private void handleRearDisplayBaseStateChangedLocked(int newBaseState) {
+        if (isDeviceOpeningLocked(newBaseState)) {
+            onStateRequestOverlayDismissedInternal(false);
+        }
+    }
+
+    /**
+     * Determines if the device is being opened and if we are going from a folded state to a
+     * non-folded state.
+     */
+    @GuardedBy("mLock")
+    private boolean isDeviceOpeningLocked(int newBaseState) {
+        return mBaseState.filter(
+                deviceState -> mFoldedDeviceStates.contains(deviceState.getIdentifier())
+                        && !mFoldedDeviceStates.contains(newBaseState)).isPresent();
+    }
+
     private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
         @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState;
 
@@ -850,6 +954,7 @@
             if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) {
                 throw new IllegalArgumentException("Invalid identifier: " + identifier);
             }
+
             mCurrentBaseState = identifier;
             setBaseState(identifier);
         }
@@ -977,9 +1082,12 @@
                 throw new IllegalArgumentException("Request token must not be null.");
             }
 
+            boolean hasControlStatePermission = getContext().checkCallingOrSelfPermission(
+                    CONTROL_DEVICE_STATE) == PERMISSION_GRANTED;
+
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
-                requestStateInternal(state, flags, callingPid, token);
+                requestStateInternal(state, flags, callingPid, token, hasControlStatePermission);
             } finally {
                 Binder.restoreCallingIdentity(callingIdentity);
             }
@@ -1034,6 +1142,21 @@
         }
 
         @Override // Binder call
+        public void onStateRequestOverlayDismissed(boolean shouldCancelRequest) {
+
+            getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+                    "CONTROL_DEVICE_STATE permission required to control the state request "
+                            + "overlay");
+
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                onStateRequestOverlayDismissedInternal(shouldCancelRequest);
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @Override // Binder call
         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                 String[] args, ShellCallback callback, ResultReceiver result) {
             new DeviceStateManagerShellCommand(DeviceStateManagerService.this)
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 7b60421..197c64e 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -483,8 +483,7 @@
 
     private static boolean isInteractivePolicy(int policy) {
         return policy == DisplayPowerRequest.POLICY_BRIGHT
-                || policy == DisplayPowerRequest.POLICY_DIM
-                || policy == DisplayPowerRequest.POLICY_VR;
+                || policy == DisplayPowerRequest.POLICY_DIM;
     }
 
     private boolean setScreenBrightnessByUser(float brightness) {
@@ -603,6 +602,14 @@
         mAmbientBrightnessThresholdsIdle.dump(pw);
     }
 
+    public float[] getLastSensorValues() {
+        return mAmbientLightRingBuffer.getAllLuxValues();
+    }
+
+    public long[] getLastSensorTimestamps() {
+        return mAmbientLightRingBuffer.getAllTimestamps();
+    }
+
     private String configStateToString(int state) {
         switch (state) {
         case AUTO_BRIGHTNESS_ENABLED:
@@ -1232,10 +1239,42 @@
             return mRingLux[offsetOf(index)];
         }
 
+        public float[] getAllLuxValues() {
+            float[] values = new float[mCount];
+            if (mCount == 0) {
+                return values;
+            }
+
+            if (mStart < mEnd) {
+                System.arraycopy(mRingLux, mStart, values, 0, mCount);
+            } else {
+                System.arraycopy(mRingLux, mStart, values, 0, mCapacity - mStart);
+                System.arraycopy(mRingLux, 0, values, mCapacity - mStart, mEnd);
+            }
+
+            return values;
+        }
+
         public long getTime(int index) {
             return mRingTime[offsetOf(index)];
         }
 
+        public long[] getAllTimestamps() {
+            long[] values = new long[mCount];
+            if (mCount == 0) {
+                return values;
+            }
+
+            if (mStart < mEnd) {
+                System.arraycopy(mRingTime, mStart, values, 0, mCount);
+            } else {
+                System.arraycopy(mRingTime, mStart, values, 0, mCapacity - mStart);
+                System.arraycopy(mRingTime, 0, values, mCapacity - mStart, mEnd);
+            }
+
+            return values;
+        }
+
         public void push(long time, float lux) {
             int next = mEnd;
             if (mCount == mCapacity) {
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index e9856d0..6e1640d 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -79,10 +79,8 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Date;
-import java.util.Deque;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
@@ -101,8 +99,6 @@
     private static final int MAX_EVENTS = 100;
     // Discard events when reading or writing that are older than this.
     private static final long MAX_EVENT_AGE = TimeUnit.DAYS.toMillis(30);
-    // Time over which we keep lux sensor readings.
-    private static final long LUX_EVENT_HORIZON = TimeUnit.SECONDS.toNanos(10);
 
     private static final String TAG_EVENTS = "events";
     private static final String TAG_EVENT = "event";
@@ -174,8 +170,6 @@
     // Lock held while collecting data related to brightness changes.
     private final Object mDataCollectionLock = new Object();
     @GuardedBy("mDataCollectionLock")
-    private Deque<LightData> mLastSensorReadings = new ArrayDeque<>();
-    @GuardedBy("mDataCollectionLock")
     private float mLastBatteryLevel = Float.NaN;
     @GuardedBy("mDataCollectionLock")
     private float mLastBrightness = -1;
@@ -327,7 +321,8 @@
      */
     public void notifyBrightnessChanged(float brightness, boolean userInitiated,
             float powerBrightnessFactor, boolean isUserSetBrightness,
-            boolean isDefaultBrightnessConfig, String uniqueDisplayId) {
+            boolean isDefaultBrightnessConfig, String uniqueDisplayId, float[] luxValues,
+            long[] luxTimestamps) {
         if (DEBUG) {
             Slog.d(TAG, String.format("notifyBrightnessChanged(brightness=%f, userInitiated=%b)",
                         brightness, userInitiated));
@@ -335,7 +330,7 @@
         Message m = mBgHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED,
                 userInitiated ? 1 : 0, 0 /*unused*/, new BrightnessChangeValues(brightness,
                         powerBrightnessFactor, isUserSetBrightness, isDefaultBrightnessConfig,
-                        mInjector.currentTimeMillis(), uniqueDisplayId));
+                        mInjector.currentTimeMillis(), uniqueDisplayId, luxValues, luxTimestamps));
         m.sendToTarget();
     }
 
@@ -349,7 +344,8 @@
 
     private void handleBrightnessChanged(float brightness, boolean userInitiated,
             float powerBrightnessFactor, boolean isUserSetBrightness,
-            boolean isDefaultBrightnessConfig, long timestamp, String uniqueDisplayId) {
+            boolean isDefaultBrightnessConfig, long timestamp, String uniqueDisplayId,
+            float[] luxValues, long[] luxTimestamps) {
         BrightnessChangeEvent.Builder builder;
 
         synchronized (mDataCollectionLock) {
@@ -376,28 +372,22 @@
             builder.setIsDefaultBrightnessConfig(isDefaultBrightnessConfig);
             builder.setUniqueDisplayId(uniqueDisplayId);
 
-            final int readingCount = mLastSensorReadings.size();
-            if (readingCount == 0) {
+            if (luxValues.length == 0) {
                 // No sensor data so ignore this.
                 return;
             }
 
-            float[] luxValues = new float[readingCount];
-            long[] luxTimestamps = new long[readingCount];
+            long[] luxTimestampsMillis = new long[luxTimestamps.length];
 
-            int pos = 0;
-
-            // Convert sensor timestamp in elapsed time nanos to current time millis.
+            // Convert lux timestamp in elapsed time to current time.
             long currentTimeMillis = mInjector.currentTimeMillis();
             long elapsedTimeNanos = mInjector.elapsedRealtimeNanos();
-            for (LightData reading : mLastSensorReadings) {
-                luxValues[pos] = reading.lux;
-                luxTimestamps[pos] = currentTimeMillis -
-                        TimeUnit.NANOSECONDS.toMillis(elapsedTimeNanos - reading.timestamp);
-                ++pos;
+            for (int i = 0; i < luxTimestamps.length; i++) {
+                luxTimestampsMillis[i] = currentTimeMillis - (TimeUnit.NANOSECONDS.toMillis(
+                        elapsedTimeNanos) - luxTimestamps[i]);
             }
             builder.setLuxValues(luxValues);
-            builder.setLuxTimestamps(luxTimestamps);
+            builder.setLuxTimestamps(luxTimestampsMillis);
 
             builder.setBatteryLevel(mLastBatteryLevel);
             builder.setLastBrightness(previousBrightness);
@@ -452,9 +442,6 @@
         if (mLightSensor != lightSensor) {
             mLightSensor = lightSensor;
             stopSensorListener();
-            synchronized (mDataCollectionLock) {
-                mLastSensorReadings.clear();
-            }
             // Attempt to restart the sensor listener. It will check to see if it should be running
             // so there is no need to also check here.
             startSensorListener();
@@ -798,12 +785,6 @@
             pw.println("  mLightSensor=" + mLightSensor);
             pw.println("  mLastBatteryLevel=" + mLastBatteryLevel);
             pw.println("  mLastBrightness=" + mLastBrightness);
-            pw.println("  mLastSensorReadings.size=" + mLastSensorReadings.size());
-            if (!mLastSensorReadings.isEmpty()) {
-                pw.println("  mLastSensorReadings time span "
-                        + mLastSensorReadings.peekFirst().timestamp + "->"
-                        + mLastSensorReadings.peekLast().timestamp);
-            }
         }
         synchronized (mEventsLock) {
             pw.println("  mEventsDirty=" + mEventsDirty);
@@ -919,43 +900,6 @@
         return ParceledListSlice.emptyList();
     }
 
-    // Not allowed to keep the SensorEvent so used to copy the data we care about.
-    private static class LightData {
-        public float lux;
-        // Time in elapsedRealtimeNanos
-        public long timestamp;
-    }
-
-    private void recordSensorEvent(SensorEvent event) {
-        long horizon = mInjector.elapsedRealtimeNanos() - LUX_EVENT_HORIZON;
-        synchronized (mDataCollectionLock) {
-            if (DEBUG) {
-                Slog.v(TAG, "Sensor event " + event);
-            }
-            if (!mLastSensorReadings.isEmpty()
-                    && event.timestamp < mLastSensorReadings.getLast().timestamp) {
-                // Ignore event that came out of order.
-                return;
-            }
-            LightData data = null;
-            while (!mLastSensorReadings.isEmpty()
-                    && mLastSensorReadings.getFirst().timestamp < horizon) {
-                // Remove data that has fallen out of the window.
-                data = mLastSensorReadings.removeFirst();
-            }
-            // We put back the last one we removed so we know how long
-            // the first sensor reading was valid for.
-            if (data != null) {
-                mLastSensorReadings.addFirst(data);
-            }
-
-            data = new LightData();
-            data.timestamp = event.timestamp;
-            data.lux = event.values[0];
-            mLastSensorReadings.addLast(data);
-        }
-    }
-
     private void recordAmbientBrightnessStats(SensorEvent event) {
         mAmbientBrightnessStatsTracker.add(mCurrentUserId, event.values[0]);
     }
@@ -969,7 +913,6 @@
     private final class SensorListener implements SensorEventListener {
         @Override
         public void onSensorChanged(SensorEvent event) {
-            recordSensorEvent(event);
             recordAmbientBrightnessStats(event);
         }
 
@@ -1056,7 +999,7 @@
                     handleBrightnessChanged(values.brightness, userInitiatedChange,
                             values.powerBrightnessFactor, values.isUserSetBrightness,
                             values.isDefaultBrightnessConfig, values.timestamp,
-                            values.uniqueDisplayId);
+                            values.uniqueDisplayId, values.luxValues, values.luxTimestamps);
                     break;
                 case MSG_START_SENSOR_LISTENER:
                     startSensorListener();
@@ -1092,16 +1035,20 @@
         public final boolean isDefaultBrightnessConfig;
         public final long timestamp;
         public final String uniqueDisplayId;
+        public final float[] luxValues;
+        public final long[] luxTimestamps;
 
         BrightnessChangeValues(float brightness, float powerBrightnessFactor,
                 boolean isUserSetBrightness, boolean isDefaultBrightnessConfig,
-                long timestamp, String uniqueDisplayId) {
+                long timestamp, String uniqueDisplayId, float[] luxValues, long[] luxTimestamps) {
             this.brightness = brightness;
             this.powerBrightnessFactor = powerBrightnessFactor;
             this.isUserSetBrightness = isUserSetBrightness;
             this.isDefaultBrightnessConfig = isDefaultBrightnessConfig;
             this.timestamp = timestamp;
             this.uniqueDisplayId = uniqueDisplayId;
+            this.luxValues = luxValues;
+            this.luxTimestamps = luxTimestamps;
         }
     }
 
@@ -1133,7 +1080,7 @@
 
         public void registerReceiver(Context context,
                 BroadcastReceiver receiver, IntentFilter filter) {
-            context.registerReceiver(receiver, filter);
+            context.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED);
         }
 
         public void unregisterReceiver(Context context,
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 9dd2f84..fc6403d 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display;
 
+import android.annotation.NonNull;
 import android.hardware.devicestate.DeviceStateManager;
 import android.os.Environment;
 import android.util.IndentingPrintWriter;
@@ -23,8 +24,10 @@
 import android.util.SparseArray;
 import android.view.DisplayAddress;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.config.layout.Layouts;
 import com.android.server.display.config.layout.XmlParser;
+import com.android.server.display.layout.DisplayIdProducer;
 import com.android.server.display.layout.Layout;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -48,13 +51,28 @@
 
     public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE;
 
+    // Direction of the display relative to the default display, whilst in this state
+    private static final int POSITION_UNKNOWN = Layout.Display.POSITION_UNKNOWN;
+    private static final int POSITION_FRONT = Layout.Display.POSITION_FRONT;
+    private static final int POSITION_REAR = Layout.Display.POSITION_REAR;
+
+    private static final String FRONT_STRING = "front";
+    private static final String REAR_STRING = "rear";
+
     private static final String CONFIG_FILE_PATH =
             "etc/displayconfig/display_layout_configuration.xml";
 
     private final SparseArray<Layout> mLayoutMap = new SparseArray<>();
+    private final DisplayIdProducer mIdProducer;
 
-    DeviceStateToLayoutMap() {
-        loadLayoutsFromConfig();
+    DeviceStateToLayoutMap(DisplayIdProducer idProducer) {
+        this(idProducer, Environment.buildPath(
+                Environment.getVendorDirectory(), CONFIG_FILE_PATH));
+    }
+
+    DeviceStateToLayoutMap(DisplayIdProducer idProducer, File configFile) {
+        mIdProducer = idProducer;
+        loadLayoutsFromConfig(configFile);
         createLayout(STATE_DEFAULT);
     }
 
@@ -76,24 +94,11 @@
         return layout;
     }
 
-    private Layout createLayout(int state) {
-        if (mLayoutMap.contains(state)) {
-            Slog.e(TAG, "Attempted to create a second layout for state " + state);
-            return null;
-        }
-
-        final Layout layout = new Layout();
-        mLayoutMap.append(state, layout);
-        return layout;
-    }
-
     /**
      * Reads display-layout-configuration files to get the layouts to use for this device.
      */
-    private void loadLayoutsFromConfig() {
-        final File configFile = Environment.buildPath(
-                Environment.getVendorDirectory(), CONFIG_FILE_PATH);
-
+    @VisibleForTesting
+    void loadLayoutsFromConfig(@NonNull File configFile) {
         if (!configFile.exists()) {
             return;
         }
@@ -109,10 +114,19 @@
                 final int state = l.getState().intValue();
                 final Layout layout = createLayout(state);
                 for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
-                    layout.createDisplayLocked(
+                    Layout.Display display = layout.createDisplayLocked(
                             DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
                             d.isDefaultDisplay(),
-                            d.isEnabled());
+                            d.isEnabled(),
+                            mIdProducer);
+
+                    if (FRONT_STRING.equals(d.getPosition())) {
+                        display.setPosition(POSITION_FRONT);
+                    } else if (REAR_STRING.equals(d.getPosition())) {
+                        display.setPosition(POSITION_REAR);
+                    } else {
+                        display.setPosition(POSITION_UNKNOWN);
+                    }
                 }
             }
         } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
@@ -120,4 +134,15 @@
                     + configFile, e);
         }
     }
+
+    private Layout createLayout(int state) {
+        if (mLayoutMap.contains(state)) {
+            Slog.e(TAG, "Attempted to create a second layout for state " + state);
+            return null;
+        }
+
+        final Layout layout = new Layout();
+        mLayoutMap.append(state, layout);
+        return layout;
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 1fc15122..4f1df3f 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -120,13 +120,14 @@
     }
 
     public static Display.Mode createMode(int width, int height, float refreshRate) {
-        return createMode(width, height, refreshRate, new float[0]);
+        return createMode(width, height, refreshRate, new float[0], new int[0]);
     }
 
     public static Display.Mode createMode(int width, int height, float refreshRate,
-            float[] alternativeRefreshRates) {
+            float[] alternativeRefreshRates,
+            @Display.HdrCapabilities.HdrType int[] supportedHdrTypes) {
         return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate,
-                alternativeRefreshRates);
+                alternativeRefreshRates, supportedHdrTypes);
     }
 
     public interface Listener {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index a3b1a42..c1e9526 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);
@@ -1942,8 +1941,8 @@
     }
 
     private void setProxSensorUnspecified() {
-        mProximitySensor.name = "";
-        mProximitySensor.type = "";
+        mProximitySensor.name = null;
+        mProximitySensor.type = null;
     }
 
     private void loadProxSensorFromDdc(DisplayConfiguration config) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 84dfe86..fe1d1a6 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -23,6 +23,7 @@
 import android.view.DisplayAddress;
 import android.view.DisplayCutout;
 import android.view.DisplayEventReceiver;
+import android.view.DisplayShape;
 import android.view.RoundedCorners;
 import android.view.Surface;
 
@@ -144,7 +145,7 @@
     /**
      * Flag: Indicates that the display should always be unlocked. Only valid on virtual displays
      * that aren't in the default display group.
-     * @see #FLAG_OWN_DISPLAY_GROUP
+     * @see #FLAG_OWN_DISPLAY_GROUP and #FLAG_DEVICE_DISPLAY_GROUP
      * @hide
      */
     public static final int FLAG_ALWAYS_UNLOCKED = 1 << 15;
@@ -158,6 +159,27 @@
     public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 16;
 
     /**
+     * Flag: Indicates that the display maintains its own focus and touch mode.
+     *
+     * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+     * behavior, but only applies to the specific display instead of system-wide to all displays.
+     *
+     * Note: The display must be trusted in order to have its own focus.
+     *
+     * @see #FLAG_TRUSTED
+     * @hide
+     */
+    public static final int FLAG_OWN_FOCUS = 1 << 17;
+
+    /**
+     * Flag: indicates that the display should not be a part of the default {@link DisplayGroup} and
+     * instead be part of a {@link DisplayGroup} associated with the Virtual Device.
+     *
+     * @hide
+     */
+    public static final int FLAG_DEVICE_DISPLAY_GROUP = 1 << 18;
+
+    /**
      * Touch attachment: Display does not receive touch.
      */
     public static final int TOUCH_NONE = 0;
@@ -223,6 +245,12 @@
     public int modeId;
 
     /**
+     * The render frame rate this display is scheduled at.
+     * @see android.view.DisplayInfo#renderFrameRate for more details.
+     */
+    public float renderFrameRate;
+
+    /**
      * The default mode of the display.
      */
     public int defaultModeId;
@@ -303,6 +331,11 @@
     public RoundedCorners roundedCorners;
 
     /**
+     * The {@link RoundedCorners} if present or {@code null} otherwise.
+     */
+    public DisplayShape displayShape;
+
+    /**
      * The touch attachment, per {@link DisplayViewport#touch}.
      */
     public int touch;
@@ -412,6 +445,7 @@
                 || width != other.width
                 || height != other.height
                 || modeId != other.modeId
+                || renderFrameRate != other.renderFrameRate
                 || defaultModeId != other.defaultModeId
                 || !Arrays.equals(supportedModes, other.supportedModes)
                 || !Arrays.equals(supportedColorModes, other.supportedColorModes)
@@ -438,7 +472,8 @@
                 || !BrightnessSynchronizer.floatEquals(brightnessDefault,
                 other.brightnessDefault)
                 || !Objects.equals(roundedCorners, other.roundedCorners)
-                || installOrientation != other.installOrientation) {
+                || installOrientation != other.installOrientation
+                || !Objects.equals(displayShape, other.displayShape)) {
             diff |= DIFF_OTHER;
         }
         return diff;
@@ -455,6 +490,7 @@
         width = other.width;
         height = other.height;
         modeId = other.modeId;
+        renderFrameRate = other.renderFrameRate;
         defaultModeId = other.defaultModeId;
         supportedModes = other.supportedModes;
         colorMode = other.colorMode;
@@ -484,6 +520,7 @@
         brightnessDefault = other.brightnessDefault;
         roundedCorners = other.roundedCorners;
         installOrientation = other.installOrientation;
+        displayShape = other.displayShape;
     }
 
     // For debugging purposes
@@ -494,6 +531,7 @@
         sb.append(name).append("\": uniqueId=\"").append(uniqueId).append("\", ");
         sb.append(width).append(" x ").append(height);
         sb.append(", modeId ").append(modeId);
+        sb.append(", renderFrameRate ").append(renderFrameRate);
         sb.append(", defaultModeId ").append(defaultModeId);
         sb.append(", supportedModes ").append(Arrays.toString(supportedModes));
         sb.append(", colorMode ").append(colorMode);
@@ -533,6 +571,9 @@
         }
         sb.append(flagsToString(flags));
         sb.append(", installOrientation ").append(installOrientation);
+        if (displayShape != null) {
+            sb.append(", displayShape ").append(displayShape);
+        }
         sb.append("}");
         return sb.toString();
     }
@@ -584,9 +625,30 @@
         if ((flags & FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) {
             msg.append(", FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD");
         }
+        if ((flags & FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) {
+            msg.append(", FLAG_DESTROY_CONTENT_ON_REMOVAL");
+        }
         if ((flags & FLAG_MASK_DISPLAY_CUTOUT) != 0) {
             msg.append(", FLAG_MASK_DISPLAY_CUTOUT");
         }
+        if ((flags & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) {
+            msg.append(", FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS");
+        }
+        if ((flags & FLAG_TRUSTED) != 0) {
+            msg.append(", FLAG_TRUSTED");
+        }
+        if ((flags & FLAG_OWN_DISPLAY_GROUP) != 0) {
+            msg.append(", FLAG_OWN_DISPLAY_GROUP");
+        }
+        if ((flags & FLAG_ALWAYS_UNLOCKED) != 0) {
+            msg.append(", FLAG_ALWAYS_UNLOCKED");
+        }
+        if ((flags & FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
+            msg.append(", FLAG_TOUCH_FEEDBACK_DISABLED");
+        }
+        if ((flags & FLAG_OWN_FOCUS) != 0) {
+            msg.append(", FLAG_OWN_FOCUS");
+        }
         return msg.toString();
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e907ebf..ae84e96 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -25,6 +25,7 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
@@ -35,6 +36,7 @@
 import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL;
+import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.ROOT_UID;
 
 import android.Manifest;
@@ -58,6 +60,7 @@
 import android.database.ContentObserver;
 import android.graphics.ColorSpace;
 import android.graphics.Point;
+import android.hardware.OverlayProperties;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.hardware.devicestate.DeviceStateManager;
@@ -104,7 +107,6 @@
 import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
-import android.sysprop.DisplayProperties;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -411,6 +413,7 @@
     private final Curve mMinimumBrightnessCurve;
     private final Spline mMinimumBrightnessSpline;
     private final ColorSpace mWideColorSpace;
+    private final OverlayProperties mOverlayProperties;
 
     private SensorManager mSensorManager;
     private BrightnessTracker mBrightnessTracker;
@@ -449,8 +452,6 @@
         }
     };
 
-    private final boolean mAllowNonNativeRefreshRateOverride;
-
     private final BrightnessSynchronizer mBrightnessSynchronizer;
 
     /**
@@ -503,7 +504,7 @@
         mCurrentUserId = UserHandle.USER_SYSTEM;
         ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces();
         mWideColorSpace = colorSpaces[1];
-        mAllowNonNativeRefreshRateOverride = mInjector.getAllowNonNativeRefreshRateOverride();
+        mOverlayProperties = SurfaceControl.getOverlaySupport();
         mSystemReady = false;
     }
 
@@ -882,20 +883,27 @@
 
     private DisplayInfo getDisplayInfoForFrameRateOverride(DisplayEventReceiver.FrameRateOverride[]
             frameRateOverrides, DisplayInfo info, int callingUid) {
-        float frameRateHz = 0;
+        float frameRateHz = info.renderFrameRate;
         for (DisplayEventReceiver.FrameRateOverride frameRateOverride : frameRateOverrides) {
             if (frameRateOverride.uid == callingUid) {
                 frameRateHz = frameRateOverride.frameRateHz;
                 break;
             }
         }
+
         if (frameRateHz == 0) {
             return info;
         }
 
+        // For non-apps users we always return the physical refresh rate from display mode
+        boolean displayModeReturnsPhysicalRefreshRate =
+                callingUid < FIRST_APPLICATION_UID
+                        || CompatChanges.isChangeEnabled(
+                                DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE, callingUid);
+
         // Override the refresh rate only if it is a divisor of the current
         // refresh rate. This calculation needs to be in sync with the native code
-        // in RefreshRateConfigs::getFrameRateDivisor
+        // in RefreshRateSelector::getFrameRateDivisor
         Display.Mode currentMode = info.getMode();
         float numPeriods = currentMode.getRefreshRate() / frameRateHz;
         float numPeriodsRound = Math.round(numPeriods);
@@ -919,32 +927,27 @@
                 }
                 overriddenInfo.refreshRateOverride = mode.getRefreshRate();
 
-                if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
-                        callingUid)) {
+                if (!displayModeReturnsPhysicalRefreshRate) {
                     overriddenInfo.modeId = mode.getModeId();
                 }
                 return overriddenInfo;
             }
         }
 
-        if (mAllowNonNativeRefreshRateOverride) {
-            overriddenInfo.refreshRateOverride = frameRateHz;
-            if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
-                    callingUid)) {
-                overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
-                        info.supportedModes.length + 1);
-                overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
-                        new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
-                                currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
-                                overriddenInfo.refreshRateOverride);
-                overriddenInfo.modeId =
-                        overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
-                                .getModeId();
-            }
-            return overriddenInfo;
+        overriddenInfo.refreshRateOverride = frameRateHz;
+        if (!displayModeReturnsPhysicalRefreshRate) {
+            overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
+                    info.supportedModes.length + 1);
+            overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
+                    new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
+                            currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
+                            overriddenInfo.refreshRateOverride,
+                            new float[0], currentMode.getSupportedHdrTypes());
+            overriddenInfo.modeId =
+                    overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
+                            .getModeId();
         }
-
-        return info;
+        return overriddenInfo;
     }
 
     private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) {
@@ -1280,6 +1283,9 @@
         if ((flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
             flags &= ~VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
         }
+        if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) == 0 && virtualDevice != null) {
+            flags |= VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
+        }
 
         if (projection != null) {
             try {
@@ -1407,7 +1413,7 @@
         // If the display is to be added to a device display group, we need to make the
         // LogicalDisplayMapper aware of the link between the new display and its associated virtual
         // device before triggering DISPLAY_DEVICE_EVENT_ADDED.
-        if (virtualDevice != null && (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) == 0) {
+        if ((flags & VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP) != 0) {
             try {
                 final int virtualDeviceId = virtualDevice.getDeviceId();
                 mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(
@@ -1578,7 +1584,7 @@
             mSyncRoot.notifyAll();
         }
 
-        sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
+        sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
 
         Runnable work = updateDisplayStateLocked(device);
         if (work != null) {
@@ -1597,7 +1603,7 @@
         // We don't bother invalidating the display info caches here because any changes to the
         // display info will trigger a cache invalidation inside of LogicalDisplay before we hit
         // this point.
-        sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+        sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
         scheduleTraversalLocked(false);
         mPersistentDataStore.saveIfNeeded();
 
@@ -1627,7 +1633,7 @@
         mDisplayStates.delete(displayId);
         mDisplayBrightnesses.delete(displayId);
         DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
-        sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+        sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
         scheduleTraversalLocked(false);
 
         if (mDisplayWindowPolicyControllers.contains(displayId)) {
@@ -1643,23 +1649,13 @@
     }
 
     private void handleLogicalDisplaySwappedLocked(@NonNull LogicalDisplay display) {
-        final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
-        final Runnable work = updateDisplayStateLocked(device);
-        if (work != null) {
-            mHandler.post(work);
-        }
-        final int displayId = display.getDisplayIdLocked();
+        handleLogicalDisplayChangedLocked(display);
 
+        final int displayId = display.getDisplayIdLocked();
         if (displayId == Display.DEFAULT_DISPLAY) {
             notifyDefaultDisplayDeviceUpdated(display);
         }
-        DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
-        if (dpc != null) {
-            dpc.onDisplayChanged();
-        }
-        mPersistentDataStore.saveIfNeeded();
         mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATIONS);
-        handleLogicalDisplayChangedLocked(display);
     }
 
     private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) {
@@ -1671,7 +1667,7 @@
         final int displayId = display.getDisplayIdLocked();
         final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
         if (dpc != null) {
-            dpc.onDeviceStateTransition();
+            dpc.onDisplayChanged();
         }
     }
 
@@ -1716,7 +1712,20 @@
         final Point userPreferredResolution =
                 mPersistentDataStore.getUserPreferredResolution(device);
         final float refreshRate = mPersistentDataStore.getUserPreferredRefreshRate(device);
-        if (userPreferredResolution == null && Float.isNaN(refreshRate)) {
+        // If value in persistentDataStore is null, preserving the mode from systemPreferredMode.
+        // This is required because in some devices, user-preferred mode was not stored in
+        // persistentDataStore, but was stored in a config which is returned through
+        // systemPreferredMode.
+        if ((userPreferredResolution == null && Float.isNaN(refreshRate))
+                || (userPreferredResolution.equals(0, 0) && refreshRate == 0.0f)) {
+            Display.Mode systemPreferredMode = device.getSystemPreferredDisplayModeLocked();
+            if (systemPreferredMode == null) {
+                return;
+            }
+            storeModeInPersistentDataStoreLocked(
+                    display.getDisplayIdLocked(), systemPreferredMode.getPhysicalWidth(),
+                    systemPreferredMode.getPhysicalHeight(), systemPreferredMode.getRefreshRate());
+            device.setUserPreferredDisplayModeLocked(systemPreferredMode);
             return;
         }
         Display.Mode.Builder modeBuilder = new Display.Mode.Builder();
@@ -1772,6 +1781,10 @@
         return mWideColorSpace.getId();
     }
 
+    OverlayProperties getOverlaySupportInternal() {
+        return mOverlayProperties;
+    }
+
     void setUserPreferredDisplayModeInternal(int displayId, Display.Mode mode) {
         synchronized (mSyncRoot) {
             if (mode != null && !isResolutionAndRefreshRateValid(mode)
@@ -1902,8 +1915,9 @@
                 if (displayDevice == null) {
                     return;
                 }
-                if (mLogicalDisplayMapper.getDisplayLocked(displayDevice)
-                        .getDisplayInfoLocked().type == Display.TYPE_INTERNAL) {
+                if (mLogicalDisplayMapper.getDisplayLocked(displayDevice) != null
+                        && mLogicalDisplayMapper.getDisplayLocked(displayDevice)
+                        .getDisplayInfoLocked().type == Display.TYPE_INTERNAL && c != null) {
                     FrameworkStatsLog.write(FrameworkStatsLog.BRIGHTNESS_CONFIGURATION_UPDATED,
                                 c.getCurve().first,
                                 c.getCurve().second,
@@ -2353,9 +2367,13 @@
         }
     }
 
-    private void sendDisplayEventLocked(int displayId, @DisplayEvent int event) {
-        Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
-        mHandler.sendMessage(msg);
+    private void sendDisplayEventLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) {
+        // Only send updates outside of DisplayManagerService for enabled displays
+        if (display.isEnabledLocked()) {
+            int displayId = display.getDisplayIdLocked();
+            Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
+            mHandler.sendMessage(msg);
+        }
     }
 
     private void sendDisplayGroupEvent(int groupId, int event) {
@@ -2581,11 +2599,6 @@
         long getDefaultDisplayDelayTimeout() {
             return WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT;
         }
-
-        boolean getAllowNonNativeRefreshRateOverride() {
-            return DisplayProperties
-                    .debug_allow_non_native_refresh_rate_override().orElse(true);
-        }
     }
 
     @VisibleForTesting
@@ -2620,7 +2633,8 @@
             // initPowerManagement has not yet been called.
             return;
         }
-        if (mBrightnessTracker == null) {
+
+        if (mBrightnessTracker == null && display.getDisplayIdLocked() == Display.DEFAULT_DISPLAY) {
             mBrightnessTracker = new BrightnessTracker(mContext, null);
         }
 
@@ -2629,7 +2643,7 @@
         final DisplayPowerControllerInterface displayPowerController;
 
         if (DeviceConfig.getBoolean("display_manager",
-                "use_newly_structured_display_power_controller", false)) {
+                "use_newly_structured_display_power_controller", true)) {
             displayPowerController = new DisplayPowerController2(
                     mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
                     mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
@@ -2644,8 +2658,7 @@
     }
 
     private void handleBrightnessChange(LogicalDisplay display) {
-        sendDisplayEventLocked(display.getDisplayIdLocked(),
-                DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED);
+        sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED);
     }
 
     private DisplayDevice getDeviceForDisplayLocked(int displayId) {
@@ -2862,12 +2875,12 @@
          * Returns the list of all display ids.
          */
         @Override // Binder call
-        public int[] getDisplayIds() {
+        public int[] getDisplayIds(boolean includeDisabled) {
             final int callingUid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mSyncRoot) {
-                    return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid);
+                    return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid, includeDisabled);
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -3358,6 +3371,11 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mSyncRoot) {
+                    LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(
+                            displayId, /* includeDisabled= */ false);
+                    if (display == null || !display.isEnabledLocked()) {
+                        return null;
+                    }
                     DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
                     if (dpc != null) {
                         return dpc.getBrightnessInfo();
@@ -3582,6 +3600,16 @@
                 }
             }
         }
+
+        @Override
+        public OverlayProperties getOverlaySupport() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return getOverlaySupportInternal();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
     }
 
     private static boolean isValidBrightness(float brightness) {
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index aa9f2dc..405a2b9 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display;
 
+import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
 import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE;
 import static android.os.PowerManager.BRIGHTNESS_INVALID;
 
@@ -33,7 +34,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;
@@ -48,7 +49,7 @@
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfigInterface;
 import android.provider.Settings;
-import android.sysprop.DisplayProperties;
+import android.sysprop.SurfaceFlingerProperties;
 import android.text.TextUtils;
 import android.util.IndentingPrintWriter;
 import android.util.Pair;
@@ -137,8 +138,7 @@
 
     private boolean mAlwaysRespectAppRequest;
 
-    // TODO(b/241447632): remove the flag once SF changes are ready
-    private final boolean mRenderFrameRateIsPhysicalRefreshRate;
+    private final boolean mSupportsFrameRateOverride;
 
     /**
      * The allowed refresh rate switching type. This is used by SurfaceFlinger.
@@ -175,7 +175,7 @@
         mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(),
                 mDeviceConfigDisplaySettings);
         mAlwaysRespectAppRequest = false;
-        mRenderFrameRateIsPhysicalRefreshRate = injector.renderFrameRateIsPhysicalRefreshRate();
+        mSupportsFrameRateOverride = injector.supportsFrameRateOverride();
     }
 
     /**
@@ -237,21 +237,6 @@
             }
         }
 
-        if (mRenderFrameRateIsPhysicalRefreshRate) {
-            for (int i = 0; i < votes.size(); i++) {
-
-                Vote vote = votes.valueAt(i);
-                vote.refreshRateRanges.physical.min = Math.max(vote.refreshRateRanges.physical.min,
-                        vote.refreshRateRanges.render.min);
-                vote.refreshRateRanges.physical.max = Math.min(vote.refreshRateRanges.physical.max,
-                        vote.refreshRateRanges.render.max);
-                vote.refreshRateRanges.render.min = Math.max(vote.refreshRateRanges.physical.min,
-                        vote.refreshRateRanges.render.min);
-                vote.refreshRateRanges.render.max = Math.min(vote.refreshRateRanges.physical.max,
-                        vote.refreshRateRanges.render.max);
-            }
-        }
-
         return votes;
     }
 
@@ -279,6 +264,18 @@
             disableRefreshRateSwitching = false;
             appRequestBaseModeRefreshRate = 0f;
         }
+
+        @Override
+        public String toString() {
+            return  "minPhysicalRefreshRate=" + minPhysicalRefreshRate
+                    + ", maxPhysicalRefreshRate=" + maxPhysicalRefreshRate
+                    + ", minRenderFrameRate=" + minRenderFrameRate
+                    + ", maxRenderFrameRate=" + maxRenderFrameRate
+                    + ", width=" + width
+                    + ", height=" + height
+                    + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching
+                    + ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate;
+        }
     }
 
     // VoteSummary is returned as an output param to cut down a bit on the number of temporary
@@ -332,18 +329,8 @@
             }
 
             if (mLoggingEnabled) {
-                Slog.w(TAG, "Vote summary for priority "
-                        + Vote.priorityToString(priority)
-                        + ": width=" + summary.width
-                        + ", height=" + summary.height
-                        + ", minPhysicalRefreshRate=" + summary.minPhysicalRefreshRate
-                        + ", maxPhysicalRefreshRate=" + summary.maxPhysicalRefreshRate
-                        + ", minRenderFrameRate=" + summary.minRenderFrameRate
-                        + ", maxRenderFrameRate=" + summary.maxRenderFrameRate
-                        + ", disableRefreshRateSwitching="
-                        + summary.disableRefreshRateSwitching
-                        + ", appRequestBaseModeRefreshRate="
-                        + summary.appRequestBaseModeRefreshRate);
+                Slog.w(TAG, "Vote summary for priority " + Vote.priorityToString(priority)
+                        + ": " + summary);
             }
         }
     }
@@ -377,6 +364,23 @@
         return !availableModes.isEmpty() ? availableModes.get(0) : null;
     }
 
+    private void disableModeSwitching(VoteSummary summary, float fps) {
+        summary.minPhysicalRefreshRate = summary.maxPhysicalRefreshRate = fps;
+        summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, fps);
+
+        if (mLoggingEnabled) {
+            Slog.i(TAG, "Disabled mode switching on summary: " + summary);
+        }
+    }
+
+    private void disableRenderRateSwitching(VoteSummary summary) {
+        summary.minRenderFrameRate = summary.maxRenderFrameRate;
+
+        if (mLoggingEnabled) {
+            Slog.i(TAG, "Disabled render rate switching on summary: " + summary);
+        }
+    }
+
     /**
      * Calculates the refresh rate ranges and display modes that the system is allowed to freely
      * switch between based on global and display-specific constraints.
@@ -405,7 +409,7 @@
             int highestConsideredPriority = Vote.MAX_PRIORITY;
 
             if (mAlwaysRespectAppRequest) {
-                lowestConsideredPriority = Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE;
+                lowestConsideredPriority = Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE;
                 highestConsideredPriority = Vote.PRIORITY_APP_REQUEST_SIZE;
             }
 
@@ -526,19 +530,21 @@
                         ranges, ranges);
             }
 
-            if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE
-                    || primarySummary.disableRefreshRateSwitching) {
+            boolean modeSwitchingDisabled =
+                    mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE
+                            || mModeSwitchingType
+                                == DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
+
+            if (modeSwitchingDisabled || primarySummary.disableRefreshRateSwitching) {
                 float fps = baseMode.getRefreshRate();
-                primarySummary.minPhysicalRefreshRate = primarySummary.maxPhysicalRefreshRate = fps;
-                if (mRenderFrameRateIsPhysicalRefreshRate) {
-                    primarySummary.minRenderFrameRate = primarySummary.maxRenderFrameRate = fps;
-                }
-                if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) {
-                    primarySummary.minRenderFrameRate = primarySummary.maxRenderFrameRate = fps;
-                    appRequestSummary.minPhysicalRefreshRate =
-                            appRequestSummary.maxPhysicalRefreshRate = fps;
-                    appRequestSummary.minRenderFrameRate =
-                            appRequestSummary.maxRenderFrameRate = fps;
+                disableModeSwitching(primarySummary, fps);
+                if (modeSwitchingDisabled) {
+                    disableModeSwitching(appRequestSummary, fps);
+                    disableRenderRateSwitching(primarySummary);
+
+                    if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) {
+                        disableRenderRateSwitching(appRequestSummary);
+                    }
                 }
             }
 
@@ -605,6 +611,22 @@
                 continue;
             }
 
+            // The physical refresh rate must be in the render frame rate range, unless
+            // frame rate override is supported.
+            if (!mSupportsFrameRateOverride) {
+                if (physicalRefreshRate < (summary.minRenderFrameRate - FLOAT_TOLERANCE)
+                        || physicalRefreshRate > (summary.maxRenderFrameRate + FLOAT_TOLERANCE)) {
+                    if (mLoggingEnabled) {
+                        Slog.w(TAG, "Discarding mode " + mode.getModeId()
+                                + ", outside render rate bounds"
+                                + ": minPhysicalRefreshRate=" + summary.minPhysicalRefreshRate
+                                + ", maxPhysicalRefreshRate=" + summary.maxPhysicalRefreshRate
+                                + ", modeRefreshRate=" + physicalRefreshRate);
+                    }
+                    continue;
+                }
+            }
+
             // Check whether the render frame rate range is achievable by the mode's physical
             // refresh rate, meaning that if a divisor of the physical refresh rate is in range
             // of the render frame rate.
@@ -665,8 +687,10 @@
      * changed
      */
     public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) {
-        mSettingsObserver.setRefreshRates(displayDeviceConfig);
-        mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig);
+        mSettingsObserver.setRefreshRates(displayDeviceConfig,
+            /* attemptLoadingFromDeviceConfig= */ true);
+        mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
+            /* attemptLoadingFromDeviceConfig= */ true);
     }
 
     /**
@@ -842,6 +866,8 @@
                 return "SWITCHING_TYPE_WITHIN_GROUPS";
             case DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS:
                 return "SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS";
+            case DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY:
+                return "SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY";
             default:
                 return "Unknown SwitchingType " + type;
         }
@@ -1314,19 +1340,25 @@
         SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
             super(handler);
             mContext = context;
-            setRefreshRates(/* displayDeviceConfig= */ null);
+            // We don't want to load from the DeviceConfig while constructing since this leads to
+            // a spike in the latency of DisplayManagerService startup. This happens because
+            // reading from the DeviceConfig is an intensive IO operation and having it in the
+            // startup phase where we thrive to keep the latency very low has significant impact.
+            setRefreshRates(/* displayDeviceConfig= */ null,
+                /* attemptLoadingFromDeviceConfig= */ false);
         }
 
         /**
          * This is used to update the refresh rate configs from the DeviceConfig, which
          * if missing from DisplayDeviceConfig, and finally fallback to config.xml.
          */
-        public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig) {
-            setDefaultPeakRefreshRate(displayDeviceConfig);
+        public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptLoadingFromDeviceConfig) {
+            setDefaultPeakRefreshRate(displayDeviceConfig, attemptLoadingFromDeviceConfig);
             mDefaultRefreshRate =
                     (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
-                            R.integer.config_defaultRefreshRate)
-                            : (float) displayDeviceConfig.getDefaultRefreshRate();
+                        R.integer.config_defaultRefreshRate)
+                        : (float) displayDeviceConfig.getDefaultRefreshRate();
         }
 
         public void observe() {
@@ -1387,13 +1419,27 @@
             }
         }
 
-        private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig) {
+        @VisibleForTesting
+        float getDefaultRefreshRate() {
+            return mDefaultRefreshRate;
+        }
+
+        @VisibleForTesting
+        float getDefaultPeakRefreshRate() {
+            return mDefaultPeakRefreshRate;
+        }
+
+        private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptLoadingFromDeviceConfig) {
             Float defaultPeakRefreshRate = null;
-            try {
-                defaultPeakRefreshRate =
+
+            if (attemptLoadingFromDeviceConfig) {
+                try {
+                    defaultPeakRefreshRate =
                         mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
-            } catch (Exception exception) {
-                // Do nothing
+                } catch (Exception exception) {
+                    // Do nothing
+                }
             }
             if (defaultPeakRefreshRate == null) {
                 defaultPeakRefreshRate =
@@ -1610,7 +1656,7 @@
             SparseArray<Display.Mode[]> modes = new SparseArray<>();
             SparseArray<Display.Mode> defaultModes = new SparseArray<>();
             DisplayInfo info = new DisplayInfo();
-            Display[] displays = dm.getDisplays();
+            Display[] displays = dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
             for (Display d : displays) {
                 final int displayId = d.getDisplayId();
                 d.getDisplayInfo(info);
@@ -1719,7 +1765,8 @@
             mContext = context;
             mHandler = handler;
             mInjector = injector;
-            updateBlockingZoneThresholds(/* displayDeviceConfig= */ null);
+            updateBlockingZoneThresholds(/* displayDeviceConfig= */ null,
+                /* attemptLoadingFromDeviceConfig= */ false);
             mRefreshRateInHighZone = context.getResources().getInteger(
                     R.integer.config_fixedRefreshRateInHighZone);
         }
@@ -1728,22 +1775,44 @@
          * This is used to update the blocking zone thresholds from the DeviceConfig, which
          * if missing from DisplayDeviceConfig, and finally fallback to config.xml.
          */
-        public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig) {
-            loadLowBrightnessThresholds(displayDeviceConfig);
-            loadHighBrightnessThresholds(displayDeviceConfig);
+        public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptLoadingFromDeviceConfig) {
+            loadLowBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+            loadHighBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
         }
 
-        private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig) {
+        @VisibleForTesting
+        int[] getLowDisplayBrightnessThreshold() {
+            return mLowDisplayBrightnessThresholds;
+        }
+
+        @VisibleForTesting
+        int[] getLowAmbientBrightnessThreshold() {
+            return mLowAmbientBrightnessThresholds;
+        }
+
+        @VisibleForTesting
+        int[] getHighDisplayBrightnessThreshold() {
+            return mHighDisplayBrightnessThresholds;
+        }
+
+        @VisibleForTesting
+        int[] getHighAmbientBrightnessThreshold() {
+            return mHighAmbientBrightnessThresholds;
+        }
+
+        private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptLoadingFromDeviceConfig) {
             mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
                     () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
                     () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
                     R.array.config_brightnessThresholdsOfPeakRefreshRate,
-                    displayDeviceConfig);
+                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
             mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
                     () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
                     () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
                     R.array.config_ambientThresholdsOfPeakRefreshRate,
-                    displayDeviceConfig);
+                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
             if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) {
                 throw new RuntimeException("display low brightness threshold array and ambient "
                         + "brightness threshold array have different length: "
@@ -1754,17 +1823,18 @@
             }
         }
 
-        private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig) {
+        private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptLoadingFromDeviceConfig) {
             mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
                     () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
                     () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
                     R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
-                    displayDeviceConfig);
+                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
             mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
                     () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
                     () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
                     R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
-                    displayDeviceConfig);
+                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
             if (mHighDisplayBrightnessThresholds.length
                     != mHighAmbientBrightnessThresholds.length) {
                 throw new RuntimeException("display high brightness threshold array and ambient "
@@ -1780,13 +1850,16 @@
                 Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable,
                 Callable<int[]> loadFromDisplayDeviceConfigCallable,
                 int brightnessThresholdOfFixedRefreshRateKey,
-                DisplayDeviceConfig displayDeviceConfig) {
+                DisplayDeviceConfig displayDeviceConfig, boolean attemptLoadingFromDeviceConfig) {
             int[] brightnessThresholds = null;
-            try {
-                brightnessThresholds =
+
+            if (attemptLoadingFromDeviceConfig) {
+                try {
+                    brightnessThresholds =
                         loadFromDeviceConfigDisplaySettingsCallable.call();
-            } catch (Exception exception) {
-                // Do nothing
+                } catch (Exception exception) {
+                    // Do nothing
+                }
             }
             if (brightnessThresholds == null) {
                 try {
@@ -2359,39 +2432,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 +2482,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);
             }
         }
@@ -2460,7 +2533,8 @@
             sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this);
 
             synchronized (mSensorObserverLock) {
-                for (Display d : mDisplayManager.getDisplays()) {
+                for (Display d : mDisplayManager.getDisplays(
+                        DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
                     mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d));
                 }
             }
@@ -2471,7 +2545,8 @@
         }
 
         private void recalculateVotesLocked() {
-            final Display[] displays = mDisplayManager.getDisplays();
+            final Display[] displays = mDisplayManager.getDisplays(
+                    DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
             for (Display d : displays) {
                 int displayId = d.getDisplayId();
                 Vote vote = null;
@@ -2919,7 +2994,7 @@
 
         IThermalService getThermalService();
 
-        boolean renderFrameRateIsPhysicalRefreshRate();
+        boolean supportsFrameRateOverride();
     }
 
     @VisibleForTesting
@@ -2974,9 +3049,11 @@
         }
 
         @Override
-        public boolean renderFrameRateIsPhysicalRefreshRate() {
-            return DisplayProperties
-                    .debug_render_frame_rate_is_physical_refresh_rate().orElse(true);
+        public boolean supportsFrameRateOverride() {
+            return SurfaceFlingerProperties.enable_frame_rate_override().orElse(false)
+                            && !SurfaceFlingerProperties.frame_rate_override_for_native_rates()
+                                    .orElse(true)
+                            && SurfaceFlingerProperties.frame_rate_override_global().orElse(false);
         }
 
         private DisplayManager getDisplayManager() {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 422e98f..9d47892 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -219,15 +219,6 @@
 
     private final float mScreenBrightnessDefault;
 
-    // The minimum allowed brightness while in VR.
-    private final float mScreenBrightnessForVrRangeMinimum;
-
-    // The maximum allowed brightness while in VR.
-    private final float mScreenBrightnessForVrRangeMaximum;
-
-    // The default screen brightness for VR.
-    private final float mScreenBrightnessForVrDefault;
-
     // True if auto-brightness should be used.
     private boolean mUseSoftwareAutoBrightnessConfig;
 
@@ -450,9 +441,6 @@
     // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set.
     private float mTemporaryScreenBrightness;
 
-    // The current screen brightness while in VR mode.
-    private float mScreenBrightnessForVr;
-
     // The last auto brightness adjustment that was set by the user and not temporary. Set to
     // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
     private float mAutoBrightnessAdjustment;
@@ -497,6 +485,9 @@
     private final String mSuspendBlockerIdProxNegative;
     private final String mSuspendBlockerIdProxDebounce;
 
+    private boolean mIsEnabled;
+    private boolean mIsInTransition;
+
     /**
      * Creates the display power controller.
      */
@@ -520,6 +511,8 @@
         mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
         mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
         mDisplayStatsId = mUniqueDisplayId.hashCode();
+        mIsEnabled = logicalDisplay.isEnabledLocked();
+        mIsInTransition = logicalDisplay.isInTransitionLocked();
         mHandler = new DisplayControllerHandler(handler.getLooper());
         mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
         mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
@@ -558,14 +551,6 @@
         mScreenBrightnessDefault = clampAbsoluteBrightness(
                 mLogicalDisplay.getDisplayInfoLocked().brightnessDefault);
 
-        // VR SETTINGS
-        mScreenBrightnessForVrDefault = clampAbsoluteBrightness(
-                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR));
-        mScreenBrightnessForVrRangeMaximum = clampAbsoluteBrightness(
-                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR));
-        mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
-                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
-
         mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
 
@@ -638,7 +623,6 @@
         loadProximitySensor();
 
         mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
-        mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
         mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
         mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -807,29 +791,36 @@
         final DisplayDeviceConfig config = device.getDisplayDeviceConfig();
         final IBinder token = device.getDisplayTokenLocked();
         final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+        final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
+        final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
         mHandler.post(() -> {
+            boolean changed = false;
             if (mDisplayDevice != device) {
+                changed = true;
                 mDisplayDevice = device;
                 mUniqueDisplayId = uniqueId;
                 mDisplayStatsId = mUniqueDisplayId.hashCode();
                 mDisplayDeviceConfig = config;
                 loadFromDisplayDeviceConfig(token, info);
+
+                // Since the underlying display-device changed, we really don't know the
+                // last command that was sent to change it's state. Lets assume it is unknown so
+                // that we trigger a change immediately.
+                mPowerState.resetScreenState();
+            }
+            if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
+                changed = true;
+                mIsEnabled = isEnabled;
+                mIsInTransition = isInTransition;
+            }
+
+            if (changed) {
                 updatePowerState();
             }
         });
     }
 
     /**
-     * Called when the displays are preparing to transition from one device state to another.
-     * This process involves turning off some displays so we need updatePowerState() to run and
-     * calculate the new state.
-     */
-    @Override
-    public void onDeviceStateTransition() {
-        sendUpdatePowerState();
-    }
-
-    /**
      * Unregisters all listeners and interrupts all running threads; halting future work.
      *
      * This method should be called when the DisplayPowerController is no longer in use; i.e. when
@@ -927,7 +918,7 @@
 
         // Initialize all of the brightness tracking state
         final float brightness = convertToNits(mPowerState.getScreenBrightness());
-        if (brightness >= PowerManager.BRIGHTNESS_MIN) {
+        if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
             mBrightnessTracker.start(brightness);
         }
         mBrightnessSettingListener = brightnessValue -> {
@@ -937,9 +928,6 @@
 
         mBrightnessSetting.registerListener(mBrightnessSettingListener);
         mContext.getContentResolver().registerContentObserver(
-                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT),
-                false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
-        mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
                 false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
     }
@@ -1059,7 +1047,9 @@
             }
 
             loadAmbientLightSensor();
-            if (mBrightnessTracker != null) {
+            // BrightnessTracker should only use one light sensor, we want to use the light sensor
+            // from the default display and not e.g. temporary displays when switching layouts.
+            if (mBrightnessTracker != null && mDisplayId == Display.DEFAULT_DISPLAY) {
                 mBrightnessTracker.setLightSensor(mLightSensor);
             }
 
@@ -1257,9 +1247,6 @@
                     mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE);
                 }
                 break;
-            case DisplayPowerRequest.POLICY_VR:
-                state = Display.STATE_VR;
-                break;
             case DisplayPowerRequest.POLICY_DIM:
             case DisplayPowerRequest.POLICY_BRIGHT:
             default:
@@ -1314,8 +1301,8 @@
             mIgnoreProximityUntilChanged = false;
         }
 
-        if (!mLogicalDisplay.isEnabled()
-                || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION
+        if (!mIsEnabled
+                || mIsInTransition
                 || mScreenOffBecauseOfProximity) {
             state = Display.STATE_OFF;
         }
@@ -1337,12 +1324,6 @@
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_SCREEN_OFF);
         }
 
-        // Always use the VR brightness when in the VR state.
-        if (state == Display.STATE_VR) {
-            brightnessState = mScreenBrightnessForVr;
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_VR);
-        }
-
         if ((Float.isNaN(brightnessState))
                 && isValidBrightnessValue(mPowerRequest.screenBrightnessOverride)) {
             brightnessState = mPowerRequest.screenBrightnessOverride;
@@ -1561,7 +1542,7 @@
                 mBrightnessThrottler.getBrightnessMaxReason());
 
         // Animate the screen brightness when the screen is on or dozing.
-        // Skip the animation when the screen is off or suspended or transition to/from VR.
+        // Skip the animation when the screen is off.
         boolean brightnessAdjusted = false;
         final boolean brightnessIsTemporary =
                 mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
@@ -1584,8 +1565,6 @@
                 }
             }
 
-            final boolean wasOrWillBeInVr =
-                    (state == Display.STATE_VR || oldState == Display.STATE_VR);
             final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState
                     != RAMP_STATE_SKIP_NONE) || skipRampBecauseOfProximityChangeToNegative;
             // While dozing, sometimes the brightness is split into buckets. Rather than animating
@@ -1627,7 +1606,7 @@
                     && (animateValue != currentBrightness
                     || sdrAnimateValue != currentSdrBrightness)) {
                 if (initialRampSkip || hasBrightnessBuckets
-                        || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) {
+                        || !isDisplayContentVisible || brightnessIsTemporary) {
                     animateScreenBrightness(animateValue, sdrAnimateValue,
                             SCREEN_ANIMATION_RATE_MINIMUM);
                 } else {
@@ -1701,6 +1680,7 @@
         mTempBrightnessEvent.setRbcStrength(mCdsi != null
                 ? mCdsi.getReduceBrightColorsStrength() : -1);
         mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
+        mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint);
         // Temporary is what we use during slider interactions. We avoid logging those so that
         // we don't spam logcat when the slider is being used.
         boolean tempToTempTransition =
@@ -1711,12 +1691,6 @@
                 || brightnessAdjustmentFlags != 0) {
             float lastBrightness = mLastBrightnessEvent.getBrightness();
             mTempBrightnessEvent.setInitialBrightness(lastBrightness);
-            mTempBrightnessEvent.setFastAmbientLux(
-                    mAutomaticBrightnessController == null
-                        ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
-            mTempBrightnessEvent.setSlowAmbientLux(
-                    mAutomaticBrightnessController == null
-                        ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
             mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
             mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
             BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
@@ -2069,12 +2043,6 @@
         }
     }
 
-    private float clampScreenBrightnessForVr(float value) {
-        return MathUtils.constrain(
-                value, mScreenBrightnessForVrRangeMinimum,
-                mScreenBrightnessForVrRangeMaximum);
-    }
-
     private float clampScreenBrightness(float value) {
         if (Float.isNaN(value)) {
             value = PowerManager.BRIGHTNESS_MIN;
@@ -2163,23 +2131,6 @@
                 mPowerState.setColorFadeLevel(1.0f);
                 mPowerState.dismissColorFade();
             }
-        } else if (target == Display.STATE_VR) {
-            // Wait for brightness animation to complete beforehand when entering VR
-            // from screen on to prevent a perceptible jump because brightness may operate
-            // differently when the display is configured for dozing.
-            if (mScreenBrightnessRampAnimator.isAnimating()
-                    && mPowerState.getScreenState() == Display.STATE_ON) {
-                return;
-            }
-
-            // Set screen state.
-            if (!setScreenState(Display.STATE_VR)) {
-                return; // screen on blocked
-            }
-
-            // Dismiss the black surface without fanfare.
-            mPowerState.setColorFadeLevel(1.0f);
-            mPowerState.dismissColorFade();
         } else if (target == Display.STATE_DOZE) {
             // Want screen dozing.
             // Wait for brightness animation to complete beforehand when entering doze
@@ -2387,9 +2338,6 @@
                 mAutomaticBrightnessController.resetShortTermModel();
             }
         }
-        // We don't bother with a pending variable for VR screen brightness since we just
-        // immediately adapt to it.
-        mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
         sendUpdatePowerState();
     }
 
@@ -2408,13 +2356,6 @@
         return clampAbsoluteBrightness(brightness);
     }
 
-    private float getScreenBrightnessForVrSetting() {
-        final float brightnessFloat = Settings.System.getFloatForUser(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT, mScreenBrightnessForVrDefault,
-                UserHandle.USER_CURRENT);
-        return clampScreenBrightnessForVr(brightnessFloat);
-    }
-
     @Override
     public void setBrightness(float brightnessValue) {
         // Update the setting, which will eventually call back into DPC to have us actually update
@@ -2485,7 +2426,7 @@
             boolean hadUserDataPoint) {
         final float brightnessInNits = convertToNits(brightness);
         if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
-                && mAutomaticBrightnessController != null) {
+                && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
             // We only want to track changes on devices that can actually map the display backlight
             // values into a physical brightness unit since the value provided by the API is in
             // nits and not using the arbitrary backlight units.
@@ -2494,7 +2435,9 @@
                     : 1.0f;
             mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
                     powerFactor, hadUserDataPoint,
-                    mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId);
+                    mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
+                    mAutomaticBrightnessController.getLastSensorValues(),
+                    mAutomaticBrightnessController.getLastSensorTimestamps());
         }
     }
 
@@ -2588,9 +2531,6 @@
         pw.println("  mScreenBrightnessRangeDefault=" + mScreenBrightnessDefault);
         pw.println("  mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
         pw.println("  mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
-        pw.println("  mScreenBrightnessForVrRangeMinimum=" + mScreenBrightnessForVrRangeMinimum);
-        pw.println("  mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum);
-        pw.println("  mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault);
         pw.println("  mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
         pw.println("  mAllowAutoBrightnessWhileDozingConfig="
                 + mAllowAutoBrightnessWhileDozingConfig);
@@ -2640,7 +2580,6 @@
         pw.println("  mBrightnessReason=" + mBrightnessReason);
         pw.println("  mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
         pw.println("  mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
-        pw.println("  mScreenBrightnessForVrFloat=" + mScreenBrightnessForVr);
         pw.println("  mAppliedAutoBrightness=" + mAppliedAutoBrightness);
         pw.println("  mAppliedDimming=" + mAppliedDimming);
         pw.println("  mAppliedLowPower=" + mAppliedLowPower);
@@ -2838,18 +2777,22 @@
                 event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
                 ? -1f : convertToNits(event.getThermalMax());
 
-        FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
-                convertToNits(event.getInitialBrightness()),
-                convertToNits(event.getBrightness()),
-                event.getSlowAmbientLux(),
-                event.getPhysicalDisplayId(),
-                event.isShortTermModelActive(),
-                appliedLowPowerMode,
-                appliedRbcStrength,
-                appliedHbmMaxNits,
-                appliedThermalCapNits,
-                event.isAutomaticBrightnessEnabled(),
-                FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+        if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
+                && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
+                    .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) {
+            FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+                    convertToNits(event.getInitialBrightness()),
+                    convertToNits(event.getBrightness()),
+                    event.getLux(),
+                    event.getPhysicalDisplayId(),
+                    event.wasShortTermModelActive(),
+                    appliedLowPowerMode,
+                    appliedRbcStrength,
+                    appliedHbmMaxNits,
+                    appliedThermalCapNits,
+                    event.isAutomaticBrightnessEnabled(),
+                    FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+        }
     }
 
     private final class DisplayControllerHandler extends Handler {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 3c1bf0b..346b340 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -26,8 +26,6 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.hardware.display.AmbientBrightnessDayStats;
 import android.hardware.display.BrightnessChangeEvent;
@@ -54,7 +52,6 @@
 import android.util.MutableFloat;
 import android.util.MutableInt;
 import android.util.Slog;
-import android.util.TimeUtils;
 import android.view.Display;
 
 import com.android.internal.R;
@@ -71,6 +68,7 @@
 import com.android.server.display.RampAnimator.DualRampAnimator;
 import com.android.server.display.brightness.BrightnessEvent;
 import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.DisplayBrightnessController;
 import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
 import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
 import com.android.server.display.utils.SensorUtils;
@@ -109,7 +107,7 @@
     private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked";
 
     private static final boolean DEBUG = false;
-    private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
+
 
     // If true, uses the color fade on animation.
     // We might want to turn this off if we cannot get a guarantee that the screen
@@ -123,31 +121,19 @@
     private static final int COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS = 400;
 
     private static final int MSG_UPDATE_POWER_STATE = 1;
-    private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2;
-    private static final int MSG_SCREEN_ON_UNBLOCKED = 3;
-    private static final int MSG_SCREEN_OFF_UNBLOCKED = 4;
-    private static final int MSG_CONFIGURE_BRIGHTNESS = 5;
-    private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 6;
-    private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7;
-    private static final int MSG_IGNORE_PROXIMITY = 8;
-    private static final int MSG_STOP = 9;
-    private static final int MSG_UPDATE_BRIGHTNESS = 10;
-    private static final int MSG_UPDATE_RBC = 11;
-    private static final int MSG_BRIGHTNESS_RAMP_DONE = 12;
-    private static final int MSG_STATSD_HBM_BRIGHTNESS = 13;
-
-    private static final int PROXIMITY_UNKNOWN = -1;
-    private static final int PROXIMITY_NEGATIVE = 0;
-    private static final int PROXIMITY_POSITIVE = 1;
-
-    // Proximity sensor debounce delay in milliseconds for positive or negative transitions.
-    private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
-    private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
+    private static final int MSG_SCREEN_ON_UNBLOCKED = 2;
+    private static final int MSG_SCREEN_OFF_UNBLOCKED = 3;
+    private static final int MSG_CONFIGURE_BRIGHTNESS = 4;
+    private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 5;
+    private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 6;
+    private static final int MSG_STOP = 7;
+    private static final int MSG_UPDATE_BRIGHTNESS = 8;
+    private static final int MSG_UPDATE_RBC = 9;
+    private static final int MSG_BRIGHTNESS_RAMP_DONE = 10;
+    private static final int MSG_STATSD_HBM_BRIGHTNESS = 11;
 
     private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
 
-    // Trigger proximity if distance is less than 5 cm.
-    private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
 
     // State machine constants for tracking initial brightness ramp skipping when enabled.
     private static final int RAMP_STATE_SKIP_NONE = 0;
@@ -200,9 +186,6 @@
     // Tracker for brightness settings changes.
     private final SettingsObserver mSettingsObserver;
 
-    // The proximity sensor, or null if not available or needed.
-    private Sensor mProximitySensor;
-
     // The doze screen brightness.
     private final float mScreenBrightnessDozeConfig;
 
@@ -215,21 +198,9 @@
 
     private final float mScreenBrightnessDefault;
 
-    // The minimum allowed brightness while in VR.
-    private final float mScreenBrightnessForVrRangeMinimum;
-
-    // The maximum allowed brightness while in VR.
-    private final float mScreenBrightnessForVrRangeMaximum;
-
-    // The default screen brightness for VR.
-    private final float mScreenBrightnessForVrDefault;
-
     // True if auto-brightness should be used.
     private boolean mUseSoftwareAutoBrightnessConfig;
 
-    // True if should use light sensor to automatically determine doze screen brightness.
-    private final boolean mAllowAutoBrightnessWhileDozingConfig;
-
     // Whether or not the color fade on screen on / off is enabled.
     private final boolean mColorFadeEnabled;
 
@@ -266,10 +237,6 @@
     @GuardedBy("mLock")
     private DisplayPowerRequest mPendingRequestLocked;
 
-    // True if a request has been made to wait for the proximity sensor to go negative.
-    @GuardedBy("mLock")
-    private boolean mPendingWaitForNegativeProximityLocked;
-
     // True if the pending power request or wait for negative proximity flag
     // has been changed since the last update occurred.
     @GuardedBy("mLock")
@@ -296,37 +263,7 @@
     // Must only be accessed on the handler thread.
     private DisplayPowerState mPowerState;
 
-    // True if the device should wait for negative proximity sensor before
-    // waking up the screen.  This is set to false as soon as a negative
-    // proximity sensor measurement is observed or when the device is forced to
-    // go to sleep by the user.  While true, the screen remains off.
-    private boolean mWaitingForNegativeProximity;
 
-    // True if the device should not take into account the proximity sensor
-    // until either the proximity sensor state changes, or there is no longer a
-    // request to listen to proximity sensor.
-    private boolean mIgnoreProximityUntilChanged;
-
-    // The actual proximity sensor threshold value.
-    private float mProximityThreshold;
-
-    // Set to true if the proximity sensor listener has been registered
-    // with the sensor manager.
-    private boolean mProximitySensorEnabled;
-
-    // The debounced proximity sensor state.
-    private int mProximity = PROXIMITY_UNKNOWN;
-
-    // The raw non-debounced proximity sensor state.
-    private int mPendingProximity = PROXIMITY_UNKNOWN;
-
-    // -1 if fully debounced. Else, represents the time in ms when the debounce suspend blocker will
-    // be removed. Applies for both positive and negative proximity flips.
-    private long mPendingProximityDebounceTime = -1;
-
-    // True if the screen was turned off because of the proximity sensor.
-    // When the screen turns on again, we report user activity to the power manager.
-    private boolean mScreenOffBecauseOfProximity;
 
     // The currently active screen on unblocker.  This field is non-null whenever
     // we are waiting for a callback to release it and unblock the screen.
@@ -353,10 +290,7 @@
     private boolean mAppliedAutoBrightness;
     private boolean mAppliedDimming;
     private boolean mAppliedLowPower;
-    private boolean mAppliedScreenBrightnessOverride;
-    private boolean mAppliedTemporaryBrightness;
     private boolean mAppliedTemporaryAutoBrightnessAdjustment;
-    private boolean mAppliedBrightnessBoost;
     private boolean mAppliedThrottling;
 
     // Reason for which the brightness was last changed. See {@link BrightnessReason} for more
@@ -400,6 +334,8 @@
     private final BrightnessEvent mLastBrightnessEvent;
     private final BrightnessEvent mTempBrightnessEvent;
 
+    private final DisplayBrightnessController mDisplayBrightnessController;
+
     // Keeps a record of brightness changes for dumpsys.
     private RingBuffer<BrightnessEvent> mBrightnessEventRingBuffer;
 
@@ -407,6 +343,9 @@
     // a medium of communication between this class and the PowerManagerService.
     private final WakelockController mWakelockController;
 
+    // Tracks and manages the proximity state of the associated display.
+    private final DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+
     // A record of state for skipping brightness ramps.
     private int mSkipRampState = RAMP_STATE_SKIP_NONE;
 
@@ -445,14 +384,6 @@
     // behalf of the user.
     private float mCurrentScreenBrightnessSetting;
 
-    // The temporary screen brightness. Typically set when a user is interacting with the
-    // brightness slider but hasn't settled on a choice yet. Set to
-    // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set.
-    private float mTemporaryScreenBrightness;
-
-    // The current screen brightness while in VR mode.
-    private float mScreenBrightnessForVr;
-
     // The last auto brightness adjustment that was set by the user and not temporary. Set to
     // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
     private float mAutoBrightnessAdjustment;
@@ -478,6 +409,8 @@
 
     private DisplayDeviceConfig mDisplayDeviceConfig;
 
+    private boolean mIsEnabled;
+    private boolean mIsInTransition;
     /**
      * Creates the display power controller.
      */
@@ -491,13 +424,22 @@
         mClock = mInjector.getClock();
         mLogicalDisplay = logicalDisplay;
         mDisplayId = mLogicalDisplay.getDisplayIdLocked();
+        mSensorManager = sensorManager;
+        mHandler = new DisplayControllerHandler(handler.getLooper());
+        mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
+                .getDisplayDeviceConfig();
+        mIsEnabled = logicalDisplay.isEnabledLocked();
+        mIsInTransition = logicalDisplay.isInTransitionLocked();
         mWakelockController = mInjector.getWakelockController(mDisplayId, callbacks);
+        mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
+                () -> updatePowerState(), mDisplayId, mSensorManager);
         mTag = "DisplayPowerController2[" + mDisplayId + "]";
 
         mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
         mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
         mDisplayStatsId = mUniqueDisplayId.hashCode();
-        mHandler = new DisplayControllerHandler(handler.getLooper());
+
         mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
         mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
 
@@ -508,7 +450,6 @@
         }
 
         mSettingsObserver = new SettingsObserver(mHandler);
-        mSensorManager = sensorManager;
         mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
         mBlanker = blanker;
         mContext = context;
@@ -534,20 +475,6 @@
         mScreenBrightnessDefault = clampAbsoluteBrightness(
                 mLogicalDisplay.getDisplayInfoLocked().brightnessDefault);
 
-        // VR SETTINGS
-        mScreenBrightnessForVrDefault = clampAbsoluteBrightness(
-                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR));
-        mScreenBrightnessForVrRangeMaximum = clampAbsoluteBrightness(
-                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR));
-        mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
-                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
-
-        mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
-                R.bool.config_allowAutoBrightnessWhileDozing);
-
-        mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
-                .getDisplayDeviceConfig();
-
         loadBrightnessRampRates();
         mSkipScreenOnBrightnessRamp = resources.getBoolean(
                 R.bool.config_skipScreenOnBrightnessRamp);
@@ -611,12 +538,10 @@
         mBrightnessBucketsInDozeConfig = resources.getBoolean(
                 R.bool.config_displayBrightnessBucketsInDoze);
 
-        loadProximitySensor();
-
+        mDisplayBrightnessController =
+                new DisplayBrightnessController(context, null, mDisplayId);
         mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
-        mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
         mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
-        mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -653,7 +578,7 @@
      */
     @Override
     public boolean isProximitySensorAvailable() {
-        return mProximitySensor != null;
+        return mDisplayPowerProximityStateController.isProximitySensorAvailable();
     }
 
     /**
@@ -727,13 +652,8 @@
                 return true;
             }
 
-            boolean changed = false;
-
-            if (waitForNegativeProximity
-                    && !mPendingWaitForNegativeProximityLocked) {
-                mPendingWaitForNegativeProximityLocked = true;
-                changed = true;
-            }
+            boolean changed = mDisplayPowerProximityStateController
+                    .setPendingWaitForNegativeProximityLocked(waitForNegativeProximity);
 
             if (mPendingRequestLocked == null) {
                 mPendingRequestLocked = new DisplayPowerRequest(request);
@@ -783,29 +703,37 @@
         final DisplayDeviceConfig config = device.getDisplayDeviceConfig();
         final IBinder token = device.getDisplayTokenLocked();
         final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+        final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
+        final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
         mHandler.post(() -> {
+            boolean changed = false;
             if (mDisplayDevice != device) {
+                changed = true;
                 mDisplayDevice = device;
                 mUniqueDisplayId = uniqueId;
                 mDisplayStatsId = mUniqueDisplayId.hashCode();
                 mDisplayDeviceConfig = config;
                 loadFromDisplayDeviceConfig(token, info);
+                mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
+
+                // Since the underlying display-device changed, we really don't know the
+                // last command that was sent to change it's state. Lets assume it is unknown so
+                // that we trigger a change immediately.
+                mPowerState.resetScreenState();
+            }
+            if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
+                changed = true;
+                mIsEnabled = isEnabled;
+                mIsInTransition = isInTransition;
+            }
+
+            if (changed) {
                 updatePowerState();
             }
         });
     }
 
     /**
-     * Called when the displays are preparing to transition from one device state to another.
-     * This process involves turning off some displays so we need updatePowerState() to run and
-     * calculate the new state.
-     */
-    @Override
-    public void onDeviceStateTransition() {
-        sendUpdatePowerState();
-    }
-
-    /**
      * Unregisters all listeners and interrupts all running threads; halting future work.
      *
      * This method should be called when the DisplayPowerController2 is no longer in use; i.e. when
@@ -838,7 +766,6 @@
         // All properties that depend on the associated DisplayDevice and the DDC must be
         // updated here.
         loadBrightnessRampRates();
-        loadProximitySensor();
         loadNitsRange(mContext.getResources());
         setUpAutoBrightness(mContext.getResources(), mHandler);
         reloadReduceBrightColours();
@@ -903,7 +830,7 @@
 
         // Initialize all of the brightness tracking state
         final float brightness = convertToNits(mPowerState.getScreenBrightness());
-        if (brightness >= PowerManager.BRIGHTNESS_MIN) {
+        if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
             mBrightnessTracker.start(brightness);
         }
         mBrightnessSettingListener = brightnessValue -> {
@@ -913,9 +840,6 @@
 
         mBrightnessSetting.registerListener(mBrightnessSettingListener);
         mContext.getContentResolver().registerContentObserver(
-                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT),
-                false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
-        mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
                 false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
     }
@@ -1035,7 +959,9 @@
             }
 
             loadAmbientLightSensor();
-            if (mBrightnessTracker != null) {
+            // BrightnessTracker should only use one light sensor, we want to use the light sensor
+            // from the default display and not e.g. temporary displays when switching layouts.
+            if (mBrightnessTracker != null && mDisplayId == Display.DEFAULT_DISPLAY) {
                 mBrightnessTracker.setLightSensor(mLightSensor);
             }
 
@@ -1132,7 +1058,7 @@
 
     /** Clean up all resources that are accessed via the {@link #mHandler} thread. */
     private void cleanupHandlerThreadAfterStop() {
-        setProximitySensorEnabled(false);
+        mDisplayPowerProximityStateController.cleanup();
         mHbmController.stop();
         mBrightnessThrottler.stop();
         mHandler.removeCallbacksAndMessages(null);
@@ -1164,7 +1090,6 @@
         final int previousPolicy;
         boolean mustInitialize = false;
         int brightnessAdjustmentFlags = 0;
-        mBrightnessReasonTemp.set(null);
         mTempBrightnessEvent.reset();
         synchronized (mLock) {
             if (mStopped) {
@@ -1177,7 +1102,7 @@
 
             if (mPowerRequest == null) {
                 mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked);
-                updatePendingProximityRequestsLocked();
+                mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
                 mPendingRequestChangedLocked = false;
                 mustInitialize = true;
                 // Assume we're on and bright until told otherwise, since that's the state we turn
@@ -1186,7 +1111,7 @@
             } else if (mPendingRequestChangedLocked) {
                 previousPolicy = mPowerRequest.policy;
                 mPowerRequest.copyFrom(mPendingRequestLocked);
-                updatePendingProximityRequestsLocked();
+                mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
                 mPendingRequestChangedLocked = false;
                 mDisplayReadyLocked = false;
             } else {
@@ -1200,7 +1125,6 @@
         // We might override this below based on other factors.
         // Initialise brightness as invalid.
         int state;
-        float brightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         boolean performScreenOffTransition = false;
         switch (mPowerRequest.policy) {
             case DisplayPowerRequest.POLICY_OFF:
@@ -1213,13 +1137,6 @@
                 } else {
                     state = Display.STATE_DOZE;
                 }
-                if (!mAllowAutoBrightnessWhileDozingConfig) {
-                    brightnessState = mPowerRequest.dozeScreenBrightness;
-                    mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE);
-                }
-                break;
-            case DisplayPowerRequest.POLICY_VR:
-                state = Display.STATE_VR;
                 break;
             case DisplayPowerRequest.POLICY_DIM:
             case DisplayPowerRequest.POLICY_BRIGHT:
@@ -1229,55 +1146,11 @@
         }
         assert (state != Display.STATE_UNKNOWN);
 
-        boolean skipRampBecauseOfProximityChangeToNegative = false;
-        // Apply the proximity sensor.
-        if (mProximitySensor != null) {
-            if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) {
-                // At this point the policy says that the screen should be on, but we've been
-                // asked to listen to the prox sensor to adjust the display state, so lets make
-                // sure the sensor is on.
-                setProximitySensorEnabled(true);
-                if (!mScreenOffBecauseOfProximity
-                        && mProximity == PROXIMITY_POSITIVE
-                        && !mIgnoreProximityUntilChanged) {
-                    // Prox sensor already reporting "near" so we should turn off the screen.
-                    // Also checked that we aren't currently set to ignore the proximity sensor
-                    // temporarily.
-                    mScreenOffBecauseOfProximity = true;
-                    sendOnProximityPositiveWithWakelock();
-                }
-            } else if (mWaitingForNegativeProximity
-                    && mScreenOffBecauseOfProximity
-                    && mProximity == PROXIMITY_POSITIVE
-                    && state != Display.STATE_OFF) {
-                // The policy says that we should have the screen on, but it's off due to the prox
-                // and we've been asked to wait until the screen is far from the user to turn it
-                // back on. Let keep the prox sensor on so we can tell when it's far again.
-                setProximitySensorEnabled(true);
-            } else {
-                // We haven't been asked to use the prox sensor and we're not waiting on the screen
-                // to turn back on...so lets shut down the prox sensor.
-                setProximitySensorEnabled(false);
-                mWaitingForNegativeProximity = false;
-            }
+        mDisplayPowerProximityStateController.updateProximityState(mPowerRequest, state);
 
-            if (mScreenOffBecauseOfProximity
-                    && (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) {
-                // The screen *was* off due to prox being near, but now it's "far" so lets turn
-                // the screen back on.  Also turn it back on if we've been asked to ignore the
-                // prox sensor temporarily.
-                mScreenOffBecauseOfProximity = false;
-                skipRampBecauseOfProximityChangeToNegative = true;
-                sendOnProximityNegativeWithWakelock();
-            }
-        } else {
-            mWaitingForNegativeProximity = false;
-            mIgnoreProximityUntilChanged = false;
-        }
-
-        if (!mLogicalDisplay.isEnabled()
-                || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION
-                || mScreenOffBecauseOfProximity) {
+        if (!mIsEnabled
+                || mIsInTransition
+                || mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
             state = Display.STATE_OFF;
         }
 
@@ -1293,28 +1166,14 @@
         animateScreenStateChange(state, performScreenOffTransition);
         state = mPowerState.getScreenState();
 
-        if (state == Display.STATE_OFF) {
-            brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT;
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_SCREEN_OFF);
-        }
-
-        // Always use the VR brightness when in the VR state.
-        if (state == Display.STATE_VR) {
-            brightnessState = mScreenBrightnessForVr;
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_VR);
-        }
-
-        if ((Float.isNaN(brightnessState))
-                && isValidBrightnessValue(mPowerRequest.screenBrightnessOverride)) {
-            brightnessState = mPowerRequest.screenBrightnessOverride;
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_OVERRIDE);
-            mAppliedScreenBrightnessOverride = true;
-        } else {
-            mAppliedScreenBrightnessOverride = false;
-        }
+        DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController
+                .updateBrightness(mPowerRequest, state);
+        float brightnessState = displayBrightnessState.getBrightness();
+        mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
 
         final boolean autoBrightnessEnabledInDoze =
-                mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
+                mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()
+                        && Display.isDozeState(state);
         final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
                 && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
                 && Float.isNaN(brightnessState)
@@ -1329,16 +1188,6 @@
 
         final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
 
-        // Use the temporary screen brightness if there isn't an override, either from
-        // WindowManager or based on the display state.
-        if (isValidBrightnessValue(mTemporaryScreenBrightness)) {
-            brightnessState = mTemporaryScreenBrightness;
-            mAppliedTemporaryBrightness = true;
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_TEMPORARY);
-        } else {
-            mAppliedTemporaryBrightness = false;
-        }
-
         final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
 
         // Use the autobrightness adjustment override if set.
@@ -1352,18 +1201,6 @@
             brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO;
             mAppliedTemporaryAutoBrightnessAdjustment = false;
         }
-        // Apply brightness boost.
-        // We do this here after deciding whether auto-brightness is enabled so that we don't
-        // disable the light sensor during this temporary state.  That way when boost ends we will
-        // be able to resume normal auto-brightness behavior without any delay.
-        if (mPowerRequest.boostScreenBrightness
-                && brightnessState != PowerManager.BRIGHTNESS_OFF_FLOAT) {
-            brightnessState = PowerManager.BRIGHTNESS_MAX;
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_BOOST);
-            mAppliedBrightnessBoost = true;
-        } else {
-            mAppliedBrightnessBoost = false;
-        }
 
         // If the brightness is already set then it's been overridden by something other than the
         // user, or is a temporary adjustment.
@@ -1522,10 +1359,11 @@
                 mBrightnessThrottler.getBrightnessMaxReason());
 
         // Animate the screen brightness when the screen is on or dozing.
-        // Skip the animation when the screen is off or suspended or transition to/from VR.
+        // Skip the animation when the screen is off or suspended.
         boolean brightnessAdjusted = false;
         final boolean brightnessIsTemporary =
-                mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
+                (mBrightnessReason.getReason() == BrightnessReason.REASON_TEMPORARY)
+                        || mAppliedTemporaryAutoBrightnessAdjustment;
         if (!mPendingScreenOff) {
             if (mSkipScreenOnBrightnessRamp) {
                 if (state == Display.STATE_ON) {
@@ -1545,10 +1383,9 @@
                 }
             }
 
-            final boolean wasOrWillBeInVr =
-                    (state == Display.STATE_VR || oldState == Display.STATE_VR);
             final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState
-                    != RAMP_STATE_SKIP_NONE) || skipRampBecauseOfProximityChangeToNegative;
+                    != RAMP_STATE_SKIP_NONE) || mDisplayPowerProximityStateController
+                    .shouldSkipRampBecauseOfProximityChangeToNegative();
             // While dozing, sometimes the brightness is split into buckets. Rather than animating
             // through the buckets, which is unlikely to be smooth in the first place, just jump
             // right to the suggested brightness.
@@ -1588,7 +1425,7 @@
                     && (animateValue != currentBrightness
                     || sdrAnimateValue != currentSdrBrightness)) {
                 if (initialRampSkip || hasBrightnessBuckets
-                        || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) {
+                        || !isDisplayContentVisible || brightnessIsTemporary) {
                     animateScreenBrightness(animateValue, sdrAnimateValue,
                             SCREEN_ANIMATION_RATE_MINIMUM);
                 } else {
@@ -1662,6 +1499,7 @@
         mTempBrightnessEvent.setRbcStrength(mCdsi != null
                 ? mCdsi.getReduceBrightColorsStrength() : -1);
         mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
+        mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint);
         // Temporary is what we use during slider interactions. We avoid logging those so that
         // we don't spam logcat when the slider is being used.
         boolean tempToTempTransition =
@@ -1672,12 +1510,6 @@
                 || brightnessAdjustmentFlags != 0) {
             float lastBrightness = mLastBrightnessEvent.getBrightness();
             mTempBrightnessEvent.setInitialBrightness(lastBrightness);
-            mTempBrightnessEvent.setFastAmbientLux(
-                    mAutomaticBrightnessController == null
-                        ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
-            mTempBrightnessEvent.setSlowAmbientLux(
-                    mAutomaticBrightnessController == null
-                        ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
             mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
             mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
             BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
@@ -1768,7 +1600,7 @@
      */
     @Override
     public void ignoreProximitySensorUntilChanged() {
-        mHandler.sendEmptyMessage(MSG_IGNORE_PROXIMITY);
+        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChanged();
     }
 
     @Override
@@ -1934,7 +1766,7 @@
                 || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
             // If we are trying to turn screen off, give policy a chance to do something before we
             // actually turn the screen off.
-            if (isOff && !mScreenOffBecauseOfProximity) {
+            if (isOff && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
                 if (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_ON
                         || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
                     setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_OFF);
@@ -1964,7 +1796,7 @@
         // it is only removed once the window manager tells us that the activity has
         // finished drawing underneath.
         if (isOff && mReportedScreenStateToPolicy != REPORTED_TO_POLICY_SCREEN_OFF
-                && !mScreenOffBecauseOfProximity) {
+                && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
             setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
             unblockScreenOn();
             mWindowManagerPolicy.screenTurnedOff(mDisplayId);
@@ -2006,28 +1838,6 @@
                 fallbackType);
     }
 
-    private void loadProximitySensor() {
-        if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
-            return;
-        }
-        final DisplayDeviceConfig.SensorData proxSensor =
-                mDisplayDeviceConfig.getProximitySensor();
-        final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
-                ? Sensor.TYPE_PROXIMITY : SensorUtils.NO_FALLBACK;
-        mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
-                fallbackType);
-        if (mProximitySensor != null) {
-            mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
-                    TYPICAL_PROXIMITY_THRESHOLD);
-        }
-    }
-
-    private float clampScreenBrightnessForVr(float value) {
-        return MathUtils.constrain(
-                value, mScreenBrightnessForVrRangeMinimum,
-                mScreenBrightnessForVrRangeMaximum);
-    }
-
     private float clampScreenBrightness(float value) {
         if (Float.isNaN(value)) {
             value = PowerManager.BRIGHTNESS_MIN;
@@ -2116,23 +1926,6 @@
                 mPowerState.setColorFadeLevel(1.0f);
                 mPowerState.dismissColorFade();
             }
-        } else if (target == Display.STATE_VR) {
-            // Wait for brightness animation to complete beforehand when entering VR
-            // from screen on to prevent a perceptible jump because brightness may operate
-            // differently when the display is configured for dozing.
-            if (mScreenBrightnessRampAnimator.isAnimating()
-                    && mPowerState.getScreenState() == Display.STATE_ON) {
-                return;
-            }
-
-            // Set screen state.
-            if (!setScreenState(Display.STATE_VR)) {
-                return; // screen on blocked
-            }
-
-            // Dismiss the black surface without fanfare.
-            mPowerState.setColorFadeLevel(1.0f);
-            mPowerState.dismissColorFade();
         } else if (target == Display.STATE_DOZE) {
             // Want screen dozing.
             // Wait for brightness animation to complete beforehand when entering doze
@@ -2223,98 +2016,6 @@
 
     private final Runnable mCleanListener = this::sendUpdatePowerState;
 
-    private void setProximitySensorEnabled(boolean enable) {
-        if (enable) {
-            if (!mProximitySensorEnabled) {
-                // Register the listener.
-                // Proximity sensor state already cleared initially.
-                mProximitySensorEnabled = true;
-                mIgnoreProximityUntilChanged = false;
-                mSensorManager.registerListener(mProximitySensorListener, mProximitySensor,
-                        SensorManager.SENSOR_DELAY_NORMAL, mHandler);
-            }
-        } else {
-            if (mProximitySensorEnabled) {
-                // Unregister the listener.
-                // Clear the proximity sensor state for next time.
-                mProximitySensorEnabled = false;
-                mProximity = PROXIMITY_UNKNOWN;
-                mIgnoreProximityUntilChanged = false;
-                mPendingProximity = PROXIMITY_UNKNOWN;
-                mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
-                mSensorManager.unregisterListener(mProximitySensorListener);
-                // release wake lock(must be last)
-                boolean proxDebounceSuspendBlockerReleased =
-                        mWakelockController.releaseWakelock(
-                                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
-                if (proxDebounceSuspendBlockerReleased) {
-                    mPendingProximityDebounceTime = -1;
-                }
-            }
-        }
-    }
-
-    private void handleProximitySensorEvent(long time, boolean positive) {
-        if (mProximitySensorEnabled) {
-            if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) {
-                return; // no change
-            }
-            if (mPendingProximity == PROXIMITY_POSITIVE && positive) {
-                return; // no change
-            }
-
-            // Only accept a proximity sensor reading if it remains
-            // stable for the entire debounce delay.  We hold a wake lock while
-            // debouncing the sensor.
-            mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
-            if (positive) {
-                mPendingProximity = PROXIMITY_POSITIVE;
-                mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY;
-                mWakelockController.acquireWakelock(
-                        WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE); // acquire wake lock
-            } else {
-                mPendingProximity = PROXIMITY_NEGATIVE;
-                mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY;
-                mWakelockController.acquireWakelock(
-                        WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE); // acquire wake lock
-            }
-
-            // Debounce the new sensor reading.
-            debounceProximitySensor();
-        }
-    }
-
-    private void debounceProximitySensor() {
-        if (mProximitySensorEnabled
-                && mPendingProximity != PROXIMITY_UNKNOWN
-                && mPendingProximityDebounceTime >= 0) {
-            final long now = mClock.uptimeMillis();
-            if (mPendingProximityDebounceTime <= now) {
-                if (mProximity != mPendingProximity) {
-                    // if the status of the sensor changed, stop ignoring.
-                    mIgnoreProximityUntilChanged = false;
-                    Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]");
-                }
-                // Sensor reading accepted.  Apply the change then release the wake lock.
-                mProximity = mPendingProximity;
-                updatePowerState();
-                // (must be last)
-                boolean proxDebounceSuspendBlockerReleased =
-                        mWakelockController.releaseWakelock(
-                                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
-                if (proxDebounceSuspendBlockerReleased) {
-                    mPendingProximityDebounceTime = -1;
-                }
-
-            } else {
-                // Need to wait a little longer.
-                // Debounce again later.  We continue holding a wake lock while waiting.
-                Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED);
-                mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime);
-            }
-        }
-    }
-
     private void sendOnStateChangedWithWakelock() {
         boolean wakeLockAcquired = mWakelockController.acquireWakelock(
                 WakelockController.WAKE_LOCK_STATE_CHANGED);
@@ -2341,9 +2042,6 @@
                 mAutomaticBrightnessController.resetShortTermModel();
             }
         }
-        // We don't bother with a pending variable for VR screen brightness since we just
-        // immediately adapt to it.
-        mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
         sendUpdatePowerState();
     }
 
@@ -2362,13 +2060,6 @@
         return clampAbsoluteBrightness(brightness);
     }
 
-    private float getScreenBrightnessForVrSetting() {
-        final float brightnessFloat = Settings.System.getFloatForUser(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT, mScreenBrightnessForVrDefault,
-                UserHandle.USER_CURRENT);
-        return clampScreenBrightnessForVr(brightnessFloat);
-    }
-
     @Override
     public void setBrightness(float brightnessValue) {
         // Update the setting, which will eventually call back into DPC to have us actually update
@@ -2425,13 +2116,15 @@
         }
         if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
             mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-            mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+            mDisplayBrightnessController
+                    .setTemporaryBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT);
             return false;
         }
         setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
         mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
         mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mDisplayBrightnessController
+                .setTemporaryBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT);
         return true;
     }
 
@@ -2439,7 +2132,7 @@
             boolean hadUserDataPoint) {
         final float brightnessInNits = convertToNits(brightness);
         if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
-                && mAutomaticBrightnessController != null) {
+                && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
             // We only want to track changes on devices that can actually map the display backlight
             // values into a physical brightness unit since the value provided by the API is in
             // nits and not using the arbitrary backlight units.
@@ -2448,7 +2141,9 @@
                     : 1.0f;
             mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
                     powerFactor, hadUserDataPoint,
-                    mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId);
+                    mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
+                    mAutomaticBrightnessController.getLastSensorValues(),
+                    mAutomaticBrightnessController.getLastSensorTimestamps());
         }
     }
 
@@ -2459,39 +2154,6 @@
         return mAutomaticBrightnessController.convertToNits(brightness);
     }
 
-    @GuardedBy("mLock")
-    private void updatePendingProximityRequestsLocked() {
-        mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
-        mPendingWaitForNegativeProximityLocked = false;
-
-        if (mIgnoreProximityUntilChanged) {
-            // Also, lets stop waiting for negative proximity if we're ignoring it.
-            mWaitingForNegativeProximity = false;
-        }
-    }
-
-    private void ignoreProximitySensorUntilChangedInternal() {
-        if (!mIgnoreProximityUntilChanged
-                && mProximity == PROXIMITY_POSITIVE) {
-            // Only ignore if it is still reporting positive (near)
-            mIgnoreProximityUntilChanged = true;
-            Slog.i(mTag, "Ignoring proximity");
-            updatePowerState();
-        }
-    }
-
-    private void sendOnProximityPositiveWithWakelock() {
-        mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
-        mHandler.post(mWakelockController.getOnProximityPositiveRunnable());
-    }
-
-
-    private void sendOnProximityNegativeWithWakelock() {
-        mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
-        mHandler.post(mWakelockController.getOnProximityNegativeRunnable());
-    }
-
-
     @Override
     public void dump(final PrintWriter pw) {
         synchronized (mLock) {
@@ -2505,8 +2167,6 @@
             pw.println("  mDisplayReadyLocked=" + mDisplayReadyLocked);
             pw.println("  mPendingRequestLocked=" + mPendingRequestLocked);
             pw.println("  mPendingRequestChangedLocked=" + mPendingRequestChangedLocked);
-            pw.println("  mPendingWaitForNegativeProximityLocked="
-                    + mPendingWaitForNegativeProximityLocked);
             pw.println("  mPendingUpdatePowerStateLocked=" + mPendingUpdatePowerStateLocked);
         }
 
@@ -2515,12 +2175,7 @@
         pw.println("  mScreenBrightnessRangeDefault=" + mScreenBrightnessDefault);
         pw.println("  mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
         pw.println("  mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
-        pw.println("  mScreenBrightnessForVrRangeMinimum=" + mScreenBrightnessForVrRangeMinimum);
-        pw.println("  mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum);
-        pw.println("  mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault);
         pw.println("  mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
-        pw.println("  mAllowAutoBrightnessWhileDozingConfig="
-                + mAllowAutoBrightnessWhileDozingConfig);
         pw.println("  mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
         pw.println("  mColorFadeFadesConfig=" + mColorFadeFadesConfig);
         pw.println("  mColorFadeEnabled=" + mColorFadeEnabled);
@@ -2541,7 +2196,6 @@
         }
         pw.println("  mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
         pw.println("  mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
-
         mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
     }
 
@@ -2549,33 +2203,19 @@
         pw.println();
         pw.println("Display Power Controller Thread State:");
         pw.println("  mPowerRequest=" + mPowerRequest);
-        pw.println("  mWaitingForNegativeProximity=" + mWaitingForNegativeProximity);
-        pw.println("  mProximitySensor=" + mProximitySensor);
-        pw.println("  mProximitySensorEnabled=" + mProximitySensorEnabled);
-        pw.println("  mProximityThreshold=" + mProximityThreshold);
-        pw.println("  mProximity=" + proximityToString(mProximity));
-        pw.println("  mPendingProximity=" + proximityToString(mPendingProximity));
-        pw.println("  mPendingProximityDebounceTime="
-                + TimeUtils.formatUptime(mPendingProximityDebounceTime));
-        pw.println("  mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
         pw.println("  mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness);
         pw.println("  mPendingScreenBrightnessSetting="
                 + mPendingScreenBrightnessSetting);
-        pw.println("  mTemporaryScreenBrightness=" + mTemporaryScreenBrightness);
         pw.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
         pw.println("  mBrightnessReason=" + mBrightnessReason);
         pw.println("  mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
         pw.println("  mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
-        pw.println("  mScreenBrightnessForVrFloat=" + mScreenBrightnessForVr);
         pw.println("  mAppliedAutoBrightness=" + mAppliedAutoBrightness);
         pw.println("  mAppliedDimming=" + mAppliedDimming);
         pw.println("  mAppliedLowPower=" + mAppliedLowPower);
         pw.println("  mAppliedThrottling=" + mAppliedThrottling);
-        pw.println("  mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride);
-        pw.println("  mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness);
         pw.println("  mAppliedTemporaryAutoBrightnessAdjustment="
                 + mAppliedTemporaryAutoBrightnessAdjustment);
-        pw.println("  mAppliedBrightnessBoost=" + mAppliedBrightnessBoost);
         pw.println("  mDozing=" + mDozing);
         pw.println("  mSkipRampState=" + skipRampStateToString(mSkipRampState));
         pw.println("  mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
@@ -2629,21 +2269,18 @@
         if (mWakelockController != null) {
             mWakelockController.dumpLocal(pw);
         }
-    }
 
-    private static String proximityToString(int state) {
-        switch (state) {
-            case PROXIMITY_UNKNOWN:
-                return "Unknown";
-            case PROXIMITY_NEGATIVE:
-                return "Negative";
-            case PROXIMITY_POSITIVE:
-                return "Positive";
-            default:
-                return Integer.toString(state);
+        pw.println();
+        if (mDisplayBrightnessController != null) {
+            mDisplayBrightnessController.dump(pw);
+        }
+
+        if (mDisplayPowerProximityStateController != null) {
+            mDisplayPowerProximityStateController.dumpLocal(pw);
         }
     }
 
+
     private static String reportedToPolicyToString(int state) {
         switch (state) {
             case REPORTED_TO_POLICY_SCREEN_OFF:
@@ -2766,19 +2403,22 @@
         float appliedThermalCapNits =
                 event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
                 ? -1f : convertToNits(event.getThermalMax());
-
-        FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
-                convertToNits(event.getInitialBrightness()),
-                convertToNits(event.getBrightness()),
-                event.getSlowAmbientLux(),
-                event.getPhysicalDisplayId(),
-                event.isShortTermModelActive(),
-                appliedLowPowerMode,
-                appliedRbcStrength,
-                appliedHbmMaxNits,
-                appliedThermalCapNits,
-                event.isAutomaticBrightnessEnabled(),
-                FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+        if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
+                && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
+                .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) {
+            FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+                    convertToNits(event.getInitialBrightness()),
+                    convertToNits(event.getBrightness()),
+                    event.getLux(),
+                    event.getPhysicalDisplayId(),
+                    event.wasShortTermModelActive(),
+                    appliedLowPowerMode,
+                    appliedRbcStrength,
+                    appliedHbmMaxNits,
+                    appliedThermalCapNits,
+                    event.isAutomaticBrightnessEnabled(),
+                    FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+        }
     }
 
     private final class DisplayControllerHandler extends Handler {
@@ -2793,10 +2433,6 @@
                     updatePowerState();
                     break;
 
-                case MSG_PROXIMITY_SENSOR_DEBOUNCED:
-                    debounceProximitySensor();
-                    break;
-
                 case MSG_SCREEN_ON_UNBLOCKED:
                     if (mPendingScreenOnUnblocker == msg.obj) {
                         unblockScreenOn();
@@ -2816,7 +2452,8 @@
 
                 case MSG_SET_TEMPORARY_BRIGHTNESS:
                     // TODO: Should we have a a timeout for the temporary brightness?
-                    mTemporaryScreenBrightness = Float.intBitsToFloat(msg.arg1);
+                    mDisplayBrightnessController
+                            .setTemporaryBrightness(Float.intBitsToFloat(msg.arg1));
                     updatePowerState();
                     break;
 
@@ -2825,10 +2462,6 @@
                     updatePowerState();
                     break;
 
-                case MSG_IGNORE_PROXIMITY:
-                    ignoreProximitySensorUntilChangedInternal();
-                    break;
-
                 case MSG_STOP:
                     cleanupHandlerThreadAfterStop();
                     break;
@@ -2858,23 +2491,6 @@
         }
     }
 
-    private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
-        @Override
-        public void onSensorChanged(SensorEvent event) {
-            if (mProximitySensorEnabled) {
-                final long time = mClock.uptimeMillis();
-                final float distance = event.values[0];
-                boolean positive = distance >= 0.0f && distance < mProximityThreshold;
-                handleProximitySensorEvent(time, positive);
-            }
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-            // Not used.
-        }
-    };
-
 
     private final class SettingsObserver extends ContentObserver {
         SettingsObserver(Handler handler) {
@@ -2964,6 +2580,15 @@
                 DisplayPowerCallbacks displayPowerCallbacks) {
             return new WakelockController(displayId, displayPowerCallbacks);
         }
+
+        DisplayPowerProximityStateController getDisplayPowerProximityStateController(
+                WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
+                Looper looper, Runnable nudgeUpdatePowerState,
+                int displayId, SensorManager sensorManager) {
+            return new DisplayPowerProximityStateController(wakelockController, displayDeviceConfig,
+                    looper, nudgeUpdatePowerState,
+                    displayId, sensorManager, /* injector= */ null);
+        }
     }
 
     static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 6677f35..46f1343 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -45,11 +45,6 @@
     void stop();
 
     /**
-     * Used to manage the displays preparing to transition from one device state to another.
-     */
-    void onDeviceStateTransition();
-
-    /**
      * Used to update the display's BrightnessConfiguration
      * @param config The new BrightnessConfiguration
      */
diff --git a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
new file mode 100644
index 0000000..a3433d9
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
@@ -0,0 +1,550 @@
+/*
+ * 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.display;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.utils.SensorUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Maintains the proximity state of the display.
+ * Internally listens for proximity updates and schedules a power state update when the proximity
+ * state changes.
+ */
+public final class DisplayPowerProximityStateController {
+    @VisibleForTesting
+    static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 1;
+    @VisibleForTesting
+    static final int PROXIMITY_UNKNOWN = -1;
+    @VisibleForTesting
+    static final int PROXIMITY_POSITIVE = 1;
+    @VisibleForTesting
+    static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
+
+    private static final int MSG_IGNORE_PROXIMITY = 2;
+
+    private static final int PROXIMITY_NEGATIVE = 0;
+
+    private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
+    // Proximity sensor debounce delay in milliseconds for positive transitions.
+
+    // Proximity sensor debounce delay in milliseconds for negative transitions.
+    private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
+    // Trigger proximity if distance is less than 5 cm.
+    private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
+
+    private final String mTag;
+    // A lock to handle the deadlock and race conditions.
+    private final Object mLock = new Object();
+    // The manager which lets us access the device's ProximitySensor
+    private final SensorManager mSensorManager;
+    // An entity which manages the wakelocks.
+    private final WakelockController mWakelockController;
+    // A handler to process all the events on this thread in a synchronous manner
+    private final DisplayPowerProximityStateHandler mHandler;
+    // A runnable to execute the utility to update the power state.
+    private final Runnable mNudgeUpdatePowerState;
+    private Clock mClock;
+    // A listener which listen's to the events emitted by the proximity sensor.
+    private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            if (mProximitySensorEnabled) {
+                final long time = mClock.uptimeMillis();
+                final float distance = event.values[0];
+                boolean positive = distance >= 0.0f && distance < mProximityThreshold;
+                handleProximitySensorEvent(time, positive);
+            }
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            // Not used.
+        }
+    };
+
+    // The proximity sensor, or null if not available or needed.
+    private Sensor mProximitySensor;
+
+    // The configurations for the associated display
+    private DisplayDeviceConfig mDisplayDeviceConfig;
+
+    // True if a request has been made to wait for the proximity sensor to go negative.
+    @GuardedBy("mLock")
+    private boolean mPendingWaitForNegativeProximityLocked;
+
+    // True if the device should wait for negative proximity sensor before
+    // waking up the screen.  This is set to false as soon as a negative
+    // proximity sensor measurement is observed or when the device is forced to
+    // go to sleep by the user.  While true, the screen remains off.
+    private boolean mWaitingForNegativeProximity;
+
+    // True if the device should not take into account the proximity sensor
+    // until either the proximity sensor state changes, or there is no longer a
+    // request to listen to proximity sensor.
+    private boolean mIgnoreProximityUntilChanged;
+
+    // Set to true if the proximity sensor listener has been registered
+    // with the sensor manager.
+    private boolean mProximitySensorEnabled;
+
+    // The raw non-debounced proximity sensor state.
+    private int mPendingProximity = PROXIMITY_UNKNOWN;
+
+    // -1 if fully debounced. Else, represents the time in ms when the debounce suspend blocker will
+    // be removed. Applies for both positive and negative proximity flips.
+    private long mPendingProximityDebounceTime = -1;
+
+    // True if the screen was turned off because of the proximity sensor.
+    // When the screen turns on again, we report user activity to the power manager.
+    private boolean mScreenOffBecauseOfProximity;
+
+    // The debounced proximity sensor state.
+    private int mProximity = PROXIMITY_UNKNOWN;
+
+    // The actual proximity sensor threshold value.
+    private float mProximityThreshold;
+
+    // A flag representing if the ramp is to be skipped when the proximity changes from positive
+    // to negative
+    private boolean mSkipRampBecauseOfProximityChangeToNegative = false;
+
+    // The DisplayId of the associated Logical Display.
+    private int mDisplayId;
+
+    /**
+     * Create a new instance of DisplayPowerProximityStateController.
+     *
+     * @param wakeLockController    WakelockController used to acquire/release wakelocks
+     * @param displayDeviceConfig   DisplayDeviceConfig instance from which the configs(Proximity
+     *                              Sensor) are to be loaded
+     * @param looper                A looper onto which the handler is to be associated.
+     * @param nudgeUpdatePowerState A runnable to execute the utility to update the power state
+     * @param displayId             The DisplayId of the associated Logical Display.
+     * @param sensorManager         The manager which lets us access the display's ProximitySensor
+     */
+    public DisplayPowerProximityStateController(
+            WakelockController wakeLockController, DisplayDeviceConfig displayDeviceConfig,
+            Looper looper,
+            Runnable nudgeUpdatePowerState, int displayId, SensorManager sensorManager,
+            Injector injector) {
+        if (injector == null) {
+            injector = new Injector();
+        }
+        mClock = injector.createClock();
+        mWakelockController = wakeLockController;
+        mHandler = new DisplayPowerProximityStateHandler(looper);
+        mNudgeUpdatePowerState = nudgeUpdatePowerState;
+        mDisplayDeviceConfig = displayDeviceConfig;
+        mDisplayId = displayId;
+        mTag = "DisplayPowerProximityStateController[" + mDisplayId + "]";
+        mSensorManager = sensorManager;
+        loadProximitySensor();
+    }
+
+    /**
+     * Manages the pending state of the proximity.
+     */
+    public void updatePendingProximityRequestsLocked() {
+        synchronized (mLock) {
+            mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
+            mPendingWaitForNegativeProximityLocked = false;
+
+            if (mIgnoreProximityUntilChanged) {
+                // Also, lets stop waiting for negative proximity if we're ignoring it.
+                mWaitingForNegativeProximity = false;
+            }
+        }
+    }
+
+    /**
+     * Clean up all resources that are accessed via the {@link #mHandler} thread.
+     */
+    public void cleanup() {
+        setProximitySensorEnabled(false);
+    }
+
+    /**
+     * Returns true if the proximity sensor screen-off function is available.
+     */
+    public boolean isProximitySensorAvailable() {
+        return mProximitySensor != null;
+    }
+
+    /**
+     * Sets the flag to indicate that the system is waiting for the negative proximity event
+     */
+    public boolean setPendingWaitForNegativeProximityLocked(
+            boolean requestWaitForNegativeProximity) {
+        synchronized (mLock) {
+            if (requestWaitForNegativeProximity
+                    && !mPendingWaitForNegativeProximityLocked) {
+                mPendingWaitForNegativeProximityLocked = true;
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Updates the proximity state of the display, based on the newly received DisplayPowerRequest
+     * and the target display state
+     */
+    public void updateProximityState(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
+            int displayState) {
+        mSkipRampBecauseOfProximityChangeToNegative = false;
+        if (mProximitySensor != null) {
+            if (displayPowerRequest.useProximitySensor && displayState != Display.STATE_OFF) {
+                // At this point the policy says that the screen should be on, but we've been
+                // asked to listen to the prox sensor to adjust the display state, so lets make
+                // sure the sensor is on.
+                setProximitySensorEnabled(true);
+                if (!mScreenOffBecauseOfProximity
+                        && mProximity == PROXIMITY_POSITIVE
+                        && !mIgnoreProximityUntilChanged) {
+                    // Prox sensor already reporting "near" so we should turn off the screen.
+                    // Also checked that we aren't currently set to ignore the proximity sensor
+                    // temporarily.
+                    mScreenOffBecauseOfProximity = true;
+                    sendOnProximityPositiveWithWakelock();
+                }
+            } else if (mWaitingForNegativeProximity
+                    && mScreenOffBecauseOfProximity
+                    && mProximity == PROXIMITY_POSITIVE
+                    && displayState != Display.STATE_OFF) {
+                // The policy says that we should have the screen on, but it's off due to the prox
+                // and we've been asked to wait until the screen is far from the user to turn it
+                // back on. Let keep the prox sensor on so we can tell when it's far again.
+                setProximitySensorEnabled(true);
+            } else {
+                // We haven't been asked to use the prox sensor and we're not waiting on the screen
+                // to turn back on...so let's shut down the prox sensor.
+                setProximitySensorEnabled(false);
+                mWaitingForNegativeProximity = false;
+            }
+            if (mScreenOffBecauseOfProximity
+                    && (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) {
+                // The screen *was* off due to prox being near, but now it's "far" so lets turn
+                // the screen back on.  Also turn it back on if we've been asked to ignore the
+                // prox sensor temporarily.
+                mScreenOffBecauseOfProximity = false;
+                mSkipRampBecauseOfProximityChangeToNegative = true;
+                sendOnProximityNegativeWithWakelock();
+            }
+        } else {
+            mWaitingForNegativeProximity = false;
+            mIgnoreProximityUntilChanged = false;
+        }
+    }
+
+    /**
+     * A utility to check if the brightness change ramp is to be skipped because the proximity was
+     * changed from positive to negative.
+     */
+    public boolean shouldSkipRampBecauseOfProximityChangeToNegative() {
+        return mSkipRampBecauseOfProximityChangeToNegative;
+    }
+
+    /**
+     * Represents of the screen is currently turned off because of the proximity state.
+     */
+    public boolean isScreenOffBecauseOfProximity() {
+        return mScreenOffBecauseOfProximity;
+    }
+
+    /**
+     * Ignores the proximity sensor until the sensor state changes, but only if the sensor is
+     * currently enabled and forcing the screen to be dark.
+     */
+    public void ignoreProximitySensorUntilChanged() {
+        mHandler.sendEmptyMessage(MSG_IGNORE_PROXIMITY);
+    }
+
+    /**
+     * This adjusts the state of this class when a change in the DisplayDevice is detected.
+     */
+    public void notifyDisplayDeviceChanged(DisplayDeviceConfig displayDeviceConfig) {
+        this.mDisplayDeviceConfig = displayDeviceConfig;
+        loadProximitySensor();
+    }
+
+    /**
+     * Used to dump the state.
+     *
+     * @param pw The PrintWriter used to dump the state.
+     */
+    public void dumpLocal(PrintWriter pw) {
+        pw.println();
+        pw.println("DisplayPowerProximityStateController:");
+        synchronized (mLock) {
+            pw.println("  mPendingWaitForNegativeProximityLocked="
+                    + mPendingWaitForNegativeProximityLocked);
+        }
+        pw.println("  mDisplayId=" + mDisplayId);
+        pw.println("  mWaitingForNegativeProximity=" + mWaitingForNegativeProximity);
+        pw.println("  mIgnoreProximityUntilChanged=" + mIgnoreProximityUntilChanged);
+        pw.println("  mProximitySensor=" + mProximitySensor);
+        pw.println("  mProximitySensorEnabled=" + mProximitySensorEnabled);
+        pw.println("  mProximityThreshold=" + mProximityThreshold);
+        pw.println("  mProximity=" + proximityToString(mProximity));
+        pw.println("  mPendingProximity=" + proximityToString(mPendingProximity));
+        pw.println("  mPendingProximityDebounceTime="
+                + TimeUtils.formatUptime(mPendingProximityDebounceTime));
+        pw.println("  mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
+        pw.println("  mSkipRampBecauseOfProximityChangeToNegative="
+                + mSkipRampBecauseOfProximityChangeToNegative);
+    }
+
+    void ignoreProximitySensorUntilChangedInternal() {
+        if (!mIgnoreProximityUntilChanged
+                && mProximity == PROXIMITY_POSITIVE) {
+            // Only ignore if it is still reporting positive (near)
+            mIgnoreProximityUntilChanged = true;
+            Slog.i(mTag, "Ignoring proximity");
+            mNudgeUpdatePowerState.run();
+        }
+    }
+
+    private void sendOnProximityPositiveWithWakelock() {
+        mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
+        mHandler.post(mWakelockController.getOnProximityPositiveRunnable());
+    }
+
+    private void sendOnProximityNegativeWithWakelock() {
+        mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
+        mHandler.post(mWakelockController.getOnProximityNegativeRunnable());
+    }
+
+    private void loadProximitySensor() {
+        if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+            return;
+        }
+        final DisplayDeviceConfig.SensorData proxSensor =
+                mDisplayDeviceConfig.getProximitySensor();
+        final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
+                ? Sensor.TYPE_PROXIMITY : SensorUtils.NO_FALLBACK;
+        mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
+                fallbackType);
+        if (mProximitySensor != null) {
+            mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
+                    TYPICAL_PROXIMITY_THRESHOLD);
+        }
+    }
+
+    private void setProximitySensorEnabled(boolean enable) {
+        if (enable) {
+            if (!mProximitySensorEnabled) {
+                // Register the listener.
+                // Proximity sensor state already cleared initially.
+                mProximitySensorEnabled = true;
+                mIgnoreProximityUntilChanged = false;
+                mSensorManager.registerListener(mProximitySensorListener, mProximitySensor,
+                        SensorManager.SENSOR_DELAY_NORMAL, mHandler);
+            }
+        } else {
+            if (mProximitySensorEnabled) {
+                // Unregister the listener.
+                // Clear the proximity sensor state for next time.
+                mProximitySensorEnabled = false;
+                mProximity = PROXIMITY_UNKNOWN;
+                mIgnoreProximityUntilChanged = false;
+                mPendingProximity = PROXIMITY_UNKNOWN;
+                mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+                mSensorManager.unregisterListener(mProximitySensorListener);
+                // release wake lock(must be last)
+                boolean proxDebounceSuspendBlockerReleased =
+                        mWakelockController.releaseWakelock(
+                                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+                if (proxDebounceSuspendBlockerReleased) {
+                    mPendingProximityDebounceTime = -1;
+                }
+            }
+        }
+    }
+
+    private void handleProximitySensorEvent(long time, boolean positive) {
+        if (mProximitySensorEnabled) {
+            if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) {
+                return; // no change
+            }
+            if (mPendingProximity == PROXIMITY_POSITIVE && positive) {
+                return; // no change
+            }
+
+            // Only accept a proximity sensor reading if it remains
+            // stable for the entire debounce delay.  We hold a wake lock while
+            // debouncing the sensor.
+            mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+            if (positive) {
+                mPendingProximity = PROXIMITY_POSITIVE;
+                mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY;
+                mWakelockController.acquireWakelock(
+                        WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE); // acquire wake lock
+            } else {
+                mPendingProximity = PROXIMITY_NEGATIVE;
+                mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY;
+                mWakelockController.acquireWakelock(
+                        WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE); // acquire wake lock
+            }
+
+            // Debounce the new sensor reading.
+            debounceProximitySensor();
+        }
+    }
+
+    private void debounceProximitySensor() {
+        if (mProximitySensorEnabled
+                && mPendingProximity != PROXIMITY_UNKNOWN
+                && mPendingProximityDebounceTime >= 0) {
+            final long now = mClock.uptimeMillis();
+            if (mPendingProximityDebounceTime <= now) {
+                if (mProximity != mPendingProximity) {
+                    // if the status of the sensor changed, stop ignoring.
+                    mIgnoreProximityUntilChanged = false;
+                    Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]");
+                }
+                // Sensor reading accepted.  Apply the change then release the wake lock.
+                mProximity = mPendingProximity;
+                mNudgeUpdatePowerState.run();
+                // (must be last)
+                boolean proxDebounceSuspendBlockerReleased =
+                        mWakelockController.releaseWakelock(
+                                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+                if (proxDebounceSuspendBlockerReleased) {
+                    mPendingProximityDebounceTime = -1;
+                }
+
+            } else {
+                // Need to wait a little longer.
+                // Debounce again later.  We continue holding a wake lock while waiting.
+                Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+                mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime);
+            }
+        }
+    }
+
+    private class DisplayPowerProximityStateHandler extends Handler {
+        DisplayPowerProximityStateHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_PROXIMITY_SENSOR_DEBOUNCED:
+                    debounceProximitySensor();
+                    break;
+
+                case MSG_IGNORE_PROXIMITY:
+                    ignoreProximitySensorUntilChangedInternal();
+                    break;
+            }
+        }
+    }
+
+    private String proximityToString(int state) {
+        switch (state) {
+            case PROXIMITY_UNKNOWN:
+                return "Unknown";
+            case PROXIMITY_NEGATIVE:
+                return "Negative";
+            case PROXIMITY_POSITIVE:
+                return "Positive";
+            default:
+                return Integer.toString(state);
+        }
+    }
+
+    @VisibleForTesting
+    boolean getPendingWaitForNegativeProximityLocked() {
+        synchronized (mLock) {
+            return mPendingWaitForNegativeProximityLocked;
+        }
+    }
+
+    @VisibleForTesting
+    boolean getWaitingForNegativeProximity() {
+        return mWaitingForNegativeProximity;
+    }
+
+    @VisibleForTesting
+    boolean shouldIgnoreProximityUntilChanged() {
+        return mIgnoreProximityUntilChanged;
+    }
+
+    boolean isProximitySensorEnabled() {
+        return mProximitySensorEnabled;
+    }
+
+    @VisibleForTesting
+    Handler getHandler() {
+        return mHandler;
+    }
+
+    @VisibleForTesting
+    int getPendingProximity() {
+        return mPendingProximity;
+    }
+
+    @VisibleForTesting
+    int getProximity() {
+        return mProximity;
+    }
+
+
+    @VisibleForTesting
+    long getPendingProximityDebounceTime() {
+        return mPendingProximityDebounceTime;
+    }
+
+    @VisibleForTesting
+    SensorEventListener getProximitySensorListener() {
+        return mProximitySensorListener;
+    }
+
+    /** Functional interface for providing time. */
+    @VisibleForTesting
+    interface Clock {
+        /**
+         * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+         */
+        long uptimeMillis();
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        Clock createClock() {
+            return () -> SystemClock.uptimeMillis();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 2f22d33..f650b11 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -145,7 +145,7 @@
     public void setScreenState(int state) {
         if (mScreenState != state) {
             if (DEBUG) {
-                Slog.d(TAG, "setScreenState: state=" + state);
+                Slog.w(TAG, "setScreenState: state=" + Display.stateToString(state));
             }
 
             mScreenState = state;
@@ -339,6 +339,15 @@
         if (mColorFade != null) mColorFade.dump(pw);
     }
 
+    /**
+     * Resets the screen state to unknown. Useful when the underlying display-device changes for the
+     * LogicalDisplay and we do not know the last state that was sent to it.
+     */
+    void resetScreenState() {
+        mScreenState = Display.STATE_UNKNOWN;
+        mScreenReady = false;
+    }
+
     private void scheduleScreenUpdate() {
         if (!mScreenUpdatePending) {
             mScreenUpdatePending = true;
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 2c2075d..ee53b60 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -38,6 +38,7 @@
 import android.view.DisplayAddress;
 import android.view.DisplayCutout;
 import android.view.DisplayEventReceiver;
+import android.view.DisplayShape;
 import android.view.RoundedCorners;
 import android.view.SurfaceControl;
 
@@ -209,7 +210,7 @@
         private int mUserPreferredModeId = INVALID_MODE_ID;
         // This is used only for the purpose of testing, to verify if the mode was correct when the
         // device started or booted.
-        private int mActiveDisplayModeAtStartId = INVALID_MODE_ID;
+        private int mActiveSfDisplayModeAtStartId = INVALID_MODE_ID;
         private Display.Mode mUserPreferredMode;
         private int mActiveModeId = INVALID_MODE_ID;
         private boolean mDisplayModeSpecsInvalid;
@@ -225,6 +226,8 @@
         private SurfaceControl.DisplayMode[] mSfDisplayModes;
         // The active display mode in SurfaceFlinger
         private SurfaceControl.DisplayMode mActiveSfDisplayMode;
+        // The active display vsync period in SurfaceFlinger
+        private float mActiveRenderFrameRate;
 
         private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =
                 new DisplayEventReceiver.FrameRateOverride[0];
@@ -241,7 +244,7 @@
             mSidekickInternal = LocalServices.getService(SidekickInternal.class);
             mBacklightAdapter = new BacklightAdapter(displayToken, isFirstDisplay,
                     mSurfaceControlProxy);
-            mActiveDisplayModeAtStartId = dynamicInfo.activeDisplayModeId;
+            mActiveSfDisplayModeAtStartId = dynamicInfo.activeDisplayModeId;
         }
 
         @Override
@@ -255,7 +258,7 @@
          */
         @Override
         public Display.Mode getActiveDisplayModeAtStartLocked() {
-            return findMode(mActiveDisplayModeAtStartId);
+            return findMode(findMatchingModeIdLocked(mActiveSfDisplayModeAtStartId));
         }
 
         /**
@@ -266,7 +269,7 @@
                 SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
             boolean changed = updateDisplayModesLocked(
                     dynamicInfo.supportedDisplayModes, dynamicInfo.preferredBootDisplayMode,
-                    dynamicInfo.activeDisplayModeId, modeSpecs);
+                    dynamicInfo.activeDisplayModeId, dynamicInfo.renderFrameRate, modeSpecs);
             changed |= updateStaticInfo(staticInfo);
             changed |= updateColorModesLocked(dynamicInfo.supportedColorModes,
                     dynamicInfo.activeColorMode);
@@ -282,7 +285,8 @@
 
         public boolean updateDisplayModesLocked(
                 SurfaceControl.DisplayMode[] displayModes, int preferredSfDisplayModeId,
-                int activeSfDisplayModeId, SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
+                int activeSfDisplayModeId, float renderFrameRate,
+                SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
             mSfDisplayModes = Arrays.copyOf(displayModes, displayModes.length);
             mActiveSfDisplayMode = getModeById(displayModes, activeSfDisplayModeId);
             SurfaceControl.DisplayMode preferredSfDisplayMode =
@@ -378,6 +382,16 @@
                 sendTraversalRequestLocked();
             }
 
+            boolean renderFrameRateChanged = false;
+
+            if (mActiveRenderFrameRate > 0 &&  mActiveRenderFrameRate != renderFrameRate) {
+                Slog.d(TAG, "The render frame rate was changed from SurfaceFlinger or the display"
+                        + " device to " + renderFrameRate);
+                mActiveRenderFrameRate = renderFrameRate;
+                renderFrameRateChanged = true;
+                sendTraversalRequestLocked();
+            }
+
             // Check whether surface flinger spontaneously changed display config specs out from
             // under us. If so, schedule a traversal to reapply our display config specs.
             if (mDisplayModeSpecs.baseModeId != INVALID_MODE_ID) {
@@ -397,7 +411,7 @@
             boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded;
             // If the records haven't changed then we're done here.
             if (!recordsChanged) {
-                return activeModeChanged || preferredModeChanged;
+                return activeModeChanged || preferredModeChanged || renderFrameRateChanged;
             }
 
             mSupportedModes.clear();
@@ -409,16 +423,19 @@
             if (mDefaultModeId == INVALID_MODE_ID) {
                 mDefaultModeId = activeRecord.mMode.getModeId();
                 mDefaultModeGroup = mActiveSfDisplayMode.group;
+                mActiveRenderFrameRate = renderFrameRate;
             } else if (modesAdded && activeModeChanged) {
                 Slog.d(TAG, "New display modes are added and the active mode has changed, "
                         + "use active mode as default mode.");
                 mDefaultModeId = activeRecord.mMode.getModeId();
                 mDefaultModeGroup = mActiveSfDisplayMode.group;
+                mActiveRenderFrameRate = renderFrameRate;
             } else if (findSfDisplayModeIdLocked(mDefaultModeId, mDefaultModeGroup) < 0) {
                 Slog.w(TAG, "Default display mode no longer available, using currently"
                         + " active mode as default.");
                 mDefaultModeId = activeRecord.mMode.getModeId();
                 mDefaultModeGroup = mActiveSfDisplayMode.group;
+                mActiveRenderFrameRate = renderFrameRate;
             }
 
             // Determine whether the display mode specs' base mode is still there.
@@ -583,7 +600,9 @@
                 DisplayModeRecord record = mSupportedModes.valueAt(i);
                 if (record.hasMatchingMode(mode)
                         && refreshRatesEquals(alternativeRefreshRates,
-                                record.mMode.getAlternativeRefreshRates())) {
+                                record.mMode.getAlternativeRefreshRates())
+                        && hdrTypesEqual(mode.supportedHdrTypes,
+                            record.mMode.getSupportedHdrTypes())) {
                     return record;
                 }
             }
@@ -617,6 +636,7 @@
                 mInfo.width = mActiveSfDisplayMode.width;
                 mInfo.height = mActiveSfDisplayMode.height;
                 mInfo.modeId = mActiveModeId;
+                mInfo.renderFrameRate = mActiveRenderFrameRate;
                 mInfo.defaultModeId = getPreferredModeId();
                 mInfo.supportedModes = getDisplayModes(mSupportedModes);
                 mInfo.colorMode = mActiveColorMode;
@@ -686,6 +706,9 @@
                         res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
                 mInfo.installOrientation = mStaticDisplayInfo.installOrientation;
 
+                mInfo.displayShape = DisplayShape.fromResources(
+                        res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+
                 if (mStaticDisplayInfo.isInternal) {
                     mInfo.type = Display.TYPE_INTERNAL;
                     mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
@@ -758,18 +781,8 @@
                             }
                         }
 
-                        // If the state change was from or to VR, then we need to tell the light
-                        // so that it can apply appropriate VR brightness settings. Also, update the
-                        // brightness so the state is propogated to light.
-                        boolean vrModeChange = false;
-                        if ((state == Display.STATE_VR || currentState == Display.STATE_VR) &&
-                                currentState != state) {
-                            setVrMode(state == Display.STATE_VR);
-                            vrModeChange = true;
-                        }
-
                         // Apply brightness changes given that we are in a non-suspended state.
-                        if (brightnessChanged || vrModeChange) {
+                        if (brightnessChanged) {
                             setDisplayBrightness(brightnessState, sdrBrightnessState);
                             mBrightnessState = brightnessState;
                             mSdrBrightnessState = sdrBrightnessState;
@@ -781,15 +794,6 @@
                         }
                     }
 
-                    private void setVrMode(boolean isVrEnabled) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "setVrMode("
-                                    + "id=" + physicalDisplayId
-                                    + ", state=" + Display.stateToString(state) + ")");
-                        }
-                        mBacklightAdapter.setVrMode(isVrEnabled);
-                    }
-
                     private void setDisplayState(int state) {
                         if (DEBUG) {
                             Slog.d(TAG, "setDisplayState("
@@ -1008,8 +1012,8 @@
             updateDeviceInfoLocked();
         }
 
-        public void onActiveDisplayModeChangedLocked(int sfModeId) {
-            if (updateActiveModeLocked(sfModeId)) {
+        public void onActiveDisplayModeChangedLocked(int sfModeId, float renderFrameRate) {
+            if (updateActiveModeLocked(sfModeId, renderFrameRate)) {
                 updateDeviceInfoLocked();
             }
         }
@@ -1021,8 +1025,9 @@
             }
         }
 
-        public boolean updateActiveModeLocked(int activeSfModeId) {
-            if (mActiveSfDisplayMode.id == activeSfModeId) {
+        public boolean updateActiveModeLocked(int activeSfModeId, float renderFrameRate) {
+            if (mActiveSfDisplayMode.id == activeSfModeId
+                    && mActiveRenderFrameRate == renderFrameRate) {
                 return false;
             }
             mActiveSfDisplayMode = getModeById(mSfDisplayModes, activeSfModeId);
@@ -1031,6 +1036,7 @@
                 Slog.w(TAG, "In unknown mode after setting allowed modes"
                         + ", activeModeId=" + activeSfModeId);
             }
+            mActiveRenderFrameRate = renderFrameRate;
             return true;
         }
 
@@ -1127,6 +1133,7 @@
                 pw.println("  " + sfDisplayMode);
             }
             pw.println("mActiveSfDisplayMode=" + mActiveSfDisplayMode);
+            pw.println("mActiveRenderFrameRate=" + mActiveRenderFrameRate);
             pw.println("mSupportedModes=");
             for (int i = 0; i < mSupportedModes.size(); i++) {
                 pw.println("  " + mSupportedModes.valueAt(i));
@@ -1241,6 +1248,13 @@
         }
     }
 
+    private boolean hdrTypesEqual(int[] modeHdrTypes, int[] recordHdrTypes) {
+        int[] modeHdrTypesCopy = Arrays.copyOf(modeHdrTypes, modeHdrTypes.length);
+        Arrays.sort(modeHdrTypesCopy);
+        // Record HDR types are already sorted when we create the DisplayModeRecord
+        return Arrays.equals(modeHdrTypesCopy, recordHdrTypes);
+    }
+
     /** Supplies a context whose Resources apply runtime-overlays */
     Context getOverlayContext() {
         if (mOverlayContext == null) {
@@ -1258,7 +1272,7 @@
         DisplayModeRecord(SurfaceControl.DisplayMode mode,
                 float[] alternativeRefreshRates) {
             mMode = createMode(mode.width, mode.height, mode.refreshRate,
-                    alternativeRefreshRates);
+                    alternativeRefreshRates, mode.supportedHdrTypes);
         }
 
         /**
@@ -1272,7 +1286,7 @@
             return mMode.getPhysicalWidth() == mode.width
                     && mMode.getPhysicalHeight() == mode.height
                     && Float.floatToIntBits(mMode.getRefreshRate())
-                        == Float.floatToIntBits(mode.refreshRate);
+                    == Float.floatToIntBits(mode.refreshRate);
         }
 
         public String toString() {
@@ -1294,7 +1308,8 @@
 
     public interface DisplayEventListener {
         void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected);
-        void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId);
+        void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+                long renderPeriod);
         void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
                 DisplayEventReceiver.FrameRateOverride[] overrides);
 
@@ -1315,8 +1330,9 @@
         }
 
         @Override
-        public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
-            mListener.onModeChanged(timestampNanos, physicalDisplayId, modeId);
+        public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+                long renderPeriod) {
+            mListener.onModeChanged(timestampNanos, physicalDisplayId, modeId, renderPeriod);
         }
 
         @Override
@@ -1339,12 +1355,14 @@
         }
 
         @Override
-        public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
+        public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+                long renderPeriod) {
             if (DEBUG) {
                 Slog.d(TAG, "onModeChanged("
                         + "timestampNanos=" + timestampNanos
                         + ", physicalDisplayId=" + physicalDisplayId
-                        + ", modeId=" + modeId + ")");
+                        + ", modeId=" + modeId
+                        + ", renderPeriod=" + renderPeriod + ")");
             }
             synchronized (getSyncRoot()) {
                 LocalDisplayDevice device = mDevices.get(physicalDisplayId);
@@ -1355,7 +1373,8 @@
                     }
                     return;
                 }
-                device.onActiveDisplayModeChangedLocked(modeId);
+                float renderFrameRate = 1e9f / renderPeriod;
+                device.onActiveDisplayModeChangedLocked(modeId, renderFrameRate);
             }
         }
 
@@ -1501,12 +1520,6 @@
             }
         }
 
-        void setVrMode(boolean isVrModeEnabled) {
-            if (mBacklight != null) {
-                mBacklight.setVrMode(isVrModeEnabled);
-            }
-        }
-
         void setForceSurfaceControl(boolean forceSurfaceControl) {
             mForceSurfaceControl = forceSurfaceControl;
         }
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index dedc56a..c7b27de 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -18,7 +18,6 @@
 
 import static com.android.server.display.DisplayDeviceInfo.TOUCH_NONE;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Point;
@@ -68,33 +67,6 @@
 final class LogicalDisplay {
     private static final String TAG = "LogicalDisplay";
 
-    /**
-     * Phase indicating the logical display's existence is hidden from the rest of the framework.
-     * This can happen if the current layout has specifically requested to keep this display
-     * disabled.
-     */
-    static final int DISPLAY_PHASE_DISABLED = -1;
-
-    /**
-     * Phase indicating that the logical display is going through a layout transition.
-     * When in this phase, other systems can choose to special case power-state handling of a
-     * display that might be in a transition.
-     */
-    static final int DISPLAY_PHASE_LAYOUT_TRANSITION = 0;
-
-    /**
-     * The display is exposed to the rest of the system and its power state is determined by a
-     * power-request from PowerManager.
-     */
-    static final int DISPLAY_PHASE_ENABLED = 1;
-
-    @IntDef(prefix = {"DISPLAY_PHASE" }, value = {
-        DISPLAY_PHASE_DISABLED,
-        DISPLAY_PHASE_LAYOUT_TRANSITION,
-        DISPLAY_PHASE_ENABLED
-    })
-    @interface DisplayPhase {}
-
     // The layer stack we use when the display has been blanked to prevent any
     // of its content from appearing.
     private static final int BLANK_LAYER_STACK = -1;
@@ -159,14 +131,6 @@
     private final Rect mTempDisplayRect = new Rect();
 
     /**
-     * Indicates the current phase of the display. Generally, phases supersede any
-     * requests from PowerManager in DPC's calculation for the display state. Only when the
-     * phase is ENABLED does PowerManager's request for the display take effect.
-     */
-    @DisplayPhase
-    private int mPhase = DISPLAY_PHASE_ENABLED;
-
-    /**
      * The UID mappings for refresh rate override
      */
     private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides;
@@ -181,12 +145,22 @@
      */
     private final SparseArray<Float> mTempFrameRateOverride;
 
+    // Indicates the display is enabled (allowed to be ON).
+    private boolean mIsEnabled;
+
+    // Indicates the display is part of a transition from one device-state ({@link
+    // DeviceStateManager}) to another. Being a "part" of a transition means that either
+    // the {@link mIsEnabled} is changing, or the underlying mPrimiaryDisplayDevice is changing.
+    private boolean mIsInTransition;
+
     public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
         mDisplayId = displayId;
         mLayerStack = layerStack;
         mPrimaryDisplayDevice = primaryDisplayDevice;
         mPendingFrameRateOverrideUids = new ArraySet<>();
         mTempFrameRateOverride = new SparseArray<>();
+        mIsEnabled = true;
+        mIsInTransition = false;
     }
 
     /**
@@ -233,6 +207,7 @@
                 info.displayCutout = mOverrideDisplayInfo.displayCutout;
                 info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi;
                 info.roundedCorners = mOverrideDisplayInfo.roundedCorners;
+                info.displayShape = mOverrideDisplayInfo.displayShape;
             }
             mInfo.set(info);
         }
@@ -384,6 +359,9 @@
             if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
                 mBaseDisplayInfo.flags |= Display.FLAG_TOUCH_FEEDBACK_DISABLED;
             }
+            if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0) {
+                mBaseDisplayInfo.flags |= Display.FLAG_OWN_FOCUS;
+            }
             Rect maskingInsets = getMaskingInsets(deviceInfo);
             int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
             int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
@@ -399,6 +377,7 @@
             mBaseDisplayInfo.logicalHeight = maskedHeight;
             mBaseDisplayInfo.rotation = Surface.ROTATION_0;
             mBaseDisplayInfo.modeId = deviceInfo.modeId;
+            mBaseDisplayInfo.renderFrameRate = deviceInfo.renderFrameRate;
             mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId;
             mBaseDisplayInfo.supportedModes = Arrays.copyOf(
                     deviceInfo.supportedModes, deviceInfo.supportedModes.length);
@@ -434,6 +413,7 @@
             mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault;
             mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners;
             mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation;
+            mBaseDisplayInfo.displayShape = deviceInfo.displayShape;
             mPrimaryDisplayDeviceInfo = deviceInfo;
             mInfo.set(null);
         }
@@ -526,7 +506,7 @@
         // Prevent displays that are disabled from receiving input.
         // TODO(b/188914255): Remove once input can dispatch against device vs layerstack.
         device.setDisplayFlagsLocked(t,
-                (isEnabled() && device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE)
+                (isEnabledLocked() && device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE)
                         ? SurfaceControl.DISPLAY_RECEIVES_INPUT
                         : 0);
 
@@ -768,32 +748,45 @@
         return old;
     }
 
-    public void setPhase(@DisplayPhase int phase) {
-        mPhase = phase;
-    }
-
-    /**
-     * Returns the currently set phase for this LogicalDisplay. Phases are used when transitioning
-     * from one device state to another. {@see LogicalDisplayMapper}.
-     */
-    @DisplayPhase
-    public int getPhase() {
-        return mPhase;
-    }
-
     /**
      * @return {@code true} if the LogicalDisplay is enabled or {@code false}
      * if disabled indicating that the display should be hidden from the rest of the apps and
      * framework.
      */
-    public boolean isEnabled() {
-        // DISPLAY_PHASE_LAYOUT_TRANSITION is still considered an 'enabled' phase.
-        return mPhase == DISPLAY_PHASE_ENABLED || mPhase == DISPLAY_PHASE_LAYOUT_TRANSITION;
+    public boolean isEnabledLocked() {
+        return mIsEnabled;
+    }
+
+    /**
+     * Sets the display as enabled.
+     *
+     * @param enable True if enabled, false otherwise.
+     */
+    public void setEnabledLocked(boolean enabled) {
+        mIsEnabled = enabled;
+    }
+
+    /**
+     * @return {@code true} if the LogicalDisplay is in a transition phase. This is used to indicate
+     * that we are getting ready to swap the underlying display-device and the display should be
+     * rendered appropriately to reduce jank.
+     */
+    public boolean isInTransitionLocked() {
+        return mIsInTransition;
+    }
+
+    /**
+     * Sets the transition phase.
+     * @param isInTransition True if it display is in transition.
+     */
+    public void setIsInTransitionLocked(boolean isInTransition) {
+        mIsInTransition = isInTransition;
     }
 
     public void dumpLocked(PrintWriter pw) {
         pw.println("mDisplayId=" + mDisplayId);
-        pw.println("mPhase=" + mPhase);
+        pw.println("mIsEnabled=" + mIsEnabled);
+        pw.println("mIsInTransition=" + mIsInTransition);
         pw.println("mLayerStack=" + mLayerStack);
         pw.println("mHasContent=" + mHasContent);
         pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index cb97e28..80f47a1 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -40,7 +40,7 @@
 import android.view.DisplayInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.display.LogicalDisplay.DisplayPhase;
+import com.android.server.display.layout.DisplayIdProducer;
 import com.android.server.display.layout.Layout;
 
 import java.io.PrintWriter;
@@ -83,6 +83,8 @@
     private static final int UPDATE_STATE_TRANSITION = 1;
     private static final int UPDATE_STATE_UPDATED = 2;
 
+    private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
+
     /**
      * Temporary display info, used for comparing display configurations.
      */
@@ -171,6 +173,8 @@
     private final ArrayMap<String, Integer> mVirtualDeviceDisplayMapping = new ArrayMap<>();
 
     private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+    private final DisplayIdProducer mIdProducer = (isDefault) ->
+            isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
     private Layout mCurrentLayout = null;
     private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
     private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
@@ -180,6 +184,14 @@
     LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
             @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
             @NonNull Handler handler) {
+        this(context, repo, listener, syncRoot, handler,
+                new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY
+                        : sNextNonDefaultDisplayId++));
+    }
+
+    LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
+            @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
+            @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap) {
         mSyncRoot = syncRoot;
         mPowerManager = context.getSystemService(PowerManager.class);
         mInteractive = mPowerManager.isInteractive();
@@ -194,7 +206,7 @@
         mDeviceStatesOnWhichToSleep = toSparseBooleanArray(context.getResources().getIntArray(
                 com.android.internal.R.array.config_deviceStatesOnWhichToSleep));
         mDisplayDeviceRepo.addListener(this);
-        mDeviceStateToLayoutMap = new DeviceStateToLayoutMap();
+        mDeviceStateToLayoutMap = deviceStateToLayoutMap;
     }
 
     @Override
@@ -231,10 +243,22 @@
     }
 
     public LogicalDisplay getDisplayLocked(int displayId) {
-        return mLogicalDisplays.get(displayId);
+        return getDisplayLocked(displayId, /* includeDisabled= */ true);
+    }
+
+    public LogicalDisplay getDisplayLocked(int displayId, boolean includeDisabled) {
+        LogicalDisplay display = mLogicalDisplays.get(displayId);
+        if (display == null || display.isEnabledLocked() || includeDisabled) {
+            return display;
+        }
+        return null;
     }
 
     public LogicalDisplay getDisplayLocked(DisplayDevice device) {
+        return getDisplayLocked(device, /* includeDisabled= */ true);
+    }
+
+    public LogicalDisplay getDisplayLocked(DisplayDevice device, boolean includeDisabled) {
         if (device == null) {
             return null;
         }
@@ -242,21 +266,26 @@
         for (int i = 0; i < count; i++) {
             final LogicalDisplay display = mLogicalDisplays.valueAt(i);
             if (display.getPrimaryDisplayDeviceLocked() == device) {
-                return display;
+                if (display.isEnabledLocked() || includeDisabled) {
+                    return display;
+                }
+                return null;
             }
         }
         return null;
     }
 
-    public int[] getDisplayIdsLocked(int callingUid) {
+    public int[] getDisplayIdsLocked(int callingUid, boolean includeDisabled) {
         final int count = mLogicalDisplays.size();
         int[] displayIds = new int[count];
         int n = 0;
         for (int i = 0; i < count; i++) {
             LogicalDisplay display = mLogicalDisplays.valueAt(i);
-            DisplayInfo info = display.getDisplayInfoLocked();
-            if (info.hasAccess(callingUid)) {
-                displayIds[n++] = mLogicalDisplays.keyAt(i);
+            if (display.isEnabledLocked() || includeDisabled) {
+                DisplayInfo info = display.getDisplayInfoLocked();
+                if (info.hasAccess(callingUid)) {
+                    displayIds[n++] = mLogicalDisplays.keyAt(i);
+                }
             }
         }
         if (n != count) {
@@ -390,14 +419,12 @@
 
     void setDeviceStateLocked(int state, boolean isOverrideActive) {
         Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState
-                + ", interactive=" + mInteractive);
+                + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted);
         // As part of a state transition, we may need to turn off some displays temporarily so that
         // the transition is smooth. Plus, on some devices, only one internal displays can be
-        // on at a time. We use DISPLAY_PHASE_LAYOUT_TRANSITION to mark a display that needs to be
+        // on at a time. We use LogicalDisplay.setIsInTransition to mark a display that needs to be
         // temporarily turned off.
-        if (mDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) {
-            resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION);
-        }
+        resetLayoutLocked(mDeviceState, state, /* transitionValue= */ true);
         mPendingDeviceState = state;
         final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
                 mInteractive, mBootCompleted);
@@ -507,7 +534,7 @@
         final int count = mLogicalDisplays.size();
         for (int i = 0; i < count; i++) {
             final LogicalDisplay display = mLogicalDisplays.valueAt(i);
-            if (display.getPhase() != LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) {
+            if (!display.isInTransitionLocked()) {
                 continue;
             }
 
@@ -523,7 +550,7 @@
     }
 
     private void transitionToPendingStateLocked() {
-        resetLayoutLocked(mDeviceState, mPendingDeviceState, LogicalDisplay.DISPLAY_PHASE_ENABLED);
+        resetLayoutLocked(mDeviceState, mPendingDeviceState, /* transitionValue= */ false);
         mDeviceState = mPendingDeviceState;
         mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
         applyLayoutLocked();
@@ -568,7 +595,7 @@
 
         // Create a logical display for the new display device
         LogicalDisplay display = createNewLogicalDisplayLocked(
-                device, Layout.assignDisplayIdLocked(false /*isDefault*/));
+                device, mIdProducer.getId(/* isDefault= */ false));
 
         applyLayoutLocked();
         updateLogicalDisplaysLocked();
@@ -601,7 +628,7 @@
                         & DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY) != 0
                         && !nextDeviceInfo.address.equals(deviceInfo.address)) {
                     layout.createDisplayLocked(nextDeviceInfo.address,
-                            /* isDefault= */ true, /* isEnabled= */ true);
+                            /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
                     applyLayoutLocked();
                     return;
                 }
@@ -838,17 +865,17 @@
 
     /**
      * Goes through all the displays used in the layouts for the specified {@code fromState} and
-     * {@code toState} and applies the specified {@code phase}. When a new layout is requested, we
-     * put the displays that will change into a transitional phase so that they can all be turned
-     * OFF. Once all are confirmed OFF, then this method gets called again to reset the phase to
-     * normal operation. This helps to ensure that all display-OFF requests are made before
+     * {@code toState} and un/marks them for transition. When a new layout is requested, we
+     * mark the displays that will change into a transitional phase so that they can all be turned
+     * OFF. Once all are confirmed OFF, then this method gets called again to reset transition
+     * marker. This helps to ensure that all display-OFF requests are made before
      * display-ON which in turn hides any resizing-jank windows might incur when switching displays.
      *
      * @param fromState The state we are switching from.
      * @param toState The state we are switching to.
-     * @param phase The new phase to apply to the displays.
+     * @param transitionValue The value to mark the transition state: true == transitioning.
      */
-    private void resetLayoutLocked(int fromState, int toState, @DisplayPhase int phase) {
+    private void resetLayoutLocked(int fromState, int toState, boolean transitionValue) {
         final Layout fromLayout = mDeviceStateToLayoutMap.get(fromState);
         final Layout toLayout = mDeviceStateToLayoutMap.get(toState);
 
@@ -866,12 +893,16 @@
             // new layout.
             final DisplayAddress address = device.getDisplayDeviceInfoLocked().address;
 
-            // Virtual displays do not have addresses.
+            // Virtual displays do not have addresses, so account for nulls.
             final Layout.Display fromDisplay =
                     address != null ? fromLayout.getByAddress(address) : null;
             final Layout.Display toDisplay =
                     address != null ? toLayout.getByAddress(address) : null;
 
+            // If the display is in one of the layouts but not the other, then the content will
+            // change, so in this case we also want to blank the displays to avoid jank.
+            final boolean displayNotInBothLayouts = (fromDisplay == null) != (toDisplay == null);
+
             // If a layout doesn't mention a display-device at all, then the display-device defaults
             // to enabled. This is why we treat null as "enabled" in the code below.
             final boolean wasEnabled = fromDisplay == null || fromDisplay.isEnabled();
@@ -886,16 +917,23 @@
             // 3) It's enabled, but it's mapped to a new logical display ID. To the user this
             //    would look like apps moving from one screen to another since task-stacks stay
             //    with the logical display [ID].
+            // 4) It's in one layout but not the other, so the content will change.
             final boolean isTransitioning =
-                    (logicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION)
+                    logicalDisplay.isInTransitionLocked()
                     || (wasEnabled != willBeEnabled)
-                    || deviceHasNewLogicalDisplayId;
+                    || deviceHasNewLogicalDisplayId
+                    || displayNotInBothLayouts;
 
             if (isTransitioning) {
-                setDisplayPhase(logicalDisplay, phase);
-                if (phase == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) {
-                    mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION);
+                if (transitionValue != logicalDisplay.isInTransitionLocked()) {
+                    Slog.i(TAG, "Set isInTransition on display " + displayId + ": "
+                            + transitionValue);
                 }
+                // This will either mark the display as "transitioning" if we are starting to change
+                // the device state, or remove the transitioning marker if the state change is
+                // ending.
+                logicalDisplay.setIsInTransitionLocked(transitionValue);
+                mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION);
             }
         }
     }
@@ -940,9 +978,7 @@
                 newDisplay.swapDisplaysLocked(oldDisplay);
             }
 
-            if (!displayLayout.isEnabled()) {
-                setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_DISABLED);
-            }
+            setEnabledLocked(newDisplay, displayLayout.isEnabled());
         }
 
     }
@@ -961,23 +997,25 @@
         final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
         display.updateLocked(mDisplayDeviceRepo);
         mLogicalDisplays.put(displayId, display);
-        setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_ENABLED);
         return display;
     }
 
-    private void setDisplayPhase(LogicalDisplay display, @DisplayPhase int phase) {
+    private void setEnabledLocked(LogicalDisplay display, boolean isEnabled) {
         final int displayId = display.getDisplayIdLocked();
         final DisplayInfo info = display.getDisplayInfoLocked();
 
         final boolean disallowSecondaryDisplay = mSingleDisplayDemoMode
                 && (info.type != Display.TYPE_INTERNAL);
-        if (phase != LogicalDisplay.DISPLAY_PHASE_DISABLED && disallowSecondaryDisplay) {
+        if (isEnabled && disallowSecondaryDisplay) {
             Slog.i(TAG, "Not creating a logical display for a secondary display because single"
                     + " display demo mode is enabled: " + display.getDisplayInfoLocked());
-            phase = LogicalDisplay.DISPLAY_PHASE_DISABLED;
+            isEnabled = false;
         }
 
-        display.setPhase(phase);
+        if (display.isEnabledLocked() != isEnabled) {
+            Slog.i(TAG, "SetEnabled on display " + displayId + ": " + isEnabled);
+            display.setEnabledLocked(isEnabled);
+        }
     }
 
     private int assignDisplayGroupIdLocked(
@@ -1005,7 +1043,8 @@
             return;
         }
         final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
-        layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true);
+        layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true,
+                mIdProducer);
     }
 
     private int assignLayerStackLocked(int displayId) {
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index b0de844..3e67f0a 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -29,6 +29,7 @@
 import android.util.DisplayMetrics;
 import android.util.Slog;
 import android.view.Display;
+import android.view.DisplayShape;
 import android.view.Gravity;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -339,6 +340,7 @@
                 mInfo.width = mode.getPhysicalWidth();
                 mInfo.height = mode.getPhysicalHeight();
                 mInfo.modeId = mode.getModeId();
+                mInfo.renderFrameRate = mode.getRefreshRate();
                 mInfo.defaultModeId = mModes[0].getModeId();
                 mInfo.supportedModes = mModes;
                 mInfo.densityDpi = rawMode.mDensityDpi;
@@ -361,6 +363,8 @@
                 mInfo.state = mState;
                 // The display is trusted since it is created by system.
                 mInfo.flags |= FLAG_TRUSTED;
+                mInfo.displayShape =
+                        DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
             }
             return mInfo;
         }
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index f30a84f..e7601bc 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -619,7 +619,7 @@
 
     private static final class DisplayState {
         private int mColorMode;
-        private float mBrightness;
+        private float mBrightness = Float.NaN;
         private int mWidth;
         private int mHeight;
         private float mRefreshRate;
@@ -700,7 +700,11 @@
                         break;
                     case TAG_BRIGHTNESS_VALUE:
                         String brightness = parser.nextText();
-                        mBrightness = Float.parseFloat(brightness);
+                        try {
+                            mBrightness = Float.parseFloat(brightness);
+                        } catch (NumberFormatException e) {
+                            mBrightness = Float.NaN;
+                        }
                         break;
                     case TAG_BRIGHTNESS_CONFIGURATIONS:
                         mDisplayBrightnessConfigurations.loadFromXml(parser);
@@ -727,7 +731,9 @@
             serializer.endTag(null, TAG_COLOR_MODE);
 
             serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
-            serializer.text(Float.toString(mBrightness));
+            if (!Float.isNaN(mBrightness)) {
+                serializer.text(Float.toString(mBrightness));
+            }
             serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
 
             serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 20b82c3..a118b2f 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -20,7 +20,9 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
@@ -31,6 +33,7 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
 
 import static com.android.server.display.DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED;
+import static com.android.server.display.DisplayDeviceInfo.FLAG_DEVICE_DISPLAY_GROUP;
 import static com.android.server.display.DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
 import static com.android.server.display.DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED;
 import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED;
@@ -51,6 +54,7 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.Display;
+import android.view.DisplayShape;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
@@ -444,6 +448,7 @@
                 mInfo.width = mWidth;
                 mInfo.height = mHeight;
                 mInfo.modeId = mMode.getModeId();
+                mInfo.renderFrameRate = mMode.getRefreshRate();
                 mInfo.defaultModeId = mMode.getModeId();
                 mInfo.supportedModes = new Display.Mode[] { mMode };
                 mInfo.densityDpi = mDensityDpi;
@@ -464,6 +469,9 @@
                         mInfo.flags |= FLAG_OWN_DISPLAY_GROUP;
                     }
                 }
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP) != 0) {
+                    mInfo.flags |= FLAG_DEVICE_DISPLAY_GROUP;
+                }
 
                 if ((mFlags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
@@ -495,13 +503,29 @@
                 if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {
                     mInfo.flags |= FLAG_TRUSTED;
                 }
-                if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0
-                        && (mInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) {
-                    mInfo.flags |= FLAG_ALWAYS_UNLOCKED;
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0) {
+                    if ((mInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0
+                            || (mFlags & VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP) != 0) {
+                        mInfo.flags |= FLAG_ALWAYS_UNLOCKED;
+                    } else {
+                        Slog.w(
+                                TAG,
+                                "Ignoring VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED as it requires"
+                                    + " VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP or"
+                                    + " VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP.");
+                    }
                 }
                 if ((mFlags & VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
                     mInfo.flags |= FLAG_TOUCH_FEEDBACK_DISABLED;
                 }
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_FOCUS) != 0) {
+                    if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {
+                        mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_FOCUS;
+                    } else {
+                        Slog.w(TAG, "Ignoring VIRTUAL_DISPLAY_FLAG_OWN_FOCUS as it requires "
+                                + "VIRTUAL_DISPLAY_FLAG_TRUSTED.");
+                    }
+                }
 
                 mInfo.type = Display.TYPE_VIRTUAL;
                 mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
@@ -511,6 +535,9 @@
 
                 mInfo.ownerUid = mOwnerUid;
                 mInfo.ownerPackageName = mOwnerPackageName;
+
+                mInfo.displayShape =
+                        DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
             }
             return mInfo;
         }
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index 146b003..e832701 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -34,6 +34,7 @@
 import android.util.Slog;
 import android.view.Display;
 import android.view.DisplayAddress;
+import android.view.DisplayShape;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
@@ -645,6 +646,7 @@
                 mInfo.width = mWidth;
                 mInfo.height = mHeight;
                 mInfo.modeId = mMode.getModeId();
+                mInfo.renderFrameRate = mMode.getRefreshRate();
                 mInfo.defaultModeId = mMode.getModeId();
                 mInfo.supportedModes = new Display.Mode[] { mMode };
                 mInfo.presentationDeadlineNanos = 1000000000L / (int) mRefreshRate; // 1 frame
@@ -655,6 +657,8 @@
                 mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
                 // The display is trusted since it is created by system.
                 mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED;
+                mInfo.displayShape =
+                        DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
             }
             return mInfo;
         }
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index e3fa622..f19852b 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -39,8 +39,6 @@
     private String mPhysicalDisplayId;
     private long mTime;
     private float mLux;
-    private float mFastAmbientLux;
-    private float mSlowAmbientLux;
     private float mPreThresholdLux;
     private float mInitialBrightness;
     private float mBrightness;
@@ -51,6 +49,7 @@
     private int mRbcStrength;
     private float mThermalMax;
     private float mPowerFactor;
+    private boolean mWasShortTermModelActive;
     private int mFlags;
     private int mAdjustmentFlags;
     private boolean mAutomaticBrightnessEnabled;
@@ -76,8 +75,6 @@
         mTime = that.getTime();
         // Lux values
         mLux = that.getLux();
-        mFastAmbientLux = that.getFastAmbientLux();
-        mSlowAmbientLux = that.getSlowAmbientLux();
         mPreThresholdLux = that.getPreThresholdLux();
         // Brightness values
         mInitialBrightness = that.getInitialBrightness();
@@ -90,6 +87,7 @@
         mRbcStrength = that.getRbcStrength();
         mThermalMax = that.getThermalMax();
         mPowerFactor = that.getPowerFactor();
+        mWasShortTermModelActive = that.wasShortTermModelActive();
         mFlags = that.getFlags();
         mAdjustmentFlags = that.getAdjustmentFlags();
         // Auto-brightness setting
@@ -105,8 +103,6 @@
         mPhysicalDisplayId = "";
         // Lux values
         mLux = 0;
-        mFastAmbientLux = 0;
-        mSlowAmbientLux = 0;
         mPreThresholdLux = 0;
         // Brightness values
         mInitialBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -119,6 +115,7 @@
         mRbcStrength = 0;
         mThermalMax = PowerManager.BRIGHTNESS_MAX;
         mPowerFactor = 1f;
+        mWasShortTermModelActive = false;
         mFlags = 0;
         mAdjustmentFlags = 0;
         // Auto-brightness setting
@@ -140,10 +137,6 @@
                 && mDisplayId == that.mDisplayId
                 && mPhysicalDisplayId.equals(that.mPhysicalDisplayId)
                 && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
-                && Float.floatToRawIntBits(mFastAmbientLux)
-                == Float.floatToRawIntBits(that.mFastAmbientLux)
-                && Float.floatToRawIntBits(mSlowAmbientLux)
-                == Float.floatToRawIntBits(that.mSlowAmbientLux)
                 && Float.floatToRawIntBits(mPreThresholdLux)
                 == Float.floatToRawIntBits(that.mPreThresholdLux)
                 && Float.floatToRawIntBits(mInitialBrightness)
@@ -161,6 +154,7 @@
                 == Float.floatToRawIntBits(that.mThermalMax)
                 && Float.floatToRawIntBits(mPowerFactor)
                 == Float.floatToRawIntBits(that.mPowerFactor)
+                && mWasShortTermModelActive == that.mWasShortTermModelActive
                 && mFlags == that.mFlags
                 && mAdjustmentFlags == that.mAdjustmentFlags
                 && mAutomaticBrightnessEnabled == that.mAutomaticBrightnessEnabled;
@@ -182,14 +176,13 @@
                 + ", rcmdBrt=" + mRecommendedBrightness
                 + ", preBrt=" + mPreThresholdBrightness
                 + ", lux=" + mLux
-                + ", fastLux=" + mFastAmbientLux
-                + ", slowLux=" + mSlowAmbientLux
                 + ", preLux=" + mPreThresholdLux
                 + ", hbmMax=" + mHbmMax
                 + ", hbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
                 + ", rbcStrength=" + mRbcStrength
                 + ", thrmMax=" + mThermalMax
                 + ", powerFactor=" + mPowerFactor
+                + ", wasShortTermModelActive=" + mWasShortTermModelActive
                 + ", flags=" + flagsToString()
                 + ", reason=" + mReason.toString(mAdjustmentFlags)
                 + ", autoBrightness=" + mAutomaticBrightnessEnabled;
@@ -240,22 +233,6 @@
         this.mLux = lux;
     }
 
-    public float getFastAmbientLux() {
-        return mFastAmbientLux;
-    }
-
-    public void setFastAmbientLux(float mFastAmbientLux) {
-        this.mFastAmbientLux = mFastAmbientLux;
-    }
-
-    public float getSlowAmbientLux() {
-        return mSlowAmbientLux;
-    }
-
-    public void setSlowAmbientLux(float mSlowAmbientLux) {
-        this.mSlowAmbientLux = mSlowAmbientLux;
-    }
-
     public float getPreThresholdLux() {
         return mPreThresholdLux;
     }
@@ -344,6 +321,20 @@
         return (mFlags & FLAG_LOW_POWER_MODE) != 0;
     }
 
+    /**
+     * Set whether the short term model was active before the brightness event.
+     */
+    public boolean setWasShortTermModelActive(boolean wasShortTermModelActive) {
+        return this.mWasShortTermModelActive = wasShortTermModelActive;
+    }
+
+    /**
+     * Returns whether the short term model was active before the brightness event.
+     */
+    public boolean wasShortTermModelActive() {
+        return this.mWasShortTermModelActive;
+    }
+
     public int getFlags() {
         return mFlags;
     }
@@ -352,10 +343,6 @@
         this.mFlags = flags;
     }
 
-    public boolean isShortTermModelActive() {
-        return (mFlags & FLAG_USER_SET) != 0;
-    }
-
     public int getAdjustmentFlags() {
         return mAdjustmentFlags;
     }
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
index d8eacd9..b6be713 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
@@ -34,10 +34,9 @@
     public static final int REASON_DOZE_DEFAULT = 3;
     public static final int REASON_AUTOMATIC = 4;
     public static final int REASON_SCREEN_OFF = 5;
-    public static final int REASON_VR = 6;
-    public static final int REASON_OVERRIDE = 7;
-    public static final int REASON_TEMPORARY = 8;
-    public static final int REASON_BOOST = 9;
+    public static final int REASON_OVERRIDE = 6;
+    public static final int REASON_TEMPORARY = 7;
+    public static final int REASON_BOOST = 8;
     public static final int REASON_MAX = REASON_BOOST;
 
     public static final int MODIFIER_DIMMED = 0x1;
@@ -185,8 +184,6 @@
                 return "automatic";
             case REASON_SCREEN_OFF:
                 return "screen_off";
-            case REASON_VR:
-                return "vr";
             case REASON_OVERRIDE:
                 return "override";
             case REASON_TEMPORARY:
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
new file mode 100644
index 0000000..fd4e296
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
@@ -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.server.display.brightness;
+
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+
+/**
+ * A helper class for eualuating brightness utilities
+ */
+public final class BrightnessUtils {
+    /**
+     * Checks whether the brightness is within the valid brightness range, not including off.
+     */
+    public static boolean isValidBrightnessValue(float brightness) {
+        return !Float.isNaN(brightness) && brightness >= PowerManager.BRIGHTNESS_MIN
+                && brightness <= PowerManager.BRIGHTNESS_MAX;
+    }
+
+    /**
+     * A utility to construct the DisplayBrightnessState
+     */
+    public static DisplayBrightnessState constructDisplayBrightnessState(
+            int brightnessChangeReason, float brightness, float sdrBrightness) {
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(brightnessChangeReason);
+        return new DisplayBrightnessState.Builder()
+                .setBrightness(brightness)
+                .setSdrBrightness(sdrBrightness)
+                .setBrightnessReason(brightnessReason)
+                .build();
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
new file mode 100644
index 0000000..bdc8d9d
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -0,0 +1,117 @@
+/*
+ * 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.display.brightness;
+
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+
+import java.io.PrintWriter;
+
+/**
+ * Deploys different DozeBrightnessStrategy to choose the current brightness for a specified
+ * display. Applies the chosen brightness.
+ */
+public final class DisplayBrightnessController {
+    private final int mDisplayId;
+    // Selects an appropriate strategy based on the request provided by the clients.
+    private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
+    private DisplayBrightnessStrategy mDisplayBrightnessStrategy;
+
+    /**
+     * The constructor of DisplayBrightnessController.
+     */
+    public DisplayBrightnessController(Context context, Injector injector, int displayId) {
+        if (injector == null) {
+            injector = new Injector();
+        }
+        mDisplayId = displayId;
+        mDisplayBrightnessStrategySelector = injector.getDisplayBrightnessStrategySelector(context,
+                displayId);
+    }
+
+    /**
+     * Updates the display brightness. This delegates the responsibility of selecting an appropriate
+     * strategy to DisplayBrightnessStrategySelector, which is then applied to evaluate the
+     * DisplayBrightnessState. In the future,
+     * 1. This will account for clamping the brightness if needed.
+     * 2. This will notify the system about the updated brightness
+     *
+     * @param displayPowerRequest The request to update the brightness
+     * @param targetDisplayState  The target display state of the system
+     */
+    public DisplayBrightnessState updateBrightness(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
+            int targetDisplayState) {
+        mDisplayBrightnessStrategy =
+                mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                        targetDisplayState);
+        return mDisplayBrightnessStrategy.updateBrightness(displayPowerRequest);
+    }
+
+    /**
+     * Sets the temporary brightness
+     */
+    public void setTemporaryBrightness(Float temporaryBrightness) {
+        mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()
+                .setTemporaryScreenBrightness(temporaryBrightness);
+    }
+
+    /**
+     * Returns the current selected DisplayBrightnessStrategy
+     */
+    public DisplayBrightnessStrategy getCurrentDisplayBrightnessStrategy() {
+        return mDisplayBrightnessStrategy;
+    }
+
+    /**
+     * Returns a boolean flag indicating if the light sensor is to be used to decide the screen
+     * brightness when dozing
+     */
+    public boolean isAllowAutoBrightnessWhileDozingConfig() {
+        return mDisplayBrightnessStrategySelector.isAllowAutoBrightnessWhileDozingConfig();
+    }
+
+    /**
+     * Used to dump the state.
+     *
+     * @param writer The PrintWriter used to dump the state.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println();
+        writer.println("DisplayBrightnessController:");
+        writer.println("  mDisplayId=: " + mDisplayId);
+        if (mDisplayBrightnessStrategy != null) {
+            writer.println("  Last selected DisplayBrightnessStrategy= "
+                    + mDisplayBrightnessStrategy.getName());
+        }
+        IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
+        mDisplayBrightnessStrategySelector.dump(ipw);
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(Context context,
+                int displayId) {
+            return new DisplayBrightnessStrategySelector(context, /* injector= */ null, displayId);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
new file mode 100644
index 0000000..7d05f13
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -0,0 +1,187 @@
+/*
+ * 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.display.brightness;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.view.Display;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
+import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
+import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
+import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy;
+import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy;
+import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
+
+import java.io.PrintWriter;
+
+/**
+ * This maintains the logic needed to decide the eligible display brightness strategy.
+ */
+public class DisplayBrightnessStrategySelector {
+    private static final String TAG = "DisplayBrightnessStrategySelector";
+    // True if light sensor is to be used to automatically determine doze screen brightness.
+    private final boolean mAllowAutoBrightnessWhileDozingConfig;
+
+    // The brightness strategy used to manage the brightness state when the display is dozing.
+    private final DozeBrightnessStrategy mDozeBrightnessStrategy;
+    // The brightness strategy used to manage the brightness state when the display is in
+    // screen off state.
+    private final ScreenOffBrightnessStrategy mScreenOffBrightnessStrategy;
+    // The brightness strategy used to manage the brightness state when the request state is
+    // invalid.
+    private final OverrideBrightnessStrategy mOverrideBrightnessStrategy;
+    // The brightness strategy used to manage the brightness state in temporary state
+    private final TemporaryBrightnessStrategy mTemporaryBrightnessStrategy;
+    // The brightness strategy used to manage the brightness state when boost is requested
+    private final BoostBrightnessStrategy mBoostBrightnessStrategy;
+    // The brightness strategy used to manage the brightness state when the request is invalid.
+    private final InvalidBrightnessStrategy mInvalidBrightnessStrategy;
+
+    // We take note of the old brightness strategy so that we can know when the strategy changes.
+    private String mOldBrightnessStrategyName;
+
+    private final int mDisplayId;
+
+    /**
+     * The constructor of DozeBrightnessStrategy.
+     */
+    public DisplayBrightnessStrategySelector(Context context, Injector injector, int displayId) {
+        if (injector == null) {
+            injector = new Injector();
+        }
+        mDisplayId = displayId;
+        mDozeBrightnessStrategy = injector.getDozeBrightnessStrategy();
+        mScreenOffBrightnessStrategy = injector.getScreenOffBrightnessStrategy();
+        mOverrideBrightnessStrategy = injector.getOverrideBrightnessStrategy();
+        mTemporaryBrightnessStrategy = injector.getTemporaryBrightnessStrategy();
+        mBoostBrightnessStrategy = injector.getBoostBrightnessStrategy();
+        mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
+        mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
+                R.bool.config_allowAutoBrightnessWhileDozing);
+        mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
+    }
+
+    /**
+     * Selects the appropriate DisplayBrightnessStrategy based on the request and the display state
+     * to which the display is transitioning
+     */
+    @NonNull
+    public DisplayBrightnessStrategy selectStrategy(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
+            int targetDisplayState) {
+        DisplayBrightnessStrategy displayBrightnessStrategy = mInvalidBrightnessStrategy;
+        if (targetDisplayState == Display.STATE_OFF) {
+            displayBrightnessStrategy = mScreenOffBrightnessStrategy;
+        } else if (displayPowerRequest.boostScreenBrightness) {
+            displayBrightnessStrategy = mBoostBrightnessStrategy;
+        } else if (shouldUseDozeBrightnessStrategy(displayPowerRequest)) {
+            displayBrightnessStrategy = mDozeBrightnessStrategy;
+        } else if (BrightnessUtils
+                .isValidBrightnessValue(displayPowerRequest.screenBrightnessOverride)) {
+            displayBrightnessStrategy = mOverrideBrightnessStrategy;
+        } else if (BrightnessUtils.isValidBrightnessValue(
+                mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) {
+            displayBrightnessStrategy = mTemporaryBrightnessStrategy;
+        }
+
+        if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) {
+            Slog.i(TAG,
+                    "Changing the DisplayBrightnessStrategy from " + mOldBrightnessStrategyName
+                            + " to" + displayBrightnessStrategy.getName() + " for display "
+                            + mDisplayId);
+            mOldBrightnessStrategyName = displayBrightnessStrategy.getName();
+        }
+        return displayBrightnessStrategy;
+    }
+
+    public TemporaryBrightnessStrategy getTemporaryDisplayBrightnessStrategy() {
+        return mTemporaryBrightnessStrategy;
+    }
+
+    /**
+     * Returns a boolean flag indicating if the light sensor is to be used to decide the screen
+     * brightness when dozing
+     */
+    public boolean isAllowAutoBrightnessWhileDozingConfig() {
+        return mAllowAutoBrightnessWhileDozingConfig;
+    }
+
+    /**
+     * Dumps the state of this class.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println();
+        writer.println("DisplayBrightnessStrategySelector:");
+        writer.println("  mDisplayId= " + mDisplayId);
+        writer.println("  mOldBrightnessStrategyName= " + mOldBrightnessStrategyName);
+        writer.println(
+                "  mAllowAutoBrightnessWhileDozingConfig= "
+                        + mAllowAutoBrightnessWhileDozingConfig);
+        IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
+        mTemporaryBrightnessStrategy.dump(ipw);
+    }
+
+    /**
+     * Validates if the conditions are met to qualify for the DozeBrightnessStrategy.
+     */
+    private boolean shouldUseDozeBrightnessStrategy(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+        // We are not checking the targetDisplayState, but rather relying on the policy because
+        // a user can define a different display state(displayPowerRequest.dozeScreenState) too
+        // in the request with the Doze policy
+        if (displayPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE) {
+            if (!mAllowAutoBrightnessWhileDozingConfig) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() {
+            return new ScreenOffBrightnessStrategy();
+        }
+
+        DozeBrightnessStrategy getDozeBrightnessStrategy() {
+            return new DozeBrightnessStrategy();
+        }
+
+        OverrideBrightnessStrategy getOverrideBrightnessStrategy() {
+            return new OverrideBrightnessStrategy();
+        }
+
+        TemporaryBrightnessStrategy getTemporaryBrightnessStrategy() {
+            return new TemporaryBrightnessStrategy();
+        }
+
+        BoostBrightnessStrategy getBoostBrightnessStrategy() {
+            return new BoostBrightnessStrategy();
+        }
+
+        InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
+            return new InvalidBrightnessStrategy();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
new file mode 100644
index 0000000..475ef50
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.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.display.brightness.strategy;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+
+/**
+ * Manages the brightness of the display when the system brightness boost is requested.
+ */
+public class BoostBrightnessStrategy implements DisplayBrightnessStrategy {
+
+    public BoostBrightnessStrategy() {
+    }
+
+    // Set the brightness to the maximum value when display brightness boost is requested
+    @Override
+    public DisplayBrightnessState updateBrightness(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+        // Todo(brup): Introduce a validator class and add validations before setting the brightness
+        DisplayBrightnessState displayBrightnessState =
+                BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_BOOST,
+                        PowerManager.BRIGHTNESS_MAX,
+                        PowerManager.BRIGHTNESS_MAX);
+        return displayBrightnessState;
+    }
+
+    @Override
+    public String getName() {
+        return "BoostBrightnessStrategy";
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.java
deleted file mode 100644
index 3be5933..0000000
--- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.java
+++ /dev/null
@@ -1,50 +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.display.brightness.strategy;
-
-import android.hardware.display.DisplayManagerInternal;
-
-import com.android.server.display.DisplayBrightnessState;
-
-import java.io.PrintWriter;
-
-/**
- * An interface to define the general skeleton of how a BrightnessModeStrategy should look like
- * This is responsible for deciding the DisplayBrightnessState that the display should change to,
- * not taking into account clamping that might be needed
- */
-public interface DisplayBrightnessModeStrategy {
-    /**
-     * Decides the DisplayBrightnessState that the system should change to.
-     *
-     * @param displayPowerRequest           The request to evaluate the updated brightness
-     * @param displayState                  The target displayState to which the system should
-     *                                      change to after processing the request
-     * @param displayBrightnessStateBuilder The DisplayBrightnessStateBuilder, consisting of
-     *                                      DisplayBrightnessState that have been constructed so far
-     */
-    DisplayBrightnessState.Builder updateBrightness(
-            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, int displayState,
-            DisplayBrightnessState.Builder displayBrightnessStateBuilder);
-
-    /**
-     * Used to dump the state.
-     *
-     * @param writer The PrintWriter used to dump the state.
-     */
-    void dump(PrintWriter writer);
-}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
new file mode 100644
index 0000000..27d04fd
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.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.server.display.brightness.strategy;
+
+import android.annotation.NonNull;
+import android.hardware.display.DisplayManagerInternal;
+
+import com.android.server.display.DisplayBrightnessState;
+
+/**
+ * Decides the DisplayBrighntessState that the display should change to based on strategy-specific
+ * logic within each implementation. Clamping should be done outside of DisplayBrightnessStrategy if
+ * not an integral part of the strategy.
+ */
+public interface DisplayBrightnessStrategy {
+    /**
+     * Decides the DisplayBrightnessState that the system should change to.
+     *
+     * @param displayPowerRequest The request to evaluate the updated brightness
+     */
+    DisplayBrightnessState updateBrightness(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest);
+
+    /**
+     * Returns the name of the Strategy
+     */
+    @NonNull
+    String getName();
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
new file mode 100644
index 0000000..0bc900b
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.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.server.display.brightness.strategy;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+
+/**
+ * Manages the brightness of the display when the system is in the doze state.
+ */
+public class DozeBrightnessStrategy implements DisplayBrightnessStrategy {
+
+    @Override
+    public DisplayBrightnessState updateBrightness(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+        // Todo(brup): Introduce a validator class and add validations before setting the brightness
+        return BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_DOZE,
+                displayPowerRequest.dozeScreenBrightness, displayPowerRequest.dozeScreenBrightness);
+    }
+
+    @Override
+    public String getName() {
+        return "DozeBrightnessStrategy";
+    }
+
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
new file mode 100644
index 0000000..612bbe9
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
@@ -0,0 +1,41 @@
+/*
+ * 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.display.brightness.strategy;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+
+/**
+ * Manages the brightness of the display when the system is in the invalid state.
+ */
+public class InvalidBrightnessStrategy implements DisplayBrightnessStrategy {
+    @Override
+    public DisplayBrightnessState updateBrightness(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+        return BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_UNKNOWN,
+                PowerManager.BRIGHTNESS_INVALID_FLOAT, PowerManager.BRIGHTNESS_INVALID_FLOAT);
+    }
+
+    @Override
+    public String getName() {
+        return "InvalidBrightnessStrategy";
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
new file mode 100644
index 0000000..f03f036
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
@@ -0,0 +1,42 @@
+/*
+ * 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.display.brightness.strategy;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+
+/**
+ * Manages the brightness of the display when the system brightness is overridden
+ */
+public class OverrideBrightnessStrategy implements DisplayBrightnessStrategy {
+    @Override
+    public DisplayBrightnessState updateBrightness(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+        // Todo(brup): Introduce a validator class and add validations before setting the brightness
+        return BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_OVERRIDE,
+                displayPowerRequest.screenBrightnessOverride,
+                displayPowerRequest.screenBrightnessOverride);
+    }
+
+    @Override
+    public String getName() {
+        return "OverrideBrightnessStrategy";
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
new file mode 100644
index 0000000..396fa06
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.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.server.display.brightness.strategy;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+
+/**
+ * Manages the brightness of the display when the system is in the ScreenOff state.
+ */
+public class ScreenOffBrightnessStrategy implements DisplayBrightnessStrategy {
+    @Override
+    public DisplayBrightnessState updateBrightness(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+        // Todo(brup): Introduce a validator class and add validations before setting the brightness
+        return BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_SCREEN_OFF,
+                PowerManager.BRIGHTNESS_OFF_FLOAT,
+                PowerManager.BRIGHTNESS_OFF_FLOAT);
+    }
+
+    @Override
+    public String getName() {
+        return "ScreenOffBrightnessStrategy";
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
new file mode 100644
index 0000000..f8063f3
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.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.display.brightness.strategy;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages the brightness of the display when the system brightness is temporary
+ */
+public class TemporaryBrightnessStrategy implements DisplayBrightnessStrategy {
+    // The temporary screen brightness. Typically set when a user is interacting with the
+    // brightness slider but hasn't settled on a choice yet. Set to
+    // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set.
+    private float mTemporaryScreenBrightness;
+
+    public TemporaryBrightnessStrategy() {
+        mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+    }
+
+    // Use the temporary screen brightness if there isn't an override, either from
+    // WindowManager or based on the display state.
+    @Override
+    public DisplayBrightnessState updateBrightness(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+        // Todo(brup): Introduce a validator class and add validations before setting the brightness
+        DisplayBrightnessState displayBrightnessState =
+                BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_TEMPORARY,
+                        mTemporaryScreenBrightness,
+                        mTemporaryScreenBrightness);
+        mTemporaryScreenBrightness = Float.NaN;
+        return displayBrightnessState;
+    }
+
+    @Override
+    public String getName() {
+        return "TemporaryBrightnessStrategy";
+    }
+
+    public float getTemporaryScreenBrightness() {
+        return mTemporaryScreenBrightness;
+    }
+
+    public void setTemporaryScreenBrightness(float temporaryScreenBrightness) {
+        mTemporaryScreenBrightness = temporaryScreenBrightness;
+    }
+
+    /**
+     * Dumps the state of this class.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println("TemporaryBrightnessStrategy:");
+        writer.println("  mTemporaryScreenBrightness:" + mTemporaryScreenBrightness);
+    }
+}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 21a8518..5824887 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -1682,6 +1682,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         @Override
         public boolean isSaturationActivated() {
+            super.isSaturationActivated_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
                 return !mGlobalSaturationTintController.isActivatedStateNotSet()
@@ -1694,6 +1696,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         @Override
         public boolean setAppSaturationLevel(String packageName, int level) {
+            super.setAppSaturationLevel_enforcePermission();
+
             final String callingPackageName = LocalServices.getService(PackageManagerInternal.class)
                     .getNameForUid(Binder.getCallingUid());
             final long token = Binder.clearCallingIdentity();
@@ -1706,6 +1710,8 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         public int getTransformCapabilities() {
+            super.getTransformCapabilities_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
                 return getTransformCapabilitiesInternal();
diff --git a/services/core/java/com/android/server/display/layout/DisplayIdProducer.java b/services/core/java/com/android/server/display/layout/DisplayIdProducer.java
new file mode 100644
index 0000000..3029757
--- /dev/null
+++ b/services/core/java/com/android/server/display/layout/DisplayIdProducer.java
@@ -0,0 +1,30 @@
+/*
+ * 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.display.layout;
+
+/**
+ * Interface for producing logical display ids.
+ */
+public interface DisplayIdProducer {
+
+    /**
+     * Generates a new display ID
+     * @param isDefault if requested display is the default display.
+     * @return the next unique logical display Id.
+     */
+    int getId(boolean isDefault);
+}
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index 7e16ea8..4a466fd 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -50,15 +50,33 @@
         return mDisplays.toString();
     }
 
+    @Override
+    public boolean equals(Object obj) {
+
+        if (!(obj instanceof  Layout)) {
+            return false;
+        }
+
+        Layout otherLayout = (Layout) obj;
+        return this.mDisplays.equals(otherLayout.mDisplays);
+    }
+
+    @Override
+    public int hashCode() {
+        return mDisplays.hashCode();
+    }
+
     /**
      * Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice.
      *
      * @param address Address of the device.
      * @param isDefault Indicates if the device is meant to be the default display.
+     * @param isEnabled Indicates if this display is usable and can be switched on
      * @return The new layout.
      */
     public Display createDisplayLocked(
-            @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled) {
+            @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
+            DisplayIdProducer idProducer) {
         if (contains(address)) {
             Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
             return null;
@@ -74,7 +92,7 @@
         // Note that the logical display ID is saved into the layout, so when switching between
         // different layouts, a logical display can be destroyed and later recreated with the
         // same logical display ID.
-        final int logicalDisplayId = assignDisplayIdLocked(isDefault);
+        final int logicalDisplayId = idProducer.getId(isDefault);
         final Display display = new Display(address, logicalDisplayId, isEnabled);
 
         mDisplays.add(display);
@@ -158,25 +176,64 @@
      * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
      */
     public static class Display {
+        public static final int POSITION_UNKNOWN = -1;
+        public static final int POSITION_FRONT = 0;
+        public static final int POSITION_REAR = 1;
+
         // Address of the display device to map to this display.
         private final DisplayAddress mAddress;
 
         // Logical Display ID to apply to this display.
         private final int mLogicalDisplayId;
 
-        // Indicates that this display is not usable and should remain off.
+        // Indicates if this display is usable and can be switched on
         private final boolean mIsEnabled;
 
+        // The direction the display faces
+        // {@link DeviceStateToLayoutMap.POSITION_FRONT} or
+        // {@link DeviceStateToLayoutMap.POSITION_REAR}.
+        // {@link DeviceStateToLayoutMap.POSITION_UNKNOWN} is unspecified.
+        private int mPosition;
+
         Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled) {
             mAddress = address;
             mLogicalDisplayId = logicalDisplayId;
             mIsEnabled = isEnabled;
+            mPosition = POSITION_UNKNOWN;
         }
 
         @Override
         public String toString() {
-            return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId
-                    + "(" + (mIsEnabled ? "ON" : "OFF") + ")}";
+            return "{"
+                    + "dispId: " + mLogicalDisplayId
+                    + "(" + (mIsEnabled ? "ON" : "OFF") + ")"
+                    + ", addr: " + mAddress
+                    +  ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition)
+                    + "}";
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof Display)) {
+                return false;
+            }
+
+            Display otherDisplay = (Display) obj;
+
+            return otherDisplay.mIsEnabled == this.mIsEnabled
+                    && otherDisplay.mPosition == this.mPosition
+                    && otherDisplay.mLogicalDisplayId == this.mLogicalDisplayId
+                    && this.mAddress.equals(otherDisplay.mAddress);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 1;
+            result = 31 * result + Boolean.hashCode(mIsEnabled);
+            result = 31 * result + mPosition;
+            result = 31 * result + mLogicalDisplayId;
+            result = 31 * result + mAddress.hashCode();
+            return result;
         }
 
         public DisplayAddress getAddress() {
@@ -190,5 +247,9 @@
         public boolean isEnabled() {
             return mIsEnabled;
         }
+
+        public void setPosition(int position) {
+            mPosition = position;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/utils/SensorUtils.java b/services/core/java/com/android/server/display/utils/SensorUtils.java
index cb40b40..4924ad5 100644
--- a/services/core/java/com/android/server/display/utils/SensorUtils.java
+++ b/services/core/java/com/android/server/display/utils/SensorUtils.java
@@ -33,6 +33,9 @@
      */
     public static Sensor findSensor(SensorManager sensorManager, String sensorType,
             String sensorName, int fallbackType) {
+        if ("".equals(sensorName) && "".equals(sensorType)) {
+            return null;
+        }
         final boolean isNameSpecified = !TextUtils.isEmpty(sensorName);
         final boolean isTypeSpecified = !TextUtils.isEmpty(sensorType);
         if (isNameSpecified || isTypeSpecified) {
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 4ca4817..ea09629 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -81,6 +81,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Service api for managing dreams.
@@ -116,10 +117,14 @@
     private final DreamUiEventLogger mDreamUiEventLogger;
     private final ComponentName mAmbientDisplayComponent;
     private final boolean mDismissDreamOnActivityStart;
-    private final boolean mDreamsOnlyEnabledForSystemUser;
+    private final boolean mDreamsOnlyEnabledForDockUser;
     private final boolean mDreamsEnabledByDefaultConfig;
     private final boolean mDreamsActivatedOnChargeByDefault;
     private final boolean mDreamsActivatedOnDockByDefault;
+    private final boolean mKeepDreamingWhenUndockedDefault;
+
+    private final CopyOnWriteArrayList<DreamManagerInternal.DreamManagerStateListener>
+            mDreamManagerStateListeners = new CopyOnWriteArrayList<>();
 
     @GuardedBy("mLock")
     private DreamRecord mCurrentDream;
@@ -211,11 +216,11 @@
         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);
+        mDreamsOnlyEnabledForDockUser =
+                mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForDockUser);
         mDismissDreamOnActivityStart = mContext.getResources().getBoolean(
                 R.bool.config_dismissDreamOnActivityStart);
 
@@ -226,6 +231,8 @@
         mDreamsActivatedOnDockByDefault = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
         mSettingsObserver = new SettingsObserver(mHandler);
+        mKeepDreamingWhenUndockedDefault = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_keepDreamingWhenUndocking);
     }
 
     @Override
@@ -292,15 +299,14 @@
             pw.println();
             pw.println("mCurrentDream=" + mCurrentDream);
             pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
-            pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
+            pw.println("mDreamsOnlyEnabledForDockUser=" + mDreamsOnlyEnabledForDockUser);
             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("mKeepDreamingWhenUndockedDefault=" + mKeepDreamingWhenUndockedDefault);
             pw.println("getDozeComponent()=" + getDozeComponent());
             pw.println();
 
@@ -329,7 +335,16 @@
         }
     }
 
-        /** Whether a real dream is occurring. */
+    private void reportKeepDreamingWhenUndockedChanged(boolean keepDreaming) {
+        mHandler.post(() -> {
+            for (DreamManagerInternal.DreamManagerStateListener listener
+                    : mDreamManagerStateListeners) {
+                listener.onKeepDreamingWhenUndockedChanged(keepDreaming);
+            }
+        });
+    }
+
+    /** Whether a real dream is occurring. */
     private boolean isDreamingInternal() {
         synchronized (mLock) {
             return mCurrentDream != null && !mCurrentDream.isPreview
@@ -491,10 +506,6 @@
         }
     }
 
-    private ComponentName getActiveDreamComponentInternal(boolean doze) {
-        return chooseDreamForUser(doze, ActivityManager.getCurrentUser());
-    }
-
     /**
      * If doze is true, returns the doze component for the user.
      * Otherwise, returns the system dream component, if present.
@@ -576,6 +587,7 @@
             }
 
             mSystemDreamComponent = componentName;
+            reportKeepDreamingWhenUndockedChanged(shouldKeepDreamingWhenUndocked());
 
             // Switch dream if currently dreaming and not dozing.
             if (isDreamingInternal() && !isDozingInternal()) {
@@ -585,6 +597,10 @@
         }
     }
 
+    private boolean shouldKeepDreamingWhenUndocked() {
+        return mKeepDreamingWhenUndockedDefault && mSystemDreamComponent == null;
+    }
+
     private ComponentName getDefaultDreamComponentForUser(int userId) {
         String name = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                 Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
@@ -606,7 +622,8 @@
     }
 
     private boolean dreamsEnabledForUser(int userId) {
-        return !mDreamsOnlyEnabledForSystemUser || (userId == UserHandle.USER_SYSTEM);
+        // TODO(b/257333623): Support non-system Dock Users in HSUM.
+        return !mDreamsOnlyEnabledForDockUser || (userId == UserHandle.USER_SYSTEM);
     }
 
     private ServiceInfo getServiceInfo(ComponentName name) {
@@ -647,7 +664,7 @@
                 .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, DREAM_WAKE_LOCK_TAG);
         final Binder dreamToken = mCurrentDream.token;
         mHandler.post(wakeLock.wrap(() -> {
-            mAtmInternal.notifyDreamStateChanged(true);
+            mAtmInternal.notifyActiveDreamChanged(name);
             mController.startDream(dreamToken, name, isPreviewMode, canDoze, userId, wakeLock,
                     mDreamOverlayServiceName, reason);
         }));
@@ -672,7 +689,7 @@
 
     @GuardedBy("mLock")
     private void cleanupDreamLocked() {
-        mHandler.post(() -> mAtmInternal.notifyDreamStateChanged(false /*dreaming*/));
+        mHandler.post(() -> mAtmInternal.notifyActiveDreamChanged(null));
 
         if (mCurrentDream == null) {
             return;
@@ -1013,13 +1030,20 @@
         }
 
         @Override
-        public ComponentName getActiveDreamComponent(boolean doze) {
-            return getActiveDreamComponentInternal(doze);
+        public void requestDream() {
+            requestDreamInternal();
         }
 
         @Override
-        public void requestDream() {
-            requestDreamInternal();
+        public void registerDreamManagerStateListener(DreamManagerStateListener listener) {
+            mDreamManagerStateListeners.add(listener);
+            // Initialize the listener's state.
+            listener.onKeepDreamingWhenUndockedChanged(shouldKeepDreamingWhenUndocked());
+        }
+
+        @Override
+        public void unregisterDreamManagerStateListener(DreamManagerStateListener listener) {
+            mDreamManagerStateListeners.remove(listener);
         }
     }
 
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/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 326d720..28dc318 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -26,6 +26,7 @@
 import android.graphics.fonts.FontManager;
 import android.graphics.fonts.FontUpdateRequest;
 import android.graphics.fonts.SystemFonts;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.ResultReceiver;
 import android.os.SharedMemory;
@@ -35,8 +36,10 @@
 import android.util.AndroidException;
 import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
+import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.graphics.fonts.IFontManager;
 import com.android.internal.security.VerityUtils;
@@ -47,7 +50,9 @@
 
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.PrintWriter;
 import java.nio.ByteBuffer;
 import java.nio.DirectByteBuffer;
@@ -68,6 +73,8 @@
     @RequiresPermission(Manifest.permission.UPDATE_FONTS)
     @Override
     public FontConfig getFontConfig() {
+        super.getFontConfig_enforcePermission();
+
         return getSystemFontConfig();
     }
 
@@ -153,9 +160,30 @@
     }
 
     private static class FsverityUtilImpl implements UpdatableFontDir.FsverityUtil {
+
+        private final String[] mDerCertPaths;
+
+        FsverityUtilImpl(String[] derCertPaths) {
+            mDerCertPaths = derCertPaths;
+        }
+
         @Override
-        public boolean hasFsverity(String filePath) {
-            return VerityUtils.hasFsverity(filePath);
+        public boolean isFromTrustedProvider(String fontPath, byte[] pkcs7Signature) {
+            final byte[] digest = VerityUtils.getFsverityDigest(fontPath);
+            if (digest == null) {
+                Log.w(TAG, "Failed to get fs-verity digest for " + fontPath);
+                return false;
+            }
+            for (String certPath : mDerCertPaths) {
+                try (InputStream is = new FileInputStream(certPath)) {
+                    if (VerityUtils.verifyPkcs7DetachedSignature(pkcs7Signature, digest, is)) {
+                        return true;
+                    }
+                } catch (IOException e) {
+                    Log.w(TAG, "Failed to read certificate file: " + certPath);
+                }
+            }
+            return false;
         }
 
         @Override
@@ -173,11 +201,15 @@
     @NonNull
     private final Context mContext;
 
+    private final boolean mIsSafeMode;
+
     private final Object mUpdatableFontDirLock = new Object();
 
+    private String mDebugCertFilePath = null;
+
     @GuardedBy("mUpdatableFontDirLock")
     @Nullable
-    private final UpdatableFontDir mUpdatableFontDir;
+    private UpdatableFontDir mUpdatableFontDir;
 
     // mSerializedFontMapLock can be acquired while holding mUpdatableFontDirLock.
     // mUpdatableFontDirLock should not be newly acquired while holding mSerializedFontMapLock.
@@ -193,22 +225,43 @@
             UpdatableFontDir.deleteAllFiles(new File(FONT_FILES_DIR), new File(CONFIG_XML_FILE));
         }
         mContext = context;
-        mUpdatableFontDir = createUpdatableFontDir(safeMode);
+        mIsSafeMode = safeMode;
         initialize();
     }
 
     @Nullable
-    private static UpdatableFontDir createUpdatableFontDir(boolean safeMode) {
+    private UpdatableFontDir createUpdatableFontDir() {
         // Never read updatable font files in safe mode.
-        if (safeMode) return null;
+        if (mIsSafeMode) return null;
         // If apk verity is supported, fs-verity should be available.
         if (!VerityUtils.isFsVeritySupported()) return null;
+
+        String[] certs = mContext.getResources().getStringArray(
+                R.array.config_fontManagerServiceCerts);
+
+        if (mDebugCertFilePath != null && (Build.IS_USERDEBUG || Build.IS_ENG)) {
+            String[] tmp = new String[certs.length + 1];
+            System.arraycopy(certs, 0, tmp, 0, certs.length);
+            tmp[certs.length] = mDebugCertFilePath;
+            certs = tmp;
+        }
+
         return new UpdatableFontDir(new File(FONT_FILES_DIR), new OtfFontFileParser(),
-                new FsverityUtilImpl(), new File(CONFIG_XML_FILE));
+                new FsverityUtilImpl(certs), new File(CONFIG_XML_FILE));
+    }
+
+    /**
+     * Add debug certificate to the cert list. This must be called only on userdebug/eng
+     * build.
+     * @param debugCertPath a debug certificate file path
+     */
+    public void addDebugCertificate(@Nullable String debugCertPath) {
+        mDebugCertFilePath = debugCertPath;
     }
 
     private void initialize() {
         synchronized (mUpdatableFontDirLock) {
+            mUpdatableFontDir = createUpdatableFontDir();
             if (mUpdatableFontDir == null) {
                 setSerializedFontMap(serializeSystemServerFontMap());
                 return;
@@ -231,12 +284,12 @@
 
     /* package */ void update(int baseVersion, List<FontUpdateRequest> requests)
             throws SystemFontException {
-        if (mUpdatableFontDir == null) {
-            throw new SystemFontException(
-                    FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
-                    "The font updater is disabled.");
-        }
         synchronized (mUpdatableFontDirLock) {
+            if (mUpdatableFontDir == null) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
+                        "The font updater is disabled.");
+            }
             // baseVersion == -1 only happens from shell command. This is filtered and treated as
             // error from SystemApi call.
             if (baseVersion != -1 && mUpdatableFontDir.getConfigVersion() != baseVersion) {
@@ -271,10 +324,10 @@
     }
 
     /* package */ Map<String, File> getFontFileMap() {
-        if (mUpdatableFontDir == null) {
-            return Collections.emptyMap();
-        }
         synchronized (mUpdatableFontDirLock) {
+            if (mUpdatableFontDir == null) {
+                return Collections.emptyMap();
+            }
             return mUpdatableFontDir.getPostScriptMap();
         }
     }
@@ -300,10 +353,10 @@
      * Returns an active system font configuration.
      */
     public @NonNull FontConfig getSystemFontConfig() {
-        if (mUpdatableFontDir == null) {
-            return SystemFonts.getSystemPreinstalledFontConfig();
-        }
         synchronized (mUpdatableFontDirLock) {
+            if (mUpdatableFontDir == null) {
+                return SystemFonts.getSystemPreinstalledFontConfig();
+            }
             return mUpdatableFontDir.getSystemFontConfig();
         }
     }
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index 88145bd..4cd0d6e 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -28,6 +28,7 @@
 import android.graphics.fonts.FontVariationAxis;
 import android.graphics.fonts.SystemFonts;
 import android.os.Binder;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.ShellCommand;
@@ -103,6 +104,10 @@
         w.println("update-family [family definition XML path]");
         w.println("    Update font families with the new definitions.");
         w.println();
+        w.println("install-debug-cert [cert file path]");
+        w.println("    Install debug certificate file. This command can be used only on userdebug");
+        w.println("    or eng device with root user.");
+        w.println();
         w.println("clear");
         w.println("    Remove all installed font files and reset to the initial state.");
         w.println();
@@ -322,6 +327,33 @@
         return 0;
     }
 
+    private int installCert(ShellCommand shell) throws SystemFontException {
+        if (!(Build.IS_USERDEBUG || Build.IS_ENG)) {
+            throw new SecurityException("Only userdebug/eng device can add debug certificate");
+        }
+        if (Binder.getCallingUid() != Process.ROOT_UID) {
+            throw new SecurityException("Only root can add debug certificate");
+        }
+
+        String certPath = shell.getNextArg();
+        if (certPath == null) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_INVALID_DEBUG_CERTIFICATE,
+                    "Cert file path argument is required.");
+        }
+        File file = new File(certPath);
+        if (!file.isFile()) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_INVALID_DEBUG_CERTIFICATE,
+                    "Cert file (" + file + ") is not found");
+        }
+
+        mService.addDebugCertificate(certPath);
+        mService.restart();
+        shell.getOutPrintWriter().println("Success");
+        return 0;
+    }
+
     private int update(ShellCommand shell) throws SystemFontException {
         String fontPath = shell.getNextArg();
         if (fontPath == null) {
@@ -494,6 +526,8 @@
                     return restart(shell);
                 case "status":
                     return status(shell);
+                case "install-debug-cert":
+                    return installCert(shell);
                 default:
                     return shell.handleDefaultCommands(cmd);
             }
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 743b4d9..457d5b7 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -40,6 +40,8 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -59,6 +61,8 @@
     private static final String TAG = "UpdatableFontDir";
     private static final String RANDOM_DIR_PREFIX = "~~";
 
+    private static final String FONT_SIGNATURE_FILE = "font.fsv_sig";
+
     /** Interface to mock font file access in tests. */
     interface FontFileParser {
         String getPostScriptName(File file) throws IOException;
@@ -72,7 +76,7 @@
 
     /** Interface to mock fs-verity in tests. */
     interface FsverityUtil {
-        boolean hasFsverity(String path);
+        boolean isFromTrustedProvider(String path, byte[] pkcs7Signature);
 
         void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException;
 
@@ -188,12 +192,35 @@
                     FileUtils.deleteContentsAndDir(dir);
                     continue;
                 }
+
+                File signatureFile = new File(dir, FONT_SIGNATURE_FILE);
+                if (!signatureFile.exists()) {
+                    Slog.i(TAG, "The signature file is missing.");
+                    FileUtils.deleteContentsAndDir(dir);
+                    continue;
+                }
+                byte[] signature;
+                try {
+                    signature = Files.readAllBytes(Paths.get(signatureFile.getAbsolutePath()));
+                } catch (IOException e) {
+                    Slog.e(TAG, "Failed to read signature file.");
+                    return;
+                }
+
                 File[] files = dir.listFiles();
-                if (files == null || files.length != 1) {
+                if (files == null || files.length != 2) {
                     Slog.e(TAG, "Unexpected files in dir: " + dir);
                     return;
                 }
-                FontFileInfo fontFileInfo = validateFontFile(files[0]);
+
+                File fontFile;
+                if (files[0].equals(signatureFile)) {
+                    fontFile = files[1];
+                } else {
+                    fontFile = files[0];
+                }
+
+                FontFileInfo fontFileInfo = validateFontFile(fontFile, signature);
                 if (fontConfig == null) {
                     fontConfig = getSystemFontConfig();
                 }
@@ -359,9 +386,25 @@
             } catch (ErrnoException e) {
                 throw new SystemFontException(
                         FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
-                        "Failed to change mode to 711", e);
+                        "Failed to change font file mode to 644", e);
             }
-            FontFileInfo fontFileInfo = validateFontFile(newFontFile);
+            File signatureFile = new File(newDir, FONT_SIGNATURE_FILE);
+            try (FileOutputStream out = new FileOutputStream(signatureFile)) {
+                out.write(pkcs7Signature);
+            } catch (IOException e) {
+                // TODO: Do we need new error code for signature write failure?
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+                        "Failed to write font signature file to storage.", e);
+            }
+            try {
+                Os.chmod(signatureFile.getAbsolutePath(), 0600);
+            } catch (ErrnoException e) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+                        "Failed to change the signature file mode to 600", e);
+            }
+            FontFileInfo fontFileInfo = validateFontFile(newFontFile, pkcs7Signature);
 
             // Try to create Typeface and treat as failure something goes wrong.
             try {
@@ -478,8 +521,9 @@
      * is higher than the currently used font.
      */
     @NonNull
-    private FontFileInfo validateFontFile(File file) throws SystemFontException {
-        if (!mFsverityUtil.hasFsverity(file.getAbsolutePath())) {
+    private FontFileInfo validateFontFile(File file, byte[] pkcs7Signature)
+            throws SystemFontException {
+        if (!mFsverityUtil.isFromTrustedProvider(file.getAbsolutePath(), pkcs7Signature)) {
             throw new SystemFontException(
                     FontManager.RESULT_ERROR_VERIFICATION_FAILURE,
                     "Font validation failed. Fs-verity is not enabled: " + file);
diff --git a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
index 049a339..ccb2633 100644
--- a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.hdmi;
 
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 
 /**
@@ -27,12 +29,16 @@
     private static final int STATE_ARC_TERMINATED = 2;
 
     // the required maximum response time specified in CEC 9.2
-    private static final int TIMEOUT_MS = 1000;
+    public static final int TIMEOUT_MS = 1000;
 
     ArcTerminationActionFromAvr(HdmiCecLocalDevice source) {
         super(source);
     }
 
+    ArcTerminationActionFromAvr(HdmiCecLocalDevice source, IHdmiControlCallback callback) {
+        super(source, callback);
+    }
+
     @Override
     boolean start() {
         mState = STATE_WAITING_FOR_INITIATE_ARC_RESPONSE;
@@ -47,10 +53,19 @@
             return false;
         }
         switch (cmd.getOpcode()) {
+            case Constants.MESSAGE_FEATURE_ABORT:
+                int originalOpcode = cmd.getParams()[0] & 0xFF;
+                if (originalOpcode == Constants.MESSAGE_TERMINATE_ARC) {
+                    mState = STATE_ARC_TERMINATED;
+                    audioSystem().processArcTermination();
+                    finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+                    return true;
+                }
+                return false;
             case Constants.MESSAGE_REPORT_ARC_TERMINATED:
                 mState = STATE_ARC_TERMINATED;
                 audioSystem().processArcTermination();
-                finish();
+                finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
                 return true;
         }
         return false;
@@ -79,13 +94,15 @@
                         audioSystem().setArcStatus(false);
                     }
                     HdmiLogger.debug("Terminate ARC was not successfully sent.");
-                    finish();
+                    finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
                 }
             });
     }
 
     private void handleTerminateArcTimeout() {
+        // Disable ARC if TV didn't respond with <Report ARC Terminated> in time.
+        audioSystem().setArcStatus(false);
         HdmiLogger.debug("handleTerminateArcTimeout");
-        finish();
+        finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index 79820a2..6925507 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -16,7 +16,7 @@
 
 package com.android.server.hdmi;
 
-import static android.hardware.hdmi.HdmiControlManager.CecSettingName;
+import static android.hardware.hdmi.HdmiControlManager.SettingName;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -45,7 +45,10 @@
 
 /**
  * The {@link HdmiCecConfig} class is used for getting information about
- * available HDMI CEC settings.
+ * available HDMI control settings, including CEC settings and eARC settings.
+ *
+ * TODO(b/240379115): rename this class and related methods in this package to represent that the
+ * settings storage mechanism applies to all HDMI control settings and not just CEC settings.
  */
 public class HdmiCecConfig {
     private static final String TAG = "HdmiCecConfig";
@@ -105,7 +108,7 @@
          *
          * @param setting name of a CEC setting that changed
          */
-        void onChange(@NonNull @CecSettingName String setting);
+        void onChange(@NonNull @SettingName String setting);
     }
 
     /**
@@ -204,21 +207,21 @@
 
     protected class Setting {
         @NonNull private final Context mContext;
-        @NonNull private final @CecSettingName String mName;
+        @NonNull private final @SettingName String mName;
         private final boolean mUserConfigurable;
 
         private Value mDefaultValue = null;
         private List<Value> mAllowedValues = new ArrayList<>();
 
         Setting(@NonNull Context context,
-                @NonNull @CecSettingName String name,
+                @NonNull @SettingName String name,
                 int userConfResId) {
             mContext = context;
             mName = name;
             mUserConfigurable = mContext.getResources().getBoolean(userConfResId);
         }
 
-        public @CecSettingName String getName() {
+        public @SettingName String getName() {
             return mName;
         }
 
@@ -315,6 +318,16 @@
                 R.bool.config_cecRoutingControlDisabled_allowed,
                 R.bool.config_cecRoutingControlDisabled_default);
 
+        Setting soundbarMode = registerSetting(
+                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+                R.bool.config_cecSoundbarMode_userConfigurable);
+        soundbarMode.registerValue(HdmiControlManager.SOUNDBAR_MODE_ENABLED,
+                R.bool.config_cecSoundbarModeEnabled_allowed,
+                R.bool.config_cecSoundbarModeEnabled_default);
+        soundbarMode.registerValue(HdmiControlManager.SOUNDBAR_MODE_DISABLED,
+                R.bool.config_cecSoundbarModeDisabled_allowed,
+                R.bool.config_cecSoundbarModeDisabled_default);
+
         Setting powerControlMode = registerSetting(
                 HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
                 R.bool.config_cecPowerControlMode_userConfigurable);
@@ -663,6 +676,16 @@
                 R.bool.config_cecQuerySadMaxDisabled_allowed,
                 R.bool.config_cecQuerySadMaxDisabled_default);
 
+        Setting earcEnabled = registerSetting(
+                HdmiControlManager.SETTING_NAME_EARC_ENABLED,
+                R.bool.config_earcEnabled_userConfigurable);
+        earcEnabled.registerValue(HdmiControlManager.EARC_FEATURE_ENABLED,
+                R.bool.config_earcFeatureEnabled_allowed,
+                R.bool.config_earcFeatureEnabled_default);
+        earcEnabled.registerValue(HdmiControlManager.EARC_FEATURE_DISABLED,
+                R.bool.config_earcFeatureDisabled_allowed,
+                R.bool.config_earcFeatureDisabled_default);
+
         verifySettings();
     }
 
@@ -670,7 +693,7 @@
         this(context, new StorageAdapter(context));
     }
 
-    private Setting registerSetting(@NonNull @CecSettingName String name,
+    private Setting registerSetting(@NonNull @SettingName String name,
                                int userConfResId) {
         Setting setting = new Setting(mContext, name, userConfResId);
         mSettings.put(name, setting);
@@ -701,6 +724,8 @@
                 return STORAGE_SHARED_PREFS;
             case HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL:
                 return STORAGE_SHARED_PREFS;
+            case HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE:
+                return STORAGE_SHARED_PREFS;
             case HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE:
                 return STORAGE_SHARED_PREFS;
             case HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE:
@@ -760,6 +785,8 @@
                 return STORAGE_SHARED_PREFS;
             case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX:
                 return STORAGE_SHARED_PREFS;
+            case HdmiControlManager.SETTING_NAME_EARC_ENABLED:
+                return STORAGE_SHARED_PREFS;
             default:
                 throw new VerificationException("Invalid CEC setting '" + setting.getName()
                         + "' storage.");
@@ -774,6 +801,8 @@
                 return setting.getName();
             case HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL:
                 return setting.getName();
+            case HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE:
+                return setting.getName();
             case HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE:
                 return setting.getName();
             case HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE:
@@ -833,6 +862,8 @@
                 return setting.getName();
             case HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX:
                 return setting.getName();
+            case HdmiControlManager.SETTING_NAME_EARC_ENABLED:
+                return setting.getName();
             default:
                 throw new VerificationException("Invalid CEC setting '" + setting.getName()
                     + "' storage key.");
@@ -871,14 +902,6 @@
         }
     }
 
-    private void notifySettingChanged(@NonNull @CecSettingName String name) {
-        Setting setting = getSetting(name);
-        if (setting == null) {
-            throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
-        }
-        notifySettingChanged(setting);
-    }
-
     protected void notifySettingChanged(@NonNull Setting setting) {
         synchronized (mLock) {
             ArrayMap<SettingChangeListener, Executor> listeners =
@@ -902,7 +925,7 @@
     /**
      * Register change listener for a given setting name using DirectExecutor.
      */
-    public void registerChangeListener(@NonNull @CecSettingName String name,
+    public void registerChangeListener(@NonNull @SettingName String name,
                                        SettingChangeListener listener) {
         registerChangeListener(name, listener, ConcurrentUtils.DIRECT_EXECUTOR);
     }
@@ -910,7 +933,7 @@
     /**
      * Register change listener for a given setting name and executor.
      */
-    public void registerChangeListener(@NonNull @CecSettingName String name,
+    public void registerChangeListener(@NonNull @SettingName String name,
                                        SettingChangeListener listener,
                                        Executor executor) {
         Setting setting = getSetting(name);
@@ -933,7 +956,7 @@
     /**
      * Remove change listener for a given setting name.
      */
-    public void removeChangeListener(@NonNull @CecSettingName String name,
+    public void removeChangeListener(@NonNull @SettingName String name,
                                      SettingChangeListener listener) {
         Setting setting = getSetting(name);
         if (setting == null) {
@@ -954,14 +977,14 @@
     /**
      * Returns a list of all settings based on the XML metadata.
      */
-    public @CecSettingName List<String> getAllSettings() {
+    public @SettingName List<String> getAllSettings() {
         return new ArrayList<>(mSettings.keySet());
     }
 
     /**
      * Returns a list of user-modifiable settings based on the XML metadata.
      */
-    public @CecSettingName List<String> getUserSettings() {
+    public @SettingName List<String> getUserSettings() {
         List<String> settings = new ArrayList<>();
         for (Setting setting: mSettings.values()) {
             if (setting.getUserConfigurable()) {
@@ -975,7 +998,7 @@
      * For a given setting name returns true if and only if the value type of that
      * setting is a string.
      */
-    public boolean isStringValueType(@NonNull @CecSettingName String name) {
+    public boolean isStringValueType(@NonNull @SettingName String name) {
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
@@ -987,7 +1010,7 @@
      * For a given setting name returns true if and only if the value type of that
      * setting is an int.
      */
-    public boolean isIntValueType(@NonNull @CecSettingName String name) {
+    public boolean isIntValueType(@NonNull @SettingName String name) {
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
@@ -998,7 +1021,7 @@
     /**
      * For a given setting name returns values that are allowed for that setting (string).
      */
-    public List<String> getAllowedStringValues(@NonNull @CecSettingName String name) {
+    public List<String> getAllowedStringValues(@NonNull @SettingName String name) {
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
@@ -1017,7 +1040,7 @@
     /**
      * For a given setting name returns values that are allowed for that setting (string).
      */
-    public List<Integer> getAllowedIntValues(@NonNull @CecSettingName String name) {
+    public List<Integer> getAllowedIntValues(@NonNull @SettingName String name) {
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
@@ -1036,7 +1059,7 @@
     /**
      * For a given setting name returns the default value for that setting (string).
      */
-    public String getDefaultStringValue(@NonNull @CecSettingName String name) {
+    public String getDefaultStringValue(@NonNull @SettingName String name) {
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
@@ -1051,7 +1074,7 @@
     /**
      * For a given setting name returns the default value for that setting (int).
      */
-    public int getDefaultIntValue(@NonNull @CecSettingName String name) {
+    public int getDefaultIntValue(@NonNull @SettingName String name) {
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
@@ -1066,7 +1089,7 @@
     /**
      * For a given setting name returns the current value of that setting (string).
      */
-    public String getStringValue(@NonNull @CecSettingName String name) {
+    public String getStringValue(@NonNull @SettingName String name) {
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
@@ -1082,7 +1105,7 @@
     /**
      * For a given setting name returns the current value of that setting (int).
      */
-    public int getIntValue(@NonNull @CecSettingName String name) {
+    public int getIntValue(@NonNull @SettingName String name) {
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
@@ -1100,7 +1123,7 @@
     /**
      * For a given setting name and value sets the current value of that setting (string).
      */
-    public void setStringValue(@NonNull @CecSettingName String name, @NonNull String value) {
+    public void setStringValue(@NonNull @SettingName String name, @NonNull String value) {
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
@@ -1123,7 +1146,7 @@
     /**
      * For a given setting name and value sets the current value of that setting (int).
      */
-    public void setIntValue(@NonNull @CecSettingName String name, int value) {
+    public void setIntValue(@NonNull @SettingName String name, int value) {
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 5c1b33c..50edd0e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -636,7 +636,7 @@
     void onReceiveCommand(HdmiCecMessage message) {
         assertRunOnServiceThread();
         if (((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_IGNORE) == 0)
-                && !mService.isControlEnabled()
+                && !mService.isCecControlEnabled()
                 && !HdmiCecMessage.isCecTransportMessage(message.getOpcode())) {
             if ((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_LOG_WARNING) != 0) {
                 HdmiLogger.warning("Message " + message + " received when cec disabled");
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 2622cef..b4d7fb9 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -698,7 +698,7 @@
 
     protected void reportFeatures() {
         List<Integer> localDeviceTypes = new ArrayList<>();
-        for (HdmiCecLocalDevice localDevice : mService.getAllLocalDevices()) {
+        for (HdmiCecLocalDevice localDevice : mService.getAllCecLocalDevices()) {
             localDeviceTypes.add(localDevice.mDeviceType);
         }
 
@@ -728,7 +728,7 @@
     protected int handleStandby(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // Seq #12
-        if (mService.isControlEnabled()
+        if (mService.isCecControlEnabled()
                 && !mService.isProhibitMode()
                 && mService.isPowerOnOrTransient()) {
             mService.standby();
@@ -1359,7 +1359,8 @@
         List<SendKeyAction> action = getActions(SendKeyAction.class);
         int logicalAddress = findAudioReceiverAddress();
         if (logicalAddress == Constants.ADDR_INVALID
-                || logicalAddress == mDeviceInfo.getLogicalAddress()) {
+                || mService.getAllCecLocalDevices().stream().anyMatch(
+                        device -> device.getDeviceInfo().getLogicalAddress() == logicalAddress)) {
             // Don't send key event to invalid device or itself.
             Slog.w(
                     TAG,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 32ff5e22..a026c4b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -226,6 +226,8 @@
     @Override
     @ServiceThreadOnly
     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
+        terminateAudioReturnChannel();
+
         super.disableDevice(initiatedByCec, callback);
         assertRunOnServiceThread();
         mService.unregisterTvInputCallback(mTvInputCallback);
@@ -456,8 +458,16 @@
             HdmiLogger.debug("ARC is not established between TV and AVR device");
             return Constants.ABORT_NOT_IN_CORRECT_MODE;
         } else {
-            removeAction(ArcTerminationActionFromAvr.class);
-            addAndStartAction(new ArcTerminationActionFromAvr(this));
+            if (!getActions(ArcTerminationActionFromAvr.class).isEmpty()
+                    && !getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.isEmpty()) {
+                IHdmiControlCallback callback =
+                        getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0);
+                removeAction(ArcTerminationActionFromAvr.class);
+                addAndStartAction(new ArcTerminationActionFromAvr(this, callback));
+            } else {
+                removeAction(ArcTerminationActionFromAvr.class);
+                addAndStartAction(new ArcTerminationActionFromAvr(this));
+            }
             return Constants.HANDLED;
         }
     }
@@ -884,7 +894,7 @@
     private void notifyArcStatusToAudioService(boolean enabled) {
         // Note that we don't set any name to ARC.
         mService.getAudioManager()
-            .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
+            .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI_ARC, enabled ? 1 : 0, "", "");
     }
 
     void reportAudioStatus(int source) {
@@ -1042,7 +1052,7 @@
             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
             return;
         }
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             setRoutingPort(portId);
             setLocalActivePort(portId);
             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
@@ -1088,6 +1098,16 @@
         }
     }
 
+    private void terminateAudioReturnChannel() {
+        // remove pending initiation actions
+        removeAction(ArcInitiationActionFromAvr.class);
+        if (!isArcEnabled()
+                || !mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
+            return;
+        }
+        addAndStartAction(new ArcTerminationActionFromAvr(this));
+    }
+
     /** Reports if System Audio Mode is supported by the connected TV */
     interface TvSystemAudioModeSupportedCallback {
 
@@ -1312,6 +1332,9 @@
     @ServiceThreadOnly
     private void launchDeviceDiscovery() {
         assertRunOnServiceThread();
+        if (mService.isDeviceDiscoveryHandledByPlayback()) {
+            return;
+        }
         if (hasAction(DeviceDiscoveryAction.class)) {
             Slog.i(TAG, "Device Discovery Action is in progress. Restarting.");
             removeAction(DeviceDiscoveryAction.class);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index e6c2e7c..3ec3f94 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -137,7 +137,7 @@
                         // Since we removed all devices when it starts and device discovery action
                         // does not poll local devices, we should put device info of local device
                         // manually here.
-                        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
+                        for (HdmiCecLocalDevice device : mService.getAllCecLocalDevices()) {
                             mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo());
                         }
 
@@ -190,7 +190,7 @@
         if (isAlreadyActiveSource(targetDevice, targetAddress, callback)) {
             return;
         }
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             setActiveSource(targetDevice, "HdmiCecLocalDevicePlayback#deviceSelect()");
             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
             return;
@@ -239,7 +239,7 @@
     @ServiceThreadOnly
     protected void onStandby(boolean initiatedByCec, int standbyAction) {
         assertRunOnServiceThread();
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             return;
         }
         boolean wasActiveSource = isActiveSource();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 9bce471f..96e7b03 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -259,7 +259,7 @@
             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
             return;
         }
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             setActiveSource(targetDevice, "HdmiCecLocalDeviceTv#deviceSelect()");
             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
             return;
@@ -272,7 +272,7 @@
     private void handleSelectInternalSource() {
         assertRunOnServiceThread();
         // Seq #18
-        if (mService.isControlEnabled()
+        if (mService.isCecControlEnabled()
                 && getActiveSource().logicalAddress != getDeviceInfo().getLogicalAddress()) {
             updateActiveSource(
                     getDeviceInfo().getLogicalAddress(),
@@ -371,7 +371,7 @@
             return;
         }
         getActiveSource().invalidate();
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             setActivePortId(portId);
             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
             return;
@@ -694,7 +694,7 @@
                         // Since we removed all devices when it starts and
                         // device discovery action does not poll local devices,
                         // we should put device info of local device manually here
-                        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
+                        for (HdmiCecLocalDevice device : mService.getAllCecLocalDevices()) {
                             mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo());
                         }
 
@@ -742,7 +742,7 @@
     // Seq #32
     void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
         assertRunOnServiceThread();
-        if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
+        if (!mService.isCecControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
             setSystemAudioMode(false);
             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
             return;
@@ -837,7 +837,7 @@
     void enableAudioReturnChannel(boolean enabled) {
         assertRunOnServiceThread();
         HdmiDeviceInfo avr = getAvrDeviceInfo();
-        if (avr != null) {
+        if (avr != null && avr.getPortId() != Constants.INVALID_PORT_ID) {
             mService.enableAudioReturnChannel(avr.getPortId(), enabled);
         }
     }
@@ -1181,7 +1181,7 @@
     }
 
     private boolean isMessageForSystemAudio(HdmiCecMessage message) {
-        return mService.isControlEnabled()
+        return mService.isCecControlEnabled()
                 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM
                 && (message.getDestination() == Constants.ADDR_TV
                         || message.getDestination() == Constants.ADDR_BROADCAST)
@@ -1330,25 +1330,37 @@
         removeAction(SystemAudioAutoInitiationAction.class);
         removeAction(VolumeControlAction.class);
 
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             setSystemAudioMode(false);
         }
     }
 
     @ServiceThreadOnly
+    private void forceDisableArcOnAllPins() {
+        List<HdmiPortInfo> ports = mService.getPortInfo();
+        for (HdmiPortInfo port : ports) {
+            if (isArcFeatureEnabled(port.getId())) {
+                mService.enableAudioReturnChannel(port.getId(), false);
+            }
+        }
+    }
+
+    @ServiceThreadOnly
     private void disableArcIfExist() {
         assertRunOnServiceThread();
         HdmiDeviceInfo avr = getAvrDeviceInfo();
         if (avr == null) {
             return;
         }
-        disableArc();
 
         // Seq #44.
         removeAllRunningArcAction();
         if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) {
             addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
         }
+
+        // Disable ARC Pin earlier, prevent the case where AVR doesn't send <Terminate ARC> in time
+        forceDisableArcOnAllPins();
     }
 
     @ServiceThreadOnly
@@ -1364,7 +1376,7 @@
     protected void onStandby(boolean initiatedByCec, int standbyAction) {
         assertRunOnServiceThread();
         // Seq #11
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             return;
         }
         boolean sendStandbyOnSleep =
@@ -1403,7 +1415,7 @@
     @Constants.HandleMessageResult
     int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
         assertRunOnServiceThread();
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
             return Constants.ABORT_NOT_IN_CORRECT_MODE;
@@ -1432,7 +1444,7 @@
     @ServiceThreadOnly
     void stopOneTouchRecord(int recorderAddress) {
         assertRunOnServiceThread();
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
             return;
@@ -1466,7 +1478,7 @@
     @ServiceThreadOnly
     void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
         assertRunOnServiceThread();
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
             announceTimerRecordingResult(recorderAddress,
                     TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
@@ -1502,7 +1514,7 @@
     @ServiceThreadOnly
     void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
         assertRunOnServiceThread();
-        if (!mService.isControlEnabled()) {
+        if (!mService.isCecControlEnabled()) {
             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
             announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE);
             return;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecPowerStatusController.java b/services/core/java/com/android/server/hdmi/HdmiCecPowerStatusController.java
index 552ff37..f819f00 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecPowerStatusController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecPowerStatusController.java
@@ -75,7 +75,7 @@
     }
 
     private void sendReportPowerStatus(int powerStatus) {
-        for (HdmiCecLocalDevice localDevice : mHdmiControlService.getAllLocalDevices()) {
+        for (HdmiCecLocalDevice localDevice : mHdmiControlService.getAllCecLocalDevices()) {
             mHdmiControlService.sendCecCommand(
                     HdmiCecMessageBuilder.buildReportPowerStatus(
                             localDevice.getDeviceInfo().getLogicalAddress(),
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 1ae1b5b..2f15e57 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -19,6 +19,8 @@
 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
 import static android.hardware.hdmi.HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
+import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_DISABLED;
+import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_ENABLED;
 
 import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
 import static com.android.server.hdmi.Constants.DISABLED;
@@ -188,6 +190,7 @@
     static final int INITIATED_BY_SCREEN_ON = 2;
     static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
     static final int INITIATED_BY_HOTPLUG = 4;
+    static final int INITIATED_BY_SOUNDBAR_MODE = 5;
 
     // The reason code representing the intent action that drives the standby
     // procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or
@@ -336,8 +339,8 @@
     // Used to synchronize the access to the service.
     private final Object mLock = new Object();
 
-    // Type of logical devices hosted in the system. Stored in the unmodifiable list.
-    private final List<Integer> mLocalDevices;
+    // Type of CEC logical devices hosted in the system. Stored in the unmodifiable list.
+    private final List<Integer> mCecLocalDevices;
 
     // List of records for HDMI control status change listener for death monitoring.
     @GuardedBy("mLock")
@@ -496,7 +499,7 @@
     @VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes,
             AudioDeviceVolumeManagerWrapperInterface audioDeviceVolumeManager) {
         super(context);
-        mLocalDevices = deviceTypes;
+        mCecLocalDevices = deviceTypes;
         mSettingsObserver = new SettingsObserver(mHandler);
         mHdmiCecConfig = new HdmiCecConfig(context);
         mAudioDeviceVolumeManager = audioDeviceVolumeManager;
@@ -504,7 +507,7 @@
 
     public HdmiControlService(Context context) {
         super(context);
-        mLocalDevices = readDeviceTypes();
+        mCecLocalDevices = readDeviceTypes();
         mSettingsObserver = new SettingsObserver(mHandler);
         mHdmiCecConfig = new HdmiCecConfig(context);
     }
@@ -666,7 +669,7 @@
                     public void onChange(String setting) {
                         @HdmiControlManager.HdmiCecControl int enabled = mHdmiCecConfig.getIntValue(
                                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
-                        setControlEnabled(enabled);
+                        setCecEnabled(enabled);
                     }
                 }, mServiceThreadExecutor);
         mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
@@ -693,6 +696,14 @@
                         }
                     }
                 }, mServiceThreadExecutor);
+        mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+                new HdmiCecConfig.SettingChangeListener() {
+                    @Override
+                    public void onChange(String setting) {
+                        setSoundbarMode(mHdmiCecConfig.getIntValue(
+                                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE));
+                    }
+                }, mServiceThreadExecutor);
         mHdmiCecConfig.registerChangeListener(
                 HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
                 new HdmiCecConfig.SettingChangeListener() {
@@ -747,7 +758,7 @@
             mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
             // Start all actions that were queued because the device was in standby
             if (mAddressAllocated) {
-                for (HdmiCecLocalDevice localDevice : getAllLocalDevices()) {
+                for (HdmiCecLocalDevice localDevice : getAllCecLocalDevices()) {
                     localDevice.startQueuedActions();
                 }
             }
@@ -770,6 +781,11 @@
     }
 
     @VisibleForTesting
+    void setAudioManager(AudioManager audioManager) {
+        mAudioManager = audioManager;
+    }
+
+    @VisibleForTesting
     void setCecController(HdmiCecController cecController) {
         mCecController = cecController;
     }
@@ -847,6 +863,61 @@
     }
 
     /**
+     * Triggers the address allocation that states the presence of a local device audio system in
+     * the network.
+     */
+    @VisibleForTesting
+    public void setSoundbarMode(final int settingValue) {
+        HdmiCecLocalDevicePlayback playback = playback();
+        HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
+        if (playback == null) {
+            Slog.w(TAG, "Device type not compatible to change soundbar mode.");
+            return;
+        }
+        if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
+            Slog.w(TAG, "Device type doesn't support ARC.");
+            return;
+        }
+        boolean isArcEnabled = false;
+        if (settingValue == SOUNDBAR_MODE_DISABLED && audioSystem != null) {
+            isArcEnabled = audioSystem.isArcEnabled();
+            if (isSystemAudioActivated()) {
+                audioSystem.terminateSystemAudioMode();
+            }
+            if (isArcEnabled) {
+                if (audioSystem.hasAction(ArcTerminationActionFromAvr.class)) {
+                    audioSystem.removeAction(ArcTerminationActionFromAvr.class);
+                }
+                audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem,
+                        new IHdmiControlCallback.Stub() {
+                            @Override
+                            public void onComplete(int result) {
+                                mAddressAllocated = false;
+                                initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
+                            }
+                        }));
+            }
+        }
+        if (!isArcEnabled) {
+            mAddressAllocated = false;
+            initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
+        }
+    }
+
+    /**
+     * Checks if the Device Discovery is handled by the local device playback.
+     * See {@link HdmiCecLocalDeviceAudioSystem#launchDeviceDiscovery}.
+     */
+    public boolean isDeviceDiscoveryHandledByPlayback() {
+        HdmiCecLocalDevicePlayback playback = playback();
+        if (playback != null && (playback.hasAction(DeviceDiscoveryAction.class)
+                || playback.hasAction(HotplugDetectionAction.class))) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Called when the initialization of local devices is complete.
      */
     private void onInitializeCecComplete(int initiatedBy) {
@@ -866,7 +937,7 @@
                 break;
             case INITIATED_BY_SCREEN_ON:
                 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
-                final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
+                final List<HdmiCecLocalDevice> devices = getAllCecLocalDevices();
                 for (HdmiCecLocalDevice device : devices) {
                     device.onInitializeCecComplete(initiatedBy);
                 }
@@ -990,15 +1061,33 @@
 
         mCecController.enableSystemCecControl(true);
         mCecController.setLanguage(mMenuLanguage);
-        initializeLocalDevices(initiatedBy);
+        initializeCecLocalDevices(initiatedBy);
+    }
+
+    /**
+     * If the Soundbar mode is turned on, adds the local device type audio system in the list of
+     * local devices types. This method is called when the local devices are initialized such that
+     * the list of local devices is in sync with the Soundbar mode setting.
+     * @return the list of integer device types
+     */
+    @ServiceThreadOnly
+    private List<Integer> getCecLocalDeviceTypes() {
+        ArrayList<Integer> allLocalDeviceTypes = new ArrayList<>(mCecLocalDevices);
+        if (mHdmiCecConfig.getIntValue(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
+                == SOUNDBAR_MODE_ENABLED
+                && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
+                && SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
+            allLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        }
+        return allLocalDeviceTypes;
     }
 
     @ServiceThreadOnly
-    private void initializeLocalDevices(final int initiatedBy) {
+    private void initializeCecLocalDevices(final int initiatedBy) {
         assertRunOnServiceThread();
         // A container for [Device type, Local device info].
         ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
-        for (int type : mLocalDevices) {
+        for (int type : getCecLocalDeviceTypes()) {
             HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type);
             if (localDevice == null) {
                 localDevice = HdmiCecLocalDevice.create(this, type);
@@ -1008,7 +1097,7 @@
         }
         // It's now safe to flush existing local devices from mCecController since they were
         // already moved to 'localDevices'.
-        clearLocalDevices();
+        clearCecLocalDevices();
         allocateLogicalAddress(localDevices, initiatedBy);
     }
 
@@ -1051,9 +1140,10 @@
 
                             // Address allocation completed for all devices. Notify each device.
                             if (allocatingDevices.size() == ++finished[0]) {
-                                if (initiatedBy != INITIATED_BY_HOTPLUG) {
-                                    // In case of the hotplug we don't call
-                                    // onInitializeCecComplete()
+                                if (initiatedBy != INITIATED_BY_HOTPLUG
+                                        && initiatedBy != INITIATED_BY_SOUNDBAR_MODE) {
+                                    // In case of the hotplug or soundbar mode setting toggle
+                                    // we don't call onInitializeCecComplete()
                                     // since we reallocate the logical address only.
                                     onInitializeCecComplete(initiatedBy);
                                 }
@@ -1331,7 +1421,7 @@
      * Returns whether the source address of a message is a local logical address.
      */
     private boolean sourceAddressIsLocal(HdmiCecMessage message) {
-        for (HdmiCecLocalDevice device : getAllLocalDevices()) {
+        for (HdmiCecLocalDevice device : getAllCecLocalDevices()) {
             if (message.getSource() == device.getDeviceInfo().getLogicalAddress()
                     && message.getSource() != Constants.ADDR_UNREGISTERED) {
                 HdmiLogger.warning(
@@ -1413,7 +1503,7 @@
         if (connected && !isTvDevice()
                 && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
             ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
-            for (int type : mLocalDevices) {
+            for (int type : getCecLocalDeviceTypes()) {
                 HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type);
                 if (localDevice == null) {
                     localDevice = HdmiCecLocalDevice.create(this, type);
@@ -1461,7 +1551,7 @@
         return strategy | iterationStrategy;
     }
 
-    List<HdmiCecLocalDevice> getAllLocalDevices() {
+    List<HdmiCecLocalDevice> getAllCecLocalDevices() {
         assertRunOnServiceThread();
         return mHdmiCecNetwork.getLocalDeviceList();
     }
@@ -1484,7 +1574,7 @@
         if (physicalAddress == getPhysicalAddress()) {
             return;
         }
-        for (HdmiCecLocalDevice device : getAllLocalDevices()) {
+        for (HdmiCecLocalDevice device : getAllCecLocalDevices()) {
             if (device.getDeviceInfo().getLogicalAddress() == logicalAddress) {
                 HdmiLogger.debug("allocate logical address for " + device.getDeviceInfo());
                 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
@@ -1555,7 +1645,7 @@
     // Set the display name in HdmiDeviceInfo of the current devices to content provided by
     // Global.DEVICE_NAME. Only set and broadcast if the new name is different.
     private void setDisplayName(String newDisplayName) {
-        for (HdmiCecLocalDevice device : getAllLocalDevices()) {
+        for (HdmiCecLocalDevice device : getAllCecLocalDevices()) {
             HdmiDeviceInfo deviceInfo = device.getDeviceInfo();
             if (deviceInfo.getDisplayName().equals(newDisplayName)) {
                 continue;
@@ -1816,10 +1906,10 @@
         @Override
         public int[] getSupportedTypes() {
             initBinderCall();
-            // mLocalDevices is an unmodifiable list - no lock necesary.
-            int[] localDevices = new int[mLocalDevices.size()];
+            // mCecLocalDevices is an unmodifiable list - no lock necessary.
+            int[] localDevices = new int[mCecLocalDevices.size()];
             for (int i = 0; i < localDevices.length; ++i) {
-                localDevices[i] = mLocalDevices.get(i);
+                localDevices[i] = mCecLocalDevices.get(i);
             }
             return localDevices;
         }
@@ -2379,7 +2469,7 @@
             runOnServiceThread(new Runnable() {
                 @Override
                 public void run() {
-                    if (!isControlEnabled()) {
+                    if (!isCecControlEnabled()) {
                         Slog.w(TAG, "Hdmi control is disabled.");
                         return ;
                     }
@@ -3172,15 +3262,15 @@
     }
 
     boolean isTvDevice() {
-        return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
+        return mCecLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
     }
 
     boolean isAudioSystemDevice() {
-        return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        return mCecLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
     }
 
     boolean isPlaybackDevice() {
-        return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
+        return mCecLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
     }
 
     boolean isSwitchDevice() {
@@ -3217,7 +3307,7 @@
         return mAudioDeviceVolumeManager;
     }
 
-    boolean isControlEnabled() {
+    boolean isCecControlEnabled() {
         synchronized (mLock) {
             return mHdmiControlEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
         }
@@ -3329,7 +3419,7 @@
         invokeVendorCommandListenersOnControlStateChanged(false,
                 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
 
-        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
+        final List<HdmiCecLocalDevice> devices = getAllCecLocalDevices();
 
         if (!isStandbyMessageReceived() && !canGoToStandby()) {
             mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
@@ -3339,7 +3429,7 @@
             return;
         }
 
-        disableDevices(new PendingActionClearedCallback() {
+        disableCecLocalDevices(new PendingActionClearedCallback() {
             @Override
             public void onCleared(HdmiCecLocalDevice device) {
                 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
@@ -3387,7 +3477,7 @@
         return mMenuLanguage;
     }
 
-    private void disableDevices(PendingActionClearedCallback callback) {
+    private void disableCecLocalDevices(PendingActionClearedCallback callback) {
         if (mCecController != null) {
             for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
                 device.disableDevice(mStandbyMessageReceived, callback);
@@ -3397,7 +3487,8 @@
     }
 
     @ServiceThreadOnly
-    private void clearLocalDevices() {
+    @VisibleForTesting
+    protected void clearCecLocalDevices() {
         assertRunOnServiceThread();
         if (mCecController == null) {
             return;
@@ -3573,7 +3664,7 @@
     }
 
     @ServiceThreadOnly
-    void setControlEnabled(@HdmiControlManager.HdmiCecControl int enabled) {
+    void setCecEnabled(@HdmiControlManager.HdmiCecControl int enabled) {
         assertRunOnServiceThread();
 
         synchronized (mLock) {
@@ -3581,7 +3672,7 @@
         }
 
         if (enabled == HDMI_CEC_CONTROL_ENABLED) {
-            enableHdmiControlService();
+            onEnableCec();
             setHdmiCecVolumeControlEnabledInternal(getHdmiCecConfig().getIntValue(
                     HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE));
             return;
@@ -3596,7 +3687,7 @@
         runOnServiceThread(new Runnable() {
             @Override
             public void run() {
-                disableHdmiControlService();
+                onDisableCec();
             }
         });
         announceHdmiControlStatusChange(enabled);
@@ -3605,7 +3696,7 @@
     }
 
     @ServiceThreadOnly
-    private void enableHdmiControlService() {
+    private void onEnableCec() {
         mCecController.enableCec(true);
         mCecController.enableSystemCecControl(true);
         mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
@@ -3614,8 +3705,8 @@
     }
 
     @ServiceThreadOnly
-    private void disableHdmiControlService() {
-        disableDevices(
+    private void onDisableCec() {
+        disableCecLocalDevices(
                 new PendingActionClearedCallback() {
                     @Override
                     public void onCleared(HdmiCecLocalDevice device) {
@@ -3627,7 +3718,7 @@
                                         mCecController.enableCec(false);
                                         mCecController.enableSystemCecControl(false);
                                         mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
-                                        clearLocalDevices();
+                                        clearCecLocalDevices();
                                     }
                                 });
                     }
@@ -3671,7 +3762,7 @@
 
         // If the current device is a source device, check if the current Active Source matches
         // the local device info.
-        for (HdmiCecLocalDevice device : getAllLocalDevices()) {
+        for (HdmiCecLocalDevice device : getAllCecLocalDevices()) {
             boolean deviceIsActiveSource =
                     logicalAddress == device.getDeviceInfo().getLogicalAddress()
                             && physicalAddress == getPhysicalAddress();
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index 573bf19..5646e1b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -20,6 +20,8 @@
 import static com.android.server.hdmi.Constants.ADDR_BACKUP_2;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 
+import static java.util.Map.entry;
+
 import android.annotation.Nullable;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
@@ -45,7 +47,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -57,38 +58,34 @@
 
     private static final String TAG = "HdmiUtils";
 
-    private static final Map<Integer, List<Integer>> ADDRESS_TO_TYPE =
-            new HashMap<Integer, List<Integer>>() {
-                {
-                    put(Constants.ADDR_TV, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV));
-                    put(Constants.ADDR_RECORDER_1,
-                            Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER));
-                    put(Constants.ADDR_RECORDER_2,
-                            Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER));
-                    put(Constants.ADDR_TUNER_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
-                    put(Constants.ADDR_PLAYBACK_1,
-                            Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK));
-                    put(Constants.ADDR_AUDIO_SYSTEM,
-                            Lists.newArrayList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM));
-                    put(Constants.ADDR_TUNER_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
-                    put(Constants.ADDR_TUNER_3, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
-                    put(Constants.ADDR_PLAYBACK_2,
-                            Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK));
-                    put(Constants.ADDR_RECORDER_3,
-                            Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER));
-                    put(Constants.ADDR_TUNER_4, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
-                    put(Constants.ADDR_PLAYBACK_3,
-                            Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK));
-                    put(Constants.ADDR_BACKUP_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
-                            HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
-                            HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR));
-                    put(Constants.ADDR_BACKUP_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
-                            HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
-                            HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR));
-                    put(Constants.ADDR_SPECIFIC_USE, Lists.newArrayList(ADDR_TV));
-                    put(Constants.ADDR_UNREGISTERED, Collections.emptyList());
-                }
-            };
+    private static final Map<Integer, List<Integer>> ADDRESS_TO_TYPE = Map.ofEntries(
+            entry(Constants.ADDR_TV, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV)),
+            entry(Constants.ADDR_RECORDER_1,
+                    Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)),
+            entry(Constants.ADDR_RECORDER_2,
+                    Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)),
+            entry(Constants.ADDR_TUNER_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+            entry(Constants.ADDR_PLAYBACK_1,
+                    Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)),
+            entry(Constants.ADDR_AUDIO_SYSTEM,
+                    Lists.newArrayList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)),
+            entry(Constants.ADDR_TUNER_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+            entry(Constants.ADDR_TUNER_3, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+            entry(Constants.ADDR_PLAYBACK_2,
+                    Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)),
+            entry(Constants.ADDR_RECORDER_3,
+                    Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)),
+            entry(Constants.ADDR_TUNER_4, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+            entry(Constants.ADDR_PLAYBACK_3,
+                    Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)),
+            entry(Constants.ADDR_BACKUP_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
+                    HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
+                    HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR)),
+            entry(Constants.ADDR_BACKUP_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
+                    HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
+                    HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR)),
+            entry(Constants.ADDR_SPECIFIC_USE, Lists.newArrayList(ADDR_TV)),
+            entry(Constants.ADDR_UNREGISTERED, Collections.emptyList()));
 
     private static final String[] DEFAULT_NAMES = {
         "TV",
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 9d4f181..c99a7a0 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -19,7 +19,13 @@
 import android.annotation.BinderThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.BatteryState;
 import android.hardware.input.IInputDeviceBatteryListener;
 import android.hardware.input.IInputDeviceBatteryState;
@@ -32,6 +38,7 @@
 import android.os.UEventObserver;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
 import android.view.InputDevice;
@@ -45,6 +52,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 /**
@@ -73,6 +81,7 @@
     private final NativeInputManagerService mNative;
     private final Handler mHandler;
     private final UEventManager mUEventManager;
+    private final BluetoothBatteryManager mBluetoothBatteryManager;
 
     // Maps a pid to the registered listener record for that process. There can only be one battery
     // listener per process.
@@ -87,18 +96,23 @@
     private boolean mIsPolling = false;
     @GuardedBy("mLock")
     private boolean mIsInteractive = true;
+    @Nullable
+    @GuardedBy("mLock")
+    private BluetoothBatteryManager.BluetoothBatteryListener mBluetoothBatteryListener;
 
     BatteryController(Context context, NativeInputManagerService nativeService, Looper looper) {
-        this(context, nativeService, looper, new UEventManager() {});
+        this(context, nativeService, looper, new UEventManager() {},
+                new LocalBluetoothBatteryManager(context));
     }
 
     @VisibleForTesting
     BatteryController(Context context, NativeInputManagerService nativeService, Looper looper,
-            UEventManager uEventManager) {
+            UEventManager uEventManager, BluetoothBatteryManager bbm) {
         mContext = context;
         mNative = nativeService;
         mHandler = new Handler(looper);
         mUEventManager = uEventManager;
+        mBluetoothBatteryManager = bbm;
     }
 
     public void systemRunning() {
@@ -149,6 +163,7 @@
                 // This is the first listener that is monitoring this device.
                 monitor = new DeviceMonitor(deviceId);
                 mDeviceMonitors.put(deviceId, monitor);
+                updateBluetoothMonitoring();
             }
 
             if (DEBUG) {
@@ -201,25 +216,39 @@
         mHandler.postDelayed(this::handlePollEvent, delayStart ? POLLING_PERIOD_MILLIS : 0);
     }
 
-    private String getInputDeviceName(int deviceId) {
+    private <R> R processInputDevice(int deviceId, R defaultValue, Function<InputDevice, R> func) {
         final InputDevice device =
                 Objects.requireNonNull(mContext.getSystemService(InputManager.class))
                         .getInputDevice(deviceId);
-        return device != null ? device.getName() : "<none>";
+        return device == null ? defaultValue : func.apply(device);
+    }
+
+    private String getInputDeviceName(int deviceId) {
+        return processInputDevice(deviceId, "<none>" /*defaultValue*/, InputDevice::getName);
     }
 
     private boolean hasBattery(int deviceId) {
-        final InputDevice device =
-                Objects.requireNonNull(mContext.getSystemService(InputManager.class))
-                        .getInputDevice(deviceId);
-        return device != null && device.hasBattery();
+        return processInputDevice(deviceId, false /*defaultValue*/, InputDevice::hasBattery);
     }
 
     private boolean isUsiDevice(int deviceId) {
-        final InputDevice device =
-                Objects.requireNonNull(mContext.getSystemService(InputManager.class))
-                        .getInputDevice(deviceId);
-        return device != null && device.supportsUsi();
+        return processInputDevice(deviceId, false /*defaultValue*/, InputDevice::supportsUsi);
+    }
+
+    @Nullable
+    private BluetoothDevice getBluetoothDevice(int inputDeviceId) {
+        return getBluetoothDevice(mContext,
+                processInputDevice(inputDeviceId, null /*defaultValue*/,
+                        InputDevice::getBluetoothAddress));
+    }
+
+    @Nullable
+    private static BluetoothDevice getBluetoothDevice(Context context, String address) {
+        if (address == null) return null;
+        final BluetoothAdapter adapter =
+                Objects.requireNonNull(context.getSystemService(BluetoothManager.class))
+                        .getAdapter();
+        return adapter.getRemoteDevice(address);
     }
 
     @GuardedBy("mLock")
@@ -349,6 +378,17 @@
         }
     }
 
+    private void handleBluetoothBatteryLevelChange(long eventTime, String address) {
+        synchronized (mLock) {
+            final DeviceMonitor monitor = findIf(mDeviceMonitors, (m) ->
+                    (m.mBluetoothDevice != null
+                            && address.equals(m.mBluetoothDevice.getAddress())));
+            if (monitor != null) {
+                monitor.onBluetoothBatteryChanged(eventTime);
+            }
+        }
+    }
+
     /** Gets the current battery state of an input device. */
     public IInputDeviceBatteryState getBatteryState(int deviceId) {
         synchronized (mLock) {
@@ -382,24 +422,28 @@
         }
     }
 
-    public void dump(PrintWriter pw, String prefix) {
+    public void dump(PrintWriter pw) {
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
         synchronized (mLock) {
-            final String indent = prefix + "  ";
-            final String indent2 = indent + "  ";
-
-            pw.println(prefix + TAG + ":");
-            pw.println(indent + "State: Polling = " + mIsPolling
+            ipw.println(TAG + ":");
+            ipw.increaseIndent();
+            ipw.println("State: Polling = " + mIsPolling
                     + ", Interactive = " + mIsInteractive);
 
-            pw.println(indent + "Listeners: " + mListenerRecords.size() + " battery listeners");
+            ipw.println("Listeners: " + mListenerRecords.size() + " battery listeners");
+            ipw.increaseIndent();
             for (int i = 0; i < mListenerRecords.size(); i++) {
-                pw.println(indent2 + i + ": " + mListenerRecords.valueAt(i));
+                ipw.println(i + ": " + mListenerRecords.valueAt(i));
             }
+            ipw.decreaseIndent();
 
-            pw.println(indent + "Device Monitors: " + mDeviceMonitors.size() + " monitors");
+            ipw.println("Device Monitors: " + mDeviceMonitors.size() + " monitors");
+            ipw.increaseIndent();
             for (int i = 0; i < mDeviceMonitors.size(); i++) {
-                pw.println(indent2 + i + ": " + mDeviceMonitors.valueAt(i));
+                ipw.println(i + ": " + mDeviceMonitors.valueAt(i));
             }
+            ipw.decreaseIndent();
+            ipw.decreaseIndent();
         }
     }
 
@@ -470,17 +514,52 @@
                 isPresent ? mNative.getBatteryCapacity(deviceId) / 100.f : Float.NaN);
     }
 
+    // Queries the battery state of an input device from Bluetooth.
+    private State queryBatteryStateFromBluetooth(int deviceId, long updateTime,
+            @NonNull BluetoothDevice bluetoothDevice) {
+        final int level = mBluetoothBatteryManager.getBatteryLevel(bluetoothDevice.getAddress());
+        if (level == BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF
+                || level == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+            return new State(deviceId);
+        }
+        return new State(deviceId, updateTime, true /*isPresent*/, BatteryState.STATUS_UNKNOWN,
+                level / 100.f);
+    }
+
+    private void updateBluetoothMonitoring() {
+        synchronized (mLock) {
+            if (anyOf(mDeviceMonitors, (m) -> m.mBluetoothDevice != null)) {
+                // At least one input device being monitored is connected over Bluetooth.
+                if (mBluetoothBatteryListener == null) {
+                    if (DEBUG) Slog.d(TAG, "Registering bluetooth battery listener");
+                    mBluetoothBatteryListener = this::handleBluetoothBatteryLevelChange;
+                    mBluetoothBatteryManager.addListener(mBluetoothBatteryListener);
+                }
+            } else if (mBluetoothBatteryListener != null) {
+                // No Bluetooth input devices are monitored, so remove the registered listener.
+                if (DEBUG) Slog.d(TAG, "Unregistering bluetooth battery listener");
+                mBluetoothBatteryManager.removeListener(mBluetoothBatteryListener);
+                mBluetoothBatteryListener = null;
+            }
+        }
+    }
+
     // Holds the state of an InputDevice for which battery changes are currently being monitored.
     private class DeviceMonitor {
         protected final State mState;
         // Represents whether the input device has a sysfs battery node.
         protected boolean mHasBattery = false;
 
+        protected final State mBluetoothState;
+        @Nullable
+        private BluetoothDevice mBluetoothDevice;
+
         @Nullable
         private UEventBatteryListener mUEventBatteryListener;
 
         DeviceMonitor(int deviceId) {
             mState = new State(deviceId);
+            mBluetoothState = new State(deviceId);
 
             // Load the initial battery state and start monitoring.
             final long eventTime = SystemClock.uptimeMillis();
@@ -501,18 +580,31 @@
         }
 
         private void configureDeviceMonitor(long eventTime) {
+            final int deviceId = mState.deviceId;
             if (mHasBattery != hasBattery(mState.deviceId)) {
                 mHasBattery = !mHasBattery;
                 if (mHasBattery) {
-                    startMonitoring();
+                    startNativeMonitoring();
                 } else {
-                    stopMonitoring();
+                    stopNativeMonitoring();
                 }
                 updateBatteryStateFromNative(eventTime);
             }
+
+            final BluetoothDevice bluetoothDevice = getBluetoothDevice(deviceId);
+            if (!Objects.equals(mBluetoothDevice, bluetoothDevice)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Bluetooth device "
+                            + ((bluetoothDevice != null) ? "is" : "is not")
+                            + " now present for deviceId " + deviceId);
+                }
+                mBluetoothDevice = bluetoothDevice;
+                updateBluetoothMonitoring();
+                updateBatteryStateFromBluetooth(eventTime);
+            }
         }
 
-        private void startMonitoring() {
+        private void startNativeMonitoring() {
             final String batteryPath = mNative.getBatteryDevicePath(mState.deviceId);
             if (batteryPath == null) {
                 return;
@@ -533,7 +625,7 @@
             return path.startsWith("/sys") ? path.substring(4) : path;
         }
 
-        private void stopMonitoring() {
+        private void stopNativeMonitoring() {
             if (mUEventBatteryListener != null) {
                 mUEventManager.removeListener(mUEventBatteryListener);
                 mUEventBatteryListener = null;
@@ -542,7 +634,9 @@
 
         // This must be called when the device is no longer being monitored.
         public void onMonitorDestroy() {
-            stopMonitoring();
+            stopNativeMonitoring();
+            mBluetoothDevice = null;
+            updateBluetoothMonitoring();
         }
 
         protected void updateBatteryStateFromNative(long eventTime) {
@@ -550,6 +644,13 @@
                     queryBatteryStateFromNative(mState.deviceId, eventTime, mHasBattery));
         }
 
+        protected void updateBatteryStateFromBluetooth(long eventTime) {
+            final State bluetoothState = mBluetoothDevice == null ? new State(mState.deviceId)
+                    : queryBatteryStateFromBluetooth(mState.deviceId, eventTime,
+                            mBluetoothDevice);
+            mBluetoothState.updateIfChanged(bluetoothState);
+        }
+
         public void onPoll(long eventTime) {
             processChangesAndNotify(eventTime, this::updateBatteryStateFromNative);
         }
@@ -558,6 +659,10 @@
             processChangesAndNotify(eventTime, this::updateBatteryStateFromNative);
         }
 
+        public void onBluetoothBatteryChanged(long eventTime) {
+            processChangesAndNotify(eventTime, this::updateBatteryStateFromBluetooth);
+        }
+
         public boolean requiresPolling() {
             return true;
         }
@@ -572,6 +677,10 @@
 
         // Returns the current battery state that can be used to notify listeners BatteryController.
         public State getBatteryStateForReporting() {
+            // Give precedence to the Bluetooth battery state if it's present.
+            if (mBluetoothState.isPresent) {
+                return new State(mBluetoothState);
+            }
             return new State(mState);
         }
 
@@ -580,7 +689,8 @@
             return "DeviceId=" + mState.deviceId
                     + ", Name='" + getInputDeviceName(mState.deviceId) + "'"
                     + ", NativeBattery=" + mState
-                    + ", UEventListener=" + (mUEventBatteryListener != null ? "added" : "none");
+                    + ", UEventListener=" + (mUEventBatteryListener != null ? "added" : "none")
+                    + ", BluetoothBattery=" + mBluetoothState;
         }
     }
 
@@ -665,6 +775,10 @@
 
         @Override
         public State getBatteryStateForReporting() {
+            // Give precedence to the Bluetooth battery state if it's present.
+            if (mBluetoothState.isPresent) {
+                return new State(mBluetoothState);
+            }
             return mValidityTimeoutCallback != null
                     ? new State(mState) : new State(mState.deviceId);
         }
@@ -724,6 +838,82 @@
         }
     }
 
+    // An interface used to change the API of adding a bluetooth battery listener to a more
+    // test-friendly format.
+    @VisibleForTesting
+    interface BluetoothBatteryManager {
+        @VisibleForTesting
+        interface BluetoothBatteryListener {
+            void onBluetoothBatteryChanged(long eventTime, String address);
+        }
+        void addListener(BluetoothBatteryListener listener);
+        void removeListener(BluetoothBatteryListener listener);
+        int getBatteryLevel(String address);
+    }
+
+    private static class LocalBluetoothBatteryManager implements BluetoothBatteryManager {
+        private final Context mContext;
+        @Nullable
+        @GuardedBy("mBroadcastReceiver")
+        private BluetoothBatteryListener mRegisteredListener;
+        @GuardedBy("mBroadcastReceiver")
+        private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (!BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED.equals(intent.getAction())) {
+                    return;
+                }
+                final BluetoothDevice bluetoothDevice = intent.getParcelableExtra(
+                        BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
+                if (bluetoothDevice == null) {
+                    return;
+                }
+                // We do not use the EXTRA_LEVEL value. Instead, the battery level will be queried
+                // from BluetoothDevice later so that we use a single source for the battery level.
+                synchronized (mBroadcastReceiver) {
+                    if (mRegisteredListener != null) {
+                        final long eventTime = SystemClock.uptimeMillis();
+                        mRegisteredListener.onBluetoothBatteryChanged(
+                                eventTime, bluetoothDevice.getAddress());
+                    }
+                }
+            }
+        };
+
+        LocalBluetoothBatteryManager(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public void addListener(BluetoothBatteryListener listener) {
+            synchronized (mBroadcastReceiver) {
+                if (mRegisteredListener != null) {
+                    throw new IllegalStateException(
+                            "Only one bluetooth battery listener can be registered at once.");
+                }
+                mRegisteredListener = listener;
+                mContext.registerReceiver(mBroadcastReceiver,
+                        new IntentFilter(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED));
+            }
+        }
+
+        @Override
+        public void removeListener(BluetoothBatteryListener listener) {
+            synchronized (mBroadcastReceiver) {
+                if (!listener.equals(mRegisteredListener)) {
+                    throw new IllegalStateException("Listener is not registered.");
+                }
+                mRegisteredListener = null;
+                mContext.unregisterReceiver(mBroadcastReceiver);
+            }
+        }
+
+        @Override
+        public int getBatteryLevel(String address) {
+            return getBluetoothDevice(mContext, address).getBatteryLevel();
+        }
+    }
+
     // Helper class that adds copying and printing functionality to IInputDeviceBatteryState.
     private static class State extends IInputDeviceBatteryState {
 
@@ -787,11 +977,17 @@
 
     // Check if any value in an ArrayMap matches the predicate in an optimized way.
     private static <K, V> boolean anyOf(ArrayMap<K, V> arrayMap, Predicate<V> test) {
+        return findIf(arrayMap, test) != null;
+    }
+
+    // Find the first value in an ArrayMap that matches the predicate in an optimized way.
+    private static <K, V> V findIf(ArrayMap<K, V> arrayMap, Predicate<V> test) {
         for (int i = 0; i < arrayMap.size(); i++) {
-            if (test.test(arrayMap.valueAt(i))) {
-                return true;
+            final V value = arrayMap.valueAt(i);
+            if (test.test(value)) {
+                return value;
             }
         }
-        return false;
+        return null;
     }
 }
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 7eb5a10..298098a 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -17,10 +17,15 @@
 package com.android.server.input;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.graphics.PointF;
 import android.hardware.display.DisplayViewport;
 import android.os.IBinder;
 import android.view.InputChannel;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 
 import java.util.List;
 
@@ -142,6 +147,20 @@
     public abstract void pilferPointers(IBinder token);
 
     /**
+     * Called when the current input method and/or {@link InputMethodSubtype} is updated.
+     *
+     * @param userId User ID to be notified about.
+     * @param subtypeHandle A {@link InputMethodSubtypeHandle} corresponds to {@code subtype}.
+     * @param subtype A {@link InputMethodSubtype} object, or {@code null} when the current
+     *                {@link InputMethodSubtype} is not suitable for the physical keyboard layout
+     *                mapping.
+     * @see InputMethodSubtype#isSuitableForPhysicalKeyboardLayoutMapping()
+     */
+    public abstract void onInputMethodSubtypeChangedForKeyboardLayoutMapping(@UserIdInt int userId,
+            @Nullable InputMethodSubtypeHandle subtypeHandle,
+            @Nullable InputMethodSubtype subtype);
+
+    /**
      * Increments keyboard backlight level if the device has an associated keyboard backlight
      * {@see Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT}
      */
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 31f63d8..81d782e 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -23,26 +23,15 @@
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.graphics.PointF;
 import android.hardware.SensorPrivacyManager;
@@ -65,7 +54,6 @@
 import android.hardware.lights.LightState;
 import android.media.AudioManager;
 import android.os.Binder;
-import android.os.Bundle;
 import android.os.CombinedVibration;
 import android.os.Environment;
 import android.os.Handler;
@@ -74,7 +62,6 @@
 import android.os.IVibratorStateListener;
 import android.os.InputEventInjectionResult;
 import android.os.InputEventInjectionSync;
-import android.os.LocaleList;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
@@ -91,6 +78,7 @@
 import android.provider.Settings.SettingNotFoundException;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -110,17 +98,16 @@
 import android.view.SurfaceControl;
 import android.view.VerifiedInputEvent;
 import android.view.ViewConfiguration;
-import android.widget.Toast;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.Watchdog;
@@ -128,7 +115,6 @@
 import com.android.server.policy.WindowManagerPolicy;
 
 import libcore.io.IoUtils;
-import libcore.io.Streams;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -137,15 +123,11 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.OptionalInt;
@@ -167,12 +149,9 @@
     private static final String VELOCITYTRACKER_STRATEGY_PROPERTY = "velocitytracker_strategy";
 
     private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
-    private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
-    private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
-    private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
-    private static final int MSG_RELOAD_DEVICE_ALIASES = 5;
-    private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 6;
-    private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 7;
+    private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
+    private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
+    private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 4;
 
     private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
     private static final AdditionalDisplayInputProperties
@@ -191,7 +170,6 @@
     private WindowManagerCallbacks mWindowManagerCallbacks;
     private WiredAccessoryCallbacks mWiredAccessoryCallbacks;
     private boolean mSystemReady;
-    private NotificationManager mNotificationManager;
 
     private final Object mTabletModeLock = new Object();
     // List of currently registered tablet mode changed listeners by process id
@@ -225,10 +203,6 @@
             new SparseArray<>();
     private final ArrayList<InputDevicesChangedListenerRecord>
             mTempInputDevicesChangedListenersToNotify = new ArrayList<>(); // handler thread only
-    private final ArrayList<InputDevice> mTempFullKeyboards =
-            new ArrayList<>(); // handler thread only
-    private boolean mKeyboardLayoutNotificationShown;
-    private Toast mSwitchedKeyboardLayoutToast;
 
     // State for vibrator tokens.
     private final Object mVibratorLock = new Object();
@@ -269,9 +243,9 @@
     // to display id (int). Currently only accessed by InputReader.
     private final Map<String, Integer> mStaticAssociations;
     private final Object mAssociationsLock = new Object();
-    @GuardedBy("mAssociationLock")
+    @GuardedBy("mAssociationsLock")
     private final Map<String, Integer> mRuntimeAssociations = new ArrayMap<>();
-    @GuardedBy("mAssociationLock")
+    @GuardedBy("mAssociationsLock")
     private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>();
 
     // Guards per-display input properties and properties relating to the mouse pointer.
@@ -302,15 +276,18 @@
     private final AdditionalDisplayInputProperties mCurrentDisplayProperties =
             new AdditionalDisplayInputProperties();
     @GuardedBy("mAdditionalDisplayInputPropertiesLock")
-    private int mIconType = PointerIcon.TYPE_NOT_SPECIFIED;
+    private int mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
     @GuardedBy("mAdditionalDisplayInputPropertiesLock")
-    private PointerIcon mIcon;
+    private PointerIcon mPointerIcon;
 
     // Holds all the registered gesture monitors that are implemented as spy windows. The spy
     // windows are mapped by their InputChannel tokens.
     @GuardedBy("mInputMonitors")
     final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
 
+    // Manages Keyboard layouts for Physical keyboards
+    private final KeyboardLayoutManager mKeyboardLayoutManager;
+
     // Manages battery state for input devices.
     private final BatteryController mBatteryController;
 
@@ -365,7 +342,7 @@
     /** Switch code: Camera lens cover. When set the lens is covered. */
     public static final int SW_CAMERA_LENS_COVER = 0x09;
 
-    /** Switch code: Microphone. When set it is off. */
+    /** Switch code: Microphone. When set, the mic is muted. */
     public static final int SW_MUTE_DEVICE = 0x0e;
 
     public static final int SW_LID_BIT = 1 << SW_LID;
@@ -426,6 +403,8 @@
         mContext = injector.getContext();
         mHandler = new InputManagerHandler(injector.getLooper());
         mNative = injector.getNativeService(this);
+        mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore,
+                injector.getLooper());
         mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
         mKeyboardBacklightController = new KeyboardBacklightController(mContext, mNative,
                 mDataStore, injector.getLooper());
@@ -514,8 +493,6 @@
         if (DEBUG) {
             Slog.d(TAG, "System ready.");
         }
-        mNotificationManager = (NotificationManager)mContext.getSystemService(
-                Context.NOTIFICATION_SERVICE);
 
         synchronized (mLidSwitchLock) {
             mSystemReady = true;
@@ -532,29 +509,17 @@
         // Set the HW mic toggle switch state
         final int micMuteState = getSwitchState(-1 /* deviceId */, InputDevice.SOURCE_ANY,
                 SW_MUTE_DEVICE);
-        if (micMuteState != InputManager.SWITCH_STATE_UNKNOWN) {
-            setSensorPrivacy(Sensors.MICROPHONE, micMuteState != InputManager.SWITCH_STATE_OFF);
+        if (micMuteState == InputManager.SWITCH_STATE_ON) {
+            setSensorPrivacy(Sensors.MICROPHONE, true);
         }
         // Set the HW camera toggle switch state
         final int cameraMuteState = getSwitchState(-1 /* deviceId */, InputDevice.SOURCE_ANY,
                 SW_CAMERA_LENS_COVER);
-        if (cameraMuteState != InputManager.SWITCH_STATE_UNKNOWN) {
-            setSensorPrivacy(Sensors.CAMERA, cameraMuteState != InputManager.SWITCH_STATE_OFF);
+        if (cameraMuteState == InputManager.SWITCH_STATE_ON) {
+            setSensorPrivacy(Sensors.CAMERA, true);
         }
 
-        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
-        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-        filter.addDataScheme("package");
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                updateKeyboardLayouts();
-            }
-        }, filter, null, mHandler);
-
-        filter = new IntentFilter(BluetoothDevice.ACTION_ALIAS_CHANGED);
+        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_ALIAS_CHANGED);
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
@@ -563,23 +528,16 @@
         }, filter, null, mHandler);
 
         mHandler.sendEmptyMessage(MSG_RELOAD_DEVICE_ALIASES);
-        mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);
 
         if (mWiredAccessoryCallbacks != null) {
             mWiredAccessoryCallbacks.systemReady();
         }
 
+        mKeyboardLayoutManager.systemRunning();
         mBatteryController.systemRunning();
         mKeyboardBacklightController.systemRunning();
     }
 
-    private void reloadKeyboardLayouts() {
-        if (DEBUG) {
-            Slog.d(TAG, "Reloading keyboard layouts.");
-        }
-        mNative.reloadKeyboardLayouts();
-    }
-
     private void reloadDeviceAliases() {
         if (DEBUG) {
             Slog.d(TAG, "Reloading device names.");
@@ -1040,9 +998,7 @@
     // Must be called on handler.
     private void deliverInputDevicesChanged(InputDevice[] oldInputDevices) {
         // Scan for changes.
-        int numFullKeyboardsAdded = 0;
         mTempInputDevicesChangedListenersToNotify.clear();
-        mTempFullKeyboards.clear();
         final int numListeners;
         final int[] deviceIdAndGeneration;
         synchronized (mInputDevicesLock) {
@@ -1067,15 +1023,6 @@
                     Log.d(TAG, "device " + inputDevice.getId() + " generation "
                             + inputDevice.getGeneration());
                 }
-
-                if (!inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
-                    if (!containsInputDeviceWithDescriptor(oldInputDevices,
-                            inputDevice.getDescriptor())) {
-                        mTempFullKeyboards.add(numFullKeyboardsAdded++, inputDevice);
-                    } else {
-                        mTempFullKeyboards.add(inputDevice);
-                    }
-                }
             }
         }
 
@@ -1085,119 +1032,6 @@
                     deviceIdAndGeneration);
         }
         mTempInputDevicesChangedListenersToNotify.clear();
-
-        // Check for missing keyboard layouts.
-        List<InputDevice> keyboardsMissingLayout = new ArrayList<>();
-        final int numFullKeyboards = mTempFullKeyboards.size();
-        synchronized (mDataStore) {
-            for (int i = 0; i < numFullKeyboards; i++) {
-                final InputDevice inputDevice = mTempFullKeyboards.get(i);
-                String layout =
-                    getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
-                if (layout == null) {
-                    layout = getDefaultKeyboardLayout(inputDevice);
-                    if (layout != null) {
-                        setCurrentKeyboardLayoutForInputDevice(
-                                inputDevice.getIdentifier(), layout);
-                    }
-                }
-                if (layout == null) {
-                    keyboardsMissingLayout.add(inputDevice);
-                }
-            }
-        }
-
-        if (mNotificationManager != null) {
-            if (!keyboardsMissingLayout.isEmpty()) {
-                if (keyboardsMissingLayout.size() > 1) {
-                    // We have more than one keyboard missing a layout, so drop the
-                    // user at the generic input methods page so they can pick which
-                    // one to set.
-                    showMissingKeyboardLayoutNotification(null);
-                } else {
-                    showMissingKeyboardLayoutNotification(keyboardsMissingLayout.get(0));
-                }
-            } else if (mKeyboardLayoutNotificationShown) {
-                hideMissingKeyboardLayoutNotification();
-            }
-        }
-        mTempFullKeyboards.clear();
-    }
-
-    private String getDefaultKeyboardLayout(final InputDevice d) {
-        final Locale systemLocale = mContext.getResources().getConfiguration().locale;
-        // If our locale doesn't have a language for some reason, then we don't really have a
-        // reasonable default.
-        if (TextUtils.isEmpty(systemLocale.getLanguage())) {
-            return null;
-        }
-        final List<KeyboardLayout> layouts = new ArrayList<>();
-        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> {
-            // Only select a default when we know the layout is appropriate. For now, this
-            // means it's a custom layout for a specific keyboard.
-            if (layout.getVendorId() != d.getVendorId()
-                    || layout.getProductId() != d.getProductId()) {
-                return;
-            }
-            final LocaleList locales = layout.getLocales();
-            final int numLocales = locales.size();
-            for (int localeIndex = 0; localeIndex < numLocales; ++localeIndex) {
-                if (isCompatibleLocale(systemLocale, locales.get(localeIndex))) {
-                    layouts.add(layout);
-                    break;
-                }
-            }
-        });
-
-        if (layouts.isEmpty()) {
-            return null;
-        }
-
-        // First sort so that ones with higher priority are listed at the top
-        Collections.sort(layouts);
-        // Next we want to try to find an exact match of language, country and variant.
-        final int N = layouts.size();
-        for (int i = 0; i < N; i++) {
-            KeyboardLayout layout = layouts.get(i);
-            final LocaleList locales = layout.getLocales();
-            final int numLocales = locales.size();
-            for (int localeIndex = 0; localeIndex < numLocales; ++localeIndex) {
-                final Locale locale = locales.get(localeIndex);
-                if (locale.getCountry().equals(systemLocale.getCountry())
-                        && locale.getVariant().equals(systemLocale.getVariant())) {
-                    return layout.getDescriptor();
-                }
-            }
-        }
-        // Then try an exact match of language and country
-        for (int i = 0; i < N; i++) {
-            KeyboardLayout layout = layouts.get(i);
-            final LocaleList locales = layout.getLocales();
-            final int numLocales = locales.size();
-            for (int localeIndex = 0; localeIndex < numLocales; ++localeIndex) {
-                final Locale locale = locales.get(localeIndex);
-                if (locale.getCountry().equals(systemLocale.getCountry())) {
-                    return layout.getDescriptor();
-                }
-            }
-        }
-
-        // Give up and just use the highest priority layout with matching language
-        return layouts.get(0).getDescriptor();
-    }
-
-    private static boolean isCompatibleLocale(Locale systemLocale, Locale keyboardLocale) {
-        // Different languages are never compatible
-        if (!systemLocale.getLanguage().equals(keyboardLocale.getLanguage())) {
-            return false;
-        }
-        // If both the system and the keyboard layout have a country specifier, they must be equal.
-        if (!TextUtils.isEmpty(systemLocale.getCountry())
-                && !TextUtils.isEmpty(keyboardLocale.getCountry())
-                && !systemLocale.getCountry().equals(keyboardLocale.getCountry())) {
-            return false;
-        }
-        return true;
     }
 
     @Override // Binder call & native callback
@@ -1298,446 +1132,88 @@
         }
     }
 
-    // Must be called on handler.
-    private void showMissingKeyboardLayoutNotification(InputDevice device) {
-        if (!mKeyboardLayoutNotificationShown) {
-            final Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS);
-            if (device != null) {
-                intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, device.getIdentifier());
-            }
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
-                    | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            final PendingIntent keyboardLayoutIntent = PendingIntent.getActivityAsUser(mContext, 0,
-                    intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
-
-            Resources r = mContext.getResources();
-            Notification notification =
-                    new Notification.Builder(mContext, SystemNotificationChannels.PHYSICAL_KEYBOARD)
-                            .setContentTitle(r.getString(
-                                    R.string.select_keyboard_layout_notification_title))
-                            .setContentText(r.getString(
-                                    R.string.select_keyboard_layout_notification_message))
-                            .setContentIntent(keyboardLayoutIntent)
-                            .setSmallIcon(R.drawable.ic_settings_language)
-                            .setColor(mContext.getColor(
-                                    com.android.internal.R.color.system_notification_accent_color))
-                            .build();
-            mNotificationManager.notifyAsUser(null,
-                    SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
-                    notification, UserHandle.ALL);
-            mKeyboardLayoutNotificationShown = true;
-        }
-    }
-
-    // Must be called on handler.
-    private void hideMissingKeyboardLayoutNotification() {
-        if (mKeyboardLayoutNotificationShown) {
-            mKeyboardLayoutNotificationShown = false;
-            mNotificationManager.cancelAsUser(null,
-                    SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
-                    UserHandle.ALL);
-        }
-    }
-
-    // Must be called on handler.
-    private void updateKeyboardLayouts() {
-        // Scan all input devices state for keyboard layouts that have been uninstalled.
-        final HashSet<String> availableKeyboardLayouts = new HashSet<String>();
-        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) ->
-                availableKeyboardLayouts.add(layout.getDescriptor()));
-        synchronized (mDataStore) {
-            try {
-                mDataStore.removeUninstalledKeyboardLayouts(availableKeyboardLayouts);
-            } finally {
-                mDataStore.saveIfNeeded();
-            }
-        }
-
-        // Reload keyboard layouts.
-        reloadKeyboardLayouts();
-    }
-
-    private static boolean containsInputDeviceWithDescriptor(InputDevice[] inputDevices,
-            String descriptor) {
-        final int numDevices = inputDevices.length;
-        for (int i = 0; i < numDevices; i++) {
-            final InputDevice inputDevice = inputDevices[i];
-            if (inputDevice.getDescriptor().equals(descriptor)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     @Override // Binder call
     public KeyboardLayout[] getKeyboardLayouts() {
-        final ArrayList<KeyboardLayout> list = new ArrayList<>();
-        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> list.add(layout));
-        return list.toArray(new KeyboardLayout[list.size()]);
+        return mKeyboardLayoutManager.getKeyboardLayouts();
     }
 
     @Override // Binder call
     public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
             final InputDeviceIdentifier identifier) {
-        final String[] enabledLayoutDescriptors =
-                getEnabledKeyboardLayoutsForInputDevice(identifier);
-        final ArrayList<KeyboardLayout> enabledLayouts =
-                new ArrayList<>(enabledLayoutDescriptors.length);
-        final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
-        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
-            boolean mHasSeenDeviceSpecificLayout;
-
-            @Override
-            public void visitKeyboardLayout(Resources resources,
-                    int keyboardLayoutResId, KeyboardLayout layout) {
-                // First check if it's enabled. If the keyboard layout is enabled then we always
-                // want to return it as a possible layout for the device.
-                for (String s : enabledLayoutDescriptors) {
-                    if (s != null && s.equals(layout.getDescriptor())) {
-                        enabledLayouts.add(layout);
-                        return;
-                    }
-                }
-                // Next find any potential layouts that aren't yet enabled for the device. For
-                // devices that have special layouts we assume there's a reason that the generic
-                // layouts don't work for them so we don't want to return them since it's likely
-                // to result in a poor user experience.
-                if (layout.getVendorId() == identifier.getVendorId()
-                        && layout.getProductId() == identifier.getProductId()) {
-                    if (!mHasSeenDeviceSpecificLayout) {
-                        mHasSeenDeviceSpecificLayout = true;
-                        potentialLayouts.clear();
-                    }
-                    potentialLayouts.add(layout);
-                } else if (layout.getVendorId() == -1 && layout.getProductId() == -1
-                        && !mHasSeenDeviceSpecificLayout) {
-                    potentialLayouts.add(layout);
-                }
-            }
-        });
-        final int enabledLayoutSize = enabledLayouts.size();
-        final int potentialLayoutSize = potentialLayouts.size();
-        KeyboardLayout[] layouts = new KeyboardLayout[enabledLayoutSize + potentialLayoutSize];
-        enabledLayouts.toArray(layouts);
-        for (int i = 0; i < potentialLayoutSize; i++) {
-            layouts[enabledLayoutSize + i] = potentialLayouts.get(i);
-        }
-        return layouts;
+        return mKeyboardLayoutManager.getKeyboardLayoutsForInputDevice(identifier);
     }
 
     @Override // Binder call
     public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
-        Objects.requireNonNull(keyboardLayoutDescriptor,
-                "keyboardLayoutDescriptor must not be null");
-
-        final KeyboardLayout[] result = new KeyboardLayout[1];
-        visitKeyboardLayout(keyboardLayoutDescriptor,
-                (resources, keyboardLayoutResId, layout) -> result[0] = layout);
-        if (result[0] == null) {
-            Slog.w(TAG, "Could not get keyboard layout with descriptor '"
-                    + keyboardLayoutDescriptor + "'.");
-        }
-        return result[0];
-    }
-
-    private void visitAllKeyboardLayouts(KeyboardLayoutVisitor visitor) {
-        final PackageManager pm = mContext.getPackageManager();
-        Intent intent = new Intent(InputManager.ACTION_QUERY_KEYBOARD_LAYOUTS);
-        for (ResolveInfo resolveInfo : pm.queryBroadcastReceivers(intent,
-                PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE)) {
-            final ActivityInfo activityInfo = resolveInfo.activityInfo;
-            final int priority = resolveInfo.priority;
-            visitKeyboardLayoutsInPackage(pm, activityInfo, null, priority, visitor);
-        }
-    }
-
-    private void visitKeyboardLayout(String keyboardLayoutDescriptor,
-            KeyboardLayoutVisitor visitor) {
-        KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(keyboardLayoutDescriptor);
-        if (d != null) {
-            final PackageManager pm = mContext.getPackageManager();
-            try {
-                ActivityInfo receiver = pm.getReceiverInfo(
-                        new ComponentName(d.packageName, d.receiverName),
-                        PackageManager.GET_META_DATA
-                                | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
-                visitKeyboardLayoutsInPackage(pm, receiver, d.keyboardLayoutName, 0, visitor);
-            } catch (NameNotFoundException ignored) {
-            }
-        }
-    }
-
-    private void visitKeyboardLayoutsInPackage(PackageManager pm, ActivityInfo receiver,
-            String keyboardName, int requestedPriority, KeyboardLayoutVisitor visitor) {
-        Bundle metaData = receiver.metaData;
-        if (metaData == null) {
-            return;
-        }
-
-        int configResId = metaData.getInt(InputManager.META_DATA_KEYBOARD_LAYOUTS);
-        if (configResId == 0) {
-            Slog.w(TAG, "Missing meta-data '" + InputManager.META_DATA_KEYBOARD_LAYOUTS
-                    + "' on receiver " + receiver.packageName + "/" + receiver.name);
-            return;
-        }
-
-        CharSequence receiverLabel = receiver.loadLabel(pm);
-        String collection = receiverLabel != null ? receiverLabel.toString() : "";
-        int priority;
-        if ((receiver.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-            priority = requestedPriority;
-        } else {
-            priority = 0;
-        }
-
-        try {
-            Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
-            try (XmlResourceParser parser = resources.getXml(configResId)) {
-                XmlUtils.beginDocument(parser, "keyboard-layouts");
-
-                while (true) {
-                    XmlUtils.nextElement(parser);
-                    String element = parser.getName();
-                    if (element == null) {
-                        break;
-                    }
-                    if (element.equals("keyboard-layout")) {
-                        TypedArray a = resources.obtainAttributes(
-                                parser, R.styleable.KeyboardLayout);
-                        try {
-                            String name = a.getString(
-                                    R.styleable.KeyboardLayout_name);
-                            String label = a.getString(
-                                    R.styleable.KeyboardLayout_label);
-                            int keyboardLayoutResId = a.getResourceId(
-                                    R.styleable.KeyboardLayout_keyboardLayout,
-                                    0);
-                            String languageTags = a.getString(
-                                    R.styleable.KeyboardLayout_locale);
-                            LocaleList locales = getLocalesFromLanguageTags(languageTags);
-                            int vid = a.getInt(
-                                    R.styleable.KeyboardLayout_vendorId, -1);
-                            int pid = a.getInt(
-                                    R.styleable.KeyboardLayout_productId, -1);
-
-                            if (name == null || label == null || keyboardLayoutResId == 0) {
-                                Slog.w(TAG, "Missing required 'name', 'label' or 'keyboardLayout' "
-                                        + "attributes in keyboard layout "
-                                        + "resource from receiver "
-                                        + receiver.packageName + "/" + receiver.name);
-                            } else {
-                                String descriptor = KeyboardLayoutDescriptor.format(
-                                        receiver.packageName, receiver.name, name);
-                                if (keyboardName == null || name.equals(keyboardName)) {
-                                    KeyboardLayout layout = new KeyboardLayout(
-                                            descriptor, label, collection, priority,
-                                            locales, vid, pid);
-                                    visitor.visitKeyboardLayout(
-                                            resources, keyboardLayoutResId, layout);
-                                }
-                            }
-                        } finally {
-                            a.recycle();
-                        }
-                    } else {
-                        Slog.w(TAG, "Skipping unrecognized element '" + element
-                                + "' in keyboard layout resource from receiver "
-                                + receiver.packageName + "/" + receiver.name);
-                    }
-                }
-            }
-        } catch (Exception ex) {
-            Slog.w(TAG, "Could not parse keyboard layout resource from receiver "
-                    + receiver.packageName + "/" + receiver.name, ex);
-        }
-    }
-
-    @NonNull
-    private static LocaleList getLocalesFromLanguageTags(String languageTags) {
-        if (TextUtils.isEmpty(languageTags)) {
-            return LocaleList.getEmptyLocaleList();
-        }
-        return LocaleList.forLanguageTags(languageTags.replace('|', ','));
-    }
-
-    /**
-     * Builds a layout descriptor for the vendor/product. This returns the
-     * descriptor for ids that aren't useful (such as the default 0, 0).
-     */
-    private String getLayoutDescriptor(InputDeviceIdentifier identifier) {
-        Objects.requireNonNull(identifier, "identifier must not be null");
-        Objects.requireNonNull(identifier.getDescriptor(), "descriptor must not be null");
-
-        if (identifier.getVendorId() == 0 && identifier.getProductId() == 0) {
-            return identifier.getDescriptor();
-        }
-        return "vendor:" + identifier.getVendorId() + ",product:" + identifier.getProductId();
+        return mKeyboardLayoutManager.getKeyboardLayout(keyboardLayoutDescriptor);
     }
 
     @Override // Binder call
     public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
-
-        String key = getLayoutDescriptor(identifier);
-        synchronized (mDataStore) {
-            String layout;
-            // try loading it using the layout descriptor if we have it
-            layout = mDataStore.getCurrentKeyboardLayout(key);
-            if (layout == null && !key.equals(identifier.getDescriptor())) {
-                // if it doesn't exist fall back to the device descriptor
-                layout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "getCurrentKeyboardLayoutForInputDevice() "
-                        + identifier.toString() + ": " + layout);
-            }
-            return layout;
-        }
+        return mKeyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(identifier);
     }
 
+    @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
     @Override // Binder call
     public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
-        if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
-                "setCurrentKeyboardLayoutForInputDevice()")) {
-            throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
-        }
-
-        Objects.requireNonNull(keyboardLayoutDescriptor,
-                "keyboardLayoutDescriptor must not be null");
-
-        String key = getLayoutDescriptor(identifier);
-        synchronized (mDataStore) {
-            try {
-                if (mDataStore.setCurrentKeyboardLayout(key, keyboardLayoutDescriptor)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "setCurrentKeyboardLayoutForInputDevice() " + identifier
-                                + " key: " + key
-                                + " keyboardLayoutDescriptor: " + keyboardLayoutDescriptor);
-                    }
-                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
-                }
-            } finally {
-                mDataStore.saveIfNeeded();
-            }
-        }
+        super.setCurrentKeyboardLayoutForInputDevice_enforcePermission();
+        mKeyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(identifier,
+                keyboardLayoutDescriptor);
     }
 
     @Override // Binder call
     public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
-        String key = getLayoutDescriptor(identifier);
-        synchronized (mDataStore) {
-            String[] layouts = mDataStore.getKeyboardLayouts(key);
-            if ((layouts == null || layouts.length == 0)
-                    && !key.equals(identifier.getDescriptor())) {
-                layouts = mDataStore.getKeyboardLayouts(identifier.getDescriptor());
-            }
-            return layouts;
-        }
+        return mKeyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(identifier);
     }
 
+    @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
     @Override // Binder call
     public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
-        if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
-                "addKeyboardLayoutForInputDevice()")) {
-            throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
-        }
-        Objects.requireNonNull(keyboardLayoutDescriptor,
-                "keyboardLayoutDescriptor must not be null");
-
-        String key = getLayoutDescriptor(identifier);
-        synchronized (mDataStore) {
-            try {
-                String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
-                if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
-                    oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
-                }
-                if (mDataStore.addKeyboardLayout(key, keyboardLayoutDescriptor)
-                        && !Objects.equals(oldLayout,
-                                mDataStore.getCurrentKeyboardLayout(key))) {
-                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
-                }
-            } finally {
-                mDataStore.saveIfNeeded();
-            }
-        }
+        super.addKeyboardLayoutForInputDevice_enforcePermission();
+        mKeyboardLayoutManager.addKeyboardLayoutForInputDevice(identifier,
+                keyboardLayoutDescriptor);
     }
 
+    @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
     @Override // Binder call
     public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
-        if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
-                "removeKeyboardLayoutForInputDevice()")) {
-            throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
-        }
-        Objects.requireNonNull(keyboardLayoutDescriptor,
-                "keyboardLayoutDescriptor must not be null");
-
-        String key = getLayoutDescriptor(identifier);
-        synchronized (mDataStore) {
-            try {
-                String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
-                if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
-                    oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
-                }
-                boolean removed = mDataStore.removeKeyboardLayout(key, keyboardLayoutDescriptor);
-                if (!key.equals(identifier.getDescriptor())) {
-                    // We need to remove from both places to ensure it is gone
-                    removed |= mDataStore.removeKeyboardLayout(identifier.getDescriptor(),
-                            keyboardLayoutDescriptor);
-                }
-                if (removed && !Objects.equals(oldLayout,
-                                mDataStore.getCurrentKeyboardLayout(key))) {
-                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
-                }
-            } finally {
-                mDataStore.saveIfNeeded();
-            }
-        }
+        super.removeKeyboardLayoutForInputDevice_enforcePermission();
+        mKeyboardLayoutManager.removeKeyboardLayoutForInputDevice(identifier,
+                keyboardLayoutDescriptor);
     }
 
+    @Override // Binder call
+    public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @NonNull InputMethodSubtype imeSubtype) {
+        return mKeyboardLayoutManager.getKeyboardLayoutForInputDevice(identifier, userId,
+                imeInfo, imeSubtype);
+    }
+
+    @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
+    @Override // Binder call
+    public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @NonNull InputMethodSubtype imeSubtype, String keyboardLayoutDescriptor) {
+        super.setKeyboardLayoutForInputDevice_enforcePermission();
+        mKeyboardLayoutManager.setKeyboardLayoutForInputDevice(identifier, userId, imeInfo,
+                imeSubtype, keyboardLayoutDescriptor);
+    }
+
+    @Override // Binder call
+    public String[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @NonNull InputMethodSubtype imeSubtype) {
+        return mKeyboardLayoutManager.getKeyboardLayoutListForInputDevice(identifier, userId,
+                imeInfo, imeSubtype);
+    }
+
+
     public void switchKeyboardLayout(int deviceId, int direction) {
-        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
-    }
-
-    // Must be called on handler.
-    private void handleSwitchKeyboardLayout(int deviceId, int direction) {
-        final InputDevice device = getInputDevice(deviceId);
-        if (device != null) {
-            final boolean changed;
-            final String keyboardLayoutDescriptor;
-
-            String key = getLayoutDescriptor(device.getIdentifier());
-            synchronized (mDataStore) {
-                try {
-                    changed = mDataStore.switchKeyboardLayout(key, direction);
-                    keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
-                            key);
-                } finally {
-                    mDataStore.saveIfNeeded();
-                }
-            }
-
-            if (changed) {
-                if (mSwitchedKeyboardLayoutToast != null) {
-                    mSwitchedKeyboardLayoutToast.cancel();
-                    mSwitchedKeyboardLayoutToast = null;
-                }
-                if (keyboardLayoutDescriptor != null) {
-                    KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
-                    if (keyboardLayout != null) {
-                        mSwitchedKeyboardLayoutToast = Toast.makeText(
-                                mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
-                        mSwitchedKeyboardLayoutToast.show();
-                    }
-                }
-
-                reloadKeyboardLayouts();
-            }
-        }
+        mKeyboardLayoutManager.switchKeyboardLayout(deviceId, direction);
     }
 
     public void setFocusedApplication(int displayId, InputApplicationHandle application) {
@@ -1810,8 +1286,8 @@
      */
     public boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
             @NonNull IBinder toChannelToken) {
-        Objects.nonNull(fromChannelToken);
-        Objects.nonNull(toChannelToken);
+        Objects.requireNonNull(fromChannelToken);
+        Objects.requireNonNull(toChannelToken);
         return mNative.transferTouchFocus(fromChannelToken, toChannelToken,
                 false /* isDragDrop */);
     }
@@ -2325,12 +1801,12 @@
             throw new IllegalArgumentException("Use setCustomPointerIcon to set custom pointers");
         }
         synchronized (mAdditionalDisplayInputPropertiesLock) {
-            mIcon = null;
-            mIconType = iconType;
+            mPointerIcon = null;
+            mPointerIconType = iconType;
 
             if (!mCurrentDisplayProperties.pointerIconVisible) return;
 
-            mNative.setPointerIconType(mIconType);
+            mNative.setPointerIconType(mPointerIconType);
         }
     }
 
@@ -2339,12 +1815,12 @@
     public void setCustomPointerIcon(PointerIcon icon) {
         Objects.requireNonNull(icon);
         synchronized (mAdditionalDisplayInputPropertiesLock) {
-            mIconType = PointerIcon.TYPE_CUSTOM;
-            mIcon = icon;
+            mPointerIconType = PointerIcon.TYPE_CUSTOM;
+            mPointerIcon = icon;
 
             if (!mCurrentDisplayProperties.pointerIconVisible) return;
 
-            mNative.setCustomPointerIcon(mIcon);
+            mNative.setCustomPointerIcon(mPointerIcon);
         }
     }
 
@@ -2676,81 +2152,107 @@
     @EnforcePermission(Manifest.permission.BLUETOOTH)
     @Override
     public String getInputDeviceBluetoothAddress(int deviceId) {
-        return mNative.getBluetoothAddress(deviceId);
+        super.getInputDeviceBluetoothAddress_enforcePermission();
+
+        final String address = mNative.getBluetoothAddress(deviceId);
+        if (address == null) return null;
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            throw new IllegalStateException("The Bluetooth address of input device " + deviceId
+                    + " should not be invalid: address=" + address);
+        }
+        return address;
+    }
+
+    @EnforcePermission(Manifest.permission.MONITOR_INPUT)
+    @Override
+    public void pilferPointers(IBinder inputChannelToken) {
+        super.pilferPointers_enforcePermission();
+
+        Objects.requireNonNull(inputChannelToken);
+        mNative.pilferPointers(inputChannelToken);
     }
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
 
-        pw.println("INPUT MANAGER (dumpsys input)\n");
+        ipw.println("INPUT MANAGER (dumpsys input)\n");
         String dumpStr = mNative.dump();
         if (dumpStr != null) {
             pw.println(dumpStr);
         }
 
-        pw.println("Input Manager Service (Java) State:");
-        dumpAssociations(pw, "  " /*prefix*/);
-        dumpSpyWindowGestureMonitors(pw, "  " /*prefix*/);
-        dumpDisplayInputPropertiesValues(pw, "  " /*prefix*/);
-        mBatteryController.dump(pw, "  " /*prefix*/);
-        mKeyboardBacklightController.dump(pw, "  " /*prefix*/);
+        ipw.println("Input Manager Service (Java) State:");
+        ipw.increaseIndent();
+        dumpAssociations(ipw);
+        dumpSpyWindowGestureMonitors(ipw);
+        dumpDisplayInputPropertiesValues(ipw);
+        mBatteryController.dump(ipw);
+        mKeyboardBacklightController.dump(ipw);
     }
 
-    private void dumpAssociations(PrintWriter pw, String prefix) {
+    private void dumpAssociations(IndentingPrintWriter pw) {
         if (!mStaticAssociations.isEmpty()) {
-            pw.println(prefix + "Static Associations:");
+            pw.println("Static Associations:");
             mStaticAssociations.forEach((k, v) -> {
-                pw.print(prefix + "  port: " + k);
+                pw.print("  port: " + k);
                 pw.println("  display: " + v);
             });
         }
 
         synchronized (mAssociationsLock) {
             if (!mRuntimeAssociations.isEmpty()) {
-                pw.println(prefix + "Runtime Associations:");
+                pw.println("Runtime Associations:");
                 mRuntimeAssociations.forEach((k, v) -> {
-                    pw.print(prefix + "  port: " + k);
+                    pw.print("  port: " + k);
                     pw.println("  display: " + v);
                 });
             }
             if (!mUniqueIdAssociations.isEmpty()) {
-                pw.println(prefix + "Unique Id Associations:");
+                pw.println("Unique Id Associations:");
                 mUniqueIdAssociations.forEach((k, v) -> {
-                    pw.print(prefix + "  port: " + k);
+                    pw.print("  port: " + k);
                     pw.println("  uniqueId: " + v);
                 });
             }
         }
     }
 
-    private void dumpSpyWindowGestureMonitors(PrintWriter pw, String prefix) {
+    private void dumpSpyWindowGestureMonitors(IndentingPrintWriter pw) {
         synchronized (mInputMonitors) {
             if (mInputMonitors.isEmpty()) return;
-            pw.println(prefix + "Gesture Monitors (implemented as spy windows):");
+            pw.println("Gesture Monitors (implemented as spy windows):");
             int i = 0;
             for (final GestureMonitorSpyWindow monitor : mInputMonitors.values()) {
-                pw.append(prefix + "  " + i++ + ": ").println(monitor.dump());
+                pw.append("  " + i++ + ": ").println(monitor.dump());
             }
         }
     }
 
-    private void dumpDisplayInputPropertiesValues(PrintWriter pw, String prefix) {
+    private void dumpDisplayInputPropertiesValues(IndentingPrintWriter pw) {
         synchronized (mAdditionalDisplayInputPropertiesLock) {
             if (mAdditionalDisplayInputProperties.size() != 0) {
-                pw.println(prefix + "mAdditionalDisplayInputProperties:");
+                pw.println("mAdditionalDisplayInputProperties:");
+                pw.increaseIndent();
                 for (int i = 0; i < mAdditionalDisplayInputProperties.size(); i++) {
-                    pw.println(prefix + "  displayId: "
+                    pw.println("displayId: "
                             + mAdditionalDisplayInputProperties.keyAt(i));
                     final AdditionalDisplayInputProperties properties =
                             mAdditionalDisplayInputProperties.valueAt(i);
-                    pw.println(prefix + "  pointerAcceleration: " + properties.pointerAcceleration);
-                    pw.println(prefix + "  pointerIconVisible: " + properties.pointerIconVisible);
+                    pw.println("pointerAcceleration: " + properties.pointerAcceleration);
+                    pw.println("pointerIconVisible: " + properties.pointerIconVisible);
                 }
+                pw.decreaseIndent();
             }
             if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
-                pw.println(prefix + "mOverriddenPointerDisplayId: " + mOverriddenPointerDisplayId);
+                pw.println("mOverriddenPointerDisplayId: " + mOverriddenPointerDisplayId);
             }
+
+            pw.println("mAcknowledgedPointerDisplayId=" + mAcknowledgedPointerDisplayId);
+            pw.println("mRequestedPointerDisplayId=" + mRequestedPointerDisplayId);
+            pw.println("mPointerIconType=" + PointerIcon.typeToString(mPointerIconType));
+            pw.println("mPointerIcon=" + mPointerIcon);
         }
     }
     private boolean checkCallingPermission(String permission, String func) {
@@ -3233,28 +2735,7 @@
         if (!mSystemReady) {
             return null;
         }
-
-        String keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
-        if (keyboardLayoutDescriptor == null) {
-            return null;
-        }
-
-        final String[] result = new String[2];
-        visitKeyboardLayout(keyboardLayoutDescriptor,
-                (resources, keyboardLayoutResId, layout) -> {
-                    try (InputStreamReader stream = new InputStreamReader(
-                            resources.openRawResource(keyboardLayoutResId))) {
-                        result[0] = layout.getDescriptor();
-                        result[1] = Streams.readFully(stream);
-                    } catch (IOException | NotFoundException ignored) {
-                    }
-                });
-        if (result[0] == null) {
-            Slog.w(TAG, "Could not get keyboard layout with descriptor '"
-                    + keyboardLayoutDescriptor + "'.");
-            return null;
-        }
-        return result;
+        return mKeyboardLayoutManager.getKeyboardLayoutOverlay(identifier);
     }
 
     // Native callback.
@@ -3452,15 +2933,6 @@
                 case MSG_DELIVER_INPUT_DEVICES_CHANGED:
                     deliverInputDevicesChanged((InputDevice[])msg.obj);
                     break;
-                case MSG_SWITCH_KEYBOARD_LAYOUT:
-                    handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
-                    break;
-                case MSG_RELOAD_KEYBOARD_LAYOUTS:
-                    reloadKeyboardLayouts();
-                    break;
-                case MSG_UPDATE_KEYBOARD_LAYOUTS:
-                    updateKeyboardLayouts();
-                    break;
                 case MSG_RELOAD_DEVICE_ALIASES:
                     reloadDeviceAliases();
                     break;
@@ -3529,39 +3001,6 @@
         }
     }
 
-    private static final class KeyboardLayoutDescriptor {
-        public String packageName;
-        public String receiverName;
-        public String keyboardLayoutName;
-
-        public static String format(String packageName,
-                String receiverName, String keyboardName) {
-            return packageName + "/" + receiverName + "/" + keyboardName;
-        }
-
-        public static KeyboardLayoutDescriptor parse(String descriptor) {
-            int pos = descriptor.indexOf('/');
-            if (pos < 0 || pos + 1 == descriptor.length()) {
-                return null;
-            }
-            int pos2 = descriptor.indexOf('/', pos + 1);
-            if (pos2 < pos + 2 || pos2 + 1 == descriptor.length()) {
-                return null;
-            }
-
-            KeyboardLayoutDescriptor result = new KeyboardLayoutDescriptor();
-            result.packageName = descriptor.substring(0, pos);
-            result.receiverName = descriptor.substring(pos + 1, pos2);
-            result.keyboardLayoutName = descriptor.substring(pos2 + 1);
-            return result;
-        }
-    }
-
-    private interface KeyboardLayoutVisitor {
-        void visitKeyboardLayout(Resources resources,
-                int keyboardLayoutResId, KeyboardLayout layout);
-    }
-
     private final class InputDevicesChangedListenerRecord implements DeathRecipient {
         private final int mPid;
         private final IInputDevicesChangedListener mListener;
@@ -3780,6 +3219,16 @@
         }
 
         @Override
+        public void onInputMethodSubtypeChangedForKeyboardLayoutMapping(@UserIdInt int userId,
+                @Nullable InputMethodSubtypeHandle subtypeHandle,
+                @Nullable InputMethodSubtype subtype) {
+            if (DEBUG) {
+                Slog.i(TAG, "InputMethodSubtype changed: userId=" + userId
+                        + " subtypeHandle=" + subtypeHandle);
+            }
+        }
+
+        @Override
         public void incrementKeyboardBacklight(int deviceId) {
             mKeyboardBacklightController.incrementKeyboardBacklight(deviceId);
         }
@@ -3839,11 +3288,11 @@
         if (properties.pointerIconVisible != mCurrentDisplayProperties.pointerIconVisible) {
             mCurrentDisplayProperties.pointerIconVisible = properties.pointerIconVisible;
             if (properties.pointerIconVisible) {
-                if (mIconType == PointerIcon.TYPE_CUSTOM) {
-                    Objects.requireNonNull(mIcon);
-                    mNative.setCustomPointerIcon(mIcon);
+                if (mPointerIconType == PointerIcon.TYPE_CUSTOM) {
+                    Objects.requireNonNull(mPointerIcon);
+                    mNative.setCustomPointerIcon(mPointerIcon);
                 } else {
-                    mNative.setPointerIconType(mIconType);
+                    mNative.setPointerIconType(mPointerIconType);
                 }
             } else {
                 mNative.setPointerIconType(PointerIcon.TYPE_NULL);
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index e33f28c..b207e27 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -24,6 +24,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -216,12 +217,14 @@
         return null;
     }
 
-    void dump(PrintWriter pw, String prefix) {
-        pw.println(prefix + TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights");
+    void dump(PrintWriter pw) {
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+        ipw.println(TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights");
+        ipw.increaseIndent();
         for (int i = 0; i < mKeyboardBacklights.size(); i++) {
             Light light = mKeyboardBacklights.get(i);
-            pw.println(prefix + "  " + i + ": { id: " + light.getId() + ", name: " + light.getName()
-                    + " }");
+            ipw.println(i + ": { id: " + light.getId() + ", name: " + light.getName() + " }");
         }
+        ipw.decreaseIndent();
     }
 }
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
new file mode 100644
index 0000000..1bb14aa
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -0,0 +1,776 @@
+/*
+ * 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.input;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.hardware.input.InputDeviceIdentifier;
+import android.hardware.input.InputManager;
+import android.hardware.input.KeyboardLayout;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.LocaleList;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.view.InputDevice;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.Toast;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.Streams;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * A component of {@link InputManagerService} responsible for managing Physical Keyboard layouts.
+ *
+ * @hide
+ */
+final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
+
+    private static final String TAG = "KeyboardLayoutManager";
+
+    // To enable these logs, run: 'adb shell setprop log.tag.KeyboardLayoutManager DEBUG'
+    // (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final int MSG_UPDATE_EXISTING_DEVICES = 1;
+    private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
+    private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
+    private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
+
+    private final Context mContext;
+    private final NativeInputManagerService mNative;
+    // The PersistentDataStore should be locked before use.
+    @GuardedBy("mDataStore")
+    private final PersistentDataStore mDataStore;
+    private final Handler mHandler;
+    private final List<InputDevice> mKeyboardsWithMissingLayouts = new ArrayList<>();
+    private boolean mKeyboardLayoutNotificationShown = false;
+    private Toast mSwitchedKeyboardLayoutToast;
+
+    KeyboardLayoutManager(Context context, NativeInputManagerService nativeService,
+            PersistentDataStore dataStore, Looper looper) {
+        mContext = context;
+        mNative = nativeService;
+        mDataStore = dataStore;
+        mHandler = new Handler(looper, this::handleMessage, true /* async */);
+    }
+
+    public void systemRunning() {
+        // Listen to new Package installations to fetch new Keyboard layouts
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        filter.addDataScheme("package");
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                updateKeyboardLayouts();
+            }
+        }, filter, null, mHandler);
+
+        mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);
+
+        // Listen to new InputDevice changes
+        InputManager inputManager = Objects.requireNonNull(
+                mContext.getSystemService(InputManager.class));
+        inputManager.registerInputDeviceListener(this, mHandler);
+
+        Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES,
+                inputManager.getInputDeviceIds());
+        mHandler.sendMessage(msg);
+    }
+
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+        onInputDeviceChanged(deviceId);
+    }
+
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        mKeyboardsWithMissingLayouts.removeIf(device -> device.getId() == deviceId);
+        maybeUpdateNotification();
+    }
+
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+        final InputDevice inputDevice = getInputDevice(deviceId);
+        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
+            return;
+        }
+        synchronized (mDataStore) {
+            String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
+            if (layout == null) {
+                layout = getDefaultKeyboardLayout(inputDevice);
+                if (layout != null) {
+                    setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
+                } else {
+                    mKeyboardsWithMissingLayouts.add(inputDevice);
+                }
+            }
+            maybeUpdateNotification();
+        }
+    }
+
+    private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
+        final Locale systemLocale = mContext.getResources().getConfiguration().locale;
+        // If our locale doesn't have a language for some reason, then we don't really have a
+        // reasonable default.
+        if (TextUtils.isEmpty(systemLocale.getLanguage())) {
+            return null;
+        }
+        final List<KeyboardLayout> layouts = new ArrayList<>();
+        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> {
+            // Only select a default when we know the layout is appropriate. For now, this
+            // means it's a custom layout for a specific keyboard.
+            if (layout.getVendorId() != inputDevice.getVendorId()
+                    || layout.getProductId() != inputDevice.getProductId()) {
+                return;
+            }
+            final LocaleList locales = layout.getLocales();
+            for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
+                final Locale locale = locales.get(localeIndex);
+                if (locale != null && isCompatibleLocale(systemLocale, locale)) {
+                    layouts.add(layout);
+                    break;
+                }
+            }
+        });
+
+        if (layouts.isEmpty()) {
+            return null;
+        }
+
+        // First sort so that ones with higher priority are listed at the top
+        Collections.sort(layouts);
+        // Next we want to try to find an exact match of language, country and variant.
+        for (KeyboardLayout layout : layouts) {
+            final LocaleList locales = layout.getLocales();
+            for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
+                final Locale locale = locales.get(localeIndex);
+                if (locale != null && locale.getCountry().equals(systemLocale.getCountry())
+                        && locale.getVariant().equals(systemLocale.getVariant())) {
+                    return layout.getDescriptor();
+                }
+            }
+        }
+        // Then try an exact match of language and country
+        for (KeyboardLayout layout : layouts) {
+            final LocaleList locales = layout.getLocales();
+            for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
+                final Locale locale = locales.get(localeIndex);
+                if (locale != null && locale.getCountry().equals(systemLocale.getCountry())) {
+                    return layout.getDescriptor();
+                }
+            }
+        }
+
+        // Give up and just use the highest priority layout with matching language
+        return layouts.get(0).getDescriptor();
+    }
+
+    private static boolean isCompatibleLocale(Locale systemLocale, Locale keyboardLocale) {
+        // Different languages are never compatible
+        if (!systemLocale.getLanguage().equals(keyboardLocale.getLanguage())) {
+            return false;
+        }
+        // If both the system and the keyboard layout have a country specifier, they must be equal.
+        return TextUtils.isEmpty(systemLocale.getCountry())
+                || TextUtils.isEmpty(keyboardLocale.getCountry())
+                || systemLocale.getCountry().equals(keyboardLocale.getCountry());
+    }
+
+    private void updateKeyboardLayouts() {
+        // Scan all input devices state for keyboard layouts that have been uninstalled.
+        final HashSet<String> availableKeyboardLayouts = new HashSet<String>();
+        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) ->
+                availableKeyboardLayouts.add(layout.getDescriptor()));
+        synchronized (mDataStore) {
+            try {
+                mDataStore.removeUninstalledKeyboardLayouts(availableKeyboardLayouts);
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+
+        // Reload keyboard layouts.
+        reloadKeyboardLayouts();
+    }
+
+    public KeyboardLayout[] getKeyboardLayouts() {
+        final ArrayList<KeyboardLayout> list = new ArrayList<>();
+        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> list.add(layout));
+        return list.toArray(new KeyboardLayout[0]);
+    }
+
+    public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
+            final InputDeviceIdentifier identifier) {
+        final String[] enabledLayoutDescriptors =
+                getEnabledKeyboardLayoutsForInputDevice(identifier);
+        final ArrayList<KeyboardLayout> enabledLayouts =
+                new ArrayList<>(enabledLayoutDescriptors.length);
+        final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
+        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
+            boolean mHasSeenDeviceSpecificLayout;
+
+            @Override
+            public void visitKeyboardLayout(Resources resources,
+                    int keyboardLayoutResId, KeyboardLayout layout) {
+                // First check if it's enabled. If the keyboard layout is enabled then we always
+                // want to return it as a possible layout for the device.
+                for (String s : enabledLayoutDescriptors) {
+                    if (s != null && s.equals(layout.getDescriptor())) {
+                        enabledLayouts.add(layout);
+                        return;
+                    }
+                }
+                // Next find any potential layouts that aren't yet enabled for the device. For
+                // devices that have special layouts we assume there's a reason that the generic
+                // layouts don't work for them so we don't want to return them since it's likely
+                // to result in a poor user experience.
+                if (layout.getVendorId() == identifier.getVendorId()
+                        && layout.getProductId() == identifier.getProductId()) {
+                    if (!mHasSeenDeviceSpecificLayout) {
+                        mHasSeenDeviceSpecificLayout = true;
+                        potentialLayouts.clear();
+                    }
+                    potentialLayouts.add(layout);
+                } else if (layout.getVendorId() == -1 && layout.getProductId() == -1
+                        && !mHasSeenDeviceSpecificLayout) {
+                    potentialLayouts.add(layout);
+                }
+            }
+        });
+        return Stream.concat(enabledLayouts.stream(), potentialLayouts.stream()).toArray(
+                KeyboardLayout[]::new);
+    }
+
+    public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
+        Objects.requireNonNull(keyboardLayoutDescriptor,
+                "keyboardLayoutDescriptor must not be null");
+
+        final KeyboardLayout[] result = new KeyboardLayout[1];
+        visitKeyboardLayout(keyboardLayoutDescriptor,
+                (resources, keyboardLayoutResId, layout) -> result[0] = layout);
+        if (result[0] == null) {
+            Slog.w(TAG, "Could not get keyboard layout with descriptor '"
+                    + keyboardLayoutDescriptor + "'.");
+        }
+        return result[0];
+    }
+
+    private void visitAllKeyboardLayouts(KeyboardLayoutVisitor visitor) {
+        final PackageManager pm = mContext.getPackageManager();
+        Intent intent = new Intent(InputManager.ACTION_QUERY_KEYBOARD_LAYOUTS);
+        for (ResolveInfo resolveInfo : pm.queryBroadcastReceivers(intent,
+                PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE)) {
+            final ActivityInfo activityInfo = resolveInfo.activityInfo;
+            final int priority = resolveInfo.priority;
+            visitKeyboardLayoutsInPackage(pm, activityInfo, null, priority, visitor);
+        }
+    }
+
+    private void visitKeyboardLayout(String keyboardLayoutDescriptor,
+            KeyboardLayoutVisitor visitor) {
+        KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(keyboardLayoutDescriptor);
+        if (d != null) {
+            final PackageManager pm = mContext.getPackageManager();
+            try {
+                ActivityInfo receiver = pm.getReceiverInfo(
+                        new ComponentName(d.packageName, d.receiverName),
+                        PackageManager.GET_META_DATA
+                                | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+                visitKeyboardLayoutsInPackage(pm, receiver, d.keyboardLayoutName, 0, visitor);
+            } catch (PackageManager.NameNotFoundException ignored) {
+            }
+        }
+    }
+
+    private void visitKeyboardLayoutsInPackage(PackageManager pm, ActivityInfo receiver,
+            String keyboardName, int requestedPriority, KeyboardLayoutVisitor visitor) {
+        Bundle metaData = receiver.metaData;
+        if (metaData == null) {
+            return;
+        }
+
+        int configResId = metaData.getInt(InputManager.META_DATA_KEYBOARD_LAYOUTS);
+        if (configResId == 0) {
+            Slog.w(TAG, "Missing meta-data '" + InputManager.META_DATA_KEYBOARD_LAYOUTS
+                    + "' on receiver " + receiver.packageName + "/" + receiver.name);
+            return;
+        }
+
+        CharSequence receiverLabel = receiver.loadLabel(pm);
+        String collection = receiverLabel != null ? receiverLabel.toString() : "";
+        int priority;
+        if ((receiver.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+            priority = requestedPriority;
+        } else {
+            priority = 0;
+        }
+
+        try {
+            Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
+            try (XmlResourceParser parser = resources.getXml(configResId)) {
+                XmlUtils.beginDocument(parser, "keyboard-layouts");
+
+                while (true) {
+                    XmlUtils.nextElement(parser);
+                    String element = parser.getName();
+                    if (element == null) {
+                        break;
+                    }
+                    if (element.equals("keyboard-layout")) {
+                        TypedArray a = resources.obtainAttributes(
+                                parser, R.styleable.KeyboardLayout);
+                        try {
+                            String name = a.getString(
+                                    R.styleable.KeyboardLayout_name);
+                            String label = a.getString(
+                                    R.styleable.KeyboardLayout_label);
+                            int keyboardLayoutResId = a.getResourceId(
+                                    R.styleable.KeyboardLayout_keyboardLayout,
+                                    0);
+                            String languageTags = a.getString(
+                                    R.styleable.KeyboardLayout_locale);
+                            LocaleList locales = getLocalesFromLanguageTags(languageTags);
+                            int vid = a.getInt(
+                                    R.styleable.KeyboardLayout_vendorId, -1);
+                            int pid = a.getInt(
+                                    R.styleable.KeyboardLayout_productId, -1);
+
+                            if (name == null || label == null || keyboardLayoutResId == 0) {
+                                Slog.w(TAG, "Missing required 'name', 'label' or 'keyboardLayout' "
+                                        + "attributes in keyboard layout "
+                                        + "resource from receiver "
+                                        + receiver.packageName + "/" + receiver.name);
+                            } else {
+                                String descriptor = KeyboardLayoutDescriptor.format(
+                                        receiver.packageName, receiver.name, name);
+                                if (keyboardName == null || name.equals(keyboardName)) {
+                                    KeyboardLayout layout = new KeyboardLayout(
+                                            descriptor, label, collection, priority,
+                                            locales, vid, pid);
+                                    visitor.visitKeyboardLayout(
+                                            resources, keyboardLayoutResId, layout);
+                                }
+                            }
+                        } finally {
+                            a.recycle();
+                        }
+                    } else {
+                        Slog.w(TAG, "Skipping unrecognized element '" + element
+                                + "' in keyboard layout resource from receiver "
+                                + receiver.packageName + "/" + receiver.name);
+                    }
+                }
+            }
+        } catch (Exception ex) {
+            Slog.w(TAG, "Could not parse keyboard layout resource from receiver "
+                    + receiver.packageName + "/" + receiver.name, ex);
+        }
+    }
+
+    @NonNull
+    private static LocaleList getLocalesFromLanguageTags(String languageTags) {
+        if (TextUtils.isEmpty(languageTags)) {
+            return LocaleList.getEmptyLocaleList();
+        }
+        return LocaleList.forLanguageTags(languageTags.replace('|', ','));
+    }
+
+    /**
+     * Builds a layout descriptor for the vendor/product. This returns the
+     * descriptor for ids that aren't useful (such as the default 0, 0).
+     */
+    private String getLayoutDescriptor(InputDeviceIdentifier identifier) {
+        Objects.requireNonNull(identifier, "identifier must not be null");
+        Objects.requireNonNull(identifier.getDescriptor(), "descriptor must not be null");
+
+        if (identifier.getVendorId() == 0 && identifier.getProductId() == 0) {
+            return identifier.getDescriptor();
+        }
+        return "vendor:" + identifier.getVendorId() + ",product:" + identifier.getProductId();
+    }
+
+    public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
+        String key = getLayoutDescriptor(identifier);
+        synchronized (mDataStore) {
+            String layout;
+            // try loading it using the layout descriptor if we have it
+            layout = mDataStore.getCurrentKeyboardLayout(key);
+            if (layout == null && !key.equals(identifier.getDescriptor())) {
+                // if it doesn't exist fall back to the device descriptor
+                layout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "getCurrentKeyboardLayoutForInputDevice() "
+                        + identifier.toString() + ": " + layout);
+            }
+            return layout;
+        }
+    }
+
+    public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            String keyboardLayoutDescriptor) {
+        Objects.requireNonNull(keyboardLayoutDescriptor,
+                "keyboardLayoutDescriptor must not be null");
+
+        String key = getLayoutDescriptor(identifier);
+        synchronized (mDataStore) {
+            try {
+                if (mDataStore.setCurrentKeyboardLayout(key, keyboardLayoutDescriptor)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "setCurrentKeyboardLayoutForInputDevice() " + identifier
+                                + " key: " + key
+                                + " keyboardLayoutDescriptor: " + keyboardLayoutDescriptor);
+                    }
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
+        String key = getLayoutDescriptor(identifier);
+        synchronized (mDataStore) {
+            String[] layouts = mDataStore.getKeyboardLayouts(key);
+            if ((layouts == null || layouts.length == 0)
+                    && !key.equals(identifier.getDescriptor())) {
+                layouts = mDataStore.getKeyboardLayouts(identifier.getDescriptor());
+            }
+            return layouts;
+        }
+    }
+
+    public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            String keyboardLayoutDescriptor) {
+        Objects.requireNonNull(keyboardLayoutDescriptor,
+                "keyboardLayoutDescriptor must not be null");
+
+        String key = getLayoutDescriptor(identifier);
+        synchronized (mDataStore) {
+            try {
+                String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
+                if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
+                    oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
+                }
+                if (mDataStore.addKeyboardLayout(key, keyboardLayoutDescriptor)
+                        && !Objects.equals(oldLayout,
+                        mDataStore.getCurrentKeyboardLayout(key))) {
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            String keyboardLayoutDescriptor) {
+        Objects.requireNonNull(keyboardLayoutDescriptor,
+                "keyboardLayoutDescriptor must not be null");
+
+        String key = getLayoutDescriptor(identifier);
+        synchronized (mDataStore) {
+            try {
+                String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
+                if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
+                    oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
+                }
+                boolean removed = mDataStore.removeKeyboardLayout(key, keyboardLayoutDescriptor);
+                if (!key.equals(identifier.getDescriptor())) {
+                    // We need to remove from both places to ensure it is gone
+                    removed |= mDataStore.removeKeyboardLayout(identifier.getDescriptor(),
+                            keyboardLayoutDescriptor);
+                }
+                if (removed && !Objects.equals(oldLayout,
+                        mDataStore.getCurrentKeyboardLayout(key))) {
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @NonNull InputMethodSubtype imeSubtype) {
+        // TODO(b/259530132): Implement the new keyboard layout API: Returning non-IME specific
+        //  layout for now.
+        return getCurrentKeyboardLayoutForInputDevice(identifier);
+    }
+
+    public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @NonNull InputMethodSubtype imeSubtype, String keyboardLayoutDescriptor) {
+        // TODO(b/259530132): Implement the new keyboard layout API: setting non-IME specific
+        //  layout for now.
+        setCurrentKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
+    }
+
+    public String[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @NonNull InputMethodSubtype imeSubtype) {
+        // TODO(b/259530132): Implement the new keyboard layout API: Returning list of all
+        //  layouts for now.
+        KeyboardLayout[] allLayouts = getKeyboardLayouts();
+        String[] allLayoutDesc = new String[allLayouts.length];
+        for (int i = 0; i < allLayouts.length; i++) {
+            allLayoutDesc[i] = allLayouts[i].getDescriptor();
+        }
+        return allLayoutDesc;
+    }
+
+    public void switchKeyboardLayout(int deviceId, int direction) {
+        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
+    }
+
+    // Must be called on handler.
+    private void handleSwitchKeyboardLayout(int deviceId, int direction) {
+        final InputDevice device = getInputDevice(deviceId);
+        if (device != null) {
+            final boolean changed;
+            final String keyboardLayoutDescriptor;
+
+            String key = getLayoutDescriptor(device.getIdentifier());
+            synchronized (mDataStore) {
+                try {
+                    changed = mDataStore.switchKeyboardLayout(key, direction);
+                    keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
+                            key);
+                } finally {
+                    mDataStore.saveIfNeeded();
+                }
+            }
+
+            if (changed) {
+                if (mSwitchedKeyboardLayoutToast != null) {
+                    mSwitchedKeyboardLayoutToast.cancel();
+                    mSwitchedKeyboardLayoutToast = null;
+                }
+                if (keyboardLayoutDescriptor != null) {
+                    KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
+                    if (keyboardLayout != null) {
+                        mSwitchedKeyboardLayoutToast = Toast.makeText(
+                                mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
+                        mSwitchedKeyboardLayoutToast.show();
+                    }
+                }
+
+                reloadKeyboardLayouts();
+            }
+        }
+    }
+
+    public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
+        String keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
+        if (keyboardLayoutDescriptor == null) {
+            return null;
+        }
+
+        final String[] result = new String[2];
+        visitKeyboardLayout(keyboardLayoutDescriptor,
+                (resources, keyboardLayoutResId, layout) -> {
+                    try (InputStreamReader stream = new InputStreamReader(
+                            resources.openRawResource(keyboardLayoutResId))) {
+                        result[0] = layout.getDescriptor();
+                        result[1] = Streams.readFully(stream);
+                    } catch (IOException | Resources.NotFoundException ignored) {
+                    }
+                });
+        if (result[0] == null) {
+            Slog.w(TAG, "Could not get keyboard layout with descriptor '"
+                    + keyboardLayoutDescriptor + "'.");
+            return null;
+        }
+        return result;
+    }
+
+    private void reloadKeyboardLayouts() {
+        if (DEBUG) {
+            Slog.d(TAG, "Reloading keyboard layouts.");
+        }
+        mNative.reloadKeyboardLayouts();
+    }
+
+    private void maybeUpdateNotification() {
+        NotificationManager notificationManager = mContext.getSystemService(
+                NotificationManager.class);
+        if (notificationManager == null) {
+            return;
+        }
+        if (!mKeyboardsWithMissingLayouts.isEmpty()) {
+            if (mKeyboardsWithMissingLayouts.size() > 1) {
+                // We have more than one keyboard missing a layout, so drop the
+                // user at the generic input methods page, so they can pick which
+                // one to set.
+                showMissingKeyboardLayoutNotification(notificationManager, null);
+            } else {
+                showMissingKeyboardLayoutNotification(notificationManager,
+                        mKeyboardsWithMissingLayouts.get(0));
+            }
+        } else if (mKeyboardLayoutNotificationShown) {
+            hideMissingKeyboardLayoutNotification(notificationManager);
+        }
+    }
+
+    // Must be called on handler.
+    private void showMissingKeyboardLayoutNotification(NotificationManager notificationManager,
+            InputDevice device) {
+        if (!mKeyboardLayoutNotificationShown) {
+            final Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS);
+            if (device != null) {
+                intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, device.getIdentifier());
+            }
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                    | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            final PendingIntent keyboardLayoutIntent = PendingIntent.getActivityAsUser(mContext, 0,
+                    intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
+
+            Resources r = mContext.getResources();
+            Notification notification =
+                    new Notification.Builder(mContext, SystemNotificationChannels.PHYSICAL_KEYBOARD)
+                            .setContentTitle(r.getString(
+                                    R.string.select_keyboard_layout_notification_title))
+                            .setContentText(r.getString(
+                                    R.string.select_keyboard_layout_notification_message))
+                            .setContentIntent(keyboardLayoutIntent)
+                            .setSmallIcon(R.drawable.ic_settings_language)
+                            .setColor(mContext.getColor(
+                                    com.android.internal.R.color.system_notification_accent_color))
+                            .build();
+            notificationManager.notifyAsUser(null,
+                    SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
+                    notification, UserHandle.ALL);
+            mKeyboardLayoutNotificationShown = true;
+        }
+    }
+
+    // Must be called on handler.
+    private void hideMissingKeyboardLayoutNotification(NotificationManager notificationManager) {
+        if (mKeyboardLayoutNotificationShown) {
+            mKeyboardLayoutNotificationShown = false;
+            notificationManager.cancelAsUser(null,
+                    SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
+                    UserHandle.ALL);
+        }
+    }
+
+    private boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_UPDATE_EXISTING_DEVICES:
+                // Circle through all the already added input devices
+                // Need to do it on handler thread and not block IMS thread
+                for (int deviceId : (int[]) msg.obj) {
+                    onInputDeviceAdded(deviceId);
+                }
+                return true;
+            case MSG_SWITCH_KEYBOARD_LAYOUT:
+                handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
+                return true;
+            case MSG_RELOAD_KEYBOARD_LAYOUTS:
+                reloadKeyboardLayouts();
+                return true;
+            case MSG_UPDATE_KEYBOARD_LAYOUTS:
+                updateKeyboardLayouts();
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private InputDevice getInputDevice(int deviceId) {
+        InputManager inputManager = mContext.getSystemService(InputManager.class);
+        return inputManager != null ? inputManager.getInputDevice(deviceId) : null;
+    }
+
+    private static final class KeyboardLayoutDescriptor {
+        public String packageName;
+        public String receiverName;
+        public String keyboardLayoutName;
+
+        public static String format(String packageName,
+                String receiverName, String keyboardName) {
+            return packageName + "/" + receiverName + "/" + keyboardName;
+        }
+
+        public static KeyboardLayoutDescriptor parse(String descriptor) {
+            int pos = descriptor.indexOf('/');
+            if (pos < 0 || pos + 1 == descriptor.length()) {
+                return null;
+            }
+            int pos2 = descriptor.indexOf('/', pos + 1);
+            if (pos2 < pos + 2 || pos2 + 1 == descriptor.length()) {
+                return null;
+            }
+
+            KeyboardLayoutDescriptor result = new KeyboardLayoutDescriptor();
+            result.packageName = descriptor.substring(0, pos);
+            result.receiverName = descriptor.substring(pos + 1, pos2);
+            result.keyboardLayoutName = descriptor.substring(pos2 + 1);
+            return result;
+        }
+    }
+
+    private interface KeyboardLayoutVisitor {
+        void visitKeyboardLayout(Resources resources,
+                int keyboardLayoutResId, KeyboardLayout layout);
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
index 8e6452b..4b040fa 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
@@ -58,6 +58,7 @@
     private static final String NODE_IMI = "imi";
     private static final String ATTR_ID = "id";
     private static final String ATTR_LABEL = "label";
+    private static final String ATTR_NAME_OVERRIDE = "nameOverride";
     private static final String ATTR_ICON = "icon";
     private static final String ATTR_IME_SUBTYPE_ID = "subtypeId";
     private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
@@ -161,6 +162,7 @@
                     }
                     out.attributeInt(null, ATTR_ICON, subtype.getIconResId());
                     out.attributeInt(null, ATTR_LABEL, subtype.getNameResId());
+                    out.attribute(null, ATTR_NAME_OVERRIDE, subtype.getNameOverride().toString());
                     out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
                     out.attribute(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG,
                             subtype.getLanguageTag());
@@ -243,6 +245,8 @@
                     }
                     final int icon = parser.getAttributeInt(null, ATTR_ICON);
                     final int label = parser.getAttributeInt(null, ATTR_LABEL);
+                    final String untranslatableName = parser.getAttributeValue(null,
+                            ATTR_NAME_OVERRIDE);
                     final String imeSubtypeLocale =
                             parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
                     final String languageTag =
@@ -258,6 +262,7 @@
                     final InputMethodSubtype.InputMethodSubtypeBuilder
                             builder = new InputMethodSubtype.InputMethodSubtypeBuilder()
                             .setSubtypeNameResId(label)
+                            .setSubtypeNameOverride(untranslatableName)
                             .setSubtypeIconResId(icon)
                             .setSubtypeLocale(imeSubtypeLocale)
                             .setLanguageTag(languageTag)
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index a4830be..1c7294f 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -18,9 +18,12 @@
 
 import static android.view.InputDevice.SOURCE_STYLUS;
 
+import android.Manifest;
 import android.annotation.AnyThread;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UiThread;
+import android.hardware.input.InputManager;
 import android.os.IBinder;
 import android.os.Looper;
 import android.util.Slog;
@@ -141,6 +144,7 @@
      * input events and disposing the input event receiver.
      * @return the handwriting session to send to the IME, or null if the request was invalid.
      */
+    @RequiresPermission(Manifest.permission.MONITOR_INPUT)
     @UiThread
     @Nullable
     HandwritingSession startHandwritingSession(
@@ -169,7 +173,7 @@
         }
         if (DEBUG) Slog.d(TAG, "Starting handwriting session in display: " + mCurrentDisplayId);
 
-        mInputManagerInternal.pilferPointers(mHandwritingSurface.getInputChannel().getToken());
+        InputManager.getInstance().pilferPointers(mHandwritingSurface.getInputChannel().getToken());
 
         // Stop processing more events.
         mHandwritingEventReceiver.dispose();
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index 1a0f6f7..c53f1a5 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -28,6 +28,7 @@
 import android.view.InputChannel;
 import android.view.MotionEvent;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputMethodSubtype;
 import android.window.ImeOnBackInvokedDispatcher;
@@ -109,11 +110,10 @@
 
     @AnyThread
     void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations,
-            int configChanges, @InputMethodNavButtonFlags int navigationBarFlags) {
+            @InputMethodNavButtonFlags int navigationBarFlags) {
         final IInputMethod.InitParams params = new IInputMethod.InitParams();
         params.token = token;
         params.privilegedOperations = privilegedOperations;
-        params.configChanges = configChanges;
         params.navigationBarFlags = navigationBarFlags;
         try {
             mTarget.initializeInternal(params);
@@ -198,9 +198,10 @@
 
     // TODO(b/192412909): Convert this back to void method
     @AnyThread
-    boolean showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
+    boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, int flags,
+            ResultReceiver resultReceiver) {
         try {
-            mTarget.showSoftInput(showInputToken, flags, resultReceiver);
+            mTarget.showSoftInput(showInputToken, statsToken, flags, resultReceiver);
         } catch (RemoteException e) {
             logRemoteException(e);
             return false;
@@ -210,9 +211,10 @@
 
     // TODO(b/192412909): Convert this back to void method
     @AnyThread
-    boolean hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
+    boolean hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, int flags,
+            ResultReceiver resultReceiver) {
         try {
-            mTarget.hideSoftInput(hideInputToken, flags, resultReceiver);
+            mTarget.hideSoftInput(hideInputToken, statsToken, flags, resultReceiver);
         } catch (RemoteException e) {
             logRemoteException(e);
             return false;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 6dbb362..079234c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -42,12 +42,15 @@
 import android.view.inputmethod.InputMethodInfo;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.IInputMethod;
 import com.android.internal.inputmethod.InputBindResult;
 import com.android.internal.inputmethod.UnbindReason;
 import com.android.server.EventLogTags;
 import com.android.server.wm.WindowManagerInternal;
 
+import java.util.concurrent.CountDownLatch;
+
 /**
  * A controller managing the state of the input method binding.
  */
@@ -77,19 +80,26 @@
     @GuardedBy("ImfLock.class") private boolean mVisibleBound;
     @GuardedBy("ImfLock.class") private boolean mSupportsStylusHw;
 
+    @Nullable private CountDownLatch mLatchForTesting;
+
     /**
      * Binding flags for establishing connection to the {@link InputMethodService}.
      */
-    private static final int IME_CONNECTION_BIND_FLAGS =
+    @VisibleForTesting
+    static final int IME_CONNECTION_BIND_FLAGS =
             Context.BIND_AUTO_CREATE
                     | Context.BIND_NOT_VISIBLE
                     | Context.BIND_NOT_FOREGROUND
                     | Context.BIND_IMPORTANT_BACKGROUND
                     | Context.BIND_SCHEDULE_LIKE_TOP_APP;
+
+    private final int mImeConnectionBindFlags;
+
     /**
      * Binding flags used only while the {@link InputMethodService} is showing window.
      */
-    private static final int IME_VISIBLE_BIND_FLAGS =
+    @VisibleForTesting
+    static final int IME_VISIBLE_BIND_FLAGS =
             Context.BIND_AUTO_CREATE
                     | Context.BIND_TREAT_LIKE_ACTIVITY
                     | Context.BIND_FOREGROUND_SERVICE
@@ -97,12 +107,19 @@
                     | Context.BIND_SHOWING_UI;
 
     InputMethodBindingController(@NonNull InputMethodManagerService service) {
+        this(service, IME_CONNECTION_BIND_FLAGS, null /* latchForTesting */);
+    }
+
+    InputMethodBindingController(@NonNull InputMethodManagerService service,
+            int imeConnectionBindFlags, CountDownLatch latchForTesting) {
         mService = service;
         mContext = mService.mContext;
         mMethodMap = mService.mMethodMap;
         mSettings = mService.mSettings;
         mPackageManagerInternal = mService.mPackageManagerInternal;
         mWindowManagerInternal = mService.mWindowManagerInternal;
+        mImeConnectionBindFlags = imeConnectionBindFlags;
+        mLatchForTesting = latchForTesting;
     }
 
     /**
@@ -242,7 +259,7 @@
         @Override public void onBindingDied(ComponentName name) {
             synchronized (ImfLock.class) {
                 mService.invalidateAutofillSessionLocked();
-                if (mVisibleBound) {
+                if (isVisibleBound()) {
                     unbindVisibleConnection();
                 }
             }
@@ -279,7 +296,7 @@
                     if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                     final InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
                     mSupportsStylusHw = info.supportsStylusHandwriting();
-                    mService.initializeImeLocked(mCurMethod, mCurToken, info.getConfigChanges());
+                    mService.initializeImeLocked(mCurMethod, mCurToken);
                     mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
                     mService.reRequestCurrentClientSessionLocked();
                     mService.performOnCreateInlineSuggestionsRequestLocked();
@@ -291,6 +308,10 @@
                 mService.scheduleResetStylusHandwriting();
             }
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
+            if (mLatchForTesting != null) {
+                mLatchForTesting.countDown(); // Notify the finish to tests
+            }
         }
 
         @GuardedBy("ImfLock.class")
@@ -338,15 +359,15 @@
 
     @GuardedBy("ImfLock.class")
     void unbindCurrentMethod() {
-        if (mVisibleBound) {
+        if (isVisibleBound()) {
             unbindVisibleConnection();
         }
 
-        if (mHasConnection) {
+        if (hasConnection()) {
             unbindMainConnection();
         }
 
-        if (mCurToken != null) {
+        if (getCurToken() != null) {
             removeCurrentToken();
             mService.resetSystemUiLocked();
         }
@@ -448,17 +469,17 @@
 
     @GuardedBy("ImfLock.class")
     private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) {
-        if (mCurIntent == null || conn == null) {
+        if (getCurIntent() == null || conn == null) {
             Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
             return false;
         }
-        return mContext.bindServiceAsUser(mCurIntent, conn, flags,
+        return mContext.bindServiceAsUser(getCurIntent(), conn, flags,
                 new UserHandle(mSettings.getCurrentUserId()));
     }
 
     @GuardedBy("ImfLock.class")
     private boolean bindCurrentInputMethodServiceMainConnection() {
-        mHasConnection = bindCurrentInputMethodService(mMainConnection, IME_CONNECTION_BIND_FLAGS);
+        mHasConnection = bindCurrentInputMethodService(mMainConnection, mImeConnectionBindFlags);
         return mHasConnection;
     }
 
@@ -472,7 +493,7 @@
     void setCurrentMethodVisible() {
         if (mCurMethod != null) {
             if (DEBUG) Slog.d(TAG, "setCurrentMethodVisible: mCurToken=" + mCurToken);
-            if (mHasConnection && !mVisibleBound) {
+            if (hasConnection() && !isVisibleBound()) {
                 mVisibleBound = bindCurrentInputMethodService(mVisibleConnection,
                         IME_VISIBLE_BIND_FLAGS);
             }
@@ -480,7 +501,7 @@
         }
 
         // No IME is currently connected. Reestablish the main connection.
-        if (!mHasConnection) {
+        if (!hasConnection()) {
             if (DEBUG) {
                 Slog.d(TAG, "Cannot show input: no IME bound. Rebinding.");
             }
@@ -512,7 +533,7 @@
      */
     @GuardedBy("ImfLock.class")
     void setCurrentMethodNotVisible() {
-        if (mVisibleBound) {
+        if (isVisibleBound()) {
             unbindVisibleConnection();
         }
     }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 9cb8f43..080d582 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -129,6 +129,7 @@
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethod;
@@ -144,12 +145,14 @@
 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;
 import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
 import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.IInputContentUriToken;
+import com.android.internal.inputmethod.IInputMethod;
 import com.android.internal.inputmethod.IInputMethodClient;
 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
 import com.android.internal.inputmethod.IInputMethodSession;
@@ -161,6 +164,7 @@
 import com.android.internal.inputmethod.InputBindResult;
 import com.android.internal.inputmethod.InputMethodDebug;
 import com.android.internal.inputmethod.InputMethodNavButtonFlags;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.inputmethod.StartInputFlags;
 import com.android.internal.inputmethod.StartInputReason;
@@ -640,6 +644,10 @@
      */
     private boolean mInputShown;
 
+    /** The token tracking the current IME request or {@code null} otherwise. */
+    @Nullable
+    private ImeTracker.Token mCurStatsToken;
+
     /**
      * {@code true} if the current input method is in fullscreen mode.
      */
@@ -759,7 +767,7 @@
      * <dd>
      *   If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
      * </dd>
-     * dt>{@link InputMethodService#IME_INVISIBLE}</dt>
+     * <dt>{@link InputMethodService#IME_INVISIBLE}</dt>
      * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
      *    currently invisible.
      * </dd>
@@ -783,8 +791,7 @@
 
     /**
      * Internal state snapshot when
-     * {@link com.android.internal.view.IInputMethod#startInput(IBinder, IRemoteInputConnection, EditorInfo,
-     * boolean)} is about to be called.
+     * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
      *
      * <p>Calling that IPC endpoint basically means that
      * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
@@ -1069,7 +1076,7 @@
 
         /**
          * Add a new entry and discard the oldest entry as needed.
-         * @param info {@lin StartInputInfo} to be added.
+         * @param info {@link StartInputInfo} to be added.
          */
         void addEntry(@NonNull StartInputInfo info) {
             final int index = mNextIndex;
@@ -1187,18 +1194,18 @@
                 } else if (accessibilityRequestingNoImeUri.equals(uri)) {
                     final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser(
                             mContext.getContentResolver(),
-                            Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, mUserId);
+                            Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0 /* def */, mUserId);
                     mAccessibilityRequestingNoSoftKeyboard =
                             (accessibilitySoftKeyboardSetting & AccessibilityService.SHOW_MODE_MASK)
                                     == AccessibilityService.SHOW_MODE_HIDDEN;
                     if (mAccessibilityRequestingNoSoftKeyboard) {
                         final boolean showRequested = mShowRequested;
-                        hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+                                0 /* flags */, null /* resultReceiver */,
                                 SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE);
                         mShowRequested = showRequested;
                     } else if (mShowRequested) {
-                        showCurrentInputLocked(mCurFocusedWindow,
-                                InputMethodManager.SHOW_IMPLICIT, null,
+                        showCurrentInputImplicitLocked(mCurFocusedWindow,
                                 SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE);
                     }
                 } else {
@@ -1592,8 +1599,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
@@ -1659,8 +1671,8 @@
         }
         // Hide soft input before user switch task since switch task may block main handler a while
         // and delayed the hideCurrentInputLocked().
-        hideCurrentInputLocked(
-                mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_SWITCH_USER);
+        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                null /* resultReceiver */, SoftInputShowHideReason.HIDE_SWITCH_USER);
         final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
                 clientToBeReset);
         mUserSwitchHandlerTask = task;
@@ -1668,12 +1680,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 +1726,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);
@@ -2199,7 +2227,7 @@
             }
             final ClientDeathRecipient deathRecipient = new ClientDeathRecipient(this, client);
             try {
-                client.asBinder().linkToDeath(deathRecipient, 0);
+                client.asBinder().linkToDeath(deathRecipient, 0 /* flags */);
             } catch (RemoteException e) {
                 throw new IllegalStateException(e);
             }
@@ -2224,7 +2252,7 @@
         synchronized (ImfLock.class) {
             ClientState cs = mClients.remove(client.asBinder());
             if (cs != null) {
-                client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0);
+                client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */);
                 clearClientSessionLocked(cs);
                 clearClientSessionForAccessibilityLocked(cs);
 
@@ -2237,8 +2265,8 @@
                 }
 
                 if (mCurClient == cs) {
-                    hideCurrentInputLocked(
-                            mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
+                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                            null /* resultReceiver */, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
                     if (mBoundToMethod) {
                         mBoundToMethod = false;
                         IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -2285,6 +2313,8 @@
             mCurClient.mSessionRequestedForAccessibility = false;
             mCurClient = null;
             mCurVirtualDisplayToScreenMatrix = null;
+            ImeTracker.get().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+            mCurStatsToken = null;
 
             mMenuController.hideInputMethodMenuLocked();
         }
@@ -2357,8 +2387,11 @@
                 navButtonFlags, mCurImeDispatcher);
         if (mShowRequested) {
             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
-            showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null,
-                    SoftInputShowHideReason.ATTACH_NEW_INPUT);
+            // Re-use current statsToken, if it exists.
+            final ImeTracker.Token statsToken = mCurStatsToken;
+            mCurStatsToken = null;
+            showCurrentInputLocked(mCurFocusedWindow, statsToken, getAppShowFlagsLocked(),
+                    null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
         }
 
         String curId = getCurIdLocked();
@@ -2477,7 +2510,8 @@
 
         if (mDisplayIdToShowIme == INVALID_DISPLAY) {
             mImeHiddenByDisplayPolicy = true;
-            hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+            hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                    null /* resultReceiver */,
                     SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
             return InputBindResult.NO_IME;
         }
@@ -2658,14 +2692,13 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
-            @android.content.pm.ActivityInfo.Config int configChanges) {
+    void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token) {
         if (DEBUG) {
             Slog.v(TAG, "Sending attach of token: " + token + " for display: "
                     + mCurTokenDisplayId);
         }
         inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
-                configChanges, getInputMethodNavButtonFlagsLocked());
+                getInputMethodNavButtonFlagsLocked());
     }
 
     @AnyThread
@@ -3179,16 +3212,30 @@
     }
 
     @GuardedBy("ImfLock.class")
+    private void notifyInputMethodSubtypeChangedLocked(@UserIdInt int userId,
+            @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
+        final InputMethodSubtype normalizedSubtype =
+                subtype != null && subtype.isSuitableForPhysicalKeyboardLayoutMapping()
+                        ? subtype : null;
+        final InputMethodSubtypeHandle newSubtypeHandle = normalizedSubtype != null
+                ? InputMethodSubtypeHandle.of(imi, normalizedSubtype) : null;
+        mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping(
+                userId, newSubtypeHandle, normalizedSubtype);
+    }
+
+    @GuardedBy("ImfLock.class")
     void setInputMethodLocked(String id, int subtypeId) {
         InputMethodInfo info = mMethodMap.get(id);
         if (info == null) {
-            throw new IllegalArgumentException("Unknown id: " + id);
+            throw getExceptionForUnknownImeId(id);
         }
 
         // See if we need to notify a subtype change within the same IME.
         if (id.equals(getSelectedMethodIdLocked())) {
+            final int userId = mSettings.getCurrentUserId();
             final int subtypeCount = info.getSubtypeCount();
             if (subtypeCount <= 0) {
+                notifyInputMethodSubtypeChangedLocked(userId, info, null);
                 return;
             }
             final InputMethodSubtype oldSubtype = mCurrentSubtype;
@@ -3203,6 +3250,7 @@
             if (newSubtype == null || oldSubtype == null) {
                 Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
                         + ", new subtype = " + newSubtype);
+                notifyInputMethodSubtypeChangedLocked(userId, info, null);
                 return;
             }
             if (newSubtype != oldSubtype) {
@@ -3240,22 +3288,23 @@
     }
 
     @Override
-    public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, int flags,
-            int lastClickTooType, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
+    public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+            @Nullable ImeTracker.Token statsToken, int flags, int lastClickTooType,
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
         int uid = Binder.getCallingUid();
         ImeTracing.getInstance().triggerManagerServiceDump(
                 "InputMethodManagerService#showSoftInput");
         synchronized (ImfLock.class) {
-            if (!canInteractWithImeLocked(uid, client, "showSoftInput")) {
+            if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken)) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
                 return false;
             }
             final long ident = Binder.clearCallingIdentity();
             try {
                 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
-                return showCurrentInputLocked(
-                        windowToken, flags, lastClickTooType, resultReceiver, reason);
+                return showCurrentInputLocked(windowToken, statsToken, flags, lastClickTooType,
+                        resultReceiver, reason);
             } finally {
                 Binder.restoreCallingIdentity(ident);
                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -3272,7 +3321,8 @@
                     "InputMethodManagerService#startStylusHandwriting");
             int uid = Binder.getCallingUid();
             synchronized (ImfLock.class) {
-                if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting")) {
+                if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting",
+                        null /* statsToken */)) {
                     return;
                 }
                 if (!hasSupportedStylusLocked()) {
@@ -3327,19 +3377,33 @@
     }
 
     @GuardedBy("ImfLock.class")
-    boolean showCurrentInputLocked(IBinder windowToken, int flags,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
-        return showCurrentInputLocked(
-                windowToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
+    boolean showCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+            int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        return showCurrentInputLocked(windowToken, statsToken, flags,
+                MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
     }
 
     @GuardedBy("ImfLock.class")
-    private boolean showCurrentInputLocked(IBinder windowToken, int flags, int lastClickToolType,
+    private boolean showCurrentInputLocked(IBinder windowToken,
+            @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        // Create statsToken is none exists.
+        if (statsToken == null) {
+            String packageName = null;
+            if (mCurEditorInfo != null) {
+                packageName = mCurEditorInfo.packageName;
+            }
+            statsToken = new ImeTracker.Token(packageName);
+            ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_SERVER_START_INPUT,
+                    reason);
+        }
+
         mShowRequested = true;
         if (mAccessibilityRequestingNoSoftKeyboard || mImeHiddenByDisplayPolicy) {
+            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
             return false;
         }
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
 
         if ((flags & InputMethodManager.SHOW_FORCED) != 0) {
             mShowExplicitlyRequested = true;
@@ -3349,8 +3413,10 @@
         }
 
         if (!mSystemReady) {
+            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
             return false;
         }
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
 
         mBindingController.setCurrentMethodVisible();
         final IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -3358,6 +3424,9 @@
             // create a placeholder token for IMS so that IMS cannot inject windows into client app.
             Binder showInputToken = new Binder();
             mShowRequestWindowMap.put(showInputToken, windowToken);
+            ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
+            mCurStatsToken = null;
             final int showFlags = getImeShowFlagsLocked();
             if (DEBUG) {
                 Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
@@ -3369,23 +3438,34 @@
                 curMethod.updateEditorToolType(lastClickToolType);
             }
             // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
-            if (curMethod.showSoftInput(showInputToken, showFlags, resultReceiver)) {
+            if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) {
                 onShowHideSoftInputRequested(true /* show */, windowToken, reason);
             }
             mInputShown = true;
             return true;
+        } else {
+            ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+            mCurStatsToken = statsToken;
         }
         return false;
     }
 
     @Override
-    public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, int flags,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+    public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+            @Nullable ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         int uid = Binder.getCallingUid();
         ImeTracing.getInstance().triggerManagerServiceDump(
                 "InputMethodManagerService#hideSoftInput");
         synchronized (ImfLock.class) {
-            if (!canInteractWithImeLocked(uid, client, "hideSoftInput")) {
+            if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
+                if (mInputShown) {
+                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+                } else {
+                    ImeTracker.get().onCancelled(statsToken,
+                            ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+                }
                 return false;
             }
             final long ident = Binder.clearCallingIdentity();
@@ -3393,7 +3473,7 @@
                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput");
                 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
                 return InputMethodManagerService.this.hideCurrentInputLocked(windowToken,
-                        flags, resultReceiver, reason);
+                        statsToken, flags, resultReceiver, reason);
             } finally {
                 Binder.restoreCallingIdentity(ident);
                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -3402,17 +3482,32 @@
     }
 
     @GuardedBy("ImfLock.class")
-    boolean hideCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
+    boolean hideCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+            int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        // Create statsToken is none exists.
+        if (statsToken == null) {
+            String packageName = null;
+            if (mCurEditorInfo != null) {
+                packageName = mCurEditorInfo.packageName;
+            }
+            statsToken = new ImeTracker.Token(packageName);
+            ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
+        }
+
         if ((flags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
                 && (mShowExplicitlyRequested || mShowForced)) {
             if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
+            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
             return false;
         }
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
+
         if (mShowForced && (flags & InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
             if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
+            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
             return false;
         }
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
 
         // There is a chance that IMM#hideSoftInput() is called in a transient state where
         // IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting
@@ -3423,8 +3518,8 @@
         // IMMS#InputShown indicates that the software keyboard is shown.
         // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
         IInputMethodInvoker curMethod = getCurMethodLocked();
-        final boolean shouldHideSoftInput = (curMethod != null) && (mInputShown
-                || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
+        final boolean shouldHideSoftInput = (curMethod != null)
+                && (mInputShown || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
         boolean res;
         if (shouldHideSoftInput) {
             final Binder hideInputToken = new Binder();
@@ -3433,17 +3528,20 @@
             // delivered to the IME process as an IPC.  Hence the inconsistency between
             // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
             // the final state.
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
             if (DEBUG) {
                 Slog.v(TAG, "Calling " + curMethod + ".hideSoftInput(0, " + hideInputToken
                         + ", " + resultReceiver + ") for reason: "
                         + InputMethodDebug.softInputDisplayReasonToString(reason));
             }
             // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
-            if (curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver)) {
+            if (curMethod.hideSoftInput(hideInputToken, statsToken, 0 /* flags */,
+                    resultReceiver)) {
                 onShowHideSoftInputRequested(false /* show */, windowToken, reason);
             }
             res = true;
         } else {
+            ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
             res = false;
         }
         mBindingController.setCurrentMethodNotVisible();
@@ -3451,6 +3549,9 @@
         mShowRequested = false;
         mShowExplicitlyRequested = false;
         mShowForced = false;
+        // Cancel existing statsToken for show IME as we got a hide request.
+        ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+        mCurStatsToken = null;
         return res;
     }
 
@@ -3608,8 +3709,8 @@
             Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
                     + " a background user, use EditorInfo.targetInputMethodUser with"
                     + " INTERACT_ACROSS_USERS_FULL permission.");
-            hideCurrentInputLocked(
-                    mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_INVALID_USER);
+            hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                    null /* resultReceiver */, SoftInputShowHideReason.HIDE_INVALID_USER);
             return InputBindResult.INVALID_USER;
         }
 
@@ -3665,7 +3766,7 @@
         boolean didStart = false;
 
         InputBindResult res = null;
-        // We shows the IME when the system allows the IME focused target window to restore the
+        // We show the IME when the system allows the IME focused target window to restore the
         // IME visibility (e.g. switching to the app task when last time the IME is visible).
         // Note that we don't restore IME visibility for some cases (e.g. when the soft input
         // state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation).
@@ -3673,10 +3774,11 @@
         // 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);
-            showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+            showCurrentInputImplicitLocked(windowToken,
                     SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY);
             return res;
         }
@@ -3689,8 +3791,8 @@
                         // be behind any soft input window, so hide the
                         // soft input window if it is shown.
                         if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
-                        hideCurrentInputLocked(
-                                mCurFocusedWindow, InputMethodManager.HIDE_NOT_ALWAYS, null,
+                        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+                                InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */,
                                 SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);
 
                         // If focused display changed, we should unbind current method
@@ -3719,24 +3821,29 @@
                                 imeDispatcher);
                         didStart = true;
                     }
-                    showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+                    showCurrentInputImplicitLocked(windowToken,
                             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:
                 if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
                     if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
-                    hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                            null /* resultReceiver */,
                             SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV);
                 }
                 break;
             case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
                 if (!sameWindowFocused) {
                     if (DEBUG) Slog.v(TAG, "Window asks to hide input");
-                    hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                            null /* resultReceiver */,
                             SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
                 }
                 break;
@@ -3752,7 +3859,7 @@
                                     imeDispatcher);
                             didStart = true;
                         }
-                        showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+                        showCurrentInputImplicitLocked(windowToken,
                                 SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV);
                     } else {
                         Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
@@ -3773,7 +3880,7 @@
                                     imeDispatcher);
                             didStart = true;
                         }
-                        showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+                        showCurrentInputImplicitLocked(windowToken,
                                 SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE);
                     }
                 } else {
@@ -3794,7 +3901,9 @@
                     // To maintain compatibility, we are now hiding the IME when we don't have
                     // an editor upon refocusing a window.
                     if (startInputByWinGainedFocus) {
-                        hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                        if (DEBUG) Slog.v(TAG, "Same window without editor will hide input");
+                        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+                                0 /* flags */, null /* resultReceiver */,
                                 SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
                     }
                 }
@@ -3807,7 +3916,9 @@
                     // 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
-                    hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                    if (DEBUG) Slog.v(TAG, "Window without editor will hide input");
+                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                            null /* resultReceiver */,
                             SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR);
                 }
                 res = startInputUncheckedLocked(cs, inputContext,
@@ -3822,8 +3933,15 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private boolean canInteractWithImeLocked(
-            int uid, IInputMethodClient client, String methodName) {
+    private void showCurrentInputImplicitLocked(@NonNull IBinder windowToken,
+            @SoftInputShowHideReason int reason) {
+        showCurrentInputLocked(windowToken, null /* statsToken */, InputMethodManager.SHOW_IMPLICIT,
+                null /* resultReceiver */, reason);
+    }
+
+    @GuardedBy("ImfLock.class")
+    private boolean canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName,
+            @Nullable ImeTracker.Token statsToken) {
         if (mCurClient == null || client == null
                 || mCurClient.mClient.asBinder() != client.asBinder()) {
             // We need to check if this is the current client with
@@ -3831,13 +3949,16 @@
             // be made before input is started in it.
             final ClientState cs = mClients.get(client.asBinder());
             if (cs == null) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
                 throw new IllegalArgumentException("unknown client " + client.asBinder());
             }
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
             if (!isImeClientFocused(mCurFocusedWindow, cs)) {
                 Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
                 return false;
             }
         }
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
         return true;
     }
 
@@ -3894,10 +4015,11 @@
 
     @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();
+
         mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
                 .sendToTarget();
     }
@@ -3907,17 +4029,32 @@
      */
     @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
     public boolean isInputMethodPickerShownForTest() {
+        super.isInputMethodPickerShownForTest_enforcePermission();
+
         synchronized (ImfLock.class) {
             return mMenuController.isisInputMethodPickerShownForTestLocked();
         }
     }
 
+    @NonNull
+    private static IllegalArgumentException getExceptionForUnknownImeId(
+            @Nullable String imeId) {
+        return new IllegalArgumentException("Unknown id: " + imeId);
+    }
+
     @BinderThread
     private void setInputMethod(@NonNull IBinder token, String id) {
+        final int callingUid = Binder.getCallingUid();
+        final int userId = UserHandle.getUserId(callingUid);
         synchronized (ImfLock.class) {
             if (!calledWithValidTokenLocked(token)) {
                 return;
             }
+            final InputMethodInfo imi = mMethodMap.get(id);
+            if (imi == null || !canCallerAccessInputMethod(
+                    imi.getPackageName(), callingUid, userId, mSettings)) {
+                throw getExceptionForUnknownImeId(id);
+            }
             setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID);
         }
     }
@@ -3925,14 +4062,20 @@
     @BinderThread
     private void setInputMethodAndSubtype(@NonNull IBinder token, String id,
             InputMethodSubtype subtype) {
+        final int callingUid = Binder.getCallingUid();
+        final int userId = UserHandle.getUserId(callingUid);
         synchronized (ImfLock.class) {
             if (!calledWithValidTokenLocked(token)) {
                 return;
             }
+            final InputMethodInfo imi = mMethodMap.get(id);
+            if (imi == null || !canCallerAccessInputMethod(
+                    imi.getPackageName(), callingUid, userId, mSettings)) {
+                throw getExceptionForUnknownImeId(id);
+            }
             if (subtype != null) {
                 setInputMethodWithSubtypeIdLocked(token, id,
-                        SubtypeUtils.getSubtypeIdFromHashCode(mMethodMap.get(id),
-                                subtype.hashCode()));
+                        SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()));
             } else {
                 setInputMethod(token, id);
             }
@@ -4168,7 +4311,7 @@
             final int curTokenDisplayId;
             synchronized (ImfLock.class) {
                 if (!canInteractWithImeLocked(callingUid, client,
-                        "getInputMethodWindowVisibleHeight")) {
+                        "getInputMethodWindowVisibleHeight", null /* statsToken */)) {
                     if (!mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.get(callingUid)) {
                         EventLog.writeEvent(0x534e4554, "204906124", callingUid, "");
                         mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.put(callingUid, true);
@@ -4186,6 +4329,8 @@
     @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
     @Override
     public void removeImeSurface() {
+        super.removeImeSurface_enforcePermission();
+
         mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
     }
 
@@ -4382,12 +4527,15 @@
      * 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();
+
         int uid = Binder.getCallingUid();
         synchronized (ImfLock.class) {
-            if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession")) {
+            if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession",
+                    null /* statsToken */)) {
                 return;
             }
             final long ident = Binder.clearCallingIdentity();
@@ -4410,9 +4558,12 @@
     @Override
     public void setStylusWindowIdleTimeoutForTest(
             IInputMethodClient client, @DurationMillisLong long timeout) {
+        super.setStylusWindowIdleTimeoutForTest_enforcePermission();
+
         int uid = Binder.getCallingUid();
         synchronized (ImfLock.class) {
-            if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest")) {
+            if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest",
+                    null /* statsToken */)) {
                 return;
             }
             final long ident = Binder.clearCallingIdentity();
@@ -4507,6 +4658,8 @@
     @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
     @Override
     public void startImeTrace() {
+        super.startImeTrace_enforcePermission();
+
         ImeTracing.getInstance().startTrace(null /* printwriter */);
         ArrayMap<IBinder, ClientState> clients;
         synchronized (ImfLock.class) {
@@ -4523,6 +4676,8 @@
     @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
     @Override
     public void stopImeTrace() {
+        super.stopImeTrace_enforcePermission();
+
         ImeTracing.getInstance().stopTrace(null /* printwriter */);
         ArrayMap<IBinder, ClientState> clients;
         synchronized (ImfLock.class) {
@@ -4593,7 +4748,8 @@
     }
 
     @BinderThread
-    private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible) {
+    private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible,
+            @Nullable ImeTracker.Token statsToken) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
         synchronized (ImfLock.class) {
             if (!calledWithValidTokenLocked(token)) {
@@ -4601,13 +4757,22 @@
             }
             if (!setVisible) {
                 if (mCurClient != null) {
+                    ImeTracker.get().onProgress(statsToken,
+                            ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
+
                     mWindowManagerInternal.hideIme(
                             mHideRequestWindowMap.get(windowToken),
-                            mCurClient.mSelfReportedDisplayId);
+                            mCurClient.mSelfReportedDisplayId, statsToken);
+                } else {
+                    ImeTracker.get().onFailed(statsToken,
+                            ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                 }
             } else {
+                ImeTracker.get().onProgress(statsToken,
+                        ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                 // Send to window manager to show IME after IME layout finishes.
-                mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken));
+                mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken),
+                        statsToken);
             }
         }
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -4674,7 +4839,8 @@
             }
             final long ident = Binder.clearCallingIdentity();
             try {
-                hideCurrentInputLocked(mLastImeTargetWindow, flags, null, reason);
+                hideCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags,
+                        null /* resultReceiver */, reason);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -4691,7 +4857,8 @@
             }
             final long ident = Binder.clearCallingIdentity();
             try {
-                showCurrentInputLocked(mLastImeTargetWindow, flags, null,
+                showCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags,
+                        null /* resultReceiver */,
                         SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME);
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -4782,7 +4949,8 @@
             case MSG_HIDE_CURRENT_INPUT_METHOD:
                 synchronized (ImfLock.class) {
                     final @SoftInputShowHideReason int reason = (int) msg.obj;
-                    hideCurrentInputLocked(mCurFocusedWindow, 0, null, reason);
+                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                            null /* resultReceiver */, reason);
 
                 }
                 return true;
@@ -5234,6 +5402,7 @@
                 mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
             }
         }
+        notifyInputMethodSubtypeChangedLocked(mSettings.getCurrentUserId(), imi, mCurrentSubtype);
 
         if (!setSubtypeOnly) {
             // Set InputMethod here
@@ -6269,7 +6438,8 @@
                     final String nextIme;
                     final List<InputMethodInfo> nextEnabledImes;
                     if (userId == mSettings.getCurrentUserId()) {
-                        hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+                                0 /* flags */, null /* resultReceiver */,
                                 SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
                         mBindingController.unbindCurrentMethod();
                         // Reset the current IME
@@ -6534,8 +6704,9 @@
 
         @BinderThread
         @Override
-        public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible) {
-            mImms.applyImeVisibility(mToken, windowToken, setVisible);
+        public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible,
+                @Nullable ImeTracker.Token statsToken) {
+            mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken);
         }
 
         @BinderThread
diff --git a/services/core/java/com/android/server/locales/AppUpdateTracker.java b/services/core/java/com/android/server/locales/AppUpdateTracker.java
new file mode 100644
index 0000000..3474f1e
--- /dev/null
+++ b/services/core/java/com/android/server/locales/AppUpdateTracker.java
@@ -0,0 +1,180 @@
+/*
+ * 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.locales;
+
+import android.app.LocaleConfig;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.LocaleList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.FeatureFlagUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Track when a app is being updated.
+ */
+public class AppUpdateTracker {
+    private static final String TAG = "AppUpdateTracker";
+
+    private final Context mContext;
+    private final LocaleManagerService mLocaleManagerService;
+    private final LocaleManagerBackupHelper mBackupHelper;
+
+    AppUpdateTracker(Context context, LocaleManagerService localeManagerService,
+            LocaleManagerBackupHelper backupHelper) {
+        mContext = context;
+        mLocaleManagerService = localeManagerService;
+        mBackupHelper = backupHelper;
+    }
+
+    /**
+     * <p><b>Note:</b> This is invoked by service's common monitor
+     * {@link LocaleManagerServicePackageMonitor#onPackageUpdateFinished} when a package is upgraded
+     * on device.
+     */
+    public void onPackageUpdateFinished(String packageName, int uid) {
+        Log.d(TAG, "onPackageUpdateFinished " + packageName);
+        int userId = UserHandle.getUserId(uid);
+        cleanApplicationLocalesIfNeeded(packageName, userId);
+    }
+
+    /**
+     * When the user has set per-app locales for a specific application from a delegate selector,
+     * and then the LocaleConfig of that application is removed in the upgraded version, the per-app
+     * locales needs to be reset to system default locales to avoid the user being unable to change
+     * system locales setting.
+     */
+    private void cleanApplicationLocalesIfNeeded(String packageName, int userId) {
+        Set<String> packageNames = new ArraySet<>();
+        SharedPreferences delegateAppLocalePackages = mBackupHelper.getPersistedInfo();
+        if (delegateAppLocalePackages != null) {
+            packageNames = delegateAppLocalePackages.getStringSet(Integer.toString(userId),
+                    new ArraySet<>());
+        }
+
+        try {
+            LocaleList appLocales = mLocaleManagerService.getApplicationLocales(packageName,
+                    userId);
+            if (appLocales.isEmpty() || isLocalesExistedInLocaleConfig(appLocales, packageName,
+                    userId) || !packageNames.contains(packageName)) {
+                return;
+            }
+        } catch (RemoteException | IllegalArgumentException e) {
+            Slog.e(TAG, "Exception when getting locales for " + packageName, e);
+            return;
+        }
+
+        Slog.d(TAG, "Clear app locales for " + packageName);
+        try {
+            mLocaleManagerService.setApplicationLocales(packageName, userId,
+                    LocaleList.forLanguageTags(""), false);
+        } catch (RemoteException | IllegalArgumentException e) {
+            Slog.e(TAG, "Could not clear locales for " + packageName, e);
+        }
+    }
+
+    /**
+     * Check whether the LocaleConfig is existed and the per-app locales is presented in the
+     * LocaleConfig file after the application is upgraded.
+     */
+    private boolean isLocalesExistedInLocaleConfig(LocaleList appLocales, String packageName,
+            int userId) {
+        LocaleList packageLocalesList = getPackageLocales(packageName, userId);
+        HashSet<Locale> packageLocales = new HashSet<>();
+
+        if (isSettingsAppLocalesOptIn()) {
+            if (packageLocalesList == null || packageLocalesList.isEmpty()) {
+                // The app locale feature is not enabled by the app
+                Slog.d(TAG, "opt-in: the app locale feature is not enabled");
+                return false;
+            }
+        } else {
+            if (packageLocalesList != null && packageLocalesList.isEmpty()) {
+                // The app locale feature is not enabled by the app
+                Slog.d(TAG, "opt-out: the app locale feature is not enabled");
+                return false;
+            }
+        }
+
+        if (packageLocalesList != null && !packageLocalesList.isEmpty()) {
+            // The app has added the supported locales into the LocaleConfig
+            for (int i = 0; i < packageLocalesList.size(); i++) {
+                packageLocales.add(packageLocalesList.get(i));
+            }
+            if (!matchesLocale(packageLocales, appLocales)) {
+                // The set app locales do not match with the list of app supported locales
+                Slog.d(TAG, "App locales: " + appLocales.toLanguageTags()
+                        + " are not existed in the supported locale list");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Get locales from LocaleConfig.
+     */
+    @VisibleForTesting
+    public LocaleList getPackageLocales(String packageName, int userId) {
+        try {
+            LocaleConfig localeConfig = new LocaleConfig(
+                    mContext.createPackageContextAsUser(packageName, 0, UserHandle.of(userId)));
+            if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) {
+                return localeConfig.getSupportedLocales();
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "Can not found the package name : " + packageName + " / " + e);
+        }
+        return null;
+    }
+
+    /**
+     * Check whether the feature to show per-app locales list in Settings is enabled.
+     */
+    @VisibleForTesting
+    public boolean isSettingsAppLocalesOptIn() {
+        return FeatureFlagUtils.isEnabled(mContext,
+                FeatureFlagUtils.SETTINGS_APP_LOCALE_OPT_IN_ENABLED);
+    }
+
+    private boolean matchesLocale(HashSet<Locale> supported, LocaleList appLocales) {
+        if (supported.size() <= 0 || appLocales.size() <= 0) {
+            return true;
+        }
+
+        for (int i = 0; i < appLocales.size(); i++) {
+            final Locale appLocale = appLocales.get(i);
+            if (supported.stream().anyMatch(
+                    locale -> LocaleList.matchesLanguageAndScript(locale, appLocale))) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 67c931f..898c6f1 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -27,14 +27,17 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.SharedPreferences;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.os.Environment;
 import android.os.HandlerThread;
 import android.os.LocaleList;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
@@ -44,18 +47,20 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
-import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.StandardCharsets;
 import java.time.Clock;
 import java.time.Duration;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.Set;
 
 /**
  * Helper class for managing backup and restore of app-specific locales.
@@ -68,9 +73,14 @@
     private static final String PACKAGE_XML_TAG = "package";
     private static final String ATTR_PACKAGE_NAME = "name";
     private static final String ATTR_LOCALES = "locales";
-    private static final String ATTR_CREATION_TIME_MILLIS = "creationTimeMillis";
+    private static final String ATTR_DELEGATE_SELECTOR = "delegate_selector";
 
     private static final String SYSTEM_BACKUP_PACKAGE_KEY = "android";
+    /**
+     * The name of the xml file used to persist the target package name that sets per-app locales
+     * from the delegate selector.
+     */
+    private static final String LOCALES_FROM_DELEGATE_PREFS = "LocalesFromDelegatePrefs.xml";
     // Stage data would be deleted on reboot since it's stored in memory. So it's retained until
     // retention period OR next reboot, whichever happens earlier.
     private static final Duration STAGE_DATA_RETENTION_PERIOD = Duration.ofDays(3);
@@ -85,23 +95,28 @@
     // SparseArray because it is more memory-efficient than a HashMap.
     private final SparseArray<StagedData> mStagedData;
 
+    // SharedPreferences to store packages whose app-locale was set by a delegate, as opposed to
+    // the application setting the app-locale itself.
+    private final SharedPreferences mDelegateAppLocalePackages;
     private final BroadcastReceiver mUserMonitor;
 
     LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
             PackageManager packageManager, HandlerThread broadcastHandlerThread) {
         this(localeManagerService.mContext, localeManagerService, packageManager, Clock.systemUTC(),
-                new SparseArray<>(), broadcastHandlerThread);
+                new SparseArray<>(), broadcastHandlerThread, null);
     }
 
     @VisibleForTesting LocaleManagerBackupHelper(Context context,
             LocaleManagerService localeManagerService,
             PackageManager packageManager, Clock clock, SparseArray<StagedData> stagedData,
-            HandlerThread broadcastHandlerThread) {
+            HandlerThread broadcastHandlerThread, SharedPreferences delegateAppLocalePackages) {
         mContext = context;
         mLocaleManagerService = localeManagerService;
         mPackageManager = packageManager;
         mClock = clock;
         mStagedData = stagedData;
+        mDelegateAppLocalePackages = delegateAppLocalePackages != null ? delegateAppLocalePackages
+                : createPersistedInfo();
 
         mUserMonitor = new UserMonitor();
         IntentFilter filter = new IntentFilter();
@@ -127,20 +142,29 @@
             cleanStagedDataForOldEntriesLocked();
         }
 
-        HashMap<String, String> pkgStates = new HashMap<>();
+        HashMap<String, LocalesInfo> pkgStates = new HashMap<>();
         for (ApplicationInfo appInfo : mPackageManager.getInstalledApplicationsAsUser(
                 PackageManager.ApplicationInfoFlags.of(0), userId)) {
             try {
                 LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
                         appInfo.packageName,
                         userId);
-                // Backup locales only for apps which do have app-specific overrides.
+                // Backup locales and package names for per-app locales set from a delegate
+                // selector only for apps which do have app-specific overrides.
                 if (!appLocales.isEmpty()) {
                     if (DEBUG) {
                         Slog.d(TAG, "Add package=" + appInfo.packageName + " locales="
                                 + appLocales.toLanguageTags() + " to backup payload");
                     }
-                    pkgStates.put(appInfo.packageName, appLocales.toLanguageTags());
+                    boolean localeSetFromDelegate = false;
+                    if (mDelegateAppLocalePackages != null) {
+                        localeSetFromDelegate = mDelegateAppLocalePackages.getStringSet(
+                                Integer.toString(userId), Collections.<String>emptySet()).contains(
+                                appInfo.packageName);
+                    }
+                    LocalesInfo localesInfo = new LocalesInfo(appLocales.toLanguageTags(),
+                            localeSetFromDelegate);
+                    pkgStates.put(appInfo.packageName, localesInfo);
                 }
             } catch (RemoteException | IllegalArgumentException e) {
                 Slog.e(TAG, "Exception when getting locales for package: " + appInfo.packageName,
@@ -200,7 +224,7 @@
 
         final ByteArrayInputStream inputStream = new ByteArrayInputStream(payload);
 
-        HashMap<String, String> pkgStates;
+        HashMap<String, LocalesInfo> pkgStates;
         try {
             // Parse the input blob into a list of BackupPackageState.
             final TypedXmlPullParser parser = Xml.newFastPullParser();
@@ -222,16 +246,17 @@
             StagedData stagedData = new StagedData(mClock.millis(), new HashMap<>());
 
             for (String pkgName : pkgStates.keySet()) {
-                String languageTags = pkgStates.get(pkgName);
+                LocalesInfo localesInfo = pkgStates.get(pkgName);
                 // Check if the application is already installed for the concerned user.
                 if (isPackageInstalledForUser(pkgName, userId)) {
                     // Don't apply the restore if the locales have already been set for the app.
-                    checkExistingLocalesAndApplyRestore(pkgName, languageTags, userId);
+                    checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId);
                 } else {
                     // Stage the data if the app isn't installed.
-                    stagedData.mPackageStates.put(pkgName, languageTags);
+                    stagedData.mPackageStates.put(pkgName, localesInfo);
                     if (DEBUG) {
-                        Slog.d(TAG, "Add locales=" + languageTags
+                        Slog.d(TAG, "Add locales=" + localesInfo.mLocales
+                                + " fromDelegate=" + localesInfo.mSetFromDelegate
                                 + " package=" + pkgName + " for lazy restore.");
                     }
                 }
@@ -276,9 +301,11 @@
      * {@link LocaleManagerServicePackageMonitor#onPackageDataCleared} when a package's data
      * is cleared.
      */
-    void onPackageDataCleared() {
+    void onPackageDataCleared(String packageName, int uid) {
         try {
             notifyBackupManager();
+            int userId = UserHandle.getUserId(uid);
+            removePackageFromPersistedInfo(packageName, userId);
         } catch (Exception e) {
             Slog.e(TAG, "Exception in onPackageDataCleared.", e);
         }
@@ -289,9 +316,11 @@
      * {@link LocaleManagerServicePackageMonitor#onPackageRemoved} when a package is removed
      * from device.
      */
-    void onPackageRemoved() {
+    void onPackageRemoved(String packageName, int uid) {
         try {
             notifyBackupManager();
+            int userId = UserHandle.getUserId(uid);
+            removePackageFromPersistedInfo(packageName, userId);
         } catch (Exception e) {
             Slog.e(TAG, "Exception in onPackageRemoved.", e);
         }
@@ -317,7 +346,12 @@
      * case, we want to keep the user settings and discard the restore.
      */
     private void checkExistingLocalesAndApplyRestore(@NonNull String pkgName,
-            @NonNull String languageTags, int userId) {
+            LocalesInfo localesInfo, int userId) {
+        if (localesInfo == null) {
+            Slog.w(TAG, "No locales info for " + pkgName);
+            return;
+        }
+
         try {
             LocaleList currLocales = mLocaleManagerService.getApplicationLocales(
                     pkgName,
@@ -325,16 +359,17 @@
             if (!currLocales.isEmpty()) {
                 return;
             }
-        } catch (RemoteException e) {
+        } catch (RemoteException | IllegalArgumentException e) {
             Slog.e(TAG, "Could not check for current locales before restoring", e);
         }
 
         // Restore the locale immediately
         try {
             mLocaleManagerService.setApplicationLocales(pkgName, userId,
-                    LocaleList.forLanguageTags(languageTags));
+                    LocaleList.forLanguageTags(localesInfo.mLocales), localesInfo.mSetFromDelegate);
             if (DEBUG) {
-                Slog.d(TAG, "Restored locales=" + languageTags + " for package=" + pkgName);
+                Slog.d(TAG, "Restored locales=" + localesInfo.mLocales + " fromDelegate="
+                        + localesInfo.mSetFromDelegate + " for package=" + pkgName);
             }
         } catch (RemoteException | IllegalArgumentException e) {
             Slog.e(TAG, "Could not restore locales for " + pkgName, e);
@@ -348,18 +383,21 @@
     /**
      * Parses the backup data from the serialized xml input stream.
      */
-    private @NonNull HashMap<String, String> readFromXml(XmlPullParser parser)
+    private @NonNull HashMap<String, LocalesInfo> readFromXml(TypedXmlPullParser parser)
             throws IOException, XmlPullParserException {
-        HashMap<String, String> packageStates = new HashMap<>();
+        HashMap<String, LocalesInfo> packageStates = new HashMap<>();
         int depth = parser.getDepth();
         while (XmlUtils.nextElementWithin(parser, depth)) {
             if (parser.getName().equals(PACKAGE_XML_TAG)) {
                 String packageName = parser.getAttributeValue(/* namespace= */ null,
                         ATTR_PACKAGE_NAME);
                 String languageTags = parser.getAttributeValue(/* namespace= */ null, ATTR_LOCALES);
+                boolean delegateSelector = parser.getAttributeBoolean(/* namespace= */ null,
+                        ATTR_DELEGATE_SELECTOR);
 
                 if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(languageTags)) {
-                    packageStates.put(packageName, languageTags);
+                    LocalesInfo localesInfo = new LocalesInfo(languageTags, delegateSelector);
+                    packageStates.put(packageName, localesInfo);
                 }
             }
         }
@@ -369,8 +407,8 @@
     /**
      * Converts the list of app backup data into a serialized xml stream.
      */
-    private static void writeToXml(OutputStream stream, @NonNull HashMap<String, String> pkgStates)
-            throws IOException {
+    private static void writeToXml(OutputStream stream,
+            @NonNull HashMap<String, LocalesInfo> pkgStates) throws IOException {
         if (pkgStates.isEmpty()) {
             // No need to write anything at all if pkgStates is empty.
             return;
@@ -384,7 +422,9 @@
         for (String pkg : pkgStates.keySet()) {
             out.startTag(/* namespace= */ null, PACKAGE_XML_TAG);
             out.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, pkg);
-            out.attribute(/* namespace= */ null, ATTR_LOCALES, pkgStates.get(pkg));
+            out.attribute(/* namespace= */ null, ATTR_LOCALES, pkgStates.get(pkg).mLocales);
+            out.attributeBoolean(/* namespace= */ null, ATTR_DELEGATE_SELECTOR,
+                    pkgStates.get(pkg).mSetFromDelegate);
             out.endTag(/*namespace= */ null, PACKAGE_XML_TAG);
         }
 
@@ -394,14 +434,24 @@
 
     static class StagedData {
         final long mCreationTimeMillis;
-        final HashMap<String, String> mPackageStates;
+        final HashMap<String, LocalesInfo> mPackageStates;
 
-        StagedData(long creationTimeMillis, HashMap<String, String> pkgStates) {
+        StagedData(long creationTimeMillis, HashMap<String, LocalesInfo> pkgStates) {
             mCreationTimeMillis = creationTimeMillis;
             mPackageStates = pkgStates;
         }
     }
 
+    static class LocalesInfo {
+        final String mLocales;
+        final boolean mSetFromDelegate;
+
+        LocalesInfo(String locales, boolean setFromDelegate) {
+            mLocales = locales;
+            mSetFromDelegate = setFromDelegate;
+        }
+    }
+
     /**
      * Broadcast listener to capture user removed event.
      *
@@ -416,6 +466,7 @@
                     final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                     synchronized (mStagedDataLock) {
                         deleteStagedDataLocked(userId);
+                        removeProfileFromPersistedInfo(userId);
                     }
                 }
             } catch (Exception e) {
@@ -443,11 +494,11 @@
 
         StagedData stagedData = mStagedData.get(userId);
         for (String pkgName : stagedData.mPackageStates.keySet()) {
-            String languageTags = stagedData.mPackageStates.get(pkgName);
+            LocalesInfo localesInfo = stagedData.mPackageStates.get(pkgName);
 
             if (pkgName.equals(packageName)) {
 
-                checkExistingLocalesAndApplyRestore(pkgName, languageTags, userId);
+                checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId);
 
                 // Remove the restored entry from the staged data list.
                 stagedData.mPackageStates.remove(pkgName);
@@ -463,4 +514,98 @@
             }
         }
     }
+
+    SharedPreferences createPersistedInfo() {
+        final File prefsFile = new File(
+                Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM),
+                LOCALES_FROM_DELEGATE_PREFS);
+        return mContext.createDeviceProtectedStorageContext().getSharedPreferences(prefsFile,
+                Context.MODE_PRIVATE);
+    }
+
+    public SharedPreferences getPersistedInfo() {
+        return mDelegateAppLocalePackages;
+    }
+
+    private void removePackageFromPersistedInfo(String packageName, @UserIdInt int userId) {
+        if (mDelegateAppLocalePackages == null) {
+            Slog.w(TAG, "Failed to persist data into the shared preference!");
+            return;
+        }
+
+        String key = Integer.toString(userId);
+        Set<String> packageNames = new ArraySet<>(
+                mDelegateAppLocalePackages.getStringSet(key, new ArraySet<>()));
+        if (packageNames.contains(packageName)) {
+            if (DEBUG) {
+                Slog.d(TAG, "remove " + packageName + " from persisted info");
+            }
+            packageNames.remove(packageName);
+            SharedPreferences.Editor editor = mDelegateAppLocalePackages.edit();
+            editor.putStringSet(key, packageNames);
+
+            // commit and log the result.
+            if (!editor.commit()) {
+                Slog.e(TAG, "Failed to commit data!");
+            }
+        }
+    }
+
+    private void removeProfileFromPersistedInfo(@UserIdInt int userId) {
+        String key = Integer.toString(userId);
+
+        if (mDelegateAppLocalePackages == null || !mDelegateAppLocalePackages.contains(key)) {
+            Slog.w(TAG, "The profile is not existed in the persisted info");
+            return;
+        }
+
+        if (!mDelegateAppLocalePackages.edit().remove(key).commit()) {
+            Slog.e(TAG, "Failed to commit data!");
+        }
+    }
+
+    /**
+     * Persists the package name of per-app locales set from a delegate selector.
+     *
+     * <p>This information is used when the user has set per-app locales for a specific application
+     * from the delegate selector, and then the LocaleConfig of that application is removed in the
+     * upgraded version, the per-app locales needs to be reset to system default locales to avoid
+     * the user being unable to change system locales setting.
+     */
+    void persistLocalesModificationInfo(@UserIdInt int userId, String packageName,
+            boolean fromDelegate, boolean emptyLocales) {
+        if (mDelegateAppLocalePackages == null) {
+            Slog.w(TAG, "Failed to persist data into the shared preference!");
+            return;
+        }
+
+        SharedPreferences.Editor editor = mDelegateAppLocalePackages.edit();
+        String user = Integer.toString(userId);
+        Set<String> packageNames = new ArraySet<>(
+                mDelegateAppLocalePackages.getStringSet(user, new ArraySet<>()));
+        if (fromDelegate && !emptyLocales) {
+            if (!packageNames.contains(packageName)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "persist package: " + packageName);
+                }
+                packageNames.add(packageName);
+                editor.putStringSet(user, packageNames);
+            }
+        } else {
+            // Remove the package name if per-app locales was not set from the delegate selector
+            // or they were set to empty.
+            if (packageNames.contains(packageName)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "remove package: " + packageName);
+                }
+                packageNames.remove(packageName);
+                editor.putStringSet(user, packageNames);
+            }
+        }
+
+        // commit and log the result.
+        if (!editor.commit()) {
+            Slog.e(TAG, "failed to commit locale setter info");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 4ce0320..783a6ae 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -38,6 +38,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -59,6 +60,10 @@
  */
 public class LocaleManagerService extends SystemService {
     private static final String TAG = "LocaleManagerService";
+    // The feature flag control that allows the active IME to query the locales of the foreground
+    // app.
+    private static final String PROP_ALLOW_IME_QUERY_APP_LOCALE =
+            "i18n.feature.allow_ime_query_app_locale";
     final Context mContext;
     private final LocaleManagerService.LocaleManagerBinderService mBinderService;
     private ActivityTaskManagerInternal mActivityTaskManagerInternal;
@@ -94,9 +99,11 @@
 
         mBackupHelper = new LocaleManagerBackupHelper(this,
                 mPackageManager, broadcastHandlerThread);
+        AppUpdateTracker appUpdateTracker =
+                new AppUpdateTracker(mContext, this, mBackupHelper);
 
         mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
-                systemAppUpdateTracker);
+                systemAppUpdateTracker, appUpdateTracker);
         mPackageMonitor.register(context, broadcastHandlerThread.getLooper(),
                 UserHandle.ALL,
                 true);
@@ -147,8 +154,9 @@
     private final class LocaleManagerBinderService extends ILocaleManager.Stub {
         @Override
         public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId,
-                @NonNull LocaleList locales) throws RemoteException {
-            LocaleManagerService.this.setApplicationLocales(appPackageName, userId, locales);
+                @NonNull LocaleList locales, boolean fromDelegate) throws RemoteException {
+            LocaleManagerService.this.setApplicationLocales(appPackageName, userId, locales,
+                    fromDelegate);
         }
 
         @Override
@@ -178,7 +186,8 @@
      * Sets the current UI locales for a specified app.
      */
     public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId,
-            @NonNull LocaleList locales) throws RemoteException, IllegalArgumentException {
+            @NonNull LocaleList locales, boolean fromDelegate)
+            throws RemoteException, IllegalArgumentException {
         AppLocaleChangedAtomRecord atomRecordForMetrics = new
                 AppLocaleChangedAtomRecord(Binder.getCallingUid());
         try {
@@ -203,6 +212,8 @@
                 enforceChangeConfigurationPermission(atomRecordForMetrics);
             }
 
+            mBackupHelper.persistLocalesModificationInfo(userId, appPackageName, fromDelegate,
+                    locales.isEmpty());
             final long token = Binder.clearCallingIdentity();
             try {
                 setApplicationLocalesUnchecked(appPackageName, userId, locales,
@@ -425,6 +436,10 @@
      * Checks if the calling app is the current input method.
      */
     private boolean isCallerFromCurrentInputMethod(int userId) {
+        if (!SystemProperties.getBoolean(PROP_ALLOW_IME_QUERY_APP_LOCALE, true)) {
+            return false;
+        }
+
         String currentInputMethod = Settings.Secure.getStringForUser(
                 mContext.getContentResolver(),
                 Settings.Secure.DEFAULT_INPUT_METHOD,
diff --git a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
index 32080ef..1a38f0c 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
@@ -34,11 +34,13 @@
 final class LocaleManagerServicePackageMonitor extends PackageMonitor {
     private LocaleManagerBackupHelper mBackupHelper;
     private SystemAppUpdateTracker mSystemAppUpdateTracker;
+    private AppUpdateTracker mAppUpdateTracker;
 
     LocaleManagerServicePackageMonitor(LocaleManagerBackupHelper localeManagerBackupHelper,
-            SystemAppUpdateTracker systemAppUpdateTracker) {
+            SystemAppUpdateTracker systemAppUpdateTracker, AppUpdateTracker appUpdateTracker) {
         mBackupHelper = localeManagerBackupHelper;
         mSystemAppUpdateTracker = systemAppUpdateTracker;
+        mAppUpdateTracker = appUpdateTracker;
     }
 
     @Override
@@ -48,16 +50,17 @@
 
     @Override
     public void onPackageDataCleared(String packageName, int uid) {
-        mBackupHelper.onPackageDataCleared();
+        mBackupHelper.onPackageDataCleared(packageName, uid);
     }
 
     @Override
     public void onPackageRemoved(String packageName, int uid) {
-        mBackupHelper.onPackageRemoved();
+        mBackupHelper.onPackageRemoved(packageName, uid);
     }
 
     @Override
     public void onPackageUpdateFinished(String packageName, int uid) {
+        mAppUpdateTracker.onPackageUpdateFinished(packageName, uid);
         mSystemAppUpdateTracker.onPackageUpdateFinished(packageName, uid);
     }
 }
diff --git a/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java b/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
index 803b5a3..c5069e5 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
@@ -56,7 +56,8 @@
         pw.println("Locale manager (locale) shell commands:");
         pw.println("  help");
         pw.println("      Print this help text.");
-        pw.println("  set-app-locales <PACKAGE_NAME> [--user <USER_ID>] [--locales <LOCALE_INFO>]");
+        pw.println("  set-app-locales <PACKAGE_NAME> [--user <USER_ID>] [--locales <LOCALE_INFO>]"
+                + "[--delegate <FROM_DELEGATE>]");
         pw.println("      Set the locales for the specified app.");
         pw.println("      --user <USER_ID>: apply for the given user, "
                 + "the current user is used when unspecified.");
@@ -64,6 +65,8 @@
                 + "as a single String separated by commas");
         pw.println("                 Empty locale list is used when unspecified.");
         pw.println("                 eg. en,en-US,hi ");
+        pw.println("      --delegate <FROM_DELEGATE>: The locales are set from a delegate, "
+                + "the value could be true or false. false is the default when unspecified.");
         pw.println("  get-app-locales <PACKAGE_NAME> [--user <USER_ID>]");
         pw.println("      Get the locales for the specified app.");
         pw.println("      --user <USER_ID>: get for the given user, "
@@ -77,6 +80,7 @@
         if (packageName != null) {
             int userId = ActivityManager.getCurrentUser();
             LocaleList locales = LocaleList.getEmptyLocaleList();
+            boolean fromDelegate = false;
             do {
                 String option = getNextOption();
                 if (option == null) {
@@ -91,6 +95,10 @@
                         locales = parseLocales();
                         break;
                     }
+                    case "--delegate": {
+                        fromDelegate = parseFromDelegate();
+                        break;
+                    }
                     default: {
                         throw new IllegalArgumentException("Unknown option: " + option);
                     }
@@ -98,7 +106,7 @@
             } while (true);
 
             try {
-                mBinderService.setApplicationLocales(packageName, userId, locales);
+                mBinderService.setApplicationLocales(packageName, userId, locales, fromDelegate);
             } catch (RemoteException e) {
                 getOutPrintWriter().println("Remote Exception: " + e);
             } catch (IllegalArgumentException e) {
@@ -148,12 +156,26 @@
     }
 
     private LocaleList parseLocales() {
-        if (getRemainingArgsCount() <= 0) {
+        String locales = getNextArg();
+        if (locales == null) {
             return LocaleList.getEmptyLocaleList();
+        } else {
+            if (locales.startsWith("-")) {
+                throw new IllegalArgumentException("Unknown locales: " + locales);
+            }
+            return LocaleList.forLanguageTags(locales);
         }
-        String[] args = peekRemainingArgs();
-        String inputLocales = args[0];
-        LocaleList locales = LocaleList.forLanguageTags(inputLocales);
-        return locales;
+    }
+
+    private boolean parseFromDelegate() {
+        String result = getNextArg();
+        if (result == null) {
+            return false;
+        } else {
+            if (result.startsWith("-")) {
+                throw new IllegalArgumentException("Unknown source: " + result);
+            }
+            return Boolean.parseBoolean(result);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/locales/OWNERS b/services/core/java/com/android/server/locales/OWNERS
index 4d93bff..e1e946b 100644
--- a/services/core/java/com/android/server/locales/OWNERS
+++ b/services/core/java/com/android/server/locales/OWNERS
@@ -2,3 +2,6 @@
 pratyushmore@google.com
 goldmanj@google.com
 ankitavyas@google.com
+allenwtsu@google.com
+calvinpan@google.com
+joshhou@google.com
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 9bd48f2..3ce51c3 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -28,6 +28,7 @@
 import static android.location.LocationManager.NETWORK_PROVIDER;
 import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS;
 import static android.location.provider.LocationProviderBase.ACTION_FUSED_PROVIDER;
+import static android.location.provider.LocationProviderBase.ACTION_GNSS_PROVIDER;
 import static android.location.provider.LocationProviderBase.ACTION_NETWORK_PROVIDER;
 
 import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
@@ -439,9 +440,24 @@
             mGnssManagerService = new GnssManagerService(mContext, mInjector, gnssNative);
             mGnssManagerService.onSystemReady();
 
+            boolean useGnssHardwareProvider = mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_useGnssHardwareProvider);
+            AbstractLocationProvider gnssProvider = null;
+            if (!useGnssHardwareProvider) {
+                gnssProvider = ProxyLocationProvider.create(
+                        mContext,
+                        GPS_PROVIDER,
+                        ACTION_GNSS_PROVIDER,
+                        com.android.internal.R.bool.config_useGnssHardwareProvider,
+                        com.android.internal.R.string.config_gnssLocationProviderPackageName);
+            }
+            if (gnssProvider == null) {
+                gnssProvider = mGnssManagerService.getGnssLocationProvider();
+            }
+
             LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
                     GPS_PROVIDER, mPassiveManager);
-            addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider());
+            addLocationProviderManager(gnssManager, gnssProvider);
         }
 
         // bind to geocoder provider
@@ -950,6 +966,8 @@
     @Override
     public void injectLocation(Location location) {
 
+        super.injectLocation_enforcePermission();
+
         Preconditions.checkArgument(location.isComplete());
 
         int userId = UserHandle.getCallingUserId();
@@ -1160,6 +1178,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public void setExtraLocationControllerPackage(String packageName) {
+        super.setExtraLocationControllerPackage_enforcePermission();
+
         synchronized (mLock) {
             mExtraLocationControllerPackage = packageName;
         }
@@ -1175,6 +1195,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public void setExtraLocationControllerPackageEnabled(boolean enabled) {
+        super.setExtraLocationControllerPackageEnabled_enforcePermission();
+
         synchronized (mLock) {
             mExtraLocationControllerPackageEnabled = enabled;
         }
@@ -1234,6 +1256,8 @@
     @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
     public void setAutomotiveGnssSuspended(boolean suspended) {
 
+        super.setAutomotiveGnssSuspended_enforcePermission();
+
         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
             throw new IllegalStateException(
                     "setAutomotiveGnssSuspended only allowed on automotive devices");
@@ -1247,6 +1271,8 @@
     @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
     public boolean isAutomotiveGnssSuspended() {
 
+        super.isAutomotiveGnssSuspended_enforcePermission();
+
         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
             throw new IllegalStateException(
                     "isAutomotiveGnssSuspended only allowed on automotive devices");
@@ -1686,7 +1712,7 @@
 
         private final Context mContext;
 
-        private final UserInfoHelper mUserInfoHelper;
+        private final SystemUserInfoHelper mUserInfoHelper;
         private final LocationSettings mLocationSettings;
         private final AlarmHelper mAlarmHelper;
         private final SystemAppOpsHelper mAppOpsHelper;
@@ -1709,7 +1735,7 @@
         @GuardedBy("this")
         private boolean mSystemReady;
 
-        SystemInjector(Context context, UserInfoHelper userInfoHelper) {
+        SystemInjector(Context context, SystemUserInfoHelper userInfoHelper) {
             mContext = context;
 
             mUserInfoHelper = userInfoHelper;
@@ -1729,6 +1755,7 @@
         }
 
         synchronized void onSystemReady() {
+            mUserInfoHelper.onSystemReady();
             mAppOpsHelper.onSystemReady();
             mLocationPermissionsHelper.onSystemReady();
             mSettingsHelper.onSystemReady();
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 51851be..7f6c2d6 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -73,6 +73,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
@@ -129,7 +130,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;
@@ -147,7 +148,6 @@
     private final ScheduledThreadPoolExecutor mDailyMetricTimer =
             new ScheduledThreadPoolExecutor(1);
 
-
     // The period of the recurring time
     private static final int PERIOD_METRIC_QUERY_DAYS = 1;
 
@@ -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));
@@ -342,11 +363,13 @@
      */
     private void initDefaultClientMap() {
         HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
-        for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
+        for (Map.Entry<Integer, ContextHubInfo> entry: mContextHubIdToInfoMap.entrySet()) {
+            int contextHubId = entry.getKey();
+            ContextHubInfo contextHubInfo = entry.getValue();
+
             mLastRestartTimestampMap.put(contextHubId,
                     new AtomicLong(SystemClock.elapsedRealtimeNanos()));
 
-            ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
             IContextHubClient client = mClientManager.registerClient(
                     contextHubInfo, createDefaultClientCallback(contextHubId),
                     /* attributionTag= */ null, mTransactionManager, mContext.getPackageName());
@@ -368,6 +391,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 +532,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) {
@@ -504,6 +552,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @Override
     public int registerCallback(IContextHubCallback callback) throws RemoteException {
+        super.registerCallback_enforcePermission();
+
         mCallbacksList.register(callback);
 
         Log.d(TAG, "Added callback, total callbacks " +
@@ -514,12 +564,16 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @Override
     public int[] getContextHubHandles() throws RemoteException {
+        super.getContextHubHandles_enforcePermission();
+
         return ContextHubServiceUtil.createPrimitiveIntArray(mContextHubIdToInfoMap.keySet());
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @Override
     public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException {
+        super.getContextHubInfo_enforcePermission();
+
         if (!mContextHubIdToInfoMap.containsKey(contextHubHandle)) {
             Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in getContextHubInfo");
             return null;
@@ -536,6 +590,8 @@
      */
     @Override
     public List<ContextHubInfo> getContextHubs() throws RemoteException {
+        super.getContextHubs_enforcePermission();
+
         return mContextHubInfoList;
     }
 
@@ -602,6 +658,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @Override
     public int loadNanoApp(int contextHubHandle, NanoApp nanoApp) throws RemoteException {
+        super.loadNanoApp_enforcePermission();
+
         if (mContextHubWrapper == null) {
             return -1;
         }
@@ -629,6 +687,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @Override
     public int unloadNanoApp(int nanoAppHandle) throws RemoteException {
+        super.unloadNanoApp_enforcePermission();
+
         if (mContextHubWrapper == null) {
             return -1;
         }
@@ -655,6 +715,8 @@
     @Override
     public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) throws RemoteException {
 
+        super.getNanoAppInstanceInfo_enforcePermission();
+
         return mNanoAppStateManager.getNanoAppInstanceInfo(nanoAppHandle);
     }
 
@@ -663,6 +725,8 @@
     public int[] findNanoAppOnHub(
             int contextHubHandle, NanoAppFilter filter) throws RemoteException {
 
+        super.findNanoAppOnHub_enforcePermission();
+
         ArrayList<Integer> foundInstances = new ArrayList<>();
         if (filter != null) {
             mNanoAppStateManager.foreachNanoAppInstanceInfo((info) -> {
@@ -707,6 +771,8 @@
     @Override
     public int sendMessage(int contextHubHandle, int nanoAppHandle, ContextHubMessage msg)
             throws RemoteException {
+        super.sendMessage_enforcePermission();
+
         if (mContextHubWrapper == null) {
             return -1;
         }
@@ -749,31 +815,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
@@ -853,11 +894,7 @@
 
             ContextHubEventLogger.getInstance().logContextHubRestart(contextHubId);
 
-            sendLocationSettingUpdate();
-            sendWifiSettingUpdate(/* forceUpdate= */ true);
-            sendAirplaneModeSettingUpdate();
-            sendMicrophoneDisableSettingUpdateForCurrentUser();
-            sendBtSettingUpdate(/* forceUpdate= */ true);
+            resetSettings();
 
             mTransactionManager.onHubReset();
             queryNanoAppsInternal(contextHubId);
@@ -928,6 +965,8 @@
     public IContextHubClient createClient(
             int contextHubId, IContextHubClientCallback clientCallback,
             @Nullable String attributionTag, String packageName) throws RemoteException {
+        super.createClient_enforcePermission();
+
         if (!isValidContextHubId(contextHubId)) {
             throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
         }
@@ -956,6 +995,8 @@
     public IContextHubClient createPendingIntentClient(
             int contextHubId, PendingIntent pendingIntent, long nanoAppId,
             @Nullable String attributionTag) throws RemoteException {
+        super.createPendingIntentClient_enforcePermission();
+
         if (!isValidContextHubId(contextHubId)) {
             throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
         }
@@ -978,6 +1019,8 @@
     public void loadNanoAppOnHub(
             int contextHubId, IContextHubTransactionCallback transactionCallback,
             NanoAppBinary nanoAppBinary) throws RemoteException {
+        super.loadNanoAppOnHub_enforcePermission();
+
         if (!checkHalProxyAndContextHubId(
                 contextHubId, transactionCallback, ContextHubTransaction.TYPE_LOAD_NANOAPP)) {
             return;
@@ -1007,6 +1050,8 @@
     public void unloadNanoAppFromHub(
             int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId)
             throws RemoteException {
+        super.unloadNanoAppFromHub_enforcePermission();
+
         if (!checkHalProxyAndContextHubId(
                 contextHubId, transactionCallback, ContextHubTransaction.TYPE_UNLOAD_NANOAPP)) {
             return;
@@ -1030,6 +1075,8 @@
     public void enableNanoApp(
             int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId)
             throws RemoteException {
+        super.enableNanoApp_enforcePermission();
+
         if (!checkHalProxyAndContextHubId(
                 contextHubId, transactionCallback, ContextHubTransaction.TYPE_ENABLE_NANOAPP)) {
             return;
@@ -1053,6 +1100,8 @@
     public void disableNanoApp(
             int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId)
             throws RemoteException {
+        super.disableNanoApp_enforcePermission();
+
         if (!checkHalProxyAndContextHubId(
                 contextHubId, transactionCallback, ContextHubTransaction.TYPE_DISABLE_NANOAPP)) {
             return;
@@ -1074,6 +1123,8 @@
     @Override
     public void queryNanoApps(int contextHubId, IContextHubTransactionCallback transactionCallback)
             throws RemoteException {
+        super.queryNanoApps_enforcePermission();
+
         if (!checkHalProxyAndContextHubId(
                 contextHubId, transactionCallback, ContextHubTransaction.TYPE_QUERY_NANOAPPS)) {
             return;
@@ -1084,6 +1135,26 @@
         mTransactionManager.addTransaction(transaction);
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    /**
+     * Queries for a list of preloaded nanoapp IDs from the specified Context Hub.
+     *
+     * @param hubInfo The Context Hub to query a list of nanoapps from.
+     * @return The list of 64-bit IDs of the preloaded nanoapps.
+     * @throws NullPointerException if hubInfo is null
+     */
+    @Override
+    public long[] getPreloadedNanoAppIds(ContextHubInfo hubInfo) throws RemoteException {
+        super.getPreloadedNanoAppIds_enforcePermission();
+        Objects.requireNonNull(hubInfo, "hubInfo cannot be null");
+
+        long[] nanoappIds = mContextHubWrapper.getPreloadedNanoappIds();
+        if (nanoappIds == null) {
+            return new long[0];
+        }
+        return nanoappIds;
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -1111,6 +1182,10 @@
         mNanoAppStateManager.foreachNanoAppInstanceInfo((info) -> pw.println(info));
 
         pw.println("");
+        pw.println("=================== PRELOADED NANOAPPS ====================");
+        dumpPreloadedNanoapps(pw);
+
+        pw.println("");
         pw.println("=================== CLIENTS ====================");
         pw.println(mClientManager);
 
@@ -1152,6 +1227,21 @@
         proto.flush();
     }
 
+    /**
+     * Dumps preloaded nanoapps to the console
+     */
+    private void dumpPreloadedNanoapps(PrintWriter pw) {
+        if (mContextHubWrapper == null) {
+            return;
+        }
+
+        long[] preloadedNanoappIds = mContextHubWrapper.getPreloadedNanoappIds();
+        for (long preloadedNanoappId: preloadedNanoappIds) {
+            pw.print("ID: 0x");
+            pw.println(Long.toHexString(preloadedNanoappId));
+        }
+    }
+
     private void checkPermissions() {
         ContextHubServiceUtil.checkPermissions(mContext);
     }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index 4f6d0d4..e46b8c0c 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -523,9 +523,9 @@
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(100);
-        TransactionRecord[] arr;
+        ContextHubServiceTransaction[] arr;
         synchronized (this) {
-            arr = mTransactionQueue.toArray(new TransactionRecord[0]);
+            arr = mTransactionQueue.toArray(new ContextHubServiceTransaction[0]);
         }
         for (int i = 0; i < arr.length; i++) {
             sb.append(i + ": " + arr[i] + "\n");
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..f55ae6e 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);
     }
 
     /**
@@ -346,6 +359,14 @@
     public abstract int queryNanoapps(int contextHubId) throws RemoteException;
 
     /**
+     * Provides the list of preloaded nanoapp IDs on the system. The output of this API must
+     * not change.
+     *
+     * @return The list of preloaded nanoapp IDs
+     */
+    public abstract long[] getPreloadedNanoappIds();
+
+    /**
      * Registers a callback with the Context Hub.
      *
      * @param contextHubId The ID of the Context Hub to register the callback with.
@@ -354,12 +375,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 +450,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 +554,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 +568,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 +584,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 +603,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 +623,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 +641,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 +659,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 +676,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 +691,79 @@
             }
         }
 
-        public void registerCallback(int contextHubId, ICallback callback) throws RemoteException {
-            mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
+        public long[] getPreloadedNanoappIds() {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return null;
+            }
+
             try {
-                mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId));
+                return hub.getPreloadedNanoappIds();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Exception while getting preloaded nanoapp IDs: " + e.getMessage());
+                return null;
+            }
+        }
+
+        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 {
+                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");
+            }
+        }
     }
 
     /**
@@ -723,12 +885,27 @@
                     mHub.queryApps(contextHubId));
         }
 
+        public long[] getPreloadedNanoappIds() {
+            return new long[0];
+        }
+
         public void registerCallback(int contextHubId, ICallback callback) throws RemoteException {
             mHidlCallbackMap.put(contextHubId,
                         new ContextHubWrapperHidlCallback(contextHubId, callback));
             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/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 45436e7..cb952ed 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -110,6 +110,11 @@
         addLog(new UserSwitchedEvent(userIdFrom, userIdTo));
     }
 
+    /** Logs a user visibility changed event. */
+    public void logUserVisibilityChanged(int userId, boolean visible) {
+        addLog(new UserVisibilityChangedEvent(userId, visible));
+    }
+
     /** Logs a location enabled/disabled event. */
     public void logLocationEnabled(int userId, boolean enabled) {
         addLog(new LocationEnabledEvent(userId, enabled));
@@ -475,6 +480,22 @@
         }
     }
 
+    private static final class UserVisibilityChangedEvent {
+
+        private final int mUserId;
+        private final boolean mVisible;
+
+        UserVisibilityChangedEvent(int userId, boolean visible) {
+            mUserId = userId;
+            mVisible = visible;
+        }
+
+        @Override
+        public String toString() {
+            return "[u" + mUserId + "] " + (mVisible ? "visible" : "invisible");
+        }
+    }
+
     private static final class LocationEnabledEvent {
 
         private final int mUserId;
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index 0f5e3d4..d3ceddd 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -387,7 +387,7 @@
             if (!mSettingsHelper.isLocationEnabled(identity.getUserId())) {
                 return false;
             }
-            if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
+            if (!mUserInfoHelper.isVisibleUserId(identity.getUserId())) {
                 return false;
             }
             if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
@@ -534,7 +534,10 @@
     }
 
     void onUserChanged(int userId, int change) {
-        if (change == UserListener.CURRENT_USER_CHANGED) {
+        // current user changes affect whether system server location requests are allowed to access
+        // location, and visibility changes affect whether any given user may access location.
+        if (change == UserListener.CURRENT_USER_CHANGED
+                || change == UserListener.USER_VISIBILITY_CHANGED) {
             updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
         }
     }
diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
index 1435016..77cd673 100644
--- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
@@ -76,6 +76,8 @@
             "ENABLE_PSDS_PERIODIC_DOWNLOAD";
     private static final String CONFIG_ENABLE_ACTIVE_SIM_EMERGENCY_SUPL =
             "ENABLE_ACTIVE_SIM_EMERGENCY_SUPL";
+    private static final String CONFIG_ENABLE_NI_SUPL_MESSAGE_INJECTION =
+            "ENABLE_NI_SUPL_MESSAGE_INJECTION";
     static final String CONFIG_LONGTERM_PSDS_SERVER_1 = "LONGTERM_PSDS_SERVER_1";
     static final String CONFIG_LONGTERM_PSDS_SERVER_2 = "LONGTERM_PSDS_SERVER_2";
     static final String CONFIG_LONGTERM_PSDS_SERVER_3 = "LONGTERM_PSDS_SERVER_3";
@@ -218,6 +220,14 @@
     }
 
     /**
+     * Returns true if NI SUPL message injection is enabled; Returns false otherwise.
+     * Default false if not set.
+     */
+    boolean isNiSuplMessageInjectionEnabled() {
+        return getBooleanConfig(CONFIG_ENABLE_NI_SUPL_MESSAGE_INJECTION, false);
+    }
+
+    /**
      * Returns true if a long-term PSDS server is configured.
      */
     boolean isLongTermPsdsServerConfigured() {
@@ -286,26 +296,24 @@
                 Log.e(TAG, "Unable to set " + CONFIG_ES_EXTENSION_SEC + ": " + mEsExtensionSec);
             }
 
-            Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>() {
-                {
-                    put(CONFIG_SUPL_VER, GnssConfiguration::native_set_supl_version);
-                    put(CONFIG_SUPL_MODE, GnssConfiguration::native_set_supl_mode);
+            Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>();
 
-                    if (isConfigSuplEsSupported(gnssConfigurationIfaceVersion)) {
-                        put(CONFIG_SUPL_ES, GnssConfiguration::native_set_supl_es);
-                    }
+            map.put(CONFIG_SUPL_VER, GnssConfiguration::native_set_supl_version);
+            map.put(CONFIG_SUPL_MODE, GnssConfiguration::native_set_supl_mode);
 
-                    put(CONFIG_LPP_PROFILE, GnssConfiguration::native_set_lpp_profile);
-                    put(CONFIG_A_GLONASS_POS_PROTOCOL_SELECT,
-                            GnssConfiguration::native_set_gnss_pos_protocol_select);
-                    put(CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL,
-                            GnssConfiguration::native_set_emergency_supl_pdn);
+            if (isConfigSuplEsSupported(gnssConfigurationIfaceVersion)) {
+                map.put(CONFIG_SUPL_ES, GnssConfiguration::native_set_supl_es);
+            }
 
-                    if (isConfigGpsLockSupported(gnssConfigurationIfaceVersion)) {
-                        put(CONFIG_GPS_LOCK, GnssConfiguration::native_set_gps_lock);
-                    }
-                }
-            };
+            map.put(CONFIG_LPP_PROFILE, GnssConfiguration::native_set_lpp_profile);
+            map.put(CONFIG_A_GLONASS_POS_PROTOCOL_SELECT,
+                    GnssConfiguration::native_set_gnss_pos_protocol_select);
+            map.put(CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL,
+                    GnssConfiguration::native_set_emergency_supl_pdn);
+
+            if (isConfigGpsLockSupported(gnssConfigurationIfaceVersion)) {
+                map.put(CONFIG_GPS_LOCK, GnssConfiguration::native_set_gps_lock);
+            }
 
             for (Entry<String, SetCarrierProperty> entry : map.entrySet()) {
                 String propertyName = entry.getKey();
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index 349b94b..567d8ac 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -317,7 +317,7 @@
                     identity.getUserId())) {
                 return false;
             }
-            if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
+            if (!mUserInfoHelper.isVisibleUserId(identity.getUserId())) {
                 return false;
             }
             if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
@@ -394,7 +394,10 @@
     }
 
     private void onUserChanged(int userId, int change) {
-        if (change == UserListener.CURRENT_USER_CHANGED) {
+        // current user changes affect whether system server location requests are allowed to access
+        // location, and visibility changes affect whether any given user may access location.
+        if (change == UserListener.CURRENT_USER_CHANGED
+                || change == UserListener.USER_VISIBILITY_CHANGED) {
             updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
         }
     }
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 6f637b8..282ad57 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -81,9 +81,11 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
 import android.provider.Settings;
+import android.provider.Telephony.Sms.Intents;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellIdentity;
 import android.telephony.CellIdentityGsm;
@@ -95,6 +97,7 @@
 import android.telephony.CellInfoLte;
 import android.telephony.CellInfoNr;
 import android.telephony.CellInfoWcdma;
+import android.telephony.SmsMessage;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -107,6 +110,7 @@
 import com.android.internal.location.GpsNetInitiatedHandler;
 import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.HexDump;
 import com.android.server.FgThread;
 import com.android.server.location.gnss.GnssSatelliteBlocklistHelper.GnssSatelliteBlocklistCallback;
 import com.android.server.location.gnss.NtpTimeHelper.InjectNtpTimeCallback;
@@ -523,23 +527,31 @@
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         intentFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
-                if (DEBUG) Log.d(TAG, "receive broadcast intent, action: " + action);
-                if (action == null) {
-                    return;
-                }
+        mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
 
-                switch (action) {
-                    case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
-                    case TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
-                        subscriptionOrCarrierConfigChanged();
-                        break;
-                }
+        if (mNetworkConnectivityHandler.isNativeAgpsRilSupported()
+                && mGnssConfiguration.isNiSuplMessageInjectionEnabled()) {
+            // Listen to WAP PUSH NI SUPL message.
+            // See User Plane Location Protocol Candidate Version 3.0,
+            // OMA-TS-ULP-V3_0-20110920-C, Section 8.3 OMA Push.
+            intentFilter = new IntentFilter();
+            intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION);
+            try {
+                intentFilter.addDataType("application/vnd.omaloc-supl-init");
+            } catch (IntentFilter.MalformedMimeTypeException e) {
+                Log.w(TAG, "Malformed SUPL init mime type");
             }
-        }, intentFilter, null, mHandler);
+            mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
+
+            // Listen to MT SMS NI SUPL message.
+            // See User Plane Location Protocol Candidate Version 3.0,
+            // OMA-TS-ULP-V3_0-20110920-C, Section 8.4 MT SMS.
+            intentFilter = new IntentFilter();
+            intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION);
+            intentFilter.addDataScheme("sms");
+            intentFilter.addDataAuthority("localhost", "7275");
+            mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
+        }
 
         mNetworkConnectivityHandler.registerNetworkCallbacks();
 
@@ -560,6 +572,80 @@
         updateEnabled();
     }
 
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (DEBUG) Log.d(TAG, "receive broadcast intent, action: " + action);
+            if (action == null) {
+                return;
+            }
+
+            switch (action) {
+                case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
+                case TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
+                    subscriptionOrCarrierConfigChanged();
+                    break;
+                case Intents.WAP_PUSH_RECEIVED_ACTION:
+                case Intents.DATA_SMS_RECEIVED_ACTION:
+                    injectSuplInit(intent);
+                    break;
+            }
+        }
+    };
+
+    private void injectSuplInit(Intent intent) {
+        if (!isNfwLocationAccessAllowed()) {
+            Log.w(TAG, "Reject SUPL INIT as no NFW location access");
+            return;
+        }
+
+        int slotIndex = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
+                SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+        if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+            Log.e(TAG, "Invalid slot index");
+            return;
+        }
+
+        byte[] suplInit = null;
+        String action = intent.getAction();
+        if (action.equals(Intents.DATA_SMS_RECEIVED_ACTION)) {
+            SmsMessage[] messages = Intents.getMessagesFromIntent(intent);
+            if (messages == null) {
+                Log.e(TAG, "Message does not exist in the intent");
+                return;
+            }
+            for (SmsMessage message : messages) {
+                suplInit = message.getUserData();
+                injectSuplInit(suplInit, slotIndex);
+            }
+        } else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) {
+            suplInit = intent.getByteArrayExtra("data");
+            injectSuplInit(suplInit, slotIndex);
+        }
+    }
+
+    private void injectSuplInit(byte[] suplInit, int slotIndex) {
+        if (suplInit != null) {
+            if (DEBUG) {
+                Log.d(TAG, "suplInit = "
+                        + HexDump.toHexString(suplInit) + " slotIndex = " + slotIndex);
+            }
+            mGnssNative.injectNiSuplMessageData(suplInit, suplInit.length , slotIndex);
+        }
+    }
+
+    private boolean isNfwLocationAccessAllowed() {
+        if (mGnssNative.isInEmergencySession()) {
+            return true;
+        }
+        if (mGnssVisibilityControl != null
+                && mGnssVisibilityControl.hasLocationPermissionEnabledProxyApps()) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Implements {@link InjectNtpTimeCallback#injectTime}
      */
@@ -845,9 +931,15 @@
     }
 
     private void updateEnabled() {
-        // Generally follow location setting for current user
-        boolean enabled = mContext.getSystemService(LocationManager.class)
-                .isLocationEnabledForUser(UserHandle.CURRENT);
+        boolean enabled = false;
+
+        // Generally follow location setting for visible users
+        LocationManager locationManager = mContext.getSystemService(LocationManager.class);
+        Set<UserHandle> visibleUserHandles =
+                mContext.getSystemService(UserManager.class).getVisibleUsers();
+        for (UserHandle visibleUserHandle : visibleUserHandles) {
+            enabled |= locationManager.isLocationEnabledForUser(visibleUserHandle);
+        }
 
         // .. but enable anyway, if there's an active bypass request (e.g. ELS or ADAS)
         enabled |= (mProviderRequest != null
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 02bdfd5..a7fffe2 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -762,6 +762,10 @@
         return APN_INVALID;
     }
 
+    protected boolean isNativeAgpsRilSupported() {
+        return native_is_agps_ril_supported();
+    }
+
     // AGPS support
     private native void native_agps_data_conn_open(long networkHandle, String apn, int apnIpType);
 
diff --git a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
index 631dbbf..4e5e5f8 100644
--- a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
@@ -437,6 +437,10 @@
         return locationPermissionEnabledProxyApps;
     }
 
+    public boolean hasLocationPermissionEnabledProxyApps() {
+        return getLocationPermissionEnabledProxyApps().length > 0;
+    }
+
     private void handleNfwNotification(NfwNotification nfwNotification) {
         if (DEBUG) Log.d(TAG, "Non-framework location access notification: " + nfwNotification);
 
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index 1fa56bc..edb2e5b 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -25,6 +25,7 @@
 import android.location.GnssMeasurementCorrections;
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssNavigationMessage;
+import android.location.GnssSignalType;
 import android.location.GnssStatus;
 import android.location.Location;
 import android.os.Binder;
@@ -988,6 +989,14 @@
         mGnssHal.injectPsdsData(data, length, psdsType);
     }
 
+    /**
+     * Injects NI SUPL message data into the GNSS HAL.
+     */
+    public void injectNiSuplMessageData(byte[] data, int length, int slotIndex) {
+        Preconditions.checkState(mRegistered);
+        mGnssHal.injectNiSuplMessageData(data, length, slotIndex);
+    }
+
     @NativeEntryPoint
     void reportGnssServiceDied() {
         // Not necessary to clear (and restore) binder identity since it runs on another thread.
@@ -1144,6 +1153,13 @@
         onCapabilitiesChanged(oldCapabilities, mCapabilities);
     }
 
+    @NativeEntryPoint
+    void setSignalTypeCapabilities(List<GnssSignalType> signalTypes) {
+        GnssCapabilities oldCapabilities = mCapabilities;
+        mCapabilities = oldCapabilities.withSignalTypes(signalTypes);
+        onCapabilitiesChanged(oldCapabilities, mCapabilities);
+    }
+
     private void onCapabilitiesChanged(GnssCapabilities oldCapabilities,
             GnssCapabilities newCapabilities) {
         Binder.withCleanCallingIdentity(() -> {
@@ -1270,7 +1286,7 @@
     }
 
     @NativeEntryPoint
-    boolean isInEmergencySession() {
+    public boolean isInEmergencySession() {
         return Binder.withCleanCallingIdentity(
                 () -> mEmergencyHelper.isInEmergency(
                         TimeUnit.SECONDS.toMillis(mConfiguration.getEsExtensionSec())));
@@ -1499,6 +1515,10 @@
         protected void injectPsdsData(byte[] data, int length, int psdsType) {
             native_inject_psds_data(data, length, psdsType);
         }
+
+        protected void injectNiSuplMessageData(byte[] data, int length, int slotIndex) {
+            native_inject_ni_supl_message_data(data, length, slotIndex);
+        }
     }
 
     // basic APIs
@@ -1642,6 +1662,9 @@
     private static native void native_agps_set_ref_location_cellid(int type, int mcc, int mnc,
             int lac, long cid, int tac, int pcid, int arfcn);
 
+    private static native void native_inject_ni_supl_message_data(byte[] data, int length,
+            int slotIndex);
+
     // PSDS APIs
 
     private static native boolean native_supports_psds();
diff --git a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
index ed1e654..40dd979 100644
--- a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
@@ -33,9 +33,11 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
 
 import java.io.FileDescriptor;
 import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * Provides accessors and listeners for all user info.
@@ -50,11 +52,21 @@
     @Nullable private IActivityManager mActivityManager;
     @GuardedBy("this")
     @Nullable private UserManager mUserManager;
+    @GuardedBy("this")
+    @Nullable private UserManagerInternal mUserManagerInternal;
 
     public SystemUserInfoHelper(Context context) {
         mContext = context;
     }
 
+    /** The function should be called when PHASE_SYSTEM_SERVICES_READY. */
+    public synchronized void onSystemReady() {
+        mUserManagerInternal =
+                Objects.requireNonNull(LocalServices.getService(UserManagerInternal.class));
+        mUserManagerInternal.addUserVisibilityListener(
+                (userId, visible) -> dispatchOnVisibleUserChanged(userId, visible));
+    }
+
     @Nullable
     protected final ActivityManagerInternal getActivityManagerInternal() {
         synchronized (this) {
@@ -136,6 +148,24 @@
     }
 
     @Override
+    public boolean isVisibleUserId(@UserIdInt int userId) {
+        synchronized (this) {
+            // if you're hitting this precondition then you are invoking this before the system is
+            // ready
+            Preconditions.checkState(mUserManagerInternal != null);
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                return mUserManagerInternal.isUserVisible(userId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
     protected int[] getProfileIds(@UserIdInt int userId) {
         UserManager userManager = getUserManager();
 
diff --git a/services/core/java/com/android/server/location/injector/UserInfoHelper.java b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
index c835370..2b9db1c 100644
--- a/services/core/java/com/android/server/location/injector/UserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
@@ -22,6 +22,7 @@
 import static com.android.server.location.injector.UserInfoHelper.UserListener.CURRENT_USER_CHANGED;
 import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STARTED;
 import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STOPPED;
+import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_VISIBILITY_CHANGED;
 
 import android.annotation.IntDef;
 import android.annotation.UserIdInt;
@@ -47,8 +48,9 @@
         int CURRENT_USER_CHANGED = 1;
         int USER_STARTED = 2;
         int USER_STOPPED = 3;
+        int USER_VISIBILITY_CHANGED = 4;
 
-        @IntDef({CURRENT_USER_CHANGED, USER_STARTED, USER_STOPPED})
+        @IntDef({CURRENT_USER_CHANGED, USER_STARTED, USER_STOPPED, USER_VISIBILITY_CHANGED})
         @Retention(RetentionPolicy.SOURCE)
         @interface UserChange {}
 
@@ -121,6 +123,18 @@
         }
     }
 
+    protected final void dispatchOnVisibleUserChanged(@UserIdInt int userId, boolean visible) {
+        if (D) {
+            Log.d(TAG, "visibility of u" + userId + " changed to "
+                    + (visible ? "visible" : "invisible"));
+        }
+        EVENT_LOG.logUserVisibilityChanged(userId, visible);
+
+        for (UserListener listener : mListeners) {
+            listener.onUserChanged(userId, USER_VISIBILITY_CHANGED);
+        }
+    }
+
     /**
      * Returns an array of running user ids. This will include all running users, and will also
      * include any profiles of the running users. The caller must never mutate the returned
@@ -129,8 +143,8 @@
     public abstract int[] getRunningUserIds();
 
     /**
-     * Returns true if the given user id is either the current user or a profile of the current
-     * user.
+     * Returns {@code true} if the given user id is either the current user or a profile of the
+     * current user.
      */
     public abstract boolean isCurrentUserId(@UserIdInt int userId);
 
@@ -140,6 +154,13 @@
      */
     public abstract @UserIdInt int getCurrentUserId();
 
+    /**
+     * Returns {@code true} if the user is visible.
+     *
+     * <p>The visibility of a user is defined by {@link android.os.UserManager#isUserVisible()}.
+     */
+    public abstract boolean isVisibleUserId(@UserIdInt int userId);
+
     protected abstract int[] getProfileIds(@UserIdInt int userId);
 
     /**
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 338a995..7063cb8 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -661,6 +661,8 @@
                 if (!GPS_PROVIDER.equals(mName)) {
                     Log.e(TAG, "adas gnss bypass request received in non-gps provider");
                     adasGnssBypass = false;
+                } else if (!mUserHelper.isCurrentUserId(getIdentity().getUserId())) {
+                    adasGnssBypass = false;
                 } else if (!mLocationSettings.getUserSettings(
                         getIdentity().getUserId()).isAdasGnssLocationEnabled()) {
                     adasGnssBypass = false;
@@ -1712,6 +1714,8 @@
             if (!GPS_PROVIDER.equals(mName)) {
                 Log.e(TAG, "adas gnss bypass request received in non-gps provider");
                 adasGnssBypass = false;
+            } else if (!mUserHelper.isCurrentUserId(identity.getUserId())) {
+                adasGnssBypass = false;
             } else if (!mLocationSettings.getUserSettings(
                     identity.getUserId()).isAdasGnssLocationEnabled()) {
                 adasGnssBypass = false;
@@ -2193,7 +2197,7 @@
                 if (!isEnabled(identity.getUserId())) {
                     return false;
                 }
-                if (!mUserHelper.isCurrentUserId(identity.getUserId())) {
+                if (!mUserHelper.isVisibleUserId(identity.getUserId())) {
                     return false;
                 }
             }
@@ -2322,6 +2326,10 @@
 
             switch (change) {
                 case UserListener.CURRENT_USER_CHANGED:
+                    // current user changes affect whether system server location requests are
+                    // allowed to access location, and visibility changes affect whether any given
+                    // user may access location.
+                case UserListener.USER_VISIBILITY_CHANGED:
                     updateRegistrations(
                             registration -> registration.getIdentity().getUserId() == userId);
                     break;
diff --git a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
index f144cf8..46f486d 100644
--- a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
+++ b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintManager;
@@ -48,11 +49,13 @@
     @NonNull private final Handler mHandler;
     @Nullable private FingerprintManager mFingerprintManager;
     @Nullable private FaceManager mFaceManager;
+    @Nullable private BiometricManager mBiometricManager;
 
     // Entries added by LockSettingsService once a user's synthetic password is known. At this point
     // things are still keyed by userId.
     @NonNull private final ArrayList<UserAuthInfo> mPendingResetLockoutsForFingerprint;
     @NonNull private final ArrayList<UserAuthInfo> mPendingResetLockoutsForFace;
+    @NonNull private final ArrayList<UserAuthInfo> mPendingResetLockouts;
 
     /**
      * Authentication info for a successful user unlock via Synthetic Password. This can be used to
@@ -125,7 +128,6 @@
     }
 
     @Nullable private FaceResetLockoutTask mFaceResetLockoutTask;
-
     private final FaceResetLockoutTask.FinishCallback mFaceFinishCallback = () -> {
         mFaceResetLockoutTask = null;
     };
@@ -135,12 +137,14 @@
         mHandler = handler;
         mPendingResetLockoutsForFingerprint = new ArrayList<>();
         mPendingResetLockoutsForFace = new ArrayList<>();
+        mPendingResetLockouts = new ArrayList<>();
     }
 
     public void systemReady(@Nullable FingerprintManager fingerprintManager,
-            @Nullable FaceManager faceManager) {
+            @Nullable FaceManager faceManager, @Nullable BiometricManager biometricManager) {
         mFingerprintManager = fingerprintManager;
         mFaceManager = faceManager;
+        mBiometricManager = biometricManager;
     }
 
     /**
@@ -151,7 +155,7 @@
      * Note that this should only ever be invoked for successful authentications, otherwise it will
      * consume a Gatekeeper authentication attempt and potentially wipe the user/device.
      *
-     * @param userId The user that the operation will apply for.
+     * @param userId             The user that the operation will apply for.
      * @param gatekeeperPassword The Gatekeeper Password
      */
     void addPendingLockoutResetForUser(int userId, @NonNull byte[] gatekeeperPassword) {
@@ -167,6 +171,12 @@
                 mPendingResetLockoutsForFingerprint.add(new UserAuthInfo(userId,
                         gatekeeperPassword));
             }
+
+            if (mBiometricManager != null) {
+                Slog.d(TAG, "Fingerprint addPendingLockoutResetForUser: " + userId);
+                mPendingResetLockouts.add(new UserAuthInfo(userId,
+                        gatekeeperPassword));
+            }
         });
     }
 
@@ -184,6 +194,14 @@
                         new ArrayList<>(mPendingResetLockoutsForFingerprint));
                 mPendingResetLockoutsForFingerprint.clear();
             }
+
+            if (!mPendingResetLockouts.isEmpty()) {
+                Slog.d(TAG, "Processing pending resetLockouts(Generic)");
+                processPendingLockoutsGeneric(
+                        new ArrayList<>(mPendingResetLockouts));
+                mPendingResetLockouts.clear();
+            }
+
         });
     }
 
@@ -257,6 +275,17 @@
         }
     }
 
+    private void processPendingLockoutsGeneric(List<UserAuthInfo> pendingResetLockouts) {
+        for (UserAuthInfo user : pendingResetLockouts) {
+            Slog.d(TAG, "Resetting biometric lockout for user: " + user.userId);
+            final byte[] hat = requestHatFromGatekeeperPassword(mSpManager, user,
+                    0 /* challenge */);
+            if (hat != null) {
+                mBiometricManager.resetLockout(user.userId, hat);
+            }
+        }
+    }
+
     @Nullable
     private static byte[] requestHatFromGatekeeperPassword(
             @NonNull SyntheticPasswordManager spManager,
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index c899cf2..99d77d6 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -69,7 +69,8 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.database.sqlite.SQLiteDatabase;
-import android.hardware.authsecret.V1_0.IAuthSecret;
+import android.hardware.authsecret.IAuthSecret;
+import android.hardware.biometrics.BiometricManager;
 import android.hardware.face.Face;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.Fingerprint;
@@ -247,14 +248,14 @@
 
     // Locking order is mUserCreationAndRemovalLock -> mSpManager.
     private final Object mUserCreationAndRemovalLock = new Object();
-    // These two arrays are only used at boot time.  To save memory, they are set to null when
-    // PHASE_BOOT_COMPLETED is reached.
+    // These two arrays are only used at boot time.  To save memory, they are set to null near the
+    // end of the boot, when onThirdPartyAppsStarted() is called.
     @GuardedBy("mUserCreationAndRemovalLock")
     private SparseIntArray mEarlyCreatedUsers = new SparseIntArray();
     @GuardedBy("mUserCreationAndRemovalLock")
     private SparseIntArray mEarlyRemovedUsers = new SparseIntArray();
     @GuardedBy("mUserCreationAndRemovalLock")
-    private boolean mBootComplete;
+    private boolean mThirdPartyAppsStarted;
 
     // Current password metrics for all secured users on the device. Updated when user unlocks the
     // device or changes password. Removed when user is stopped.
@@ -264,7 +265,8 @@
     protected boolean mHasSecureLockScreen;
 
     protected IGateKeeperService mGateKeeperService;
-    protected IAuthSecret mAuthSecretService;
+    protected IAuthSecret mAuthSecretServiceAidl;
+    protected android.hardware.authsecret.V1_0.IAuthSecret mAuthSecretServiceHidl;
 
     private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
 
@@ -296,16 +298,9 @@
         @Override
         public void onBootPhase(int phase) {
             super.onBootPhase(phase);
-            switch (phase) {
-                case PHASE_ACTIVITY_MANAGER_READY:
-                    mLockSettingsService.migrateOldDataAfterSystemReady();
-                    mLockSettingsService.loadEscrowData();
-                    break;
-                case PHASE_BOOT_COMPLETED:
-                    mLockSettingsService.bootCompleted();
-                    break;
-                default:
-                    break;
+            if (phase == PHASE_ACTIVITY_MANAGER_READY) {
+                mLockSettingsService.migrateOldDataAfterSystemReady();
+                mLockSettingsService.loadEscrowData();
             }
         }
 
@@ -552,14 +547,8 @@
             }
         }
 
-        public int settingsGlobalGetInt(ContentResolver contentResolver, String keyName,
-                int defaultValue) {
-            return Settings.Global.getInt(contentResolver, keyName, defaultValue);
-        }
-
-        public int settingsSecureGetInt(ContentResolver contentResolver, String keyName,
-                int defaultValue, int userId) {
-            return Settings.Secure.getIntForUser(contentResolver, keyName, defaultValue, userId);
+        public BiometricManager getBiometricManager() {
+            return (BiometricManager) mContext.getSystemService(Context.BIOMETRIC_SERVICE);
         }
 
         public java.security.KeyStore getJavaKeyStore() {
@@ -744,8 +733,8 @@
      * <p>
      * This is primarily needed for users that were removed by Android 13 or earlier, which didn't
      * guarantee removal of LSS state as it relied on the {@code ACTION_USER_REMOVED} intent.  It is
-     * also needed because {@link #removeUser()} delays requests to remove LSS state until the
-     * {@code PHASE_BOOT_COMPLETED} boot phase, so they can be lost.
+     * also needed because {@link #removeUser()} delays requests to remove LSS state until Weaver is
+     * guaranteed to be available, so they can be lost.
      * <p>
      * Stale state is detected by checking whether the user serial number changed.  This works
      * because user serial numbers are never reused.
@@ -837,7 +826,7 @@
         // TODO: maybe skip this for split system user mode.
         mStorage.prefetchUser(UserHandle.USER_SYSTEM);
         mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(),
-                mInjector.getFaceManager());
+                mInjector.getFaceManager(), mInjector.getBiometricManager());
     }
 
     private void loadEscrowData() {
@@ -845,12 +834,19 @@
     }
 
     private void getAuthSecretHal() {
-        try {
-            mAuthSecretService = IAuthSecret.getService(/* retry */ true);
-        } catch (NoSuchElementException e) {
-            Slog.i(TAG, "Device doesn't implement AuthSecret HAL");
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Failed to get AuthSecret HAL", e);
+        mAuthSecretServiceAidl = IAuthSecret.Stub.asInterface(ServiceManager.
+                                 waitForDeclaredService(IAuthSecret.DESCRIPTOR + "/default"));
+        if (mAuthSecretServiceAidl == null) {
+            Slog.i(TAG, "Device doesn't implement AuthSecret HAL(aidl), try to get hidl version");
+
+            try {
+                mAuthSecretServiceHidl =
+                    android.hardware.authsecret.V1_0.IAuthSecret.getService(/* retry */ true);
+            } catch (NoSuchElementException e) {
+                Slog.i(TAG, "Device doesn't implement AuthSecret HAL(hidl)");
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to get AuthSecret HAL(hidl)", e);
+            }
         }
     }
 
@@ -926,7 +922,9 @@
         return success;
     }
 
-    private void bootCompleted() {
+    // This is called when Weaver is guaranteed to be available (if the device supports Weaver).
+    // It does any synthetic password related work that was delayed from earlier in the boot.
+    private void onThirdPartyAppsStarted() {
         synchronized (mUserCreationAndRemovalLock) {
             // Handle delayed calls to LSS.removeUser() and LSS.createNewUser().
             for (int i = 0; i < mEarlyRemovedUsers.size(); i++) {
@@ -971,7 +969,7 @@
                 setString("migrated_all_users_to_sp_and_bound_ce", "true", 0);
             }
 
-            mBootComplete = true;
+            mThirdPartyAppsStarted = true;
         }
     }
 
@@ -1027,9 +1025,9 @@
 
     private void enforceFrpResolved() {
         final ContentResolver cr = mContext.getContentResolver();
-        final boolean inSetupWizard = mInjector.settingsSecureGetInt(cr,
+        final boolean inSetupWizard = Settings.Secure.getIntForUser(cr,
                 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_SYSTEM) == 0;
-        final boolean secureFrp = mInjector.settingsSecureGetInt(cr,
+        final boolean secureFrp = Settings.Secure.getIntForUser(cr,
                 Settings.Secure.SECURE_FRP_MODE, 0, UserHandle.USER_SYSTEM) == 1;
         if (inSetupWizard && secureFrp) {
             throw new SecurityException("Cannot change credential in SUW while factory reset"
@@ -2081,9 +2079,11 @@
     public VerifyCredentialResponse checkCredential(LockscreenCredential credential, int userId,
             ICheckCredentialProgressCallback progressCallback) {
         checkPasswordReadPermission();
+        final long identity = Binder.clearCallingIdentity();
         try {
             return doVerifyCredential(credential, userId, progressCallback, 0 /* flags */);
         } finally {
+            Binder.restoreCallingIdentity(identity);
             scheduleGc();
         }
     }
@@ -2153,7 +2153,7 @@
         if (credential == null || credential.isNone()) {
             throw new IllegalArgumentException("Credential can't be null or empty");
         }
-        if (userId == USER_FRP && mInjector.settingsGlobalGetInt(mContext.getContentResolver(),
+        if (userId == USER_FRP && Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
             Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
             return VerifyCredentialResponse.ERROR;
@@ -2299,14 +2299,14 @@
 
     private void createNewUser(@UserIdInt int userId, int userSerialNumber) {
         synchronized (mUserCreationAndRemovalLock) {
-            // Before PHASE_BOOT_COMPLETED, don't actually create the synthetic password yet, but
-            // rather automatically delay it to later.  We do this because protecting the synthetic
+            // During early boot, don't actually create the synthetic password yet, but rather
+            // automatically delay it to later.  We do this because protecting the synthetic
             // password requires the Weaver HAL if the device supports it, and some devices don't
             // make Weaver available until fairly late in the boot process.  This logic ensures a
             // consistent flow across all devices, regardless of their Weaver implementation.
-            if (!mBootComplete) {
-                Slogf.i(TAG, "Delaying locksettings state creation for user %d until boot complete",
-                        userId);
+            if (!mThirdPartyAppsStarted) {
+                Slogf.i(TAG, "Delaying locksettings state creation for user %d until third-party " +
+                        "apps are started", userId);
                 mEarlyCreatedUsers.put(userId, userSerialNumber);
                 mEarlyRemovedUsers.delete(userId);
                 return;
@@ -2320,14 +2320,14 @@
 
     private void removeUser(@UserIdInt int userId) {
         synchronized (mUserCreationAndRemovalLock) {
-            // Before PHASE_BOOT_COMPLETED, don't actually remove the LSS state yet, but rather
-            // automatically delay it to later.  We do this because deleting synthetic password
-            // protectors requires the Weaver HAL if the device supports it, and some devices don't
-            // make Weaver available until fairly late in the boot process.  This logic ensures a
-            // consistent flow across all devices, regardless of their Weaver implementation.
-            if (!mBootComplete) {
-                Slogf.i(TAG, "Delaying locksettings state removal for user %d until boot complete",
-                        userId);
+            // During early boot, don't actually remove the LSS state yet, but rather automatically
+            // delay it to later.  We do this because deleting synthetic password protectors
+            // requires the Weaver HAL if the device supports it, and some devices don't make Weaver
+            // available until fairly late in the boot process.  This logic ensures a consistent
+            // flow across all devices, regardless of their Weaver implementation.
+            if (!mThirdPartyAppsStarted) {
+                Slogf.i(TAG, "Delaying locksettings state removal for user %d until third-party " +
+                        "apps are started", userId);
                 if (mEarlyCreatedUsers.indexOfKey(userId) >= 0) {
                     mEarlyCreatedUsers.delete(userId);
                 } else {
@@ -2609,17 +2609,25 @@
         // If the given user is the primary user, pass the auth secret to the HAL.  Only the system
         // user can be primary.  Check for the system user ID before calling getUserInfo(), as other
         // users may still be under construction.
-        if (mAuthSecretService != null && userId == UserHandle.USER_SYSTEM &&
+        if (userId == UserHandle.USER_SYSTEM &&
                 mUserManager.getUserInfo(userId).isPrimary()) {
-            try {
-                final byte[] rawSecret = sp.deriveVendorAuthSecret();
-                final ArrayList<Byte> secret = new ArrayList<>(rawSecret.length);
-                for (int i = 0; i < rawSecret.length; ++i) {
-                    secret.add(rawSecret[i]);
+            final byte[] rawSecret = sp.deriveVendorAuthSecret();
+            if (mAuthSecretServiceAidl != null) {
+                try {
+                    mAuthSecretServiceAidl.setPrimaryUserCredential(rawSecret);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL(aidl)", e);
                 }
-                mAuthSecretService.primaryUserCredential(secret);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL", e);
+            } else if (mAuthSecretServiceHidl != null) {
+                try {
+                    final ArrayList<Byte> secret = new ArrayList<>(rawSecret.length);
+                    for (int i = 0; i < rawSecret.length; ++i) {
+                        secret.add(rawSecret[i]);
+                    }
+                    mAuthSecretServiceHidl.primaryUserCredential(secret);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL(hidl)", e);
+                }
             }
         }
     }
@@ -2629,9 +2637,8 @@
      * protects the user's CE key with a key derived from the SP.
      * <p>
      * This is called just once in the lifetime of the user: at user creation time (possibly delayed
-     * until {@code PHASE_BOOT_COMPLETED} to ensure that the Weaver HAL is available if the device
-     * supports it), or when upgrading from Android 13 or earlier where users with no LSKF didn't
-     * necessarily have an SP.
+     * until the time when Weaver is guaranteed to be available), or when upgrading from Android 13
+     * or earlier where users with no LSKF didn't necessarily have an SP.
      */
     @GuardedBy("mSpManager")
     @VisibleForTesting
@@ -3154,7 +3161,7 @@
 
         pw.println("PasswordHandleCount: " + mGatekeeperPasswords.size());
         synchronized (mUserCreationAndRemovalLock) {
-            pw.println("BootComplete: " + mBootComplete);
+            pw.println("ThirdPartyAppsStarted: " + mThirdPartyAppsStarted);
         }
     }
 
@@ -3281,6 +3288,7 @@
             for (UserInfo user : users) {
                 if (userOwnsFrpCredential(mContext, user)) {
                     if (!isUserSecure(user.id)) {
+                        Slogf.d(TAG, "Clearing FRP credential tied to user %d", user.id);
                         mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, user.id,
                                 0, null);
                     }
@@ -3312,6 +3320,11 @@
     private final class LocalService extends LockSettingsInternal {
 
         @Override
+        public void onThirdPartyAppsStarted() {
+            LockSettingsService.this.onThirdPartyAppsStarted();
+        }
+
+        @Override
         public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
             LockSettingsService.this.unlockUserKeyIfUnsecured(userId);
         }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index db036b0..473c4b6 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -36,6 +36,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.AtomicFile;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -52,6 +53,7 @@
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.channels.FileChannel;
@@ -307,30 +309,31 @@
     }
 
     private void writeFile(File path, byte[] data) {
+        writeFile(path, data, /* syncParentDir= */ true);
+    }
+
+    private void writeFile(File path, byte[] data, boolean syncParentDir) {
         synchronized (mFileWriteLock) {
-            RandomAccessFile raf = null;
+            // Use AtomicFile to guarantee atomicity of the file write, including when an existing
+            // file is replaced with a new one.  This method is usually used to create new files,
+            // but there are some edge cases in which it is used to replace an existing file.
+            AtomicFile file = new AtomicFile(path);
+            FileOutputStream out = null;
             try {
-                // Write the data to the file, requiring each write to be synchronized to the
-                // underlying storage device immediately to avoid data loss in case of power loss.
-                raf = new RandomAccessFile(path, "rws");
-                // Truncate the file if the data is empty.
-                if (data == null || data.length == 0) {
-                    raf.setLength(0);
-                } else {
-                    raf.write(data, 0, data.length);
-                }
-                raf.close();
-                fsyncDirectory(path.getParentFile());
+                out = file.startWrite();
+                out.write(data);
+                file.finishWrite(out);
+                out = null;
             } catch (IOException e) {
-                Slog.e(TAG, "Error writing to file " + e);
+                Slog.e(TAG, "Error writing file " + path, e);
             } finally {
-                if (raf != null) {
-                    try {
-                        raf.close();
-                    } catch (IOException e) {
-                        Slog.e(TAG, "Error closing file " + e);
-                    }
-                }
+                file.failWrite(out);
+            }
+            // For performance reasons, AtomicFile only syncs the file itself, not also the parent
+            // directory.  The latter must be done explicitly when requested here, as some callers
+            // need a guarantee that the file really exists on-disk when this returns.
+            if (syncParentDir) {
+                fsyncDirectory(path.getParentFile());
             }
             mCache.putFile(path, data);
         }
@@ -338,18 +341,20 @@
 
     private void deleteFile(File path) {
         synchronized (mFileWriteLock) {
+            // Zeroize the file to try to make its contents unrecoverable.  This is *not* guaranteed
+            // to be effective, and in fact it usually isn't, but it doesn't hurt.  We also don't
+            // bother zeroizing |path|.new, which may exist from an interrupted AtomicFile write.
             if (path.exists()) {
-                // Zeroize the file to try to make its contents unrecoverable.  This is *not*
-                // guaranteed to be effective, and in fact it usually isn't, but it doesn't hurt.
                 try (RandomAccessFile raf = new RandomAccessFile(path, "rws")) {
                     final int fileSize = (int) raf.length();
                     raf.write(new byte[fileSize]);
                 } catch (Exception e) {
                     Slog.w(TAG, "Failed to zeroize " + path, e);
                 }
-                path.delete();
-                mCache.putFile(path, null);
             }
+            // To ensure that |path|.new is deleted if it exists, use AtomicFile.delete() here.
+            new AtomicFile(path).delete();
+            mCache.putFile(path, null);
         }
     }
 
@@ -379,10 +384,20 @@
         }
     }
 
+    /**
+     * Writes the synthetic password state file for the given user ID, protector ID, and state name.
+     * If the file already exists, then it is atomically replaced.
+     * <p>
+     * This doesn't sync the parent directory, and a result the new state file may be lost if the
+     * system crashes.  The caller must call {@link syncSyntheticPasswordState()} afterwards to sync
+     * the parent directory if needed, preferably after batching up other state file creations for
+     * the same user.  We do it this way because directory syncs are expensive on some filesystems.
+     */
     public void writeSyntheticPasswordState(int userId, long protectorId, String name,
             byte[] data) {
         ensureSyntheticPasswordDirectoryForUser(userId);
-        writeFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name), data);
+        writeFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name), data,
+                /* syncParentDir= */ false);
     }
 
     public byte[] readSyntheticPasswordState(int userId, long protectorId, String name) {
@@ -393,6 +408,13 @@
         deleteFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name));
     }
 
+    /**
+     * Ensures that all synthetic password state files for the user have really been saved to disk.
+     */
+    public void syncSyntheticPasswordState(int userId) {
+        fsyncDirectory(getSyntheticPasswordDirectoryForUser(userId));
+    }
+
     public Map<Integer, List<Long>> listSyntheticPasswordProtectorsForAllUsers(String stateName) {
         Map<Integer, List<Long>> result = new ArrayMap<>();
         final UserManager um = UserManager.get(mContext);
@@ -528,7 +550,7 @@
         mCache.clear();
     }
 
-    @Nullable @VisibleForTesting
+    @Nullable
     PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
         if (mPersistentDataBlockManagerInternal == null) {
             mPersistentDataBlockManagerInternal =
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 3fd488e..acd7cc1 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -32,6 +32,7 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.security.GateKeeper;
 import android.security.Scrypt;
 import android.service.gatekeeper.GateKeeperResponse;
@@ -457,6 +458,11 @@
         mPasswordSlotManager = passwordSlotManager;
     }
 
+    private boolean isDeviceProvisioned() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+    }
+
     @VisibleForTesting
     protected IWeaver getWeaverService() throws RemoteException {
         try {
@@ -619,12 +625,16 @@
 
     /**
      * Creates a new synthetic password (SP) for the given user.
-     *
+     * <p>
      * Any existing SID for the user is cleared.
-     *
+     * <p>
      * Also saves the escrow information necessary to re-generate the synthetic password under
      * an escrow scheme. This information can be removed with {@link #destroyEscrowData} if
      * password escrow should be disabled completely on the given user.
+     * <p>
+     * {@link syncState()} is not called yet; the caller should create a protector afterwards, which
+     * handles this.  This makes it so that all the user's initial SP state files, including the
+     * initial LSKF-based protector, are efficiently created with only a single {@link syncState()}.
      */
     SyntheticPassword newSyntheticPassword(int userId) {
         clearSidForUser(userId);
@@ -668,6 +678,7 @@
 
     private void saveSyntheticPasswordHandle(byte[] spHandle, int userId) {
         saveState(SP_HANDLE_NAME, spHandle, NULL_PROTECTOR_ID, userId);
+        syncState(userId);
     }
 
     private boolean loadEscrowData(SyntheticPassword sp, int userId) {
@@ -677,6 +688,11 @@
         return e0 != null && p1 != null;
     }
 
+    /**
+     * Saves the escrow data for the synthetic password.  The caller is responsible for calling
+     * {@link syncState()} afterwards, once the user's other initial synthetic password state files
+     * have been created.
+     */
     private void saveEscrowData(SyntheticPassword sp, int userId) {
         saveState(SP_E0_NAME, sp.mEncryptedEscrowSplit0, NULL_PROTECTOR_ID, userId);
         saveState(SP_P1_NAME, sp.mEscrowSplit1, NULL_PROTECTOR_ID, userId);
@@ -708,6 +724,10 @@
         return buffer.getInt();
     }
 
+    /**
+     * Creates a file that stores the Weaver slot the protector is using.  The caller is responsible
+     * for calling {@link syncState()} afterwards, once all the protector's files have been created.
+     */
     private void saveWeaverSlot(int slot, long protectorId, int userId) {
         ByteBuffer buffer = ByteBuffer.allocate(Byte.BYTES + Integer.BYTES);
         buffer.put(WEAVER_VERSION);
@@ -756,6 +776,17 @@
     private int getNextAvailableWeaverSlot() {
         Set<Integer> usedSlots = getUsedWeaverSlots();
         usedSlots.addAll(mPasswordSlotManager.getUsedSlots());
+        // If the device is not yet provisioned, then the Weaver slot used by the FRP credential may
+        // be still needed and must not be reused yet.  (This *should* instead check "has FRP been
+        // resolved yet?", which would allow reusing the slot a bit earlier.  However, the
+        // SECURE_FRP_MODE setting gets set to 1 too late for it to be used here.)
+        if (!isDeviceProvisioned()) {
+            PersistentData persistentData = mStorage.readPersistentDataBlock();
+            if (persistentData != null && persistentData.type == PersistentData.TYPE_SP_WEAVER) {
+                int slot = persistentData.userId; // Note: field name is misleading
+                usedSlots.add(slot);
+            }
+        }
         for (int i = 0; i < mWeaverConfig.slots; i++) {
             if (!usedSlots.contains(i)) {
                 return i;
@@ -800,9 +831,14 @@
 
             protectorSecret = transformUnderWeaverSecret(stretchedLskf, weaverSecret);
         } else {
-            // Weaver is unavailable, so make the protector use Gatekeeper to verify the LSKF
-            // instead.  However, skip Gatekeeper when the LSKF is empty, since it wouldn't give any
-            // benefit in that case as Gatekeeper isn't expected to provide secure deletion.
+            // Weaver is unavailable, so make the protector use Gatekeeper (GK) to verify the LSKF.
+            //
+            // However, skip GK when the LSKF is empty.  There are two reasons for this, one
+            // performance and one correctness.  The performance reason is that GK wouldn't give any
+            // benefit with an empty LSKF anyway, since GK isn't expected to provide secure
+            // deletion.  The correctness reason is that it is unsafe to enroll a password in the
+            // 'fakeUserId' GK range on an FRP-protected device that is in the setup wizard with FRP
+            // not passed yet, as that may overwrite the enrollment used by the FRP credential.
             if (!credential.isNone()) {
                 // In case GK enrollment leaves persistent state around (in RPMB), this will nuke
                 // them to prevent them from accumulating and causing problems.
@@ -837,6 +873,7 @@
         }
         createSyntheticPasswordBlob(protectorId, PROTECTOR_TYPE_LSKF_BASED, sp, protectorSecret,
                 sid, userId);
+        syncState(userId); // ensure the new files are really saved to disk
         return protectorId;
     }
 
@@ -893,12 +930,40 @@
         }
     }
 
+    private static boolean isNoneCredential(PasswordData pwd) {
+        return pwd == null || pwd.credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE;
+    }
+
+    private boolean shouldSynchronizeFrpCredential(@Nullable PasswordData pwd, int userId) {
+        if (mStorage.getPersistentDataBlockManager() == null) {
+            return false;
+        }
+        UserInfo userInfo = mUserManager.getUserInfo(userId);
+        if (!LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) {
+            return false;
+        }
+        // When initializing the synthetic password of the user that will own the FRP credential,
+        // the FRP data block must not be cleared if the device isn't provisioned yet, since in this
+        // case the old value of the block may still be needed for the FRP authentication step.  The
+        // FRP data block will instead be cleared later, by
+        // LockSettingsService.DeviceProvisionedObserver.clearFrpCredentialIfOwnerNotSecure().
+        //
+        // Don't check the SECURE_FRP_MODE setting here, as it gets set to 1 too late.
+        //
+        // Don't delay anything for a nonempty credential.  A nonempty credential can be set before
+        // the device has been provisioned, but it's guaranteed to be after FRP was resolved.
+        if (isNoneCredential(pwd) && !isDeviceProvisioned()) {
+            Slog.d(TAG, "Not clearing FRP credential yet because device is not yet provisioned");
+            return false;
+        }
+        return true;
+    }
+
     private void synchronizeFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
             int userId) {
-        if (mStorage.getPersistentDataBlockManager() != null
-                && LockPatternUtils.userOwnsFrpCredential(mContext,
-                mUserManager.getUserInfo(userId))) {
-            if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+        if (shouldSynchronizeFrpCredential(pwd, userId)) {
+            Slogf.d(TAG, "Syncing Gatekeeper-based FRP credential tied to user %d", userId);
+            if (!isNoneCredential(pwd)) {
                 mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
                         pwd.toBytes());
             } else {
@@ -909,10 +974,9 @@
 
     private void synchronizeWeaverFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
             int userId, int weaverSlot) {
-        if (mStorage.getPersistentDataBlockManager() != null
-                && LockPatternUtils.userOwnsFrpCredential(mContext,
-                mUserManager.getUserInfo(userId))) {
-            if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+        if (shouldSynchronizeFrpCredential(pwd, userId)) {
+            Slogf.d(TAG, "Syncing Weaver-based FRP credential tied to user %d", userId);
+            if (!isNoneCredential(pwd)) {
                 mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
                         requestedQuality, pwd.toBytes());
             } else {
@@ -996,6 +1060,7 @@
         saveSecdiscardable(tokenHandle, tokenData.secdiscardableOnDisk, userId);
         createSyntheticPasswordBlob(tokenHandle, getTokenBasedProtectorType(tokenData.mType), sp,
                 tokenData.aggregatedSecret, 0L, userId);
+        syncState(userId); // ensure the new files are really saved to disk
         tokenMap.get(userId).remove(tokenHandle);
         if (tokenData.mCallback != null) {
             tokenData.mCallback.onEscrowTokenActivated(tokenHandle, userId);
@@ -1003,6 +1068,11 @@
         return true;
     }
 
+    /**
+     * Creates a synthetic password blob, i.e. the file that stores the encrypted synthetic password
+     * (or encrypted escrow secret) for a protector.  The caller is responsible for calling
+     * {@link syncState()} afterwards, once all the protector's files have been created.
+     */
     private void createSyntheticPasswordBlob(long protectorId, byte protectorType,
             SyntheticPassword sp, byte[] protectorSecret, long sid, int userId) {
         final byte[] spSecret;
@@ -1037,7 +1107,7 @@
         AuthenticationResult result = new AuthenticationResult();
 
         if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
-            // This should never happen, due to the migration done in LSS.bootCompleted().
+            // This should never happen, due to the migration done in LSS.onThirdPartyAppsStarted().
             Slogf.wtf(TAG, "Synthetic password not found for user %d", userId);
             result.gkResponse = VerifyCredentialResponse.ERROR;
             return result;
@@ -1118,6 +1188,7 @@
                             // (getting rid of CREDENTIAL_TYPE_PASSWORD_OR_PIN)
                             pwd.credentialType = credential.getType();
                             saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
+                            syncState(userId);
                             synchronizeFrpPassword(pwd, 0, userId);
                         } else {
                             Slog.w(TAG, "Fail to re-enroll user password for user " + userId);
@@ -1156,6 +1227,7 @@
         if (result.syntheticPassword != null && !credential.isNone() &&
                 !hasPasswordMetrics(protectorId, userId)) {
             savePasswordMetrics(credential, result.syntheticPassword, protectorId, userId);
+            syncState(userId); // Not strictly needed as the upgrade can be re-done, but be safe.
         }
         return result;
     }
@@ -1275,6 +1347,7 @@
                     + blob.mProtectorType);
             createSyntheticPasswordBlob(protectorId, blob.mProtectorType, result, protectorSecret,
                     sid, userId);
+            syncState(userId); // Not strictly needed as the upgrade can be re-done, but be safe.
         }
         return result;
     }
@@ -1396,12 +1469,21 @@
         return ArrayUtils.concat(data, secdiscardable);
     }
 
+    /**
+     * Generates and writes the secdiscardable file for the given protector.  The caller is
+     * responsible for calling {@link syncState()} afterwards, once all the protector's files have
+     * been created.
+     */
     private byte[] createSecdiscardable(long protectorId, int userId) {
         byte[] data = secureRandom(SECDISCARDABLE_LENGTH);
         saveSecdiscardable(protectorId, data, userId);
         return data;
     }
 
+    /**
+     * Writes the secdiscardable file for the given protector.  The caller is responsible for
+     * calling {@link syncState()} afterwards, once all the protector's files have been created.
+     */
     private void saveSecdiscardable(long protectorId, byte[] secdiscardable, int userId) {
         saveState(SECDISCARDABLE_NAME, secdiscardable, protectorId, userId);
     }
@@ -1445,6 +1527,11 @@
         return VersionedPasswordMetrics.deserialize(decrypted).getMetrics();
     }
 
+    /**
+     * Creates the password metrics file: the file associated with the LSKF-based protector that
+     * contains the encrypted metrics about the LSKF.  The caller is responsible for calling
+     * {@link syncState()} afterwards if needed.
+     */
     private void savePasswordMetrics(LockscreenCredential credential, SyntheticPassword sp,
             long protectorId, int userId) {
         final byte[] encrypted = SyntheticPasswordCrypto.encrypt(sp.deriveMetricsKey(),
@@ -1466,10 +1553,21 @@
         return mStorage.readSyntheticPasswordState(userId, protectorId, stateName);
     }
 
+    /**
+     * Persists the given synthetic password state for the given user ID and protector ID.
+     * <p>
+     * For performance reasons, this doesn't sync the user's synthetic password state directory.  As
+     * a result, it doesn't guarantee that the file will really be present after a crash.  If that
+     * is needed, call {@link syncState()} afterwards, preferably after batching up related updates.
+     */
     private void saveState(String stateName, byte[] data, long protectorId, int userId) {
         mStorage.writeSyntheticPasswordState(userId, protectorId, stateName, data);
     }
 
+    private void syncState(int userId) {
+        mStorage.syncSyntheticPasswordState(userId);
+    }
+
     private void destroyState(String stateName, long protectorId, int userId) {
         mStorage.deleteSyntheticPasswordState(userId, protectorId, stateName);
     }
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index fdc5bab..497ed03 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -41,7 +42,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ILogAccessDialogCallback;
-import com.android.internal.app.LogAccessDialogActivity;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -62,6 +62,10 @@
 public final class LogcatManagerService extends SystemService {
     private static final String TAG = "LogcatManagerService";
     private static final boolean DEBUG = false;
+    private static final String TARGET_PACKAGE_NAME = "com.android.systemui";
+    private static final String TARGET_ACTIVITY_NAME =
+            "com.android.systemui.logcat.LogAccessDialogActivity";
+    public static final String EXTRA_CALLBACK = "EXTRA_CALLBACK";
 
     /** How long to wait for the user to approve/decline before declining automatically */
     @VisibleForTesting
@@ -442,6 +446,7 @@
                 mClock.get() + PENDING_CONFIRMATION_TIMEOUT_MILLIS);
         final Intent mIntent = createIntent(client);
         mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mIntent.setComponent(new ComponentName(TARGET_PACKAGE_NAME, TARGET_ACTIVITY_NAME));
         mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM);
     }
 
@@ -536,13 +541,13 @@
      * Create the Intent for LogAccessDialogActivity.
      */
     public Intent createIntent(LogAccessClient client) {
-        final Intent intent = new Intent(mContext, LogAccessDialogActivity.class);
+        final Intent intent = new Intent();
 
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
 
         intent.putExtra(Intent.EXTRA_PACKAGE_NAME, client.mPackageName);
         intent.putExtra(Intent.EXTRA_UID, client.mUid);
-        intent.putExtra(LogAccessDialogActivity.EXTRA_CALLBACK, mDialogCallback.asBinder());
+        intent.putExtra(EXTRA_CALLBACK, mDialogCallback.asBinder());
 
         return intent;
     }
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index dcdb881..72ce38b 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -275,6 +275,10 @@
                 String.valueOf(mComponentType));
     }
 
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
     @ComponentType
     private static int getComponentType(PendingIntent pendingIntent) {
         if (pendingIntent.isBroadcast()) {
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 439e9bd..d6846be 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -41,6 +41,7 @@
 import android.media.MediaRoute2ProviderService;
 import android.media.MediaRouter2Manager;
 import android.media.RouteDiscoveryPreference;
+import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Binder;
 import android.os.Bundle;
@@ -131,8 +132,6 @@
                             UserHandler::updateDiscoveryPreferenceOnHandler, userHandler));
                 }
             }
-
-            mEventLogger.enqueue(new EventLogger.StringEvent("mScreenOnOffReceiver", null));
         }
     };
 
@@ -151,7 +150,7 @@
         mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
     }
 
-    // Methods that implement MediaRouter2 operations.
+    // Start of methods that implement MediaRouter2 operations.
 
     @NonNull
     public void enforceMediaContentControlPermission() {
@@ -199,46 +198,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)) {
@@ -299,6 +258,24 @@
         }
     }
 
+    public void setRouteListingPreference(
+            @NonNull IMediaRouter2 router,
+            @Nullable RouteListingPreference routeListingPreference) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                RouterRecord routerRecord = mAllRouterRecords.get(router.asBinder());
+                if (routerRecord == null) {
+                    Slog.w(TAG, "Ignoring updating route listing of null routerRecord.");
+                    return;
+                }
+                setRouteListingPreferenceLocked(routerRecord, routeListingPreference);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     public void setRouteVolumeWithRouter2(@NonNull IMediaRouter2 router,
             @NonNull MediaRoute2Info route, int volume) {
         Objects.requireNonNull(router, "router must not be null");
@@ -390,6 +367,9 @@
             @NonNull String uniqueSessionId, int volume) {
         Objects.requireNonNull(router, "router must not be null");
         Objects.requireNonNull(uniqueSessionId, "uniqueSessionId must not be null");
+        if (TextUtils.isEmpty(uniqueSessionId)) {
+            throw new IllegalArgumentException("uniqueSessionId must not be empty");
+        }
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -418,7 +398,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 +592,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");
 
@@ -639,9 +667,11 @@
                                 "userId: %d", newActiveUserId));
 
                 mCurrentActiveUserId = newActiveUserId;
-                for (int i = 0; i < mUserRecords.size(); i++) {
-                    int userId = mUserRecords.keyAt(i);
-                    UserRecord userRecord = mUserRecords.valueAt(i);
+                // disposeUserIfNeededLocked might modify the collection, hence clone
+                final var userRecords = mUserRecords.clone();
+                for (int i = 0; i < userRecords.size(); i++) {
+                    int userId = userRecords.keyAt(i);
+                    UserRecord userRecord = userRecords.valueAt(i);
                     if (isUserActiveLocked(userId)) {
                         // userId corresponds to the active user, or one of its profiles. We
                         // ensure the associated structures are initialized.
@@ -678,6 +708,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,
@@ -760,6 +792,31 @@
                         routerRecord.mUserRecord.mHandler));
     }
 
+    @GuardedBy("mLock")
+    private void setRouteListingPreferenceLocked(
+            RouterRecord routerRecord, @Nullable RouteListingPreference routeListingPreference) {
+        routerRecord.mRouteListingPreference = routeListingPreference;
+        String routeListingAsString =
+                routeListingPreference != null
+                        ? routeListingPreference.getItems().stream()
+                                .map(RouteListingPreference.Item::getRouteId)
+                                .collect(Collectors.joining(","))
+                        : null;
+        mEventLogger.enqueue(
+                EventLogger.StringEvent.from(
+                        "setRouteListingPreference",
+                        "router id: %d, route listing preference: [%s]",
+                        routerRecord.mRouterId,
+                        routeListingAsString));
+
+        routerRecord.mUserRecord.mHandler.sendMessage(
+                obtainMessage(
+                        UserHandler::notifyRouteListingPreferenceChangeToManagers,
+                        routerRecord.mUserRecord.mHandler,
+                        routerRecord.mPackageName,
+                        routeListingPreference));
+    }
+
     private void setRouteVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
             @NonNull MediaRoute2Info route, int volume) {
         final IBinder binder = router.asBinder();
@@ -952,6 +1009,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();
@@ -1006,6 +1067,15 @@
         // RouteCallback#onRoutesAdded() for system MR2 will never be called with initial routes
         // due to the lack of features.
         for (RouterRecord routerRecord : userRecord.mRouterRecords) {
+            // Send route listing preferences before discovery preferences and routes to avoid an
+            // inconsistent state where there are routes to show, but the manager thinks
+            // the app has not expressed a preference for listing.
+            userRecord.mHandler.sendMessage(
+                    obtainMessage(
+                            UserHandler::notifyRouteListingPreferenceChangeToManagers,
+                            routerRecord.mUserRecord.mHandler,
+                            routerRecord.mPackageName,
+                            routerRecord.mRouteListingPreference));
             // TODO: UserRecord <-> routerRecord, why do they reference each other?
             // How about removing mUserRecord from routerRecord?
             routerRecord.mUserRecord.mHandler.sendMessage(
@@ -1260,6 +1330,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 +1368,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;
     }
@@ -1379,6 +1455,7 @@
         public final int mRouterId;
 
         public RouteDiscoveryPreference mDiscoveryPreference;
+        @Nullable public RouteListingPreference mRouteListingPreference;
 
         RouterRecord(UserRecord userRecord, IMediaRouter2 router, int uid, int pid,
                 String packageName, boolean hasConfigureWifiDisplayPermission,
@@ -1667,6 +1744,9 @@
                     indexOfRouteProviderInfoByUniqueId(provider.getUniqueId(), mLastProviderInfos);
             MediaRoute2ProviderInfo oldInfo =
                     providerInfoIndex == -1 ? null : mLastProviderInfos.get(providerInfoIndex);
+            MediaRouter2ServiceImpl mediaRouter2Service = mServiceRef.get();
+            EventLogger eventLogger =
+                    mediaRouter2Service != null ? mediaRouter2Service.mEventLogger : null;
             if (oldInfo == newInfo) {
                 // Nothing to do.
                 return;
@@ -1692,6 +1772,7 @@
             }
 
             // Add new routes to the maps.
+            ArrayList<MediaRoute2Info> addedRoutes = new ArrayList<>();
             boolean hasAddedOrModifiedRoutes = false;
             for (MediaRoute2Info newRouteInfo : newRoutes) {
                 if (!newRouteInfo.isValid()) {
@@ -1706,11 +1787,14 @@
                 MediaRoute2Info oldRouteInfo =
                         mLastNotifiedRoutesToPrivilegedRouters.put(
                                 newRouteInfo.getId(), newRouteInfo);
-                hasAddedOrModifiedRoutes |=
-                        oldRouteInfo == null || !oldRouteInfo.equals(newRouteInfo);
+                hasAddedOrModifiedRoutes |= !newRouteInfo.equals(oldRouteInfo);
+                if (oldRouteInfo == null) {
+                    addedRoutes.add(newRouteInfo);
+                }
             }
 
             // Remove stale routes from the maps.
+            ArrayList<MediaRoute2Info> removedRoutes = new ArrayList<>();
             Collection<MediaRoute2Info> oldRoutes =
                     oldInfo == null ? Collections.emptyList() : oldInfo.getRoutes();
             boolean hasRemovedRoutes = false;
@@ -1720,6 +1804,26 @@
                     hasRemovedRoutes = true;
                     mLastNotifiedRoutesToPrivilegedRouters.remove(oldRouteId);
                     mLastNotifiedRoutesToNonPrivilegedRouters.remove(oldRouteId);
+                    removedRoutes.add(oldRoute);
+                }
+            }
+
+            if (eventLogger != null) {
+                if (!addedRoutes.isEmpty()) {
+                    // If routes were added, newInfo cannot be null.
+                    eventLogger.enqueue(
+                            toLoggingEvent(
+                                    /* source= */ "addProviderRoutes",
+                                    newInfo.getUniqueId(),
+                                    addedRoutes));
+                }
+                if (!removedRoutes.isEmpty()) {
+                    // If routes were removed, oldInfo cannot be null.
+                    eventLogger.enqueue(
+                            toLoggingEvent(
+                                    /* source= */ "removeProviderRoutes",
+                                    oldInfo.getUniqueId(),
+                                    removedRoutes));
                 }
             }
 
@@ -1730,6 +1834,16 @@
                     mSystemProvider.getDefaultRoute());
         }
 
+        private static EventLogger.Event toLoggingEvent(
+                String source, String providerId, ArrayList<MediaRoute2Info> routes) {
+            String routesString =
+                    routes.stream()
+                            .map(it -> String.format("%s | %s", it.getOriginalId(), it.getName()))
+                            .collect(Collectors.joining(/* delimiter= */ ", "));
+            return EventLogger.StringEvent.from(
+                    source, "provider: %s, routes: [%s]", providerId, routesString);
+        }
+
         /**
          * Dispatches the latest route updates in {@link #mLastNotifiedRoutesToPrivilegedRouters}
          * and {@link #mLastNotifiedRoutesToNonPrivilegedRouters} to registered {@link
@@ -2406,6 +2520,34 @@
             }
         }
 
+        private void notifyRouteListingPreferenceChangeToManagers(
+                String routerPackageName, @Nullable RouteListingPreference routeListingPreference) {
+            MediaRouter2ServiceImpl service = mServiceRef.get();
+            if (service == null) {
+                return;
+            }
+            List<IMediaRouter2Manager> managers = new ArrayList<>();
+            synchronized (service.mLock) {
+                for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
+                    managers.add(managerRecord.mManager);
+                }
+            }
+            for (IMediaRouter2Manager manager : managers) {
+                try {
+                    manager.notifyRouteListingPreferenceChange(
+                            routerPackageName, routeListingPreference);
+                } catch (RemoteException ex) {
+                    Slog.w(
+                            TAG,
+                            "Failed to notify preferred features changed."
+                                    + " Manager probably died.",
+                            ex);
+                }
+            }
+            // TODO(b/238178508): In order to support privileged media router instances, we also
+            //    need to update routers other than the one making the update.
+        }
+
         private void notifyRequestFailedToManager(@NonNull IMediaRouter2Manager manager,
                 int requestId, int reason) {
             try {
@@ -2425,10 +2567,9 @@
             List<RouterRecord> routerRecords = getRouterRecords();
             List<ManagerRecord> managerRecords = getManagerRecords();
 
-            boolean shouldBindProviders = false;
-
+            boolean isManagerScanning = false;
             if (service.mPowerManager.isInteractive()) {
-                boolean isManagerScanning = managerRecords.stream().anyMatch(manager ->
+                isManagerScanning = managerRecords.stream().anyMatch(manager ->
                         manager.mIsScanning && service.mActivityManager
                                 .getPackageImportance(manager.mPackageName)
                                 <= PACKAGE_IMPORTANCE_FOR_DISCOVERY);
@@ -2437,7 +2578,6 @@
                     discoveryPreferences = routerRecords.stream()
                             .map(record -> record.mDiscoveryPreference)
                             .collect(Collectors.toList());
-                    shouldBindProviders = true;
                 } else {
                     discoveryPreferences = routerRecords.stream().filter(record ->
                             service.mActivityManager.getPackageImportance(record.mPackageName)
@@ -2450,7 +2590,7 @@
             for (MediaRoute2Provider provider : mRouteProviders) {
                 if (provider instanceof MediaRoute2ProviderServiceProxy) {
                     ((MediaRoute2ProviderServiceProxy) provider)
-                            .setManagerScanning(shouldBindProviders);
+                            .setManagerScanning(isManagerScanning);
                 }
             }
 
@@ -2466,7 +2606,7 @@
                 activeScan |= preference.shouldPerformActiveScan();
             }
             RouteDiscoveryPreference newPreference = new RouteDiscoveryPreference.Builder(
-                    List.copyOf(preferredFeatures), activeScan).build();
+                    List.copyOf(preferredFeatures), activeScan || isManagerScanning).build();
 
             synchronized (service.mLock) {
                 if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)) {
@@ -2487,7 +2627,6 @@
             }
             return null;
         }
-
     }
     static final class SessionCreationRequest {
         public final RouterRecord mRouterRecord;
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 0f6192a..beab5ea 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -17,6 +17,7 @@
 package com.android.server.media;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.app.UserSwitchObserver;
@@ -43,6 +44,7 @@
 import android.media.RemoteDisplayState;
 import android.media.RemoteDisplayState.RemoteDisplayInfo;
 import android.media.RouteDiscoveryPreference;
+import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Binder;
 import android.os.Bundle;
@@ -189,10 +191,6 @@
     // Binder call
     @Override
     public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) {
-        if (client == null) {
-            throw new IllegalArgumentException("client must not be null");
-        }
-
         final int uid = Binder.getCallingUid();
         if (!validatePackageName(uid, packageName)) {
             throw new SecurityException("packageName must match the calling uid");
@@ -217,9 +215,6 @@
     // Binder call
     @Override
     public void registerClientGroupId(IMediaRouterClient client, String groupId) {
-        if (client == null) {
-            throw new NullPointerException("client must not be null");
-        }
         if (mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -240,10 +235,6 @@
     // Binder call
     @Override
     public void unregisterClient(IMediaRouterClient client) {
-        if (client == null) {
-            throw new IllegalArgumentException("client must not be null");
-        }
-
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
@@ -257,10 +248,6 @@
     // Binder call
     @Override
     public MediaRouterClientState getState(IMediaRouterClient client) {
-        if (client == null) {
-            throw new IllegalArgumentException("client must not be null");
-        }
-
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
@@ -274,10 +261,6 @@
     // Binder call
     @Override
     public boolean isPlaybackActive(IMediaRouterClient client) {
-        if (client == null) {
-            throw new IllegalArgumentException("client must not be null");
-        }
-
         final long token = Binder.clearCallingIdentity();
         try {
             ClientRecord clientRecord;
@@ -314,10 +297,6 @@
     @Override
     public void setDiscoveryRequest(IMediaRouterClient client,
             int routeTypes, boolean activeScan) {
-        if (client == null) {
-            throw new IllegalArgumentException("client must not be null");
-        }
-
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
@@ -336,10 +315,6 @@
     // selected route or a default selection.
     @Override
     public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) {
-        if (client == null) {
-            throw new IllegalArgumentException("client must not be null");
-        }
-
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
@@ -353,12 +328,7 @@
     // Binder call
     @Override
     public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) {
-        if (client == null) {
-            throw new IllegalArgumentException("client must not be null");
-        }
-        if (routeId == null) {
-            throw new IllegalArgumentException("routeId must not be null");
-        }
+        Objects.requireNonNull(routeId, "routeId must not be null");
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -373,12 +343,7 @@
     // Binder call
     @Override
     public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) {
-        if (client == null) {
-            throw new IllegalArgumentException("client must not be null");
-        }
-        if (routeId == null) {
-            throw new IllegalArgumentException("routeId must not be null");
-        }
+        Objects.requireNonNull(routeId, "routeId must not be null");
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -457,6 +422,14 @@
 
     // Binder call
     @Override
+    public void setRouteListingPreference(
+            @NonNull IMediaRouter2 router,
+            @Nullable RouteListingPreference routeListingPreference) {
+        mService2.setRouteListingPreference(router, routeListingPreference);
+    }
+
+    // Binder call
+    @Override
     public void setRouteVolumeWithRouter2(IMediaRouter2 router,
             MediaRoute2Info route, int volume) {
         mService2.setRouteVolumeWithRouter2(router, route, volume);
@@ -665,9 +638,11 @@
         synchronized (mLock) {
             if (mCurrentActiveUserId != newActiveUserId) {
                 mCurrentActiveUserId = newActiveUserId;
-                for (int i = 0; i < mUserRecords.size(); i++) {
-                    int userId = mUserRecords.keyAt(i);
-                    UserRecord userRecord = mUserRecords.valueAt(i);
+                // disposeUserIfNeededLocked might modify the collection, hence clone
+                final var userRecords = mUserRecords.clone();
+                for (int i = 0; i < userRecords.size(); i++) {
+                    int userId = userRecords.keyAt(i);
+                    UserRecord userRecord = userRecords.valueAt(i);
                     if (isUserActiveLocked(userId)) {
                         // userId corresponds to the active user, or one of its profiles. We
                         // ensure the associated structures are initialized.
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 337d5e5..f2a39b8 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -24,6 +24,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.server.utils.EventLogger;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -38,6 +40,8 @@
     private static final boolean DEBUG = MediaSessionService.DEBUG;
     private static final String TAG = "MediaSessionStack";
 
+    private static final int DUMP_EVENTS_MAX_COUNT = 70;
+
     /**
      * Listen the change in the media button session.
      */
@@ -57,6 +61,8 @@
     private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
     private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
 
+    private final EventLogger mEventLogger = new EventLogger(DUMP_EVENTS_MAX_COUNT, TAG);
+
     /**
      * The media button session which receives media key events.
      * It could be null if the previous media button session is released.
@@ -80,6 +86,11 @@
      * @param record The record to add.
      */
     public void addSession(MediaSessionRecordImpl record) {
+        mEventLogger.enqueue(EventLogger.StringEvent.from(
+                "addSession() (to bottom of stack)",
+                "record: %s",
+                record
+        ));
         mSessions.add(record);
         clearCache(record.getUserId());
 
@@ -95,6 +106,11 @@
      * @param record The record to remove.
      */
     public void removeSession(MediaSessionRecordImpl record) {
+        mEventLogger.enqueue(EventLogger.StringEvent.from(
+                "removeSession()",
+                "record: %s",
+                record
+        ));
         mSessions.remove(record);
         if (mMediaButtonSession == record) {
             // When the media button session is removed, nullify the media button session and do not
@@ -140,6 +156,11 @@
     public void onPlaybackStateChanged(
             MediaSessionRecordImpl record, boolean shouldUpdatePriority) {
         if (shouldUpdatePriority) {
+            mEventLogger.enqueue(EventLogger.StringEvent.from(
+                    "onPlaybackStateChanged() - Pushing session to top",
+                    "record: %s",
+                    record
+            ));
             mSessions.remove(record);
             mSessions.add(0, record);
             clearCache(record.getUserId());
@@ -344,6 +365,8 @@
         for (MediaSessionRecordImpl record : mSessions) {
             record.dump(pw, indent);
         }
+        pw.println(prefix + "Session stack events:");
+        mEventLogger.dump(pw, indent);
     }
 
     /**
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index eb37ceb..90135ad 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3422,6 +3422,8 @@
         @Override
         public void setToastRateLimitingEnabled(boolean enable) {
 
+            super.setToastRateLimitingEnabled_enforcePermission();
+
             synchronized (mToastQueue) {
                 int uid = Binder.getCallingUid();
                 int userId = UserHandle.getUserId(uid);
@@ -4965,10 +4967,10 @@
             }
             enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
 
-            // If the caller is system, take the package name from the rule's owner rather than
-            // from the caller's package.
+            // If the calling app is the system (from any user), take the package name from the
+            // rule's owner rather than from the caller's package.
             String rulePkg = pkg;
-            if (isCallingUidSystem()) {
+            if (isCallingAppIdSystem()) {
                 if (automaticZenRule.getOwner() != null) {
                     rulePkg = automaticZenRule.getOwner().getPackageName();
                 }
@@ -9771,6 +9773,12 @@
         return uid == Process.SYSTEM_UID;
     }
 
+    protected boolean isCallingAppIdSystem() {
+        final int uid = Binder.getCallingUid();
+        final int appid = UserHandle.getAppId(uid);
+        return appid == Process.SYSTEM_UID;
+    }
+
     protected boolean isUidSystemOrPhone(int uid) {
         final int appid = UserHandle.getAppId(uid);
         return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 12324bf..e6fd7ec 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -261,9 +261,11 @@
     private boolean packageRequestsNotificationPermission(String packageName,
             @UserIdInt int userId) {
         try {
-            String[] permissions = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS,
-                    userId).requestedPermissions;
-            return ArrayUtils.contains(permissions, NOTIFICATION_PERMISSION);
+            PackageInfo pi = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, userId);
+            if (pi != null) {
+                String[] permissions = pi.requestedPermissions;
+                return ArrayUtils.contains(permissions, NOTIFICATION_PERMISSION);
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Could not reach system server", e);
         }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index bbbf452..1bbcc83 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -852,7 +852,9 @@
         Objects.requireNonNull(pkg);
         Objects.requireNonNull(group);
         Objects.requireNonNull(group.getId());
-        Objects.requireNonNull(!TextUtils.isEmpty(group.getName()));
+        if (TextUtils.isEmpty(group.getName())) {
+            throw new IllegalArgumentException("group.getName() can't be empty");
+        }
         boolean needsDndChange = false;
         synchronized (mPackagePreferences) {
             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 4bbd40d..5f8572b 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -59,6 +59,9 @@
 
     static final int CONCURRENT_SNOOZE_LIMIT = 500;
 
+    // A safe size for strings to be put in persistent storage, to avoid breaking the XML write.
+    static final int MAX_STRING_LENGTH = 1000;
+
     protected static final String XML_TAG_NAME = "snoozed-notifications";
 
     private static final String XML_SNOOZED_NOTIFICATION = "notification";
@@ -200,7 +203,7 @@
         scheduleRepost(key, duration);
         Long activateAt = System.currentTimeMillis() + duration;
         synchronized (mLock) {
-            mPersistedSnoozedNotifications.put(key, activateAt);
+            mPersistedSnoozedNotifications.put(getTrimmedString(key), activateAt);
         }
     }
 
@@ -210,7 +213,10 @@
     protected void snooze(NotificationRecord record, String contextId) {
         if (contextId != null) {
             synchronized (mLock) {
-                mPersistedSnoozedNotificationsWithContext.put(record.getKey(), contextId);
+                mPersistedSnoozedNotificationsWithContext.put(
+                        getTrimmedString(record.getKey()),
+                        getTrimmedString(contextId)
+                );
             }
         }
         snooze(record);
@@ -225,6 +231,13 @@
         }
     }
 
+    private String getTrimmedString(String key) {
+        if (key != null && key.length() > MAX_STRING_LENGTH) {
+            return key.substring(0, MAX_STRING_LENGTH);
+        }
+        return key;
+    }
+
     protected boolean cancel(int userId, String pkg, String tag, int id) {
         synchronized (mLock) {
             final Set<Map.Entry<String, NotificationRecord>> records =
@@ -293,10 +306,12 @@
     }
 
     protected void repost(String key, int userId, boolean muteOnReturn) {
+        final String trimmedKey = getTrimmedString(key);
+
         NotificationRecord record;
         synchronized (mLock) {
-            mPersistedSnoozedNotifications.remove(key);
-            mPersistedSnoozedNotificationsWithContext.remove(key);
+            mPersistedSnoozedNotifications.remove(trimmedKey);
+            mPersistedSnoozedNotificationsWithContext.remove(trimmedKey);
             record = mSnoozedNotifications.remove(key);
         }
 
diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java
index 6735d55..4c6110b 100644
--- a/services/core/java/com/android/server/oemlock/OemLockService.java
+++ b/services/core/java/com/android/server/oemlock/OemLockService.java
@@ -25,7 +25,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
-import android.hardware.oemlock.V1_0.IOemLock;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -58,15 +57,18 @@
     private OemLock mOemLock;
 
     public static boolean isHalPresent() {
-        return VendorLock.getOemLockHalService() != null;
+        return (VendorLockHidl.getOemLockHalService() != null)
+                || (VendorLockAidl.getOemLockHalService() != null);
     }
 
     /** Select the OEM lock implementation */
     private static OemLock getOemLock(Context context) {
-        final IOemLock oemLockHal = VendorLock.getOemLockHalService();
-        if (oemLockHal != null) {
-            Slog.i(TAG, "Using vendor lock via the HAL");
-            return new VendorLock(context, oemLockHal);
+        if (VendorLockAidl.getOemLockHalService() != null) {
+            Slog.i(TAG, "Using vendor lock via the HAL(aidl)");
+            return new VendorLockAidl(context);
+        } else if (VendorLockHidl.getOemLockHalService() != null) {
+            Slog.i(TAG, "Using vendor lock via the HAL(hidl)");
+            return new VendorLockHidl(context);
         } else {
             Slog.i(TAG, "Using persistent data block based lock");
             return new PersistentDataBlockLock(context);
@@ -120,6 +122,8 @@
         @Nullable
         @EnforcePermission(MANAGE_CARRIER_OEM_UNLOCK_STATE)
         public String getLockName() {
+            super.getLockName_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
                 return mOemLock.getLockName();
@@ -131,6 +135,8 @@
         @Override
         @EnforcePermission(MANAGE_CARRIER_OEM_UNLOCK_STATE)
         public void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) {
+            super.setOemUnlockAllowedByCarrier_enforcePermission();
+
             enforceUserIsAdmin();
 
             final long token = Binder.clearCallingIdentity();
@@ -144,6 +150,8 @@
         @Override
         @EnforcePermission(MANAGE_CARRIER_OEM_UNLOCK_STATE)
         public boolean isOemUnlockAllowedByCarrier() {
+            super.isOemUnlockAllowedByCarrier_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
               return mOemLock.isOemUnlockAllowedByCarrier();
@@ -157,6 +165,8 @@
         @Override
         @EnforcePermission(MANAGE_USER_OEM_UNLOCK_STATE)
         public void setOemUnlockAllowedByUser(boolean allowedByUser) {
+            super.setOemUnlockAllowedByUser_enforcePermission();
+
             if (ActivityManager.isUserAMonkey()) {
                 // Prevent a monkey from changing this
                 return;
@@ -183,6 +193,8 @@
         @Override
         @EnforcePermission(MANAGE_USER_OEM_UNLOCK_STATE)
         public boolean isOemUnlockAllowedByUser() {
+            super.isOemUnlockAllowedByUser_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
                 return mOemLock.isOemUnlockAllowedByDevice();
@@ -199,6 +211,8 @@
         @Override
         @EnforcePermission(anyOf = {READ_OEM_UNLOCK_STATE, OEM_UNLOCK_STATE})
         public boolean isOemUnlockAllowed() {
+            super.isOemUnlockAllowed_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
                 boolean allowed = mOemLock.isOemUnlockAllowedByCarrier()
@@ -213,6 +227,8 @@
         @Override
         @EnforcePermission(anyOf = {READ_OEM_UNLOCK_STATE, OEM_UNLOCK_STATE})
         public boolean isDeviceOemUnlocked() {
+            super.isDeviceOemUnlocked_enforcePermission();
+
             String locked = SystemProperties.get(FLASH_LOCK_PROP);
             switch (locked) {
                 case FLASH_LOCK_UNLOCKED:
diff --git a/services/core/java/com/android/server/oemlock/VendorLock.java b/services/core/java/com/android/server/oemlock/VendorLock.java
deleted file mode 100644
index 9c876da..0000000
--- a/services/core/java/com/android/server/oemlock/VendorLock.java
+++ /dev/null
@@ -1,200 +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.server.oemlock;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.oemlock.V1_0.IOemLock;
-import android.hardware.oemlock.V1_0.OemLockSecureStatus;
-import android.hardware.oemlock.V1_0.OemLockStatus;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import java.util.ArrayList;
-import java.util.NoSuchElementException;
-
-/**
- * Uses the OEM lock HAL.
- */
-class VendorLock extends OemLock {
-    private static final String TAG = "OemLock";
-
-    private Context mContext;
-    private IOemLock mOemLock;
-
-    static IOemLock getOemLockHalService() {
-        try {
-            return IOemLock.getService(/* retry */ true);
-        } catch (NoSuchElementException e) {
-            Slog.i(TAG, "OemLock HAL not present on device");
-            return null;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    VendorLock(Context context, IOemLock oemLock) {
-        mContext = context;
-        mOemLock = oemLock;
-    }
-
-    @Override
-    @Nullable
-    String getLockName() {
-        final Integer[] requestStatus = new Integer[1];
-        final String[] lockName = new String[1];
-
-        try {
-            mOemLock.getName((status, name) -> {
-                requestStatus[0] = status;
-                lockName[0] = name;
-            });
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to get name from HAL", e);
-            throw e.rethrowFromSystemServer();
-        }
-
-        switch (requestStatus[0]) {
-            case OemLockStatus.OK:
-                // Success
-                return lockName[0];
-
-            case OemLockStatus.FAILED:
-                Slog.e(TAG, "Failed to get OEM lock name.");
-                return null;
-
-            default:
-                Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
-                return null;
-        }
-    }
-
-    @Override
-    void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) {
-        try {
-            ArrayList<Byte> signatureBytes = toByteArrayList(signature);
-            switch (mOemLock.setOemUnlockAllowedByCarrier(allowed, signatureBytes)) {
-                case OemLockSecureStatus.OK:
-                    Slog.i(TAG, "Updated carrier allows OEM lock state to: " + allowed);
-                    return;
-
-                case OemLockSecureStatus.INVALID_SIGNATURE:
-                    if (signatureBytes.isEmpty()) {
-                        throw new IllegalArgumentException("Signature required for carrier unlock");
-                    }
-                    throw new SecurityException(
-                            "Invalid signature used in attempt to carrier unlock");
-
-                default:
-                    Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
-                    // Fallthrough
-                case OemLockSecureStatus.FAILED:
-                    throw new RuntimeException("Failed to set carrier OEM unlock state");
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to set carrier state with HAL", e);
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @Override
-    boolean isOemUnlockAllowedByCarrier() {
-        final Integer[] requestStatus = new Integer[1];
-        final Boolean[] allowedByCarrier = new Boolean[1];
-
-        try {
-            mOemLock.isOemUnlockAllowedByCarrier((status, allowed) -> {
-                requestStatus[0] = status;
-                allowedByCarrier[0] = allowed;
-            });
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to get carrier state from HAL");
-            throw e.rethrowFromSystemServer();
-        }
-
-        switch (requestStatus[0]) {
-            case OemLockStatus.OK:
-                // Success
-                return allowedByCarrier[0];
-
-            default:
-                Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
-                // Fallthrough
-            case OemLockStatus.FAILED:
-                throw new RuntimeException("Failed to get carrier OEM unlock state");
-        }
-    }
-
-    @Override
-    void setOemUnlockAllowedByDevice(boolean allowedByDevice) {
-        try {
-            switch (mOemLock.setOemUnlockAllowedByDevice(allowedByDevice)) {
-                case OemLockSecureStatus.OK:
-                    Slog.i(TAG, "Updated device allows OEM lock state to: " + allowedByDevice);
-                    return;
-
-                default:
-                    Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
-                    // Fallthrough
-                case OemLockSecureStatus.FAILED:
-                    throw new RuntimeException("Failed to set device OEM unlock state");
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to set device state with HAL", e);
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @Override
-    boolean isOemUnlockAllowedByDevice() {
-        final Integer[] requestStatus = new Integer[1];
-        final Boolean[] allowedByDevice = new Boolean[1];
-
-        try {
-            mOemLock.isOemUnlockAllowedByDevice((status, allowed) -> {
-                requestStatus[0] = status;
-                allowedByDevice[0] = allowed;
-            });
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to get devie state from HAL");
-            throw e.rethrowFromSystemServer();
-        }
-
-        switch (requestStatus[0]) {
-            case OemLockStatus.OK:
-                // Success
-                return allowedByDevice[0];
-
-            default:
-                Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
-                // Fallthrough
-            case OemLockStatus.FAILED:
-                throw new RuntimeException("Failed to get device OEM unlock state");
-        }
-    }
-
-    private ArrayList<Byte> toByteArrayList(byte[] data) {
-        if (data == null) {
-            return new ArrayList<Byte>();
-        }
-        ArrayList<Byte> result = new ArrayList<Byte>(data.length);
-        for (final byte b : data) {
-            result.add(b);
-        }
-        return result;
-    }
-}
diff --git a/services/core/java/com/android/server/oemlock/VendorLockAidl.java b/services/core/java/com/android/server/oemlock/VendorLockAidl.java
new file mode 100644
index 0000000..82d45ab
--- /dev/null
+++ b/services/core/java/com/android/server/oemlock/VendorLockAidl.java
@@ -0,0 +1,115 @@
+/*
+ * 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.oemlock;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.oemlock.IOemLock;
+import android.hardware.oemlock.OemLockSecureStatus;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+/** Uses the OEM lock HAL. */
+class VendorLockAidl extends OemLock {
+    private static final String TAG = "OemLock";
+    private IOemLock mOemLock;
+
+    static IOemLock getOemLockHalService() {
+        return IOemLock.Stub.asInterface(
+                ServiceManager.waitForDeclaredService(IOemLock.DESCRIPTOR + "/default"));
+    }
+
+    VendorLockAidl(Context context) {
+        mOemLock = getOemLockHalService();
+    }
+
+    @Override
+    @Nullable
+    String getLockName() {
+        try {
+            return mOemLock.getName();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get name from HAL", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) {
+        try {
+            final int status;
+            if (signature == null) {
+                status = mOemLock.setOemUnlockAllowedByCarrier(allowed, new byte[0]);
+            } else {
+                status = mOemLock.setOemUnlockAllowedByCarrier(allowed, signature);
+            }
+            switch (status) {
+                case OemLockSecureStatus.OK:
+                    Slog.i(TAG, "Updated carrier allows OEM lock state to: " + allowed);
+                    return;
+
+                case OemLockSecureStatus.INVALID_SIGNATURE:
+                    if (signature == null) {
+                        throw new IllegalArgumentException("Signature required for carrier unlock");
+                    }
+                    throw new SecurityException(
+                            "Invalid signature used in attempt to carrier unlock");
+
+                default:
+                    Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
+                    // Fallthrough
+                case OemLockSecureStatus.FAILED:
+                    throw new RuntimeException("Failed to set carrier OEM unlock state");
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to set carrier state with HAL", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    boolean isOemUnlockAllowedByCarrier() {
+        try {
+            return mOemLock.isOemUnlockAllowedByCarrier();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get carrier state from HAL");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    void setOemUnlockAllowedByDevice(boolean allowedByDevice) {
+        try {
+            mOemLock.setOemUnlockAllowedByDevice(allowedByDevice);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to set device state with HAL", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    boolean isOemUnlockAllowedByDevice() {
+
+        try {
+            return mOemLock.isOemUnlockAllowedByDevice();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get devie state from HAL");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/oemlock/VendorLockHidl.java b/services/core/java/com/android/server/oemlock/VendorLockHidl.java
new file mode 100644
index 0000000..fe76787
--- /dev/null
+++ b/services/core/java/com/android/server/oemlock/VendorLockHidl.java
@@ -0,0 +1,200 @@
+/*
+ * 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.server.oemlock;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.oemlock.V1_0.IOemLock;
+import android.hardware.oemlock.V1_0.OemLockSecureStatus;
+import android.hardware.oemlock.V1_0.OemLockStatus;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+
+/** Uses the OEM lock HAL. */
+class VendorLockHidl extends OemLock {
+    private static final String TAG = "OemLock";
+
+    private Context mContext;
+    private IOemLock mOemLock;
+
+    static IOemLock getOemLockHalService() {
+        try {
+            return IOemLock.getService(/* retry */ true);
+        } catch (NoSuchElementException e) {
+            Slog.i(TAG, "OemLock Hidl HAL not present on device");
+            return null;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    VendorLockHidl(Context context) {
+        mContext = context;
+        mOemLock = getOemLockHalService();
+    }
+
+    @Override
+    @Nullable
+    String getLockName() {
+        final String[] lockName = new String[1];
+        final Integer[] requestStatus = new Integer[1];
+
+        try {
+            mOemLock.getName(
+                    (status, name) -> {
+                        requestStatus[0] = status;
+                        lockName[0] = name;
+                    });
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get name from HAL", e);
+            throw e.rethrowFromSystemServer();
+        }
+
+        switch (requestStatus[0]) {
+            case OemLockStatus.OK:
+                // Success
+                return lockName[0];
+
+            case OemLockStatus.FAILED:
+                Slog.e(TAG, "Failed to get OEM lock name.");
+                return null;
+
+            default:
+                Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
+                return null;
+        }
+    }
+
+    @Override
+    void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) {
+        try {
+            ArrayList<Byte> signatureBytes = toByteArrayList(signature);
+            switch (mOemLock.setOemUnlockAllowedByCarrier(allowed, signatureBytes)) {
+                case OemLockSecureStatus.OK:
+                    Slog.i(TAG, "Updated carrier allows OEM lock state to: " + allowed);
+                    return;
+
+                case OemLockSecureStatus.INVALID_SIGNATURE:
+                    if (signatureBytes.isEmpty()) {
+                        throw new IllegalArgumentException("Signature required for carrier unlock");
+                    }
+                    throw new SecurityException(
+                            "Invalid signature used in attempt to carrier unlock");
+
+                default:
+                    Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
+                    // Fallthrough
+                case OemLockSecureStatus.FAILED:
+                    throw new RuntimeException("Failed to set carrier OEM unlock state");
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to set carrier state with HAL", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    boolean isOemUnlockAllowedByCarrier() {
+        final Boolean[] allowedByCarrier = new Boolean[1];
+        final Integer[] requestStatus = new Integer[1];
+        try {
+            mOemLock.isOemUnlockAllowedByCarrier(
+                    (status, allowed) -> {
+                        requestStatus[0] = status;
+                        allowedByCarrier[0] = allowed;
+                    });
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get carrier state from HAL");
+            throw e.rethrowFromSystemServer();
+        }
+
+        switch (requestStatus[0]) {
+            case OemLockStatus.OK:
+                // Success
+                return allowedByCarrier[0];
+
+            default:
+                Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
+                // Fallthrough
+            case OemLockStatus.FAILED:
+                throw new RuntimeException("Failed to get carrier OEM unlock state");
+        }
+    }
+
+    @Override
+    void setOemUnlockAllowedByDevice(boolean allowedByDevice) {
+        try {
+            switch (mOemLock.setOemUnlockAllowedByDevice(allowedByDevice)) {
+                case OemLockSecureStatus.OK:
+                    Slog.i(TAG, "Updated device allows OEM lock state to: " + allowedByDevice);
+                    return;
+
+                default:
+                    Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
+                    // Fallthrough
+                case OemLockSecureStatus.FAILED:
+                    throw new RuntimeException("Failed to set device OEM unlock state");
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to set device state with HAL", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    boolean isOemUnlockAllowedByDevice() {
+        final Boolean[] allowedByDevice = new Boolean[1];
+
+        final Integer[] requestStatus = new Integer[1];
+        try {
+            mOemLock.isOemUnlockAllowedByDevice(
+                    (status, allowed) -> {
+                        requestStatus[0] = status;
+                        allowedByDevice[0] = allowed;
+                    });
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get devie state from HAL");
+            throw e.rethrowFromSystemServer();
+        }
+
+        switch (requestStatus[0]) {
+            case OemLockStatus.OK:
+                // Success
+                return allowedByDevice[0];
+
+            default:
+                Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
+                // Fallthrough
+            case OemLockStatus.FAILED:
+                throw new RuntimeException("Failed to get device OEM unlock state");
+        }
+    }
+
+    private ArrayList<Byte> toByteArrayList(byte[] data) {
+        if (data == null) {
+            return new ArrayList<Byte>();
+        }
+        ArrayList<Byte> result = new ArrayList<Byte>(data.length);
+        for (final byte b : data) {
+            result.add(b);
+        }
+        return result;
+    }
+}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index baa471c..79f2b3f 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;
@@ -377,6 +379,8 @@
             final String packageName = data.getSchemeSpecificPart();
 
             final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+            final boolean systemUpdateUninstall =
+                    intent.getBooleanExtra(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, false);
 
             final int[] userIds;
             final int extraUid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
@@ -403,7 +407,7 @@
                     break;
                 case ACTION_PACKAGE_REMOVED:
                     if (replacing) {
-                        onPackageReplacing(packageName, userIds);
+                        onPackageReplacing(packageName, systemUpdateUninstall, userIds);
                     } else {
                         onPackageRemoved(packageName, userIds);
                     }
@@ -461,7 +465,7 @@
         }
 
         private void onPackageReplacing(@NonNull final String packageName,
-                @NonNull final int[] userIds) {
+                boolean systemUpdateUninstall, @NonNull final int[] userIds) {
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName);
                 for (int userId : userIds) {
@@ -470,8 +474,8 @@
                                 packageName, userId);
                         if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
                             try {
-                                updateTargetPackagesLocked(
-                                        mImpl.onPackageReplacing(packageName, userId));
+                                updateTargetPackagesLocked(mImpl.onPackageReplacing(packageName,
+                                        systemUpdateUninstall, userId));
                             } catch (OperationFailedException e) {
                                 Slog.e(TAG, "onPackageReplacing internal error", e);
                             }
@@ -880,14 +884,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 +936,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 +977,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 +1374,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 +1409,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 +1476,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..9d5830c 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -21,6 +21,7 @@
 import static android.content.om.OverlayInfo.STATE_MISSING_TARGET;
 import static android.content.om.OverlayInfo.STATE_NO_IDMAP;
 import static android.content.om.OverlayInfo.STATE_OVERLAY_IS_BEING_REPLACED;
+import static android.content.om.OverlayInfo.STATE_SYSTEM_UPDATE_UNINSTALL;
 import static android.content.om.OverlayInfo.STATE_TARGET_IS_BEING_REPLACED;
 import static android.os.UserHandle.USER_SYSTEM;
 
@@ -35,6 +36,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;
@@ -77,6 +79,7 @@
 
     // Flags to use in conjunction with updateState.
     private static final int FLAG_OVERLAY_IS_BEING_REPLACED = 1 << 1;
+    private static final int FLAG_SYSTEM_UPDATE_UNINSTALL = 1 << 2;
 
     private final PackageManagerHelper mPackageManager;
     private final IdmapManager mIdmapManager;
@@ -154,13 +157,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 +188,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 +240,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 +261,44 @@
     }
 
     @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)
-            throws OperationFailedException {
-        return reconcileSettingsForPackage(pkgName, userId, FLAG_OVERLAY_IS_BEING_REPLACED);
+    Set<UserPackage> onPackageReplacing(@NonNull final String pkgName,
+            boolean systemUpdateUninstall, final int userId) throws OperationFailedException {
+        int flags = FLAG_OVERLAY_IS_BEING_REPLACED;
+        if (systemUpdateUninstall) {
+            flags |= FLAG_SYSTEM_UPDATE_UNINSTALL;
+        }
+        return reconcileSettingsForPackage(pkgName, userId, flags);
     }
 
     @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 +306,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 +323,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 +339,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 +360,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 +374,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 +389,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 +430,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 +449,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 +457,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 +500,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 +509,7 @@
     }
 
     @NonNull
-    Set<PackageAndUser> registerFabricatedOverlay(
+    Set<UserPackage> registerFabricatedOverlay(
             @NonNull final FabricatedOverlayInternal overlay)
             throws OperationFailedException {
         if (FrameworkParsingPackageUtils.validateName(overlay.overlayName,
@@ -516,7 +523,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 +531,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 +550,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 +561,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 +571,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 +580,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 +588,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 +634,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 +651,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 +659,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 +674,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 +682,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 +697,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) {
@@ -839,6 +846,10 @@
             return STATE_OVERLAY_IS_BEING_REPLACED;
         }
 
+        if ((flags & FLAG_SYSTEM_UPDATE_UNINSTALL) != 0) {
+            return STATE_SYSTEM_UPDATE_UNINSTALL;
+        }
+
         if (targetPackage == null) {
             return STATE_MISSING_TARGET;
         }
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index 5e98cc0..978e436 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -29,6 +29,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Binder;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ShellCommand;
@@ -64,7 +65,8 @@
     private final IOverlayManager mInterface;
     private static final Map<String, Integer> TYPE_MAP = Map.of(
             "color", TypedValue.TYPE_FIRST_COLOR_INT,
-            "string", TypedValue.TYPE_STRING);
+            "string", TypedValue.TYPE_STRING,
+            "drawable", -1);
 
     OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) {
         mContext = ctx;
@@ -258,7 +260,7 @@
         String name = "";
         String filename = null;
         String opt;
-        String configuration = null;
+        String config = null;
         while ((opt = getNextOption()) != null) {
             switch (opt) {
                 case "--user":
@@ -277,7 +279,7 @@
                     filename = getNextArgRequired();
                     break;
                 case "--config":
-                    configuration = getNextArgRequired();
+                    config = getNextArgRequired();
                     break;
                 default:
                     err.println("Error: Unknown option: " + opt);
@@ -312,7 +314,9 @@
             final String resourceName = getNextArgRequired();
             final String typeStr = getNextArgRequired();
             final String strData = String.join(" ", peekRemainingArgs());
-            addOverlayValue(overlayBuilder, resourceName, typeStr, strData, configuration);
+            if (addOverlayValue(overlayBuilder, resourceName, typeStr, strData, config) != 0) {
+                return 1;
+            }
         }
 
         mInterface.commit(new OverlayManagerTransaction.Builder()
@@ -369,8 +373,10 @@
                             return 1;
                         }
                         String config = parser.getAttributeValue(null, "config");
-                        addOverlayValue(overlayBuilder, targetPackage + ':' + target,
-                                overlayType, value, config);
+                        if (addOverlayValue(overlayBuilder, targetPackage + ':' + target,
+                                  overlayType, value, config) != 0) {
+                            return 1;
+                        }
                     }
                 }
             }
@@ -384,7 +390,7 @@
         return 0;
     }
 
-    private void addOverlayValue(FabricatedOverlay.Builder overlayBuilder,
+    private int addOverlayValue(FabricatedOverlay.Builder overlayBuilder,
             String resourceName, String typeString, String valueString, String configuration) {
         final int type;
         typeString = typeString.toLowerCase(Locale.getDefault());
@@ -399,6 +405,9 @@
         }
         if (type == TypedValue.TYPE_STRING) {
             overlayBuilder.setResourceValue(resourceName, type, valueString, configuration);
+        } else if (type < 0) {
+            ParcelFileDescriptor pfd =  openFileForSystem(valueString, "r");
+            overlayBuilder.setResourceValue(resourceName, pfd, configuration);
         } else {
             final int intData;
             if (valueString.startsWith("0x")) {
@@ -408,6 +417,7 @@
             }
             overlayBuilder.setResourceValue(resourceName, type, intData, configuration);
         }
+        return 0;
     }
 
     private int runEnableExclusive() throws RemoteException {
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..b8bdabe 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;
@@ -213,7 +213,7 @@
 
         final int appId = UserHandle.getAppId(pkg.getUid());
 
-        String pkgSeInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
+        String pkgSeInfo = ps.getSeInfo();
 
         Preconditions.checkNotNull(pkgSeInfo);
 
diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java
new file mode 100644
index 0000000..e6e8212a
--- /dev/null
+++ b/services/core/java/com/android/server/pm/AppStateHelper.java
@@ -0,0 +1,219 @@
+/*
+ * 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.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
+import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.IAudioService;
+import android.os.ServiceManager;
+import android.telecom.TelecomManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper class to provide queries for app states concerning gentle-update.
+ */
+public class AppStateHelper {
+    private final Context mContext;
+
+    public AppStateHelper(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * True if the package is loaded into the process.
+     */
+    private static boolean isPackageLoaded(RunningAppProcessInfo info, String packageName) {
+        return ArrayUtils.contains(info.pkgList, packageName)
+                || ArrayUtils.contains(info.pkgDeps, packageName);
+    }
+
+    /**
+     * Returns the importance of the given package.
+     */
+    private int getImportance(String packageName) {
+        var am = mContext.getSystemService(ActivityManager.class);
+        return am.getPackageImportance(packageName);
+    }
+
+    /**
+     * True if the app owns the audio focus.
+     */
+    private boolean hasAudioFocus(String packageName) {
+        var audioService = IAudioService.Stub.asInterface(
+                ServiceManager.getService(Context.AUDIO_SERVICE));
+        try {
+            var focusInfos = audioService.getFocusStack();
+            int size = focusInfos.size();
+            var audioFocusPackage = (size > 0) ? focusInfos.get(size - 1).getPackageName() : null;
+            return TextUtils.equals(packageName, audioFocusPackage);
+        } catch (Exception ignore) {
+        }
+        return false;
+    }
+
+    /**
+     * True if any app is using voice communication.
+     */
+    private boolean hasVoiceCall() {
+        var am = mContext.getSystemService(AudioManager.class);
+        try {
+            for (var apc : am.getActivePlaybackConfigurations()) {
+                if (!apc.isActive()) {
+                    continue;
+                }
+                var usage = apc.getAudioAttributes().getUsage();
+                if (usage == USAGE_VOICE_COMMUNICATION
+                        || usage == USAGE_VOICE_COMMUNICATION_SIGNALLING) {
+                    return true;
+                }
+            }
+        } catch (Exception ignore) {
+        }
+        return false;
+    }
+
+    /**
+     * True if the app is recording audio.
+     */
+    private boolean isRecordingAudio(String packageName) {
+        var am = mContext.getSystemService(AudioManager.class);
+        try {
+            for (var arc : am.getActiveRecordingConfigurations()) {
+                if (TextUtils.equals(arc.getClientPackageName(), packageName)) {
+                    return true;
+                }
+            }
+        } catch (Exception ignore) {
+        }
+        return false;
+    }
+
+    /**
+     * True if the app is in the foreground.
+     */
+    private boolean isAppForeground(String packageName) {
+        return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+    }
+
+    /**
+     * True if the app is currently at the top of the screen that the user is interacting with.
+     */
+    public boolean isAppTopVisible(String packageName) {
+        return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+    }
+
+    /**
+     * True if the app is playing/recording audio.
+     */
+    private boolean hasActiveAudio(String packageName) {
+        return hasAudioFocus(packageName) || isRecordingAudio(packageName);
+    }
+
+    /**
+     * True if the app is sending or receiving network data.
+     */
+    private boolean hasActiveNetwork(String packageName) {
+        // To be implemented
+        return false;
+    }
+
+    /**
+     * True if any app is interacting with the user.
+     */
+    public boolean hasInteractingApp(List<String> packageNames) {
+        for (var packageName : packageNames) {
+            if (hasActiveAudio(packageName)
+                    || hasActiveNetwork(packageName)
+                    || isAppTopVisible(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * True if any app is in the foreground.
+     */
+    public boolean hasForegroundApp(List<String> packageNames) {
+        for (var packageName : packageNames) {
+            if (isAppForeground(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * True if any app is top visible.
+     */
+    public boolean hasTopVisibleApp(List<String> packageNames) {
+        for (var packageName : packageNames) {
+            if (isAppTopVisible(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * True if there is an ongoing phone call.
+     */
+    public boolean isInCall() {
+        // TelecomManager doesn't handle the case where some apps don't implement ConnectionService.
+        // We check apps using voice communication to detect if the device is in call.
+        var tm = mContext.getSystemService(TelecomManager.class);
+        return tm.isInCall() || hasVoiceCall();
+    }
+
+    /**
+     * Returns a list of packages which depend on {@code packageNames}. These are the packages
+     * that will be affected when updating {@code packageNames} and should participate in
+     * the evaluation of install constraints.
+     */
+    public List<String> getDependencyPackages(List<String> packageNames) {
+        var results = new ArraySet<String>();
+        var am = mContext.getSystemService(ActivityManager.class);
+        for (var info : am.getRunningAppProcesses()) {
+            for (var packageName : packageNames) {
+                if (!isPackageLoaded(info, packageName)) {
+                    continue;
+                }
+                for (var pkg : info.pkgList) {
+                    results.add(pkg);
+                }
+            }
+        }
+        var amInternal = LocalServices.getService(ActivityManagerInternal.class);
+        for (var packageName : packageNames) {
+            results.addAll(amInternal.getClientPackages(packageName));
+        }
+        return new ArrayList<>(results);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 3b676c65..b4792c6 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -44,7 +44,6 @@
 import com.android.server.pm.snapshot.PackageDataSnapshot;
 import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.Watched;
-import com.android.server.utils.WatchedArrayList;
 import com.android.server.utils.WatchedArrayMap;
 import com.android.server.utils.WatchedArraySet;
 import com.android.server.utils.WatchedSparseBooleanMatrix;
@@ -179,9 +178,9 @@
 
     @NonNull
     @Watched
-    protected WatchedArrayList<String> mProtectedBroadcasts;
+    protected WatchedArraySet<String> mProtectedBroadcasts;
     @NonNull
-    protected SnapshotCache<WatchedArrayList<String>> mProtectedBroadcastsSnapshot;
+    protected SnapshotCache<WatchedArraySet<String>> mProtectedBroadcastsSnapshot;
 
     /**
      * This structure maps uid -> uid and indicates whether access from the first should be
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index e3a2fb2..5b837f1 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -73,7 +73,6 @@
 import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.Watchable;
 import com.android.server.utils.WatchableImpl;
-import com.android.server.utils.WatchedArrayList;
 import com.android.server.utils.WatchedArraySet;
 import com.android.server.utils.WatchedSparseBooleanMatrix;
 import com.android.server.utils.WatchedSparseSetArray;
@@ -82,11 +81,8 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 
 /**
  * Implementation of the methods that update the internal structures of AppsFilter. Because of the
@@ -114,7 +110,7 @@
      */
     @GuardedBy("mQueryableViaUsesPermissionLock")
     @NonNull
-    private HashMap<String, Set<Integer>> mPermissionToUids;
+    private final ArrayMap<String, ArraySet<Integer>> mPermissionToUids;
 
     /**
      * A cache that maps parsed {@link android.R.styleable#AndroidManifestUsesPermission
@@ -124,7 +120,7 @@
      */
     @GuardedBy("mQueryableViaUsesPermissionLock")
     @NonNull
-    private HashMap<String, Set<Integer>> mUsesPermissionToUids;
+    private final ArrayMap<String, ArraySet<Integer>> mUsesPermissionToUids;
 
     /**
      * Ensures an observer is in the list, exactly once. The observer cannot be null.  The
@@ -223,11 +219,11 @@
         mForceQueryable = new WatchedArraySet<>();
         mForceQueryableSnapshot = new SnapshotCache.Auto<>(
                 mForceQueryable, mForceQueryable, "AppsFilter.mForceQueryable");
-        mProtectedBroadcasts = new WatchedArrayList<>();
+        mProtectedBroadcasts = new WatchedArraySet<>();
         mProtectedBroadcastsSnapshot = new SnapshotCache.Auto<>(
                 mProtectedBroadcasts, mProtectedBroadcasts, "AppsFilter.mProtectedBroadcasts");
-        mPermissionToUids = new HashMap<>();
-        mUsesPermissionToUids = new HashMap<>();
+        mPermissionToUids = new ArrayMap<>();
+        mUsesPermissionToUids = new ArrayMap<>();
 
         mSnapshot = new SnapshotCache<AppsFilterSnapshot>(this, this) {
             @Override
@@ -492,9 +488,11 @@
      *
      * @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
@@ -505,7 +503,8 @@
         try {
             if (isReplace) {
                 // let's first remove any prior rules for this package
-                removePackageInternal(snapshot, newPkgSetting, true /*isReplace*/);
+                removePackageInternal(snapshot, newPkgSetting,
+                        true /*isReplace*/, retainImplicitGrantOnReplace);
             }
             final ArrayMap<String, ? extends PackageStateInternal> settings =
                     snapshot.getPackageStates();
@@ -570,13 +569,17 @@
             return null;
         }
 
-        final boolean protectedBroadcastsChanged;
-        synchronized (mProtectedBroadcastsLock) {
-            protectedBroadcastsChanged =
-                    mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts());
-        }
-        if (protectedBroadcastsChanged) {
-            mQueriesViaComponentRequireRecompute.set(true);
+        final List<String> newBroadcasts = newPkg.getProtectedBroadcasts();
+        if (newBroadcasts.size() != 0) {
+            final boolean protectedBroadcastsChanged;
+            synchronized (mProtectedBroadcastsLock) {
+                final int oldSize = mProtectedBroadcasts.size();
+                mProtectedBroadcasts.addAll(newBroadcasts);
+                protectedBroadcastsChanged = mProtectedBroadcasts.size() != oldSize;
+            }
+            if (protectedBroadcastsChanged) {
+                mQueriesViaComponentRequireRecompute.set(true);
+            }
         }
 
         final boolean newIsForceQueryable;
@@ -603,7 +606,10 @@
                     // Lookup in the mPermissionToUids cache if installed packages have
                     // defined this permission.
                     if (mPermissionToUids.containsKey(usesPermissionName)) {
-                        for (int targetAppId : mPermissionToUids.get(usesPermissionName)) {
+                        final ArraySet<Integer> permissionDefiners =
+                                mPermissionToUids.get(usesPermissionName);
+                        for (int j = 0; j < permissionDefiners.size(); j++) {
+                            final int targetAppId = permissionDefiners.valueAt(j);
                             if (targetAppId != newPkgSetting.getAppId()) {
                                 mQueryableViaUsesPermission.add(newPkgSetting.getAppId(),
                                         targetAppId);
@@ -613,7 +619,7 @@
                     // Record in mUsesPermissionToUids that a permission was requested
                     // by a new package
                     if (!mUsesPermissionToUids.containsKey(usesPermissionName)) {
-                        mUsesPermissionToUids.put(usesPermissionName, new HashSet<>());
+                        mUsesPermissionToUids.put(usesPermissionName, new ArraySet<>());
                     }
                     mUsesPermissionToUids.get(usesPermissionName).add(newPkgSetting.getAppId());
                 }
@@ -627,7 +633,10 @@
                     // Lookup in the mUsesPermissionToUids cache if installed packages have
                     // requested this permission.
                     if (mUsesPermissionToUids.containsKey(permissionName)) {
-                        for (int queryingAppId : mUsesPermissionToUids.get(permissionName)) {
+                        final ArraySet<Integer> permissionUsers = mUsesPermissionToUids.get(
+                                permissionName);
+                        for (int j = 0; j < permissionUsers.size(); j++) {
+                            final int queryingAppId = permissionUsers.valueAt(j);
                             if (queryingAppId != newPkgSetting.getAppId()) {
                                 mQueryableViaUsesPermission.add(queryingAppId,
                                         newPkgSetting.getAppId());
@@ -636,7 +645,7 @@
                     }
                     // Record in mPermissionToUids that a permission was defined by a new package
                     if (!mPermissionToUids.containsKey(permissionName)) {
-                        mPermissionToUids.put(permissionName, new HashSet<>());
+                        mPermissionToUids.put(permissionName, new ArraySet<>());
                     }
                     mPermissionToUids.get(permissionName).add(newPkgSetting.getAppId());
                 }
@@ -1016,13 +1025,14 @@
     }
 
     /**
-     * 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 */);
     }
 
     /**
@@ -1032,7 +1042,8 @@
      */
     public void removePackage(Computer snapshot, PackageStateInternal setting) {
         final long currentTimeUs = SystemClock.currentTimeMicro();
-        removePackageInternal(snapshot, setting, false /* isReplace */);
+        removePackageInternal(snapshot, setting,
+                false /* isReplace */, false /* retainImplicitGrantOnReplace */);
         logCacheUpdated(
                 PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_DELETED,
                 SystemClock.currentTimeMicro() - currentTimeUs,
@@ -1046,33 +1057,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.
      */
     private void removePackageInternal(Computer snapshot, PackageStateInternal setting,
-            boolean isReplace) {
+            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);
+                    }
                 }
             }
         }
@@ -1140,7 +1155,12 @@
                 final ArrayList<String> protectedBroadcasts = new ArrayList<>(
                         mProtectedBroadcasts.untrackedStorage());
                 collectProtectedBroadcasts(settings, removingPackageName);
-                protectedBroadcastsChanged = !mProtectedBroadcasts.containsAll(protectedBroadcasts);
+                for (int i = 0; i < protectedBroadcasts.size(); ++i) {
+                    if (!mProtectedBroadcasts.contains(protectedBroadcasts.get(i))) {
+                        protectedBroadcastsChanged = true;
+                        break;
+                    }
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java
index 7daa0b9..bf28479 100644
--- a/services/core/java/com/android/server/pm/AppsFilterUtils.java
+++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java
@@ -29,7 +29,7 @@
 import com.android.server.pm.pkg.component.ParsedIntentInfo;
 import com.android.server.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.utils.WatchedArrayList;
+import com.android.server.utils.WatchedArraySet;
 
 import java.util.List;
 import java.util.Set;
@@ -45,7 +45,7 @@
 
     /** Returns true if the querying package may query for the potential target package */
     public static boolean canQueryViaComponents(AndroidPackage querying,
-            AndroidPackage potentialTarget, WatchedArrayList<String> protectedBroadcasts) {
+            AndroidPackage potentialTarget, WatchedArraySet<String> protectedBroadcasts) {
         if (!querying.getQueriesIntents().isEmpty()) {
             for (Intent intent : querying.getQueriesIntents()) {
                 if (matchesPackage(intent, potentialTarget, protectedBroadcasts)) {
@@ -69,11 +69,11 @@
     public static boolean canQueryAsInstaller(PackageStateInternal querying,
             AndroidPackage potentialTarget) {
         final InstallSource installSource = querying.getInstallSource();
-        if (potentialTarget.getPackageName().equals(installSource.installerPackageName)) {
+        if (potentialTarget.getPackageName().equals(installSource.mInstallerPackageName)) {
             return true;
         }
-        if (!installSource.isInitiatingPackageUninstalled
-                && potentialTarget.getPackageName().equals(installSource.initiatingPackageName)) {
+        if (!installSource.mIsInitiatingPackageUninstalled
+                && potentialTarget.getPackageName().equals(installSource.mInitiatingPackageName)) {
             return true;
         }
         return false;
@@ -117,7 +117,7 @@
     }
 
     private static boolean matchesPackage(Intent intent, AndroidPackage potentialTarget,
-            WatchedArrayList<String> protectedBroadcasts) {
+            WatchedArraySet<String> protectedBroadcasts) {
         if (matchesAnyComponents(
                 intent, potentialTarget.getServices(), null /*protectedBroadcasts*/)) {
             return true;
@@ -138,7 +138,7 @@
 
     private static boolean matchesAnyComponents(Intent intent,
             List<? extends ParsedMainComponent> components,
-            WatchedArrayList<String> protectedBroadcasts) {
+            WatchedArraySet<String> protectedBroadcasts) {
         for (int i = ArrayUtils.size(components) - 1; i >= 0; i--) {
             ParsedMainComponent component = components.get(i);
             if (!component.isExported()) {
@@ -152,7 +152,7 @@
     }
 
     private static boolean matchesAnyFilter(Intent intent, ParsedComponent component,
-            WatchedArrayList<String> protectedBroadcasts) {
+            WatchedArraySet<String> protectedBroadcasts) {
         List<ParsedIntentInfo> intents = component.getIntents();
         for (int i = ArrayUtils.size(intents) - 1; i >= 0; i--) {
             IntentFilter intentFilter = intents.get(i).getIntentFilter();
@@ -164,7 +164,7 @@
     }
 
     private static boolean matchesIntentFilter(Intent intent, IntentFilter intentFilter,
-            @Nullable WatchedArrayList<String> protectedBroadcasts) {
+            @Nullable WatchedArraySet<String> protectedBroadcasts) {
         return intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(),
                 intent.getData(), intent.getCategories(), "AppsFilter", true,
                 protectedBroadcasts != null ? protectedBroadcasts.untrackedStorage() : null) > 0;
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index e7412c5..cda7503 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -17,8 +17,11 @@
 package com.android.server.pm;
 
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 import static com.android.server.pm.dex.ArtStatsLogUtils.BackgroundDexoptJobStatsLogger;
 
+import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -89,6 +92,8 @@
             new ComponentName("android", BackgroundDexOptJobService.class.getName());
 
     // Possible return codes of individual optimization steps.
+    /** Initial value. */
+    public static final int STATUS_UNSPECIFIED = -1;
     /** Ok status: Optimizations finished, All packages were processed, can continue */
     public static final int STATUS_OK = 0;
     /** Optimizations should be aborted. Job scheduler requested it. */
@@ -105,16 +110,20 @@
      * job will exclude those failed packages.
      */
     public static final int STATUS_DEX_OPT_FAILED = 5;
+    /** Encountered fatal error, such as a runtime exception. */
+    public static final int STATUS_FATAL_ERROR = 6;
 
     @IntDef(prefix = {"STATUS_"},
             value =
                     {
+                            STATUS_UNSPECIFIED,
                             STATUS_OK,
                             STATUS_ABORT_BY_CANCELLATION,
                             STATUS_ABORT_NO_SPACE_LEFT,
                             STATUS_ABORT_THERMAL,
                             STATUS_ABORT_BATTERY,
                             STATUS_DEX_OPT_FAILED,
+                            STATUS_FATAL_ERROR,
                     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Status {}
@@ -150,10 +159,8 @@
     // True if JobScheduler invocations of dexopt have been disabled.
     @GuardedBy("mLock") private boolean mDisableJobSchedulerJobs;
 
-    @GuardedBy("mLock") @Status private int mLastExecutionStatus = STATUS_OK;
+    @GuardedBy("mLock") @Status private int mLastExecutionStatus = STATUS_UNSPECIFIED;
 
-    @GuardedBy("mLock") private long mLastExecutionStartTimeMs;
-    @GuardedBy("mLock") private long mLastExecutionDurationIncludingSleepMs;
     @GuardedBy("mLock") private long mLastExecutionStartUptimeMs;
     @GuardedBy("mLock") private long mLastExecutionDurationMs;
 
@@ -234,10 +241,6 @@
             writer.println(mDisableJobSchedulerJobs);
             writer.print("mLastExecutionStatus:");
             writer.println(mLastExecutionStatus);
-            writer.print("mLastExecutionStartTimeMs:");
-            writer.println(mLastExecutionStartTimeMs);
-            writer.print("mLastExecutionDurationIncludingSleepMs:");
-            writer.println(mLastExecutionDurationIncludingSleepMs);
             writer.print("mLastExecutionStartUptimeMs:");
             writer.println(mLastExecutionStartUptimeMs);
             writer.print("mLastExecutionDurationMs:");
@@ -410,7 +413,7 @@
                                 job.jobFinished(params, !completed);
                             } else {
                                 // Periodic job
-                                job.jobFinished(params, true);
+                                job.jobFinished(params, false /* reschedule */);
                             }
                             markDexOptCompleted();
                         }
@@ -564,22 +567,26 @@
     private boolean runIdleOptimization(
             PackageManagerService pm, List<String> pkgs, boolean isPostBootUpdate) {
         synchronized (mLock) {
-            mLastExecutionStartTimeMs = SystemClock.elapsedRealtime();
-            mLastExecutionDurationIncludingSleepMs = -1;
+            mLastExecutionStatus = STATUS_UNSPECIFIED;
             mLastExecutionStartUptimeMs = SystemClock.uptimeMillis();
             mLastExecutionDurationMs = -1;
         }
-        long lowStorageThreshold = getLowStorageThreshold();
-        int status = idleOptimizePackages(pm, pkgs, lowStorageThreshold, isPostBootUpdate);
-        logStatus(status);
-        synchronized (mLock) {
-            mLastExecutionStatus = status;
-            mLastExecutionDurationIncludingSleepMs =
-                    SystemClock.elapsedRealtime() - mLastExecutionStartTimeMs;
-            mLastExecutionDurationMs = SystemClock.uptimeMillis() - mLastExecutionStartUptimeMs;
-        }
 
-        return status == STATUS_OK || status == STATUS_DEX_OPT_FAILED;
+        int status = STATUS_UNSPECIFIED;
+        try {
+            long lowStorageThreshold = getLowStorageThreshold();
+            status = idleOptimizePackages(pm, pkgs, lowStorageThreshold, isPostBootUpdate);
+            logStatus(status);
+            return status == STATUS_OK || status == STATUS_DEX_OPT_FAILED;
+        } catch (RuntimeException e) {
+            status = STATUS_FATAL_ERROR;
+            throw e;
+        } finally {
+            synchronized (mLock) {
+                mLastExecutionStatus = status;
+                mLastExecutionDurationMs = SystemClock.uptimeMillis() - mLastExecutionStartUptimeMs;
+            }
+        }
     }
 
     /** Gets the size of the directory. It uses recursion to go over all files. */
@@ -758,10 +765,21 @@
             return PackageDexOptimizer.DEX_OPT_CANCELLED;
         }
         int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
+        String filter = getCompilerFilterForReason(reason);
         int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE | DexoptOptions.DEXOPT_DOWNGRADE;
+
+        if (isProfileGuidedCompilerFilter(filter)) {
+            // We don't expect updates in current profiles to be significant here, but
+            // DEXOPT_CHECK_FOR_PROFILES_UPDATES is set to replicate behaviour that will be
+            // unconditionally enabled for profile guided filters when ART Service is called instead
+            // of the legacy PackageDexOptimizer implementation.
+            dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
+        }
+
         if (!isPostBootUpdate) {
             dexoptFlags |= DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
         }
+
         long package_size_before = getPackageSize(snapshot, pkg);
         int result = PackageDexOptimizer.DEX_OPT_SKIPPED;
         if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
@@ -772,10 +790,10 @@
                 // remove their compiler artifacts from dalvik cache.
                 pm.deleteOatArtifactsOfPackage(snapshot, pkg);
             } else {
-                result = performDexOptPrimary(pkg, reason, dexoptFlags);
+                result = performDexOptPrimary(pkg, reason, filter, dexoptFlags);
             }
         } else {
-            result = performDexOptSecondary(pkg, reason, dexoptFlags);
+            result = performDexOptSecondary(pkg, reason, filter, dexoptFlags);
         }
 
         if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -811,32 +829,42 @@
     private int optimizePackage(String pkg, boolean isForPrimaryDex, boolean isPostBootUpdate) {
         int reason = isPostBootUpdate ? PackageManagerService.REASON_POST_BOOT
                                       : PackageManagerService.REASON_BACKGROUND_DEXOPT;
+        String filter = getCompilerFilterForReason(reason);
+
         int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE;
         if (!isPostBootUpdate) {
             dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
                     | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
         }
 
+        if (isProfileGuidedCompilerFilter(filter)) {
+            // Ensure DEXOPT_CHECK_FOR_PROFILES_UPDATES is enabled if the filter is profile guided,
+            // to replicate behaviour that will be unconditionally enabled when ART Service is
+            // called instead of the legacy PackageDexOptimizer implementation.
+            dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
+        }
+
         // System server share the same code path as primary dex files.
         // PackageManagerService will select the right optimization path for it.
         if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
-            return performDexOptPrimary(pkg, reason, dexoptFlags);
+            return performDexOptPrimary(pkg, reason, filter, dexoptFlags);
         } else {
-            return performDexOptSecondary(pkg, reason, dexoptFlags);
+            return performDexOptSecondary(pkg, reason, filter, dexoptFlags);
         }
     }
 
     @DexOptResult
-    private int performDexOptPrimary(String pkg, int reason, int dexoptFlags) {
-        DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, dexoptFlags);
+    private int performDexOptPrimary(String pkg, int reason, String filter, int dexoptFlags) {
+        DexoptOptions dexoptOptions =
+                new DexoptOptions(pkg, reason, filter, /*splitName=*/null, dexoptFlags);
         return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/true,
                 () -> mDexOptHelper.performDexOptWithStatus(dexoptOptions));
     }
 
     @DexOptResult
-    private int performDexOptSecondary(String pkg, int reason, int dexoptFlags) {
-        DexoptOptions dexoptOptions = new DexoptOptions(
-                pkg, reason, dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
+    private int performDexOptSecondary(String pkg, int reason, String filter, int dexoptFlags) {
+        DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, filter, /*splitName=*/null,
+                dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
         return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/false,
                 ()
                         -> mDexOptHelper.performDexOpt(dexoptOptions)
@@ -979,10 +1007,9 @@
         synchronized (mLock) {
             status = mLastExecutionStatus;
             durationMs = mLastExecutionDurationMs;
-            durationIncludingSleepMs = mLastExecutionDurationIncludingSleepMs;
         }
 
-        mStatsLogger.write(status, params.getStopReason(), durationMs, durationIncludingSleepMs);
+        mStatsLogger.write(status, params.getStopReason(), durationMs);
     }
 
     /** Injector pattern for testing purpose */
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/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index bf00a33..5b8ee2b 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -125,6 +125,14 @@
     ActivityInfo getActivityInfo(ComponentName component, long flags, int userId);
 
     /**
+     * Similar to {@link Computer#getActivityInfo(android.content.ComponentName, long, int)} but
+     * only visible as internal service. This method bypass INTERACT_ACROSS_USERS or
+     * INTERACT_ACROSS_USERS_FULL permission checks and only to be used for intent resolution across
+     * chained cross profiles
+     */
+    ActivityInfo getActivityInfoCrossProfile(ComponentName component, long flags, int userId);
+
+    /**
      * Important: The provided filterCallingUid is used exclusively to filter out activities
      * that can be seen based on user state. It's typically the original caller uid prior
      * to clearing. Because it can only be provided by trusted code, its value can be
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index b9967f9..45b633f 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -44,6 +44,7 @@
 import static android.content.pm.PackageManager.TYPE_RECEIVER;
 import static android.content.pm.PackageManager.TYPE_SERVICE;
 import static android.content.pm.PackageManager.TYPE_UNKNOWN;
+import static android.os.Process.INVALID_UID;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
 import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
@@ -63,7 +64,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 +112,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 +397,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 +452,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 +461,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.
@@ -837,6 +836,24 @@
     }
 
     /**
+     * Similar to {@link Computer#getActivityInfo(android.content.ComponentName, long, int)} but
+     * only visible as internal service. This method bypass INTERACT_ACROSS_USERS or
+     * INTERACT_ACROSS_USERS_FULL permission checks and only to be used for intent resolution across
+     * chained cross profiles
+     * @param component application's component
+     * @param flags resolve info flags
+     * @param userId user id where activity resides
+     * @return ActivityInfo corresponding to requested component.
+     */
+    public final ActivityInfo getActivityInfoCrossProfile(ComponentName component,
+            @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
+        if (!mUserManager.exists(userId)) return null;
+        flags = updateFlagsForComponent(flags, userId);
+
+        return getActivityInfoInternalBody(component, flags, Binder.getCallingUid(), userId);
+    }
+
+    /**
      * Important: The provided filterCallingUid is used exclusively to filter out activities
      * that can be seen based on user state. It's typically the original caller uid prior
      * to clearing. Because it can only be provided by trusted code, its value can be
@@ -968,10 +985,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 +1002,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 +1550,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 +1574,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 +1597,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 +1665,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 +1693,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 +1708,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);
     }
 
@@ -1777,7 +1730,7 @@
         ComponentName forwardingActivityComponentName = new ComponentName(
                 androidApplication().packageName, className);
         ActivityInfo forwardingActivityInfo =
-                getActivityInfo(forwardingActivityComponentName, 0,
+                getActivityInfoCrossProfile(forwardingActivityComponentName, 0,
                         sourceUserId);
         if (!targetIsProfile) {
             forwardingActivityInfo.showUserIcon = targetUserId;
@@ -2624,7 +2577,7 @@
             }
         }
 
-        return -1;
+        return INVALID_UID;
     }
 
     /**
@@ -3155,24 +3108,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 +3536,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
@@ -4345,18 +4326,18 @@
     @Override
     public int getUidForSharedUser(@NonNull String sharedUserName) {
         if (sharedUserName == null) {
-            return Process.INVALID_UID;
+            return INVALID_UID;
         }
         final int callingUid = Binder.getCallingUid();
         if (getInstantAppPackageName(callingUid) != null) {
-            return Process.INVALID_UID;
+            return INVALID_UID;
         }
         final SharedUserSetting suid = mSettings.getSharedUserFromId(sharedUserName);
         if (suid != null && !shouldFilterApplicationIncludingUninstalled(suid, callingUid,
                 UserHandle.getUserId(callingUid))) {
             return suid.mAppId;
         }
-        return Process.INVALID_UID;
+        return INVALID_UID;
     }
 
     @Override
@@ -4563,10 +4544,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 +4575,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;
@@ -4942,7 +4919,7 @@
         if (installSource == null) {
             throw new IllegalArgumentException("Unknown package: " + packageName);
         }
-        String installerPackageName = installSource.installerPackageName;
+        String installerPackageName = installSource.mInstallerPackageName;
         if (installerPackageName != null) {
             final PackageStateInternal ps = mSettings.getPackage(installerPackageName);
             if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid,
@@ -4985,7 +4962,7 @@
             return null;
         }
 
-        installerPackageName = installSource.installerPackageName;
+        installerPackageName = installSource.mInstallerPackageName;
         if (installerPackageName != null) {
             final PackageStateInternal ps = mSettings.getPackage(installerPackageName);
             if (ps == null
@@ -4994,25 +4971,25 @@
             }
         }
 
-        if (installSource.isInitiatingPackageUninstalled) {
+        if (installSource.mIsInitiatingPackageUninstalled) {
             // We can't check visibility in the usual way, since the initiating package is no
             // longer present. So we apply simpler rules to whether to expose the info:
             // 1. Instant apps can't see it.
             // 2. Otherwise only the installed app itself can see it.
             final boolean isInstantApp = getInstantAppPackageName(callingUid) != null;
             if (!isInstantApp && isCallerSameApp(packageName, callingUid)) {
-                initiatingPackageName = installSource.initiatingPackageName;
+                initiatingPackageName = installSource.mInitiatingPackageName;
             } else {
                 initiatingPackageName = null;
             }
         } else {
-            if (Objects.equals(installSource.initiatingPackageName,
-                    installSource.installerPackageName)) {
+            if (Objects.equals(installSource.mInitiatingPackageName,
+                    installSource.mInstallerPackageName)) {
                 // The installer and initiator will often be the same, and when they are
                 // we can skip doing the same check again.
                 initiatingPackageName = installerPackageName;
             } else {
-                initiatingPackageName = installSource.initiatingPackageName;
+                initiatingPackageName = installSource.mInitiatingPackageName;
                 final PackageStateInternal ps = mSettings.getPackage(initiatingPackageName);
                 if (ps == null
                         || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) {
@@ -5021,7 +4998,7 @@
             }
         }
 
-        originatingPackageName = installSource.originatingPackageName;
+        originatingPackageName = installSource.mOriginatingPackageName;
         if (originatingPackageName != null) {
             final PackageStateInternal ps = mSettings.getPackage(originatingPackageName);
             if (ps == null
@@ -5041,7 +5018,7 @@
         // If you can see the initiatingPackageName, and we have valid signing info for it,
         // then we let you see that too.
         final SigningInfo initiatingPackageSigningInfo;
-        final PackageSignatures signatures = installSource.initiatingPackageSignatures;
+        final PackageSignatures signatures = installSource.mInitiatingPackageSignatures;
         if (initiatingPackageName != null && signatures != null
                 && signatures.mSigningDetails != SigningDetails.UNKNOWN) {
             initiatingPackageSigningInfo = new SigningInfo(signatures.mSigningDetails);
@@ -5050,7 +5027,7 @@
         }
 
         return new InstallSourceInfo(initiatingPackageName, initiatingPackageSigningInfo,
-                originatingPackageName, installerPackageName, installSource.packageSource);
+                originatingPackageName, installerPackageName, installSource.mPackageSource);
     }
 
     @PackageManager.EnabledState
@@ -5270,7 +5247,7 @@
         final int targetAppId = UserHandle.getAppId(
                 getPackageUid(targetPackageName, 0 /* flags */, userId));
         // For update or already installed case, leverage the existing visibility rule.
-        if (targetAppId != Process.INVALID_UID) {
+        if (targetAppId != INVALID_UID) {
             final Object targetSetting = mSettings.getSettingBase(targetAppId);
             if (targetSetting instanceof PackageSetting) {
                 return !shouldFilterApplication(
@@ -5331,7 +5308,7 @@
         }
 
         final PackageStateInternal installerPackageState = getPackageStateInternal(
-                packageState.getInstallSource().installerPackageName);
+                packageState.getInstallSource().mInstallerPackageName);
         return installerPackageState != null
                 && UserHandle.isSameApp(installerPackageState.getAppId(), callingUid);
     }
@@ -5616,7 +5593,8 @@
             return PackageInfoUtils.generateProcessInfo(sus.processes, 0);
         } else if (settingBase instanceof PackageSetting) {
             final PackageSetting ps = (PackageSetting) settingBase;
-            return PackageInfoUtils.generateProcessInfo(ps.getPkg().getProcesses(), 0);
+            final AndroidPackage pkg = ps.getPkg();
+            return pkg == null ? null : PackageInfoUtils.generateProcessInfo(pkg.getProcesses(), 0);
         }
         return null;
     }
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
index 0cd698a..04bd135 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
@@ -46,6 +46,18 @@
     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;
+
+    /*
+    This flag, denotes if further cross profile resolution is allowed, e.g. if profile#0 is linked
+    to profile#1 and profile#2 . When intent resolution from profile#1 is started we resolve it in
+    profile#1 and profile#0. The profile#0 is also linked to profile#2, we will only resolve in
+    profile#2 if CrossProfileIntentFilter between profile#1 and profile#0 have set flag
+    FLAG_ALLOW_CHAINED_RESOLUTION.
+     */
+    public static final int FLAG_ALLOW_CHAINED_RESOLUTION = 0x00000010;
+
     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..5d97cb7 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;
@@ -41,7 +43,9 @@
 import com.android.server.pm.verify.domain.DomainVerificationUtils;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Function;
 
 /**
@@ -54,13 +58,17 @@
     private final UserManagerService mUserManager;
     private final DomainVerificationManagerInternal mDomainVerificationManager;
     private final DefaultAppProvider mDefaultAppProvider;
+    private final Context mContext;
+    private final UserManagerInternal mUserManagerInternal;
 
     public CrossProfileIntentResolverEngine(UserManagerService userManager,
             DomainVerificationManagerInternal domainVerificationManager,
-            DefaultAppProvider defaultAppProvider) {
+            DefaultAppProvider defaultAppProvider, Context context) {
         mUserManager = userManager;
         mDomainVerificationManager = domainVerificationManager;
         mDefaultAppProvider = defaultAppProvider;
+        mContext = context;
+        mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
     }
 
     /**
@@ -83,14 +91,14 @@
             String resolvedType, int userId, long flags, String pkgName,
             boolean hasNonNegativePriorityResult,
             Function<String, PackageStateInternal> pkgSettingFunction) {
-        return resolveIntentInternal(computer, intent, resolvedType, userId, flags, pkgName,
-                hasNonNegativePriorityResult, pkgSettingFunction);
+        return resolveIntentInternal(computer, intent, resolvedType, userId, userId, flags, pkgName,
+                hasNonNegativePriorityResult, pkgSettingFunction, null);
     }
 
     /**
      * Resolves intent in directly linked profiles and return list of {@link CrossProfileDomainInfo}
-     * which contains {@link ResolveInfo}. This would also iteratively call profiles not directly
-     * linked using Breadth First Search.
+     * which contains {@link ResolveInfo}. This would also recursively call profiles not directly
+     * linked using Depth First Search.
      *
      * It first finds {@link CrossProfileIntentFilter} configured in current profile to find list of
      * target user profiles that can serve current intent request. It uses corresponding strategy
@@ -99,34 +107,39 @@
      * @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
      * @param intent request
      * @param resolvedType the MIME data type of intent request
-     * @param userId source user for which intent request is called
+     * @param sourceUserId source user for which intent request is called
+     * @param userId current user for cross profile resolution
      * @param flags used for intent resolution
      * @param pkgName the application package name this Intent is limited to.
      * @param hasNonNegativePriorityResult signifies if current profile have any non-negative(active
      *                                     and valid) ResolveInfo in current profile.
      * @param pkgSettingFunction function to find PackageStateInternal for given package
+     * @param visitedUserIds users for which we have already performed resolution
      * @return list of {@link CrossProfileDomainInfo} from linked profiles.
      */
     private List<CrossProfileDomainInfo> resolveIntentInternal(@NonNull Computer computer,
-            Intent intent, String resolvedType, int userId, long flags, String pkgName,
-            boolean hasNonNegativePriorityResult,
-            Function<String, PackageStateInternal> pkgSettingFunction) {
+            Intent intent, String resolvedType, int sourceUserId, int userId, long flags,
+            String pkgName, boolean hasNonNegativePriorityResult,
+            Function<String, PackageStateInternal> pkgSettingFunction,
+            Set<Integer> visitedUserIds) {
 
+        if (visitedUserIds != null) visitedUserIds.add(userId);
         List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
 
         List<CrossProfileIntentFilter> matchingFilters =
-                computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
+                computer.getMatchingCrossProfileIntentFilters(intent, resolvedType,
+                        userId);
 
         if (matchingFilters == null || matchingFilters.isEmpty()) {
-            /** if intent is web intent, checking if parent profile should handle the intent even
-            if there is no matching filter. The configuration is based on user profile
-            restriction android.os.UserManager#ALLOW_PARENT_PROFILE_APP_LINKING **/
-            if (intent.hasWebURI()) {
+            /** if intent is web intent, checking if parent profile should handle the intent
+             * even if there is no matching filter. The configuration is based on user profile
+             * restriction android.os.UserManager#ALLOW_PARENT_PROFILE_APP_LINKING **/
+            if (sourceUserId == userId && intent.hasWebURI()) {
                 UserInfo parent = computer.getProfileParent(userId);
                 if (parent != null) {
                     CrossProfileDomainInfo generalizedCrossProfileDomainInfo = computer
-                            .getCrossProfileDomainPreferredLpr(intent, resolvedType, flags, userId,
-                                    parent.id);
+                            .getCrossProfileDomainPreferredLpr(intent, resolvedType, flags,
+                                    userId, parent.id);
                     if (generalizedCrossProfileDomainInfo != null) {
                         crossProfileDomainInfos.add(generalizedCrossProfileDomainInfo);
                     }
@@ -135,10 +148,9 @@
             return crossProfileDomainInfos;
         }
 
-        UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
-        UserInfo sourceUserInfo = umInternal.getUserInfo(userId);
+        UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
 
-       // Grouping the CrossProfileIntentFilters based on targerId
+        // Grouping the CrossProfileIntentFilters based on targerId
         SparseArray<List<CrossProfileIntentFilter>> crossProfileIntentFiltersByUser =
                 new SparseArray<>();
 
@@ -154,30 +166,65 @@
                     .add(crossProfileIntentFilter);
         }
 
+        if (visitedUserIds == null) {
+            visitedUserIds = new HashSet<>();
+            visitedUserIds.add(userId);
+        }
+
         /*
          For each target user, we would call their corresponding strategy
          {@link CrossProfileResolver} to resolve intent in corresponding user
          */
         for (int index = 0; index < crossProfileIntentFiltersByUser.size(); index++) {
 
-            UserInfo targetUserInfo = umInternal.getUserInfo(crossProfileIntentFiltersByUser
-                    .keyAt(index));
+            int targetUserId = crossProfileIntentFiltersByUser.keyAt(index);
+
+            //if user is already visited then skip resolution for particular user.
+            if (visitedUserIds.contains(targetUserId)) {
+                continue;
+            }
+
+            UserInfo targetUserInfo = mUserManagerInternal.getUserInfo(targetUserId);
 
             // Choosing strategy based on source and target user
             CrossProfileResolver crossProfileResolver =
-                    chooseCrossProfileResolver(computer, sourceUserInfo, targetUserInfo);
+                    chooseCrossProfileResolver(computer, userInfo, targetUserInfo);
 
-            /*
-            If {@link CrossProfileResolver} is available for source,target pair we will call it to
-            get {@link CrossProfileDomainInfo}s from that user.
-             */
+        /*
+        If {@link CrossProfileResolver} is available for source,target pair we will call it to
+        get {@link CrossProfileDomainInfo}s from that user.
+         */
             if (crossProfileResolver != null) {
                 List<CrossProfileDomainInfo> crossProfileInfos = crossProfileResolver
                         .resolveIntent(computer, intent, resolvedType, userId,
-                                crossProfileIntentFiltersByUser.keyAt(index), flags, pkgName,
+                                targetUserId, flags, pkgName,
                                 crossProfileIntentFiltersByUser.valueAt(index),
                                 hasNonNegativePriorityResult, pkgSettingFunction);
                 crossProfileDomainInfos.addAll(crossProfileInfos);
+                visitedUserIds.add(targetUserId);
+
+                /*
+                Adding target user to queue if flag
+                {@link CrossProfileIntentFilter#FLAG_ALLOW_CHAINED_RESOLUTION} is set for any
+                {@link CrossProfileIntentFilter}
+                 */
+                boolean allowChainedResolution = false;
+                for (int filterIndex = 0; filterIndex < crossProfileIntentFiltersByUser
+                        .valueAt(index).size(); filterIndex++) {
+                    if ((CrossProfileIntentFilter
+                            .FLAG_ALLOW_CHAINED_RESOLUTION & crossProfileIntentFiltersByUser
+                            .valueAt(index).get(filterIndex).mFlags) != 0) {
+                        allowChainedResolution = true;
+                        break;
+                    }
+                }
+                if (allowChainedResolution) {
+                    crossProfileDomainInfos.addAll(resolveIntentInternal(computer, intent,
+                            resolvedType, sourceUserId, targetUserId, flags, pkgName,
+                            hasNonNegativePriority(crossProfileInfos), pkgSettingFunction,
+                            visitedUserIds));
+                }
+
             }
         }
 
@@ -196,6 +243,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);
     }
@@ -213,7 +275,9 @@
     public boolean canReachTo(@NonNull Computer computer, @NonNull Intent intent,
             @Nullable String resolvedType, @UserIdInt int sourceUserId,
             @UserIdInt int targetUserId) {
-        return canReachToInternal(computer, intent, resolvedType, sourceUserId, targetUserId);
+        Set<Integer> visitedUserIds = new HashSet<>();
+        return canReachToInternal(computer, intent, resolvedType, sourceUserId, targetUserId,
+                visitedUserIds);
     }
 
     /**
@@ -225,21 +289,42 @@
      * @param resolvedType the MIME data type of intent request
      * @param sourceUserId source user
      * @param targetUserId target user
+     * @param visitedUserIds users for which resolution is checked
      * @return true if we source user can reach target user for given intent
      */
     private boolean canReachToInternal(@NonNull Computer computer, @NonNull Intent intent,
             @Nullable String resolvedType, @UserIdInt int sourceUserId,
-            @UserIdInt int targetUserId) {
+            @UserIdInt int targetUserId, Set<Integer> visitedUserIds) {
         if (sourceUserId == targetUserId) return true;
+        visitedUserIds.add(sourceUserId);
 
         List<CrossProfileIntentFilter> matches =
                 computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, sourceUserId);
+
         if (matches != null) {
             for (int index = 0; index < matches.size(); index++) {
                 CrossProfileIntentFilter crossProfileIntentFilter = matches.get(index);
                 if (crossProfileIntentFilter.mTargetUserId == targetUserId) {
                     return true;
                 }
+                if (visitedUserIds.contains(crossProfileIntentFilter.mTargetUserId)) {
+                    continue;
+                }
+
+                /*
+                 If source cannot directly reach to target, we will add
+                 CrossProfileIntentFilter.mTargetUserId user to queue to check if target user
+                 can be reached via CrossProfileIntentFilter.mTargetUserId i.e. it can be
+                 indirectly reached through chained/linked profiles.
+                 */
+                if ((CrossProfileIntentFilter.FLAG_ALLOW_CHAINED_RESOLUTION
+                        & crossProfileIntentFilter.mFlags) != 0) {
+                    visitedUserIds.add(crossProfileIntentFilter.mTargetUserId);
+                    if (canReachToInternal(computer, intent, resolvedType,
+                            crossProfileIntentFilter.mTargetUserId, targetUserId, visitedUserIds)) {
+                        return true;
+                    }
+                }
             }
         }
         return false;
@@ -313,8 +398,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));
@@ -543,8 +626,7 @@
             categorizeResolveInfoByTargetUser, int sourceUserId, int highestApprovalLevel) {
 
         List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
-        UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
-        UserInfo sourceUserInfo = umInternal.getUserInfo(sourceUserId);
+        UserInfo sourceUserInfo = mUserManagerInternal.getUserInfo(sourceUserId);
 
         for (int index = 0; index < categorizeResolveInfoByTargetUser.size(); index++) {
 
@@ -554,7 +636,7 @@
             } else {
                 // finding cross profile strategy based on source and target user
                 CrossProfileResolver crossProfileIntentResolver =
-                        chooseCrossProfileResolver(computer, sourceUserInfo, umInternal
+                        chooseCrossProfileResolver(computer, sourceUserInfo, mUserManagerInternal
                                 .getUserInfo(categorizeResolveInfoByTargetUser.keyAt(index)));
                 // if strategy is available call it and add its filtered results
                 if (crossProfileIntentResolver != null) {
@@ -588,4 +670,14 @@
 
         return resolveInfoList;
     }
+
+    /**
+     * @param crossProfileDomainInfos list of cross profile domain info in descending priority order
+     * @return if the list contains a resolve info with non-negative priority
+     */
+    private boolean hasNonNegativePriority(List<CrossProfileDomainInfo> crossProfileDomainInfos) {
+        return crossProfileDomainInfos.size() > 0
+                && crossProfileDomainInfos.get(0).mResolveInfo != null
+                && crossProfileDomainInfos.get(0).mResolveInfo.priority >= 0;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index cac9323..ceaaefd 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -319,4 +319,135 @@
                 HOME,
                 MOBILE_NETWORK_SETTINGS);
     }
+
+    /**
+     * Clone profile's DefaultCrossProfileIntentFilter
+     */
+
+    /*
+     Allowing media capture from clone to parent profile as clone profile would not have camera
+     */
+    private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_MEDIA_CAPTURE =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                                            // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(MediaStore.ACTION_IMAGE_CAPTURE)
+                    .addAction(MediaStore.ACTION_IMAGE_CAPTURE_SECURE)
+                    .addAction(MediaStore.ACTION_VIDEO_CAPTURE)
+                    .addAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
+                    .addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
+                    .addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
+                    .addAction(MediaStore.INTENT_ACTION_VIDEO_CAMERA)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .build();
+
+    /*
+     Allowing send action from clone to parent profile to share content from clone apps to parent
+     apps
+     */
+    private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_SEND_ACTION =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                    // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_SEND)
+                    .addAction(Intent.ACTION_SEND_MULTIPLE)
+                    .addAction(Intent.ACTION_SENDTO)
+                    .addDataType("*/*")
+                    .build();
+
+    /*
+     Allowing send action from parent to clone profile to share content from parent apps to clone
+     apps
+     */
+    private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_SEND_ACTION =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                                            // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_SEND)
+                    .addAction(Intent.ACTION_SEND_MULTIPLE)
+                    .addAction(Intent.ACTION_SENDTO)
+                    .addDataType("*/*")
+                    .build();
+
+    /*
+     Allowing view action from clone to parent profile to open any app-links or web links
+     */
+    private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_VIEW_ACTION =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                    // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_VIEW)
+                    .addDataScheme("https")
+                    .addDataScheme("http")
+                    .build();
+
+    /*
+     Allowing view action from parent to clone profile to open any app-links or web links
+     */
+    private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_VIEW_ACTION =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                                            // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_VIEW)
+                    .addDataScheme("https")
+                    .addDataScheme("http")
+                    .build();
+
+    /*
+     Allowing pick,insert and edit action from clone to parent profile to open picker or contacts
+     insert/edit.
+     */
+    private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_PICK_INSERT_ACTION =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                                            // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_PICK)
+                    .addAction(Intent.ACTION_GET_CONTENT)
+                    .addAction(Intent.ACTION_EDIT)
+                    .addAction(Intent.ACTION_INSERT)
+                    .addAction(Intent.ACTION_INSERT_OR_EDIT)
+                    .addDataType("*/*")
+                    .build();
+
+    /*
+     Allowing pick,insert and edit action from parent to clone profile to open picker
+     */
+    private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_PICK_INSERT_ACTION =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                                            // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_PICK)
+                    .addAction(Intent.ACTION_GET_CONTENT)
+                    .addAction(Intent.ACTION_EDIT)
+                    .addAction(Intent.ACTION_INSERT)
+                    .addAction(Intent.ACTION_INSERT_OR_EDIT)
+                    .addDataType("*/*")
+                    .build();
+
+    public static List<DefaultCrossProfileIntentFilter> getDefaultCloneProfileFilters() {
+        return Arrays.asList(
+                PARENT_TO_CLONE_SEND_ACTION,
+                PARENT_TO_CLONE_VIEW_ACTION,
+                PARENT_TO_CLONE_PICK_INSERT_ACTION,
+                CLONE_TO_PARENT_MEDIA_CAPTURE,
+                CLONE_TO_PARENT_SEND_ACTION,
+                CLONE_TO_PARENT_VIEW_ACTION,
+                CLONE_TO_PARENT_PICK_INSERT_ACTION
+
+        );
+    }
 }
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 88a3f8e..6998db7 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -261,6 +261,7 @@
             final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
             info.sendPackageRemovedBroadcasts(killApp, removedBySystem);
             info.sendSystemPackageUpdatedBroadcasts();
+            PackageMetrics.onUninstallSucceeded(info, deleteFlags, mUserManagerInternal);
         }
 
         // Force a gc to clear up things.
@@ -543,7 +544,7 @@
                 outInfo.mDataRemoved = true;
             }
             outInfo.mRemovedPackage = ps.getPackageName();
-            outInfo.mInstallerPackageName = ps.getInstallSource().installerPackageName;
+            outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName;
             outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibraryName() != null;
             outInfo.mRemovedAppId = ps.getAppId();
             outInfo.mRemovedUsers = userIds;
@@ -813,7 +814,7 @@
 
     private boolean isOrphaned(@NonNull Computer snapshot, String packageName) {
         final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
-        return packageState != null && packageState.getInstallSource().isOrphaned;
+        return packageState != null && packageState.getInstallSource().mIsOrphaned;
     }
 
     private boolean isCallerAllowedToSilentlyUninstall(@NonNull Computer snapshot, int callingUid,
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index c4f6836..0066592 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -33,6 +33,8 @@
 import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_APEX_PKG;
 import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PKG;
 
+import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -70,8 +72,6 @@
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 
-import dalvik.system.DexFile;
-
 import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -236,20 +236,24 @@
                 mPm.mArtManagerService.compileLayouts(pkg);
             }
 
-            // checkProfiles is false to avoid merging profiles during boot which
-            // might interfere with background compilation (b/28612421).
-            // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
-            // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
-            // trade-off worth doing to save boot time work.
             int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
+
+            String filter = getCompilerFilterForReason(pkgCompilationReason);
+            if (isProfileGuidedCompilerFilter(filter)) {
+                // DEXOPT_CHECK_FOR_PROFILES_UPDATES used to be false to avoid merging profiles
+                // during boot which might interfere with background compilation (b/28612421).
+                // However those problems were related to the verify-profile compiler filter which
+                // doesn't exist any more, so enable it again.
+                dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
+            }
+
             if (compilationReason == REASON_FIRST_BOOT) {
                 // TODO: This doesn't cover the upgrade case, we should check for this too.
                 dexoptFlags |= DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE;
             }
-            int primaryDexOptStatus = performDexOptTraced(new DexoptOptions(
-                    pkg.getPackageName(),
-                    pkgCompilationReason,
-                    dexoptFlags));
+            int primaryDexOptStatus = performDexOptTraced(
+                    new DexoptOptions(pkg.getPackageName(), pkgCompilationReason, filter,
+                            /*splitName*/ null, dexoptFlags));
 
             switch (primaryDexOptStatus) {
                 case PackageDexOptimizer.DEX_OPT_PERFORMED:
@@ -297,7 +301,7 @@
                 SystemProperties.get("dalvik.vm.systemuicompilerfilter", defaultCompilerFilter);
         String compilerFilter;
 
-        if (DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter)) {
+        if (isProfileGuidedCompilerFilter(targetCompilerFilter)) {
             compilerFilter = defaultCompilerFilter;
             File profileFile = new File(getPrebuildProfilePath(pkg));
 
@@ -322,8 +326,16 @@
             compilerFilter = targetCompilerFilter;
         }
 
+        // We don't expect updates in current profiles to be significant here, but
+        // DEXOPT_CHECK_FOR_PROFILES_UPDATES is set to replicate behaviour that will be
+        // unconditionally enabled for profile guided filters when ART Service is called instead of
+        // the legacy PackageDexOptimizer implementation.
+        int dexoptFlags = isProfileGuidedCompilerFilter(compilerFilter)
+                ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
+                : 0;
+
         performDexOptTraced(new DexoptOptions(pkg.getPackageName(), REASON_BOOT_AFTER_OTA,
-                compilerFilter, null /* splitName */, 0 /* dexoptFlags */));
+                compilerFilter, null /* splitName */, dexoptFlags));
     }
 
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
@@ -622,16 +634,21 @@
     }
 
     public boolean performDexOptMode(@NonNull Computer snapshot, String packageName,
-            boolean checkProfiles, String targetCompilerFilter, boolean force,
-            boolean bootComplete, String splitName) {
+            String targetCompilerFilter, boolean force, boolean bootComplete, String splitName) {
         if (!PackageManagerServiceUtils.isSystemOrRootOrShell()
                 && !isCallerInstallerForPackage(snapshot, packageName)) {
             throw new SecurityException("performDexOptMode");
         }
 
-        int flags = (checkProfiles ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES : 0)
-                | (force ? DexoptOptions.DEXOPT_FORCE : 0)
+        int flags = (force ? DexoptOptions.DEXOPT_FORCE : 0)
                 | (bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0);
+
+        if (isProfileGuidedCompilerFilter(targetCompilerFilter)) {
+            // Set this flag whenever the filter is profile guided, to align with ART Service
+            // behavior.
+            flags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
+        }
+
         return performDexOpt(new DexoptOptions(packageName, REASON_CMDLINE,
                 targetCompilerFilter, splitName, flags));
     }
@@ -644,7 +661,7 @@
         final InstallSource installSource = packageState.getInstallSource();
 
         final PackageStateInternal installerPackageState =
-                snapshot.getPackageStateInternal(installSource.installerPackageName);
+                snapshot.getPackageStateInternal(installSource.mInstallerPackageName);
         if (installerPackageState == null) {
             return false;
         }
diff --git a/services/core/java/com/android/server/pm/GentleUpdateHelper.java b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
new file mode 100644
index 0000000..247ac90
--- /dev/null
+++ b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
@@ -0,0 +1,171 @@
+/*
+ * 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.WorkerThread;
+import android.app.ActivityThread;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageInstaller.InstallConstraints;
+import android.content.pm.PackageInstaller.InstallConstraintsResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Slog;
+
+import java.util.ArrayDeque;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A helper class to coordinate install flow for sessions with install constraints.
+ * These sessions will be pending and wait until the constraints are satisfied to
+ * resume installation.
+ */
+public class GentleUpdateHelper {
+    private static final String TAG = "GentleUpdateHelper";
+    private static final int JOB_ID = 235306967; // bug id
+    // The timeout used to determine whether the device is idle or not.
+    private static final long PENDING_CHECK_MILLIS = TimeUnit.SECONDS.toMillis(10);
+
+    /**
+     * A wrapper class used by JobScheduler to schedule jobs.
+     */
+    public static class Service extends JobService {
+        @Override
+        public boolean onStartJob(JobParameters params) {
+            try {
+                var pis = (PackageInstallerService) ActivityThread.getPackageManager()
+                        .getPackageInstaller();
+                var helper = pis.getGentleUpdateHelper();
+                helper.mHandler.post(helper::runIdleJob);
+            } catch (Exception e) {
+                Slog.e(TAG, "Failed to get PackageInstallerService", e);
+            }
+            return false;
+        }
+
+        @Override
+        public boolean onStopJob(JobParameters params) {
+            return false;
+        }
+    }
+
+    private static class PendingInstallConstraintsCheck {
+        public final List<String> packageNames;
+        public final InstallConstraints constraints;
+        public final CompletableFuture<InstallConstraintsResult> future;
+        PendingInstallConstraintsCheck(List<String> packageNames,
+                InstallConstraints constraints,
+                CompletableFuture<InstallConstraintsResult> future) {
+            this.packageNames = packageNames;
+            this.constraints = constraints;
+            this.future = future;
+        }
+    }
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final AppStateHelper mAppStateHelper;
+    // Worker thread only
+    private final ArrayDeque<PendingInstallConstraintsCheck> mPendingChecks = new ArrayDeque<>();
+    private boolean mHasPendingIdleJob;
+
+    GentleUpdateHelper(Context context, Looper looper, AppStateHelper appStateHelper) {
+        mContext = context;
+        mHandler = new Handler(looper);
+        mAppStateHelper = appStateHelper;
+    }
+
+    /**
+     * Checks if install constraints are satisfied for the given packages.
+     */
+    CompletableFuture<InstallConstraintsResult> checkInstallConstraints(
+            List<String> packageNames, InstallConstraints constraints) {
+        var future = new CompletableFuture<InstallConstraintsResult>();
+        mHandler.post(() -> {
+            var pendingCheck = new PendingInstallConstraintsCheck(
+                    packageNames, constraints, future);
+            if (constraints.isRequireDeviceIdle()) {
+                mPendingChecks.add(pendingCheck);
+                // JobScheduler doesn't provide queries about whether the device is idle.
+                // We schedule 2 tasks to determine device idle. If the idle job is executed
+                // before the delayed runnable, we know the device is idle.
+                // Note #processPendingCheck will be no-op for the task executed later.
+                scheduleIdleJob();
+                mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
+                        PENDING_CHECK_MILLIS);
+            } else {
+                processPendingCheck(pendingCheck, false);
+            }
+        });
+        return future;
+    }
+
+    @WorkerThread
+    private void scheduleIdleJob() {
+        if (mHasPendingIdleJob) {
+            // No need to schedule the job again
+            return;
+        }
+        mHasPendingIdleJob = true;
+        var componentName = new ComponentName(
+                mContext.getPackageName(), GentleUpdateHelper.Service.class.getName());
+        var jobInfo = new JobInfo.Builder(JOB_ID, componentName)
+                .setRequiresDeviceIdle(true)
+                .build();
+        var jobScheduler = mContext.getSystemService(JobScheduler.class);
+        jobScheduler.schedule(jobInfo);
+    }
+
+    @WorkerThread
+    private void runIdleJob() {
+        mHasPendingIdleJob = false;
+        processPendingChecksInIdle();
+    }
+
+    @WorkerThread
+    private void processPendingCheck(PendingInstallConstraintsCheck pendingCheck, boolean isIdle) {
+        var future = pendingCheck.future;
+        if (future.isDone()) {
+            return;
+        }
+        var constraints = pendingCheck.constraints;
+        var packageNames = mAppStateHelper.getDependencyPackages(pendingCheck.packageNames);
+        var constraintsSatisfied = (!constraints.isRequireDeviceIdle() || isIdle)
+                && (!constraints.isRequireAppNotForeground()
+                        || !mAppStateHelper.hasForegroundApp(packageNames))
+                && (!constraints.isRequireAppNotInteracting()
+                        || !mAppStateHelper.hasInteractingApp(packageNames))
+                && (!constraints.isRequireAppNotTopVisible()
+                        || !mAppStateHelper.hasTopVisibleApp(packageNames))
+                && (!constraints.isRequireNotInCall()
+                        || !mAppStateHelper.isInCall());
+        future.complete(new InstallConstraintsResult((constraintsSatisfied)));
+    }
+
+    @WorkerThread
+    private void processPendingChecksInIdle() {
+        while (!mPendingChecks.isEmpty()) {
+            processPendingCheck(mPendingChecks.remove(), true);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 05a0adc..5c3890c 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -57,6 +57,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.permission.PermissionManager;
+import android.util.Log;
 
 import com.android.internal.R;
 import com.android.internal.content.InstallLocationUtils;
@@ -964,8 +965,12 @@
             boolean checkProfiles, String targetCompilerFilter, boolean force,
             boolean bootComplete, String splitName) {
         final Computer snapshot = snapshot();
-        return mDexOptHelper.performDexOptMode(snapshot, packageName, checkProfiles,
-                targetCompilerFilter, force, bootComplete, splitName);
+        if (!checkProfiles) {
+            // There is no longer a flag to skip profile checking.
+            Log.w(PackageManagerService.TAG, "Ignored checkProfiles=false flag");
+        }
+        return mDexOptHelper.performDexOptMode(
+                snapshot, packageName, targetCompilerFilter, force, bootComplete, splitName);
     }
 
     /**
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/InstallArgs.java b/services/core/java/com/android/server/pm/InstallArgs.java
index a94a4e2..ced547c 100644
--- a/services/core/java/com/android/server/pm/InstallArgs.java
+++ b/services/core/java/com/android/server/pm/InstallArgs.java
@@ -59,6 +59,7 @@
     final boolean mForceQueryableOverride;
     final int mDataLoaderType;
     final int mPackageSource;
+    final boolean mKeepApplicationEnabledSetting;
 
     // The list of instruction sets supported by this app. This is currently
     // only used during the rmdex() phase to clean up resources. We can get rid of this
@@ -72,7 +73,8 @@
             List<String> allowlistedRestrictedPermissions,
             int autoRevokePermissionsMode, String traceMethod, int traceCookie,
             SigningDetails signingDetails, int installReason, int installScenario,
-            boolean forceQueryableOverride, int dataLoaderType, int packageSource) {
+            boolean forceQueryableOverride, int dataLoaderType, int packageSource,
+            boolean keepApplicationEnabledSetting) {
         mOriginInfo = originInfo;
         mMoveInfo = moveInfo;
         mInstallFlags = installFlags;
@@ -93,6 +95,7 @@
         mForceQueryableOverride = forceQueryableOverride;
         mDataLoaderType = dataLoaderType;
         mPackageSource = packageSource;
+        mKeepApplicationEnabledSetting = keepApplicationEnabledSetting;
     }
 
     /**
@@ -104,7 +107,7 @@
                 null, null, instructionSets, null, null, null, MODE_DEFAULT, null, 0,
                 SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN,
                 PackageManager.INSTALL_SCENARIO_DEFAULT, false, DataLoaderType.NONE,
-                PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
+                PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, false);
         mCodeFile = (codePath != null) ? new File(codePath) : null;
     }
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 9d007c9..7553370 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -20,6 +20,7 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
 import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_PERMISSION_GROUP;
+import static android.content.pm.PackageManager.INSTALL_FAILED_DEPRECATED_SDK_VERSION;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION_GROUP;
@@ -136,6 +137,7 @@
 import android.os.incremental.IncrementalStorage;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
+import android.provider.DeviceConfig;
 import android.stats.storage.StorageEnums;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -150,7 +152,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.security.VerityUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
@@ -175,6 +176,7 @@
 import com.android.server.pm.pkg.component.ParsedPermissionGroup;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.rollback.RollbackManagerInternal;
+import com.android.server.security.FileIntegrityService;
 import com.android.server.utils.WatchedArrayMap;
 import com.android.server.utils.WatchedLongSparseArray;
 
@@ -308,9 +310,9 @@
         // previous device state.
         InstallSource installSource = request.getInstallSource();
         if (installSource != null) {
-            if (installSource.initiatingPackageName != null) {
+            if (installSource.mInitiatingPackageName != null) {
                 final PackageSetting ips = mPm.mSettings.getPackageLPr(
-                        installSource.initiatingPackageName);
+                        installSource.mInitiatingPackageName);
                 if (ips != null) {
                     installSource = installSource.setInitiatingPackageSignatures(
                             ips.getSignatures());
@@ -427,7 +429,7 @@
             mPm.snapshotComputer().checkPackageFrozen(pkgName);
         }
 
-        final boolean isReplace = request.isReplace();
+        final boolean isReplace = request.isInstallReplace();
         // Also need to kill any apps that are dependent on the library, except the case of
         // installation of new version static shared library.
         if (clientLibPkgs != null) {
@@ -463,7 +465,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) {
@@ -762,7 +765,7 @@
                 || (installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
 
         if (ps != null && doSnapshotOrRestore) {
-            final String seInfo = AndroidPackageUtils.getSeInfo(request.getPkg(), ps);
+            final String seInfo = ps.getSeInfo();
             final RollbackManagerInternal rollbackManager =
                     mInjector.getLocalService(RollbackManagerInternal.class);
             rollbackManager.snapshotAndRestoreUserData(packageName,
@@ -813,6 +816,7 @@
             for (InstallRequest request : requests) {
                 try {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
+                    request.onPrepareStarted();
                     preparePackageLI(request);
                 } catch (PrepareFailure prepareFailure) {
                     request.setError(prepareFailure.error,
@@ -821,6 +825,7 @@
                     request.setOriginPermission(prepareFailure.mConflictingPermission);
                     return;
                 } finally {
+                    request.onPrepareFinished();
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
 
@@ -833,11 +838,13 @@
                 request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
                 final String packageName = packageToScan.getPackageName();
                 try {
+                    request.onScanStarted();
                     final ScanResult scanResult = scanPackageTracedLI(request.getParsedPackage(),
                             request.getParseFlags(), request.getScanFlags(),
                             System.currentTimeMillis(), request.getUser(),
                             request.getAbiOverride());
                     request.setScanResult(scanResult);
+                    request.onScanFinished();
                     if (!scannedPackages.add(packageName)) {
                         request.setError(
                                 PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
@@ -870,7 +877,7 @@
                 }
             }
 
-            Map<String, ReconciledPackage> reconciledPackages;
+            List<ReconciledPackage> reconciledPackages;
             synchronized (mPm.mLock) {
                 try {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
@@ -965,7 +972,7 @@
         final boolean isRollback =
                 request.getInstallReason() == PackageManager.INSTALL_REASON_ROLLBACK;
         @PackageManagerService.ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
-        if (request.isMoveInstall()) {
+        if (request.isInstallMove()) {
             // moving a complete application; perform an initial scan on the new install location
             scanFlags |= SCAN_INITIAL;
         }
@@ -1012,6 +1019,28 @@
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
+        // If the minimum installable SDK version enforcement is enabled, block the install
+        // of apps using a lower target SDK version than required. This helps improve security
+        // and privacy as malware can target older SDK versions to avoid enforcement of new API
+        // behavior.
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                "MinInstallableTargetSdk__install_block_enabled",
+                false)) {
+            int minInstallableTargetSdk =
+                    DeviceConfig.getInt(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                            "MinInstallableTargetSdk__min_installable_target_sdk",
+                            0);
+            if (parsedPackage.getTargetSdkVersion() < minInstallableTargetSdk) {
+                Slog.w(TAG, "App " + parsedPackage.getPackageName()
+                        + " targets deprecated sdk version");
+                throw new PrepareFailure(INSTALL_FAILED_DEPRECATED_SDK_VERSION,
+                        "App package must target at least version "
+                                + minInstallableTargetSdk);
+            }
+        } else {
+            Slog.i(TAG, "Minimum installable target sdk enforcement not enabled");
+        }
+
         // Instant apps have several additional install-time checks.
         if (instantApp) {
             if (parsedPackage.getTargetSdkVersion() < Build.VERSION_CODES.O) {
@@ -1348,7 +1377,7 @@
             }
         }
 
-        if (request.isMoveInstall()) {
+        if (request.isInstallMove()) {
             // We did an in-place move, so dex is ready to roll
             scanFlags |= SCAN_NO_DEX;
             scanFlags |= SCAN_MOVE;
@@ -1398,7 +1427,7 @@
             doRenameLI(request, parsedPackage);
 
             try {
-                setUpFsVerityIfPossible(parsedPackage);
+                setUpFsVerity(parsedPackage);
             } catch (Installer.InstallerException | IOException | DigestException
                     | NoSuchAlgorithmException e) {
                 throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR,
@@ -1561,7 +1590,7 @@
                 removedInfo.mUid = oldPackage.getUid();
                 removedInfo.mRemovedPackage = oldPackage.getPackageName();
                 removedInfo.mInstallerPackageName =
-                        ps.getInstallSource().installerPackageName;
+                        ps.getInstallSource().mInstallerPackageName;
                 removedInfo.mIsStaticSharedLib =
                         parsedPackage.getStaticSharedLibraryName() != null;
                 removedInfo.mIsUpdate = true;
@@ -1667,7 +1696,7 @@
             ParsedPackage parsedPackage) throws PrepareFailure {
         final int status = request.getReturnCode();
         final String statusMsg = request.getReturnMsg();
-        if (request.isMoveInstall()) {
+        if (request.isInstallMove()) {
             if (status != PackageManager.INSTALL_SUCCEEDED) {
                 mRemovePackageHelper.cleanUpForMoveInstall(request.getMoveToUuid(),
                         request.getMovePackageName(), request.getMoveFromCodePath());
@@ -1795,13 +1824,10 @@
     }
 
     /**
-     * Set up fs-verity for the given package if possible.  This requires a feature flag of system
-     * property to be enabled only if the kernel supports fs-verity.
-     *
-     * <p>When the feature flag is set to legacy mode, only APK is supported (with some experimental
-     * kernel patches). In normal mode, all file format can be supported.
+     * Set up fs-verity for the given package. For older devices that do not support fs-verity,
+     * this is a no-op.
      */
-    private void setUpFsVerityIfPossible(AndroidPackage pkg) throws Installer.InstallerException,
+    private void setUpFsVerity(AndroidPackage pkg) throws Installer.InstallerException,
             PrepareFailure, IOException, DigestException, NoSuchAlgorithmException {
         if (!PackageManagerServiceUtils.isApkVerityEnabled()) {
             return;
@@ -1814,8 +1840,6 @@
 
         // Collect files we care for fs-verity setup.
         ArrayMap<String, String> fsverityCandidates = new ArrayMap<>();
-        // NB: These files will become only accessible if the signing key is loaded in kernel's
-        // .fs-verity keyring.
         fsverityCandidates.put(pkg.getBaseApkPath(),
                 VerityUtils.getFsveritySignatureFilePath(pkg.getBaseApkPath()));
 
@@ -1835,18 +1859,28 @@
             }
         }
 
+        var fis = FileIntegrityService.getService();
         for (Map.Entry<String, String> entry : fsverityCandidates.entrySet()) {
-            final String filePath = entry.getKey();
-            final String signaturePath = entry.getValue();
-
-            // fs-verity is optional for now.  Only set up if signature is provided.
-            if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) {
-                try {
-                    VerityUtils.setUpFsverity(filePath, signaturePath);
-                } catch (IOException e) {
-                    throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
-                            "Failed to enable fs-verity: " + e);
+            try {
+                final String filePath = entry.getKey();
+                if (VerityUtils.hasFsverity(filePath)) {
+                    continue;
                 }
+
+                final String signaturePath = entry.getValue();
+                if (new File(signaturePath).exists()) {
+                    // If signature is provided, enable fs-verity first so that the file can be
+                    // measured for signature check below.
+                    VerityUtils.setUpFsverity(filePath, (byte[]) null);
+
+                    if (!fis.verifyPkcs7DetachedSignature(signaturePath, filePath)) {
+                        throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
+                                "fs-verity signature does not verify against a known key");
+                    }
+                }
+            } catch (IOException e) {
+                throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
+                        "Failed to enable fs-verity: " + e);
             }
         }
     }
@@ -1876,18 +1910,19 @@
     }
 
     @GuardedBy("mPm.mLock")
-    private void commitPackagesLocked(Map<String, ReconciledPackage> reconciledPackages,
+    private void commitPackagesLocked(List<ReconciledPackage> reconciledPackages,
             @NonNull int[] allUsers) {
         // TODO: remove any expected failures from this method; this should only be able to fail due
         //       to unavoidable errors (I/O, etc.)
-        for (ReconciledPackage reconciledPkg : reconciledPackages.values()) {
+        for (ReconciledPackage reconciledPkg : reconciledPackages) {
             final InstallRequest installRequest = reconciledPkg.mInstallRequest;
             final ParsedPackage parsedPackage = installRequest.getParsedPackage();
             final String packageName = parsedPackage.getPackageName();
             final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
             final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
 
-            if (installRequest.isReplace()) {
+            installRequest.onCommitStarted();
+            if (installRequest.isInstallReplace()) {
                 AndroidPackage oldPackage = mPm.mPackages.get(packageName);
 
                 // Set the update and install times
@@ -1904,7 +1939,7 @@
                         mPm.mAppsFilter.getVisibilityAllowList(mPm.snapshotComputer(),
                                 installRequest.getScannedPackageSetting(),
                                 allUsers, mPm.mSettings.getPackagesLocked());
-                if (installRequest.isSystem()) {
+                if (installRequest.isInstallSystem()) {
                     // Remove existing system package
                     removePackageHelper.removePackage(oldPackage, true);
                     if (!disableSystemPackageLPw(oldPackage)) {
@@ -1972,6 +2007,7 @@
                 mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
                 mPm.updateInstantAppInstallerLocked(packageName);
             }
+            installRequest.onCommitFinished();
         }
         ApplicationPackageManager.invalidateGetPackagesForUidCache();
     }
@@ -2008,7 +2044,8 @@
                         Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName);
                     }
                     // Enable system package for requested users
-                    if (installedForUsers != null) {
+                    if (installedForUsers != null
+                            && !installRequest.isKeepApplicationEnabledSetting()) {
                         for (int origUserId : installedForUsers) {
                             if (userId == UserHandle.USER_ALL || userId == origUserId) {
                                 ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT,
@@ -2058,16 +2095,22 @@
 
                 if (userId != UserHandle.USER_ALL) {
                     // It's implied that when a user requests installation, they want the app to
-                    // be installed and enabled.
+                    // be installed and enabled. The caller, however, can explicitly specify to
+                    // keep the existing enabled state.
                     ps.setInstalled(true, userId);
-                    ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
+                    if (!installRequest.isKeepApplicationEnabledSetting()) {
+                        ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId,
+                                installerPackageName);
+                    }
                 } else if (allUsers != null) {
                     // The caller explicitly specified INSTALL_ALL_USERS flag.
                     // Thus, updating the settings to install the app for all users.
                     for (int currentUserId : allUsers) {
                         ps.setInstalled(true, currentUserId);
-                        ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId,
-                                installerPackageName);
+                        if (!installRequest.isKeepApplicationEnabledSetting()) {
+                            ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, currentUserId,
+                                    installerPackageName);
+                        }
                     }
                 }
 
@@ -2196,9 +2239,9 @@
      * locks on {@link com.android.server.pm.PackageManagerService.mLock}.
      */
     @GuardedBy("mPm.mInstallLock")
-    private void executePostCommitStepsLIF(Map<String, ReconciledPackage> reconciledPackages) {
+    private void executePostCommitStepsLIF(List<ReconciledPackage> reconciledPackages) {
         final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
-        for (ReconciledPackage reconciledPkg : reconciledPackages.values()) {
+        for (ReconciledPackage reconciledPkg : reconciledPackages) {
             final InstallRequest installRequest = reconciledPkg.mInstallRequest;
             final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
             final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0);
@@ -2215,6 +2258,28 @@
                 }
                 incrementalStorages.add(storage);
             }
+
+            // Enabling fs-verity is a blocking operation. To reduce the impact to the install time,
+            // run in a background thread.
+            final ArrayList<String> apkPaths = new ArrayList<>();
+            apkPaths.add(pkg.getBaseApkPath());
+            if (pkg.getSplitCodePaths() != null) {
+                Collections.addAll(apkPaths, pkg.getSplitCodePaths());
+            }
+            mInjector.getBackgroundHandler().post(() -> {
+                try {
+                    for (String path : apkPaths) {
+                        if (!VerityUtils.hasFsverity(path)) {
+                            VerityUtils.setUpFsverity(path, (byte[]) null);
+                        }
+                    }
+                } catch (IOException e) {
+                    // There's nothing we can do if the setup failed. Since fs-verity is
+                    // optional, just ignore the error for now.
+                    Slog.e(TAG, "Failed to fully enable fs-verity to " + packageName);
+                }
+            });
+
             // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
             mAppDataHelper.prepareAppDataPostCommitLIF(pkg, 0);
             if (installRequest.isClearCodeCache()) {
@@ -2222,7 +2287,7 @@
                         FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
                                 | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
             }
-            if (installRequest.isReplace()) {
+            if (installRequest.isInstallReplace()) {
                 mDexManager.notifyPackageUpdated(pkg.getPackageName(),
                         pkg.getBaseApkPath(), pkg.getSplitCodePaths());
             }
@@ -2329,45 +2394,6 @@
                 incrementalStorages);
     }
 
-    public int installLocationPolicy(PackageInfoLite pkgLite, int installFlags) {
-        String packageName = pkgLite.packageName;
-        int installLocation = pkgLite.installLocation;
-        // reader
-        synchronized (mPm.mLock) {
-            // Currently installed package which the new package is attempting to replace or
-            // null if no such package is installed.
-            AndroidPackage installedPkg = mPm.mPackages.get(packageName);
-
-            if (installedPkg != null) {
-                if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
-                    // Check for updated system application.
-                    if (installedPkg.isSystem()) {
-                        return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
-                    } else {
-                        // If current upgrade specifies particular preference
-                        if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
-                            // Application explicitly specified internal.
-                            return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
-                        } else if (
-                                installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
-                            // App explicitly prefers external. Let policy decide
-                        } else {
-                            // Prefer previous location
-                            if (installedPkg.isExternalStorage()) {
-                                return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
-                            }
-                            return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
-                        }
-                    }
-                } else {
-                    // Invalid install. Return error code
-                    return InstallLocationUtils.RECOMMEND_FAILED_ALREADY_EXISTS;
-                }
-            }
-        }
-        return pkgLite.recommendedInstallLocation;
-    }
-
     Pair<Integer, String> verifyReplacingVersionCode(PackageInfoLite pkgLite,
             long requiredInstalledVersionCode, int installFlags) {
         if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
@@ -2650,11 +2676,29 @@
                 }
             }
 
+            Bundle extras = new Bundle();
+            extras.putInt(Intent.EXTRA_UID, request.getUid());
+            if (update) {
+                extras.putBoolean(Intent.EXTRA_REPLACING, true);
+            }
+            extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
+
+            // If a package is a static shared library, then only the installer of the package
+            // should get the broadcast.
+            if (installerPackageName != null
+                    && request.getPkg().getStaticSharedLibraryName() != null) {
+                mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
+                        extras, 0 /*flags*/,
+                        installerPackageName, null /*finishedReceiver*/,
+                        request.getNewUsers(), null /* instantUserIds*/,
+                        null /* broadcastAllowList */, null);
+            }
+
             // Send installed broadcasts if the package is not a static shared lib.
             if (request.getPkg().getStaticSharedLibraryName() == null) {
                 mPm.mProcessLoggingHandler.invalidateBaseApkHash(request.getPkg().getBaseApkPath());
 
-                // Send added for users that see the package for the first time
+                // Send PACKAGE_ADDED broadcast for users that see the package for the first time
                 // sendPackageAddedForNewUsers also deals with system apps
                 int appId = UserHandle.getAppId(request.getUid());
                 boolean isSystem = request.getPkg().isSystem();
@@ -2662,13 +2706,9 @@
                         isSystem || virtualPreload, virtualPreload /*startReceiver*/, appId,
                         firstUserIds, firstInstantUserIds, dataLoaderType);
 
-                // Send added for users that don't see the package for the first time
-                Bundle extras = new Bundle();
-                extras.putInt(Intent.EXTRA_UID, request.getUid());
-                if (update) {
-                    extras.putBoolean(Intent.EXTRA_REPLACING, true);
-                }
-                extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
+                // Send PACKAGE_ADDED broadcast for users that don't see
+                // the package for the first time
+
                 // Send to all running apps.
                 final SparseArray<int[]> newBroadcastAllowList;
                 synchronized (mPm.mLock) {
@@ -2681,8 +2721,8 @@
                         extras, 0 /*flags*/,
                         null /*targetPackage*/, null /*finishedReceiver*/,
                         updateUserIds, instantUserIds, newBroadcastAllowList, null);
+                // Send to the installer, even if it's not running.
                 if (installerPackageName != null) {
-                    // Send to the installer, even if it's not running.
                     mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                             extras, 0 /*flags*/,
                             installerPackageName, null /*finishedReceiver*/,
@@ -3636,7 +3676,7 @@
             boolean appIdCreated = false;
             try {
                 final String pkgName = scanResult.mPkgSetting.getPackageName();
-                final Map<String, ReconciledPackage> reconcileResult =
+                final List<ReconciledPackage> reconcileResult =
                         ReconcilePackageUtils.reconcilePackages(
                                 Collections.singletonList(installRequest),
                                 mPm.mPackages, Collections.singletonMap(pkgName,
@@ -3648,7 +3688,7 @@
                 } else {
                     installRequest.setScannedPackageSettingAppId(Process.INVALID_UID);
                 }
-                commitReconciledScanResultLocked(reconcileResult.get(pkgName),
+                commitReconciledScanResultLocked(reconcileResult.get(0),
                         mPm.mUserManager.getUserIds());
             } catch (PackageManagerException e) {
                 if (appIdCreated) {
@@ -3889,13 +3929,20 @@
                 && !pkgSetting.getPathString().equals(parsedPackage.getPath());
         final boolean newPkgVersionGreater = pkgAlreadyExists
                 && parsedPackage.getLongVersionCode() > pkgSetting.getVersionCode();
+        final boolean newSharedUserSetting = pkgAlreadyExists
+                && (initialScanRequest.mOldSharedUserSetting
+                != initialScanRequest.mSharedUserSetting);
         final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated
-                && newPkgChangedPaths && newPkgVersionGreater;
+                && newPkgChangedPaths && (newPkgVersionGreater || newSharedUserSetting);
         if (isSystemPkgBetter) {
             // The version of the application on /system is greater than the version on
             // /data. Switch back to the application on /system.
             // It's safe to assume the application on /system will correctly scan. If not,
             // there won't be a working copy of the application.
+            // Also, if the sharedUserSetting of the application on /system is different
+            // from the sharedUserSetting on /data, switch back to the application on /system.
+            // We should trust the sharedUserSetting on /system, even if the application
+            // version on /system is smaller than the version on /data.
             synchronized (mPm.mLock) {
                 // just remove the loaded entries from package lists
                 mPm.mPackages.remove(pkgSetting.getPackageName());
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 573082a..5974a9c 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -16,10 +16,13 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED;
 import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
 import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
+import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
 import static android.os.Process.INVALID_UID;
 
+import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
 import static com.android.server.pm.PackageManagerService.TAG;
 
 import android.annotation.NonNull;
@@ -57,8 +60,10 @@
     @Nullable
     private PackageRemovedInfo mRemovedInfo;
 
-    private @PackageManagerService.ScanFlags int mScanFlags;
-    private @ParsingPackageUtils.ParseFlags int mParseFlags;
+    @PackageManagerService.ScanFlags
+    private int mScanFlags;
+    @ParsingPackageUtils.ParseFlags
+    private int mParseFlags;
     private boolean mReplace;
 
     @Nullable /* The original Package if it is being replaced, otherwise {@code null} */
@@ -76,7 +81,7 @@
     /** Package Installed Info */
     @Nullable
     private String mName;
-    private int mUid = -1;
+    private int mUid = INVALID_UID;
     // The set of users that originally had this package installed.
     @Nullable
     private int[] mOrigUsers;
@@ -105,6 +110,14 @@
     @Nullable
     private ScanResult mScanResult;
 
+    private boolean mIsInstallInherit;
+    private boolean mIsInstallForUsers;
+
+    @Nullable
+    private final PackageMetrics mPackageMetrics;
+    private final int mSessionId;
+    private final int mRequireUserAction;
+
     // New install
     InstallRequest(InstallingSession params) {
         mUserId = params.getUser().getIdentifier();
@@ -115,7 +128,12 @@
                 params.mAutoRevokePermissionsMode,
                 params.mTraceMethod, params.mTraceCookie, params.mSigningDetails,
                 params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
-                params.mDataLoaderType, params.mPackageSource);
+                params.mDataLoaderType, params.mPackageSource,
+                params.mKeepApplicationEnabledSetting);
+        mPackageMetrics = new PackageMetrics(this);
+        mIsInstallInherit = params.mIsInherit;
+        mSessionId = params.mSessionId;
+        mRequireUserAction = params.mRequireUserAction;
     }
 
     // Install existing package as user
@@ -127,6 +145,10 @@
         mPkg = pkg;
         mNewUsers = newUsers;
         mPostInstallRunnable = runnable;
+        mPackageMetrics = new PackageMetrics(this);
+        mIsInstallForUsers = true;
+        mSessionId = -1;
+        mRequireUserAction = USER_ACTION_UNSPECIFIED;
     }
 
     // addForInit
@@ -143,6 +165,9 @@
         mParseFlags = parseFlags;
         mScanFlags = scanFlags;
         mScanResult = scanResult;
+        mPackageMetrics = null; // No logging from this code path
+        mSessionId = -1;
+        mRequireUserAction = USER_ACTION_UNSPECIFIED;
     }
 
     @Nullable
@@ -200,7 +225,7 @@
         return mInstallArgs == null ? null : mInstallArgs.mObserver;
     }
 
-    public boolean isMoveInstall() {
+    public boolean isInstallMove() {
         return mInstallArgs != null && mInstallArgs.mMoveInfo != null;
     }
 
@@ -278,7 +303,7 @@
         return mRemovedInfo != null ? mRemovedInfo.mRemovedPackage : null;
     }
 
-    public boolean isInstallForExistingUser() {
+    public boolean isInstallExistingForUser() {
         return mInstallArgs == null;
     }
 
@@ -290,7 +315,12 @@
     @Nullable
     public String getInstallerPackageName() {
         return (mInstallArgs != null && mInstallArgs.mInstallSource != null)
-                ? mInstallArgs.mInstallSource.installerPackageName : null;
+                ? mInstallArgs.mInstallSource.mInstallerPackageName : null;
+    }
+
+    public int getInstallerPackageUid() {
+        return (mInstallArgs != null && mInstallArgs.mInstallSource != null)
+                ? mInstallArgs.mInstallSource.mInstallerPackageUid : INVALID_UID;
     }
 
     public int getDataLoaderType() {
@@ -319,7 +349,7 @@
 
     @Nullable
     public String getSourceInstallerPackageName() {
-        return mInstallArgs.mInstallSource.installerPackageName;
+        return mInstallArgs.mInstallSource.mInstallerPackageName;
     }
 
     public boolean isRollback() {
@@ -380,11 +410,13 @@
         return mParsedPackage;
     }
 
-    public @ParsingPackageUtils.ParseFlags int getParseFlags() {
+    @ParsingPackageUtils.ParseFlags
+    public int getParseFlags() {
         return mParseFlags;
     }
 
-    public @PackageManagerService.ScanFlags int getScanFlags() {
+    @PackageManagerService.ScanFlags
+    public int getScanFlags() {
         return mScanFlags;
     }
 
@@ -406,14 +438,27 @@
         return mClearCodeCache;
     }
 
-    public boolean isReplace() {
+    public boolean isInstallReplace() {
         return mReplace;
     }
 
-    public boolean isSystem() {
+    public boolean isInstallSystem() {
         return mSystem;
     }
 
+    public boolean isInstallInherit() {
+        return mIsInstallInherit;
+    }
+
+    public boolean isInstallForUsers() {
+        return mIsInstallForUsers;
+    }
+
+    public boolean isInstallFromAdb() {
+        return mInstallArgs != null
+                && (mInstallArgs.mInstallFlags & PackageManager.INSTALL_FROM_ADB) != 0;
+    }
+
     @Nullable
     public PackageSetting getOriginalPackageSetting() {
         return mOriginalPs;
@@ -454,6 +499,10 @@
         return mScanResult.mChangedAbiCodePath;
     }
 
+    public boolean isKeepApplicationEnabledSetting() {
+        return mInstallArgs == null ? false : mInstallArgs.mKeepApplicationEnabledSetting;
+    }
+
     public boolean isForceQueryableOverride() {
         return mInstallArgs != null && mInstallArgs.mForceQueryableOverride;
     }
@@ -520,6 +569,10 @@
         return mScanResult.mRequest.mIsPlatformPackage;
     }
 
+    public boolean isInstantInstall() {
+        return (mScanFlags & SCAN_AS_INSTANT_APP) != 0;
+    }
+
     public void assertScanResultExists() {
         if (mScanResult == null) {
             // Should not happen. This indicates a bug in the installation code flow
@@ -529,7 +582,14 @@
                 Slog.e(TAG, "ScanResult is null and it should not happen");
             }
         }
+    }
 
+    public int getSessionId() {
+        return mSessionId;
+    }
+
+    public int getRequireUserAction() {
+        return mRequireUserAction;
     }
 
     public void setScanFlags(int scanFlags) {
@@ -558,12 +618,18 @@
         setReturnCode(code);
         setReturnMessage(msg);
         Slog.w(TAG, msg);
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onInstallFailed();
+        }
     }
 
     public void setError(String msg, PackageManagerException e) {
         mReturnCode = e.error;
         setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e));
         Slog.w(TAG, msg, e);
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onInstallFailed();
+        }
     }
 
     public void setReturnCode(int returnCode) {
@@ -658,4 +724,60 @@
             mRemovedInfo.mRemovedAppId = appId;
         }
     }
+
+    public void onPrepareStarted() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepStarted(PackageMetrics.STEP_PREPARE);
+        }
+    }
+
+    public void onPrepareFinished() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepFinished(PackageMetrics.STEP_PREPARE);
+        }
+    }
+
+    public void onScanStarted() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepStarted(PackageMetrics.STEP_SCAN);
+        }
+    }
+
+    public void onScanFinished() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepFinished(PackageMetrics.STEP_SCAN);
+        }
+    }
+
+    public void onReconcileStarted() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepStarted(PackageMetrics.STEP_RECONCILE);
+        }
+    }
+
+    public void onReconcileFinished() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepFinished(PackageMetrics.STEP_RECONCILE);
+        }
+    }
+
+    public void onCommitStarted() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepStarted(PackageMetrics.STEP_COMMIT);
+        }
+    }
+
+    public void onCommitFinished() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepFinished(PackageMetrics.STEP_COMMIT);
+        }
+    }
+
+    public void onInstallCompleted() {
+        if (getReturnCode() == INSTALL_SUCCEEDED) {
+            if (mPackageMetrics != null) {
+                mPackageMetrics.onInstallSucceed();
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/InstallSource.java b/services/core/java/com/android/server/pm/InstallSource.java
index e5f7f71..dde9905 100644
--- a/services/core/java/com/android/server/pm/InstallSource.java
+++ b/services/core/java/com/android/server/pm/InstallSource.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static android.os.Process.INVALID_UID;
+
 import android.annotation.Nullable;
 import android.content.pm.PackageInstaller;
 
@@ -32,27 +34,27 @@
      * An instance of InstallSource representing an absence of knowledge of the source of
      * a package. Used in preference to null.
      */
-    static final InstallSource EMPTY = new InstallSource(null, null, null, null, false, false,
-            null, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
+    static final InstallSource EMPTY = new InstallSource(null, null, null, INVALID_UID, null,
+            false, false, null, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
 
     /** We also memoize this case because it is common - all un-updated system apps. */
     private static final InstallSource EMPTY_ORPHANED = new InstallSource(
-            null, null, null, null, true, false, null,
+            null, null, null, INVALID_UID, null, true, false, null,
             PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
 
     /**
      * The package that requested the installation, if known. May not correspond to a currently
-     * installed package if {@link #isInitiatingPackageUninstalled} is true.
+     * installed package if {@link #mIsInitiatingPackageUninstalled} is true.
      */
     @Nullable
-    final String initiatingPackageName;
+    final String mInitiatingPackageName;
 
     /**
      * The signing details of the initiating package, if known. Always null if
-     * {@link #initiatingPackageName} is null.
+     * {@link #mInitiatingPackageName} is null.
      */
     @Nullable
-    final PackageSignatures initiatingPackageSignatures;
+    final PackageSignatures mInitiatingPackageSignatures;
 
     /**
      * The package on behalf of which the initiating package requested the installation, if any.
@@ -61,64 +63,63 @@
      * verified by the framework.
      */
     @Nullable
-    final String originatingPackageName;
+    final String mOriginatingPackageName;
 
     /**
      * Package name of the app that installed this package (the installer of record). Note that
      * this may be modified.
      */
     @Nullable
-    final String installerPackageName;
+    final String mInstallerPackageName;
 
+    /**
+     * UID of the installer package, corresponding to the {@link #mInstallerPackageName}.
+     */
+    final int mInstallerPackageUid;
 
     /**
      * {@link android.content.Context#getAttributionTag()} of installing context.
      */
     @Nullable
-    final String installerAttributionTag;
+    final String mInstallerAttributionTag;
 
     /** Indicates if the package that was the installerPackageName has been uninstalled. */
-    final boolean isOrphaned;
+    final boolean mIsOrphaned;
 
     /**
      * Indicates if the package in initiatingPackageName has been uninstalled. Always false if
-     * {@link #initiatingPackageName} is null.
+     * {@link #mInitiatingPackageName} is null.
      */
-    final boolean isInitiatingPackageUninstalled;
+    final boolean mIsInitiatingPackageUninstalled;
 
-    final int packageSource;
+    final int mPackageSource;
 
     static InstallSource create(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            @Nullable String installerAttributionTag) {
-        return create(initiatingPackageName, originatingPackageName, installerPackageName,
-                installerAttributionTag, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
-    }
-
-    static InstallSource create(@Nullable String initiatingPackageName,
-            @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            @Nullable String installerAttributionTag, boolean isOrphaned,
+            int installerPackageUid, @Nullable String installerAttributionTag, boolean isOrphaned,
             boolean isInitiatingPackageUninstalled) {
         return create(initiatingPackageName, originatingPackageName, installerPackageName,
-                installerAttributionTag, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, isOrphaned,
+                installerPackageUid, installerAttributionTag,
+                PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, isOrphaned,
                 isInitiatingPackageUninstalled);
     }
 
     static InstallSource create(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            @Nullable String installerAttributionTag, int packageSource) {
+            int installerPackageUid, @Nullable String installerAttributionTag, int packageSource) {
         return create(initiatingPackageName, originatingPackageName, installerPackageName,
-                installerAttributionTag, packageSource, false, false);
+                installerPackageUid, installerAttributionTag, packageSource, false, false);
     }
 
     static InstallSource create(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            @Nullable String installerAttributionTag, int packageSource, boolean isOrphaned,
-            boolean isInitiatingPackageUninstalled) {
+            int installerPackageUid, @Nullable String installerAttributionTag, int packageSource,
+            boolean isOrphaned, boolean isInitiatingPackageUninstalled) {
         return createInternal(
                 intern(initiatingPackageName),
                 intern(originatingPackageName),
                 intern(installerPackageName),
+                installerPackageUid,
                 installerAttributionTag,
                 packageSource,
                 isOrphaned, isInitiatingPackageUninstalled, null);
@@ -126,8 +127,8 @@
 
     private static InstallSource createInternal(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            @Nullable String installerAttributionTag, int packageSource, boolean isOrphaned,
-            boolean isInitiatingPackageUninstalled,
+            int installerPackageUid, @Nullable String installerAttributionTag, int packageSource,
+            boolean isOrphaned, boolean isInitiatingPackageUninstalled,
             @Nullable PackageSignatures initiatingPackageSignatures) {
         if (initiatingPackageName == null && originatingPackageName == null
                 && installerPackageName == null && initiatingPackageSignatures == null
@@ -136,13 +137,14 @@
             return isOrphaned ? EMPTY_ORPHANED : EMPTY;
         }
         return new InstallSource(initiatingPackageName, originatingPackageName,
-                installerPackageName, installerAttributionTag, isOrphaned,
+                installerPackageName, installerPackageUid, installerAttributionTag, isOrphaned,
                 isInitiatingPackageUninstalled, initiatingPackageSignatures, packageSource
         );
     }
 
     private InstallSource(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
+            int installerPackageUid,
             @Nullable String installerAttributionTag, boolean isOrphaned,
             boolean isInitiatingPackageUninstalled,
             @Nullable PackageSignatures initiatingPackageSignatures,
@@ -151,53 +153,58 @@
             Preconditions.checkArgument(initiatingPackageSignatures == null);
             Preconditions.checkArgument(!isInitiatingPackageUninstalled);
         }
-        this.initiatingPackageName = initiatingPackageName;
-        this.originatingPackageName = originatingPackageName;
-        this.installerPackageName = installerPackageName;
-        this.installerAttributionTag = installerAttributionTag;
-        this.isOrphaned = isOrphaned;
-        this.isInitiatingPackageUninstalled = isInitiatingPackageUninstalled;
-        this.initiatingPackageSignatures = initiatingPackageSignatures;
-        this.packageSource = packageSource;
+        mInitiatingPackageName = initiatingPackageName;
+        mOriginatingPackageName = originatingPackageName;
+        mInstallerPackageName = installerPackageName;
+        mInstallerPackageUid = installerPackageUid;
+        mInstallerAttributionTag = installerAttributionTag;
+        mIsOrphaned = isOrphaned;
+        mIsInitiatingPackageUninstalled = isInitiatingPackageUninstalled;
+        mInitiatingPackageSignatures = initiatingPackageSignatures;
+        mPackageSource = packageSource;
     }
 
     /**
      * Return an InstallSource the same as this one except with the specified
-     * {@link #installerPackageName}.
+     * {@link #mInstallerPackageName}.
      */
-    InstallSource setInstallerPackage(@Nullable String installerPackageName) {
-        if (Objects.equals(installerPackageName, this.installerPackageName)) {
+    InstallSource setInstallerPackage(@Nullable String installerPackageName,
+            int installerPackageUid) {
+        if (Objects.equals(installerPackageName, mInstallerPackageName)) {
             return this;
         }
-        return createInternal(initiatingPackageName, originatingPackageName,
-                intern(installerPackageName), installerAttributionTag, packageSource, isOrphaned,
-                isInitiatingPackageUninstalled, initiatingPackageSignatures);
+        return createInternal(mInitiatingPackageName, mOriginatingPackageName,
+                intern(installerPackageName), installerPackageUid, mInstallerAttributionTag,
+                mPackageSource, mIsOrphaned, mIsInitiatingPackageUninstalled,
+                mInitiatingPackageSignatures);
     }
 
     /**
      * Return an InstallSource the same as this one except with the specified value for
-     * {@link #isOrphaned}.
+     * {@link #mIsOrphaned}.
      */
     InstallSource setIsOrphaned(boolean isOrphaned) {
-        if (isOrphaned == this.isOrphaned) {
+        if (isOrphaned == mIsOrphaned) {
             return this;
         }
-        return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
-                installerAttributionTag, packageSource, isOrphaned, isInitiatingPackageUninstalled,
-                initiatingPackageSignatures);
+        return createInternal(mInitiatingPackageName, mOriginatingPackageName,
+                mInstallerPackageName,
+                mInstallerPackageUid, mInstallerAttributionTag, mPackageSource, isOrphaned,
+                mIsInitiatingPackageUninstalled, mInitiatingPackageSignatures);
     }
 
     /**
      * Return an InstallSource the same as this one except with the specified
-     * {@link #initiatingPackageSignatures}.
+     * {@link #mInitiatingPackageSignatures}.
      */
     InstallSource setInitiatingPackageSignatures(@Nullable PackageSignatures signatures) {
-        if (signatures == initiatingPackageSignatures) {
+        if (signatures == mInitiatingPackageSignatures) {
             return this;
         }
-        return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
-                installerAttributionTag, packageSource, isOrphaned,
-                isInitiatingPackageUninstalled, signatures);
+        return createInternal(mInitiatingPackageName, mOriginatingPackageName,
+                mInstallerPackageName,
+                mInstallerPackageUid, mInstallerAttributionTag, mPackageSource, mIsOrphaned,
+                mIsInitiatingPackageUninstalled, signatures);
     }
 
     /**
@@ -210,12 +217,13 @@
         }
 
         boolean modified = false;
-        boolean isInitiatingPackageUninstalled = this.isInitiatingPackageUninstalled;
-        String originatingPackageName = this.originatingPackageName;
-        String installerPackageName = this.installerPackageName;
-        boolean isOrphaned = this.isOrphaned;
+        boolean isInitiatingPackageUninstalled = mIsInitiatingPackageUninstalled;
+        String originatingPackageName = mOriginatingPackageName;
+        String installerPackageName = mInstallerPackageName;
+        int installerPackageUid = mInstallerPackageUid;
+        boolean isOrphaned = mIsOrphaned;
 
-        if (packageName.equals(this.initiatingPackageName)) {
+        if (packageName.equals(mInitiatingPackageName)) {
             if (!isInitiatingPackageUninstalled) {
                 // In this case we deliberately do not clear the package name (and signatures).
                 // We allow an app to retrieve details of its own install initiator even after
@@ -230,6 +238,7 @@
         }
         if (packageName.equals(installerPackageName)) {
             installerPackageName = null;
+            installerPackageUid = INVALID_UID;
             isOrphaned = true;
             modified = true;
         }
@@ -238,9 +247,9 @@
             return this;
         }
 
-        return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
-                null, packageSource, isOrphaned,
-                isInitiatingPackageUninstalled, initiatingPackageSignatures);
+        return createInternal(mInitiatingPackageName, originatingPackageName, installerPackageName,
+                installerPackageUid, null, mPackageSource, isOrphaned,
+                isInitiatingPackageUninstalled, mInitiatingPackageSignatures);
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 16b3a81..2b6398a 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -17,6 +17,8 @@
 package com.android.server.pm;
 
 import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.content.pm.PackageInstaller.SessionParams.MODE_INHERIT_EXISTING;
+import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
 import static android.content.pm.PackageManager.INSTALL_STAGED;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
@@ -93,7 +95,12 @@
     final PackageManagerService mPm;
     final InstallPackageHelper mInstallPackageHelper;
     final RemovePackageHelper mRemovePackageHelper;
+    final boolean mIsInherit;
+    final int mSessionId;
+    final int mRequireUserAction;
+    final boolean mKeepApplicationEnabledSetting;
 
+    // For move install
     InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
             int installFlags, InstallSource installSource, String volumeUuid,
             UserHandle user, String packageAbiOverride, int packageSource,
@@ -121,9 +128,13 @@
         mRequiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
         mPackageSource = packageSource;
         mPackageLite = packageLite;
+        mIsInherit = false;
+        mSessionId = -1;
+        mRequireUserAction = USER_ACTION_UNSPECIFIED;
+        mKeepApplicationEnabledSetting = false;
     }
 
-    InstallingSession(File stagedDir, IPackageInstallObserver2 observer,
+    InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
             PackageInstaller.SessionParams sessionParams, InstallSource installSource,
             UserHandle user, SigningDetails signingDetails, int installerUid,
             PackageLite packageLite, PackageManagerService pm) {
@@ -134,7 +145,7 @@
         mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
         mMoveInfo = null;
         mInstallReason = fixUpInstallReason(
-                installSource.installerPackageName, installerUid, sessionParams.installReason);
+                installSource.mInstallerPackageName, installerUid, sessionParams.installReason);
         mInstallScenario = sessionParams.installScenario;
         mObserver = observer;
         mInstallFlags = sessionParams.installFlags;
@@ -151,6 +162,10 @@
         mRequiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode;
         mPackageSource = sessionParams.packageSource;
         mPackageLite = packageLite;
+        mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING;
+        mSessionId = sessionId;
+        mRequireUserAction = sessionParams.requireUserAction;
+        mKeepApplicationEnabledSetting = sessionParams.keepApplicationEnabledSetting;
     }
 
     @Override
@@ -211,7 +226,7 @@
      * policy if needed and then create install arguments based
      * on the install location.
      */
-    private void handleStartCopy() {
+    private void handleStartCopy(InstallRequest request) {
         if ((mInstallFlags & PackageManager.INSTALL_APEX) != 0) {
             mRet = INSTALL_SUCCEEDED;
             return;
@@ -227,6 +242,7 @@
                     pkgLite, mRequiredInstalledVersionCode, mInstallFlags);
             mRet = ret.first;
             if (mRet != INSTALL_SUCCEEDED) {
+                request.setError(mRet, "Failed to verify version code");
                 return;
             }
         }
@@ -246,14 +262,16 @@
         }
         mRet = overrideInstallLocation(pkgLite.packageName, pkgLite.recommendedInstallLocation,
                 pkgLite.installLocation);
+        if (mRet != INSTALL_SUCCEEDED) {
+            request.setError(mRet, "Failed to override installation location");
+        }
     }
 
-    private void handleReturnCode() {
-        processPendingInstall();
+    private void handleReturnCode(InstallRequest installRequest) {
+        processPendingInstall(installRequest);
     }
 
-    private void processPendingInstall() {
-        InstallRequest installRequest = new InstallRequest(this);
+    private void processPendingInstall(InstallRequest installRequest) {
         if (mRet == PackageManager.INSTALL_SUCCEEDED) {
             mRet = copyApk(installRequest);
         }
@@ -290,21 +308,26 @@
                 request.setCodeFile(mOriginInfo.mFile);
                 return PackageManager.INSTALL_SUCCEEDED;
             }
-
+            int ret;
             try {
                 final boolean isEphemeral =
                         (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
                 request.setCodeFile(
                         mPm.mInstallerService.allocateStageDirLegacy(mVolumeUuid, isEphemeral));
             } catch (IOException e) {
-                Slog.w(TAG, "Failed to create copy file: " + e);
-                return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                final String errorMessage = "Failed to create copy file";
+                Slog.w(TAG, errorMessage + ": " + e);
+                ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                request.setError(ret, errorMessage);
+                return ret;
             }
 
-            int ret = PackageManagerServiceUtils.copyPackage(
+            ret = PackageManagerServiceUtils.copyPackage(
                     mOriginInfo.mFile.getAbsolutePath(), request.getCodeFile());
             if (ret != PackageManager.INSTALL_SUCCEEDED) {
-                Slog.e(TAG, "Failed to copy package");
+                final String errorMessage = "Failed to copy package";
+                Slog.e(TAG, errorMessage);
+                request.setError(ret, errorMessage);
                 return ret;
             }
 
@@ -317,8 +340,10 @@
                 ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
                         request.getAbiOverride(), isIncremental);
             } catch (IOException e) {
-                Slog.e(TAG, "Copying native libraries failed", e);
+                final String errorMessage = "Copying native libraries failed";
+                Slog.e(TAG, errorMessage, e);
                 ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+                request.setError(ret, errorMessage);
             } finally {
                 IoUtils.closeQuietly(handle);
             }
@@ -340,8 +365,11 @@
                         mMoveInfo.mPackageName, mMoveInfo.mAppId, mMoveInfo.mSeInfo,
                         mMoveInfo.mTargetSdkVersion, mMoveInfo.mFromCodePath);
             } catch (Installer.InstallerException e) {
-                Slog.w(TAG, "Failed to move app", e);
-                return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+                final String errorMessage = "Failed to move app";
+                final int ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+                request.setError(ret, errorMessage);
+                Slog.w(TAG, errorMessage, e);
+                return ret;
             }
         }
 
@@ -444,8 +472,9 @@
         Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
                 System.identityHashCode(this));
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startInstall");
-        handleStartCopy();
-        handleReturnCode();
+        InstallRequest installRequest = new InstallRequest(this);
+        handleStartCopy(installRequest);
+        handleReturnCode(installRequest);
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
 
@@ -506,6 +535,7 @@
             mInstallPackageHelper.installPackagesTraced(installRequests);
 
             for (InstallRequest request : installRequests) {
+                request.onInstallCompleted();
                 doPostInstall(request);
             }
         }
@@ -531,7 +561,7 @@
     }
 
     private void cleanUpForFailedInstall(InstallRequest request) {
-        if (request.isMoveInstall()) {
+        if (request.isInstallMove()) {
             mRemovePackageHelper.cleanUpForMoveInstall(request.getMoveToUuid(),
                     request.getMovePackageName(), request.getMoveFromCodePath());
         } else {
@@ -572,19 +602,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);
@@ -626,11 +652,18 @@
             Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
                     System.identityHashCode(this));
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "start");
-            for (InstallingSession childInstallingSession : mChildInstallingSessions) {
-                childInstallingSession.handleStartCopy();
+
+            final int numChildSessions = mChildInstallingSessions.size();
+            final ArrayList<InstallRequest> installRequests = new ArrayList<>(numChildSessions);
+
+            for (int i = 0; i < numChildSessions; i++) {
+                final InstallingSession childSession = mChildInstallingSessions.get(i);
+                final InstallRequest installRequest = new InstallRequest(childSession);
+                installRequests.add(installRequest);
+                childSession.handleStartCopy(installRequest);
             }
-            for (InstallingSession childInstallingSession : mChildInstallingSessions) {
-                childInstallingSession.handleReturnCode();
+            for (int i = 0; i < numChildSessions; i++) {
+                mChildInstallingSessions.get(i).handleReturnCode(installRequests.get(i));
             }
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
@@ -638,6 +671,7 @@
         public void tryProcessInstallRequest(InstallRequest request) {
             mCurrentInstallRequests.add(request);
             if (mCurrentInstallRequests.size() != mChildInstallingSessions.size()) {
+                // Wait until all the installRequests have finished copying
                 return;
             }
             int completeStatus = PackageManager.INSTALL_SUCCEEDED;
diff --git a/services/core/java/com/android/server/pm/KnownPackages.java b/services/core/java/com/android/server/pm/KnownPackages.java
index dcf7152..154709a 100644
--- a/services/core/java/com/android/server/pm/KnownPackages.java
+++ b/services/core/java/com/android/server/pm/KnownPackages.java
@@ -47,6 +47,7 @@
             PACKAGE_RETAIL_DEMO,
             PACKAGE_RECENTS,
             PACKAGE_AMBIENT_CONTEXT_DETECTION,
+            PACKAGE_WEARABLE_SENSING,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface KnownPackage {
@@ -71,9 +72,10 @@
     public static final int PACKAGE_RETAIL_DEMO = 16;
     public static final int PACKAGE_RECENTS = 17;
     public static final int PACKAGE_AMBIENT_CONTEXT_DETECTION = 18;
+    public static final int PACKAGE_WEARABLE_SENSING = 19;
     // Integer value of the last known package ID. Increases as new ID is added to KnownPackage.
     // Please note the numbers should be continuous.
-    public static final int LAST_KNOWN_PACKAGE = PACKAGE_AMBIENT_CONTEXT_DETECTION;
+    public static final int LAST_KNOWN_PACKAGE = PACKAGE_WEARABLE_SENSING;
 
     private final DefaultAppProvider mDefaultAppProvider;
     private final String mRequiredInstallerPackage;
@@ -86,6 +88,7 @@
     private final String mConfiguratorPackage;
     private final String mIncidentReportApproverPackage;
     private final String mAmbientContextDetectionPackage;
+    private final String mWearableSensingPackage;
     private final String mAppPredictionServicePackage;
     private final String mCompanionPackage;
     private final String mRetailDemoPackage;
@@ -97,9 +100,9 @@
             String[] requiredVerifierPackages, String defaultTextClassifierPackage,
             String systemTextClassifierPackageName, String requiredPermissionControllerPackage,
             String configuratorPackage, String incidentReportApproverPackage,
-            String ambientContextDetectionPackage, String appPredictionServicePackage,
-            String companionPackageName, String retailDemoPackage,
-            String overlayConfigSignaturePackage, String recentsPackage) {
+            String ambientContextDetectionPackage, String wearableSensingPackage,
+            String appPredictionServicePackage, String companionPackageName,
+            String retailDemoPackage, String overlayConfigSignaturePackage, String recentsPackage) {
         mDefaultAppProvider = defaultAppProvider;
         mRequiredInstallerPackage = requiredInstallerPackage;
         mRequiredUninstallerPackage = requiredUninstallerPackage;
@@ -111,6 +114,7 @@
         mConfiguratorPackage = configuratorPackage;
         mIncidentReportApproverPackage = incidentReportApproverPackage;
         mAmbientContextDetectionPackage = ambientContextDetectionPackage;
+        mWearableSensingPackage = wearableSensingPackage;
         mAppPredictionServicePackage = appPredictionServicePackage;
         mCompanionPackage = companionPackageName;
         mRetailDemoPackage = retailDemoPackage;
@@ -165,6 +169,8 @@
                 return "Recents";
             case PACKAGE_AMBIENT_CONTEXT_DETECTION:
                 return "Ambient Context Detection";
+            case PACKAGE_WEARABLE_SENSING:
+                return "Wearable sensing";
         }
         return "Unknown";
     }
@@ -194,6 +200,8 @@
                 return snapshot.filterOnlySystemPackages(mIncidentReportApproverPackage);
             case PACKAGE_AMBIENT_CONTEXT_DETECTION:
                 return snapshot.filterOnlySystemPackages(mAmbientContextDetectionPackage);
+            case PACKAGE_WEARABLE_SENSING:
+                return snapshot.filterOnlySystemPackages(mWearableSensingPackage);
             case PACKAGE_APP_PREDICTOR:
                 return snapshot.filterOnlySystemPackages(mAppPredictionServicePackage);
             case PACKAGE_COMPANION:
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index b27373e..b66c6ac 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -129,7 +129,7 @@
         final InstallSource installSource = packageState.getInstallSource();
         final String packageAbiOverride = packageState.getCpuAbiOverride();
         final int appId = UserHandle.getAppId(pkg.getUid());
-        final String seinfo = AndroidPackageUtils.getSeInfo(pkg, packageState);
+        final String seinfo = packageState.getSeInfo();
         final String label = String.valueOf(pm.getApplicationLabel(
                 AndroidPackageUtils.generateAppInfoWithoutState(pkg)));
         final int targetSdkVersion = pkg.getTargetSdkVersion();
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 2a2410fd..49f3a3c 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -493,7 +493,7 @@
             // TODO: Consider adding 2 different APIs for primary and secondary dexopt.
             // installd only uses downgrade flag for secondary dex files and ignores it for
             // primary dex files.
-            String seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
+            String seInfo = pkgSetting.getSeInfo();
             boolean completed = getInstallerLI().dexopt(path, uid, pkg.getPackageName(), isa,
                     dexoptNeeded, oatDir, dexoptFlags, compilerFilter, pkg.getVolumeUuid(),
                     classLoaderContext, seInfo, /* downgrade= */ false ,
@@ -785,7 +785,9 @@
     private String getRealCompilerFilter(ApplicationInfo info, String targetCompilerFilter,
             boolean isUsedByOtherApps) {
         if (info.isEmbeddedDexUsed()) {
-            return "verify";
+            // Downgrade optimizing filters to "verify", but don't upgrade lower filters.
+            return DexFile.isOptimizedCompilerFilter(targetCompilerFilter) ? "verify"
+                                                                           : targetCompilerFilter;
         }
 
         // We force vmSafeMode on debuggable apps as well:
@@ -822,7 +824,9 @@
      */
     private String getRealCompilerFilter(AndroidPackage pkg, String targetCompilerFilter) {
         if (pkg.isUseEmbeddedDex()) {
-            return "verify";
+            // Downgrade optimizing filters to "verify", but don't upgrade lower filters.
+            return DexFile.isOptimizedCompilerFilter(targetCompilerFilter) ? "verify"
+                                                                           : targetCompilerFilter;
         }
 
         // We force vmSafeMode on debuggable apps as well:
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 4e8b0a1..93a119c 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -36,7 +36,6 @@
 import static com.android.server.pm.PackageManagerService.SEND_PENDING_BROADCAST;
 import static com.android.server.pm.PackageManagerService.TAG;
 import static com.android.server.pm.PackageManagerService.WRITE_PACKAGE_LIST;
-import static com.android.server.pm.PackageManagerService.WRITE_PACKAGE_RESTRICTIONS;
 import static com.android.server.pm.PackageManagerService.WRITE_SETTINGS;
 
 import android.content.Intent;
@@ -95,7 +94,7 @@
 
                 request.closeFreezer();
                 request.runPostInstallRunnable();
-                if (!request.isInstallForExistingUser()) {
+                if (!request.isInstallExistingForUser()) {
                     mInstallPackageHelper.handlePackagePostInstall(request, didRestore);
                 } else if (DEBUG_INSTALL) {
                     // No post-install when we run restore from installExistingPackageForUser
@@ -119,10 +118,7 @@
                 }
             } break;
             case WRITE_SETTINGS: {
-                mPm.writeSettings();
-            } break;
-            case WRITE_PACKAGE_RESTRICTIONS: {
-                mPm.writePendingRestrictions();
+                mPm.writeSettings(/*sync=*/false);
             } break;
             case WRITE_PACKAGE_LIST: {
                 mPm.writePackageList(msg.arg1);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index cc1d879..409d352 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -17,6 +17,7 @@
 package com.android.server.pm;
 
 import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO;
+import static android.os.Process.INVALID_UID;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -43,6 +44,7 @@
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.InstallConstraints;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageItemInfo;
@@ -53,12 +55,14 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
+import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SELinux;
@@ -87,6 +91,7 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.ImageUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.IoThread;
@@ -185,6 +190,7 @@
 
     private final InternalCallback mInternalCallback = new InternalCallback();
     private final PackageSessionVerifier mSessionVerifier;
+    private final GentleUpdateHelper mGentleUpdateHelper;
 
     /**
      * Used for generating session IDs. Since this is created at boot time,
@@ -271,6 +277,8 @@
         mStagingManager = new StagingManager(context);
         mSessionVerifier = new PackageSessionVerifier(context, mPm, mApexManager,
                 apexParserSupplier, mInstallThread.getLooper());
+        mGentleUpdateHelper = new GentleUpdateHelper(
+                context, mInstallThread.getLooper(), new AppStateHelper(context));
 
         LocalServices.getService(SystemServiceManager.class).startService(
                 new Lifecycle(context, this));
@@ -847,8 +855,18 @@
                 params.forceQueryableOverride = false;
             }
         }
+        int requestedInstallerPackageUid = INVALID_UID;
+        if (requestedInstallerPackageName != null) {
+            requestedInstallerPackageUid = snapshot.getPackageUid(requestedInstallerPackageName,
+                    0 /* flags */, userId);
+        }
+        if (requestedInstallerPackageUid == INVALID_UID) {
+            // Requested installer package is invalid, reset it
+            requestedInstallerPackageName = null;
+        }
+
         InstallSource installSource = InstallSource.create(installerPackageName,
-                originatingPackageName, requestedInstallerPackageName,
+                originatingPackageName, requestedInstallerPackageName, requestedInstallerPackageUid,
                 installerAttributionTag, params.packageSource);
         session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
                 mSilentUpdatePolicy, mInstallThread.getLooper(), mStagingManager, sessionId,
@@ -1222,6 +1240,33 @@
     }
 
     @Override
+    public void checkInstallConstraints(String installerPackageName, List<String> packageNames,
+            InstallConstraints constraints, RemoteCallback callback) {
+        Preconditions.checkArgument(packageNames != null);
+        Preconditions.checkArgument(constraints != null);
+        Preconditions.checkArgument(callback != null);
+
+        final var snapshot = mPm.snapshotComputer();
+        final int callingUid = Binder.getCallingUid();
+        if (!isCalledBySystemOrShell(callingUid)) {
+            for (var packageName : packageNames) {
+                var ps = snapshot.getPackageStateInternal(packageName);
+                if (ps == null || !TextUtils.equals(
+                        ps.getInstallSource().mInstallerPackageName, installerPackageName)) {
+                    throw new SecurityException("Caller has no access to package " + packageName);
+                }
+            }
+        }
+
+        var future = mGentleUpdateHelper.checkInstallConstraints(packageNames, constraints);
+        future.thenAccept(result -> {
+            var b = new Bundle();
+            b.putParcelable("result", result);
+            callback.sendResult(b);
+        });
+    }
+
+    @Override
     public void registerCallback(IPackageInstallerCallback callback, int userId) {
         final Computer snapshot = mPm.snapshotComputer();
         snapshot.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
@@ -1254,6 +1299,11 @@
     }
 
     @Override
+    public GentleUpdateHelper getGentleUpdateHelper() {
+        return mGentleUpdateHelper;
+    }
+
+    @Override
     public void bypassNextStagedInstallerCheck(boolean value) {
         if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
             throw new SecurityException("Caller not allowed to bypass staged installer check");
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5df73a6..3983acf 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -33,6 +33,7 @@
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
 import static android.content.pm.PackageManager.INSTALL_STAGED;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
+import static android.os.Process.INVALID_UID;
 import static android.system.OsConstants.O_CREAT;
 import static android.system.OsConstants.O_RDONLY;
 import static android.system.OsConstants.O_WRONLY;
@@ -217,6 +218,7 @@
     private static final String ATTR_SESSION_ID = "sessionId";
     private static final String ATTR_USER_ID = "userId";
     private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
+    private static final String ATTR_INSTALLER_PACKAGE_UID = "installerPackageUid";
     private static final String ATTR_INSTALLER_ATTRIBUTION_TAG = "installerAttributionTag";
     private static final String ATTR_INSTALLER_UID = "installerUid";
     private static final String ATTR_INITIATING_PACKAGE_NAME =
@@ -267,6 +269,8 @@
     private static final String ATTR_SIGNATURE = "signature";
     private static final String ATTR_CHECKSUM_KIND = "checksumKind";
     private static final String ATTR_CHECKSUM_VALUE = "checksumValue";
+    private static final String ATTR_KEEP_APPLICATION_ENABLED_SETTING =
+            "keepApplicationEnabledSetting";
 
     private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
     private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
@@ -380,6 +384,14 @@
     @GuardedBy("mLock")
     private boolean mStageDirInUse = false;
 
+    /**
+     * True if the verification is already in progress. This is used to prevent running
+     * verification again while one is already in progress which will break internal states.
+     *
+     * Worker thread only.
+     */
+    private boolean mVerificationInProgress = false;
+
     /** Permissions have been accepted by the user (see {@link #setPermissionsResult}) */
     @GuardedBy("mLock")
     private boolean mPermissionsManuallyAccepted = false;
@@ -804,7 +816,7 @@
         // It may wait for a long time to finish {@code dpmi.canSilentlyInstallPackage}.
         // Please don't acquire mLock before calling {@code dpmi.canSilentlyInstallPackage}.
         return dpmi != null && dpmi.canSilentlyInstallPackage(
-                getInstallSource().installerPackageName, mInstallerUid);
+                getInstallSource().mInstallerPackageName, mInstallerUid);
     }
 
     private static final int USER_ACTION_NOT_NEEDED = 0;
@@ -928,7 +940,7 @@
         mOriginalInstallerUid = installerUid;
         mInstallerUid = installerUid;
         mInstallSource = Objects.requireNonNull(installSource);
-        mOriginalInstallerPackageName = mInstallSource.installerPackageName;
+        mOriginalInstallerPackageName = mInstallSource.mInstallerPackageName;
         this.params = params;
         this.createdMillis = createdMillis;
         this.updatedMillis = createdMillis;
@@ -1039,8 +1051,8 @@
         synchronized (mLock) {
             info.sessionId = sessionId;
             info.userId = userId;
-            info.installerPackageName = mInstallSource.installerPackageName;
-            info.installerAttributionTag = mInstallSource.installerAttributionTag;
+            info.installerPackageName = mInstallSource.mInstallerPackageName;
+            info.installerAttributionTag = mInstallSource.mInstallerAttributionTag;
             info.resolvedBaseCodePath = (mResolvedBaseFile != null) ?
                     mResolvedBaseFile.getAbsolutePath() : null;
             info.progress = progress;
@@ -1088,6 +1100,7 @@
             info.requireUserAction = params.requireUserAction;
             info.installerUid = mInstallerUid;
             info.packageSource = params.packageSource;
+            info.keepApplicationEnabledSetting = params.keepApplicationEnabledSetting;
         }
         return info;
     }
@@ -1302,10 +1315,10 @@
         }
 
         final String installerPackageName;
-        if (!TextUtils.isEmpty(getInstallSource().initiatingPackageName)) {
-            installerPackageName = getInstallSource().initiatingPackageName;
+        if (!TextUtils.isEmpty(getInstallSource().mInitiatingPackageName)) {
+            installerPackageName = getInstallSource().mInitiatingPackageName;
         } else {
-            installerPackageName = getInstallSource().installerPackageName;
+            installerPackageName = getInstallSource().mInstallerPackageName;
         }
         if (TextUtils.isEmpty(installerPackageName)) {
             throw new IllegalStateException("Installer package is empty.");
@@ -1346,7 +1359,7 @@
             @NonNull IOnChecksumsReadyListener onChecksumsReadyListener) {
         assertCallerIsOwnerRootOrVerifier();
         final File file = new File(stageDir, name);
-        final String installerPackageName = getInstallSource().initiatingPackageName;
+        final String installerPackageName = getInstallSource().mInitiatingPackageName;
         try {
             mPm.requestFileChecksums(file, installerPackageName, optional, required,
                     trustedInstallers, onChecksumsReadyListener);
@@ -2101,8 +2114,8 @@
             }
 
             mInstallerUid = newOwnerAppInfo.uid;
-            mInstallSource = InstallSource.create(packageName, null, packageName, null,
-                    params.packageSource);
+            mInstallSource = InstallSource.create(packageName, null, packageName,
+                    mInstallerUid, null, params.packageSource);
         }
     }
 
@@ -2171,7 +2184,7 @@
         if (isInstallerDeviceOwnerOrAffiliatedProfileOwner()) {
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.INSTALL_PACKAGE)
-                    .setAdmin(getInstallSource().installerPackageName)
+                    .setAdmin(getInstallSource().mInstallerPackageName)
                     .write();
         }
 
@@ -2201,6 +2214,12 @@
             activate();
         }
 
+        if (mVerificationInProgress) {
+            Slog.w(TAG, "Verification is already in progress for session " + sessionId);
+            return;
+        }
+        mVerificationInProgress = true;
+
         if (params.isStaged) {
             mStagedSession.verifySession();
         } else {
@@ -2550,8 +2569,8 @@
         }
 
         synchronized (mLock) {
-            return new InstallingSession(stageDir, localObserver, params, mInstallSource, user,
-                    mSigningDetails, mInstallerUid, mPackageLite, mPm);
+            return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource,
+                    user, mSigningDetails, mInstallerUid, mPackageLite, mPm);
         }
     }
 
@@ -2592,7 +2611,7 @@
         final int packageUid;
         if (returnCode != INSTALL_SUCCEEDED) {
             // Package didn't install; no valid uid
-            packageUid = Process.INVALID_UID;
+            packageUid = INVALID_UID;
         } else {
             packageUid = mPm.snapshotComputer().getPackageUid(packageName, 0, userId);
         }
@@ -2763,10 +2782,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 +3346,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;
@@ -3444,11 +3464,11 @@
     }
 
     String getInstallerPackageName() {
-        return getInstallSource().installerPackageName;
+        return getInstallSource().mInstallerPackageName;
     }
 
     String getInstallerAttributionTag() {
-        return getInstallSource().installerAttributionTag;
+        return getInstallSource().mInstallerAttributionTag;
     }
 
     InstallSource getInstallSource() {
@@ -3694,7 +3714,9 @@
     private boolean dispatchPendingAbandonCallback() {
         final Runnable callback;
         synchronized (mLock) {
-            Preconditions.checkState(mStageDirInUse);
+            if (!mStageDirInUse) {
+                return false;
+            }
             mStageDirInUse = false;
             callback = mPendingAbandonCallback;
             mPendingAbandonCallback = null;
@@ -4291,6 +4313,11 @@
         mPreapprovalRequested.set(true);
     }
 
+    @Override
+    public boolean isKeepApplicationEnabledSetting() {
+        return params.keepApplicationEnabledSetting;
+    }
+
     void setSessionReady() {
         synchronized (mLock) {
             // Do not allow destroyed/failed session to change state
@@ -4434,9 +4461,9 @@
         pw.printPair("userId", userId);
         pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid);
         pw.printPair("mOriginalInstallerPackageName", mOriginalInstallerPackageName);
-        pw.printPair("installerPackageName", mInstallSource.installerPackageName);
-        pw.printPair("installInitiatingPackageName", mInstallSource.initiatingPackageName);
-        pw.printPair("installOriginatingPackageName", mInstallSource.originatingPackageName);
+        pw.printPair("installerPackageName", mInstallSource.mInstallerPackageName);
+        pw.printPair("installInitiatingPackageName", mInstallSource.mInitiatingPackageName);
+        pw.printPair("installOriginatingPackageName", mInstallSource.mOriginatingPackageName);
         pw.printPair("mInstallerUid", mInstallerUid);
         pw.printPair("createdMillis", createdMillis);
         pw.printPair("updatedMillis", updatedMillis);
@@ -4625,14 +4652,15 @@
             out.attributeInt(null, ATTR_SESSION_ID, sessionId);
             out.attributeInt(null, ATTR_USER_ID, userId);
             writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME,
-                    mInstallSource.installerPackageName);
+                    mInstallSource.mInstallerPackageName);
+            out.attributeInt(null, ATTR_INSTALLER_PACKAGE_UID, mInstallSource.mInstallerPackageUid);
             writeStringAttribute(out, ATTR_INSTALLER_ATTRIBUTION_TAG,
-                    mInstallSource.installerAttributionTag);
+                    mInstallSource.mInstallerAttributionTag);
             out.attributeInt(null, ATTR_INSTALLER_UID, mInstallerUid);
             writeStringAttribute(out, ATTR_INITIATING_PACKAGE_NAME,
-                    mInstallSource.initiatingPackageName);
+                    mInstallSource.mInitiatingPackageName);
             writeStringAttribute(out, ATTR_ORIGINATING_PACKAGE_NAME,
-                    mInstallSource.originatingPackageName);
+                    mInstallSource.mOriginatingPackageName);
             out.attributeLong(null, ATTR_CREATED_MILLIS, createdMillis);
             out.attributeLong(null, ATTR_UPDATED_MILLIS, updatedMillis);
             out.attributeLong(null, ATTR_COMMITTED_MILLIS, committedMillis);
@@ -4671,6 +4699,8 @@
             writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
             writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
             out.attributeInt(null, ATTR_INSTALL_REASON, params.installReason);
+            writeBooleanAttribute(out, ATTR_KEEP_APPLICATION_ENABLED_SETTING,
+                    params.keepApplicationEnabledSetting);
 
             final boolean isDataLoader = params.dataLoaderParams != null;
             writeBooleanAttribute(out, ATTR_IS_DATALOADER, isDataLoader);
@@ -4789,6 +4819,8 @@
         final int sessionId = in.getAttributeInt(null, ATTR_SESSION_ID);
         final int userId = in.getAttributeInt(null, ATTR_USER_ID);
         final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME);
+        final int installPackageUid = in.getAttributeInt(null, ATTR_INSTALLER_PACKAGE_UID,
+                INVALID_UID);
         final String installerAttributionTag = readStringAttribute(in,
                 ATTR_INSTALLER_ATTRIBUTION_TAG);
         final int installerUid = in.getAttributeInt(null, ATTR_INSTALLER_UID, pm.snapshotComputer()
@@ -4830,6 +4862,8 @@
         params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
         params.installReason = in.getAttributeInt(null, ATTR_INSTALL_REASON);
         params.packageSource = in.getAttributeInt(null, ATTR_PACKAGE_SOURCE);
+        params.keepApplicationEnabledSetting = in.getAttributeBoolean(null,
+                ATTR_KEEP_APPLICATION_ENABLED_SETTING, false);
 
         if (in.getAttributeBoolean(null, ATTR_IS_DATALOADER, false)) {
             params.dataLoaderParams = new DataLoaderParams(
@@ -4958,8 +4992,8 @@
         }
 
         InstallSource installSource = InstallSource.create(installInitiatingPackageName,
-                installOriginatingPackageName, installerPackageName, installerAttributionTag,
-                params.packageSource);
+                installOriginatingPackageName, installerPackageName, installPackageUid,
+                installerAttributionTag, params.packageSource);
         return new PackageInstallerSession(callback, context, pm, sessionProvider,
                 silentUpdatePolicy, installerThread, stagingManager, sessionId, userId,
                 installerUid, installSource, params, createdMillis, committedMillis, stageDir,
diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java
index 21c2f2c..d163d3d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerLocal.java
+++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java
@@ -30,7 +30,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 import java.util.Map;
-import java.util.function.Consumer;
 
 /**
  * In-process API for server side PackageManager related infrastructure.
@@ -167,15 +166,15 @@
         PackageState getPackageState(@NonNull String packageName);
 
         /**
-         * Iterates on all states. This should only be used when either the target package name
-         * is not known or the large majority of the states are expected to be used.
-         *
+         * Returns a map of all {@link PackageState PackageStates} on the device.
+         * <p>
          * This will cause app visibility filtering to be invoked on each state on the device,
-         * which can be expensive.
+         * which can be expensive. Prefer {@link #getPackageState(String)} if possible.
          *
-         * @param consumer Block to accept each state as it becomes available post-filtering.
+         * @return Mapping of package name to {@link PackageState}.
          */
-        void forAllPackageStates(@NonNull Consumer<PackageState> consumer);
+        @NonNull
+        Map<String, PackageState> getPackageStates();
 
         @Override
         void close();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 23cf262..cf59a1e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -29,6 +29,7 @@
 import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.INVALID_UID;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
@@ -83,7 +84,6 @@
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver2;
 import android.content.pm.IPackageLoadingProgressCallback;
-import android.content.pm.IPackageManager;
 import android.content.pm.IPackageMoveObserver;
 import android.content.pm.IncrementalStatesInfo;
 import android.content.pm.InstallSourceInfo;
@@ -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;
@@ -557,6 +558,7 @@
     static final char RANDOM_CODEPATH_PREFIX = '-';
 
     final Handler mHandler;
+    final Handler mBackgroundHandler;
 
     final ProcessLoggingHandler mProcessLoggingHandler;
 
@@ -690,7 +692,6 @@
     private final ModuleInfoProvider mModuleInfoProvider;
 
     final ApexManager mApexManager;
-    final ApexPackageInfo mApexPackageInfo;
 
     final PackageManagerServiceInjector mInjector;
 
@@ -873,7 +874,7 @@
     // public static final int UNUSED = 5;
     static final int POST_INSTALL = 9;
     static final int WRITE_SETTINGS = 13;
-    static final int WRITE_PACKAGE_RESTRICTIONS = 14;
+    static final int WRITE_DIRTY_PACKAGE_RESTRICTIONS = 14;
     static final int PACKAGE_VERIFIED = 15;
     static final int CHECK_PENDING_VERIFICATION = 16;
     // public static final int UNUSED = 17;
@@ -890,6 +891,8 @@
     static final int PRUNE_UNUSED_STATIC_SHARED_LIBRARIES = 28;
     static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER = 29;
 
+    static final int WRITE_USER_PACKAGE_RESTRICTIONS = 30;
+
     static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
     private static final int DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS = 500;
     private static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER_DELAY_MS = 1000;
@@ -937,6 +940,7 @@
     final @Nullable String mOverlayConfigSignaturePackage;
     final @Nullable String mRecentsPackage;
     final @Nullable String mAmbientContextDetectionPackage;
+    final @Nullable String mWearableSensingPackage;
     private final @NonNull String mRequiredSdkSandboxPackage;
 
     @GuardedBy("mLock")
@@ -1397,28 +1401,33 @@
                 mDirtyUsers.add(userId);
             }
         }
-        if (!mHandler.hasMessages(WRITE_PACKAGE_RESTRICTIONS)) {
-            mHandler.sendEmptyMessageDelayed(WRITE_PACKAGE_RESTRICTIONS, WRITE_SETTINGS_DELAY);
+        if (!mBackgroundHandler.hasMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS)) {
+            mBackgroundHandler.sendMessageDelayed(
+                    mBackgroundHandler.obtainMessage(WRITE_DIRTY_PACKAGE_RESTRICTIONS, this),
+                    WRITE_SETTINGS_DELAY);
         }
     }
 
     void writePendingRestrictions() {
+        final Integer[] dirtyUsers;
         synchronized (mLock) {
-            mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
+            mBackgroundHandler.removeMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS);
             synchronized (mDirtyUsers) {
-                for (int userId : mDirtyUsers) {
-                    mSettings.writePackageRestrictionsLPr(userId);
+                if (mDirtyUsers.isEmpty()) {
+                    return;
                 }
+                dirtyUsers = mDirtyUsers.toArray(Integer[]::new);
                 mDirtyUsers.clear();
             }
         }
+        mSettings.writePackageRestrictions(dirtyUsers);
     }
 
-    void writeSettings() {
+    void writeSettings(boolean sync) {
         synchronized (mLock) {
             mHandler.removeMessages(WRITE_SETTINGS);
-            mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
-            writeSettingsLPrTEMP();
+            mBackgroundHandler.removeMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS);
+            writeSettingsLPrTEMP(sync);
             synchronized (mDirtyUsers) {
                 mDirtyUsers.clear();
             }
@@ -1432,7 +1441,27 @@
         }
     }
 
-    public static Pair<PackageManagerService, IPackageManager> main(Context context,
+    private static final Handler.Callback BACKGROUND_HANDLER_CALLBACK = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(@NonNull Message msg) {
+            switch (msg.what) {
+                case WRITE_DIRTY_PACKAGE_RESTRICTIONS: {
+                    PackageManagerService pm = (PackageManagerService) msg.obj;
+                    pm.writePendingRestrictions();
+                    return true;
+                }
+                case WRITE_USER_PACKAGE_RESTRICTIONS: {
+                    final Runnable r = (Runnable) msg.obj;
+                    r.run();
+                    return true;
+                }
+            }
+            return false;
+        }
+    };
+
+    /** Starts PackageManagerService. */
+    public static PackageManagerService main(Context context,
             Installer installer, @NonNull DomainVerificationService domainVerificationService,
             boolean factoryTest) {
         // Self-check for initial settings.
@@ -1446,7 +1475,8 @@
         HandlerThread backgroundThread = new ServiceThread("PackageManagerBg",
                 Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
         backgroundThread.start();
-        Handler backgroundHandler = new Handler(backgroundThread.getLooper());
+        Handler backgroundHandler = new Handler(backgroundThread.getLooper(),
+                BACKGROUND_HANDLER_CALLBACK);
 
         PackageManagerServiceInjector injector = new PackageManagerServiceInjector(
                 context, lock, installer, installLock, new PackageAbiHelperImpl(),
@@ -1460,7 +1490,8 @@
                 (i, pm) -> new Settings(Environment.getDataDirectory(),
                         RuntimePermissionsPersistence.createInstance(),
                         i.getPermissionManagerServiceInternal(),
-                        domainVerificationService, backgroundHandler, lock),
+                        domainVerificationService, backgroundHandler,
+                        lock),
                 (i, pm) -> AppsFilterImpl.create(i,
                         i.getLocalService(PackageManagerInternal.class)),
                 (i, pm) -> (PlatformCompat) ServiceManager.getService("platform_compat"),
@@ -1529,7 +1560,7 @@
                 AndroidPackage pkg = packageState.getPkg();
                 SharedUserApi sharedUser = snapshot.getSharedUser(
                         packageState.getSharedUserAppId());
-                String oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, packageState);
+                String oldSeInfo = packageState.getSeInfo();
 
                 if (pkg == null) {
                     Slog.e(TAG, "Failed to find package " + packageName);
@@ -1560,7 +1591,7 @@
         ServiceManager.addService("package_native", pmn);
         LocalManagerRegistry.addManager(PackageManagerLocal.class,
                 new PackageManagerLocalImpl(m));
-        return Pair.create(m, iPackageManager);
+        return m;
     }
 
     /** Install/uninstall system packages for all users based on their user-type, as applicable. */
@@ -1638,10 +1669,10 @@
         mUserNeedsBadging = new UserNeedsBadgingCache(mUserManager);
         mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
         mHandler = injector.getHandler();
+        mBackgroundHandler = injector.getBackgroundHandler();
         mSharedLibraries = injector.getSharedLibrariesImpl();
 
         mApexManager = testParams.apexManager;
-        mApexPackageInfo = new ApexPackageInfo(this);
         mArtManagerService = testParams.artManagerService;
         mAvailableFeatures = testParams.availableFeatures;
         mBackgroundDexOptService = testParams.backgroundDexOptService;
@@ -1683,6 +1714,7 @@
         mRetailDemoPackage = testParams.retailDemoPackage;
         mRecentsPackage = testParams.recentsPackage;
         mAmbientContextDetectionPackage = testParams.ambientContextDetectionPackage;
+        mWearableSensingPackage = testParams.wearableSensingPackage;
         mConfiguratorPackage = testParams.configuratorPackage;
         mAppPredictionServicePackage = testParams.appPredictionServicePackage;
         mIncidentReportApproverPackage = testParams.incidentReportApproverPackage;
@@ -1725,8 +1757,8 @@
     }
 
     public PackageManagerService(PackageManagerServiceInjector injector, boolean factoryTest,
-            final String buildFingerprint, final boolean isEngBuild, final boolean isUserDebugBuild,
-            final int sdkVersion, final String incrementalVersion) {
+            final String partitionsFingerprint, final boolean isEngBuild,
+            final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion) {
         mIsEngBuild = isEngBuild;
         mIsUserDebugBuild = isUserDebugBuild;
         mSdkVersion = sdkVersion;
@@ -1829,6 +1861,7 @@
         mMoveCallbacks = new MovePackageHelper.MoveCallbacks(FgThread.get().getLooper());
         mViewCompiler = injector.getViewCompiler();
         mSharedLibraries = mInjector.getSharedLibrariesImpl();
+        mBackgroundHandler = injector.getBackgroundHandler();
 
         mContext.getSystemService(DisplayManager.class)
                 .getDisplay(Display.DEFAULT_DISPLAY).getMetrics(mMetrics);
@@ -1841,7 +1874,6 @@
         mProtectedPackages = new ProtectedPackages(mContext);
 
         mApexManager = injector.getApexManager();
-        mApexPackageInfo = new ApexPackageInfo(this);
         mAppsFilter = mInjector.getAppsFilter();
 
         mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager,
@@ -1973,14 +2005,15 @@
 
             final VersionInfo ver = mSettings.getInternalVersion();
             mIsUpgrade =
-                    !buildFingerprint.equals(ver.fingerprint);
+                    !partitionsFingerprint.equals(ver.fingerprint);
             if (mIsUpgrade) {
-                PackageManagerServiceUtils.logCriticalInfo(Log.INFO, "Upgrading from "
-                        + ver.fingerprint + " to " + PackagePartitions.FINGERPRINT);
+                PackageManagerServiceUtils.logCriticalInfo(Log.INFO,
+                        "Upgrading from " + ver.fingerprint + " (" + ver.buildFingerprint + ") to "
+                                + PackagePartitions.FINGERPRINT + " (" + Build.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 =
@@ -2041,6 +2074,9 @@
             mAmbientContextDetectionPackage = ensureSystemPackageName(computer,
                     getPackageFromComponentString(
                             R.string.config_defaultAmbientContextDetectionService));
+            mWearableSensingPackage = ensureSystemPackageName(computer,
+                    getPackageFromComponentString(
+                            R.string.config_defaultWearableSensingService));
 
             // Now that we know all of the shared libraries, update all clients to have
             // the correct library paths.
@@ -2083,14 +2119,14 @@
                     + ((SystemClock.uptimeMillis() - startTime) / 1000f)
                     + " seconds");
 
-            // If the build fingerprint has changed since the last time we booted,
+            // If the partitions fingerprint has changed since the last time we booted,
             // we need to re-grant app permission to catch any new ones that
             // appear.  This is really a hack, and means that apps can in some
             // cases get permissions that the user didn't initially explicitly
             // allow...  it would be nice to have some better way to handle
             // this situation.
             if (mIsUpgrade) {
-                Slog.i(TAG, "Build fingerprint changed from " + ver.fingerprint + " to "
+                Slog.i(TAG, "Partitions fingerprint changed from " + ver.fingerprint + " to "
                         + PackagePartitions.FINGERPRINT
                         + "; regranting permissions for internal storage");
             }
@@ -2122,6 +2158,7 @@
                                         | Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES);
                     }
                 }
+                ver.buildFingerprint = Build.FINGERPRINT;
                 ver.fingerprint = PackagePartitions.FINGERPRINT;
             }
 
@@ -2902,9 +2939,9 @@
             mPackageUsage.writeNow(mSettings.getPackagesLocked());
 
             if (mHandler.hasMessages(WRITE_SETTINGS)
-                    || mHandler.hasMessages(WRITE_PACKAGE_RESTRICTIONS)
+                    || mBackgroundHandler.hasMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS)
                     || mHandler.hasMessages(WRITE_PACKAGE_LIST)) {
-                writeSettings();
+                writeSettings(/*sync=*/true);
             }
         }
     }
@@ -2989,6 +3026,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,
@@ -3036,7 +3074,7 @@
             int userId) {
         final PackageRemovedInfo info = new PackageRemovedInfo(this);
         info.mRemovedPackage = packageName;
-        info.mInstallerPackageName = packageState.getInstallSource().installerPackageName;
+        info.mInstallerPackageName = packageState.getInstallSource().mInstallerPackageName;
         info.mRemovedUsers = new int[] {userId};
         info.mBroadcastUsers = new int[] {userId};
         info.mUid = UserHandle.getUid(userId, packageState.getAppId());
@@ -3967,7 +4005,7 @@
         synchronized (mDirtyUsers) {
             mDirtyUsers.remove(userId);
             if (mDirtyUsers.isEmpty()) {
-                mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
+                mBackgroundHandler.removeMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS);
             }
         }
     }
@@ -4460,7 +4498,7 @@
 
                 if (wasNotLaunched) {
                     final String installerPackageName =
-                            packageState.getInstallSource().installerPackageName;
+                            packageState.getInstallSource().mInstallerPackageName;
                     if (installerPackageName != null) {
                         notifyFirstLaunch(packageName, installerPackageName, userId);
                     }
@@ -5064,8 +5102,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);
@@ -5251,10 +5292,6 @@
                 Map<String, String> classLoaderContextMap,
                 String loaderIsa) {
             int callingUid = Binder.getCallingUid();
-
-            // TODO(b/254043366): System server should not report its own dex load because there's
-            // nothing ART can do with it.
-
             Computer snapshot = snapshot();
 
             // System server should be able to report dex load on behalf of other apps. E.g., it
@@ -5441,7 +5478,7 @@
                 }
 
                 if (!Objects.equals(callerPackageName,
-                        packageState.getInstallSource().installerPackageName)) {
+                        packageState.getInstallSource().mInstallerPackageName)) {
                     throw new IllegalArgumentException("Calling package " + callerPackageName
                             + " is not installer for " + packageName);
                 }
@@ -5674,7 +5711,8 @@
         }
 
         @Override
-        public void setInstallerPackageName(String targetPackage, String installerPackageName) {
+        public void setInstallerPackageName(String targetPackage,
+                @Nullable String installerPackageName) {
             final int callingUid = Binder.getCallingUid();
             final int callingUserId = UserHandle.getUserId(callingUid);
             final FunctionalUtils.ThrowingCheckedFunction<Computer, Boolean, RuntimeException>
@@ -5729,7 +5767,7 @@
                 // Verify: if target already has an installer package, it must
                 // be signed with the same cert as the caller.
                 String targetInstallerPackageName =
-                        targetPackageState.getInstallSource().installerPackageName;
+                        targetPackageState.getInstallSource().mInstallerPackageName;
                 PackageStateInternal targetInstallerPkgSetting = targetInstallerPackageName == null
                         ? null : snapshot.getPackageStateInternal(targetInstallerPackageName);
 
@@ -5772,16 +5810,21 @@
             if (allowed) {
                 // TODO: Need to lock around here to handle mSettings.addInstallerPackageNames,
                 //  should find an alternative which avoids any race conditions
+                final int installerPackageUid = installerPackageName == null
+                        ? INVALID_UID : snapshotComputer().getPackageUid(installerPackageName,
+                        0 /* flags */, callingUserId);
                 PackageStateInternal targetPackageState;
                 synchronized (mLock) {
                     PackageStateMutator.Result result = commitPackageStateMutation(initialState,
-                            targetPackage, state -> state.setInstaller(installerPackageName));
+                            targetPackage, state -> state.setInstaller(installerPackageName,
+                                    installerPackageUid));
                     if (result.isPackagesChanged() || result.isStateChanged()) {
                         synchronized (mPackageStateWriteLock) {
                             allowed = implementation.apply(snapshotComputer());
                             if (allowed) {
                                 commitPackageStateMutation(null, targetPackage,
-                                        state -> state.setInstaller(installerPackageName));
+                                        state -> state.setInstaller(installerPackageName,
+                                                installerPackageUid));
                             } else {
                                 return;
                             }
@@ -6029,6 +6072,7 @@
                     mConfiguratorPackage,
                     mIncidentReportApproverPackage,
                     mAmbientContextDetectionPackage,
+                    mWearableSensingPackage,
                     mAppPredictionServicePackage,
                     COMPANION_PACKAGE_NAME,
                     mRetailDemoPackage,
@@ -6863,9 +6907,14 @@
      * TODO: In the meantime, can this be moved to a schedule call?
      * TODO(b/182523293): This should be removed once we finish migration of permission storage.
      */
-    void writeSettingsLPrTEMP() {
+    void writeSettingsLPrTEMP(boolean sync) {
         mPermissionManager.writeLegacyPermissionsTEMP(mSettings.mPermissions);
-        mSettings.writeLPr(mLiveComputer);
+        mSettings.writeLPr(mLiveComputer, sync);
+    }
+
+    // Default async version.
+    void writeSettingsLPrTEMP() {
+        writeSettingsLPrTEMP(/*sync=*/false);
     }
 
     @Override
@@ -7062,6 +7111,7 @@
                 mConfiguratorPackage,
                 mIncidentReportApproverPackage,
                 mAmbientContextDetectionPackage,
+                mWearableSensingPackage,
                 mAppPredictionServicePackage,
                 COMPANION_PACKAGE_NAME,
                 mRetailDemoPackage,
@@ -7267,6 +7317,7 @@
             }
 
             consumer.accept(mPackageStateMutator);
+            mPackageStateMutator.onFinished();
             onChanged();
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 4391fdd..bffbb84 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -94,6 +94,7 @@
     public @Nullable String retailDemoPackage;
     public @Nullable String recentsPackage;
     public @Nullable String ambientContextDetectionPackage;
+    public @Nullable String wearableSensingPackage;
     public ComponentName resolveComponentName;
     public ArrayMap<String, AndroidPackage> packages;
     public boolean enableFreeCacheV2;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 77334e5..a72ae56 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -142,6 +142,11 @@
     public static final Predicate<PackageStateInternal> REMOVE_IF_NULL_PKG =
             pkgSetting -> pkgSetting.getPkg() == null;
 
+    // This is a horrible hack to workaround b/240373119, specifically for fixing the T branch.
+    // A proper fix should be implemented in master instead.
+    public static final ThreadLocal<Boolean> DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS =
+            ThreadLocal.withInitial(() -> false);
+
     /**
      * Components of apps targeting Android T and above will stop receiving intents from
      * external callers that do not match its declared intent filters.
@@ -1093,6 +1098,8 @@
             PlatformCompat compat, ComponentResolverApi resolver,
             List<ResolveInfo> resolveInfos, boolean isReceiver,
             Intent intent, String resolvedType, int filterCallingUid) {
+        if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
+
         final Printer logPrinter = DEBUG_INTENT_MATCHING
                 ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
                 : null;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 3c1cba3..e1efc61 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -88,7 +88,6 @@
 import android.os.ServiceSpecificException;
 import android.os.ShellCommand;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -1787,13 +1786,11 @@
 
     private int runCompile() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
-        boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
         boolean forceCompilation = false;
         boolean allPackages = false;
         boolean clearProfileData = false;
         String compilerFilter = null;
         String compilationReason = null;
-        String checkProfilesRaw = null;
         boolean secondaryDex = false;
         String split = null;
 
@@ -1816,7 +1813,9 @@
                     compilationReason = getNextArgRequired();
                     break;
                 case "--check-prof":
-                    checkProfilesRaw = getNextArgRequired();
+                    getNextArgRequired();
+                    pw.println("Warning: Ignoring obsolete flag --check-prof "
+                            + "- it is unconditionally enabled now");
                     break;
                 case "--reset":
                     forceCompilation = true;
@@ -1835,17 +1834,6 @@
             }
         }
 
-        if (checkProfilesRaw != null) {
-            if ("true".equals(checkProfilesRaw)) {
-                checkProfiles = true;
-            } else if ("false".equals(checkProfilesRaw)) {
-                checkProfiles = false;
-            } else {
-                pw.println("Invalid value for \"--check-prof\". Expected \"true\" or \"false\".");
-                return 1;
-            }
-        }
-
         final boolean compilerFilterGiven = compilerFilter != null;
         final boolean compilationReasonGiven = compilationReason != null;
         // Make sure exactly one of -m, or -r is given.
@@ -1922,11 +1910,10 @@
             }
 
             final boolean result = secondaryDex
-                    ? mInterface.performDexOptSecondary(packageName,
-                            targetCompilerFilter, forceCompilation)
-                    : mInterface.performDexOptMode(packageName,
-                            checkProfiles, targetCompilerFilter, forceCompilation,
-                            true /* bootComplete */, split);
+                    ? mInterface.performDexOptSecondary(
+                            packageName, targetCompilerFilter, forceCompilation)
+                    : mInterface.performDexOptMode(packageName, true /* checkProfiles */,
+                            targetCompilerFilter, forceCompilation, true /* bootComplete */, split);
             if (!result) {
                 failedPackages.add(packageName);
             }
@@ -3247,6 +3234,9 @@
                 case "--skip-verification":
                     sessionParams.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
                     break;
+                case "--skip-enable":
+                    sessionParams.setKeepApplicationEnabledSetting();
+                    break;
                 default:
                     throw new IllegalArgumentException("Unknown option " + opt);
             }
@@ -3707,7 +3697,7 @@
                 fd = ParcelFileDescriptor.dup(getInFileDescriptor());
             }
             if (sizeBytes <= 0) {
-                getErrPrintWriter().println("Error: must specify a APK size");
+                getErrPrintWriter().println("Error: must specify an APK size");
                 return 1;
             }
 
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
new file mode 100644
index 0000000..81f1a98
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -0,0 +1,245 @@
+/*
+ * 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.Process.INVALID_UID;
+
+import android.annotation.IntDef;
+import android.content.pm.PackageManager;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Stream;
+
+/**
+ * Metrics class for reporting stats to logging infrastructures like Westworld
+ */
+final class PackageMetrics {
+    public static final int STEP_PREPARE = 1;
+    public static final int STEP_SCAN = 2;
+    public static final int STEP_RECONCILE = 3;
+    public static final int STEP_COMMIT = 4;
+
+    @IntDef(prefix = {"STEP_"}, value = {
+            STEP_PREPARE,
+            STEP_SCAN,
+            STEP_RECONCILE,
+            STEP_COMMIT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StepInt {
+    }
+
+    private final long mInstallStartTimestampMillis;
+    private final SparseArray<InstallStep> mInstallSteps = new SparseArray<>();
+    private final InstallRequest mInstallRequest;
+
+    PackageMetrics(InstallRequest installRequest) {
+        // New instance is used for tracking installation metrics only.
+        // Other metrics should use static methods of this class.
+        mInstallStartTimestampMillis = System.currentTimeMillis();
+        mInstallRequest = installRequest;
+    }
+
+    public void onInstallSucceed() {
+        // TODO(b/239722919): report to SecurityLog if on work profile or managed device
+        reportInstallationStats(true /* success */);
+    }
+
+    public void onInstallFailed() {
+        reportInstallationStats(false /* success */);
+    }
+
+    private void reportInstallationStats(boolean success) {
+        UserManagerInternal userManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
+        final long installDurationMillis =
+                System.currentTimeMillis() - mInstallStartTimestampMillis;
+        // write to stats
+        final Pair<int[], long[]> stepDurations = getInstallStepDurations();
+        final int[] newUsers = mInstallRequest.getNewUsers();
+        final int[] originalUsers = mInstallRequest.getOriginUsers();
+        final String packageName;
+        // only reporting package name for failed non-adb installations
+        if (success || mInstallRequest.isInstallFromAdb()) {
+            packageName = null;
+        } else {
+            packageName = mInstallRequest.getName();
+        }
+
+        final int installerPackageUid = mInstallRequest.getInstallerPackageUid();
+
+        long versionCode = 0, apksSize = 0;
+        if (success) {
+            final PackageSetting ps = mInstallRequest.getScannedPackageSetting();
+            if (ps != null) {
+                versionCode = ps.getVersionCode();
+                apksSize = getApksSize(ps.getPath());
+            }
+        }
+
+        FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
+                mInstallRequest.getSessionId() /* session_id */,
+                packageName /* package_name */,
+                mInstallRequest.getUid() /* uid */,
+                newUsers /* user_ids */,
+                userManagerInternal.getUserTypesForStatsd(newUsers) /* user_types */,
+                originalUsers /* original_user_ids */,
+                userManagerInternal.getUserTypesForStatsd(originalUsers) /* original_user_types */,
+                mInstallRequest.getReturnCode() /* public_return_code */,
+                0 /* internal_error_code */,
+                apksSize /* apks_size_bytes */,
+                versionCode /* version_code */,
+                stepDurations.first /* install_steps */,
+                stepDurations.second /* step_duration_millis */,
+                installDurationMillis /* total_duration_millis */,
+                mInstallRequest.getInstallFlags() /* install_flags */,
+                installerPackageUid /* installer_package_uid */,
+                -1 /* original_installer_package_uid */,
+                mInstallRequest.getDataLoaderType() /* data_loader_type */,
+                mInstallRequest.getRequireUserAction() /* user_action_required_type */,
+                mInstallRequest.isInstantInstall() /* is_instant */,
+                mInstallRequest.isInstallReplace() /* is_replace */,
+                mInstallRequest.isInstallSystem() /* is_system */,
+                mInstallRequest.isInstallInherit() /* is_inherit */,
+                mInstallRequest.isInstallForUsers() /* is_installing_existing_as_user */,
+                mInstallRequest.isInstallMove() /* is_move_install */,
+                false /* is_staged */
+        );
+    }
+
+    private long getApksSize(File apkDir) {
+        // TODO(b/249294752): also count apk sizes for failed installs
+        final AtomicLong apksSize = new AtomicLong();
+        try (Stream<Path> walkStream = Files.walk(apkDir.toPath())) {
+            walkStream.filter(p -> p.toFile().isFile()
+                    && ApkLiteParseUtils.isApkFile(p.toFile())).forEach(
+                            f -> apksSize.addAndGet(f.toFile().length()));
+        } catch (IOException e) {
+            // ignore
+        }
+        return apksSize.get();
+    }
+
+    public void onStepStarted(@StepInt int step) {
+        mInstallSteps.put(step, new InstallStep());
+    }
+
+    public void onStepFinished(@StepInt int step) {
+        final InstallStep installStep = mInstallSteps.get(step);
+        if (installStep != null) {
+            // Only valid if the start timestamp is set; otherwise no-op
+            installStep.finish();
+        }
+    }
+
+    // List of steps (e.g., 1, 2, 3) and corresponding list of durations (e.g., 200ms, 100ms, 150ms)
+    private Pair<int[], long[]> getInstallStepDurations() {
+        ArrayList<Integer> steps = new ArrayList<>();
+        ArrayList<Long> durations = new ArrayList<>();
+        for (int i = 0; i < mInstallSteps.size(); i++) {
+            final long duration = mInstallSteps.valueAt(i).getDurationMillis();
+            if (duration >= 0) {
+                steps.add(mInstallSteps.keyAt(i));
+                durations.add(mInstallSteps.valueAt(i).getDurationMillis());
+            }
+        }
+        int[] stepsArray = new int[steps.size()];
+        long[] durationsArray = new long[durations.size()];
+        for (int i = 0; i < stepsArray.length; i++) {
+            stepsArray[i] = steps.get(i);
+            durationsArray[i] = durations.get(i);
+        }
+        return new Pair<>(stepsArray, durationsArray);
+    }
+
+    private static class InstallStep {
+        private final long mStartTimestampMillis;
+        private long mDurationMillis = -1;
+
+        InstallStep() {
+            mStartTimestampMillis = System.currentTimeMillis();
+        }
+
+        void finish() {
+            mDurationMillis = System.currentTimeMillis() - mStartTimestampMillis;
+        }
+
+        long getDurationMillis() {
+            return mDurationMillis;
+        }
+    }
+
+    public static void onUninstallSucceeded(PackageRemovedInfo info, int deleteFlags,
+            UserManagerInternal userManagerInternal) {
+        if (info.mIsUpdate) {
+            // Not logging uninstalls caused by app updates
+            return;
+        }
+        final int[] removedUsers = info.mRemovedUsers;
+        final int[] removedUserTypes = userManagerInternal.getUserTypesForStatsd(removedUsers);
+        final int[] originalUsers = info.mOrigUsers;
+        final int[] originalUserTypes = userManagerInternal.getUserTypesForStatsd(originalUsers);
+        FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_UNINSTALLATION_REPORTED,
+                info.mUid, removedUsers, removedUserTypes, originalUsers, originalUserTypes,
+                deleteFlags, PackageManager.DELETE_SUCCEEDED, info.mIsRemovedPackageSystemUpdate,
+                !info.mRemovedForAllUsers);
+    }
+
+    public static void onVerificationFailed(VerifyingSession verifyingSession) {
+        FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
+                verifyingSession.getSessionId() /* session_id */,
+                null /* package_name */,
+                INVALID_UID /* uid */,
+                null /* user_ids */,
+                null /* user_types */,
+                null /* original_user_ids */,
+                null /* original_user_types */,
+                verifyingSession.getRet() /* public_return_code */,
+                0 /* internal_error_code */,
+                0 /* apks_size_bytes */,
+                0 /* version_code */,
+                null /* install_steps */,
+                null /* step_duration_millis */,
+                0 /* total_duration_millis */,
+                0 /* install_flags */,
+                verifyingSession.getInstallerPackageUid() /* installer_package_uid */,
+                INVALID_UID /* original_installer_package_uid */,
+                verifyingSession.getDataLoaderType() /* data_loader_type */,
+                verifyingSession.getUserActionRequiredType() /* user_action_required_type */,
+                verifyingSession.isInstant() /* is_instant */,
+                false /* is_replace */,
+                false /* is_system */,
+                verifyingSession.isInherit() /* is_inherit */,
+                false /* is_installing_existing_as_user */,
+                false /* is_move_install */,
+                verifyingSession.isStaged() /* is_staged */
+        );
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index 3c863d0..dd580a5 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -111,16 +111,11 @@
     }
 
     private void sendPackageRemovedBroadcastInternal(boolean killApp, boolean removedBySystem) {
-        // Don't send static shared library removal broadcasts as these
-        // libs are visible only the apps that depend on them an one
-        // cannot remove the library if it has a dependency.
-        if (mIsStaticSharedLib) {
-            return;
-        }
         Bundle extras = new Bundle();
         final int removedUid = mRemovedAppId >= 0  ? mRemovedAppId : mUid;
         extras.putInt(Intent.EXTRA_UID, removedUid);
         extras.putBoolean(Intent.EXTRA_DATA_REMOVED, mDataRemoved);
+        extras.putBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, mIsRemovedPackageSystemUpdate);
         extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp);
         extras.putBoolean(Intent.EXTRA_USER_INITIATED, !removedBySystem);
         final boolean isReplace = mIsUpdate || mIsRemovedPackageSystemUpdate;
@@ -128,15 +123,22 @@
             extras.putBoolean(Intent.EXTRA_REPLACING, true);
         }
         extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, mRemovedForAllUsers);
+
+        // Send PACKAGE_REMOVED broadcast to the respective installer.
+        if (mRemovedPackage != null && mInstallerPackageName != null) {
+            mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
+                    mRemovedPackage, extras, 0 /*flags*/,
+                    mInstallerPackageName, null, mBroadcastUsers, mInstantUserIds, null, null);
+        }
+        if (mIsStaticSharedLib) {
+            // When uninstalling static shared libraries, only the package's installer needs to be
+            // sent a PACKAGE_REMOVED broadcast. There are no other intended recipients.
+            return;
+        }
         if (mRemovedPackage != null) {
             mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
                     mRemovedPackage, extras, 0, null /*targetPackage*/, null,
                     mBroadcastUsers, mInstantUserIds, mBroadcastAllowList, null);
-            if (mInstallerPackageName != null) {
-                mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
-                        mRemovedPackage, extras, 0 /*flags*/,
-                        mInstallerPackageName, null, mBroadcastUsers, mInstantUserIds, null, null);
-            }
             mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED_INTERNAL,
                     mRemovedPackage, extras, 0 /*flags*/, PLATFORM_PACKAGE_NAME,
                     null /*finishedReceiver*/, mBroadcastUsers, mInstantUserIds,
diff --git a/services/core/java/com/android/server/pm/PackageSessionProvider.java b/services/core/java/com/android/server/pm/PackageSessionProvider.java
index ad5cf13..79b88b3 100644
--- a/services/core/java/com/android/server/pm/PackageSessionProvider.java
+++ b/services/core/java/com/android/server/pm/PackageSessionProvider.java
@@ -29,4 +29,9 @@
     PackageInstallerSession getSession(int sessionId);
 
     PackageSessionVerifier getSessionVerifier();
+
+    /**
+     * Get the GentleUpdateHelper instance.
+     */
+    GentleUpdateHelper getGentleUpdateHelper();
 }
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
index 28a021b..434a62d 100644
--- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -483,7 +483,7 @@
             return;
         }
         final String packageName = session.getPackageName();
-        final String installerPackageName = session.getInstallSource().installerPackageName;
+        final String installerPackageName = session.getInstallSource().mInstallerPackageName;
         if (!isApexUpdateAllowed(packageName, installerPackageName)) {
             throw new PackageManagerException(
                     PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 8d6abe0..b18179e 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -277,7 +277,7 @@
         proto.write(PackageProto.UID, mAppId);
         proto.write(PackageProto.VERSION_CODE, versionCode);
         proto.write(PackageProto.UPDATE_TIME_MS, lastUpdateTime);
-        proto.write(PackageProto.INSTALLER_NAME, installSource.installerPackageName);
+        proto.write(PackageProto.INSTALLER_NAME, installSource.mInstallerPackageName);
 
         if (pkg != null) {
             proto.write(PackageProto.VERSION_STRING, pkg.getVersionName());
@@ -297,9 +297,9 @@
 
             long sourceToken = proto.start(PackageProto.INSTALL_SOURCE);
             proto.write(PackageProto.InstallSourceProto.INITIATING_PACKAGE_NAME,
-                    installSource.initiatingPackageName);
+                    installSource.mInitiatingPackageName);
             proto.write(PackageProto.InstallSourceProto.ORIGINATING_PACKAGE_NAME,
-                    installSource.originatingPackageName);
+                    installSource.mOriginatingPackageName);
             proto.end(sourceToken);
         }
         proto.write(PackageProto.StatesProto.IS_LOADING, isLoading());
@@ -360,8 +360,10 @@
         return this;
     }
 
-    public PackageSetting setInstallerPackageName(String packageName) {
-        installSource = installSource.setInstallerPackage(packageName);
+    public PackageSetting setInstallerPackage(@Nullable String installerPackageName,
+            int installerPackageUid) {
+        installSource = installSource.setInstallerPackage(installerPackageName,
+                installerPackageUid);
         onChanged();
         return this;
     }
@@ -372,7 +374,7 @@
         return this;
     }
 
-    PackageSetting removeInstallerPackage(String packageName) {
+    PackageSetting removeInstallerPackage(@Nullable String packageName) {
         installSource = installSource.removeInstallerPackage(packageName);
         onChanged();
         return this;
@@ -662,7 +664,9 @@
                 mUserStates.put(other.mUserStates.keyAt(i),
                         other.mUserStates.valueAt(i).snapshot());
             } else {
-                mUserStates.put(other.mUserStates.keyAt(i), other.mUserStates.valueAt(i));
+                var userState = other.mUserStates.valueAt(i);
+                userState.setWatchable(this);
+                mUserStates.put(other.mUserStates.keyAt(i), userState);
             }
         }
 
@@ -1356,6 +1360,17 @@
     }
 
     @Nullable
+    @Override
+    public String getSeInfo() {
+        String overrideSeInfo = getTransientState().getOverrideSeInfo();
+        if (!TextUtils.isEmpty(overrideSeInfo)) {
+            return overrideSeInfo;
+        }
+
+        return getTransientState().getSeInfo();
+    }
+
+    @Nullable
     public String getPrimaryCpuAbiLegacy() {
         return mPrimaryCpuAbi;
     }
@@ -1365,7 +1380,11 @@
         return mSecondaryCpuAbi;
     }
 
-
+    @ApplicationInfo.HiddenApiEnforcementPolicy
+    @Override
+    public int getHiddenApiEnforcementPolicy() {
+        return AndroidPackageUtils.getHiddenApiEnforcementPolicy(getAndroidPackage(), this);
+    }
 
     // Code below generated by codegen v1.0.23.
     //
@@ -1516,10 +1535,10 @@
     }
 
     @DataClass.Generated(
-            time = 1662666062860L,
+            time = 1665779003744L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
-            inputSignatures = "private  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate  boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate  boolean updateAvailable\nprivate  boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+            inputSignatures = "private  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate  boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate  boolean updateAvailable\nprivate  boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java b/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java
index 3b306a8..b310c62a 100644
--- a/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java
+++ b/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java
@@ -16,7 +16,7 @@
 
 package com.android.server.pm;
 
-import android.annotation.NonNull;;
+import android.annotation.NonNull;
 import android.text.TextUtils;
 
 import com.android.internal.util.HexDump;
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index ffce69e..99bcbc9 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -35,6 +35,7 @@
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.utils.WatchedLongSparseArray;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -48,14 +49,14 @@
  * as install) led to the request.
  */
 final class ReconcilePackageUtils {
-    public static Map<String, ReconciledPackage> reconcilePackages(
+    public static List<ReconciledPackage> reconcilePackages(
             List<InstallRequest> installRequests,
             Map<String, AndroidPackage> allPackages,
             Map<String, Settings.VersionInfo> versionInfos,
             SharedLibrariesImpl sharedLibraries,
             KeySetManagerService ksms, Settings settings)
             throws ReconcileFailure {
-        final Map<String, ReconciledPackage> result = new ArrayMap<>(installRequests.size());
+        final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
 
         // make a copy of the existing set of packages so we can combine them with incoming packages
         final ArrayMap<String, AndroidPackage> combinedPackages =
@@ -67,6 +68,7 @@
                 new ArrayMap<>();
 
         for (InstallRequest installRequest :  installRequests) {
+            installRequest.onReconcileStarted();
             final String installPackageName = installRequest.getParsedPackage().getPackageName();
 
             // add / replace existing with incoming packages
@@ -87,10 +89,9 @@
             }
 
 
-
             final DeletePackageAction deletePackageAction;
             // we only want to try to delete for non system apps
-            if (installRequest.isReplace() && !installRequest.isSystem()) {
+            if (installRequest.isInstallReplace() && !installRequest.isInstallSystem()) {
                 final boolean killApp = (installRequest.getScanFlags() & SCAN_DONT_KILL_APP) == 0;
                 final int deleteFlags = PackageManager.DELETE_KEEP_DATA
                         | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
@@ -256,13 +257,11 @@
                 }
             }
 
-            result.put(installPackageName,
+            final ReconciledPackage reconciledPackage =
                     new ReconciledPackage(installRequests, allPackages, installRequest,
                             deletePackageAction, allowedSharedLibInfos, signingDetails,
-                            sharedUserSignaturesChanged, removeAppKeySetData));
-        }
+                            sharedUserSignaturesChanged, removeAppKeySetData);
 
-        for (InstallRequest installRequest : installRequests) {
             // Check all shared libraries and map to their actual file path.
             // We only do this here for apps not on a system dir, because those
             // are the only ones that can fail an install due to this.  We
@@ -270,20 +269,21 @@
             // library paths after the scan is done. Also during the initial
             // scan don't update any libs as we do this wholesale after all
             // apps are scanned to avoid dependency based scanning.
-            if ((installRequest.getScanFlags() & SCAN_BOOTING) != 0
-                    || (installRequest.getParseFlags() & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
-                    != 0) {
-                continue;
+            if ((installRequest.getScanFlags() & SCAN_BOOTING) == 0
+                    && (installRequest.getParseFlags() & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
+                    == 0) {
+                try {
+                    reconciledPackage.mCollectedSharedLibraryInfos =
+                            sharedLibraries.collectSharedLibraryInfos(
+                                    installRequest.getParsedPackage(), combinedPackages,
+                                    incomingSharedLibraries);
+                } catch (PackageManagerException e) {
+                    throw new ReconcileFailure(e.error, e.getMessage());
+                }
             }
-            final String installPackageName = installRequest.getParsedPackage().getPackageName();
-            try {
-                result.get(installPackageName).mCollectedSharedLibraryInfos =
-                        sharedLibraries.collectSharedLibraryInfos(
-                                installRequest.getParsedPackage(), combinedPackages,
-                                incomingSharedLibraries);
-            } catch (PackageManagerException e) {
-                throw new ReconcileFailure(e.error, e.getMessage());
-            }
+
+            installRequest.onReconcileFinished();
+            result.add(reconciledPackage);
         }
 
         return result;
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 7e93673..8c58397 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -269,7 +269,7 @@
         final AndroidPackage deletedPkg = deletedPs.getPkg();
         if (outInfo != null) {
             outInfo.mRemovedPackage = packageName;
-            outInfo.mInstallerPackageName = deletedPs.getInstallSource().installerPackageName;
+            outInfo.mInstallerPackageName = deletedPs.getInstallSource().mInstallerPackageName;
             outInfo.mIsStaticSharedLib = deletedPkg != null
                     && deletedPkg.getStaticSharedLibraryName() != null;
             outInfo.populateUsers(deletedPs.queryInstalledUsers(
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index a905df9..6572d7b 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -265,8 +265,8 @@
             pkgSetting.getPkgState().setUpdatedSystemApp(true);
         }
 
-        parsedPackage.setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage, sharedUserSetting,
-                injector.getCompatibility()));
+        pkgSetting.getTransientState().setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage,
+                sharedUserSetting, injector.getCompatibility()));
 
         if (parsedPackage.isSystem()) {
             configurePackageComponents(parsedPackage);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 12c9d4b..809911a 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -29,6 +29,7 @@
 import static android.os.Process.SYSTEM_UID;
 
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.pm.PackageManagerService.WRITE_USER_PACKAGE_RESTRICTIONS;
 import static com.android.server.pm.SharedUidMigration.BEST_EFFORT;
 
 import android.annotation.NonNull;
@@ -56,7 +57,6 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.CreateAppDataArgs;
-import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.Message;
@@ -102,7 +102,6 @@
 import com.android.server.backup.PreferredActivityBackupHelper;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.permission.LegacyPermissionDataProvider;
 import com.android.server.pm.permission.LegacyPermissionSettings;
 import com.android.server.pm.permission.LegacyPermissionState;
@@ -353,6 +352,7 @@
     private static final String ATTR_SPLASH_SCREEN_THEME = "splash-screen-theme";
 
     private static final String ATTR_PACKAGE_NAME = "packageName";
+    private static final String ATTR_BUILD_FINGERPRINT = "buildFingerprint";
     private static final String ATTR_FINGERPRINT = "fingerprint";
     private static final String ATTR_VOLUME_UUID = "volumeUuid";
     private static final String ATTR_SDK_VERSION = "sdkVersion";
@@ -367,14 +367,27 @@
     @Watched(manual = true)
     private final RuntimePermissionPersistence mRuntimePermissionsPersistence;
 
+    // Current settings file.
     private final File mSettingsFilename;
-    private final File mBackupSettingsFilename;
+    // Compressed current settings file.
+    private final File mCompressedSettingsFilename;
+    // Previous settings file.
+    // Removed when the current settings file successfully stored.
+    private final File mPreviousSettingsFilename;
+
     private final File mPackageListFilename;
     private final File mStoppedPackagesFilename;
     private final File mBackupStoppedPackagesFilename;
     /** The top level directory in configfs for sdcardfs to push the package->uid,userId mappings */
     private final File mKernelMappingFilename;
 
+    // Lock for user package restrictions operations.
+    private final Object mPackageRestrictionsLock = new Object();
+
+    // Pending write operations.
+    @GuardedBy("mPackageRestrictionsLock")
+    private final SparseIntArray mPendingAsyncPackageRestrictionsWrites = new SparseIntArray();
+
     /** Map from package name to settings */
     @Watched
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -432,7 +445,12 @@
         int databaseVersion;
 
         /**
-         * Last known value of {@link Build#FINGERPRINT}. Used to determine when
+         * Last known value of {@link Build#FINGERPRINT}. Stored for debug purposes.
+         */
+        String buildFingerprint;
+
+        /**
+         * Last known value of {@link PackagePartitions#FINGERPRINT}. Used to determine when
          * an system update has occurred, meaning we need to clear code caches.
          */
         String fingerprint;
@@ -444,6 +462,7 @@
         public void forceCurrent() {
             sdkVersion = Build.VERSION.SDK_INT;
             databaseVersion = CURRENT_DATABASE_VERSION;
+            buildFingerprint = Build.FINGERPRINT;
             fingerprint = PackagePartitions.FINGERPRINT;
         }
     }
@@ -455,19 +474,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 +500,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 +536,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 +609,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.
@@ -595,7 +641,8 @@
         mRuntimePermissionsPersistence = null;
         mPermissionDataProvider = null;
         mSettingsFilename = null;
-        mBackupSettingsFilename = null;
+        mCompressedSettingsFilename = null;
+        mPreviousSettingsFilename = null;
         mPackageListFilename = null;
         mStoppedPackagesFilename = null;
         mBackupStoppedPackagesFilename = null;
@@ -623,6 +670,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;
@@ -646,7 +713,8 @@
                 |FileUtils.S_IROTH|FileUtils.S_IXOTH,
                 -1, -1);
         mSettingsFilename = new File(mSystemDir, "packages.xml");
-        mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
+        mCompressedSettingsFilename = new File(mSystemDir, "packages.compressed");
+        mPreviousSettingsFilename = new File(mSystemDir, "packages-backup.xml");
         mPackageListFilename = new File(mSystemDir, "packages.list");
         FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
 
@@ -687,7 +755,8 @@
         mLock = null;
         mRuntimePermissionsPersistence = r.mRuntimePermissionsPersistence;
         mSettingsFilename = null;
-        mBackupSettingsFilename = null;
+        mCompressedSettingsFilename = null;
+        mPreviousSettingsFilename = null;
         mPackageListFilename = null;
         mStoppedPackagesFilename = null;
         mBackupStoppedPackagesFilename = null;
@@ -699,24 +768,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;
@@ -1419,31 +1491,46 @@
         return (userId == UserHandle.USER_ALL) ? null : mDefaultBrowserApp.removeReturnOld(userId);
     }
 
-    private File getUserPackagesStateFile(int userId) {
-        // TODO: Implement a cleaner solution when adding tests.
+    private File getUserSystemDirectory(int userId) {
         // This instead of Environment.getUserSystemDirectory(userId) to support testing.
-        File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));
-        return new File(userDir, "package-restrictions.xml");
+        return new File(new File(mSystemDir, "users"), Integer.toString(userId));
+    }
+
+    // The method itself does not have to be guarded, but the file does.
+    @GuardedBy("mPackageRestrictionsLock")
+    private File getUserPackagesStateFile(int userId) {
+        return new File(getUserSystemDirectory(userId), "package-restrictions.xml");
+    }
+
+    // The method itself does not have to be guarded, but the file does.
+    @GuardedBy("mPackageRestrictionsLock")
+    private File getUserPackagesStateBackupFile(int userId) {
+        return new File(getUserSystemDirectory(userId), "package-restrictions-backup.xml");
     }
 
     private File getUserRuntimePermissionsFile(int userId) {
-        // TODO: Implement a cleaner solution when adding tests.
-        // This instead of Environment.getUserSystemDirectory(userId) to support testing.
-        File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));
-        return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME);
+        return new File(getUserSystemDirectory(userId), RUNTIME_PERMISSIONS_FILE_NAME);
     }
 
-    private File getUserPackagesStateBackupFile(int userId) {
-        return new File(Environment.getUserSystemDirectory(userId),
-                "package-restrictions-backup.xml");
-    }
-
+    // Default version is writing restrictions asynchronously.
     void writeAllUsersPackageRestrictionsLPr() {
+        writeAllUsersPackageRestrictionsLPr(/*sync=*/false);
+    }
+
+    void writeAllUsersPackageRestrictionsLPr(boolean sync) {
         List<UserInfo> users = getAllUsers(UserManagerService.getInstance());
         if (users == null) return;
 
+        if (sync) {
+            // Cancel all pending per-user writes.
+            synchronized (mPackageRestrictionsLock) {
+                mPendingAsyncPackageRestrictionsWrites.clear();
+            }
+            mHandler.removeMessages(WRITE_USER_PACKAGE_RESTRICTIONS);
+        }
+
         for (UserInfo user : users) {
-            writePackageRestrictionsLPr(user.id);
+            writePackageRestrictionsLPr(user.id, sync);
         }
     }
 
@@ -1634,63 +1721,75 @@
             Log.i(TAG, "Reading package restrictions for user=" + userId);
         }
         FileInputStream str = null;
-        File userPackagesStateFile = getUserPackagesStateFile(userId);
-        File backupFile = getUserPackagesStateBackupFile(userId);
-        if (backupFile.exists()) {
-            try {
-                str = new FileInputStream(backupFile);
-                mReadMessages.append("Reading from backup stopped packages file\n");
-                PackageManagerService.reportSettingsProblem(Log.INFO,
-                        "Need to read from backup stopped packages file");
-                if (userPackagesStateFile.exists()) {
-                    // If both the backup and normal file exist, we
-                    // ignore the normal one since it might have been
-                    // corrupted.
-                    Slog.w(PackageManagerService.TAG, "Cleaning up stopped packages file "
-                            + userPackagesStateFile);
-                    userPackagesStateFile.delete();
+
+        synchronized (mPackageRestrictionsLock) {
+            File userPackagesStateFile = getUserPackagesStateFile(userId);
+            File backupFile = getUserPackagesStateBackupFile(userId);
+            if (backupFile.exists()) {
+                try {
+                    str = new FileInputStream(backupFile);
+                    mReadMessages.append("Reading from backup stopped packages file\n");
+                    PackageManagerService.reportSettingsProblem(Log.INFO,
+                            "Need to read from backup stopped packages file");
+                    if (userPackagesStateFile.exists()) {
+                        // If both the backup and normal file exist, we
+                        // ignore the normal one since it might have been
+                        // corrupted.
+                        Slog.w(PackageManagerService.TAG, "Cleaning up stopped packages file "
+                                + userPackagesStateFile);
+                        userPackagesStateFile.delete();
+                    }
+                } catch (java.io.IOException e) {
+                    // We'll try for the normal settings file.
                 }
-            } catch (java.io.IOException e) {
-                // We'll try for the normal settings file.
+            }
+
+            if (str == null && userPackagesStateFile.exists()) {
+                try {
+                    str = new FileInputStream(userPackagesStateFile);
+                    if (DEBUG_MU) Log.i(TAG, "Reading " + userPackagesStateFile);
+                } catch (java.io.IOException e) {
+                    mReadMessages.append("Error reading: " + e.toString());
+                    PackageManagerService.reportSettingsProblem(Log.ERROR,
+                            "Error reading settings: " + e);
+                    Slog.wtf(TAG, "Error reading package manager stopped packages", e);
+                }
             }
         }
 
-        try {
-            if (str == null) {
-                if (!userPackagesStateFile.exists()) {
-                    mReadMessages.append("No stopped packages file found\n");
-                    PackageManagerService.reportSettingsProblem(Log.INFO,
-                            "No stopped packages file; "
+        if (str == null) {
+            mReadMessages.append("No stopped packages file found\n");
+            PackageManagerService.reportSettingsProblem(Log.INFO,
+                    "No stopped packages file; "
                             + "assuming all started");
-                    // At first boot, make sure no packages are stopped.
-                    // We usually want to have third party apps initialize
-                    // in the stopped state, but not at first boot.  Also
-                    // consider all applications to be installed.
-                    for (PackageSetting pkg : mPackages.values()) {
-                        pkg.setUserState(userId, 0, COMPONENT_ENABLED_STATE_DEFAULT,
-                                true  /*installed*/,
-                                false /*stopped*/,
-                                false /*notLaunched*/,
-                                false /*hidden*/,
-                                0 /*distractionFlags*/,
-                                null /*suspendParams*/,
-                                false /*instantApp*/,
-                                false /*virtualPreload*/,
-                                null /*lastDisableAppCaller*/,
-                                null /*enabledComponents*/,
-                                null /*disabledComponents*/,
-                                PackageManager.INSTALL_REASON_UNKNOWN,
-                                PackageManager.UNINSTALL_REASON_UNKNOWN,
-                                null /*harmfulAppWarning*/,
-                                null /* splashScreenTheme*/,
-                                0 /*firstInstallTime*/
-                        );
-                    }
-                    return;
-                }
-                str = new FileInputStream(userPackagesStateFile);
-                if (DEBUG_MU) Log.i(TAG, "Reading " + userPackagesStateFile);
+            // At first boot, make sure no packages are stopped.
+            // We usually want to have third party apps initialize
+            // in the stopped state, but not at first boot.  Also
+            // consider all applications to be installed.
+            for (PackageSetting pkg : mPackages.values()) {
+                pkg.setUserState(userId, 0, COMPONENT_ENABLED_STATE_DEFAULT,
+                        true  /*installed*/,
+                        false /*stopped*/,
+                        false /*notLaunched*/,
+                        false /*hidden*/,
+                        0 /*distractionFlags*/,
+                        null /*suspendParams*/,
+                        false /*instantApp*/,
+                        false /*virtualPreload*/,
+                        null /*lastDisableAppCaller*/,
+                        null /*enabledComponents*/,
+                        null /*disabledComponents*/,
+                        PackageManager.INSTALL_REASON_UNKNOWN,
+                        PackageManager.UNINSTALL_REASON_UNKNOWN,
+                        null /*harmfulAppWarning*/,
+                        null /* splashScreenTheme*/,
+                        0 /*firstInstallTime*/
+                );
             }
+            return;
+        }
+
+        try {
             final TypedXmlPullParser parser = Xml.resolvePullParser(str);
 
             int type;
@@ -2013,179 +2112,247 @@
         }
     }
 
+    // Default version is writing restrictions asynchronously.
     void writePackageRestrictionsLPr(int userId) {
+        writePackageRestrictionsLPr(userId, /*sync=*/false);
+    }
+
+    void writePackageRestrictionsLPr(int userId, boolean sync) {
         invalidatePackageCache();
 
+        final long startTime = SystemClock.uptimeMillis();
+
+        if (sync) {
+            writePackageRestrictions(userId, startTime, sync);
+        } else {
+            if (DEBUG_MU) {
+                Log.i(TAG, "Scheduling deferred IO sync for user=" + userId);
+            }
+            synchronized (mPackageRestrictionsLock) {
+                int pending = mPendingAsyncPackageRestrictionsWrites.get(userId, 0) + 1;
+                mPendingAsyncPackageRestrictionsWrites.put(userId, pending);
+            }
+            Runnable r = () -> writePackageRestrictions(userId, startTime, sync);
+            mHandler.obtainMessage(WRITE_USER_PACKAGE_RESTRICTIONS, r).sendToTarget();
+        }
+    }
+
+    void writePackageRestrictions(Integer[] userIds) {
+        invalidatePackageCache();
+        final long startTime = SystemClock.uptimeMillis();
+        for (int userId : userIds) {
+            writePackageRestrictions(userId, startTime, /*sync=*/true);
+        }
+    }
+
+    void writePackageRestrictions(int userId, long startTime, boolean sync) {
         if (DEBUG_MU) {
             Log.i(TAG, "Writing package restrictions for user=" + userId);
         }
-        final long startTime = SystemClock.uptimeMillis();
 
-        // Keep the old stopped packages around until we know the new ones have
-        // been successfully written.
-        File userPackagesStateFile = getUserPackagesStateFile(userId);
-        File backupFile = getUserPackagesStateBackupFile(userId);
-        new File(userPackagesStateFile.getParent()).mkdirs();
-        if (userPackagesStateFile.exists()) {
-            // Presence of backup settings file indicates that we failed
-            // to persist packages earlier. So preserve the older
-            // backup for future reference since the current packages
-            // might have been corrupted.
-            if (!backupFile.exists()) {
-                if (!userPackagesStateFile.renameTo(backupFile)) {
-                    Slog.wtf(PackageManagerService.TAG,
-                            "Unable to backup user packages state file, "
-                            + "current changes will be lost at reboot");
+        final File userPackagesStateFile;
+        final File backupFile;
+        final FileOutputStream fstr;
+
+        synchronized (mPackageRestrictionsLock) {
+            if (!sync) {
+                int pending = mPendingAsyncPackageRestrictionsWrites.get(userId, 0) - 1;
+                if (pending < 0) {
+                    Log.i(TAG, "Cancel writing package restrictions for user=" + userId);
                     return;
                 }
-            } else {
-                userPackagesStateFile.delete();
-                Slog.w(PackageManagerService.TAG, "Preserving older stopped packages backup");
+                mPendingAsyncPackageRestrictionsWrites.put(userId, pending);
+            }
+
+            // Keep the old stopped packages around until we know the new ones have
+            // been successfully written.
+            userPackagesStateFile = getUserPackagesStateFile(userId);
+            backupFile = getUserPackagesStateBackupFile(userId);
+            new File(userPackagesStateFile.getParent()).mkdirs();
+            if (userPackagesStateFile.exists()) {
+                // Presence of backup settings file indicates that we failed
+                // to persist packages earlier. So preserve the older
+                // backup for future reference since the current packages
+                // might have been corrupted.
+                if (!backupFile.exists()) {
+                    if (!userPackagesStateFile.renameTo(backupFile)) {
+                        Slog.wtf(PackageManagerService.TAG,
+                                "Unable to backup user packages state file, "
+                                        + "current changes will be lost at reboot");
+                        return;
+                    }
+                } else {
+                    userPackagesStateFile.delete();
+                    Slog.w(PackageManagerService.TAG, "Preserving older stopped packages backup");
+                }
+            }
+
+            try {
+                fstr = new FileOutputStream(userPackagesStateFile);
+                // File is created, set permissions.
+                FileUtils.setPermissions(userPackagesStateFile.toString(),
+                        FileUtils.S_IRUSR | FileUtils.S_IWUSR
+                                | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+                        -1, -1);
+            } catch (java.io.IOException e) {
+                Slog.wtf(PackageManagerService.TAG,
+                        "Unable to write package manager user packages state, "
+                                + " current changes will be lost at reboot", e);
+                return;
             }
         }
 
         try {
-            final FileOutputStream fstr = new FileOutputStream(userPackagesStateFile);
-            final TypedXmlSerializer serializer = Xml.resolveSerializer(fstr);
-            serializer.startDocument(null, true);
-            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            synchronized (mLock) {
+                final TypedXmlSerializer serializer = Xml.resolveSerializer(fstr);
+                serializer.startDocument(null, true);
+                serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
+                        true);
 
-            serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS);
+                serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS);
 
-            if (DEBUG_MU) {
-                Slogf.i(TAG, "Writing %s (%d packages)", userPackagesStateFile,
-                        mPackages.values().size());
-            }
-            for (final PackageSetting pkg : mPackages.values()) {
-                final PackageUserStateInternal ustate = pkg.readUserState(userId);
                 if (DEBUG_MU) {
-                    Log.v(TAG, "  pkg=" + pkg.getPackageName()
-                            + ", installed=" + ustate.isInstalled()
-                            + ", state=" + ustate.getEnabledState());
+                    Slogf.i(TAG, "Writing %s (%d packages)", userPackagesStateFile,
+                            mPackages.values().size());
                 }
-
-                serializer.startTag(null, TAG_PACKAGE);
-                serializer.attribute(null, ATTR_NAME, pkg.getPackageName());
-                if (ustate.getCeDataInode() != 0) {
-                    serializer.attributeLong(null, ATTR_CE_DATA_INODE, ustate.getCeDataInode());
-                }
-                if (!ustate.isInstalled()) {
-                    serializer.attributeBoolean(null, ATTR_INSTALLED, false);
-                }
-                if (ustate.isStopped()) {
-                    serializer.attributeBoolean(null, ATTR_STOPPED, true);
-                }
-                if (ustate.isNotLaunched()) {
-                    serializer.attributeBoolean(null, ATTR_NOT_LAUNCHED, true);
-                }
-                if (ustate.isHidden()) {
-                    serializer.attributeBoolean(null, ATTR_HIDDEN, true);
-                }
-                if (ustate.getDistractionFlags() != 0) {
-                    serializer.attributeInt(null, ATTR_DISTRACTION_FLAGS,
-                            ustate.getDistractionFlags());
-                }
-                if (ustate.isSuspended()) {
-                    serializer.attributeBoolean(null, ATTR_SUSPENDED, true);
-                }
-                if (ustate.isInstantApp()) {
-                    serializer.attributeBoolean(null, ATTR_INSTANT_APP, true);
-                }
-                if (ustate.isVirtualPreload()) {
-                    serializer.attributeBoolean(null, ATTR_VIRTUAL_PRELOAD, true);
-                }
-                if (ustate.getEnabledState() != COMPONENT_ENABLED_STATE_DEFAULT) {
-                    serializer.attributeInt(null, ATTR_ENABLED, ustate.getEnabledState());
-                    if (ustate.getLastDisableAppCaller() != null) {
-                        serializer.attribute(null, ATTR_ENABLED_CALLER,
-                                ustate.getLastDisableAppCaller());
+                for (final PackageSetting pkg : mPackages.values()) {
+                    final PackageUserStateInternal ustate = pkg.readUserState(userId);
+                    if (DEBUG_MU) {
+                        Log.v(TAG, "  pkg=" + pkg.getPackageName()
+                                + ", installed=" + ustate.isInstalled()
+                                + ", state=" + ustate.getEnabledState());
                     }
-                }
-                if (ustate.getInstallReason() != PackageManager.INSTALL_REASON_UNKNOWN) {
-                    serializer.attributeInt(null, ATTR_INSTALL_REASON,
-                            ustate.getInstallReason());
-                }
-                serializer.attributeLongHex(null, ATTR_FIRST_INSTALL_TIME,
-                        ustate.getFirstInstallTime());
-                if (ustate.getUninstallReason() != PackageManager.UNINSTALL_REASON_UNKNOWN) {
-                    serializer.attributeInt(null, ATTR_UNINSTALL_REASON,
-                            ustate.getUninstallReason());
-                }
-                if (ustate.getHarmfulAppWarning() != null) {
-                    serializer.attribute(null, ATTR_HARMFUL_APP_WARNING,
-                            ustate.getHarmfulAppWarning());
-                }
-                if (ustate.getSplashScreenTheme() != null) {
-                    serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME,
-                            ustate.getSplashScreenTheme());
-                }
-                if (ustate.isSuspended()) {
-                    for (int i = 0; i < ustate.getSuspendParams().size(); i++) {
-                        final String suspendingPackage = ustate.getSuspendParams().keyAt(i);
-                        serializer.startTag(null, TAG_SUSPEND_PARAMS);
-                        serializer.attribute(null, ATTR_SUSPENDING_PACKAGE, suspendingPackage);
-                        final SuspendParams params =
-                                ustate.getSuspendParams().valueAt(i);
-                        if (params != null) {
-                            params.saveToXml(serializer);
+
+                    serializer.startTag(null, TAG_PACKAGE);
+                    serializer.attribute(null, ATTR_NAME, pkg.getPackageName());
+                    if (ustate.getCeDataInode() != 0) {
+                        serializer.attributeLong(null, ATTR_CE_DATA_INODE, ustate.getCeDataInode());
+                    }
+                    if (!ustate.isInstalled()) {
+                        serializer.attributeBoolean(null, ATTR_INSTALLED, false);
+                    }
+                    if (ustate.isStopped()) {
+                        serializer.attributeBoolean(null, ATTR_STOPPED, true);
+                    }
+                    if (ustate.isNotLaunched()) {
+                        serializer.attributeBoolean(null, ATTR_NOT_LAUNCHED, true);
+                    }
+                    if (ustate.isHidden()) {
+                        serializer.attributeBoolean(null, ATTR_HIDDEN, true);
+                    }
+                    if (ustate.getDistractionFlags() != 0) {
+                        serializer.attributeInt(null, ATTR_DISTRACTION_FLAGS,
+                                ustate.getDistractionFlags());
+                    }
+                    if (ustate.isSuspended()) {
+                        serializer.attributeBoolean(null, ATTR_SUSPENDED, true);
+                    }
+                    if (ustate.isInstantApp()) {
+                        serializer.attributeBoolean(null, ATTR_INSTANT_APP, true);
+                    }
+                    if (ustate.isVirtualPreload()) {
+                        serializer.attributeBoolean(null, ATTR_VIRTUAL_PRELOAD, true);
+                    }
+                    if (ustate.getEnabledState() != COMPONENT_ENABLED_STATE_DEFAULT) {
+                        serializer.attributeInt(null, ATTR_ENABLED, ustate.getEnabledState());
+                        if (ustate.getLastDisableAppCaller() != null) {
+                            serializer.attribute(null, ATTR_ENABLED_CALLER,
+                                    ustate.getLastDisableAppCaller());
                         }
-                        serializer.endTag(null, TAG_SUSPEND_PARAMS);
                     }
-                }
-                final ArraySet<String> enabledComponents = ustate.getEnabledComponents();
-                if (enabledComponents != null && enabledComponents.size() > 0) {
-                    serializer.startTag(null, TAG_ENABLED_COMPONENTS);
-                    for (int i = 0; i < enabledComponents.size(); i++) {
-                        serializer.startTag(null, TAG_ITEM);
-                        serializer.attribute(null, ATTR_NAME,
-                                enabledComponents.valueAt(i));
-                        serializer.endTag(null, TAG_ITEM);
+                    if (ustate.getInstallReason() != PackageManager.INSTALL_REASON_UNKNOWN) {
+                        serializer.attributeInt(null, ATTR_INSTALL_REASON,
+                                ustate.getInstallReason());
                     }
-                    serializer.endTag(null, TAG_ENABLED_COMPONENTS);
-                }
-                final ArraySet<String> disabledComponents = ustate.getDisabledComponents();
-                if (disabledComponents != null && disabledComponents.size() > 0) {
-                    serializer.startTag(null, TAG_DISABLED_COMPONENTS);
-                    for (int i = 0; i < disabledComponents.size(); i++) {
-                        serializer.startTag(null, TAG_ITEM);
-                        serializer.attribute(null, ATTR_NAME,
-                                disabledComponents.valueAt(i));
-                        serializer.endTag(null, TAG_ITEM);
+                    serializer.attributeLongHex(null, ATTR_FIRST_INSTALL_TIME,
+                            ustate.getFirstInstallTime());
+                    if (ustate.getUninstallReason() != PackageManager.UNINSTALL_REASON_UNKNOWN) {
+                        serializer.attributeInt(null, ATTR_UNINSTALL_REASON,
+                                ustate.getUninstallReason());
                     }
-                    serializer.endTag(null, TAG_DISABLED_COMPONENTS);
+                    if (ustate.getHarmfulAppWarning() != null) {
+                        serializer.attribute(null, ATTR_HARMFUL_APP_WARNING,
+                                ustate.getHarmfulAppWarning());
+                    }
+                    if (ustate.getSplashScreenTheme() != null) {
+                        serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME,
+                                ustate.getSplashScreenTheme());
+                    }
+                    if (ustate.isSuspended()) {
+                        for (int i = 0; i < ustate.getSuspendParams().size(); i++) {
+                            final String suspendingPackage = ustate.getSuspendParams().keyAt(i);
+                            serializer.startTag(null, TAG_SUSPEND_PARAMS);
+                            serializer.attribute(null, ATTR_SUSPENDING_PACKAGE, suspendingPackage);
+                            final SuspendParams params =
+                                    ustate.getSuspendParams().valueAt(i);
+                            if (params != null) {
+                                params.saveToXml(serializer);
+                            }
+                            serializer.endTag(null, TAG_SUSPEND_PARAMS);
+                        }
+                    }
+                    final ArraySet<String> enabledComponents = ustate.getEnabledComponents();
+                    if (enabledComponents != null && enabledComponents.size() > 0) {
+                        serializer.startTag(null, TAG_ENABLED_COMPONENTS);
+                        for (int i = 0; i < enabledComponents.size(); i++) {
+                            serializer.startTag(null, TAG_ITEM);
+                            serializer.attribute(null, ATTR_NAME,
+                                    enabledComponents.valueAt(i));
+                            serializer.endTag(null, TAG_ITEM);
+                        }
+                        serializer.endTag(null, TAG_ENABLED_COMPONENTS);
+                    }
+                    final ArraySet<String> disabledComponents = ustate.getDisabledComponents();
+                    if (disabledComponents != null && disabledComponents.size() > 0) {
+                        serializer.startTag(null, TAG_DISABLED_COMPONENTS);
+                        for (int i = 0; i < disabledComponents.size(); i++) {
+                            serializer.startTag(null, TAG_ITEM);
+                            serializer.attribute(null, ATTR_NAME,
+                                    disabledComponents.valueAt(i));
+                            serializer.endTag(null, TAG_ITEM);
+                        }
+                        serializer.endTag(null, TAG_DISABLED_COMPONENTS);
+                    }
+
+                    serializer.endTag(null, TAG_PACKAGE);
                 }
 
-                serializer.endTag(null, TAG_PACKAGE);
+                writePreferredActivitiesLPr(serializer, userId, true);
+                writePersistentPreferredActivitiesLPr(serializer, userId);
+                writeCrossProfileIntentFiltersLPr(serializer, userId);
+                writeDefaultAppsLPr(serializer, userId);
+                writeBlockUninstallPackagesLPr(serializer, userId);
+
+                serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
+
+                serializer.endDocument();
             }
 
-            writePreferredActivitiesLPr(serializer, userId, true);
-            writePersistentPreferredActivitiesLPr(serializer, userId);
-            writeCrossProfileIntentFiltersLPr(serializer, userId);
-            writeDefaultAppsLPr(serializer, userId);
-            writeBlockUninstallPackagesLPr(serializer, userId);
-
-            serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
-
-            serializer.endDocument();
-
             fstr.flush();
             FileUtils.sync(fstr);
-            fstr.close();
+            IoUtils.closeQuietly(fstr);
 
-            // New settings successfully written, old ones are no longer
-            // needed.
-            backupFile.delete();
-            FileUtils.setPermissions(userPackagesStateFile.toString(),
-                    FileUtils.S_IRUSR|FileUtils.S_IWUSR
-                    |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
-                    -1, -1);
+            synchronized (mPackageRestrictionsLock) {
+                // File is created, set permissions.
+                FileUtils.setPermissions(userPackagesStateFile.toString(),
+                        FileUtils.S_IRUSR | FileUtils.S_IWUSR
+                                | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+                        -1, -1);
+                // New settings successfully written, old ones are no longer needed.
+                backupFile.delete();
+            }
+
+            if (DEBUG_MU) {
+                Log.i(TAG, "New settings successfully written for user=" + userId + ": "
+                        + userPackagesStateFile);
+            }
 
             com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
                     "package-user-" + userId, SystemClock.uptimeMillis() - startTime);
 
             // Done, all is good!
             return;
-        } catch(java.io.IOException e) {
+        } catch (java.io.IOException e) {
             Slog.wtf(PackageManagerService.TAG,
                     "Unable to write package manager user packages state, "
                     + " current changes will be lost at reboot", e);
@@ -2396,7 +2563,7 @@
         }
     }
 
-    void writeLPr(@NonNull Computer computer) {
+    void writeLPr(@NonNull Computer computer, boolean sync) {
         //Debug.startMethodTracing("/data/system/packageprof", 8 * 1024 * 1024);
 
         final long startTime = SystemClock.uptimeMillis();
@@ -2414,10 +2581,10 @@
             // to persist settings earlier. So preserve the older
             // backup for future reference since the current settings
             // might have been corrupted.
-            if (!mBackupSettingsFilename.exists()) {
-                if (!mSettingsFilename.renameTo(mBackupSettingsFilename)) {
+            if (!mPreviousSettingsFilename.exists()) {
+                if (!mSettingsFilename.renameTo(mPreviousSettingsFilename)) {
                     Slog.wtf(PackageManagerService.TAG,
-                            "Unable to backup package manager settings, "
+                            "Unable to store older package manager settings, "
                             + " current changes will be lost at reboot");
                     return;
                 }
@@ -2426,6 +2593,8 @@
                 Slog.w(PackageManagerService.TAG, "Preserving older settings backup");
             }
         }
+        // Compressed settings are not valid anymore.
+        mCompressedSettingsFilename.delete();
 
         mPastSignatures.clear();
 
@@ -2445,6 +2614,8 @@
                 XmlUtils.writeStringAttribute(serializer, ATTR_VOLUME_UUID, volumeUuid);
                 serializer.attributeInt(null, ATTR_SDK_VERSION, ver.sdkVersion);
                 serializer.attributeInt(null, ATTR_DATABASE_VERSION, ver.databaseVersion);
+                XmlUtils.writeStringAttribute(serializer, ATTR_BUILD_FINGERPRINT,
+                        ver.buildFingerprint);
                 XmlUtils.writeStringAttribute(serializer, ATTR_FINGERPRINT, ver.fingerprint);
                 serializer.endTag(null, TAG_VERSION);
             }
@@ -2509,17 +2680,37 @@
             FileUtils.sync(fstr);
             fstr.close();
 
-            // New settings successfully written, old ones are no longer
-            // needed.
-            mBackupSettingsFilename.delete();
+            // New settings successfully written, old ones are no longer needed.
+            mPreviousSettingsFilename.delete();
+
             FileUtils.setPermissions(mSettingsFilename.toString(),
-                    FileUtils.S_IRUSR|FileUtils.S_IWUSR
-                    |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
+                    FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
                     -1, -1);
 
+            final FileInputStream fis = new FileInputStream(mSettingsFilename);
+            final AtomicFile compressed = new AtomicFile(mCompressedSettingsFilename);
+            final FileOutputStream fos = compressed.startWrite();
+
+            BackgroundThread.getHandler().post(() -> {
+                try {
+                    if (!nativeCompressLz4(fis.getFD().getInt$(), fos.getFD().getInt$())) {
+                        throw new IOException("Failed to compress");
+                    }
+                    compressed.finishWrite(fos);
+                    FileUtils.setPermissions(mCompressedSettingsFilename.toString(),
+                            FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP
+                                    | FileUtils.S_IWGRP, -1, -1);
+                } catch (IOException e) {
+                    Slog.e(PackageManagerService.TAG, "Failed to write compressed settings file: "
+                            + mCompressedSettingsFilename, e);
+                    compressed.delete();
+                }
+                IoUtils.closeQuietly(fis);
+            });
+
             writeKernelMappingLPr();
             writePackageListLPr();
-            writeAllUsersPackageRestrictionsLPr();
+            writeAllUsersPackageRestrictionsLPr(sync);
             writeAllRuntimePermissionsLPr();
             com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
                     "package", SystemClock.uptimeMillis() - startTime);
@@ -2539,6 +2730,8 @@
         //Debug.stopMethodTracing();
     }
 
+    private native boolean nativeCompressLz4(int inputFd, int outputFd);
+
     private void writeKernelRemoveUserLPr(int userId) {
         if (mKernelMappingFilename == null) return;
 
@@ -2739,7 +2932,7 @@
                 sb.append(isDebug ? " 1 " : " 0 ");
                 sb.append(dataPath);
                 sb.append(" ");
-                sb.append(AndroidPackageUtils.getSeInfo(pkg.getPkg(), pkg));
+                sb.append(pkg.getSeInfo());
                 sb.append(" ");
                 final int gidsSize = gids.size();
                 if (gids != null && gids.size() > 0) {
@@ -2762,9 +2955,9 @@
                     sb.append("@system");
                 } else if (pkg.isProduct()) {
                     sb.append("@product");
-                } else if (pkg.getInstallSource().installerPackageName != null
-                           && !pkg.getInstallSource().installerPackageName.isEmpty()) {
-                    sb.append(pkg.getInstallSource().installerPackageName);
+                } else if (pkg.getInstallSource().mInstallerPackageName != null
+                           && !pkg.getInstallSource().mInstallerPackageName.isEmpty()) {
+                    sb.append(pkg.getInstallSource().mInstallerPackageName);
                 } else {
                     sb.append("@null");
                 }
@@ -2855,26 +3048,29 @@
             serializer.attributeInt(null, "sharedUserId", pkg.getAppId());
         }
         InstallSource installSource = pkg.getInstallSource();
-        if (installSource.installerPackageName != null) {
-            serializer.attribute(null, "installer", installSource.installerPackageName);
+        if (installSource.mInstallerPackageName != null) {
+            serializer.attribute(null, "installer", installSource.mInstallerPackageName);
         }
-        if (installSource.installerAttributionTag != null) {
+        if (installSource.mInstallerPackageUid != INVALID_UID) {
+            serializer.attributeInt(null, "installerUid", installSource.mInstallerPackageUid);
+        }
+        if (installSource.mInstallerAttributionTag != null) {
             serializer.attribute(null, "installerAttributionTag",
-                    installSource.installerAttributionTag);
+                    installSource.mInstallerAttributionTag);
         }
         serializer.attributeInt(null, "packageSource",
-                installSource.packageSource);
-        if (installSource.isOrphaned) {
+                installSource.mPackageSource);
+        if (installSource.mIsOrphaned) {
             serializer.attributeBoolean(null, "isOrphaned", true);
         }
-        if (installSource.initiatingPackageName != null) {
-            serializer.attribute(null, "installInitiator", installSource.initiatingPackageName);
+        if (installSource.mInitiatingPackageName != null) {
+            serializer.attribute(null, "installInitiator", installSource.mInitiatingPackageName);
         }
-        if (installSource.isInitiatingPackageUninstalled) {
+        if (installSource.mIsInitiatingPackageUninstalled) {
             serializer.attributeBoolean(null, "installInitiatorUninstalled", true);
         }
-        if (installSource.originatingPackageName != null) {
-            serializer.attribute(null, "installOriginator", installSource.originatingPackageName);
+        if (installSource.mOriginatingPackageName != null) {
+            serializer.attribute(null, "installOriginator", installSource.mOriginatingPackageName);
         }
         if (pkg.getVolumeUuid() != null) {
             serializer.attribute(null, "volumeUuid", pkg.getVolumeUuid());
@@ -2903,8 +3099,8 @@
 
         pkg.getSignatures().writeXml(serializer, "sigs", mPastSignatures.untrackedStorage());
 
-        if (installSource.initiatingPackageSignatures != null) {
-            installSource.initiatingPackageSignatures.writeXml(
+        if (installSource.mInitiatingPackageSignatures != null) {
+            installSource.mInitiatingPackageSignatures.writeXml(
                     serializer, "install-initiator-sigs", mPastSignatures.untrackedStorage());
         }
 
@@ -2946,16 +3142,15 @@
 
     boolean readLPw(@NonNull Computer computer, @NonNull List<UserInfo> users) {
         FileInputStream str = null;
-        if (mBackupSettingsFilename.exists()) {
+        if (mPreviousSettingsFilename.exists()) {
             try {
-                str = new FileInputStream(mBackupSettingsFilename);
+                str = new FileInputStream(mPreviousSettingsFilename);
                 mReadMessages.append("Reading from backup settings file\n");
                 PackageManagerService.reportSettingsProblem(Log.INFO,
                         "Need to read from backup settings file");
                 if (mSettingsFilename.exists()) {
-                    // If both the backup and settings file exist, we
-                    // ignore the settings since it might have been
-                    // corrupted.
+                    // If both the previous and current settings files exist,
+                    // we ignore the current since it might have been corrupted.
                     Slog.w(PackageManagerService.TAG, "Cleaning up settings file "
                             + mSettingsFilename);
                     mSettingsFilename.delete();
@@ -3055,6 +3250,8 @@
 
                     internal.sdkVersion = parser.getAttributeInt(null, "internal", 0);
                     external.sdkVersion = parser.getAttributeInt(null, "external", 0);
+                    internal.buildFingerprint = external.buildFingerprint =
+                            XmlUtils.readStringAttribute(parser, "buildFingerprint");
                     internal.fingerprint = external.fingerprint =
                             XmlUtils.readStringAttribute(parser, "fingerprint");
 
@@ -3086,6 +3283,8 @@
                     final VersionInfo ver = findOrCreateVersion(volumeUuid);
                     ver.sdkVersion = parser.getAttributeInt(null, ATTR_SDK_VERSION);
                     ver.databaseVersion = parser.getAttributeInt(null, ATTR_DATABASE_VERSION);
+                    ver.buildFingerprint = XmlUtils.readStringAttribute(parser,
+                            ATTR_BUILD_FINGERPRINT);
                     ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT);
                 } else if (tagName.equals(DomainVerificationPersistence.TAG_DOMAIN_VERIFICATIONS)) {
                     mDomainVerificationManager.readSettings(computer, parser);
@@ -3150,7 +3349,7 @@
             mBackupStoppedPackagesFilename.delete();
             mStoppedPackagesFilename.delete();
             // Migrate to new file format
-            writePackageRestrictionsLPr(UserHandle.USER_SYSTEM);
+            writePackageRestrictionsLPr(UserHandle.USER_SYSTEM, /*sync=*/true);
         } else {
             for (UserInfo user : users) {
                 readPackageRestrictionsLPr(user.id, originalFirstInstallTimes);
@@ -3644,6 +3843,7 @@
         String cpuAbiOverrideString = null;
         String systemStr = null;
         String installerPackageName = null;
+        int installerPackageUid = INVALID_UID;
         String installerAttributionTag = null;
         int packageSource = PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED;
         boolean isOrphaned = false;
@@ -3686,6 +3886,7 @@
 
             versionCode = parser.getAttributeLong(null, "version", 0);
             installerPackageName = parser.getAttributeValue(null, "installer");
+            installerPackageUid = parser.getAttributeInt(null, "installerUid", INVALID_UID);
             installerAttributionTag = parser.getAttributeValue(null, "installerAttributionTag");
             packageSource = parser.getAttributeInt(null, "packageSource",
                     PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
@@ -3828,8 +4029,8 @@
         if (packageSetting != null) {
             InstallSource installSource = InstallSource.create(
                     installInitiatingPackageName, installOriginatingPackageName,
-                    installerPackageName, installerAttributionTag, packageSource, isOrphaned,
-                    installInitiatorUninstalled);
+                    installerPackageName, installerPackageUid, installerAttributionTag,
+                    packageSource, isOrphaned, installInitiatorUninstalled);
             packageSetting.setInstallSource(installSource)
                     .setVolumeUuid(volumeUuid)
                     .setCategoryOverride(categoryHint)
@@ -3973,14 +4174,14 @@
     }
 
     void addInstallerPackageNames(InstallSource installSource) {
-        if (installSource.installerPackageName != null) {
-            mInstallerPackages.add(installSource.installerPackageName);
+        if (installSource.mInstallerPackageName != null) {
+            mInstallerPackages.add(installSource.mInstallerPackageName);
         }
-        if (installSource.initiatingPackageName != null) {
-            mInstallerPackages.add(installSource.initiatingPackageName);
+        if (installSource.mInitiatingPackageName != null) {
+            mInstallerPackages.add(installSource.mInitiatingPackageName);
         }
-        if (installSource.originatingPackageName != null) {
-            mInstallerPackages.add(installSource.originatingPackageName);
+        if (installSource.mOriginatingPackageName != null) {
+            mInstallerPackages.add(installSource.mOriginatingPackageName);
         }
     }
 
@@ -4189,7 +4390,7 @@
                     // (CE storage is not ready yet; the CE data directories will be created later,
                     // when the user is "unlocked".)  Accumulate all required args, and call the
                     // installer after the mPackages lock has been released.
-                    final String seInfo = AndroidPackageUtils.getSeInfo(ps.getPkg(), ps);
+                    final String seInfo = ps.getSeInfo();
                     final boolean usesSdk = !ps.getPkg().getUsesSdkLibraries().isEmpty();
                     final CreateAppDataArgs args = Installer.buildCreateAppDataArgs(
                             ps.getVolumeUuid(), ps.getPackageName(), userHandle,
@@ -4221,10 +4422,15 @@
             entry.getValue().removeUser(userId);
         }
         mPreferredActivities.remove(userId);
-        File file = getUserPackagesStateFile(userId);
-        file.delete();
-        file = getUserPackagesStateBackupFile(userId);
-        file.delete();
+
+        synchronized (mPackageRestrictionsLock) {
+            File file = getUserPackagesStateFile(userId);
+            file.delete();
+            file = getUserPackagesStateBackupFile(userId);
+            file.delete();
+            mPendingAsyncPackageRestrictionsWrites.delete(userId);
+        }
+
         removeCrossProfileIntentFiltersLPw(userId);
 
         mRuntimePermissionsPersistence.onUserRemoved(userId);
@@ -4269,7 +4475,7 @@
         if (mVerifierDeviceIdentity == null) {
             mVerifierDeviceIdentity = VerifierDeviceIdentity.generate();
 
-            writeLPr(computer);
+            writeLPr(computer, /*sync=*/false);
         }
 
         return mVerifierDeviceIdentity;
@@ -4464,6 +4670,7 @@
             pw.printPair("sdkVersion", ver.sdkVersion);
             pw.printPair("databaseVersion", ver.databaseVersion);
             pw.println();
+            pw.printPair("buildFingerprint", ver.buildFingerprint);
             pw.printPair("fingerprint", ver.fingerprint);
             pw.println();
             pw.decreaseIndent();
@@ -4488,12 +4695,13 @@
             pw.print(",");
             pw.print(ps.getLastUpdateTime());
             pw.print(",");
-            pw.print(ps.getInstallSource().installerPackageName != null
-                    ? ps.getInstallSource().installerPackageName : "?");
-            pw.print(ps.getInstallSource().installerAttributionTag != null
-                    ? "(" + ps.getInstallSource().installerAttributionTag + ")" : "");
+            pw.print(ps.getInstallSource().mInstallerPackageName != null
+                    ? ps.getInstallSource().mInstallerPackageName : "?");
+            pw.print(ps.getInstallSource().mInstallerPackageUid);
+            pw.print(ps.getInstallSource().mInstallerAttributionTag != null
+                    ? "(" + ps.getInstallSource().mInstallerAttributionTag + ")" : "");
             pw.print(",");
-            pw.print(ps.getInstallSource().packageSource);
+            pw.print(ps.getInstallSource().mPackageSource);
             pw.println();
             if (pkg != null) {
                 pw.print(checkinTag); pw.print("-"); pw.print("splt,");
@@ -4765,16 +4973,20 @@
         pw.print(prefix); pw.print("  lastUpdateTime=");
             date.setTime(ps.getLastUpdateTime());
             pw.println(sdf.format(date));
-        if (ps.getInstallSource().installerPackageName != null) {
+        if (ps.getInstallSource().mInstallerPackageName != null) {
             pw.print(prefix); pw.print("  installerPackageName=");
-            pw.println(ps.getInstallSource().installerPackageName);
+            pw.println(ps.getInstallSource().mInstallerPackageName);
         }
-        if (ps.getInstallSource().installerAttributionTag != null) {
+        if (ps.getInstallSource().mInstallerPackageUid != INVALID_UID) {
+            pw.print(prefix); pw.print("  installerPackageUid=");
+            pw.println(ps.getInstallSource().mInstallerPackageUid);
+        }
+        if (ps.getInstallSource().mInstallerAttributionTag != null) {
             pw.print(prefix); pw.print("  installerAttributionTag=");
-            pw.println(ps.getInstallSource().installerAttributionTag);
+            pw.println(ps.getInstallSource().mInstallerAttributionTag);
         }
         pw.print(prefix); pw.print("  packageSource=");
-        pw.println(ps.getInstallSource().packageSource);
+        pw.println(ps.getInstallSource().mPackageSource);
         if (ps.isLoading()) {
             pw.print(prefix); pw.println("  loadingProgress=" +
                     (int) (ps.getLoadingProgress() * 100) + "%");
@@ -5414,8 +5626,8 @@
     }
 
     private static final class RuntimePermissionPersistence {
-        // 200-400ms delay to avoid monopolizing PMS lock when written for multiple users.
-        private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 300;
+        // 700-1300ms delay to avoid monopolizing PMS lock when written for multiple users.
+        private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 1000;
         private static final double WRITE_PERMISSIONS_DELAY_JITTER = 0.3;
 
         private static final long MAX_WRITE_PERMISSIONS_DELAY_MILLIS = 2000;
@@ -5433,8 +5645,7 @@
 
         // Low-priority handlers running on SystemBg thread.
         private final Handler mAsyncHandler = new MyHandler();
-        private final Handler mPersistenceHandler = new Handler(
-                BackgroundThread.getHandler().getLooper());
+        private final Handler mPersistenceHandler = new PersistenceHandler();
 
         private final Object mLock = new Object();
 
@@ -5581,20 +5792,22 @@
                 @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers,
                 @Nullable Handler pmHandler, @NonNull Object pmLock,
                 boolean sync) {
-            final int version;
-            final String fingerprint;
-            final boolean isLegacyPermissionStateStale;
             synchronized (mLock) {
                 mAsyncHandler.removeMessages(userId);
                 mWriteScheduled.delete(userId);
-
-                version = mVersions.get(userId, INITIAL_VERSION);
-                fingerprint = mFingerprints.get(userId);
-                isLegacyPermissionStateStale = mIsLegacyPermissionStateStale;
-                mIsLegacyPermissionStateStale = false;
             }
 
             Runnable writer = () -> {
+                final int version;
+                final String fingerprint;
+                final boolean isLegacyPermissionStateStale;
+                synchronized (mLock) {
+                    version = mVersions.get(userId, INITIAL_VERSION);
+                    fingerprint = mFingerprints.get(userId);
+                    isLegacyPermissionStateStale = mIsLegacyPermissionStateStale;
+                    mIsLegacyPermissionStateStale = false;
+                }
+
                 final RuntimePermissionsState runtimePermissions;
                 synchronized (pmLock) {
                     if (sync || isLegacyPermissionStateStale) {
@@ -5643,7 +5856,7 @@
                 }
                 if (pmHandler != null) {
                     // Async version.
-                    mPersistenceHandler.post(() -> writePendingStates());
+                    mPersistenceHandler.obtainMessage(userId).sendToTarget();
                 } else {
                     // Sync version.
                     writePendingStates();
@@ -5919,6 +6132,17 @@
                 }
             }
         }
+
+        private final class PersistenceHandler extends Handler {
+            PersistenceHandler() {
+                super(BackgroundThread.getHandler().getLooper());
+            }
+
+            @Override
+            public void handleMessage(Message message) {
+                writePendingStates();
+            }
+        }
     }
 
     /**
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/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 0362ddd..4fddc9c 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1470,9 +1470,15 @@
         }
 
         // Then make sure none of the activities have more than the max number of shortcuts.
+        int total = 0;
         for (int i = counts.size() - 1; i >= 0; i--) {
-            service.enforceMaxActivityShortcuts(counts.valueAt(i));
+            int count = counts.valueAt(i);
+            service.enforceMaxActivityShortcuts(count);
+            total += count;
         }
+
+        // Finally make sure that the app doesn't have more than the max number of shortcuts.
+        service.enforceMaxAppShortcuts(total);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index b6f09ff..12a33ee 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;
@@ -181,6 +181,9 @@
     static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15;
 
     @VisibleForTesting
+    static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 60;
+
+    @VisibleForTesting
     static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
 
     @VisibleForTesting
@@ -257,6 +260,11 @@
         String KEY_MAX_SHORTCUTS = "max_shortcuts";
 
         /**
+         * Key name for the max dynamic shortcuts per app. (int)
+         */
+        String KEY_MAX_SHORTCUTS_PER_APP = "max_shortcuts_per_app";
+
+        /**
          * Key name for icon compression quality, 0-100.
          */
         String KEY_ICON_QUALITY = "icon_quality";
@@ -329,9 +337,14 @@
             new SparseArray<>();
 
     /**
+     * Max number of dynamic + manifest shortcuts that each activity can have at a time.
+     */
+    private int mMaxShortcutsPerActivity;
+
+    /**
      * Max number of dynamic + manifest shortcuts that each application can have at a time.
      */
-    private int mMaxShortcuts;
+    private int mMaxShortcutsPerApp;
 
     /**
      * Max number of updating API calls that each application can make during the interval.
@@ -804,9 +817,12 @@
         mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong(
                 ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL));
 
-        mMaxShortcuts = Math.max(0, (int) parser.getLong(
+        mMaxShortcutsPerActivity = Math.max(0, (int) parser.getLong(
                 ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY));
 
+        mMaxShortcutsPerApp = Math.max(0, (int) parser.getLong(
+                ConfigConstants.KEY_MAX_SHORTCUTS_PER_APP, DEFAULT_MAX_SHORTCUTS_PER_APP));
+
         final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
                 ? (int) parser.getLong(
                 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
@@ -1746,16 +1762,33 @@
      *                                  {@link #getMaxActivityShortcuts()}.
      */
     void enforceMaxActivityShortcuts(int numShortcuts) {
-        if (numShortcuts > mMaxShortcuts) {
+        if (numShortcuts > mMaxShortcutsPerActivity) {
             throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
         }
     }
 
     /**
+     * @throws IllegalArgumentException if {@code numShortcuts} is bigger than
+     *                                  {@link #getMaxAppShortcuts()}.
+     */
+    void enforceMaxAppShortcuts(int numShortcuts) {
+        if (numShortcuts > mMaxShortcutsPerApp) {
+            throw new IllegalArgumentException("Max number of dynamic shortcuts per app exceeded");
+        }
+    }
+
+    /**
      * Return the max number of dynamic + manifest shortcuts for each launcher icon.
      */
     int getMaxActivityShortcuts() {
-        return mMaxShortcuts;
+        return mMaxShortcutsPerActivity;
+    }
+
+    /**
+     * Return the max number of dynamic + manifest shortcuts for each launcher icon.
+     */
+    int getMaxAppShortcuts() {
+        return mMaxShortcutsPerApp;
     }
 
     /**
@@ -2188,6 +2221,8 @@
             ps.ensureNotImmutable(shortcut.getId(), /*ignoreInvisible=*/ true);
             fillInDefaultActivity(Arrays.asList(shortcut));
 
+            enforceMaxAppShortcuts(ps.getShortcutCount());
+
             if (!shortcut.hasRank()) {
                 shortcut.setRank(0);
             }
@@ -2575,7 +2610,7 @@
             throws RemoteException {
         verifyCaller(packageName, userId);
 
-        return mMaxShortcuts;
+        return mMaxShortcutsPerActivity;
     }
 
     @Override
@@ -3774,7 +3809,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 +3824,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);
                     }
                 }
@@ -4723,7 +4759,7 @@
                 pw.print("    maxUpdatesPerInterval: ");
                 pw.println(mMaxUpdatesPerInterval);
                 pw.print("    maxShortcutsPerActivity: ");
-                pw.println(mMaxShortcuts);
+                pw.println(mMaxShortcutsPerActivity);
                 pw.println();
 
                 mStatLogger.dump(pw, "  ");
@@ -5210,7 +5246,7 @@
 
     @VisibleForTesting
     int getMaxShortcutsForTest() {
-        return mMaxShortcuts;
+        return mMaxShortcutsPerActivity;
     }
 
     @VisibleForTesting
@@ -5274,7 +5310,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/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 1da442b..74594cc 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -53,7 +53,6 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUtils;
@@ -347,7 +346,7 @@
             // an update, and hence need to restore data for all installed users.
             final int[] installedUsers = PackageStateUtils.queryInstalledUsers(ps, allUsers, true);
 
-            final String seInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
+            final String seInfo = ps.getSeInfo();
             rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers),
                     appId, ceDataInode, seInfo, 0 /*token*/);
         }
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index aeb11b7..4f7c2bd 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);
             }
         }
@@ -205,7 +208,7 @@
         synchronized (mPm.mLock) {
             final boolean isUpgrade = !PackagePartitions.FINGERPRINT.equals(ver.fingerprint);
             if (isUpgrade) {
-                logCriticalInfo(Log.INFO, "Build fingerprint changed from " + ver.fingerprint
+                logCriticalInfo(Log.INFO, "Partitions fingerprint changed from " + ver.fingerprint
                         + " to " + PackagePartitions.FINGERPRINT + "; regranting permissions for "
                         + volumeUuid);
             }
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 56ec8e4..0f920c6 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -25,6 +25,7 @@
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.os.UserManager;
+import android.util.DebugUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -46,6 +47,18 @@
     public @interface OwnerType {
     }
 
+    public static final int USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE = 1;
+    public static final int USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE = 2;
+    public static final int USER_ASSIGNMENT_RESULT_FAILURE = -1;
+
+    private static final String PREFIX_USER_ASSIGNMENT_RESULT = "USER_ASSIGNMENT_RESULT_";
+    @IntDef(flag = false, prefix = {PREFIX_USER_ASSIGNMENT_RESULT}, value = {
+            USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE,
+            USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE,
+            USER_ASSIGNMENT_RESULT_FAILURE
+    })
+    public @interface UserAssignmentResult {}
+
     public interface UserRestrictionsListener {
         /**
          * Called when a user restriction changes.
@@ -77,6 +90,23 @@
     }
 
     /**
+     * Listener for {@link UserManager#isUserVisible() user visibility} changes.
+     */
+    public interface UserVisibilityListener {
+
+        /**
+         * Called when the {@link UserManager#isUserVisible() user visibility} changed.
+         *
+         * <p><b>Note:</b> this method is called independently of
+         * {@link com.android.server.SystemService} callbacks; for example, the call with
+         * {@code visible} {@code true} might be called before the
+         * {@link com.android.server.SystemService#onUserStarting(com.android.server.SystemService.TargetUser)}
+         * call.
+         */
+        void onUserVisibilityChanged(@UserIdInt int userId, boolean visible);
+    }
+
+    /**
      * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to set
      * restrictions enforced by the user.
      *
@@ -326,29 +356,28 @@
     public abstract @Nullable UserProperties getUserProperties(@UserIdInt int userId);
 
     /**
-     * Assigns a user to a display.
-     *
-     * <p>On most devices this call will be a no-op, but it will be used on devices that support
-     * multiple users on multiple displays (like automotives with passenger displays).
-     *
-     * <p><b>NOTE: </b>this method doesn't validate if the display exists, it's up to the caller to
-     * check it. In fact, one of the intended clients for this method is
-     * {@code DisplayManagerService}, which will call it when a virtual display is created (another
-     * client is {@code UserController}, which will call it when a user is started).
-     *
-     */
-    public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId);
-
-    /**
-     * Unassigns a user from its current display.
-     *
-     * <p>On most devices this call will be a no-op, but it will be used on devices that support
-     * multiple users on multiple displays (like automotives with passenger displays).
+     * Assigns a user to a display when it's starting, returning whether the assignment succeeded
+     * and the user is {@link UserManager#isUserVisible() visible}.
      *
      * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
-     * is stopped) and {@code DisplayManagerService} (when a virtual display is destroyed).
+     * is started). If other clients (like {@code CarService} need to explicitly change the user /
+     * display assignment, we'll need to provide other APIs.
+     *
+     * <p><b>NOTE: </b>this method doesn't validate if the display exists, it's up to the caller to
+     * pass a valid display id.
      */
-    public abstract void unassignUserFromDisplay(@UserIdInt int userId);
+    public abstract @UserAssignmentResult int assignUserToDisplayOnStart(@UserIdInt int userId,
+            @UserIdInt int profileGroupId,
+            boolean foreground, int displayId);
+
+    /**
+     * Unassigns a user from its current display when it's stopping.
+     *
+     * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
+     * is stopped). If other clients (like {@code CarService} need to explicitly change the user /
+     * display assignment, we'll need to provide other APIs.
+     */
+    public abstract void unassignUserFromDisplayOnStop(@UserIdInt int userId);
 
     /**
      * Returns {@code true} if the user is visible (as defined by
@@ -390,4 +419,36 @@
      * would make such call).
      */
     public abstract @UserIdInt int getUserAssignedToDisplay(int displayId);
+
+    /**
+     * Gets the user-friendly representation of the {@code result} of a
+     * {@link #assignUserToDisplayOnStart(int, int, boolean, int)} call.
+     */
+    public static String userAssignmentResultToString(@UserAssignmentResult int result) {
+        return DebugUtils.constantToString(UserManagerInternal.class, PREFIX_USER_ASSIGNMENT_RESULT,
+                result);
+    }
+
+    /** Adds a {@link UserVisibilityListener}. */
+    public abstract void addUserVisibilityListener(UserVisibilityListener listener);
+
+    /** Removes a {@link UserVisibilityListener}. */
+    public abstract void removeUserVisibilityListener(UserVisibilityListener listener);
+
+    // TODO(b/242195409): remove this method if not needed anymore
+    /** Notify {@link UserVisibilityListener listeners} that the visibility of the
+     * {@link android.os.UserHandle#USER_SYSTEM} changed. */
+    public abstract void onSystemUserVisibilityChanged(boolean visible);
+
+    /** Return the integer types of the given user IDs. Only used for reporting metrics to statsd.
+     */
+    public abstract int[] getUserTypesForStatsd(@UserIdInt int[] userIds);
+
+    /**
+     * Returns the user id of the main user, or {@link android.os.UserHandle#USER_NULL} if there is
+     * no main user.
+     *
+     * @see UserManager#isMainUser()
+     */
+    public abstract @UserIdInt int getMainUserId();
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index cf0ea43..3234e87 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -18,7 +18,9 @@
 
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
 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 +54,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;
@@ -125,6 +128,7 @@
 import com.android.server.am.UserState;
 import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
 import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
+import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
@@ -185,6 +189,7 @@
     private static final String ATTR_CREATION_TIME = "created";
     private static final String ATTR_LAST_LOGGED_IN_TIME = "lastLoggedIn";
     private static final String ATTR_LAST_LOGGED_IN_FINGERPRINT = "lastLoggedInFingerprint";
+    private static final String ATTR_LAST_ENTERED_FOREGROUND_TIME = "lastEnteredForeground";
     private static final String ATTR_SERIAL_NO = "serialNumber";
     private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
     private static final String ATTR_PARTIAL = "partial";
@@ -259,7 +264,7 @@
     @VisibleForTesting
     static final int MAX_RECENTLY_REMOVED_IDS_SIZE = 100;
 
-    private static final int USER_VERSION = 10;
+    private static final int USER_VERSION = 11;
 
     private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
 
@@ -313,7 +318,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
@@ -335,6 +340,9 @@
         /** Elapsed realtime since boot when the user was unlocked. */
         long unlockRealtime;
 
+        /** Wall clock time in millis when the user last entered the foreground. */
+        long mLastEnteredForegroundTimeMillis;
+
         private long mLastRequestQuietModeEnabledMillis;
 
         /**
@@ -670,6 +678,10 @@
                 final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier());
                 if (user != null) {
                     user.startRealtime = SystemClock.elapsedRealtime();
+                    if (targetUser.getUserIdentifier() == UserHandle.USER_SYSTEM
+                            && targetUser.isFull()) {
+                        mUms.setLastEnteredForegroundTimeToNow(user);
+                    }
                 }
             }
         }
@@ -685,6 +697,16 @@
         }
 
         @Override
+        public void onUserSwitching(@NonNull TargetUser from, @NonNull TargetUser to) {
+            synchronized (mUms.mUsersLock) {
+                final UserData user = mUms.getUserDataLU(to.getUserIdentifier());
+                if (user != null) {
+                    mUms.setLastEnteredForegroundTimeToNow(user);
+                }
+            }
+        }
+
+        @Override
         public void onUserStopping(@NonNull TargetUser targetUser) {
             synchronized (mUms.mUsersLock) {
                 final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier());
@@ -723,6 +745,7 @@
         mPackagesLock = packagesLock;
         mUsers = users != null ? users : new SparseArray<>();
         mHandler = new MainHandler();
+        mUserVisibilityMediator = new UserVisibilityMediator(mHandler);
         mUserDataPreparer = userDataPreparer;
         mUserTypes = UserTypeFactory.getUserTypes();
         invalidateOwnerNameIfNecessary(context.getResources(), true /* forceUpdate */);
@@ -747,7 +770,6 @@
         mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING);
         mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
         emulateSystemUserModeIfNeeded();
-        mUserVisibilityMediator = new UserVisibilityMediator(this);
     }
 
     void systemReady() {
@@ -891,6 +913,49 @@
         return null;
     }
 
+    @Override
+    public @UserIdInt int getMainUserId() {
+        checkQueryOrCreateUsersPermission("get main user id");
+        return getMainUserIdUnchecked();
+    }
+
+    private @UserIdInt int getMainUserIdUnchecked() {
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
+                final UserInfo user = mUsers.valueAt(i).info;
+                if (user.isMain() && !mRemovingUserIds.get(user.id)) {
+                    return user.id;
+                }
+            }
+        }
+        return UserHandle.USER_NULL;
+    }
+
+    @Override
+    public int getPreviousFullUserToEnterForeground() {
+        checkQueryOrCreateUsersPermission("get previous user");
+        int previousUser = UserHandle.USER_NULL;
+        long latestEnteredTime = 0;
+        final int currentUser = getCurrentUserId();
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
+                final UserData userData = mUsers.valueAt(i);
+                final int userId = userData.info.id;
+                if (userId != currentUser && userData.info.isFull() && !userData.info.partial
+                        && !mRemovingUserIds.get(userId)) {
+                    final long userEnteredTime = userData.mLastEnteredForegroundTimeMillis;
+                    if (userEnteredTime > latestEnteredTime) {
+                        latestEnteredTime = userEnteredTime;
+                        previousUser = userId;
+                    }
+                }
+            }
+        }
+        return previousUser;
+    }
+
     public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
         return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */
                 true);
@@ -1636,8 +1701,7 @@
         return isProfileUnchecked(userId);
     }
 
-    // TODO(b/244644281): make it private once UserVisibilityMediator don't use it anymore
-    boolean isProfileUnchecked(@UserIdInt int userId) {
+    private boolean isProfileUnchecked(@UserIdInt int userId) {
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
             return userInfo != null && userInfo.isProfile();
@@ -1774,26 +1838,14 @@
     }
 
     @Override
-    public List<UserHandle> getVisibleUsers() {
+    public int[] getVisibleUsers() {
         if (!hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) {
             throw new SecurityException("Caller needs MANAGE_USERS or INTERACT_ACROSS_USERS "
                     + "permission to get list of visible users");
         }
         final long ident = Binder.clearCallingIdentity();
         try {
-            // TODO(b/2399825580): refactor into UserDisplayAssigner
-            synchronized (mUsersLock) {
-                int usersSize = mUsers.size();
-                ArrayList<UserHandle> visibleUsers = new ArrayList<>(usersSize);
-                for (int i = 0; i < usersSize; i++) {
-                    UserInfo ui = mUsers.valueAt(i).info;
-                    if (!ui.partial && !ui.preCreated && !mRemovingUserIds.get(ui.id)
-                            && mUserVisibilityMediator.isUserVisible(ui.id)) {
-                        visibleUsers.add(UserHandle.of(ui.id));
-                    }
-                }
-                return visibleUsers;
-            }
+            return mUserVisibilityMediator.getVisibleUsers().toArray();
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -2805,7 +2857,8 @@
         synchronized (mUsersLock) {
             count = getAliveUsersExcludingGuestsCountLU();
         }
-        return count >= UserManager.getMaxSupportedUsers();
+        return count >= UserManager.getMaxSupportedUsers()
+                && !isCreationOverrideEnabled();
     }
 
     /**
@@ -2815,15 +2868,16 @@
      * <p>For checking whether more profiles can be added to a particular parent use
      * {@link #canAddMoreProfilesToUser}.
      */
-    private boolean canAddMoreUsersOfType(UserTypeDetails userTypeDetails) {
-        if (!userTypeDetails.isEnabled()) {
+    private boolean canAddMoreUsersOfType(@NonNull UserTypeDetails userTypeDetails) {
+        if (!isUserTypeEnabled(userTypeDetails)) {
             return false;
         }
         final int max = userTypeDetails.getMaxAllowed();
         if (max == UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) {
             return true; // Indicates that there is no max.
         }
-        return getNumberOfUsersOfType(userTypeDetails.getName()) < max;
+        return getNumberOfUsersOfType(userTypeDetails.getName()) < max
+                || isCreationOverrideEnabled();
     }
 
     /**
@@ -2834,7 +2888,7 @@
     public int getRemainingCreatableUserCount(String userType) {
         checkQueryOrCreateUsersPermission("get the remaining number of users that can be added.");
         final UserTypeDetails type = mUserTypes.get(userType);
-        if (type == null || !type.isEnabled()) {
+        if (type == null || !isUserTypeEnabled(type)) {
             return 0;
         }
         synchronized (mUsersLock) {
@@ -2908,7 +2962,21 @@
     public boolean isUserTypeEnabled(String userType) {
         checkCreateUsersPermission("check if user type is enabled.");
         final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
-        return userTypeDetails != null && userTypeDetails.isEnabled();
+        return userTypeDetails != null && isUserTypeEnabled(userTypeDetails);
+    }
+
+    /** Returns whether the creation of users of the given user type is enabled on this device. */
+    private boolean isUserTypeEnabled(@NonNull UserTypeDetails userTypeDetails) {
+        return userTypeDetails.isEnabled() || isCreationOverrideEnabled();
+    }
+
+    /**
+     * Returns whether to almost-always allow creating users even beyond their limit or if disabled.
+     * For Debug builds only.
+     */
+    private boolean isCreationOverrideEnabled() {
+        return Build.isDebuggable()
+                && SystemProperties.getBoolean(DEV_CREATE_OVERRIDE_PROPERTY, false);
     }
 
     @Override
@@ -2921,7 +2989,8 @@
     @Override
     public boolean canAddMoreProfilesToUser(String userType, @UserIdInt int userId,
             boolean allowedToRemoveOne) {
-        return 0 < getRemainingCreatableProfileCount(userType, userId, allowedToRemoveOne);
+        return 0 < getRemainingCreatableProfileCount(userType, userId, allowedToRemoveOne)
+                || isCreationOverrideEnabled();
     }
 
     @Override
@@ -2939,7 +3008,7 @@
         checkQueryOrCreateUsersPermission(
                 "get the remaining number of profiles that can be added to the given user.");
         final UserTypeDetails type = mUserTypes.get(userType);
-        if (type == null || !type.isEnabled()) {
+        if (type == null || !isUserTypeEnabled(type)) {
             return 0;
         }
         // Managed profiles have their own specific rules.
@@ -3267,11 +3336,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;
     }
 
@@ -3283,30 +3380,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) {
@@ -3316,12 +3394,13 @@
                     Slogf.wtf(LOG_TAG, "emulateSystemUserModeIfNeeded(): no system user data");
                     return;
                 }
+                final int oldMainUserId = getMainUserIdUnchecked();
                 final int oldFlags = systemUserData.info.flags;
                 final int newFlags;
                 final String newUserType;
                 if (newHeadlessSystemUserMode) {
                     newUserType = UserManager.USER_TYPE_SYSTEM_HEADLESS;
-                    newFlags = oldFlags & ~UserInfo.FLAG_FULL;
+                    newFlags = oldFlags & ~UserInfo.FLAG_FULL & ~UserInfo.FLAG_MAIN;
                 } else {
                     newUserType = UserManager.USER_TYPE_FULL_SYSTEM;
                     newFlags = oldFlags | UserInfo.FLAG_FULL;
@@ -3336,13 +3415,42 @@
                         + "%s, flags changed from %s to %s",
                         systemUserData.info.userType, newUserType,
                         UserInfo.flagsToString(oldFlags), UserInfo.flagsToString(newFlags));
+
                 systemUserData.info.userType = newUserType;
                 systemUserData.info.flags = newFlags;
                 writeUserLP(systemUserData);
+
+                // Switch the MainUser to a reasonable choice if needed.
+                // (But if there was no MainUser, we deliberately continue to have no MainUser.)
+                final UserData oldMain = getUserDataNoChecks(oldMainUserId);
+                if (newHeadlessSystemUserMode) {
+                    if (oldMain != null && (oldMain.info.flags & UserInfo.FLAG_SYSTEM) != 0) {
+                        // System was MainUser. So we need a new choice for Main. Pick the oldest.
+                        // If no oldest, don't set any. Let the BootUserInitializer do that later.
+                        final UserInfo newMainUser = getEarliestCreatedFullUser();
+                        if (newMainUser != null) {
+                            Slogf.i(LOG_TAG, "Designating user " + newMainUser.id + " to be Main");
+                            newMainUser.flags |= UserInfo.FLAG_MAIN;
+                            writeUserLP(getUserDataNoChecks(newMainUser.id));
+                        }
+                    }
+                } else {
+                    // TODO(b/256624031): For now, we demand the Main user (if there is one) is
+                    //  always the system in non-HSUM. In the future, when we relax this, change how
+                    //  we handle MAIN.
+                    if (oldMain != null && (oldMain.info.flags & UserInfo.FLAG_SYSTEM) == 0) {
+                        // Someone else was the MainUser; transfer it to System.
+                        Slogf.i(LOG_TAG, "Transferring Main to user 0 from " + oldMain.info.id);
+                        oldMain.info.flags &= ~UserInfo.FLAG_MAIN;
+                        systemUserData.info.flags |= UserInfo.FLAG_MAIN;
+                        writeUserLP(oldMain);
+                        writeUserLP(systemUserData);
+                    }
+                }
             }
         }
 
-        // 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;
     }
 
@@ -3532,7 +3640,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);
@@ -3607,6 +3719,24 @@
             userVersion = 10;
         }
 
+        if (userVersion < 11) {
+            // Add FLAG_MAIN
+            if (isHeadlessSystemUserMode()) {
+                final UserInfo earliestCreatedUser = getEarliestCreatedFullUser();
+                if (earliestCreatedUser != null) {
+                    earliestCreatedUser.flags |= UserInfo.FLAG_MAIN;
+                    userIdsToWrite.add(earliestCreatedUser.id);
+                }
+            } else {
+                synchronized (mUsersLock) {
+                    final UserData userData = mUsers.get(UserHandle.USER_SYSTEM);
+                    userData.info.flags |= UserInfo.FLAG_MAIN;
+                    userIdsToWrite.add(userData.info.id);
+                }
+            }
+            userVersion = 11;
+        }
+
         // Reminder: If you add another upgrade, make sure to increment USER_VERSION too.
 
         // Done with userVersion changes, moving on to deal with userTypeVersion upgrades
@@ -3736,12 +3866,28 @@
         userInfo.profileBadge = getFreeProfileBadgeLU(userInfo.profileGroupId, userInfo.userType);
     }
 
+    /** Returns the oldest Full Admin user, or null is if there none. */
+    private @Nullable UserInfo getEarliestCreatedFullUser() {
+        final List<UserInfo> users = getUsersInternal(true, true, true);
+        UserInfo earliestUser = null;
+        long earliestCreationTime = Long.MAX_VALUE;
+        for (int i = 0; i < users.size(); i++) {
+            final UserInfo info = users.get(i);
+            if (info.isFull() && info.isAdmin() && info.creationTime >= 0
+                    && info.creationTime < earliestCreationTime) {
+                earliestCreationTime = info.creationTime;
+                earliestUser = info;
+            }
+        }
+        return earliestUser;
+    }
+
     @GuardedBy({"mPackagesLock"})
     private void fallbackToSingleUserLP() {
         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();
@@ -3863,6 +4009,8 @@
             serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
                     userInfo.lastLoggedInFingerprint);
         }
+        serializer.attributeLong(
+                null, ATTR_LAST_ENTERED_FOREGROUND_TIME, userData.mLastEnteredForegroundTimeMillis);
         if (userInfo.iconPath != null) {
             serializer.attribute(null,  ATTR_ICON_PATH, userInfo.iconPath);
         }
@@ -4036,6 +4184,7 @@
         long lastLoggedInTime = 0L;
         long lastRequestQuietModeEnabledTimestamp = 0L;
         String lastLoggedInFingerprint = null;
+        long lastEnteredForegroundTime = 0L;
         int profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
         int profileBadge = 0;
         int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
@@ -4081,6 +4230,8 @@
             lastLoggedInTime = parser.getAttributeLong(null, ATTR_LAST_LOGGED_IN_TIME, 0);
             lastLoggedInFingerprint = parser.getAttributeValue(null,
                     ATTR_LAST_LOGGED_IN_FINGERPRINT);
+            lastEnteredForegroundTime =
+                    parser.getAttributeLong(null, ATTR_LAST_ENTERED_FOREGROUND_TIME, 0L);
             profileGroupId = parser.getAttributeInt(null, ATTR_PROFILE_GROUP_ID,
                     UserInfo.NO_PROFILE_GROUP_ID);
             profileBadge = parser.getAttributeInt(null, ATTR_PROFILE_BADGE, 0);
@@ -4175,6 +4326,7 @@
         userData.seedAccountOptions = seedAccountOptions;
         userData.userProperties = userProperties;
         userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp);
+        userData.mLastEnteredForegroundTimeMillis = lastEnteredForegroundTime;
         if (ignorePrepareStorageErrors) {
             userData.setIgnorePrepareStorageErrors();
         }
@@ -4385,7 +4537,7 @@
                     + ") indicated SYSTEM user, which cannot be created.");
             return null;
         }
-        if (!userTypeDetails.isEnabled()) {
+        if (!isUserTypeEnabled(userTypeDetails)) {
             throwCheckedUserOperationException(
                     "Cannot add a user of disabled type " + userType + ".",
                     UserManager.USER_OPERATION_ERROR_MAX_USERS);
@@ -4457,27 +4609,12 @@
                                     + " for user " + parentId,
                             UserManager.USER_OPERATION_ERROR_MAX_USERS);
                 }
-                // In legacy mode, restricted profile's parent can only be the owner user
-                if (isRestricted && !UserManager.isSplitSystemUser()
-                        && (parentId != UserHandle.USER_SYSTEM)) {
+                if (isRestricted && (parentId != UserHandle.USER_SYSTEM)
+                        && !isCreationOverrideEnabled()) {
                     throwCheckedUserOperationException(
-                            "Cannot add restricted profile - parent user must be owner",
+                            "Cannot add restricted profile - parent user must be system",
                             UserManager.USER_OPERATION_ERROR_UNKNOWN);
                 }
-                if (isRestricted && UserManager.isSplitSystemUser()) {
-                    if (parent == null) {
-                        throwCheckedUserOperationException(
-                                "Cannot add restricted profile - parent user must be specified",
-                                UserManager.USER_OPERATION_ERROR_UNKNOWN);
-                    }
-                    if (!parent.info.canHaveProfile()) {
-                        throwCheckedUserOperationException(
-                                "Cannot add restricted profile - profiles cannot be created for "
-                                        + "the specified parent user id "
-                                        + parentId,
-                                UserManager.USER_OPERATION_ERROR_UNKNOWN);
-                    }
-                }
 
                 userId = getNextAvailableId();
                 Slog.i(LOG_TAG, "Creating user " + userId + " of type " + userType);
@@ -5233,7 +5370,8 @@
                 Slog.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
             }
 
-            if (userData.info.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
+            if (userData.info.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
+                    && userData.info.isProfile()) {
                 sendProfileRemovedBroadcast(userData.info.profileGroupId, userData.info.id,
                         userData.info.userType);
             }
@@ -5848,6 +5986,7 @@
                 Slog.d(LOG_TAG, "updateUserIds(): userIds= " + Arrays.toString(mUserIds)
                         + " includingPreCreated=" + Arrays.toString(mUserIdsIncludingPreCreated));
             }
+            UserPackage.setValidUserIds(mUserIds);
         }
     }
 
@@ -6107,6 +6246,11 @@
                         || someUserHasSeedAccountNoChecks(accountName, accountType));
     }
 
+    private void setLastEnteredForegroundTimeToNow(@NonNull UserData userData) {
+        userData.mLastEnteredForegroundTimeMillis = System.currentTimeMillis();
+        scheduleWriteUser(userData);
+    }
+
     @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
@@ -6130,7 +6274,7 @@
                     dumpUser(pw, UserHandle.parseUserArg(args[1]), sb, now, nowRealtime);
                     return;
                 case "--visibility-mediator":
-                    mUserVisibilityMediator.dump(pw);
+                    mUserVisibilityMediator.dump(pw, args);
                     return;
             }
         }
@@ -6196,7 +6340,7 @@
         } // synchronized (mPackagesLock)
 
         pw.println();
-        mUserVisibilityMediator.dump(pw);
+        mUserVisibilityMediator.dump(pw, args);
         pw.println();
 
         // Dump some capabilities
@@ -6208,9 +6352,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)");
@@ -6325,6 +6472,9 @@
         pw.print("    Unlock time: ");
         dumpTimeAgo(pw, tempStringBuilder, nowRealtime, userData.unlockRealtime);
 
+        pw.print("    Last entered foreground: ");
+        dumpTimeAgo(pw, tempStringBuilder, now, userData.mLastEnteredForegroundTimeMillis);
+
         pw.print("    Has profile owner: ");
         pw.println(mIsUserManaged.get(userId));
         pw.println("    Restrictions:");
@@ -6769,13 +6919,15 @@
         }
 
         @Override
-        public void assignUserToDisplay(@UserIdInt int userId, int displayId) {
-            mUserVisibilityMediator.assignUserToDisplay(userId, displayId);
+        public int assignUserToDisplayOnStart(@UserIdInt int userId, @UserIdInt int profileGroupId,
+                boolean foreground, int displayId) {
+            return mUserVisibilityMediator.assignUserToDisplayOnStart(userId, profileGroupId,
+                    foreground, displayId);
         }
 
         @Override
-        public void unassignUserFromDisplay(@UserIdInt int userId) {
-            mUserVisibilityMediator.unassignUserFromDisplay(userId);
+        public void unassignUserFromDisplayOnStop(@UserIdInt int userId) {
+            mUserVisibilityMediator.unassignUserFromDisplayOnStop(userId);
         }
 
         @Override
@@ -6797,8 +6949,49 @@
         public @UserIdInt int getUserAssignedToDisplay(int displayId) {
             return mUserVisibilityMediator.getUserAssignedToDisplay(displayId);
         }
+
+        @Override
+        public void addUserVisibilityListener(UserVisibilityListener listener) {
+            mUserVisibilityMediator.addListener(listener);
+        }
+
+        @Override
+        public void removeUserVisibilityListener(UserVisibilityListener listener) {
+            mUserVisibilityMediator.removeListener(listener);
+        }
+
+        @Override
+        public void onSystemUserVisibilityChanged(boolean visible) {
+            mUserVisibilityMediator.onSystemUserVisibilityChanged(visible);
+        }
+
+        @Override
+        public int[] getUserTypesForStatsd(@UserIdInt int[] userIds) {
+            if (userIds == null) {
+                return null;
+            }
+            final int[] userTypes = new int[userIds.length];
+            for (int i = 0; i < userTypes.length; i++) {
+                final UserInfo userInfo = getUserInfo(userIds[i]);
+                if (userInfo == null) {
+                    // Not possible because the input user ids should all be valid
+                    userTypes[i] = UserManager.getUserTypeForStatsd("");
+                } else {
+                    userTypes[i] = UserManager.getUserTypeForStatsd(userInfo.userType);
+                }
+            }
+            return userTypes;
+        }
+
+        @Override
+        public @UserIdInt int getMainUserId() {
+            return getMainUserIdUnchecked();
+        }
+
     } // class LocalService
 
+
+
     /**
      * Check if user has restrictions
      * @param restriction restrictions to check
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index d1f3341e..27d74d5 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -147,7 +147,9 @@
             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,
+            UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO
     });
 
     public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
@@ -195,7 +197,9 @@
             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,
+            UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO
     );
 
     /**
@@ -234,7 +238,9 @@
                     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,
+                    UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO
     );
 
     /**
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index b98d20e..8fb5773 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -126,9 +126,13 @@
                 .setCrossProfileIntentFilterAccessControl(
                         CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM)
                 .setIsCredentialSharableWithParent(true)
+                .setDefaultCrossProfileIntentFilters(getDefaultCloneCrossProfileIntentFilter())
                 .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)
+                        .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
+                        .setUseParentsContacts(true));
     }
 
     /**
@@ -163,7 +167,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));
     }
 
     /**
@@ -257,7 +262,8 @@
     private static UserTypeDetails.Builder getDefaultTypeFullSystem() {
         return new UserTypeDetails.Builder()
                 .setName(USER_TYPE_FULL_SYSTEM)
-                .setBaseType(FLAG_SYSTEM | FLAG_FULL);
+                .setBaseType(FLAG_SYSTEM | FLAG_FULL)
+                .setDefaultUserInfoPropertyFlags(UserInfo.FLAG_MAIN);
     }
 
     /**
@@ -307,6 +313,10 @@
         return DefaultCrossProfileIntentFiltersUtils.getDefaultManagedProfileFilters();
     }
 
+    private static List<DefaultCrossProfileIntentFilter> getDefaultCloneCrossProfileIntentFilter() {
+        return DefaultCrossProfileIntentFiltersUtils.getDefaultCloneProfileFilters();
+    }
+
     /**
      * Reads the given xml parser to obtain device user-type customization, and updates the given
      * map of {@link UserTypeDetails.Builder}s accordingly.
@@ -388,6 +398,7 @@
                 }
 
                 setIntAttribute(parser, "enabled", builder::setEnabled);
+                setIntAttribute(parser, "max-allowed", builder::setMaxAllowed);
 
                 // Process child elements.
                 final int depth = parser.getDepth();
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index f725c48..9b9ca10 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -15,242 +15,413 @@
  */
 package com.android.server.pm;
 
+import static android.content.pm.UserInfo.NO_PROFILE_GROUP_ID;
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
+
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Dumpable;
+import android.util.EventLog;
 import android.util.IndentingPrintWriter;
+import android.util.IntArray;
 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.am.EventLogTags;
+import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
+import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
 import com.android.server.utils.Slogf;
 
 import java.io.PrintWriter;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Class responsible for deciding whether a user is visible (or visible for a given display).
  *
+ * <p>Currently, it has 2 "modes" (set on constructor), which defines the class behavior (i.e, the
+ * logic that dictates the result of methods such as {@link #isUserVisible(int)} and
+ * {@link #isUserVisible(int, int)}):
+ *
+ * <ul>
+ *   <li>default: this is the most common mode (used by phones, tablets, foldables, automotives with
+ *   just cluster and driver displayes, etc...), where the logic is based solely on the current
+ *   foreground user (and its started profiles)
+ *   <li>{@code MUMD}: mode for "(concurrent) Multiple Users on Multiple Displays", which is used on
+ *   automotives with passenger display. In this mode, users started in background on the secondary
+ *   display are stored in map.
+ * </ul>
+ *
  * <p>This class is thread safe.
  */
-// TODO(b/244644281): improve javadoc (for example, explain all cases / modes)
-public final class UserVisibilityMediator {
+public final class UserVisibilityMediator implements Dumpable {
 
     private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+    private static final boolean VERBOSE = false; // DO NOT SUBMIT WITH TRUE
 
     private static final String TAG = UserVisibilityMediator.class.getSimpleName();
 
-    private final Object mLock = new Object();
+    public static final int SECONDARY_DISPLAY_MAPPING_NEEDED = 1;
+    public static final int SECONDARY_DISPLAY_MAPPING_NOT_NEEDED = 2;
+    public static final int SECONDARY_DISPLAY_MAPPING_FAILED = -1;
 
-    // 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;
+    /**
+     * Whether a user / display assignment requires adding an entry to the
+     * {@code mUsersOnSecondaryDisplays} map.
+     */
+    @IntDef(flag = false, prefix = {"SECONDARY_DISPLAY_MAPPING_"}, value = {
+            SECONDARY_DISPLAY_MAPPING_NEEDED,
+            SECONDARY_DISPLAY_MAPPING_NOT_NEEDED,
+            SECONDARY_DISPLAY_MAPPING_FAILED
+    })
+    public @interface SecondaryDisplayMappingStatus {}
+
+    // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
+    @VisibleForTesting
+    static final int INITIAL_CURRENT_USER_ID = USER_SYSTEM;
+
+    private final Object mLock = new Object();
 
     private final boolean mUsersOnSecondaryDisplaysEnabled;
 
+    @UserIdInt
+    @GuardedBy("mLock")
+    private int mCurrentUserId = INITIAL_CURRENT_USER_ID;
+
+    /**
+     * Map of background users started on secondary displays.
+     *
+     * <p>Only set when {@code mUsersOnSecondaryDisplaysEnabled} is {@code true}.
+     */
     @Nullable
     @GuardedBy("mLock")
     private final SparseIntArray mUsersOnSecondaryDisplays;
 
-    UserVisibilityMediator(UserManagerService service) {
-        this(service, UserManager.isUsersOnSecondaryDisplaysEnabled(),
-                /* usersOnSecondaryDisplays= */ null);
+    /**
+     * Mapping from each started user to its profile group.
+     */
+    @GuardedBy("mLock")
+    private final SparseIntArray mStartedProfileGroupIds = new SparseIntArray();
+
+    /**
+     * Handler user to call listeners
+     */
+    private final Handler mHandler;
+
+    // @GuardedBy("mLock") - hold lock for writes, no lock necessary for simple reads
+    final CopyOnWriteArrayList<UserVisibilityListener> mListeners =
+            new CopyOnWriteArrayList<>();
+
+    UserVisibilityMediator(Handler handler) {
+        this(UserManager.isUsersOnSecondaryDisplaysEnabled(), handler);
     }
 
     @VisibleForTesting
-    UserVisibilityMediator(UserManagerService service, boolean usersOnSecondaryDisplaysEnabled,
-            @Nullable SparseIntArray usersOnSecondaryDisplays) {
-        mService = service;
+    UserVisibilityMediator(boolean usersOnSecondaryDisplaysEnabled, Handler handler) {
         mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled;
-        if (mUsersOnSecondaryDisplaysEnabled) {
-            mUsersOnSecondaryDisplays = usersOnSecondaryDisplays == null
-                    ? new SparseIntArray() // default behavior
-                    : usersOnSecondaryDisplays; // passed by unit test
-        } else {
-            mUsersOnSecondaryDisplays = null;
-        }
+        mUsersOnSecondaryDisplays = mUsersOnSecondaryDisplaysEnabled ? new SparseIntArray() : null;
+        mHandler = handler;
+        // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
+        mStartedProfileGroupIds.put(INITIAL_CURRENT_USER_ID, INITIAL_CURRENT_USER_ID);
     }
 
     /**
-     * See {@link UserManagerInternal#assignUserToDisplay(int, int)}.
+     * See {@link UserManagerInternal#assignUserToDisplayOnStart(int, int, boolean, int)}.
      */
-    public void assignUserToDisplay(int userId, int displayId) {
+    public @UserAssignmentResult int assignUserToDisplayOnStart(@UserIdInt int userId,
+            @UserIdInt int unResolvedProfileGroupId, boolean foreground, int displayId) {
+        Preconditions.checkArgument(!isSpecialUserId(userId), "user id cannot be generic: %d",
+                userId);
+        // This method needs to perform 4 actions:
+        //
+        // 1. Check if the user can be started given the provided arguments
+        // 2. If it can, decide whether it's visible or not (which is the return value)
+        // 3. Update the current user / profiles state
+        // 4. Update the users on secondary display state (if applicable)
+        //
+        // Notice that steps 3 and 4 should be done atomically (i.e., while holding mLock), so the
+        // previous steps are delegated to other methods (canAssignUserToDisplayLocked() and
+        // getUserVisibilityOnStartLocked() respectively).
+
+
+        int profileGroupId = unResolvedProfileGroupId == NO_PROFILE_GROUP_ID
+                ? userId
+                : unResolvedProfileGroupId;
         if (DBG) {
-            Slogf.d(TAG, "assignUserToDisplay(%d, %d)", userId, displayId);
+            Slogf.d(TAG, "assignUserToDisplayOnStart(%d, %d, %b, %d): actualProfileGroupId=%d",
+                    userId, unResolvedProfileGroupId, foreground, displayId, profileGroupId);
         }
 
-        // 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);
-        }
+        int result;
+        IntArray visibleUsersBefore, visibleUsersAfter;
         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);
-                }
-            }
-
+            result = getUserVisibilityOnStartLocked(userId, profileGroupId, foreground, displayId);
             if (DBG) {
-                Slogf.d(TAG, "Adding full user %d -> display %d", userId, displayId);
+                Slogf.d(TAG, "result of getUserVisibilityOnStartLocked(%s)",
+                        userAssignmentResultToString(result));
             }
-            mUsersOnSecondaryDisplays.put(userId, displayId);
+            if (result == USER_ASSIGNMENT_RESULT_FAILURE) {
+                return result;
+            }
+
+            int mappingResult = canAssignUserToDisplayLocked(userId, profileGroupId, displayId);
+            if (mappingResult == SECONDARY_DISPLAY_MAPPING_FAILED) {
+                return USER_ASSIGNMENT_RESULT_FAILURE;
+            }
+
+            visibleUsersBefore = getVisibleUsers();
+
+            // Set current user / profiles state
+            if (foreground) {
+                mCurrentUserId = userId;
+            }
+            if (DBG) {
+                Slogf.d(TAG, "adding user / profile mapping (%d -> %d)", userId, profileGroupId);
+            }
+            mStartedProfileGroupIds.put(userId, profileGroupId);
+
+            //  Set user / display state
+            switch (mappingResult) {
+                case SECONDARY_DISPLAY_MAPPING_NEEDED:
+                    if (DBG) {
+                        Slogf.d(TAG, "adding user / display mapping (%d -> %d)", userId, displayId);
+                    }
+                    mUsersOnSecondaryDisplays.put(userId, displayId);
+                    break;
+                case SECONDARY_DISPLAY_MAPPING_NOT_NEEDED:
+                    if (DBG) {
+                        // Don't need to do set state because methods (such as isUserVisible())
+                        // already know that the current user (and their profiles) is assigned to
+                        // the default display.
+                        Slogf.d(TAG, "don't need to update mUsersOnSecondaryDisplays");
+                    }
+                    break;
+                default:
+                    Slogf.wtf(TAG,  "invalid resut from canAssignUserToDisplayLocked: %d",
+                            mappingResult);
+            }
+
+            visibleUsersAfter = getVisibleUsers();
         }
+
+        dispatchVisibilityChanged(visibleUsersBefore, visibleUsersAfter);
+
+        if (DBG) {
+            Slogf.d(TAG, "returning %s", userAssignmentResultToString(result));
+        }
+
+        return result;
+    }
+
+    @GuardedBy("mLock")
+    @UserAssignmentResult
+    private int getUserVisibilityOnStartLocked(@UserIdInt int userId,
+            @UserIdInt int profileGroupId, boolean foreground, int displayId) {
+        if (displayId != DEFAULT_DISPLAY) {
+            if (foreground) {
+                Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: cannot start "
+                        + "foreground user on secondary display", userId, profileGroupId,
+                        foreground, displayId);
+                return USER_ASSIGNMENT_RESULT_FAILURE;
+            }
+            if (!mUsersOnSecondaryDisplaysEnabled) {
+                Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: called on "
+                        + "device that doesn't support multiple users on multiple displays",
+                        userId, profileGroupId, foreground, displayId);
+                return USER_ASSIGNMENT_RESULT_FAILURE;
+            }
+        }
+
+        if (isProfile(userId, profileGroupId)) {
+            if (displayId != DEFAULT_DISPLAY) {
+                Slogf.w(TAG, "canStartUserLocked(%d, %d, %b, %d) failed: cannot start profile user "
+                        + "on secondary display", userId, profileGroupId, foreground,
+                        displayId);
+                return USER_ASSIGNMENT_RESULT_FAILURE;
+            }
+            if (foreground) {
+                Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in "
+                        + "foreground", userId, profileGroupId, foreground, displayId);
+                return USER_ASSIGNMENT_RESULT_FAILURE;
+            } else {
+                boolean isParentVisibleOnDisplay = isUserVisible(profileGroupId, displayId);
+                if (DBG) {
+                    Slogf.d(TAG, "parent visible on display: %b", isParentVisibleOnDisplay);
+                }
+                return isParentVisibleOnDisplay
+                        ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE
+                        : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+            }
+        }
+
+        return foreground || displayId != DEFAULT_DISPLAY
+                ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE
+                : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+    }
+
+    @GuardedBy("mLock")
+    @SecondaryDisplayMappingStatus
+    private int canAssignUserToDisplayLocked(@UserIdInt int userId,
+            @UserIdInt int profileGroupId, int displayId) {
+        if (displayId == DEFAULT_DISPLAY
+                && (!mUsersOnSecondaryDisplaysEnabled || !isProfile(userId, profileGroupId))) {
+            // Don't need to do anything because methods (such as isUserVisible()) already
+            // know that the current user (and its profiles) is assigned to the default display.
+            // But on MUMD devices, profiles are only supported in the default display, so 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).
+            if (DBG) {
+                Slogf.d(TAG, "ignoring mapping for default display");
+            }
+            return SECONDARY_DISPLAY_MAPPING_NOT_NEEDED;
+        }
+
+        if (userId == UserHandle.USER_SYSTEM) {
+            Slogf.w(TAG, "Cannot assign system user to secondary display (%d)", displayId);
+            return SECONDARY_DISPLAY_MAPPING_FAILED;
+        }
+        if (displayId == Display.INVALID_DISPLAY) {
+            Slogf.w(TAG, "Cannot assign to INVALID_DISPLAY (%d)", displayId);
+            return SECONDARY_DISPLAY_MAPPING_FAILED;
+        }
+        if (userId == mCurrentUserId) {
+            Slogf.w(TAG, "Cannot assign current user (%d) to other displays", userId);
+            return SECONDARY_DISPLAY_MAPPING_FAILED;
+        }
+
+        if (isProfile(userId, profileGroupId)) {
+            // Profile can only start in the same display as parent. And for simplicity,
+            // that display must be the DEFAULT_DISPLAY.
+            if (displayId != Display.DEFAULT_DISPLAY) {
+                Slogf.w(TAG, "Profile user can only be started in the default display");
+                return SECONDARY_DISPLAY_MAPPING_FAILED;
+
+            }
+            if (DBG) {
+                Slogf.d(TAG, "Don't need to map profile user %d to default display", userId);
+            }
+            return SECONDARY_DISPLAY_MAPPING_NOT_NEEDED;
+        }
+
+        // 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) {
+                Slogf.w(TAG, "Cannot assign user %d to display %d because such display is already "
+                        + "assigned to user %d", userId, displayId, assignedUserId);
+                return SECONDARY_DISPLAY_MAPPING_FAILED;
+            }
+            if (userId == assignedUserId) {
+                Slogf.w(TAG, "Cannot assign user %d to display %d because such user is as already "
+                        + "assigned to display %d", userId, displayId, assignedUserId);
+                return SECONDARY_DISPLAY_MAPPING_FAILED;
+            }
+        }
+        return SECONDARY_DISPLAY_MAPPING_NEEDED;
     }
 
     /**
-     * See {@link UserManagerInternal#unassignUserFromDisplay(int)}.
+     * See {@link UserManagerInternal#unassignUserFromDisplayOnStop(int)}.
      */
-    public void unassignUserFromDisplay(int userId) {
+    public void unassignUserFromDisplayOnStop(@UserIdInt int userId) {
         if (DBG) {
-            Slogf.d(TAG, "unassignUserFromDisplay(%d)", userId);
+            Slogf.d(TAG, "unassignUserFromDisplayOnStop(%d)", userId);
         }
+        IntArray visibleUsersBefore, visibleUsersAfter;
+        synchronized (mLock) {
+            visibleUsersBefore = getVisibleUsers();
+
+            unassignUserFromDisplayOnStopLocked(userId);
+
+            visibleUsersAfter = getVisibleUsers();
+        }
+        dispatchVisibilityChanged(visibleUsersBefore, visibleUsersAfter);
+    }
+
+    @GuardedBy("mLock")
+    private void unassignUserFromDisplayOnStopLocked(@UserIdInt int userId) {
+        if (DBG) {
+            Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId,
+                    mStartedProfileGroupIds);
+        }
+        mStartedProfileGroupIds.delete(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");
-            }
+            // Don't need to do update mUsersOnSecondaryDisplays because methods (such as
+            // isUserVisible()) already know that the current user (and their profiles) is
+            // assigned to the default display.
             return;
         }
-
-        synchronized (mLock) {
-            if (DBG) {
-                Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
-                        mUsersOnSecondaryDisplays);
-            }
-            mUsersOnSecondaryDisplays.delete(userId);
+        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) {
+    public boolean isUserVisible(@UserIdInt int userId) {
         // First check current foreground user and their profiles (on main display)
         if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+            if (VERBOSE) {
+                Slogf.v(TAG, "isUserVisible(%d): true to current user or profile", userId);
+            }
             return true;
         }
 
         // Device doesn't support multiple users on multiple displays, so only users checked above
         // can be visible
         if (!mUsersOnSecondaryDisplaysEnabled) {
+            if (DBG) {
+                Slogf.d(TAG, "isUserVisible(%d): false for non-current user on MUMD", userId);
+            }
             return false;
         }
 
+        boolean visible;
         synchronized (mLock) {
-            return mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0;
+            visible = mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0;
         }
+        if (DBG) {
+            Slogf.d(TAG, "isUserVisible(%d): %b from mapping", userId, visible);
+        }
+        return visible;
     }
 
     /**
      * See {@link UserManagerInternal#isUserVisible(int, int)}.
      */
-    public boolean isUserVisible(int userId, int displayId) {
+    public boolean isUserVisible(@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 (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;
-            }
+        if (!mUsersOnSecondaryDisplaysEnabled || displayId == Display.DEFAULT_DISPLAY) {
+            // TODO(b/245939659): will need to move the displayId == Display.DEFAULT_DISPLAY outside
+            // once it supports background users on DEFAULT_DISPLAY (for example, passengers in a
+            // no-driver configuration)
+            return isCurrentUserOrRunningProfileOfCurrentUser(userId);
         }
 
         synchronized (mLock) {
@@ -261,7 +432,7 @@
     /**
      * See {@link UserManagerInternal#getDisplayAssignedToUser(int)}.
      */
-    public int getDisplayAssignedToUser(int userId) {
+    public int getDisplayAssignedToUser(@UserIdInt int userId) {
         if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
             return Display.DEFAULT_DISPLAY;
         }
@@ -278,7 +449,7 @@
     /**
      * See {@link UserManagerInternal#getUserAssignedToDisplay(int)}.
      */
-    public int getUserAssignedToDisplay(int displayId) {
+    public int getUserAssignedToDisplay(@UserIdInt int displayId) {
         if (displayId == Display.DEFAULT_DISPLAY || !mUsersOnSecondaryDisplaysEnabled) {
             return getCurrentUserId();
         }
@@ -289,7 +460,7 @@
                     continue;
                 }
                 int userId = mUsersOnSecondaryDisplays.keyAt(i);
-                if (!isProfileUnchecked(userId)) {
+                if (!isStartedProfile(userId)) {
                     return userId;
                 } else if (DBG) {
                     Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
@@ -306,24 +477,167 @@
         return currentUserId;
     }
 
+    /**
+     * Gets the ids of the visible users.
+     */
+    public IntArray getVisibleUsers() {
+        // TODO(b/258054362): this method's performance is O(n2), as it interacts through all users
+        // here, then again on isUserVisible(). We could "fix" it to be O(n), but given that the
+        // number of users is too small, the gain is probably not worth the increase on complexity.
+        IntArray visibleUsers = new IntArray();
+        synchronized (mLock) {
+            for (int i = 0; i < mStartedProfileGroupIds.size(); i++) {
+                int userId = mStartedProfileGroupIds.keyAt(i);
+                if (isUserVisible(userId)) {
+                    visibleUsers.add(userId);
+                }
+            }
+        }
+        return visibleUsers;
+    }
+
+    /**
+     * Adds a {@link UserVisibilityListener listener}.
+     */
+    public void addListener(UserVisibilityListener listener) {
+        if (DBG) {
+            Slogf.d(TAG, "adding listener %s", listener);
+        }
+        synchronized (mLock) {
+            mListeners.add(listener);
+        }
+    }
+
+    /**
+     * Removes a {@link UserVisibilityListener listener}.
+     */
+    public void removeListener(UserVisibilityListener listener) {
+        if (DBG) {
+            Slogf.d(TAG, "removing listener %s", listener);
+        }
+        synchronized (mLock) {
+            mListeners.remove(listener);
+        }
+    }
+
+    // TODO(b/242195409): remove this method if not needed anymore
+    /**
+     * Nofify all listeners that the system user visibility changed.
+     */
+    void onSystemUserVisibilityChanged(boolean visible) {
+        dispatchVisibilityChanged(mListeners, USER_SYSTEM, visible);
+    }
+
+    /**
+     * Nofify all listeners about the visibility changes from before / after a change of state.
+     */
+    private void dispatchVisibilityChanged(IntArray visibleUsersBefore,
+            IntArray visibleUsersAfter) {
+        if (visibleUsersBefore == null) {
+            // Optimization - it's only null when listeners is empty
+            if (DBG) {
+                Slogf.d(TAG,  "dispatchVisibilityChanged(): ignoring, no listeners");
+            }
+            return;
+        }
+        CopyOnWriteArrayList<UserVisibilityListener> listeners = mListeners;
+        if (DBG) {
+            Slogf.d(TAG,
+                    "dispatchVisibilityChanged(): visibleUsersBefore=%s, visibleUsersAfter=%s, "
+                    + "%d listeners (%s)", visibleUsersBefore, visibleUsersAfter, listeners.size(),
+                    listeners);
+        }
+        for (int i = 0; i < visibleUsersBefore.size(); i++) {
+            int userId = visibleUsersBefore.get(i);
+            if (visibleUsersAfter.indexOf(userId) == -1) {
+                dispatchVisibilityChanged(listeners, userId, /* visible= */ false);
+            }
+        }
+        for (int i = 0; i < visibleUsersAfter.size(); i++) {
+            int userId = visibleUsersAfter.get(i);
+            if (visibleUsersBefore.indexOf(userId) == -1) {
+                dispatchVisibilityChanged(listeners, userId, /* visible= */ true);
+            }
+        }
+    }
+
+    private void dispatchVisibilityChanged(CopyOnWriteArrayList<UserVisibilityListener> listeners,
+            @UserIdInt int userId, boolean visible) {
+        EventLog.writeEvent(EventLogTags.UM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
+        if (DBG) {
+            Slogf.d(TAG, "dispatchVisibilityChanged(%d -> %b): sending to %d listeners",
+                    userId, visible, listeners.size());
+        }
+        for (int i = 0; i < mListeners.size(); i++) {
+            UserVisibilityListener listener =  mListeners.get(i);
+            if (VERBOSE) {
+                Slogf.v(TAG, "dispatchVisibilityChanged(%d -> %b): sending to %s",
+                        userId, visible, listener);
+            }
+            mHandler.post(() -> listener.onUserVisibilityChanged(userId, visible));
+        }
+    }
+
     private void dump(IndentingPrintWriter ipw) {
-        ipw.println("UserVisibilityManager");
+        ipw.println("UserVisibilityMediator");
         ipw.increaseIndent();
 
-        ipw.print("Supports users on secondary displays: ");
-        ipw.println(mUsersOnSecondaryDisplaysEnabled);
+        synchronized (mLock) {
+            ipw.print("Current user id: ");
+            ipw.println(mCurrentUserId);
 
-        if (mUsersOnSecondaryDisplaysEnabled) {
-            ipw.print("Users on secondary displays: ");
-            synchronized (mLock) {
-                ipw.println(mUsersOnSecondaryDisplays);
+            ipw.print("Visible users: ");
+            ipw.println(getVisibleUsers());
+
+            dumpSparseIntArray(ipw, mStartedProfileGroupIds, "started user / profile group",
+                    "u", "pg");
+
+            ipw.print("Supports background users on secondary displays: ");
+            ipw.println(mUsersOnSecondaryDisplaysEnabled);
+
+            if (mUsersOnSecondaryDisplays != null) {
+                dumpSparseIntArray(ipw, mUsersOnSecondaryDisplays,
+                        "background user / secondary display", "u", "d");
+            }
+            int numberListeners = mListeners.size();
+            ipw.print("Number of listeners: ");
+            ipw.println(numberListeners);
+            if (numberListeners > 0) {
+                ipw.increaseIndent();
+                for (int i = 0; i < numberListeners; i++) {
+                    ipw.print(i);
+                    ipw.print(": ");
+                    ipw.println(mListeners.get(i));
+                }
+                ipw.decreaseIndent();
             }
         }
 
         ipw.decreaseIndent();
     }
 
-    void dump(PrintWriter pw) {
+    private static void dumpSparseIntArray(IndentingPrintWriter ipw, SparseIntArray array,
+            String arrayDescription, String keyName, String valueName) {
+        ipw.print("Number of ");
+        ipw.print(arrayDescription);
+        ipw.print(" mappings: ");
+        ipw.println(array.size());
+        if (array.size() <= 0) {
+            return;
+        }
+        ipw.increaseIndent();
+        for (int i = 0; i < array.size(); i++) {
+            ipw.print(keyName); ipw.print(':');
+            ipw.print(array.keyAt(i));
+            ipw.print(" -> ");
+            ipw.print(valueName); ipw.print(':');
+            ipw.println(array.valueAt(i));
+        }
+        ipw.decreaseIndent();
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String[] args) {
         if (pw instanceof IndentingPrintWriter) {
             dump((IndentingPrintWriter) pw);
             return;
@@ -331,20 +645,56 @@
         dump(new IndentingPrintWriter(pw));
     }
 
-    // TODO(b/244644281): remove methods below once this class caches that state
+    private static boolean isSpecialUserId(@UserIdInt int userId) {
+        switch (userId) {
+            case UserHandle.USER_ALL:
+            case UserHandle.USER_CURRENT:
+            case UserHandle.USER_CURRENT_OR_SELF:
+            case UserHandle.USER_NULL:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static boolean isProfile(@UserIdInt int userId, @UserIdInt int profileGroupId) {
+        return profileGroupId != NO_PROFILE_GROUP_ID && profileGroupId != userId;
+    }
+
+    // NOTE: methods below are needed because some APIs use the current users (full and profiles)
+    // state to decide whether a user is visible or not. If we decide to always store that info into
+    // mUsersOnSecondaryDisplays, we should remove them.
+
     private @UserIdInt int getCurrentUserId() {
-        return mService.getCurrentUserId();
+        synchronized (mLock) {
+            return mCurrentUserId;
+        }
     }
 
     private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
-        return mService.isCurrentUserOrRunningProfileOfCurrentUser(userId);
+        synchronized (mLock) {
+            // Special case as NO_PROFILE_GROUP_ID == USER_NULL
+            if (userId == USER_NULL || mCurrentUserId == USER_NULL) {
+                return false;
+            }
+            if (mCurrentUserId == userId) {
+                return true;
+            }
+            return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID) == mCurrentUserId;
+        }
     }
 
-    private boolean isProfileUnchecked(@UserIdInt int userId) {
-        return mService.isProfileUnchecked(userId);
+    private boolean isStartedProfile(@UserIdInt int userId) {
+        int profileGroupId;
+        synchronized (mLock) {
+            profileGroupId = mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+        }
+        return isProfile(userId, profileGroupId);
     }
 
-    private @UserIdInt int getProfileParentId(@UserIdInt int userId) {
-        return mService.getProfileParentId(userId);
+    private @UserIdInt int getStartedProfileGroupId(@UserIdInt int userId) {
+        synchronized (mLock) {
+            return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/VerificationUtils.java b/services/core/java/com/android/server/pm/VerificationUtils.java
index e1026b4..30f2132 100644
--- a/services/core/java/com/android/server/pm/VerificationUtils.java
+++ b/services/core/java/com/android/server/pm/VerificationUtils.java
@@ -112,7 +112,7 @@
 
         VerificationUtils.broadcastPackageVerified(verificationId, originUri,
                 verificationCode, null,
-                verifyingSession.mDataLoaderType, verifyingSession.getUser(),
+                verifyingSession.getDataLoaderType(), verifyingSession.getUser(),
                 pms.mContext);
 
         if (state.isInstallAllowed()) {
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 415ddd3..a54f526 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -19,6 +19,7 @@
 import static android.content.Intent.EXTRA_LONG_VERSION_CODE;
 import static android.content.Intent.EXTRA_PACKAGE_NAME;
 import static android.content.Intent.EXTRA_VERSION_CODE;
+import static android.content.pm.PackageInstaller.SessionParams.MODE_INHERIT_EXISTING;
 import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
@@ -114,30 +115,32 @@
 
     final OriginInfo mOriginInfo;
     final IPackageInstallObserver2 mObserver;
-    final int mInstallFlags;
+    private final int mInstallFlags;
     @NonNull
-    final InstallSource mInstallSource;
-    final String mPackageAbiOverride;
-    final VerificationInfo mVerificationInfo;
-    final SigningDetails mSigningDetails;
+    private final InstallSource mInstallSource;
+    private final String mPackageAbiOverride;
+    private final VerificationInfo mVerificationInfo;
+    private final SigningDetails mSigningDetails;
     @Nullable
     MultiPackageVerifyingSession mParentVerifyingSession;
-    final long mRequiredInstalledVersionCode;
-    final int mDataLoaderType;
-    final int mSessionId;
-    final boolean mUserActionRequired;
-
+    private final long mRequiredInstalledVersionCode;
+    private final int mDataLoaderType;
+    private final int mSessionId;
+    private final boolean mUserActionRequired;
+    private final int mUserActionRequiredType;
     private boolean mWaitForVerificationToComplete;
     private boolean mWaitForIntegrityVerificationToComplete;
     private boolean mWaitForEnableRollbackToComplete;
     private int mRet = PackageManager.INSTALL_SUCCEEDED;
     private String mErrorMessage = null;
+    private final boolean mIsInherit;
+    private final boolean mIsStaged;
 
-    final PackageLite mPackageLite;
+    private final PackageLite mPackageLite;
     private final UserHandle mUser;
     @NonNull
-    final PackageManagerService mPm;
-    final InstallPackageHelper mInstallPackageHelper;
+    private final PackageManagerService mPm;
+    private final InstallPackageHelper mInstallPackageHelper;
 
     VerifyingSession(UserHandle user, File stagedDir, IPackageInstallObserver2 observer,
             PackageInstaller.SessionParams sessionParams, InstallSource installSource,
@@ -164,6 +167,9 @@
         mSessionId = sessionId;
         mPackageLite = lite;
         mUserActionRequired = userActionRequired;
+        mUserActionRequiredType = sessionParams.requireUserAction;
+        mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING;
+        mIsStaged = sessionParams.isStaged;
     }
 
     @Override
@@ -186,7 +192,7 @@
         // Perform package verification and enable rollback (unless we are simply moving the
         // package).
         if (!mOriginInfo.mExisting) {
-            if ((mInstallFlags & PackageManager.INSTALL_APEX) == 0) {
+            if (!isApex()) {
                 // TODO(b/182426975): treat APEX as APK when APK verification is concerned
                 sendApkVerificationRequest(pkgLite);
             }
@@ -674,10 +680,9 @@
         }
 
         final int installerUid = mVerificationInfo == null ? -1 : mVerificationInfo.mInstallerUid;
-        final int installFlags = mInstallFlags;
 
         // Check if installing from ADB
-        if ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0) {
+        if ((mInstallFlags & PackageManager.INSTALL_FROM_ADB) != 0) {
             boolean requestedDisableVerification =
                     (mInstallFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0;
             return isAdbVerificationEnabled(pkgInfoLite, userId, requestedDisableVerification);
@@ -685,8 +690,7 @@
 
         // only when not installed from ADB, skip verification for instant apps when
         // the installer and verifier are the same.
-        if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0
-                && mPm.mInstantAppInstallerActivity != null) {
+        if (isInstant() && mPm.mInstantAppInstallerActivity != null) {
             String installerPackage = mPm.mInstantAppInstallerActivity.packageName;
             for (String requiredVerifierPackage : requiredVerifierPackages) {
                 if (installerPackage.equals(requiredVerifierPackage)) {
@@ -764,7 +768,7 @@
 
     void populateInstallerExtras(Intent intent) {
         intent.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE,
-                mInstallSource.initiatingPackageName);
+                mInstallSource.mInitiatingPackageName);
 
         if (mVerificationInfo != null) {
             if (mVerificationInfo.mOriginatingUri != null) {
@@ -818,6 +822,9 @@
             return;
         }
         sendVerificationCompleteNotification();
+        if (mRet != INSTALL_SUCCEEDED) {
+            PackageMetrics.onVerificationFailed(this);
+        }
     }
 
     private void sendVerificationCompleteNotification() {
@@ -865,4 +872,28 @@
     public UserHandle getUser() {
         return mUser;
     }
+    public int getSessionId() {
+        return mSessionId;
+    }
+    public int getDataLoaderType() {
+        return mDataLoaderType;
+    }
+    public int getUserActionRequiredType() {
+        return mUserActionRequiredType;
+    }
+    public boolean isInstant() {
+        return (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+    }
+    public boolean isInherit() {
+        return mIsInherit;
+    }
+    public int getInstallerPackageUid() {
+        return mInstallSource.mInstallerPackageUid;
+    }
+    public boolean isApex() {
+        return (mInstallFlags & PackageManager.INSTALL_APEX) != 0;
+    }
+    public boolean isStaged() {
+        return mIsStaged;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 0bdd980..046db92 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -320,15 +320,13 @@
 
         switch (profileType) {
             case ArtManager.PROFILE_APPS :
-                return SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
+                return true;
             case ArtManager.PROFILE_BOOT_IMAGE:
                 // The device config property overrides the system property version.
                 boolean profileBootClassPath = SystemProperties.getBoolean(
                         "persist.device_config.runtime_native_boot.profilebootclasspath",
                         SystemProperties.getBoolean("dalvik.vm.profilebootclasspath", false));
-                return (Build.IS_USERDEBUG || Build.IS_ENG) &&
-                        SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false) &&
-                        profileBootClassPath;
+                return (Build.IS_USERDEBUG || Build.IS_ENG) && profileBootClassPath;
             default:
                 throw new IllegalArgumentException("Invalid profile type:" + profileType);
         }
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index 905bcf9..9f21097 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -298,12 +298,15 @@
                     dexMetadataType,
                     apkType,
                     ISA_MAP.getOrDefault(isa,
-                            ArtStatsLog.ART_DATUM_REPORTED__ISA__ART_ISA_UNKNOWN));
+                            ArtStatsLog.ART_DATUM_REPORTED__ISA__ART_ISA_UNKNOWN),
+                    ArtStatsLog.ART_DATUM_REPORTED__GC__ART_GC_COLLECTOR_TYPE_UNKNOWN);
         }
     }
 
     private static final Map<Integer, Integer> STATUS_MAP =
-            Map.of(BackgroundDexOptService.STATUS_OK,
+            Map.of(BackgroundDexOptService.STATUS_UNSPECIFIED,
+                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN,
+                    BackgroundDexOptService.STATUS_OK,
                     ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_JOB_FINISHED,
                     BackgroundDexOptService.STATUS_ABORT_BY_CANCELLATION,
                     ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BY_CANCELLATION,
@@ -314,18 +317,23 @@
                     BackgroundDexOptService.STATUS_ABORT_BATTERY,
                     ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BATTERY,
                     BackgroundDexOptService.STATUS_DEX_OPT_FAILED,
-                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_JOB_FINISHED);
+                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_JOB_FINISHED,
+                    BackgroundDexOptService.STATUS_FATAL_ERROR,
+                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_FATAL_ERROR);
 
     /** Helper class to write background dexopt job stats to statsd. */
     public static class BackgroundDexoptJobStatsLogger {
         /** Writes background dexopt job stats to statsd. */
         public void write(@BackgroundDexOptService.Status int status,
-                @JobParameters.StopReason int cancellationReason, long durationMs,
-                long durationIncludingSleepMs) {
-            ArtStatsLog.write(ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED,
+                          @JobParameters.StopReason int cancellationReason,
+                          long durationMs) {
+            ArtStatsLog.write(
+                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED,
                     STATUS_MAP.getOrDefault(status,
                             ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN),
-                    cancellationReason, durationMs, durationIncludingSleepMs);
+                    cancellationReason,
+                    durationMs,
+                    0);  // deprecated, used to be durationIncludingSleepMs
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index f5557c4..411c19f 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -18,6 +18,8 @@
 
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 
+import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
+
 import android.annotation.Nullable;
 
 import com.android.server.art.ReasonMapping;
@@ -26,8 +28,6 @@
 import com.android.server.pm.DexOptHelper;
 import com.android.server.pm.PackageManagerService;
 
-import dalvik.system.DexFile;
-
 /**
  * Options used for dexopt invocations.
  */
@@ -218,12 +218,11 @@
 
         /*@OptimizeFlags*/ int flags = extraFlags;
         if ((mFlags & DEXOPT_CHECK_FOR_PROFILES_UPDATES) == 0
-                && DexFile.isProfileGuidedCompilerFilter(mCompilerFilter)) {
-            // ART Service doesn't support bypassing this, so not setting this flag is not
-            // supported.
-            DexOptHelper.reportArtManagerFallback(mPackageName,
-                    "DEXOPT_CHECK_FOR_PROFILES_UPDATES not set with profile compiler filter");
-            return null;
+                && isProfileGuidedCompilerFilter(mCompilerFilter)) {
+            // ART Service doesn't support bypassing the profile update check when profiles are
+            // used, so not setting this flag is not supported.
+            throw new IllegalArgumentException(
+                    "DEXOPT_CHECK_FOR_PROFILES_UPDATES must be set with profile guided filter");
         }
         if ((mFlags & DEXOPT_FORCE) != 0) {
             flags |= ArtFlags.FLAG_FORCE;
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
index 5ba209d..9bca155 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -295,6 +295,7 @@
      * NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]"
      * for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader.
      */
+    @SuppressWarnings("ReturnValueIgnored")
     /*package*/ static String encodeClassLoader(String classpath, String classLoaderName) {
         classpath.getClass();  // Throw NPE if classpath is null
         String classLoaderDexoptEncoding = classLoaderName;
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/dex/SystemServerDexLoadReporter.java b/services/core/java/com/android/server/pm/dex/SystemServerDexLoadReporter.java
deleted file mode 100644
index 807c82d..0000000
--- a/services/core/java/com/android/server/pm/dex/SystemServerDexLoadReporter.java
+++ /dev/null
@@ -1,83 +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.pm.dex;
-
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-
-import android.content.pm.IPackageManager;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Slog;
-
-import dalvik.system.BaseDexClassLoader;
-import dalvik.system.VMRuntime;
-
-import java.util.Map;
-
-/**
- * Reports dex file use to the package manager on behalf of system server.
- */
-public class SystemServerDexLoadReporter implements BaseDexClassLoader.Reporter {
-    private static final String TAG = "SystemServerDexLoadReporter";
-
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private final IPackageManager mPackageManager;
-
-    private SystemServerDexLoadReporter(IPackageManager pm) {
-        mPackageManager = pm;
-    }
-
-    @Override
-    public void report(Map<String, String> classLoaderContextMap) {
-        if (DEBUG) {
-            Slog.i(TAG, "Reporting "  + classLoaderContextMap);
-        }
-        if (classLoaderContextMap.isEmpty()) {
-            Slog.wtf(TAG, "Bad call to DexLoadReporter: empty classLoaderContextMap");
-            return;
-        }
-
-        try {
-            mPackageManager.notifyDexLoad(
-                    PLATFORM_PACKAGE_NAME,
-                    classLoaderContextMap,
-                    VMRuntime.getRuntime().vmInstructionSet());
-        } catch (RemoteException ignored) {
-            // We're in system server, it can't happen.
-        }
-    }
-
-    /**
-     * Configures system server dex file reporting.
-     * <p>The method will install a reporter in the BaseDexClassLoader and also
-     * force the reporting of any dex files already loaded by the system server.
-     */
-    public static void configureSystemServerDexReporter(IPackageManager pm) {
-        Slog.i(TAG, "Configuring system server dex reporter");
-
-        SystemServerDexLoadReporter reporter = new SystemServerDexLoadReporter(pm);
-        BaseDexClassLoader.setReporter(reporter);
-        ClassLoader currrentClassLoader = reporter.getClass().getClassLoader();
-        if (currrentClassLoader instanceof BaseDexClassLoader) {
-            ((BaseDexClassLoader) currrentClassLoader).reportClassLoaderChain();
-        } else {
-            Slog.wtf(TAG, "System server class loader is not a BaseDexClassLoader. type="
-                    + currrentClassLoader.getClass().getName());
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 4ff0d59..f8e1547 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -22,6 +22,7 @@
 import android.annotation.UserIdInt;
 import android.os.Binder;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 
 import com.android.server.pm.Computer;
 import com.android.server.pm.PackageManagerLocal;
@@ -30,11 +31,9 @@
 import com.android.server.pm.snapshot.PackageDataSnapshot;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.function.Consumer;
 
 /** @hide */
 public class PackageManagerLocalImpl implements PackageManagerLocal {
@@ -143,7 +142,7 @@
         private final int mUserId;
 
         @Nullable
-        private ArrayList<PackageState> mFilteredPackageStates;
+        private Map<String, PackageState> mFilteredPackageStates;
 
         @Nullable
         private final UnfilteredSnapshotImpl mParentSnapshot;
@@ -179,26 +178,24 @@
             return mSnapshot.getPackageStateFiltered(packageName, mCallingUid, mUserId);
         }
 
+        @NonNull
         @Override
-        public void forAllPackageStates(@NonNull Consumer<PackageState> consumer) {
+        public Map<String, PackageState> getPackageStates() {
             checkClosed();
 
             if (mFilteredPackageStates == null) {
                 var packageStates = mSnapshot.getPackageStates();
-                var filteredPackageStates = new ArrayList<PackageState>();
+                var filteredPackageStates = new ArrayMap<String, PackageState>();
                 for (int index = 0, size = packageStates.size(); index < size; index++) {
                     var packageState = packageStates.valueAt(index);
                     if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) {
-                        filteredPackageStates.add(packageState);
+                        filteredPackageStates.put(packageStates.keyAt(index), packageState);
                     }
                 }
-                mFilteredPackageStates = filteredPackageStates;
+                mFilteredPackageStates = Collections.unmodifiableMap(filteredPackageStates);
             }
 
-            for (int index = 0, size = mFilteredPackageStates.size(); index < size; index++) {
-                var packageState = mFilteredPackageStates.get(index);
-                consumer.accept(packageState);
-            }
+            return mFilteredPackageStates;
         }
     }
 }
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..a3fa25d 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.seInfo = pkgSetting.getSeInfo();
+        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;
@@ -570,7 +533,7 @@
             ai.metaData = null;
         }
         ai.applicationInfo = applicationInfo;
-        ai.targetDisplayCategory = a.getTargetDisplayCategory();
+        ai.requiredDisplayCategory = a.getRequiredDisplayCategory();
         ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts());
         assignFieldsComponentInfoParsedMainComponent(ai, a, pkgSetting, userId);
         return ai;
@@ -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/AndroidPackageHidden.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
index 944e4ad..876bf17 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
@@ -40,13 +40,6 @@
     String getPrimaryCpuAbi();
 
     /**
-     * @see ApplicationInfo#seInfo
-     * TODO: This field is deriveable and might not have to be cached here.
-     */
-    @Nullable
-    String getSeInfo();
-
-    /**
      * @see ApplicationInfo#secondaryCpuAbi
      */
     @Nullable
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..82b5fa2 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
@@ -27,7 +27,6 @@
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.incremental.IncrementalManager;
-import android.text.TextUtils;
 
 import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.util.ArrayUtils;
@@ -235,10 +234,12 @@
                 || !pkg.getLibraryNames().isEmpty();
     }
 
-    public static int getHiddenApiEnforcementPolicy(AndroidPackage pkg,
+    public static int getHiddenApiEnforcementPolicy(@Nullable AndroidPackage pkg,
             @NonNull PackageStateInternal pkgSetting) {
         boolean isAllowedToUseHiddenApis;
-        if (pkg.isSignedWithPlatformKey()) {
+        if (pkg == null) {
+            isAllowedToUseHiddenApis = false;
+        } else if (pkg.isSignedWithPlatformKey()) {
             isAllowedToUseHiddenApis = true;
         } else if (pkg.isSystem() || pkgSetting.getTransientState().isUpdatedSystemApp()) {
             isAllowedToUseHiddenApis = pkg.isUsesNonSdkApi()
@@ -288,16 +289,6 @@
         return ((AndroidPackageHidden) pkg).getSecondaryCpuAbi();
     }
 
-    public static String getSeInfo(AndroidPackage pkg, @Nullable PackageStateInternal pkgSetting) {
-        if (pkgSetting != null) {
-            String overrideSeInfo = pkgSetting.getTransientState().getOverrideSeInfo();
-            if (!TextUtils.isEmpty(overrideSeInfo)) {
-                return overrideSeInfo;
-            }
-        }
-        return ((AndroidPackageHidden) pkg).getSeInfo();
-    }
-
     @Deprecated
     @NonNull
     public static ApplicationInfo generateAppInfoWithoutState(AndroidPackage pkg) {
@@ -321,8 +312,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/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index a43b979..ba36ab7 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -462,10 +462,6 @@
     @DataClass.ParcelWith(ForInternedString.class)
     protected String secondaryNativeLibraryDir;
 
-    @Nullable
-    @DataClass.ParcelWith(ForInternedString.class)
-    protected String seInfo;
-
     /**
      * This is an appId, the uid if the userId is == USER_SYSTEM
      */
@@ -1339,6 +1335,11 @@
     }
 
     @Override
+    public UUID getStorageUuid() {
+        return mStorageUuid;
+    }
+
+    @Override
     public int getTargetSandboxVersion() {
         return targetSandboxVersion;
     }
@@ -2905,12 +2906,6 @@
     }
 
     @Override
-    public PackageImpl setSeInfo(@Nullable String seInfo) {
-        this.seInfo = TextUtils.safeIntern(seInfo);
-        return this;
-    }
-
-    @Override
     public PackageImpl setSplitCodePaths(@Nullable String[] splitCodePaths) {
         this.splitCodePaths = splitCodePaths;
         if (splitCodePaths != null) {
@@ -2993,7 +2988,6 @@
         appInfo.primaryCpuAbi = primaryCpuAbi;
         appInfo.secondaryCpuAbi = secondaryCpuAbi;
         appInfo.secondaryNativeLibraryDir = secondaryNativeLibraryDir;
-        appInfo.seInfo = seInfo;
         appInfo.seInfoUser = SELinuxUtil.COMPLETE_STR;
         appInfo.uid = uid;
         return appInfo;
@@ -3147,7 +3141,6 @@
         sForInternedString.parcel(this.primaryCpuAbi, dest, flags);
         sForInternedString.parcel(this.secondaryCpuAbi, dest, flags);
         dest.writeString(this.secondaryNativeLibraryDir);
-        dest.writeString(this.seInfo);
         dest.writeInt(this.uid);
         dest.writeLong(this.mBooleans);
         dest.writeLong(this.mBooleans2);
@@ -3307,7 +3300,6 @@
         this.primaryCpuAbi = sForInternedString.unparcel(in);
         this.secondaryCpuAbi = sForInternedString.unparcel(in);
         this.secondaryNativeLibraryDir = in.readString();
-        this.seInfo = in.readString();
         this.uid = in.readInt();
         this.mBooleans = in.readLong();
         this.mBooleans2 = in.readLong();
@@ -3377,12 +3369,6 @@
         return secondaryNativeLibraryDir;
     }
 
-    @Nullable
-    @Override
-    public String getSeInfo() {
-        return seInfo;
-    }
-
     @Override
     public boolean isCoreApp() {
         return getBoolean(Booleans.CORE_APP);
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
index d306341..aeaff6d 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
@@ -103,8 +103,6 @@
 
     ParsedPackage setRestrictUpdateHash(byte[] restrictUpdateHash);
 
-    ParsedPackage setSeInfo(String seInfo);
-
     ParsedPackage setSecondaryNativeLibraryDir(String secondaryNativeLibraryDir);
 
     /**
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 8588267..83e17a5 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -86,6 +86,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -165,6 +166,11 @@
         COARSE_BACKGROUND_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
     }
 
+    private static final Set<String> FINE_LOCATION_PERMISSIONS = new ArraySet<>();
+    static {
+        FINE_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
+    }
+
     private static final Set<String> ACTIVITY_RECOGNITION_PERMISSIONS = new ArraySet<>();
     static {
         ACTIVITY_RECOGNITION_PERMISSIONS.add(Manifest.permission.ACTIVITY_RECOGNITION);
@@ -616,6 +622,10 @@
         grantPermissionsToSystemPackage(pm, getDefaultCaptivePortalLoginPackage(), userId,
                 NOTIFICATION_PERMISSIONS);
 
+        // Dock Manager
+        grantPermissionsToSystemPackage(pm, getDefaultDockManagerPackage(), userId,
+                NOTIFICATION_PERMISSIONS);
+
         // Camera
         grantPermissionsToSystemPackage(pm,
                 getDefaultSystemHandlerActivityPackage(pm, MediaStore.ACTION_IMAGE_CAPTURE, userId),
@@ -783,6 +793,8 @@
                         CONTACTS_PERMISSIONS, CALENDAR_PERMISSIONS, MICROPHONE_PERMISSIONS,
                         PHONE_PERMISSIONS, SMS_PERMISSIONS, COARSE_BACKGROUND_LOCATION_PERMISSIONS,
                         NEARBY_DEVICES_PERMISSIONS, NOTIFICATION_PERMISSIONS);
+                revokeRuntimePermissions(pm, voiceInteractPackageName, FINE_LOCATION_PERMISSIONS,
+                        false, userId);
             }
         }
 
@@ -933,6 +945,10 @@
         return mContext.getString(R.string.config_defaultCaptivePortalLoginPackageName);
     }
 
+    private String getDefaultDockManagerPackage() {
+        return mContext.getString(R.string.config_defaultDockManagerPackageName);
+    }
+
     @SafeVarargs
     private final void grantPermissionToEachSystemPackage(PackageManagerWrapper pm,
             ArrayList<String> packages, int userId, Set<String>... permissions) {
@@ -1924,7 +1940,7 @@
                             mPkgRequestingPerm, newRestrictionExcemptFlags, -1, mUser);
                 }
 
-                if (newGranted != null && newGranted != mOriginalGranted) {
+                if (newGranted != null && !Objects.equals(newGranted, mOriginalGranted)) {
                     if (newGranted) {
                         NO_PM_CACHE.grantPermission(mPermission, mPkgRequestingPerm, mUser);
                     } else {
diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
index 661161f..2a65a01 100644
--- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
+++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
@@ -16,17 +16,18 @@
 
 package com.android.server.pm.permission;
 
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
-
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.os.Handler;
+import android.os.RemoteException;
 import android.permission.PermissionControllerManager;
 import android.provider.DeviceConfig;
 import android.util.Log;
@@ -48,7 +49,7 @@
             "one_time_permissions_killed_delay_millis";
 
     private final @NonNull Context mContext;
-    private final @NonNull ActivityManager mActivityManager;
+    private final @NonNull IActivityManager mIActivityManager;
     private final @NonNull AlarmManager mAlarmManager;
     private final @NonNull PermissionControllerManager mPermissionControllerManager;
 
@@ -78,50 +79,15 @@
 
     OneTimePermissionUserManager(@NonNull Context context) {
         mContext = context;
-        mActivityManager = context.getSystemService(ActivityManager.class);
+        mIActivityManager = ActivityManager.getService();
         mAlarmManager = context.getSystemService(AlarmManager.class);
         mPermissionControllerManager = new PermissionControllerManager(
                 mContext, PermissionThread.getHandler());
         mHandler = context.getMainThreadHandler();
     }
 
-    /**
-     * Starts a one-time permission session for a given package. A one-time permission session is
-     * ended if app becomes inactive. Inactivity is defined as the package's uid importance level
-     * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid
-     * importance level goes <= importanceToResetTimer then the timer is reset and doesn't start
-     * until going > importanceToResetTimer.
-     * <p>
-     * When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive
-     * then the session is extended until either the importance goes above
-     * importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which
-     * will continue the session and reset the timer.
-     * </p>
-     * <p>
-     * Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}.
-     * </p>
-     * <p>
-     * Once the session ends PermissionControllerService#onNotifyOneTimePermissionSessionTimeout
-     * is invoked.
-     * </p>
-     * <p>
-     * Note that if there is currently an active session for a package a new one isn't created and
-     * the existing one isn't changed.
-     * </p>
-     * @param packageName The package to start a one-time permission session for
-     * @param timeoutMillis Number of milliseconds for an app to be in an inactive state
-     * @param revokeAfterKilledDelayMillis Number of milliseconds to wait after the process dies
-     *                                     before ending the session. Set to -1 to use default value
-     *                                     for the device.
-     * @param importanceToResetTimer The least important level to uid must be to reset the timer
-     * @param importanceToKeepSessionAlive The least important level the uid must be to keep the
-     *                                     session alive
-     *
-     * @hide
-     */
     void startPackageOneTimeSession(@NonNull String packageName, long timeoutMillis,
-            long revokeAfterKilledDelayMillis, int importanceToResetTimer,
-            int importanceToKeepSessionAlive) {
+            long revokeAfterKilledDelayMillis) {
         int uid;
         try {
             uid = mContext.getPackageManager().getPackageUid(packageName, 0);
@@ -133,13 +99,11 @@
         synchronized (mLock) {
             PackageInactivityListener listener = mListeners.get(uid);
             if (listener != null) {
-                listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis,
-                        importanceToResetTimer, importanceToKeepSessionAlive);
+                listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis);
                 return;
             }
             listener = new PackageInactivityListener(uid, packageName, timeoutMillis,
-                    revokeAfterKilledDelayMillis, importanceToResetTimer,
-                    importanceToKeepSessionAlive);
+                    revokeAfterKilledDelayMillis);
             mListeners.put(uid, listener);
         }
     }
@@ -184,34 +148,58 @@
 
         private static final long TIMER_INACTIVE = -1;
 
+        private static final int STATE_GONE = 0;
+        private static final int STATE_TIMER = 1;
+        private static final int STATE_ACTIVE = 2;
+
         private final int mUid;
         private final @NonNull String mPackageName;
         private long mTimeout;
         private long mRevokeAfterKilledDelay;
-        private int mImportanceToResetTimer;
-        private int mImportanceToKeepSessionAlive;
 
         private boolean mIsAlarmSet;
         private boolean mIsFinished;
 
         private long mTimerStart = TIMER_INACTIVE;
 
-        private final ActivityManager.OnUidImportanceListener mStartTimerListener;
-        private final ActivityManager.OnUidImportanceListener mSessionKillableListener;
-        private final ActivityManager.OnUidImportanceListener mGoneListener;
-
         private final Object mInnerLock = new Object();
         private final Object mToken = new Object();
+        private final IUidObserver.Stub mObserver = new IUidObserver.Stub() {
+            @Override
+            public void onUidGone(int uid, boolean disabled) {
+                if (uid == mUid) {
+                    PackageInactivityListener.this.updateUidState(STATE_GONE);
+                }
+            }
+
+            @Override
+            public void onUidStateChanged(int uid, int procState, long procStateSeq,
+                    int capability) {
+                if (uid == mUid) {
+                    if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+                            && procState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+                        PackageInactivityListener.this.updateUidState(STATE_TIMER);
+                    } else {
+                        PackageInactivityListener.this.updateUidState(STATE_ACTIVE);
+                    }
+                }
+            }
+
+            public void onUidActive(int uid) {
+            }
+            public void onUidIdle(int uid, boolean disabled) {
+            }
+            public void onUidProcAdjChanged(int uid) {
+            }
+            public void onUidCachedChanged(int uid, boolean cached) {
+            }
+        };
 
         private PackageInactivityListener(int uid, @NonNull String packageName, long timeout,
-                long revokeAfterkilledDelay, int importanceToResetTimer,
-                int importanceToKeepSessionAlive) {
-
+                long revokeAfterkilledDelay) {
             Log.i(LOG_TAG,
                     "Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout
-                            + " killedDelay=" + revokeAfterkilledDelay
-                            + " importanceToResetTimer=" + importanceToResetTimer
-                            + " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive);
+                            + " killedDelay=" + revokeAfterkilledDelay);
 
             mUid = uid;
             mPackageName = packageName;
@@ -221,27 +209,24 @@
                             DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY,
                             DEFAULT_KILLED_DELAY_MILLIS)
                     : revokeAfterkilledDelay;
-            mImportanceToResetTimer = importanceToResetTimer;
-            mImportanceToKeepSessionAlive = importanceToKeepSessionAlive;
 
-            mStartTimerListener =
-                    (changingUid, importance) -> onImportanceChanged(changingUid, importance);
-            mSessionKillableListener =
-                    (changingUid, importance) -> onImportanceChanged(changingUid, importance);
-            mGoneListener =
-                    (changingUid, importance) -> onImportanceChanged(changingUid, importance);
+            try {
+                mIActivityManager.registerUidObserver(mObserver,
+                        ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_PROCSTATE,
+                        ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
+                        null);
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, "Couldn't check uid proc state", e);
+                // Can't register uid observer, just revoke immediately
+                synchronized (mInnerLock) {
+                    onPackageInactiveLocked();
+                }
+            }
 
-            mActivityManager.addOnUidImportanceListener(mStartTimerListener,
-                    importanceToResetTimer);
-            mActivityManager.addOnUidImportanceListener(mSessionKillableListener,
-                    importanceToKeepSessionAlive);
-            mActivityManager.addOnUidImportanceListener(mGoneListener, IMPORTANCE_CACHED);
-
-            onImportanceChanged(mUid, mActivityManager.getPackageImportance(packageName));
+            updateUidState();
         }
 
-        public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis,
-                int importanceToResetTimer, int importanceToKeepSessionAlive) {
+        public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis) {
             synchronized (mInnerLock) {
                 mTimeout = Math.min(mTimeout, timeoutMillis);
                 mRevokeAfterKilledDelay = Math.min(mRevokeAfterKilledDelay,
@@ -250,63 +235,79 @@
                                 DeviceConfig.NAMESPACE_PERMISSIONS,
                                 PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS)
                                 : revokeAfterKilledDelayMillis);
-                mImportanceToResetTimer = Math.min(importanceToResetTimer, mImportanceToResetTimer);
-                mImportanceToKeepSessionAlive = Math.min(importanceToKeepSessionAlive,
-                        mImportanceToKeepSessionAlive);
                 Log.v(LOG_TAG,
                         "Updated params for " + mPackageName + ". timeout=" + mTimeout
-                                + " killedDelay=" + mRevokeAfterKilledDelay
-                                + " importanceToResetTimer=" + mImportanceToResetTimer
-                                + " importanceToKeepSessionAlive=" + mImportanceToKeepSessionAlive);
-                onImportanceChanged(mUid, mActivityManager.getPackageImportance(mPackageName));
+                                + " killedDelay=" + mRevokeAfterKilledDelay);
+                updateUidState();
             }
         }
 
-        private void onImportanceChanged(int uid, int importance) {
-            if (uid != mUid) {
-                return;
+        private int getCurrentState() {
+            try {
+                return getStateFromProcState(mIActivityManager.getUidProcessState(mUid, null));
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, "Couldn't check uid proc state", e);
             }
+            return STATE_GONE;
+        }
 
-            Log.v(LOG_TAG, "Importance changed for " + mPackageName + " (" + mUid + ")."
-                    + " importance=" + importance);
+        private int getStateFromProcState(int procState) {
+            if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+                return STATE_GONE;
+            } else {
+                if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+                    return STATE_TIMER;
+                } else {
+                    return STATE_ACTIVE;
+                }
+            }
+        }
+
+        private void updateUidState() {
+            updateUidState(getCurrentState());
+        }
+
+        private void updateUidState(int state) {
+            Log.v(LOG_TAG, "Updating state for " + mPackageName + " (" + mUid + ")."
+                    + " state=" + state);
             synchronized (mInnerLock) {
                 // Remove any pending inactivity callback
                 mHandler.removeCallbacksAndMessages(mToken);
 
-                if (importance > IMPORTANCE_CACHED) {
+                if (state == STATE_GONE) {
                     if (mRevokeAfterKilledDelay == 0) {
                         onPackageInactiveLocked();
                         return;
                     }
                     // Delay revocation in case app is restarting
                     mHandler.postDelayed(() -> {
-                        int imp = mActivityManager.getUidImportance(mUid);
-                        if (imp > IMPORTANCE_CACHED) {
-                            onPackageInactiveLocked();
-                        } else {
-                            if (DEBUG) {
-                                Log.d(LOG_TAG, "No longer gone after delayed revocation. "
-                                        + "Rechecking for " + mPackageName + " (" + mUid + ").");
+                        int currentState;
+                        synchronized (mInnerLock) {
+                            currentState = getCurrentState();
+                            if (currentState == STATE_GONE) {
+                                onPackageInactiveLocked();
+                                return;
                             }
-                            onImportanceChanged(mUid, imp);
                         }
+                        if (DEBUG) {
+                            Log.d(LOG_TAG, "No longer gone after delayed revocation. "
+                                    + "Rechecking for " + mPackageName + " (" + mUid
+                                    + ").");
+                        }
+                        updateUidState(currentState);
                     }, mToken, mRevokeAfterKilledDelay);
                     return;
-                }
-                if (importance > mImportanceToResetTimer) {
+                } else if (state == STATE_TIMER) {
                     if (mTimerStart == TIMER_INACTIVE) {
                         if (DEBUG) {
                             Log.d(LOG_TAG, "Start the timer for "
                                     + mPackageName + " (" + mUid + ").");
                         }
                         mTimerStart = System.currentTimeMillis();
+                        setAlarmLocked();
                     }
-                } else {
+                } else if (state == STATE_ACTIVE) {
                     mTimerStart = TIMER_INACTIVE;
-                }
-                if (importance > mImportanceToKeepSessionAlive) {
-                    setAlarmLocked();
-                } else {
                     cancelAlarmLocked();
                 }
             }
@@ -320,19 +321,9 @@
                 mIsFinished = true;
                 cancelAlarmLocked();
                 try {
-                    mActivityManager.removeOnUidImportanceListener(mStartTimerListener);
-                } catch (IllegalArgumentException e) {
-                    Log.e(LOG_TAG, "Could not remove start timer listener", e);
-                }
-                try {
-                    mActivityManager.removeOnUidImportanceListener(mSessionKillableListener);
-                } catch (IllegalArgumentException e) {
-                    Log.e(LOG_TAG, "Could not remove session killable listener", e);
-                }
-                try {
-                    mActivityManager.removeOnUidImportanceListener(mGoneListener);
-                } catch (IllegalArgumentException e) {
-                    Log.e(LOG_TAG, "Could not remove gone listener", e);
+                    mIActivityManager.unregisterUidObserver(mObserver);
+                } catch (RemoteException e) {
+                    Log.e(LOG_TAG, "Unable to unregister uid observer.", e);
                 }
             }
         }
@@ -396,9 +387,11 @@
                         mPermissionControllerManager.notifyOneTimePermissionSessionTimeout(
                                 mPackageName);
                     });
-            mActivityManager.removeOnUidImportanceListener(mStartTimerListener);
-            mActivityManager.removeOnUidImportanceListener(mSessionKillableListener);
-            mActivityManager.removeOnUidImportanceListener(mGoneListener);
+            try {
+                mIActivityManager.unregisterUidObserver(mObserver);
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, "Unable to unregister uid observer.", e);
+            }
             synchronized (mLock) {
                 mListeners.remove(mUid);
             }
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 69e7bf1..165c52d 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -322,6 +322,10 @@
         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_COMPANION) != 0;
     }
 
+    public boolean isModule() {
+        return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_MODULE) != 0;
+    }
+
     public boolean isRetailDemo() {
         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO) != 0;
     }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 3b9f0ba..cefe9cd 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -50,6 +50,7 @@
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.permission.SplitPermissionInfoParcelable;
+import android.healthconnect.HealthConnectManager;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Process;
@@ -385,8 +386,7 @@
 
     @Override
     public void startOneTimePermissionSession(String packageName, @UserIdInt int userId,
-            long timeoutMillis, long revokeAfterKilledDelayMillis, int importanceToResetTimer,
-            int importanceToKeepSessionAlive) {
+            long timeoutMillis, long revokeAfterKilledDelayMillis) {
         mContext.enforceCallingOrSelfPermission(
                 Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
                 "Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS
@@ -396,8 +396,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             getOneTimePermissionUserManager(userId).startPackageOneTimeSession(packageName,
-                    timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer,
-                    importanceToKeepSessionAlive);
+                    timeoutMillis, revokeAfterKilledDelayMillis);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -406,6 +405,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS)
     @Override
     public void stopOneTimePermissionSession(String packageName, @UserIdInt int userId) {
+        super.stopOneTimePermissionSession_enforcePermission();
+
         Objects.requireNonNull(packageName);
 
         final long token = Binder.clearCallingIdentity();
@@ -466,7 +467,7 @@
 
     @Override
     public PermissionInfo getPermissionInfo(String permissionName, String packageName, int flags) {
-        return mPermissionManagerServiceImpl.getPermissionInfo(permissionName, packageName, flags);
+        return mPermissionManagerServiceImpl.getPermissionInfo(permissionName, flags, packageName);
     }
 
     @Override
@@ -791,14 +792,14 @@
 
         @NonNull
         @Override
-        public ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+        public List<PermissionInfo> getAllPermissionsWithProtection(
                 @PermissionInfo.Protection int protection) {
             return mPermissionManagerServiceImpl.getAllPermissionsWithProtection(protection);
         }
 
         @NonNull
         @Override
-        public ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+        public List<PermissionInfo> getAllPermissionsWithProtectionFlags(
                 @PermissionInfo.ProtectionFlags int protectionFlags) {
             return mPermissionManagerServiceImpl
                     .getAllPermissionsWithProtectionFlags(protectionFlags);
@@ -1100,7 +1101,7 @@
                     if (resolvedPackageName == null) {
                         return;
                     }
-                    appOpsManager.finishOp(accessorSource.getToken(), op,
+                    appOpsManager.finishOp(attributionSourceState.token, op,
                             accessorSource.getUid(), resolvedPackageName,
                             accessorSource.getAttributionTag());
                 } else {
@@ -1109,8 +1110,9 @@
                     if (resolvedAttributionSource.getPackageName() == null) {
                         return;
                     }
-                    appOpsManager.finishProxyOp(AppOpsManager.opToPublicName(op),
-                            resolvedAttributionSource, skipCurrentFinish);
+                    appOpsManager.finishProxyOp(attributionSourceState.token,
+                            AppOpsManager.opToPublicName(op), resolvedAttributionSource,
+                            skipCurrentFinish);
                 }
                 RegisteredAttribution registered =
                         sRunningAttributionSources.remove(current.getToken());
@@ -1156,7 +1158,8 @@
             if (permissionInfo == null) {
                 try {
                     permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
-                    if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) {
+                    if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)
+                            || HealthConnectManager.isHealthPermission(context, permission)) {
                         // Double addition due to concurrency is fine - the backing
                         // store is concurrent.
                         sPlatformPermissions.put(permission, permissionInfo);
@@ -1225,10 +1228,11 @@
                         && next.getNext() == null);
                 final boolean selfAccess = singleReceiverFromDatasource || next == null;
 
-                final int opMode = performOpTransaction(context, op, current, message,
-                        forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks,
-                        selfAccess, singleReceiverFromDatasource, AppOpsManager.OP_NONE,
-                        AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+                final int opMode = performOpTransaction(context, attributionSource.getToken(), op,
+                        current, message, forDataDelivery, /*startDataDelivery*/ false,
+                        skipCurrentChecks, selfAccess, singleReceiverFromDatasource,
+                        AppOpsManager.OP_NONE, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+                        AppOpsManager.ATTRIBUTION_FLAGS_NONE,
                         AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
 
                 switch (opMode) {
@@ -1331,10 +1335,10 @@
                         attributionSource, next, fromDatasource, startDataDelivery, selfAccess,
                         isLinkTrusted) : ATTRIBUTION_FLAGS_NONE;
 
-                final int opMode = performOpTransaction(context, op, current, message,
-                        forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
-                        singleReceiverFromDatasource, attributedOp, proxyAttributionFlags,
-                        proxiedAttributionFlags, attributionChainId);
+                final int opMode = performOpTransaction(context, attributionSource.getToken(), op,
+                        current, message, forDataDelivery, startDataDelivery, skipCurrentChecks,
+                        selfAccess, singleReceiverFromDatasource, attributedOp,
+                        proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
 
                 switch (opMode) {
                     case AppOpsManager.MODE_ERRORED: {
@@ -1479,8 +1483,8 @@
                         attributionSource, next, /*fromDatasource*/ false, startDataDelivery,
                         selfAccess, isLinkTrusted) : ATTRIBUTION_FLAGS_NONE;
 
-                final int opMode = performOpTransaction(context, op, current, message,
-                        forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
+                final int opMode = performOpTransaction(context, current.getToken(), op, current,
+                        message, forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
                         /*fromDatasource*/ false, AppOpsManager.OP_NONE, proxyAttributionFlags,
                         proxiedAttributionFlags, attributionChainId);
 
@@ -1502,7 +1506,8 @@
         }
 
         @SuppressWarnings("ConstantConditions")
-        private static int performOpTransaction(@NonNull Context context, int op,
+        private static int performOpTransaction(@NonNull Context context,
+                @NonNull IBinder chainStartToken, int op,
                 @NonNull AttributionSource attributionSource, @Nullable String message,
                 boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation,
                 boolean selfAccess, boolean singleReceiverFromDatasource, int attributedOp,
@@ -1564,7 +1569,7 @@
                 if (selfAccess) {
                     try {
                         startedOpResult = appOpsManager.startOpNoThrow(
-                                resolvedAttributionSource.getToken(), startedOp,
+                                chainStartToken, startedOp,
                                 resolvedAttributionSource.getUid(),
                                 resolvedAttributionSource.getPackageName(),
                                 /*startIfModeDefault*/ false,
@@ -1575,14 +1580,14 @@
                                 + " platform defined runtime permission "
                                 + AppOpsManager.opToPermission(op) + " while not having "
                                 + Manifest.permission.UPDATE_APP_OPS_STATS);
-                        startedOpResult = appOpsManager.startProxyOpNoThrow(attributedOp,
-                                attributionSource, message, skipProxyOperation,
+                        startedOpResult = appOpsManager.startProxyOpNoThrow(chainStartToken,
+                                attributedOp, attributionSource, message, skipProxyOperation,
                                 proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
                     }
                 } else {
                     try {
-                        startedOpResult = appOpsManager.startProxyOpNoThrow(startedOp,
-                                resolvedAttributionSource, message, skipProxyOperation,
+                        startedOpResult = appOpsManager.startProxyOpNoThrow(chainStartToken,
+                                startedOp, resolvedAttributionSource, message, skipProxyOperation,
                                 proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
                     } catch (SecurityException e) {
                         //TODO 195339480: remove
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 ab223ef..2a2bcab 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -231,12 +231,15 @@
         READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VIDEO);
         READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_IMAGES);
         READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
+        READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED);
         NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_ADVERTISE);
         NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT);
         NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN);
         NOTIFICATION_PERMISSIONS.add(Manifest.permission.POST_NOTIFICATIONS);
     }
 
+    @NonNull private final ApexManager mApexManager;
+
     /** Set of source package names for Privileged Permission Allowlist */
     private final ArraySet<String> mPrivilegedPermissionAllowlistSourcePackageNames =
             new ArraySet<>();
@@ -420,6 +423,7 @@
         mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
         mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
         mIsLeanback = availableFeatures.containsKey(PackageManager.FEATURE_LEANBACK);
+        mApexManager = ApexManager.getInstance();
 
         mPrivilegedPermissionAllowlistSourcePackageNames.add(PLATFORM_PACKAGE_NAME);
         // PackageManager.hasSystemFeature() is not used here because PackageManagerService
@@ -558,8 +562,8 @@
 
     @Override
     @Nullable
-    public PermissionInfo getPermissionInfo(@NonNull String permName, @NonNull String opPackageName,
-            @PackageManager.PermissionInfoFlags int flags) {
+    public PermissionInfo getPermissionInfo(@NonNull String permName,
+            @PackageManager.PermissionInfoFlags int flags, @NonNull String opPackageName) {
         final int callingUid = Binder.getCallingUid();
         if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
             return null;
@@ -2126,7 +2130,7 @@
             for (int i = 0; i < numRequestedPermissions; i++) {
                 PermissionInfo permInfo = getPermissionInfo(
                         newPackage.getRequestedPermissions().get(i),
-                        newPackage.getPackageName(), 0);
+                        0, newPackage.getPackageName());
                 if (permInfo == null) {
                     continue;
                 }
@@ -3308,9 +3312,8 @@
             return true;
         }
         final String permissionName = permission.getName();
-        final ApexManager apexManager = ApexManager.getInstance();
         final String containingApexPackageName =
-                apexManager.getActiveApexPackageNameContainingPackage(packageName);
+                mApexManager.getActiveApexPackageNameContainingPackage(packageName);
         if (isInSystemConfigPrivAppPermissions(pkg, permissionName,
                 containingApexPackageName)) {
             return true;
@@ -3364,8 +3367,7 @@
         } else if (pkg.isSystemExt()) {
             permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
         } else if (containingApexPackageName != null) {
-            final ApexManager apexManager = ApexManager.getInstance();
-            final String apexName = apexManager.getApexModuleNameForPackageName(
+            final String apexName = mApexManager.getApexModuleNameForPackageName(
                     containingApexPackageName);
             final Set<String> privAppPermissions = systemConfig.getPrivAppPermissions(
                     pkg.getPackageName());
@@ -3581,6 +3583,11 @@
             // Special permission for the recents app.
             allowed = true;
         }
+        if (!allowed && bp.isModule() && mApexManager.getActiveApexPackageNameContainingPackage(
+                pkg.getPackageName()) != null) {
+            // Special permission granted for APKs inside APEX modules.
+            allowed = true;
+        }
         return allowed;
     }
 
@@ -5203,9 +5210,9 @@
 
     @NonNull
     @Override
-    public ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+    public List<PermissionInfo> getAllPermissionsWithProtection(
             @PermissionInfo.Protection int protection) {
-        ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>();
+        List<PermissionInfo> matchingPermissions = new ArrayList<>();
 
         synchronized (mLock) {
             for (final Permission permission : mRegistry.getPermissions()) {
@@ -5220,9 +5227,9 @@
 
     @NonNull
     @Override
-    public ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+    public List<PermissionInfo> getAllPermissionsWithProtectionFlags(
             @PermissionInfo.ProtectionFlags int protectionFlags) {
-        ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>();
+        List<PermissionInfo> matchingPermissions = new ArrayList<>();
 
         synchronized (mLock) {
             for (final Permission permission : mRegistry.getPermissions()) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index 930936b..d9caec7 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -32,7 +32,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -77,8 +76,8 @@
      * @return a {@link PermissionInfo} containing information about the permission, or {@code null}
      *         if not found
      */
-    PermissionInfo getPermissionInfo(@NonNull String permName, @NonNull String opPackageName,
-            @PackageManager.PermissionInfoFlags int flags);
+    PermissionInfo getPermissionInfo(@NonNull String permName,
+            @PackageManager.PermissionInfoFlags int flags, @NonNull String opPackageName);
 
     /**
      * Query for all of the permissions associated with a particular group.
@@ -487,11 +486,11 @@
 
     /** Get all permissions that have a certain protection */
     @NonNull
-    ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+    List<PermissionInfo> getAllPermissionsWithProtection(
             @PermissionInfo.Protection int protection);
 
     /** Get all permissions that have certain protection flags */
-    @NonNull ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+    @NonNull List<PermissionInfo> getAllPermissionsWithProtectionFlags(
             @PermissionInfo.ProtectionFlags int protectionFlags);
 
     /**
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index f20620e..97ac749 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -164,11 +164,12 @@
 
     /** Get all permissions that have a certain protection */
     @NonNull
-    ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+    List<PermissionInfo> getAllPermissionsWithProtection(
             @PermissionInfo.Protection int protection);
 
-    /** Get all permissions that have certain protection flags */
-    @NonNull ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+    /** Get all permissions that have certain protection flags
+     * @return*/
+    @NonNull List<PermissionInfo> getAllPermissionsWithProtectionFlags(
             @PermissionInfo.ProtectionFlags int protectionFlags);
 
     /**
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
index e3dad45..84907a5 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -34,6 +34,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.SigningDetails;
 import android.os.Bundle;
+import android.os.storage.StorageManager;
 import android.processor.immutability.Immutable;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -58,6 +59,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * The representation of an application on disk, as parsed from its split APKs' manifests.
@@ -111,6 +113,13 @@
     String getStaticSharedLibraryName();
 
     /**
+     * @return The {@link UUID} for use with {@link StorageManager} APIs identifying where this
+     * package was installed.
+     */
+    @NonNull
+    UUID getStorageUuid();
+
+    /**
      * @see ApplicationInfo#targetSdkVersion
      * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
      */
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 3c79cdf..67b7647 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -112,6 +112,19 @@
     int getAppId();
 
     /**
+     * Retrieves effective hidden API policy for this app. The state can be dependent on
+     * {@link #getAndroidPackage()} availability and whether the app is a system app.
+     *
+     * Note that during process start, this policy may be mutated by device specific process
+     * configuration, so this value isn't truly final.
+     *
+     * @return The (mostly) final {@link ApplicationInfo.HiddenApiEnforcementPolicy} that should be
+     * applied to this package.
+     */
+    @ApplicationInfo.HiddenApiEnforcementPolicy
+    int getHiddenApiEnforcementPolicy();
+
+    /**
      * @see PackageInfo#packageName
      * @see AndroidPackage#getPackageName()
      */
@@ -131,6 +144,26 @@
     String getSecondaryCpuAbi();
 
     /**
+     * @see ApplicationInfo#seInfo
+     * @return The SE info for this package, which may be overridden by a system configured value,
+     * or null if the package isn't available.
+     */
+    @Nullable
+    String getSeInfo();
+
+    /**
+     * @return State for a user or {@link PackageUserState#DEFAULT} if the state doesn't exist.
+     */
+    @NonNull
+    PackageUserState getStateForUser(@NonNull UserHandle user);
+
+    /**
+     * @see R.styleable#AndroidManifestUsesLibrary
+     */
+    @NonNull
+    List<SharedLibrary> getUsesLibraries();
+
+    /**
      * @see AndroidPackage#isPrivileged()
      */
     boolean isPrivileged();
@@ -146,18 +179,6 @@
      */
     boolean isUpdatedSystemApp();
 
-    /**
-     * @return State for a user or {@link PackageUserState#DEFAULT} if the state doesn't exist.
-     */
-    @NonNull
-    PackageUserState getStateForUser(@NonNull UserHandle user);
-
-    /**
-     * @see R.styleable#AndroidManifestUsesLibrary
-     */
-    @NonNull
-    List<SharedLibrary> getUsesLibraries();
-
     // Methods below this comment are not yet exposed as API
 
     /**
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index c6ce40e..43d019a 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.SigningInfo;
@@ -118,6 +119,8 @@
     private final int mCategoryOverride;
     @Nullable
     private final String mCpuAbiOverride;
+    @ApplicationInfo.HiddenApiEnforcementPolicy
+    private final int mHiddenApiEnforcementPolicy;
     private final long mLastModifiedTime;
     private final long mLastUpdateTime;
     private final long mLongVersionCode;
@@ -129,6 +132,8 @@
     private final String mPrimaryCpuAbi;
     @Nullable
     private final String mSecondaryCpuAbi;
+    @Nullable
+    private final String mSeInfo;
     private final boolean mHasSharedUser;
     private final int mSharedUserAppId;
     @NonNull
@@ -168,6 +173,7 @@
         mAppId = pkgState.getAppId();
         mCategoryOverride = pkgState.getCategoryOverride();
         mCpuAbiOverride = pkgState.getCpuAbiOverride();
+        mHiddenApiEnforcementPolicy = pkgState.getHiddenApiEnforcementPolicy();
         mLastModifiedTime = pkgState.getLastModifiedTime();
         mLastUpdateTime = pkgState.getLastUpdateTime();
         mLongVersionCode = pkgState.getVersionCode();
@@ -175,6 +181,7 @@
         mPath = pkgState.getPath();
         mPrimaryCpuAbi = pkgState.getPrimaryCpuAbi();
         mSecondaryCpuAbi = pkgState.getSecondaryCpuAbi();
+        mSeInfo = pkgState.getSeInfo();
         mHasSharedUser = pkgState.hasSharedUser();
         mSharedUserAppId = pkgState.getSharedUserAppId();
         mUsesSdkLibraries = pkgState.getUsesSdkLibraries();
@@ -542,7 +549,7 @@
         }
 
         @DataClass.Generated(
-                time = 1661977809886L,
+                time = 1666719622708L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
                 inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final  long mFirstInstallTime\npublic static  com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@@ -606,6 +613,11 @@
     }
 
     @DataClass.Generated.Member
+    public @ApplicationInfo.HiddenApiEnforcementPolicy int getHiddenApiEnforcementPolicy() {
+        return mHiddenApiEnforcementPolicy;
+    }
+
+    @DataClass.Generated.Member
     public long getLastModifiedTime() {
         return mLastModifiedTime;
     }
@@ -641,6 +653,11 @@
     }
 
     @DataClass.Generated.Member
+    public @Nullable String getSeInfo() {
+        return mSeInfo;
+    }
+
+    @DataClass.Generated.Member
     public boolean isHasSharedUser() {
         return mHasSharedUser;
     }
@@ -697,10 +714,10 @@
     }
 
     @DataClass.Generated(
-            time = 1661977809932L,
+            time = 1666719622749L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
-            inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final  boolean mHasSharedUser\nprivate final  int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nprivate static final  int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+            inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy int mHiddenApiEnforcementPolicy\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final  boolean mHasSharedUser\nprivate final  int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nprivate static final  int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
index b22c038..57fbfe9 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.content.pm.PackageManager;
 import android.content.pm.SharedLibraryInfo;
+import android.text.TextUtils;
 
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
@@ -62,6 +63,9 @@
     @Nullable
     private String overrideSeInfo;
 
+    @NonNull
+    private String seInfo;
+
     // TODO: Remove in favor of finer grained change notification
     @NonNull
     private final PackageSetting mPackageSetting;
@@ -138,6 +142,7 @@
         this.apkInUpdatedApex = other.apkInUpdatedApex;
         this.lastPackageUsageTimeInMills = other.lastPackageUsageTimeInMills;
         this.overrideSeInfo = other.overrideSeInfo;
+        this.seInfo = other.seInfo;
         mPackageSetting.onChanged();
     }
 
@@ -206,6 +211,13 @@
         return this;
     }
 
+    @NonNull
+    public PackageStateUnserialized setSeInfo(@NonNull String value) {
+        seInfo = TextUtils.safeIntern(value);
+        mPackageSetting.onChanged();
+        return this;
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -271,15 +283,20 @@
     }
 
     @DataClass.Generated.Member
+    public @NonNull String getSeInfo() {
+        return seInfo;
+    }
+
+    @DataClass.Generated.Member
     public @NonNull PackageSetting getPackageSetting() {
         return mPackageSetting;
     }
 
     @DataClass.Generated(
-            time = 1661373697219L,
+            time = 1666291743725L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java",
-            inputSignatures = "private  boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate  boolean updatedSystemApp\nprivate  boolean apkInApex\nprivate  boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate  long[] lazyInitLastPackageUsageTimeInMills()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic  long getLatestPackageUseTimeInMills()\npublic  long getLatestForegroundPackageUseTimeInMills()\npublic  void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
+            inputSignatures = "private  boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate  boolean updatedSystemApp\nprivate  boolean apkInApex\nprivate  boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate @android.annotation.NonNull java.lang.String seInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate  long[] lazyInitLastPackageUsageTimeInMills()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic  long getLatestPackageUseTimeInMills()\npublic  long getLatestForegroundPackageUseTimeInMills()\npublic  void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized setSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index a536f90..b3deb1c 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -44,7 +44,7 @@
 /** @hide */
 @DataClass(genConstructor = false, genBuilder = false, genEqualsHashCode = true)
 @DataClass.Suppress({"mOverlayPathsLock", "mOverlayPaths", "mSharedLibraryOverlayPathsLock",
-        "mSharedLibraryOverlayPaths", "setOverlayPaths", "setCachedOverlayPaths"})
+        "mSharedLibraryOverlayPaths", "setOverlayPaths", "setCachedOverlayPaths", "getWatchable"})
 public class PackageUserStateImpl extends WatchableImpl implements PackageUserStateInternal,
         Snappable {
 
@@ -92,8 +92,9 @@
 
     private long mFirstInstallTime;
 
+    // TODO(b/239050028): Remove, enforce notifying parent through PMS commit method
     @Nullable
-    private final Watchable mWatchable;
+    private Watchable mWatchable;
 
     @NonNull
     final SnapshotCache<PackageUserStateImpl> mSnapshot;
@@ -550,71 +551,30 @@
                 ? Collections.emptyMap() : mSharedLibraryOverlayPaths;
     }
 
-    @Override
-    public boolean equals(@Nullable Object o) {
-        // You can override field equality logic by defining either of the methods like:
-        // boolean fieldNameEquals(PackageUserStateImpl other) { ... }
-        // boolean fieldNameEquals(FieldType otherValue) { ... }
-
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        @SuppressWarnings("unchecked")
-        PackageUserStateImpl that = (PackageUserStateImpl) o;
-        //noinspection PointlessBooleanExpression
-        return Objects.equals(mDisabledComponentsWatched, that.mDisabledComponentsWatched)
-                && Objects.equals(mEnabledComponentsWatched, that.mEnabledComponentsWatched)
-                && mCeDataInode == that.mCeDataInode
-                && mInstalled == that.mInstalled
-                && mStopped == that.mStopped
-                && mNotLaunched == that.mNotLaunched
-                && mHidden == that.mHidden
-                && mDistractionFlags == that.mDistractionFlags
-                && mInstantApp == that.mInstantApp
-                && mVirtualPreload == that.mVirtualPreload
-                && mEnabledState == that.mEnabledState
-                && mInstallReason == that.mInstallReason
-                && mUninstallReason == that.mUninstallReason
-                && Objects.equals(mHarmfulAppWarning, that.mHarmfulAppWarning)
-                && Objects.equals(mLastDisableAppCaller, that.mLastDisableAppCaller)
-                && Objects.equals(mOverlayPaths, that.mOverlayPaths)
-                && Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths)
-                && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme)
-                && Objects.equals(mSuspendParams, that.mSuspendParams)
-                && Objects.equals(mComponentLabelIconOverrideMap,
-                        that.mComponentLabelIconOverrideMap)
-                && mFirstInstallTime == that.mFirstInstallTime
-                && Objects.equals(mWatchable, that.mWatchable);
+    @NonNull
+    public PackageUserStateImpl setWatchable(@NonNull Watchable watchable) {
+        mWatchable = watchable;
+        return this;
     }
 
-    @Override
-    public int hashCode() {
-        // You can override field hashCode logic by defining methods like:
-        // int fieldNameHashCode() { ... }
+    private boolean watchableEquals(Watchable other) {
+        // Ignore the Watchable for equality
+        return true;
+    }
 
-        int _hash = 1;
-        _hash = 31 * _hash + Objects.hashCode(mDisabledComponentsWatched);
-        _hash = 31 * _hash + Objects.hashCode(mEnabledComponentsWatched);
-        _hash = 31 * _hash + Long.hashCode(mCeDataInode);
-        _hash = 31 * _hash + Boolean.hashCode(mInstalled);
-        _hash = 31 * _hash + Boolean.hashCode(mStopped);
-        _hash = 31 * _hash + Boolean.hashCode(mNotLaunched);
-        _hash = 31 * _hash + Boolean.hashCode(mHidden);
-        _hash = 31 * _hash + mDistractionFlags;
-        _hash = 31 * _hash + Boolean.hashCode(mInstantApp);
-        _hash = 31 * _hash + Boolean.hashCode(mVirtualPreload);
-        _hash = 31 * _hash + mEnabledState;
-        _hash = 31 * _hash + mInstallReason;
-        _hash = 31 * _hash + mUninstallReason;
-        _hash = 31 * _hash + Objects.hashCode(mHarmfulAppWarning);
-        _hash = 31 * _hash + Objects.hashCode(mLastDisableAppCaller);
-        _hash = 31 * _hash + Objects.hashCode(mOverlayPaths);
-        _hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths);
-        _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme);
-        _hash = 31 * _hash + Objects.hashCode(mSuspendParams);
-        _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap);
-        _hash = 31 * _hash + Long.hashCode(mFirstInstallTime);
-        _hash = 31 * _hash + Objects.hashCode(mWatchable);
-        return _hash;
+    private int watchableHashCode() {
+        // Ignore the Watchable for equality
+        return 0;
+    }
+
+    private boolean snapshotEquals(SnapshotCache<PackageUserStateImpl> other) {
+        // Ignore the SnapshotCache for equality
+        return true;
+    }
+
+    private int snapshotHashCode() {
+        // Ignore the SnapshotCache for equality
+        return 0;
     }
 
 
@@ -736,11 +696,6 @@
     }
 
     @DataClass.Generated.Member
-    public @Nullable Watchable getWatchable() {
-        return mWatchable;
-    }
-
-    @DataClass.Generated.Member
     public @NonNull SnapshotCache<PackageUserStateImpl> getSnapshot() {
         return mSnapshot;
     }
@@ -778,11 +733,82 @@
         return this;
     }
 
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(PackageUserStateImpl other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        PackageUserStateImpl that = (PackageUserStateImpl) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Objects.equals(mDisabledComponentsWatched, that.mDisabledComponentsWatched)
+                && Objects.equals(mEnabledComponentsWatched, that.mEnabledComponentsWatched)
+                && mCeDataInode == that.mCeDataInode
+                && mInstalled == that.mInstalled
+                && mStopped == that.mStopped
+                && mNotLaunched == that.mNotLaunched
+                && mHidden == that.mHidden
+                && mDistractionFlags == that.mDistractionFlags
+                && mInstantApp == that.mInstantApp
+                && mVirtualPreload == that.mVirtualPreload
+                && mEnabledState == that.mEnabledState
+                && mInstallReason == that.mInstallReason
+                && mUninstallReason == that.mUninstallReason
+                && Objects.equals(mHarmfulAppWarning, that.mHarmfulAppWarning)
+                && Objects.equals(mLastDisableAppCaller, that.mLastDisableAppCaller)
+                && Objects.equals(mOverlayPaths, that.mOverlayPaths)
+                && Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths)
+                && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme)
+                && Objects.equals(mSuspendParams, that.mSuspendParams)
+                && Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap)
+                && mFirstInstallTime == that.mFirstInstallTime
+                && watchableEquals(that.mWatchable)
+                && snapshotEquals(that.mSnapshot);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Objects.hashCode(mDisabledComponentsWatched);
+        _hash = 31 * _hash + Objects.hashCode(mEnabledComponentsWatched);
+        _hash = 31 * _hash + Long.hashCode(mCeDataInode);
+        _hash = 31 * _hash + Boolean.hashCode(mInstalled);
+        _hash = 31 * _hash + Boolean.hashCode(mStopped);
+        _hash = 31 * _hash + Boolean.hashCode(mNotLaunched);
+        _hash = 31 * _hash + Boolean.hashCode(mHidden);
+        _hash = 31 * _hash + mDistractionFlags;
+        _hash = 31 * _hash + Boolean.hashCode(mInstantApp);
+        _hash = 31 * _hash + Boolean.hashCode(mVirtualPreload);
+        _hash = 31 * _hash + mEnabledState;
+        _hash = 31 * _hash + mInstallReason;
+        _hash = 31 * _hash + mUninstallReason;
+        _hash = 31 * _hash + Objects.hashCode(mHarmfulAppWarning);
+        _hash = 31 * _hash + Objects.hashCode(mLastDisableAppCaller);
+        _hash = 31 * _hash + Objects.hashCode(mOverlayPaths);
+        _hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths);
+        _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme);
+        _hash = 31 * _hash + Objects.hashCode(mSuspendParams);
+        _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap);
+        _hash = 31 * _hash + Long.hashCode(mFirstInstallTime);
+        _hash = 31 * _hash + watchableHashCode();
+        _hash = 31 * _hash + snapshotHashCode();
+        return _hash;
+    }
+
     @DataClass.Generated(
-            time = 1645040852569L,
+            time = 1668033772891L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
-            inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate  long mCeDataInode\nprivate  boolean mInstalled\nprivate  boolean mStopped\nprivate  boolean mNotLaunched\nprivate  boolean mHidden\nprivate  int mDistractionFlags\nprivate  boolean mInstantApp\nprivate  boolean mVirtualPreload\nprivate  int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate  long mFirstInstallTime\nprivate final @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate  void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @java.lang.Override boolean equals(java.lang.Object)\npublic @java.lang.Override int hashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+            inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate  long mCeDataInode\nprivate  boolean mInstalled\nprivate  boolean mStopped\nprivate  boolean mNotLaunched\nprivate  boolean mHidden\nprivate  int mDistractionFlags\nprivate  boolean mInstantApp\nprivate  boolean mVirtualPreload\nprivate  int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate  long mFirstInstallTime\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate  void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate  boolean watchableEquals(com.android.server.utils.Watchable)\nprivate  int watchableHashCode()\nprivate  boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate  int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
index e019215..1826f7a 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
@@ -98,8 +98,8 @@
     boolean isSupportsSizeChanges();
 
     /**
-     * Gets the category of the target display this activity is supposed to run on.
+     * Gets the required category of the display this activity is supposed to run on.
      */
     @Nullable
-    String getTargetDisplayCategory();
+    String getRequiredDisplayCategory();
 }
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
index 278e547..68d5428 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
@@ -97,7 +97,7 @@
     private ActivityInfo.WindowLayout windowLayout;
 
     @Nullable
-    private String mTargetDisplayCategory;
+    private String mRequiredDisplayCategory;
 
     public ParsedActivityImpl(ParsedActivityImpl other) {
         super(other);
@@ -125,7 +125,7 @@
         this.colorMode = other.colorMode;
         this.windowLayout = other.windowLayout;
         this.mKnownActivityEmbeddingCerts = other.mKnownActivityEmbeddingCerts;
-        this.mTargetDisplayCategory = other.mTargetDisplayCategory;
+        this.mRequiredDisplayCategory = other.mRequiredDisplayCategory;
     }
 
     /**
@@ -193,7 +193,7 @@
         alias.requestedVrComponent = target.getRequestedVrComponent();
         alias.setDirectBootAware(target.isDirectBootAware());
         alias.setProcessName(target.getProcessName());
-        alias.setTargetDisplayCategory(target.getTargetDisplayCategory());
+        alias.setRequiredDisplayCategory(target.getRequiredDisplayCategory());
         return alias;
 
         // Not all attributes from the target ParsedActivity are copied to the alias.
@@ -321,7 +321,7 @@
             dest.writeBoolean(false);
         }
         sForStringSet.parcel(this.mKnownActivityEmbeddingCerts, dest, flags);
-        dest.writeString8(this.mTargetDisplayCategory);
+        dest.writeString8(this.mRequiredDisplayCategory);
     }
 
     public ParsedActivityImpl() {
@@ -356,7 +356,7 @@
             windowLayout = new ActivityInfo.WindowLayout(in);
         }
         this.mKnownActivityEmbeddingCerts = sForStringSet.unparcel(in);
-        this.mTargetDisplayCategory = in.readString8();
+        this.mRequiredDisplayCategory = in.readString8();
     }
 
     @NonNull
@@ -414,7 +414,7 @@
             int rotationAnimation,
             int colorMode,
             @Nullable ActivityInfo.WindowLayout windowLayout,
-            @Nullable String targetDisplayCategory) {
+            @Nullable String requiredDisplayCategory) {
         this.theme = theme;
         this.uiOptions = uiOptions;
         this.targetActivity = targetActivity;
@@ -439,7 +439,7 @@
         this.rotationAnimation = rotationAnimation;
         this.colorMode = colorMode;
         this.windowLayout = windowLayout;
-        this.mTargetDisplayCategory = targetDisplayCategory;
+        this.mRequiredDisplayCategory = requiredDisplayCategory;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -560,8 +560,8 @@
     }
 
     @DataClass.Generated.Member
-    public @Nullable String getTargetDisplayCategory() {
-        return mTargetDisplayCategory;
+    public @Nullable String getRequiredDisplayCategory() {
+        return mRequiredDisplayCategory;
     }
 
     @DataClass.Generated.Member
@@ -691,16 +691,16 @@
     }
 
     @DataClass.Generated.Member
-    public @NonNull ParsedActivityImpl setTargetDisplayCategory(@NonNull String value) {
-        mTargetDisplayCategory = value;
+    public @NonNull ParsedActivityImpl setRequiredDisplayCategory(@NonNull String value) {
+        mRequiredDisplayCategory = value;
         return this;
     }
 
     @DataClass.Generated(
-            time = 1664805688714L,
+            time = 1669437519576L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java",
-            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mTargetDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.server.pm.pkg.component.ParsedActivity)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic  void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.server.pm.pkg.component.ParsedActivity)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic  void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
index 305062b..ea791e1 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
@@ -220,17 +220,17 @@
                 pkg.setVisibleToInstantApps(true);
             }
 
-            String targetDisplayCategory = sa.getNonConfigurationString(
-                    R.styleable.AndroidManifestActivity_targetDisplayCategory, 0);
+            String requiredDisplayCategory = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestActivity_requiredDisplayCategory, 0);
 
-            if (targetDisplayCategory != null
-                    && FrameworkParsingPackageUtils.validateName(targetDisplayCategory,
+            if (requiredDisplayCategory != null
+                    && FrameworkParsingPackageUtils.validateName(requiredDisplayCategory,
                     false /* requireSeparator */, false /* requireFilename */) != null) {
-                return input.error("targetDisplayCategory attribute can only consists of "
-                        + "alphanumeric characters, '_', and '.'");
+                return input.error("requiredDisplayCategory attribute can only consist "
+                        + "of alphanumeric characters, '_', and '.'");
             }
 
-            activity.setTargetDisplayCategory(targetDisplayCategory);
+            activity.setRequiredDisplayCategory(requiredDisplayCategory);
 
             return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, receiver,
                     false /*isAlias*/, visibleToEphemeral, input,
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 951ddfa..4a8ef96 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -40,6 +40,8 @@
     private final Function<String, PackageSetting> mActiveStateFunction;
     private final Function<String, PackageSetting> mDisabledStateFunction;
 
+    private final ArraySet<PackageSetting> mChangedStates = new ArraySet<>();
+
     public PackageStateMutator(@NonNull Function<String, PackageSetting> activeStateFunction,
             @NonNull Function<String, PackageSetting> disabledStateFunction) {
         mActiveStateFunction = activeStateFunction;
@@ -52,23 +54,23 @@
 
     @NonNull
     public PackageStateWrite forPackage(@NonNull String packageName) {
-        return mStateWrite.setState(mActiveStateFunction.apply(packageName));
+        return setState(mActiveStateFunction.apply(packageName));
     }
 
     @Nullable
     public PackageStateWrite forPackageNullable(@NonNull String packageName) {
         final PackageSetting packageState = mActiveStateFunction.apply(packageName);
-        mStateWrite.setState(packageState);
+        setState(packageState);
         if (packageState == null) {
             return null;
         }
 
-        return mStateWrite.setState(packageState);
+        return setState(packageState);
     }
 
     @NonNull
     public PackageStateWrite forDisabledSystemPackage(@NonNull String packageName) {
-        return mStateWrite.setState(mDisabledStateFunction.apply(packageName));
+        return setState(mDisabledStateFunction.apply(packageName));
     }
 
     @Nullable
@@ -78,7 +80,7 @@
             return null;
         }
 
-        return mStateWrite.setState(packageState);
+        return setState(packageState);
     }
 
     @NonNull
@@ -109,6 +111,21 @@
         }
     }
 
+    public void onFinished() {
+        for (int index = 0; index < mChangedStates.size(); index++) {
+            mChangedStates.valueAt(index).onChanged();
+        }
+    }
+
+    @NonNull
+    private StateWriteWrapper setState(@Nullable PackageSetting state) {
+        // State can be nullable because this infrastructure no-ops on non-existent states
+        if (state != null) {
+            mChangedStates.add(state);
+        }
+        return mStateWrite.setState(state);
+    }
+
     public static class InitialState {
 
         private final int mPackageSequence;
@@ -173,8 +190,11 @@
         @NonNull
         @Override
         public PackageUserStateWrite userState(int userId) {
-            return mUserStateWrite.setStates(
-                    mState == null ? null : mState.getOrCreateUserState(userId));
+            var userState = mState == null ? null : mState.getOrCreateUserState(userId);
+            if (userState != null) {
+                userState.setWatchable(mState);
+            }
+            return mUserStateWrite.setStates(userState);
         }
 
         @Override
@@ -263,9 +283,10 @@
 
         @NonNull
         @Override
-        public PackageStateWrite setInstaller(@NonNull String installerPackageName) {
+        public PackageStateWrite setInstaller(@Nullable String installerPackageName,
+                int installerPackageUid) {
             if (mState != null) {
-                mState.setInstallerPackageName(installerPackageName);
+                mState.setInstallerPackage(installerPackageName, installerPackageUid);
             }
             return this;
         }
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
index 1ac0b05..dc9cd3b 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
@@ -56,5 +56,5 @@
     PackageStateWrite setOverrideSeInfo(@Nullable String newSeInfo);
 
     @NonNull
-    PackageStateWrite setInstaller(@NonNull String installerPackageName);
+    PackageStateWrite setInstaller(@Nullable String installerPackageName, int installerPackageUid);
 }
diff --git a/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java b/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
index b2080b2..90a0c7c 100644
--- a/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
+++ b/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
@@ -16,7 +16,6 @@
 
 package com.android.server.pm.snapshot;
 
-import android.annotation.SystemApi;
 import android.content.pm.PackageManagerInternal;
 
 import com.android.server.pm.Computer;
@@ -32,6 +31,5 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface PackageDataSnapshot {
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 595c34c..f80ead6 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -1209,6 +1209,7 @@
     public void printOwnersForPackage(@NonNull IndentingPrintWriter writer,
             @Nullable String packageName, @Nullable @UserIdInt Integer userId)
             throws NameNotFoundException {
+        mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
         final Computer snapshot = mConnection.snapshot();
         synchronized (mLock) {
             if (packageName == null) {
@@ -1257,6 +1258,7 @@
     @Override
     public void printOwnersForDomains(@NonNull IndentingPrintWriter writer,
             @NonNull List<String> domains, @Nullable @UserIdInt Integer userId) {
+        mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
         final Computer snapshot = mConnection.snapshot();
         synchronized (mLock) {
             int size = domains.size();
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index a6d148c..383249f 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -45,13 +45,11 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.function.DecFunction;
 import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.QuintConsumer;
 import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriFunction;
 import com.android.internal.util.function.UndecFunction;
 import com.android.server.LocalServices;
 
@@ -257,14 +255,14 @@
     }
 
     @Override
-    public SyncNotedAppOp startProxyOperation(int code,
+    public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
             @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
             boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
             @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
-            @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean,
-                    Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
-        return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
+            @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean, Boolean, String,
+                    Boolean, Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
+        return superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(),
                 attributionSource.getPackageName(), attributionSource.getAttributionTag()),
                 attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
                 shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
@@ -280,10 +278,10 @@
     }
 
     @Override
-    public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
-            boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource,
-            Boolean, Void> superImpl) {
-        superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
+    public void finishProxyOperation(@NonNull IBinder clientId, int code,
+            @NonNull AttributionSource attributionSource, boolean skipProxyOperation,
+            @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean, Void> superImpl) {
+        superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(),
                 attributionSource.getPackageName(), attributionSource.getAttributionTag()),
                 attributionSource, skipProxyOperation);
     }
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index f8fcaff..91bb677 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -96,6 +96,7 @@
     private static final String CONFIG_FILE_NAME = "device_state_configuration.xml";
     private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS";
     private static final String FLAG_APP_INACCESSIBLE = "FLAG_APP_INACCESSIBLE";
+    private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY";
 
     /** Interface that allows reading the device state configuration. */
     interface ReadableConfig {
@@ -149,6 +150,8 @@
                                 case FLAG_APP_INACCESSIBLE:
                                     flags |= DeviceState.FLAG_APP_INACCESSIBLE;
                                     break;
+                                case FLAG_EMULATED_ONLY:
+                                    flags |= DeviceState.FLAG_EMULATED_ONLY;
                                 default:
                                     Slog.w(TAG, "Parsed unknown flag with name: "
                                             + configFlagString);
@@ -225,7 +228,13 @@
             }
             final Conditions conditions = stateConditions.get(i);
             if (conditions == null) {
-                mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
+                // If this state has the FLAG_EMULATED_ONLY flag on it, it should never be triggered
+                // by a physical hardware change, and should always return false for it's conditions
+                if (deviceStates.get(i).hasFlag(DeviceState.FLAG_EMULATED_ONLY)) {
+                    mStateConditions.put(state, FALSE_BOOLEAN_SUPPLIER);
+                } else {
+                    mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
+                }
                 continue;
             }
 
@@ -358,7 +367,7 @@
                 return;
             }
 
-            int newState = mOrderedStates[0].getIdentifier();
+            int newState = INVALID_DEVICE_STATE;
             for (int i = 0; i < mOrderedStates.length; i++) {
                 int state = mOrderedStates[i].getIdentifier();
                 if (DEBUG) {
@@ -369,13 +378,14 @@
                 try {
                     conditionSatisfied = mStateConditions.get(state).getAsBoolean();
                 } catch (IllegalStateException e) {
-                    // Failed to compute the current state based on current available data. Return
+                    // Failed to compute the current state based on current available data. Continue
                     // with the expectation that notifyDeviceStateChangedIfNeeded() will be called
-                    // when a callback with the missing data is triggered.
+                    // when a callback with the missing data is triggered. May trigger another state
+                    // change if another state is satisfied currently.
                     if (DEBUG) {
                         Slog.d(TAG, "Unable to check current state", e);
                     }
-                    return;
+                    continue;
                 }
 
                 if (conditionSatisfied) {
@@ -386,8 +396,12 @@
                     break;
                 }
             }
+            if (newState == INVALID_DEVICE_STATE) {
+                Slog.e(TAG, "No declared device states match any of the required conditions.");
+                dumpSensorValues();
+            }
 
-            if (newState != mLastReportedState) {
+            if (newState != INVALID_DEVICE_STATE && newState != mLastReportedState) {
                 mLastReportedState = newState;
                 stateToReport = newState;
             }
@@ -583,6 +597,19 @@
         return null;
     }
 
+    @GuardedBy("mLock")
+    private void dumpSensorValues() {
+        Slog.i(TAG, "Sensor values:");
+        for (Sensor sensor : mLatestSensorEvent.keySet()) {
+            SensorEvent sensorEvent = mLatestSensorEvent.get(sensor);
+            if (sensorEvent != null) {
+                Slog.i(TAG, sensor.getName() + ": " + Arrays.toString(sensorEvent.values));
+            } else {
+                Slog.i(TAG, sensor.getName() + ": null");
+            }
+        }
+    }
+
     /**
      * Tries to parse the provided file into a {@link DeviceStateConfig} object. Returns
      * {@code null} if the file could not be successfully parsed.
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index ffb652e..d6cac33 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -58,6 +58,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManagerInternal.PackageListObserver;
 import android.content.pm.PermissionInfo;
+import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.os.Build;
 import android.os.Bundle;
@@ -78,7 +79,6 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.LongSparseLongArray;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 
@@ -146,7 +146,7 @@
      * scheduled for a package/user.
      */
     @GuardedBy("mLock")
-    private final ArraySet<Pair<String, Integer>> mIsPackageSyncsScheduled = new ArraySet<>();
+    private final ArraySet<UserPackage> mIsPackageSyncsScheduled = new ArraySet<>();
 
     /**
      * Whether an async {@link #resetAppOpPermissionsIfNotRequestedForUid} is currently
@@ -223,14 +223,16 @@
                 this::synchronizePackagePermissionsAndAppOpsAsyncForUser);
 
         mAppOpsCallback = new IAppOpsCallback.Stub() {
-            public void opChanged(int op, int uid, String packageName) {
-                synchronizePackagePermissionsAndAppOpsAsyncForUser(packageName,
-                        UserHandle.getUserId(uid));
+            public void opChanged(int op, int uid, @Nullable String packageName) {
+                if (packageName != null) {
+                    synchronizePackagePermissionsAndAppOpsAsyncForUser(packageName,
+                            UserHandle.getUserId(uid));
+                }
                 resetAppOpPermissionsIfNotRequestedForUidAsync(uid);
             }
         };
 
-        final ArrayList<PermissionInfo> dangerousPerms =
+        final List<PermissionInfo> dangerousPerms =
                 mPermissionManagerInternal.getAllPermissionsWithProtection(
                         PermissionInfo.PROTECTION_DANGEROUS);
         try {
@@ -372,7 +374,7 @@
             @UserIdInt int changedUserId) {
         if (isStarted(changedUserId)) {
             synchronized (mLock) {
-                if (mIsPackageSyncsScheduled.add(new Pair<>(packageName, changedUserId))) {
+                if (mIsPackageSyncsScheduled.add(UserPackage.of(changedUserId, packageName))) {
                     // TODO(b/165030092): migrate this to PermissionThread.getHandler().
                     // synchronizePackagePermissionsAndAppOpsForUser is a heavy operation.
                     // Dispatched on a PermissionThread, it interferes with user switch.
@@ -640,7 +642,7 @@
     private void synchronizePackagePermissionsAndAppOpsForUser(@NonNull String packageName,
             @UserIdInt int userId) {
         synchronized (mLock) {
-            mIsPackageSyncsScheduled.remove(new Pair<>(packageName, userId));
+            mIsPackageSyncsScheduled.remove(UserPackage.of(userId, packageName));
         }
 
         if (DEBUG) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 98b5c1b..2f0f88a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -151,6 +151,7 @@
 import android.provider.DeviceConfig;
 import android.provider.MediaStore;
 import android.provider.Settings;
+import android.provider.Settings.Secure;
 import android.service.dreams.DreamManagerInternal;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
@@ -516,6 +517,7 @@
     int mDoublePressOnStemPrimaryBehavior;
     int mTriplePressOnStemPrimaryBehavior;
     int mLongPressOnStemPrimaryBehavior;
+    boolean mStylusButtonsDisabled = false;
     boolean mHasSoftInput = false;
     boolean mHapticTextHandleEnabled;
     boolean mUseTvRouting;
@@ -771,6 +773,9 @@
             resolver.registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.POWER_BUTTON_SUPPRESSION_DELAY_AFTER_GESTURE_WAKE), false, this,
                     UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
+                    Settings.Secure.STYLUS_BUTTONS_DISABLED), false, this,
+                    UserHandle.USER_ALL);
             updateSettings();
         }
 
@@ -2560,6 +2565,9 @@
                     Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
                     mContext.getResources().getInteger(
                             com.android.internal.R.integer.config_keyChordPowerVolumeUp));
+
+            mStylusButtonsDisabled = Settings.Secure.getIntForUser(resolver,
+                    Secure.STYLUS_BUTTONS_DISABLED, 0, UserHandle.USER_CURRENT) == 1;
         }
         if (updateRotation) {
             updateRotation(true);
@@ -2912,6 +2920,27 @@
                     return key_consumed;
                 }
                 break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                    StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+                    if (statusbar != null) {
+                        statusbar.goToFullscreenFromSplit();
+                    }
+                    return key_consumed;
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                    enterStageSplitFromRunningApp(true /* leftOrTop */);
+                    return key_consumed;
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                    enterStageSplitFromRunningApp(false /* leftOrTop */);
+                    return key_consumed;
+                }
+                break;
             case KeyEvent.KEYCODE_SLASH:
                 if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) {
                     toggleKeyboardShortcutsMenu(event.getDeviceId());
@@ -3566,6 +3595,13 @@
         }
     }
 
+    private void enterStageSplitFromRunningApp(boolean leftOrTop) {
+        StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+        if (statusbar != null) {
+            statusbar.enterStageSplitFromRunningApp(leftOrTop);
+        }
+    }
+
     void launchHomeFromHotKey(int displayId) {
         launchHomeFromHotKey(displayId, true /* awakenFromDreams */, true /*respectKeyguard*/);
     }
@@ -3589,7 +3625,12 @@
                     @Override
                     public void onKeyguardExitResult(boolean success) {
                         if (success) {
-                            startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
+                            final long origId = Binder.clearCallingIdentity();
+                            try {
+                                startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
+                            } finally {
+                                Binder.restoreCallingIdentity(origId);
+                            }
                         }
                     }
                 });
@@ -4178,7 +4219,9 @@
             case KeyEvent.KEYCODE_DEMO_APP_3:
             case KeyEvent.KEYCODE_DEMO_APP_4: {
                 // TODO(b/254604589): Dispatch KeyEvent to System UI.
-                sendSystemKeyToStatusBarAsync(keyCode);
+                if (!mStylusButtonsDisabled) {
+                    sendSystemKeyToStatusBarAsync(keyCode);
+                }
 
                 // Just drop if keys are not intercepted for direct key.
                 result &= ~ACTION_PASS_TO_USER;
@@ -4203,11 +4246,13 @@
             wakeUpFromWakeKey(event);
         }
 
-        if ((result & ACTION_PASS_TO_USER) != 0) {
+        if ((result & ACTION_PASS_TO_USER) != 0 && !mPerDisplayFocusEnabled
+                && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
             // If the key event is targeted to a specific display, then the user is interacting with
-            // that display. Therefore, give focus to the display that the user is interacting with.
-            if (!mPerDisplayFocusEnabled
-                    && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
+            // that display. Therefore, give focus to the display that the user is interacting with,
+            // unless that display maintains its own focus.
+            Display display = mDisplayManager.getDisplay(displayId);
+            if ((display.getFlags() & Display.FLAG_OWN_FOCUS) == 0) {
                 // An event is targeting a non-focused display. Move the display to top so that
                 // it can become the focused display to interact with the user.
                 // This should be done asynchronously, once the focus logic is fully moved to input
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
index 8582f54..2d76c50 100644
--- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -127,7 +127,7 @@
      */
     public void notifyPowerPressed() {
         Log.i(TAG, "notifyPowerPressed");
-        if (mFingerprintManager == null) {
+        if (mFingerprintManager == null && mSideFpsEventHandlerReady.get()) {
             mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
         }
         if (mFingerprintManager == null) {
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 7737421..85f1357 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -200,6 +200,9 @@
             if (!mKeyguardState.enabled) {
                 mKeyguardService.setKeyguardEnabled(mKeyguardState.enabled);
             }
+            if (mKeyguardState.dreaming) {
+                mKeyguardService.onDreamingStarted();
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 431cf38..1c4e143 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -324,11 +324,6 @@
         return mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DIM;
     }
 
-    public boolean isPolicyVrLocked() {
-        return mDisplayPowerRequest.isVr();
-
-    }
-
     public boolean isBrightOrDimLocked() {
         return mDisplayPowerRequest.isBrightOrDim();
     }
@@ -382,7 +377,7 @@
 
     @VisibleForTesting
     int getDesiredScreenPolicyLocked(boolean quiescent, boolean dozeAfterScreenOff,
-            boolean vrModeEnabled, boolean bootCompleted, boolean screenBrightnessBoostInProgress) {
+            boolean bootCompleted, boolean screenBrightnessBoostInProgress) {
         final int wakefulness = getWakefulnessLocked();
         final int wakeLockSummary = getWakeLockSummaryLocked();
         if (wakefulness == WAKEFULNESS_ASLEEP || quiescent) {
@@ -398,13 +393,6 @@
             // doze after screen off.  This causes the screen off transition to be skipped.
         }
 
-        // It is important that POLICY_VR check happens after the wakefulness checks above so
-        // that VR-mode does not prevent displays from transitioning to the correct state when
-        // dozing or sleeping.
-        if (vrModeEnabled) {
-            return DisplayPowerRequest.POLICY_VR;
-        }
-
         if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
                 || !bootCompleted
                 || (getUserActivitySummaryLocked() & USER_ACTIVITY_SCREEN_BRIGHT) != 0
@@ -423,10 +411,10 @@
             boolean useProximitySensor, boolean boostScreenBrightness, int dozeScreenState,
             float dozeScreenBrightness, boolean overrideDrawWakeLock,
             PowerSaveState powerSaverState, boolean quiescent, boolean dozeAfterScreenOff,
-            boolean vrModeEnabled, boolean bootCompleted, boolean screenBrightnessBoostInProgress,
+            boolean bootCompleted, boolean screenBrightnessBoostInProgress,
             boolean waitForNegativeProximity) {
         mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(quiescent, dozeAfterScreenOff,
-                vrModeEnabled, bootCompleted, screenBrightnessBoostInProgress);
+                bootCompleted, screenBrightnessBoostInProgress);
         mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
         mDisplayPowerRequest.useAutoBrightness = autoBrightness;
         mDisplayPowerRequest.useProximitySensor = useProximitySensor;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index d8b1120..6e3c827 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -90,8 +90,6 @@
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.service.dreams.DreamManagerInternal;
-import android.service.vr.IVrManager;
-import android.service.vr.IVrStateCallbacks;
 import android.sysprop.InitProperties;
 import android.sysprop.PowerProperties;
 import android.util.ArrayMap;
@@ -196,8 +194,6 @@
     private static final int DIRTY_SCREEN_BRIGHTNESS_BOOST = 1 << 11;
     // Dirty bit: sQuiescent changed
     private static final int DIRTY_QUIESCENT = 1 << 12;
-    // Dirty bit: VR Mode enabled changed
-    private static final int DIRTY_VR_MODE_CHANGED = 1 << 13;
     // Dirty bit: attentive timer may have timed out
     private static final int DIRTY_ATTENTIVE = 1 << 14;
     // Dirty bit: display group wakefulness has changed
@@ -423,6 +419,9 @@
     // The current battery level percentage.
     private int mBatteryLevel;
 
+    // The amount of battery drained while the device has been in a dream state.
+    private int mDreamsBatteryLevelDrain;
+
     // True if updatePowerStateLocked() is already in progress.
     // TODO(b/215518989): Remove this once transactions are in place
     private boolean mUpdatePowerStateInProgress;
@@ -455,11 +454,6 @@
     @GuardedBy("mEnhancedDischargeTimeLock")
     private boolean mEnhancedDischargePredictionIsPersonalized;
 
-    // The battery level percentage at the time the dream started.
-    // This is used to terminate a dream and go to sleep if the battery is
-    // draining faster than it is charging and the user activity timeout has expired.
-    private int mBatteryLevelWhenDreamStarted;
-
     // The current dock state.
     private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
 
@@ -472,9 +466,6 @@
     // True if the device should wake up when plugged or unplugged.
     private boolean mWakeUpWhenPluggedOrUnpluggedConfig;
 
-    // True if the device should keep dreaming when undocked.
-    private boolean mKeepDreamingWhenUndockingConfig;
-
     // True if the device should wake up when plugged or unplugged in theater mode.
     private boolean mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig;
 
@@ -585,9 +576,6 @@
     public final float mScreenBrightnessDefault;
     public final float mScreenBrightnessDoze;
     public final float mScreenBrightnessDim;
-    public final float mScreenBrightnessMinimumVr;
-    public final float mScreenBrightnessMaximumVr;
-    public final float mScreenBrightnessDefaultVr;
 
     // Value we store for tracking face down behavior.
     private boolean mIsFaceDown = false;
@@ -671,9 +659,6 @@
     // True if double tap to wake is enabled
     private boolean mDoubleTapWakeEnabled;
 
-    // True if we are currently in VR Mode.
-    private boolean mIsVrModeEnabled;
-
     // True if we in the process of performing a forceSuspend
     private boolean mForceSuspendActive;
 
@@ -681,6 +666,19 @@
     // but the DreamService has not yet been told to start (it's an async process).
     private boolean mDozeStartInProgress;
 
+    // Whether to keep dreaming when the device is undocked.
+    private boolean mKeepDreamingWhenUndocked;
+
+    private final class DreamManagerStateListener implements
+            DreamManagerInternal.DreamManagerStateListener {
+        @Override
+        public void onKeepDreamingWhenUndockedChanged(boolean keepDreaming) {
+            synchronized (mLock) {
+                mKeepDreamingWhenUndocked = keepDreaming;
+            }
+        }
+    }
+
     private final class PowerGroupWakefulnessChangeListener implements
             PowerGroup.PowerGroupListener {
         @GuardedBy("mLock")
@@ -1053,7 +1051,7 @@
         super(context);
 
         mContext = context;
-        mBinderService = new BinderService();
+        mBinderService = new BinderService(mContext);
         mLocalService = new LocalService();
         mNativeWrapper = injector.createNativeWrapper();
         mSystemProperties = injector.createSystemPropertiesWrapper();
@@ -1137,29 +1135,6 @@
             mScreenBrightnessDim = dim;
         }
 
-        final float vrMin = mContext.getResources().getFloat(com.android.internal.R.dimen
-                .config_screenBrightnessSettingForVrMinimumFloat);
-        final float vrMax = mContext.getResources().getFloat(com.android.internal.R.dimen
-                .config_screenBrightnessSettingForVrMaximumFloat);
-        final float vrDef = mContext.getResources().getFloat(com.android.internal.R.dimen
-                .config_screenBrightnessSettingForVrDefaultFloat);
-        if (vrMin == INVALID_BRIGHTNESS_IN_CONFIG || vrMax == INVALID_BRIGHTNESS_IN_CONFIG
-                || vrDef == INVALID_BRIGHTNESS_IN_CONFIG) {
-            mScreenBrightnessMinimumVr = BrightnessSynchronizer.brightnessIntToFloat(
-                    mContext.getResources().getInteger(com.android.internal.R.integer
-                            .config_screenBrightnessForVrSettingMinimum));
-            mScreenBrightnessMaximumVr = BrightnessSynchronizer.brightnessIntToFloat(
-                    mContext.getResources().getInteger(com.android.internal.R.integer
-                            .config_screenBrightnessForVrSettingMaximum));
-            mScreenBrightnessDefaultVr = BrightnessSynchronizer.brightnessIntToFloat(
-                    mContext.getResources().getInteger(com.android.internal.R.integer
-                            .config_screenBrightnessForVrSettingDefault));
-        } else {
-            mScreenBrightnessMinimumVr = vrMin;
-            mScreenBrightnessMaximumVr = vrMax;
-            mScreenBrightnessDefaultVr = vrDef;
-        }
-
         synchronized (mLock) {
             mBootingSuspendBlocker =
                     mInjector.createSuspendBlocker(this, "PowerManagerService.Booting");
@@ -1285,6 +1260,9 @@
                     new DisplayGroupPowerChangeListener();
             mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener);
 
+            // This DreamManager method does not acquire a lock, so it should be safe to call.
+            mDreamManager.registerDreamManagerStateListener(new DreamManagerStateListener());
+
             mWirelessChargerDetector = mInjector.createWirelessChargerDetector(sensorManager,
                     mInjector.createSuspendBlocker(
                             this, "PowerManagerService.WirelessChargerDetector"),
@@ -1362,14 +1340,6 @@
         resolver.registerContentObserver(Settings.Global.getUriFor(
                 Settings.Global.DEVICE_DEMO_MODE),
                 false, mSettingsObserver, UserHandle.USER_SYSTEM);
-        IVrManager vrManager = IVrManager.Stub.asInterface(getBinderService(Context.VR_SERVICE));
-        if (vrManager != null) {
-            try {
-                vrManager.registerListener(mVrStateCallbacks);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to register VR mode state listener: " + e);
-            }
-        }
 
         // Register for broadcasts from other components of the system.
         IntentFilter filter = new IntentFilter();
@@ -1402,8 +1372,6 @@
                 com.android.internal.R.bool.config_powerDecoupleInteractiveModeFromDisplay);
         mWakeUpWhenPluggedOrUnpluggedConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_unplugTurnsOnScreen);
-        mKeepDreamingWhenUndockingConfig = resources.getBoolean(
-                com.android.internal.R.bool.config_keepDreamingWhenUndocking);
         mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug);
         mSuspendWhenScreenOffDueToProximityConfig = resources.getBoolean(
@@ -2195,6 +2163,15 @@
                     if (sQuiescent) {
                         mDirty |= DIRTY_QUIESCENT;
                     }
+                    PowerGroup defaultGroup = mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP);
+                    if (defaultGroup.getWakefulnessLocked() == WAKEFULNESS_DOZING) {
+                        // Workaround for b/187231320 where the AOD can get stuck in a "half on /
+                        // half off" state when a non-default-group VirtualDisplay causes the global
+                        // wakefulness to change to awake, even though the default display is
+                        // dozing. We set sandman summoned to restart dreaming to get it unstuck.
+                        // TODO(b/255688811) - fix this so that AOD never gets interrupted at all.
+                        defaultGroup.setSandmanSummonedLocked(true);
+                    }
                     break;
 
                 case WAKEFULNESS_ASLEEP:
@@ -2458,15 +2435,25 @@
             final int oldPlugType = mPlugType;
             mIsPowered = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
             mPlugType = mBatteryManagerInternal.getPlugType();
+            final int oldBatteryLevel = mBatteryLevel;
             mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
             mBatteryLevelLow = mBatteryManagerInternal.getBatteryLevelLow();
+            final boolean isOverheat = mBatteryManagerInternal.getBatteryHealth()
+                    == BatteryManager.BATTERY_HEALTH_OVERHEAT;
 
             if (DEBUG_SPEW) {
                 Slog.d(TAG, "updateIsPoweredLocked: wasPowered=" + wasPowered
                         + ", mIsPowered=" + mIsPowered
                         + ", oldPlugType=" + oldPlugType
                         + ", mPlugType=" + mPlugType
-                        + ", mBatteryLevel=" + mBatteryLevel);
+                        + ", oldBatteryLevel=" + oldBatteryLevel
+                        + ", mBatteryLevel=" + mBatteryLevel
+                        + ", isOverheat=" + isOverheat);
+            }
+
+            if (!isOverheat && oldBatteryLevel > 0
+                    && getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING) {
+                mDreamsBatteryLevelDrain += (oldBatteryLevel - mBatteryLevel);
             }
 
             if (wasPowered != mIsPowered || oldPlugType != mPlugType) {
@@ -2518,7 +2505,7 @@
         }
 
         // Don't wake when undocking while dreaming if configured not to.
-        if (mKeepDreamingWhenUndockingConfig
+        if (mKeepDreamingWhenUndocked
                 && getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING
                 && wasPowered && !mIsPowered
                 && oldPlugType == BatteryManager.BATTERY_PLUGGED_DOCK) {
@@ -2820,7 +2807,7 @@
                         >= powerGroup.getLastWakeTimeLocked()) {
                     groupNextTimeout = lastUserActivityTimeNoChangeLights + screenOffTimeout;
                     if (now < groupNextTimeout) {
-                        if (powerGroup.isPolicyBrightLocked() || powerGroup.isPolicyVrLocked()) {
+                        if (powerGroup.isPolicyBrightLocked()) {
                             groupUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
                         } else if (powerGroup.isPolicyDimLocked()) {
                             groupUserActivitySummary = USER_ACTIVITY_SCREEN_DIM;
@@ -3289,7 +3276,7 @@
 
             // Remember the initial battery level when the dream started.
             if (startDreaming && isDreaming) {
-                mBatteryLevelWhenDreamStarted = mBatteryLevel;
+                mDreamsBatteryLevelDrain = 0;
                 if (wakefulness == WAKEFULNESS_DOZING) {
                     Slog.i(TAG, "Dozing...");
                 } else {
@@ -3310,16 +3297,15 @@
             if (wakefulness == WAKEFULNESS_DREAMING) {
                 if (isDreaming && canDreamLocked(powerGroup)) {
                     if (mDreamsBatteryLevelDrainCutoffConfig >= 0
-                            && mBatteryLevel < mBatteryLevelWhenDreamStarted
-                                    - mDreamsBatteryLevelDrainCutoffConfig
+                            && mDreamsBatteryLevelDrain > mDreamsBatteryLevelDrainCutoffConfig
                             && !isBeingKeptAwakeLocked(powerGroup)) {
                         // If the user activity timeout expired and the battery appears
                         // to be draining faster than it is charging then stop dreaming
                         // and go to sleep.
                         Slog.i(TAG, "Stopping dream because the battery appears to "
                                 + "be draining faster than it is charging.  "
-                                + "Battery level when dream started: "
-                                + mBatteryLevelWhenDreamStarted + "%.  "
+                                + "Battery level drained while dreaming: "
+                                + mDreamsBatteryLevelDrain + "%.  "
                                 + "Battery level now: " + mBatteryLevel + "%.");
                     } else {
                         return; // continue dreaming
@@ -3388,7 +3374,6 @@
                 || !mDreamsSupportedConfig
                 || !mDreamsEnabledSetting
                 || !(powerGroup.isBrightOrDimLocked())
-                || powerGroup.isPolicyVrLocked()
                 || (powerGroup.getUserActivitySummaryLocked() & (USER_ACTIVITY_SCREEN_BRIGHT
                 | USER_ACTIVITY_SCREEN_DIM | USER_ACTIVITY_SCREEN_DREAM)) == 0) {
             return false;
@@ -3433,8 +3418,8 @@
         final boolean oldPowerGroupsReady = areAllPowerGroupsReadyLocked();
         if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS
                 | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED
-                | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_VR_MODE_CHANGED |
-                DIRTY_QUIESCENT | DIRTY_DISPLAY_GROUP_WAKEFULNESS)) != 0) {
+                | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST
+                | DIRTY_QUIESCENT | DIRTY_DISPLAY_GROUP_WAKEFULNESS)) != 0) {
             if ((dirty & DIRTY_QUIESCENT) != 0) {
                 if (areAllPowerGroupsReadyLocked()) {
                     sQuiescent = false;
@@ -3469,7 +3454,7 @@
                         mDozeScreenBrightnessOverrideFromDreamManagerFloat,
                         mDrawWakeLockOverrideFromSidekick,
                         mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.SCREEN_BRIGHTNESS),
-                        sQuiescent, mDozeAfterScreenOff, mIsVrModeEnabled, mBootCompleted,
+                        sQuiescent, mDozeAfterScreenOff, mBootCompleted,
                         mScreenBrightnessBoostInProgress, mRequestWaitForNegativeProximity);
                 int wakefulness = powerGroup.getWakefulnessLocked();
                 if (DEBUG_SPEW) {
@@ -3487,7 +3472,6 @@
                             + ", useAutoBrightness=" + autoBrightness
                             + ", mScreenBrightnessBoostInProgress="
                             + mScreenBrightnessBoostInProgress
-                            + ", mIsVrModeEnabled= " + mIsVrModeEnabled
                             + ", sQuiescent=" + sQuiescent);
                 }
 
@@ -3535,7 +3519,7 @@
     }
 
     private boolean shouldBoostScreenBrightness() {
-        return !mIsVrModeEnabled && mScreenBrightnessBoostInProgress;
+        return mScreenBrightnessBoostInProgress;
     }
 
     private static boolean isValidBrightness(float value) {
@@ -3546,10 +3530,15 @@
     @GuardedBy("mLock")
     int getDesiredScreenPolicyLocked(int groupId) {
         return mPowerGroups.get(groupId).getDesiredScreenPolicyLocked(sQuiescent,
-                mDozeAfterScreenOff, mIsVrModeEnabled, mBootCompleted,
+                mDozeAfterScreenOff, mBootCompleted,
                 mScreenBrightnessBoostInProgress);
     }
 
+    @VisibleForTesting
+    int getDreamsBatteryLevelDrain() {
+        return mDreamsBatteryLevelDrain;
+    }
+
     private final DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks =
             new DisplayManagerInternal.DisplayPowerCallbacks() {
 
@@ -3622,8 +3611,7 @@
     @GuardedBy("mLock")
     private boolean shouldUseProximitySensorLocked() {
         // Use default display group for proximity sensor.
-        return !mIsVrModeEnabled
-                && (mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP).getWakeLockSummaryLocked()
+        return (mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP).getWakeLockSummaryLocked()
                         & WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0;
     }
 
@@ -4247,11 +4235,6 @@
         }
     }
 
-    @VisibleForTesting
-    void setVrModeEnabled(boolean enabled) {
-        mIsVrModeEnabled = enabled;
-    }
-
     private void setPowerBoostInternal(int boost, int durationMs) {
         // Maybe filter the event.
         mNativeWrapper.nativeSetPowerBoost(boost, durationMs);
@@ -4394,7 +4377,7 @@
             pw.println("  mIsPowered=" + mIsPowered);
             pw.println("  mPlugType=" + mPlugType);
             pw.println("  mBatteryLevel=" + mBatteryLevel);
-            pw.println("  mBatteryLevelWhenDreamStarted=" + mBatteryLevelWhenDreamStarted);
+            pw.println("  mDreamsBatteryLevelDrain=" + mDreamsBatteryLevelDrain);
             pw.println("  mDockState=" + mDockState);
             pw.println("  mStayOn=" + mStayOn);
             pw.println("  mProximityPositive=" + mProximityPositive);
@@ -4472,8 +4455,7 @@
                     + mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig);
             pw.println("  mTheaterModeEnabled="
                     + mTheaterModeEnabled);
-            pw.println("  mKeepDreamingWhenUndockingConfig="
-                    + mKeepDreamingWhenUndockingConfig);
+            pw.println("  mKeepDreamingWhenUndocked=" + mKeepDreamingWhenUndocked);
             pw.println("  mSuspendWhenScreenOffDueToProximityConfig="
                     + mSuspendWhenScreenOffDueToProximityConfig);
             pw.println("  mDreamsSupportedConfig=" + mDreamsSupportedConfig);
@@ -4522,7 +4504,6 @@
             pw.println("  mScreenBrightnessMaximum=" + mScreenBrightnessMaximum);
             pw.println("  mScreenBrightnessDefault=" + mScreenBrightnessDefault);
             pw.println("  mDoubleTapWakeEnabled=" + mDoubleTapWakeEnabled);
-            pw.println("  mIsVrModeEnabled=" + mIsVrModeEnabled);
             pw.println("  mForegroundProfile=" + mForegroundProfile);
             pw.println("  mUserId=" + mUserId);
 
@@ -4640,8 +4621,8 @@
             proto.write(PowerManagerServiceDumpProto.PLUG_TYPE, mPlugType);
             proto.write(PowerManagerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel);
             proto.write(
-                    PowerManagerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED,
-                    mBatteryLevelWhenDreamStarted);
+                    PowerManagerServiceDumpProto.BATTERY_LEVEL_DRAINED_WHILE_DREAMING,
+                    mDreamsBatteryLevelDrain);
             proto.write(PowerManagerServiceDumpProto.DOCK_STATE, mDockState);
             proto.write(PowerManagerServiceDumpProto.IS_STAY_ON, mStayOn);
             proto.write(PowerManagerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive);
@@ -4933,9 +4914,6 @@
             proto.write(
                     PowerServiceSettingsAndConfigurationDumpProto.IS_DOUBLE_TAP_WAKE_ENABLED,
                     mDoubleTapWakeEnabled);
-            proto.write(
-                    PowerServiceSettingsAndConfigurationDumpProto.IS_VR_MODE_ENABLED,
-                    mIsVrModeEnabled);
             proto.end(settingsAndConfigurationToken);
 
             final long attentiveTimeout = getAttentiveTimeoutLocked();
@@ -5064,21 +5042,6 @@
         }
     }
 
-    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
-        @Override
-        public void onVrStateChanged(boolean enabled) {
-            setPowerModeInternal(Mode.VR, enabled);
-
-            synchronized (mLock) {
-                if (mIsVrModeEnabled != enabled) {
-                    setVrModeEnabled(enabled);
-                    mDirty |= DIRTY_VR_MODE_CHANGED;
-                    updatePowerStateLocked();
-                }
-            }
-        }
-    };
-
     private final AmbientDisplaySuppressionChangedCallback mAmbientSuppressionChangedCallback =
             new AmbientDisplaySuppressionChangedCallback() {
                 @Override
@@ -5485,12 +5448,17 @@
 
     @VisibleForTesting
     final class BinderService extends IPowerManager.Stub {
+        private final PowerManagerShellCommand mShellCommand;
+
+        BinderService(Context context) {
+            mShellCommand = new PowerManagerShellCommand(context, this);
+        }
+
         @Override
         public void onShellCommand(FileDescriptor in, FileDescriptor out,
                 FileDescriptor err, String[] args, ShellCallback callback,
                 ResultReceiver resultReceiver) {
-            (new PowerManagerShellCommand(this)).exec(
-                    this, in, out, err, args, callback, resultReceiver);
+            mShellCommand.exec(this, in, out, err, args, callback, resultReceiver);
         }
 
         @Override // Binder call
@@ -5801,12 +5769,6 @@
                     return mScreenBrightnessDim;
                 case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE:
                     return mScreenBrightnessDoze;
-                case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR:
-                    return mScreenBrightnessMinimumVr;
-                case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR:
-                    return mScreenBrightnessMaximumVr;
-                case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR:
-                    return mScreenBrightnessDefaultVr;
                 default:
                     return PowerManager.BRIGHTNESS_INVALID_FLOAT;
             }
@@ -6590,7 +6552,6 @@
                 case Display.STATE_DOZE_SUSPEND:
                 case Display.STATE_ON_SUSPEND:
                 case Display.STATE_ON:
-                case Display.STATE_VR:
                     break;
                 default:
                     screenState = Display.STATE_UNKNOWN;
diff --git a/services/core/java/com/android/server/power/PowerManagerShellCommand.java b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
index a9b33ed..9439b76 100644
--- a/services/core/java/com/android/server/power/PowerManagerShellCommand.java
+++ b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
@@ -16,10 +16,15 @@
 
 package com.android.server.power;
 
+import android.content.Context;
 import android.content.Intent;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
 import android.os.ShellCommand;
+import android.util.SparseArray;
+import android.view.Display;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -27,9 +32,13 @@
 class PowerManagerShellCommand extends ShellCommand {
     private static final int LOW_POWER_MODE_ON = 1;
 
-    final PowerManagerService.BinderService mService;
+    private final Context mContext;
+    private final PowerManagerService.BinderService mService;
 
-    PowerManagerShellCommand(PowerManagerService.BinderService service) {
+    private SparseArray<WakeLock> mProxWakelocks = new SparseArray<>();
+
+    PowerManagerShellCommand(Context context, PowerManagerService.BinderService service) {
+        mContext = context;
         mService = service;
     }
 
@@ -52,6 +61,8 @@
                     return runSuppressAmbientDisplay();
                 case "list-ambient-display-suppression-tokens":
                     return runListAmbientDisplaySuppressionTokens();
+                case "set-prox":
+                    return runSetProx();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -117,6 +128,56 @@
 
         return 0;
     }
+
+    /** TODO: Consider updating this code to support all wakelock types. */
+    private int runSetProx() throws RemoteException {
+        PrintWriter pw = getOutPrintWriter();
+        final boolean acquire;
+        switch (getNextArgRequired().toLowerCase()) {
+            case "list":
+                pw.println("Wakelocks:");
+                pw.println(mProxWakelocks);
+                return 0;
+            case "acquire":
+                acquire = true;
+                break;
+            case "release":
+                acquire = false;
+                break;
+            default:
+                pw.println("Error: Allowed options are 'list' 'enable' and 'disable'.");
+                return -1;
+        }
+
+        int displayId = Display.INVALID_DISPLAY;
+        String displayOption = getNextArg();
+        if ("-d".equals(displayOption)) {
+            String idStr = getNextArg();
+            displayId = Integer.parseInt(idStr);
+            if (displayId < 0) {
+                pw.println("Error: Specified displayId (" + idStr + ") must a non-negative int.");
+                return -1;
+            }
+        }
+
+        int wakelockIndex = displayId + 1; // SparseArray doesn't support negative indexes
+        WakeLock wakelock = mProxWakelocks.get(wakelockIndex);
+        if (wakelock == null) {
+            PowerManager pm = mContext.getSystemService(PowerManager.class);
+            wakelock = pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
+                        "PowerManagerShellCommand[" + displayId + "]", displayId);
+            mProxWakelocks.put(wakelockIndex, wakelock);
+        }
+
+        if (acquire) {
+            wakelock.acquire();
+        } else {
+            wakelock.release();
+        }
+        pw.println(wakelock);
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -138,6 +199,11 @@
         pw.println("    ambient display");
         pw.println("  list-ambient-display-suppression-tokens");
         pw.println("    prints the tokens used to suppress ambient display");
+        pw.println("  set-prox [list|acquire|release] (-d <display_id>)");
+        pw.println("    Acquires the proximity sensor wakelock. Wakelock is associated with");
+        pw.println("    a specific display if specified. 'list' lists wakelocks previously");
+        pw.println("    created by set-prox including their held status.");
+
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
     }
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index dfa1281..0d13831 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -24,6 +24,7 @@
 import android.os.IBinder;
 import android.os.IHintManager;
 import android.os.IHintSession;
+import android.os.PerformanceHintManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.ArrayMap;
@@ -147,6 +148,8 @@
         private static native void nativeReportActualWorkDuration(
                 long halPtr, long[] actualDurationNanos, long[] timeStampNanos);
 
+        private static native void nativeSendHint(long halPtr, int hint);
+
         private static native long nativeGetHintSessionPreferredRate();
 
         /** Wrapper for HintManager.nativeInit */
@@ -186,6 +189,11 @@
                     timeStampNanos);
         }
 
+        /** Wrapper for HintManager.sendHint */
+        public void halSendHint(long halPtr, int hint) {
+            nativeSendHint(halPtr, hint);
+        }
+
         /** Wrapper for HintManager.nativeGetHintSessionPreferredRate */
         public long halGetHintSessionPreferredRate() {
             return nativeGetHintSessionPreferredRate();
@@ -475,6 +483,18 @@
             }
         }
 
+        @Override
+        public void sendHint(@PerformanceHintManager.Session.Hint int hint) {
+            synchronized (mLock) {
+                if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+                    return;
+                }
+                Preconditions.checkArgument(hint >= 0, "the hint ID the hint value should be"
+                        + " greater than zero.");
+                mNativeWrapper.halSendHint(mHalSessionPtr, hint);
+            }
+        }
+
         private void onProcStateChanged() {
             updateHintAllowed();
         }
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 916df89..af4fa85 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -11507,6 +11507,9 @@
 
         mHistory.reset();
 
+        // Store the empty state to disk to ensure consistency
+        writeSyncLocked();
+
         // Flush external data, gathering snapshots, but don't process it since it is pre-reset data
         mIgnoreNextExternalStats = true;
         mExternalSync.scheduleSync("reset", ExternalStatsSync.UPDATE_ON_RESET);
@@ -12226,6 +12229,7 @@
     @GuardedBy("this")
     private void incrementPerRatDataLocked(ModemActivityInfo deltaInfo, long elapsedRealtimeMs) {
         final int infoSize = deltaInfo.getSpecificInfoLength();
+
         if (infoSize == 1 && deltaInfo.getSpecificInfoRat(0)
                 == AccessNetworkConstants.AccessNetworkType.UNKNOWN
                 && deltaInfo.getSpecificInfoFrequencyRange(0)
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/power/stats/MobileRadioPowerCalculator.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
index 806ed64..2c7aea9 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
@@ -15,45 +15,79 @@
  */
 package com.android.server.power.stats;
 
+import android.annotation.Nullable;
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
 import android.telephony.CellSignalStrength;
+import android.telephony.ServiceState;
 import android.util.Log;
+import android.util.LongArrayQueue;
 import android.util.SparseArray;
 
 import com.android.internal.os.PowerProfile;
+import com.android.internal.power.ModemPowerProfile;
+
+import java.util.ArrayList;
 
 public class MobileRadioPowerCalculator extends PowerCalculator {
     private static final String TAG = "MobRadioPowerCalculator";
     private static final boolean DEBUG = PowerCalculator.DEBUG;
 
+    private static final double MILLIS_IN_HOUR = 1000.0 * 60 * 60;
+
     private static final int NUM_SIGNAL_STRENGTH_LEVELS =
             CellSignalStrength.getNumSignalStrengthLevels();
 
     private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0];
+    private static final int IGNORE = -1;
 
-    private final UsageBasedPowerEstimator mActivePowerEstimator;
+    private final UsageBasedPowerEstimator mActivePowerEstimator; // deprecated
     private final UsageBasedPowerEstimator[] mIdlePowerEstimators =
-            new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS];
-    private final UsageBasedPowerEstimator mScanPowerEstimator;
+            new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS]; // deprecated
+    private final UsageBasedPowerEstimator mScanPowerEstimator; // deprecated
+
+    @Nullable
+    private final UsageBasedPowerEstimator mSleepPowerEstimator;
+    @Nullable
+    private final UsageBasedPowerEstimator mIdlePowerEstimator;
+
+    private final PowerProfile mPowerProfile;
 
     private static class PowerAndDuration {
-        public long durationMs;
+        public long remainingDurationMs;
         public double remainingPowerMah;
         public long totalAppDurationMs;
         public double totalAppPowerMah;
-        public long signalDurationMs;
-        public long noCoverageDurationMs;
     }
 
     public MobileRadioPowerCalculator(PowerProfile profile) {
-        // Power consumption when radio is active
+        mPowerProfile = profile;
+
+        final double sleepDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
+                PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP,
+                Double.NaN);
+        if (Double.isNaN(sleepDrainRateMa)) {
+            mSleepPowerEstimator = null;
+        } else {
+            mSleepPowerEstimator = new UsageBasedPowerEstimator(sleepDrainRateMa);
+        }
+
+        final double idleDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
+                PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE,
+                Double.NaN);
+        if (Double.isNaN(idleDrainRateMa)) {
+            mIdlePowerEstimator = null;
+        } else {
+            mIdlePowerEstimator = new UsageBasedPowerEstimator(idleDrainRateMa);
+        }
+
+        // Instantiate legacy power estimators
         double powerRadioActiveMa =
-                profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, -1);
-        if (powerRadioActiveMa == -1) {
+                profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, Double.NaN);
+        if (Double.isNaN(powerRadioActiveMa)) {
             double sum = 0;
             sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
             for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
@@ -61,11 +95,10 @@
             }
             powerRadioActiveMa = sum / (NUM_SIGNAL_STRENGTH_LEVELS + 1);
         }
-
         mActivePowerEstimator = new UsageBasedPowerEstimator(powerRadioActiveMa);
 
-        // Power consumption when radio is on, but idle
-        if (profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, -1) != -1) {
+        if (!Double.isNaN(
+                profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, Double.NaN))) {
             for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
                 mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(
                         profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i));
@@ -95,6 +128,23 @@
 
         PowerAndDuration total = new PowerAndDuration();
 
+        final long totalConsumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
+        final int powerModel = getPowerModel(totalConsumptionUC, query);
+
+        final double totalActivePowerMah;
+        final ArrayList<UidBatteryConsumer.Builder> apps;
+        final LongArrayQueue appDurationsMs;
+        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+            // Measured energy is available, don't bother calculating power.
+            totalActivePowerMah = Double.NaN;
+            apps = null;
+            appDurationsMs = null;
+        } else {
+            totalActivePowerMah = calculateActiveModemPowerMah(batteryStats, rawRealtimeUs);
+            apps = new ArrayList();
+            appDurationsMs = new LongArrayQueue();
+        }
+
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                 builder.getUidBatteryConsumerBuilders();
         BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
@@ -110,132 +160,352 @@
                 }
             }
 
-            calculateApp(app, uid, total, query, keys);
+            // Sum and populate each app's active radio duration.
+            final long radioActiveDurationMs = calculateDuration(uid,
+                    BatteryStats.STATS_SINCE_CHARGED);
+            if (!app.isVirtualUid()) {
+                total.totalAppDurationMs += radioActiveDurationMs;
+            }
+            app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                    radioActiveDurationMs);
+
+            if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+                // Measured energy is available, populate the consumed power now.
+                final long appConsumptionUC = uid.getMobileRadioMeasuredBatteryConsumptionUC();
+                if (appConsumptionUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
+                    final double appConsumptionMah = uCtoMah(appConsumptionUC);
+                    if (!app.isVirtualUid()) {
+                        total.totalAppPowerMah += appConsumptionMah;
+                    }
+                    app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                            appConsumptionMah, powerModel);
+
+                    if (query.isProcessStateDataNeeded() && keys != null) {
+                        for (BatteryConsumer.Key key : keys) {
+                            final int processState = key.processState;
+                            if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                                // Already populated with the total across all process states
+                                continue;
+                            }
+                            final long consumptionInStateUc =
+                                    uid.getMobileRadioMeasuredBatteryConsumptionUC(processState);
+                            final double powerInStateMah = uCtoMah(consumptionInStateUc);
+                            app.setConsumedPower(key, powerInStateMah, powerModel);
+                        }
+                    }
+                }
+            } else {
+                // Cache the app and its active duration for later calculations.
+                apps.add(app);
+                appDurationsMs.addLast(radioActiveDurationMs);
+            }
         }
 
-        final long totalConsumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(totalConsumptionUC, query);
-        calculateRemaining(total, powerModel, batteryStats, rawRealtimeUs, totalConsumptionUC);
+        long totalActiveDurationMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
+                BatteryStats.STATS_SINCE_CHARGED) / 1000;
+        if (totalActiveDurationMs < total.totalAppDurationMs) {
+            totalActiveDurationMs = total.totalAppDurationMs;
+        }
+
+        if (powerModel != BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+            // Need to smear the calculated total active power across the apps based on app
+            // active durations.
+            final int appSize = apps.size();
+            for (int i = 0; i < appSize; i++) {
+                final UidBatteryConsumer.Builder app = apps.get(i);
+                final long activeDurationMs = appDurationsMs.get(i);
+
+                // Proportionally attribute radio power consumption based on active duration.
+                final double appConsumptionMah;
+                if (totalActiveDurationMs == 0.0) {
+                    appConsumptionMah = 0.0;
+                } else {
+                    appConsumptionMah =
+                            (totalActivePowerMah * activeDurationMs) / totalActiveDurationMs;
+                }
+
+                if (!app.isVirtualUid()) {
+                    total.totalAppPowerMah += appConsumptionMah;
+                }
+                app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                        appConsumptionMah, powerModel);
+
+                if (query.isProcessStateDataNeeded() && keys != null) {
+                    final BatteryStats.Uid uid = app.getBatteryStatsUid();
+                    for (BatteryConsumer.Key key : keys) {
+                        final int processState = key.processState;
+                        if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                            // Already populated with the total across all process states
+                            continue;
+                        }
+
+                        final long durationInStateMs =
+                                uid.getMobileRadioActiveTimeInProcessState(processState) / 1000;
+                        // Proportionally attribute per process state radio power consumption
+                        // based on time state duration.
+                        final double powerInStateMah;
+                        if (activeDurationMs == 0.0) {
+                            powerInStateMah = 0.0;
+                        } else {
+                            powerInStateMah =
+                                    (appConsumptionMah * durationInStateMs) / activeDurationMs;
+                        }
+                        app.setConsumedPower(key, powerInStateMah, powerModel);
+                    }
+                }
+            }
+        }
+
+        total.remainingDurationMs = totalActiveDurationMs - total.totalAppDurationMs;
+
+        // Calculate remaining power consumption.
+        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+            total.remainingPowerMah = uCtoMah(totalConsumptionUC) - total.totalAppPowerMah;
+            if (total.remainingPowerMah < 0) total.remainingPowerMah = 0;
+        } else {
+            // Smear unattributed active time and add it to the remaining power consumption.
+            total.remainingPowerMah +=
+                    (totalActivePowerMah * total.remainingDurationMs) / totalActiveDurationMs;
+
+            // Calculate the inactive modem power consumption.
+            final BatteryStats.ControllerActivityCounter modemActivity =
+                    batteryStats.getModemControllerActivity();
+            if (modemActivity != null && (mSleepPowerEstimator != null
+                    || mIdlePowerEstimator != null)) {
+                final long sleepDurationMs = modemActivity.getSleepTimeCounter().getCountLocked(
+                        BatteryStats.STATS_SINCE_CHARGED);
+                total.remainingPowerMah += mSleepPowerEstimator.calculatePower(sleepDurationMs);
+                final long idleDurationMs = modemActivity.getIdleTimeCounter().getCountLocked(
+                        BatteryStats.STATS_SINCE_CHARGED);
+                total.remainingPowerMah += mIdlePowerEstimator.calculatePower(idleDurationMs);
+            } else {
+                // Modem activity counters unavailable. Use legacy calculations for inactive usage.
+                final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
+                        BatteryStats.STATS_SINCE_CHARGED) / 1000;
+                total.remainingPowerMah += calcScanTimePowerMah(scanningTimeMs);
+                for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
+                    long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
+                            BatteryStats.STATS_SINCE_CHARGED) / 1000;
+                    total.remainingPowerMah += calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
+                }
+            }
+
+        }
 
         if (total.remainingPowerMah != 0 || total.totalAppPowerMah != 0) {
             builder.getAggregateBatteryConsumerBuilder(
-                    BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                            BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                            total.durationMs)
+                            total.remainingDurationMs + total.totalAppDurationMs)
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
                             total.remainingPowerMah + total.totalAppPowerMah, powerModel);
 
             builder.getAggregateBatteryConsumerBuilder(
-                    BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                            BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                            total.durationMs)
+                            total.totalAppDurationMs)
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
                             total.totalAppPowerMah, powerModel);
         }
     }
 
-    private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
-            PowerAndDuration total,
-            BatteryUsageStatsQuery query, BatteryConsumer.Key[] keys) {
-        final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED);
-        final long consumptionUC = u.getMobileRadioMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(consumptionUC, query);
-        final double powerMah = calculatePower(u, powerModel, radioActiveDurationMs, consumptionUC);
-
-        if (!app.isVirtualUid()) {
-            total.totalAppDurationMs += radioActiveDurationMs;
-            total.totalAppPowerMah += powerMah;
-        }
-
-        app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                        radioActiveDurationMs)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, powerMah,
-                        powerModel);
-
-        if (query.isProcessStateDataNeeded() && keys != null) {
-            for (BatteryConsumer.Key key: keys) {
-                final int processState = key.processState;
-                if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
-                    // Already populated with the total across all process states
-                    continue;
-                }
-
-                final long durationInStateMs =
-                        u.getMobileRadioActiveTimeInProcessState(processState) / 1000;
-                final long consumptionInStateUc =
-                        u.getMobileRadioMeasuredBatteryConsumptionUC(processState);
-                final double powerInStateMah = calculatePower(u, powerModel, durationInStateMs,
-                        consumptionInStateUc);
-                app.setConsumedPower(key, powerInStateMah, powerModel);
-            }
-        }
-    }
-
     private long calculateDuration(BatteryStats.Uid u, int statsType) {
         return u.getMobileRadioActiveTime(statsType) / 1000;
     }
 
-    private double calculatePower(BatteryStats.Uid u, @BatteryConsumer.PowerModel int powerModel,
-            long radioActiveDurationMs, long measuredChargeUC) {
-        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
-            return uCtoMah(measuredChargeUC);
+    private double calculateActiveModemPowerMah(BatteryStats bs, long elapsedRealtimeUs) {
+        final long elapsedRealtimeMs = elapsedRealtimeUs / 1000;
+        final int txLvlCount = CellSignalStrength.getNumSignalStrengthLevels();
+        double consumptionMah = 0.0;
+
+        if (DEBUG) {
+            Log.d(TAG, "Calculating radio power consumption at elapased real timestamp : "
+                    + elapsedRealtimeMs + " ms");
         }
 
-        if (radioActiveDurationMs > 0) {
-            return calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
+        boolean hasConstants = false;
+
+        for (int rat = 0; rat < BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
+            final int freqCount = rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR
+                    ? ServiceState.FREQUENCY_RANGE_COUNT : 1;
+            for (int freq = 0; freq < freqCount; freq++) {
+                for (int txLvl = 0; txLvl < txLvlCount; txLvl++) {
+                    final long txDurationMs = bs.getActiveTxRadioDurationMs(rat, freq, txLvl,
+                            elapsedRealtimeMs);
+                    if (txDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
+                        continue;
+                    }
+                    final double txConsumptionMah = calcTxStatePowerMah(rat, freq, txLvl,
+                            txDurationMs);
+                    if (Double.isNaN(txConsumptionMah)) {
+                        continue;
+                    }
+                    hasConstants = true;
+                    consumptionMah += txConsumptionMah;
+                }
+
+                final long rxDurationMs = bs.getActiveRxRadioDurationMs(rat, freq,
+                        elapsedRealtimeMs);
+                if (rxDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
+                    continue;
+                }
+                final double rxConsumptionMah = calcRxStatePowerMah(rat, freq, rxDurationMs);
+                if (Double.isNaN(rxConsumptionMah)) {
+                    continue;
+                }
+                hasConstants = true;
+                consumptionMah += rxConsumptionMah;
+            }
         }
-        return 0;
+
+        if (!hasConstants) {
+            final long radioActiveDurationMs = bs.getMobileRadioActiveTime(elapsedRealtimeUs,
+                    BatteryStats.STATS_SINCE_CHARGED) / 1000;
+            if (DEBUG) {
+                Log.d(TAG,
+                        "Failed to calculate radio power consumption. Reattempted with legacy "
+                                + "method. Radio active duration : "
+                                + radioActiveDurationMs + " ms");
+            }
+            if (radioActiveDurationMs > 0) {
+                consumptionMah = calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
+            } else {
+                consumptionMah = 0.0;
+            }
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "Total active radio power consumption calculated to be " + consumptionMah
+                    + " mAH.");
+        }
+
+        return consumptionMah;
     }
 
-    private void calculateRemaining(PowerAndDuration total,
-            @BatteryConsumer.PowerModel int powerModel, BatteryStats batteryStats,
-            long rawRealtimeUs, long totalConsumptionUC) {
-        long signalTimeMs = 0;
-        double powerMah = 0;
+    private static long buildModemPowerProfileKey(@ModemPowerProfile.ModemDrainType int drainType,
+            @BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange,
+            int txLevel) {
+        long key = PowerProfile.SUBSYSTEM_MODEM;
 
-        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
-            powerMah = uCtoMah(totalConsumptionUC) - total.totalAppPowerMah;
-            if (powerMah < 0) powerMah = 0;
+        // Attach Modem drain type to the key if specified.
+        if (drainType != IGNORE) {
+            key |= drainType;
         }
 
-        for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
-            long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
-                    BatteryStats.STATS_SINCE_CHARGED) / 1000;
-            if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
-                final double p = calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
-                if (DEBUG && p != 0) {
-                    Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
-                            + BatteryStats.formatCharge(p));
-                }
-                powerMah += p;
-            }
-            signalTimeMs += strengthTimeMs;
-            if (i == 0) {
-                total.noCoverageDurationMs = strengthTimeMs;
-            }
+        // Attach RadioAccessTechnology to the key if specified.
+        switch (rat) {
+            case IGNORE:
+                // do nothing
+                break;
+            case BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER:
+                key |= ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT;
+                break;
+            case BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE:
+                key |= ModemPowerProfile.MODEM_RAT_TYPE_LTE;
+                break;
+            case BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR:
+                key |= ModemPowerProfile.MODEM_RAT_TYPE_NR;
+                break;
+            default:
+                Log.w(TAG, "Unexpected RadioAccessTechnology : " + rat);
         }
 
-        final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED) / 1000;
-        long radioActiveTimeMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED) / 1000;
-        long remainingActiveTimeMs = radioActiveTimeMs - total.totalAppDurationMs;
-
-        if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
-            final double p = calcScanTimePowerMah(scanningTimeMs);
-            if (DEBUG && p != 0) {
-                Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs
-                        + " power=" + BatteryStats.formatCharge(p));
-            }
-            powerMah += p;
-
-            if (remainingActiveTimeMs > 0) {
-                powerMah += calcPowerFromRadioActiveDurationMah(remainingActiveTimeMs);
-            }
+        // Attach NR Frequency Range to the key if specified.
+        switch (freqRange) {
+            case IGNORE:
+                // do nothing
+                break;
+            case ServiceState.FREQUENCY_RANGE_UNKNOWN:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT;
+                break;
+            case ServiceState.FREQUENCY_RANGE_LOW:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW;
+                break;
+            case ServiceState.FREQUENCY_RANGE_MID:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID;
+                break;
+            case ServiceState.FREQUENCY_RANGE_HIGH:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH;
+                break;
+            case ServiceState.FREQUENCY_RANGE_MMWAVE:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE;
+                break;
+            default:
+                Log.w(TAG, "Unexpected NR frequency range : " + freqRange);
         }
-        total.durationMs = radioActiveTimeMs;
-        total.remainingPowerMah = powerMah;
-        total.signalDurationMs = signalTimeMs;
+
+        // Attach transmission level to the key if specified.
+        switch (txLevel) {
+            case IGNORE:
+                // do nothing
+                break;
+            case 0:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_0;
+                break;
+            case 1:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_1;
+                break;
+            case 2:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_2;
+                break;
+            case 3:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_3;
+                break;
+            case 4:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_4;
+                break;
+            default:
+                Log.w(TAG, "Unexpected transmission level : " + txLevel);
+        }
+        return key;
+    }
+
+    /**
+     * Calculates active receive radio power consumption (in milliamp-hours) from the given state's
+     * duration.
+     */
+    public double calcRxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
+            @ServiceState.FrequencyRange int freqRange, long rxDurationMs) {
+        final long rxKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat,
+                freqRange, IGNORE);
+        final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(rxKey,
+                Double.NaN);
+        if (Double.isNaN(drainRateMa)) {
+            Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(rxKey));
+            return Double.NaN;
+        }
+
+        final double consumptionMah = drainRateMa * rxDurationMs / MILLIS_IN_HOUR;
+        if (DEBUG) {
+            Log.d(TAG, "Calculated RX consumption " + consumptionMah + " mAH from a drain rate of "
+                    + drainRateMa + " mA and a duration of " + rxDurationMs + " ms for "
+                    + ModemPowerProfile.keyToString((int) rxKey));
+        }
+        return consumptionMah;
+    }
+
+    /**
+     * Calculates active transmit radio power consumption (in milliamp-hours) from the given state's
+     * duration.
+     */
+    public double calcTxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
+            @ServiceState.FrequencyRange int freqRange, int txLevel, long txDurationMs) {
+        final long txKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_TX, rat,
+                freqRange, txLevel);
+        final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(txKey,
+                Double.NaN);
+        if (Double.isNaN(drainRateMa)) {
+            Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(txKey));
+            return Double.NaN;
+        }
+
+        final double consumptionMah = drainRateMa * txDurationMs / MILLIS_IN_HOUR;
+        if (DEBUG) {
+            Log.d(TAG, "Calculated TX consumption " + consumptionMah + " mAH from a drain rate of "
+                    + drainRateMa + " mA and a duration of " + txDurationMs + " ms for "
+                    + ModemPowerProfile.keyToString((int) txKey));
+        }
+        return consumptionMah;
     }
 
     /**
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 9953ca8..1358417 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -83,6 +83,9 @@
     @Nullable
     @GuardedBy("this")
     private Looper mLooper;
+    @Nullable
+    @GuardedBy("this")
+    private EnergyConsumer[] mEnergyConsumers = null;
 
     @VisibleForTesting
     static class Injector {
@@ -260,6 +263,15 @@
         }
     }
 
+    private EnergyConsumer[] getEnergyConsumerInfo() {
+        synchronized (this) {
+            if (mEnergyConsumers == null) {
+                mEnergyConsumers = getPowerStatsHal().getEnergyConsumerInfo();
+            }
+            return mEnergyConsumers;
+        }
+    }
+
     public PowerStatsService(Context context) {
         this(context, new Injector());
     }
@@ -327,7 +339,69 @@
 
     private void getEnergyConsumedAsync(CompletableFuture<EnergyConsumerResult[]> future,
             int[] energyConsumerIds) {
-        future.complete(getPowerStatsHal().getEnergyConsumed(energyConsumerIds));
+        EnergyConsumerResult[] results = getPowerStatsHal().getEnergyConsumed(energyConsumerIds);
+
+        // STOPSHIP(253292374): Remove once missing EnergyConsumer results issue is resolved.
+        EnergyConsumer[] energyConsumers = getEnergyConsumerInfo();
+        if (energyConsumers != null) {
+            final int expectedLength;
+            if (energyConsumerIds.length == 0) {
+                // Empty request is a request for all available EnergyConsumers.
+                expectedLength = energyConsumers.length;
+            } else {
+                expectedLength = energyConsumerIds.length;
+            }
+
+            if (results == null || expectedLength != results.length) {
+                // Mismatch in requested/received energy consumer data.
+                StringBuilder sb = new StringBuilder();
+                sb.append("Requested ids:");
+                if (energyConsumerIds.length == 0) {
+                    sb.append("ALL");
+                }
+                sb.append("[");
+                for (int i = 0; i < expectedLength; i++) {
+                    final int id = energyConsumerIds[i];
+                    sb.append(id);
+                    sb.append("(type:");
+                    sb.append(energyConsumers[id].type);
+                    sb.append(",ord:");
+                    sb.append(energyConsumers[id].ordinal);
+                    sb.append(",name:");
+                    sb.append(energyConsumers[id].name);
+                    sb.append(")");
+                    if (i != expectedLength - 1) {
+                        sb.append(", ");
+                    }
+                }
+                sb.append("]");
+
+                sb.append(", Received result ids:");
+                if (results == null) {
+                    sb.append("null");
+                } else {
+                    sb.append("[");
+                    final int resultLength = results.length;
+                    for (int i = 0; i < resultLength; i++) {
+                        final int id = results[i].id;
+                        sb.append(id);
+                        sb.append("(type:");
+                        sb.append(energyConsumers[id].type);
+                        sb.append(",ord:");
+                        sb.append(energyConsumers[id].ordinal);
+                        sb.append(",name:");
+                        sb.append(energyConsumers[id].name);
+                        sb.append(")");
+                        if (i != resultLength - 1) {
+                            sb.append(", ");
+                        }
+                    }
+                    sb.append("]");
+                }
+                Slog.wtf(TAG, "Missing result from getEnergyConsumedAsync call. " + sb);
+            }
+        }
+        future.complete(results);
     }
 
     private void getStateResidencyAsync(CompletableFuture<StateResidencyResult[]> future,
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/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index 466ac74..5ae6973 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -23,27 +23,37 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Environment;
 import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
 import android.os.UserHandle;
 import android.security.IFileIntegrityService;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.security.VerityUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
-import java.util.Collection;
 
 /**
  * A {@link SystemService} that provides file integrity related operations.
@@ -52,9 +62,19 @@
 public class FileIntegrityService extends SystemService {
     private static final String TAG = "FileIntegrityService";
 
+    /** The maximum size of signature file.  This is just to avoid potential abuse. */
+    private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192;
+
     private static CertificateFactory sCertFactory;
 
-    private Collection<X509Certificate> mTrustedCertificates = new ArrayList<X509Certificate>();
+    @GuardedBy("mTrustedCertificates")
+    private final ArrayList<X509Certificate> mTrustedCertificates =
+            new ArrayList<X509Certificate>();
+
+    /** Gets the instance of the service */
+    public static FileIntegrityService getService() {
+        return LocalServices.getService(FileIntegrityService.class);
+    }
 
     private final IBinder mService = new IFileIntegrityService.Stub() {
         @Override
@@ -75,13 +95,23 @@
                     Slog.w(TAG, "Received a null certificate");
                     return false;
                 }
-                return mTrustedCertificates.contains(toCertificate(certificateBytes));
+                synchronized (mTrustedCertificates) {
+                    return mTrustedCertificates.contains(toCertificate(certificateBytes));
+                }
             } catch (CertificateException e) {
                 Slog.e(TAG, "Failed to convert the certificate: " + e);
                 return false;
             }
         }
 
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out,
+                FileDescriptor err, String[] args, ShellCallback callback,
+                ResultReceiver resultReceiver) {
+            new FileIntegrityServiceShellCommand()
+                    .exec(this, in, out, err, args, callback, resultReceiver);
+        }
+
         private void checkCallerPermission(String packageName) {
             final int callingUid = Binder.getCallingUid();
             final int callingUserId = UserHandle.getUserId(callingUid);
@@ -116,6 +146,7 @@
         } catch (CertificateException e) {
             Slog.wtf(TAG, "Cannot get an instance of X.509 certificate factory");
         }
+        LocalServices.addService(FileIntegrityService.class, this);
     }
 
     @Override
@@ -124,6 +155,34 @@
         publishBinderService(Context.FILE_INTEGRITY_SERVICE, mService);
     }
 
+    /**
+     * Returns whether the signature over the file's fs-verity digest can be verified by one of the
+     * known certiticates.
+     */
+    public boolean verifyPkcs7DetachedSignature(String signaturePath, String filePath)
+            throws IOException {
+        if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) {
+            throw new SecurityException("Signature file is unexpectedly large: "
+                    + signaturePath);
+        }
+        byte[] signatureBytes = Files.readAllBytes(Paths.get(signaturePath));
+        byte[] digest = VerityUtils.getFsverityDigest(filePath);
+        synchronized (mTrustedCertificates) {
+            for (var cert : mTrustedCertificates) {
+                try {
+                    byte[] derEncoded = cert.getEncoded();
+                    if (VerityUtils.verifyPkcs7DetachedSignature(signatureBytes, digest,
+                            new ByteArrayInputStream(derEncoded))) {
+                        return true;
+                    }
+                } catch (CertificateEncodingException e) {
+                    Slog.w(TAG, "Ignoring ill-formed certificate: " + e);
+                }
+            }
+        }
+        return false;
+    }
+
     private void loadAllCertificates() {
         // A better alternative to load certificates would be to read from .fs-verity kernel
         // keyring, which fsverity_init loads to during earlier boot time from the same sources
@@ -148,10 +207,6 @@
 
             for (File cert : files) {
                 byte[] certificateBytes = Files.readAllBytes(cert.toPath());
-                if (certificateBytes == null) {
-                    Slog.w(TAG, "The certificate file is empty, ignoring " + cert);
-                    continue;
-                }
                 collectCertificate(certificateBytes);
             }
         } catch (IOException e) {
@@ -165,7 +220,9 @@
      */
     private void collectCertificate(@NonNull byte[] bytes) {
         try {
-            mTrustedCertificates.add(toCertificate(bytes));
+            synchronized (mTrustedCertificates) {
+                mTrustedCertificates.add(toCertificate(bytes));
+            }
         } catch (CertificateException e) {
             Slog.e(TAG, "Invalid certificate, ignored: " + e);
         }
@@ -184,4 +241,71 @@
         }
         return (X509Certificate) certificate;
     }
+
+
+    private class FileIntegrityServiceShellCommand extends ShellCommand {
+        @Override
+        public int onCommand(String cmd) {
+            if (!Build.IS_DEBUGGABLE) {
+                return -1;
+            }
+            if (cmd == null) {
+                return handleDefaultCommands(cmd);
+            }
+            final PrintWriter pw = getOutPrintWriter();
+            switch (cmd) {
+                case "append-cert":
+                    String nextArg = getNextArg();
+                    if (nextArg == null) {
+                        pw.println("Invalid argument");
+                        pw.println("");
+                        onHelp();
+                        return -1;
+                    }
+                    ParcelFileDescriptor pfd = openFileForSystem(nextArg, "r");
+                    if (pfd == null) {
+                        pw.println("Cannot open the file");
+                        return -1;
+                    }
+                    InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+                    try {
+                        collectCertificate(is.readAllBytes());
+                    } catch (IOException e) {
+                        pw.println("Failed to add certificate: " + e);
+                        return -1;
+                    }
+                    pw.println("Certificate is added successfully");
+                    return 0;
+
+                case "remove-last-cert":
+                    synchronized (mTrustedCertificates) {
+                        if (mTrustedCertificates.size() == 0) {
+                            pw.println("Certificate list is already empty");
+                            return -1;
+                        }
+                        mTrustedCertificates.remove(mTrustedCertificates.size() - 1);
+                    }
+                    pw.println("Certificate is removed successfully");
+                    return 0;
+                default:
+                    pw.println("Unknown action");
+                    pw.println("");
+                    onHelp();
+            }
+            return -1;
+        }
+
+        @Override
+        public void onHelp() {
+            final PrintWriter pw = getOutPrintWriter();
+            pw.println("File integrity service commands:");
+            pw.println("  help");
+            pw.println("    Print this help text.");
+            pw.println("  append-cert path/to/cert.der");
+            pw.println("    Add the DER-encoded certificate (only in debug builds)");
+            pw.println("  remove-last-cert");
+            pw.println("    Remove the last certificate in the key list (only in debug builds)");
+            pw.println("");
+        }
+    }
 }
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/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
index fbb6644..f17e5e7 100644
--- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java
+++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
@@ -43,6 +43,43 @@
     public abstract void removeProximityActiveListener(@NonNull ProximityActiveListener listener);
 
     /**
+     * Creates a sensor that is registered at runtime by the system with the sensor service.
+     *
+     * The runtime sensors created here are different from the
+     * <a href="https://source.android.com/docs/core/interaction/sensors/sensors-hal2#dynamic-sensors">
+     * dynamic sensor support in the HAL</a>. These sensors have no HAL dependency and correspond to
+     * sensors that belong to an external (virtual) device.
+     *
+     * @param deviceId The identifier of the device this sensor is associated with.
+     * @param type The generic type of the sensor.
+     * @param name The name of the sensor.
+     * @param vendor The vendor string of the sensor.
+     * @param callback The callback to get notified when the sensor listeners have changed.
+     * @return The sensor handle.
+     */
+    public abstract int createRuntimeSensor(int deviceId, int type, @NonNull String name,
+            @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback);
+
+    /**
+     * Unregisters the sensor with the given handle from the framework.
+     */
+    public abstract void removeRuntimeSensor(int handle);
+
+    /**
+     * Sends an event for the runtime sensor with the given handle to the framework.
+     *
+     * Only relevant for sending runtime sensor events. @see #createRuntimeSensor.
+     *
+     * @param handle The sensor handle.
+     * @param type The type of the sensor.
+     * @param timestampNanos When the event occurred.
+     * @param values The values of the event.
+     * @return Whether the event injection was successful.
+     */
+    public abstract boolean sendSensorEvent(int handle, int type, long timestampNanos,
+            @NonNull float[] values);
+
+    /**
      * Listener for proximity sensor state changes.
      */
     public interface ProximityActiveListener {
@@ -52,4 +89,17 @@
          */
         void onProximityActive(boolean isActive);
     }
+
+    /**
+     * Callback for runtime sensor state changes. Only relevant to sensors created via
+     * {@link #createRuntimeSensor}, i.e. the dynamic sensors created via the dynamic sensor HAL are
+     * not covered.
+     */
+    public interface RuntimeSensorStateChangeCallback {
+        /**
+         * Invoked when the listeners of the runtime sensor have changed.
+         */
+        void onStateChanged(boolean enabled, int samplingPeriodMicros,
+                int batchReportLatencyMicros);
+    }
 }
diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java
index 8fe2d52..d8e3bdd 100644
--- a/services/core/java/com/android/server/sensors/SensorService.java
+++ b/services/core/java/com/android/server/sensors/SensorService.java
@@ -29,7 +29,9 @@
 import com.android.server.SystemService;
 import com.android.server.utils.TimingsTraceAndSlog;
 
+import java.util.HashSet;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
 
@@ -40,6 +42,8 @@
     private final ArrayMap<ProximityActiveListener, ProximityListenerProxy> mProximityListeners =
             new ArrayMap<>();
     @GuardedBy("mLock")
+    private final Set<Integer> mRuntimeSensorHandles = new HashSet<>();
+    @GuardedBy("mLock")
     private Future<?> mSensorServiceStart;
     @GuardedBy("mLock")
     private long mPtr;
@@ -51,6 +55,12 @@
     private static native void registerProximityActiveListenerNative(long ptr);
     private static native void unregisterProximityActiveListenerNative(long ptr);
 
+    private static native int registerRuntimeSensorNative(long ptr, int deviceId, int type,
+            String name, String vendor,
+            SensorManagerInternal.RuntimeSensorStateChangeCallback callback);
+    private static native void unregisterRuntimeSensorNative(long ptr, int handle);
+    private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type,
+            long timestampNanos, float[] values);
 
     public SensorService(Context ctx) {
         super(ctx);
@@ -85,6 +95,38 @@
 
     class LocalService extends SensorManagerInternal {
         @Override
+        public int createRuntimeSensor(int deviceId, int type, @NonNull String name,
+                @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback) {
+            synchronized (mLock) {
+                int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor,
+                        callback);
+                mRuntimeSensorHandles.add(handle);
+                return handle;
+            }
+        }
+
+        @Override
+        public void removeRuntimeSensor(int handle) {
+            synchronized (mLock) {
+                if (mRuntimeSensorHandles.contains(handle)) {
+                    mRuntimeSensorHandles.remove(handle);
+                    unregisterRuntimeSensorNative(mPtr, handle);
+                }
+            }
+        }
+
+        @Override
+        public boolean sendSensorEvent(int handle, int type, long timestampNanos,
+                @NonNull float[] values) {
+            synchronized (mLock) {
+                if (!mRuntimeSensorHandles.contains(handle)) {
+                    return false;
+                }
+                return sendRuntimeSensorEventNative(mPtr, handle, type, timestampNanos, values);
+            }
+        }
+
+        @Override
         public void addProximityActiveListener(@NonNull Executor executor,
                 @NonNull ProximityActiveListener listener) {
             Objects.requireNonNull(executor, "executor must not be null");
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
index d9f504e..ac97038 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -206,16 +206,21 @@
                 Log.d(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo);
             }
 
+            mRebinder = null;
+
             Intent bindIntent = new Intent(mBoundServiceInfo.getAction()).setComponent(
                     mBoundServiceInfo.getComponentName());
-            if (!mContext.bindServiceAsUser(bindIntent, this,
-                    BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
-                    mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
-                Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
-                mRebinder = this::bind;
-                mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
-            } else {
-                mRebinder = null;
+            try {
+                if (!mContext.bindServiceAsUser(bindIntent, this,
+                        BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
+                        mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
+                    Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
+                    mRebinder = this::bind;
+                    mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
+                }
+            } catch (SecurityException e) {
+                // if anything goes wrong it shouldn't crash the system server
+                Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " bind failed", e);
             }
         }
 
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 4111446..392fda9 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,29 @@
     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);
+
+    /**
+     * Shows the rear display educational dialog
+     *
+     * @see com.android.internal.statusbar.IStatusBar#showRearDisplayDialog
+     */
+    void showRearDisplayDialog(int currentBaseState);
+
+    /**
+     * Called when requested to go to fullscreen from the active split app.
+     */
+    void goToFullscreenFromSplit();
+
+    /**
+     * Enters stage split from a current running app.
+     *
+     * @see com.android.internal.statusbar.IStatusBar#enterStageSplitFromRunningApp
+     */
+    void enterStageSplitFromRunningApp(boolean leftOrTop);
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index d378b11..8d71d9c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -16,18 +16,23 @@
 
 package com.android.server.statusbar;
 
+import static android.Manifest.permission.CONTROL_DEVICE_STATE;
+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;
 
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
@@ -53,7 +58,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 +85,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 +178,7 @@
 
     private final SparseArray<UiState> mDisplayUiState = new SparseArray<>();
     @GuardedBy("mLock")
-    private IUdfpsHbmListener mUdfpsHbmListener;
+    private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback;
     @GuardedBy("mLock")
     private IBiometricContextListener mBiometricContextListener;
 
@@ -268,7 +274,6 @@
         mContext = context;
 
         LocalServices.addService(StatusBarManagerInternal.class, mInternalService);
-        LocalServices.addService(GlobalActionsProvider.class, mGlobalActionsProvider);
 
         // We always have a default display.
         final UiState state = new UiState();
@@ -285,6 +290,17 @@
         mSessionMonitor = new SessionMonitor(mContext);
     }
 
+    /**
+     * Publish the {@link GlobalActionsProvider}.
+     */
+    // TODO(b/259420401): investigate if we can extract GlobalActionsProvider to its own system
+    // service.
+    public void publishGlobalActionsProvider() {
+        if (LocalServices.getService(GlobalActionsProvider.class) == null) {
+            LocalServices.addService(GlobalActionsProvider.class, mGlobalActionsProvider);
+        }
+    }
+
     private IOverlayManager getOverlayManager() {
         // No need to synchronize; worst-case scenario it will be fetched twice.
         if (mOverlayManager == null) {
@@ -614,15 +630,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 +707,40 @@
         }
 
         @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) { }
+            }
+        }
+
+        @Override
+        public void showRearDisplayDialog(int currentBaseState) {
+            if (mBar != null) {
+                try {
+                    mBar.showRearDisplayDialog(currentBaseState);
+                } catch (RemoteException ex) { }
+            }
+        }
+
+        @Override
+        public void goToFullscreenFromSplit() {
+            if (mBar != null) {
+                try {
+                    mBar.goToFullscreenFromSplit();
+                } catch (RemoteException ex) { }
+            }
+        }
+
+        @Override
+        public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+            if (mBar != null) {
+                try {
+                    mBar.enterStageSplitFromRunningApp(leftOrTop);
                 } catch (RemoteException ex) { }
             }
         }
@@ -941,11 +984,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 +1251,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 +1263,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 +1347,28 @@
                 "StatusBarManagerService");
     }
 
+    @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+    private void enforceControlDeviceStatePermission() {
+        mContext.enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, "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 +1408,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 +1419,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 +2074,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(),
@@ -2177,6 +2235,19 @@
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+    @Override
+    public void showRearDisplayDialog(int currentState) {
+        enforceControlDeviceStatePermission();
+        if (mBar != null) {
+            try {
+                mBar.showRearDisplayDialog(currentState);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "showRearDisplayDialog", e);
+            }
+        }
+    }
+
     /** @hide */
     public void passThroughShellCommand(String[] args, FileDescriptor fd) {
         enforceStatusBarOrShell();
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 4972412..5801920 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
@@ -128,4 +128,5 @@
     @Override
     public void dumpDebugLog(@NonNull PrintWriter printWriter) {
         SystemClockTime.dump(printWriter);
-    }}
+    }
+}
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index 773b517..6782229 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;
@@ -35,7 +35,9 @@
 import java.time.DateTimeException;
 import java.time.Duration;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -185,8 +187,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();
 
@@ -212,8 +213,12 @@
     }
 
     private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+        // Copy the listeners to notify under the "mListeners" lock but don't hold the lock while
+        // delivering the notifications to avoid deadlocks.
+        List<StateChangeListener> listenersToNotify;
         synchronized (mListeners) {
-            for (Map.Entry<ConfigurationChangeListener, HashSet<String>> listenerEntry
+            listenersToNotify = new ArrayList<>(mListeners.size());
+            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
@@ -226,10 +231,14 @@
                 HashSet<String> monitoredKeys = listenerEntry.getValue();
                 Iterable<String> modifiedKeys = properties.getKeyset();
                 if (containsAny(monitoredKeys, modifiedKeys)) {
-                    listenerEntry.getKey().onChange();
+                    listenersToNotify.add(listenerEntry.getKey());
                 }
             }
         }
+
+        for (StateChangeListener listener : listenersToNotify) {
+            listener.onChange();
+        }
     }
 
     private static boolean containsAny(
@@ -249,7 +258,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..dc2a974 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
@@ -166,21 +166,27 @@
         }
     }
 
-    private synchronized void handleConfigurationInternalChangeOnMainThread() {
-        for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) {
+    private void handleConfigurationInternalChangeOnMainThread() {
+        // Copy the listeners holding the "this" lock but don't hold the lock while delivering the
+        // notifications to avoid deadlocks.
+        List<StateChangeListener> configurationInternalListeners;
+        synchronized (this) {
+            configurationInternalListeners = new ArrayList<>(this.mConfigurationInternalListeners);
+        }
+        for (StateChangeListener changeListener : configurationInternalListeners) {
             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/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 64adbb6..9d098c6 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -266,6 +266,8 @@
             for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) {
                 ITimeDetectorListener listener = mListeners.valueAt(listenerIndex);
                 try {
+                    // No need to surrender the mListeners lock while doing this:
+                    // ITimeDetectorListener is declared "oneway".
                     listener.onChange();
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Unable to notify listener=" + listener, e);
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/ConfigurationChangeListener.java b/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
deleted file mode 100644
index aa8ad37..0000000
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.timezonedetector;
-
-/**
- * A listener used to receive notification that configuration has / may have changed (depending on
- * the usecase).
- */
-@FunctionalInterface
-public interface ConfigurationChangeListener {
-    /** Called when the configuration may have changed. */
-    void onChange();
-}
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 8e2a5f4..111b4f6 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;
@@ -110,7 +109,7 @@
      * testing only. See {@link #isGeoDetectionExecutionEnabled()} and {@link #getDetectionMode()}
      * for details.
      */
-    boolean getGeoDetectionRunInBackgroundEnabled() {
+    boolean getGeoDetectionRunInBackgroundEnabledSetting() {
         return mGeoDetectionRunInBackgroundEnabled;
     }
 
@@ -133,7 +132,7 @@
      * from the raw setting value.
      */
     public boolean getAutoDetectionEnabledBehavior() {
-        return isAutoDetectionSupported() && mAutoDetectionEnabledSetting;
+        return isAutoDetectionSupported() && getAutoDetectionEnabledSetting();
     }
 
     /** Returns the ID of the user this configuration is associated with. */
@@ -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;
@@ -173,43 +171,59 @@
      * time zone.
      */
     public @DetectionMode int getDetectionMode() {
-        if (!getAutoDetectionEnabledBehavior()) {
+        if (!isAutoDetectionSupported()) {
+            // Handle the easy case first: No auto detection algorithms supported must mean manual.
             return DETECTION_MODE_MANUAL;
-        } else if (isGeoDetectionSupported() && getLocationEnabledSetting()
-                && getGeoDetectionEnabledSetting()) {
+        } else if (!getAutoDetectionEnabledSetting()) {
+            // Auto detection algorithms are supported, but disabled by the user.
+            return DETECTION_MODE_MANUAL;
+        } else if (getGeoDetectionEnabledBehavior()) {
             return DETECTION_MODE_GEO;
-        } else {
+        } else if (isTelephonyDetectionSupported()) {
             return DETECTION_MODE_TELEPHONY;
+        } else {
+            // On devices with telephony detection support, telephony is used instead of geo when
+            // geo cannot be used. This "unknown" case can occur on devices with only the location
+            // detection algorithm supported when the user's master location setting prevents its
+            // use.
+            return DETECTION_MODE_UNKNOWN;
         }
     }
 
+    private boolean getGeoDetectionEnabledBehavior() {
+        // isAutoDetectionSupported() should already have been checked before calling this method.
+        if (isGeoDetectionSupported() && getLocationEnabledSetting()) {
+            if (isTelephonyDetectionSupported()) {
+                // This is the "normal" case for smartphones that have both telephony and geo
+                // detection: the user chooses which type of detection to use.
+                return getGeoDetectionEnabledSetting();
+            } else {
+                // When only geo detection is supported then there is no choice for the user to
+                // make between detection modes, so no user setting is consulted.
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Returns true if geolocation time zone detection behavior can execute. Typically, this will
      * agree with {@link #getDetectionMode()}, but under rare circumstances the geolocation detector
-     * may be run in the background if the user's settings allow. See also {@link
-     * #getGeoDetectionRunInBackgroundEnabled()}.
+     * may be run in the background if the user's settings allow.
      */
     public boolean isGeoDetectionExecutionEnabled() {
-        return isGeoDetectionSupported()
-                && getLocationEnabledSetting()
-                && ((mAutoDetectionEnabledSetting && getGeoDetectionEnabledSetting())
-                || getGeoDetectionRunInBackgroundEnabled());
+        return getDetectionMode() == DETECTION_MODE_GEO
+                || getGeoDetectionRunInBackgroundEnabledBehavior();
     }
 
-    /**
-     * 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());
+    private boolean getGeoDetectionRunInBackgroundEnabledBehavior() {
+        return isGeoDetectionSupported()
+                && getLocationEnabledSetting()
+                && getGeoDetectionRunInBackgroundEnabledSetting();
     }
 
     @NonNull
-    private TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
+    public TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
         UserHandle userHandle = UserHandle.of(mUserId);
         TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userHandle);
 
@@ -230,11 +244,19 @@
         builder.setConfigureAutoDetectionEnabledCapability(configureAutoDetectionEnabledCapability);
 
         boolean deviceHasLocationTimeZoneDetection = isGeoDetectionSupported();
+        boolean deviceHasTelephonyDetection = isTelephonyDetectionSupported();
+
         // Note: allowConfigDateTime does not restrict the ability to change location time zone
         // detection enabled. This is intentional as it has user privacy implications and so it
-        // makes sense to leave this under a user's control.
+        // makes sense to leave this under a user's control. The only time this is not true is
+        // on devices that only support location-based detection and the main auto detection setting
+        // is used to influence whether location can be used.
         final @CapabilityState int configureGeolocationDetectionEnabledCapability;
-        if (!deviceHasLocationTimeZoneDetection) {
+        if (!deviceHasLocationTimeZoneDetection || !deviceHasTelephonyDetection) {
+            // If the device doesn't have geolocation detection support OR it ONLY has geolocation
+            // detection support (no telephony) then the user doesn't need the ability to toggle the
+            // location-based detection on and off (the auto detection toggle is considered
+            // sufficient).
             configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
         } else if (!mAutoDetectionEnabledSetting || !getLocationEnabledSetting()) {
             configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_APPLICABLE;
@@ -262,7 +284,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 +357,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 +369,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 +390,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/DeviceActivityMonitorImpl.java b/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitorImpl.java
index 8c9bd3b..6d2e723 100644
--- a/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitorImpl.java
@@ -77,12 +77,18 @@
         mListeners.add(listener);
     }
 
-    private synchronized void notifyFlightComplete() {
+    private void notifyFlightComplete() {
         if (DBG) {
             Slog.d(LOG_TAG, "notifyFlightComplete");
         }
 
-        for (Listener listener : mListeners) {
+        // Copy the listeners holding the "this" lock but don't hold the lock while delivering the
+        // notifications to avoid deadlocks.
+        List<Listener> listeners;
+        synchronized (this) {
+            listeners = new ArrayList<>(mListeners);
+        }
+        for (Listener listener : listeners) {
             listener.onFlightComplete();
         }
     }
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/GeolocationTimeZoneSuggestion.java b/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
index 8218fa5..80d9599 100644
--- a/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
+++ b/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
@@ -19,20 +19,15 @@
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.os.ShellCommand;
-import android.os.SystemClock;
 
-import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
-import java.util.StringTokenizer;
 
 /**
- * A time zone suggestion from the location_time_zone_manager service to the time_zone_detector
- * service.
+ * A time zone suggestion from the location_time_zone_manager service (AKA the location-based time
+ * zone detection algorithm).
  *
  * <p>Geolocation-based suggestions have the following properties:
  *
@@ -63,24 +58,16 @@
  *     location_time_zone_manager may become uncertain if components further downstream cannot
  *     determine the device's location with sufficient accuracy, or if the location is known but no
  *     time zone can be determined because no time zone mapping information is available.</li>
- *     <li>{@code debugInfo} contains debugging metadata associated with the suggestion. This is
- *     used to record why the suggestion exists and how it was obtained. This information exists
- *     only to aid in debugging and therefore is used by {@link #toString()}, but it is not for use
- *     in detection logic and is not considered in {@link #hashCode()} or {@link #equals(Object)}.
  *     </li>
  * </ul>
- *
- * @hide
  */
 public final class GeolocationTimeZoneSuggestion {
 
     @ElapsedRealtimeLong private final long mEffectiveFromElapsedMillis;
     @Nullable private final List<String> mZoneIds;
-    @Nullable private ArrayList<String> mDebugInfo;
 
     private GeolocationTimeZoneSuggestion(
-            @ElapsedRealtimeLong long effectiveFromElapsedMillis,
-            @Nullable List<String> zoneIds) {
+            @ElapsedRealtimeLong long effectiveFromElapsedMillis, @Nullable List<String> zoneIds) {
         mEffectiveFromElapsedMillis = effectiveFromElapsedMillis;
         if (zoneIds == null) {
             // Unopinionated
@@ -104,8 +91,7 @@
      */
     @NonNull
     public static GeolocationTimeZoneSuggestion createCertainSuggestion(
-            @ElapsedRealtimeLong long effectiveFromElapsedMillis,
-            @NonNull List<String> zoneIds) {
+            @ElapsedRealtimeLong long effectiveFromElapsedMillis, @NonNull List<String> zoneIds) {
         return new GeolocationTimeZoneSuggestion(effectiveFromElapsedMillis, zoneIds);
     }
 
@@ -126,25 +112,6 @@
         return mZoneIds;
     }
 
-    /** Returns debug information. See {@link GeolocationTimeZoneSuggestion} for details. */
-    @NonNull
-    public List<String> getDebugInfo() {
-        return mDebugInfo == null
-                ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
-    }
-
-    /**
-     * Associates information with the instance that can be useful for debugging / logging. The
-     * information is present in {@link #toString()} but is not considered for
-     * {@link #equals(Object)} and {@link #hashCode()}.
-     */
-    public void addDebugInfo(String... debugInfos) {
-        if (mDebugInfo == null) {
-            mDebugInfo = new ArrayList<>();
-        }
-        mDebugInfo.addAll(Arrays.asList(debugInfos));
-    }
-
     @Override
     public boolean equals(Object o) {
         if (this == o) {
@@ -169,59 +136,6 @@
         return "GeolocationTimeZoneSuggestion{"
                 + "mEffectiveFromElapsedMillis=" + mEffectiveFromElapsedMillis
                 + ", mZoneIds=" + mZoneIds
-                + ", mDebugInfo=" + mDebugInfo
                 + '}';
     }
-
-    /** @hide */
-    public static GeolocationTimeZoneSuggestion parseCommandLineArg(@NonNull ShellCommand cmd) {
-        String zoneIdsString = null;
-        String opt;
-        while ((opt = cmd.getNextArg()) != null) {
-            switch (opt) {
-                case "--zone_ids": {
-                    zoneIdsString  = cmd.getNextArgRequired();
-                    break;
-                }
-                default: {
-                    throw new IllegalArgumentException("Unknown option: " + opt);
-                }
-            }
-        }
-
-        if (zoneIdsString == null) {
-            throw new IllegalArgumentException("Missing --zone_ids");
-        }
-
-        long elapsedRealtimeMillis = SystemClock.elapsedRealtime();
-        List<String> zoneIds = parseZoneIdsArg(zoneIdsString);
-        GeolocationTimeZoneSuggestion suggestion =
-                new GeolocationTimeZoneSuggestion(elapsedRealtimeMillis, zoneIds);
-        suggestion.addDebugInfo("Command line injection");
-        return suggestion;
-    }
-
-    private static List<String> parseZoneIdsArg(String zoneIdsString) {
-        if ("UNCERTAIN".equals(zoneIdsString)) {
-            return null;
-        } else if ("EMPTY".equals(zoneIdsString)) {
-            return Collections.emptyList();
-        } else {
-            ArrayList<String> zoneIds = new ArrayList<>();
-            StringTokenizer tokenizer = new StringTokenizer(zoneIdsString, ",");
-            while (tokenizer.hasMoreTokens()) {
-                zoneIds.add(tokenizer.nextToken());
-            }
-            return zoneIds;
-        }
-    }
-
-    /** @hide */
-    public static void printCommandLineOpts(@NonNull PrintWriter pw) {
-        pw.println("Geolocation suggestion options:");
-        pw.println("  --zone_ids {UNCERTAIN|EMPTY|<Olson ID>+}");
-        pw.println();
-        pw.println("See " + GeolocationTimeZoneSuggestion.class.getName()
-                + " for more information");
-    }
 }
diff --git a/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java b/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java
new file mode 100644
index 0000000..1ffd9a1
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java
@@ -0,0 +1,194 @@
+/*
+ * 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.timezonedetector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringTokenizer;
+
+/**
+ * An event from the location_time_zone_manager service (AKA the location-based time zone detection
+ * algorithm). An event can represent a new time zone recommendation, an algorithm status change, or
+ * both.
+ *
+ * <p>Events have the following properties:
+ *
+ * <ul>
+ *     <li>{@code algorithmStatus}: The current status of the location-based time zone detection
+ *     algorithm.</li>
+ *     <li>{@code suggestion}: The latest time zone suggestion, if there is one.</li>
+ *     <li>{@code debugInfo} contains debugging metadata associated with the suggestion. This is
+ *     used to record why the event exists and how information contained within it was obtained.
+ *     This information exists only to aid in debugging and therefore is used by
+ *     {@link #toString()}, but it is not for use in detection logic and is not considered in
+ *     {@link #hashCode()} or {@link #equals(Object)}.
+ *     </li>
+ * </ul>
+ */
+public final class LocationAlgorithmEvent {
+
+    @NonNull private final LocationTimeZoneAlgorithmStatus mAlgorithmStatus;
+    @Nullable private final GeolocationTimeZoneSuggestion mSuggestion;
+    @Nullable private ArrayList<String> mDebugInfo;
+
+    /** Creates a new instance. */
+    public LocationAlgorithmEvent(
+            @NonNull LocationTimeZoneAlgorithmStatus algorithmStatus,
+            @Nullable GeolocationTimeZoneSuggestion suggestion) {
+        mAlgorithmStatus = Objects.requireNonNull(algorithmStatus);
+        mSuggestion = suggestion;
+    }
+
+    /**
+     * Returns the status of the location time zone detector algorithm.
+     */
+    @NonNull
+    public LocationTimeZoneAlgorithmStatus getAlgorithmStatus() {
+        return mAlgorithmStatus;
+    }
+
+    /**
+     * Returns the latest location algorithm suggestion. See {@link LocationAlgorithmEvent} for
+     * details.
+     */
+    @Nullable
+    public GeolocationTimeZoneSuggestion getSuggestion() {
+        return mSuggestion;
+    }
+
+    /** Returns debug information. See {@link LocationAlgorithmEvent} for details. */
+    @NonNull
+    public List<String> getDebugInfo() {
+        return mDebugInfo == null
+                ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
+    }
+
+    /**
+     * Associates information with the instance that can be useful for debugging / logging. The
+     * information is present in {@link #toString()} but is not considered for
+     * {@link #equals(Object)} and {@link #hashCode()}.
+     */
+    public void addDebugInfo(String... debugInfos) {
+        if (mDebugInfo == null) {
+            mDebugInfo = new ArrayList<>();
+        }
+        mDebugInfo.addAll(Arrays.asList(debugInfos));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        LocationAlgorithmEvent that = (LocationAlgorithmEvent) o;
+        return mAlgorithmStatus.equals(that.mAlgorithmStatus)
+                && Objects.equals(mSuggestion, that.mSuggestion);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAlgorithmStatus, mSuggestion);
+    }
+
+    @Override
+    public String toString() {
+        return "LocationAlgorithmEvent{"
+                + "mAlgorithmStatus=" + mAlgorithmStatus
+                + ", mSuggestion=" + mSuggestion
+                + ", mDebugInfo=" + mDebugInfo
+                + '}';
+    }
+
+    static LocationAlgorithmEvent parseCommandLineArg(@NonNull ShellCommand cmd) {
+        String suggestionString = null;
+        LocationTimeZoneAlgorithmStatus algorithmStatus = null;
+        String opt;
+        while ((opt = cmd.getNextArg()) != null) {
+            switch (opt) {
+                case "--status": {
+                    algorithmStatus = LocationTimeZoneAlgorithmStatus.parseCommandlineArg(
+                            cmd.getNextArgRequired());
+                    break;
+                }
+                case "--suggestion": {
+                    suggestionString  = cmd.getNextArgRequired();
+                    break;
+                }
+                default: {
+                    throw new IllegalArgumentException("Unknown option: " + opt);
+                }
+            }
+        }
+
+        if (algorithmStatus == null) {
+            throw new IllegalArgumentException("Missing --status");
+        }
+
+        GeolocationTimeZoneSuggestion suggestion = null;
+        if (suggestionString != null) {
+            List<String> zoneIds = parseZoneIds(suggestionString);
+            long elapsedRealtimeMillis = SystemClock.elapsedRealtime();
+            if (zoneIds == null) {
+                suggestion = GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+                        elapsedRealtimeMillis);
+            } else {
+                suggestion = GeolocationTimeZoneSuggestion.createCertainSuggestion(
+                        elapsedRealtimeMillis, zoneIds);
+            }
+        }
+
+        LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
+        event.addDebugInfo("Command line injection");
+        return event;
+    }
+
+    private static List<String> parseZoneIds(String zoneIdsString) {
+        if ("UNCERTAIN".equals(zoneIdsString)) {
+            return null;
+        } else if ("EMPTY".equals(zoneIdsString)) {
+            return Collections.emptyList();
+        } else {
+            ArrayList<String> zoneIds = new ArrayList<>();
+            StringTokenizer tokenizer = new StringTokenizer(zoneIdsString, ",");
+            while (tokenizer.hasMoreTokens()) {
+                zoneIds.add(tokenizer.nextToken());
+            }
+            return zoneIds;
+        }
+    }
+
+    static void printCommandLineOpts(@NonNull PrintWriter pw) {
+        pw.println("Location algorithm event options:");
+        pw.println("  --status {LocationTimeZoneAlgorithmStatus toString() format}");
+        pw.println("  [--suggestion {UNCERTAIN|EMPTY|<Olson ID>+}]");
+        pw.println();
+        pw.println("See " + LocationAlgorithmEvent.class.getName() + " for more information");
+    }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
index 6c36989..59691f8 100644
--- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
+++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
@@ -89,7 +89,7 @@
             @NonNull String deviceTimeZoneId,
             @Nullable ManualTimeZoneSuggestion latestManualSuggestion,
             @Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion,
-            @Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion) {
+            @Nullable LocationAlgorithmEvent latestLocationAlgorithmEvent) {
 
         boolean includeZoneIds = configurationInternal.isEnhancedMetricsCollectionEnabled();
         String metricDeviceTimeZoneId = includeZoneIds ? deviceTimeZoneId : null;
@@ -101,9 +101,13 @@
         MetricsTimeZoneSuggestion latestCanonicalTelephonySuggestion =
                 createMetricsTimeZoneSuggestion(
                         tzIdOrdinalGenerator, latestTelephonySuggestion, includeZoneIds);
-        MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion =
-                createMetricsTimeZoneSuggestion(
-                        tzIdOrdinalGenerator, latestGeolocationSuggestion, includeZoneIds);
+
+        MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion = null;
+        if (latestLocationAlgorithmEvent != null) {
+            GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion();
+            latestCanonicalGeolocationSuggestion = createMetricsTimeZoneSuggestion(
+                    tzIdOrdinalGenerator, suggestion, includeZoneIds);
+        }
 
         return new MetricsTimeZoneDetectorState(
                 configurationInternal, deviceTimeZoneIdOrdinal, metricDeviceTimeZoneId,
@@ -132,7 +136,7 @@
      * testing only.
      */
     public boolean getGeoDetectionRunInBackgroundEnabled() {
-        return mConfigurationInternal.getGeoDetectionRunInBackgroundEnabled();
+        return mConfigurationInternal.getGeoDetectionRunInBackgroundEnabledSetting();
     }
 
     /** Returns true if enhanced metric collection is enabled. */
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..6ebaf14c 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
@@ -206,21 +205,27 @@
         }
     }
 
-    private synchronized void handleConfigurationInternalChangeOnMainThread() {
-        for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) {
+    private void handleConfigurationInternalChangeOnMainThread() {
+        // Copy the listeners holding the "this" lock but don't hold the lock while delivering the
+        // notifications to avoid deadlocks.
+        List<StateChangeListener> configurationInternalListeners;
+        synchronized (this) {
+            configurationInternalListeners = new ArrayList<>(this.mConfigurationInternalListeners);
+        }
+        for (StateChangeListener changeListener : configurationInternalListeners) {
             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 +242,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);
@@ -274,15 +279,18 @@
             final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled();
             setAutoDetectionEnabledIfRequired(autoDetectionEnabled);
 
-            // Avoid writing the geo detection enabled setting for devices with settings that
-            // are currently overridden by server flags: otherwise we might overwrite a droidfood
-            // user's real setting permanently.
-            // Also avoid writing the geo detection enabled setting for devices that do not support
-            // geo time zone detection: if we wrote it down then we'd set the value explicitly,
-            // which would prevent detecting "default" later. That might influence what happens on
-            // later releases that start to support geo detection on the same hardware.
+            // Only write the geo detection enabled setting when its values is used, e.g.:
+            // 1) Devices with a setting value that is not currently overridden by server flags
+            // 2) Devices that support both telephony and location detection algorithms
+            //
+            // If we wrote a setting value down when it's not used then we'd be setting the value
+            // explicitly, which would prevent detecting the setting is in "default" state later.
+            // Not being able to detect if the user has actually expressed a preference could
+            // influence what happens on later releases that start to support geo detection on the
+            // user's same hardware.
             if (!getGeoDetectionSettingEnabledOverride().isPresent()
-                    && isGeoTimeZoneDetectionFeatureSupported()) {
+                    && isGeoTimeZoneDetectionFeatureSupported()
+                    && isTelephonyTimeZoneDetectionFeatureSupported()) {
                 final boolean geoDetectionEnabledSetting = configuration.isGeoDetectionEnabled();
                 setGeoDetectionEnabledSettingIfRequired(userId, geoDetectionEnabledSetting);
             }
@@ -292,7 +300,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 +363,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/StateChangeListener.java b/services/core/java/com/android/server/timezonedetector/StateChangeListener.java
new file mode 100644
index 0000000..2b5639c
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/StateChangeListener.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezonedetector;
+
+/**
+ * A listener used to receive notification that state has / may have changed (depending on
+ * the usecase).
+ */
+@FunctionalInterface
+public interface StateChangeListener {
+    /** Called when something (may have) changed. */
+    void onChange();
+}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
index 80cf1d6..74a518b 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
@@ -59,11 +59,11 @@
     boolean setManualTimeZoneForDpm(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion);
 
     /**
-     * Suggests the current time zone, determined using geolocation, to the detector. The
-     * detector may ignore the signal based on system settings, whether better information is
-     * available, and so on. This method may be implemented asynchronously.
+     * Handles the supplied {@link LocationAlgorithmEvent}. The detector may ignore the event based
+     * on system settings, whether better information is available, and so on. This method may be
+     * implemented asynchronously.
      */
-    void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion);
+    void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent locationAlgorithmEvent);
 
     /** Generates a state snapshot for metrics. */
     @NonNull
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index ce64eac..07d0473 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);
     }
 
@@ -80,13 +76,14 @@
     }
 
     @Override
-    public void suggestGeolocationTimeZone(
-            @NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
-        Objects.requireNonNull(timeZoneSuggestion);
+    public void handleLocationAlgorithmEvent(
+            @NonNull LocationAlgorithmEvent locationAlgorithmEvent) {
+        Objects.requireNonNull(locationAlgorithmEvent);
 
         // This call can take place on the mHandler thread because there is no return value.
         mHandler.post(
-                () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion));
+                () -> mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent(
+                        locationAlgorithmEvent));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 13f1694..10cd5d1 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);
 
@@ -91,7 +91,7 @@
             deviceActivityMonitor.addListener(new DeviceActivityMonitor.Listener() {
                 @Override
                 public void onFlightComplete() {
-                    timeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
+                    timeZoneDetectorStrategy.enableTelephonyTimeZoneFallback("onFlightComplete()");
                 }
             });
 
@@ -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) {
@@ -303,12 +300,13 @@
     }
 
     /** Provided for command-line access. This is not exposed as a binder API. */
-    void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
+    void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent locationAlgorithmEvent) {
         enforceSuggestGeolocationTimeZonePermission();
-        Objects.requireNonNull(timeZoneSuggestion);
+        Objects.requireNonNull(locationAlgorithmEvent);
 
         mHandler.post(
-                () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion));
+                () -> mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent(
+                        locationAlgorithmEvent));
     }
 
     @Override
@@ -404,9 +402,9 @@
      * Sends a signal to enable telephony fallback. Provided for command-line access for use
      * during tests. This is not exposed as a binder API.
      */
-    void enableTelephonyFallback() {
+    void enableTelephonyFallback(@NonNull String reason) {
         enforceManageTimeZoneDetectorPermission();
-        mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
+        mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(reason);
     }
 
     /**
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index 1b9f8e6..ab68e83 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -19,6 +19,7 @@
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_DUMP_METRICS;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_GET_TIME_ZONE_STATE;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED;
@@ -27,7 +28,6 @@
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_TIME_ZONE_STATE;
-import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE;
 import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
@@ -79,8 +79,8 @@
                 return runIsGeoDetectionEnabled();
             case SHELL_COMMAND_SET_GEO_DETECTION_ENABLED:
                 return runSetGeoDetectionEnabled();
-            case SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE:
-                return runSuggestGeolocationTimeZone();
+            case SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT:
+                return runHandleLocationEvent();
             case SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE:
                 return runSuggestManualTimeZone();
             case SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE:
@@ -153,34 +153,34 @@
         return mInterface.updateConfiguration(userId, configuration) ? 0 : 1;
     }
 
-    private int runSuggestGeolocationTimeZone() {
-        return runSuggestTimeZone(
-                () -> GeolocationTimeZoneSuggestion.parseCommandLineArg(this),
-                mInterface::suggestGeolocationTimeZone);
+    private int runHandleLocationEvent() {
+        return runSingleArgMethod(
+                () -> LocationAlgorithmEvent.parseCommandLineArg(this),
+                mInterface::handleLocationAlgorithmEvent);
     }
 
     private int runSuggestManualTimeZone() {
-        return runSuggestTimeZone(
+        return runSingleArgMethod(
                 () -> ManualTimeZoneSuggestion.parseCommandLineArg(this),
                 mInterface::suggestManualTimeZone);
     }
 
     private int runSuggestTelephonyTimeZone() {
-        return runSuggestTimeZone(
+        return runSingleArgMethod(
                 () -> TelephonyTimeZoneSuggestion.parseCommandLineArg(this),
                 mInterface::suggestTelephonyTimeZone);
     }
 
-    private <T> int runSuggestTimeZone(Supplier<T> suggestionParser, Consumer<T> invoker) {
+    private <T> int runSingleArgMethod(Supplier<T> argParser, Consumer<T> invoker) {
         final PrintWriter pw = getOutPrintWriter();
         try {
-            T suggestion = suggestionParser.get();
-            if (suggestion == null) {
-                pw.println("Error: suggestion not specified");
+            T arg = argParser.get();
+            if (arg == null) {
+                pw.println("Error: arg not specified");
                 return 1;
             }
-            invoker.accept(suggestion);
-            pw.println("Suggestion " + suggestion + " injected.");
+            invoker.accept(arg);
+            pw.println("Arg " + arg + " injected.");
             return 0;
         } catch (RuntimeException e) {
             pw.println(e);
@@ -189,7 +189,7 @@
     }
 
     private int runEnableTelephonyFallback() {
-        mInterface.enableTelephonyFallback();
+        mInterface.enableTelephonyFallback("Command line");
         return 0;
     }
 
@@ -263,18 +263,18 @@
         pw.printf("    Sets the geolocation time zone detection enabled setting.\n");
         pw.printf("  %s\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK);
         pw.printf("    Signals that telephony time zone detection fall back can be used if"
-                + " geolocation detection is supported and enabled. This is a temporary state until"
-                + " geolocation detection becomes \"certain\". To have an effect this requires that"
-                + " the telephony fallback feature is supported on the device, see below for"
-                + " for device_config flags.\n");
-        pw.println();
-        pw.printf("  %s <geolocation suggestion opts>\n",
-                SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE);
-        pw.printf("    Suggests a time zone as if via the \"location\" origin.\n");
+                + " geolocation detection is supported and enabled.\n)");
+        pw.printf("    This is a temporary state until geolocation detection becomes \"certain\"."
+                + "\n");
+        pw.printf("    To have an effect this requires that the telephony fallback feature is"
+                + " supported on the device, see below for device_config flags.\n");
+        pw.printf("  %s <location event opts>\n", SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT);
+        pw.printf("    Simulates an event from the location time zone detection algorithm.\n");
         pw.printf("  %s <manual suggestion opts>\n", SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE);
-        pw.printf("    Suggests a time zone as if via the \"manual\" origin.\n");
+        pw.printf("    Suggests a time zone as if supplied by a user manually.\n");
         pw.printf("  %s <telephony suggestion opts>\n", SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
-        pw.printf("    Suggests a time zone as if via the \"telephony\" origin.\n");
+        pw.printf("    Simulates a time zone suggestion from the telephony time zone detection"
+                + " algorithm.\n");
         pw.printf("  %s\n", SHELL_COMMAND_GET_TIME_ZONE_STATE);
         pw.printf("    Returns the current time zone setting state.\n");
         pw.printf("  %s <time zone state options>\n", SHELL_COMMAND_SET_TIME_ZONE_STATE);
@@ -284,7 +284,7 @@
         pw.printf("  %s\n", SHELL_COMMAND_DUMP_METRICS);
         pw.printf("    Dumps the service metrics to stdout for inspection.\n");
         pw.println();
-        GeolocationTimeZoneSuggestion.printCommandLineOpts(pw);
+        LocationAlgorithmEvent.printCommandLineOpts(pw);
         pw.println();
         ManualTimeZoneSuggestion.printCommandLineOpts(pw);
         pw.println();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 69284e3..37e67c9 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;
@@ -33,13 +35,13 @@
  * <p>Devices can have zero, one or two automatic time zone detection algorithms available at any
  * point in time.
  *
- * <p>The two automatic detection algorithms supported are "telephony" and "geolocation". Algorithm
+ * <p>The two automatic detection algorithms supported are "telephony" and "location". Algorithm
  * availability and use depends on several factors:
  * <ul>
  * <li>Telephony is only available on devices with a telephony stack.
- * <li>Geolocation is also optional and configured at image creation time. When enabled on a
- * device, its availability depends on the current user's settings, so switching between users can
- * change the automatic algorithm used by the device.</li>
+ * <li>Location is also optional and configured at image creation time. When enabled on a device,
+ * its availability depends on the current user's settings, so switching between users can change
+ * the automatic detection algorithm used by the device.</li>
  * </ul>
  *
  * <p>If there are no automatic time zone detections algorithms available then the user can usually
@@ -54,14 +56,14 @@
  * slotIndexes must have an empty suggestion submitted in order to "withdraw" their previous
  * suggestion otherwise it will remain in use.
  *
- * <p>Geolocation detection is dependent on the current user and their settings. The device retains
- * at most one geolocation suggestion. Generally, use of a device's location is dependent on the
- * user's "location toggle", but even when that is enabled the user may choose to enable / disable
- * the use of geolocation for device time zone detection. If the current user changes to one that
- * does not have geolocation detection enabled, or the user turns off geolocation detection, then
- * the strategy discards the latest geolocation suggestion. Devices that lose a location fix must
- * have an empty suggestion submitted in order to "withdraw" their previous suggestion otherwise it
- * will remain in use.
+ * <p>Location-based detection is dependent on the current user and their settings. The device
+ * retains at most one geolocation suggestion. Generally, use of a device's location is dependent on
+ * the user's "location toggle", but even when that is enabled the user may choose to enable /
+ * disable the use of location for device time zone detection. If the current user changes to one
+ * that does not have location-based detection enabled, or the user turns off the location-based
+ * detection, then the strategy will be sent an event that clears the latest suggestion. Devices
+ * that lose their location fix must have an empty suggestion submitted in order to "withdraw" their
+ * previous suggestion otherwise it will remain in use.
  *
  * <p>The strategy uses only one algorithm at a time and does not attempt consensus even when
  * more than one is available on a device. This "use only one" behavior is deliberate as different
@@ -70,30 +72,74 @@
  * users enter areas without the necessary signals. Ultimately, with no perfect algorithm available,
  * the user is left to choose which algorithm works best for their circumstances.
  *
- * <p>When geolocation detection is supported and enabled, in certain circumstances, such as during
- * international travel, it makes sense to prioritize speed of detection via telephony (when
- * available) Vs waiting for the geolocation algorithm to reach certainty. Geolocation detection can
- * sometimes be slow to get a location fix and can require network connectivity (which cannot be
- * assumed when users are travelling) for server-assisted location detection or time zone lookup.
- * Therefore, as a restricted form of prioritization between geolocation and telephony algorithms,
- * the strategy provides "telephony fallback" behavior, which can be set to "supported" via device
- * config. Fallback mode is toggled on at runtime via {@link #enableTelephonyTimeZoneFallback()} in
- * response to signals outside of the scope of this class. Telephony fallback allows the use of
- * telephony suggestions to help with faster detection but only until geolocation detection
- * provides a concrete, "certain" suggestion. After geolocation has made the first certain
- * suggestion, telephony fallback is disabled until the next call to {@link
- * #enableTelephonyTimeZoneFallback()}.
+ * <p>When the location detection algorithm is supported and enabled, in certain circumstances, such
+ * as during international travel, it makes sense to prioritize speed of detection via telephony
+ * (when available) Vs waiting for the location-based detection algorithm to reach certainty.
+ * Location-based detection can sometimes be slow to get a location fix and can require network
+ * connectivity (which cannot be assumed when users are travelling) for server-assisted location
+ * detection or time zone lookup. Therefore, as a restricted form of prioritization between location
+ * and telephony algorithms, the strategy provides "telephony fallback mode" behavior, which can be
+ * set to "supported" via device config. Fallback mode is entered at runtime in response to signals
+ * from outside of the strategy, e.g. from a call to {@link
+ * #enableTelephonyTimeZoneFallback(String)}, or from information in the latest {@link
+ * LocationAlgorithmEvent}. For telephony fallback mode to actually use a telephony suggestion, the
+ * location algorithm <em>must</em> report it is uncertain. Telephony fallback allows the use of
+ * telephony suggestions to help with faster detection but only until the location algorithm
+ * provides a concrete, "certain" suggestion. After the location algorithm has made a certain
+ * suggestion, telephony fallback mode is disabled.
  *
  * <p>Threading:
  *
  * <p>Implementations of this class must be thread-safe as calls calls like {@link
  * #generateMetricsState()} and {@link #dump(IndentingPrintWriter, String[])} may be called on
- * differents thread concurrently with other operations.
+ * different threads concurrently with other operations.
  *
  * @hide
  */
 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();
@@ -113,10 +159,9 @@
     boolean confirmTimeZone(@NonNull String timeZoneId);
 
     /**
-     * Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if
-     * {@link GeolocationTimeZoneSuggestion#getZoneIds()} is {@code null}.
+     * Handles an event from the location-based time zone detection algorithm.
      */
-    void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion suggestion);
+    void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent event);
 
     /**
      * Suggests a time zone for the device using manually-entered (i.e. user sourced) information.
@@ -138,11 +183,11 @@
     void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion);
 
     /**
-     * Tells the strategy that it can fall back to telephony detection while geolocation detection
-     * remains uncertain. {@link #suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion)} can
-     * disable it again. See {@link TimeZoneDetectorStrategy} for details.
+     * Tells the strategy that it can fall back to telephony detection while the location detection
+     * algorithm remains uncertain. {@link #handleLocationAlgorithmEvent(LocationAlgorithmEvent)}
+     * can disable it again. See {@link TimeZoneDetectorStrategy} for details.
      */
-    void enableTelephonyTimeZoneFallback();
+    void enableTelephonyTimeZoneFallback(@NonNull String reason);
 
     /** Generates a state snapshot for metrics. */
     @NonNull
@@ -151,6 +196,6 @@
     /** Returns {@code true} if the device supports telephony time zone detection. */
     boolean isTelephonyTimeZoneDetectionSupported();
 
-    /** Returns {@code true} if the device supports geolocation time zone detection. */
+    /** Returns {@code true} if the device supports location-based time zone detection. */
     boolean isGeoTimeZoneDetectionSupported();
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 18c8885..e0e3565 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -29,12 +29,16 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.time.DetectorStatusTypes;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
 import android.app.time.TimeZoneCapabilities;
 import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
 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;
@@ -43,9 +47,11 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.SystemTimeZone.TimeZoneConfidence;
+import com.android.server.timezonedetector.ConfigurationInternal.DetectionMode;
 
 import java.io.PrintWriter;
 import java.time.Duration;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
@@ -57,27 +63,13 @@
 public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrategy {
 
     /**
-     * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device configuration / settings
-     * / system properties. It can be faked for testing.
-     *
-     * <p>Note: Because the settings / system properties-derived values can currently be modified
-     * independently and from different threads (and processes!), their use is prone to race
-     * conditions.
+     * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device state besides that
+     * available from {@link #mServiceConfigAccessor}. It can be faked for testing.
      */
     @VisibleForTesting
     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();
@@ -192,11 +184,10 @@
             new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
 
     /**
-     * The latest geolocation suggestion received. If the user disabled geolocation time zone
-     * detection then the latest suggestion is cleared.
+     * The latest location algorithm event received.
      */
     @GuardedBy("this")
-    private final ReferenceWithHistory<GeolocationTimeZoneSuggestion> mLatestGeoLocationSuggestion =
+    private final ReferenceWithHistory<LocationAlgorithmEvent> mLatestLocationAlgorithmEvent =
             new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
 
     /**
@@ -206,6 +197,30 @@
     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 detector status. A local copy is cached because it is relatively
+     * heavyweight to obtain and is used more often than it is expected to change.
+     */
+    @GuardedBy("this")
+    @NonNull
+    private TimeZoneDetectorStatus mDetectorStatus;
+
+    /**
+     * 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;
@@ -215,7 +230,7 @@
      * allows).
      *
      * <p>This field is only actually used when telephony time zone fallback is supported, but the
-     * value is maintained even when it isn't supported as it can be turned on at any time via
+     * value is maintained even when it isn't supported as support can be turned on at any time via
      * server flags. The elapsed realtime when the mode last changed is used to help ordering
      * between fallback mode switches and suggestions.
      *
@@ -229,29 +244,121 @@
      * 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);
+
+            // Initialize mCurrentConfigurationInternal and mDetectorStatus with their starting
+            // values.
+            updateCurrentConfigurationInternalIfRequired("TimeZoneDetectorStrategyImpl:");
         }
     }
 
     @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(
+                mDetectorStatus,
+                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) {
+            String logMsg = "updateConfiguration:"
+                    + " userId=" + userId
+                    + ", configuration=" + configuration
+                    + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks;
+            updateCurrentConfigurationInternalIfRequired(logMsg);
+        }
+        return updateSuccessful;
+    }
+
+    @GuardedBy("this")
+    private void updateCurrentConfigurationInternalIfRequired(@NonNull String logMsg) {
+        ConfigurationInternal newCurrentConfigurationInternal =
+                mServiceConfigAccessor.getCurrentUserConfigurationInternal();
+        // mCurrentConfigurationInternal is null the first time this method is called.
+        ConfigurationInternal oldCurrentConfigurationInternal = mCurrentConfigurationInternal;
+
+        // If the configuration actually changed, update the cached copy synchronously and do
+        // other necessary house-keeping / (async) listener notifications.
+        if (!newCurrentConfigurationInternal.equals(oldCurrentConfigurationInternal)) {
+            mCurrentConfigurationInternal = newCurrentConfigurationInternal;
+
+            logMsg += " [oldConfiguration=" + oldCurrentConfigurationInternal
+                    + ", newConfiguration=" + newCurrentConfigurationInternal
+                    + "]";
+            logTimeZoneDebugInfo(logMsg);
+
+            // ConfigurationInternal changes can affect the detector's status.
+            updateDetectorStatus();
+
+            // The configuration and maybe the status changed so notify listeners.
+            notifyStateChangeListenersAsynchronously();
+
+            // The configuration change may have changed available suggestions or the way
+            // suggestions are used, so re-run detection.
+            doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg);
+        }
+    }
+
+    @GuardedBy("this")
+    private void notifyStateChangeListenersAsynchronously() {
+        for (StateChangeListener listener : mStateChangeListeners) {
+            // This is queuing asynchronous notification, so no need to surrender the "this" lock.
+            mStateChangeHandler.post(listener::onChange);
+        }
+    }
+
+    @Override
+    public synchronized void addChangeListener(StateChangeListener listener) {
+        mStateChangeListeners.add(listener);
+    }
+
+    @Override
     public synchronized boolean confirmTimeZone(@NonNull String timeZoneId) {
         Objects.requireNonNull(timeZoneId);
 
@@ -285,33 +392,44 @@
     }
 
     @Override
-    public synchronized void suggestGeolocationTimeZone(
-            @NonNull GeolocationTimeZoneSuggestion suggestion) {
-
+    public synchronized void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent event) {
         ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal;
         if (DBG) {
-            Slog.d(LOG_TAG, "Geolocation suggestion received."
+            Slog.d(LOG_TAG, "Location algorithm event received."
                     + " currentUserConfig=" + currentUserConfig
-                    + " newSuggestion=" + suggestion);
+                    + " event=" + event);
         }
-        Objects.requireNonNull(suggestion);
+        Objects.requireNonNull(event);
 
-        // Geolocation suggestions may be stored but not used during time zone detection if the
+        // Location algorithm events may be stored but not used during time zone detection if the
         // configuration doesn't have geo time zone detection enabled. The caller is expected to
-        // withdraw a previous suggestion (i.e. submit an "uncertain" suggestion, when geo time zone
-        // detection is disabled.
+        // withdraw a previous suggestion, i.e. submit an event containing an "uncertain"
+        // suggestion, when geo time zone detection is disabled.
 
-        // The suggestion's "effective from" time is ignored: we currently assume suggestions
-        // are made in a sensible order and the most recent is always the best one to use.
-        mLatestGeoLocationSuggestion.set(suggestion);
+        // We currently assume events are made in a sensible order and the most recent is always the
+        // best one to use.
+        mLatestLocationAlgorithmEvent.set(event);
 
-        // Update the mTelephonyTimeZoneFallbackEnabled state if needed: a certain suggestion
-        // will usually disable telephony fallback mode if it is currently enabled.
-        disableTelephonyFallbackIfNeeded();
+        // The latest location algorithm event can affect the cached detector status, so update it
+        // and notify state change listeners as needed.
+        boolean statusChanged = updateDetectorStatus();
+        if (statusChanged) {
+            notifyStateChangeListenersAsynchronously();
+        }
 
-        // Now perform auto time zone detection. The new suggestion may be used to modify the
-        // time zone setting.
-        String reason = "New geolocation time zone suggested. suggestion=" + suggestion;
+        // Manage telephony fallback state.
+        if (event.getAlgorithmStatus().couldEnableTelephonyFallback()) {
+            // An event may trigger entry into telephony fallback mode if the status
+            // indicates the location algorithm cannot work and is likely to stay not working.
+            enableTelephonyTimeZoneFallback("handleLocationAlgorithmEvent(), event=" + event);
+        } else {
+            // A certain suggestion will exit telephony fallback mode.
+            disableTelephonyFallbackIfNeeded();
+        }
+
+        // Now perform auto time zone detection. The new event may be used to modify the time zone
+        // setting.
+        String reason = "New location algorithm event received. event=" + event;
         doAutoTimeZoneDetection(currentUserConfig, reason);
     }
 
@@ -334,9 +452,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
@@ -381,38 +498,41 @@
     }
 
     @Override
-    public synchronized void enableTelephonyTimeZoneFallback() {
-        // Only do any work if fallback is currently not enabled.
+    public synchronized void enableTelephonyTimeZoneFallback(@NonNull String reason) {
+        // Only do any work to enter fallback mode if fallback is currently not already enabled.
         if (!mTelephonyTimeZoneFallbackEnabled.getValue()) {
             ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal;
             final boolean fallbackEnabled = true;
             mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>(
                     mEnvironment.elapsedRealtimeMillis(), fallbackEnabled);
 
-            String logMsg = "enableTelephonyTimeZoneFallbackMode: "
-                    + " currentUserConfig=" + currentUserConfig
-                    + ", mTelephonyTimeZoneFallbackEnabled="
-                    + mTelephonyTimeZoneFallbackEnabled;
+            String logMsg = "enableTelephonyTimeZoneFallback: "
+                    + " reason=" + reason
+                    + ", currentUserConfig=" + currentUserConfig
+                    + ", mTelephonyTimeZoneFallbackEnabled=" + mTelephonyTimeZoneFallbackEnabled;
             logTimeZoneDebugInfo(logMsg);
 
-            // mTelephonyTimeZoneFallbackEnabled and mLatestGeoLocationSuggestion interact.
-            // If there is currently a certain geolocation suggestion, then the telephony fallback
-            // value needs to be considered after changing it.
+            // mTelephonyTimeZoneFallbackEnabled and mLatestLocationAlgorithmEvent interact.
+            // If the latest location algorithm event contains a "certain" geolocation suggestion,
+            // then the telephony fallback mode needs to be (re)considered after changing it.
+            //
             // With the way that the mTelephonyTimeZoneFallbackEnabled time is currently chosen
             // above, and the fact that geolocation suggestions should never have a time in the
-            // future, the following call will be a no-op, and telephony fallback will remain
-            // enabled. This comment / call is left as a reminder that it is possible for there to
-            // be a current, "certain" geolocation suggestion when this signal arrives and it is
-            // intentional that fallback stays enabled in this case. The choice to do this
-            // is mostly for symmetry WRT the case where fallback is enabled and an old "certain"
-            // geolocation is received; that would also leave telephony fallback enabled.
-            // This choice means that telephony fallback will remain enabled until a new "certain"
-            // geolocation suggestion is received. If, instead, the next geolocation is "uncertain",
-            // then telephony fallback will occur.
+            // future, the following call will usually be a no-op, and telephony fallback mode will
+            // remain enabled. This comment / call is left as a reminder that it is possible in some
+            // cases for there to be a current, "certain" geolocation suggestion when an attempt is
+            // made to enable telephony fallback mode and it is intentional that fallback mode stays
+            // enabled in this case. The choice to do this is mostly for symmetry WRT the case where
+            // fallback is enabled and then an old "certain" geolocation suggestion is received;
+            // that would also leave telephony fallback mode enabled.
+            //
+            // This choice means that telephony fallback mode remains enabled if there is an
+            // existing "certain" suggestion until a new "certain" geolocation suggestion is
+            // received. If, instead, the next geolocation suggestion is "uncertain", then telephony
+            // fallback, i.e. the use of a telephony suggestion, will actually occur.
             disableTelephonyFallbackIfNeeded();
 
             if (currentUserConfig.isTelephonyFallbackSupported()) {
-                String reason = "enableTelephonyTimeZoneFallbackMode";
                 doAutoTimeZoneDetection(currentUserConfig, reason);
             }
         }
@@ -437,7 +557,7 @@
                 mEnvironment.getDeviceTimeZone(),
                 getLatestManualSuggestion(),
                 telephonySuggestion,
-                getLatestGeolocationSuggestion());
+                getLatestLocationAlgorithmEvent());
     }
 
     @Override
@@ -482,9 +602,10 @@
     @GuardedBy("this")
     private void doAutoTimeZoneDetection(
             @NonNull ConfigurationInternal currentUserConfig, @NonNull String detectionReason) {
-        // Use the correct algorithm based on the user's current configuration. If it changes, then
-        // detection will be re-run.
-        switch (currentUserConfig.getDetectionMode()) {
+        // Use the correct detection algorithm based on the device's config and the user's current
+        // configuration. If user config changes, then detection will be re-run.
+        @DetectionMode int detectionMode = currentUserConfig.getDetectionMode();
+        switch (detectionMode) {
             case ConfigurationInternal.DETECTION_MODE_MANUAL:
                 // No work to do.
                 break;
@@ -520,9 +641,14 @@
             case ConfigurationInternal.DETECTION_MODE_TELEPHONY:
                 doTelephonyTimeZoneDetection(detectionReason);
                 break;
+            case ConfigurationInternal.DETECTION_MODE_UNKNOWN:
+                // The "DETECTION_MODE_UNKNOWN" state can occur on devices with only location
+                // detection algorithm support and when the user's master location toggle is off.
+                Slog.i(LOG_TAG, "Unknown detection mode: " + detectionMode + ", is location off?");
+                break;
             default:
-                Slog.wtf(LOG_TAG, "Unknown detection mode: "
-                        + currentUserConfig.getDetectionMode());
+                // Coding error
+                Slog.wtf(LOG_TAG, "Unknown detection mode: " + detectionMode);
         }
     }
 
@@ -536,13 +662,15 @@
      */
     @GuardedBy("this")
     private boolean doGeolocationTimeZoneDetection(@NonNull String detectionReason) {
-        GeolocationTimeZoneSuggestion latestGeolocationSuggestion =
-                mLatestGeoLocationSuggestion.get();
-        if (latestGeolocationSuggestion == null) {
+        // Terminate early if there's nothing to do.
+        LocationAlgorithmEvent latestLocationAlgorithmEvent = mLatestLocationAlgorithmEvent.get();
+        if (latestLocationAlgorithmEvent == null
+                || latestLocationAlgorithmEvent.getSuggestion() == null) {
             return false;
         }
 
-        List<String> zoneIds = latestGeolocationSuggestion.getZoneIds();
+        GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion();
+        List<String> zoneIds = suggestion.getZoneIds();
         if (zoneIds == null) {
             // This means the originator of the suggestion is uncertain about the time zone. The
             // existing time zone setting must be left as it is but detection can go on looking for
@@ -575,13 +703,18 @@
     }
 
     /**
-     * Sets the mTelephonyTimeZoneFallbackEnabled state to {@code false} if the latest geo
-     * suggestion is a "certain" suggestion that comes after the time when telephony fallback was
-     * enabled.
+     * Sets the mTelephonyTimeZoneFallbackEnabled state to {@code false} if the latest location
+     * algorithm event contains a "certain" suggestion that comes after the time when telephony
+     * fallback was enabled.
      */
     @GuardedBy("this")
     private void disableTelephonyFallbackIfNeeded() {
-        GeolocationTimeZoneSuggestion suggestion = mLatestGeoLocationSuggestion.get();
+        LocationAlgorithmEvent latestLocationAlgorithmEvent = mLatestLocationAlgorithmEvent.get();
+        if (latestLocationAlgorithmEvent == null) {
+            return;
+        }
+
+        GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion();
         boolean isLatestSuggestionCertain = suggestion != null && suggestion.getZoneIds() != null;
         if (isLatestSuggestionCertain && mTelephonyTimeZoneFallbackEnabled.getValue()) {
             // This transition ONLY changes mTelephonyTimeZoneFallbackEnabled from
@@ -735,18 +868,31 @@
         return findBestTelephonySuggestion();
     }
 
-    private synchronized void handleConfigurationInternalChanged() {
-        ConfigurationInternal currentUserConfig =
-                mEnvironment.getCurrentUserConfigurationInternal();
-        String logMsg = "handleConfigurationInternalChanged:"
-                + " oldConfiguration=" + mCurrentConfigurationInternal
-                + ", newConfiguration=" + currentUserConfig;
-        logTimeZoneDebugInfo(logMsg);
-        mCurrentConfigurationInternal = currentUserConfig;
+    /**
+     * Handles a configuration change notification.
+     */
+    private synchronized void handleConfigurationInternalMaybeChanged() {
+        String logMsg = "handleConfigurationInternalMaybeChanged:";
+        updateCurrentConfigurationInternalIfRequired(logMsg);
+    }
 
-        // The configuration change may have changed available suggestions or the way suggestions
-        // are used, so re-run detection.
-        doAutoTimeZoneDetection(currentUserConfig, logMsg);
+    /**
+     * Called whenever the information that contributes to {@link #mDetectorStatus} could have
+     * changed. Updates the cached status snapshot if required.
+     *
+     * @return true if the status had changed and has been updated
+     */
+    @GuardedBy("this")
+    private boolean updateDetectorStatus() {
+        TimeZoneDetectorStatus newDetectorStatus = createTimeZoneDetectorStatus(
+                mCurrentConfigurationInternal, mLatestLocationAlgorithmEvent.get());
+        // mDetectorStatus is null the first time this method is called.
+        TimeZoneDetectorStatus oldDetectorStatus = mDetectorStatus;
+        boolean statusChanged = !newDetectorStatus.equals(oldDetectorStatus);
+        if (statusChanged) {
+            mDetectorStatus = newDetectorStatus;
+        }
+        return statusChanged;
     }
 
     /**
@@ -758,10 +904,10 @@
 
         ipw.increaseIndent(); // level 1
         ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal);
+        ipw.println("mDetectorStatus=" + mDetectorStatus);
         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());
@@ -782,9 +928,9 @@
         mLatestManualSuggestion.dump(ipw);
         ipw.decreaseIndent(); // level 2
 
-        ipw.println("Geolocation suggestion history:");
+        ipw.println("Location algorithm event history:");
         ipw.increaseIndent(); // level 2
-        mLatestGeoLocationSuggestion.dump(ipw);
+        mLatestLocationAlgorithmEvent.dump(ipw);
         ipw.decreaseIndent(); // level 2
 
         ipw.println("Telephony suggestion history:");
@@ -798,6 +944,7 @@
      * A method used to inspect strategy state during tests. Not intended for general use.
      */
     @VisibleForTesting
+    @Nullable
     public synchronized ManualTimeZoneSuggestion getLatestManualSuggestion() {
         return mLatestManualSuggestion.get();
     }
@@ -806,6 +953,7 @@
      * A method used to inspect strategy state during tests. Not intended for general use.
      */
     @VisibleForTesting
+    @Nullable
     public synchronized QualifiedTelephonyTimeZoneSuggestion getLatestTelephonySuggestion(
             int slotIndex) {
         return mTelephonySuggestionsBySlotIndex.get(slotIndex);
@@ -815,8 +963,9 @@
      * A method used to inspect strategy state during tests. Not intended for general use.
      */
     @VisibleForTesting
-    public synchronized GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() {
-        return mLatestGeoLocationSuggestion.get();
+    @Nullable
+    public synchronized LocationAlgorithmEvent getLatestLocationAlgorithmEvent() {
+        return mLatestLocationAlgorithmEvent.get();
     }
 
     @VisibleForTesting
@@ -824,6 +973,16 @@
         return mTelephonyTimeZoneFallbackEnabled.getValue();
     }
 
+    @VisibleForTesting
+    public synchronized ConfigurationInternal getCachedCapabilitiesAndConfigForTests() {
+        return mCurrentConfigurationInternal;
+    }
+
+    @VisibleForTesting
+    public synchronized TimeZoneDetectorStatus getCachedDetectorStatusForTests() {
+        return mDetectorStatus;
+    }
+
     /**
      * A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
      */
@@ -877,4 +1036,58 @@
     private static String formatDebugString(TimestampedValue<?> value) {
         return value.getValue() + " @ " + Duration.ofMillis(value.getReferenceTimeMillis());
     }
+
+    @NonNull
+    private static TimeZoneDetectorStatus createTimeZoneDetectorStatus(
+            @NonNull ConfigurationInternal currentConfigurationInternal,
+            @Nullable LocationAlgorithmEvent latestLocationAlgorithmEvent) {
+
+        int detectorStatus;
+        if (!currentConfigurationInternal.isAutoDetectionSupported()) {
+            detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_NOT_SUPPORTED;
+        } else if (currentConfigurationInternal.getAutoDetectionEnabledBehavior()) {
+            detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+        } else {
+            detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_NOT_RUNNING;
+        }
+
+        TelephonyTimeZoneAlgorithmStatus telephonyAlgorithmStatus =
+                createTelephonyAlgorithmStatus(currentConfigurationInternal);
+
+        LocationTimeZoneAlgorithmStatus locationAlgorithmStatus = createLocationAlgorithmStatus(
+                currentConfigurationInternal, latestLocationAlgorithmEvent);
+
+        return new TimeZoneDetectorStatus(
+                detectorStatus, telephonyAlgorithmStatus, locationAlgorithmStatus);
+    }
+
+    @NonNull
+    private static LocationTimeZoneAlgorithmStatus createLocationAlgorithmStatus(
+            ConfigurationInternal currentConfigurationInternal,
+            LocationAlgorithmEvent latestLocationAlgorithmEvent) {
+        LocationTimeZoneAlgorithmStatus locationAlgorithmStatus;
+        if (latestLocationAlgorithmEvent != null) {
+            locationAlgorithmStatus = latestLocationAlgorithmEvent.getAlgorithmStatus();
+        } else if (!currentConfigurationInternal.isGeoDetectionSupported()) {
+            locationAlgorithmStatus = LocationTimeZoneAlgorithmStatus.NOT_SUPPORTED;
+        } else if (currentConfigurationInternal.isGeoDetectionExecutionEnabled()) {
+            locationAlgorithmStatus = LocationTimeZoneAlgorithmStatus.RUNNING_NOT_REPORTED;
+        } else {
+            locationAlgorithmStatus = LocationTimeZoneAlgorithmStatus.NOT_RUNNING;
+        }
+        return locationAlgorithmStatus;
+    }
+
+    @NonNull
+    private static TelephonyTimeZoneAlgorithmStatus createTelephonyAlgorithmStatus(
+            @NonNull ConfigurationInternal currentConfigurationInternal) {
+        int algorithmStatus;
+        if (!currentConfigurationInternal.isTelephonyDetectionSupported()) {
+            algorithmStatus = DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+        } else {
+            // The telephony detector is passive, so we treat it as "running".
+            algorithmStatus = DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+        }
+        return new TelephonyTimeZoneAlgorithmStatus(algorithmStatus);
+    }
 }
diff --git a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
index a1de294..71aa10d 100644
--- a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
@@ -53,7 +53,7 @@
     }
 
     @Override
-    void onInitialize() {
+    boolean onInitialize() {
         mProxy.initialize(new LocationTimeZoneProviderProxy.Listener() {
             @Override
             public void onReportTimeZoneProviderEvent(
@@ -71,6 +71,7 @@
                 handleTemporaryFailure("onProviderUnbound()");
             }
         });
+        return true;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/timezonedetector/location/DisabledLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/DisabledLocationTimeZoneProvider.java
new file mode 100644
index 0000000..5d6184e
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/location/DisabledLocationTimeZoneProvider.java
@@ -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.server.timezonedetector.location;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+
+import java.time.Duration;
+
+/**
+ * A {@link LocationTimeZoneProvider} that provides minimal responses needed to operate correctly
+ * when there is no "real" provider configured / enabled. This is used when the platform supports
+ * more providers than are needed for an Android deployment.
+ *
+ * <p>That is, the {@link LocationTimeZoneProviderController} supports a primary and a secondary
+ * {@link LocationTimeZoneProvider}, but if only a primary is configured, the secondary provider
+ * config will marked as "disabled" and the {@link LocationTimeZoneProvider} implementation will use
+ * {@link DisabledLocationTimeZoneProvider}. The {@link DisabledLocationTimeZoneProvider} fails
+ * initialization and immediately moves to a "permanent failure" state, which ensures the {@link
+ * LocationTimeZoneProviderController} correctly categorizes it and won't attempt to use it.
+ */
+class DisabledLocationTimeZoneProvider extends LocationTimeZoneProvider {
+
+    DisabledLocationTimeZoneProvider(
+            @NonNull ProviderMetricsLogger providerMetricsLogger,
+            @NonNull ThreadingDomain threadingDomain,
+            @NonNull String providerName,
+            boolean recordStateChanges) {
+        super(providerMetricsLogger, threadingDomain, providerName, x -> x, recordStateChanges);
+    }
+
+    @Override
+    boolean onInitialize() {
+        // Fail initialization, preventing further use.
+        return false;
+    }
+
+    @Override
+    void onDestroy() {
+    }
+
+    @Override
+    void onStartUpdates(@NonNull Duration initializationTimeout,
+            @NonNull Duration eventFilteringAgeThreshold) {
+        throw new UnsupportedOperationException("Provider is disabled");
+    }
+
+    @Override
+    void onStopUpdates() {
+        throw new UnsupportedOperationException("Provider is disabled");
+    }
+
+    @Override
+    public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
+        synchronized (mSharedLock) {
+            ipw.println("{DisabledLocationTimeZoneProvider}");
+            ipw.println("mProviderName=" + mProviderName);
+            ipw.println("mCurrentState=" + mCurrentState);
+        }
+    }
+
+    @Override
+    public String toString() {
+        synchronized (mSharedLock) {
+            return "DisabledLocationTimeZoneProvider{"
+                    + "mProviderName=" + mProviderName
+                    + ", mCurrentState=" + mCurrentState
+                    + '}';
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
index 36ab111d..8d98544 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
@@ -447,11 +447,18 @@
 
         @NonNull
         LocationTimeZoneProvider createProvider() {
-            LocationTimeZoneProviderProxy proxy = createProxy();
             ProviderMetricsLogger providerMetricsLogger = new RealProviderMetricsLogger(mIndex);
-            return new BinderLocationTimeZoneProvider(
-                    providerMetricsLogger, mThreadingDomain, mName, proxy,
-                    mServiceConfigAccessor.getRecordStateChangesForTests());
+
+            String mode = getMode();
+            if (Objects.equals(mode, PROVIDER_MODE_DISABLED)) {
+                return new DisabledLocationTimeZoneProvider(providerMetricsLogger, mThreadingDomain,
+                        mName, mServiceConfigAccessor.getRecordStateChangesForTests());
+            } else {
+                LocationTimeZoneProviderProxy proxy = createBinderProxy();
+                return new BinderLocationTimeZoneProvider(
+                        providerMetricsLogger, mThreadingDomain, mName, proxy,
+                        mServiceConfigAccessor.getRecordStateChangesForTests());
+            }
         }
 
         @Override
@@ -460,17 +467,6 @@
             ipw.printf("getPackageName()=%s\n", getPackageName());
         }
 
-        @NonNull
-        private LocationTimeZoneProviderProxy createProxy() {
-            String mode = getMode();
-            if (Objects.equals(mode, PROVIDER_MODE_DISABLED)) {
-                return new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
-            } else {
-                // mode == PROVIDER_MODE_OVERRIDE_ENABLED (or unknown).
-                return createRealProxy();
-            }
-        }
-
         /** Returns the mode of the provider (enabled/disabled). */
         @NonNull
         private String getMode() {
@@ -482,7 +478,7 @@
         }
 
         @NonNull
-        private RealLocationTimeZoneProviderProxy createRealProxy() {
+        private RealLocationTimeZoneProviderProxy createBinderProxy() {
             String providerServiceAction = mServiceAction;
             boolean isTestProvider = isTestProvider();
             String providerPackageName = getPackageName();
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
index 1f752f4..e90a1fe 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
-import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
 import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState;
 import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State;
 
@@ -32,14 +32,14 @@
 final class LocationTimeZoneManagerServiceState {
 
     private final @State String mControllerState;
-    @Nullable private final GeolocationTimeZoneSuggestion mLastSuggestion;
+    @Nullable private final LocationAlgorithmEvent mLastEvent;
     @NonNull private final List<@State String> mControllerStates;
     @NonNull private final List<ProviderState> mPrimaryProviderStates;
     @NonNull private final List<ProviderState> mSecondaryProviderStates;
 
     LocationTimeZoneManagerServiceState(@NonNull Builder builder) {
         mControllerState = builder.mControllerState;
-        mLastSuggestion = builder.mLastSuggestion;
+        mLastEvent = builder.mLastEvent;
         mControllerStates = Objects.requireNonNull(builder.mControllerStates);
         mPrimaryProviderStates = Objects.requireNonNull(builder.mPrimaryProviderStates);
         mSecondaryProviderStates = Objects.requireNonNull(builder.mSecondaryProviderStates);
@@ -50,8 +50,8 @@
     }
 
     @Nullable
-    public GeolocationTimeZoneSuggestion getLastSuggestion() {
-        return mLastSuggestion;
+    public LocationAlgorithmEvent getLastEvent() {
+        return mLastEvent;
     }
 
     @NonNull
@@ -73,7 +73,7 @@
     public String toString() {
         return "LocationTimeZoneManagerServiceState{"
                 + "mControllerState=" + mControllerState
-                + ", mLastSuggestion=" + mLastSuggestion
+                + ", mLastEvent=" + mLastEvent
                 + ", mControllerStates=" + mControllerStates
                 + ", mPrimaryProviderStates=" + mPrimaryProviderStates
                 + ", mSecondaryProviderStates=" + mSecondaryProviderStates
@@ -83,7 +83,7 @@
     static final class Builder {
 
         private @State String mControllerState;
-        private GeolocationTimeZoneSuggestion mLastSuggestion;
+        private LocationAlgorithmEvent mLastEvent;
         private List<@State String> mControllerStates;
         private List<ProviderState> mPrimaryProviderStates;
         private List<ProviderState> mSecondaryProviderStates;
@@ -95,8 +95,8 @@
         }
 
         @NonNull
-        Builder setLastSuggestion(@NonNull GeolocationTimeZoneSuggestion lastSuggestion) {
-            mLastSuggestion = Objects.requireNonNull(lastSuggestion);
+        Builder setLastEvent(@NonNull LocationAlgorithmEvent lastEvent) {
+            mLastEvent = Objects.requireNonNull(lastEvent);
             return this;
         }
 
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
index 60bbea7..cefd0b5 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
@@ -15,6 +15,10 @@
  */
 package com.android.server.timezonedetector.location;
 
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
 import static android.app.time.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO;
 import static android.app.time.LocationTimeZoneManager.NULL_PACKAGE_NAME_TOKEN;
 import static android.app.time.LocationTimeZoneManager.SERVICE_NAME;
@@ -51,9 +55,14 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
 import android.app.time.GeolocationTimeZoneSuggestionProto;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.LocationTimeZoneAlgorithmStatusProto;
 import android.app.time.LocationTimeZoneManagerProto;
 import android.app.time.LocationTimeZoneManagerServiceStateProto;
+import android.app.time.LocationTimeZoneProviderEventProto;
+import android.app.time.TimeZoneDetectorProto;
 import android.app.time.TimeZoneProviderStateProto;
 import android.app.timezonedetector.TimeZoneDetector;
 import android.os.ShellCommand;
@@ -62,6 +71,7 @@
 
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
 import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
 import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State;
 
@@ -239,19 +249,39 @@
             outputStream = new DualDumpOutputStream(
                     new IndentingPrintWriter(getOutPrintWriter(), "  "));
         }
-        if (state.getLastSuggestion() != null) {
-            GeolocationTimeZoneSuggestion lastSuggestion = state.getLastSuggestion();
-            long lastSuggestionToken = outputStream.start(
-                    "last_suggestion", LocationTimeZoneManagerServiceStateProto.LAST_SUGGESTION);
-            for (String zoneId : lastSuggestion.getZoneIds()) {
-                outputStream.write(
-                        "zone_ids" , GeolocationTimeZoneSuggestionProto.ZONE_IDS, zoneId);
+
+        if (state.getLastEvent() != null) {
+            LocationAlgorithmEvent lastEvent = state.getLastEvent();
+            long lastEventToken = outputStream.start(
+                    "last_event", LocationTimeZoneManagerServiceStateProto.LAST_EVENT);
+
+            // lastEvent.algorithmStatus
+            LocationTimeZoneAlgorithmStatus algorithmStatus = lastEvent.getAlgorithmStatus();
+            long algorithmStatusToken = outputStream.start(
+                    "algorithm_status", LocationTimeZoneProviderEventProto.ALGORITHM_STATUS);
+            outputStream.write("status", LocationTimeZoneAlgorithmStatusProto.STATUS,
+                    convertDetectionAlgorithmStatusToEnumToProtoEnum(algorithmStatus.getStatus()));
+            outputStream.end(algorithmStatusToken);
+
+            // lastEvent.suggestion
+            if (lastEvent.getSuggestion() != null) {
+                long suggestionToken = outputStream.start(
+                        "suggestion", LocationTimeZoneProviderEventProto.SUGGESTION);
+                GeolocationTimeZoneSuggestion lastSuggestion = lastEvent.getSuggestion();
+                for (String zoneId : lastSuggestion.getZoneIds()) {
+                    outputStream.write(
+                            "zone_ids", GeolocationTimeZoneSuggestionProto.ZONE_IDS, zoneId);
+                }
+                outputStream.end(suggestionToken);
             }
-            for (String debugInfo : lastSuggestion.getDebugInfo()) {
+
+            // lastEvent.debugInfo
+            for (String debugInfo : lastEvent.getDebugInfo()) {
                 outputStream.write(
-                        "debug_info", GeolocationTimeZoneSuggestionProto.DEBUG_INFO, debugInfo);
+                        "debug_info", LocationTimeZoneProviderEventProto.DEBUG_INFO, debugInfo);
             }
-            outputStream.end(lastSuggestionToken);
+
+            outputStream.end(lastEventToken);
         }
 
         writeControllerStates(outputStream, state.getControllerStates());
@@ -330,6 +360,22 @@
         }
     }
 
+    private static int convertDetectionAlgorithmStatusToEnumToProtoEnum(
+            @DetectionAlgorithmStatus int statusEnum) {
+        switch (statusEnum) {
+            case DETECTION_ALGORITHM_STATUS_UNKNOWN:
+                return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_UNKNOWN;
+            case DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED:
+                return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+            case DETECTION_ALGORITHM_STATUS_NOT_RUNNING:
+                return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+            case DETECTION_ALGORITHM_STATUS_RUNNING:
+                return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_RUNNING;
+            default:
+                throw new IllegalArgumentException("Unknown statusEnum=" + statusEnum);
+        }
+    }
+
     private void reportError(@NonNull Throwable e) {
         PrintWriter errPrintWriter = getErrPrintWriter();
         errPrintWriter.println("Error: ");
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
index 90540b0..ba7c328 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
@@ -16,6 +16,10 @@
 
 package com.android.server.timezonedetector.location;
 
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
 import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
 import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
 import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
@@ -33,9 +37,11 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.service.timezone.TimeZoneProviderEvent;
+import android.service.timezone.TimeZoneProviderStatus;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -294,6 +300,40 @@
                     || stateEnum == PROVIDER_STATE_DESTROYED;
         }
 
+        /**
+         * Maps the internal state enum value to one of the status values exposed to the layers
+         * above.
+         */
+        public @ProviderStatus int getProviderStatus() {
+            switch (stateEnum) {
+                case PROVIDER_STATE_STARTED_INITIALIZING:
+                    return PROVIDER_STATUS_NOT_READY;
+                case PROVIDER_STATE_STARTED_CERTAIN:
+                    return PROVIDER_STATUS_IS_CERTAIN;
+                case PROVIDER_STATE_STARTED_UNCERTAIN:
+                    return PROVIDER_STATUS_IS_UNCERTAIN;
+                case PROVIDER_STATE_PERM_FAILED:
+                    // Perm failed means the providers wasn't configured, configured properly,
+                    // or has removed itself for other reasons, e.g. turned-down server.
+                    return PROVIDER_STATUS_NOT_PRESENT;
+                case PROVIDER_STATE_STOPPED:
+                case PROVIDER_STATE_DESTROYED:
+                    // This is a "safe" default that best describes a provider that isn't in one of
+                    // the more obviously mapped states.
+                    return PROVIDER_STATUS_NOT_READY;
+                case PROVIDER_STATE_UNKNOWN:
+                default:
+                    throw new IllegalStateException(
+                            "Unknown state enum:" + prettyPrintStateEnum(stateEnum));
+            }
+        }
+
+        /** Returns the status reported by the provider, if available. */
+        @Nullable
+        TimeZoneProviderStatus getReportedStatus() {
+            return event == null ? null : event.getTimeZoneProviderStatus();
+        }
+
         @Override
         public String toString() {
             // this.provider is omitted deliberately to avoid recursion, since the provider holds
@@ -408,13 +448,21 @@
             currentState = currentState.newState(PROVIDER_STATE_STOPPED, null, null, "initialize");
             setCurrentState(currentState, false);
 
+            boolean initializationSuccess;
+            String initializationFailureReason;
             // Guard against uncaught exceptions due to initialization problems.
             try {
-                onInitialize();
+                initializationSuccess = onInitialize();
+                initializationFailureReason = "onInitialize() returned false";
             } catch (RuntimeException e) {
-                warnLog("Unable to initialize the provider", e);
+                warnLog("Unable to initialize the provider due to exception", e);
+                initializationSuccess = false;
+                initializationFailureReason = "onInitialize() threw exception:" + e.getMessage();
+            }
+
+            if (!initializationSuccess) {
                 currentState = currentState.newState(PROVIDER_STATE_PERM_FAILED, null, null,
-                        "Failed to initialize: " + e.getMessage());
+                        "Failed to initialize: " + initializationFailureReason);
                 setCurrentState(currentState, true);
             }
         }
@@ -422,9 +470,12 @@
 
     /**
      * Implemented by subclasses to do work during {@link #initialize}.
+     *
+     * @return returns {@code true} on success, {@code false} if the provider should be considered
+     *   "permanently failed" / disabled
      */
     @GuardedBy("mSharedLock")
-    abstract void onInitialize();
+    abstract boolean onInitialize();
 
     /**
      * Destroys the provider. Called after the provider is stopped. This instance will not be called
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
index a9b9884..ed7ea00 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
@@ -35,6 +35,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringDef;
+import android.app.time.DetectorStatusTypes;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus;
 import android.service.timezone.TimeZoneProviderEvent;
 import android.service.timezone.TimeZoneProviderSuggestion;
 import android.util.IndentingPrintWriter;
@@ -44,6 +48,7 @@
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.Dumpable;
 import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
 import com.android.server.timezonedetector.ReferenceWithHistory;
 import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue;
 
@@ -83,8 +88,7 @@
  * <p>All incoming calls except for {@link
  * LocationTimeZoneProviderController#dump(android.util.IndentingPrintWriter, String[])} must be
  * made on the {@link android.os.Handler} thread of the {@link ThreadingDomain} passed to {@link
- * #LocationTimeZoneProviderController(ThreadingDomain, LocationTimeZoneProvider,
- * LocationTimeZoneProvider)}.
+ * #LocationTimeZoneProviderController}.
  *
  * <p>Provider / controller integration notes:
  *
@@ -172,10 +176,10 @@
     @GuardedBy("mSharedLock")
     private final ReferenceWithHistory<@State String> mState = new ReferenceWithHistory<>(10);
 
-    /** Contains the last suggestion actually made, if there is one. */
+    /** Contains the last event reported, if there is one. */
     @GuardedBy("mSharedLock")
     @Nullable
-    private GeolocationTimeZoneSuggestion mLastSuggestion;
+    private LocationAlgorithmEvent mLastEvent;
 
     LocationTimeZoneProviderController(@NonNull ThreadingDomain threadingDomain,
             @NonNull MetricsLogger metricsLogger,
@@ -213,7 +217,7 @@
             setState(STATE_PROVIDERS_INITIALIZING);
             mPrimaryProvider.initialize(providerListener);
             mSecondaryProvider.initialize(providerListener);
-            setState(STATE_STOPPED);
+            setStateAndReportStatusOnlyEvent(STATE_STOPPED, "initialize()");
 
             alterProvidersStartedStateIfRequired(
                     null /* oldConfiguration */, mCurrentUserConfiguration);
@@ -273,13 +277,51 @@
             // Enter destroyed state.
             mPrimaryProvider.destroy();
             mSecondaryProvider.destroy();
-            setState(STATE_DESTROYED);
+            setStateAndReportStatusOnlyEvent(STATE_DESTROYED, "destroy()");
         }
     }
 
     /**
-     * Updates {@link #mState} if needed, and performs all the record-keeping / callbacks associated
-     * with state changes.
+     * Sets the state and reports an event containing the algorithm status and a {@code null}
+     * suggestion.
+     */
+    @GuardedBy("mSharedLock")
+    private void setStateAndReportStatusOnlyEvent(@State String state, @NonNull String reason) {
+        setState(state);
+
+        final GeolocationTimeZoneSuggestion suggestion = null;
+        LocationAlgorithmEvent event =
+                new LocationAlgorithmEvent(generateCurrentAlgorithmStatus(), suggestion);
+        event.addDebugInfo(reason);
+        reportEvent(event);
+    }
+
+    /**
+     * Reports an event containing the algorithm status and the supplied suggestion.
+     */
+    @GuardedBy("mSharedLock")
+    private void reportSuggestionEvent(
+            @NonNull GeolocationTimeZoneSuggestion suggestion, @NonNull String reason) {
+        LocationTimeZoneAlgorithmStatus algorithmStatus = generateCurrentAlgorithmStatus();
+        LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+                algorithmStatus, suggestion);
+        event.addDebugInfo(reason);
+        reportEvent(event);
+    }
+
+    /**
+     * Sends an event immediately. This method updates {@link #mLastEvent}.
+     */
+    @GuardedBy("mSharedLock")
+    private void reportEvent(@NonNull LocationAlgorithmEvent event) {
+        debugLog("makeSuggestion: suggestion=" + event);
+        mCallback.sendEvent(event);
+        mLastEvent = event;
+    }
+
+    /**
+     * Updates the state if needed. This includes setting {@link #mState} and performing all the
+     * record-keeping / callbacks associated with state changes.
      */
     @GuardedBy("mSharedLock")
     private void setState(@State String state) {
@@ -300,17 +342,7 @@
         // By definition, if both providers are stopped, the controller is uncertain.
         cancelUncertaintyTimeout();
 
-        // If a previous "certain" suggestion has been made, then a new "uncertain"
-        // suggestion must now be made to indicate the controller {does not / no longer has}
-        // an opinion and will not be sending further updates (until at least the providers are
-        // re-started).
-        if (Objects.equals(mState.get(), STATE_CERTAIN)) {
-            GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
-                    mEnvironment.elapsedRealtimeMillis(),
-                    "Withdraw previous suggestion, providers are stopping: " + reason);
-            makeSuggestion(suggestion, STATE_UNCERTAIN);
-        }
-        setState(STATE_STOPPED);
+        setStateAndReportStatusOnlyEvent(STATE_STOPPED, "Providers stopped: " + reason);
     }
 
     @GuardedBy("mSharedLock")
@@ -381,7 +413,7 @@
         //    timeout started when the primary entered {started uncertain} should be cancelled.
 
         if (newIsGeoDetectionExecutionEnabled) {
-            setState(STATE_INITIALIZING);
+            setStateAndReportStatusOnlyEvent(STATE_INITIALIZING, "initializing()");
 
             // Try to start the primary provider.
             tryStartProvider(mPrimaryProvider, newConfiguration);
@@ -397,13 +429,11 @@
                 ProviderState newSecondaryState = mSecondaryProvider.getCurrentState();
                 if (!newSecondaryState.isStarted()) {
                     // If both providers are {perm failed} then the controller immediately
-                    // reports uncertain.
-                    GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
-                            mEnvironment.elapsedRealtimeMillis(),
-                            "Providers are failed:"
-                                    + " primary=" + mPrimaryProvider.getCurrentState()
-                                    + " secondary=" + mPrimaryProvider.getCurrentState());
-                    makeSuggestion(suggestion, STATE_FAILED);
+                    // reports the failure.
+                    String reason = "Providers are failed:"
+                            + " primary=" + mPrimaryProvider.getCurrentState()
+                            + " secondary=" + mPrimaryProvider.getCurrentState();
+                    setStateAndReportStatusOnlyEvent(STATE_FAILED, reason);
                 }
             }
         } else {
@@ -537,12 +567,10 @@
 
             // If both providers are now terminated, then a suggestion must be sent informing the
             // time zone detector that there are no further updates coming in the future.
-            GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
-                    mEnvironment.elapsedRealtimeMillis(),
-                    "Both providers are terminated:"
-                            + " primary=" + primaryCurrentState.provider
-                            + ", secondary=" + secondaryCurrentState.provider);
-            makeSuggestion(suggestion, STATE_FAILED);
+            String reason = "Both providers are terminated:"
+                    + " primary=" + primaryCurrentState.provider
+                    + ", secondary=" + secondaryCurrentState.provider;
+            setStateAndReportStatusOnlyEvent(STATE_FAILED, reason);
         }
     }
 
@@ -615,6 +643,9 @@
 
         TimeZoneProviderSuggestion providerSuggestion = providerEvent.getSuggestion();
 
+        // Set the current state so it is correct when the suggestion event is created.
+        setState(STATE_CERTAIN);
+
         // For the suggestion's effectiveFromElapsedMillis, use the time embedded in the provider's
         // suggestion (which indicates the time when the provider detected the location used to
         // establish the time zone).
@@ -623,15 +654,13 @@
         // this would hinder the ability for the time_zone_detector to judge which suggestions are
         // based on newer information when comparing suggestions between different sources.
         long effectiveFromElapsedMillis = providerSuggestion.getElapsedRealtimeMillis();
-        GeolocationTimeZoneSuggestion geoSuggestion =
+        GeolocationTimeZoneSuggestion suggestion =
                 GeolocationTimeZoneSuggestion.createCertainSuggestion(
                         effectiveFromElapsedMillis, providerSuggestion.getTimeZoneIds());
-
-        String debugInfo = "Event received provider=" + provider
+        String debugInfo = "Provider event received: provider=" + provider
                 + ", providerEvent=" + providerEvent
                 + ", suggestionCreationTime=" + mEnvironment.elapsedRealtimeMillis();
-        geoSuggestion.addDebugInfo(debugInfo);
-        makeSuggestion(geoSuggestion, STATE_CERTAIN);
+        reportSuggestionEvent(suggestion, debugInfo);
     }
 
     @Override
@@ -647,7 +676,7 @@
                     + mEnvironment.getProviderInitializationTimeoutFuzz());
             ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay());
             ipw.println("mState=" + mState.get());
-            ipw.println("mLastSuggestion=" + mLastSuggestion);
+            ipw.println("mLastEvent=" + mLastEvent);
 
             ipw.println("State history:");
             ipw.increaseIndent(); // level 2
@@ -668,19 +697,6 @@
         }
     }
 
-    /**
-     * Sends an immediate suggestion and enters a new state if needed. This method updates
-     * mLastSuggestion and changes mStateEnum / reports the new state for metrics.
-     */
-    @GuardedBy("mSharedLock")
-    private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion,
-            @State String newState) {
-        debugLog("makeSuggestion: suggestion=" + suggestion);
-        mCallback.suggest(suggestion);
-        mLastSuggestion = suggestion;
-        setState(newState);
-    }
-
     /** Clears the uncertainty timeout. */
     @GuardedBy("mSharedLock")
     private void cancelUncertaintyTimeout() {
@@ -688,18 +704,16 @@
     }
 
     /**
-     * Called when a provider has become "uncertain" about the time zone.
+     * Called when a provider has reported it is "uncertain" about the time zone.
      *
      * <p>A provider is expected to report its uncertainty as soon as it becomes uncertain, as
      * this enables the most flexibility for the controller to start other providers when there are
-     * multiple ones available. The controller is therefore responsible for deciding when to make a
-     * "uncertain" suggestion to the downstream time zone detector.
+     * multiple ones available. The controller is therefore responsible for deciding when to pass
+     * the "uncertain" suggestion to the downstream time zone detector.
      *
      * <p>This method schedules an "uncertainty" timeout (if one isn't already scheduled) to be
      * triggered later if nothing else preempts it. It can be preempted if the provider becomes
-     * certain (or does anything else that calls {@link
-     * #makeSuggestion(GeolocationTimeZoneSuggestion, String)}) within {@link
-     * Environment#getUncertaintyDelay()}. Preemption causes the scheduled
+     * certain within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled
      * "uncertainty" timeout to be cancelled. If the provider repeatedly sends uncertainty events
      * within the uncertainty delay period, those events are effectively ignored (i.e. the timeout
      * is not reset each time).
@@ -741,6 +755,8 @@
         synchronized (mSharedLock) {
             long afterUncertaintyTimeoutElapsedMillis = mEnvironment.elapsedRealtimeMillis();
 
+            setState(STATE_UNCERTAIN);
+
             // For the effectiveFromElapsedMillis suggestion property, use the
             // uncertaintyStartedElapsedMillis. This is the time when the provider first reported
             // uncertainty, i.e. before the uncertainty timeout.
@@ -749,30 +765,65 @@
             // the location_time_zone_manager finally confirms that the time zone was uncertain,
             // but the suggestion property allows the information to be back-dated, which should
             // help when comparing suggestions from different sources.
-            GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
-                    uncertaintyStartedElapsedMillis,
-                    "Uncertainty timeout triggered for " + provider.getName() + ":"
-                            + " primary=" + mPrimaryProvider
-                            + ", secondary=" + mSecondaryProvider
-                            + ", uncertaintyStarted="
-                            + Duration.ofMillis(uncertaintyStartedElapsedMillis)
-                            + ", afterUncertaintyTimeout="
-                            + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis)
-                            + ", uncertaintyDelay=" + uncertaintyDelay
-            );
-            makeSuggestion(suggestion, STATE_UNCERTAIN);
+            GeolocationTimeZoneSuggestion suggestion =
+                    GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+                            uncertaintyStartedElapsedMillis);
+            String debugInfo = "Uncertainty timeout triggered for " + provider.getName() + ":"
+                    + " primary=" + mPrimaryProvider
+                    + ", secondary=" + mSecondaryProvider
+                    + ", uncertaintyStarted="
+                    + Duration.ofMillis(uncertaintyStartedElapsedMillis)
+                    + ", afterUncertaintyTimeout="
+                    + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis)
+                    + ", uncertaintyDelay=" + uncertaintyDelay;
+            reportSuggestionEvent(suggestion, debugInfo);
         }
     }
 
+    @GuardedBy("mSharedLock")
     @NonNull
-    private static GeolocationTimeZoneSuggestion createUncertainSuggestion(
-            @ElapsedRealtimeLong long effectiveFromElapsedMillis,
-            @NonNull String reason) {
-        GeolocationTimeZoneSuggestion suggestion =
-                GeolocationTimeZoneSuggestion.createUncertainSuggestion(
-                        effectiveFromElapsedMillis);
-        suggestion.addDebugInfo(reason);
-        return suggestion;
+    private LocationTimeZoneAlgorithmStatus generateCurrentAlgorithmStatus() {
+        @State String controllerState = mState.get();
+        ProviderState primaryProviderState = mPrimaryProvider.getCurrentState();
+        ProviderState secondaryProviderState = mSecondaryProvider.getCurrentState();
+        return createAlgorithmStatus(controllerState, primaryProviderState, secondaryProviderState);
+    }
+
+    @NonNull
+    private static LocationTimeZoneAlgorithmStatus createAlgorithmStatus(
+            @NonNull @State String controllerState,
+            @NonNull ProviderState primaryProviderState,
+            @NonNull ProviderState secondaryProviderState) {
+
+        @DetectionAlgorithmStatus int algorithmStatus =
+                mapControllerStateToDetectionAlgorithmStatus(controllerState);
+        @ProviderStatus int primaryProviderStatus = primaryProviderState.getProviderStatus();
+        @ProviderStatus int secondaryProviderStatus = secondaryProviderState.getProviderStatus();
+
+        // Neither provider is running. The algorithm is not running.
+        return new LocationTimeZoneAlgorithmStatus(algorithmStatus,
+                primaryProviderStatus, primaryProviderState.getReportedStatus(),
+                secondaryProviderStatus, secondaryProviderState.getReportedStatus());
+    }
+
+    /**
+     * Maps the internal state enum value to one of the status values exposed to the layers above.
+     */
+    private static @DetectionAlgorithmStatus int mapControllerStateToDetectionAlgorithmStatus(
+            @NonNull @State String controllerState) {
+        switch (controllerState) {
+            case STATE_INITIALIZING:
+            case STATE_PROVIDERS_INITIALIZING:
+            case STATE_CERTAIN:
+            case STATE_UNCERTAIN:
+                return DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+            case STATE_STOPPED:
+            case STATE_DESTROYED:
+            case STATE_FAILED:
+            case STATE_UNKNOWN:
+            default:
+                return DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+        }
     }
 
     /**
@@ -798,8 +849,8 @@
         synchronized (mSharedLock) {
             LocationTimeZoneManagerServiceState.Builder builder =
                     new LocationTimeZoneManagerServiceState.Builder();
-            if (mLastSuggestion != null) {
-                builder.setLastSuggestion(mLastSuggestion);
+            if (mLastEvent != null) {
+                builder.setLastEvent(mLastEvent);
             }
             builder.setControllerState(mState.get())
                     .setStateChanges(mRecordedStates)
@@ -867,17 +918,15 @@
     abstract static class Callback {
 
         @NonNull protected final ThreadingDomain mThreadingDomain;
-        @NonNull protected final Object mSharedLock;
 
         Callback(@NonNull ThreadingDomain threadingDomain) {
             mThreadingDomain = Objects.requireNonNull(threadingDomain);
-            mSharedLock = threadingDomain.getLockObject();
         }
 
         /**
          * Suggests the latest time zone state for the device.
          */
-        abstract void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion);
+        abstract void sendEvent(@NonNull LocationAlgorithmEvent event);
     }
 
     /**
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
index 0c751aa..7eb7e01 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 
 import com.android.server.LocalServices;
-import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
 import com.android.server.timezonedetector.TimeZoneDetectorInternal;
 
 /**
@@ -34,11 +34,11 @@
     }
 
     @Override
-    void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion) {
+    void sendEvent(@NonNull LocationAlgorithmEvent event) {
         mThreadingDomain.assertCurrentThread();
 
         TimeZoneDetectorInternal timeZoneDetector =
                 LocalServices.getService(TimeZoneDetectorInternal.class);
-        timeZoneDetector.suggestGeolocationTimeZone(suggestion);
+        timeZoneDetector.handleLocationAlgorithmEvent(event);
     }
 }
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/timezonedetector/location/NullLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java
deleted file mode 100644
index 9cb1813..0000000
--- a/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.timezonedetector.location;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.SystemClock;
-import android.service.timezone.TimeZoneProviderEvent;
-import android.util.IndentingPrintWriter;
-
-/**
- * A {@link LocationTimeZoneProviderProxy} that provides minimal responses needed for the {@link
- * BinderLocationTimeZoneProvider} to operate correctly when there is no "real" provider
- * configured / enabled. This can be used during development / testing, or in a production build
- * when the platform supports more providers than are needed for an Android deployment.
- *
- * <p>For example, if the {@link LocationTimeZoneProviderController} supports a primary
- * and a secondary {@link LocationTimeZoneProvider}, but only a primary is configured, the secondary
- * config will be left null and the {@link LocationTimeZoneProviderProxy} implementation will be
- * defaulted to a {@link NullLocationTimeZoneProviderProxy}. The {@link
- * NullLocationTimeZoneProviderProxy} sends a "permanent failure" event immediately after being
- * started for the first time, which ensures the {@link LocationTimeZoneProviderController} won't
- * expect any further {@link TimeZoneProviderEvent}s to come from it, and won't attempt to use it
- * again.
- */
-class NullLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
-
-    /** Creates the instance. */
-    NullLocationTimeZoneProviderProxy(
-            @NonNull Context context, @NonNull ThreadingDomain threadingDomain) {
-        super(context, threadingDomain);
-    }
-
-    @Override
-    void onInitialize() {
-        // No-op
-    }
-
-    @Override
-    void onDestroy() {
-        // No-op
-    }
-
-    @Override
-    void setRequest(@NonNull TimeZoneProviderRequest request) {
-        if (request.sendUpdates()) {
-            TimeZoneProviderEvent event = TimeZoneProviderEvent.createPermanentFailureEvent(
-                    SystemClock.elapsedRealtime(), "Provider is disabled");
-            handleTimeZoneProviderEvent(event);
-        }
-    }
-
-    @Override
-    public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
-        synchronized (mSharedLock) {
-            ipw.println("{NullLocationTimeZoneProviderProxy}");
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
index 8a6f927..aa2b74e 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
@@ -58,7 +58,7 @@
         if (hasInvalidZones(event)) {
             TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder(
                     event.getTimeZoneProviderStatus())
-                    .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
                     .build();
             return TimeZoneProviderEvent.createUncertainEvent(
                     event.getCreationElapsedMillis(), providerStatus);
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 0b1f6b9..f971db9 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -107,6 +107,7 @@
     // Trust state
     private boolean mTrusted;
     private boolean mWaitingForTrustableDowngrade = false;
+    private boolean mWithinSecurityLockdownWindow = false;
     private boolean mTrustable;
     private CharSequence mMessage;
     private boolean mDisplayTrustGrantedMessage;
@@ -160,6 +161,7 @@
                     mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
                     if ((flags & FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) {
                         mWaitingForTrustableDowngrade = true;
+                        setSecurityWindowTimer();
                     } else {
                         mWaitingForTrustableDowngrade = false;
                     }
@@ -452,6 +454,9 @@
             if (mBound) {
                 scheduleRestart();
             }
+            if (mWithinSecurityLockdownWindow) {
+                mTrustManagerService.lockUser(mUserId);
+            }
             // mTrustDisabledByDpm maintains state
         }
     };
@@ -673,6 +678,22 @@
         }
     }
 
+    private void setSecurityWindowTimer() {
+        mWithinSecurityLockdownWindow = true;
+        long expiration = SystemClock.elapsedRealtime() + (15 * 1000); // timer for 15 seconds
+        mAlarmManager.setExact(
+                AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                expiration,
+                TAG,
+                new AlarmManager.OnAlarmListener() {
+                    @Override
+                    public void onAlarm() {
+                        mWithinSecurityLockdownWindow = false;
+                    }
+                },
+                Handler.getMain());
+    }
+
     public boolean isManagingTrust() {
         return mManagingTrust && !mTrustDisabledByDpm;
     }
@@ -691,7 +712,6 @@
 
     public void destroy() {
         mHandler.removeMessages(MSG_RESTART_TIMEOUT);
-
         if (!mBound) {
             return;
         }
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index cd0096b..c192057 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1752,6 +1752,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.TRUST_LISTENER)
         @Override
         public boolean isTrustUsuallyManaged(int userId) {
+            super.isTrustUsuallyManaged_enforcePermission();
+
             return isTrustUsuallyManagedInternal(userId);
         }
 
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index f52f0b7..2c8fd96 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1066,7 +1066,28 @@
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
+        }
 
+        @Override
+        public void notifyRecordingStopped(IBinder sessionToken, String recordingId, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyRecordingStopped");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyRecordingStopped(recordingId);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyRecordingStopped", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
 
         @Override
@@ -2253,6 +2274,23 @@
         }
 
         @Override
+        public void onRequestStopRecording(String recordingId) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestStopRecording");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestStopRecording(recordingId, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestStopRecording", e);
+                }
+            }
+        }
+
+        @Override
         public void onRequestSigning(String id, String algorithm, String alias, byte[] data) {
             synchronized (mLock) {
                 if (DEBUG) {
diff --git a/services/core/java/com/android/server/utils/EventLogger.java b/services/core/java/com/android/server/utils/EventLogger.java
index 11766a3..4772bbf 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;
 
 /**
@@ -34,8 +36,11 @@
  */
 public class EventLogger {
 
+    /** Prefix for the title added at the beginning of a {@link #dump(PrintWriter)} operation */
+    private static final String DUMP_TITLE_PREFIX = "Events log: ";
+
     /** Identifies the source of events. */
-    private final String mTag;
+    @Nullable private final String mTag;
 
     /** Stores the events using a ring buffer. */
     private final ArrayDeque<Event> mEvents;
@@ -53,7 +58,7 @@
      * @param size the maximum number of events to keep in log
      * @param tag the string displayed before the recorded log
      */
-    public EventLogger(int size, String tag) {
+    public EventLogger(int size, @Nullable String tag) {
         mEvents = new ArrayDeque<>(size);
         mMemSize = size;
         mTag = tag;
@@ -62,10 +67,10 @@
     /** Enqueues {@code event} to be logged. */
     public synchronized void enqueue(Event event) {
         if (mEvents.size() >= mMemSize) {
-            mEvents.removeLast();
+            mEvents.removeFirst();
         }
 
-        mEvents.addFirst(event);
+        mEvents.addLast(event);
     }
 
     /**
@@ -79,20 +84,40 @@
         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}. */
     public synchronized void dump(PrintWriter pw) {
         dump(pw, "" /* prefix */);
     }
 
+    protected String getDumpTitle() {
+        if (mTag == null) {
+            return DUMP_TITLE_PREFIX;
+        }
+        return DUMP_TITLE_PREFIX + mTag;
+    }
+
     /** 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(getDumpTitle());
+
         for (Event evt : mEvents) {
             pw.println(indent + 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/utils/Slogf.java b/services/core/java/com/android/server/utils/Slogf.java
index e88ac63..6efbd89 100644
--- a/services/core/java/com/android/server/utils/Slogf.java
+++ b/services/core/java/com/android/server/utils/Slogf.java
@@ -162,7 +162,7 @@
     }
 
     /**
-     * Logs a {@link Log.VEBOSE} message with an exception
+     * Logs a {@link Log.VEBOSE} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#VERBOSE} logging
      * is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -170,10 +170,10 @@
      * you're calling this method in a critical path, make sure to explicitly do the check before
      * calling it.
      */
-    public static void v(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void v(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.VERBOSE)) return;
 
-        v(tag, getMessage(format, args), exception);
+        v(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -192,7 +192,7 @@
     }
 
     /**
-     * Logs a {@link Log.DEBUG} message with an exception
+     * Logs a {@link Log.DEBUG} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#DEBUG} logging
      * is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -200,10 +200,10 @@
      * you're calling this method in a critical path, make sure to explicitly do the check before
      * calling it.
      */
-    public static void d(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void d(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.DEBUG)) return;
 
-        d(tag, getMessage(format, args), exception);
+        d(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -222,7 +222,7 @@
     }
 
     /**
-     * Logs a {@link Log.INFO} message with an exception
+     * Logs a {@link Log.INFO} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#INFO} logging
      * is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -230,10 +230,10 @@
      * you're calling this method in a critical path, make sure to explicitly do the check before
      * calling it.
      */
-    public static void i(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void i(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.INFO)) return;
 
-        i(tag, getMessage(format, args), exception);
+        i(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -252,7 +252,7 @@
     }
 
     /**
-     * Logs a {@link Log.WARN} message with an exception
+     * Logs a {@link Log.WARN} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
      * enabled for the given {@code tag}, but the compiler will still create an intermediate array
@@ -260,10 +260,10 @@
      * calling this method in a critical path, make sure to explicitly do the check before calling
      * it.
      */
-    public static void w(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void w(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.WARN)) return;
 
-        w(tag, getMessage(format, args), exception);
+        w(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -282,7 +282,7 @@
     }
 
     /**
-     * Logs a {@link Log.ERROR} message with an exception
+     * Logs a {@link Log.ERROR} message with a throwable
      *
      * <p><strong>Note: </strong>the message will only be formatted if {@link Log#ERROR} logging is
      * enabled for the given {@code tag}, but the compiler will still create an intermediate array
@@ -290,10 +290,10 @@
      * calling this method in a critical path, make sure to explicitly do the check before calling
      * it.
      */
-    public static void e(String tag, Exception exception, String format, @Nullable Object... args) {
+    public static void e(String tag, Throwable throwable, String format, @Nullable Object... args) {
         if (!isLoggable(tag, Log.ERROR)) return;
 
-        e(tag, getMessage(format, args), exception);
+        e(tag, getMessage(format, args), throwable);
     }
 
     /**
@@ -304,11 +304,11 @@
     }
 
     /**
-     * Logs a {@code wtf} message with an exception.
+     * Logs a {@code wtf} message with a throwable.
      */
-    public static void wtf(String tag, Exception exception, String format,
+    public static void wtf(String tag, Throwable throwable, String format,
             @Nullable Object... args) {
-        wtf(tag, getMessage(format, args), exception);
+        wtf(tag, getMessage(format, args), throwable);
     }
 
     private static String getMessage(String format, @Nullable Object... args) {
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index 5c305c6..ca4a32f 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -390,8 +390,13 @@
             Objects.requireNonNull(privilegedPackages, "privilegedPackages was null");
             Objects.requireNonNull(subIdToCarrierConfigMap, "subIdToCarrierConfigMap was null");
 
-            mSubIdToInfoMap = Collections.unmodifiableMap(subIdToInfoMap);
-            mSubIdToCarrierConfigMap = Collections.unmodifiableMap(subIdToCarrierConfigMap);
+            mSubIdToInfoMap =
+                    Collections.unmodifiableMap(
+                            new HashMap<Integer, SubscriptionInfo>(subIdToInfoMap));
+            mSubIdToCarrierConfigMap =
+                    Collections.unmodifiableMap(
+                            new HashMap<Integer, PersistableBundleWrapper>(
+                                    subIdToCarrierConfigMap));
 
             final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>();
             for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) {
diff --git a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
index 999d406..d22ec0a 100644
--- a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
+++ b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
@@ -544,6 +544,20 @@
             return mBundle.getInt(key, defaultValue);
         }
 
+        /**
+         * Returns the value associated with the given key, or null if no mapping of the desired
+         * type exists for the given key or a null value is explicitly associated with the key.
+         *
+         * @param key a String, or null
+         * @param defaultValue the value to return if key does not exist
+         * @return an int[] value, or null
+         */
+        @Nullable
+        public int[] getIntArray(@Nullable String key, @Nullable int[] defaultValue) {
+            final int[] value = mBundle.getIntArray(key);
+            return value == null ? defaultValue : value;
+        }
+
         @Override
         public int hashCode() {
             return getHashCode(mBundle);
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 6012993..d944a3b 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -655,7 +655,8 @@
     }
 
     private void registerSettingsChangeReceiver(IntentFilter intentFilter) {
-        mContext.registerReceiver(mSettingChangeReceiver, intentFilter);
+        mContext.registerReceiver(mSettingChangeReceiver, intentFilter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index abb57bc..5d08461 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -270,11 +270,12 @@
                         + " sys=" + sysWallpaperChanged
                         + " lock=" + lockWallpaperChanged
                         + " imagePending=" + wallpaper.imageWallpaperPending
-                        + " whichPending=0x" + Integer.toHexString(wallpaper.whichPending)
+                        + " mWhich=0x" + Integer.toHexString(wallpaper.mWhich)
                         + " written=" + written);
             }
 
             if (moved && lockWallpaperChanged) {
+                // TODO(b/253507223) Start lock screen WallpaperService
                 // We just migrated sys -> lock to preserve imagery for an impending
                 // new system-only wallpaper.  Tell keyguard about it and make sure it
                 // has the right SELinux label.
@@ -320,9 +321,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);
                                     }
                                 };
@@ -331,8 +330,10 @@
                                         false, wallpaper, callback);
                                 notifyColorsWhich |= FLAG_SYSTEM;
                             }
+                            // TODO(b/253507223) Start lock screen WallpaperService if only lock
+                            // screen wp changed
                             if (lockWallpaperChanged
-                                    || (wallpaper.whichPending & FLAG_LOCK) != 0) {
+                                    || (wallpaper.mWhich & FLAG_LOCK) != 0) {
                                 if (DEBUG) {
                                     Slog.i(TAG, "Lock-relevant wallpaper changed");
                                 }
@@ -611,7 +612,7 @@
 
         if (DEBUG) {
             Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
-                    + Integer.toHexString(wallpaper.whichPending)
+                    + Integer.toHexString(wallpaper.mWhich)
                     + " to " + wallpaper.cropFile.getName()
                     + " crop=(" + cropHint.width() + 'x' + cropHint.height()
                     + ") dim=(" + wpData.mWidth + 'x' + wpData.mHeight + ')');
@@ -924,10 +925,9 @@
         boolean imageWallpaperPending;
 
         /**
-         * Which new wallpapers are being written; mirrors the 'which'
-         * selector bit field to setWallpaper().
+         * Which wallpaper is set. Flag values are from {@link SetWallpaperFlags}.
          */
-        int whichPending;
+        int mWhich;
 
         /**
          * Callback once the set + crop is finished
@@ -1159,6 +1159,8 @@
                     Slog.w(TAG, "WallpaperService is not connected yet");
                     return;
                 }
+                TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+                t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent);
                 if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
                 mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
                         null /* options */);
@@ -1166,7 +1168,7 @@
                 try {
                     connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
                             wpdData.mWidth, wpdData.mHeight,
-                            wpdData.mPadding, mDisplayId, FLAG_SYSTEM | FLAG_LOCK);
+                            wpdData.mPadding, mDisplayId, mWallpaper.mWhich);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed attaching wallpaper on display", e);
                     if (wallpaper != null && !wallpaper.wallpaperUpdating
@@ -1175,6 +1177,7 @@
                                 false /* fromUser */, wallpaper, null /* reply */);
                     }
                 }
+                t.traceEnd();
             }
 
             void disconnectLocked() {
@@ -1324,6 +1327,8 @@
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
+            TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+            t.traceBegin("WPMS.onServiceConnected-" + name);
             synchronized (mLock) {
                 if (mWallpaper.connection == this) {
                     mService = IWallpaperService.Stub.asInterface(service);
@@ -1340,6 +1345,7 @@
                     mContext.getMainThreadHandler().removeCallbacks(mDisconnectRunnable);
                 }
             }
+            t.traceEnd();
         }
 
         @Override
@@ -1547,12 +1553,17 @@
         public void engineShown(IWallpaperEngine engine) {
             synchronized (mLock) {
                 if (mReply != null) {
+                    TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+                    t.traceBegin("WPMS.mReply.sendResult");
                     final long ident = Binder.clearCallingIdentity();
                     try {
                         mReply.sendResult(null);
                     } catch (RemoteException e) {
+                        Slog.d(TAG, "failed to send callback!", e);
+                    } finally {
                         Binder.restoreCallingIdentity(ident);
                     }
+                    t.traceEnd();
                     mReply = null;
                 }
             }
@@ -1888,12 +1899,9 @@
         }
     }
 
-    private static final HashMap<Integer, String> sWallpaperType = new HashMap<Integer, String>() {
-        {
-            put(FLAG_SYSTEM, RECORD_FILE);
-            put(FLAG_LOCK, RECORD_LOCK_FILE);
-        }
-    };
+    private static final Map<Integer, String> sWallpaperType = Map.of(
+            FLAG_SYSTEM, RECORD_FILE,
+            FLAG_LOCK, RECORD_LOCK_FILE);
 
     private void errorCheck(int userID) {
         sWallpaperType.forEach((type, filename) -> {
@@ -2409,13 +2417,19 @@
 
     @Override
     public WallpaperInfo getWallpaperInfo(int userId) {
+        return getWallpaperInfoWithFlags(FLAG_SYSTEM, userId);
+    }
+
+    @Override
+    public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which, int userId) {
         final boolean allow =
                 hasPermission(READ_WALLPAPER_INTERNAL) || hasPermission(QUERY_ALL_PACKAGES);
         if (allow) {
             userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);
             synchronized (mLock) {
-                WallpaperData wallpaper = mWallpaperMap.get(userId);
+                WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
+                        : mWallpaperMap.get(userId);
                 if (wallpaper != null && wallpaper.connection != null) {
                     return wallpaper.connection.mInfo;
                 }
@@ -2864,7 +2878,7 @@
                 ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper, extras);
                 if (pfd != null) {
                     wallpaper.imageWallpaperPending = true;
-                    wallpaper.whichPending = which;
+                    wallpaper.mWhich = which;
                     wallpaper.setComplete = completion;
                     wallpaper.fromForegroundApp = fromForegroundApp;
                     wallpaper.cropHint.set(cropHint);
@@ -2895,6 +2909,7 @@
         lockWP.allowBackup = sysWP.allowBackup;
         lockWP.primaryColors = sysWP.primaryColors;
         lockWP.mWallpaperDimAmount = sysWP.mWallpaperDimAmount;
+        lockWP.mWhich = FLAG_LOCK;
 
         // Migrate the bitmap files outright; no need to copy
         try {
@@ -2947,25 +2962,27 @@
 
     @Override
     public void setWallpaperComponentChecked(ComponentName name, String callingPackage,
-            int userId) {
+            @SetWallpaperFlags int which, int userId) {
 
         if (isWallpaperSupported(callingPackage) && isSetWallpaperAllowed(callingPackage)) {
-            setWallpaperComponent(name, userId);
+            setWallpaperComponent(name, which, userId);
         }
     }
 
     // ToDo: Remove this version of the function
     @Override
     public void setWallpaperComponent(ComponentName name) {
-        setWallpaperComponent(name, UserHandle.getCallingUserId());
+        setWallpaperComponent(name, UserHandle.getCallingUserId(), FLAG_SYSTEM);
     }
 
-    private void setWallpaperComponent(ComponentName name, int userId) {
+    private void setWallpaperComponent(ComponentName name, @SetWallpaperFlags int which,
+            int userId) {
         userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
                 false /* all */, true /* full */, "changing live wallpaper", null /* pkg */);
         checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
 
-        int which = FLAG_SYSTEM;
+        // TODO(b/253507223) Use passed destination and properly start lock screen LWP
+        int legacyWhich = FLAG_SYSTEM;
         boolean shouldNotifyColors = false;
         WallpaperData wallpaper;
 
@@ -2993,11 +3010,12 @@
 
             // New live wallpaper is also a lock wallpaper if nothing is set
             if (mLockWallpaperMap.get(userId) == null) {
-                which |= FLAG_LOCK;
+                legacyWhich |= FLAG_LOCK;
             }
 
             try {
                 wallpaper.imageWallpaperPending = false;
+                wallpaper.mWhich = which;
                 boolean same = changingToSame(name, wallpaper);
                 if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {
                     if (!same) {
@@ -3026,7 +3044,7 @@
         }
 
         if (shouldNotifyColors) {
-            notifyWallpaperColorsChanged(wallpaper, which);
+            notifyWallpaperColorsChanged(wallpaper, legacyWhich);
             notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);
         }
     }
@@ -3058,6 +3076,8 @@
             return true;
         }
 
+        TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+        t.traceBegin("WPMS.bindWallpaperComponentLocked-" + componentName);
         try {
             if (componentName == null) {
                 componentName = mDefaultWallpaperComponent;
@@ -3190,6 +3210,8 @@
             }
             Slog.w(TAG, msg);
             return false;
+        } finally {
+            t.traceEnd();
         }
         return true;
     }
@@ -3234,7 +3256,10 @@
     }
 
     private void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
+        TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+        t.traceBegin("WPMS.attachServiceLocked");
         conn.forEachDisplayConnector(connector-> connector.connectLocked(conn, wallpaper));
+        t.traceEnd();
     }
 
     private void notifyCallbacksLocked(WallpaperData wallpaper) {
@@ -3360,6 +3385,8 @@
     }
 
     void saveSettingsLocked(int userId) {
+        TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+        t.traceBegin("WPMS.saveSettingsLocked-" + userId);
         JournaledFile journal = makeJournaledFile(userId);
         FileOutputStream fstream = null;
         try {
@@ -3388,6 +3415,7 @@
             IoUtils.closeQuietly(fstream);
             journal.rollback();
         }
+        t.traceEnd();
     }
 
 
diff --git a/services/core/java/com/android/server/wearable/OWNERS b/services/core/java/com/android/server/wearable/OWNERS
new file mode 100644
index 0000000..073e2d7
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/OWNERS
@@ -0,0 +1,3 @@
+charliewang@google.com
+oni@google.com
+volnov@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
new file mode 100644
index 0000000..b2bbcda
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
@@ -0,0 +1,87 @@
+/*
+ * 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.wearable;
+
+import static android.content.Context.BIND_FOREGROUND_SERVICE;
+import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.SharedMemory;
+import android.service.wearable.IWearableSensingService;
+import android.service.wearable.WearableSensingService;
+import android.util.Slog;
+
+import com.android.internal.infra.ServiceConnector;
+
+/** Manages the connection to the remote wearable sensing service. */
+final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearableSensingService> {
+    private static final String TAG =
+            com.android.server.wearable.RemoteWearableSensingService.class.getSimpleName();
+    private final static boolean DEBUG = false;
+
+    RemoteWearableSensingService(Context context, ComponentName serviceName,
+            int userId) {
+        super(context, new Intent(
+                        WearableSensingService.SERVICE_INTERFACE).setComponent(serviceName),
+                BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
+                IWearableSensingService.Stub::asInterface);
+
+        // Bind right away
+        connect();
+    }
+
+    @Override
+    protected long getAutoDisconnectTimeoutMs() {
+        // Disable automatic unbinding.
+        return -1;
+    }
+
+    /**
+     * Provides the implementation a data stream to the wearable.
+     *
+     * @param parcelFileDescriptor The data stream to the wearable
+     * @param callback The callback for service status
+     */
+    public void provideDataStream(ParcelFileDescriptor parcelFileDescriptor,
+            RemoteCallback callback) {
+        if (DEBUG) {
+            Slog.i(TAG, "Providing data stream.");
+        }
+        post(service -> service.provideDataStream(parcelFileDescriptor, callback));
+    }
+
+    /**
+     * Provides the implementation data.
+     *
+     * @param data Application configuration data to provide to the implementation.
+     * @param sharedMemory The unrestricted data blob to provide to the implementation.
+     * @param callback The callback for service status
+     */
+    public void provideData(PersistableBundle data,
+            SharedMemory sharedMemory,
+            RemoteCallback callback) {
+        if (DEBUG) {
+            Slog.i(TAG, "Providing data.");
+        }
+        post(service -> service.provideData(data, sharedMemory, callback));
+    }
+}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
new file mode 100644
index 0000000..e73fd0f
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
@@ -0,0 +1,196 @@
+/*
+ * 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.wearable;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.app.wearable.WearableSensingManager;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Bundle;
+import android.system.OsConstants;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+import java.io.PrintWriter;
+
+/**
+ * Per-user manager service for managing sensing {@link AmbientContextEvent}s on Wearables.
+ */
+final class WearableSensingManagerPerUserService extends
+        AbstractPerUserSystemService<WearableSensingManagerPerUserService,
+                WearableSensingManagerService> {
+    private static final String TAG = WearableSensingManagerPerUserService.class.getSimpleName();
+
+    @Nullable
+    @VisibleForTesting
+    RemoteWearableSensingService mRemoteService;
+
+    private ComponentName mComponentName;
+
+    WearableSensingManagerPerUserService(
+            @NonNull WearableSensingManagerService master, Object lock, @UserIdInt int userId) {
+        super(master, lock, userId);
+    }
+
+    static void notifyStatusCallback(RemoteCallback statusCallback, int statusCode) {
+        Bundle bundle = new Bundle();
+        bundle.putInt(
+                WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY, statusCode);
+        statusCallback.sendResult(bundle);
+    }
+
+    void destroyLocked() {
+        Slog.d(TAG, "Trying to cancel the remote request. Reason: Service destroyed.");
+        if (mRemoteService != null) {
+            synchronized (mLock) {
+                mRemoteService.unbind();
+                mRemoteService = null;
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void ensureRemoteServiceInitiated() {
+        if (mRemoteService == null) {
+            mRemoteService = new RemoteWearableSensingService(
+                    getContext(), mComponentName, getUserId());
+        }
+    }
+
+    /**
+     * get the currently bound component name.
+     */
+    @VisibleForTesting
+    ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+
+    /**
+     * Resolves and sets up the service if it had not been done yet. Returns true if the service
+     * is available.
+     */
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    boolean setUpServiceIfNeeded() {
+        if (mComponentName == null) {
+            mComponentName = updateServiceInfoLocked();
+        }
+        if (mComponentName == null) {
+            return false;
+        }
+
+        ServiceInfo serviceInfo;
+        try {
+            serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
+                    mComponentName, 0, mUserId);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException while setting up service");
+            return false;
+        }
+        return serviceInfo != null;
+    }
+
+    @Override
+    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+            throws PackageManager.NameNotFoundException {
+        ServiceInfo serviceInfo;
+        try {
+            serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+                    0, mUserId);
+            if (serviceInfo != null) {
+                final String permission = serviceInfo.permission;
+                if (!Manifest.permission.BIND_WEARABLE_SENSING_SERVICE.equals(
+                        permission)) {
+                    throw new SecurityException(String.format(
+                            "Service %s requires %s permission. Found %s permission",
+                            serviceInfo.getComponentName(),
+                            Manifest.permission.BIND_WEARABLE_SENSING_SERVICE,
+                            serviceInfo.permission));
+                }
+            }
+        } catch (RemoteException e) {
+            throw new PackageManager.NameNotFoundException(
+                    "Could not get service for " + serviceComponent);
+        }
+        return serviceInfo;
+    }
+
+    @Override
+    protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
+        synchronized (super.mLock) {
+            super.dumpLocked(prefix, pw);
+        }
+        if (mRemoteService != null) {
+            mRemoteService.dump("", new IndentingPrintWriter(pw, "  "));
+        }
+    }
+
+    /**
+     * Handles sending the provided data stream for the wearable to the wearable sensing service.
+     */
+    public void onProvideDataStream(
+            ParcelFileDescriptor parcelFileDescriptor,
+            RemoteCallback callback) {
+        Slog.i(TAG, "onProvideDataStream in per user service.");
+        synchronized (mLock) {
+            if (!setUpServiceIfNeeded()) {
+                Slog.w(TAG, "Detection service is not available at this moment.");
+                notifyStatusCallback(callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            Slog.i(TAG, "calling over to remote servvice.");
+            ensureRemoteServiceInitiated();
+            mRemoteService.provideDataStream(parcelFileDescriptor, callback);
+        }
+    }
+
+    /**
+     * Handles sending the provided data to the wearable sensing service.
+     */
+    public void onProvidedData(PersistableBundle data,
+            SharedMemory sharedMemory,
+            RemoteCallback callback) {
+        synchronized (mLock) {
+            if (!setUpServiceIfNeeded()) {
+                Slog.w(TAG, "Detection service is not available at this moment.");
+                notifyStatusCallback(callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            ensureRemoteServiceInitiated();
+            if (sharedMemory != null) {
+                sharedMemory.setProtect(OsConstants.PROT_READ);
+            }
+            mRemoteService.provideData(data, sharedMemory, callback);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
new file mode 100644
index 0000000..707d0044
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -0,0 +1,238 @@
+/*
+ * 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.wearable;
+
+import static android.provider.DeviceConfig.NAMESPACE_WEARABLE_SENSING;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.app.wearable.IWearableSensingManager;
+import android.app.wearable.WearableSensingManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.ResultReceiver;
+import android.os.SharedMemory;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+import com.android.server.pm.KnownPackages;
+
+import java.io.FileDescriptor;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * System service for managing sensing {@link AmbientContextEvent}s on Wearables.
+ */
+public class WearableSensingManagerService extends
+        AbstractMasterSystemService<WearableSensingManagerService,
+                WearableSensingManagerPerUserService> {
+    private static final String TAG = WearableSensingManagerService.class.getSimpleName();
+    private static final String KEY_SERVICE_ENABLED = "service_enabled";
+
+    /** Default value in absence of {@link DeviceConfig} override. */
+    private static final boolean DEFAULT_SERVICE_ENABLED = true;
+    public static final int MAX_TEMPORARY_SERVICE_DURATION_MS = 30000;
+
+    private final Context mContext;
+    volatile boolean mIsServiceEnabled;
+
+    public WearableSensingManagerService(Context context) {
+        super(context,
+                new FrameworkResourcesServiceNameResolver(
+                        context,
+                        R.string.config_defaultWearableSensingService),
+                /*disallowProperty=*/null,
+                PACKAGE_UPDATE_POLICY_REFRESH_EAGER
+                        | /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
+        mContext = context;
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(
+                Context.WEARABLE_SENSING_SERVICE, new WearableSensingManagerInternal());
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            DeviceConfig.addOnPropertiesChangedListener(
+                    NAMESPACE_WEARABLE_SENSING,
+                    getContext().getMainExecutor(),
+                    (properties) -> onDeviceConfigChange(properties.getKeyset()));
+
+            mIsServiceEnabled = DeviceConfig.getBoolean(
+                    NAMESPACE_WEARABLE_SENSING,
+                    KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
+        }
+    }
+
+
+    private void onDeviceConfigChange(@NonNull Set<String> keys) {
+        if (keys.contains(KEY_SERVICE_ENABLED)) {
+            mIsServiceEnabled = DeviceConfig.getBoolean(
+                    NAMESPACE_WEARABLE_SENSING,
+                    KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
+        }
+    }
+
+    @Override
+    protected WearableSensingManagerPerUserService newServiceLocked(int resolvedUserId,
+            boolean disabled) {
+        return new WearableSensingManagerPerUserService(this, mLock, resolvedUserId);
+    }
+
+    @Override
+    protected void onServiceRemoved(
+            WearableSensingManagerPerUserService service, @UserIdInt int userId) {
+        Slog.d(TAG, "onServiceRemoved");
+        service.destroyLocked();
+    }
+
+    @Override
+    protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
+        Slog.d(TAG, "onServicePackageRestartedLocked.");
+    }
+
+    @Override
+    protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
+        Slog.d(TAG, "onServicePackageUpdatedLocked.");
+    }
+
+    @Override
+    protected void enforceCallingPermissionForManagement() {
+        getContext().enforceCallingPermission(
+                Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
+    }
+
+    @Override
+    protected int getMaximumTemporaryServiceDurationMs() {
+        return MAX_TEMPORARY_SERVICE_DURATION_MS;
+    }
+
+    /** Returns {@code true} if the detection service is configured on this device. */
+    public static boolean isDetectionServiceConfigured() {
+        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+        final String[] packageNames = pmi.getKnownPackageNames(
+                KnownPackages.PACKAGE_WEARABLE_SENSING, UserHandle.USER_SYSTEM);
+        boolean isServiceConfigured = (packageNames.length != 0);
+        Slog.i(TAG, "Wearable sensing service configured: " + isServiceConfigured);
+        return isServiceConfigured;
+    }
+
+    /**
+     * Returns the AmbientContextManagerPerUserService component for this user.
+     */
+    public ComponentName getComponentName(@UserIdInt int userId) {
+        synchronized (mLock) {
+            final WearableSensingManagerPerUserService service = getServiceForUserLocked(userId);
+            if (service != null) {
+                return service.getComponentName();
+            }
+        }
+        return null;
+    }
+
+    // Used in testing.
+    void provideDataStream(@UserIdInt int userId, ParcelFileDescriptor parcelFileDescriptor,
+            RemoteCallback callback) {
+        synchronized (mLock) {
+            final WearableSensingManagerPerUserService mService = getServiceForUserLocked(userId);
+            if (mService != null) {
+                mService.onProvideDataStream(parcelFileDescriptor, callback);
+            } else {
+                Slog.w(TAG, "Service not available.");
+            }
+        }
+    }
+
+    // Used in testing.
+    void provideData(@UserIdInt int userId, PersistableBundle data, SharedMemory sharedMemory,
+            RemoteCallback callback) {
+        synchronized (mLock) {
+            final WearableSensingManagerPerUserService mService = getServiceForUserLocked(userId);
+            if (mService != null) {
+                mService.onProvidedData(data, sharedMemory, callback);
+            } else {
+                Slog.w(TAG, "Service not available.");
+            }
+        }
+    }
+
+    private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
+        final WearableSensingManagerPerUserService mService = getServiceForUserLocked(
+                UserHandle.getCallingUserId());
+
+        @Override
+        public void provideDataStream(
+                ParcelFileDescriptor parcelFileDescriptor,
+                RemoteCallback callback) {
+            Slog.i(TAG, "WearableSensingManagerInternal provideDataStream.");
+            Objects.requireNonNull(parcelFileDescriptor);
+            Objects.requireNonNull(callback);
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+            if (!mIsServiceEnabled) {
+                Slog.w(TAG, "Service not available.");
+                WearableSensingManagerPerUserService.notifyStatusCallback(callback,
+                        WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            mService.onProvideDataStream(parcelFileDescriptor, callback);
+        }
+
+        @Override
+        public void provideData(
+                PersistableBundle data,
+                SharedMemory sharedMemory,
+                RemoteCallback callback) {
+            Slog.i(TAG, "WearableSensingManagerInternal provideData.");
+            Objects.requireNonNull(data);
+            Objects.requireNonNull(callback);
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+            if (!mIsServiceEnabled) {
+                Slog.w(TAG, "Service not available.");
+                WearableSensingManagerPerUserService.notifyStatusCallback(callback,
+                        WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            mService.onProvidedData(data, sharedMemory, callback);
+        }
+
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+                String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+            new WearableSensingShellCommand(WearableSensingManagerService.this).exec(
+                    this, in, out, err, args, callback, resultReceiver);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java b/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java
new file mode 100644
index 0000000..842bccb
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java
@@ -0,0 +1,212 @@
+/*
+ * 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.wearable;
+
+import android.annotation.NonNull;
+import android.app.wearable.WearableSensingManager;
+import android.content.ComponentName;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.ShellCommand;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+final class WearableSensingShellCommand extends ShellCommand {
+    private static final String TAG = WearableSensingShellCommand.class.getSimpleName();
+
+    static final TestableCallbackInternal sTestableCallbackInternal =
+            new TestableCallbackInternal();
+
+    @NonNull
+    private final WearableSensingManagerService mService;
+
+    private static ParcelFileDescriptor[] sPipe;
+
+    WearableSensingShellCommand(@NonNull WearableSensingManagerService service) {
+        mService = service;
+    }
+
+    /** Callbacks for WearableSensingService results used internally for testing. */
+    static class TestableCallbackInternal {
+        private int mLastStatus;
+
+        public int getLastStatus() {
+            return mLastStatus;
+        }
+
+        @NonNull
+        private RemoteCallback createRemoteStatusCallback() {
+            return new RemoteCallback(result -> {
+                int status = result.getInt(WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mLastStatus = status;
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            });
+        }
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+
+        switch (cmd) {
+            case "create-data-stream":
+                return createDataStream();
+            case "destroy-data-stream":
+                return destroyDataStream();
+            case "provide-data-stream":
+                return provideDataStream();
+            case "write-to-data-stream":
+                return writeToDataStream();
+            case "provide-data":
+                return provideData();
+            case "get-last-status-code":
+                return getLastStatusCode();
+            case "get-bound-package":
+                return getBoundPackageName();
+            case "set-temporary-service":
+                return setTemporaryService();
+            default:
+                return handleDefaultCommands(cmd);
+        }
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("WearableSensingCommands commands: ");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println();
+        pw.println("  create-data-stream: Creates a data stream to be provided.");
+        pw.println("  destroy-data-stream: Destroys a data stream if one was previously created.");
+        pw.println("  provide-data-stream USER_ID: "
+                + "Provides data stream to WearableSensingService.");
+        pw.println("  write-to-data-stream STRING: writes string to data stream.");
+        pw.println("  provide-data USER_ID KEY INTEGER: provide integer as data with key.");
+        pw.println("  get-last-status-code: Prints the latest request status code.");
+        pw.println("  get-bound-package USER_ID:"
+                + "     Print the bound package that implements the service.");
+        pw.println("  set-temporary-service USER_ID [PACKAGE_NAME] [COMPONENT_NAME DURATION]");
+        pw.println("    Temporarily (for DURATION ms) changes the service implementation.");
+        pw.println("    To reset, call with just the USER_ID argument.");
+    }
+
+    private int createDataStream() {
+        Slog.d(TAG, "createDataStream");
+        try {
+            sPipe = ParcelFileDescriptor.createPipe();
+        } catch (IOException e) {
+            Slog.d(TAG, "Failed to createDataStream.", e);
+        }
+        return 0;
+    }
+
+    private int destroyDataStream() {
+        Slog.d(TAG, "destroyDataStream");
+        try {
+            if (sPipe != null) {
+                sPipe[0].close();
+                sPipe[1].close();
+            }
+        } catch (IOException e) {
+            Slog.d(TAG, "Failed to destroyDataStream.", e);
+        }
+        return 0;
+    }
+
+    private int provideDataStream() {
+        Slog.d(TAG, "provideDataStream");
+        if (sPipe != null) {
+            final int userId = Integer.parseInt(getNextArgRequired());
+            mService.provideDataStream(userId, sPipe[0],
+                    sTestableCallbackInternal.createRemoteStatusCallback());
+        }
+        return 0;
+    }
+
+    private int writeToDataStream() {
+        Slog.d(TAG, "writeToDataStream");
+        if (sPipe != null) {
+            final String value = getNextArgRequired();
+            try {
+                ParcelFileDescriptor writePipe = sPipe[1].dup();
+                OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(writePipe);
+                os.write(value.getBytes());
+            } catch (IOException e) {
+                Slog.d(TAG, "Failed to writeToDataStream.", e);
+            }
+        }
+        return 0;
+    }
+
+    private int provideData() {
+        Slog.d(TAG, "provideData");
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String key = getNextArgRequired();
+        final int value = Integer.parseInt(getNextArgRequired());
+        PersistableBundle data = new PersistableBundle();
+        data.putInt(key, value);
+
+        mService.provideData(userId, data, null,
+                sTestableCallbackInternal.createRemoteStatusCallback());
+        return 0;
+    }
+
+    private int getLastStatusCode() {
+        Slog.d(TAG, "getLastStatusCode");
+        final PrintWriter resultPrinter = getOutPrintWriter();
+        int lastStatus = sTestableCallbackInternal.getLastStatus();
+        resultPrinter.println(lastStatus);
+        return 0;
+    }
+
+    private int setTemporaryService() {
+        final PrintWriter out = getOutPrintWriter();
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String serviceName = getNextArg();
+        if (serviceName == null) {
+            mService.resetTemporaryService(userId);
+            out.println("WearableSensingManagerService temporary reset. ");
+            return 0;
+        }
+
+        final int duration = Integer.parseInt(getNextArgRequired());
+        mService.setTemporaryService(userId, serviceName, duration);
+        out.println("WearableSensingService temporarily set to " + serviceName
+                + " for " + duration + "ms");
+        return 0;
+    }
+
+    private int getBoundPackageName() {
+        final PrintWriter resultPrinter = getOutPrintWriter();
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final ComponentName componentName = mService.getComponentName(userId);
+        resultPrinter.println(componentName == null ? "" : componentName.getPackageName());
+        return 0;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 44b83096..4fef2a8 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -2225,8 +2225,7 @@
                 ProtoOutputStream proto = new ProtoOutputStream();
                 proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
                 long timeOffsetNs =
-                        TimeUnit.NANOSECONDS.convert(System.currentTimeMillis(),
-                                                     TimeUnit.NANOSECONDS)
+                        TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
                         - SystemClock.elapsedRealtimeNanos();
                 proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
                 mBuffer.writeTraceToFile(mTraceFile, proto);
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index d0c381e..21b241a 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -56,8 +56,11 @@
     private static final int SURFACE_FLINGER_CALLBACK_WINDOWS_STABLE_TIMES_MS = 35;
     // To avoid the surface flinger callbacks always comes within in 2 frames, then no windows
     // are reported to the A11y framework, and the animation duration time is 500ms, so setting
-    // this value as the max timeout value to force computing changed windows.
-    private static final int WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS = 500;
+    // this value as the max timeout value to force computing changed windows. However, since
+    // UiAutomator waits 500ms to determine that things are idle. Since we aren't actually idle,
+    // we need to reduce the timeout here a little so that we can deliver an updated state before
+    // UiAutomator reports idle based-on stale information.
+    private static final int WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS = 450;
 
     private static final float[] sTempFloats = new float[9];
 
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 59f37c2..af430f9 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -86,6 +86,7 @@
 import com.android.internal.app.AssistUtils;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.Watchdog;
 import com.android.server.pm.KnownPackages;
@@ -454,6 +455,39 @@
                         finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
                 if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
                         || (finishWithRootActivity && r == rootR)) {
+                    ActivityRecord topActivity =
+                            r.getTask().getTopNonFinishingActivity();
+                    boolean passesAsmChecks = topActivity != null
+                            && topActivity.getUid() == r.getUid();
+                    if (!passesAsmChecks) {
+                        Slog.i(TAG, "Finishing task from background. r: " + r);
+                        FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
+                                /* caller_uid */
+                                r.getUid(),
+                                /* caller_activity_class_name */
+                                r.info.name,
+                                /* target_task_top_activity_uid */
+                                topActivity == null ? -1 : topActivity.getUid(),
+                                /* target_task_top_activity_class_name */
+                                topActivity == null ? null : topActivity.info.name,
+                                /* target_task_is_different */
+                                false,
+                                /* target_activity_uid */
+                                -1,
+                                /* target_activity_class_name */
+                                null,
+                                /* target_intent_action */
+                                null,
+                                /* target_intent_flags */
+                                0,
+                                /* action */
+                                FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
+                                /* version */
+                                1,
+                                /* multi_window */
+                                false
+                        );
+                    }
                     // If requested, remove the task that is associated to this activity only if it
                     // was the root activity in the task. The result code and data is ignored
                     // because we don't support returning them across task boundaries. Also, to
@@ -580,17 +614,18 @@
     }
 
     /**
-     * Returns the windowing mode of the task that hosts the activity, or {@code -1} if task is not
-     * found.
+     * Returns the {@link Configuration} of the task which hosts the Activity, or {@code null} if
+     * the task {@link Configuration} cannot be obtained.
      */
     @Override
-    public int getTaskWindowingMode(IBinder activityToken) {
+    @Nullable
+    public Configuration getTaskConfiguration(IBinder activityToken) {
         synchronized (mGlobalLock) {
             final ActivityRecord ar = ActivityRecord.isInAnyTask(activityToken);
             if (ar == null) {
-                return -1;
+                return null;
             }
-            return ar.getTask().getWindowingMode();
+            return ar.getTask().getConfiguration();
         }
     }
 
@@ -1177,7 +1212,8 @@
             synchronized (mGlobalLock) {
                 final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
                 if (r != null) {
-                    r.reportFullyDrawnLocked(restoredFromBundle);
+                    mTaskSupervisor.getActivityMetricsLogger().notifyFullyDrawn(r,
+                            restoredFromBundle);
                 }
             }
         } finally {
@@ -1351,8 +1387,38 @@
         }
     }
 
+    /**
+     * Return {@code true} when the given Activity is a relative Task root. That is, the rest of
+     * the Activities in the Task should be finished when it finishes. Otherwise, return {@code
+     * false}.
+     */
+    private boolean isRelativeTaskRootActivity(ActivityRecord r, ActivityRecord taskRoot) {
+        // Not a relative root if the given Activity is not the root Activity of its TaskFragment.
+        final TaskFragment taskFragment = r.getTaskFragment();
+        if (r != taskFragment.getActivity(ar -> !ar.finishing || ar == r,
+                false /* traverseTopToBottom */)) {
+            return false;
+        }
+
+        // The given Activity is the relative Task root if its TaskFragment is a companion
+        // TaskFragment to the taskRoot (i.e. the taskRoot TF will be finished together).
+        return taskRoot.getTaskFragment().getCompanionTaskFragment() == taskFragment;
+    }
+
+    private boolean isTopActivityInTaskFragment(ActivityRecord activity) {
+        return activity.getTaskFragment().topRunningActivity() == activity;
+    }
+
+    private void requestCallbackFinish(IRequestFinishCallback callback) {
+        try {
+            callback.requestFinish();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to invoke request finish callback", e);
+        }
+    }
+
     @Override
-    public void onBackPressedOnTaskRoot(IBinder token, IRequestFinishCallback callback) {
+    public void onBackPressed(IBinder token, IRequestFinishCallback callback) {
         final long origId = Binder.clearCallingIdentity();
         try {
             final Intent baseActivityIntent;
@@ -1362,20 +1428,29 @@
                 final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
                 if (r == null) return;
 
-                if (mService.mWindowOrganizerController.mTaskOrganizerController
+                final Task task = r.getTask();
+                final ActivityRecord root = task.getRootActivity(false /*ignoreRelinquishIdentity*/,
+                        true /*setToBottomIfNone*/);
+                final boolean isTaskRoot = r == root;
+                if (isTaskRoot) {
+                    if (mService.mWindowOrganizerController.mTaskOrganizerController
                         .handleInterceptBackPressedOnTaskRoot(r.getRootTask())) {
-                    // This task is handled by a task organizer that has requested the back pressed
-                    // callback.
+                        // This task is handled by a task organizer that has requested the back
+                        // pressed callback.
+                        return;
+                    }
+                } else if (!isRelativeTaskRootActivity(r, root)) {
+                    // Finish the Activity if the activity is not the task root or relative root.
+                    requestCallbackFinish(callback);
                     return;
                 }
 
-                final Task task = r.getTask();
-                isLastRunningActivity = task.topRunningActivity() == r;
+                isLastRunningActivity = isTopActivityInTaskFragment(isTaskRoot ? root : r);
 
-                final boolean isBaseActivity = r.mActivityComponent.equals(task.realActivity);
-                baseActivityIntent = isBaseActivity ? r.intent : null;
+                final boolean isBaseActivity = root.mActivityComponent.equals(task.realActivity);
+                baseActivityIntent = isBaseActivity ? root.intent : null;
 
-                launchedFromHome = r.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
+                launchedFromHome = root.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
             }
 
             // If the activity is one of the main entry points for the application, then we should
@@ -1390,16 +1465,12 @@
             if (baseActivityIntent != null && isLastRunningActivity
                     && ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent))
                         || isLauncherActivity(baseActivityIntent.getComponent()))) {
-                moveActivityTaskToBack(token, false /* nonRoot */);
+                moveActivityTaskToBack(token, true /* nonRoot */);
                 return;
             }
 
             // The default option for handling the back button is to finish the Activity.
-            try {
-                callback.requestFinish();
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to invoke request finish callback", e);
-            }
+            requestCallbackFinish(callback);
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index f0de1d3..13111fb 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -454,6 +454,7 @@
         final int windowsFullyDrawnDelayMs;
         final int activityRecordIdHashCode;
         final boolean relaunched;
+        final long timestampNs;
 
         private TransitionInfoSnapshot(TransitionInfo info) {
             this(info, info.mLastLaunchedActivity, INVALID_DELAY);
@@ -483,6 +484,7 @@
             activityRecordIdHashCode = System.identityHashCode(launchedActivity);
             this.windowsFullyDrawnDelayMs = windowsFullyDrawnDelayMs;
             relaunched = info.mRelaunched;
+            timestampNs = info.mLaunchingState.mStartRealtimeNs;
         }
 
         @WaitResult.LaunchState int getLaunchState() {
@@ -498,6 +500,10 @@
             }
         }
 
+        boolean isIntresetedToEventLog() {
+            return type == TYPE_TRANSITION_WARM_LAUNCH || type == TYPE_TRANSITION_COLD_LAUNCH;
+        }
+
         PackageOptimizationInfo getPackageOptimizationInfo(ArtManagerInternal artManagerInternal) {
             return artManagerInternal == null || launchedActivityAppRecordRequiredAbi == null
                     ? PackageOptimizationInfo.createWithNoInfo()
@@ -734,7 +740,7 @@
         // visible such as after the top task is finished.
         for (int i = mTransitionInfoList.size() - 2; i >= 0; i--) {
             final TransitionInfo prevInfo = mTransitionInfoList.get(i);
-            if (prevInfo.mIsDrawn || !prevInfo.mLastLaunchedActivity.mVisibleRequested) {
+            if (prevInfo.mIsDrawn || !prevInfo.mLastLaunchedActivity.isVisibleRequested()) {
                 scheduleCheckActivityToBeDrawn(prevInfo.mLastLaunchedActivity, 0 /* delay */);
             }
         }
@@ -861,7 +867,7 @@
             return;
         }
         if (DEBUG_METRICS) {
-            Slog.i(TAG, "notifyVisibilityChanged " + r + " visible=" + r.mVisibleRequested
+            Slog.i(TAG, "notifyVisibilityChanged " + r + " visible=" + r.isVisibleRequested()
                     + " state=" + r.getState() + " finishing=" + r.finishing);
         }
         if (r.isState(ActivityRecord.State.RESUMED) && r.mDisplayContent.isSleeping()) {
@@ -870,7 +876,7 @@
             // the tracking of launch event.
             return;
         }
-        if (!r.mVisibleRequested || r.finishing) {
+        if (!r.isVisibleRequested() || r.finishing) {
             // Check if the tracker can be cancelled because the last launched activity may be
             // no longer visible.
             scheduleCheckActivityToBeDrawn(r, 0 /* delay */);
@@ -903,7 +909,7 @@
             // activities in this task may be finished, invisible or drawn, so the transition event
             // should be cancelled.
             if (t != null && t.forAllActivities(
-                    a -> a.mVisibleRequested && !a.isReportedDrawn() && !a.finishing)) {
+                    a -> a.isVisibleRequested() && !a.isReportedDrawn() && !a.finishing)) {
                 return;
             }
 
@@ -1022,16 +1028,17 @@
         // This will avoid any races with other operations that modify the ActivityRecord.
         final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info);
         if (info.isInterestingToLoggerAndObserver()) {
-            final long timestampNs = info.mLaunchingState.mStartRealtimeNs;
             final long uptimeNs = info.mLaunchingState.mStartUptimeNs;
             final int transitionDelay = info.mCurrentTransitionDelayMs;
             final int processState = info.mProcessState;
             final int processOomAdj = info.mProcessOomAdj;
             mLoggerHandler.post(() -> logAppTransition(
-                    timestampNs, uptimeNs, transitionDelay, infoSnapshot, isHibernating,
+                    uptimeNs, transitionDelay, infoSnapshot, isHibernating,
                     processState, processOomAdj));
         }
-        mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
+        if (infoSnapshot.isIntresetedToEventLog()) {
+            mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
+        }
         if (info.mPendingFullyDrawn != null) {
             info.mPendingFullyDrawn.run();
         }
@@ -1040,7 +1047,7 @@
     }
 
     // This gets called on another thread without holding the activity manager lock.
-    private void logAppTransition(long transitionStartTimeNs, long transitionDeviceUptimeNs,
+    private void logAppTransition(long transitionDeviceUptimeNs,
             int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating,
             int processState, int processOomAdj) {
         final LogMaker builder = new LogMaker(APP_TRANSITION);
@@ -1108,7 +1115,7 @@
                 isIncremental,
                 isLoading,
                 info.launchedActivityName.hashCode(),
-                TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs),
+                TimeUnit.NANOSECONDS.toMillis(info.timestampNs),
                 processState,
                 processOomAdj);
 
@@ -1132,10 +1139,6 @@
     }
 
     private void logAppDisplayed(TransitionInfoSnapshot info) {
-        if (info.type != TYPE_TRANSITION_WARM_LAUNCH && info.type != TYPE_TRANSITION_COLD_LAUNCH) {
-            return;
-        }
-
         EventLog.writeEvent(WM_ACTIVITY_LAUNCH_TIME,
                 info.userId, info.activityRecordIdHashCode, info.launchedActivityShortComponentName,
                 info.windowsDrawnDelayMs);
@@ -1181,8 +1184,7 @@
     }
 
     /** @see android.app.Activity#reportFullyDrawn */
-    TransitionInfoSnapshot logAppTransitionReportedDrawn(ActivityRecord r,
-            boolean restoredFromBundle) {
+    TransitionInfoSnapshot notifyFullyDrawn(ActivityRecord r, boolean restoredFromBundle) {
         final TransitionInfo info = mLastTransitionInfo.get(r);
         if (info == null) {
             return null;
@@ -1191,7 +1193,7 @@
             // There are still undrawn activities, postpone reporting fully drawn until all of its
             // windows are drawn. So that is closer to an usable state.
             info.mPendingFullyDrawn = () -> {
-                logAppTransitionReportedDrawn(r, restoredFromBundle);
+                notifyFullyDrawn(r, restoredFromBundle);
                 info.mPendingFullyDrawn = null;
             };
             return null;
@@ -1204,7 +1206,9 @@
                         currentTimestampNs - info.mLaunchingState.mStartUptimeNs);
         final TransitionInfoSnapshot infoSnapshot =
                 new TransitionInfoSnapshot(info, r, (int) startupTimeMs);
-        mLoggerHandler.post(() -> logAppFullyDrawn(infoSnapshot));
+        if (infoSnapshot.isIntresetedToEventLog()) {
+            mLoggerHandler.post(() -> logAppFullyDrawn(infoSnapshot));
+        }
         mLastTransitionInfo.remove(r);
 
         if (!info.isInterestingToLoggerAndObserver()) {
@@ -1216,46 +1220,8 @@
         // fullfils (handling reportFullyDrawn() callbacks).
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                 "ActivityManager:ReportingFullyDrawn " + info.mLastLaunchedActivity.packageName);
-
-        final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN);
-        builder.setPackageName(r.packageName);
-        builder.addTaggedData(FIELD_CLASS_NAME, r.info.name);
-        builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS, startupTimeMs);
-        builder.setType(restoredFromBundle
-                ? TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE
-                : TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE);
-        builder.addTaggedData(APP_TRANSITION_PROCESS_RUNNING,
-                info.mProcessRunning ? 1 : 0);
-        mMetricsLogger.write(builder);
-        final PackageOptimizationInfo packageOptimizationInfo =
-                infoSnapshot.getPackageOptimizationInfo(getArtManagerInternal());
-        // Incremental info
-        boolean isIncremental = false, isLoading = false;
-        final String codePath = info.mLastLaunchedActivity.info.applicationInfo.getCodePath();
-        if (codePath != null && IncrementalManager.isIncrementalPath(codePath)) {
-            isIncremental = true;
-            isLoading = isIncrementalLoading(info.mLastLaunchedActivity.packageName,
-                            info.mLastLaunchedActivity.mUserId);
-        }
-        FrameworkStatsLog.write(
-                FrameworkStatsLog.APP_START_FULLY_DRAWN,
-                info.mLastLaunchedActivity.info.applicationInfo.uid,
-                info.mLastLaunchedActivity.packageName,
-                restoredFromBundle
-                        ? FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITH_BUNDLE
-                        : FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITHOUT_BUNDLE,
-                info.mLastLaunchedActivity.info.name,
-                info.mProcessRunning,
-                startupTimeMs,
-                packageOptimizationInfo.getCompilationReason(),
-                packageOptimizationInfo.getCompilationFilter(),
-                info.mSourceType,
-                info.mSourceEventDelayMs,
-                isIncremental,
-                isLoading,
-                info.mLastLaunchedActivity.info.name.hashCode(),
-                TimeUnit.NANOSECONDS.toMillis(info.mLaunchingState.mStartRealtimeNs));
-
+        mLoggerHandler.post(() -> logAppFullyDrawnMetrics(infoSnapshot, restoredFromBundle,
+                info.mProcessRunning));
         // Ends the trace started at the beginning of this function. This is located here to allow
         // the trace slice to have a noticable duration.
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -1266,11 +1232,48 @@
         return infoSnapshot;
     }
 
-    private void logAppFullyDrawn(TransitionInfoSnapshot info) {
-        if (info.type != TYPE_TRANSITION_WARM_LAUNCH && info.type != TYPE_TRANSITION_COLD_LAUNCH) {
-            return;
+    private void logAppFullyDrawnMetrics(TransitionInfoSnapshot info, boolean restoredFromBundle,
+            boolean processRunning) {
+        final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN);
+        builder.setPackageName(info.packageName);
+        builder.addTaggedData(FIELD_CLASS_NAME, info.launchedActivityName);
+        builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS,
+                (long) info.windowsFullyDrawnDelayMs);
+        builder.setType(restoredFromBundle
+                ? TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE
+                : TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE);
+        builder.addTaggedData(APP_TRANSITION_PROCESS_RUNNING, processRunning ? 1 : 0);
+        mMetricsLogger.write(builder);
+        final PackageOptimizationInfo packageOptimizationInfo =
+                info.getPackageOptimizationInfo(getArtManagerInternal());
+        // Incremental info
+        boolean isIncremental = false, isLoading = false;
+        final String codePath = info.applicationInfo.getCodePath();
+        if (codePath != null && IncrementalManager.isIncrementalPath(codePath)) {
+            isIncremental = true;
+            isLoading = isIncrementalLoading(info.packageName, info.userId);
         }
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.APP_START_FULLY_DRAWN,
+                info.applicationInfo.uid,
+                info.packageName,
+                restoredFromBundle
+                        ? FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITH_BUNDLE
+                        : FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITHOUT_BUNDLE,
+                info.launchedActivityName,
+                processRunning,
+                info.windowsFullyDrawnDelayMs,
+                packageOptimizationInfo.getCompilationReason(),
+                packageOptimizationInfo.getCompilationFilter(),
+                info.sourceType,
+                info.sourceEventDelayMs,
+                isIncremental,
+                isLoading,
+                info.launchedActivityName.hashCode(),
+                TimeUnit.NANOSECONDS.toMillis(info.timestampNs));
+    }
 
+    private void logAppFullyDrawn(TransitionInfoSnapshot info) {
         StringBuilder sb = mStringBuilder;
         sb.setLength(0);
         sb.append("Fully drawn ");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 17a9a63..aec06f0 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
@@ -800,7 +800,7 @@
     // it will sometimes be true a little earlier: when the activity record has
     // been shown, but is still waiting for its app transition to execute
     // before making its windows shown.
-    boolean mVisibleRequested;
+    private boolean mVisibleRequested;
 
     // Last visibility state we reported to the app token.
     boolean reportedVisible;
@@ -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);
@@ -1758,6 +1747,7 @@
         }
 
         prevDc.mClosingApps.remove(this);
+        prevDc.getDisplayPolicy().removeRelaunchingApp(this);
 
         if (prevDc.mFocusedApp == this) {
             prevDc.setFocusedApp(null);
@@ -2687,13 +2677,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 +2708,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 +2739,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 +2768,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");
@@ -3138,8 +3131,8 @@
         }
 
         // Check to see if PiP is supported for the display this container is on.
-        if (mDisplayContent != null && !mDisplayContent.mDwpcHelper.isWindowingModeSupported(
-                WINDOWING_MODE_PINNED)) {
+        if (mDisplayContent != null && !mDisplayContent.mDwpcHelper.isEnteringPipAllowed(
+                getUid())) {
             Slog.w(TAG, "Display " + mDisplayContent.getDisplayId()
                     + " doesn't support enter picture-in-picture mode. caller = " + caller);
             return false;
@@ -3324,9 +3317,17 @@
                 mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(resultGrants,
                         resultTo.getUriPermissionsLocked());
             }
-            if (mForceSendResultForMediaProjection) {
-                resultTo.sendResult(this.getUid(), resultWho, requestCode, resultCode,
-                        resultData, resultGrants, true /* forceSendForMediaProjection */);
+            if (mForceSendResultForMediaProjection || resultTo.isState(RESUMED)) {
+                // Sending the result to the resultTo activity asynchronously to prevent the
+                // resultTo activity getting results before this Activity paused.
+                final ActivityRecord resultToActivity = resultTo;
+                mAtmService.mH.post(() -> {
+                    synchronized (mAtmService.mGlobalLock) {
+                        resultToActivity.sendResult(this.getUid(), resultWho, requestCode,
+                                resultCode, resultData, resultGrants,
+                                mForceSendResultForMediaProjection);
+                    }
+                });
             } else {
                 resultTo.addResultLocked(this, resultWho, requestCode, resultCode, resultData);
             }
@@ -3621,7 +3622,7 @@
         // implied that the current finishing activity should be added into stopping list rather
         // than destroy immediately.
         final boolean isNextNotYetVisible = next != null
-                && (!next.nowVisible || !next.mVisibleRequested);
+                && (!next.nowVisible || !next.isVisibleRequested());
 
         // Clear last paused activity to ensure top activity can be resumed during sleeping.
         if (isNextNotYetVisible && mDisplayContent.isSleeping()
@@ -3967,6 +3968,9 @@
     void startRelaunching() {
         if (mPendingRelaunchCount == 0) {
             mRelaunchStartTime = SystemClock.elapsedRealtime();
+            if (mVisibleRequested) {
+                mDisplayContent.getDisplayPolicy().addRelaunchingApp(this);
+            }
         }
         clearAllDrawn();
 
@@ -3980,7 +3984,7 @@
             mPendingRelaunchCount--;
             if (mPendingRelaunchCount == 0 && !isClientVisible()) {
                 // Don't count if the client won't report drawn.
-                mRelaunchStartTime = 0;
+                finishOrAbortReplacingWindow();
             }
         } else {
             // Update keyguard flags upon finishing relaunch.
@@ -4001,7 +4005,12 @@
             return;
         }
         mPendingRelaunchCount = 0;
+        finishOrAbortReplacingWindow();
+    }
+
+    void finishOrAbortReplacingWindow() {
         mRelaunchStartTime = 0;
+        mDisplayContent.getDisplayPolicy().removeRelaunchingApp(this);
     }
 
     /**
@@ -4247,7 +4256,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 +4354,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 +4420,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();
@@ -4435,7 +4440,7 @@
     void transferStartingWindowFromHiddenAboveTokenIfNeeded() {
         task.forAllActivities(fromActivity -> {
             if (fromActivity == this) return true;
-            return !fromActivity.mVisibleRequested && transferStartingWindow(fromActivity);
+            return !fromActivity.isVisibleRequested() && transferStartingWindow(fromActivity);
         });
     }
 
@@ -4633,7 +4638,7 @@
                 false /* forceSendForMediaProjection */);
     }
 
-    private void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
+    void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
             Intent data, NeededUriGrants dataGrants, boolean forceSendForMediaProjection) {
         if (callingUid > 0) {
             mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(dataGrants,
@@ -5100,7 +5105,8 @@
      * This is the only place that writes {@link #mVisibleRequested} (except unit test). The caller
      * outside of this class should use {@link #setVisibility}.
      */
-    private void setVisibleRequested(boolean visible) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    void setVisibleRequested(boolean visible) {
         if (visible == mVisibleRequested) {
             return;
         }
@@ -5114,6 +5120,9 @@
             mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
         }
         logAppCompatState();
+        if (!visible) {
+            finishOrAbortReplacingWindow();
+        }
     }
 
     /**
@@ -5251,42 +5260,7 @@
         }
         // If we are preparing an app transition, then delay changing
         // the visibility of this token until we execute that transition.
-        // Note that we ignore display frozen since we want the opening / closing transition type
-        // can be updated correctly even display frozen, and it's safe since in applyAnimation will
-        // still check DC#okToAnimate again if the transition animation is fine to apply.
-        // TODO(new-app-transition): Rewrite this logic using WM Shell.
-        final boolean recentsAnimating = isAnimating(PARENTS, ANIMATION_TYPE_RECENTS);
-        final boolean isEnteringPipWithoutVisibleChange = mWaitForEnteringPinnedMode
-                && mVisible == visible;
-        if (okToAnimate(true /* ignoreFrozen */, canTurnScreenOn())
-                && (appTransition.isTransitionSet()
-                || (recentsAnimating && !isActivityTypeHome()))
-                // If the visibility is not changed during enter PIP, we don't want to include it in
-                // app transition to affect the animation theme, because the Pip organizer will
-                // animate the entering PIP instead.
-                && !isEnteringPipWithoutVisibleChange) {
-            if (visible) {
-                displayContent.mOpeningApps.add(this);
-                mEnteringAnimation = true;
-            } else if (mVisible) {
-                displayContent.mClosingApps.add(this);
-                mEnteringAnimation = false;
-            }
-            if ((appTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0) {
-                // We're launchingBehind, add the launching activity to mOpeningApps.
-                final WindowState win = getDisplayContent().findFocusedWindow();
-                if (win != null) {
-                    final ActivityRecord focusedActivity = win.mActivityRecord;
-                    if (focusedActivity != null) {
-                        ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
-                                "TRANSIT_FLAG_OPEN_BEHIND,  adding %s to mOpeningApps",
-                                focusedActivity);
-
-                        // Force animation to be loaded.
-                        displayContent.mOpeningApps.add(focusedActivity);
-                    }
-                }
-            }
+        if (deferCommitVisibilityChange(visible)) {
             return;
         }
 
@@ -5294,6 +5268,61 @@
         updateReportedVisibilityLocked();
     }
 
+    /**
+     * Returns {@code true} if this activity is either added to opening-apps or closing-apps.
+     * Then its visibility will be committed until the transition is ready.
+     */
+    private boolean deferCommitVisibilityChange(boolean visible) {
+        if (!mDisplayContent.mAppTransition.isTransitionSet()) {
+            if (mTransitionController.isShellTransitionsEnabled()) {
+                // Shell transition doesn't use opening/closing sets.
+                return false;
+            }
+            // Defer committing visibility for non-home app which is animating by recents.
+            if (isActivityTypeHome() || !isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
+                return false;
+            }
+        }
+        if (mWaitForEnteringPinnedMode && mVisible == visible) {
+            // If the visibility is not changed during enter PIP, we don't want to include it in
+            // app transition to affect the animation theme, because the Pip organizer will
+            // animate the entering PIP instead.
+            return false;
+        }
+
+        // The animation will be visible soon so do not skip by screen off.
+        final boolean ignoreScreenOn = canTurnScreenOn() || mTaskSupervisor.getKeyguardController()
+                .isKeyguardGoingAway(mDisplayContent.mDisplayId);
+        // Ignore display frozen so the opening / closing transition type can be updated correctly
+        // even if the display is frozen. And it's safe since in applyAnimation will still check
+        // DC#okToAnimate again if the transition animation is fine to apply.
+        if (!okToAnimate(true /* ignoreFrozen */, ignoreScreenOn)) {
+            return false;
+        }
+        if (visible) {
+            mDisplayContent.mOpeningApps.add(this);
+            mEnteringAnimation = true;
+        } else if (mVisible) {
+            mDisplayContent.mClosingApps.add(this);
+            mEnteringAnimation = false;
+        }
+        if ((mDisplayContent.mAppTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0) {
+            // Add the launching-behind activity to mOpeningApps.
+            final WindowState win = mDisplayContent.findFocusedWindow();
+            if (win != null) {
+                final ActivityRecord focusedActivity = win.mActivityRecord;
+                if (focusedActivity != null) {
+                    ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
+                            "TRANSIT_FLAG_OPEN_BEHIND,  adding %s to mOpeningApps",
+                            focusedActivity);
+                    // Force animation to be loaded.
+                    mDisplayContent.mOpeningApps.add(focusedActivity);
+                }
+            }
+        }
+        return true;
+    }
+
     @Override
     boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
             boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
@@ -5405,25 +5434,20 @@
      */
     private void postApplyAnimation(boolean visible, boolean fromTransition) {
         final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled();
-        final boolean delayed = isAnimating(PARENTS | CHILDREN,
+        final boolean delayed = !usingShellTransitions && isAnimating(PARENTS | CHILDREN,
                 ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
                         | ANIMATION_TYPE_RECENTS);
-        if (!delayed) {
+        if (!delayed && !usingShellTransitions) {
             // We aren't delayed anything, but exiting windows rely on the animation finished
             // callback being called in case the ActivityRecord was pretending to be delayed,
             // which we might have done because we were in closing/opening apps list.
-            if (!usingShellTransitions) {
-                onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
-                if (visible) {
-                    // The token was made immediately visible, there will be no entrance animation.
-                    // We need to inform the client the enter animation was finished.
-                    mEnteringAnimation = true;
-                    mWmService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(
-                            token);
-                }
-            } else {
-                // update wallpaper target
-                setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER, "ActivityRecord");
+            onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
+            if (visible) {
+                // The token was made immediately visible, there will be no entrance animation.
+                // We need to inform the client the enter animation was finished.
+                mEnteringAnimation = true;
+                mWmService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(
+                        token);
             }
         }
 
@@ -5432,8 +5456,8 @@
         // updated.
         // If we're becoming invisible, update the client visibility if we are not running an
         // animation. Otherwise, we'll update client visibility in onAnimationFinished.
-        if (visible || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
-                || usingShellTransitions) {
+        if (visible || usingShellTransitions
+                || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
             setClientVisible(visible);
         }
 
@@ -6497,15 +6521,6 @@
         }
     }
 
-    void reportFullyDrawnLocked(boolean restoredFromBundle) {
-        final TransitionInfoSnapshot info = mTaskSupervisor
-            .getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle);
-        if (info != null) {
-            mTaskSupervisor.reportActivityLaunched(false /* timeout */, this,
-                    info.windowsFullyDrawnDelayMs, info.getLaunchState());
-        }
-    }
-
     void onFirstWindowDrawn(WindowState win) {
         firstWindowDrawn = true;
         // stop tracking
@@ -6526,14 +6541,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.isVisibleRequested() && !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 +6760,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 +6803,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;
             }
         }
 
@@ -6880,6 +6891,10 @@
         if (r == null || r.getParent() == null) {
             return INVALID_TASK_ID;
         }
+        return getTaskForActivityLocked(r, onlyRoot);
+    }
+
+    static int getTaskForActivityLocked(ActivityRecord r, boolean onlyRoot) {
         final Task task = r.task;
         if (onlyRoot && r.compareTo(task.getRootActivity(
                 false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)) > 0) {
@@ -6912,11 +6927,8 @@
      *         {@link android.view.Display#INVALID_DISPLAY} if not attached.
      */
     int getDisplayId() {
-        final Task rootTask = getRootTask();
-        if (rootTask == null) {
-            return INVALID_DISPLAY;
-        }
-        return rootTask.getDisplayId();
+        return task != null && task.mDisplayContent != null
+                 ? task.mDisplayContent.mDisplayId : INVALID_DISPLAY;
     }
 
     final boolean isDestroyable() {
@@ -7426,8 +7438,10 @@
             } else if (!show && mLastSurfaceShowing) {
                 getSyncTransaction().hide(mSurfaceControl);
             }
-            if (show) {
-                mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getSyncTransaction());
+            // Input sink surface is not a part of animation, so just apply in a steady state
+            // (non-sync) with pending transaction.
+            if (show && mSyncState == SYNC_STATE_NONE) {
+                mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getPendingTransaction());
             }
         }
         if (mThumbnail != null) {
@@ -7552,7 +7566,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) {
@@ -7916,8 +7931,8 @@
     }
 
     @Override
-    float getSizeCompatScale() {
-        return hasSizeCompatBounds() ? mSizeCompatScale : super.getSizeCompatScale();
+    float getCompatScale() {
+        return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale();
     }
 
     @Override
@@ -8901,9 +8916,7 @@
         }
 
         if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN)
-                && getParent().getConfiguration().orientation == ORIENTATION_PORTRAIT
-                && getParent().getWindowConfiguration().getWindowingMode()
-                        == WINDOWING_MODE_FULLSCREEN) {
+                && isParentFullscreenPortrait()) {
             // We are using the parent configuration here as this is the most recent one that gets
             // passed to onConfigurationChanged when a relevant change takes place
             return info.getMinAspectRatio();
@@ -8926,6 +8939,13 @@
         return info.getMinAspectRatio();
     }
 
+    private boolean isParentFullscreenPortrait() {
+        final WindowContainer parent = getParent();
+        return parent != null
+                && parent.getConfiguration().orientation == ORIENTATION_PORTRAIT
+                && parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+    }
+
     /**
      * Returns true if the activity has maximum or minimum aspect ratio.
      */
@@ -9219,10 +9239,10 @@
                         + " preserveWindow=" + preserveWindow);
         if (andResume) {
             EventLogTags.writeWmRelaunchResumeActivity(mUserId, System.identityHashCode(this),
-                    task.mTaskId, shortComponentName);
+                    task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
         } else {
             EventLogTags.writeWmRelaunchActivity(mUserId, System.identityHashCode(this),
-                    task.mTaskId, shortComponentName);
+                    task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
         }
 
         startFreezingScreenLocked(0);
@@ -9650,7 +9670,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/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
index 30c7b23..0859d40 100644
--- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
+++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
@@ -92,7 +92,7 @@
 
     public boolean isActivityVisible() {
         synchronized (mService.mGlobalLock) {
-            return mActivity.mVisibleRequested || mActivity.isState(RESUMED, PAUSING);
+            return mActivity.isVisibleRequested() || mActivity.isState(RESUMED, PAUSING);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index c49d672..05ec3b5 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -559,47 +559,52 @@
         final Task rootTask = mService.mRootWindowContainer.getDefaultTaskDisplayArea()
                 .getRootTask(WINDOWING_MODE_UNDEFINED, activityType);
         if (rootTask == null) return false;
+        final RemoteTransition remote = options.getRemoteTransition();
         final ActivityRecord r = rootTask.topRunningActivity();
-        if (r == null || r.mVisibleRequested || !r.attachedToProcess()
+        if (r == null || r.isVisibleRequested() || !r.attachedToProcess() || remote == null
                 || !r.mActivityComponent.equals(intent.getComponent())
                 // Recents keeps invisible while device is locked.
                 || r.mDisplayContent.isKeyguardLocked()) {
             return false;
         }
         mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(true /* forceSend */, r);
-        final RemoteTransition remote = options.getRemoteTransition();
-        if (remote != null && rootTask.mTransitionController.isCollecting()) {
-            final Transition transition = new Transition(WindowManager.TRANSIT_TO_FRONT,
-                    0 /* flags */, rootTask.mTransitionController,
-                    mService.mWindowManager.mSyncEngine);
+        final ActivityMetricsLogger.LaunchingState launchingState =
+                mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
+        final Transition transition = new Transition(WindowManager.TRANSIT_TO_FRONT,
+                0 /* flags */, r.mTransitionController, mService.mWindowManager.mSyncEngine);
+        if (r.mTransitionController.isCollecting()) {
             // Special case: we are entering recents while an existing transition is running. In
             // this case, we know it's safe to "defer" the activity launch, so lets do so now so
             // that it can get its own transition and thus update launcher correctly.
             mService.mWindowManager.mSyncEngine.queueSyncSet(
-                    () -> rootTask.mTransitionController.moveToCollecting(transition),
                     () -> {
-                        final Task task = r.getTask();
-                        task.mTransitionController.requestStartTransition(transition,
-                                task, remote, null /* displayChange */);
-                        task.mTransitionController.collect(task);
-                        startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+                        if (r.isAttached()) {
+                            r.mTransitionController.moveToCollecting(transition);
+                        }
+                    },
+                    () -> {
+                        if (r.isAttached() && transition.isCollecting()) {
+                            startExistingRecentsIfPossibleInner(options, r, rootTask,
+                                    launchingState, remote, transition);
+                        }
                     });
         } else {
-            final Task task = r.getTask();
-            task.mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_TO_FRONT,
-                    0 /* flags */, task, task /* readyGroupRef */,
-                    options.getRemoteTransition(), null /* displayChange */);
-            startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+            r.mTransitionController.moveToCollecting(transition);
+            startExistingRecentsIfPossibleInner(options, r, rootTask, launchingState, remote,
+                    transition);
         }
         return true;
     }
 
-    void startExistingRecentsIfPossibleInner(Intent intent, ActivityOptions options,
-            ActivityRecord r, Task task, Task rootTask) {
-        final ActivityMetricsLogger.LaunchingState launchingState =
-                mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
+    private void startExistingRecentsIfPossibleInner(ActivityOptions options, ActivityRecord r,
+            Task rootTask, ActivityMetricsLogger.LaunchingState launchingState,
+            RemoteTransition remoteTransition, Transition transition) {
+        final Task task = r.getTask();
         mService.deferWindowLayout();
         try {
+            r.mTransitionController.requestStartTransition(transition,
+                    task, remoteTransition, null /* displayChange */);
+            r.mTransitionController.collect(task);
             r.mTransitionController.setTransientLaunch(r,
                     TaskDisplayArea.getRootTaskAbove(rootTask));
             task.moveToFront("startExistingRecents");
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index d7c5e93..719f72c 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -399,8 +399,11 @@
      * @return The intercepting intent if needed.
      */
     private Intent interceptWithConfirmCredentialsIfNeeded(ActivityInfo aInfo, int userId) {
+        if (!mService.mAmInternal.shouldConfirmCredentials(userId)) {
+            return null;
+        }
         if ((aInfo.flags & ActivityInfo.FLAG_SHOW_WHEN_LOCKED) != 0
-                || !mService.mAmInternal.shouldConfirmCredentials(userId)) {
+                && (mUserManager.isUserUnlocked(userId) || aInfo.directBootAware)) {
             return null;
         }
         final IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 1d70146..1e06375 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -53,7 +53,9 @@
 import static android.content.pm.ActivityInfo.launchModeToString;
 import static android.os.Process.INVALID_UID;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
@@ -78,7 +80,6 @@
 import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK;
-import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -1388,9 +1389,9 @@
                 && transitionController.getTransitionPlayer() != null)
                 ? transitionController.createTransition(TRANSIT_OPEN) : null;
         RemoteTransition remoteTransition = r.takeRemoteTransition();
-        transitionController.collect(r);
         try {
             mService.deferWindowLayout();
+            transitionController.collect(r);
             try {
                 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
                 result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
@@ -1663,7 +1664,8 @@
         }
         final Task startedTask = mStartActivity.getTask();
         if (newTask) {
-            EventLogTags.writeWmCreateTask(mStartActivity.mUserId, startedTask.mTaskId);
+            EventLogTags.writeWmCreateTask(mStartActivity.mUserId, startedTask.mTaskId,
+                    startedTask.getRootTaskId(), startedTask.getDisplayId());
         }
         mStartActivity.logStartActivity(EventLogTags.WM_CREATE_ACTIVITY, startedTask);
 
@@ -1856,6 +1858,11 @@
                         + " from background: " + mSourceRecord
                         + ". New task: " + newTask);
                 boolean newOrEmptyTask = newTask || (targetTopActivity == null);
+                int action = newTask
+                        ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_NEW_TASK
+                        : (mSourceRecord.getTask().equals(targetTask)
+                                ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_SAME_TASK
+                                :  FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_DIFFERENT_TASK);
                 FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
                         /* caller_uid */
                         callerUid,
@@ -1874,7 +1881,14 @@
                         /* target_intent_action */
                         r.intent.getAction(),
                         /* target_intent_flags */
-                        r.intent.getFlags()
+                        r.intent.getFlags(),
+                        /* action */
+                        action,
+                        /* version */
+                        1,
+                        /* multi_window */
+                        targetTask != null && !targetTask.equals(mSourceRecord.getTask())
+                                && targetTask.isVisible()
                 );
             }
         }
@@ -1976,12 +1990,6 @@
                 ? targetTask.getTopNonFinishingActivity()
                 : targetTaskTop;
 
-        // At this point we are certain we want the task moved to the front. If we need to dismiss
-        // any other always-on-top root tasks, now is the time to do it.
-        if (targetTaskTop.canTurnScreenOn() && mService.isDreaming()) {
-            targetTaskTop.mTaskSupervisor.wakeUp("recycleTask#turnScreenOnFlag");
-        }
-
         if (mMovedToFront) {
             // We moved the task to front, use starting window to hide initial drawn delay.
             targetTaskTop.showStartingWindow(true /* taskSwitch */);
@@ -1993,6 +2001,12 @@
         // And for paranoia, make sure we have correctly resumed the top activity.
         resumeTargetRootTaskIfNeeded();
 
+        // This is moving an existing task to front. But since dream activity has a higher z-order
+        // to cover normal activities, it needs the awakening event to be dismissed.
+        if (mService.isDreaming() && targetTaskTop.canTurnScreenOn()) {
+            targetTaskTop.mTaskSupervisor.wakeUp("recycleTask#turnScreenOnFlag");
+        }
+
         mLastStartActivityRecord = targetTaskTop;
         return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
     }
@@ -2123,10 +2137,15 @@
                             mStartActivity.mUserId);
             if (act != null) {
                 final Task task = act.getTask();
-                boolean actuallyMoved = task.moveActivityToFrontLocked(act);
+                boolean actuallyMoved = task.moveActivityToFront(act);
                 if (actuallyMoved) {
                     // Only record if the activity actually moved.
                     mMovedToTopActivity = act;
+                    if (mNoAnimation) {
+                        act.mDisplayContent.prepareAppTransition(TRANSIT_NONE);
+                    } else {
+                        act.mDisplayContent.prepareAppTransition(TRANSIT_TO_FRONT);
+                    }
                 }
                 act.updateOptionsLocked(mOptions);
                 deliverNewIntent(act, intentGrants);
@@ -2618,7 +2637,7 @@
                     // If the activity is visible in multi-windowing mode, it may already be on
                     // the top (visible to user but not the global top), then the result code
                     // should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
-                    final boolean wasTopOfVisibleRootTask = intentActivity.mVisibleRequested
+                    final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
                             && intentActivity.inMultiWindowMode()
                             && intentActivity == mTargetRootTask.topRunningActivity();
                     // We only want to move to the front, if we aren't going to launch on a
@@ -2643,10 +2662,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.
@@ -2728,7 +2751,6 @@
                 newParent = candidateTf;
             }
         }
-        newParent.mTransitionController.collect(newParent);
         if (mStartActivity.getTaskFragment() == null
                 || mStartActivity.getTaskFragment() == newParent) {
             newParent.addChild(mStartActivity, POSITION_TOP);
@@ -2761,11 +2783,6 @@
                 errMsg = "The app:" + mCallingUid + "is not trusted to " + mStartActivity;
                 break;
             }
-            case EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT: {
-                errMsg = "Cannot embed activity across TaskFragments for result, resultTo: "
-                        + mStartActivity.resultTo;
-                break;
-            }
             default:
                 errMsg = "Unhandled embed result:" + result;
         }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 4d970f0..ec48643 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -287,8 +287,10 @@
 
     /**
      * Called when the device changes its dreaming state.
+     *
+     * @param activeDreamComponent The currently active dream. If null, the device is not dreaming.
      */
-    public abstract void notifyDreamStateChanged(boolean dreaming);
+    public abstract void notifyActiveDreamChanged(@Nullable ComponentName activeDreamComponent);
 
     /**
      * Set a uid that is allowed to bypass stopped app switches, launching an app
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ecc43f7..eb04687 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -209,7 +209,6 @@
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.service.dreams.DreamActivity;
-import android.service.dreams.DreamManagerInternal;
 import android.service.voice.IVoiceInteractionSession;
 import android.service.voice.VoiceInteractionManagerInternal;
 import android.sysprop.DisplayProperties;
@@ -669,11 +668,12 @@
     private volatile boolean mSleeping;
 
     /**
-     * The mDreaming state is set by the {@link DreamManagerService} when it receives a request to
-     * start/stop the dream. It is set to true shortly  before the {@link DreamService} is started.
-     * It is set to false after the {@link DreamService} is stopped.
+     * The mActiveDreamComponent state is set by the {@link DreamManagerService} when it receives a
+     * request to start/stop the dream. It is set to the active dream shortly before the
+     * {@link DreamService} is started. It is set to null after the {@link DreamService} is stopped.
      */
-    private volatile boolean mDreaming;
+    @Nullable
+    private volatile ComponentName mActiveDreamComponent;
 
     /**
      * The process state used for processes that are running the top activities.
@@ -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);
     }
 
@@ -1439,31 +1439,21 @@
     }
 
     boolean isDreaming() {
-        return mDreaming;
+        return mActiveDreamComponent != null;
     }
 
     boolean canLaunchDreamActivity(String packageName) {
-        if (!mDreaming || packageName == null) {
+        if (mActiveDreamComponent == null || packageName == null) {
             ProtoLog.e(WM_DEBUG_DREAM, "Cannot launch dream activity due to invalid state. "
-                    + "dreaming: %b packageName: %s", mDreaming, packageName);
+                    + "dream component: %s packageName: %s", mActiveDreamComponent, packageName);
             return false;
         }
-        final DreamManagerInternal dreamManager =
-                LocalServices.getService(DreamManagerInternal.class);
-        // Verify that the package is the current active dream or doze component. The
-        // getActiveDreamComponent() call path does not acquire the DreamManager lock and thus
-        // is safe to use.
-        final ComponentName activeDream = dreamManager.getActiveDreamComponent(false /* doze */);
-        if (activeDream != null && packageName.equals(activeDream.getPackageName())) {
-            return true;
-        }
-        final ComponentName activeDoze = dreamManager.getActiveDreamComponent(true /* doze */);
-        if (activeDoze != null && packageName.equals(activeDoze.getPackageName())) {
+        if (packageName.equals(mActiveDreamComponent.getPackageName())) {
             return true;
         }
         ProtoLog.e(WM_DEBUG_DREAM,
-                "Dream packageName does not match active dream. Package %s does not match %s or %s",
-                packageName, String.valueOf(activeDream), String.valueOf(activeDoze));
+                "Dream packageName does not match active dream. Package %s does not match %s",
+                packageName, String.valueOf(mActiveDreamComponent));
         return false;
     }
 
@@ -4396,6 +4386,13 @@
                 values.touchscreen,
                 values.uiMode);
 
+        // Note: certain tests currently run as platform_app which is not allowed
+        // to set debug system properties. To ensure that system properties are set
+        // only when allowed, we check the current UID.
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            SystemProperties.set("debug.tracing.mcc", Integer.toString(values.mcc));
+            SystemProperties.set("debug.tracing.mnc", Integer.toString(values.mnc));
+        }
 
         if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
             final LocaleList locales = values.getLocales();
@@ -5680,9 +5677,9 @@
         }
 
         @Override
-        public void notifyDreamStateChanged(boolean dreaming) {
+        public void notifyActiveDreamChanged(@Nullable ComponentName dreamComponent) {
             synchronized (mGlobalLock) {
-                mDreaming = dreaming;
+                mActiveDreamComponent = dreamComponent;
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 3c457e1..8a247cf 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -27,6 +27,7 @@
 import static android.app.ActivityManager.START_FLAG_NATIVE_DEBUGGING;
 import static android.app.ActivityManager.START_FLAG_TRACK_ALLOCATION;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
 import static android.app.WaitResult.INVALID_DELAY;
@@ -144,6 +145,7 @@
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.HostingRecord;
 import com.android.server.am.UserState;
+import com.android.server.pm.PackageManagerServiceUtils;
 import com.android.server.utils.Slogf;
 import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
 
@@ -2577,6 +2579,11 @@
                         // Apply options to prevent pendingOptions be taken when scheduling
                         // activity lifecycle transaction to make sure the override pending app
                         // transition will be applied immediately.
+                        if (activityOptions != null
+                                && activityOptions.getAnimationType() == ANIM_REMOTE_ANIMATION) {
+                            targetActivity.mPendingRemoteAnimation =
+                                    activityOptions.getRemoteAnimationAdapter();
+                        }
                         targetActivity.applyOptionsAnimation();
                         if (activityOptions != null && activityOptions.getLaunchCookie() != null) {
                             targetActivity.mLaunchCookie = activityOptions.getLaunchCookie();
@@ -2613,12 +2620,17 @@
         // ActivityStarter will acquire the lock where the places need, so execute the request
         // outside of the lock.
         try {
+            // We need to temporarily disable the explicit intent filter matching enforcement
+            // because Task does not store the resolved type of the intent data, causing filter
+            // mismatch in certain cases. (b/240373119)
+            PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(true);
             return mService.getActivityStartController().startActivityInPackage(taskCallingUid,
                     callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null,
                     null, 0, 0, options, userId, task, "startActivityFromRecents",
                     false /* validateIncomingUser */, null /* originatingPendingIntent */,
                     false /* allowBackgroundActivityStart */);
         } finally {
+            PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(false);
             synchronized (mService.mGlobalLock) {
                 mService.continueWindowLayout();
             }
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/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index a487797..b9a4ed8 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -169,7 +169,8 @@
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
 
-    private final TransitionAnimation mTransitionAnimation;
+    @VisibleForTesting
+    final TransitionAnimation mTransitionAnimation;
 
     private @TransitionFlags int mNextAppTransitionFlags = 0;
     private final ArrayList<Integer> mNextAppTransitionRequests = new ArrayList<>();
@@ -308,10 +309,33 @@
         setAppTransitionState(APP_STATE_TIMEOUT);
     }
 
+    /**
+     * Gets the animation overridden by app via {@link #overridePendingAppTransition}.
+     */
+    @Nullable
+    Animation getNextAppRequestedAnimation(boolean enter) {
+        final Animation a = mTransitionAnimation.loadAppTransitionAnimation(
+                mNextAppTransitionPackage,
+                enter ? mNextAppTransitionEnter : mNextAppTransitionExit);
+        if (mNextAppTransitionBackgroundColor != 0 && a != null) {
+            a.setBackdropColor(mNextAppTransitionBackgroundColor);
+        }
+        return a;
+    }
+
+    /**
+     * Gets the animation background color overridden by app via
+     * {@link #overridePendingAppTransition}.
+     */
     @ColorInt int getNextAppTransitionBackgroundColor() {
         return mNextAppTransitionBackgroundColor;
     }
 
+    @VisibleForTesting
+    boolean isNextAppTransitionOverrideRequested() {
+        return mNextAppTransitionOverrideRequested;
+    }
+
     HardwareBuffer getAppTransitionThumbnailHeader(WindowContainer container) {
         AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
                 container.hashCode());
@@ -401,9 +425,12 @@
     }
 
     void clear() {
+        clear(true /* clearAppOverride */);
+    }
+
+    private void clear(boolean clearAppOverride) {
         mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
         mNextAppTransitionOverrideRequested = false;
-        mNextAppTransitionPackage = null;
         mNextAppTransitionAnimationsSpecs.clear();
         mRemoteAnimationController = null;
         mNextAppTransitionAnimationsSpecsFuture = null;
@@ -411,6 +438,12 @@
         mAnimationFinishedCallback = null;
         mOverrideTaskTransition = false;
         mNextAppTransitionIsSync = false;
+        if (clearAppOverride) {
+            mNextAppTransitionPackage = null;
+            mNextAppTransitionEnter = 0;
+            mNextAppTransitionExit = 0;
+            mNextAppTransitionBackgroundColor = 0;
+        }
     }
 
     void freeze() {
@@ -516,7 +549,7 @@
         return TransitionAnimation.loadAnimationSafely(context, resId, TAG);
     }
 
-    static int mapOpenCloseTransitTypes(int transit, boolean enter) {
+    private static int mapOpenCloseTransitTypes(int transit, boolean enter) {
         int animAttr = 0;
         switch (transit) {
             case TRANSIT_OLD_ACTIVITY_OPEN:
@@ -776,11 +809,7 @@
                     "applyAnimation: anim=%s transit=%s Callers=%s", a,
                     appTransitionOldToString(transit), Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
-            a = mTransitionAnimation.loadAppTransitionAnimation(mNextAppTransitionPackage,
-                    enter ? mNextAppTransitionEnter : mNextAppTransitionExit);
-            if (mNextAppTransitionBackgroundColor != 0) {
-                a.setBackdropColor(mNextAppTransitionBackgroundColor);
-            }
+            a = getNextAppRequestedAnimation(enter);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s "
                             + "isEntrance=%b Callers=%s",
@@ -1020,7 +1049,9 @@
         ProtoLog.i(WM_DEBUG_APP_TRANSITIONS, "Override pending remote transitionSet=%b adapter=%s",
                         isTransitionSet(), remoteAnimationAdapter);
         if (isTransitionSet() && !mNextAppTransitionIsSync) {
-            clear();
+            // ActivityEmbedding animation will run by the app process for which we want to respect
+            // the app override for whether or not to show background color.
+            clear(!isActivityEmbedding /* clearAppOverride */);
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE;
             mRemoteAnimationController = new RemoteAnimationController(mService, mDisplayContent,
                     remoteAnimationAdapter, mHandler, isActivityEmbedding);
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index c2e87e6..74d52b2 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -76,7 +76,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.IntDef;
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Rect;
 import android.os.Trace;
@@ -167,16 +166,6 @@
                 ? null : wallpaperTarget;
     }
 
-    @NonNull
-    private static ArraySet<ActivityRecord> getAppsForAnimation(
-            @NonNull ArraySet<ActivityRecord> apps, boolean excludeLauncherFromAnimation) {
-        final ArraySet<ActivityRecord> appsForAnimation = new ArraySet<>(apps);
-        if (excludeLauncherFromAnimation) {
-            appsForAnimation.removeIf(ConfigurationContainer::isActivityTypeHome);
-        }
-        return appsForAnimation;
-    }
-
     /**
      * Handle application transition for given display.
      */
@@ -226,45 +215,42 @@
         mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(
                 mDisplayContent.mOpeningApps);
 
-        // Remove launcher from app transition animation while recents is running. Recents animation
-        // is managed outside of app transition framework, so we just need to commit visibility.
-        final boolean excludeLauncherFromAnimation =
-                mDisplayContent.mOpeningApps.stream().anyMatch(
-                        (app) -> app.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS))
-                || mDisplayContent.mClosingApps.stream().anyMatch(
-                        (app) -> app.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS));
-        final ArraySet<ActivityRecord> openingAppsForAnimation = getAppsForAnimation(
-                mDisplayContent.mOpeningApps, excludeLauncherFromAnimation);
-        final ArraySet<ActivityRecord> closingAppsForAnimation = getAppsForAnimation(
-                mDisplayContent.mClosingApps, excludeLauncherFromAnimation);
+        ArraySet<ActivityRecord> tmpOpenApps = mDisplayContent.mOpeningApps;
+        ArraySet<ActivityRecord> tmpCloseApps = mDisplayContent.mClosingApps;
+        if (mDisplayContent.mAtmService.mBackNavigationController.isWaitBackTransition()) {
+            tmpOpenApps = new ArraySet<>(mDisplayContent.mOpeningApps);
+            tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps);
+            if (mDisplayContent.mAtmService.mBackNavigationController
+                    .removeIfContainsBackAnimationTargets(tmpOpenApps, tmpCloseApps)) {
+                mDisplayContent.mAtmService.mBackNavigationController.clearBackAnimations(null);
+            }
+        }
 
         @TransitionOldType final int transit = getTransitCompatType(
-                mDisplayContent.mAppTransition, openingAppsForAnimation, closingAppsForAnimation,
-                mDisplayContent.mChangingContainers,
+                mDisplayContent.mAppTransition, tmpOpenApps,
+                tmpCloseApps, mDisplayContent.mChangingContainers,
                 mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),
                 mDisplayContent.mSkipAppTransitionAnimation);
         mDisplayContent.mSkipAppTransitionAnimation = false;
 
         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                 "handleAppTransitionReady: displayId=%d appTransition={%s}"
-                + " excludeLauncherFromAnimation=%b openingApps=[%s] closingApps=[%s] transit=%s",
-                mDisplayContent.mDisplayId, appTransition.toString(), excludeLauncherFromAnimation,
-                mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                AppTransition.appTransitionOldToString(transit));
+                + " openingApps=[%s] closingApps=[%s] transit=%s",
+                mDisplayContent.mDisplayId, appTransition.toString(), tmpOpenApps,
+                tmpCloseApps, AppTransition.appTransitionOldToString(transit));
 
         // Find the layout params of the top-most application window in the tokens, which is
         // what will control the animation theme. If all closing windows are obscured, then there is
         // no need to do an animation. This is the case, for example, when this transition is being
         // done behind a dream window.
-        final ArraySet<Integer> activityTypes = collectActivityTypes(openingAppsForAnimation,
-                closingAppsForAnimation, mDisplayContent.mChangingContainers);
+        final ArraySet<Integer> activityTypes = collectActivityTypes(tmpOpenApps,
+                tmpCloseApps, mDisplayContent.mChangingContainers);
         final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes,
-                openingAppsForAnimation, closingAppsForAnimation,
-                mDisplayContent.mChangingContainers);
+                tmpOpenApps, tmpCloseApps, mDisplayContent.mChangingContainers);
         final ActivityRecord topOpeningApp =
-                getTopApp(openingAppsForAnimation, false /* ignoreHidden */);
+                getTopApp(tmpOpenApps, false /* ignoreHidden */);
         final ActivityRecord topClosingApp =
-                getTopApp(closingAppsForAnimation, false /* ignoreHidden */);
+                getTopApp(tmpCloseApps, false /* ignoreHidden */);
         final ActivityRecord topChangingApp =
                 getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
         final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
@@ -276,17 +262,17 @@
             overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
         }
 
-        final boolean voiceInteraction = containsVoiceInteraction(closingAppsForAnimation)
-                || containsVoiceInteraction(openingAppsForAnimation);
+        final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mClosingApps)
+                || containsVoiceInteraction(mDisplayContent.mOpeningApps);
 
         final int layoutRedo;
         mService.mSurfaceAnimationRunner.deferStartingAnimations();
         try {
-            applyAnimations(openingAppsForAnimation, closingAppsForAnimation, transit, animLp,
-                    voiceInteraction);
+            applyAnimations(tmpOpenApps, tmpCloseApps, transit, animLp, voiceInteraction);
             handleClosingApps();
             handleOpeningApps();
             handleChangingApps(transit);
+            handleClosingChangingContainers();
 
             appTransition.setLastAppTransition(transit, topOpeningApp,
                     topClosingApp, topChangingApp);
@@ -294,8 +280,8 @@
             final int flags = appTransition.getTransitFlags();
             layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
             appTransition.postAnimationCallback();
-            appTransition.clear();
         } finally {
+            appTransition.clear();
             mService.mSurfaceAnimationRunner.continueStartingAnimations();
         }
 
@@ -305,6 +291,7 @@
         mDisplayContent.mClosingApps.clear();
         mDisplayContent.mChangingContainers.clear();
         mDisplayContent.mUnknownAppVisibilityController.clear();
+        mDisplayContent.mClosingChangingContainers.clear();
 
         // This has changed the visibility of windows, so perform
         // a new layout to get them all up-to-date.
@@ -585,6 +572,34 @@
     }
 
     /**
+     * Whether the transition contains any embedded {@link TaskFragment} that does not fill the
+     * parent {@link Task} before or after the transition.
+     */
+    private boolean transitionContainsTaskFragmentWithBoundsOverride() {
+        for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
+            final WindowContainer wc = mDisplayContent.mChangingContainers.valueAt(i);
+            if (wc.isEmbedded()) {
+                // Contains embedded TaskFragment with bounds changed.
+                return true;
+            }
+        }
+        mTempTransitionWindows.clear();
+        mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
+        mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
+        boolean containsTaskFragmentWithBoundsOverride = false;
+        for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
+            final ActivityRecord r = mTempTransitionWindows.get(i).asActivityRecord();
+            final TaskFragment tf = r.getTaskFragment();
+            if (tf != null && tf.isEmbeddedWithBoundsOverride()) {
+                containsTaskFragmentWithBoundsOverride = true;
+                break;
+            }
+        }
+        mTempTransitionWindows.clear();
+        return containsTaskFragmentWithBoundsOverride;
+    }
+
+    /**
      * Finds the common parent {@link Task} that is parent of all embedded app windows in the
      * current transition.
      * @return {@code null} if app windows in the transition are not children of the same Task, or
@@ -687,12 +702,17 @@
         if (transitionMayContainNonAppWindows(transit)) {
             return false;
         }
+        if (!transitionContainsTaskFragmentWithBoundsOverride()) {
+            // No need to play TaskFragment remote animation if all embedded TaskFragment in the
+            // transition fill the Task.
+            return false;
+        }
 
         final Task task = findParentTaskForAllEmbeddedWindows();
         final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task);
         final RemoteAnimationDefinition definition = organizer != null
                 ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
-                    .getRemoteAnimationDefinition(organizer, task.mTaskId)
+                    .getRemoteAnimationDefinition(organizer)
                 : null;
         final RemoteAnimationAdapter adapter = definition != null
                 ? definition.getAdapter(transit, activityTypes)
@@ -1160,6 +1180,24 @@
         }
     }
 
+    private void handleClosingChangingContainers() {
+        final ArrayMap<WindowContainer, Rect> containers =
+                mDisplayContent.mClosingChangingContainers;
+        while (!containers.isEmpty()) {
+            final WindowContainer container = containers.keyAt(0);
+            containers.remove(container);
+
+            // For closing changing windows that are part of the transition, they should have been
+            // removed from mClosingChangingContainers in WindowContainer#getAnimationAdapter()
+            // If the closing changing TaskFragment is not part of the transition, update its
+            // surface after removing it from mClosingChangingContainers.
+            final TaskFragment taskFragment = container.asTaskFragment();
+            if (taskFragment != null) {
+                taskFragment.updateOrganizedTaskFragmentSurface();
+            }
+        }
+    }
+
     private void handleChangingApps(@TransitionOldType int transit) {
         final ArraySet<WindowContainer> apps = mDisplayContent.mChangingContainers;
         final int appsCount = apps.size();
@@ -1194,20 +1232,35 @@
                     "Delaying app transition for screen rotation animation to finish");
             return false;
         }
+        final boolean isRecentsInOpening = mDisplayContent.mOpeningApps.stream().anyMatch(
+                ConfigurationContainer::isActivityTypeRecents);
         for (int i = 0; i < apps.size(); i++) {
             WindowContainer wc = apps.valueAt(i);
             final ActivityRecord activity = getAppFromContainer(wc);
             if (activity == null) {
                 continue;
             }
+            // In order to avoid visual clutter caused by a conflict between app transition
+            // animation and recents animation, app transition is delayed until recents finishes.
+            // One exceptional case. When 3P launcher is used and a user taps a task screenshot in
+            // task switcher (isRecentsInOpening=true), app transition must start even though
+            // recents is running. Otherwise app transition is blocked until timeout (b/232984498).
+            // When 1P launcher is used, this animation is controlled by the launcher outside of
+            // the app transition, so delaying app transition doesn't cause visible delay. After
+            // recents finishes, app transition is handled just to commit visibility on apps.
+            if (!isRecentsInOpening && activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
+                ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+                        "Delaying app transition for recents animation to finish");
+                return false;
+            }
             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/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index d345227..cd26e2e 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -226,6 +226,9 @@
         }
 
         private void setReady(boolean ready) {
+            if (mReady == ready) {
+                return;
+            }
             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready", mSyncId);
             mReady = ready;
             if (!ready) return;
@@ -239,7 +242,9 @@
             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Adding to group: %s", mSyncId, wc);
             wc.setSyncGroup(this);
             wc.prepareSync();
-            mWm.mWindowPlacerLocked.requestTraversal();
+            if (mReady) {
+                mWm.mWindowPlacerLocked.requestTraversal();
+            }
         }
 
         void onCancelSync(WindowContainer wc) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 1cb83f12..14131e6 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -16,15 +16,21 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
+import static com.android.server.wm.BackNavigationProto.ANIMATION_IN_PROGRESS;
+import static com.android.server.wm.BackNavigationProto.LAST_BACK_TYPE;
+import static com.android.server.wm.BackNavigationProto.SHOW_WALLPAPER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.ComponentName;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
@@ -33,7 +39,9 @@
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.util.ArraySet;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 import android.view.IWindowFocusObserver;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
@@ -41,9 +49,7 @@
 import android.window.BackNavigationInfo;
 import android.window.IBackAnimationFinishedCallback;
 import android.window.OnBackInvokedCallbackInfo;
-import android.window.ScreenCapture;
 import android.window.TaskSnapshot;
-import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
@@ -59,12 +65,13 @@
     private WindowManagerService mWindowManagerService;
     private IWindowFocusObserver mFocusObserver;
     private boolean mBackAnimationInProgress;
+    private @BackNavigationInfo.BackTargetType int mLastBackType;
     private boolean mShowWallpaper;
     private Runnable mPendingAnimation;
 
-    // TODO (b/241808055) Find a appropriate time to remove during refactor
-    // Execute back animation with legacy transition system. Temporary flag for easier debugging.
-    static final boolean ENABLE_SHELL_TRANSITIONS = WindowManagerService.sEnableShellTransitions;
+    private final AnimationTargets mAnimationTargets = new AnimationTargets();
+    private final ArrayList<WindowContainer> mTmpOpenApps = new ArrayList<>();
+    private final ArrayList<WindowContainer> mTmpCloseApps = new ArrayList<>();
 
     /**
      * true if the back predictability feature is enabled
@@ -161,6 +168,12 @@
                             + "recents. Overriding back callback to recents controller callback.");
                     return null;
                 }
+
+                if (!window.isDrawn()) {
+                    ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
+                            "Focused window didn't have a valid surface drawn.");
+                    return null;
+                }
             }
 
             if (window == null) {
@@ -214,15 +227,15 @@
                 infoBuilder.setOnBackNavigationDone(new RemoteCallback(result ->
                         onBackNavigationDone(result, finalFocusedWindow,
                                 BackNavigationInfo.TYPE_CALLBACK)));
-
+                mLastBackType = backType;
                 return infoBuilder.setType(backType).build();
             }
 
             mBackAnimationInProgress = true;
             // We don't have an application callback, let's find the destination of the back gesture
-            Task finalTask = currentTask;
-            prevActivity = currentTask.getActivity(
-                    (r) -> !r.finishing && r.getTask() == finalTask && !r.isTopRunningActivity());
+            // The search logic should align with ActivityClientController#finishActivity
+            prevActivity = currentTask.topRunningActivity(currentActivity.token, INVALID_TASK_ID);
+            final boolean isOccluded = isKeyguardOccluded(window);
             // TODO Dialog window does not need to attach on activity, check
             // window.mAttrs.type != TYPE_BASE_APPLICATION
             if ((window.getParent().getChildCount() > 1
@@ -232,27 +245,46 @@
                 backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
                 removedWindowContainer = window;
             } else if (prevActivity != null) {
-                // We have another Activity in the same currentTask to go to
-                backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
-                removedWindowContainer = currentActivity;
+                if (!isOccluded || prevActivity.canShowWhenLocked()) {
+                    // We have another Activity in the same currentTask to go to
+                    backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
+                    removedWindowContainer = currentActivity;
+                    prevTask = prevActivity.getTask();
+                } else {
+                    backType = BackNavigationInfo.TYPE_CALLBACK;
+                }
             } else if (currentTask.returnsToHomeRootTask()) {
-                // Our Task should bring back to home
-                removedWindowContainer = currentTask;
-                backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
-                mShowWallpaper = true;
+                if (isOccluded) {
+                    backType = BackNavigationInfo.TYPE_CALLBACK;
+                } else {
+                    // Our Task should bring back to home
+                    removedWindowContainer = currentTask;
+                    prevTask = currentTask.getDisplayArea().getRootHomeTask();
+                    backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+                    mShowWallpaper = true;
+                }
             } 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);
+                prevTask = currentTask.mRootWindowContainer.getTask(Task::showToCurrentUser,
+                        currentTask, false /*includeBoundary*/, true /*traverseTopToBottom*/);
                 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 (prevActivity == null || (isOccluded && !prevActivity.canShowWhenLocked())) {
+                        backType = BackNavigationInfo.TYPE_CALLBACK;
+                    } else if (prevTask.isActivityTypeHome()) {
+                        backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+                        mShowWallpaper = true;
+                    } else {
+                        backType = BackNavigationInfo.TYPE_CROSS_TASK;
+                    }
                 }
-                mShowWallpaper = true;
             }
             infoBuilder.setType(backType);
 
@@ -263,8 +295,11 @@
                     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, cross task or cross-activity.
+            boolean prepareAnimation =
+                    (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+                            || backType == BackNavigationInfo.TYPE_CROSS_TASK
+                            || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY)
                     && adapter != null;
 
             // Only prepare animation if no leash has been created (no animation is running).
@@ -277,7 +312,6 @@
             }
 
             if (prepareAnimation) {
-                infoBuilder.setDepartingWCT(toWindowContainerToken(currentTask));
                 prepareAnimationIfNeeded(currentTask, prevTask, prevActivity,
                         removedWindowContainer, backType, adapter);
             }
@@ -292,15 +326,248 @@
                     result, finalFocusedWindow, finalBackType));
             infoBuilder.setOnBackNavigationDone(onBackNavigationDone);
         }
-
+        mLastBackType = backType;
         return infoBuilder.build();
     }
 
-    private static WindowContainerToken toWindowContainerToken(WindowContainer<?> windowContainer) {
-        if (windowContainer == null || windowContainer.mRemoteToken == null) {
-            return null;
+    boolean isWaitBackTransition() {
+        return mAnimationTargets.mComposed && mAnimationTargets.mWaitTransition;
+    }
+
+    boolean isKeyguardOccluded(WindowState focusWindow) {
+        final KeyguardController kc = mWindowManagerService.mAtmService.mKeyguardController;
+        final int displayId = focusWindow.getDisplayId();
+        return kc.isKeyguardLocked(displayId) && kc.isDisplayOccluded(displayId);
+    }
+
+    // For legacy transition.
+    /**
+     *  Once we find the transition targets match back animation targets, remove the target from
+     *  list, so that transition won't count them in since the close animation was finished.
+     *
+     *  @return {@code true} if the participants of this transition was animated by back gesture
+     *  animations, and shouldn't join next transition.
+     */
+    boolean removeIfContainsBackAnimationTargets(ArraySet<ActivityRecord> openApps,
+            ArraySet<ActivityRecord> closeApps) {
+        if (!isWaitBackTransition()) {
+            return false;
         }
-        return windowContainer.mRemoteToken.toWindowContainerToken();
+        mTmpCloseApps.addAll(closeApps);
+        boolean result = false;
+        // Note: TmpOpenApps is empty. Unlike shell transition, the open apps will be removed from
+        // mOpeningApps if there is no visibility change.
+        if (mAnimationTargets.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps)) {
+            // remove close target from close list, open target from open list;
+            // but the open target can be in close list.
+            for (int i = openApps.size() - 1; i >= 0; --i) {
+                final ActivityRecord ar = openApps.valueAt(i);
+                if (mAnimationTargets.isTarget(ar, true /* open */)) {
+                    openApps.removeAt(i);
+                }
+            }
+            for (int i = closeApps.size() - 1; i >= 0; --i) {
+                final ActivityRecord ar = closeApps.valueAt(i);
+                if (mAnimationTargets.isTarget(ar, false /* open */)) {
+                    closeApps.removeAt(i);
+                }
+            }
+            result = true;
+        }
+        mTmpCloseApps.clear();
+        return result;
+    }
+
+    // For shell transition
+    /**
+     *  Check whether the transition targets was animated by back gesture animation.
+     *  Because the opening target could request to do other stuff at onResume, so it could become
+     *  close target for a transition. So the condition here is
+     *  The closing target should only exist in close list, but the opening target can be either in
+     *  open or close list.
+     *  @return {@code true} if the participants of this transition was animated by back gesture
+     *  animations, and shouldn't join next transition.
+     */
+    boolean containsBackAnimationTargets(Transition transition) {
+        if (!mAnimationTargets.mComposed
+                || (transition.mType != TRANSIT_CLOSE && transition.mType != TRANSIT_TO_BACK)) {
+            return false;
+        }
+        final ArraySet<WindowContainer> targets = transition.mParticipants;
+        for (int i = targets.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = targets.valueAt(i);
+            if (wc.asActivityRecord() == null && wc.asTask() == null) {
+                continue;
+            }
+            // WC can be visible due to setLaunchBehind
+            if (wc.isVisibleRequested()) {
+                mTmpOpenApps.add(wc);
+            } else {
+                mTmpCloseApps.add(wc);
+            }
+        }
+        final boolean result = mAnimationTargets.containsBackAnimationTargets(
+                mTmpOpenApps, mTmpCloseApps);
+        mTmpOpenApps.clear();
+        mTmpCloseApps.clear();
+        return result;
+    }
+
+    boolean isMonitorTransitionTarget(WindowContainer wc) {
+        if (!mAnimationTargets.mComposed || !mAnimationTargets.mWaitTransition) {
+            return false;
+        }
+        return mAnimationTargets.isTarget(wc, wc.isVisibleRequested() /* open */);
+    }
+
+    /**
+     * Cleanup animation, this can either happen when transition ready or finish.
+     * @param cleanupTransaction The transaction which the caller want to apply the internal
+     *                           cleanup together.
+     */
+    void clearBackAnimations(SurfaceControl.Transaction cleanupTransaction) {
+        mAnimationTargets.clearBackAnimateTarget(cleanupTransaction);
+    }
+
+    /**
+     * TODO: Animation composer
+     * prepareAnimationIfNeeded will become too complicated in order to support
+     * ActivityRecord/WindowState, using a factory class to create the RemoteAnimationTargets for
+     * different scenario.
+     */
+    private static class AnimationTargets {
+        ActivityRecord mCloseTarget; // Must be activity
+        WindowContainer mOpenTarget; // Can be activity or task if activity was removed
+        private boolean mComposed;
+        private boolean mWaitTransition;
+        private int mSwitchType = UNKNOWN;
+        private SurfaceControl.Transaction mFinishedTransaction;
+
+        private static final int UNKNOWN = 0;
+        private static final int TASK_SWITCH = 1;
+        private static final int ACTIVITY_SWITCH = 2;
+
+        void reset(@NonNull WindowContainer close, @NonNull WindowContainer open) {
+            clearBackAnimateTarget(null);
+            if (close == null || open == null) {
+                Slog.e(TAG, "reset animation with null target close: "
+                        + close + " open: " + open);
+                return;
+            }
+            if (close.asActivityRecord() != null && open.asActivityRecord() != null
+                    && (close.asActivityRecord().getTask() == open.asActivityRecord().getTask())) {
+                mSwitchType = ACTIVITY_SWITCH;
+                mCloseTarget = close.asActivityRecord();
+            } else if (close.asTask() != null && open.asTask() != null
+                    && close.asTask() != open.asTask()) {
+                mSwitchType = TASK_SWITCH;
+                mCloseTarget = close.asTask().getTopNonFinishingActivity();
+            } else {
+                mSwitchType = UNKNOWN;
+                return;
+            }
+
+            mOpenTarget = open;
+            mComposed = false;
+            mWaitTransition = false;
+        }
+
+        void composeNewAnimations(@NonNull WindowContainer close, @NonNull WindowContainer open) {
+            reset(close, open);
+            if (mSwitchType == UNKNOWN || mComposed || mCloseTarget == mOpenTarget
+                    || mCloseTarget == null || mOpenTarget == null) {
+                return;
+            }
+            mComposed = true;
+            mWaitTransition = false;
+        }
+
+        boolean containTarget(ArrayList<WindowContainer> wcs, boolean open) {
+            for (int i = wcs.size() - 1; i >= 0; --i) {
+                if (isTarget(wcs.get(i), open)) {
+                    return true;
+                }
+            }
+            return wcs.isEmpty();
+        }
+
+        boolean isTarget(WindowContainer wc, boolean open) {
+            if (open) {
+                return wc == mOpenTarget || mOpenTarget.hasChild(wc);
+            }
+            if (mSwitchType == TASK_SWITCH) {
+                return  wc == mCloseTarget
+                        || (wc.asTask() != null && wc.hasChild(mCloseTarget));
+            } else if (mSwitchType == ACTIVITY_SWITCH) {
+                return wc == mCloseTarget;
+            }
+            return false;
+        }
+
+        boolean setFinishTransaction(SurfaceControl.Transaction finishTransaction) {
+            if (!mComposed) {
+                return false;
+            }
+            mFinishedTransaction = finishTransaction;
+            return true;
+        }
+
+        void finishPresentAnimations(SurfaceControl.Transaction t) {
+            if (!mComposed) {
+                return;
+            }
+            final SurfaceControl.Transaction pt = t != null ? t
+                    : mOpenTarget.getPendingTransaction();
+            if (mFinishedTransaction != null) {
+                pt.merge(mFinishedTransaction);
+                mFinishedTransaction = null;
+            }
+        }
+
+        void clearBackAnimateTarget(SurfaceControl.Transaction cleanupTransaction) {
+            finishPresentAnimations(cleanupTransaction);
+            mCloseTarget = null;
+            mOpenTarget = null;
+            mComposed = false;
+            mWaitTransition = false;
+            mSwitchType = UNKNOWN;
+            if (mFinishedTransaction != null) {
+                Slog.w(TAG, "Clear back animation, found un-processed finished transaction");
+                if (cleanupTransaction != null) {
+                    cleanupTransaction.merge(mFinishedTransaction);
+                } else {
+                    mFinishedTransaction.apply();
+                }
+                mFinishedTransaction = null;
+            }
+        }
+
+        // The close target must in close list
+        // The open target can either in close or open list
+        boolean containsBackAnimationTargets(ArrayList<WindowContainer> openApps,
+                ArrayList<WindowContainer> closeApps) {
+            return containTarget(closeApps, false /* open */)
+                    && (containTarget(openApps, true /* open */)
+                    || containTarget(openApps, false /* open */));
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder(128);
+            sb.append("AnimationTargets{");
+            sb.append(" mOpenTarget= ");
+            sb.append(mOpenTarget);
+            sb.append(" mCloseTarget= ");
+            sb.append(mCloseTarget);
+            sb.append(" mSwitchType= ");
+            sb.append(mSwitchType);
+            sb.append(" mComposed= ");
+            sb.append(mComposed);
+            sb.append(" mWaitTransition= ");
+            sb.append(mWaitTransition);
+            sb.append('}');
+            return sb.toString();
+        }
     }
 
     private void prepareAnimationIfNeeded(Task currentTask,
@@ -328,6 +595,7 @@
         RemoteAnimationTarget behindAppTarget = null;
         if (needsScreenshot(backType)) {
             HardwareBuffer screenshotBuffer = null;
+            Task backTargetTask = prevTask;
             switch(backType) {
                 case BackNavigationInfo.TYPE_CROSS_TASK:
                     int prevTaskId = prevTask != null ? prevTask.mTaskId : 0;
@@ -335,14 +603,10 @@
                     screenshotBuffer = getTaskSnapshot(prevTaskId, prevUserId);
                     break;
                 case BackNavigationInfo.TYPE_CROSS_ACTIVITY:
-                    //TODO(207481538) Remove once the infrastructure to support per-activity
-                    // screenshot is implemented. For now we simply have the mBackScreenshots hash
-                    // map that dumbly saves the screenshots.
-                    if (prevActivity != null
-                            && prevActivity.mActivityComponent != null) {
-                        screenshotBuffer =
-                                getActivitySnapshot(currentTask, prevActivity.mActivityComponent);
+                    if (prevActivity != null && prevActivity.mActivityComponent != null) {
+                        screenshotBuffer = getActivitySnapshot(currentTask, prevActivity);
                     }
+                    backTargetTask = currentTask;
                     break;
             }
 
@@ -361,36 +625,30 @@
                 // leash needs to be added before to be in the synchronized block.
                 startedTransaction.setLayer(topAppTarget.leash, 1);
 
-                behindAppTarget = createRemoteAnimationTargetLocked(
-                        prevTask, screenshotSurface, MODE_OPENING);
+                behindAppTarget =
+                        createRemoteAnimationTargetLocked(
+                                backTargetTask, screenshotSurface, MODE_OPENING);
 
                 // reset leash after animation finished.
                 leashes.add(screenshotSurface);
             }
-        } else if (prevTask != null) {
-            if (!ENABLE_SHELL_TRANSITIONS) {
-                // Special handling for preventing next transition.
-                currentTask.mBackGestureStarted = true;
-            }
-            prevActivity = prevTask.getTopNonFinishingActivity();
-            if (prevActivity != null) {
-                // Make previous task show from behind by marking its top activity as visible
-                // and launch-behind to bump its visibility for the duration of the back gesture.
-                setLaunchBehind(prevActivity);
+        } else if (prevTask != null && prevActivity != null) {
+            // Make previous task show from behind by marking its top activity as visible
+            // and launch-behind to bump its visibility for the duration of the back gesture.
+            setLaunchBehind(prevActivity);
 
-                final SurfaceControl leash = prevActivity.makeAnimationLeash()
-                        .setName("BackPreview Leash for " + prevActivity)
-                        .setHidden(false)
-                        .build();
-                prevActivity.reparentSurfaceControl(startedTransaction, leash);
-                behindAppTarget = createRemoteAnimationTargetLocked(
-                        prevTask, leash, MODE_OPENING);
+            final SurfaceControl leash = prevActivity.makeAnimationLeash()
+                    .setName("BackPreview Leash for " + prevActivity)
+                    .setHidden(false)
+                    .build();
+            prevActivity.reparentSurfaceControl(startedTransaction, leash);
+            behindAppTarget = createRemoteAnimationTargetLocked(
+                    prevTask, leash, MODE_OPENING);
 
-                // reset leash after animation finished.
-                leashes.add(leash);
-                prevActivity.reparentSurfaceControl(finishedTransaction,
-                        prevActivity.getParentSurfaceControl());
-            }
+            // reset leash after animation finished.
+            leashes.add(leash);
+            prevActivity.reparentSurfaceControl(finishedTransaction,
+                    prevActivity.getParentSurfaceControl());
         }
 
         if (mShowWallpaper) {
@@ -412,35 +670,36 @@
                         for (SurfaceControl sc: leashes) {
                             finishedTransaction.remove(sc);
                         }
-
                         synchronized (mWindowManagerService.mGlobalLock) {
-                            if (ENABLE_SHELL_TRANSITIONS) {
-                                if (!triggerBack) {
-                                    if (!needsScreenshot(backType)) {
-                                        restoreLaunchBehind(finalPrevActivity);
-                                    }
+                            if (triggerBack) {
+                                final SurfaceControl surfaceControl =
+                                        removedWindowContainer.getSurfaceControl();
+                                if (surfaceControl != null && surfaceControl.isValid()) {
+                                    // The animation is finish and start waiting for transition,
+                                    // hide the task surface before it re-parented to avoid flicker.
+                                    finishedTransaction.hide(surfaceControl);
                                 }
+                            } else if (!needsScreenshot(backType)) {
+                                restoreLaunchBehind(finalPrevActivity);
+                            }
+                            if (!mAnimationTargets.setFinishTransaction(finishedTransaction)) {
+                                finishedTransaction.apply();
+                            }
+                            if (!triggerBack) {
+                                mAnimationTargets.clearBackAnimateTarget(null);
                             } else {
-                                if (triggerBack) {
-                                    final SurfaceControl surfaceControl =
-                                            removedWindowContainer.getSurfaceControl();
-                                    if (surfaceControl != null && surfaceControl.isValid()) {
-                                        // When going back to home, hide the task surface before it
-                                        // re-parented to avoid flicker.
-                                        finishedTransaction.hide(surfaceControl);
-                                    }
-                                } else {
-                                    currentTask.mBackGestureStarted = false;
-                                    if (!needsScreenshot(backType)) {
-                                        restoreLaunchBehind(finalPrevActivity);
-                                    }
-                                }
+                                mAnimationTargets.mWaitTransition = true;
                             }
                         }
-                        finishedTransaction.apply();
+                        // TODO Add timeout monitor if transition didn't happen
                     }
                 };
-
+        if (backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
+            mAnimationTargets.composeNewAnimations(removedWindowContainer, prevActivity);
+        } else if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+                || backType == BackNavigationInfo.TYPE_CROSS_TASK) {
+            mAnimationTargets.composeNewAnimations(removedWindowContainer, prevTask);
+        }
         scheduleAnimationLocked(backType, targets, adapter, callback);
     }
 
@@ -535,14 +794,8 @@
         mShowWallpaper = false;
     }
 
-    private HardwareBuffer getActivitySnapshot(@NonNull Task task,
-            ComponentName activityComponent) {
-        // Check if we have a screenshot of the previous activity, indexed by its
-        // component name.
-        ScreenCapture.ScreenshotHardwareBuffer backBuffer = task.mBackScreenshots
-                .get(activityComponent.flattenToString());
-        return backBuffer != null ? backBuffer.getHardwareBuffer() : null;
-
+    private HardwareBuffer getActivitySnapshot(@NonNull Task task, ActivityRecord r) {
+        return task.getSnapshotForActivityRecord(r);
     }
 
     private HardwareBuffer getTaskSnapshot(int taskId, int userId) {
@@ -574,7 +827,7 @@
         if (activity == null) {
             return;
         }
-        if (!activity.mVisibleRequested) {
+        if (!activity.isVisibleRequested()) {
             activity.setVisibility(true);
         }
         activity.mLaunchTaskBehind = true;
@@ -612,9 +865,17 @@
     }
 
     boolean isWallpaperVisible(WindowState w) {
-        if (mBackAnimationInProgress && w.isFocused()) {
-            return mShowWallpaper;
-        }
-        return false;
+        return mAnimationTargets.mComposed && mShowWallpaper
+                && w.mAttrs.type == TYPE_BASE_APPLICATION && w.mActivityRecord != null
+                && mAnimationTargets.isTarget(w.mActivityRecord, true /* open */);
+    }
+
+    // Called from WindowManagerService to write to a protocol buffer output stream.
+    void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(ANIMATION_IN_PROGRESS, mBackAnimationInProgress);
+        proto.write(LAST_BACK_TYPE, mLastBackType);
+        proto.write(SHOW_WALLPAPER, mShowWallpaper);
+        proto.end(token);
     }
 }
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/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index b84b2d8..89f1bd0 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -343,7 +343,11 @@
             if (childArea == null) {
                 continue;
             }
-            pw.println(prefix + "* " + childArea.getName());
+            pw.print(prefix + "* " + childArea.getName());
+            if (childArea.isOrganized()) {
+                pw.print(" (organized)");
+            }
+            pw.println();
             if (childArea.isTaskDisplayArea()) {
                 // TaskDisplayArea can only contain task. And it is already printed by display.
                 continue;
@@ -369,6 +373,55 @@
     }
 
     @Override
+    ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom,
+            ActivityRecord boundary) {
+        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+            return null;
+        }
+        return super.getActivity(callback, traverseTopToBottom, boundary);
+    }
+
+    @Override
+    Task getTask(Predicate<Task> callback, boolean traverseTopToBottom) {
+        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+            return null;
+        }
+        return super.getTask(callback, traverseTopToBottom);
+    }
+
+    @Override
+    boolean forAllActivities(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
+        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+            return false;
+        }
+        return super.forAllActivities(callback, traverseTopToBottom);
+    }
+
+    @Override
+    boolean forAllRootTasks(Predicate<Task> callback, boolean traverseTopToBottom) {
+        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+            return false;
+        }
+        return super.forAllRootTasks(callback, traverseTopToBottom);
+    }
+
+    @Override
+    boolean forAllTasks(Predicate<Task> callback) {
+        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+            return false;
+        }
+        return super.forAllTasks(callback);
+    }
+
+    @Override
+    boolean forAllLeafTasks(Predicate<Task> callback) {
+        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+            return false;
+        }
+        return super.forAllLeafTasks(callback);
+    }
+
+    @Override
     void forAllDisplayAreas(Consumer<DisplayArea> callback) {
         super.forAllDisplayAreas(callback);
         callback.accept(this);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8b34443..d5802cf 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -192,6 +192,7 @@
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.provider.Settings;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.DisplayUtils;
@@ -207,6 +208,7 @@
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
+import android.view.DisplayShape;
 import android.view.Gravity;
 import android.view.IDisplayWindowInsetsController;
 import android.view.ISystemGestureExclusionListener;
@@ -230,6 +232,7 @@
 import android.view.WindowManager;
 import android.view.WindowManager.DisplayImePolicy;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.view.inputmethod.ImeTracker;
 import android.window.DisplayWindowPolicyController;
 import android.window.IDisplayAreaOrganizer;
 import android.window.ScreenCapture;
@@ -310,6 +313,9 @@
      */
     private SurfaceControl mOverlayLayer;
 
+    /** A surfaceControl specifically for accessibility overlays. */
+    private SurfaceControl mA11yOverlayLayer;
+
     /**
      * The direct child layer of the display to put all non-overlay windows. This is also used for
      * screen rotation animation so that there is a parent layer to put the animation leash.
@@ -351,6 +357,11 @@
     final ArraySet<ActivityRecord> mClosingApps = new ArraySet<>();
     final ArraySet<WindowContainer> mChangingContainers = new ArraySet<>();
     final UnknownAppVisibilityController mUnknownAppVisibilityController;
+    /**
+     * If a container is closing when resizing, keeps track of its starting bounds when it is
+     * removed from {@link #mChangingContainers}.
+     */
+    final ArrayMap<WindowContainer, Rect> mClosingChangingContainers = new ArrayMap<>();
 
     private MetricsLogger mMetricsLogger;
 
@@ -387,6 +398,10 @@
             mPrivacyIndicatorBoundsCache = new
             RotationCache<>(this::calculatePrivacyIndicatorBoundsForRotationUncached);
 
+    DisplayShape mInitialDisplayShape;
+    private final RotationCache<DisplayShape, DisplayShape> mDisplayShapeCache =
+            new RotationCache<>(this::calculateDisplayShapeForRotationUncached);
+
     /**
      * Overridden display size. Initialized with {@link #mInitialDisplayWidth}
      * and {@link #mInitialDisplayHeight}, but can be set via shell command "adb shell wm size".
@@ -454,7 +469,7 @@
 
     /**
      * Compat metrics computed based on {@link #mDisplayMetrics}.
-     * @see #updateDisplayAndOrientation(int, Configuration)
+     * @see #updateDisplayAndOrientation(Configuration)
      */
     private final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics();
 
@@ -861,11 +876,11 @@
             final ActivityRecord activity = w.mActivityRecord;
             if (gone) Slog.v(TAG, "  GONE: mViewVisibility=" + w.mViewVisibility
                     + " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible()
-                    + " visibleRequested=" + (activity != null && activity.mVisibleRequested)
+                    + " visibleRequested=" + (activity != null && activity.isVisibleRequested())
                     + " parentHidden=" + w.isParentWindowHidden());
             else Slog.v(TAG, "  VIS: mViewVisibility=" + w.mViewVisibility
                     + " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible()
-                    + " visibleRequested=" + (activity != null && activity.mVisibleRequested)
+                    + " visibleRequested=" + (activity != null && activity.isVisibleRequested())
                     + " parentHidden=" + w.isParentWindowHidden());
         }
 
@@ -1091,7 +1106,8 @@
         mDisplayFrames = new DisplayFrames(mInsetsStateController.getRawInsetsState(),
                 mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
                 calculateRoundedCornersForRotation(mDisplayInfo.rotation),
-                calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation));
+                calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation),
+                calculateDisplayShapeForRotation(mDisplayInfo.rotation));
         initializeDisplayBaseInfo();
 
         mHoldScreenWakeLock = mWmService.mPowerManager.newWakeLock(
@@ -1268,12 +1284,21 @@
             transaction.reparent(mOverlayLayer, mSurfaceControl);
         }
 
+        if (mA11yOverlayLayer == null) {
+            mA11yOverlayLayer =
+                    b.setName("Accessibility Overlays").setParent(mSurfaceControl).build();
+        } else {
+            transaction.reparent(mA11yOverlayLayer, mSurfaceControl);
+        }
+
         transaction
                 .setLayer(mSurfaceControl, 0)
                 .setLayerStack(mSurfaceControl, mDisplayId)
                 .show(mSurfaceControl)
                 .setLayer(mOverlayLayer, Integer.MAX_VALUE)
-                .show(mOverlayLayer);
+                .show(mOverlayLayer)
+                .setLayer(mA11yOverlayLayer, Integer.MAX_VALUE - 1)
+                .show(mA11yOverlayLayer);
     }
 
     boolean isReady() {
@@ -1680,7 +1705,7 @@
                         .notifyTaskRequestedOrientationChanged(task.mTaskId, orientation);
             }
             // The orientation source may not be the top if it uses SCREEN_ORIENTATION_BEHIND.
-            final ActivityRecord topCandidate = !r.mVisibleRequested ? topRunningActivity() : r;
+            final ActivityRecord topCandidate = !r.isVisibleRequested() ? topRunningActivity() : r;
             if (handleTopActivityLaunchingInDifferentOrientation(
                     topCandidate, r, true /* checkOpening */)) {
                 // Display orientation should be deferred until the top fixed rotation is finished.
@@ -1956,8 +1981,9 @@
         final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
         final PrivacyIndicatorBounds indicatorBounds =
                 calculatePrivacyIndicatorBoundsForRotation(rotation);
+        final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation);
         final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(), info,
-                cutout, roundedCorners, indicatorBounds);
+                cutout, roundedCorners, indicatorBounds, displayShape);
         token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration);
     }
 
@@ -2075,13 +2101,13 @@
     }
 
     /**
-     * @see DisplayWindowPolicyController#canShowTasksInRecents()
+     * @see DisplayWindowPolicyController#canShowTasksInHostDeviceRecents()
      */
-    boolean canShowTasksInRecents() {
+    boolean canShowTasksInHostDeviceRecents() {
         if (mDwpcHelper == null) {
             return true;
         }
-        return mDwpcHelper.canShowTasksInRecents();
+        return mDwpcHelper.canShowTasksInHostDeviceRecents();
     }
 
     /**
@@ -2165,6 +2191,7 @@
         // Update application display metrics.
         final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
         final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
+        final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation);
 
         final Rect appFrame = mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh).mNonDecorFrame;
         mDisplayInfo.rotation = rotation;
@@ -2181,6 +2208,7 @@
         }
         mDisplayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
         mDisplayInfo.roundedCorners = roundedCorners;
+        mDisplayInfo.displayShape = displayShape;
         mDisplayInfo.getAppMetrics(mDisplayMetrics);
         if (mDisplayScalingDisabled) {
             mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED;
@@ -2267,6 +2295,23 @@
         return bounds.rotate(rotation);
     }
 
+    DisplayShape calculateDisplayShapeForRotation(int rotation) {
+        return mDisplayShapeCache.getOrCompute(mInitialDisplayShape, rotation);
+    }
+
+    private DisplayShape calculateDisplayShapeForRotationUncached(
+            DisplayShape displayShape, int rotation) {
+        if (displayShape == null) {
+            return DisplayShape.NONE;
+        }
+
+        if (rotation == ROTATION_0) {
+            return displayShape;
+        }
+
+        return displayShape.setRotation(rotation);
+    }
+
     /**
      * Compute display info and configuration according to the given rotation without changing
      * current display.
@@ -2667,7 +2712,7 @@
         mWmService.mWindowsChanged = true;
         // If the transition finished callback cannot match the token for some reason, make sure the
         // rotated state is cleared if it is already invisible.
-        if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.mVisibleRequested
+        if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.isVisibleRequested()
                 && !mFixedRotationLaunchingApp.isVisible()
                 && !mDisplayRotation.isRotatingSeamlessly()) {
             clearFixedRotationLaunchingApp();
@@ -2768,7 +2813,8 @@
         return displayFrames.update(rotation, w, h,
                 calculateDisplayCutoutForRotation(rotation),
                 calculateRoundedCornersForRotation(rotation),
-                calculatePrivacyIndicatorBoundsForRotation(rotation));
+                calculatePrivacyIndicatorBoundsForRotation(rotation),
+                calculateDisplayShapeForRotation(rotation));
     }
 
     @Override
@@ -2808,6 +2854,7 @@
         mInitialRoundedCorners = mDisplayInfo.roundedCorners;
         mCurrentPrivacyIndicatorBounds = new PrivacyIndicatorBounds(new Rect[4],
                 mDisplayInfo.rotation);
+        mInitialDisplayShape = mDisplayInfo.displayShape;
         final Display.Mode maxDisplayMode =
                 DisplayUtils.getMaximumResolutionDisplayMode(mDisplayInfo.supportedModes);
         mPhysicalDisplaySize = new Point(
@@ -2835,6 +2882,7 @@
                 ? DisplayCutout.NO_CUTOUT : mDisplayInfo.displayCutout;
         final String newUniqueId = mDisplayInfo.uniqueId;
         final RoundedCorners newRoundedCorners = mDisplayInfo.roundedCorners;
+        final DisplayShape newDisplayShape = mDisplayInfo.displayShape;
 
         final boolean displayMetricsChanged = mInitialDisplayWidth != newWidth
                 || mInitialDisplayHeight != newHeight
@@ -2842,7 +2890,8 @@
                 || mInitialPhysicalXDpi != newXDpi
                 || mInitialPhysicalYDpi != newYDpi
                 || !Objects.equals(mInitialDisplayCutout, newCutout)
-                || !Objects.equals(mInitialRoundedCorners, newRoundedCorners);
+                || !Objects.equals(mInitialRoundedCorners, newRoundedCorners)
+                || !Objects.equals(mInitialDisplayShape, newDisplayShape);
         final boolean physicalDisplayChanged = !newUniqueId.equals(mCurrentUniqueDisplayId);
 
         if (displayMetricsChanged || physicalDisplayChanged) {
@@ -2880,6 +2929,7 @@
             mInitialPhysicalYDpi = newYDpi;
             mInitialDisplayCutout = newCutout;
             mInitialRoundedCorners = newRoundedCorners;
+            mInitialDisplayShape = newDisplayShape;
             mCurrentUniqueDisplayId = newUniqueId;
             reconfigureDisplayLocked();
 
@@ -3249,6 +3299,7 @@
             setRemoteInsetsController(null);
             mWmService.mAnimator.removeDisplayLocked(mDisplayId);
             mOverlayLayer.release();
+            mA11yOverlayLayer.release();
             mWindowingLayer.release();
             mInputMonitor.onDisplayRemoved();
             mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this);
@@ -3366,7 +3417,7 @@
                     }
                 }
                 mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
-                controller.mTransitionMetricsReporter.associate(t,
+                controller.mTransitionMetricsReporter.associate(t.getToken(),
                         startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
                 startAsyncRotation(false /* shouldDebounce */);
             }
@@ -3471,9 +3522,8 @@
 
     @Override
     public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
-        super.dump(pw, prefix, dumpAll);
         pw.print(prefix);
-        pw.println("Display: mDisplayId=" + mDisplayId + " rootTasks=" + getRootTaskCount());
+        pw.println("Display: mDisplayId=" + mDisplayId + (isOrganized() ? " (organized)" : ""));
         final String subPrefix = "  " + prefix;
         pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x");
         pw.print(mInitialDisplayHeight); pw.print(" "); pw.print(mInitialDisplayDensity);
@@ -3504,6 +3554,7 @@
         pw.println(" mTouchExcludeRegion=" + mTouchExcludeRegion);
 
         pw.println();
+        super.dump(pw, prefix, dumpAll);
         pw.print(prefix); pw.print("mLayoutSeq="); pw.println(mLayoutSeq);
 
         pw.print("  mCurrentFocus="); pw.println(mCurrentFocus);
@@ -3595,6 +3646,7 @@
         pw.println();
         mInsetsStateController.dump(prefix, pw);
         mDwpcHelper.dump(prefix, pw);
+        pw.println();
     }
 
     @Override
@@ -3669,7 +3721,7 @@
      * @return The focused window or null if there isn't any or no need to seek.
      */
     WindowState findFocusedWindowIfNeeded(int topFocusedDisplayId) {
-        return (mWmService.mPerDisplayFocusEnabled || topFocusedDisplayId == INVALID_DISPLAY)
+        return (hasOwnFocus() || topFocusedDisplayId == INVALID_DISPLAY)
                     ? findFocusedWindow() : null;
     }
 
@@ -4941,9 +4993,8 @@
     @Override
     boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) {
         return okToDisplay(ignoreFrozen, ignoreScreenOn)
-                && (mDisplayId != DEFAULT_DISPLAY
-                || mWmService.mPolicy.okToAnimate(ignoreScreenOn))
-                && getDisplayPolicy().isScreenOnFully();
+                && (mDisplayId != DEFAULT_DISPLAY || mWmService.mPolicy.okToAnimate(ignoreScreenOn))
+                && (ignoreFrozen || mDisplayPolicy.isScreenOnFully());
     }
 
     static final class TaskForResizePointSearchResult implements Predicate<Task> {
@@ -5031,7 +5082,7 @@
      *   layer has been assigned since), to facilitate assigning the layer from the IME target, or
      *   fall back if there is no target.
      * - the container doesn't always participate in window traversal, according to
-     *   {@link #skipImeWindowsDuringTraversal()}
+     *   {@link #skipImeWindowsDuringTraversal(DisplayContent)}
      */
     private static class ImeContainer extends DisplayArea.Tokens {
         boolean mNeedsLayer = false;
@@ -5501,6 +5552,10 @@
         return mOverlayLayer;
     }
 
+    SurfaceControl getA11yOverlayLayer() {
+        return mA11yOverlayLayer;
+    }
+
     SurfaceControl[] findRoundedCornerOverlays() {
         List<SurfaceControl> roundedCornerOverlays = new ArrayList<>();
         for (WindowToken token : mTokenMap.values()) {
@@ -6298,6 +6353,14 @@
     }
 
     /**
+     * @return whether this display maintains its own focus and touch mode.
+     */
+    boolean hasOwnFocus() {
+        return mWmService.mPerDisplayFocusEnabled
+                || (mDisplayInfo.flags & Display.FLAG_OWN_FOCUS) != 0;
+    }
+
+    /**
      * @return whether the keyguard is occluded on this display
      */
     boolean isKeyguardOccluded() {
@@ -6712,25 +6775,35 @@
                 mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(),
                         stateController.getControlsForDispatch(this));
             } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to deliver inset state change", e);
+                Slog.w(TAG, "Failed to deliver inset control state change", e);
             }
         }
 
         @Override
-        public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+        public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             try {
-                mRemoteInsetsController.showInsets(types, fromIme);
+                ImeTracker.get().onProgress(statsToken,
+                        ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
+                mRemoteInsetsController.showInsets(types, fromIme, statsToken);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to deliver showInsets", e);
+                ImeTracker.get().onFailed(statsToken,
+                        ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
             }
         }
 
         @Override
-        public void hideInsets(@InsetsType int types, boolean fromIme) {
+        public void hideInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             try {
-                mRemoteInsetsController.hideInsets(types, fromIme);
+                ImeTracker.get().onProgress(statsToken,
+                        ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS);
+                mRemoteInsetsController.hideInsets(types, fromIme, statsToken);
             } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to deliver showInsets", e);
+                Slog.w(TAG, "Failed to deliver hideInsets", e);
+                ImeTracker.get().onFailed(statsToken,
+                        ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS);
             }
         }
 
@@ -6741,10 +6814,9 @@
 
         @Override
         public boolean isRequestedVisible(@InsetsType int types) {
-            if (types == ime()) {
-                return getInsetsStateController().getImeSourceProvider().isImeShowing();
-            }
-            return (mRequestedVisibleTypes & types) != 0;
+            return ((types & ime()) != 0
+                            && getInsetsStateController().getImeSourceProvider().isImeShowing())
+                    || (mRequestedVisibleTypes & types) != 0;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 33641f7..e984456 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -26,6 +26,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
+import android.view.DisplayShape;
 import android.view.InsetsState;
 import android.view.PrivacyIndicatorBounds;
 import android.view.RoundedCorners;
@@ -56,10 +57,11 @@
     public int mRotation;
 
     public DisplayFrames(InsetsState insetsState, DisplayInfo info, DisplayCutout cutout,
-            RoundedCorners roundedCorners, PrivacyIndicatorBounds indicatorBounds) {
+            RoundedCorners roundedCorners, PrivacyIndicatorBounds indicatorBounds,
+            DisplayShape displayShape) {
         mInsetsState = insetsState;
         update(info.rotation, info.logicalWidth, info.logicalHeight, cutout, roundedCorners,
-                indicatorBounds);
+                indicatorBounds, displayShape);
     }
 
     DisplayFrames() {
@@ -73,7 +75,8 @@
      */
     public boolean update(int rotation, int w, int h, @NonNull DisplayCutout displayCutout,
             @NonNull RoundedCorners roundedCorners,
-            @NonNull PrivacyIndicatorBounds indicatorBounds) {
+            @NonNull PrivacyIndicatorBounds indicatorBounds,
+            @NonNull DisplayShape displayShape) {
         final InsetsState state = mInsetsState;
         final Rect safe = mDisplayCutoutSafe;
         if (mRotation == rotation && mWidth == w && mHeight == h
@@ -91,6 +94,7 @@
         state.setDisplayCutout(displayCutout);
         state.setRoundedCorners(roundedCorners);
         state.setPrivacyIndicatorBounds(indicatorBounds);
+        state.setDisplayShape(displayShape);
         state.getDisplayCutoutSafe(safe);
         if (safe.left > unrestricted.left) {
             state.getSource(ITYPE_LEFT_DISPLAY_CUTOUT).setFrame(
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 8723994..300deca 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -57,7 +57,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_WAKE;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED;
 import static android.view.WindowManagerPolicyConstants.ALT_BAR_BOTTOM;
@@ -75,11 +74,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 +114,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;
@@ -276,6 +269,12 @@
 
     private final ArraySet<WindowState> mInsetsSourceWindowsExceptIme = new ArraySet<>();
 
+    /** Apps which are controlling the appearance of system bars */
+    private final ArraySet<ActivityRecord> mSystemBarColorApps = new ArraySet<>();
+
+    /** Apps which are relaunching and were controlling the appearance of system bars */
+    private final ArraySet<ActivityRecord> mRelaunchingSystemBarColorApps = new ArraySet<>();
+
     private boolean mIsFreeformWindowOverlappingWithNavBar;
 
     private boolean mLastImmersiveMode;
@@ -318,7 +317,6 @@
     private int mLastAppearance;
     private int mLastBehavior;
     private int mLastRequestedVisibleTypes = Type.defaultVisible();
-    private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
     private AppearanceRegion[] mLastStatusBarAppearanceRegions;
     private LetterboxDetails[] mLastLetterboxDetails;
 
@@ -790,13 +788,7 @@
             if (!mDisplayContent.isDefaultDisplay) {
                 return;
             }
-            if (mAwake && mDisplayContent.mTransitionController.isShellTransitionsEnabled()
-                    && !mDisplayContent.mTransitionController.isCollecting()) {
-                // Start a transition for waking. This is needed for showWhenLocked activities.
-                mDisplayContent.mTransitionController.requestTransitionIfNeeded(TRANSIT_WAKE,
-                        0 /* flags */, null /* trigger */, mDisplayContent);
-            }
-            mService.mAtmService.mKeyguardController.updateDeferWakeTransition(
+            mService.mAtmService.mKeyguardController.updateDeferTransitionForAod(
                     mAwake /* waiting */);
         }
     }
@@ -1203,7 +1195,6 @@
             return null;
         }
         return (displayFrames, windowContainer, inOutFrame) -> {
-            inOutFrame.inset(win.mGivenContentInsets);
             final LayoutParams lp = win.mAttrs.forRotation(displayFrames.mRotation);
             final InsetsFrameProvider ifp = lp.providedInsets[index];
             InsetsFrameProvider.calculateInsetsFrame(displayFrames.mUnrestricted,
@@ -1341,90 +1332,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()) {
@@ -1540,6 +1447,7 @@
         mStatusBarBackgroundWindows.clear();
         mStatusBarColorCheckedBounds.setEmpty();
         mStatusBarBackgroundCheckedBounds.setEmpty();
+        mSystemBarColorApps.clear();
 
         mAllowLockscreenWhenOn = false;
         mShowingDream = false;
@@ -1616,6 +1524,7 @@
                             win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
                             new Rect(win.getFrame())));
                     mStatusBarColorCheckedBounds.union(sTmpRect);
+                    addSystemBarColorApp(win);
                 }
             }
 
@@ -1628,6 +1537,7 @@
             if (isOverlappingWithNavBar) {
                 if (mNavBarColorWindowCandidate == null) {
                     mNavBarColorWindowCandidate = win;
+                    addSystemBarColorApp(win);
                 }
                 if (mNavBarBackgroundWindow == null) {
                     mNavBarBackgroundWindow = win;
@@ -1646,9 +1556,11 @@
             }
         } else if (win.isDimming()) {
             if (mStatusBar != null) {
-                addStatusBarAppearanceRegionsForDimmingWindow(
+                if (addStatusBarAppearanceRegionsForDimmingWindow(
                         win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
-                        mStatusBar.getFrame(), win.getBounds(), win.getFrame());
+                        mStatusBar.getFrame(), win.getBounds(), win.getFrame())) {
+                    addSystemBarColorApp(win);
+                }
             }
             if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) {
                 mNavBarColorWindowCandidate = win;
@@ -1656,18 +1568,21 @@
         }
     }
 
-    private void addStatusBarAppearanceRegionsForDimmingWindow(int appearance, Rect statusBarFrame,
-            Rect winBounds, Rect winFrame) {
+    /**
+     * Returns true if mStatusBarAppearanceRegionList is changed.
+     */
+    private boolean addStatusBarAppearanceRegionsForDimmingWindow(
+            int appearance, Rect statusBarFrame, Rect winBounds, Rect winFrame) {
         if (!sTmpRect.setIntersect(winBounds, statusBarFrame)) {
-            return;
+            return false;
         }
         if (mStatusBarColorCheckedBounds.contains(sTmpRect)) {
-            return;
+            return false;
         }
         if (appearance == 0 || !sTmpRect2.setIntersect(winFrame, statusBarFrame)) {
             mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(winBounds)));
             mStatusBarColorCheckedBounds.union(sTmpRect);
-            return;
+            return true;
         }
         // A dimming window can divide status bar into different appearance regions (up to 3).
         // +---------+-------------+---------+
@@ -1696,6 +1611,14 @@
             // We don't have vertical status bar yet, so we don't handle the other orientation.
         }
         mStatusBarColorCheckedBounds.union(sTmpRect);
+        return true;
+    }
+
+    private void addSystemBarColorApp(WindowState win) {
+        final ActivityRecord app = win.mActivityRecord;
+        if (app != null) {
+            mSystemBarColorApps.add(app);
+        }
     }
 
     /**
@@ -1729,7 +1652,16 @@
      */
     private void applyKeyguardPolicy(WindowState win, WindowState imeTarget) {
         if (win.canBeHiddenByKeyguard()) {
-            if (shouldBeHiddenByKeyguard(win, imeTarget)) {
+            final boolean shouldBeHiddenByKeyguard = shouldBeHiddenByKeyguard(win, imeTarget);
+            if (win.mIsImWindow) {
+                // Notify IME insets provider to freeze the IME insets. In case when turning off
+                // the screen, the IME insets source window will be hidden because of keyguard
+                // policy change and affects the system to freeze the last insets state. (And
+                // unfreeze when the IME is going to show)
+                mDisplayContent.getInsetsStateController().getImeSourceProvider().setFrozen(
+                        shouldBeHiddenByKeyguard);
+            }
+            if (shouldBeHiddenByKeyguard) {
                 win.hide(false /* doAnimation */, true /* requestAnim */);
             } else {
                 win.show(false /* doAnimation */, true /* requestAnim */);
@@ -2131,7 +2063,8 @@
             // Don't show status bar when swiping on already visible navigation bar.
             // But restore the position of navigation bar if it has been moved by the control
             // target.
-            controlTarget.showInsets(Type.navigationBars(), false);
+            controlTarget.showInsets(Type.navigationBars(), false /* fromIme */,
+                    null /* statsToken */);
             return;
         }
 
@@ -2139,10 +2072,12 @@
             // Show transient bars if they are hidden; restore position if they are visible.
             mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_SWIPE,
                     isGestureOnSystemBar);
-            controlTarget.showInsets(restorePositionTypes, false);
+            controlTarget.showInsets(restorePositionTypes, false /* fromIme */,
+                    null /* statsToken */);
         } else {
             // Restore visibilities and positions of system bars.
-            controlTarget.showInsets(Type.statusBars() | Type.navigationBars(), false);
+            controlTarget.showInsets(Type.statusBars() | Type.navigationBars(),
+                    false /* fromIme */, null /* statsToken */);
             // To further allow the pull-down-from-the-top gesture to pull down the notification
             // shade as a consistent motion, we reroute the touch events here from the currently
             // touched window to the status bar after making it visible.
@@ -2168,21 +2103,35 @@
         return mDisplayContent.getInsetsPolicy();
     }
 
+    /**
+     * Called when an app has started replacing its main window.
+     */
+    void addRelaunchingApp(ActivityRecord app) {
+        if (mSystemBarColorApps.contains(app)) {
+            mRelaunchingSystemBarColorApps.add(app);
+        }
+    }
+
+    /**
+     * Called when an app has finished replacing its main window or aborted.
+     */
+    void removeRelaunchingApp(ActivityRecord app) {
+        final boolean removed = mRelaunchingSystemBarColorApps.remove(app);
+        if (removed & mRelaunchingSystemBarColorApps.isEmpty()) {
+            updateSystemBarAttributes();
+        }
+    }
+
     void resetSystemBarAttributes() {
         mLastDisableFlags = 0;
         updateSystemBarAttributes();
     }
 
     void updateSystemBarAttributes() {
-        WindowState winCandidate = mFocusedWindow;
-        if (winCandidate == null && mTopFullscreenOpaqueWindowState != null
-                && (mTopFullscreenOpaqueWindowState.mAttrs.flags
-                & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0) {
-            // Only focusable window can take system bar control.
-            winCandidate = mTopFullscreenOpaqueWindowState;
-        }
         // If there is no window focused, there will be nobody to handle the events
         // anyway, so just hang on in whatever state we're in until things settle down.
+        WindowState winCandidate = mFocusedWindow != null ? mFocusedWindow
+                : mTopFullscreenOpaqueWindowState;
         if (winCandidate == null) {
             return;
         }
@@ -2210,6 +2159,11 @@
         final int displayId = getDisplayId();
         final int disableFlags = win.getDisableFlags();
         final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);
+        if (!mRelaunchingSystemBarColorApps.isEmpty()) {
+            // The appearance of system bars might change while relaunching apps. We don't report
+            // the intermediate state to system UI. Otherwise, it might trigger redundant effects.
+            return;
+        }
         final WindowState navColorWin = chooseNavigationColorWindowLw(mNavBarColorWindowCandidate,
                 mDisplayContent.mInputMethodWindow, mNavigationBarPosition);
         final boolean isNavbarColorManagedByIme =
@@ -2246,35 +2200,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) {
@@ -2691,6 +2626,14 @@
             pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState=");
             pw.println(mTopFullscreenOpaqueWindowState);
         }
+        if (!mSystemBarColorApps.isEmpty()) {
+            pw.print(prefix); pw.print("mSystemBarColorApps=");
+            pw.println(mSystemBarColorApps);
+        }
+        if (!mRelaunchingSystemBarColorApps.isEmpty()) {
+            pw.print(prefix); pw.print("mRelaunchingSystemBarColorApps=");
+            pw.println(mRelaunchingSystemBarColorApps);
+        }
         if (mNavBarColorWindowCandidate != null) {
             pw.print(prefix); pw.print("mNavBarColorWindowCandidate=");
             pw.println(mNavBarColorWindowCandidate);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index a8d13c5..185e06e 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1537,6 +1537,7 @@
         private int mHalfFoldSavedRotation = -1; // No saved rotation
         private DeviceStateController.FoldState mFoldState =
                 DeviceStateController.FoldState.UNKNOWN;
+        private boolean mInHalfFoldTransition = false;
 
         boolean overrideFrozenRotation() {
             return mFoldState == DeviceStateController.FoldState.HALF_FOLDED;
@@ -1544,6 +1545,7 @@
 
         boolean shouldRevertOverriddenRotation() {
             return mFoldState == DeviceStateController.FoldState.OPEN // When transitioning to open.
+                    && mInHalfFoldTransition
                     && mHalfFoldSavedRotation != -1 // Ignore if we've already reverted.
                     && mUserRotationMode
                     == WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked.
@@ -1552,6 +1554,7 @@
         int revertOverriddenRotation() {
             int savedRotation = mHalfFoldSavedRotation;
             mHalfFoldSavedRotation = -1;
+            mInHalfFoldTransition = false;
             return savedRotation;
         }
 
@@ -1577,14 +1580,11 @@
                 mService.updateRotation(false /* alwaysSendConfiguration */,
                         false /* forceRelayout */);
             } else {
-                // Revert the rotation to our saved value if we transition from HALF_FOLDED.
-                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).
+                mInHalfFoldTransition = true;
+                mFoldState = newState;
+                // Tell the device to update its orientation.
                 mService.updateRotation(false /* alwaysSendConfiguration */,
                         false /* forceRelayout */);
-                // Once we are rotated, set mFoldstate, effectively removing the lock override.
-                mFoldState = newState;
             }
         }
     }
@@ -1681,6 +1681,7 @@
 
     private static class RotationHistory {
         private static final int MAX_SIZE = 8;
+        private static final int NO_FOLD_CONTROLLER = -2;
         private static class Record {
             final @Surface.Rotation int mFromRotation;
             final @Surface.Rotation int mToRotation;
@@ -1692,6 +1693,9 @@
             final String mLastOrientationSource;
             final @ActivityInfo.ScreenOrientation int mSourceOrientation;
             final long mTimestamp = System.currentTimeMillis();
+            final int mHalfFoldSavedRotation;
+            final boolean mInHalfFoldTransition;
+            final DeviceStateController.FoldState mFoldState;
 
             Record(DisplayRotation dr, int fromRotation, int toRotation) {
                 mFromRotation = fromRotation;
@@ -1717,6 +1721,15 @@
                     mLastOrientationSource = null;
                     mSourceOrientation = SCREEN_ORIENTATION_UNSET;
                 }
+                if (dr.mFoldController != null) {
+                    mHalfFoldSavedRotation = dr.mFoldController.mHalfFoldSavedRotation;
+                    mInHalfFoldTransition = dr.mFoldController.mInHalfFoldTransition;
+                    mFoldState = dr.mFoldController.mFoldState;
+                } else {
+                    mHalfFoldSavedRotation = NO_FOLD_CONTROLLER;
+                    mInHalfFoldTransition = false;
+                    mFoldState = DeviceStateController.FoldState.UNKNOWN;
+                }
             }
 
             void dump(String prefix, PrintWriter pw) {
@@ -1733,6 +1746,12 @@
                 if (mNonDefaultRequestingTaskDisplayArea != null) {
                     pw.println(prefix + "  requestingTda=" + mNonDefaultRequestingTaskDisplayArea);
                 }
+                if (mHalfFoldSavedRotation != NO_FOLD_CONTROLLER) {
+                    pw.println(prefix + " halfFoldSavedRotation="
+                            + mHalfFoldSavedRotation
+                            + " mInHalfFoldTransition=" + mInHalfFoldTransition
+                            + " mFoldState=" + mFoldState);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index 5d49042..69fd00c 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -153,15 +153,26 @@
     }
 
     /**
-     * @see DisplayWindowPolicyController#canShowTasksInRecents()
+     * @see DisplayWindowPolicyController#canShowTasksInHostDeviceRecents()
      */
-    public final boolean canShowTasksInRecents() {
+    public final boolean canShowTasksInHostDeviceRecents() {
         if (mDisplayWindowPolicyController == null) {
             return true;
         }
-        return mDisplayWindowPolicyController.canShowTasksInRecents();
+        return mDisplayWindowPolicyController.canShowTasksInHostDeviceRecents();
     }
 
+    /**
+     * @see DisplayWindowPolicyController#isEnteringPipAllowed(int)
+     */
+    public final boolean isEnteringPipAllowed(int uid) {
+        if (mDisplayWindowPolicyController == null) {
+            return true;
+        }
+        return mDisplayWindowPolicyController.isEnteringPipAllowed(uid);
+    }
+
+
     void dump(String prefix, PrintWriter pw) {
         if (mDisplayWindowPolicyController != null) {
             pw.println();
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index e0644b6..b735b30 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -442,11 +442,12 @@
                     mRemoveContentMode = other.mRemoveContentMode;
                     changed = true;
                 }
-                if (other.mShouldShowWithInsecureKeyguard != mShouldShowWithInsecureKeyguard) {
+                if (!Objects.equals(
+                        other.mShouldShowWithInsecureKeyguard, mShouldShowWithInsecureKeyguard)) {
                     mShouldShowWithInsecureKeyguard = other.mShouldShowWithInsecureKeyguard;
                     changed = true;
                 }
-                if (other.mShouldShowSystemDecors != mShouldShowSystemDecors) {
+                if (!Objects.equals(other.mShouldShowSystemDecors, mShouldShowSystemDecors)) {
                     mShouldShowSystemDecors = other.mShouldShowSystemDecors;
                     changed = true;
                 }
@@ -458,15 +459,15 @@
                     mFixedToUserRotation = other.mFixedToUserRotation;
                     changed = true;
                 }
-                if (other.mIgnoreOrientationRequest != mIgnoreOrientationRequest) {
+                if (!Objects.equals(other.mIgnoreOrientationRequest, mIgnoreOrientationRequest)) {
                     mIgnoreOrientationRequest = other.mIgnoreOrientationRequest;
                     changed = true;
                 }
-                if (other.mIgnoreDisplayCutout != mIgnoreDisplayCutout) {
+                if (!Objects.equals(other.mIgnoreDisplayCutout, mIgnoreDisplayCutout)) {
                     mIgnoreDisplayCutout = other.mIgnoreDisplayCutout;
                     changed = true;
                 }
-                if (other.mDontMoveToTop != mDontMoveToTop) {
+                if (!Objects.equals(other.mDontMoveToTop, mDontMoveToTop)) {
                     mDontMoveToTop = other.mDontMoveToTop;
                     changed = true;
                 }
@@ -522,14 +523,13 @@
                     mRemoveContentMode = delta.mRemoveContentMode;
                     changed = true;
                 }
-                if (delta.mShouldShowWithInsecureKeyguard != null
-                        && delta.mShouldShowWithInsecureKeyguard
-                        != mShouldShowWithInsecureKeyguard) {
+                if (delta.mShouldShowWithInsecureKeyguard != null && !Objects.equals(
+                        delta.mShouldShowWithInsecureKeyguard, mShouldShowWithInsecureKeyguard)) {
                     mShouldShowWithInsecureKeyguard = delta.mShouldShowWithInsecureKeyguard;
                     changed = true;
                 }
-                if (delta.mShouldShowSystemDecors != null
-                        && delta.mShouldShowSystemDecors != mShouldShowSystemDecors) {
+                if (delta.mShouldShowSystemDecors != null && !Objects.equals(
+                        delta.mShouldShowSystemDecors, mShouldShowSystemDecors)) {
                     mShouldShowSystemDecors = delta.mShouldShowSystemDecors;
                     changed = true;
                 }
@@ -543,18 +543,18 @@
                     mFixedToUserRotation = delta.mFixedToUserRotation;
                     changed = true;
                 }
-                if (delta.mIgnoreOrientationRequest != null
-                        && delta.mIgnoreOrientationRequest != mIgnoreOrientationRequest) {
+                if (delta.mIgnoreOrientationRequest != null && !Objects.equals(
+                        delta.mIgnoreOrientationRequest, mIgnoreOrientationRequest)) {
                     mIgnoreOrientationRequest = delta.mIgnoreOrientationRequest;
                     changed = true;
                 }
-                if (delta.mIgnoreDisplayCutout != null
-                        && delta.mIgnoreDisplayCutout != mIgnoreDisplayCutout) {
+                if (delta.mIgnoreDisplayCutout != null && !Objects.equals(
+                        delta.mIgnoreDisplayCutout, mIgnoreDisplayCutout)) {
                     mIgnoreDisplayCutout = delta.mIgnoreDisplayCutout;
                     changed = true;
                 }
-                if (delta.mDontMoveToTop != null
-                        && delta.mDontMoveToTop != mDontMoveToTop) {
+                if (delta.mDontMoveToTop != null && !Objects.equals(
+                        delta.mDontMoveToTop, mDontMoveToTop)) {
                     mDontMoveToTop = delta.mDontMoveToTop;
                     changed = true;
                 }
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 7bb036d..bd83794 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -191,7 +191,7 @@
             if (!r.attachedToProcess()) {
                 makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges, isTop,
                         resumeTopActivity && isTop, r);
-            } else if (r.mVisibleRequested) {
+            } else if (r.isVisibleRequested()) {
                 // If this activity is already visible, then there is nothing to do here.
                 if (DEBUG_VISIBILITY) {
                     Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r);
@@ -244,7 +244,7 @@
         // invisible. If the app is already visible, it must have died while it was visible. In this
         // case, we'll show the dead window but will not restart the app. Otherwise we could end up
         // thrashing.
-        if (!isTop && r.mVisibleRequested && !r.isState(INITIALIZING)) {
+        if (!isTop && r.isVisibleRequested() && !r.isState(INITIALIZING)) {
             return;
         }
 
@@ -256,7 +256,7 @@
         if (r != starting) {
             r.startFreezingScreenLocked(configChanges);
         }
-        if (!r.mVisibleRequested || r.mLaunchTaskBehind) {
+        if (!r.isVisibleRequested() || r.mLaunchTaskBehind) {
             if (DEBUG_VISIBILITY) {
                 Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
             }
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index 1e5a219..d94bf4b 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -8,11 +8,11 @@
 # An activity is being finished:
 30001 wm_finish_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
 # A task is being brought to the front of the screen:
-30002 wm_task_to_front (User|1|5),(Task|1|5)
+30002 wm_task_to_front (User|1|5),(Task|1|5),(Display Id|1|5)
 # An existing activity is being given a new intent:
 30003 wm_new_intent (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
 # A new task is being created:
-30004 wm_create_task (User|1|5),(Task ID|1|5)
+30004 wm_create_task (User|1|5),(Task ID|1|5),(Root Task ID|1|5),(Display Id|1|5)
 # A new activity is being created in an existing task:
 30005 wm_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
 # An activity has been resumed into the foreground but was not already running:
@@ -32,9 +32,9 @@
 # An activity is being destroyed:
 30018 wm_destroy_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
 # An activity has been relaunched, resumed, and is now in the foreground:
-30019 wm_relaunch_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
+30019 wm_relaunch_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(config mask|3)
 # An activity has been relaunched:
-30020 wm_relaunch_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
+30020 wm_relaunch_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(config mask|3)
 
 # Activity set to resumed
 30043 wm_set_resumed_activity (User|1|5),(Component Name|3),(Reason|3)
@@ -45,9 +45,6 @@
 # Attempting to stop an activity
 30048 wm_stop_activity (User|1|5),(Token|1|5),(Component Name|3)
 
-# The task is being removed from its parent task
-30061 wm_remove_task (Task ID|1|5), (Root Task ID|1|5)
-
 # An activity been add into stopping list
 30066 wm_add_to_stopping (User|1|5),(Token|1|5),(Component Name|3),(Reason|3)
 
@@ -57,11 +54,11 @@
 # Out of memory for surfaces.
 31000 wm_no_surface_memory (Window|3),(PID|1|5),(Operation|3)
 # Task created.
-31001 wm_task_created (TaskId|1|5),(RootTaskId|1|5)
+31001 wm_task_created (TaskId|1|5)
 # Task moved to top (1) or bottom (0).
-31002 wm_task_moved (TaskId|1|5),(ToTop|1),(Index|1)
+31002 wm_task_moved (TaskId|1|5),(Root Task ID|1|5),(Display Id|1|5),(ToTop|1),(Index|1)
 # Task removed with source explanation.
-31003 wm_task_removed (TaskId|1|5),(Reason|3)
+31003 wm_task_removed (TaskId|1|5),(Root Task ID|1|5),(Display Id|1|5),(Reason|3)
 # bootanim finished:
 31007 wm_boot_animation_done (time|2|3)
 
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 38eca35..7fd093f 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -33,9 +33,11 @@
 import android.os.Trace;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsSource;
+import android.view.InsetsSourceConsumer;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.WindowInsets;
+import android.view.inputmethod.ImeTracker;
 import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -49,12 +51,21 @@
  */
 final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider {
 
+    /** The token tracking the current IME request or {@code null} otherwise. */
+    @Nullable
+    private ImeTracker.Token mImeRequesterStatsToken;
     private InsetsControlTarget mImeRequester;
     private Runnable mShowImeRunner;
     private boolean mIsImeLayoutDrawn;
     private boolean mImeShowing;
     private final InsetsSource mLastSource = new InsetsSource(ITYPE_IME);
 
+    /** @see #setFrozen(boolean) */
+    private boolean mFrozen;
+
+    /** @see #setServerVisible(boolean) */
+    private boolean mServerVisible;
+
     ImeInsetsSourceProvider(InsetsSource source,
             InsetsStateController stateController, DisplayContent displayContent) {
         super(source, stateController, displayContent);
@@ -81,6 +92,32 @@
     }
 
     @Override
+    void setServerVisible(boolean serverVisible) {
+        mServerVisible = serverVisible;
+        if (!mFrozen) {
+            super.setServerVisible(serverVisible);
+        }
+    }
+
+    /**
+     * Freeze IME insets source state when required.
+     *
+     * When setting {@param frozen} as {@code true}, the IME insets provider will freeze the
+     * current IME insets state and pending the IME insets state update until setting
+     * {@param frozen} as {@code false}.
+     */
+    void setFrozen(boolean frozen) {
+        if (mFrozen == frozen) {
+            return;
+        }
+        mFrozen = frozen;
+        if (!frozen) {
+            // Unfreeze and process the pending IME insets states.
+            super.setServerVisible(mServerVisible);
+        }
+    }
+
+    @Override
     void updateSourceFrame(Rect frame) {
         super.updateSourceFrame(frame);
         onSourceChanged();
@@ -130,14 +167,20 @@
     }
 
     /**
-     * Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService}
-     * requests to show IME on {@param imeTarget}.
+     * Called from {@link WindowManagerInternal#showImePostLayout}
+     * when {@link android.inputmethodservice.InputMethodService} requests to show IME
+     * on {@param imeTarget}.
      *
-     * @param imeTarget imeTarget on which IME request is coming from.
+     * @param imeTarget imeTarget on which IME show request is coming from.
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      */
-    void scheduleShowImePostLayout(InsetsControlTarget imeTarget) {
+    void scheduleShowImePostLayout(InsetsControlTarget imeTarget,
+            @Nullable ImeTracker.Token statsToken) {
         boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
         mImeRequester = imeTarget;
+        // There was still a stats token, so that request presumably failed.
+        ImeTracker.get().onFailed(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+        mImeRequesterStatsToken = statsToken;
         if (targetChanged) {
             // target changed, check if new target can show IME.
             ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord");
@@ -151,15 +194,20 @@
         ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null
                 ? mImeRequester : mImeRequester.getWindow().getName());
         mShowImeRunner = () -> {
+            ImeTracker.get().onProgress(mImeRequesterStatsToken,
+                    ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
             ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
             // Target should still be the same.
             if (isReadyToShowIme()) {
+                ImeTracker.get().onProgress(mImeRequesterStatsToken,
+                        ImeTracker.PHASE_WM_SHOW_IME_READY);
                 final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
 
                 ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s",
                         target.getWindow() != null ? target.getWindow().getName() : "");
                 setImeShowing(true);
-                target.showInsets(WindowInsets.Type.ime(), true /* fromIme */);
+                target.showInsets(WindowInsets.Type.ime(), true /* fromIme */,
+                        mImeRequesterStatsToken);
                 Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
                 if (target != mImeRequester && mImeRequester != null) {
                     ProtoLog.w(WM_DEBUG_IME,
@@ -167,7 +215,12 @@
                             (mImeRequester.getWindow() != null
                                     ? mImeRequester.getWindow().getName() : ""));
                 }
+            } else {
+                ImeTracker.get().onFailed(mImeRequesterStatsToken,
+                        ImeTracker.PHASE_WM_SHOW_IME_READY);
             }
+            // Clear token here so we don't report an error in abortShowImePostLayout().
+            mImeRequesterStatsToken = null;
             abortShowImePostLayout();
         };
         mDisplayContent.mWmService.requestTraversal();
@@ -202,6 +255,8 @@
         mImeRequester = null;
         mIsImeLayoutDrawn = false;
         mShowImeRunner = null;
+        ImeTracker.get().onCancelled(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+        mImeRequesterStatsToken = null;
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index 4c18d0b..56edde0 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -57,7 +57,6 @@
 import android.view.WindowInsets.Type;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
-import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.Button;
@@ -109,18 +108,13 @@
         mContext = display.getDisplayId() == DEFAULT_DISPLAY
                 ? uiContext : uiContext.createDisplayContext(display);
         mHandler = new H(looper);
-        mShowDelayMs = getNavBarExitDuration() * 3;
+        mShowDelayMs = context.getResources().getInteger(R.integer.dock_enter_exit_duration) * 3L;
         mPanicThresholdMs = context.getResources()
                 .getInteger(R.integer.config_immersive_mode_confirmation_panic);
         mVrModeEnabled = vrModeEnabled;
         mCanSystemBarsBeShownByUser = canSystemBarsBeShownByUser;
     }
 
-    private long getNavBarExitDuration() {
-        Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit);
-        return exit != null ? exit.getDuration() : 0;
-    }
-
     static boolean loadSetting(int currentUserId, Context context) {
         final boolean wasConfirmed = sConfirmed;
         sConfirmed = false;
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 7860b15..3e1105b 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -270,7 +270,7 @@
                 InputConfigAdapter.getMask());
 
         final boolean focusable = w.canReceiveKeys()
-                && (mService.mPerDisplayFocusEnabled || mDisplayContent.isOnTop());
+                && (mDisplayContent.hasOwnFocus() || mDisplayContent.isOnTop());
         inputWindowHandle.setFocusable(focusable);
 
         final boolean hasWallpaper = mDisplayContent.mWallpaperController.isWallpaperTarget(w)
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index d35b7c3..8ecbc17 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -16,9 +16,11 @@
 
 package com.android.server.wm;
 
+import android.annotation.Nullable;
 import android.inputmethodservice.InputMethodService;
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
 
 /**
  * Generalization of an object that can control insets state.
@@ -57,8 +59,10 @@
      *
      * @param types to specify which types of insets source window should be shown.
      * @param fromIme {@code true} if IME show request originated from {@link InputMethodService}.
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      */
-    default void showInsets(@InsetsType int types, boolean fromIme) {
+    default void showInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
     }
 
     /**
@@ -66,8 +70,10 @@
      *
      * @param types to specify which types of insets source window should be hidden.
      * @param fromIme {@code true} if IME hide request originated from {@link InputMethodService}.
+     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
      */
-    default void hideInsets(@InsetsType int types, boolean fromIme) {
+    default void hideInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index b9fa80c..67cab10 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -454,8 +454,7 @@
             final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME);
 
             if (originalImeSource != null) {
-                final boolean imeVisibility =
-                        w.mActivityRecord.mLastImeShown || w.isRequestedVisible(Type.ime());
+                final boolean imeVisibility = w.isRequestedVisible(Type.ime());
                 final InsetsState state = copyState ? new InsetsState(originalState)
                         : originalState;
                 final InsetsSource imeSource = new InsetsSource(originalImeSource);
@@ -799,7 +798,7 @@
                         show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show
                                 ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
                                 : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
-                        null /* translator */);
+                        null /* translator */, null /* statsToken */);
                 SurfaceAnimationThread.getHandler().post(
                         () -> mListener.onReady(mAnimationControl, typesReady));
             }
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 1567fa7..e9badef 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -37,6 +37,7 @@
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
 
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
@@ -176,7 +177,7 @@
         final boolean keyguardChanged = (keyguardShowing != state.mKeyguardShowing)
                 || (state.mKeyguardGoingAway && keyguardShowing && !aodRemoved);
         if (aodRemoved) {
-            updateDeferWakeTransition(false /* waiting */);
+            updateDeferTransitionForAod(false /* waiting */);
         }
         if (!keyguardChanged && !aodChanged) {
             setWakeTransitionReady();
@@ -533,24 +534,25 @@
 
     private final Runnable mResetWaitTransition = () -> {
         synchronized (mWindowManager.mGlobalLock) {
-            updateDeferWakeTransition(false /* waiting */);
+            updateDeferTransitionForAod(false /* waiting */);
         }
     };
 
-    void updateDeferWakeTransition(boolean waiting) {
+    // Defer transition until AOD dismissed.
+    void updateDeferTransitionForAod(boolean waiting) {
         if (waiting == mWaitingForWakeTransition) {
             return;
         }
-        if (!mWindowManager.mAtmService.getTransitionController().isShellTransitionsEnabled()) {
+        if (!mService.getTransitionController().isCollecting()) {
             return;
         }
-        // if aod is showing, defer the wake transition until aod state changed.
+        // if AOD is showing, defer the wake transition until AOD state changed.
         if (waiting && isAodShowing(DEFAULT_DISPLAY)) {
             mWaitingForWakeTransition = true;
             mWindowManager.mAtmService.getTransitionController().deferTransitionReady();
             mWindowManager.mH.postDelayed(mResetWaitTransition, DEFER_WAKE_TRANSITION_TIMEOUT_MS);
         } else if (!waiting) {
-            // dismiss the deferring if the aod state change or cancel awake.
+            // dismiss the deferring if the AOD state change or cancel awake.
             mWaitingForWakeTransition = false;
             mWindowManager.mAtmService.getTransitionController().continueTransitionReady();
             mWindowManager.mH.removeCallbacks(mResetWaitTransition);
@@ -648,6 +650,12 @@
             mRequestDismissKeyguard = lastDismissKeyguardActivity != mDismissingKeyguardActivity
                     && !mOccluded && !mKeyguardGoingAway
                     && mDismissingKeyguardActivity != null;
+            if (mOccluded && mKeyguardShowing && !display.isSleeping() && !top.fillsParent()
+                    && display.mWallpaperController.getWallpaperTarget() == null) {
+                // The occluding activity may be translucent or not fill screen. Then let wallpaper
+                // to check whether it should set itself as target to avoid blank background.
+                display.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+            }
 
             if (mTopTurnScreenOnActivity != lastTurnScreenOnActivity
                     && mTopTurnScreenOnActivity != null
@@ -657,10 +665,18 @@
                 mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
             }
 
+            boolean hasChange = false;
             if (lastOccluded != mOccluded) {
                 controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
+                hasChange = true;
             } else if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
                 controller.handleKeyguardGoingAwayChanged(display);
+                hasChange = true;
+            }
+            // Collect the participates for shell transition, so that transition won't happen too
+            // early since the transition was set ready.
+            if (hasChange && top != null && (mOccluded || mKeyguardGoingAway)) {
+                display.mTransitionController.collect(top);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/LaunchParamsUtil.java b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
index a0e22e7..cd071af 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsUtil.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
@@ -26,6 +26,7 @@
 import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
 import android.util.Size;
+import android.view.View;
 
 /**
  * The static class that defines some utility constants and functions that are shared among launch
@@ -43,6 +44,10 @@
     private static final int DEFAULT_LANDSCAPE_FREEFORM_WIDTH_DP = 1064;
     private static final int DEFAULT_LANDSCAPE_FREEFORM_HEIGHT_DP = 600;
 
+    private static final int DISPLAY_EDGE_OFFSET_DP = 27;
+
+    private static final Rect TMP_STABLE_BOUNDS = new Rect();
+
     private LaunchParamsUtil() {}
 
     /**
@@ -126,4 +131,68 @@
 
         return new Size(adjWidth, adjHeight);
     }
+
+    static void adjustBoundsToFitInDisplayArea(@NonNull TaskDisplayArea displayArea,
+                                               int layoutDirection,
+                                               @NonNull ActivityInfo.WindowLayout layout,
+                                               @NonNull Rect inOutBounds) {
+        // Give a small margin between the window bounds and the display bounds.
+        final Rect stableBounds = TMP_STABLE_BOUNDS;
+        displayArea.getStableRect(stableBounds);
+        final float density = (float) displayArea.getConfiguration().densityDpi / DENSITY_DEFAULT;
+        final int displayEdgeOffset = (int) (DISPLAY_EDGE_OFFSET_DP * density + 0.5f);
+        stableBounds.inset(displayEdgeOffset, displayEdgeOffset);
+
+        if (stableBounds.width() < inOutBounds.width()
+                || stableBounds.height() < inOutBounds.height()) {
+            final float heightShrinkRatio = stableBounds.width() / (float) inOutBounds.width();
+            final float widthShrinkRatio =
+                    stableBounds.height() / (float) inOutBounds.height();
+            final float shrinkRatio = Math.min(heightShrinkRatio, widthShrinkRatio);
+            // Minimum layout requirements.
+            final int layoutMinWidth = (layout == null) ? -1 : layout.minWidth;
+            final int layoutMinHeight = (layout == null) ? -1 : layout.minHeight;
+            int adjustedWidth = Math.max(layoutMinWidth, (int) (inOutBounds.width() * shrinkRatio));
+            int adjustedHeight = Math.max(layoutMinHeight,
+                    (int) (inOutBounds.height() * shrinkRatio));
+            if (stableBounds.width() < adjustedWidth
+                    || stableBounds.height() < adjustedHeight) {
+                // There is no way for us to fit the bounds in the displayArea without breaking min
+                // size constraints. Set the min size to make visible as much content as possible.
+                final int left = layoutDirection == View.LAYOUT_DIRECTION_RTL
+                        ? stableBounds.right - adjustedWidth
+                        : stableBounds.left;
+                inOutBounds.set(left, stableBounds.top, left + adjustedWidth,
+                        stableBounds.top + adjustedHeight);
+                return;
+            }
+            inOutBounds.set(inOutBounds.left, inOutBounds.top,
+                    inOutBounds.left + adjustedWidth, inOutBounds.top + adjustedHeight);
+        }
+
+        final int dx;
+        if (inOutBounds.right > stableBounds.right) {
+            // Right edge is out of displayArea.
+            dx = stableBounds.right - inOutBounds.right;
+        } else if (inOutBounds.left < stableBounds.left) {
+            // Left edge is out of displayArea.
+            dx = stableBounds.left - inOutBounds.left;
+        } else {
+            // Vertical edges are all in displayArea.
+            dx = 0;
+        }
+
+        final int dy;
+        if (inOutBounds.top < stableBounds.top) {
+            // Top edge is out of displayArea.
+            dy = stableBounds.top - inOutBounds.top;
+        } else if (inOutBounds.bottom > stableBounds.bottom) {
+            // Bottom edge is out of displayArea.
+            dy = stableBounds.bottom - inOutBounds.bottom;
+        } else {
+            // Horizontal edges are all in displayArea.
+            dy = 0;
+        }
+        inOutBounds.offset(dx, dy);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index ea82417..bcea6f4 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -87,10 +87,6 @@
     private final LetterboxConfiguration mLetterboxConfiguration;
     private final ActivityRecord mActivityRecord;
 
-    // Taskbar expanded height. Used to determine whether to crop an app window to display rounded
-    // corners above the taskbar.
-    private final float mExpandedTaskBarHeight;
-
     private boolean mShowWallpaperForLetterboxBackground;
 
     @Nullable
@@ -102,8 +98,6 @@
         // is created in its constructor. It shouldn't be used in this constructor but it's safe
         // to use it after since controller is only used in ActivityRecord.
         mActivityRecord = activityRecord;
-        mExpandedTaskBarHeight =
-                getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
     }
 
     /** Cleans up {@link Letterbox} if it exists.*/
@@ -285,14 +279,17 @@
     }
 
     float getSplitScreenAspectRatio() {
+        // Getting the same aspect ratio that apps get in split screen.
+        final DisplayContent displayContent = mActivityRecord.getDisplayContent();
+        if (displayContent == null) {
+            return getDefaultMinAspectRatioForUnresizableApps();
+        }
         int dividerWindowWidth =
                 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
         int dividerInsets =
                 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
         int dividerSize = dividerWindowWidth - dividerInsets * 2;
-
-        // Getting the same aspect ratio that apps get in split screen.
-        Rect bounds = new Rect(mActivityRecord.getDisplayContent().getBounds());
+        final Rect bounds = new Rect(displayContent.getBounds());
         if (bounds.width() >= bounds.height()) {
             bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
             bounds.right = bounds.centerX();
@@ -501,12 +498,16 @@
 
             if (hasVisibleTaskbar(mainWindow)) {
                 cropBounds = new Rect(mActivityRecord.getBounds());
+
+                // Rounded corners should be displayed above the taskbar.
+                // It is important to call adjustBoundsForTaskbarUnchecked before offsetTo
+                // because taskbar bounds are in screen coordinates
+                adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds);
+
                 // Activity bounds are in screen coordinates while (0,0) for activity's surface
                 // control is at the top left corner of an app window so offsetting bounds
                 // accordingly.
                 cropBounds.offsetTo(0, 0);
-                // Rounded corners should be displayed above the taskbar.
-                adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds);
             }
 
             transaction
@@ -551,7 +552,6 @@
         final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
 
         return taskbarInsetsSource != null
-                && taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight
                 && taskbarInsetsSource.isVisible();
     }
 
@@ -576,9 +576,8 @@
         // Rounded corners should be displayed above the taskbar.
         bounds.bottom =
                 Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top);
-        if (mActivityRecord.inSizeCompatMode()
-                && mActivityRecord.getSizeCompatScale() < 1.0f) {
-            bounds.scale(1.0f / mActivityRecord.getSizeCompatScale());
+        if (mActivityRecord.inSizeCompatMode() && mActivityRecord.getCompatScale() < 1.0f) {
+            bounds.scale(1.0f / mActivityRecord.getCompatScale());
         }
     }
 
@@ -674,6 +673,12 @@
                 + getHorizontalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
         pw.println(prefix + "  letterboxVerticalPositionMultiplier="
                 + getVerticalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
+        pw.println(prefix + "  letterboxPositionForHorizontalReachability="
+                + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
+                    mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+        pw.println(prefix + "  letterboxPositionForVerticalReachability="
+                + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
+                    mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
         pw.println(prefix + "  fixedOrientationLetterboxAspectRatio="
                 + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
         pw.println(prefix + "  defaultMinAspectRatioForUnresizableApps="
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/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 1fc061b..c827062 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1393,7 +1393,7 @@
         // Ignore the task if it is started on a display which is not allow to show its tasks on
         // Recents.
         if (task.getDisplayContent() != null
-                && !task.getDisplayContent().canShowTasksInRecents()) {
+                && !task.getDisplayContent().canShowTasksInHostDeviceRecents()) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index ffe3374..be90588 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -112,7 +112,7 @@
                 mTargetActivityType);
         ActivityRecord targetActivity = getTargetActivity(targetRootTask);
         if (targetActivity != null) {
-            if (targetActivity.mVisibleRequested || targetActivity.isTopRunningActivity()) {
+            if (targetActivity.isVisibleRequested() || targetActivity.isTopRunningActivity()) {
                 // The activity is ready.
                 return;
             }
@@ -195,7 +195,7 @@
 
         // Send launch hint if we are actually launching the target. If it's already visible
         // (shouldn't happen in general) we don't need to send it.
-        if (targetActivity == null || !targetActivity.mVisibleRequested) {
+        if (targetActivity == null || !targetActivity.isVisibleRequested()) {
             mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(
                     true /* forceSend */, targetActivity);
         }
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index ccc71bb..de42c55 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -16,15 +16,21 @@
 
 package com.android.server.wm;
 
+import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
+import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
+
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 
+import android.hardware.display.DisplayManager;
 import android.view.Display;
 import android.view.Display.Mode;
 import android.view.DisplayInfo;
+import android.view.Surface;
 import android.view.SurfaceControl.RefreshRateRange;
 
 import java.util.HashMap;
+import java.util.Objects;
 
 /**
  * Policy to select a lower refresh rate for the display if applicable.
@@ -154,39 +160,109 @@
         return LAYER_PRIORITY_UNSET;
     }
 
-    float getPreferredRefreshRate(WindowState w) {
+    public static class FrameRateVote {
+        float mRefreshRate;
+        @Surface.FrameRateCompatibility int mCompatibility;
+
+        FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
+            update(refreshRate, compatibility);
+        }
+
+        FrameRateVote() {
+            reset();
+        }
+
+        boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
+            if (!refreshRateEquals(refreshRate) || mCompatibility != compatibility) {
+                mRefreshRate = refreshRate;
+                mCompatibility = compatibility;
+                return true;
+            }
+            return false;
+        }
+
+        boolean reset() {
+            return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof FrameRateVote)) {
+                return false;
+            }
+
+            FrameRateVote other = (FrameRateVote) o;
+            return refreshRateEquals(other.mRefreshRate)
+                    && mCompatibility == other.mCompatibility;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mRefreshRate, mCompatibility);
+        }
+
+        @Override
+        public String toString() {
+            return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility;
+        }
+
+        private boolean refreshRateEquals(float refreshRate) {
+            return mRefreshRate <= refreshRate + RefreshRateRange.FLOAT_TOLERANCE
+                    && mRefreshRate >= refreshRate - RefreshRateRange.FLOAT_TOLERANCE;
+        }
+    }
+
+    boolean updateFrameRateVote(WindowState w) {
+        @DisplayManager.SwitchingType int refreshRateSwitchingType =
+                mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType();
+
+        // If refresh rate switching is disabled there is no point to set the frame rate on the
+        // surface as the refresh rate will be limited by display manager to a single value
+        // and SurfaceFlinger wouldn't be able to change it anyways.
+        if (refreshRateSwitchingType == SWITCHING_TYPE_NONE) {
+            return w.mFrameRateVote.reset();
+        }
+
         // If app is animating, it's not able to control refresh rate because we want the animation
         // to run in default refresh rate.
         if (w.isAnimating(TRANSITION | PARENTS)) {
-            return 0;
+            return w.mFrameRateVote.reset();
         }
 
         // If the app set a preferredDisplayModeId, the preferred refresh rate is the refresh rate
         // of that mode id.
-        final int preferredModeId = w.mAttrs.preferredDisplayModeId;
-        if (preferredModeId > 0) {
-            DisplayInfo info = w.getDisplayInfo();
-            if (info != null) {
-                for (Display.Mode mode : info.supportedModes) {
-                    if (preferredModeId == mode.getModeId()) {
-                        return mode.getRefreshRate();
+        if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
+            final int preferredModeId = w.mAttrs.preferredDisplayModeId;
+            if (preferredModeId > 0) {
+                DisplayInfo info = w.getDisplayInfo();
+                if (info != null) {
+                    for (Display.Mode mode : info.supportedModes) {
+                        if (preferredModeId == mode.getModeId()) {
+                            return w.mFrameRateVote.update(mode.getRefreshRate(),
+                                    Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+
+                        }
                     }
                 }
             }
         }
 
         if (w.mAttrs.preferredRefreshRate > 0) {
-            return w.mAttrs.preferredRefreshRate;
+            return w.mFrameRateVote.update(w.mAttrs.preferredRefreshRate,
+                    Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
         }
 
         // If the app didn't set a preferred mode id or refresh rate, but it is part of the deny
         // list, we return the low refresh rate as the preferred one.
-        final String packageName = w.getOwningPackage();
-        if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
-            return mLowRefreshRateMode.getRefreshRate();
+        if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
+            final String packageName = w.getOwningPackage();
+            if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
+                return w.mFrameRateVote.update(mLowRefreshRateMode.getRefreshRate(),
+                        Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+            }
         }
 
-        return 0;
+        return w.mFrameRateVote.reset();
     }
 
     float getPreferredMinRefreshRate(WindowState w) {
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index d34e610..3635ebb 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -104,10 +104,29 @@
     RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,
             Point position, Rect localBounds, Rect endBounds, Rect startBounds,
             boolean showBackdrop) {
+        return createRemoteAnimationRecord(windowContainer, position, localBounds, endBounds,
+                startBounds, showBackdrop, startBounds != null /* shouldCreateSnapshot */);
+    }
+
+    /**
+     * Creates an animation record for each individual {@link WindowContainer}.
+     *
+     * @param windowContainer The windows to animate.
+     * @param position        The position app bounds relative to its parent.
+     * @param localBounds     The bounds of the app relative to its parent.
+     * @param endBounds       The end bounds after the transition, in screen coordinates.
+     * @param startBounds     The start bounds before the transition, in screen coordinates.
+     * @param showBackdrop    To show background behind a window during animation.
+     * @param shouldCreateSnapshot   Whether this target should create a snapshot animation.
+     * @return The record representing animation(s) to run on the app.
+     */
+    RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,
+            Point position, Rect localBounds, Rect endBounds, Rect startBounds,
+            boolean showBackdrop, boolean shouldCreateSnapshot) {
         ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s",
                 windowContainer);
         final RemoteAnimationRecord adapters = new RemoteAnimationRecord(windowContainer, position,
-                localBounds, endBounds, startBounds, showBackdrop);
+                localBounds, endBounds, startBounds, showBackdrop, shouldCreateSnapshot);
         mPendingAnimations.add(adapters);
         return adapters;
     }
@@ -441,14 +460,15 @@
         private @RemoteAnimationTarget.Mode int mMode = RemoteAnimationTarget.MODE_CHANGING;
 
         RemoteAnimationRecord(WindowContainer windowContainer, Point endPos, Rect localBounds,
-                Rect endBounds, Rect startBounds, boolean showBackdrop) {
+                Rect endBounds, @Nullable Rect startBounds, boolean showBackdrop,
+                boolean shouldCreateSnapshot) {
             mWindowContainer = windowContainer;
             mShowBackdrop = showBackdrop;
             if (startBounds != null) {
                 mStartBounds = new Rect(startBounds);
                 mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds,
                         mStartBounds, mShowBackdrop);
-                if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) {
+                if (shouldCreateSnapshot && mRemoteAnimationAdapter.getChangeNeedsSnapshot()) {
                     final Rect thumbnailLocalBounds = new Rect(startBounds);
                     thumbnailLocalBounds.offsetTo(0, 0);
                     // Snapshot is located at (0,0) of the animation leash. It doesn't have size
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 0ed4835..1ee4d6b 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -149,6 +149,7 @@
 import com.android.server.am.UserState;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.utils.Slogf;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -1503,7 +1504,8 @@
         }
 
         if (aInfo == null) {
-            Slog.wtf(TAG, "No home screen found for " + homeIntent, new Throwable());
+            Slogf.wtf(TAG, new Exception(), "No home screen found for %s and user %d", homeIntent,
+                    userId);
             return null;
         }
 
@@ -2617,7 +2619,7 @@
         final ArrayList<Task> addedTasks = new ArrayList<>();
         forAllActivities((r) -> {
             final Task task = r.getTask();
-            if (r.mVisibleRequested && r.mStartingData == null && !addedTasks.contains(task)) {
+            if (r.isVisibleRequested() && r.mStartingData == null && !addedTasks.contains(task)) {
                 r.showStartingWindow(true /*taskSwitch*/);
                 addedTasks.add(task);
             }
@@ -2642,7 +2644,7 @@
         forAllLeafTasks(task -> {
             final int oldRank = task.mLayerRank;
             final ActivityRecord r = task.topRunningActivityLocked();
-            if (r != null && r.mVisibleRequested) {
+            if (r != null && r.isVisibleRequested()) {
                 task.mLayerRank = ++mTmpTaskLayerRank;
             } else {
                 task.mLayerRank = Task.LAYER_RANK_INVISIBLE;
@@ -3405,7 +3407,6 @@
             final DisplayContent display = getChildAt(i);
             display.dump(pw, prefix, dumpAll);
         }
-        pw.println();
     }
 
     /**
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/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 94d4dde..2e5ab1a 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -441,6 +441,7 @@
                         .setPixelFormat(PixelFormat.RGBA_8888)
                         .setChildrenOnly(true)
                         .setAllowProtected(true)
+                        .setCaptureSecureLayers(true)
                         .build();
         final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
                 ScreenCapture.captureLayers(captureArgs);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 435ab97..6d4a526 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -21,9 +21,6 @@
 import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED;
 import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-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.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -67,7 +64,6 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
@@ -358,6 +354,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. */
@@ -495,6 +498,12 @@
      */
     boolean mInRemoveTask;
 
+    /**
+     * When set, disassociate the leaf task if relaunched and reparented it to TDA as root task if
+     * possible.
+     */
+    boolean mReparentLeafTaskIfRelaunch;
+
     private final AnimatingActivityRegistry mAnimatingActivityRegistry =
             new AnimatingActivityRegistry();
 
@@ -600,12 +609,6 @@
 
     boolean mLastSurfaceShowing = true;
 
-    /**
-     * Tracks if a back gesture is in progress.
-     * Skips any system transition animations if this is set to {@code true}.
-     */
-    boolean mBackGestureStarted = false;
-
     private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
             Intent _affinityIntent, String _affinity, String _rootAffinity,
             ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
@@ -673,7 +676,7 @@
         mLaunchCookie = _launchCookie;
         mDeferTaskAppear = _deferTaskAppear;
         mRemoveWithTaskOrganizer = _removeWithTaskOrganizer;
-        EventLogTags.writeWmTaskCreated(mTaskId, isRootTask() ? INVALID_TASK_ID : getRootTaskId());
+        EventLogTags.writeWmTaskCreated(mTaskId);
     }
 
     static Task fromWindowContainerToken(WindowContainerToken token) {
@@ -1290,7 +1293,8 @@
     }
 
     void updateTaskMovement(boolean toTop, int position) {
-        EventLogTags.writeWmTaskMoved(mTaskId, toTop ? 1 : 0, position);
+        EventLogTags.writeWmTaskMoved(mTaskId, getRootTaskId(), getDisplayId(), toTop ? 1 : 0,
+                position);
         final TaskDisplayArea taskDisplayArea = getDisplayArea();
         if (taskDisplayArea != null && isLeafTask()) {
             taskDisplayArea.onLeafTaskMoved(this, toTop);
@@ -1400,13 +1404,26 @@
      * Reorder the history task so that the passed activity is brought to the front.
      * @return whether it was actually moved (vs already being top).
      */
-    final boolean moveActivityToFrontLocked(ActivityRecord newTop) {
+    final boolean moveActivityToFront(ActivityRecord newTop) {
         ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing and adding activity %s to root task at top "
                 + "callers=%s", newTop, Debug.getCallers(4));
-        int origDist = getDistanceFromTop(newTop);
-        positionChildAtTop(newTop);
+        final TaskFragment taskFragment = newTop.getTaskFragment();
+        boolean moved;
+        if (taskFragment != this) {
+            if (taskFragment.isEmbedded() && taskFragment.getNonFinishingActivityCount() == 1) {
+                taskFragment.mClearedForReorderActivityToFront = true;
+            }
+            newTop.reparent(this, POSITION_TOP);
+            moved = true;
+            if (taskFragment.isEmbedded()) {
+                mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
+                        .onActivityReparentedToTask(newTop);
+            }
+        } else {
+            moved = moveChildToFront(newTop);
+        }
         updateEffectiveIntent();
-        return getDistanceFromTop(newTop) != origDist;
+        return moved;
     }
 
     @Override
@@ -1574,10 +1591,22 @@
                 removeChild(r, reason);
             });
         } else {
-            forAllActivities((r) -> {
+            final ArrayList<ActivityRecord> finishingActivities = new ArrayList<>();
+            forAllActivities(r -> {
                 if (r.finishing || (excludingTaskOverlay && r.isTaskOverlay())) {
                     return;
                 }
+                finishingActivities.add(r);
+            });
+
+            // Finish or destroy apps from the bottom to ensure that all the other activity have
+            // been finished and the top task in another task gets resumed when a top activity is
+            // removed. Otherwise, the next top activity could be started while the top activity
+            // is removed, which is not necessary since the next top activity is on the same Task
+            // and should also be removed.
+            for (int i = finishingActivities.size() - 1; i >= 0; i--) {
+                final ActivityRecord r = finishingActivities.get(i);
+
                 // Prevent the transition from being executed too early if the top activity is
                 // resumed but the mVisibleRequested of any other activity is true, the transition
                 // should wait until next activity resumed.
@@ -1587,7 +1616,7 @@
                 } else {
                     r.destroyIfPossible(reason);
                 }
-            });
+            }
         }
     }
 
@@ -2145,7 +2174,7 @@
     }
 
     private boolean shouldStartChangeTransition(int prevWinMode, @NonNull Rect prevBounds) {
-        if (!isLeafTask() || !canStartChangeTransition()) {
+        if (!(isLeafTask() || mCreatedByOrganizer) || !canStartChangeTransition()) {
             return false;
         }
         final int newWinMode = getWindowingMode();
@@ -2433,7 +2462,7 @@
 
         final String myReason = reason + " adjustFocusToNextFocusableTask";
         final ActivityRecord top = focusableTask.topRunningActivity();
-        if (focusableTask.isActivityTypeHome() && (top == null || !top.mVisibleRequested)) {
+        if (focusableTask.isActivityTypeHome() && (top == null || !top.isVisibleRequested())) {
             // If we will be focusing on the root home task next and its current top activity isn't
             // visible, then use the move the root home task to top to make the activity visible.
             focusableTask.getDisplayArea().moveHomeActivityToTop(myReason);
@@ -2553,7 +2582,7 @@
         }
         mRemoving = true;
 
-        EventLogTags.writeWmTaskRemoved(mTaskId, reason);
+        EventLogTags.writeWmTaskRemoved(mTaskId, getRootTaskId(), getDisplayId(), reason);
         clearPinnedTaskIfNeed();
         // If applicable let the TaskOrganizer know the Task is vanishing.
         setTaskOrganizer(null);
@@ -2566,7 +2595,8 @@
     void reparent(Task rootTask, int position, boolean moveParents, String reason) {
         if (DEBUG_ROOT_TASK) Slog.i(TAG, "reParentTask: removing taskId=" + mTaskId
                 + " from rootTask=" + getRootTask());
-        EventLogTags.writeWmTaskRemoved(mTaskId, "reParentTask:" + reason);
+        EventLogTags.writeWmTaskRemoved(mTaskId, getRootTaskId(), getDisplayId(),
+                "reParentTask:" + reason);
 
         reparent(rootTask, position);
 
@@ -2745,7 +2775,7 @@
      */
     private static void getMaxVisibleBounds(ActivityRecord token, Rect out, boolean[] foundTop) {
         // skip hidden (or about to hide) apps
-        if (token.mIsExiting || !token.isClientVisible() || !token.mVisibleRequested) {
+        if (token.mIsExiting || !token.isClientVisible() || !token.isVisibleRequested()) {
             return;
         }
         final WindowState win = token.findMainWindow();
@@ -3054,7 +3084,7 @@
      * this activity.
      */
     ActivityRecord getTopVisibleActivity() {
-        return getActivity((r) -> !r.mIsExiting && r.isClientVisible() && r.mVisibleRequested);
+        return getActivity((r) -> !r.mIsExiting && r.isClientVisible() && r.isVisibleRequested());
     }
 
     /**
@@ -3073,20 +3103,6 @@
         });
     }
 
-    void positionChildAtTop(ActivityRecord child) {
-        positionChildAt(child, POSITION_TOP);
-    }
-
-    void positionChildAt(ActivityRecord child, int position) {
-        if (child == null) {
-            Slog.w(TAG_WM,
-                    "Attempted to position of non-existing app");
-            return;
-        }
-
-        positionChildAt(position, child, false /* includeParents */);
-    }
-
     void setTaskDescription(TaskDescription taskDescription) {
         mTaskDescription = taskDescription;
     }
@@ -3102,20 +3118,6 @@
     }
 
     @Override
-    int getOrientation(int candidate) {
-        return canSpecifyOrientation() ? super.getOrientation(candidate) : SCREEN_ORIENTATION_UNSET;
-    }
-
-    private boolean canSpecifyOrientation() {
-        final int windowingMode = getWindowingMode();
-        final int activityType = getActivityType();
-        return windowingMode == WINDOWING_MODE_FULLSCREEN
-                || activityType == ACTIVITY_TYPE_HOME
-                || activityType == ACTIVITY_TYPE_RECENTS
-                || activityType == ACTIVITY_TYPE_ASSISTANT;
-    }
-
-    @Override
     void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
         final int count = mChildren.size();
         boolean isLeafTask = true;
@@ -3324,14 +3326,6 @@
                     }
                 });
             }
-        } else if (mBackGestureStarted) {
-            // Cancel playing transitions if a back navigation animation is in progress.
-            // This bit is set by {@link BackNavigationController} when a back gesture is started.
-            // It is used as a one-off transition overwrite that is cleared when the back gesture
-            // is committed and triggers a transition, or when the gesture is cancelled.
-            mBackGestureStarted = false;
-            mDisplayContent.mSkipAppTransitionAnimation = true;
-            ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Skipping app transition animation. task=%s", this);
         } else {
             super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
         }
@@ -3673,6 +3667,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));
@@ -4294,13 +4291,14 @@
     }
 
     /**
-     * @return true if the task is currently focused.
+     * @return {@code true} if the task is currently focused or one of its children is focused.
      */
     boolean isFocused() {
         if (mDisplayContent == null || mDisplayContent.mFocusedApp == null) {
             return false;
         }
-        return mDisplayContent.mFocusedApp.getTask() == this;
+        final Task focusedTask = mDisplayContent.mFocusedApp.getTask();
+        return focusedTask == this || (focusedTask != null && focusedTask.getParent() == this);
     }
 
     /**
@@ -4320,6 +4318,8 @@
      */
     void onAppFocusChanged(boolean hasFocus) {
         dispatchTaskInfoChangedIfNeeded(false /* force */);
+        final Task parentTask = getParent().asTask();
+        if (parentTask != null) parentTask.dispatchTaskInfoChangedIfNeeded(false /* force */);
     }
 
     void onPictureInPictureParamsChanged() {
@@ -5163,7 +5163,16 @@
         final Task task = taskTop.getTask();
 
         // If ActivityOptions are moved out and need to be aborted or moved to taskTop.
-        final ActivityOptions topOptions = sResetTargetTaskHelper.process(task, forceReset);
+        final ActivityOptions topOptions;
+
+        // Set the task to be reused, so the TaskFragment#mClearedTaskForReuse can be set if the
+        // embedded activities are finished while reset task.
+        mReuseTask = true;
+        try {
+            topOptions = sResetTargetTaskHelper.process(task, forceReset);
+        } finally {
+            mReuseTask = false;
+        }
 
         if (mChildren.contains(task)) {
             final ActivityRecord newTop = task.getTopNonFinishingActivity();
@@ -5724,7 +5733,7 @@
         forAllActivities(r -> {
             if (!r.info.packageName.equals(packageName)) return;
             r.forceNewConfig = true;
-            if (starting != null && r == starting && r.mVisibleRequested) {
+            if (starting != null && r == starting && r.isVisibleRequested()) {
                 r.startFreezingScreenLocked(CONFIG_SCREEN_LAYOUT);
             }
         });
@@ -6068,6 +6077,12 @@
         }
     }
 
+    void setReparentLeafTaskIfRelaunch(boolean reparentLeafTaskIfRelaunch) {
+        if (isOrganized()) {
+            mReparentLeafTaskIfRelaunch = reparentLeafTaskIfRelaunch;
+        }
+    }
+
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
             @WindowTraceLogLevel int logLevel) {
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index b6c14bb..25b58fa 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -27,7 +27,6 @@
 import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
@@ -455,7 +454,7 @@
         }
 
         mLastLeafTaskToFrontId = t.mTaskId;
-        EventLogTags.writeWmTaskToFront(t.mUserId, t.mTaskId);
+        EventLogTags.writeWmTaskToFront(t.mUserId, t.mTaskId, getDisplayId());
         // Notifying only when a leaf task moved to front. Or the listeners would be notified
         // couple times from the leaf task all the way up to the root task.
         mAtmService.getTaskChangeNotificationController().notifyTaskMovedToFront(t.getTaskInfo());
@@ -649,17 +648,6 @@
             }, SCREEN_ORIENTATION_UNSET);
         }
 
-        // Apps and their containers are not allowed to specify an orientation of non floating
-        // visible tasks created by organizer and that has an adjacent task.
-        final Task nonFloatingTopTask =
-                getTask(t -> !t.getWindowConfiguration().tasksAreFloating());
-        if (nonFloatingTopTask != null) {
-            final Task task = nonFloatingTopTask.getCreatedByOrganizerTask();
-            if (task != null && task.getAdjacentTaskFragment() != null && task.isVisible()) {
-                return SCREEN_ORIENTATION_UNSPECIFIED;
-            }
-        }
-
         final int orientation = super.getOrientation(candidate);
         if (orientation != SCREEN_ORIENTATION_UNSET
                 && orientation != SCREEN_ORIENTATION_BEHIND) {
@@ -911,15 +899,16 @@
             }
         } else if (candidateTask != null) {
             final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
-            final Task launchRootTask = getLaunchRootTask(resolvedWindowingMode, activityType,
+            final Task launchParentTask = getLaunchRootTask(resolvedWindowingMode, activityType,
                     options, sourceTask, launchFlags, candidateTask);
-            if (launchRootTask != null) {
+            if (launchParentTask != null) {
                 if (candidateTask.getParent() == null) {
-                    launchRootTask.addChild(candidateTask, position);
-                } else if (candidateTask.getParent() != launchRootTask) {
-                    candidateTask.reparent(launchRootTask, position);
+                    launchParentTask.addChild(candidateTask, position);
+                } else if (candidateTask.getParent() != launchParentTask) {
+                    candidateTask.reparent(launchParentTask, position);
                 }
-            } else if (candidateTask.getDisplayArea() != this) {
+            } else if (candidateTask.getDisplayArea() != this
+                    || candidateTask.getRootTask().mReparentLeafTaskIfRelaunch) {
                 if (candidateTask.getParent() == null) {
                     addChild(candidateTask, position);
                 } else {
@@ -929,6 +918,7 @@
             // Update windowing mode if necessary, e.g. launch into a different windowing mode.
             if (windowingMode != WINDOWING_MODE_UNDEFINED && candidateTask.isRootTask()
                     && candidateTask.getWindowingMode() != windowingMode) {
+                candidateTask.mTransitionController.collect(candidateTask);
                 candidateTask.setWindowingMode(windowingMode);
             }
             return candidateTask.getRootTask();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 230b760..66b868a 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -17,7 +17,9 @@
 package com.android.server.wm;
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -27,6 +29,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
 import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -76,9 +80,11 @@
 import android.app.servertransaction.NewIntentItem;
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.ResumeActivityItem;
+import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -166,13 +172,6 @@
      * indicate that an Activity can't be embedded because the Activity is started on a new task.
      */
     static final int EMBEDDING_DISALLOWED_NEW_TASK = 3;
-    /**
-     * An embedding check result of
-     * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
-     * indicate that an Activity can't be embedded because the Activity is started on a new
-     * TaskFragment, e.g. start an Activity on a new TaskFragment for result.
-     */
-    static final int EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT = 4;
 
     /**
      * Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
@@ -183,7 +182,6 @@
             EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
             EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
             EMBEDDING_DISALLOWED_NEW_TASK,
-            EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT,
     })
     @interface EmbeddingCheckResult {}
 
@@ -223,6 +221,14 @@
     private TaskFragment mAdjacentTaskFragment;
 
     /**
+     * Unlike the {@link mAdjacentTaskFragment}, the companion TaskFragment is not always visually
+     * adjacent to this one, but this TaskFragment will be removed by the organizer if the
+     * companion TaskFragment is removed.
+     */
+    @Nullable
+    private TaskFragment mCompanionTaskFragment;
+
+    /**
      * Prevents duplicate calls to onTaskAppeared.
      */
     boolean mTaskFragmentAppearedSent;
@@ -240,6 +246,12 @@
     boolean mClearedTaskFragmentForPip;
 
     /**
+     * The last running activity of the TaskFragment was removed and added to the top-most of the
+     * Task because it was launched with FLAG_ACTIVITY_REORDER_TO_FRONT.
+     */
+    boolean mClearedForReorderActivityToFront;
+
+    /**
      * When we are in the process of pausing an activity, before starting the
      * next one, this variable holds the activity that is currently being paused.
      *
@@ -290,6 +302,12 @@
     private final IBinder mFragmentToken;
 
     /**
+     * Whether to delay the call to {@link #updateOrganizedTaskFragmentSurface()} when there is a
+     * configuration change.
+     */
+    private boolean mDelayOrganizedTaskFragmentSurfaceUpdate;
+
+    /**
      * Whether to delay the last activity of TaskFragment being immediately removed while finishing.
      * This should only be set on a embedded TaskFragment, where the organizer can have the
      * opportunity to perform animations and finishing the adjacent TaskFragment.
@@ -328,7 +346,7 @@
         }
 
         void process(ActivityRecord start, boolean preserveWindow) {
-            if (start == null || !start.mVisibleRequested) {
+            if (start == null || !start.isVisibleRequested()) {
                 return;
             }
             reset(preserveWindow);
@@ -389,6 +407,14 @@
         }
     }
 
+    void setCompanionTaskFragment(@Nullable TaskFragment companionTaskFragment) {
+        mCompanionTaskFragment = companionTaskFragment;
+    }
+
+    TaskFragment getCompanionTaskFragment() {
+        return mCompanionTaskFragment;
+    }
+
     void resetAdjacentTaskFragment() {
         // Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment.
         if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
@@ -554,15 +580,7 @@
 
     @Override
     boolean isEmbedded() {
-        if (mIsEmbedded) {
-            return true;
-        }
-        final WindowContainer<?> parent = getParent();
-        if (parent != null) {
-            final TaskFragment taskFragment = parent.asTaskFragment();
-            return taskFragment != null && taskFragment.isEmbedded();
-        }
-        return false;
+        return mIsEmbedded;
     }
 
     @EmbeddingCheckResult
@@ -590,14 +608,6 @@
             return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
         }
 
-        // Cannot embed activity across TaskFragments for activity result.
-        // If the activity that started for result is finishing, it's likely that this start mode
-        // is used to place an activity in the same task. Since the finishing activity won't be
-        // able to get the results, so it's OK to embed in a different TaskFragment.
-        if (a.resultTo != null && !a.resultTo.finishing && a.resultTo.getTaskFragment() != this) {
-            return EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
-        }
-
         return EMBEDDING_ALLOWED;
     }
 
@@ -1141,8 +1151,16 @@
         }
 
         next.delayedResume = false;
-        final TaskDisplayArea taskDisplayArea = getDisplayArea();
 
+        // If we are currently pausing an activity, then don't do anything until that is done.
+        final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
+        if (!allPausedComplete) {
+            ProtoLog.v(WM_DEBUG_STATES,
+                    "resumeTopActivity: Skip resume: some activity pausing.");
+            return false;
+        }
+
+        final TaskDisplayArea taskDisplayArea = getDisplayArea();
         // If the top activity is the resumed one, nothing to do.
         if (mResumedActivity == next && next.isState(RESUMED)
                 && taskDisplayArea.allResumedActivitiesComplete()) {
@@ -1165,14 +1183,6 @@
             return false;
         }
 
-        // If we are currently pausing an activity, then don't do anything until that is done.
-        final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
-        if (!allPausedComplete) {
-            ProtoLog.v(WM_DEBUG_STATES,
-                    "resumeTopActivity: Skip resume: some activity pausing.");
-            return false;
-        }
-
         // If we are sleeping, and there is no resumed activity, and the top activity is paused,
         // well that is the state we want.
         if (mLastPausedActivity == next && shouldSleepOrShutDownActivities()) {
@@ -1346,7 +1356,7 @@
         if (next.attachedToProcess()) {
             if (DEBUG_SWITCH) {
                 Slog.v(TAG_SWITCH, "Resume running: " + next + " stopped=" + next.stopped
-                        + " visibleRequested=" + next.mVisibleRequested);
+                        + " visibleRequested=" + next.isVisibleRequested());
             }
 
             // If the previous activity is translucent, force a visibility update of
@@ -1360,7 +1370,7 @@
                     || mLastPausedActivity != null && !mLastPausedActivity.occludesParent();
 
             // This activity is now becoming visible.
-            if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) {
+            if (!next.isVisibleRequested() || next.stopped || lastActivityTranslucent) {
                 next.app.addToPendingTop();
                 next.setVisibility(true);
             }
@@ -1411,7 +1421,7 @@
                     // Do over!
                     mTaskSupervisor.scheduleResumeTopActivities();
                 }
-                if (!next.mVisibleRequested || next.stopped) {
+                if (!next.isVisibleRequested() || next.stopped) {
                     next.setVisibility(true);
                 }
                 next.completeResumeLocked();
@@ -1722,7 +1732,7 @@
             } else if (prev.attachedToProcess()) {
                 ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
                                 + "wasStopping=%b visibleRequested=%b",  prev,  wasStopping,
-                        prev.mVisibleRequested);
+                        prev.isVisibleRequested());
                 if (prev.deferRelaunchUntilPaused) {
                     // Complete the deferred relaunch that was waiting for pause to complete.
                     ProtoLog.v(WM_DEBUG_STATES, "Re-launching after pause: %s", prev);
@@ -1732,7 +1742,7 @@
                     // We can't clobber it, because the stop confirmation will not be handled.
                     // We don't need to schedule another stop, we only need to let it happen.
                     prev.setState(STOPPING, "completePausedLocked");
-                } else if (!prev.mVisibleRequested || shouldSleepOrShutDownActivities()) {
+                } else if (!prev.isVisibleRequested() || shouldSleepOrShutDownActivities()) {
                     // Clear out any deferred client hide we might currently have.
                     prev.setDeferHidingClient(false);
                     // If we were visible then resumeTopActivities will release resources before
@@ -1788,6 +1798,51 @@
         }
     }
 
+    @ActivityInfo.ScreenOrientation
+    @Override
+    int getOrientation(@ActivityInfo.ScreenOrientation int candidate) {
+        if (shouldReportOrientationUnspecified()) {
+            return SCREEN_ORIENTATION_UNSPECIFIED;
+        }
+        if (canSpecifyOrientation()) {
+            return super.getOrientation(candidate);
+        }
+        return SCREEN_ORIENTATION_UNSET;
+    }
+
+    /**
+     * Whether or not to allow this container to specify an app requested orientation.
+     *
+     * This is different from {@link #providesOrientation()} that
+     * 1. The container may still provide an orientation even if it can't specify the app requested
+     *    one, such as {@link #shouldReportOrientationUnspecified()}
+     * 2. Even if the container can specify an app requested orientation, it may not be used by the
+     *    parent container if it is {@link ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
+     */
+    boolean canSpecifyOrientation() {
+        final int windowingMode = getWindowingMode();
+        final int activityType = getActivityType();
+        return windowingMode == WINDOWING_MODE_FULLSCREEN
+                || activityType == ACTIVITY_TYPE_HOME
+                || activityType == ACTIVITY_TYPE_RECENTS
+                || activityType == ACTIVITY_TYPE_ASSISTANT;
+    }
+
+    /**
+     * Whether or not the parent container should use the orientation provided by this container
+     * even if it is {@link ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
+     */
+    @Override
+    boolean providesOrientation() {
+        return super.providesOrientation() || shouldReportOrientationUnspecified();
+    }
+
+    private boolean shouldReportOrientationUnspecified() {
+        // Apps and their containers are not allowed to specify orientation from adjacent
+        // TaskFragment.
+        return getAdjacentTaskFragment() != null && isVisibleRequested();
+    }
+
     @Override
     void forAllTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
         super.forAllTaskFragments(callback, traverseTopToBottom);
@@ -1845,6 +1900,7 @@
         ActivityRecord r = topRunningActivity();
         mClearedTaskForReuse = false;
         mClearedTaskFragmentForPip = false;
+        mClearedForReorderActivityToFront = false;
 
         final ActivityRecord addingActivity = child.asActivityRecord();
         final boolean isAddingActivity = addingActivity != null;
@@ -1859,7 +1915,6 @@
         super.addChild(child, index);
 
         if (isAddingActivity && task != null) {
-
             // TODO(b/207481538): temporary per-activity screenshoting
             if (r != null && BackNavigationController.isScreenshotEnabled()) {
                 ProtoLog.v(WM_DEBUG_BACK_PREVIEW, "Screenshotting Activity %s",
@@ -1891,10 +1946,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;
     }
@@ -2264,35 +2319,42 @@
 
     @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
-        // Task will animate differently.
-        if (mTaskFragmentOrganizer != null) {
-            mTmpPrevBounds.set(getBounds());
-        }
-
         super.onConfigurationChanged(newParentConfig);
-
-        final boolean shouldStartChangeTransition = shouldStartChangeTransition(mTmpPrevBounds);
-        if (shouldStartChangeTransition) {
-            initializeChangeTransition(mTmpPrevBounds);
-        }
-        if (mTaskFragmentOrganizer != null) {
-            if (mTransitionController.isShellTransitionsEnabled()
-                    && !mTransitionController.isCollecting(this)) {
-                // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
-                // update the surface here if it is not collected by Shell transition.
-                updateOrganizedTaskFragmentSurface();
-            } else if (!mTransitionController.isShellTransitionsEnabled()
-                    && !shouldStartChangeTransition) {
-                // Update the surface here instead of in the organizer so that we can make sure
-                // it can be synced with the surface freezer for legacy app transition.
-                updateOrganizedTaskFragmentSurface();
-            }
-        }
-
+        updateOrganizedTaskFragmentSurface();
         sendTaskFragmentInfoChanged();
     }
 
-    private void updateOrganizedTaskFragmentSurface() {
+    void deferOrganizedTaskFragmentSurfaceUpdate() {
+        mDelayOrganizedTaskFragmentSurfaceUpdate = true;
+    }
+
+    void continueOrganizedTaskFragmentSurfaceUpdate() {
+        mDelayOrganizedTaskFragmentSurfaceUpdate = false;
+        updateOrganizedTaskFragmentSurface();
+    }
+
+    /**
+     * TaskFragmentOrganizer doesn't have access to the surface for security reasons, so we need to
+     * update its surface on the server side if it is not collected for Shell or in pending
+     * animation.
+     */
+    void updateOrganizedTaskFragmentSurface() {
+        if (mDelayOrganizedTaskFragmentSurfaceUpdate || mTaskFragmentOrganizer == null) {
+            return;
+        }
+        if (mTransitionController.isShellTransitionsEnabled()
+                && !mTransitionController.isCollecting(this)) {
+            // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
+            // update the surface here if it is not collected by Shell transition.
+            updateOrganizedTaskFragmentSurfaceUnchecked();
+        } else if (!mTransitionController.isShellTransitionsEnabled() && !isAnimating()) {
+            // Update the surface here instead of in the organizer so that we can make sure
+            // it can be synced with the surface freezer for legacy app transition.
+            updateOrganizedTaskFragmentSurfaceUnchecked();
+        }
+    }
+
+    private void updateOrganizedTaskFragmentSurfaceUnchecked() {
         final SurfaceControl.Transaction t = getSyncTransaction();
         updateSurfacePosition(t);
         updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
@@ -2309,7 +2371,10 @@
             return;
         }
 
-        final Rect bounds = getBounds();
+        // If this TaskFragment is closing while resizing, crop to the starting bounds instead.
+        final Rect bounds = isClosingWhenResizing()
+                ? mDisplayContent.mClosingChangingContainers.get(this)
+                : getBounds();
         final int width = bounds.width();
         final int height = bounds.height();
         if (!forceUpdate && width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
@@ -2327,6 +2392,11 @@
         if (mTaskFragmentOrganizer != null
                 && (mLastSurfaceSize.x != 0 || mLastSurfaceSize.y != 0)) {
             t.setWindowCrop(mSurfaceControl, 0, 0);
+            final SurfaceControl.Transaction syncTransaction = getSyncTransaction();
+            if (t != syncTransaction) {
+                // Avoid restoring to old window crop if the sync transaction is applied later.
+                syncTransaction.setWindowCrop(mSurfaceControl, 0, 0);
+            }
             mLastSurfaceSize.set(0, 0);
         }
     }
@@ -2341,7 +2411,7 @@
     }
 
     /** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */
-    private boolean shouldStartChangeTransition(Rect startBounds) {
+    boolean shouldStartChangeTransition(Rect startBounds) {
         if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) {
             return false;
         }
@@ -2352,6 +2422,15 @@
                 || endBounds.height() != startBounds.height();
     }
 
+    /** Records the starting bounds of the closing organized TaskFragment. */
+    void setClosingChangingStartBoundsIfNeeded() {
+        if (isOrganizedTaskFragment() && mDisplayContent != null
+                && mDisplayContent.mChangingContainers.remove(this)) {
+            mDisplayContent.mClosingChangingContainers.put(
+                    this, new Rect(mSurfaceFreezer.mFreezeBounds));
+        }
+    }
+
     @Override
     boolean isSyncFinished() {
         return super.isSyncFinished() && isReadyToTransit();
@@ -2361,7 +2440,7 @@
     void setSurfaceControl(SurfaceControl sc) {
         super.setSurfaceControl(sc);
         if (mTaskFragmentOrganizer != null) {
-            updateOrganizedTaskFragmentSurface();
+            updateOrganizedTaskFragmentSurfaceUnchecked();
             // If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to
             // emit the callbacks now.
             sendTaskFragmentAppeared();
@@ -2423,6 +2502,7 @@
                 positionInParent,
                 mClearedTaskForReuse,
                 mClearedTaskFragmentForPip,
+                mClearedForReorderActivityToFront,
                 calculateMinDimension());
     }
 
@@ -2469,6 +2549,22 @@
         return mTaskFragmentOrganizer != null;
     }
 
+    /**
+     * Whether this is an embedded {@link TaskFragment} that does not fill the parent {@link Task}.
+     */
+    boolean isEmbeddedWithBoundsOverride() {
+        if (!mIsEmbedded) {
+            return false;
+        }
+        final Task task = getTask();
+        if (task == null) {
+            return false;
+        }
+        final Rect taskBounds = task.getBounds();
+        final Rect taskFragBounds = getBounds();
+        return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
+    }
+
     /** Whether the Task should be visible. */
     boolean isTaskVisibleRequested() {
         final Task task = getTask();
@@ -2497,6 +2593,14 @@
         return false;
     }
 
+    @Override
+    boolean canCustomizeAppTransition() {
+        // This is only called when the app transition is going to be played by system server. In
+        // this case, we should allow custom app transition for fullscreen embedded TaskFragment
+        // just like Activity.
+        return isEmbedded() && matchParentBounds();
+    }
+
     /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
     void clearLastPausedActivity() {
         forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null);
@@ -2528,6 +2632,19 @@
         return !mCreatedByOrganizer || mIsRemovalRequested;
     }
 
+    @Nullable
+    HardwareBuffer getSnapshotForActivityRecord(@Nullable ActivityRecord r) {
+        if (!BackNavigationController.isScreenshotEnabled()) {
+            return null;
+        }
+        if (r != null && r.mActivityComponent != null) {
+            ScreenCapture.ScreenshotHardwareBuffer backBuffer =
+                    mBackScreenshots.get(r.mActivityComponent.flattenToString());
+            return backBuffer != null ? backBuffer.getHardwareBuffer() : null;
+        }
+        return null;
+    }
+
     @Override
     void removeChild(WindowContainer child) {
         removeChild(child, true /* removeSelfIfPossible */);
@@ -2696,6 +2813,16 @@
         return callback.test(this) ? this : null;
     }
 
+    /**
+     * Moves the passed child to front
+     * @return whether it was actually moved (vs already being top).
+     */
+    boolean moveChildToFront(WindowContainer newTop) {
+        int origDist = getDistanceFromTop(newTop);
+        positionChildAt(POSITION_TOP, newTop, false /* includeParents */);
+        return getDistanceFromTop(newTop) != origDist;
+    }
+
     String toFullString() {
         final StringBuilder sb = new StringBuilder(128);
         sb.append(this);
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 509b1e6..6e4df79 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -34,8 +34,8 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Intent;
 import android.content.res.Configuration;
-import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -132,12 +132,11 @@
                 new WeakHashMap<>();
 
         /**
-         * Map from Task Id to {@link RemoteAnimationDefinition}.
-         * @see android.window.TaskFragmentOrganizer#registerRemoteAnimations(int,
-         * RemoteAnimationDefinition) )
+         * {@link RemoteAnimationDefinition} for embedded activities transition animation that is
+         * organized by this organizer.
          */
-        private final SparseArray<RemoteAnimationDefinition> mRemoteAnimationDefinitions =
-                new SparseArray<>();
+        @Nullable
+        private RemoteAnimationDefinition mRemoteAnimationDefinition;
 
         /**
          * Map from {@link TaskFragmentTransaction#getTransactionToken()} to the
@@ -322,9 +321,10 @@
                         + " is not in a task belong to the organizer app.");
                 return null;
             }
-            if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) {
+            if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED
+                    || !task.isAllowedToEmbedActivityInTrustedMode(activity, mOrganizerUid)) {
                 Slog.d(TAG, "Reparent activity=" + activity.token
-                        + " is not allowed to be embedded.");
+                        + " is not allowed to be embedded in trusted mode.");
                 return null;
             }
 
@@ -350,7 +350,7 @@
                     activity.token, task.mTaskId);
             return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK)
                     .setTaskId(task.mTaskId)
-                    .setActivityIntent(activity.intent)
+                    .setActivityIntent(trimIntent(activity.intent))
                     .setActivityToken(activityToken);
         }
 
@@ -425,7 +425,7 @@
             ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
                     "Register task fragment organizer=%s uid=%d pid=%d",
                     organizer.asBinder(), uid, pid);
-            if (mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) {
+            if (isOrganizerRegistered(organizer)) {
                 throw new IllegalStateException(
                         "Replacing existing organizer currently unsupported");
             }
@@ -453,7 +453,7 @@
     }
 
     @Override
-    public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId,
+    public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer,
             @NonNull RemoteAnimationDefinition definition) {
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
@@ -466,20 +466,19 @@
             if (organizerState == null) {
                 throw new IllegalStateException("The organizer hasn't been registered.");
             }
-            if (organizerState.mRemoteAnimationDefinitions.contains(taskId)) {
+            if (organizerState.mRemoteAnimationDefinition != null) {
                 throw new IllegalStateException(
                         "The organizer has already registered remote animations="
-                                + organizerState.mRemoteAnimationDefinitions.get(taskId)
-                                + " for TaskId=" + taskId);
+                                + organizerState.mRemoteAnimationDefinition);
             }
 
             definition.setCallingPidUid(pid, uid);
-            organizerState.mRemoteAnimationDefinitions.put(taskId, definition);
+            organizerState.mRemoteAnimationDefinition = definition;
         }
     }
 
     @Override
-    public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId) {
+    public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer) {
         final int pid = Binder.getCallingPid();
         final long uid = Binder.getCallingUid();
         synchronized (mGlobalLock) {
@@ -493,7 +492,7 @@
                 return;
             }
 
-            organizerState.mRemoteAnimationDefinitions.remove(taskId);
+            organizerState.mRemoteAnimationDefinition = null;
         }
     }
 
@@ -503,10 +502,18 @@
             @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
         // Keep the calling identity to avoid unsecure change.
         synchronized (mGlobalLock) {
-            applyTransaction(wct, transitionType, shouldApplyIndependently);
-            final TaskFragmentOrganizerState state = validateAndGetState(
-                    wct.getTaskFragmentOrganizer());
-            state.onTransactionFinished(transactionToken);
+            if (isValidTransaction(wct)) {
+                applyTransaction(wct, transitionType, shouldApplyIndependently);
+            }
+            // Even if the transaction is empty, we still need to invoke #onTransactionFinished
+            // unless the organizer has been unregistered.
+            final ITaskFragmentOrganizer organizer = wct.getTaskFragmentOrganizer();
+            final TaskFragmentOrganizerState state = organizer != null
+                    ? mTaskFragmentOrganizerState.get(organizer.asBinder())
+                    : null;
+            if (state != null) {
+                state.onTransactionFinished(transactionToken);
+            }
         }
     }
 
@@ -515,7 +522,7 @@
             @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
         // Keep the calling identity to avoid unsecure change.
         synchronized (mGlobalLock) {
-            if (wct.isEmpty()) {
+            if (!isValidTransaction(wct)) {
                 return;
             }
             mWindowOrganizerController.applyTaskFragmentTransactionLocked(wct, transitionType,
@@ -525,16 +532,16 @@
 
     /**
      * Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
-     * {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode.
+     * {@code null} if it doesn't.
      */
     @Nullable
     public RemoteAnimationDefinition getRemoteAnimationDefinition(
-            @NonNull ITaskFragmentOrganizer organizer, int taskId) {
+            @NonNull ITaskFragmentOrganizer organizer) {
         synchronized (mGlobalLock) {
             final TaskFragmentOrganizerState organizerState =
                     mTaskFragmentOrganizerState.get(organizer.asBinder());
             return organizerState != null
-                    ? organizerState.mRemoteAnimationDefinitions.get(taskId)
+                    ? organizerState.mRemoteAnimationDefinition
                     : null;
         }
     }
@@ -656,7 +663,7 @@
             }
             organizer = organizedTf[0].getTaskFragmentOrganizer();
         }
-        if (!mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) {
+        if (!isOrganizerRegistered(organizer)) {
             Slog.w(TAG, "The last TaskFragmentOrganizer no longer exists");
             return;
         }
@@ -702,7 +709,7 @@
         mPendingTaskFragmentEvents.get(event.mTaskFragmentOrg.asBinder()).remove(event);
     }
 
-    boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) {
+    private boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) {
         return mTaskFragmentOrganizerState.containsKey(organizer.asBinder());
     }
 
@@ -739,6 +746,20 @@
         return state;
     }
 
+    boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
+        if (t.isEmpty()) {
+            return false;
+        }
+        final ITaskFragmentOrganizer organizer = t.getTaskFragmentOrganizer();
+        if (t.getTaskFragmentOrganizer() == null || !isOrganizerRegistered(organizer)) {
+            // Transaction from an unregistered organizer should not be applied. This can happen
+            // when the organizer process died before the transaction is applied.
+            Slog.e(TAG, "Caller organizer=" + organizer + " is no longer registered");
+            return false;
+        }
+        return true;
+    }
+
     /**
      * A class to store {@link ITaskFragmentOrganizer} and its organized
      * {@link TaskFragment TaskFragments} with different pending event request.
@@ -1083,16 +1104,18 @@
                 return false;
             }
             final TaskFragment taskFragment = activity.getOrganizedTaskFragment();
-            if (taskFragment == null) {
-                return false;
-            }
-            final Task parentTask = taskFragment.getTask();
-            if (parentTask != null) {
-                final Rect taskBounds = parentTask.getBounds();
-                final Rect taskFragBounds = taskFragment.getBounds();
-                return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
-            }
-            return false;
+            return taskFragment != null && taskFragment.isEmbeddedWithBoundsOverride();
         }
     }
+
+    /**
+     * Trims the given Intent to only those that are needed to for embedding rules. This helps to
+     * make it safer for cross-uid embedding even if we only send the Intent for trusted embedding.
+     */
+    private static Intent trimIntent(@NonNull Intent intent) {
+        return new Intent()
+                .setComponent(intent.getComponent())
+                .setPackage(intent.getPackage())
+                .setAction(intent.getAction());
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 8444489..ad46770 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -51,7 +51,6 @@
 import android.util.Size;
 import android.util.Slog;
 import android.view.Gravity;
-import android.view.View;
 import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -166,9 +165,11 @@
         }
         // If the launch windowing mode is still undefined, inherit from the target task if the
         // task is already on the right display area (otherwise, the task may be on a different
-        // display area that has incompatible windowing mode).
+        // display area that has incompatible windowing mode or the task organizer request to
+        // disassociate the leaf task if relaunched and reparented it to TDA as root task).
         if (launchMode == WINDOWING_MODE_UNDEFINED
-                && task != null && task.getTaskDisplayArea() == suggestedDisplayArea) {
+                && task != null && task.getTaskDisplayArea() == suggestedDisplayArea
+                && !task.getRootTask().mReparentLeafTaskIfRelaunch) {
             launchMode = task.getWindowingMode();
             if (DEBUG) {
                 appendLog("inherit-from-task="
@@ -182,26 +183,34 @@
         // is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is
         // different, we should recalculating the bounds.
         boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = false;
-        final boolean canApplyFreeformPolicy =
+        // Note that initial bounds needs to be set to fullscreen tasks too as it's used as restore
+        // bounds.
+        final boolean canCalculateBoundsForFullscreenTask =
+                canCalculateBoundsForFullscreenTask(suggestedDisplayArea, launchMode);
+        final boolean canApplyFreeformWindowPolicy =
                 canApplyFreeformWindowPolicy(suggestedDisplayArea, launchMode);
-        if (mSupervisor.canUseActivityOptionsLaunchBounds(options)
-                && (canApplyFreeformPolicy || canApplyPipWindowPolicy(launchMode))) {
+        final boolean canApplyWindowLayout = layout != null
+                && (canApplyFreeformWindowPolicy || canCalculateBoundsForFullscreenTask);
+        final boolean canApplyBoundsFromActivityOptions =
+                mSupervisor.canUseActivityOptionsLaunchBounds(options)
+                        && (canApplyFreeformWindowPolicy
+                        || canApplyPipWindowPolicy(launchMode)
+                        || canCalculateBoundsForFullscreenTask);
+
+        if (canApplyBoundsFromActivityOptions) {
             hasInitialBounds = true;
-            launchMode = launchMode == WINDOWING_MODE_UNDEFINED
+            // |launchMode| at this point can be fullscreen, PIP, MultiWindow, etc. Only set
+            // freeform windowing mode if appropriate by checking |canApplyFreeformWindowPolicy|.
+            launchMode = launchMode == WINDOWING_MODE_UNDEFINED && canApplyFreeformWindowPolicy
                     ? WINDOWING_MODE_FREEFORM
                     : launchMode;
             outParams.mBounds.set(options.getLaunchBounds());
             if (DEBUG) appendLog("activity-options-bounds=" + outParams.mBounds);
-        } else if (launchMode == WINDOWING_MODE_PINNED) {
-            // System controls PIP window's bounds, so don't apply launch bounds.
-            if (DEBUG) appendLog("empty-window-layout-for-pip");
-        } else if (launchMode == WINDOWING_MODE_FULLSCREEN) {
-            if (DEBUG) appendLog("activity-options-fullscreen=" + outParams.mBounds);
-        } else if (layout != null && canApplyFreeformPolicy) {
+        } else if (canApplyWindowLayout) {
             mTmpBounds.set(currentParams.mBounds);
             getLayoutBounds(suggestedDisplayArea, root, layout, mTmpBounds);
             if (!mTmpBounds.isEmpty()) {
-                launchMode = WINDOWING_MODE_FREEFORM;
+                launchMode = canApplyFreeformWindowPolicy ? WINDOWING_MODE_FREEFORM : launchMode;
                 outParams.mBounds.set(mTmpBounds);
                 hasInitialBounds = true;
                 hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = true;
@@ -211,6 +220,8 @@
             }
         } else if (launchMode == WINDOWING_MODE_MULTI_WINDOW
                 && options != null && options.getLaunchBounds() != null) {
+            // TODO: Investigate whether we can migrate this clause to the
+            //  |canApplyBoundsFromActivityOptions| case above.
             outParams.mBounds.set(options.getLaunchBounds());
             hasInitialBounds = true;
             if (DEBUG) appendLog("multiwindow-activity-options-bounds=" + outParams.mBounds);
@@ -250,11 +261,9 @@
             if (!currentParams.mBounds.isEmpty()) {
                 // Carry over bounds from callers regardless of launch mode because bounds is still
                 // used to restore last non-fullscreen bounds when launch mode is not freeform.
-                // Therefore it's not a resolution step for non-freeform launch mode and only
-                // consider it fully resolved only when launch mode is freeform.
                 outParams.mBounds.set(currentParams.mBounds);
+                fullyResolvedCurrentParam = true;
                 if (launchMode == WINDOWING_MODE_FREEFORM) {
-                    fullyResolvedCurrentParam = true;
                     if (DEBUG) appendLog("inherit-bounds=" + outParams.mBounds);
                 }
             }
@@ -364,13 +373,13 @@
             if (resolvedMode == WINDOWING_MODE_FREEFORM) {
                 // Make sure bounds are in the displayArea.
                 if (currentParams.mPreferredTaskDisplayArea != taskDisplayArea) {
-                    adjustBoundsToFitInDisplayArea(taskDisplayArea, outParams.mBounds);
+                    adjustBoundsToFitInDisplayArea(taskDisplayArea, layout, outParams.mBounds);
                 }
                 // Even though we want to keep original bounds, we still don't want it to stomp on
                 // an existing task.
                 adjustBoundsToAvoidConflictInDisplayArea(taskDisplayArea, outParams.mBounds);
             }
-        } else if (taskDisplayArea.inFreeformWindowingMode()) {
+        } else {
             if (source != null && source.inFreeformWindowingMode()
                     && resolvedMode == WINDOWING_MODE_FREEFORM
                     && outParams.mBounds.isEmpty()
@@ -562,10 +571,19 @@
         return display.getDisplayId() == source.getDisplayId();
     }
 
+    private boolean canCalculateBoundsForFullscreenTask(@NonNull TaskDisplayArea displayArea,
+                                                        int launchMode) {
+        return mSupervisor.mService.mSupportsFreeformWindowManagement
+                && ((displayArea.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                && launchMode == WINDOWING_MODE_UNDEFINED)
+                || launchMode == WINDOWING_MODE_FULLSCREEN);
+    }
+
     private boolean canApplyFreeformWindowPolicy(@NonNull TaskDisplayArea suggestedDisplayArea,
             int launchMode) {
         return mSupervisor.mService.mSupportsFreeformWindowManagement
-                && (suggestedDisplayArea.inFreeformWindowingMode()
+                && ((suggestedDisplayArea.inFreeformWindowingMode()
+                && launchMode == WINDOWING_MODE_UNDEFINED)
                 || launchMode == WINDOWING_MODE_FREEFORM);
     }
 
@@ -727,16 +745,10 @@
     private void getTaskBounds(@NonNull ActivityRecord root, @NonNull TaskDisplayArea displayArea,
             @NonNull ActivityInfo.WindowLayout layout, int resolvedMode, boolean hasInitialBounds,
             @NonNull Rect inOutBounds) {
-        if (resolvedMode == WINDOWING_MODE_FULLSCREEN) {
-            // We don't handle letterboxing here. Letterboxing will be handled by valid checks
-            // later.
-            inOutBounds.setEmpty();
-            if (DEBUG) appendLog("maximized-bounds");
-            return;
-        }
-
-        if (resolvedMode != WINDOWING_MODE_FREEFORM) {
-            // We don't apply freeform bounds adjustment to other windowing modes.
+        if (resolvedMode != WINDOWING_MODE_FREEFORM
+                && resolvedMode != WINDOWING_MODE_FULLSCREEN) {
+            // This function should be used only for freeform bounds adjustment. Freeform bounds
+            // needs to be set to fullscreen tasks too as restore bounds.
             if (DEBUG) {
                 appendLog("skip-bounds-" + WindowConfiguration.windowingModeToString(resolvedMode));
             }
@@ -775,9 +787,10 @@
             // to the center of suggested bounds (or the displayArea if no suggested bounds). The
             // default size might be too big to center to source activity bounds in displayArea, so
             // we may need to move it back to the displayArea.
+            adjustBoundsToFitInDisplayArea(displayArea, layout, mTmpBounds);
+            inOutBounds.setEmpty();
             LaunchParamsUtil.centerBounds(displayArea, mTmpBounds.width(), mTmpBounds.height(),
                     inOutBounds);
-            adjustBoundsToFitInDisplayArea(displayArea, inOutBounds);
             if (DEBUG) appendLog("freeform-size-mismatch=" + inOutBounds);
         }
 
@@ -824,47 +837,12 @@
     }
 
     private void adjustBoundsToFitInDisplayArea(@NonNull TaskDisplayArea displayArea,
-            @NonNull Rect inOutBounds) {
-        final Rect stableBounds = mTmpStableBounds;
-        displayArea.getStableRect(stableBounds);
-
-        if (stableBounds.width() < inOutBounds.width()
-                || stableBounds.height() < inOutBounds.height()) {
-            // There is no way for us to fit the bounds in the displayArea without changing width
-            // or height. Just move the start to align with the displayArea.
-            final int layoutDirection =
-                    mSupervisor.mRootWindowContainer.getConfiguration().getLayoutDirection();
-            final int left = layoutDirection == View.LAYOUT_DIRECTION_RTL
-                    ? stableBounds.right - inOutBounds.right + inOutBounds.left
-                    : stableBounds.left;
-            inOutBounds.offsetTo(left, stableBounds.top);
-            return;
-        }
-
-        final int dx;
-        if (inOutBounds.right > stableBounds.right) {
-            // Right edge is out of displayArea.
-            dx = stableBounds.right - inOutBounds.right;
-        } else if (inOutBounds.left < stableBounds.left) {
-            // Left edge is out of displayArea.
-            dx = stableBounds.left - inOutBounds.left;
-        } else {
-            // Vertical edges are all in displayArea.
-            dx = 0;
-        }
-
-        final int dy;
-        if (inOutBounds.top < stableBounds.top) {
-            // Top edge is out of displayArea.
-            dy = stableBounds.top - inOutBounds.top;
-        } else if (inOutBounds.bottom > stableBounds.bottom) {
-            // Bottom edge is out of displayArea.
-            dy = stableBounds.bottom - inOutBounds.bottom;
-        } else {
-            // Horizontal edges are all in displayArea.
-            dy = 0;
-        }
-        inOutBounds.offset(dx, dy);
+                                                @NonNull ActivityInfo.WindowLayout layout,
+                                                @NonNull Rect inOutBounds) {
+        final int layoutDirection = mSupervisor.mRootWindowContainer.getConfiguration()
+                .getLayoutDirection();
+        LaunchParamsUtil.adjustBoundsToFitInDisplayArea(displayArea, layoutDirection, layout,
+                inOutBounds);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 0a2e877..274d7ff 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -691,6 +691,8 @@
             if (mainWindow == null || mainWindow.mRemoved) {
                 removalInfo.playRevealAnimation = false;
             } else if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
+                removalInfo.roundedCornerRadius =
+                        topActivity.mLetterboxUiController.getRoundedCornersRadius(mainWindow);
                 removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
                 removalInfo.mainFrame = mainWindow.getRelativeFrame();
             }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 29c98b9..c1b9e662 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -16,29 +16,14 @@
 
 package com.android.server.wm;
 
-import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
-
-import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
-import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
-import static com.android.internal.policy.DecorView.getNavigationBarRect;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityThread;
-import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.RecordingCanvas;
@@ -59,12 +44,11 @@
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowManager.LayoutParams;
 import android.window.ScreenCapture;
+import android.window.SnapshotDrawerUtils;
 import android.window.TaskSnapshot;
 
-import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
-import com.android.internal.policy.DecorView;
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.wm.utils.InsetUtils;
 
@@ -585,7 +569,8 @@
         final Rect taskBounds = task.getBounds();
         final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride();
         final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
-        final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
+        final SnapshotDrawerUtils.SystemBarBackgroundPainter decorPainter =
+                new SnapshotDrawerUtils.SystemBarBackgroundPainter(attrs.flags,
                 attrs.privateFlags, attrs.insetsFlags.appearance, task.getTaskDescription(),
                 mHighResTaskSnapshotScale, mainWindow.getRequestedVisibleTypes());
         final int taskWidth = taskBounds.width();
@@ -599,7 +584,7 @@
         final RecordingCanvas c = node.start(width, height);
         c.drawColor(color);
         decorPainter.setInsets(systemBarInsets);
-        decorPainter.drawDecors(c /* statusBarExcludeFrame */);
+        decorPainter.drawDecors(c /* statusBarExcludeFrame */, null /* alreadyDrawnFrame */);
         node.end(c);
         final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
         if (hwBitmap == null) {
@@ -736,92 +721,4 @@
         pw.println(prefix + "mTaskSnapshotEnabled=" + mTaskSnapshotEnabled);
         mCache.dump(pw, prefix);
     }
-
-    /**
-     * Helper class to draw the background of the system bars in regions the task snapshot isn't
-     * filling the window.
-     */
-    static class SystemBarBackgroundPainter {
-
-        private final Paint mStatusBarPaint = new Paint();
-        private final Paint mNavigationBarPaint = new Paint();
-        private final int mStatusBarColor;
-        private final int mNavigationBarColor;
-        private final int mWindowFlags;
-        private final int mWindowPrivateFlags;
-        private final float mScale;
-        private final @Type.InsetsType int mRequestedVisibleTypes;
-        private final Rect mSystemBarInsets = new Rect();
-
-        SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
-                ActivityManager.TaskDescription taskDescription, float scale,
-                @Type.InsetsType int requestedVisibleTypes) {
-            mWindowFlags = windowFlags;
-            mWindowPrivateFlags = windowPrivateFlags;
-            mScale = scale;
-            final Context context = ActivityThread.currentActivityThread().getSystemUiContext();
-            final int semiTransparent = context.getColor(
-                    R.color.system_bar_background_semi_transparent);
-            mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS,
-                    semiTransparent, taskDescription.getStatusBarColor(), appearance,
-                    APPEARANCE_LIGHT_STATUS_BARS,
-                    taskDescription.getEnsureStatusBarContrastWhenTransparent());
-            mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
-                    FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
-                    taskDescription.getNavigationBarColor(), appearance,
-                    APPEARANCE_LIGHT_NAVIGATION_BARS,
-                    taskDescription.getEnsureNavigationBarContrastWhenTransparent()
-                            && context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
-            mStatusBarPaint.setColor(mStatusBarColor);
-            mNavigationBarPaint.setColor(mNavigationBarColor);
-            mRequestedVisibleTypes = requestedVisibleTypes;
-        }
-
-        void setInsets(Rect systemBarInsets) {
-            mSystemBarInsets.set(systemBarInsets);
-        }
-
-        int getStatusBarColorViewHeight() {
-            final boolean forceBarBackground =
-                    (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
-            if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
-                    mRequestedVisibleTypes, mStatusBarColor, mWindowFlags, forceBarBackground)) {
-                return (int) (mSystemBarInsets.top * mScale);
-            } else {
-                return 0;
-            }
-        }
-
-        private boolean isNavigationBarColorViewVisible() {
-            final boolean forceBarBackground =
-                    (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
-            return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
-                    mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags, forceBarBackground);
-        }
-
-        void drawDecors(Canvas c) {
-            drawStatusBarBackground(c, getStatusBarColorViewHeight());
-            drawNavigationBarBackground(c);
-        }
-
-        @VisibleForTesting
-        void drawStatusBarBackground(Canvas c,
-                int statusBarHeight) {
-            if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0) {
-                final int rightInset = (int) (mSystemBarInsets.right * mScale);
-                c.drawRect(0, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint);
-            }
-        }
-
-        @VisibleForTesting
-        void drawNavigationBarBackground(Canvas c) {
-            final Rect navigationBarRect = new Rect();
-            getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect,
-                    mScale);
-            final boolean visible = isNavigationBarColorViewVisible();
-            if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) {
-                c.drawRect(navigationBarRect, mNavigationBarPaint);
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 80c9803..2737456 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -91,6 +91,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -100,7 +101,7 @@
  * Represents a logical transition.
  * @see TransitionController
  */
-class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener {
+class Transition implements BLASTSyncEngine.TransactionReadyListener {
     private static final String TAG = "Transition";
     private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition";
 
@@ -151,6 +152,7 @@
     private @TransitionFlags int mFlags;
     private final TransitionController mController;
     private final BLASTSyncEngine mSyncEngine;
+    private final Token mToken;
     private RemoteTransition mRemoteTransition = null;
 
     /** Only use for clean-up after binder death! */
@@ -158,9 +160,9 @@
     private SurfaceControl.Transaction mFinishTransaction = null;
 
     /**
-     * Contains change infos for both participants and all ancestors. We have to track ancestors
-     * because they are all promotion candidates and thus we need their start-states
-     * to be captured.
+     * Contains change infos for both participants and all remote-animatable ancestors. The
+     * ancestors can be the promotion candidates so their start-states need to be captured.
+     * @see #getAnimatableParent
      */
     final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>();
 
@@ -213,10 +215,27 @@
         mFlags = flags;
         mController = controller;
         mSyncEngine = syncEngine;
+        mToken = new Token(this);
 
         controller.mTransitionTracer.logState(this);
     }
 
+    @Nullable
+    static Transition fromBinder(@Nullable IBinder token) {
+        if (token == null) return null;
+        try {
+            return ((Token) token).mTransition.get();
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Invalid transition token: " + token, e);
+            return null;
+        }
+    }
+
+    @NonNull
+    IBinder getToken() {
+        return mToken;
+    }
+
     void addFlag(int flag) {
         mFlags |= flag;
     }
@@ -338,7 +357,7 @@
         return mFinishTransaction;
     }
 
-    private boolean isCollecting() {
+    boolean isCollecting() {
         return mState == STATE_COLLECTING || mState == STATE_STARTED;
     }
 
@@ -392,8 +411,9 @@
                 mSyncId, wc);
         // "snapshot" all parents (as potential promotion targets). Do this before checking
         // if this is already a participant in case it has since been re-parented.
-        for (WindowContainer curr = wc.getParent(); curr != null && !mChanges.containsKey(curr);
-                curr = curr.getParent()) {
+        for (WindowContainer<?> curr = getAnimatableParent(wc);
+                curr != null && !mChanges.containsKey(curr);
+                curr = getAnimatableParent(curr)) {
             mChanges.put(curr, new ChangeInfo(curr));
             if (isReadyGroup(curr)) {
                 mReadyTracker.addGroup(curr);
@@ -469,6 +489,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) {
@@ -577,8 +639,8 @@
                 if (target.asDisplayContent() != null || target.asActivityRecord() != null) {
                     t.setCrop(targetLeash, null /* crop */);
                 } else {
-                    // Crop to the requested bounds.
-                    final Rect clipRect = target.getRequestedOverrideBounds();
+                    // Crop to the resolved override bounds.
+                    final Rect clipRect = target.getResolvedOverrideBounds();
                     t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height());
                 }
                 t.setCornerRadius(targetLeash, 0);
@@ -684,6 +746,11 @@
             Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
                     System.identityHashCode(this));
         }
+        // Close the transactions now. They were originally copied to Shell in case we needed to
+        // apply them due to a remote failure. Since we don't need to apply them anymore, free them
+        // immediately.
+        if (mStartTransaction != null) mStartTransaction.close();
+        if (mFinishTransaction != null) mFinishTransaction.close();
         mStartTransaction = mFinishTransaction = null;
         if (mState < STATE_PLAYING) {
             throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
@@ -825,13 +892,14 @@
             mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
                     false /* forceRelayout */);
         }
+        cleanUpInternal();
     }
 
     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;
@@ -867,15 +935,9 @@
             dc.getPendingTransaction().merge(transaction);
             mSyncId = -1;
             mOverrideOptions = null;
+            cleanUpInternal();
             return;
         }
-        // Ensure that wallpaper visibility is updated with the latest wallpaper target.
-        for (int i = mParticipants.size() - 1; i >= 0; --i) {
-            final WindowContainer<?> wc = mParticipants.valueAt(i);
-            if (isWallpaper(wc) && wc.getDisplayContent() != null) {
-                wc.getDisplayContent().mWallpaperController.adjustWallpaperWindows();
-            }
-        }
 
         mState = STATE_PLAYING;
         mStartTransaction = transaction;
@@ -886,10 +948,16 @@
             mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
         }
 
+        // Check whether the participants were animated from back navigation.
+        final boolean markBackAnimated = mController.mAtm.mBackNavigationController
+                .containsBackAnimationTargets(this);
         // Resolve the animating targets from the participants
         mTargets = calculateTargets(mParticipants, mChanges);
         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges,
                 transaction);
+        if (markBackAnimated) {
+            mController.mAtm.mBackNavigationController.clearBackAnimations(mStartTransaction);
+        }
         if (mOverrideOptions != null) {
             info.setAnimationOptions(mOverrideOptions);
             if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
@@ -924,7 +992,7 @@
         // show here in the same way that we manually hide in finishTransaction.
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
-            if (ar == null || !ar.mVisibleRequested) continue;
+            if (ar == null || !ar.isVisibleRequested()) continue;
             transaction.show(ar.getSurfaceControl());
 
             // Also manually show any non-reported parents. This is necessary in a few cases
@@ -985,7 +1053,9 @@
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "Calling onTransitionReady: %s", info);
                 mController.getTransitionPlayer().onTransitionReady(
-                        this, info, transaction, mFinishTransaction);
+                        mToken, info, transaction, mFinishTransaction);
+                // Since we created root-leash but no longer reference it from core, release it now
+                info.releaseAnimSurfaces();
                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
                     Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
                             System.identityHashCode(this));
@@ -1018,7 +1088,17 @@
         if (mFinishTransaction != null) {
             mFinishTransaction.apply();
         }
-        mController.finishTransition(this);
+        mController.finishTransition(mToken);
+    }
+
+    private void cleanUpInternal() {
+        // Clean-up any native references.
+        for (int i = 0; i < mChanges.size(); ++i) {
+            final ChangeInfo ci = mChanges.valueAt(i);
+            if (ci.mSnapshot != null) {
+                ci.mSnapshot.release();
+            }
+        }
     }
 
     /** @see RecentsAnimationController#attachNavigationBarToApp */
@@ -1166,7 +1246,7 @@
         ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
             ActivityRecord r = mParticipants.valueAt(i).asActivityRecord();
-            if (r == null || !r.mVisibleRequested) continue;
+            if (r == null || !r.isVisibleRequested()) continue;
             int transitionReason = APP_TRANSITION_WINDOWS_DRAWN;
             // At this point, r is "ready", but if it's not "ALL ready" then it is probably only
             // ready due to starting-window.
@@ -1193,6 +1273,16 @@
         return sb.toString();
     }
 
+    /** Returns the parent that the remote animator can animate or control. */
+    private static WindowContainer<?> getAnimatableParent(WindowContainer<?> wc) {
+        WindowContainer<?> parent = wc.getParent();
+        while (parent != null
+                && (!parent.canCreateRemoteAnimationTarget() && !parent.isOrganized())) {
+            parent = parent.getParent();
+        }
+        return parent;
+    }
+
     private static boolean reportIfNotTop(WindowContainer wc) {
         // Organized tasks need to be reported anyways because Core won't show() their surfaces
         // and we can't rely on onTaskAppeared because it isn't in sync.
@@ -1416,7 +1506,8 @@
             intermediates.clear();
             boolean foundParentInTargets = false;
             // Collect the intermediate parents between target and top changed parent.
-            for (WindowContainer<?> p = wc.getParent(); p != null; p = p.getParent()) {
+            for (WindowContainer<?> p = getAnimatableParent(wc); p != null;
+                    p = getAnimatableParent(p)) {
                 final ChangeInfo parentChange = changes.get(p);
                 if (parentChange == null || !parentChange.hasChanged(p)) break;
                 if (p.mRemoteToken == null) {
@@ -1524,23 +1615,13 @@
             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);
+        // Make leash based on highest (z-order) direct child of ancestor with a participant.
+        // TODO(b/261418859): Handle the case when the target contains window containers which
+        // belong to a different display. As a workaround we use topApp, from which wallpaper
+        // window container is removed, instead of sortedTargets here.
+        WindowContainer leashReference = topApp;
         while (leashReference.getParent() != ancestor) {
             leashReference = leashReference.getParent();
         }
@@ -1654,6 +1735,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
@@ -1747,10 +1868,6 @@
         return isCollecting() && mSyncId >= 0;
     }
 
-    static Transition fromBinder(IBinder binder) {
-        return (Transition) binder;
-    }
-
     @VisibleForTesting
     static class ChangeInfo {
         private static final int FLAG_NONE = 0;
@@ -1772,10 +1889,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;
@@ -1857,9 +1983,20 @@
             final Task task = wc.asTask();
             if (task != null) {
                 final ActivityRecord topActivity = task.getTopNonFinishingActivity();
-                if (topActivity != null && topActivity.mStartingData != null
-                        && topActivity.mStartingData.hasImeSurface()) {
-                    flags |= FLAG_WILL_IME_SHOWN;
+                if (topActivity != null) {
+                    if (topActivity.mStartingData != null
+                            && topActivity.mStartingData.hasImeSurface()) {
+                        flags |= FLAG_WILL_IME_SHOWN;
+                    }
+                    if (topActivity.mAtmService.mBackNavigationController
+                            .isMonitorTransitionTarget(topActivity)) {
+                        flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+                    }
+                } else {
+                    if (task.mAtmService.mBackNavigationController
+                            .isMonitorTransitionTarget(task)) {
+                        flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+                    }
                 }
                 if (task.voiceSession != null) {
                     flags |= FLAG_IS_VOICE_INTERACTION;
@@ -1873,6 +2010,10 @@
                     flags |= FLAG_IS_VOICE_INTERACTION;
                 }
                 flags |= record.mTransitionChangeFlags;
+                if (record.mAtmService.mBackNavigationController
+                        .isMonitorTransitionTarget(record)) {
+                    flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+                }
             }
             final TaskFragment taskFragment = wc.asTaskFragment();
             if (taskFragment != null && task == null) {
@@ -2233,4 +2374,18 @@
             }
         }
     }
+
+    private static class Token extends Binder {
+        final WeakReference<Transition> mTransition;
+
+        Token(Transition transition) {
+            mTransition = new WeakReference<>(transition);
+        }
+
+        @Override
+        public String toString() {
+            return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " "
+                    + mTransition.get() + "}";
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index ac85c9a..cf541fc 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -38,6 +38,7 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.ITransitionMetricsReporter;
 import android.window.ITransitionPlayer;
@@ -116,6 +117,8 @@
      */
     boolean mBuildingFinishLayers = false;
 
+    private final SurfaceControl.Transaction mWakeT = new SurfaceControl.Transaction();
+
     TransitionController(ActivityTaskManagerService atm,
             TaskSnapshotController taskSnapshotController,
             TransitionTracer transitionTracer) {
@@ -423,7 +426,7 @@
         Transition newTransition = null;
         if (isCollecting()) {
             if (displayChange != null) {
-                throw new IllegalArgumentException("Provided displayChange for a non-new request");
+                Slog.e(TAG, "Provided displayChange for a non-new request", new Throwable());
             }
             // Make the collecting transition wait until this request is ready.
             mCollectingTransition.setReady(readyGroupRef, false);
@@ -458,8 +461,9 @@
                 info = new ActivityManager.RunningTaskInfo();
                 startTask.fillTaskInfo(info);
             }
-            mTransitionPlayer.requestStartTransition(transition, new TransitionRequestInfo(
-                    transition.mType, info, remoteTransition, displayChange));
+            mTransitionPlayer.requestStartTransition(transition.getToken(),
+                    new TransitionRequestInfo(transition.mType, info, remoteTransition,
+                            displayChange));
             transition.setRemoteTransition(remoteTransition);
         } catch (RemoteException e) {
             Slog.e(TAG, "Error requesting transition", e);
@@ -533,6 +537,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;
@@ -607,8 +622,16 @@
     private void updateRunningRemoteAnimation(Transition transition, boolean isPlaying) {
         if (mTransitionPlayerProc == null) return;
         if (isPlaying) {
+            mWakeT.setEarlyWakeupStart();
+            mWakeT.apply();
+            // Usually transitions put quite a load onto the system already (with all the things
+            // happening in app), so pause task snapshot persisting to not increase the load.
+            mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(true);
             mTransitionPlayerProc.setRunningRemoteAnimation(true);
         } else if (mPlayingTransitions.isEmpty()) {
+            mWakeT.setEarlyWakeupEnd();
+            mWakeT.apply();
+            mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(false);
             mTransitionPlayerProc.setRunningRemoteAnimation(false);
             mRemotePlayer.clear();
             return;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 6522d93..5de143d9 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -114,12 +114,6 @@
 
     private boolean mShouldUpdateZoom;
 
-    /**
-     * Temporary storage for taking a screenshot of the wallpaper.
-     * @see #screenshotWallpaperLocked()
-     */
-    private WindowState mTmpTopWallpaper;
-
     @Nullable private Point mLargestDisplaySize = null;
 
     private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult();
@@ -731,9 +725,9 @@
         }
 
         final boolean newTargetHidden = wallpaperTarget.mActivityRecord != null
-                && !wallpaperTarget.mActivityRecord.mVisibleRequested;
+                && !wallpaperTarget.mActivityRecord.isVisibleRequested();
         final boolean oldTargetHidden = prevWallpaperTarget.mActivityRecord != null
-                && !prevWallpaperTarget.mActivityRecord.mVisibleRequested;
+                && !prevWallpaperTarget.mActivityRecord.isVisibleRequested();
 
         ProtoLog.v(WM_DEBUG_WALLPAPER, "Animating wallpapers: "
                 + "old: %s hidden=%b new: %s hidden=%b",
@@ -965,21 +959,16 @@
     }
 
     WindowState getTopVisibleWallpaper() {
-        mTmpTopWallpaper = null;
-
         for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
             final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
-            token.forAllWindows(w -> {
-                final WindowStateAnimator winAnim = w.mWinAnimator;
-                if (winAnim != null && winAnim.getShown() && winAnim.mLastAlpha > 0f) {
-                    mTmpTopWallpaper = w;
-                    return true;
+            for (int i = token.getChildCount() - 1; i >= 0; i--) {
+                final WindowState w = token.getChildAt(i);
+                if (w.mWinAnimator.getShown() && w.mWinAnimator.mLastAlpha > 0f) {
+                    return w;
                 }
-                return false;
-            }, true /* traverseTopToBottom */);
+            }
         }
-
-        return mTmpTopWallpaper;
+        return null;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 6ee30bb..32cfb70 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -136,7 +136,7 @@
                 recentsAnimationController.linkFixedRotationTransformIfNeeded(this);
             } else if ((wallpaperTarget.mActivityRecord == null
                     // Ignore invisible activity because it may be moving to background.
-                    || wallpaperTarget.mActivityRecord.mVisibleRequested)
+                    || wallpaperTarget.mActivityRecord.isVisibleRequested())
                     && wallpaperTarget.mToken.hasFixedRotationTransform()) {
                 // If the wallpaper target has a fixed rotation, we want the wallpaper to follow its
                 // rotation
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0b5de85..6e61071 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();
@@ -809,6 +813,7 @@
     void removeImmediately() {
         final DisplayContent dc = getDisplayContent();
         if (dc != null) {
+            dc.mClosingChangingContainers.remove(this);
             mSurfaceFreezer.unfreeze(getSyncTransaction());
         }
         while (!mChildren.isEmpty()) {
@@ -1018,9 +1023,12 @@
      * @param dc The display this container is on after changes.
      */
     void onDisplayChanged(DisplayContent dc) {
-        if (mDisplayContent != null && mDisplayContent.mChangingContainers.remove(this)) {
-            // Cancel any change transition queued-up for this container on the old display.
-            mSurfaceFreezer.unfreeze(getSyncTransaction());
+        if (mDisplayContent != null) {
+            mDisplayContent.mClosingChangingContainers.remove(this);
+            if (mDisplayContent.mChangingContainers.remove(this)) {
+                // Cancel any change transition queued-up for this container on the old display.
+                mSurfaceFreezer.unfreeze(getSyncTransaction());
+            }
         }
         mDisplayContent = dc;
         if (dc != null && dc != this) {
@@ -1297,6 +1305,13 @@
         // If we are losing visibility, then a snapshot isn't necessary and we are no-longer
         // part of a change transition.
         if (!visible) {
+            if (asTaskFragment() != null) {
+                // If the organized TaskFragment is closing while resizing, we want to keep track of
+                // its starting bounds to make sure the animation starts at the correct position.
+                // This should be called before unfreeze() because we record the starting bounds
+                // in SurfaceFreezer.
+                asTaskFragment().setClosingChangingStartBoundsIfNeeded();
+            }
             mSurfaceFreezer.unfreeze(getSyncTransaction());
         }
         WindowContainer parent = getParent();
@@ -1305,6 +1320,12 @@
         }
     }
 
+    /** Whether this window is closing while resizing. */
+    boolean isClosingWhenResizing() {
+        return mDisplayContent != null
+                && mDisplayContent.mClosingChangingContainers.containsKey(this);
+    }
+
     void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         proto.write(HASH_CODE, System.identityHashCode(this));
@@ -3005,20 +3026,31 @@
                     // screen empty. Show background color to cover that.
                     showBackdrop = getDisplayContent().mChangingContainers.size() > 1;
                 } else {
-                    // Check whether or not to show backdrop for open/close transition.
-                    final int animAttr = AppTransition.mapOpenCloseTransitTypes(transit, enter);
-                    final Animation a = animAttr != 0
-                            ? appTransition.loadAnimationAttr(lp, animAttr, transit) : null;
+                    // Check whether the app has requested to show backdrop for open/close
+                    // transition.
+                    final Animation a = appTransition.getNextAppRequestedAnimation(enter);
                     showBackdrop = a != null && a.getShowBackdrop();
                 }
                 backdropColor = appTransition.getNextAppTransitionBackgroundColor();
             }
             final Rect localBounds = new Rect(mTmpRect);
             localBounds.offsetTo(mTmpPoint.x, mTmpPoint.y);
-            final RemoteAnimationController.RemoteAnimationRecord adapters =
-                    controller.createRemoteAnimationRecord(
-                            this, mTmpPoint, localBounds, screenBounds,
-                            (isChanging ? mSurfaceFreezer.mFreezeBounds : null), showBackdrop);
+            final RemoteAnimationController.RemoteAnimationRecord adapters;
+            if (!isChanging && !enter && isClosingWhenResizing()) {
+                // Container that is closing while resizing. Pass in the closing start bounds, so
+                // the animation can start with the correct bounds, there won't be a snapshot.
+                // Cleanup the mClosingChangingContainers so that when the animation is finished, it
+                // will reset the surface.
+                final Rect closingStartBounds = getDisplayContent().mClosingChangingContainers
+                        .remove(this);
+                adapters = controller.createRemoteAnimationRecord(
+                        this, mTmpPoint, localBounds, screenBounds, closingStartBounds,
+                        showBackdrop, false /* shouldCreateSnapshot */);
+            } else {
+                final Rect startBounds = isChanging ? mSurfaceFreezer.mFreezeBounds : null;
+                adapters = controller.createRemoteAnimationRecord(
+                        this, mTmpPoint, localBounds, screenBounds, startBounds, showBackdrop);
+            }
             if (backdropColor != 0) {
                 adapters.setBackDropColor(backdropColor);
             }
@@ -3273,9 +3305,10 @@
 
     void resetSurfacePositionForAnimationLeash(Transaction t) {
         t.setPosition(mSurfaceControl, 0, 0);
-        if (mSyncState != SYNC_STATE_NONE && t != mSyncTransaction) {
+        final SurfaceControl.Transaction syncTransaction = getSyncTransaction();
+        if (t != syncTransaction) {
             // Avoid restoring to old position if the sync transaction is applied later.
-            mSyncTransaction.setPosition(mSurfaceControl, 0, 0);
+            syncTransaction.setPosition(mSurfaceControl, 0, 0);
         }
         mLastSurfacePosition.set(0, 0);
     }
@@ -3466,7 +3499,13 @@
             return;
         }
 
-        getRelativePosition(mTmpPos);
+        if (isClosingWhenResizing()) {
+            // This container is closing while resizing, keep its surface at the starting position
+            // to prevent animation flicker.
+            getRelativePosition(mDisplayContent.mClosingChangingContainers.get(this), mTmpPos);
+        } else {
+            getRelativePosition(mTmpPos);
+        }
         final int deltaRotation = getRelativeDisplayRotation();
         if (mTmpPos.equals(mLastSurfacePosition) && deltaRotation == mLastDeltaRotation) {
             return;
@@ -3531,9 +3570,14 @@
         outSurfaceInsets.setEmpty();
     }
 
+    /** Gets the position of this container in its parent's coordinate. */
     void getRelativePosition(Point outPos) {
-        final Rect dispBounds = getBounds();
-        outPos.set(dispBounds.left, dispBounds.top);
+        getRelativePosition(getBounds(), outPos);
+    }
+
+    /** Gets the position of {@code curBounds} in this container's parent's coordinate. */
+    void getRelativePosition(Rect curBounds, Point outPos) {
+        outPos.set(curBounds.left, curBounds.top);
         final WindowContainer parent = getParent();
         if (parent != null) {
             final Rect parentBounds = parent.getBounds();
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index c206a15..1282acb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -43,6 +43,7 @@
 import android.view.SurfaceControlViewHost;
 import android.view.WindowInfo;
 import android.view.WindowManager.DisplayImePolicy;
+import android.view.inputmethod.ImeTracker;
 
 import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.server.input.InputManagerService;
@@ -729,16 +730,20 @@
      * Show IME on imeTargetWindow once IME has finished layout.
      *
      * @param imeTargetWindowToken token of the (IME target) window on which IME should be shown.
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      */
-    public abstract void showImePostLayout(IBinder imeTargetWindowToken);
+    public abstract void showImePostLayout(IBinder imeTargetWindowToken,
+            @Nullable ImeTracker.Token statsToken);
 
     /**
      * Hide IME using imeTargetWindow when requested.
      *
      * @param imeTargetWindowToken token of the (IME target) window on which IME should be hidden.
      * @param displayId the id of the display the IME is on.
+     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
      */
-    public abstract void hideIme(IBinder imeTargetWindowToken, int displayId);
+    public abstract void hideIme(IBinder imeTargetWindowToken, int displayId,
+            @Nullable ImeTracker.Token statsToken);
 
     /**
      * Tell window manager about a package that should be running with a restricted range of
@@ -898,4 +903,7 @@
      * could not be prepared and the session needs to be torn down.
      */
     public abstract boolean setContentRecordingSession(ContentRecordingSession incomingSession);
+
+    /** Returns the SurfaceControl accessibility services should use for accessibility overlays. */
+    public abstract SurfaceControl getA11yOverlayLayer(int displayId);
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 848c231..f6f825f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -140,6 +140,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_VERBOSE_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerServiceDumpProto.BACK_NAVIGATION;
 import static com.android.server.wm.WindowManagerServiceDumpProto.DISPLAY_FROZEN;
 import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_APP;
 import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_DISPLAY_ID;
@@ -292,6 +293,7 @@
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
 import android.view.displayhash.DisplayHash;
 import android.view.displayhash.VerifiedDisplayHash;
+import android.view.inputmethod.ImeTracker;
 import android.window.ClientWindowFrames;
 import android.window.ITaskFpsCallback;
 import android.window.ScreenCapture;
@@ -735,7 +737,6 @@
 
     @VisibleForTesting
     final ContentRecordingController mContentRecordingController = new ContentRecordingController();
-
     @VisibleForTesting
     final class SettingsObserver extends ContentObserver {
         private final Uri mDisplayInversionEnabledUri =
@@ -1842,7 +1843,7 @@
                 // Make this invalid which indicates a null attached frame.
                 outAttachedFrame.set(0, 0, -1, -1);
             }
-            outSizeCompatScale[0] = win.getSizeCompatScale();
+            outSizeCompatScale[0] = win.getCompatScaleForClient();
         }
 
         Binder.restoreCallingIdentity(origId);
@@ -1928,22 +1929,14 @@
                     && attachedWindow.mActivityRecord.mTargetSdk >= Build.VERSION_CODES.O;
         } else {
             // Otherwise, look at the package
-            try {
-                ApplicationInfo appInfo = mContext.getPackageManager()
-                        .getApplicationInfoAsUser(packageName, 0,
-                                UserHandle.getUserId(callingUid));
-                if (appInfo.uid != callingUid) {
-                    throw new SecurityException("Package " + packageName + " not in UID "
-                            + callingUid);
-                }
-                if (appInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
-                    return true;
-                }
-            } catch (PackageManager.NameNotFoundException e) {
-                /* ignore */
+            final ApplicationInfo appInfo = mPmInternal.getApplicationInfo(
+                    packageName, 0 /* flags */, SYSTEM_UID, UserHandle.getUserId(callingUid));
+            if (appInfo == null || appInfo.uid != callingUid) {
+                throw new SecurityException("Package " + packageName + " not in UID "
+                        + callingUid);
             }
+            return appInfo.targetSdkVersion >= Build.VERSION_CODES.O;
         }
-        return false;
     }
 
     /**
@@ -2560,6 +2553,12 @@
                         && win.mSyncSeqId > lastSyncSeqId) {
                     maybeSyncSeqId = win.shouldSyncWithBuffers() ? win.mSyncSeqId : -1;
                     win.markRedrawForSyncReported();
+                    if (win.mSyncState == WindowContainer.SYNC_STATE_WAITING_FOR_DRAW
+                            && winAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN
+                            && maybeSyncSeqId < 0) {
+                        // Do not wait for a drawn window which won't report draw.
+                        win.onSyncFinishedDrawing();
+                    }
                 } else {
                     maybeSyncSeqId = -1;
                 }
@@ -3848,6 +3847,11 @@
                     || displayContent.isInTouchMode() == inTouch)) {
                 return;
             }
+            final boolean displayHasOwnTouchMode =
+                    displayContent != null && displayContent.hasOwnFocus();
+            if (displayHasOwnTouchMode && displayContent.isInTouchMode() == inTouch) {
+                return;
+            }
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
             final boolean hasPermission =
@@ -3856,17 +3860,17 @@
                             /* printlog= */ false);
             final long token = Binder.clearCallingIdentity();
             try {
-                // If perDisplayFocusEnabled is set, then just update the display pointed by
-                // displayId
-                if (perDisplayFocusEnabled) {
+                // If perDisplayFocusEnabled is set or the display maintains its own touch mode,
+                // then just update the display pointed by displayId
+                if (perDisplayFocusEnabled || displayHasOwnTouchMode) {
                     if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission, displayId)) {
                         displayContent.setInTouchMode(inTouch);
                     }
-                } else {  // Otherwise update all displays
+                } else {  // Otherwise update all displays that do not maintain their own touch mode
                     final int displayCount = mRoot.mChildren.size();
                     for (int i = 0; i < displayCount; ++i) {
                         DisplayContent dc = mRoot.mChildren.get(i);
-                        if (dc.isInTouchMode() == inTouch) {
+                        if (dc.isInTouchMode() == inTouch || dc.hasOwnFocus()) {
                             continue;
                         }
                         if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission,
@@ -5295,7 +5299,6 @@
         public static final int WINDOW_FREEZE_TIMEOUT = 11;
 
         public static final int PERSIST_ANIMATION_SCALE = 14;
-        public static final int FORCE_GC = 15;
         public static final int ENABLE_SCREEN = 16;
         public static final int APP_FREEZE_TIMEOUT = 17;
         public static final int REPORT_WINDOWS_CHANGE = 19;
@@ -5386,26 +5389,6 @@
                     break;
                 }
 
-                case FORCE_GC: {
-                    synchronized (mGlobalLock) {
-                        // Since we're holding both mWindowMap and mAnimator we don't need to
-                        // hold mAnimator.mLayoutToAnim.
-                        if (mAnimator.isAnimationScheduled()) {
-                            // If we are animating, don't do the gc now but
-                            // delay a bit so we don't interrupt the animation.
-                            sendEmptyMessageDelayed(H.FORCE_GC, 2000);
-                            return;
-                        }
-                        // If we are currently rotating the display, it will
-                        // schedule a new message when done.
-                        if (mDisplayFrozen) {
-                            return;
-                        }
-                    }
-                    Runtime.getRuntime().gc();
-                    break;
-                }
-
                 case ENABLE_SCREEN: {
                     performEnableScreen();
                     break;
@@ -6264,14 +6247,6 @@
         // now to catch that.
         configChanged = displayContent != null && displayContent.updateOrientation();
 
-        // A little kludge: a lot could have happened while the
-        // display was frozen, so now that we are coming back we
-        // do a gc so that any remote references the system
-        // processes holds on others can be released if they are
-        // no longer needed.
-        mH.removeMessages(H.FORCE_GC);
-        mH.sendEmptyMessageDelayed(H.FORCE_GC, 2000);
-
         mScreenFrozenLock.release();
 
         if (updateRotation && displayContent != null) {
@@ -6591,6 +6566,9 @@
         // Once we move the window layout to the client side, this can be false when we are waiting
         // for the frames.
         proto.write(WINDOW_FRAMES_VALID, true);
+
+        // Write the BackNavigationController's state into the protocol buffer
+        mAtmService.mBackNavigationController.dumpDebug(proto, BACK_NAVIGATION);
     }
 
     private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
@@ -8051,7 +8029,8 @@
         }
 
         @Override
-        public void showImePostLayout(IBinder imeTargetWindowToken) {
+        public void showImePostLayout(IBinder imeTargetWindowToken,
+                @Nullable ImeTracker.Token statsToken) {
             synchronized (mGlobalLock) {
                 InputTarget imeTarget = getInputTargetFromWindowTokenLocked(imeTargetWindowToken);
                 if (imeTarget == null) {
@@ -8060,17 +8039,18 @@
                 Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
                 final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget();
                 imeTarget = controlTarget.getWindow();
-                // If InsetsControlTarget doesn't have a window, its using remoteControlTarget which
-                // is controlled by default display
+                // If InsetsControlTarget doesn't have a window, it's using remoteControlTarget
+                // which is controlled by default display
                 final DisplayContent dc = imeTarget != null
                         ? imeTarget.getDisplayContent() : getDefaultDisplayContentLocked();
                 dc.getInsetsStateController().getImeSourceProvider()
-                        .scheduleShowImePostLayout(controlTarget);
+                        .scheduleShowImePostLayout(controlTarget, statsToken);
             }
         }
 
         @Override
-        public void hideIme(IBinder imeTargetWindowToken, int displayId) {
+        public void hideIme(IBinder imeTargetWindowToken, int displayId,
+                @Nullable ImeTracker.Token statsToken) {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.hideIme");
             synchronized (mGlobalLock) {
                 WindowState imeTarget = mWindowMap.get(imeTargetWindowToken);
@@ -8086,10 +8066,15 @@
                     dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
                 }
                 if (dc != null && dc.getImeTarget(IME_TARGET_CONTROL) != null) {
+                    ImeTracker.get().onProgress(statsToken,
+                            ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
                     ProtoLog.d(WM_DEBUG_IME, "hideIme Control target: %s ",
                             dc.getImeTarget(IME_TARGET_CONTROL));
-                    dc.getImeTarget(IME_TARGET_CONTROL).hideInsets(
-                            WindowInsets.Type.ime(), true /* fromIme */);
+                    dc.getImeTarget(IME_TARGET_CONTROL).hideInsets(WindowInsets.Type.ime(),
+                            true /* fromIme */, statsToken);
+                } else {
+                    ImeTracker.get().onFailed(statsToken,
+                            ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
                 }
                 if (dc != null) {
                     dc.getInsetsStateController().getImeSourceProvider().setImeShowing(false);
@@ -8330,6 +8315,17 @@
                 return true;
             }
         }
+
+        @Override
+        public SurfaceControl getA11yOverlayLayer(int displayId) {
+            synchronized (mGlobalLock) {
+                DisplayContent dc = mRoot.getDisplayContent(displayId);
+                if (dc != null) {
+                    return dc.getA11yOverlayLayer();
+                }
+            }
+            return null;
+        }
     }
 
     void registerAppFreezeListener(AppFreezeListener listener) {
@@ -8932,7 +8928,7 @@
                 outInsetsState.set(state, true /* copySources */);
                 if (WindowState.hasCompatScale(attrs, token, overrideScale)) {
                     final float compatScale = token != null && token.hasSizeCompatBounds()
-                            ? token.getSizeCompatScale() * overrideScale
+                            ? token.getCompatScale() * overrideScale
                             : overrideScale;
                     outInsetsState.scale(1f / compatScale);
                 }
@@ -8944,14 +8940,14 @@
     }
 
     @Override
-    public List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName) {
+    public List<DisplayInfo> getPossibleDisplayInfo(int displayId) {
         final int callingUid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                if (packageName == null || !isRecentsComponent(packageName, callingUid)) {
-                    Slog.e(TAG, "Unable to verify uid for package " + packageName
-                            + " for getPossibleMaximumWindowMetrics");
+                if (!mAtmService.isCallerRecents(callingUid)) {
+                    Slog.e(TAG, "Unable to verify uid for getPossibleDisplayInfo"
+                            + " on uid " + callingUid);
                     return new ArrayList<>();
                 }
 
@@ -8969,31 +8965,6 @@
         return mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId);
     }
 
-    /**
-     * Returns {@code true} when the calling package is the recents component.
-     */
-    boolean isRecentsComponent(@NonNull String callingPackageName, int callingUid) {
-        String recentsPackage;
-        try {
-            String recentsComponent = mContext.getResources().getString(
-                    R.string.config_recentsComponentName);
-            if (recentsComponent == null) {
-                return false;
-            }
-            recentsPackage = ComponentName.unflattenFromString(recentsComponent).getPackageName();
-        } catch (Resources.NotFoundException e) {
-            Slog.e(TAG, "Unable to verify if recents component", e);
-            return false;
-        }
-        try {
-            return callingUid == mContext.getPackageManager().getPackageUid(callingPackageName, 0)
-                    && callingPackageName.equals(recentsPackage);
-        } catch (PackageManager.NameNotFoundException e) {
-            Slog.e(TAG, "Unable to verify if recents component", e);
-            return false;
-        }
-    }
-
     void grantEmbeddedWindowFocus(Session session, IBinder focusToken, boolean grantFocus) {
         synchronized (mGlobalLock) {
             final EmbeddedWindowController.EmbeddedWindow embeddedWindow =
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 2838304..46a30fb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1221,6 +1221,12 @@
             pw.println("Default position for vertical reachability: "
                     + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
                     mLetterboxConfiguration.getDefaultPositionForVerticalReachability()));
+            pw.println("Current position for horizontal reachability:"
+                    + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
+                        mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+            pw.println("Current position for vertical reachability:"
+                    + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
+                        mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
             pw.println("Is education enabled: "
                     + mLetterboxConfiguration.getIsEducationEnabled());
             pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
diff --git a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
index 6f2930c..1b70d1d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
+++ b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
@@ -21,7 +21,7 @@
 import static android.os.Process.myTid;
 import static android.os.Process.setThreadPriority;
 
-import static com.android.server.LockGuard.INDEX_WINDOW;;
+import static com.android.server.LockGuard.INDEX_WINDOW;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.AnimationThread;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3590e9c2..738adc3 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -23,6 +23,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_FINISH_ACTIVITY;
@@ -39,8 +40,10 @@
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT;
 
@@ -48,6 +51,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
 import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -144,6 +148,8 @@
     @VisibleForTesting
     final ArrayMap<IBinder, TaskFragment> mLaunchTaskFragments = new ArrayMap<>();
 
+    private final Rect mTmpBounds = new Rect();
+
     WindowOrganizerController(ActivityTaskManagerService atm) {
         mService = atm;
         mGlobalLock = atm.mGlobalLock;
@@ -301,7 +307,7 @@
                                         nextTransition.setAllReady();
                                     }
                                 });
-                        return nextTransition;
+                        return nextTransition.getToken();
                     }
                     transition = mTransitionController.createTransition(type);
                 }
@@ -310,7 +316,7 @@
                 if (needsSetReady) {
                     transition.setAllReady();
                 }
-                return transition;
+                return transition.getToken();
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -397,9 +403,6 @@
      */
     void applyTaskFragmentTransactionLocked(@NonNull WindowContainerTransaction wct,
             @WindowManager.TransitionType int type, boolean shouldApplyIndependently) {
-        if (!isValidTransaction(wct)) {
-            return;
-        }
         enforceTaskFragmentOrganizerPermission("applyTaskFragmentTransaction()",
                 Objects.requireNonNull(wct.getTaskFragmentOrganizer()),
                 Objects.requireNonNull(wct));
@@ -453,7 +456,7 @@
                     // calls startSyncSet.
                     () -> mTransitionController.moveToCollecting(nextTransition),
                     () -> {
-                        if (isValidTransaction(wct)) {
+                        if (mTaskFragmentOrganizerController.isValidTransaction(wct)) {
                             applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
                             mTransitionController.requestStartTransition(nextTransition,
                                     null /* startTask */, null /* remoteTransition */,
@@ -702,7 +705,7 @@
     }
 
     private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) {
-        int effects = 0;
+        int effects = applyChanges(tr, c, null /* errorCallbackToken */);
         final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
 
         if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
@@ -717,6 +720,10 @@
             effects = TRANSACT_EFFECTS_LIFECYCLE;
         }
 
+        if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
+            tr.setDragResizing(c.getDragResizing(), DRAG_RESIZE_MODE_FREEFORM);
+        }
+
         final int childWindowingMode = c.getActivityWindowingMode();
         if (childWindowingMode > -1) {
             tr.forAllActivities(a -> { a.setWindowingMode(childWindowingMode); });
@@ -759,6 +766,7 @@
     private int applyDisplayAreaChanges(DisplayArea displayArea,
             WindowContainerTransaction.Change c) {
         final int[] effects = new int[1];
+        effects[0] = applyChanges(displayArea, c, null /* errorCallbackToken */);
 
         if ((c.getChangeMask()
                 & WindowContainerTransaction.Change.CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
@@ -779,6 +787,27 @@
         return effects[0];
     }
 
+    private int applyTaskFragmentChanges(@NonNull TaskFragment taskFragment,
+            @NonNull WindowContainerTransaction.Change c, @Nullable IBinder errorCallbackToken) {
+        if (taskFragment.isEmbeddedTaskFragmentInPip()) {
+            // No override from organizer for embedded TaskFragment in a PIP Task.
+            return 0;
+        }
+
+        // When the TaskFragment is resized, we may want to create a change transition for it, for
+        // which we want to defer the surface update until we determine whether or not to start
+        // change transition.
+        mTmpBounds.set(taskFragment.getBounds());
+        taskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
+        final int effects = applyChanges(taskFragment, c, errorCallbackToken);
+        if (taskFragment.shouldStartChangeTransition(mTmpBounds)) {
+            taskFragment.initializeChangeTransition(mTmpBounds);
+        }
+        taskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
+        mTmpBounds.set(0, 0, 0, 0);
+        return effects;
+    }
+
     private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects,
             int syncId, @Nullable Transition transition, boolean isInLockTaskMode,
             @NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken,
@@ -823,6 +852,10 @@
                 effects |= setAdjacentRootsHierarchyOp(hop);
                 break;
             }
+            case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: {
+                effects |= clearAdjacentRootsHierarchyOp(hop);
+                break;
+            }
             case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: {
                 final TaskFragmentCreationParams taskFragmentCreationOptions =
                         hop.getTaskFragmentCreationOptions();
@@ -967,6 +1000,14 @@
                 tf1.setAdjacentTaskFragment(tf2);
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
 
+                // Clear the focused app if the focused app is no longer visible after reset the
+                // adjacent TaskFragments.
+                if (tf2 == null && tf1.getDisplayContent().mFocusedApp != null
+                        && tf1.hasChild(tf1.getDisplayContent().mFocusedApp)
+                        && !tf1.shouldBeVisible(null /* starting */)) {
+                    tf1.getDisplayContent().setFocusedApp(null);
+                }
+
                 final Bundle bundle = hop.getLaunchOptions();
                 final WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams =
                         bundle != null ? new WindowContainerTransaction.TaskFragmentAdjacentParams(
@@ -1080,6 +1121,22 @@
                 effects |= sanitizeAndApplyHierarchyOp(wc, hop);
                 break;
             }
+            case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT: {
+                final IBinder fragmentToken = hop.getContainer();
+                final IBinder companionToken = hop.getCompanionContainer();
+                final TaskFragment fragment = mLaunchTaskFragments.get(fragmentToken);
+                final TaskFragment companion = companionToken != null ? mLaunchTaskFragments.get(
+                        companionToken) : null;
+                if (fragment == null || !fragment.isAttached()) {
+                    final Throwable exception = new IllegalArgumentException(
+                            "Not allowed to set companion on invalid fragment tokens");
+                    sendTaskFragmentOperationFailure(organizer, errorCallbackToken, fragment, type,
+                            exception);
+                    break;
+                }
+                fragment.setCompanionTaskFragment(companion);
+                break;
+            }
             default: {
                 // The other operations may change task order so they are skipped while in lock
                 // task mode. The above operations are still allowed because they don't move
@@ -1177,7 +1234,7 @@
                 WindowContainer.fromBinder(hop.getContainer())
                         .removeLocalInsetsSourceProvider(hop.getInsetsTypes());
                 break;
-            case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
+            case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: {
                 final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
                 if (container == null || container.asDisplayArea() == null
                         || !container.isAttached()) {
@@ -1188,7 +1245,26 @@
                 container.setAlwaysOnTop(hop.isAlwaysOnTop());
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 break;
-
+            }
+            case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH: {
+                final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
+                final Task task = container != null ? container.asTask() : null;
+                if (task == null || !task.isAttached()) {
+                    Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+                            + container);
+                    break;
+                }
+                if (!task.mCreatedByOrganizer) {
+                    throw new UnsupportedOperationException(
+                            "Cannot set reparent leaf task flag on non-organized task : " + task);
+                }
+                if (!task.isRootTask()) {
+                    throw new UnsupportedOperationException(
+                            "Cannot set reparent leaf task flag on non-root task : " + task);
+                }
+                task.setReparentLeafTaskIfRelaunch(hop.isReparentLeafTaskIfRelaunch());
+                break;
+            }
         }
         return effects;
     }
@@ -1435,6 +1511,17 @@
         return TRANSACT_EFFECTS_LIFECYCLE;
     }
 
+    private int clearAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
+        final TaskFragment root = WindowContainer.fromBinder(hop.getContainer()).asTaskFragment();
+        if (!root.mCreatedByOrganizer) {
+            throw new IllegalArgumentException("clearAdjacentRootsHierarchyOp: Not created by"
+                    + " organizer root=" + root);
+        }
+
+        root.resetAdjacentTaskFragment();
+        return TRANSACT_EFFECTS_LIFECYCLE;
+    }
+
     private void sanitizeWindowContainer(WindowContainer wc) {
         if (!(wc instanceof TaskFragment) && !(wc instanceof DisplayArea)) {
             throw new RuntimeException("Invalid token in task fragment or displayArea transaction");
@@ -1444,20 +1531,15 @@
     private int applyWindowContainerChange(WindowContainer wc,
             WindowContainerTransaction.Change c, @Nullable IBinder errorCallbackToken) {
         sanitizeWindowContainer(wc);
-        if (wc.asTaskFragment() != null && wc.asTaskFragment().isEmbeddedTaskFragmentInPip()) {
-            // No override from organizer for embedded TaskFragment in a PIP Task.
-            return 0;
+        if (wc.asDisplayArea() != null) {
+            return applyDisplayAreaChanges(wc.asDisplayArea(), c);
+        } else if (wc.asTask() != null) {
+            return applyTaskChanges(wc.asTask(), c);
+        } else if (wc.asTaskFragment() != null) {
+            return applyTaskFragmentChanges(wc.asTaskFragment(), c, errorCallbackToken);
+        } else {
+            return applyChanges(wc, c, errorCallbackToken);
         }
-
-        int effects = applyChanges(wc, c, errorCallbackToken);
-
-        if (wc instanceof DisplayArea) {
-            effects |= applyDisplayAreaChanges(wc.asDisplayArea(), c);
-        } else if (wc instanceof Task) {
-            effects |= applyTaskChanges(wc.asTask(), c);
-        }
-
-        return effects;
     }
 
     @Override
@@ -1547,6 +1629,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) {
@@ -1563,18 +1651,6 @@
         return (cfgChanges & CONTROLLABLE_CONFIGS) == 0;
     }
 
-    private boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
-        if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
-                .isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
-            // Transaction from an unregistered organizer should not be applied. This can happen
-            // when the organizer process died before the transaction is applied.
-            Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer()
-                    + " is no longer registered");
-            return false;
-        }
-        return true;
-    }
-
     /**
      * Makes sure that the transaction only contains operations that are allowed for the
      * {@link WindowContainerTransaction#getTaskFragmentOrganizer()}.
@@ -1610,6 +1686,10 @@
                             WindowContainer.fromBinder(hop.getAdjacentRoot()),
                             organizer);
                     break;
+                case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS:
+                    enforceTaskFragmentOrganized(func,
+                            WindowContainer.fromBinder(hop.getContainer()), organizer);
+                    break;
                 case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT:
                     // We are allowing organizer to create TaskFragment. We will check the
                     // ownerToken in #createTaskFragment, and trigger error callback if that is not
@@ -1622,6 +1702,12 @@
                 case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
                     enforceTaskFragmentOrganized(func, hop.getNewParent(), organizer);
                     break;
+                case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT:
+                    enforceTaskFragmentOrganized(func, hop.getContainer(), organizer);
+                    if (hop.getCompanionContainer() != null) {
+                        enforceTaskFragmentOrganized(func, hop.getCompanionContainer(), organizer);
+                    }
+                    break;
                 case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS:
                     enforceTaskFragmentOrganized(func, hop.getContainer(), organizer);
                     if (hop.getAdjacentRoot() != null) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 8f63e93..d2cd8f8 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -765,7 +765,7 @@
         // - no longer visible OR
         // - not focusable (in PiP mode for instance)
         if (topDisplay == null
-                || !mPreQTopResumedActivity.mVisibleRequested
+                || !mPreQTopResumedActivity.isVisibleRequested()
                 || !mPreQTopResumedActivity.isFocusable()) {
             canUpdate = true;
         }
@@ -874,7 +874,7 @@
             // to those activities that are part of the package whose app-specific settings changed
             if (packageName.equals(r.packageName)
                     && r.applyAppSpecificConfig(nightMode, localesOverride)
-                    && r.mVisibleRequested) {
+                    && r.isVisibleRequested()) {
                 r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
             }
         }
@@ -956,7 +956,7 @@
             }
             // Don't consider any activities that are currently not in a state where they
             // can be destroyed.
-            if (r.mVisibleRequested || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
+            if (r.isVisibleRequested() || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
                     || r.isState(STARTED, RESUMED, PAUSING, PAUSED, STOPPING)) {
                 if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Not releasing in-use activity: " + r);
                 continue;
@@ -1002,7 +1002,7 @@
                 final int displayId = r.getDisplayId();
                 final Context c = root.getDisplayUiContext(displayId);
 
-                if (c != null && r.mVisibleRequested && !displayContexts.contains(c)) {
+                if (c != null && r.isVisibleRequested() && !displayContexts.contains(c)) {
                     displayContexts.add(c);
                 }
             }
@@ -1070,7 +1070,7 @@
             if (task != null && task.mLayerRank != Task.LAYER_RANK_INVISIBLE) {
                 stateFlags |= ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK;
             }
-            if (r.mVisibleRequested) {
+            if (r.isVisibleRequested()) {
                 if (r.isState(RESUMED)) {
                     stateFlags |= ACTIVITY_STATE_FLAG_HAS_RESUMED;
                 }
@@ -1282,7 +1282,7 @@
         }
         for (int i = activities.size() - 1; i >= 0; i--) {
             final ActivityRecord r = activities.get(i);
-            if (r.mVisibleRequested || r.isVisible()) {
+            if (r.isVisibleRequested() || r.isVisible()) {
                 // While an activity launches a new activity, it's possible that the old activity
                 // is already requested to be hidden (mVisibleRequested=false), but this visibility
                 // is not yet committed, so isVisible()=true.
@@ -1503,7 +1503,7 @@
             Configuration overrideConfig = new Configuration(r.getRequestedOverrideConfiguration());
             overrideConfig.assetsSeq = assetSeq;
             r.onRequestedOverrideConfigurationChanged(overrideConfig);
-            if (r.mVisibleRequested) {
+            if (r.isVisibleRequested()) {
                 r.ensureActivityConfiguration(0, true);
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6d750a4..1b7bd9e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -24,7 +24,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.graphics.GraphicsProtos.dumpPointProto;
-import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.PowerManager.DRAW_WAKE_LOCK;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
@@ -247,6 +246,7 @@
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
 import android.window.ClientWindowFrames;
 import android.window.OnBackInvokedCallbackInfo;
 
@@ -258,6 +258,7 @@
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
+import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -473,7 +474,7 @@
     // Current transformation being applied.
     float mGlobalScale = 1f;
     float mInvGlobalScale = 1f;
-    float mSizeCompatScale = 1f;
+    float mCompatScale = 1f;
     final float mOverrideScale;
     float mHScale = 1f, mVScale = 1f;
     float mLastHScale = 1f, mLastVScale = 1f;
@@ -789,7 +790,7 @@
      * preferredDisplayModeId or is part of the high refresh rate deny list.
      * The variable is cached, so we do not send too many updates to SF.
      */
-    float mAppPreferredFrameRate = 0f;
+    FrameRateVote mFrameRateVote = new FrameRateVote();
 
     static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */
 
@@ -1254,19 +1255,21 @@
 
     void updateGlobalScale() {
         if (hasCompatScale()) {
-            mSizeCompatScale = (mOverrideScale == 1f || mToken.hasSizeCompatBounds())
-                    ? mToken.getSizeCompatScale()
+            mCompatScale = (mOverrideScale == 1f || mToken.hasSizeCompatBounds())
+                    ? mToken.getCompatScale()
                     : 1f;
-            mGlobalScale = mSizeCompatScale * mOverrideScale;
+            mGlobalScale = mCompatScale * mOverrideScale;
             mInvGlobalScale = 1f / mGlobalScale;
             return;
         }
 
-        mGlobalScale = mInvGlobalScale = mSizeCompatScale = 1f;
+        mGlobalScale = mInvGlobalScale = mCompatScale = 1f;
     }
 
-    float getSizeCompatScale() {
-        return mSizeCompatScale;
+    float getCompatScaleForClient() {
+        // If this window in the size compat mode. The scaling is fully controlled at the server
+        // side. The client doesn't need to take it into account.
+        return mToken.hasSizeCompatBounds() ? 1f : mCompatScale;
     }
 
     /**
@@ -1535,10 +1538,11 @@
             mWmService.makeWindowFreezingScreenIfNeededLocked(this);
 
             // If the orientation is changing, or we're starting or ending a drag resizing action,
-            // then we need to hold off on unfreezing the display until this window has been
-            // redrawn; to do that, we need to go through the process of getting informed by the
-            // application when it has finished drawing.
-            if (getOrientationChanging() || dragResizingChanged) {
+            // or we're resizing an embedded Activity, then we need to hold off on unfreezing the
+            // display until this window has been redrawn; to do that, we need to go through the
+            // process of getting informed by the application when it has finished drawing.
+            if (getOrientationChanging() || dragResizingChanged
+                    || isEmbeddedActivityResizeChanged()) {
                 if (dragResizingChanged) {
                     ProtoLog.v(WM_DEBUG_RESIZE,
                             "Resize start waiting for draw, "
@@ -1842,8 +1846,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
@@ -1953,7 +1957,7 @@
      */
     // TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
     boolean isWinVisibleLw() {
-        return (mActivityRecord == null || mActivityRecord.mVisibleRequested
+        return (mActivityRecord == null || mActivityRecord.isVisibleRequested()
                 || mActivityRecord.isAnimating(TRANSITION | PARENTS)) && isVisible();
     }
 
@@ -1990,7 +1994,7 @@
         final ActivityRecord atoken = mActivityRecord;
         return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
                 && isVisibleByPolicy() && !isParentWindowHidden()
-                && (atoken == null || atoken.mVisibleRequested)
+                && (atoken == null || atoken.isVisibleRequested())
                 && !mAnimatingExit && !mDestroying;
     }
 
@@ -2086,7 +2090,10 @@
         } else {
             final Task task = getTask();
             final boolean canFromTask = task != null && task.canAffectSystemUiFlags();
-            return canFromTask && mActivityRecord.isVisible();
+            return canFromTask && mActivityRecord.isVisible()
+            // Do not let snapshot window control the bar
+                    && (mAttrs.type != TYPE_APPLICATION_STARTING
+                    || !(mStartingData instanceof SnapshotStartingData));
         }
     }
 
@@ -2097,7 +2104,7 @@
     boolean isDisplayed() {
         final ActivityRecord atoken = mActivityRecord;
         return isDrawn() && isVisibleByPolicy()
-                && ((!isParentWindowHidden() && (atoken == null || atoken.mVisibleRequested))
+                && ((!isParentWindowHidden() && (atoken == null || atoken.isVisibleRequested()))
                         || isAnimating(TRANSITION | PARENTS));
     }
 
@@ -2119,7 +2126,7 @@
                 // a layout since they can request relayout when client visibility is false.
                 // TODO (b/157682066) investigate if we can clean up isVisible
                 || (atoken == null && !(wouldBeVisibleIfPolicyIgnored() && isVisibleByPolicy()))
-                || (atoken != null && !atoken.mVisibleRequested)
+                || (atoken != null && !atoken.isVisibleRequested())
                 || isParentWindowGoneForLayout()
                 || (mAnimatingExit && !isAnimatingLw())
                 || mDestroying;
@@ -2166,7 +2173,7 @@
             return;
         }
         if (mActivityRecord != null) {
-            if (!mActivityRecord.mVisibleRequested) return;
+            if (!mActivityRecord.isVisibleRequested()) return;
             if (mActivityRecord.allDrawn) {
                 // The allDrawn of activity is reset when the visibility is changed to visible, so
                 // the content should be ready if allDrawn is set.
@@ -2739,7 +2746,7 @@
                         + " exiting=" + mAnimatingExit + " destroying=" + mDestroying);
                 if (mActivityRecord != null) {
                     Slog.i(TAG_WM, "  mActivityRecord.visibleRequested="
-                            + mActivityRecord.mVisibleRequested);
+                            + mActivityRecord.isVisibleRequested());
                 }
             }
         }
@@ -3215,7 +3222,7 @@
         }
 
         return !mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
-                && mActivityRecord.mVisibleRequested;
+                && mActivityRecord.isVisibleRequested();
     }
 
     /**
@@ -3863,14 +3870,15 @@
                 outFrames.attachedFrame.scale(mInvGlobalScale);
             }
         }
-        outFrames.sizeCompatScale = mSizeCompatScale;
+
+        outFrames.compatScale = getCompatScaleForClient();
 
         // Note: in the cases where the window is tied to an activity, we should not send a
         // configuration update when the window has requested to be hidden. Doing so can lead to
         // the client erroneously accepting a configuration that would have otherwise caused an
         // activity restart. We instead hand back the last reported {@link MergedConfiguration}.
         if (useLatestConfig || (relayoutVisible && (mActivityRecord == null
-                || mActivityRecord.mVisibleRequested))) {
+                || mActivityRecord.isVisibleRequested()))) {
             final Configuration globalConfig = getProcessGlobalConfiguration();
             final Configuration overrideConfig = getMergedOverrideConfiguration();
             outMergedConfiguration.setConfiguration(globalConfig, overrideConfig);
@@ -4013,7 +4021,7 @@
             mClient.insetsControlChanged(getCompatInsetsState(),
                     stateController.getControlsForDispatch(this));
         } catch (RemoteException e) {
-            Slog.w(TAG, "Failed to deliver inset state change to w=" + this, e);
+            Slog.w(TAG, "Failed to deliver inset control state change to w=" + this, e);
         }
     }
 
@@ -4023,20 +4031,30 @@
     }
 
     @Override
-    public void showInsets(@InsetsType int types, boolean fromIme) {
+    public void showInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
         try {
-            mClient.showInsets(types, fromIme);
+            ImeTracker.get().onProgress(statsToken,
+                    ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
+            mClient.showInsets(types, fromIme, statsToken);
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to deliver showInsets", e);
+            ImeTracker.get().onFailed(statsToken,
+                    ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
         }
     }
 
     @Override
-    public void hideInsets(@InsetsType int types, boolean fromIme) {
+    public void hideInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
         try {
-            mClient.hideInsets(types, fromIme);
+            ImeTracker.get().onProgress(statsToken,
+                    ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
+            mClient.hideInsets(types, fromIme, statsToken);
         } catch (RemoteException e) {
-            Slog.w(TAG, "Failed to deliver showInsets", e);
+            Slog.w(TAG, "Failed to deliver hideInsets", e);
+            ImeTracker.get().onFailed(statsToken,
+                    ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
         }
     }
 
@@ -4144,6 +4162,20 @@
         return mActivityRecord == null || mActivityRecord.isFullyTransparentBarAllowed(frame);
     }
 
+    /**
+     * Whether this window belongs to a resizing embedded activity.
+     */
+    private boolean isEmbeddedActivityResizeChanged() {
+        if (mActivityRecord == null || !isVisibleRequested()) {
+            // No need to update if the window is in the background.
+            return false;
+        }
+
+        final TaskFragment embeddedTaskFragment = mActivityRecord.getOrganizedTaskFragment();
+        return embeddedTaskFragment != null
+                && mDisplayContent.mChangingContainers.contains(embeddedTaskFragment);
+    }
+
     boolean isDragResizeChanged() {
         return mDragResizing != computeDragResizing();
     }
@@ -4705,7 +4737,7 @@
                     + " during animation: policyVis=" + isVisibleByPolicy()
                     + " parentHidden=" + isParentWindowHidden()
                     + " tok.visibleRequested="
-                    + (mActivityRecord != null && mActivityRecord.mVisibleRequested)
+                    + (mActivityRecord != null && mActivityRecord.isVisibleRequested())
                     + " tok.visible=" + (mActivityRecord != null && mActivityRecord.isVisible())
                     + " animating=" + isAnimating(TRANSITION | PARENTS)
                     + " tok animating="
@@ -4898,7 +4930,8 @@
         // animation on the keyguard but seeing the IME window that originally on the app
         // which behinds the keyguard.
         final WindowState imeInputTarget = getImeInputTarget();
-        if (imeInputTarget != null && !(imeInputTarget.isDrawn() || imeInputTarget.isVisible())) {
+        if (imeInputTarget != null
+                && !(imeInputTarget.isDrawn() || imeInputTarget.isVisibleRequested())) {
             return false;
         }
         return mDisplayContent.forAllImeWindows(callback, traverseTopToBottom);
@@ -5165,7 +5198,7 @@
                         + " pv=" + isVisibleByPolicy()
                         + " mDrawState=" + mWinAnimator.mDrawState
                         + " ph=" + isParentWindowHidden()
-                        + " th=" + (mActivityRecord != null && mActivityRecord.mVisibleRequested)
+                        + " th=" + (mActivityRecord != null && mActivityRecord.isVisibleRequested())
                         + " a=" + isAnimating(TRANSITION | PARENTS));
             }
         }
@@ -5476,18 +5509,12 @@
                     mFrameRateSelectionPriority);
         }
 
-        // If refresh rate switching is disabled there is no point to set the frame rate on the
-        // surface as the refresh rate will be limited by display manager to a single value
-        // and SurfaceFlinger wouldn't be able to change it anyways.
-        if (mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType()
-                != SWITCHING_TYPE_NONE) {
-            final float refreshRate = refreshRatePolicy.getPreferredRefreshRate(this);
-            if (mAppPreferredFrameRate != refreshRate) {
-                mAppPreferredFrameRate = refreshRate;
-                getPendingTransaction().setFrameRate(
-                        mSurfaceControl, mAppPreferredFrameRate,
-                        Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
-            }
+        boolean voteChanged = refreshRatePolicy.updateFrameRateVote(this);
+        if (voteChanged) {
+            getPendingTransaction().setFrameRate(
+                    mSurfaceControl, mFrameRateVote.mRefreshRate,
+                    mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
+
         }
     }
 
@@ -5713,6 +5740,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.
@@ -6002,7 +6038,7 @@
             final long duration =
                     SystemClock.elapsedRealtime() - mActivityRecord.mRelaunchStartTime;
             Slog.i(TAG, "finishDrawing of relaunch: " + this + " " + duration + "ms");
-            mActivityRecord.mRelaunchStartTime = 0;
+            mActivityRecord.finishOrAbortReplacingWindow();
         }
         if (mActivityRecord != null && mAttrs.type == TYPE_APPLICATION_STARTING) {
             mWmService.mAtmService.mTaskSupervisor.getActivityMetricsLogger()
@@ -6094,10 +6130,10 @@
         if (mRedrawForSyncReported) {
             return false;
         }
-        // TODO(b/233286785): Remove mIsWallpaper once WallpaperService handles syncId of relayout.
-        if (mInRelayout && !mIsWallpaper) {
-            // The last sync seq id will return to the client, so there is no need to request the
-            // client to redraw.
+        if (mInRelayout && (mPrepareSyncSeqId > 0 || (mViewVisibility == View.VISIBLE
+                && mWinAnimator.mDrawState == DRAW_PENDING))) {
+            // The client will report draw if it gets the sync seq id from relayout or it is
+            // drawing for being visible, then no need to request redraw.
             return false;
         }
         return useBLASTSync();
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/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 7c481f5..f2527b6 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -258,7 +258,7 @@
      * @return The scale for applications running in compatibility mode. Multiply the size in the
      *         application by this scale will be the size in the screen.
      */
-    float getSizeCompatScale() {
+    float getCompatScale() {
         return mDisplayContent.mCompatibleScreenScale;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index efcca5d..416d042 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -355,7 +355,7 @@
             ProtoOutputStream proto = new ProtoOutputStream();
             proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
             long timeOffsetNs =
-                    TimeUnit.NANOSECONDS.convert(System.currentTimeMillis(), TimeUnit.NANOSECONDS)
+                    TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
                     - SystemClock.elapsedRealtimeNanos();
             proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
             mBuffer.writeTraceToFile(mTraceFile, proto);
diff --git a/services/core/java/com/android/server/wm/utils/StateMachine.java b/services/core/java/com/android/server/wm/utils/StateMachine.java
new file mode 100644
index 0000000..91a5dc4
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/StateMachine.java
@@ -0,0 +1,280 @@
+/*
+ * 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.wm.utils;
+
+import android.annotation.IntRange;
+import android.annotation.Nullable;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/**
+ * Simple hierarchical state machine.
+ *
+ * The state is represented by an integer value. The root state has a value {@code 0x0}, and top
+ * level state has a value in range {@code 0x1} to {@code 0xF}. To indicate a state B is a sub state
+ * of a state A, assign an integer state_value(B) = state_value(A) << 4 + (0x0 .. 0xF).
+ */
+public class StateMachine {
+    private static final String TAG = "StateMachine";
+
+    /**
+     * Interface for implementing state specific actions.
+     */
+    public interface Handler {
+        /**
+         * Called when state machine changes its state to this state.
+         */
+        default void enter() {}
+
+        /**
+         * Called when state machine changes its state from this state to other state.
+         */
+        default void exit() {}
+
+        /**
+         * @param event type of this event.
+         * @param param parameter passed to {@link StateMachine#handle(int, Object)}
+         * @return {@code true} if the event was handled in this handler, so we don't need to
+         *          check the parent state. Otherwise, handle() of the parent state is triggered.
+         */
+        default boolean handle(int event, @Nullable Object param) {
+            return false;
+        }
+    }
+
+    /**
+     * The most recent state requested by transit() call.
+     *
+     * @note When transit() is called recursively, this might not be same value as mState until
+     *       transit() finishes.
+     */
+    private int mLastRequestedState;
+
+    /**
+     * The current state of this state machine.
+     */
+    private int mState;
+
+    private final IntArray mTmp = new IntArray();
+    private final SparseArray<Handler> mStateHandlers = new SparseArray<>();
+
+    /**
+     * Actions which need to execute to finish requested transition.
+     */
+    private final Queue<Command> mCommands = new ArrayDeque<>();
+
+    protected static class Command {
+        static final int COMMIT = 1;
+        static final int ENTER = 2;
+        static final int EXIT = 3;
+
+        final int mType;
+        final int mState;
+
+        private Command(int type, @IntRange(from = 0) int state) {
+            mType = type;
+            AnnotationValidations.validate(IntRange.class, null, state, "from", 0);
+            mState = state;
+        }
+
+        static Command newCommit(int state) {
+            return new Command(COMMIT, state);
+        }
+
+        static Command newEnter(int state) {
+            return new Command(ENTER, state);
+        }
+
+        static Command newExit(int state) {
+            return new Command(EXIT, state);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("Command{ type: ");
+            switch (mType) {
+                case COMMIT:
+                    sb.append("commit");
+                    break;
+                case ENTER:
+                    sb.append("enter");
+                    break;
+                case EXIT:
+                    sb.append("exit");
+                    break;
+                default:
+                    sb.append("UNKNOWN(");
+                    sb.append(mType);
+                    sb.append(")");
+                    break;
+            }
+            sb.append(" state: ");
+            sb.append(Integer.toHexString(mState));
+            sb.append(" }");
+            return sb.toString();
+        }
+    }
+
+    public StateMachine() {
+        this(0);
+    }
+
+    public StateMachine(@IntRange(from = 0) int initialState) {
+        mState = initialState;
+        AnnotationValidations.validate(IntRange.class, null, initialState, "from", 0);
+        mLastRequestedState = initialState;
+    }
+
+    /**
+     * @see #mLastRequestedState
+     */
+    public int getState() {
+        return mLastRequestedState;
+    }
+
+    protected int getCurrentState() {
+        return mState;
+    }
+
+    protected Command[] getCommands() {
+        final Command[] commands = new Command[mCommands.size()];
+        mCommands.toArray(commands);
+        return commands;
+    }
+
+    /**
+     * Add a handler for a specific state.
+     *
+     * @param state State which the given handler processes.
+     * @param handler A handler which runs entry, exit actions and processes events.
+     * @return Previous state handler if it's already registered, or {@code null}.
+     */
+    @Nullable public Handler addStateHandler(int state, @Nullable Handler handler) {
+        final Handler handlerOld = mStateHandlers.get(state);
+        mStateHandlers.put(state, handler);
+        return handlerOld;
+    }
+
+    /**
+     * Process an event. Search handler for a given event and {@link Handler#handle(int)}. If the
+     * handler cannot handle the event, delegate it to a handler for a parent of the given state.
+     *
+     * @param event Type of an event.
+     */
+    public void handle(int event, @Nullable Object param) {
+        int state = mState;
+        while (state != 0) {
+            final Handler h = mStateHandlers.get(state);
+            if (h != null && h.handle(event, param)) {
+                return;
+            }
+            state >>= 4;
+        }
+    }
+
+    protected void enter(@IntRange(from = 0) int state) {
+        AnnotationValidations.validate(IntRange.class, null, state, "from", 0);
+        final Handler h = mStateHandlers.get(state);
+        if (h != null) {
+            h.enter();
+        }
+    }
+
+    protected void exit(@IntRange(from = 0) int state) {
+        AnnotationValidations.validate(IntRange.class, null, state, "from", 0);
+        final Handler h = mStateHandlers.get(state);
+        if (h != null) {
+            h.exit();
+        }
+    }
+
+    /**
+     * @return {@code true} if a given sub state is a descendant of a given super state.
+     */
+    public static boolean isIn(int subState, int superState) {
+        while (subState > superState) {
+            subState >>= 4;
+        }
+        return subState == superState;
+    }
+
+    /**
+     * Check if the last requested state is a sub state of a given state.
+     *
+     * @return {@code true} if the last requested state (via {@link #transit(int)}) is a sub state
+     *         of a given state.
+     */
+    public boolean isIn(int state) {
+        return isIn(mLastRequestedState, state);
+    }
+
+    /**
+     * Change state to the requested state.
+     *
+     * @param newState The new state that the state machine should be changed.
+     */
+    public void transit(@IntRange(from = 0) int newState) {
+        AnnotationValidations.validate(IntRange.class, null, newState, "from", 0);
+
+        // entry and exit action might start another transition, so this transit() function can be
+        // called recursively. In order to guarantee entry and exit actions in expected order,
+        // we first compute the sequence and push them into a queue, then process them later.
+        mCommands.add(Command.newCommit(newState));
+        if (mLastRequestedState == newState) {
+            mCommands.add(Command.newExit(newState));
+            mCommands.add(Command.newEnter(newState));
+        } else {
+            // mLastRequestedState to least common ancestor
+            for (int s = mLastRequestedState; !isIn(newState, s); s >>= 4) {
+                mCommands.add(Command.newExit(s));
+            }
+            // least common ancestor to newState
+            mTmp.clear();
+            for (int s = newState; !isIn(mLastRequestedState, s); s >>= 4) {
+                mTmp.add(s);
+            }
+            for (int i = mTmp.size() - 1; i >= 0; --i) {
+                mCommands.add(Command.newEnter(mTmp.get(i)));
+            }
+        }
+        mLastRequestedState = newState;
+        while (!mCommands.isEmpty()) {
+            final Command cmd = mCommands.remove();
+            switch (cmd.mType) {
+                case Command.EXIT:
+                    exit(cmd.mState);
+                    break;
+                case Command.ENTER:
+                    enter(cmd.mState);
+                    break;
+                case Command.COMMIT:
+                    mState = cmd.mState;
+                    break;
+                default:
+                    Slog.e(TAG, "Unknown command type: " + cmd.mType);
+                    break;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/utils/TEST_MAPPING b/services/core/java/com/android/server/wm/utils/TEST_MAPPING
new file mode 100644
index 0000000..aa69d2a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "WmTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.wm.utils"
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 5fa8dcc..1bcf4d8 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -31,6 +31,9 @@
         "BroadcastRadio/convert.cpp",
         "BroadcastRadio/regions.cpp",
         "stats/SurfaceFlingerPuller.cpp",
+        "tvinput/BufferProducerThread.cpp",
+        "tvinput/JTvInputHal.cpp",
+        "tvinput/TvInputHal_hidl.cpp",
         "com_android_server_adb_AdbDebuggingManager.cpp",
         "com_android_server_am_BatteryStatsService.cpp",
         "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
@@ -68,6 +71,7 @@
         "com_android_server_PersistentDataBlockService.cpp",
         "com_android_server_am_LowMemDetector.cpp",
         "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
+        "com_android_server_pm_Settings.cpp",
         "com_android_server_sensor_SensorService.cpp",
         "com_android_server_wm_TaskFpsCallbackController.cpp",
         "onload.cpp",
@@ -135,6 +139,7 @@
         "libschedulerservicehidl",
         "libsensorservice",
         "libsensorservicehidl",
+        "libsensorserviceaidl",
         "libgui",
         "libtimestats_atoms_proto",
         "libusbhost",
@@ -148,11 +153,13 @@
         "libpsi",
         "libdataloader",
         "libincfs",
+        "liblz4",
         "android.hardware.audio.common@2.0",
+        "android.media.audio.common.types-V1-ndk",
         "android.hardware.broadcastradio@1.0",
         "android.hardware.broadcastradio@1.1",
         "android.hardware.contexthub@1.0",
-        "android.hardware.gnss-V2-cpp",
+        "android.hardware.gnss-V3-cpp",
         "android.hardware.gnss@1.0",
         "android.hardware.gnss@1.1",
         "android.hardware.gnss@2.0",
@@ -162,7 +169,7 @@
         "android.hardware.graphics.bufferqueue@2.0",
         "android.hardware.graphics.common@1.2",
         "android.hardware.graphics.mapper@4.0",
-        "android.hardware.input.processor-V1-ndk",
+        "android.hardware.input.processor-V2-ndk",
         "android.hardware.ir@1.0",
         "android.hardware.light@2.0",
         "android.hardware.memtrack-V1-ndk",
@@ -170,11 +177,12 @@
         "android.hardware.power@1.1",
         "android.hardware.power@1.2",
         "android.hardware.power@1.3",
-        "android.hardware.power-V3-cpp",
+        "android.hardware.power-V4-cpp",
         "android.hardware.power.stats@1.0",
         "android.hardware.power.stats-V1-ndk",
         "android.hardware.thermal@1.0",
         "android.hardware.tv.input@1.0",
+        "android.hardware.tv.input-V1-ndk",
         "android.hardware.vibrator-V2-cpp",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
@@ -184,6 +192,7 @@
         "android.hidl.token@1.0-utils",
         "android.frameworks.schedulerservice@1.0",
         "android.frameworks.sensorservice@1.0",
+        "android.frameworks.sensorservice-V1-ndk",
         "android.frameworks.stats@1.0",
         "android.frameworks.stats-V2-ndk",
         "android.system.suspend.control-V1-cpp",
@@ -195,6 +204,7 @@
 
     static_libs: [
         "android.hardware.broadcastradio@common-utils-1x-lib",
+        "libaidlcommonsupport",
     ],
 
     product_variables: {
@@ -224,3 +234,26 @@
         "com_android_server_app_GameManagerService.cpp",
     ],
 }
+
+// Settings JNI library for unit tests.
+cc_library_shared {
+    name: "libservices.core.settings.testonly",
+    defaults: ["libservices.core-libs"],
+
+    cpp_std: "c++2a",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+        "-Wthread-safety",
+    ],
+
+    srcs: [
+        "com_android_server_pm_Settings.cpp",
+        "onload_settings.cpp",
+    ],
+
+    header_libs: [
+        "bionic_libc_platform_headers",
+    ],
+}
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index b171a07..be18f64 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -14,35 +14,31 @@
  * limitations under the License.
  */
 
-#include <dlfcn.h>
-#include <pthread.h>
-
-#include <chrono>
-#include <thread>
-
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-
+#include <android-base/properties.h>
 #include <android/binder_manager.h>
 #include <android/binder_stability.h>
 #include <android/hidl/manager/1.2/IServiceManager.h>
 #include <binder/IServiceManager.h>
+#include <bionic/malloc.h>
+#include <bionic/reserved_signals.h>
+#include <dlfcn.h>
 #include <hidl/HidlTransportSupport.h>
 #include <incremental_service.h>
-
+#include <jni.h>
 #include <memtrackproxy/MemtrackProxy.h>
+#include <nativehelper/JNIHelp.h>
+#include <pthread.h>
 #include <schedulerservice/SchedulingPolicyService.h>
+#include <sensorserviceaidl/SensorManagerAidl.h>
 #include <sensorservicehidl/SensorManager.h>
 #include <stats/StatsAidl.h>
 #include <stats/StatsHal.h>
-
-#include <bionic/malloc.h>
-#include <bionic/reserved_signals.h>
-
-#include <android-base/properties.h>
+#include <utils/AndroidThreads.h>
 #include <utils/Log.h>
 #include <utils/misc.h>
-#include <utils/AndroidThreads.h>
+
+#include <chrono>
+#include <thread>
 
 using namespace std::chrono_literals;
 
@@ -57,7 +53,9 @@
     const std::string instance = std::string() + IStats::descriptor + "/default";
     const binder_exception_t err =
             AServiceManager_addService(statsService->asBinder().get(), instance.c_str());
-    LOG_ALWAYS_FATAL_IF(err != EX_NONE, "Cannot register AIDL %s: %d", instance.c_str(), err);
+    if (err != EX_NONE) {
+        ALOGW("Cannot register AIDL %s: %d", instance.c_str(), err);
+    }
 }
 
 static void startStatsHidlService() {
@@ -69,6 +67,42 @@
     ALOGW_IF(err != android::OK, "Cannot register HIDL %s: %d", IStats::descriptor, err);
 }
 
+static void startSensorManagerAidlService(JNIEnv* env) {
+    using ::aidl::android::frameworks::sensorservice::ISensorManager;
+    using ::android::frameworks::sensorservice::implementation::SensorManagerAidl;
+
+    JavaVM* vm;
+    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Cannot get Java VM");
+
+    std::shared_ptr<SensorManagerAidl> sensorService =
+            ndk::SharedRefBase::make<SensorManagerAidl>(vm);
+    const std::string instance = std::string() + ISensorManager::descriptor + "/default";
+    const binder_exception_t err =
+            AServiceManager_addService(sensorService->asBinder().get(), instance.c_str());
+    LOG_ALWAYS_FATAL_IF(err != EX_NONE, "Cannot register AIDL %s: %d", instance.c_str(), err);
+}
+
+static void startSensorManagerHidlService(JNIEnv* env) {
+    using ::android::frameworks::sensorservice::V1_0::ISensorManager;
+    using ::android::frameworks::sensorservice::V1_0::implementation::SensorManager;
+    using ::android::hardware::configureRpcThreadpool;
+    using ::android::hidl::manager::V1_0::IServiceManager;
+
+    JavaVM* vm;
+    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Cannot get Java VM");
+
+    android::sp<ISensorManager> sensorService = new SensorManager(vm);
+    if (IServiceManager::Transport::HWBINDER ==
+        android::hardware::defaultServiceManager1_2()->getTransport(ISensorManager::descriptor,
+                                                                    "default")) {
+        android::status_t err = sensorService->registerAsService();
+        LOG_ALWAYS_FATAL_IF(err != android::OK, "Cannot register %s: %d",
+                            ISensorManager::descriptor, err);
+    } else {
+        ALOGW("%s is deprecated. Skipping registration.", ISensorManager::descriptor);
+    }
+}
+
 } // namespace
 
 namespace android {
@@ -78,6 +112,12 @@
     startStatsAidlService();
 }
 
+static void android_server_SystemServer_startISensorManagerService(JNIEnv* env,
+                                                                   jobject /* clazz */) {
+    startSensorManagerHidlService(env);
+    startSensorManagerAidlService(env);
+}
+
 static void android_server_SystemServer_startMemtrackProxyService(JNIEnv* env,
                                                                   jobject /* clazz */) {
     using aidl::android::hardware::memtrack::MemtrackProxy;
@@ -93,35 +133,19 @@
     LOG_ALWAYS_FATAL_IF(err != EX_NONE, "Cannot register %s: %d", memtrackProxyService, err);
 }
 
-static void android_server_SystemServer_startHidlServices(JNIEnv* env, jobject /* clazz */) {
+static void android_server_SystemServer_startHidlServices(JNIEnv* /* env */, jobject /* clazz */) {
     using ::android::frameworks::schedulerservice::V1_0::ISchedulingPolicyService;
     using ::android::frameworks::schedulerservice::V1_0::implementation::SchedulingPolicyService;
-    using ::android::frameworks::sensorservice::V1_0::ISensorManager;
-    using ::android::frameworks::sensorservice::V1_0::implementation::SensorManager;
     using ::android::hardware::configureRpcThreadpool;
     using ::android::hidl::manager::V1_0::IServiceManager;
 
-    status_t err;
-
     configureRpcThreadpool(5, false /* callerWillJoin */);
 
-    JavaVM *vm;
-    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Cannot get Java VM");
-
-    sp<ISensorManager> sensorService = new SensorManager(vm);
-    if (IServiceManager::Transport::HWBINDER ==
-        hardware::defaultServiceManager1_2()->getTransport(ISensorManager::descriptor, "default")) {
-        err = sensorService->registerAsService();
-        LOG_ALWAYS_FATAL_IF(err != OK, "Cannot register %s: %d", ISensorManager::descriptor, err);
-    } else {
-        ALOGW("%s is deprecated. Skipping registration.", ISensorManager::descriptor);
-    }
-
     sp<ISchedulingPolicyService> schedulingService = new SchedulingPolicyService();
     if (IServiceManager::Transport::HWBINDER ==
         hardware::defaultServiceManager1_2()->getTransport(ISchedulingPolicyService::descriptor,
                                                            "default")) {
-        err = schedulingService->registerAsService("default");
+        status_t err = schedulingService->registerAsService("default");
         LOG_ALWAYS_FATAL_IF(err != OK, "Cannot register %s: %d",
                             ISchedulingPolicyService::descriptor, err);
     } else {
@@ -156,6 +180,8 @@
 static const JNINativeMethod gMethods[] = {
         /* name, signature, funcPtr */
         {"startIStatsService", "()V", (void*)android_server_SystemServer_startIStatsService},
+        {"startISensorManagerService", "()V",
+         (void*)android_server_SystemServer_startISensorManagerService},
         {"startMemtrackProxyService", "()V",
          (void*)android_server_SystemServer_startMemtrackProxyService},
         {"startHidlServices", "()V", (void*)android_server_SystemServer_startHidlServices},
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index 16eaa77..3678ced 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -98,7 +98,7 @@
 public:
     binder::Status notifyWakeup(bool success,
                                 const std::vector<std::string>& wakeupReasons) override {
-        ALOGI("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted");
+        ALOGV("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted");
         bool reasonsCaptured = false;
         {
             std::unique_lock<std::mutex> reasonsLock(mReasonsMutex, std::defer_lock);
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index 000cb83..d975760 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -34,6 +34,7 @@
 #include "jni.h"
 
 using android::hardware::power::IPowerHintSession;
+using android::hardware::power::SessionHint;
 using android::hardware::power::WorkDuration;
 
 using android::base::StringPrintf;
@@ -81,6 +82,11 @@
     appSession->reportActualWorkDuration(actualDurations);
 }
 
+static void sendHint(int64_t session_ptr, SessionHint hint) {
+    sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    appSession->sendHint(hint);
+}
+
 static int64_t getHintSessionPreferredRate() {
     int64_t rate = -1;
     auto result = gPowerHalController.getHintSessionPreferredRate();
@@ -139,6 +145,10 @@
     reportActualWorkDuration(session_ptr, actualList);
 }
 
+static void nativeSendHint(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint hint) {
+    sendHint(session_ptr, static_cast<SessionHint>(hint));
+}
+
 static jlong nativeGetHintSessionPreferredRate(JNIEnv* /* env */, jclass /* clazz */) {
     return static_cast<jlong>(getHintSessionPreferredRate());
 }
@@ -153,6 +163,7 @@
         {"nativeCloseHintSession", "(J)V", (void*)nativeCloseHintSession},
         {"nativeUpdateTargetWorkDuration", "(JJ)V", (void*)nativeUpdateTargetWorkDuration},
         {"nativeReportActualWorkDuration", "(J[J[J)V", (void*)nativeReportActualWorkDuration},
+        {"nativeSendHint", "(JI)V", (void*)nativeSendHint},
         {"nativeGetHintSessionPreferredRate", "()J", (void*)nativeGetHintSessionPreferredRate},
 };
 
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 0d87237..969056e 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -260,10 +260,9 @@
 
 // --- NativeInputManager ---
 
-class NativeInputManager : public virtual RefBase,
-    public virtual InputReaderPolicyInterface,
-    public virtual InputDispatcherPolicyInterface,
-    public virtual PointerControllerPolicyInterface {
+class NativeInputManager : public virtual InputReaderPolicyInterface,
+                           public virtual InputDispatcherPolicyInterface,
+                           public virtual PointerControllerPolicyInterface {
 protected:
     virtual ~NativeInputManager();
 
@@ -311,7 +310,7 @@
             const InputDeviceIdentifier& identifier) override;
     std::string getDeviceAlias(const InputDeviceIdentifier& identifier) override;
     TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
-                                                           int32_t surfaceRotation) override;
+                                                           ui::Rotation surfaceRotation) override;
 
     TouchAffineTransformation getTouchAffineTransformation(JNIEnv* env, jfloatArray matrixArr);
     void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override;
@@ -450,6 +449,10 @@
         dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
                              mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled",
                              mLocked.pointerCaptureRequest.seq);
+        auto pointerController = mLocked.pointerController.lock();
+        if (pointerController != nullptr) {
+            pointerController->dump(dump);
+        }
     }
     dump += "\n";
 
@@ -1150,7 +1153,7 @@
 }
 
 TouchAffineTransformation NativeInputManager::getTouchAffineTransformation(
-        const std::string& inputDeviceDescriptor, int32_t surfaceRotation) {
+        const std::string& inputDeviceDescriptor, ui::Rotation surfaceRotation) {
     JNIEnv* env = jniEnv();
 
     ScopedLocalRef<jstring> descriptorObj(env, env->NewStringUTF(inputDeviceDescriptor.c_str()));
@@ -1188,8 +1191,9 @@
                 static_cast<const KeyEvent*>(inputEvent));
         break;
     case AINPUT_EVENT_TYPE_MOTION:
-        inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
-                static_cast<const MotionEvent*>(inputEvent));
+        inputEventObj =
+                android_view_MotionEvent_obtainAsCopy(env,
+                                                      static_cast<const MotionEvent&>(*inputEvent));
         break;
     default:
         return true; // dispatch the event normally
@@ -1516,8 +1520,14 @@
         return 0;
     }
 
-    NativeInputManager* im = new NativeInputManager(serviceObj, messageQueue->getLooper());
-    im->incStrong(0);
+    static std::once_flag nativeInitialize;
+    NativeInputManager* im = nullptr;
+    std::call_once(nativeInitialize, [&]() {
+        // Create the NativeInputManager, which should not be destroyed or deallocated for the
+        // lifetime of the process.
+        im = new NativeInputManager(serviceObj, messageQueue->getLooper());
+    });
+    LOG_ALWAYS_FATAL_IF(im == nullptr, "NativeInputManager was already initialized.");
     return reinterpret_cast<jlong>(im);
 }
 
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 9fa23c2..e1de05c 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -482,6 +482,17 @@
     agnssRilIface->setSetId(type, setid_string);
 }
 
+static void android_location_gnss_hal_GnssNative_inject_ni_supl_message_data(JNIEnv* env, jclass,
+                                                                             jbyteArray data,
+                                                                             jint length,
+                                                                             jint slotIndex) {
+    if (agnssRilIface == nullptr) {
+        ALOGE("%s: IAGnssRil interface not available.", __func__);
+        return;
+    }
+    agnssRilIface->injectNiSuplMessageData(data, length, slotIndex);
+}
+
 static jint android_location_gnss_hal_GnssNative_read_nmea(JNIEnv* env, jclass,
                                                            jbyteArray nmeaArray, jint buffer_size) {
     return gnssHal->readNmea(nmeaArray, buffer_size);
@@ -974,6 +985,8 @@
                  android_location_gnss_hal_GnssNative_agps_set_reference_location_cellid)},
         {"native_set_agps_server", "(ILjava/lang/String;I)V",
          reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_set_agps_server)},
+        {"native_inject_ni_supl_message_data", "([BII)V",
+         reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_ni_supl_message_data)},
         {"native_send_ni_response", "(II)V",
          reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_send_ni_response)},
         {"native_get_internal_state", "()Ljava/lang/String;",
diff --git a/services/core/jni/com_android_server_pm_Settings.cpp b/services/core/jni/com_android_server_pm_Settings.cpp
new file mode 100644
index 0000000..9633a11
--- /dev/null
+++ b/services/core/jni/com_android_server_pm_Settings.cpp
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_ADB
+#define LOG_TAG "Settings-jni"
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/no_destructor.h>
+#include <core_jni_helpers.h>
+#include <lz4frame.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <vector>
+
+namespace android {
+
+namespace {
+
+struct LZ4FCContextDeleter {
+    void operator()(LZ4F_cctx* cctx) { LZ4F_freeCompressionContext(cctx); }
+};
+
+static constexpr int LZ4_BUFFER_SIZE = 64 * 1024;
+
+static bool writeToFile(std::vector<char>& outBuffer, int fdOut) {
+    if (!android::base::WriteFully(fdOut, outBuffer.data(), outBuffer.size())) {
+        PLOG(ERROR) << "Error to write to output file";
+        return false;
+    }
+    outBuffer.clear();
+    return true;
+}
+
+static bool compressAndWriteLz4(LZ4F_cctx* context, std::vector<char>& inBuffer,
+                                std::vector<char>& outBuffer, int fdOut) {
+    auto inSize = inBuffer.size();
+    if (inSize > 0) {
+        auto prvSize = outBuffer.size();
+        auto outSize = LZ4F_compressBound(inSize, nullptr);
+        outBuffer.resize(prvSize + outSize);
+        auto rc = LZ4F_compressUpdate(context, outBuffer.data() + prvSize, outSize, inBuffer.data(),
+                                      inSize, nullptr);
+        if (LZ4F_isError(rc)) {
+            LOG(ERROR) << "LZ4F_compressUpdate failed: " << LZ4F_getErrorName(rc);
+            return false;
+        }
+        outBuffer.resize(prvSize + rc);
+    }
+
+    if (outBuffer.size() > LZ4_BUFFER_SIZE) {
+        return writeToFile(outBuffer, fdOut);
+    }
+
+    return true;
+}
+
+static jboolean nativeCompressLz4(JNIEnv* env, jclass klass, jint fdIn, jint fdOut) {
+    LZ4F_cctx* cctx;
+    if (LZ4F_createCompressionContext(&cctx, LZ4F_VERSION) != 0) {
+        LOG(ERROR) << "Failed to initialize LZ4 compression context.";
+        return false;
+    }
+    std::unique_ptr<LZ4F_cctx, LZ4FCContextDeleter> context(cctx);
+
+    std::vector<char> inBuffer, outBuffer;
+    inBuffer.reserve(LZ4_BUFFER_SIZE);
+    outBuffer.reserve(2 * LZ4_BUFFER_SIZE);
+
+    LZ4F_preferences_t prefs;
+
+    memset(&prefs, 0, sizeof(prefs));
+
+    // Set compression parameters.
+    prefs.autoFlush = 0;
+    prefs.compressionLevel = 0;
+    prefs.frameInfo.blockMode = LZ4F_blockLinked;
+    prefs.frameInfo.blockSizeID = LZ4F_default;
+    prefs.frameInfo.blockChecksumFlag = LZ4F_noBlockChecksum;
+    prefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
+    prefs.favorDecSpeed = 0;
+
+    struct stat sb;
+    if (fstat(fdIn, &sb) == -1) {
+        PLOG(ERROR) << "Failed to obtain input file size.";
+        return false;
+    }
+    prefs.frameInfo.contentSize = sb.st_size;
+
+    // Write header first.
+    outBuffer.resize(LZ4F_HEADER_SIZE_MAX);
+    auto rc = LZ4F_compressBegin(context.get(), outBuffer.data(), outBuffer.size(), &prefs);
+    if (LZ4F_isError(rc)) {
+        LOG(ERROR) << "LZ4F_compressBegin failed: " << LZ4F_getErrorName(rc);
+        return false;
+    }
+    outBuffer.resize(rc);
+
+    bool eof = false;
+    while (!eof) {
+        constexpr auto capacity = LZ4_BUFFER_SIZE;
+        inBuffer.resize(capacity);
+        auto read = TEMP_FAILURE_RETRY(::read(fdIn, inBuffer.data(), inBuffer.size()));
+        if (read < 0) {
+            PLOG(ERROR) << "Failed to read from input file.";
+            return false;
+        }
+
+        inBuffer.resize(read);
+
+        if (read == 0) {
+            eof = true;
+        }
+
+        if (!compressAndWriteLz4(context.get(), inBuffer, outBuffer, fdOut)) {
+            return false;
+        }
+    }
+
+    // Footer.
+    auto prvSize = outBuffer.size();
+    outBuffer.resize(outBuffer.capacity());
+    rc = LZ4F_compressEnd(context.get(), outBuffer.data() + prvSize, outBuffer.size() - prvSize,
+                          nullptr);
+    if (LZ4F_isError(rc)) {
+        LOG(ERROR) << "LZ4F_compressEnd failed: " << LZ4F_getErrorName(rc);
+        return false;
+    }
+    outBuffer.resize(prvSize + rc);
+
+    if (!writeToFile(outBuffer, fdOut)) {
+        return false;
+    }
+
+    return true;
+}
+
+static const JNINativeMethod method_table[] = {
+        {"nativeCompressLz4", "(II)Z", (void*)nativeCompressLz4},
+};
+
+} // namespace
+
+int register_android_server_com_android_server_pm_Settings(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "com/android/server/pm/Settings", method_table,
+                                    NELEM(method_table));
+}
+
+} // namespace android
diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp
index 63b7dfb..10d8b42 100644
--- a/services/core/jni/com_android_server_sensor_SensorService.cpp
+++ b/services/core/jni/com_android_server_sensor_SensorService.cpp
@@ -22,6 +22,7 @@
 #include <cutils/properties.h>
 #include <jni.h>
 #include <sensorservice/SensorService.h>
+#include <string.h>
 #include <utils/Log.h>
 #include <utils/misc.h>
 
@@ -30,10 +31,14 @@
 #define PROXIMITY_ACTIVE_CLASS \
     "com/android/server/sensors/SensorManagerInternal$ProximityActiveListener"
 
+#define RUNTIME_SENSOR_CALLBACK_CLASS \
+    "com/android/server/sensors/SensorManagerInternal$RuntimeSensorStateChangeCallback"
+
 namespace android {
 
 static JavaVM* sJvm = nullptr;
 static jmethodID sMethodIdOnProximityActive;
+static jmethodID sMethodIdOnStateChanged;
 
 class NativeSensorService {
 public:
@@ -41,6 +46,11 @@
 
     void registerProximityActiveListener();
     void unregisterProximityActiveListener();
+    jint registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name, jstring vendor,
+                               jobject callback);
+    void unregisterRuntimeSensor(jint handle);
+    jboolean sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, jlong timestamp,
+                                    jfloatArray values);
 
 private:
     sp<SensorService> mService;
@@ -56,6 +66,18 @@
         jobject mListener;
     };
     sp<ProximityActiveListenerDelegate> mProximityActiveListenerDelegate;
+
+    class RuntimeSensorCallbackDelegate : public SensorService::RuntimeSensorStateChangeCallback {
+    public:
+        RuntimeSensorCallbackDelegate(JNIEnv* env, jobject callback);
+        ~RuntimeSensorCallbackDelegate();
+
+        void onStateChanged(bool enabled, int64_t samplingPeriodNs,
+                            int64_t batchReportLatencyNs) override;
+
+    private:
+        jobject mCallback;
+    };
 };
 
 NativeSensorService::NativeSensorService(JNIEnv* env, jobject listener)
@@ -85,6 +107,109 @@
     mService->removeProximityActiveListener(mProximityActiveListenerDelegate);
 }
 
+jint NativeSensorService::registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name,
+                                                jstring vendor, jobject callback) {
+    if (mService == nullptr) {
+        ALOGD("Dropping registerRuntimeSensor, sensor service not available.");
+        return -1;
+    }
+
+    sensor_t sensor{
+            .name = env->GetStringUTFChars(name, 0),
+            .vendor = env->GetStringUTFChars(vendor, 0),
+            .version = sizeof(sensor_t),
+            .type = type,
+    };
+
+    sp<RuntimeSensorCallbackDelegate> callbackDelegate(
+            new RuntimeSensorCallbackDelegate(env, callback));
+    return mService->registerRuntimeSensor(sensor, deviceId, callbackDelegate);
+}
+
+void NativeSensorService::unregisterRuntimeSensor(jint handle) {
+    if (mService == nullptr) {
+        ALOGD("Dropping unregisterProximityActiveListener, sensor service not available.");
+        return;
+    }
+
+    mService->unregisterRuntimeSensor(handle);
+}
+
+jboolean NativeSensorService::sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type,
+                                                     jlong timestamp, jfloatArray values) {
+    if (mService == nullptr) {
+        ALOGD("Dropping sendRuntimeSensorEvent, sensor service not available.");
+        return false;
+    }
+    if (values == nullptr) {
+        ALOGD("Dropping sendRuntimeSensorEvent, no values.");
+        return false;
+    }
+
+    sensors_event_t event{
+            .version = sizeof(sensors_event_t),
+            .timestamp = timestamp,
+            .sensor = handle,
+            .type = type,
+    };
+
+    int valuesLength = env->GetArrayLength(values);
+    jfloat* sensorValues = env->GetFloatArrayElements(values, nullptr);
+
+    switch (type) {
+        case SENSOR_TYPE_ACCELEROMETER:
+        case SENSOR_TYPE_MAGNETIC_FIELD:
+        case SENSOR_TYPE_ORIENTATION:
+        case SENSOR_TYPE_GYROSCOPE:
+        case SENSOR_TYPE_GRAVITY:
+        case SENSOR_TYPE_LINEAR_ACCELERATION: {
+            if (valuesLength != 3) {
+                ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values.");
+                return false;
+            }
+            event.acceleration.x = sensorValues[0];
+            event.acceleration.y = sensorValues[1];
+            event.acceleration.z = sensorValues[2];
+            break;
+        }
+        case SENSOR_TYPE_DEVICE_ORIENTATION:
+        case SENSOR_TYPE_LIGHT:
+        case SENSOR_TYPE_PRESSURE:
+        case SENSOR_TYPE_TEMPERATURE:
+        case SENSOR_TYPE_PROXIMITY:
+        case SENSOR_TYPE_RELATIVE_HUMIDITY:
+        case SENSOR_TYPE_AMBIENT_TEMPERATURE:
+        case SENSOR_TYPE_SIGNIFICANT_MOTION:
+        case SENSOR_TYPE_STEP_DETECTOR:
+        case SENSOR_TYPE_TILT_DETECTOR:
+        case SENSOR_TYPE_WAKE_GESTURE:
+        case SENSOR_TYPE_GLANCE_GESTURE:
+        case SENSOR_TYPE_PICK_UP_GESTURE:
+        case SENSOR_TYPE_WRIST_TILT_GESTURE:
+        case SENSOR_TYPE_STATIONARY_DETECT:
+        case SENSOR_TYPE_MOTION_DETECT:
+        case SENSOR_TYPE_HEART_BEAT:
+        case SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT: {
+            if (valuesLength != 1) {
+                ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values.");
+                return false;
+            }
+            event.data[0] = sensorValues[0];
+            break;
+        }
+        default: {
+            if (valuesLength > 16) {
+                ALOGD("Dropping sendRuntimeSensorEvent, number of values exceeds the maximum.");
+                return false;
+            }
+            memcpy(event.data, sensorValues, valuesLength * sizeof(float));
+        }
+    }
+
+    status_t err = mService->sendRuntimeSensorEvent(event);
+    return err == OK;
+}
+
 NativeSensorService::ProximityActiveListenerDelegate::ProximityActiveListenerDelegate(
         JNIEnv* env, jobject listener)
       : mListener(env->NewGlobalRef(listener)) {}
@@ -98,6 +223,22 @@
     jniEnv->CallVoidMethod(mListener, sMethodIdOnProximityActive, static_cast<jboolean>(isActive));
 }
 
+NativeSensorService::RuntimeSensorCallbackDelegate::RuntimeSensorCallbackDelegate(JNIEnv* env,
+                                                                                  jobject callback)
+      : mCallback(env->NewGlobalRef(callback)) {}
+
+NativeSensorService::RuntimeSensorCallbackDelegate::~RuntimeSensorCallbackDelegate() {
+    AndroidRuntime::getJNIEnv()->DeleteGlobalRef(mCallback);
+}
+
+void NativeSensorService::RuntimeSensorCallbackDelegate::onStateChanged(
+        bool enabled, int64_t samplingPeriodNs, int64_t batchReportLatencyNs) {
+    auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+    jniEnv->CallVoidMethod(mCallback, sMethodIdOnStateChanged, static_cast<jboolean>(enabled),
+                           static_cast<jint>(ns2us(samplingPeriodNs)),
+                           static_cast<jint>(ns2us(batchReportLatencyNs)));
+}
+
 static jlong startSensorServiceNative(JNIEnv* env, jclass, jobject listener) {
     NativeSensorService* service = new NativeSensorService(env, listener);
     return reinterpret_cast<jlong>(service);
@@ -113,26 +254,46 @@
     service->unregisterProximityActiveListener();
 }
 
-static const JNINativeMethod methods[] = {
-        {
-                "startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J",
-                reinterpret_cast<void*>(startSensorServiceNative)
-        },
-        {
-                "registerProximityActiveListenerNative", "(J)V",
-                reinterpret_cast<void*>(registerProximityActiveListenerNative)
-        },
-        {
-                "unregisterProximityActiveListenerNative", "(J)V",
-                reinterpret_cast<void*>(unregisterProximityActiveListenerNative)
-         },
+static jint registerRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint deviceId, jint type,
+                                        jstring name, jstring vendor, jobject callback) {
+    auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+    return service->registerRuntimeSensor(env, deviceId, type, name, vendor, callback);
+}
 
+static void unregisterRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint handle) {
+    auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+    service->unregisterRuntimeSensor(handle);
+}
+
+static jboolean sendRuntimeSensorEventNative(JNIEnv* env, jclass, jlong ptr, jint handle, jint type,
+                                             jlong timestamp, jfloatArray values) {
+    auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+    return service->sendRuntimeSensorEvent(env, handle, type, timestamp, values);
+}
+
+static const JNINativeMethod methods[] = {
+        {"startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J",
+         reinterpret_cast<void*>(startSensorServiceNative)},
+        {"registerProximityActiveListenerNative", "(J)V",
+         reinterpret_cast<void*>(registerProximityActiveListenerNative)},
+        {"unregisterProximityActiveListenerNative", "(J)V",
+         reinterpret_cast<void*>(unregisterProximityActiveListenerNative)},
+        {"registerRuntimeSensorNative",
+         "(JIILjava/lang/String;Ljava/lang/String;L" RUNTIME_SENSOR_CALLBACK_CLASS ";)I",
+         reinterpret_cast<void*>(registerRuntimeSensorNative)},
+        {"unregisterRuntimeSensorNative", "(JI)V",
+         reinterpret_cast<void*>(unregisterRuntimeSensorNative)},
+        {"sendRuntimeSensorEventNative", "(JIIJ[F)Z",
+         reinterpret_cast<void*>(sendRuntimeSensorEventNative)},
 };
 
 int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env) {
     sJvm = vm;
     jclass listenerClass = FindClassOrDie(env, PROXIMITY_ACTIVE_CLASS);
     sMethodIdOnProximityActive = GetMethodIDOrDie(env, listenerClass, "onProximityActive", "(Z)V");
+    jclass runtimeSensorCallbackClass = FindClassOrDie(env, RUNTIME_SENSOR_CALLBACK_CLASS);
+    sMethodIdOnStateChanged =
+            GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onStateChanged", "(ZII)V");
     return jniRegisterNativeMethods(env, "com/android/server/sensors/SensorService", methods,
                                     NELEM(methods));
 }
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index a5311f3..a8d2f4e 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -18,577 +18,15 @@
 
 //#define LOG_NDEBUG 0
 
-#include "android_os_MessageQueue.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/android_view_Surface.h"
-#include <nativehelper/JNIHelp.h>
-#include "jni.h"
+#include "tvinput/JTvInputHal.h"
 
-#include <android/hardware/tv/input/1.0/ITvInputCallback.h>
-#include <android/hardware/tv/input/1.0/ITvInput.h>
-#include <android/hardware/tv/input/1.0/types.h>
-#include <gui/Surface.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/Log.h>
-#include <utils/Looper.h>
-#include <utils/NativeHandle.h>
-#include <hardware/tv_input.h>
-
-using ::android::hardware::audio::common::V2_0::AudioDevice;
-using ::android::hardware::tv::input::V1_0::ITvInput;
-using ::android::hardware::tv::input::V1_0::ITvInputCallback;
-using ::android::hardware::tv::input::V1_0::Result;
-using ::android::hardware::tv::input::V1_0::TvInputDeviceInfo;
-using ::android::hardware::tv::input::V1_0::TvInputEvent;
-using ::android::hardware::tv::input::V1_0::TvInputEventType;
-using ::android::hardware::tv::input::V1_0::TvInputType;
-using ::android::hardware::tv::input::V1_0::TvStreamConfig;
-using ::android::hardware::Return;
-using ::android::hardware::Void;
-using ::android::hardware::hidl_vec;
-using ::android::hardware::hidl_string;
+gTvInputHalClassInfoType gTvInputHalClassInfo;
+gTvStreamConfigClassInfoType gTvStreamConfigClassInfo;
+gTvStreamConfigBuilderClassInfoType gTvStreamConfigBuilderClassInfo;
+gTvInputHardwareInfoBuilderClassInfoType gTvInputHardwareInfoBuilderClassInfo;
 
 namespace android {
 
-static struct {
-    jmethodID deviceAvailable;
-    jmethodID deviceUnavailable;
-    jmethodID streamConfigsChanged;
-    jmethodID firstFrameCaptured;
-} gTvInputHalClassInfo;
-
-static struct {
-    jclass clazz;
-} gTvStreamConfigClassInfo;
-
-static struct {
-    jclass clazz;
-
-    jmethodID constructor;
-    jmethodID streamId;
-    jmethodID type;
-    jmethodID maxWidth;
-    jmethodID maxHeight;
-    jmethodID generation;
-    jmethodID build;
-} gTvStreamConfigBuilderClassInfo;
-
-static struct {
-    jclass clazz;
-
-    jmethodID constructor;
-    jmethodID deviceId;
-    jmethodID type;
-    jmethodID hdmiPortId;
-    jmethodID cableConnectionStatus;
-    jmethodID audioType;
-    jmethodID audioAddress;
-    jmethodID build;
-} gTvInputHardwareInfoBuilderClassInfo;
-
-////////////////////////////////////////////////////////////////////////////////
-
-class BufferProducerThread : public Thread {
-public:
-    BufferProducerThread(tv_input_device_t* device, int deviceId, const tv_stream_t* stream);
-
-    virtual status_t readyToRun();
-
-    void setSurface(const sp<Surface>& surface);
-    void onCaptured(uint32_t seq, bool succeeded);
-    void shutdown();
-
-private:
-    Mutex mLock;
-    Condition mCondition;
-    sp<Surface> mSurface;
-    tv_input_device_t* mDevice;
-    int mDeviceId;
-    tv_stream_t mStream;
-    sp<ANativeWindowBuffer_t> mBuffer;
-    enum {
-        CAPTURING,
-        CAPTURED,
-        RELEASED,
-    } mBufferState;
-    uint32_t mSeq;
-    bool mShutdown;
-
-    virtual bool threadLoop();
-
-    void setSurfaceLocked(const sp<Surface>& surface);
-};
-
-BufferProducerThread::BufferProducerThread(
-        tv_input_device_t* device, int deviceId, const tv_stream_t* stream)
-    : Thread(false),
-      mDevice(device),
-      mDeviceId(deviceId),
-      mBuffer(NULL),
-      mBufferState(RELEASED),
-      mSeq(0u),
-      mShutdown(false) {
-    memcpy(&mStream, stream, sizeof(mStream));
-}
-
-status_t BufferProducerThread::readyToRun() {
-    sp<ANativeWindow> anw(mSurface);
-    status_t err = native_window_set_usage(anw.get(), mStream.buffer_producer.usage);
-    if (err != NO_ERROR) {
-        return err;
-    }
-    err = native_window_set_buffers_dimensions(
-            anw.get(), mStream.buffer_producer.width, mStream.buffer_producer.height);
-    if (err != NO_ERROR) {
-        return err;
-    }
-    err = native_window_set_buffers_format(anw.get(), mStream.buffer_producer.format);
-    if (err != NO_ERROR) {
-        return err;
-    }
-    return NO_ERROR;
-}
-
-void BufferProducerThread::setSurface(const sp<Surface>& surface) {
-    Mutex::Autolock autoLock(&mLock);
-    setSurfaceLocked(surface);
-}
-
-void BufferProducerThread::setSurfaceLocked(const sp<Surface>& surface) {
-    if (surface == mSurface) {
-        return;
-    }
-
-    if (mBufferState == CAPTURING) {
-        mDevice->cancel_capture(mDevice, mDeviceId, mStream.stream_id, mSeq);
-    }
-    while (mBufferState == CAPTURING) {
-        status_t err = mCondition.waitRelative(mLock, s2ns(1));
-        if (err != NO_ERROR) {
-            ALOGE("error %d while wating for buffer state to change.", err);
-            break;
-        }
-    }
-    mBuffer.clear();
-    mBufferState = RELEASED;
-
-    mSurface = surface;
-    mCondition.broadcast();
-}
-
-void BufferProducerThread::onCaptured(uint32_t seq, bool succeeded) {
-    Mutex::Autolock autoLock(&mLock);
-    if (seq != mSeq) {
-        ALOGW("Incorrect sequence value: expected %u actual %u", mSeq, seq);
-    }
-    if (mBufferState != CAPTURING) {
-        ALOGW("mBufferState != CAPTURING : instead %d", mBufferState);
-    }
-    if (succeeded) {
-        mBufferState = CAPTURED;
-    } else {
-        mBuffer.clear();
-        mBufferState = RELEASED;
-    }
-    mCondition.broadcast();
-}
-
-void BufferProducerThread::shutdown() {
-    Mutex::Autolock autoLock(&mLock);
-    mShutdown = true;
-    setSurfaceLocked(NULL);
-    requestExitAndWait();
-}
-
-bool BufferProducerThread::threadLoop() {
-    Mutex::Autolock autoLock(&mLock);
-
-    status_t err = NO_ERROR;
-    if (mSurface == NULL) {
-        err = mCondition.waitRelative(mLock, s2ns(1));
-        // It's OK to time out here.
-        if (err != NO_ERROR && err != TIMED_OUT) {
-            ALOGE("error %d while wating for non-null surface to be set", err);
-            return false;
-        }
-        return true;
-    }
-    sp<ANativeWindow> anw(mSurface);
-    while (mBufferState == CAPTURING) {
-        err = mCondition.waitRelative(mLock, s2ns(1));
-        if (err != NO_ERROR) {
-            ALOGE("error %d while wating for buffer state to change.", err);
-            return false;
-        }
-    }
-    if (mBufferState == CAPTURED && anw != NULL) {
-        err = anw->queueBuffer(anw.get(), mBuffer.get(), -1);
-        if (err != NO_ERROR) {
-            ALOGE("error %d while queueing buffer to surface", err);
-            return false;
-        }
-        mBuffer.clear();
-        mBufferState = RELEASED;
-    }
-    if (mBuffer == NULL && !mShutdown && anw != NULL) {
-        ANativeWindowBuffer_t* buffer = NULL;
-        err = native_window_dequeue_buffer_and_wait(anw.get(), &buffer);
-        if (err != NO_ERROR) {
-            ALOGE("error %d while dequeueing buffer to surface", err);
-            return false;
-        }
-        mBuffer = buffer;
-        mBufferState = CAPTURING;
-        mDevice->request_capture(mDevice, mDeviceId, mStream.stream_id,
-                                 buffer->handle, ++mSeq);
-    }
-
-    return true;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-class JTvInputHal {
-public:
-    ~JTvInputHal();
-
-    static JTvInputHal* createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper);
-
-    int addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface);
-    int removeStream(int deviceId, int streamId);
-    const hidl_vec<TvStreamConfig> getStreamConfigs(int deviceId);
-
-    void onDeviceAvailable(const TvInputDeviceInfo& info);
-    void onDeviceUnavailable(int deviceId);
-    void onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus);
-    void onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded);
-
-private:
-    // Connection between a surface and a stream.
-    class Connection {
-    public:
-        Connection() {}
-
-        sp<Surface> mSurface;
-        tv_stream_type_t mStreamType;
-
-        // Only valid when mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE
-        sp<NativeHandle> mSourceHandle;
-        // Only valid when mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER
-        sp<BufferProducerThread> mThread;
-    };
-
-    class NotifyHandler : public MessageHandler {
-    public:
-        NotifyHandler(JTvInputHal* hal, const TvInputEvent& event);
-
-        virtual void handleMessage(const Message& message);
-
-    private:
-        TvInputEvent mEvent;
-        JTvInputHal* mHal;
-    };
-
-    class TvInputCallback : public ITvInputCallback {
-    public:
-        explicit TvInputCallback(JTvInputHal* hal);
-        Return<void> notify(const TvInputEvent& event) override;
-    private:
-        JTvInputHal* mHal;
-    };
-
-    JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput, const sp<Looper>& looper);
-
-    Mutex mLock;
-    Mutex mStreamLock;
-    jweak mThiz;
-    sp<Looper> mLooper;
-
-    KeyedVector<int, KeyedVector<int, Connection> > mConnections;
-
-    sp<ITvInput> mTvInput;
-    sp<ITvInputCallback> mTvInputCallback;
-};
-
-JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput,
-        const sp<Looper>& looper) {
-    mThiz = env->NewWeakGlobalRef(thiz);
-    mTvInput = tvInput;
-    mLooper = looper;
-    mTvInputCallback = new TvInputCallback(this);
-    mTvInput->setCallback(mTvInputCallback);
-}
-
-JTvInputHal::~JTvInputHal() {
-    mTvInput->setCallback(nullptr);
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    env->DeleteWeakGlobalRef(mThiz);
-    mThiz = NULL;
-}
-
-JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper) {
-    // TODO(b/31632518)
-    sp<ITvInput> tvInput = ITvInput::getService();
-    if (tvInput == nullptr) {
-        ALOGE("Couldn't get tv.input service.");
-        return nullptr;
-    }
-
-    return new JTvInputHal(env, thiz, tvInput, looper);
-}
-
-int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface) {
-    Mutex::Autolock autoLock(&mStreamLock);
-    KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
-    if (connections.indexOfKey(streamId) < 0) {
-        connections.add(streamId, Connection());
-    }
-    Connection& connection = connections.editValueFor(streamId);
-    if (connection.mSurface == surface) {
-        // Nothing to do
-        return NO_ERROR;
-    }
-    // Clear the surface in the connection.
-    if (connection.mSurface != NULL) {
-        if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) {
-            if (Surface::isValid(connection.mSurface)) {
-                connection.mSurface->setSidebandStream(NULL);
-            }
-        }
-        connection.mSurface.clear();
-    }
-    if (connection.mSourceHandle == NULL && connection.mThread == NULL) {
-        // Need to configure stream
-        Result result = Result::UNKNOWN;
-        hidl_vec<TvStreamConfig> list;
-        mTvInput->getStreamConfigurations(deviceId,
-                [&result, &list](Result res, hidl_vec<TvStreamConfig> configs) {
-                    result = res;
-                    if (res == Result::OK) {
-                        list = configs;
-                    }
-                });
-        if (result != Result::OK) {
-            ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId, result);
-            return UNKNOWN_ERROR;
-        }
-        int configIndex = -1;
-        for (size_t i = 0; i < list.size(); ++i) {
-            if (list[i].streamId == streamId) {
-                configIndex = i;
-                break;
-            }
-        }
-        if (configIndex == -1) {
-            ALOGE("Cannot find a config with given stream ID: %d", streamId);
-            return BAD_VALUE;
-        }
-        connection.mStreamType = TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE;
-
-        result = Result::UNKNOWN;
-        const native_handle_t* sidebandStream;
-        mTvInput->openStream(deviceId, streamId,
-                [&result, &sidebandStream](Result res, const native_handle_t* handle) {
-                    result = res;
-                    if (res == Result::OK) {
-                        if (handle) {
-                            sidebandStream = native_handle_clone(handle);
-                        } else {
-                            result = Result::UNKNOWN;
-                        }
-                    }
-                });
-        if (result != Result::OK) {
-            ALOGE("Couldn't open stream. device id:%d stream id:%d result:%d", deviceId, streamId,
-                    result);
-            return UNKNOWN_ERROR;
-        }
-        connection.mSourceHandle = NativeHandle::create((native_handle_t*)sidebandStream, true);
-    }
-    connection.mSurface = surface;
-    if (connection.mSurface != nullptr) {
-        connection.mSurface->setSidebandStream(connection.mSourceHandle);
-    }
-    return NO_ERROR;
-}
-
-int JTvInputHal::removeStream(int deviceId, int streamId) {
-    Mutex::Autolock autoLock(&mStreamLock);
-    KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
-    if (connections.indexOfKey(streamId) < 0) {
-        return BAD_VALUE;
-    }
-    Connection& connection = connections.editValueFor(streamId);
-    if (connection.mSurface == NULL) {
-        // Nothing to do
-        return NO_ERROR;
-    }
-    if (Surface::isValid(connection.mSurface)) {
-        connection.mSurface->setSidebandStream(NULL);
-    }
-    connection.mSurface.clear();
-    if (connection.mThread != NULL) {
-        connection.mThread->shutdown();
-        connection.mThread.clear();
-    }
-    if (mTvInput->closeStream(deviceId, streamId) != Result::OK) {
-        ALOGE("Couldn't close stream. device id:%d stream id:%d", deviceId, streamId);
-        return BAD_VALUE;
-    }
-    if (connection.mSourceHandle != NULL) {
-        connection.mSourceHandle.clear();
-    }
-    return NO_ERROR;
-}
-
-const hidl_vec<TvStreamConfig> JTvInputHal::getStreamConfigs(int deviceId) {
-    Result result = Result::UNKNOWN;
-    hidl_vec<TvStreamConfig> list;
-    mTvInput->getStreamConfigurations(deviceId,
-            [&result, &list](Result res, hidl_vec<TvStreamConfig> configs) {
-                result = res;
-                if (res == Result::OK) {
-                    list = configs;
-                }
-            });
-    if (result != Result::OK) {
-        ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId, result);
-    }
-    return list;
-}
-
-void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfo& info) {
-    {
-        Mutex::Autolock autoLock(&mLock);
-        mConnections.add(info.deviceId, KeyedVector<int, Connection>());
-    }
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-
-    jobject builder = env->NewObject(
-            gTvInputHardwareInfoBuilderClassInfo.clazz,
-            gTvInputHardwareInfoBuilderClassInfo.constructor);
-    env->CallObjectMethod(
-            builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.deviceId);
-    env->CallObjectMethod(
-            builder, gTvInputHardwareInfoBuilderClassInfo.type, info.type);
-    if (info.type == TvInputType::HDMI) {
-        env->CallObjectMethod(
-                builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.portId);
-    }
-    env->CallObjectMethod(
-            builder, gTvInputHardwareInfoBuilderClassInfo.cableConnectionStatus,
-            info.cableConnectionStatus);
-    env->CallObjectMethod(
-            builder, gTvInputHardwareInfoBuilderClassInfo.audioType, info.audioType);
-    if (info.audioType != AudioDevice::NONE) {
-        uint8_t buffer[info.audioAddress.size() + 1];
-        memcpy(buffer, info.audioAddress.data(), info.audioAddress.size());
-        buffer[info.audioAddress.size()] = '\0';
-        jstring audioAddress = env->NewStringUTF(reinterpret_cast<const char *>(buffer));
-        env->CallObjectMethod(
-                builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress, audioAddress);
-        env->DeleteLocalRef(audioAddress);
-    }
-
-    jobject infoObject = env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.build);
-
-    env->CallVoidMethod(
-            mThiz,
-            gTvInputHalClassInfo.deviceAvailable,
-            infoObject);
-
-    env->DeleteLocalRef(builder);
-    env->DeleteLocalRef(infoObject);
-}
-
-void JTvInputHal::onDeviceUnavailable(int deviceId) {
-    {
-        Mutex::Autolock autoLock(&mLock);
-        KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
-        for (size_t i = 0; i < connections.size(); ++i) {
-            removeStream(deviceId, connections.keyAt(i));
-        }
-        connections.clear();
-        mConnections.removeItem(deviceId);
-    }
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    env->CallVoidMethod(
-            mThiz,
-            gTvInputHalClassInfo.deviceUnavailable,
-            deviceId);
-}
-
-void JTvInputHal::onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus) {
-    {
-        Mutex::Autolock autoLock(&mLock);
-        KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
-        for (size_t i = 0; i < connections.size(); ++i) {
-            removeStream(deviceId, connections.keyAt(i));
-        }
-        connections.clear();
-    }
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    env->CallVoidMethod(mThiz, gTvInputHalClassInfo.streamConfigsChanged, deviceId,
-                        cableConnectionStatus);
-}
-
-void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) {
-    sp<BufferProducerThread> thread;
-    {
-        Mutex::Autolock autoLock(&mLock);
-        KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
-        Connection& connection = connections.editValueFor(streamId);
-        if (connection.mThread == NULL) {
-            ALOGE("capture thread not existing.");
-            return;
-        }
-        thread = connection.mThread;
-    }
-    thread->onCaptured(seq, succeeded);
-    if (seq == 0) {
-        JNIEnv* env = AndroidRuntime::getJNIEnv();
-        env->CallVoidMethod(
-                mThiz,
-                gTvInputHalClassInfo.firstFrameCaptured,
-                deviceId,
-                streamId);
-    }
-}
-
-JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const TvInputEvent& event) {
-    mHal = hal;
-    mEvent = event;
-}
-
-void JTvInputHal::NotifyHandler::handleMessage(const Message& message) {
-    switch (mEvent.type) {
-        case TvInputEventType::DEVICE_AVAILABLE: {
-            mHal->onDeviceAvailable(mEvent.deviceInfo);
-        } break;
-        case TvInputEventType::DEVICE_UNAVAILABLE: {
-            mHal->onDeviceUnavailable(mEvent.deviceInfo.deviceId);
-        } break;
-        case TvInputEventType::STREAM_CONFIGURATIONS_CHANGED: {
-            int cableConnectionStatus = static_cast<int>(mEvent.deviceInfo.cableConnectionStatus);
-            mHal->onStreamConfigurationsChanged(mEvent.deviceInfo.deviceId, cableConnectionStatus);
-        } break;
-        default:
-            ALOGE("Unrecognizable event");
-    }
-}
-
-JTvInputHal::TvInputCallback::TvInputCallback(JTvInputHal* hal) {
-    mHal = hal;
-}
-
-Return<void> JTvInputHal::TvInputCallback::notify(const TvInputEvent& event) {
-    mHal->mLooper->sendMessage(new NotifyHandler(mHal, event), static_cast<int>(event.type));
-    return Void();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
 static jlong nativeOpen(JNIEnv* env, jobject thiz, jobject messageQueueObj) {
     sp<MessageQueue> messageQueue =
             android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
@@ -617,7 +55,7 @@
 static jobjectArray nativeGetStreamConfigs(JNIEnv* env, jclass clazz,
         jlong ptr, jint deviceId, jint generation) {
     JTvInputHal* tvInputHal = (JTvInputHal*)ptr;
-    const hidl_vec<TvStreamConfig> configs = tvInputHal->getStreamConfigs(deviceId);
+    const std::vector<AidlTvStreamConfig> configs = tvInputHal->getStreamConfigs(deviceId);
 
     jobjectArray result = env->NewObjectArray(configs.size(), gTvStreamConfigClassInfo.clazz, NULL);
     for (size_t i = 0; i < configs.size(); ++i) {
diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp
index 34e4976..c7a1af7 100644
--- a/services/core/jni/gnss/AGnssRil.cpp
+++ b/services/core/jni/gnss/AGnssRil.cpp
@@ -84,8 +84,19 @@
     networkAttributes.capabilities = static_cast<int32_t>(capabilities),
     networkAttributes.apn = jniApn.c_str();
 
-    auto result = mIAGnssRil->updateNetworkState(networkAttributes);
-    return checkAidlStatus(result, "IAGnssRilAidl updateNetworkState() failed.");
+    auto status = mIAGnssRil->updateNetworkState(networkAttributes);
+    return checkAidlStatus(status, "IAGnssRilAidl updateNetworkState() failed.");
+}
+
+jboolean AGnssRil::injectNiSuplMessageData(const jbyteArray& msgData, jint length, jint slotIndex) {
+    JNIEnv* env = getJniEnv();
+    jbyte* bytes = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(msgData, 0));
+    auto status = mIAGnssRil->injectNiSuplMessageData(std::vector<uint8_t>((const uint8_t*)bytes,
+                                                                           (const uint8_t*)bytes +
+                                                                                   length),
+                                                      static_cast<int>(slotIndex));
+    env->ReleasePrimitiveArrayCritical(msgData, bytes, JNI_ABORT);
+    return checkAidlStatus(status, "IAGnssRil injectNiSuplMessageData() failed.");
 }
 
 // Implementation of AGnssRil_V1_0
@@ -151,6 +162,11 @@
     return checkHidlReturn(result, "IAGnssRil_V1_0 updateNetworkState() failed.");
 }
 
+jboolean AGnssRil_V1_0::injectNiSuplMessageData(const jbyteArray&, jint, jint) {
+    ALOGI("IAGnssRil_V1_0 interface does not support injectNiSuplMessageData.");
+    return JNI_FALSE;
+}
+
 // Implementation of AGnssRil_V2_0
 
 AGnssRil_V2_0::AGnssRil_V2_0(const sp<IAGnssRil_V2_0>& iAGnssRil)
diff --git a/services/core/jni/gnss/AGnssRil.h b/services/core/jni/gnss/AGnssRil.h
index ce14a77d..b7e0282 100644
--- a/services/core/jni/gnss/AGnssRil.h
+++ b/services/core/jni/gnss/AGnssRil.h
@@ -43,6 +43,8 @@
     virtual jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming,
                                         jboolean available, const jstring& apn, jlong networkHandle,
                                         jshort capabilities) = 0;
+    virtual jboolean injectNiSuplMessageData(const jbyteArray& msgData, jint length,
+                                             jint slotIndex) = 0;
 };
 
 class AGnssRil : public AGnssRilInterface {
@@ -55,6 +57,8 @@
     jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available,
                                 const jstring& apn, jlong networkHandle,
                                 jshort capabilities) override;
+    jboolean injectNiSuplMessageData(const jbyteArray& msgData, jint length,
+                                     jint slotIndex) override;
 
 private:
     const sp<android::hardware::gnss::IAGnssRil> mIAGnssRil;
@@ -70,6 +74,7 @@
     jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available,
                                 const jstring& apn, jlong networkHandle,
                                 jshort capabilities) override;
+    jboolean injectNiSuplMessageData(const jbyteArray&, jint, jint) override;
 
 private:
     const sp<android::hardware::gnss::V1_0::IAGnssRil> mAGnssRil_V1_0;
diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp
index 0531ae2..f3ba484f62 100644
--- a/services/core/jni/gnss/Android.bp
+++ b/services/core/jni/gnss/Android.bp
@@ -61,7 +61,7 @@
         "libnativehelper",
         "libhardware_legacy",
         "libutils",
-        "android.hardware.gnss-V2-cpp",
+        "android.hardware.gnss-V3-cpp",
         "android.hardware.gnss@1.0",
         "android.hardware.gnss@1.1",
         "android.hardware.gnss@2.0",
diff --git a/services/core/jni/gnss/GnssCallback.cpp b/services/core/jni/gnss/GnssCallback.cpp
index b931e91..3c1ac1e 100644
--- a/services/core/jni/gnss/GnssCallback.cpp
+++ b/services/core/jni/gnss/GnssCallback.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "GnssCallbckJni"
+#define LOG_TAG "GnssCallbackJni"
 
 #include "GnssCallback.h"
 
@@ -31,6 +31,7 @@
 using hardware::Void;
 
 using GnssLocationAidl = android::hardware::gnss::GnssLocation;
+using GnssSignalType = android::hardware::gnss::GnssSignalType;
 using GnssLocation_V1_0 = android::hardware::gnss::V1_0::GnssLocation;
 using GnssLocation_V2_0 = android::hardware::gnss::V2_0::GnssLocation;
 using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback;
@@ -42,11 +43,18 @@
 
 namespace {
 
+jclass class_arrayList;
+jclass class_gnssSignalType;
+
+jmethodID method_arrayListAdd;
+jmethodID method_arrayListCtor;
+jmethodID method_gnssSignalTypeCreate;
 jmethodID method_reportLocation;
 jmethodID method_reportStatus;
 jmethodID method_reportSvStatus;
 jmethodID method_reportNmea;
 jmethodID method_setTopHalCapabilities;
+jmethodID method_setSignalTypeCapabilities;
 jmethodID method_setGnssYearOfHardware;
 jmethodID method_setGnssHardwareModelName;
 jmethodID method_requestLocation;
@@ -88,6 +96,8 @@
     method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
 
     method_setTopHalCapabilities = env->GetMethodID(clazz, "setTopHalCapabilities", "(I)V");
+    method_setSignalTypeCapabilities =
+            env->GetMethodID(clazz, "setSignalTypeCapabilities", "(Ljava/util/List;)V");
     method_setGnssYearOfHardware = env->GetMethodID(clazz, "setGnssYearOfHardware", "(I)V");
     method_setGnssHardwareModelName =
             env->GetMethodID(clazz, "setGnssHardwareModelName", "(Ljava/lang/String;)V");
@@ -95,16 +105,58 @@
     method_requestLocation = env->GetMethodID(clazz, "requestLocation", "(ZZ)V");
     method_requestUtcTime = env->GetMethodID(clazz, "requestUtcTime", "()V");
     method_reportGnssServiceDied = env->GetMethodID(clazz, "reportGnssServiceDied", "()V");
+
+    jclass arrayListClass = env->FindClass("java/util/ArrayList");
+    class_arrayList = (jclass)env->NewGlobalRef(arrayListClass);
+    method_arrayListCtor = env->GetMethodID(class_arrayList, "<init>", "()V");
+    method_arrayListAdd = env->GetMethodID(class_arrayList, "add", "(Ljava/lang/Object;)Z");
+
+    jclass gnssSignalTypeClass = env->FindClass("android/location/GnssSignalType");
+    class_gnssSignalType = (jclass)env->NewGlobalRef(gnssSignalTypeClass);
+    method_gnssSignalTypeCreate =
+            env->GetStaticMethodID(class_gnssSignalType, "create",
+                                   "(IDLjava/lang/String;)Landroid/location/GnssSignalType;");
 }
 
 Status GnssCallbackAidl::gnssSetCapabilitiesCb(const int capabilities) {
-    ALOGD("GnssCallbackAidl::%s: %du\n", __func__, capabilities);
+    ALOGD("%s: %du\n", __func__, capabilities);
     JNIEnv* env = getJniEnv();
     env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     return Status::ok();
 }
 
+namespace {
+
+jobject translateSingleSignalType(JNIEnv* env, const GnssSignalType& signalType) {
+    jstring jstringCodeType = env->NewStringUTF(signalType.codeType.c_str());
+    jobject signalTypeObject =
+            env->CallStaticObjectMethod(class_gnssSignalType, method_gnssSignalTypeCreate,
+                                        signalType.constellation, signalType.carrierFrequencyHz,
+                                        jstringCodeType);
+    env->DeleteLocalRef(jstringCodeType);
+    return signalTypeObject;
+}
+
+} // anonymous namespace
+
+Status GnssCallbackAidl::gnssSetSignalTypeCapabilitiesCb(
+        const std::vector<GnssSignalType>& signalTypes) {
+    ALOGD("%s: %d signal types", __func__, (int)signalTypes.size());
+    JNIEnv* env = getJniEnv();
+    jobject arrayList = env->NewObject(class_arrayList, method_arrayListCtor);
+    for (auto& signalType : signalTypes) {
+        jobject signalTypeObject = translateSingleSignalType(env, signalType);
+        env->CallBooleanMethod(arrayList, method_arrayListAdd, signalTypeObject);
+        // Delete Local Refs
+        env->DeleteLocalRef(signalTypeObject);
+    }
+    env->CallVoidMethod(mCallbacksObj, method_setSignalTypeCapabilities, arrayList);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    env->DeleteLocalRef(arrayList);
+    return Status::ok();
+}
+
 Status GnssCallbackAidl::gnssStatusCb(const GnssStatusValue status) {
     JNIEnv* env = getJniEnv();
     env->CallVoidMethod(mCallbacksObj, method_reportStatus, status);
diff --git a/services/core/jni/gnss/GnssCallback.h b/services/core/jni/gnss/GnssCallback.h
index a7f96fb..33acec8 100644
--- a/services/core/jni/gnss/GnssCallback.h
+++ b/services/core/jni/gnss/GnssCallback.h
@@ -61,6 +61,8 @@
 class GnssCallbackAidl : public hardware::gnss::BnGnssCallback {
 public:
     binder::Status gnssSetCapabilitiesCb(const int capabilities) override;
+    binder::Status gnssSetSignalTypeCapabilitiesCb(
+            const std::vector<android::hardware::gnss::GnssSignalType>& signalTypes) override;
     binder::Status gnssStatusCb(const GnssStatusValue status) override;
     binder::Status gnssSvStatusCb(const std::vector<GnssSvInfo>& svInfoList) override;
     binder::Status gnssLocationCb(const hardware::gnss::GnssLocation& location) override;
@@ -180,4 +182,4 @@
 
 } // namespace android::gnss
 
-#endif // _ANDROID_SERVER_GNSS_GNSSCALLBACK_H
\ No newline at end of file
+#endif // _ANDROID_SERVER_GNSS_GNSSCALLBACK_H
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 00f851f..1845057 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -56,6 +56,7 @@
 int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
 int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(JNIEnv* env);
 int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
+int register_android_server_com_android_server_pm_Settings(JNIEnv* env);
 int register_android_server_AdbDebuggingManager(JNIEnv* env);
 int register_android_server_FaceService(JNIEnv* env);
 int register_android_server_GpuService(JNIEnv* env);
@@ -114,6 +115,7 @@
     register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);
     register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(env);
     register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
+    register_android_server_com_android_server_pm_Settings(env);
     register_android_server_AdbDebuggingManager(env);
     register_android_server_FaceService(env);
     register_android_server_GpuService(env);
diff --git a/services/core/jni/onload_settings.cpp b/services/core/jni/onload_settings.cpp
new file mode 100644
index 0000000..b21c34a
--- /dev/null
+++ b/services/core/jni/onload_settings.cpp
@@ -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.
+ */
+
+#include "jni.h"
+#include "utils/Log.h"
+
+namespace android {
+int register_android_server_com_android_server_pm_Settings(JNIEnv* env);
+};
+
+using namespace android;
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+    JNIEnv* env = NULL;
+    jint result = -1;
+
+    if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
+        ALOGE("GetEnv failed!");
+        return result;
+    }
+    ALOG_ASSERT(env, "Could not retrieve the env!");
+
+    register_android_server_com_android_server_pm_Settings(env);
+
+    return JNI_VERSION_1_4;
+}
diff --git a/services/core/jni/tvinput/BufferProducerThread.cpp b/services/core/jni/tvinput/BufferProducerThread.cpp
new file mode 100644
index 0000000..f39dcee
--- /dev/null
+++ b/services/core/jni/tvinput/BufferProducerThread.cpp
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+#include "BufferProducerThread.h"
+
+namespace android {
+
+BufferProducerThread::BufferProducerThread(tv_input_device_t* device, int deviceId,
+                                           const tv_stream_t* stream)
+      : Thread(false),
+        mDevice(device),
+        mDeviceId(deviceId),
+        mBuffer(NULL),
+        mBufferState(RELEASED),
+        mSeq(0u),
+        mShutdown(false) {
+    memcpy(&mStream, stream, sizeof(mStream));
+}
+
+status_t BufferProducerThread::readyToRun() {
+    sp<ANativeWindow> anw(mSurface);
+    status_t err = native_window_set_usage(anw.get(), mStream.buffer_producer.usage);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    err = native_window_set_buffers_dimensions(anw.get(), mStream.buffer_producer.width,
+                                               mStream.buffer_producer.height);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    err = native_window_set_buffers_format(anw.get(), mStream.buffer_producer.format);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    return NO_ERROR;
+}
+
+void BufferProducerThread::setSurface(const sp<Surface>& surface) {
+    Mutex::Autolock autoLock(&mLock);
+    setSurfaceLocked(surface);
+}
+
+void BufferProducerThread::setSurfaceLocked(const sp<Surface>& surface) {
+    if (surface == mSurface) {
+        return;
+    }
+
+    if (mBufferState == CAPTURING) {
+        mDevice->cancel_capture(mDevice, mDeviceId, mStream.stream_id, mSeq);
+    }
+    while (mBufferState == CAPTURING) {
+        status_t err = mCondition.waitRelative(mLock, s2ns(1));
+        if (err != NO_ERROR) {
+            ALOGE("error %d while wating for buffer state to change.", err);
+            break;
+        }
+    }
+    mBuffer.clear();
+    mBufferState = RELEASED;
+
+    mSurface = surface;
+    mCondition.broadcast();
+}
+
+void BufferProducerThread::onCaptured(uint32_t seq, bool succeeded) {
+    Mutex::Autolock autoLock(&mLock);
+    if (seq != mSeq) {
+        ALOGW("Incorrect sequence value: expected %u actual %u", mSeq, seq);
+    }
+    if (mBufferState != CAPTURING) {
+        ALOGW("mBufferState != CAPTURING : instead %d", mBufferState);
+    }
+    if (succeeded) {
+        mBufferState = CAPTURED;
+    } else {
+        mBuffer.clear();
+        mBufferState = RELEASED;
+    }
+    mCondition.broadcast();
+}
+
+void BufferProducerThread::shutdown() {
+    Mutex::Autolock autoLock(&mLock);
+    mShutdown = true;
+    setSurfaceLocked(NULL);
+    requestExitAndWait();
+}
+
+bool BufferProducerThread::threadLoop() {
+    Mutex::Autolock autoLock(&mLock);
+
+    status_t err = NO_ERROR;
+    if (mSurface == NULL) {
+        err = mCondition.waitRelative(mLock, s2ns(1));
+        // It's OK to time out here.
+        if (err != NO_ERROR && err != TIMED_OUT) {
+            ALOGE("error %d while wating for non-null surface to be set", err);
+            return false;
+        }
+        return true;
+    }
+    sp<ANativeWindow> anw(mSurface);
+    while (mBufferState == CAPTURING) {
+        err = mCondition.waitRelative(mLock, s2ns(1));
+        if (err != NO_ERROR) {
+            ALOGE("error %d while wating for buffer state to change.", err);
+            return false;
+        }
+    }
+    if (mBufferState == CAPTURED && anw != NULL) {
+        err = anw->queueBuffer(anw.get(), mBuffer.get(), -1);
+        if (err != NO_ERROR) {
+            ALOGE("error %d while queueing buffer to surface", err);
+            return false;
+        }
+        mBuffer.clear();
+        mBufferState = RELEASED;
+    }
+    if (mBuffer == NULL && !mShutdown && anw != NULL) {
+        ANativeWindowBuffer_t* buffer = NULL;
+        err = native_window_dequeue_buffer_and_wait(anw.get(), &buffer);
+        if (err != NO_ERROR) {
+            ALOGE("error %d while dequeueing buffer to surface", err);
+            return false;
+        }
+        mBuffer = buffer;
+        mBufferState = CAPTURING;
+        mDevice->request_capture(mDevice, mDeviceId, mStream.stream_id, buffer->handle, ++mSeq);
+    }
+
+    return true;
+}
+
+} // namespace android
diff --git a/services/core/jni/tvinput/BufferProducerThread.h b/services/core/jni/tvinput/BufferProducerThread.h
new file mode 100644
index 0000000..2e0fa91
--- /dev/null
+++ b/services/core/jni/tvinput/BufferProducerThread.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <gui/Surface.h>
+#include <hardware/tv_input.h>
+#include <utils/Thread.h>
+
+namespace android {
+
+class BufferProducerThread : public Thread {
+public:
+    BufferProducerThread(tv_input_device_t* device, int deviceId, const tv_stream_t* stream);
+
+    virtual status_t readyToRun();
+
+    void setSurface(const sp<Surface>& surface);
+    void onCaptured(uint32_t seq, bool succeeded);
+    void shutdown();
+
+private:
+    Mutex mLock;
+    Condition mCondition;
+    sp<Surface> mSurface;
+    tv_input_device_t* mDevice;
+    int mDeviceId;
+    tv_stream_t mStream;
+    sp<ANativeWindowBuffer_t> mBuffer;
+    enum {
+        CAPTURING,
+        CAPTURED,
+        RELEASED,
+    } mBufferState;
+    uint32_t mSeq;
+    bool mShutdown;
+
+    virtual bool threadLoop();
+
+    void setSurfaceLocked(const sp<Surface>& surface);
+};
+
+} // namespace android
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
new file mode 100644
index 0000000..3453cbd
--- /dev/null
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -0,0 +1,381 @@
+/*
+ * 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.
+ */
+
+#include "JTvInputHal.h"
+
+namespace android {
+
+JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, std::shared_ptr<ITvInputWrapper> tvInput,
+                         const sp<Looper>& looper) {
+    mThiz = env->NewWeakGlobalRef(thiz);
+    mTvInput = tvInput;
+    mLooper = looper;
+    mTvInputCallback = ::ndk::SharedRefBase::make<TvInputCallback>(this);
+    mTvInput->setCallback(mTvInputCallback);
+}
+
+JTvInputHal::~JTvInputHal() {
+    mTvInput->setCallback(nullptr);
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->DeleteWeakGlobalRef(mThiz);
+    mThiz = NULL;
+}
+
+JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper) {
+    sp<HidlITvInput> hidlITvInput = HidlITvInput::getService();
+    if (hidlITvInput != nullptr) {
+        ALOGD("tv.input service is HIDL.");
+        return new JTvInputHal(env, thiz,
+                               std::shared_ptr<ITvInputWrapper>(new ITvInputWrapper(hidlITvInput)),
+                               looper);
+    }
+    std::shared_ptr<AidlITvInput> aidlITvInput = nullptr;
+    if (AServiceManager_isDeclared(TV_INPUT_AIDL_SERVICE_NAME)) {
+        ::ndk::SpAIBinder binder(AServiceManager_waitForService(TV_INPUT_AIDL_SERVICE_NAME));
+        aidlITvInput = AidlITvInput::fromBinder(binder);
+    }
+    if (aidlITvInput == nullptr) {
+        ALOGE("Couldn't get tv.input service.");
+        return nullptr;
+    }
+    return new JTvInputHal(env, thiz,
+                           std::shared_ptr<ITvInputWrapper>(new ITvInputWrapper(aidlITvInput)),
+                           looper);
+}
+
+int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface) {
+    Mutex::Autolock autoLock(&mStreamLock);
+    KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+    if (connections.indexOfKey(streamId) < 0) {
+        connections.add(streamId, Connection());
+    }
+    Connection& connection = connections.editValueFor(streamId);
+    if (connection.mSurface == surface) {
+        // Nothing to do
+        return NO_ERROR;
+    }
+    // Clear the surface in the connection.
+    if (connection.mSurface != NULL) {
+        if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) {
+            if (Surface::isValid(connection.mSurface)) {
+                connection.mSurface->setSidebandStream(NULL);
+            }
+        }
+        connection.mSurface.clear();
+    }
+    if (connection.mSourceHandle == NULL && connection.mThread == NULL) {
+        // Need to configure stream
+        ::ndk::ScopedAStatus status;
+        std::vector<AidlTvStreamConfig> list;
+        status = mTvInput->getStreamConfigurations(deviceId, &list);
+        if (!status.isOk()) {
+            ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId,
+                  status.getServiceSpecificError());
+            return UNKNOWN_ERROR;
+        }
+        int configIndex = -1;
+        for (size_t i = 0; i < list.size(); ++i) {
+            if (list[i].streamId == streamId) {
+                configIndex = i;
+                break;
+            }
+        }
+        if (configIndex == -1) {
+            ALOGE("Cannot find a config with given stream ID: %d", streamId);
+            return BAD_VALUE;
+        }
+        connection.mStreamType = TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE;
+
+        AidlNativeHandle sidebandStream;
+        status = mTvInput->openStream(deviceId, streamId, &sidebandStream);
+        if (!status.isOk()) {
+            ALOGE("Couldn't open stream. device id:%d stream id:%d result:%d", deviceId, streamId,
+                  status.getServiceSpecificError());
+            return UNKNOWN_ERROR;
+        }
+        connection.mSourceHandle = NativeHandle::create(makeFromAidl(sidebandStream), true);
+    }
+    connection.mSurface = surface;
+    if (connection.mSurface != nullptr) {
+        connection.mSurface->setSidebandStream(connection.mSourceHandle);
+    }
+    return NO_ERROR;
+}
+
+int JTvInputHal::removeStream(int deviceId, int streamId) {
+    Mutex::Autolock autoLock(&mStreamLock);
+    KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+    if (connections.indexOfKey(streamId) < 0) {
+        return BAD_VALUE;
+    }
+    Connection& connection = connections.editValueFor(streamId);
+    if (connection.mSurface == NULL) {
+        // Nothing to do
+        return NO_ERROR;
+    }
+    if (Surface::isValid(connection.mSurface)) {
+        connection.mSurface->setSidebandStream(NULL);
+    }
+    connection.mSurface.clear();
+    if (connection.mThread != NULL) {
+        connection.mThread->shutdown();
+        connection.mThread.clear();
+    }
+    if (!mTvInput->closeStream(deviceId, streamId).isOk()) {
+        ALOGE("Couldn't close stream. device id:%d stream id:%d", deviceId, streamId);
+        return BAD_VALUE;
+    }
+    if (connection.mSourceHandle != NULL) {
+        connection.mSourceHandle.clear();
+    }
+    return NO_ERROR;
+}
+
+const std::vector<AidlTvStreamConfig> JTvInputHal::getStreamConfigs(int deviceId) {
+    std::vector<AidlTvStreamConfig> list;
+    ::ndk::ScopedAStatus status = mTvInput->getStreamConfigurations(deviceId, &list);
+    if (!status.isOk()) {
+        ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId,
+              status.getServiceSpecificError());
+        return std::vector<AidlTvStreamConfig>();
+    }
+    return list;
+}
+
+void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) {
+    {
+        Mutex::Autolock autoLock(&mLock);
+        mConnections.add(info.deviceId, KeyedVector<int, Connection>());
+    }
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jobject builder = env->NewObject(gTvInputHardwareInfoBuilderClassInfo.clazz,
+                                     gTvInputHardwareInfoBuilderClassInfo.constructor);
+    env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.deviceId);
+    env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.type, info.type);
+    if (info.type == TvInputType::HDMI) {
+        env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId,
+                              info.portId);
+    }
+    env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.cableConnectionStatus,
+                          info.cableConnectionStatus);
+    if (info.isHidl) {
+        hidlSetUpAudioInfo(env, builder, info);
+    } else {
+        AidlAudioDeviceType audioType = info.aidlAudioDevice.type.type;
+        env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.audioType, audioType);
+        if (audioType != AidlAudioDeviceType::NONE) {
+            std::stringstream ss;
+            switch (info.aidlAudioDevice.address.getTag()) {
+                case AidlAudioDeviceAddress::id:
+                    ss << info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::id>();
+                    break;
+                case AidlAudioDeviceAddress::mac: {
+                    std::vector<uint8_t> addrList =
+                            info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::mac>();
+                    for (int i = 0; i < addrList.size(); i++) {
+                        if (i != 0) {
+                            ss << ':';
+                        }
+                        ss << std::uppercase << std::setfill('0') << std::setw(2) << std::hex
+                           << static_cast<int32_t>(addrList[i]);
+                    }
+                } break;
+                case AidlAudioDeviceAddress::ipv4: {
+                    std::vector<uint8_t> addrList =
+                            info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::ipv4>();
+                    for (int i = 0; i < addrList.size(); i++) {
+                        if (i != 0) {
+                            ss << '.';
+                        }
+                        ss << static_cast<int32_t>(addrList[i]);
+                    }
+                } break;
+                case AidlAudioDeviceAddress::ipv6: {
+                    std::vector<int32_t> addrList =
+                            info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::ipv6>();
+                    for (int i = 0; i < addrList.size(); i++) {
+                        if (i != 0) {
+                            ss << ':';
+                        }
+                        ss << std::uppercase << std::setfill('0') << std::setw(4) << std::hex
+                           << addrList[i];
+                    }
+                } break;
+                case AidlAudioDeviceAddress::alsa: {
+                    std::vector<int32_t> addrList =
+                            info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::alsa>();
+                    ss << "card=" << addrList[0] << ";device=" << addrList[1];
+                } break;
+            }
+            std::string bufferStr = ss.str();
+            jstring audioAddress = env->NewStringUTF(bufferStr.c_str());
+            env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress,
+                                  audioAddress);
+            env->DeleteLocalRef(audioAddress);
+        }
+    }
+
+    jobject infoObject = env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.build);
+
+    env->CallVoidMethod(mThiz, gTvInputHalClassInfo.deviceAvailable, infoObject);
+
+    env->DeleteLocalRef(builder);
+    env->DeleteLocalRef(infoObject);
+}
+
+void JTvInputHal::onDeviceUnavailable(int deviceId) {
+    {
+        Mutex::Autolock autoLock(&mLock);
+        KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+        for (size_t i = 0; i < connections.size(); ++i) {
+            removeStream(deviceId, connections.keyAt(i));
+        }
+        connections.clear();
+        mConnections.removeItem(deviceId);
+    }
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->CallVoidMethod(mThiz, gTvInputHalClassInfo.deviceUnavailable, deviceId);
+}
+
+void JTvInputHal::onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus) {
+    {
+        Mutex::Autolock autoLock(&mLock);
+        KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+        for (size_t i = 0; i < connections.size(); ++i) {
+            removeStream(deviceId, connections.keyAt(i));
+        }
+        connections.clear();
+    }
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->CallVoidMethod(mThiz, gTvInputHalClassInfo.streamConfigsChanged, deviceId,
+                        cableConnectionStatus);
+}
+
+void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) {
+    sp<BufferProducerThread> thread;
+    {
+        Mutex::Autolock autoLock(&mLock);
+        KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+        Connection& connection = connections.editValueFor(streamId);
+        if (connection.mThread == NULL) {
+            ALOGE("capture thread not existing.");
+            return;
+        }
+        thread = connection.mThread;
+    }
+    thread->onCaptured(seq, succeeded);
+    if (seq == 0) {
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+        env->CallVoidMethod(mThiz, gTvInputHalClassInfo.firstFrameCaptured, deviceId, streamId);
+    }
+}
+
+JTvInputHal::TvInputDeviceInfoWrapper
+JTvInputHal::TvInputDeviceInfoWrapper::createDeviceInfoWrapper(
+        const AidlTvInputDeviceInfo& aidlTvInputDeviceInfo) {
+    TvInputDeviceInfoWrapper deviceInfo;
+    deviceInfo.isHidl = false;
+    deviceInfo.deviceId = aidlTvInputDeviceInfo.deviceId;
+    deviceInfo.type = aidlTvInputDeviceInfo.type;
+    deviceInfo.portId = aidlTvInputDeviceInfo.portId;
+    deviceInfo.cableConnectionStatus = aidlTvInputDeviceInfo.cableConnectionStatus;
+    deviceInfo.aidlAudioDevice = aidlTvInputDeviceInfo.audioDevice;
+    return deviceInfo;
+}
+
+JTvInputHal::TvInputEventWrapper JTvInputHal::TvInputEventWrapper::createEventWrapper(
+        const AidlTvInputEvent& aidlTvInputEvent) {
+    TvInputEventWrapper event;
+    event.type = aidlTvInputEvent.type;
+    event.deviceInfo =
+            TvInputDeviceInfoWrapper::createDeviceInfoWrapper(aidlTvInputEvent.deviceInfo);
+    return event;
+}
+
+JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const TvInputEventWrapper& event) {
+    mHal = hal;
+    mEvent = event;
+}
+
+void JTvInputHal::NotifyHandler::handleMessage(const Message& message) {
+    switch (mEvent.type) {
+        case TvInputEventType::DEVICE_AVAILABLE: {
+            mHal->onDeviceAvailable(mEvent.deviceInfo);
+        } break;
+        case TvInputEventType::DEVICE_UNAVAILABLE: {
+            mHal->onDeviceUnavailable(mEvent.deviceInfo.deviceId);
+        } break;
+        case TvInputEventType::STREAM_CONFIGURATIONS_CHANGED: {
+            int cableConnectionStatus = static_cast<int>(mEvent.deviceInfo.cableConnectionStatus);
+            mHal->onStreamConfigurationsChanged(mEvent.deviceInfo.deviceId, cableConnectionStatus);
+        } break;
+        default:
+            ALOGE("Unrecognizable event");
+    }
+}
+
+JTvInputHal::TvInputCallback::TvInputCallback(JTvInputHal* hal) {
+    mHal = hal;
+}
+
+::ndk::ScopedAStatus JTvInputHal::TvInputCallback::notify(const AidlTvInputEvent& event) {
+    mHal->mLooper->sendMessage(new NotifyHandler(mHal,
+                                                 TvInputEventWrapper::createEventWrapper(event)),
+                               static_cast<int>(event.type));
+    return ::ndk::ScopedAStatus::ok();
+}
+
+JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput)
+      : mIsHidl(false), mAidlTvInput(aidlTvInput) {}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::setCallback(
+        const std::shared_ptr<TvInputCallback>& in_callback) {
+    if (mIsHidl) {
+        return hidlSetCallback(in_callback);
+    } else {
+        return mAidlTvInput->setCallback(in_callback);
+    }
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getStreamConfigurations(
+        int32_t in_deviceId, std::vector<AidlTvStreamConfig>* _aidl_return) {
+    if (mIsHidl) {
+        return hidlGetStreamConfigurations(in_deviceId, _aidl_return);
+    } else {
+        return mAidlTvInput->getStreamConfigurations(in_deviceId, _aidl_return);
+    }
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::openStream(int32_t in_deviceId,
+                                                              int32_t in_streamId,
+                                                              AidlNativeHandle* _aidl_return) {
+    if (mIsHidl) {
+        return hidlOpenStream(in_deviceId, in_streamId, _aidl_return);
+    } else {
+        return mAidlTvInput->openStream(in_deviceId, in_streamId, _aidl_return);
+    }
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::closeStream(int32_t in_deviceId,
+                                                               int32_t in_streamId) {
+    if (mIsHidl) {
+        return hidlCloseStream(in_deviceId, in_streamId);
+    } else {
+        return mAidlTvInput->closeStream(in_deviceId, in_streamId);
+    }
+}
+
+} // namespace android
diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h
new file mode 100644
index 0000000..2697294
--- /dev/null
+++ b/services/core/jni/tvinput/JTvInputHal.h
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#define TV_INPUT_AIDL_SERVICE_NAME "android.hardware.tv.input.ITvInput/default"
+
+#include <aidl/android/hardware/tv/input/BnTvInputCallback.h>
+#include <aidl/android/hardware/tv/input/CableConnectionStatus.h>
+#include <aidl/android/hardware/tv/input/ITvInput.h>
+#include <aidl/android/media/audio/common/AudioDevice.h>
+#include <aidlcommonsupport/NativeHandle.h>
+#include <android/binder_manager.h>
+#include <nativehelper/JNIHelp.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+#include <utils/NativeHandle.h>
+
+#include <iomanip>
+
+#include "BufferProducerThread.h"
+#include "TvInputHal_hidl.h"
+#include "android_os_MessageQueue.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/android_view_Surface.h"
+#include "tvinput/jstruct.h"
+
+using ::aidl::android::hardware::tv::input::BnTvInputCallback;
+using ::aidl::android::hardware::tv::input::CableConnectionStatus;
+using ::aidl::android::hardware::tv::input::TvInputEventType;
+using ::aidl::android::hardware::tv::input::TvInputType;
+
+using AidlAudioDevice = ::aidl::android::media::audio::common::AudioDevice;
+using AidlAudioDeviceAddress = ::aidl::android::media::audio::common::AudioDeviceAddress;
+using AidlAudioDeviceType = ::aidl::android::media::audio::common::AudioDeviceType;
+using AidlITvInput = ::aidl::android::hardware::tv::input::ITvInput;
+using AidlNativeHandle = ::aidl::android::hardware::common::NativeHandle;
+using AidlTvInputDeviceInfo = ::aidl::android::hardware::tv::input::TvInputDeviceInfo;
+using AidlTvInputEvent = ::aidl::android::hardware::tv::input::TvInputEvent;
+using AidlTvStreamConfig = ::aidl::android::hardware::tv::input::TvStreamConfig;
+
+extern gTvInputHalClassInfoType gTvInputHalClassInfo;
+extern gTvStreamConfigClassInfoType gTvStreamConfigClassInfo;
+extern gTvStreamConfigBuilderClassInfoType gTvStreamConfigBuilderClassInfo;
+extern gTvInputHardwareInfoBuilderClassInfoType gTvInputHardwareInfoBuilderClassInfo;
+
+namespace android {
+
+class JTvInputHal {
+public:
+    ~JTvInputHal();
+
+    static JTvInputHal* createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper);
+
+    int addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface);
+    int removeStream(int deviceId, int streamId);
+    const std::vector<AidlTvStreamConfig> getStreamConfigs(int deviceId);
+
+private:
+    // Connection between a surface and a stream.
+    class Connection {
+    public:
+        Connection() {}
+
+        sp<Surface> mSurface;
+        tv_stream_type_t mStreamType;
+
+        // Only valid when mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE
+        sp<NativeHandle> mSourceHandle;
+        // Only valid when mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER
+        sp<BufferProducerThread> mThread;
+    };
+
+    class TvInputDeviceInfoWrapper {
+    public:
+        TvInputDeviceInfoWrapper() {}
+
+        static TvInputDeviceInfoWrapper createDeviceInfoWrapper(
+                const AidlTvInputDeviceInfo& aidlTvInputDeviceInfo);
+        static TvInputDeviceInfoWrapper createDeviceInfoWrapper(
+                const HidlTvInputDeviceInfo& hidlTvInputDeviceInfo);
+
+        bool isHidl;
+        int deviceId;
+        TvInputType type;
+        int portId;
+        CableConnectionStatus cableConnectionStatus;
+        AidlAudioDevice aidlAudioDevice;
+        HidlAudioDevice hidlAudioType;
+        ::android::hardware::hidl_array<uint8_t, 32> hidlAudioAddress;
+    };
+
+    class TvInputEventWrapper {
+    public:
+        TvInputEventWrapper() {}
+
+        static TvInputEventWrapper createEventWrapper(const AidlTvInputEvent& aidlTvInputEvent);
+        static TvInputEventWrapper createEventWrapper(const HidlTvInputEvent& hidlTvInputEvent);
+
+        TvInputEventType type;
+        TvInputDeviceInfoWrapper deviceInfo;
+    };
+
+    class NotifyHandler : public MessageHandler {
+    public:
+        NotifyHandler(JTvInputHal* hal, const TvInputEventWrapper& event);
+
+        void handleMessage(const Message& message) override;
+
+    private:
+        TvInputEventWrapper mEvent;
+        JTvInputHal* mHal;
+    };
+
+    class TvInputCallback : public HidlITvInputCallback, public BnTvInputCallback {
+    public:
+        explicit TvInputCallback(JTvInputHal* hal);
+        ::ndk::ScopedAStatus notify(const AidlTvInputEvent& event) override;
+        Return<void> notify(const HidlTvInputEvent& event) override;
+
+    private:
+        JTvInputHal* mHal;
+    };
+
+    class ITvInputWrapper {
+    public:
+        ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput);
+        ITvInputWrapper(sp<HidlITvInput>& hidlTvInput);
+
+        ::ndk::ScopedAStatus setCallback(const std::shared_ptr<TvInputCallback>& in_callback);
+        ::ndk::ScopedAStatus getStreamConfigurations(int32_t in_deviceId,
+                                                     std::vector<AidlTvStreamConfig>* _aidl_return);
+        ::ndk::ScopedAStatus openStream(int32_t in_deviceId, int32_t in_streamId,
+                                        AidlNativeHandle* _aidl_return);
+        ::ndk::ScopedAStatus closeStream(int32_t in_deviceId, int32_t in_streamId);
+
+    private:
+        ::ndk::ScopedAStatus hidlSetCallback(const std::shared_ptr<TvInputCallback>& in_callback);
+        ::ndk::ScopedAStatus hidlGetStreamConfigurations(
+                int32_t in_deviceId, std::vector<AidlTvStreamConfig>* _aidl_return);
+        ::ndk::ScopedAStatus hidlOpenStream(int32_t in_deviceId, int32_t in_streamId,
+                                            AidlNativeHandle* _aidl_return);
+        ::ndk::ScopedAStatus hidlCloseStream(int32_t in_deviceId, int32_t in_streamId);
+
+        bool mIsHidl;
+        sp<HidlITvInput> mHidlTvInput;
+        std::shared_ptr<AidlITvInput> mAidlTvInput;
+    };
+
+    JTvInputHal(JNIEnv* env, jobject thiz, std::shared_ptr<ITvInputWrapper> tvInput,
+                const sp<Looper>& looper);
+
+    void hidlSetUpAudioInfo(JNIEnv* env, jobject& builder, const TvInputDeviceInfoWrapper& info);
+    void onDeviceAvailable(const TvInputDeviceInfoWrapper& info);
+    void onDeviceUnavailable(int deviceId);
+    void onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus);
+    void onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded);
+
+    Mutex mLock;
+    Mutex mStreamLock;
+    jweak mThiz;
+    sp<Looper> mLooper;
+
+    KeyedVector<int, KeyedVector<int, Connection> > mConnections;
+
+    std::shared_ptr<ITvInputWrapper> mTvInput;
+    std::shared_ptr<TvInputCallback> mTvInputCallback;
+};
+
+} // namespace android
diff --git a/services/core/jni/tvinput/OWNERS b/services/core/jni/tvinput/OWNERS
new file mode 100644
index 0000000..adc5827
--- /dev/null
+++ b/services/core/jni/tvinput/OWNERS
@@ -0,0 +1,3 @@
+include /media/java/android/media/tv/OWNERS
+
+yixiaoluo@google.com
diff --git a/services/core/jni/tvinput/TvInputHal_hidl.cpp b/services/core/jni/tvinput/TvInputHal_hidl.cpp
new file mode 100644
index 0000000..37cf844
--- /dev/null
+++ b/services/core/jni/tvinput/TvInputHal_hidl.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+#include "JTvInputHal.h"
+
+// Implement all HIDL related functions here.
+
+namespace android {
+
+void JTvInputHal::hidlSetUpAudioInfo(JNIEnv* env, jobject& builder,
+                                     const TvInputDeviceInfoWrapper& info) {
+    env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.audioType,
+                          info.hidlAudioType);
+    if (info.hidlAudioType != HidlAudioDevice::NONE) {
+        uint8_t buffer[info.hidlAudioAddress.size() + 1];
+        memcpy(buffer, info.hidlAudioAddress.data(), info.hidlAudioAddress.size());
+        buffer[info.hidlAudioAddress.size()] = '\0';
+        jstring audioAddress = env->NewStringUTF(reinterpret_cast<const char*>(buffer));
+        env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress,
+                              audioAddress);
+        env->DeleteLocalRef(audioAddress);
+    }
+}
+
+JTvInputHal::TvInputDeviceInfoWrapper
+JTvInputHal::TvInputDeviceInfoWrapper::createDeviceInfoWrapper(
+        const HidlTvInputDeviceInfo& hidlTvInputDeviceInfo) {
+    TvInputDeviceInfoWrapper deviceInfo;
+    deviceInfo.isHidl = true;
+    deviceInfo.deviceId = hidlTvInputDeviceInfo.deviceId;
+    deviceInfo.type = TvInputType(static_cast<int32_t>(hidlTvInputDeviceInfo.type));
+    deviceInfo.portId = hidlTvInputDeviceInfo.portId;
+    deviceInfo.cableConnectionStatus = CableConnectionStatus(
+            static_cast<int32_t>(hidlTvInputDeviceInfo.cableConnectionStatus));
+    deviceInfo.hidlAudioType = hidlTvInputDeviceInfo.audioType;
+    deviceInfo.hidlAudioAddress = hidlTvInputDeviceInfo.audioAddress;
+    return deviceInfo;
+}
+
+JTvInputHal::TvInputEventWrapper JTvInputHal::TvInputEventWrapper::createEventWrapper(
+        const HidlTvInputEvent& hidlTvInputEvent) {
+    TvInputEventWrapper event;
+    event.type = TvInputEventType(static_cast<int32_t>(hidlTvInputEvent.type));
+    event.deviceInfo =
+            TvInputDeviceInfoWrapper::createDeviceInfoWrapper(hidlTvInputEvent.deviceInfo);
+    return event;
+}
+
+Return<void> JTvInputHal::TvInputCallback::notify(const HidlTvInputEvent& event) {
+    mHal->mLooper->sendMessage(new NotifyHandler(mHal,
+                                                 TvInputEventWrapper::createEventWrapper(event)),
+                               static_cast<int>(event.type));
+    return Void();
+}
+
+JTvInputHal::ITvInputWrapper::ITvInputWrapper(sp<HidlITvInput>& hidlTvInput)
+      : mIsHidl(true), mHidlTvInput(hidlTvInput) {}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::hidlSetCallback(
+        const std::shared_ptr<TvInputCallback>& in_callback) {
+    mHidlTvInput->setCallback(in_callback == nullptr ? nullptr
+                                                     : sp<TvInputCallback>(in_callback.get()));
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::hidlGetStreamConfigurations(
+        int32_t in_deviceId, std::vector<AidlTvStreamConfig>* _aidl_return) {
+    Result result = Result::UNKNOWN;
+    hidl_vec<HidlTvStreamConfig> list;
+    mHidlTvInput->getStreamConfigurations(in_deviceId,
+                                          [&result, &list](Result res,
+                                                           hidl_vec<HidlTvStreamConfig> configs) {
+                                              result = res;
+                                              if (res == Result::OK) {
+                                                  list = configs;
+                                              }
+                                          });
+    if (result != Result::OK) {
+        ALOGE("Couldn't get stream configs for device id:%d result:%d", in_deviceId, result);
+        return ::ndk::ScopedAStatus::fromServiceSpecificError(static_cast<int32_t>(result));
+    }
+    for (size_t i = 0; i < list.size(); ++i) {
+        AidlTvStreamConfig config;
+        config.streamId = list[i].streamId;
+        config.maxVideoHeight = list[i].maxVideoHeight;
+        config.maxVideoWidth = list[i].maxVideoWidth;
+        _aidl_return->push_back(config);
+    }
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::hidlOpenStream(int32_t in_deviceId,
+                                                                  int32_t in_streamId,
+                                                                  AidlNativeHandle* _aidl_return) {
+    Result result = Result::UNKNOWN;
+    native_handle_t* sidebandStream;
+    mHidlTvInput->openStream(in_deviceId, in_streamId,
+                             [&result, &sidebandStream](Result res, const native_handle_t* handle) {
+                                 result = res;
+                                 if (res == Result::OK) {
+                                     if (handle) {
+                                         sidebandStream = native_handle_clone(handle);
+                                     }
+                                 }
+                             });
+    if (result != Result::OK) {
+        ALOGE("Couldn't open stream. device id:%d stream id:%d result:%d", in_deviceId, in_streamId,
+              result);
+        return ::ndk::ScopedAStatus::fromServiceSpecificError(static_cast<int32_t>(result));
+    }
+    *_aidl_return = makeToAidl(sidebandStream);
+    native_handle_delete(sidebandStream);
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::hidlCloseStream(int32_t in_deviceId,
+                                                                   int32_t in_streamId) {
+    Result result = mHidlTvInput->closeStream(in_deviceId, in_streamId);
+    if (result != Result::OK) {
+        return ::ndk::ScopedAStatus::fromServiceSpecificError(static_cast<int32_t>(result));
+    }
+    return ::ndk::ScopedAStatus::ok();
+}
+
+} // namespace android
diff --git a/services/core/jni/tvinput/TvInputHal_hidl.h b/services/core/jni/tvinput/TvInputHal_hidl.h
new file mode 100644
index 0000000..948f86b
--- /dev/null
+++ b/services/core/jni/tvinput/TvInputHal_hidl.h
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+// Include all HIDL related files/names here.
+
+#include <android/hardware/tv/input/1.0/ITvInput.h>
+#include <android/hardware/tv/input/1.0/ITvInputCallback.h>
+#include <android/hardware/tv/input/1.0/types.h>
+
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::tv::input::V1_0::Result;
+
+using HidlAudioDevice = ::android::hardware::audio::common::V2_0::AudioDevice;
+using HidlITvInput = ::android::hardware::tv::input::V1_0::ITvInput;
+using HidlITvInputCallback = ::android::hardware::tv::input::V1_0::ITvInputCallback;
+using HidlTvInputDeviceInfo = ::android::hardware::tv::input::V1_0::TvInputDeviceInfo;
+using HidlTvInputEvent = ::android::hardware::tv::input::V1_0::TvInputEvent;
+using HidlTvStreamConfig = ::android::hardware::tv::input::V1_0::TvStreamConfig;
diff --git a/services/core/jni/tvinput/jstruct.h b/services/core/jni/tvinput/jstruct.h
new file mode 100644
index 0000000..0a4a64d
--- /dev/null
+++ b/services/core/jni/tvinput/jstruct.h
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "jni.h"
+
+typedef struct {
+    jmethodID deviceAvailable;
+    jmethodID deviceUnavailable;
+    jmethodID streamConfigsChanged;
+    jmethodID firstFrameCaptured;
+} gTvInputHalClassInfoType;
+
+typedef struct {
+    jclass clazz;
+} gTvStreamConfigClassInfoType;
+
+typedef struct {
+    jclass clazz;
+
+    jmethodID constructor;
+    jmethodID streamId;
+    jmethodID type;
+    jmethodID maxWidth;
+    jmethodID maxHeight;
+    jmethodID generation;
+    jmethodID build;
+} gTvStreamConfigBuilderClassInfoType;
+
+typedef struct {
+    jclass clazz;
+
+    jmethodID constructor;
+    jmethodID deviceId;
+    jmethodID type;
+    jmethodID hdmiPortId;
+    jmethodID cableConnectionStatus;
+    jmethodID audioType;
+    jmethodID audioAddress;
+    jmethodID build;
+} gTvInputHardwareInfoBuilderClassInfoType;
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index e14139a..842d97a 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -50,6 +50,7 @@
     <xs:complexType name="display">
         <xs:sequence>
             <xs:element name="address" type="xs:nonNegativeInteger"/>
+            <xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
         </xs:sequence>
         <xs:attribute name="enabled" type="xs:boolean" use="optional" />
         <xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" />
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index f391575..55f866c 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -4,11 +4,13 @@
   public class Display {
     ctor public Display();
     method public java.math.BigInteger getAddress();
+    method public String getPosition();
     method public boolean isDefaultDisplay();
     method public boolean isEnabled();
     method public void setAddress(java.math.BigInteger);
     method public void setDefaultDisplay(boolean);
     method public void setEnabled(boolean);
+    method public void setPosition(String);
   }
 
   public class Layout {
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
new file mode 100644
index 0000000..3d337b8
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -0,0 +1,110 @@
+/*
+ * 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.credentials.CreateCredentialRequest;
+import android.credentials.CreateCredentialResponse;
+import android.credentials.CredentialManager;
+import android.credentials.ICreateCredentialCallback;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.RequestInfo;
+import android.os.RemoteException;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Central session for a single {@link CredentialManager#executeCreateCredential} request.
+ * This class listens to the responses from providers, and the UX app, and updates the
+ * provider(s) state maintained in {@link ProviderCreateSession}.
+ */
+public final class CreateRequestSession extends RequestSession<CreateCredentialRequest,
+        ICreateCredentialCallback>
+        implements ProviderSession.ProviderInternalCallback<CreateCredentialResponse> {
+    private static final String TAG = "CreateRequestSession";
+
+    CreateRequestSession(@NonNull Context context, int userId,
+            CreateCredentialRequest request,
+            ICreateCredentialCallback callback,
+            String callingPackage) {
+        super(context, userId, request, callback, RequestInfo.TYPE_CREATE, callingPackage);
+    }
+
+    /**
+     * Creates a new provider session, and adds it to list of providers that are contributing to
+     * this request session.
+     *
+     * @return the provider session that was started
+     */
+    @Override
+    @Nullable
+    public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+            RemoteCredentialService remoteCredentialService) {
+        ProviderCreateSession providerCreateSession = ProviderCreateSession
+                .createNewSession(mContext, mUserId, providerInfo,
+                this, remoteCredentialService);
+        if (providerCreateSession != null) {
+            Log.i(TAG, "In startProviderSession - provider session created and being added");
+            mProviders.put(providerCreateSession.getComponentName().flattenToString(),
+                    providerCreateSession);
+        }
+        return providerCreateSession;
+    }
+
+    @Override
+    protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+        try {
+            mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
+                    RequestInfo.newCreateRequestInfo(
+                            mRequestId, mClientRequest, mIsFirstUiTurn, mClientCallingPackage),
+                    providerDataList));
+        } catch (RemoteException e) {
+            Log.i(TAG, "Issue with invoking pending intent: " + e.getMessage());
+            // TODO: Propagate failure
+        }
+    }
+
+    private void respondToClientAndFinish(CreateCredentialResponse response) {
+        Log.i(TAG, "respondToClientAndFinish");
+        try {
+            mClientCallback.onResponse(response);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+        finishSession();
+    }
+
+    @Override
+    public void onProviderStatusChanged(ProviderSession.Status status,
+            ComponentName componentName) {
+        super.onProviderStatusChanged(status, componentName);
+    }
+
+    @Override
+    public void onFinalResponseReceived(ComponentName componentName,
+            CreateCredentialResponse response) {
+        Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+        if (response != null) {
+            respondToClientAndFinish(response);
+        }
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 40412db..d3b9e10 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -22,8 +22,11 @@
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.credentials.ClearCredentialStateRequest;
 import android.credentials.CreateCredentialRequest;
+import android.credentials.GetCredentialOption;
 import android.credentials.GetCredentialRequest;
+import android.credentials.IClearCredentialStateCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.ICredentialManager;
 import android.credentials.IGetCredentialCallback;
@@ -32,6 +35,8 @@
 import android.os.ICancellationSignal;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.service.credentials.BeginCreateCredentialRequest;
+import android.service.credentials.GetCredentialsRequest;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -42,6 +47,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * Entry point service for credential management.
@@ -90,17 +96,15 @@
             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])) {
+        for (String serviceName : serviceNames) {
+            Log.i(TAG, "in newServiceListLocked, service: " + serviceName);
+            if (TextUtils.isEmpty(serviceName)) {
                 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) {
+                        serviceName));
+            } catch (PackageManager.NameNotFoundException | SecurityException e) {
                 Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
             }
         }
@@ -114,15 +118,31 @@
             synchronized (mLock) {
                 final List<CredentialManagerServiceImpl> services =
                         getServiceListForUserLocked(userId);
-                services.forEach(s -> {
+                for (CredentialManagerServiceImpl s : services) {
                     c.accept(s);
-                });
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
     }
 
+    private List<ProviderSession> initiateProviderSessions(RequestSession session,
+            List<String> requestOptions) {
+        List<ProviderSession> providerSessions = new ArrayList<>();
+        // Invoke all services of a user to initiate a provider session
+        runForUser((service) -> {
+            if (service.isServiceCapable(requestOptions)) {
+                ProviderSession providerSession = service
+                        .initiateProviderSessionForRequest(session);
+                if (providerSession != null) {
+                    providerSessions.add(providerSession);
+                }
+            }
+        });
+        return providerSessions;
+    }
+
     final class CredentialManagerServiceStub extends ICredentialManager.Stub {
         @Override
         public ICancellationSignal executeGetCredential(
@@ -136,11 +156,22 @@
             // New request session, scoped for this request only.
             final GetRequestSession session = new GetRequestSession(getContext(),
                     UserHandle.getCallingUserId(),
-                    callback);
+                    callback,
+                    request,
+                    callingPackage);
 
-            // Invoke all services of a user
-            runForUser((service) -> {
-                service.getCredential(request, session, callingPackage);
+            // Initiate all provider sessions
+            List<ProviderSession> providerSessions =
+                    initiateProviderSessions(session, request.getGetCredentialOptions()
+                            .stream().map(GetCredentialOption::getType)
+                            .collect(Collectors.toList()));
+            // TODO : Return error when no providers available
+
+            // Iterate over all provider sessions and invoke the request
+            providerSessions.forEach(providerGetSession -> {
+                providerGetSession.getRemoteCredentialService().onGetCredentials(
+                        (GetCredentialsRequest) providerGetSession.getProviderRequest(),
+                        /*callback=*/providerGetSession);
             });
             return cancelTransport;
         }
@@ -150,8 +181,37 @@
                 CreateCredentialRequest request,
                 ICreateCredentialCallback callback,
                 String callingPackage) {
+            Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
+            // TODO : Implement cancellation
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+            // New request session, scoped for this request only.
+            final CreateRequestSession session = new CreateRequestSession(getContext(),
+                    UserHandle.getCallingUserId(),
+                    request,
+                    callback,
+                    callingPackage);
+
+            // Initiate all provider sessions
+            List<ProviderSession> providerSessions =
+                    initiateProviderSessions(session, List.of(request.getType()));
+            // TODO : Return error when no providers available
+
+            // Iterate over all provider sessions and invoke the request
+            providerSessions.forEach(providerCreateSession -> {
+                providerCreateSession.getRemoteCredentialService().onCreateCredential(
+                        (BeginCreateCredentialRequest)
+                                providerCreateSession.getProviderRequest(),
+                        /*callback=*/providerCreateSession);
+            });
+            return cancelTransport;
+        }
+
+        @Override
+        public ICancellationSignal clearCredentialState(ClearCredentialStateRequest request,
+                IClearCredentialStateCallback callback, String callingPackage) {
             // TODO: implement.
-            Log.i(TAG, "executeCreateCredential");
+            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 cc03f9b..0c32304 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -21,13 +21,13 @@
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
-import android.credentials.GetCredentialRequest;
 import android.service.credentials.CredentialProviderInfo;
-import android.service.credentials.GetCredentialsRequest;
 import android.util.Slog;
 
 import com.android.server.infra.AbstractPerUserSystemService;
 
+import java.util.List;
+
 
 /**
  * Per-user, per remote service implementation of {@link CredentialManagerService}
@@ -61,50 +61,38 @@
         return mInfo.getServiceInfo();
     }
 
-    public void getCredential(GetCredentialRequest request, GetRequestSession requestSession,
-            String callingPackage) {
-        Slog.i(TAG, "in getCredential in CredManServiceImpl");
+    /**
+     * Starts a provider session and associates it with the given request session. */
+    @Nullable
+    public ProviderSession initiateProviderSessionForRequest(
+            RequestSession requestSession) {
+        Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl");
         if (mInfo == null) {
-            Slog.i(TAG, "in getCredential in CredManServiceImpl, but mInfo is null");
-            return;
+            Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl, "
+                    + "but mInfo is null. This shouldn't happen");
+            return null;
         }
-
-        // 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);
-        }
+        ProviderSession providerSession =
+                requestSession.initiateProviderSession(mInfo, remoteService);
+        return 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 true if at least one capability found. */
+    boolean isServiceCapable(List<String> requestedOptions) {
+        if (mInfo == null) {
+            Slog.i(TAG, "in isServiceCapable, mInfo is null");
+            return false;
         }
-        return null;
+        for (String capability : requestedOptions) {
+            if (mInfo.hasCapability(capability)) {
+                Slog.i(TAG, "Provider can handle: " + capability);
+                return true;
+            } else {
+                Slog.i(TAG, "Provider cannot handle: " + capability);
+            }
+        }
+        return false;
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 69fb1ea..33c5ec9 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -16,7 +16,7 @@
 package com.android.server.credentials;
 
 import android.annotation.NonNull;
-import android.app.Activity;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.credentials.ui.IntentFactory;
@@ -31,6 +31,7 @@
 import android.util.Slog;
 
 import java.util.ArrayList;
+import java.util.UUID;
 
 /** Initiates the Credential Manager UI and receives results. */
 public class CredentialManagerUi {
@@ -38,6 +39,7 @@
     @NonNull
     private final CredentialManagerUiCallback mCallbacks;
     @NonNull private final Context mContext;
+    // TODO : Use for starting the activity for this user
     private final int mUserId;
     @NonNull private final ResultReceiver mResultReceiver = new ResultReceiver(
             new Handler(Looper.getMainLooper())) {
@@ -48,7 +50,7 @@
     };
 
     private void handleUiResult(int resultCode, Bundle resultData) {
-        if (resultCode == Activity.RESULT_OK) {
+        if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION) {
             UserSelectionDialogResult selection = UserSelectionDialogResult
                     .fromResultData(resultData);
             if (selection != null) {
@@ -56,8 +58,8 @@
             } else {
                 Slog.i(TAG, "No selection found in UI result");
             }
-        } else if (resultCode == Activity.RESULT_CANCELED) {
-            mCallbacks.onUiCancelation();
+        } else if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_CANCELED) {
+            mCallbacks.onUiCancellation();
         }
     }
 
@@ -68,7 +70,7 @@
         /** Called when the user makes a selection. */
         void onUiSelection(UserSelectionDialogResult selection);
         /** Called when the user cancels the UI. */
-        void onUiCancelation();
+        void onUiCancellation();
     }
     public CredentialManagerUi(Context context, int userId,
             CredentialManagerUiCallback callbacks) {
@@ -79,14 +81,20 @@
     }
 
     /**
-     * Surfaces the Credential Manager bottom sheet UI.
+     * Creates a {@link PendingIntent} to be used to invoke the credential manager selector UI,
+     * by the calling app process.
+     * @param requestInfo the information about the request
      * @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,
-                mResultReceiver);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(intent);
+    public PendingIntent createPendingIntent(
+            RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
+        Log.i(TAG, "In createPendingIntent");
+        Intent intent = IntentFactory.newIntent(requestInfo, providerDataList, new ArrayList<>(),
+                mResultReceiver)
+                .setAction(UUID.randomUUID().toString());
+        //TODO: Create unique pending intent using request code and cancel any pre-existing pending
+        // intents
+        return PendingIntent.getActivity(
+                mContext, /*requestCode=*/0, intent, PendingIntent.FLAG_IMMUTABLE);
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 80f0fec..c092b3a 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -16,149 +16,92 @@
 
 package com.android.server.credentials;
 
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
-import android.credentials.Credential;
+import android.credentials.GetCredentialRequest;
 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.service.credentials.CredentialProviderInfo;
 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 {
+public final class GetRequestSession extends RequestSession<GetCredentialRequest,
+        IGetCredentialCallback>
+        implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
     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<>();
+            IGetCredentialCallback callback, GetCredentialRequest request,
+            String callingPackage) {
+        super(context, userId, request, callback, RequestInfo.TYPE_GET, callingPackage);
     }
 
     /**
-     * Adds a new provider to the list of providers that are contributing to this session.
+     * Creates a new provider session, and adds it list of providers that are contributing to
+     * this session.
+     * @return the provider session created within this request session, for the given provider
+     * info.
      */
-    public void addProviderSession(ProviderGetSession providerSession) {
-        mProviders.put(providerSession.getComponentName().flattenToString(),
-                providerSession);
+    @Override
+    @Nullable
+    public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+            RemoteCredentialService remoteCredentialService) {
+        ProviderGetSession providerGetSession = ProviderGetSession
+                .createNewSession(mContext, mUserId, providerInfo,
+                        this, remoteCredentialService);
+        if (providerGetSession != null) {
+            Log.i(TAG, "In startProviderSession - provider session created and being added");
+            mProviders.put(providerGetSession.getComponentName().flattenToString(),
+                    providerGetSession);
+        }
+        return providerGetSession;
     }
 
     @Override
+    protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+        try {
+            mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
+                    RequestInfo.newGetRequestInfo(
+                    mRequestId, null, mIsFirstUiTurn, ""),
+                    providerDataList));
+        } catch (RemoteException e) {
+            Log.i(TAG, "Issue with invoking pending intent: " + e.getMessage());
+            // TODO: Propagate failure
+        }
+    }
+
+    @Override // from provider session
     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();
-        }
+        super.onProviderStatusChanged(status, componentName);
     }
 
+
     @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();
+    public void onFinalResponseReceived(ComponentName componentName,
+            GetCredentialResponse response) {
+        Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+        if (response != null) {
+            respondToClientAndFinish(response);
         }
     }
 
-    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) {
+    private void respondToClientAndFinish(GetCredentialResponse response) {
+        Log.i(TAG, "respondToClientAndFinish");
         try {
-            mClientCallback.onResponse(new GetCredentialResponse(credential));
+            mClientCallback.onResponse(response);
         } 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/PendingIntentResultHandler.java b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
new file mode 100644
index 0000000..d0bc074
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
@@ -0,0 +1,70 @@
+/*
+ * 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.app.Activity;
+import android.content.Intent;
+import android.credentials.CreateCredentialResponse;
+import android.credentials.Credential;
+import android.credentials.ui.ProviderPendingIntentResponse;
+import android.service.credentials.CredentialProviderService;
+import android.service.credentials.CredentialsResponseContent;
+
+/**
+ * Helper class for setting up pending intent, and extracting objects from it.
+ *
+ * @hide
+ */
+public class PendingIntentResultHandler {
+    /** Returns true if the result is successful and may contain result extras. */
+    public static boolean isSuccessfulResponse(
+            ProviderPendingIntentResponse pendingIntentResponse) {
+        //TODO: Differentiate based on extra_error in the resultData
+        return pendingIntentResponse.getResultCode() == Activity.RESULT_OK;
+    }
+
+    /** Extracts the {@link CredentialsResponseContent} object added to the result data. */
+    public static CredentialsResponseContent extractResponseContent(Intent resultData) {
+        if (resultData == null) {
+            return null;
+        }
+        return resultData.getParcelableExtra(
+                CredentialProviderService
+                        .EXTRA_GET_CREDENTIALS_CONTENT_RESULT,
+                CredentialsResponseContent.class);
+    }
+
+    /** Extracts the {@link CreateCredentialResponse} object added to the result data. */
+    public static CreateCredentialResponse extractCreateCredentialResponse(Intent resultData) {
+        if (resultData == null) {
+            return null;
+        }
+        return resultData.getParcelableExtra(
+                CredentialProviderService.EXTRA_CREATE_CREDENTIAL_RESULT,
+                CreateCredentialResponse.class);
+    }
+
+    /** Extracts the {@link Credential} object added to the result data. */
+    public static Credential extractCredential(Intent resultData) {
+        if (resultData == null) {
+            return null;
+        }
+        return resultData.getParcelableExtra(
+                CredentialProviderService.EXTRA_CREDENTIAL_RESULT,
+                Credential.class);
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
new file mode 100644
index 0000000..8854453
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -0,0 +1,240 @@
+/*
+ * 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.annotation.UserIdInt;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.credentials.ui.CreateCredentialProviderData;
+import android.credentials.ui.Entry;
+import android.credentials.ui.ProviderPendingIntentResponse;
+import android.service.credentials.BeginCreateCredentialRequest;
+import android.service.credentials.BeginCreateCredentialResponse;
+import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.CreateEntry;
+import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialProviderService;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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 ProviderCreateSession extends ProviderSession<
+        BeginCreateCredentialRequest, BeginCreateCredentialResponse> {
+    private static final String TAG = "ProviderCreateSession";
+
+    // Key to be used as an entry key for a save entry
+    private static final String SAVE_ENTRY_KEY = "save_entry_key";
+
+    @NonNull
+    private final Map<String, CreateEntry> mUiSaveEntries = new HashMap<>();
+    /** The complete request to be used in the second round. */
+    private final CreateCredentialRequest mCompleteRequest;
+
+    /** Creates a new provider session to be used by the request session. */
+    @Nullable public static ProviderCreateSession createNewSession(
+            Context context,
+            @UserIdInt int userId,
+            CredentialProviderInfo providerInfo,
+            CreateRequestSession createRequestSession,
+            RemoteCredentialService remoteCredentialService) {
+        CreateCredentialRequest providerCreateRequest =
+                createProviderRequest(providerInfo.getCapabilities(),
+                        createRequestSession.mClientRequest,
+                        createRequestSession.mClientCallingPackage);
+        if (providerCreateRequest != null) {
+            BeginCreateCredentialRequest providerBeginCreateRequest =
+                    new BeginCreateCredentialRequest(
+                            providerCreateRequest.getCallingPackage(),
+                            providerCreateRequest.getType(),
+                            createRequestSession.mClientRequest.getCandidateQueryData());
+            return new ProviderCreateSession(context, providerInfo, createRequestSession, userId,
+                    remoteCredentialService, providerBeginCreateRequest, providerCreateRequest);
+        }
+        Log.i(TAG, "Unable to create provider session");
+        return null;
+    }
+
+    @Nullable
+    private static CreateCredentialRequest createProviderRequest(List<String> providerCapabilities,
+            android.credentials.CreateCredentialRequest clientRequest,
+            String clientCallingPackage) {
+        String capability = clientRequest.getType();
+        if (providerCapabilities.contains(capability)) {
+            return new CreateCredentialRequest(clientCallingPackage, capability,
+                    clientRequest.getCredentialData());
+        }
+        Log.i(TAG, "Unable to create provider request - capabilities do not match");
+        return null;
+    }
+
+    private ProviderCreateSession(
+            @NonNull Context context,
+            @NonNull CredentialProviderInfo info,
+            @NonNull ProviderInternalCallback callbacks,
+            @UserIdInt int userId,
+            @NonNull RemoteCredentialService remoteCredentialService,
+            @NonNull BeginCreateCredentialRequest beginCreateRequest,
+            @NonNull CreateCredentialRequest completeCreateRequest) {
+        super(context, info, beginCreateRequest, callbacks, userId,
+                remoteCredentialService);
+        mCompleteRequest = completeCreateRequest;
+        setStatus(Status.PENDING);
+    }
+
+    /** Returns the save entry maintained in state by this provider session. */
+    public CreateEntry getUiSaveEntry(String entryId) {
+        return mUiSaveEntries.get(entryId);
+    }
+
+    @Override
+    public void onProviderResponseSuccess(
+            @Nullable BeginCreateCredentialResponse response) {
+        Log.i(TAG, "in onProviderResponseSuccess");
+        onUpdateResponse(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");
+        }
+    }
+
+    private void onUpdateResponse(BeginCreateCredentialResponse response) {
+        Log.i(TAG, "updateResponse with save entries");
+        mProviderResponse = response;
+        updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
+    }
+
+    @Override
+    @Nullable protected CreateCredentialProviderData prepareUiData()
+            throws IllegalArgumentException {
+        Log.i(TAG, "In prepareUiData");
+        if (!ProviderSession.isUiInvokingStatus(getStatus())) {
+            Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
+            return null;
+        }
+        final BeginCreateCredentialResponse response = getProviderResponse();
+        if (response == null) {
+            Log.i(TAG, "In prepareUiData response null");
+            throw new IllegalStateException("Response must be in completion mode");
+        }
+        if (response.getCreateEntries() != null) {
+            Log.i(TAG, "In prepareUiData save entries not null");
+            return prepareUiProviderData(
+                    prepareUiSaveEntries(response.getCreateEntries()),
+                    null,
+                    /*isDefaultProvider=*/false);
+        }
+        return null;
+    }
+
+    @Override
+    public void onUiEntrySelected(String entryType, String entryKey,
+            ProviderPendingIntentResponse providerPendingIntentResponse) {
+        switch (entryType) {
+            case SAVE_ENTRY_KEY:
+                if (mUiSaveEntries.containsKey(entryKey)) {
+                    onSaveEntrySelected(providerPendingIntentResponse);
+                } else {
+                    //TODO: Handle properly
+                    Log.i(TAG, "Unexpected save entry key");
+                }
+                break;
+            case REMOTE_ENTRY_KEY:
+                if (mUiRemoteEntry.first.equals(entryKey)) {
+                    onRemoteEntrySelected(providerPendingIntentResponse);
+                } else {
+                    //TODO: Handle properly
+                    Log.i(TAG, "Unexpected remote entry key");
+                }
+                break;
+            default:
+                Log.i(TAG, "Unsupported entry type selected");
+        }
+    }
+
+    private List<Entry> prepareUiSaveEntries(@NonNull List<CreateEntry> saveEntries) {
+        Log.i(TAG, "in populateUiSaveEntries");
+        List<Entry> uiSaveEntries = new ArrayList<>();
+
+        // Populate the save entries
+        for (CreateEntry createEntry : saveEntries) {
+            String entryId = generateEntryId();
+            mUiSaveEntries.put(entryId, createEntry);
+            Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
+            uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, createEntry.getSlice(),
+                    createEntry.getPendingIntent(), setUpFillInIntent(
+                            createEntry.getPendingIntent())));
+        }
+        return uiSaveEntries;
+    }
+
+    private Intent setUpFillInIntent(PendingIntent pendingIntent) {
+        Intent intent = pendingIntent.getIntent();
+        intent.putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
+                mCompleteRequest);
+        return intent;
+    }
+
+    private CreateCredentialProviderData prepareUiProviderData(List<Entry> saveEntries,
+            Entry remoteEntry, boolean isDefaultProvider) {
+        return new CreateCredentialProviderData.Builder(
+                mComponentName.flattenToString())
+                .setSaveEntries(saveEntries)
+                .setIsDefaultProvider(isDefaultProvider)
+                .build();
+    }
+
+    private void onSaveEntrySelected(ProviderPendingIntentResponse pendingIntentResponse) {
+        if (pendingIntentResponse == null) {
+            return;
+            //TODO: Handle failure if pending intent is null
+        }
+        if (PendingIntentResultHandler.isSuccessfulResponse(pendingIntentResponse)) {
+            android.credentials.CreateCredentialResponse credentialResponse =
+                    PendingIntentResultHandler.extractCreateCredentialResponse(
+                            pendingIntentResponse.getResultData());
+            if (credentialResponse != null) {
+                mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
+                return;
+            }
+        }
+        //TODO: Handle failure case is pending intent response does not have a credential
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 24610df..6cd011b 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -18,15 +18,22 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.slice.Slice;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.credentials.Credential;
+import android.credentials.GetCredentialOption;
+import android.credentials.GetCredentialResponse;
 import android.credentials.ui.Entry;
-import android.credentials.ui.ProviderData;
+import android.credentials.ui.GetCredentialProviderData;
+import android.credentials.ui.ProviderPendingIntentResponse;
 import android.service.credentials.Action;
 import android.service.credentials.CredentialEntry;
 import android.service.credentials.CredentialProviderInfo;
-import android.service.credentials.CredentialsDisplayContent;
+import android.service.credentials.CredentialsResponseContent;
+import android.service.credentials.GetCredentialsRequest;
 import android.service.credentials.GetCredentialsResponse;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 
 import java.util.ArrayList;
@@ -38,75 +45,102 @@
 /**
  * Central provider session that listens for provider callbacks, and maintains provider state.
  * Will likely split this into remote response state and UI state.
+ *
+ * @hide
  */
-public final class ProviderGetSession extends ProviderSession<GetCredentialsResponse>
-        implements RemoteCredentialService.ProviderCallbacks<GetCredentialsResponse> {
+public final class ProviderGetSession extends ProviderSession<GetCredentialsRequest,
+        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;
+    // Key to be used as the entry key for an action entry
+    private static final String ACTION_ENTRY_KEY = "action_key";
+    // Key to be used as the entry key for the authentication entry
+    private static final String AUTHENTICATION_ACTION_ENTRY_KEY = "authentication_action_key";
 
     @NonNull
-    private final Map<String, CredentialEntry> mUiCredentials = new HashMap<>();
-
+    private final Map<String, CredentialEntry> mUiCredentialEntries = 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
+    private final Map<String, Action> mUiActionsEntries = new HashMap<>();
     @Nullable
-    public GetCredentialsResponse getResponse() {
-        return  mResponse;
+    private Pair<String, Action> mUiAuthenticationAction = null;
+
+    /** Creates a new provider session to be used by the request session. */
+    @Nullable public static ProviderGetSession createNewSession(
+            Context context,
+            @UserIdInt int userId,
+            CredentialProviderInfo providerInfo,
+            GetRequestSession getRequestSession,
+            RemoteCredentialService remoteCredentialService) {
+        GetCredentialsRequest providerRequest =
+                createProviderRequest(providerInfo.getCapabilities(),
+                        getRequestSession.mClientRequest,
+                        getRequestSession.mClientCallingPackage);
+        if (providerRequest != null) {
+            return new ProviderGetSession(context, providerInfo, getRequestSession, userId,
+                    remoteCredentialService, providerRequest);
+        }
+        Log.i(TAG, "Unable to create provider session");
+        return null;
+    }
+
+    @Nullable
+    private static GetCredentialsRequest createProviderRequest(List<String> providerCapabilities,
+            android.credentials.GetCredentialRequest clientRequest,
+            String clientCallingPackage) {
+        List<GetCredentialOption> filteredOptions = new ArrayList<>();
+        for (GetCredentialOption option : clientRequest.getGetCredentialOptions()) {
+            if (providerCapabilities.contains(option.getType())) {
+                Log.i(TAG, "In createProviderRequest - capability found : "
+                        + option.getType());
+                filteredOptions.add(option);
+            } else {
+                Log.i(TAG, "In createProviderRequest - capability not "
+                        + "found : " + option.getType());
+            }
+        }
+        if (!filteredOptions.isEmpty()) {
+            return new GetCredentialsRequest.Builder(clientCallingPackage).setGetCredentialOptions(
+                    filteredOptions).build();
+        }
+        Log.i(TAG, "In createProviderRequest - returning null");
+        return null;
+    }
+
+    public ProviderGetSession(Context context,
+            CredentialProviderInfo info,
+            ProviderInternalCallback callbacks,
+            int userId, RemoteCredentialService remoteCredentialService,
+            GetCredentialsRequest request) {
+        super(context, info, request, callbacks, userId, remoteCredentialService);
+        setStatus(Status.PENDING);
     }
 
     /** 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);
+        return mUiCredentialEntries.get(entryId);
     }
 
     /** Called when the provider response has been updated by an external source. */
-    @Override
+    @Override // Callback from the remote provider
     public void onProviderResponseSuccess(@Nullable GetCredentialsResponse response) {
         Log.i(TAG, "in onProviderResponseSuccess");
-        updateResponse(response);
+        onUpdateResponse(response);
     }
 
     /** Called when the provider response resulted in a failure. */
-    @Override
+    @Override // Callback from the remote provider
     public void onProviderResponseFailure(int errorCode, @Nullable CharSequence message) {
         updateStatusAndInvokeCallback(toStatus(errorCode));
     }
 
     /** Called when provider service dies. */
-    @Override
+    @Override // Callback from the remote provider
     public void onProviderServiceDied(RemoteCredentialService service) {
         if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
             updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
@@ -116,82 +150,202 @@
         }
     }
 
-    @Override
-    protected final ProviderData 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");
+    @Override // Selection call from the request provider
+    protected void onUiEntrySelected(String entryType, String entryKey,
+            ProviderPendingIntentResponse providerPendingIntentResponse) {
+        switch (entryType) {
+            case CREDENTIAL_ENTRY_KEY:
+                CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
+                if (credentialEntry == null) {
+                    Log.i(TAG, "Credential entry not found");
+                    //TODO: Handle properly
+                    return;
+                }
+                onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
+                break;
+            case ACTION_ENTRY_KEY:
+                Action actionEntry = mUiActionsEntries.get(entryKey);
+                if (actionEntry == null) {
+                    Log.i(TAG, "Action entry not found");
+                    //TODO: Handle properly
+                    return;
+                }
+                onActionEntrySelected(providerPendingIntentResponse);
+                break;
+            case AUTHENTICATION_ACTION_ENTRY_KEY:
+                if (mUiAuthenticationAction.first.equals(entryKey)) {
+                    onAuthenticationEntrySelected(providerPendingIntentResponse);
+                } else {
+                    //TODO: Handle properly
+                    Log.i(TAG, "Authentication entry not found");
+                }
+                break;
+            case REMOTE_ENTRY_KEY:
+                if (mUiRemoteEntry.first.equals(entryKey)) {
+                    onRemoteEntrySelected(providerPendingIntentResponse);
+                } else {
+                    //TODO: Handle properly
+                    Log.i(TAG, "Remote entry not found");
+                }
+                break;
+            default:
+                Log.i(TAG, "Unsupported entry type selected");
         }
-        GetCredentialsResponse response = getResponse();
-        if (response == null) {
-            Log.i(TAG, "In prepareUiData response null");
+    }
 
+    @Override // Call from request session to data to be shown on the UI
+    @Nullable protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
+        Log.i(TAG, "In prepareUiData");
+        if (!ProviderSession.isUiInvokingStatus(getStatus())) {
+            Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
+                    + mComponentName.flattenToString());
+            return null;
+        }
+        if (mProviderResponse == 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 (mProviderResponse.getAuthenticationAction() != null) {
+            Log.i(TAG, "In prepareUiData - top level authentication mode");
+            return prepareUiProviderData(null, null,
+                    prepareUiAuthenticationAction(mProviderResponse.getAuthenticationAction()),
+                    /*remoteEntry=*/null);
         }
-        if (response.getCredentialsDisplayContent() != null){
-            Log.i(TAG, "In prepareUiData credentials not null");
-
-            return prepareUiProviderDataWithCredentials(response.getCredentialsDisplayContent());
+        if (mProviderResponse.getCredentialsResponseContent() != null) {
+            Log.i(TAG, "In prepareUiData credentialsResponseContent not null");
+            return prepareUiProviderData(prepareUiActionEntries(
+                            mProviderResponse.getCredentialsResponseContent().getActions()),
+                    prepareUiCredentialEntries(mProviderResponse.getCredentialsResponseContent()
+                            .getCredentialEntries()),
+                    /*authenticationAction=*/null,
+                    prepareUiRemoteEntry(mProviderResponse
+                            .getCredentialsResponseContent().getRemoteCredentialEntry()));
         }
         return null;
     }
 
-    /**
-     * To be called by {@link ProviderGetSession} when the UI is to be invoked.
-     */
-    @Nullable
-    private ProviderData prepareUiProviderDataWithCredentials(@NonNull
-            CredentialsDisplayContent content) {
-        Log.i(TAG, "in prepareUiProviderData");
-        List<Entry> credentialEntries = new ArrayList<>();
-        List<Entry> actionChips = new ArrayList<>();
-        Entry authenticationEntry = null;
+    private Entry prepareUiRemoteEntry(CredentialEntry remoteCredentialEntry) {
+        if (remoteCredentialEntry == null) {
+            return null;
+        }
+        String entryId = generateEntryId();
+        Entry remoteEntry = new Entry(REMOTE_ENTRY_KEY, entryId, remoteCredentialEntry.getSlice());
+        mUiRemoteEntry = new Pair<>(entryId, remoteCredentialEntry);
+        return remoteEntry;
+    }
+
+    private Entry prepareUiAuthenticationAction(@NonNull Action authenticationAction) {
+        String entryId = generateEntryId();
+        Entry authEntry = new Entry(
+                AUTHENTICATION_ACTION_ENTRY_KEY, entryId, authenticationAction.getSlice(),
+                authenticationAction.getPendingIntent(), /*fillInIntent=*/null);
+        mUiAuthenticationAction = new Pair<>(entryId, authenticationAction);
+        return authEntry;
+    }
+
+    private List<Entry> prepareUiCredentialEntries(@NonNull
+            List<CredentialEntry> credentialEntries) {
+        Log.i(TAG, "in prepareUiProviderDataWithCredentials");
+        List<Entry> credentialUiEntries = new ArrayList<>();
 
         // Populate the credential entries
-        for (CredentialEntry credentialEntry : content.getCredentialEntries()) {
-            String entryId = UUID.randomUUID().toString();
-            mUiCredentials.put(entryId, credentialEntry);
+        for (CredentialEntry credentialEntry : credentialEntries) {
+            String entryId = generateEntryId();
+            mUiCredentialEntries.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()));
+            if (credentialEntry.getPendingIntent() != null) {
+                credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
+                        credentialEntry.getSlice(), credentialEntry.getPendingIntent(),
+                        /*fillInIntent=*/null));
+            } else if (credentialEntry.getCredential() != null) {
+                credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
+                        credentialEntry.getSlice()));
+            } else {
+                Log.i(TAG, "No credential or pending intent. Should not happen.");
+            }
         }
-        // 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 credentialUiEntries;
+    }
 
-        // TODO : Set the correct last used time
-        return new ProviderData.Builder(mComponentName.flattenToString(),
-                mProviderInfo.getServiceLabel() == null ? "" :
-                        mProviderInfo.getServiceLabel().toString(),
-                /*icon=*/null)
+    private List<Entry> prepareUiActionEntries(@Nullable List<Action> actions) {
+        List<Entry> actionEntries = new ArrayList<>();
+        for (Action action : actions) {
+            String entryId = UUID.randomUUID().toString();
+            mUiActionsEntries.put(entryId, action);
+            // TODO : Remove conversion of string to int after change in Entry class
+            actionEntries.add(new Entry(ACTION_ENTRY_KEY, entryId, action.getSlice(),
+                    action.getPendingIntent(), /*fillInIntent=*/null));
+        }
+        return actionEntries;
+    }
+
+    private GetCredentialProviderData prepareUiProviderData(List<Entry> actionEntries,
+            List<Entry> credentialEntries, Entry authenticationActionEntry,
+            Entry remoteEntry) {
+        return new GetCredentialProviderData.Builder(
+                mComponentName.flattenToString()).setActionChips(actionEntries)
                 .setCredentialEntries(credentialEntries)
-                .setActionChips(actionChips)
-                .setAuthenticationEntry(authenticationEntry)
-                .setLastUsedTimeMillis(0)
+                .setAuthenticationEntry(authenticationActionEntry)
+                .setRemoteEntry(remoteEntry)
                 .build();
     }
 
-    /**
-     * To be called by {@link ProviderGetSession} when the UI is to be invoked.
-     */
-    @Nullable
-    private ProviderData prepareUiProviderDataWithAuthentication(@NonNull
-            Action authenticationEntry) {
-        // TODO : Implement authentication flow
-        return null;
+    private void onCredentialEntrySelected(CredentialEntry credentialEntry,
+            ProviderPendingIntentResponse providerPendingIntentResponse) {
+        if (credentialEntry.getCredential() != null) {
+            mCallbacks.onFinalResponseReceived(mComponentName, new GetCredentialResponse(
+                    credentialEntry.getCredential()));
+            return;
+        } else if (providerPendingIntentResponse != null) {
+            if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
+                Credential credential = PendingIntentResultHandler.extractCredential(
+                        providerPendingIntentResponse.getResultData());
+                if (credential != null) {
+                    mCallbacks.onFinalResponseReceived(mComponentName,
+                            new GetCredentialResponse(credential));
+                    return;
+                }
+            }
+            // TODO: Handle other pending intent statuses
+        }
+        Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
+        // TODO: Propagate failure to client
+    }
+
+    private void onAuthenticationEntrySelected(
+            @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
+        if (providerPendingIntentResponse != null) {
+            if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
+                CredentialsResponseContent content = PendingIntentResultHandler
+                        .extractResponseContent(providerPendingIntentResponse
+                                .getResultData());
+                if (content != null) {
+                    onUpdateResponse(GetCredentialsResponse.createWithResponseContent(content));
+                    return;
+                }
+            }
+            //TODO: Other provider intent statuses
+        }
+        Log.i(TAG, "Display content not present in pending intent result");
+        // TODO: Propagate error to client
+    }
+
+    private void onActionEntrySelected(ProviderPendingIntentResponse
+            providerPendingIntentResponse) {
+        //TODO: Implement if any result expected after an action
+    }
+
+
+    /** Updates the response being maintained in state by this provider session. */
+    private void onUpdateResponse(GetCredentialsResponse response) {
+        mProviderResponse = response;
+        if (response.getAuthenticationAction() != null) {
+            Log.i(TAG , "updateResponse with authentication entry");
+            updateStatusAndInvokeCallback(Status.REQUIRES_AUTHENTICATION);
+        } else if (response.getCredentialsResponseContent() != null) {
+            Log.i(TAG , "updateResponse with credentialEntries");
+            // TODO validate response
+            updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
+        }
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 3a9f964..ac360bd 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -17,67 +17,130 @@
 package com.android.server.credentials;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ComponentName;
+import android.content.Context;
+import android.credentials.Credential;
 import android.credentials.ui.ProviderData;
+import android.credentials.ui.ProviderPendingIntentResponse;
+import android.service.credentials.CredentialEntry;
 import android.service.credentials.CredentialProviderException;
 import android.service.credentials.CredentialProviderInfo;
+import android.util.Pair;
+
+import java.util.UUID;
 
 /**
  * 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.
+ * @param <T> The request to be sent to the provider
+ * @param <R> The response to be expected from the provider
  */
-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";
+public abstract class ProviderSession<T, R>
+        implements RemoteCredentialService.ProviderCallbacks<R> {
+    // Key to be used as an entry key for a remote entry
+    protected static final String REMOTE_ENTRY_KEY = "remote_entry_key";
 
+    @NonNull protected final Context mContext;
     @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;
+    @Nullable protected Credential mFinalCredentialResponse;
+    @NonNull protected final T mProviderRequest;
+    @Nullable protected R mProviderResponse;
+    @Nullable protected Pair<String, CredentialEntry> mUiRemoteEntry;
+
+    /**
+     * Returns true if the given status reflects that the provider state is ready to be shown
+     * on the credMan UI.
+     */
+    public static boolean isUiInvokingStatus(Status status) {
+        return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED
+                || status == Status.REQUIRES_AUTHENTICATION;
+    }
+
+    /**
+     * Returns true if the given status reflects that the provider is waiting for a remote
+     * response.
+     */
+    public static boolean isStatusWaitingForRemoteResponse(Status status) {
+        return status == Status.PENDING;
+    }
+
+    /**
+     * 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 reflects that the provider is done getting the response,
+     * and is ready to return the final credential back to the user.
+     */
+    public static boolean isCompletionStatus(Status status) {
+        return status == Status.CREDENTIAL_RECEIVED_FROM_INTENT
+                || status == Status.CREDENTIAL_RECEIVED_FROM_SELECTION;
+    }
 
     /**
      * 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.
+     * @param <V> the type of the final response expected
      */
-    public interface ProviderInternalCallback {
-        /**
-         * Called when status changes.
-         */
+    public interface ProviderInternalCallback<V> {
+        /** Called when status changes. */
         void onProviderStatusChanged(Status status, ComponentName componentName);
+
+        /** Called when the final credential to be returned to the client has been received. */
+        void onFinalResponseReceived(ComponentName componentName, V response);
     }
 
-    protected ProviderSession(@NonNull CredentialProviderInfo info,
+    protected ProviderSession(@NonNull Context context, @NonNull CredentialProviderInfo info,
+            @NonNull T providerRequest,
             @NonNull ProviderInternalCallback callbacks,
             @NonNull int userId,
             @NonNull RemoteCredentialService remoteCredentialService) {
+        mContext = context;
         mProviderInfo = info;
+        mProviderRequest = providerRequest;
         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. */
+    // TODO: Review status values, and adjust where needed
     enum Status {
         NOT_STARTED,
         PENDING,
         REQUIRES_AUTHENTICATION,
-        COMPLETE,
+        CREDENTIALS_RECEIVED,
         SERVICE_DEAD,
-        CANCELED
+        CREDENTIAL_RECEIVED_FROM_INTENT,
+        PENDING_INTENT_INVOKED,
+        CREDENTIAL_RECEIVED_FROM_SELECTION,
+        SAVE_ENTRIES_RECEIVED, CANCELED
+    }
+
+    /** Converts exception to a provider session status. */
+    @NonNull
+    public static Status toStatus(
+            @CredentialProviderException.CredentialProviderError int errorCode) {
+        // TODO : Add more mappings as more flows are supported
+        return Status.CANCELED;
+    }
+
+    protected String generateEntryId() {
+        return UUID.randomUUID().toString();
+    }
+
+    public Credential getFinalCredentialResponse() {
+        return  mFinalCredentialResponse;
     }
 
     protected void setStatus(@NonNull Status status) {
@@ -94,31 +157,37 @@
         return mComponentName;
     }
 
+    @NonNull
+    protected RemoteCredentialService getRemoteCredentialService() {
+        return mRemoteCredentialService;
+    }
+
     /** 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;
+    protected void onRemoteEntrySelected(
+            ProviderPendingIntentResponse providerPendingIntentResponse) {
+        //TODO: Implement
     }
 
-    /**
-     * 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;
+    /** Get the request to be sent to the provider. */
+    protected T getProviderRequest() {
+        return mProviderRequest;
     }
 
-    /**
-     * 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;
+    /** Update the response state stored with the provider session. */
+    @Nullable protected R getProviderResponse() {
+        return mProviderResponse;
     }
+
+    /** Should be overridden to prepare, and stores state for {@link ProviderData} to be
+     * shown on the UI. */
+    @Nullable protected abstract ProviderData prepareUiData();
+
+    /** Should be overridden to handle the selected entry from the UI. */
+    protected abstract void onUiEntrySelected(String entryType, String entryId,
+            ProviderPendingIntentResponse providerPendingIntentResponse);
 }
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index d0b6e7d..e385bcb 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -24,11 +24,14 @@
 import android.os.Handler;
 import android.os.ICancellationSignal;
 import android.os.RemoteException;
+import android.service.credentials.BeginCreateCredentialRequest;
+import android.service.credentials.BeginCreateCredentialResponse;
 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.IBeginCreateCredentialCallback;
 import android.service.credentials.ICredentialProviderService;
 import android.service.credentials.IGetCredentialsCallback;
 import android.text.format.DateUtils;
@@ -76,7 +79,7 @@
     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,
+                        .setComponent(componentName), /*bindingFlags=*/0,
                 userId, ICredentialProviderService.Stub::asInterface);
         mComponentName = componentName;
     }
@@ -101,7 +104,7 @@
      * 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
+     *                 {@link ProviderGetSession} class that maintains provider state
      */
     public void onGetCredentials(@NonNull GetCredentialsRequest request,
             ProviderCallbacks<GetCredentialsResponse> callback) {
@@ -114,21 +117,21 @@
             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 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));
-                }
-            });
+                        @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);
@@ -137,38 +140,91 @@
             }
             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);
+        futureRef.set(connectThenExecute);
+        connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
+                handleExecutionResponse(result, error, cancellationSink, callback)));
+    }
+
+    /** Main entry point to be called for executing a beginCreateCredential 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 ProviderCreateSession} class that maintains provider state
+     */
+    public void onCreateCredential(@NonNull BeginCreateCredentialRequest request,
+            ProviderCallbacks<BeginCreateCredentialResponse> callback) {
+        Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
+        AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+        AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef =
+                new AtomicReference<>();
+
+        CompletableFuture<BeginCreateCredentialResponse> connectThenExecute = postAsync(service -> {
+            CompletableFuture<BeginCreateCredentialResponse> createCredentialFuture =
+                    new CompletableFuture<>();
+            ICancellationSignal cancellationSignal = service.onBeginCreateCredential(
+                    request, new IBeginCreateCredentialCallback.Stub() {
+                        @Override
+                        public void onSuccess(BeginCreateCredentialResponse response) {
+                            Log.i(TAG, "In onSuccess onBeginCreateCredential "
+                                    + "in RemoteCredentialService");
+                            createCredentialFuture.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);
+                            createCredentialFuture.completeExceptionally(
+                                    new CredentialProviderException(errorCode, errorMsg));
+                        }});
+            CompletableFuture<BeginCreateCredentialResponse> future = futureRef.get();
+            if (future != null && future.isCancelled()) {
+                dispatchCancellationSignal(cancellationSignal);
             } 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());
-                }
+                cancellationSink.set(cancellationSignal);
             }
-        }));
+            return createCredentialFuture;
+        }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+
+        futureRef.set(connectThenExecute);
+        connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
+                handleExecutionResponse(result, error, cancellationSink, callback)));
+    }
+
+    private <T> void handleExecutionResponse(T result,
+            Throwable error,
+            AtomicReference<ICancellationSignal> cancellationSink,
+            ProviderCallbacks<T> callback) {
+        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) {
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 1bacbb3..71fc67c 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -20,18 +20,29 @@
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Context;
+import android.credentials.ui.ProviderData;
 import android.credentials.ui.UserSelectionDialogResult;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * 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 {
+abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialManagerUiCallback{
+    private static final String TAG = "RequestSession";
+
+    // TODO: Revise access levels of attributes
+    @NonNull protected final T mClientRequest;
+    @NonNull protected final U mClientCallback;
     @NonNull protected final IBinder mRequestId;
     @NonNull protected final Context mContext;
     @NonNull protected final CredentialManagerUi mCredentialManagerUi;
@@ -39,29 +50,119 @@
     @NonNull protected final Handler mHandler;
     @NonNull protected boolean mIsFirstUiTurn = true;
     @UserIdInt protected final int mUserId;
+    @NonNull protected final String mClientCallingPackage;
+
+    protected final Map<String, ProviderSession> mProviders = new HashMap<>();
 
     protected RequestSession(@NonNull Context context,
-            @UserIdInt int userId, @NonNull String requestType) {
+            @UserIdInt int userId, @NonNull T clientRequest, U clientCallback,
+            @NonNull String requestType,
+            String clientCallingPackage) {
         mContext = context;
         mUserId = userId;
+        mClientRequest = clientRequest;
+        mClientCallback = clientCallback;
         mRequestType = requestType;
+        mClientCallingPackage = clientCallingPackage;
         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;
+    public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+            RemoteCredentialService remoteCredentialService);
+
+    protected abstract void launchUiWithProviderData(ArrayList<ProviderData> providerDataList);
+
+    // UI callbacks
+
+    @Override // from CredentialManagerUiCallbacks
+    public void onUiSelection(UserSelectionDialogResult selection) {
+        String providerId = selection.getProviderId();
+        Log.i(TAG, "onUiSelection, providerId: " + providerId);
+        ProviderSession providerSession = mProviders.get(providerId);
+        if (providerSession == null) {
+            Log.i(TAG, "providerSession not found in onUiSelection");
+            return;
+        }
+        Log.i(TAG, "Provider session found");
+        providerSession.onUiEntrySelected(selection.getEntryKey(),
+                selection.getEntrySubkey(), selection.getPendingIntentProviderResponse());
     }
 
-    @Override // from CredentialManagerUiCallback
-    public abstract void onUiSelection(UserSelectionDialogResult selection);
+    @Override // from CredentialManagerUiCallbacks
+    public void onUiCancellation() {
+        // User canceled the activity
+        finishSession();
+    }
 
-    @Override // from CredentialManagerUiCallback
-    public abstract void onUiCancelation();
+    protected void onProviderStatusChanged(ProviderSession.Status status,
+            ComponentName componentName) {
+        Log.i(TAG, "in onStatusChanged with status: " + status);
+        if (ProviderSession.isTerminatingStatus(status)) {
+            Log.i(TAG, "in onStatusChanged terminating status");
+            onProviderTerminated(componentName);
+            //TODO: Check if this was the provider we were waiting for and can invoke the UI now
+        } else if (ProviderSession.isCompletionStatus(status)) {
+            Log.i(TAG, "in onStatusChanged isCompletionStatus status");
+            onProviderResponseComplete(componentName);
+        } else if (ProviderSession.isUiInvokingStatus(status)) {
+            Log.i(TAG, "in onStatusChanged isUiInvokingStatus status");
+            onProviderResponseRequiresUi();
+        }
+    }
 
-    @Override // from ProviderInternalCallback
-    public abstract void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName);
+    protected void onProviderTerminated(ComponentName componentName) {
+        //TODO: Implement
+    }
+
+    protected void onProviderResponseComplete(ComponentName componentName) {
+        //TODO: Implement
+    }
+
+    protected void onProviderResponseRequiresUi() {
+        Log.i(TAG, "in onProviderResponseComplete");
+        // TODO: Determine whether UI has already been invoked, and deal accordingly
+        if (!isAnyProviderPending()) {
+            Log.i(TAG, "in onProviderResponseComplete - isResponseCompleteAcrossProviders");
+            getProviderDataAndInitiateUi();
+        } else {
+            Log.i(TAG, "Can't invoke UI - waiting on some providers");
+        }
+    }
+
+    protected void finishSession() {
+        clearProviderSessions();
+    }
+
+    protected void clearProviderSessions() {
+        //TODO: Implement
+        mProviders.clear();
+    }
+
+    private boolean isAnyProviderPending() {
+        for (ProviderSession session : mProviders.values()) {
+            if (ProviderSession.isStatusWaitingForRemoteResponse(session.getStatus())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void getProviderDataAndInitiateUi() {
+        ArrayList<ProviderData> providerDataList = new ArrayList<>();
+        for (ProviderSession session : mProviders.values()) {
+            Log.i(TAG, "preparing data for : " + session.getComponentName());
+            ProviderData providerData = session.prepareUiData();
+            if (providerData != null) {
+                Log.i(TAG, "Provider data is not null");
+                providerDataList.add(providerData);
+            }
+        }
+        if (!providerDataList.isEmpty()) {
+            Log.i(TAG, "provider list not empty about to initiate ui");
+            launchUiWithProviderData(providerDataList);
+        }
+    }
 }
diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp
index 3a9853d..d4d17ec 100644
--- a/services/devicepolicy/Android.bp
+++ b/services/devicepolicy/Android.bp
@@ -22,5 +22,6 @@
     libs: [
         "services.core",
         "app-compat-annotations",
+        "service-permission.stubs.system_server",
     ],
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
new file mode 100644
index 0000000..9cb7533
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.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.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+final class BooleanPolicySerializer extends PolicySerializer<Boolean> {
+
+    @Override
+    void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Boolean value)
+            throws IOException {
+        Objects.requireNonNull(value);
+        serializer.attributeBoolean(/* namespace= */ null, attributeName, value);
+    }
+
+    @Nullable
+    @Override
+    Boolean readFromXml(TypedXmlPullParser parser, String attributeName) {
+        try {
+            return parser.getAttributeBoolean(/* namespace= */ null, attributeName);
+        } catch (XmlPullParserException e) {
+            Log.e(DevicePolicyEngine.TAG, "Error parsing Boolean policy value", e);
+            return null;
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
new file mode 100644
index 0000000..15c8c27
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -0,0 +1,461 @@
+/*
+ * 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.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Environment;
+import android.os.UserHandle;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Class responsible for setting, resolving, and enforcing policies set by multiple management
+ * admins on the device.
+ */
+final class DevicePolicyEngine {
+    static final String TAG = "DevicePolicyEngine";
+
+    private final Context mContext;
+    // TODO(b/256849338): add more granular locks
+    private final Object mLock = new Object();
+
+    /**
+     * Map of <userId, Map<policyKey, policyState>>
+     */
+    private final SparseArray<Map<String, PolicyState<?>>> mUserPolicies;
+
+    /**
+     * Map of <policyKey, policyState>
+     */
+    private final Map<String, PolicyState<?>> mGlobalPolicies;
+
+    DevicePolicyEngine(@NonNull Context context) {
+        mContext = Objects.requireNonNull(context);
+        mUserPolicies = new SparseArray<>();
+        mGlobalPolicies = new HashMap<>();
+    }
+
+    // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
+    /**
+     * Set the policy for the provided {@code policyDefinition}
+     * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
+     * Returns {@code true} if the enforced policy has been changed.
+     *
+     */
+    <V> boolean setLocalPolicy(
+            @NonNull PolicyDefinition<V> policyDefinition,
+            @NonNull EnforcingAdmin enforcingAdmin,
+            @NonNull V value,
+            int userId) {
+
+        Objects.requireNonNull(policyDefinition);
+        Objects.requireNonNull(enforcingAdmin);
+        Objects.requireNonNull(value);
+
+        synchronized (mLock) {
+            PolicyState<V> policyState = getLocalPolicyStateLocked(policyDefinition, userId);
+
+            boolean policyChanged = policyState.setPolicy(enforcingAdmin, value);
+
+            if (policyChanged) {
+                enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
+            }
+            return policyChanged;
+        }
+    }
+
+    // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
+    /**
+     * Set the policy for the provided {@code policyDefinition}
+     * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
+     * Returns {@code true} if the enforced policy has been changed.
+     *
+     */
+    <V> boolean setGlobalPolicy(
+            @NonNull PolicyDefinition<V> policyDefinition,
+            @NonNull EnforcingAdmin enforcingAdmin,
+            @NonNull V value) {
+
+        Objects.requireNonNull(policyDefinition);
+        Objects.requireNonNull(enforcingAdmin);
+        Objects.requireNonNull(value);
+
+        synchronized (mLock) {
+            PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
+
+            boolean policyChanged = policyState.setPolicy(enforcingAdmin, value);
+
+            if (policyChanged) {
+                enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
+                        UserHandle.USER_ALL);
+            }
+            return policyChanged;
+        }
+    }
+
+
+    // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
+    /**
+     * Removes any previously set policy for the provided {@code policyDefinition}
+     * (see {@link PolicyDefinition}) and {@code enforcingAdmin}.
+     * Returns {@code true} if the enforced policy has been changed.
+     *
+     */
+    <V> boolean removeLocalPolicy(
+            @NonNull PolicyDefinition<V> policyDefinition,
+            @NonNull EnforcingAdmin enforcingAdmin,
+            int userId) {
+
+        Objects.requireNonNull(policyDefinition);
+        Objects.requireNonNull(enforcingAdmin);
+
+        synchronized (mLock) {
+            PolicyState<V> policyState = getLocalPolicyStateLocked(policyDefinition, userId);
+            boolean policyChanged = policyState.removePolicy(enforcingAdmin);
+
+            if (policyChanged) {
+                enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
+            }
+            write();
+            return policyChanged;
+        }
+    }
+
+    // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
+    /**
+     * Removes any previously set policy for the provided {@code policyDefinition}
+     * (see {@link PolicyDefinition}) and {@code enforcingAdmin}.
+     * Returns {@code true} if the enforced policy has been changed.
+     *
+     */
+    <V> boolean removeGlobalPolicy(
+            @NonNull PolicyDefinition<V> policyDefinition,
+            @NonNull EnforcingAdmin enforcingAdmin) {
+
+        Objects.requireNonNull(policyDefinition);
+        Objects.requireNonNull(enforcingAdmin);
+
+        synchronized (mLock) {
+            PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
+            boolean policyChanged = policyState.removePolicy(enforcingAdmin);
+
+            if (policyChanged) {
+                enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
+                        UserHandle.USER_ALL);
+            }
+            write();
+            return policyChanged;
+        }
+    }
+
+    /**
+     * Retrieves policies set by all admins for the provided {@code policyDefinition}.
+     *
+     */
+    <V> PolicyState<V> getLocalPolicy(@NonNull PolicyDefinition<V> policyDefinition, int userId) {
+        Objects.requireNonNull(policyDefinition);
+
+        synchronized (mLock) {
+            return getLocalPolicyStateLocked(policyDefinition, userId);
+        }
+    }
+
+    /**
+     * Retrieves policies set by all admins for the provided {@code policyDefinition}.
+     *
+     */
+    <V> PolicyState<V> getGlobalPolicy(@NonNull PolicyDefinition<V> policyDefinition) {
+        Objects.requireNonNull(policyDefinition);
+
+        synchronized (mLock) {
+            return getGlobalPolicyStateLocked(policyDefinition);
+        }
+    }
+
+    @NonNull
+    private <V> PolicyState<V> getLocalPolicyStateLocked(
+            PolicyDefinition<V> policyDefinition, int userId) {
+
+        if (policyDefinition.isGlobalOnlyPolicy()) {
+            throw new IllegalArgumentException("Can't set global policy "
+                    + policyDefinition.getPolicyKey() + " locally.");
+        }
+
+        if (!mUserPolicies.contains(userId)) {
+            mUserPolicies.put(userId, new HashMap<>());
+        }
+        if (!mUserPolicies.get(userId).containsKey(policyDefinition.getPolicyKey())) {
+            mUserPolicies.get(userId).put(
+                    policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition));
+        }
+        return getPolicyState(mUserPolicies.get(userId), policyDefinition);
+    }
+
+    @NonNull
+    private <V> PolicyState<V> getGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {
+
+        if (policyDefinition.isLocalOnlyPolicy()) {
+            throw new IllegalArgumentException("Can't set local policy "
+                    + policyDefinition.getPolicyKey() + " globally.");
+        }
+
+        if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) {
+            mGlobalPolicies.put(
+                    policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition));
+        }
+        return getPolicyState(mGlobalPolicies, policyDefinition);
+    }
+
+    private static <V> PolicyState<V> getPolicyState(
+            Map<String, PolicyState<?>> policies, PolicyDefinition<V> policyDefinition) {
+        try {
+            // This will not throw an exception because policyDefinition is of type V, so unless
+            // we've created two policies with the same key but different types - we can only have
+            // stored a PolicyState of the right type.
+            PolicyState<V> policyState = (PolicyState<V>) policies.get(
+                    policyDefinition.getPolicyKey());
+            return policyState;
+        } catch (ClassCastException exception) {
+            // TODO: handle exception properly
+            throw new IllegalArgumentException();
+        }
+    }
+
+    private <V> void enforcePolicy(
+            PolicyDefinition<V> policyDefinition, @Nullable V policyValue, int userId) {
+        // TODO: null policyValue means remove any enforced policies, ensure callbacks handle this
+        //  properly
+        policyDefinition.enforcePolicy(policyValue, mContext, userId);
+        // TODO: send broadcast or call callback to notify admins of policy change
+        // TODO: notify calling admin of result (e.g. success, runtime failure, policy set by
+        //  a different admin)
+    }
+
+    private void write() {
+        Log.d(TAG, "Writing device policies to file.");
+        new DevicePoliciesReaderWriter().writeToFileLocked();
+    }
+
+    // TODO(b/256852787): trigger resolving logic after loading policies as roles are recalculated
+    //  and could result in a different enforced policy
+    void load() {
+        Log.d(TAG, "Reading device policies from file.");
+        synchronized (mLock) {
+            clear();
+            new DevicePoliciesReaderWriter().readFromFileLocked();
+        }
+    }
+
+    private void clear() {
+        synchronized (mLock) {
+            mGlobalPolicies.clear();
+            mUserPolicies.clear();
+        }
+    }
+
+    private class DevicePoliciesReaderWriter {
+        private static final String DEVICE_POLICIES_XML = "device_policies.xml";
+        private static final String TAG_USER_POLICY_ENTRY = "user-policy-entry";
+        private static final String TAG_DEVICE_POLICY_ENTRY = "device-policy-entry";
+        private static final String TAG_ADMINS_POLICY_ENTRY = "admins-policy-entry";
+        private static final String ATTR_USER_ID = "user-id";
+        private static final String ATTR_POLICY_ID = "policy-id";
+
+        private final File mFile;
+
+        private DevicePoliciesReaderWriter() {
+            mFile = new File(Environment.getDataSystemDirectory(), DEVICE_POLICIES_XML);
+        }
+
+        void writeToFileLocked() {
+            Log.d(TAG, "Writing to " + mFile);
+
+            AtomicFile f = new AtomicFile(mFile);
+            FileOutputStream outputStream = null;
+            try {
+                outputStream = f.startWrite();
+                TypedXmlSerializer out = Xml.resolveSerializer(outputStream);
+
+                out.startDocument(null, true);
+
+                // Actual content
+                writeInner(out);
+
+                out.endDocument();
+                out.flush();
+
+                // Commit the content.
+                f.finishWrite(outputStream);
+                outputStream = null;
+
+            } catch (IOException e) {
+                Log.e(TAG, "Exception when writing", e);
+                if (outputStream != null) {
+                    f.failWrite(outputStream);
+                }
+            }
+        }
+
+        // TODO(b/256846294): Add versioning to read/write
+        void writeInner(TypedXmlSerializer serializer) throws IOException {
+            writeUserPoliciesInner(serializer);
+            writeDevicePoliciesInner(serializer);
+        }
+
+        private void writeUserPoliciesInner(TypedXmlSerializer serializer) throws IOException {
+            if (mUserPolicies != null) {
+                for (int i = 0; i < mUserPolicies.size(); i++) {
+                    int userId = mUserPolicies.keyAt(i);
+                    for (Map.Entry<String, PolicyState<?>> policy : mUserPolicies.get(
+                            userId).entrySet()) {
+                        serializer.startTag(/* namespace= */ null, TAG_USER_POLICY_ENTRY);
+
+                        serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, userId);
+                        serializer.attribute(
+                                /* namespace= */ null, ATTR_POLICY_ID, policy.getKey());
+
+                        serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+                        policy.getValue().saveToXml(serializer);
+                        serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+
+                        serializer.endTag(/* namespace= */ null, TAG_USER_POLICY_ENTRY);
+                    }
+                }
+            }
+        }
+
+        private void writeDevicePoliciesInner(TypedXmlSerializer serializer) throws IOException {
+            if (mGlobalPolicies != null) {
+                for (Map.Entry<String, PolicyState<?>> policy : mGlobalPolicies.entrySet()) {
+                    serializer.startTag(/* namespace= */ null, TAG_DEVICE_POLICY_ENTRY);
+
+                    serializer.attribute(/* namespace= */ null, ATTR_POLICY_ID, policy.getKey());
+
+                    serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+                    policy.getValue().saveToXml(serializer);
+                    serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+
+                    serializer.endTag(/* namespace= */ null, TAG_DEVICE_POLICY_ENTRY);
+                }
+            }
+        }
+
+        void readFromFileLocked() {
+            if (!mFile.exists()) {
+                Log.d(TAG, "" + mFile + " doesn't exist");
+                return;
+            }
+
+            Log.d(TAG, "Reading from " + mFile);
+            AtomicFile f = new AtomicFile(mFile);
+            InputStream input = null;
+            try {
+                input = f.openRead();
+                TypedXmlPullParser parser = Xml.resolvePullParser(input);
+
+                readInner(parser);
+
+            } catch (XmlPullParserException | IOException | ClassNotFoundException e) {
+                Log.e(TAG, "Error parsing resources file", e);
+            } finally {
+                IoUtils.closeQuietly(input);
+            }
+        }
+
+        private void readInner(TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException, ClassNotFoundException {
+            int outerDepth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                String tag = parser.getName();
+                switch (tag) {
+                    case TAG_USER_POLICY_ENTRY:
+                        readUserPoliciesInner(parser);
+                        break;
+                    case TAG_DEVICE_POLICY_ENTRY:
+                        readDevicePoliciesInner(parser);
+                        break;
+                    default:
+                        Log.e(TAG, "Unknown tag " + tag);
+                }
+            }
+        }
+
+        private void readUserPoliciesInner(TypedXmlPullParser parser)
+                throws XmlPullParserException, IOException {
+            int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
+            String policyKey = parser.getAttributeValue(
+                    /* namespace= */ null, ATTR_POLICY_ID);
+            if (!mUserPolicies.contains(userId)) {
+                mUserPolicies.put(userId, new HashMap<>());
+            }
+            PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
+            if (adminsPolicy != null) {
+                mUserPolicies.get(userId).put(policyKey, adminsPolicy);
+            } else {
+                Log.e(TAG, "Error parsing file, " + policyKey + "doesn't have an "
+                        + "AdminsPolicy.");
+            }
+        }
+
+        private void readDevicePoliciesInner(TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_ID);
+            PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
+            if (adminsPolicy != null) {
+                mGlobalPolicies.put(policyKey, adminsPolicy);
+            } else {
+                Log.e(TAG, "Error parsing file, " + policyKey + "doesn't have an "
+                        + "AdminsPolicy.");
+            }
+        }
+
+        @Nullable
+        private PolicyState<?> parseAdminsPolicy(TypedXmlPullParser parser)
+                throws XmlPullParserException, IOException {
+            int outerDepth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                String tag = parser.getName();
+                if (tag.equals(TAG_ADMINS_POLICY_ENTRY)) {
+                    return PolicyState.readFromXml(parser);
+                }
+                Log.e(TAG, "Unknown tag " + tag);
+            }
+            Log.e(TAG, "Error parsing file, AdminsPolicy not found");
+            return null;
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 70422bb..1ec2438 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -23,6 +23,7 @@
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY;
 import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
 import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
 import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
@@ -46,6 +47,7 @@
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
 import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
+import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_APP_STANDBY;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_IDS;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -79,6 +81,7 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
 import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED;
 import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY;
 import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT;
@@ -138,6 +141,7 @@
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
 import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -259,6 +263,7 @@
 import android.content.pm.Signature;
 import android.content.pm.StringParceledListSlice;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.database.Cursor;
@@ -306,6 +311,7 @@
 import android.provider.CalendarContract;
 import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsInternal;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Telephony;
@@ -329,8 +335,10 @@
 import android.util.AtomicFile;
 import android.util.DebugUtils;
 import android.util.IndentingPrintWriter;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
 import android.view.IWindowManager;
@@ -645,6 +653,15 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
     private static final long USE_SET_LOCATION_ENABLED = 117835097L;
 
+    /**
+     * Forces wipeDataNoLock to attempt removing the user or throw an error as
+     * opposed to trying to factory reset the device first and only then falling back to user
+     * removal.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final long EXPLICIT_WIPE_BEHAVIOUR = 242193913L;
+
     // Only add to the end of the list. Do not change or rearrange these values, that will break
     // historical data. Do not use negative numbers or zero, logger only handles positive
     // integers.
@@ -661,6 +678,17 @@
     private @interface CopyAccountStatus {}
 
     /**
+     * Mapping of {@link android.app.admin.DevicePolicyManager.ApplicationExemptionConstants} to
+     * corresponding app-ops.
+     */
+    private static final Map<Integer, String> APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS =
+            new ArrayMap<>();
+    static {
+        APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.put(
+                EXEMPT_FROM_APP_STANDBY, OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY);
+    }
+
+    /**
      * Admin apps targeting Android S+ may not use
      * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} to set password quality
      * on the {@code DevicePolicyManager} instance obtained by calling
@@ -687,6 +715,27 @@
                     + "management app's authentication policy";
     private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s";
 
+    private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
+    private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = false;
+
+    // TODO(b/258425381) remove the flag after rollout.
+    private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
+    private static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false;
+
+    /**
+     * This feature flag is checked once after boot and this value us used until the next reboot to
+     * avoid needing to handle the flag changing on the fly.
+     */
+    private final boolean mKeepProfilesRunning = isKeepProfilesRunningFlagEnabled();
+
+    /**
+     * For apps targeting U+
+     * Enable multiple admins to coexist on the same device.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    static final long ENABLE_COEXISTENCE_CHANGE = 260560985L;
+
     final Context mContext;
     final Injector mInjector;
     final PolicyPathProvider mPathProvider;
@@ -710,8 +759,7 @@
      * Contains (package-user) pairs to remove. An entry (p, u) implies that removal of package p
      * is requested for user u.
      */
-    private final Set<Pair<String, Integer>> mPackagesToRemove =
-            new ArraySet<Pair<String, Integer>>();
+    private final Set<UserPackage> mPackagesToRemove = new ArraySet<>();
 
     final LocalService mLocalService;
 
@@ -771,6 +819,8 @@
     private final DeviceManagementResourcesProvider mDeviceManagementResourcesProvider;
     private final DevicePolicyManagementRoleObserver mDevicePolicyManagementRoleObserver;
 
+    private final DevicePolicyEngine mDevicePolicyEngine;
+
     private static final boolean ENABLE_LOCK_GUARD = true;
 
     /**
@@ -1840,6 +1890,8 @@
         mUserData = new SparseArray<>();
         mOwners = makeOwners(injector, pathProvider);
 
+        mDevicePolicyEngine = new DevicePolicyEngine(mContext);
+
         if (!mHasFeature) {
             // Skip the rest of the initialization
             mSetupContentObserver = null;
@@ -1884,6 +1936,9 @@
         mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
 
         mDeviceManagementResourcesProvider.load();
+        if (isCoexistenceFlagEnabled()) {
+            mDevicePolicyEngine.load();
+        }
 
         // The binder caches are not enabled until the first invalidation.
         invalidateBinderCaches();
@@ -2878,60 +2933,60 @@
 
         policy.validatePasswordOwner();
         updateMaximumTimeToLockLocked(userHandle);
-        updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
+        updateLockTaskPackagesLocked(mContext, policy.mLockTaskPackages, userHandle);
         updateLockTaskFeaturesLocked(policy.mLockTaskFeatures, userHandle);
         if (policy.mStatusBarDisabled) {
             setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle);
         }
     }
 
-    private void updateLockTaskPackagesLocked(List<String> packages, int userId) {
-        String[] packagesArray = null;
+    static void updateLockTaskPackagesLocked(Context context, List<String> packages, int userId) {
+        Binder.withCleanCallingIdentity(() -> {
 
-        if (!packages.isEmpty()) {
-            // When adding packages, we need to include the exempt apps so they can still be
-            // launched (ideally we should use a different AM API as these apps don't need to use
-            // lock-task mode).
-            // They're not added when the packages is empty though, as in that case we're disabling
-            // lock-task mode.
-            List<String> exemptApps = listPolicyExemptAppsUnchecked();
-            if (!exemptApps.isEmpty()) {
-                // TODO(b/175377361): add unit test to verify it (cannot be CTS because the policy-
-                // -exempt apps are provided by OEM and the test would have no control over it) once
-                // tests are migrated to the new infra-structure
-                HashSet<String> updatedPackages = new HashSet<>(packages);
-                updatedPackages.addAll(exemptApps);
-                if (VERBOSE_LOG) {
-                    Slogf.v(LOG_TAG, "added %d policy-exempt apps to %d lock task packages. Final "
-                            + "list: %s", exemptApps.size(), packages.size(), updatedPackages);
+            String[] packagesArray = null;
+            if (!packages.isEmpty()) {
+                // When adding packages, we need to include the exempt apps so they can still be
+                // launched (ideally we should use a different AM API as these apps don't need to
+                // use lock-task mode).
+                // They're not added when the packages is empty though, as in that case we're
+                // disabling lock-task mode.
+                List<String> exemptApps = listPolicyExemptAppsUnchecked(context);
+                if (!exemptApps.isEmpty()) {
+                    // TODO(b/175377361): add unit test to verify it (cannot be CTS because the
+                    //  policy-exempt apps are provided by OEM and the test would have no control
+                    //  over it) once tests are migrated to the new infra-structure
+                    HashSet<String> updatedPackages = new HashSet<>(packages);
+                    updatedPackages.addAll(exemptApps);
+                    if (VERBOSE_LOG) {
+                        Slogf.v(LOG_TAG, "added %d policy-exempt apps to %d lock task "
+                                + "packages. Final list: %s",
+                                exemptApps.size(), packages.size(), updatedPackages);
+                    }
+                    packagesArray = updatedPackages.toArray(new String[updatedPackages.size()]);
                 }
-                packagesArray = updatedPackages.toArray(new String[updatedPackages.size()]);
             }
-        }
 
-        if (packagesArray == null) {
-            packagesArray = packages.toArray(new String[packages.size()]);
-        }
-
-        long ident = mInjector.binderClearCallingIdentity();
-        try {
-            mInjector.getIActivityManager().updateLockTaskPackages(userId, packagesArray);
-        } catch (RemoteException e) {
-            // Not gonna happen.
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
-        }
+            if (packagesArray == null) {
+                packagesArray = packages.toArray(new String[packages.size()]);
+            }
+            try {
+                ActivityManager.getService().updateLockTaskPackages(userId, packagesArray);
+            } catch (RemoteException e) {
+                // Shouldn't happen.
+                Slog.wtf(LOG_TAG, "Remote Exception: ", e);
+            }
+        });
     }
 
-    private void updateLockTaskFeaturesLocked(int flags, int userId) {
-        long ident = mInjector.binderClearCallingIdentity();
-        try {
-            mInjector.getIActivityTaskManager().updateLockTaskFeatures(userId, flags);
-        } catch (RemoteException e) {
-            // Not gonna happen.
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
-        }
+    static void updateLockTaskFeaturesLocked(int flags, int userId) {
+        Binder.withCleanCallingIdentity(() -> {
+            try {
+                ActivityTaskManager.getService().updateLockTaskFeatures(userId, flags);
+            } catch (RemoteException e) {
+                // Shouldn't happen.
+                Slog.wtf(LOG_TAG, "Remote Exception: ", e);
+            }
+        });
     }
 
     static void validateQualityConstant(int quality) {
@@ -6699,8 +6754,8 @@
     }
 
     @Override
-    public void wipeDataWithReason(int flags, String wipeReasonForUser,
-            boolean calledOnParentInstance) {
+    public void wipeDataWithReason(int flags, @NonNull String wipeReasonForUser,
+            boolean calledOnParentInstance, boolean factoryReset) {
         if (!mHasFeature && !hasCallingOrSelfPermission(permission.MASTER_CLEAR)) {
             return;
         }
@@ -6782,7 +6837,8 @@
                 "DevicePolicyManager.wipeDataWithReason() from %s, organization-owned? %s",
                 adminName, calledByProfileOwnerOnOrgOwnedDevice);
 
-        wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId);
+        wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId,
+                calledOnParentInstance, factoryReset);
     }
 
     private String getGenericWipeReason(
@@ -6844,8 +6900,13 @@
         Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
     }
 
+    /**
+     * @param factoryReset null: legacy behaviour, false: attempt to remove user, true: attempt to
+     *                     factory reset
+     */
     private void wipeDataNoLock(ComponentName admin, int flags, String internalReason,
-                                String wipeReasonForUser, int userId) {
+            @NonNull String wipeReasonForUser, int userId, boolean calledOnParentInstance,
+            @Nullable Boolean factoryReset) {
         wtfIfInLock();
 
         mInjector.binderWithCleanCallingIdentity(() -> {
@@ -6863,7 +6924,39 @@
                         + " restriction is set for user " + userId);
             }
 
-            if (userId == UserHandle.USER_SYSTEM) {
+            boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
+            boolean wipeDevice;
+            if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
+                    admin.getPackageName(),
+                    userId)) {
+                // Legacy mode
+                wipeDevice = isSystemUser;
+            } else {
+                // Explicit behaviour
+                if (factoryReset) {
+                    // TODO(b/254031494) Replace with new factory reset permission checks
+                    boolean hasPermission = isDeviceOwnerUserId(userId)
+                            || (isOrganizationOwnedDeviceWithManagedProfile()
+                            && calledOnParentInstance);
+                    Preconditions.checkState(hasPermission,
+                            "Admin %s does not have permission to factory reset the device.",
+                            userId);
+                    wipeDevice = true;
+                } else {
+                    Preconditions.checkCallAuthorization(!isSystemUser,
+                            "User %s is a system user and cannot be removed", userId);
+                    boolean isLastNonHeadlessUser = getUserInfo(userId).isFull()
+                            && mUserManager.getAliveUsers().stream()
+                            .filter((it) -> it.getUserHandle().getIdentifier() != userId)
+                            .noneMatch(UserInfo::isFull);
+                    Preconditions.checkState(!isLastNonHeadlessUser,
+                            "Removing user %s would leave the device without any active users. "
+                                    + "Consider factory resetting the device instead.",
+                            userId);
+                    wipeDevice = false;
+                }
+            }
+            if (wipeDevice) {
                 forceWipeDeviceNoLock(
                         (flags & WIPE_EXTERNAL_STORAGE) != 0,
                         internalReason,
@@ -7131,7 +7224,7 @@
     }
 
     @Override
-    public void reportFailedPasswordAttempt(int userHandle) {
+    public void reportFailedPasswordAttempt(int userHandle, boolean parent) {
         Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
 
         final CallerIdentity caller = getCallerIdentity();
@@ -7153,7 +7246,7 @@
                 saveSettingsLocked(userHandle);
                 if (mHasFeature) {
                     strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked(
-                            userHandle, /* parent */ false);
+                            userHandle, /* parent= */ false);
                     int max = strictestAdmin != null
                             ? strictestAdmin.maximumFailedPasswordsForWipe : 0;
                     if (max > 0 && policy.mFailedPasswordAttempts >= max) {
@@ -7183,10 +7276,13 @@
             // IMPORTANT: Call without holding the lock to prevent deadlock.
             try {
                 wipeDataNoLock(strictestAdmin.info.getComponent(),
-                        /*flags=*/ 0,
-                        /*reason=*/ "reportFailedPasswordAttempt()",
+                        /* flags= */ 0,
+                        /* reason= */ "reportFailedPasswordAttempt()",
                         getFailedPasswordAttemptWipeMessage(),
-                        userId);
+                        userId,
+                        /* calledOnParentInstance= */ parent,
+                        // factoryReset=null to enable U- behaviour
+                        /* factoryReset= */ null);
             } catch (SecurityException e) {
                 Slogf.w(LOG_TAG, "Failed to wipe user " + userId
                         + " after max failed password attempts reached.", e);
@@ -7195,7 +7291,7 @@
 
         if (mInjector.securityLogIsLoggingEnabled()) {
             SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT,
-                    /*result*/ 0, /*method strength*/ 1);
+                    /* result= */ 0, /* method strength= */ 1);
         }
     }
 
@@ -7886,8 +7982,17 @@
         Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
                 || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
 
-        mInjector.binderWithCleanCallingIdentity(() ->
-                mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
+        if (isCoexistenceEnabled(caller)) {
+            mDevicePolicyEngine.setGlobalPolicy(
+                    PolicyDefinition.AUTO_TIMEZONE,
+                    // TODO(b/260573124): add correct enforcing admin when permission changes are
+                    //  merged.
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()),
+                    enabled);
+        } else {
+            mInjector.binderWithCleanCallingIdentity(() ->
+                    mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
+        }
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_AUTO_TIME_ZONE)
@@ -8941,7 +9046,7 @@
         policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
         policy.mAffiliationIds.clear();
         policy.mLockTaskPackages.clear();
-        updateLockTaskPackagesLocked(policy.mLockTaskPackages, userId);
+        updateLockTaskPackagesLocked(mContext, policy.mLockTaskPackages, userId);
         policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
         saveSettingsLocked(userId);
 
@@ -9797,6 +9902,8 @@
                             (size == 1 ? "" : "s"));
                 }
                 pw.println();
+                pw.println("Keep profiles running: " + mKeepProfilesRunning);
+                pw.println();
 
                 mPolicyCache.dump(pw);
                 pw.println();
@@ -11217,7 +11324,7 @@
     private String[] populateNonExemptAndExemptFromPolicyApps(String[] packageNames,
             Set<String> outputExemptApps) {
         Preconditions.checkArgument(outputExemptApps.isEmpty(), "outputExemptApps is not empty");
-        List<String> exemptAppsList = listPolicyExemptAppsUnchecked();
+        List<String> exemptAppsList = listPolicyExemptAppsUnchecked(mContext);
         if (exemptAppsList.isEmpty()) {
             return packageNames;
         }
@@ -11330,16 +11437,16 @@
                 hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)
                         || isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
-        return listPolicyExemptAppsUnchecked();
+        return listPolicyExemptAppsUnchecked(mContext);
     }
 
-    private List<String> listPolicyExemptAppsUnchecked() {
+    private static List<String> listPolicyExemptAppsUnchecked(Context context) {
         // TODO(b/181238156): decide whether it should only list the apps set by the resources,
         // or also the "critical" apps defined by PersonalAppsSuspensionHelper (like SMS app).
         // If it's the latter, refactor PersonalAppsSuspensionHelper so it (or a superclass) takes
         // the resources on constructor.
-        String[] core = mContext.getResources().getStringArray(R.array.policy_exempt_apps);
-        String[] vendor = mContext.getResources().getStringArray(R.array.vendor_policy_exempt_apps);
+        String[] core = context.getResources().getStringArray(R.array.policy_exempt_apps);
+        String[] vendor = context.getResources().getStringArray(R.array.vendor_policy_exempt_apps);
 
         int size = core.length + vendor.length;
         Set<String> apps = new ArraySet<>(size);
@@ -11516,7 +11623,7 @@
                 && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
 
-        List<String> exemptApps = listPolicyExemptAppsUnchecked();
+        List<String> exemptApps = listPolicyExemptAppsUnchecked(mContext);
         if (exemptApps.contains(packageName)) {
             Slogf.d(LOG_TAG, "setApplicationHidden(): ignoring %s as it's on policy-exempt list",
                     packageName);
@@ -12180,8 +12287,38 @@
         synchronized (getLockObject()) {
             enforceCanCallLockTaskLocked(caller);
             checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES);
-            final int userHandle = caller.getUserId();
-            setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
+        }
+
+        if (isCoexistenceEnabled(caller)) {
+            EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who);
+            if (packages.length == 0) {
+                mDevicePolicyEngine.removeLocalPolicy(
+                        PolicyDefinition.LOCK_TASK,
+                        admin,
+                        caller.getUserId());
+            } else {
+                LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
+                        PolicyDefinition.LOCK_TASK,
+                        caller.getUserId()).getPoliciesSetByAdmins().get(admin);
+                LockTaskPolicy policy;
+                if (currentPolicy == null) {
+                    policy = new LockTaskPolicy(Set.of(packages));
+                } else {
+                    policy = currentPolicy.clone();
+                    policy.setPackages(Set.of(packages));
+                }
+
+                mDevicePolicyEngine.setLocalPolicy(
+                        PolicyDefinition.LOCK_TASK,
+                        EnforcingAdmin.createEnterpriseEnforcingAdmin(who),
+                        policy,
+                        caller.getUserId());
+            }
+        } else {
+            synchronized (getLockObject()) {
+                final int userHandle = caller.getUserId();
+                setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
+            }
         }
     }
 
@@ -12191,7 +12328,7 @@
 
         // Store the settings persistently.
         saveSettingsLocked(userHandle);
-        updateLockTaskPackagesLocked(packages, userHandle);
+        updateLockTaskPackagesLocked(mContext, packages, userHandle);
     }
 
     @Override
@@ -12202,15 +12339,28 @@
 
         synchronized (getLockObject()) {
             enforceCanCallLockTaskLocked(caller);
-            final List<String> packages = getUserData(userHandle).mLockTaskPackages;
-            return packages.toArray(new String[packages.size()]);
+        }
+
+        if (isCoexistenceEnabled(caller)) {
+            LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+                    PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy();
+            if (policy == null) {
+                return new String[0];
+            } else {
+                return policy.getPackages().toArray(new String[policy.getPackages().size()]);
+            }
+        } else {
+            synchronized (getLockObject()) {
+                final List<String> packages = getUserData(userHandle).mLockTaskPackages;
+                return packages.toArray(new String[packages.size()]);
+            }
         }
     }
 
     @Override
     public boolean isLockTaskPermitted(String pkg) {
         // Check policy-exempt apps first, as it doesn't require the lock
-        if (listPolicyExemptAppsUnchecked().contains(pkg)) {
+        if (listPolicyExemptAppsUnchecked(mContext).contains(pkg)) {
             if (VERBOSE_LOG) {
                 Slogf.v(LOG_TAG, "isLockTaskPermitted(%s): returning true for policy-exempt app",
                             pkg);
@@ -12219,8 +12369,19 @@
         }
 
         final int userId = mInjector.userHandleGetCallingUserId();
-        synchronized (getLockObject()) {
-            return getUserData(userId).mLockTaskPackages.contains(pkg);
+        // TODO(b/260560985): This is not the right check, as the flag could be enabled but there
+        //  could be an admin that hasn't targeted U.
+        if (isCoexistenceFlagEnabled()) {
+            LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+                    PolicyDefinition.LOCK_TASK, userId).getCurrentResolvedPolicy();
+            if (policy == null) {
+                return false;
+            }
+            return policy.getPackages().contains(pkg);
+        } else {
+            synchronized (getLockObject()) {
+                return getUserData(userId).mLockTaskPackages.contains(pkg);
+            }
         }
     }
 
@@ -12243,7 +12404,28 @@
             enforceCanCallLockTaskLocked(caller);
             enforceCanSetLockTaskFeaturesOnFinancedDevice(caller, flags);
             checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES);
-            setLockTaskFeaturesLocked(userHandle, flags);
+        }
+        if (isCoexistenceEnabled(caller)) {
+            EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who);
+            LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
+                    PolicyDefinition.LOCK_TASK,
+                    caller.getUserId()).getPoliciesSetByAdmins().get(admin);
+            if (currentPolicy == null) {
+                throw new IllegalArgumentException("Can't set a lock task flags without setting "
+                        + "lock task packages first.");
+            }
+            LockTaskPolicy policy = currentPolicy.clone();
+            policy.setFlags(flags);
+
+            mDevicePolicyEngine.setLocalPolicy(
+                    PolicyDefinition.LOCK_TASK,
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(who),
+                    policy,
+                    caller.getUserId());
+        } else {
+            synchronized (getLockObject()) {
+                setLockTaskFeaturesLocked(userHandle, flags);
+            }
         }
     }
 
@@ -12261,7 +12443,21 @@
         final int userHandle = caller.getUserId();
         synchronized (getLockObject()) {
             enforceCanCallLockTaskLocked(caller);
-            return getUserData(userHandle).mLockTaskFeatures;
+        }
+
+        if (isCoexistenceEnabled(caller)) {
+            LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+                    PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy();
+            if (policy == null) {
+                // We default on the power button menu, in order to be consistent with pre-P
+                // behaviour.
+                return DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+            }
+            return policy.getFlags();
+        } else {
+            synchronized (getLockObject()) {
+                return getUserData(userHandle).mLockTaskFeatures;
+            }
         }
     }
 
@@ -13349,6 +13545,11 @@
             }
         }
 
+        @Override
+        public boolean isKeepProfilesRunningEnabled() {
+            return mKeepProfilesRunning;
+        }
+
         private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
             return getDefaultCrossProfilePackages().contains(packageName)
                     ? AppOpsManager.MODE_ALLOWED
@@ -13840,6 +14041,20 @@
             if (isFinancedDeviceOwner(caller)) {
                 enforcePermissionGrantStateOnFinancedDevice(packageName, permission);
             }
+        }
+        if (isCoexistenceEnabled(caller)) {
+            mDevicePolicyEngine.setLocalPolicy(
+                    PolicyDefinition.PERMISSION_GRANT(packageName, permission),
+                    // TODO(b/260573124): Add correct enforcing admin when permission changes are
+                    //  merged, and don't forget to handle delegates! Enterprise admins assume
+                    //  component name isn't null.
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()),
+                    grantState,
+                    caller.getUserId());
+            // TODO: update javadoc to reflect that callback no longer return success/failure
+            callback.sendResult(Bundle.EMPTY);
+        } else {
+            synchronized (getLockObject()) {
             long ident = mInjector.binderClearCallingIdentity();
             try {
                 boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
@@ -13856,14 +14071,16 @@
                     callback.sendResult(null);
                     return;
                 }
-                if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
+                if (grantState == PERMISSION_GRANT_STATE_GRANTED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
                     AdminPermissionControlParams permissionParams =
-                            new AdminPermissionControlParams(packageName, permission, grantState,
+                            new AdminPermissionControlParams(packageName, permission,
+                                    grantState,
                                     canAdminGrantSensorsPermissionsForUser(caller.getUserId()));
                     mInjector.getPermissionControllerManager(caller.getUserHandle())
-                            .setRuntimePermissionGrantStateByDeviceAdmin(caller.getPackageName(),
+                            .setRuntimePermissionGrantStateByDeviceAdmin(
+                                    caller.getPackageName(),
                                     permissionParams, mContext.getMainExecutor(),
                                     (permissionWasSet) -> {
                                         if (isPostQAdmin && !permissionWasSet) {
@@ -13882,13 +14099,14 @@
 
                                         callback.sendResult(Bundle.EMPTY);
                                     });
-                }
-            } catch (SecurityException e) {
-                Slogf.e(LOG_TAG, "Could not set permission grant state", e);
+                    }
+                } catch (SecurityException e) {
+                    Slogf.e(LOG_TAG, "Could not set permission grant state", e);
 
-                callback.sendResult(null);
-            } finally {
-                mInjector.binderRestoreCallingIdentity(ident);
+                    callback.sendResult(null);
+                } finally {
+                    mInjector.binderRestoreCallingIdentity(ident);
+                }
             }
         }
     }
@@ -13982,7 +14200,6 @@
     @Override
     public boolean isProvisioningAllowed(String action, String packageName) {
         Objects.requireNonNull(packageName);
-
         final CallerIdentity caller = getCallerIdentity();
         final long ident = mInjector.binderClearCallingIdentity();
         try {
@@ -13994,21 +14211,21 @@
             mInjector.binderRestoreCallingIdentity(ident);
         }
 
-        return checkProvisioningPreconditionSkipPermission(action, packageName) == STATUS_OK;
+        return checkProvisioningPreconditionSkipPermission(action, packageName, caller.getUserId())
+                == STATUS_OK;
     }
 
     @Override
     public int checkProvisioningPrecondition(String action, String packageName) {
         Objects.requireNonNull(packageName, "packageName is null");
-
+        final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
-        return checkProvisioningPreconditionSkipPermission(action, packageName);
+        return checkProvisioningPreconditionSkipPermission(action, packageName, caller.getUserId());
     }
-
     private int checkProvisioningPreconditionSkipPermission(String action,
-            String packageName) {
+            String packageName, int userId) {
         if (!mHasFeature) {
             logMissingFeatureAction("Cannot check provisioning for action " + action);
             return STATUS_DEVICE_ADMIN_NOT_SUPPORTED;
@@ -14016,7 +14233,8 @@
         if (!isProvisioningAllowed()) {
             return STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
         }
-        final int code = checkProvisioningPreConditionSkipPermissionNoLog(action, packageName);
+        final int code = checkProvisioningPreConditionSkipPermissionNoLog(
+                action, packageName, userId);
         if (code != STATUS_OK) {
             Slogf.d(LOG_TAG, "checkProvisioningPreCondition(" + action + ", " + packageName
                     + ") failed: "
@@ -14043,15 +14261,14 @@
     }
 
     private int checkProvisioningPreConditionSkipPermissionNoLog(String action,
-            String packageName) {
-        final int callingUserId = mInjector.userHandleGetCallingUserId();
+            String packageName, int userId) {
         if (action != null) {
             switch (action) {
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE:
-                    return checkManagedProfileProvisioningPreCondition(packageName, callingUserId);
+                    return checkManagedProfileProvisioningPreCondition(packageName, userId);
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
                 case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
-                    return checkDeviceOwnerProvisioningPreCondition(callingUserId);
+                    return checkDeviceOwnerProvisioningPreCondition(userId);
             }
         }
         throw new IllegalArgumentException("Unknown provisioning action " + action);
@@ -15044,7 +15261,7 @@
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
 
-        Pair<String, Integer> packageUserPair = new Pair<>(packageName, caller.getUserId());
+        UserPackage packageUserPair = UserPackage.of(caller.getUserId(), packageName);
         synchronized (getLockObject()) {
             return mPackagesToRemove.contains(packageUserPair);
         }
@@ -15072,7 +15289,7 @@
             throw new IllegalArgumentException("Cannot uninstall a package with a device owner");
         }
 
-        final Pair<String, Integer> packageUserPair = new Pair<>(packageName, userId);
+        final UserPackage packageUserPair = UserPackage.of(userId, packageName);
         synchronized (getLockObject()) {
             mPackagesToRemove.add(packageUserPair);
         }
@@ -15132,7 +15349,7 @@
     }
 
     private void startUninstallIntent(final String packageName, final int userId) {
-        final Pair<String, Integer> packageUserPair = new Pair<>(packageName, userId);
+        final UserPackage packageUserPair = UserPackage.of(userId, packageName);
         synchronized (getLockObject()) {
             if (!mPackagesToRemove.contains(packageUserPair)) {
                 // Do nothing if uninstall was not requested or was already started.
@@ -16969,6 +17186,88 @@
         });
     }
 
+    @Override
+    public void setApplicationExemptions(String packageName, int[] exemptions) {
+        if (!mHasFeature) {
+            return;
+        }
+        Preconditions.checkStringNotEmpty(packageName, "Package name cannot be empty.");
+        Objects.requireNonNull(exemptions, "Application exemptions must not be null.");
+        Preconditions.checkArgument(areApplicationExemptionsValid(exemptions),
+                "Invalid application exemption constant found in application exemptions set.");
+        Preconditions.checkCallAuthorization(
+                hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS));
+
+        final CallerIdentity caller = getCallerIdentity();
+        final ApplicationInfo packageInfo;
+        packageInfo = getPackageInfoWithNullCheck(packageName, caller);
+
+        for (Map.Entry<Integer, String> entry :
+                APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) {
+            int currentMode = mInjector.getAppOpsManager().unsafeCheckOpNoThrow(
+                    entry.getValue(), packageInfo.uid, packageInfo.packageName);
+            int newMode = ArrayUtils.contains(exemptions, entry.getKey())
+                    ? MODE_ALLOWED : MODE_DEFAULT;
+            mInjector.binderWithCleanCallingIdentity(() -> {
+                if (currentMode != newMode) {
+                    mInjector.getAppOpsManager()
+                            .setMode(entry.getValue(),
+                                    packageInfo.uid,
+                                    packageName,
+                                    newMode);
+                }
+            });
+        }
+    }
+
+    @Override
+    public int[] getApplicationExemptions(String packageName) {
+        if (!mHasFeature) {
+            return new int[0];
+        }
+        Preconditions.checkStringNotEmpty(packageName, "Package name cannot be empty.");
+        Preconditions.checkCallAuthorization(
+                hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS));
+
+        final CallerIdentity caller = getCallerIdentity();
+        final ApplicationInfo packageInfo;
+        packageInfo = getPackageInfoWithNullCheck(packageName, caller);
+
+        IntArray appliedExemptions = new IntArray(0);
+        for (Map.Entry<Integer, String> entry :
+                APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) {
+            if (mInjector.getAppOpsManager().unsafeCheckOpNoThrow(
+                    entry.getValue(), packageInfo.uid, packageInfo.packageName) == MODE_ALLOWED) {
+                appliedExemptions.add(entry.getKey());
+            }
+        }
+        return appliedExemptions.toArray();
+    }
+
+    private ApplicationInfo getPackageInfoWithNullCheck(String packageName, CallerIdentity caller) {
+        final ApplicationInfo packageInfo =
+                mInjector.getPackageManagerInternal().getApplicationInfo(
+                        packageName,
+                        /* flags= */ 0,
+                        caller.getUid(),
+                        caller.getUserId());
+        if (packageInfo == null) {
+            throw new ServiceSpecificException(
+                    DevicePolicyManager.ERROR_PACKAGE_NAME_NOT_FOUND,
+                    "Package name not found.");
+        }
+        return packageInfo;
+    }
+
+    private boolean areApplicationExemptionsValid(int[] exemptions) {
+        for (int exemption : exemptions) {
+            if (!APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.containsKey(exemption)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private boolean isCallingFromPackage(String packageName, int callingUid) {
         return mInjector.binderWithCleanCallingIdentity(() -> {
             try {
@@ -17622,7 +17921,7 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             final int result = checkProvisioningPreconditionSkipPermission(
-                    ACTION_PROVISION_MANAGED_PROFILE, admin.getPackageName());
+                    ACTION_PROVISION_MANAGED_PROFILE, admin.getPackageName(), caller.getUserId());
             if (result != STATUS_OK) {
                 throw new ServiceSpecificException(
                         ERROR_PRE_CONDITION_FAILED,
@@ -18032,7 +18331,8 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             int result = checkProvisioningPreconditionSkipPermission(
-                    ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName());
+                    ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName(),
+                    caller.getUserId());
             if (result != STATUS_OK) {
                 throw new ServiceSpecificException(
                         ERROR_PRE_CONDITION_FAILED,
@@ -18870,4 +19170,25 @@
             return result;
         });
     }
+
+    // TODO(b/260560985): properly gate coexistence changes
+    private boolean isCoexistenceEnabled(CallerIdentity caller) {
+        return isCoexistenceFlagEnabled()
+                && mInjector.isChangeEnabled(
+                        ENABLE_COEXISTENCE_CHANGE, caller.getPackageName(), caller.getUserId());
+    }
+
+    private boolean isCoexistenceFlagEnabled() {
+        return DeviceConfig.getBoolean(
+                NAMESPACE_DEVICE_POLICY_MANAGER,
+                ENABLE_COEXISTENCE_FLAG,
+                DEFAULT_ENABLE_COEXISTENCE_FLAG);
+    }
+
+    private static boolean isKeepProfilesRunningFlagEnabled() {
+        return DeviceConfig.getBoolean(
+                NAMESPACE_DEVICE_POLICY_MANAGER,
+                KEEP_PROFILES_RUNNING_FLAG,
+                DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
new file mode 100644
index 0000000..9261d59
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -0,0 +1,219 @@
+/*
+ * 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.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import com.android.role.RoleManagerLocal;
+import com.android.server.LocalManagerRegistry;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * {@code EnforcingAdmins} can have the following authority types:
+ *
+ * <ul>
+ *     <li> {@link #DPC_AUTHORITY} meaning it's an enterprise admin (e.g. PO, DO, COPE)
+ *     <li> {@link #DEVICE_ADMIN_AUTHORITY} which is a legacy non enterprise admin
+ *     <li> Or a role authority, in which case {@link #mAuthorities} contains a list of all roles
+ *     held by the given {@code packageName}
+ * </ul>
+ *
+ */
+final class EnforcingAdmin {
+    static final String ROLE_AUTHORITY_PREFIX = "role:";
+    static final String DPC_AUTHORITY = "enterprise";
+    static final String DEVICE_ADMIN_AUTHORITY = "device_admin";
+    static final String DEFAULT_AUTHORITY = "default";
+
+    private static final String ATTR_PACKAGE_NAME = "package-name";
+    private static final String ATTR_CLASS_NAME = "class-name";
+    private static final String ATTR_AUTHORITIES = "authorities";
+    private static final String ATTR_AUTHORITIES_SEPARATOR = ";";
+    private static final String ATTR_USER_ID = "user-id";
+    private static final String ATTR_IS_ROLE = "is-role";
+
+    private final String mPackageName;
+    // This is needed for DPCs and active admins
+    private final ComponentName mComponentName;
+    private Set<String> mAuthorities;
+    private final int mUserId;
+    private final boolean mIsRoleAuthority;
+
+    static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId) {
+        Objects.requireNonNull(packageName);
+        return new EnforcingAdmin(packageName, userId);
+    }
+
+    static EnforcingAdmin createEnterpriseEnforcingAdmin(@NonNull ComponentName componentName) {
+        Objects.requireNonNull(componentName);
+        return new EnforcingAdmin(
+                componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY));
+    }
+
+    static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName) {
+        Objects.requireNonNull(componentName);
+        return new EnforcingAdmin(
+                componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY));
+    }
+
+    static String getRoleAuthorityOf(String roleName) {
+        return ROLE_AUTHORITY_PREFIX + roleName;
+    }
+
+    private EnforcingAdmin(
+            String packageName, ComponentName componentName, Set<String> authorities) {
+        Objects.requireNonNull(packageName);
+        Objects.requireNonNull(componentName);
+        Objects.requireNonNull(authorities);
+
+        // Role authorities should not be using this constructor
+        mIsRoleAuthority = false;
+        mPackageName = packageName;
+        mComponentName = componentName;
+        mAuthorities = new HashSet<>(authorities);
+        mUserId = -1; // not needed for non role authorities
+    }
+
+    private EnforcingAdmin(String packageName, int userId) {
+        Objects.requireNonNull(packageName);
+
+        // Only role authorities use this constructor.
+        mIsRoleAuthority = true;
+        mPackageName = packageName;
+        mUserId = userId;
+        mComponentName = null;
+        // authorities will be loaded when needed
+        mAuthorities = null;
+    }
+
+    private static Set<String> getRoleAuthoritiesOrDefault(String packageName, int userId) {
+        Set<String> roles = getRoles(packageName, userId);
+        Set<String> authorities = new HashSet<>();
+        for (String role : roles) {
+            authorities.add(ROLE_AUTHORITY_PREFIX + role);
+        }
+        return authorities.isEmpty() ? Set.of(DEFAULT_AUTHORITY) : authorities;
+    }
+
+    // TODO(b/259042794): move this logic to RoleManagerLocal
+    private static Set<String> getRoles(String packageName, int userId) {
+        RoleManagerLocal roleManagerLocal = LocalManagerRegistry.getManager(
+                RoleManagerLocal.class);
+        Set<String> roles = new HashSet<>();
+        Map<String, Set<String>> rolesAndHolders = roleManagerLocal.getRolesAndHolders(userId);
+        for (String role : rolesAndHolders.keySet()) {
+            if (rolesAndHolders.get(role).contains(packageName)) {
+                roles.add(role);
+            }
+        }
+        return roles;
+    }
+
+    private Set<String> getAuthorities() {
+        if (mAuthorities == null) {
+            mAuthorities = getRoleAuthoritiesOrDefault(mPackageName, mUserId);
+        }
+        return mAuthorities;
+    }
+
+    boolean hasAuthority(String authority) {
+        return getAuthorities().contains(authority);
+    }
+
+    /**
+     * For two EnforcingAdmins to be equal they must:
+     *
+     * <ul>
+     *     <li> have the same package names and component names and either
+     *     <li> have exactly the same authorities ({@link #DPC_AUTHORITY} or
+     *     {@link #DEVICE_ADMIN_AUTHORITY}), or have any role or default authorities.
+     * </ul>
+     *
+     * <p>EnforcingAdmins are considered equal if they have any role authority as they can have
+     * roles granted/revoked between calls.
+     */
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        EnforcingAdmin other = (EnforcingAdmin) o;
+        return Objects.equals(mPackageName, other.mPackageName)
+                && Objects.equals(mComponentName, other.mComponentName)
+                && Objects.equals(mIsRoleAuthority, other.mIsRoleAuthority)
+                && hasMatchingAuthorities(this, other);
+    }
+
+    private static boolean hasMatchingAuthorities(EnforcingAdmin admin1, EnforcingAdmin admin2) {
+        if (admin1.mIsRoleAuthority && admin2.mIsRoleAuthority) {
+            return true;
+        }
+        return admin1.getAuthorities().equals(admin2.getAuthorities());
+    }
+
+    @Override
+    public int hashCode() {
+        if (mIsRoleAuthority) {
+            // TODO(b/256854977): should we add UserId?
+            return Objects.hash(mPackageName);
+        } else {
+            return Objects.hash(mComponentName, getAuthorities());
+        }
+    }
+
+    void saveToXml(TypedXmlSerializer serializer) throws IOException {
+        serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName);
+        serializer.attributeBoolean(/* namespace= */ null, ATTR_IS_ROLE, mIsRoleAuthority);
+        if (mIsRoleAuthority) {
+            serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, mUserId);
+        } else {
+            serializer.attribute(
+                    /* namespace= */ null, ATTR_CLASS_NAME, mComponentName.getClassName());
+            serializer.attribute(
+                    /* namespace= */ null,
+                    ATTR_AUTHORITIES,
+                    String.join(ATTR_AUTHORITIES_SEPARATOR, getAuthorities()));
+        }
+    }
+
+    static EnforcingAdmin readFromXml(TypedXmlPullParser parser)
+            throws XmlPullParserException {
+        String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME);
+        boolean isRoleAuthority = parser.getAttributeBoolean(/* namespace= */ null, ATTR_IS_ROLE);
+        String authoritiesStr = parser.getAttributeValue(/* namespace= */ null, ATTR_AUTHORITIES);
+
+        if (isRoleAuthority) {
+            int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
+            return new EnforcingAdmin(packageName, userId);
+        } else {
+            String className = parser.getAttributeValue(/* namespace= */ null, ATTR_CLASS_NAME);
+            ComponentName componentName = new ComponentName(packageName, className);
+            Set<String> authorities = Set.of(authoritiesStr.split(ATTR_AUTHORITIES_SEPARATOR));
+            return new EnforcingAdmin(packageName, componentName, authorities);
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
new file mode 100644
index 0000000..d5949dd
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.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.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+final class IntegerPolicySerializer extends PolicySerializer<Integer> {
+
+    @Override
+    void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Integer value)
+            throws IOException {
+        Objects.requireNonNull(value);
+        serializer.attributeInt(/* namespace= */ null, attributeName, value);
+    }
+
+    @Nullable
+    @Override
+    Integer readFromXml(TypedXmlPullParser parser, String attributeName) {
+        try {
+            return parser.getAttributeInt(/* namespace= */ null, attributeName);
+        } catch (XmlPullParserException e) {
+            Log.e(DevicePolicyEngine.TAG, "Error parsing Integer policy value", e);
+            return null;
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerUnion.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerUnion.java
new file mode 100644
index 0000000..00bc261
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerUnion.java
@@ -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.server.devicepolicy;
+
+import android.annotation.NonNull;
+
+import java.util.LinkedHashMap;
+import java.util.Objects;
+
+final class IntegerUnion extends ResolutionMechanism<Integer> {
+
+    @Override
+    Integer resolve(@NonNull LinkedHashMap<EnforcingAdmin, Integer> adminPolicies) {
+        Objects.requireNonNull(adminPolicies);
+        if (adminPolicies.isEmpty()) {
+            return null;
+        }
+
+        Integer unionOfPolicies = 0;
+        for (Integer policy : adminPolicies.values()) {
+            unionOfPolicies |= policy;
+        }
+        return unionOfPolicies;
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
new file mode 100644
index 0000000..d3e8de4
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
@@ -0,0 +1,139 @@
+/*
+ * 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.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.util.Log;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+final class LockTaskPolicy {
+    static final int DEFAULT_LOCK_TASK_FLAG = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+    private Set<String> mPackages = new HashSet<>();
+    private int mFlags = DEFAULT_LOCK_TASK_FLAG;
+
+    @NonNull
+    Set<String> getPackages() {
+        return mPackages;
+    }
+
+    int getFlags() {
+        return mFlags;
+    }
+
+    LockTaskPolicy(Set<String> packages) {
+        Objects.requireNonNull(packages);
+        mPackages.addAll(packages);
+    }
+
+    private LockTaskPolicy(Set<String> packages, int flags) {
+        Objects.requireNonNull(packages);
+        mPackages = new HashSet<>(packages);
+        mFlags = flags;
+    }
+
+    void setPackages(@NonNull Set<String> packages) {
+        Objects.requireNonNull(packages);
+        mPackages = new HashSet<>(packages);
+    }
+
+    void setFlags(int flags) {
+        mFlags = flags;
+    }
+
+    @Override
+    public LockTaskPolicy clone() {
+        LockTaskPolicy policy = new LockTaskPolicy(mPackages);
+        policy.setFlags(mFlags);
+        return policy;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        LockTaskPolicy other = (LockTaskPolicy) o;
+        return Objects.equals(mPackages, other.mPackages)
+                && mFlags == other.mFlags;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPackages, mFlags);
+    }
+
+    @Override
+    public String toString() {
+        return "mPackages= " + String.join(", ", mPackages) + "; mFlags= " + mFlags;
+    }
+
+    static final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> {
+
+        private static final String ATTR_PACKAGES = ":packages";
+        private static final String ATTR_PACKAGES_SEPARATOR = ";";
+        private static final String ATTR_FLAGS = ":flags";
+
+        @Override
+        void saveToXml(TypedXmlSerializer serializer, String attributeNamePrefix,
+                @NonNull LockTaskPolicy value) throws IOException {
+            Objects.requireNonNull(value);
+            if (value.mPackages == null || value.mPackages.isEmpty()) {
+                throw new IllegalArgumentException("Error saving LockTaskPolicy to file, lock task "
+                        + "packages must be present");
+            }
+            serializer.attribute(
+                    /* namespace= */ null,
+                    attributeNamePrefix + ATTR_PACKAGES,
+                    String.join(ATTR_PACKAGES_SEPARATOR, value.mPackages));
+            serializer.attributeInt(
+                    /* namespace= */ null,
+                    attributeNamePrefix + ATTR_FLAGS,
+                    value.mFlags);
+        }
+
+        @Override
+        LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
+            String packagesStr = parser.getAttributeValue(
+                    /* namespace= */ null,
+                    attributeNamePrefix + ATTR_PACKAGES);
+            if (packagesStr == null) {
+                Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value.");
+                return null;
+            }
+            Set<String> packages = Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
+            try {
+                int flags = parser.getAttributeInt(
+                        /* namespace= */ null,
+                        attributeNamePrefix + ATTR_FLAGS);
+                return new LockTaskPolicy(packages, flags);
+            } catch (XmlPullParserException e) {
+                Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value", e);
+                return null;
+            }
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java b/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java
new file mode 100644
index 0000000..9a24dcf
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.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.server.devicepolicy;
+
+import android.annotation.NonNull;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+final class MostRestrictive<V> extends ResolutionMechanism<V> {
+
+    private List<V> mMostToLeastRestrictive;
+
+    MostRestrictive(@NonNull List<V> mostToLeastRestrictive) {
+        mMostToLeastRestrictive = mostToLeastRestrictive;
+    }
+
+    @Override
+    V resolve(@NonNull LinkedHashMap<EnforcingAdmin, V> adminPolicies) {
+        if (adminPolicies.isEmpty()) {
+            return null;
+        }
+        for (V value : mMostToLeastRestrictive) {
+            if (adminPolicies.containsValue(value)) {
+                return value;
+            }
+        }
+        // Return first set policy if none can be found in known values
+        Map.Entry<EnforcingAdmin, V> policy = adminPolicies.entrySet().stream().findFirst().get();
+        return policy.getValue();
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
new file mode 100644
index 0000000..a787a0b
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -0,0 +1,230 @@
+/*
+ * 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.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+
+import com.android.internal.util.function.QuadFunction;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+final class PolicyDefinition<V> {
+    private static final int POLICY_FLAG_NONE = 0;
+
+    // Only use this flag if a policy can not be applied locally.
+    private static final int POLICY_FLAG_GLOBAL_ONLY_POLICY = 1;
+
+    // Only use this flag if a policy can not be applied globally.
+    private static final int POLICY_FLAG_LOCAL_ONLY_POLICY = 1 << 1;
+
+    private static final MostRestrictive<Boolean> FALSE_MORE_RESTRICTIVE = new MostRestrictive<>(
+            List.of(false, true));
+
+    private static final String ATTR_POLICY_KEY = "policy-key";
+    private static final String ATTR_POLICY_DEFINITION_KEY = "policy-type-key";
+    private static final String ATTR_CALLBACK_ARGS = "callback-args";
+    private static final String ATTR_CALLBACK_ARGS_SEPARATOR = ";";
+
+
+    static PolicyDefinition<Boolean> AUTO_TIMEZONE = new PolicyDefinition<>(
+            DevicePolicyManager.AUTO_TIMEZONE_POLICY,
+            // auto timezone is enabled by default, hence disabling it is more restrictive.
+            FALSE_MORE_RESTRICTIVE,
+            POLICY_FLAG_GLOBAL_ONLY_POLICY,
+            (Boolean value, Context context, Integer userId, String[] args) ->
+                    PolicyEnforcerCallbacks.setAutoTimezoneEnabled(value, context),
+            new BooleanPolicySerializer());
+
+    // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
+    // actual permission grant policy with the correct arguments (packageName and permission name)
+    // when reading the policies from xml.
+    private static final PolicyDefinition<Integer> PERMISSION_GRANT_NO_ARGS =
+            new PolicyDefinition<>(DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY,
+                    // TODO: is this really the best mechanism, what makes denied more
+                    //  restrictive than
+                    //  granted?
+                    new MostRestrictive<>(
+                            List.of(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED,
+                                    DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED,
+                                    DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT)),
+                    POLICY_FLAG_LOCAL_ONLY_POLICY,
+                    PolicyEnforcerCallbacks::setPermissionGrantState,
+                    new IntegerPolicySerializer());
+
+    static PolicyDefinition<Integer> PERMISSION_GRANT(
+            @NonNull String packageName, @NonNull String permission) {
+        return PERMISSION_GRANT_NO_ARGS.setArgs(
+                DevicePolicyManager.PERMISSION_GRANT_POLICY(packageName, permission),
+                new String[]{packageName, permission});
+    }
+
+    static PolicyDefinition<LockTaskPolicy> LOCK_TASK = new PolicyDefinition<>(
+            DevicePolicyManager.LOCK_TASK_POLICY,
+            new TopPriority<>(List.of(
+                    // TODO(b/258166155): add correct device lock role name
+                    EnforcingAdmin.getRoleAuthorityOf("DeviceLock"),
+                    EnforcingAdmin.DPC_AUTHORITY)),
+            POLICY_FLAG_LOCAL_ONLY_POLICY,
+            (LockTaskPolicy value, Context context, Integer userId, String[] args) ->
+                    PolicyEnforcerCallbacks.setLockTask(value, context, userId),
+            new LockTaskPolicy.LockTaskPolicySerializer());
+
+    private static Map<String, PolicyDefinition<?>> sPolicyDefinitions = Map.of(
+            DevicePolicyManager.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE,
+            DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY, PERMISSION_GRANT_NO_ARGS
+    );
+
+
+    private final String mPolicyKey;
+    private final String mPolicyDefinitionKey;
+    private final ResolutionMechanism<V> mResolutionMechanism;
+    private final int mPolicyFlags;
+    // A function that accepts  policy to apple, context, userId, callback arguments, and returns
+    // true if the policy has been enforced successfully.
+    private final QuadFunction<V, Context, Integer, String[], Boolean> mPolicyEnforcerCallback;
+    private final String[] mCallbackArgs;
+    private final PolicySerializer<V> mPolicySerializer;
+
+    private PolicyDefinition<V> setArgs(String key, String[] callbackArgs) {
+        return new PolicyDefinition<>(key, mPolicyDefinitionKey, mResolutionMechanism,
+                mPolicyFlags, mPolicyEnforcerCallback, mPolicySerializer, callbackArgs);
+    }
+
+    String getPolicyKey() {
+        return mPolicyKey;
+    }
+
+    /**
+     * Returns {@code true} if the policy is a global policy by nature and can't be applied locally.
+     */
+    boolean isGlobalOnlyPolicy() {
+        return (mPolicyFlags & POLICY_FLAG_GLOBAL_ONLY_POLICY) != 0;
+    }
+
+    /**
+     * Returns {@code true} if the policy is a local policy by nature and can't be applied globally.
+     */
+    boolean isLocalOnlyPolicy() {
+        return (mPolicyFlags & POLICY_FLAG_LOCAL_ONLY_POLICY) != 0;
+    }
+
+    @Nullable
+    V resolvePolicy(LinkedHashMap<EnforcingAdmin, V> adminsPolicy) {
+        return mResolutionMechanism.resolve(adminsPolicy);
+    }
+
+    boolean enforcePolicy(@Nullable V value, Context context, int userId) {
+        return mPolicyEnforcerCallback.apply(value, context, userId, mCallbackArgs);
+    }
+
+    /**
+     * Callers must ensure that {@code policyType} have implemented an appropriate
+     * {@link Object#equals} implementation.
+     */
+    private PolicyDefinition(
+            String key,
+            ResolutionMechanism<V> resolutionMechanism,
+            QuadFunction<V, Context, Integer, String[], Boolean> policyEnforcerCallback,
+            PolicySerializer<V> policySerializer) {
+        this(key, resolutionMechanism, POLICY_FLAG_NONE, policyEnforcerCallback, policySerializer);
+    }
+
+    /**
+     * Callers must ensure that {@code policyType} have implemented an appropriate
+     * {@link Object#equals} implementation.
+     */
+    private PolicyDefinition(
+            String key,
+            ResolutionMechanism<V> resolutionMechanism,
+            int policyFlags,
+            QuadFunction<V, Context, Integer, String[], Boolean> policyEnforcerCallback,
+            PolicySerializer<V> policySerializer) {
+        this(key, key, resolutionMechanism, policyFlags, policyEnforcerCallback,
+                policySerializer, /* callbackArs= */ null);
+    }
+
+    /**
+     * Callers must ensure that {@code policyType} have implemented an appropriate
+     * {@link Object#equals} implementation.
+     */
+    private PolicyDefinition(
+            String policyKey,
+            String policyDefinitionKey,
+            ResolutionMechanism<V> resolutionMechanism,
+            int policyFlags,
+            QuadFunction<V, Context, Integer, String[], Boolean> policyEnforcerCallback,
+            PolicySerializer<V> policySerializer,
+            String[] callbackArgs) {
+        mPolicyKey = policyKey;
+        mPolicyDefinitionKey = policyDefinitionKey;
+        mResolutionMechanism = resolutionMechanism;
+        mPolicyFlags = policyFlags;
+        mPolicyEnforcerCallback = policyEnforcerCallback;
+        mPolicySerializer = policySerializer;
+        mCallbackArgs = callbackArgs;
+
+        // TODO: maybe use this instead of manually adding to the map
+//        sPolicyDefinitions.put(policyDefinitionKey, this);
+    }
+
+    void saveToXml(TypedXmlSerializer serializer) throws IOException {
+        serializer.attribute(/* namespace= */ null, ATTR_POLICY_KEY, mPolicyKey);
+        serializer.attribute(
+                /* namespace= */ null, ATTR_POLICY_DEFINITION_KEY, mPolicyDefinitionKey);
+        if (mCallbackArgs != null) {
+            serializer.attribute(/* namespace= */ null, ATTR_CALLBACK_ARGS,
+                    String.join(ATTR_CALLBACK_ARGS_SEPARATOR, mCallbackArgs));
+        }
+    }
+
+    static <V> PolicyDefinition<V> readFromXml(TypedXmlPullParser parser) {
+        String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_KEY);
+        String policyDefinitionKey = parser.getAttributeValue(
+                /* namespace= */ null, ATTR_POLICY_DEFINITION_KEY);
+        String callbackArgsStr = parser.getAttributeValue(
+                /* namespace= */ null, ATTR_CALLBACK_ARGS);
+        String[] callbackArgs = callbackArgsStr == null
+                ? null
+                : callbackArgsStr.split(ATTR_CALLBACK_ARGS_SEPARATOR);
+
+        // TODO: can we avoid casting?
+        if (callbackArgs == null) {
+            return (PolicyDefinition<V>) sPolicyDefinitions.get(policyDefinitionKey);
+        } else {
+            return (PolicyDefinition<V>) sPolicyDefinitions.get(policyDefinitionKey).setArgs(
+                    policyKey, callbackArgs);
+        }
+    }
+
+    void savePolicyValueToXml(TypedXmlSerializer serializer, String attributeName, V value)
+            throws IOException {
+        mPolicySerializer.saveToXml(serializer, attributeName, value);
+    }
+
+    @Nullable
+    V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) {
+        return mPolicySerializer.readFromXml(parser, attributeName);
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
new file mode 100644
index 0000000..74b6f9e
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -0,0 +1,135 @@
+/*
+ * 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.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.permission.AdminPermissionControlParams;
+import android.permission.PermissionControllerManager;
+import android.provider.Settings;
+
+import com.android.server.utils.Slogf;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+final class PolicyEnforcerCallbacks {
+
+    private static final String LOG_TAG = "PolicyEnforcerCallbacks";
+
+    static boolean setAutoTimezoneEnabled(@Nullable Boolean enabled, @NonNull Context context) {
+        return Binder.withCleanCallingIdentity(() -> {
+            Objects.requireNonNull(context);
+
+            int value = enabled != null && enabled ? 1 : 0;
+            return Settings.Global.putInt(
+                    context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
+                    value);
+        });
+    }
+
+    static boolean setPermissionGrantState(
+            @Nullable Integer grantState, @NonNull Context context, int userId,
+            @NonNull String[] args) {
+        return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+            if (args == null || args.length < 2) {
+                throw new IllegalArgumentException("Package name and permission name must be "
+                        + "provided as arguments");
+            }
+            String packageName = args[0];
+            String permissionName = args[1];
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(permissionName);
+            Objects.requireNonNull(context);
+
+            int value = grantState == null
+                    ? DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT
+                    : grantState;
+
+            BlockingCallback callback = new BlockingCallback();
+            // TODO: remove canAdminGrantSensorPermissions once we expose a new method in
+            //  permissionController that doesn't need it.
+            AdminPermissionControlParams permissionParams = new AdminPermissionControlParams(
+                    packageName, permissionName, value,
+                    /* canAdminGrantSensorPermissions= */ true);
+            getPermissionControllerManager(context, UserHandle.of(userId))
+                    // TODO: remove callingPackage param and stop passing context.getPackageName()
+                    .setRuntimePermissionGrantStateByDeviceAdmin(context.getPackageName(),
+                            permissionParams, context.getMainExecutor(), callback::trigger);
+            try {
+                return callback.await(20_000, TimeUnit.MILLISECONDS);
+            } catch (Exception e) {
+                // TODO: add logging
+                return false;
+            }
+        }));
+    }
+
+    @NonNull
+    private static PermissionControllerManager getPermissionControllerManager(
+            Context context, UserHandle user) {
+        if (user.equals(context.getUser())) {
+            return context.getSystemService(PermissionControllerManager.class);
+        } else {
+            try {
+                return context.createPackageContextAsUser(context.getPackageName(), /* flags= */ 0,
+                        user).getSystemService(PermissionControllerManager.class);
+            } catch (PackageManager.NameNotFoundException notPossible) {
+                // not possible
+                throw new IllegalStateException(notPossible);
+            }
+        }
+    }
+
+    static boolean setLockTask(
+            @Nullable LockTaskPolicy policy, @NonNull Context context, int userId) {
+        List<String> packages = Collections.emptyList();
+        int flags = LockTaskPolicy.DEFAULT_LOCK_TASK_FLAG;
+        if (policy != null) {
+            packages = List.copyOf(policy.getPackages());
+            flags = policy.getFlags();
+        }
+        DevicePolicyManagerService.updateLockTaskPackagesLocked(context, packages, userId);
+        DevicePolicyManagerService.updateLockTaskFeaturesLocked(flags, userId);
+        return true;
+    }
+
+    private static class BlockingCallback {
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private final AtomicReference<Boolean> mValue = new AtomicReference<>();
+        public void trigger(Boolean value) {
+            mValue.set(value);
+            mLatch.countDown();
+        }
+
+        public Boolean await(long timeout, TimeUnit unit) throws InterruptedException {
+            if (!mLatch.await(timeout, unit)) {
+                Slogf.e(LOG_TAG, "Callback was not received");
+            }
+            return mValue.get();
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
new file mode 100644
index 0000000..528d3b0
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
@@ -0,0 +1,30 @@
+/*
+ * 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.devicepolicy;
+
+import android.annotation.NonNull;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+
+abstract class PolicySerializer<V> {
+    abstract void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull V value)
+            throws IOException;
+    abstract V readFromXml(TypedXmlPullParser parser, String attributeName);
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
new file mode 100644
index 0000000..d3dee98
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -0,0 +1,145 @@
+/*
+ * 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.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Objects;
+
+/**
+ * Class containing all values set for a certain policy by different admins.
+ */
+final class PolicyState<V> {
+    private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry";
+    private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry";
+    private static final String ATTR_POLICY_VALUE = "policy-value";
+    private static final String ATTR_RESOLVED_POLICY = "resolved-policy";
+
+    private final PolicyDefinition<V> mPolicyDefinition;
+    private final LinkedHashMap<EnforcingAdmin, V> mPoliciesSetByAdmins = new LinkedHashMap<>();
+    private V mCurrentResolvedPolicy;
+
+    PolicyState(@NonNull PolicyDefinition<V> policyDefinition) {
+        mPolicyDefinition = Objects.requireNonNull(policyDefinition);
+    }
+
+    private PolicyState(
+            @NonNull PolicyDefinition<V> policyDefinition,
+            @NonNull LinkedHashMap<EnforcingAdmin, V> policiesSetByAdmins,
+            V currentEnforcedPolicy) {
+        Objects.requireNonNull(policyDefinition);
+        Objects.requireNonNull(policiesSetByAdmins);
+
+        mPolicyDefinition = policyDefinition;
+        mPoliciesSetByAdmins.putAll(policiesSetByAdmins);
+        mCurrentResolvedPolicy = currentEnforcedPolicy;
+    }
+
+    /**
+     * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
+     */
+    boolean setPolicy(@NonNull EnforcingAdmin admin, @NonNull V value) {
+        mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(value));
+
+        return resolvePolicy();
+    }
+
+    boolean removePolicy(@NonNull EnforcingAdmin admin) {
+        Objects.requireNonNull(admin);
+
+        if (mPoliciesSetByAdmins.remove(admin) == null) {
+            return false;
+        }
+
+        return resolvePolicy();
+    }
+
+    LinkedHashMap<EnforcingAdmin, V> getPoliciesSetByAdmins() {
+        return mPoliciesSetByAdmins;
+    }
+
+    private boolean resolvePolicy() {
+        V resolvedPolicy = mPolicyDefinition.resolvePolicy(mPoliciesSetByAdmins);
+        boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy);
+        mCurrentResolvedPolicy = resolvedPolicy;
+
+        return policyChanged;
+    }
+
+    @Nullable
+    V getCurrentResolvedPolicy() {
+        return mCurrentResolvedPolicy;
+    }
+
+    void saveToXml(TypedXmlSerializer serializer) throws IOException {
+        mPolicyDefinition.saveToXml(serializer);
+
+        if (mCurrentResolvedPolicy != null) {
+            mPolicyDefinition.savePolicyValueToXml(
+                    serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy);
+        }
+
+        for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
+            serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY);
+
+            mPolicyDefinition.savePolicyValueToXml(
+                    serializer, ATTR_POLICY_VALUE, mPoliciesSetByAdmins.get(admin));
+
+            serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY);
+            admin.saveToXml(serializer);
+            serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY);
+
+            serializer.endTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY);
+        }
+    }
+
+    static <V> PolicyState<V> readFromXml(TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        PolicyDefinition<V> policyDefinition = PolicyDefinition.readFromXml(parser);
+        LinkedHashMap<EnforcingAdmin, V> adminsPolicy = new LinkedHashMap<>();
+        V currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(
+                parser, ATTR_RESOLVED_POLICY);
+        int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            String tag = parser.getName();
+            if (TAG_ADMIN_POLICY_ENTRY.equals(tag)) {
+                V value = policyDefinition.readPolicyValueFromXml(
+                        parser, ATTR_POLICY_VALUE);
+                EnforcingAdmin admin;
+                int adminPolicyDepth = parser.getDepth();
+                if (XmlUtils.nextElementWithin(parser, adminPolicyDepth)
+                        && parser.getName().equals(TAG_ENFORCING_ADMIN_ENTRY)) {
+                    admin = EnforcingAdmin.readFromXml(parser);
+                    adminsPolicy.put(admin, value);
+                }
+            } else {
+                Log.e(DevicePolicyEngine.TAG, "Unknown tag: " + tag);
+            }
+        }
+        return new PolicyState<V>(policyDefinition, adminsPolicy, currentResolvedPolicy);
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java b/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java
new file mode 100644
index 0000000..7b720bc
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java
@@ -0,0 +1,26 @@
+/*
+ * 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.devicepolicy;
+
+import android.annotation.Nullable;
+
+import java.util.LinkedHashMap;
+
+abstract class ResolutionMechanism<V> {
+    @Nullable
+    abstract V resolve(LinkedHashMap<EnforcingAdmin, V> adminPolicies);
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SetUnion.java b/services/devicepolicy/java/com/android/server/devicepolicy/SetUnion.java
new file mode 100644
index 0000000..8a932c3
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SetUnion.java
@@ -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.server.devicepolicy;
+
+import android.annotation.NonNull;
+
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Objects;
+import java.util.Set;
+
+final class SetUnion<V> extends ResolutionMechanism<Set<V>> {
+
+    @Override
+    Set<V> resolve(@NonNull LinkedHashMap<EnforcingAdmin, Set<V>> adminPolicies) {
+        Objects.requireNonNull(adminPolicies);
+        if (adminPolicies.isEmpty()) {
+            return null;
+        }
+        Set<V> unionOfPolicies = new HashSet<>();
+        for (Set<V> policy : adminPolicies.values()) {
+            unionOfPolicies.addAll(policy);
+        }
+        return unionOfPolicies;
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java b/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java
new file mode 100644
index 0000000..4467b22
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.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 com.android.server.devicepolicy;
+
+import android.annotation.NonNull;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+final class TopPriority<V> extends ResolutionMechanism<V> {
+
+    private final List<String> mHighestToLowestPriorityAuthorities;
+
+    TopPriority(@NonNull List<String> highestToLowestPriorityAuthorities) {
+        Objects.requireNonNull(highestToLowestPriorityAuthorities);
+        mHighestToLowestPriorityAuthorities = highestToLowestPriorityAuthorities;
+    }
+
+    @Override
+    V resolve(@NonNull LinkedHashMap<EnforcingAdmin, V> adminPolicies) {
+        if (adminPolicies.isEmpty()) {
+            return null;
+        }
+        for (String authority : mHighestToLowestPriorityAuthorities) {
+            Optional<EnforcingAdmin> admin = adminPolicies.keySet().stream()
+                    .filter(a -> a.hasAuthority(authority)).findFirst();
+            if (admin.isPresent()) {
+                return adminPolicies.get(admin.get());
+            }
+        }
+        // Return first set policy if no known authority is found
+        Map.Entry<EnforcingAdmin, V> policy = adminPolicies.entrySet().stream().findFirst().get();
+        return policy.getValue();
+    }
+}
diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java
index 46e59a9..c3329795 100644
--- a/services/java/com/android/server/BootUserInitializer.java
+++ b/services/java/com/android/server/BootUserInitializer.java
@@ -83,9 +83,10 @@
             Slogf.d(TAG, "Creating initial user");
             t.traceBegin("create-initial-user");
             try {
+                int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN;
                 // TODO(b/204091126): proper name for user
                 UserInfo newUser = um.createUserEvenWhenDisallowed("Real User",
-                        UserManager.USER_TYPE_FULL_SECONDARY, UserInfo.FLAG_ADMIN,
+                        UserManager.USER_TYPE_FULL_SECONDARY, flags,
                         /* disallowedPackages= */ null, /* token= */ null);
                 Slogf.i(TAG, "Created initial user: %s", newUser.toFullString());
                 initialUserId = newUser.id;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 593e648..e96eff21 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -41,7 +41,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
@@ -55,6 +54,7 @@
 import android.net.ConnectivityManager;
 import android.net.ConnectivityModuleConnector;
 import android.net.NetworkStackClient;
+import android.os.ArtModuleServiceManager;
 import android.os.BaseBundle;
 import android.os.Binder;
 import android.os.Build;
@@ -103,10 +103,12 @@
 import com.android.internal.util.EmergencyAffordanceManager;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.ambientcontext.AmbientContextManagerService;
 import com.android.server.appbinding.AppBindingService;
 import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.ArtModuleServiceInitializer;
 import com.android.server.attention.AttentionManagerService;
 import com.android.server.audio.AudioService;
 import com.android.server.biometrics.AuthService;
@@ -165,7 +167,6 @@
 import com.android.server.pm.ShortcutService;
 import com.android.server.pm.UserManagerService;
 import com.android.server.pm.dex.OdsignStatsLogger;
-import com.android.server.pm.dex.SystemServerDexLoadReporter;
 import com.android.server.pm.verify.domain.DomainVerificationService;
 import com.android.server.policy.AppOpsPolicy;
 import com.android.server.policy.PermissionPolicyService;
@@ -210,6 +211,7 @@
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.vibrator.VibratorManagerService;
 import com.android.server.vr.VrManagerService;
+import com.android.server.wearable.WearableSensingManagerService;
 import com.android.server.webkit.WebViewUpdateService;
 import com.android.server.wm.ActivityTaskManagerService;
 import com.android.server.wm.WindowManagerGlobalLock;
@@ -318,8 +320,6 @@
             "com.android.clockwork.displayoffload.DisplayOffloadService";
     private static final String WEAR_DISPLAY_SERVICE_CLASS =
             "com.android.clockwork.display.WearDisplayService";
-    private static final String WEAR_LEFTY_SERVICE_CLASS =
-            "com.google.android.clockwork.lefty.WearLeftyService";
     private static final String WEAR_TIME_SERVICE_CLASS =
             "com.android.clockwork.time.WearTimeService";
     private static final String WEAR_GLOBAL_ACTIONS_SERVICE_CLASS =
@@ -466,6 +466,7 @@
     private final long mRuntimeStartUptime;
 
     private static final String START_HIDL_SERVICES = "StartHidlServices";
+    private static final String START_SENSOR_MANAGER_SERVICE = "StartISensorManagerService";
     private static final String START_BLOB_STORE_SERVICE = "startBlobStoreManagerService";
 
     private static final String SYSPROP_START_COUNT = "sys.system_server.start_count";
@@ -484,6 +485,9 @@
     /** Start the IStats services. This is a blocking call and can take time. */
     private static native void startIStatsService();
 
+    /** Start the ISensorManager service. This is a blocking call and can take time. */
+    private static native void startISensorManagerService();
+
     /**
      * Start the memtrack proxy service.
      */
@@ -1205,24 +1209,16 @@
         mSystemServiceManager.startService(domainVerificationService);
         t.traceEnd();
 
-        IPackageManager iPackageManager;
         t.traceBegin("StartPackageManagerService");
         try {
             Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain");
-            Pair<PackageManagerService, IPackageManager> pmsPair = PackageManagerService.main(
+            mPackageManagerService = PackageManagerService.main(
                     mSystemContext, installer, domainVerificationService,
                     mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF);
-            mPackageManagerService = pmsPair.first;
-            iPackageManager = pmsPair.second;
         } finally {
             Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");
         }
 
-        // Now that the package manager has started, register the dex load reporter to capture any
-        // dex files loaded by system server.
-        // These dex files will be optimized by the BackgroundDexOptService.
-        SystemServerDexLoadReporter.configureSystemServerDexReporter(iPackageManager);
-
         mFirstBoot = mPackageManagerService.isFirstBoot();
         mPackageManager = mSystemContext.getPackageManager();
         t.traceEnd();
@@ -1404,7 +1400,6 @@
                 false);
         boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
                 false);
-        boolean enableLeftyService = SystemProperties.getBoolean("config.enable_lefty", false);
 
         boolean isEmulator = SystemProperties.get("ro.boot.qemu").equals("1");
 
@@ -1570,11 +1565,18 @@
             wm.onInitReady();
             t.traceEnd();
 
-            // Start receiving calls from HIDL services. Start in in a separate thread
+            // Start receiving calls from SensorManager services. Start in a separate thread
             // because it need to connect to SensorManager. This has to start
             // after PHASE_WAIT_FOR_SENSOR_SERVICE is done.
             SystemServerInitThreadPool.submit(() -> {
                 TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog();
+                traceLog.traceBegin(START_SENSOR_MANAGER_SERVICE);
+                startISensorManagerService();
+                traceLog.traceEnd();
+            }, START_SENSOR_MANAGER_SERVICE);
+
+            SystemServerInitThreadPool.submit(() -> {
+                TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog();
                 traceLog.traceBegin(START_HIDL_SERVICES);
                 startHidlServices();
                 traceLog.traceEnd();
@@ -1668,7 +1670,18 @@
         // Bring up services needed for UI.
         if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
             t.traceBegin("StartInputMethodManagerLifecycle");
-            mSystemServiceManager.startService(InputMethodManagerService.Lifecycle.class);
+            String immsClassName = context.getResources().getString(
+                    R.string.config_deviceSpecificInputMethodManagerService);
+            if (immsClassName.isEmpty()) {
+                mSystemServiceManager.startService(InputMethodManagerService.Lifecycle.class);
+            } else {
+                try {
+                    Slog.i(TAG, "Starting custom IMMS: " + immsClassName);
+                    mSystemServiceManager.startService(immsClassName);
+                } catch (Throwable e) {
+                    reportWtf("starting " + immsClassName, e);
+                }
+            }
             t.traceEnd();
 
             t.traceBegin("StartAccessibilityManagerService");
@@ -1789,17 +1802,18 @@
             dpms = mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
             t.traceEnd();
 
-            if (!isWatch) {
-                t.traceBegin("StartStatusBarManagerService");
-                try {
-                    statusBar = new StatusBarManagerService(context);
-                    ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar, false,
-                            DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
-                } catch (Throwable e) {
-                    reportWtf("starting StatusBarManagerService", e);
+            t.traceBegin("StartStatusBarManagerService");
+            try {
+                statusBar = new StatusBarManagerService(context);
+                if (!isWatch) {
+                    statusBar.publishGlobalActionsProvider();
                 }
-                t.traceEnd();
+                ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar, false,
+                        DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
+            } catch (Throwable e) {
+                reportWtf("starting StatusBarManagerService", e);
             }
+            t.traceEnd();
 
             if (deviceHasConfigString(context,
                     R.string.config_defaultMusicRecognitionService)) {
@@ -1817,6 +1831,7 @@
             startSystemCaptionsManagerService(context, t);
             startTextToSpeechManagerService(context, t);
             startAmbientContextService(t);
+            startWearableSensingService(t);
 
             // System Speech Recognition Service
             t.traceBegin("StartSpeechRecognitionManagerService");
@@ -2502,12 +2517,6 @@
             mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
             t.traceEnd();
 
-            if (enableLeftyService) {
-                t.traceBegin("StartWearLeftyService");
-                mSystemServiceManager.startService(WEAR_LEFTY_SERVICE_CLASS);
-                t.traceEnd();
-            }
-
             t.traceBegin("StartWearGlobalActionsService");
             mSystemServiceManager.startService(WEAR_GLOBAL_ACTIONS_SERVICE_CLASS);
             t.traceEnd();
@@ -2708,6 +2717,7 @@
         t.traceEnd();
 
         t.traceBegin("ArtManagerLocal");
+        ArtModuleServiceInitializer.setArtModuleServiceManager(new ArtModuleServiceManager());
         LocalManagerRegistry.addManager(ArtManagerLocal.class, new ArtManagerLocal(context));
         t.traceEnd();
 
@@ -3017,6 +3027,14 @@
             t.traceEnd();
         }, t);
 
+        t.traceBegin("LockSettingsThirdPartyAppsStarted");
+        LockSettingsInternal lockSettingsInternal =
+            LocalServices.getService(LockSettingsInternal.class);
+        if (lockSettingsInternal != null) {
+            lockSettingsInternal.onThirdPartyAppsStarted();
+        }
+        t.traceEnd();
+
         t.traceBegin("StartSystemUI");
         try {
             startSystemUi(context, windowManagerF);
@@ -3154,6 +3172,12 @@
         t.traceEnd();
     }
 
+    private void startWearableSensingService(@NonNull TimingsTraceAndSlog t) {
+        t.traceBegin("startWearableSensingService");
+        mSystemServiceManager.startService(WearableSensingManagerService.class);
+        t.traceEnd();
+    }
+
     private static void startSystemUi(Context context, WindowManagerService windowManager) {
         PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
         Intent intent = new Intent();
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index eab3b77..292320e 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -53,6 +53,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
@@ -372,7 +373,8 @@
         @Override
         public boolean equals(Object o) {
             ListenerKey key = (ListenerKey) o;
-            return key.getPackageName().equals(mPackageName) && key.getUserId() == mUserId
+            return key.getPackageName().equals(mPackageName)
+                    && Objects.equals(key.getUserId(), mUserId)
                     && key.getShortcutId().equals(mShortcutId);
         }
 
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 693f3a0..1bd5031 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -788,7 +788,7 @@
 
     private void updateDefaultSmsApp(@NonNull UserData userData) {
         ComponentName component = SmsApplication.getDefaultSmsApplicationAsUser(
-                mContext, /* updateIfNeeded= */ false, userData.getUserId());
+                mContext, /* updateIfNeeded= */ false, UserHandle.of(userData.getUserId()));
         String defaultSmsApp = component != null ? component.getPackageName() : null;
         userData.setDefaultSmsApp(defaultSmsApp);
     }
diff --git a/services/permission/Android.bp b/services/permission/Android.bp
new file mode 100644
index 0000000..dc9b558
--- /dev/null
+++ b/services/permission/Android.bp
@@ -0,0 +1,42 @@
+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"],
+}
+
+filegroup {
+    name: "services.permission-sources",
+    srcs: [
+        "java/**/*.java",
+        "java/**/*.kt",
+    ],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.permission",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.permission-sources"],
+    libs: [
+        "services.core",
+        // Soong fails to automatically add this dependency because all the
+        // *.kt sources are inside a filegroup.
+        "kotlin-annotations",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+        // Adds reflection-less suppressed exceptions and AutoCloseable.use().
+        "kotlin-stdlib-jdk7",
+    ],
+    jarjar_rules: "jarjar-rules.txt",
+    kotlincflags: [
+        "-Xjvm-default=all",
+        "-Xno-call-assertions",
+        "-Xno-param-assertions",
+        "-Xno-receiver-assertions",
+    ],
+}
diff --git a/services/permission/OWNERS b/services/permission/OWNERS
new file mode 100644
index 0000000..6c6c9fc
--- /dev/null
+++ b/services/permission/OWNERS
@@ -0,0 +1,4 @@
+ashfall@google.com
+joecastro@google.com
+ntmyren@google.com
+zhanghai@google.com
diff --git a/services/permission/jarjar-rules.txt b/services/permission/jarjar-rules.txt
new file mode 100644
index 0000000..34af3af
--- /dev/null
+++ b/services/permission/jarjar-rules.txt
@@ -0,0 +1 @@
+rule kotlin.** com.android.server.permission.jarjar.@0
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
new file mode 100644
index 0000000..7b96d42
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.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.server.permission.access
+
+import com.android.internal.annotations.Keep
+import com.android.server.permission.access.external.PackageState
+
+@Keep
+class AccessCheckingService {
+    @Volatile
+    private lateinit var state: AccessState
+    private val stateLock = Any()
+
+    private val policy = AccessPolicy()
+
+    private val persistence = AccessPersistence(policy)
+
+    fun init() {
+        val state = AccessState()
+        state.systemState.userIds.apply {
+            // TODO: Get and add all user IDs.
+            // TODO: Maybe get and add all packages?
+        }
+        persistence.read(state)
+        this.state = state
+    }
+
+    fun getDecision(subject: AccessUri, `object`: AccessUri): Int =
+        getState {
+            with(policy) { getDecision(subject, `object`) }
+        }
+
+    fun setDecision(subject: AccessUri, `object`: AccessUri, decision: Int) {
+        mutateState {
+            with(policy) { setDecision(subject, `object`, decision) }
+        }
+    }
+
+    fun onUserAdded(userId: Int) {
+        mutateState {
+            with(policy) { onUserAdded(userId) }
+        }
+    }
+
+    fun onUserRemoved(userId: Int) {
+        mutateState {
+            with(policy) { onUserRemoved(userId) }
+        }
+    }
+
+    fun onPackageAdded(packageState: PackageState) {
+        mutateState {
+            with(policy) { onPackageAdded(packageState) }
+        }
+    }
+
+    fun onPackageRemoved(packageState: PackageState) {
+        mutateState {
+            with(policy) { onPackageRemoved(packageState) }
+        }
+    }
+
+    internal inline fun <T> getState(action: GetStateScope.() -> T): T =
+        GetStateScope(state).action()
+
+    internal inline fun mutateState(action: MutateStateScope.() -> Unit) {
+        synchronized(stateLock) {
+            val oldState = state
+            val newState = oldState.copy()
+            MutateStateScope(oldState, newState).action()
+            persistence.write(newState)
+            state = newState
+        }
+    }
+
+    internal fun getSchemePolicy(subjectScheme: String, objectScheme: String): SchemePolicy =
+        policy.getSchemePolicy(subjectScheme, objectScheme)
+}
diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
new file mode 100644
index 0000000..022f09a
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access
+
+import android.util.AtomicFile
+import android.util.Log
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.PermissionApex
+import com.android.server.permission.access.util.parseBinaryXml
+import com.android.server.permission.access.util.read
+import com.android.server.permission.access.util.serializeBinaryXml
+import com.android.server.permission.access.util.writeInlined
+import java.io.File
+import java.io.FileNotFoundException
+
+class AccessPersistence(
+    private val policy: AccessPolicy
+) {
+    fun read(state: AccessState) {
+        readSystemState(state.systemState)
+        val userStates = state.userStates
+        state.systemState.userIds.forEachIndexed { _, userId ->
+            readUserState(userId, userStates[userId])
+        }
+    }
+
+    private fun readSystemState(systemState: SystemState) {
+        systemFile.parse {
+            // This is the canonical way to call an extension function in a different class.
+            // TODO(b/259469752): Use context receiver for this when it becomes stable.
+            with(policy) { parseSystemState(systemState) }
+        }
+    }
+
+    private fun readUserState(userId: Int, userState: UserState) {
+        getUserFile(userId).parse {
+            with(policy) { parseUserState(userId, userState) }
+        }
+    }
+
+    private inline fun File.parse(block: BinaryXmlPullParser.() -> Unit) {
+        try {
+            AtomicFile(this).read { it.parseBinaryXml(block) }
+        } catch (e: FileNotFoundException) {
+            Log.i(LOG_TAG, "$this not found")
+        } catch (e: Exception) {
+            throw IllegalStateException("Failed to read $this", e)
+        }
+    }
+
+    fun write(state: AccessState) {
+        writeState(state.systemState, ::writeSystemState)
+        state.userStates.forEachIndexed { _, userId, userState ->
+            writeState(userState) { writeUserState(userId, it) }
+        }
+    }
+
+    private inline fun <T : WritableState> writeState(state: T, write: (T) -> Unit) {
+        when (val writeMode = state.writeMode) {
+            WriteMode.NONE -> {}
+            WriteMode.SYNC -> write(state)
+            WriteMode.ASYNC -> TODO()
+            else -> error(writeMode)
+        }
+    }
+
+    private fun writeSystemState(systemState: SystemState) {
+        systemFile.serialize {
+            with(policy) { serializeSystemState(systemState) }
+        }
+    }
+
+    private fun writeUserState(userId: Int, userState: UserState) {
+        getUserFile(userId).serialize {
+            with(policy) { serializeUserState(userId, userState) }
+        }
+    }
+
+    private inline fun File.serialize(block: BinaryXmlSerializer.() -> Unit) {
+        try {
+            AtomicFile(this).writeInlined { it.serializeBinaryXml(block) }
+        } catch (e: Exception) {
+            Log.e(LOG_TAG, "Failed to serialize $this", e)
+        }
+    }
+
+    private val systemFile: File
+        get() = File(PermissionApex.systemDataDirectory, FILE_NAME)
+
+    private fun getUserFile(userId: Int): File =
+        File(PermissionApex.getUserDataDirectory(userId), FILE_NAME)
+
+    companion object {
+        private val LOG_TAG = AccessPersistence::class.java.simpleName
+
+        private const val FILE_NAME = "access.abx"
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
new file mode 100644
index 0000000..e9741c6
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -0,0 +1,253 @@
+/*
+ * 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.permission.access
+
+import android.util.Log
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.appop.PackageAppOpPolicy
+import com.android.server.permission.access.appop.UidAppOpPolicy
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.external.PackageState
+import com.android.server.permission.access.permission.UidPermissionPolicy
+import com.android.server.permission.access.util.forEachTag
+import com.android.server.permission.access.util.tag
+import com.android.server.permission.access.util.tagName
+
+class AccessPolicy private constructor(
+    private val schemePolicies: IndexedMap<String, IndexedMap<String, SchemePolicy>>
+) {
+    constructor() : this(
+        IndexedMap<String, IndexedMap<String, SchemePolicy>>().apply {
+            fun addPolicy(policy: SchemePolicy) =
+                getOrPut(policy.subjectScheme) { IndexedMap() }.put(policy.objectScheme, policy)
+            addPolicy(UidPermissionPolicy())
+            addPolicy(UidAppOpPolicy())
+            addPolicy(PackageAppOpPolicy())
+        }
+    )
+
+    fun getSchemePolicy(subjectScheme: String, objectScheme: String): SchemePolicy =
+        checkNotNull(schemePolicies[subjectScheme]?.get(objectScheme)) {
+            "Scheme policy for $subjectScheme and $objectScheme does not exist"
+        }
+
+    fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int =
+        with(getSchemePolicy(subject, `object`)){ getDecision(subject, `object`) }
+
+    fun MutateStateScope.setDecision(subject: AccessUri, `object`: AccessUri, decision: Int) {
+        with(getSchemePolicy(subject, `object`)) { setDecision(subject, `object`, decision) }
+    }
+
+    fun MutateStateScope.onUserAdded(userId: Int) {
+        newState.systemState.userIds += userId
+        newState.userStates[userId] = UserState()
+        forEachSchemePolicy {
+            with(it) { onUserAdded(userId) }
+        }
+    }
+
+    fun MutateStateScope.onUserRemoved(userId: Int) {
+        newState.systemState.userIds -= userId
+        newState.userStates -= userId
+        forEachSchemePolicy {
+            with(it) { onUserRemoved(userId) }
+        }
+    }
+
+    fun MutateStateScope.onPackageAdded(packageState: PackageState) {
+        var isAppIdAdded = false
+        newState.systemState.apply {
+            packageStates[packageState.packageName] = packageState
+            appIds.getOrPut(packageState.appId) {
+                isAppIdAdded = true
+                IndexedListSet()
+            }.add(packageState.packageName)
+        }
+        if (isAppIdAdded) {
+            forEachSchemePolicy {
+                with(it) { onAppIdAdded(packageState.appId) }
+            }
+        }
+        forEachSchemePolicy {
+            with(it) { onPackageAdded(packageState) }
+        }
+    }
+
+    fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
+        var isAppIdRemoved = false
+        newState.systemState.apply {
+            packageStates -= packageState.packageName
+            appIds.apply appIds@{
+                this[packageState.appId]?.apply {
+                    this -= packageState.packageName
+                    if (isEmpty()) {
+                        this@appIds -= packageState.appId
+                        isAppIdRemoved = true
+                    }
+                }
+            }
+        }
+        forEachSchemePolicy {
+            with(it) { onPackageRemoved(packageState) }
+        }
+        if (isAppIdRemoved) {
+            forEachSchemePolicy {
+                with(it) { onAppIdRemoved(packageState.appId) }
+            }
+        }
+    }
+
+    fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {
+        forEachTag {
+            when (tagName) {
+                TAG_ACCESS -> {
+                    forEachTag {
+                        forEachSchemePolicy {
+                            with(it) { parseSystemState(systemState) }
+                        }
+                    }
+                }
+                else -> Log.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state")
+            }
+        }
+    }
+
+    fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) {
+        tag(TAG_ACCESS) {
+            forEachSchemePolicy {
+                with(it) { serializeSystemState(systemState) }
+            }
+        }
+    }
+
+    fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {
+        forEachTag {
+            when (tagName) {
+                TAG_ACCESS -> {
+                    forEachTag {
+                        forEachSchemePolicy {
+                            with(it) { parseUserState(userId, userState) }
+                        }
+                    }
+                }
+                else -> {
+                    Log.w(
+                        LOG_TAG,
+                        "Ignoring unknown tag $tagName when parsing user state for user $userId"
+                    )
+                }
+            }
+        }
+    }
+
+    fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {
+        tag(TAG_ACCESS) {
+            forEachSchemePolicy {
+                with(it) { serializeUserState(userId, userState) }
+            }
+        }
+    }
+
+    private fun getSchemePolicy(subject: AccessUri, `object`: AccessUri): SchemePolicy =
+        getSchemePolicy(subject.scheme, `object`.scheme)
+
+    private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) {
+        schemePolicies.forEachValueIndexed { _, objectSchemePolicies ->
+            objectSchemePolicies.forEachValueIndexed { _, schemePolicy ->
+                action(schemePolicy)
+            }
+        }
+    }
+
+    companion object {
+        private val LOG_TAG = AccessPolicy::class.java.simpleName
+
+        private const val TAG_ACCESS = "access"
+    }
+}
+
+abstract class SchemePolicy {
+    @Volatile
+    private var onDecisionChangedListeners = IndexedListSet<OnDecisionChangedListener>()
+    private val onDecisionChangedListenersLock = Any()
+
+    abstract val subjectScheme: String
+
+    abstract val objectScheme: String
+
+    abstract fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int
+
+    abstract fun MutateStateScope.setDecision(
+        subject: AccessUri,
+        `object`: AccessUri,
+        decision: Int
+    )
+
+    fun addOnDecisionChangedListener(listener: OnDecisionChangedListener) {
+        synchronized(onDecisionChangedListenersLock) {
+            onDecisionChangedListeners = onDecisionChangedListeners + listener
+        }
+    }
+
+    fun removeOnDecisionChangedListener(listener: OnDecisionChangedListener) {
+        synchronized(onDecisionChangedListenersLock) {
+            onDecisionChangedListeners = onDecisionChangedListeners - listener
+        }
+    }
+
+    protected fun notifyOnDecisionChangedListeners(
+        subject: AccessUri,
+        `object`: AccessUri,
+        oldDecision: Int,
+        newDecision: Int
+    ) {
+        val listeners = onDecisionChangedListeners
+        listeners.forEachIndexed { _, it ->
+            it.onDecisionChanged(subject, `object`, oldDecision, newDecision)
+        }
+    }
+
+    open fun MutateStateScope.onUserAdded(userId: Int) {}
+
+    open fun MutateStateScope.onUserRemoved(userId: Int) {}
+
+    open fun MutateStateScope.onAppIdAdded(appId: Int) {}
+
+    open fun MutateStateScope.onAppIdRemoved(appId: Int) {}
+
+    open fun MutateStateScope.onPackageAdded(packageState: PackageState) {}
+
+    open fun MutateStateScope.onPackageRemoved(packageState: PackageState) {}
+
+    open fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {}
+
+    open fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) {}
+
+    open fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {}
+
+    open fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {}
+
+    fun interface OnDecisionChangedListener {
+        fun onDecisionChanged(
+            subject: AccessUri,
+            `object`: AccessUri,
+            oldDecision: Int,
+            newDecision: Int
+        )
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
new file mode 100644
index 0000000..cf8f383
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.permission.access
+
+import android.content.pm.PermissionGroupInfo
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.data.Permission
+import com.android.server.permission.access.external.PackageState
+
+class AccessState private constructor(
+    val systemState: SystemState,
+    val userStates: IntMap<UserState>
+) {
+    constructor() : this(SystemState(), IntMap())
+
+    fun copy(): AccessState = AccessState(systemState.copy(), userStates.copy { it.copy() })
+}
+
+class SystemState private constructor(
+    val userIds: IntSet,
+    val packageStates: IndexedMap<String, PackageState>,
+    val disabledSystemPackageStates: IndexedMap<String, PackageState>,
+    val appIds: IntMap<IndexedListSet<String>>,
+    // A map of KnownPackagesInt to a set of known package names
+    val knownPackages: IntMap<IndexedListSet<String>>,
+    // A map of userId to packageName
+    val deviceAndProfileOwners: IntMap<String>,
+    // A map of packageName to (A map of oem permission name to whether it's granted)
+    val oemPermissions: IndexedMap<String, IndexedMap<String, Boolean>>,
+    val privilegedPermissionAllowlistSourcePackageNames: IndexedListSet<String>,
+    // A map of packageName to a set of vendor priv app permission names
+    val vendorPrivAppPermissions: Map<String, Set<String>>,
+    val productPrivAppPermissions: Map<String, Set<String>>,
+    val systemExtPrivAppPermissions: Map<String, Set<String>>,
+    val privAppPermissions: Map<String, Set<String>>,
+    val apexPrivAppPermissions: Map<String, Map<String, Set<String>>>,
+    val vendorPrivAppDenyPermissions: Map<String, Set<String>>,
+    val productPrivAppDenyPermissions: Map<String, Set<String>>,
+    val systemExtPrivAppDenyPermissions: Map<String, Set<String>>,
+    val apexPrivAppDenyPermissions: Map<String, Map<String, Set<String>>>,
+    val privAppDenyPermissions: Map<String, Set<String>>,
+    val implicitToSourcePermissions: Map<String, Set<String>>,
+    val permissionGroups: IndexedMap<String, PermissionGroupInfo>,
+    val permissionTrees: IndexedMap<String, Permission>,
+    val permissions: IndexedMap<String, Permission>
+) : WritableState() {
+    constructor() : this(
+        IntSet(), IndexedMap(), IndexedMap(), IntMap(), IntMap(), IntMap(), IndexedMap(),
+        IndexedListSet(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(),
+        IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(),
+        IndexedMap(), IndexedMap(), IndexedMap()
+    )
+
+    fun copy(): SystemState =
+        SystemState(
+            userIds.copy(),
+            packageStates.copy { it },
+            disabledSystemPackageStates.copy { it },
+            appIds.copy { it.copy() },
+            knownPackages.copy { it.copy() },
+            deviceAndProfileOwners.copy { it },
+            oemPermissions.copy { it.copy { it } },
+            privilegedPermissionAllowlistSourcePackageNames.copy(),
+            vendorPrivAppPermissions,
+            productPrivAppPermissions,
+            systemExtPrivAppPermissions,
+            privAppPermissions,
+            apexPrivAppPermissions,
+            vendorPrivAppDenyPermissions,
+            productPrivAppDenyPermissions,
+            systemExtPrivAppDenyPermissions,
+            apexPrivAppDenyPermissions,
+            privAppDenyPermissions,
+            implicitToSourcePermissions,
+            permissionGroups.copy { it },
+            permissionTrees.copy { it },
+            permissions.copy { it }
+        )
+}
+
+class UserState private constructor(
+    // A map of (appId to a map of (permissionName to permissionFlags))
+    val permissionFlags: IntMap<IndexedMap<String, Int>>,
+    val uidAppOpModes: IntMap<IndexedMap<String, Int>>,
+    val packageAppOpModes: IndexedMap<String, IndexedMap<String, Int>>
+) : WritableState() {
+    constructor() : this(IntMap(), IntMap(), IndexedMap())
+
+    fun copy(): UserState = UserState(permissionFlags.copy { it.copy { it } },
+        uidAppOpModes.copy { it.copy { it } }, packageAppOpModes.copy { it.copy { it } })
+}
+
+object WriteMode {
+    const val NONE = 0
+    const val SYNC = 1
+    const val ASYNC = 2
+}
+
+abstract class WritableState {
+    var writeMode: Int = WriteMode.NONE
+        private set
+
+    fun requestWrite(sync: Boolean = false) {
+        if (sync) {
+            writeMode = WriteMode.SYNC
+        } else {
+            if (writeMode != WriteMode.SYNC) {
+                writeMode = WriteMode.ASYNC
+            }
+        }
+    }
+}
+
+class GetStateScope(
+    val state: AccessState
+)
+
+class MutateStateScope(
+    val oldState: AccessState,
+    val newState: AccessState
+)
diff --git a/services/permission/java/com/android/server/permission/access/AccessUri.kt b/services/permission/java/com/android/server/permission/access/AccessUri.kt
new file mode 100644
index 0000000..7e98d2c
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/AccessUri.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access
+
+import com.android.server.permission.access.external.UserHandle
+import com.android.server.permission.access.external.UserHandleCompat
+
+sealed class AccessUri(
+    val scheme: String
+) {
+    override fun equals(other: Any?): Boolean {
+        throw NotImplementedError()
+    }
+
+    override fun hashCode(): Int {
+        throw NotImplementedError()
+    }
+
+    override fun toString(): String {
+        throw NotImplementedError()
+    }
+}
+
+data class AppOpUri(
+    val appOpName: String
+) : AccessUri(SCHEME) {
+    override fun toString(): String = "$scheme:///$appOpName"
+
+    companion object {
+        const val SCHEME = "app-op"
+    }
+}
+
+data class PackageUri(
+    val packageName: String,
+    val userId: Int
+) : AccessUri(SCHEME) {
+    override fun toString(): String = "$scheme:///$packageName/$userId"
+
+    companion object {
+        const val SCHEME = "package"
+    }
+}
+
+data class PermissionUri(
+    val permissionName: String
+) : AccessUri(SCHEME) {
+    override fun toString(): String = "$scheme:///$permissionName"
+
+    companion object {
+        const val SCHEME = "permission"
+    }
+}
+
+data class UidUri(
+    val uid: Int
+) : AccessUri(SCHEME) {
+    val userId: Int
+        get() = UserHandleCompat.getUserId(uid)
+
+    val appId: Int
+        get() = UserHandle.getAppId(uid)
+
+    override fun toString(): String = "$scheme:///$uid"
+
+    companion object {
+        const val SCHEME = "uid"
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpModes.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpModes.kt
new file mode 100644
index 0000000..d90846f
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpModes.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.server.permission.access.appop
+
+import android.app.AppOpsManager
+
+object AppOpModes {
+    const val MODE_ALLOWED = AppOpsManager.MODE_ALLOWED
+    const val MODE_IGNORED = AppOpsManager.MODE_IGNORED
+    const val MODE_ERRORED = AppOpsManager.MODE_ERRORED
+    const val MODE_DEFAULT = AppOpsManager.MODE_DEFAULT
+    const val MODE_FOREGROUND = AppOpsManager.MODE_FOREGROUND
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt
new file mode 100644
index 0000000..a565feb
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.permission.access.appop
+
+import android.util.ArraySet
+import android.util.SparseBooleanArray
+import android.util.SparseIntArray
+import com.android.server.appop.AppOpsCheckingServiceInterface
+import com.android.server.appop.OnOpModeChangedListener
+import com.android.server.permission.access.AccessCheckingService
+import java.io.PrintWriter
+
+class AppOpsCheckingServiceCompatImpl(
+    private val accessCheckingService: AccessCheckingService
+) : AppOpsCheckingServiceInterface {
+    override fun getNonDefaultUidModes(uid: Int): SparseIntArray {
+        TODO("Not yet implemented")
+    }
+
+    override fun getUidMode(uid: Int, op: Int): Int {
+        TODO("Not yet implemented")
+    }
+
+    override fun setUidMode(uid: Int, op: Int, mode: Int): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun getPackageMode(packageName: String, op: Int, userId: Int): Int {
+        TODO("Not yet implemented")
+    }
+
+    override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun removePackage(packageName: String, userId: Int): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun removeUid(uid: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun areUidModesDefault(uid: Int): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun arePackageModesDefault(packageName: String, userId: Int): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun clearAllModes() {
+        TODO("Not yet implemented")
+    }
+
+    override fun startWatchingOpModeChanged(changedListener: OnOpModeChangedListener, op: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun startWatchingPackageModeChanged(
+        changedListener: OnOpModeChangedListener,
+        packageName: String
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun removeListener(changedListener: OnOpModeChangedListener) {
+        TODO("Not yet implemented")
+    }
+
+    override fun getOpModeChangedListeners(op: Int): ArraySet<OnOpModeChangedListener> {
+        TODO("Not yet implemented")
+    }
+
+    override fun getPackageModeChangedListeners(
+        packageName: String
+    ): ArraySet<OnOpModeChangedListener> {
+        TODO("Not yet implemented")
+    }
+
+    override fun notifyWatchersOfChange(op: Int, uid: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun notifyOpChanged(
+        changedListener: OnOpModeChangedListener,
+        op: Int,
+        uid: Int,
+        packageName: String?
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun notifyOpChangedForAllPkgsInUid(
+        op: Int,
+        uid: Int,
+        onlyForeground: Boolean,
+        callbackToIgnore: OnOpModeChangedListener?
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun evalForegroundUidOps(
+        uid: Int,
+        foregroundOps: SparseBooleanArray?
+    ): SparseBooleanArray {
+        TODO("Not yet implemented")
+    }
+
+    override fun evalForegroundPackageOps(
+        packageName: String,
+        foregroundOps: SparseBooleanArray?,
+        userId: Int
+    ): SparseBooleanArray {
+        TODO("Not yet implemented")
+    }
+
+    override fun dumpListeners(
+        dumpOp: Int,
+        dumpUid: Int,
+        dumpPackage: String?,
+        printWriter: PrintWriter
+    ): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    companion object {
+        private val LOG_TAG = AppOpsCheckingServiceCompatImpl::class.java.simpleName
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
new file mode 100644
index 0000000..031c928
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
@@ -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.
+ */
+
+package com.android.server.permission.access.appop
+
+import android.util.Log
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.UserState
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.attributeInt
+import com.android.server.permission.access.util.attributeInterned
+import com.android.server.permission.access.util.forEachTag
+import com.android.server.permission.access.util.getAttributeIntOrThrow
+import com.android.server.permission.access.util.getAttributeValueOrThrow
+import com.android.server.permission.access.util.tag
+import com.android.server.permission.access.util.tagName
+
+abstract class BaseAppOpPersistence {
+    abstract fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState)
+
+    abstract fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState)
+
+    protected fun BinaryXmlPullParser.parseAppOps(appOpModes: IndexedMap<String, Int>) {
+        forEachTag {
+            when (tagName) {
+                TAG_APP_OP -> parseAppOp(appOpModes)
+                else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
+            }
+        }
+    }
+
+    private fun BinaryXmlPullParser.parseAppOp(appOpModes: IndexedMap<String, Int>) {
+        val name = getAttributeValueOrThrow(ATTR_NAME).intern()
+        val mode = getAttributeIntOrThrow(ATTR_MODE)
+        appOpModes[name] = mode
+    }
+
+    protected fun BinaryXmlSerializer.serializeAppOps(appOpModes: IndexedMap<String, Int>) {
+        appOpModes.forEachIndexed { _, name, mode ->
+            serializeAppOp(name, mode)
+        }
+    }
+
+    private fun BinaryXmlSerializer.serializeAppOp(name: String, mode: Int) {
+        tag(TAG_APP_OP) {
+            attributeInterned(ATTR_NAME, name)
+            attributeInt(ATTR_MODE, mode)
+        }
+    }
+
+    companion object {
+        private val LOG_TAG = BaseAppOpPersistence::class.java.simpleName
+
+        private const val TAG_APP_OP = "app-op"
+
+        private const val ATTR_NAME = "name"
+        private const val ATTR_MODE = "mode"
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
new file mode 100644
index 0000000..a1a5e2d
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.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.server.permission.access.appop
+
+import android.app.AppOpsManager
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.AccessUri
+import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.SchemePolicy
+import com.android.server.permission.access.UserState
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+
+abstract class BaseAppOpPolicy(private val persistence: BaseAppOpPersistence) : SchemePolicy() {
+    override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
+        `object` as AppOpUri
+        return getModes(subject)
+            .getWithDefault(`object`.appOpName, opToDefaultMode(`object`.appOpName))
+    }
+
+    override fun MutateStateScope.setDecision(
+        subject: AccessUri,
+        `object`: AccessUri,
+        decision: Int
+    ) {
+        `object` as AppOpUri
+        val modes = getOrCreateModes(subject)
+        val oldMode = modes.putWithDefault(`object`.appOpName, decision,
+            opToDefaultMode(`object`.appOpName))
+        if (modes.isEmpty()) {
+            removeModes(subject)
+        }
+        if (oldMode != decision) {
+            notifyOnDecisionChangedListeners(subject, `object`, oldMode, decision)
+        }
+    }
+
+    abstract fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>?
+
+    abstract fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int>
+
+    abstract fun MutateStateScope.removeModes(subject: AccessUri)
+
+    // TODO need to check that [AppOpsManager.getSystemAlertWindowDefault] works; likely no issue
+    //  since running in system process.
+    private fun opToDefaultMode(appOpName: String) = AppOpsManager.opToDefaultMode(appOpName)
+
+    override fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {
+        with(persistence) { this@parseUserState.parseUserState(userId, userState) }
+    }
+
+    override fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {
+        with(persistence) { this@serializeUserState.serializeUserState(userId, userState) }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt
new file mode 100644
index 0000000..183a352
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.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.server.permission.access.appop
+
+import android.util.Log
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.UserState
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.attributeInterned
+import com.android.server.permission.access.util.forEachTag
+import com.android.server.permission.access.util.getAttributeValueOrThrow
+import com.android.server.permission.access.util.tag
+import com.android.server.permission.access.util.tagName
+
+class PackageAppOpPersistence : BaseAppOpPersistence() {
+    override fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {
+        when (tagName) {
+            TAG_PACKAGE_APP_OPS -> parsePackageAppOps(userState)
+            else -> {}
+        }
+    }
+
+    private fun BinaryXmlPullParser.parsePackageAppOps(userState: UserState) {
+        forEachTag {
+            when (tagName) {
+                TAG_PACKAGE -> parsePackage(userState)
+                else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
+            }
+        }
+    }
+
+    private fun BinaryXmlPullParser.parsePackage(userState: UserState) {
+        val packageName = getAttributeValueOrThrow(ATTR_NAME).intern()
+        val appOpModes = IndexedMap<String, Int>()
+        userState.packageAppOpModes[packageName] = appOpModes
+        parseAppOps(appOpModes)
+    }
+
+    override fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {
+        serializePackageAppOps(userState)
+    }
+
+    private fun BinaryXmlSerializer.serializePackageAppOps(userState: UserState) {
+        tag(TAG_PACKAGE_APP_OPS) {
+            userState.packageAppOpModes.forEachIndexed { _, packageName, appOpModes ->
+                serializePackage(packageName, appOpModes)
+            }
+        }
+    }
+
+    private fun BinaryXmlSerializer.serializePackage(
+        packageName: String,
+        appOpModes: IndexedMap<String, Int>
+    ) {
+        tag(TAG_PACKAGE) {
+            attributeInterned(ATTR_NAME, packageName)
+            serializeAppOps(appOpModes)
+        }
+    }
+
+    companion object {
+        private val LOG_TAG = PackageAppOpPersistence::class.java.simpleName
+
+        private const val TAG_PACKAGE_APP_OPS = "package-app-ops"
+        private const val TAG_PACKAGE = "package"
+
+        private const val ATTR_NAME = "name"
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
new file mode 100644
index 0000000..966489f
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.permission.access.appop
+
+import com.android.server.permission.access.AccessUri
+import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.PackageUri
+import com.android.server.permission.access.UserState
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.external.PackageState
+
+class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
+    override val subjectScheme: String
+        get() = PackageUri.SCHEME
+
+    override val objectScheme: String
+        get() = AppOpUri.SCHEME
+
+    override fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>? {
+        subject as PackageUri
+        return state.userStates[subject.userId]?.packageAppOpModes?.get(subject.packageName)
+    }
+
+    override fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int> {
+        subject as PackageUri
+        return newState.userStates.getOrPut(subject.userId) { UserState() }
+            .packageAppOpModes.getOrPut(subject.packageName) { IndexedMap() }
+    }
+
+    override fun MutateStateScope.removeModes(subject: AccessUri) {
+        subject as PackageUri
+        newState.userStates[subject.userId]?.packageAppOpModes?.remove(subject.packageName)
+    }
+
+    override fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
+        newState.userStates.forEachIndexed { _, _, userState ->
+            userState.packageAppOpModes -= packageState.packageName
+        }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPersistence.kt
new file mode 100644
index 0000000..3c3a9d1
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPersistence.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.server.permission.access.appop
+
+import android.util.Log
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.UserState
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.attributeInt
+import com.android.server.permission.access.util.forEachTag
+import com.android.server.permission.access.util.getAttributeIntOrThrow
+import com.android.server.permission.access.util.tag
+import com.android.server.permission.access.util.tagName
+
+class UidAppOpPersistence : BaseAppOpPersistence() {
+    override fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {
+        when (tagName) {
+            TAG_UID_APP_OPS -> parseUidAppOps(userState)
+            else -> {}
+        }
+    }
+
+    private fun BinaryXmlPullParser.parseUidAppOps(userState: UserState) {
+        forEachTag {
+            when (tagName) {
+                TAG_UID -> parseUid(userState)
+                else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
+            }
+        }
+    }
+
+    private fun BinaryXmlPullParser.parseUid(userState: UserState) {
+        val uid = getAttributeIntOrThrow(ATTR_UID)
+        val appOpModes = IndexedMap<String, Int>()
+        userState.uidAppOpModes[uid] = appOpModes
+        parseAppOps(appOpModes)
+    }
+
+    override fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {
+        serializeUidAppOps(userState)
+    }
+
+    private fun BinaryXmlSerializer.serializeUidAppOps(userState: UserState) {
+        tag(TAG_UID_APP_OPS) {
+            userState.uidAppOpModes.forEachIndexed { _, uid, appOpModes ->
+                serializeUid(uid, appOpModes)
+            }
+        }
+    }
+
+    private fun BinaryXmlSerializer.serializeUid(uid: Int, appOpModes: IndexedMap<String, Int>) {
+        tag(TAG_UID) {
+            attributeInt(ATTR_UID, uid)
+            serializeAppOps(appOpModes)
+        }
+    }
+
+    companion object {
+        private val LOG_TAG = UidAppOpPersistence::class.java.simpleName
+
+        private const val TAG_UID_APP_OPS = "uid-app-ops"
+        private const val TAG_UID = "uid"
+
+        private const val ATTR_UID = "uid"
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
new file mode 100644
index 0000000..862db8f
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.permission.access.appop
+
+import com.android.server.permission.access.AccessUri
+import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.UserState
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+
+class UidAppOpPolicy : BaseAppOpPolicy(UidAppOpPersistence()) {
+    override val subjectScheme: String
+        get() = UidUri.SCHEME
+
+    override val objectScheme: String
+        get() = AppOpUri.SCHEME
+
+    override fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>? {
+        subject as UidUri
+        return state.userStates[subject.userId]?.uidAppOpModes?.get(subject.appId)
+    }
+
+    override fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int> {
+        subject as UidUri
+        return newState.userStates.getOrPut(subject.userId) { UserState() }
+            .uidAppOpModes.getOrPut(subject.appId) { IndexedMap() }
+    }
+
+    override fun MutateStateScope.removeModes(subject: AccessUri) {
+        subject as UidUri
+        newState.userStates[subject.userId]?.uidAppOpModes?.remove(subject.appId)
+    }
+
+    override fun MutateStateScope.onAppIdRemoved(appId: Int) {
+        newState.userStates.forEachIndexed { _, _, userState ->
+            userState.uidAppOpModes -= appId
+        }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
new file mode 100644
index 0000000..5ba435c79
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.permission.access.collection
+
+typealias IndexedList<T> = ArrayList<T>
+
+inline fun <T> IndexedList<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (!predicate(index, this[index])) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> IndexedList<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (predicate(index, this[index])) {
+            return true
+        }
+    }
+    return false
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> IndexedList<T>.copy(): IndexedList<T> = IndexedList(this)
+
+inline fun <T> IndexedList<T>.forEachIndexed(action: (Int, T) -> Unit) {
+    for (index in indices) {
+        action(index, this[index])
+    }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> IndexedList<T>.minus(element: T): IndexedList<T> =
+    copy().apply { this -= element }
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> IndexedList<T>.minusAssign(element: T) {
+    remove(element)
+}
+
+inline fun <T> IndexedList<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (predicate(index, this[index])) {
+            return false
+        }
+    }
+    return true
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> IndexedList<T>.plus(element: T): IndexedList<T> =
+    copy().apply { this += element }
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> IndexedList<T>.plusAssign(element: T) {
+    add(element)
+}
+
+inline fun <T> IndexedList<T>.removeAllIndexed(predicate: (Int, T) -> Boolean) {
+    for (index in lastIndex downTo 0) {
+        if (predicate(index, this[index])) {
+            removeAt(index)
+        }
+    }
+}
+
+inline fun <T> IndexedList<T>.retainAllIndexed(predicate: (Int, T) -> Boolean) {
+    for (index in lastIndex downTo 0) {
+        if (!predicate(index, this[index])) {
+            removeAt(index)
+        }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
new file mode 100644
index 0000000..ac552ff
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
@@ -0,0 +1,140 @@
+/*
+ * 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.permission.access.collection
+
+class IndexedListSet<T> private constructor(
+    private val list: ArrayList<T>
+) : MutableSet<T> {
+    constructor() : this(ArrayList())
+
+    override val size: Int
+        get() = list.size
+
+    override fun contains(element: T): Boolean = list.contains(element)
+
+    override fun isEmpty(): Boolean = list.isEmpty()
+
+    override fun iterator(): MutableIterator<T> = list.iterator()
+
+    override fun containsAll(elements: Collection<T>): Boolean {
+        throw NotImplementedError()
+    }
+
+    fun elementAt(index: Int): T = list[index]
+
+    fun indexOf(element: T): Int = list.indexOf(element)
+
+    override fun add(element: T): Boolean =
+        if (list.contains(element)) {
+            false
+        } else {
+            list.add(element)
+            true
+        }
+
+    override fun remove(element: T): Boolean = list.remove(element)
+
+    override fun clear() {
+        list.clear()
+    }
+
+    override fun addAll(elements: Collection<T>): Boolean {
+        throw NotImplementedError()
+    }
+
+    override fun removeAll(elements: Collection<T>): Boolean {
+        throw NotImplementedError()
+    }
+
+    override fun retainAll(elements: Collection<T>): Boolean {
+        throw NotImplementedError()
+    }
+
+    fun removeAt(index: Int): T? = list.removeAt(index)
+
+    fun copy(): IndexedListSet<T> = IndexedListSet(ArrayList(list))
+}
+
+inline fun <T> IndexedListSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (!predicate(index, elementAt(index))) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> IndexedListSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (predicate(index, elementAt(index))) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> IndexedListSet<T>.forEachIndexed(action: (Int, T) -> Unit) {
+    for (index in indices) {
+        action(index, elementAt(index))
+    }
+}
+
+inline val <T> IndexedListSet<T>.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> IndexedListSet<T>.minus(element: T): IndexedListSet<T> =
+    copy().apply { this -= element }
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> IndexedListSet<T>.minusAssign(element: T) {
+    remove(element)
+}
+
+inline fun <T> IndexedListSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (predicate(index, elementAt(index))) {
+            return false
+        }
+    }
+    return true
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> IndexedListSet<T>.plus(element: T): IndexedListSet<T> =
+    copy().apply { this += element }
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> IndexedListSet<T>.plusAssign(element: T) {
+    add(element)
+}
+
+inline fun <T> IndexedListSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean) {
+    for (index in lastIndex downTo 0) {
+        if (predicate(index, elementAt(index))) {
+            removeAt(index)
+        }
+    }
+}
+
+inline fun <T> IndexedListSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean) {
+    for (index in lastIndex downTo 0) {
+        if (!predicate(index, elementAt(index))) {
+            removeAt(index)
+        }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
new file mode 100644
index 0000000..1251666
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.permission.access.collection
+
+import android.util.ArrayMap
+
+typealias IndexedMap<K, V> = ArrayMap<K, V>
+
+inline fun <K, V> IndexedMap<K, V>.allIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (!predicate(index, keyAt(index), valueAt(index))) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <K, V> IndexedMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (predicate(index, keyAt(index), valueAt(index))) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <K, V> IndexedMap<K, V>.copy(copyValue: (V) -> V): IndexedMap<K, V> =
+    IndexedMap(this).apply {
+        forEachValueIndexed { index, value ->
+            setValueAt(index, copyValue(value))
+        }
+    }
+
+inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? {
+    for (index in 0 until size) {
+        transform(index, keyAt(index), valueAt(index))?.let { return it }
+    }
+    return null
+}
+
+inline fun <K, V> IndexedMap<K, V>.forEachIndexed(action: (Int, K, V) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <K, V> IndexedMap<K, V>.forEachKeyIndexed(action: (Int, K) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index))
+    }
+}
+
+inline fun <K, V> IndexedMap<K, V>.forEachValueIndexed(action: (Int, V) -> Unit) {
+    for (index in 0 until size) {
+        action(index, valueAt(index))
+    }
+}
+
+inline fun <K, V> IndexedMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
+    get(key)?.let { return it }
+    return defaultValue().also { put(key, it) }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun <K, V> IndexedMap<K, V>?.getWithDefault(key: K, defaultValue: V): V {
+    this ?: return defaultValue
+    val index = indexOfKey(key)
+    return if (index >= 0) valueAt(index) else defaultValue
+}
+
+inline val <K, V> IndexedMap<K, V>.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <K, V> IndexedMap<K, V>.minusAssign(key: K) {
+    remove(key)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun <K, V> IndexedMap<K, V>.putWithDefault(key: K, value: V, defaultValue: V): V {
+    val index = indexOfKey(key)
+    if (index >= 0) {
+        val oldValue = valueAt(index)
+        if (value != oldValue) {
+            if (value == defaultValue) {
+                removeAt(index)
+            } else {
+                setValueAt(index, value)
+            }
+        }
+        return oldValue
+    } else {
+        if (value != defaultValue) {
+            put(key, value)
+        }
+        return defaultValue
+    }
+}
+
+inline fun <K, V> IndexedMap<K, V>.removeAllIndexed(predicate: (Int, K, V) -> Boolean) {
+    for (index in lastIndex downTo 0) {
+        if (predicate(index, keyAt(index), valueAt(index))) {
+            removeAt(index)
+        }
+    }
+}
+
+inline fun <K, V> IndexedMap<K, V>.retainAllIndexed(predicate: (Int, K, V) -> Boolean) {
+    for (index in lastIndex downTo 0) {
+        if (!predicate(index, keyAt(index), valueAt(index))) {
+            removeAt(index)
+        }
+    }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <K, V> IndexedMap<K, V>.set(key: K, value: V) {
+    put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
new file mode 100644
index 0000000..36d8ff0c
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.collection
+
+import android.util.ArraySet
+
+typealias IndexedSet<T> = ArraySet<T>
+
+inline fun <T> IndexedSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (!predicate(index, elementAt(index))) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> IndexedSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (predicate(index, elementAt(index))) {
+            return true
+        }
+    }
+    return false
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> IndexedSet<T>.copy(): IndexedSet<T> = IndexedSet(this)
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> IndexedSet<T>.elementAt(index: Int): T = valueAt(index)
+
+inline fun <T> IndexedSet<T>.forEachIndexed(action: (Int, T) -> Unit) {
+    for (index in indices) {
+        action(index, elementAt(index))
+    }
+}
+
+inline val <T> IndexedSet<T>.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> IndexedSet<T>.minus(element: T): IndexedSet<T> =
+    copy().apply { this -= element }
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> IndexedSet<T>.minusAssign(element: T) {
+    remove(element)
+}
+
+inline fun <T> IndexedSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (predicate(index, elementAt(index))) {
+            return false
+        }
+    }
+    return true
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> IndexedSet<T>.plus(element: T): IndexedSet<T> =
+    copy().apply { this += element }
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> IndexedSet<T>.plusAssign(element: T) {
+    add(element)
+}
+
+inline fun <T> IndexedSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean) {
+    for (index in lastIndex downTo 0) {
+        if (predicate(index, elementAt(index))) {
+            removeAt(index)
+        }
+    }
+}
+
+inline fun <T> IndexedSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean) {
+    for (index in lastIndex downTo 0) {
+        if (!predicate(index, elementAt(index))) {
+            removeAt(index)
+        }
+    }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> indexedSetOf(vararg elements: T): IndexedSet<T> = IndexedSet(elements.asList())
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntMap.kt b/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
new file mode 100644
index 0000000..9051c66
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
@@ -0,0 +1,140 @@
+/*
+ * 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.permission.access.collection
+
+import android.util.SparseArray
+
+typealias IntMap<T> = SparseArray<T>
+
+inline fun <T> IntMap<T>.allIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (!predicate(index, keyAt(index), valueAt(index))) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> IntMap<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (predicate(index, keyAt(index), valueAt(index))) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> IntMap<T>.copy(copyValue: (T) -> T): IntMap<T> =
+    this.clone().apply {
+        forEachValueIndexed { index, value ->
+            setValueAt(index, copyValue(value))
+        }
+    }
+
+inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? {
+    for (index in 0 until size) {
+        transform(index, keyAt(index), valueAt(index))?.let { return it }
+    }
+    return null
+}
+
+inline fun <T> IntMap<T>.forEachIndexed(action: (Int, Int, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <T> IntMap<T>.forEachKeyIndexed(action: (Int, Int) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index))
+    }
+}
+
+inline fun <T> IntMap<T>.forEachValueIndexed(action: (Int, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, valueAt(index))
+    }
+}
+
+inline fun <T> IntMap<T>.getOrPut(key: Int, defaultValue: () -> T): T {
+    get(key)?.let { return it }
+    return defaultValue().also { put(key, it) }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> IntMap<T>?.getWithDefault(key: Int, defaultValue: T): T {
+    this ?: return defaultValue
+    val index = indexOfKey(key)
+    return if (index >= 0) valueAt(index) else defaultValue
+}
+
+inline val <T> IntMap<T>.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> IntMap<T>.minusAssign(key: Int) {
+    remove(key)
+}
+
+inline fun <T> IntMap<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (predicate(index, keyAt(index), valueAt(index))) {
+            return false
+        }
+    }
+    return true
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> IntMap<T>.putWithDefault(key: Int, value: T, defaultValue: T): T {
+    val index = indexOfKey(key)
+    if (index >= 0) {
+        val oldValue = valueAt(index)
+        if (value != oldValue) {
+            if (value == defaultValue) {
+                removeAt(index)
+            } else {
+                setValueAt(index, value)
+            }
+        }
+        return oldValue
+    } else {
+        if (value != defaultValue) {
+            put(key, value)
+        }
+        return defaultValue
+    }
+}
+
+inline fun <T> IntMap<T>.removeAllIndexed(predicate: (Int, Int, T) -> Boolean) {
+    for (index in lastIndex downTo 0) {
+        if (predicate(index, keyAt(index), valueAt(index))) {
+            removeAt(index)
+        }
+    }
+}
+
+inline fun <T> IntMap<T>.retainAllIndexed(predicate: (Int, Int, T) -> Boolean) {
+    for (index in lastIndex downTo 0) {
+        if (!predicate(index, keyAt(index), valueAt(index))) {
+            removeAt(index)
+        }
+    }
+}
+
+inline val <T> IntMap<T>.size: Int
+    get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntSet.kt b/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
new file mode 100644
index 0000000..d6dfe9d
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.permission.access.collection
+
+import android.util.SparseBooleanArray
+
+class IntSet private constructor(
+    private val array: SparseBooleanArray
+) {
+    constructor() : this(SparseBooleanArray())
+
+    val size: Int
+        get() = array.size()
+
+    operator fun contains(element: Int): Boolean = array[element]
+
+    fun elementAt(index: Int): Int = array.keyAt(index)
+
+    fun indexOf(element: Int): Int = array.indexOfKey(element)
+
+    fun add(element: Int) {
+        array.put(element, true)
+    }
+
+    fun remove(element: Int) {
+        array.delete(element)
+    }
+
+    fun clear() {
+        array.clear()
+    }
+
+    fun removeAt(index: Int) {
+        array.removeAt(index)
+    }
+
+    fun copy(): IntSet = IntSet(array.clone())
+}
+
+inline fun IntSet.allIndexed(predicate: (Int, Int) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (!predicate(index, elementAt(index))) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun IntSet.anyIndexed(predicate: (Int, Int) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (predicate(index, elementAt(index))) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun IntSet.forEachIndexed(action: (Int, Int) -> Unit) {
+    for (index in 0 until size) {
+        action(index, elementAt(index))
+    }
+}
+
+inline val IntSet.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun IntSet.minus(element: Int): IntSet = copy().apply { this -= element }
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun IntSet.minusAssign(element: Int) {
+    remove(element)
+}
+
+inline fun IntSet.noneIndexed(predicate: (Int, Int) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (predicate(index, elementAt(index))) {
+            return false
+        }
+    }
+    return true
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun IntSet.plus(element: Int): IntSet = copy().apply { this += element }
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun IntSet.plusAssign(element: Int) {
+    add(element)
+}
+
+inline fun IntSet.removeAllIndexed(predicate: (Int, Int) -> Boolean) {
+    for (index in lastIndex downTo 0) {
+        if (predicate(index, elementAt(index))) {
+            removeAt(index)
+        }
+    }
+}
+
+inline fun IntSet.retainAllIndexed(predicate: (Int, Int) -> Boolean) {
+    for (index in lastIndex downTo 0) {
+        if (!predicate(index, elementAt(index))) {
+            removeAt(index)
+        }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/List.kt b/services/permission/java/com/android/server/permission/access/collection/List.kt
new file mode 100644
index 0000000..dc28642
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/List.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.permission.access.collection
+
+inline fun <T> List<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (!predicate(index, this[index])) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> List<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (predicate(index, this[index])) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> List<T>.forEachIndexed(action: (Int, T) -> Unit) {
+    for (index in indices) {
+        action(index, this[index])
+    }
+}
+
+inline fun <T> List<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    for (index in 0 until size) {
+        if (predicate(index, this[index])) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> MutableList<T>.removeAllIndexed(predicate: (Int, T) -> Boolean) {
+    for (index in lastIndex downTo 0) {
+        if (predicate(index, this[index])) {
+            removeAt(index)
+        }
+    }
+}
+
+inline fun <T> MutableList<T>.retainAllIndexed(predicate: (Int, T) -> Boolean) {
+    for (index in lastIndex downTo 0) {
+        if (!predicate(index, this[index])) {
+            removeAt(index)
+        }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/data/Package.kt b/services/permission/java/com/android/server/permission/access/data/Package.kt
new file mode 100644
index 0000000..d6f98ab
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/data/Package.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.permission.access.data
+
+import com.android.server.permission.access.external.AndroidPackage
+
+class Package(
+    private val androidPackage: AndroidPackage
+) {
+    val name: String
+        get() = androidPackage.packageName
+
+    val adoptPermissions: List<String>
+        get() = androidPackage.adoptPermissions
+
+    val appId: Int
+        get() = androidPackage.appId
+
+    val requestedPermissions: List<String>
+        get() = androidPackage.requestedPermissions
+
+    override fun equals(other: Any?): Boolean {
+        throw NotImplementedError()
+    }
+
+    override fun hashCode(): Int {
+        throw NotImplementedError()
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/data/Permission.kt b/services/permission/java/com/android/server/permission/access/data/Permission.kt
new file mode 100644
index 0000000..877f23b
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/data/Permission.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.permission.access.data
+
+import android.content.pm.PermissionInfo
+import com.android.server.permission.access.util.hasBits
+
+data class Permission(
+    val permissionInfo: PermissionInfo,
+    val isReconciled: Boolean,
+    val type: Int,
+    val appId: Int
+) {
+    inline val name: String
+        get() = permissionInfo.name
+
+    inline val packageName: String
+        get() = permissionInfo.packageName
+
+    inline val isDynamic: Boolean
+        get() = type == TYPE_DYNAMIC
+
+    inline val isNormal: Boolean
+        get() = permissionInfo.protection == PermissionInfo.PROTECTION_NORMAL
+
+    inline val isRuntime: Boolean
+        get() = permissionInfo.protection == PermissionInfo.PROTECTION_DANGEROUS
+
+    inline val isSoftRestricted: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.FLAG_SOFT_RESTRICTED)
+
+    inline val isHardRestricted: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.FLAG_HARD_RESTRICTED)
+
+    inline val isSignature: Boolean
+        get() = permissionInfo.protection == PermissionInfo.PROTECTION_SIGNATURE
+
+    inline val isInternal: Boolean
+        get() = permissionInfo.protection == PermissionInfo.PROTECTION_INTERNAL
+
+    inline val isDevelopment: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_DEVELOPMENT)
+
+    inline val isInstaller: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTALLER)
+
+    inline val isOem: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_OEM)
+
+    inline val isPre23: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRE23)
+
+    inline val isPreInstalled: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PREINSTALLED)
+
+    inline val isPrivileged: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRIVILEGED)
+
+    inline val isSetup: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_SETUP)
+
+    inline val isVerifier: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_VERIFIER)
+
+    inline val isVendorPrivileged: Boolean
+        get() = permissionInfo.protectionFlags
+            .hasBits(PROTECTION_FLAG_VENDOR_PRIVILEGED)
+
+    inline val isSystemTextClassifier: Boolean
+        get() = permissionInfo.protectionFlags
+            .hasBits(PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER)
+
+    inline val isConfigurator: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_CONFIGURATOR)
+
+    inline val isIncidentReportApprover: Boolean
+        get() = permissionInfo.protectionFlags
+            .hasBits(PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER)
+
+    inline val isAppPredictor: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR)
+
+    inline val isCompanion: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_COMPANION)
+
+    inline val isRetailDemo: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO)
+
+    inline val isRecents: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RECENTS)
+
+    inline val isRole: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_ROLE)
+
+    inline val isKnownSigner: Boolean
+        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER)
+
+    inline val protectionLevel: Int
+        @Suppress("DEPRECATION")
+        get() = permissionInfo.protectionLevel
+
+    inline val knownCerts: Set<String>
+        get() = permissionInfo.knownCerts
+
+    companion object {
+        // The permission is defined in an application manifest.
+        const val TYPE_MANIFEST = 0
+        // The permission is defined in a system config.
+        const val TYPE_CONFIG = 1
+        // The permission is defined dynamically.
+        const val TYPE_DYNAMIC = 2
+
+        // TODO: PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED is a testApi
+        const val PROTECTION_FLAG_VENDOR_PRIVILEGED = 0x8000
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/external/CompatibilityPermissionInfo.kt b/services/permission/java/com/android/server/permission/access/external/CompatibilityPermissionInfo.kt
new file mode 100644
index 0000000..aadd8ba
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/external/CompatibilityPermissionInfo.kt
@@ -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.server.permission.access.external
+
+class CompatibilityPermissionInfo {
+    companion object {
+        val COMPAT_PERMS = arrayOf(CompatibilityPermissionInfo())
+    }
+
+    val name: String
+        get() = throw NotImplementedError()
+
+    val sdkVersion: Int
+        get() = throw NotImplementedError()
+}
diff --git a/services/permission/java/com/android/server/permission/access/external/KnownPackages.kt b/services/permission/java/com/android/server/permission/access/external/KnownPackages.kt
new file mode 100644
index 0000000..1239450
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/external/KnownPackages.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.server.permission.access.external
+
+class KnownPackages {
+    companion object {
+        const val PACKAGE_SYSTEM = 0
+        const val PACKAGE_SETUP_WIZARD = 1
+        const val PACKAGE_INSTALLER = 2
+        const val PACKAGE_VERIFIER = 4
+        const val PACKAGE_SYSTEM_TEXT_CLASSIFIER = 6
+        const val PACKAGE_PERMISSION_CONTROLLER = 7
+        const val PACKAGE_CONFIGURATOR = 10
+        const val PACKAGE_INCIDENT_REPORT_APPROVER = 11
+        const val PACKAGE_APP_PREDICTOR = 12
+        const val PACKAGE_COMPANION = 15
+        const val PACKAGE_RETAIL_DEMO = 16
+        const val PACKAGE_RECENTS = 17
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/external/PackageInfoUtils.kt b/services/permission/java/com/android/server/permission/access/external/PackageInfoUtils.kt
new file mode 100644
index 0000000..24b28bd
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/external/PackageInfoUtils.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.external
+
+import android.content.pm.PermissionGroupInfo
+import android.content.pm.PermissionInfo
+
+object PackageInfoUtils {
+    fun generatePermissionInfo(parsedPermission: ParsedPermission, flags: Long): PermissionInfo {
+        throw NotImplementedError()
+    }
+
+    fun generatePermissionGroupInfo(
+        parsedPermissionGroup: ParsedPermissionGroup,
+        flags: Long
+    ): PermissionGroupInfo {
+        throw NotImplementedError()
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/external/PackageState.kt b/services/permission/java/com/android/server/permission/access/external/PackageState.kt
new file mode 100644
index 0000000..b81d794
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/external/PackageState.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.external
+
+import android.util.SparseArray
+
+interface PackageState {
+    val androidPackage: AndroidPackage?
+    val appId: Int
+    val isSystem: Boolean
+    val isUpdatedSystemApp: Boolean
+    val packageName: String
+    val userStates: SparseArray<PackageUserState>
+    val hasSharedUser: Boolean
+    val sharedUserAppId: Int
+    val signingDetails: SigningDetails
+}
+
+interface AndroidPackage {
+    val packageName: String
+    val apexModuleName: String?
+    val appId: Int
+    val isPrivileged: Boolean
+    val isOem: Boolean
+    val isVendor: Boolean
+    val isProduct: Boolean
+    val isSystemExt: Boolean
+    val targetSdkVersion: Int
+    val adoptPermissions: List<String>
+    val permissions: List<ParsedPermission>
+    val permissionGroups: List<ParsedPermissionGroup>
+    val requestedPermissions: List<String>
+    val implicitPermissions: List<String>
+}
+
+interface ParsedPermission {
+    val name: String
+    val isTree: Boolean
+    val packageName: String
+    val isSignature: Boolean
+    val protectionLevel: Int
+}
+
+interface ParsedPermissionGroup {
+    val name: String
+    val packageName: String
+}
+
+interface PackageUserState {
+    val isInstantApp: Boolean
+}
diff --git a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt b/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
new file mode 100644
index 0000000..528680e7
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.permission.access.external
+
+class RoSystemProperties {
+    companion object {
+        const val CONTROL_PRIVAPP_PERMISSIONS_DISABLE = false
+        const val CONTROL_PRIVAPP_PERMISSIONS_ENFORCE = false
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/external/SigningDetails.kt b/services/permission/java/com/android/server/permission/access/external/SigningDetails.kt
new file mode 100644
index 0000000..25917f9
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/external/SigningDetails.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.permission.access.external
+
+object SigningDetails {
+    fun hasCommonSignerWithCapability(otherDetails: SigningDetails, flags: Int): Boolean {
+        throw NotImplementedError()
+    }
+
+    fun hasAncestorOrSelf(oldDetails: SigningDetails): Boolean {
+        throw NotImplementedError()
+    }
+
+    fun checkCapability(oldDetails: SigningDetails, flags: Int): Boolean {
+        throw NotImplementedError()
+    }
+
+    fun hasAncestorOrSelfWithDigest(certDigests: Set<String>): Boolean {
+        throw NotImplementedError()
+    }
+
+    class CertCapabilities {
+        companion object {
+            /** grant SIGNATURE permissions to pkgs with this cert  */
+            var PERMISSION = 4
+        }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/external/UserHandle.kt b/services/permission/java/com/android/server/permission/access/external/UserHandle.kt
new file mode 100644
index 0000000..e1072a7
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/external/UserHandle.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.permission.access.external
+
+interface UserHandle {
+    companion object {
+        fun getAppId(uid: Int): Int {
+            throw NotImplementedError()
+        }
+    }
+}
+
+object UserHandleCompat {
+    fun getUserId(uid: Int): Int {
+        throw NotImplementedError()
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/ModernPermissionManagerServiceImpl.kt b/services/permission/java/com/android/server/permission/access/permission/ModernPermissionManagerServiceImpl.kt
new file mode 100644
index 0000000..cc51866
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/ModernPermissionManagerServiceImpl.kt
@@ -0,0 +1,456 @@
+/*
+ * 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.permission.access.permission
+
+import android.content.pm.PackageManager
+import android.content.pm.PackageManagerInternal
+import android.content.pm.PermissionGroupInfo
+import android.content.pm.PermissionInfo
+import android.content.pm.permission.SplitPermissionInfoParcelable
+import android.os.Binder
+import android.os.Build
+import android.os.Process
+import android.os.UserHandle
+import android.permission.IOnPermissionsChangeListener
+import com.android.server.LocalManagerRegistry
+import com.android.server.LocalServices
+import com.android.server.pm.PackageManagerLocal
+import com.android.server.pm.permission.PermissionManagerServiceInterface
+import com.android.server.permission.access.AccessCheckingService
+import com.android.server.permission.access.PermissionUri
+import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.data.Permission
+import com.android.server.permission.access.util.hasBits
+import com.android.server.pm.permission.LegacyPermission
+import com.android.server.pm.permission.LegacyPermissionSettings
+import com.android.server.pm.permission.LegacyPermissionState
+import com.android.server.pm.permission.PermissionManagerServiceInternal
+import com.android.server.pm.pkg.AndroidPackage
+import java.io.FileDescriptor
+import java.io.PrintWriter
+
+/**
+ * Modern implementation of [PermissionManagerServiceInterface].
+ */
+class ModernPermissionManagerServiceImpl(
+    private val service: AccessCheckingService
+) : PermissionManagerServiceInterface {
+    private val policy =
+        service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as UidPermissionPolicy
+
+    private val packageManagerInternal =
+        LocalServices.getService(PackageManagerInternal::class.java)
+
+    private val packageManagerLocal =
+        LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
+
+    override fun getAllPermissionGroups(flags: Int): List<PermissionGroupInfo> {
+        TODO("Not yet implemented")
+    }
+
+    override fun getPermissionGroupInfo(
+        permissionGroupName: String,
+        flags: Int
+    ): PermissionGroupInfo? {
+        val permissionGroup: PermissionGroupInfo
+        packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+            val callingUid = Binder.getCallingUid()
+            if (snapshot.isUidInstantApp(callingUid)) {
+                return null
+            }
+
+            permissionGroup = service.getState {
+                with(policy) { getPermissionGroup(permissionGroupName) }
+            } ?: return null
+
+            val isPermissionGroupVisible =
+                snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)
+            if (!isPermissionGroupVisible) {
+                return null
+            }
+        }
+
+        return permissionGroup.generatePermissionGroupInfo(flags)
+    }
+
+    /**
+     * Generate a new [PermissionGroupInfo] from [PermissionGroupInfo] and adjust it accordingly.
+     */
+    private fun PermissionGroupInfo.generatePermissionGroupInfo(flags: Int): PermissionGroupInfo =
+        @Suppress("DEPRECATION")
+        PermissionGroupInfo(this).apply {
+            if (!flags.hasBits(PackageManager.GET_META_DATA)) {
+                metaData = null
+            }
+        }
+
+    override fun getPermissionInfo(
+        permissionName: String,
+        flags: Int,
+        opPackageName: String
+    ): PermissionInfo? {
+        val permission: Permission
+        val targetSdkVersion: Int
+        packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+            val callingUid = Binder.getCallingUid()
+            if (snapshot.isUidInstantApp(callingUid)) {
+                return null
+            }
+
+            permission = service.getState {
+                with(policy) { getPermission(permissionName) }
+            } ?: return null
+
+            val isPermissionVisible =
+                snapshot.isPackageVisibleToUid(permission.packageName, callingUid)
+            if (!isPermissionVisible) {
+                return null
+            }
+
+            val callingAppId = UserHandle.getAppId(callingUid)
+            val opPackage = snapshot.packageStates[opPackageName]?.androidPackage
+            targetSdkVersion = when {
+                // System sees all flags.
+                callingAppId == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID ||
+                    callingAppId == Process.SHELL_UID -> Build.VERSION_CODES.CUR_DEVELOPMENT
+                opPackage != null -> opPackage.targetSdkVersion
+                else -> Build.VERSION_CODES.CUR_DEVELOPMENT
+            }
+        }
+
+        return permission.generatePermissionInfo(flags, targetSdkVersion)
+    }
+
+    /**
+     * Generate a new [PermissionInfo] from [Permission] and adjust it accordingly.
+     */
+    private fun Permission.generatePermissionInfo(
+        flags: Int,
+        targetSdkVersion: Int
+    ): PermissionInfo =
+        @Suppress("DEPRECATION")
+        PermissionInfo(permissionInfo).apply {
+            if (!flags.hasBits(PackageManager.GET_META_DATA)) {
+                metaData = null
+            }
+            if (targetSdkVersion < Build.VERSION_CODES.O) {
+                val protection = protection
+                // Signature permission's protection flags are always reported.
+                if (protection != PermissionInfo.PROTECTION_SIGNATURE) {
+                    protectionLevel = protection
+                }
+            }
+        }
+
+    override fun queryPermissionsByGroup(
+        permissionGroupName: String,
+        flags: Int
+    ): List<PermissionInfo> {
+        TODO("Not yet implemented")
+    }
+
+    override fun getAllPermissionsWithProtection(protection: Int): List<PermissionInfo> {
+        TODO("Not yet implemented")
+    }
+
+    override fun getAllPermissionsWithProtectionFlags(protectionFlags: Int): List<PermissionInfo> {
+        TODO("Not yet implemented")
+    }
+
+    override fun getPermissionGids(permissionName: String, userId: Int): IntArray {
+        TODO("Not yet implemented")
+    }
+
+    override fun addPermission(permissionInfo: PermissionInfo, async: Boolean): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun removePermission(permissionName: String) {
+        TODO("Not yet implemented")
+    }
+
+    override fun checkPermission(packageName: String, permissionName: String, userId: Int): Int {
+        TODO("Not yet implemented")
+    }
+
+    override fun checkUidPermission(uid: Int, permissionName: String): Int {
+        TODO("Not yet implemented")
+    }
+
+    override fun getGrantedPermissions(packageName: String, userId: Int): Set<String> {
+        TODO("Not yet implemented")
+    }
+
+    override fun grantRuntimePermission(packageName: String, permissionName: String, userId: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun revokeRuntimePermission(
+        packageName: String,
+        permissionName: String,
+        userId: Int,
+        reason: String?
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun revokePostNotificationPermissionWithoutKillForTest(
+        packageName: String,
+        userId: Int
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun addOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
+        TODO("Not yet implemented")
+    }
+
+    override fun removeOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
+        TODO("Not yet implemented")
+    }
+
+    override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int {
+        TODO("Not yet implemented")
+    }
+
+    override fun isPermissionRevokedByPolicy(
+        packageName: String,
+        permissionName: String,
+        userId: Int
+    ): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun isPermissionsReviewRequired(packageName: String, userId: Int): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun shouldShowRequestPermissionRationale(
+        packageName: String,
+        permissionName: String,
+        userId: Int
+    ): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun updatePermissionFlags(
+        packageName: String,
+        permissionName: String,
+        flagMask: Int,
+        flagValues: Int,
+        checkAdjustPolicyFlagPermission: Boolean,
+        userId: Int
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun updatePermissionFlagsForAllApps(flagMask: Int, flagValues: Int, userId: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun addAllowlistedRestrictedPermission(
+        packageName: String,
+        permissionName: String,
+        flags: Int,
+        userId: Int
+    ): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun getAllowlistedRestrictedPermissions(
+        packageName: String,
+        flags: Int,
+        userId: Int
+    ): MutableList<String> {
+        TODO("Not yet implemented")
+    }
+
+    override fun removeAllowlistedRestrictedPermission(
+        packageName: String,
+        permissionName: String,
+        flags: Int,
+        userId: Int
+    ): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun resetRuntimePermissions(androidPackage: AndroidPackage, userId: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun resetRuntimePermissionsForUser(userId: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun addOnRuntimePermissionStateChangedListener(
+        listener: PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun removeOnRuntimePermissionStateChangedListener(
+        listener: PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun getSplitPermissions(): List<SplitPermissionInfoParcelable> {
+        TODO("Not yet implemented")
+    }
+
+    override fun getAppOpPermissionPackages(permissionName: String): Array<String> {
+        TODO("Not yet implemented")
+    }
+
+    override fun getAllAppOpPermissionPackages(): Map<String, Set<String>> {
+        TODO("Not yet implemented")
+    }
+
+    override fun getGidsForUid(uid: Int): IntArray {
+        TODO("Not yet implemented")
+    }
+
+    override fun backupRuntimePermissions(userId: Int): ByteArray? {
+        TODO("Not yet implemented")
+    }
+
+    override fun restoreRuntimePermissions(backup: ByteArray, userId: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun restoreDelayedRuntimePermissions(packageName: String, userId: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>?) {
+        TODO("Not yet implemented")
+    }
+
+    override fun getPermissionTEMP(
+        permissionName: String
+    ): com.android.server.pm.permission.Permission? {
+        TODO("Not yet implemented")
+    }
+
+    override fun getLegacyPermissions(): List<LegacyPermission> {
+        TODO("Not yet implemented")
+    }
+
+    override fun readLegacyPermissionsTEMP(legacyPermissionSettings: LegacyPermissionSettings) {
+        TODO("Not yet implemented")
+    }
+
+    override fun writeLegacyPermissionsTEMP(legacyPermissionSettings: LegacyPermissionSettings) {
+        TODO("Not yet implemented")
+    }
+
+    override fun getLegacyPermissionState(appId: Int): LegacyPermissionState {
+        TODO("Not yet implemented")
+    }
+
+    override fun readLegacyPermissionStateTEMP() {
+        TODO("Not yet implemented")
+    }
+
+    override fun writeLegacyPermissionStateTEMP() {
+        TODO("Not yet implemented")
+    }
+
+    override fun onSystemReady() {
+        TODO("Not yet implemented")
+    }
+
+    override fun onUserCreated(userId: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun onUserRemoved(userId: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun onStorageVolumeMounted(volumeUuid: String, fingerprintChanged: Boolean) {
+        TODO("Not yet implemented")
+    }
+
+    override fun onPackageAdded(
+        androidPackage: AndroidPackage,
+        isInstantApp: Boolean,
+        oldPackage: AndroidPackage?
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun onPackageInstalled(
+        androidPackage: AndroidPackage,
+        previousAppId: Int,
+        params: PermissionManagerServiceInternal.PackageInstalledParams,
+        userId: Int
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun onPackageUninstalled(
+        packageName: String,
+        appId: Int,
+        androidPackage: AndroidPackage?,
+        sharedUserPkgs: MutableList<AndroidPackage>,
+        userId: Int
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun onPackageRemoved(androidPackage: AndroidPackage) {
+        TODO("Not yet implemented")
+    }
+
+    /**
+     * Check whether a UID belongs to an instant app.
+     */
+    private fun PackageManagerLocal.UnfilteredSnapshot.isUidInstantApp(uid: Int): Boolean {
+        if (Process.isIsolatedUid(uid)) {
+            // Unfortunately we don't have the API for getting the owner UID of an isolated UID yet,
+            // so for now we just keep calling the old API.
+            return packageManagerInternal.getInstantAppPackageName(uid) != null
+        }
+        val appId = UserHandle.getAppId(uid)
+        // Instant apps can't have shared UIDs, so we can just take the first package.
+        val firstPackageState = packageStates.values.firstOrNull { it.appId == appId }
+            ?: return false
+        val userId = UserHandle.getUserId(uid)
+        return firstPackageState.getUserStateOrDefault(userId).isInstantApp
+    }
+
+    /**
+     * Check whether a package is visible to a UID within the same user as the UID.
+     */
+    private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid(
+        packageName: String,
+        uid: Int
+    ): Boolean = isPackageVisibleToUid(packageName, UserHandle.getUserId(uid), uid)
+
+    /**
+     * Check whether a package in a particular user is visible to a UID.
+     */
+    private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid(
+        packageName: String,
+        userId: Int,
+        uid: Int
+    ): Boolean {
+        val user = UserHandle.of(userId)
+        return filtered(uid, user).use { it.getPackageState(packageName) != null }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
new file mode 100644
index 0000000..bb1a86c
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.permission.access.permission
+
+object PermissionFlags {
+    const val INSTALL_GRANTED = 1 shl 0
+    const val INSTALL_REVOKED = 1 shl 1
+    const val PROTECTION_GRANTED = 1 shl 2
+    const val ROLE_GRANTED = 1 shl 3
+    // For permissions that are granted in other ways,
+    // ex: via an API or implicit permissions that inherit from granted install permissions
+    const val OTHER_GRANTED = 1 shl 4
+    // For the permissions that are implicit for the package
+    const val IMPLICIT = 1 shl 5
+
+    const val MASK_GRANTED = INSTALL_GRANTED or PROTECTION_GRANTED or OTHER_GRANTED or ROLE_GRANTED
+    const val MASK_RUNTIME = OTHER_GRANTED or IMPLICIT
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPersistence.kt
new file mode 100644
index 0000000..3489061
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPersistence.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.permission.access.permission
+
+import android.content.pm.PermissionInfo
+import android.util.Log
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.SystemState
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.data.Permission
+import com.android.server.permission.access.util.attribute
+import com.android.server.permission.access.util.attributeInt
+import com.android.server.permission.access.util.attributeIntHex
+import com.android.server.permission.access.util.attributeIntHexWithDefault
+import com.android.server.permission.access.util.attributeInterned
+import com.android.server.permission.access.util.forEachTag
+import com.android.server.permission.access.util.getAttributeIntHexOrDefault
+import com.android.server.permission.access.util.getAttributeIntHexOrThrow
+import com.android.server.permission.access.util.getAttributeIntOrThrow
+import com.android.server.permission.access.util.getAttributeValue
+import com.android.server.permission.access.util.getAttributeValueOrThrow
+import com.android.server.permission.access.util.tag
+import com.android.server.permission.access.util.tagName
+
+class UidPermissionPersistence {
+    fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {
+        when (tagName) {
+            TAG_PERMISSION_TREES -> parsePermissions(systemState.permissionTrees)
+            TAG_PERMISSIONS -> parsePermissions(systemState.permissions)
+            else -> {}
+        }
+    }
+
+    private fun BinaryXmlPullParser.parsePermissions(permissions: IndexedMap<String, Permission>) {
+        forEachTag {
+            when (val tagName = tagName) {
+                TAG_PERMISSION -> parsePermission(permissions)
+                else -> Log.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing permissions")
+            }
+        }
+    }
+
+    private fun BinaryXmlPullParser.parsePermission(permissions: IndexedMap<String, Permission>) {
+        val name = getAttributeValueOrThrow(ATTR_NAME).intern()
+        @Suppress("DEPRECATION")
+        val permissionInfo = PermissionInfo().apply {
+            this.name = name
+            packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern()
+            protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL)
+        }
+        val type = getAttributeIntOrThrow(ATTR_TYPE)
+        when (type) {
+            Permission.TYPE_MANIFEST -> {}
+            Permission.TYPE_CONFIG -> {
+                Log.w(LOG_TAG, "Ignoring unexpected config permission $name")
+                return
+            }
+            Permission.TYPE_DYNAMIC -> {
+                permissionInfo.apply {
+                    icon = getAttributeIntHexOrDefault(ATTR_ICON, 0)
+                    nonLocalizedLabel = getAttributeValue(ATTR_LABEL)
+                }
+            }
+            else -> {
+                Log.w(LOG_TAG, "Ignoring permission $name with unknown type $type")
+                return
+            }
+        }
+        val permission = Permission(permissionInfo, false, type, 0)
+        permissions[name] = permission
+    }
+
+    fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) {
+        serializePermissions(TAG_PERMISSION_TREES, systemState.permissionTrees)
+        serializePermissions(TAG_PERMISSIONS, systemState.permissions)
+    }
+
+    private fun BinaryXmlSerializer.serializePermissions(
+        tagName: String,
+        permissions: IndexedMap<String, Permission>
+    ) {
+        tag(tagName) {
+            permissions.forEachValueIndexed { _, it -> serializePermission(it) }
+        }
+    }
+
+    private fun BinaryXmlSerializer.serializePermission(permission: Permission) {
+        val type = permission.type
+        when (type) {
+            Permission.TYPE_MANIFEST, Permission.TYPE_DYNAMIC -> {}
+            Permission.TYPE_CONFIG -> return
+            else -> {
+                Log.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type")
+                return
+            }
+        }
+        tag(TAG_PERMISSION) {
+            attributeInterned(ATTR_NAME, permission.name)
+            attributeInterned(ATTR_PACKAGE_NAME, permission.packageName)
+            attributeIntHex(ATTR_PROTECTION_LEVEL, permission.protectionLevel)
+            attributeInt(ATTR_TYPE, type)
+            if (type == Permission.TYPE_DYNAMIC) {
+                val permissionInfo = permission.permissionInfo
+                attributeIntHexWithDefault(ATTR_ICON, permissionInfo.icon, 0)
+                permissionInfo.nonLocalizedLabel?.toString()?.let { attribute(ATTR_LABEL, it) }
+            }
+        }
+    }
+
+    companion object {
+        private val LOG_TAG = UidPermissionPersistence::class.java.simpleName
+
+        private const val TAG_PERMISSION = "permission"
+        private const val TAG_PERMISSION_TREES = "permission-trees"
+        private const val TAG_PERMISSIONS = "permissions"
+
+        private const val ATTR_ICON = "icon"
+        private const val ATTR_LABEL = "label"
+        private const val ATTR_NAME = "name"
+        private const val ATTR_PACKAGE_NAME = "packageName"
+        private const val ATTR_PROTECTION_LEVEL = "protectionLevel"
+        private const val ATTR_TYPE = "type"
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
new file mode 100644
index 0000000..e081924
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -0,0 +1,875 @@
+/*
+ * 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.permission.access.permission
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.content.pm.PermissionGroupInfo
+import android.content.pm.PermissionInfo
+import android.os.Build
+import android.os.UserHandle
+import android.util.Log
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.AccessUri
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.PermissionUri
+import com.android.server.permission.access.SchemePolicy
+import com.android.server.permission.access.SystemState
+import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.UserState
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.data.Permission
+import com.android.server.permission.access.external.AndroidPackage
+import com.android.server.permission.access.external.CompatibilityPermissionInfo
+import com.android.server.permission.access.external.KnownPackages
+import com.android.server.permission.access.external.PackageInfoUtils
+import com.android.server.permission.access.external.PackageState
+import com.android.server.permission.access.external.RoSystemProperties
+import com.android.server.permission.access.external.SigningDetails
+import com.android.server.permission.access.util.hasAnyBit
+import com.android.server.permission.access.util.hasBits
+
+class UidPermissionPolicy : SchemePolicy() {
+    private val persistence = UidPermissionPersistence()
+
+    override val subjectScheme: String
+        get() = UidUri.SCHEME
+
+    override val objectScheme: String
+        get() = PermissionUri.SCHEME
+
+    override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
+        subject as UidUri
+        `object` as PermissionUri
+        return state.userStates[subject.userId]?.permissionFlags?.get(subject.appId)
+            ?.get(`object`.permissionName) ?: 0
+    }
+
+    override fun MutateStateScope.setDecision(
+        subject: AccessUri,
+        `object`: AccessUri,
+        decision: Int
+    ) {
+        subject as UidUri
+        `object` as PermissionUri
+        val uidFlags = newState.userStates.getOrPut(subject.userId) { UserState() }
+            .permissionFlags.getOrPut(subject.appId) { IndexedMap() }
+        uidFlags[`object`.permissionName] = decision
+    }
+
+    override fun MutateStateScope.onUserAdded(userId: Int) {
+        newState.systemState.packageStates.forEachValueIndexed { _, packageState ->
+            evaluateAllPermissionStatesForPackageAndUser(packageState, null, userId)
+            grantImplicitPermissions(packageState, userId)
+        }
+    }
+
+    override fun MutateStateScope.onAppIdAdded(appId: Int) {
+        newState.userStates.forEachIndexed { _, _, userState ->
+            userState.permissionFlags.getOrPut(appId) { IndexedMap() }
+        }
+    }
+
+    override fun MutateStateScope.onAppIdRemoved(appId: Int) {
+        newState.userStates.forEachIndexed { _, _, userState -> userState.permissionFlags -= appId }
+    }
+
+    override fun MutateStateScope.onPackageAdded(packageState: PackageState) {
+        val changedPermissionNames = IndexedSet<String>()
+        adoptPermissions(packageState, changedPermissionNames)
+        addPermissionGroups(packageState)
+        addPermissions(packageState, changedPermissionNames)
+        // TODO: revokeStoragePermissionsIfScopeExpandedInternal()
+        trimPermissions(packageState.packageName)
+        changedPermissionNames.forEachIndexed { _, permissionName ->
+            evaluatePermissionStateForAllPackages(permissionName, packageState)
+        }
+
+        evaluateAllPermissionStatesForPackage(packageState, packageState)
+        newState.systemState.userIds.forEachIndexed { _, userId ->
+            grantImplicitPermissions(packageState, userId)
+        }
+
+        // TODO: add trimPermissionStates() here for removing the permission states that are
+        // no longer requested. (equivalent to revokeUnusedSharedUserPermissionsLocked())
+    }
+
+    private fun MutateStateScope.adoptPermissions(
+        packageState: PackageState,
+        changedPermissionNames: IndexedSet<String>
+    ) {
+        val `package` = packageState.androidPackage!!
+        `package`.adoptPermissions.forEachIndexed { _, originalPackageName ->
+            val packageName = `package`.packageName
+            if (!canAdoptPermissions(packageName, originalPackageName)) {
+                return@forEachIndexed
+            }
+            newState.systemState.permissions.let { permissions ->
+                permissions.forEachIndexed permissions@ {
+                    permissionIndex, permissionName, oldPermission ->
+                    if (oldPermission.packageName != originalPackageName) {
+                        return@permissions
+                    }
+                    @Suppress("DEPRECATION")
+                    val newPermissionInfo = PermissionInfo().apply {
+                        name = oldPermission.permissionInfo.name
+                        this.packageName = packageName
+                        protectionLevel = oldPermission.permissionInfo.protectionLevel
+                    }
+                    val newPermission = Permission(newPermissionInfo, false, oldPermission.type, 0)
+                    changedPermissionNames += permissionName
+                    permissions.setValueAt(permissionIndex, newPermission)
+                }
+            }
+        }
+    }
+
+    private fun MutateStateScope.canAdoptPermissions(
+        packageName: String,
+        originalPackageName: String
+    ): Boolean {
+        val originalPackageState = newState.systemState.packageStates[originalPackageName]
+            ?: return false
+        if (!originalPackageState.isSystem) {
+            Log.w(
+                LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" +
+                    " original package not in system partition"
+            )
+            return false
+        }
+        if (originalPackageState.androidPackage != null) {
+            Log.w(
+                LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" +
+                    " original package still exists"
+            )
+            return false
+        }
+        return true
+    }
+
+    private fun MutateStateScope.addPermissionGroups(packageState: PackageState) {
+        // Different from the old implementation, which decides whether the app is an instant app by
+        // the install flags, now for consistent behavior we allow adding permission groups if the
+        // app is non-instant in at least one user.
+        val isInstantApp = packageState.userStates.allIndexed { _, _, it -> it.isInstantApp }
+        if (isInstantApp) {
+            Log.w(
+                LOG_TAG, "Ignoring permission groups declared in package" +
+                    " ${packageState.packageName}: instant apps cannot declare permission groups"
+            )
+            return
+        }
+        packageState.androidPackage!!.permissionGroups.forEachIndexed { _, parsedPermissionGroup ->
+            val newPermissionGroup = PackageInfoUtils.generatePermissionGroupInfo(
+                parsedPermissionGroup, PackageManager.GET_META_DATA.toLong()
+            )
+            // TODO: Clear permission state on group take-over?
+            val permissionGroupName = newPermissionGroup.name
+            val oldPermissionGroup = newState.systemState.permissionGroups[permissionGroupName]
+            if (oldPermissionGroup != null &&
+                newPermissionGroup.packageName != oldPermissionGroup.packageName) {
+                Log.w(
+                    LOG_TAG, "Ignoring permission group $permissionGroupName declared in package" +
+                        " ${newPermissionGroup.packageName}: already declared in another package" +
+                        " ${oldPermissionGroup.packageName}"
+                )
+                return@forEachIndexed
+            }
+            newState.systemState.permissionGroups[permissionGroupName] = newPermissionGroup
+        }
+    }
+
+    private fun MutateStateScope.addPermissions(
+        packageState: PackageState,
+        changedPermissionNames: IndexedSet<String>
+    ) {
+        packageState.androidPackage!!.permissions.forEachIndexed { _, parsedPermission ->
+            // TODO:
+            // parsedPermission.flags = parsedPermission.flags andInv PermissionInfo.FLAG_INSTALLED
+            // TODO: This seems actually unused.
+            // if (packageState.androidPackage.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+            //    parsedPermission.setParsedPermissionGroup(
+            //        newState.systemState.permissionGroup[parsedPermission.group]
+            //    )
+            // }
+            val newPermissionInfo = PackageInfoUtils.generatePermissionInfo(
+                parsedPermission, PackageManager.GET_META_DATA.toLong()
+            )
+            // TODO: newPermissionInfo.flags |= PermissionInfo.FLAG_INSTALLED
+            val permissionName = newPermissionInfo.name
+            val oldPermission = if (parsedPermission.isTree) {
+                newState.systemState.permissionTrees[permissionName]
+            } else {
+                newState.systemState.permissions[permissionName]
+            }
+            // Different from the old implementation, which may add an (incomplete) signature
+            // permission inside another package's permission tree, we now consistently ignore such
+            // permissions.
+            val permissionTree = getPermissionTree(permissionName)
+            val newPackageName = newPermissionInfo.packageName
+            if (permissionTree != null && newPackageName != permissionTree.packageName) {
+                Log.w(
+                    LOG_TAG, "Ignoring permission $permissionName declared in package" +
+                        " $newPackageName: base permission tree ${permissionTree.name} is" +
+                        " declared in another package ${permissionTree.packageName}"
+                )
+                return@forEachIndexed
+            }
+            val newPermission = if (oldPermission != null &&
+                newPackageName != oldPermission.packageName) {
+                val oldPackageName = oldPermission.packageName
+                // Only allow system apps to redefine non-system permissions.
+                if (!packageState.isSystem) {
+                    Log.w(
+                        LOG_TAG, "Ignoring permission $permissionName declared in package" +
+                            " $newPackageName: already declared in another package" +
+                            " $oldPackageName"
+                    )
+                    return@forEachIndexed
+                }
+                if (oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled) {
+                    // It's a config permission and has no owner, take ownership now.
+                    Permission(newPermissionInfo, true, Permission.TYPE_CONFIG, packageState.appId)
+                } else if (newState.systemState.packageStates[oldPackageName]?.isSystem != true) {
+                    Log.w(
+                        LOG_TAG, "Overriding permission $permissionName with new declaration in" +
+                            " system package $newPackageName: originally declared in another" +
+                            " package $oldPackageName"
+                    )
+                    // Remove permission state on owner change.
+                    newState.userStates.forEachValueIndexed { _, userState ->
+                        userState.permissionFlags.forEachValueIndexed { _, permissionFlags ->
+                            permissionFlags -= newPermissionInfo.name
+                        }
+                    }
+                    // TODO: Notify re-evaluation of this permission.
+                    Permission(
+                        newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId
+                    )
+                } else {
+                    Log.w(
+                        LOG_TAG, "Ignoring permission $permissionName declared in system package" +
+                            " $newPackageName: already declared in another system package" +
+                            " $oldPackageName")
+                    return@forEachIndexed
+                }
+            } else {
+                // TODO: STOPSHIP: Clear permission state on type or group change.
+                // Different from the old implementation, which doesn't update the permission
+                // definition upon app update, but does update it on the next boot, we now
+                // consistently update the permission definition upon app update.
+                Permission(newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId)
+            }
+
+            changedPermissionNames += permissionName
+            if (parsedPermission.isTree) {
+                newState.systemState.permissionTrees[permissionName] = newPermission
+            } else {
+                newState.systemState.permissions[permissionName] = newPermission
+            }
+        }
+    }
+
+    private fun MutateStateScope.trimPermissions(packageName: String) {
+        val packageState = newState.systemState.packageStates[packageName]
+        val androidPackage = packageState?.androidPackage
+        if (packageState != null && androidPackage == null) {
+            return
+        }
+
+        newState.systemState.permissionTrees.removeAllIndexed {
+            _, permissionTreeName, permissionTree ->
+            permissionTree.packageName == packageName && (
+                packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
+                    it.isTree && it.name == permissionTreeName
+                }
+            )
+        }
+
+        newState.systemState.permissions.removeAllIndexed { i, permissionName, permission ->
+            val updatedPermission = updatePermissionIfDynamic(permission)
+            newState.systemState.permissions.setValueAt(i, updatedPermission)
+            if (updatedPermission.packageName == packageName && (
+                packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
+                    !it.isTree && it.name == permissionName
+                }
+            )) {
+                if (!isPermissionDeclaredByDisabledSystemPackage(permission)) {
+                    newState.userStates.forEachIndexed { _, userId, userState ->
+                        userState.permissionFlags.forEachKeyIndexed { _, appId ->
+                            setPermissionFlags(
+                                appId, permissionName, getPermissionFlags(
+                                    appId, permissionName, userId
+                                ) and PermissionFlags.INSTALL_REVOKED, userId
+                            )
+                        }
+                    }
+                }
+                true
+            } else {
+                false
+            }
+        }
+    }
+
+    private fun MutateStateScope.isPermissionDeclaredByDisabledSystemPackage(
+        permission: Permission
+    ): Boolean {
+        val disabledSystemPackage = newState.systemState
+            .disabledSystemPackageStates[permission.packageName]?.androidPackage ?: return false
+        return disabledSystemPackage.permissions.anyIndexed { _, it ->
+            it.name == permission.name && it.protectionLevel == permission.protectionLevel
+        }
+    }
+
+    private fun MutateStateScope.updatePermissionIfDynamic(permission: Permission): Permission {
+        if (!permission.isDynamic) {
+            return permission
+        }
+        val permissionTree = getPermissionTree(permission.name) ?: return permission
+        @Suppress("DEPRECATION")
+        return permission.copy(
+            permissionInfo = PermissionInfo(permission.permissionInfo).apply {
+                packageName = permissionTree.packageName
+            }, appId = permissionTree.appId, isReconciled = true
+        )
+    }
+
+    private fun MutateStateScope.getPermissionTree(permissionName: String): Permission? =
+        newState.systemState.permissionTrees.firstNotNullOfOrNullIndexed {
+            _, permissionTreeName, permissionTree ->
+            if (permissionName.startsWith(permissionTreeName) &&
+                permissionName.length > permissionTreeName.length &&
+                permissionName[permissionTreeName.length] == '.') {
+                permissionTree
+            } else {
+                null
+            }
+        }
+
+    private fun MutateStateScope.evaluatePermissionStateForAllPackages(
+        permissionName: String,
+        installedPackageState: PackageState?
+    ) {
+        newState.systemState.userIds.forEachIndexed { _, userId ->
+            oldState.userStates[userId]?.permissionFlags?.forEachIndexed {
+                _, appId, permissionFlags ->
+                if (permissionName in permissionFlags) {
+                    evaluatePermissionState(appId, permissionName, installedPackageState, userId)
+                }
+            }
+        }
+    }
+
+    private fun MutateStateScope.evaluateAllPermissionStatesForPackage(
+        packageState: PackageState,
+        installedPackageState: PackageState?
+    ) {
+        newState.systemState.userIds.forEachIndexed { _, userId ->
+            evaluateAllPermissionStatesForPackageAndUser(
+                packageState, installedPackageState, userId
+            )
+        }
+    }
+
+    private fun MutateStateScope.evaluateAllPermissionStatesForPackageAndUser(
+        packageState: PackageState,
+        installedPackageState: PackageState?,
+        userId: Int
+    ) {
+        packageState.androidPackage?.requestedPermissions?.forEachIndexed { _, permissionName ->
+            evaluatePermissionState(
+                packageState.appId, permissionName, installedPackageState, userId
+            )
+        }
+    }
+
+    private fun MutateStateScope.evaluatePermissionState(
+        appId: Int,
+        permissionName: String,
+        installedPackageState: PackageState?,
+        userId: Int
+    ) {
+        val packageNames = newState.systemState.appIds[appId]
+        val hasMissingPackage = packageNames.anyIndexed { _, packageName ->
+            newState.systemState.packageStates[packageName]!!.androidPackage == null
+        }
+        if (packageNames.size == 1 && hasMissingPackage) {
+            // For non-shared-user packages with missing androidPackage, skip evaluation.
+            return
+        }
+        val permission = newState.systemState.permissions[permissionName] ?: return
+        val oldFlags = getPermissionFlags(appId, permissionName, userId)
+        if (permission.isNormal) {
+            val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED)
+            if (!wasGranted) {
+                val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED)
+                val isRequestedByInstalledPackage = installedPackageState != null &&
+                    permissionName in installedPackageState.androidPackage!!.requestedPermissions
+                val isRequestedBySystemPackage = anyPackageInAppId(appId) {
+                    it.isSystem && permissionName in it.androidPackage!!.requestedPermissions
+                }
+                val isCompatibilityPermission = anyPackageInAppId(appId) {
+                    isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName)
+                }
+                // If this is an existing, non-system package,
+                // then we can't add any new permissions to it.
+                // Except if this is a permission that was added to the platform
+                val newFlags = if (!wasRevoked || isRequestedByInstalledPackage ||
+                    isRequestedBySystemPackage || isCompatibilityPermission) {
+                    PermissionFlags.INSTALL_GRANTED
+                } else {
+                    PermissionFlags.INSTALL_REVOKED
+                }
+                setPermissionFlags(appId, permissionName, newFlags, userId)
+            }
+        } else if (permission.isSignature || permission.isInternal) {
+            val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED)
+            var newFlags = if (hasMissingPackage && wasProtectionGranted) {
+                // Keep the non-runtime permission grants for shared UID with missing androidPackage
+                PermissionFlags.PROTECTION_GRANTED
+            } else {
+                val mayGrantByPrivileged = !permission.isPrivileged || (
+                    anyPackageInAppId(appId) {
+                        checkPrivilegedPermissionAllowlist(it, permission)
+                    }
+                )
+                val shouldGrantBySignature = permission.isSignature && (
+                    anyPackageInAppId(appId) {
+                        shouldGrantPermissionBySignature(it, permission)
+                    }
+                )
+                val shouldGrantByProtectionFlags = anyPackageInAppId(appId) {
+                    shouldGrantPermissionByProtectionFlags(it, permission)
+                }
+                if (mayGrantByPrivileged &&
+                    (shouldGrantBySignature || shouldGrantByProtectionFlags)) {
+                    PermissionFlags.PROTECTION_GRANTED
+                } else {
+                    0
+                }
+            }
+            // Different from the old implementation, which seemingly allows granting an
+            // unallowlisted privileged permission via development or role but revokes it upon next
+            // reconciliation, we now properly allows that because the privileged protection flag
+            // should only affect the other static flags, but not dynamic flags like development or
+            // role. This may be useful in the case of an updated system app.
+            if (permission.isDevelopment) {
+                newFlags = newFlags or (oldFlags and PermissionFlags.OTHER_GRANTED)
+            }
+            if (permission.isRole) {
+                newFlags = newFlags or (oldFlags and PermissionFlags.ROLE_GRANTED)
+            }
+            setPermissionFlags(appId, permissionName, newFlags, userId)
+        } else if (permission.isRuntime) {
+            // TODO: add runtime permissions
+        } else {
+            Log.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" +
+                "for permission ${permission.name} while evaluating permission state" +
+                "for appId $appId and userId $userId")
+        }
+
+        // TODO: revokePermissionsNoLongerImplicitLocked() for runtime permissions
+    }
+
+    private fun MutateStateScope.grantImplicitPermissions(packageState: PackageState, userId: Int) {
+        val appId = packageState.appId
+        val androidPackage = packageState.androidPackage ?: return
+        androidPackage.implicitPermissions.forEachIndexed implicitPermissions@ {
+            _, implicitPermissionName ->
+            val implicitPermission = newState.systemState.permissions[implicitPermissionName]
+            checkNotNull(implicitPermission) {
+                "Unknown implicit permission $implicitPermissionName in split permissions"
+            }
+            if (!implicitPermission.isRuntime) {
+                return@implicitPermissions
+            }
+            // Explicitly check against the old state to determine if this permission is new.
+            val isNewPermission = getPermissionFlags(
+                appId, implicitPermissionName, userId, oldState
+            ) == 0
+            if (!isNewPermission) {
+                return@implicitPermissions
+            }
+            val sourcePermissions = newState.systemState
+                .implicitToSourcePermissions[implicitPermissionName] ?: return@implicitPermissions
+            var newFlags = 0
+            sourcePermissions.forEachIndexed sourcePermissions@ { _, sourcePermissionName ->
+                val sourcePermission = newState.systemState.permissions[sourcePermissionName]
+                checkNotNull(sourcePermission) {
+                    "Unknown source permission $sourcePermissionName in split permissions"
+                }
+                val sourceFlags = getPermissionFlags(appId, sourcePermissionName, userId)
+                val isSourceGranted = sourceFlags.hasAnyBit(PermissionFlags.MASK_GRANTED)
+                val isNewGranted = newFlags.hasAnyBit(PermissionFlags.MASK_GRANTED)
+                val isGrantingNewFromRevoke = isSourceGranted && !isNewGranted
+                if (isSourceGranted == isNewGranted || isGrantingNewFromRevoke) {
+                    if (isGrantingNewFromRevoke) {
+                        newFlags = 0
+                    }
+                    newFlags = newFlags or (sourceFlags and PermissionFlags.MASK_RUNTIME)
+                    if (!sourcePermission.isRuntime && isSourceGranted) {
+                        newFlags = newFlags or PermissionFlags.OTHER_GRANTED
+                    }
+                }
+            }
+            newFlags = newFlags or PermissionFlags.IMPLICIT
+            setPermissionFlags(appId, implicitPermissionName, newFlags, userId)
+        }
+    }
+
+    private fun MutateStateScope.getPermissionFlags(
+        appId: Int,
+        permissionName: String,
+        userId: Int,
+        state: AccessState = newState
+    ): Int = state.userStates[userId].permissionFlags[appId].getWithDefault(permissionName, 0)
+
+    private fun MutateStateScope.setPermissionFlags(
+        appId: Int,
+        permissionName: String,
+        flags: Int,
+        userId: Int
+    ) {
+        newState.userStates[userId].permissionFlags[appId]!!
+            .putWithDefault(permissionName, flags, 0)
+    }
+
+    private fun isCompatibilityPermissionForPackage(
+        androidPackage: AndroidPackage,
+        permissionName: String
+    ): Boolean {
+        for (compatibilityPermission in CompatibilityPermissionInfo.COMPAT_PERMS) {
+            if (compatibilityPermission.name == permissionName &&
+                androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion) {
+                Log.i(
+                    LOG_TAG, "Auto-granting $permissionName to old package" +
+                    " ${androidPackage.packageName}"
+                )
+                return true
+            }
+        }
+        return false
+    }
+
+    private fun MutateStateScope.shouldGrantPermissionBySignature(
+        packageState: PackageState,
+        permission: Permission
+    ): Boolean {
+        // check if the package is allow to use this signature permission.  A package is allowed to
+        // use a signature permission if:
+        //     - it has the same set of signing certificates as the source package
+        //     - or its signing certificate was rotated from the source package's certificate
+        //     - or its signing certificate is a previous signing certificate of the defining
+        //       package, and the defining package still trusts the old certificate for permissions
+        //     - or it shares a common signing certificate in its lineage with the defining package,
+        //       and the defining package still trusts the old certificate for permissions
+        //     - or it shares the above relationships with the system package
+        val sourceSigningDetails = newState.systemState
+            .packageStates[permission.packageName]?.signingDetails
+        val platformSigningDetails = newState.systemState
+            .packageStates[PLATFORM_PACKAGE_NAME]!!.signingDetails
+        return sourceSigningDetails?.hasCommonSignerWithCapability(packageState.signingDetails,
+            SigningDetails.CertCapabilities.PERMISSION) == true ||
+            packageState.signingDetails.hasAncestorOrSelf(platformSigningDetails) ||
+            platformSigningDetails.checkCapability(packageState.signingDetails,
+                    SigningDetails.CertCapabilities.PERMISSION)
+    }
+
+    private fun MutateStateScope.checkPrivilegedPermissionAllowlist(
+        packageState: PackageState,
+        permission: Permission
+    ): Boolean {
+        if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
+            return true
+        }
+        if (packageState.packageName == PLATFORM_PACKAGE_NAME) {
+            return true
+        }
+        val androidPackage = packageState.androidPackage!!
+        if (!androidPackage.isPrivileged) {
+            return true
+        }
+        if (permission.packageName !in
+            newState.systemState.privilegedPermissionAllowlistSourcePackageNames) {
+            return true
+        }
+        if (isInSystemConfigPrivAppPermissions(androidPackage, permission.name)) {
+            return true
+        }
+        if (isInSystemConfigPrivAppDenyPermissions(androidPackage, permission.name)) {
+            return false
+        }
+        // Updated system apps do not need to be allowlisted
+        if (packageState.isUpdatedSystemApp) {
+            return true
+        }
+        // TODO: Enforce the allowlist on boot
+        return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE
+    }
+
+    private fun MutateStateScope.isInSystemConfigPrivAppPermissions(
+        androidPackage: AndroidPackage,
+        permissionName: String
+    ): Boolean {
+        val apexModuleName = androidPackage.apexModuleName
+        val systemState = newState.systemState
+        val packageName = androidPackage.packageName
+        val permissionNames = when {
+            androidPackage.isVendor -> systemState.vendorPrivAppPermissions[packageName]
+            androidPackage.isProduct -> systemState.productPrivAppPermissions[packageName]
+            androidPackage.isSystemExt -> systemState.systemExtPrivAppPermissions[packageName]
+            apexModuleName != null -> {
+                val apexPrivAppPermissions = systemState.apexPrivAppPermissions[apexModuleName]
+                    ?.get(packageName)
+                val privAppPermissions = systemState.privAppPermissions[packageName]
+                when {
+                    apexPrivAppPermissions == null -> privAppPermissions
+                    privAppPermissions == null -> apexPrivAppPermissions
+                    else -> apexPrivAppPermissions + privAppPermissions
+                }
+            }
+            else -> systemState.privAppPermissions[packageName]
+        }
+        return permissionNames?.contains(permissionName) == true
+    }
+
+    private fun MutateStateScope.isInSystemConfigPrivAppDenyPermissions(
+        androidPackage: AndroidPackage,
+        permissionName: String
+    ): Boolean {
+        // Different from the previous implementation, which may incorrectly use the APEX package
+        // name, we now use the APEX module name to be consistent with the allowlist.
+        val apexModuleName = androidPackage.apexModuleName
+        val systemState = newState.systemState
+        val packageName = androidPackage.packageName
+        val permissionNames = when {
+            androidPackage.isVendor -> systemState.vendorPrivAppDenyPermissions[packageName]
+            androidPackage.isProduct -> systemState.productPrivAppDenyPermissions[packageName]
+            androidPackage.isSystemExt -> systemState.systemExtPrivAppDenyPermissions[packageName]
+            // Different from the previous implementation, which ignores the regular priv app
+            // denylist in this case, we now respect it as well to be consistent with the allowlist.
+            apexModuleName != null -> {
+                val apexPrivAppDenyPermissions = systemState
+                    .apexPrivAppDenyPermissions[apexModuleName]?.get(packageName)
+                val privAppDenyPermissions = systemState.privAppDenyPermissions[packageName]
+                when {
+                    apexPrivAppDenyPermissions == null -> privAppDenyPermissions
+                    privAppDenyPermissions == null -> apexPrivAppDenyPermissions
+                    else -> apexPrivAppDenyPermissions + privAppDenyPermissions
+                }
+            }
+            else -> systemState.privAppDenyPermissions[packageName]
+        }
+        return permissionNames?.contains(permissionName) == true
+    }
+
+    private fun MutateStateScope.anyPackageInAppId(
+        appId: Int,
+        state: AccessState = newState,
+        predicate: (PackageState) -> Boolean
+    ): Boolean {
+        val packageNames = state.systemState.appIds[appId]
+        return packageNames.anyIndexed { _, packageName ->
+            val packageState = state.systemState.packageStates[packageName]!!
+            packageState.androidPackage != null && predicate(packageState)
+        }
+    }
+
+    private fun MutateStateScope.shouldGrantPermissionByProtectionFlags(
+        packageState: PackageState,
+        permission: Permission
+    ): Boolean {
+        val androidPackage = packageState.androidPackage!!
+        val knownPackages = newState.systemState.knownPackages
+        val packageName = packageState.packageName
+        if ((permission.isPrivileged || permission.isOem) && packageState.isSystem) {
+            val shouldGrant = if (packageState.isUpdatedSystemApp) {
+                // For updated system applications, a privileged/oem permission
+                // is granted only if it had been defined by the original application.
+                val disabledSystemPackage = newState.systemState
+                    .disabledSystemPackageStates[packageState.packageName]?.androidPackage
+                disabledSystemPackage != null &&
+                    permission.name in disabledSystemPackage.requestedPermissions &&
+                    shouldGrantPrivilegedOrOemPermission(disabledSystemPackage, permission)
+            } else {
+                shouldGrantPrivilegedOrOemPermission(androidPackage, permission)
+            }
+            if (shouldGrant) {
+                return true
+            }
+        }
+        if (permission.isPre23 && androidPackage.targetSdkVersion < Build.VERSION_CODES.M) {
+            // If this was a previously normal/dangerous permission that got moved
+            // to a system permission as part of the runtime permission redesign, then
+            // we still want to blindly grant it to old apps.
+            return true
+        }
+        if (permission.isInstaller && (
+            packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER] ||
+                packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]
+        )) {
+            // If this permission is to be granted to the system installer and
+            // this app is an installer or permission controller, then it gets the permission.
+            return true
+        }
+        if (permission.isVerifier &&
+            packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]) {
+            // If this permission is to be granted to the system verifier and
+            // this app is a verifier, then it gets the permission.
+            return true
+        }
+        if (permission.isPreInstalled && packageState.isSystem) {
+            // Any pre-installed system app is allowed to get this permission.
+            return true
+        }
+        if (permission.isKnownSigner &&
+            packageState.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) {
+            // If the permission is to be granted to a known signer then check if any of this
+            // app's signing certificates are in the trusted certificate digest Set.
+            return true
+        }
+        if (permission.isSetup &&
+            packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]) {
+            // If this permission is to be granted to the system setup wizard and
+            // this app is a setup wizard, then it gets the permission.
+            return true
+        }
+        if (permission.isSystemTextClassifier &&
+            packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]) {
+            // Special permissions for the system default text classifier.
+            return true
+        }
+        if (permission.isConfigurator &&
+            packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]) {
+            // Special permissions for the device configurator.
+            return true
+        }
+        if (permission.isIncidentReportApprover &&
+            packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]) {
+            // If this permission is to be granted to the incident report approver and
+            // this app is the incident report approver, then it gets the permission.
+            return true
+        }
+        if (permission.isAppPredictor &&
+            packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]) {
+            // Special permissions for the system app predictor.
+            return true
+        }
+        if (permission.isCompanion &&
+            packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]) {
+            // Special permissions for the system companion device manager.
+            return true
+        }
+        if (permission.isRetailDemo &&
+            packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO] &&
+            isDeviceOrProfileOwnerUid(packageState.appId)) {
+            // Special permission granted only to the OEM specified retail demo app.
+            // Note that the original code was passing app ID as UID, so this behavior is kept
+            // unchanged.
+            return true
+        }
+        if (permission.isRecents &&
+            packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]) {
+            // Special permission for the recents app.
+            return true
+        }
+        return false
+    }
+
+    private fun MutateStateScope.shouldGrantPrivilegedOrOemPermission(
+        androidPackage: AndroidPackage,
+        permission: Permission
+    ): Boolean {
+        val permissionName = permission.name
+        val packageName = androidPackage.packageName
+        when {
+            permission.isPrivileged -> {
+                if (androidPackage.isPrivileged) {
+                    // In any case, don't grant a privileged permission to privileged vendor apps,
+                    // if the permission's protectionLevel does not have the extra vendorPrivileged
+                    // flag.
+                    if (androidPackage.isVendor && !permission.isVendorPrivileged) {
+                        Log.w(
+                            LOG_TAG, "Permission $permissionName cannot be granted to privileged" +
+                            " vendor app $packageName because it isn't a vendorPrivileged" +
+                            " permission"
+                        )
+                        return false
+                    }
+                    return true
+                }
+            }
+            permission.isOem -> {
+                if (androidPackage.isOem) {
+                    val isOemAllowlisted = newState.systemState
+                        .oemPermissions[packageName]?.get(permissionName)
+                    checkNotNull(isOemAllowlisted) {
+                        "OEM permission $permissionName requested by package" +
+                            " $packageName must be explicitly declared granted or not"
+                    }
+                    return isOemAllowlisted
+                }
+            }
+        }
+        return false
+    }
+
+    private fun MutateStateScope.isDeviceOrProfileOwnerUid(uid: Int): Boolean {
+        val userId = UserHandle.getUserId(uid)
+        val ownerPackageName = newState.systemState.deviceAndProfileOwners[userId] ?: return false
+        val ownerPackageState = newState.systemState.packageStates[ownerPackageName] ?: return false
+        val ownerUid = UserHandle.getUid(userId, ownerPackageState.appId)
+        return uid == ownerUid
+    }
+
+    override fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
+        // TODO
+    }
+
+    override fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {
+        with(persistence) { this@parseSystemState.parseSystemState(systemState) }
+    }
+
+    override fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) {
+        with(persistence) { this@serializeSystemState.serializeSystemState(systemState) }
+    }
+
+    fun GetStateScope.getPermissionGroup(permissionGroupName: String): PermissionGroupInfo? =
+        state.systemState.permissionGroups[permissionGroupName]
+
+    fun GetStateScope.getPermission(permissionName: String): Permission? =
+        state.systemState.permissions[permissionName]
+
+    companion object {
+        private val LOG_TAG = UidPermissionPolicy::class.java.simpleName
+
+        private const val PLATFORM_PACKAGE_NAME = "android"
+
+        // A set of permissions that we don't want to revoke when they are no longer implicit.
+        private val RETAIN_IMPLICIT_GRANT_PERMISSIONS = indexedSetOf(
+            Manifest.permission.ACCESS_MEDIA_LOCATION,
+            Manifest.permission.ACTIVITY_RECOGNITION,
+            Manifest.permission.READ_MEDIA_AUDIO,
+            Manifest.permission.READ_MEDIA_IMAGES,
+            Manifest.permission.READ_MEDIA_VIDEO,
+        )
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
new file mode 100644
index 0000000..984dfb5
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.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.server.permission.access.util
+
+import android.util.AtomicFile
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.IOException
+
+/**
+ * Read from an [AtomicFile] and close everything safely when done.
+ */
+@Throws(IOException::class)
+inline fun AtomicFile.read(block: (FileInputStream) -> Unit) {
+    openRead().use(block)
+}
+
+/**
+ * Write to an [AtomicFile] and close everything safely when done.
+ */
+@Throws(IOException::class)
+// Renamed to writeInlined() to avoid conflict with the hidden AtomicFile.write() that isn't inline.
+inline fun AtomicFile.writeInlined(block: (FileOutputStream) -> Unit) {
+    startWrite().use {
+        try {
+            block(it)
+            finishWrite(it)
+        } catch (t: Throwable) {
+            failWrite(it)
+            throw t
+        }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt
new file mode 100644
index 0000000..1d27aef
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt
@@ -0,0 +1,300 @@
+/*
+ * 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.permission.access.util
+
+import com.android.modules.utils.BinaryXmlPullParser
+import java.io.IOException
+import java.io.InputStream
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+
+/**
+ * Parse content from [InputStream] with [BinaryXmlPullParser].
+ */
+@Throws(IOException::class, XmlPullParserException::class)
+inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) {
+    BinaryXmlPullParser().apply {
+        setInput(this@parseBinaryXml, null)
+        block()
+    }
+}
+
+/**
+ * Iterate through child tags of the current tag.
+ * <p>
+ * Attributes for the current tag needs to be accessed before this method is called because this
+ * method will advance the parser past the start tag of the current tag. The code inspecting each
+ * child tag may access the attributes of the child tag, and/or call [forEachTag] recursively to
+ * inspect grandchild tags, which will naturally leave the parser at either the start tag or the end
+ * tag of the child tag it inspected.
+ *
+ * @see BinaryXmlPullParser.next
+ * @see BinaryXmlPullParser.getEventType
+ * @see BinaryXmlPullParser.getDepth
+ */
+@Throws(IOException::class, XmlPullParserException::class)
+inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) {
+    when (val eventType = eventType) {
+        // Document start or start tag of the parent tag.
+        XmlPullParser.START_DOCUMENT, XmlPullParser.START_TAG -> nextTagOrEnd()
+        else -> throw XmlPullParserException("Unexpected event type $eventType")
+    }
+    while (true) {
+        when (val eventType = eventType) {
+            // Start tag of a child tag.
+            XmlPullParser.START_TAG -> {
+                val childDepth = depth
+                block()
+                // block() should leave the parser at either the start tag (no grandchild tags
+                // expected) or the end tag (grandchild tags parsed with forEachTag()) of this child
+                // tag.
+                val postBlockDepth = depth
+                if (postBlockDepth != childDepth) {
+                    throw XmlPullParserException(
+                        "Unexpected post-block depth $postBlockDepth, expected $childDepth"
+                    )
+                }
+                // Skip the parser to the end tag of this child tag.
+                while (true) {
+                    when (val childEventType = this.eventType) {
+                        // Start tag of either this child tag or a grandchild tag.
+                        XmlPullParser.START_TAG -> nextTagOrEnd()
+                        XmlPullParser.END_TAG -> {
+                            if (depth > childDepth) {
+                                // End tag of a grandchild tag.
+                                nextTagOrEnd()
+                            } else {
+                                // End tag of this child tag.
+                                break
+                            }
+                        }
+                        else ->
+                            throw XmlPullParserException("Unexpected event type $childEventType")
+                    }
+                }
+                // Skip the end tag of this child tag.
+                nextTagOrEnd()
+            }
+            // End tag of the parent tag, or document end.
+            XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT -> break
+            else -> throw XmlPullParserException("Unexpected event type $eventType")
+        }
+    }
+}
+
+/**
+ * Advance the parser until the current event is one of [XmlPullParser.START_TAG],
+ * [XmlPullParser.START_TAG] and [XmlPullParser.START_TAG]
+ *
+ * @see BinaryXmlPullParser.next
+ */
+@Throws(IOException::class, XmlPullParserException::class)
+@Suppress("NOTHING_TO_INLINE")
+inline fun BinaryXmlPullParser.nextTagOrEnd(): Int {
+    while (true) {
+        when (val eventType = next()) {
+            XmlPullParser.START_TAG, XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT ->
+                return eventType
+            else -> continue
+        }
+    }
+}
+
+/**
+ * @see BinaryXmlPullParser.getName
+ */
+inline val BinaryXmlPullParser.tagName: String
+    get() = name
+
+/**
+ * Check whether an attribute exists for the current tag.
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun BinaryXmlPullParser.hasAttribute(name: String): Boolean = getAttributeIndex(name) != -1
+
+/**
+ * @see BinaryXmlPullParser.getAttributeIndex
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun BinaryXmlPullParser.getAttributeIndex(name: String): Int = getAttributeIndex(null, name)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeIndexOrThrow
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(XmlPullParserException::class)
+inline fun BinaryXmlPullParser.getAttributeIndexOrThrow(name: String): Int =
+    getAttributeIndexOrThrow(null, name)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeValue
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(XmlPullParserException::class)
+inline fun BinaryXmlPullParser.getAttributeValue(name: String): String? =
+    getAttributeValue(null, name)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeValue
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(XmlPullParserException::class)
+inline fun BinaryXmlPullParser.getAttributeValueOrThrow(name: String): String =
+    getAttributeValue(getAttributeIndexOrThrow(name))
+
+/**
+ * @see BinaryXmlPullParser.getAttributeBytesHex
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun BinaryXmlPullParser.getAttributeBytesHex(name: String): ByteArray? =
+    getAttributeBytesHex(null, name, null)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeBytesHex
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(XmlPullParserException::class)
+inline fun BinaryXmlPullParser.getAttributeBytesHexOrThrow(name: String): ByteArray =
+    getAttributeBytesHex(null, name)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeBytesBase64
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun BinaryXmlPullParser.getAttributeBytesBase64(name: String): ByteArray? =
+    getAttributeBytesBase64(null, name, null)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeBytesBase64
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(XmlPullParserException::class)
+inline fun BinaryXmlPullParser.getAttributeBytesBase64OrThrow(name: String): ByteArray =
+    getAttributeBytesBase64(null, name)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeInt
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun BinaryXmlPullParser.getAttributeIntOrDefault(name: String, defaultValue: Int): Int =
+    getAttributeInt(null, name, defaultValue)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeInt
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(XmlPullParserException::class)
+inline fun BinaryXmlPullParser.getAttributeIntOrThrow(name: String): Int =
+    getAttributeInt(null, name)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeIntHex
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun BinaryXmlPullParser.getAttributeIntHexOrDefault(name: String, defaultValue: Int): Int =
+    getAttributeIntHex(null, name, defaultValue)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeIntHex
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(XmlPullParserException::class)
+inline fun BinaryXmlPullParser.getAttributeIntHexOrThrow(name: String): Int =
+    getAttributeIntHex(null, name)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeLong
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun BinaryXmlPullParser.getAttributeLongOrDefault(name: String, defaultValue: Long): Long =
+    getAttributeLong(null, name, defaultValue)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeLong
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(XmlPullParserException::class)
+inline fun BinaryXmlPullParser.getAttributeLongOrThrow(name: String): Long =
+    getAttributeLong(null, name)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeLongHex
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun BinaryXmlPullParser.getAttributeLongHexOrDefault(
+    name: String,
+    defaultValue: Long
+): Long = getAttributeLongHex(null, name, defaultValue)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeLongHex
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(XmlPullParserException::class)
+inline fun BinaryXmlPullParser.getAttributeLongHexOrThrow(name: String): Long =
+    getAttributeLongHex(null, name)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeFloat
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun BinaryXmlPullParser.getAttributeFloatOrDefault(
+    name: String,
+    defaultValue: Float
+): Float = getAttributeFloat(null, name, defaultValue)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeFloat
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(XmlPullParserException::class)
+inline fun BinaryXmlPullParser.getAttributeFloatOrThrow(name: String): Float =
+    getAttributeFloat(null, name)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeDouble
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun BinaryXmlPullParser.getAttributeDoubleOrDefault(
+    name: String,
+    defaultValue: Double
+): Double = getAttributeDouble(null, name, defaultValue)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeDouble
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(XmlPullParserException::class)
+inline fun BinaryXmlPullParser.getAttributeDoubleOrThrow(name: String): Double =
+    getAttributeDouble(null, name)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeBoolean
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun BinaryXmlPullParser.getAttributeBooleanOrDefault(
+    name: String,
+    defaultValue: Boolean
+): Boolean = getAttributeBoolean(null, name, defaultValue)
+
+/**
+ * @see BinaryXmlPullParser.getAttributeBoolean
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(XmlPullParserException::class)
+inline fun BinaryXmlPullParser.getAttributeBooleanOrThrow(name: String): Boolean =
+    getAttributeBoolean(null, name)
diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt
new file mode 100644
index 0000000..c8cd586
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt
@@ -0,0 +1,262 @@
+/*
+ * 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.permission.access.util
+
+import com.android.modules.utils.BinaryXmlSerializer
+import java.io.IOException
+import java.io.OutputStream
+
+/**
+ * Serialize content into [OutputStream] with [BinaryXmlSerializer].
+ */
+@Throws(IOException::class)
+inline fun OutputStream.serializeBinaryXml(block: BinaryXmlSerializer.() -> Unit) {
+    BinaryXmlSerializer().apply {
+        setOutput(this@serializeBinaryXml, null)
+        document(block)
+    }
+}
+
+/**
+ * Write a document with [BinaryXmlSerializer].
+ *
+ * @see BinaryXmlSerializer.startDocument
+ * @see BinaryXmlSerializer.endDocument
+ */
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.document(block: BinaryXmlSerializer.() -> Unit) {
+    startDocument(null, true)
+    block()
+    endDocument()
+}
+
+/**
+ * Write a tag with [BinaryXmlSerializer].
+ *
+ * @see BinaryXmlSerializer.startTag
+ * @see BinaryXmlSerializer.endTag
+ */
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.tag(name: String, block: BinaryXmlSerializer.() -> Unit) {
+    startTag(null, name)
+    block()
+    endTag(null, name)
+}
+
+/**
+ * @see BinaryXmlSerializer.attribute
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attribute(name: String, value: String) {
+    attribute(null, name, value)
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeInterned
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeInterned(name: String, value: String) {
+    attributeInterned(null, name, value)
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeBytesHex
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeBytesHex(name: String, value: ByteArray) {
+    attributeBytesHex(null, name, value)
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeBytesBase64
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeBytesBase64(name: String, value: ByteArray) {
+    attributeBytesBase64(null, name, value)
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeInt
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeInt(name: String, value: Int) {
+    attributeInt(null, name, value)
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeInt
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeIntWithDefault(
+    name: String,
+    value: Int,
+    defaultValue: Int
+) {
+    if (value != defaultValue) {
+        attributeInt(null, name, value)
+    }
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeIntHex
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeIntHex(name: String, value: Int) {
+    attributeIntHex(null, name, value)
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeIntHex
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeIntHexWithDefault(
+    name: String,
+    value: Int,
+    defaultValue: Int
+) {
+    if (value != defaultValue) {
+        attributeIntHex(null, name, value)
+    }
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeLong
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeLong(name: String, value: Long) {
+    attributeLong(null, name, value)
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeLong
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeLongWithDefault(
+    name: String,
+    value: Long,
+    defaultValue: Long
+) {
+    if (value != defaultValue) {
+        attributeLong(null, name, value)
+    }
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeLongHex
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeLongHex(name: String, value: Long) {
+    attributeLongHex(null, name, value)
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeLongHex
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeLongHexWithDefault(
+    name: String,
+    value: Long,
+    defaultValue: Long
+) {
+    if (value != defaultValue) {
+        attributeLongHex(null, name, value)
+    }
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeFloat
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeFloat(name: String, value: Float) {
+    attributeFloat(null, name, value)
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeFloat
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeFloatWithDefault(
+    name: String,
+    value: Float,
+    defaultValue: Float
+) {
+    if (value != defaultValue) {
+        attributeFloat(null, name, value)
+    }
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeDouble
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeDouble(name: String, value: Double) {
+    attributeDouble(null, name, value)
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeDouble
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeDoubleWithDefault(
+    name: String,
+    value: Double,
+    defaultValue: Double
+) {
+    if (value != defaultValue) {
+        attributeDouble(null, name, value)
+    }
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeBoolean
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeBoolean(name: String, value: Boolean) {
+    attributeBoolean(null, name, value)
+}
+
+/**
+ * @see BinaryXmlSerializer.attributeBoolean
+ */
+@Suppress("NOTHING_TO_INLINE")
+@Throws(IOException::class)
+inline fun BinaryXmlSerializer.attributeBooleanWithDefault(
+    name: String,
+    value: Boolean,
+    defaultValue: Boolean
+) {
+    if (value != defaultValue) {
+        attributeBoolean(null, name, value)
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/util/IntExtensions.kt b/services/permission/java/com/android/server/permission/access/util/IntExtensions.kt
new file mode 100644
index 0000000..e71d7a1
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/util/IntExtensions.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.permission.access.util
+
+fun Int.hasAnyBit(bits: Int): Boolean = this and bits != 0
+
+fun Int.hasBits(bits: Int): Boolean = this and bits == bits
+
+infix fun Int.andInv(other: Int): Int = this and other.inv()
diff --git a/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt
new file mode 100644
index 0000000..e6b4e3e
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/util/PermissionApex.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.server.permission.access.util
+
+import android.content.ApexEnvironment
+import android.os.UserHandle
+import java.io.File
+
+object PermissionApex {
+    private const val MODULE_NAME = "com.android.permission"
+
+    /**
+     * @see ApexEnvironment.getDeviceProtectedDataDir
+     */
+    val systemDataDirectory: File
+        get() = apexEnvironment.deviceProtectedDataDir
+
+    /**
+     * @see ApexEnvironment.getDeviceProtectedDataDirForUser
+     */
+    fun getUserDataDirectory(userId: Int): File =
+        apexEnvironment.getDeviceProtectedDataDirForUser(UserHandle.of(userId))
+
+    private val apexEnvironment: ApexEnvironment
+        get() = ApexEnvironment.getApexEnvironment(MODULE_NAME)
+}
diff --git a/services/proguard.flags b/services/proguard.flags
index 27fe505..6cdf11c 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -88,6 +88,7 @@
 -keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssPowerStats { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.hal.GnssNative { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.pm.PackageManagerShellCommandDataLoader { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$RuntimeSensorStateChangeCallback { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$ProximityActiveListener { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorService { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession { *; }
diff --git a/services/proguard_permission.flags b/services/proguard_permission.flags
new file mode 100644
index 0000000..15edc61
--- /dev/null
+++ b/services/proguard_permission.flags
@@ -0,0 +1,9 @@
+# Only shrink services.permission classes.
+# Note that while more aggressive services shrinking is enabled by default (see proguard.flags), for
+# cases where that's not yet possible, we still need to shrink the permission package to prune out
+# unused Kotlin stdlib dependencies.
+-keep class !com.android.server.permission.** { *; }
+
+# CoverageService guards optional jacoco class references with a runtime guard, so we can safely
+# suppress build-time warnings.
+-dontwarn org.jacoco.agent.rt.*
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 962a07a..6af7269 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -74,9 +74,12 @@
 import android.app.Application;
 import android.app.IBackupAgent;
 import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
 import android.app.backup.BackupManager;
+import android.app.backup.BackupManagerMonitor;
+import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.BackupTransport;
 import android.app.backup.IBackupCallback;
 import android.app.backup.IBackupManager;
@@ -89,6 +92,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.DeadObjectException;
 import android.os.Handler;
@@ -100,6 +104,7 @@
 import android.util.Pair;
 
 import com.android.internal.backup.IBackupTransport;
+import com.android.internal.infra.AndroidFuture;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.backup.BackupRestoreTask;
@@ -131,6 +136,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
 import org.mockito.InOrder;
 import org.mockito.Mock;
@@ -178,7 +184,7 @@
     private static final String BACKUP_AGENT_SHARED_PREFS_SYNCHRONIZER_CLASS =
             "android.app.backup.BackupAgent$SharedPrefsSynchronizer";
     private static final int USER_ID = 10;
-    private static final int OPERATION_TYPE = BackupManager.OperationType.BACKUP;
+    private static final int BACKUP_DESTINATION = BackupAnnotations.BackupDestination.CLOUD;
 
     @Mock private TransportManager mTransportManager;
     @Mock private DataChangedJournal mOldJournal;
@@ -259,7 +265,8 @@
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
         mBackupEligibilityRules = new BackupEligibilityRules(mPackageManager,
-                LocalServices.getService(PackageManagerInternal.class), USER_ID, OPERATION_TYPE);
+                LocalServices.getService(PackageManagerInternal.class), USER_ID,
+                BACKUP_DESTINATION);
     }
 
     @After
@@ -1448,6 +1455,36 @@
     }
 
     @Test
+    public void testRunTask_whenFinishBackupSucceeds_sendsAgentLogsToMonitor() throws Exception {
+        TransportMock transportMock = setUpInitializedTransport(mTransport);
+        AgentMock agentMock = setUpAgentWithData(PACKAGE_1);
+        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
+        // Mock the agent logging and returning its logs.
+        List<BackupRestoreEventLogger.DataTypeResult> results = new ArrayList<>();
+        results.add(new BackupRestoreEventLogger.DataTypeResult("testDataTypeResult"));
+        doAnswer(
+                        invocation -> {
+                            AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> in =
+                                    invocation.getArgument(0);
+                            in.complete(results);
+                            return null;
+                        })
+                .when(agentMock.agentBinder)
+                .getLoggerResults(any());
+
+        runTask(task);
+
+        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mMonitor).onEvent(bundleCaptor.capture());
+        Bundle eventBundle = bundleCaptor.getValue();
+        List<BackupRestoreEventLogger.DataTypeResult> sentLoggingResults =
+                eventBundle.getParcelableArrayList(
+                        BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS,
+                        BackupRestoreEventLogger.DataTypeResult.class);
+        assertThat(sentLoggingResults.get(0).getDataType()).isEqualTo("testDataTypeResult");
+    }
+
+    @Test
     public void testRunTask_whenFinishBackupSucceeds_notifiesCorrectly() throws Exception {
         TransportMock transportMock = setUpInitializedTransport(mTransport);
         setUpAgentWithData(PACKAGE_1);
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..212ec14
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
@@ -0,0 +1,61 @@
+<?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" />
+    <queries>
+        <intent>
+            <action android:name="android.view.InputMethod" />
+        </intent>
+    </queries>
+
+    <!-- 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" />
+
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
+    <uses-permission android:name="android.permission.BIND_INPUT_METHOD" />
+
+    <application android:testOnly="true"
+                 android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+        <service android:name="com.android.server.inputmethod.InputMethodBindingControllerTest$EmptyInputMethodService"
+                 android:label="Empty IME"
+                 android:permission="android.permission.BIND_INPUT_METHOD"
+                 android:process=":service"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.view.InputMethod"/>
+            </intent-filter>
+            <meta-data android:name="android.view.im"
+                       android:resource="@xml/method"/>
+        </service>
+    </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/res/xml/method.xml b/services/tests/InputMethodSystemServerTests/res/xml/method.xml
new file mode 100644
index 0000000..89b06bb
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/res/xml/method.xml
@@ -0,0 +1,18 @@
+<?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.
+  -->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android" />
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
new file mode 100644
index 0000000..42d373b
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.inputmethodservice.InputMethodService;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.inputmethod.InputBindResult;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+public class InputMethodBindingControllerTest extends InputMethodManagerServiceTestBase {
+
+    private static final String PACKAGE_NAME = "com.android.frameworks.inputmethodtests";
+    private static final String TEST_SERVICE_NAME =
+            "com.android.server.inputmethod.InputMethodBindingControllerTest"
+                    + "$EmptyInputMethodService";
+    private static final String TEST_IME_ID = PACKAGE_NAME + "/" + TEST_SERVICE_NAME;
+    private static final long TIMEOUT_IN_SECONDS = 3;
+
+    private InputMethodBindingController mBindingController;
+    private Instrumentation mInstrumentation;
+    private final int mImeConnectionBindFlags =
+            InputMethodBindingController.IME_CONNECTION_BIND_FLAGS
+                    & ~Context.BIND_SCHEDULE_LIKE_TOP_APP;
+    private CountDownLatch mCountDownLatch;
+
+    public static class EmptyInputMethodService extends InputMethodService {}
+
+    @Before
+    public void setUp() throws RemoteException {
+        super.setUp();
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mCountDownLatch = new CountDownLatch(1);
+        // Remove flag Context.BIND_SCHEDULE_LIKE_TOP_APP because in tests we are not calling
+        // from system.
+        mBindingController =
+                new InputMethodBindingController(
+                        mInputMethodManagerService, mImeConnectionBindFlags, mCountDownLatch);
+    }
+
+    @Test
+    public void testBindCurrentMethod_noIme() {
+        synchronized (ImfLock.class) {
+            mBindingController.setSelectedMethodId(null);
+            InputBindResult result = mBindingController.bindCurrentMethod();
+            assertThat(result).isEqualTo(InputBindResult.NO_IME);
+        }
+    }
+
+    @Test
+    public void testBindCurrentMethod_unknownId() {
+        synchronized (ImfLock.class) {
+            mBindingController.setSelectedMethodId("unknown ime id");
+        }
+        assertThrows(IllegalArgumentException.class, () -> {
+            synchronized (ImfLock.class) {
+                mBindingController.bindCurrentMethod();
+            }
+        });
+    }
+
+    @Test
+    public void testBindCurrentMethod_notConnected() {
+        synchronized (ImfLock.class) {
+            mBindingController.setSelectedMethodId(TEST_IME_ID);
+            doReturn(false)
+                    .when(mContext)
+                    .bindServiceAsUser(
+                            any(Intent.class),
+                            any(ServiceConnection.class),
+                            anyInt(),
+                            any(UserHandle.class));
+
+            InputBindResult result = mBindingController.bindCurrentMethod();
+            assertThat(result).isEqualTo(InputBindResult.IME_NOT_CONNECTED);
+        }
+    }
+
+    @Test
+    public void testBindAndUnbindMethod() throws Exception {
+        // Bind with main connection
+        testBindCurrentMethodWithMainConnection();
+
+        // Bind with visible connection
+        testBindCurrentMethodWithVisibleConnection();
+
+        // Unbind both main and visible connections
+        testUnbindCurrentMethod();
+    }
+
+    private void testBindCurrentMethodWithMainConnection() throws Exception {
+        synchronized (ImfLock.class) {
+            mBindingController.setSelectedMethodId(TEST_IME_ID);
+        }
+        InputMethodInfo info = mInputMethodManagerService.mMethodMap.get(TEST_IME_ID);
+        assertThat(info).isNotNull();
+        assertThat(info.getId()).isEqualTo(TEST_IME_ID);
+        assertThat(info.getServiceName()).isEqualTo(TEST_SERVICE_NAME);
+
+        // Bind input method with main connection. It is called on another thread because we should
+        // wait for onServiceConnected() to finish.
+        InputBindResult result = callOnMainSync(() -> {
+            synchronized (ImfLock.class) {
+                return mBindingController.bindCurrentMethod();
+            }
+        });
+
+        verify(mContext, times(1))
+                .bindServiceAsUser(
+                        any(Intent.class),
+                        any(ServiceConnection.class),
+                        eq(mImeConnectionBindFlags),
+                        any(UserHandle.class));
+        assertThat(result.result).isEqualTo(InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING);
+        assertThat(result.id).isEqualTo(info.getId());
+        synchronized (ImfLock.class) {
+            assertThat(mBindingController.hasConnection()).isTrue();
+            assertThat(mBindingController.getCurId()).isEqualTo(info.getId());
+            assertThat(mBindingController.getCurToken()).isNotNull();
+        }
+        // Wait for onServiceConnected()
+        mCountDownLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+
+        // Verify onServiceConnected() is called and bound successfully.
+        synchronized (ImfLock.class) {
+            assertThat(mBindingController.getCurMethod()).isNotNull();
+            assertThat(mBindingController.getCurMethodUid()).isNotEqualTo(Process.INVALID_UID);
+        }
+    }
+
+    private void testBindCurrentMethodWithVisibleConnection() {
+        mInstrumentation.runOnMainSync(() -> {
+            synchronized (ImfLock.class) {
+                mBindingController.setCurrentMethodVisible();
+            }
+        });
+        // Bind input method with visible connection
+        verify(mContext, times(1))
+                .bindServiceAsUser(
+                        any(Intent.class),
+                        any(ServiceConnection.class),
+                        eq(InputMethodBindingController.IME_VISIBLE_BIND_FLAGS),
+                        any(UserHandle.class));
+        synchronized (ImfLock.class) {
+            assertThat(mBindingController.isVisibleBound()).isTrue();
+        }
+    }
+
+    private void testUnbindCurrentMethod() {
+        mInstrumentation.runOnMainSync(() -> {
+            synchronized (ImfLock.class) {
+                mBindingController.unbindCurrentMethod();
+            }
+        });
+
+        synchronized (ImfLock.class) {
+            // Unbind both main connection and visible connection
+            assertThat(mBindingController.hasConnection()).isFalse();
+            assertThat(mBindingController.isVisibleBound()).isFalse();
+            verify(mContext, times(2)).unbindService(any(ServiceConnection.class));
+            assertThat(mBindingController.getCurToken()).isNull();
+            assertThat(mBindingController.getCurId()).isNull();
+            assertThat(mBindingController.getCurMethod()).isNull();
+            assertThat(mBindingController.getCurMethodUid()).isEqualTo(Process.INVALID_UID);
+        }
+    }
+
+    private static <V> V callOnMainSync(Callable<V> callable) {
+        AtomicReference<V> result = new AtomicReference<>();
+        InstrumentationRegistry.getInstrumentation()
+                .runOnMainSync(
+                        () -> {
+                            try {
+                                result.set(callable.call());
+                            } catch (Exception e) {
+                                throw new RuntimeException("Exception was thrown", e);
+                            }
+                        });
+        return result.get();
+    }
+}
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..640bde3
--- /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(), 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(), 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/PackageManagerServiceTests/TEST_MAPPING b/services/tests/PackageManagerServiceTests/TEST_MAPPING
index af0008c..fe27a37 100644
--- a/services/tests/PackageManagerServiceTests/TEST_MAPPING
+++ b/services/tests/PackageManagerServiceTests/TEST_MAPPING
@@ -2,11 +2,45 @@
   "presubmit": [
     {
       "name": "AppEnumerationInternalTests"
+    },
+    {
+      "name": "PackageManagerServiceServerTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.pm."
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
     }
   ],
   "postsubmit": [
     {
       "name": "PackageManagerServiceHostTests"
+    },
+    {
+      "name": "PackageManagerServiceServerTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.pm."
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.Postsubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
     }
   ],
   "imports": [
diff --git a/services/tests/servicestests/apks/Android.bp b/services/tests/PackageManagerServiceTests/apks/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/Android.bp
diff --git a/services/tests/servicestests/apks/OWNERS b/services/tests/PackageManagerServiceTests/apks/OWNERS
similarity index 100%
rename from services/tests/servicestests/apks/OWNERS
rename to services/tests/PackageManagerServiceTests/apks/OWNERS
diff --git a/services/tests/servicestests/apks/install-split-base/Android.bp b/services/tests/PackageManagerServiceTests/apks/install-split-base/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/install-split-base/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/install-split-base/Android.bp
diff --git a/services/tests/servicestests/apks/install-split-base/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install-split-base/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/install-split-base/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/install-split-base/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/install-split-base/src/com/google/android/dexapis/splitapp/BaseActivity.java b/services/tests/PackageManagerServiceTests/apks/install-split-base/src/com/google/android/dexapis/splitapp/BaseActivity.java
similarity index 100%
rename from services/tests/servicestests/apks/install-split-base/src/com/google/android/dexapis/splitapp/BaseActivity.java
rename to services/tests/PackageManagerServiceTests/apks/install-split-base/src/com/google/android/dexapis/splitapp/BaseActivity.java
diff --git a/services/tests/servicestests/apks/install-split-feature-a/Android.bp b/services/tests/PackageManagerServiceTests/apks/install-split-feature-a/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/install-split-feature-a/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/install-split-feature-a/Android.bp
diff --git a/services/tests/servicestests/apks/install-split-feature-a/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install-split-feature-a/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/install-split-feature-a/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/install-split-feature-a/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/install-split-feature-a/src/com/google/android/dexapis/splitapp/feature_a/FeatureAActivity.java b/services/tests/PackageManagerServiceTests/apks/install-split-feature-a/src/com/google/android/dexapis/splitapp/feature_a/FeatureAActivity.java
similarity index 100%
rename from services/tests/servicestests/apks/install-split-feature-a/src/com/google/android/dexapis/splitapp/feature_a/FeatureAActivity.java
rename to services/tests/PackageManagerServiceTests/apks/install-split-feature-a/src/com/google/android/dexapis/splitapp/feature_a/FeatureAActivity.java
diff --git a/services/tests/servicestests/apks/install/Android.bp b/services/tests/PackageManagerServiceTests/apks/install/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/install/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/install/Android.bp
diff --git a/services/tests/servicestests/apks/install/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/install/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/install/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/install/res/values/strings.xml b/services/tests/PackageManagerServiceTests/apks/install/res/values/strings.xml
similarity index 100%
rename from services/tests/servicestests/apks/install/res/values/strings.xml
rename to services/tests/PackageManagerServiceTests/apks/install/res/values/strings.xml
diff --git a/services/tests/servicestests/apks/install_bad_dex/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_bad_dex/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/install_bad_dex/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/install_bad_dex/Android.bp
diff --git a/services/tests/servicestests/apks/install_bad_dex/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install_bad_dex/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_bad_dex/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/install_bad_dex/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/install_bad_dex/classes.dex b/services/tests/PackageManagerServiceTests/apks/install_bad_dex/classes.dex
similarity index 100%
rename from services/tests/servicestests/apks/install_bad_dex/classes.dex
rename to services/tests/PackageManagerServiceTests/apks/install_bad_dex/classes.dex
diff --git a/services/tests/servicestests/apks/install_bad_dex/res/values/strings.xml b/services/tests/PackageManagerServiceTests/apks/install_bad_dex/res/values/strings.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_bad_dex/res/values/strings.xml
rename to services/tests/PackageManagerServiceTests/apks/install_bad_dex/res/values/strings.xml
diff --git a/services/tests/servicestests/apks/install_bad_dex/src/com/android/frameworks/coretests/TestActivity.java b/services/tests/PackageManagerServiceTests/apks/install_bad_dex/src/com/android/frameworks/coretests/TestActivity.java
similarity index 100%
rename from services/tests/servicestests/apks/install_bad_dex/src/com/android/frameworks/coretests/TestActivity.java
rename to services/tests/PackageManagerServiceTests/apks/install_bad_dex/src/com/android/frameworks/coretests/TestActivity.java
diff --git a/services/tests/servicestests/apks/install_complete_package_info/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_complete_package_info/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/install_complete_package_info/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/install_complete_package_info/Android.bp
diff --git a/services/tests/servicestests/apks/install_complete_package_info/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install_complete_package_info/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_complete_package_info/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/install_complete_package_info/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestActivity.java b/services/tests/PackageManagerServiceTests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestActivity.java
similarity index 100%
rename from services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestActivity.java
rename to services/tests/PackageManagerServiceTests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestActivity.java
diff --git a/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestProvider.java b/services/tests/PackageManagerServiceTests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestProvider.java
similarity index 100%
rename from services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestProvider.java
rename to services/tests/PackageManagerServiceTests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestProvider.java
diff --git a/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestReceiver.java b/services/tests/PackageManagerServiceTests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestReceiver.java
similarity index 100%
rename from services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestReceiver.java
rename to services/tests/PackageManagerServiceTests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestReceiver.java
diff --git a/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestService.java b/services/tests/PackageManagerServiceTests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestService.java
similarity index 100%
rename from services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestService.java
rename to services/tests/PackageManagerServiceTests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestService.java
diff --git a/services/tests/servicestests/apks/install_decl_perm/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_decl_perm/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/install_decl_perm/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/install_decl_perm/Android.bp
diff --git a/services/tests/servicestests/apks/install_decl_perm/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install_decl_perm/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_decl_perm/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/install_decl_perm/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/install_decl_perm/res/values/strings.xml b/services/tests/PackageManagerServiceTests/apks/install_decl_perm/res/values/strings.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_decl_perm/res/values/strings.xml
rename to services/tests/PackageManagerServiceTests/apks/install_decl_perm/res/values/strings.xml
diff --git a/services/tests/servicestests/apks/install_intent_filters/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_intent_filters/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/install_intent_filters/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/install_intent_filters/Android.bp
diff --git a/services/tests/servicestests/apks/install_intent_filters/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install_intent_filters/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_intent_filters/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/install_intent_filters/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/install_intent_filters/src/com/android/frameworks/servicestests/TestActivity.java b/services/tests/PackageManagerServiceTests/apks/install_intent_filters/src/com/android/frameworks/servicestests/TestActivity.java
similarity index 100%
rename from services/tests/servicestests/apks/install_intent_filters/src/com/android/frameworks/servicestests/TestActivity.java
rename to services/tests/PackageManagerServiceTests/apks/install_intent_filters/src/com/android/frameworks/servicestests/TestActivity.java
diff --git a/services/tests/servicestests/apks/install_loc_auto/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_loc_auto/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/install_loc_auto/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/install_loc_auto/Android.bp
diff --git a/services/tests/servicestests/apks/install_loc_auto/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install_loc_auto/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_loc_auto/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/install_loc_auto/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/install_loc_auto/res/values/strings.xml b/services/tests/PackageManagerServiceTests/apks/install_loc_auto/res/values/strings.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_loc_auto/res/values/strings.xml
rename to services/tests/PackageManagerServiceTests/apks/install_loc_auto/res/values/strings.xml
diff --git a/services/tests/servicestests/apks/install_loc_internal/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_loc_internal/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/install_loc_internal/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/install_loc_internal/Android.bp
diff --git a/services/tests/servicestests/apks/install_loc_internal/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install_loc_internal/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_loc_internal/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/install_loc_internal/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/install_loc_internal/res/values/strings.xml b/services/tests/PackageManagerServiceTests/apks/install_loc_internal/res/values/strings.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_loc_internal/res/values/strings.xml
rename to services/tests/PackageManagerServiceTests/apks/install_loc_internal/res/values/strings.xml
diff --git a/services/tests/servicestests/apks/install_loc_sdcard/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_loc_sdcard/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/install_loc_sdcard/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/install_loc_sdcard/Android.bp
diff --git a/services/tests/servicestests/apks/install_loc_sdcard/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install_loc_sdcard/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_loc_sdcard/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/install_loc_sdcard/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/install_loc_sdcard/res/values/strings.xml b/services/tests/PackageManagerServiceTests/apks/install_loc_sdcard/res/values/strings.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_loc_sdcard/res/values/strings.xml
rename to services/tests/PackageManagerServiceTests/apks/install_loc_sdcard/res/values/strings.xml
diff --git a/services/tests/servicestests/apks/install_loc_unspecified/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_loc_unspecified/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/install_loc_unspecified/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/install_loc_unspecified/Android.bp
diff --git a/services/tests/servicestests/apks/install_loc_unspecified/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install_loc_unspecified/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_loc_unspecified/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/install_loc_unspecified/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/install_loc_unspecified/res/values/strings.xml b/services/tests/PackageManagerServiceTests/apks/install_loc_unspecified/res/values/strings.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_loc_unspecified/res/values/strings.xml
rename to services/tests/PackageManagerServiceTests/apks/install_loc_unspecified/res/values/strings.xml
diff --git a/services/tests/servicestests/apks/install_use_perm_good/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_use_perm_good/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/install_use_perm_good/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/install_use_perm_good/Android.bp
diff --git a/services/tests/servicestests/apks/install_use_perm_good/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install_use_perm_good/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_use_perm_good/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/install_use_perm_good/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/install_use_perm_good/res/values/strings.xml b/services/tests/PackageManagerServiceTests/apks/install_use_perm_good/res/values/strings.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_use_perm_good/res/values/strings.xml
rename to services/tests/PackageManagerServiceTests/apks/install_use_perm_good/res/values/strings.xml
diff --git a/services/tests/servicestests/apks/install_uses_feature/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_uses_feature/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/install_uses_feature/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/install_uses_feature/Android.bp
diff --git a/services/tests/servicestests/apks/install_uses_feature/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_feature/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_uses_feature/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_feature/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/install_uses_feature/res/values/strings.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_feature/res/values/strings.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_uses_feature/res/values/strings.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_feature/res/values/strings.xml
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp
new file mode 100644
index 0000000..2894395
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp
@@ -0,0 +1,56 @@
+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_helper_app {
+    name: "FrameworksServicesTests_install_uses_sdk_q0",
+    defaults: ["FrameworksServicesTests_apks_defaults"],
+    manifest: "AndroidManifest-q0.xml",
+}
+
+android_test_helper_app {
+    name: "FrameworksServicesTests_install_uses_sdk_q0_r0",
+    defaults: ["FrameworksServicesTests_apks_defaults"],
+    manifest: "AndroidManifest-q0-r0.xml",
+}
+
+android_test_helper_app {
+    name: "FrameworksServicesTests_install_uses_sdk_r_none",
+    defaults: ["FrameworksServicesTests_apks_defaults"],
+    manifest: "AndroidManifest-r-none.xml",
+}
+
+android_test_helper_app {
+    name: "FrameworksServicesTests_install_uses_sdk_r0",
+    defaults: ["FrameworksServicesTests_apks_defaults"],
+    manifest: "AndroidManifest-r0.xml",
+}
+
+android_test_helper_app {
+    name: "FrameworksServicesTests_install_uses_sdk_r1000",
+    defaults: ["FrameworksServicesTests_apks_defaults"],
+    manifest: "AndroidManifest-r1000.xml",
+}
+
+android_test_helper_app {
+    name: "FrameworksServicesTests_install_uses_sdk_r0_s0",
+    defaults: ["FrameworksServicesTests_apks_defaults"],
+    manifest: "AndroidManifest-r0-s0.xml",
+}
+
+android_test_helper_app {
+    name: "FrameworksServicesTests_install_uses_sdk_r0_s1000",
+    defaults: ["FrameworksServicesTests_apks_defaults"],
+    manifest: "AndroidManifest-r0-s1000.xml",
+}
+
+android_test_helper_app {
+    name: "FrameworksServicesTests_install_uses_sdk_0",
+    defaults: ["FrameworksServicesTests_apks_defaults"],
+    manifest: "AndroidManifest-0.xml",
+}
diff --git a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-0.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-0.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-0.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-0.xml
diff --git a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-q0-r0.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-q0-r0.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-q0-r0.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-q0-r0.xml
diff --git a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-q0.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-q0.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-q0.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-q0.xml
diff --git a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r-none.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r-none.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r-none.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r-none.xml
diff --git a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s0.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s0.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s0.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s0.xml
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
new file mode 100644
index 0000000..25743b8
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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.servicestests.install_uses_sdk">
+
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+        <!-- This fails because 31 is not version 5 -->
+        <extension-sdk android:sdkVersion="30" android:minExtensionVersion="0" />
+        <extension-sdk android:sdkVersion="31" android:minExtensionVersion="1000" />
+    </uses-sdk>
+
+    <application>
+    </application>
+</manifest>
diff --git a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0.xml
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
new file mode 100644
index 0000000..9bf9254
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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.servicestests.install_uses_sdk">
+
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+        <!-- This will fail to install, because minExtensionVersion is not met -->
+        <extension-sdk android:sdkVersion="30" android:minExtensionVersion="1000" />
+    </uses-sdk>
+
+    <application>
+    </application>
+</manifest>
diff --git a/services/tests/servicestests/apks/install_uses_sdk/res/values/strings.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/res/values/strings.xml
similarity index 100%
rename from services/tests/servicestests/apks/install_uses_sdk/res/values/strings.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/res/values/strings.xml
diff --git a/services/tests/servicestests/apks/keyset/Android.bp b/services/tests/PackageManagerServiceTests/apks/keyset/Android.bp
similarity index 100%
rename from services/tests/servicestests/apks/keyset/Android.bp
rename to services/tests/PackageManagerServiceTests/apks/keyset/Android.bp
diff --git a/services/tests/servicestests/apks/keyset/api_test/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/keyset/api_test/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/keyset/api_test/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/keyset/api_test/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/keyset/permDef/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/keyset/permDef/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/keyset/permDef/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/keyset/permDef/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/keyset/permUse/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/keyset/permUse/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/keyset/permUse/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/keyset/permUse/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/keyset/res/values/strings.xml b/services/tests/PackageManagerServiceTests/apks/keyset/res/values/strings.xml
similarity index 100%
rename from services/tests/servicestests/apks/keyset/res/values/strings.xml
rename to services/tests/PackageManagerServiceTests/apks/keyset/res/values/strings.xml
diff --git a/services/tests/servicestests/apks/keyset/uA/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/keyset/uA/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/keyset/uA/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/keyset/uA/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/keyset/uAB/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/keyset/uAB/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/keyset/uAB/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/keyset/uAB/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/keyset/uAuB/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/keyset/uAuB/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/keyset/uAuB/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/keyset/uAuB/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/keyset/uB/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/keyset/uB/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/keyset/uB/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/keyset/uB/AndroidManifest.xml
diff --git a/services/tests/servicestests/apks/keyset/uNone/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/keyset/uNone/AndroidManifest.xml
similarity index 100%
rename from services/tests/servicestests/apks/keyset/uNone/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/apks/keyset/uNone/AndroidManifest.xml
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayPathsUninstallSystemUpdatesTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayPathsUninstallSystemUpdatesTest.kt
new file mode 100644
index 0000000..4044780
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayPathsUninstallSystemUpdatesTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test
+
+import com.android.internal.util.test.SystemPreparer
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class OverlayPathsUninstallSystemUpdatesTest : BaseHostJUnit4Test() {
+
+    companion object {
+        private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app"
+        private const val VERSION_ONE = "PackageManagerTestAppVersion1.apk"
+        private const val VERSION_TWO = "PackageManagerTestAppVersion2.apk"
+
+        @get:ClassRule
+        val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
+    }
+
+    private val tempFolder = TemporaryFolder()
+    private val preparer: SystemPreparer = SystemPreparer(tempFolder,
+            SystemPreparer.RebootStrategy.FULL, deviceRebootRule, true) { this.device }
+
+    @Rule
+    @JvmField
+    val rules = RuleChain.outerRule(tempFolder).around(preparer)!!
+    private val filePath = HostUtils.makePathForApk("PackageManagerTestApp.apk", Partition.PRODUCT)
+
+    @Before
+    @After
+    fun removeApk() {
+        device.uninstallPackage(TEST_PKG_NAME)
+    }
+
+    @Test
+    fun verify() {
+        // First, push a system app to the device and then update it so there's a data variant
+        preparer.pushResourceFile(VERSION_ONE, filePath.toString())
+                .reboot()
+
+        val versionTwoFile = HostUtils.copyResourceToHostFile(VERSION_TWO, tempFolder.newFile())
+
+        assertThat(device.installPackage(versionTwoFile, true)).isNull()
+
+        device.executeShellCommand(
+                "cmd overlay fabricate --target-name TestResources" +
+                " --target $TEST_PKG_NAME" +
+                " --name UninstallSystemUpdatesTest" +
+                " $TEST_PKG_NAME:color/overlay_test 0x1C 0xFFFFFFFF"
+        )
+
+        device.executeShellCommand(
+                "cmd overlay enable --user 0 com.android.shell:UninstallSystemUpdatesTest"
+        )
+
+        fun verifyValueOverlaid() {
+            assertThat(device.executeShellCommand(
+                    "cmd overlay lookup --user 0 $TEST_PKG_NAME $TEST_PKG_NAME:color/overlay_test"
+            ).trim()).isEqualTo("#ffffffff")
+        }
+
+        verifyValueOverlaid()
+
+        assertThat(
+                device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME"
+        ).trim()).endsWith("Success")
+
+        // Wait for paths to re-propagate. This doesn't do a retry loop in case the path clear also
+        // has some latency. There must be some minimum wait time for the paths to settle, and then
+        // a wait time for the paths to re-propagate. Rather than complicate the logic, just wait
+        // a long enough time for both events to occur.
+        Thread.sleep(5000)
+
+        verifyValueOverlaid()
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/colors.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/colors.xml
new file mode 100644
index 0000000..5a41d88
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+<resources>
+    <color name="overlay_test">#FF000000</color>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/overlayable.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/overlayable.xml
new file mode 100644
index 0000000..c5ba450
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/overlayable.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+<resources>
+    <overlayable name="TestResources">
+        <policy type="public">
+            <item type="color" name="overlay_test" />
+        </policy>
+    </overlayable>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
new file mode 100644
index 0000000..cc26593
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -0,0 +1,160 @@
+//########################################################################
+// Build PackageManagerServiceServerTests package
+//########################################################################
+
+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: "PackageManagerServiceServerTests",
+
+    // Include all test java files.
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "frameworks-base-testutils",
+        "services.accessibility",
+        "services.appwidget",
+        "services.autofill",
+        "services.backup",
+        "services.companion",
+        "services.core",
+        "services.devicepolicy",
+        "services.net",
+        "services.people",
+        "services.usage",
+        "guava",
+        "guava-android-testlib",
+        "androidx.test.core",
+        "androidx.test.ext.truth",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "cts-wm-util",
+        "platform-compat-test-rules",
+        "mockito-target-minus-junit4",
+        "platform-test-annotations",
+        "ShortcutManagerTestUtils",
+        "truth-prebuilt",
+        "testables",
+        "ub-uiautomator",
+        "platformprotosnano",
+        "framework-protos",
+        "hamcrest-library",
+        "servicestests-core-utils",
+        "servicestests-dpm-utils",
+        "servicestests-utils",
+        "service-permission.impl",
+        "testng",
+        "truth-prebuilt",
+        "junit",
+        "junit-params",
+        "platform-compat-test-rules",
+        "ActivityContext",
+        "coretests-aidl",
+    ],
+
+    libs: [
+        "android.hardware.power-V1-java",
+        "android.hardware.tv.cec-V1.0-java",
+        "android.hardware.vibrator-V2-java",
+        "android.hidl.manager-V1.0-java",
+        "android.test.mock",
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    platform_apis: true,
+
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
+
+    certificate: "platform",
+
+    // These are not normally accessible from apps so they must be explicitly included.
+    jni_libs: [
+        "libbase",
+        "libbinder",
+        "libc++",
+        "libcutils",
+        "liblog",
+        "liblzma",
+        "libnativehelper",
+        "libpsi",
+        "libui",
+        "libunwindstack",
+        "libutils",
+        "netd_aidl_interface-V5-cpp",
+        "libservices.core.settings.testonly",
+    ],
+
+    dxflags: ["--multi-dex"],
+
+    java_resources: [
+        ":PackageParserTestApp1",
+        ":PackageParserTestApp2",
+        ":PackageParserTestApp3",
+        ":PackageParserTestApp4",
+        ":PackageParserTestApp5",
+        ":PackageParserTestApp6",
+    ],
+    resource_zips: [":PackageManagerServiceServerTests_apks_as_resources"],
+}
+
+// Rules to copy all the test apks to the intermediate raw resource directory
+java_genrule {
+    name: "PackageManagerServiceServerTests_apks_as_resources",
+    srcs: [
+        ":FrameworksServicesTests_install",
+        ":FrameworksServicesTests_install_bad_dex",
+        ":FrameworksServicesTests_install_complete_package_info",
+        ":FrameworksServicesTests_install_decl_perm",
+        ":FrameworksServicesTests_install_intent_filters",
+        ":FrameworksServicesTests_install_loc_auto",
+        ":FrameworksServicesTests_install_loc_internal",
+        ":FrameworksServicesTests_install_loc_sdcard",
+        ":FrameworksServicesTests_install_loc_unspecified",
+        ":FrameworksServicesTests_install_use_perm_good",
+        ":FrameworksServicesTests_install_uses_feature",
+        ":FrameworksServicesTests_install_uses_sdk_0",
+        ":FrameworksServicesTests_install_uses_sdk_q0",
+        ":FrameworksServicesTests_install_uses_sdk_q0_r0",
+        ":FrameworksServicesTests_install_uses_sdk_r0",
+        ":FrameworksServicesTests_install_uses_sdk_r1000",
+        ":FrameworksServicesTests_install_uses_sdk_r_none",
+        ":FrameworksServicesTests_install_uses_sdk_r0_s0",
+        ":FrameworksServicesTests_install_uses_sdk_r0_s1000",
+        ":FrameworksServicesTests_keyset_permdef_sa_unone",
+        ":FrameworksServicesTests_keyset_permuse_sa_ua_ub",
+        ":FrameworksServicesTests_keyset_permuse_sb_ua_ub",
+        ":FrameworksServicesTests_keyset_sa_ua",
+        ":FrameworksServicesTests_keyset_sa_ua_ub",
+        ":FrameworksServicesTests_keyset_sa_uab",
+        ":FrameworksServicesTests_keyset_sa_ub",
+        ":FrameworksServicesTests_keyset_sa_unone",
+        ":FrameworksServicesTests_keyset_sab_ua",
+        ":FrameworksServicesTests_keyset_sau_ub",
+        ":FrameworksServicesTests_keyset_sb_ua",
+        ":FrameworksServicesTests_keyset_sb_ub",
+        ":FrameworksServicesTests_keyset_splat_api",
+        ":FrameworksServicesTests_keyset_splata_api",
+    ],
+    out: ["PackageManagerServiceServerTests_apks_as_resources.res.zip"],
+    tools: ["soong_zip"],
+
+    cmd: "mkdir -p $(genDir)/res/raw && " +
+        "for i in $(in); do " +
+        "  x=$${i##*FrameworksCoreTests_}; cp $$i $(genDir)/res/raw/$${x%.apk};" +
+        "  x=$${i##*FrameworksServicesTests_}; cp $$i $(genDir)/res/raw/$${x%.apk};" +
+        "done && " +
+        "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res",
+}
diff --git a/services/tests/PackageManagerServiceTests/server/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/server/AndroidManifest.xml
new file mode 100644
index 0000000..c383197
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/AndroidManifest.xml
@@ -0,0 +1,126 @@
+<?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.server.pm.test.service.server">
+
+    <uses-permission android:name="android.permission.READ_LOGS"/>
+    <uses-permission android:name="android.permission.ACCESS_VR_MANAGER"/>
+    <uses-permission android:name="android.permission.ACCOUNT_MANAGER"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.BROADCAST_STICKY"/>
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/>
+    <uses-permission android:name="android.permission.MANAGE_APP_TOKENS"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+    <uses-permission android:name="android.permission.REAL_GET_TASKS"/>
+    <uses-permission android:name="android.permission.GET_DETAILED_TASKS"/>
+    <uses-permission android:name="android.permission.REORDER_TASKS"/>
+    <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY"/>
+    <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
+    <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.MANAGE_USERS"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+    <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS"/>
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD"/>
+    <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/>
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
+    <uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/>
+    <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
+    <uses-permission android:name="android.permission.DELETE_PACKAGES"/>
+    <uses-permission android:name="android.permission.GET_APP_OPS_STATS"/>
+    <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS"/>
+    <uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES"/>
+    <uses-permission android:name="android.permission.DEVICE_POWER"/>
+    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
+    <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <uses-permission android:name="android.permission.STATUS_BAR"/>
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE"/>
+    <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER"/>
+    <uses-permission android:name="android.permission.READ_FRAME_BUFFER"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.STORAGE_INTERNAL"/>
+    <uses-permission android:name="android.permission.WATCH_APPOPS"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.SUSPEND_APPS"/>
+    <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.CONTROL_KEYGUARD"/>
+    <uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"/>
+    <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/>
+    <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
+    <uses-permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"/>
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
+    <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
+    <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+    <uses-permission android:name="android.permission.DUMP"/>
+    <uses-permission android:name="android.permission.READ_DREAM_STATE"/>
+    <uses-permission android:name="android.permission.READ_DREAM_SUPPRESSION"/>
+    <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
+    <uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE"/>
+    <uses-permission android:name="android.permission.VIBRATE_ALWAYS_ON"/>
+    <uses-permission android:name="android.permission.CONTROL_DEVICE_STATE"/>
+    <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/>
+    <uses-permission android:name="android.permission.KILL_UID"/>
+    <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK"/>
+    <uses-permission
+        android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+    <uses-permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY" />
+    <uses-permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
+    <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
+    <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
+    <uses-permission android:name="android.permission.BATTERY_STATS" />
+
+    <!-- Uses API introduced in O (26) -->
+    <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="26"/>
+
+    <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.server.pm.test.service.server"
+         android:label="Package Manager Service Tests"/>
+
+    <key-sets>
+        <key-set android:name="A" >
+            <public-key android:name="keyA"
+                        android:value="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsMpNthdOxud7roPDZMMomOqXgJJdRfIWpkKEqmC61Mv+Nf6QY3TorEwJeghjSmqj7IbBKrtvfQq4E2XJO1HuspmQO4Ng2gvn+r+6EwNfKc9k55d6s+27SR867jKurBbHNtZMG+tjL1yH4r+tNzcuJCsgyAFqLmxFdcxEwzNvREyRpoYc5RDR0mmTwkMCUhJ6CId1EYEKiCEdNzxv+fWPEb21u+/MWpleGCILs8kglRVb2q/WOzAAvGr4FY5plfaE6N+lr7+UschQ+aMi1+uqewo2o0qPFVmZP5hnwj55K4UMzu/NhhDqQQsX4cSGES1KgHo5MTqRqZjN/I7emw5pFQIDAQAB"/>
+        </key-set>
+        <upgrade-key-set android:name="A"/>
+    </key-sets>
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/server/AndroidTest.xml b/services/tests/PackageManagerServiceTests/server/AndroidTest.xml
new file mode 100644
index 0000000..869d60e
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs Frameworks 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="PackageManagerServiceServerTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="PackageManagerServiceServerTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.server.pm.test.service.server" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+        <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
+    </test>
+</configuration>
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256-lineage-2-signers b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256-lineage-2-signers
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256-lineage-2-signers
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256-lineage-2-signers
Binary files differ
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256-lineage-3-signers b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256-lineage-3-signers
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256-lineage-3-signers
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256-lineage-3-signers
Binary files differ
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256.pk8 b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256.pk8
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256.pk8
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256.pk8
Binary files differ
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256.x509.der b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256.x509.der
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256.x509.der
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256.x509.der
Binary files differ
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_2.pk8 b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256_2.pk8
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_2.pk8
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256_2.pk8
Binary files differ
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_2.x509.der b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256_2.x509.der
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_2.x509.der
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256_2.x509.der
Binary files differ
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_3.pk8 b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256_3.pk8
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_3.pk8
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256_3.pk8
Binary files differ
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_3.x509.der b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256_3.x509.der
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_3.x509.der
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/certs/ec-p256_3.x509.der
Binary files differ
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/README b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/README
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/README
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/README
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-extra-cert-tag.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-extra-cert-tag.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-extra-cert-tag.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-extra-cert-tag.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-cert-index.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-invalid-cert-index.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-cert-index.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-invalid-cert-index.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-cert-key.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-invalid-cert-key.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-cert-key.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-invalid-cert-key.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-public-key-cert-key.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-invalid-public-key-cert-key.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-public-key-cert-key.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-invalid-public-key-cert-key.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-tag.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-invalid-tag.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-tag.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-invalid-tag.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-index.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-missing-cert-index.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-index.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-missing-cert-index.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-key.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-missing-cert-key.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-key.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-missing-cert-key.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-tag.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-missing-cert-tag.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-tag.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-missing-cert-tag.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-scheme-version.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-missing-scheme-version.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-scheme-version.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-missing-scheme-version.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-sigs-count.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-missing-sigs-count.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-sigs-count.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-missing-sigs-count.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-previous-cert.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-previous-cert.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-previous-cert.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer-previous-cert.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/one-signer.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-invalid-pastSigs-count.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/three-signers-in-lineage-invalid-pastSigs-count.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-invalid-pastSigs-count.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/three-signers-in-lineage-invalid-pastSigs-count.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-pastSigs-cert-tag.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-pastSigs-cert-tag.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-pastSigs-cert-tag.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-pastSigs-cert-tag.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-pastSigs-count.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-pastSigs-count.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-pastSigs-count.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-pastSigs-count.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-scheme-version.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-scheme-version.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-scheme-version.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-scheme-version.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/three-signers-in-lineage.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/three-signers-in-lineage.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-invalid-certs-flags.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage-invalid-certs-flags.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-invalid-certs-flags.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage-invalid-certs-flags.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-invalid-pastSigs-cert-index.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage-invalid-pastSigs-cert-index.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-invalid-pastSigs-cert-index.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage-invalid-pastSigs-cert-index.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-missing-certs-flags.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage-missing-certs-flags.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-missing-certs-flags.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage-missing-certs-flags.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-missing-pastSigs-cert-index.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage-missing-pastSigs-cert-index.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-missing-pastSigs-cert-index.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage-missing-pastSigs-cert-index.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-multiple-pastSigs-tags.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage-multiple-pastSigs-tags.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-multiple-pastSigs-tags.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage-multiple-pastSigs-tags.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-no-caps.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage-no-caps.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-no-caps.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage-no-caps.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-undefined-pastSigs-index.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage-undefined-pastSigs-index.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-undefined-pastSigs-index.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage-undefined-pastSigs-index.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-in-lineage.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-v1v2-missing-cert-tag.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-v1v2-missing-cert-tag.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-v1v2-missing-cert-tag.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-v1v2-missing-cert-tag.xml
diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-v1v2.xml b/services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-v1v2.xml
similarity index 100%
rename from services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-v1v2.xml
rename to services/tests/PackageManagerServiceTests/server/assets/PackageSignaturesTest/xml/two-signers-v1v2.xml
diff --git a/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyActionSdkQ.apk b/services/tests/PackageManagerServiceTests/server/res/raw/PackageParsingTestAppEmptyActionSdkQ.apk
similarity index 100%
rename from services/tests/servicestests/res/raw/PackageParsingTestAppEmptyActionSdkQ.apk
rename to services/tests/PackageManagerServiceTests/server/res/raw/PackageParsingTestAppEmptyActionSdkQ.apk
Binary files differ
diff --git a/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyActionSdkR.apk b/services/tests/PackageManagerServiceTests/server/res/raw/PackageParsingTestAppEmptyActionSdkR.apk
similarity index 100%
rename from services/tests/servicestests/res/raw/PackageParsingTestAppEmptyActionSdkR.apk
rename to services/tests/PackageManagerServiceTests/server/res/raw/PackageParsingTestAppEmptyActionSdkR.apk
Binary files differ
diff --git a/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyCategorySdkQ.apk b/services/tests/PackageManagerServiceTests/server/res/raw/PackageParsingTestAppEmptyCategorySdkQ.apk
similarity index 100%
rename from services/tests/servicestests/res/raw/PackageParsingTestAppEmptyCategorySdkQ.apk
rename to services/tests/PackageManagerServiceTests/server/res/raw/PackageParsingTestAppEmptyCategorySdkQ.apk
Binary files differ
diff --git a/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyCategorySdkR.apk b/services/tests/PackageManagerServiceTests/server/res/raw/PackageParsingTestAppEmptyCategorySdkR.apk
similarity index 100%
rename from services/tests/servicestests/res/raw/PackageParsingTestAppEmptyCategorySdkR.apk
rename to services/tests/PackageManagerServiceTests/server/res/raw/PackageParsingTestAppEmptyCategorySdkR.apk
Binary files differ
diff --git a/services/tests/servicestests/res/raw/PackageParsingTestAppMissingAppSdkQ.apk b/services/tests/PackageManagerServiceTests/server/res/raw/PackageParsingTestAppMissingAppSdkQ.apk
similarity index 100%
rename from services/tests/servicestests/res/raw/PackageParsingTestAppMissingAppSdkQ.apk
rename to services/tests/PackageManagerServiceTests/server/res/raw/PackageParsingTestAppMissingAppSdkQ.apk
Binary files differ
diff --git a/services/tests/servicestests/res/raw/PackageParsingTestAppMissingAppSdkR.apk b/services/tests/PackageManagerServiceTests/server/res/raw/PackageParsingTestAppMissingAppSdkR.apk
similarity index 100%
rename from services/tests/servicestests/res/raw/PackageParsingTestAppMissingAppSdkR.apk
rename to services/tests/PackageManagerServiceTests/server/res/raw/PackageParsingTestAppMissingAppSdkR.apk
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app1_cert1 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert1
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app1_cert1
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert1
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app1_cert1_cert2 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert1_cert2
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app1_cert1_cert2
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert1_cert2
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app1_cert2 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert2
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app1_cert2
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert2
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app1_cert3 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert3
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app1_cert3
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert3
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app1_cert3_cert4 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert3_cert4
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app1_cert3_cert4
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert3_cert4
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app1_cert5 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert5
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app1_cert5
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert5
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app1_cert5_rotated_cert6 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert5_rotated_cert6
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app1_cert5_rotated_cert6
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert5_rotated_cert6
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app1_cert6 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert6
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app1_cert6
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app1_cert6
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app1_unsigned b/services/tests/PackageManagerServiceTests/server/res/raw/install_app1_unsigned
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app1_unsigned
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app1_unsigned
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app2_cert1 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert1
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app2_cert1
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert1
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app2_cert1_cert2 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert1_cert2
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app2_cert1_cert2
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert1_cert2
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app2_cert2 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert2
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app2_cert2
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert2
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app2_cert3 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert3
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app2_cert3
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert3
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app2_cert5_rotated_cert6 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app2_cert5_rotated_cert6
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_app2_unsigned b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_unsigned
similarity index 100%
rename from services/tests/servicestests/res/raw/install_app2_unsigned
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_app2_unsigned
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_shared1_cert1 b/services/tests/PackageManagerServiceTests/server/res/raw/install_shared1_cert1
similarity index 100%
rename from services/tests/servicestests/res/raw/install_shared1_cert1
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_shared1_cert1
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_shared1_cert1_cert2 b/services/tests/PackageManagerServiceTests/server/res/raw/install_shared1_cert1_cert2
similarity index 100%
rename from services/tests/servicestests/res/raw/install_shared1_cert1_cert2
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_shared1_cert1_cert2
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_shared1_cert2 b/services/tests/PackageManagerServiceTests/server/res/raw/install_shared1_cert2
similarity index 100%
rename from services/tests/servicestests/res/raw/install_shared1_cert2
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_shared1_cert2
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_shared1_unsigned b/services/tests/PackageManagerServiceTests/server/res/raw/install_shared1_unsigned
similarity index 100%
rename from services/tests/servicestests/res/raw/install_shared1_unsigned
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_shared1_unsigned
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_shared2_cert1 b/services/tests/PackageManagerServiceTests/server/res/raw/install_shared2_cert1
similarity index 100%
rename from services/tests/servicestests/res/raw/install_shared2_cert1
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_shared2_cert1
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_shared2_cert1_cert2 b/services/tests/PackageManagerServiceTests/server/res/raw/install_shared2_cert1_cert2
similarity index 100%
rename from services/tests/servicestests/res/raw/install_shared2_cert1_cert2
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_shared2_cert1_cert2
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_shared2_cert2 b/services/tests/PackageManagerServiceTests/server/res/raw/install_shared2_cert2
similarity index 100%
rename from services/tests/servicestests/res/raw/install_shared2_cert2
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_shared2_cert2
Binary files differ
diff --git a/services/tests/servicestests/res/raw/install_shared2_unsigned b/services/tests/PackageManagerServiceTests/server/res/raw/install_shared2_unsigned
similarity index 100%
rename from services/tests/servicestests/res/raw/install_shared2_unsigned
rename to services/tests/PackageManagerServiceTests/server/res/raw/install_shared2_unsigned
Binary files differ
diff --git a/services/tests/PackageManagerServiceTests/server/res/values/values.xml b/services/tests/PackageManagerServiceTests/server/res/values/values.xml
new file mode 100644
index 0000000..79c6653
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/res/values/values.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="module_1_name" translatable="false">module_1_name</string>
+    <string name="module_2_name" translatable="false">module_2_name</string>
+</resources>
diff --git a/services/tests/servicestests/res/xml/unparseable_metadata1.xml b/services/tests/PackageManagerServiceTests/server/res/xml/unparseable_metadata1.xml
similarity index 100%
rename from services/tests/servicestests/res/xml/unparseable_metadata1.xml
rename to services/tests/PackageManagerServiceTests/server/res/xml/unparseable_metadata1.xml
diff --git a/services/tests/servicestests/res/xml/unparseable_metadata2.xml b/services/tests/PackageManagerServiceTests/server/res/xml/unparseable_metadata2.xml
similarity index 100%
rename from services/tests/servicestests/res/xml/unparseable_metadata2.xml
rename to services/tests/PackageManagerServiceTests/server/res/xml/unparseable_metadata2.xml
diff --git a/services/tests/servicestests/res/xml/well_formed_metadata.xml b/services/tests/PackageManagerServiceTests/server/res/xml/well_formed_metadata.xml
similarity index 100%
rename from services/tests/servicestests/res/xml/well_formed_metadata.xml
rename to services/tests/PackageManagerServiceTests/server/res/xml/well_formed_metadata.xml
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/AppsFilterImplTest.java
new file mode 100644
index 0000000..3575b57
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/AppsFilterImplTest.java
@@ -0,0 +1,1680 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+
+import static android.os.Process.INVALID_UID;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.UserInfo;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.om.OverlayReferenceMapper;
+import com.android.server.pm.parsing.pkg.PackageImpl;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.component.ParsedActivityImpl;
+import com.android.server.pm.pkg.component.ParsedComponentImpl;
+import com.android.server.pm.pkg.component.ParsedInstrumentationImpl;
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
+import com.android.server.pm.pkg.component.ParsedPermission;
+import com.android.server.pm.pkg.component.ParsedPermissionImpl;
+import com.android.server.pm.pkg.component.ParsedProviderImpl;
+import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.utils.WatchableTester;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+import java.security.cert.CertificateException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Presubmit
+@RunWith(JUnit4.class)
+public class AppsFilterImplTest {
+
+    private static final int DUMMY_CALLING_APPID = 10345;
+    private static final int DUMMY_TARGET_APPID = 10556;
+    private static final int DUMMY_ACTOR_APPID = 10656;
+    private static final int DUMMY_OVERLAY_APPID = 10756;
+    private static final int SYSTEM_USER = 0;
+    private static final int SECONDARY_USER = 10;
+    private static final int ADDED_USER = 11;
+    private static final int[] USER_ARRAY = {SYSTEM_USER, SECONDARY_USER};
+    private static final int[] USER_ARRAY_WITH_ADDED = {SYSTEM_USER, SECONDARY_USER, ADDED_USER};
+    private static final UserInfo[] USER_INFO_LIST = toUserInfos(USER_ARRAY);
+    private static final UserInfo[] USER_INFO_LIST_WITH_ADDED = toUserInfos(USER_ARRAY_WITH_ADDED);
+
+    private static UserInfo[] toUserInfos(int[] userIds) {
+        return Arrays.stream(userIds)
+                .mapToObj(id -> new UserInfo(id, Integer.toString(id), 0))
+                .toArray(UserInfo[]::new);
+    }
+
+    @Mock
+    FeatureConfig mFeatureConfigMock;
+    @Mock
+    Computer mSnapshot;
+    @Mock
+    Handler mMockHandler;
+    @Mock
+    PackageManagerInternal mPmInternal;
+
+    private ArrayMap<String, PackageSetting> mExisting = new ArrayMap<>();
+    private Collection<SharedUserSetting> mSharedUserSettings = new ArraySet<>();
+
+    private static ParsingPackage pkg(String packageName) {
+        return PackageImpl.forTesting(packageName)
+                .setTargetSdkVersion(Build.VERSION_CODES.R);
+    }
+
+    private static ParsingPackage pkg(String packageName, Intent... queries) {
+        ParsingPackage pkg = pkg(packageName);
+        if (queries != null) {
+            for (Intent intent : queries) {
+                pkg.addQueriesIntent(intent);
+            }
+        }
+        return pkg;
+    }
+
+    private static ParsingPackage pkgQueriesProvider(String packageName,
+            String... queriesAuthorities) {
+        ParsingPackage pkg = pkg(packageName);
+        if (queriesAuthorities != null) {
+            for (String authority : queriesAuthorities) {
+                pkg.addQueriesProvider(authority);
+            }
+        }
+        return pkg;
+    }
+
+    private static ParsingPackage pkg(String packageName, String... queriesPackages) {
+        ParsingPackage pkg = pkg(packageName);
+        if (queriesPackages != null) {
+            for (String queryPackageName : queriesPackages) {
+                pkg.addQueriesPackage(queryPackageName);
+            }
+        }
+        return pkg;
+    }
+
+    private static ParsingPackage pkg(String packageName, IntentFilter... filters) {
+        ParsedActivity activity = createActivity(packageName, filters);
+        return pkg(packageName).addActivity(activity);
+    }
+
+    private static ParsingPackage pkgWithReceiver(String packageName, IntentFilter... filters) {
+        ParsedActivity receiver = createActivity(packageName, filters);
+        return pkg(packageName).addReceiver(receiver);
+    }
+
+    private static ParsingPackage pkgWithSharedLibrary(String packageName, String libName) {
+        return pkg(packageName).addLibraryName(libName);
+    }
+
+    private static ParsingPackage pkgWithCustomPermissions(String packageName,
+            String... permNames) {
+        ParsingPackage newPkg = pkg(packageName);
+        for (String permName : permNames) {
+            ParsedPermission permission = new ParsedPermissionImpl();
+            ((ParsedComponentImpl) permission).setName(permName);
+            newPkg.addPermission(permission);
+        }
+        return newPkg;
+    }
+
+    private static ParsedActivity createActivity(String packageName, IntentFilter[] filters) {
+        ParsedActivityImpl activity = new ParsedActivityImpl();
+        activity.setPackageName(packageName);
+        for (IntentFilter filter : filters) {
+            final ParsedIntentInfoImpl info = new ParsedIntentInfoImpl();
+            final IntentFilter intentInfoFilter = info.getIntentFilter();
+            if (filter.countActions() > 0) {
+                filter.actionsIterator().forEachRemaining(intentInfoFilter::addAction);
+            }
+            if (filter.countCategories() > 0) {
+                filter.actionsIterator().forEachRemaining(intentInfoFilter::addAction);
+            }
+            if (filter.countDataAuthorities() > 0) {
+                filter.authoritiesIterator().forEachRemaining(intentInfoFilter::addDataAuthority);
+            }
+            if (filter.countDataSchemes() > 0) {
+                filter.schemesIterator().forEachRemaining(intentInfoFilter::addDataScheme);
+            }
+            activity.addIntent(info);
+            activity.setExported(true);
+        }
+        return activity;
+    }
+
+    private static ParsingPackage pkgWithInstrumentation(
+            String packageName, String instrumentationTargetPackage) {
+        ParsedInstrumentationImpl instrumentation = new ParsedInstrumentationImpl();
+        instrumentation.setTargetPackage(instrumentationTargetPackage);
+        return pkg(packageName).addInstrumentation(instrumentation);
+    }
+
+    private static ParsingPackage pkgWithProvider(String packageName, String authority) {
+        ParsedProviderImpl provider = new ParsedProviderImpl();
+        provider.setPackageName(packageName);
+        provider.setExported(true);
+        provider.setAuthority(authority);
+        return pkg(packageName)
+                .addProvider(provider);
+    }
+
+    @Before
+    public void setup() throws Exception {
+        mExisting = new ArrayMap<>();
+
+        MockitoAnnotations.initMocks(this);
+        when(mSnapshot.getPackageStates()).thenAnswer(x -> mExisting);
+        when(mSnapshot.getAllSharedUsers()).thenReturn(mSharedUserSettings);
+        when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST);
+        when(mPmInternal.snapshot()).thenReturn(mSnapshot);
+
+        // Can't mock postDelayed because of some weird bug in Mockito.
+        when(mMockHandler.sendMessageDelayed(any(Message.class), anyLong())).thenAnswer(
+                invocation -> {
+                    ((Message) invocation.getArgument(0)).getCallback().run();
+                    return null;
+                });
+
+        when(mFeatureConfigMock.isGloballyEnabled()).thenReturn(true);
+        when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class))).thenAnswer(
+                (Answer<Boolean>) invocation ->
+                        ((AndroidPackage) invocation.getArgument(SYSTEM_USER)).getTargetSdkVersion()
+                                >= Build.VERSION_CODES.R);
+    }
+
+    @Test
+    public void testSystemReadyPropogates() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */null, mMockHandler);
+        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
+        watcher.register();
+        appsFilter.onSystemReady(mPmInternal);
+        watcher.verifyChangeReported("systemReady");
+        verify(mFeatureConfigMock).onSystemReady();
+    }
+
+    @Test
+    public void testQueriesAction_FilterMatches() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
+        watcher.register();
+        simulateAddBasicAndroid(appsFilter);
+        watcher.verifyChangeReported("addBasicAndroid");
+        appsFilter.onSystemReady(mPmInternal);
+        watcher.verifyChangeReported("systemReady");
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package", new IntentFilter("TEST_ACTION")), DUMMY_TARGET_APPID);
+        watcher.verifyChangeReported("add package");
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID);
+        watcher.verifyChangeReported("add package");
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+        watcher.verifyNoChangeReported("shouldFilterAplication");
+    }
+
+    @Test
+    public void testQueriesProtectedAction_FilterDoesNotMatch() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
+        watcher.register();
+        final Signature frameworkSignature = Mockito.mock(Signature.class);
+        final SigningDetails frameworkSigningDetails =
+                new SigningDetails(new Signature[]{frameworkSignature}, 1);
+        final ParsingPackage android = pkg("android");
+        watcher.verifyNoChangeReported("prepare");
+        android.addProtectedBroadcast("TEST_ACTION");
+        simulateAddPackage(appsFilter, android, 1000,
+                b -> b.setSigningDetails(frameworkSigningDetails));
+        watcher.verifyChangeReported("addPackage");
+        appsFilter.onSystemReady(mPmInternal);
+        watcher.verifyChangeReported("systemReady");
+
+        final int activityUid = DUMMY_TARGET_APPID;
+        PackageSetting targetActivity = simulateAddPackage(appsFilter,
+                pkg("com.target.activity", new IntentFilter("TEST_ACTION")), activityUid);
+        watcher.verifyChangeReported("addPackage");
+        final int receiverUid = DUMMY_TARGET_APPID + 1;
+        PackageSetting targetReceiver = simulateAddPackage(appsFilter,
+                pkgWithReceiver("com.target.receiver", new IntentFilter("TEST_ACTION")),
+                receiverUid);
+        watcher.verifyChangeReported("addPackage");
+        final int callingUid = DUMMY_CALLING_APPID;
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.calling.action", new Intent("TEST_ACTION")), callingUid);
+        watcher.verifyChangeReported("addPackage");
+        final int wildcardUid = DUMMY_CALLING_APPID + 1;
+        PackageSetting callingWildCard = simulateAddPackage(appsFilter,
+                pkg("com.calling.wildcard", new Intent("*")), wildcardUid);
+        watcher.verifyChangeReported("addPackage");
+
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, callingUid, calling,
+                targetActivity, SYSTEM_USER));
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, callingUid, calling,
+                targetReceiver, SYSTEM_USER));
+
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
+                wildcardUid, callingWildCard, targetActivity, SYSTEM_USER));
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot,
+                wildcardUid, callingWildCard, targetReceiver, SYSTEM_USER));
+        watcher.verifyNoChangeReported("shouldFilterApplication");
+    }
+
+    @Test
+    public void testQueriesProvider_FilterMatches() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
+        watcher.register();
+        simulateAddBasicAndroid(appsFilter);
+        watcher.verifyChangeReported("addPackage");
+        appsFilter.onSystemReady(mPmInternal);
+        watcher.verifyChangeReported("systemReady");
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID);
+        watcher.verifyChangeReported("addPackage");
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkgQueriesProvider("com.some.other.package", "com.some.authority"),
+                DUMMY_CALLING_APPID);
+        watcher.verifyChangeReported("addPackage");
+
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
+                target, SYSTEM_USER));
+        watcher.verifyNoChangeReported("shouldFilterApplication");
+    }
+
+    @Test
+    public void testOnUserUpdated_FilterMatches() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID);
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkgQueriesProvider("com.some.other.package", "com.some.authority"),
+                DUMMY_CALLING_APPID);
+
+        for (int subjectUserId : USER_ARRAY) {
+            for (int otherUserId : USER_ARRAY) {
+                assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
+                        UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
+                        otherUserId));
+            }
+        }
+
+        // adds new user
+        when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST_WITH_ADDED);
+        appsFilter.onUserCreated(mSnapshot, ADDED_USER);
+
+        for (int subjectUserId : USER_ARRAY_WITH_ADDED) {
+            for (int otherUserId : USER_ARRAY_WITH_ADDED) {
+                assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
+                        UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
+                        otherUserId));
+            }
+        }
+
+        // delete user
+        when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST);
+        appsFilter.onUserDeleted(mSnapshot, ADDED_USER);
+
+        for (int subjectUserId : USER_ARRAY) {
+            for (int otherUserId : USER_ARRAY) {
+                assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
+                        UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
+                        otherUserId));
+            }
+        }
+    }
+
+    @Test
+    public void testQueriesDifferentProvider_Filters() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
+        watcher.register();
+        simulateAddBasicAndroid(appsFilter);
+        watcher.verifyChangeReported("addPackage");
+        appsFilter.onSystemReady(mPmInternal);
+        watcher.verifyChangeReported("systemReady");
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID);
+        watcher.verifyChangeReported("addPackage");
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkgQueriesProvider("com.some.other.package", "com.some.other.authority"),
+                DUMMY_CALLING_APPID);
+        watcher.verifyChangeReported("addPackage");
+
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
+                target, SYSTEM_USER));
+        watcher.verifyNoChangeReported("shouldFilterApplication");
+    }
+
+    @Test
+    public void testQueriesProviderWithSemiColon_FilterMatches() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"),
+                DUMMY_TARGET_APPID);
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkgQueriesProvider("com.some.other.package", "com.some.authority"),
+                DUMMY_CALLING_APPID);
+
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
+                target, SYSTEM_USER));
+    }
+
+    @Test
+    public void testQueriesAction_NoMatchingAction_Filters() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package"), DUMMY_TARGET_APPID);
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID);
+
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
+                target, SYSTEM_USER));
+    }
+
+    @Test
+    public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package"), DUMMY_TARGET_APPID);
+        ParsingPackage callingPkg = pkg("com.some.other.package",
+                new Intent("TEST_ACTION"))
+                .setTargetSdkVersion(Build.VERSION_CODES.P);
+        PackageSetting calling = simulateAddPackage(appsFilter, callingPkg,
+                DUMMY_CALLING_APPID);
+
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testNoQueries_Filters() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package"), DUMMY_TARGET_APPID);
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package"), DUMMY_CALLING_APPID);
+
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testNoUsesLibrary_Filters() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        final Signature mockSignature = Mockito.mock(Signature.class);
+        final SigningDetails mockSigningDetails = new SigningDetails(
+                new Signature[]{mockSignature},
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2);
+
+        final PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithSharedLibrary("com.some.package", "com.some.shared_library"),
+                DUMMY_TARGET_APPID,
+                setting -> setting.setSigningDetails(mockSigningDetails)
+                        .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+        final PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package"), DUMMY_CALLING_APPID);
+
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testUsesLibrary_DoesntFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        final Signature mockSignature = Mockito.mock(Signature.class);
+        final SigningDetails mockSigningDetails = new SigningDetails(
+                new Signature[]{mockSignature},
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2);
+
+        final PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithSharedLibrary("com.some.package", "com.some.shared_library"),
+                DUMMY_TARGET_APPID,
+                setting -> setting.setSigningDetails(mockSigningDetails)
+                        .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+        final PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package").addUsesLibrary("com.some.shared_library"),
+                DUMMY_CALLING_APPID);
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testUsesOptionalLibrary_DoesntFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        final Signature mockSignature = Mockito.mock(Signature.class);
+        final SigningDetails mockSigningDetails = new SigningDetails(
+                new Signature[]{mockSignature},
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2);
+
+        final PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithSharedLibrary("com.some.package", "com.some.shared_library"),
+                DUMMY_TARGET_APPID,
+                setting -> setting.setSigningDetails(mockSigningDetails)
+                        .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+        final PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package").addUsesOptionalLibrary("com.some.shared_library"),
+                DUMMY_CALLING_APPID);
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testUsesLibrary_ShareUid_DoesntFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        final Signature mockSignature = Mockito.mock(Signature.class);
+        final SigningDetails mockSigningDetails = new SigningDetails(
+                new Signature[]{mockSignature},
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2);
+
+        final PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithSharedLibrary("com.some.package", "com.some.shared_library"),
+                DUMMY_TARGET_APPID,
+                setting -> setting.setSigningDetails(mockSigningDetails)
+                        .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+        final PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package_a").setSharedUserId("com.some.uid"),
+                DUMMY_CALLING_APPID);
+        simulateAddPackage(appsFilter, pkg("com.some.other.package_b")
+                        .setSharedUserId("com.some.uid").addUsesLibrary("com.some.shared_library"),
+                DUMMY_CALLING_APPID);
+
+        // Although package_a doesn't use library, it should be granted visibility. It's because
+        // package_a shares userId with package_b, and package_b uses that shared library.
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testForceQueryable_SystemDoesntFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_APPID,
+                setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package"), DUMMY_CALLING_APPID);
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+
+    @Test
+    public void testForceQueryable_NonSystemFilters() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_APPID);
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package"), DUMMY_CALLING_APPID);
+
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testForceQueryableByDevice_SystemCaller_DoesntFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{"com.some.package"},
+                        /* systemAppsQueryable */ false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package"), DUMMY_TARGET_APPID,
+                setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package"), DUMMY_CALLING_APPID);
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+
+    @Test
+    public void testSystemSignedTarget_DoesntFilter() throws CertificateException {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        appsFilter.onSystemReady(mPmInternal);
+
+        final Signature frameworkSignature = Mockito.mock(Signature.class);
+        final SigningDetails frameworkSigningDetails =
+                new SigningDetails(new Signature[]{frameworkSignature}, 1);
+
+        final Signature otherSignature = Mockito.mock(Signature.class);
+        final SigningDetails otherSigningDetails =
+                new SigningDetails(new Signature[]{otherSignature}, 1);
+
+        simulateAddPackage(appsFilter, pkg("android"), 1000,
+                b -> b.setSigningDetails(frameworkSigningDetails));
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
+                DUMMY_TARGET_APPID,
+                b -> b.setSigningDetails(frameworkSigningDetails)
+                        .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package"), DUMMY_CALLING_APPID,
+                b -> b.setSigningDetails(otherSigningDetails));
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testForceQueryableByDevice_NonSystemCaller_Filters() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{"com.some.package"},
+                        /* systemAppsQueryable */ false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package"), DUMMY_TARGET_APPID);
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package"), DUMMY_CALLING_APPID);
+
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+
+    @Test
+    public void testSystemQueryable_DoesntFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        true, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package"), DUMMY_TARGET_APPID,
+                setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package"), DUMMY_CALLING_APPID);
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testQueriesPackage_DoesntFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package"), DUMMY_TARGET_APPID);
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package", "com.some.package"), DUMMY_CALLING_APPID);
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testNoQueries_FeatureOff_DoesntFilter() throws Exception {
+        when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class)))
+                .thenReturn(false);
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(
+                appsFilter, pkg("com.some.package"), DUMMY_TARGET_APPID);
+        PackageSetting calling = simulateAddPackage(
+                appsFilter, pkg("com.some.other.package"), DUMMY_CALLING_APPID);
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testSystemUid_DoesntFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package"), DUMMY_TARGET_APPID);
+
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, SYSTEM_USER, null, target,
+                SYSTEM_USER));
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
+                Process.FIRST_APPLICATION_UID - 1, null, target, SYSTEM_USER));
+    }
+
+    @Test
+    public void testSystemUidSecondaryUser_DoesntFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package"), DUMMY_TARGET_APPID);
+
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, 0, null, target,
+                SECONDARY_USER));
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
+                UserHandle.getUid(SECONDARY_USER, Process.FIRST_APPLICATION_UID - 1),
+                null, target, SECONDARY_USER));
+    }
+
+    @Test
+    public void testNonSystemUid_NoCallingSetting_Filters() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package"), DUMMY_TARGET_APPID);
+
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, null, target,
+                SYSTEM_USER));
+    }
+
+    @Test
+    public void testNoTargetPackage_Filters() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = new PackageSettingBuilder()
+                .setAppId(DUMMY_TARGET_APPID)
+                .setName("com.some.package")
+                .setCodePath("/")
+                .setPVersionCode(1L)
+                .build();
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID);
+
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testActsOnTargetOfOverlay() throws Exception {
+        final String actorName = "overlay://test/actorName";
+
+        var target = pkg("com.some.package.target")
+                .addOverlayable("overlayableName", actorName)
+                .hideAsParsed();
+        var overlay = pkg("com.some.package.overlay")
+                .setOverlay(true)
+                .setOverlayTarget(target.getPackageName())
+                .setOverlayTargetOverlayableName("overlayableName")
+                .hideAsParsed();
+        var actor = pkg("com.some.package.actor")
+                .hideAsParsed();
+
+        final AppsFilterImpl appsFilter = new AppsFilterImpl(
+                mFeatureConfigMock,
+                new String[]{},
+                false,
+                new OverlayReferenceMapper.Provider() {
+                    @Nullable
+                    @Override
+                    public String getActorPkg(String actorString) {
+                        if (actorName.equals(actorString)) {
+                            return actor.getPackageName();
+                        }
+                        return null;
+                    }
+
+                    @NonNull
+                    @Override
+                    public Map<String, Set<String>> getTargetToOverlayables(
+                            @NonNull AndroidPackage pkg) {
+                        if (overlay.getPackageName().equals(pkg.getPackageName())) {
+                            Map<String, Set<String>> map = new ArrayMap<>();
+                            Set<String> set = new ArraySet<>();
+                            set.add(overlay.getOverlayTargetOverlayableName());
+                            map.put(overlay.getOverlayTarget(), set);
+                            return map;
+                        }
+                        return Collections.emptyMap();
+                    }
+                },
+                mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        // Packages must be added in actor -> overlay -> target order so that the implicit
+        // visibility of the actor into the overlay can be tested
+
+        PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_APPID);
+        PackageSetting overlaySetting =
+                simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID);
+
+        // Actor can not see overlay (yet)
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
+                overlaySetting, SYSTEM_USER));
+
+        PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID);
+
+        // Actor can see both target and overlay
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
+                targetSetting, SYSTEM_USER));
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
+                overlaySetting, SYSTEM_USER));
+
+        // But target/overlay can't see each other
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, targetSetting,
+                overlaySetting, SYSTEM_USER));
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_OVERLAY_APPID,
+                overlaySetting, targetSetting, SYSTEM_USER));
+
+        // And can't see the actor
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, targetSetting,
+                actorSetting, SYSTEM_USER));
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_OVERLAY_APPID,
+                overlaySetting, actorSetting, SYSTEM_USER));
+
+        appsFilter.removePackage(mSnapshot, targetSetting);
+
+        // Actor loses visibility to the overlay via removal of the target
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
+                overlaySetting, SYSTEM_USER));
+    }
+
+    @Test
+    public void testActsOnTargetOfOverlayThroughSharedUser() throws Exception {
+//        Debug.waitForDebugger();
+
+        final String actorName = "overlay://test/actorName";
+
+        var target = pkg("com.some.package.target")
+                .addOverlayable("overlayableName", actorName)
+                .hideAsParsed();
+        var overlay = pkg("com.some.package.overlay")
+                .setOverlay(true)
+                .setOverlayTarget(target.getPackageName())
+                .setOverlayTargetOverlayableName("overlayableName")
+                .hideAsParsed();
+        var actorOne = pkg("com.some.package.actor.one").hideAsParsed();
+        var actorTwo = pkg("com.some.package.actor.two").hideAsParsed();
+        PackageSetting ps1 = getPackageSettingFromParsingPackage(actorOne, DUMMY_ACTOR_APPID,
+                null /*settingBuilder*/);
+        PackageSetting ps2 = getPackageSettingFromParsingPackage(actorTwo, DUMMY_ACTOR_APPID,
+                null /*settingBuilder*/);
+
+        final AppsFilterImpl appsFilter = new AppsFilterImpl(
+                mFeatureConfigMock,
+                new String[]{},
+                false,
+                new OverlayReferenceMapper.Provider() {
+                    @Nullable
+                    @Override
+                    public String getActorPkg(String actorString) {
+                        // Only actorOne is mapped as a valid actor
+                        if (actorName.equals(actorString)) {
+                            return actorOne.getPackageName();
+                        }
+                        return null;
+                    }
+
+                    @NonNull
+                    @Override
+                    public Map<String, Set<String>> getTargetToOverlayables(
+                            @NonNull AndroidPackage pkg) {
+                        if (overlay.getPackageName().equals(pkg.getPackageName())) {
+                            Map<String, Set<String>> map = new ArrayMap<>();
+                            Set<String> set = new ArraySet<>();
+                            set.add(overlay.getOverlayTargetOverlayableName());
+                            map.put(overlay.getOverlayTarget(), set);
+                            return map;
+                        }
+                        return Collections.emptyMap();
+                    }
+                },
+                mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID);
+        SharedUserSetting actorSharedSetting = new SharedUserSetting("actorSharedUser",
+                targetSetting.getFlags(), targetSetting.getPrivateFlags());
+        actorSharedSetting.mAppId = 100; /* mimic a valid sharedUserSetting.mAppId */
+        PackageSetting overlaySetting =
+                simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID);
+        simulateAddPackage(ps1, appsFilter, actorSharedSetting);
+        simulateAddPackage(ps2, appsFilter, actorSharedSetting);
+
+        // actorTwo can see both target and overlay
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID,
+                actorSharedSetting, targetSetting, SYSTEM_USER));
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID,
+                actorSharedSetting, overlaySetting, SYSTEM_USER));
+    }
+
+    @Test
+    public void testInitiatingApp_DoesntFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
+                DUMMY_TARGET_APPID);
+        PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
+                DUMMY_CALLING_APPID,
+                withInstallSource(target.getPackageName(), null, null, INVALID_UID, null, false));
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testUninstalledInitiatingApp_Filters() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
+                DUMMY_TARGET_APPID);
+        PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
+                DUMMY_CALLING_APPID,
+                withInstallSource(target.getPackageName(), null, null, INVALID_UID, null, true));
+
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testOriginatingApp_Filters() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
+        watcher.register();
+        simulateAddBasicAndroid(appsFilter);
+        watcher.verifyChangeReported("addBasicAndroid");
+        appsFilter.onSystemReady(mPmInternal);
+        watcher.verifyChangeReported("systemReady");
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
+                DUMMY_TARGET_APPID);
+        watcher.verifyChangeReported("add package");
+        PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
+                DUMMY_CALLING_APPID, withInstallSource(null, target.getPackageName(), null,
+                        INVALID_UID, null, false));
+        watcher.verifyChangeReported("add package");
+
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+        watcher.verifyNoChangeReported("shouldFilterAplication");
+    }
+
+    @Test
+    public void testInstallingApp_DoesntFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
+        watcher.register();
+        simulateAddBasicAndroid(appsFilter);
+        watcher.verifyChangeReported("addBasicAndroid");
+        appsFilter.onSystemReady(mPmInternal);
+        watcher.verifyChangeReported("systemReady");
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
+                DUMMY_TARGET_APPID);
+        watcher.verifyChangeReported("add package");
+        PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
+                DUMMY_CALLING_APPID, withInstallSource(null, null, target.getPackageName(),
+                        DUMMY_TARGET_APPID, null, false));
+        watcher.verifyChangeReported("add package");
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+        watcher.verifyNoChangeReported("shouldFilterAplication");
+    }
+
+    @Test
+    public void testInstrumentation_DoesntFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
+        watcher.register();
+        simulateAddBasicAndroid(appsFilter);
+        watcher.verifyChangeReported("addBasicAndroid");
+        appsFilter.onSystemReady(mPmInternal);
+        watcher.verifyChangeReported("systemReady");
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
+                DUMMY_TARGET_APPID);
+        watcher.verifyChangeReported("add package");
+        PackageSetting instrumentation = simulateAddPackage(appsFilter,
+                pkgWithInstrumentation("com.some.other.package", "com.some.package"),
+                DUMMY_CALLING_APPID);
+        watcher.verifyChangeReported("add package");
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+                        target, SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, target,
+                        instrumentation, SYSTEM_USER));
+        watcher.verifyNoChangeReported("shouldFilterAplication");
+    }
+
+    @Test
+    public void testWhoCanSee() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
+        watcher.register();
+        simulateAddBasicAndroid(appsFilter);
+        watcher.verifyChangeReported("addBasicAndroid");
+        appsFilter.onSystemReady(mPmInternal);
+        watcher.verifyChangeReported("systemReady");
+
+        final int systemAppId = Process.FIRST_APPLICATION_UID - 1;
+        final int seesNothingAppId = Process.FIRST_APPLICATION_UID;
+        final int hasProviderAppId = Process.FIRST_APPLICATION_UID + 1;
+        final int queriesProviderAppId = Process.FIRST_APPLICATION_UID + 2;
+
+        PackageSetting system = simulateAddPackage(appsFilter, pkg("some.system.pkg"), systemAppId);
+        watcher.verifyChangeReported("add package");
+        PackageSetting seesNothing = simulateAddPackage(appsFilter, pkg("com.some.package"),
+                seesNothingAppId);
+        watcher.verifyChangeReported("add package");
+        PackageSetting hasProvider = simulateAddPackage(appsFilter,
+                pkgWithProvider("com.some.other.package", "com.some.authority"), hasProviderAppId);
+        watcher.verifyChangeReported("add package");
+        PackageSetting queriesProvider = simulateAddPackage(appsFilter,
+                pkgQueriesProvider("com.yet.some.other.package", "com.some.authority"),
+                queriesProviderAppId);
+        watcher.verifyChangeReported("add package");
+
+        final SparseArray<int[]> systemFilter =
+                appsFilter.getVisibilityAllowList(mSnapshot, system, USER_ARRAY, mExisting);
+        watcher.verifyNoChangeReported("getVisibility");
+        assertThat(toList(systemFilter.get(SYSTEM_USER)),
+                contains(seesNothingAppId, hasProviderAppId, queriesProviderAppId));
+        watcher.verifyNoChangeReported("getVisibility");
+
+        final SparseArray<int[]> seesNothingFilter =
+                appsFilter.getVisibilityAllowList(mSnapshot, seesNothing, USER_ARRAY, mExisting);
+        watcher.verifyNoChangeReported("getVisibility");
+        assertThat(toList(seesNothingFilter.get(SYSTEM_USER)),
+                contains(seesNothingAppId));
+        watcher.verifyNoChangeReported("getVisibility");
+        assertThat(toList(seesNothingFilter.get(SECONDARY_USER)),
+                contains(seesNothingAppId));
+        watcher.verifyNoChangeReported("getVisibility");
+
+        final SparseArray<int[]> hasProviderFilter =
+                appsFilter.getVisibilityAllowList(mSnapshot, hasProvider, USER_ARRAY, mExisting);
+        assertThat(toList(hasProviderFilter.get(SYSTEM_USER)),
+                contains(hasProviderAppId, queriesProviderAppId));
+
+        SparseArray<int[]> queriesProviderFilter =
+                appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
+                        mExisting);
+        watcher.verifyNoChangeReported("getVisibility");
+        assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
+                contains(queriesProviderAppId));
+        watcher.verifyNoChangeReported("getVisibility");
+
+        // provider read
+        appsFilter.grantImplicitAccess(hasProviderAppId, queriesProviderAppId,
+                false /* retainOnUpdate */);
+        watcher.verifyChangeReported("grantImplicitAccess");
+
+        // ensure implicit access is included in the filter
+        queriesProviderFilter =
+                appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
+                        mExisting);
+        watcher.verifyNoChangeReported("getVisibility");
+        assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
+                contains(hasProviderAppId, queriesProviderAppId));
+        watcher.verifyNoChangeReported("getVisibility");
+    }
+
+    @Test
+    public void testOnChangeReport() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
+        watcher.register();
+        simulateAddBasicAndroid(appsFilter);
+        watcher.verifyChangeReported("addBasic");
+        appsFilter.onSystemReady(mPmInternal);
+        watcher.verifyChangeReported("systemReady");
+
+        final int systemAppId = Process.FIRST_APPLICATION_UID - 1;
+        final int seesNothingAppId = Process.FIRST_APPLICATION_UID;
+        final int hasProviderAppId = Process.FIRST_APPLICATION_UID + 1;
+        final int queriesProviderAppId = Process.FIRST_APPLICATION_UID + 2;
+
+        PackageSetting system = simulateAddPackage(appsFilter, pkg("some.system.pkg"), systemAppId);
+        watcher.verifyChangeReported("addPackage");
+        PackageSetting seesNothing = simulateAddPackage(appsFilter, pkg("com.some.package"),
+                seesNothingAppId);
+        watcher.verifyChangeReported("addPackage");
+        PackageSetting hasProvider = simulateAddPackage(appsFilter,
+                pkgWithProvider("com.some.other.package", "com.some.authority"), hasProviderAppId);
+        watcher.verifyChangeReported("addPackage");
+        PackageSetting queriesProvider = simulateAddPackage(appsFilter,
+                pkgQueriesProvider("com.yet.some.other.package", "com.some.authority"),
+                queriesProviderAppId);
+        watcher.verifyChangeReported("addPackage");
+
+        final SparseArray<int[]> systemFilter =
+                appsFilter.getVisibilityAllowList(mSnapshot, system, USER_ARRAY, mExisting);
+        assertThat(toList(systemFilter.get(SYSTEM_USER)),
+                contains(seesNothingAppId, hasProviderAppId, queriesProviderAppId));
+        watcher.verifyNoChangeReported("get");
+
+        final SparseArray<int[]> seesNothingFilter =
+                appsFilter.getVisibilityAllowList(mSnapshot, seesNothing, USER_ARRAY, mExisting);
+        assertThat(toList(seesNothingFilter.get(SYSTEM_USER)),
+                contains(seesNothingAppId));
+        assertThat(toList(seesNothingFilter.get(SECONDARY_USER)),
+                contains(seesNothingAppId));
+        watcher.verifyNoChangeReported("get");
+
+        final SparseArray<int[]> hasProviderFilter =
+                appsFilter.getVisibilityAllowList(mSnapshot, hasProvider, USER_ARRAY, mExisting);
+        assertThat(toList(hasProviderFilter.get(SYSTEM_USER)),
+                contains(hasProviderAppId, queriesProviderAppId));
+        watcher.verifyNoChangeReported("get");
+
+        SparseArray<int[]> queriesProviderFilter =
+                appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
+                        mExisting);
+        assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
+                contains(queriesProviderAppId));
+        watcher.verifyNoChangeReported("get");
+
+        // provider read
+        appsFilter.grantImplicitAccess(
+                hasProviderAppId, queriesProviderAppId, false /* retainOnUpdate */);
+        watcher.verifyChangeReported("grantImplicitAccess");
+
+        // ensure implicit access is included in the filter
+        queriesProviderFilter =
+                appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
+                        mExisting);
+        assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
+                contains(hasProviderAppId, queriesProviderAppId));
+        watcher.verifyNoChangeReported("get");
+
+        // remove a package
+        appsFilter.removePackage(mSnapshot, seesNothing);
+        watcher.verifyChangeReported("removePackage");
+    }
+
+    @Test
+    public void testOnChangeReportedFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange filter");
+        watcher.register();
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
+                DUMMY_TARGET_APPID);
+        PackageSetting instrumentation = simulateAddPackage(appsFilter,
+                pkgWithInstrumentation("com.some.other.package", "com.some.package"),
+                DUMMY_CALLING_APPID);
+        watcher.verifyChangeReported("addPackage");
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+                        target, SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, target,
+                        instrumentation, SYSTEM_USER));
+        watcher.verifyNoChangeReported("shouldFilterApplication");
+    }
+
+    @Test
+    public void testAppsFilterRead() throws Exception {
+        when(mFeatureConfigMock.snapshot()).thenReturn(mFeatureConfigMock);
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
+                DUMMY_TARGET_APPID);
+        PackageSetting instrumentation = simulateAddPackage(appsFilter,
+                pkgWithInstrumentation("com.some.other.package", "com.some.package"),
+                DUMMY_CALLING_APPID);
+
+        final int hasProviderAppId = Process.FIRST_APPLICATION_UID + 1;
+        final int queriesProviderAppId = Process.FIRST_APPLICATION_UID + 2;
+        PackageSetting queriesProvider = simulateAddPackage(appsFilter,
+                pkgQueriesProvider("com.yet.some.other.package", "com.some.authority"),
+                queriesProviderAppId);
+        appsFilter.grantImplicitAccess(
+                hasProviderAppId, queriesProviderAppId, false /* retainOnUpdate */);
+
+        AppsFilterSnapshot snapshot = appsFilter.snapshot();
+        assertFalse(
+                snapshot.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+                        target,
+                        SYSTEM_USER));
+        assertFalse(
+                snapshot.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, target,
+                        instrumentation,
+                        SYSTEM_USER));
+
+        SparseArray<int[]> queriesProviderFilter =
+                snapshot.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY, mExisting);
+        assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)), contains(queriesProviderAppId));
+        assertTrue(snapshot.canQueryPackage(instrumentation.getPkg(),
+                target.getPackageName()));
+
+        // New changes don't affect the snapshot
+        appsFilter.removePackage(mSnapshot, target);
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+                        target,
+                        SYSTEM_USER));
+        assertFalse(
+                snapshot.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+                        target,
+                        SYSTEM_USER));
+
+    }
+
+    @Test
+    public void testSdkSandbox_canSeeForceQueryable() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_APPID,
+                setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+
+        int callingUid = 20123;
+        assertTrue(Process.isSdkSandboxUid(callingUid));
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, callingUid,
+                        null /* callingSetting */, target, SYSTEM_USER));
+    }
+
+    @Test
+    public void testSdkSandbox_cannotSeeNonForceQueryable() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package"), DUMMY_TARGET_APPID,
+                setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+
+        int callingUid = 20123;
+        assertTrue(Process.isSdkSandboxUid(callingUid));
+
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, callingUid,
+                        null /* callingSetting */, target, SYSTEM_USER));
+    }
+
+    @Test
+    public void testSdkSandbox_implicitAccessGranted_canSeePackage() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
+                        mMockHandler);
+        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
+        watcher.register();
+        simulateAddBasicAndroid(appsFilter);
+        watcher.verifyChangeReported("addBasic");
+        appsFilter.onSystemReady(mPmInternal);
+        watcher.verifyChangeReported("systemReady");
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package"), DUMMY_TARGET_APPID,
+                setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+
+        int callingUid = 20123;
+        assertTrue(Process.isSdkSandboxUid(callingUid));
+
+        // Without granting the implicit access the app shouldn't be visible to the sdk sandbox uid.
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, callingUid,
+                        null /* callingSetting */, target, SYSTEM_USER));
+
+        appsFilter.grantImplicitAccess(callingUid, target.getAppId(), false /* retainOnUpdate */);
+        watcher.verifyChangeReported("grantImplicitAccess");
+
+        // After implicit access was granted the app should be visible to the sdk sandbox uid.
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, callingUid,
+                        null /* callingSetting */, target, SYSTEM_USER));
+    }
+
+    @Test
+    public void testUsesPermission_installPermissionDefinerBeforeRequester_DoesntFilter()
+            throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        final PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithCustomPermissions("com.some.package",
+                        "com.some.custom_permission"),
+                DUMMY_TARGET_APPID);
+        final PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package").addUsesPermission(
+                        new ParsedUsesPermissionImpl("com.some.custom_permission", 0)),
+                DUMMY_CALLING_APPID);
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testUsesPermission_installPermissionRequesterBeforeDefiner_DoesntFilter()
+            throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        final PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package").addUsesPermission(
+                        new ParsedUsesPermissionImpl("com.some.custom_permission", 0)),
+                DUMMY_CALLING_APPID);
+
+        final PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithCustomPermissions("com.some.package",
+                        "com.some.custom_permission"),
+                DUMMY_TARGET_APPID);
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testUsesPermission_visibilityFromPermissionDefinerToRequester_Filters()
+            throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        final PackageSetting calling = simulateAddPackage(appsFilter,
+                pkgWithCustomPermissions("com.some.package",
+                        "com.some.custom_permission"),
+                DUMMY_CALLING_APPID);
+
+        final PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package").addUsesPermission(
+                        new ParsedUsesPermissionImpl("com.some.custom_permission", 0)),
+                DUMMY_TARGET_APPID);
+
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+
+    @Test
+    public void testUsesPermission_multipleCustomPermissions() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+        final PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithCustomPermissions("com.some.package",
+                        "com.some.custom_permission1", "com.some.custom_permission2"),
+                DUMMY_TARGET_APPID);
+
+        final PackageSetting calling1 = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package")
+                        .addUsesPermission(new ParsedUsesPermissionImpl(
+                                "com.some.custom_permission1", 0)),
+                DUMMY_CALLING_APPID);
+
+        final PackageSetting calling2 = simulateAddPackage(appsFilter,
+                pkg("com.some.another.package")
+                        .addUsesPermission(new ParsedUsesPermissionImpl(
+                                "com.some.custom_permission2", 0))
+                        .addUsesPermission(new ParsedUsesPermissionImpl(
+                                "com.some.custom_permission3", 0)),
+                DUMMY_CALLING_APPID + 1);
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling1, target,
+                        SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID + 1,
+                        calling2, target, SYSTEM_USER));
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling1,
+                        calling2, SYSTEM_USER));
+    }
+
+    @Test
+    public void testUsesPermission_multiplePermissionDefiners_DoesntFilter() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+
+        final PackageSetting target1 = simulateAddPackage(appsFilter,
+                pkgWithCustomPermissions("com.some.package1", "com.some.custom_permission"),
+                DUMMY_TARGET_APPID);
+        final PackageSetting target2 = simulateAddPackage(appsFilter,
+                pkgWithCustomPermissions("com.some.package2", "com.some.custom_permission"),
+                DUMMY_TARGET_APPID + 1);
+        final PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package")
+                        .addUsesPermission(new ParsedUsesPermissionImpl(
+                                "com.some.custom_permission", 0)),
+                DUMMY_CALLING_APPID);
+
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target1,
+                        SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target2,
+                        SYSTEM_USER));
+
+    }
+
+    @Test
+    public void testNoUsesPermission_Filters() throws Exception {
+        final AppsFilterImpl appsFilter =
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+                        false, /* overlayProvider */ null, mMockHandler);
+
+        simulateAddBasicAndroid(appsFilter);
+        appsFilter.onSystemReady(mPmInternal);
+
+
+        final PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithCustomPermissions("com.some.package", "com.some.custom_permission"),
+                DUMMY_TARGET_APPID);
+        final PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package"),
+                DUMMY_CALLING_APPID);
+
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
+    }
+    private List<Integer> toList(int[] array) {
+        ArrayList<Integer> ret = new ArrayList<>(array.length);
+        for (int i = 0; i < array.length; i++) {
+            ret.add(i, array[i]);
+        }
+        return ret;
+    }
+
+    private interface WithSettingBuilder {
+        PackageSettingBuilder withBuilder(PackageSettingBuilder builder);
+    }
+
+    private void simulateAddBasicAndroid(AppsFilterImpl appsFilter) throws Exception {
+        final Signature frameworkSignature = Mockito.mock(Signature.class);
+        final SigningDetails frameworkSigningDetails =
+                new SigningDetails(new Signature[]{frameworkSignature}, 1);
+        final ParsedPackage android = pkg("android").hideAsParsed();
+        simulateAddPackage(appsFilter, android, 1000,
+                b -> b.setSigningDetails(frameworkSigningDetails));
+    }
+
+    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
+            ParsedPackage newPkgBuilder, int appId) {
+        return simulateAddPackage(filter, newPkgBuilder, appId, null /*settingBuilder*/);
+    }
+
+    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
+            ParsedPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action) {
+        return simulateAddPackage(filter, newPkgBuilder, appId, action, null /*sharedUserSetting*/);
+    }
+
+    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
+            ParsedPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action,
+            @Nullable SharedUserSetting sharedUserSetting) {
+        final PackageSetting setting =
+                getPackageSettingFromParsingPackage(newPkgBuilder, appId, action);
+        simulateAddPackage(setting, filter, sharedUserSetting);
+        return setting;
+    }
+
+    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
+            ParsingPackage newPkgBuilder, int appId) {
+        return simulateAddPackage(filter, newPkgBuilder.hideAsParsed(), appId,
+                null /*settingBuilder*/);
+    }
+
+    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
+            ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action) {
+        return simulateAddPackage(filter, newPkgBuilder.hideAsParsed(), appId, action,
+                null /*sharedUserSetting*/);
+    }
+
+    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
+            ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action,
+            @Nullable SharedUserSetting sharedUserSetting) {
+        return simulateAddPackage(filter, newPkgBuilder.hideAsParsed(), appId, action,
+                sharedUserSetting);
+    }
+
+    private PackageSetting getPackageSettingFromParsingPackage(ParsedPackage newPkgBuilder,
+            int appId, @Nullable WithSettingBuilder action) {
+        AndroidPackage newPkg = newPkgBuilder.hideAsFinal();
+        final PackageSettingBuilder settingBuilder = new PackageSettingBuilder()
+                .setPackage(newPkg)
+                .setAppId(appId)
+                .setName(newPkg.getPackageName())
+                .setCodePath("/")
+                .setPVersionCode(1L);
+        final PackageSetting setting =
+                (action == null ? settingBuilder : action.withBuilder(settingBuilder)).build();
+        return setting;
+    }
+
+    private void simulateAddPackage(PackageSetting setting, AppsFilterImpl filter,
+            @Nullable SharedUserSetting sharedUserSetting) {
+        mExisting.put(setting.getPackageName(), setting);
+        if (sharedUserSetting != null) {
+            sharedUserSetting.addPackage(setting);
+            setting.setSharedUserAppId(sharedUserSetting.mAppId);
+            mSharedUserSettings.add(sharedUserSetting);
+        }
+        filter.addPackage(mSnapshot, setting);
+    }
+
+    private WithSettingBuilder withInstallSource(String initiatingPackageName,
+            String originatingPackageName, String installerPackageName, int installerPackageUid,
+            String installerAttributionTag, boolean isInitiatingPackageUninstalled) {
+        final InstallSource installSource = InstallSource.create(initiatingPackageName,
+                originatingPackageName, installerPackageName, installerPackageUid,
+                installerAttributionTag, /* isOrphaned= */ false, isInitiatingPackageUninstalled);
+        return setting -> setting.setInstallSource(installSource);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BundleUtilsTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/BundleUtilsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/BundleUtilsTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/BundleUtilsTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/CompatibilityModeTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/CompatibilityModeTest.java
new file mode 100644
index 0000000..f0d389b
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/CompatibilityModeTest.java
@@ -0,0 +1,238 @@
+/*
+ * 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.pm;
+
+import static android.content.pm.ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS;
+import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS;
+import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS;
+import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES;
+import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS;
+import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+import android.platform.test.annotations.Presubmit;
+
+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;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+@Presubmit
+public class CompatibilityModeTest {
+
+    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);
+    }
+
+    @After
+    public void tearDown() {
+        setGlobalCompatibilityMode(mCompatibilityModeEnabled);
+    }
+
+    // The following tests ensure that apps with target SDK of Cupcake always use compat mode.
+
+    @Test
+    public void testGlobalCompatModeEnabled_oldApp_supportAllScreens_usesCompatMode() {
+        setGlobalCompatibilityMode(true);
+        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS
+                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
+                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
+        final ApplicationInfo info =
+                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, flags);
+        assertThat(info.usesCompatibilityMode(), is(true));
+    }
+
+    @Test
+    public void testGlobalCompatModeEnabled_oldApp_supportSomeScreens_usesCompatMode() {
+        setGlobalCompatibilityMode(true);
+        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS
+                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
+                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
+        final ApplicationInfo info =
+                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, flags);
+        assertThat(info.usesCompatibilityMode(), is(true));
+    }
+
+    @Test
+    public void testGlobalCompatModeEnabled_oldApp_supportOnlyOneScreen_usesCompatMode() {
+        setGlobalCompatibilityMode(true);
+        final int flags = FLAG_SUPPORTS_NORMAL_SCREENS;
+        final ApplicationInfo info =
+                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, flags);
+        assertThat(info.usesCompatibilityMode(), is(true));
+    }
+
+    @Test
+    public void testGlobalCompatModeEnabled_oldApp_DoesntSupportAllScreens_usesCompatMode() {
+        setGlobalCompatibilityMode(true);
+        final ApplicationInfo info =
+                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, 0 /*flags*/);
+        assertThat(info.usesCompatibilityMode(), is(true));
+    }
+
+    @Test
+    public void testGlobalCompatModeDisabled_oldApp_supportAllScreens_usesCompatMode() {
+        setGlobalCompatibilityMode(false);
+        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS
+                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
+                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
+        final ApplicationInfo info =
+                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, flags);
+        assertThat(info.usesCompatibilityMode(), is(true));
+    }
+
+    @Test
+    public void testGlobalCompatModeDisabled_oldApp_supportSomeScreens_usesCompatMode() {
+        setGlobalCompatibilityMode(false);
+        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS
+                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
+                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
+        final ApplicationInfo info =
+                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, flags);
+        assertThat(info.usesCompatibilityMode(), is(true));
+    }
+
+    @Test
+    public void testGlobalCompatModeDisabled_oldApp_supportOnlyOneScreen_usesCompatMode() {
+        setGlobalCompatibilityMode(false);
+        final int flags = FLAG_SUPPORTS_NORMAL_SCREENS;
+        final ApplicationInfo info =
+                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, flags);
+        assertThat(info.usesCompatibilityMode(), is(true));
+    }
+
+    @Test
+    public void testGlobalCompatModeDisabled_oldApp_doesntSupportAllScreens_usesCompatMode() {
+        setGlobalCompatibilityMode(false);
+        final ApplicationInfo info =
+                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, 0 /*flags*/);
+        assertThat(info.usesCompatibilityMode(), is(true));
+    }
+
+    // The following tests ensure that apps with newer target SDK use compat mode as expected.
+
+    @Test
+    public void testGlobalCompatModeEnabled_newApp_supportAllScreens_doesntUseCompatMode() {
+        setGlobalCompatibilityMode(true);
+        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS
+                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
+                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
+        final ApplicationInfo info = generateMockApplicationInfo(Build.VERSION_CODES.DONUT, flags);
+        assertThat(info.usesCompatibilityMode(), is(false));
+    }
+
+    @Test
+    public void testGlobalCompatModeEnabled_newApp_supportSomeScreens_doesntUseCompatMode() {
+        setGlobalCompatibilityMode(true);
+        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS
+                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
+                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
+        final ApplicationInfo info = generateMockApplicationInfo(Build.VERSION_CODES.DONUT, flags);
+        assertThat(info.usesCompatibilityMode(), is(false));
+    }
+
+    @Test
+    public void testGlobalCompatModeEnabled_newApp_supportOnlyOneScreen_doesntUseCompatMode() {
+        setGlobalCompatibilityMode(true);
+        final int flags = FLAG_SUPPORTS_NORMAL_SCREENS;
+        final ApplicationInfo info = generateMockApplicationInfo(Build.VERSION_CODES.DONUT, flags);
+        assertThat(info.usesCompatibilityMode(), is(false));
+    }
+
+    @Test
+    public void testGlobalCompatModeEnabled_newApp_doesntSupportAllScreens_usesCompatMode() {
+        setGlobalCompatibilityMode(true);
+        final ApplicationInfo info =
+                generateMockApplicationInfo(Build.VERSION_CODES.DONUT, 0 /*flags*/);
+        assertThat(info.usesCompatibilityMode(), is(true));
+    }
+
+    @Test
+    public void testGlobalCompatModeDisabled_newApp_supportAllScreens_doesntUseCompatMode() {
+        setGlobalCompatibilityMode(false);
+        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS
+                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
+                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
+        final ApplicationInfo info = generateMockApplicationInfo(Build.VERSION_CODES.DONUT, flags);
+        assertThat(info.usesCompatibilityMode(), is(false));
+    }
+
+    @Test
+    public void testGlobalCompatModeDisabled_newApp_supportSomeScreens_doesntUseCompatMode() {
+        setGlobalCompatibilityMode(false);
+        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS
+                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
+                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
+        final ApplicationInfo info = generateMockApplicationInfo(Build.VERSION_CODES.DONUT, flags);
+        assertThat(info.usesCompatibilityMode(), is(false));
+    }
+
+    @Test
+    public void testGlobalCompatModeDisabled_newApp_supportOnlyOneScreen_doesntUseCompatMode() {
+        setGlobalCompatibilityMode(false);
+        final int flags = FLAG_SUPPORTS_NORMAL_SCREENS;
+        final ApplicationInfo info = generateMockApplicationInfo(Build.VERSION_CODES.DONUT, flags);
+        assertThat(info.usesCompatibilityMode(), is(false));
+    }
+
+    @Test
+    public void testGlobalCompatModeDisabled_newApp_doesntSupportAllScreens_doesntUseCompatMode() {
+        setGlobalCompatibilityMode(false);
+        final ApplicationInfo info =
+                generateMockApplicationInfo(Build.VERSION_CODES.DONUT, 0 /*flags*/);
+        assertThat(info.usesCompatibilityMode(), is(false));
+    }
+
+    private ApplicationInfo generateMockApplicationInfo(int targetSdkVersion, int flags) {
+        final ApplicationInfo info = new ApplicationInfo();
+        info.targetSdkVersion = targetSdkVersion;
+        info.flags |= flags;
+        when(mMockAndroidPackage.toAppInfoWithoutState()).thenReturn(info);
+        return PackageInfoUtils.generateApplicationInfo(mMockAndroidPackage,
+                0 /*flags*/, mMockUserState, 0 /*userId*/, mMockPackageState);
+    }
+
+    private void setGlobalCompatibilityMode(boolean enabled) {
+        if (ParsingPackageUtils.sCompatibilityModeEnabled == enabled) {
+            return;
+        }
+        ParsingPackageUtils.setCompatibilityModeEnabled(enabled);
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/CrossProfileAppsServiceImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/CrossProfileAppsServiceImplTest.java
new file mode 100644
index 0000000..129efc6
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/CrossProfileAppsServiceImplTest.java
@@ -0,0 +1,781 @@
+package com.android.server.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
+import android.app.AppOpsManager;
+import android.app.IApplicationThread;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.AttributionSourceState;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.PermissionChecker;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.CrossProfileAppsInternal;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.permission.PermissionCheckerManager;
+import android.permission.PermissionManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
+import com.android.server.LocalServices;
+import com.android.server.pm.permission.PermissionManagerService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ * atest PackageManagerServiceServerTests:com.android.server.pm.CrossProfileAppsServiceImplTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class CrossProfileAppsServiceImplTest {
+    private static final String PACKAGE_ONE = "com.one";
+    private static final String FEATURE_ID = "feature.one";
+    private static final int PACKAGE_ONE_UID = 1111;
+    private static final ComponentName ACTIVITY_COMPONENT =
+            new ComponentName("com.one", "test");
+
+    private static final String PACKAGE_TWO = "com.two";
+    private static final int PACKAGE_TWO_UID = 2222;
+
+    private static final int PRIMARY_USER = 0;
+    private static final int PROFILE_OF_PRIMARY_USER = 10;
+    private static final int SECONDARY_USER = 11;
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private PackageManagerInternal mPackageManagerInternal;
+    @Mock
+    private AppOpsManager mAppOpsManager;
+    @Mock
+    private ActivityManagerInternal mActivityManagerInternal;
+    @Mock
+    private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+    @Mock
+    private IPackageManager mIPackageManager;
+    @Mock
+    private DevicePolicyManagerInternal mDevicePolicyManagerInternal;
+
+    private TestInjector mTestInjector;
+    private ActivityInfo mActivityInfo;
+    private CrossProfileAppsServiceImpl mCrossProfileAppsServiceImpl;
+    private IApplicationThread mIApplicationThread;
+
+    private SparseArray<Boolean> mUserEnabled = new SparseArray<>();
+
+    @Before
+    public void initCrossProfileAppsServiceImpl() {
+        mTestInjector = new TestInjector();
+        LocalServices.removeServiceForTest(CrossProfileAppsInternal.class);
+        mCrossProfileAppsServiceImpl = new CrossProfileAppsServiceImpl(mContext, mTestInjector);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+    }
+
+    @Before
+    public void setupEnabledProfiles() {
+        mUserEnabled.put(PRIMARY_USER, true);
+        mUserEnabled.put(PROFILE_OF_PRIMARY_USER, true);
+        mUserEnabled.put(SECONDARY_USER, true);
+
+        when(mUserManager.getEnabledProfileIds(anyInt())).thenAnswer(
+                invocation -> {
+                    List<Integer> users = new ArrayList<>();
+                    final int targetUser = invocation.getArgument(0);
+                    users.add(targetUser);
+
+                    int profileUserId = -1;
+                    if (targetUser == PRIMARY_USER) {
+                        profileUserId = PROFILE_OF_PRIMARY_USER;
+                    } else if (targetUser == PROFILE_OF_PRIMARY_USER) {
+                        profileUserId = PRIMARY_USER;
+                    }
+
+                    if (profileUserId != -1 && mUserEnabled.get(profileUserId)) {
+                        users.add(profileUserId);
+                    }
+                    return users.stream().mapToInt(i -> i).toArray();
+                });
+    }
+
+    @Before
+    public void setupCaller() {
+        mTestInjector.setCallingUid(PACKAGE_ONE_UID);
+        mTestInjector.setCallingUserId(PRIMARY_USER);
+    }
+
+    @Before
+    public void setupPackage() throws Exception {
+        // PACKAGE_ONE are installed in all users.
+        mockAppsInstalled(PACKAGE_ONE, PRIMARY_USER, true);
+        mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, true);
+        mockAppsInstalled(PACKAGE_ONE, SECONDARY_USER, true);
+
+        // Packages are resolved to their corresponding UID.
+        doAnswer(invocation -> {
+            final int uid = invocation.getArgument(0);
+            final String packageName = invocation.getArgument(1);
+            if (uid == PACKAGE_ONE_UID && PACKAGE_ONE.equals(packageName)) {
+                return null;
+            } else if (uid ==PACKAGE_TWO_UID && PACKAGE_TWO.equals(packageName)) {
+                return null;
+            }
+            throw new SecurityException("Not matching");
+        }).when(mAppOpsManager).checkPackage(anyInt(), anyString());
+
+        // The intent is resolved to the ACTIVITY_COMPONENT.
+        mockActivityLaunchIntentResolvedTo(ACTIVITY_COMPONENT);
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromPrimaryUser_installed() throws Exception {
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).containsExactly(UserHandle.of(PROFILE_OF_PRIMARY_USER));
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromPrimaryUser_notInstalled() throws Exception {
+        mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).isEmpty();
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromPrimaryUser_userNotEnabled() throws Exception {
+        mUserEnabled.put(PROFILE_OF_PRIMARY_USER, false);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).isEmpty();
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromSecondaryUser() throws Exception {
+        mTestInjector.setCallingUserId(SECONDARY_USER);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).isEmpty();
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromProfile_installed() throws Exception {
+        mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).containsExactly(UserHandle.of(PRIMARY_USER));
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromProfile_notInstalled() throws Exception {
+        mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+        mockAppsInstalled(PACKAGE_ONE, PRIMARY_USER, false);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).isEmpty();
+    }
+
+    @Test(expected = SecurityException.class)
+    public void getTargetUserProfiles_fakeCaller() throws Exception {
+        mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_TWO);
+    }
+
+    @Test
+    public void startActivityAsUser_currentUser() throws Exception {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_currentUser() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_profile_notInstalled() throws Exception {
+        mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_notInstalled() {
+        mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_profile_fakeCaller() throws Exception {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_TWO,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_fakeCaller() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_TWO,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_profile_notExported() throws Exception {
+        mActivityInfo.exported = false;
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_notExported() {
+        try {
+            when(mPackageManager.getPermissionInfo(anyString(), anyInt()))
+                    .thenReturn(new PermissionInfo());
+        } catch (PackageManager.NameNotFoundException ignored) {
+        }
+        mActivityInfo.exported = false;
+
+
+        // There's a bug in static mocking if the APK is large - so here is the next best thing...
+        doReturn(Context.PERMISSION_CHECKER_SERVICE).when(mContext)
+                .getSystemServiceName(PermissionCheckerManager.class);
+        PermissionCheckerManager permissionCheckerManager = mock(PermissionCheckerManager.class);
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(permissionCheckerManager)
+                .checkPermission(eq(Manifest.permission.INTERACT_ACROSS_PROFILES), any(
+                        AttributionSourceState.class), anyString(), anyBoolean(), anyBoolean(),
+                        anyBoolean(), anyInt());
+        doReturn(permissionCheckerManager).when(mContext).getSystemService(
+                Context.PERMISSION_CHECKER_SERVICE);
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_profile_anotherPackage() throws Exception {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                new ComponentName(PACKAGE_TWO, "test"),
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_anotherPackage() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                new ComponentName(PACKAGE_TWO, "test"),
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_secondaryUser() throws Exception {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(SECONDARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_secondaryUser() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(SECONDARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_fromProfile_success() throws Exception {
+        mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                mIApplicationThread,
+                PACKAGE_ONE,
+                FEATURE_ID,
+                ACTIVITY_COMPONENT,
+                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                true,
+                /* targetTask */ null,
+                /* options */ null);
+
+        verify(mActivityTaskManagerInternal)
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        eq(PACKAGE_ONE),
+                        eq(FEATURE_ID),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        eq(PRIMARY_USER));
+    }
+
+    @Test
+    public void startActivityAsUser_sameTask_fromProfile_success() throws Exception {
+        mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+        Bundle options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
+        Binder targetTask = new Binder();
+        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                mIApplicationThread,
+                PACKAGE_ONE,
+                FEATURE_ID,
+                ACTIVITY_COMPONENT,
+                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                true,
+                targetTask,
+                options);
+        verify(mActivityTaskManagerInternal)
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        eq(PACKAGE_ONE),
+                        eq(FEATURE_ID),
+                        any(Intent.class),
+                        eq(targetTask),
+                        anyInt(),
+                        eq(options),
+                        eq(PRIMARY_USER));
+    }
+
+    private void mockAppsInstalled(String packageName, int user, boolean installed) {
+        when(mPackageManagerInternal.getPackageInfo(
+                eq(packageName),
+                anyLong(),
+                anyInt(),
+                eq(user)))
+                .thenReturn(installed ? createInstalledPackageInfo() : null);
+    }
+
+    private PackageInfo createInstalledPackageInfo() {
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.applicationInfo = new ApplicationInfo();
+        packageInfo.applicationInfo.enabled = true;
+        return packageInfo;
+    }
+
+    private void mockActivityLaunchIntentResolvedTo(ComponentName componentName) {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = componentName.getPackageName();
+        activityInfo.name = componentName.getClassName();
+        activityInfo.exported = true;
+        resolveInfo.activityInfo = activityInfo;
+        mActivityInfo = activityInfo;
+
+        when(mPackageManagerInternal.queryIntentActivities(
+                any(Intent.class), nullable(String.class), anyLong(), anyInt(), anyInt()))
+                .thenReturn(Collections.singletonList(resolveInfo));
+    }
+
+    private class TestInjector implements CrossProfileAppsServiceImpl.Injector {
+        private int mCallingUid;
+        private int mCallingUserId;
+        private int mCallingPid;
+
+        public void setCallingUid(int uid) {
+            mCallingUid = uid;
+        }
+
+        public void setCallingPid(int pid) {
+            mCallingPid = pid;
+        }
+
+        public void setCallingUserId(int userId) {
+            mCallingUserId = userId;
+        }
+
+        @Override
+        public int getCallingUid() {
+            return mCallingUid;
+        }
+
+        @Override
+        public int getCallingPid() {
+            return mCallingPid;
+        }
+
+        @Override
+        public int getCallingUserId() {
+            return mCallingUserId;
+        }
+
+        @Override
+        public UserHandle getCallingUserHandle() {
+            return UserHandle.of(mCallingUserId);
+        }
+
+        @Override
+        public long clearCallingIdentity() {
+            return 0;
+        }
+
+        @Override
+        public void restoreCallingIdentity(long token) {
+        }
+
+        @Override
+        public void withCleanCallingIdentity(ThrowingRunnable action) {
+            action.run();
+        }
+
+        @Override
+        public <T> T withCleanCallingIdentity(ThrowingSupplier<T> action) {
+            return action.get();
+        }
+
+        @Override
+        public UserManager getUserManager() {
+            return mUserManager;
+        }
+
+        @Override
+        public PackageManagerInternal getPackageManagerInternal() {
+            return mPackageManagerInternal;
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mPackageManager;
+        }
+
+        @Override
+        public AppOpsManager getAppOpsManager() {
+            return mAppOpsManager;
+        }
+
+        @Override
+        public ActivityManagerInternal getActivityManagerInternal() {
+            return mActivityManagerInternal;
+        }
+
+        @Override
+        public ActivityTaskManagerInternal getActivityTaskManagerInternal() {
+            return mActivityTaskManagerInternal;
+        }
+
+        @Override
+        public IPackageManager getIPackageManager() {
+            return mIPackageManager;
+        }
+
+        @Override
+        public DevicePolicyManagerInternal getDevicePolicyManagerInternal() {
+            return mDevicePolicyManagerInternal;
+        }
+
+        @Override
+        public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+            mContext.sendBroadcastAsUser(intent, user);
+        }
+
+        @Override
+        public int checkComponentPermission(
+                String permission, int uid, int owningUid, boolean exported) {
+            return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported);
+        }
+
+        @Override
+        public void killUid(int uid) {
+            PermissionManagerService.killUid(
+                    UserHandle.getAppId(uid),
+                    UserHandle.getUserId(uid),
+                    PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/InstallerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/InstallerTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/InstallerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetManagerServiceTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetManagerServiceTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/KeySetStrings.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetStrings.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/KeySetStrings.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetStrings.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/KeySetUtils.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetUtils.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/KeySetUtils.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetUtils.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/ModuleInfoProviderTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/ModuleInfoProviderTest.java
new file mode 100644
index 0000000..ad58507
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/ModuleInfoProviderTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.pm;
+
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.content.Context;
+import android.content.pm.ModuleInfo;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.Presubmit;
+import android.test.InstrumentationTestCase;
+
+import com.android.server.pm.test.service.server.R;
+
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.List;
+
+@Presubmit
+public class ModuleInfoProviderTest extends InstrumentationTestCase {
+
+    @Mock private ApexManager mApexManager;
+
+    public void setUp() {
+        initMocks(this);
+    }
+
+    public void testSuccessfulParse() {
+        when(mApexManager.getApexModuleNameForPackageName("com.android.module1"))
+                .thenReturn("com.module1.apex");
+        when(mApexManager.getApexModuleNameForPackageName("com.android.module2"))
+                .thenReturn("com.module2.apex");
+
+        ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata);
+
+        List<ModuleInfo> mi = provider.getInstalledModules(PackageManager.MATCH_ALL);
+        assertEquals(2, mi.size());
+
+        Collections.sort(mi, (ModuleInfo m1, ModuleInfo m2) ->
+                m1.getPackageName().compareTo(m1.getPackageName()));
+        assertEquals("com.android.module1", mi.get(0).getPackageName());
+        assertEquals("com.android.module2", mi.get(1).getPackageName());
+
+        ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0);
+        assertEquals("com.android.module1", mi1.getPackageName());
+        assertEquals("module_1_name", mi1.getName());
+        assertEquals("com.module1.apex", mi1.getApexModuleName());
+        assertEquals(false, mi1.isHidden());
+
+        ModuleInfo mi2 = provider.getModuleInfo("com.android.module2", 0);
+        assertEquals("com.android.module2", mi2.getPackageName());
+        assertEquals("module_2_name", mi2.getName());
+        assertEquals("com.module2.apex", mi2.getApexModuleName());
+        assertEquals(true, mi2.isHidden());
+    }
+
+    public void testParseFailure_incorrectTopLevelElement() {
+        ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata1);
+        assertEquals(0, provider.getInstalledModules(PackageManager.MATCH_ALL).size());
+    }
+
+    public void testParseFailure_incorrectModuleElement() {
+        ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata2);
+        assertEquals(0, provider.getInstalledModules(PackageManager.MATCH_ALL).size());
+    }
+
+    public void testParse_unknownAttributesIgnored() {
+        ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata);
+
+        List<ModuleInfo> mi = provider.getInstalledModules(PackageManager.MATCH_ALL);
+        assertEquals(2, mi.size());
+
+        ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0);
+        assertEquals("com.android.module1", mi1.getPackageName());
+        assertEquals("module_1_name", mi1.getName());
+        assertEquals(false, mi1.isHidden());
+    }
+
+    /**
+     * Constructs an {@code ModuleInfoProvider} using the test package resources.
+     */
+    private ModuleInfoProvider getProvider(int resourceId) {
+        final Context ctx = getInstrumentation().getContext();
+        return new ModuleInfoProvider(
+                ctx.getResources().getXml(resourceId), ctx.getResources(), mApexManager);
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageInstallerSessionTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageInstallerSessionTest.java
new file mode 100644
index 0000000..4da082e
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageInstallerSessionTest.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import libcore.io.IoUtils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class PackageInstallerSessionTest {
+    @Rule
+    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    private File mTmpDir;
+    private AtomicFile mSessionsFile;
+    private static final String TAG_SESSIONS = "sessions";
+
+    @Mock
+    PackageManagerService mMockPackageManagerInternal;
+
+    @Mock
+    Computer mSnapshot;
+
+    @Before
+    public void setUp() throws Exception {
+        mTmpDir = mTemporaryFolder.newFolder("PackageInstallerSessionTest");
+        mSessionsFile = new AtomicFile(
+                new File(mTmpDir.getAbsolutePath() + "/sessions.xml"), "package-session");
+        MockitoAnnotations.initMocks(this);
+        when(mSnapshot.getPackageUid(anyString(), anyLong(), anyInt())).thenReturn(0);
+        when(mMockPackageManagerInternal.snapshotComputer()).thenReturn(mSnapshot);
+    }
+
+    @Test
+    public void testWriteAndRestoreSessionXmlSimpleSession() {
+        PackageInstallerSession session = createSimpleSession();
+        dumpSession(session);
+        List<PackageInstallerSession> restored = restoreSessions();
+        assertEquals(1, restored.size());
+        assertSessionsEquivalent(session, restored.get(0));
+    }
+
+    @Test
+    public void testWriteAndRestoreSessionXmlStagedSession() {
+        PackageInstallerSession session = createStagedSession();
+        dumpSession(session);
+        List<PackageInstallerSession> restored = restoreSessions();
+        assertEquals(1, restored.size());
+        assertSessionsEquivalent(session, restored.get(0));
+    }
+
+    @Test
+    public void testWriteAndRestoreSessionXmlGrantedPermission() {
+        PackageInstallerSession session = createSessionWithGrantedPermissions();
+        dumpSession(session);
+        List<PackageInstallerSession> restored = restoreSessions();
+        assertEquals(1, restored.size());
+        assertSessionsEquivalent(session, restored.get(0));
+    }
+
+    @Test
+    public void testWriteAndRestoreSessionXmlMultiPackageSessions() {
+        PackageInstallerSession session = createMultiPackageParentSession(123, new int[]{234, 345});
+        PackageInstallerSession childSession1 = createMultiPackageChildSession(234, 123);
+        PackageInstallerSession childSession2 = createMultiPackageChildSession(345, 123);
+        List<PackageInstallerSession> sessionGroup =
+                Arrays.asList(session, childSession1, childSession2);
+        dumpSessions(sessionGroup);
+        List<PackageInstallerSession> restored = restoreSessions();
+        assertEquals(3, restored.size());
+        assertSessionsEquivalent(sessionGroup, restored);
+    }
+
+    private PackageInstallerSession createSimpleSession() {
+        return createSession(false, false, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
+                null);
+    }
+
+    private PackageInstallerSession createStagedSession() {
+        return createSession(true, false, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
+                null);
+    }
+
+    private PackageInstallerSession createSessionWithGrantedPermissions() {
+        return createSession(false, true, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
+                null);
+    }
+
+    private PackageInstallerSession createMultiPackageParentSession(int sessionId,
+                                                                    int[] childSessionIds) {
+        return createSession(false, false, sessionId, true,
+                PackageInstaller.SessionInfo.INVALID_ID, childSessionIds);
+    }
+
+    private PackageInstallerSession createMultiPackageChildSession(int sessionId,
+                                                                   int parentSessionId) {
+        return createSession(false, false, sessionId, false, parentSessionId, null);
+    }
+
+    private PackageInstallerSession createSession(boolean staged, boolean withGrantedPermissions,
+                                                  int sessionId, boolean isMultiPackage,
+                                                  int parentSessionId, int[] childSessionIds) {
+        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        if (staged) {
+            params.isStaged = true;
+        }
+        if (withGrantedPermissions) {
+            params.grantedRuntimePermissions = new String[]{"permission1", "permission2"};
+        }
+        if (isMultiPackage) {
+            params.isMultiPackage = true;
+        }
+        InstallSource installSource = InstallSource.create("testInstallInitiator",
+                "testInstallOriginator", "testInstaller", -1, "testAttributionTag",
+                PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
+        return new PackageInstallerSession(
+                /* callback */ null,
+                /* context */null,
+                /* pm */ mMockPackageManagerInternal,
+                /* sessionProvider */ null,
+                /* silentUpdatePolicy */ null,
+                /* looper */ BackgroundThread.getHandler().getLooper(),
+                /* stagingManager */ null,
+                /* sessionId */ sessionId,
+                /* userId */  456,
+                /* installerUid */ -1,
+                /* installSource */ installSource,
+                /* sessionParams */ params,
+                /* createdMillis */ 0L,
+                /* committedMillis */ 0L,
+                /* stageDir */ mTmpDir,
+                /* stageCid */ null,
+                /* files */ null,
+                /* checksums */ null,
+                /* prepared */ true,
+                /* committed */ true,
+                /* destroyed */ staged ? true : false,
+                /* sealed */ false,  // Setting to true would trigger some PM logic.
+                /* childSessionIds */ childSessionIds != null ? childSessionIds : new int[0],
+                /* parentSessionId */ parentSessionId,
+                /* isReady */ staged ? true : false,
+                /* isFailed */ false,
+                /* isApplied */false,
+                /* stagedSessionErrorCode */
+                PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
+                /* stagedSessionErrorMessage */ "some error");
+    }
+
+    private void dumpSession(PackageInstallerSession session) {
+        dumpSessions(Arrays.asList(session));
+    }
+
+    private void dumpSessions(List<PackageInstallerSession> sessions) {
+        FileOutputStream fos = null;
+        try {
+            fos = mSessionsFile.startWrite();
+
+            TypedXmlSerializer out = Xml.resolveSerializer(fos);
+            out.startDocument(null, true);
+            out.startTag(null, TAG_SESSIONS);
+            for (PackageInstallerSession session : sessions) {
+                session.write(out, mTmpDir);
+            }
+            out.endTag(null, TAG_SESSIONS);
+            out.endDocument();
+
+            mSessionsFile.finishWrite(fos);
+            Slog.d("PackageInstallerSessionTest", new String(mSessionsFile.readFully()));
+        } catch (IOException e) {
+            if (fos != null) {
+                mSessionsFile.failWrite(fos);
+            }
+        }
+    }
+
+    // This is roughly the logic used in PackageInstallerService to read the session. Note that
+    // this test stresses readFromXml method from PackageInstallerSession, and doesn't cover the
+    // PackageInstallerService portion of the parsing.
+    private List<PackageInstallerSession> restoreSessions() {
+        List<PackageInstallerSession> ret = new ArrayList<>();
+        FileInputStream fis = null;
+        try {
+            fis = mSessionsFile.openRead();
+            TypedXmlPullParser in = Xml.resolvePullParser(fis);
+
+            int type;
+            while ((type = in.next()) != END_DOCUMENT) {
+                if (type == START_TAG) {
+                    final String tag = in.getName();
+                    if (PackageInstallerSession.TAG_SESSION.equals(tag)) {
+                        final PackageInstallerSession session;
+                        try {
+                            session = PackageInstallerSession.readFromXml(in, null,
+                                    null, mMockPackageManagerInternal,
+                                    BackgroundThread.getHandler().getLooper(), null,
+                                    mTmpDir, null, null);
+                            ret.add(session);
+                        } catch (Exception e) {
+                            Slog.e("PackageInstallerSessionTest", "Exception ", e);
+                            continue;
+                        }
+                    }
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // Missing sessions are okay, probably first boot
+        } catch (IOException | XmlPullParserException e) {
+
+        } finally {
+            IoUtils.closeQuietly(fis);
+        }
+        return ret;
+    }
+
+    private void assertSessionParamsEquivalent(PackageInstaller.SessionParams expected,
+                                               PackageInstaller.SessionParams actual) {
+        assertEquals(expected.mode, actual.mode);
+        assertEquals(expected.installFlags, actual.installFlags);
+        assertEquals(expected.installLocation, actual.installLocation);
+        assertEquals(expected.installReason, actual.installReason);
+        assertEquals(expected.sizeBytes, actual.sizeBytes);
+        assertEquals(expected.appPackageName, actual.appPackageName);
+        assertEquals(expected.appIcon, actual.appIcon);
+        assertEquals(expected.originatingUri, actual.originatingUri);
+        assertEquals(expected.originatingUid, actual.originatingUid);
+        assertEquals(expected.referrerUri, actual.referrerUri);
+        assertEquals(expected.abiOverride, actual.abiOverride);
+        assertEquals(expected.volumeUuid, actual.volumeUuid);
+        assertArrayEquals(expected.grantedRuntimePermissions, actual.grantedRuntimePermissions);
+        assertEquals(expected.installerPackageName, actual.installerPackageName);
+        assertEquals(expected.isMultiPackage, actual.isMultiPackage);
+        assertEquals(expected.isStaged, actual.isStaged);
+    }
+
+    private void assertSessionsEquivalent(List<PackageInstallerSession> expected,
+                                          List<PackageInstallerSession> actual) {
+        assertEquals(expected.size(), actual.size());
+        for (PackageInstallerSession expectedSession : expected) {
+            boolean foundSession = false;
+            for (PackageInstallerSession actualSession : actual) {
+                if (expectedSession.sessionId == actualSession.sessionId) {
+                    // We should only encounter each expected session once.
+                    assertFalse(foundSession);
+                    foundSession = true;
+                    assertSessionsEquivalent(expectedSession, actualSession);
+                }
+            }
+            assertTrue(foundSession);
+        }
+    }
+
+    private void assertSessionsEquivalent(PackageInstallerSession expected,
+                                          PackageInstallerSession actual) {
+        assertEquals(expected.sessionId, actual.sessionId);
+        assertEquals(expected.userId, actual.userId);
+        assertSessionParamsEquivalent(expected.params, actual.params);
+        assertEquals(expected.getInstallerUid(), actual.getInstallerUid());
+        assertEquals(expected.getInstallerPackageName(), actual.getInstallerPackageName());
+        assertInstallSourcesEquivalent(expected.getInstallSource(), actual.getInstallSource());
+        assertEquals(expected.stageDir.getAbsolutePath(), actual.stageDir.getAbsolutePath());
+        assertEquals(expected.stageCid, actual.stageCid);
+        assertEquals(expected.isPrepared(), actual.isPrepared());
+        assertEquals(expected.isStaged(), actual.isStaged());
+        assertEquals(expected.isSessionApplied(), actual.isSessionApplied());
+        assertEquals(expected.isSessionFailed(), actual.isSessionFailed());
+        assertEquals(expected.isSessionReady(), actual.isSessionReady());
+        assertEquals(expected.getSessionErrorCode(), actual.getSessionErrorCode());
+        assertEquals(expected.getSessionErrorMessage(),
+                actual.getSessionErrorMessage());
+        assertEquals(expected.isPrepared(), actual.isPrepared());
+        assertEquals(expected.isCommitted(), actual.isCommitted());
+        assertEquals(expected.isPreapprovalRequested(), actual.isPreapprovalRequested());
+        assertEquals(expected.createdMillis, actual.createdMillis);
+        assertEquals(expected.isSealed(), actual.isSealed());
+        assertEquals(expected.isMultiPackage(), actual.isMultiPackage());
+        assertEquals(expected.hasParentSessionId(), actual.hasParentSessionId());
+        assertEquals(expected.getParentSessionId(), actual.getParentSessionId());
+        assertArrayEquals(expected.getChildSessionIds(), actual.getChildSessionIds());
+    }
+
+    private void assertInstallSourcesEquivalent(InstallSource expected, InstallSource actual) {
+        assertEquals(expected.mInstallerPackageName, actual.mInstallerPackageName);
+        assertEquals(expected.mInitiatingPackageName, actual.mInitiatingPackageName);
+        assertEquals(expected.mOriginatingPackageName, actual.mOriginatingPackageName);
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerServiceTest.java
new file mode 100644
index 0000000..b82ffb4
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerServiceTest.java
@@ -0,0 +1,616 @@
+/*
+ * 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.server.pm;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
+import static java.lang.reflect.Modifier.isFinal;
+import static java.lang.reflect.Modifier.isPublic;
+import static java.lang.reflect.Modifier.isStatic;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.content.IIntentReceiver;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.Postsubmit;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.HexDump;
+import com.android.server.pm.PerPackageReadTimeouts.Timeouts;
+import com.android.server.pm.PerPackageReadTimeouts.VersionCodes;
+
+import com.google.android.collect.Lists;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+// atest PackageManagerServiceTest
+@Postsubmit
+@RunWith(AndroidJUnit4.class)
+public class PackageManagerServiceTest {
+
+    private static final String PACKAGE_NAME = "com.android.frameworks.servicestests";
+
+    private static final String TEST_DATA_PATH = "/data/local/tmp/servicestests/";
+    private static final String TEST_APP_APK = "StubTestApp.apk";
+    private static final String TEST_PKG_NAME = "com.android.servicestests.apps.stubapp";
+
+    private IPackageManager mIPackageManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mIPackageManager = AppGlobals.getPackageManager();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+
+    @Test
+    public void testPackageRemoval() {
+        class PackageSenderImpl implements PackageSender {
+            public void sendPackageBroadcast(final String action, final String pkg,
+                    final Bundle extras, final int flags, final String targetPkg,
+                    final IIntentReceiver finishedReceiver, final int[] userIds,
+                    int[] instantUserIds, SparseArray<int[]> broadcastAllowList,
+                    @Nullable Bundle bOptions) {
+            }
+
+            public void sendPackageAddedForNewUsers(@NonNull Computer snapshot, String packageName,
+                    boolean sendBootComplete, boolean includeStopped, int appId,
+                    int[] userIds, int[] instantUserIds, int dataLoaderType) {
+            }
+
+            @Override
+            public void notifyPackageAdded(String packageName, int uid) {
+            }
+
+            @Override
+            public void notifyPackageChanged(String packageName, int uid) {
+
+            }
+
+            @Override
+            public void notifyPackageRemoved(String packageName, int uid) {
+            }
+        }
+
+        PackageSenderImpl sender = new PackageSenderImpl();
+        PackageSetting setting = null;
+        PackageRemovedInfo pri = new PackageRemovedInfo(sender);
+
+        // Initial conditions: nothing there
+        Assert.assertNull(pri.mRemovedUsers);
+        Assert.assertNull(pri.mBroadcastUsers);
+
+        // populateUsers with nothing leaves nothing
+        pri.populateUsers(null, setting);
+        Assert.assertNull(pri.mBroadcastUsers);
+
+        // Create a real (non-null) PackageSetting and confirm that the removed
+        // users are copied properly
+        setting = new PackageSettingBuilder()
+                .setName("name")
+                .setRealName("realName")
+                .setCodePath("codePath")
+                .setLegacyNativeLibraryPathString("legacyNativeLibraryPathString")
+                .setPrimaryCpuAbiString("primaryCpuAbiString")
+                .setSecondaryCpuAbiString("secondaryCpuAbiString")
+                .setCpuAbiOverrideString("cpuAbiOverrideString")
+                .build();
+        pri.populateUsers(new int[] {
+                1, 2, 3, 4, 5
+        }, setting);
+        Assert.assertNotNull(pri.mBroadcastUsers);
+        Assert.assertEquals(5, pri.mBroadcastUsers.length);
+        Assert.assertNotNull(pri.mInstantUserIds);
+        Assert.assertEquals(0, pri.mInstantUserIds.length);
+
+        // Exclude a user
+        pri.mBroadcastUsers = null;
+        final int EXCLUDED_USER_ID = 4;
+        setting.setInstantApp(true, EXCLUDED_USER_ID);
+        pri.populateUsers(new int[] {
+                1, 2, 3, EXCLUDED_USER_ID, 5
+        }, setting);
+        Assert.assertNotNull(pri.mBroadcastUsers);
+        Assert.assertEquals(4, pri.mBroadcastUsers.length);
+        Assert.assertNotNull(pri.mInstantUserIds);
+        Assert.assertEquals(1, pri.mInstantUserIds.length);
+
+        // TODO: test that sendApplicationHiddenForUser() actually fills in
+        // broadcastUsers
+    }
+
+    @Test
+    public void testPartitions() {
+        String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" };
+        String[] appdir = { "app", "priv-app" };
+        for (int i = 0; i < partitions.length; i++) {
+            final ScanPartition scanPartition =
+                    PackageManagerService.SYSTEM_PARTITIONS.get(i);
+            for (int j = 0; j < appdir.length; j++) {
+                File path = new File(String.format("%s/%s/A.apk", partitions[i], appdir[j]));
+                Assert.assertEquals(j == 1 && i != 3, scanPartition.containsPrivApp(path));
+
+                final int scanFlag = scanPartition.scanFlag;
+                Assert.assertEquals(i == 1, scanFlag == PackageManagerService.SCAN_AS_VENDOR);
+                Assert.assertEquals(i == 2, scanFlag == PackageManagerService.SCAN_AS_ODM);
+                Assert.assertEquals(i == 3, scanFlag == PackageManagerService.SCAN_AS_OEM);
+                Assert.assertEquals(i == 4, scanFlag == PackageManagerService.SCAN_AS_PRODUCT);
+                Assert.assertEquals(i == 5, scanFlag == PackageManagerService.SCAN_AS_SYSTEM_EXT);
+            }
+        }
+    }
+
+    @Test
+    public void testKnownPackageToString_shouldNotGetUnknown() {
+        final List<String> packageNames = new ArrayList<>();
+        for (int i = 0; i <= KnownPackages.LAST_KNOWN_PACKAGE; i++) {
+            packageNames.add(KnownPackages.knownPackageToString(i));
+        }
+        assertWithMessage(
+                "The Ids of KnownPackage should be continuous and the string representation "
+                        + "should not be unknown.").that(
+                packageNames).containsNoneIn(Lists.newArrayList("Unknown"));
+    }
+
+    @Test
+    public void testKnownPackage_lastKnownPackageIsTheLast() throws Exception {
+        final List<Integer> knownPackageIds = getKnownPackageIdsList();
+        assertWithMessage(
+                "The last KnownPackage Id should be assigned to PackageManagerInternal"
+                        + ".LAST_KNOWN_PACKAGE.").that(
+                knownPackageIds.get(knownPackageIds.size() - 1)).isEqualTo(
+                KnownPackages.LAST_KNOWN_PACKAGE);
+    }
+
+    @Test
+    public void testKnownPackage_IdsShouldBeUniqueAndContinuous() throws Exception {
+        final List<Integer> knownPackageIds = getKnownPackageIdsList();
+        for (int i = 0, size = knownPackageIds.size(); i < size - 1; i++) {
+            assertWithMessage(
+                    "The KnownPackage Ids should be unique and continuous. KnownPackageIds = "
+                            + Arrays.toString(knownPackageIds.toArray())).that(
+                    knownPackageIds.get(i) + 1).isEqualTo(knownPackageIds.get(i + 1));
+        }
+    }
+
+    @Test
+    public void testTimeouts() {
+        Timeouts defaults = Timeouts.parse("3600000001:3600000002:3600000003");
+        Assert.assertEquals(3600000001L, defaults.minTimeUs);
+        Assert.assertEquals(3600000002L, defaults.minPendingTimeUs);
+        Assert.assertEquals(3600000003L, defaults.maxPendingTimeUs);
+
+        Timeouts empty = Timeouts.parse("");
+        Assert.assertEquals(3600000000L, empty.minTimeUs);
+        Assert.assertEquals(3600000000L, empty.minPendingTimeUs);
+        Assert.assertEquals(3600000000L, empty.maxPendingTimeUs);
+
+        Timeouts partial0 = Timeouts.parse("10000::");
+        Assert.assertEquals(10000L, partial0.minTimeUs);
+        Assert.assertEquals(3600000000L, partial0.minPendingTimeUs);
+        Assert.assertEquals(3600000000L, partial0.maxPendingTimeUs);
+
+        Timeouts partial1 = Timeouts.parse("10000:10001:");
+        Assert.assertEquals(10000L, partial1.minTimeUs);
+        Assert.assertEquals(10001L, partial1.minPendingTimeUs);
+        Assert.assertEquals(3600000000L, partial1.maxPendingTimeUs);
+
+        Timeouts fullDefault = Timeouts.parse("3600000000:3600000000:3600000000");
+        Assert.assertEquals(3600000000L, fullDefault.minTimeUs);
+        Assert.assertEquals(3600000000L, fullDefault.minPendingTimeUs);
+        Assert.assertEquals(3600000000L, fullDefault.maxPendingTimeUs);
+
+        Timeouts full = Timeouts.parse("10000:10001:10002");
+        Assert.assertEquals(10000L, full.minTimeUs);
+        Assert.assertEquals(10001L, full.minPendingTimeUs);
+        Assert.assertEquals(10002L, full.maxPendingTimeUs);
+
+        Timeouts invalid0 = Timeouts.parse(":10000");
+        Assert.assertEquals(3600000000L, invalid0.minTimeUs);
+        Assert.assertEquals(3600000000L, invalid0.minPendingTimeUs);
+        Assert.assertEquals(3600000000L, invalid0.maxPendingTimeUs);
+
+        Timeouts invalid1 = Timeouts.parse(":10000::");
+        Assert.assertEquals(3600000000L, invalid1.minTimeUs);
+        Assert.assertEquals(3600000000L, invalid1.minPendingTimeUs);
+        Assert.assertEquals(3600000000L, invalid1.maxPendingTimeUs);
+
+        Timeouts invalid2 = Timeouts.parse("10000:10001:abcd");
+        Assert.assertEquals(10000L, invalid2.minTimeUs);
+        Assert.assertEquals(10001L, invalid2.minPendingTimeUs);
+        Assert.assertEquals(3600000000L, invalid2.maxPendingTimeUs);
+
+        Timeouts invalid3 = Timeouts.parse(":10000:");
+        Assert.assertEquals(3600000000L, invalid3.minTimeUs);
+        Assert.assertEquals(3600000000L, invalid3.minPendingTimeUs);
+        Assert.assertEquals(3600000000L, invalid3.maxPendingTimeUs);
+
+        Timeouts invalid4 = Timeouts.parse("abcd:10001:10002");
+        Assert.assertEquals(3600000000L, invalid4.minTimeUs);
+        Assert.assertEquals(3600000000L, invalid4.minPendingTimeUs);
+        Assert.assertEquals(3600000000L, invalid4.maxPendingTimeUs);
+
+        Timeouts invalid5 = Timeouts.parse("::1000000000000000000000000");
+        Assert.assertEquals(3600000000L, invalid5.minTimeUs);
+        Assert.assertEquals(3600000000L, invalid5.minPendingTimeUs);
+        Assert.assertEquals(3600000000L, invalid5.maxPendingTimeUs);
+
+        Timeouts invalid6 = Timeouts.parse("-10000:10001:10002");
+        Assert.assertEquals(3600000000L, invalid6.minTimeUs);
+        Assert.assertEquals(3600000000L, invalid6.minPendingTimeUs);
+        Assert.assertEquals(3600000000L, invalid6.maxPendingTimeUs);
+    }
+
+    @Test
+    public void testVersionCodes() {
+        final VersionCodes defaults = VersionCodes.parse("");
+        Assert.assertEquals(Long.MIN_VALUE, defaults.minVersionCode);
+        Assert.assertEquals(Long.MAX_VALUE, defaults.maxVersionCode);
+
+        VersionCodes single = VersionCodes.parse("191000070");
+        Assert.assertEquals(191000070, single.minVersionCode);
+        Assert.assertEquals(191000070, single.maxVersionCode);
+
+        VersionCodes single2 = VersionCodes.parse("191000070-191000070");
+        Assert.assertEquals(191000070, single2.minVersionCode);
+        Assert.assertEquals(191000070, single2.maxVersionCode);
+
+        VersionCodes upto = VersionCodes.parse("-191000070");
+        Assert.assertEquals(Long.MIN_VALUE, upto.minVersionCode);
+        Assert.assertEquals(191000070, upto.maxVersionCode);
+
+        VersionCodes andabove = VersionCodes.parse("191000070-");
+        Assert.assertEquals(191000070, andabove.minVersionCode);
+        Assert.assertEquals(Long.MAX_VALUE, andabove.maxVersionCode);
+
+        VersionCodes range = VersionCodes.parse("191000070-201000070");
+        Assert.assertEquals(191000070, range.minVersionCode);
+        Assert.assertEquals(201000070, range.maxVersionCode);
+
+        VersionCodes invalid0 = VersionCodes.parse("201000070-191000070");
+        Assert.assertEquals(Long.MIN_VALUE, invalid0.minVersionCode);
+        Assert.assertEquals(Long.MAX_VALUE, invalid0.maxVersionCode);
+
+        VersionCodes invalid1 = VersionCodes.parse("abcd-191000070");
+        Assert.assertEquals(Long.MIN_VALUE, invalid1.minVersionCode);
+        Assert.assertEquals(191000070, invalid1.maxVersionCode);
+
+        VersionCodes invalid2 = VersionCodes.parse("abcd");
+        Assert.assertEquals(Long.MIN_VALUE, invalid2.minVersionCode);
+        Assert.assertEquals(Long.MAX_VALUE, invalid2.maxVersionCode);
+
+        VersionCodes invalid3 = VersionCodes.parse("191000070-abcd");
+        Assert.assertEquals(191000070, invalid3.minVersionCode);
+        Assert.assertEquals(Long.MAX_VALUE, invalid3.maxVersionCode);
+    }
+
+    @Test
+    public void testPerPackageReadTimeouts() {
+        final String sha256 = "336faefc91bb2dddf9b21829106fbc607b862132fecd273e1b6b3ea55f09d4e1";
+        final VersionCodes defVCs = VersionCodes.parse("");
+        final Timeouts defTs = Timeouts.parse("3600000001:3600000002:3600000003");
+
+        PerPackageReadTimeouts empty = PerPackageReadTimeouts.parse("", defVCs, defTs);
+        Assert.assertNull(empty);
+
+        PerPackageReadTimeouts packageOnly = PerPackageReadTimeouts.parse("package.com", defVCs,
+                defTs);
+        Assert.assertEquals("package.com", packageOnly.packageName);
+        Assert.assertEquals(null, packageOnly.sha256certificate);
+        Assert.assertEquals(Long.MIN_VALUE, packageOnly.versionCodes.minVersionCode);
+        Assert.assertEquals(Long.MAX_VALUE, packageOnly.versionCodes.maxVersionCode);
+        Assert.assertEquals(3600000001L, packageOnly.timeouts.minTimeUs);
+        Assert.assertEquals(3600000002L, packageOnly.timeouts.minPendingTimeUs);
+        Assert.assertEquals(3600000003L, packageOnly.timeouts.maxPendingTimeUs);
+
+        PerPackageReadTimeouts packageHash = PerPackageReadTimeouts.parse(
+                "package.com:" + sha256, defVCs, defTs);
+        Assert.assertEquals("package.com", packageHash.packageName);
+        Assert.assertEquals(sha256, bytesToHexString(packageHash.sha256certificate));
+        Assert.assertEquals(Long.MIN_VALUE, packageHash.versionCodes.minVersionCode);
+        Assert.assertEquals(Long.MAX_VALUE, packageHash.versionCodes.maxVersionCode);
+        Assert.assertEquals(3600000001L, packageHash.timeouts.minTimeUs);
+        Assert.assertEquals(3600000002L, packageHash.timeouts.minPendingTimeUs);
+        Assert.assertEquals(3600000003L, packageHash.timeouts.maxPendingTimeUs);
+
+        PerPackageReadTimeouts packageVersionCode = PerPackageReadTimeouts.parse(
+                "package.com::191000070", defVCs, defTs);
+        Assert.assertEquals("package.com", packageVersionCode.packageName);
+        Assert.assertEquals(null, packageVersionCode.sha256certificate);
+        Assert.assertEquals(191000070, packageVersionCode.versionCodes.minVersionCode);
+        Assert.assertEquals(191000070, packageVersionCode.versionCodes.maxVersionCode);
+        Assert.assertEquals(3600000001L, packageVersionCode.timeouts.minTimeUs);
+        Assert.assertEquals(3600000002L, packageVersionCode.timeouts.minPendingTimeUs);
+        Assert.assertEquals(3600000003L, packageVersionCode.timeouts.maxPendingTimeUs);
+
+        PerPackageReadTimeouts full = PerPackageReadTimeouts.parse(
+                "package.com:" + sha256 + ":191000070-201000070:10001:10002:10003", defVCs, defTs);
+        Assert.assertEquals("package.com", full.packageName);
+        Assert.assertEquals(sha256, bytesToHexString(full.sha256certificate));
+        Assert.assertEquals(191000070, full.versionCodes.minVersionCode);
+        Assert.assertEquals(201000070, full.versionCodes.maxVersionCode);
+        Assert.assertEquals(10001L, full.timeouts.minTimeUs);
+        Assert.assertEquals(10002L, full.timeouts.minPendingTimeUs);
+        Assert.assertEquals(10003L, full.timeouts.maxPendingTimeUs);
+    }
+
+    @Test
+    public void testGetPerPackageReadTimeouts() {
+        Assert.assertEquals(0, getPerPackageReadTimeouts(null).length);
+        Assert.assertEquals(0, getPerPackageReadTimeouts("").length);
+        Assert.assertEquals(0, getPerPackageReadTimeouts(",,,,").length);
+
+        final String sha256 = "0fae93f1a7925b4c68bbea80ad3eaa41acfc9bc6f10bf1054f5d93a2bd556093";
+
+        PerPackageReadTimeouts[] singlePackage = getPerPackageReadTimeouts(
+                "package.com:" + sha256 + ":191000070-201000070:10001:10002:10003");
+        Assert.assertEquals(1, singlePackage.length);
+        Assert.assertEquals("package.com", singlePackage[0].packageName);
+        Assert.assertEquals(sha256, bytesToHexString(singlePackage[0].sha256certificate));
+        Assert.assertEquals(191000070, singlePackage[0].versionCodes.minVersionCode);
+        Assert.assertEquals(201000070, singlePackage[0].versionCodes.maxVersionCode);
+        Assert.assertEquals(10001L, singlePackage[0].timeouts.minTimeUs);
+        Assert.assertEquals(10002L, singlePackage[0].timeouts.minPendingTimeUs);
+        Assert.assertEquals(10003L, singlePackage[0].timeouts.maxPendingTimeUs);
+
+        PerPackageReadTimeouts[] multiPackage = getPerPackageReadTimeouts("package.com:" + sha256
+                + ":191000070-201000070:10001:10002:10003,package1.com::123456");
+        Assert.assertEquals(2, multiPackage.length);
+        Assert.assertEquals("package.com", multiPackage[0].packageName);
+        Assert.assertEquals(sha256, bytesToHexString(multiPackage[0].sha256certificate));
+        Assert.assertEquals(191000070, multiPackage[0].versionCodes.minVersionCode);
+        Assert.assertEquals(201000070, multiPackage[0].versionCodes.maxVersionCode);
+        Assert.assertEquals(10001L, multiPackage[0].timeouts.minTimeUs);
+        Assert.assertEquals(10002L, multiPackage[0].timeouts.minPendingTimeUs);
+        Assert.assertEquals(10003L, multiPackage[0].timeouts.maxPendingTimeUs);
+        Assert.assertEquals("package1.com", multiPackage[1].packageName);
+        Assert.assertEquals(null, multiPackage[1].sha256certificate);
+        Assert.assertEquals(123456, multiPackage[1].versionCodes.minVersionCode);
+        Assert.assertEquals(123456, multiPackage[1].versionCodes.maxVersionCode);
+        Assert.assertEquals(3600000001L, multiPackage[1].timeouts.minTimeUs);
+        Assert.assertEquals(3600000002L, multiPackage[1].timeouts.minPendingTimeUs);
+        Assert.assertEquals(3600000003L, multiPackage[1].timeouts.maxPendingTimeUs);
+    }
+
+    // Report an error from the Computer structure validation test.
+    private void flag(String name, String msg) {
+        fail(name + " " + msg);
+    }
+
+    // Return a string that identifies a Method.  This is not very efficient but it is not
+    // called very often.
+    private String displayName(Method m) {
+        String r = m.getName();
+        String p = Arrays.toString(m.getGenericParameterTypes())
+                   .replaceAll("([a-zA-Z0-9]+\\.)+", "")
+                   .replace("class ", "")
+                   .replaceAll("^\\[", "(")
+                   .replaceAll("\\]$", ")");
+        return r + p;
+    }
+
+    // Match a method to an array of Methods.  Matching is on method signature: name and
+    // parameter types.  If a method in the declared array matches, return it.  Otherwise
+    // return null.
+    private Method matchMethod(Method m, Method[] declared) {
+        String n = m.getName();
+        Type[] t = m.getGenericParameterTypes();
+        for (int i = 0; i < declared.length; i++) {
+            Method l = declared[i];
+            if (l != null && l.getName().equals(n)
+                    && Arrays.equals(l.getGenericParameterTypes(), t)) {
+                Method result = l;
+                // Set the method to null since it has been visited already.
+                declared[i] = null;
+                return result;
+            }
+        }
+        return null;
+    }
+
+    private static PerPackageReadTimeouts[] getPerPackageReadTimeouts(String knownDigestersList) {
+        final String defaultTimeouts = "3600000001:3600000002:3600000003";
+        List<PerPackageReadTimeouts> result = PerPackageReadTimeouts.parseDigestersList(
+                defaultTimeouts, knownDigestersList);
+        if (result == null) {
+            return null;
+        }
+        return result.toArray(new PerPackageReadTimeouts[result.size()]);
+    }
+
+    private static String bytesToHexString(byte[] bytes) {
+        return HexDump.toHexString(bytes, 0, bytes.length, /*upperCase=*/ false);
+    }
+
+    private List<Integer> getKnownPackageIdsList() throws IllegalAccessException {
+        final ArrayList<Integer> knownPackageIds = new ArrayList<>();
+        final Field[] allFields = KnownPackages.class.getDeclaredFields();
+        for (Field field : allFields) {
+            final int modifier = field.getModifiers();
+            if (isPublic(modifier) && isStatic(modifier) && isFinal(modifier)
+                    && Pattern.matches("PACKAGE(_[A-Z]+)+", field.getName())) {
+                knownPackageIds.add(field.getInt(null));
+            }
+        }
+        Collections.sort(knownPackageIds);
+        return knownPackageIds;
+    }
+
+    @Test
+    public void testInstallReason_afterUpdate_keepUnchanged() throws Exception {
+        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
+        try {
+            // Try to install test APK with reason INSTALL_REASON_DEVICE_SETUP
+            runShellCommand("pm install --install-reason 3 " + testApk);
+            assertWithMessage("The install reason of test APK is incorrect.").that(
+                    mIPackageManager.getInstallReason(TEST_PKG_NAME,
+                            UserHandle.myUserId())).isEqualTo(
+                    PackageManager.INSTALL_REASON_DEVICE_SETUP);
+
+            // Try to update test APK with different reason INSTALL_REASON_USER
+            runShellCommand("pm install --install-reason 4 " + testApk);
+            assertWithMessage("The install reason should keep unchanged after update.").that(
+                    mIPackageManager.getInstallReason(TEST_PKG_NAME,
+                            UserHandle.myUserId())).isEqualTo(
+                    PackageManager.INSTALL_REASON_DEVICE_SETUP);
+        } finally {
+            runShellCommand("pm uninstall " + TEST_PKG_NAME);
+        }
+    }
+
+    @Test
+    public void testInstallReason_userRemainsUninstalled_keepUnknown() throws Exception {
+        Assume.assumeTrue(UserManager.supportsMultipleUsers());
+        final UserManager um = UserManager.get(
+                InstrumentationRegistry.getInstrumentation().getContext());
+        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
+        int userId = UserHandle.USER_NULL;
+        try {
+            // Try to install test APK with reason INSTALL_REASON_DEVICE_SETUP
+            runShellCommand("pm install --install-reason 3 " + testApk);
+            assertWithMessage("The install reason of test APK is incorrect.").that(
+                    mIPackageManager.getInstallReason(TEST_PKG_NAME,
+                            UserHandle.myUserId())).isEqualTo(
+                    PackageManager.INSTALL_REASON_DEVICE_SETUP);
+
+            // Create and start the 2nd user.
+            userId = um.createUser("Test User", 0 /* flags */).getUserHandle().getIdentifier();
+            runShellCommand("am start-user -w " + userId);
+            // Since the test APK isn't installed on the 2nd user, the reason should be unknown.
+            assertWithMessage("The test APK should not be installed in the 2nd user").that(
+                    mIPackageManager.getPackageInfo(TEST_PKG_NAME, 0 /* flags */, userId)).isNull();
+            assertWithMessage("The install reason in 2nd user should be unknown.").that(
+                    mIPackageManager.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo(
+                    PackageManager.INSTALL_REASON_UNKNOWN);
+        } finally {
+            runShellCommand("pm uninstall " + TEST_PKG_NAME);
+            if (userId != UserHandle.USER_NULL) {
+                um.removeUser(userId);
+            }
+        }
+    }
+
+    @Test
+    public void testInstallReason_installForAllUsers_sameReason() throws Exception {
+        Assume.assumeTrue(UserManager.supportsMultipleUsers());
+        final UserManager um = UserManager.get(
+                InstrumentationRegistry.getInstrumentation().getContext());
+        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
+        int userId = UserHandle.USER_NULL;
+        try {
+            // Create and start the 2nd user.
+            userId = um.createUser("Test User", 0 /* flags */).getUserHandle().getIdentifier();
+            runShellCommand("am start-user -w " + userId);
+
+            // Try to install test APK to all users with reason INSTALL_REASON_DEVICE_SETUP
+            runShellCommand("pm install --install-reason 3 " + testApk);
+            assertWithMessage("The install reason is inconsistent across users.").that(
+                    mIPackageManager.getInstallReason(TEST_PKG_NAME,
+                            UserHandle.myUserId())).isEqualTo(
+                    mIPackageManager.getInstallReason(TEST_PKG_NAME, userId));
+        } finally {
+            runShellCommand("pm uninstall " + TEST_PKG_NAME);
+            if (userId != UserHandle.USER_NULL) {
+                um.removeUser(userId);
+            }
+        }
+    }
+
+    @Test
+    public void testInstallReason_installSeparately_withSeparatedReason() throws Exception {
+        Assume.assumeTrue(UserManager.supportsMultipleUsers());
+        final UserManager um = UserManager.get(
+                InstrumentationRegistry.getInstrumentation().getContext());
+        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
+        int userId = UserHandle.USER_NULL;
+        try {
+            // Create and start the 2nd user.
+            userId = um.createUser("Test User", 0 /* flags */).getUserHandle().getIdentifier();
+            runShellCommand("am start-user -w " + userId);
+
+            // Try to install test APK on the current user with reason INSTALL_REASON_DEVICE_SETUP
+            runShellCommand("pm install --user cur --install-reason 3 " + testApk);
+            assertWithMessage("The install reason on the current user is incorrect.").that(
+                    mIPackageManager.getInstallReason(TEST_PKG_NAME,
+                            UserHandle.myUserId())).isEqualTo(
+                    PackageManager.INSTALL_REASON_DEVICE_SETUP);
+
+            // Try to install test APK on the 2nd user with reason INSTALL_REASON_USER
+            runShellCommand("pm install --user " + userId + " --install-reason 4 " + testApk);
+            assertWithMessage("The install reason on the 2nd user is incorrect.").that(
+                    mIPackageManager.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo(
+                    PackageManager.INSTALL_REASON_USER);
+        } finally {
+            runShellCommand("pm uninstall " + TEST_PKG_NAME);
+            if (userId != UserHandle.USER_NULL) {
+                um.removeUser(userId);
+            }
+        }
+    }
+
+    @Test
+    public void testSetSplashScreenTheme_samePackage_succeeds() throws Exception {
+        mIPackageManager.setSplashScreenTheme(PACKAGE_NAME, null /* themeName */,
+                UserHandle.myUserId());
+        // Invoking setSplashScreenTheme on the same package shouldn't get any exception.
+    }
+
+    @Test
+    public void testSetSplashScreenTheme_differentPackage_fails() throws Exception {
+        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
+        try {
+            runShellCommand("pm install " + testApk);
+            mIPackageManager.setSplashScreenTheme(TEST_PKG_NAME, null /* themeName */,
+                    UserHandle.myUserId());
+            fail("setSplashScreenTheme did not throw SecurityException as expected");
+        } catch (SecurityException e) {
+            // expected
+        } finally {
+            runShellCommand("pm uninstall " + TEST_PKG_NAME);
+        }
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
new file mode 100644
index 0000000..b3f64b6
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
@@ -0,0 +1,1813 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_MORE_DETAILS;
+import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND;
+import static android.content.pm.parsing.FrameworkParsingPackageUtils.parsePublicKey;
+import static android.content.res.Resources.ID_NULL;
+
+import static com.android.server.pm.PackageManagerService.WRITE_USER_PACKAGE_RESTRICTIONS;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.PropertyInvalidatedCache;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.SuspendDialogInfo;
+import android.content.pm.UserInfo;
+import android.os.BaseBundle;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.permission.persistence.RuntimePermissionsPersistence;
+import com.android.server.LocalServices;
+import com.android.server.pm.parsing.pkg.PackageImpl;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.permission.LegacyPermissionDataProvider;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.SuspendParams;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+import com.android.server.testutils.TestHandler;
+import com.android.server.utils.Watchable;
+import com.android.server.utils.WatchableTester;
+import com.android.server.utils.WatchedArrayMap;
+import com.android.server.utils.WatchedArraySet;
+import com.android.server.utils.Watcher;
+
+import com.google.common.truth.Truth;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PackageManagerSettingsTests {
+    private static final String TAG = "PackageManagerSettingsTests";
+    private static final String PACKAGE_NAME_1 = "com.android.app1";
+    private static final String PACKAGE_NAME_2 = "com.android.app2";
+    private static final String PACKAGE_NAME_3 = "com.android.app3";
+    private static final int TEST_RESOURCE_ID = 2131231283;
+
+    static {
+        System.loadLibrary("services.core.settings.testonly");
+    }
+
+    @Mock
+    RuntimePermissionsPersistence mRuntimePermissionsPersistence;
+    @Mock
+    LegacyPermissionDataProvider mPermissionDataProvider;
+    @Mock
+    DomainVerificationManagerInternal mDomainVerificationManager;
+    @Mock
+    Computer computer;
+
+    final ArrayMap<String, Long> mOrigFirstInstallTimes = new ArrayMap<>();
+
+    final TestHandler mHandler = new TestHandler((@NonNull Message msg) -> {
+        return true;
+    });
+
+    @Before
+    public void initializeMocks() {
+        MockitoAnnotations.initMocks(this);
+        when(mDomainVerificationManager.generateNewId())
+                .thenAnswer(invocation -> UUID.randomUUID());
+    }
+
+    @Before
+    public void setup() {
+        // Disable binder caches in this process.
+        PropertyInvalidatedCache.disableForTestMode();
+    }
+
+    /** make sure our initialized KeySetManagerService metadata matches packages.xml */
+    @Test
+    public void testReadKeySetSettings()
+            throws ReflectiveOperationException, IllegalAccessException {
+        /* write out files and read */
+        writeOldFiles();
+        Settings settings = makeSettings();
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        verifyKeySetMetaData(settings);
+    }
+
+    /** read in data, write it out, and read it back in.  Verify same. */
+    @Test
+    public void testWriteKeySetSettings()
+            throws ReflectiveOperationException, IllegalAccessException {
+        // write out files and read
+        writeOldFiles();
+        Settings settings = makeSettings();
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+
+        // write out, read back in and verify the same
+        settings.writeLPr(computer, /*sync=*/true);
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        verifyKeySetMetaData(settings);
+    }
+
+    @Test
+    public void testSettingsReadOld() {
+        // Write delegateshellthe package files and make sure they're parsed properly the first time
+        writeOldFiles();
+        Settings settings = makeSettings();
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        assertThat(settings.getPackageLPr(PACKAGE_NAME_3), is(notNullValue()));
+        assertThat(settings.getPackageLPr(PACKAGE_NAME_1), is(notNullValue()));
+
+        PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_1);
+        assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DEFAULT));
+        assertThat(ps.getNotLaunched(0), is(true));
+
+        ps = settings.getPackageLPr(PACKAGE_NAME_2);
+        assertThat(ps.getStopped(0), is(false));
+        assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DISABLED_USER));
+        assertThat(ps.getEnabled(1), is(COMPONENT_ENABLED_STATE_DEFAULT));
+    }
+
+    @Test
+    public void testNewPackageRestrictionsFile() throws ReflectiveOperationException {
+        // Write the package files and make sure they're parsed properly the first time
+        writeOldFiles();
+        Settings settings = makeSettings();
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        settings.writeLPr(computer, /*sync=*/true);
+
+        // Create Settings again to make it read from the new files
+        settings = makeSettings();
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+
+        PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_2);
+        assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DISABLED_USER));
+        assertThat(ps.getEnabled(1), is(COMPONENT_ENABLED_STATE_DEFAULT));
+
+        // Verify that the snapshot passes the same test
+        Settings snapshot = settings.snapshot();
+        ps = snapshot.getPackageLPr(PACKAGE_NAME_2);
+        assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DISABLED_USER));
+        assertThat(ps.getEnabled(1), is(COMPONENT_ENABLED_STATE_DEFAULT));
+    }
+
+    private static PersistableBundle createPersistableBundle(String packageName, long longVal,
+            double doubleVal, boolean boolVal, String textVal) {
+        final PersistableBundle bundle = new PersistableBundle();
+        bundle.putString(packageName + ".TEXT_VALUE", textVal);
+        bundle.putLong(packageName + ".LONG_VALUE", longVal);
+        bundle.putBoolean(packageName + ".BOOL_VALUE", boolVal);
+        bundle.putDouble(packageName + ".DOUBLE_VALUE", doubleVal);
+        return bundle;
+    }
+
+    @Test
+    public void testReadPackageRestrictions_noSuspendingPackage() {
+        writePackageRestrictions_noSuspendingPackageXml(0);
+        Settings settingsUnderTest = makeSettings();
+        final WatchableTester watcher =
+                new WatchableTester(settingsUnderTest, "noSuspendingPackage");
+        watcher.register();
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1));
+        settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        watcher.verifyChangeReported("put package 1");
+        // Collect a snapshot at the midway point (package 2 has not been added)
+        final Settings snapshot = settingsUnderTest.snapshot();
+        watcher.verifyNoChangeReported("snapshot");
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, createPackageSetting(PACKAGE_NAME_2));
+        watcher.verifyChangeReported("put package 2");
+        settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+
+        PackageSetting ps1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1);
+        PackageUserStateInternal packageUserState1 = ps1.readUserState(0);
+        assertThat(packageUserState1.isSuspended(), is(true));
+        assertThat(packageUserState1.getSuspendParams().size(), is(1));
+        assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android"));
+        assertThat(packageUserState1.getSuspendParams().valueAt(0).getAppExtras(), is(nullValue()));
+        assertThat(packageUserState1.getSuspendParams().valueAt(0).getDialogInfo(),
+                is(nullValue()));
+        assertThat(packageUserState1.getSuspendParams().valueAt(0).getLauncherExtras(),
+                is(nullValue()));
+
+        // Verify that the snapshot returns the same answers
+        ps1 = snapshot.mPackages.get(PACKAGE_NAME_1);
+        packageUserState1 = ps1.readUserState(0);
+        assertThat(packageUserState1.isSuspended(), is(true));
+        assertThat(packageUserState1.getSuspendParams().size(), is(1));
+        assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android"));
+        assertThat(packageUserState1.getSuspendParams().valueAt(0).getAppExtras(), is(nullValue()));
+        assertThat(packageUserState1.getSuspendParams().valueAt(0).getDialogInfo(),
+                is(nullValue()));
+        assertThat(packageUserState1.getSuspendParams().valueAt(0).getLauncherExtras(),
+                is(nullValue()));
+
+        PackageSetting ps2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2);
+        PackageUserStateInternal packageUserState2 = ps2.readUserState(0);
+        assertThat(packageUserState2.isSuspended(), is(false));
+        assertThat(packageUserState2.getSuspendParams(), is(nullValue()));
+
+        // Verify that the snapshot returns different answers
+        ps2 = snapshot.mPackages.get(PACKAGE_NAME_2);
+        assertTrue(ps2 == null);
+    }
+
+    @Test
+    public void testReadPackageRestrictions_noSuspendParamsMap() {
+        writePackageRestrictions_noSuspendParamsMapXml(0);
+        final Settings settingsUnderTest = makeSettings();
+        final WatchableTester watcher =
+                new WatchableTester(settingsUnderTest, "noSuspendParamsMap");
+        watcher.register();
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1));
+        watcher.verifyChangeReported("put package 1");
+        settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        watcher.verifyChangeReported("readPackageRestrictions");
+
+        final PackageSetting ps1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1);
+        watcher.verifyNoChangeReported("get package 1");
+        final PackageUserStateInternal packageUserState1 = ps1.readUserState(0);
+        watcher.verifyNoChangeReported("readUserState");
+        assertThat(packageUserState1.isSuspended(), is(true));
+        assertThat(packageUserState1.getSuspendParams().size(), is(1));
+        assertThat(packageUserState1.getSuspendParams().keyAt(0), is(PACKAGE_NAME_3));
+        final SuspendParams params = packageUserState1.getSuspendParams().valueAt(0);
+        watcher.verifyNoChangeReported("fetch user state");
+        assertThat(params, is(notNullValue()));
+        assertThat(params.getAppExtras().size(), is(1));
+        assertThat(params.getAppExtras().getString("app_extra_string"), is("value"));
+        assertThat(params.getLauncherExtras().size(), is(1));
+        assertThat(params.getLauncherExtras().getLong("launcher_extra_long"), is(4L));
+        assertThat(params.getDialogInfo(), is(notNullValue()));
+        assertThat(params.getDialogInfo().getDialogMessage(), is("Dialog Message"));
+        assertThat(params.getDialogInfo().getTitleResId(), is(ID_NULL));
+        assertThat(params.getDialogInfo().getIconResId(), is(TEST_RESOURCE_ID));
+        assertThat(params.getDialogInfo().getNeutralButtonTextResId(), is(ID_NULL));
+        assertThat(params.getDialogInfo().getNeutralButtonAction(), is(BUTTON_ACTION_MORE_DETAILS));
+        assertThat(params.getDialogInfo().getDialogMessageResId(), is(ID_NULL));
+    }
+
+    @Test
+    public void testReadWritePackageRestrictions_suspendInfo() {
+        final Settings settingsUnderTest = makeSettings();
+        final WatchableTester watcher = new WatchableTester(settingsUnderTest, "suspendInfo");
+        watcher.register();
+        final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
+        final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
+        final PackageSetting ps3 = createPackageSetting(PACKAGE_NAME_3);
+
+        final PersistableBundle appExtras1 = createPersistableBundle(
+                PACKAGE_NAME_1, 1L, 0.01, true, "appString1");
+        final PersistableBundle appExtras2 = createPersistableBundle(
+                PACKAGE_NAME_2, 2L, 0.02, true, "appString2");
+
+        final PersistableBundle launcherExtras1 = createPersistableBundle(
+                PACKAGE_NAME_1, 10L, 0.1, false, "launcherString1");
+        final PersistableBundle launcherExtras2 = createPersistableBundle(
+                PACKAGE_NAME_2, 20L, 0.2, false, "launcherString2");
+
+        final SuspendDialogInfo dialogInfo1 = new SuspendDialogInfo.Builder()
+                .setIcon(0x11220001)
+                .setTitle("String Title")
+                .setMessage("1st message")
+                .setNeutralButtonText(0x11220003)
+                .setNeutralButtonAction(BUTTON_ACTION_MORE_DETAILS)
+                .build();
+        final SuspendDialogInfo dialogInfo2 = new SuspendDialogInfo.Builder()
+                .setIcon(0x22220001)
+                .setTitle(0x22220002)
+                .setMessage("2nd message")
+                .setNeutralButtonText("String button text")
+                .setNeutralButtonAction(BUTTON_ACTION_UNSUSPEND)
+                .build();
+
+        ps1.modifyUserState(0).putSuspendParams("suspendingPackage1",
+                new SuspendParams(dialogInfo1, appExtras1, launcherExtras1));
+        ps1.modifyUserState(0).putSuspendParams("suspendingPackage2",
+                new SuspendParams(dialogInfo2, appExtras2, launcherExtras2));
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
+        watcher.verifyChangeReported("put package 1");
+
+        ps2.modifyUserState(0).putSuspendParams("suspendingPackage3",
+                new SuspendParams(null, appExtras1, null));
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
+        watcher.verifyChangeReported("put package 2");
+
+        ps3.modifyUserState(0).removeSuspension("irrelevant");
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_3, ps3);
+        watcher.verifyChangeReported("put package 3");
+
+        settingsUnderTest.writePackageRestrictionsLPr(0, /*sync=*/true);
+        watcher.verifyChangeReported("writePackageRestrictions");
+
+        settingsUnderTest.mPackages.clear();
+        watcher.verifyChangeReported("clear packages");
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1));
+        watcher.verifyChangeReported("put package 1");
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, createPackageSetting(PACKAGE_NAME_2));
+        watcher.verifyChangeReported("put package 2");
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_3, createPackageSetting(PACKAGE_NAME_3));
+        watcher.verifyChangeReported("put package 3");
+        // now read and verify
+        settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        watcher.verifyChangeReported("readPackageRestrictions");
+        final PackageUserStateInternal readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1)
+                .readUserState(0);
+        watcher.verifyNoChangeReported("package get 1");
+        assertThat(readPus1.isSuspended(), is(true));
+        assertThat(readPus1.getSuspendParams().size(), is(2));
+        watcher.verifyNoChangeReported("read package param");
+
+        assertThat(readPus1.getSuspendParams().keyAt(0), is("suspendingPackage1"));
+        final SuspendParams params11 = readPus1.getSuspendParams().valueAt(0);
+        watcher.verifyNoChangeReported("read package param");
+        assertThat(params11, is(notNullValue()));
+        assertThat(params11.getDialogInfo(), is(dialogInfo1));
+        assertThat(BaseBundle.kindofEquals(params11.getAppExtras(), appExtras1), is(true));
+        assertThat(BaseBundle.kindofEquals(params11.getLauncherExtras(), launcherExtras1),
+                is(true));
+        watcher.verifyNoChangeReported("read package param");
+
+        assertThat(readPus1.getSuspendParams().keyAt(1), is("suspendingPackage2"));
+        final SuspendParams params12 = readPus1.getSuspendParams().valueAt(1);
+        assertThat(params12, is(notNullValue()));
+        assertThat(params12.getDialogInfo(), is(dialogInfo2));
+        assertThat(BaseBundle.kindofEquals(params12.getAppExtras(), appExtras2), is(true));
+        assertThat(BaseBundle.kindofEquals(params12.getLauncherExtras(), launcherExtras2),
+                is(true));
+        watcher.verifyNoChangeReported("read package param");
+
+        final PackageUserStateInternal readPus2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2)
+                .readUserState(0);
+        assertThat(readPus2.isSuspended(), is(true));
+        assertThat(readPus2.getSuspendParams().size(), is(1));
+        assertThat(readPus2.getSuspendParams().keyAt(0), is("suspendingPackage3"));
+        final SuspendParams params21 = readPus2.getSuspendParams().valueAt(0);
+        assertThat(params21, is(notNullValue()));
+        assertThat(params21.getDialogInfo(), is(nullValue()));
+        assertThat(BaseBundle.kindofEquals(params21.getAppExtras(), appExtras1), is(true));
+        assertThat(params21.getLauncherExtras(), is(nullValue()));
+        watcher.verifyNoChangeReported("read package param");
+
+        final PackageUserStateInternal readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3)
+                .readUserState(0);
+        assertThat(readPus3.isSuspended(), is(false));
+        assertThat(readPus3.getSuspendParams(), is(nullValue()));
+        watcher.verifyNoChangeReported("package get 3");
+    }
+
+    @Test
+    public void testPackageRestrictionsSuspendedDefault() {
+        final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1);
+        assertThat(defaultSetting.getUserStateOrDefault(0).isSuspended(), is(false));
+    }
+
+    private void populateDefaultSettings(Settings settings) {
+        settings.mPackages.clear();
+        settings.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1));
+        settings.mPackages.put(PACKAGE_NAME_2, createPackageSetting(PACKAGE_NAME_2));
+        settings.mPackages.put(PACKAGE_NAME_3, createPackageSetting(PACKAGE_NAME_3));
+    }
+
+    private void verifyDefaultDistractionFlags(Settings settings) {
+        final PackageUserState readPus1 = settings.mPackages.get(PACKAGE_NAME_1)
+                .readUserState(0);
+        assertThat(readPus1.getDistractionFlags(), is(0));
+
+        final PackageUserState readPus2 = settings.mPackages.get(PACKAGE_NAME_2)
+                .readUserState(0);
+        assertThat(readPus2.getDistractionFlags(), is(0));
+
+        final PackageUserState readPus3 = settings.mPackages.get(PACKAGE_NAME_3)
+                .readUserState(0);
+        assertThat(readPus3.getDistractionFlags(), is(0));
+    }
+
+    private void populateDistractionFlags(Settings settings) {
+        settings.mPackages.clear();
+        final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
+        final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
+        final PackageSetting ps3 = createPackageSetting(PACKAGE_NAME_3);
+
+        final int distractionFlags1 = PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
+        ps1.setDistractionFlags(distractionFlags1, 0);
+        settings.mPackages.put(PACKAGE_NAME_1, ps1);
+
+        final int distractionFlags2 = PackageManager.RESTRICTION_HIDE_NOTIFICATIONS
+                | PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
+        ps2.setDistractionFlags(distractionFlags2, 0);
+        settings.mPackages.put(PACKAGE_NAME_2, ps2);
+
+        final int distractionFlags3 = PackageManager.RESTRICTION_NONE;
+        ps3.setDistractionFlags(distractionFlags3, 0);
+        settings.mPackages.put(PACKAGE_NAME_3, ps3);
+    }
+
+    private void verifyDistractionFlags(Settings settings) {
+        final int distractionFlags1 = PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
+        final int distractionFlags2 = PackageManager.RESTRICTION_HIDE_NOTIFICATIONS
+                | PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
+        final int distractionFlags3 = PackageManager.RESTRICTION_NONE;
+
+        final PackageUserState readPus1 = settings.mPackages.get(PACKAGE_NAME_1)
+                .readUserState(0);
+        assertThat(readPus1.getDistractionFlags(), is(distractionFlags1));
+
+        final PackageUserState readPus2 = settings.mPackages.get(PACKAGE_NAME_2)
+                .readUserState(0);
+        assertThat(readPus2.getDistractionFlags(), is(distractionFlags2));
+
+        final PackageUserState readPus3 = settings.mPackages.get(PACKAGE_NAME_3)
+                .readUserState(0);
+        assertThat(readPus3.getDistractionFlags(), is(distractionFlags3));
+    }
+
+    @Test
+    public void testReadWritePackageRestrictions_distractionFlags() {
+        final Settings settingsUnderTest = makeSettings();
+
+        populateDistractionFlags(settingsUnderTest);
+        settingsUnderTest.writePackageRestrictionsLPr(0, /*sync=*/true);
+
+        // now read and verify
+        populateDefaultSettings(settingsUnderTest);
+        settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        verifyDistractionFlags(settingsUnderTest);
+    }
+
+    @Test
+    public void testReadWritePackageRestrictionsAsync() {
+        final Settings settingsWrite = makeSettings();
+        final Settings settingsRead = makeSettings();
+        mHandler.clear();
+
+        // Initial empty state.
+        settingsWrite.removeUserLPw(0);
+
+        // Schedule 3 async writes.
+        settingsWrite.writePackageRestrictionsLPr(0, /*sync=*/false);
+        settingsWrite.writePackageRestrictionsLPr(0, /*sync=*/false);
+        settingsWrite.writePackageRestrictionsLPr(0, /*sync=*/false);
+
+        PriorityQueue<TestHandler.MsgInfo> messages = mHandler.getPendingMessages();
+        assertEquals(3, messages.size());
+        final Runnable asyncWrite1 = (Runnable) messages.poll().message.obj;
+        final Runnable asyncWrite2 = (Runnable) messages.poll().message.obj;
+        final Runnable asyncWrite3 = (Runnable) messages.poll().message.obj;
+        mHandler.clear();
+
+        // First read should read old data.
+        populateDefaultSettings(settingsRead);
+        settingsRead.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        verifyDefaultDistractionFlags(settingsRead);
+
+        // 1st write: with flags.
+        populateDistractionFlags(settingsWrite);
+        asyncWrite1.run();
+
+        // New data.
+        populateDefaultSettings(settingsRead);
+        settingsRead.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        verifyDistractionFlags(settingsRead);
+
+        // 2nd write: without.
+        populateDefaultSettings(settingsWrite);
+        asyncWrite2.run();
+
+        // Default data.
+        populateDefaultSettings(settingsRead);
+        settingsRead.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        verifyDefaultDistractionFlags(settingsRead);
+
+        // 3rd write: with flags.
+        populateDistractionFlags(settingsWrite);
+        asyncWrite3.run();
+
+        // New data.
+        populateDefaultSettings(settingsRead);
+        settingsRead.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        verifyDistractionFlags(settingsRead);
+    }
+
+    @Test
+    public void testReadWritePackageRestrictionsAsyncUserRemoval() {
+        final Settings settingsWrite = makeSettings();
+        final Settings settingsRead = makeSettings();
+        mHandler.clear();
+
+        // Initial empty state.
+        settingsWrite.removeUserLPw(0);
+
+        // 2 async writes.
+        populateDistractionFlags(settingsWrite);
+        settingsWrite.writePackageRestrictionsLPr(0, /*sync=*/false);
+        settingsWrite.writePackageRestrictionsLPr(0, /*sync=*/false);
+
+        PriorityQueue<TestHandler.MsgInfo> messages = mHandler.getPendingMessages();
+        assertEquals(2, messages.size());
+        final Runnable asyncWrite1 = (Runnable) messages.poll().message.obj;
+        final Runnable asyncWrite2 = (Runnable) messages.poll().message.obj;
+        mHandler.clear();
+
+        // First read should not read anything.
+        populateDefaultSettings(settingsRead);
+        settingsRead.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        verifyDefaultDistractionFlags(settingsRead);
+
+        // 1st write.
+        asyncWrite1.run();
+
+        // Second read should read new data.
+        populateDefaultSettings(settingsRead);
+        settingsRead.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        verifyDistractionFlags(settingsRead);
+
+        // Now remove the user.
+        settingsWrite.removeUserLPw(0);
+
+        // 2nd write.
+        asyncWrite2.run();
+
+        // Re-read and verify that nothing was read.
+        populateDefaultSettings(settingsRead);
+        settingsRead.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        verifyDefaultDistractionFlags(settingsRead);
+    }
+
+    @Test
+    public void testReadWritePackageRestrictionsSyncAfterAsync() {
+        final Settings settingsWrite = makeSettings();
+        final Settings settingsRead = makeSettings();
+        mHandler.clear();
+
+        // Initial state, distraction flags populated.
+        populateDistractionFlags(settingsWrite);
+        settingsWrite.writePackageRestrictionsLPr(0, /*sync=*/true);
+
+        // 2 async writes of empty distraction flags.
+        populateDefaultSettings(settingsWrite);
+        settingsWrite.writePackageRestrictionsLPr(0, /*sync=*/false);
+        settingsWrite.writePackageRestrictionsLPr(0, /*sync=*/false);
+
+        PriorityQueue<TestHandler.MsgInfo> messages = mHandler.getPendingMessages();
+        assertEquals(2, messages.size());
+        final Runnable asyncWrite1 = (Runnable) messages.poll().message.obj;
+        final Runnable asyncWrite2 = (Runnable) messages.poll().message.obj;
+
+        // First read should use read initial data (with flags).
+        populateDefaultSettings(settingsRead);
+        settingsRead.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        verifyDistractionFlags(settingsRead);
+
+        // 1st write.
+        asyncWrite1.run();
+
+        // Second read should read updated data.
+        populateDefaultSettings(settingsRead);
+        settingsRead.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        verifyDefaultDistractionFlags(settingsRead);
+
+        // Sync write with flags - overrides all async writes.
+        populateDistractionFlags(settingsWrite);
+        settingsWrite.writeAllUsersPackageRestrictionsLPr(/*sync=*/true);
+
+        // Expect removeMessages call.
+        assertFalse(mHandler.hasMessages(WRITE_USER_PACKAGE_RESTRICTIONS));
+
+        populateDefaultSettings(settingsRead);
+        settingsRead.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        verifyDistractionFlags(settingsRead);
+
+        // 2nd write.
+        asyncWrite2.run();
+
+        // Re-read and verify.
+        populateDefaultSettings(settingsRead);
+        settingsRead.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        verifyDistractionFlags(settingsRead);
+    }
+
+    @Test
+    public void testWriteReadUsesStaticLibraries() {
+        final Settings settingsUnderTest = makeSettings();
+        final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
+        ps1.setAppId(Process.FIRST_APPLICATION_UID);
+        ps1.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed())
+                .setUid(ps1.getAppId())
+                .setSystem(true)
+                .hideAsFinal());
+        final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
+        ps2.setAppId(Process.FIRST_APPLICATION_UID + 1);
+        ps2.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_2).hideAsParsed())
+                .setUid(ps2.getAppId())
+                .hideAsFinal());
+
+        ps1.setUsesStaticLibraries(new String[] { "com.example.shared.one" });
+        ps1.setUsesStaticLibrariesVersions(new long[] { 12 });
+        ps1.setFlags(ps1.getFlags() | ApplicationInfo.FLAG_SYSTEM);
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
+        assertThat(settingsUnderTest.disableSystemPackageLPw(PACKAGE_NAME_1, false), is(true));
+
+        ps2.setUsesStaticLibraries(new String[] { "com.example.shared.two" });
+        ps2.setUsesStaticLibrariesVersions(new long[] { 34 });
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
+
+        settingsUnderTest.writeLPr(computer, /*sync=*/true);
+
+        settingsUnderTest.mPackages.clear();
+        settingsUnderTest.mDisabledSysPackages.clear();
+
+        assertThat(settingsUnderTest.readLPw(computer, createFakeUsers()), is(true));
+
+        PackageSetting readPs1 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_1);
+        PackageSetting readPs2 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_2);
+
+        Truth.assertThat(readPs1).isNotNull();
+        Truth.assertThat(readPs1.getUsesStaticLibraries()).isNotNull();
+        Truth.assertThat(readPs1.getUsesStaticLibrariesVersions()).isNotNull();
+        Truth.assertThat(readPs2).isNotNull();
+        Truth.assertThat(readPs2.getUsesStaticLibraries()).isNotNull();
+        Truth.assertThat(readPs2.getUsesStaticLibrariesVersions()).isNotNull();
+
+        List<Long> ps1VersionsAsList = new ArrayList<>();
+        for (long version : ps1.getUsesStaticLibrariesVersions()) {
+            ps1VersionsAsList.add(version);
+        }
+
+        List<Long> ps2VersionsAsList = new ArrayList<>();
+        for (long version : ps2.getUsesStaticLibrariesVersions()) {
+            ps2VersionsAsList.add(version);
+        }
+
+        Truth.assertThat(readPs1.getUsesStaticLibraries()).asList()
+                .containsExactlyElementsIn(ps1.getUsesStaticLibraries()).inOrder();
+
+        Truth.assertThat(readPs1.getUsesStaticLibrariesVersions()).asList()
+                .containsExactlyElementsIn(ps1VersionsAsList).inOrder();
+
+        Truth.assertThat(readPs2.getUsesStaticLibraries()).asList()
+                .containsExactlyElementsIn(ps2.getUsesStaticLibraries()).inOrder();
+
+        Truth.assertThat(readPs2.getUsesStaticLibrariesVersions()).asList()
+                .containsExactlyElementsIn(ps2VersionsAsList).inOrder();
+    }
+
+    @Test
+    public void testWriteReadUsesSdkLibraries() {
+        final Settings settingsUnderTest = makeSettings();
+        final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
+        ps1.setAppId(Process.FIRST_APPLICATION_UID);
+        ps1.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed())
+                .setUid(ps1.getAppId())
+                .setSystem(true)
+                .hideAsFinal());
+        final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
+        ps2.setAppId(Process.FIRST_APPLICATION_UID + 1);
+        ps2.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_2).hideAsParsed())
+                .setUid(ps2.getAppId())
+                .hideAsFinal());
+
+        ps1.setUsesSdkLibraries(new String[] { "com.example.sdk.one" });
+        ps1.setUsesSdkLibrariesVersionsMajor(new long[] { 12 });
+        ps1.setFlags(ps1.getFlags() | ApplicationInfo.FLAG_SYSTEM);
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
+        assertThat(settingsUnderTest.disableSystemPackageLPw(PACKAGE_NAME_1, false), is(true));
+
+        ps2.setUsesSdkLibraries(new String[] { "com.example.sdk.two" });
+        ps2.setUsesSdkLibrariesVersionsMajor(new long[] { 34 });
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
+
+        settingsUnderTest.writeLPr(computer, /*sync=*/true);
+
+        settingsUnderTest.mPackages.clear();
+        settingsUnderTest.mDisabledSysPackages.clear();
+
+        assertThat(settingsUnderTest.readLPw(computer, createFakeUsers()), is(true));
+
+        PackageSetting readPs1 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_1);
+        PackageSetting readPs2 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_2);
+
+        Truth.assertThat(readPs1).isNotNull();
+        Truth.assertThat(readPs1.getUsesSdkLibraries()).isNotNull();
+        Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).isNotNull();
+        Truth.assertThat(readPs2).isNotNull();
+        Truth.assertThat(readPs2.getUsesSdkLibraries()).isNotNull();
+        Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).isNotNull();
+
+        List<Long> ps1VersionsAsList = new ArrayList<>();
+        for (long version : ps1.getUsesSdkLibrariesVersionsMajor()) {
+            ps1VersionsAsList.add(version);
+        }
+
+        List<Long> ps2VersionsAsList = new ArrayList<>();
+        for (long version : ps2.getUsesSdkLibrariesVersionsMajor()) {
+            ps2VersionsAsList.add(version);
+        }
+
+        Truth.assertThat(readPs1.getUsesSdkLibraries()).asList()
+                .containsExactlyElementsIn(ps1.getUsesSdkLibraries()).inOrder();
+
+        Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).asList()
+                .containsExactlyElementsIn(ps1VersionsAsList).inOrder();
+
+        Truth.assertThat(readPs2.getUsesSdkLibraries()).asList()
+                .containsExactlyElementsIn(ps2.getUsesSdkLibraries()).inOrder();
+
+        Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).asList()
+                .containsExactlyElementsIn(ps2VersionsAsList).inOrder();
+    }
+
+    @Test
+    public void testPackageRestrictionsDistractionFlagsDefault() {
+        final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1);
+        assertThat(defaultSetting.getDistractionFlags(0), is(PackageManager.RESTRICTION_NONE));
+    }
+
+    @Test
+    public void testEnableDisable() {
+        // Write the package files and make sure they're parsed properly the first time
+        writeOldFiles();
+        Settings settings = makeSettings();
+        final WatchableTester watcher = new WatchableTester(settings, "testEnableDisable");
+        watcher.register();
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        watcher.verifyChangeReported("readLPw");
+
+        // Enable/Disable a package
+        PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_1);
+        watcher.verifyNoChangeReported("getPackageLPr");
+        assertThat(ps.getEnabled(0), is(not(COMPONENT_ENABLED_STATE_DISABLED)));
+        assertThat(ps.getEnabled(1), is(not(COMPONENT_ENABLED_STATE_ENABLED)));
+        ps.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, 0, null);
+        watcher.verifyChangeReported("setEnabled DISABLED");
+        ps.setEnabled(COMPONENT_ENABLED_STATE_ENABLED, 1, null);
+        watcher.verifyChangeReported("setEnabled ENABLED");
+        assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DISABLED));
+        assertThat(ps.getEnabled(1), is(COMPONENT_ENABLED_STATE_ENABLED));
+        watcher.verifyNoChangeReported("getEnabled");
+
+        // Enable/Disable a component
+        WatchedArraySet<String> components = new WatchedArraySet<String>();
+        String component1 = PACKAGE_NAME_1 + "/.Component1";
+        components.add(component1);
+        ps.setDisabledComponents(components, 0);
+        WatchedArraySet<String> componentsDisabled = ps.getDisabledComponents(0);
+        assertThat(componentsDisabled.size(), is(1));
+        assertThat(componentsDisabled.untrackedStorage().toArray()[0], is(component1));
+        boolean hasEnabled =
+                ps.getEnabledComponents(0) != null && ps.getEnabledComponents(1).size() > 0;
+        assertThat(hasEnabled, is(false));
+
+        // User 1 should not have any disabled components
+        boolean hasDisabled =
+                ps.getDisabledComponents(1) != null && ps.getDisabledComponents(1).size() > 0;
+        assertThat(hasDisabled, is(false));
+        ps.setEnabledComponents(components, 1);
+        assertThat(ps.getEnabledComponents(1).size(), is(1));
+        hasEnabled = ps.getEnabledComponents(0) != null && ps.getEnabledComponents(0).size() > 0;
+        assertThat(hasEnabled, is(false));
+    }
+
+    private static final String PACKAGE_NAME = "com.android.bar";
+    private static final String REAL_PACKAGE_NAME = "com.android.foo";
+    private static final File INITIAL_CODE_PATH =
+            new File(InstrumentationRegistry.getContext().getFilesDir(), "com.android.bar-1");
+    private static final File UPDATED_CODE_PATH =
+            new File(InstrumentationRegistry.getContext().getFilesDir(), "com.android.bar-2");
+    private static final long INITIAL_VERSION_CODE = 10023L;
+    private static final long UPDATED_VERSION_CODE = 10025L;
+
+    @Test
+    public void testPackageStateCopy01() {
+        final PackageSetting origPkgSetting01 = new PackageSetting(
+                PACKAGE_NAME,
+                REAL_PACKAGE_NAME,
+                INITIAL_CODE_PATH /*codePath*/,
+                null /*legacyNativeLibraryPathString*/,
+                "x86_64" /*primaryCpuAbiString*/,
+                "x86" /*secondaryCpuAbiString*/,
+                null /*cpuAbiOverrideString*/,
+                INITIAL_VERSION_CODE,
+                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
+                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
+                0,
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
+                null /*usesStaticLibraries*/,
+                null /*usesStaticLibrariesVersions*/,
+                null /*mimeGroups*/,
+                UUID.randomUUID());
+        origPkgSetting01.setPkg(mockAndroidPackage(origPkgSetting01));
+        final PackageSetting testPkgSetting01 = new PackageSetting(origPkgSetting01);
+        verifySettingCopy(origPkgSetting01, testPkgSetting01);
+    }
+
+    @Test
+    public void testPackageStateCopy02() {
+        final PackageSetting origPkgSetting01 = new PackageSetting(
+                PACKAGE_NAME /*pkgName*/,
+                REAL_PACKAGE_NAME /*realPkgName*/,
+                INITIAL_CODE_PATH /*codePath*/,
+                null /*legacyNativeLibraryPathString*/,
+                "x86_64" /*primaryCpuAbiString*/,
+                "x86" /*secondaryCpuAbiString*/,
+                null /*cpuAbiOverrideString*/,
+                INITIAL_VERSION_CODE,
+                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
+                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
+                0,
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
+                null /*usesStaticLibraries*/,
+                null /*usesStaticLibrariesVersions*/,
+                null /*mimeGroups*/,
+                UUID.randomUUID());
+        origPkgSetting01.setUserState(0, 100, 1, true, false, false, false, 0, null, false,
+                false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}),
+                new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning",
+                "splashScreenTheme", 1000L);
+        final PersistableBundle appExtras1 = createPersistableBundle(
+                PACKAGE_NAME_1, 1L, 0.01, true, "appString1");
+        final PersistableBundle launcherExtras1 = createPersistableBundle(
+                PACKAGE_NAME_1, 10L, 0.1, false, "launcherString1");
+        final SuspendDialogInfo dialogInfo1 = new SuspendDialogInfo.Builder()
+                .setIcon(0x11220001)
+                .setTitle("String Title")
+                .setMessage("1st message")
+                .setNeutralButtonText(0x11220003)
+                .setNeutralButtonAction(BUTTON_ACTION_MORE_DETAILS)
+                .build();
+        origPkgSetting01.modifyUserState(0).putSuspendParams("suspendingPackage1",
+                new SuspendParams(dialogInfo1, appExtras1, launcherExtras1));
+        origPkgSetting01.setPkg(mockAndroidPackage(origPkgSetting01));
+        final PackageSetting testPkgSetting01 = new PackageSetting(
+                PACKAGE_NAME /*pkgName*/,
+                REAL_PACKAGE_NAME /*realPkgName*/,
+                UPDATED_CODE_PATH /*codePath*/,
+                null /*legacyNativeLibraryPathString*/,
+                null /*primaryCpuAbiString*/,
+                null /*secondaryCpuAbiString*/,
+                null /*cpuAbiOverrideString*/,
+                UPDATED_VERSION_CODE,
+                0 /*pkgFlags*/,
+                0 /*pkgPrivateFlags*/,
+                0,
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
+                null /*usesStaticLibraries*/,
+                null /*usesStaticLibrariesVersions*/,
+                null /*mimeGroups*/,
+                UUID.randomUUID());
+        testPkgSetting01.copyPackageSetting(origPkgSetting01, true);
+        verifySettingCopy(origPkgSetting01, testPkgSetting01);
+        verifyUserStatesCopy(origPkgSetting01.readUserState(0),
+                origPkgSetting01.getStateForUser(UserHandle.of(0)));
+        verifyUserStatesCopy(origPkgSetting01.readUserState(0),
+                testPkgSetting01.readUserState(0));
+    }
+
+    /** Update package */
+    @Test
+    public void testUpdatePackageSetting01() throws PackageManagerException {
+        final PackageSetting testPkgSetting01 =
+                createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
+        testPkgSetting01.setInstalled(false /*installed*/, 0 /*userId*/);
+        assertThat(testPkgSetting01.getFlags(), is(0));
+        assertThat(testPkgSetting01.getPrivateFlags(), is(0));
+        final PackageSetting oldPkgSetting01 = new PackageSetting(testPkgSetting01);
+        Settings.updatePackageSetting(
+                testPkgSetting01,
+                null /*disabledPkg*/,
+                null /*existingSharedUserSetting*/,
+                null /*sharedUser*/,
+                UPDATED_CODE_PATH /*codePath*/,
+                null /*legacyNativeLibraryPath*/,
+                "arm64-v8a" /*primaryCpuAbi*/,
+                "armeabi" /*secondaryCpuAbi*/,
+                0 /*pkgFlags*/,
+                0 /*pkgPrivateFlags*/,
+                UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
+                null /*usesStaticLibraries*/,
+                null /*usesStaticLibrariesVersions*/,
+                null /*mimeGroups*/,
+                UUID.randomUUID());
+        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
+        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
+        assertThat(testPkgSetting01.getFlags(), is(0));
+        assertThat(testPkgSetting01.getPrivateFlags(), is(0));
+        final PackageUserState userState = testPkgSetting01.readUserState(0);
+        verifyUserState(userState, false /*notLaunched*/,
+                false /*stopped*/, false /*installed*/);
+    }
+
+    /** Update package; package now on /system, install for user '0' */
+    @Test
+    public void testUpdatePackageSetting02() throws PackageManagerException {
+        final PackageSetting testPkgSetting01 =
+                createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
+        testPkgSetting01.setInstalled(false /*installed*/, 0 /*userId*/);
+        assertThat(testPkgSetting01.getFlags(), is(0));
+        assertThat(testPkgSetting01.getPrivateFlags(), is(0));
+        final PackageSetting oldPkgSetting01 = new PackageSetting(testPkgSetting01);
+        Settings.updatePackageSetting(
+                testPkgSetting01,
+                null /*disabledPkg*/,
+                null /*existingSharedUserSetting*/,
+                null /*sharedUser*/,
+                UPDATED_CODE_PATH /*codePath*/,
+                null /*legacyNativeLibraryPath*/,
+                "arm64-v8a" /*primaryCpuAbi*/,
+                "armeabi" /*secondaryCpuAbi*/,
+                ApplicationInfo.FLAG_SYSTEM /*pkgFlags*/,
+                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED /*pkgPrivateFlags*/,
+                UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
+                null /*usesStaticLibraries*/,
+                null /*usesStaticLibrariesVersions*/,
+                null /*mimeGroups*/,
+                UUID.randomUUID());
+        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
+        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
+        assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM));
+        assertThat(testPkgSetting01.isSystem(), is(true));
+        assertThat(testPkgSetting01.getPrivateFlags(), is(ApplicationInfo.PRIVATE_FLAG_PRIVILEGED));
+        assertThat(testPkgSetting01.isPrivileged(), is(true));
+        final PackageUserState userState = testPkgSetting01.readUserState(0);
+        verifyUserState(userState,  false /*notLaunched*/,
+                false /*stopped*/, true /*installed*/);
+    }
+
+    /** Update package; changing shared user throws exception */
+    @Test
+    public void testUpdatePackageSetting03() {
+        Settings settings = makeSettings();
+        final SharedUserSetting testUserSetting01 = createSharedUserSetting(
+                settings, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
+        final PackageSetting testPkgSetting01 =
+                createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
+        try {
+            Settings.updatePackageSetting(
+                    testPkgSetting01,
+                    null /*disabledPkg*/,
+                    null /*existingSharedUserSetting*/,
+                    testUserSetting01 /*sharedUser*/,
+                    UPDATED_CODE_PATH /*codePath*/,
+                    null /*legacyNativeLibraryPath*/,
+                    "arm64-v8a" /*primaryCpuAbi*/,
+                    "armeabi" /*secondaryCpuAbi*/,
+                    0 /*pkgFlags*/,
+                    0 /*pkgPrivateFlags*/,
+                    UserManagerService.getInstance(),
+                    null /*usesSdkLibraries*/,
+                    null /*usesSdkLibrariesVersions*/,
+                    null /*usesStaticLibraries*/,
+                    null /*usesStaticLibrariesVersions*/,
+                    null /*mimeGroups*/,
+                    UUID.randomUUID());
+            fail("Expected a PackageManagerException");
+        } catch (PackageManagerException expected) {
+        }
+    }
+
+    /** Create a new PackageSetting based on an original package setting */
+    @Test
+    public void testCreateNewSetting01() {
+        final PackageSetting originalPkgSetting01 =
+                createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
+        final PackageSignatures originalSignatures = originalPkgSetting01.getSignatures();
+        final PackageSetting testPkgSetting01 = Settings.createNewSetting(
+                REAL_PACKAGE_NAME,
+                originalPkgSetting01 /*originalPkg*/,
+                null /*disabledPkg*/,
+                null /*realPkgName*/,
+                null /*sharedUser*/,
+                UPDATED_CODE_PATH /*codePath*/,
+                null /*legacyNativeLibraryPath*/,
+                "arm64-v8a" /*primaryCpuAbi*/,
+                "armeabi" /*secondaryCpuAbi*/,
+                UPDATED_VERSION_CODE /*versionCode*/,
+                ApplicationInfo.FLAG_SYSTEM /*pkgFlags*/,
+                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED /*pkgPrivateFlags*/,
+                null /*installUser*/,
+                false /*allowInstall*/,
+                false /*instantApp*/,
+                false /*virtualPreload*/,
+                UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
+                null /*usesStaticLibraries*/,
+                null /*usesStaticLibrariesVersions*/,
+                null /*mimeGroups*/,
+                UUID.randomUUID());
+        assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
+        assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
+        assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM));
+        assertThat(testPkgSetting01.isSystem(), is(true));
+        assertThat(testPkgSetting01.getPrivateFlags(), is(ApplicationInfo.PRIVATE_FLAG_PRIVILEGED));
+        assertThat(testPkgSetting01.isPrivileged(), is(true));
+        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
+        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
+        // signatures object must be different
+        assertNotSame(testPkgSetting01.getSignatures(), originalSignatures);
+        assertThat(testPkgSetting01.getVersionCode(), is(UPDATED_VERSION_CODE));
+        final PackageUserState userState = testPkgSetting01.readUserState(0);
+        verifyUserState(userState, false /*notLaunched*/, false /*stopped*/, true /*installed*/);
+    }
+
+    /** Create a new non-system PackageSetting */
+    @Test
+    public void testCreateNewSetting02() {
+        final PackageSetting testPkgSetting01 = Settings.createNewSetting(
+                PACKAGE_NAME,
+                null /*originalPkg*/,
+                null /*disabledPkg*/,
+                null /*realPkgName*/,
+                null /*sharedUser*/,
+                INITIAL_CODE_PATH /*codePath*/,
+                null /*legacyNativeLibraryPath*/,
+                "x86_64" /*primaryCpuAbiString*/,
+                "x86" /*secondaryCpuAbiString*/,
+                INITIAL_VERSION_CODE /*versionCode*/,
+                0 /*pkgFlags*/,
+                0 /*pkgPrivateFlags*/,
+                UserHandle.SYSTEM /*installUser*/,
+                true /*allowInstall*/,
+                false /*instantApp*/,
+                false /*virtualPreload*/,
+                UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
+                null /*usesStaticLibraries*/,
+                null /*usesStaticLibrariesVersions*/,
+                null /*mimeGroups*/,
+                UUID.randomUUID());
+        assertThat(testPkgSetting01.getAppId(), is(0));
+        assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
+        assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
+        assertThat(testPkgSetting01.getFlags(), is(0));
+        assertThat(testPkgSetting01.getPrivateFlags(), is(0));
+        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("x86_64"));
+        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("x86_64"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("x86"));
+        assertThat(testPkgSetting01.getVersionCode(), is(INITIAL_VERSION_CODE));
+        // by default, the package is considered stopped
+        final PackageUserState userState = testPkgSetting01.readUserState(0);
+        verifyUserState(userState, true /*notLaunched*/, true /*stopped*/, true /*installed*/);
+    }
+
+    /** Create PackageSetting for a shared user */
+    @Test
+    public void testCreateNewSetting03() {
+        Settings settings = makeSettings();
+        final SharedUserSetting testUserSetting01 = createSharedUserSetting(
+                settings, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
+        final PackageSetting testPkgSetting01 = Settings.createNewSetting(
+                PACKAGE_NAME,
+                null /*originalPkg*/,
+                null /*disabledPkg*/,
+                null /*realPkgName*/,
+                testUserSetting01 /*sharedUser*/,
+                INITIAL_CODE_PATH /*codePath*/,
+                null /*legacyNativeLibraryPath*/,
+                "x86_64" /*primaryCpuAbiString*/,
+                "x86" /*secondaryCpuAbiString*/,
+                INITIAL_VERSION_CODE /*versionCode*/,
+                0 /*pkgFlags*/,
+                0 /*pkgPrivateFlags*/,
+                null /*installUser*/,
+                false /*allowInstall*/,
+                false /*instantApp*/,
+                false /*virtualPreload*/,
+                UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
+                null /*usesStaticLibraries*/,
+                null /*usesStaticLibrariesVersions*/,
+                null /*mimeGroups*/,
+                UUID.randomUUID());
+        assertThat(testPkgSetting01.getAppId(), is(10064));
+        assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
+        assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
+        assertThat(testPkgSetting01.getFlags(), is(0));
+        assertThat(testPkgSetting01.getPrivateFlags(), is(0));
+        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("x86_64"));
+        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("x86_64"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("x86"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("x86"));
+        assertThat(testPkgSetting01.getVersionCode(), is(INITIAL_VERSION_CODE));
+        final PackageUserState userState = testPkgSetting01.readUserState(0);
+        verifyUserState(userState, false /*notLaunched*/, false /*stopped*/, true /*installed*/);
+    }
+
+    /** Create a new PackageSetting based on a disabled package setting */
+    @Test
+    public void testCreateNewSetting04() {
+        final PackageSetting disabledPkgSetting01 =
+                createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
+        disabledPkgSetting01.setAppId(10064);
+        final PackageSignatures disabledSignatures = disabledPkgSetting01.getSignatures();
+        final PackageSetting testPkgSetting01 = Settings.createNewSetting(
+                PACKAGE_NAME,
+                null /*originalPkg*/,
+                disabledPkgSetting01 /*disabledPkg*/,
+                null /*realPkgName*/,
+                null /*sharedUser*/,
+                UPDATED_CODE_PATH /*codePath*/,
+                null /*legacyNativeLibraryPath*/,
+                "arm64-v8a" /*primaryCpuAbi*/,
+                "armeabi" /*secondaryCpuAbi*/,
+                UPDATED_VERSION_CODE /*versionCode*/,
+                0 /*pkgFlags*/,
+                0 /*pkgPrivateFlags*/,
+                null /*installUser*/,
+                false /*allowInstall*/,
+                false /*instantApp*/,
+                false /*virtualPreload*/,
+                UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
+                null /*usesStaticLibraries*/,
+                null /*usesStaticLibrariesVersions*/,
+                null /*mimeGroups*/,
+                UUID.randomUUID());
+        assertThat(testPkgSetting01.getAppId(), is(10064));
+        assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
+        assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
+        assertThat(testPkgSetting01.getFlags(), is(0));
+        assertThat(testPkgSetting01.getPrivateFlags(), is(0));
+        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
+        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
+        assertNotSame(testPkgSetting01.getSignatures(), disabledSignatures);
+        assertThat(testPkgSetting01.getVersionCode(), is(UPDATED_VERSION_CODE));
+        final PackageUserState userState = testPkgSetting01.readUserState(0);
+        verifyUserState(userState, false /*notLaunched*/, false /*stopped*/, true /*installed*/);
+    }
+
+    @Test
+    public void testSetPkgStateLibraryFiles_addNewFiles() {
+        final PackageSetting packageSetting = createPackageSetting("com.foo");
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        packageSetting.registerObserver(new Watcher() {
+            @Override
+            public void onChange(Watchable what) {
+                countDownLatch.countDown();
+            }
+        });
+
+        final List<String> newUsesLibraryFiles = new ArrayList<>();
+        newUsesLibraryFiles.add("code/path/A.apk");
+        newUsesLibraryFiles.add("code/path/B.apk");
+        packageSetting.setPkgStateLibraryFiles(newUsesLibraryFiles);
+
+        assertThat(countDownLatch.getCount(), is(0L));
+    }
+
+    @Test
+    public void testSetPkgStateLibraryFiles_removeOneExistingFile() {
+        final PackageSetting packageSetting = createPackageSetting("com.foo");
+        final List<String> oldUsesLibraryFiles = new ArrayList<>();
+        oldUsesLibraryFiles.add("code/path/A.apk");
+        oldUsesLibraryFiles.add("code/path/B.apk");
+        oldUsesLibraryFiles.add("code/path/C.apk");
+        packageSetting.setPkgStateLibraryFiles(oldUsesLibraryFiles);
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        packageSetting.registerObserver(new Watcher() {
+            @Override
+            public void onChange(Watchable what) {
+                countDownLatch.countDown();
+            }
+        });
+
+        final List<String> newUsesLibraryFiles = new ArrayList<>();
+        oldUsesLibraryFiles.add("code/path/A.apk");
+        oldUsesLibraryFiles.add("code/path/B.apk");
+        packageSetting.setPkgStateLibraryFiles(newUsesLibraryFiles);
+
+        assertThat(countDownLatch.getCount(), is(0L));
+    }
+
+    @Test
+    public void testSetPkgStateLibraryFiles_changeOneOfFile() {
+        final PackageSetting packageSetting = createPackageSetting("com.foo");
+        final List<String> oldUsesLibraryFiles = new ArrayList<>();
+        oldUsesLibraryFiles.add("code/path/A.apk");
+        oldUsesLibraryFiles.add("code/path/B.apk");
+        packageSetting.setPkgStateLibraryFiles(oldUsesLibraryFiles);
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        packageSetting.registerObserver(new Watcher() {
+            @Override
+            public void onChange(Watchable what) {
+                countDownLatch.countDown();
+            }
+        });
+
+        final List<String> newUsesLibraryFiles = new ArrayList<>();
+        newUsesLibraryFiles.add("code/path/A.apk");
+        newUsesLibraryFiles.add("code/path/B-1.apk");
+        packageSetting.setPkgStateLibraryFiles(newUsesLibraryFiles);
+
+        assertThat(countDownLatch.getCount(), is(0L));
+    }
+
+    @Test
+    public void testSetPkgStateLibraryFiles_nothingChanged() {
+        final PackageSetting packageSetting = createPackageSetting("com.foo");
+        final List<String> oldUsesLibraryFiles = new ArrayList<>();
+        oldUsesLibraryFiles.add("code/path/A.apk");
+        oldUsesLibraryFiles.add("code/path/B.apk");
+        packageSetting.setPkgStateLibraryFiles(oldUsesLibraryFiles);
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        packageSetting.registerObserver(new Watcher() {
+            @Override
+            public void onChange(Watchable what) {
+                countDownLatch.countDown();
+            }
+        });
+
+        final List<String> newUsesLibraryFiles = new ArrayList<>();
+        newUsesLibraryFiles.add("code/path/A.apk");
+        newUsesLibraryFiles.add("code/path/B.apk");
+        packageSetting.setPkgStateLibraryFiles(newUsesLibraryFiles);
+
+        assertThat(countDownLatch.getCount(), is(1L));
+    }
+
+    @Test
+    public void testSetPkgStateLibraryFiles_addNewSdks() {
+        final PackageSetting packageSetting = createPackageSetting("com.foo");
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        packageSetting.registerObserver(new Watcher() {
+            @Override
+            public void onChange(Watchable what) {
+                countDownLatch.countDown();
+            }
+        });
+
+        final List<String> files = new ArrayList<>();
+        files.add("com.sdk1_123");
+        files.add("com.sdk9_876");
+        packageSetting.setUsesSdkLibraries(files.toArray(new String[files.size()]));
+
+        assertThat(countDownLatch.getCount(), is(0L));
+    }
+
+    @Test
+    public void testRegisterAndRemoveAppId() throws PackageManagerException {
+        // Test that the first new app UID should start from FIRST_APPLICATION_UID
+        final Settings settings = makeSettings();
+        final PackageSetting ps = createPackageSetting("com.foo");
+        assertTrue(settings.registerAppIdLPw(ps, false));
+        assertEquals(10000, ps.getAppId());
+        // Set up existing app IDs: 10000, 10001, 10003
+        final PackageSetting ps1 = createPackageSetting("com.foo1");
+        ps1.setAppId(10001);
+        final PackageSetting ps2 = createPackageSetting("com.foo2");
+        ps2.setAppId(10003);
+        final PackageSetting ps3 = createPackageSetting("com.foo3");
+        assertEquals(0, ps3.getAppId());
+        assertTrue(settings.registerAppIdLPw(ps1, false));
+        assertTrue(settings.registerAppIdLPw(ps2, false));
+        assertTrue(settings.registerAppIdLPw(ps3, false));
+        assertEquals(10001, ps1.getAppId());
+        assertEquals(10003, ps2.getAppId());
+        // Expecting the new one to start with the next available uid
+        assertEquals(10002, ps3.getAppId());
+        // Remove and insert a new one and the new one should not reuse the same uid
+        settings.removeAppIdLPw(10002);
+        final PackageSetting ps4 = createPackageSetting("com.foo4");
+        assertTrue(settings.registerAppIdLPw(ps4, false));
+        assertEquals(10004, ps4.getAppId());
+        // Keep adding more
+        final PackageSetting ps5 = createPackageSetting("com.foo5");
+        assertTrue(settings.registerAppIdLPw(ps5, false));
+        assertEquals(10005, ps5.getAppId());
+        // Remove the last one and the new one should use incremented uid
+        settings.removeAppIdLPw(10005);
+        final PackageSetting ps6 = createPackageSetting("com.foo6");
+        assertTrue(settings.registerAppIdLPw(ps6, false));
+        assertEquals(10006, ps6.getAppId());
+    }
+
+    /**
+     * Test replacing a PackageSetting with a SharedUserSetting in mAppIds
+     */
+    @Test
+    public void testAddPackageSetting() throws PackageManagerException {
+        final Settings settings = makeSettings();
+        final SharedUserSetting sus1 = new SharedUserSetting(
+                "TestUser", 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
+        sus1.mAppId = 10001;
+        final PackageSetting ps1 = createPackageSetting("com.foo");
+        ps1.setAppId(10001);
+        assertTrue(settings.registerAppIdLPw(ps1, false));
+        settings.addPackageSettingLPw(ps1, sus1);
+        assertSame(sus1, settings.getSharedUserSettingLPr(ps1));
+    }
+
+    private void verifyUserState(PackageUserState userState,
+            boolean notLaunched, boolean stopped, boolean installed) {
+        assertThat(userState.getEnabledState(), is(0));
+        assertThat(userState.isHidden(), is(false));
+        assertThat(userState.isInstalled(), is(installed));
+        assertThat(userState.isNotLaunched(), is(notLaunched));
+        assertThat(userState.isStopped(), is(stopped));
+        assertThat(userState.isSuspended(), is(false));
+        assertThat(userState.getDistractionFlags(), is(0));
+    }
+
+    private void verifyKeySetData(PackageKeySetData originalData, PackageKeySetData testData) {
+        assertThat(originalData.getProperSigningKeySet(),
+                equalTo(testData.getProperSigningKeySet()));
+        assertThat(originalData.getUpgradeKeySets(), is(testData.getUpgradeKeySets()));
+        assertThat(originalData.getAliases(), is(testData.getAliases()));
+    }
+
+    private void verifySettingCopy(PackageSetting origPkgSetting, PackageSetting testPkgSetting) {
+        assertThat(origPkgSetting, is(not(testPkgSetting)));
+        assertThat(origPkgSetting.getAppId(), is(testPkgSetting.getAppId()));
+        assertSame(origPkgSetting.getPath(), testPkgSetting.getPath());
+        assertThat(origPkgSetting.getPath(), is(testPkgSetting.getPath()));
+        assertSame(origPkgSetting.getPathString(), testPkgSetting.getPathString());
+        assertThat(origPkgSetting.getPathString(), is(testPkgSetting.getPathString()));
+        assertSame(origPkgSetting.getCpuAbiOverride(), testPkgSetting.getCpuAbiOverride());
+        assertThat(origPkgSetting.getCpuAbiOverride(), is(testPkgSetting.getCpuAbiOverride()));
+        assertThat(origPkgSetting.getDomainSetId(), is(testPkgSetting.getDomainSetId()));
+        assertSame(origPkgSetting.getInstallSource(), testPkgSetting.getInstallSource());
+        assertThat(origPkgSetting.isInstallPermissionsFixed(),
+                is(testPkgSetting.isInstallPermissionsFixed()));
+        verifyKeySetData(origPkgSetting.getKeySetData(), testPkgSetting.getKeySetData());
+        assertThat(origPkgSetting.getLastUpdateTime(), is(testPkgSetting.getLastUpdateTime()));
+        assertSame(origPkgSetting.getLegacyNativeLibraryPath(),
+                testPkgSetting.getLegacyNativeLibraryPath());
+        assertThat(origPkgSetting.getLegacyNativeLibraryPath(),
+                is(testPkgSetting.getLegacyNativeLibraryPath()));
+        if (origPkgSetting.getMimeGroups() != null
+                && origPkgSetting.getMimeGroups() != Collections.<String, Set<String>>emptyMap()) {
+            assertNotSame(origPkgSetting.getMimeGroups(), testPkgSetting.getMimeGroups());
+        }
+        assertThat(origPkgSetting.getMimeGroups(), is(testPkgSetting.getMimeGroups()));
+        assertNotSame(origPkgSetting.mLegacyPermissionsState,
+                testPkgSetting.mLegacyPermissionsState);
+        assertThat(origPkgSetting.mLegacyPermissionsState,
+                is(testPkgSetting.mLegacyPermissionsState));
+        assertThat(origPkgSetting.getPackageName(), is(testPkgSetting.getPackageName()));
+        // mOldCodePaths is _not_ copied
+        // assertNotSame(origPkgSetting.mOldCodePaths, testPkgSetting.mOldCodePaths);
+        // assertThat(origPkgSetting.mOldCodePaths, is(not(testPkgSetting.mOldCodePaths)));
+        assertSame(origPkgSetting.getPkg(), testPkgSetting.getPkg());
+        assertSame(origPkgSetting.getAndroidPackage(), origPkgSetting.getPkg());
+        assertThat(origPkgSetting.getAndroidPackage().getPackageName(),
+                is(origPkgSetting.getPackageName()));
+        // No equals() method for this object
+        // assertThat(origPkgSetting.pkg, is(testPkgSetting.pkg));
+        assertThat(origPkgSetting.getFlags(), is(testPkgSetting.getFlags()));
+        assertThat(origPkgSetting.getPrivateFlags(), is(testPkgSetting.getPrivateFlags()));
+        assertSame(origPkgSetting.getPrimaryCpuAbi(), testPkgSetting.getPrimaryCpuAbi());
+        assertThat(origPkgSetting.getPrimaryCpuAbi(), is(testPkgSetting.getPrimaryCpuAbi()));
+        assertSame(origPkgSetting.getPrimaryCpuAbiLegacy(), testPkgSetting.getPrimaryCpuAbiLegacy());
+        assertThat(origPkgSetting.getPrimaryCpuAbiLegacy(), is(testPkgSetting.getPrimaryCpuAbiLegacy()));
+        assertThat(origPkgSetting.getRealName(), is(testPkgSetting.getRealName()));
+        assertSame(origPkgSetting.getSecondaryCpuAbi(), testPkgSetting.getSecondaryCpuAbi());
+        assertThat(origPkgSetting.getSecondaryCpuAbi(), is(testPkgSetting.getSecondaryCpuAbi()));
+        assertSame(origPkgSetting.getSecondaryCpuAbiLegacy(), testPkgSetting.getSecondaryCpuAbiLegacy());
+        assertThat(origPkgSetting.getSecondaryCpuAbiLegacy(), is(testPkgSetting.getSecondaryCpuAbiLegacy()));
+        assertSame(origPkgSetting.getSignatures(), testPkgSetting.getSignatures());
+        assertThat(origPkgSetting.getSignatures(), is(testPkgSetting.getSignatures()));
+        assertThat(origPkgSetting.getLastModifiedTime(), is(testPkgSetting.getLastModifiedTime()));
+        assertNotSame(origPkgSetting.getUserStates(), is(testPkgSetting.getUserStates()));
+        // No equals() method for SparseArray object
+        // assertThat(origPkgSetting.getUserState(), is(testPkgSetting.getUserState()));
+        assertThat(origPkgSetting.getVersionCode(), is(testPkgSetting.getVersionCode()));
+        assertSame(origPkgSetting.getVolumeUuid(), testPkgSetting.getVolumeUuid());
+        assertThat(origPkgSetting.getVolumeUuid(), is(testPkgSetting.getVolumeUuid()));
+    }
+
+    private void verifyUserStatesCopy(PackageUserStateInternal origPus,
+            PackageUserStateInternal testPus) {
+        assertThat(userStateEquals(origPus, testPus), is(true));
+        // Verify suspendParams are copied over
+        assertThat(origPus.getSuspendParams(), is(notNullValue()));
+        assertThat(testPus.getSuspendParams(), is(notNullValue()));
+        SuspendParams origSuspendParams = origPus.getSuspendParams().valueAt(0);
+        SuspendParams testSuspendParams = testPus.getSuspendParams().valueAt(0);
+        assertThat(origSuspendParams.getDialogInfo().equals(testSuspendParams.getDialogInfo()),
+                is(true));
+        assertThat(BaseBundle.kindofEquals(
+                origSuspendParams.getAppExtras(), testSuspendParams.getAppExtras()), is(true));
+        assertThat(BaseBundle.kindofEquals(origSuspendParams.getLauncherExtras(),
+                testSuspendParams.getLauncherExtras()), is(true));
+    }
+
+    private void verifyUserStatesCopy(PackageUserState origPus,
+            PackageUserState testPus) {
+        assertThat(userStateEquals(origPus, testPus), is(true));
+        // Verify that disabledComponents and enabledComponents are copied
+        assertThat(origPus.getDisabledComponents(), is(notNullValue()));
+        assertThat(origPus.getDisabledComponents().equals(testPus.getDisabledComponents()),
+                is(true));
+        assertThat(origPus.getEnabledComponents(), is(notNullValue()));
+        assertThat(origPus.getEnabledComponents().equals(testPus.getEnabledComponents()),
+                is(true));
+    }
+
+    private boolean userStateEquals(PackageUserState userState, PackageUserState oldUserState) {
+        return userState.isHidden() == oldUserState.isHidden()
+                && userState.isStopped() == oldUserState.isStopped()
+                && userState.isInstalled() == oldUserState.isInstalled()
+                && userState.isSuspended() == oldUserState.isSuspended()
+                && userState.isNotLaunched() == oldUserState.isNotLaunched()
+                && userState.isInstantApp() == oldUserState.isInstantApp()
+                && userState.isVirtualPreload() == oldUserState.isVirtualPreload()
+                && (userState.getAllOverlayPaths() != null
+                ? userState.getAllOverlayPaths().equals(oldUserState.getAllOverlayPaths())
+                : oldUserState.getOverlayPaths() == null)
+                && userState.getCeDataInode() == oldUserState.getCeDataInode()
+                && userState.getDistractionFlags() == oldUserState.getDistractionFlags()
+                && userState.getFirstInstallTime() == oldUserState.getFirstInstallTime()
+                && userState.getEnabledState() == oldUserState.getEnabledState()
+                 && userState.getHarmfulAppWarning().equals(oldUserState.getHarmfulAppWarning())
+                && userState.getInstallReason() == oldUserState.getInstallReason()
+                && userState.getLastDisableAppCaller().equals(
+                        oldUserState.getLastDisableAppCaller())
+                && (userState.getSharedLibraryOverlayPaths() != null
+                ? userState.getSharedLibraryOverlayPaths().equals(
+                        oldUserState.getSharedLibraryOverlayPaths())
+                : oldUserState.getSharedLibraryOverlayPaths() == null)
+                && userState.getSplashScreenTheme().equals(
+                        oldUserState.getSplashScreenTheme())
+                && userState.getUninstallReason() == oldUserState.getUninstallReason();
+    }
+
+    private SharedUserSetting createSharedUserSetting(Settings settings, String userName,
+            int sharedUserId, int pkgFlags, int pkgPrivateFlags) {
+        return settings.addSharedUserLPw(
+                userName,
+                sharedUserId,
+                pkgFlags,
+                pkgPrivateFlags);
+    }
+    private PackageSetting createPackageSetting(int sharedUserId, int pkgFlags) {
+        return new PackageSetting(
+                PACKAGE_NAME,
+                REAL_PACKAGE_NAME,
+                INITIAL_CODE_PATH /*codePath*/,
+                null /*legacyNativeLibraryPathString*/,
+                "x86_64" /*primaryCpuAbiString*/,
+                "x86" /*secondaryCpuAbiString*/,
+                null /*cpuAbiOverrideString*/,
+                INITIAL_VERSION_CODE,
+                pkgFlags,
+                0 /*privateFlags*/,
+                sharedUserId,
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
+                null /*usesStaticLibraries*/,
+                null /*usesStaticLibrariesVersions*/,
+                null /*mimeGroups*/,
+                UUID.randomUUID());
+    }
+
+    private PackageSetting createPackageSetting(String packageName) {
+        return new PackageSetting(
+                packageName,
+                packageName,
+                INITIAL_CODE_PATH /*codePath*/,
+                null /*legacyNativeLibraryPathString*/,
+                "x86_64" /*primaryCpuAbiString*/,
+                "x86" /*secondaryCpuAbiString*/,
+                null /*cpuAbiOverrideString*/,
+                INITIAL_VERSION_CODE,
+                0,
+                0 /*privateFlags*/,
+                0,
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
+                null /*usesStaticLibraries*/,
+                null /*usesStaticLibrariesVersions*/,
+                null /*mimeGroups*/,
+                UUID.randomUUID());
+    }
+
+    private @NonNull List<UserInfo> createFakeUsers() {
+        ArrayList<UserInfo> users = new ArrayList<>();
+        users.add(new UserInfo(UserHandle.USER_SYSTEM, "test user", UserInfo.FLAG_INITIALIZED));
+        return users;
+    }
+
+    private void writeFile(File file, byte[] data) {
+        file.mkdirs();
+        try {
+            AtomicFile aFile = new AtomicFile(file);
+            FileOutputStream fos = aFile.startWrite();
+            fos.write(data);
+            aFile.finishWrite(fos);
+        } catch (IOException ioe) {
+            Log.e(TAG, "Cannot write file " + file.getPath());
+        }
+    }
+
+    private void writePackagesXml() {
+        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages.xml"),
+                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<packages>"
+                + "<last-platform-version internal=\"15\" external=\"0\" fingerprint=\"foo\" />"
+                + "<permission-trees>"
+                + "<item name=\"com.google.android.permtree\" package=\"com.google.android.permpackage\" />"
+                + "</permission-trees>"
+                + "<permissions>"
+                + "<item name=\"android.permission.WRITE_CALL_LOG\" package=\"android\" protection=\"1\" />"
+                + "<item name=\"android.permission.ASEC_ACCESS\" package=\"android\" protection=\"2\" />"
+                + "<item name=\"android.permission.REBOOT\" package=\"android\" protection=\"18\" />"
+                + "</permissions>"
+                + "<package name=\"com.android.app1\" codePath=\"/system/app/app1.apk\" nativeLibraryPath=\"/data/data/com.android.app1/lib\" flags=\"1\" ft=\"1360e2caa70\" it=\"135f2f80d08\" ut=\"1360e2caa70\" version=\"1109\" sharedUserId=\"11000\">"
+                + "<sigs count=\"1\">"
+                + "<cert index=\"0\" key=\"" + KeySetStrings.ctsKeySetCertA + "\" />"
+                + "</sigs>"
+                + "<proper-signing-keyset identifier=\"1\" />"
+                + "</package>"
+                + "<package name=\"com.android.app2\" codePath=\"/system/app/app2.apk\" nativeLibraryPath=\"/data/data/com.android.app2/lib\" flags=\"1\" ft=\"1360e578718\" it=\"135f2f80d08\" ut=\"1360e578718\" version=\"15\" enabled=\"3\" userId=\"11001\">"
+                + "<sigs count=\"1\">"
+                + "<cert index=\"0\" />"
+                + "</sigs>"
+                + "<proper-signing-keyset identifier=\"1\" />"
+                + "<defined-keyset alias=\"AB\" identifier=\"4\" />"
+                + "</package>"
+                + "<package name=\"com.android.app3\" codePath=\"/system/app/app3.apk\" nativeLibraryPath=\"/data/data/com.android.app3/lib\" flags=\"1\" ft=\"1360e577b60\" it=\"135f2f80d08\" ut=\"1360e577b60\" version=\"15\" userId=\"11030\">"
+                + "<sigs count=\"1\">"
+                + "<cert index=\"1\" key=\"" + KeySetStrings.ctsKeySetCertB + "\" />"
+                + "</sigs>"
+                + "<proper-signing-keyset identifier=\"2\" />"
+                + "<upgrade-keyset identifier=\"3\" />"
+                + "<defined-keyset alias=\"C\" identifier=\"3\" />"
+                + "</package>"
+                + "<shared-user name=\"com.android.shared1\" userId=\"11000\">"
+                + "<sigs count=\"1\">"
+                + "<cert index=\"1\" />"
+                + "</sigs>"
+                + "<perms>"
+                + "<item name=\"android.permission.REBOOT\" />"
+                + "</perms>"
+                + "</shared-user>"
+                + "<keyset-settings version=\"1\">"
+                + "<keys>"
+                + "<public-key identifier=\"1\" value=\"" + KeySetStrings.ctsKeySetPublicKeyA + "\" />"
+                + "<public-key identifier=\"2\" value=\"" + KeySetStrings.ctsKeySetPublicKeyB + "\" />"
+                + "<public-key identifier=\"3\" value=\"" + KeySetStrings.ctsKeySetPublicKeyC + "\" />"
+                + "</keys>"
+                + "<keysets>"
+                + "<keyset identifier=\"1\">"
+                + "<key-id identifier=\"1\" />"
+                + "</keyset>"
+                + "<keyset identifier=\"2\">"
+                + "<key-id identifier=\"2\" />"
+                + "</keyset>"
+                + "<keyset identifier=\"3\">"
+                + "<key-id identifier=\"3\" />"
+                + "</keyset>"
+                + "<keyset identifier=\"4\">"
+                + "<key-id identifier=\"1\" />"
+                + "<key-id identifier=\"2\" />"
+                + "</keyset>"
+                + "</keysets>"
+                + "<lastIssuedKeyId value=\"3\" />"
+                + "<lastIssuedKeySetId value=\"4\" />"
+                + "</keyset-settings>"
+                + "</packages>").getBytes());
+    }
+
+    private void writePackageRestrictions_noSuspendingPackageXml(final int userId) {
+        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/users/"
+                        + userId + "/package-restrictions.xml"),
+                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                        + "<package-restrictions>\n"
+                        + "    <pkg name=\"" + PACKAGE_NAME_1 + "\" suspended=\"true\" />"
+                        + "    <pkg name=\"" + PACKAGE_NAME_2 + "\" suspended=\"false\" />"
+                        + "    <preferred-activities />\n"
+                        + "    <persistent-preferred-activities />\n"
+                        + "    <crossProfile-intent-filters />\n"
+                        + "    <default-apps />\n"
+                        + "</package-restrictions>\n")
+                        .getBytes());
+    }
+
+    private void writePackageRestrictions_noSuspendParamsMapXml(final int userId) {
+        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/users/"
+                        + userId + "/package-restrictions.xml"),
+                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                        + "<package-restrictions>\n"
+                        + "    <pkg name=\"" + PACKAGE_NAME_1 + "\" "
+                        + "     suspended=\"true\" suspending-package=\"" + PACKAGE_NAME_3 + "\">\n"
+                        + "        <suspended-dialog-info dialogMessage=\"Dialog Message\""
+                        + "         iconResId=\"" + TEST_RESOURCE_ID + "\"/>\n"
+                        + "        <suspended-app-extras>\n"
+                        + "            <string name=\"app_extra_string\">value</string>\n"
+                        + "        </suspended-app-extras>\n"
+                        + "        <suspended-launcher-extras>\n"
+                        + "            <long name=\"launcher_extra_long\" value=\"4\" />\n"
+                        + "        </suspended-launcher-extras>\n"
+                        + "    </pkg>\n"
+                        + "    <preferred-activities />\n"
+                        + "    <persistent-preferred-activities />\n"
+                        + "    <crossProfile-intent-filters />\n"
+                        + "    <default-apps />\n"
+                        + "</package-restrictions>\n")
+                        .getBytes());
+    }
+
+    private void writeStoppedPackagesXml() {
+        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages-stopped.xml"),
+                ( "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<stopped-packages>"
+                + "<pkg name=\"com.android.app1\" nl=\"1\" />"
+                + "<pkg name=\"com.android.app3\" nl=\"1\" />"
+                + "</stopped-packages>")
+                .getBytes());
+    }
+
+    private void writePackagesList() {
+        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages.list"),
+                ( "com.android.app1 11000 0 /data/data/com.android.app1 seinfo1"
+                + "com.android.app2 11001 0 /data/data/com.android.app2 seinfo2"
+                + "com.android.app3 11030 0 /data/data/com.android.app3 seinfo3")
+                .getBytes());
+    }
+
+    private void deleteSystemFolder() {
+        File systemFolder = new File(InstrumentationRegistry.getContext().getFilesDir(), "system");
+        deleteFolder(systemFolder);
+    }
+
+    private static void deleteFolder(File folder) {
+        File[] files = folder.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                deleteFolder(file);
+            }
+        }
+        folder.delete();
+    }
+
+    private void writeOldFiles() {
+        deleteSystemFolder();
+        writePackagesXml();
+        writeStoppedPackagesXml();
+        writePackagesList();
+    }
+
+    @Before
+    public void createUserManagerServiceRef() throws ReflectiveOperationException {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync((Runnable) () -> {
+            try {
+                // unregister the user manager from the local service
+                LocalServices.removeServiceForTest(UserManagerInternal.class);
+                new UserManagerService(InstrumentationRegistry.getContext());
+            } catch (Exception e) {
+                e.printStackTrace();
+                fail("Could not create user manager service; " + e);
+            }
+        });
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
+    }
+
+    private Settings makeSettings() {
+        return new Settings(InstrumentationRegistry.getContext().getFilesDir(),
+                mRuntimePermissionsPersistence, mPermissionDataProvider,
+                mDomainVerificationManager, mHandler,
+                new PackageManagerTracedLock());
+    }
+
+    private void verifyKeySetMetaData(Settings settings)
+            throws ReflectiveOperationException, IllegalAccessException {
+        WatchedArrayMap<String, PackageSetting> packages = settings.mPackages;
+        KeySetManagerService ksms = settings.getKeySetManagerService();
+
+        /* verify keyset and public key ref counts */
+        assertThat(KeySetUtils.getKeySetRefCount(ksms, 1), is(2));
+        assertThat(KeySetUtils.getKeySetRefCount(ksms, 2), is(1));
+        assertThat(KeySetUtils.getKeySetRefCount(ksms, 3), is(1));
+        assertThat(KeySetUtils.getKeySetRefCount(ksms, 4), is(1));
+        assertThat(KeySetUtils.getPubKeyRefCount(ksms, 1), is(2));
+        assertThat(KeySetUtils.getPubKeyRefCount(ksms, 2), is(2));
+        assertThat(KeySetUtils.getPubKeyRefCount(ksms, 3), is(1));
+
+        /* verify public keys properly read */
+        PublicKey keyA = parsePublicKey(KeySetStrings.ctsKeySetPublicKeyA);
+        PublicKey keyB = parsePublicKey(KeySetStrings.ctsKeySetPublicKeyB);
+        PublicKey keyC = parsePublicKey(KeySetStrings.ctsKeySetPublicKeyC);
+        assertThat(KeySetUtils.getPubKey(ksms, 1), is(keyA));
+        assertThat(KeySetUtils.getPubKey(ksms, 2), is(keyB));
+        assertThat(KeySetUtils.getPubKey(ksms, 3), is(keyC));
+
+        /* verify mapping is correct (ks -> pub keys) */
+        LongSparseArray<ArraySet<Long>> ksMapping = KeySetUtils.getKeySetMapping(ksms);
+        ArraySet<Long> mapping = ksMapping.get(1);
+        assertThat(mapping.size(), is(1));
+        assertThat(mapping.contains(new Long(1)), is(true));
+        mapping = ksMapping.get(2);
+        assertThat(mapping.size(), is(1));
+        assertThat(mapping.contains(new Long(2)), is(true));
+        mapping = ksMapping.get(3);
+        assertThat(mapping.size(), is(1));
+        assertThat(mapping.contains(new Long(3)), is(true));
+        mapping = ksMapping.get(4);
+        assertThat(mapping.size(), is(2));
+        assertThat(mapping.contains(new Long(1)), is(true));
+        assertThat(mapping.contains(new Long(2)), is(true));
+
+        /* verify lastIssuedIds are consistent */
+        assertThat(KeySetUtils.getLastIssuedKeyId(ksms), is(3L));
+        assertThat(KeySetUtils.getLastIssuedKeySetId(ksms), is(4L));
+
+        /* verify packages have been given the appropriate information */
+        PackageSetting ps = packages.get("com.android.app1");
+        assertThat(ps.getKeySetData().getProperSigningKeySet(), is(1L));
+        ps = packages.get("com.android.app2");
+        assertThat(ps.getKeySetData().getProperSigningKeySet(), is(1L));
+        assertThat(ps.getKeySetData().getAliases().get("AB"), is(4L));
+        ps = packages.get("com.android.app3");
+        assertThat(ps.getKeySetData().getProperSigningKeySet(), is(2L));
+        assertThat(ps.getKeySetData().getAliases().get("C"), is(3L));
+        assertThat(ps.getKeySetData().getUpgradeKeySets().length, is(1));
+        assertThat(ps.getKeySetData().getUpgradeKeySets()[0], is(3L));
+    }
+
+    @NonNull
+    private AndroidPackage mockAndroidPackage(PackageSetting pkgSetting) {
+        return PackageImpl.forTesting(pkgSetting.getPackageName()).hideAsParsed().hideAsFinal();
+    }
+
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerTests.java
new file mode 100644
index 0000000..92bdd64
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerTests.java
@@ -0,0 +1,3022 @@
+/*
+ * 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.system.OsConstants.S_IFDIR;
+import static android.system.OsConstants.S_IFMT;
+import static android.system.OsConstants.S_IRGRP;
+import static android.system.OsConstants.S_IROTH;
+import static android.system.OsConstants.S_IRWXU;
+import static android.system.OsConstants.S_ISDIR;
+import static android.system.OsConstants.S_IXGRP;
+import static android.system.OsConstants.S_IXOTH;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.KeySet;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PermissionInfo;
+import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.StatFs;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.filters.Suppress;
+
+import com.android.server.pm.test.service.server.R;
+import com.android.internal.content.InstallLocationUtils;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+
+import dalvik.system.VMRuntime;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+public class PackageManagerTests extends AndroidTestCase {
+    private static final boolean localLOGV = true;
+
+    public static final String TAG = "PackageManagerTests";
+
+    public static final long MAX_WAIT_TIME = 25 * 1000;
+
+    public static final long WAIT_TIME_INCR = 5 * 1000;
+
+    private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec";
+
+    private static final int APP_INSTALL_AUTO = InstallLocationUtils.APP_INSTALL_AUTO;
+
+    private static final int APP_INSTALL_DEVICE = InstallLocationUtils.APP_INSTALL_INTERNAL;
+
+    private static final int APP_INSTALL_SDCARD = InstallLocationUtils.APP_INSTALL_EXTERNAL;
+
+    void failStr(String errMsg) {
+        Log.w(TAG, "errMsg=" + errMsg);
+        fail(errMsg);
+    }
+
+    void failStr(Exception e) {
+        failStr(e.getMessage());
+    }
+
+    private abstract static class GenericReceiver extends BroadcastReceiver {
+        private boolean doneFlag = false;
+
+        boolean received = false;
+
+        Intent intent;
+
+        IntentFilter filter;
+
+        abstract boolean notifyNow(Intent intent);
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (notifyNow(intent)) {
+                synchronized (this) {
+                    received = true;
+                    doneFlag = true;
+                    this.intent = intent;
+                    notifyAll();
+                }
+            }
+        }
+
+        public boolean isDone() {
+            return doneFlag;
+        }
+
+        public void setFilter(IntentFilter filter) {
+            this.filter = filter;
+        }
+    }
+
+    private static class InstallReceiver extends GenericReceiver {
+        String pkgName;
+
+        InstallReceiver(String pkgName) {
+            this.pkgName = pkgName;
+            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+            filter.addDataScheme("package");
+            super.setFilter(filter);
+        }
+
+        public boolean notifyNow(Intent intent) {
+            String action = intent.getAction();
+            if (!Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+                return false;
+            }
+            Uri data = intent.getData();
+            String installedPkg = data.getEncodedSchemeSpecificPart();
+            if (pkgName.equals(installedPkg)) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private static class LocalIntentReceiver {
+        private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
+
+        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+            @Override
+            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+                try {
+                    mResult.offer(intent, 5, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        };
+
+        public IntentSender getIntentSender() {
+            return new IntentSender((IIntentSender) mLocalSender);
+        }
+
+        public Intent getResult() {
+            try {
+                return mResult.take();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private PackageManager getPm() {
+        return mContext.getPackageManager();
+    }
+
+    private PackageInstaller getPi() {
+        return getPm().getPackageInstaller();
+    }
+
+    private void writeSplitToInstallSession(PackageInstaller.Session session, String inPath,
+            String splitName) throws RemoteException {
+        long sizeBytes = 0;
+        final File file = new File(inPath);
+        if (file.isFile()) {
+            sizeBytes = file.length();
+        } else {
+            return;
+        }
+
+        InputStream in = null;
+        OutputStream out = null;
+        try {
+            in = new FileInputStream(inPath);
+            out = session.openWrite(splitName, 0, sizeBytes);
+
+            int total = 0;
+            byte[] buffer = new byte[65536];
+            int c;
+            while ((c = in.read(buffer)) != -1) {
+                total += c;
+                out.write(buffer, 0, c);
+            }
+            session.fsync(out);
+        } catch (IOException e) {
+            fail("Error: failed to write; " + e.getMessage());
+        } finally {
+            IoUtils.closeQuietly(out);
+            IoUtils.closeQuietly(in);
+            IoUtils.closeQuietly(session);
+        }
+    }
+
+    private void invokeInstallPackage(Uri packageUri, int flags, GenericReceiver receiver,
+            boolean shouldSucceed) {
+        mContext.registerReceiver(receiver, receiver.filter);
+        synchronized (receiver) {
+            final String inPath = packageUri.getPath();
+            PackageInstaller.Session session = null;
+            try {
+                final SessionParams sessionParams =
+                        new SessionParams(SessionParams.MODE_FULL_INSTALL);
+                sessionParams.installFlags = flags;
+                final int sessionId = getPi().createSession(sessionParams);
+                session = getPi().openSession(sessionId);
+                writeSplitToInstallSession(session, inPath, "base.apk");
+                final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
+                session.commit(localReceiver.getIntentSender());
+                final Intent result = localReceiver.getResult();
+                final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                        PackageInstaller.STATUS_FAILURE);
+                if (shouldSucceed) {
+                    if (status != PackageInstaller.STATUS_SUCCESS) {
+                        fail("Installation should have succeeded, but got code " + status);
+                    }
+                } else {
+                    if (status == PackageInstaller.STATUS_SUCCESS) {
+                        fail("Installation should have failed");
+                    }
+                    // We'll never get a broadcast since the package failed to install
+                    return;
+                }
+                // Verify we received the broadcast
+                long waitTime = 0;
+                while ((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME)) {
+                    try {
+                        receiver.wait(WAIT_TIME_INCR);
+                        waitTime += WAIT_TIME_INCR;
+                    } catch (InterruptedException e) {
+                        Log.i(TAG, "Interrupted during sleep", e);
+                    }
+                }
+                if (!receiver.isDone()) {
+                    fail("Timed out waiting for PACKAGE_ADDED notification");
+                }
+            } catch (IllegalArgumentException | IOException | RemoteException e) {
+                Log.w(TAG, "Failed to install package; path=" + inPath, e);
+                fail("Failed to install package; path=" + inPath + ", e=" + e);
+            } finally {
+                IoUtils.closeQuietly(session);
+                mContext.unregisterReceiver(receiver);
+            }
+        }
+    }
+
+    private void invokeInstallPackageFail(Uri packageUri, int flags, int expectedResult) {
+        final String inPath = packageUri.getPath();
+        PackageInstaller.Session session = null;
+        try {
+            final SessionParams sessionParams =
+                    new SessionParams(SessionParams.MODE_FULL_INSTALL);
+            sessionParams.installFlags = flags;
+            final int sessionId = getPi().createSession(sessionParams);
+            session = getPi().openSession(sessionId);
+            writeSplitToInstallSession(session, inPath, "base.apk");
+            final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
+            session.commit(localReceiver.getIntentSender());
+            final Intent result = localReceiver.getResult();
+            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                    PackageInstaller.STATUS_SUCCESS);
+            String statusMessage = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+            assertEquals(statusMessage, expectedResult, status);
+        } catch (IllegalArgumentException | IOException | RemoteException e) {
+            Log.w(TAG, "Failed to install package; path=" + inPath, e);
+            fail("Failed to install package; path=" + inPath + ", e=" + e);
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+    }
+
+    private Uri getInstallablePackage(int fileResId, File outFile) {
+        Resources res = mContext.getResources();
+        InputStream is = null;
+        try {
+            is = res.openRawResource(fileResId);
+        } catch (NotFoundException e) {
+            failStr("Failed to load resource with id: " + fileResId);
+        }
+        FileUtils.setPermissions(outFile.getPath(),
+                FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IRWXO,
+                -1, -1);
+        assertTrue(FileUtils.copyToFile(is, outFile));
+        FileUtils.setPermissions(outFile.getPath(),
+                FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IRWXO,
+                -1, -1);
+        return Uri.fromFile(outFile);
+    }
+
+    private ParsedPackage parsePackage(Uri packageURI) {
+        final String archiveFilePath = packageURI.getPath();
+        ParseResult<ParsedPackage> result = ParsingPackageUtils.parseDefaultOneTime(
+                new File(archiveFilePath), 0 /*flags*/, Collections.emptyList(),
+                false /*collectCertificates*/);
+        if (result.isError()) {
+            throw new IllegalStateException(result.getErrorMessage(), result.getException());
+        }
+        return result.getResult();
+    }
+
+    private boolean checkSd(long pkgLen) {
+        String status = Environment.getExternalStorageState();
+        if (!status.equals(Environment.MEDIA_MOUNTED)) {
+            return false;
+        }
+        long sdSize = -1;
+        StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath());
+        sdSize = (long) sdStats.getAvailableBlocks() * (long) sdStats.getBlockSize();
+        // TODO check for thresholds here
+        return pkgLen <= sdSize;
+
+    }
+
+    private boolean checkInt(long pkgLen) {
+        StatFs intStats = new StatFs(Environment.getDataDirectory().getPath());
+        long intSize = (long) intStats.getBlockCount() * (long) intStats.getBlockSize();
+        long iSize = (long) intStats.getAvailableBlocks() * (long) intStats.getBlockSize();
+        // TODO check for thresholds here?
+        return pkgLen <= iSize;
+    }
+
+    private static final int INSTALL_LOC_INT = 1;
+
+    private static final int INSTALL_LOC_SD = 2;
+
+    private static final int INSTALL_LOC_ERR = -1;
+
+    private int getInstallLoc(int flags, int expInstallLocation, long pkgLen) {
+        // Flags explicitly over ride everything else.
+        if ((flags & PackageManager.INSTALL_INTERNAL) != 0) {
+            return INSTALL_LOC_INT;
+        }
+        // Manifest option takes precedence next
+        if (expInstallLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
+            if (checkSd(pkgLen)) {
+                return INSTALL_LOC_SD;
+            }
+            if (checkInt(pkgLen)) {
+                return INSTALL_LOC_INT;
+            }
+            return INSTALL_LOC_ERR;
+        }
+        if (expInstallLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+            if (checkInt(pkgLen)) {
+                return INSTALL_LOC_INT;
+            }
+            return INSTALL_LOC_ERR;
+        }
+        if (expInstallLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
+            // Check for free memory internally
+            if (checkInt(pkgLen)) {
+                return INSTALL_LOC_INT;
+            }
+            // Check for free memory externally
+            if (checkSd(pkgLen)) {
+                return INSTALL_LOC_SD;
+            }
+            return INSTALL_LOC_ERR;
+        }
+        // Check for settings preference.
+        boolean checkSd = false;
+        int userPref = getDefaultInstallLoc();
+        if (userPref == APP_INSTALL_DEVICE) {
+            if (checkInt(pkgLen)) {
+                return INSTALL_LOC_INT;
+            }
+            return INSTALL_LOC_ERR;
+        } else if (userPref == APP_INSTALL_SDCARD) {
+            if (checkSd(pkgLen)) {
+                return INSTALL_LOC_SD;
+            }
+            return INSTALL_LOC_ERR;
+        }
+        // Default system policy for apps with no manifest option specified.
+        // Check for free memory internally
+        if (checkInt(pkgLen)) {
+            return INSTALL_LOC_INT;
+        }
+        return INSTALL_LOC_ERR;
+    }
+
+    private void assertInstall(ParsedPackage pkg, int flags, int expInstallLocation) {
+        try {
+            String pkgName = pkg.getPackageName();
+            ApplicationInfo info = getPm().getApplicationInfo(pkgName, 0);
+            assertNotNull(info);
+            assertEquals(pkgName, info.packageName);
+            File dataDir = Environment.getDataDirectory();
+            String appInstallParent = new File(dataDir, "app").getPath();
+            File srcDir = new File(info.sourceDir);
+            String srcPathParent = srcDir.getParentFile().getParentFile().getParent();
+            File publicSrcDir = new File(info.publicSourceDir);
+            String publicSrcPath = publicSrcDir.getParentFile().getParentFile().getParent();
+            long pkgLen = new File(info.sourceDir).length();
+            String expectedLibPath = new File(new File(info.sourceDir).getParentFile(), "lib")
+                    .getPath();
+
+            int rLoc = getInstallLoc(flags, expInstallLocation, pkgLen);
+            if (rLoc == INSTALL_LOC_INT) {
+                assertEquals(appInstallParent, srcPathParent);
+                assertEquals(appInstallParent, publicSrcPath);
+                assertStartsWith("Native library should point to shared lib directory",
+                        expectedLibPath, info.nativeLibraryDir);
+                assertDirOwnerGroupPermsIfExists(
+                        "Native library directory should be owned by system:system and 0755",
+                        Process.SYSTEM_UID, Process.SYSTEM_UID,
+                        S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH,
+                        info.nativeLibraryDir);
+                assertFalse((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
+
+                // Make sure the native library dir is not a symlink
+                final File nativeLibDir = new File(info.nativeLibraryDir);
+                if (nativeLibDir.exists()) {
+                    try {
+                        assertEquals("Native library dir should not be a symlink",
+                                info.nativeLibraryDir, nativeLibDir.getCanonicalPath());
+                    } catch (IOException e) {
+                        fail("Can't read " + nativeLibDir.getPath());
+                    }
+                }
+            } else if (rLoc == INSTALL_LOC_SD) {
+                assertTrue("Application flags (" + info.flags
+                        + ") should contain FLAG_EXTERNAL_STORAGE",
+                        (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
+                // Might need to check:
+                // ((info.privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0)
+                assertStartsWith("The APK path should point to the ASEC",
+                        SECURE_CONTAINERS_PREFIX, srcPathParent);
+                assertStartsWith("The public APK path should point to the ASEC",
+                        SECURE_CONTAINERS_PREFIX, publicSrcPath);
+                assertStartsWith("The native library path should point to the ASEC",
+                        SECURE_CONTAINERS_PREFIX, info.nativeLibraryDir);
+
+                // Make sure the native library in /data/data/<app>/lib is a
+                // symlink to the ASEC
+                final File nativeLibSymLink = new File(info.dataDir, "lib");
+                assertTrue("Native library symlink should exist at " + nativeLibSymLink.getPath(),
+                        nativeLibSymLink.exists());
+                try {
+                    assertEquals(nativeLibSymLink.getPath() + " should be a symlink to "
+                            + info.nativeLibraryDir, info.nativeLibraryDir,
+                            nativeLibSymLink.getCanonicalPath());
+                } catch (IOException e) {
+                    fail("Can't read " + nativeLibSymLink.getPath());
+                }
+            } else {
+                // TODO handle error. Install should have failed.
+                fail("Install should have failed");
+            }
+        } catch (NameNotFoundException e) {
+            failStr("failed with exception : " + e);
+        }
+    }
+
+    private void assertDirOwnerGroupPermsIfExists(String reason, int uid, int gid, int perms,
+            String path) {
+        if (!new File(path).exists()) {
+            return;
+        }
+
+        final StructStat stat;
+        try {
+            stat = Os.lstat(path);
+        } catch (ErrnoException e) {
+            throw new AssertionError(reason + "\n" + "Got: " + path + " does not exist");
+        }
+
+        StringBuilder sb = new StringBuilder();
+
+        if (!S_ISDIR(stat.st_mode)) {
+            sb.append("\nExpected type: ");
+            sb.append(S_IFDIR);
+            sb.append("\ngot type: ");
+            sb.append((stat.st_mode & S_IFMT));
+        }
+
+        if (stat.st_uid != uid) {
+            sb.append("\nExpected owner: ");
+            sb.append(uid);
+            sb.append("\nGot owner: ");
+            sb.append(stat.st_uid);
+        }
+
+        if (stat.st_gid != gid) {
+            sb.append("\nExpected group: ");
+            sb.append(gid);
+            sb.append("\nGot group: ");
+            sb.append(stat.st_gid);
+        }
+
+        if ((stat.st_mode & ~S_IFMT) != perms) {
+            sb.append("\nExpected permissions: ");
+            sb.append(Integer.toOctalString(perms));
+            sb.append("\nGot permissions: ");
+            sb.append(Integer.toOctalString(stat.st_mode & ~S_IFMT));
+        }
+
+        if (sb.length() > 0) {
+            throw new AssertionError(reason + sb.toString());
+        }
+    }
+
+    private static void assertStartsWith(String prefix, String actual) {
+        assertStartsWith("", prefix, actual);
+    }
+
+    private static void assertStartsWith(String description, String prefix, String actual) {
+        if (!actual.startsWith(prefix)) {
+            StringBuilder sb = new StringBuilder(description);
+            sb.append("\nExpected prefix: ");
+            sb.append(prefix);
+            sb.append("\n     got: ");
+            sb.append(actual);
+            sb.append('\n');
+            throw new AssertionError(sb.toString());
+        }
+    }
+
+    private void assertNotInstalled(String pkgName) {
+        try {
+            ApplicationInfo info = getPm().getApplicationInfo(pkgName, 0);
+            fail(pkgName + " shouldnt be installed");
+        } catch (NameNotFoundException e) {
+        }
+    }
+
+    class InstallParams {
+        Uri packageURI;
+
+        ParsedPackage pkg;
+
+        InstallParams(String outFileName, int rawResId) {
+            this.pkg = getParsedPackage(outFileName, rawResId);
+            this.packageURI = Uri.fromFile(new File(pkg.getPath()));
+        }
+
+        InstallParams(ParsedPackage pkg) {
+            this.packageURI = Uri.fromFile(new File(pkg.getPath()));
+            this.pkg = pkg;
+        }
+
+        long getApkSize() {
+            File file = new File(pkg.getPath());
+            return file.length();
+        }
+    }
+
+    private InstallParams sampleInstallFromRawResource(int flags, boolean cleanUp)
+            throws Exception {
+        return installFromRawResource("install.apk", R.raw.install, flags, cleanUp, false, -1,
+                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+    }
+
+    static final String PERM_PACKAGE = "package";
+
+    static final String PERM_DEFINED = "defined";
+
+    static final String PERM_UNDEFINED = "undefined";
+
+    static final String PERM_USED = "used";
+
+    static final String PERM_NOTUSED = "notused";
+
+    private void assertPermissions(String[] cmds) {
+        final PackageManager pm = getPm();
+        String pkg = null;
+        PackageInfo pkgInfo = null;
+        String mode = PERM_DEFINED;
+        int i = 0;
+        while (i < cmds.length) {
+            String cmd = cmds[i++];
+            if (cmd == PERM_PACKAGE) {
+                pkg = cmds[i++];
+                try {
+                    pkgInfo = pm.getPackageInfo(pkg,
+                            PackageManager.GET_PERMISSIONS
+                            | PackageManager.MATCH_UNINSTALLED_PACKAGES);
+                } catch (NameNotFoundException e) {
+                    pkgInfo = null;
+                }
+            } else if (cmd == PERM_DEFINED || cmd == PERM_UNDEFINED
+                    || cmd == PERM_USED || cmd == PERM_NOTUSED) {
+                mode = cmds[i++];
+            } else {
+                if (mode == PERM_DEFINED) {
+                    try {
+                        PermissionInfo pi = pm.getPermissionInfo(cmd, 0);
+                        assertNotNull(pi);
+                        assertEquals(pi.packageName, pkg);
+                        assertEquals(pi.name, cmd);
+                        assertNotNull(pkgInfo);
+                        boolean found = false;
+                        for (int j = 0; j < pkgInfo.permissions.length && !found; j++) {
+                            if (pkgInfo.permissions[j].name.equals(cmd)) {
+                                found = true;
+                            }
+                        }
+                        if (!found) {
+                            fail("Permission not found: " + cmd);
+                        }
+                    } catch (NameNotFoundException e) {
+                        throw new RuntimeException(e);
+                    }
+                } else if (mode == PERM_UNDEFINED) {
+                    try {
+                        pm.getPermissionInfo(cmd, 0);
+                        throw new RuntimeException("Permission exists: " + cmd);
+                    } catch (NameNotFoundException e) {
+                    }
+                    if (pkgInfo != null) {
+                        boolean found = false;
+                        for (int j = 0; j < pkgInfo.permissions.length && !found; j++) {
+                            if (pkgInfo.permissions[j].name.equals(cmd)) {
+                                found = true;
+                            }
+                        }
+                        if (found) {
+                            fail("Permission still exists: " + cmd);
+                        }
+                    }
+                } else if (mode == PERM_USED || mode == PERM_NOTUSED) {
+                    boolean found = false;
+                    for (int j = 0; j < pkgInfo.requestedPermissions.length && !found; j++) {
+                        if (pkgInfo.requestedPermissions[j].equals(cmd)) {
+                            found = true;
+                        }
+                    }
+                    if (!found) {
+                        fail("Permission not requested: " + cmd);
+                    }
+                    if (mode == PERM_USED) {
+                        if (pm.checkPermission(cmd, pkg) != PackageManager.PERMISSION_GRANTED) {
+                            fail("Permission not granted: " + cmd);
+                        }
+                    } else {
+                        if (pm.checkPermission(cmd, pkg) != PackageManager.PERMISSION_DENIED) {
+                            fail("Permission granted: " + cmd);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private ParsedPackage getParsedPackage(String outFileName, int rawResId) {
+        PackageManager pm = mContext.getPackageManager();
+        File filesDir = mContext.getFilesDir();
+        File outFile = new File(filesDir, outFileName);
+        Uri packageURI = getInstallablePackage(rawResId, outFile);
+        return parsePackage(packageURI);
+    }
+
+    /*
+     * Utility function that reads a apk bundled as a raw resource
+     * copies it into own data directory and invokes
+     * PackageManager api to install it.
+     */
+    private void installFromRawResource(InstallParams ip, int flags, boolean cleanUp, boolean fail,
+            int result, int expInstallLocation) throws Exception {
+        PackageManager pm = mContext.getPackageManager();
+        ParsedPackage pkg = ip.pkg;
+        Uri packageURI = ip.packageURI;
+        if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) {
+            // Make sure the package doesn't exist
+            try {
+                ApplicationInfo appInfo = pm.getApplicationInfo(pkg.getPackageName(),
+                        PackageManager.MATCH_UNINSTALLED_PACKAGES);
+                GenericReceiver receiver = new DeleteReceiver(pkg.getPackageName());
+                invokeDeletePackage(pkg.getPackageName(), 0, receiver);
+            } catch (IllegalArgumentException | NameNotFoundException e) {
+            }
+        }
+        try {
+            if (fail) {
+                invokeInstallPackageFail(packageURI, flags, result);
+                if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) {
+                    assertNotInstalled(pkg.getPackageName());
+                }
+            } else {
+                InstallReceiver receiver = new InstallReceiver(pkg.getPackageName());
+                invokeInstallPackage(packageURI, flags, receiver, true);
+                // Verify installed information
+                assertInstall(pkg, flags, expInstallLocation);
+            }
+        } finally {
+            if (cleanUp) {
+                cleanUpInstall(ip);
+            }
+        }
+    }
+
+    /*
+     * Utility function that reads a apk bundled as a raw resource
+     * copies it into own data directory and invokes
+     * PackageManager api to install it.
+     */
+    private InstallParams installFromRawResource(String outFileName, int rawResId, int flags,
+            boolean cleanUp, boolean fail, int result, int expInstallLocation) throws Exception {
+        InstallParams ip = new InstallParams(outFileName, rawResId);
+        installFromRawResource(ip, flags, cleanUp, fail, result, expInstallLocation);
+        return ip;
+    }
+
+    @LargeTest
+    public void testInstallNormalInternal() throws Exception {
+        sampleInstallFromRawResource(0, true);
+    }
+
+    /* ------------------------- Test replacing packages -------------- */
+    class ReplaceReceiver extends GenericReceiver {
+        String pkgName;
+
+        final static int INVALID = -1;
+
+        final static int REMOVED = 1;
+
+        final static int ADDED = 2;
+
+        final static int REPLACED = 3;
+
+        int removed = INVALID;
+
+        // for updated system apps only
+        boolean update = false;
+
+        ReplaceReceiver(String pkgName) {
+            this.pkgName = pkgName;
+            filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+            if (update) {
+                filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+            }
+            filter.addDataScheme("package");
+            super.setFilter(filter);
+        }
+
+        public boolean notifyNow(Intent intent) {
+            String action = intent.getAction();
+            Uri data = intent.getData();
+            String installedPkg = data.getEncodedSchemeSpecificPart();
+            if (pkgName == null || !pkgName.equals(installedPkg)) {
+                return false;
+            }
+            if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+                removed = REMOVED;
+            } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+                if (removed != REMOVED) {
+                    return false;
+                }
+                boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                if (!replacing) {
+                    return false;
+                }
+                removed = ADDED;
+                if (!update) {
+                    return true;
+                }
+            } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+                if (removed != ADDED) {
+                    return false;
+                }
+                removed = REPLACED;
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /*
+     * Utility function that reads a apk bundled as a raw resource
+     * copies it into own data directory and invokes
+     * PackageManager api to install first and then replace it
+     * again.
+     */
+    private void sampleReplaceFromRawResource(int flags) throws Exception {
+        InstallParams ip = sampleInstallFromRawResource(flags, false);
+        boolean replace = ((flags & PackageManager.INSTALL_REPLACE_EXISTING) != 0);
+        Log.i(TAG, "replace=" + replace);
+        GenericReceiver receiver;
+        if (replace) {
+            receiver = new ReplaceReceiver(ip.pkg.getPackageName());
+            Log.i(TAG, "Creating replaceReceiver");
+        } else {
+            receiver = new InstallReceiver(ip.pkg.getPackageName());
+        }
+        try {
+            invokeInstallPackage(ip.packageURI, flags, receiver, true);
+            if (replace) {
+                assertInstall(ip.pkg, flags, ip.pkg.getInstallLocation());
+            }
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    @LargeTest
+    public void testReplaceFlagDoesNotNeedToBeSet() throws Exception {
+        sampleReplaceFromRawResource(0);
+    }
+
+    @LargeTest
+    public void testReplaceNormalInternal() throws Exception {
+        sampleReplaceFromRawResource(PackageManager.INSTALL_REPLACE_EXISTING);
+    }
+
+    /* -------------- Delete tests --- */
+    private static class DeleteObserver extends IPackageDeleteObserver.Stub {
+        private CountDownLatch mLatch = new CountDownLatch(1);
+
+        private int mReturnCode;
+
+        private final String mPackageName;
+
+        private String mObservedPackage;
+
+        public DeleteObserver(String packageName) {
+            mPackageName = packageName;
+        }
+
+        public boolean isSuccessful() {
+            return mReturnCode == PackageManager.DELETE_SUCCEEDED;
+        }
+
+        public void packageDeleted(String packageName, int returnCode) throws RemoteException {
+            mObservedPackage = packageName;
+
+            mReturnCode = returnCode;
+
+            mLatch.countDown();
+        }
+
+        public void waitForCompletion(long timeoutMillis) {
+            final long deadline = SystemClock.uptimeMillis() + timeoutMillis;
+
+            long waitTime = timeoutMillis;
+            while (waitTime > 0) {
+                try {
+                    boolean done = mLatch.await(waitTime, TimeUnit.MILLISECONDS);
+                    if (done) {
+                        assertEquals(mPackageName, mObservedPackage);
+                        return;
+                    }
+                } catch (InterruptedException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+                waitTime = deadline - SystemClock.uptimeMillis();
+            }
+
+            throw new AssertionError("Timeout waiting for package deletion");
+        }
+    }
+
+    class DeleteReceiver extends GenericReceiver {
+        String pkgName;
+
+        DeleteReceiver(String pkgName) {
+            this.pkgName = pkgName;
+            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addDataScheme("package");
+            super.setFilter(filter);
+        }
+
+        public boolean notifyNow(Intent intent) {
+            String action = intent.getAction();
+            if (!Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+                return false;
+            }
+            Uri data = intent.getData();
+            String installedPkg = data.getEncodedSchemeSpecificPart();
+            if (pkgName.equals(installedPkg)) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    public boolean invokeDeletePackage(final String pkgName, int flags, GenericReceiver receiver)
+            throws Exception {
+        ApplicationInfo info = getPm().getApplicationInfo(pkgName,
+                PackageManager.MATCH_UNINSTALLED_PACKAGES);
+
+        mContext.registerReceiver(receiver, receiver.filter);
+        try {
+            final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
+            getPi().uninstall(pkgName,
+                    flags | PackageManager.DELETE_ALL_USERS,
+                    localReceiver.getIntentSender());
+            localReceiver.getResult();
+
+            assertUninstalled(info);
+
+            // Verify we received the broadcast
+            // TODO replace this with a CountDownLatch
+            synchronized (receiver) {
+                long waitTime = 0;
+                while ((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME)) {
+                    receiver.wait(WAIT_TIME_INCR);
+                    waitTime += WAIT_TIME_INCR;
+                }
+                if (!receiver.isDone()) {
+                    throw new Exception("Timed out waiting for PACKAGE_REMOVED notification");
+                }
+            }
+            return receiver.received;
+        } finally {
+            mContext.unregisterReceiver(receiver);
+        }
+    }
+
+    private static void assertUninstalled(ApplicationInfo info) throws Exception {
+        File nativeLibraryFile = new File(info.nativeLibraryDir);
+        assertFalse("Native library directory " + info.nativeLibraryDir
+                + " should be erased", nativeLibraryFile.exists());
+    }
+
+    public void deleteFromRawResource(int iFlags, int dFlags) throws Exception {
+        InstallParams ip = sampleInstallFromRawResource(iFlags, false);
+        boolean retainData = ((dFlags & PackageManager.DELETE_KEEP_DATA) != 0);
+        GenericReceiver receiver = new DeleteReceiver(ip.pkg.getPackageName());
+        try {
+            assertTrue(invokeDeletePackage(ip.pkg.getPackageName(), dFlags, receiver));
+            ApplicationInfo info = null;
+            Log.i(TAG, "okay4");
+            try {
+                info = getPm().getApplicationInfo(ip.pkg.getPackageName(),
+                        PackageManager.MATCH_UNINSTALLED_PACKAGES);
+            } catch (NameNotFoundException e) {
+                info = null;
+            }
+            if (retainData) {
+                assertNotNull(info);
+                assertEquals(info.packageName, ip.pkg.getPackageName());
+            } else {
+                assertNull(info);
+            }
+        } catch (Exception e) {
+            failStr(e);
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    @LargeTest
+    public void testDeleteNormalInternal() throws Exception {
+        deleteFromRawResource(0, 0);
+    }
+
+
+    @LargeTest
+    public void testDeleteNormalInternalRetainData() throws Exception {
+        deleteFromRawResource(0, PackageManager.DELETE_KEEP_DATA);
+    }
+
+    void cleanUpInstall(InstallParams ip) throws Exception {
+        if (ip == null) {
+            return;
+        }
+        Runtime.getRuntime().gc();
+        try {
+            cleanUpInstall(ip.pkg.getPackageName());
+        } finally {
+            File outFile = new File(ip.pkg.getPath());
+            if (outFile != null && outFile.exists()) {
+                outFile.delete();
+            }
+        }
+    }
+
+    private void cleanUpInstall(String pkgName) throws Exception {
+        if (pkgName == null) {
+            return;
+        }
+        Log.i(TAG, "Deleting package : " + pkgName);
+        try {
+            final ApplicationInfo info = getPm().getApplicationInfo(pkgName,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES);
+            if (info != null) {
+                final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
+                getPi().uninstall(pkgName,
+                        PackageManager.DELETE_ALL_USERS,
+                        localReceiver.getIntentSender());
+                localReceiver.getResult();
+                assertUninstalled(info);
+            }
+        } catch (IllegalArgumentException | NameNotFoundException e) {
+        }
+    }
+
+    @LargeTest
+    public void testManifestInstallLocationInternal() throws Exception {
+        installFromRawResource("install.apk", R.raw.install_loc_internal,
+                0, true, false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+    }
+
+    @LargeTest
+    public void testManifestInstallLocationSdcard() throws Exception {
+        // Do not run on devices with emulated external storage.
+        if (Environment.isExternalStorageEmulated()) {
+            return;
+        }
+
+        installFromRawResource("install.apk", R.raw.install_loc_sdcard,
+                0, true, false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+    }
+
+    @LargeTest
+    public void testManifestInstallLocationAuto() throws Exception {
+        installFromRawResource("install.apk", R.raw.install_loc_auto,
+                0, true, false, -1, PackageInfo.INSTALL_LOCATION_AUTO);
+    }
+
+    @LargeTest
+    public void testManifestInstallLocationUnspecified() throws Exception {
+        installFromRawResource("install.apk", R.raw.install_loc_unspecified,
+                0, true, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+    }
+
+    @LargeTest
+    public void testManifestInstallLocationReplaceInternalSdcard() throws Exception {
+        // Do not run on devices with emulated external storage.
+        if (Environment.isExternalStorageEmulated()) {
+            return;
+        }
+
+        int iFlags = 0;
+        int iApk = R.raw.install_loc_internal;
+        int rFlags = 0;
+        int rApk = R.raw.install_loc_sdcard;
+        InstallParams ip = installFromRawResource("install.apk", iApk,
+                iFlags, false,
+                false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+        GenericReceiver receiver = new ReplaceReceiver(ip.pkg.getPackageName());
+        int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
+        try {
+            InstallParams rp = installFromRawResource("install.apk", rApk,
+                    replaceFlags, false,
+                    false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+            assertInstall(rp.pkg, replaceFlags, rp.pkg.getInstallLocation());
+        } catch (Exception e) {
+            failStr("Failed with exception : " + e);
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    @LargeTest
+    public void testManifestInstallLocationReplaceSdcardInternal() throws Exception {
+        // Do not run on devices with emulated external storage.
+        if (Environment.isExternalStorageEmulated()) {
+            return;
+        }
+
+        int iFlags = 0;
+        int iApk = R.raw.install_loc_sdcard;
+        int rFlags = 0;
+        int rApk = R.raw.install_loc_unspecified;
+        InstallParams ip = installFromRawResource("install.apk", iApk,
+                iFlags, false,
+                false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+        int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
+        try {
+            InstallParams rp = installFromRawResource("install.apk", rApk,
+                    replaceFlags, false,
+                    false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+            assertInstall(rp.pkg, replaceFlags, ip.pkg.getInstallLocation());
+        } catch (Exception e) {
+            failStr("Failed with exception : " + e);
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    class MoveReceiver extends GenericReceiver {
+        String pkgName;
+
+        final static int INVALID = -1;
+
+        final static int REMOVED = 1;
+
+        final static int ADDED = 2;
+
+        int removed = INVALID;
+
+        MoveReceiver(String pkgName) {
+            this.pkgName = pkgName;
+            filter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+            filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+            super.setFilter(filter);
+        }
+
+        public boolean notifyNow(Intent intent) {
+            String action = intent.getAction();
+            Log.i(TAG, "MoveReceiver::" + action);
+            if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+                String[] list = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                if (list != null) {
+                    for (String pkg : list) {
+                        if (pkg.equals(pkgName)) {
+                            removed = REMOVED;
+                            break;
+                        }
+                    }
+                }
+                removed = REMOVED;
+            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+                if (removed != REMOVED) {
+                    return false;
+                }
+                String[] list = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                if (list != null) {
+                    for (String pkg : list) {
+                        if (pkg.equals(pkgName)) {
+                            removed = ADDED;
+                            return true;
+                        }
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    public boolean invokeMovePackage(String pkgName, int flags, GenericReceiver receiver)
+            throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    private boolean invokeMovePackageFail(String pkgName, int flags, int errCode) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    private int getDefaultInstallLoc() {
+        int origDefaultLoc = PackageInfo.INSTALL_LOCATION_AUTO;
+        try {
+            origDefaultLoc = Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.DEFAULT_INSTALL_LOCATION);
+        } catch (SettingNotFoundException e1) {
+        }
+        return origDefaultLoc;
+    }
+
+    private void setInstallLoc(int loc) {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEFAULT_INSTALL_LOCATION, loc);
+    }
+
+    /*
+     * Tests for moving apps between internal and external storage
+     */
+    /*
+     * Utility function that reads a apk bundled as a raw resource
+     * copies it into own data directory and invokes
+     * PackageManager api to install first and then replace it
+     * again.
+     */
+
+    private void moveFromRawResource(String outFileName, int rawResId, int installFlags,
+            int moveFlags, boolean cleanUp, boolean fail, int result) throws Exception {
+        int origDefaultLoc = getDefaultInstallLoc();
+        InstallParams ip = null;
+        try {
+            setInstallLoc(InstallLocationUtils.APP_INSTALL_AUTO);
+            // Install first
+            ip = installFromRawResource("install.apk", rawResId, installFlags, false,
+                    false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+            ApplicationInfo oldAppInfo = getPm().getApplicationInfo(ip.pkg.getPackageName(), 0);
+            if (fail) {
+                assertTrue(invokeMovePackageFail(ip.pkg.getPackageName(), moveFlags, result));
+                ApplicationInfo info = getPm().getApplicationInfo(ip.pkg.getPackageName(), 0);
+                assertNotNull(info);
+                assertEquals(oldAppInfo.flags, info.flags);
+            } else {
+                // Create receiver based on expRetCode
+                MoveReceiver receiver = new MoveReceiver(ip.pkg.getPackageName());
+                boolean retCode = invokeMovePackage(ip.pkg.getPackageName(), moveFlags, receiver);
+                assertTrue(retCode);
+                ApplicationInfo info = getPm().getApplicationInfo(ip.pkg.getPackageName(), 0);
+                assertNotNull("ApplicationInfo for recently installed application should exist",
+                        info);
+                if ((moveFlags & PackageManager.MOVE_INTERNAL) != 0) {
+                    assertTrue("ApplicationInfo.FLAG_EXTERNAL_STORAGE flag should NOT be set",
+                            (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0);
+                    assertStartsWith("Native library dir should be in dataDir",
+                            info.dataDir, info.nativeLibraryDir);
+                } else if ((moveFlags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0) {
+                    assertTrue("ApplicationInfo.FLAG_EXTERNAL_STORAGE flag should be set",
+                            (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
+                    assertStartsWith("Native library dir should point to ASEC",
+                            SECURE_CONTAINERS_PREFIX, info.nativeLibraryDir);
+                }
+            }
+        } catch (NameNotFoundException e) {
+            failStr("Pkg hasnt been installed correctly");
+        } finally {
+            if (ip != null) {
+                cleanUpInstall(ip);
+            }
+            // Restore default install location
+            setInstallLoc(origDefaultLoc);
+        }
+    }
+
+    private void sampleMoveFromRawResource(int installFlags, int moveFlags, boolean fail,
+            int result) throws Exception {
+        moveFromRawResource("install.apk",
+                R.raw.install, installFlags, moveFlags, true,
+                fail, result);
+    }
+
+    @LargeTest
+    public void testMoveAppInternalToExternal() throws Exception {
+        // Do not run on devices with emulated external storage.
+        if (Environment.isExternalStorageEmulated()) {
+            return;
+        }
+
+        int installFlags = PackageManager.INSTALL_INTERNAL;
+        int moveFlags = PackageManager.MOVE_EXTERNAL_MEDIA;
+        boolean fail = false;
+        int result = PackageManager.MOVE_SUCCEEDED;
+        sampleMoveFromRawResource(installFlags, moveFlags, fail, result);
+    }
+
+    @Suppress
+    @LargeTest
+    public void testMoveAppInternalToInternal() throws Exception {
+        int installFlags = PackageManager.INSTALL_INTERNAL;
+        int moveFlags = PackageManager.MOVE_INTERNAL;
+        boolean fail = true;
+        int result = PackageManager.MOVE_FAILED_INVALID_LOCATION;
+        sampleMoveFromRawResource(installFlags, moveFlags, fail, result);
+    }
+
+    @LargeTest
+    public void testMoveAppFailInternalToExternalDelete() throws Exception {
+        // Do not run on devices with emulated external storage.
+        if (Environment.isExternalStorageEmulated()) {
+            return;
+        }
+
+        int installFlags = 0;
+        int moveFlags = PackageManager.MOVE_EXTERNAL_MEDIA;
+        boolean fail = true;
+        final int result = PackageManager.MOVE_FAILED_DOESNT_EXIST;
+
+        int rawResId = R.raw.install;
+        int origDefaultLoc = getDefaultInstallLoc();
+        InstallParams ip = null;
+        try {
+            PackageManager pm = getPm();
+            setInstallLoc(InstallLocationUtils.APP_INSTALL_AUTO);
+            // Install first
+            ip = installFromRawResource("install.apk", R.raw.install, installFlags, false,
+                    false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+            // Delete the package now retaining data.
+            GenericReceiver receiver = new DeleteReceiver(ip.pkg.getPackageName());
+            invokeDeletePackage(ip.pkg.getPackageName(), PackageManager.DELETE_KEEP_DATA, receiver);
+            assertTrue(invokeMovePackageFail(ip.pkg.getPackageName(), moveFlags, result));
+        } catch (Exception e) {
+            failStr(e);
+        } finally {
+            if (ip != null) {
+                cleanUpInstall(ip);
+            }
+            // Restore default install location
+            setInstallLoc(origDefaultLoc);
+        }
+    }
+
+    /*---------- Recommended install location tests ----*/
+    /*
+     * PrecedenceSuffixes:
+     * Flag : FlagI, FlagE, FlagF
+     * I - internal, E - external, F - forward locked, Flag suffix absent if not using any option.
+     * Manifest: ManifestI, ManifestE, ManifestA, Manifest suffix absent if not using any option.
+     * Existing: Existing suffix absent if not existing.
+     * User: UserI, UserE, UserA, User suffix absent if not existing.
+     *
+     */
+
+    /*
+     * Install an app on internal flash
+     */
+    @LargeTest
+    public void testFlagI() throws Exception {
+        sampleInstallFromRawResource(PackageManager.INSTALL_INTERNAL, true);
+    }
+
+    /*
+     * Install an app with both internal and manifest option set.
+     * should install on internal.
+     */
+    @LargeTest
+    public void testFlagIManifestI() throws Exception {
+        installFromRawResource("install.apk", R.raw.install_loc_internal,
+                PackageManager.INSTALL_INTERNAL,
+                true,
+                false, -1,
+                PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+    }
+    /*
+     * Install an app with both internal and manifest preference for
+     * preferExternal. Should install on internal.
+     */
+    @LargeTest
+    public void testFlagIManifestE() throws Exception {
+        installFromRawResource("install.apk", R.raw.install_loc_sdcard,
+                PackageManager.INSTALL_INTERNAL,
+                true,
+                false, -1,
+                PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+    }
+    /*
+     * Install an app with both internal and manifest preference for
+     * auto. should install internal.
+     */
+    @LargeTest
+    public void testFlagIManifestA() throws Exception {
+        installFromRawResource("install.apk", R.raw.install_loc_auto,
+                PackageManager.INSTALL_INTERNAL,
+                true,
+                false, -1,
+                PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+    }
+
+    /*
+     * The following test functions verify install location for existing apps.
+     * ie existing app can be installed internally or externally. If install
+     * flag is explicitly set it should override current location. If manifest location
+     * is set, that should over ride current location too. if not the existing install
+     * location should be honoured.
+     * testFlagI/E/F/ExistingI/E -
+     */
+    @LargeTest
+    public void testFlagIExistingI() throws Exception {
+        int iFlags = PackageManager.INSTALL_INTERNAL;
+        int rFlags = PackageManager.INSTALL_INTERNAL | PackageManager.INSTALL_REPLACE_EXISTING;
+        // First install.
+        installFromRawResource("install.apk", R.raw.install,
+                iFlags,
+                false,
+                false, -1,
+                -1);
+        // Replace now
+        installFromRawResource("install.apk", R.raw.install,
+                rFlags,
+                true,
+                false, -1,
+                -1);
+    }
+
+    /*
+     * The following set of tests verify the installation of apps with
+     * install location attribute set to internalOnly, preferExternal and auto.
+     * The manifest option should dictate the install location.
+     * public void testManifestI/E/A
+     * TODO out of memory fall back behaviour.
+     */
+    @LargeTest
+    public void testManifestI() throws Exception {
+        installFromRawResource("install.apk", R.raw.install_loc_internal,
+                0,
+                true,
+                false, -1,
+                PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+    }
+
+    @LargeTest
+    public void testManifestE() throws Exception {
+        // Do not run on devices with emulated external storage.
+        if (Environment.isExternalStorageEmulated()) {
+            return;
+        }
+
+        installFromRawResource("install.apk", R.raw.install_loc_sdcard,
+                0,
+                true,
+                false, -1,
+                PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+    }
+
+    @LargeTest
+    public void testManifestA() throws Exception {
+        installFromRawResource("install.apk", R.raw.install_loc_auto,
+                0,
+                true,
+                false, -1,
+                PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+    }
+
+    /*
+     * The following set of tests verify the installation of apps
+     * with install location attribute set to internalOnly, preferExternal and auto
+     * for already existing apps. The manifest option should take precedence.
+     * TODO add out of memory fall back behaviour.
+     * testManifestI/E/AExistingI/E
+     */
+    @LargeTest
+    public void testManifestIExistingI() throws Exception {
+        int iFlags = PackageManager.INSTALL_INTERNAL;
+        int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+        // First install.
+        installFromRawResource("install.apk", R.raw.install,
+                iFlags,
+                false,
+                false, -1,
+                -1);
+        // Replace now
+        installFromRawResource("install.apk", R.raw.install_loc_internal,
+                rFlags,
+                true,
+                false, -1,
+                PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+    }
+
+    @LargeTest
+    public void testManifestEExistingI() throws Exception {
+        // Do not run on devices with emulated external storage.
+        if (Environment.isExternalStorageEmulated()) {
+            return;
+        }
+
+        int iFlags = PackageManager.INSTALL_INTERNAL;
+        int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+        // First install.
+        installFromRawResource("install.apk", R.raw.install,
+                iFlags,
+                false,
+                false, -1,
+                -1);
+        // Replace now
+        installFromRawResource("install.apk", R.raw.install_loc_sdcard,
+                rFlags,
+                true,
+                false, -1,
+                PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+    }
+
+    @LargeTest
+    public void testManifestAExistingI() throws Exception {
+        int iFlags = PackageManager.INSTALL_INTERNAL;
+        int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+        // First install.
+        installFromRawResource("install.apk", R.raw.install,
+                iFlags,
+                false,
+                false, -1,
+                -1);
+        // Replace now
+        installFromRawResource("install.apk", R.raw.install_loc_auto,
+                rFlags,
+                true,
+                false, -1,
+                PackageInfo.INSTALL_LOCATION_AUTO);
+    }
+
+    /*
+     * The following set of tests check install location for existing
+     * application based on user setting.
+     */
+    private int getExpectedInstallLocation(int userSetting) {
+        int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+        boolean enable = getUserSettingSetInstallLocation();
+        if (enable) {
+            if (userSetting == InstallLocationUtils.APP_INSTALL_AUTO) {
+                iloc = PackageInfo.INSTALL_LOCATION_AUTO;
+            } else if (userSetting == InstallLocationUtils.APP_INSTALL_EXTERNAL) {
+                iloc = PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL;
+            } else if (userSetting == InstallLocationUtils.APP_INSTALL_INTERNAL) {
+                iloc = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+            }
+        }
+        return iloc;
+    }
+
+    private void setExistingXUserX(int userSetting, int iFlags, int iloc) throws Exception {
+        int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+        // First install.
+        installFromRawResource("install.apk", R.raw.install,
+                iFlags,
+                false,
+                false, -1,
+                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        int origSetting = getDefaultInstallLoc();
+        try {
+            // Set user setting
+            setInstallLoc(userSetting);
+            // Replace now
+            installFromRawResource("install.apk", R.raw.install,
+                    rFlags,
+                    true,
+                    false, -1,
+                    iloc);
+        } finally {
+            setInstallLoc(origSetting);
+        }
+    }
+    @LargeTest
+    public void testExistingIUserI() throws Exception {
+        int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
+        int iFlags = PackageManager.INSTALL_INTERNAL;
+        setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+    }
+
+    @LargeTest
+    public void testExistingIUserE() throws Exception {
+        // Do not run on devices with emulated external storage.
+        if (Environment.isExternalStorageEmulated()) {
+            return;
+        }
+
+        int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
+        int iFlags = PackageManager.INSTALL_INTERNAL;
+        setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+    }
+
+    @LargeTest
+    public void testExistingIUserA() throws Exception {
+        int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
+        int iFlags = PackageManager.INSTALL_INTERNAL;
+        setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+    }
+
+    /*
+     * The following set of tests verify that the user setting defines
+     * the install location.
+     *
+     */
+    private boolean getUserSettingSetInstallLocation() {
+        try {
+            return Settings.Global.getInt(
+                    mContext.getContentResolver(), Settings.Global.SET_INSTALL_LOCATION) != 0;
+        } catch (SettingNotFoundException e1) {
+        }
+        return false;
+    }
+
+    private void setUserSettingSetInstallLocation(boolean value) {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.SET_INSTALL_LOCATION, value ? 1 : 0);
+    }
+
+    private void setUserX(boolean enable, int userSetting, int iloc) throws Exception {
+        boolean origUserSetting = getUserSettingSetInstallLocation();
+        int origSetting = getDefaultInstallLoc();
+        try {
+            setUserSettingSetInstallLocation(enable);
+            // Set user setting
+            setInstallLoc(userSetting);
+            // Replace now
+            installFromRawResource("install.apk", R.raw.install,
+                    0,
+                    true,
+                    false, -1,
+                    iloc);
+        } finally {
+            // Restore original setting
+            setUserSettingSetInstallLocation(origUserSetting);
+            setInstallLoc(origSetting);
+        }
+    }
+    @LargeTest
+    public void testUserI() throws Exception {
+        int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
+        int iloc = getExpectedInstallLocation(userSetting);
+        setUserX(true, userSetting, iloc);
+    }
+
+    @LargeTest
+    public void testUserE() throws Exception {
+        // Do not run on devices with emulated external storage.
+        if (Environment.isExternalStorageEmulated()) {
+            return;
+        }
+
+        int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
+        int iloc = getExpectedInstallLocation(userSetting);
+        setUserX(true, userSetting, iloc);
+    }
+
+    @LargeTest
+    public void testUserA() throws Exception {
+        int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
+        int iloc = getExpectedInstallLocation(userSetting);
+        setUserX(true, userSetting, iloc);
+    }
+
+    /*
+     * The following set of tests turn on/off the basic
+     * user setting for turning on install location.
+     */
+    @LargeTest
+    public void testUserPrefOffUserI() throws Exception {
+        int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
+        int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+        setUserX(false, userSetting, iloc);
+    }
+
+    @LargeTest
+    public void testUserPrefOffUserE() throws Exception {
+        // Do not run on devices with emulated external storage.
+        if (Environment.isExternalStorageEmulated()) {
+            return;
+        }
+
+        int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
+        int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+        setUserX(false, userSetting, iloc);
+    }
+
+    @LargeTest
+    public void testUserPrefOffA() throws Exception {
+        int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
+        int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+        setUserX(false, userSetting, iloc);
+    }
+
+    static final String BASE_PERMISSIONS_DEFINED[] = new String[] {
+        PERM_PACKAGE, "com.android.unit_tests.install_decl_perm",
+        PERM_DEFINED,
+        "com.android.frameworks.coretests.NORMAL",
+        "com.android.frameworks.coretests.DANGEROUS",
+        "com.android.frameworks.coretests.SIGNATURE",
+    };
+
+    static final String BASE_PERMISSIONS_UNDEFINED[] = new String[] {
+        PERM_PACKAGE, "com.android.frameworks.coretests.install_decl_perm",
+        PERM_UNDEFINED,
+        "com.android.frameworks.coretests.NORMAL",
+        "com.android.frameworks.coretests.DANGEROUS",
+        "com.android.frameworks.coretests.SIGNATURE",
+    };
+
+    static final String BASE_PERMISSIONS_USED[] = new String[] {
+        PERM_PACKAGE, "com.android.frameworks.coretests.install_use_perm_good",
+        PERM_USED,
+        "com.android.frameworks.coretests.NORMAL",
+        "com.android.frameworks.coretests.DANGEROUS",
+        "com.android.frameworks.coretests.SIGNATURE",
+    };
+
+    static final String BASE_PERMISSIONS_NOTUSED[] = new String[] {
+        PERM_PACKAGE, "com.android.frameworks.coretests.install_use_perm_good",
+        PERM_NOTUSED,
+        "com.android.frameworks.coretests.NORMAL",
+        "com.android.frameworks.coretests.DANGEROUS",
+        "com.android.frameworks.coretests.SIGNATURE",
+    };
+
+    static final String BASE_PERMISSIONS_SIGUSED[] = new String[] {
+        PERM_PACKAGE, "com.android.frameworks.coretests.install_use_perm_good",
+        PERM_USED,
+        "com.android.frameworks.coretests.SIGNATURE",
+        PERM_NOTUSED,
+        "com.android.frameworks.coretests.NORMAL",
+        "com.android.frameworks.coretests.DANGEROUS",
+    };
+
+    /*
+     * Ensure that permissions are properly declared.
+     */
+    @LargeTest
+    public void testInstallDeclaresPermissions() throws Exception {
+        InstallParams ip = null;
+        InstallParams ip2 = null;
+        try {
+            // **: Upon installing a package, are its declared permissions published?
+
+            int iFlags = PackageManager.INSTALL_INTERNAL;
+            int iApk = R.raw.install_decl_perm;
+            ip = installFromRawResource("install.apk", iApk,
+                    iFlags, false,
+                    false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+            assertInstall(ip.pkg, iFlags, ip.pkg.getInstallLocation());
+            assertPermissions(BASE_PERMISSIONS_DEFINED);
+
+            // **: Upon installing package, are its permissions granted?
+
+            int i2Flags = PackageManager.INSTALL_INTERNAL;
+            int i2Apk = R.raw.install_use_perm_good;
+            ip2 = installFromRawResource("install2.apk", i2Apk,
+                    i2Flags, false,
+                    false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+            assertInstall(ip2.pkg, i2Flags, ip2.pkg.getInstallLocation());
+            assertPermissions(BASE_PERMISSIONS_USED);
+
+            // **: Upon removing but not deleting, are permissions retained?
+
+            GenericReceiver receiver = new DeleteReceiver(ip.pkg.getPackageName());
+
+            try {
+                invokeDeletePackage(ip.pkg.getPackageName(), PackageManager.DELETE_KEEP_DATA, receiver);
+            } catch (Exception e) {
+                failStr(e);
+            }
+            assertPermissions(BASE_PERMISSIONS_DEFINED);
+            assertPermissions(BASE_PERMISSIONS_USED);
+
+            // **: Upon re-installing, are permissions retained?
+
+            ip = installFromRawResource("install.apk", iApk,
+                    iFlags | PackageManager.INSTALL_REPLACE_EXISTING, false,
+                    false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+            assertInstall(ip.pkg, iFlags, ip.pkg.getInstallLocation());
+            assertPermissions(BASE_PERMISSIONS_DEFINED);
+            assertPermissions(BASE_PERMISSIONS_USED);
+
+            // **: Upon deleting package, are all permissions removed?
+
+            try {
+                invokeDeletePackage(ip.pkg.getPackageName(), 0, receiver);
+                ip = null;
+            } catch (Exception e) {
+                failStr(e);
+            }
+            assertPermissions(BASE_PERMISSIONS_UNDEFINED);
+            assertPermissions(BASE_PERMISSIONS_NOTUSED);
+
+            // **: Delete package using permissions; nothing to check here.
+
+            GenericReceiver receiver2 = new DeleteReceiver(ip2.pkg.getPackageName());
+            try {
+                invokeDeletePackage(ip2.pkg.getPackageName(), 0, receiver);
+                ip2 = null;
+            } catch (Exception e) {
+                failStr(e);
+            }
+
+            // **: Re-install package using permissions; no permissions can be granted.
+
+            ip2 = installFromRawResource("install2.apk", i2Apk,
+                    i2Flags, false,
+                    false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+            assertInstall(ip2.pkg, i2Flags, ip2.pkg.getInstallLocation());
+            assertPermissions(BASE_PERMISSIONS_NOTUSED);
+
+            // **: Upon installing declaring package, are sig permissions granted
+            // to other apps (but not other perms)?
+
+            ip = installFromRawResource("install.apk", iApk,
+                    iFlags, false,
+                    false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+            assertInstall(ip.pkg, iFlags, ip.pkg.getInstallLocation());
+            assertPermissions(BASE_PERMISSIONS_DEFINED);
+            assertPermissions(BASE_PERMISSIONS_SIGUSED);
+
+            // **: Re-install package using permissions; are all permissions granted?
+
+            ip2 = installFromRawResource("install2.apk", i2Apk,
+                    i2Flags | PackageManager.INSTALL_REPLACE_EXISTING, false,
+                    false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+            assertInstall(ip2.pkg, i2Flags, ip2.pkg.getInstallLocation());
+            assertPermissions(BASE_PERMISSIONS_NOTUSED);
+
+            // **: Upon deleting package, are all permissions removed?
+
+            try {
+                invokeDeletePackage(ip.pkg.getPackageName(), 0, receiver);
+                ip = null;
+            } catch (Exception e) {
+                failStr(e);
+            }
+            assertPermissions(BASE_PERMISSIONS_UNDEFINED);
+            assertPermissions(BASE_PERMISSIONS_NOTUSED);
+
+            // **: Delete package using permissions; nothing to check here.
+
+            try {
+                invokeDeletePackage(ip2.pkg.getPackageName(), 0, receiver);
+                ip2 = null;
+            } catch (Exception e) {
+                failStr(e);
+            }
+
+        } finally {
+            if (ip2 != null) {
+                cleanUpInstall(ip2);
+            }
+            if (ip != null) {
+                cleanUpInstall(ip);
+            }
+        }
+    }
+
+    /*
+     * The following series of tests are related to upgrading apps with
+     * different certificates.
+     */
+    private static final int APP1_UNSIGNED = R.raw.install_app1_unsigned;
+
+    private static final int APP1_CERT1 = R.raw.install_app1_cert1;
+
+    private static final int APP1_CERT2 = R.raw.install_app1_cert2;
+
+    private static final int APP1_CERT1_CERT2 = R.raw.install_app1_cert1_cert2;
+
+    private static final int APP1_CERT3_CERT4 = R.raw.install_app1_cert3_cert4;
+
+    private static final int APP1_CERT3 = R.raw.install_app1_cert3;
+
+    private static final int APP1_CERT5 = R.raw.install_app1_cert5;
+
+    private static final int APP1_CERT5_ROTATED_CERT6 = R.raw.install_app1_cert5_rotated_cert6;
+
+    private static final int APP1_CERT6 = R.raw.install_app1_cert6;
+
+    private static final int APP2_UNSIGNED = R.raw.install_app2_unsigned;
+
+    private static final int APP2_CERT1 = R.raw.install_app2_cert1;
+
+    private static final int APP2_CERT2 = R.raw.install_app2_cert2;
+
+    private static final int APP2_CERT1_CERT2 = R.raw.install_app2_cert1_cert2;
+
+    private static final int APP2_CERT3 = R.raw.install_app2_cert3;
+
+    private static final int APP2_CERT5_ROTATED_CERT6 = R.raw.install_app2_cert5_rotated_cert6;
+
+    private InstallParams replaceCerts(int apk1, int apk2, boolean cleanUp, boolean fail,
+            int retCode) throws Exception {
+        int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+        String apk1Name = "install1.apk";
+        String apk2Name = "install2.apk";
+        var pkg1 = getParsedPackage(apk1Name, apk1);
+        try {
+            InstallParams ip = installFromRawResource(apk1Name, apk1, 0, false,
+                    false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+            installFromRawResource(apk2Name, apk2, rFlags, false,
+                    fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+            return ip;
+        } catch (Exception e) {
+            failStr(e.getMessage());
+        } finally {
+            if (cleanUp) {
+                cleanUpInstall(pkg1.getPackageName());
+            }
+        }
+        return null;
+    }
+
+    /*
+     * Test that an app signed with two certificates can be upgraded by the
+     * same app signed with two certificates.
+     */
+    @LargeTest
+    public void testReplaceMatchAllCerts() throws Exception {
+        replaceCerts(APP1_CERT1_CERT2, APP1_CERT1_CERT2, true, false, -1);
+    }
+
+    /*
+     * Test that an app signed with two certificates cannot be upgraded
+     * by an app signed with a different certificate.
+     */
+    @LargeTest
+    public void testReplaceMatchNoCerts1() throws Exception {
+        replaceCerts(APP1_CERT1_CERT2, APP1_CERT3, true, true,
+                PackageInstaller.STATUS_FAILURE_CONFLICT);
+    }
+
+    /*
+     * Test that an app signed with two certificates cannot be upgraded
+     * by an app signed with a different certificate.
+     */
+    @LargeTest
+    public void testReplaceMatchNoCerts2() throws Exception {
+        replaceCerts(APP1_CERT1_CERT2, APP1_CERT3_CERT4, true, true,
+                PackageInstaller.STATUS_FAILURE_CONFLICT);
+    }
+
+    /*
+     * Test that an app signed with two certificates cannot be upgraded by
+     * an app signed with a subset of initial certificates.
+     */
+    @LargeTest
+    public void testReplaceMatchSomeCerts1() throws Exception {
+        replaceCerts(APP1_CERT1_CERT2, APP1_CERT1, true, true,
+                PackageInstaller.STATUS_FAILURE_CONFLICT);
+    }
+
+    /*
+     * Test that an app signed with two certificates cannot be upgraded by
+     * an app signed with the last certificate.
+     */
+    @LargeTest
+    public void testReplaceMatchSomeCerts2() throws Exception {
+        replaceCerts(APP1_CERT1_CERT2, APP1_CERT2, true, true,
+                PackageInstaller.STATUS_FAILURE_CONFLICT);
+    }
+
+    /*
+     * Test that an app signed with a certificate can be upgraded by app
+     * signed with a superset of certificates.
+     */
+    @LargeTest
+    public void testReplaceMatchMoreCerts() throws Exception {
+        replaceCerts(APP1_CERT1, APP1_CERT1_CERT2, true, true,
+                PackageInstaller.STATUS_FAILURE_CONFLICT);
+    }
+
+    /*
+     * Test that an app signed with a certificate can be upgraded by app
+     * signed with a superset of certificates. Then verify that the an app
+     * signed with the original set of certs cannot upgrade the new one.
+     */
+    @LargeTest
+    public void testReplaceMatchMoreCertsReplaceSomeCerts() throws Exception {
+        InstallParams ip = replaceCerts(APP1_CERT1, APP1_CERT1_CERT2, false, true,
+                PackageInstaller.STATUS_FAILURE_CONFLICT);
+        try {
+            int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+            installFromRawResource("install.apk", APP1_CERT1, rFlags, false,
+                    false, -1,
+                    PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        } catch (Exception e) {
+            failStr(e.getMessage());
+        } finally {
+            if (ip != null) {
+                cleanUpInstall(ip);
+            }
+        }
+    }
+
+    /**
+     * The following tests are related to testing KeySets-based key rotation
+     */
+    /*
+     * Check if an apk which does not specify an upgrade-keyset may be upgraded
+     * by an apk which does
+     */
+    public void testNoKSToUpgradeKS() throws Exception {
+        replaceCerts(R.raw.keyset_sa_unone, R.raw.keyset_sa_ua, true, false, -1);
+    }
+
+    /*
+     * Check if an apk which does specify an upgrade-keyset may be downgraded to
+     * an apk which does not
+     */
+    public void testUpgradeKSToNoKS() throws Exception {
+        replaceCerts(R.raw.keyset_sa_ua, R.raw.keyset_sa_unone, true, false, -1);
+    }
+
+    /*
+     * Check if an apk signed by a key other than the upgrade keyset can update
+     * an app
+     */
+    public void testUpgradeKSWithWrongKey() throws Exception {
+        replaceCerts(R.raw.keyset_sa_ua, R.raw.keyset_sb_ua, true, true,
+                PackageInstaller.STATUS_FAILURE_CONFLICT);
+    }
+
+    /*
+     * Check if an apk signed by its signing key, which is not an upgrade key,
+     * can upgrade an app.
+     */
+    public void testUpgradeKSWithWrongSigningKey() throws Exception {
+        replaceCerts(R.raw.keyset_sa_ub, R.raw.keyset_sa_ub, true, true,
+                PackageInstaller.STATUS_FAILURE_CONFLICT);
+    }
+
+    /*
+     * Check if an apk signed by its upgrade key, which is not its signing key,
+     * can upgrade an app.
+     */
+    public void testUpgradeKSWithUpgradeKey() throws Exception {
+        replaceCerts(R.raw.keyset_sa_ub, R.raw.keyset_sb_ub, true, false, -1);
+    }
+    /*
+     * Check if an apk signed by its upgrade key, which is its signing key, can
+     * upgrade an app.
+     */
+    public void testUpgradeKSWithSigningUpgradeKey() throws Exception {
+        replaceCerts(R.raw.keyset_sa_ua, R.raw.keyset_sa_ua, true, false, -1);
+    }
+
+    /*
+     * Check if an apk signed by multiple keys, one of which is its upgrade key,
+     * can upgrade an app.
+     */
+    public void testMultipleUpgradeKSWithUpgradeKey() throws Exception {
+        replaceCerts(R.raw.keyset_sa_ua, R.raw.keyset_sab_ua, true, false, -1);
+    }
+
+    /*
+     * Check if an apk signed by multiple keys, one of which is its signing key,
+     * but none of which is an upgrade key, can upgrade an app.
+     */
+    public void testMultipleUpgradeKSWithSigningKey() throws Exception {
+        replaceCerts(R.raw.keyset_sau_ub, R.raw.keyset_sa_ua, true, true,
+                PackageInstaller.STATUS_FAILURE_CONFLICT);
+    }
+
+    /*
+     * Check if an apk which defines multiple (two) upgrade keysets is
+     * upgrade-able by either.
+     */
+    public void testUpgradeKSWithMultipleUpgradeKeySets() throws Exception {
+        replaceCerts(R.raw.keyset_sa_ua_ub, R.raw.keyset_sa_ua, true, false, -1);
+        replaceCerts(R.raw.keyset_sa_ua_ub, R.raw.keyset_sb_ub, true, false, -1);
+    }
+
+    /*
+     * Check if an apk's sigs are changed after upgrading with a non-signing
+     * key.
+     *
+     * TODO: consider checking against hard-coded Signatures in the Sig-tests
+     */
+    public void testSigChangeAfterUpgrade() throws Exception {
+        // install original apk and grab sigs
+        installFromRawResource("tmp.apk", R.raw.keyset_sa_ub,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        PackageManager pm = getPm();
+        String pkgName = "com.android.frameworks.coretests.keysets";
+        PackageInfo pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
+        assertTrue("Package should only have one signature, sig A",
+                pi.signatures.length == 1);
+        String sigBefore = pi.signatures[0].toCharsString();
+        // install apk signed by different upgrade KeySet
+        installFromRawResource("tmp2.apk", R.raw.keyset_sb_ub,
+                PackageManager.INSTALL_REPLACE_EXISTING, false, false, -1,
+                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
+        assertTrue("Package should only have one signature, sig B",
+                pi.signatures.length == 1);
+        String sigAfter = pi.signatures[0].toCharsString();
+        assertFalse("Package signatures did not change after upgrade!",
+                sigBefore.equals(sigAfter));
+        cleanUpInstall(pkgName);
+    }
+
+    /*
+     * Check if an apk's sig is the same  after upgrading with a signing
+     * key.
+     */
+    public void testSigSameAfterUpgrade() throws Exception {
+        // install original apk and grab sigs
+        installFromRawResource("tmp.apk", R.raw.keyset_sa_ua,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        PackageManager pm = getPm();
+        String pkgName = "com.android.frameworks.coretests.keysets";
+        PackageInfo pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
+        assertTrue("Package should only have one signature, sig A",
+                pi.signatures.length == 1);
+        String sigBefore = pi.signatures[0].toCharsString();
+        // install apk signed by same upgrade KeySet
+        installFromRawResource("tmp2.apk", R.raw.keyset_sa_ua,
+                PackageManager.INSTALL_REPLACE_EXISTING, false, false, -1,
+                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
+        assertTrue("Package should only have one signature, sig A",
+                pi.signatures.length == 1);
+        String sigAfter = pi.signatures[0].toCharsString();
+        assertTrue("Package signatures changed after upgrade!",
+                sigBefore.equals(sigAfter));
+        cleanUpInstall(pkgName);
+    }
+
+    /*
+     * Check if an apk's sigs are the same after upgrading with an app with
+     * a subset of the original signing keys.
+     */
+    public void testSigRemovedAfterUpgrade() throws Exception {
+        // install original apk and grab sigs
+        installFromRawResource("tmp.apk", R.raw.keyset_sab_ua,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        PackageManager pm = getPm();
+        String pkgName = "com.android.frameworks.coretests.keysets";
+        PackageInfo pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
+        assertTrue("Package should have two signatures, sig A and sig B",
+                pi.signatures.length == 2);
+        Set<String> sigsBefore = new HashSet<String>();
+        for (int i = 0; i < pi.signatures.length; i++) {
+            sigsBefore.add(pi.signatures[i].toCharsString());
+        }
+        // install apk signed subset upgrade KeySet
+        installFromRawResource("tmp2.apk", R.raw.keyset_sa_ua,
+                PackageManager.INSTALL_REPLACE_EXISTING, false, false, -1,
+                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
+        assertTrue("Package should only have one signature, sig A",
+                pi.signatures.length == 1);
+        String sigAfter = pi.signatures[0].toCharsString();
+        assertTrue("Original package signatures did not contain new sig",
+                sigsBefore.contains(sigAfter));
+        cleanUpInstall(pkgName);
+    }
+
+    /*
+     * Check if an apk's sigs are added to after upgrading with an app with
+     * a superset of the original signing keys.
+     */
+    public void testSigAddedAfterUpgrade() throws Exception {
+        // install original apk and grab sigs
+        installFromRawResource("tmp.apk", R.raw.keyset_sa_ua,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        PackageManager pm = getPm();
+        String pkgName = "com.android.frameworks.coretests.keysets";
+        PackageInfo pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
+        assertTrue("Package should only have one signature, sig A",
+                pi.signatures.length == 1);
+        String sigBefore = pi.signatures[0].toCharsString();
+        // install apk signed subset upgrade KeySet
+        installFromRawResource("tmp2.apk", R.raw.keyset_sab_ua,
+                PackageManager.INSTALL_REPLACE_EXISTING, false, false, -1,
+                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
+        assertTrue("Package should have two signatures, sig A and sig B",
+                pi.signatures.length == 2);
+        Set<String> sigsAfter = new HashSet<String>();
+        for (int i = 0; i < pi.signatures.length; i++) {
+            sigsAfter.add(pi.signatures[i].toCharsString());
+        }
+        assertTrue("Package signatures did not change after upgrade!",
+                sigsAfter.contains(sigBefore));
+        cleanUpInstall(pkgName);
+    }
+
+    /*
+     * Check if an apk gains signature-level permission after changing to the a
+     * new signature, for which a permission should be granted.
+     */
+    public void testUpgradeSigPermGained() throws Exception {
+        // install apk which defines permission
+        installFromRawResource("permDef.apk", R.raw.keyset_permdef_sa_unone,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        // install apk which uses permission but does not have sig
+        installFromRawResource("permUse.apk", R.raw.keyset_permuse_sb_ua_ub,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        // verify that package does not have perm before
+        PackageManager pm = getPm();
+        String permPkgName = "com.android.frameworks.coretests.keysets_permdef";
+        String pkgName = "com.android.frameworks.coretests.keysets";
+        String permName = "com.android.frameworks.coretests.keysets_permdef.keyset_perm";
+        assertFalse("keyset permission granted to app without same signature!",
+                    pm.checkPermission(permName, pkgName)
+                    == PackageManager.PERMISSION_GRANTED);
+        // upgrade to apk with perm signature
+        installFromRawResource("permUse2.apk", R.raw.keyset_permuse_sa_ua_ub,
+                PackageManager.INSTALL_REPLACE_EXISTING, false, false, -1,
+                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        assertTrue("keyset permission not granted to app after upgrade to same sig",
+                    pm.checkPermission(permName, pkgName)
+                    == PackageManager.PERMISSION_GRANTED);
+        cleanUpInstall(permPkgName);
+        cleanUpInstall(pkgName);
+    }
+
+    /*
+     * Check if an apk loses signature-level permission after changing to the a
+     * new signature, from one which a permission should be granted.
+     */
+    public void testUpgradeSigPermLost() throws Exception {
+        // install apk which defines permission
+        installFromRawResource("permDef.apk", R.raw.keyset_permdef_sa_unone,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        // install apk which uses permission, signed by same sig
+        installFromRawResource("permUse.apk", R.raw.keyset_permuse_sa_ua_ub,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        // verify that package does not have perm before
+        PackageManager pm = getPm();
+        String permPkgName = "com.android.frameworks.coretests.keysets_permdef";
+        String pkgName = "com.android.frameworks.coretests.keysets";
+        String permName = "com.android.frameworks.coretests.keysets_permdef.keyset_perm";
+        assertTrue("keyset permission not granted to app with same sig",
+                    pm.checkPermission(permName, pkgName)
+                    == PackageManager.PERMISSION_GRANTED);
+        // upgrade to apk without perm signature
+        installFromRawResource("permUse2.apk", R.raw.keyset_permuse_sb_ua_ub,
+                PackageManager.INSTALL_REPLACE_EXISTING, false, false, -1,
+                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+
+        assertFalse("keyset permission not revoked from app which upgraded to a "
+                    + "different signature",
+                    pm.checkPermission(permName, pkgName)
+                    == PackageManager.PERMISSION_GRANTED);
+        cleanUpInstall(permPkgName);
+        cleanUpInstall(pkgName);
+    }
+
+    /**
+     * The following tests are related to testing KeySets-based API
+     */
+
+    /*
+     * testGetSigningKeySetNull - ensure getSigningKeySet() returns null on null
+     * input and when calling a package other than that which made the call.
+     */
+    public void testGetSigningKeySet() throws Exception {
+        PackageManager pm = getPm();
+        String mPkgName = mContext.getPackageName();
+        String otherPkgName = "com.android.frameworks.coretests.keysets_api";
+        KeySet ks;
+        try {
+            ks = pm.getSigningKeySet(null);
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            ks = pm.getSigningKeySet("keysets.test.bogus.package");
+            assertTrue(false); // should have thrown
+        } catch (IllegalArgumentException e) {
+        }
+        final InstallParams ip = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        try {
+            ks = pm.getSigningKeySet(otherPkgName);
+            assertTrue(false); // should have thrown
+        } catch (SecurityException e) {
+        } finally {
+            cleanUpInstall(ip);
+        }
+        ks = pm.getSigningKeySet(mContext.getPackageName());
+        assertNotNull(ks);
+    }
+
+    /*
+     * testGetKeySetByAlias - same as getSigningKeySet, but for keysets defined
+     * by this package.
+     */
+    public void testGetKeySetByAlias() throws Exception {
+        PackageManager pm = getPm();
+        String mPkgName = mContext.getPackageName();
+        String otherPkgName = "com.android.frameworks.coretests.keysets_api";
+        KeySet ks;
+        try {
+            ks = pm.getKeySetByAlias(null, null);
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            ks = pm.getKeySetByAlias(null, "keysetBogus");
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            ks = pm.getKeySetByAlias("keysets.test.bogus.package", null);
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            ks = pm.getKeySetByAlias("keysets.test.bogus.package", "A");
+            assertTrue(false); // should have thrown
+        } catch(IllegalArgumentException e) {
+        }
+        try {
+            ks = pm.getKeySetByAlias(mPkgName, "keysetBogus");
+            assertTrue(false); // should have thrown
+        } catch(IllegalArgumentException e) {
+        }
+
+        // make sure we can get a KeySet from our pkg
+        ks = pm.getKeySetByAlias(mPkgName, "A");
+        assertNotNull(ks);
+
+        // and another
+        final InstallParams ip = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        try {
+            ks = pm.getKeySetByAlias(otherPkgName, "A");
+            assertNotNull(ks);
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    public void testIsSignedBy() throws Exception {
+        PackageManager pm = getPm();
+        String mPkgName = mContext.getPackageName();
+        String otherPkgName = "com.android.frameworks.coretests.keysets_api";
+        KeySet mSigningKS = pm.getSigningKeySet(mPkgName);
+        KeySet mDefinedKS = pm.getKeySetByAlias(mPkgName, "A");
+
+        try {
+            assertFalse(pm.isSignedBy(null, null));
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            assertFalse(pm.isSignedBy(null, mSigningKS));
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            assertFalse(pm.isSignedBy(mPkgName, null));
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            assertFalse(pm.isSignedBy("keysets.test.bogus.package", mDefinedKS));
+        } catch(IllegalArgumentException e) {
+        }
+        assertFalse(pm.isSignedBy(mPkgName, mDefinedKS));
+        assertFalse(pm.isSignedBy(mPkgName, new KeySet(new Binder())));
+        assertTrue(pm.isSignedBy(mPkgName, mSigningKS));
+
+        final InstallParams ip1 = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        try {
+            assertFalse(pm.isSignedBy(otherPkgName, mDefinedKS));
+            assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
+        } finally {
+            cleanUpInstall(ip1);
+        }
+
+        final InstallParams ip2 = installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        try {
+            assertTrue(pm.isSignedBy(otherPkgName, mDefinedKS));
+            assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
+        } finally {
+            cleanUpInstall(ip2);
+        }
+    }
+
+    public void testIsSignedByExactly() throws Exception {
+        PackageManager pm = getPm();
+        String mPkgName = mContext.getPackageName();
+        String otherPkgName = "com.android.frameworks.coretests.keysets_api";
+        KeySet mSigningKS = pm.getSigningKeySet(mPkgName);
+        KeySet mDefinedKS = pm.getKeySetByAlias(mPkgName, "A");
+        try {
+            assertFalse(pm.isSignedBy(null, null));
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            assertFalse(pm.isSignedBy(null, mSigningKS));
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            assertFalse(pm.isSignedBy(mPkgName, null));
+            assertTrue(false); // should have thrown
+        } catch (NullPointerException e) {
+        }
+        try {
+            assertFalse(pm.isSignedByExactly("keysets.test.bogus.package", mDefinedKS));
+        } catch(IllegalArgumentException e) {
+        }
+        assertFalse(pm.isSignedByExactly(mPkgName, mDefinedKS));
+        assertFalse(pm.isSignedByExactly(mPkgName, new KeySet(new Binder())));
+        assertTrue(pm.isSignedByExactly(mPkgName, mSigningKS));
+
+        final InstallParams ip1 = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        try {
+            assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
+            assertTrue(pm.isSignedByExactly(otherPkgName, mSigningKS));
+        } finally {
+            cleanUpInstall(ip1);
+        }
+
+        final InstallParams ip2 = installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
+                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        try {
+            assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
+            assertFalse(pm.isSignedByExactly(otherPkgName, mSigningKS));
+        } finally {
+            cleanUpInstall(ip2);
+        }
+    }
+
+
+
+    /**
+     * The following tests are related to testing the checkSignatures api.
+     */
+    private void checkSignatures(int apk1, int apk2, int expMatchResult) throws Exception {
+        checkSharedSignatures(apk1, apk2, true, false, -1, expMatchResult);
+    }
+
+    @LargeTest
+    public void testCheckSignaturesAllMatch() throws Exception {
+        int apk1 = APP1_CERT1_CERT2;
+        int apk2 = APP2_CERT1_CERT2;
+        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
+    }
+
+    @LargeTest
+    public void testCheckSignaturesNoMatch() throws Exception {
+        int apk1 = APP1_CERT1;
+        int apk2 = APP2_CERT2;
+        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH);
+    }
+
+    @LargeTest
+    public void testCheckSignaturesSomeMatch1() throws Exception {
+        int apk1 = APP1_CERT1_CERT2;
+        int apk2 = APP2_CERT1;
+        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH);
+    }
+
+    @LargeTest
+    public void testCheckSignaturesSomeMatch2() throws Exception {
+        int apk1 = APP1_CERT1_CERT2;
+        int apk2 = APP2_CERT2;
+        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH);
+    }
+
+    @LargeTest
+    public void testCheckSignaturesMoreMatch() throws Exception {
+        int apk1 = APP1_CERT1;
+        int apk2 = APP2_CERT1_CERT2;
+        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH);
+    }
+
+    @LargeTest
+    public void testCheckSignaturesUnknown() throws Exception {
+        int apk1 = APP1_CERT1_CERT2;
+        int apk2 = APP2_CERT1_CERT2;
+        String apk1Name = "install1.apk";
+        String apk2Name = "install2.apk";
+
+        final InstallParams ip = installFromRawResource(apk1Name, apk1, 0, false,
+                false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+        try {
+            PackageManager pm = mContext.getPackageManager();
+            // Delete app2
+            File filesDir = mContext.getFilesDir();
+            File outFile = new File(filesDir, apk2Name);
+            int rawResId = apk2;
+            Uri packageURI = getInstallablePackage(rawResId, outFile);
+            var pkg = parsePackage(packageURI);
+            try {
+                getPi().uninstall(pkg.getPackageName(),
+                        PackageManager.DELETE_ALL_USERS,
+                        null /*statusReceiver*/);
+            } catch (IllegalArgumentException ignore) {
+            }
+            // Check signatures now
+            int match = mContext.getPackageManager().checkSignatures(
+                    ip.pkg.getPackageName(), pkg.getPackageName());
+            assertEquals(PackageManager.SIGNATURE_UNKNOWN_PACKAGE, match);
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    @LargeTest
+    public void testCheckSignaturesRotatedAgainstOriginal() throws Exception {
+        // checkSignatures should be backwards compatible with pre-rotation behavior; this test
+        // verifies that an app signed with a rotated key results in a signature match with an app
+        // signed with the original key in the lineage.
+        int apk1 = APP1_CERT5;
+        int apk2 = APP2_CERT5_ROTATED_CERT6;
+        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
+    }
+
+    @LargeTest
+    public void testCheckSignaturesRotatedAgainstRotated() throws Exception {
+        // checkSignatures should be successful when both apps have been signed with the same
+        // rotated key since the initial signature comparison between the two apps should
+        // return a match.
+        int apk1 = APP1_CERT5_ROTATED_CERT6;
+        int apk2 = APP2_CERT5_ROTATED_CERT6;
+        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
+    }
+
+    @LargeTest
+    public void testInstallNoCertificates() throws Exception {
+        int apk1 = APP1_UNSIGNED;
+        String apk1Name = "install1.apk";
+
+        installFromRawResource(apk1Name, apk1, 0, false,
+                true, PackageInstaller.STATUS_FAILURE_INVALID,
+                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+    }
+
+    /*
+     * The following tests are related to apps using shared uids signed with
+     * different certs.
+     */
+    private int SHARED1_UNSIGNED = R.raw.install_shared1_unsigned;
+
+    private int SHARED1_CERT1 = R.raw.install_shared1_cert1;
+
+    private int SHARED1_CERT2 = R.raw.install_shared1_cert2;
+
+    private int SHARED1_CERT1_CERT2 = R.raw.install_shared1_cert1_cert2;
+
+    private int SHARED2_UNSIGNED = R.raw.install_shared2_unsigned;
+
+    private int SHARED2_CERT1 = R.raw.install_shared2_cert1;
+
+    private int SHARED2_CERT2 = R.raw.install_shared2_cert2;
+
+    private int SHARED2_CERT1_CERT2 = R.raw.install_shared2_cert1_cert2;
+
+    private void checkSharedSignatures(int apk1, int apk2, boolean cleanUp, boolean fail,
+            int retCode, int expMatchResult) throws Exception {
+        String apk1Name = "install1.apk";
+        String apk2Name = "install2.apk";
+        var pkg1 = getParsedPackage(apk1Name, apk1);
+        var pkg2 = getParsedPackage(apk2Name, apk2);
+
+        try {
+            // Clean up before testing first.
+            cleanUpInstall(pkg1.getPackageName());
+            cleanUpInstall(pkg2.getPackageName());
+            installFromRawResource(apk1Name, apk1, 0, false, false, -1,
+                    PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+            if (fail) {
+                installFromRawResource(apk2Name, apk2, 0, false, true, retCode,
+                        PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+            } else {
+                installFromRawResource(apk2Name, apk2, 0, false, false, -1,
+                        PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+                // TODO: All checkSignatures tests should return the same result regardless of
+                // querying by package name or uid; however if there are any edge cases where
+                // individual packages within a shareduid are compared with signatures that do not
+                // match the full lineage of the shareduid this method should be overloaded to
+                // accept the expected response for the uid query.
+                PackageManager pm = getPm();
+                int matchByName = pm.checkSignatures(pkg1.getPackageName(), pkg2.getPackageName());
+                int pkg1Uid = pm.getApplicationInfo(pkg1.getPackageName(), 0).uid;
+                int pkg2Uid = pm.getApplicationInfo(pkg2.getPackageName(), 0).uid;
+                int matchByUid = pm.checkSignatures(pkg1Uid, pkg2Uid);
+                assertEquals(expMatchResult, matchByName);
+                assertEquals(expMatchResult, matchByUid);
+            }
+        } finally {
+            if (cleanUp) {
+                cleanUpInstall(pkg1.getPackageName());
+                cleanUpInstall(pkg2.getPackageName());
+            }
+        }
+    }
+
+    @LargeTest
+    public void testCheckSignaturesSharedAllMatch() throws Exception {
+        int apk1 = SHARED1_CERT1_CERT2;
+        int apk2 = SHARED2_CERT1_CERT2;
+        boolean fail = false;
+        int retCode = -1;
+        int expMatchResult = PackageManager.SIGNATURE_MATCH;
+        checkSharedSignatures(apk1, apk2, true, fail, retCode, expMatchResult);
+    }
+
+    @LargeTest
+    public void testCheckSignaturesSharedNoMatch() throws Exception {
+        int apk1 = SHARED1_CERT1;
+        int apk2 = SHARED2_CERT2;
+        boolean fail = true;
+        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
+        int expMatchResult = -1;
+        checkSharedSignatures(apk1, apk2, true, fail, retCode, expMatchResult);
+    }
+
+    /*
+     * Test that an app signed with cert1 and cert2 cannot be replaced when
+     * signed with cert1 alone.
+     */
+    @LargeTest
+    public void testCheckSignaturesSharedSomeMatch1() throws Exception {
+        int apk1 = SHARED1_CERT1_CERT2;
+        int apk2 = SHARED2_CERT1;
+        boolean fail = true;
+        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
+        int expMatchResult = -1;
+        checkSharedSignatures(apk1, apk2, true, fail, retCode, expMatchResult);
+    }
+
+    /*
+     * Test that an app signed with cert1 and cert2 cannot be replaced when
+     * signed with cert2 alone.
+     */
+    @LargeTest
+    public void testCheckSignaturesSharedSomeMatch2() throws Exception {
+        int apk1 = SHARED1_CERT1_CERT2;
+        int apk2 = SHARED2_CERT2;
+        boolean fail = true;
+        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
+        int expMatchResult = -1;
+        checkSharedSignatures(apk1, apk2, true, fail, retCode, expMatchResult);
+    }
+
+    @LargeTest
+    public void testCheckSignaturesSharedUnknown() throws Exception {
+        int apk1 = SHARED1_CERT1_CERT2;
+        int apk2 = SHARED2_CERT1_CERT2;
+        String apk1Name = "install1.apk";
+        String apk2Name = "install2.apk";
+        InstallParams ip1 = null;
+
+        try {
+            ip1 = installFromRawResource(apk1Name, apk1, 0, false,
+                    false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+            PackageManager pm = mContext.getPackageManager();
+            // Delete app2
+            var pkg = getParsedPackage(apk2Name, apk2);
+            try {
+                getPi().uninstall(pkg.getPackageName(), PackageManager.DELETE_ALL_USERS,
+                        null /*statusReceiver*/);
+            } catch (IllegalArgumentException ignore) {
+            }
+            // Check signatures now
+            int match = mContext.getPackageManager().checkSignatures(
+                    ip1.pkg.getPackageName(), pkg.getPackageName());
+            assertEquals(PackageManager.SIGNATURE_UNKNOWN_PACKAGE, match);
+        } finally {
+            if (ip1 != null) {
+                cleanUpInstall(ip1);
+            }
+        }
+    }
+
+    @LargeTest
+    public void testReplaceFirstSharedMatchAllCerts() throws Exception {
+        int apk1 = SHARED1_CERT1;
+        int apk2 = SHARED2_CERT1;
+        int rapk1 = SHARED1_CERT1;
+        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
+        replaceCerts(apk1, rapk1, true, false, -1);
+    }
+
+    @LargeTest
+    public void testReplaceSecondSharedMatchAllCerts() throws Exception {
+        int apk1 = SHARED1_CERT1;
+        int apk2 = SHARED2_CERT1;
+        int rapk2 = SHARED2_CERT1;
+        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
+        replaceCerts(apk2, rapk2, true, false, -1);
+    }
+
+    @LargeTest
+    public void testReplaceFirstSharedMatchSomeCerts() throws Exception {
+        int apk1 = SHARED1_CERT1_CERT2;
+        int apk2 = SHARED2_CERT1_CERT2;
+        int rapk1 = SHARED1_CERT1;
+        boolean fail = true;
+        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
+        checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
+        installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true,
+                fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+    }
+
+    @LargeTest
+    public void testReplaceSecondSharedMatchSomeCerts() throws Exception {
+        int apk1 = SHARED1_CERT1_CERT2;
+        int apk2 = SHARED2_CERT1_CERT2;
+        int rapk2 = SHARED2_CERT1;
+        boolean fail = true;
+        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
+        checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
+        installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true,
+                fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+    }
+
+    @LargeTest
+    public void testReplaceFirstSharedMatchNoCerts() throws Exception {
+        int apk1 = SHARED1_CERT1;
+        int apk2 = SHARED2_CERT1;
+        int rapk1 = SHARED1_CERT2;
+        boolean fail = true;
+        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
+        checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
+        installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true,
+                fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+    }
+
+    @LargeTest
+    public void testReplaceSecondSharedMatchNoCerts() throws Exception {
+        int apk1 = SHARED1_CERT1;
+        int apk2 = SHARED2_CERT1;
+        int rapk2 = SHARED2_CERT2;
+        boolean fail = true;
+        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
+        checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
+        installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true,
+                fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+    }
+
+    @LargeTest
+    public void testReplaceFirstSharedMatchMoreCerts() throws Exception {
+        int apk1 = SHARED1_CERT1;
+        int apk2 = SHARED2_CERT1;
+        int rapk1 = SHARED1_CERT1_CERT2;
+        boolean fail = true;
+        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
+        checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
+        installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true,
+                fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+    }
+
+    @LargeTest
+    public void testReplaceSecondSharedMatchMoreCerts() throws Exception {
+        int apk1 = SHARED1_CERT1;
+        int apk2 = SHARED2_CERT1;
+        int rapk2 = SHARED2_CERT1_CERT2;
+        boolean fail = true;
+        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
+        checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
+        installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true,
+                fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+    }
+
+    /**
+     * Unknown features should be allowed to install. This prevents older phones
+     * from rejecting new packages that specify features that didn't exist when
+     * an older phone existed. All older phones are assumed to have those
+     * features.
+     * <p>
+     * Right now we allow all packages to be installed regardless of their
+     * features.
+     */
+    @LargeTest
+    public void testUsesFeatureUnknownFeature() throws Exception {
+        int retCode = PackageManager.INSTALL_SUCCEEDED;
+        installFromRawResource("install.apk", R.raw.install_uses_feature, 0, true, false, retCode,
+                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+    }
+
+    @LargeTest
+    public void testInstallNonexistentFile() throws Exception {
+        int retCode = PackageInstaller.STATUS_FAILURE_INVALID;
+        File invalidFile = new File("/nonexistent-file.apk");
+        invokeInstallPackageFail(Uri.fromFile(invalidFile), 0, retCode);
+    }
+
+    @SmallTest
+    public void testGetVerifierDeviceIdentity() throws Exception {
+        PackageManager pm = getPm();
+        VerifierDeviceIdentity id = pm.getVerifierDeviceIdentity();
+
+        assertNotNull("Verifier device identity should not be null", id);
+    }
+
+    public void testGetInstalledPackages() throws Exception {
+        List<PackageInfo> packages = getPm().getInstalledPackages(0);
+        assertNotNull("installed packages cannot be null", packages);
+        assertTrue("installed packages cannot be empty", packages.size() > 0);
+    }
+
+    public void testGetUnInstalledPackages() throws Exception {
+        List<PackageInfo> packages = getPm().getInstalledPackages(
+                PackageManager.MATCH_UNINSTALLED_PACKAGES);
+        assertNotNull("installed packages cannot be null", packages);
+        assertTrue("installed packages cannot be empty", packages.size() > 0);
+    }
+
+    /**
+     * Test that getInstalledPackages returns all the data specified in flags.
+     */
+    public void testGetInstalledPackagesAll() throws Exception {
+        final int flags = PackageManager.GET_ACTIVITIES | PackageManager.GET_GIDS
+                | PackageManager.GET_CONFIGURATIONS | PackageManager.GET_INSTRUMENTATION
+                | PackageManager.GET_PERMISSIONS | PackageManager.GET_PROVIDERS
+                | PackageManager.GET_RECEIVERS | PackageManager.GET_SERVICES
+                | PackageManager.GET_SIGNATURES | PackageManager.MATCH_UNINSTALLED_PACKAGES;
+
+        final InstallParams ip =
+                installFromRawResource("install.apk", R.raw.install_complete_package_info,
+                        0 /*flags*/, false /*cleanUp*/, false /*fail*/, -1 /*result*/,
+                        PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+        try {
+            final List<PackageInfo> packages = getPm().getInstalledPackages(flags);
+            assertNotNull("installed packages cannot be null", packages);
+            assertTrue("installed packages cannot be empty", packages.size() > 0);
+
+            PackageInfo packageInfo = null;
+
+            // Find the package with all components specified in the AndroidManifest
+            // to ensure no null values
+            for (PackageInfo pi : packages) {
+                if ("com.android.frameworks.coretests.install_complete_package_info"
+                        .equals(pi.packageName)) {
+                    packageInfo = pi;
+                    break;
+                }
+            }
+            assertNotNull("activities should not be null", packageInfo.activities);
+            assertNotNull("configPreferences should not be null", packageInfo.configPreferences);
+            assertNotNull("instrumentation should not be null", packageInfo.instrumentation);
+            assertNotNull("permissions should not be null", packageInfo.permissions);
+            assertNotNull("providers should not be null", packageInfo.providers);
+            assertNotNull("receivers should not be null", packageInfo.receivers);
+            assertNotNull("services should not be null", packageInfo.services);
+            assertNotNull("signatures should not be null", packageInfo.signatures);
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    /**
+     * Test that getInstalledPackages returns all the data specified in
+     * flags when the GET_UNINSTALLED_PACKAGES flag is set.
+     */
+    public void testGetUnInstalledPackagesAll() throws Exception {
+        final int flags = PackageManager.MATCH_UNINSTALLED_PACKAGES
+                | PackageManager.GET_ACTIVITIES | PackageManager.GET_GIDS
+                | PackageManager.GET_CONFIGURATIONS | PackageManager.GET_INSTRUMENTATION
+                | PackageManager.GET_PERMISSIONS | PackageManager.GET_PROVIDERS
+                | PackageManager.GET_RECEIVERS | PackageManager.GET_SERVICES
+                | PackageManager.GET_SIGNATURES;
+
+        // first, install the package
+        final InstallParams ip =
+                installFromRawResource("install.apk", R.raw.install_complete_package_info,
+                        0 /*flags*/, false /*cleanUp*/, false /*fail*/, -1 /*result*/,
+                        PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+        try {
+            // then, remove it, keeping it's data around
+            final GenericReceiver receiver = new DeleteReceiver(ip.pkg.getPackageName());
+            invokeDeletePackage(ip.pkg.getPackageName(), PackageManager.DELETE_KEEP_DATA, receiver);
+
+            final List<PackageInfo> packages = getPm().getInstalledPackages(flags);
+            assertNotNull("installed packages cannot be null", packages);
+            assertTrue("installed packages cannot be empty", packages.size() > 0);
+
+            PackageInfo packageInfo = null;
+
+            // Find the package with all components specified in the AndroidManifest
+            // to ensure no null values
+            for (PackageInfo pi : packages) {
+                if ("com.android.frameworks.coretests.install_complete_package_info"
+                        .equals(pi.packageName)) {
+                    packageInfo = pi;
+                    break;
+                }
+            }
+            assertNotNull("activities should not be null", packageInfo.activities);
+            assertNotNull("configPreferences should not be null", packageInfo.configPreferences);
+            assertNotNull("instrumentation should not be null", packageInfo.instrumentation);
+            assertNotNull("permissions should not be null", packageInfo.permissions);
+            assertNotNull("providers should not be null", packageInfo.providers);
+            assertNotNull("receivers should not be null", packageInfo.receivers);
+            assertNotNull("services should not be null", packageInfo.services);
+            assertNotNull("signatures should not be null", packageInfo.signatures);
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    @Suppress
+    public void testInstall_BadDex_CleanUp() throws Exception {
+        int retCode = PackageInstaller.STATUS_FAILURE_INVALID;
+        installFromRawResource("install.apk", R.raw.install_bad_dex, 0, true, true, retCode,
+                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+    }
+
+    private static class TestDexModuleRegisterCallback
+            extends PackageManager.DexModuleRegisterCallback {
+        private String mDexModulePath = null;
+        private boolean mSuccess = false;
+        private String mMessage = null;
+        CountDownLatch doneSignal = new CountDownLatch(1);
+
+        @Override
+        public void onDexModuleRegistered(String dexModulePath, boolean success, String message) {
+            mDexModulePath = dexModulePath;
+            mSuccess = success;
+            mMessage = message;
+            doneSignal.countDown();
+        }
+
+        boolean waitTillDone() {
+            long startTime = System.currentTimeMillis();
+            while (System.currentTimeMillis() - startTime < MAX_WAIT_TIME) {
+                try {
+                    return doneSignal.await(MAX_WAIT_TIME, TimeUnit.MILLISECONDS);
+                } catch (InterruptedException e) {
+                    Log.i(TAG, "Interrupted during sleep", e);
+                }
+            }
+            return false;
+        }
+
+    }
+
+    // Verify that the base code path cannot be registered.
+    public void testRegisterDexModuleBaseCode() throws Exception {
+        PackageManager pm = getPm();
+        ApplicationInfo info = getContext().getApplicationInfo();
+        TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
+        pm.registerDexModule(info.getBaseCodePath(), callback);
+        assertTrue(callback.waitTillDone());
+        assertEquals(info.getBaseCodePath(), callback.mDexModulePath);
+        assertFalse("BaseCodePath should not be registered", callback.mSuccess);
+    }
+
+    // Verify that modules which are not own by the calling package are not registered.
+    public void testRegisterDexModuleNotOwningModule() throws Exception {
+        TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
+        String moduleBelongingToOtherPackage = "/data/user/0/com.google.android.gms/module.apk";
+        getPm().registerDexModule(moduleBelongingToOtherPackage, callback);
+        assertTrue(callback.waitTillDone());
+        assertEquals(moduleBelongingToOtherPackage, callback.mDexModulePath);
+        assertTrue(callback.waitTillDone());
+        assertFalse("Only modules belonging to the calling package can be registered",
+                callback.mSuccess);
+    }
+
+    // Verify that modules owned by the package are successfully registered.
+    public void testRegisterDexModuleSuccessfully() throws Exception {
+        ApplicationInfo info = getContext().getApplicationInfo();
+        // Copy the main apk into the data folder and use it as a "module".
+        File dexModuleDir = new File(info.dataDir, "module-dir");
+        File dexModule = new File(dexModuleDir, "module.apk");
+        try {
+            assertNotNull(FileUtils.createDir(
+                    dexModuleDir.getParentFile(), dexModuleDir.getName()));
+            Files.copy(Paths.get(info.getBaseCodePath()), dexModule.toPath(),
+                    StandardCopyOption.REPLACE_EXISTING);
+            TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
+            getPm().registerDexModule(dexModule.toString(), callback);
+            assertTrue(callback.waitTillDone());
+            assertEquals(dexModule.toString(), callback.mDexModulePath);
+            assertTrue(callback.waitTillDone());
+            assertTrue(callback.mMessage, callback.mSuccess);
+
+            // NOTE:
+            // This actually verifies internal behaviour which might change. It's not
+            // ideal but it's the best we can do since there's no other place we can currently
+            // write a better test.
+            for(String isa : getAppDexInstructionSets(info)) {
+                Files.exists(Paths.get(dexModuleDir.toString(), "oat", isa, "module.odex"));
+                Files.exists(Paths.get(dexModuleDir.toString(), "oat", isa, "module.vdex"));
+            }
+        } finally {
+            FileUtils.deleteContentsAndDir(dexModuleDir);
+        }
+    }
+
+    // If the module does not exist on disk we should get a failure.
+    public void testRegisterDexModuleNotExists() throws Exception {
+        ApplicationInfo info = getContext().getApplicationInfo();
+        String nonExistentApk = Paths.get(info.dataDir, "non-existent.apk").toString();
+        TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
+        getPm().registerDexModule(nonExistentApk, callback);
+        assertTrue(callback.waitTillDone());
+        assertEquals(nonExistentApk, callback.mDexModulePath);
+        assertTrue(callback.waitTillDone());
+        assertFalse("DexModule registration should fail", callback.mSuccess);
+    }
+
+    // If the module does not exist on disk we should get a failure.
+    public void testRegisterDexModuleNotExistsNoCallback() throws Exception {
+        ApplicationInfo info = getContext().getApplicationInfo();
+        String nonExistentApk = Paths.get(info.dataDir, "non-existent.apk").toString();
+        getPm().registerDexModule(nonExistentApk, null);
+    }
+
+    // Copied from com.android.server.pm.InstructionSets because we don't have access to it here.
+    private static String[] getAppDexInstructionSets(ApplicationInfo info) {
+        if (info.primaryCpuAbi != null) {
+            if (info.secondaryCpuAbi != null) {
+                return new String[] {
+                        VMRuntime.getInstructionSet(info.primaryCpuAbi),
+                        VMRuntime.getInstructionSet(info.secondaryCpuAbi) };
+            } else {
+                return new String[] {
+                        VMRuntime.getInstructionSet(info.primaryCpuAbi) };
+            }
+        }
+
+        return new String[] { VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]) };
+    }
+
+    /*---------- Recommended install location tests ----*/
+    /*
+     * TODO's
+     * check version numbers for upgrades
+     * check permissions of installed packages
+     * how to do tests on updated system apps?
+     * verify updates to system apps cannot be installed on the sdcard.
+     */
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageParserTest.java
new file mode 100644
index 0000000..c8e2676
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageParserTest.java
@@ -0,0 +1,1155 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.permission.CompatibilityPermissionInfo.COMPAT_PERMS;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import static java.lang.Boolean.TRUE;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static java.util.stream.Collectors.toList;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FeatureGroupInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.Property;
+import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
+
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.parsing.PackageCacher;
+import com.android.server.pm.parsing.PackageInfoUtils;
+import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.TestPackageParser2;
+import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
+import com.android.server.pm.parsing.pkg.PackageImpl;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.permission.CompatibilityPermissionInfo;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.component.ParsedActivityImpl;
+import com.android.server.pm.pkg.component.ParsedApexSystemService;
+import com.android.server.pm.pkg.component.ParsedComponent;
+import com.android.server.pm.pkg.component.ParsedInstrumentation;
+import com.android.server.pm.pkg.component.ParsedInstrumentationImpl;
+import com.android.server.pm.pkg.component.ParsedIntentInfo;
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
+import com.android.server.pm.pkg.component.ParsedPermission;
+import com.android.server.pm.pkg.component.ParsedPermissionGroup;
+import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl;
+import com.android.server.pm.pkg.component.ParsedPermissionImpl;
+import com.android.server.pm.pkg.component.ParsedPermissionUtils;
+import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.pkg.component.ParsedProviderImpl;
+import com.android.server.pm.pkg.component.ParsedService;
+import com.android.server.pm.pkg.component.ParsedServiceImpl;
+import com.android.server.pm.pkg.component.ParsedUsesPermission;
+import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class PackageParserTest {
+    // TODO(b/135203078): Update this test with all fields and validate equality. Initial change
+    //  was just migrating to new interfaces. Consider adding actual equals() methods.
+
+    @Rule
+    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    private File mTmpDir;
+    private static final File FRAMEWORK = new File("/system/framework/framework-res.apk");
+    private static final String TEST_APP1_APK = "PackageParserTestApp1.apk";
+    private static final String TEST_APP2_APK = "PackageParserTestApp2.apk";
+    private static final String TEST_APP3_APK = "PackageParserTestApp3.apk";
+    private static final String TEST_APP4_APK = "PackageParserTestApp4.apk";
+    private static final String TEST_APP5_APK = "PackageParserTestApp5.apk";
+    private static final String TEST_APP6_APK = "PackageParserTestApp6.apk";
+    private static final String PACKAGE_NAME = "com.android.servicestests.apps.packageparserapp";
+
+    @Before
+    public void setUp() throws IOException {
+        // Create a new temporary directory for each of our tests.
+        mTmpDir = mTemporaryFolder.newFolder("PackageParserTest");
+    }
+
+    @Test
+    public void testParse_noCache() throws Exception {
+        CachePackageNameParser pp = new CachePackageNameParser(null);
+        ParsedPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
+                false /* useCaches */);
+        assertNotNull(pkg);
+
+        pp.setCacheDir(mTmpDir);
+        pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
+                false /* useCaches */);
+        assertNotNull(pkg);
+
+        // Make sure that we always write out a cache entry for future reference,
+        // whether or not we're asked to use caches.
+        assertEquals(1, mTmpDir.list().length);
+    }
+
+    @Test
+    public void testParse_withCache() throws Exception {
+        CachePackageNameParser pp = new CachePackageNameParser(null);
+
+        pp.setCacheDir(mTmpDir);
+        // The first parse will write this package to the cache.
+        pp.parsePackage(FRAMEWORK, 0 /* parseFlags */, true /* useCaches */);
+
+        // Now attempt to parse the package again, should return the
+        // cached result.
+        ParsedPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
+                true /* useCaches */);
+        assertEquals("cache_android", pkg.getPackageName());
+
+        // Try again, with useCaches == false, shouldn't return the parsed
+        // result.
+        pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */, false /* useCaches */);
+        assertEquals("android", pkg.getPackageName());
+
+        // We haven't set a cache directory here : the parse should still succeed,
+        // just not using the cached results.
+        pp = new CachePackageNameParser(null);
+        pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */, true /* useCaches */);
+        assertEquals("android", pkg.getPackageName());
+
+        pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */, false /* useCaches */);
+        assertEquals("android", pkg.getPackageName());
+    }
+
+    @Test
+    public void test_serializePackage() throws Exception {
+        try (PackageParser2 pp = PackageParser2.forParsingFileWithDefaults()) {
+            AndroidPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
+                    true /* useCaches */).hideAsFinal();
+
+            Parcel p = Parcel.obtain();
+            ((Parcelable) pkg).writeToParcel(p, 0 /* flags */);
+
+            p.setDataPosition(0);
+            ParsedPackage deserialized = new PackageImpl(p);
+
+            assertPackagesEqual(pkg, deserialized);
+        }
+    }
+
+    @Test
+    @SmallTest
+    @Presubmit
+    public void test_roundTripKnownFields() throws Exception {
+        ParsingPackage pkg = PackageImpl.forTesting("foo");
+        setKnownFields(pkg);
+
+        Parcel p = Parcel.obtain();
+        ((Parcelable) pkg).writeToParcel(p, 0 /* flags */);
+
+        p.setDataPosition(0);
+        ParsedPackage deserialized = new PackageImpl(p);
+        assertAllFieldsExist(deserialized);
+    }
+
+    @Test
+    public void test_stringInterning() throws Exception {
+        ParsingPackage pkg = PackageImpl.forTesting("foo");
+        setKnownFields(pkg);
+
+        Parcel p = Parcel.obtain();
+        ((Parcelable) pkg.hideAsParsed().hideAsFinal()).writeToParcel(p, 0 /* flags */);
+
+        p.setDataPosition(0);
+        AndroidPackage deserialized = new PackageImpl(p);
+
+        p.setDataPosition(0);
+        AndroidPackage deserialized2 = new PackageImpl(p);
+
+        assertSame(deserialized.getPackageName(), deserialized2.getPackageName());
+        assertSame(deserialized.getPermission(),
+                deserialized2.getPermission());
+        assertSame(deserialized.getRequestedPermissions().get(0),
+                deserialized2.getRequestedPermissions().get(0));
+
+        List<String> protectedBroadcastsOne = new ArrayList<>(1);
+        protectedBroadcastsOne.addAll(deserialized.getProtectedBroadcasts());
+
+        List<String> protectedBroadcastsTwo = new ArrayList<>(1);
+        protectedBroadcastsTwo.addAll(deserialized2.getProtectedBroadcasts());
+
+        assertSame(protectedBroadcastsOne.get(0), protectedBroadcastsTwo.get(0));
+
+        assertSame(deserialized.getUsesLibraries().get(0),
+                deserialized2.getUsesLibraries().get(0));
+        assertSame(deserialized.getUsesOptionalLibraries().get(0),
+                deserialized2.getUsesOptionalLibraries().get(0));
+        assertSame(deserialized.getVersionName(), deserialized2.getVersionName());
+        assertSame(deserialized.getSharedUserId(), deserialized2.getSharedUserId());
+    }
+
+    private File extractFile(String filename) throws Exception {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final File tmpFile = File.createTempFile(filename, ".apk");
+        try (InputStream inputStream = context.getAssets().openNonAsset(filename)) {
+            Files.copy(inputStream, tmpFile.toPath(), REPLACE_EXISTING);
+        }
+        return tmpFile;
+    }
+
+    /**
+     * Extracts the asset file to $mTmpDir/$dirname/$filename.
+     */
+    private File extractFile(String filename, String dirname) throws Exception {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        File dir = new File(mTmpDir, dirname);
+        dir.mkdir();
+        final File tmpFile = new File(dir, filename);
+        try (InputStream inputStream = context.getAssets().openNonAsset(filename)) {
+            Files.copy(inputStream, tmpFile.toPath(), REPLACE_EXISTING);
+        }
+        return tmpFile;
+    }
+
+    /**
+     * Tests the path of cached ParsedPackage.
+     */
+    @Test
+    public void testCache_SameFileName() throws Exception {
+        // Prepare 2 package files with the same name but different paths
+        TestPackageParser2 parser = new TestPackageParser2(mTmpDir);
+        final File f1 = extractFile(TEST_APP1_APK, "dir1");
+        final File f2 = extractFile(TEST_APP1_APK, "dir2");
+        // Sleep for a while so that the cache file will be newer and valid
+        Thread.sleep(1000);
+        ParsedPackage pr1 = parser.parsePackage(f1, 0, true);
+        ParsedPackage pr2 = parser.parsePackage(f2, 0, true);
+        // Check the path of cached ParsedPackage
+        assertThat(pr1.getPath()).isEqualTo(f1.getAbsolutePath());
+        assertThat(pr2.getPath()).isEqualTo(f2.getAbsolutePath());
+    }
+
+    /**
+     * Tests AndroidManifest.xml with no android:isolatedSplits attribute.
+     */
+    @Test
+    public void testParseIsolatedSplitsDefault() throws Exception {
+        final File testFile = extractFile(TEST_APP1_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            assertFalse("isolatedSplits", pkg.isIsolatedSplitLoading());
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    /**
+     * Tests AndroidManifest.xml with an android:isolatedSplits attribute set to a constant.
+     */
+    @Test
+    public void testParseIsolatedSplitsConstant() throws Exception {
+        final File testFile = extractFile(TEST_APP2_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            assertTrue("isolatedSplits", pkg.isIsolatedSplitLoading());
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    /**
+     * Tests AndroidManifest.xml with an android:isolatedSplits attribute set to a resource.
+     */
+    @Test
+    public void testParseIsolatedSplitsResource() throws Exception {
+        final File testFile = extractFile(TEST_APP3_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            assertTrue("isolatedSplits", pkg.isIsolatedSplitLoading());
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    @Test
+    public void testParseActivityRequiredDisplayCategoryValid() throws Exception {
+        final File testFile = extractFile(TEST_APP4_APK);
+        String actualDisplayCategory = null;
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            final List<ParsedActivity> activities = pkg.getActivities();
+            for (ParsedActivity activity : activities) {
+                if ((PACKAGE_NAME + ".MyActivity").equals(activity.getName())) {
+                    actualDisplayCategory = activity.getRequiredDisplayCategory();
+                }
+            }
+        } finally {
+            testFile.delete();
+        }
+        assertEquals("automotive", actualDisplayCategory);
+    }
+
+    @Test
+    public void testParseActivityRequiredDisplayCategoryInvalid() throws Exception {
+        final File testFile = extractFile(TEST_APP6_APK);
+        String actualDisplayCategory = null;
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            final List<ParsedActivity> activities = pkg.getActivities();
+            for (ParsedActivity activity : activities) {
+                if ((PACKAGE_NAME + ".MyActivity").equals(activity.getName())) {
+                    actualDisplayCategory = activity.getRequiredDisplayCategory();
+                }
+            }
+        } catch (PackageManagerException e) {
+            assertThat(e.getMessage()).contains(
+                    "requiredDisplayCategory attribute can only consist"
+                            + " of alphanumeric characters, '_', and '.'");
+        } finally {
+            testFile.delete();
+        }
+        assertNotEquals("$automotive", actualDisplayCategory);
+    }
+
+    private static final int PROPERTY_TYPE_BOOLEAN = 1;
+    private static final int PROPERTY_TYPE_FLOAT = 2;
+    private static final int PROPERTY_TYPE_INTEGER = 3;
+    private static final int PROPERTY_TYPE_RESOURCE = 4;
+    private static final int PROPERTY_TYPE_STRING = 5;
+    public void assertProperty(Map<String, Property> properties, String propertyName,
+            int propertyType, Object propertyValue) {
+        assertTrue(properties.containsKey(propertyName));
+
+        final Property testProperty = properties.get(propertyName);
+        assertEquals(propertyType, testProperty.getType());
+
+        if (propertyType == PROPERTY_TYPE_BOOLEAN) {
+            assertTrue(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Boolean boolValue = (Boolean) propertyValue;
+            if (boolValue.booleanValue()) {
+                assertTrue(testProperty.getBoolean());
+            } else {
+                assertFalse(testProperty.getBoolean());
+            }
+            // assert the other values have an appropriate default
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(0, testProperty.getResourceId());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_FLOAT) {
+            assertFalse(testProperty.isBoolean());
+            assertTrue(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Float floatValue = (Float) propertyValue;
+            assertEquals(floatValue.floatValue(), testProperty.getFloat(), 0.0f);
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(0, testProperty.getResourceId());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_INTEGER) {
+            assertFalse(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertTrue(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Integer integerValue = (Integer) propertyValue;
+            assertEquals(integerValue.intValue(), testProperty.getInteger());
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getResourceId());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_RESOURCE) {
+            assertFalse(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertTrue(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Integer resourceValue = (Integer) propertyValue;
+            assertEquals(resourceValue.intValue(), testProperty.getResourceId());
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_STRING) {
+            assertFalse(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertTrue(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final String stringValue = (String) propertyValue;
+            assertEquals(stringValue, testProperty.getString());
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(0, testProperty.getResourceId());
+        } else {
+            fail("Unknown property type");
+        }
+    }
+
+    @Test
+    public void testParseApplicationProperties() throws Exception {
+        final File testFile = extractFile(TEST_APP4_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            final Map<String, Property> properties = pkg.getProperties();
+            assertEquals(10, properties.size());
+            assertProperty(properties,
+                    "android.cts.PROPERTY_RESOURCE_XML", PROPERTY_TYPE_RESOURCE, 0x7f060000);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_RESOURCE_INTEGER", PROPERTY_TYPE_RESOURCE, 0x7f040000);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_BOOLEAN", PROPERTY_TYPE_BOOLEAN, TRUE);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_BOOLEAN_VIA_RESOURCE", PROPERTY_TYPE_BOOLEAN, TRUE);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_FLOAT", PROPERTY_TYPE_FLOAT, 3.14f);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_FLOAT_VIA_RESOURCE", PROPERTY_TYPE_FLOAT, 2.718f);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_INTEGER", PROPERTY_TYPE_INTEGER, 42);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_INTEGER_VIA_RESOURCE", PROPERTY_TYPE_INTEGER, 123);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_STRING", PROPERTY_TYPE_STRING, "koala");
+            assertProperty(properties,
+                    "android.cts.PROPERTY_STRING_VIA_RESOURCE", PROPERTY_TYPE_STRING, "giraffe");
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    @Test
+    public void testParseActivityProperties() throws Exception {
+        final File testFile = extractFile(TEST_APP4_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            final List<ParsedActivity> activities = pkg.getActivities();
+            for (ParsedActivity activity : activities) {
+                final Map<String, Property> properties = activity.getProperties();
+                if ((PACKAGE_NAME + ".MyActivityAlias").equals(activity.getName())) {
+                    assertEquals(2, properties.size());
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_ACTIVITY_ALIAS", PROPERTY_TYPE_INTEGER, 123);
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_COMPONENT", PROPERTY_TYPE_INTEGER, 123);
+                } else if ((PACKAGE_NAME + ".MyActivity").equals(activity.getName())) {
+                    assertEquals(3, properties.size());
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_ACTIVITY", PROPERTY_TYPE_INTEGER, 123);
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_COMPONENT", PROPERTY_TYPE_INTEGER, 123);
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_STRING", PROPERTY_TYPE_STRING, "koala activity");
+                } else if ("android.app.AppDetailsActivity".equals(activity.getName())) {
+                    // ignore default added activity
+                } else {
+                    fail("Found unknown activity; name = " + activity.getName());
+                }
+            }
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    @Test
+    public void testParseProviderProperties() throws Exception {
+        final File testFile = extractFile(TEST_APP4_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            final List<ParsedProvider> providers = pkg.getProviders();
+            for (ParsedProvider provider : providers) {
+                final Map<String, Property> properties = provider.getProperties();
+                if ((PACKAGE_NAME + ".MyProvider").equals(provider.getName())) {
+                    assertEquals(1, properties.size());
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_PROVIDER", PROPERTY_TYPE_INTEGER, 123);
+                } else {
+                    fail("Found unknown provider; name = " + provider.getName());
+                }
+            }
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    @Test
+    public void testParseReceiverProperties() throws Exception {
+        final File testFile = extractFile(TEST_APP4_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            final List<ParsedActivity> receivers = pkg.getReceivers();
+            for (ParsedActivity receiver : receivers) {
+                final Map<String, Property> properties = receiver.getProperties();
+                if ((PACKAGE_NAME + ".MyReceiver").equals(receiver.getName())) {
+                    assertEquals(2, properties.size());
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_RECEIVER", PROPERTY_TYPE_INTEGER, 123);
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_STRING", PROPERTY_TYPE_STRING, "koala receiver");
+                } else {
+                    fail("Found unknown receiver; name = " + receiver.getName());
+                }
+            }
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    @Test
+    public void testParseServiceProperties() throws Exception {
+        final File testFile = extractFile(TEST_APP4_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            final List<ParsedService> services = pkg.getServices();
+            for (ParsedService service : services) {
+                final Map<String, Property> properties = service.getProperties();
+                if ((PACKAGE_NAME + ".MyService").equals(service.getName())) {
+                    assertEquals(2, properties.size());
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_SERVICE", PROPERTY_TYPE_INTEGER, 123);
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_COMPONENT", PROPERTY_TYPE_RESOURCE, 0x7f040000);
+                } else {
+                    fail("Found unknown service; name = " + service.getName());
+                }
+            }
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    @Test
+    public void testParseApexSystemService() throws Exception {
+        final File testFile = extractFile(TEST_APP4_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            final List<ParsedApexSystemService> systemServices = pkg.getApexSystemServices();
+            for (ParsedApexSystemService systemService: systemServices) {
+                assertEquals(PACKAGE_NAME + ".SystemService", systemService.getName());
+                assertEquals("service-test.jar", systemService.getJarPath());
+                assertEquals("30", systemService.getMinSdkVersion());
+                assertEquals("31", systemService.getMaxSdkVersion());
+            }
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    @Test
+    public void testParseModernPackageHasNoCompatPermissions() throws Exception {
+        final File testFile = extractFile(TEST_APP1_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2()
+                    .parsePackage(testFile, 0 /*flags*/, false /*useCaches*/);
+            final List<String> compatPermissions =
+                    Arrays.stream(COMPAT_PERMS).map(CompatibilityPermissionInfo::getName)
+                            .collect(toList());
+            assertWithMessage(
+                    "Compatibility permissions shouldn't be added into uses permissions.")
+                    .that(pkg.getUsesPermissions().stream().map(ParsedUsesPermission::getName)
+                            .collect(toList()))
+                    .containsNoneIn(compatPermissions);
+            assertWithMessage(
+                    "Compatibility permissions shouldn't be added into requested permissions.")
+                    .that(pkg.getRequestedPermissions()).containsNoneIn(compatPermissions);
+            assertWithMessage(
+                    "Compatibility permissions shouldn't be added into implicit permissions.")
+                    .that(pkg.getImplicitPermissions()).containsNoneIn(compatPermissions);
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    @Test
+    public void testParseLegacyPackageHasCompatPermissions() throws Exception {
+        final File testFile = extractFile(TEST_APP5_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2()
+                    .parsePackage(testFile, 0 /*flags*/, false /*useCaches*/);
+            assertWithMessage(
+                    "Compatibility permissions should be added into uses permissions.")
+                    .that(Arrays.stream(COMPAT_PERMS).map(CompatibilityPermissionInfo::getName)
+                            .allMatch(pkg.getUsesPermissions().stream()
+                                    .map(ParsedUsesPermission::getName)
+                            .collect(toList())::contains))
+                    .isTrue();
+            assertWithMessage(
+                    "Compatibility permissions should be added into requested permissions.")
+                    .that(Arrays.stream(COMPAT_PERMS).map(CompatibilityPermissionInfo::getName)
+                            .allMatch(pkg.getRequestedPermissions()::contains))
+                    .isTrue();
+            assertWithMessage(
+                    "Compatibility permissions should be added into implicit permissions.")
+                    .that(Arrays.stream(COMPAT_PERMS).map(CompatibilityPermissionInfo::getName)
+                            .allMatch(pkg.getImplicitPermissions()::contains))
+                    .isTrue();
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    @Test
+    public void testNoComponentMetadataIsCoercedToNullForInfoObject() throws Exception {
+        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, pkgSetting);
+            for (ParsedActivity activity : pkg.getActivities()) {
+                assertNotNull(activity.getMetaData());
+                assertNull(PackageInfoUtils.generateActivityInfo(pkg, activity, 0,
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
+            }
+            for (ParsedProvider provider : pkg.getProviders()) {
+                assertNotNull(provider.getMetaData());
+                assertNull(PackageInfoUtils.generateProviderInfo(pkg, provider, 0,
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
+            }
+            for (ParsedActivity receiver : pkg.getReceivers()) {
+                assertNotNull(receiver.getMetaData());
+                assertNull(PackageInfoUtils.generateActivityInfo(pkg, receiver, 0,
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
+            }
+            for (ParsedService service : pkg.getServices()) {
+                assertNotNull(service.getMetaData());
+                assertNull(PackageInfoUtils.generateServiceInfo(pkg, service, 0,
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
+            }
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    /**
+     * A subclass of package parser that adds a "cache_" prefix to the package name for the cached
+     * results. This is used by tests to tell if a ParsedPackage is generated from the cache or not.
+     */
+    public static class CachePackageNameParser extends PackageParser2 {
+
+        CachePackageNameParser(@Nullable File cacheDir) {
+            super(null, null, null, new Callback() {
+                @Override
+                public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
+                    return true;
+                }
+
+                @Override
+                public boolean hasFeature(String feature) {
+                    return false;
+                }
+            });
+            if (cacheDir != null) {
+                setCacheDir(cacheDir);
+            }
+        }
+
+        void setCacheDir(@NonNull File cacheDir) {
+            this.mCacher = new PackageCacher(cacheDir) {
+                @Override
+                public ParsedPackage fromCacheEntry(byte[] cacheEntry) {
+                    ParsedPackage parsed = super.fromCacheEntry(cacheEntry);
+                    parsed.setPackageName("cache_" + parsed.getPackageName());
+                    return parsed;
+                }
+            };
+        }
+    }
+
+    private static PackageSetting mockPkgSetting(AndroidPackage pkg) {
+        return new PackageSettingBuilder()
+                .setName(pkg.getPackageName())
+                .setRealName(pkg.getManifestPackageName())
+                .setCodePath(pkg.getPath())
+                .setPrimaryCpuAbiString(AndroidPackageUtils.getRawPrimaryCpuAbi(pkg))
+                .setSecondaryCpuAbiString(AndroidPackageUtils.getRawSecondaryCpuAbi(pkg))
+                .setPVersionCode(pkg.getLongVersionCode())
+                .setPkgFlags(PackageInfoUtils.appInfoFlags(pkg, null))
+                .setPrivateFlags(PackageInfoUtils.appInfoPrivateFlags(pkg, null))
+                .setSharedUserId(pkg.getSharedUserLabel())
+                .build();
+    }
+
+    // NOTE: The equality assertions below are based on code autogenerated by IntelliJ.
+
+    public static void assertPackagesEqual(AndroidPackage a, AndroidPackage b) {
+        assertEquals(a.getBaseRevisionCode(), b.getBaseRevisionCode());
+        assertEquals(a.isBaseHardwareAccelerated(), b.isBaseHardwareAccelerated());
+        assertEquals(a.getLongVersionCode(), b.getLongVersionCode());
+        assertEquals(a.getSharedUserLabel(), b.getSharedUserLabel());
+        assertEquals(a.getInstallLocation(), b.getInstallLocation());
+        assertEquals(a.isCoreApp(), b.isCoreApp());
+        assertEquals(a.isRequiredForAllUsers(), b.isRequiredForAllUsers());
+        assertEquals(a.getCompileSdkVersion(), b.getCompileSdkVersion());
+        assertEquals(a.getCompileSdkVersionCodeName(), b.getCompileSdkVersionCodeName());
+        assertEquals(a.isUse32BitAbi(), b.isUse32BitAbi());
+        assertEquals(a.getPackageName(), b.getPackageName());
+        assertArrayEquals(a.getSplitNames(), b.getSplitNames());
+        assertEquals(a.getVolumeUuid(), b.getVolumeUuid());
+        assertEquals(a.getPath(), b.getPath());
+        assertEquals(a.getBaseApkPath(), b.getBaseApkPath());
+        assertArrayEquals(a.getSplitCodePaths(), b.getSplitCodePaths());
+        assertArrayEquals(a.getSplitRevisionCodes(), b.getSplitRevisionCodes());
+        assertArrayEquals(a.getSplitFlags(), b.getSplitFlags());
+
+        PackageInfo aInfo = PackageInfoUtils.generate(a, new int[]{}, 0, 0, 0,
+                Collections.emptySet(), PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(a));
+        PackageInfo bInfo = PackageInfoUtils.generate(b, new int[]{}, 0, 0, 0,
+                Collections.emptySet(), PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(b));
+        assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
+
+        assertEquals(ArrayUtils.size(a.getPermissions()), ArrayUtils.size(b.getPermissions()));
+        for (int i = 0; i < ArrayUtils.size(a.getPermissions()); ++i) {
+            assertPermissionsEqual(a.getPermissions().get(i), b.getPermissions().get(i));
+        }
+
+        assertEquals(ArrayUtils.size(a.getPermissionGroups()),
+                ArrayUtils.size(b.getPermissionGroups()));
+        for (int i = 0; i < a.getPermissionGroups().size(); ++i) {
+            assertPermissionGroupsEqual(a.getPermissionGroups().get(i),
+                    b.getPermissionGroups().get(i));
+        }
+
+        assertEquals(ArrayUtils.size(a.getActivities()), ArrayUtils.size(b.getActivities()));
+        for (int i = 0; i < ArrayUtils.size(a.getActivities()); ++i) {
+            assertActivitiesEqual(a, a.getActivities().get(i), b, b.getActivities().get(i));
+        }
+
+        assertEquals(ArrayUtils.size(a.getReceivers()), ArrayUtils.size(b.getReceivers()));
+        for (int i = 0; i < ArrayUtils.size(a.getReceivers()); ++i) {
+            assertActivitiesEqual(a, a.getReceivers().get(i), b, b.getReceivers().get(i));
+        }
+
+        assertEquals(ArrayUtils.size(a.getProviders()), ArrayUtils.size(b.getProviders()));
+        for (int i = 0; i < ArrayUtils.size(a.getProviders()); ++i) {
+            assertProvidersEqual(a, a.getProviders().get(i), b, b.getProviders().get(i));
+        }
+
+        assertEquals(ArrayUtils.size(a.getServices()), ArrayUtils.size(b.getServices()));
+        for (int i = 0; i < ArrayUtils.size(a.getServices()); ++i) {
+            assertServicesEqual(a, a.getServices().get(i), b, b.getServices().get(i));
+        }
+
+        assertEquals(ArrayUtils.size(a.getInstrumentations()),
+                ArrayUtils.size(b.getInstrumentations()));
+        for (int i = 0; i < ArrayUtils.size(a.getInstrumentations()); ++i) {
+            assertInstrumentationEqual(a.getInstrumentations().get(i),
+                    b.getInstrumentations().get(i));
+        }
+
+        assertEquals(a.getProperties().size(), b.getProperties().size());
+        final Iterator<String> iter = a.getProperties().keySet().iterator();
+        while (iter.hasNext()) {
+            final String key = iter.next();
+            assertEquals(a.getProperties().get(key), b.getProperties().get(key));
+        }
+
+        assertEquals(a.getRequestedPermissions(), b.getRequestedPermissions());
+        assertEquals(a.getProtectedBroadcasts(), b.getProtectedBroadcasts());
+        assertEquals(a.getLibraryNames(), b.getLibraryNames());
+        assertEquals(a.getUsesLibraries(), b.getUsesLibraries());
+        assertEquals(a.getUsesOptionalLibraries(), b.getUsesOptionalLibraries());
+        assertEquals(a.getOriginalPackages(), b.getOriginalPackages());
+        assertEquals(a.getManifestPackageName(), b.getManifestPackageName());
+        assertEquals(a.getAdoptPermissions(), b.getAdoptPermissions());
+        assertBundleApproximateEquals(a.getMetaData(), b.getMetaData());
+        assertEquals(a.getVersionName(), b.getVersionName());
+        assertEquals(a.getSharedUserId(), b.getSharedUserId());
+        assertArrayEquals(a.getSigningDetails().getSignatures(),
+                b.getSigningDetails().getSignatures());
+        assertEquals(a.getRestrictedAccountType(), b.getRestrictedAccountType());
+        assertEquals(a.getRequiredAccountType(), b.getRequiredAccountType());
+        assertEquals(a.getOverlayTarget(), b.getOverlayTarget());
+        assertEquals(a.getOverlayTargetOverlayableName(), b.getOverlayTargetOverlayableName());
+        assertEquals(a.getOverlayCategory(), b.getOverlayCategory());
+        assertEquals(a.getOverlayPriority(), b.getOverlayPriority());
+        assertEquals(a.isOverlayIsStatic(), b.isOverlayIsStatic());
+        assertEquals(a.getSigningDetails().getPublicKeys(), b.getSigningDetails().getPublicKeys());
+        assertEquals(a.getUpgradeKeySets(), b.getUpgradeKeySets());
+        assertEquals(a.getKeySetMapping(), b.getKeySetMapping());
+        assertArrayEquals(a.getRestrictUpdateHash(), b.getRestrictUpdateHash());
+    }
+
+    private static void assertBundleApproximateEquals(Bundle a, Bundle b) {
+        if (a == b) {
+            return;
+        }
+
+        // Force the bundles to be unparceled.
+        a.getBoolean("foo");
+        b.getBoolean("foo");
+
+        assertEquals(a.toString(), b.toString());
+    }
+
+    private static void assertComponentsEqual(ParsedComponent a, ParsedComponent b) {
+        assertEquals(a.getName(), b.getName());
+        assertBundleApproximateEquals(a.getMetaData(), b.getMetaData());
+        assertEquals(a.getComponentName(), b.getComponentName());
+
+        if (a.getIntents() != null && b.getIntents() != null) {
+            assertEquals(a.getIntents().size(), b.getIntents().size());
+        } else if (a.getIntents() == null || b.getIntents() == null) {
+            return;
+        }
+
+        for (int i = 0; i < a.getIntents().size(); ++i) {
+            ParsedIntentInfo aIntent = a.getIntents().get(i);
+            ParsedIntentInfo bIntent = b.getIntents().get(i);
+
+            assertEquals(aIntent.isHasDefault(), bIntent.isHasDefault());
+            assertEquals(aIntent.getLabelRes(), bIntent.getLabelRes());
+            assertEquals(aIntent.getNonLocalizedLabel(), bIntent.getNonLocalizedLabel());
+            assertEquals(aIntent.getIcon(), bIntent.getIcon());
+        }
+
+        assertEquals(a.getProperties().size(), b.getProperties().size());
+        final Iterator<String> iter = a.getProperties().keySet().iterator();
+        while (iter.hasNext()) {
+            final String key = iter.next();
+            assertEquals(a.getProperties().get(key), b.getProperties().get(key));
+        }
+    }
+
+    private static void assertPermissionsEqual(ParsedPermission a, ParsedPermission b) {
+        assertComponentsEqual(a, b);
+        assertEquals(a.isTree(), b.isTree());
+
+        // Verify basic flags in PermissionInfo to make sure they're consistent. We don't perform
+        // a full structural equality here because the code that serializes them isn't parser
+        // specific and is tested elsewhere.
+        assertEquals(ParsedPermissionUtils.getProtection(a),
+                ParsedPermissionUtils.getProtection(b));
+        assertEquals(a.getGroup(), b.getGroup());
+        assertEquals(a.getFlags(), b.getFlags());
+
+        if (a.getParsedPermissionGroup() != null && b.getParsedPermissionGroup() != null) {
+            assertPermissionGroupsEqual(a.getParsedPermissionGroup(), b.getParsedPermissionGroup());
+        } else if (a.getParsedPermissionGroup() != null || b.getParsedPermissionGroup() != null) {
+            throw new AssertionError();
+        }
+    }
+
+    private static void assertInstrumentationEqual(ParsedInstrumentation a,
+            ParsedInstrumentation b) {
+        assertComponentsEqual(a, b);
+
+        // Validity check for InstrumentationInfo.
+        assertEquals(a.getTargetPackage(), b.getTargetPackage());
+        assertEquals(a.getTargetProcesses(), b.getTargetProcesses());
+        assertEquals(a.isHandleProfiling(), b.isHandleProfiling());
+        assertEquals(a.isFunctionalTest(), b.isFunctionalTest());
+    }
+
+    private static void assertServicesEqual(
+            AndroidPackage aPkg,
+            ParsedService a,
+            AndroidPackage bPkg,
+            ParsedService b
+    ) {
+        assertComponentsEqual(a, b);
+
+        // Validity check for ServiceInfo.
+        ServiceInfo aInfo = PackageInfoUtils.generateServiceInfo(aPkg, a, 0,
+                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(aPkg));
+        ServiceInfo bInfo = PackageInfoUtils.generateServiceInfo(bPkg, b, 0,
+                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(bPkg));
+        assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
+        assertEquals(a.getName(), b.getName());
+    }
+
+    private static void assertProvidersEqual(
+            AndroidPackage aPkg,
+            ParsedProvider a,
+            AndroidPackage bPkg,
+            ParsedProvider b
+    ) {
+        assertComponentsEqual(a, b);
+        assertEquals(a.getName(), b.getName());
+    }
+
+    private static void assertActivitiesEqual(
+            AndroidPackage aPkg,
+            ParsedActivity a,
+            AndroidPackage bPkg,
+            ParsedActivity b
+    ) {
+        assertComponentsEqual(a, b);
+
+        // Validity check for ActivityInfo.
+        ActivityInfo aInfo = PackageInfoUtils.generateActivityInfo(aPkg, a, 0,
+                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(aPkg));
+        ActivityInfo bInfo = PackageInfoUtils.generateActivityInfo(bPkg, b, 0,
+                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(bPkg));
+        assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
+        assertEquals(a.getName(), b.getName());
+    }
+
+    private static void assertPermissionGroupsEqual(ParsedPermissionGroup a,
+            ParsedPermissionGroup b) {
+        assertComponentsEqual(a, b);
+
+        // Validity check for PermissionGroupInfo.
+        assertEquals(a.getName(), b.getName());
+        assertEquals(a.getDescriptionRes(), b.getDescriptionRes());
+    }
+
+    private static void assertApplicationInfoEqual(ApplicationInfo a, ApplicationInfo that) {
+        assertEquals(a.descriptionRes, that.descriptionRes);
+        assertEquals(a.theme, that.theme);
+        assertEquals(a.fullBackupContent, that.fullBackupContent);
+        assertEquals(a.uiOptions, that.uiOptions);
+        assertEquals(Integer.toBinaryString(a.flags), Integer.toBinaryString(that.flags));
+        assertEquals(a.privateFlags, that.privateFlags);
+        assertEquals(a.requiresSmallestWidthDp, that.requiresSmallestWidthDp);
+        assertEquals(a.compatibleWidthLimitDp, that.compatibleWidthLimitDp);
+        assertEquals(a.largestWidthLimitDp, that.largestWidthLimitDp);
+        assertEquals(a.nativeLibraryRootRequiresIsa, that.nativeLibraryRootRequiresIsa);
+        assertEquals(a.uid, that.uid);
+        assertEquals(a.minSdkVersion, that.minSdkVersion);
+        assertEquals(a.targetSdkVersion, that.targetSdkVersion);
+        assertEquals(a.versionCode, that.versionCode);
+        assertEquals(a.enabled, that.enabled);
+        assertEquals(a.enabledSetting, that.enabledSetting);
+        assertEquals(a.installLocation, that.installLocation);
+        assertEquals(a.networkSecurityConfigRes, that.networkSecurityConfigRes);
+        assertEquals(a.taskAffinity, that.taskAffinity);
+        assertEquals(a.permission, that.permission);
+        assertEquals(a.getKnownActivityEmbeddingCerts(), that.getKnownActivityEmbeddingCerts());
+        assertEquals(a.processName, that.processName);
+        assertEquals(a.className, that.className);
+        assertEquals(a.manageSpaceActivityName, that.manageSpaceActivityName);
+        assertEquals(a.backupAgentName, that.backupAgentName);
+        assertEquals(a.volumeUuid, that.volumeUuid);
+        assertEquals(a.scanSourceDir, that.scanSourceDir);
+        assertEquals(a.scanPublicSourceDir, that.scanPublicSourceDir);
+        assertEquals(a.sourceDir, that.sourceDir);
+        assertEquals(a.publicSourceDir, that.publicSourceDir);
+        assertArrayEquals(a.splitSourceDirs, that.splitSourceDirs);
+        assertArrayEquals(a.splitPublicSourceDirs, that.splitPublicSourceDirs);
+        assertArrayEquals(a.resourceDirs, that.resourceDirs);
+        assertArrayEquals(a.overlayPaths, that.overlayPaths);
+        assertEquals(a.seInfo, that.seInfo);
+        assertArrayEquals(a.sharedLibraryFiles, that.sharedLibraryFiles);
+        assertEquals(a.dataDir, that.dataDir);
+        assertEquals(a.deviceProtectedDataDir, that.deviceProtectedDataDir);
+        assertEquals(a.credentialProtectedDataDir, that.credentialProtectedDataDir);
+        assertEquals(a.nativeLibraryDir, that.nativeLibraryDir);
+        assertEquals(a.secondaryNativeLibraryDir, that.secondaryNativeLibraryDir);
+        assertEquals(a.nativeLibraryRootDir, that.nativeLibraryRootDir);
+        assertEquals(a.primaryCpuAbi, that.primaryCpuAbi);
+        assertEquals(a.secondaryCpuAbi, that.secondaryCpuAbi);
+    }
+
+    public static void setKnownFields(ParsingPackage pkg) {
+        Bundle bundle = new Bundle();
+        bundle.putString("key", "value");
+
+        ParsedPermissionImpl permission = new ParsedPermissionImpl();
+        permission.setParsedPermissionGroup(new ParsedPermissionGroupImpl());
+
+        ((ParsedPackage) pkg.setBaseRevisionCode(100)
+                .setBaseHardwareAccelerated(true)
+                .setSharedUserLabel(100)
+                .setInstallLocation(100)
+                .setRequiredForAllUsers(true)
+                .asSplit(
+                        new String[]{"foo2"},
+                        new String[]{"foo6"},
+                        new int[]{100},
+                        null
+                )
+                .setUse32BitAbi(true)
+                .setVolumeUuid("d52ef59a-7def-4541-bf21-4c28ed4b65a0")
+                .addPermission(permission)
+                .addPermissionGroup(new ParsedPermissionGroupImpl())
+                .addActivity(new ParsedActivityImpl())
+                .addReceiver(new ParsedActivityImpl())
+                .addProvider(new ParsedProviderImpl())
+                .addService(new ParsedServiceImpl())
+                .addInstrumentation(new ParsedInstrumentationImpl())
+                .addUsesPermission(new ParsedUsesPermissionImpl("foo7", 0))
+                .addImplicitPermission("foo25")
+                .addProtectedBroadcast("foo8")
+                .setSdkLibraryName("sdk12")
+                .setSdkLibVersionMajor(42)
+                .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"})
+                .setStaticSharedLibraryName("foo23")
+                .setStaticSharedLibVersion(100)
+                .addUsesStaticLibrary("foo23", 100, new String[]{"digest"})
+                .addLibraryName("foo10")
+                .addUsesLibrary("foo11")
+                .addUsesOptionalLibrary("foo12")
+                .addOriginalPackage("foo14")
+                .addAdoptPermission("foo16")
+                .setMetaData(bundle)
+                .setVersionName("foo17")
+                .setSharedUserId("foo18")
+                .setSigningDetails(
+                        new SigningDetails(
+                                new Signature[]{new Signature(new byte[16])},
+                                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
+                                new ArraySet<>(),
+                                null)
+                )
+                .setRestrictedAccountType("foo19")
+                .setRequiredAccountType("foo20")
+                .setOverlayTarget("foo21")
+                .setOverlayPriority(100)
+                .setUpgradeKeySets(new ArraySet<>())
+                .addPreferredActivityFilter("className", new ParsedIntentInfoImpl())
+                .addConfigPreference(new ConfigurationInfo())
+                .addReqFeature(new FeatureInfo())
+                .addFeatureGroup(new FeatureGroupInfo())
+                .setCompileSdkVersionCodeName("foo23")
+                .setCompileSdkVersion(100)
+                .setOverlayCategory("foo24")
+                .setOverlayIsStatic(true)
+                .setOverlayTargetOverlayableName("foo26")
+                .setVisibleToInstantApps(true)
+                .setSplitHasCode(0, true)
+                .hideAsParsed())
+                .setBaseApkPath("foo5")
+                .setPath("foo4")
+                .setVersionCode(100)
+                .setRestrictUpdateHash(new byte[16])
+                .setVersionCodeMajor(100)
+                .setCoreApp(true)
+                .hideAsFinal();
+    }
+
+    private static void assertAllFieldsExist(ParsedPackage pkg) throws Exception {
+        Field[] fields = ParsedPackage.class.getDeclaredFields();
+
+        Set<String> nonSerializedFields = new HashSet<>();
+        nonSerializedFields.add("mExtras");
+        nonSerializedFields.add("packageUsageTimeMillis");
+        nonSerializedFields.add("isStub");
+
+        for (Field f : fields) {
+            final Class<?> fieldType = f.getType();
+
+            if (nonSerializedFields.contains(f.getName())) {
+                continue;
+            }
+
+            if (List.class.isAssignableFrom(fieldType)) {
+                // Validity check for list fields: Assume they're non-null and contain precisely
+                // one element.
+                List<?> list = (List<?>) f.get(pkg);
+                assertNotNull("List was null: " + f, list);
+                assertEquals(1, list.size());
+            } else if (fieldType.getComponentType() != null) {
+                // Validity check for array fields: Assume they're non-null and contain precisely
+                // one element.
+                Object array = f.get(pkg);
+                assertNotNull(Array.get(array, 0));
+            } else if (fieldType == String.class) {
+                // String fields: Check that they're set to "foo".
+                String value = (String) f.get(pkg);
+
+                assertTrue("Bad value for field: " + f, value != null && value.startsWith("foo"));
+            } else if (fieldType == int.class) {
+                // int fields: Check that they're set to 100.
+                int value = (int) f.get(pkg);
+                assertEquals("Bad value for field: " + f, 100, value);
+            } else if (fieldType == boolean.class) {
+                // boolean fields: Check that they're set to true.
+                boolean value = (boolean) f.get(pkg);
+                assertTrue("Bad value for field: " + f, value);
+            } else {
+                // All other fields: Check that they're set.
+                Object o = f.get(pkg);
+                assertNotNull("Field was null: " + f, o);
+            }
+        }
+    }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageSignaturesTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageSignaturesTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageUserStateTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageUserStateTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageVerificationStateTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageVerificationStateTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/ParallelPackageParserTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/ParallelPackageParserTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/PreferredComponentTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PreferredComponentTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/PreferredComponentTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/PreferredComponentTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/RestrictionsSetTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/RestrictionsSetTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/RestrictionsSetTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/RestrictionsSetTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanRequestBuilder.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanRequestBuilder.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/ScanRequestBuilder.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanRequestBuilder.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanTests.java
new file mode 100644
index 0000000..ce0b3a2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanTests.java
@@ -0,0 +1,635 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.SharedLibraryInfo.TYPE_DYNAMIC;
+import static android.content.pm.SharedLibraryInfo.TYPE_SDK_PACKAGE;
+import static android.content.pm.SharedLibraryInfo.TYPE_STATIC;
+import static android.content.pm.SharedLibraryInfo.VERSION_UNDEFINED;
+
+import static com.android.server.pm.PackageManagerService.SCAN_AS_FULL_APP;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
+import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
+import static com.android.server.pm.PackageManagerService.SCAN_NEW_INSTALL;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.collection.IsArrayContainingInOrder.arrayContaining;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertNotSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.SharedLibraryInfo;
+import android.content.res.TypedArray;
+import android.os.Environment;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.util.Pair;
+
+import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.parsing.PackageInfoUtils;
+import com.android.server.pm.parsing.pkg.PackageImpl;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.File;
+import java.util.UUID;
+
+@RunWith(MockitoJUnitRunner.class)
+@Presubmit
+// TODO: shared user tests
+public class ScanTests {
+
+    private static final String DUMMY_PACKAGE_NAME = "some.app.to.test";
+
+    private static final UUID UUID_ONE = UUID.randomUUID();
+    private static final UUID UUID_TWO = UUID.randomUUID();
+
+    @Mock
+    PackageAbiHelper mMockPackageAbiHelper;
+    @Mock
+    UserManagerInternal mMockUserManager;
+    @Mock
+    PlatformCompat mMockCompatibility;
+    @Mock
+    PackageManagerServiceInjector mMockInjector;
+    @Mock
+    PackageManagerService mMockPackageManager;
+    @Mock
+    Installer mMockInstaller;
+
+    @Before
+    public void setupInjector() {
+        when(mMockInjector.getAbiHelper()).thenReturn(mMockPackageAbiHelper);
+        when(mMockInjector.getUserManagerInternal()).thenReturn(mMockUserManager);
+        when(mMockInjector.getCompatibility()).thenReturn(mMockCompatibility);
+
+        DomainVerificationManagerInternal domainVerificationManager =
+                mock(DomainVerificationManagerInternal.class);
+        when(domainVerificationManager.generateNewId())
+                .thenAnswer(invocation -> UUID.randomUUID());
+
+        when(mMockInjector.getDomainVerificationManagerInternal())
+                .thenReturn(domainVerificationManager);
+        when(mMockInjector.getInstaller()).thenReturn(mMockInstaller);
+    }
+
+    @Before
+    public void setupDefaultUser() {
+        when(mMockUserManager.getUserIds()).thenReturn(new int[]{0});
+    }
+
+    @Before
+    public void setupDefaultAbiBehavior() throws Exception {
+        when(mMockPackageAbiHelper.derivePackageAbi(
+                any(AndroidPackage.class), anyBoolean(), nullable(String.class),
+                any(File.class)))
+                .thenReturn(new Pair<>(
+                        new PackageAbiHelper.Abis("derivedPrimary", "derivedSecondary"),
+                        new PackageAbiHelper.NativeLibraryPaths(
+                                "derivedRootDir", true, "derivedNativeDir", "derivedNativeDir2")));
+        when(mMockPackageAbiHelper.deriveNativeLibraryPaths(
+                any(AndroidPackage.class), anyBoolean(), any(File.class)))
+                .thenReturn(new PackageAbiHelper.NativeLibraryPaths(
+                        "getRootDir", true, "getNativeDir", "getNativeDir2"
+                ));
+        when(mMockPackageAbiHelper.getBundledAppAbis(
+                any(AndroidPackage.class)))
+                .thenReturn(new PackageAbiHelper.Abis("bundledPrimary", "bundledSecondary"));
+    }
+
+    @Test
+    public void newInstallSimpleAllNominal() throws Exception {
+        final ScanRequest scanRequest =
+                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
+                        .addScanFlag(PackageManagerService.SCAN_NEW_INSTALL)
+                        .addScanFlag(PackageManagerService.SCAN_AS_FULL_APP)
+                        .build();
+
+        final ScanResult scanResult = executeScan(scanRequest);
+
+        assertBasicPackageScanResult(scanResult, DUMMY_PACKAGE_NAME, false /*isInstant*/);
+        assertThat(scanResult.mExistingSettingCopied, is(false));
+        assertPathsNotDerived(scanResult);
+    }
+
+    @Test
+    public void newInstallForAllUsers() throws Exception {
+        final int[] userIds = {0, 10, 11};
+        when(mMockUserManager.getUserIds()).thenReturn(userIds);
+
+        final ScanRequest scanRequest =
+                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
+                        .setRealPkgName(null)
+                        .addScanFlag(PackageManagerService.SCAN_NEW_INSTALL)
+                        .addScanFlag(PackageManagerService.SCAN_AS_FULL_APP)
+                        .build();
+        final ScanResult scanResult = executeScan(scanRequest);
+
+        for (int uid : userIds) {
+            assertThat(scanResult.mPkgSetting.readUserState(uid).isInstalled(), is(true));
+        }
+    }
+
+    @Test
+    public void installRealPackageName() throws Exception {
+        final ScanRequest scanRequest =
+                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
+                        .setRealPkgName("com.package.real")
+                        .build();
+
+        final ScanResult scanResult = executeScan(scanRequest);
+
+        assertThat(scanResult.mPkgSetting.getRealName(), is("com.package.real"));
+
+        final ScanRequest scanRequestNoRealPkg =
+                createBasicScanRequestBuilder(
+                        createBasicPackage(DUMMY_PACKAGE_NAME)
+                                .addOriginalPackage("com.package.real"))
+                        .build();
+
+        final ScanResult scanResultNoReal = executeScan(scanRequestNoRealPkg);
+        assertThat(scanResultNoReal.mPkgSetting.getRealName(), nullValue());
+    }
+
+    @Test
+    public void updateSimpleNominal() throws Exception {
+        when(mMockUserManager.getUserIds()).thenReturn(new int[]{0});
+
+        final PackageSetting pkgSetting = createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
+                .setPrimaryCpuAbiString("primaryCpuAbi")
+                .setSecondaryCpuAbiString("secondaryCpuAbi")
+                .build();
+        final ScanRequest scanRequest =
+                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
+                        .addScanFlag(PackageManagerService.SCAN_AS_FULL_APP)
+                        .setPkgSetting(pkgSetting)
+                        .build();
+
+
+        final ScanResult scanResult = executeScan(scanRequest);
+
+        assertThat(scanResult.mExistingSettingCopied, is(true));
+
+        // ensure we don't overwrite the existing pkgSetting, in case something post-scan fails
+        assertNotSame(pkgSetting, scanResult.mPkgSetting);
+
+        assertBasicPackageScanResult(scanResult, DUMMY_PACKAGE_NAME, false /*isInstant*/);
+
+        assertThat(scanResult.mPkgSetting.getPrimaryCpuAbiLegacy(), is("primaryCpuAbi"));
+        assertThat(scanResult.mPkgSetting.getSecondaryCpuAbiLegacy(), is("secondaryCpuAbi"));
+        assertThat(scanResult.mPkgSetting.getCpuAbiOverride(), nullValue());
+
+        assertPathsNotDerived(scanResult);
+    }
+
+    @Test
+    public void updateInstantSimpleNominal() throws Exception {
+        when(mMockUserManager.getUserIds()).thenReturn(new int[]{0});
+
+        final PackageSetting existingPkgSetting =
+                createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
+                        .setInstantAppUserState(0, true)
+                        .build();
+
+        final ScanRequest scanRequest =
+                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
+                        .setPkgSetting(existingPkgSetting)
+                        .build();
+
+
+        final ScanResult scanResult = executeScan(scanRequest);
+
+        assertBasicPackageScanResult(scanResult, DUMMY_PACKAGE_NAME, true /*isInstant*/);
+    }
+
+    @Test
+    public void installSdkLibrary() throws Exception {
+        final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("ogl.sdk_123")
+                .setSdkLibraryName("ogl.sdk")
+                .setSdkLibVersionMajor(123)
+                .hideAsParsed())
+                .setPackageName("ogl.sdk_123")
+                .setVersionCodeMajor(5)
+                .setVersionCode(678)
+                .setBaseApkPath("/some/path.apk")
+                .setSplitCodePaths(new String[] {"/some/other/path.apk"});
+
+        final ScanRequest scanRequest = new ScanRequestBuilder(pkg)
+                .setUser(UserHandle.of(0)).build();
+
+        final ScanResult scanResult = executeScan(scanRequest);
+
+        assertThat(scanResult.mSdkSharedLibraryInfo.getPackageName(), is("ogl.sdk_123"));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getName(), is("ogl.sdk"));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getLongVersion(), is(123L));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getType(), is(TYPE_SDK_PACKAGE));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getDeclaringPackage().getPackageName(),
+                is("ogl.sdk_123"));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getDeclaringPackage().getLongVersionCode(),
+                is(pkg.getLongVersionCode()));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getAllCodePaths(),
+                hasItems("/some/path.apk", "/some/other/path.apk"));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getDependencies(), nullValue());
+        assertThat(scanResult.mSdkSharedLibraryInfo.getDependentPackages(), empty());
+    }
+
+    @Test
+    public void installStaticSharedLibrary() throws Exception {
+        final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("static.lib.pkg")
+                .setStaticSharedLibraryName("static.lib")
+                .setStaticSharedLibVersion(123L)
+                .hideAsParsed())
+                .setPackageName("static.lib.pkg.123")
+                .setVersionCodeMajor(1)
+                .setVersionCode(234)
+                .setBaseApkPath("/some/path.apk")
+                .setSplitCodePaths(new String[] {"/some/other/path.apk"});
+
+        final ScanRequest scanRequest = new ScanRequestBuilder(pkg)
+                .setUser(UserHandle.of(0)).build();
+
+
+        final ScanResult scanResult = executeScan(scanRequest);
+
+        assertThat(scanResult.mStaticSharedLibraryInfo.getPackageName(), is("static.lib.pkg.123"));
+        assertThat(scanResult.mStaticSharedLibraryInfo.getName(), is("static.lib"));
+        assertThat(scanResult.mStaticSharedLibraryInfo.getLongVersion(), is(123L));
+        assertThat(scanResult.mStaticSharedLibraryInfo.getType(), is(TYPE_STATIC));
+        assertThat(scanResult.mStaticSharedLibraryInfo.getDeclaringPackage().getPackageName(),
+                is("static.lib.pkg"));
+        assertThat(scanResult.mStaticSharedLibraryInfo.getDeclaringPackage().getLongVersionCode(),
+                is(pkg.getLongVersionCode()));
+        assertThat(scanResult.mStaticSharedLibraryInfo.getAllCodePaths(),
+                hasItems("/some/path.apk", "/some/other/path.apk"));
+        assertThat(scanResult.mStaticSharedLibraryInfo.getDependencies(), nullValue());
+        assertThat(scanResult.mStaticSharedLibraryInfo.getDependentPackages(), empty());
+    }
+
+    @Test
+    public void installDynamicLibraries() throws Exception {
+        final ParsedPackage pkg = ((ParsedPackage) createBasicPackage(
+                "dynamic.lib.pkg")
+                .addLibraryName("liba")
+                .addLibraryName("libb")
+                .hideAsParsed())
+                .setVersionCodeMajor(1)
+                .setVersionCode(234)
+                .setBaseApkPath("/some/path.apk")
+                .setSplitCodePaths(new String[] {"/some/other/path.apk"});
+
+        final ScanRequest scanRequest =
+                new ScanRequestBuilder(pkg).setUser(UserHandle.of(0)).build();
+
+
+        final ScanResult scanResult = executeScan(scanRequest);
+
+        final SharedLibraryInfo dynamicLib0 = scanResult.mDynamicSharedLibraryInfos.get(0);
+        assertThat(dynamicLib0.getPackageName(), is("dynamic.lib.pkg"));
+        assertThat(dynamicLib0.getName(), is("liba"));
+        assertThat(dynamicLib0.getLongVersion(), is((long) VERSION_UNDEFINED));
+        assertThat(dynamicLib0.getType(), is(TYPE_DYNAMIC));
+        assertThat(dynamicLib0.getDeclaringPackage().getPackageName(), is("dynamic.lib.pkg"));
+        assertThat(dynamicLib0.getDeclaringPackage().getLongVersionCode(),
+                is(pkg.getLongVersionCode()));
+        assertThat(dynamicLib0.getAllCodePaths(),
+                hasItems("/some/path.apk", "/some/other/path.apk"));
+        assertThat(dynamicLib0.getDependencies(), nullValue());
+        assertThat(dynamicLib0.getDependentPackages(), empty());
+
+        final SharedLibraryInfo dynamicLib1 = scanResult.mDynamicSharedLibraryInfos.get(1);
+        assertThat(dynamicLib1.getPackageName(), is("dynamic.lib.pkg"));
+        assertThat(dynamicLib1.getName(), is("libb"));
+        assertThat(dynamicLib1.getLongVersion(), is((long) VERSION_UNDEFINED));
+        assertThat(dynamicLib1.getType(), is(TYPE_DYNAMIC));
+        assertThat(dynamicLib1.getDeclaringPackage().getPackageName(), is("dynamic.lib.pkg"));
+        assertThat(dynamicLib1.getDeclaringPackage().getLongVersionCode(),
+                is(pkg.getLongVersionCode()));
+        assertThat(dynamicLib1.getAllCodePaths(),
+                hasItems("/some/path.apk", "/some/other/path.apk"));
+        assertThat(dynamicLib1.getDependencies(), nullValue());
+        assertThat(dynamicLib1.getDependentPackages(), empty());
+    }
+
+    @Test
+    public void volumeUuidChangesOnUpdate() throws Exception {
+        final PackageSetting pkgSetting =
+                createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
+                        .setVolumeUuid("someUuid")
+                        .build();
+
+        final ParsedPackage basicPackage = ((ParsedPackage) createBasicPackage(DUMMY_PACKAGE_NAME)
+                .setVolumeUuid(UUID_TWO.toString())
+                .hideAsParsed());
+
+
+        final ScanResult scanResult = executeScan(
+                new ScanRequestBuilder(basicPackage).setPkgSetting(pkgSetting).build());
+
+        assertThat(scanResult.mPkgSetting.getVolumeUuid(), is(UUID_TWO.toString()));
+    }
+
+    @Test
+    public void scanFirstBoot_derivesAbis() throws Exception {
+        final PackageSetting pkgSetting =
+                createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME).build();
+
+        final ParsedPackage basicPackage =
+                ((ParsedPackage) createBasicPackage(DUMMY_PACKAGE_NAME)
+                        .hideAsParsed());
+
+
+        final ScanResult scanResult = executeScan(new ScanRequestBuilder(
+                basicPackage)
+                .setPkgSetting(pkgSetting)
+                .addScanFlag(SCAN_FIRST_BOOT_OR_UPGRADE)
+                .build());
+
+        assertAbiAndPathssDerived(scanResult);
+    }
+
+    @Test
+    public void scanWithOriginalPkgSetting_packageNameChanges() throws Exception {
+        final PackageSetting originalPkgSetting =
+                createBasicPackageSettingBuilder("original.package").build();
+
+        final ParsedPackage basicPackage =
+                (ParsedPackage) createBasicPackage(DUMMY_PACKAGE_NAME)
+                        .hideAsParsed();
+
+
+        final ScanResult result =
+                executeScan(new ScanRequestBuilder(basicPackage)
+                        .setOriginalPkgSetting(originalPkgSetting)
+                        .build());
+
+        assertThat(result.mRequest.mParsedPackage.getPackageName(), is("original.package"));
+    }
+
+    @Test
+    public void updateInstant_changeToFull() throws Exception {
+        when(mMockUserManager.getUserIds()).thenReturn(new int[]{0});
+
+        final PackageSetting existingPkgSetting =
+                createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
+                        .setInstantAppUserState(0, true)
+                        .build();
+
+        final ScanRequest scanRequest =
+                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
+                        .setPkgSetting(existingPkgSetting)
+                        .addScanFlag(SCAN_AS_FULL_APP)
+                        .build();
+
+
+        final ScanResult scanResult = executeScan(scanRequest);
+
+        assertBasicPackageScanResult(scanResult, DUMMY_PACKAGE_NAME, false /*isInstant*/);
+    }
+
+    @Test
+    public void updateFull_changeToInstant() throws Exception {
+        when(mMockUserManager.getUserIds()).thenReturn(new int[]{0});
+
+        final PackageSetting existingPkgSetting =
+                createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
+                        .setInstantAppUserState(0, false)
+                        .build();
+
+        final ScanRequest scanRequest =
+                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
+                        .setPkgSetting(existingPkgSetting)
+                        .addScanFlag(SCAN_AS_INSTANT_APP)
+                        .build();
+
+
+        final ScanResult scanResult = executeScan(scanRequest);
+
+        assertBasicPackageScanResult(scanResult, DUMMY_PACKAGE_NAME, true /*isInstant*/);
+    }
+
+    @Test
+    public void updateSystemApp_applicationInfoFlagSet() throws Exception {
+        final PackageSetting existingPkgSetting =
+                createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
+                        .setPkgFlags(ApplicationInfo.FLAG_SYSTEM)
+                        .build();
+
+        final ScanRequest scanRequest =
+                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
+                        .setPkgSetting(existingPkgSetting)
+                        .setDisabledPkgSetting(existingPkgSetting)
+                        .addScanFlag(SCAN_NEW_INSTALL)
+                        .build();
+
+        final ScanResult scanResult = executeScan(scanRequest);
+
+        int appInfoFlags = PackageInfoUtils.appInfoFlags(scanResult.mRequest.mParsedPackage,
+                scanResult.mPkgSetting);
+        assertThat(appInfoFlags, hasFlag(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP));
+    }
+
+    @Test
+    public void factoryTestFlagSet() throws Exception {
+        final ParsingPackage basicPackage = createBasicPackage(DUMMY_PACKAGE_NAME)
+                .addUsesPermission(
+                        new ParsedUsesPermissionImpl(Manifest.permission.FACTORY_TEST, 0));
+
+        final ScanResult scanResult = ScanPackageUtils.scanPackageOnlyLI(
+                createBasicScanRequestBuilder(basicPackage).build(),
+                mMockInjector,
+                true /*isUnderFactoryTest*/,
+                System.currentTimeMillis());
+
+        int appInfoFlags = PackageInfoUtils.appInfoFlags(scanResult.mRequest.mParsedPackage,
+                scanResult.mRequest.mPkgSetting);
+        assertThat(appInfoFlags, hasFlag(ApplicationInfo.FLAG_FACTORY_TEST));
+    }
+
+    @Test
+    public void scanSystemApp_isOrphanedTrue() throws Exception {
+        final ParsedPackage pkg = ((ParsedPackage) createBasicPackage(DUMMY_PACKAGE_NAME)
+                .hideAsParsed())
+                .setSystem(true);
+
+        final ScanRequest scanRequest =
+                createBasicScanRequestBuilder(pkg)
+                        .build();
+
+        final ScanResult scanResult = executeScan(scanRequest);
+
+        assertThat(scanResult.mPkgSetting.getInstallSource().mIsOrphaned, is(true));
+    }
+
+    private static Matcher<Integer> hasFlag(final int flag) {
+        return new BaseMatcher<Integer>() {
+            @Override public void describeTo(Description description) {
+                description.appendText("flags ");
+            }
+
+            @Override public boolean matches(Object item) {
+                return ((int) item & flag) != 0;
+            }
+
+            @Override
+            public void describeMismatch(Object item, Description mismatchDescription) {
+                mismatchDescription
+                        .appendValue(item)
+                        .appendText(" does not contain flag ")
+                        .appendValue(flag);
+            }
+        };
+    }
+
+    private ScanResult executeScan(
+            ScanRequest scanRequest) throws PackageManagerException {
+        ScanResult result = ScanPackageUtils.scanPackageOnlyLI(
+                scanRequest,
+                mMockInjector,
+                false /*isUnderFactoryTest*/,
+                System.currentTimeMillis());
+
+        // Need to call hideAsFinal to cache derived fields. This is normally done in PMS, but not
+        // in this cut down flow used for the test.
+        ((ParsedPackage) result.mPkgSetting.getPkg()).hideAsFinal();
+        return result;
+    }
+
+    private static String createCodePath(String packageName) {
+        return "/data/app/" + packageName + "-randompath";
+    }
+
+    private static PackageSettingBuilder createBasicPackageSettingBuilder(String packageName) {
+        return new PackageSettingBuilder()
+                .setName(packageName)
+                .setCodePath(createCodePath(packageName));
+    }
+
+    private static ScanRequestBuilder createBasicScanRequestBuilder(ParsingPackage pkg) {
+        return new ScanRequestBuilder((ParsedPackage) pkg.hideAsParsed())
+                .setUser(UserHandle.of(0));
+    }
+
+    private static ScanRequestBuilder createBasicScanRequestBuilder(ParsedPackage pkg) {
+        return new ScanRequestBuilder(pkg)
+                .setUser(UserHandle.of(0));
+    }
+
+    private static ParsingPackage createBasicPackage(String packageName) {
+        // TODO(b/135203078): Make this use PackageImpl.forParsing and separate the steps
+        return (ParsingPackage) ((ParsedPackage) new PackageImpl(packageName,
+                "/data/tmp/randompath/base.apk", createCodePath(packageName),
+                mock(TypedArray.class), false)
+                .setVolumeUuid(UUID_ONE.toString())
+                .addUsesStaticLibrary("some.static.library", 234L, new String[]{"testCert1"})
+                .addUsesStaticLibrary("some.other.static.library", 456L, new String[]{"testCert2"})
+                .addUsesSdkLibrary("some.sdk.library", 123L, new String[]{"testCert3"})
+                .addUsesSdkLibrary("some.other.sdk.library", 789L, new String[]{"testCert4"})
+                .hideAsParsed())
+                .setNativeLibraryRootDir("/data/tmp/randompath/base.apk:/lib")
+                .setVersionCodeMajor(1)
+                .setVersionCode(2345);
+    }
+
+    private static void assertBasicPackageScanResult(
+            ScanResult scanResult, String packageName, boolean isInstant) {
+
+        final PackageSetting pkgSetting = scanResult.mPkgSetting;
+        assertBasicPackageSetting(scanResult, packageName, isInstant, pkgSetting);
+
+        final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
+                pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
+        assertBasicApplicationInfo(scanResult, applicationInfo);
+    }
+
+    private static void assertBasicPackageSetting(ScanResult scanResult,
+            String packageName, boolean isInstant, PackageSetting pkgSetting) {
+        assertThat(pkgSetting.getPkg().getPackageName(), is(packageName));
+        assertThat(pkgSetting.getInstantApp(0), is(isInstant));
+        assertThat(pkgSetting.getUsesStaticLibraries(),
+                arrayContaining("some.static.library", "some.other.static.library"));
+        assertThat(pkgSetting.getUsesStaticLibrariesVersions(), is(new long[]{234L, 456L}));
+        assertThat(pkgSetting.getUsesSdkLibraries(),
+                arrayContaining("some.sdk.library", "some.other.sdk.library"));
+        assertThat(pkgSetting.getUsesSdkLibrariesVersionsMajor(), is(new long[]{123L, 789L}));
+        assertThat(pkgSetting.getPkg(), is(scanResult.mRequest.mParsedPackage));
+        assertThat(pkgSetting.getPath(), is(new File(createCodePath(packageName))));
+        assertThat(pkgSetting.getVersionCode(),
+                is(PackageInfo.composeLongVersionCode(1, 2345)));
+    }
+
+    private static void assertBasicApplicationInfo(ScanResult scanResult,
+            ApplicationInfo applicationInfo) {
+        assertThat(applicationInfo.processName,
+                is(scanResult.mRequest.mParsedPackage.getPackageName()));
+
+        final int uid = applicationInfo.uid;
+        assertThat(UserHandle.getUserId(uid), is(UserHandle.USER_SYSTEM));
+
+        final String calculatedCredentialId = Environment.getDataUserCePackageDirectory(
+                applicationInfo.volumeUuid, UserHandle.USER_SYSTEM,
+                scanResult.mRequest.mParsedPackage.getPackageName()).getAbsolutePath();
+        assertThat(applicationInfo.credentialProtectedDataDir, is(calculatedCredentialId));
+        assertThat(applicationInfo.dataDir, is(applicationInfo.credentialProtectedDataDir));
+    }
+
+    private static void assertAbiAndPathssDerived(ScanResult scanResult) {
+        PackageSetting pkgSetting = scanResult.mPkgSetting;
+        final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
+                pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
+        assertThat(applicationInfo.primaryCpuAbi, is("derivedPrimary"));
+        assertThat(applicationInfo.secondaryCpuAbi, is("derivedSecondary"));
+
+        assertThat(applicationInfo.nativeLibraryRootDir, is("derivedRootDir"));
+        assertThat(pkgSetting.getLegacyNativeLibraryPath(), is("derivedRootDir"));
+        assertThat(applicationInfo.nativeLibraryRootRequiresIsa, is(true));
+        assertThat(applicationInfo.nativeLibraryDir, is("derivedNativeDir"));
+        assertThat(applicationInfo.secondaryNativeLibraryDir, is("derivedNativeDir2"));
+    }
+
+    private static void assertPathsNotDerived(ScanResult scanResult) {
+        PackageSetting pkgSetting = scanResult.mPkgSetting;
+        final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
+                pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
+        assertThat(applicationInfo.nativeLibraryRootDir, is("getRootDir"));
+        assertThat(pkgSetting.getLegacyNativeLibraryPath(), is("getRootDir"));
+        assertThat(applicationInfo.nativeLibraryRootRequiresIsa, is(true));
+        assertThat(applicationInfo.nativeLibraryDir, is("getNativeDir"));
+        assertThat(applicationInfo.secondaryNativeLibraryDir, is("getNativeDir2"));
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/UserDataPreparerTest.java
new file mode 100644
index 0000000..afbe352
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/UserDataPreparerTest.java
@@ -0,0 +1,258 @@
+/*
+ * 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.server.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.FileUtils;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Collections;
+
+// atest PackageManagerServiceTest:com.android.server.pm.UserDataPreparerTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+@SmallTest
+public class UserDataPreparerTest {
+
+    private static final int TEST_USER_SERIAL = 1000;
+    private static final int TEST_USER_ID = 10;
+
+    private TestUserDataPreparer mUserDataPreparer;
+
+    @Mock
+    private StorageManager mStorageManagerMock;
+
+    @Mock
+    private Context mContextMock;
+
+    @Mock
+    private Installer mInstaller;
+
+    private Object mInstallLock;
+
+    @Before
+    public void setup() {
+        Context ctx = InstrumentationRegistry.getContext();
+        FileUtils.deleteContents(ctx.getCacheDir());
+        mInstallLock = new Object();
+        MockitoAnnotations.initMocks(this);
+        mUserDataPreparer = new TestUserDataPreparer(mInstaller, mInstallLock, mContextMock,
+                ctx.getCacheDir());
+        when(mContextMock.getSystemServiceName(StorageManager.class))
+                .thenReturn(Context.STORAGE_SERVICE);
+        when(mContextMock.getSystemService(eq(Context.STORAGE_SERVICE)))
+                .thenReturn(mStorageManagerMock);
+        VolumeInfo testVolume = new VolumeInfo("testuuid", VolumeInfo.TYPE_PRIVATE, null, null);
+        when(mStorageManagerMock.getWritablePrivateVolumes()).thenReturn(Arrays.asList(testVolume));
+    }
+
+    @Test
+    public void testPrepareUserData_De() throws Exception {
+        File userDeDir = mUserDataPreparer.getDataUserDeDirectory(null, TEST_USER_ID);
+        userDeDir.mkdirs();
+        File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID);
+        systemDeDir.mkdirs();
+        mUserDataPreparer
+                .prepareUserData(TEST_USER_ID, TEST_USER_SERIAL, StorageManager.FLAG_STORAGE_DE);
+        verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
+                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
+        verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
+                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
+        int serialNumber = UserDataPreparer.getSerialNumber(userDeDir);
+        assertEquals(TEST_USER_SERIAL, serialNumber);
+        serialNumber = UserDataPreparer.getSerialNumber(systemDeDir);
+        assertEquals(TEST_USER_SERIAL, serialNumber);
+    }
+
+    @Test
+    public void testPrepareUserData_Ce() throws Exception {
+        File userCeDir = mUserDataPreparer.getDataUserCeDirectory(null, TEST_USER_ID);
+        userCeDir.mkdirs();
+        File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
+        systemCeDir.mkdirs();
+        mUserDataPreparer
+                .prepareUserData(TEST_USER_ID, TEST_USER_SERIAL, StorageManager.FLAG_STORAGE_CE);
+        verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
+                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
+        verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
+                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
+        int serialNumber = UserDataPreparer.getSerialNumber(userCeDir);
+        assertEquals(TEST_USER_SERIAL, serialNumber);
+        serialNumber = UserDataPreparer.getSerialNumber(systemCeDir);
+        assertEquals(TEST_USER_SERIAL, serialNumber);
+    }
+
+    @Test
+    public void testDestroyUserData_De_DoesNotDestroyCe() throws Exception {
+        // Add file in CE storage
+        File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
+        systemCeDir.mkdirs();
+        File ceFile = new File(systemCeDir, "file");
+        writeFile(ceFile, "-----" );
+        // Destroy DE storage, then verify that CE storage wasn't destroyed too.
+        mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_DE);
+        assertEquals(Collections.singletonList(ceFile), Arrays.asList(FileUtils.listFilesOrEmpty(
+                systemCeDir)));
+    }
+
+    @Test
+    public void testDestroyUserData_De() throws Exception {
+        File systemDir = mUserDataPreparer.getUserSystemDirectory(TEST_USER_ID);
+        systemDir.mkdirs();
+        writeFile(new File(systemDir, "file"), "-----" );
+        File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID);
+        systemDeDir.mkdirs();
+        writeFile(new File(systemDeDir, "file"), "-----" );
+
+        mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_DE);
+
+        verify(mInstaller).destroyUserData(isNull(String.class), eq(TEST_USER_ID),
+                        eq(StorageManager.FLAG_STORAGE_DE));
+        verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
+                        eq(StorageManager.FLAG_STORAGE_DE));
+
+        // systemDir (normal path: /data/system/users/$userId) should have been deleted.
+        assertFalse(systemDir.exists());
+        // systemDeDir (normal path: /data/system_de/$userId) should still exist but be empty, since
+        // UserDataPreparer itself is responsible for deleting the contents of this directory, but
+        // it delegates to StorageManager.destroyUserStorage() for deleting the directory itself.
+        // We've mocked out StorageManager, so StorageManager.destroyUserStorage() will be a no-op.
+        assertTrue(systemDeDir.exists());
+        assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
+                systemDeDir)));
+    }
+
+    @Test
+    public void testDestroyUserData_Ce() throws Exception {
+        File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
+        systemCeDir.mkdirs();
+        writeFile(new File(systemCeDir, "file"), "-----" );
+
+        mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_CE);
+
+        verify(mInstaller).destroyUserData(isNull(String.class), eq(TEST_USER_ID),
+                eq(StorageManager.FLAG_STORAGE_CE));
+        verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
+                eq(StorageManager.FLAG_STORAGE_CE));
+
+        // systemCeDir (normal path: /data/system_ce/$userId) should still exist but be empty, since
+        // UserDataPreparer itself is responsible for deleting the contents of this directory, but
+        // it delegates to StorageManager.destroyUserStorage() for deleting the directory itself.
+        // We've mocked out StorageManager, so StorageManager.destroyUserStorage() will be a no-op.
+        assertTrue(systemCeDir.exists());
+        assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
+                systemCeDir)));
+    }
+
+    @Test
+    public void testReconcileUsers() throws Exception {
+        UserInfo u1 = new UserInfo(1, "u1", 0);
+        UserInfo u2 = new UserInfo(2, "u2", 0);
+        File testDir = mUserDataPreparer.testDir;
+        File dir1 = new File(testDir, "1");
+        dir1.mkdirs();
+        File dir2 = new File(testDir, "2");
+        dir2.mkdirs();
+        File dir3 = new File(testDir, "3");
+        dir3.mkdirs();
+
+        mUserDataPreparer
+                .reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL, Arrays.asList(u1, u2),
+                        Arrays.asList(dir1, dir2, dir3));
+        // Verify that user 3 data is removed
+        verify(mInstaller).destroyUserData(isNull(String.class), eq(3),
+                eq(StorageManager.FLAG_STORAGE_DE|StorageManager.FLAG_STORAGE_CE));
+    }
+
+    private static void writeFile(File file, String content) throws IOException {
+        try (FileOutputStream os = new FileOutputStream(file)) {
+            os.write(content.getBytes(Charset.defaultCharset()));
+        }
+    }
+
+    private static class TestUserDataPreparer extends UserDataPreparer {
+        File testDir;
+
+        TestUserDataPreparer(Installer installer, Object installLock, Context context,
+                File testDir) {
+            super(installer, installLock, context);
+            this.testDir = testDir;
+        }
+
+        @Override
+        protected File getDataMiscCeDirectory(int userId) {
+            return new File(testDir, "misc_ce_" + userId);
+        }
+
+        @Override
+        protected File getDataSystemCeDirectory(int userId) {
+            return new File(testDir, "system_ce_" + userId);
+        }
+
+        @Override
+        protected File getDataMiscDeDirectory(int userId) {
+            return new File(testDir, "misc_de_" + userId);
+        }
+
+        @Override
+        protected File getUserSystemDirectory(int userId) {
+            return new File(testDir, "user_system_" + userId);
+        }
+
+        @Override
+        protected File getDataUserCeDirectory(String volumeUuid, int userId) {
+            return new File(testDir, "user_ce_" + userId);
+        }
+
+        @Override
+        protected File getDataSystemDeDirectory(int userId) {
+            return new File(testDir, "system_de_" + userId);
+        }
+
+        @Override
+        protected File getDataUserDeDirectory(String volumeUuid, int userId) {
+            return new File(testDir, "user_de_" + userId);
+        }
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/WatchedIntentHandlingTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/WatchedIntentHandlingTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParserLegacyCoreTest.java
new file mode 100644
index 0000000..ebf309f
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParserLegacyCoreTest.java
@@ -0,0 +1,605 @@
+/*
+ * 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.pm.parsing;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+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.platform.test.annotations.Presubmit;
+import android.util.Pair;
+import android.util.SparseIntArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.pm.test.service.server.R;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.PackageManagerException;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedActivityUtils;
+import com.android.server.pm.pkg.component.ParsedComponent;
+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.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * {@link ParsedPackage} was moved to the server, so this test moved along with it.
+ *
+ * This should be eventually refactored to a comprehensive parsing test, combined with its
+ * server variant in the parent package.
+ *
+ * TODO(b/135203078): Remove this test and replicate the cases in the actual com.android.server
+ *  variant.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PackageParserLegacyCoreTest {
+    private static final String RELEASED = null;
+    private static final String OLDER_PRE_RELEASE = "Q";
+    private static final String PRE_RELEASE = "R";
+    private static final String NEWER_PRE_RELEASE = "Z";
+
+    // Codenames with a fingerprint attached to them. These may only be present in the apps
+    // declared min SDK and not as platform codenames.
+    private static final String OLDER_PRE_RELEASE_WITH_FINGERPRINT = "Q.fingerprint";
+    private static final String PRE_RELEASE_WITH_FINGERPRINT = "R.fingerprint";
+    private static final String NEWER_PRE_RELEASE_WITH_FINGERPRINT = "Z.fingerprint";
+
+    private static final String[] CODENAMES_RELEASED = { /* empty */};
+    private static final String[] CODENAMES_PRE_RELEASE = {PRE_RELEASE};
+
+    private static final int OLDER_VERSION = 10;
+    private static final int PLATFORM_VERSION = 20;
+    private static final int NEWER_VERSION = 30;
+
+    @Rule public final Expect expect = Expect.create();
+
+    private void verifyComputeMinSdkVersion(int minSdkVersion, String minSdkCodename,
+            boolean isPlatformReleased, int expectedMinSdk) {
+        final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat();
+        final ParseResult<Integer> result = FrameworkParsingPackageUtils.computeMinSdkVersion(
+                minSdkVersion,
+                minSdkCodename,
+                PLATFORM_VERSION,
+                isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE,
+                input);
+
+        if (expectedMinSdk == -1) {
+            assertTrue(result.isError());
+        } else {
+            assertTrue(result.isSuccess());
+            assertEquals(expectedMinSdk, (int) result.getResult());
+        }
+    }
+
+    @Test
+    public void testComputeMinSdkVersion_preReleasePlatform() {
+        // Do allow older release minSdkVersion on pre-release platform.
+        // APP: Released API 10
+        // DEV: Pre-release API 20
+        verifyComputeMinSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION);
+
+        // Do allow same release minSdkVersion on pre-release platform.
+        // APP: Released API 20
+        // DEV: Pre-release API 20
+        verifyComputeMinSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION);
+
+        // Don't allow newer release minSdkVersion on pre-release platform.
+        // APP: Released API 30
+        // DEV: Pre-release API 20
+        verifyComputeMinSdkVersion(NEWER_VERSION, RELEASED, false, -1);
+
+        // Don't allow older pre-release minSdkVersion on pre-release platform.
+        // APP: Pre-release API 10
+        // DEV: Pre-release API 20
+        verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1);
+        verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
+
+        // Do allow same pre-release minSdkVersion on pre-release platform,
+        // but overwrite the specified version with CUR_DEVELOPMENT.
+        // APP: Pre-release API 20
+        // DEV: Pre-release API 20
+        verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false,
+                Build.VERSION_CODES.CUR_DEVELOPMENT);
+        verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, false,
+                Build.VERSION_CODES.CUR_DEVELOPMENT);
+
+
+        // Don't allow newer pre-release minSdkVersion on pre-release platform.
+        // APP: Pre-release API 30
+        // DEV: Pre-release API 20
+        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1);
+        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
+    }
+
+    @Test
+    public void testComputeMinSdkVersion_releasedPlatform() {
+        // Do allow older release minSdkVersion on released platform.
+        // APP: Released API 10
+        // DEV: Released API 20
+        verifyComputeMinSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION);
+
+        // Do allow same release minSdkVersion on released platform.
+        // APP: Released API 20
+        // DEV: Released API 20
+        verifyComputeMinSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION);
+
+        // Don't allow newer release minSdkVersion on released platform.
+        // APP: Released API 30
+        // DEV: Released API 20
+        verifyComputeMinSdkVersion(NEWER_VERSION, RELEASED, true, -1);
+
+        // Don't allow older pre-release minSdkVersion on released platform.
+        // APP: Pre-release API 10
+        // DEV: Released API 20
+        verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1);
+        verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+
+        // Don't allow same pre-release minSdkVersion on released platform.
+        // APP: Pre-release API 20
+        // DEV: Released API 20
+        verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1);
+        verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+
+
+        // Don't allow newer pre-release minSdkVersion on released platform.
+        // APP: Pre-release API 30
+        // DEV: Released API 20
+        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1);
+        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+    }
+
+    private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename,
+            boolean isPlatformReleased, boolean allowUnknownCodenames, int expectedTargetSdk) {
+        final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat();
+        final ParseResult<Integer> result = FrameworkParsingPackageUtils.computeTargetSdkVersion(
+                targetSdkVersion,
+                targetSdkCodename,
+                isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE,
+                input,
+                allowUnknownCodenames);
+
+        if (expectedTargetSdk == -1) {
+            assertTrue(result.isError());
+        } else {
+            assertTrue(result.isSuccess());
+            assertEquals(expectedTargetSdk, (int) result.getResult());
+        }
+    }
+
+    @Test
+    public void testComputeTargetSdkVersion_preReleasePlatform() {
+        // Do allow older release targetSdkVersion on pre-release platform.
+        // APP: Released API 10
+        // DEV: Pre-release API 20
+        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, false, OLDER_VERSION);
+
+        // Do allow same release targetSdkVersion on pre-release platform.
+        // APP: Released API 20
+        // DEV: Pre-release API 20
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, false, PLATFORM_VERSION);
+
+        // Do allow newer release targetSdkVersion on pre-release platform.
+        // APP: Released API 30
+        // DEV: Pre-release API 20
+        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, false, NEWER_VERSION);
+
+        // Don't allow older pre-release targetSdkVersion on pre-release platform.
+        // APP: Pre-release API 10
+        // DEV: Pre-release API 20
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, false, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false,
+                false, -1
+        );
+
+        // Don't allow older pre-release targetSdkVersion on pre-release platform when
+        // allowUnknownCodenames is true.
+        // APP: Pre-release API 10
+        // DEV: Pre-release API 20
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false,
+                true, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false,
+                true, -1);
+
+        // Do allow same pre-release targetSdkVersion on pre-release platform,
+        // but overwrite the specified version with CUR_DEVELOPMENT.
+        // APP: Pre-release API 20
+        // DEV: Pre-release API 20
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false,
+                false, Build.VERSION_CODES.CUR_DEVELOPMENT);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, false,
+                false, Build.VERSION_CODES.CUR_DEVELOPMENT);
+
+        // Don't allow newer pre-release targetSdkVersion on pre-release platform.
+        // APP: Pre-release API 30
+        // DEV: Pre-release API 20
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, false, -1);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
+                false, -1
+        );
+
+        // Do allow newer pre-release targetSdkVersion on pre-release platform when
+        // allowUnknownCodenames is true.
+        // APP: Pre-release API 30
+        // DEV: Pre-release API 20
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false,
+                true, Build.VERSION_CODES.CUR_DEVELOPMENT);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
+                true, Build.VERSION_CODES.CUR_DEVELOPMENT);
+
+    }
+
+    @Test
+    public void testComputeTargetSdkVersion_releasedPlatform() {
+        // Do allow older release targetSdkVersion on released platform.
+        // APP: Released API 10
+        // DEV: Released API 20
+        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, false, OLDER_VERSION);
+
+        // Do allow same release targetSdkVersion on released platform.
+        // APP: Released API 20
+        // DEV: Released API 20
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, false, PLATFORM_VERSION);
+
+        // Do allow newer release targetSdkVersion on released platform.
+        // APP: Released API 30
+        // DEV: Released API 20
+        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, false, NEWER_VERSION);
+
+        // Don't allow older pre-release targetSdkVersion on released platform.
+        // APP: Pre-release API 10
+        // DEV: Released API 20
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, false, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true,
+                false, -1
+        );
+
+        // Don't allow same pre-release targetSdkVersion on released platform.
+        // APP: Pre-release API 20
+        // DEV: Released API 20
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, false, -1);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, false,
+                -1
+        );
+
+        // Don't allow same pre-release targetSdkVersion on released platform when
+        // allowUnknownCodenames is true.
+        // APP: Pre-release API 20
+        // DEV: Released API 20
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, true,
+                -1);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, true,
+                -1);
+
+        // Don't allow newer pre-release targetSdkVersion on released platform.
+        // APP: Pre-release API 30
+        // DEV: Released API 20
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, false, -1);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true,
+                false, -1
+        );
+        // Do allow newer pre-release targetSdkVersion on released platform when
+        // allowUnknownCodenames is true.
+        // APP: Pre-release API 30
+        // DEV: Released API 20
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, true,
+                Build.VERSION_CODES.CUR_DEVELOPMENT);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true,
+                true, Build.VERSION_CODES.CUR_DEVELOPMENT);
+    }
+
+    /**
+     * Unit test for PackageParser.getActivityConfigChanges().
+     * If the bit is 1 in the original configChanges, it is still 1 in the final configChanges.
+     * If the bit is 0 in the original configChanges and the bit is not set to 1 in
+     * recreateOnConfigChanges, the bit is changed to 1 in the final configChanges by default.
+     */
+    @Test
+    public void testGetActivityConfigChanges() {
+        // Not set in either configChanges or recreateOnConfigChanges.
+        int configChanges = 0x0000; // 00000000.
+        int recreateOnConfigChanges = 0x0000; // 00000000.
+        int finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges,
+                recreateOnConfigChanges);
+        assertEquals(0x0003, finalConfigChanges); // Should be 00000011.
+
+        // Not set in configChanges, but set in recreateOnConfigChanges.
+        configChanges = 0x0000; // 00000000.
+        recreateOnConfigChanges = 0x0003; // 00000011.
+        finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges,
+                recreateOnConfigChanges);
+        assertEquals(0x0000, finalConfigChanges); // Should be 00000000.
+
+        // Set in configChanges.
+        configChanges = 0x0003; // 00000011.
+        recreateOnConfigChanges = 0X0000; // 00000000.
+        finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges,
+                recreateOnConfigChanges);
+        assertEquals(0x0003, finalConfigChanges); // Should be 00000011.
+
+        recreateOnConfigChanges = 0x0003; // 00000011.
+        finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges,
+                recreateOnConfigChanges);
+        assertEquals(0x0003, finalConfigChanges); // Should still be 00000011.
+
+        // Other bit set in configChanges.
+        configChanges = 0x0080; // 10000000, orientation.
+        recreateOnConfigChanges = 0x0000; // 00000000.
+        finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges,
+                recreateOnConfigChanges);
+        assertEquals(0x0083, finalConfigChanges); // Should be 10000011.
+    }
+
+    /**
+     * Copies a specified {@code resourceId} to a file. Returns a non-null file if the copy
+     * succeeded, or {@code null} otherwise.
+     */
+    File copyRawResourceToFile(String baseName, int resourceId) {
+        // Copy the resource to a file.
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        InputStream is = context.getResources().openRawResource(resourceId);
+        File outFile = null;
+        try {
+            outFile = new File(context.getFilesDir(), baseName);
+            assertTrue(FileUtils.copyToFile(is, outFile));
+            return outFile;
+        } catch (Exception e) {
+            if (outFile != null) {
+                outFile.delete();
+            }
+
+            return null;
+        }
+    }
+
+    /**
+     * Attempts to parse a package.
+     *
+     * APKs are put into coretests/apks/packageparser_*.
+     *
+     * @param apkFileName   temporary file name to store apk extracted from resources
+     * @param apkResourceId identifier of the apk as a resource
+     */
+    ParsedPackage parsePackage(String apkFileName, int apkResourceId,
+            Function<ParsedPackage, ParsedPackage> converter) throws Exception {
+        // Copy the resource to a file.
+        File outFile = null;
+        try {
+            outFile = copyRawResourceToFile(apkFileName, apkResourceId);
+            return converter.apply(new TestPackageParser2()
+                    .parsePackage(outFile, 0 /* flags */, false));
+        } finally {
+            if (outFile != null) {
+                outFile.delete();
+            }
+        }
+    }
+
+    /**
+     * Asserts basic properties about a component.
+     */
+    private void assertComponent(String className, int numIntents, ParsedComponent component) {
+        assertEquals(className, component.getName());
+        assertEquals(numIntents, component.getIntents().size());
+    }
+
+    /**
+     * Asserts four regularly-named components of each type: one Activity, one Service, one
+     * Provider, and one Receiver.
+     *
+     * @param template templated string with %s subbed with Activity, Service, Provider, Receiver
+     */
+    private void assertOneComponentOfEachType(String template, AndroidPackage p) {
+        assertEquals(1, p.getActivities().size());
+        assertComponent(String.format(template, "Activity"),
+                0 /* intents */, p.getActivities().get(0));
+        assertEquals(1, p.getServices().size());
+        assertComponent(String.format(template, "Service"),
+                0 /* intents */, p.getServices().get(0));
+        assertEquals(1, p.getProviders().size());
+        assertComponent(String.format(template, "Provider"),
+                0 /* intents */, p.getProviders().get(0));
+        assertEquals(1, p.getReceivers().size());
+        assertComponent(String.format(template, "Receiver"),
+                0 /* intents */, p.getReceivers().get(0));
+    }
+
+    private void assertPermission(String name, int protectionLevel, ParsedPermission permission) {
+        assertEquals(name, permission.getName());
+        assertEquals(protectionLevel, ParsedPermissionUtils.getProtection(permission));
+    }
+
+    private void assertMetadata(Bundle b, String... keysAndValues) {
+        assertTrue("Odd number of elements in keysAndValues", (keysAndValues.length % 2) == 0);
+
+        assertNotNull(b);
+        assertEquals(keysAndValues.length / 2, b.size());
+
+        for (int i = 0; i < keysAndValues.length; i += 2) {
+            final String key = keysAndValues[i];
+            final String value = keysAndValues[i + 1];
+
+            assertEquals(value, b.getString(key));
+        }
+    }
+
+    // TODO Add a "_cached" test for testMultiPackageComponents() too, after fixing b/64295061.
+    // Package.writeToParcel can't handle circular package references.
+
+    @Test
+    public void testPackageWithComponents_no_cache() throws Exception {
+        checkPackageWithComponents(p -> p);
+    }
+
+    @Test
+    public void testPackageWithComponents_cached() throws Exception {
+        checkPackageWithComponents(p ->
+                PackageCacher.fromCacheEntryStatic(PackageCacher.toCacheEntryStatic(p)));
+    }
+
+    private void checkPackageWithComponents(
+            Function<ParsedPackage, ParsedPackage> converter) throws Exception {
+        ParsedPackage p = parsePackage(
+                "install_complete_package_info.apk", R.raw.install_complete_package_info,
+                converter);
+        String packageName = "com.android.frameworks.coretests.install_complete_package_info";
+
+        assertEquals(packageName, p.getPackageName());
+        assertEquals(1, p.getPermissions().size());
+        assertPermission(
+                "com.android.frameworks.coretests.install_complete_package_info.test_permission",
+                PermissionInfo.PROTECTION_NORMAL, p.getPermissions().get(0));
+
+        findAndRemoveAppDetailsActivity(p);
+
+        assertOneComponentOfEachType("com.android.frameworks.coretests.Test%s", p);
+
+        assertMetadata(p.getMetaData(),
+                "key1", "value1",
+                "key2", "this_is_app");
+        assertMetadata(p.getActivities().get(0).getMetaData(),
+                "key1", "value1",
+                "key2", "this_is_activity");
+        assertMetadata(p.getServices().get(0).getMetaData(),
+                "key1", "value1",
+                "key2", "this_is_service");
+        assertMetadata(p.getReceivers().get(0).getMetaData(),
+                "key1", "value1",
+                "key2", "this_is_receiver");
+        assertMetadata(p.getProviders().get(0).getMetaData(),
+                "key1", "value1",
+                "key2", "this_is_provider");
+
+    }
+
+    private void findAndRemoveAppDetailsActivity(ParsedPackage p) {
+        // Hidden "app details" activity is added to every package.
+        boolean foundAppDetailsActivity = false;
+        for (int i = 0; i < ArrayUtils.size(p.getActivities()); i++) {
+            if (p.getActivities().get(i).getClassName().equals(
+                    PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME)) {
+                foundAppDetailsActivity = true;
+                p.getActivities().remove(i);
+                break;
+            }
+        }
+        assertTrue("Did not find app details activity", foundAppDetailsActivity);
+    }
+
+    @Test
+    public void testPackageWithIntentFilters_no_cache() throws Exception {
+        checkPackageWithIntentFilters(p -> p);
+    }
+
+    @Test
+    public void testPackageWithIntentFilters_cached() throws Exception {
+        checkPackageWithIntentFilters(p ->
+                PackageCacher.fromCacheEntryStatic(PackageCacher.toCacheEntryStatic(p)));
+    }
+
+    private void checkPackageWithIntentFilters(
+            Function<ParsedPackage, ParsedPackage> converter) throws Exception {
+        ParsedPackage p = parsePackage(
+                "install_intent_filters.apk", R.raw.install_intent_filters,
+                converter);
+        String packageName = "com.android.frameworks.servicestests.install_intent_filters";
+
+        assertEquals(packageName, p.getPackageName());
+
+        findAndRemoveAppDetailsActivity(p);
+
+        assertEquals("Expected exactly one activity", 1, p.getActivities().size());
+        List<ParsedIntentInfo> intentInfos = p.getActivities().get(0).getIntents();
+        assertEquals("Expected exactly one intent filter", 1, intentInfos.size());
+        IntentFilter intentFilter = intentInfos.get(0).getIntentFilter();
+        assertEquals("Expected exactly one mime group in intent filter", 1,
+                intentFilter.countMimeGroups());
+        assertTrue("Did not find expected mime group 'mime_group_1'",
+                intentFilter.hasMimeGroup("mime_group_1"));
+    }
+
+    @Test
+    public void testUsesSdk() throws Exception {
+        ParsedPackage pkg;
+        SparseIntArray minExtVers;
+        pkg = parsePackage("install_uses_sdk.apk_r0", R.raw.install_uses_sdk_r0, x -> x);
+        minExtVers = pkg.getMinExtensionVersions();
+        assertEquals(1, minExtVers.size());
+        assertEquals(0, minExtVers.get(30, -1));
+
+        pkg = parsePackage("install_uses_sdk.apk_r0_s0", R.raw.install_uses_sdk_r0_s0, x -> x);
+        minExtVers = pkg.getMinExtensionVersions();
+        assertEquals(2, minExtVers.size());
+        assertEquals(0, minExtVers.get(30, -1));
+        assertEquals(0, minExtVers.get(31, -1));
+
+        Map<Pair<String, Integer>, Integer> appToError = new HashMap<>();
+        appToError.put(Pair.create("install_uses_sdk.apk_r1000", R.raw.install_uses_sdk_r1000),
+                       PackageManager.INSTALL_FAILED_OLDER_SDK);
+        appToError.put(
+                Pair.create("install_uses_sdk.apk_r0_s1000", R.raw.install_uses_sdk_r0_s1000),
+                PackageManager.INSTALL_FAILED_OLDER_SDK);
+
+        appToError.put(Pair.create("install_uses_sdk.apk_q0", R.raw.install_uses_sdk_q0),
+                       PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED);
+        appToError.put(Pair.create("install_uses_sdk.apk_q0_r0", R.raw.install_uses_sdk_q0_r0),
+                       PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED);
+        appToError.put(Pair.create("install_uses_sdk.apk_r_none", R.raw.install_uses_sdk_r_none),
+                       PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED);
+        appToError.put(Pair.create("install_uses_sdk.apk_0", R.raw.install_uses_sdk_0),
+                       PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED);
+
+        for (Map.Entry<Pair<String, Integer>, Integer> entry : appToError.entrySet()) {
+            String filename = entry.getKey().first;
+            int resId = entry.getKey().second;
+            int result = entry.getValue();
+            try {
+                parsePackage(filename, resId, x -> x);
+                expect.withMessage("Expected parsing error %s from %s", result, filename).fail();
+            } catch (PackageManagerException expected) {
+                expect.that(expected.error).isEqualTo(result);
+            }
+        }
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParsingDeferErrorTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParsingDeferErrorTest.kt
new file mode 100644
index 0000000..3b926c2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParsingDeferErrorTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.pm.parsing
+
+import android.annotation.RawRes
+import android.content.Context
+import com.android.server.pm.pkg.parsing.ParsingPackage
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils
+import android.content.pm.parsing.result.ParseResult
+import android.platform.test.annotations.Presubmit
+import androidx.test.InstrumentationRegistry
+import com.android.server.pm.parsing.pkg.ParsedPackage
+import com.android.server.pm.test.service.server.R
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+/**
+ * There are 2 known errors when parsing a manifest that were promoted to true failures in R:
+ * 1. Missing an <application> or <instrumentation> tag
+ * 2. An empty string action/category in an intent-filter
+ *
+ * This verifies these failures when the APK targets R.
+ */
+@Presubmit
+class PackageParsingDeferErrorTest {
+
+    companion object {
+        private const val TEST_ACTIVITY =
+                "com.android.servicestests.pm.parsing.test.TestActivity"
+        private const val TEST_ACTION =
+                "com.android.servicestests.pm.parsing.test.TEST_ACTION"
+        private const val TEST_CATEGORY =
+                "com.android.servicestests.pm.parsing.test.TEST_CATEGORY"
+        private const val TEST_PERMISSION =
+                "com.android.servicestests.pm.parsing.missingapp.TEST_PERMISSION"
+    }
+
+    private val context: Context = InstrumentationRegistry.getContext()
+
+    @get:Rule
+    val tempFolder = TemporaryFolder(context.filesDir)
+
+    @Test
+    fun emptyIntentFilterActionSdkQ() {
+        val result = parseFile(R.raw.PackageParsingTestAppEmptyActionSdkQ)
+        assertWithMessage(result.errorMessage).that(result.isError).isFalse()
+        val activities = result.result.activities
+        // 2 because of AppDetailsActivity
+        assertThat(activities).hasSize(2)
+        val first = activities.first()
+        assertThat(first.name).isEqualTo(TEST_ACTIVITY)
+        val intents = first.intents
+        assertThat(intents).hasSize(1)
+        val intentFilter = intents.first().intentFilter
+        assertThat(intentFilter.hasCategory(TEST_CATEGORY)).isTrue()
+        assertThat(intentFilter.hasAction(TEST_ACTION)).isTrue()
+    }
+
+    @Test
+    fun emptyIntentFilterActionSdkR() {
+        val result = parseFile(R.raw.PackageParsingTestAppEmptyActionSdkR)
+        assertThat(result.isError).isTrue()
+    }
+
+    @Test
+    fun emptyIntentFilterCategorySdkQ() {
+        val result = parseFile(R.raw.PackageParsingTestAppEmptyCategorySdkQ)
+        assertWithMessage(result.errorMessage).that(result.isError).isFalse()
+        val activities = result.result.activities
+        // 2 because of AppDetailsActivity
+        assertThat(activities).hasSize(2)
+        val first = activities.first()
+        assertThat(first.name).isEqualTo(TEST_ACTIVITY)
+        val intents = first.intents
+        assertThat(intents).hasSize(1)
+        val intentFilter = intents.first().intentFilter
+        assertThat(intentFilter.hasAction(TEST_ACTION)).isTrue()
+    }
+
+    @Test
+    fun emptyIntentFilterCategorySdkR() {
+        val result = parseFile(R.raw.PackageParsingTestAppEmptyCategorySdkR)
+        assertThat(result.isError).isTrue()
+    }
+
+    @Test
+    fun missingAppTagSdkQ() {
+        val result = parseFile(R.raw.PackageParsingTestAppMissingAppSdkQ)
+        assertWithMessage(result.errorMessage).that(result.isError).isFalse()
+        val permissions = result.result.permissions
+        assertThat(permissions).hasSize(1)
+        assertThat(permissions.first().name).isEqualTo(TEST_PERMISSION)
+    }
+
+    @Test
+    fun missingAppTagSdkR() {
+        val result = parseFile(R.raw.PackageParsingTestAppMissingAppSdkR)
+        assertThat(result.isError).isTrue()
+    }
+
+    private fun parseFile(@RawRes id: Int): ParseResult<ParsedPackage> {
+        val file = tempFolder.newFile()
+        context.resources.openRawResource(id).use { input ->
+            file.outputStream().use { output ->
+                input.copyTo(output)
+            }
+        }
+        return ParsingPackageUtils.parseDefaultOneTime(file, 0 /*flags*/, emptyList(),
+                false /*collectCertificates*/)
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/SystemPartitionParseTest.kt
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/SystemPartitionParseTest.kt
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidHidlUpdaterTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidHidlUpdaterTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestBaseUpdaterTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestBaseUpdaterTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ApexSharedLibraryUpdaterTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ApexSharedLibraryUpdaterTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/OptionalClassRunner.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OptionalClassRunner.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/parsing/library/OptionalClassRunner.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OptionalClassRunner.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageBackwardCompatibilityTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageBackwardCompatibilityTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageSharedLibraryUpdaterTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageSharedLibraryUpdaterTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/OWNERS b/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/OWNERS
new file mode 100644
index 0000000..1853220
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/OWNERS
@@ -0,0 +1,4 @@
+per-file WatchableTester.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file WatchableTester.java = shombert@google.com
+per-file WatcherTest.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file WatcherTest.java = shombert@google.com
diff --git a/services/tests/servicestests/src/com/android/server/utils/WatchableTester.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatchableTester.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/utils/WatchableTester.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatchableTester.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatcherTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatcherTest.java
new file mode 100644
index 0000000..74d491d
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatcherTest.java
@@ -0,0 +1,1258 @@
+/**
+ * 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.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+/**
+ * Test class for various utility classes that support the Watchable or Snappable
+ * features.  This covers {@link Watcher}, {@link Watchable}, {@link WatchableImpl},
+ * {@link WatchedArrayMap}, {@link WatchedSparseArray}, and
+ * {@link WatchedSparseBooleanArray}.
+ *
+ * Build/Install/Run:
+ *  atest PackageManagerServiceTest:WatcherTest
+ */
+@SmallTest
+public class WatcherTest {
+
+    // A counter to generate unique IDs for Leaf elements.
+    private int mLeafId = 0;
+
+    // Useful indices used in the tests.
+    private static final int INDEX_A = 1;
+    private static final int INDEX_B = 2;
+    private static final int INDEX_C = 3;
+    private static final int INDEX_D = 4;
+
+    // A small Watchable leaf node
+    private class Leaf extends WatchableImpl implements Snappable {
+        private int mId;
+        private int mDatum;
+
+        Leaf() {
+            mDatum = 0;
+            mId = mLeafId++;
+        }
+
+        void set(int i) {
+            if (mDatum != i) {
+                mDatum = i;
+                dispatchChange(this);
+            }
+        }
+        int get() {
+            return mDatum;
+        }
+        void tick() {
+            set(mDatum + 1);
+        }
+        public Leaf snapshot() {
+            Leaf result = new Leaf();
+            result.mDatum = mDatum;
+            result.mId = mId;
+            result.seal();
+            return result;
+        }
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof Leaf) {
+                return mDatum == ((Leaf) o).mDatum && mId == ((Leaf) o).mId;
+            } else {
+                return false;
+            }
+        }
+        @Override
+        public String toString() {
+            return "Leaf(" + mDatum + "," + mId + ")";
+        }
+    }
+
+    // Execute the {@link Runnable} and if {@link UnsupportedOperationException} is
+    // thrown, do nothing.  If no exception is thrown, fail the test.
+    private void verifySealed(String msg, Runnable test) {
+        try {
+            test.run();
+            fail(msg + " should be sealed");
+        } catch (IllegalStateException e) {
+            // The exception was expected.
+        }
+    }
+
+    // Execute the {@link Runnable} and if {@link UnsupportedOperationException} is
+    // thrown, fail the test.  If no exception is thrown, do nothing.
+    private void verifyNotSealed(String msg, Runnable test) {
+        try {
+            test.run();
+        } catch (IllegalStateException e) {
+            fail(msg + " should be not sealed");
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+
+    @Test
+    public void testBasicBehavior() {
+        WatchableTester tester;
+
+        // Create a few leaves
+        Leaf leafA = new Leaf();
+
+        // Basic test.  Create a leaf and verify that changes to the leaf get notified to
+        // the tester.
+        tester = new WatchableTester(leafA, "Leaf");
+        tester.verify(0, "Initial leaf - no registration");
+        leafA.tick();
+        tester.verify(0, "Updates with no registration");
+        tester.register();
+        leafA.tick();
+        tester.verify(1, "Updates with registration");
+        leafA.tick();
+        leafA.tick();
+        tester.verify(3, "Updates with registration");
+        // Create a snapshot.  Verify that the snapshot matches the
+        Leaf leafASnapshot = leafA.snapshot();
+        assertEquals("Leaf snapshot", leafA.get(), leafASnapshot.get());
+        leafA.tick();
+        assertTrue(leafA.get() != leafASnapshot.get());
+        tester.verify(4, "Tick after snapshot");
+        verifySealed("Leaf", ()->leafASnapshot.tick());
+
+        // Add the same leaf to more than one tester.  Verify that a change to the leaf is seen by
+        // all registered listeners.
+        tester.clear();
+        WatchableTester buddy1 = new WatchableTester(leafA, "Leaf2");
+        WatchableTester buddy2 = new WatchableTester(leafA, "Leaf3");
+        buddy1.verify(0, "Initial leaf - no registration");
+        buddy2.verify(0, "Initial leaf - no registration");
+        leafA.tick();
+        tester.verify(1, "Updates with buddies");
+        buddy1.verify(0, "Updates - no registration");
+        buddy2.verify(0, "Updates - no registration");
+        buddy1.register();
+        buddy2.register();
+        buddy1.verify(0, "No updates - registered");
+        buddy2.verify(0, "No updates - registered");
+        leafA.tick();
+        buddy1.verify(1, "First update");
+        buddy2.verify(1, "First update");
+        buddy1.unregister();
+        leafA.tick();
+        buddy1.verify(1, "Second update - unregistered");
+        buddy2.verify(2, "Second update");
+    }
+
+    @Test
+    public void testWatchedArrayMap() {
+        final String name = "WatchedArrayMap";
+        WatchableTester tester;
+
+        // Create a few leaves
+        Leaf leafA = new Leaf();
+        Leaf leafB = new Leaf();
+        Leaf leafC = new Leaf();
+        Leaf leafD = new Leaf();
+
+        // Test WatchedArrayMap
+        WatchedArrayMap<Integer, Leaf> array = new WatchedArrayMap<>();
+        array.put(INDEX_A, leafA);
+        array.put(INDEX_B, leafB);
+        tester = new WatchableTester(array, name);
+        tester.verify(0, "Initial array - no registration");
+        leafA.tick();
+        tester.verify(0, "Updates with no registration");
+        tester.register();
+        tester.verify(0, "Updates with no registration");
+        leafA.tick();
+        tester.verify(1, "Updates with registration");
+        leafB.tick();
+        tester.verify(2, "Updates with registration");
+        array.remove(INDEX_B);
+        tester.verify(3, "Removed b");
+        leafB.tick();
+        tester.verify(3, "Updates with b not watched");
+        array.put(INDEX_B, leafB);
+        array.put(INDEX_C, leafB);
+        tester.verify(5, "Added b twice");
+        leafB.tick();
+        tester.verify(6, "Changed b - single notification");
+        array.remove(INDEX_C);
+        tester.verify(7, "Removed first b");
+        leafB.tick();
+        tester.verify(8, "Changed b - single notification");
+        array.remove(INDEX_B);
+        tester.verify(9, "Removed second b");
+        leafB.tick();
+        tester.verify(9, "Updated b - no change");
+        array.clear();
+        tester.verify(10, "Cleared array");
+        leafB.tick();
+        tester.verify(10, "Change to b not in array");
+
+        // Special methods
+        array.put(INDEX_C, leafC);
+        tester.verify(11, "Added c");
+        leafC.tick();
+        tester.verify(12, "Ticked c");
+        array.setValueAt(array.indexOfKey(INDEX_C), leafD);
+        tester.verify(13, "Replaced c with d");
+        leafC.tick();
+        leafD.tick();
+        tester.verify(14, "Ticked d and c (c not registered)");
+
+        // Snapshot
+        {
+            final WatchedArrayMap<Integer, Leaf> arraySnap = array.snapshot();
+            tester.verify(14, "Generate snapshot (no changes)");
+            // Verify that the snapshot is a proper copy of the source.
+            assertEquals(name + " snap same size",
+                         array.size(), arraySnap.size());
+            for (int i = 0; i < array.size(); i++) {
+                for (int j = 0; j < arraySnap.size(); j++) {
+                    assertTrue(name + " elements differ",
+                               array.valueAt(i) != arraySnap.valueAt(j));
+                }
+                assertTrue(name + " element copy",
+                           array.valueAt(i).equals(arraySnap.valueAt(i)));
+            }
+            leafD.tick();
+            tester.verify(15, "Tick after snapshot");
+            // Verify that the snapshot is sealed
+            verifySealed(name, ()->arraySnap.put(INDEX_A, leafA));
+            assertTrue(!array.isSealed());
+            assertTrue(arraySnap.isSealed());
+        }
+        // Recreate the snapshot since the test corrupted it.
+        {
+            final WatchedArrayMap<Integer, Leaf> arraySnap = array.snapshot();
+            // Verify that elements are also snapshots
+            final Leaf arraySnapElement = arraySnap.valueAt(0);
+            verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick());
+        }
+        // Verify copy-in/out
+        {
+            final String msg = name + " copy-in/out failed";
+            ArrayMap<Integer, Leaf> base = new ArrayMap<>();
+            array.copyTo(base);
+            WatchedArrayMap<Integer, Leaf> copy = new WatchedArrayMap<>();
+            copy.copyFrom(base);
+            if (!array.equals(copy)) {
+                fail(msg);
+            }
+        }
+    }
+
+    @Test
+    public void testWatchedArraySet() {
+        final String name = "WatchedArraySet";
+        WatchableTester tester;
+
+        // Create a few leaves
+        Leaf leafA = new Leaf();
+        Leaf leafB = new Leaf();
+        Leaf leafC = new Leaf();
+        Leaf leafD = new Leaf();
+
+        // Test WatchedArraySet
+        WatchedArraySet<Leaf> array = new WatchedArraySet<>();
+        array.add(leafA);
+        array.add(leafB);
+        tester = new WatchableTester(array, name);
+        tester.verify(0, "Initial array - no registration");
+        leafA.tick();
+        tester.verify(0, "Updates with no registration");
+        tester.register();
+        tester.verify(0, "Updates with no registration");
+        leafA.tick();
+        tester.verify(1, "Updates with registration");
+        leafB.tick();
+        tester.verify(2, "Updates with registration");
+        array.remove(leafB);
+        tester.verify(3, "Removed b");
+        leafB.tick();
+        tester.verify(3, "Updates with b not watched");
+        array.add(leafB);
+        array.add(leafB);
+        tester.verify(5, "Added b once");
+        leafB.tick();
+        tester.verify(6, "Changed b - single notification");
+        array.remove(leafB);
+        tester.verify(7, "Removed b");
+        leafB.tick();
+        tester.verify(7, "Changed b - not watched");
+        array.remove(leafB);
+        tester.verify(7, "Removed non-existent b");
+        array.clear();
+        tester.verify(8, "Cleared array");
+        leafA.tick();
+        tester.verify(8, "Change to a not in array");
+
+        // Special methods
+        array.add(leafA);
+        array.add(leafB);
+        array.add(leafC);
+        tester.verify(11, "Added a, b, c");
+        leafC.tick();
+        tester.verify(12, "Ticked c");
+        array.removeAt(array.indexOf(leafC));
+        tester.verify(13, "Removed c");
+        leafC.tick();
+        tester.verify(13, "Ticked c, not registered");
+        array.append(leafC);
+        tester.verify(14, "Append c");
+        leafC.tick();
+        leafD.tick();
+        tester.verify(15, "Ticked d and c");
+        assertEquals("Verify three elements", 3, array.size());
+
+        // Snapshot
+        {
+            final WatchedArraySet<Leaf> arraySnap = array.snapshot();
+            tester.verify(15, "Generate snapshot (no changes)");
+            // Verify that the snapshot is a proper copy of the source.
+            assertEquals(name + " snap same size",
+                         array.size(), arraySnap.size());
+            for (int i = 0; i < array.size(); i++) {
+                for (int j = 0; j < arraySnap.size(); j++) {
+                    assertTrue(name + " elements differ",
+                               array.valueAt(i) != arraySnap.valueAt(j));
+                }
+            }
+            leafC.tick();
+            tester.verify(16, "Tick after snapshot");
+            // Verify that the array snapshot is sealed
+            verifySealed(name, ()->arraySnap.add(leafB));
+            assertTrue(!array.isSealed());
+            assertTrue(arraySnap.isSealed());
+        }
+        // Recreate the snapshot since the test corrupted it.
+        {
+            final WatchedArraySet<Leaf> arraySnap = array.snapshot();
+            // Verify that elements are also snapshots
+            final Leaf arraySnapElement = arraySnap.valueAt(0);
+            verifySealed(name + " snap element", ()->arraySnapElement.tick());
+        }
+        // Verify copy-in/out
+        {
+            final String msg = name + " copy-in/out";
+            ArraySet<Leaf> base = new ArraySet<>();
+            array.copyTo(base);
+            WatchedArraySet<Leaf> copy = new WatchedArraySet<>();
+            copy.copyFrom(base);
+            if (!array.equals(copy)) {
+                fail(msg);
+            }
+        }
+    }
+
+    @Test
+    public void testWatchedArrayList() {
+        final String name = "WatchedArrayList";
+        WatchableTester tester;
+
+        // Create a few leaves
+        Leaf leafA = new Leaf();
+        Leaf leafB = new Leaf();
+        Leaf leafC = new Leaf();
+        Leaf leafD = new Leaf();
+
+        // Redefine the indices used in the tests to be zero-based
+        final int indexA = 0;
+        final int indexB = 1;
+        final int indexC = 2;
+        final int indexD = 3;
+
+        // Test WatchedArrayList
+        WatchedArrayList<Leaf> array = new WatchedArrayList<>();
+        // A spacer that takes up index 0 (and is not Watchable).
+        array.add(indexA, leafA);
+        array.add(indexB, leafB);
+        tester = new WatchableTester(array, name);
+        tester.verify(0, "Initial array - no registration");
+        leafA.tick();
+        tester.verify(0, "Updates with no registration");
+        tester.register();
+        tester.verify(0, "Updates with no registration");
+        leafA.tick();
+        tester.verify(1, "Updates with registration");
+        leafB.tick();
+        tester.verify(2, "Updates with registration");
+        array.remove(indexB);
+        tester.verify(3, "Removed b");
+        leafB.tick();
+        tester.verify(3, "Updates with b not watched");
+        array.add(indexB, leafB);
+        array.add(indexC, leafB);
+        tester.verify(5, "Added b twice");
+        leafB.tick();
+        tester.verify(6, "Changed b - single notification");
+        array.remove(indexC);
+        tester.verify(7, "Removed first b");
+        leafB.tick();
+        tester.verify(8, "Changed b - single notification");
+        array.remove(indexB);
+        tester.verify(9, "Removed second b");
+        leafB.tick();
+        tester.verify(9, "Updated leafB - no change");
+        array.clear();
+        tester.verify(10, "Cleared array");
+        leafB.tick();
+        tester.verify(10, "Change to b not in array");
+
+        // Special methods
+        array.add(indexA, leafA);
+        array.add(indexB, leafB);
+        array.add(indexC, leafC);
+        tester.verify(13, "Added c");
+        leafC.tick();
+        tester.verify(14, "Ticked c");
+        array.set(array.indexOf(leafC), leafD);
+        tester.verify(15, "Replaced c with d");
+        leafC.tick();
+        leafD.tick();
+        tester.verify(16, "Ticked d and c (c not registered)");
+        array.add(leafC);
+        tester.verify(17, "Append c");
+        leafC.tick();
+        leafD.tick();
+        tester.verify(19, "Ticked d and c");
+
+        // Snapshot
+        {
+            final WatchedArrayList<Leaf> arraySnap = array.snapshot();
+            tester.verify(19, "Generate snapshot (no changes)");
+            // Verify that the snapshot is a proper copy of the source.
+            assertEquals(name + " snap same size",
+                         array.size(), arraySnap.size());
+            for (int i = 0; i < array.size(); i++) {
+                for (int j = 0; j < arraySnap.size(); j++) {
+                    assertTrue(name + " elements differ",
+                               array.get(i) != arraySnap.get(j));
+                }
+                assertTrue(name + " element copy",
+                           array.get(i).equals(arraySnap.get(i)));
+            }
+            leafD.tick();
+            tester.verify(20, "Tick after snapshot");
+            // Verify that the array snapshot is sealed
+            verifySealed(name, ()->arraySnap.add(indexA, leafB));
+            assertTrue(!array.isSealed());
+            assertTrue(arraySnap.isSealed());
+        }
+        // Recreate the snapshot since the test corrupted it.
+        {
+            final WatchedArrayList<Leaf> arraySnap = array.snapshot();
+            // Verify that elements are also snapshots
+            final Leaf arraySnapElement = arraySnap.get(0);
+            verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick());
+        }
+        // Verify copy-in/out
+        {
+            final String msg = name + " copy-in/out";
+            ArrayList<Leaf> base = new ArrayList<>();
+            array.copyTo(base);
+            WatchedArrayList<Leaf> copy = new WatchedArrayList<>();
+            copy.copyFrom(base);
+            if (!array.equals(copy)) {
+                fail(msg);
+            }
+        }
+    }
+
+    @Test
+    public void testWatchedSparseArray() {
+        final String name = "WatchedSparseArray";
+        WatchableTester tester;
+
+        // Create a few leaves
+        Leaf leafA = new Leaf();
+        Leaf leafB = new Leaf();
+        Leaf leafC = new Leaf();
+        Leaf leafD = new Leaf();
+
+        // Test WatchedSparseArray
+        WatchedSparseArray<Leaf> array = new WatchedSparseArray<>();
+        array.put(INDEX_A, leafA);
+        array.put(INDEX_B, leafB);
+        tester = new WatchableTester(array, name);
+        tester.verify(0, "Initial array - no registration");
+        leafA.tick();
+        tester.verify(0, "Updates with no registration");
+        tester.register();
+        tester.verify(0, "Updates with no registration");
+        leafA.tick();
+        tester.verify(1, "Updates with registration");
+        leafB.tick();
+        tester.verify(2, "Updates with registration");
+        array.remove(INDEX_B);
+        tester.verify(3, "Removed b");
+        leafB.tick();
+        tester.verify(3, "Updates with b not watched");
+        array.put(INDEX_B, leafB);
+        array.put(INDEX_C, leafB);
+        tester.verify(5, "Added b twice");
+        leafB.tick();
+        tester.verify(6, "Changed b - single notification");
+        array.remove(INDEX_C);
+        tester.verify(7, "Removed first b");
+        leafB.tick();
+        tester.verify(8, "Changed b - single notification");
+        array.remove(INDEX_B);
+        tester.verify(9, "Removed second b");
+        leafB.tick();
+        tester.verify(9, "Updated leafB - no change");
+        array.clear();
+        tester.verify(10, "Cleared array");
+        leafB.tick();
+        tester.verify(10, "Change to b not in array");
+
+        // Special methods
+        array.put(INDEX_A, leafA);
+        array.put(INDEX_B, leafB);
+        array.put(INDEX_C, leafC);
+        tester.verify(13, "Added c");
+        leafC.tick();
+        tester.verify(14, "Ticked c");
+        array.setValueAt(array.indexOfKey(INDEX_C), leafD);
+        tester.verify(15, "Replaced c with d");
+        leafC.tick();
+        leafD.tick();
+        tester.verify(16, "Ticked d and c (c not registered)");
+        array.append(INDEX_D, leafC);
+        tester.verify(17, "Append c");
+        leafC.tick();
+        leafD.tick();
+        tester.verify(19, "Ticked d and c");
+        assertEquals("Verify four elements", 4, array.size());
+        // Figure out which elements are at which indices.
+        Leaf[] x = new Leaf[4];
+        for (int i = 0; i < 4; i++) {
+            x[i] = array.valueAt(i);
+        }
+        array.removeAtRange(0, 2);
+        tester.verify(20, "Removed two elements in one operation");
+        x[0].tick();
+        x[1].tick();
+        tester.verify(20, "Ticked two removed elements");
+        x[2].tick();
+        x[3].tick();
+        tester.verify(22, "Ticked two remaining elements");
+
+        // Snapshot
+        {
+            final WatchedSparseArray<Leaf> arraySnap = array.snapshot();
+            tester.verify(22, "Generate snapshot (no changes)");
+            // Verify that the snapshot is a proper copy of the source.
+            assertEquals(name + " snap same size",
+                         array.size(), arraySnap.size());
+            for (int i = 0; i < array.size(); i++) {
+                for (int j = 0; j < arraySnap.size(); j++) {
+                    assertTrue(name + " elements differ",
+                               array.valueAt(i) != arraySnap.valueAt(j));
+                }
+                assertTrue(name + " element copy",
+                           array.valueAt(i).equals(arraySnap.valueAt(i)));
+            }
+            leafD.tick();
+            tester.verify(23, "Tick after snapshot");
+            // Verify that the array snapshot is sealed
+            verifySealed(name, ()->arraySnap.put(INDEX_A, leafB));
+            assertTrue(!array.isSealed());
+            assertTrue(arraySnap.isSealed());
+        }
+        // Recreate the snapshot since the test corrupted it.
+        {
+            final WatchedSparseArray<Leaf> arraySnap = array.snapshot();
+            // Verify that elements are also snapshots
+            final Leaf arraySnapElement = arraySnap.valueAt(0);
+            verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick());
+        }
+        // Verify copy-in/out
+        {
+            final String msg = name + " copy-in/out";
+            SparseArray<Leaf> base = new SparseArray<>();
+            array.copyTo(base);
+            WatchedSparseArray<Leaf> copy = new WatchedSparseArray<>();
+            copy.copyFrom(base);
+            final int end = array.size();
+            assertTrue(msg + " size mismatch " + end + " " + copy.size(), end == copy.size());
+            for (int i = 0; i < end; i++) {
+                final int key = array.keyAt(i);
+                assertTrue(msg, array.get(i) == copy.get(i));
+            }
+        }
+    }
+
+    @Test
+    public void testWatchedLongSparseArray() {
+        final String name = "WatchedLongSparseArray";
+        WatchableTester tester;
+
+        // Create a few leaves
+        Leaf leafA = new Leaf();
+        Leaf leafB = new Leaf();
+        Leaf leafC = new Leaf();
+        Leaf leafD = new Leaf();
+
+        // Test WatchedLongSparseArray
+        WatchedLongSparseArray<Leaf> array = new WatchedLongSparseArray<>();
+        array.put(INDEX_A, leafA);
+        array.put(INDEX_B, leafB);
+        tester = new WatchableTester(array, name);
+        tester.verify(0, "Initial array - no registration");
+        leafA.tick();
+        tester.verify(0, "Updates with no registration");
+        tester.register();
+        tester.verify(0, "Updates with no registration");
+        leafA.tick();
+        tester.verify(1, "Updates with registration");
+        leafB.tick();
+        tester.verify(2, "Updates with registration");
+        array.remove(INDEX_B);
+        tester.verify(3, "Removed b");
+        leafB.tick();
+        tester.verify(3, "Updates with b not watched");
+        array.put(INDEX_B, leafB);
+        array.put(INDEX_C, leafB);
+        tester.verify(5, "Added b twice");
+        leafB.tick();
+        tester.verify(6, "Changed b - single notification");
+        array.remove(INDEX_C);
+        tester.verify(7, "Removed first b");
+        leafB.tick();
+        tester.verify(8, "Changed b - single notification");
+        array.remove(INDEX_B);
+        tester.verify(9, "Removed second b");
+        leafB.tick();
+        tester.verify(9, "Updated leafB - no change");
+        array.clear();
+        tester.verify(10, "Cleared array");
+        leafB.tick();
+        tester.verify(10, "Change to b not in array");
+
+        // Special methods
+        array.put(INDEX_A, leafA);
+        array.put(INDEX_B, leafB);
+        array.put(INDEX_C, leafC);
+        tester.verify(13, "Added c");
+        leafC.tick();
+        tester.verify(14, "Ticked c");
+        array.setValueAt(array.indexOfKey(INDEX_C), leafD);
+        tester.verify(15, "Replaced c with d");
+        leafC.tick();
+        tester.verify(15, "Ticked c (c not registered)");
+        leafD.tick();
+        tester.verify(16, "Ticked d and c (c not registered)");
+        array.append(INDEX_D, leafC);
+        tester.verify(17, "Append c");
+        leafC.tick();
+        leafD.tick();
+        tester.verify(19, "Ticked d and c");
+        assertEquals("Verify four elements", 4, array.size());
+        // Figure out which elements are at which indices.
+        Leaf[] x = new Leaf[4];
+        for (int i = 0; i < 4; i++) {
+            x[i] = array.valueAt(i);
+        }
+        array.removeAt(1);
+        tester.verify(20, "Removed one element");
+        x[1].tick();
+        tester.verify(20, "Ticked one removed element");
+        x[2].tick();
+        tester.verify(21, "Ticked one remaining element");
+
+        // Snapshot
+        {
+            final WatchedLongSparseArray<Leaf> arraySnap = array.snapshot();
+            tester.verify(21, "Generate snapshot (no changes)");
+            // Verify that the snapshot is a proper copy of the source.
+            assertEquals(name + " snap same size",
+                         array.size(), arraySnap.size());
+            for (int i = 0; i < array.size(); i++) {
+                for (int j = 0; j < arraySnap.size(); j++) {
+                    assertTrue(name + " elements differ",
+                               array.valueAt(i) != arraySnap.valueAt(j));
+                }
+                assertTrue(name + " element copy",
+                           array.valueAt(i).equals(arraySnap.valueAt(i)));
+            }
+            leafD.tick();
+            tester.verify(22, "Tick after snapshot");
+            // Verify that the array snapshot is sealed
+            verifySealed(name, ()->arraySnap.put(INDEX_A, leafB));
+            assertTrue(!array.isSealed());
+            assertTrue(arraySnap.isSealed());
+        }
+        // Recreate the snapshot since the test corrupted it.
+        {
+            final WatchedLongSparseArray<Leaf> arraySnap = array.snapshot();
+            // Verify that elements are also snapshots
+            final Leaf arraySnapElement = arraySnap.valueAt(0);
+            verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick());
+        }
+        // Verify copy-in/out
+        {
+            final String msg = name + " copy-in/out";
+            LongSparseArray<Leaf> base = new LongSparseArray<>();
+            array.copyTo(base);
+            WatchedLongSparseArray<Leaf> copy = new WatchedLongSparseArray<>();
+            copy.copyFrom(base);
+            final int end = array.size();
+            assertTrue(msg + " size mismatch " + end + " " + copy.size(), end == copy.size());
+            for (int i = 0; i < end; i++) {
+                final long key = array.keyAt(i);
+                assertTrue(msg, array.get(i) == copy.get(i));
+            }
+        }
+    }
+
+    @Test
+    public void testWatchedSparseBooleanArray() {
+        final String name = "WatchedSparseBooleanArray";
+        WatchableTester tester;
+
+        // Test WatchedSparseBooleanArray
+        WatchedSparseBooleanArray array = new WatchedSparseBooleanArray();
+        tester = new WatchableTester(array, name);
+        tester.verify(0, "Initial array - no registration");
+        array.put(INDEX_A, true);
+        tester.verify(0, "Updates with no registration");
+        tester.register();
+        tester.verify(0, "Updates with no registration");
+        array.put(INDEX_B, true);
+        tester.verify(1, "Updates with registration");
+        array.put(INDEX_B, false);
+        array.put(INDEX_C, true);
+        tester.verify(3, "Updates with registration");
+        // Special methods
+        array.setValueAt(array.indexOfKey(INDEX_C), false);
+        tester.verify(4, "Replaced true with false");
+        array.append(INDEX_D, true);
+        tester.verify(5, "Append true");
+
+        // Snapshot
+        {
+            WatchedSparseBooleanArray arraySnap = array.snapshot();
+            tester.verify(5, "Generate snapshot");
+            // Verify that the snapshot is a proper copy of the source.
+            assertEquals("WatchedSparseBooleanArray snap same size",
+                         array.size(), arraySnap.size());
+            for (int i = 0; i < array.size(); i++) {
+                assertEquals("WatchedSparseArray element copy",
+                             array.valueAt(i), arraySnap.valueAt(i));
+            }
+            array.put(INDEX_D, false);
+            tester.verify(6, "Tick after snapshot");
+            // Verify that the array is sealed
+            verifySealed(name, ()->arraySnap.put(INDEX_D, false));
+            assertTrue(!array.isSealed());
+            assertTrue(arraySnap.isSealed());
+        }
+        // Verify copy-in/out
+        {
+            final String msg = name + " copy-in/out";
+            SparseBooleanArray base = new SparseBooleanArray();
+            array.copyTo(base);
+            WatchedSparseBooleanArray copy = new WatchedSparseBooleanArray();
+            copy.copyFrom(base);
+            final int end = array.size();
+            assertTrue(msg + " size mismatch/2 " + end + " " + copy.size(), end == copy.size());
+            for (int i = 0; i < end; i++) {
+                final int key = array.keyAt(i);
+                assertTrue(msg + " element", array.get(i) == copy.get(i));
+            }
+        }
+    }
+
+    @Test
+    public void testWatchedSparseIntArray() {
+        final String name = "WatchedSparseIntArray";
+        WatchableTester tester;
+
+        // Test WatchedSparseIntArray
+        WatchedSparseIntArray array = new WatchedSparseIntArray();
+        tester = new WatchableTester(array, name);
+        tester.verify(0, "Initial array - no registration");
+        array.put(INDEX_A, 1);
+        tester.verify(0, "Updates with no registration");
+        tester.register();
+        tester.verify(0, "Updates with no registration");
+        array.put(INDEX_B, 2);
+        tester.verify(1, "Updates with registration");
+        array.put(INDEX_B, 4);
+        array.put(INDEX_C, 5);
+        tester.verify(3, "Updates with registration");
+        // Special methods
+        array.setValueAt(array.indexOfKey(INDEX_C), 7);
+        tester.verify(4, "Replaced 6 with 7");
+        array.append(INDEX_D, 8);
+        tester.verify(5, "Append 8");
+
+        // Snapshot
+        {
+            WatchedSparseIntArray arraySnap = array.snapshot();
+            tester.verify(5, "Generate snapshot");
+            // Verify that the snapshot is a proper copy of the source.
+            assertEquals("WatchedSparseIntArray snap same size",
+                         array.size(), arraySnap.size());
+            for (int i = 0; i < array.size(); i++) {
+                assertEquals(name + " element copy",
+                             array.valueAt(i), arraySnap.valueAt(i));
+            }
+            array.put(INDEX_D, 9);
+            tester.verify(6, "Tick after snapshot");
+            // Verify that the array is sealed
+            verifySealed(name, ()->arraySnap.put(INDEX_D, 10));
+            assertTrue(!array.isSealed());
+            assertTrue(arraySnap.isSealed());
+        }
+        // Verify copy-in/out
+        {
+            final String msg = name + " copy-in/out";
+            SparseIntArray base = new SparseIntArray();
+            array.copyTo(base);
+            WatchedSparseIntArray copy = new WatchedSparseIntArray();
+            copy.copyFrom(base);
+            final int end = array.size();
+            assertTrue(msg + " size mismatch " + end + " " + copy.size(), end == copy.size());
+            for (int i = 0; i < end; i++) {
+                final int key = array.keyAt(i);
+                assertTrue(msg, array.get(i) == copy.get(i));
+            }
+        }
+    }
+
+    @Test
+    public void testWatchedSparseSetArray() {
+        final String name = "WatchedSparseSetArray";
+        WatchableTester tester;
+
+        // Test WatchedSparseSetArray
+        WatchedSparseSetArray array = new WatchedSparseSetArray();
+        tester = new WatchableTester(array, name);
+        tester.verify(0, "Initial array - no registration");
+        array.add(INDEX_A, 1);
+        tester.verify(0, "Updates with no registration");
+        tester.register();
+        tester.verify(0, "Updates with no registration");
+        array.add(INDEX_B, 2);
+        tester.verify(1, "Updates with registration");
+        array.add(INDEX_B, 4);
+        array.add(INDEX_C, 5);
+        tester.verify(3, "Updates with registration");
+        // Special methods
+        assertTrue(array.remove(INDEX_C, 5));
+        tester.verify(4, "Removed 5 from key 3");
+        array.remove(INDEX_B);
+        tester.verify(5, "Removed everything for key 2");
+
+        // Snapshot
+        {
+            WatchedSparseSetArray arraySnap = (WatchedSparseSetArray) array.snapshot();
+            tester.verify(5, "Generate snapshot");
+            // Verify that the snapshot is a proper copy of the source.
+            assertEquals("WatchedSparseSetArray snap same size",
+                    array.size(), arraySnap.size());
+            for (int i = 0; i < array.size(); i++) {
+                ArraySet set = array.get(array.keyAt(i));
+                ArraySet setSnap = arraySnap.get(arraySnap.keyAt(i));
+                assertNotNull(set);
+                assertTrue(set.equals(setSnap));
+            }
+            array.add(INDEX_D, 9);
+            tester.verify(6, "Tick after snapshot");
+            // Verify that the array is sealed
+            verifySealed(name, ()->arraySnap.add(INDEX_D, 10));
+            assertTrue(!array.isSealed());
+            assertTrue(arraySnap.isSealed());
+        }
+        array.clear();
+        tester.verify(7, "Cleared all entries");
+    }
+
+    private static class IndexGenerator {
+        private final int mSeed;
+        private final Random mRandom;
+        public IndexGenerator(int seed) {
+            mSeed = seed;
+            mRandom = new Random(mSeed);
+        }
+        public int next() {
+            return mRandom.nextInt(50000);
+        }
+        public void reset() {
+            mRandom.setSeed(mSeed);
+        }
+        // This is an inefficient way to know if a value appears in an array.
+        private boolean contains(int[] s, int length, int k) {
+            for (int i = 0; i < length; i++) {
+                if (s[i] == k) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        public int[] indexes(int size) {
+            reset();
+            int[] r = new int[size];
+            for (int i = 0; i < size; i++) {
+                int key = next();
+                // Ensure the list of indices are unique.
+                while (contains(r, i, key)) {
+                    key = next();
+                }
+                r[i] = key;
+            }
+            return r;
+        }
+    }
+
+    // Return a value based on the row and column.  The algorithm tries to avoid simple
+    // patterns like checkerboard.
+    private final boolean cellValue(int row, int col) {
+        return (((row * 4 + col) % 3)& 1) == 1;
+    }
+
+    // Fill a matrix
+    private void fill(WatchedSparseBooleanMatrix matrix, int size, int[] indexes) {
+        for (int i = 0; i < size; i++) {
+            int row = indexes[i];
+            for (int j = 0; j < size; j++) {
+                int col = indexes[j];
+                boolean want = cellValue(i, j);
+                matrix.put(row, col, want);
+            }
+        }
+    }
+
+    // Fill new cells in the matrix which has enlarged capacity.
+    private void fillNew(WatchedSparseBooleanMatrix matrix, int initialCapacity,
+            int newCapacity, int[] indexes) {
+        final int size = newCapacity;
+        for (int i = 0; i < size; i++) {
+            for (int j = 0; j < size; j++) {
+                if (i < initialCapacity && j < initialCapacity) {
+                    // Do not touch old cells
+                    continue;
+                }
+                final int row = indexes[i];
+                final int col = indexes[j];
+                matrix.put(row, col, cellValue(i, j));
+            }
+        }
+    }
+
+    // Verify the content of a matrix.  This asserts on mismatch.  Selected indices may
+    // have been deleted.
+    private void verify(WatchedSparseBooleanMatrix matrix, int[] indexes, boolean[] absent) {
+        for (int i = 0; i < matrix.size(); i++) {
+            int row = indexes[i];
+            for (int j = 0; j < matrix.size(); j++) {
+                int col = indexes[j];
+                if (absent != null && (absent[i] || absent[j])) {
+                    boolean want = false;
+                    String msg = String.format("matrix(%d:%d, %d:%d) (deleted)", i, row, j, col);
+                    assertEquals(msg, matrix.get(row, col), false);
+                    assertEquals(msg, matrix.get(row, col, false), false);
+                    assertEquals(msg, matrix.get(row, col, true), true);
+                } else {
+                    boolean want = cellValue(i, j);
+                    String msg = String.format("matrix(%d:%d, %d:%d)", i, row, j, col);
+                    assertEquals(msg, matrix.get(row, col), want);
+                    assertEquals(msg, matrix.get(row, col, false), want);
+                    assertEquals(msg, matrix.get(row, col, true), want);
+                }
+            }
+        }
+    }
+
+    private void matrixGrow(WatchedSparseBooleanMatrix matrix, int size, IndexGenerator indexer) {
+        int[] indexes = indexer.indexes(size);
+
+        // Set values in the matrix, then read back and verify.
+        fill(matrix, size, indexes);
+        assertEquals(matrix.size(), size);
+        verify(matrix, indexes, null);
+
+        // Test the keyAt/indexOfKey methods
+        for (int i = 0; i < matrix.size(); i++) {
+            int key = indexes[i];
+            assertEquals(matrix.keyAt(matrix.indexOfKey(key)), key);
+        }
+    }
+
+    private void matrixDelete(WatchedSparseBooleanMatrix matrix, int size, IndexGenerator indexer) {
+        int[] indexes = indexer.indexes(size);
+        fill(matrix, size, indexes);
+
+        // Delete a bunch of rows.  Verify that reading back results in false and that
+        // contains() is false.  Recreate the rows and verify that all cells (other than
+        // the one just created) are false.
+        boolean[] absent = new boolean[size];
+        for (int i = 0; i < size; i += 13) {
+            matrix.deleteKey(indexes[i]);
+            absent[i] = true;
+        }
+        verify(matrix, indexes, absent);
+    }
+
+    private void matrixShrink(WatchedSparseBooleanMatrix matrix, int size, IndexGenerator indexer) {
+        int[] indexes = indexer.indexes(size);
+        fill(matrix, size, indexes);
+
+        int initialCapacity = matrix.capacity();
+
+        // Delete every other row, remembering which rows were deleted.  The goal is to
+        // make room for compaction.
+        boolean[] absent = new boolean[size];
+        for (int i = 0; i < size; i += 2) {
+            matrix.deleteKey(indexes[i]);
+            absent[i] = true;
+        }
+
+        matrix.compact();
+        int finalCapacity = matrix.capacity();
+        assertTrue("Matrix shrink", initialCapacity > finalCapacity);
+        assertTrue("Matrix shrink", finalCapacity - matrix.size() < matrix.STEP);
+    }
+
+    private void matrixSetCapacity(WatchedSparseBooleanMatrix matrix, int newCapacity,
+            IndexGenerator indexer) {
+        final int initialCapacity = matrix.capacity();
+        final int[] indexes = indexer.indexes(Math.max(initialCapacity, newCapacity));
+        fill(matrix, initialCapacity, indexes);
+
+        matrix.setCapacity(newCapacity);
+        fillNew(matrix, initialCapacity, newCapacity, indexes);
+
+        assertEquals(matrix.size(), indexes.length);
+        verify(matrix, indexes, null);
+        // Test the keyAt/indexOfKey methods
+        for (int i = 0; i < matrix.size(); i++) {
+            int key = indexes[i];
+            assertEquals(matrix.keyAt(matrix.indexOfKey(key)), key);
+        }
+    }
+
+    @Test
+    public void testWatchedSparseBooleanMatrix() {
+        final String name = "WatchedSparseBooleanMatrix";
+
+        // Test the core matrix functionality.  The three tess are meant to test various
+        // combinations of auto-grow.
+        IndexGenerator indexer = new IndexGenerator(3);
+        matrixGrow(new WatchedSparseBooleanMatrix(), 10, indexer);
+        matrixGrow(new WatchedSparseBooleanMatrix(1000), 500, indexer);
+        matrixGrow(new WatchedSparseBooleanMatrix(1000), 2000, indexer);
+        matrixDelete(new WatchedSparseBooleanMatrix(), 500, indexer);
+        matrixShrink(new WatchedSparseBooleanMatrix(), 500, indexer);
+
+        // Test Watchable behavior.
+        WatchedSparseBooleanMatrix matrix = new WatchedSparseBooleanMatrix();
+        WatchableTester tester = new WatchableTester(matrix, name);
+        tester.verify(0, "Initial array - no registration");
+        matrix.put(INDEX_A, INDEX_A, true);
+        tester.verify(0, "Updates with no registration");
+        tester.register();
+        tester.verify(0, "Updates with no registration");
+        matrix.put(INDEX_A, INDEX_B, true);
+        tester.verify(1, "Single cell assignment");
+        matrix.put(INDEX_A, INDEX_B, true);
+        tester.verify(2, "Single cell assignment - same value");
+        matrix.put(INDEX_C, INDEX_B, true);
+        tester.verify(3, "Single cell assignment");
+        matrix.deleteKey(INDEX_B);
+        tester.verify(4, "Delete key");
+        assertEquals(matrix.get(INDEX_B, INDEX_C), false);
+        assertEquals(matrix.get(INDEX_B, INDEX_C, false), false);
+        assertEquals(matrix.get(INDEX_B, INDEX_C, true), true);
+
+        matrix.clear();
+        tester.verify(5, "Clear");
+        assertEquals(matrix.size(), 0);
+        fill(matrix, 10, indexer.indexes(10));
+        int[] keys = matrix.keys();
+        assertEquals(keys.length, matrix.size());
+        for (int i = 0; i < matrix.size(); i++) {
+            assertEquals(matrix.keyAt(i), keys[i]);
+        }
+
+        WatchedSparseBooleanMatrix a = new WatchedSparseBooleanMatrix();
+        matrixGrow(a, 10, indexer);
+        assertEquals(a.size(), 10);
+        WatchedSparseBooleanMatrix b = new WatchedSparseBooleanMatrix();
+        matrixGrow(b, 10, indexer);
+        assertEquals(b.size(), 10);
+        assertEquals(a.equals(b), true);
+        int rowIndex = b.keyAt(3);
+        int colIndex = b.keyAt(4);
+        b.put(rowIndex, colIndex, !b.get(rowIndex, colIndex));
+        assertEquals(a.equals(b), false);
+
+        // Test Snappable behavior.
+        WatchedSparseBooleanMatrix s = a.snapshot();
+        assertEquals(a.equals(s), true);
+        a.put(rowIndex, colIndex, !a.get(rowIndex, colIndex));
+        assertEquals(a.equals(s), false);
+
+        // Verify copy-in/out
+        {
+            final String msg = name + " copy";
+            WatchedSparseBooleanMatrix copy = new WatchedSparseBooleanMatrix();
+            copy.copyFrom(matrix);
+            final int end = copy.size();
+            assertTrue(msg + " size mismatch " + end + " " + matrix.size(), end == matrix.size());
+            for (int i = 0; i < end; i++) {
+                assertEquals(copy.keyAt(i), keys[i]);
+            }
+        }
+    }
+
+    @Test
+    public void testWatchedSparseBooleanMatrix_setCapacity() {
+        final IndexGenerator indexer = new IndexGenerator(3);
+        matrixSetCapacity(new WatchedSparseBooleanMatrix(500), 1000, indexer);
+        matrixSetCapacity(new WatchedSparseBooleanMatrix(1000), 500, indexer);
+    }
+
+    @Test
+    public void testWatchedSparseBooleanMatrix_removeRangeAndShrink() {
+        final IndexGenerator indexer = new IndexGenerator(3);
+        final int initialCapacity = 500;
+        final int removeCounts = 33;
+        final WatchedSparseBooleanMatrix matrix = new WatchedSparseBooleanMatrix(initialCapacity);
+        final int[] indexes = indexer.indexes(initialCapacity);
+        final boolean[] absents = new boolean[initialCapacity];
+        fill(matrix, initialCapacity, indexes);
+        assertEquals(matrix.size(), initialCapacity);
+
+        for (int i = 0; i < initialCapacity / removeCounts; i++) {
+            final int size = matrix.size();
+            final int fromIndex = (size / 2 < removeCounts ? 0 : size / 2 - removeCounts);
+            final int toIndex = (fromIndex + removeCounts > size ? size : fromIndex + removeCounts);
+            for (int index = fromIndex; index < toIndex; index++) {
+                final int key = matrix.keyAt(index);
+                for (int j = 0; j < indexes.length; j++) {
+                    if (key == indexes[j]) {
+                        absents[j] = true;
+                        break;
+                    }
+                }
+            }
+            matrix.removeRange(fromIndex, toIndex);
+            assertEquals(matrix.size(), size - (toIndex - fromIndex));
+            verify(matrix, indexes, absents);
+
+            matrix.compact();
+            verify(matrix, indexes, absents);
+        }
+    }
+
+    @Test
+    public void testNestedArrays() {
+        final String name = "NestedArrays";
+        WatchableTester tester;
+
+        // Create a few leaves
+        Leaf leafA = new Leaf();
+        Leaf leafB = new Leaf();
+        Leaf leafC = new Leaf();
+        Leaf leafD = new Leaf();
+
+        // Test nested arrays.
+        WatchedLongSparseArray<Leaf> lsaA = new WatchedLongSparseArray<>();
+        lsaA.put(2, leafA);
+        WatchedLongSparseArray<Leaf> lsaB = new WatchedLongSparseArray<>();
+        lsaB.put(4, leafB);
+        WatchedLongSparseArray<Leaf> lsaC = new WatchedLongSparseArray<>();
+        lsaC.put(6, leafC);
+
+        WatchedArrayMap<String, WatchedLongSparseArray<Leaf>> array =
+                new WatchedArrayMap<>();
+        array.put("A", lsaA);
+        array.put("B", lsaB);
+
+        // Test WatchedSparseIntArray
+        tester = new WatchableTester(array, name);
+        tester.verify(0, "Initial array - no registration");
+        tester.register();
+        tester.verify(0, "Initial array - post registration");
+        leafA.tick();
+        tester.verify(1, "tick grand-leaf");
+        lsaA.put(2, leafD);
+        tester.verify(2, "replace leafA");
+        leafA.tick();
+        tester.verify(2, "tick unregistered leafA");
+        leafD.tick();
+        tester.verify(3, "tick leafD");
+    }
+
+    @Test
+    public void testSnapshotCache() {
+        final String name = "SnapshotCache";
+        WatchableTester tester;
+
+        Leaf leafA = new Leaf();
+        SnapshotCache<Leaf> cache = new SnapshotCache<>(leafA, leafA) {
+                @Override
+                public Leaf createSnapshot() {
+                    return mSource.snapshot();
+                }};
+
+        Leaf s1 = cache.snapshot();
+        assertTrue(s1 == cache.snapshot());
+        leafA.tick();
+        Leaf s2 = cache.snapshot();
+        assertTrue(s1 != s2);
+        assertTrue(leafA.get() == s1.get() + 1);
+        assertTrue(leafA.get() == s2.get());
+
+        // Test sealed snapshots
+        SnapshotCache<Leaf> sealed = new SnapshotCache.Sealed();
+        try {
+            Leaf x1 = sealed.snapshot();
+            fail(name + " sealed snapshot did not throw");
+        } catch (UnsupportedOperationException e) {
+            // This is the passing scenario - the exception is expected.
+        }
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
index faa2352..5f26d6f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
@@ -53,6 +53,11 @@
         snapshot.use {
             val packageStates = it.packageStates
 
+            // Check for unmodifiable
+            assertFailsWith(UnsupportedOperationException::class) {
+                it.packageStates.clear()
+            }
+
             // Check contents
             assertThat(packageStates).containsExactly(
                 packageStateAll.packageName, packageStateAll,
@@ -78,9 +83,14 @@
             assertThat(filteredOne.getPackageState(packageStateUser10.packageName)).isNull()
 
             filteredThree.use {
-                val statesList = mutableListOf<PackageState>()
-                assertThat(it.forAllPackageStates { statesList += it })
-                assertThat(statesList).containsExactly(packageStateAll, packageStateUser10)
+                // Check for unmodifiable
+                assertFailsWith(UnsupportedOperationException::class) {
+                    it.packageStates.clear()
+                }
+                assertThat(it.packageStates).containsExactly(
+                    packageStateAll.packageName, packageStateAll,
+                    packageStateUser10.packageName, packageStateUser10,
+                )
             }
 
             // Call after child close, parent open fails
@@ -96,7 +106,7 @@
 
         // Call after close fails
         assertClosedFailure { snapshot.packageStates }
-        assertClosedFailure { filteredOne.forAllPackageStates {} }
+        assertClosedFailure { filteredOne.packageStates }
         assertClosedFailure {
             filteredTwo.getPackageState(packageStateAll.packageName)
         }
@@ -116,9 +126,15 @@
                 .isEqualTo(packageStateUser0)
             assertThat(it.getPackageState(packageStateUser10.packageName)).isNull()
 
-            val statesList = mutableListOf<PackageState>()
-            assertThat(it.forAllPackageStates { statesList += it })
-            assertThat(statesList).containsExactly(packageStateAll, packageStateUser0)
+            // Check for unmodifiable
+            assertFailsWith(UnsupportedOperationException::class) {
+                it.packageStates.clear()
+            }
+
+            assertThat(it.packageStates).containsExactly(
+                packageStateAll.packageName, packageStateAll,
+                packageStateUser0.packageName, packageStateUser0,
+            )
         }
 
         // Call after close fails
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 1f66a11..7bf9a9e 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -17,7 +17,12 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import android.content.Intent
-import android.content.pm.*
+import android.content.pm.ApplicationInfo
+import android.content.pm.ConfigurationInfo
+import android.content.pm.FeatureGroupInfo
+import android.content.pm.FeatureInfo
+import android.content.pm.PackageManager
+import android.content.pm.SigningDetails
 import android.net.Uri
 import android.os.Bundle
 import android.os.Parcelable
@@ -27,16 +32,32 @@
 import com.android.internal.R
 import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.component.*
+import com.android.server.pm.pkg.component.ParsedActivityImpl
+import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl
+import com.android.server.pm.pkg.component.ParsedAttributionImpl
+import com.android.server.pm.pkg.component.ParsedComponentImpl
+import com.android.server.pm.pkg.component.ParsedInstrumentationImpl
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
+import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl
+import com.android.server.pm.pkg.component.ParsedPermissionImpl
+import com.android.server.pm.pkg.component.ParsedProcessImpl
+import com.android.server.pm.pkg.component.ParsedProviderImpl
+import com.android.server.pm.pkg.component.ParsedServiceImpl
+import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
 import java.security.KeyPairGenerator
 import java.security.PublicKey
+import java.util.UUID
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
 class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, PackageImpl::class) {
 
+    companion object {
+        private val TEST_UUID = UUID.fromString("57554103-df3e-4475-ae7a-8feba49353ac")
+    }
+
     override val defaultImpl = PackageImpl.forTesting("com.example.test")
     override val creator = PackageImpl.CREATOR
 
@@ -72,8 +93,6 @@
         "getLongVersionCode",
         // Tested through constructor
         "getManifestPackageName",
-        // Utility methods
-        "getStorageUuid",
         // Removal not tested, irrelevant for parcelling concerns
         "removeUsesOptionalLibrary",
         "clearAdoptPermissions",
@@ -85,6 +104,7 @@
         // Tested manually
         "getMimeGroups",
         "getRequestedPermissions",
+        "getStorageUuid",
         // Tested through asSplit
         "asSplit",
         "getSplits",
@@ -155,7 +175,6 @@
         AndroidPackage::getResizeableActivity,
         AndroidPackage::getRestrictedAccountType,
         AndroidPackage::getRoundIconRes,
-        PackageImpl::getSeInfo,
         PackageImpl::getSecondaryCpuAbi,
         AndroidPackage::getSecondaryNativeLibraryDir,
         AndroidPackage::getSharedUserId,
@@ -241,7 +260,7 @@
     )
 
     override fun extraParams() = listOf(
-        getter(AndroidPackage::getVolumeUuid, "57554103-df3e-4475-ae7a-8feba49353ac"),
+        getter(AndroidPackage::getVolumeUuid, TEST_UUID.toString()),
         getter(AndroidPackage::isProfileable, true),
         getter(PackageImpl::getVersionCode, 3),
         getter(PackageImpl::getVersionCodeMajor, 9),
@@ -602,6 +621,8 @@
         expect.that(after.usesStaticLibrariesCertDigests!!.size).isEqualTo(1)
         expect.that(after.usesStaticLibrariesCertDigests!![0]).asList()
                 .containsExactly("testCertDigest2")
+
+        expect.that(after.storageUuid).isEqualTo(TEST_UUID)
     }
 
     private fun testKey() = KeyPairGenerator.getInstance("RSA")
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
index 4ceae96..0e2e35f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -54,7 +54,7 @@
         ParsedActivity::getTheme,
         ParsedActivity::getUiOptions,
         ParsedActivity::isSupportsSizeChanges,
-        ParsedActivity::getTargetDisplayCategory
+        ParsedActivity::getRequiredDisplayCategory
     )
 
     override fun mainComponentSubclassExtraParams() = listOf(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index e4d124e..55645d7 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -219,6 +219,20 @@
                     printState(mock(Computer::class.java), mock(IndentingPrintWriter::class.java),
                         null, null)
                 },
+                service(Type.QUERENT, "printOwnersForPackage") {
+                    printOwnersForPackage(
+                        mock(IndentingPrintWriter::class.java),
+                        it.targetPackageName,
+                        it.userId
+                    )
+                },
+                service(Type.QUERENT, "printOwnersForDomains") {
+                    printOwnersForDomains(
+                        mock(IndentingPrintWriter::class.java),
+                        listOf("example.com"),
+                        it.userId
+                    )
+                },
                 service(Type.VERIFIER, "setStatus") {
                     setDomainVerificationStatus(
                         it.targetDomainSetId,
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 16317fe..681bfcf 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -56,6 +56,7 @@
         "service-jobscheduler",
         "service-permission.impl",
         "service-sdksandbox.impl",
+        "services.backup",
         "services.companion",
         "services.core",
         "services.devicepolicy",
@@ -78,6 +79,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/res/xml/game_manager_service_metadata_config_interventions_enabled_battery_opt_in.xml b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_battery_opt_in.xml
new file mode 100644
index 0000000..4f41ee6
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_battery_opt_in.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<game-mode-config
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:supportsPerformanceGameMode="false"
+    android:supportsBatteryGameMode="true"
+    android:allowGameAngleDriver="true"
+    android:allowGameDownscaling="true"
+    android:allowGameFpsOverride="true"
+/>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_performance_opt_in.xml b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_performance_opt_in.xml
new file mode 100644
index 0000000..0242aec
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_performance_opt_in.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<game-mode-config
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:supportsPerformanceGameMode="true"
+    android:supportsBatteryGameMode="false"
+    android:allowGameAngleDriver="true"
+    android:allowGameDownscaling="true"
+    android:allowGameFpsOverride="true"
+/>
\ No newline at end of file
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/DumpableDumperRule.java b/services/tests/mockingservicestests/src/com/android/server/DumpableDumperRule.java
new file mode 100644
index 0000000..33275bd
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/DumpableDumperRule.java
@@ -0,0 +1,91 @@
+/*
+ * 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;
+
+import android.util.Dumpable;
+import android.util.Log;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@code JUnit} rule that logs (using tag {@value #TAG} the contents of
+ * {@link Dumpable dumpables} in case of failure.
+ */
+public final class DumpableDumperRule implements TestRule {
+
+    private static final String TAG = DumpableDumperRule.class.getSimpleName();
+
+    private static final String[] NO_ARGS = {};
+
+    private final List<Dumpable> mDumpables = new ArrayList<>();
+
+    /**
+     * Adds a {@link Dumpable} to be logged if the test case fails.
+     */
+    public void addDumpable(Dumpable dumpable) {
+        mDumpables.add(dumpable);
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                try {
+                    base.evaluate();
+                } catch (Throwable t) {
+                    dumpOnFailure(description.getMethodName());
+                    throw t;
+                }
+            }
+        };
+    }
+
+    private void dumpOnFailure(String testName) throws IOException {
+        if (mDumpables.isEmpty()) {
+            return;
+        }
+        Log.w(TAG, "Dumping " + mDumpables.size() + " dumpables on failure of " + testName);
+        mDumpables.forEach(d -> logDumpable(d));
+    }
+
+    private void logDumpable(Dumpable dumpable) {
+        try {
+            try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
+                dumpable.dump(pw, NO_ARGS);
+                String[] dump = sw.toString().split(System.lineSeparator());
+                Log.w(TAG, "Dumping " + dumpable.getDumpableName() + " (" + dump.length
+                        + " lines):");
+                for (String line : dump) {
+                    Log.w(TAG, line);
+                }
+
+            } catch (RuntimeException e) {
+                Log.e(TAG, "RuntimeException dumping " + dumpable.getDumpableName(), e);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "IOException dumping " + dumpable.getDumpableName(), e);
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
index 9aa28ce..693a96d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
@@ -21,8 +21,13 @@
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
 
+import com.google.common.truth.Expect;
+import com.google.common.truth.StandardSubjectBuilder;
+
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 
@@ -38,6 +43,14 @@
 
     private MockitoSession mSession;
 
+    protected final Expect mExpect = Expect.create();
+    protected final DumpableDumperRule mDumpableDumperRule = new DumpableDumperRule();
+
+    @Rule
+    public final RuleChain mTwoRingsOfPowerAndOneChainToRuleThemAll = RuleChain
+            .outerRule(mDumpableDumperRule)
+            .around(mExpect);
+
     @Before
     public void startSession() {
         if (DEBUG) {
@@ -78,4 +91,12 @@
             mSession.finishMocking();
         }
     }
+
+    protected final StandardSubjectBuilder expectWithMessage(String msg) {
+        return mExpect.withMessage(msg);
+    }
+
+    protected final StandardSubjectBuilder expectWithMessage(String format, Object...args) {
+        return mExpect.withMessage(format, args);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 51fa851..e7c384b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -252,6 +252,7 @@
         noteBoot(4);
         assertTrue(RescueParty.isRebootPropertySet());
 
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
         noteBoot(5);
         assertTrue(RescueParty.isFactoryResetPropertySet());
     }
@@ -276,6 +277,7 @@
         noteAppCrash(4, true);
         assertTrue(RescueParty.isRebootPropertySet());
 
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
         noteAppCrash(5, true);
         assertTrue(RescueParty.isFactoryResetPropertySet());
     }
@@ -429,6 +431,27 @@
         for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
             noteBoot(i + 1);
         }
+        assertFalse(RescueParty.isFactoryResetPropertySet());
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+        noteBoot(LEVEL_FACTORY_RESET + 1);
+        assertTrue(RescueParty.isAttemptingFactoryReset());
+        assertTrue(RescueParty.isFactoryResetPropertySet());
+    }
+
+    @Test
+    public void testIsAttemptingFactoryResetOnlyAfterRebootCompleted() {
+        for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
+            noteBoot(i + 1);
+        }
+        int mitigationCount = LEVEL_FACTORY_RESET + 1;
+        assertFalse(RescueParty.isFactoryResetPropertySet());
+        noteBoot(mitigationCount++);
+        assertFalse(RescueParty.isFactoryResetPropertySet());
+        noteBoot(mitigationCount++);
+        assertFalse(RescueParty.isFactoryResetPropertySet());
+        noteBoot(mitigationCount++);
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+        noteBoot(mitigationCount + 1);
         assertTrue(RescueParty.isAttemptingFactoryReset());
         assertTrue(RescueParty.isFactoryResetPropertySet());
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
new file mode 100644
index 0000000..ea14ffb
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -0,0 +1,282 @@
+/*
+ * 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 static android.os.Process.myPid;
+import static android.os.Process.myUid;
+
+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.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.IApplicationThread;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Arrays;
+
+
+/**
+ * Tests to verify process starts are completed or timeout correctly
+ */
+@MediumTest
+@SuppressWarnings("GuardedBy")
+public class AsyncProcessStartTest {
+    private static final String TAG = "AsyncProcessStartTest";
+
+    private static final String PACKAGE = "com.foo";
+
+    @Rule
+    public final ApplicationExitInfoTest.ServiceThreadRule
+            mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
+
+    private Context mContext;
+    private HandlerThread mHandlerThread;
+
+    @Mock
+    private AppOpsService mAppOpsService;
+    @Mock
+    private DropBoxManagerInternal mDropBoxManagerInt;
+    @Mock
+    private PackageManagerInternal mPackageManagerInt;
+    @Mock
+    private UsageStatsManagerInternal mUsageStatsManagerInt;
+    @Mock
+    private ActivityManagerInternal mActivityManagerInt;
+    @Mock
+    private ActivityTaskManagerInternal mActivityTaskManagerInt;
+    @Mock
+    private BatteryStatsService mBatteryStatsService;
+
+    private ActivityManagerService mRealAms;
+    private ActivityManagerService mAms;
+
+    private ProcessList mRealProcessList = new ProcessList();
+    private ProcessList mProcessList;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+
+        LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+        LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt);
+
+        LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+        LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt);
+
+        doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+        doReturn(true).when(mActivityTaskManagerInt).attachApplication(any());
+        doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any());
+
+        mRealAms = new ActivityManagerService(
+                new TestInjector(mContext), mServiceThreadRule.getThread());
+        mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+        mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+        mRealAms.mAtmInternal = mActivityTaskManagerInt;
+        mRealAms.mPackageManagerInt = mPackageManagerInt;
+        mRealAms.mUsageStatsService = mUsageStatsManagerInt;
+        mRealAms.mProcessesReady = true;
+        mAms = spy(mRealAms);
+        mRealProcessList.mService = mAms;
+        mProcessList = spy(mRealProcessList);
+
+        doAnswer((invocation) -> {
+            Log.v(TAG, "Intercepting isProcStartValidLocked() for "
+                    + Arrays.toString(invocation.getArguments()));
+            return null;
+        }).when(mProcessList).isProcStartValidLocked(any(), anyLong());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+    }
+
+    private class TestInjector extends Injector {
+        TestInjector(Context context) {
+            super(context);
+        }
+
+        @Override
+        public AppOpsService getAppOpsService(File file, Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return mHandlerThread.getThreadHandler();
+        }
+
+        @Override
+        public ProcessList getProcessList(ActivityManagerService service) {
+            return mRealProcessList;
+        }
+
+        @Override
+        public BatteryStatsService getBatteryStatsService() {
+            return mBatteryStatsService;
+        }
+    }
+
+    private ProcessRecord makeActiveProcessRecord(String packageName, boolean wedge)
+            throws Exception {
+        final ApplicationInfo ai = makeApplicationInfo(packageName);
+        return makeActiveProcessRecord(ai, wedge);
+    }
+
+    private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, boolean wedge)
+            throws Exception {
+        final IApplicationThread thread = mock(IApplicationThread.class);
+        final IBinder threadBinder = new Binder();
+        doReturn(threadBinder).when(thread).asBinder();
+        doAnswer((invocation) -> {
+            Log.v(TAG, "Intercepting bindApplication() for "
+                    + Arrays.toString(invocation.getArguments()));
+            if (!wedge) {
+                mRealAms.finishAttachApplication(0);
+            }
+            return null;
+        }).when(thread).bindApplication(
+                any(), any(),
+                any(), any(),
+                any(), any(),
+                any(), any(),
+                any(),
+                any(), anyInt(),
+                anyBoolean(), anyBoolean(),
+                anyBoolean(), anyBoolean(), any(),
+                any(), any(), any(),
+                any(), any(),
+                any(), any(),
+                any(),
+                anyLong(), anyLong());
+
+        final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
+        r.setPid(myPid());
+        r.setStartUid(myUid());
+        r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST));
+        r.makeActive(thread, mAms.mProcessStats);
+        doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(),
+                anyBoolean());
+
+        return r;
+    }
+
+    static ApplicationInfo makeApplicationInfo(String packageName) {
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = packageName;
+        ai.processName = packageName;
+        ai.uid = myUid();
+        return ai;
+    }
+
+    /**
+     * Verify that we don't kill a normal process
+     */
+    @Test
+    public void testNormal() throws Exception {
+        ProcessRecord app = startProcessAndWait(false);
+
+        verify(app, never()).killLocked(any(), anyInt(), anyBoolean());
+    }
+
+    /**
+     * Verify that we kill a wedged process after the process start timeout
+     */
+    @Test
+    public void testWedged() throws Exception {
+        ProcessRecord app = startProcessAndWait(true);
+
+        verify(app).killLocked(any(), anyInt(), anyBoolean());
+    }
+
+    private ProcessRecord startProcessAndWait(boolean wedge) throws Exception {
+        final ProcessRecord app = makeActiveProcessRecord(PACKAGE, wedge);
+        final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE);
+
+        mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false,
+                /* expectedStartSeq */ 0, /* procAttached */ false);
+
+        app.getThread().bindApplication(PACKAGE, appInfo,
+                null, null,
+                null,
+                null,
+                null, null,
+                null,
+                null, 0,
+                false, false,
+                true, false,
+                null,
+                null, null,
+                null,
+                null, null, null,
+                null, null,
+                0, 0);
+
+        // Sleep until timeout should have triggered
+        SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+
+        return app;
+    }
+}
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..8d78cd6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -16,6 +16,13 @@
 
 package com.android.server.am;
 
+import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_ALARM;
+import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_FOREGROUND;
+import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_INTERACTIVE;
+import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_MANIFEST;
+import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_ORDERED;
+import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_PRIORITIZED;
+import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_RESULT_TO;
 import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
 import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
 import static com.android.server.am.BroadcastQueueTest.CLASS_GREEN;
@@ -25,6 +32,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;
 
@@ -35,19 +43,25 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 
 import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
+import android.appwidget.AppWidgetManager;
+import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ResolveInfo;
 import android.media.AudioManager;
 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 +73,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;
@@ -135,6 +151,18 @@
         }
     }
 
+    private static Intent makeMockIntent() {
+        return mock(Intent.class);
+    }
+
+    private static ResolveInfo makeMockManifestReceiver() {
+        return mock(ResolveInfo.class);
+    }
+
+    private static BroadcastFilter makeMockRegisteredReceiver() {
+        return mock(BroadcastFilter.class);
+    }
+
     private BroadcastRecord makeBroadcastRecord(Intent intent) {
         return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(),
                 List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), false);
@@ -145,6 +173,10 @@
                 List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true);
     }
 
+    private BroadcastRecord makeBroadcastRecord(Intent intent, List receivers) {
+        return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(), receivers, false);
+    }
+
     private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options) {
         return makeBroadcastRecord(intent, options,
                 List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), false);
@@ -152,12 +184,23 @@
 
     private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options,
             List receivers, boolean ordered) {
+        return makeBroadcastRecord(intent, options, receivers, null, ordered);
+    }
+
+    private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options,
+            List receivers, IIntentReceiver resultTo, boolean ordered) {
         return new BroadcastRecord(mImpl, intent, mProcess, PACKAGE_RED, null, 21, 42, false, null,
-                null, null, null, AppOpsManager.OP_NONE, options, receivers, null, null,
+                null, null, null, AppOpsManager.OP_NONE, options, receivers, null, resultTo,
                 Activity.RESULT_OK, null, null, ordered, false, false, UserHandle.USER_SYSTEM,
                 false, null, false, null);
     }
 
+    private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
+            BroadcastRecord record, int recordIndex, long enqueueTime) {
+        queue.enqueueOrReplaceBroadcast(record, recordIndex);
+        record.enqueueTime = enqueueTime;
+    }
+
     @Test
     public void testRunnableList_Simple() {
         assertRunnableList(List.of(), mHead);
@@ -282,8 +325,9 @@
                 PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+        final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
+                List.of(makeMockRegisteredReceiver()));
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
 
         queue.setProcessCached(false);
         final long notCachedRunnableAt = queue.getRunnableAt();
@@ -305,12 +349,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 +383,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());
@@ -362,8 +406,9 @@
                 PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+        final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
+                List.of(makeMockRegisteredReceiver()));
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
 
         mConstants.MAX_PENDING_BROADCASTS = 128;
         queue.invalidateRunnableAt();
@@ -377,6 +422,312 @@
     }
 
     /**
+     * Verify that a cached process that would normally be delayed becomes
+     * immediately runnable when the given broadcast is enqueued.
+     */
+    private void doRunnableAt_Cached(BroadcastRecord testRecord, int testRunnableAtReason) {
+        final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+        queue.setProcessCached(true);
+
+        final BroadcastRecord lazyRecord = makeBroadcastRecord(
+                new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
+                List.of(makeMockRegisteredReceiver()));
+
+        queue.enqueueOrReplaceBroadcast(lazyRecord, 0);
+        assertThat(queue.getRunnableAt()).isGreaterThan(lazyRecord.enqueueTime);
+        assertThat(queue.getRunnableAtReason()).isNotEqualTo(testRunnableAtReason);
+
+        queue.enqueueOrReplaceBroadcast(testRecord, 0);
+        assertThat(queue.getRunnableAt()).isAtMost(testRecord.enqueueTime);
+        assertThat(queue.getRunnableAtReason()).isEqualTo(testRunnableAtReason);
+    }
+
+    @Test
+    public void testRunnableAt_Cached_Manifest() {
+        doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null,
+                List.of(makeMockManifestReceiver()), null, false), REASON_CONTAINS_MANIFEST);
+    }
+
+    @Test
+    public void testRunnableAt_Cached_Ordered() {
+        doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null,
+                List.of(makeMockRegisteredReceiver()), null, true), REASON_CONTAINS_ORDERED);
+    }
+
+    @Test
+    public void testRunnableAt_Cached_ResultTo() {
+        final IIntentReceiver resultTo = mock(IIntentReceiver.class);
+        doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null,
+                List.of(makeMockRegisteredReceiver()), resultTo, false), REASON_CONTAINS_RESULT_TO);
+    }
+
+    @Test
+    public void testRunnableAt_Cached_Foreground() {
+        final Intent foregroundIntent = new Intent();
+        foregroundIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        doRunnableAt_Cached(makeBroadcastRecord(foregroundIntent, null,
+                List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_FOREGROUND);
+    }
+
+    @Test
+    public void testRunnableAt_Cached_Interactive() {
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setInteractive(true);
+        doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
+                List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_INTERACTIVE);
+    }
+
+    @Test
+    public void testRunnableAt_Cached_Alarm() {
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setAlarmBroadcast(true);
+        doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
+                List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_ALARM);
+    }
+
+    @Test
+    public void testRunnableAt_Cached_Prioritized() {
+        final List receivers = List.of(
+                withPriority(makeManifestReceiver(PACKAGE_RED, PACKAGE_RED), 10),
+                withPriority(makeManifestReceiver(PACKAGE_GREEN, PACKAGE_GREEN), -10));
+        doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null,
+                receivers, null, false), REASON_CONTAINS_PRIORITIZED);
+    }
+
+    /**
+     * 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 we don't let urgent broadcasts starve delivery of non-urgent
+     */
+    @Test
+    public void testUrgentStarvation() {
+        final BroadcastOptions optInteractive = BroadcastOptions.makeBasic();
+        optInteractive.setInteractive(true);
+
+        mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 2;
+        BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+        long timeCounter = 100;
+
+        // mix of broadcasts, with more than 2 fg/urgent
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)),
+                        0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)),
+                        0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES),
+                        optInteractive), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
+                        optInteractive), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL),
+                        optInteractive), 0, timeCounter++);
+
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_APPLICATION_PREFERENCES, queue.getActive().intent.getAction());
+        // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+        // and then back to prioritizing urgent ones
+        queue.makeActiveNextPending();
+        assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
+                queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_INPUT_METHOD_CHANGED, queue.getActive().intent.getAction());
+        // verify the reset-count-then-resume worked too
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+    }
+
+    /**
+     * Verify that offload broadcasts are not starved because of broadcasts in higher priority
+     * queues.
+     */
+    @Test
+    public void testOffloadStarvation() {
+        final BroadcastOptions optInteractive = BroadcastOptions.makeBasic();
+        optInteractive.setInteractive(true);
+
+        mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 1;
+        mConstants.MAX_CONSECUTIVE_NORMAL_DISPATCHES = 2;
+        final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+        long timeCounter = 100;
+
+        // mix of broadcasts, with more than 2 normal
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)),
+                        0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_PACKAGE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)),
+                0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES),
+                        optInteractive), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
+                        optInteractive), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL),
+                        optInteractive), 0, timeCounter++);
+
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+        // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+        // and then back to prioritizing urgent ones
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_APPLICATION_PREFERENCES, queue.getActive().intent.getAction());
+        // after MAX_CONSECUTIVE_URGENT_DISPATCHES, again an ordinary one next
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+        // and then back to prioritizing urgent ones
+        queue.makeActiveNextPending();
+        assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
+                queue.getActive().intent.getAction());
+        // after MAX_CONSECUTIVE_URGENT_DISPATCHES and MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+        // expect an offload one
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction());
+        // and then back to prioritizing urgent ones
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_INPUT_METHOD_CHANGED, queue.getActive().intent.getAction());
+    }
+
+    /**
+     * Verify that BroadcastProcessQueue#setPrioritizeEarliest() works as expected.
+     */
+    @Test
+    public void testPrioritizeEarliest() {
+        final BroadcastOptions optInteractive = BroadcastOptions.makeBasic();
+        optInteractive.setInteractive(true);
+
+        BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+        queue.setPrioritizeEarliest(true);
+        long timeCounter = 100;
+
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)),
+                        0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_PACKAGE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)),
+                        0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)),
+                        0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
+                        optInteractive), 0, timeCounter++);
+
+        // When we mark BroadcastProcessQueue to prioritize earliest, we should
+        // expect to dispatch broadcasts in the order they were enqueued
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+        // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_PACKAGE_CHANGED, queue.getActive().intent.getAction());
+        // and then back to prioritizing urgent ones
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIME_TICK, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+        // verify the reset-count-then-resume worked too
+        queue.makeActiveNextPending();
+        assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
+                queue.getActive().intent.getAction());
+    }
+
+    /**
      * Verify that sending a broadcast that removes any matching pending
      * broadcasts is applied as expected.
      */
@@ -425,7 +776,7 @@
         final BroadcastOptions optionsMusicVolumeChanged = BroadcastOptions.makeBasic();
         optionsMusicVolumeChanged.setDeliveryGroupPolicy(
                 BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
-        optionsMusicVolumeChanged.setDeliveryGroupKey("audio",
+        optionsMusicVolumeChanged.setDeliveryGroupMatchingKey("audio",
                 String.valueOf(AudioManager.STREAM_MUSIC));
 
         final Intent alarmVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
@@ -434,7 +785,7 @@
         final BroadcastOptions optionsAlarmVolumeChanged = BroadcastOptions.makeBasic();
         optionsAlarmVolumeChanged.setDeliveryGroupPolicy(
                 BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
-        optionsAlarmVolumeChanged.setDeliveryGroupKey("audio",
+        optionsAlarmVolumeChanged.setDeliveryGroupMatchingKey("audio",
                 String.valueOf(AudioManager.STREAM_ALARM));
 
         // Halt all processing so that we get a consistent view
@@ -490,7 +841,8 @@
         final BroadcastOptions optionsPackageChangedForUid = BroadcastOptions.makeBasic();
         optionsPackageChangedForUid.setDeliveryGroupPolicy(
                 BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
-        optionsPackageChangedForUid.setDeliveryGroupKey("package", String.valueOf(TEST_UID));
+        optionsPackageChangedForUid.setDeliveryGroupMatchingKey("package",
+                String.valueOf(TEST_UID));
         optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger);
 
         final Intent secondPackageChangedForUid = createPackageChangedIntent(TEST_UID,
@@ -501,7 +853,8 @@
         final BroadcastOptions optionsPackageChangedForUid2 = BroadcastOptions.makeBasic();
         optionsPackageChangedForUid.setDeliveryGroupPolicy(
                 BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
-        optionsPackageChangedForUid.setDeliveryGroupKey("package", String.valueOf(TEST_UID2));
+        optionsPackageChangedForUid.setDeliveryGroupMatchingKey("package",
+                String.valueOf(TEST_UID2));
         optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger);
 
         // Halt all processing so that we get a consistent view
@@ -524,6 +877,75 @@
         verifyPendingRecords(queue, List.of(packageChangedForUid2, expectedPackageChangedForUid));
     }
 
+    @Test
+    public void testDeliveryGroupPolicy_matchingFilter() {
+        final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+        final BroadcastOptions optionsTimeTick = BroadcastOptions.makeBasic();
+        optionsTimeTick.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+        final Intent musicVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
+        musicVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                AudioManager.STREAM_MUSIC);
+        final IntentFilter filterMusicVolumeChanged = new IntentFilter(
+                AudioManager.VOLUME_CHANGED_ACTION);
+        filterMusicVolumeChanged.addExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                AudioManager.STREAM_MUSIC);
+        final BroadcastOptions optionsMusicVolumeChanged = BroadcastOptions.makeBasic();
+        optionsMusicVolumeChanged.setDeliveryGroupPolicy(
+                BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+        optionsMusicVolumeChanged.setDeliveryGroupMatchingFilter(filterMusicVolumeChanged);
+
+        final Intent alarmVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
+        alarmVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                AudioManager.STREAM_ALARM);
+        final IntentFilter filterAlarmVolumeChanged = new IntentFilter(
+                AudioManager.VOLUME_CHANGED_ACTION);
+        filterAlarmVolumeChanged.addExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                AudioManager.STREAM_ALARM);
+        final BroadcastOptions optionsAlarmVolumeChanged = BroadcastOptions.makeBasic();
+        optionsAlarmVolumeChanged.setDeliveryGroupPolicy(
+                BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+        optionsAlarmVolumeChanged.setDeliveryGroupMatchingFilter(filterAlarmVolumeChanged);
+
+        // Halt all processing so that we get a consistent view
+        mHandlerThread.getLooper().getQueue().postSyncBarrier();
+
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+                optionsMusicVolumeChanged));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+                optionsAlarmVolumeChanged));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+                optionsMusicVolumeChanged));
+
+        final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        // Verify that the older musicVolumeChanged has been removed.
+        verifyPendingRecords(queue,
+                List.of(timeTick, alarmVolumeChanged, musicVolumeChanged));
+
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+                optionsAlarmVolumeChanged));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+                optionsMusicVolumeChanged));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+                optionsAlarmVolumeChanged));
+        // Verify that the older alarmVolumeChanged has been removed.
+        verifyPendingRecords(queue,
+                List.of(timeTick, musicVolumeChanged, alarmVolumeChanged));
+
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+                optionsMusicVolumeChanged));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+                optionsAlarmVolumeChanged));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+        // Verify that the older timeTick has been removed.
+        verifyPendingRecords(queue,
+                List.of(musicVolumeChanged, alarmVolumeChanged, timeTick));
+    }
+
     private Intent createPackageChangedIntent(int uid, List<String> componentNameList) {
         final Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
         packageChangedIntent.putExtra(Intent.EXTRA_UID, uid);
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..b9615f6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -50,7 +50,6 @@
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.app.IApplicationThread;
-import android.app.RemoteServiceException.CannotDeliverBroadcastException;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
@@ -280,13 +279,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 +502,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);
     }
@@ -959,8 +963,8 @@
 
         // First broadcast should have already been dead
         verifyScheduleRegisteredReceiver(receiverApp, airplane);
-        verify(receiverApp).scheduleCrashLocked(any(),
-                eq(CannotDeliverBroadcastException.TYPE_ID), any());
+        // The old receiverApp should be killed gently
+        assertTrue(receiverApp.isKilled());
 
         // Second broadcast in new process should work fine
         final ProcessRecord restartedReceiverApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
@@ -990,8 +994,8 @@
 
         // First broadcast should have already been dead
         verifyScheduleReceiver(receiverApp, airplane);
-        verify(receiverApp).scheduleCrashLocked(any(),
-                eq(CannotDeliverBroadcastException.TYPE_ID), any());
+        // The old receiverApp should be killed gently
+        assertTrue(receiverApp.isKilled());
 
         // Second broadcast in new process should work fine
         final ProcessRecord restartedReceiverApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
@@ -1634,4 +1638,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..3864c88 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;
@@ -35,13 +36,17 @@
 import static org.mockito.Mockito.doReturn;
 
 import android.app.ActivityManagerInternal;
+import android.app.BroadcastOptions;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
+import android.telephony.SubscriptionManager;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
@@ -99,6 +104,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 +126,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 +149,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
@@ -141,9 +180,8 @@
         intent.putExtra(Intent.EXTRA_INDEX, 42);
         final BroadcastRecord r = createBroadcastRecord(
                 List.of(createResolveInfo(PACKAGE1, getAppId(1))), UserHandle.USER_ALL, intent,
-                (uid, extras) -> {
-                    return Bundle.EMPTY;
-                });
+                (uid, extras) -> Bundle.EMPTY,
+                null /* options */);
         final Intent actual = r.getReceiverIntent(r.receivers.get(0));
         assertEquals(PACKAGE1, actual.getComponent().getPackageName());
         assertEquals(-1, actual.getIntExtra(Intent.EXTRA_INDEX, -1));
@@ -157,9 +195,8 @@
         intent.putExtra(Intent.EXTRA_INDEX, 42);
         final BroadcastRecord r = createBroadcastRecord(
                 List.of(createResolveInfo(PACKAGE1, getAppId(1))), UserHandle.USER_ALL, intent,
-                (uid, extras) -> {
-                    return null;
-                });
+                (uid, extras) -> null,
+                null /* options */);
         final Intent actual = r.getReceiverIntent(r.receivers.get(0));
         assertNull(actual);
         assertNull(r.intent.getComponent());
@@ -255,6 +292,122 @@
         assertEquals(origReceiversSize, br.receivers.size());
     }
 
+    @Test
+    public void testMatchesDeliveryGroup() {
+        final List<ResolveInfo> receivers = List.of(createResolveInfo(PACKAGE1, getAppId(1)));
+
+        final Intent intent1 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent1.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        intent1.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+        final BroadcastOptions options1 = BroadcastOptions.makeBasic();
+        final BroadcastRecord record1 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent1, options1);
+
+        final Intent intent2 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent2.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        intent2.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 2);
+        final BroadcastOptions options2 = BroadcastOptions.makeBasic();
+        final BroadcastRecord record2 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent2, options2);
+
+        assertTrue(record2.matchesDeliveryGroup(record1));
+    }
+
+    @Test
+    public void testMatchesDeliveryGroup_withMatchingKey() {
+        final List<ResolveInfo> receivers = List.of(createResolveInfo(PACKAGE1, getAppId(1)));
+
+        final Intent intent1 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent1.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        intent1.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+        final BroadcastOptions options1 = BroadcastOptions.makeBasic();
+        options1.setDeliveryGroupMatchingKey(Intent.ACTION_SERVICE_STATE, "key1");
+        final BroadcastRecord record1 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent1, options1);
+
+        final Intent intent2 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent2.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        intent2.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 2);
+        final BroadcastOptions options2 = BroadcastOptions.makeBasic();
+        options2.setDeliveryGroupMatchingKey(Intent.ACTION_SERVICE_STATE, "key2");
+        final BroadcastRecord record2 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent2, options2);
+
+        final Intent intent3 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent3.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 1);
+        intent3.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 3);
+        final BroadcastOptions options3 = BroadcastOptions.makeBasic();
+        options3.setDeliveryGroupMatchingKey(Intent.ACTION_SERVICE_STATE, "key1");
+        final BroadcastRecord record3 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent3, options3);
+
+        // record2 and record1 have different matching keys, so their delivery groups
+        // shouldn't match
+        assertFalse(record2.matchesDeliveryGroup(record1));
+        // record3 and record2 have different matching keys, so their delivery groups
+        // shouldn't match
+        assertFalse(record3.matchesDeliveryGroup(record2));
+        // record3 and record1 have same matching keys, so their delivery groups should match even
+        // if the intent has different extras.
+        assertTrue(record3.matchesDeliveryGroup(record1));
+    }
+
+    @Test
+    public void testMatchesDeliveryGroup_withMatchingFilter() {
+        final List<ResolveInfo> receivers = List.of(createResolveInfo(PACKAGE1, getAppId(1)));
+
+        final Intent intent1 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent1.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        intent1.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+        intent1.putExtra(Intent.EXTRA_REASON, "reason1");
+        final IntentFilter filter1 = new IntentFilter(Intent.ACTION_SERVICE_STATE);
+        final PersistableBundle bundle1 = new PersistableBundle();
+        bundle1.putInt(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        bundle1.putInt(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+        filter1.setExtras(bundle1);
+        final BroadcastOptions options1 = BroadcastOptions.makeBasic();
+        options1.setDeliveryGroupMatchingFilter(filter1);
+        final BroadcastRecord record1 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent1, options1);
+
+        final Intent intent2 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent2.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        intent2.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 2);
+        intent2.putExtra(Intent.EXTRA_REASON, "reason2");
+        final IntentFilter filter2 = new IntentFilter(Intent.ACTION_SERVICE_STATE);
+        final PersistableBundle bundle2 = new PersistableBundle();
+        bundle2.putInt(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        bundle2.putInt(SubscriptionManager.EXTRA_SLOT_INDEX, 2);
+        filter2.setExtras(bundle2);
+        final BroadcastOptions options2 = BroadcastOptions.makeBasic();
+        options2.setDeliveryGroupMatchingFilter(filter2);
+        final BroadcastRecord record2 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent2, options2);
+
+        final Intent intent3 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent3.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 1);
+        intent3.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 3);
+        intent3.putExtra(Intent.EXTRA_REASON, "reason3");
+        final IntentFilter filter3 = new IntentFilter(Intent.ACTION_SERVICE_STATE);
+        final PersistableBundle bundle3 = new PersistableBundle();
+        bundle3.putInt(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        bundle3.putInt(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+        filter3.setExtras(bundle3);
+        final BroadcastOptions options3 = BroadcastOptions.makeBasic();
+        options3.setDeliveryGroupMatchingFilter(filter3);
+        final BroadcastRecord record3 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent3, options3);
+
+        // record2's matchingFilter doesn't match record1's intent, so their delivery groups
+        // shouldn't match
+        assertFalse(record2.matchesDeliveryGroup(record1));
+        // record3's matchingFilter doesn't match record2's intent, so their delivery groups
+        // shouldn't match
+        assertFalse(record3.matchesDeliveryGroup(record2));
+        // record3's matchingFilter matches record1's intent, so their delivery groups should match.
+        assertTrue(record3.matchesDeliveryGroup(record1));
+    }
+
     private BroadcastRecord createBootCompletedBroadcastRecord(String action) {
         final List<ResolveInfo> receivers = createReceiverInfos(PACKAGE_LIST, USER_LIST);
         final BroadcastRecord br = createBroadcastRecord(receivers, UserHandle.USER_ALL,
@@ -504,11 +657,19 @@
 
     private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
             Intent intent) {
-        return createBroadcastRecord(receivers, userId, intent, null);
+        return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
+                null /* options */);
     }
 
     private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
-            Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+            Intent intent, BroadcastOptions options) {
+        return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
+                options);
+    }
+
+    private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
+            Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+            BroadcastOptions options) {
         return new BroadcastRecord(
                 mQueue /* queue */,
                 intent,
@@ -523,7 +684,7 @@
                 null /* excludedPermissions */,
                 null /* excludedPackages */,
                 0 /* appOp */,
-                null /* options */,
+                options,
                 new ArrayList<>(receivers), // Make a copy to not affect the original list.
                 null /* resultToApp */,
                 null /* resultTo */,
@@ -543,4 +704,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/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1c34548..9234431 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_NETWORK;
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
@@ -94,7 +95,6 @@
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.SparseArray;
 
 import com.android.server.LocalServices;
@@ -414,7 +414,8 @@
     public void testUpdateOomAdj_DoOne_FgServiceLocation() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        app.mServices.setHasForegroundServices(true, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
+        app.mServices.setHasForegroundServices(true, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION,
+                /* hasNoneType=*/false);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
 
@@ -427,7 +428,7 @@
     public void testUpdateOomAdj_DoOne_FgService() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        app.mServices.setHasForegroundServices(true, 0);
+        app.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
 
@@ -437,6 +438,83 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoOne_FgService_ShortFgs() {
+        sService.mConstants.TOP_TO_FGS_GRACE_DURATION = 100_000;
+        sService.mConstants.mShortFgsProcStateExtraWaitDuration = 200_000;
+
+        ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
+        s.startRequested = true;
+        s.isForeground = true;
+        s.foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+        s.setShortFgsInfo(SystemClock.uptimeMillis());
+
+        // SHORT_SERVICE FGS will get IMP_FG and a slightly different recent-adjustment.
+        {
+            ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                    MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+            app.mServices.startService(s);
+            app.mServices.setHasForegroundServices(true,
+                    ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+            app.mState.setLastTopTime(SystemClock.uptimeMillis());
+            sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
+            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+            assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
+                    PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
+            // Should get network access.
+            assertTrue((app.mState.getSetCapability() & PROCESS_CAPABILITY_NETWORK) != 0);
+        }
+
+        // SHORT_SERVICE, but no longer recent.
+        {
+            ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                    MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+            app.mServices.setHasForegroundServices(true,
+                    ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+            app.mServices.startService(s);
+            app.mState.setLastTopTime(SystemClock.uptimeMillis()
+                    - sService.mConstants.TOP_TO_FGS_GRACE_DURATION);
+            sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
+            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+            assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
+                    PERCEPTIBLE_MEDIUM_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
+            // Still should get network access.
+            assertTrue((app.mState.getSetCapability() & PROCESS_CAPABILITY_NETWORK) != 0);
+        }
+
+        // SHORT_SERVICE, timed out already.
+        s = ServiceRecord.newEmptyInstanceForTest(sService);
+        s.startRequested = true;
+        s.isForeground = true;
+        s.foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+        s.setShortFgsInfo(SystemClock.uptimeMillis()
+                - sService.mConstants.mShortFgsTimeoutDuration
+                - sService.mConstants.mShortFgsProcStateExtraWaitDuration);
+        {
+            ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                    MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+            app.mServices.setHasForegroundServices(true,
+                    ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+            app.mServices.startService(s);
+            app.mState.setLastTopTime(SystemClock.uptimeMillis()
+                    - sService.mConstants.TOP_TO_FGS_GRACE_DURATION);
+            sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
+            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+            // Procstate should be lower than FGS. (It should be SERVICE)
+            assertEquals(app.mState.getSetProcState(), PROCESS_STATE_SERVICE);
+
+            // Shouldn't have the network capability now.
+            assertTrue((app.mState.getSetCapability() & PROCESS_CAPABILITY_NETWORK) == 0);
+        }
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_DoOne_OverlayUi() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
@@ -453,7 +531,7 @@
     public void testUpdateOomAdj_DoOne_PerceptibleRecent_FgService() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        app.mServices.setHasForegroundServices(true, 0);
+        app.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         app.mState.setLastTopTime(SystemClock.uptimeMillis());
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -482,7 +560,7 @@
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
             sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-            assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+            assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
         }
 
         // Out of grace period but valid binding allows the adjustment.
@@ -495,14 +573,14 @@
             app.mState.setLastTopTime(nowUptime);
             // Simulate the system starting and binding to a service in the app.
             ServiceRecord s = bindService(app, system,
-                    null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
+                    null, Context.BIND_ALMOST_PERCEPTIBLE + 2, mock(IBinder.class));
             s.lastTopAlmostPerceptibleBindRequestUptimeMs =
                     nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
             app.mServices.updateHasTopStartedAlmostPerceptibleServices();
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
             sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-            assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+            assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
         }
 
         // Out of grace period and no valid binding so no adjustment.
@@ -523,7 +601,7 @@
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
             sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-            assertNotEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+            assertNotEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
         }
     }
     @SuppressWarnings("GuardedBy")
@@ -876,7 +954,7 @@
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, 0, mock(IBinder.class));
-        client.mServices.setHasForegroundServices(true, 0);
+        client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
 
@@ -964,7 +1042,7 @@
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
             sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-            assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ, app.mState.getSetAdj());
+            assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
         }
 
         {
@@ -980,7 +1058,7 @@
             sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
             doReturn(false).when(wpc).isHeavyWeightProcess();
 
-            assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ, app.mState.getSetAdj());
+            assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
         }
     }
 
@@ -1068,7 +1146,7 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
-        client.mServices.setHasForegroundServices(true, 0);
+        client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindProvider(app, client, null, null, false);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1137,7 +1215,7 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(app, client2, null, 0, mock(IBinder.class));
-        client2.mServices.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
 
@@ -1156,7 +1234,7 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(client, client2, null, 0, mock(IBinder.class));
-        client2.mServices.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
 
@@ -1175,7 +1253,7 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(client, client2, null, 0, mock(IBinder.class));
-        client2.mServices.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(client2, app, null, 0, mock(IBinder.class));
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
@@ -1192,7 +1270,7 @@
         assertProcStates(client2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
 
-        client2.mServices.setHasForegroundServices(false, 0);
+        client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(client2, OomAdjuster.OOM_ADJ_REASON_NONE);
 
@@ -1213,7 +1291,7 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(client2, client, null, 0, mock(IBinder.class));
-        client.mServices.setHasForegroundServices(true, 0);
+        client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app);
@@ -1242,7 +1320,7 @@
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(client, client2, null, 0, mock(IBinder.class));
         bindService(client2, client, null, 0, mock(IBinder.class));
-        client.mServices.setHasForegroundServices(true, 0);
+        client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app);
@@ -1278,7 +1356,7 @@
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
         bindService(client3, client4, null, 0, mock(IBinder.class));
         bindService(client4, client3, null, 0, mock(IBinder.class));
-        client.mServices.setHasForegroundServices(true, 0);
+        client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app);
@@ -1312,7 +1390,7 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(client, client2, null, 0, mock(IBinder.class));
-        client2.mServices.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(client2, app, null, 0, mock(IBinder.class));
         ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
                 MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
@@ -1416,7 +1494,7 @@
         bindService(app, client3, null, 0, mock(IBinder.class));
         ProcessRecord client4 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
-        client4.mServices.setHasForegroundServices(true, 0);
+        client4.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(app, client4, null, 0, mock(IBinder.class));
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
@@ -1445,7 +1523,7 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(app, client2, null, 0, mock(IBinder.class));
-        client2.mServices.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
                 MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
         client3.mState.setForcingToImportant(new Object());
@@ -1468,7 +1546,7 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindProvider(client, client2, null, null, false);
-        client2.mServices.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
 
@@ -1487,7 +1565,7 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindProvider(client, client2, null, null, false);
-        client2.mServices.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(client2, app, null, 0, mock(IBinder.class));
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
@@ -1512,7 +1590,7 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindProvider(client, client2, null, null, false);
-        client2.mServices.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
 
@@ -1531,7 +1609,7 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindProvider(client, client2, null, null, false);
-        client2.mServices.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindProvider(client2, app, null, null, false);
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
@@ -1562,7 +1640,7 @@
         bindService(app2, client2, null, Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                 mock(IBinder.class));
         client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
-        client2.mServices.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
 
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -1629,7 +1707,7 @@
         s2.getConnections().clear();
         client1.mState.setMaxAdj(UNKNOWN_ADJ);
         client2.mState.setMaxAdj(UNKNOWN_ADJ);
-        client1.mServices.setHasForegroundServices(true, 0);
+        client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         client2.mState.setHasOverlayUi(true);
 
         bindService(app1, client1, s1, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
@@ -1657,6 +1735,46 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoOne_BindNotPerceptibleFGS() {
+        final ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        final ProcessRecord client1 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+                MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+        client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+
+        app1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
+        bindService(app1, client1, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
+
+        sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+        assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+                SCHED_GROUP_DEFAULT);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testUpdateOomAdj_DoOne_BindAlmostPerceptibleFGS() {
+        final ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        final ProcessRecord client1 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+                MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+        client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+
+        app1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
+        bindService(app1, client1, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
+
+        sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+        assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+                SCHED_GROUP_DEFAULT);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_UidIdle_StopService() {
         final ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
@@ -1678,7 +1796,7 @@
         client1.setUidRecord(clientUidRecord);
         client2.setUidRecord(clientUidRecord);
 
-        client1.mServices.setHasForegroundServices(true, 0);
+        client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         client2.mState.setForcingToImportant(new Object());
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
@@ -1735,7 +1853,7 @@
             assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app2.mState.getSetProcState());
             assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, client2.mState.getSetProcState());
 
-            client1.mServices.setHasForegroundServices(false, 0);
+            client1.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
             client2.mState.setForcingToImportant(null);
             app1UidRecord.reset();
             app2UidRecord.reset();
@@ -1772,7 +1890,7 @@
         app.mState.setForcingToImportant(new Object());
         ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
-        app2.mServices.setHasForegroundServices(true, 0);
+        app2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app);
@@ -1795,7 +1913,7 @@
         app.mState.setForcingToImportant(new Object());
         ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
-        app2.mServices.setHasForegroundServices(true, 0);
+        app2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(app, app2, null, 0, mock(IBinder.class));
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
@@ -1822,7 +1940,7 @@
         ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(app2, app3, null, 0, mock(IBinder.class));
-        app3.mServices.setHasForegroundServices(true, 0);
+        app3.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(app3, app, null, 0, mock(IBinder.class));
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
@@ -1870,7 +1988,7 @@
         bindService(app, app4, s, 0, mock(IBinder.class));
         ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
-        app5.mServices.setHasForegroundServices(true, 0);
+        app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(app, app5, s, 0, mock(IBinder.class));
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
@@ -1915,7 +2033,7 @@
         bindService(app, app4, s, 0, mock(IBinder.class));
         ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
-        app5.mServices.setHasForegroundServices(true, 0);
+        app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(app, app5, s, 0, mock(IBinder.class));
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
@@ -1960,7 +2078,7 @@
         bindService(app, app4, s, 0, mock(IBinder.class));
         ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
-        app5.mServices.setHasForegroundServices(true, 0);
+        app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(app, app5, s, 0, mock(IBinder.class));
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
@@ -2035,7 +2153,7 @@
         bindProvider(app, app4, cr, null, false);
         ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
-        app5.mServices.setHasForegroundServices(true, 0);
+        app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindProvider(app, app5, cr, null, false);
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
@@ -2282,7 +2400,8 @@
         services.setConnectionGroup(connectionGroup);
         services.setConnectionImportance(connectionImportance);
         services.setHasClientActivities(hasClientActivities);
-        services.setHasForegroundServices(hasForegroundServices, fgServiceTypes);
+        services.setHasForegroundServices(hasForegroundServices, fgServiceTypes,
+                /* hasNoneType=*/false);
         services.setHasAboveClient(hasAboveClient);
         services.setTreatLikeActivity(treatLikeActivity);
         services.setExecServicesFg(execServicesFg);
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index d78f6d83..d3fa92c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -17,8 +17,14 @@
 package com.android.server.app;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.server.app.GameManagerService.CANCEL_GAME_LOADING_MODE;
+import static com.android.server.app.GameManagerService.LOADING_BOOST_MAX_DURATION;
+import static com.android.server.app.GameManagerService.SET_GAME_STATE;
+import static com.android.server.app.GameManagerService.WRITE_DELAY_MILLIS;
+import static com.android.server.app.GameManagerService.WRITE_GAME_MODE_INTERVENTION_LIST_FILE;
 import static com.android.server.app.GameManagerService.WRITE_SETTINGS;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -32,6 +38,7 @@
 import static org.mockito.Mockito.any;
 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;
@@ -39,8 +46,10 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.app.GameManager;
+import android.app.GameModeConfiguration;
 import android.app.GameModeInfo;
 import android.app.GameState;
+import android.app.IGameModeListener;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -54,13 +63,15 @@
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.hardware.power.Mode;
+import android.os.Binder;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.PowerManagerInternal;
+import android.os.RemoteException;
 import android.os.UserManager;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
-import android.util.ArraySet;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -73,7 +84,9 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoSession;
@@ -82,6 +95,7 @@
 import java.io.File;
 import java.nio.file.Files;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.function.Supplier;
@@ -95,7 +109,8 @@
     private static final String PACKAGE_NAME_INVALID = "com.android.app";
     private static final int USER_ID_1 = 1001;
     private static final int USER_ID_2 = 1002;
-    private static final int DEFAULT_PACKAGE_UID = 12345;
+    // to pass the valid package check in some of the server methods
+    private static final int DEFAULT_PACKAGE_UID = Binder.getCallingUid();
 
     private MockitoSession mMockingSession;
     private String mPackageName;
@@ -108,6 +123,9 @@
     private UserManager mMockUserManager;
     private BroadcastReceiver mShutDownActionReceiver;
 
+    @Captor
+    ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor;
+
     // Stolen from ConnectivityServiceTest.MockContext
     class MockContext extends ContextWrapper {
         private static final String TAG = "MockContext";
@@ -178,7 +196,7 @@
 
         @Override
         public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) {
-            mShutDownActionReceiver =  receiver;
+            mShutDownActionReceiver = receiver;
             return null;
         }
     }
@@ -193,28 +211,33 @@
                 .startMocking();
         mMockContext = new MockContext(InstrumentationRegistry.getContext());
         mPackageName = mMockContext.getPackageName();
-        final ApplicationInfo applicationInfo = new ApplicationInfo();
-        applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
-        applicationInfo.packageName = mPackageName;
-        final PackageInfo pi = new PackageInfo();
-        pi.packageName = mPackageName;
-        pi.applicationInfo = applicationInfo;
-        final List<PackageInfo> packages = new ArrayList<>();
-        packages.add(pi);
-
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
         final Resources resources =
                 InstrumentationRegistry.getInstrumentation().getContext().getResources();
         when(mMockPackageManager.getResourcesForApplication(anyString()))
                 .thenReturn(resources);
-        when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
-                .thenReturn(packages);
-        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
-                .thenReturn(applicationInfo);
         when(mMockPackageManager.getPackageUidAsUser(mPackageName, USER_ID_1)).thenReturn(
                 DEFAULT_PACKAGE_UID);
         LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
     }
 
+    private void mockAppCategory(String packageName, @ApplicationInfo.Category int category)
+            throws Exception {
+        reset(mMockPackageManager);
+        final ApplicationInfo gameApplicationInfo = new ApplicationInfo();
+        gameApplicationInfo.category = category;
+        gameApplicationInfo.packageName = packageName;
+        final PackageInfo pi = new PackageInfo();
+        pi.packageName = packageName;
+        pi.applicationInfo = gameApplicationInfo;
+        final List<PackageInfo> packages = new ArrayList<>();
+        packages.add(pi);
+        when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
+                .thenReturn(packages);
+        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(gameApplicationInfo);
+    }
+
     @After
     public void tearDown() throws Exception {
         LocalServices.removeServiceForTest(PowerManagerInternal.class);
@@ -387,6 +410,17 @@
                 .thenReturn(applicationInfo);
     }
 
+    private void mockInterventionsEnabledBatteryOptInFromXml() throws Exception {
+        seedGameManagerServiceMetaDataFromFile(mPackageName, 123, "res/xml/"
+                + "game_manager_service_metadata_config_interventions_enabled_battery_opt_in.xml");
+    }
+
+    private void mockInterventionsEnabledPerformanceOptInFromXml() throws Exception {
+        seedGameManagerServiceMetaDataFromFile(mPackageName, 123, "res/xml/"
+                + "game_manager_service_metadata_config_interventions_enabled_performance_opt_in"
+                + ".xml");
+    }
+
     private void mockInterventionsEnabledNoOptInFromXml() throws Exception {
         seedGameManagerServiceMetaDataFromFile(mPackageName, 123,
                 "res/xml/game_manager_service_metadata_config_interventions_enabled_no_opt_in.xml");
@@ -431,16 +465,21 @@
     }
 
     /**
-     * By default game mode is not supported.
+     * By default game mode is set to STANDARD
      */
     @Test
-    public void testGameModeDefaultValue() {
-        GameManagerService gameManagerService =
-                new GameManagerService(mMockContext, mTestLooper.getLooper());
-
-        startUser(gameManagerService, USER_ID_1);
+    public void testGetGameMode_defaultValue() {
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
         mockModifyGameModeGranted();
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+    }
 
+    @Test
+    public void testGetGameMode_nonGame() throws Exception {
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        mockModifyGameModeGranted();
         assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
                 gameManagerService.getGameMode(mPackageName, USER_ID_1));
     }
@@ -457,7 +496,7 @@
         mockModifyGameModeGranted();
 
         gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_2);
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+        assertEquals(GameManager.GAME_MODE_STANDARD,
                 gameManagerService.getGameMode(mPackageName, USER_ID_2));
     }
 
@@ -473,7 +512,7 @@
         startUser(gameManagerService, USER_ID_1);
         gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
         mockModifyGameModeGranted();
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+        assertEquals(GameManager.GAME_MODE_STANDARD,
                 gameManagerService.getGameMode(mPackageName, USER_ID_1));
         // We need to make sure the mode is supported before setting it.
         mockDeviceConfigAll();
@@ -580,31 +619,47 @@
                 gameManagerService.getGameMode(mPackageName, USER_ID_2));
     }
 
-    private void checkReportedModes(GameManagerService gameManagerService, int ...requiredModes) {
-        if (gameManagerService == null) {
-            gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper());
-            startUser(gameManagerService, USER_ID_1);
-            gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
-        }
-        ArraySet<Integer> reportedModes = new ArraySet<>();
-        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
-        for (int mode : modes) {
-            reportedModes.add(mode);
-        }
-        assertEquals(requiredModes.length, reportedModes.size());
-        for (int requiredMode : requiredModes) {
-            assertTrue("Required game mode not supported: " + requiredMode,
-                    reportedModes.contains(requiredMode));
+    private GameManagerService createServiceAndStartUser(int userId) {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext,
+                mTestLooper.getLooper());
+        startUser(gameManagerService, userId);
+        return gameManagerService;
+    }
+
+    private void checkReportedAvailableGameModes(GameManagerService gameManagerService,
+            int... requiredAvailableModes) {
+        Arrays.sort(requiredAvailableModes);
+        // check getAvailableGameModes
+        int[] reportedAvailableModes = gameManagerService.getAvailableGameModes(mPackageName,
+                USER_ID_1);
+        Arrays.sort(reportedAvailableModes);
+        assertArrayEquals(requiredAvailableModes, reportedAvailableModes);
+
+        // check GetModeInfo.getAvailableGameModes
+        GameModeInfo info = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        if (requiredAvailableModes.length == 0) {
+            assertNull(info);
+        } else {
+            assertNotNull(info);
+            reportedAvailableModes = info.getAvailableGameModes();
+            Arrays.sort(reportedAvailableModes);
+            assertArrayEquals(requiredAvailableModes, reportedAvailableModes);
         }
     }
 
+    private void checkReportedOverriddenGameModes(GameManagerService gameManagerService,
+            int... requiredOverriddenModes) {
+        Arrays.sort(requiredOverriddenModes);
+        // check GetModeInfo.getOverriddenGameModes
+        GameModeInfo info = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertNotNull(info);
+        int[] overriddenModes = info.getOverriddenGameModes();
+        Arrays.sort(overriddenModes);
+        assertArrayEquals(requiredOverriddenModes, overriddenModes);
+    }
+
     private void checkDownscaling(GameManagerService gameManagerService,
-                int gameMode, float scaling) {
-        if (gameManagerService == null) {
-            gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper());
-            startUser(gameManagerService, USER_ID_1);
-            gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
-        }
+            int gameMode, float scaling) {
         GameManagerService.GamePackageConfiguration config =
                 gameManagerService.getConfig(mPackageName, USER_ID_1);
         assertEquals(scaling, config.getGameModeConfiguration(gameMode).getScaling(), 0.01f);
@@ -625,8 +680,6 @@
 
     private void checkLoadingBoost(GameManagerService gameManagerService, int gameMode,
             int loadingBoost) {
-        gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
-
         // Validate GamePackageConfiguration returns the correct value.
         GameManagerService.GamePackageConfiguration config =
                 gameManagerService.getConfig(mPackageName, USER_ID_1);
@@ -639,17 +692,12 @@
     }
 
     private void checkFps(GameManagerService gameManagerService, int gameMode, int fps) {
-        if (gameManagerService == null) {
-            gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper());
-            startUser(gameManagerService, USER_ID_1);
-            gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
-        }
         GameManagerService.GamePackageConfiguration config =
                 gameManagerService.getConfig(mPackageName, USER_ID_1);
         assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
     }
 
-    private boolean checkOptedIn(GameManagerService gameManagerService, int gameMode) {
+    private boolean checkOverridden(GameManagerService gameManagerService, int gameMode) {
         GameManagerService.GamePackageConfiguration config =
                 gameManagerService.getConfig(mPackageName, USER_ID_1);
         return config.willGamePerformOptimizations(gameMode);
@@ -662,7 +710,8 @@
     public void testDeviceConfigDefault() {
         mockDeviceConfigDefault();
         mockModifyGameModeGranted();
-        checkReportedModes(null);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -672,38 +721,8 @@
     public void testDeviceConfigNone() {
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
-        checkReportedModes(null);
-    }
-
-    /**
-     * Phenotype device config for performance mode exists and is valid.
-     */
-    @Test
-    public void testDeviceConfigPerformance() {
-        mockDeviceConfigPerformance();
-        mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD);
-    }
-
-    /**
-     * Phenotype device config for battery mode exists and is valid.
-     */
-    @Test
-    public void testDeviceConfigBattery() {
-        mockDeviceConfigBattery();
-        mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD);
-    }
-
-    /**
-     * Phenotype device configs for both battery and performance modes exists and are valid.
-     */
-    @Test
-    public void testDeviceConfigAll() {
-        mockDeviceConfigAll();
-        mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -713,7 +732,8 @@
     public void testDeviceConfigInvalid() {
         mockDeviceConfigInvalid();
         mockModifyGameModeGranted();
-        checkReportedModes(null);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -723,7 +743,16 @@
     public void testDeviceConfigMalformed() {
         mockDeviceConfigMalformed();
         mockModifyGameModeGranted();
-        checkReportedModes(null);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
+    }
+
+    @Test
+    public void testDeviceConfig_nonGame() throws Exception {
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+        mockDeviceConfigAll();
+        mockModifyGameModeGranted();
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1));
     }
 
     /**
@@ -740,8 +769,8 @@
         gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                 GameManager.GAME_MODE_PERFORMANCE, "120", "0.3");
 
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.3f);
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120);
     }
@@ -760,8 +789,8 @@
         gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                 GameManager.GAME_MODE_BATTERY, "60", "0.5");
 
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.5f);
         checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 60);
     }
@@ -782,8 +811,9 @@
         gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                 GameManager.GAME_MODE_BATTERY, "60", "0.5");
 
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
-                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.3f);
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.5f);
@@ -840,8 +870,8 @@
                 mTestLooper.getLooper());
         startUser(gameManagerService, USER_ID_1);
 
-        assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
-        assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_BATTERY));
+        assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
+        assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_BATTERY));
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
 
         gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, 3, "40",
@@ -854,9 +884,9 @@
         mockInterventionsDisabledAllOptInFromXml();
         gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
 
-        assertTrue(checkOptedIn(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
+        assertTrue(checkOverridden(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
         // opt-in is still false for battery mode as override exists
-        assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_BATTERY));
+        assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_BATTERY));
     }
 
     /**
@@ -876,8 +906,8 @@
         gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1,
                 GameManager.GAME_MODE_PERFORMANCE);
 
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.5f);
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
     }
@@ -899,8 +929,8 @@
         gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1,
                 GameManager.GAME_MODE_BATTERY);
 
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.7f);
         checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30);
     }
@@ -923,8 +953,9 @@
 
         gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1, -1);
 
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
-                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.5f);
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.7f);
@@ -952,8 +983,9 @@
         gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1,
                 GameManager.GAME_MODE_BATTERY);
 
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
-                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.3f);
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.7f);
@@ -968,12 +1000,12 @@
         mockGameModeOptInAll();
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
     }
 
 
-
     /**
      * BATTERY game mode is available through the app manifest opt-in.
      */
@@ -982,7 +1014,9 @@
         mockGameModeOptInBattery();
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -993,7 +1027,9 @@
         mockGameModeOptInPerformance();
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -1005,8 +1041,9 @@
         mockGameModeOptInBattery();
         mockDeviceConfigPerformance();
         mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -1018,8 +1055,9 @@
         mockGameModeOptInPerformance();
         mockDeviceConfigBattery();
         mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -1029,7 +1067,8 @@
     public void testInterventionAllowScalingDefault() throws Exception {
         mockDeviceConfigPerformance();
         mockModifyGameModeGranted();
-        checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, 0.5f);
+        checkDownscaling(createServiceAndStartUser(USER_ID_1), GameManager.GAME_MODE_PERFORMANCE,
+                0.5f);
     }
 
     /**
@@ -1040,7 +1079,8 @@
         mockDeviceConfigPerformance();
         mockInterventionAllowDownscaleFalse();
         mockModifyGameModeGranted();
-        checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, -1.0f);
+        checkDownscaling(createServiceAndStartUser(USER_ID_1), GameManager.GAME_MODE_PERFORMANCE,
+                -1.0f);
     }
 
     /**
@@ -1052,7 +1092,8 @@
         mockDeviceConfigPerformance();
         mockInterventionAllowDownscaleTrue();
         mockModifyGameModeGranted();
-        checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, 0.5f);
+        checkDownscaling(createServiceAndStartUser(USER_ID_1), GameManager.GAME_MODE_PERFORMANCE,
+                0.5f);
     }
 
     /**
@@ -1060,12 +1101,9 @@
      */
     @Test
     public void testInterventionAllowAngleDefault() throws Exception {
-        GameManagerService gameManagerService = new GameManagerService(
-                mMockContext, mTestLooper.getLooper());
-
-        startUser(gameManagerService, USER_ID_1);
         mockDeviceConfigPerformance();
         mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
         checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, false);
     }
 
@@ -1075,12 +1113,9 @@
      */
     @Test
     public void testInterventionAllowLoadingBoostDefault() throws Exception {
-        GameManagerService gameManagerService = new GameManagerService(
-                mMockContext, mTestLooper.getLooper());
-
-        startUser(gameManagerService, USER_ID_1);
         mockDeviceConfigPerformance();
         mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
         checkLoadingBoost(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, -1);
     }
 
@@ -1089,12 +1124,10 @@
      */
     @Test
     public void testInterventionAllowAngleFalse() throws Exception {
-        GameManagerService gameManagerService =
-                new GameManagerService(mMockContext, mTestLooper.getLooper());
-        startUser(gameManagerService, USER_ID_1);
         mockDeviceConfigPerformanceEnableAngle();
         mockInterventionAllowAngleFalse();
         mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
         checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, false);
     }
 
@@ -1171,8 +1204,9 @@
     public void testInterventionFps() throws Exception {
         mockDeviceConfigAll();
         mockModifyGameModeGranted();
-        checkFps(null, GameManager.GAME_MODE_PERFORMANCE, 90);
-        checkFps(null, GameManager.GAME_MODE_BATTERY, 30);
+        GameManagerService service = createServiceAndStartUser(USER_ID_1);
+        checkFps(service, GameManager.GAME_MODE_PERFORMANCE, 90);
+        checkFps(service, GameManager.GAME_MODE_BATTERY, 30);
     }
 
     /**
@@ -1195,7 +1229,7 @@
 
     /**
      * Ensure that, if a game no longer supports any game modes, we set the game mode to
-     * UNSUPPORTED
+     * STANDARD
      */
     @Test
     public void testUnsetInvalidGameMode() throws Exception {
@@ -1206,7 +1240,7 @@
         startUser(gameManagerService, USER_ID_1);
         gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
         gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+        assertEquals(GameManager.GAME_MODE_STANDARD,
                 gameManagerService.getGameMode(mPackageName, USER_ID_1));
     }
 
@@ -1247,6 +1281,7 @@
     static {
         System.loadLibrary("mockingservicestestjni");
     }
+
     @Test
     public void testGetGameModeInfoPermissionDenied() {
         mockDeviceConfigAll();
@@ -1261,30 +1296,30 @@
     }
 
     @Test
-    public void testGetGameModeInfoWithAllGameModesDefault() {
-        mockDeviceConfigAll();
-        mockModifyGameModeGranted();
-        GameManagerService gameManagerService =
-                new GameManagerService(mMockContext, mTestLooper.getLooper());
-        startUser(gameManagerService, USER_ID_1);
-        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
-
-        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
-        assertEquals(3, gameModeInfo.getAvailableGameModes().length);
-    }
-
-    @Test
     public void testGetGameModeInfoWithAllGameModes() {
         mockDeviceConfigAll();
         mockModifyGameModeGranted();
         GameManagerService gameManagerService =
                 new GameManagerService(mMockContext, mTestLooper.getLooper());
         startUser(gameManagerService, USER_ID_1);
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
         GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
+        assertTrue(gameModeInfo.isDownscalingAllowed());
+        assertTrue(gameModeInfo.isFpsOverrideAllowed());
 
-        assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameModeInfo.getActiveGameMode());
-        assertEquals(3, gameModeInfo.getAvailableGameModes().length);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
+        checkReportedOverriddenGameModes(gameManagerService);
+
+        assertEquals(new GameModeConfiguration.Builder()
+                .setFpsOverride(30)
+                .setScalingFactor(0.7f)
+                .build(), gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+        assertEquals(new GameModeConfiguration.Builder()
+                .setFpsOverride(90)
+                .setScalingFactor(0.5f)
+                .build(), gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
     }
 
     @Test
@@ -1298,7 +1333,14 @@
         GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
 
         assertEquals(GameManager.GAME_MODE_BATTERY, gameModeInfo.getActiveGameMode());
-        assertEquals(2, gameModeInfo.getAvailableGameModes().length);
+
+        checkReportedAvailableGameModes(gameManagerService,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
+        checkReportedOverriddenGameModes(gameManagerService);
+
+        assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
     }
 
     @Test
@@ -1312,11 +1354,17 @@
         GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
 
         assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameModeInfo.getActiveGameMode());
-        assertEquals(2, gameModeInfo.getAvailableGameModes().length);
+        checkReportedAvailableGameModes(gameManagerService,
+                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
+        checkReportedOverriddenGameModes(gameManagerService);
+
+        assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
     }
 
     @Test
-    public void testGetGameModeInfoWithUnsupportedGameMode() {
+    public void testGetGameModeInfoWithDefaultGameModes() {
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
         GameManagerService gameManagerService =
@@ -1324,52 +1372,198 @@
         startUser(gameManagerService, USER_ID_1);
         GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
 
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameModeInfo.getActiveGameMode());
-        assertEquals(0, gameModeInfo.getAvailableGameModes().length);
+        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_STANDARD));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
     }
 
     @Test
-    public void testGameStateLoadingRequiresPerformanceMode() {
+    public void testGetGameModeInfoWithAllGameModesOverridden_noDeviceConfig()
+            throws Exception {
+        mockModifyGameModeGranted();
+        mockInterventionsEnabledAllOptInFromXml();
+        mockDeviceConfigNone();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
+        verifyAllModesOverriddenAndInterventionsAvailable(gameManagerService, gameModeInfo);
+
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+    }
+
+    @Test
+    public void testGetGameModeInfoWithAllGameModesOverridden_allDeviceConfig()
+            throws Exception {
+        mockModifyGameModeGranted();
+        mockInterventionsEnabledAllOptInFromXml();
+        mockDeviceConfigAll();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
+        verifyAllModesOverriddenAndInterventionsAvailable(gameManagerService, gameModeInfo);
+
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+    }
+
+    private void verifyAllModesOverriddenAndInterventionsAvailable(
+            GameManagerService gameManagerService,
+            GameModeInfo gameModeInfo) {
+        checkReportedAvailableGameModes(gameManagerService,
+                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
+        checkReportedOverriddenGameModes(gameManagerService,
+                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY);
+        assertTrue(gameModeInfo.isFpsOverrideAllowed());
+        assertTrue(gameModeInfo.isDownscalingAllowed());
+    }
+
+    @Test
+    public void testGetGameModeInfoWithBatteryModeOverridden_withBatteryDeviceConfig()
+            throws Exception {
+        mockModifyGameModeGranted();
+        mockInterventionsEnabledBatteryOptInFromXml();
+        mockDeviceConfigBattery();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
+
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
+        checkReportedOverriddenGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY);
+
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+    }
+
+    @Test
+    public void testGetGameModeInfoWithPerformanceModeOverridden_withAllDeviceConfig()
+            throws Exception {
+        mockModifyGameModeGranted();
+        mockInterventionsEnabledPerformanceOptInFromXml();
+        mockDeviceConfigAll();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
+
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
+        checkReportedOverriddenGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE);
+
+        assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+    }
+
+    @Test
+    public void testGetGameModeInfoWithInterventionsDisabled() throws Exception {
+        mockModifyGameModeGranted();
+        mockInterventionsDisabledAllOptInFromXml();
+        mockDeviceConfigAll();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertFalse(gameModeInfo.isFpsOverrideAllowed());
+        assertFalse(gameModeInfo.isDownscalingAllowed());
+    }
+
+    @Test
+    public void testSetGameState_loadingRequiresPerformanceMode() {
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
-        GameManagerService gameManagerService =
-                new GameManagerService(mMockContext, mTestLooper.getLooper());
-        startUser(gameManagerService, USER_ID_1);
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
         GameState gameState = new GameState(true, GameState.MODE_NONE);
         gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
         mTestLooper.dispatchAll();
         verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
     }
 
-    private void setGameState(boolean isLoading) {
+    @Test
+    public void testSetGameStateLoading_withNoDeviceConfig() {
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
-        GameManagerService gameManagerService =
-                new GameManagerService(mMockContext, mTestLooper.getLooper());
-        startUser(gameManagerService, USER_ID_1);
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
         gameManagerService.setGameMode(
                 mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
-        int testMode = GameState.MODE_NONE;
+        assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
+                GameManager.GAME_MODE_PERFORMANCE);
+        int testMode = GameState.MODE_GAMEPLAY_INTERRUPTIBLE;
         int testLabel = 99;
         int testQuality = 123;
-        GameState gameState = new GameState(isLoading, testMode, testLabel, testQuality);
-        assertEquals(isLoading, gameState.isLoading());
+        GameState gameState = new GameState(true, testMode, testLabel, testQuality);
         assertEquals(testMode, gameState.getMode());
         assertEquals(testLabel, gameState.getLabel());
         assertEquals(testQuality, gameState.getQuality());
         gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
         mTestLooper.dispatchAll();
-        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, isLoading);
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+        reset(mMockPowerManager);
+        assertTrue(
+                gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME_LOADING, false);
+        mTestLooper.moveTimeForward(GameManagerService.LOADING_BOOST_MAX_DURATION);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
     }
 
     @Test
-    public void testSetGameStateLoading() {
-        setGameState(true);
+    public void testSetGameStateLoading_withDeviceConfig() {
+        String configString = "mode=2,loadingBoost=2000";
+        when(DeviceConfig.getProperty(anyString(), anyString()))
+                .thenReturn(configString);
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.setGameMode(
+                mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        GameState gameState = new GameState(true, GameState.MODE_GAMEPLAY_INTERRUPTIBLE, 99, 123);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME_LOADING, false);
+        reset(mMockPowerManager);
+        assertTrue(
+                gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+        mTestLooper.moveTimeForward(2000);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
     }
 
     @Test
     public void testSetGameStateNotLoading() {
-        setGameState(false);
+        mockDeviceConfigNone();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.setGameMode(
+                mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        int testMode = GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE;
+        int testLabel = 99;
+        int testQuality = 123;
+        GameState gameState = new GameState(false, testMode, testLabel, testQuality);
+        assertFalse(gameState.isLoading());
+        assertEquals(testMode, gameState.getMode());
+        assertEquals(testLabel, gameState.getLabel());
+        assertEquals(testQuality, gameState.getQuality());
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        assertTrue(gameManagerService.mHandler.hasEqualMessages(SET_GAME_STATE, gameState));
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+        assertFalse(
+                gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+    }
+
+    @Test
+    public void testSetGameState_nonGame() throws Exception {
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+        mockDeviceConfigNone();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameState gameState = new GameState(true, GameState.MODE_NONE);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
     }
 
     private List<String> readGameModeInterventionList() throws Exception {
@@ -1381,7 +1575,7 @@
     }
 
     private void mockInterventionListForMultipleUsers() {
-        final String[] packageNames = new String[] {"com.android.app0",
+        final String[] packageNames = new String[]{"com.android.app0",
                 "com.android.app1", "com.android.app2"};
 
         final ApplicationInfo[] applicationInfos = new ApplicationInfo[3];
@@ -1424,8 +1618,8 @@
         final Context context = InstrumentationRegistry.getContext();
         GameManagerService gameManagerService =
                 new GameManagerService(mMockContext,
-                                       mTestLooper.getLooper(),
-                                       context.getFilesDir());
+                        mTestLooper.getLooper(),
+                        context.getFilesDir());
         startUser(gameManagerService, USER_ID_1);
         startUser(gameManagerService, USER_ID_2);
 
@@ -1437,14 +1631,14 @@
 
         /* Expected fileOutput (order may vary)
          # user 1001:
-         com.android.app2 <UID>   0   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.5,fps=60
+         com.android.app2 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.5,fps=60
          com.android.app1 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
-         com.android.app0 <UID>   0   2   angle=0,scaling=0.6,fps=120 3   angle=0,scaling=0.7,fps=30
+         com.android.app0 <UID>   1   2   angle=0,scaling=0.6,fps=120 3   angle=0,scaling=0.7,fps=30
 
          # user 1002:
-         com.android.app2 <UID>   0   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
+         com.android.app2 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
          com.android.app1 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
-         com.android.app0 <UID>   0   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
+         com.android.app0 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
          The current game mode would only be set to non-zero if the current user have that game
          installed.
         */
@@ -1461,7 +1655,7 @@
         assertEquals(splitLine[6], "angle=0,scaling=0.5,fps=60");
         splitLine = fileOutput.get(1).split("\\s+");
         assertEquals(splitLine[0], "com.android.app1");
-        assertEquals(splitLine[2], "0");
+        assertEquals(splitLine[2], "1");
         assertEquals(splitLine[3], "2");
         assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
         assertEquals(splitLine[5], "3");
@@ -1484,7 +1678,7 @@
 
         splitLine = fileOutput.get(0).split("\\s+");
         assertEquals(splitLine[0], "com.android.app2");
-        assertEquals(splitLine[2], "0");
+        assertEquals(splitLine[2], "1");
         assertEquals(splitLine[3], "2");
         assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
         assertEquals(splitLine[5], "3");
@@ -1498,7 +1692,7 @@
         assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
         splitLine = fileOutput.get(2).split("\\s+");
         assertEquals(splitLine[0], "com.android.app0");
-        assertEquals(splitLine[2], "0");
+        assertEquals(splitLine[2], "1");
         assertEquals(splitLine[3], "2");
         assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
         assertEquals(splitLine[5], "3");
@@ -1507,6 +1701,39 @@
     }
 
     @Test
+    public void testSwitchUser() {
+        mockManageUsersGranted();
+        mockModifyGameModeGranted();
+
+        mockDeviceConfigBattery();
+        final Context context = InstrumentationRegistry.getContext();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext,
+                mTestLooper.getLooper(), context.getFilesDir());
+        startUser(gameManagerService, USER_ID_1);
+        startUser(gameManagerService, USER_ID_2);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_CUSTOM);
+        assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
+                GameManager.GAME_MODE_BATTERY);
+
+        mockDeviceConfigAll();
+        switchUser(gameManagerService, USER_ID_1, USER_ID_2);
+        assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_2),
+                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_CUSTOM);
+
+        switchUser(gameManagerService, USER_ID_2, USER_ID_1);
+        assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
+                GameManager.GAME_MODE_BATTERY);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_CUSTOM);
+    }
+
+    @Test
     public void testUpdateResolutionScalingFactor() {
         mockModifyGameModeGranted();
         mockDeviceConfigBattery();
@@ -1607,6 +1834,86 @@
     }
 
     @Test
+    public void testUpdateCustomGameModeConfiguration_permissionDenied() {
+        mockModifyGameModeDenied();
+        mockDeviceConfigAll();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        assertThrows(SecurityException.class, () -> {
+            gameManagerService.updateCustomGameModeConfiguration(mPackageName,
+                    new GameModeConfiguration.Builder().setScalingFactor(0.5f).build(),
+                    USER_ID_1);
+        });
+    }
+
+    @Test
+    public void testUpdateCustomGameModeConfiguration_noUserId() {
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_2);
+        assertThrows(IllegalArgumentException.class, () -> {
+            gameManagerService.updateCustomGameModeConfiguration(mPackageName,
+                    new GameModeConfiguration.Builder().setScalingFactor(0.5f).build(),
+                    USER_ID_1);
+        });
+    }
+
+    @Test
+    public void testUpdateCustomGameModeConfiguration_nonGame() throws Exception {
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.updateCustomGameModeConfiguration(mPackageName,
+                new GameModeConfiguration.Builder().setScalingFactor(0.35f).setFpsOverride(
+                        60).build(),
+                USER_ID_1);
+        assertFalse(gameManagerService.mHandler.hasMessages(WRITE_SETTINGS));
+        GameManagerService.GamePackageConfiguration pkgConfig = gameManagerService.getConfig(
+                mPackageName, USER_ID_1);
+        assertNull(pkgConfig);
+    }
+
+    @Test
+    public void testUpdateCustomGameModeConfiguration() throws InterruptedException {
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.updateCustomGameModeConfiguration(mPackageName,
+                new GameModeConfiguration.Builder().setScalingFactor(0.35f).setFpsOverride(
+                        60).build(),
+                USER_ID_1);
+
+        assertTrue(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
+        assertTrue(
+                gameManagerService.mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
+                        USER_ID_1));
+
+        GameManagerService.GamePackageConfiguration pkgConfig = gameManagerService.getConfig(
+                mPackageName, USER_ID_1);
+        assertNotNull(pkgConfig);
+        GameManagerService.GamePackageConfiguration.GameModeConfiguration modeConfig =
+                pkgConfig.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM);
+        assertNotNull(modeConfig);
+        assertEquals(modeConfig.getScaling(), 0.35f, 0.01f);
+        assertEquals(modeConfig.getFps(), 60);
+        // creates a new service to check that no data has been stored
+        mTestLooper.dispatchAll();
+        gameManagerService = createServiceAndStartUser(USER_ID_1);
+        pkgConfig = gameManagerService.getConfig(mPackageName, USER_ID_1);
+        assertNull(pkgConfig);
+
+        mTestLooper.moveTimeForward(WRITE_DELAY_MILLIS + 500);
+        mTestLooper.dispatchAll();
+        // creates a new service to check that data is persisted after delay
+        gameManagerService = createServiceAndStartUser(USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+        pkgConfig = gameManagerService.getConfig(mPackageName, USER_ID_1);
+        assertNotNull(pkgConfig);
+        modeConfig = pkgConfig.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM);
+        assertNotNull(modeConfig);
+        assertEquals(modeConfig.getScaling(), 0.35f, 0.01f);
+        assertEquals(modeConfig.getFps(), 60);
+    }
+
+    @Test
     public void testWritingSettingFile_onShutdown() throws InterruptedException {
         mockModifyGameModeGranted();
         mockDeviceConfigAll();
@@ -1686,7 +1993,7 @@
     }
 
     @Test
-    public void testResetInterventions_onGameModeOptedIn() throws Exception {
+    public void testResetInterventions_onGameModeOverridden() throws Exception {
         mockModifyGameModeGranted();
         String configStringBefore =
                 "mode=2,downscaleFactor=1.0,fps=90";
@@ -1710,6 +2017,53 @@
                 ArgumentMatchers.eq(0.0f));
     }
 
+    @Test
+    public void testAddGameModeListener() throws RemoteException {
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        mockDeviceConfigAll();
+        startUser(gameManagerService, USER_ID_1);
+        mockModifyGameModeGranted();
+
+        IGameModeListener mockListener = Mockito.mock(IGameModeListener.class);
+        IBinder binder = Mockito.mock(IBinder.class);
+        when(mockListener.asBinder()).thenReturn(binder);
+        gameManagerService.addGameModeListener(mockListener);
+        verify(binder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
+
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        verify(mockListener).onGameModeChanged(mPackageName, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        reset(mockListener);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+        verify(mockListener).onGameModeChanged(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_BATTERY, USER_ID_1);
+        reset(mockListener);
+
+        mDeathRecipientCaptor.getValue().binderDied();
+        verify(binder).unlinkToDeath(eq(mDeathRecipientCaptor.getValue()), anyInt());
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_CUSTOM, USER_ID_1);
+        verify(mockListener, never()).onGameModeChanged(anyString(), anyInt(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void testRemoveGameModeListener() throws RemoteException {
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        mockDeviceConfigAll();
+        startUser(gameManagerService, USER_ID_1);
+        mockModifyGameModeGranted();
+
+        IGameModeListener mockListener = Mockito.mock(IGameModeListener.class);
+        IBinder binder = Mockito.mock(IBinder.class);
+        when(mockListener.asBinder()).thenReturn(binder);
+
+        gameManagerService.addGameModeListener(mockListener);
+        gameManagerService.removeGameModeListener(mockListener);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        verify(mockListener, never()).onGameModeChanged(anyString(), anyInt(), anyInt(), anyInt());
+    }
+
     private static void deleteFolder(File folder) {
         File[] files = folder.listFiles();
         if (files != null) {
@@ -1719,4 +2073,111 @@
         }
         folder.delete();
     }
+
+    @Test
+    public void testNotifyGraphicsEnvironmentSetup() {
+        String configString = "mode=2,loadingBoost=2000";
+        when(DeviceConfig.getProperty(anyString(), anyString()))
+                .thenReturn(configString);
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+        reset(mMockPowerManager);
+        assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+        mTestLooper.moveTimeForward(2000);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+    }
+
+    @Test
+    public void testNotifyGraphicsEnvironmentSetup_outOfBoundBoostValue() {
+        String configString = "mode=2,loadingBoost=0:mode=3,loadingBoost=7000";
+        when(DeviceConfig.getProperty(anyString(), anyString()))
+                .thenReturn(configString);
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+        reset(mMockPowerManager);
+        assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+        mTestLooper.moveTimeForward(100);
+        mTestLooper.dispatchAll();
+        // 0 loading boost value should still trigger max timeout
+        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+        assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+        mTestLooper.moveTimeForward(LOADING_BOOST_MAX_DURATION);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+        reset(mMockPowerManager);
+        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+
+        // 7000 loading boost value should exceed the max timeout of 5s and be bounded
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+        reset(mMockPowerManager);
+        assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+        mTestLooper.moveTimeForward(LOADING_BOOST_MAX_DURATION);
+        mTestLooper.dispatchAll();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+    }
+
+    @Test
+    public void testNotifyGraphicsEnvironmentSetup_noDeviceConfig() {
+        mockDeviceConfigNone();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+    }
+
+    @Test
+    public void testNotifyGraphicsEnvironmentSetup_noLoadingBoostValue() {
+        mockDeviceConfigAll();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+    }
+
+    @Test
+    public void testNotifyGraphicsEnvironmentSetup_nonGame() throws Exception {
+        String configString = "mode=2,loadingBoost=2000";
+        when(DeviceConfig.getProperty(anyString(), anyString()))
+                .thenReturn(configString);
+        mockModifyGameModeGranted();
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+    }
+
+    @Test
+    public void testNotifyGraphicsEnvironmentSetup_differentApp() throws Exception {
+        String configString = "mode=2,loadingBoost=2000";
+        when(DeviceConfig.getProperty(anyString(), anyString()))
+                .thenReturn(configString);
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        String someGamePkg = "some.game";
+        mockAppCategory(someGamePkg, ApplicationInfo.CATEGORY_GAME);
+        when(mMockPackageManager.getPackageUidAsUser(someGamePkg, USER_ID_1)).thenReturn(
+                DEFAULT_PACKAGE_UID + 1);
+        gameManagerService.setGameMode(someGamePkg, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+                gameManagerService.getGameMode(someGamePkg, USER_ID_1));
+        gameManagerService.notifyGraphicsEnvironmentSetup(someGamePkg, USER_ID_1);
+        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
index 5dc1251..be13bad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
@@ -48,7 +48,7 @@
     StaticMockitoSession mSession;
 
     @Mock
-    AppOpsService.Constants mConstants;
+    AppOpsServiceImpl.Constants mConstants;
 
     @Mock
     Context mContext;
@@ -57,7 +57,7 @@
     Handler mHandler;
 
     @Mock
-    AppOpsServiceInterface mLegacyAppOpsService;
+    AppOpsCheckingServiceInterface mLegacyAppOpsService;
 
     AppOpsRestrictions mAppOpsRestrictions;
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index c0688d1..7d4bc6f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -22,6 +22,8 @@
 import static android.app.AppOpsManager.OP_READ_SMS;
 import static android.app.AppOpsManager.OP_WIFI_SCAN;
 import static android.app.AppOpsManager.OP_WRITE_SMS;
+import static android.app.AppOpsManager.resolvePackageName;
+import static android.os.Process.INVALID_UID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -39,6 +41,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 
+import android.app.AppOpsManager;
 import android.app.AppOpsManager.OpEntry;
 import android.app.AppOpsManager.PackageOps;
 import android.content.ContentResolver;
@@ -86,13 +89,13 @@
 
     private File mAppOpsFile;
     private Handler mHandler;
-    private AppOpsService mAppOpsService;
+    private AppOpsServiceImpl mAppOpsService;
     private int mMyUid;
     private long mTestStartMillis;
     private StaticMockitoSession mMockingSession;
 
     private void setupAppOpsService() {
-        mAppOpsService = new AppOpsService(mAppOpsFile, mHandler, spy(sContext));
+        mAppOpsService = new AppOpsServiceImpl(mAppOpsFile, mHandler, spy(sContext));
         mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver());
 
         // Always approve all permission checks
@@ -161,17 +164,20 @@
 
     @Test
     public void testNoteOperationAndGetOpsForPackage() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null);
 
         // Note an op that's allowed.
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
 
         // Note another op that's not allowed.
-        mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
-                false);
+        mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
         loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
         assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
@@ -185,18 +191,20 @@
     @Test
     public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() {
         // This op controls WIFI_SCAN
-        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED);
+        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED, null);
 
-        assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
-                null, false).getOpMode()).isEqualTo(MODE_ALLOWED);
+        assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ALLOWED);
 
         assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1,
                 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
 
         // Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well.
-        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED);
-        assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
-                null, false).getOpMode()).isEqualTo(MODE_ERRORED);
+        mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED, null);
+        assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ERRORED);
 
         assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis,
                 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
@@ -205,11 +213,14 @@
     // Tests the dumping and restoring of the in-memory state to/from XML.
     @Test
     public void testStatePersistence() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
-        mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
-                false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+        mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
         mAppOpsService.writeState();
 
         // Create a new app ops service which will initialize its state from XML.
@@ -224,8 +235,10 @@
     // Tests that ops are persisted during shutdown.
     @Test
     public void testShutdown() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
         mAppOpsService.shutdown();
 
         // Create a new app ops service which will initialize its state from XML.
@@ -238,8 +251,10 @@
 
     @Test
     public void testGetOpsForPackage() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
 
         // Query all ops
         List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage(
@@ -267,8 +282,10 @@
 
     @Test
     public void testPackageRemoved() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
 
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
@@ -322,8 +339,10 @@
 
     @Test
     public void testUidRemoved() {
-        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+        mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+                resolvePackageName(mMyUid, sMyPackageName), null,
+                INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
 
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index e1713b0..3efd5e7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -22,6 +22,7 @@
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_COARSE_LOCATION;
 import static android.app.AppOpsManager.OP_FINE_LOCATION;
+import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.OP_WIFI_SCAN;
 import static android.app.AppOpsManager.UID_STATE_BACKGROUND;
@@ -75,7 +76,7 @@
     ActivityManagerInternal mAmi;
 
     @Mock
-    AppOpsService.Constants mConstants;
+    AppOpsServiceImpl.Constants mConstants;
 
     AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor();
 
@@ -127,6 +128,8 @@
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+        assertEquals(MODE_IGNORED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -137,6 +140,8 @@
                 .update();
 
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+        assertEquals(MODE_ALLOWED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
 
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
@@ -151,6 +156,8 @@
                 .update();
 
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+        assertEquals(MODE_ALLOWED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
 
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
@@ -169,6 +176,8 @@
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+        assertEquals(MODE_IGNORED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -183,6 +192,8 @@
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+        assertEquals(MODE_IGNORED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -197,6 +208,8 @@
 
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+        assertEquals(MODE_IGNORED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -211,6 +224,8 @@
 
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+        assertEquals(MODE_IGNORED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -314,6 +329,8 @@
                 .update();
 
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+        assertEquals(MODE_ALLOWED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -328,6 +345,8 @@
                 .update();
 
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+        assertEquals(MODE_IGNORED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -403,6 +422,8 @@
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+        assertEquals(MODE_ALLOWED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -418,6 +439,8 @@
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+        assertEquals(MODE_ALLOWED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -433,6 +456,8 @@
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+        assertEquals(MODE_ALLOWED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index e08a715..298dbf4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -93,12 +93,13 @@
         }
     }
 
-    private void assertSameModes(SparseArray<AppOpsService.UidState> uidStates, int op1, int op2) {
+    private void assertSameModes(SparseArray<AppOpsServiceImpl.UidState> uidStates,
+            int op1, int op2) {
         int numberOfNonDefaultOps = 0;
         final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1);
         final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2);
         for(int i = 0; i < uidStates.size(); i++) {
-            final AppOpsService.UidState uidState = uidStates.valueAt(i);
+            final AppOpsServiceImpl.UidState uidState = uidStates.valueAt(i);
             SparseIntArray opModes = uidState.getNonDefaultUidModes();
             if (opModes != null) {
                 final int uidMode1 = opModes.get(op1, defaultModeOp1);
@@ -112,12 +113,12 @@
                 continue;
             }
             for (int j = 0; j < uidState.pkgOps.size(); j++) {
-                final AppOpsService.Ops ops = uidState.pkgOps.valueAt(j);
+                final AppOpsServiceImpl.Ops ops = uidState.pkgOps.valueAt(j);
                 if (ops == null) {
                     continue;
                 }
-                final AppOpsService.Op _op1 = ops.get(op1);
-                final AppOpsService.Op _op2 = ops.get(op2);
+                final AppOpsServiceImpl.Op _op1 = ops.get(op1);
+                final AppOpsServiceImpl.Op _op2 = ops.get(op2);
                 final int mode1 = (_op1 == null) ? defaultModeOp1 : _op1.getMode();
                 final int mode2 = (_op2 == null) ? defaultModeOp2 : _op2.getMode();
                 assertEquals(mode1, mode2);
@@ -158,8 +159,8 @@
         // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat
         when(testPM.getPackagesForUid(anyInt())).thenReturn(null);
 
-        AppOpsService testService = spy(
-                new AppOpsService(mAppOpsFile, mHandler, testContext)); // trigger upgrade
+        AppOpsServiceImpl testService = spy(
+                new AppOpsServiceImpl(mAppOpsFile, mHandler, testContext)); // trigger upgrade
         assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND,
                 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
         mHandler.removeCallbacks(testService.mWriteRunner);
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
new file mode 100644
index 0000000..f535997
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
@@ -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.
+ */
+
+package com.android.server.backup;
+
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.modules.utils.testing.TestableDeviceConfig;
+
+import static com.google.common.truth.Truth.assertThat;
+
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupAndRestoreFeatureFlagsTest {
+    @Rule
+    public TestableDeviceConfig.TestableDeviceConfigRule
+            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+    @Test
+    public void getBackupTransportFutureTimeoutMillis_notSet_returnsDefault() {
+        assertThat(
+                BackupAndRestoreFeatureFlags.getBackupTransportFutureTimeoutMillis()).isEqualTo(
+                600000);
+    }
+
+    @Test
+    public void getBackupTransportFutureTimeoutMillis_set_returnsSetValue() {
+        DeviceConfig.setProperty("backup_and_restore", "backup_transport_future_timeout_millis",
+                "1234", false);
+
+        assertThat(
+                BackupAndRestoreFeatureFlags.getBackupTransportFutureTimeoutMillis()).isEqualTo(
+                1234);
+    }
+
+    @Test
+    public void getBackupTransportCallbackTimeoutMillis_notSet_returnsDefault() {
+        assertThat(
+                BackupAndRestoreFeatureFlags.getBackupTransportCallbackTimeoutMillis()).isEqualTo(
+                300000);
+    }
+
+    @Test
+    public void getBackupTransportCallbackTimeoutMillis_set_returnsSetValue() {
+        DeviceConfig.setProperty("backup_and_restore", "backup_transport_callback_timeout_millis",
+                "5678", false);
+
+        assertThat(
+                BackupAndRestoreFeatureFlags.getBackupTransportCallbackTimeoutMillis()).isEqualTo(
+                5678);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/OWNERS b/services/tests/mockingservicestests/src/com/android/server/backup/OWNERS
new file mode 100644
index 0000000..d99779e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/OWNERS
@@ -0,0 +1 @@
+include /services/backup/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
new file mode 100644
index 0000000..7ab1363
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
@@ -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.server.cpu;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.os.ServiceManager;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public final class CpuMonitorServiceTest extends ExtendedMockitoTestCase {
+    private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG =
+            new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
+                    .addThreshold(30).addThreshold(70).build();
+
+    private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2 =
+            new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
+                    .addThreshold(10).addThreshold(90).build();
+
+    @Mock
+    private Context mContext;
+    private CpuMonitorService mService;
+    private HandlerExecutor mHandlerExecutor;
+    private CpuMonitorInternal mLocalService;
+
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder.mockStatic(ServiceManager.class);
+    }
+
+    @Before
+    public void setUp() {
+        mService = new CpuMonitorService(mContext);
+        mHandlerExecutor = new HandlerExecutor(new Handler(Looper.getMainLooper()));
+        doNothing().when(() -> ServiceManager.addService(eq("cpu_monitor"), any(Binder.class),
+                anyBoolean(), anyInt()));
+        mService.onStart();
+        mLocalService = LocalServices.getService(CpuMonitorInternal.class);
+    }
+
+    @After
+    public void tearDown() {
+        // The CpuMonitorInternal.class service is added by the mService.onStart call.
+        // Remove the service to ensure the setUp procedure can add this service again.
+        LocalServices.removeServiceForTest(CpuMonitorInternal.class);
+    }
+
+    @Test
+    public void testAddRemoveCpuAvailabilityCallback() {
+        CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
+                CpuMonitorInternal.CpuAvailabilityCallback.class);
+
+        mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
+                TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
+
+        // TODO(b/242722241): Verify that {@link mockCallback.onAvailabilityChanged} and
+        //  {@link mockCallback.onMonitoringIntervalChanged} are called when the callback is added.
+
+        mLocalService.removeCpuAvailabilityCallback(mockCallback);
+    }
+
+
+    @Test
+    public void testDuplicateAddCpuAvailabilityCallback() {
+        CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
+                CpuMonitorInternal.CpuAvailabilityCallback.class);
+
+        mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
+                TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
+
+        mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
+                TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2, mockCallback);
+
+        // TODO(b/242722241): Verify that {@link mockCallback} is called only when CPU availability
+        //  thresholds cross the bounds specified in the
+        //  {@link TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2} config.
+
+        mLocalService.removeCpuAvailabilityCallback(mockCallback);
+    }
+
+    @Test
+    public void testRemoveInvalidCpuAvailabilityCallback() {
+        CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
+                CpuMonitorInternal.CpuAvailabilityCallback.class);
+
+        mLocalService.removeCpuAvailabilityCallback(mockCallback);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 20af02e..f2cba40 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -33,6 +33,7 @@
 import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.PowerManager;
 import android.os.test.TestLooper;
 import android.util.FloatProperty;
@@ -135,6 +136,16 @@
                     DisplayPowerCallbacks displayPowerCallbacks) {
                 return mWakelockController;
             }
+
+            @Override
+            DisplayPowerProximityStateController getDisplayPowerProximityStateController(
+                    WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
+                    Looper looper, Runnable nudgeUpdatePowerState, int displayId,
+                    SensorManager sensorManager) {
+                return new DisplayPowerProximityStateController(wakelockController,
+                        displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
+                        sensorManager, /* injector= */ null);
+            }
         };
 
         addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
@@ -226,8 +237,8 @@
         when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
         when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock);
         when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
-        when(mLogicalDisplayMock.isEnabled()).thenReturn(true);
-        when(mLogicalDisplayMock.getPhase()).thenReturn(LogicalDisplay.DISPLAY_PHASE_ENABLED);
+        when(mLogicalDisplayMock.isEnabledLocked()).thenReturn(true);
+        when(mLogicalDisplayMock.isInTransitionLocked()).thenReturn(false);
         when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
         when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId);
         when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index 9a4bb22..4f8cb88 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -219,8 +219,8 @@
         when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
         when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock);
         when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
-        when(mLogicalDisplayMock.isEnabled()).thenReturn(true);
-        when(mLogicalDisplayMock.getPhase()).thenReturn(LogicalDisplay.DISPLAY_PHASE_ENABLED);
+        when(mLogicalDisplayMock.isEnabledLocked()).thenReturn(true);
+        when(mLogicalDisplayMock.isInTransitionLocked()).thenReturn(false);
         when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
         when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId);
         when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock);
@@ -233,4 +233,4 @@
                 });
         when(mDisplayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
     }
-}
\ No newline at end of file
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
new file mode 100644
index 0000000..6e91b24
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -0,0 +1,407 @@
+/*
+ * 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.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.test.TestLooper;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.testutils.OffsettableClock;
+
+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(AndroidJUnit4.class)
+public final class DisplayPowerProximityStateControllerTest {
+    @Mock
+    WakelockController mWakelockController;
+
+    @Mock
+    DisplayDeviceConfig mDisplayDeviceConfig;
+
+    @Mock
+    Runnable mNudgeUpdatePowerState;
+
+    @Mock
+    SensorManager mSensorManager;
+
+    private Sensor mProximitySensor;
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+    private SensorEventListener mSensorEventListener;
+    private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+
+    @Before
+    public void before() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        // This is kept null because currently there is no way to define a sensor
+                        // name in TestUtils
+                        name = null;
+                    }
+                });
+        setUpProxSensor();
+        DisplayPowerProximityStateController.Injector injector =
+                new DisplayPowerProximityStateController.Injector() {
+                    @Override
+                    DisplayPowerProximityStateController.Clock createClock() {
+                        return new DisplayPowerProximityStateController.Clock() {
+                            @Override
+                            public long uptimeMillis() {
+                                return mClock.now();
+                            }
+                        };
+                    }
+                };
+        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+                mNudgeUpdatePowerState, 0,
+                mSensorManager, injector);
+        mSensorEventListener = mDisplayPowerProximityStateController.getProximitySensorListener();
+    }
+
+    @Test
+    public void updatePendingProximityRequestsWorksAsExpectedWhenPending() {
+        // Set the system to pending wait for proximity
+        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        assertTrue(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Update the pending proximity wait request
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertTrue(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+    }
+
+    @Test
+    public void updatePendingProximityRequestsWorksAsExpectedWhenNotPending() {
+        // Will not wait or be in the pending wait state of not already pending
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+    }
+
+    @Test
+    public void updatePendingProximityRequestsWorksAsExpectedWhenPendingAndProximityIgnored()
+            throws Exception {
+        // Set the system to the state where it will ignore proximity unless changed
+        enableProximitySensor();
+        emitAndValidatePositiveProximityEvent();
+        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+        advanceTime(1);
+        assertTrue(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        verify(mNudgeUpdatePowerState, times(2)).run();
+
+        // Do not set the system to pending wait for proximity
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Set the system to pending wait for proximity. But because the proximity is being
+        // ignored, it will not wait or not set the pending wait
+        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+    }
+
+    @Test
+    public void cleanupDisablesTheProximitySensor() {
+        enableProximitySensor();
+        mDisplayPowerProximityStateController.cleanup();
+        verify(mSensorManager).unregisterListener(
+                mSensorEventListener);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    @Test
+    public void isProximitySensorAvailableReturnsTrueWhenAvailable() {
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void isProximitySensorAvailableReturnsFalseWhenNotAvailable() {
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = null;
+                        name = null;
+                    }
+                });
+        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+                mNudgeUpdatePowerState, 1,
+                mSensorManager, null);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception {
+        DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class);
+        when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        name = null;
+                    }
+                });
+        Sensor newProxSensor = TestUtils.createSensor(
+                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f);
+        when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(newProxSensor));
+        mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(
+                updatedDisplayDeviceConfig);
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void setPendingWaitForNegativeProximityLockedWorksAsExpected() {
+        // Doesn't do anything not asked to wait
+        assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                false));
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Sets pending wait negative proximity if not already waiting
+        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        assertTrue(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Will not set pending wait negative proximity if already waiting
+        assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        assertTrue(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+    }
+
+    @Test
+    public void evaluateProximityStateWhenRequestedUseOfProximitySensor() throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Again evaluate the proximity state, with system having positive proximity
+        setScreenOffBecauseOfPositiveProximityState();
+    }
+
+    @Test
+    public void evaluateProximityStateWhenScreenOffBecauseOfPositiveProximity() throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Again evaluate the proximity state, with system having positive proximity
+        setScreenOffBecauseOfPositiveProximityState();
+
+        // Set the system to pending wait for proximity
+        mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(true);
+        // Update the pending proximity wait request
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+
+        // Start ignoring proximity sensor
+        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+        // Re-evaluate the proximity state, such that the system is detecting the positive
+        // proximity, and screen is off because of that
+        when(mWakelockController.getOnProximityNegativeRunnable()).thenReturn(mock(Runnable.class));
+        mDisplayPowerProximityStateController.updateProximityState(mock(
+                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+        assertTrue(
+                mDisplayPowerProximityStateController
+                        .shouldSkipRampBecauseOfProximityChangeToNegative());
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
+    }
+
+    @Test
+    public void evaluateProximityStateWhenDisplayIsTurningOff() throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Again evaluate the proximity state, with system having positive proximity
+        setScreenOffBecauseOfPositiveProximityState();
+
+        // Re-evaluate the proximity state, such that the system is detecting the positive
+        // proximity, and screen is off because of that
+        mDisplayPowerProximityStateController.updateProximityState(mock(
+                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_OFF);
+        verify(mSensorManager).unregisterListener(
+                mSensorEventListener);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    @Test
+    public void evaluateProximityStateNotWaitingForNegativeProximityAndNotUsingProxSensor()
+            throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Re-evaluate the proximity state, such that the system is detecting the positive
+        // proximity, and screen is off because of that
+        mDisplayPowerProximityStateController.updateProximityState(mock(
+                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+        verify(mSensorManager).unregisterListener(
+                mSensorEventListener);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    private void setUpProxSensor() throws Exception {
+        mProximitySensor = TestUtils.createSensor(
+                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 5.0f);
+        when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(mProximitySensor));
+    }
+
+    private void emitAndValidatePositiveProximityEvent() throws Exception {
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        mSensorEventListener.onSensorChanged(TestUtils.createSensorEvent(mProximitySensor, 4));
+        verify(mSensorManager).registerListener(mSensorEventListener,
+                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+                mDisplayPowerProximityStateController.getHandler());
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+        verify(mNudgeUpdatePowerState).run();
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    // Call evaluateProximityState with the request for using the proximity sensor. This will
+    // register the proximity sensor listener, which will be needed for mocking positive
+    // proximity scenarios.
+    private void enableProximitySensor() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.useProximitySensor = true;
+        mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+                Display.STATE_ON);
+        verify(mSensorManager).registerListener(
+                mSensorEventListener,
+                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+                mDisplayPowerProximityStateController.getHandler());
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+        verifyZeroInteractions(mWakelockController);
+    }
+
+    private void setScreenOffBecauseOfPositiveProximityState() {
+        // Prepare a request to indicate that the proximity sensor is to be used
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.useProximitySensor = true;
+
+        Runnable onProximityPositiveRunnable = mock(Runnable.class);
+        when(mWakelockController.getOnProximityPositiveRunnable()).thenReturn(
+                onProximityPositiveRunnable);
+
+        mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+                Display.STATE_ON);
+        verify(mSensorManager).registerListener(
+                mSensorEventListener,
+                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+                mDisplayPowerProximityStateController.getHandler());
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertTrue(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index d41ac70..f105971 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -23,6 +23,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -58,6 +59,7 @@
 import com.google.common.truth.Truth;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -84,6 +86,8 @@
 
     private static final long HANDLER_WAIT_MS = 100;
 
+    private static final int[] HDR_TYPES = new int[]{1, 2};
+
     private StaticMockitoSession mMockitoSession;
 
     private LocalDisplayAdapter mAdapter;
@@ -161,6 +165,10 @@
         when(mMockedResources.getIntArray(
                 com.android.internal.R.array.config_autoBrightnessLevels))
                 .thenReturn(new int[]{});
+        when(mMockedResources.obtainTypedArray(R.array.config_displayShapeArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray))
+                .thenReturn(mockArray);
     }
 
     @After
@@ -198,6 +206,38 @@
                 PORT_C, false);
     }
 
+    @Test
+    public void testSupportedDisplayModesGetOverriddenWhenDisplayIsUpdated()
+            throws InterruptedException {
+        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 0);
+        displayMode.supportedHdrTypes = new int[0];
+        FakeDisplay display = new FakeDisplay(PORT_A, new SurfaceControl.DisplayMode[]{displayMode},
+                0, 0);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+        Assert.assertEquals(1, supportedModes.length);
+        Assert.assertEquals(0, supportedModes[0].getSupportedHdrTypes().length);
+
+        displayMode.supportedHdrTypes = new int[]{3, 2};
+        display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{displayMode};
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+
+        Assert.assertEquals(1, supportedModes.length);
+        assertArrayEquals(new int[]{2, 3}, supportedModes[0].getSupportedHdrTypes());
+    }
+
     /**
      * Confirm that all local displays are public when config_localPrivateDisplayPorts is empty.
      */
@@ -353,7 +393,7 @@
         SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
         SurfaceControl.DisplayMode[] modes =
                 new SurfaceControl.DisplayMode[]{displayMode};
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0);
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -409,7 +449,7 @@
         SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
         SurfaceControl.DisplayMode[] modes =
                 new SurfaceControl.DisplayMode[]{displayMode};
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0);
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -464,7 +504,7 @@
                 createFakeDisplayMode(0, 1920, 1080, 60f),
                 createFakeDisplayMode(1, 1920, 1080, 50f)
         };
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0);
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0, 60f);
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -498,6 +538,49 @@
     }
 
     @Test
+    public void testAfterDisplayChange_RenderFrameRateIsUpdated() throws Exception {
+        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
+                createFakeDisplayMode(0, 1920, 1080, 60f),
+        };
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0,
+                /* renderFrameRate */30f);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+                .getDisplayDeviceInfoLocked();
+
+        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+        assertEquals(Float.floatToIntBits(30f),
+                Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+
+        // Change the render frame rate
+        display.dynamicInfo.renderFrameRate = 60f;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+        assertEquals(Float.floatToIntBits(60f),
+                Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+    }
+
+    @Test
     public void testAfterDisplayChange_HdrCapabilitiesAreUpdated() throws Exception {
         FakeDisplay display = new FakeDisplay(PORT_A);
         Display.HdrCapabilities initialHdrCapabilities = new Display.HdrCapabilities(new int[0],
@@ -682,7 +765,7 @@
                 createFakeDisplayMode(1, 1920, 1080, 50f)
         };
         final int activeMode = 0;
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode);
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode, 60f);
         display.desiredDisplayModeSpecs.defaultMode = 1;
 
         setUpDisplay(display);
@@ -939,11 +1022,13 @@
             dynamicInfo.activeDisplayModeId = 0;
         }
 
-        private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode) {
+        private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
+                float renderFrameRate) {
             address = createDisplayAddress(port);
             info = createFakeDisplayInfo();
             dynamicInfo.supportedDisplayModes = modes;
             dynamicInfo.activeDisplayModeId = activeMode;
+            dynamicInfo.renderFrameRate = renderFrameRate;
         }
 
         private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
@@ -1004,6 +1089,7 @@
         mode.xDpi = 100;
         mode.yDpi = 100;
         mode.group = group;
+        mode.supportedHdrTypes = HDR_TYPES;
         return mode;
     }
 
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 2547347..6e4d214 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -17,6 +17,7 @@
 package com.android.server.job;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+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;
@@ -34,14 +35,20 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
+import android.app.IActivityManager;
 import android.app.job.JobInfo;
 import android.content.ComponentName;
 import android.content.Context;
@@ -49,8 +56,11 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
@@ -98,12 +108,23 @@
     @Mock
     private IPackageManager mIPackageManager;
 
-    static class InjectorForTest extends JobConcurrencyManager.Injector {
+    private static class InjectorForTest extends JobConcurrencyManager.Injector {
+        public final ArrayMap<JobServiceContext, JobStatus> contexts = new ArrayMap<>();
+
         @Override
         JobServiceContext createJobServiceContext(JobSchedulerService service,
                 JobConcurrencyManager concurrencyManager, IBatteryStats batteryStats,
                 JobPackageTracker tracker, Looper looper) {
-            return mock(JobServiceContext.class);
+            final JobServiceContext context = mock(JobServiceContext.class);
+            doAnswer((Answer<Boolean>) invocationOnMock -> {
+                Object[] args = invocationOnMock.getArguments();
+                final JobStatus job = (JobStatus) args[0];
+                contexts.put(context, job);
+                doReturn(job).when(context).getRunningJobLocked();
+                return true;
+            }).when(context).executeRunnableJob(any(), anyInt());
+            contexts.put(context, null);
+            return context;
         }
     }
 
@@ -135,13 +156,22 @@
                 R.bool.config_jobSchedulerRestrictBackgroundUser);
         when(mContext.getResources()).thenReturn(mResources);
         doReturn(mContext).when(jobSchedulerService).getTestableContext();
+        doReturn(jobSchedulerService).when(jobSchedulerService).getLock();
         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);
+        doReturn(mock(PowerManager.class)).when(mContext).getSystemService(PowerManager.class);
         mInjector = new InjectorForTest();
+        doAnswer((Answer<Long>) invocationOnMock -> {
+            Object[] args = invocationOnMock.getArguments();
+            final JobStatus job = (JobStatus) args[0];
+            return job.shouldTreatAsExpeditedJob()
+                    ? JobSchedulerService.Constants.DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS
+                    : JobSchedulerService.Constants.DEFAULT_RUNTIME_MIN_GUARANTEE_MS;
+        }).when(jobSchedulerService).getMinJobExecutionGuaranteeMs(any());
         mJobConcurrencyManager = new JobConcurrencyManager(jobSchedulerService, mInjector);
         mGracePeriodObserver = mock(GracePeriodObserver.class);
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -150,6 +180,16 @@
         createCurrentUser(true);
         mNextUserId = 10;
         mJobConcurrencyManager.mGracePeriodObserver = mGracePeriodObserver;
+
+        IActivityManager activityManager = ActivityManager.getService();
+        spyOn(activityManager);
+        try {
+            doNothing().when(activityManager).registerUserSwitchObserver(any(), anyString());
+        } catch (RemoteException e) {
+            fail("registerUserSwitchObserver threw exception: " + e.getMessage());
+        }
+
+        mJobConcurrencyManager.onSystemReady();
     }
 
     @After
@@ -167,32 +207,93 @@
         final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
         final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
         final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
-        mJobConcurrencyManager
-                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
 
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
         assertEquals(0, preferredUidOnly.size());
         assertEquals(0, stoppable.size());
+        assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(0, assignmentInfo.numRunningTopEj);
     }
 
     @Test
     public void testPrepareForAssignmentDetermination_onlyPendingJobs() {
-        final ArraySet<JobStatus> jobs = new ArraySet<>();
         for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
             JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
             mPendingJobQueue.add(job);
-            jobs.add(job);
         }
 
         final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
         final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
         final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
-        mJobConcurrencyManager
-                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
 
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
         assertEquals(0, preferredUidOnly.size());
         assertEquals(0, stoppable.size());
+        assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(0, assignmentInfo.numRunningTopEj);
+    }
+
+    @Test
+    public void testPrepareForAssignmentDetermination_onlyPreferredUidOnly() {
+        for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
+            JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
+            mJobConcurrencyManager.addRunningJobForTesting(job);
+        }
+
+        for (int i = 0; i < mInjector.contexts.size(); ++i) {
+            doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+        }
+
+        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
+
+        assertEquals(0, idle.size());
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+        assertEquals(0, stoppable.size());
+        assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(0, assignmentInfo.numRunningTopEj);
+    }
+
+    @Test
+    public void testPrepareForAssignmentDetermination_onlyRunningTopEjs() {
+        for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
+            JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
+            job.startedAsExpeditedJob = true;
+            job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+            mJobConcurrencyManager.addRunningJobForTesting(job);
+        }
+
+        for (int i = 0; i < mInjector.contexts.size(); ++i) {
+            doReturn(i % 2 == 0).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+        }
+
+        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
+
+        assertEquals(0, idle.size());
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, preferredUidOnly.size());
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, stoppable.size());
+        assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
+                assignmentInfo.numRunningTopEj);
     }
 
     @Test
@@ -213,10 +314,13 @@
         final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
         final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
         final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
         mJobConcurrencyManager
-                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
-        mJobConcurrencyManager
-                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable);
+                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
+                        assignmentInfo);
 
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, changed.size());
         for (int i = changed.size() - 1; i >= 0; --i) {
@@ -226,6 +330,175 @@
     }
 
     @Test
+    public void testDetermineAssignments_allPreferredUidOnly_shortTimeLeft() throws Exception {
+        mConfigBuilder.setBoolean(JobConcurrencyManager.KEY_ENABLE_MAX_WAIT_TIME_BYPASS, true);
+        setConcurrencyConfig(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
+                new TypeConfig(WORK_TYPE_BG, 0, JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT));
+        for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT * 2; ++i) {
+            final int uid = mDefaultUserId * UserHandle.PER_USER_RANGE + i;
+            final String sourcePkgName = "com.source.package." + UserHandle.getAppId(uid);
+            setPackageUid(sourcePkgName, uid);
+            final JobStatus job = createJob(uid, sourcePkgName);
+            spyOn(job);
+            doReturn(i % 2 == 0).when(job).shouldTreatAsExpeditedJob();
+            if (i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT) {
+                mJobConcurrencyManager.addRunningJobForTesting(job);
+            } else {
+                mPendingJobQueue.add(job);
+            }
+        }
+
+        // Waiting time is too short, so we shouldn't create any extra contexts.
+        final long remainingTimeMs = JobConcurrencyManager.DEFAULT_MAX_WAIT_EJ_MS / 2;
+        for (int i = 0; i < mInjector.contexts.size(); ++i) {
+            doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+            doReturn(remainingTimeMs)
+                    .when(mInjector.contexts.keyAt(i)).getRemainingGuaranteedTimeMs(anyLong());
+        }
+
+        final ArraySet<JobConcurrencyManager.ContextAssignment> changed = new ArraySet<>();
+        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
+
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
+        assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+
+        mJobConcurrencyManager
+                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
+                        assignmentInfo);
+
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+        assertEquals(0, changed.size());
+    }
+
+    @Test
+    public void testDetermineAssignments_allPreferredUidOnly_mediumTimeLeft() throws Exception {
+        mConfigBuilder.setBoolean(JobConcurrencyManager.KEY_ENABLE_MAX_WAIT_TIME_BYPASS, true);
+        setConcurrencyConfig(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
+                new TypeConfig(WORK_TYPE_BG, 0, JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT));
+        final ArraySet<JobStatus> jobs = new ArraySet<>();
+        for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT * 2; ++i) {
+            final int uid = mDefaultUserId * UserHandle.PER_USER_RANGE + i;
+            final String sourcePkgName = "com.source.package." + UserHandle.getAppId(uid);
+            setPackageUid(sourcePkgName, uid);
+            final JobStatus job = createJob(uid, sourcePkgName);
+            spyOn(job);
+            doReturn(i % 2 == 0).when(job).shouldTreatAsExpeditedJob();
+            if (i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT) {
+                mJobConcurrencyManager.addRunningJobForTesting(job);
+            } else {
+                mPendingJobQueue.add(job);
+                jobs.add(job);
+            }
+        }
+
+        // Waiting time is longer than the EJ waiting time, but shorter than regular job waiting
+        // time, so we should only create an extra context for an EJ.
+        final long remainingTimeMs = (JobConcurrencyManager.DEFAULT_MAX_WAIT_EJ_MS
+                + JobConcurrencyManager.DEFAULT_MAX_WAIT_REGULAR_MS) / 2;
+        for (int i = 0; i < mInjector.contexts.size(); ++i) {
+            doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+            doReturn(remainingTimeMs)
+                    .when(mInjector.contexts.keyAt(i)).getRemainingGuaranteedTimeMs(anyLong());
+        }
+
+        final ArraySet<JobConcurrencyManager.ContextAssignment> changed = new ArraySet<>();
+        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
+
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
+        assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+
+        mJobConcurrencyManager
+                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
+                        assignmentInfo);
+
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+        for (int i = changed.size() - 1; i >= 0; --i) {
+            jobs.remove(changed.valueAt(i).newJob);
+        }
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT - 1, jobs.size());
+        assertEquals(1, changed.size());
+        JobStatus assignedJob = changed.valueAt(0).newJob;
+        assertTrue(assignedJob.shouldTreatAsExpeditedJob());
+    }
+
+    @Test
+    public void testDetermineAssignments_allPreferredUidOnly_longTimeLeft() throws Exception {
+        mConfigBuilder.setBoolean(JobConcurrencyManager.KEY_ENABLE_MAX_WAIT_TIME_BYPASS, true);
+        setConcurrencyConfig(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
+                new TypeConfig(WORK_TYPE_BG, 0, JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT));
+        final ArraySet<JobStatus> jobs = new ArraySet<>();
+        for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT * 2; ++i) {
+            final int uid = mDefaultUserId * UserHandle.PER_USER_RANGE + i;
+            final String sourcePkgName = "com.source.package." + UserHandle.getAppId(uid);
+            setPackageUid(sourcePkgName, uid);
+            final JobStatus job = createJob(uid, sourcePkgName);
+            spyOn(job);
+            doReturn(i % 2 == 0).when(job).shouldTreatAsExpeditedJob();
+            if (i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT) {
+                mJobConcurrencyManager.addRunningJobForTesting(job);
+            } else {
+                mPendingJobQueue.add(job);
+                jobs.add(job);
+            }
+        }
+
+        // Waiting time is longer than even the regular job waiting time, so we should
+        // create an extra context for an EJ, and potentially one for a regular job.
+        final long remainingTimeMs = 2 * JobConcurrencyManager.DEFAULT_MAX_WAIT_REGULAR_MS;
+        for (int i = 0; i < mInjector.contexts.size(); ++i) {
+            doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+            doReturn(remainingTimeMs)
+                    .when(mInjector.contexts.keyAt(i)).getRemainingGuaranteedTimeMs(anyLong());
+        }
+
+        final ArraySet<JobConcurrencyManager.ContextAssignment> changed = new ArraySet<>();
+        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
+
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
+        assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+
+        mJobConcurrencyManager
+                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
+                        assignmentInfo);
+
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+        // Depending on iteration order, we may create 1 or 2 contexts.
+        final long numAssignedJobs = changed.size();
+        assertTrue(numAssignedJobs > 0);
+        assertTrue(numAssignedJobs <= 2);
+        for (int i = 0; i < numAssignedJobs; ++i) {
+            jobs.remove(changed.valueAt(i).newJob);
+        }
+        assertEquals(numAssignedJobs,
+                JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT - jobs.size());
+        JobStatus firstAssignedJob = changed.valueAt(0).newJob;
+        if (!firstAssignedJob.shouldTreatAsExpeditedJob()) {
+            assertEquals(2, numAssignedJobs);
+            assertTrue(changed.valueAt(1).newJob.shouldTreatAsExpeditedJob());
+        } else if (numAssignedJobs == 2) {
+            assertFalse(changed.valueAt(1).newJob.shouldTreatAsExpeditedJob());
+        }
+    }
+
+    @Test
     public void testIsPkgConcurrencyLimited_top() {
         final JobStatus topJob = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0);
         topJob.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
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/location/injector/FakeUserInfoHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
index ac23d4e..014ef3d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
@@ -34,14 +34,16 @@
     public static final int DEFAULT_USERID = 0;
 
     private final IntArray mRunningUserIds;
+    private final IntArray mVisibleUserIds;
     private final SparseArray<IntArray> mProfiles;
 
     private int mCurrentUserId;
 
     public FakeUserInfoHelper() {
         mCurrentUserId = DEFAULT_USERID;
-        mRunningUserIds = IntArray.wrap(new int[]{DEFAULT_USERID});
+        mRunningUserIds = IntArray.wrap(new int[] {DEFAULT_USERID});
         mProfiles = new SparseArray<>();
+        mVisibleUserIds = IntArray.wrap(new int[] {DEFAULT_USERID});
     }
 
     public void startUser(int userId) {
@@ -65,6 +67,7 @@
             mRunningUserIds.remove(idx);
         }
 
+        setUserInvisibleInternal(userId);
         dispatchOnUserStopped(userId);
     }
 
@@ -82,16 +85,39 @@
         // ensure all profiles are started if they didn't exist before...
         for (int userId : currentProfileUserIds) {
             startUserInternal(userId, false);
+            setUserVisibleInternal(userId, true);
         }
 
         if (oldUserId != mCurrentUserId) {
             dispatchOnCurrentUserChanged(oldUserId, mCurrentUserId);
+            setUserVisibleInternal(mCurrentUserId, true);
         }
     }
 
-    @Override
-    public int[] getRunningUserIds() {
-        return mRunningUserIds.toArray();
+    private void setUserVisibleInternal(int userId, boolean alwaysDispatch) {
+        int idx = mVisibleUserIds.indexOf(userId);
+        if (idx < 0) {
+            mVisibleUserIds.add(userId);
+        } else if (!alwaysDispatch) {
+            return;
+        }
+        dispatchOnVisibleUserChanged(userId, true);
+    }
+
+    private void setUserInvisibleInternal(int userId) {
+        int idx = mVisibleUserIds.indexOf(userId);
+        if (idx >= 0) {
+            mVisibleUserIds.remove(userId);
+        }
+        dispatchOnVisibleUserChanged(userId, false);
+    }
+
+    public void setUserVisible(int userId, boolean visible) {
+        if (visible) {
+            setUserVisibleInternal(userId, true);
+        } else {
+            setUserInvisibleInternal(userId);
+        }
     }
 
     @Override
@@ -100,11 +126,21 @@
     }
 
     @Override
+    public int[] getRunningUserIds() {
+        return mRunningUserIds.toArray();
+    }
+
+    @Override
     public int getCurrentUserId() {
         return mCurrentUserId;
     }
 
     @Override
+    public boolean isVisibleUserId(int userId) {
+        return mVisibleUserIds.indexOf(userId) >= 0;
+    }
+
+    @Override
     protected int[] getProfileIds(int userId) {
         IntArray profiles = mProfiles.get(userId);
         if (profiles != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemUserInfoHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemUserInfoHelperTest.java
index 490b2e8..d9aa232 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemUserInfoHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemUserInfoHelperTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.location.injector;
 
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -31,6 +32,7 @@
 
 import com.android.server.LocalServices;
 import com.android.server.location.injector.UserInfoHelper.UserListener;
+import com.android.server.pm.UserManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
@@ -45,13 +47,14 @@
 
     private static final int USER1_ID = 1;
     private static final int USER1_MANAGED_ID = 11;
-    private static final int[] USER1_PROFILES = new int[]{USER1_ID, USER1_MANAGED_ID};
+    private static final int[] USER1_PROFILES = new int[] {USER1_ID, USER1_MANAGED_ID};
     private static final int USER2_ID = 2;
     private static final int USER2_MANAGED_ID = 12;
-    private static final int[] USER2_PROFILES = new int[]{USER2_ID, USER2_MANAGED_ID};
+    private static final int[] USER2_PROFILES = new int[] {USER2_ID, USER2_MANAGED_ID};
 
     @Mock private Context mContext;
     @Mock private UserManager mUserManager;
+    @Mock private UserManagerInternal mUserManagerInternal;
 
     private SystemUserInfoHelper mHelper;
 
@@ -63,12 +66,15 @@
         doReturn(USER1_PROFILES).when(mUserManager).getEnabledProfileIds(USER1_ID);
         doReturn(USER2_PROFILES).when(mUserManager).getEnabledProfileIds(USER2_ID);
 
+        LocalServices.addService(UserManagerInternal.class, mUserManagerInternal);
+
         mHelper = new SystemUserInfoHelper(mContext);
     }
 
     @After
     public void tearDown() {
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
     }
 
     @Test
@@ -77,11 +83,11 @@
         mHelper.addListener(listener);
 
         mHelper.dispatchOnCurrentUserChanged(USER1_ID, USER2_ID);
-        verify(listener, times(1)).onUserChanged(USER1_ID, UserListener.CURRENT_USER_CHANGED);
-        verify(listener, times(1)).onUserChanged(USER1_MANAGED_ID,
+        verify(listener).onUserChanged(USER1_ID, UserListener.CURRENT_USER_CHANGED);
+        verify(listener).onUserChanged(USER1_MANAGED_ID,
                 UserListener.CURRENT_USER_CHANGED);
-        verify(listener, times(1)).onUserChanged(USER2_ID, UserListener.CURRENT_USER_CHANGED);
-        verify(listener, times(1)).onUserChanged(USER2_MANAGED_ID,
+        verify(listener).onUserChanged(USER2_ID, UserListener.CURRENT_USER_CHANGED);
+        verify(listener).onUserChanged(USER2_MANAGED_ID,
                 UserListener.CURRENT_USER_CHANGED);
 
         mHelper.dispatchOnCurrentUserChanged(USER2_ID, USER1_ID);
@@ -94,6 +100,25 @@
     }
 
     @Test
+    public void testListener_UserVisibilityChanged() {
+        mHelper.onSystemReady();
+        verify(mUserManagerInternal).addUserVisibilityListener(any());
+
+        UserListener listener = mock(UserListener.class);
+        mHelper.addListener(listener);
+
+        mHelper.dispatchOnVisibleUserChanged(USER1_ID, false);
+        mHelper.dispatchOnVisibleUserChanged(USER2_ID, true);
+        verify(listener).onUserChanged(USER1_ID, UserListener.USER_VISIBILITY_CHANGED);
+        verify(listener).onUserChanged(USER2_ID, UserListener.USER_VISIBILITY_CHANGED);
+
+        mHelper.dispatchOnVisibleUserChanged(USER2_ID, false);
+        mHelper.dispatchOnVisibleUserChanged(USER1_ID, true);
+        verify(listener, times(2)).onUserChanged(USER2_ID, UserListener.USER_VISIBILITY_CHANGED);
+        verify(listener, times(2)).onUserChanged(USER1_ID, UserListener.USER_VISIBILITY_CHANGED);
+    }
+
+    @Test
     public void testListener_StartUser() {
         UserListener listener = mock(UserListener.class);
         mHelper.addListener(listener);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 20e4e80..aa28ad4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -175,7 +175,9 @@
         doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
 
         mInjector = new TestInjector(mContext);
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
         mInjector.getUserInfoHelper().startUser(OTHER_USER);
+        mInjector.getUserInfoHelper().setUserVisible(OTHER_USER, true);
 
         mPassive = new PassiveLocationProviderManager(mContext, mInjector);
         mPassive.startManager(null);
@@ -331,6 +333,20 @@
     }
 
     @Test
+    public void testGetLastLocation_InvisibleUser() {
+        Location loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
+        assertThat(mManager.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+                PERMISSION_FINE)).isNull();
+
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
+        assertThat(mManager.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+                PERMISSION_FINE)).isEqualTo(loc);
+    }
+
+    @Test
     public void testGetLastLocation_Bypass() {
         mInjector.getSettingsHelper().setIgnoreSettingsAllowlist(
                 new PackageTagsList.Builder().add(
@@ -569,6 +585,25 @@
     }
 
     @Test
+    public void testRegisterListener_InvisibleUser() throws Exception {
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = new LocationRequest.Builder(0)
+                .setWorkSource(WORK_SOURCE)
+                .build();
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
+        mProvider.setProviderLocation(createLocationResult(NAME, mRandom));
+        verify(listener, never()).onLocationChanged(any(List.class),
+                nullable(IRemoteCallback.class));
+
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
+        LocationResult loc = createLocationResult(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        verify(listener).onLocationChanged(eq(loc.asList()), nullable(IRemoteCallback.class));
+    }
+
+    @Test
     public void testRegisterListener_ExpiringAlarm() throws Exception {
         ILocationListener listener = createMockLocationListener();
         LocationRequest request = new LocationRequest.Builder(0)
@@ -799,6 +834,17 @@
     }
 
     @Test
+    public void testGetCurrentLocation_InvisibleUser() throws Exception {
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
+
+        ILocationCallback listener = createMockGetCurrentLocationListener();
+        LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
+        mManager.getCurrentLocation(request, IDENTITY, PERMISSION_FINE, listener);
+
+        verify(listener).onLocation(isNull());
+    }
+
+    @Test
     public void testFlush() throws Exception {
         ILocationListener listener = createMockLocationListener();
         mManager.registerLocationRequest(
@@ -1008,6 +1054,21 @@
     }
 
     @Test
+    public void testProviderRequest_InvisibleUser() {
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = new LocationRequest.Builder(5)
+                .setWorkSource(WORK_SOURCE)
+                .build();
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
+        assertThat(mProvider.getRequest().isActive()).isFalse();
+
+        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
+        assertThat(mProvider.getRequest().isActive()).isTrue();
+    }
+
+    @Test
     public void testProviderRequest_IgnoreLocationSettings() {
         mInjector.getSettingsHelper().setIgnoreSettingsAllowlist(
                 new PackageTagsList.Builder().add(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
new file mode 100644
index 0000000..aabec22
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -0,0 +1,572 @@
+/*
+ * 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.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.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.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.runner.AndroidJUnit4;
+
+import com.android.server.pm.parsing.PackageParser2;
+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;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+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 PackageParser2 mPackageParser2;
+
+    private IApexService mApexService = mock(IApexService.class);
+
+    private PackageManagerService mPmService;
+
+    private InstallPackageHelper mInstallPackageHelper;
+
+    @Before
+    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 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 testScanActivePackage() {
+        var apexInfos = createApexInfoForTestPkg(true, false);
+        var results = scanApexInfos(apexInfos);
+        var active = findActive(results);
+        var factory = findFactory(results, TEST_APEX_PKG);
+
+        assertThat(active).isNotNull();
+        assertThat(active.packageName).isEqualTo(TEST_APEX_PKG);
+
+        assertThat(factory).isNull();
+    }
+
+    @Test
+    public void testScanFactoryPackage() {
+        var apexInfos = createApexInfoForTestPkg(false, true);
+        var results = scanApexInfos(apexInfos);
+        var active = findActive(results);
+        var factory = findFactory(results, TEST_APEX_PKG);
+
+        assertThat(factory).isNotNull();
+        assertThat(factory.packageName).contains(TEST_APEX_PKG);
+
+        assertThat(active).isNull();
+    }
+
+    @Test
+    public void testGetApexSystemServices() {
+        ApexInfo[] apexInfo = new ApexInfo[]{
+                createApexInfoForTestPkg(false, true, 1),
+                // only active apex reports apex-system-service
+                createApexInfoForTestPkg(true, false, 2),
+        };
+
+        List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo);
+        mApexManager.notifyScanResult(scanResults);
+
+        List<ApexSystemServiceInfo> services = mApexManager.getApexSystemServices();
+        assertThat(services).hasSize(1);
+        assertThat(services.stream().map(ApexSystemServiceInfo::getName).findFirst().orElse(null))
+                .matches("com.android.apex.test.ApexSystemService");
+    }
+
+    @Test
+    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
+    public void testIsApexSupported() {
+        assertThat(mApexManager.isApexSupported()).isTrue();
+    }
+
+    @Test
+    public void testGetStagedSessionInfo() throws RemoteException {
+        when(mApexService.getStagedSessionInfo(anyInt())).thenReturn(
+                getFakeStagedSessionInfo());
+
+        mApexManager.getStagedSessionInfo(TEST_SESSION_ID);
+        verify(mApexService, times(1)).getStagedSessionInfo(TEST_SESSION_ID);
+    }
+
+    @Test
+    public void testGetStagedSessionInfo_unKnownStagedSessionId() throws RemoteException {
+        when(mApexService.getStagedSessionInfo(anyInt())).thenReturn(
+                getFakeUnknownSessionInfo());
+
+        assertThat(mApexManager.getStagedSessionInfo(TEST_SESSION_ID)).isNull();
+    }
+
+    @Test
+    public void testSubmitStagedSession_throwPackageManagerException() throws RemoteException {
+        doAnswer(invocation -> {
+            throw new Exception();
+        }).when(mApexService).submitStagedSession(any(), any());
+
+        assertThrows(PackageManagerException.class,
+                () -> mApexManager.submitStagedSession(testParamsWithChildren()));
+    }
+
+    @Test
+    public void testSubmitStagedSession_throwRunTimeException() throws RemoteException {
+        doThrow(RemoteException.class).when(mApexService).submitStagedSession(any(), any());
+
+        assertThrows(RuntimeException.class,
+                () -> mApexManager.submitStagedSession(testParamsWithChildren()));
+    }
+
+    @Test
+    public void testGetStagedApexInfos_throwRunTimeException() throws RemoteException {
+        doThrow(RemoteException.class).when(mApexService).getStagedApexInfos(any());
+
+        assertThrows(RuntimeException.class,
+                () -> mApexManager.getStagedApexInfos(testParamsWithChildren()));
+    }
+
+    @Test
+    public void testGetStagedApexInfos_returnsEmptyArrayOnError() throws RemoteException {
+        doThrow(ServiceSpecificException.class).when(mApexService).getStagedApexInfos(any());
+
+        assertThat(mApexManager.getStagedApexInfos(testParamsWithChildren())).hasLength(0);
+    }
+
+    @Test
+    public void testMarkStagedSessionReady_throwPackageManagerException() throws RemoteException {
+        doAnswer(invocation -> {
+            throw new Exception();
+        }).when(mApexService).markStagedSessionReady(anyInt());
+
+        assertThrows(PackageManagerException.class,
+                () -> mApexManager.markStagedSessionReady(TEST_SESSION_ID));
+    }
+
+    @Test
+    public void testMarkStagedSessionReady_throwRunTimeException() throws RemoteException {
+        doThrow(RemoteException.class).when(mApexService).markStagedSessionReady(anyInt());
+
+        assertThrows(RuntimeException.class,
+                () -> mApexManager.markStagedSessionReady(TEST_SESSION_ID));
+    }
+
+    @Test
+    public void testRevertActiveSessions_remoteException() throws RemoteException {
+        doThrow(RemoteException.class).when(mApexService).revertActiveSessions();
+
+        try {
+            assertThat(mApexManager.revertActiveSessions()).isFalse();
+        } catch (Exception e) {
+            throw new AssertionError("ApexManager should not raise Exception");
+        }
+    }
+
+    @Test
+    public void testMarkStagedSessionSuccessful_throwRemoteException() throws RemoteException {
+        doThrow(RemoteException.class).when(mApexService).markStagedSessionSuccessful(anyInt());
+
+        assertThrows(RuntimeException.class,
+                () -> mApexManager.markStagedSessionSuccessful(TEST_SESSION_ID));
+    }
+
+    @Test
+    public void testUninstallApex_throwException_returnFalse() throws RemoteException {
+        doAnswer(invocation -> {
+            throw new Exception();
+        }).when(mApexService).unstagePackages(any());
+
+        assertThat(mApexManager.uninstallApex(TEST_APEX_PKG)).isFalse();
+    }
+
+    @Test
+    public void testReportErrorWithApkInApex() throws RemoteException {
+        when(mApexService.getActivePackages()).thenReturn(createApexInfoForTestPkg(true, true));
+        final ApexManager.ActiveApexInfo activeApex = mApexManager.getActiveApexInfos().get(0);
+        assertThat(activeApex.apexModuleName).isEqualTo(TEST_APEX_PKG);
+
+        ApexInfo[] apexInfo = createApexInfoForTestPkg(true, true);
+        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");
+    }
+
+    /**
+     * registerApkInApex method checks if the prefix of base apk path contains the apex package
+     * name. When an apex package name is a prefix of another apex package name, e.g,
+     * com.android.media and com.android.mediaprovider, then we need to ensure apk inside apex
+     * mediaprovider does not get registered under apex media.
+     */
+    @Test
+    public void testRegisterApkInApexDoesNotRegisterSimilarPrefix() throws RemoteException {
+        when(mApexService.getActivePackages()).thenReturn(createApexInfoForTestPkg(true, true));
+        final ApexManager.ActiveApexInfo activeApex = mApexManager.getActiveApexInfos().get(0);
+        assertThat(activeApex.apexModuleName).isEqualTo(TEST_APEX_PKG);
+
+        AndroidPackage fakeApkInApex = mock(AndroidPackage.class);
+        when(fakeApkInApex.getBaseApkPath()).thenReturn("/apex/" + TEST_APEX_PKG + "randomSuffix");
+        when(fakeApkInApex.getPackageName()).thenReturn("randomPackageName");
+
+        ApexInfo[] apexInfo = createApexInfoForTestPkg(true, true);
+        List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo);
+        mApexManager.notifyScanResult(scanResults);
+
+        assertThat(mApexManager.getApksInApex(activeApex.apexModuleName)).isEmpty();
+        mApexManager.registerApkInApex(fakeApkInApex);
+        assertThat(mApexManager.getApksInApex(activeApex.apexModuleName)).isEmpty();
+    }
+
+    @Test
+    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"));
+        ApexInfo[] apexInfo = new ApexInfo[]{activeApexInfo};
+        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,
+                /* isFactory= */ false, finalApex);
+        when(mApexService.installAndActivatePackage(anyString())).thenReturn(newApexInfo);
+
+        File installedApex = extractResource("installed", "test.rebootless_apex_v2.apex");
+        newApexInfo = mApexManager.installPackage(installedApex);
+
+        var newPkg = mockParsePackage(mPackageParser2, newApexInfo);
+        assertThat(newPkg.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath());
+        assertThat(newPkg.getLongVersionCode()).isEqualTo(2);
+
+        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"));
+        ApexInfo activeApexInfo = createApexInfo("test.apex_rebootless", 1, /* isActive= */ true,
+                /* isFactory= */ false, extractResource("test.apex.rebootless@1",
+                        "test.rebootless_apex_v1.apex"));
+        ApexInfo[] apexInfo = new ApexInfo[]{factoryApexInfo, activeApexInfo};
+        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,
+                /* isFactory= */ false, finalApex);
+        when(mApexService.installAndActivatePackage(anyString())).thenReturn(newApexInfo);
+
+        File installedApex = extractResource("installed", "test.rebootless_apex_v2.apex");
+        newApexInfo = mApexManager.installPackage(installedApex);
+
+        var newPkg = mockParsePackage(mPackageParser2, newApexInfo);
+        assertThat(newPkg.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath());
+        assertThat(newPkg.getLongVersionCode()).isEqualTo(2);
+
+        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 {
+        when(mApexService.installAndActivatePackage(anyString())).thenThrow(
+                new RuntimeException("install failed :("));
+
+        File installedApex = extractResource("test.apex_rebootless_v1",
+                "test.rebootless_apex_v1.apex");
+        assertThrows(PackageManagerException.class,
+                () -> mApexManager.installPackage(installedApex));
+    }
+
+    @Test
+    public void testGetActivePackageNameForApexModuleName() {
+        final String moduleName = "com.android.module_name";
+
+        ApexInfo[] apexInfo = createApexInfoForTestPkg(true, false);
+        apexInfo[0].moduleName = moduleName;
+        List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo);
+        mApexManager.notifyScanResult(scanResults);
+
+        assertThat(mApexManager.getActivePackageNameForApexModuleName(moduleName))
+                .isEqualTo(TEST_APEX_PKG);
+    }
+
+    @Test
+    public void testGetBackingApexFiles() throws Exception {
+        final ApexInfo apex = createApexInfoForTestPkg(true, true, 37);
+        when(mApexService.getActivePackages()).thenReturn(new ApexInfo[]{apex});
+
+        final File backingApexFile = mApexManager.getBackingApexFile(
+                new File(mMockSystem.system().getApexDirectory(),
+                        TEST_APEX_PKG + "/apk/App/App.apk"));
+        assertThat(backingApexFile.getAbsolutePath()).isEqualTo(apex.modulePath);
+    }
+
+    @Test
+    public void testGetBackingApexFile_fileNotOnApexMountPoint_returnsNull() {
+        File result = mApexManager.getBackingApexFile(
+                new File("/data/local/tmp/whatever/does-not-matter"));
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void testGetBackingApexFiles_unknownApex_returnsNull() throws Exception {
+        final ApexInfo apex = createApexInfoForTestPkg(true, true, 37);
+        when(mApexService.getActivePackages()).thenReturn(new ApexInfo[]{apex});
+
+        final File backingApexFile = mApexManager.getBackingApexFile(
+                new File(mMockSystem.system().getApexDirectory(), "com.wrong.apex/apk/App"));
+        assertThat(backingApexFile).isNull();
+    }
+
+    @Test
+    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() {
+        ApexManager flattenedApexManager = new ApexManager.ApexManagerFlattenedApex();
+        final File backingApexFile = flattenedApexManager.getBackingApexFile(
+                new File(mMockSystem.system().getApexDirectory(),
+                        "com.android.apex.cts.shim/app/CtsShim/CtsShim.apk"));
+        assertThat(backingApexFile).isNull();
+    }
+
+    @Test
+    public void testActiveApexChanged() throws RemoteException {
+        ApexInfo apex1 = createApexInfo(
+                "com.apex1", 37, true, true, new File("/data/apex/active/com.apex@37.apex"));
+        apex1.activeApexChanged = true;
+        apex1.preinstalledModulePath = apex1.modulePath;
+        when(mApexService.getActivePackages()).thenReturn(new ApexInfo[]{apex1});
+        final ApexManager.ActiveApexInfo activeApex = mApexManager.getActiveApexInfos().get(0);
+        assertThat(activeApex.apexModuleName).isEqualTo("com.apex1");
+        assertThat(activeApex.activeApexChanged).isTrue();
+    }
+
+    private ApexInfo createApexInfoForTestPkg(boolean isActive, boolean isFactory, int version) {
+        File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME);
+        ApexInfo apexInfo = new ApexInfo();
+        apexInfo.isActive = isActive;
+        apexInfo.isFactory = isFactory;
+        apexInfo.moduleName = TEST_APEX_PKG;
+        apexInfo.modulePath = apexFile.getPath();
+        apexInfo.versionCode = version;
+        apexInfo.preinstalledModulePath = apexFile.getPath();
+        return apexInfo;
+    }
+
+    private ApexInfo[] createApexInfoForTestPkg(boolean isActive, boolean isFactory) {
+        return new ApexInfo[]{createApexInfoForTestPkg(isActive, isFactory, 191000070)};
+    }
+
+    private ApexInfo createApexInfo(String moduleName, int versionCode, boolean isActive,
+            boolean isFactory, File apexFile) {
+        ApexInfo apexInfo = new ApexInfo();
+        apexInfo.moduleName = moduleName;
+        apexInfo.versionCode = versionCode;
+        apexInfo.isActive = isActive;
+        apexInfo.isFactory = isFactory;
+        apexInfo.modulePath = apexFile.getPath();
+        return apexInfo;
+    }
+
+    private ApexSessionInfo getFakeStagedSessionInfo() {
+        ApexSessionInfo stagedSessionInfo = new ApexSessionInfo();
+        stagedSessionInfo.sessionId = TEST_SESSION_ID;
+        stagedSessionInfo.isStaged = true;
+
+        return stagedSessionInfo;
+    }
+
+    private ApexSessionInfo getFakeUnknownSessionInfo() {
+        ApexSessionInfo stagedSessionInfo = new ApexSessionInfo();
+        stagedSessionInfo.sessionId = TEST_SESSION_ID;
+        stagedSessionInfo.isUnknown = true;
+
+        return stagedSessionInfo;
+    }
+
+    private static ApexSessionParams testParamsWithChildren() {
+        ApexSessionParams params = new ApexSessionParams();
+        params.sessionId = TEST_SESSION_ID;
+        params.childSessionIds = TEST_CHILD_SESSION_ID;
+        return params;
+    }
+
+    // Extracts the binary data from a resource and writes it to a temp file
+    private static File extractResource(String baseName, String fullResourceName) {
+        File file;
+        try {
+            file = File.createTempFile(baseName, ".apex");
+        } catch (IOException e) {
+            throw new AssertionError("CreateTempFile IOException" + e);
+        }
+
+        try (
+                InputStream in = ApexManager.class.getClassLoader()
+                        .getResourceAsStream(fullResourceName);
+                OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
+            if (in == null) {
+                throw new IllegalArgumentException("Resource not found: " + fullResourceName);
+            }
+            byte[] buf = new byte[65536];
+            int chunkSize;
+            while ((chunkSize = in.read(buf)) != -1) {
+                out.write(buf, 0, chunkSize);
+            }
+            return file;
+        } catch (IOException e) {
+            throw new AssertionError("Exception while converting stream to file" + e);
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
new file mode 100644
index 0000000..ddfbf16
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
@@ -0,0 +1,183 @@
+/*
+ * 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 org.junit.Assert.fail;
+
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
+
+import com.google.common.truth.Expect;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+/**
+ * {@link UserVisibilityListener} implementation that expects callback events to be asynchronously
+ * received.
+ */
+public final class AsyncUserVisibilityListener implements UserVisibilityListener {
+
+    private static final String TAG = AsyncUserVisibilityListener.class.getSimpleName();
+
+    private static final long WAIT_TIMEOUT_MS = 2_000;
+    private static final long WAIT_NO_EVENTS_TIMEOUT_MS = 100;
+
+    private static int sNextId;
+
+    private final Object mLock = new Object();
+    private final Expect mExpect;
+    private final int mId = ++sNextId;
+    private final Thread mExpectedReceiverThread;
+    private final CountDownLatch mLatch;
+    private final List<UserVisibilityChangedEvent> mExpectedEvents;
+
+    @GuardedBy("mLock")
+    private final List<UserVisibilityChangedEvent> mReceivedEvents = new ArrayList<>();
+
+    @GuardedBy("mLock")
+    private final List<String> mErrors = new ArrayList<>();
+
+    private AsyncUserVisibilityListener(Expect expect, Thread expectedReceiverThread,
+            List<UserVisibilityChangedEvent> expectedEvents) {
+        mExpect = expect;
+        mExpectedReceiverThread = expectedReceiverThread;
+        mExpectedEvents = expectedEvents;
+        mLatch = new CountDownLatch(expectedEvents.size());
+    }
+
+    @Override
+    public void onUserVisibilityChanged(int userId, boolean visible) {
+        UserVisibilityChangedEvent event = new UserVisibilityChangedEvent(userId, visible);
+        Thread callingThread = Thread.currentThread();
+        Log.d(TAG, "Received event (" + event + ") on thread " + callingThread);
+
+        if (callingThread != mExpectedReceiverThread) {
+            addError("event %s received in on thread %s but was expected on thread %s",
+                    event, callingThread, mExpectedReceiverThread);
+        }
+        synchronized (mLock) {
+            mReceivedEvents.add(event);
+            mLatch.countDown();
+        }
+    }
+
+    /**
+     * Verifies the expected events were called.
+     */
+    public void verify() throws InterruptedException {
+        waitForEventsAndCheckErrors();
+
+        List<UserVisibilityChangedEvent> receivedEvents = getReceivedEvents();
+
+        if (receivedEvents.isEmpty()) {
+            mExpect.withMessage("received events").that(receivedEvents).isEmpty();
+            return;
+        }
+
+        // NOTE: check "inOrder" might be too harsh in some cases (for example, if the fg user
+        // has 2 profiles, the order of the events on the profiles wouldn't matter), but we
+        // still need some dependency (like "user A became invisible before user B became
+        // visible", so this is fine for now (but eventually we might need to add more
+        // sophisticated assertions)
+        mExpect.withMessage("received events").that(receivedEvents)
+                .containsExactlyElementsIn(mExpectedEvents).inOrder();
+    }
+
+    @Override
+    public String toString() {
+        List<UserVisibilityChangedEvent> receivedEvents = getReceivedEvents();
+        return "[" + getClass().getSimpleName() + ": id=" + mId
+                + ", creationThread=" + mExpectedReceiverThread
+                + ", received=" + receivedEvents.size()
+                + ", events=" + receivedEvents + "]";
+    }
+
+    private List<UserVisibilityChangedEvent> getReceivedEvents() {
+        synchronized (mLock) {
+            return Collections.unmodifiableList(mReceivedEvents);
+        }
+    }
+
+    private void waitForEventsAndCheckErrors() throws InterruptedException {
+        waitForEvents();
+        synchronized (mLock) {
+            if (!mErrors.isEmpty()) {
+                fail(mErrors.size() + " errors on received events: " + mErrors);
+            }
+        }
+    }
+
+    private void waitForEvents() throws InterruptedException {
+        if (mExpectedEvents.isEmpty()) {
+            Log.v(TAG, "Sleeping " + WAIT_NO_EVENTS_TIMEOUT_MS + "ms to make sure no event is "
+                    + "received");
+            Thread.sleep(WAIT_NO_EVENTS_TIMEOUT_MS);
+            return;
+        }
+
+        int expectedNumberEvents = mExpectedEvents.size();
+        Log.v(TAG, "Waiting up to " + WAIT_TIMEOUT_MS + "ms until " + expectedNumberEvents
+                + " events are received");
+        if (!mLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            List<UserVisibilityChangedEvent> receivedEvents = getReceivedEvents();
+            addError("Timed out (%d ms) waiting for %d events; received %d so far (%s), "
+                    + "but expecting %d (%s)", WAIT_NO_EVENTS_TIMEOUT_MS, expectedNumberEvents,
+                    receivedEvents.size(), receivedEvents, expectedNumberEvents, mExpectedEvents);
+        }
+    }
+
+    @SuppressWarnings("AnnotateFormatMethod")
+    private void addError(String format, Object...args) {
+        synchronized (mLock) {
+            mErrors.add(String.format(format, args));
+        }
+    }
+
+    /**
+     * Factory for {@link AsyncUserVisibilityListener} objects.
+     */
+    public static final class Factory {
+        private final Expect mExpect;
+        private final Thread mExpectedReceiverThread;
+
+        public Factory(Expect expect, Thread expectedReceiverThread) {
+            mExpect = expect;
+            mExpectedReceiverThread = expectedReceiverThread;
+        }
+
+        /**
+         * Creates a {@link AsyncUserVisibilityListener} that is expecting the given events.
+         */
+        public AsyncUserVisibilityListener forEvents(UserVisibilityChangedEvent...expectedEvents) {
+            return new AsyncUserVisibilityListener(mExpect, mExpectedReceiverThread,
+                    Arrays.asList(expectedEvents));
+        }
+
+        /**
+         * Creates a {@link AsyncUserVisibilityListener} that is expecting no events.
+         */
+        public AsyncUserVisibilityListener forNoEvents() {
+            return new AsyncUserVisibilityListener(mExpect, mExpectedReceiverThread,
+                    Collections.emptyList());
+        }
+    }
+}
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..01674bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
@@ -17,6 +17,7 @@
 package com.android.server.pm;
 
 import static com.android.server.pm.BackgroundDexOptService.STATUS_DEX_OPT_FAILED;
+import static com.android.server.pm.BackgroundDexOptService.STATUS_FATAL_ERROR;
 import static com.android.server.pm.BackgroundDexOptService.STATUS_OK;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -24,7 +25,9 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
@@ -105,12 +108,16 @@
     @Mock
     private BackgroundDexOptJobService mJobServiceForIdle;
 
-    private final JobParameters mJobParametersForPostBoot = new JobParameters(null,
-            BackgroundDexOptService.JOB_POST_BOOT_UPDATE, null, null, null,
-            0, false, false, null, null, null);
-    private final JobParameters mJobParametersForIdle = new JobParameters(null,
-            BackgroundDexOptService.JOB_IDLE_OPTIMIZE, null, null, null,
-            0, false, false, null, null, null);
+    private final JobParameters mJobParametersForPostBoot =
+            createJobParameters(BackgroundDexOptService.JOB_POST_BOOT_UPDATE);
+    private final JobParameters mJobParametersForIdle =
+            createJobParameters(BackgroundDexOptService.JOB_IDLE_OPTIMIZE);
+
+    private static JobParameters createJobParameters(int jobId) {
+        JobParameters params = mock(JobParameters.class);
+        when(params.getJobId()).thenReturn(jobId);
+        return params;
+    }
 
     private BackgroundDexOptService mService;
 
@@ -223,7 +230,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 +248,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 +263,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();
@@ -264,6 +271,20 @@
     }
 
     @Test
+    public void testIdleJobFullRunWithFatalError() {
+        initUntilBootCompleted();
+        runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
+                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
+                /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
+
+        doThrow(RuntimeException.class).when(mDexOptHelper).performDexOptWithStatus(any());
+
+        runFullJob(mJobServiceForIdle, mJobParametersForIdle,
+                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_FATAL_ERROR,
+                /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
+    }
+
+    @Test
     public void testSystemReadyWhenDisabled() {
         when(mInjector.isBackgroundDexOptDisabled()).thenReturn(true);
 
@@ -393,7 +414,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 +442,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);
     }
 
@@ -510,13 +531,21 @@
         ArgumentCaptor<Runnable> argThreadRunnable = ArgumentCaptor.forClass(Runnable.class);
         verify(mInjector, atLeastOnce()).createAndStartThread(any(), argThreadRunnable.capture());
 
-        argThreadRunnable.getValue().run();
+        try {
+            argThreadRunnable.getValue().run();
+        } catch (RuntimeException e) {
+            if (expectedStatus != STATUS_FATAL_ERROR) {
+                throw e;
+            }
+        }
 
         verify(jobService, times(totalJobFinishedWithParams)).jobFinished(params,
                 expectedReschedule);
         // Never block
         verify(mDexOptHelper, never()).controlDexOptBlocking(true);
-        verifyPerformDexOpt();
+        if (expectedStatus != STATUS_FATAL_ERROR) {
+            verifyPerformDexOpt();
+        }
         assertThat(getLastExecutionStatus()).isEqualTo(expectedStatus);
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
index 7ccd6d9..e0662c4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
@@ -51,6 +51,7 @@
 
         mUserManagerInternal = rule.mocks().injector.userManagerInternal
         whenever(mUserManagerInternal.getUserIds()).thenReturn(intArrayOf(0, 1))
+        whenever(mUserManagerInternal.getUserTypesForStatsd(any())).thenReturn(intArrayOf(1, 1))
 
         mPms = createPackageManagerService()
         doAnswer { false }.`when`(mPms).isPackageDeviceAdmin(any(), any())
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..27d0662 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 =
@@ -136,7 +139,7 @@
                 .strictness(Strictness.LENIENT)
                 .mockStatic(SystemProperties::class.java)
                 .mockStatic(SystemConfig::class.java)
-                .mockStatic(SELinuxMMAC::class.java)
+                .mockStatic(SELinuxMMAC::class.java, Mockito.CALLS_REAL_METHODS)
                 .mockStatic(FallbackCategoryProvider::class.java)
                 .mockStatic(PackageManagerServiceUtils::class.java)
                 .mockStatic(Environment::class.java)
@@ -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/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index 1464405..cfd5279 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -723,7 +723,7 @@
         params.isStaged = true;
 
         InstallSource installSource = InstallSource.create("testInstallInitiator",
-                "testInstallOriginator", "testInstaller", "testAttributionTag",
+                "testInstallOriginator", "testInstaller", 100, "testAttributionTag",
                 PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
 
         PackageInstallerSession session = new PackageInstallerSession(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
deleted file mode 100644
index 6c85b7a..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
+++ /dev/null
@@ -1,213 +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.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.annotation.UserIdInt;
-import android.app.ActivityManagerInternal;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.os.UserManager;
-import android.util.Log;
-import android.util.SparseArray;
-
-import androidx.test.annotation.UiThreadTest;
-
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
-import com.android.server.ExtendedMockitoTestCase;
-import com.android.server.LocalServices;
-import com.android.server.am.UserState;
-import com.android.server.pm.UserManagerService.UserData;
-
-import org.junit.After;
-import org.junit.Before;
-import org.mockito.Mock;
-
-/**
- * Base class for {@link UserManagerInternalTest} and {@link UserManagerInternalTest}.
- *
- * <p>{@link UserManagerService} and its {@link UserManagerInternal} implementation have a
- * "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
- * 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();
-
-    /**
-     * Id for a simple user (that doesn't have profiles).
-     */
-    protected static final int USER_ID = 600;
-
-    /**
-     * Id for another simple user.
-     */
-    protected static final int OTHER_USER_ID = 666;
-
-    /**
-     * Id for a user that has one profile (whose id is {@link #PROFILE_USER_ID}.
-     *
-     * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
-     */
-    protected static final int PARENT_USER_ID = 642;
-
-    /**
-     * Id for a profile whose parent is {@link #PARENTUSER_ID}.
-     *
-     * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
-     */
-    protected static final int PROFILE_USER_ID = 643;
-
-    private final Object mPackagesLock = new Object();
-    private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
-            .getTargetContext();
-    private final SparseArray<UserData> mUsers = new SparseArray<>();
-
-    private Context mSpiedContext;
-
-    private @Mock PackageManagerService mMockPms;
-    private @Mock UserDataPreparer mMockUserDataPreparer;
-    private @Mock ActivityManagerInternal mActivityManagerInternal;
-
-    /**
-     * Reference to the {@link UserManagerService} being tested.
-     */
-    protected UserManagerService mUms;
-
-    /**
-     * Reference to the {@link UserManagerInternal} being tested.
-     */
-    protected UserManagerInternal mUmi;
-
-    @Override
-    protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        builder
-                .spyStatic(UserManager.class)
-                .spyStatic(LocalServices.class);
-    }
-
-    @Before
-    @UiThreadTest // Needed to initialize main handler
-    public final void setFixtures() {
-        mSpiedContext = spy(mRealContext);
-
-        // Called when WatchedUserStates is constructed
-        doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());
-
-        // 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();
-    }
-
-    @After
-    public final void resetUserManagerInternal() {
-        // LocalServices follows the "Highlander rule" - There can be only one!
-        LocalServices.removeServiceForTest(UserManagerInternal.class);
-    }
-
-    ///////////////////////////////////////////
-    // Helper methods exposed to sub-classes //
-    ///////////////////////////////////////////
-
-    protected final void mockCurrentUser(@UserIdInt int userId) {
-        mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
-
-        when(mActivityManagerInternal.getCurrentUserId()).thenReturn(userId);
-    }
-
-    protected final <T> void mockGetLocalService(Class<T> serviceClass, T service) {
-        doReturn(service).when(() -> LocalServices.getService(serviceClass));
-    }
-
-    protected final void addDefaultProfileAndParent() {
-        addUser(PARENT_USER_ID);
-        addProfile(PROFILE_USER_ID, PARENT_USER_ID);
-    }
-
-    protected final void addProfile(@UserIdInt int profileId, @UserIdInt int parentId) {
-        TestUserData profileData = new TestUserData(profileId);
-        profileData.info.flags = UserInfo.FLAG_PROFILE;
-        profileData.info.profileGroupId = parentId;
-
-        addUserData(profileData);
-    }
-
-    protected final void addUser(@UserIdInt int userId) {
-        TestUserData userData = new TestUserData(userId);
-
-        addUserData(userData);
-    }
-
-    protected final void startDefaultProfile() {
-        startUser(PROFILE_USER_ID);
-    }
-
-    protected final void stopDefaultProfile() {
-        stopUser(PROFILE_USER_ID);
-    }
-
-    protected final void startUser(@UserIdInt int userId) {
-        setUserState(userId, UserState.STATE_RUNNING_UNLOCKED);
-    }
-
-    protected final void stopUser(@UserIdInt int userId) {
-        setUserState(userId, UserState.STATE_STOPPING);
-    }
-
-    protected final void setUserState(@UserIdInt int userId, int userState) {
-        mUmi.setUserState(userId, userState);
-    }
-
-    ///////////////////
-    // Private infra //
-    ///////////////////
-
-    private void addUserData(TestUserData userData) {
-        Log.d(TAG, "Adding " + userData);
-        mUsers.put(userData.info.id, userData);
-    }
-
-    private static final class TestUserData extends UserData {
-
-        @SuppressWarnings("deprecation")
-        TestUserData(@UserIdInt int userId) {
-            info = new UserInfo();
-            info.id = userId;
-        }
-
-        @Override
-        public String toString() {
-            return "TestUserData[" + info.toFullString() + "]";
-        }
-    }
-}
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 b5ffe5f..1367af8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -15,17 +15,116 @@
  */
 package com.android.server.pm;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.app.ActivityManagerInternal;
-import android.os.UserHandle;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.test.annotation.UiThreadTest;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.LocalServices;
+import com.android.server.am.UserState;
+import com.android.server.pm.UserManagerService.UserData;
+
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
 
 /**
  * Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest}
  */
-public final class UserManagerServiceTest extends UserManagerServiceOrInternalTestCase {
+public final class UserManagerServiceTest extends ExtendedMockitoTestCase {
+
+    private static final String TAG = UserManagerServiceTest.class.getSimpleName();
+
+    /**
+     * Id for a simple user (that doesn't have profiles).
+     */
+    private static final int USER_ID = 600;
+
+    /**
+     * Id for another simple user.
+     */
+    private static final int OTHER_USER_ID = 666;
+
+    /**
+     * Id for a user that has one profile (whose id is {@link #PROFILE_USER_ID}.
+     *
+     * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
+     */
+    private static final int PARENT_USER_ID = 642;
+
+    /**
+     * Id for a profile whose parent is {@link #PARENTUSER_ID}.
+     *
+     * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
+     */
+    private static final int PROFILE_USER_ID = 643;
+
+    private final Object mPackagesLock = new Object();
+    private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
+            .getTargetContext();
+    private final SparseArray<UserData> mUsers = new SparseArray<>();
+
+    private Context mSpiedContext;
+
+    private @Mock PackageManagerService mMockPms;
+    private @Mock UserDataPreparer mMockUserDataPreparer;
+    private @Mock ActivityManagerInternal mActivityManagerInternal;
+
+    /**
+     * Reference to the {@link UserManagerService} being tested.
+     */
+    private UserManagerService mUms;
+
+    /**
+     * Reference to the {@link UserManagerInternal} being tested.
+     */
+    private UserManagerInternal mUmi;
+
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder
+                .spyStatic(UserManager.class)
+                .spyStatic(LocalServices.class);
+    }
+
+    @Before
+    @UiThreadTest // Needed to initialize main handler
+    public void setFixtures() {
+        mSpiedContext = spy(mRealContext);
+
+        // Called when WatchedUserStates is constructed
+        doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());
+
+        // 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();
+    }
+
+    @After
+    public void resetUserManagerInternal() {
+        // LocalServices follows the "Highlander rule" - There can be only one!
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+    }
 
     @Test
     public void testGetCurrentUserId_amInternalNotReady() {
@@ -123,4 +222,72 @@
         assertWithMessage("isUserRunning(%s)", PROFILE_USER_ID)
                 .that(mUms.isUserRunning(PROFILE_USER_ID)).isFalse();
     }
+
+    private void mockCurrentUser(@UserIdInt int userId) {
+        mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
+
+        when(mActivityManagerInternal.getCurrentUserId()).thenReturn(userId);
+    }
+
+    private <T> void mockGetLocalService(Class<T> serviceClass, T service) {
+        doReturn(service).when(() -> LocalServices.getService(serviceClass));
+    }
+
+    private void addDefaultProfileAndParent() {
+        addUser(PARENT_USER_ID);
+        addProfile(PROFILE_USER_ID, PARENT_USER_ID);
+    }
+
+    private void addProfile(@UserIdInt int profileId, @UserIdInt int parentId) {
+        TestUserData profileData = new TestUserData(profileId);
+        profileData.info.flags = UserInfo.FLAG_PROFILE;
+        profileData.info.profileGroupId = parentId;
+
+        addUserData(profileData);
+    }
+
+    private void addUser(@UserIdInt int userId) {
+        TestUserData userData = new TestUserData(userId);
+
+        addUserData(userData);
+    }
+
+    private void startDefaultProfile() {
+        startUser(PROFILE_USER_ID);
+    }
+
+    private void stopDefaultProfile() {
+        stopUser(PROFILE_USER_ID);
+    }
+
+    private void startUser(@UserIdInt int userId) {
+        setUserState(userId, UserState.STATE_RUNNING_UNLOCKED);
+    }
+
+    private void stopUser(@UserIdInt int userId) {
+        setUserState(userId, UserState.STATE_STOPPING);
+    }
+
+    private void setUserState(@UserIdInt int userId, int userState) {
+        mUmi.setUserState(userId, userState);
+    }
+
+    private void addUserData(TestUserData userData) {
+        Log.d(TAG, "Adding " + userData);
+        mUsers.put(userData.info.id, userData);
+    }
+
+    private static final class TestUserData extends UserData {
+
+        @SuppressWarnings("deprecation")
+        TestUserData(@UserIdInt int userId) {
+            info = new UserInfo();
+            info.id = userId;
+        }
+
+        @Override
+        public String toString() {
+            return "TestUserData[" + info.toFullString() + "]";
+        }
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityChangedEvent.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityChangedEvent.java
new file mode 100644
index 0000000..58a265b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityChangedEvent.java
@@ -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.server.pm;
+
+import android.annotation.UserIdInt;
+
+/**
+ * Representation of a {@link UserManagerInternal.UserVisibilityListener} event.
+ */
+public final class UserVisibilityChangedEvent {
+
+    public @UserIdInt int userId;
+    public boolean visible;
+
+    UserVisibilityChangedEvent(@UserIdInt int userId, boolean visible) {
+        this.userId = userId;
+        this.visible = visible;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + userId;
+        result = prime * result + (visible ? 1231 : 1237);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        UserVisibilityChangedEvent other = (UserVisibilityChangedEvent) obj;
+        if (userId != other.userId) return false;
+        if (visible != other.visible) return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return userId + ":" + (visible ? "visible" : "invisible");
+    }
+
+    /**
+     * Factory method.
+     */
+    public static UserVisibilityChangedEvent onVisible(@UserIdInt int userId) {
+        return new UserVisibilityChangedEvent(userId, /* visible= */ true);
+    }
+
+    /**
+     * Factory method.
+     */
+    public static UserVisibilityChangedEvent onInvisible(@UserIdInt int userId) {
+        return new UserVisibilityChangedEvent(userId, /* visible= */ false);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
index 21f541f..6d8910e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -15,15 +15,16 @@
  */
 package com.android.server.pm;
 
-import static android.os.UserHandle.USER_SYSTEM;
+import static android.os.UserHandle.USER_NULL;
 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 static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
+import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
+import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
 
 import org.junit.Test;
 
@@ -36,268 +37,205 @@
  */
 public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediatorTestCase {
 
-    private static final String TAG = UserVisibilityMediatorMUMDTest.class.getSimpleName();
-
-    public UserVisibilityMediatorMUMDTest() {
+    public UserVisibilityMediatorMUMDTest() throws Exception {
         super(/* usersOnSecondaryDisplaysEnabled= */ true);
     }
 
     @Test
-    public void testAssignUserToDisplay_systemUser() {
-        assertThrows(IllegalArgumentException.class,
-                () -> mMediator.assignUserToDisplay(USER_SYSTEM, SECONDARY_DISPLAY_ID));
+    public void testStartFgUser_onDefaultDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(USER_ID));
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(USER_ID);
+        expectUserIsVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+        expectVisibleUsers(USER_ID);
+
+        expectDisplayAssignedToUser(USER_ID, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, USER_ID);
+        expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+        expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
+
+        listener.verify();
     }
 
     @Test
-    public void testAssignUserToDisplay_invalidDisplay() {
-        assertThrows(IllegalArgumentException.class,
-                () -> mMediator.assignUserToDisplay(USER_ID, INVALID_DISPLAY));
+    public void testSwitchFgUser_onDefaultDisplay() throws Exception {
+        int previousCurrentUserId = OTHER_USER_ID;
+        int currentUserId = USER_ID;
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(previousCurrentUserId),
+                onInvisible(previousCurrentUserId),
+                onVisible(currentUserId));
+        startForegroundUser(previousCurrentUserId);
+
+        int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(currentUserId);
+        expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
+        expectVisibleUsers(currentUserId);
+
+        expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
+        expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
+
+        expectUserIsNotVisibleAtAll(previousCurrentUserId);
+        expectNoDisplayAssignedToUser(previousCurrentUserId);
+
+        listener.verify();
     }
 
     @Test
-    public void testAssignUserToDisplay_currentUser() {
-        mockCurrentUser(USER_ID);
+    public void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(PARENT_USER_ID),
+                onVisible(PROFILE_USER_ID));
+        startForegroundUser(PARENT_USER_ID);
 
-        assertThrows(IllegalArgumentException.class,
-                () -> mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
 
-        assertNoUserAssignedToDisplay();
+        expectUserIsVisible(PROFILE_USER_ID);
+        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
+
+        expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+
+        listener.verify();
     }
 
     @Test
-    public void testAssignUserToDisplay_startedProfileOfCurrentUser() {
-        mockCurrentUser(PARENT_USER_ID);
-        addDefaultProfileAndParent();
+    public void testStartFgUser_onInvalidDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG, INVALID_DISPLAY);
+
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        listener.verify();
+    }
+
+    @Test
+    public void testStartBgUser_onInvalidDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG, INVALID_DISPLAY);
+
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsNotVisibleAtAll(USER_ID);
+
+        listener.verify();
+    }
+
+    @Test
+    public void testStartBgUser_onSecondaryDisplay_displayAvailable() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+                SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(USER_ID);
+        expectUserIsVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+        expectVisibleUsers(INITIAL_CURRENT_USER_ID, USER_ID);
+
+        expectDisplayAssignedToUser(USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+        listener.verify();
+    }
+
+    @Test
+    public void testVisibilityOfCurrentUserAndProfilesOnDisplayAssignedToAnotherUser()
+            throws Exception {
         startDefaultProfile();
 
-        IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
-                () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+        // Make sure they were visible before
+        expectUserIsNotVisibleOnDisplay("before", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay("before", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
 
-        Log.v(TAG, "Exception: " + e);
-        assertNoUserAssignedToDisplay();
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+                SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsNotVisibleOnDisplay("after", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay("after", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
     }
 
     @Test
-    public void testAssignUserToDisplay_stoppedProfileOfCurrentUser() {
-        mockCurrentUser(PARENT_USER_ID);
-        addDefaultProfileAndParent();
-        stopDefaultProfile();
+    public void testStartBgUser_onSecondaryDisplay_displayAlreadyAssigned() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(OTHER_USER_ID));
+        startUserInSecondaryDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
 
-        IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
-                () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+                SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
-        Log.v(TAG, "Exception: " + e);
-        assertNoUserAssignedToDisplay();
+        expectUserIsNotVisibleAtAll(USER_ID);
+        expectNoDisplayAssignedToUser(USER_ID);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, OTHER_USER_ID);
+
+        listener.verify();
     }
 
     @Test
-    public void testAssignUserToDisplay_displayAvailable() {
-        mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+    public void testStartBgUser_onSecondaryDisplay_userAlreadyAssigned() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
+        startUserInSecondaryDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
-        assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+                SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsVisible(USER_ID);
+        expectUserIsVisibleOnDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+        expectVisibleUsers(INITIAL_CURRENT_USER_ID, USER_ID);
+
+        expectDisplayAssignedToUser(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+        expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, USER_ID);
+
+        listener.verify();
     }
 
     @Test
-    public void testAssignUserToDisplay_displayAlreadyAssigned() {
-        mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+    public void testStartBgProfile_onDefaultDisplay_whenParentVisibleOnSecondaryDisplay()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
+        startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
-        IllegalStateException e = assertThrows(IllegalStateException.class,
-                () -> mMediator.assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID));
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
-        Log.v(TAG, "Exception: " + e);
-        assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
-                .matches("Cannot.*" + OTHER_USER_ID + ".*" + SECONDARY_DISPLAY_ID + ".*already.*"
-                        + USER_ID + ".*");
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+        expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
+
+        listener.verify();
     }
-
-    @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
index 7ae8117..1065392 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -15,9 +15,15 @@
  */
 package com.android.server.pm;
 
-import static android.os.UserHandle.USER_SYSTEM;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 
-import static org.junit.Assert.assertThrows;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
+import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
+import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
 
 import org.junit.Test;
 
@@ -35,41 +41,100 @@
     }
 
     @Test
-    public void testAssignUserToDisplay_otherDisplay_currentUser() {
-        mockCurrentUser(USER_ID);
+    public void testStartFgUser_onDefaultDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(USER_ID));
 
-        assertThrows(UnsupportedOperationException.class,
-                () -> mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(USER_ID);
+        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+        expectUserIsVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+        expectUserIsVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+        expectVisibleUsers(USER_ID);
+
+        expectDisplayAssignedToUser(USER_ID, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, USER_ID);
+        expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+        expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
+
+        listener.verify();
     }
 
     @Test
-    public void testAssignUserToDisplay_otherDisplay_startProfileOfcurrentUser() {
-        mockCurrentUser(PARENT_USER_ID);
-        addDefaultProfileAndParent();
-        startDefaultProfile();
+    public void testSwitchFgUser_onDefaultDisplay() throws Exception {
+        int previousCurrentUserId = OTHER_USER_ID;
+        int currentUserId = USER_ID;
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(previousCurrentUserId),
+                onInvisible(previousCurrentUserId),
+                onVisible(currentUserId));
+        startForegroundUser(previousCurrentUserId);
 
-        assertThrows(UnsupportedOperationException.class,
-                () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+        int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(currentUserId);
+        expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
+        expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
+        expectUserIsVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
+        expectVisibleUsers(currentUserId);
+
+        expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
+        expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
+
+        expectUserIsNotVisibleAtAll(previousCurrentUserId);
+        expectNoDisplayAssignedToUser(previousCurrentUserId);
+
+        listener.verify();
     }
 
     @Test
-    public void testAssignUserToDisplay_otherDisplay_stoppedProfileOfcurrentUser() {
-        mockCurrentUser(PARENT_USER_ID);
-        addDefaultProfileAndParent();
-        stopDefaultProfile();
+    public void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(PARENT_USER_ID),
+                onVisible(PROFILE_USER_ID));
+        startForegroundUser(PARENT_USER_ID);
 
-        assertThrows(UnsupportedOperationException.class,
-                () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(PROFILE_USER_ID);
+        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+        expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
+
+        expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+
+        listener.verify();
     }
 
     @Test
-    public void testUnassignUserFromDisplay_ignored() {
-        mockCurrentUser(USER_ID);
+    public void testStartBgUser_onSecondaryDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
-        mMediator.unassignUserFromDisplay(USER_SYSTEM);
-        mMediator.unassignUserFromDisplay(USER_ID);
-        mMediator.unassignUserFromDisplay(OTHER_USER_ID);
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+                SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
-        assertNoUserAssignedToDisplay();
+        expectUserIsNotVisibleAtAll(USER_ID);
+        expectNoDisplayAssignedToUser(USER_ID);
+
+        expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+
+        listener.verify();
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 22e6e0d..4487d13 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -15,22 +15,39 @@
  */
 package com.android.server.pm;
 
+import static android.os.UserHandle.USER_ALL;
+import static android.os.UserHandle.USER_CURRENT;
+import static android.os.UserHandle.USER_CURRENT_OR_SELF;
 import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
 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.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
+import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
+import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
+import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertThrows;
+
 import android.annotation.UserIdInt;
-import android.util.SparseIntArray;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.IntArray;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.ExtendedMockitoTestCase;
 
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.LinkedHashMap;
-import java.util.Map;
+import java.util.Arrays;
 
 /**
  * Base class for {@link UserVisibilityMediator} tests.
@@ -38,8 +55,39 @@
  * <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).
+ *
+ * <p><P>NOTE: <p> rather than adopting the "one test case for method approach", this class (and
+ * its subclass) adds "one test case for scenario" approach, so it can test many properties (if user
+ * is visible, display associated to the user, etc...) for each scenario (full user started on fg,
+ * profile user started on bg, etc...).
  */
-abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrInternalTestCase {
+abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
+
+    private static final String TAG = UserVisibilityMediatorTestCase.class.getSimpleName();
+
+    /**
+     * Id for a simple user (that doesn't have profiles).
+     */
+    protected static final int USER_ID = 600;
+
+    /**
+     * Id for another simple user.
+     */
+    protected static final int OTHER_USER_ID = 666;
+
+    /**
+     * Id for a user that has one profile (whose id is {@link #PROFILE_USER_ID}.
+     *
+     * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
+     */
+    protected static final int PARENT_USER_ID = 642;
+
+    /**
+     * Id for a profile whose parent is {@link #PARENTUSER_ID}.
+     *
+     * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
+     */
+    protected static final int PROFILE_USER_ID = 643;
 
     /**
      * Id of a secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
@@ -51,12 +99,13 @@
      */
     protected static final int OTHER_SECONDARY_DISPLAY_ID = 108;
 
-    private final boolean mUsersOnSecondaryDisplaysEnabled;
+    protected static final boolean FG = true;
+    protected static final boolean BG = false;
 
-    // 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();
+    private Handler mHandler;
+    protected AsyncUserVisibilityListener.Factory mListenerFactory;
+
+    private final boolean mUsersOnSecondaryDisplaysEnabled;
 
     protected UserVisibilityMediator mMediator;
 
@@ -65,259 +114,383 @@
     }
 
     @Before
-    public final void setMediator() {
-        mMediator = new UserVisibilityMediator(mUms, mUsersOnSecondaryDisplaysEnabled,
-                mUsersOnSecondaryDisplays);
+    public final void setFixtures() {
+        mHandler = Handler.getMain();
+        Thread thread = mHandler.getLooper().getThread();
+        Log.i(TAG, "setFixtures(): using thread " + thread + " (from handler " + mHandler + ")");
+        mListenerFactory = new AsyncUserVisibilityListener.Factory(mExpect, thread);
+        mMediator = new UserVisibilityMediator(mUsersOnSecondaryDisplaysEnabled, mHandler);
+        mDumpableDumperRule.addDumpable(mMediator);
     }
 
     @Test
-    public final void testAssignUserToDisplay_defaultDisplayIgnored() {
-        mMediator.assignUserToDisplay(USER_ID, DEFAULT_DISPLAY);
-
-        assertNoUserAssignedToDisplay();
+    public final void testAssignUserToDisplayOnStart_invalidUserIds() {
+        assertThrows(IllegalArgumentException.class, () -> mMediator
+                .assignUserToDisplayOnStart(USER_NULL, USER_ID, FG, DEFAULT_DISPLAY));
+        assertThrows(IllegalArgumentException.class, () -> mMediator
+                .assignUserToDisplayOnStart(USER_ALL, USER_ID, FG, DEFAULT_DISPLAY));
+        assertThrows(IllegalArgumentException.class, () -> mMediator
+                .assignUserToDisplayOnStart(USER_CURRENT, USER_ID, FG, DEFAULT_DISPLAY));
+        assertThrows(IllegalArgumentException.class, () -> mMediator
+                .assignUserToDisplayOnStart(USER_CURRENT_OR_SELF, USER_ID, FG, DEFAULT_DISPLAY));
     }
 
     @Test
-    public final void testIsUserVisible_invalidUser() {
-        mockCurrentUser(USER_ID);
+    public final void testStartFgUser_onSecondaryDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
-        assertWithMessage("isUserVisible(%s)", USER_NULL)
-                .that(mMediator.isUserVisible(USER_NULL)).isFalse();
+        int result =
+                mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsNotVisibleAtAll(USER_ID);
+        expectNoDisplayAssignedToUser(USER_ID);
+        expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+
+        listener.verify();
     }
 
     @Test
-    public final void testIsUserVisible_currentUser() {
-        mockCurrentUser(USER_ID);
+    public final void testStartBgUser_onDefaultDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
-        assertWithMessage("isUserVisible(%s)", USER_ID)
-                .that(mMediator.isUserVisible(USER_ID)).isTrue();
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+
+        expectUserIsNotVisibleAtAll(USER_ID);
+        expectNoDisplayAssignedToUser(USER_ID);
+        expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+
+        listener.verify();
     }
 
     @Test
-    public final void testIsUserVisible_nonCurrentUser() {
-        mockCurrentUser(OTHER_USER_ID);
+    public final void testStartBgSystemUser_onSecondaryDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(USER_ID));
+        // Must explicitly set current user, as USER_SYSTEM is the default current user
+        startForegroundUser(USER_ID);
 
-        assertWithMessage("isUserVisible(%s)", USER_ID)
-                .that(mMediator.isUserVisible(USER_ID)).isFalse();
+        int result = mMediator.assignUserToDisplayOnStart(USER_SYSTEM, USER_SYSTEM, BG,
+                SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsNotVisibleAtAll(USER_SYSTEM);
+
+        expectNoDisplayAssignedToUser(USER_SYSTEM);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+        listener.verify();
     }
 
     @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);
+    public final void testStopVisibleProfile() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(PARENT_USER_ID),
+                onVisible(PROFILE_USER_ID),
+                onInvisible(PROFILE_USER_ID));
         startDefaultProfile();
 
-        assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
-                .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
+        mMediator.unassignUserFromDisplayOnStop(PROFILE_USER_ID);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+
+        listener.verify();
     }
 
     @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);
+    public final void testVisibleProfileBecomesInvisibleWhenParentIsSwitchedOut() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(PARENT_USER_ID),
+                onVisible(PROFILE_USER_ID),
+                onInvisible(PARENT_USER_ID),
+                onInvisible(PROFILE_USER_ID),
+                onVisible(OTHER_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();
+        startForegroundUser(OTHER_USER_ID);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, OTHER_USER_ID);
+
+        listener.verify();
     }
 
     @Test
-    public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserDefaultDisplay() {
-        addDefaultProfileAndParent();
-        mockCurrentUser(PARENT_USER_ID);
-        stopDefaultProfile();
+    public final void testStartBgProfile_onDefaultDisplay_whenParentIsNotStarted()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
-        assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
-                .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+
+        listener.verify();
     }
 
     @Test
-    public final void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplay() {
-        addDefaultProfileAndParent();
-        mockCurrentUser(PARENT_USER_ID);
-        startDefaultProfile();
-        setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
+    public final void testStartBgProfile_onDefaultDisplay_whenParentIsStartedOnBg()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
+        startBackgroundUser(PARENT_USER_ID);
 
-        assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
-                .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+        expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+
+        listener.verify();
+    }
+
+    // Not supported - profiles can only be started on default display
+    @Test
+    public final void testStartBgProfile_onSecondaryDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+                SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+        expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+
+        listener.verify();
     }
 
     @Test
-    public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserSecondaryDisplay() {
-        addDefaultProfileAndParent();
-        mockCurrentUser(PARENT_USER_ID);
-        stopDefaultProfile();
+    public final void testStartFgProfile_onDefaultDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
-        assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
-                .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, FG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+        expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+
+        listener.verify();
     }
 
     @Test
-    public void testGetDisplayAssignedToUser_invalidUser() {
-        mockCurrentUser(USER_ID);
+    public final void testStartFgProfile_onSecondaryDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
-        assertWithMessage("getDisplayAssignedToUser(%s)", USER_NULL)
-                .that(mMediator.getDisplayAssignedToUser(USER_NULL)).isEqualTo(INVALID_DISPLAY);
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, FG,
+                SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+        expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+
+        listener.verify();
     }
 
     @Test
-    public void testGetDisplayAssignedToUser_currentUser() {
-        mockCurrentUser(USER_ID);
-
-        assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
-                .that(mMediator.getDisplayAssignedToUser(USER_ID)).isEqualTo(DEFAULT_DISPLAY);
+    public final void testIsUserVisible_invalidUsers() throws Exception {
+        expectWithMessage("isUserVisible(%s)", USER_NULL)
+                .that(mMediator.isUserVisible(USER_NULL))
+                .isFalse();
+        expectWithMessage("isUserVisible(%s)", USER_NULL)
+                .that(mMediator.isUserVisible(USER_ALL))
+                .isFalse();
+        expectWithMessage("isUserVisible(%s)", USER_NULL)
+                .that(mMediator.isUserVisible(USER_CURRENT))
+                .isFalse();
+        expectWithMessage("isUserVisible(%s)", USER_NULL)
+                .that(mMediator.isUserVisible(USER_CURRENT_OR_SELF))
+                .isFalse();
     }
 
     @Test
-    public final void testGetDisplayAssignedToUser_nonCurrentUser() {
-        mockCurrentUser(OTHER_USER_ID);
+    public final void testRemoveListener() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
-        assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
-                .that(mMediator.getDisplayAssignedToUser(USER_ID)).isEqualTo(INVALID_DISPLAY);
+        mMediator.removeListener(listener);
+
+        startForegroundUser(USER_ID);
+        listener.verify();
     }
 
     @Test
-    public final void testGetDisplayAssignedToUser_startedProfileOfcurrentUser() {
-        addDefaultProfileAndParent();
-        mockCurrentUser(PARENT_USER_ID);
-        startDefaultProfile();
-        setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
+    public final void testOnSystemUserVisibilityChanged() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_SYSTEM));
 
-        assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
-                .that(mMediator.getDisplayAssignedToUser(PROFILE_USER_ID))
-                .isEqualTo(DEFAULT_DISPLAY);
+        mMediator.onSystemUserVisibilityChanged(/* visible= */ true);
+
+        listener.verify();
     }
 
-    @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));
+    /**
+     * Starts a user in foreground on the default display, asserting it was properly started.
+     *
+     * <p><b>NOTE: </b>should only be used as a helper method, not to test the behavior of the
+     * {@link UserVisibilityMediator#assignUserToDisplayOnStart(int, int, boolean, int)} method per
+     * se.
+     */
+    protected void startForegroundUser(@UserIdInt int userId) {
+        Log.d(TAG, "startForegroundUSer(" + userId + ")");
+        int result = mMediator.assignUserToDisplayOnStart(userId, userId, FG, DEFAULT_DISPLAY);
+        if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
+            throw new IllegalStateException("Failed to start foreground user " + userId
+                    + ": mediator returned " + userAssignmentResultToString(result));
         }
-        return map;
+    }
+
+    /**
+     * Starts a user in background on the default display, asserting it was properly started.
+     *
+     * <p><b>NOTE: </b>should only be used as a helper method, not to test the behavior of the
+     * {@link UserVisibilityMediator#assignUserToDisplayOnStart(int, int, boolean, int)} method per
+     * se.
+     */
+    protected void startBackgroundUser(@UserIdInt int userId) {
+        Log.d(TAG, "startBackgroundUser(" + userId + ")");
+        int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG, DEFAULT_DISPLAY);
+        if (result != USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE) {
+            throw new IllegalStateException("Failed to start background user " + userId
+                    + ": mediator returned " + userAssignmentResultToString(result));
+        }
+    }
+
+    /**
+     * Starts the {@link #PROFILE_USER_ID default profile} in background and its
+     * {@link #PARENT_USER_ID parent} in foreground on the main display, asserting that
+     * both were properly started.
+     *
+     * <p><b>NOTE: </b>should only be used as a helper method, not to test the behavior of the
+     * {@link UserVisibilityMediator#assignUserToDisplayOnStart(int, int, boolean, int)} method per
+     * se.
+     */
+    protected void startDefaultProfile() {
+        startForegroundUser(PARENT_USER_ID);
+        Log.d(TAG, "starting default profile (" + PROFILE_USER_ID + ") in background after starting"
+                + " its parent (" + PARENT_USER_ID + ") on foreground");
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+                DEFAULT_DISPLAY);
+        if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
+            throw new IllegalStateException("Failed to start profile user " + PROFILE_USER_ID
+                    + ": mediator returned " + userAssignmentResultToString(result));
+        }
+    }
+
+    /**
+     * Starts a user in background on the secondary display, asserting it was properly started.
+     *
+     * <p><b>NOTE: </b>should only be used as a helper method, not to test the behavior of the
+     * {@link UserVisibilityMediator#assignUserToDisplayOnStart(int, int, boolean, int)} method per
+     * se.
+     */
+    protected final void startUserInSecondaryDisplay(@UserIdInt int userId, int displayId) {
+        Preconditions.checkArgument(displayId != INVALID_DISPLAY && displayId != DEFAULT_DISPLAY,
+                "must pass a secondary display, not %d", displayId);
+        Log.d(TAG, "startUserInSecondaryDisplay(" + userId + ", " + displayId + ")");
+        int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG, displayId);
+        if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
+            throw new IllegalStateException("Failed to startuser " + userId
+                    + " on background: mediator returned " + userAssignmentResultToString(result));
+        }
+    }
+
+    protected AsyncUserVisibilityListener addListenerForNoEvents() {
+        AsyncUserVisibilityListener listener = mListenerFactory.forNoEvents();
+        mMediator.addListener(listener);
+        return listener;
+    }
+
+    protected AsyncUserVisibilityListener addListenerForEvents(
+            UserVisibilityChangedEvent... events) {
+        AsyncUserVisibilityListener listener = mListenerFactory.forEvents(events);
+        mMediator.addListener(listener);
+        return listener;
+    }
+
+    protected void assertStartUserResult(int actualResult, int expectedResult) {
+        assertWithMessage("startUser() result (where %s=%s and %s=%s)",
+                expectedResult, userAssignmentResultToString(expectedResult),
+                actualResult, userAssignmentResultToString(actualResult))
+                        .that(actualResult).isEqualTo(expectedResult);
+    }
+
+    protected void expectUserIsVisible(@UserIdInt int userId) {
+        expectWithMessage("mediator.isUserVisible(%s)", userId)
+                .that(mMediator.isUserVisible(userId))
+                .isTrue();
+    }
+
+    protected void expectVisibleUsers(@UserIdInt Integer... userIds) {
+        IntArray visibleUsers = mMediator.getVisibleUsers();
+        expectWithMessage("getVisibleUsers()").that(visibleUsers).isNotNull();
+        expectWithMessage("getVisibleUsers()").that(visibleUsers.toArray()).asList()
+                .containsExactlyElementsIn(Arrays.asList(userIds));
+    }
+
+    protected void expectUserIsVisibleOnDisplay(@UserIdInt int userId, int displayId) {
+        expectWithMessage("mediator.isUserVisible(%s, %s)", userId, displayId)
+                .that(mMediator.isUserVisible(userId, displayId))
+                .isTrue();
+    }
+
+    protected void expectUserIsNotVisibleOnDisplay(@UserIdInt int userId, int displayId) {
+        expectWithMessage("mediator.isUserVisible(%s, %s)", userId, displayId)
+                .that(mMediator.isUserVisible(userId, displayId))
+                .isFalse();
+    }
+
+    protected void expectUserIsNotVisibleOnDisplay(String when, @UserIdInt int userId,
+            int displayId) {
+        String suffix = TextUtils.isEmpty(when) ? "" : " on " + when;
+        expectWithMessage("mediator.isUserVisible(%s, %s)%s", userId, displayId, suffix)
+                .that(mMediator.isUserVisible(userId, displayId))
+                .isFalse();
+    }
+
+    protected void expectUserIsNotVisibleAtAll(@UserIdInt int userId) {
+        expectWithMessage("mediator.isUserVisible(%s)", userId)
+                .that(mMediator.isUserVisible(userId))
+                .isFalse();
+        expectUserIsNotVisibleOnDisplay(userId, DEFAULT_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(userId, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(userId, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay(userId, OTHER_SECONDARY_DISPLAY_ID);
+    }
+
+    protected void expectDisplayAssignedToUser(@UserIdInt int userId, int displayId) {
+        expectWithMessage("getDisplayAssignedToUser(%s)", userId)
+                .that(mMediator.getDisplayAssignedToUser(userId)).isEqualTo(displayId);
+    }
+
+    protected void expectNoDisplayAssignedToUser(@UserIdInt int userId) {
+        expectWithMessage("getDisplayAssignedToUser(%s)", userId)
+                .that(mMediator.getDisplayAssignedToUser(userId)).isEqualTo(INVALID_DISPLAY);
+    }
+
+    protected void expectUserAssignedToDisplay(int displayId, @UserIdInt int userId) {
+        expectWithMessage("getUserAssignedToDisplay(%s)", displayId)
+                .that(mMediator.getUserAssignedToDisplay(displayId)).isEqualTo(userId);
+    }
+
+    protected void expectNoUserAssignedToDisplay(int displayId) {
+        expectWithMessage("getUserAssignedToDisplay(%s)", displayId)
+                .that(mMediator.getUserAssignedToDisplay(displayId))
+                .isEqualTo(INITIAL_CURRENT_USER_ID);
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
index 234d70b..93a1f30 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
@@ -20,7 +20,6 @@
 import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
 import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
 import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
-import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_VR;
 import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
 import static android.view.Display.DEFAULT_DISPLAY_GROUP;
 
@@ -59,8 +58,7 @@
             Arrays.asList(POLICY_OFF,
                     POLICY_DOZE,
                     POLICY_DIM,
-                    POLICY_BRIGHT,
-                    POLICY_VR);
+                    POLICY_BRIGHT);
     private static final int OTHER_DISPLAY_GROUP = DEFAULT_DISPLAY_GROUP + 1;
 
     @ClassRule
@@ -291,7 +289,7 @@
 
     @Test
     public void recordScreenPolicy_dimToNonBright_resets() {
-        for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE, POLICY_VR)) {
+        for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE)) {
             setup();
             mScreenUndimDetector.mUndimCounter = 1;
             mScreenUndimDetector.mUndimCounterStartedMillis = 123;
@@ -309,7 +307,7 @@
 
     @Test
     public void recordScreenPolicy_brightToNonDim_resets() {
-        for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE, POLICY_VR)) {
+        for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE)) {
             setup();
             mScreenUndimDetector.mUndimCounter = 1;
             mScreenUndimDetector.mUndimCounterStartedMillis = 123;
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..799a7fe 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -73,7 +74,12 @@
         }
 
         @Override
-        long getHardSatiatedConsumptionLimit() {
+        long getMinSatiatedConsumptionLimit() {
+            return 0;
+        }
+
+        @Override
+        long getMaxSatiatedConsumptionLimit() {
             return 0;
         }
 
@@ -103,7 +109,7 @@
     @Before
     public void setUp() {
         final InternalResourceService irs = mock(InternalResourceService.class);
-        when(irs.isVip(anyInt(), anyString())).thenReturn(false);
+        when(irs.isVip(anyInt(), anyString(), anyLong())).thenReturn(false);
         mEconomicPolicy = new MockEconomicPolicy(irs);
     }
 
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/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
index ddfa05c..c46ebf2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -402,6 +402,6 @@
         ApplicationInfo applicationInfo = new ApplicationInfo();
         applicationInfo.uid = UserHandle.getUid(userId, Math.abs(pkgName.hashCode()));
         pkgInfo.applicationInfo = applicationInfo;
-        mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(pkgInfo));
+        mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(getContext(), pkgInfo));
     }
 }
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/SlogfTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
index ae25c1b..cb59d37 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
@@ -44,7 +44,7 @@
 
     private MockitoSession mSession;
 
-    private final Exception mException = new Exception("D'OH!");
+    private final Throwable mThrowable = new Throwable("D'OH!");
 
     @Before
     public void setup() {
@@ -78,10 +78,10 @@
     }
 
     @Test
-    public void testV_msgAndException() {
-        Slogf.v(TAG, "msg", mException);
+    public void testV_msgAndThrowable() {
+        Slogf.v(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.v(TAG, "msg", mException));
+        verify(()-> Slog.v(TAG, "msg", mThrowable));
     }
 
     @Test
@@ -103,12 +103,12 @@
     }
 
     @Test
-    public void testV_msgFormattedWithException_enabled() {
+    public void testV_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.VERBOSE);
 
-        Slogf.v(TAG, mException, "msg in a %s", "bottle");
+        Slogf.v(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.v(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.v(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
@@ -128,10 +128,10 @@
     }
 
     @Test
-    public void testD_msgAndException() {
-        Slogf.d(TAG, "msg", mException);
+    public void testD_msgAndThrowable() {
+        Slogf.d(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.d(TAG, "msg", mException));
+        verify(()-> Slog.d(TAG, "msg", mThrowable));
     }
 
     @Test
@@ -153,19 +153,19 @@
     }
 
     @Test
-    public void testD_msgFormattedWithException_enabled() {
+    public void testD_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.DEBUG);
 
-        Slogf.d(TAG, mException, "msg in a %s", "bottle");
+        Slogf.d(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.d(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.d(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
     public void testD_msgFormattedWithException_disabled() {
         disableLogging(Log.DEBUG);
 
-        Slogf.d(TAG, mException, "msg in a %s", "bottle");
+        Slogf.d(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.d(eq(TAG), any(String.class), any(Throwable.class)), never());
     }
@@ -178,10 +178,10 @@
     }
 
     @Test
-    public void testI_msgAndException() {
-        Slogf.i(TAG, "msg", mException);
+    public void testI_msgAndThrowable() {
+        Slogf.i(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.i(TAG, "msg", mException));
+        verify(()-> Slog.i(TAG, "msg", mThrowable));
     }
 
     @Test
@@ -203,19 +203,19 @@
     }
 
     @Test
-    public void testI_msgFormattedWithException_enabled() {
+    public void testI_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.INFO);
 
-        Slogf.i(TAG, mException, "msg in a %s", "bottle");
+        Slogf.i(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.i(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.i(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
     public void testI_msgFormattedWithException_disabled() {
         disableLogging(Log.INFO);
 
-        Slogf.i(TAG, mException, "msg in a %s", "bottle");
+        Slogf.i(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.i(eq(TAG), any(String.class), any(Throwable.class)), never());
     }
@@ -228,17 +228,17 @@
     }
 
     @Test
-    public void testW_msgAndException() {
-        Slogf.w(TAG, "msg", mException);
+    public void testW_msgAndThrowable() {
+        Slogf.w(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.w(TAG, "msg", mException));
+        verify(()-> Slog.w(TAG, "msg", mThrowable));
     }
 
     @Test
-    public void testW_exception() {
-        Slogf.w(TAG, mException);
+    public void testW_Throwable() {
+        Slogf.w(TAG, mThrowable);
 
-        verify(()-> Slog.w(TAG, mException));
+        verify(()-> Slog.w(TAG, mThrowable));
     }
 
     @Test
@@ -260,19 +260,19 @@
     }
 
     @Test
-    public void testW_msgFormattedWithException_enabled() {
+    public void testW_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.WARN);
 
-        Slogf.w(TAG, mException, "msg in a %s", "bottle");
+        Slogf.w(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.w(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.w(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
     public void testW_msgFormattedWithException_disabled() {
         disableLogging(Log.WARN);
 
-        Slogf.w(TAG, mException, "msg in a %s", "bottle");
+        Slogf.w(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.w(eq(TAG), any(String.class), any(Throwable.class)), never());
     }
@@ -285,10 +285,10 @@
     }
 
     @Test
-    public void testE_msgAndException() {
-        Slogf.e(TAG, "msg", mException);
+    public void testE_msgAndThrowable() {
+        Slogf.e(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.e(TAG, "msg", mException));
+        verify(()-> Slog.e(TAG, "msg", mThrowable));
     }
 
     @Test
@@ -310,19 +310,19 @@
     }
 
     @Test
-    public void testE_msgFormattedWithException_enabled() {
+    public void testE_msgFormattedWithThrowable_enabled() {
         enableLogging(Log.ERROR);
 
-        Slogf.e(TAG, mException, "msg in a %s", "bottle");
+        Slogf.e(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.e(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.e(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
     public void testE_msgFormattedWithException_disabled() {
         disableLogging(Log.ERROR);
 
-        Slogf.e(TAG, mException, "msg in a %s", "bottle");
+        Slogf.e(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.e(eq(TAG), any(String.class), any(Throwable.class)), never());
     }
@@ -335,17 +335,17 @@
     }
 
     @Test
-    public void testWtf_msgAndException() {
-        Slogf.wtf(TAG, "msg", mException);
+    public void testWtf_msgAndThrowable() {
+        Slogf.wtf(TAG, "msg", mThrowable);
 
-        verify(()-> Slog.wtf(TAG, "msg", mException));
+        verify(()-> Slog.wtf(TAG, "msg", mThrowable));
     }
 
     @Test
-    public void testWtf_exception() {
-        Slogf.wtf(TAG, mException);
+    public void testWtf_Throwable() {
+        Slogf.wtf(TAG, mThrowable);
 
-        verify(()-> Slog.wtf(TAG, mException));
+        verify(()-> Slog.wtf(TAG, mThrowable));
     }
 
     @Test
@@ -377,10 +377,10 @@
     }
 
     @Test
-    public void testWtf_msgFormattedWithException() {
-        Slogf.wtf(TAG, mException, "msg in a %s", "bottle");
+    public void testWtf_msgFormattedWithThrowable() {
+        Slogf.wtf(TAG, mThrowable, "msg in a %s", "bottle");
 
-        verify(()-> Slog.wtf(TAG, "msg in a bottle", mException));
+        verify(()-> Slog.wtf(TAG, "msg in a bottle", mThrowable));
     }
 
     private void enableLogging(@Log.Level int level) {
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..80305fa 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -54,7 +54,6 @@
         "hamcrest-library",
         "servicestests-utils",
         "service-jobscheduler",
-        "service-permission.impl",
         // TODO: remove once Android migrates to JUnit 4.12,
         // which provides assertThrows
         "testng",
@@ -115,18 +114,10 @@
         ":SimpleServiceTestApp3",
         ":StubTestApp",
         ":SuspendTestApp",
+        ":MediaButtonReceiverHolderTestHelperApp",
     ],
 
     java_resources: [
-        ":PackageParserTestApp1",
-        ":PackageParserTestApp2",
-        ":PackageParserTestApp3",
-        ":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",
@@ -140,6 +131,7 @@
         "src/com/android/server/am/DeviceConfigSession.java",
         "src/com/android/server/display/TestUtils.java",
         "src/com/android/server/pm/PackageSettingBuilder.java",
+        "src/com/android/server/pm/parsing/TestPackageParser2.kt",
     ],
     static_libs: [
         "services.core",
@@ -148,6 +140,33 @@
 }
 
 java_library {
+    name: "servicestests-dpm-utils",
+    srcs: [
+        "src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java",
+        "src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java",
+        "src/com/android/server/devicepolicy/DpmMockContext.java",
+        "src/com/android/server/devicepolicy/DpmTestBase.java",
+        "src/com/android/server/devicepolicy/DpmTestUtils.java",
+        "src/com/android/server/devicepolicy/DummyDeviceAdmins.java",
+        "src/com/android/server/devicepolicy/MockSystemServices.java",
+        "src/com/android/server/devicepolicy/MockUtils.java",
+    ],
+    libs: [
+        "android.test.mock",
+        "android.test.base",
+        "mockito-target-minus-junit4",
+    ],
+    static_libs: [
+        "frameworks-base-testutils",
+        "androidx.test.core",
+        "androidx.test.ext.truth",
+        "androidx.test.rules",
+        "services.core",
+        "services.devicepolicy",
+    ],
+}
+
+java_library {
     name: "servicestests-utils",
     srcs: [
         "utils/**/*.java",
@@ -190,41 +209,8 @@
 java_genrule {
     name: "FrameworksServicesTests_apks_as_resources",
     srcs: [
-        ":FrameworksServicesTests_install",
-        ":FrameworksServicesTests_install_bad_dex",
-        ":FrameworksServicesTests_install_complete_package_info",
-        ":FrameworksServicesTests_install_decl_perm",
-        ":FrameworksServicesTests_install_intent_filters",
-        ":FrameworksServicesTests_install_loc_auto",
-        ":FrameworksServicesTests_install_loc_internal",
-        ":FrameworksServicesTests_install_loc_sdcard",
-        ":FrameworksServicesTests_install_loc_unspecified",
         ":FrameworksServicesTests_install_split_base",
         ":FrameworksServicesTests_install_split_feature_a",
-        ":FrameworksServicesTests_install_use_perm_good",
-        ":FrameworksServicesTests_install_uses_feature",
-        ":FrameworksServicesTests_install_uses_sdk_0",
-        ":FrameworksServicesTests_install_uses_sdk_q0",
-        ":FrameworksServicesTests_install_uses_sdk_q0_r0",
-        ":FrameworksServicesTests_install_uses_sdk_r0",
-        ":FrameworksServicesTests_install_uses_sdk_r5",
-        ":FrameworksServicesTests_install_uses_sdk_r_none",
-        ":FrameworksServicesTests_install_uses_sdk_r0_s0",
-        ":FrameworksServicesTests_install_uses_sdk_r0_s5",
-        ":FrameworksServicesTests_keyset_permdef_sa_unone",
-        ":FrameworksServicesTests_keyset_permuse_sa_ua_ub",
-        ":FrameworksServicesTests_keyset_permuse_sb_ua_ub",
-        ":FrameworksServicesTests_keyset_sa_ua",
-        ":FrameworksServicesTests_keyset_sa_ua_ub",
-        ":FrameworksServicesTests_keyset_sa_uab",
-        ":FrameworksServicesTests_keyset_sa_ub",
-        ":FrameworksServicesTests_keyset_sa_unone",
-        ":FrameworksServicesTests_keyset_sab_ua",
-        ":FrameworksServicesTests_keyset_sau_ub",
-        ":FrameworksServicesTests_keyset_sb_ua",
-        ":FrameworksServicesTests_keyset_sb_ub",
-        ":FrameworksServicesTests_keyset_splat_api",
-        ":FrameworksServicesTests_keyset_splata_api",
     ],
     out: ["FrameworkServicesTests_apks_as_resources.res.zip"],
     tools: ["soong_zip"],
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 6551bde..7149265 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -98,15 +98,22 @@
     <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK"/>
     <uses-permission
         android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
     <uses-permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY" />
     <uses-permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" />
     <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
     <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
     <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
     <uses-permission android:name="android.permission.BATTERY_STATS" />
+    <uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" />
+    <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" />
+    <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
 
     <queries>
         <package android:name="com.android.servicestests.apps.suspendtestapp" />
+        <intent>
+            <action android:name="android.media.browse.MediaBrowserService" />
+        </intent>
     </queries>
 
     <!-- Uses API introduced in O (26) -->
@@ -301,11 +308,4 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
          android:targetPackage="com.android.frameworks.servicestests"
          android:label="Frameworks Services Tests"/>
-    <key-sets>
-        <key-set android:name="A" >
-            <public-key android:name="keyA"
-                android:value="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsMpNthdOxud7roPDZMMomOqXgJJdRfIWpkKEqmC61Mv+Nf6QY3TorEwJeghjSmqj7IbBKrtvfQq4E2XJO1HuspmQO4Ng2gvn+r+6EwNfKc9k55d6s+27SR867jKurBbHNtZMG+tjL1yH4r+tNzcuJCsgyAFqLmxFdcxEwzNvREyRpoYc5RDR0mmTwkMCUhJ6CId1EYEKiCEdNzxv+fWPEb21u+/MWpleGCILs8kglRVb2q/WOzAAvGr4FY5plfaE6N+lr7+UschQ+aMi1+uqewo2o0qPFVmZP5hnwj55K4UMzu/NhhDqQQsX4cSGES1KgHo5MTqRqZjN/I7emw5pFQIDAQAB"/>
-        </key-set>
-        <upgrade-key-set android:name="A"/>
-    </key-sets>
 </manifest>
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 9052f58..d967647 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -33,6 +33,7 @@
         <option name="test-file-name" value="SimpleServiceTestApp1.apk" />
         <option name="test-file-name" value="SimpleServiceTestApp2.apk" />
         <option name="test-file-name" value="SimpleServiceTestApp3.apk" />
+        <option name="test-file-name" value="MediaButtonReceiverHolderTestHelperApp.apk" />
     </target_preparer>
 
     <!-- Create place to store apks -->
diff --git a/services/tests/servicestests/apks/install_uses_sdk/Android.bp b/services/tests/servicestests/apks/install_uses_sdk/Android.bp
deleted file mode 100644
index a51293d..0000000
--- a/services/tests/servicestests/apks/install_uses_sdk/Android.bp
+++ /dev/null
@@ -1,56 +0,0 @@
-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_helper_app {
-    name: "FrameworksServicesTests_install_uses_sdk_q0",
-    defaults: ["FrameworksServicesTests_apks_defaults"],
-    manifest: "AndroidManifest-q0.xml",
-}
-
-android_test_helper_app {
-    name: "FrameworksServicesTests_install_uses_sdk_q0_r0",
-    defaults: ["FrameworksServicesTests_apks_defaults"],
-    manifest: "AndroidManifest-q0-r0.xml",
-}
-
-android_test_helper_app {
-    name: "FrameworksServicesTests_install_uses_sdk_r_none",
-    defaults: ["FrameworksServicesTests_apks_defaults"],
-    manifest: "AndroidManifest-r-none.xml",
-}
-
-android_test_helper_app {
-    name: "FrameworksServicesTests_install_uses_sdk_r0",
-    defaults: ["FrameworksServicesTests_apks_defaults"],
-    manifest: "AndroidManifest-r0.xml",
-}
-
-android_test_helper_app {
-    name: "FrameworksServicesTests_install_uses_sdk_r5",
-    defaults: ["FrameworksServicesTests_apks_defaults"],
-    manifest: "AndroidManifest-r5.xml",
-}
-
-android_test_helper_app {
-    name: "FrameworksServicesTests_install_uses_sdk_r0_s0",
-    defaults: ["FrameworksServicesTests_apks_defaults"],
-    manifest: "AndroidManifest-r0-s0.xml",
-}
-
-android_test_helper_app {
-    name: "FrameworksServicesTests_install_uses_sdk_r0_s5",
-    defaults: ["FrameworksServicesTests_apks_defaults"],
-    manifest: "AndroidManifest-r0-s5.xml",
-}
-
-android_test_helper_app {
-    name: "FrameworksServicesTests_install_uses_sdk_0",
-    defaults: ["FrameworksServicesTests_apks_defaults"],
-    manifest: "AndroidManifest-0.xml",
-}
diff --git a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s5.xml b/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s5.xml
deleted file mode 100644
index bafe4c4..0000000
--- a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s5.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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.servicestests.install_uses_sdk">
-
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
-        <!-- This fails because 31 is not version 5 -->
-        <extension-sdk android:sdkVersion="30" android:minExtensionVersion="0" />
-        <extension-sdk android:sdkVersion="31" android:minExtensionVersion="5" />
-    </uses-sdk>
-
-    <application>
-    </application>
-</manifest>
diff --git a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r5.xml b/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r5.xml
deleted file mode 100644
index 7723d05..0000000
--- a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r5.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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.servicestests.install_uses_sdk">
-
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
-        <!-- This will fail to install, because minExtensionVersion is not met -->
-        <extension-sdk android:sdkVersion="30" android:minExtensionVersion="5" />
-    </uses-sdk>
-
-    <application>
-    </application>
-</manifest>
diff --git a/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml b/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml
new file mode 100644
index 0000000..5335f96
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml
@@ -0,0 +1,32 @@
+<?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.
+  -->
+
+<device name="Android">
+    <!-- Cellular modem related values. These constants are deprecated, but still supported and
+         need to be tested -->
+    <item name="radio.scanning">720</item>
+    <item name="modem.controller.sleep">70</item>
+    <item name="modem.controller.idle">360</item>
+    <item name="modem.controller.rx">1440</item>
+    <array name="modem.controller.tx"> <!-- Strength 0 to 4 -->
+        <value>720</value>
+        <value>1080</value>
+        <value>1440</value>
+        <value>1800</value>
+        <value>2160</value>
+    </array>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml
new file mode 100644
index 0000000..f57bc0f
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.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.
+  -->
+
+<device name="Android">
+    <modem>
+        <sleep>70</sleep>
+        <idle>360</idle>
+        <active rat="DEFAULT">
+            <receive>1440</receive>
+            <transmit level="0">720</transmit>
+            <transmit level="1">1080</transmit>
+            <transmit level="2">1440</transmit>
+            <transmit level="3">1800</transmit>
+            <transmit level="4">2160</transmit>
+        </active>
+    </modem>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml
new file mode 100644
index 0000000..4f5e674
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml
@@ -0,0 +1,63 @@
+<?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.
+  -->
+
+<device name="Android">
+    <modem>
+        <sleep>70</sleep>
+        <idle>360</idle>
+        <active rat="DEFAULT">
+            <receive>1440</receive>
+            <transmit level="0">720</transmit>
+            <transmit level="1">1080</transmit>
+            <transmit level="2">1440</transmit>
+            <transmit level="3">1800</transmit>
+            <transmit level="4">2160</transmit>
+        </active>
+        <active rat="LTE">
+            <receive>2000</receive>
+            <transmit level="0">800</transmit>
+            <transmit level="1">1200</transmit>
+            <transmit level="2">1600</transmit>
+            <transmit level="3">2000</transmit>
+            <transmit level="4">2400</transmit>
+        </active>
+        <active rat="NR" nrFrequency="DEFAULT">
+            <receive>2222</receive>
+            <transmit level="0">999</transmit>
+            <transmit level="1">1333</transmit>
+            <transmit level="2">1888</transmit>
+            <transmit level="3">2222</transmit>
+            <transmit level="4">2666</transmit>
+        </active>
+        <active rat="NR" nrFrequency="HIGH">
+            <receive>2727</receive>
+            <transmit level="0">1818</transmit>
+            <transmit level="1">2727</transmit>
+            <transmit level="2">3636</transmit>
+            <transmit level="3">4545</transmit>
+            <transmit level="4">5454</transmit>
+        </active>
+        <active rat="NR" nrFrequency="MMWAVE">
+            <receive>3456</receive>
+            <transmit level="0">2345</transmit>
+            <transmit level="1">3456</transmit>
+            <transmit level="2">4567</transmit>
+            <transmit level="3">5678</transmit>
+            <transmit level="4">6789</transmit>
+        </active>
+    </modem>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml
new file mode 100644
index 0000000..ab016fb
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml
@@ -0,0 +1,35 @@
+<?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.
+  -->
+
+<device name="Android">
+    <modem>
+        <!-- Modem sleep drain current value in mA. -->
+        <sleep>10</sleep>
+        <!-- Modem idle drain current value in mA. -->
+        <idle>20</idle>
+        <active rat="DEFAULT">
+            <!-- Transmit current drain in mA. -->
+            <receive>30</receive>
+            <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+            <transmit level="0">40</transmit>
+            <transmit level="1">50</transmit>
+            <transmit level="2">60</transmit>
+            <transmit level="3">70</transmit>
+            <transmit level="4">80</transmit>
+        </active>
+    </modem>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/usertypes_test_full.xml b/services/tests/servicestests/res/xml/usertypes_test_full.xml
index 099ccbe..9568143 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_full.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_full.xml
@@ -16,7 +16,8 @@
 <user-types>
     <full-type
         name='android.test.1'
-        max-allowed-per-parent='12' >
+        max-allowed-per-parent='12'
+        max-allowed='17' >
         <default-restrictions no_remove_user='true' no_bluetooth='true' />
         <badge-colors>
             <item res='@*android:color/profile_badge_1' />
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index b27f49d..1a6dae37 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -33,6 +33,7 @@
         <user-properties
             showInLauncher='2020'
             startWithParent='false'
+            useParentsContacts='false'
         />
     </profile-type>
     <profile-type name='custom.test.1' max-allowed-per-parent='14' />
diff --git a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
index 42c4129..653ed1a 100644
--- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemProperties;
@@ -36,8 +37,7 @@
 import org.junit.runner.RunWith;
 
 import java.io.FileDescriptor;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
 public class BinaryTransparencyServiceTest {
@@ -96,7 +96,7 @@
     @Test
     public void getApexInfo_postInitialize_returnsValidEntries() throws RemoteException {
         prepApexInfo();
-        Map result = mTestInterface.getApexInfo();
+        List result = mTestInterface.getApexInfo();
         Assert.assertNotNull("Apex info map should not be null", result);
         Assert.assertFalse("Apex info map should not be empty", result.isEmpty());
     }
@@ -105,13 +105,18 @@
     public void getApexInfo_postInitialize_returnsActualApexs()
             throws RemoteException, PackageManager.NameNotFoundException {
         prepApexInfo();
-        Map result = mTestInterface.getApexInfo();
+        List resultList = mTestInterface.getApexInfo();
 
         PackageManager pm = mContext.getPackageManager();
         Assert.assertNotNull(pm);
-        HashMap<PackageInfo, String> castedResult = (HashMap<PackageInfo, String>) result;
-        for (PackageInfo packageInfo : castedResult.keySet()) {
-            Assert.assertTrue(packageInfo.packageName + "is not an APEX!", packageInfo.isApex);
+        List<Bundle> castedResult = (List<Bundle>) resultList;
+        for (Bundle resultBundle : castedResult) {
+            PackageInfo resultPackageInfo = resultBundle.getParcelable(
+                    BinaryTransparencyService.BUNDLE_PACKAGE_INFO, PackageInfo.class);
+            Assert.assertNotNull("PackageInfo for APEX should not be null",
+                    resultPackageInfo);
+            Assert.assertTrue(resultPackageInfo.packageName + "is not an APEX!",
+                    resultPackageInfo.isApex);
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/DockObserverTest.java b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
index c325778..ee09074 100644
--- a/services/tests/servicestests/src/com/android/server/DockObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
@@ -20,6 +20,7 @@
 
 import android.content.Intent;
 import android.os.Looper;
+import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
@@ -74,6 +75,11 @@
                 .isEqualTo(Intent.EXTRA_DOCK_STATE_UNDOCKED);
     }
 
+    void setDeviceProvisioned(boolean provisioned) {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+                provisioned ? 1 : 0);
+    }
+
     @Before
     public void setUp() {
         if (Looper.myLooper() == null) {
@@ -131,4 +137,25 @@
         assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY5=5",
                 Intent.EXTRA_DOCK_STATE_HE_DESK);
     }
+
+    @Test
+    public void testDockIntentBroadcast_deviceNotProvisioned()
+            throws ExecutionException, InterruptedException {
+        DockObserver observer = new DockObserver(mInterceptingContext);
+        // Set the device as not provisioned.
+        setDeviceProvisioned(false);
+        observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+
+        BroadcastInterceptingContext.FutureIntent futureIntent =
+                updateExtconDockState(observer, "DOCK=1");
+        TestableLooper.get(this).processAllMessages();
+        // Verify no broadcast was sent as device was not provisioned.
+        futureIntent.assertNotReceived();
+
+        // Ensure we send the broadcast when the device is provisioned.
+        setDeviceProvisioned(true);
+        TestableLooper.get(this).processAllMessages();
+        assertThat(futureIntent.get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+                .isEqualTo(Intent.EXTRA_DOCK_STATE_DESK);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
new file mode 100644
index 0000000..cbe6d26
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
+import static android.util.DebugUtils.valueToString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.INetdUnsolicitedEventListener;
+import android.net.LinkAddress;
+import android.net.NetworkPolicyManager;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArrayMap;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.server.NetworkManagementService.Dependencies;
+import com.android.server.net.BaseNetworkObserver;
+
+import org.junit.After;
+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.function.BiFunction;
+
+/**
+ * Tests for {@link NetworkManagementService}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkManagementServiceTest {
+    private NetworkManagementService mNMService;
+    @Mock private Context mContext;
+    @Mock private ConnectivityManager mCm;
+    @Mock private IBatteryStats.Stub mBatteryStatsService;
+    @Mock private INetd.Stub mNetdService;
+
+    private static final int TEST_UID = 111;
+
+    @NonNull
+    @Captor
+    private ArgumentCaptor<INetdUnsolicitedEventListener> mUnsolListenerCaptor;
+
+    private final MockDependencies mDeps = new MockDependencies();
+
+    private final class MockDependencies extends Dependencies {
+        @Override
+        public IBinder getService(String name) {
+            switch (name) {
+                case BatteryStats.SERVICE_NAME:
+                    return mBatteryStatsService;
+                default:
+                    throw new UnsupportedOperationException("Unknown service " + name);
+            }
+        }
+
+        @Override
+        public void registerLocalService(NetworkManagementInternal nmi) {
+        }
+
+        @Override
+        public INetd getNetd() {
+            return mNetdService;
+        }
+
+        @Override
+        public int getCallingUid() {
+            return Process.SYSTEM_UID;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doNothing().when(mNetdService)
+                .registerUnsolicitedEventListener(mUnsolListenerCaptor.capture());
+        doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
+                eq(ConnectivityManager.class));
+        doReturn(mCm).when(mContext).getSystemService(eq(Context.CONNECTIVITY_SERVICE));
+        // Start the service and wait until it connects to our socket.
+        mNMService = NetworkManagementService.create(mContext, mDeps);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mNMService.shutdown();
+    }
+
+    private static <T> T expectSoon(T mock) {
+        return verify(mock, timeout(200));
+    }
+
+    /**
+     * Tests that network observers work properly.
+     */
+    @Test
+    public void testNetworkObservers() throws Exception {
+        BaseNetworkObserver observer = mock(BaseNetworkObserver.class);
+        doReturn(new Binder()).when(observer).asBinder();  // Used by registerObserver.
+        mNMService.registerObserver(observer);
+
+        // Forget everything that happened to the mock so far, so we can explicitly verify
+        // everything that happens and does not happen to it from now on.
+
+        INetdUnsolicitedEventListener unsolListener = mUnsolListenerCaptor.getValue();
+        reset(observer);
+        // Now call unsolListener methods and ensure that the observer methods are
+        // called. After every method we expect a callback soon after; to ensure that
+        // invalid messages don't cause any callbacks, we call verifyNoMoreInteractions at the end.
+
+        /**
+         * Interface changes.
+         */
+        unsolListener.onInterfaceAdded("rmnet12");
+        expectSoon(observer).interfaceAdded("rmnet12");
+
+        unsolListener.onInterfaceRemoved("eth1");
+        expectSoon(observer).interfaceRemoved("eth1");
+
+        unsolListener.onInterfaceChanged("clat4", true);
+        expectSoon(observer).interfaceStatusChanged("clat4", true);
+
+        unsolListener.onInterfaceLinkStateChanged("rmnet0", false);
+        expectSoon(observer).interfaceLinkStateChanged("rmnet0", false);
+
+        /**
+         * Bandwidth control events.
+         */
+        unsolListener.onQuotaLimitReached("data", "rmnet_usb0");
+        expectSoon(observer).limitReached("data", "rmnet_usb0");
+
+        /**
+         * Interface class activity.
+         */
+        unsolListener.onInterfaceClassActivityChanged(true, 1, 1234, TEST_UID);
+        expectSoon(observer).interfaceClassDataActivityChanged(1, true, 1234, TEST_UID);
+
+        unsolListener.onInterfaceClassActivityChanged(false, 9, 5678, TEST_UID);
+        expectSoon(observer).interfaceClassDataActivityChanged(9, false, 5678, TEST_UID);
+
+        unsolListener.onInterfaceClassActivityChanged(false, 9, 4321, TEST_UID);
+        expectSoon(observer).interfaceClassDataActivityChanged(9, false, 4321, TEST_UID);
+
+        /**
+         * IP address changes.
+         */
+        unsolListener.onInterfaceAddressUpdated("fe80::1/64", "wlan0", 128, 253);
+        expectSoon(observer).addressUpdated("wlan0", new LinkAddress("fe80::1/64", 128, 253));
+
+        unsolListener.onInterfaceAddressRemoved("fe80::1/64", "wlan0", 128, 253);
+        expectSoon(observer).addressRemoved("wlan0", new LinkAddress("fe80::1/64", 128, 253));
+
+        unsolListener.onInterfaceAddressRemoved("2001:db8::1/64", "wlan0", 1, 0);
+        expectSoon(observer).addressRemoved("wlan0", new LinkAddress("2001:db8::1/64", 1, 0));
+
+        /**
+         * DNS information broadcasts.
+         */
+        unsolListener.onInterfaceDnsServerInfo("rmnet_usb0", 3600, new String[]{"2001:db8::1"});
+        expectSoon(observer).interfaceDnsServerInfo("rmnet_usb0", 3600,
+                new String[]{"2001:db8::1"});
+
+        unsolListener.onInterfaceDnsServerInfo("wlan0", 14400,
+                new String[]{"2001:db8::1", "2001:db8::2"});
+        expectSoon(observer).interfaceDnsServerInfo("wlan0", 14400,
+                new String[]{"2001:db8::1", "2001:db8::2"});
+
+        // We don't check for negative lifetimes, only for parse errors.
+        unsolListener.onInterfaceDnsServerInfo("wlan0", -3600, new String[]{"::1"});
+        expectSoon(observer).interfaceDnsServerInfo("wlan0", -3600,
+                new String[]{"::1"});
+
+        // No syntax checking on the addresses.
+        unsolListener.onInterfaceDnsServerInfo("wlan0", 600,
+                new String[]{"", "::", "", "foo", "::1"});
+        expectSoon(observer).interfaceDnsServerInfo("wlan0", 600,
+                new String[]{"", "::", "", "foo", "::1"});
+
+        // Make sure nothing else was called.
+        verifyNoMoreInteractions(observer);
+    }
+
+    @Test
+    public void testFirewallEnabled() {
+        mNMService.setFirewallEnabled(true);
+        assertTrue(mNMService.isFirewallEnabled());
+
+        mNMService.setFirewallEnabled(false);
+        assertFalse(mNMService.isFirewallEnabled());
+    }
+
+    @Test
+    public void testNetworkRestrictedDefault() {
+        assertFalse(mNMService.isNetworkRestricted(TEST_UID));
+    }
+
+    @Test
+    public void testMeteredNetworkRestrictions() throws RemoteException {
+        // Make sure the mocked netd method returns true.
+        doReturn(true).when(mNetdService).bandwidthEnableDataSaver(anyBoolean());
+
+        // Restrict usage of mobile data in background
+        mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, true);
+        assertTrue("Should be true since mobile data usage is restricted",
+                mNMService.isNetworkRestricted(TEST_UID));
+        verify(mCm).addUidToMeteredNetworkDenyList(TEST_UID);
+
+        mNMService.setDataSaverModeEnabled(true);
+        verify(mNetdService).bandwidthEnableDataSaver(true);
+
+        mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, false);
+        assertTrue("Should be true since data saver is on and the uid is not allowlisted",
+                mNMService.isNetworkRestricted(TEST_UID));
+        verify(mCm).removeUidFromMeteredNetworkDenyList(TEST_UID);
+
+        mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, true);
+        assertFalse("Should be false since data saver is on and the uid is allowlisted",
+                mNMService.isNetworkRestricted(TEST_UID));
+        verify(mCm).addUidToMeteredNetworkAllowList(TEST_UID);
+
+        // remove uid from allowlist and turn datasaver off again
+        mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false);
+        verify(mCm).removeUidFromMeteredNetworkAllowList(TEST_UID);
+        mNMService.setDataSaverModeEnabled(false);
+        verify(mNetdService).bandwidthEnableDataSaver(false);
+        assertFalse("Network should not be restricted when data saver is off",
+                mNMService.isNetworkRestricted(TEST_UID));
+    }
+
+    @Test
+    public void testFirewallChains() {
+        final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>();
+        // Dozable chain
+        final ArrayMap<Integer, Boolean> isRestrictedForDozable = new ArrayMap<>();
+        isRestrictedForDozable.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForDozable.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForDozable.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable);
+        // Powersaver chain
+        final ArrayMap<Integer, Boolean> isRestrictedForPowerSave = new ArrayMap<>();
+        isRestrictedForPowerSave.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave);
+        // Standby chain
+        final ArrayMap<Integer, Boolean> isRestrictedForStandby = new ArrayMap<>();
+        isRestrictedForStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, false);
+        isRestrictedForStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForStandby.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_STANDBY, isRestrictedForStandby);
+        // Restricted mode chain
+        final ArrayMap<Integer, Boolean> isRestrictedForRestrictedMode = new ArrayMap<>();
+        isRestrictedForRestrictedMode.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_RESTRICTED, isRestrictedForRestrictedMode);
+        // Low Power Standby chain
+        final ArrayMap<Integer, Boolean> isRestrictedForLowPowerStandby = new ArrayMap<>();
+        isRestrictedForLowPowerStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, isRestrictedForLowPowerStandby);
+
+        final int[] chains = {
+                FIREWALL_CHAIN_STANDBY,
+                FIREWALL_CHAIN_POWERSAVE,
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_RESTRICTED,
+                FIREWALL_CHAIN_LOW_POWER_STANDBY
+        };
+        final int[] states = {
+                INetd.FIREWALL_RULE_ALLOW,
+                INetd.FIREWALL_RULE_DENY,
+                NetworkPolicyManager.FIREWALL_RULE_DEFAULT
+        };
+        BiFunction<Integer, Integer, String> errorMsg = (chain, state) -> {
+            return String.format("Unexpected value for chain: %s and state: %s",
+                    valueToString(INetd.class, "FIREWALL_CHAIN_", chain),
+                    valueToString(INetd.class, "FIREWALL_RULE_", state));
+        };
+        for (int chain : chains) {
+            final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain);
+            mNMService.setFirewallChainEnabled(chain, true);
+            verify(mCm).setFirewallChainEnabled(chain, true /* enabled */);
+            for (int state : states) {
+                mNMService.setFirewallUidRule(chain, TEST_UID, state);
+                assertEquals(errorMsg.apply(chain, state),
+                        expectedValues.get(state), mNMService.isNetworkRestricted(TEST_UID));
+            }
+            mNMService.setFirewallChainEnabled(chain, false);
+            verify(mCm).setFirewallChainEnabled(chain, false /* enabled */);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 0f09252..52a550b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -58,7 +58,6 @@
 import android.view.DisplayAdjustments;
 import android.view.DisplayInfo;
 import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.accessibility.AccessibilityWindowAttributes;
 
 import androidx.test.InstrumentationRegistry;
@@ -106,8 +105,6 @@
             LABEL,
             DESCRIPTION,
             TEST_PENDING_INTENT);
-    private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION =
-            new AccessibilityAction(ACTION_ID, LABEL);
 
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY + 1;
 
@@ -282,10 +279,12 @@
     @Test
     public void testRegisterProxy() throws Exception {
         mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
-        verify(mProxyManager).registerProxy(mMockServiceClient, TEST_DISPLAY);
+        verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY),
+                eq(mTestableContext), anyInt(), any(), eq(mMockSecurityPolicy),
+                eq(mA11yms), eq(mA11yms.getTraceManager()),
+                eq(mMockWindowManagerService), eq(mMockA11yWindowManager));
     }
 
-
     @SmallTest
     @Test
     public void testRegisterProxyWithoutPermission() throws Exception {
@@ -296,7 +295,8 @@
             Assert.fail();
         } catch (SecurityException expected) {
         }
-        verify(mProxyManager, never()).registerProxy(mMockServiceClient, TEST_DISPLAY);
+        verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+                any(), any(), any(), any());
     }
 
     @SmallTest
@@ -307,7 +307,8 @@
             Assert.fail();
         } catch (IllegalArgumentException expected) {
         }
-        verify(mProxyManager, never()).registerProxy(mMockServiceClient, Display.DEFAULT_DISPLAY);
+        verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+                any(), any(), any(), any());
     }
 
     @SmallTest
@@ -318,7 +319,30 @@
             Assert.fail();
         } catch (IllegalArgumentException expected) {
         }
-        verify(mProxyManager, never()).registerProxy(mMockServiceClient, Display.INVALID_DISPLAY);
+        verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+                any(), any(), any(), any());
+    }
+
+    @SmallTest
+    @Test
+    public void testUnRegisterProxyWithPermission() throws Exception {
+        mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
+        mA11yms.unregisterProxyForDisplay(TEST_DISPLAY);
+
+        verify(mProxyManager).unregisterProxy(TEST_DISPLAY);
+    }
+
+    @SmallTest
+    @Test
+    public void testUnRegisterProxyWithoutPermission() throws Exception {
+        doThrow(SecurityException.class).when(mMockSecurityPolicy)
+                .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+        try {
+            mA11yms.unregisterProxyForDisplay(TEST_DISPLAY);
+            Assert.fail();
+        } catch (SecurityException expected) {
+        }
+        verify(mProxyManager, never()).unregisterProxy(TEST_DISPLAY);
     }
 
     @SmallTest
@@ -417,6 +441,8 @@
     @SmallTest
     @Test
     public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() {
+        when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
         final AccessibilityUserState userState = mA11yms.mUserStates.get(
                 mA11yms.getCurrentUserIdLocked());
         userState.mAccessibilityShortcutKeyTargets.add(MAGNIFICATION_CONTROLLER_NAME);
@@ -432,6 +458,8 @@
     @SmallTest
     @Test
     public void testOnClientChange_boundServiceCanControlMagnification_requestConnection() {
+        when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
         setupAccessibilityServiceConnection(0);
         when(mMockSecurityPolicy.canControlMagnification(any())).thenReturn(true);
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index c15f6a9..bbcf77b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -326,7 +326,7 @@
 
         void register(Context context) {
             if (!mRegistered) {
-                context.registerReceiver(this, mFilter);
+                context.registerReceiver(this, mFilter, Context.RECEIVER_EXPORTED_UNAUDITED);
                 mRegistered = true;
             }
         }
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index f28ad79..e54a48b 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -3470,7 +3470,8 @@
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-            return mMockContext.registerReceiver(receiver, filter);
+            return mMockContext.registerReceiver(receiver, filter,
+                    Context.RECEIVER_EXPORTED_UNAUDITED);
         }
 
         @Override
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..d47f063 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;
@@ -114,6 +113,8 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Tests for {@link UserController}.
@@ -147,9 +148,11 @@
 
     private static final List<String> START_FOREGROUND_USER_ACTIONS = newArrayList(
             Intent.ACTION_USER_STARTED,
-            Intent.ACTION_USER_SWITCHED,
             Intent.ACTION_USER_STARTING);
 
+    private static final List<String> START_FOREGROUND_USER_DEFERRED_ACTIONS = newArrayList(
+            Intent.ACTION_USER_SWITCHED);
+
     private static final List<String> START_BACKGROUND_USER_ACTIONS = newArrayList(
             Intent.ACTION_USER_STARTED,
             Intent.ACTION_LOCKED_BOOT_COMPLETED,
@@ -183,6 +186,12 @@
             mockIsUsersOnSecondaryDisplaysEnabled(false);
             // All UserController params are set to default.
 
+            // Starts with a generic assumption that the user starts visible, but on tests where
+            // that's not the case, the test should call mockAssignUserToMainDisplay()
+            doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE)
+                    .when(mInjector.mUserManagerInternalMock)
+                    .assignUserToDisplayOnStart(anyInt(), anyInt(), anyBoolean(), anyInt());
+
             mUserController = new UserController(mInjector);
             mUserController.setAllowUserUnlocking(true);
             setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
@@ -220,6 +229,17 @@
     }
 
     @Test
+    public void testStartUser_displayAssignmentFailed() {
+        doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE)
+                .when(mInjector.mUserManagerInternalMock)
+                .assignUserToDisplayOnStart(eq(TEST_USER_ID), anyInt(), eq(true), anyInt());
+
+        boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ true);
+
+        assertWithMessage("startUser(%s, foreground=true)", TEST_USER_ID).that(started).isFalse();
+    }
+
+    @Test
     public void testStartUserOnSecondaryDisplay_defaultDisplay() {
         assertThrows(IllegalArgumentException.class, () -> mUserController
                 .startUserOnSecondaryDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY));
@@ -235,7 +255,6 @@
                 .isTrue();
         verifyUserAssignedToDisplay(TEST_USER_ID, 42);
 
-        // TODO(b/239982558): might need to change assertions
         verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
         verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
         verify(mInjector, never()).clearAllLockedTasks(anyString());
@@ -283,8 +302,6 @@
         // binder calls, but their side effects (in this case, that the user is stopped right away)
         assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes())
                 .containsExactly(USER_START_MSG);
-
-        verifyUserAssignedToDisplay(TEST_PRE_CREATED_USER_ID, Display.DEFAULT_DISPLAY);
     }
 
     private void startUserAssertions(
@@ -383,11 +400,11 @@
     private void continueAndCompleteUserSwitch(UserState userState, int oldUserId, int newUserId) {
         mUserController.continueUserSwitch(userState, oldUserId, newUserId);
         mInjector.mHandler.removeMessages(UserController.COMPLETE_USER_SWITCH_MSG);
-        mUserController.completeUserSwitch(newUserId);
+        mUserController.completeUserSwitch(oldUserId, newUserId);
     }
 
     @Test
-    public void testContinueUserSwitch() throws RemoteException {
+    public void testContinueUserSwitch() {
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
         // Start user -- this will update state of mUserController
@@ -402,12 +419,12 @@
         continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
         verify(mInjector, times(0)).dismissKeyguard(any(), anyString());
         verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
-        continueUserSwitchAssertions(TEST_USER_ID, false);
-        verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+        continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
+        verifySystemUserVisibilityChangesNeverNotified();
     }
 
     @Test
-    public void testContinueUserSwitchDismissKeyguard() throws RemoteException {
+    public void testContinueUserSwitchDismissKeyguard() {
         when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(false);
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
@@ -423,12 +440,12 @@
         continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
         verify(mInjector, times(1)).dismissKeyguard(any(), anyString());
         verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
-        continueUserSwitchAssertions(TEST_USER_ID, false);
-        verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+        continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
+        verifySystemUserVisibilityChangesNeverNotified();
     }
 
     @Test
-    public void testContinueUserSwitchUIDisabled() throws RemoteException {
+    public void testContinueUserSwitchUIDisabled() {
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
 
@@ -443,11 +460,11 @@
         // Verify that continueUserSwitch worked as expected
         continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
         verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
-        continueUserSwitchAssertions(TEST_USER_ID, false);
+        continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
     }
 
-    private void continueUserSwitchAssertions(int expectedUserId, boolean backgroundUserStopping)
-            throws RemoteException {
+    private void continueUserSwitchAssertions(int expectedOldUserId, int expectedNewUserId,
+            boolean backgroundUserStopping) {
         Set<Integer> expectedCodes = new LinkedHashSet<>();
         expectedCodes.add(COMPLETE_USER_SWITCH_MSG);
         expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG);
@@ -459,7 +476,8 @@
         assertEquals("Unexpected message sent", expectedCodes, actualCodes);
         Message msg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG);
         assertNotNull(msg);
-        assertEquals("Unexpected userId", expectedUserId, msg.arg1);
+        assertEquals("Unexpected oldUserId", expectedOldUserId, msg.arg1);
+        assertEquals("Unexpected newUserId", expectedNewUserId, msg.arg2);
     }
 
     @Test
@@ -472,16 +490,21 @@
         mUserController.startUser(TEST_USER_ID, true);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
         assertNotNull(reportMsg);
+        int oldUserId = reportMsg.arg1;
         int newUserId = reportMsg.arg2;
         mInjector.mHandler.clearAllRecordedMessages();
         // Mockito can't reset only interactions, so just verify that this hasn't been
         // called with 'false' until after dispatchUserSwitchComplete.
         verify(mInjector.getWindowManager(), never()).setSwitchingUser(false);
         // Call dispatchUserSwitchComplete
-        mUserController.dispatchUserSwitchComplete(newUserId);
+        mUserController.dispatchUserSwitchComplete(oldUserId, newUserId);
         verify(observer, times(1)).onUserSwitchComplete(anyInt());
         verify(observer).onUserSwitchComplete(TEST_USER_ID);
         verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(false);
+        startUserAssertions(Stream.concat(
+                        START_FOREGROUND_USER_ACTIONS.stream(),
+                        START_FOREGROUND_USER_DEFERRED_ACTIONS.stream()
+                ).collect(Collectors.toList()), Collections.emptySet());
     }
 
     @Test
@@ -531,7 +554,7 @@
         assertFalse(mUserController.canStartMoreUsers());
         assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}),
                 mUserController.getRunningUsersLU());
-        verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+        verifySystemUserVisibilityChangesNeverNotified();
     }
 
     /**
@@ -615,6 +638,39 @@
                 /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
     }
 
+    @Test
+    public void testStopUser_invalidUser() {
+        int userId = -1;
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mUserController.stopUser(userId, /* force= */ true,
+                        /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+                        /* keyEvictedCallback= */ null));
+    }
+
+    @Test
+    public void testStopUser_systemUser() {
+        int userId = UserHandle.USER_SYSTEM;
+
+        int r = mUserController.stopUser(userId, /* force= */ true,
+                /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+                /* keyEvictedCallback= */ null);
+
+        assertThat(r).isEqualTo(ActivityManager.USER_OP_ERROR_IS_SYSTEM);
+    }
+
+    @Test
+    public void testStopUser_currentUser() {
+        setUpUser(TEST_USER_ID1, /* flags= */ 0);
+        mUserController.startUser(TEST_USER_ID1, /* foreground= */ true);
+
+        int r = mUserController.stopUser(TEST_USER_ID1, /* force= */ true,
+                /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+                /* keyEvictedCallback= */ null);
+
+        assertThat(r).isEqualTo(ActivityManager.USER_OP_IS_CURRENT);
+    }
+
     /**
      * Test conditional delayed locking with mDelayUserDataLocking true.
      */
@@ -888,8 +944,7 @@
     }
 
     private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
-            int expectedNumberOfCalls, boolean expectOldUserStopping)
-            throws RemoteException {
+            int expectedNumberOfCalls, boolean expectOldUserStopping) {
         // Start user -- this will update state of mUserController
         mUserController.startUser(newUserId, true);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -904,7 +959,7 @@
         continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
         verify(mInjector.getWindowManager(), times(expectedNumberOfCalls))
                 .stopFreezingScreen();
-        continueUserSwitchAssertions(newUserId, expectOldUserStopping);
+        continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping);
     }
 
     private void setUpUser(@UserIdInt int userId, @UserInfoFlag int flags) {
@@ -949,23 +1004,25 @@
     }
 
     private void verifyUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
-        verify(mInjector.getUserManagerInternal()).assignUserToDisplay(userId, displayId);
+        verify(mInjector.getUserManagerInternal()).assignUserToDisplayOnStart(eq(userId), anyInt(),
+                anyBoolean(), eq(displayId));
     }
 
     private void verifyUserNeverAssignedToDisplay() {
-        verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplay(anyInt(), anyInt());
+        verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplayOnStart(anyInt(),
+                anyInt(), anyBoolean(), anyInt());
     }
 
     private void verifyUserUnassignedFromDisplay(@UserIdInt int userId) {
-        verify(mInjector.getUserManagerInternal()).unassignUserFromDisplay(userId);
+        verify(mInjector.getUserManagerInternal()).unassignUserFromDisplayOnStop(userId);
     }
 
     private void verifyUserUnassignedFromDisplayNeverCalled(@UserIdInt int userId) {
-        verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplay(userId);
+        verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplayOnStop(userId);
     }
 
-    private void verifyOnUserStarting(@UserIdInt int userId, boolean visible) {
-        verify(mInjector).onUserStarting(userId, visible);
+    private void verifySystemUserVisibilityChangesNeverNotified() {
+        verify(mInjector, never()).onSystemUserVisibilityChanged(anyBoolean());
     }
 
     // Should be public to allow mocking
@@ -1105,8 +1162,13 @@
         }
 
         @Override
-        void onUserStarting(@UserIdInt int userId, boolean visible) {
-            Log.i(TAG, "onUserStarting(" + userId + ", " + visible + ")");
+        void onUserStarting(@UserIdInt int userId) {
+            Log.i(TAG, "onUserStarting(" + userId + ")");
+        }
+
+        @Override
+        void onSystemUserVisibilityChanged(boolean visible) {
+            Log.i(TAG, "onSystemUserVisibilityChanged(" + visible + ")");
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
index e8dd541..fde3422 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -52,6 +52,8 @@
     private static final String PACKAGE_NAME_1 = "com.android.app1";
     private static final String PACKAGE_NAME_2 = "com.android.app2";
     private static final String PACKAGE_NAME_3 = "com.android.app3";
+    private static final String PACKAGE_NAME_4 = "com.android.app4";
+
 
     private void writeFile(File file, byte[] data) {
         file.mkdirs();
@@ -69,16 +71,23 @@
         writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(),
                         "system/game-manager-service.xml"),
                 ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
-                        + "<packages>\n"
-                        + "  <package name=\"com.android.app1\" gameMode=\"1\">\n"
-                        + "  </package>\n"
+                        + "<packages>"
+                        + "\n" // app1: no package config setting
+                        + "\n" // app2: performance mode is selected with override
                         + "  <package name=\"com.android.app2\" gameMode=\"2\">\n"
                         + "     <gameModeConfig gameMode=\"2\" scaling=\"0.99\" "
                         + "useAngle=\"true\" fps=\"90\" loadingBoost=\"123\"></gameModeConfig>\n"
                         + "     <gameModeConfig gameMode=\"3\"></gameModeConfig>\n"
-                        + "  </package>\n"
+                        + "  </package>"
+                        + "\n" // app3: only battery mode is selected
                         + "  <package name=\"com.android.app3\" gameMode=\"3\">\n"
-                        + "  </package>\n"
+                        + "  </package>"
+                        + "\n" // app4: no game mode selected but custom game mode config
+                        + "  <package name=\"com.android.app4\">\n"
+                        + "     <gameModeConfig gameMode=\"4\" scaling=\"0.4\" "
+                        + "fps=\"30\"></gameModeConfig>\n"
+                        + "  </package>"
+                        + "\n"
                         + "</packages>\n").getBytes());
     }
 
@@ -115,14 +124,15 @@
         assertTrue(settings.readPersistentDataLocked());
 
         // test game modes
-        assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_1));
-        assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
-        assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_3));
+        assertEquals(GameManager.GAME_MODE_STANDARD, settings.getGameModeLocked(PACKAGE_NAME_1));
+        assertEquals(GameManager.GAME_MODE_PERFORMANCE, settings.getGameModeLocked(PACKAGE_NAME_2));
+        assertEquals(GameManager.GAME_MODE_BATTERY, settings.getGameModeLocked(PACKAGE_NAME_3));
+        assertEquals(GameManager.GAME_MODE_STANDARD, settings.getGameModeLocked(PACKAGE_NAME_4));
 
         // test game mode configs
         assertNull(settings.getConfigOverride(PACKAGE_NAME_1));
         assertNull(settings.getConfigOverride(PACKAGE_NAME_3));
-        final GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_2);
+        GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_2);
         assertNotNull(config);
 
         assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_STANDARD));
@@ -141,6 +151,14 @@
                 GameModeConfiguration.DEFAULT_LOADING_BOOST_DURATION);
         assertEquals(batteryConfig.getFpsStr(), GameModeConfiguration.DEFAULT_FPS);
         assertFalse(batteryConfig.getUseAngle());
+
+        config = settings.getConfigOverride(PACKAGE_NAME_4);
+        assertNotNull(config);
+        GameModeConfiguration customConfig = config.getGameModeConfiguration(
+                GameManager.GAME_MODE_CUSTOM);
+        assertNotNull(customConfig);
+        assertEquals(customConfig.getScaling(), 0.4f, 0.1f);
+        assertEquals(customConfig.getFps(), 30);
     }
 
     @Test
@@ -176,23 +194,27 @@
         writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(),
                         "system/game-manager-service.xml"),
                 ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
-                        + "<packages>\n"
+                        + "<packages>"
+                        + "\n" // missing package name
                         + "  <package gameMode=\"1\">\n"
-                        + "  </package>\n"
+                        + "  </package>"
+                        + "\n" // app2 with unknown sub element
                         + "  <package name=\"com.android.app2\" gameMode=\"2\">\n"
                         + "     <unknown></unknown>"
                         + "     <gameModeConfig gameMode=\"3\" fps=\"90\"></gameModeConfig>\n"
                         + "     foo bar"
-                        + "  </package>\n"
+                        + "  </package>"
+                        + "\n" // unknown package element
                         + "  <unknownTag></unknownTag>\n"
-                        + "    foo bar\n"
+                        + "    foo bar"
+                        + "\n" // app3 after unknown element
                         + "  <package name=\"com.android.app3\" gameMode=\"3\">\n"
                         + "  </package>\n"
                         + "</packages>\n").getBytes());
         final Context context = InstrumentationRegistry.getContext();
         GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
         assertTrue(settings.readPersistentDataLocked());
-        assertEquals(0, settings.getGameModeLocked(PACKAGE_NAME_1));
+        assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_1));
         assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
         assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_3));
 
@@ -214,6 +236,8 @@
         settings.setGameModeLocked(PACKAGE_NAME_1, GameManager.GAME_MODE_BATTERY);
         settings.setGameModeLocked(PACKAGE_NAME_2, GameManager.GAME_MODE_PERFORMANCE);
         settings.setGameModeLocked(PACKAGE_NAME_3, GameManager.GAME_MODE_STANDARD);
+
+        // set config for app2
         GamePackageConfiguration config = new GamePackageConfiguration(PACKAGE_NAME_2);
         GameModeConfiguration performanceConfig = config.getOrAddDefaultGameModeConfiguration(
                 GameManager.GAME_MODE_PERFORMANCE);
@@ -225,18 +249,29 @@
                 GameManager.GAME_MODE_BATTERY);
         batteryConfig.setScaling(0.77f);
         settings.setConfigOverride(PACKAGE_NAME_2, config);
+
+        // set config for app4
+        config = new GamePackageConfiguration(PACKAGE_NAME_4);
+        GameModeConfiguration customConfig = config.getOrAddDefaultGameModeConfiguration(
+                GameManager.GAME_MODE_CUSTOM);
+        customConfig.setScaling(0.4f);
+        customConfig.setFpsStr("30");
+        settings.setConfigOverride(PACKAGE_NAME_4, config);
+
         settings.writePersistentDataLocked();
 
         // clear the settings in memory
         settings.removeGame(PACKAGE_NAME_1);
         settings.removeGame(PACKAGE_NAME_2);
         settings.removeGame(PACKAGE_NAME_3);
+        settings.removeGame(PACKAGE_NAME_4);
 
         // read back in and verify
         assertTrue(settings.readPersistentDataLocked());
         assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_1));
         assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
         assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_3));
+        assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_4));
 
         config = settings.getConfigOverride(PACKAGE_NAME_1);
         assertNull(config);
@@ -256,5 +291,14 @@
         assertEquals(performanceConfig.getLoadingBoostDuration(), 321);
         assertEquals(performanceConfig.getFpsStr(), "60");
         assertTrue(performanceConfig.getUseAngle());
+
+        config = settings.getConfigOverride(PACKAGE_NAME_4);
+        assertNotNull(config);
+        customConfig = config.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM);
+        assertNotNull(customConfig);
+        assertEquals(customConfig.getScaling(), 0.4f, 0.1f);
+        assertEquals(customConfig.getFps(), 30);
+        assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+        assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 7acb6d6..7f54b63 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -84,12 +84,12 @@
         final AudioDeviceAttributes usbDevice = new AudioDeviceAttributes(
                 /*native type*/ AudioSystem.DEVICE_OUT_USB_DEVICE, /*address*/ "bla");
 
-        mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName, TAG);
+        mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName);
         mTestLooper.dispatchAll();
         verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
                         AudioManager.STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
 
-        mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName, TAG);
+        mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName);
         mTestLooper.dispatchAll();
         verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
                 AudioManager.STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 9ae8922..cd2f205 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -29,7 +29,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.backup.BackupAgent;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
 import android.content.Context;
@@ -38,6 +38,7 @@
 import android.content.pm.PackageManager;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.backup.internal.LifecycleOperationStorage;
@@ -147,18 +148,18 @@
     }
 
     @Test
-    public void testGetOperationTypeFromTransport_returnsBackupByDefault()
+    public void testGetBackupDestinationFromTransport_returnsCloudByDefault()
             throws Exception {
         when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransport);
         when(mBackupTransport.getTransportFlags()).thenReturn(0);
 
-        int operationType = mService.getOperationTypeFromTransport(mTransportConnection);
+        int backupDestination = mService.getBackupDestinationFromTransport(mTransportConnection);
 
-        assertThat(operationType).isEqualTo(OperationType.BACKUP);
+        assertThat(backupDestination).isEqualTo(BackupDestination.CLOUD);
     }
 
     @Test
-    public void testGetOperationTypeFromTransport_returnsMigrationForMigrationTransport()
+    public void testGetBackupDestinationFromTransport_returnsDeviceTransferForD2dTransport()
             throws Exception {
         // This is a temporary flag to control the new behaviour until it's ready to be fully
         // rolled out.
@@ -168,12 +169,13 @@
         when(mBackupTransport.getTransportFlags()).thenReturn(
                 BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER);
 
-        int operationType = mService.getOperationTypeFromTransport(mTransportConnection);
+        int backupDestination = mService.getBackupDestinationFromTransport(mTransportConnection);
 
-        assertThat(operationType).isEqualTo(OperationType.MIGRATION);
+        assertThat(backupDestination).isEqualTo(BackupDestination.DEVICE_TRANSFER);
     }
 
     @Test
+    @FlakyTest
     public void testAgentDisconnected_cancelsCurrentOperations() throws Exception {
         when(mOperationStorage.operationTokensForPackage(eq("com.android.foo"))).thenReturn(
                 ImmutableSet.of(123, 456, 789)
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
index 310c8f4..48b0aad 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
@@ -23,7 +23,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.when;
 
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -78,7 +78,7 @@
         MockitoAnnotations.initMocks(this);
 
         mUserId = UserHandle.USER_SYSTEM;
-        mBackupEligibilityRules = getBackupEligibilityRules(OperationType.BACKUP);
+        mBackupEligibilityRules = getBackupEligibilityRules(BackupDestination.CLOUD);
     }
 
     @Test
@@ -225,7 +225,7 @@
                 /* flags */ 0, CUSTOM_BACKUP_AGENT_NAME);
 
         BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
-                OperationType.MIGRATION);
+                BackupDestination.DEVICE_TRANSFER);
         boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
 
         assertThat(isEligible).isTrue();
@@ -237,7 +237,7 @@
         ApplicationInfo applicationInfo = getApplicationInfo(Process.SYSTEM_UID,
                 ApplicationInfo.FLAG_SYSTEM, CUSTOM_BACKUP_AGENT_NAME);
         BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
-                OperationType.MIGRATION);
+                BackupDestination.DEVICE_TRANSFER);
         boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
 
         assertThat(isEligible).isFalse();
@@ -250,7 +250,7 @@
         ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
                 /* flags */ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED, CUSTOM_BACKUP_AGENT_NAME);
         BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
-                OperationType.ADB_BACKUP);
+                BackupDestination.ADB_BACKUP);
         when(mPackageManager.getPropertyAsUser(eq(PackageManager.PROPERTY_ALLOW_ADB_BACKUP),
                 eq(TEST_PACKAGE_NAME), isNull(), eq(mUserId)))
                 .thenReturn(getAdbBackupProperty(/* allowAdbBackup */ false));
@@ -267,7 +267,7 @@
         ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
                 /* flags */ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED, CUSTOM_BACKUP_AGENT_NAME);
         BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
-                OperationType.ADB_BACKUP);
+                BackupDestination.ADB_BACKUP);
         when(mPackageManager.getPropertyAsUser(eq(PackageManager.PROPERTY_ALLOW_ADB_BACKUP),
                 eq(TEST_PACKAGE_NAME), isNull(), eq(mUserId)))
                 .thenReturn(getAdbBackupProperty(/* allowAdbBackup */ true));
@@ -284,7 +284,7 @@
         ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
                 /* flags */ ApplicationInfo.FLAG_DEBUGGABLE, CUSTOM_BACKUP_AGENT_NAME);
         BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
-                OperationType.ADB_BACKUP);
+                BackupDestination.ADB_BACKUP);
 
         boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
 
@@ -298,7 +298,7 @@
         ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
                 ApplicationInfo.FLAG_ALLOW_BACKUP, CUSTOM_BACKUP_AGENT_NAME);
         BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
-                OperationType.ADB_BACKUP);
+                BackupDestination.ADB_BACKUP);
 
         boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
 
@@ -312,7 +312,7 @@
         ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
                 /* flags */ 0, CUSTOM_BACKUP_AGENT_NAME);
         BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
-                OperationType.ADB_BACKUP);
+                BackupDestination.ADB_BACKUP);
 
         boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
 
@@ -787,9 +787,10 @@
         assertThat(result).isFalse();
     }
 
-    private BackupEligibilityRules getBackupEligibilityRules(@OperationType int operationType) {
+    private BackupEligibilityRules getBackupEligibilityRules(
+            @BackupDestination int backupDestination) {
         return new BackupEligibilityRules(mPackageManager, mMockPackageManagerInternal, mUserId,
-                operationType);
+                backupDestination);
     }
 
     private static Signature generateSignature(byte i) {
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
index d3fd89c..cadc890 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
@@ -21,13 +21,20 @@
 import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION;
 import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME;
 import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.app.IBackupAgent;
+import android.app.backup.BackupManagerMonitor;
+import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.IBackupManagerMonitor;
 import android.content.pm.PackageInfo;
 import android.os.Bundle;
@@ -37,6 +44,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.infra.AndroidFuture;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,6 +53,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @SmallTest
 @Presubmit
 @RunWith(AndroidJUnit4.class)
@@ -143,6 +155,44 @@
     }
 
     @Test
+    public void monitorAgentLoggingResults_fillsBundleCorrectly() throws Exception {
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = "test.package";
+        // Mock an agent that returns a logging result.
+        IBackupAgent agent = spy(IBackupAgent.class);
+        List<BackupRestoreEventLogger.DataTypeResult> loggingResults = new ArrayList<>();
+        loggingResults.add(new BackupRestoreEventLogger.DataTypeResult("testLoggingResult"));
+        doAnswer(
+                        invocation -> {
+                            AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> in =
+                                    invocation.getArgument(0);
+                            in.complete(loggingResults);
+                            return null;
+                        })
+                .when(agent)
+                .getLoggerResults(any());
+
+        IBackupManagerMonitor result =
+                BackupManagerMonitorUtils.monitorAgentLoggingResults(
+                        mMonitorMock, packageInfo, agent);
+
+        assertThat(result).isEqualTo(mMonitorMock);
+        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mMonitorMock).onEvent(bundleCaptor.capture());
+        Bundle eventBundle = bundleCaptor.getValue();
+        assertThat(eventBundle.getInt(EXTRA_LOG_EVENT_ID))
+                .isEqualTo(LOG_EVENT_ID_AGENT_LOGGING_RESULTS);
+        assertThat(eventBundle.getInt(EXTRA_LOG_EVENT_CATEGORY))
+                .isEqualTo(LOG_EVENT_CATEGORY_AGENT);
+        assertThat(eventBundle.getString(EXTRA_LOG_EVENT_PACKAGE_NAME)).isEqualTo("test.package");
+        List<BackupRestoreEventLogger.DataTypeResult> filledLoggingResults =
+                eventBundle.getParcelableArrayList(
+                        BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS,
+                        BackupRestoreEventLogger.DataTypeResult.class);
+        assertThat(filledLoggingResults.get(0).getDataType()).isEqualTo("testLoggingResult");
+    }
+
+    @Test
     public void putMonitoringExtraString_bundleExists_fillsBundleCorrectly() throws Exception {
         Bundle bundle = new Bundle();
 
@@ -204,5 +254,4 @@
         assertThat(result.size()).isEqualTo(1);
         assertThat(result.getBoolean("key")).isTrue();
     }
-
 }
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 85d8aba..0d6d1a2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -64,6 +64,7 @@
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
+import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
 import android.os.IBinder;
@@ -75,7 +76,10 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.internal.statusbar.ISessionListener;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.server.biometrics.log.BiometricContextProvider;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
 import org.junit.Before;
@@ -129,6 +133,16 @@
     ITrustManager mTrustManager;
     @Mock
     DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    private IStatusBarService mStatusBarService;
+    @Mock
+    private ISessionListener mSessionListener;
+    @Mock
+    private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+    @Mock
+    private AuthSessionCoordinator mAuthSessionCoordinator;
+
+    BiometricContextProvider mBiometricContextProvider;
 
     @Before
     public void setUp() {
@@ -160,6 +174,11 @@
         when(mResources.getString(R.string.biometric_error_user_canceled))
                 .thenReturn(ERROR_USER_CANCELED);
 
+        when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+        mBiometricContextProvider = new BiometricContextProvider(mAmbientDisplayConfiguration,
+                mStatusBarService, null /* handler */, mAuthSessionCoordinator);
+        when(mInjector.getBiometricContext(any())).thenReturn(mBiometricContextProvider);
+
         final String[] config = {
                 "0:2:15",  // ID0:Fingerprint:Strong
                 "1:8:15",  // ID1:Face:Strong
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index 0cff4f1..bb00634 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -125,12 +125,9 @@
         mProbe.destroy();
         mProbe.enable();
 
-        AtomicInteger lux = new AtomicInteger(10);
-        mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
-
         verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
         verifyNoMoreInteractions(mSensorManager);
-        assertThat(lux.get()).isLessThan(0);
+        assertThat(mProbe.getMostRecentLux()).isLessThan(0);
     }
 
     @Test
@@ -323,15 +320,27 @@
     }
 
     @Test
-    public void testNoNextLuxWhenDestroyed() {
+    public void testDestroyAllowsAwaitLuxExactlyOnce() {
+        final float lastValue = 5.5f;
         mProbe.destroy();
 
-        AtomicInteger lux = new AtomicInteger(-20);
+        AtomicInteger lux = new AtomicInteger(10);
         mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
 
-        assertThat(lux.get()).isEqualTo(-1);
-        verify(mSensorManager, never()).registerListener(
+        verify(mSensorManager).registerListener(
                 mSensorEventListenerCaptor.capture(), any(), anyInt());
+        mSensorEventListenerCaptor.getValue().onSensorChanged(
+                new SensorEvent(mLightSensor, 1, 1, new float[]{lastValue}));
+
+        assertThat(lux.get()).isEqualTo(Math.round(lastValue));
+        verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue()));
+
+        lux.set(22);
+        mProbe.enable();
+        mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+        mProbe.enable();
+
+        assertThat(lux.get()).isEqualTo(Math.round(lastValue));
         verifyNoMoreInteractions(mSensorManager);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java
index c5a8557..ebf7fd8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java
@@ -61,11 +61,11 @@
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo(
                 AUTHENTICATOR_DEFAULT);
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo(
-                AUTHENTICATOR_UNLOCKED);
+                AUTHENTICATOR_DEFAULT);
     }
 
     @Test
-    public void testLockout() {
+    public void testConvenientLockout() {
         mAuthResultCoordinator.lockedOutFor(
                 BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
 
@@ -80,7 +80,7 @@
     }
 
     @Test
-    public void testConvenientLockout() {
+    public void testConvenientUnlock() {
         mAuthResultCoordinator.authenticatedFor(
                 BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
 
@@ -91,11 +91,26 @@
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo(
                 AUTHENTICATOR_DEFAULT);
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo(
-                AUTHENTICATOR_UNLOCKED);
+                AUTHENTICATOR_DEFAULT);
     }
 
     @Test
     public void testWeakLockout() {
+        mAuthResultCoordinator.lockedOutFor(
+                BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
+
+        Map<Integer, Integer> authMap = mAuthResultCoordinator.getResult();
+
+        assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo(
+                AUTHENTICATOR_DEFAULT);
+        assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo(
+                AUTHENTICATOR_DEFAULT);
+        assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo(
+                AUTHENTICATOR_LOCKED);
+    }
+
+    @Test
+    public void testWeakUnlock() {
         mAuthResultCoordinator.authenticatedFor(
                 BiometricManager.Authenticators.BIOMETRIC_WEAK);
 
@@ -104,13 +119,29 @@
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo(
                 AUTHENTICATOR_DEFAULT);
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo(
-                AUTHENTICATOR_UNLOCKED);
+                AUTHENTICATOR_DEFAULT);
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo(
-                AUTHENTICATOR_UNLOCKED);
+                AUTHENTICATOR_DEFAULT);
     }
 
     @Test
     public void testStrongLockout() {
+        mAuthResultCoordinator.lockedOutFor(
+                BiometricManager.Authenticators.BIOMETRIC_STRONG);
+
+        Map<Integer, Integer> authMap = mAuthResultCoordinator.getResult();
+
+        assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo(
+                AUTHENTICATOR_LOCKED);
+        assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo(
+                AUTHENTICATOR_LOCKED);
+        assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo(
+                AUTHENTICATOR_LOCKED);
+    }
+
+
+    @Test
+    public void testStrongUnlock() {
         mAuthResultCoordinator.authenticatedFor(
                 BiometricManager.Authenticators.BIOMETRIC_STRONG);
 
@@ -136,9 +167,9 @@
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo(
                 AUTHENTICATOR_DEFAULT);
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo(
-                AUTHENTICATOR_UNLOCKED | AUTHENTICATOR_LOCKED);
+                AUTHENTICATOR_LOCKED);
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo(
-                AUTHENTICATOR_UNLOCKED | AUTHENTICATOR_LOCKED);
+                AUTHENTICATOR_LOCKED);
 
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java
index 6e44875..c3b9cb1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java
@@ -53,22 +53,28 @@
     }
 
     @Test
-    public void testUserUnlocked() {
+    public void testUserUnlockedWithWeak() {
         mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */);
         mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_STRONG, 1 /* sensorId */,
                 0 /* requestId */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
 
         mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */);
-        mCoordinator.authenticatedFor(PRIMARY_USER, BIOMETRIC_WEAK, 1 /* sensorId */,
-                0 /* requestId */);
+        mCoordinator.authEndedFor(PRIMARY_USER, BIOMETRIC_WEAK, 1 /* sensorId */,
+                0 /* requestId */, true /* wasSuccessful */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 
     @Test
@@ -77,38 +83,79 @@
         mCoordinator.authStartedFor(PRIMARY_USER, 2 /* sensorId */, 0 /* requestId */);
         mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_STRONG, 1 /* sensorId */,
                 0 /* requestId */);
-        mCoordinator.authenticatedFor(PRIMARY_USER, BIOMETRIC_WEAK, 2 /* sensorId */,
-                0 /* requestId */);
+        mCoordinator.authEndedFor(PRIMARY_USER, BIOMETRIC_WEAK, 2 /* sensorId */,
+                0 /* requestId */, false /* wasSuccessful */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
 
         mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */);
-        mCoordinator.authenticatedFor(PRIMARY_USER, BIOMETRIC_WEAK, 1 /* sensorId */,
-                0 /* requestId */);
+        mCoordinator.authEndedFor(PRIMARY_USER, BIOMETRIC_WEAK, 1 /* sensorId */,
+                0 /* requestId */, false /* wasSuccessful */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+    }
+
+    @Test
+    public void testWeakAndConvenientCannotResetLockout() {
+        mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */);
+        mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_STRONG, 1 /* sensorId */,
+                0 /* requestId */);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+
+        mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_WEAK, 0 /* requestId */);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+
+        mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE, 0 /* requestId */);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 
     @Test
     public void testUserCanAuthDuringLockoutOfSameSession() {
         mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG, 0 /* requestId */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
 
         mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */);
         mCoordinator.authStartedFor(PRIMARY_USER, 2 /* sensorId */, 0 /* requestId */);
         mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_WEAK, 2 /* sensorId */,
                 0 /* requestId */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
     }
 
     @Test
@@ -123,25 +170,39 @@
 
         mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG, 0 /* requestId */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
 
-        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_WEAK)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(
+                mCoordinator.getLockoutStateFor(SECONDARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(SECONDARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(SECONDARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
 
         mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */);
         mCoordinator.authStartedFor(PRIMARY_USER, 2 /* sensorId */, 0 /* requestId */);
         mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_WEAK, 2 /* sensorId */,
                 0 /* requestId */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
 
-        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_WEAK)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(
+                mCoordinator.getLockoutStateFor(SECONDARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(SECONDARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(SECONDARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 }
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..4c898b0 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
@@ -17,6 +17,7 @@
 package com.android.server.biometrics.sensors;
 
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS;
 
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
@@ -34,7 +35,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;
@@ -79,17 +79,24 @@
     private static final int TEST_SENSOR_ID = 1;
     private static final int LOG_NUM_RECENT_OPERATIONS = 2;
     @Rule
-    public final TestableContext mContext =
-            new TestableContext(InstrumentationRegistry.getContext(), null);
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getContext(), null);
     private BiometricScheduler mScheduler;
     private IBinder mToken;
     @Mock
     private IBiometricService mBiometricService;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private AuthSessionCoordinator mAuthSessionCoordinator;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mToken = new Binder();
+        when(mAuthSessionCoordinator.getLockoutStateFor(anyInt(), anyInt())).thenReturn(
+                BIOMETRIC_SUCCESS);
+        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
         mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()),
                 BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
                 mBiometricService, LOG_NUM_RECENT_OPERATIONS);
@@ -99,10 +106,10 @@
     public void testClientDuplicateFinish_ignoredBySchedulerAndDoesNotCrash() {
         final Supplier<Object> nonNullDaemon = () -> mock(Object.class);
 
-        final HalClientMonitor<Object> client1 =
-                new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
-        final HalClientMonitor<Object> client2 =
-                new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
+        final HalClientMonitor<Object> client1 = new TestHalClientMonitor(mContext, mToken,
+                nonNullDaemon);
+        final HalClientMonitor<Object> client2 = new TestHalClientMonitor(mContext, mToken,
+                nonNullDaemon);
         mScheduler.scheduleClientMonitor(client1);
         mScheduler.scheduleClientMonitor(client2);
 
@@ -113,10 +120,9 @@
     @Test
     public void testRemovesPendingOperations_whenNullHal_andNotBiometricPrompt() {
         // Even if second client has a non-null daemon, it needs to be canceled.
-        final TestHalClientMonitor client1 = new TestHalClientMonitor(
-                mContext, mToken, () -> null);
-        final TestHalClientMonitor client2 = new TestHalClientMonitor(
-                mContext, mToken, () -> mock(Object.class));
+        final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, () -> null);
+        final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken,
+                () -> mock(Object.class));
 
         final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
         final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class);
@@ -151,10 +157,10 @@
 
         final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class);
 
-        final TestAuthenticationClient client1 =
-                new TestAuthenticationClient(mContext, () -> null, mToken, listener1);
-        final TestHalClientMonitor client2 =
-                new TestHalClientMonitor(mContext, mToken, () -> daemon2);
+        final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext, () -> null,
+                mToken, listener1, mBiometricContext);
+        final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken,
+                () -> daemon2);
 
         final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
         final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class);
@@ -189,15 +195,15 @@
     @Test
     public void testCancelNotInvoked_whenOperationWaitingForCookie() {
         final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
-        final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
-                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class));
+        final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext, lazyDaemon1,
+                mToken, mock(ClientMonitorCallbackConverter.class), mBiometricContext);
         final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
 
         // Schedule a BiometricPrompt authentication request
         mScheduler.scheduleClientMonitor(client1, callback1);
 
-        assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart(
-                mock(ClientMonitorCallback.class)));
+        assertNotEquals(0,
+                mScheduler.mCurrentOperation.isReadyToStart(mock(ClientMonitorCallback.class)));
         assertEquals(client1, mScheduler.mCurrentOperation.getClientMonitor());
         assertEquals(0, mScheduler.mPendingOperations.size());
 
@@ -305,7 +311,7 @@
         final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
         final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
-                mToken, callback);
+                mToken, callback, mBiometricContext);
 
         // Add a non-cancellable client, then add the auth client
         mScheduler.scheduleClientMonitor(client1);
@@ -368,7 +374,8 @@
         final Supplier<Object> lazyDaemon = () -> mock(Object.class);
         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
         testCancelsWhenRequestId(requestId, cancelRequestId, started,
-                new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback));
+                new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback,
+                        mBiometricContext));
     }
 
     @Test
@@ -449,11 +456,11 @@
         final long requestId2 = 20;
         final Supplier<Object> lazyDaemon = () -> mock(Object.class);
         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
-        final TestAuthenticationClient client1 = new TestAuthenticationClient(
-                mContext, lazyDaemon, mToken, callback);
+        final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext, lazyDaemon,
+                mToken, callback, mBiometricContext);
         client1.setRequestId(requestId1);
-        final TestAuthenticationClient client2 = new TestAuthenticationClient(
-                mContext, lazyDaemon, mToken, callback);
+        final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
+                mToken, callback, mBiometricContext);
         client2.setRequestId(requestId2);
 
         mScheduler.scheduleClientMonitor(client1);
@@ -475,8 +482,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 +498,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);
@@ -507,8 +514,8 @@
     @Test
     public void testClientDestroyed_afterFinish() {
         final Supplier<Object> nonNullDaemon = () -> mock(Object.class);
-        final TestHalClientMonitor client =
-                new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
+        final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken,
+                nonNullDaemon);
         mScheduler.scheduleClientMonitor(client);
         client.mCallback.onClientFinished(client, true /* success */);
         waitForIdle();
@@ -521,7 +528,8 @@
         final TestableLooper looper = TestableLooper.get(this);
         final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
         final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
-                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */);
+                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
+                mBiometricContext);
         final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
 
         mScheduler.scheduleClientMonitor(client1, callback1);
@@ -556,7 +564,8 @@
         final TestableLooper looper = TestableLooper.get(this);
         final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
         final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
-                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */);
+                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
+                mBiometricContext);
         final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
 
         mScheduler.scheduleClientMonitor(client1, callback1);
@@ -590,7 +599,8 @@
 
         //Run additional auth client
         final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext,
-                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */);
+                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
+                mBiometricContext);
         final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class);
 
         mScheduler.scheduleClientMonitor(client2, callback2);
@@ -627,7 +637,8 @@
         final TestableLooper looper = TestableLooper.get(this);
         final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
         final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
-                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */);
+                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
+                mBiometricContext);
         final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
 
         mScheduler.scheduleClientMonitor(client1, callback1);
@@ -680,19 +691,22 @@
 
         TestAuthenticationClient(@NonNull Context context,
                 @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
-                @NonNull ClientMonitorCallbackConverter listener) {
-            this(context, lazyDaemon, token, listener, 1 /* cookie */);
+                @NonNull ClientMonitorCallbackConverter listener,
+                BiometricContext biometricContext) {
+            this(context, lazyDaemon, token, listener, 1 /* cookie */, biometricContext);
         }
 
         TestAuthenticationClient(@NonNull Context context,
                 @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
-                @NonNull ClientMonitorCallbackConverter listener, int cookie) {
+                @NonNull ClientMonitorCallbackConverter listener, int cookie,
+                @NonNull BiometricContext biometricContext) {
             super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
                     false /* restricted */, TAG, cookie, false /* requireConfirmation */,
-                    TEST_SENSOR_ID, mock(BiometricLogger.class), mock(BiometricContext.class),
+                    TEST_SENSOR_ID, mock(BiometricLogger.class), biometricContext,
                     true /* isStrongBiometric */, null /* taskStackListener */,
-                    mock(LockoutTracker.class), false /* isKeyguard */,
-                    true /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
+                    null /* lockoutTracker */, false /* isKeyguard */,
+                    true /* shouldVibrate */, false /* isKeyguardBypassEnabled */,
+                    0 /* sensorStrength */);
         }
 
         @Override
@@ -743,14 +757,12 @@
         boolean mStoppedHal = false;
         int mNumCancels = 0;
 
-        TestEnrollClient(@NonNull Context context,
-                @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
-                @NonNull ClientMonitorCallbackConverter listener) {
+        TestEnrollClient(@NonNull Context context, @NonNull Supplier<Object> lazyDaemon,
+                @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener) {
             super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69],
-                    "test" /* owner */, mock(BiometricUtils.class),
-                    5 /* timeoutSec */, TEST_SENSOR_ID,
-                    true /* shouldVibrate */,
-                    mock(BiometricLogger.class), mock(BiometricContext.class));
+                    "test" /* owner */, mock(BiometricUtils.class), 5 /* timeoutSec */,
+                    TEST_SENSOR_ID, true /* shouldVibrate */, mock(BiometricLogger.class),
+                    mock(BiometricContext.class));
         }
 
         @Override
@@ -788,9 +800,9 @@
 
         TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
                 @NonNull Supplier<Object> lazyDaemon, int cookie, int protoEnum) {
-            super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */,
-                    TAG, cookie, TEST_SENSOR_ID,
-                    mock(BiometricLogger.class), mock(BiometricContext.class));
+            super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */, TAG,
+                    cookie, TEST_SENSOR_ID, mock(BiometricLogger.class),
+                    mock(BiometricContext.class));
             mProtoEnum = protoEnum;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java
index 0b10a7b..968844e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java
@@ -50,16 +50,22 @@
 
     private static void unlockAllBiometrics(MultiBiometricLockoutState lockoutState, int userId) {
         lockoutState.setAuthenticatorTo(userId, BIOMETRIC_STRONG, true /* canAuthenticate */);
-        assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_STRONG)).isTrue();
-        assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_WEAK)).isTrue();
-        assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(lockoutState.getLockoutState(userId, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(lockoutState.getLockoutState(userId, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(lockoutState.getLockoutState(userId, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
     }
 
     private static void lockoutAllBiometrics(MultiBiometricLockoutState lockoutState, int userId) {
         lockoutState.setAuthenticatorTo(userId, BIOMETRIC_STRONG, false /* canAuthenticate */);
-        assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_STRONG)).isFalse();
-        assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_WEAK)).isFalse();
-        assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_CONVENIENCE)).isFalse();
+        assertThat(lockoutState.getLockoutState(userId, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(lockoutState.getLockoutState(userId, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(lockoutState.getLockoutState(userId, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 
     private void unlockAllBiometrics() {
@@ -79,9 +85,12 @@
 
     @Test
     public void testInitialStateLockedOut() {
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
     }
 
     @Test
@@ -89,20 +98,26 @@
         unlockAllBiometrics();
         mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_CONVENIENCE,
                 false /* canAuthenticate */);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
         assertThat(
-                mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+                mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 
     @Test
     public void testWeakLockout() {
         unlockAllBiometrics();
         mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_WEAK, false /* canAuthenticate */);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
         assertThat(
-                mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+                mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 
     @Test
@@ -110,10 +125,13 @@
         lockoutAllBiometrics();
         mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_STRONG,
                 false /* canAuthenticate */);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
         assertThat(
-                mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+                mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 
     @Test
@@ -121,18 +139,24 @@
         lockoutAllBiometrics();
         mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_CONVENIENCE,
                 true /* canAuthenticate */);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
     }
 
     @Test
     public void testWeakUnlock() {
         lockoutAllBiometrics();
         mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_WEAK, true /* canAuthenticate */);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
     }
 
     @Test
@@ -140,9 +164,12 @@
         lockoutAllBiometrics();
         mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_STRONG,
                 true /* canAuthenticate */);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
     }
 
     @Test
@@ -154,45 +181,66 @@
         lockoutAllBiometrics(lockoutState, userTwo);
 
         lockoutState.setAuthenticatorTo(userOne, BIOMETRIC_WEAK, true /* canAuthenticate */);
-        assertThat(lockoutState.canUserAuthenticate(userOne, BIOMETRIC_STRONG)).isFalse();
-        assertThat(lockoutState.canUserAuthenticate(userOne, BIOMETRIC_WEAK)).isTrue();
-        assertThat(lockoutState.canUserAuthenticate(userOne, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(lockoutState.getLockoutState(userOne, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(lockoutState.getLockoutState(userOne, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(lockoutState.getLockoutState(userOne, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
 
-        assertThat(lockoutState.canUserAuthenticate(userTwo, BIOMETRIC_STRONG)).isFalse();
-        assertThat(lockoutState.canUserAuthenticate(userTwo, BIOMETRIC_WEAK)).isFalse();
-        assertThat(lockoutState.canUserAuthenticate(userTwo, BIOMETRIC_CONVENIENCE)).isFalse();
+        assertThat(lockoutState.getLockoutState(userTwo, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(lockoutState.getLockoutState(userTwo, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(lockoutState.getLockoutState(userTwo, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 
     @Test
     public void testTimedLockout() {
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
 
         mLockoutState.increaseLockoutTime(PRIMARY_USER, BIOMETRIC_STRONG,
                 System.currentTimeMillis() + 1);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_TIMED);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_TIMED);
         assertThat(
-                mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+                mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_TIMED);
     }
 
     @Test
     public void testTimedLockoutAfterDuration() {
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
 
         when(mClock.millis()).thenReturn(0L);
         mLockoutState.increaseLockoutTime(PRIMARY_USER, BIOMETRIC_STRONG, 1);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_TIMED);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_TIMED);
         assertThat(
-                mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+                mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_TIMED);
 
         when(mClock.millis()).thenReturn(2L);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 2dc3583..d10d7d4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -47,7 +47,6 @@
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.face.UsageStats;
 
 import org.junit.Before;
@@ -85,8 +84,6 @@
     @Mock
     private BiometricContext mBiometricContext;
     @Mock
-    private LockoutCache mLockoutCache;
-    @Mock
     private UsageStats mUsageStats;
     @Mock
     private ClientMonitorCallback mCallback;
@@ -161,7 +158,7 @@
                 false /* restricted */, "test-owner", 4 /* cookie */,
                 false /* requireConfirmation */, 9 /* sensorId */,
                 mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
-                mUsageStats, mLockoutCache, false /* allowBackgroundAuthentication */,
+                mUsageStats, null /* mLockoutCache */, false /* allowBackgroundAuthentication */,
                 false /* isKeyguardBypassEnabled */, null /* sensorPrivacyManager */,
                 0 /* biometricStrength */) {
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 2afc4d7..1f29bec 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -17,6 +17,7 @@
 package com.android.server.biometrics.sensors.face.aidl;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
@@ -25,7 +26,10 @@
 
 import android.content.Context;
 import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.SensorProps;
 import android.os.Handler;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
@@ -36,6 +40,7 @@
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -74,12 +79,18 @@
     private BiometricContext mBiometricContext;
     @Mock
     private AuthSessionCoordinator mAuthSessionCoordinator;
+    @Mock
+    private IFace mDaemon;
+    @Mock
+    private BiometricStateCallback mBiometricStateCallback;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
 
     private UserAwareBiometricScheduler mScheduler;
     private Sensor.HalSessionCallback mHalCallback;
+    private FaceProvider mFaceProvider;
+    private SensorProps[] mSensorProps;
 
     @Before
     public void setUp() {
@@ -99,6 +110,16 @@
         mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
                 TAG, mScheduler, SENSOR_ID,
                 USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback);
+
+        final SensorProps sensor1 = new SensorProps();
+        sensor1.commonProps = new CommonProps();
+        sensor1.commonProps.sensorId = 0;
+        final SensorProps sensor2 = new SensorProps();
+        sensor2.commonProps = new CommonProps();
+        sensor2.commonProps.sensorId = 1;
+        mSensorProps = new SensorProps[]{sensor1, sensor2};
+        mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
+                mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext);
     }
 
     @Test
@@ -128,6 +149,18 @@
         verifyNotLocked();
     }
 
+    @Test
+    public void onBinderDied_noErrorOnNullClient() {
+        mScheduler.reset();
+        assertNull(mScheduler.getCurrentClient());
+        mFaceProvider.binderDied();
+
+        for (int i = 0; i < mFaceProvider.mSensors.size(); i++) {
+            final Sensor sensor = mFaceProvider.mSensors.valueAt(i);
+            assertNull(sensor.getSessionForUser(USER_ID));
+        }
+    }
+
     private void verifyNotLocked() {
         assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID));
         verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index a2cade7..d174533 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -105,7 +105,8 @@
                 FaceSensorProperties.TYPE_UNKNOWN, supportsFaceDetection, supportsSelfIllumination,
                 resetLockoutRequiresChallenge);
 
-        Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST"));
+        Face10.sSystemClock = Clock.fixed(
+                Instant.ofEpochMilli(100), ZoneId.of("America/Los_Angeles"));
         mFace10 = new Face10(mContext, mBiometricStateCallback, sensorProps,
                 mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
         mBinder = new Binder();
@@ -115,7 +116,7 @@
         waitForIdle();
         Face10.sSystemClock = Clock.fixed(Instant.ofEpochSecond(
                 Face10.sSystemClock.instant().getEpochSecond() + seconds),
-                ZoneId.of("PST"));
+                ZoneId.of("America/Los_Angeles"));
     }
 
     @Test
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 675f0e3..22c53d3 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
@@ -16,8 +16,6 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyFloat;
@@ -63,7 +61,6 @@
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutCache;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -88,7 +85,7 @@
     private static final int USER_ID = 8;
     private static final long OP_ID = 7;
     private static final long REQUEST_ID = 88;
-    private static final int POINTER_ID = 0;
+    private static final int POINTER_ID = 3;
     private static final int TOUCH_X = 8;
     private static final int TOUCH_Y = 20;
     private static final float TOUCH_MAJOR = 4.4f;
@@ -114,8 +111,6 @@
     @Mock
     private BiometricManager mBiometricManager;
     @Mock
-    private LockoutCache mLockoutCache;
-    @Mock
     private IUdfpsOverlayController mUdfpsOverlayController;
     @Mock
     private ISidefpsController mSideFpsController;
@@ -138,8 +133,6 @@
     @Captor
     private ArgumentCaptor<OperationContext> mOperationContextCaptor;
     @Captor
-    private ArgumentCaptor<PointerContext> mPointerContextCaptor;
-    @Captor
     private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
     private final TestLooper mLooper = new TestLooper();
 
@@ -179,7 +172,10 @@
     public void pointerUp_v1() throws RemoteException {
         final FingerprintAuthenticationClient client = createClient(1);
         client.start(mCallback);
-        client.onPointerUp();
+
+        PointerContext pc = new PointerContext();
+        pc.pointerId = POINTER_ID;
+        client.onPointerUp(pc);
 
         verify(mHal).onPointerUp(eq(POINTER_ID));
         verify(mHal, never()).onPointerUpWithContext(any());
@@ -189,10 +185,17 @@
     public void pointerDown_v1() throws RemoteException {
         final FingerprintAuthenticationClient client = createClient(1);
         client.start(mCallback);
-        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
 
-        verify(mHal).onPointerDown(eq(0),
-                eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MAJOR), eq(TOUCH_MINOR));
+        PointerContext pc = new PointerContext();
+        pc.pointerId = POINTER_ID;
+        pc.x = TOUCH_X;
+        pc.y = TOUCH_Y;
+        pc.minor = TOUCH_MINOR;
+        pc.major = TOUCH_MAJOR;
+        client.onPointerDown(pc);
+
+        verify(mHal).onPointerDown(eq(POINTER_ID), eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MINOR),
+                eq(TOUCH_MAJOR));
         verify(mHal, never()).onPointerDownWithContext(any());
     }
 
@@ -200,26 +203,30 @@
     public void pointerUpWithContext_v2() throws RemoteException {
         final FingerprintAuthenticationClient client = createClient(2);
         client.start(mCallback);
-        client.onPointerUp();
 
-        verify(mHal).onPointerUpWithContext(mPointerContextCaptor.capture());
+        PointerContext pc = new PointerContext();
+        pc.pointerId = POINTER_ID;
+        client.onPointerUp(pc);
+
+        verify(mHal).onPointerUpWithContext(eq(pc));
         verify(mHal, never()).onPointerUp(eq(POINTER_ID));
-
-        final PointerContext pContext = mPointerContextCaptor.getValue();
-        assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
     }
 
     @Test
     public void pointerDownWithContext_v2() throws RemoteException {
         final FingerprintAuthenticationClient client = createClient(2);
         client.start(mCallback);
-        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
 
-        verify(mHal).onPointerDownWithContext(mPointerContextCaptor.capture());
+        PointerContext pc = new PointerContext();
+        pc.pointerId = POINTER_ID;
+        pc.x = TOUCH_X;
+        pc.y = TOUCH_Y;
+        pc.minor = TOUCH_MINOR;
+        pc.major = TOUCH_MAJOR;
+        client.onPointerDown(pc);
+
+        verify(mHal).onPointerDownWithContext(eq(pc));
         verify(mHal, never()).onPointerDown(anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat());
-
-        final PointerContext pContext = mPointerContextCaptor.getValue();
-        assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
     }
 
     @Test
@@ -376,6 +383,7 @@
     @Test
     public void fingerprintPowerIgnoresAuthInWindow() throws Exception {
         when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+        when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal);
 
         final FingerprintAuthenticationClient client = createClient(1);
         client.start(mCallback);
@@ -386,11 +394,13 @@
         mLooper.dispatchAll();
 
         verify(mCallback).onClientFinished(any(), eq(false));
+        verify(mCancellationSignal).cancel();
     }
 
     @Test
     public void fingerprintAuthIgnoredWaitingForPower() throws Exception {
         when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+        when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal);
 
         final FingerprintAuthenticationClient client = createClient(1);
         client.start(mCallback);
@@ -401,11 +411,13 @@
         mLooper.dispatchAll();
 
         verify(mCallback).onClientFinished(any(), eq(false));
+        verify(mCancellationSignal).cancel();
     }
 
     @Test
-    public void fingerprintAuthSucceedsAfterPowerWindow() throws Exception {
+    public void fingerprintAuthFailsWhenAuthAfterPower() throws Exception {
         when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+        when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal);
 
         final FingerprintAuthenticationClient client = createClient(1);
         client.start(mCallback);
@@ -419,7 +431,9 @@
         mLooper.moveTimeForward(1000);
         mLooper.dispatchAll();
 
-        verify(mCallback).onClientFinished(any(), eq(true));
+        verify(mCallback, never()).onClientFinished(any(), eq(true));
+        verify(mCallback).onClientFinished(any(), eq(false));
+        when(mHal.authenticateWithContext(anyLong(), any())).thenReturn(mCancellationSignal);
     }
 
     @Test
@@ -620,6 +634,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 */);
     }
@@ -644,7 +672,7 @@
                 false /* requireConfirmation */,
                 9 /* sensorId */, mBiometricLogger, mBiometricContext,
                 true /* isStrongBiometric */,
-                null /* taskStackListener */, mLockoutCache,
+                null /* taskStackListener */, null /* lockoutCache */,
                 mUdfpsOverlayController, mSideFpsController, null, allowBackgroundAuthentication,
                 mSensorProps,
                 new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {
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 38b06c4..c3d4783 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
@@ -18,8 +18,6 @@
 
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -74,7 +72,7 @@
     private static final byte[] HAT = new byte[69];
     private static final int USER_ID = 8;
     private static final long REQUEST_ID = 9;
-    private static final int POINTER_ID = 0;
+    private static final int POINTER_ID = 3;
     private static final int TOUCH_X = 8;
     private static final int TOUCH_Y = 20;
     private static final float TOUCH_MAJOR = 4.4f;
@@ -153,7 +151,10 @@
     public void pointerUp_v1() throws RemoteException {
         final FingerprintEnrollClient client = createClient(1);
         client.start(mCallback);
-        client.onPointerUp();
+
+        PointerContext pc = new PointerContext();
+        pc.pointerId = POINTER_ID;
+        client.onPointerUp(pc);
 
         verify(mHal).onPointerUp(eq(POINTER_ID));
         verify(mHal, never()).onPointerUpWithContext(any());
@@ -163,10 +164,17 @@
     public void pointerDown_v1() throws RemoteException {
         final FingerprintEnrollClient client = createClient(1);
         client.start(mCallback);
-        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
 
-        verify(mHal).onPointerDown(eq(0),
-                eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MAJOR), eq(TOUCH_MINOR));
+        PointerContext pc = new PointerContext();
+        pc.pointerId = POINTER_ID;
+        pc.x = TOUCH_X;
+        pc.y = TOUCH_Y;
+        pc.minor = TOUCH_MINOR;
+        pc.major = TOUCH_MAJOR;
+        client.onPointerDown(pc);
+
+        verify(mHal).onPointerDown(eq(POINTER_ID), eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MINOR),
+                eq(TOUCH_MAJOR));
         verify(mHal, never()).onPointerDownWithContext(any());
     }
 
@@ -174,26 +182,30 @@
     public void pointerUpWithContext_v2() throws RemoteException {
         final FingerprintEnrollClient client = createClient(2);
         client.start(mCallback);
-        client.onPointerUp();
 
-        verify(mHal).onPointerUpWithContext(mPointerContextCaptor.capture());
-        verify(mHal, never()).onPointerUp(eq(POINTER_ID));
+        PointerContext pc = new PointerContext();
+        pc.pointerId = POINTER_ID;
+        client.onPointerUp(pc);
 
-        final PointerContext pContext = mPointerContextCaptor.getValue();
-        assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+        verify(mHal).onPointerUpWithContext(eq(pc));
+        verify(mHal, never()).onPointerUp(anyInt());
     }
 
     @Test
     public void pointerDownWithContext_v2() throws RemoteException {
         final FingerprintEnrollClient client = createClient(2);
         client.start(mCallback);
-        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
 
-        verify(mHal).onPointerDownWithContext(mPointerContextCaptor.capture());
+        PointerContext pc = new PointerContext();
+        pc.pointerId = POINTER_ID;
+        pc.x = TOUCH_X;
+        pc.y = TOUCH_Y;
+        pc.minor = TOUCH_MINOR;
+        pc.major = TOUCH_MAJOR;
+        client.onPointerDown(pc);
+
+        verify(mHal).onPointerDownWithContext(eq(pc));
         verify(mHal, never()).onPointerDown(anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat());
-
-        final PointerContext pContext = mPointerContextCaptor.getValue();
-        assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
     }
 
     @Test
@@ -204,8 +216,10 @@
         verify(mLuxProbe).enable();
 
         client.onAcquired(2, 0);
-        client.onPointerUp();
-        client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+        PointerContext pc = new PointerContext();
+        client.onPointerUp(pc);
+        client.onPointerDown(pc);
         verify(mLuxProbe, never()).disable();
         verify(mLuxProbe, never()).destroy();
 
diff --git a/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
index ea746d1..faad961 100644
--- a/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
+++ b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
@@ -30,7 +30,7 @@
 import android.view.Display;
 import android.view.Surface;
 
-import java.util.HashMap;
+import java.util.Map;
 
 @RunWith(JUnit4.class)
 public class CameraServiceProxyTest {
@@ -75,24 +75,22 @@
                 /*ignoreResizableAndSdkCheck*/true)).isEqualTo(
                 CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
         // Check rotation and lens facing combinations
-        HashMap<Integer, Integer> backFacingMap = new HashMap<Integer, Integer>() {{
-            put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
-            put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_90);
-            put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_270);
-            put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
-        }};
+        Map<Integer, Integer> backFacingMap = Map.of(
+                Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE,
+                Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_90,
+                Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_270,
+                Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
         taskInfo.isFixedOrientationPortrait = true;
         backFacingMap.forEach((key, value) -> {
             assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
                     key, CameraCharacteristics.LENS_FACING_BACK,
                     /*ignoreResizableAndSdkCheck*/true)).isEqualTo(value);
         });
-        HashMap<Integer, Integer> frontFacingMap = new HashMap<Integer, Integer>() {{
-            put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
-            put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_270);
-            put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_90);
-            put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
-        }};
+        Map<Integer, Integer> frontFacingMap = Map.of(
+                Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE,
+                Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_270,
+                Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_90,
+                Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
         frontFacingMap.forEach((key, value) -> {
             assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
                     key, CameraCharacteristics.LENS_FACING_FRONT,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/OWNERS b/services/tests/servicestests/src/com/android/server/companion/virtual/OWNERS
new file mode 100644
index 0000000..2968104
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/OWNERS
@@ -0,0 +1 @@
+include /services/companion/java/com/android/server/companion/virtual/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
new file mode 100644
index 0000000..ef8a49f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.companion.virtual;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.hardware.Sensor;
+import android.os.Binder;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.server.LocalServices;
+import com.android.server.sensors.SensorManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class SensorControllerTest {
+
+    private static final int VIRTUAL_DEVICE_ID = 42;
+    private static final String VIRTUAL_SENSOR_NAME = "VirtualAccelerometer";
+    private static final int SENSOR_HANDLE = 7;
+
+    @Mock
+    private SensorManagerInternal mSensorManagerInternalMock;
+    private SensorController mSensorController;
+    private VirtualSensorEvent mSensorEvent;
+    private VirtualSensorConfig mVirtualSensorConfig;
+    private IBinder mSensorToken;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        LocalServices.removeServiceForTest(SensorManagerInternal.class);
+        LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+
+        mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
+        mSensorEvent = new VirtualSensorEvent.Builder(new float[] { 1f, 2f, 3f}).build();
+        mVirtualSensorConfig =
+                new VirtualSensorConfig.Builder(Sensor.TYPE_ACCELEROMETER, VIRTUAL_SENSOR_NAME)
+                        .build();
+        mSensorToken = new Binder("sensorToken");
+    }
+
+    @Test
+    public void createSensor_invalidHandle_throwsException() {
+        doReturn(/* handle= */0).when(mSensorManagerInternalMock).createRuntimeSensor(
+                anyInt(), anyInt(), anyString(), anyString(), any());
+
+        Throwable thrown = assertThrows(
+                RuntimeException.class,
+                () -> mSensorController.createSensor(mSensorToken, mVirtualSensorConfig));
+
+        assertThat(thrown.getCause().getMessage())
+                .contains("Received an invalid virtual sensor handle");
+    }
+
+    @Test
+    public void createSensor_success() {
+        doCreateSensorSuccessfully();
+
+        assertThat(mSensorController.getSensorDescriptors()).isNotEmpty();
+    }
+
+    @Test
+    public void sendSensorEvent_invalidToken_throwsException() {
+        doCreateSensorSuccessfully();
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mSensorController.sendSensorEvent(
+                        new Binder("invalidSensorToken"), mSensorEvent));
+    }
+
+    @Test
+    public void sendSensorEvent_success() {
+        doCreateSensorSuccessfully();
+
+        mSensorController.sendSensorEvent(mSensorToken, mSensorEvent);
+        verify(mSensorManagerInternalMock).sendSensorEvent(
+                SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, mSensorEvent.getTimestampNanos(),
+                mSensorEvent.getValues());
+    }
+
+    @Test
+    public void unregisterSensor_invalidToken_throwsException() {
+        doCreateSensorSuccessfully();
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mSensorController.unregisterSensor(new Binder("invalidSensorToken")));
+    }
+
+    @Test
+    public void unregisterSensor_success() {
+        doCreateSensorSuccessfully();
+
+        mSensorController.unregisterSensor(mSensorToken);
+        verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
+        assertThat(mSensorController.getSensorDescriptors()).isEmpty();
+    }
+
+    private void doCreateSensorSuccessfully() {
+        doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
+                anyInt(), anyInt(), anyString(), anyString(), any());
+        mSensorController.createSensor(mSensorToken, mVirtualSensorConfig);
+    }
+}
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 5fda3d6..9de6190 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
@@ -16,6 +16,9 @@
 
 package com.android.server.companion.virtual;
 
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
 import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -44,23 +47,29 @@
 import android.app.admin.DevicePolicyManager;
 import android.companion.AssociationInfo;
 import android.companion.virtual.IVirtualDeviceActivityListener;
+import android.companion.virtual.VirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
-import android.graphics.Point;
+import android.hardware.Sensor;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.IInputManager;
+import android.hardware.input.VirtualDpadConfig;
 import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualKeyboardConfig;
 import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseConfig;
 import android.hardware.input.VirtualMouseRelativeEvent;
 import android.hardware.input.VirtualMouseScrollEvent;
 import android.hardware.input.VirtualTouchEvent;
+import android.hardware.input.VirtualTouchscreenConfig;
 import android.net.MacAddress;
 import android.os.Binder;
 import android.os.Handler;
@@ -84,6 +93,7 @@
 import com.android.internal.app.BlockedAppStreamingActivity;
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
+import com.android.server.sensors.SensorManagerInternal;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -95,6 +105,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.function.Consumer;
 
 @Presubmit
@@ -121,16 +132,49 @@
     private static final int VENDOR_ID = 5;
     private static final String UNIQUE_ID = "uniqueid";
     private static final String PHYS = "phys";
-    private static final int DEVICE_ID = 42;
+    private static final int DEVICE_ID = 53;
     private static final int HEIGHT = 1800;
     private static final int WIDTH = 900;
+    private static final int SENSOR_HANDLE = 64;
     private static final Binder BINDER = new Binder("binder");
     private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
+    private static final int VIRTUAL_DEVICE_ID = 42;
+    private static final VirtualDpadConfig DPAD_CONFIG =
+            new VirtualDpadConfig.Builder()
+                    .setVendorId(VENDOR_ID)
+                    .setProductId(PRODUCT_ID)
+                    .setInputDeviceName(DEVICE_NAME)
+                    .setAssociatedDisplayId(DISPLAY_ID)
+                    .build();
+    private static final VirtualKeyboardConfig KEYBOARD_CONFIG =
+            new VirtualKeyboardConfig.Builder()
+                    .setVendorId(VENDOR_ID)
+                    .setProductId(PRODUCT_ID)
+                    .setInputDeviceName(DEVICE_NAME)
+                    .setAssociatedDisplayId(DISPLAY_ID)
+                    .build();
+    private static final VirtualMouseConfig MOUSE_CONFIG =
+            new VirtualMouseConfig.Builder()
+                    .setVendorId(VENDOR_ID)
+                    .setProductId(PRODUCT_ID)
+                    .setInputDeviceName(DEVICE_NAME)
+                    .setAssociatedDisplayId(DISPLAY_ID)
+                    .build();
+    private static final VirtualTouchscreenConfig TOUCHSCREEN_CONFIG =
+            new VirtualTouchscreenConfig.Builder()
+                    .setVendorId(VENDOR_ID)
+                    .setProductId(PRODUCT_ID)
+                    .setInputDeviceName(DEVICE_NAME)
+                    .setAssociatedDisplayId(DISPLAY_ID)
+                    .setWidthInPixels(WIDTH)
+                    .setHeightInPixels(HEIGHT)
+                    .build();
 
     private Context mContext;
     private InputManagerMockHelper mInputManagerMockHelper;
     private VirtualDeviceImpl mDeviceImpl;
     private InputController mInputController;
+    private SensorController mSensorController;
     private AssociationInfo mAssociationInfo;
     private VirtualDeviceManagerService mVdms;
     private VirtualDeviceManagerInternal mLocalService;
@@ -145,6 +189,8 @@
     @Mock
     private InputManagerInternal mInputManagerInternalMock;
     @Mock
+    private SensorManagerInternal mSensorManagerInternalMock;
+    @Mock
     private IVirtualDeviceActivityListener mActivityListener;
     @Mock
     private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
@@ -177,14 +223,36 @@
         return blockedActivities;
     }
 
+    private Intent createRestrictedActivityBlockedIntent(List displayCategories,
+            String targetDisplayCategory) {
+        mDeviceImpl.onVirtualDisplayCreatedLocked(
+                mDeviceImpl.createWindowPolicyController(displayCategories), DISPLAY_ID);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+                DISPLAY_ID);
+        doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+        ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoveDevices= */ true,
+                targetDisplayCategory);
+        Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+                activityInfos.get(0), mAssociationInfo.getDisplayName());
+        gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+        return blockedAppIntent;
+    }
+
+
     private ArrayList<ActivityInfo> getActivityInfoList(
-            String packageName, String name, boolean displayOnRemoveDevices) {
+            String packageName, String name, boolean displayOnRemoveDevices,
+            String requiredDisplayCategory) {
         ActivityInfo activityInfo = new ActivityInfo();
         activityInfo.packageName = packageName;
         activityInfo.name = name;
         activityInfo.flags = displayOnRemoveDevices
                 ? FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES : FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES;
         activityInfo.applicationInfo = mApplicationInfoMock;
+        activityInfo.requiredDisplayCategory = requiredDisplayCategory;
         return new ArrayList<>(Arrays.asList(activityInfo));
     }
 
@@ -201,6 +269,9 @@
         LocalServices.removeServiceForTest(InputManagerInternal.class);
         LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
 
+        LocalServices.removeServiceForTest(SensorManagerInternal.class);
+        LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+
         final DisplayInfo displayInfo = new DisplayInfo();
         displayInfo.uniqueId = UNIQUE_ID;
         doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
@@ -225,6 +296,7 @@
         mInputController = new InputController(new Object(), mNativeWrapperMock,
                 new Handler(TestableLooper.get(this).getLooper()),
                 mContext.getSystemService(WindowManager.class), threadVerifier);
+        mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
 
         mAssociationInfo = new AssociationInfo(1, 0, null,
                 MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
@@ -237,15 +309,64 @@
                 .setBlockedActivities(getBlockedActivities())
                 .build();
         mDeviceImpl = new VirtualDeviceImpl(mContext,
-                mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
-                mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
-                mActivityListener, mRunningAppsChangedCallback, params);
+                mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
+                mInputController, mSensorController, (int associationId) -> {},
+                mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
+        mVdms.addVirtualDevice(mDeviceImpl);
+    }
+
+    @Test
+    public void getDevicePolicy_invalidDeviceId_returnsDefault() {
+        assertThat(
+                mLocalService.getDevicePolicy(
+                        VirtualDeviceManager.INVALID_DEVICE_ID, POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_DEFAULT);
+    }
+
+    @Test
+    public void getDevicePolicy_defaultDeviceId_returnsDefault() {
+        assertThat(
+                mLocalService.getDevicePolicy(
+                        VirtualDeviceManager.DEFAULT_DEVICE_ID, POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_DEFAULT);
+    }
+
+    @Test
+    public void getDevicePolicy_nonExistentDeviceId_returnsDefault() {
+        assertThat(
+                mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId() + 1, POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_DEFAULT);
+    }
+
+    @Test
+    public void getDevicePolicy_unspecifiedPolicy_returnsDefault() {
+        assertThat(
+                mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_DEFAULT);
+    }
+
+    @Test
+    public void getDevicePolicy_returnsCustom() {
+        VirtualDeviceParams params = new VirtualDeviceParams
+                .Builder()
+                .setBlockedActivities(getBlockedActivities())
+                .setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+                .build();
+        mDeviceImpl = new VirtualDeviceImpl(mContext,
+                mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
+                mInputController, mSensorController, (int associationId) -> {},
+                mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
+        mVdms.addVirtualDevice(mDeviceImpl);
+
+        assertThat(
+                mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_CUSTOM);
     }
 
     @Test
     public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         // This call should not throw any exceptions.
         mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID);
     }
@@ -264,7 +385,7 @@
     public void onVirtualDisplayRemovedLocked_listenersNotified() {
         mLocalService.registerVirtualDisplayListener(mDisplayListener);
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
 
         mLocalService.onVirtualDisplayRemoved(mDeviceImpl, DISPLAY_ID);
         TestableLooper.get(this).processAllMessages();
@@ -326,7 +447,7 @@
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
                 nullable(String.class), anyInt(), eq(null));
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
                 nullable(String.class), eq(DISPLAY_ID), eq(null));
@@ -335,9 +456,10 @@
     @Test
     public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
             throws RemoteException {
-        GenericWindowPolicyController gwpc = mDeviceImpl.createWindowPolicyController();
+        GenericWindowPolicyController gwpc = mDeviceImpl.createWindowPolicyController(
+                new ArrayList<>());
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         assertThrows(IllegalStateException.class,
                 () -> mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID));
         TestableLooper.get(this).processAllMessages();
@@ -356,7 +478,7 @@
     @Test
     public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -372,7 +494,7 @@
     @Test
     public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -388,34 +510,77 @@
 
     @Test
     public void createVirtualDpad_noDisplay_failsSecurityException() {
-        assertThrows(
-                SecurityException.class,
-                () -> mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
-                        PRODUCT_ID, BINDER));
+        assertThrows(SecurityException.class,
+                () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
     }
 
     @Test
     public void createVirtualKeyboard_noDisplay_failsSecurityException() {
-        assertThrows(
-                SecurityException.class,
-                () -> mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
-                        PRODUCT_ID, BINDER));
+        assertThrows(SecurityException.class,
+                () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
     }
 
     @Test
     public void createVirtualMouse_noDisplay_failsSecurityException() {
-        assertThrows(
-                SecurityException.class,
-                () -> mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
-                        PRODUCT_ID, BINDER));
+        assertThrows(SecurityException.class,
+                () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
     }
 
     @Test
     public void createVirtualTouchscreen_noDisplay_failsSecurityException() {
-        assertThrows(
-                SecurityException.class,
-                () -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME,
-                        VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)));
+        assertThrows(SecurityException.class,
+                () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
+    }
+
+    @Test
+    public void createVirtualTouchscreen_zeroDisplayDimension_failsIllegalArgumentException() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        final VirtualTouchscreenConfig zeroConfig =
+                new VirtualTouchscreenConfig.Builder()
+                        .setVendorId(VENDOR_ID)
+                        .setProductId(PRODUCT_ID)
+                        .setInputDeviceName(DEVICE_NAME)
+                        .setAssociatedDisplayId(DISPLAY_ID)
+                        .setWidthInPixels(0)
+                        .setHeightInPixels(0)
+                        .build();
+        assertThrows(IllegalArgumentException.class,
+                () -> mDeviceImpl.createVirtualTouchscreen(zeroConfig, BINDER));
+    }
+
+    @Test
+    public void createVirtualTouchscreen_negativeDisplayDimension_failsIllegalArgumentException() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        final VirtualTouchscreenConfig negativeConfig =
+                new VirtualTouchscreenConfig.Builder()
+                        .setVendorId(VENDOR_ID)
+                        .setProductId(PRODUCT_ID)
+                        .setInputDeviceName(DEVICE_NAME)
+                        .setAssociatedDisplayId(DISPLAY_ID)
+                        .setWidthInPixels(-100)
+                        .setHeightInPixels(-100)
+                        .build();
+        assertThrows(IllegalArgumentException.class,
+                () -> mDeviceImpl.createVirtualTouchscreen(negativeConfig, BINDER));
+
+    }
+
+    @Test
+    public void createVirtualTouchscreen_positiveDisplayDimension_successful() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        VirtualTouchscreenConfig positiveConfig =
+                new VirtualTouchscreenConfig.Builder()
+                        .setVendorId(VENDOR_ID)
+                        .setProductId(PRODUCT_ID)
+                        .setInputDeviceName(DEVICE_NAME)
+                        .setAssociatedDisplayId(DISPLAY_ID)
+                        .setWidthInPixels(600)
+                        .setHeightInPixels(800)
+                        .build();
+        mDeviceImpl.createVirtualTouchscreen(positiveConfig, BINDER);
+        assertWithMessage(
+                "Virtual touchscreen should create input device descriptor on successful creation"
+                        + ".").that(mInputController.getInputDeviceDescriptors()).isNotEmpty();
     }
 
     @Test
@@ -430,10 +595,8 @@
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
                 eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(
-                SecurityException.class,
-                () -> mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
-                        PRODUCT_ID, BINDER));
+        assertThrows(SecurityException.class,
+                () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
     }
 
     @Test
@@ -441,10 +604,8 @@
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
                 eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(
-                SecurityException.class,
-                () -> mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
-                        PRODUCT_ID, BINDER));
+        assertThrows(SecurityException.class,
+                () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
     }
 
     @Test
@@ -452,10 +613,8 @@
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
                 eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(
-                SecurityException.class,
-                () -> mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
-                        PRODUCT_ID, BINDER));
+        assertThrows(SecurityException.class,
+                () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
     }
 
     @Test
@@ -463,10 +622,20 @@
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
                 eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+        assertThrows(SecurityException.class,
+                () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
+    }
+
+    @Test
+    public void createVirtualSensor_noPermission_failsSecurityException() {
+        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
         assertThrows(
                 SecurityException.class,
-                () -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME,
-                        VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)));
+                () -> mDeviceImpl.createVirtualSensor(
+                        BINDER,
+                        new VirtualSensorConfig.Builder(
+                                Sensor.TYPE_ACCELEROMETER, DEVICE_NAME).build()));
     }
 
     @Test
@@ -489,21 +658,19 @@
     @Test
     public void createVirtualDpad_hasDisplay_obtainFileDescriptor() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
-        mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
-                BINDER);
-        assertWithMessage("Virtual dpad should register fd when the display matches")
-          	.that(mInputController.mInputDeviceDescriptors).isNotEmpty();
-        verify(mNativeWrapperMock).openUinputDpad(eq(DEVICE_NAME), eq(VENDOR_ID),
-                eq(PRODUCT_ID), anyString());
+        mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER);
+        assertWithMessage("Virtual dpad should register fd when the display matches").that(
+                mInputController.getInputDeviceDescriptors()).isNotEmpty();
+        verify(mNativeWrapperMock).openUinputDpad(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID),
+                anyString());
     }
 
     @Test
     public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
-        mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
-                BINDER);
-        assertWithMessage("Virtual keyboard should register fd when the display matches")
-                .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
+        mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
+        assertWithMessage("Virtual keyboard should register fd when the display matches").that(
+                mInputController.getInputDeviceDescriptors()).isNotEmpty();
         verify(mNativeWrapperMock).openUinputKeyboard(eq(DEVICE_NAME), eq(VENDOR_ID),
                 eq(PRODUCT_ID), anyString());
     }
@@ -511,10 +678,9 @@
     @Test
     public void createVirtualMouse_hasDisplay_obtainFileDescriptor() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
-        mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
-                BINDER);
-        assertWithMessage("Virtual mouse should register fd when the display matches")
-                .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
+        mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER);
+        assertWithMessage("Virtual mouse should register fd when the display matches").that(
+                mInputController.getInputDeviceDescriptors()).isNotEmpty();
         verify(mNativeWrapperMock).openUinputMouse(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID),
                 anyString());
     }
@@ -522,10 +688,9 @@
     @Test
     public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() {
         mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
-        mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
-                BINDER, new Point(WIDTH, HEIGHT));
-        assertWithMessage("Virtual touchscreen should register fd when the display matches")
-                .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
+        mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER);
+        assertWithMessage("Virtual touchscreen should register fd when the display matches").that(
+                mInputController.getInputDeviceDescriptors()).isNotEmpty();
         verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
                 eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
     }
@@ -543,7 +708,7 @@
     @Test
     public void onAudioSessionStarting_hasVirtualAudioController() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
 
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
 
@@ -553,7 +718,7 @@
     @Test
     public void onAudioSessionEnded_noVirtualAudioController() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
 
         mDeviceImpl.onAudioSessionEnded();
@@ -564,7 +729,7 @@
     @Test
     public void close_cleanVirtualAudioController() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
 
         mDeviceImpl.close();
@@ -573,6 +738,17 @@
     }
 
     @Test
+    public void close_cleanSensorController() {
+        mSensorController.addSensorForTesting(
+                BINDER, SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, DEVICE_NAME);
+
+        mDeviceImpl.close();
+
+        assertThat(mSensorController.getSensorDescriptors()).isEmpty();
+        verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
+    }
+
+    @Test
     public void sendKeyEvent_noFd() {
         assertThrows(
                 IllegalArgumentException.class,
@@ -768,9 +944,28 @@
         mDeviceImpl.mVirtualDisplayIds.add(1);
         mDeviceImpl.mVirtualDisplayIds.add(2);
         mDeviceImpl.mVirtualDisplayIds.add(3);
-        mDeviceImpl.createVirtualMouse(1, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER);
-        mDeviceImpl.createVirtualMouse(2, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER);
-        mDeviceImpl.createVirtualMouse(3, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER);
+        VirtualMouseConfig config1 = new VirtualMouseConfig.Builder()
+                .setAssociatedDisplayId(1)
+                .setInputDeviceName(DEVICE_NAME)
+                .setVendorId(VENDOR_ID)
+                .setProductId(PRODUCT_ID)
+                .build();
+        VirtualMouseConfig config2 = new VirtualMouseConfig.Builder()
+                .setAssociatedDisplayId(2)
+                .setInputDeviceName(DEVICE_NAME)
+                .setVendorId(VENDOR_ID)
+                .setProductId(PRODUCT_ID)
+                .build();
+        VirtualMouseConfig config3 = new VirtualMouseConfig.Builder()
+                .setAssociatedDisplayId(3)
+                .setInputDeviceName(DEVICE_NAME)
+                .setVendorId(VENDOR_ID)
+                .setProductId(PRODUCT_ID)
+                .build();
+
+        mDeviceImpl.createVirtualMouse(config1, BINDER);
+        mDeviceImpl.createVirtualMouse(config2, BINDER);
+        mDeviceImpl.createVirtualMouse(config3, BINDER);
         mDeviceImpl.setShowPointerIcon(false);
         verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(false), anyInt());
         verify(mInputManagerInternalMock, never()).setPointerIconVisible(eq(true), anyInt());
@@ -781,14 +976,16 @@
     @Test
     public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 NONBLOCKED_APP_PACKAGE_NAME,
-                NONBLOCKED_APP_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoveDevices */ true,
+                /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfos.get(0), mAssociationInfo.getDisplayName());
         gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
@@ -800,14 +997,16 @@
     @Test
     public void openPermissionControllerOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 PERMISSION_CONTROLLER_PACKAGE_NAME,
-                PERMISSION_CONTROLLER_PACKAGE_NAME, /* displayOnRemoveDevices */  false);
+                PERMISSION_CONTROLLER_PACKAGE_NAME,
+                /* displayOnRemoveDevices */  false,
+                /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfos.get(0), mAssociationInfo.getDisplayName());
         gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
@@ -819,14 +1018,16 @@
     @Test
     public void openSettingsOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 SETTINGS_PACKAGE_NAME,
-                SETTINGS_PACKAGE_NAME, /* displayOnRemoveDevices */  true);
+                SETTINGS_PACKAGE_NAME,
+                /* displayOnRemoveDevices */ true,
+                /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfos.get(0), mAssociationInfo.getDisplayName());
         gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
@@ -838,14 +1039,16 @@
     @Test
     public void openVendingOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 VENDING_PACKAGE_NAME,
-                VENDING_PACKAGE_NAME, /* displayOnRemoveDevices */  true);
+                VENDING_PACKAGE_NAME,
+                /* displayOnRemoveDevices */ true,
+                /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfos.get(0), mAssociationInfo.getDisplayName());
         gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
@@ -857,14 +1060,16 @@
     @Test
     public void openGoogleDialerOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 GOOGLE_DIALER_PACKAGE_NAME,
-                GOOGLE_DIALER_PACKAGE_NAME, /* displayOnRemoveDevices */  true);
+                GOOGLE_DIALER_PACKAGE_NAME,
+                /* displayOnRemoveDevices */ true,
+                /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfos.get(0), mAssociationInfo.getDisplayName());
         gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
@@ -876,14 +1081,16 @@
     @Test
     public void openGoogleMapsOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 GOOGLE_MAPS_PACKAGE_NAME,
-                GOOGLE_MAPS_PACKAGE_NAME, /* displayOnRemoveDevices */  true);
+                GOOGLE_MAPS_PACKAGE_NAME,
+                /* displayOnRemoveDevices */ true,
+                /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfos.get(0), mAssociationInfo.getDisplayName());
         gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
@@ -896,7 +1103,7 @@
     public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
 
@@ -911,7 +1118,7 @@
     public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
                 DISPLAY_ID);
         mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID);
@@ -921,4 +1128,37 @@
 
         assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
     }
+
+    @Test
+    public void nonRestrictedActivityOnRestrictedVirtualDisplay_startBlockedAlertActivity() {
+        Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"),
+                /* targetDisplayCategory= */ null);
+        verify(mContext).startActivityAsUser(argThat(intent ->
+                intent.filterEquals(blockedAppIntent)), any(), any());
+
+    }
+
+    @Test
+    public void restrictedActivityOnRestrictedVirtualDisplay_doesNotStartBlockedAlertActivity() {
+        Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"), "abc");
+        verify(mContext, never()).startActivityAsUser(argThat(intent ->
+                intent.filterEquals(blockedAppIntent)), any(), any());
+    }
+
+    @Test
+    public void restrictedActivityOnNonRestrictedVirtualDisplay_startBlockedAlertActivity() {
+        Intent blockedAppIntent = createRestrictedActivityBlockedIntent(
+                /* displayCategories= */ List.of(), "abc");
+        verify(mContext).startActivityAsUser(argThat(intent ->
+                intent.filterEquals(blockedAppIntent)), any(), any());
+    }
+
+    @Test
+    public void
+            restrictedActivityOnNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() {
+        Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"), "def");
+        verify(mContext).startActivityAsUser(argThat(intent ->
+                intent.filterEquals(blockedAppIntent)), any(), any());
+    }
+
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
index 77f1e24..aefe4b6 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -16,9 +16,14 @@
 
 package com.android.server.companion.virtual;
 
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
+import static android.hardware.Sensor.TYPE_ACCELEROMETER;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.os.Parcel;
 import android.os.UserHandle;
 
@@ -27,16 +32,25 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
 import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
 public class VirtualDeviceParamsTest {
 
+    private static final String SENSOR_NAME = "VirtualSensorName";
+    private static final String SENSOR_VENDOR = "VirtualSensorVendor";
+
     @Test
     public void parcelable_shouldRecreateSuccessfully() {
         VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
                 .setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
                 .setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
+                .setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+                .addVirtualSensorConfig(
+                        new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+                                .setVendor(SENSOR_VENDOR)
+                                .build())
                 .build();
         Parcel parcel = Parcel.obtain();
         originalParams.writeToParcel(parcel, 0);
@@ -47,5 +61,14 @@
         assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
         assertThat(params.getUsersWithMatchingAccounts())
                 .containsExactly(UserHandle.of(123), UserHandle.of(456));
+        assertThat(params.getDevicePolicy(POLICY_TYPE_SENSORS)).isEqualTo(DEVICE_POLICY_CUSTOM);
+
+        List<VirtualSensorConfig> sensorConfigs = params.getVirtualSensorConfigs();
+        assertThat(sensorConfigs).hasSize(1);
+        VirtualSensorConfig sensorConfig = sensorConfigs.get(0);
+        assertThat(sensorConfig.getType()).isEqualTo(TYPE_ACCELEROMETER);
+        assertThat(sensorConfig.getName()).isEqualTo(SENSOR_NAME);
+        assertThat(sensorConfig.getVendor()).isEqualTo(SENSOR_VENDOR);
+        assertThat(sensorConfig.getStateChangeCallback()).isNull();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 4c939f0..aaa1351 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.server.companion.virtual.audio;
 
-import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
 import static android.media.AudioAttributes.FLAG_SECURE;
 import static android.media.AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -72,21 +71,26 @@
         MockitoAnnotations.initMocks(this);
         mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
         mVirtualAudioController = new VirtualAudioController(mContext);
-        mGenericWindowPolicyController = new GenericWindowPolicyController(
-                FLAG_SECURE,
-                SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
-                /* allowedUsers= */ new ArraySet<>(),
-                /* allowedCrossTaskNavigations= */ new ArraySet<>(),
-                /* blockedCrossTaskNavigations= */ new ArraySet<>(),
-                /* allowedActivities= */ new ArraySet<>(),
-                /* blockedActivities= */ new ArraySet<>(),
-                VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED,
-                /* activityListener= */ null,
-                /* activityBlockedCallback= */ null,
-                /* secureWindowCallback= */ null,
-                /* deviceProfile= */ DEVICE_PROFILE_APP_STREAMING);
+        mGenericWindowPolicyController =
+                new GenericWindowPolicyController(
+                        FLAG_SECURE,
+                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                        /* allowedUsers= */ new ArraySet<>(),
+                        /* allowedCrossTaskNavigations= */ new ArraySet<>(),
+                        /* blockedCrossTaskNavigations= */ new ArraySet<>(),
+                        /* allowedActivities= */ new ArraySet<>(),
+                        /* blockedActivities= */ new ArraySet<>(),
+                        VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED,
+                        /* activityListener= */ null,
+                        /* pipBlockedCallback= */ null,
+                        /* activityBlockedCallback= */ null,
+                        /* secureWindowCallback= */ null,
+                        /* displayCategories= */ new ArrayList<>(),
+                        /* recentsPolicy= */
+                        VirtualDeviceParams.RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS);
     }
 
+
     @Test
     public void startListening_receivesCallback() throws RemoteException {
         ArraySet<Integer> runningUids = new ArraySet<>();
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
index c2a81d9..d4e3d44 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
@@ -16,12 +16,40 @@
 
 package com.android.server.content;
 
+import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
+import static android.content.pm.UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT;
+import static android.content.pm.UserProperties.SHOW_IN_SETTINGS_WITH_PARENT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.accounts.AccountManagerInternal;
 import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
 import android.os.Bundle;
+import android.os.UserManager;
+import android.provider.ContactsContract;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.job.JobSchedulerInternal;
+
 import junit.framework.TestCase;
 
+import org.jetbrains.annotations.NotNull;
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
 /**
  * Tests for SyncManager.
  *
@@ -33,6 +61,43 @@
     final String KEY_1 = "key_1";
     final String KEY_2 = "key_2";
 
+    private SyncManager mSyncManager;
+    private Context mContext;
+
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private AccountManagerInternal mAccountManagerInternal;
+    @Mock
+    private JobSchedulerInternal mJobSchedulerInternal;
+
+    private class SyncManagerWithMockedServices extends SyncManager {
+
+        @Override
+        protected AccountManagerInternal getAccountManagerInternal() {
+            return mAccountManagerInternal;
+        }
+
+        @Override
+        protected JobSchedulerInternal getJobSchedulerInternal() {
+            return mJobSchedulerInternal;
+        }
+
+        private SyncManagerWithMockedServices(Context context, boolean factoryTest) {
+            super(context, factoryTest);
+        }
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        doNothing().when(mAccountManagerInternal).addOnAppPermissionChangeListener(any());
+        when(mJobSchedulerInternal.getSystemScheduledPendingJobs()).thenReturn(new ArrayList<>());
+        mSyncManager = new SyncManagerWithMockedServices(mContext, true);
+    }
+
     public void testSyncExtrasEquals_WithNull() throws Exception {
         Bundle b1 = new Bundle();
         Bundle b2 = new Bundle();
@@ -140,4 +205,45 @@
         final StringBuilder sb = new StringBuilder();
         assertEquals(expected, SyncManager.formatDurationHMS(sb, time * 1000).toString());
     }
+
+    private UserInfo createUserInfo(String name, int id, int groupId, int flags) {
+        final UserInfo ui = new UserInfo(id, name, flags | UserInfo.FLAG_INITIALIZED);
+        ui.profileGroupId = groupId;
+        return ui;
+    }
+
+    @NotNull
+    private UserProperties getCloneUserProperties() {
+        return new UserProperties.Builder()
+                .setStartWithParent(true)
+                .setShowInLauncher(SHOW_IN_LAUNCHER_WITH_PARENT)
+                .setShowInSettings(SHOW_IN_SETTINGS_WITH_PARENT)
+                .setUseParentsContacts(true)
+                .setInheritDevicePolicy(INHERIT_DEVICE_POLICY_FROM_PARENT)
+                .build();
+    }
+
+    private void mockUserProperties(UserInfo primaryUserInfo, UserInfo cloneUserInfo) {
+        UserProperties cloneUserProperties = getCloneUserProperties();
+        when(mUserManager.getUserProperties(cloneUserInfo.getUserHandle()))
+                .thenReturn(cloneUserProperties);
+        // Set default user properties for primary user
+        when(mUserManager.getUserProperties(primaryUserInfo.getUserHandle()))
+                .thenReturn(new UserProperties.Builder().build());
+    }
+
+    public void testShouldDisableSync() {
+        UserInfo primaryUserInfo = createUserInfo("primary", 0 /* id */, 0 /* groupId */,
+                UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN);
+        UserInfo cloneUserInfo = createUserInfo("clone", 10 /* id */, 0 /* groupId */,
+                UserInfo.FLAG_PROFILE);
+
+        mockUserProperties(primaryUserInfo, cloneUserInfo);
+
+        // Clone user accounts must have contact syncs disabled
+        assertThat(mSyncManager.shouldDisableSyncForUser(cloneUserInfo,
+                ContactsContract.AUTHORITY)).isTrue();
+        assertThat(mSyncManager.shouldDisableSyncForUser(primaryUserInfo,
+                ContactsContract.AUTHORITY)).isFalse();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 3f0022d..f0013a6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -35,6 +35,7 @@
 import android.net.IIpConnectivityMetrics;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.Looper;
 import android.os.PowerManagerInternal;
 import android.os.UserHandle;
@@ -58,6 +59,7 @@
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.Map;
 
@@ -449,7 +451,7 @@
         @Override
         public TransferOwnershipMetadataManager newTransferOwnershipMetadataManager() {
             return new TransferOwnershipMetadataManager(
-                    new TransferOwnershipMetadataManagerTest.MockInjector());
+                    new TransferOwnershipMetadataManagerMockInjector());
         }
 
         @Override
@@ -502,4 +504,12 @@
             return context;
         }
     }
+
+    static class TransferOwnershipMetadataManagerMockInjector extends
+            TransferOwnershipMetadataManager.Injector {
+        @Override
+        public File getOwnerTransferMetadataDir() {
+            return Environment.getExternalStorageDirectory();
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ddb3049..a02b0f1 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -133,18 +133,18 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.security.KeyChain;
 import android.security.keystore.AttestationUtils;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
-import android.test.MoreAsserts; // TODO(b/171932723): replace by Truth
+import android.test.MoreAsserts;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
@@ -4782,6 +4782,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 260145949)
     public void testLockTaskPolicyForProfileOwner() throws Exception {
         mockPolicyExemptApps();
         mockVendorPolicyExemptApps();
@@ -4817,6 +4818,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 260145949)
     public void testLockTaskFeatures_IllegalArgumentException() throws Exception {
         // Setup a device owner.
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
@@ -5087,7 +5089,7 @@
     }
 
     @Test
-    public void testWipeDataDeviceOwner() throws Exception {
+    public void testWipeDevice_DeviceOwner() throws Exception {
         setDeviceOwner();
         when(getServices().userManager.getUserRestrictionSource(
                 UserManager.DISALLOW_FACTORY_RESET,
@@ -5096,7 +5098,7 @@
         when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
                 thenReturn("Just a test string.");
 
-        dpm.wipeData(0);
+        dpm.wipeDevice(0);
 
         verifyRebootWipeUserData(/* wipeEuicc= */ false);
     }
@@ -5111,13 +5113,13 @@
         when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
                 thenReturn("Just a test string.");
 
-        dpm.wipeData(WIPE_EUICC);
+        dpm.wipeDevice(WIPE_EUICC);
 
         verifyRebootWipeUserData(/* wipeEuicc= */ true);
     }
 
     @Test
-    public void testWipeDataDeviceOwnerDisallowed() throws Exception {
+    public void testWipeDevice_DeviceOwnerDisallowed() throws Exception {
         setDeviceOwner();
         when(getServices().userManager.getUserRestrictionSource(
                 UserManager.DISALLOW_FACTORY_RESET,
@@ -5128,7 +5130,7 @@
         // The DO is not allowed to wipe the device if the user restriction was set
         // by the system
         assertExpectException(SecurityException.class, /* messageRegex= */ null,
-                () -> dpm.wipeData(0));
+                () -> dpm.wipeDevice(0));
     }
 
     @Test
@@ -7812,6 +7814,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 260145949)
     public void testSetLockTaskFeatures_financeDo_validLockTaskFeatures_lockTaskFeaturesSet()
             throws Exception {
         int validLockTaskFeatures = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
@@ -7827,6 +7830,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 260145949)
     public void testSetLockTaskFeatures_financeDo_invalidLockTaskFeatures_throwsException()
             throws Exception {
         int invalidLockTaskFeatures = LOCK_TASK_FEATURE_NONE | LOCK_TASK_FEATURE_OVERVIEW
@@ -7843,6 +7847,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 260145949)
     public void testIsUninstallBlocked_financeDo_success() throws Exception {
         String packageName = "com.android.foo.package";
         setDeviceOwner();
@@ -7943,6 +7948,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 260145949)
     public void testSetLockTaskPackages_financeDo_success() throws Exception {
         String[] packages = {"com.android.foo.package"};
         mockEmptyPolicyExemptApps();
@@ -7986,7 +7992,7 @@
     }
 
     @Test
-    public void testWipeData_financeDo_success() throws Exception {
+    public void testWipeDevice_financeDo_success() throws Exception {
         setDeviceOwner();
         dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
         when(getServices().userManager.getUserRestrictionSource(
@@ -7997,7 +8003,7 @@
                 .getString(R.string.work_profile_deleted_description_dpm_wipe))
                 .thenReturn("Test string");
 
-        dpm.wipeData(0);
+        dpm.wipeDevice(0);
 
         verifyRebootWipeUserData(/* wipeEuicc= */ false);
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index e991ec6..ac1667d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -442,7 +442,8 @@
     @Override
     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
         mMockSystemServices.registerReceiver(receiver, filter, null);
-        return spiedContext.registerReceiver(receiver, filter);
+        return spiedContext.registerReceiver(receiver, filter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
index df672c9..2c4fe53 100644
--- a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
@@ -424,7 +424,7 @@
 
         @Override
         public LocalDate getLocalDate() {
-            return LocalDate.from(mLocalDate);
+            return mLocalDate;
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 0206839..ae36871 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
@@ -176,7 +177,7 @@
 
         // Send new sensor value and verify
         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
-        assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), 0.001f);
+        assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
 
         // Set up system to return 0.0f (minimum possible brightness) as a brightness value
         float lux2 = 10.0f;
@@ -190,7 +191,7 @@
 
         // Send new sensor value and verify
         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
-        assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), 0.001f);
+        assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
     }
 
     @Test
@@ -219,7 +220,7 @@
 
         // Send new sensor value and verify
         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
-        assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), 0.001f);
+        assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
 
 
         // Set up system to return 1.0f as a brightness value (brightness_max)
@@ -234,7 +235,7 @@
 
         // Send new sensor value and verify
         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
-        assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), 0.001f);
+        assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
     }
 
     @Test
@@ -416,6 +417,12 @@
         // ambient lux goes to 0
         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
         assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+        // only the values within the horizon should be kept
+        assertArrayEquals(new float[] {10000, 10000, 0, 0, 0}, mController.getLastSensorValues(),
+                EPSILON);
+        assertArrayEquals(new long[] {4000, 4500, 5000, 5500, 6000},
+                mController.getLastSensorTimestamps());
     }
 
     @Test
@@ -487,4 +494,92 @@
                 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
         assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
     }
+
+    @Test
+    public void testGetSensorReadings() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Choose values such that the ring buffer's capacity is extended and the buffer is pruned
+        int increment = 11;
+        int lux = 5000;
+        for (int i = 0; i < 1000; i++) {
+            lux += increment;
+            mClock.fastForward(increment);
+            listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+        }
+
+        int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment + 1);
+        float[] sensorValues = mController.getLastSensorValues();
+        long[] sensorTimestamps = mController.getLastSensorTimestamps();
+
+        // Only the values within the horizon should be kept
+        assertEquals(valuesCount, sensorValues.length);
+        assertEquals(valuesCount, sensorTimestamps.length);
+
+        long sensorTimestamp = mClock.now();
+        for (int i = valuesCount - 1; i >= 1; i--) {
+            assertEquals(lux, sensorValues[i], EPSILON);
+            assertEquals(sensorTimestamp, sensorTimestamps[i]);
+            lux -= increment;
+            sensorTimestamp -= increment;
+        }
+        assertEquals(lux, sensorValues[0], EPSILON);
+        assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
+    }
+
+    @Test
+    public void testGetSensorReadingsFullBuffer() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+        int initialCapacity = 150;
+
+        // Choose values such that the ring buffer is pruned
+        int increment1 = 200;
+        int lux = 5000;
+        for (int i = 0; i < 20; i++) {
+            lux += increment1;
+            mClock.fastForward(increment1);
+            listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+        }
+
+        int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment1 + 1);
+
+        // Choose values such that the buffer becomes full
+        int increment2 = 1;
+        for (int i = 0; i < initialCapacity - valuesCount; i++) {
+            lux += increment2;
+            mClock.fastForward(increment2);
+            listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+        }
+
+        float[] sensorValues = mController.getLastSensorValues();
+        long[] sensorTimestamps = mController.getLastSensorTimestamps();
+
+        // The buffer should be full
+        assertEquals(initialCapacity, sensorValues.length);
+        assertEquals(initialCapacity, sensorTimestamps.length);
+
+        long sensorTimestamp = mClock.now();
+        for (int i = initialCapacity - 1; i >= 1; i--) {
+            assertEquals(lux, sensorValues[i], EPSILON);
+            assertEquals(sensorTimestamp, sensorTimestamps[i]);
+
+            if (i >= valuesCount) {
+                lux -= increment2;
+                sensorTimestamp -= increment2;
+            } else {
+                lux -= increment1;
+                sensorTimestamp -= increment1;
+            }
+        }
+        assertEquals(lux, sensorValues[0], EPSILON);
+        assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index c2e8417f..6def7b1 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -136,28 +136,28 @@
         assertNull(mInjector.mSensorListener);
         assertNotNull(mInjector.mBroadcastReceiver);
         assertTrue(mInjector.mIdleScheduled);
-        mInjector.sendScreenChange(/*screen on */ true);
+        mInjector.sendScreenChange(/* screenOn= */ true);
         assertNotNull(mInjector.mSensorListener);
         assertTrue(mInjector.mColorSamplingEnabled);
 
-        mInjector.sendScreenChange(/*screen on */ false);
+        mInjector.sendScreenChange(/* screenOn= */ false);
         assertNull(mInjector.mSensorListener);
         assertFalse(mInjector.mColorSamplingEnabled);
 
         // Turn screen on while brightness mode is manual
-        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic */ false);
-        mInjector.sendScreenChange(/*screen on */ true);
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
+        mInjector.sendScreenChange(/* screenOn= */ true);
         assertNull(mInjector.mSensorListener);
         assertFalse(mInjector.mColorSamplingEnabled);
 
         // Set brightness mode to automatic while screen is off.
-        mInjector.sendScreenChange(/*screen on */ false);
-        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic */ true);
+        mInjector.sendScreenChange(/* screenOn= */ false);
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
         assertNull(mInjector.mSensorListener);
         assertFalse(mInjector.mColorSamplingEnabled);
 
         // Turn on screen while brightness mode is automatic.
-        mInjector.sendScreenChange(/*screen on */ true);
+        mInjector.sendScreenChange(/* screenOn= */ true);
         assertNotNull(mInjector.mSensorListener);
         assertTrue(mInjector.mColorSamplingEnabled);
 
@@ -188,14 +188,14 @@
         assertFalse(mInjector.mColorSamplingEnabled);
 
         // Pretend screen is off, update config to turn on color sampling.
-        mInjector.sendScreenChange(/*screen on */ false);
+        mInjector.sendScreenChange(/* screenOn= */ false);
         mTracker.setBrightnessConfiguration(buildBrightnessConfiguration(
                 /* collectColorSamples= */ true));
         mInjector.waitForHandler();
         assertFalse(mInjector.mColorSamplingEnabled);
 
         // Pretend screen is on.
-        mInjector.sendScreenChange(/*screen on */ true);
+        mInjector.sendScreenChange(/* screenOn= */ true);
         assertTrue(mInjector.mColorSamplingEnabled);
 
         mTracker.stop();
@@ -261,7 +261,7 @@
         assertFalse(mInjector.mColorSamplingEnabled);
         assertNull(mInjector.mDisplayListener);
 
-        mInjector.setBrightnessMode(/*isBrightnessModeAutomatic*/ true);
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
         assertNotNull(mInjector.mSensorListener);
         assertTrue(mInjector.mColorSamplingEnabled);
         assertNotNull(mInjector.mDisplayListener);
@@ -272,16 +272,15 @@
         mInjector.mColorSamplingEnabled = false;
         mInjector.mDisplayListener = null;
         // Duplicate notification
-        mInjector.setBrightnessMode(/*isBrightnessModeAutomatic*/ true);
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
         // Sensor shouldn't have been registered as it was already registered.
         assertNull(mInjector.mSensorListener);
         assertFalse(mInjector.mColorSamplingEnabled);
         assertNull(mInjector.mDisplayListener);
-        mInjector.mSensorListener = listener;
         mInjector.mDisplayListener = displayListener;
         mInjector.mColorSamplingEnabled = true;
 
-        mInjector.setBrightnessMode(/*isBrightnessModeAutomatic*/ false);
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
         assertNull(mInjector.mSensorListener);
         assertFalse(mInjector.mColorSamplingEnabled);
         assertNull(mInjector.mDisplayListener);
@@ -301,19 +300,21 @@
         final String displayId = "1234";
 
         startTracker(mTracker);
-        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
+        final long sensorTime = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
         mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-        notifyBrightnessChanged(mTracker, brightness, displayId);
+        final long currentTime = mInjector.currentTimeMillis();
+        notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1.0f},
+                new long[] {sensorTime});
         List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
         mTracker.stop();
 
         assertEquals(1, events.size());
         BrightnessChangeEvent event = events.get(0);
-        assertEquals(mInjector.currentTimeMillis(), event.timeStamp);
+        assertEquals(currentTime, event.timeStamp);
         assertEquals(displayId, event.uniqueDisplayId);
         assertEquals(1, event.luxValues.length);
         assertEquals(1.0f, event.luxValues[0], FLOAT_DELTA);
-        assertEquals(mInjector.currentTimeMillis() - TimeUnit.SECONDS.toMillis(2),
+        assertEquals(currentTime - TimeUnit.SECONDS.toMillis(2),
                 event.luxTimestamps[0]);
         assertEquals(brightness, event.brightness, FLOAT_DELTA);
         assertEquals(DEFAULT_INITIAL_BRIGHTNESS, event.lastBrightness, FLOAT_DELTA);
@@ -339,9 +340,9 @@
         startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED);
         mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
                 batteryChangeEvent(30, 60));
-        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
-        final long sensorTime = mInjector.currentTimeMillis();
-        notifyBrightnessChanged(mTracker, brightness, displayId);
+        final long currentTime = mInjector.currentTimeMillis();
+        notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1000.0f},
+                new long[] {TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos())});
         List<BrightnessChangeEvent> eventsNoPackage
                 = mTracker.getEvents(0, false).getList();
         List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
@@ -349,10 +350,10 @@
 
         assertEquals(1, events.size());
         BrightnessChangeEvent event = events.get(0);
-        assertEquals(event.timeStamp, mInjector.currentTimeMillis());
+        assertEquals(event.timeStamp, currentTime);
         assertEquals(displayId, event.uniqueDisplayId);
-        assertArrayEquals(new float[] {1000.0f}, event.luxValues, 0.01f);
-        assertArrayEquals(new long[] {sensorTime}, event.luxTimestamps);
+        assertArrayEquals(new float[] {1000.0f}, event.luxValues, FLOAT_DELTA);
+        assertArrayEquals(new long[] {currentTime}, event.luxTimestamps);
         assertEquals(brightness, event.brightness, FLOAT_DELTA);
         assertEquals(initialBrightness, event.lastBrightness, FLOAT_DELTA);
         assertEquals(0.5, event.batteryLevel, FLOAT_DELTA);
@@ -374,13 +375,12 @@
     public void testIgnoreAutomaticBrightnessChange() {
         final int initialBrightness = 30;
         startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED);
-        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
         mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
 
         final int systemUpdatedBrightness = 20;
-        notifyBrightnessChanged(mTracker, systemUpdatedBrightness, false /*userInitiated*/,
-                0.5f /*powerBrightnessFactor(*/, false /*isUserSetBrightness*/,
-                false /*isDefaultBrightnessConfig*/, DEFAULT_DISPLAY_ID);
+        notifyBrightnessChanged(mTracker, systemUpdatedBrightness, /* userInitiated= */ false,
+                /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ false,
+                /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID);
         List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
         // No events because we filtered out our change.
         assertEquals(0, events.size());
@@ -408,10 +408,8 @@
     @Test
     public void testLimitedBufferSize() {
         startTracker(mTracker);
-        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
 
         for (int brightness = 0; brightness <= 255; ++brightness) {
-            mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
             mInjector.incrementTime(TimeUnit.SECONDS.toNanos(1));
             notifyBrightnessChanged(mTracker, brightness);
         }
@@ -427,33 +425,6 @@
     }
 
     @Test
-    public void testLimitedSensorEvents() {
-        final int brightness = 20;
-
-        startTracker(mTracker);
-        // 20 Sensor events 1 second apart.
-        for (int i = 0; i < 20; ++i) {
-            mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
-            mInjector.mSensorListener.onSensorChanged(createSensorEvent(i + 1.0f));
-        }
-        notifyBrightnessChanged(mTracker, 20);
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        mTracker.stop();
-
-        assertEquals(1, events.size());
-        BrightnessChangeEvent event = events.get(0);
-        assertEquals(mInjector.currentTimeMillis(), event.timeStamp);
-
-        // 12 sensor events, 11 for 0->10 seconds + 1 previous event.
-        assertEquals(12, event.luxValues.length);
-        for (int i = 0; i < 12; ++i) {
-            assertEquals(event.luxTimestamps[11 - i],
-                    mInjector.currentTimeMillis() - i * TimeUnit.SECONDS.toMillis(1));
-        }
-        assertEquals(brightness, event.brightness, FLOAT_DELTA);
-    }
-
-    @Test
     public void testReadEvents() throws Exception {
         BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
                 mInjector);
@@ -607,15 +578,16 @@
         startTracker(mTracker);
         mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
                 batteryChangeEvent(30, 100));
-        mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f));
-        final long firstSensorTime = mInjector.currentTimeMillis();
+        final long elapsedTime1 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+        final long currentTime1 = mInjector.currentTimeMillis();
         mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-        mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
-        final long secondSensorTime = mInjector.currentTimeMillis();
+        final long elapsedTime2 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+        final long currentTime2 = mInjector.currentTimeMillis();
         mInjector.incrementTime(TimeUnit.SECONDS.toMillis(3));
-        notifyBrightnessChanged(mTracker, brightness, true /*userInitiated*/,
-                0.5f /*powerBrightnessFactor*/, true /*hasUserBrightnessPoints*/,
-                false /*isDefaultBrightnessConfig*/, displayId);
+        notifyBrightnessChanged(mTracker, brightness, /* userInitiated= */ true,
+                /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ true,
+                /* isDefaultBrightnessConfig= */ false, displayId, new float[] {2000.0f, 3000.0f},
+                new long[] {elapsedTime1, elapsedTime2});
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         mTracker.writeEventsLocked(baos);
         mTracker.stop();
@@ -631,7 +603,7 @@
         BrightnessChangeEvent event = events.get(0);
         assertEquals(displayId, event.uniqueDisplayId);
         assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
-        assertArrayEquals(new long[] {firstSensorTime, secondSensorTime}, event.luxTimestamps);
+        assertArrayEquals(new long[] {currentTime1, currentTime2}, event.luxTimestamps);
         assertEquals(brightness, event.brightness, FLOAT_DELTA);
         assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
         assertTrue(event.nightMode);
@@ -647,53 +619,6 @@
     }
 
     @Test
-    public void testWritePrunesOldEvents() throws Exception {
-        final int brightness = 20;
-
-        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
-        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
-
-        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
-        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
-
-        startTracker(mTracker);
-        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
-                batteryChangeEvent(30, 100));
-        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
-        mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f));
-        final long sensorTime = mInjector.currentTimeMillis();
-        notifyBrightnessChanged(mTracker, brightness);
-
-        // 31 days later
-        mInjector.incrementTime(TimeUnit.DAYS.toMillis(31));
-        mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
-        notifyBrightnessChanged(mTracker, brightness);
-        final long eventTime = mInjector.currentTimeMillis();
-
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        assertEquals(2, events.size());
-
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        mTracker.writeEventsLocked(baos);
-        events = mTracker.getEvents(0, true).getList();
-        mTracker.stop();
-
-        assertEquals(1, events.size());
-        BrightnessChangeEvent event = events.get(0);
-        assertEquals(eventTime, event.timeStamp);
-
-        // We will keep one of the old sensor events because we keep 1 event outside the window.
-        assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
-        assertArrayEquals(new long[] {sensorTime, eventTime}, event.luxTimestamps);
-        assertEquals(brightness, event.brightness, FLOAT_DELTA);
-        assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
-        assertTrue(event.nightMode);
-        assertTrue(event.reduceBrightColors);
-        assertEquals(3339, event.colorTemperature);
-    }
-
-    @Test
     public void testParcelUnParcel() {
         Parcel parcel = Parcel.obtain();
         BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder();
@@ -796,9 +721,10 @@
 
         // Send an event.
         long eventTime = mInjector.currentTimeMillis();
-        mTracker.notifyBrightnessChanged(brightness, true /*userInitiated*/,
-                1.0f /*powerBrightnessFactor*/, false /*isUserSetBrightness*/,
-                false /*isDefaultBrightnessConfig*/, DEFAULT_DISPLAY_ID);
+        mTracker.notifyBrightnessChanged(brightness, /* userInitiated= */ true,
+                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+                /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID, new float[10],
+                new long[10]);
 
         // Time passes before handler can run.
         mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
@@ -890,20 +816,33 @@
     public void testOnlyOneReceiverRegistered() {
         assertNull(mInjector.mLightSensor);
         assertNull(mInjector.mSensorListener);
+        assertNull(mInjector.mContentObserver);
+        assertNull(mInjector.mBroadcastReceiver);
+        assertFalse(mInjector.mIdleScheduled);
         startTracker(mTracker, 0.3f, false);
 
         assertNotNull(mInjector.mLightSensor);
         assertNotNull(mInjector.mSensorListener);
+        assertNotNull(mInjector.mContentObserver);
+        assertNotNull(mInjector.mBroadcastReceiver);
+        assertTrue(mInjector.mIdleScheduled);
         Sensor registeredLightSensor = mInjector.mLightSensor;
         SensorEventListener registeredSensorListener = mInjector.mSensorListener;
+        ContentObserver registeredContentObserver = mInjector.mContentObserver;
+        BroadcastReceiver registeredBroadcastReceiver = mInjector.mBroadcastReceiver;
 
         mTracker.start(0.3f);
         assertSame(registeredLightSensor, mInjector.mLightSensor);
         assertSame(registeredSensorListener, mInjector.mSensorListener);
+        assertSame(registeredContentObserver, mInjector.mContentObserver);
+        assertSame(registeredBroadcastReceiver, mInjector.mBroadcastReceiver);
 
         mTracker.stop();
         assertNull(mInjector.mLightSensor);
         assertNull(mInjector.mSensorListener);
+        assertNull(mInjector.mContentObserver);
+        assertNull(mInjector.mBroadcastReceiver);
+        assertFalse(mInjector.mIdleScheduled);
 
         // mInjector asserts that we aren't removing a null receiver
         mTracker.stop();
@@ -954,23 +893,41 @@
 
     private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
             String displayId) {
-        notifyBrightnessChanged(tracker, brightness, true /*userInitiated*/,
-                1.0f /*powerBrightnessFactor*/, false /*isUserSetBrightness*/,
-                false /*isDefaultBrightnessConfig*/, displayId);
+        notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true,
+                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+                /* isDefaultBrightnessConfig= */ false, displayId, new float[10], new long[10]);
+    }
+
+    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+            String displayId, float[] luxValues, long[] luxTimestamps) {
+        notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true,
+                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+                /* isDefaultBrightnessConfig= */ false, displayId, luxValues, luxTimestamps);
     }
 
     private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
             boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
             boolean isDefaultBrightnessConfig, String displayId) {
         tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
-                isUserSetBrightness, isDefaultBrightnessConfig, displayId);
+                isUserSetBrightness, isDefaultBrightnessConfig, displayId, new float[10],
+                new long[10]);
+        mInjector.waitForHandler();
+    }
+
+    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+            boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
+            boolean isDefaultBrightnessConfig, String displayId, float[] luxValues,
+            long[] luxTimestamps) {
+        tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
+                isUserSetBrightness, isDefaultBrightnessConfig, displayId, luxValues,
+                luxTimestamps);
         mInjector.waitForHandler();
     }
 
     private BrightnessConfiguration buildBrightnessConfiguration(boolean collectColorSamples) {
         BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(
-                /* lux = */ new float[] {0f, 10f, 100f},
-                /* nits = */ new float[] {1f, 90f, 100f});
+                /* lux= */ new float[] {0f, 10f, 100f},
+                /* nits= */ new float[] {1f, 90f, 100f});
         builder.setShouldCollectColorSamples(collectColorSamples);
         return builder.build();
     }
diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
new file mode 100644
index 0000000..bcae50e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.display;
+
+
+import static org.junit.Assert.assertEquals;
+
+import android.view.DisplayAddress;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.layout.DisplayIdProducer;
+import com.android.server.display.layout.Layout;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+
+@SmallTest
+public class DeviceStateToLayoutMapTest {
+    private DeviceStateToLayoutMap mDeviceStateToLayoutMap;
+
+    @Mock DisplayIdProducer mDisplayIdProducerMock;
+
+    @Before
+    public void setUp() throws IOException {
+        MockitoAnnotations.initMocks(this);
+
+        Mockito.when(mDisplayIdProducerMock.getId(false)).thenReturn(1);
+
+        setupDeviceStateToLayoutMap();
+    }
+
+    //////////////////
+    // Test Methods //
+    //////////////////
+
+    @Test
+    public void testInitialState() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(0);
+
+        Layout testLayout = new Layout();
+        testLayout.createDisplayLocked(
+                DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ true,
+                /* isEnabled= */ true, mDisplayIdProducerMock);
+        testLayout.createDisplayLocked(
+                DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ false,
+                /* isEnabled= */ false, mDisplayIdProducerMock);
+        assertEquals(testLayout, configLayout);
+    }
+
+    @Test
+    public void testSwitchedState() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(1);
+
+        Layout testLayout = new Layout();
+        testLayout.createDisplayLocked(
+                DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ true,
+                /* isEnabled= */ true, mDisplayIdProducerMock);
+        testLayout.createDisplayLocked(
+                DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ false,
+                /* isEnabled= */ false, mDisplayIdProducerMock);
+
+        assertEquals(testLayout, configLayout);
+    }
+
+    @Test
+    public void testConcurrentState() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(2);
+
+        Layout testLayout = new Layout();
+        testLayout.createDisplayLocked(
+                DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true,
+                /* isEnabled= */ true, mDisplayIdProducerMock);
+        testLayout.createDisplayLocked(
+                DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false,
+                /* isEnabled= */ true, mDisplayIdProducerMock);
+
+        assertEquals(testLayout, configLayout);
+    }
+
+    ////////////////////
+    // Helper Methods //
+    ////////////////////
+
+    private void setupDeviceStateToLayoutMap() throws IOException {
+        Path tempFile = Files.createTempFile("device_state_layout_map", ".tmp");
+        Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+        mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(mDisplayIdProducerMock,
+                tempFile.toFile());
+    }
+
+    private String getContent() {
+        return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                +  "<layouts>\n"
+                +    "<layout>\n"
+                +      "<state>0</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+                +        "<address>123456</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"false\">\n"
+                +        "<address>78910</address>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+
+                +    "<layout>\n"
+                +      "<state>1</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+                +        "<address>78910</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"false\">\n"
+                +        "<address>123456</address>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+
+                +    "<layout>\n"
+                +      "<state>2</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+                +        "<address>345</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"true\">\n"
+                +        "<address>678</address>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+                +  "</layouts>\n";
+    }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 062bde8..ca8e45a 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -16,7 +16,9 @@
 
 package com.android.server.display;
 
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
 import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
 
@@ -171,22 +173,6 @@
 
     private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
 
-    private final DisplayManagerService.Injector mAllowNonNativeRefreshRateOverrideInjector =
-            new BasicInjector() {
-                @Override
-                boolean getAllowNonNativeRefreshRateOverride() {
-                    return true;
-                }
-            };
-
-    private final DisplayManagerService.Injector mDenyNonNativeRefreshRateOverrideInjector =
-            new BasicInjector() {
-                @Override
-                boolean getAllowNonNativeRefreshRateOverride() {
-                    return false;
-                }
-            };
-
     @Mock InputManagerInternal mMockInputManagerInternal;
     @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
     @Mock IVirtualDisplayCallback.Stub mMockAppToken;
@@ -235,6 +221,7 @@
         SurfaceControl.DisplayMode displayMode = new SurfaceControl.DisplayMode();
         displayMode.width = 100;
         displayMode.height = 200;
+        displayMode.supportedHdrTypes = new int[]{1, 2};
         dynamicDisplayMode.supportedDisplayModes = new SurfaceControl.DisplayMode[] {displayMode};
         when(mSurfaceControlProxy.getDynamicDisplayInfo(mMockDisplayToken))
                 .thenReturn(dynamicDisplayMode);
@@ -335,7 +322,7 @@
 
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
 
-        final int displayIds[] = bs.getDisplayIds();
+        final int[] displayIds = bs.getDisplayIds(/* includeDisabled= */ true);
         final int size = displayIds.length;
         assertTrue(size > 0);
 
@@ -408,6 +395,75 @@
         assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0);
     }
 
+    @Test
+    public void testCreateVirtualDisplayOwnFocus() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Own Focus Test";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
+                | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+                PackageManager.PERMISSION_GRANTED);
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setFlags(flags);
+        builder.setUniqueId(uniqueId);
+        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+                /* projection= */ null, PACKAGE_NAME);
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0);
+    }
+
+    @Test
+    public void testCreateVirtualDisplayOwnFocus_nonTrustedDisplay() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Own Focus Test -- nonTrustedDisplay";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setFlags(flags);
+        builder.setUniqueId(uniqueId);
+        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+                /* projection= */ null, PACKAGE_NAME);
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) == 0);
+    }
+
     /**
      * Tests that the virtual display is created along-side the default display.
      */
@@ -670,9 +726,6 @@
         registerDefaultDisplays(displayManager);
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
 
-        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
-                .thenReturn(PackageManager.PERMISSION_DENIED);
-
         IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
         when(mMockVirtualDeviceManagerInternal.isValidVirtualDevice(virtualDevice))
                 .thenReturn(true);
@@ -726,9 +779,6 @@
         registerDefaultDisplays(displayManager);
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
 
-        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
-                .thenReturn(PackageManager.PERMISSION_DENIED);
-
         IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
         when(mMockVirtualDeviceManagerInternal.isValidVirtualDevice(virtualDevice))
                 .thenReturn(true);
@@ -738,7 +788,7 @@
         // virtual device.
         final VirtualDisplayConfig.Builder builder1 =
                 new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setUniqueId("uniqueId --- device display group 1");
+                        .setUniqueId("uniqueId --- device display group");
 
         int displayId1 =
                 localService.createVirtualDisplay(
@@ -754,7 +804,7 @@
         final VirtualDisplayConfig.Builder builder2 =
                 new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
                         .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
-                        .setUniqueId("uniqueId --- device display group 1");
+                        .setUniqueId("uniqueId --- own display group");
 
         int displayId2 =
                 localService.createVirtualDisplay(
@@ -773,6 +823,99 @@
     }
 
     @Test
+    public void displaysInDeviceOrOwnDisplayGroupShouldPreserveAlwaysUnlockedFlag()
+            throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+
+        registerDefaultDisplays(displayManager);
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+        when(mMockVirtualDeviceManagerInternal.isValidVirtualDevice(virtualDevice))
+                .thenReturn(true);
+        when(virtualDevice.getDeviceId()).thenReturn(1);
+
+        // Allow an ALWAYS_UNLOCKED display to be created.
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        // Create a virtual display in a device display group.
+        final VirtualDisplayConfig deviceDisplayGroupDisplayConfig =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- device display group 1")
+                        .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
+                        .build();
+
+        int deviceDisplayGroupDisplayId =
+                localService.createVirtualDisplay(
+                        deviceDisplayGroupDisplayConfig,
+                        mMockAppToken /* callback */,
+                        virtualDevice /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+
+        // Check that FLAG_ALWAYS_UNLOCKED is set.
+        assertNotEquals(
+                "FLAG_ALWAYS_UNLOCKED should be set for displays created in a device display"
+                        + " group.",
+                (displayManager.getDisplayDeviceInfoInternal(deviceDisplayGroupDisplayId).flags
+                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+                0);
+
+        // Create a virtual display in its own display group.
+        final VirtualDisplayConfig ownDisplayGroupConfig =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- own display group 1")
+                        .setFlags(
+                                VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
+                                        | VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
+                        .build();
+
+        int ownDisplayGroupDisplayId =
+                localService.createVirtualDisplay(
+                        ownDisplayGroupConfig,
+                        mMockAppToken /* callback */,
+                        virtualDevice /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+
+        // Check that FLAG_ALWAYS_UNLOCKED is set.
+        assertNotEquals(
+                "FLAG_ALWAYS_UNLOCKED should be set for displays created in their own display"
+                        + " group.",
+                (displayManager.getDisplayDeviceInfoInternal(ownDisplayGroupDisplayId).flags
+                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+                0);
+
+        // Create a virtual display in a device display group.
+        final VirtualDisplayConfig defaultDisplayGroupConfig =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- default display group 1")
+                        .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
+                        .build();
+
+        int defaultDisplayGroupDisplayId =
+                localService.createVirtualDisplay(
+                        defaultDisplayGroupConfig,
+                        mMockAppToken /* callback */,
+                        null /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+
+        // Check that FLAG_ALWAYS_UNLOCKED is not set.
+        assertEquals(
+                "FLAG_ALWAYS_UNLOCKED should not be set for displays created in the default"
+                        + " display group.",
+                (displayManager.getDisplayDeviceInfoInternal(defaultDisplayGroupDisplayId).flags
+                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+                0);
+    }
+
+    @Test
     public void testGetDisplayIdToMirror() throws Exception {
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         registerDefaultDisplays(displayManager);
@@ -1044,13 +1187,32 @@
     }
 
     /**
-     * Tests that the frame rate override is updated accordingly to the
-     * allowNonNativeRefreshRateOverride policy.
+     * Tests that the frame rate override is returning the correct value from
+     * DisplayInfo#getRefreshRate
      */
     @Test
     public void testDisplayInfoNonNativeFrameRateOverride() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverride(mDenyNonNativeRefreshRateOverrideInjector);
-        testDisplayInfoNonNativeFrameRateOverride(mAllowNonNativeRefreshRateOverrideInjector);
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid(), 20f)
+                });
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
     }
 
     /**
@@ -1078,10 +1240,7 @@
     @Test
     @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
     public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/ false);
-        testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  false);
+        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ false);
     }
 
     /**
@@ -1090,10 +1249,77 @@
     @Test
     @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
     public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  true);
-        testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  true);
+        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ true);
+    }
+
+    /**
+     * Tests that there is a display change notification if the render frame rate is updated
+     */
+    @Test
+    public void testShouldNotifyChangeWhenDisplayInfoRenderFrameRateChanged() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+                displayManagerBinderService, displayDevice);
+
+        updateRenderFrameRate(displayManager, displayDevice, 30f);
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+
+        updateRenderFrameRate(displayManager, displayDevice, 30f);
+        assertFalse(callback.mDisplayChangedCalled);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+    }
+
+    /**
+     * Tests that the DisplayInfo is updated correctly with a render frame rate
+     */
+    @Test
+    public void testDisplayInfoRenderFrameRate() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+    }
+
+    /**
+     * Tests that the mode reflects the render frame rate is in compat mode
+     */
+    @Test
+    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public  void testDisplayInfoRenderFrameRateModeCompat() throws Exception {
+        testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ false);
+    }
+
+    /**
+     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
+     */
+    @Test
+    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public  void testDisplayInfoRenderFrameRateMode() throws Exception {
+        testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ true);
     }
 
     /**
@@ -1316,10 +1542,37 @@
         assertEquals(expectedMode, displayInfo.getMode());
     }
 
-    private void testDisplayInfoNonNativeFrameRateOverrideMode(
-            DisplayManagerService.Injector injector, boolean compatChangeEnabled) {
+    private void testDisplayInfoRenderFrameRateModeCompat(boolean compatChangeEnabled)
+            throws Exception {
         DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, injector);
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+        Display.Mode expectedMode;
+        if (compatChangeEnabled) {
+            expectedMode = new Display.Mode(1, 100, 200, 60f);
+        } else {
+            expectedMode = new Display.Mode(3, 100, 200, 20f);
+        }
+        assertEquals(expectedMode, displayInfo.getMode());
+    }
+
+    private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
                 displayManager.new BinderService();
         registerDefaultDisplays(displayManager);
@@ -1341,46 +1594,19 @@
         Display.Mode expectedMode;
         if (compatChangeEnabled) {
             expectedMode = new Display.Mode(1, 100, 200, 60f);
-        } else if (injector.getAllowNonNativeRefreshRateOverride()) {
-            expectedMode = new Display.Mode(255, 100, 200, 20f);
         } else {
-            expectedMode = new Display.Mode(1, 100, 200, 60f);
+            expectedMode = new Display.Mode(255, 100, 200, 20f);
         }
         assertEquals(expectedMode, displayInfo.getMode());
     }
 
-    private void testDisplayInfoNonNativeFrameRateOverride(
-            DisplayManagerService.Injector injector) {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, injector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid(), 20f)
-                });
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        float expectedRefreshRate = injector.getAllowNonNativeRefreshRateOverride() ? 20f : 60f;
-        assertEquals(expectedRefreshRate, displayInfo.getRefreshRate(), 0.01f);
-    }
-
     private int getDisplayIdForDisplayDevice(
             DisplayManagerService displayManager,
             DisplayManagerService.BinderService displayManagerBinderService,
             FakeDisplayDevice displayDevice) {
 
-        final int[] displayIds = displayManagerBinderService.getDisplayIds();
+        final int[] displayIds = displayManagerBinderService.getDisplayIds(
+                /* includeDisabled= */ true);
         assertTrue(displayIds.length > 0);
         int displayId = Display.INVALID_DISPLAY;
         for (int i = 0; i < displayIds.length; i++) {
@@ -1413,6 +1639,15 @@
         updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
     }
 
+    private void updateRenderFrameRate(DisplayManagerService displayManager,
+            FakeDisplayDevice displayDevice,
+            float renderFrameRate) {
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
+        displayDeviceInfo.renderFrameRate = renderFrameRate;
+        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
+    }
+
     private void updateModeId(DisplayManagerService displayManager,
             FakeDisplayDevice displayDevice,
             int modeId) {
@@ -1452,6 +1687,7 @@
                     new Display.Mode(i + 1, width, height, refreshRates[i]);
         }
         displayDeviceInfo.modeId = 1;
+        displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate();
         displayDeviceInfo.width = width;
         displayDeviceInfo.height = height;
         final Rect zeroRect = new Rect();
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..865bc98 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -21,6 +21,7 @@
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
@@ -31,6 +32,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -48,6 +51,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
 import android.hardware.SensorEventListener;
@@ -57,7 +61,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;
@@ -76,6 +80,7 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.test.FakeSettingsProvider;
@@ -195,12 +200,7 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testDisplayModeVoting(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testDisplayModeVoting() {
         // With no votes present, DisplayModeDirector should allow any refresh rate.
         DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
         DesiredDisplayModeSpecs modeSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
@@ -237,12 +237,7 @@
                         .isEqualTo((float) (minFps + i));
                 assertThat(modeSpecs.primary.physical.max)
                         .isEqualTo((float) (maxFps - i));
-                if (frameRateIsRefreshRate) {
-                    assertThat(modeSpecs.primary.render.min)
-                            .isEqualTo((float) (minFps + i));
-                } else {
-                    assertThat(modeSpecs.primary.render.min).isZero();
-                }
+                assertThat(modeSpecs.primary.render.min).isZero();
                 assertThat(modeSpecs.primary.render.max)
                         .isEqualTo((float) (maxFps - i));
                 if (priority >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF) {
@@ -250,12 +245,7 @@
                             .isEqualTo((float) (minFps + i));
                     assertThat(modeSpecs.appRequest.physical.max)
                             .isEqualTo((float) (maxFps - i));
-                    if (frameRateIsRefreshRate) {
-                        assertThat(modeSpecs.appRequest.render.min).isEqualTo(
-                                (float) (minFps + i));
-                    } else {
-                        assertThat(modeSpecs.appRequest.render.min).isZero();
-                    }
+                    assertThat(modeSpecs.appRequest.render.min).isZero();
                     assertThat(modeSpecs.appRequest.render.max).isEqualTo(
                             (float) (maxFps - i));
                 } else {
@@ -287,12 +277,7 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testVotingWithFloatingPointErrors(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testVotingWithFloatingPointErrors() {
         DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
         SparseArray<Vote> votes = new SparseArray<>();
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -313,12 +298,7 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testFlickerHasLowerPriorityThanUserAndRangeIsSingle(
-            boolean frameRateIsRefreshRate) {
+    public void testFlickerHasLowerPriorityThanUserAndRangeIsSingle() {
         assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE
                 < Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
         assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE
@@ -327,7 +307,6 @@
         assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH
                 > Vote.PRIORITY_LOW_POWER_MODE);
 
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
         Display.Mode[] modes = new Display.Mode[4];
         modes[0] = new Display.Mode(
                 /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
@@ -403,17 +382,12 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testLPMHasHigherPriorityThanUser(boolean frameRateIsRefreshRate) {
+    public void testLPMHasHigherPriorityThanUser() {
         assertTrue(Vote.PRIORITY_LOW_POWER_MODE
                 > Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
         assertTrue(Vote.PRIORITY_LOW_POWER_MODE
                 > Vote.PRIORITY_APP_REQUEST_SIZE);
 
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
         Display.Mode[] modes = new Display.Mode[4];
         modes[0] = new Display.Mode(
                 /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
@@ -438,11 +412,7 @@
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.baseModeId).isEqualTo(2);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-        } else {
-            assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        }
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
         assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
 
@@ -457,11 +427,7 @@
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.baseModeId).isEqualTo(4);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
-        } else {
-            assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        }
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
         assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90);
         assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90);
 
@@ -476,11 +442,7 @@
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.baseModeId).isEqualTo(2);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-        } else {
-            assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        }
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
         assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
 
@@ -495,21 +457,13 @@
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.baseModeId).isEqualTo(4);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
-        } else {
-            assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        }
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
         assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90);
         assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90);
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testAppRequestRefreshRateRange(boolean frameRateIsRefreshRate) {
+    public void testAppRequestRefreshRateRange() {
         // Confirm that the app request range doesn't include flicker or min refresh rate settings,
         // but does include everything else.
         assertTrue(
@@ -520,7 +474,6 @@
         assertTrue(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE
                 >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
 
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
         Display.Mode[] modes = new Display.Mode[3];
         modes[0] = new Display.Mode(
                 /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
@@ -577,12 +530,7 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testSpecsFromRefreshRateSettings(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testSpecsFromRefreshRateSettings() {
         // Confirm that, with varying settings for min, peak, and default refresh rate,
         // DesiredDisplayModeSpecs is calculated correctly.
         float[] refreshRates = {30.f, 60.f, 90.f, 120.f, 150.f};
@@ -602,27 +550,12 @@
 
         RefreshRateRanges frameRateAll = new RefreshRateRanges(rangeAll, rangeAll);
         RefreshRateRanges frameRate90toInf = new RefreshRateRanges(range90toInf, range90toInf);
-        RefreshRateRanges frameRate0to60;
-        RefreshRateRanges frameRate0to90;
-        RefreshRateRanges frameRate0to120;
-        RefreshRateRanges frameRate60to90;
-        RefreshRateRanges frameRate90to90;
-        RefreshRateRanges frameRate90to120;
-        if (frameRateIsRefreshRate) {
-            frameRate0to60 = new RefreshRateRanges(range0to60, range0to60);
-            frameRate0to90 = new RefreshRateRanges(range0to90, range0to90);
-            frameRate0to120 = new RefreshRateRanges(range0to120, range0to120);
-            frameRate60to90 = new RefreshRateRanges(range60to90, range60to90);
-            frameRate90to90 = new RefreshRateRanges(range90to90, range90to90);
-            frameRate90to120 = new RefreshRateRanges(range90to120, range90to120);
-        } else {
-            frameRate0to60 = new RefreshRateRanges(rangeAll, range0to60);
-            frameRate0to90 = new RefreshRateRanges(rangeAll, range0to90);
-            frameRate0to120 = new RefreshRateRanges(rangeAll, range0to120);
-            frameRate60to90 = new RefreshRateRanges(range60toInf, range60to90);
-            frameRate90to90 = new RefreshRateRanges(range90toInf, range90to90);
-            frameRate90to120 = new RefreshRateRanges(range90toInf, range90to120);
-        }
+        RefreshRateRanges frameRate0to60 = new RefreshRateRanges(rangeAll, range0to60);
+        RefreshRateRanges frameRate0to90 = new RefreshRateRanges(rangeAll, range0to90);
+        RefreshRateRanges frameRate0to120 = new RefreshRateRanges(rangeAll, range0to120);
+        RefreshRateRanges frameRate60to90 = new RefreshRateRanges(range60toInf, range60to90);
+        RefreshRateRanges frameRate90to90 = new RefreshRateRanges(range90toInf, range90to90);
+        RefreshRateRanges frameRate90to120 = new RefreshRateRanges(range90toInf, range90to120);
 
         verifySpecsWithRefreshRateSettings(director, 0, 0, 0, frameRateAll, frameRateAll);
         verifySpecsWithRefreshRateSettings(director, 0, 0, 90, frameRate0to90, frameRateAll);
@@ -652,12 +585,7 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testBrightnessObserverCallWithRefreshRateSettings(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testBrightnessObserverCallWithRefreshRateSettings() {
         // Confirm that, with varying settings for min, peak, and default refresh rate, we make the
         // correct call to the brightness observer.
         float[] refreshRates = {60.f, 90.f, 120.f};
@@ -672,12 +600,7 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testVotingWithAlwaysRespectAppRequest(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testVotingWithAlwaysRespectAppRequest() {
         Display.Mode[] modes = new Display.Mode[3];
         modes[0] = new Display.Mode(
                 /*modeId=*/50, /*width=*/1000, /*height=*/1000, 50);
@@ -706,11 +629,7 @@
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
 
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-        } else {
-            assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        }
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
         assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.baseModeId).isEqualTo(60);
@@ -729,23 +648,14 @@
 
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-        } else {
-            assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        }
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
         assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.baseModeId).isEqualTo(60);
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testVotingWithSwitchingTypeNone(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testVotingWithSwitchingTypeNone() {
         DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
         SparseArray<Vote> votes = new SparseArray<>();
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -760,20 +670,11 @@
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
 
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-        } else {
-            assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        }
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
         assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
         assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(
-                    60);
-        } else {
-            assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
-        }
+        assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
         assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
         assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.baseModeId).isEqualTo(30);
@@ -795,6 +696,90 @@
     }
 
     @Test
+    public void testVotingWithSwitchingTypeRenderFrameRateOnly() {
+        DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(30, 90));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+
+        director.injectVotesByDisplay(votesByDisplay);
+        assertThat(director.getModeSwitchingType())
+                .isNotEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(30);
+
+        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        assertThat(director.getModeSwitchingType())
+                .isEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(30);
+
+        assertThat(desiredSpecs.baseModeId).isEqualTo(30);
+    }
+
+    @Test
+    public void testVotingWithSwitchingTypeRenderFrameRateOnlyRenderRateIsNotPhysicalRefreshRate() {
+        DisplayModeDirector director = createDirectorFromFpsRange(90, 120);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(30, 90));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+
+        director.injectVotesByDisplay(votesByDisplay);
+        assertThat(director.getModeSwitchingType())
+                .isNotEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
+
+        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        assertThat(director.getModeSwitchingType())
+                .isEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+
+        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
+    }
+
+    @Test
     public void testVotingWithSwitchingTypeWithinGroups() {
         DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
 
@@ -817,12 +802,7 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testDefaultDisplayModeIsSelectedIfAvailable(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testDefaultDisplayModeIsSelectedIfAvailable() {
         final float[] refreshRates = new float[]{24f, 25f, 30f, 60f, 90f};
         final int defaultModeId = 3;
         DisplayModeDirector director = createDirectorFromRefreshRateArray(
@@ -1081,10 +1061,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 +1073,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);
@@ -1104,17 +1083,12 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testAppRequestMinRefreshRate(boolean frameRateIsRefreshRate) {
+    public void testAppRequestMinRefreshRate() {
         // Confirm that the app min request range doesn't include flicker or min refresh rate
         // settings but does include everything else.
         assertTrue(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE
                 >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
 
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
         Display.Mode[] modes = new Display.Mode[3];
         modes[0] = new Display.Mode(
                 /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
@@ -1156,11 +1130,7 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testAppRequestMaxRefreshRate(boolean frameRateIsRefreshRate) {
+    public void testAppRequestMaxRefreshRate() {
         // Confirm that the app max request range doesn't include flicker or min refresh rate
         // settings but does include everything else.
         assertTrue(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE
@@ -1174,7 +1144,6 @@
         modes[2] = new Display.Mode(
                 /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90);
 
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
         DisplayModeDirector director = createDirectorFromModeArray(modes, modes[1]);
         SparseArray<Vote> votes = new SparseArray<>();
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -1185,19 +1154,11 @@
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
-        } else {
-            assertThat(desiredSpecs.primary.render.min).isZero();
-        }
+        assertThat(desiredSpecs.primary.render.min).isZero();
         assertThat(desiredSpecs.primary.render.max).isAtMost(60);
         assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
         assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.appRequest.render.min).isAtMost(60f);
-        } else {
-            assertThat(desiredSpecs.appRequest.render.min).isZero();
-        }
+        assertThat(desiredSpecs.appRequest.render.min).isZero();
         assertThat(desiredSpecs.appRequest.render.max).isAtLeast(90f);
 
         votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
@@ -1219,30 +1180,16 @@
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(75);
         assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(75);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(75);
-        } else {
-            assertThat(desiredSpecs.primary.render.min).isZero();
-        }
+        assertThat(desiredSpecs.primary.render.min).isZero();
         assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(75);
         assertThat(desiredSpecs.appRequest.physical.min).isZero();
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(
-                    75);
-        } else {
-            assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
-        }
+        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
         assertThat(desiredSpecs.appRequest.render.min).isZero();
         assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(75);
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testAppRequestObserver_modeId(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testAppRequestObserver_modeId() {
         DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
         director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 0, 0);
 
@@ -1304,12 +1251,7 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testAppRequestObserver_minRefreshRate(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testAppRequestObserver_minRefreshRate() {
         DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
         director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 60, 0);
         Vote appRequestRefreshRate =
@@ -1322,15 +1264,9 @@
         Vote appRequestRefreshRateRange =
                 director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
         assertNotNull(appRequestRefreshRateRange);
-        if (frameRateIsRefreshRate) {
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min)
-                    .isWithin(FLOAT_TOLERANCE).of(60);
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max).isAtLeast(90);
-        } else {
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                    .isPositiveInfinity();
-        }
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+                .isPositiveInfinity();
         assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
                 .isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max).isAtLeast(90);
@@ -1348,15 +1284,9 @@
         appRequestRefreshRateRange =
                 director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
         assertNotNull(appRequestRefreshRateRange);
-        if (frameRateIsRefreshRate) {
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isWithin(
-                    FLOAT_TOLERANCE).of(90);
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max).isAtLeast(90);
-        } else {
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                    .isPositiveInfinity();
-        }
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+                .isPositiveInfinity();
 
         assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
                 .isWithin(FLOAT_TOLERANCE).of(90);
@@ -1366,12 +1296,7 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testAppRequestObserver_maxRefreshRate(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testAppRequestObserver_maxRefreshRate() {
         DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
         director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 90);
         Vote appRequestRefreshRate =
@@ -1385,13 +1310,8 @@
                 director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
         assertNotNull(appRequestRefreshRateRange);
         assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-        if (frameRateIsRefreshRate) {
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                    .isWithin(FLOAT_TOLERANCE).of(90);
-        } else {
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                    .isPositiveInfinity();
-        }
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+                .isPositiveInfinity();
 
         assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min).isZero();
         assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
@@ -1411,13 +1331,8 @@
                 director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
         assertNotNull(appRequestRefreshRateRange);
         assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-        if (frameRateIsRefreshRate) {
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                    .isWithin(FLOAT_TOLERANCE).of(60);
-        } else {
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                    .isPositiveInfinity();
-        }
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+                .isPositiveInfinity();
 
         assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min).isZero();
         assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
@@ -1443,12 +1358,7 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testAppRequestObserver_modeIdAndRefreshRateRange(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testAppRequestObserver_modeIdAndRefreshRateRange() {
         DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
         director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 90, 90);
 
@@ -1478,16 +1388,9 @@
         Vote appRequestRefreshRateRange =
                 director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
         assertNotNull(appRequestRefreshRateRange);
-        if (frameRateIsRefreshRate) {
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min)
-                    .isWithin(FLOAT_TOLERANCE).of(90);
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                    .isWithin(FLOAT_TOLERANCE).of(90);
-        } else {
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-            assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                    .isPositiveInfinity();
-        }
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+                .isPositiveInfinity();
         assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
                 .isWithin(FLOAT_TOLERANCE).of(90);
         assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
@@ -1497,12 +1400,7 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testAppRequestsIsTheDefaultMode(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testAppRequestsIsTheDefaultMode() {
         Display.Mode[] modes = new Display.Mode[2];
         modes[0] = new Display.Mode(
                 /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
@@ -1531,12 +1429,7 @@
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testDisableRefreshRateSwitchingVote(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testDisableRefreshRateSwitchingVote() {
         DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
         SparseArray<Vote> votes = new SparseArray<>();
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -1581,8 +1474,8 @@
             "true",
             "false"
     })
-    public void testBaseModeIdInPrimaryRange(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testBaseModeIdInPrimaryRange(boolean supportsFrameRateOverride) {
+        when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride);
         DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
         SparseArray<Vote> votes = new SparseArray<>();
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -1593,12 +1486,12 @@
         director.injectVotesByDisplay(votesByDisplay);
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-            assertThat(desiredSpecs.baseModeId).isEqualTo(50);
-        } else {
-            assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        if (supportsFrameRateOverride) {
             assertThat(desiredSpecs.baseModeId).isEqualTo(70);
+        } else {
+            assertThat(desiredSpecs.baseModeId).isEqualTo(50);
+
         }
         assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
         assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
@@ -1610,11 +1503,7 @@
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-        } else {
-            assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        }
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
         assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
         assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.baseModeId).isEqualTo(55);
@@ -1628,12 +1517,11 @@
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-            assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        } else {
-            assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        if (supportsFrameRateOverride) {
             assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(52);
+        } else {
+            assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
         }
         assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
         assertThat(desiredSpecs.baseModeId).isEqualTo(55);
@@ -1647,23 +1535,14 @@
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(58);
-        } else {
-            assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        }
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
         assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
         assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(58);
         assertThat(desiredSpecs.baseModeId).isEqualTo(55);
     }
 
     @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testStaleAppVote(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testStaleAppVote() {
         Display.Mode[] modes = new Display.Mode[4];
         modes[0] = new Display.Mode(
                 /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
@@ -1713,8 +1592,8 @@
             "true",
             "false"
     })
-    public void testRefreshRateIsSubsetOfFrameRate(boolean frameRateIsRefreshRate) {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+    public void testRefreshRateIsSubsetOfFrameRate(boolean supportsFrameRateOverride) {
+        when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride);
         DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
         SparseArray<Vote> votes = new SparseArray<>();
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -1726,11 +1605,7 @@
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
         assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(90);
-        } else {
-            assertThat(desiredSpecs.appRequest.render.min).isZero();
-        }
+        assertThat(desiredSpecs.appRequest.render.min).isZero();
         assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
 
         votes.clear();
@@ -1741,13 +1616,11 @@
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
         assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(90);
-            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(
-                    120);
-        } else {
-            assertThat(desiredSpecs.appRequest.render.min).isZero();
+        assertThat(desiredSpecs.appRequest.render.min).isZero();
+        if (supportsFrameRateOverride) {
             assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        } else {
+            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
         }
 
         votes.clear();
@@ -1758,13 +1631,12 @@
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
         assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(90);
-            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(
-                    120);
-        } else {
+        if (supportsFrameRateOverride) {
             assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(60);
             assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        } else {
+            assertThat(desiredSpecs.appRequest.render.min).isZero();
+            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
         }
 
         votes.clear();
@@ -1775,17 +1647,12 @@
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
         assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
-        if (frameRateIsRefreshRate) {
-            assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(90);
-        } else {
-            assertThat(desiredSpecs.appRequest.render.min).isZero();
-        }
+        assertThat(desiredSpecs.appRequest.render.min).isZero();
         assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
     }
 
     @Test
     public void testRenderFrameRateIsAchievableByPhysicalRefreshRate() {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(false);
         DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
         SparseArray<Vote> votes = new SparseArray<>();
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -1803,8 +1670,34 @@
     }
 
     @Test
+    @Parameters({
+            "true",
+            "false"
+    })
+    public void testRenderFrameRateIncludesPhysicalRefreshRate(boolean supportsFrameRateOverride) {
+        when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride);
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(0, 30));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.appRequest.physical.min).isZero();
+        assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.appRequest.render.min).isZero();
+        if (supportsFrameRateOverride) {
+            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(30);
+        } else {
+            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        }
+    }
+
+    @Test
     public void testRenderFrameRateIsDroppedIfLowerPriorityThenBaseModeRefreshRate() {
-        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(false);
         DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
         SparseArray<Vote> votes = new SparseArray<>();
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -2321,16 +2214,83 @@
 
     @Test
     public void testNotifyDefaultDisplayDeviceUpdated() {
-        DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
-        when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{});
-        when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{});
-        when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{});
-        when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{});
+        Resources resources = mock(Resources.class);
+        when(mContext.getResources()).thenReturn(resources);
+        when(resources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
+            .thenReturn(75);
+        when(resources.getInteger(R.integer.config_defaultRefreshRate))
+            .thenReturn(45);
+        when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+            .thenReturn(new int[]{5});
+        when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+            .thenReturn(new int[]{10});
+        when(
+            resources.getIntArray(R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+            .thenReturn(new int[]{250});
+        when(
+            resources.getIntArray(R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+            .thenReturn(new int[]{7000});
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        // We don't expect any interaction with DeviceConfig when the director is initialized
+        // because we explicitly avoid doing this as this can lead to a latency spike in the
+        // startup of DisplayManagerService
+        // Verify all the loaded values are from DisplayDeviceConfig
+        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 45, 0.0);
+        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 75,
+                0.0);
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
+                new int[]{250});
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
+                new int[]{7000});
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
+                new int[]{5});
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
+                new int[]{10});
+
+        // Notify that the default display is updated, such that DisplayDeviceConfig has new values
+        DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
+        when(displayDeviceConfig.getDefaultRefreshRate()).thenReturn(50);
+        when(displayDeviceConfig.getDefaultPeakRefreshRate()).thenReturn(55);
+        when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
+        when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
+        when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
+        when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
         director.defaultDisplayDeviceUpdated(displayDeviceConfig);
-        verify(displayDeviceConfig).getDefaultRefreshRate();
-        verify(displayDeviceConfig).getDefaultPeakRefreshRate();
+
+        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0);
+        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 55,
+                0.0);
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
+                new int[]{210});
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
+                new int[]{2100});
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
+                new int[]{25});
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
+                new int[]{30});
+
+        // Notify that the default display is updated, such that DeviceConfig has new values
+        FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setDefaultPeakRefreshRate(60);
+        config.setLowAmbientBrightnessThresholds(new int[]{20});
+        config.setLowDisplayBrightnessThresholds(new int[]{10});
+        config.setHighDisplayBrightnessThresholds(new int[]{255});
+        config.setHighAmbientBrightnessThresholds(new int[]{8000});
+
+        director.defaultDisplayDeviceUpdated(displayDeviceConfig);
+
+        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0);
+        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 60,
+                0.0);
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
+                new int[]{255});
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
+                new int[]{8000});
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
+                new int[]{10});
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
+                new int[]{20});
     }
 
     private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
@@ -2420,6 +2380,12 @@
                     String.valueOf(fps));
         }
 
+        void setDefaultPeakRefreshRate(int fps) {
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_PEAK_REFRESH_RATE_DEFAULT,
+                    String.valueOf(fps));
+        }
+
         void setHighDisplayBrightnessThresholds(int[] brightnessThresholds) {
             String thresholds = toPropertyValue(brightnessThresholds);
 
@@ -2550,7 +2516,7 @@
         }
 
         @Override
-        public boolean renderFrameRateIsPhysicalRefreshRate() {
+        public boolean supportsFrameRateOverride() {
             return true;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 657bda6..c7caa43 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -30,6 +30,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
@@ -52,6 +54,9 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.display.layout.DisplayIdProducer;
+import com.android.server.display.layout.Layout;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -59,6 +64,7 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -71,6 +77,7 @@
     private static int sUniqueTestDisplayId = 0;
     private static final int DEVICE_STATE_CLOSED = 0;
     private static final int DEVICE_STATE_OPEN = 2;
+    private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
 
     private DisplayDeviceRepository mDisplayDeviceRepo;
     private LogicalDisplayMapper mLogicalDisplayMapper;
@@ -78,11 +85,16 @@
     private Handler mHandler;
     private PowerManager mPowerManager;
 
+    private final DisplayIdProducer mIdProducer = (isDefault) ->
+            isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
+
     @Mock LogicalDisplayMapper.Listener mListenerMock;
     @Mock Context mContextMock;
     @Mock Resources mResourcesMock;
     @Mock IPowerManager mIPowerManagerMock;
     @Mock IThermalService mIThermalServiceMock;
+    @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy =
+            new DeviceStateToLayoutMap(mIdProducer);
 
     @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
 
@@ -132,7 +144,8 @@
         mLooper = new TestLooper();
         mHandler = new Handler(mLooper.getLooper());
         mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
-                mListenerMock, new DisplayManagerService.SyncRoot(), mHandler);
+                mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
+                mDeviceStateToLayoutMapSpy);
     }
 
 
@@ -259,7 +272,8 @@
         add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0));
         add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
 
-        int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID);
+        int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID,
+                /* includeDisabled= */ true);
         assertEquals(3, ids.length);
         Arrays.sort(ids);
         assertEquals(DEFAULT_DISPLAY, ids[0]);
@@ -503,10 +517,193 @@
                 /* isBootCompleted= */true));
     }
 
+    @Test
+    public void testDeviceStateLocked() {
+        DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        Layout layout = new Layout();
+        layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
+                true, true, mIdProducer);
+        layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
+                false, false, mIdProducer);
+        when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
+
+        layout = new Layout();
+        layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
+                false, false, mIdProducer);
+        layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
+                true, true, mIdProducer);
+        when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout);
+        when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout);
+
+        LogicalDisplay display1 = add(device1);
+        assertEquals(info(display1).address, info(device1).address);
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        LogicalDisplay display2 = add(device2);
+        assertEquals(info(display2).address, info(device2).address);
+        // We can only have one default display
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+        advanceTime(1000);
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+
+        mLogicalDisplayMapper.setDeviceStateLocked(1, false);
+        advanceTime(1000);
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+
+        mLogicalDisplayMapper.setDeviceStateLocked(2, false);
+        advanceTime(1000);
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+    }
+
+    @Test
+    public void testEnabledAndDisabledDisplays() {
+        DisplayAddress displayAddressOne = new TestUtils.TestDisplayAddress();
+        DisplayAddress displayAddressTwo = new TestUtils.TestDisplayAddress();
+        DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress();
+
+        TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, "one",
+                Display.TYPE_INTERNAL, 600, 800,
+                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, "two",
+                Display.TYPE_INTERNAL, 200, 800,
+                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+        TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, "three",
+                Display.TYPE_INTERNAL, 600, 900,
+                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+
+        Layout threeDevicesEnabledLayout = new Layout();
+        threeDevicesEnabledLayout.createDisplayLocked(
+                displayAddressOne,
+                /* isDefault= */ true,
+                /* isEnabled= */ true,
+                mIdProducer);
+        threeDevicesEnabledLayout.createDisplayLocked(
+                displayAddressTwo,
+                /* isDefault= */ false,
+                /* isEnabled= */ true,
+                mIdProducer);
+        threeDevicesEnabledLayout.createDisplayLocked(
+                displayAddressThree,
+                /* isDefault= */ false,
+                /* isEnabled= */ true,
+                mIdProducer);
+
+        when(mDeviceStateToLayoutMapSpy.get(DeviceStateToLayoutMap.STATE_DEFAULT))
+                .thenReturn(threeDevicesEnabledLayout);
+
+        LogicalDisplay display1 = add(device1);
+        LogicalDisplay display2 = add(device2);
+        LogicalDisplay display3 = add(device3);
+
+        // ensure 3 displays are returned
+        int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID, false);
+        assertEquals(3, ids.length);
+        Arrays.sort(ids);
+        assertEquals(DEFAULT_DISPLAY, ids[0]);
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1,
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device2,
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device3,
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+                threeDevicesEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+                threeDevicesEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+                threeDevicesEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+
+        Layout oneDeviceEnabledLayout = new Layout();
+        oneDeviceEnabledLayout.createDisplayLocked(
+                displayAddressOne,
+                /* isDefault= */ true,
+                /* isEnabled= */ true,
+                mIdProducer);
+        oneDeviceEnabledLayout.createDisplayLocked(
+                displayAddressTwo,
+                /* isDefault= */ false,
+                /* isEnabled= */ false,
+                mIdProducer);
+        oneDeviceEnabledLayout.createDisplayLocked(
+                displayAddressThree,
+                /* isDefault= */ false,
+                /* isEnabled= */ false,
+                mIdProducer);
+
+        when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout);
+        when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);
+
+        // 1) Set the new state
+        // 2) Mark the displays as STATE_OFF so that it can continue with transition
+        // 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state
+        // 4) Dispatch handler events.
+        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
+        advanceTime(1000);
+        final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked(
+                Process.SYSTEM_UID, false);
+        if (allDisplayIds.length != 1) {
+            throw new RuntimeException("Displays: \n"
+                    + mLogicalDisplayMapper.getDisplayLocked(device1).toString()
+                    + "\n" + mLogicalDisplayMapper.getDisplayLocked(device2).toString()
+                    + "\n" + mLogicalDisplayMapper.getDisplayLocked(device3).toString());
+        }
+        // ensure only one display is returned
+        assertEquals(1, allDisplayIds.length);
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1,
+                /* includeDisabled= */ false));
+        assertNull(mLogicalDisplayMapper.getDisplayLocked(device2,
+                /* includeDisabled= */ false));
+        assertNull(mLogicalDisplayMapper.getDisplayLocked(device3,
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+                oneDeviceEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+        assertNull(mLogicalDisplayMapper.getDisplayLocked(
+                oneDeviceEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+        assertNull(mLogicalDisplayMapper.getDisplayLocked(
+                oneDeviceEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+
+        // Now do it again to go back to state 1
+        mLogicalDisplayMapper.setDeviceStateLocked(1, false);
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
+        advanceTime(1000);
+        final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked(
+                Process.SYSTEM_UID, false);
+
+        // ensure all three displays are returned
+        assertEquals(3, threeDisplaysEnabled.length);
+    }
+
     /////////////////
     // Helper Methods
     /////////////////
 
+    private void advanceTime(long timeMs) {
+        mLooper.moveTimeForward(1000);
+        mLooper.dispatchAll();
+    }
+
     private TestDisplayDevice createDisplayDevice(int type, int width, int height, int flags) {
         return createDisplayDevice(
                 new TestUtils.TestDisplayAddress(), /*  uniqueId */ "", type, width, height, flags);
@@ -575,6 +772,7 @@
     class TestDisplayDevice extends DisplayDevice {
         private DisplayDeviceInfo mInfo;
         private DisplayDeviceInfo mSentInfo;
+        private int mState;
 
         TestDisplayDevice() {
             super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock);
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index 5a43530..1d70fc6 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -126,12 +126,12 @@
         verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
         reset(t);
 
-        mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_DISABLED);
+        mLogicalDisplay.setEnabledLocked(false);
         mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
         verify(t).setDisplayFlags(any(), eq(0));
         reset(t);
 
-        mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_ENABLED);
+        mLogicalDisplay.setEnabledLocked(true);
         mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
         mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
         verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 3b0a22f..35a677e 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -344,6 +344,40 @@
         assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f);
     }
 
+    @Test
+    public void testBrightnessInitialisesWithInvalidFloat() {
+        final String uniqueDisplayId = "test:123";
+        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+
+        // Set any value which initialises Display state
+        float refreshRate = 85.3f;
+        mDataStore.loadIfNeeded();
+        mDataStore.setUserPreferredRefreshRate(testDisplayDevice, refreshRate);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mInjector.setWriteStream(baos);
+        mDataStore.saveIfNeeded();
+        mTestLooper.dispatchAll();
+        assertTrue(mInjector.wasWriteSuccessful());
+        TestInjector newInjector = new TestInjector();
+        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        newInjector.setReadStream(bais);
+        newDataStore.loadIfNeeded();
+        assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice)));
+    }
+
+
     public class TestInjector extends PersistentDataStore.Injector {
         private InputStream mReadStream;
         private OutputStream mWriteStream;
diff --git a/services/tests/servicestests/src/com/android/server/display/TestUtils.java b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
index 0454587..a419b3f 100644
--- a/services/tests/servicestests/src/com/android/server/display/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
@@ -51,6 +51,12 @@
         }
     }
 
+    public static void setMaximumRange(Sensor sensor, float maximumRange) throws Exception {
+        Method setter = Sensor.class.getDeclaredMethod("setRange", Float.TYPE, Float.TYPE);
+        setter.setAccessible(true);
+        setter.invoke(sensor, maximumRange, 1);
+    }
+
     public static Sensor createSensor(int type, String strType) throws Exception {
         Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
         constr.setAccessible(true);
@@ -59,6 +65,16 @@
         return sensor;
     }
 
+    public static Sensor createSensor(int type, String strType, float maximumRange)
+            throws Exception {
+        Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+        constr.setAccessible(true);
+        Sensor sensor = constr.newInstance();
+        setSensorType(sensor, type, strType);
+        setMaximumRange(sensor, maximumRange);
+        return sensor;
+    }
+
     /**
      * Create a custom {@link DisplayAddress} to ensure we're not relying on any specific
      * display-address implementation in our code. Intentionally uses default object (reference)
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
index fabf535..d332b30 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -39,8 +39,6 @@
                 getReason(BrightnessReason.REASON_DOZE, BrightnessReason.MODIFIER_LOW_POWER));
         mBrightnessEvent.setPhysicalDisplayId("test");
         mBrightnessEvent.setLux(100.0f);
-        mBrightnessEvent.setFastAmbientLux(90.0f);
-        mBrightnessEvent.setSlowAmbientLux(85.0f);
         mBrightnessEvent.setPreThresholdLux(150.0f);
         mBrightnessEvent.setTime(System.currentTimeMillis());
         mBrightnessEvent.setInitialBrightness(25.0f);
@@ -50,6 +48,7 @@
         mBrightnessEvent.setRbcStrength(-1);
         mBrightnessEvent.setThermalMax(0.65f);
         mBrightnessEvent.setPowerFactor(0.2f);
+        mBrightnessEvent.setWasShortTermModelActive(true);
         mBrightnessEvent.setHbmMode(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
         mBrightnessEvent.setFlags(0);
         mBrightnessEvent.setAdjustmentFlags(0);
@@ -69,9 +68,9 @@
         String actualString = mBrightnessEvent.toString(false);
         String expectedString =
                 "BrightnessEvent: disp=1, physDisp=test, brt=0.6, initBrt=25.0, rcmdBrt=0.6,"
-                + " preBrt=NaN, lux=100.0, fastLux=90.0, slowLux=85.0, preLux=150.0, hbmMax=0.62,"
-                + " hbmMode=off, rbcStrength=-1, thrmMax=0.65, powerFactor=0.2, flags=, reason=doze"
-                + " [ low_pwr ], autoBrightness=true";
+                + " preBrt=NaN, lux=100.0, preLux=150.0, hbmMax=0.62, hbmMode=off, rbcStrength=-1,"
+                + " thrmMax=0.65, powerFactor=0.2, wasShortTermModelActive=true, flags=,"
+                + " reason=doze [ low_pwr ], autoBrightness=true";
         assertEquals(expectedString, actualString);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
new file mode 100644
index 0000000..cbeaf7b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.display.brightness;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayBrightnessControllerTest {
+    private static final int DISPLAY_ID = 1;
+
+    @Mock
+    private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
+    @Mock
+    private Context mContext;
+
+    private DisplayBrightnessController mDisplayBrightnessController;
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+        DisplayBrightnessController.Injector injector = new DisplayBrightnessController.Injector() {
+            @Override
+            DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(
+                    Context context, int displayId) {
+                return mDisplayBrightnessStrategySelector;
+            }
+        };
+        mDisplayBrightnessController = new DisplayBrightnessController(mContext, injector,
+                DISPLAY_ID);
+    }
+
+    @Test
+    public void updateBrightnessWorksAsExpected() {
+        DisplayPowerRequest displayPowerRequest = mock(DisplayPowerRequest.class);
+        DisplayBrightnessStrategy displayBrightnessStrategy = mock(DisplayBrightnessStrategy.class);
+        int targetDisplayState = Display.STATE_DOZE;
+        when(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                targetDisplayState)).thenReturn(displayBrightnessStrategy);
+        mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState);
+        verify(displayBrightnessStrategy).updateBrightness(displayPowerRequest);
+    }
+
+    @Test
+    public void isAllowAutoBrightnessWhileDozingConfigDelegatesToDozeBrightnessStrategy() {
+        mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig();
+        verify(mDisplayBrightnessStrategySelector).isAllowAutoBrightnessWhileDozingConfig();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
new file mode 100644
index 0000000..dcf217c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.display.brightness;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
+import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
+import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
+import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy;
+import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy;
+import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayBrightnessStrategySelectorTest {
+    private static final boolean DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING = false;
+    private static final int DISPLAY_ID = 1;
+
+    @Mock
+    private ScreenOffBrightnessStrategy mScreenOffBrightnessModeStrategy;
+    @Mock
+    private DozeBrightnessStrategy mDozeBrightnessModeStrategy;
+    @Mock
+    private OverrideBrightnessStrategy mOverrideBrightnessStrategy;
+    @Mock
+    private TemporaryBrightnessStrategy mTemporaryBrightnessStrategy;
+    @Mock
+    private BoostBrightnessStrategy mBoostBrightnessStrategy;
+    @Mock
+    private InvalidBrightnessStrategy mInvalidBrightnessStrategy;
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+
+    private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mInvalidBrightnessStrategy.getName()).thenReturn("InvalidBrightnessStrategy");
+        DisplayBrightnessStrategySelector.Injector injector =
+                new DisplayBrightnessStrategySelector.Injector() {
+                    @Override
+                    ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() {
+                        return mScreenOffBrightnessModeStrategy;
+                    }
+
+                    @Override
+                    DozeBrightnessStrategy getDozeBrightnessStrategy() {
+                        return mDozeBrightnessModeStrategy;
+                    }
+
+                    @Override
+                    OverrideBrightnessStrategy getOverrideBrightnessStrategy() {
+                        return mOverrideBrightnessStrategy;
+                    }
+
+                    @Override
+                    TemporaryBrightnessStrategy getTemporaryBrightnessStrategy() {
+                        return mTemporaryBrightnessStrategy;
+                    }
+
+                    @Override
+                    BoostBrightnessStrategy getBoostBrightnessStrategy() {
+                        return mBoostBrightnessStrategy;
+                    }
+
+                    @Override
+                    InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
+                        return mInvalidBrightnessStrategy;
+                    }
+                };
+        mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+                injector, DISPLAY_ID);
+
+    }
+
+    @Test
+    public void selectStrategySelectsDozeStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
+                DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_DOZE), mDozeBrightnessModeStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsScreenOffStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_OFF), mScreenOffBrightnessModeStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsOverrideStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.screenBrightnessOverride = 0.4f;
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mOverrideBrightnessStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsTemporaryStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(0.3f);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mTemporaryBrightnessStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsBoostStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.boostScreenBrightness = true;
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mBoostBrightnessStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsInvalidStrategyWhenNoStrategyIsValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mInvalidBrightnessStrategy);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
new file mode 100644
index 0000000..431a239
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.display.brightness.strategy;
+
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+
+public class BoostBrightnessStrategyTest {
+    private BoostBrightnessStrategy mBoostBrightnessStrategy;
+
+    @Before
+    public void before() {
+        mBoostBrightnessStrategy = new BoostBrightnessStrategy();
+    }
+
+    @Test
+    public void updateBrightnessWorksAsExpectedWhenBoostBrightnessIsRequested() {
+        DisplayManagerInternal.DisplayPowerRequest
+                displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
+        displayPowerRequest.boostScreenBrightness = true;
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_BOOST);
+        DisplayBrightnessState expectedDisplayBrightnessState =
+                new DisplayBrightnessState.Builder()
+                        .setBrightness(PowerManager.BRIGHTNESS_MAX)
+                        .setBrightnessReason(brightnessReason)
+                        .setSdrBrightness(PowerManager.BRIGHTNESS_MAX)
+                        .build();
+        DisplayBrightnessState updatedDisplayBrightnessState =
+                mBoostBrightnessStrategy.updateBrightness(displayPowerRequest);
+        assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
new file mode 100644
index 0000000..29652ff
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.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 com.android.server.display.brightness.strategy;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DozeBrightnessStrategyTest {
+    private DozeBrightnessStrategy mDozeBrightnessModeStrategy;
+
+    @Before
+    public void before() {
+        mDozeBrightnessModeStrategy = new DozeBrightnessStrategy();
+    }
+
+    @Test
+    public void updateBrightnessWorksAsExpectedWhenScreenDozeStateIsRequested() {
+        DisplayPowerRequest displayPowerRequest = new DisplayPowerRequest();
+        float dozeScreenBrightness = 0.2f;
+        displayPowerRequest.dozeScreenBrightness = dozeScreenBrightness;
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_DOZE);
+        DisplayBrightnessState expectedDisplayBrightnessState =
+                new DisplayBrightnessState.Builder()
+                        .setBrightness(dozeScreenBrightness)
+                        .setBrightnessReason(brightnessReason)
+                        .setSdrBrightness(dozeScreenBrightness)
+                        .build();
+        DisplayBrightnessState updatedDisplayBrightnessState =
+                mDozeBrightnessModeStrategy.updateBrightness(displayPowerRequest);
+        assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
new file mode 100644
index 0000000..4d89c28
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.display.brightness.strategy;
+
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+
+public class OverrideBrightnessStrategyTest {
+    private OverrideBrightnessStrategy mOverrideBrightnessStrategy;
+
+    @Before
+    public void before() {
+        mOverrideBrightnessStrategy = new OverrideBrightnessStrategy();
+    }
+
+    @Test
+    public void updateBrightnessWorksAsExpectedWhenScreenDozeStateIsRequested() {
+        DisplayManagerInternal.DisplayPowerRequest
+                displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
+        float overrideBrightness = 0.2f;
+        displayPowerRequest.screenBrightnessOverride = overrideBrightness;
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_OVERRIDE);
+        DisplayBrightnessState expectedDisplayBrightnessState =
+                new DisplayBrightnessState.Builder()
+                        .setBrightness(overrideBrightness)
+                        .setBrightnessReason(brightnessReason)
+                        .setSdrBrightness(overrideBrightness)
+                        .build();
+        DisplayBrightnessState updatedDisplayBrightnessState =
+                mOverrideBrightnessStrategy.updateBrightness(displayPowerRequest);
+        assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
new file mode 100644
index 0000000..0505475
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.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 com.android.server.display.brightness.strategy;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class ScreenOffBrightnessStrategyTest {
+
+    private ScreenOffBrightnessStrategy mScreenOffBrightnessModeStrategy;
+
+    @Before
+    public void before() {
+        mScreenOffBrightnessModeStrategy = new ScreenOffBrightnessStrategy();
+    }
+
+    @Test
+    public void updateBrightnessWorksAsExpectedWhenScreenOffDisplayState() {
+        DisplayPowerRequest displayPowerRequest = new DisplayPowerRequest();
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_SCREEN_OFF);
+        DisplayBrightnessState expectedDisplayBrightnessState =
+                new DisplayBrightnessState.Builder()
+                        .setBrightness(PowerManager.BRIGHTNESS_OFF_FLOAT)
+                        .setSdrBrightness(PowerManager.BRIGHTNESS_OFF_FLOAT)
+                        .setBrightnessReason(brightnessReason)
+                        .build();
+        DisplayBrightnessState updatedDisplayBrightnessState =
+                mScreenOffBrightnessModeStrategy.updateBrightness(displayPowerRequest);
+        assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
new file mode 100644
index 0000000..4a32796
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.display.brightness.strategy;
+
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+
+public class TemporaryBrightnessStrategyTest {
+    private TemporaryBrightnessStrategy mTemporaryBrightnessStrategy;
+
+    @Before
+    public void before() {
+        mTemporaryBrightnessStrategy = new TemporaryBrightnessStrategy();
+    }
+
+    @Test
+    public void updateBrightnessWorksAsExpectedWhenTemporaryBrightnessIsSet() {
+        DisplayManagerInternal.DisplayPowerRequest
+                displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
+        float temporaryBrightness = 0.2f;
+        mTemporaryBrightnessStrategy.setTemporaryScreenBrightness(temporaryBrightness);
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_TEMPORARY);
+        DisplayBrightnessState expectedDisplayBrightnessState =
+                new DisplayBrightnessState.Builder()
+                        .setBrightness(temporaryBrightness)
+                        .setBrightnessReason(brightnessReason)
+                        .setSdrBrightness(temporaryBrightness)
+                        .build();
+        DisplayBrightnessState updatedDisplayBrightnessState =
+                mTemporaryBrightnessStrategy.updateBrightness(displayPowerRequest);
+        assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+        assertEquals(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness(),
+                Float.NaN, 0.0f);
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index f9b8373..9672085 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -108,7 +108,7 @@
         }
 
         @Override
-        public boolean hasFsverity(String path) {
+        public boolean isFromTrustedProvider(String path, byte[] signature) {
             return mHasFsverityPaths.contains(path);
         }
 
@@ -291,6 +291,32 @@
     }
 
     @Test
+    public void construct_missingSignatureFile() throws Exception {
+        UpdatableFontDir dirForPreparation = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
+                mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
+        dirForPreparation.loadFontFileMap();
+        dirForPreparation.update(Arrays.asList(
+                newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE)));
+        assertThat(mUpdatableFontFilesDir.list()).hasLength(1);
+
+        // Remove signature file next to the font file.
+        File fontDir = dirForPreparation.getPostScriptMap().get("foo");
+        File sigFile = new File(fontDir.getParentFile(), "font.fsv_sig");
+        assertThat(sigFile.exists()).isTrue();
+        sigFile.delete();
+
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
+                mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
+        dir.loadFontFileMap();
+        // The font file should be removed and should not be loaded.
+        assertThat(dir.getPostScriptMap()).isEmpty();
+        assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
+        assertThat(dir.getFontFamilyMap()).isEmpty();
+    }
+
+    @Test
     public void construct_olderThanPreinstalledFont() throws Exception {
         Function<Map<String, File>, FontConfig> configSupplier = (map) -> {
             FontConfig.Font fooFont = new FontConfig.Font(
@@ -782,8 +808,8 @@
         UpdatableFontDir.FsverityUtil fakeFsverityUtil = new UpdatableFontDir.FsverityUtil() {
 
             @Override
-            public boolean hasFsverity(String path) {
-                return mFakeFsverityUtil.hasFsverity(path);
+            public boolean isFromTrustedProvider(String path, byte[] signature) {
+                return mFakeFsverityUtil.isFromTrustedProvider(path, signature);
             }
 
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index 5b11466..2cb46da 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -24,6 +24,8 @@
 
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.media.AudioManager;
 import android.os.Looper;
@@ -52,6 +54,7 @@
     private Context mContextSpy;
     private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
     private FakePowerManagerWrapper mPowerManager;
+    private TestCallback mCallback;
     private ArcTerminationActionFromAvr mAction;
 
     private FakeNativeWrapper mNativeWrapper;
@@ -112,7 +115,9 @@
             }
         };
         mHdmiCecLocalDeviceAudioSystem.init();
-        mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem);
+        mCallback = new TestCallback();
+        mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem,
+                mCallback);
 
         mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
         hdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
@@ -121,6 +126,20 @@
         mTestLooper.dispatchAll();
     }
 
+    private static class TestCallback extends IHdmiControlCallback.Stub {
+        private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();
+
+        @Override
+        public void onComplete(int result) {
+            mCallbackResult.add(result);
+        }
+
+        private int getResult() {
+            assertThat(mCallbackResult.size()).isEqualTo(1);
+            return mCallbackResult.get(0);
+        }
+    }
+
     @Test
     public void testSendMessage_sendFailed() {
         mNativeWrapper.setMessageSendResult(Constants.MESSAGE_TERMINATE_ARC,
@@ -133,6 +152,7 @@
         assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
 
         assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+        assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
     }
 
     @Test
@@ -143,8 +163,13 @@
                 Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
 
         assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
+        mTestLooper.dispatchAll();
 
-        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
+        mTestLooper.moveTimeForward(ArcTerminationActionFromAvr.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+        assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT);
     }
 
     @Test
@@ -163,5 +188,28 @@
         mTestLooper.dispatchAll();
 
         assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+        assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+    }
+
+    @Test
+    public void testReportArcTerminated_featureAbort() {
+        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+                Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
+
+        HdmiCecMessage arcTerminatedResponse = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                Constants.ADDR_TV,
+                Constants.ADDR_AUDIO_SYSTEM,
+                Constants.MESSAGE_TERMINATE_ARC,
+                Constants.ABORT_REFUSED);
+
+        mNativeWrapper.onCecMessage(arcTerminatedResponse);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+        assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index 7df0078..6a899e8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -102,7 +102,7 @@
                         Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
                         new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
-                    boolean isControlEnabled() {
+                    boolean isCecControlEnabled() {
                         return true;
                     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index ac57834..0419768 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -111,7 +111,7 @@
                         Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
                         new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
-                    boolean isControlEnabled() {
+                    boolean isCecControlEnabled() {
                         return true;
                     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
index 0028969..167e0f8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
@@ -80,6 +80,17 @@
                 R.bool.config_cecRoutingControlDisabled_default);
 
         doReturn(true).when(resources).getBoolean(
+                R.bool.config_cecSoundbarMode_userConfigurable);
+        doReturn(true).when(resources).getBoolean(
+                R.bool.config_cecSoundbarModeEnabled_allowed);
+        doReturn(false).when(resources).getBoolean(
+                R.bool.config_cecSoundbarModeEnabled_default);
+        doReturn(true).when(resources).getBoolean(
+                R.bool.config_cecSoundbarModeDisabled_allowed);
+        doReturn(true).when(resources).getBoolean(
+                R.bool.config_cecSoundbarModeDisabled_default);
+
+        doReturn(true).when(resources).getBoolean(
                 R.bool.config_cecPowerControlMode_userConfigurable);
         doReturn(true).when(resources).getBoolean(
                 R.bool.config_cecPowerControlModeTv_allowed);
@@ -348,6 +359,17 @@
         doReturn(true).when(resources).getBoolean(R.bool.config_cecQuerySadMaxDisabled_allowed);
         doReturn(false).when(resources).getBoolean(R.bool.config_cecQuerySadMaxDisabled_default);
 
+        doReturn(true).when(resources).getBoolean(
+                R.bool.config_earcEnabled_userConfigurable);
+        doReturn(true).when(resources).getBoolean(
+                R.bool.config_earcFeatureEnabled_allowed);
+        doReturn(true).when(resources).getBoolean(
+                R.bool.config_earcFeatureEnabled_default);
+        doReturn(true).when(resources).getBoolean(
+                R.bool.config_earcFeatureDisabled_allowed);
+        doReturn(false).when(resources).getBoolean(
+                R.bool.config_earcFeatureDisabled_default);
+
         return resources;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
index 8e756ae..870b681 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
@@ -76,6 +76,7 @@
                 .containsExactly(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
                     HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                     HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL,
+                    HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
                     HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
@@ -105,7 +106,8 @@
                     HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD,
                     HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST,
                     HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
-                    HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX);
+                    HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX,
+                    HdmiControlManager.SETTING_NAME_EARC_ENABLED);
     }
 
     @Test
@@ -115,6 +117,7 @@
                 .containsExactly(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
                     HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                     HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL,
+                    HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
                     HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
@@ -144,7 +147,8 @@
                     HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD,
                     HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST,
                     HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
-                    HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX);
+                    HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX,
+                    HdmiControlManager.SETTING_NAME_EARC_ENABLED);
     }
 
     @Test
@@ -155,6 +159,7 @@
                 .containsExactly(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                     HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL,
+                    HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
                     HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
                     HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
                     HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
@@ -183,7 +188,8 @@
                     HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD,
                     HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST,
                     HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
-                    HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX);
+                    HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX,
+                    HdmiControlManager.SETTING_NAME_EARC_ENABLED);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 08d0e90..de2c218 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -354,7 +354,7 @@
         HdmiCecMessage expectedMessage =
                 HdmiCecMessageBuilder.buildSetSystemAudioMode(
                         ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
-        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+        assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
         assertThat(mMusicMute).isTrue();
     }
 
@@ -535,6 +535,25 @@
     }
 
     @Test
+    public void handleRequestArcTerminate_callbackIsPreserved() throws Exception {
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
+        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
+                new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem, callback));
+
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM);
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message))
+                .isEqualTo(Constants.HANDLED);
+
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(
+                ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0)).isEqualTo(callback);
+    }
+
+    @Test
     public void handleRequestArcInit_arcIsNotSupported() throws Exception {
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
@@ -880,4 +899,13 @@
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
                 systemAudioModeRequest_fromAudioSystem);
     }
+
+    private static class TestCallback extends IHdmiControlCallback.Stub {
+        private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();
+
+        @Override
+        public void onComplete(int result) {
+            mCallbackResult.add(result);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 75c4d92..3ed8983 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -31,6 +31,7 @@
 import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioManager;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
@@ -45,6 +46,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -85,9 +88,13 @@
     private boolean mActiveMediaSessionsPaused;
     private FakePowerManagerInternalWrapper mPowerManagerInternal =
             new FakePowerManagerInternalWrapper();
+    @Mock
+    protected AudioManager mAudioManager;
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
         Context context = InstrumentationRegistry.getTargetContext();
         mMyLooper = mTestLooper.getLooper();
 
@@ -103,12 +110,17 @@
                     }
 
                     @Override
+                    AudioManager getAudioManager() {
+                        return mAudioManager;
+                    }
+
+                    @Override
                     void pauseActiveMediaSessions() {
                         mActiveMediaSessionsPaused = true;
                     }
 
                     @Override
-                    boolean isControlEnabled() {
+                    boolean isCecControlEnabled() {
                         return true;
                     }
 
@@ -1424,6 +1436,32 @@
     }
 
     @Test
+    public void sendVolumeKeyEvent_toLocalDevice_discardMessage() {
+        HdmiCecLocalDeviceAudioSystem audioSystem =
+                new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
+        audioSystem.init();
+        mLocalDevices.add(audioSystem);
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+
+        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+                HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        mHdmiControlService.setSystemAudioActivated(true);
+
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, false);
+
+        HdmiCecMessage keyPressed = HdmiCecMessageBuilder.buildUserControlPressed(
+                mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM, HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP);
+        HdmiCecMessage keyReleased = HdmiCecMessageBuilder.buildUserControlReleased(
+                mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(keyPressed);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(keyReleased);
+    }
+
+    @Test
     public void handleSetStreamPath_broadcastsActiveSource() {
         HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
                 mPlaybackPhysicalAddress);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index 48e70fe..b30118c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -141,7 +141,7 @@
                 new HdmiControlService(context, Collections.emptyList(),
                         new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
-                    boolean isControlEnabled() {
+                    boolean isCecControlEnabled() {
                         return isControlEnabled;
                     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 54baf18..5246107 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -136,7 +136,7 @@
                         super.wakeUp();
                     }
                     @Override
-                    boolean isControlEnabled() {
+                    boolean isCecControlEnabled() {
                         return true;
                     }
 
@@ -849,4 +849,53 @@
         verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
                 anyInt());
     }
+
+    @Test
+    public void tvSendRequestArcTerminationOnSleep() {
+        // Emulate Audio device on port 0x2000 (supports ARC)
+
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        mTestLooper.dispatchAll();
+
+        mHdmiCecLocalDeviceTv.startArcAction(true);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+        HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+
+        mNativeWrapper.onCecMessage(initiateArc);
+        mTestLooper.dispatchAll();
+
+        // Finish querying SADs
+        assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        // ARC should be established after RequestSadAction is finished
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
+
+        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+    }
+
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index a08e398..4e5336e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -68,7 +68,7 @@
                 Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
                 new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
-            boolean isControlEnabled() {
+            boolean isCecControlEnabled() {
                 return true;
             }
 
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..49a0a9a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -44,6 +44,7 @@
 import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
 import android.hardware.hdmi.IHdmiControlStatusChangeListener;
 import android.hardware.hdmi.IHdmiVendorCommandListener;
+import android.media.AudioManager;
 import android.os.Binder;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -58,7 +59,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.Mock;
 import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -86,13 +89,16 @@
     private HdmiPortInfo[] mHdmiPortInfo;
     private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>();
 
+    @Mock protected AudioManager mAudioManager;
+
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
         HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
         mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_PLAYBACK);
-        mLocalDeviceTypes.add(DEVICE_AUDIO_SYSTEM);
 
         mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, mLocalDeviceTypes,
                 new FakeAudioDeviceVolumeManagerWrapper()));
@@ -133,6 +139,7 @@
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlServiceSpy.setPowerManager(mPowerManager);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlServiceSpy.setAudioManager(mAudioManager);
 
         mTestLooper.dispatchAll();
     }
@@ -196,7 +203,7 @@
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mNativeWrapper.clearResultMessages();
 
         assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo(
@@ -229,7 +236,7 @@
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
         mTestLooper.dispatchAll();
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mNativeWrapper.clearResultMessages();
         mTestLooper.dispatchAll();
 
@@ -286,11 +293,11 @@
         int volumeControlEnabled = HdmiControlManager.VOLUME_CONTROL_ENABLED;
         mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(volumeControlEnabled);
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         assertThat(mHdmiControlServiceSpy.getHdmiCecVolumeControl()).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(mHdmiControlServiceSpy.getHdmiCecVolumeControl())
                 .isEqualTo(volumeControlEnabled);
     }
@@ -301,12 +308,12 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, volumeControlEnabled);
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
                 volumeControlEnabled);
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo(
                 volumeControlEnabled);
@@ -321,13 +328,13 @@
         VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback();
         mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback);
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         assertThat(callback.mCallbackReceived).isTrue();
         assertThat(callback.mVolumeControlEnabled).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
 
 
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(callback.mVolumeControlEnabled).isEqualTo(
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
     }
@@ -424,7 +431,7 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
     }
@@ -434,7 +441,7 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
     }
@@ -444,14 +451,14 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
 
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo(
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
     }
@@ -461,7 +468,7 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mTestLooper.dispatchAll();
 
         mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV,
@@ -479,7 +486,8 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
         mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV,
@@ -500,7 +508,8 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
         HdmiCecMessage reportFeatures = ReportFeaturesMessage.build(
@@ -516,7 +525,8 @@
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
         HdmiCecMessage reportFeatures = ReportFeaturesMessage.build(
@@ -622,11 +632,11 @@
     @Test
     public void initCec_statusListener_CecEnabled_CecAvailable_TvOn() {
         HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback();
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         mTestLooper.dispatchAll();
 
         mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
@@ -645,11 +655,11 @@
     @Test
     public void initCec_statusListener_CecEnabled_CecAvailable_TvStandby() {
         HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback();
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         mTestLooper.dispatchAll();
 
         mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
@@ -668,11 +678,11 @@
     @Test
     public void initCec_statusListener_CecEnabled_CecAvailable_TvTransientToOn() {
         HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback();
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         mTestLooper.dispatchAll();
 
         mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
@@ -691,11 +701,11 @@
     @Test
     public void initCec_statusListener_CecEnabled_CecAvailable_TvTransientToStandby() {
         HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback();
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
         mTestLooper.dispatchAll();
 
         mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback);
-        mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
@@ -1024,6 +1034,54 @@
                 .containsExactly(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM);
     }
 
+    @Test
+    public void setSoundbarMode_enabled_addAudioSystemLocalDevice() {
+        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
+        // Initialize the local devices excluding the audio system.
+        mHdmiControlServiceSpy.clearCecLocalDevices();
+        mLocalDevices.remove(mAudioSystemDeviceSpy);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
+
+        // Enable the setting and check if the audio system local device is found in the network.
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+                HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlServiceSpy.audioSystem()).isNotNull();
+    }
+
+    @Test
+    public void setSoundbarMode_disabled_removeAudioSystemLocalDevice() {
+        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
+        // Initialize the local devices excluding the audio system.
+        mHdmiControlServiceSpy.clearCecLocalDevices();
+        mLocalDevices.remove(mAudioSystemDeviceSpy);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
+
+        // Enable the setting and check if the audio system local device is found in the network.
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+                HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlServiceSpy.audioSystem()).isNotNull();
+
+        // Disable the setting and check if the audio system local device is removed from the
+        // network.
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+                HdmiControlManager.SOUNDBAR_MODE_DISABLED);
+        mTestLooper.dispatchAll();
+
+        // Wait for ArcTerminationActionFromAvr timeout for the logical address allocation to start.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
+    }
+
     protected static class MockPlaybackDevice extends HdmiCecLocalDevicePlayback {
 
         private boolean mCanGoToStandby;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index c07d4be..f719ca1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -99,7 +99,7 @@
                 new HdmiControlService(context, Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
                         new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
-                    boolean isControlEnabled() {
+                    boolean isCecControlEnabled() {
                         return true;
                     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index f5bf30b..be62df8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -151,7 +151,7 @@
                         Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
                         new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
-                    boolean isControlEnabled() {
+                    boolean isCecControlEnabled() {
                         return true;
                     }
 
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 6590a2b..ecd9d89 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.input
 
+import android.bluetooth.BluetoothDevice
 import android.content.Context
 import android.content.ContextWrapper
 import android.hardware.BatteryState.STATUS_CHARGING
@@ -33,6 +34,8 @@
 import android.platform.test.annotations.Presubmit
 import android.view.InputDevice
 import androidx.test.InstrumentationRegistry
+import com.android.server.input.BatteryController.BluetoothBatteryManager
+import com.android.server.input.BatteryController.BluetoothBatteryManager.BluetoothBatteryListener
 import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS
 import com.android.server.input.BatteryController.UEventManager
 import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener
@@ -52,6 +55,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.notNull
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
@@ -172,6 +176,8 @@
         const val SECOND_DEVICE_ID = 11
         const val USI_DEVICE_ID = 101
         const val SECOND_USI_DEVICE_ID = 102
+        const val BT_DEVICE_ID = 100001
+        const val SECOND_BT_DEVICE_ID = 100002
         const val TIMESTAMP = 123456789L
     }
 
@@ -184,6 +190,8 @@
     private lateinit var iInputManager: IInputManager
     @Mock
     private lateinit var uEventManager: UEventManager
+    @Mock
+    private lateinit var bluetoothBatteryManager: BluetoothBatteryManager
 
     private lateinit var batteryController: BatteryController
     private lateinit var context: Context
@@ -203,11 +211,13 @@
         addInputDevice(DEVICE_ID)
         addInputDevice(SECOND_DEVICE_ID)
 
-        batteryController = BatteryController(context, native, testLooper.looper, uEventManager)
+        batteryController = BatteryController(context, native, testLooper.looper, uEventManager,
+            bluetoothBatteryManager)
         batteryController.systemRunning()
         val listenerCaptor = ArgumentCaptor.forClass(IInputDevicesChangedListener::class.java)
         verify(iInputManager).registerInputDevicesChangedListener(listenerCaptor.capture())
         devicesChangedListener = listenerCaptor.value
+        testLooper.dispatchAll()
     }
 
     private fun notifyDeviceChanged(
@@ -230,7 +240,7 @@
     private fun addInputDevice(
             deviceId: Int,
         hasBattery: Boolean = true,
-        supportsUsi: Boolean = false
+        supportsUsi: Boolean = false,
     ) {
         deviceGenerationMap[deviceId] = 0
         notifyDeviceChanged(deviceId, hasBattery, supportsUsi)
@@ -634,4 +644,125 @@
         assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
             matchesState(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f))
     }
+
+    @Test
+    fun testRegisterBluetoothListenerForMonitoredBluetoothDevices() {
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
+            .thenReturn("11:22:33:44:55:66")
+        addInputDevice(BT_DEVICE_ID)
+        testLooper.dispatchNext()
+        addInputDevice(SECOND_BT_DEVICE_ID)
+        testLooper.dispatchNext()
+
+        // Ensure that a BT battery listener is not added when there are no monitored BT devices.
+        verify(bluetoothBatteryManager, never()).addListener(any())
+
+        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+        val listener = createMockListener()
+
+        // The BT battery listener is added when the first BT input device is monitored.
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+
+        // The BT listener is only added once for all BT devices.
+        batteryController.registerBatteryListener(SECOND_BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager, times(1)).addListener(any())
+
+        // The BT listener is only removed when there are no monitored BT devices.
+        batteryController.unregisterBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager, never()).removeListener(any())
+
+        `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
+            .thenReturn(null)
+        notifyDeviceChanged(SECOND_BT_DEVICE_ID)
+        testLooper.dispatchNext()
+        verify(bluetoothBatteryManager).removeListener(bluetoothListener.value)
+    }
+
+    @Test
+    fun testNotifiesBluetoothBatteryChanges() {
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
+        addInputDevice(BT_DEVICE_ID)
+        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
+
+        // When the state has not changed, the listener is not notified again.
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+        listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f)
+
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(25)
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f)
+    }
+
+    @Test
+    fun testBluetoothBatteryIsPrioritized() {
+        `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device")
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
+        `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98)
+        addInputDevice(BT_DEVICE_ID)
+        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+        val listener = createMockListener()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+
+        // When the device is first monitored and both native and BT battery is available,
+        // the latter is used.
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+        verify(uEventManager).addListener(uEventListener.capture(), any())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
+        assertThat("battery state matches", batteryController.getBatteryState(BT_DEVICE_ID),
+            matchesState(BT_DEVICE_ID, capacity = 0.21f))
+
+        // If only the native battery state changes the listener is not notified.
+        `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(97)
+        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+        listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f)
+        assertThat("battery state matches", batteryController.getBatteryState(BT_DEVICE_ID),
+            matchesState(BT_DEVICE_ID, capacity = 0.21f))
+    }
+
+    @Test
+    fun testFallBackToNativeBatteryStateWhenBluetoothStateInvalid() {
+        `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device")
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
+        `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98)
+        addInputDevice(BT_DEVICE_ID)
+        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+        val listener = createMockListener()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+        verify(uEventManager).addListener(uEventListener.capture(), any())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
+
+        // Fall back to the native state when BT is off.
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF")))
+            .thenReturn(BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF)
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.98f)
+
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(22)
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+        verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.22f)
+
+        // Fall back to the native state when BT battery is unknown.
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF")))
+            .thenReturn(BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+        listener.verifyNotified(BT_DEVICE_ID, mode = times(2), capacity = 0.98f)
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index 9092ec3..0884b78 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -367,6 +367,7 @@
         assertFalse(item_en_us_allcaps.mIsSystemLocale);
     }
 
+    @SuppressWarnings("SelfComparison")
     @Test
     public void testImeSubtypeListComparator() throws Exception {
         final ComponentName imeX1 = new ComponentName("com.example.imeX", "Ime1");
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
index d91a748..f5029ec 100644
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
@@ -71,7 +71,7 @@
     private static final String TEST_APP_PACKAGE = "com.android.servicestests.apps.jobtestapp";
     private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestJobActivity";
     private static final long POLL_INTERVAL = 500;
-    private static final long DEFAULT_WAIT_TIMEOUT = 5000;
+    private static final long DEFAULT_WAIT_TIMEOUT = 10_000;
 
     private Context mContext;
     private AppOpsManager mAppOpsManager;
@@ -115,7 +115,8 @@
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(ACTION_JOB_STARTED);
         intentFilter.addAction(ACTION_JOB_STOPPED);
-        mContext.registerReceiver(mJobStateChangeReceiver, intentFilter);
+        mContext.registerReceiver(mJobStateChangeReceiver, intentFilter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
         setAppOpsModeAllowed(true);
         setPowerExemption(false);
     }
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index f138311..0589b3a 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -20,11 +20,10 @@
 import android.content.pm.PackageManagerInternal;
 import android.net.NetworkRequest;
 import android.os.Build;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.os.SystemClock;
 import android.test.RenamingDelegatingContext;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 
@@ -32,7 +31,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.util.HexDump;
 import com.android.server.LocalServices;
 import com.android.server.job.JobStore.JobSet;
 import com.android.server.job.controllers.JobStatus;
@@ -42,10 +40,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
 import java.time.Clock;
 import java.time.ZoneOffset;
-import java.util.Arrays;
-import java.util.Iterator;
 
 /**
  * Test reading and writing correctly from file.
@@ -98,11 +95,147 @@
         mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L);
     }
 
+    private void setUseSplitFiles(boolean useSplitFiles) throws Exception {
+        mTaskStoreUnderTest.setUseSplitFiles(useSplitFiles);
+        waitForPendingIo();
+    }
+
     private void waitForPendingIo() throws Exception {
         assertTrue("Timed out waiting for persistence I/O to complete",
                 mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L));
     }
 
+    /** Test that we properly remove the last job of an app from the persisted file. */
+    @Test
+    public void testRemovingLastJob_singleFile() throws Exception {
+        setUseSplitFiles(false);
+        runRemovingLastJob();
+    }
+
+    /** Test that we properly remove the last job of an app from the persisted file. */
+    @Test
+    public void testRemovingLastJob_splitFiles() throws Exception {
+        setUseSplitFiles(true);
+        runRemovingLastJob();
+    }
+
+    private void runRemovingLastJob() throws Exception {
+        final JobInfo task1 = new Builder(8, mComponent)
+                .setRequiresDeviceIdle(true)
+                .setPeriodic(10000L)
+                .setRequiresCharging(true)
+                .setPersisted(true)
+                .build();
+        final JobInfo task2 = new Builder(12, mComponent)
+                .setMinimumLatency(5000L)
+                .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
+                .setOverrideDeadline(30000L)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+                .setPersisted(true)
+                .build();
+        final int uid1 = SOME_UID;
+        final int uid2 = uid1 + 1;
+        final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
+        final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
+        runWritingJobsToDisk(JobStatus1, JobStatus2);
+
+        // Remove 1 job
+        mTaskStoreUnderTest.remove(JobStatus1, true);
+        waitForPendingIo();
+        JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+
+        assertJobsEqual(JobStatus2, loaded);
+        assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(JobStatus2));
+
+        // Remove 2nd job
+        mTaskStoreUnderTest.remove(JobStatus2, true);
+        waitForPendingIo();
+        jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
+    }
+
+    /** Test that we properly clear the persisted file when all jobs are dropped. */
+    @Test
+    public void testClearJobs_singleFile() throws Exception {
+        setUseSplitFiles(false);
+        runClearJobs();
+    }
+
+    /** Test that we properly clear the persisted file when all jobs are dropped. */
+    @Test
+    public void testClearJobs_splitFiles() throws Exception {
+        setUseSplitFiles(true);
+        runClearJobs();
+    }
+
+    private void runClearJobs() throws Exception {
+        final JobInfo task1 = new Builder(8, mComponent)
+                .setRequiresDeviceIdle(true)
+                .setPeriodic(10000L)
+                .setRequiresCharging(true)
+                .setPersisted(true)
+                .build();
+        final JobInfo task2 = new Builder(12, mComponent)
+                .setMinimumLatency(5000L)
+                .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
+                .setOverrideDeadline(30000L)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+                .setPersisted(true)
+                .build();
+        final int uid1 = SOME_UID;
+        final int uid2 = uid1 + 1;
+        final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
+        final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
+        runWritingJobsToDisk(JobStatus1, JobStatus2);
+
+        // Remove all jobs
+        mTaskStoreUnderTest.clear();
+        waitForPendingIo();
+        JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
+    }
+
+    @Test
+    public void testExtractUidFromJobFileName() {
+        File file = new File(mTestContext.getFilesDir(), "randomName");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), "jobs.xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), ".xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), "1000.xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), "10000");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX);
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "text.xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + ".xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "-10123.xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "1.xml");
+        assertEquals(1, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "101023.xml");
+        assertEquals(101023, JobStore.extractUidFromJobFileName(file));
+    }
+
     @Test
     public void testStringToIntArrayAndIntArrayToString() {
         final int[] netCapabilitiesIntArray = { 1, 3, 5, 7, 9 };
@@ -143,19 +276,23 @@
 
         assertEquals("Didn't get expected number of persisted tasks.", 1, jobStatusSet.size());
         final JobStatus loadedTaskStatus = jobStatusSet.getAllJobs().get(0);
-        assertTasksEqual(task, loadedTaskStatus.getJob());
+        assertJobsEqual(ts, loadedTaskStatus);
         assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(ts));
-        assertEquals("Different uids.", SOME_UID, loadedTaskStatus.getUid());
-        assertEquals(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION,
-                loadedTaskStatus.getInternalFlags());
-        compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
-                ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime());
-        compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
-                ts.getLatestRunTimeElapsed(), loadedTaskStatus.getLatestRunTimeElapsed());
     }
 
     @Test
-    public void testWritingTwoFilesToDisk() throws Exception {
+    public void testWritingTwoJobsToDisk_singleFile() throws Exception {
+        setUseSplitFiles(false);
+        runWritingTwoJobsToDisk();
+    }
+
+    @Test
+    public void testWritingTwoJobsToDisk_splitFiles() throws Exception {
+        setUseSplitFiles(true);
+        runWritingTwoJobsToDisk();
+    }
+
+    private void runWritingTwoJobsToDisk() throws Exception {
         final JobInfo task1 = new Builder(8, mComponent)
                 .setRequiresDeviceIdle(true)
                 .setPeriodic(10000L)
@@ -169,39 +306,48 @@
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
                 .setPersisted(true)
                 .build();
-        final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, SOME_UID, null, -1, null);
-        final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, SOME_UID, null, -1, null);
-        mTaskStoreUnderTest.add(taskStatus1);
-        mTaskStoreUnderTest.add(taskStatus2);
+        final int uid1 = SOME_UID;
+        final int uid2 = uid1 + 1;
+        final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
+        final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
+
+        runWritingJobsToDisk(taskStatus1, taskStatus2);
+    }
+
+    private void runWritingJobsToDisk(JobStatus... jobStatuses) throws Exception {
+        ArraySet<JobStatus> expectedJobs = new ArraySet<>();
+        for (JobStatus jobStatus : jobStatuses) {
+            mTaskStoreUnderTest.add(jobStatus);
+            expectedJobs.add(jobStatus);
+        }
         waitForPendingIo();
 
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
-        assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size());
-        Iterator<JobStatus> it = jobStatusSet.getAllJobs().iterator();
-        JobStatus loaded1 = it.next();
-        JobStatus loaded2 = it.next();
+        assertEquals("Incorrect # of persisted tasks.", expectedJobs.size(), jobStatusSet.size());
+        int count = 0;
+        final int expectedCount = expectedJobs.size();
+        for (JobStatus loaded : jobStatusSet.getAllJobs()) {
+            count++;
+            for (int i = 0; i < expectedJobs.size(); ++i) {
+                JobStatus expected = expectedJobs.valueAt(i);
 
-        // Reverse them so we know which comparison to make.
-        if (loaded1.getJobId() != 8) {
-            JobStatus tmp = loaded1;
-            loaded1 = loaded2;
-            loaded2 = tmp;
+                try {
+                    assertJobsEqual(expected, loaded);
+                    expectedJobs.remove(expected);
+                    break;
+                } catch (AssertionError e) {
+                    // Not equal. Move along.
+                }
+            }
         }
-
-        assertTasksEqual(task1, loaded1.getJob());
-        assertTasksEqual(task2, loaded2.getJob());
-        assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1));
-        assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus2));
-        // Check that the loaded task has the correct runtimes.
-        compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
-                taskStatus1.getEarliestRunTime(), loaded1.getEarliestRunTime());
-        compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
-                taskStatus1.getLatestRunTimeElapsed(), loaded1.getLatestRunTimeElapsed());
-        compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
-                taskStatus2.getEarliestRunTime(), loaded2.getEarliestRunTime());
-        compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
-                taskStatus2.getLatestRunTimeElapsed(), loaded2.getLatestRunTimeElapsed());
+        assertEquals("Loaded more jobs than expected", expectedCount, count);
+        if (expectedJobs.size() > 0) {
+            fail("Not all expected jobs were restored");
+        }
+        for (JobStatus jobStatus : jobStatuses) {
+            assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(jobStatus));
+        }
     }
 
     @Test
@@ -227,7 +373,7 @@
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
         assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
         JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
-        assertTasksEqual(task, loaded.getJob());
+        assertJobsEqual(taskStatus, loaded);
     }
 
     @Test
@@ -414,6 +560,35 @@
     }
 
     @Test
+    public void testEstimatedNetworkBytes() throws Exception {
+        assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .setRequiredNetwork(new NetworkRequest.Builder().build())
+                .setEstimatedNetworkBytes(
+                        JobInfo.NETWORK_BYTES_UNKNOWN, JobInfo.NETWORK_BYTES_UNKNOWN)
+                .build());
+        assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .setRequiredNetwork(new NetworkRequest.Builder().build())
+                .setEstimatedNetworkBytes(5, 15)
+                .build());
+    }
+
+    @Test
+    public void testMinimumNetworkChunkBytes() throws Exception {
+        assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .setRequiredNetwork(new NetworkRequest.Builder().build())
+                .setMinimumNetworkChunkBytes(JobInfo.NETWORK_BYTES_UNKNOWN)
+                .build());
+        assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .setRequiredNetwork(new NetworkRequest.Builder().build())
+                .setMinimumNetworkChunkBytes(42)
+                .build());
+    }
+
+    @Test
     public void testPersistedIdleConstraint() throws Exception {
         JobInfo.Builder b = new Builder(8, mComponent)
                 .setRequiresDeviceIdle(true)
@@ -502,62 +677,30 @@
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
         final JobStatus second = jobStatusSet.getAllJobs().iterator().next();
-        assertTasksEqual(first.getJob(), second.getJob());
+        assertJobsEqual(first, second);
     }
 
     /**
-     * Helper function to throw an error if the provided task and TaskStatus objects are not equal.
+     * Helper function to throw an error if the provided JobStatus objects are not equal.
      */
-    private void assertTasksEqual(JobInfo first, JobInfo second) {
-        assertEquals("Different task ids.", first.getId(), second.getId());
-        assertEquals("Different components.", first.getService(), second.getService());
-        assertEquals("Different periodic status.", first.isPeriodic(), second.isPeriodic());
-        assertEquals("Different period.", first.getIntervalMillis(), second.getIntervalMillis());
-        assertEquals("Different inital backoff.", first.getInitialBackoffMillis(),
-                second.getInitialBackoffMillis());
-        assertEquals("Different backoff policy.", first.getBackoffPolicy(),
-                second.getBackoffPolicy());
+    private void assertJobsEqual(JobStatus expected, JobStatus actual) {
+        assertEquals(expected.getJob(), actual.getJob());
 
-        assertEquals("Invalid charging constraint.", first.isRequireCharging(),
-                second.isRequireCharging());
-        assertEquals("Invalid battery not low constraint.", first.isRequireBatteryNotLow(),
-                second.isRequireBatteryNotLow());
-        assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(),
-                second.isRequireDeviceIdle());
-        assertEquals("Invalid network type.",
-                first.getNetworkType(), second.getNetworkType());
-        assertEquals("Invalid network.",
-                first.getRequiredNetwork(), second.getRequiredNetwork());
-        assertEquals("Invalid deadline constraint.",
-                first.hasLateConstraint(),
-                second.hasLateConstraint());
-        assertEquals("Invalid delay constraint.",
-                first.hasEarlyConstraint(),
-                second.hasEarlyConstraint());
-        assertEquals("Extras don't match",
-                first.getExtras().toString(), second.getExtras().toString());
-        assertEquals("Transient xtras don't match",
-                first.getTransientExtras().toString(), second.getTransientExtras().toString());
+        // Source UID isn't persisted, but the rest of the app info is.
+        assertEquals("Source package not equal",
+                expected.getSourcePackageName(), actual.getSourcePackageName());
+        assertEquals("Source user not equal", expected.getSourceUserId(), actual.getSourceUserId());
+        assertEquals("Calling UID not equal", expected.getUid(), actual.getUid());
+        assertEquals("Calling user not equal", expected.getUserId(), actual.getUserId());
 
-        // Since people can forget to add tests here for new fields, do one last
-        // validity check based on bits-on-wire equality.
-        final byte[] firstBytes = marshall(first);
-        final byte[] secondBytes = marshall(second);
-        if (!Arrays.equals(firstBytes, secondBytes)) {
-            Log.w(TAG, "First: " + HexDump.dumpHexString(firstBytes));
-            Log.w(TAG, "Second: " + HexDump.dumpHexString(secondBytes));
-            fail("Raw JobInfo aren't equal; see logs for details");
-        }
-    }
+        assertEquals("Internal flags not equal",
+                expected.getInternalFlags(), actual.getInternalFlags());
 
-    private static byte[] marshall(Parcelable p) {
-        final Parcel parcel = Parcel.obtain();
-        try {
-            p.writeToParcel(parcel, 0);
-            return parcel.marshall();
-        } finally {
-            parcel.recycle();
-        }
+        // Check that the loaded task has the correct runtimes.
+        compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
+                expected.getEarliestRunTime(), actual.getEarliestRunTime());
+        compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
+                expected.getLatestRunTimeElapsed(), actual.getLatestRunTimeElapsed());
     }
 
     /**
@@ -572,5 +715,4 @@
     }
 
     private static class StubClass {}
-
 }
diff --git a/services/tests/servicestests/src/com/android/server/locales/AppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/AppUpdateTrackerTest.java
new file mode 100644
index 0000000..2c5d97d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locales/AppUpdateTrackerTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.locales;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Binder;
+import android.os.LocaleList;
+import android.util.ArraySet;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link AppUpdateTracker}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AppUpdateTrackerTest {
+    private static final String DEFAULT_PACKAGE_NAME = "com.android.myapp";
+    private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
+    private static final int DEFAULT_USER_ID = 0;
+    private static final String DEFAULT_LOCALE_TAGS = "en-XC,ar-XB";
+    private static final LocaleList DEFAULT_LOCALES = LocaleList.forLanguageTags(
+            DEFAULT_LOCALE_TAGS);
+    private AppUpdateTracker mAppUpdateTracker;
+
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private LocaleManagerService mMockLocaleManagerService;
+    @Mock
+    private ShadowLocaleManagerBackupHelper mMockBackupHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockContext = mock(Context.class);
+        mMockLocaleManagerService = mock(LocaleManagerService.class);
+        mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);
+        mAppUpdateTracker = spy(
+                new AppUpdateTracker(mMockContext, mMockLocaleManagerService, mMockBackupHelper));
+    }
+
+    @Test
+    public void testPackageUpgraded_localeEmpty_doNothing() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
+        setUpPackageLocaleConfig(null, DEFAULT_PACKAGE_NAME);
+        setUpAppLocalesOptIn(true);
+
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verifyNoLocalesCleared();
+    }
+
+    @Test
+    public void testPackageUpgraded_pkgNotInSp_doNothing() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        String pkgNameA = "com.android.myAppA";
+        String pkgNameB = "com.android.myAppB";
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB)));
+        setUpPackageLocaleConfig(null, DEFAULT_PACKAGE_NAME);
+        setUpAppLocalesOptIn(true);
+
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verifyNoLocalesCleared();
+    }
+
+    @Test
+    public void testPackageUpgraded_appLocalesSupported_doNothing() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
+        setUpPackageLocaleConfig(DEFAULT_LOCALES, DEFAULT_PACKAGE_NAME);
+
+        setUpAppLocalesOptIn(true);
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verifyNoLocalesCleared();
+
+        setUpAppLocalesOptIn(false);
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verifyNoLocalesCleared();
+
+        setUpAppLocalesOptIn(false);
+        setUpPackageLocaleConfig(null, DEFAULT_PACKAGE_NAME);
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verifyNoLocalesCleared();
+    }
+
+    @Test
+    public void testPackageUpgraded_appLocalesNotSupported_clearAppLocale() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
+        setUpPackageLocaleConfig(null, DEFAULT_PACKAGE_NAME);
+        setUpAppLocalesOptIn(true);
+
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
+
+        setUpPackageLocaleConfig(LocaleList.getEmptyLocaleList(), DEFAULT_PACKAGE_NAME);
+
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verify(mMockLocaleManagerService, times(2)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
+
+        setUpAppLocalesOptIn(false);
+
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verify(mMockLocaleManagerService, times(3)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
+    }
+
+    @Test
+    public void testPackageUpgraded_appLocalesNotInLocaleConfig_clearAppLocale() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
+        setUpPackageLocaleConfig(LocaleList.forLanguageTags("hi,fr"), DEFAULT_PACKAGE_NAME);
+        setUpAppLocalesOptIn(true);
+
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
+
+        setUpAppLocalesOptIn(false);
+
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verify(mMockLocaleManagerService, times(2)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
+    }
+
+    private void setUpLocalesForPackage(String packageName, LocaleList locales) throws Exception {
+        doReturn(locales).when(mMockLocaleManagerService).getApplicationLocales(eq(packageName),
+                anyInt());
+    }
+
+    private void setUpPackageNamesForSp(Set<String> packageNames) {
+        SharedPreferences mockSharedPreference = mock(SharedPreferences.class);
+        doReturn(mockSharedPreference).when(mMockBackupHelper).getPersistedInfo();
+        doReturn(packageNames).when(mockSharedPreference).getStringSet(anyString(), any());
+    }
+
+    private void setUpPackageLocaleConfig(LocaleList locales, String packageName) {
+        doReturn(locales).when(mAppUpdateTracker).getPackageLocales(eq(packageName), anyInt());
+    }
+
+    private void setUpAppLocalesOptIn(boolean optIn) {
+        doReturn(optIn).when(mAppUpdateTracker).isSettingsAppLocalesOptIn();
+    }
+
+    /**
+     * Verifies that no app locales needs to be cleared for any package.
+     *
+     * <p>If {@link LocaleManagerService#setApplicationLocales} is not invoked when receiving the
+     * callback of package upgraded, we can conclude that no app locales needs to be cleared.
+     */
+    private void verifyNoLocalesCleared() throws Exception {
+        verify(mMockLocaleManagerService, times(0)).setApplicationLocales(anyString(), anyInt(),
+                any(), anyBoolean());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 8f0fb0b..4d42afa 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -18,9 +18,11 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -35,6 +37,7 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -44,6 +47,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SimpleClock;
+import android.util.ArraySet;
 import android.util.SparseArray;
 import android.util.Xml;
 
@@ -53,6 +57,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.locales.LocaleManagerBackupHelper.LocalesInfo;
 
 import org.junit.After;
 import org.junit.Before;
@@ -69,9 +74,12 @@
 import java.time.Clock;
 import java.time.Duration;
 import java.time.ZoneOffset;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Unit tests for the {@link LocaleManagerInternal}.
@@ -89,8 +97,8 @@
     private static final Duration RETENTION_PERIOD = Duration.ofDays(3);
     private static final LocaleList DEFAULT_LOCALES =
             LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS);
-    private static final Map<String, String> DEFAULT_PACKAGE_LOCALES_MAP = Map.of(
-            DEFAULT_PACKAGE_NAME, DEFAULT_LOCALE_TAGS);
+    private static final Map<String, LocalesInfo> DEFAULT_PACKAGE_LOCALES_INFO_MAP = Map.of(
+            DEFAULT_PACKAGE_NAME, new LocalesInfo(DEFAULT_LOCALE_TAGS, false));
     private static final SparseArray<LocaleManagerBackupHelper.StagedData> STAGE_DATA =
             new SparseArray<>();
 
@@ -103,6 +111,10 @@
     private PackageManager mMockPackageManager;
     @Mock
     private LocaleManagerService mMockLocaleManagerService;
+    @Mock
+    private SharedPreferences mMockDelegateAppLocalePackages;
+    @Mock
+    private SharedPreferences.Editor mMockSpEditor;
 
     BroadcastReceiver mUserMonitor;
     PackageMonitor mPackageMonitor;
@@ -127,9 +139,13 @@
         mMockContext = mock(Context.class);
         mMockPackageManager = mock(PackageManager.class);
         mMockLocaleManagerService = mock(LocaleManagerService.class);
+        mMockDelegateAppLocalePackages = mock(SharedPreferences.class);
+        mMockSpEditor = mock(SharedPreferences.Editor.class);
         SystemAppUpdateTracker systemAppUpdateTracker = mock(SystemAppUpdateTracker.class);
+        AppUpdateTracker appUpdateTracker = mock(AppUpdateTracker.class);
 
         doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
+        doReturn(mMockSpEditor).when(mMockDelegateAppLocalePackages).edit();
 
         HandlerThread broadcastHandlerThread = new HandlerThread(TAG,
                 Process.THREAD_PRIORITY_BACKGROUND);
@@ -137,12 +153,12 @@
 
         mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext,
                 mMockLocaleManagerService, mMockPackageManager, mClock, STAGE_DATA,
-                broadcastHandlerThread));
+                broadcastHandlerThread, mMockDelegateAppLocalePackages));
         doNothing().when(mBackupHelper).notifyBackupManager();
 
         mUserMonitor = mBackupHelper.getUserMonitor();
         mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
-            systemAppUpdateTracker);
+            systemAppUpdateTracker, appUpdateTracker);
         setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS);
     }
 
@@ -171,9 +187,23 @@
     public void testBackupPayload_appLocalesSet_returnsNonNullBlob() throws Exception {
         setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
         setUpDummyAppForPackageManager(DEFAULT_PACKAGE_NAME);
+        setUpPackageNamesForSp(Collections.<String>emptySet());
 
         byte[] payload = mBackupHelper.getBackupPayload(DEFAULT_USER_ID);
-        verifyPayloadForAppLocales(DEFAULT_PACKAGE_LOCALES_MAP, payload);
+        verifyPayloadForAppLocales(DEFAULT_PACKAGE_LOCALES_INFO_MAP, payload);
+    }
+
+    @Test
+    public void testBackupPayload_appLocalesSet_fromDelegateSelector() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        setUpDummyAppForPackageManager(DEFAULT_PACKAGE_NAME);
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
+        Map<String, LocalesInfo> expectPackageLocalePack = Map.of(DEFAULT_PACKAGE_NAME,
+                new LocalesInfo(DEFAULT_LOCALE_TAGS, true));
+
+        byte[] payload = mBackupHelper.getBackupPayload(DEFAULT_USER_ID);
+
+        verifyPayloadForAppLocales(expectPackageLocalePack, payload);
     }
 
     @Test
@@ -195,14 +225,15 @@
         anotherAppInfo.packageName = "com.android.anotherapp";
         doReturn(List.of(defaultAppInfo, anotherAppInfo)).when(mMockPackageManager)
                 .getInstalledApplicationsAsUser(any(), anyInt());
-
         setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        setUpPackageNamesForSp(Collections.<String>emptySet());
         // Exception when getting locales for anotherApp.
         doThrow(new RemoteException("mock")).when(mMockLocaleManagerService).getApplicationLocales(
                 eq(anotherAppInfo.packageName), anyInt());
 
         byte[] payload = mBackupHelper.getBackupPayload(DEFAULT_USER_ID);
-        verifyPayloadForAppLocales(DEFAULT_PACKAGE_LOCALES_MAP, payload);
+
+        verifyPayloadForAppLocales(DEFAULT_PACKAGE_LOCALES_INFO_MAP, payload);
     }
 
     @Test
@@ -226,8 +257,7 @@
     @Test
     public void testRestore_allAppsInstalled_noStageDataCreated() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
-
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
         setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
         setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
 
@@ -235,38 +265,104 @@
 
         // Locales were restored
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
-                DEFAULT_USER_ID, DEFAULT_LOCALES);
-
+                DEFAULT_USER_ID, DEFAULT_LOCALES, false);
         checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
     @Test
+    public void testRestore_allAppsInstalled_nothingToSp() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>());
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        // Locales were restored
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, DEFAULT_LOCALES, false);
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, false,
+                false);
+
+        verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
+    }
+
+    @Test
+    public void testRestore_allAppsInstalled_storeInfoToSp() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        Map<String, LocalesInfo> pkgLocalesMap = Map.of(DEFAULT_PACKAGE_NAME,
+                new LocalesInfo(DEFAULT_LOCALE_TAGS, true));
+        writeTestPayload(out, pkgLocalesMap);
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>());
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        // Locales were restored
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, DEFAULT_LOCALES, true);
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, true,
+                false);
+
+        verify(mMockSpEditor, times(1)).putStringSet(Integer.toString(DEFAULT_USER_ID),
+                new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
+    }
+
+    @Test
+    public void testRestore_allAppsInstalled_InfoHasExistedInSp() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        Map<String, LocalesInfo> pkgLocalesMap = Map.of(DEFAULT_PACKAGE_NAME,
+                new LocalesInfo(DEFAULT_LOCALE_TAGS, true));
+        writeTestPayload(out, pkgLocalesMap);
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        // Locales were restored
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID, DEFAULT_LOCALES, true);
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, true,
+                false);
+
+        verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
+    }
+
+    @Test
     public void testRestore_noAppsInstalled_everythingStaged() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
-
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
         setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
 
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
         verifyNothingRestored();
-        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP,
                 DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
     }
 
     @Test
     public void testRestore_someAppsInstalled_partiallyStaged() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        HashMap<String, String> pkgLocalesMap = new HashMap<>();
-
+        HashMap<String, LocalesInfo> pkgLocalesMap = new HashMap<>();
         String pkgNameA = "com.android.myAppA";
         String pkgNameB = "com.android.myAppB";
         String langTagsA = "ru";
         String langTagsB = "hi,fr";
-        pkgLocalesMap.put(pkgNameA, langTagsA);
-        pkgLocalesMap.put(pkgNameB, langTagsB);
+        LocalesInfo localesInfoA = new LocalesInfo(langTagsA, true);
+        LocalesInfo localesInfoB = new LocalesInfo(langTagsB, true);
+        pkgLocalesMap.put(pkgNameA, localesInfoA);
+        pkgLocalesMap.put(pkgNameB, localesInfoB);
         writeTestPayload(out, pkgLocalesMap);
-
         setUpPackageInstalled(pkgNameA);
         setUpPackageNotInstalled(pkgNameB);
         setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
@@ -274,9 +370,10 @@
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
-                LocaleList.forLanguageTags(langTagsA));
+                LocaleList.forLanguageTags(langTagsA), true);
 
         pkgLocalesMap.remove(pkgNameA);
+
         verifyStageDataForUser(pkgLocalesMap,
                 DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
     }
@@ -284,8 +381,7 @@
     @Test
     public void testRestore_appLocalesAlreadySet_nothingRestoredAndNoStageData() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
-
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
         setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
         setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.forLanguageTags("hi,mr"));
 
@@ -300,19 +396,20 @@
     public void testRestore_appLocalesSetForSomeApps_restoresOnlyForAppsHavingNoLocalesSet()
             throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        HashMap<String, String> pkgLocalesMap = new HashMap<>();
-
+        HashMap<String, LocalesInfo> pkgLocalesMap = new HashMap<>();
         String pkgNameA = "com.android.myAppA";
         String pkgNameB = "com.android.myAppB";
         String pkgNameC = "com.android.myAppC";
         String langTagsA = "ru";
         String langTagsB = "hi,fr";
         String langTagsC = "zh,es";
-        pkgLocalesMap.put(pkgNameA, langTagsA);
-        pkgLocalesMap.put(pkgNameB, langTagsB);
-        pkgLocalesMap.put(pkgNameC, langTagsC);
+        LocalesInfo localesInfoA = new LocalesInfo(langTagsA, true);
+        LocalesInfo localesInfoB = new LocalesInfo(langTagsB, true);
+        LocalesInfo localesInfoC = new LocalesInfo(langTagsC, true);
+        pkgLocalesMap.put(pkgNameA, localesInfoA);
+        pkgLocalesMap.put(pkgNameB, localesInfoB);
+        pkgLocalesMap.put(pkgNameC, localesInfoC);
         writeTestPayload(out, pkgLocalesMap);
-
         // Both app A & B are installed on the device but A has locales already set.
         setUpPackageInstalled(pkgNameA);
         setUpPackageInstalled(pkgNameB);
@@ -320,20 +417,22 @@
         setUpLocalesForPackage(pkgNameA, LocaleList.forLanguageTags("mr,fr"));
         setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
         setUpLocalesForPackage(pkgNameC, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB, pkgNameC)));
 
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
         // Restore locales only for myAppB.
         verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameA), anyInt(),
-                any());
+                any(), anyBoolean());
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
-                LocaleList.forLanguageTags(langTagsB));
+                LocaleList.forLanguageTags(langTagsB), true);
         verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameC), anyInt(),
-                any());
+                any(), anyBoolean());
 
         // App C is staged.
         pkgLocalesMap.remove(pkgNameA);
         pkgLocalesMap.remove(pkgNameB);
+
         verifyStageDataForUser(pkgLocalesMap,
                 DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
     }
@@ -341,40 +440,41 @@
     @Test
     public void testRestore_restoreInvokedAgain_creationTimeChanged() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
-
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
         setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
 
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
-        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP,
                 DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
 
         final long newCreationTime = DEFAULT_CREATION_TIME_MILLIS + 100;
         setCurrentTimeMillis(newCreationTime);
+
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
-        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP,
                 newCreationTime, DEFAULT_USER_ID);
     }
 
     @Test
     public void testRestore_appInstalledAfterSUW_restoresFromStage() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        HashMap<String, String> pkgLocalesMap = new HashMap<>();
-
+        HashMap<String, LocalesInfo> pkgLocalesMap = new HashMap<>();
         String pkgNameA = "com.android.myAppA";
         String pkgNameB = "com.android.myAppB";
         String langTagsA = "ru";
         String langTagsB = "hi,fr";
-        pkgLocalesMap.put(pkgNameA, langTagsA);
-        pkgLocalesMap.put(pkgNameB, langTagsB);
+        LocalesInfo localesInfoA = new LocalesInfo(langTagsA, false);
+        LocalesInfo localesInfoB = new LocalesInfo(langTagsB, true);
+        pkgLocalesMap.put(pkgNameA, localesInfoA);
+        pkgLocalesMap.put(pkgNameB, localesInfoB);
         writeTestPayload(out, pkgLocalesMap);
-
         setUpPackageNotInstalled(pkgNameA);
         setUpPackageNotInstalled(pkgNameB);
         setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
         setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>());
 
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
@@ -385,9 +485,14 @@
         mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
-                LocaleList.forLanguageTags(langTagsA));
+                LocaleList.forLanguageTags(langTagsA), false);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameA, false, false);
+
+        verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
 
         pkgLocalesMap.remove(pkgNameA);
+
         verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
 
         setUpPackageInstalled(pkgNameB);
@@ -395,7 +500,12 @@
         mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
-                LocaleList.forLanguageTags(langTagsB));
+                LocaleList.forLanguageTags(langTagsB), true);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameB, true, false);
+
+        verify(mMockSpEditor, times(1)).putStringSet(Integer.toString(DEFAULT_USER_ID),
+                new ArraySet<>(Arrays.asList(pkgNameB)));
         checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
@@ -403,15 +513,14 @@
     public void testRestore_appInstalledAfterSUWAndLocalesAlreadySet_restoresNothing()
             throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
-
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
         // Package is not present on the device when the SUW restore is going on.
         setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
 
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
         verifyNothingRestored();
-        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP,
                 DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
 
         // App is installed later (post SUW).
@@ -429,14 +538,13 @@
     public void testStageDataDeletion_backupPassRunAfterRetentionPeriod_stageDataDeleted()
             throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
-
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
         setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
 
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
         verifyNothingRestored();
-        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP,
                 DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
 
         // Retention period has not elapsed.
@@ -444,8 +552,8 @@
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
         doReturn(List.of()).when(mMockPackageManager)
                 .getInstalledApplicationsAsUser(any(), anyInt());
-        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
         checkStageDataExists(DEFAULT_USER_ID);
 
         // Exactly RETENTION_PERIOD amount of time has passed so stage data should still not be
@@ -453,8 +561,8 @@
         setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.toMillis());
         doReturn(List.of()).when(mMockPackageManager)
                 .getInstalledApplicationsAsUser(any(), anyInt());
-        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
         checkStageDataExists(DEFAULT_USER_ID);
 
         // Retention period has now expired, stage data should be deleted.
@@ -462,8 +570,8 @@
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
         doReturn(List.of()).when(mMockPackageManager)
                 .getInstalledApplicationsAsUser(any(), anyInt());
-        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
         checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
@@ -471,16 +579,16 @@
     public void testStageDataDeletion_lazyRestoreAfterRetentionPeriod_stageDataDeleted()
             throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        HashMap<String, String> pkgLocalesMap = new HashMap<>();
-
+        HashMap<String, LocalesInfo> pkgLocalesMap = new HashMap<>();
         String pkgNameA = "com.android.myAppA";
         String pkgNameB = "com.android.myAppB";
         String langTagsA = "ru";
         String langTagsB = "hi,fr";
-        pkgLocalesMap.put(pkgNameA, langTagsA);
-        pkgLocalesMap.put(pkgNameB, langTagsB);
+        LocalesInfo localesInfoA = new LocalesInfo(langTagsA, false);
+        LocalesInfo localesInfoB = new LocalesInfo(langTagsB, false);
+        pkgLocalesMap.put(pkgNameA, localesInfoA);
+        pkgLocalesMap.put(pkgNameB, localesInfoB);
         writeTestPayload(out, pkgLocalesMap);
-
         setUpPackageNotInstalled(pkgNameA);
         setUpPackageNotInstalled(pkgNameB);
         setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
@@ -494,40 +602,40 @@
         // Retention period has not elapsed.
         setCurrentTimeMillis(
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
-
         setUpPackageInstalled(pkgNameA);
+
         mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
 
-        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
-                LocaleList.forLanguageTags(langTagsA));
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(
+                pkgNameA, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsA), false);
 
         pkgLocalesMap.remove(pkgNameA);
+
         verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
 
         // Retention period has now expired, stage data should be deleted.
         setCurrentTimeMillis(
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
         setUpPackageInstalled(pkgNameB);
+
         mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
 
         verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameB), anyInt(),
-                any());
-
+                any(), anyBoolean());
         checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
     @Test
     public void testUserRemoval_userRemoved_stageDataDeleted() throws Exception {
         final ByteArrayOutputStream outDefault = new ByteArrayOutputStream();
-        writeTestPayload(outDefault, DEFAULT_PACKAGE_LOCALES_MAP);
-
+        writeTestPayload(outDefault, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
         final ByteArrayOutputStream outWorkProfile = new ByteArrayOutputStream();
         String anotherPackage = "com.android.anotherapp";
         String anotherLangTags = "mr,zh";
-        HashMap<String, String> pkgLocalesMapWorkProfile = new HashMap<>();
-        pkgLocalesMapWorkProfile.put(anotherPackage, anotherLangTags);
+        LocalesInfo localesInfo = new LocalesInfo(anotherLangTags, false);
+        HashMap<String, LocalesInfo> pkgLocalesMapWorkProfile = new HashMap<>();
+        pkgLocalesMapWorkProfile.put(anotherPackage, localesInfo);
         writeTestPayload(outWorkProfile, pkgLocalesMapWorkProfile);
-
         // DEFAULT_PACKAGE_NAME is NOT installed on the device.
         setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
         setUpPackageNotInstalled(anotherPackage);
@@ -537,8 +645,7 @@
                 WORK_PROFILE_USER_ID);
 
         verifyNothingRestored();
-
-        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP,
                 DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
         verifyStageDataForUser(pkgLocalesMapWorkProfile,
                 DEFAULT_CREATION_TIME_MILLIS, WORK_PROFILE_USER_ID);
@@ -554,6 +661,45 @@
                 DEFAULT_CREATION_TIME_MILLIS, WORK_PROFILE_USER_ID);
     }
 
+    @Test
+    public void testPackageRemoved_noInfoInSp() throws Exception {
+        String pkgNameA = "com.android.myAppA";
+        String pkgNameB = "com.android.myAppB";
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB)));
+
+        mPackageMonitor.onPackageRemoved(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+
+        verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
+    }
+
+    @Test
+    public void testPackageRemoved_removeInfoFromSp() throws Exception {
+        String pkgNameA = "com.android.myAppA";
+        String pkgNameB = "com.android.myAppB";
+        Set<String> pkgNames = new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB));
+        setUpPackageNamesForSp(pkgNames);
+
+        mPackageMonitor.onPackageRemoved(pkgNameA, DEFAULT_UID);
+        pkgNames.remove(pkgNameA);
+
+        verify(mMockSpEditor, times(1)).putStringSet(
+                Integer.toString(DEFAULT_USER_ID), pkgNames);
+    }
+
+    @Test
+    public void testPackageDataCleared_removeInfoFromSp() throws Exception {
+        String pkgNameA = "com.android.myAppA";
+        String pkgNameB = "com.android.myAppB";
+        Set<String> pkgNames = new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB));
+        setUpPackageNamesForSp(pkgNames);
+
+        mPackageMonitor.onPackageDataCleared(pkgNameB, DEFAULT_UID);
+        pkgNames.remove(pkgNameB);
+
+        verify(mMockSpEditor, times(1)).putStringSet(
+                Integer.toString(DEFAULT_USER_ID), pkgNames);
+    }
+
     private void setUpPackageInstalled(String packageName) throws Exception {
         doReturn(new PackageInfo()).when(mMockPackageManager).getPackageInfoAsUser(
                 eq(packageName), anyInt(), anyInt());
@@ -576,6 +722,11 @@
                 .getInstalledApplicationsAsUser(any(), anyInt());
     }
 
+    private void setUpPackageNamesForSp(Set<String> packageNames) {
+        doReturn(packageNames).when(mMockDelegateAppLocalePackages).getStringSet(anyString(),
+                any());
+    }
+
     /**
      * Verifies that nothing was restored for any package.
      *
@@ -584,31 +735,34 @@
      */
     private void verifyNothingRestored() throws Exception {
         verify(mMockLocaleManagerService, times(0)).setApplicationLocales(anyString(), anyInt(),
-                any());
+                any(), anyBoolean());
     }
 
-    private static void verifyPayloadForAppLocales(Map<String, String> expectedPkgLocalesMap,
+    private static void verifyPayloadForAppLocales(Map<String, LocalesInfo> expectedPkgLocalesMap,
             byte[] payload)
             throws IOException, XmlPullParserException {
         final ByteArrayInputStream stream = new ByteArrayInputStream(payload);
         final TypedXmlPullParser parser = Xml.newFastPullParser();
         parser.setInput(stream, StandardCharsets.UTF_8.name());
 
-        Map<String, String> backupDataMap = new HashMap<>();
+        Map<String, LocalesInfo> backupDataMap = new HashMap<>();
         XmlUtils.beginDocument(parser, TEST_LOCALES_XML_TAG);
         int depth = parser.getDepth();
         while (XmlUtils.nextElementWithin(parser, depth)) {
             if (parser.getName().equals("package")) {
                 String packageName = parser.getAttributeValue(null, "name");
                 String languageTags = parser.getAttributeValue(null, "locales");
-                backupDataMap.put(packageName, languageTags);
+                boolean delegateSelector = parser.getAttributeBoolean(null, "delegate_selector");
+                LocalesInfo localesInfo = new LocalesInfo(languageTags, delegateSelector);
+                backupDataMap.put(packageName, localesInfo);
             }
         }
 
-        assertEquals(expectedPkgLocalesMap, backupDataMap);
+        verifyStageData(expectedPkgLocalesMap, backupDataMap);
     }
 
-    private static void writeTestPayload(OutputStream stream, Map<String, String> pkgLocalesMap)
+    private static void writeTestPayload(OutputStream stream,
+            Map<String, LocalesInfo> pkgLocalesMap)
             throws IOException {
         if (pkgLocalesMap.isEmpty()) {
             return;
@@ -622,7 +776,9 @@
         for (String pkg : pkgLocalesMap.keySet()) {
             out.startTag(/* namespace= */ null, "package");
             out.attribute(/* namespace= */ null, "name", pkg);
-            out.attribute(/* namespace= */ null, "locales", pkgLocalesMap.get(pkg));
+            out.attribute(/* namespace= */ null, "locales", pkgLocalesMap.get(pkg).mLocales);
+            out.attributeBoolean(/* namespace= */ null, "delegate_selector",
+                    pkgLocalesMap.get(pkg).mSetFromDelegate);
             out.endTag(/*namespace= */ null, "package");
         }
 
@@ -630,12 +786,23 @@
         out.endDocument();
     }
 
-    private void verifyStageDataForUser(Map<String, String> expectedPkgLocalesMap,
+    private void verifyStageDataForUser(Map<String, LocalesInfo> expectedPkgLocalesMap,
             long expectedCreationTimeMillis, int userId) {
         LocaleManagerBackupHelper.StagedData stagedDataForUser = STAGE_DATA.get(userId);
         assertNotNull(stagedDataForUser);
         assertEquals(expectedCreationTimeMillis, stagedDataForUser.mCreationTimeMillis);
-        assertEquals(expectedPkgLocalesMap, stagedDataForUser.mPackageStates);
+        verifyStageData(expectedPkgLocalesMap, stagedDataForUser.mPackageStates);
+    }
+
+    private static void verifyStageData(Map<String, LocalesInfo> expectedPkgLocalesMap,
+            Map<String, LocalesInfo> stageData) {
+        assertEquals(expectedPkgLocalesMap.size(), stageData.size());
+        for (String pkg : expectedPkgLocalesMap.keySet()) {
+            assertTrue(stageData.containsKey(pkg));
+            assertEquals(expectedPkgLocalesMap.get(pkg).mLocales, stageData.get(pkg).mLocales);
+            assertEquals(expectedPkgLocalesMap.get(pkg).mSetFromDelegate,
+                    stageData.get(pkg).mSetFromDelegate);
+        }
     }
 
     private static void checkStageDataExists(int userId) {
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 dc9f907..79ed7d1 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -133,7 +133,7 @@
 
         try {
             mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
-                    LocaleList.getEmptyLocaleList());
+                    LocaleList.getEmptyLocaleList(), false);
             fail("Expected SecurityException");
         } finally {
             verify(mMockContext).enforceCallingOrSelfPermission(
@@ -148,7 +148,7 @@
     public void testSetApplicationLocales_nullPackageName_fails() throws Exception {
         try {
             mLocaleManagerService.setApplicationLocales(/* appPackageName = */ null,
-                    DEFAULT_USER_ID, LocaleList.getEmptyLocaleList());
+                    DEFAULT_USER_ID, LocaleList.getEmptyLocaleList(), false);
             fail("Expected NullPointerException");
         } finally {
             verify(mMockBackupHelper, times(0)).notifyBackupManager();
@@ -162,7 +162,7 @@
 
         try {
             mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
-                    /* locales = */ null);
+                    /* locales = */ null, false);
             fail("Expected NullPointerException");
         } finally {
             verify(mMockBackupHelper, times(0)).notifyBackupManager();
@@ -180,7 +180,7 @@
         setUpPassingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
 
         mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
-                DEFAULT_LOCALES);
+                DEFAULT_LOCALES, true);
 
         assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
         verify(mMockBackupHelper, times(1)).notifyBackupManager();
@@ -193,7 +193,7 @@
                 .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
 
         mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
-                DEFAULT_LOCALES);
+                DEFAULT_LOCALES, false);
 
         assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
         verify(mMockBackupHelper, times(1)).notifyBackupManager();
@@ -205,7 +205,7 @@
                 .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
         try {
             mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
-                    LocaleList.getEmptyLocaleList());
+                    LocaleList.getEmptyLocaleList(), false);
             fail("Expected IllegalArgumentException");
         } finally {
             assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
diff --git a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
index e403c87..9f7cbe3 100644
--- a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
+++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
@@ -17,6 +17,7 @@
 package com.android.server.locales;
 
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.os.HandlerThread;
 import android.util.SparseArray;
@@ -33,8 +34,8 @@
             LocaleManagerService localeManagerService,
             PackageManager packageManager, Clock clock,
             SparseArray<LocaleManagerBackupHelper.StagedData> stagedData,
-            HandlerThread broadcastHandlerThread) {
+            HandlerThread broadcastHandlerThread, SharedPreferences delegateAppLocalePackages) {
         super(context, localeManagerService, packageManager, clock, stagedData,
-                broadcastHandlerThread);
+                broadcastHandlerThread, delegateAppLocalePackages);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index ee97466..dc0740a 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -135,8 +135,9 @@
         mSystemAppUpdateTracker = new SystemAppUpdateTracker(mMockContext,
             mLocaleManagerService, mStoragefile);
 
-        mPackageMonitor = new LocaleManagerServicePackageMonitor(
-                mockLocaleManagerBackupHelper, mSystemAppUpdateTracker);
+        AppUpdateTracker appUpdateTracker = mock(AppUpdateTracker.class);
+        mPackageMonitor = new LocaleManagerServicePackageMonitor(mockLocaleManagerBackupHelper,
+                mSystemAppUpdateTracker, appUpdateTracker);
     }
 
     @After
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index c934e65..15eaf5d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -33,22 +33,27 @@
 import android.app.admin.DeviceStateCache;
 import android.app.trust.TrustManager;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
-import android.hardware.authsecret.V1_0.IAuthSecret;
+import android.hardware.authsecret.IAuthSecret;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.FileUtils;
 import android.os.IProgressListener;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
+import android.provider.Settings;
 import android.security.KeyStore;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.internal.widget.LockscreenCredential;
@@ -59,6 +64,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.runner.RunWith;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
@@ -106,7 +112,8 @@
     FingerprintManager mFingerprintManager;
     FaceManager mFaceManager;
     PackageManager mPackageManager;
-    FakeSettings mSettings;
+    @Rule
+    public FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
 
     @Before
     public void setUp_baseServices() throws Exception {
@@ -126,7 +133,6 @@
         mFingerprintManager = mock(FingerprintManager.class);
         mFaceManager = mock(FaceManager.class);
         mPackageManager = mock(PackageManager.class);
-        mSettings = new FakeSettings();
 
         LocalServices.removeServiceForTest(LockSettingsInternal.class);
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
@@ -134,12 +140,13 @@
         LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal);
         LocalServices.addService(WindowManagerInternal.class, mMockWindowManager);
 
-        mContext = new MockLockSettingsContext(InstrumentationRegistry.getContext(), mUserManager,
-                mNotificationManager, mDevicePolicyManager, mock(StorageManager.class),
-                mock(TrustManager.class), mock(KeyguardManager.class), mFingerprintManager,
-                mFaceManager, mPackageManager);
+        final Context origContext = InstrumentationRegistry.getContext();
+        mContext = new MockLockSettingsContext(origContext,
+                mSettingsRule.mockContentResolver(origContext), mUserManager, mNotificationManager,
+                mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class),
+                mock(KeyguardManager.class), mFingerprintManager, mFaceManager, mPackageManager);
         mStorage = new LockSettingsStorageTestable(mContext,
-                new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
+                new File(origContext.getFilesDir(), "locksettings"));
         File storageDir = mStorage.mStorageDir;
         if (storageDir.exists()) {
             FileUtils.deleteContents(storageDir);
@@ -153,7 +160,7 @@
         mService = new LockSettingsServiceTestable(mContext, mStorage,
                 mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
                 mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager,
-                mUserManagerInternal, mDeviceStateCache, mSettings);
+                mUserManagerInternal, mDeviceStateCache);
         mService.mHasSecureLockScreen = true;
         when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
         mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
@@ -186,10 +193,25 @@
         mockBiometricsHardwareFingerprintsAndTemplates(PRIMARY_USER_ID);
         mockBiometricsHardwareFingerprintsAndTemplates(MANAGED_PROFILE_USER_ID);
 
-        mSettings.setDeviceProvisioned(true);
+        setDeviceProvisioned(true);
         mLocalService = LocalServices.getService(LockSettingsInternal.class);
     }
 
+    protected void setDeviceProvisioned(boolean provisioned) {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, provisioned ? 1 : 0);
+    }
+
+    protected void setUserSetupComplete(boolean complete) {
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, complete ? 1 : 0, UserHandle.USER_SYSTEM);
+    }
+
+    protected void setSecureFrpMode(boolean secure) {
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.SECURE_FRP_MODE, secure ? 1 : 0, UserHandle.USER_SYSTEM);
+    }
+
     private UserInfo installChildProfile(int profileId) {
         final UserInfo userInfo = new UserInfo(
             profileId, null, null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java b/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java
deleted file mode 100644
index 2bcd653..0000000
--- a/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.locksettings;
-
-import android.content.ContentResolver;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-public class FakeSettings {
-
-    private int mDeviceProvisioned;
-    private int mSecureFrpMode;
-    private int mUserSetupComplete;
-
-    public void setDeviceProvisioned(boolean provisioned) {
-        mDeviceProvisioned = provisioned ? 1 : 0;
-    }
-
-    public void setSecureFrpMode(boolean secure) {
-        mSecureFrpMode = secure ? 1 : 0;
-    }
-
-    public void setUserSetupComplete(boolean complete) {
-        mUserSetupComplete = complete ? 1 : 0;
-    }
-
-    public int globalGetInt(String keyName) {
-        switch (keyName) {
-            case Settings.Global.DEVICE_PROVISIONED:
-                return mDeviceProvisioned;
-            default:
-                throw new IllegalArgumentException("Unhandled global settings: " + keyName);
-        }
-    }
-
-    public int secureGetInt(ContentResolver contentResolver, String keyName, int defaultValue,
-            int userId) {
-        if (Settings.Secure.SECURE_FRP_MODE.equals(keyName) && userId == UserHandle.USER_SYSTEM) {
-            return mSecureFrpMode;
-        }
-        if (Settings.Secure.USER_SETUP_COMPLETE.equals(keyName)
-                && userId == UserHandle.USER_SYSTEM) {
-            return mUserSetupComplete;
-        }
-        return defaultValue;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 85db23c..d3b647d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -20,10 +20,9 @@
 
 import android.app.IActivityManager;
 import android.app.admin.DeviceStateCache;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.UserInfo;
-import android.hardware.authsecret.V1_0.IAuthSecret;
+import android.hardware.authsecret.IAuthSecret;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.Process;
@@ -52,14 +51,12 @@
         private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
         private UserManagerInternal mUserManagerInternal;
         private DeviceStateCache mDeviceStateCache;
-        private FakeSettings mSettings;
 
         public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
                 IActivityManager activityManager,
                 IStorageManager storageManager, SyntheticPasswordManager spManager,
                 FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
-                UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache,
-                FakeSettings settings) {
+                UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
             super(context);
             mLockSettingsStorage = storage;
             mKeyStore = keyStore;
@@ -70,7 +67,6 @@
             mRecoverableKeyStoreManager = recoverableKeyStoreManager;
             mUserManagerInternal = userManagerInternal;
             mDeviceStateCache = deviceStateCache;
-            mSettings = settings;
         }
 
         @Override
@@ -119,18 +115,6 @@
         }
 
         @Override
-        public int settingsGlobalGetInt(ContentResolver contentResolver, String keyName,
-                int defaultValue) {
-            return mSettings.globalGetInt(keyName);
-        }
-
-        @Override
-        public int settingsSecureGetInt(ContentResolver contentResolver, String keyName,
-                int defaultValue, int userId) {
-            return mSettings.secureGetInt(contentResolver, keyName, defaultValue, userId);
-        }
-
-        @Override
         public UserManagerInternal getUserManagerInternal() {
             return mUserManagerInternal;
         }
@@ -165,13 +149,12 @@
             IStorageManager storageManager, IActivityManager mActivityManager,
             SyntheticPasswordManager spManager, IAuthSecret authSecretService,
             FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
-            UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache,
-            FakeSettings settings) {
+            UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
         super(new MockInjector(context, storage, keystore, mActivityManager,
-                storageManager, spManager, gsiService,
-                recoverableKeyStoreManager, userManagerInternal, deviceStateCache, settings));
+                storageManager, spManager, gsiService, recoverableKeyStoreManager,
+                userManagerInternal, deviceStateCache));
         mGateKeeperService = gatekeeper;
-        mAuthSecretService = authSecretService;
+        mAuthSecretServiceAidl = authSecretService;
     }
 
     @Override
@@ -216,4 +199,4 @@
         UserInfo userInfo = mUserManager.getUserInfo(userId);
         return userInfo.isCloneProfile() || userInfo.isManagedProfile();
     }
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 3f259e3..196226a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -424,8 +424,8 @@
 
     @Test
     public void testCredentialChangeNotPossibleInSecureFrpModeDuringSuw() {
-        mSettings.setUserSetupComplete(false);
-        mSettings.setSecureFrpMode(true);
+        setUserSetupComplete(false);
+        setSecureFrpMode(true);
         try {
             mService.setLockCredential(newPassword("1234"), nonePassword(), PRIMARY_USER_ID);
             fail("Password shouldn't be changeable before FRP unlock");
@@ -434,8 +434,8 @@
 
     @Test
     public void testCredentialChangePossibleInSecureFrpModeAfterSuw() {
-        mSettings.setUserSetupComplete(true);
-        mSettings.setSecureFrpMode(true);
+        setUserSetupComplete(true);
+        setSecureFrpMode(true);
         assertTrue(mService.setLockCredential(newPassword("1234"), nonePassword(),
                 PRIMARY_USER_ID));
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index a663858..95d0e15 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -31,6 +31,7 @@
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
+import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.database.sqlite.SQLiteDatabase;
@@ -49,11 +50,14 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.locksettings.LockSettingsStorage.PersistentData;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -78,14 +82,17 @@
 
     public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 33};
 
-    LockSettingsStorageTestable mStorage;
-    File mStorageDir;
-
+    private LockSettingsStorageTestable mStorage;
+    private File mStorageDir;
     private File mDb;
+    @Rule
+    public FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
 
     @Before
     public void setUp() throws Exception {
-        mStorageDir = new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings");
+        final Context origContext = InstrumentationRegistry.getContext();
+
+        mStorageDir = new File(origContext.getFilesDir(), "locksettings");
         mDb = InstrumentationRegistry.getContext().getDatabasePath("locksettings.db");
 
         assertTrue(mStorageDir.exists() || mStorageDir.mkdirs());
@@ -98,8 +105,8 @@
         // User 3 is a profile of user 0.
         when(mockUserManager.getProfileParent(eq(3))).thenReturn(new UserInfo(0, "name", 0));
 
-        MockLockSettingsContext context = new MockLockSettingsContext(
-                InstrumentationRegistry.getContext(), mockUserManager,
+        MockLockSettingsContext context = new MockLockSettingsContext(origContext,
+                mSettingsRule.mockContentResolver(origContext), mockUserManager,
                 mock(NotificationManager.class), mock(DevicePolicyManager.class),
                 mock(StorageManager.class), mock(TrustManager.class), mock(KeyguardManager.class),
                 mock(FingerprintManager.class), mock(FaceManager.class),
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
index c2f94e2..fc0ca7e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
@@ -44,7 +44,7 @@
     @Before
     public void setDeviceNotProvisioned() throws Exception {
         // FRP credential can only be verified prior to provisioning
-        mSettings.setDeviceProvisioned(false);
+        setDeviceProvisioned(false);
     }
 
     @Before
@@ -98,6 +98,7 @@
         mService.setLockCredential(newPassword("1234"), nonePassword(), PRIMARY_USER_ID);
         assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(USER_FRP));
 
+        setDeviceProvisioned(true);
         mService.setLockCredential(nonePassword(), newPassword("1234"), PRIMARY_USER_ID);
         assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(USER_FRP));
     }
@@ -106,7 +107,7 @@
     public void testFrpCredential_cannotVerifyAfterProvsioning() {
         mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
 
-        mSettings.setDeviceProvisioned(true);
+        setDeviceProvisioned(true);
         assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
                 mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */)
                         .getResponseCode());
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
index efa1b04..21c367b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
@@ -21,6 +21,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
@@ -35,22 +36,25 @@
 
 public class MockLockSettingsContext extends ContextWrapper {
 
-    private UserManager mUserManager;
-    private NotificationManager mNotificationManager;
-    private DevicePolicyManager mDevicePolicyManager;
-    private StorageManager mStorageManager;
-    private TrustManager mTrustManager;
-    private KeyguardManager mKeyguardManager;
-    private FingerprintManager mFingerprintManager;
-    private FaceManager mFaceManager;
-    private PackageManager mPackageManager;
+    private final ContentResolver mContentResolver;
+    private final UserManager mUserManager;
+    private final NotificationManager mNotificationManager;
+    private final DevicePolicyManager mDevicePolicyManager;
+    private final StorageManager mStorageManager;
+    private final TrustManager mTrustManager;
+    private final KeyguardManager mKeyguardManager;
+    private final FingerprintManager mFingerprintManager;
+    private final FaceManager mFaceManager;
+    private final PackageManager mPackageManager;
 
-    public MockLockSettingsContext(Context base, UserManager userManager,
-            NotificationManager notificationManager, DevicePolicyManager devicePolicyManager,
-            StorageManager storageManager, TrustManager trustManager,
-            KeyguardManager keyguardManager, FingerprintManager fingerprintManager,
-            FaceManager faceManager, PackageManager packageManager) {
+    public MockLockSettingsContext(Context base, ContentResolver contentResolver,
+            UserManager userManager, NotificationManager notificationManager,
+            DevicePolicyManager devicePolicyManager, StorageManager storageManager,
+            TrustManager trustManager, KeyguardManager keyguardManager,
+            FingerprintManager fingerprintManager, FaceManager faceManager,
+            PackageManager packageManager) {
         super(base);
+        mContentResolver = contentResolver;
         mUserManager = userManager;
         mNotificationManager = notificationManager;
         mDevicePolicyManager = devicePolicyManager;
@@ -63,6 +67,11 @@
     }
 
     @Override
+    public ContentResolver getContentResolver() {
+        return mContentResolver;
+    }
+
+    @Override
     public Object getSystemService(String name) {
         if (USER_SERVICE.equals(name)) {
             return mUserManager;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java
index 1e855a9..1eb4fa5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java
@@ -58,4 +58,4 @@
         } catch (Exception e) {
         }
     }
-};
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 3f4bec6..a441824 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -59,6 +59,7 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Arrays;
 
 
 /**
@@ -136,6 +137,43 @@
         assertTrue(mService.isSyntheticPasswordBasedCredential(userId));
     }
 
+    protected void initializeSyntheticPassword(int userId) {
+        synchronized (mService.mSpManager) {
+            mService.initializeSyntheticPasswordLocked(userId);
+        }
+    }
+
+    // Tests that the FRP credential is updated when an LSKF-based protector is created for the user
+    // that owns the FRP credential, if the device is already provisioned.
+    @Test
+    public void testFrpCredentialSyncedIfDeviceProvisioned() throws RemoteException {
+        setDeviceProvisioned(true);
+        initializeSyntheticPassword(PRIMARY_USER_ID);
+        verify(mStorage.mPersistentDataBlockManager).setFrpCredentialHandle(any());
+    }
+
+    // Tests that the FRP credential is not updated when an LSKF-based protector is created for the
+    // user that owns the FRP credential, if the new credential is empty and the device is not yet
+    // provisioned.
+    @Test
+    public void testEmptyFrpCredentialNotSyncedIfDeviceNotProvisioned() throws RemoteException {
+        setDeviceProvisioned(false);
+        initializeSyntheticPassword(PRIMARY_USER_ID);
+        verify(mStorage.mPersistentDataBlockManager, never()).setFrpCredentialHandle(any());
+    }
+
+    // Tests that the FRP credential is updated when an LSKF-based protector is created for the user
+    // that owns the FRP credential, if the new credential is nonempty and the device is not yet
+    // provisioned.
+    @Test
+    public void testNonEmptyFrpCredentialSyncedIfDeviceNotProvisioned() throws RemoteException {
+        setDeviceProvisioned(false);
+        initializeSyntheticPassword(PRIMARY_USER_ID);
+        verify(mStorage.mPersistentDataBlockManager, never()).setFrpCredentialHandle(any());
+        mService.setLockCredential(newPassword("password"), nonePassword(), PRIMARY_USER_ID);
+        verify(mStorage.mPersistentDataBlockManager).setFrpCredentialHandle(any());
+    }
+
     @Test
     public void testChangeCredential() throws RemoteException {
         final LockscreenCredential password = newPassword("password");
@@ -192,9 +230,11 @@
                 badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
 
         // Check the same secret was passed each time
-        ArgumentCaptor<ArrayList<Byte>> secret = ArgumentCaptor.forClass(ArrayList.class);
-        verify(mAuthSecretService, atLeastOnce()).primaryUserCredential(secret.capture());
-        assertEquals(1, secret.getAllValues().stream().distinct().count());
+        ArgumentCaptor<byte[]> secret = ArgumentCaptor.forClass(byte[].class);
+        verify(mAuthSecretService, atLeastOnce()).setPrimaryUserCredential(secret.capture());
+        for (byte[] val : secret.getAllValues()) {
+          assertArrayEquals(val, secret.getAllValues().get(0));
+        }
     }
 
     @Test
@@ -205,7 +245,7 @@
         reset(mAuthSecretService);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
-        verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
+        verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
     }
 
     @Test
@@ -215,7 +255,7 @@
         initializeCredential(password, SECONDARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, SECONDARY_USER_ID, 0 /* flags */).getResponseCode());
-        verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
+        verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
     }
 
     @Test
@@ -226,7 +266,7 @@
 
         reset(mAuthSecretService);
         mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
-        verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
+        verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
     }
 
     @Test
@@ -554,7 +594,7 @@
         initializeCredential(password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
-        verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
+        verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
index a3ac515..6c13a6f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
@@ -1,11 +1,17 @@
 package com.android.server.locksettings;
 
+import static org.junit.Assert.assertEquals;
+
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+import com.google.android.collect.Sets;
+
 import org.junit.Before;
+import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @SmallTest
@@ -17,4 +23,36 @@
     public void enableWeaver() throws Exception {
         mSpManager.enableWeaver();
     }
+
+    // Tests that if the device is not yet provisioned and the FRP credential uses Weaver, then the
+    // Weaver slot of the FRP credential is not reused.  Assumes that Weaver slots are allocated
+    // sequentially, starting at slot 0.
+    @Test
+    public void testFrpWeaverSlotNotReused() {
+        final int userId = 10;
+        final int frpWeaverSlot = 0;
+
+        setDeviceProvisioned(false);
+        assertEquals(Sets.newHashSet(), mPasswordSlotManager.getUsedSlots());
+        mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, frpWeaverSlot, 0,
+                new byte[1]);
+        initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
+        assertEquals(Sets.newHashSet(1), mPasswordSlotManager.getUsedSlots());
+    }
+
+    // Tests that if the device is already provisioned and the FRP credential uses Weaver, then the
+    // Weaver slot of the FRP credential is reused.  This is not a very interesting test by itself;
+    // it's here as a control for testFrpWeaverSlotNotReused().
+    @Test
+    public void testFrpWeaverSlotReused() {
+        final int userId = 10;
+        final int frpWeaverSlot = 0;
+
+        setDeviceProvisioned(true);
+        assertEquals(Sets.newHashSet(), mPasswordSlotManager.getUsedSlots());
+        mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, frpWeaverSlot, 0,
+                new byte[1]);
+        initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
+        assertEquals(Sets.newHashSet(0), mPasswordSlotManager.getUsedSlots());
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java
index ea3c5fa..d9ebb4c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java
@@ -62,7 +62,7 @@
                 context.unregisterReceiver(this);
                 latch.countDown();
             }
-        }, new IntentFilter(TEST_INTENT_ACTION));
+        }, new IntentFilter(TEST_INTENT_ACTION), Context.RECEIVER_EXPORTED_UNAUDITED);
 
         mStorage.setSnapshotListener(recoveryAgentUid, intent);
 
@@ -83,7 +83,8 @@
                 latch.countDown();
             }
         };
-        context.registerReceiver(broadcastReceiver, new IntentFilter(TEST_INTENT_ACTION));
+        context.registerReceiver(broadcastReceiver, new IntentFilter(TEST_INTENT_ACTION),
+                Context.RECEIVER_EXPORTED_UNAUDITED);
 
         mStorage.setSnapshotListener(recoveryAgentUid, intent);
         mStorage.setSnapshotListener(recoveryAgentUid, intent);
diff --git a/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java b/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java
new file mode 100644
index 0000000..1c4ee69
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.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 com.android.server.media;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaButtonReceiverHolderTest {
+
+    @Test
+    public void createMediaButtonReceiverHolder_resolvesNullComponentName() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+        PendingIntent pi = PendingIntent.getBroadcast(context, /* requestCode= */ 0, intent,
+                PendingIntent.FLAG_IMMUTABLE);
+        MediaButtonReceiverHolder a = MediaButtonReceiverHolder.create(/* userId= */ 0, pi,
+                context.getPackageName());
+        Truth.assertWithMessage("Component name must match PendingIntent creator package.").that(
+                a.getComponentName()).isNull();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/OWNERS b/services/tests/servicestests/src/com/android/server/media/OWNERS
new file mode 100644
index 0000000..55ffde2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137631
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
\ No newline at end of file
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..578b888 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();
@@ -324,4 +323,19 @@
         FakeIdmapDaemon.IdmapHeader idmap = idmapd.getIdmap(overlayPath);
         assertEquals(0, CONFIG_SIGNATURE & idmap.policies);
     }
+
+    @Test
+    public void testOnTargetSystemPackageUninstall() throws Exception {
+        installAndAssert(target(TARGET), USER,
+                Set.of(UserPackage.of(USER, TARGET)));
+        installAndAssert(overlay(OVERLAY, TARGET), USER,
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
+        upgradeAndAssert(target(TARGET), USER,
+                Set.of(UserPackage.of(USER, TARGET)),
+                Set.of(UserPackage.of(USER, TARGET)));
+
+        downgradeAndAssert(target(TARGET), USER,
+                Set.of(UserPackage.of(USER, TARGET)),
+                Set.of(UserPackage.of(USER, TARGET)));
+    }
 }
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..dab4335 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -24,10 +24,12 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
+import android.content.Intent;
 import android.content.om.OverlayIdentifier;
 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;
@@ -159,12 +161,12 @@
      * Adds the package to the device.
      *
      * This corresponds to when the OMS receives the
-     * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast.
+     * {@link Intent#ACTION_PACKAGE_ADDED} broadcast.
      *
      * @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");
@@ -177,23 +179,51 @@
      * Begins upgrading the package.
      *
      * This corresponds to when the OMS receives the
-     * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast with the
-     * {@link android.content.Intent#EXTRA_REPLACING} extra and then receives the
-     * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the
-     * {@link android.content.Intent#EXTRA_REPLACING} extra.
+     * {@link Intent#ACTION_PACKAGE_REMOVED} broadcast with the
+     * {@link Intent#EXTRA_REPLACING} extra and then receives the
+     * {@link Intent#ACTION_PACKAGE_ADDED} broadcast with the
+     * {@link Intent#EXTRA_REPLACING} extra.
      *
      * @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) {
             throw new IllegalStateException("package " + pkg.packageName + " not installed");
         }
 
-        assertEquals(onReplacingUpdatedPackages, mImpl.onPackageReplacing(pkg.packageName, userId));
+        assertEquals(onReplacingUpdatedPackages, mImpl.onPackageReplacing(pkg.packageName,
+                /* systemUpdateUninstall */ false, userId));
+        mState.add(pkg, userId);
+        assertEquals(onReplacedUpdatedPackages, mImpl.onPackageReplaced(pkg.packageName, userId));
+    }
+
+    /**
+     * Begins downgrading the package. Usually used simulating a system uninstall of its /data
+     * variant.
+     *
+     * This corresponds to when the OMS receives the
+     * {@link Intent#ACTION_PACKAGE_REMOVED} broadcast with the
+     * {@link Intent#EXTRA_REPLACING} and {@link Intent#EXTRA_SYSTEM_UPDATE_UNINSTALL} extras
+     * and then receives the {@link Intent#ACTION_PACKAGE_ADDED} broadcast with the
+     * {@link Intent#EXTRA_REPLACING} extra.
+     *
+     * @throws IllegalStateException if the package is not currently installed
+     */
+    void downgradeAndAssert(FakeDeviceState.PackageBuilder pkg, int userId,
+            @NonNull Set<UserPackage> onReplacingUpdatedPackages,
+            @NonNull Set<UserPackage> onReplacedUpdatedPackages)
+            throws OperationFailedException {
+        final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
+        if (replacedPackage == null) {
+            throw new IllegalStateException("package " + pkg.packageName + " not installed");
+        }
+
+        assertEquals(onReplacingUpdatedPackages, mImpl.onPackageReplacing(pkg.packageName,
+                /* systemUpdateUninstall */ true, userId));
         mState.add(pkg, userId);
         assertEquals(onReplacedUpdatedPackages, mImpl.onPackageReplaced(pkg.packageName, userId));
     }
@@ -202,12 +232,12 @@
      * Removes the package from the device.
      *
      * This corresponds to when the OMS receives the
-     * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast.
+     * {@link Intent#ACTION_PACKAGE_REMOVED} broadcast.
      *
      * @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/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
deleted file mode 100644
index a7739ed..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ /dev/null
@@ -1,599 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-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.anyString;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-import android.apex.ApexInfo;
-import android.apex.ApexSessionInfo;
-import android.apex.ApexSessionParams;
-import android.apex.IApexService;
-import android.content.Context;
-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 org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.List;
-
-@SmallTest
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-
-public class ApexManagerTest {
-    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);
-
-    @Before
-    public void setUp() throws RemoteException {
-        mContext = InstrumentationRegistry.getInstrumentation().getContext();
-        ApexManager.ApexManagerImpl managerImpl = spy(new ApexManager.ApexManagerImpl());
-        doReturn(mApexService).when(managerImpl).waitForApexService();
-        mApexManager = managerImpl;
-        mPackageParser2 = new TestPackageParser2();
-    }
-
-    @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);
-
-        assertThat(activePair).isNotNull();
-        assertThat(activePair.second.getPackageName()).contains(TEST_APEX_PKG);
-
-        final var factoryPair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
-                ApexManager.MATCH_FACTORY_PACKAGE);
-
-        assertThat(factoryPair).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);
-
-        assertThat(factoryPair).isNotNull();
-        assertThat(factoryPair.second.getPackageName()).contains(TEST_APEX_PKG);
-
-        final var activePair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
-                ApexManager.MATCH_ACTIVE_PACKAGE);
-
-        assertThat(activePair).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[] {
-                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());
-        mApexManager.notifyScanResult(scanResults);
-
-        List<ApexSystemServiceInfo> services = mApexManager.getApexSystemServices();
-        assertThat(services).hasSize(1);
-        assertThat(services.stream().map(ApexSystemServiceInfo::getName).findFirst().orElse(null))
-                .matches("com.android.apex.test.ApexSystemService");
-    }
-
-    @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();
-    }
-
-    @Test
-    public void testIsApexSupported() {
-        assertThat(mApexManager.isApexSupported()).isTrue();
-    }
-
-    @Test
-    public void testGetStagedSessionInfo() throws RemoteException {
-        when(mApexService.getStagedSessionInfo(anyInt())).thenReturn(
-                getFakeStagedSessionInfo());
-
-        mApexManager.getStagedSessionInfo(TEST_SESSION_ID);
-        verify(mApexService, times(1)).getStagedSessionInfo(TEST_SESSION_ID);
-    }
-
-    @Test
-    public void testGetStagedSessionInfo_unKnownStagedSessionId() throws RemoteException {
-        when(mApexService.getStagedSessionInfo(anyInt())).thenReturn(
-                getFakeUnknownSessionInfo());
-
-        assertThat(mApexManager.getStagedSessionInfo(TEST_SESSION_ID)).isNull();
-    }
-
-    @Test
-    public void testSubmitStagedSession_throwPackageManagerException() throws RemoteException {
-        doAnswer(invocation -> {
-            throw new Exception();
-        }).when(mApexService).submitStagedSession(any(), any());
-
-        assertThrows(PackageManagerException.class,
-                () -> mApexManager.submitStagedSession(testParamsWithChildren()));
-    }
-
-    @Test
-    public void testSubmitStagedSession_throwRunTimeException() throws RemoteException {
-        doThrow(RemoteException.class).when(mApexService).submitStagedSession(any(), any());
-
-        assertThrows(RuntimeException.class,
-                () -> mApexManager.submitStagedSession(testParamsWithChildren()));
-    }
-
-    @Test
-    public void testGetStagedApexInfos_throwRunTimeException() throws RemoteException {
-        doThrow(RemoteException.class).when(mApexService).getStagedApexInfos(any());
-
-        assertThrows(RuntimeException.class,
-                () -> mApexManager.getStagedApexInfos(testParamsWithChildren()));
-    }
-
-    @Test
-    public void testGetStagedApexInfos_returnsEmptyArrayOnError() throws RemoteException {
-        doThrow(ServiceSpecificException.class).when(mApexService).getStagedApexInfos(any());
-
-        assertThat(mApexManager.getStagedApexInfos(testParamsWithChildren())).hasLength(0);
-    }
-
-    @Test
-    public void testMarkStagedSessionReady_throwPackageManagerException() throws RemoteException {
-        doAnswer(invocation -> {
-            throw new Exception();
-        }).when(mApexService).markStagedSessionReady(anyInt());
-
-        assertThrows(PackageManagerException.class,
-                () -> mApexManager.markStagedSessionReady(TEST_SESSION_ID));
-    }
-
-    @Test
-    public void testMarkStagedSessionReady_throwRunTimeException() throws RemoteException {
-        doThrow(RemoteException.class).when(mApexService).markStagedSessionReady(anyInt());
-
-        assertThrows(RuntimeException.class,
-                () -> mApexManager.markStagedSessionReady(TEST_SESSION_ID));
-    }
-
-    @Test
-    public void testRevertActiveSessions_remoteException() throws RemoteException {
-        doThrow(RemoteException.class).when(mApexService).revertActiveSessions();
-
-        try {
-            assertThat(mApexManager.revertActiveSessions()).isFalse();
-        } catch (Exception e) {
-            throw new AssertionError("ApexManager should not raise Exception");
-        }
-    }
-
-    @Test
-    public void testMarkStagedSessionSuccessful_throwRemoteException() throws RemoteException {
-        doThrow(RemoteException.class).when(mApexService).markStagedSessionSuccessful(anyInt());
-
-        assertThrows(RuntimeException.class,
-                () -> mApexManager.markStagedSessionSuccessful(TEST_SESSION_ID));
-    }
-
-    @Test
-    public void testUninstallApex_throwException_returnFalse() throws RemoteException {
-        doAnswer(invocation -> {
-            throw new Exception();
-        }).when(mApexService).unstagePackages(any());
-
-        assertThat(mApexManager.uninstallApex(TEST_APEX_PKG)).isFalse();
-    }
-
-    @Test
-    public void testReportErrorWithApkInApex() throws RemoteException {
-        when(mApexService.getActivePackages()).thenReturn(createApexInfoForTestPkg(true, true));
-        final ApexManager.ActiveApexInfo activeApex = mApexManager.getActiveApexInfos().get(0);
-        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());
-        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");
-    }
-
-    /**
-     * registerApkInApex method checks if the prefix of base apk path contains the apex package
-     * name. When an apex package name is a prefix of another apex package name, e.g,
-     * com.android.media and com.android.mediaprovider, then we need to ensure apk inside apex
-     * mediaprovider does not get registered under apex media.
-     */
-    @Test
-    public void testRegisterApkInApexDoesNotRegisterSimilarPrefix() throws RemoteException {
-        when(mApexService.getActivePackages()).thenReturn(createApexInfoForTestPkg(true, true));
-        final ApexManager.ActiveApexInfo activeApex = mApexManager.getActiveApexInfos().get(0);
-        assertThat(activeApex.apexModuleName).isEqualTo(TEST_APEX_PKG);
-
-        AndroidPackage fakeApkInApex = mock(AndroidPackage.class);
-        when(fakeApkInApex.getBaseApkPath()).thenReturn("/apex/" + TEST_APEX_PKG + "randomSuffix");
-        when(fakeApkInApex.getPackageName()).thenReturn("randomPackageName");
-
-        ApexInfo[] apexInfo = createApexInfoForTestPkg(true, true);
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        List<ApexManager.ScanResult> scanResults = apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
-        mApexManager.notifyScanResult(scanResults);
-
-        assertThat(mApexManager.getApksInApex(activeApex.apexModuleName)).isEmpty();
-        mApexManager.registerApkInApex(fakeApkInApex);
-        assertThat(mApexManager.getApksInApex(activeApex.apexModuleName)).isEmpty();
-    }
-
-    @Test
-    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"));
-        ApexInfo[] apexInfo = new ApexInfo[]{activeApexInfo};
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
-
-        File finalApex = extractResource("test.rebootles_apex_v2", "test.rebootless_apex_v2.apex");
-        ApexInfo newApexInfo = createApexInfo("test.apex_rebootless", 2, /* isActive= */ true,
-                /* isFactory= */ false, finalApex);
-        when(mApexService.installAndActivatePackage(anyString())).thenReturn(newApexInfo);
-
-        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 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();
-    }
-
-    @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"));
-        ApexInfo activeApexInfo = createApexInfo("test.apex_rebootless", 1, /* isActive= */ true,
-                /* isFactory= */ false, extractResource("test.apex.rebootless@1",
-                  "test.rebootless_apex_v1.apex"));
-        ApexInfo[] apexInfo = new ApexInfo[]{factoryApexInfo, activeApexInfo};
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
-
-        File finalApex = extractResource("test.rebootles_apex_v2", "test.rebootless_apex_v2.apex");
-        ApexInfo newApexInfo = createApexInfo("test.apex_rebootless", 2, /* isActive= */ true,
-                /* isFactory= */ false, finalApex);
-        when(mApexService.installAndActivatePackage(anyString())).thenReturn(newApexInfo);
-
-        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 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();
-    }
-
-    @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 :("));
-
-        File installedApex = extractResource("test.apex_rebootless_v1",
-                "test.rebootless_apex_v1.apex");
-        assertThrows(PackageManagerException.class,
-                () -> mApexManager.installPackage(installedApex));
-    }
-
-    @Test
-    public void testGetActivePackageNameForApexModuleName() throws Exception {
-        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());
-        mApexManager.notifyScanResult(scanResults);
-
-        assertThat(mApexManager.getActivePackageNameForApexModuleName(moduleName))
-                .isEqualTo(TEST_APEX_PKG);
-    }
-
-    @Test
-    public void testGetBackingApexFiles() throws Exception {
-        final ApexInfo apex = createApexInfoForTestPkg(true, true, 37);
-        when(mApexService.getActivePackages()).thenReturn(new ApexInfo[]{apex});
-
-        final File backingApexFile = mApexManager.getBackingApexFile(
-                new File("/apex/" + TEST_APEX_PKG + "/apk/App/App.apk"));
-        assertThat(backingApexFile.getAbsolutePath()).isEqualTo(apex.modulePath);
-    }
-
-    @Test
-    public void testGetBackingApexFile_fileNotOnApexMountPoint_returnsNull() throws Exception {
-        File result = mApexManager.getBackingApexFile(
-                new File("/data/local/tmp/whatever/does-not-matter"));
-        assertThat(result).isNull();
-    }
-
-    @Test
-    public void testGetBackingApexFiles_unknownApex_returnsNull() throws Exception {
-        final ApexInfo apex = createApexInfoForTestPkg(true, true, 37);
-        when(mApexService.getActivePackages()).thenReturn(new ApexInfo[]{apex});
-
-        final File backingApexFile = mApexManager.getBackingApexFile(
-                new File("/apex/com.wrong.apex/apk/App"));
-        assertThat(backingApexFile).isNull();
-    }
-
-    @Test
-    public void testGetBackingApexFiles_topLevelApexDir_returnsNull() throws Exception {
-        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 {
-        ApexManager flattenedApexManager = new ApexManager.ApexManagerFlattenedApex();
-        final File backingApexFile = flattenedApexManager.getBackingApexFile(
-                new File("/apex/com.android.apex.cts.shim/app/CtsShim/CtsShim.apk"));
-        assertThat(backingApexFile).isNull();
-    }
-
-    @Test
-    public void testActiveApexChanged() throws RemoteException {
-        ApexInfo apex1 = createApexInfo(
-                "com.apex1", 37, true, true, new File("/data/apex/active/com.apex@37.apex"));
-        apex1.activeApexChanged = true;
-        apex1.preinstalledModulePath = apex1.modulePath;
-        when(mApexService.getActivePackages()).thenReturn(new ApexInfo[]{apex1});
-        final ApexManager.ActiveApexInfo activeApex = mApexManager.getActiveApexInfos().get(0);
-        assertThat(activeApex.apexModuleName).isEqualTo("com.apex1");
-        assertThat(activeApex.activeApexChanged).isTrue();
-    }
-
-    private ApexInfo createApexInfoForTestPkg(boolean isActive, boolean isFactory, int version) {
-        File apexFile = extractResource(TEST_APEX_PKG,  TEST_APEX_FILE_NAME);
-        ApexInfo apexInfo = new ApexInfo();
-        apexInfo.isActive = isActive;
-        apexInfo.isFactory = isFactory;
-        apexInfo.moduleName = TEST_APEX_PKG;
-        apexInfo.modulePath = apexFile.getPath();
-        apexInfo.versionCode = version;
-        apexInfo.preinstalledModulePath = apexFile.getPath();
-        return apexInfo;
-    }
-
-    private ApexInfo[] createApexInfoForTestPkg(boolean isActive, boolean isFactory) {
-        return new ApexInfo[]{createApexInfoForTestPkg(isActive, isFactory, 191000070)};
-    }
-
-    private ApexInfo createApexInfo(String moduleName, int versionCode, boolean isActive,
-            boolean isFactory, File apexFile) {
-        ApexInfo apexInfo = new ApexInfo();
-        apexInfo.moduleName = moduleName;
-        apexInfo.versionCode = versionCode;
-        apexInfo.isActive = isActive;
-        apexInfo.isFactory = isFactory;
-        apexInfo.modulePath = apexFile.getPath();
-        return apexInfo;
-    }
-
-    private ApexSessionInfo getFakeStagedSessionInfo() {
-        ApexSessionInfo stagedSessionInfo = new ApexSessionInfo();
-        stagedSessionInfo.sessionId = TEST_SESSION_ID;
-        stagedSessionInfo.isStaged = true;
-
-        return stagedSessionInfo;
-    }
-
-    private ApexSessionInfo getFakeUnknownSessionInfo() {
-        ApexSessionInfo stagedSessionInfo = new ApexSessionInfo();
-        stagedSessionInfo.sessionId = TEST_SESSION_ID;
-        stagedSessionInfo.isUnknown = true;
-
-        return stagedSessionInfo;
-    }
-
-    private static ApexSessionParams testParamsWithChildren() {
-        ApexSessionParams params = new ApexSessionParams();
-        params.sessionId = TEST_SESSION_ID;
-        params.childSessionIds = TEST_CHILD_SESSION_ID;
-        return params;
-    }
-
-    // Extracts the binary data from a resource and writes it to a temp file
-    private static File extractResource(String baseName, String fullResourceName) {
-        File file;
-        try {
-            file = File.createTempFile(baseName, ".apex");
-        } catch (IOException e) {
-            throw new AssertionError("CreateTempFile IOException" + e);
-        }
-
-        try (
-                InputStream in = ApexManager.class.getClassLoader()
-                        .getResourceAsStream(fullResourceName);
-                OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
-            if (in == null) {
-                throw new IllegalArgumentException("Resource not found: " + fullResourceName);
-            }
-            byte[] buf = new byte[65536];
-            int chunkSize;
-            while ((chunkSize = in.read(buf)) != -1) {
-                out.write(buf, 0, chunkSize);
-            }
-            return file;
-        } catch (IOException e) {
-            throw new AssertionError("Exception while converting stream to file" + e);
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
deleted file mode 100644
index 1a8ef9e..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
+++ /dev/null
@@ -1,1678 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.contains;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.Nullable;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManagerInternal;
-import android.content.pm.Signature;
-import android.content.pm.SigningDetails;
-import android.content.pm.UserInfo;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Process;
-import android.os.UserHandle;
-import android.platform.test.annotations.Presubmit;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.SparseArray;
-
-import androidx.annotation.NonNull;
-
-import com.android.server.om.OverlayReferenceMapper;
-import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedActivityImpl;
-import com.android.server.pm.pkg.component.ParsedComponentImpl;
-import com.android.server.pm.pkg.component.ParsedInstrumentationImpl;
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionImpl;
-import com.android.server.pm.pkg.component.ParsedProviderImpl;
-import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.utils.WatchableTester;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-import java.security.cert.CertificateException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-@Presubmit
-@RunWith(JUnit4.class)
-public class AppsFilterImplTest {
-
-    private static final int DUMMY_CALLING_APPID = 10345;
-    private static final int DUMMY_TARGET_APPID = 10556;
-    private static final int DUMMY_ACTOR_APPID = 10656;
-    private static final int DUMMY_OVERLAY_APPID = 10756;
-    private static final int SYSTEM_USER = 0;
-    private static final int SECONDARY_USER = 10;
-    private static final int ADDED_USER = 11;
-    private static final int[] USER_ARRAY = {SYSTEM_USER, SECONDARY_USER};
-    private static final int[] USER_ARRAY_WITH_ADDED = {SYSTEM_USER, SECONDARY_USER, ADDED_USER};
-    private static final UserInfo[] USER_INFO_LIST = toUserInfos(USER_ARRAY);
-    private static final UserInfo[] USER_INFO_LIST_WITH_ADDED = toUserInfos(USER_ARRAY_WITH_ADDED);
-
-    private static UserInfo[] toUserInfos(int[] userIds) {
-        return Arrays.stream(userIds)
-                .mapToObj(id -> new UserInfo(id, Integer.toString(id), 0))
-                .toArray(UserInfo[]::new);
-    }
-
-    @Mock
-    FeatureConfig mFeatureConfigMock;
-    @Mock
-    Computer mSnapshot;
-    @Mock
-    Handler mMockHandler;
-    @Mock
-    PackageManagerInternal mPmInternal;
-
-    private ArrayMap<String, PackageSetting> mExisting = new ArrayMap<>();
-    private Collection<SharedUserSetting> mSharedUserSettings = new ArraySet<>();
-
-    private static ParsingPackage pkg(String packageName) {
-        return PackageImpl.forTesting(packageName)
-                .setTargetSdkVersion(Build.VERSION_CODES.R);
-    }
-
-    private static ParsingPackage pkg(String packageName, Intent... queries) {
-        ParsingPackage pkg = pkg(packageName);
-        if (queries != null) {
-            for (Intent intent : queries) {
-                pkg.addQueriesIntent(intent);
-            }
-        }
-        return pkg;
-    }
-
-    private static ParsingPackage pkgQueriesProvider(String packageName,
-            String... queriesAuthorities) {
-        ParsingPackage pkg = pkg(packageName);
-        if (queriesAuthorities != null) {
-            for (String authority : queriesAuthorities) {
-                pkg.addQueriesProvider(authority);
-            }
-        }
-        return pkg;
-    }
-
-    private static ParsingPackage pkg(String packageName, String... queriesPackages) {
-        ParsingPackage pkg = pkg(packageName);
-        if (queriesPackages != null) {
-            for (String queryPackageName : queriesPackages) {
-                pkg.addQueriesPackage(queryPackageName);
-            }
-        }
-        return pkg;
-    }
-
-    private static ParsingPackage pkg(String packageName, IntentFilter... filters) {
-        ParsedActivity activity = createActivity(packageName, filters);
-        return pkg(packageName).addActivity(activity);
-    }
-
-    private static ParsingPackage pkgWithReceiver(String packageName, IntentFilter... filters) {
-        ParsedActivity receiver = createActivity(packageName, filters);
-        return pkg(packageName).addReceiver(receiver);
-    }
-
-    private static ParsingPackage pkgWithSharedLibrary(String packageName, String libName) {
-        return pkg(packageName).addLibraryName(libName);
-    }
-
-    private static ParsingPackage pkgWithCustomPermissions(String packageName,
-            String... permNames) {
-        ParsingPackage newPkg = pkg(packageName);
-        for (String permName : permNames) {
-            ParsedPermission permission = new ParsedPermissionImpl();
-            ((ParsedComponentImpl) permission).setName(permName);
-            newPkg.addPermission(permission);
-        }
-        return newPkg;
-    }
-
-    private static ParsedActivity createActivity(String packageName, IntentFilter[] filters) {
-        ParsedActivityImpl activity = new ParsedActivityImpl();
-        activity.setPackageName(packageName);
-        for (IntentFilter filter : filters) {
-            final ParsedIntentInfoImpl info = new ParsedIntentInfoImpl();
-            final IntentFilter intentInfoFilter = info.getIntentFilter();
-            if (filter.countActions() > 0) {
-                filter.actionsIterator().forEachRemaining(intentInfoFilter::addAction);
-            }
-            if (filter.countCategories() > 0) {
-                filter.actionsIterator().forEachRemaining(intentInfoFilter::addAction);
-            }
-            if (filter.countDataAuthorities() > 0) {
-                filter.authoritiesIterator().forEachRemaining(intentInfoFilter::addDataAuthority);
-            }
-            if (filter.countDataSchemes() > 0) {
-                filter.schemesIterator().forEachRemaining(intentInfoFilter::addDataScheme);
-            }
-            activity.addIntent(info);
-            activity.setExported(true);
-        }
-        return activity;
-    }
-
-    private static ParsingPackage pkgWithInstrumentation(
-            String packageName, String instrumentationTargetPackage) {
-        ParsedInstrumentationImpl instrumentation = new ParsedInstrumentationImpl();
-        instrumentation.setTargetPackage(instrumentationTargetPackage);
-        return pkg(packageName).addInstrumentation(instrumentation);
-    }
-
-    private static ParsingPackage pkgWithProvider(String packageName, String authority) {
-        ParsedProviderImpl provider = new ParsedProviderImpl();
-        provider.setPackageName(packageName);
-        provider.setExported(true);
-        provider.setAuthority(authority);
-        return pkg(packageName)
-                .addProvider(provider);
-    }
-
-    @Before
-    public void setup() throws Exception {
-        mExisting = new ArrayMap<>();
-
-        MockitoAnnotations.initMocks(this);
-        when(mSnapshot.getPackageStates()).thenAnswer(x -> mExisting);
-        when(mSnapshot.getAllSharedUsers()).thenReturn(mSharedUserSettings);
-        when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST);
-        when(mPmInternal.snapshot()).thenReturn(mSnapshot);
-
-        // Can't mock postDelayed because of some weird bug in Mockito.
-        when(mMockHandler.sendMessageDelayed(any(Message.class), anyLong())).thenAnswer(
-                invocation -> {
-                    ((Message) invocation.getArgument(0)).getCallback().run();
-                    return null;
-                });
-
-        when(mFeatureConfigMock.isGloballyEnabled()).thenReturn(true);
-        when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class))).thenAnswer(
-                (Answer<Boolean>) invocation ->
-                        ((AndroidPackage) invocation.getArgument(SYSTEM_USER)).getTargetSdkVersion()
-                                >= Build.VERSION_CODES.R);
-    }
-
-    @Test
-    public void testSystemReadyPropogates() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */null, mMockHandler);
-        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
-        watcher.register();
-        appsFilter.onSystemReady(mPmInternal);
-        watcher.verifyChangeReported("systemReady");
-        verify(mFeatureConfigMock).onSystemReady();
-    }
-
-    @Test
-    public void testQueriesAction_FilterMatches() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
-        watcher.register();
-        simulateAddBasicAndroid(appsFilter);
-        watcher.verifyChangeReported("addBasicAndroid");
-        appsFilter.onSystemReady(mPmInternal);
-        watcher.verifyChangeReported("systemReady");
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package", new IntentFilter("TEST_ACTION")), DUMMY_TARGET_APPID);
-        watcher.verifyChangeReported("add package");
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID);
-        watcher.verifyChangeReported("add package");
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-        watcher.verifyNoChangeReported("shouldFilterAplication");
-    }
-
-    @Test
-    public void testQueriesProtectedAction_FilterDoesNotMatch() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
-        watcher.register();
-        final Signature frameworkSignature = Mockito.mock(Signature.class);
-        final SigningDetails frameworkSigningDetails =
-                new SigningDetails(new Signature[]{frameworkSignature}, 1);
-        final ParsingPackage android = pkg("android");
-        watcher.verifyNoChangeReported("prepare");
-        android.addProtectedBroadcast("TEST_ACTION");
-        simulateAddPackage(appsFilter, android, 1000,
-                b -> b.setSigningDetails(frameworkSigningDetails));
-        watcher.verifyChangeReported("addPackage");
-        appsFilter.onSystemReady(mPmInternal);
-        watcher.verifyChangeReported("systemReady");
-
-        final int activityUid = DUMMY_TARGET_APPID;
-        PackageSetting targetActivity = simulateAddPackage(appsFilter,
-                pkg("com.target.activity", new IntentFilter("TEST_ACTION")), activityUid);
-        watcher.verifyChangeReported("addPackage");
-        final int receiverUid = DUMMY_TARGET_APPID + 1;
-        PackageSetting targetReceiver = simulateAddPackage(appsFilter,
-                pkgWithReceiver("com.target.receiver", new IntentFilter("TEST_ACTION")),
-                receiverUid);
-        watcher.verifyChangeReported("addPackage");
-        final int callingUid = DUMMY_CALLING_APPID;
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.calling.action", new Intent("TEST_ACTION")), callingUid);
-        watcher.verifyChangeReported("addPackage");
-        final int wildcardUid = DUMMY_CALLING_APPID + 1;
-        PackageSetting callingWildCard = simulateAddPackage(appsFilter,
-                pkg("com.calling.wildcard", new Intent("*")), wildcardUid);
-        watcher.verifyChangeReported("addPackage");
-
-        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, callingUid, calling,
-                targetActivity, SYSTEM_USER));
-        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, callingUid, calling,
-                targetReceiver, SYSTEM_USER));
-
-        assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
-                wildcardUid, callingWildCard, targetActivity, SYSTEM_USER));
-        assertTrue(appsFilter.shouldFilterApplication(mSnapshot,
-                wildcardUid, callingWildCard, targetReceiver, SYSTEM_USER));
-        watcher.verifyNoChangeReported("shouldFilterApplication");
-    }
-
-    @Test
-    public void testQueriesProvider_FilterMatches() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
-        watcher.register();
-        simulateAddBasicAndroid(appsFilter);
-        watcher.verifyChangeReported("addPackage");
-        appsFilter.onSystemReady(mPmInternal);
-        watcher.verifyChangeReported("systemReady");
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID);
-        watcher.verifyChangeReported("addPackage");
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkgQueriesProvider("com.some.other.package", "com.some.authority"),
-                DUMMY_CALLING_APPID);
-        watcher.verifyChangeReported("addPackage");
-
-        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
-                target, SYSTEM_USER));
-        watcher.verifyNoChangeReported("shouldFilterApplication");
-    }
-
-    @Test
-    public void testOnUserUpdated_FilterMatches() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID);
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkgQueriesProvider("com.some.other.package", "com.some.authority"),
-                DUMMY_CALLING_APPID);
-
-        for (int subjectUserId : USER_ARRAY) {
-            for (int otherUserId : USER_ARRAY) {
-                assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
-                        UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
-                        otherUserId));
-            }
-        }
-
-        // adds new user
-        when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST_WITH_ADDED);
-        appsFilter.onUserCreated(mSnapshot, ADDED_USER);
-
-        for (int subjectUserId : USER_ARRAY_WITH_ADDED) {
-            for (int otherUserId : USER_ARRAY_WITH_ADDED) {
-                assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
-                        UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
-                        otherUserId));
-            }
-        }
-
-        // delete user
-        when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST);
-        appsFilter.onUserDeleted(mSnapshot, ADDED_USER);
-
-        for (int subjectUserId : USER_ARRAY) {
-            for (int otherUserId : USER_ARRAY) {
-                assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
-                        UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
-                        otherUserId));
-            }
-        }
-    }
-
-    @Test
-    public void testQueriesDifferentProvider_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
-        watcher.register();
-        simulateAddBasicAndroid(appsFilter);
-        watcher.verifyChangeReported("addPackage");
-        appsFilter.onSystemReady(mPmInternal);
-        watcher.verifyChangeReported("systemReady");
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID);
-        watcher.verifyChangeReported("addPackage");
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkgQueriesProvider("com.some.other.package", "com.some.other.authority"),
-                DUMMY_CALLING_APPID);
-        watcher.verifyChangeReported("addPackage");
-
-        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
-                target, SYSTEM_USER));
-        watcher.verifyNoChangeReported("shouldFilterApplication");
-    }
-
-    @Test
-    public void testQueriesProviderWithSemiColon_FilterMatches() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"),
-                DUMMY_TARGET_APPID);
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkgQueriesProvider("com.some.other.package", "com.some.authority"),
-                DUMMY_CALLING_APPID);
-
-        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
-                target, SYSTEM_USER));
-    }
-
-    @Test
-    public void testQueriesAction_NoMatchingAction_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package"), DUMMY_TARGET_APPID);
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID);
-
-        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
-                target, SYSTEM_USER));
-    }
-
-    @Test
-    public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package"), DUMMY_TARGET_APPID);
-        ParsingPackage callingPkg = pkg("com.some.other.package",
-                new Intent("TEST_ACTION"))
-                .setTargetSdkVersion(Build.VERSION_CODES.P);
-        PackageSetting calling = simulateAddPackage(appsFilter, callingPkg,
-                DUMMY_CALLING_APPID);
-
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testNoQueries_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package"), DUMMY_TARGET_APPID);
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package"), DUMMY_CALLING_APPID);
-
-        assertTrue(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testNoUsesLibrary_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        final Signature mockSignature = Mockito.mock(Signature.class);
-        final SigningDetails mockSigningDetails = new SigningDetails(
-                new Signature[]{mockSignature},
-                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2);
-
-        final PackageSetting target = simulateAddPackage(appsFilter,
-                pkgWithSharedLibrary("com.some.package", "com.some.shared_library"),
-                DUMMY_TARGET_APPID,
-                setting -> setting.setSigningDetails(mockSigningDetails)
-                        .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
-        final PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package"), DUMMY_CALLING_APPID);
-
-        assertTrue(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testUsesLibrary_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        final Signature mockSignature = Mockito.mock(Signature.class);
-        final SigningDetails mockSigningDetails = new SigningDetails(
-                new Signature[]{mockSignature},
-                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2);
-
-        final PackageSetting target = simulateAddPackage(appsFilter,
-                pkgWithSharedLibrary("com.some.package", "com.some.shared_library"),
-                DUMMY_TARGET_APPID,
-                setting -> setting.setSigningDetails(mockSigningDetails)
-                        .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
-        final PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package").addUsesLibrary("com.some.shared_library"),
-                DUMMY_CALLING_APPID);
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testUsesOptionalLibrary_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        final Signature mockSignature = Mockito.mock(Signature.class);
-        final SigningDetails mockSigningDetails = new SigningDetails(
-                new Signature[]{mockSignature},
-                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2);
-
-        final PackageSetting target = simulateAddPackage(appsFilter,
-                pkgWithSharedLibrary("com.some.package", "com.some.shared_library"),
-                DUMMY_TARGET_APPID,
-                setting -> setting.setSigningDetails(mockSigningDetails)
-                        .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
-        final PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package").addUsesOptionalLibrary("com.some.shared_library"),
-                DUMMY_CALLING_APPID);
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testUsesLibrary_ShareUid_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        final Signature mockSignature = Mockito.mock(Signature.class);
-        final SigningDetails mockSigningDetails = new SigningDetails(
-                new Signature[]{mockSignature},
-                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2);
-
-        final PackageSetting target = simulateAddPackage(appsFilter,
-                pkgWithSharedLibrary("com.some.package", "com.some.shared_library"),
-                DUMMY_TARGET_APPID,
-                setting -> setting.setSigningDetails(mockSigningDetails)
-                        .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
-        final PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package_a").setSharedUserId("com.some.uid"),
-                DUMMY_CALLING_APPID);
-        simulateAddPackage(appsFilter, pkg("com.some.other.package_b")
-                        .setSharedUserId("com.some.uid").addUsesLibrary("com.some.shared_library"),
-                DUMMY_CALLING_APPID);
-
-        // Although package_a doesn't use library, it should be granted visibility. It's because
-        // package_a shares userId with package_b, and package_b uses that shared library.
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testForceQueryable_SystemDoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_APPID,
-                setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package"), DUMMY_CALLING_APPID);
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-
-    @Test
-    public void testForceQueryable_NonSystemFilters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_APPID);
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package"), DUMMY_CALLING_APPID);
-
-        assertTrue(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testForceQueryableByDevice_SystemCaller_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{"com.some.package"},
-                        /* systemAppsQueryable */ false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package"), DUMMY_TARGET_APPID,
-                setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package"), DUMMY_CALLING_APPID);
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-
-    @Test
-    public void testSystemSignedTarget_DoesntFilter() throws CertificateException {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        appsFilter.onSystemReady(mPmInternal);
-
-        final Signature frameworkSignature = Mockito.mock(Signature.class);
-        final SigningDetails frameworkSigningDetails =
-                new SigningDetails(new Signature[]{frameworkSignature}, 1);
-
-        final Signature otherSignature = Mockito.mock(Signature.class);
-        final SigningDetails otherSigningDetails =
-                new SigningDetails(new Signature[]{otherSignature}, 1);
-
-        simulateAddPackage(appsFilter, pkg("android"), 1000,
-                b -> b.setSigningDetails(frameworkSigningDetails));
-        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
-                DUMMY_TARGET_APPID,
-                b -> b.setSigningDetails(frameworkSigningDetails)
-                        .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package"), DUMMY_CALLING_APPID,
-                b -> b.setSigningDetails(otherSigningDetails));
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testForceQueryableByDevice_NonSystemCaller_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{"com.some.package"},
-                        /* systemAppsQueryable */ false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package"), DUMMY_TARGET_APPID);
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package"), DUMMY_CALLING_APPID);
-
-        assertTrue(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-
-    @Test
-    public void testSystemQueryable_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        true, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package"), DUMMY_TARGET_APPID,
-                setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package"), DUMMY_CALLING_APPID);
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testQueriesPackage_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package"), DUMMY_TARGET_APPID);
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package", "com.some.package"), DUMMY_CALLING_APPID);
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testNoQueries_FeatureOff_DoesntFilter() throws Exception {
-        when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class)))
-                .thenReturn(false);
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(
-                appsFilter, pkg("com.some.package"), DUMMY_TARGET_APPID);
-        PackageSetting calling = simulateAddPackage(
-                appsFilter, pkg("com.some.other.package"), DUMMY_CALLING_APPID);
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testSystemUid_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package"), DUMMY_TARGET_APPID);
-
-        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, SYSTEM_USER, null, target,
-                SYSTEM_USER));
-        assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
-                Process.FIRST_APPLICATION_UID - 1, null, target, SYSTEM_USER));
-    }
-
-    @Test
-    public void testSystemUidSecondaryUser_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package"), DUMMY_TARGET_APPID);
-
-        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, 0, null, target,
-                SECONDARY_USER));
-        assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
-                UserHandle.getUid(SECONDARY_USER, Process.FIRST_APPLICATION_UID - 1),
-                null, target, SECONDARY_USER));
-    }
-
-    @Test
-    public void testNonSystemUid_NoCallingSetting_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package"), DUMMY_TARGET_APPID);
-
-        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, null, target,
-                SYSTEM_USER));
-    }
-
-    @Test
-    public void testNoTargetPackage_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = new PackageSettingBuilder()
-                .setAppId(DUMMY_TARGET_APPID)
-                .setName("com.some.package")
-                .setCodePath("/")
-                .setPVersionCode(1L)
-                .build();
-        PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID);
-
-        assertTrue(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testActsOnTargetOfOverlay() throws Exception {
-        final String actorName = "overlay://test/actorName";
-
-        var target = pkg("com.some.package.target")
-                .addOverlayable("overlayableName", actorName)
-                .hideAsParsed();
-        var overlay = pkg("com.some.package.overlay")
-                .setOverlay(true)
-                .setOverlayTarget(target.getPackageName())
-                .setOverlayTargetOverlayableName("overlayableName")
-                .hideAsParsed();
-        var actor = pkg("com.some.package.actor")
-                .hideAsParsed();
-
-        final AppsFilterImpl appsFilter = new AppsFilterImpl(
-                mFeatureConfigMock,
-                new String[]{},
-                false,
-                new OverlayReferenceMapper.Provider() {
-                    @Nullable
-                    @Override
-                    public String getActorPkg(String actorString) {
-                        if (actorName.equals(actorString)) {
-                            return actor.getPackageName();
-                        }
-                        return null;
-                    }
-
-                    @NonNull
-                    @Override
-                    public Map<String, Set<String>> getTargetToOverlayables(
-                            @NonNull AndroidPackage pkg) {
-                        if (overlay.getPackageName().equals(pkg.getPackageName())) {
-                            Map<String, Set<String>> map = new ArrayMap<>();
-                            Set<String> set = new ArraySet<>();
-                            set.add(overlay.getOverlayTargetOverlayableName());
-                            map.put(overlay.getOverlayTarget(), set);
-                            return map;
-                        }
-                        return Collections.emptyMap();
-                    }
-                },
-                mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        // Packages must be added in actor -> overlay -> target order so that the implicit
-        // visibility of the actor into the overlay can be tested
-
-        PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_APPID);
-        PackageSetting overlaySetting =
-                simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID);
-
-        // Actor can not see overlay (yet)
-        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
-                overlaySetting, SYSTEM_USER));
-
-        PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID);
-
-        // Actor can see both target and overlay
-        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
-                targetSetting, SYSTEM_USER));
-        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
-                overlaySetting, SYSTEM_USER));
-
-        // But target/overlay can't see each other
-        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, targetSetting,
-                overlaySetting, SYSTEM_USER));
-        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_OVERLAY_APPID,
-                overlaySetting, targetSetting, SYSTEM_USER));
-
-        // And can't see the actor
-        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, targetSetting,
-                actorSetting, SYSTEM_USER));
-        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_OVERLAY_APPID,
-                overlaySetting, actorSetting, SYSTEM_USER));
-
-        appsFilter.removePackage(mSnapshot, targetSetting);
-
-        // Actor loses visibility to the overlay via removal of the target
-        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
-                overlaySetting, SYSTEM_USER));
-    }
-
-    @Test
-    public void testActsOnTargetOfOverlayThroughSharedUser() throws Exception {
-//        Debug.waitForDebugger();
-
-        final String actorName = "overlay://test/actorName";
-
-        var target = pkg("com.some.package.target")
-                .addOverlayable("overlayableName", actorName)
-                .hideAsParsed();
-        var overlay = pkg("com.some.package.overlay")
-                .setOverlay(true)
-                .setOverlayTarget(target.getPackageName())
-                .setOverlayTargetOverlayableName("overlayableName")
-                .hideAsParsed();
-        var actorOne = pkg("com.some.package.actor.one").hideAsParsed();
-        var actorTwo = pkg("com.some.package.actor.two").hideAsParsed();
-        PackageSetting ps1 = getPackageSettingFromParsingPackage(actorOne, DUMMY_ACTOR_APPID,
-                null /*settingBuilder*/);
-        PackageSetting ps2 = getPackageSettingFromParsingPackage(actorTwo, DUMMY_ACTOR_APPID,
-                null /*settingBuilder*/);
-
-        final AppsFilterImpl appsFilter = new AppsFilterImpl(
-                mFeatureConfigMock,
-                new String[]{},
-                false,
-                new OverlayReferenceMapper.Provider() {
-                    @Nullable
-                    @Override
-                    public String getActorPkg(String actorString) {
-                        // Only actorOne is mapped as a valid actor
-                        if (actorName.equals(actorString)) {
-                            return actorOne.getPackageName();
-                        }
-                        return null;
-                    }
-
-                    @NonNull
-                    @Override
-                    public Map<String, Set<String>> getTargetToOverlayables(
-                            @NonNull AndroidPackage pkg) {
-                        if (overlay.getPackageName().equals(pkg.getPackageName())) {
-                            Map<String, Set<String>> map = new ArrayMap<>();
-                            Set<String> set = new ArraySet<>();
-                            set.add(overlay.getOverlayTargetOverlayableName());
-                            map.put(overlay.getOverlayTarget(), set);
-                            return map;
-                        }
-                        return Collections.emptyMap();
-                    }
-                },
-                mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID);
-        SharedUserSetting actorSharedSetting = new SharedUserSetting("actorSharedUser",
-                targetSetting.getFlags(), targetSetting.getPrivateFlags());
-        actorSharedSetting.mAppId = 100; /* mimic a valid sharedUserSetting.mAppId */
-        PackageSetting overlaySetting =
-                simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID);
-        simulateAddPackage(ps1, appsFilter, actorSharedSetting);
-        simulateAddPackage(ps2, appsFilter, actorSharedSetting);
-
-        // actorTwo can see both target and overlay
-        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID,
-                actorSharedSetting, targetSetting, SYSTEM_USER));
-        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID,
-                actorSharedSetting, overlaySetting, SYSTEM_USER));
-    }
-
-    @Test
-    public void testInitiatingApp_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
-                DUMMY_TARGET_APPID);
-        PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
-                DUMMY_CALLING_APPID,
-                withInstallSource(target.getPackageName(), null, null, null, false));
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testUninstalledInitiatingApp_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
-                DUMMY_TARGET_APPID);
-        PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
-                DUMMY_CALLING_APPID,
-                withInstallSource(target.getPackageName(), null, null, null, true));
-
-        assertTrue(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testOriginatingApp_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
-        watcher.register();
-        simulateAddBasicAndroid(appsFilter);
-        watcher.verifyChangeReported("addBasicAndroid");
-        appsFilter.onSystemReady(mPmInternal);
-        watcher.verifyChangeReported("systemReady");
-
-        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
-                DUMMY_TARGET_APPID);
-        watcher.verifyChangeReported("add package");
-        PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
-                DUMMY_CALLING_APPID, withInstallSource(null, target.getPackageName(), null, null,
-                        false));
-        watcher.verifyChangeReported("add package");
-
-        assertTrue(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-        watcher.verifyNoChangeReported("shouldFilterAplication");
-    }
-
-    @Test
-    public void testInstallingApp_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
-        watcher.register();
-        simulateAddBasicAndroid(appsFilter);
-        watcher.verifyChangeReported("addBasicAndroid");
-        appsFilter.onSystemReady(mPmInternal);
-        watcher.verifyChangeReported("systemReady");
-
-        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
-                DUMMY_TARGET_APPID);
-        watcher.verifyChangeReported("add package");
-        PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
-                DUMMY_CALLING_APPID, withInstallSource(null, null, target.getPackageName(), null,
-                        false));
-        watcher.verifyChangeReported("add package");
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-        watcher.verifyNoChangeReported("shouldFilterAplication");
-    }
-
-    @Test
-    public void testInstrumentation_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
-        watcher.register();
-        simulateAddBasicAndroid(appsFilter);
-        watcher.verifyChangeReported("addBasicAndroid");
-        appsFilter.onSystemReady(mPmInternal);
-        watcher.verifyChangeReported("systemReady");
-
-        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
-                DUMMY_TARGET_APPID);
-        watcher.verifyChangeReported("add package");
-        PackageSetting instrumentation = simulateAddPackage(appsFilter,
-                pkgWithInstrumentation("com.some.other.package", "com.some.package"),
-                DUMMY_CALLING_APPID);
-        watcher.verifyChangeReported("add package");
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
-                        target, SYSTEM_USER));
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, target,
-                        instrumentation, SYSTEM_USER));
-        watcher.verifyNoChangeReported("shouldFilterAplication");
-    }
-
-    @Test
-    public void testWhoCanSee() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
-        watcher.register();
-        simulateAddBasicAndroid(appsFilter);
-        watcher.verifyChangeReported("addBasicAndroid");
-        appsFilter.onSystemReady(mPmInternal);
-        watcher.verifyChangeReported("systemReady");
-
-        final int systemAppId = Process.FIRST_APPLICATION_UID - 1;
-        final int seesNothingAppId = Process.FIRST_APPLICATION_UID;
-        final int hasProviderAppId = Process.FIRST_APPLICATION_UID + 1;
-        final int queriesProviderAppId = Process.FIRST_APPLICATION_UID + 2;
-
-        PackageSetting system = simulateAddPackage(appsFilter, pkg("some.system.pkg"), systemAppId);
-        watcher.verifyChangeReported("add package");
-        PackageSetting seesNothing = simulateAddPackage(appsFilter, pkg("com.some.package"),
-                seesNothingAppId);
-        watcher.verifyChangeReported("add package");
-        PackageSetting hasProvider = simulateAddPackage(appsFilter,
-                pkgWithProvider("com.some.other.package", "com.some.authority"), hasProviderAppId);
-        watcher.verifyChangeReported("add package");
-        PackageSetting queriesProvider = simulateAddPackage(appsFilter,
-                pkgQueriesProvider("com.yet.some.other.package", "com.some.authority"),
-                queriesProviderAppId);
-        watcher.verifyChangeReported("add package");
-
-        final SparseArray<int[]> systemFilter =
-                appsFilter.getVisibilityAllowList(mSnapshot, system, USER_ARRAY, mExisting);
-        watcher.verifyNoChangeReported("getVisibility");
-        assertThat(toList(systemFilter.get(SYSTEM_USER)),
-                contains(seesNothingAppId, hasProviderAppId, queriesProviderAppId));
-        watcher.verifyNoChangeReported("getVisibility");
-
-        final SparseArray<int[]> seesNothingFilter =
-                appsFilter.getVisibilityAllowList(mSnapshot, seesNothing, USER_ARRAY, mExisting);
-        watcher.verifyNoChangeReported("getVisibility");
-        assertThat(toList(seesNothingFilter.get(SYSTEM_USER)),
-                contains(seesNothingAppId));
-        watcher.verifyNoChangeReported("getVisibility");
-        assertThat(toList(seesNothingFilter.get(SECONDARY_USER)),
-                contains(seesNothingAppId));
-        watcher.verifyNoChangeReported("getVisibility");
-
-        final SparseArray<int[]> hasProviderFilter =
-                appsFilter.getVisibilityAllowList(mSnapshot, hasProvider, USER_ARRAY, mExisting);
-        assertThat(toList(hasProviderFilter.get(SYSTEM_USER)),
-                contains(hasProviderAppId, queriesProviderAppId));
-
-        SparseArray<int[]> queriesProviderFilter =
-                appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
-                        mExisting);
-        watcher.verifyNoChangeReported("getVisibility");
-        assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
-                contains(queriesProviderAppId));
-        watcher.verifyNoChangeReported("getVisibility");
-
-        // provider read
-        appsFilter.grantImplicitAccess(hasProviderAppId, queriesProviderAppId,
-                false /* retainOnUpdate */);
-        watcher.verifyChangeReported("grantImplicitAccess");
-
-        // ensure implicit access is included in the filter
-        queriesProviderFilter =
-                appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
-                        mExisting);
-        watcher.verifyNoChangeReported("getVisibility");
-        assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
-                contains(hasProviderAppId, queriesProviderAppId));
-        watcher.verifyNoChangeReported("getVisibility");
-    }
-
-    @Test
-    public void testOnChangeReport() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
-        watcher.register();
-        simulateAddBasicAndroid(appsFilter);
-        watcher.verifyChangeReported("addBasic");
-        appsFilter.onSystemReady(mPmInternal);
-        watcher.verifyChangeReported("systemReady");
-
-        final int systemAppId = Process.FIRST_APPLICATION_UID - 1;
-        final int seesNothingAppId = Process.FIRST_APPLICATION_UID;
-        final int hasProviderAppId = Process.FIRST_APPLICATION_UID + 1;
-        final int queriesProviderAppId = Process.FIRST_APPLICATION_UID + 2;
-
-        PackageSetting system = simulateAddPackage(appsFilter, pkg("some.system.pkg"), systemAppId);
-        watcher.verifyChangeReported("addPackage");
-        PackageSetting seesNothing = simulateAddPackage(appsFilter, pkg("com.some.package"),
-                seesNothingAppId);
-        watcher.verifyChangeReported("addPackage");
-        PackageSetting hasProvider = simulateAddPackage(appsFilter,
-                pkgWithProvider("com.some.other.package", "com.some.authority"), hasProviderAppId);
-        watcher.verifyChangeReported("addPackage");
-        PackageSetting queriesProvider = simulateAddPackage(appsFilter,
-                pkgQueriesProvider("com.yet.some.other.package", "com.some.authority"),
-                queriesProviderAppId);
-        watcher.verifyChangeReported("addPackage");
-
-        final SparseArray<int[]> systemFilter =
-                appsFilter.getVisibilityAllowList(mSnapshot, system, USER_ARRAY, mExisting);
-        assertThat(toList(systemFilter.get(SYSTEM_USER)),
-                contains(seesNothingAppId, hasProviderAppId, queriesProviderAppId));
-        watcher.verifyNoChangeReported("get");
-
-        final SparseArray<int[]> seesNothingFilter =
-                appsFilter.getVisibilityAllowList(mSnapshot, seesNothing, USER_ARRAY, mExisting);
-        assertThat(toList(seesNothingFilter.get(SYSTEM_USER)),
-                contains(seesNothingAppId));
-        assertThat(toList(seesNothingFilter.get(SECONDARY_USER)),
-                contains(seesNothingAppId));
-        watcher.verifyNoChangeReported("get");
-
-        final SparseArray<int[]> hasProviderFilter =
-                appsFilter.getVisibilityAllowList(mSnapshot, hasProvider, USER_ARRAY, mExisting);
-        assertThat(toList(hasProviderFilter.get(SYSTEM_USER)),
-                contains(hasProviderAppId, queriesProviderAppId));
-        watcher.verifyNoChangeReported("get");
-
-        SparseArray<int[]> queriesProviderFilter =
-                appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
-                        mExisting);
-        assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
-                contains(queriesProviderAppId));
-        watcher.verifyNoChangeReported("get");
-
-        // provider read
-        appsFilter.grantImplicitAccess(
-                hasProviderAppId, queriesProviderAppId, false /* retainOnUpdate */);
-        watcher.verifyChangeReported("grantImplicitAccess");
-
-        // ensure implicit access is included in the filter
-        queriesProviderFilter =
-                appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
-                        mExisting);
-        assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
-                contains(hasProviderAppId, queriesProviderAppId));
-        watcher.verifyNoChangeReported("get");
-
-        // remove a package
-        appsFilter.removePackage(mSnapshot, seesNothing);
-        watcher.verifyChangeReported("removePackage");
-    }
-
-    @Test
-    public void testOnChangeReportedFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange filter");
-        watcher.register();
-
-        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
-                DUMMY_TARGET_APPID);
-        PackageSetting instrumentation = simulateAddPackage(appsFilter,
-                pkgWithInstrumentation("com.some.other.package", "com.some.package"),
-                DUMMY_CALLING_APPID);
-        watcher.verifyChangeReported("addPackage");
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
-                        target, SYSTEM_USER));
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, target,
-                        instrumentation, SYSTEM_USER));
-        watcher.verifyNoChangeReported("shouldFilterApplication");
-    }
-
-    @Test
-    public void testAppsFilterRead() throws Exception {
-        when(mFeatureConfigMock.snapshot()).thenReturn(mFeatureConfigMock);
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
-                DUMMY_TARGET_APPID);
-        PackageSetting instrumentation = simulateAddPackage(appsFilter,
-                pkgWithInstrumentation("com.some.other.package", "com.some.package"),
-                DUMMY_CALLING_APPID);
-
-        final int hasProviderAppId = Process.FIRST_APPLICATION_UID + 1;
-        final int queriesProviderAppId = Process.FIRST_APPLICATION_UID + 2;
-        PackageSetting queriesProvider = simulateAddPackage(appsFilter,
-                pkgQueriesProvider("com.yet.some.other.package", "com.some.authority"),
-                queriesProviderAppId);
-        appsFilter.grantImplicitAccess(
-                hasProviderAppId, queriesProviderAppId, false /* retainOnUpdate */);
-
-        AppsFilterSnapshot snapshot = appsFilter.snapshot();
-        assertFalse(
-                snapshot.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
-                        target,
-                        SYSTEM_USER));
-        assertFalse(
-                snapshot.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, target,
-                        instrumentation,
-                        SYSTEM_USER));
-
-        SparseArray<int[]> queriesProviderFilter =
-                snapshot.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY, mExisting);
-        assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)), contains(queriesProviderAppId));
-        assertTrue(snapshot.canQueryPackage(instrumentation.getPkg(),
-                target.getPackageName()));
-
-        // New changes don't affect the snapshot
-        appsFilter.removePackage(mSnapshot, target);
-        assertTrue(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
-                        target,
-                        SYSTEM_USER));
-        assertFalse(
-                snapshot.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
-                        target,
-                        SYSTEM_USER));
-
-    }
-
-    @Test
-    public void testSdkSandbox_canSeeForceQueryable() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_APPID,
-                setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
-
-        int callingUid = 20123;
-        assertTrue(Process.isSdkSandboxUid(callingUid));
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, callingUid,
-                        null /* callingSetting */, target, SYSTEM_USER));
-    }
-
-    @Test
-    public void testSdkSandbox_cannotSeeNonForceQueryable() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package"), DUMMY_TARGET_APPID,
-                setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
-
-        int callingUid = 20123;
-        assertTrue(Process.isSdkSandboxUid(callingUid));
-
-        assertTrue(
-                appsFilter.shouldFilterApplication(mSnapshot, callingUid,
-                        null /* callingSetting */, target, SYSTEM_USER));
-    }
-
-    @Test
-    public void testSdkSandbox_implicitAccessGranted_canSeePackage() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
-                        mMockHandler);
-        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
-        watcher.register();
-        simulateAddBasicAndroid(appsFilter);
-        watcher.verifyChangeReported("addBasic");
-        appsFilter.onSystemReady(mPmInternal);
-        watcher.verifyChangeReported("systemReady");
-
-        PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.package"), DUMMY_TARGET_APPID,
-                setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
-
-        int callingUid = 20123;
-        assertTrue(Process.isSdkSandboxUid(callingUid));
-
-        // Without granting the implicit access the app shouldn't be visible to the sdk sandbox uid.
-        assertTrue(
-                appsFilter.shouldFilterApplication(mSnapshot, callingUid,
-                        null /* callingSetting */, target, SYSTEM_USER));
-
-        appsFilter.grantImplicitAccess(callingUid, target.getAppId(), false /* retainOnUpdate */);
-        watcher.verifyChangeReported("grantImplicitAccess");
-
-        // After implicit access was granted the app should be visible to the sdk sandbox uid.
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, callingUid,
-                        null /* callingSetting */, target, SYSTEM_USER));
-    }
-
-    @Test
-    public void testUsesPermission_installPermissionDefinerBeforeRequester_DoesntFilter()
-            throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        final PackageSetting target = simulateAddPackage(appsFilter,
-                pkgWithCustomPermissions("com.some.package",
-                        "com.some.custom_permission"),
-                DUMMY_TARGET_APPID);
-        final PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package").addUsesPermission(
-                        new ParsedUsesPermissionImpl("com.some.custom_permission", 0)),
-                DUMMY_CALLING_APPID);
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testUsesPermission_installPermissionRequesterBeforeDefiner_DoesntFilter()
-            throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        final PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package").addUsesPermission(
-                        new ParsedUsesPermissionImpl("com.some.custom_permission", 0)),
-                DUMMY_CALLING_APPID);
-
-        final PackageSetting target = simulateAddPackage(appsFilter,
-                pkgWithCustomPermissions("com.some.package",
-                        "com.some.custom_permission"),
-                DUMMY_TARGET_APPID);
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testUsesPermission_visibilityFromPermissionDefinerToRequester_Filters()
-            throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        final PackageSetting calling = simulateAddPackage(appsFilter,
-                pkgWithCustomPermissions("com.some.package",
-                        "com.some.custom_permission"),
-                DUMMY_CALLING_APPID);
-
-        final PackageSetting target = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package").addUsesPermission(
-                        new ParsedUsesPermissionImpl("com.some.custom_permission", 0)),
-                DUMMY_TARGET_APPID);
-
-        assertTrue(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-
-    @Test
-    public void testUsesPermission_multipleCustomPermissions() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-        final PackageSetting target = simulateAddPackage(appsFilter,
-                pkgWithCustomPermissions("com.some.package",
-                        "com.some.custom_permission1", "com.some.custom_permission2"),
-                DUMMY_TARGET_APPID);
-
-        final PackageSetting calling1 = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package")
-                        .addUsesPermission(new ParsedUsesPermissionImpl(
-                                "com.some.custom_permission1", 0)),
-                DUMMY_CALLING_APPID);
-
-        final PackageSetting calling2 = simulateAddPackage(appsFilter,
-                pkg("com.some.another.package")
-                        .addUsesPermission(new ParsedUsesPermissionImpl(
-                                "com.some.custom_permission2", 0))
-                        .addUsesPermission(new ParsedUsesPermissionImpl(
-                                "com.some.custom_permission3", 0)),
-                DUMMY_CALLING_APPID + 1);
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling1, target,
-                        SYSTEM_USER));
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID + 1,
-                        calling2, target, SYSTEM_USER));
-        assertTrue(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling1,
-                        calling2, SYSTEM_USER));
-    }
-
-    @Test
-    public void testUsesPermission_multiplePermissionDefiners_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-
-        final PackageSetting target1 = simulateAddPackage(appsFilter,
-                pkgWithCustomPermissions("com.some.package1", "com.some.custom_permission"),
-                DUMMY_TARGET_APPID);
-        final PackageSetting target2 = simulateAddPackage(appsFilter,
-                pkgWithCustomPermissions("com.some.package2", "com.some.custom_permission"),
-                DUMMY_TARGET_APPID + 1);
-        final PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package")
-                        .addUsesPermission(new ParsedUsesPermissionImpl(
-                                "com.some.custom_permission", 0)),
-                DUMMY_CALLING_APPID);
-
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target1,
-                        SYSTEM_USER));
-        assertFalse(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target2,
-                        SYSTEM_USER));
-
-    }
-
-    @Test
-    public void testNoUsesPermission_Filters() throws Exception {
-        final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
-                        false, /* overlayProvider */ null, mMockHandler);
-
-        simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady(mPmInternal);
-
-
-        final PackageSetting target = simulateAddPackage(appsFilter,
-                pkgWithCustomPermissions("com.some.package", "com.some.custom_permission"),
-                DUMMY_TARGET_APPID);
-        final PackageSetting calling = simulateAddPackage(appsFilter,
-                pkg("com.some.other.package"),
-                DUMMY_CALLING_APPID);
-
-        assertTrue(
-                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
-                        SYSTEM_USER));
-    }
-    private List<Integer> toList(int[] array) {
-        ArrayList<Integer> ret = new ArrayList<>(array.length);
-        for (int i = 0; i < array.length; i++) {
-            ret.add(i, array[i]);
-        }
-        return ret;
-    }
-
-    private interface WithSettingBuilder {
-        PackageSettingBuilder withBuilder(PackageSettingBuilder builder);
-    }
-
-    private void simulateAddBasicAndroid(AppsFilterImpl appsFilter) throws Exception {
-        final Signature frameworkSignature = Mockito.mock(Signature.class);
-        final SigningDetails frameworkSigningDetails =
-                new SigningDetails(new Signature[]{frameworkSignature}, 1);
-        final ParsedPackage android = pkg("android").hideAsParsed();
-        simulateAddPackage(appsFilter, android, 1000,
-                b -> b.setSigningDetails(frameworkSigningDetails));
-    }
-
-    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
-            ParsedPackage newPkgBuilder, int appId) {
-        return simulateAddPackage(filter, newPkgBuilder, appId, null /*settingBuilder*/);
-    }
-
-    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
-            ParsedPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action) {
-        return simulateAddPackage(filter, newPkgBuilder, appId, action, null /*sharedUserSetting*/);
-    }
-
-    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
-            ParsedPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action,
-            @Nullable SharedUserSetting sharedUserSetting) {
-        final PackageSetting setting =
-                getPackageSettingFromParsingPackage(newPkgBuilder, appId, action);
-        simulateAddPackage(setting, filter, sharedUserSetting);
-        return setting;
-    }
-
-    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
-            ParsingPackage newPkgBuilder, int appId) {
-        return simulateAddPackage(filter, newPkgBuilder.hideAsParsed(), appId,
-                null /*settingBuilder*/);
-    }
-
-    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
-            ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action) {
-        return simulateAddPackage(filter, newPkgBuilder.hideAsParsed(), appId, action,
-                null /*sharedUserSetting*/);
-    }
-
-    private PackageSetting simulateAddPackage(AppsFilterImpl filter,
-            ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action,
-            @Nullable SharedUserSetting sharedUserSetting) {
-        return simulateAddPackage(filter, newPkgBuilder.hideAsParsed(), appId, action,
-                sharedUserSetting);
-    }
-
-    private PackageSetting getPackageSettingFromParsingPackage(ParsedPackage newPkgBuilder,
-            int appId, @Nullable WithSettingBuilder action) {
-        AndroidPackage newPkg = newPkgBuilder.hideAsFinal();
-        final PackageSettingBuilder settingBuilder = new PackageSettingBuilder()
-                .setPackage(newPkg)
-                .setAppId(appId)
-                .setName(newPkg.getPackageName())
-                .setCodePath("/")
-                .setPVersionCode(1L);
-        final PackageSetting setting =
-                (action == null ? settingBuilder : action.withBuilder(settingBuilder)).build();
-        return setting;
-    }
-
-    private void simulateAddPackage(PackageSetting setting, AppsFilterImpl filter,
-            @Nullable SharedUserSetting sharedUserSetting) {
-        mExisting.put(setting.getPackageName(), setting);
-        if (sharedUserSetting != null) {
-            sharedUserSetting.addPackage(setting);
-            setting.setSharedUserAppId(sharedUserSetting.mAppId);
-            mSharedUserSettings.add(sharedUserSetting);
-        }
-        filter.addPackage(mSnapshot, setting);
-    }
-
-    private WithSettingBuilder withInstallSource(String initiatingPackageName,
-            String originatingPackageName, String installerPackageName,
-            String installerAttributionTag, boolean isInitiatingPackageUninstalled) {
-        final InstallSource installSource = InstallSource.create(initiatingPackageName,
-                originatingPackageName, installerPackageName, installerAttributionTag,
-                /* isOrphaned= */ false, isInitiatingPackageUninstalled);
-        return setting -> setting.setInstallSource(installSource);
-    }
-}
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
deleted file mode 100644
index eaa0e9b..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
+++ /dev/null
@@ -1,233 +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.pm;
-
-import static android.content.pm.ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS;
-import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS;
-import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS;
-import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES;
-import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS;
-import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.pm.ApplicationInfo;
-import android.os.Build;
-import android.platform.test.annotations.Presubmit;
-
-import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.pkg.PackageUserStateImpl;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-@Presubmit
-public class CompatibilityModeTest {
-
-    private boolean mCompatibilityModeEnabled;;
-    private PackageImpl mMockAndroidPackage;
-    private PackageUserStateImpl mMockUserState;
-
-    @Before
-    public void setUp() {
-        mCompatibilityModeEnabled = ParsingPackageUtils.sCompatibilityModeEnabled;
-        mMockAndroidPackage = mock(PackageImpl.class);
-        mMockUserState = new PackageUserStateImpl();
-        mMockUserState.setInstalled(true);
-    }
-
-    @After
-    public void tearDown() {
-        setGlobalCompatibilityMode(mCompatibilityModeEnabled);
-    }
-
-    // The following tests ensure that apps with target SDK of Cupcake always use compat mode.
-
-    @Test
-    public void testGlobalCompatModeEnabled_oldApp_supportAllScreens_usesCompatMode() {
-        setGlobalCompatibilityMode(true);
-        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS
-                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
-                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
-        final ApplicationInfo info =
-                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, flags);
-        assertThat(info.usesCompatibilityMode(), is(true));
-    }
-
-    @Test
-    public void testGlobalCompatModeEnabled_oldApp_supportSomeScreens_usesCompatMode() {
-        setGlobalCompatibilityMode(true);
-        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS
-                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
-                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
-        final ApplicationInfo info =
-                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, flags);
-        assertThat(info.usesCompatibilityMode(), is(true));
-    }
-
-    @Test
-    public void testGlobalCompatModeEnabled_oldApp_supportOnlyOneScreen_usesCompatMode() {
-        setGlobalCompatibilityMode(true);
-        final int flags = FLAG_SUPPORTS_NORMAL_SCREENS;
-        final ApplicationInfo info =
-                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, flags);
-        assertThat(info.usesCompatibilityMode(), is(true));
-    }
-
-    @Test
-    public void testGlobalCompatModeEnabled_oldApp_DoesntSupportAllScreens_usesCompatMode() {
-        setGlobalCompatibilityMode(true);
-        final ApplicationInfo info =
-                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, 0 /*flags*/);
-        assertThat(info.usesCompatibilityMode(), is(true));
-    }
-
-    @Test
-    public void testGlobalCompatModeDisabled_oldApp_supportAllScreens_usesCompatMode() {
-        setGlobalCompatibilityMode(false);
-        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS
-                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
-                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
-        final ApplicationInfo info =
-                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, flags);
-        assertThat(info.usesCompatibilityMode(), is(true));
-    }
-
-    @Test
-    public void testGlobalCompatModeDisabled_oldApp_supportSomeScreens_usesCompatMode() {
-        setGlobalCompatibilityMode(false);
-        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS
-                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
-                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
-        final ApplicationInfo info =
-                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, flags);
-        assertThat(info.usesCompatibilityMode(), is(true));
-    }
-
-    @Test
-    public void testGlobalCompatModeDisabled_oldApp_supportOnlyOneScreen_usesCompatMode() {
-        setGlobalCompatibilityMode(false);
-        final int flags = FLAG_SUPPORTS_NORMAL_SCREENS;
-        final ApplicationInfo info =
-                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, flags);
-        assertThat(info.usesCompatibilityMode(), is(true));
-    }
-
-    @Test
-    public void testGlobalCompatModeDisabled_oldApp_doesntSupportAllScreens_usesCompatMode() {
-        setGlobalCompatibilityMode(false);
-        final ApplicationInfo info =
-                generateMockApplicationInfo(Build.VERSION_CODES.CUPCAKE, 0 /*flags*/);
-        assertThat(info.usesCompatibilityMode(), is(true));
-    }
-
-    // The following tests ensure that apps with newer target SDK use compat mode as expected.
-
-    @Test
-    public void testGlobalCompatModeEnabled_newApp_supportAllScreens_doesntUseCompatMode() {
-        setGlobalCompatibilityMode(true);
-        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS
-                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
-                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
-        final ApplicationInfo info = generateMockApplicationInfo(Build.VERSION_CODES.DONUT, flags);
-        assertThat(info.usesCompatibilityMode(), is(false));
-    }
-
-    @Test
-    public void testGlobalCompatModeEnabled_newApp_supportSomeScreens_doesntUseCompatMode() {
-        setGlobalCompatibilityMode(true);
-        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS
-                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
-                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
-        final ApplicationInfo info = generateMockApplicationInfo(Build.VERSION_CODES.DONUT, flags);
-        assertThat(info.usesCompatibilityMode(), is(false));
-    }
-
-    @Test
-    public void testGlobalCompatModeEnabled_newApp_supportOnlyOneScreen_doesntUseCompatMode() {
-        setGlobalCompatibilityMode(true);
-        final int flags = FLAG_SUPPORTS_NORMAL_SCREENS;
-        final ApplicationInfo info = generateMockApplicationInfo(Build.VERSION_CODES.DONUT, flags);
-        assertThat(info.usesCompatibilityMode(), is(false));
-    }
-
-    @Test
-    public void testGlobalCompatModeEnabled_newApp_doesntSupportAllScreens_usesCompatMode() {
-        setGlobalCompatibilityMode(true);
-        final ApplicationInfo info =
-                generateMockApplicationInfo(Build.VERSION_CODES.DONUT, 0 /*flags*/);
-        assertThat(info.usesCompatibilityMode(), is(true));
-    }
-
-    @Test
-    public void testGlobalCompatModeDisabled_newApp_supportAllScreens_doesntUseCompatMode() {
-        setGlobalCompatibilityMode(false);
-        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS
-                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
-                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
-        final ApplicationInfo info = generateMockApplicationInfo(Build.VERSION_CODES.DONUT, flags);
-        assertThat(info.usesCompatibilityMode(), is(false));
-    }
-
-    @Test
-    public void testGlobalCompatModeDisabled_newApp_supportSomeScreens_doesntUseCompatMode() {
-        setGlobalCompatibilityMode(false);
-        final int flags = (FLAG_SUPPORTS_LARGE_SCREENS
-                | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS
-                | FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
-        final ApplicationInfo info = generateMockApplicationInfo(Build.VERSION_CODES.DONUT, flags);
-        assertThat(info.usesCompatibilityMode(), is(false));
-    }
-
-    @Test
-    public void testGlobalCompatModeDisabled_newApp_supportOnlyOneScreen_doesntUseCompatMode() {
-        setGlobalCompatibilityMode(false);
-        final int flags = FLAG_SUPPORTS_NORMAL_SCREENS;
-        final ApplicationInfo info = generateMockApplicationInfo(Build.VERSION_CODES.DONUT, flags);
-        assertThat(info.usesCompatibilityMode(), is(false));
-    }
-
-    @Test
-    public void testGlobalCompatModeDisabled_newApp_doesntSupportAllScreens_doesntUseCompatMode() {
-        setGlobalCompatibilityMode(false);
-        final ApplicationInfo info =
-                generateMockApplicationInfo(Build.VERSION_CODES.DONUT, 0 /*flags*/);
-        assertThat(info.usesCompatibilityMode(), is(false));
-    }
-
-    private ApplicationInfo generateMockApplicationInfo(int targetSdkVersion, int flags) {
-        final ApplicationInfo info = new ApplicationInfo();
-        info.targetSdkVersion = targetSdkVersion;
-        info.flags |= flags;
-        when(mMockAndroidPackage.toAppInfoWithoutState()).thenReturn(info);
-        return PackageInfoUtils.generateApplicationInfo(mMockAndroidPackage,
-                0 /*flags*/, mMockUserState, 0 /*userId*/, null);
-    }
-
-    private void setGlobalCompatibilityMode(boolean enabled) {
-        if (ParsingPackageUtils.sCompatibilityModeEnabled == enabled) {
-            return;
-        }
-        ParsingPackageUtils.setCompatibilityModeEnabled(enabled);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index ce322f7..c81fbb4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -38,6 +38,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.UserHandle;
@@ -47,7 +48,6 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.SparseArray;
 
-import com.android.activitycontext.ActivityContext;
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
 import com.android.server.LocalServices;
@@ -611,34 +611,23 @@
         mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
 
         Bundle options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
-        IBinder result = ActivityContext.getWithContext(activity -> {
-            try {
-                IBinder targetTask = activity.getActivityToken();
-                mCrossProfileAppsServiceImpl.startActivityAsUser(
-                        mIApplicationThread,
-                        PACKAGE_ONE,
-                        FEATURE_ID,
-                        ACTIVITY_COMPONENT,
-                        UserHandle.of(PRIMARY_USER).getIdentifier(),
-                        true,
-                        targetTask,
-                        options);
-                return targetTask;
-            } catch (Exception re) {
-                return null;
-            }
-        });
-        if (result == null) {
-            throw new Exception();
-        }
-
+        Binder targetTask = new Binder();
+        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                mIApplicationThread,
+                PACKAGE_ONE,
+                FEATURE_ID,
+                ACTIVITY_COMPONENT,
+                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                true,
+                targetTask,
+                options);
         verify(mActivityTaskManagerInternal)
                 .startActivityAsUser(
                         nullable(IApplicationThread.class),
                         eq(PACKAGE_ONE),
                         eq(FEATURE_ID),
                         any(Intent.class),
-                        eq(result),
+                        eq(targetTask),
                         anyInt(),
                         eq(options),
                         eq(PRIMARY_USER));
@@ -789,4 +778,4 @@
                     PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
deleted file mode 100644
index 9ea7907..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.pm;
-
-import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
-
-import android.content.Context;
-import android.content.pm.ModuleInfo;
-import android.content.pm.PackageManager;
-import android.platform.test.annotations.Presubmit;
-import android.test.InstrumentationTestCase;
-
-import com.android.frameworks.servicestests.R;
-
-import org.mockito.Mock;
-
-import java.util.Collections;
-import java.util.List;
-
-@Presubmit
-public class ModuleInfoProviderTest extends InstrumentationTestCase {
-
-    @Mock private ApexManager mApexManager;
-
-    public void setUp() {
-        initMocks(this);
-    }
-
-    public void testSuccessfulParse() {
-        when(mApexManager.getApexModuleNameForPackageName("com.android.module1"))
-                .thenReturn("com.module1.apex");
-        when(mApexManager.getApexModuleNameForPackageName("com.android.module2"))
-                .thenReturn("com.module2.apex");
-
-        ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata);
-
-        List<ModuleInfo> mi = provider.getInstalledModules(PackageManager.MATCH_ALL);
-        assertEquals(2, mi.size());
-
-        Collections.sort(mi, (ModuleInfo m1, ModuleInfo m2) ->
-                m1.getPackageName().compareTo(m1.getPackageName()));
-        assertEquals("com.android.module1", mi.get(0).getPackageName());
-        assertEquals("com.android.module2", mi.get(1).getPackageName());
-
-        ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0);
-        assertEquals("com.android.module1", mi1.getPackageName());
-        assertEquals("module_1_name", mi1.getName());
-        assertEquals("com.module1.apex", mi1.getApexModuleName());
-        assertEquals(false, mi1.isHidden());
-
-        ModuleInfo mi2 = provider.getModuleInfo("com.android.module2", 0);
-        assertEquals("com.android.module2", mi2.getPackageName());
-        assertEquals("module_2_name", mi2.getName());
-        assertEquals("com.module2.apex", mi2.getApexModuleName());
-        assertEquals(true, mi2.isHidden());
-    }
-
-    public void testParseFailure_incorrectTopLevelElement() {
-        ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata1);
-        assertEquals(0, provider.getInstalledModules(PackageManager.MATCH_ALL).size());
-    }
-
-    public void testParseFailure_incorrectModuleElement() {
-        ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata2);
-        assertEquals(0, provider.getInstalledModules(PackageManager.MATCH_ALL).size());
-    }
-
-    public void testParse_unknownAttributesIgnored() {
-        ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata);
-
-        List<ModuleInfo> mi = provider.getInstalledModules(PackageManager.MATCH_ALL);
-        assertEquals(2, mi.size());
-
-        ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0);
-        assertEquals("com.android.module1", mi1.getPackageName());
-        assertEquals("module_1_name", mi1.getName());
-        assertEquals(false, mi1.isHidden());
-    }
-
-    /**
-     * Constructs an {@code ModuleInfoProvider} using the test package resources.
-     */
-    private ModuleInfoProvider getProvider(int resourceId) {
-        final Context ctx = getInstrumentation().getContext();
-        return new ModuleInfoProvider(
-                ctx.getResources().getXml(resourceId), ctx.getResources(), mApexManager);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
deleted file mode 100644
index 648f895..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.when;
-import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
-import static org.xmlpull.v1.XmlPullParser.START_TAG;
-
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.platform.test.annotations.Presubmit;
-import android.util.AtomicFile;
-import android.util.Slog;
-import android.util.Xml;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.os.BackgroundThread;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import libcore.io.IoUtils;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-@Presubmit
-public class PackageInstallerSessionTest {
-    @Rule
-    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
-
-    private File mTmpDir;
-    private AtomicFile mSessionsFile;
-    private static final String TAG_SESSIONS = "sessions";
-
-    @Mock
-    PackageManagerService mMockPackageManagerInternal;
-
-    @Mock
-    Computer mSnapshot;
-
-    @Before
-    public void setUp() throws Exception {
-        mTmpDir = mTemporaryFolder.newFolder("PackageInstallerSessionTest");
-        mSessionsFile = new AtomicFile(
-                new File(mTmpDir.getAbsolutePath() + "/sessions.xml"), "package-session");
-        MockitoAnnotations.initMocks(this);
-        when(mSnapshot.getPackageUid(anyString(), anyLong(), anyInt())).thenReturn(0);
-        when(mMockPackageManagerInternal.snapshotComputer()).thenReturn(mSnapshot);
-    }
-
-    @Test
-    public void testWriteAndRestoreSessionXmlSimpleSession() {
-        PackageInstallerSession session = createSimpleSession();
-        dumpSession(session);
-        List<PackageInstallerSession> restored = restoreSessions();
-        assertEquals(1, restored.size());
-        assertSessionsEquivalent(session, restored.get(0));
-    }
-
-    @Test
-    public void testWriteAndRestoreSessionXmlStagedSession() {
-        PackageInstallerSession session = createStagedSession();
-        dumpSession(session);
-        List<PackageInstallerSession> restored = restoreSessions();
-        assertEquals(1, restored.size());
-        assertSessionsEquivalent(session, restored.get(0));
-    }
-
-    @Test
-    public void testWriteAndRestoreSessionXmlGrantedPermission() {
-        PackageInstallerSession session = createSessionWithGrantedPermissions();
-        dumpSession(session);
-        List<PackageInstallerSession> restored = restoreSessions();
-        assertEquals(1, restored.size());
-        assertSessionsEquivalent(session, restored.get(0));
-    }
-
-    @Test
-    public void testWriteAndRestoreSessionXmlMultiPackageSessions() {
-        PackageInstallerSession session = createMultiPackageParentSession(123, new int[]{234, 345});
-        PackageInstallerSession childSession1 = createMultiPackageChildSession(234, 123);
-        PackageInstallerSession childSession2 = createMultiPackageChildSession(345, 123);
-        List<PackageInstallerSession> sessionGroup =
-                Arrays.asList(session, childSession1, childSession2);
-        dumpSessions(sessionGroup);
-        List<PackageInstallerSession> restored = restoreSessions();
-        assertEquals(3, restored.size());
-        assertSessionsEquivalent(sessionGroup, restored);
-    }
-
-    private PackageInstallerSession createSimpleSession() {
-        return createSession(false, false, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
-                null);
-    }
-
-    private PackageInstallerSession createStagedSession() {
-        return createSession(true, false, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
-                null);
-    }
-
-    private PackageInstallerSession createSessionWithGrantedPermissions() {
-        return createSession(false, true, 123, false, PackageInstaller.SessionInfo.INVALID_ID,
-                null);
-    }
-
-    private PackageInstallerSession createMultiPackageParentSession(int sessionId,
-                                                                    int[] childSessionIds) {
-        return createSession(false, false, sessionId, true,
-                PackageInstaller.SessionInfo.INVALID_ID, childSessionIds);
-    }
-
-    private PackageInstallerSession createMultiPackageChildSession(int sessionId,
-                                                                   int parentSessionId) {
-        return createSession(false, false, sessionId, false, parentSessionId, null);
-    }
-
-    private PackageInstallerSession createSession(boolean staged, boolean withGrantedPermissions,
-                                                  int sessionId, boolean isMultiPackage,
-                                                  int parentSessionId, int[] childSessionIds) {
-        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
-                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-        if (staged) {
-            params.isStaged = true;
-        }
-        if (withGrantedPermissions) {
-            params.grantedRuntimePermissions = new String[]{"permission1", "permission2"};
-        }
-        if (isMultiPackage) {
-            params.isMultiPackage = true;
-        }
-        InstallSource installSource = InstallSource.create("testInstallInitiator",
-                "testInstallOriginator", "testInstaller", "testAttributionTag",
-                PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
-        return new PackageInstallerSession(
-                /* callback */ null,
-                /* context */null,
-                /* pm */ mMockPackageManagerInternal,
-                /* sessionProvider */ null,
-                /* silentUpdatePolicy */ null,
-                /* looper */ BackgroundThread.getHandler().getLooper(),
-                /* stagingManager */ null,
-                /* sessionId */ sessionId,
-                /* userId */  456,
-                /* installerUid */ -1,
-                /* installSource */ installSource,
-                /* sessionParams */ params,
-                /* createdMillis */ 0L,
-                /* committedMillis */ 0L,
-                /* stageDir */ mTmpDir,
-                /* stageCid */ null,
-                /* files */ null,
-                /* checksums */ null,
-                /* prepared */ true,
-                /* committed */ true,
-                /* destroyed */ staged ? true : false,
-                /* sealed */ false,  // Setting to true would trigger some PM logic.
-                /* childSessionIds */ childSessionIds != null ? childSessionIds : new int[0],
-                /* parentSessionId */ parentSessionId,
-                /* isReady */ staged ? true : false,
-                /* isFailed */ false,
-                /* isApplied */false,
-                /* stagedSessionErrorCode */
-                PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
-                /* stagedSessionErrorMessage */ "some error");
-    }
-
-    private void dumpSession(PackageInstallerSession session) {
-        dumpSessions(Arrays.asList(session));
-    }
-
-    private void dumpSessions(List<PackageInstallerSession> sessions) {
-        FileOutputStream fos = null;
-        try {
-            fos = mSessionsFile.startWrite();
-
-            TypedXmlSerializer out = Xml.resolveSerializer(fos);
-            out.startDocument(null, true);
-            out.startTag(null, TAG_SESSIONS);
-            for (PackageInstallerSession session : sessions) {
-                session.write(out, mTmpDir);
-            }
-            out.endTag(null, TAG_SESSIONS);
-            out.endDocument();
-
-            mSessionsFile.finishWrite(fos);
-            Slog.d("PackageInstallerSessionTest", new String(mSessionsFile.readFully()));
-        } catch (IOException e) {
-            if (fos != null) {
-                mSessionsFile.failWrite(fos);
-            }
-        }
-    }
-
-    // This is roughly the logic used in PackageInstallerService to read the session. Note that
-    // this test stresses readFromXml method from PackageInstallerSession, and doesn't cover the
-    // PackageInstallerService portion of the parsing.
-    private List<PackageInstallerSession> restoreSessions() {
-        List<PackageInstallerSession> ret = new ArrayList<>();
-        FileInputStream fis = null;
-        try {
-            fis = mSessionsFile.openRead();
-            TypedXmlPullParser in = Xml.resolvePullParser(fis);
-
-            int type;
-            while ((type = in.next()) != END_DOCUMENT) {
-                if (type == START_TAG) {
-                    final String tag = in.getName();
-                    if (PackageInstallerSession.TAG_SESSION.equals(tag)) {
-                        final PackageInstallerSession session;
-                        try {
-                            session = PackageInstallerSession.readFromXml(in, null,
-                                    null, mMockPackageManagerInternal,
-                                    BackgroundThread.getHandler().getLooper(), null,
-                                    mTmpDir, null, null);
-                            ret.add(session);
-                        } catch (Exception e) {
-                            Slog.e("PackageInstallerSessionTest", "Exception ", e);
-                            continue;
-                        }
-                    }
-                }
-            }
-        } catch (FileNotFoundException e) {
-            // Missing sessions are okay, probably first boot
-        } catch (IOException | XmlPullParserException e) {
-
-        } finally {
-            IoUtils.closeQuietly(fis);
-        }
-        return ret;
-    }
-
-    private void assertSessionParamsEquivalent(PackageInstaller.SessionParams expected,
-                                               PackageInstaller.SessionParams actual) {
-        assertEquals(expected.mode, actual.mode);
-        assertEquals(expected.installFlags, actual.installFlags);
-        assertEquals(expected.installLocation, actual.installLocation);
-        assertEquals(expected.installReason, actual.installReason);
-        assertEquals(expected.sizeBytes, actual.sizeBytes);
-        assertEquals(expected.appPackageName, actual.appPackageName);
-        assertEquals(expected.appIcon, actual.appIcon);
-        assertEquals(expected.originatingUri, actual.originatingUri);
-        assertEquals(expected.originatingUid, actual.originatingUid);
-        assertEquals(expected.referrerUri, actual.referrerUri);
-        assertEquals(expected.abiOverride, actual.abiOverride);
-        assertEquals(expected.volumeUuid, actual.volumeUuid);
-        assertArrayEquals(expected.grantedRuntimePermissions, actual.grantedRuntimePermissions);
-        assertEquals(expected.installerPackageName, actual.installerPackageName);
-        assertEquals(expected.isMultiPackage, actual.isMultiPackage);
-        assertEquals(expected.isStaged, actual.isStaged);
-    }
-
-    private void assertSessionsEquivalent(List<PackageInstallerSession> expected,
-                                          List<PackageInstallerSession> actual) {
-        assertEquals(expected.size(), actual.size());
-        for (PackageInstallerSession expectedSession : expected) {
-            boolean foundSession = false;
-            for (PackageInstallerSession actualSession : actual) {
-                if (expectedSession.sessionId == actualSession.sessionId) {
-                    // We should only encounter each expected session once.
-                    assertFalse(foundSession);
-                    foundSession = true;
-                    assertSessionsEquivalent(expectedSession, actualSession);
-                }
-            }
-            assertTrue(foundSession);
-        }
-    }
-
-    private void assertSessionsEquivalent(PackageInstallerSession expected,
-                                          PackageInstallerSession actual) {
-        assertEquals(expected.sessionId, actual.sessionId);
-        assertEquals(expected.userId, actual.userId);
-        assertSessionParamsEquivalent(expected.params, actual.params);
-        assertEquals(expected.getInstallerUid(), actual.getInstallerUid());
-        assertEquals(expected.getInstallerPackageName(), actual.getInstallerPackageName());
-        assertInstallSourcesEquivalent(expected.getInstallSource(), actual.getInstallSource());
-        assertEquals(expected.stageDir.getAbsolutePath(), actual.stageDir.getAbsolutePath());
-        assertEquals(expected.stageCid, actual.stageCid);
-        assertEquals(expected.isPrepared(), actual.isPrepared());
-        assertEquals(expected.isStaged(), actual.isStaged());
-        assertEquals(expected.isSessionApplied(), actual.isSessionApplied());
-        assertEquals(expected.isSessionFailed(), actual.isSessionFailed());
-        assertEquals(expected.isSessionReady(), actual.isSessionReady());
-        assertEquals(expected.getSessionErrorCode(), actual.getSessionErrorCode());
-        assertEquals(expected.getSessionErrorMessage(),
-                actual.getSessionErrorMessage());
-        assertEquals(expected.isPrepared(), actual.isPrepared());
-        assertEquals(expected.isCommitted(), actual.isCommitted());
-        assertEquals(expected.isPreapprovalRequested(), actual.isPreapprovalRequested());
-        assertEquals(expected.createdMillis, actual.createdMillis);
-        assertEquals(expected.isSealed(), actual.isSealed());
-        assertEquals(expected.isMultiPackage(), actual.isMultiPackage());
-        assertEquals(expected.hasParentSessionId(), actual.hasParentSessionId());
-        assertEquals(expected.getParentSessionId(), actual.getParentSessionId());
-        assertArrayEquals(expected.getChildSessionIds(), actual.getChildSessionIds());
-    }
-
-    private void assertInstallSourcesEquivalent(InstallSource expected, InstallSource actual) {
-        assertEquals(expected.installerPackageName, actual.installerPackageName);
-        assertEquals(expected.initiatingPackageName, actual.initiatingPackageName);
-        assertEquals(expected.originatingPackageName, actual.originatingPackageName);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
deleted file mode 100644
index 1877d45..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ /dev/null
@@ -1,618 +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.server.pm;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.fail;
-
-import static java.lang.reflect.Modifier.isFinal;
-import static java.lang.reflect.Modifier.isPublic;
-import static java.lang.reflect.Modifier.isStatic;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AppGlobals;
-import android.content.IIntentReceiver;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.platform.test.annotations.Postsubmit;
-import android.util.SparseArray;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.HexDump;
-import com.android.server.pm.PerPackageReadTimeouts.Timeouts;
-import com.android.server.pm.PerPackageReadTimeouts.VersionCodes;
-
-import com.google.android.collect.Lists;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.regex.Pattern;
-
-// atest PackageManagerServiceTest
-// runtest -c com.android.server.pm.PackageManagerServiceTest frameworks-services
-// bit FrameworksServicesTests:com.android.server.pm.PackageManagerServiceTest
-@Postsubmit
-@RunWith(AndroidJUnit4.class)
-public class PackageManagerServiceTest {
-
-    private static final String PACKAGE_NAME = "com.android.frameworks.servicestests";
-
-    private static final String TEST_DATA_PATH = "/data/local/tmp/servicestests/";
-    private static final String TEST_APP_APK = "StubTestApp.apk";
-    private static final String TEST_PKG_NAME = "com.android.servicestests.apps.stubapp";
-
-    private IPackageManager mIPackageManager;
-
-    @Before
-    public void setUp() throws Exception {
-        mIPackageManager = AppGlobals.getPackageManager();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-    }
-
-    @Test
-    public void testPackageRemoval() {
-        class PackageSenderImpl implements PackageSender {
-            public void sendPackageBroadcast(final String action, final String pkg,
-                    final Bundle extras, final int flags, final String targetPkg,
-                    final IIntentReceiver finishedReceiver, final int[] userIds,
-                    int[] instantUserIds, SparseArray<int[]> broadcastAllowList,
-                    @Nullable Bundle bOptions) {
-            }
-
-            public void sendPackageAddedForNewUsers(@NonNull Computer snapshot, String packageName,
-                    boolean sendBootComplete, boolean includeStopped, int appId,
-                    int[] userIds, int[] instantUserIds, int dataLoaderType) {
-            }
-
-            @Override
-            public void notifyPackageAdded(String packageName, int uid) {
-            }
-
-            @Override
-            public void notifyPackageChanged(String packageName, int uid) {
-
-            }
-
-            @Override
-            public void notifyPackageRemoved(String packageName, int uid) {
-            }
-        }
-
-        PackageSenderImpl sender = new PackageSenderImpl();
-        PackageSetting setting = null;
-        PackageRemovedInfo pri = new PackageRemovedInfo(sender);
-
-        // Initial conditions: nothing there
-        Assert.assertNull(pri.mRemovedUsers);
-        Assert.assertNull(pri.mBroadcastUsers);
-
-        // populateUsers with nothing leaves nothing
-        pri.populateUsers(null, setting);
-        Assert.assertNull(pri.mBroadcastUsers);
-
-        // Create a real (non-null) PackageSetting and confirm that the removed
-        // users are copied properly
-        setting = new PackageSettingBuilder()
-                .setName("name")
-                .setRealName("realName")
-                .setCodePath("codePath")
-                .setLegacyNativeLibraryPathString("legacyNativeLibraryPathString")
-                .setPrimaryCpuAbiString("primaryCpuAbiString")
-                .setSecondaryCpuAbiString("secondaryCpuAbiString")
-                .setCpuAbiOverrideString("cpuAbiOverrideString")
-                .build();
-        pri.populateUsers(new int[] {
-                1, 2, 3, 4, 5
-        }, setting);
-        Assert.assertNotNull(pri.mBroadcastUsers);
-        Assert.assertEquals(5, pri.mBroadcastUsers.length);
-        Assert.assertNotNull(pri.mInstantUserIds);
-        Assert.assertEquals(0, pri.mInstantUserIds.length);
-
-        // Exclude a user
-        pri.mBroadcastUsers = null;
-        final int EXCLUDED_USER_ID = 4;
-        setting.setInstantApp(true, EXCLUDED_USER_ID);
-        pri.populateUsers(new int[] {
-                1, 2, 3, EXCLUDED_USER_ID, 5
-        }, setting);
-        Assert.assertNotNull(pri.mBroadcastUsers);
-        Assert.assertEquals(4, pri.mBroadcastUsers.length);
-        Assert.assertNotNull(pri.mInstantUserIds);
-        Assert.assertEquals(1, pri.mInstantUserIds.length);
-
-        // TODO: test that sendApplicationHiddenForUser() actually fills in
-        // broadcastUsers
-    }
-
-    @Test
-    public void testPartitions() {
-        String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" };
-        String[] appdir = { "app", "priv-app" };
-        for (int i = 0; i < partitions.length; i++) {
-            final ScanPartition scanPartition =
-                    PackageManagerService.SYSTEM_PARTITIONS.get(i);
-            for (int j = 0; j < appdir.length; j++) {
-                File path = new File(String.format("%s/%s/A.apk", partitions[i], appdir[j]));
-                Assert.assertEquals(j == 1 && i != 3, scanPartition.containsPrivApp(path));
-
-                final int scanFlag = scanPartition.scanFlag;
-                Assert.assertEquals(i == 1, scanFlag == PackageManagerService.SCAN_AS_VENDOR);
-                Assert.assertEquals(i == 2, scanFlag == PackageManagerService.SCAN_AS_ODM);
-                Assert.assertEquals(i == 3, scanFlag == PackageManagerService.SCAN_AS_OEM);
-                Assert.assertEquals(i == 4, scanFlag == PackageManagerService.SCAN_AS_PRODUCT);
-                Assert.assertEquals(i == 5, scanFlag == PackageManagerService.SCAN_AS_SYSTEM_EXT);
-            }
-        }
-    }
-
-    @Test
-    public void testKnownPackageToString_shouldNotGetUnknown() {
-        final List<String> packageNames = new ArrayList<>();
-        for (int i = 0; i <= KnownPackages.LAST_KNOWN_PACKAGE; i++) {
-            packageNames.add(KnownPackages.knownPackageToString(i));
-        }
-        assertWithMessage(
-                "The Ids of KnownPackage should be continuous and the string representation "
-                        + "should not be unknown.").that(
-                packageNames).containsNoneIn(Lists.newArrayList("Unknown"));
-    }
-
-    @Test
-    public void testKnownPackage_lastKnownPackageIsTheLast() throws Exception {
-        final List<Integer> knownPackageIds = getKnownPackageIdsList();
-        assertWithMessage(
-                "The last KnownPackage Id should be assigned to PackageManagerInternal"
-                        + ".LAST_KNOWN_PACKAGE.").that(
-                knownPackageIds.get(knownPackageIds.size() - 1)).isEqualTo(
-                KnownPackages.LAST_KNOWN_PACKAGE);
-    }
-
-    @Test
-    public void testKnownPackage_IdsShouldBeUniqueAndContinuous() throws Exception {
-        final List<Integer> knownPackageIds = getKnownPackageIdsList();
-        for (int i = 0, size = knownPackageIds.size(); i < size - 1; i++) {
-            assertWithMessage(
-                    "The KnownPackage Ids should be unique and continuous. KnownPackageIds = "
-                            + Arrays.toString(knownPackageIds.toArray())).that(
-                    knownPackageIds.get(i) + 1).isEqualTo(knownPackageIds.get(i + 1));
-        }
-    }
-
-    @Test
-    public void testTimeouts() {
-        Timeouts defaults = Timeouts.parse("3600000001:3600000002:3600000003");
-        Assert.assertEquals(3600000001L, defaults.minTimeUs);
-        Assert.assertEquals(3600000002L, defaults.minPendingTimeUs);
-        Assert.assertEquals(3600000003L, defaults.maxPendingTimeUs);
-
-        Timeouts empty = Timeouts.parse("");
-        Assert.assertEquals(3600000000L, empty.minTimeUs);
-        Assert.assertEquals(3600000000L, empty.minPendingTimeUs);
-        Assert.assertEquals(3600000000L, empty.maxPendingTimeUs);
-
-        Timeouts partial0 = Timeouts.parse("10000::");
-        Assert.assertEquals(10000L, partial0.minTimeUs);
-        Assert.assertEquals(3600000000L, partial0.minPendingTimeUs);
-        Assert.assertEquals(3600000000L, partial0.maxPendingTimeUs);
-
-        Timeouts partial1 = Timeouts.parse("10000:10001:");
-        Assert.assertEquals(10000L, partial1.minTimeUs);
-        Assert.assertEquals(10001L, partial1.minPendingTimeUs);
-        Assert.assertEquals(3600000000L, partial1.maxPendingTimeUs);
-
-        Timeouts fullDefault = Timeouts.parse("3600000000:3600000000:3600000000");
-        Assert.assertEquals(3600000000L, fullDefault.minTimeUs);
-        Assert.assertEquals(3600000000L, fullDefault.minPendingTimeUs);
-        Assert.assertEquals(3600000000L, fullDefault.maxPendingTimeUs);
-
-        Timeouts full = Timeouts.parse("10000:10001:10002");
-        Assert.assertEquals(10000L, full.minTimeUs);
-        Assert.assertEquals(10001L, full.minPendingTimeUs);
-        Assert.assertEquals(10002L, full.maxPendingTimeUs);
-
-        Timeouts invalid0 = Timeouts.parse(":10000");
-        Assert.assertEquals(3600000000L, invalid0.minTimeUs);
-        Assert.assertEquals(3600000000L, invalid0.minPendingTimeUs);
-        Assert.assertEquals(3600000000L, invalid0.maxPendingTimeUs);
-
-        Timeouts invalid1 = Timeouts.parse(":10000::");
-        Assert.assertEquals(3600000000L, invalid1.minTimeUs);
-        Assert.assertEquals(3600000000L, invalid1.minPendingTimeUs);
-        Assert.assertEquals(3600000000L, invalid1.maxPendingTimeUs);
-
-        Timeouts invalid2 = Timeouts.parse("10000:10001:abcd");
-        Assert.assertEquals(10000L, invalid2.minTimeUs);
-        Assert.assertEquals(10001L, invalid2.minPendingTimeUs);
-        Assert.assertEquals(3600000000L, invalid2.maxPendingTimeUs);
-
-        Timeouts invalid3 = Timeouts.parse(":10000:");
-        Assert.assertEquals(3600000000L, invalid3.minTimeUs);
-        Assert.assertEquals(3600000000L, invalid3.minPendingTimeUs);
-        Assert.assertEquals(3600000000L, invalid3.maxPendingTimeUs);
-
-        Timeouts invalid4 = Timeouts.parse("abcd:10001:10002");
-        Assert.assertEquals(3600000000L, invalid4.minTimeUs);
-        Assert.assertEquals(3600000000L, invalid4.minPendingTimeUs);
-        Assert.assertEquals(3600000000L, invalid4.maxPendingTimeUs);
-
-        Timeouts invalid5 = Timeouts.parse("::1000000000000000000000000");
-        Assert.assertEquals(3600000000L, invalid5.minTimeUs);
-        Assert.assertEquals(3600000000L, invalid5.minPendingTimeUs);
-        Assert.assertEquals(3600000000L, invalid5.maxPendingTimeUs);
-
-        Timeouts invalid6 = Timeouts.parse("-10000:10001:10002");
-        Assert.assertEquals(3600000000L, invalid6.minTimeUs);
-        Assert.assertEquals(3600000000L, invalid6.minPendingTimeUs);
-        Assert.assertEquals(3600000000L, invalid6.maxPendingTimeUs);
-    }
-
-    @Test
-    public void testVersionCodes() {
-        final VersionCodes defaults = VersionCodes.parse("");
-        Assert.assertEquals(Long.MIN_VALUE, defaults.minVersionCode);
-        Assert.assertEquals(Long.MAX_VALUE, defaults.maxVersionCode);
-
-        VersionCodes single = VersionCodes.parse("191000070");
-        Assert.assertEquals(191000070, single.minVersionCode);
-        Assert.assertEquals(191000070, single.maxVersionCode);
-
-        VersionCodes single2 = VersionCodes.parse("191000070-191000070");
-        Assert.assertEquals(191000070, single2.minVersionCode);
-        Assert.assertEquals(191000070, single2.maxVersionCode);
-
-        VersionCodes upto = VersionCodes.parse("-191000070");
-        Assert.assertEquals(Long.MIN_VALUE, upto.minVersionCode);
-        Assert.assertEquals(191000070, upto.maxVersionCode);
-
-        VersionCodes andabove = VersionCodes.parse("191000070-");
-        Assert.assertEquals(191000070, andabove.minVersionCode);
-        Assert.assertEquals(Long.MAX_VALUE, andabove.maxVersionCode);
-
-        VersionCodes range = VersionCodes.parse("191000070-201000070");
-        Assert.assertEquals(191000070, range.minVersionCode);
-        Assert.assertEquals(201000070, range.maxVersionCode);
-
-        VersionCodes invalid0 = VersionCodes.parse("201000070-191000070");
-        Assert.assertEquals(Long.MIN_VALUE, invalid0.minVersionCode);
-        Assert.assertEquals(Long.MAX_VALUE, invalid0.maxVersionCode);
-
-        VersionCodes invalid1 = VersionCodes.parse("abcd-191000070");
-        Assert.assertEquals(Long.MIN_VALUE, invalid1.minVersionCode);
-        Assert.assertEquals(191000070, invalid1.maxVersionCode);
-
-        VersionCodes invalid2 = VersionCodes.parse("abcd");
-        Assert.assertEquals(Long.MIN_VALUE, invalid2.minVersionCode);
-        Assert.assertEquals(Long.MAX_VALUE, invalid2.maxVersionCode);
-
-        VersionCodes invalid3 = VersionCodes.parse("191000070-abcd");
-        Assert.assertEquals(191000070, invalid3.minVersionCode);
-        Assert.assertEquals(Long.MAX_VALUE, invalid3.maxVersionCode);
-    }
-
-    @Test
-    public void testPerPackageReadTimeouts() {
-        final String sha256 = "336faefc91bb2dddf9b21829106fbc607b862132fecd273e1b6b3ea55f09d4e1";
-        final VersionCodes defVCs = VersionCodes.parse("");
-        final Timeouts defTs = Timeouts.parse("3600000001:3600000002:3600000003");
-
-        PerPackageReadTimeouts empty = PerPackageReadTimeouts.parse("", defVCs, defTs);
-        Assert.assertNull(empty);
-
-        PerPackageReadTimeouts packageOnly = PerPackageReadTimeouts.parse("package.com", defVCs,
-                defTs);
-        Assert.assertEquals("package.com", packageOnly.packageName);
-        Assert.assertEquals(null, packageOnly.sha256certificate);
-        Assert.assertEquals(Long.MIN_VALUE, packageOnly.versionCodes.minVersionCode);
-        Assert.assertEquals(Long.MAX_VALUE, packageOnly.versionCodes.maxVersionCode);
-        Assert.assertEquals(3600000001L, packageOnly.timeouts.minTimeUs);
-        Assert.assertEquals(3600000002L, packageOnly.timeouts.minPendingTimeUs);
-        Assert.assertEquals(3600000003L, packageOnly.timeouts.maxPendingTimeUs);
-
-        PerPackageReadTimeouts packageHash = PerPackageReadTimeouts.parse(
-                "package.com:" + sha256, defVCs, defTs);
-        Assert.assertEquals("package.com", packageHash.packageName);
-        Assert.assertEquals(sha256, bytesToHexString(packageHash.sha256certificate));
-        Assert.assertEquals(Long.MIN_VALUE, packageHash.versionCodes.minVersionCode);
-        Assert.assertEquals(Long.MAX_VALUE, packageHash.versionCodes.maxVersionCode);
-        Assert.assertEquals(3600000001L, packageHash.timeouts.minTimeUs);
-        Assert.assertEquals(3600000002L, packageHash.timeouts.minPendingTimeUs);
-        Assert.assertEquals(3600000003L, packageHash.timeouts.maxPendingTimeUs);
-
-        PerPackageReadTimeouts packageVersionCode = PerPackageReadTimeouts.parse(
-                "package.com::191000070", defVCs, defTs);
-        Assert.assertEquals("package.com", packageVersionCode.packageName);
-        Assert.assertEquals(null, packageVersionCode.sha256certificate);
-        Assert.assertEquals(191000070, packageVersionCode.versionCodes.minVersionCode);
-        Assert.assertEquals(191000070, packageVersionCode.versionCodes.maxVersionCode);
-        Assert.assertEquals(3600000001L, packageVersionCode.timeouts.minTimeUs);
-        Assert.assertEquals(3600000002L, packageVersionCode.timeouts.minPendingTimeUs);
-        Assert.assertEquals(3600000003L, packageVersionCode.timeouts.maxPendingTimeUs);
-
-        PerPackageReadTimeouts full = PerPackageReadTimeouts.parse(
-                "package.com:" + sha256 + ":191000070-201000070:10001:10002:10003", defVCs, defTs);
-        Assert.assertEquals("package.com", full.packageName);
-        Assert.assertEquals(sha256, bytesToHexString(full.sha256certificate));
-        Assert.assertEquals(191000070, full.versionCodes.minVersionCode);
-        Assert.assertEquals(201000070, full.versionCodes.maxVersionCode);
-        Assert.assertEquals(10001L, full.timeouts.minTimeUs);
-        Assert.assertEquals(10002L, full.timeouts.minPendingTimeUs);
-        Assert.assertEquals(10003L, full.timeouts.maxPendingTimeUs);
-    }
-
-    @Test
-    public void testGetPerPackageReadTimeouts() {
-        Assert.assertEquals(0, getPerPackageReadTimeouts(null).length);
-        Assert.assertEquals(0, getPerPackageReadTimeouts("").length);
-        Assert.assertEquals(0, getPerPackageReadTimeouts(",,,,").length);
-
-        final String sha256 = "0fae93f1a7925b4c68bbea80ad3eaa41acfc9bc6f10bf1054f5d93a2bd556093";
-
-        PerPackageReadTimeouts[] singlePackage = getPerPackageReadTimeouts(
-                "package.com:" + sha256 + ":191000070-201000070:10001:10002:10003");
-        Assert.assertEquals(1, singlePackage.length);
-        Assert.assertEquals("package.com", singlePackage[0].packageName);
-        Assert.assertEquals(sha256, bytesToHexString(singlePackage[0].sha256certificate));
-        Assert.assertEquals(191000070, singlePackage[0].versionCodes.minVersionCode);
-        Assert.assertEquals(201000070, singlePackage[0].versionCodes.maxVersionCode);
-        Assert.assertEquals(10001L, singlePackage[0].timeouts.minTimeUs);
-        Assert.assertEquals(10002L, singlePackage[0].timeouts.minPendingTimeUs);
-        Assert.assertEquals(10003L, singlePackage[0].timeouts.maxPendingTimeUs);
-
-        PerPackageReadTimeouts[] multiPackage = getPerPackageReadTimeouts("package.com:" + sha256
-                + ":191000070-201000070:10001:10002:10003,package1.com::123456");
-        Assert.assertEquals(2, multiPackage.length);
-        Assert.assertEquals("package.com", multiPackage[0].packageName);
-        Assert.assertEquals(sha256, bytesToHexString(multiPackage[0].sha256certificate));
-        Assert.assertEquals(191000070, multiPackage[0].versionCodes.minVersionCode);
-        Assert.assertEquals(201000070, multiPackage[0].versionCodes.maxVersionCode);
-        Assert.assertEquals(10001L, multiPackage[0].timeouts.minTimeUs);
-        Assert.assertEquals(10002L, multiPackage[0].timeouts.minPendingTimeUs);
-        Assert.assertEquals(10003L, multiPackage[0].timeouts.maxPendingTimeUs);
-        Assert.assertEquals("package1.com", multiPackage[1].packageName);
-        Assert.assertEquals(null, multiPackage[1].sha256certificate);
-        Assert.assertEquals(123456, multiPackage[1].versionCodes.minVersionCode);
-        Assert.assertEquals(123456, multiPackage[1].versionCodes.maxVersionCode);
-        Assert.assertEquals(3600000001L, multiPackage[1].timeouts.minTimeUs);
-        Assert.assertEquals(3600000002L, multiPackage[1].timeouts.minPendingTimeUs);
-        Assert.assertEquals(3600000003L, multiPackage[1].timeouts.maxPendingTimeUs);
-    }
-
-    // Report an error from the Computer structure validation test.
-    private void flag(String name, String msg) {
-        fail(name + " " + msg);
-    }
-
-    // Return a string that identifies a Method.  This is not very efficient but it is not
-    // called very often.
-    private String displayName(Method m) {
-        String r = m.getName();
-        String p = Arrays.toString(m.getGenericParameterTypes())
-                   .replaceAll("([a-zA-Z0-9]+\\.)+", "")
-                   .replace("class ", "")
-                   .replaceAll("^\\[", "(")
-                   .replaceAll("\\]$", ")");
-        return r + p;
-    }
-
-    // Match a method to an array of Methods.  Matching is on method signature: name and
-    // parameter types.  If a method in the declared array matches, return it.  Otherwise
-    // return null.
-    private Method matchMethod(Method m, Method[] declared) {
-        String n = m.getName();
-        Type[] t = m.getGenericParameterTypes();
-        for (int i = 0; i < declared.length; i++) {
-            Method l = declared[i];
-            if (l != null && l.getName().equals(n)
-                    && Arrays.equals(l.getGenericParameterTypes(), t)) {
-                Method result = l;
-                // Set the method to null since it has been visited already.
-                declared[i] = null;
-                return result;
-            }
-        }
-        return null;
-    }
-
-    private static PerPackageReadTimeouts[] getPerPackageReadTimeouts(String knownDigestersList) {
-        final String defaultTimeouts = "3600000001:3600000002:3600000003";
-        List<PerPackageReadTimeouts> result = PerPackageReadTimeouts.parseDigestersList(
-                defaultTimeouts, knownDigestersList);
-        if (result == null) {
-            return null;
-        }
-        return result.toArray(new PerPackageReadTimeouts[result.size()]);
-    }
-
-    private static String bytesToHexString(byte[] bytes) {
-        return HexDump.toHexString(bytes, 0, bytes.length, /*upperCase=*/ false);
-    }
-
-    private List<Integer> getKnownPackageIdsList() throws IllegalAccessException {
-        final ArrayList<Integer> knownPackageIds = new ArrayList<>();
-        final Field[] allFields = KnownPackages.class.getDeclaredFields();
-        for (Field field : allFields) {
-            final int modifier = field.getModifiers();
-            if (isPublic(modifier) && isStatic(modifier) && isFinal(modifier)
-                    && Pattern.matches("PACKAGE(_[A-Z]+)+", field.getName())) {
-                knownPackageIds.add(field.getInt(null));
-            }
-        }
-        Collections.sort(knownPackageIds);
-        return knownPackageIds;
-    }
-
-    @Test
-    public void testInstallReason_afterUpdate_keepUnchanged() throws Exception {
-        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
-        try {
-            // Try to install test APK with reason INSTALL_REASON_DEVICE_SETUP
-            runShellCommand("pm install --install-reason 3 " + testApk);
-            assertWithMessage("The install reason of test APK is incorrect.").that(
-                    mIPackageManager.getInstallReason(TEST_PKG_NAME,
-                            UserHandle.myUserId())).isEqualTo(
-                    PackageManager.INSTALL_REASON_DEVICE_SETUP);
-
-            // Try to update test APK with different reason INSTALL_REASON_USER
-            runShellCommand("pm install --install-reason 4 " + testApk);
-            assertWithMessage("The install reason should keep unchanged after update.").that(
-                    mIPackageManager.getInstallReason(TEST_PKG_NAME,
-                            UserHandle.myUserId())).isEqualTo(
-                    PackageManager.INSTALL_REASON_DEVICE_SETUP);
-        } finally {
-            runShellCommand("pm uninstall " + TEST_PKG_NAME);
-        }
-    }
-
-    @Test
-    public void testInstallReason_userRemainsUninstalled_keepUnknown() throws Exception {
-        Assume.assumeTrue(UserManager.supportsMultipleUsers());
-        final UserManager um = UserManager.get(
-                InstrumentationRegistry.getInstrumentation().getContext());
-        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
-        int userId = UserHandle.USER_NULL;
-        try {
-            // Try to install test APK with reason INSTALL_REASON_DEVICE_SETUP
-            runShellCommand("pm install --install-reason 3 " + testApk);
-            assertWithMessage("The install reason of test APK is incorrect.").that(
-                    mIPackageManager.getInstallReason(TEST_PKG_NAME,
-                            UserHandle.myUserId())).isEqualTo(
-                    PackageManager.INSTALL_REASON_DEVICE_SETUP);
-
-            // Create and start the 2nd user.
-            userId = um.createUser("Test User", 0 /* flags */).getUserHandle().getIdentifier();
-            runShellCommand("am start-user -w " + userId);
-            // Since the test APK isn't installed on the 2nd user, the reason should be unknown.
-            assertWithMessage("The test APK should not be installed in the 2nd user").that(
-                    mIPackageManager.getPackageInfo(TEST_PKG_NAME, 0 /* flags */, userId)).isNull();
-            assertWithMessage("The install reason in 2nd user should be unknown.").that(
-                    mIPackageManager.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo(
-                    PackageManager.INSTALL_REASON_UNKNOWN);
-        } finally {
-            runShellCommand("pm uninstall " + TEST_PKG_NAME);
-            if (userId != UserHandle.USER_NULL) {
-                um.removeUser(userId);
-            }
-        }
-    }
-
-    @Test
-    public void testInstallReason_installForAllUsers_sameReason() throws Exception {
-        Assume.assumeTrue(UserManager.supportsMultipleUsers());
-        final UserManager um = UserManager.get(
-                InstrumentationRegistry.getInstrumentation().getContext());
-        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
-        int userId = UserHandle.USER_NULL;
-        try {
-            // Create and start the 2nd user.
-            userId = um.createUser("Test User", 0 /* flags */).getUserHandle().getIdentifier();
-            runShellCommand("am start-user -w " + userId);
-
-            // Try to install test APK to all users with reason INSTALL_REASON_DEVICE_SETUP
-            runShellCommand("pm install --install-reason 3 " + testApk);
-            assertWithMessage("The install reason is inconsistent across users.").that(
-                    mIPackageManager.getInstallReason(TEST_PKG_NAME,
-                            UserHandle.myUserId())).isEqualTo(
-                    mIPackageManager.getInstallReason(TEST_PKG_NAME, userId));
-        } finally {
-            runShellCommand("pm uninstall " + TEST_PKG_NAME);
-            if (userId != UserHandle.USER_NULL) {
-                um.removeUser(userId);
-            }
-        }
-    }
-
-    @Test
-    public void testInstallReason_installSeparately_withSeparatedReason() throws Exception {
-        Assume.assumeTrue(UserManager.supportsMultipleUsers());
-        final UserManager um = UserManager.get(
-                InstrumentationRegistry.getInstrumentation().getContext());
-        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
-        int userId = UserHandle.USER_NULL;
-        try {
-            // Create and start the 2nd user.
-            userId = um.createUser("Test User", 0 /* flags */).getUserHandle().getIdentifier();
-            runShellCommand("am start-user -w " + userId);
-
-            // Try to install test APK on the current user with reason INSTALL_REASON_DEVICE_SETUP
-            runShellCommand("pm install --user cur --install-reason 3 " + testApk);
-            assertWithMessage("The install reason on the current user is incorrect.").that(
-                    mIPackageManager.getInstallReason(TEST_PKG_NAME,
-                            UserHandle.myUserId())).isEqualTo(
-                    PackageManager.INSTALL_REASON_DEVICE_SETUP);
-
-            // Try to install test APK on the 2nd user with reason INSTALL_REASON_USER
-            runShellCommand("pm install --user " + userId + " --install-reason 4 " + testApk);
-            assertWithMessage("The install reason on the 2nd user is incorrect.").that(
-                    mIPackageManager.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo(
-                    PackageManager.INSTALL_REASON_USER);
-        } finally {
-            runShellCommand("pm uninstall " + TEST_PKG_NAME);
-            if (userId != UserHandle.USER_NULL) {
-                um.removeUser(userId);
-            }
-        }
-    }
-
-    @Test
-    public void testSetSplashScreenTheme_samePackage_succeeds() throws Exception {
-        mIPackageManager.setSplashScreenTheme(PACKAGE_NAME, null /* themeName */,
-                UserHandle.myUserId());
-        // Invoking setSplashScreenTheme on the same package shouldn't get any exception.
-    }
-
-    @Test
-    public void testSetSplashScreenTheme_differentPackage_fails() throws Exception {
-        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
-        try {
-            runShellCommand("pm install " + testApk);
-            mIPackageManager.setSplashScreenTheme(TEST_PKG_NAME, null /* themeName */,
-                    UserHandle.myUserId());
-            fail("setSplashScreenTheme did not throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        } finally {
-            runShellCommand("pm uninstall " + TEST_PKG_NAME);
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
deleted file mode 100644
index 88932b0..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ /dev/null
@@ -1,1613 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_MORE_DETAILS;
-import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND;
-import static android.content.pm.parsing.FrameworkParsingPackageUtils.parsePublicKey;
-import static android.content.res.Resources.ID_NULL;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.app.PropertyInvalidatedCache;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.SuspendDialogInfo;
-import android.content.pm.UserInfo;
-import android.os.BaseBundle;
-import android.os.PersistableBundle;
-import android.os.Process;
-import android.os.UserHandle;
-import android.platform.test.annotations.Presubmit;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.LongSparseArray;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.permission.persistence.RuntimePermissionsPersistence;
-import com.android.server.LocalServices;
-import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.permission.LegacyPermissionDataProvider;
-import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.PackageUserState;
-import com.android.server.pm.pkg.PackageUserStateInternal;
-import com.android.server.pm.pkg.SuspendParams;
-import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
-import com.android.server.utils.Watchable;
-import com.android.server.utils.WatchableTester;
-import com.android.server.utils.WatchedArrayMap;
-import com.android.server.utils.WatchedArraySet;
-import com.android.server.utils.Watcher;
-
-import com.google.common.truth.Truth;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.security.PublicKey;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class PackageManagerSettingsTests {
-    private static final String TAG = "PackageManagerSettingsTests";
-    private static final String PACKAGE_NAME_1 = "com.android.app1";
-    private static final String PACKAGE_NAME_2 = "com.android.app2";
-    private static final String PACKAGE_NAME_3 = "com.android.app3";
-    private static final int TEST_RESOURCE_ID = 2131231283;
-
-    @Mock
-    RuntimePermissionsPersistence mRuntimePermissionsPersistence;
-    @Mock
-    LegacyPermissionDataProvider mPermissionDataProvider;
-    @Mock
-    DomainVerificationManagerInternal mDomainVerificationManager;
-    @Mock
-    Computer computer;
-
-    final ArrayMap<String, Long> mOrigFirstInstallTimes = new ArrayMap<>();
-
-    @Before
-    public void initializeMocks() {
-        MockitoAnnotations.initMocks(this);
-        when(mDomainVerificationManager.generateNewId())
-                .thenAnswer(invocation -> UUID.randomUUID());
-    }
-
-    @Before
-    public void setup() {
-        // Disable binder caches in this process.
-        PropertyInvalidatedCache.disableForTestMode();
-    }
-
-    /** make sure our initialized KeySetManagerService metadata matches packages.xml */
-    @Test
-    public void testReadKeySetSettings()
-            throws ReflectiveOperationException, IllegalAccessException {
-        /* write out files and read */
-        writeOldFiles();
-        Settings settings = makeSettings();
-        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
-        verifyKeySetMetaData(settings);
-    }
-
-    /** read in data, write it out, and read it back in.  Verify same. */
-    @Test
-    public void testWriteKeySetSettings()
-            throws ReflectiveOperationException, IllegalAccessException {
-        // write out files and read
-        writeOldFiles();
-        Settings settings = makeSettings();
-        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
-
-        // write out, read back in and verify the same
-        settings.writeLPr(computer);
-        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
-        verifyKeySetMetaData(settings);
-    }
-
-    @Test
-    public void testSettingsReadOld() {
-        // Write delegateshellthe package files and make sure they're parsed properly the first time
-        writeOldFiles();
-        Settings settings = makeSettings();
-        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
-        assertThat(settings.getPackageLPr(PACKAGE_NAME_3), is(notNullValue()));
-        assertThat(settings.getPackageLPr(PACKAGE_NAME_1), is(notNullValue()));
-
-        PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_1);
-        assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DEFAULT));
-        assertThat(ps.getNotLaunched(0), is(true));
-
-        ps = settings.getPackageLPr(PACKAGE_NAME_2);
-        assertThat(ps.getStopped(0), is(false));
-        assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DISABLED_USER));
-        assertThat(ps.getEnabled(1), is(COMPONENT_ENABLED_STATE_DEFAULT));
-    }
-
-    @Test
-    public void testNewPackageRestrictionsFile() throws ReflectiveOperationException {
-        // Write the package files and make sure they're parsed properly the first time
-        writeOldFiles();
-        Settings settings = makeSettings();
-        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
-        settings.writeLPr(computer);
-
-        // Create Settings again to make it read from the new files
-        settings = makeSettings();
-        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
-
-        PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_2);
-        assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DISABLED_USER));
-        assertThat(ps.getEnabled(1), is(COMPONENT_ENABLED_STATE_DEFAULT));
-
-        // Verify that the snapshot passes the same test
-        Settings snapshot = settings.snapshot();
-        ps = snapshot.getPackageLPr(PACKAGE_NAME_2);
-        assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DISABLED_USER));
-        assertThat(ps.getEnabled(1), is(COMPONENT_ENABLED_STATE_DEFAULT));
-    }
-
-    private static PersistableBundle createPersistableBundle(String packageName, long longVal,
-            double doubleVal, boolean boolVal, String textVal) {
-        final PersistableBundle bundle = new PersistableBundle();
-        bundle.putString(packageName + ".TEXT_VALUE", textVal);
-        bundle.putLong(packageName + ".LONG_VALUE", longVal);
-        bundle.putBoolean(packageName + ".BOOL_VALUE", boolVal);
-        bundle.putDouble(packageName + ".DOUBLE_VALUE", doubleVal);
-        return bundle;
-    }
-
-    @Test
-    public void testReadPackageRestrictions_noSuspendingPackage() {
-        writePackageRestrictions_noSuspendingPackageXml(0);
-        Settings settingsUnderTest = makeSettings();
-        final WatchableTester watcher =
-                new WatchableTester(settingsUnderTest, "noSuspendingPackage");
-        watcher.register();
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1));
-        settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
-        watcher.verifyChangeReported("put package 1");
-        // Collect a snapshot at the midway point (package 2 has not been added)
-        final Settings snapshot = settingsUnderTest.snapshot();
-        watcher.verifyNoChangeReported("snapshot");
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, createPackageSetting(PACKAGE_NAME_2));
-        watcher.verifyChangeReported("put package 2");
-        settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
-
-        PackageSetting ps1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1);
-        PackageUserStateInternal packageUserState1 = ps1.readUserState(0);
-        assertThat(packageUserState1.isSuspended(), is(true));
-        assertThat(packageUserState1.getSuspendParams().size(), is(1));
-        assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android"));
-        assertThat(packageUserState1.getSuspendParams().valueAt(0).getAppExtras(), is(nullValue()));
-        assertThat(packageUserState1.getSuspendParams().valueAt(0).getDialogInfo(),
-                is(nullValue()));
-        assertThat(packageUserState1.getSuspendParams().valueAt(0).getLauncherExtras(),
-                is(nullValue()));
-
-        // Verify that the snapshot returns the same answers
-        ps1 = snapshot.mPackages.get(PACKAGE_NAME_1);
-        packageUserState1 = ps1.readUserState(0);
-        assertThat(packageUserState1.isSuspended(), is(true));
-        assertThat(packageUserState1.getSuspendParams().size(), is(1));
-        assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android"));
-        assertThat(packageUserState1.getSuspendParams().valueAt(0).getAppExtras(), is(nullValue()));
-        assertThat(packageUserState1.getSuspendParams().valueAt(0).getDialogInfo(),
-                is(nullValue()));
-        assertThat(packageUserState1.getSuspendParams().valueAt(0).getLauncherExtras(),
-                is(nullValue()));
-
-        PackageSetting ps2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2);
-        PackageUserStateInternal packageUserState2 = ps2.readUserState(0);
-        assertThat(packageUserState2.isSuspended(), is(false));
-        assertThat(packageUserState2.getSuspendParams(), is(nullValue()));
-
-        // Verify that the snapshot returns different answers
-        ps2 = snapshot.mPackages.get(PACKAGE_NAME_2);
-        assertTrue(ps2 == null);
-    }
-
-    @Test
-    public void testReadPackageRestrictions_noSuspendParamsMap() {
-        writePackageRestrictions_noSuspendParamsMapXml(0);
-        final Settings settingsUnderTest = makeSettings();
-        final WatchableTester watcher =
-                new WatchableTester(settingsUnderTest, "noSuspendParamsMap");
-        watcher.register();
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1));
-        watcher.verifyChangeReported("put package 1");
-        settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
-        watcher.verifyChangeReported("readPackageRestrictions");
-
-        final PackageSetting ps1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1);
-        watcher.verifyNoChangeReported("get package 1");
-        final PackageUserStateInternal packageUserState1 = ps1.readUserState(0);
-        watcher.verifyNoChangeReported("readUserState");
-        assertThat(packageUserState1.isSuspended(), is(true));
-        assertThat(packageUserState1.getSuspendParams().size(), is(1));
-        assertThat(packageUserState1.getSuspendParams().keyAt(0), is(PACKAGE_NAME_3));
-        final SuspendParams params = packageUserState1.getSuspendParams().valueAt(0);
-        watcher.verifyNoChangeReported("fetch user state");
-        assertThat(params, is(notNullValue()));
-        assertThat(params.getAppExtras().size(), is(1));
-        assertThat(params.getAppExtras().getString("app_extra_string"), is("value"));
-        assertThat(params.getLauncherExtras().size(), is(1));
-        assertThat(params.getLauncherExtras().getLong("launcher_extra_long"), is(4L));
-        assertThat(params.getDialogInfo(), is(notNullValue()));
-        assertThat(params.getDialogInfo().getDialogMessage(), is("Dialog Message"));
-        assertThat(params.getDialogInfo().getTitleResId(), is(ID_NULL));
-        assertThat(params.getDialogInfo().getIconResId(), is(TEST_RESOURCE_ID));
-        assertThat(params.getDialogInfo().getNeutralButtonTextResId(), is(ID_NULL));
-        assertThat(params.getDialogInfo().getNeutralButtonAction(), is(BUTTON_ACTION_MORE_DETAILS));
-        assertThat(params.getDialogInfo().getDialogMessageResId(), is(ID_NULL));
-    }
-
-    @Test
-    public void testReadWritePackageRestrictions_suspendInfo() {
-        final Settings settingsUnderTest = makeSettings();
-        final WatchableTester watcher = new WatchableTester(settingsUnderTest, "suspendInfo");
-        watcher.register();
-        final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
-        final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
-        final PackageSetting ps3 = createPackageSetting(PACKAGE_NAME_3);
-
-        final PersistableBundle appExtras1 = createPersistableBundle(
-                PACKAGE_NAME_1, 1L, 0.01, true, "appString1");
-        final PersistableBundle appExtras2 = createPersistableBundle(
-                PACKAGE_NAME_2, 2L, 0.02, true, "appString2");
-
-        final PersistableBundle launcherExtras1 = createPersistableBundle(
-                PACKAGE_NAME_1, 10L, 0.1, false, "launcherString1");
-        final PersistableBundle launcherExtras2 = createPersistableBundle(
-                PACKAGE_NAME_2, 20L, 0.2, false, "launcherString2");
-
-        final SuspendDialogInfo dialogInfo1 = new SuspendDialogInfo.Builder()
-                .setIcon(0x11220001)
-                .setTitle("String Title")
-                .setMessage("1st message")
-                .setNeutralButtonText(0x11220003)
-                .setNeutralButtonAction(BUTTON_ACTION_MORE_DETAILS)
-                .build();
-        final SuspendDialogInfo dialogInfo2 = new SuspendDialogInfo.Builder()
-                .setIcon(0x22220001)
-                .setTitle(0x22220002)
-                .setMessage("2nd message")
-                .setNeutralButtonText("String button text")
-                .setNeutralButtonAction(BUTTON_ACTION_UNSUSPEND)
-                .build();
-
-        ps1.modifyUserState(0).putSuspendParams( "suspendingPackage1",
-                new SuspendParams(dialogInfo1, appExtras1, launcherExtras1));
-        ps1.modifyUserState(0).putSuspendParams( "suspendingPackage2",
-                new SuspendParams(dialogInfo2, appExtras2, launcherExtras2));
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
-        watcher.verifyChangeReported("put package 1");
-
-        ps2.modifyUserState(0).putSuspendParams( "suspendingPackage3",
-                new SuspendParams(null, appExtras1, null));
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
-        watcher.verifyChangeReported("put package 2");
-
-        ps3.modifyUserState(0).removeSuspension("irrelevant");
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_3, ps3);
-        watcher.verifyChangeReported("put package 3");
-
-        settingsUnderTest.writePackageRestrictionsLPr(0);
-        watcher.verifyChangeReported("writePackageRestrictions");
-
-        settingsUnderTest.mPackages.clear();
-        watcher.verifyChangeReported("clear packages");
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1));
-        watcher.verifyChangeReported("put package 1");
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, createPackageSetting(PACKAGE_NAME_2));
-        watcher.verifyChangeReported("put package 2");
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_3, createPackageSetting(PACKAGE_NAME_3));
-        watcher.verifyChangeReported("put package 3");
-        // now read and verify
-        settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
-        watcher.verifyChangeReported("readPackageRestrictions");
-        final PackageUserStateInternal readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1)
-                .readUserState(0);
-        watcher.verifyNoChangeReported("package get 1");
-        assertThat(readPus1.isSuspended(), is(true));
-        assertThat(readPus1.getSuspendParams().size(), is(2));
-        watcher.verifyNoChangeReported("read package param");
-
-        assertThat(readPus1.getSuspendParams().keyAt(0), is("suspendingPackage1"));
-        final SuspendParams params11 = readPus1.getSuspendParams().valueAt(0);
-        watcher.verifyNoChangeReported("read package param");
-        assertThat(params11, is(notNullValue()));
-        assertThat(params11.getDialogInfo(), is(dialogInfo1));
-        assertThat(BaseBundle.kindofEquals(params11.getAppExtras(), appExtras1), is(true));
-        assertThat(BaseBundle.kindofEquals(params11.getLauncherExtras(), launcherExtras1),
-                is(true));
-        watcher.verifyNoChangeReported("read package param");
-
-        assertThat(readPus1.getSuspendParams().keyAt(1), is("suspendingPackage2"));
-        final SuspendParams params12 = readPus1.getSuspendParams().valueAt(1);
-        assertThat(params12, is(notNullValue()));
-        assertThat(params12.getDialogInfo(), is(dialogInfo2));
-        assertThat(BaseBundle.kindofEquals(params12.getAppExtras(), appExtras2), is(true));
-        assertThat(BaseBundle.kindofEquals(params12.getLauncherExtras(), launcherExtras2),
-                is(true));
-        watcher.verifyNoChangeReported("read package param");
-
-        final PackageUserStateInternal readPus2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2)
-                .readUserState(0);
-        assertThat(readPus2.isSuspended(), is(true));
-        assertThat(readPus2.getSuspendParams().size(), is(1));
-        assertThat(readPus2.getSuspendParams().keyAt(0), is("suspendingPackage3"));
-        final SuspendParams params21 = readPus2.getSuspendParams().valueAt(0);
-        assertThat(params21, is(notNullValue()));
-        assertThat(params21.getDialogInfo(), is(nullValue()));
-        assertThat(BaseBundle.kindofEquals(params21.getAppExtras(), appExtras1), is(true));
-        assertThat(params21.getLauncherExtras(), is(nullValue()));
-        watcher.verifyNoChangeReported("read package param");
-
-        final PackageUserStateInternal readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3)
-                .readUserState(0);
-        assertThat(readPus3.isSuspended(), is(false));
-        assertThat(readPus3.getSuspendParams(), is(nullValue()));
-        watcher.verifyNoChangeReported("package get 3");
-    }
-
-    @Test
-    public void testPackageRestrictionsSuspendedDefault() {
-        final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1);
-        assertThat(defaultSetting.getUserStateOrDefault(0).isSuspended(), is(false));
-    }
-
-    @Test
-    public void testReadWritePackageRestrictions_distractionFlags() {
-        final Settings settingsUnderTest = makeSettings();
-        final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
-        final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
-        final PackageSetting ps3 = createPackageSetting(PACKAGE_NAME_3);
-
-        final int distractionFlags1 = PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
-        ps1.setDistractionFlags(distractionFlags1, 0);
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
-
-        final int distractionFlags2 = PackageManager.RESTRICTION_HIDE_NOTIFICATIONS
-                | PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
-        ps2.setDistractionFlags(distractionFlags2, 0);
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
-
-        final int distractionFlags3 = PackageManager.RESTRICTION_NONE;
-        ps3.setDistractionFlags(distractionFlags3, 0);
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_3, ps3);
-
-        settingsUnderTest.writePackageRestrictionsLPr(0);
-
-        settingsUnderTest.mPackages.clear();
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1));
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, createPackageSetting(PACKAGE_NAME_2));
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_3, createPackageSetting(PACKAGE_NAME_3));
-        // now read and verify
-        settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
-        final PackageUserState readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1)
-                .readUserState(0);
-        assertThat(readPus1.getDistractionFlags(), is(distractionFlags1));
-
-        final PackageUserState readPus2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2)
-                .readUserState(0);
-        assertThat(readPus2.getDistractionFlags(), is(distractionFlags2));
-
-        final PackageUserState readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3)
-                .readUserState(0);
-        assertThat(readPus3.getDistractionFlags(), is(distractionFlags3));
-    }
-
-    @Test
-    public void testWriteReadUsesStaticLibraries() {
-        final Settings settingsUnderTest = makeSettings();
-        final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
-        ps1.setAppId(Process.FIRST_APPLICATION_UID);
-        ps1.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed())
-                .setUid(ps1.getAppId())
-                .setSystem(true)
-                .hideAsFinal());
-        final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
-        ps2.setAppId(Process.FIRST_APPLICATION_UID + 1);
-        ps2.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_2).hideAsParsed())
-                .setUid(ps2.getAppId())
-                .hideAsFinal());
-
-        ps1.setUsesStaticLibraries(new String[] { "com.example.shared.one" });
-        ps1.setUsesStaticLibrariesVersions(new long[] { 12 });
-        ps1.setFlags(ps1.getFlags() | ApplicationInfo.FLAG_SYSTEM);
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
-        assertThat(settingsUnderTest.disableSystemPackageLPw(PACKAGE_NAME_1, false), is(true));
-
-        ps2.setUsesStaticLibraries(new String[] { "com.example.shared.two" });
-        ps2.setUsesStaticLibrariesVersions(new long[] { 34 });
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
-
-        settingsUnderTest.writeLPr(computer);
-
-        settingsUnderTest.mPackages.clear();
-        settingsUnderTest.mDisabledSysPackages.clear();
-
-        assertThat(settingsUnderTest.readLPw(computer, createFakeUsers()), is(true));
-
-        PackageSetting readPs1 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_1);
-        PackageSetting readPs2 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_2);
-
-        Truth.assertThat(readPs1).isNotNull();
-        Truth.assertThat(readPs1.getUsesStaticLibraries()).isNotNull();
-        Truth.assertThat(readPs1.getUsesStaticLibrariesVersions()).isNotNull();
-        Truth.assertThat(readPs2).isNotNull();
-        Truth.assertThat(readPs2.getUsesStaticLibraries()).isNotNull();
-        Truth.assertThat(readPs2.getUsesStaticLibrariesVersions()).isNotNull();
-
-        List<Long> ps1VersionsAsList = new ArrayList<>();
-        for (long version : ps1.getUsesStaticLibrariesVersions()) {
-            ps1VersionsAsList.add(version);
-        }
-
-        List<Long> ps2VersionsAsList = new ArrayList<>();
-        for (long version : ps2.getUsesStaticLibrariesVersions()) {
-            ps2VersionsAsList.add(version);
-        }
-
-        Truth.assertThat(readPs1.getUsesStaticLibraries()).asList()
-                .containsExactlyElementsIn(ps1.getUsesStaticLibraries()).inOrder();
-
-        Truth.assertThat(readPs1.getUsesStaticLibrariesVersions()).asList()
-                .containsExactlyElementsIn(ps1VersionsAsList).inOrder();
-
-        Truth.assertThat(readPs2.getUsesStaticLibraries()).asList()
-                .containsExactlyElementsIn(ps2.getUsesStaticLibraries()).inOrder();
-
-        Truth.assertThat(readPs2.getUsesStaticLibrariesVersions()).asList()
-                .containsExactlyElementsIn(ps2VersionsAsList).inOrder();
-    }
-
-    @Test
-    public void testWriteReadUsesSdkLibraries() {
-        final Settings settingsUnderTest = makeSettings();
-        final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
-        ps1.setAppId(Process.FIRST_APPLICATION_UID);
-        ps1.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed())
-                .setUid(ps1.getAppId())
-                .setSystem(true)
-                .hideAsFinal());
-        final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
-        ps2.setAppId(Process.FIRST_APPLICATION_UID + 1);
-        ps2.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_2).hideAsParsed())
-                .setUid(ps2.getAppId())
-                .hideAsFinal());
-
-        ps1.setUsesSdkLibraries(new String[] { "com.example.sdk.one" });
-        ps1.setUsesSdkLibrariesVersionsMajor(new long[] { 12 });
-        ps1.setFlags(ps1.getFlags() | ApplicationInfo.FLAG_SYSTEM);
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
-        assertThat(settingsUnderTest.disableSystemPackageLPw(PACKAGE_NAME_1, false), is(true));
-
-        ps2.setUsesSdkLibraries(new String[] { "com.example.sdk.two" });
-        ps2.setUsesSdkLibrariesVersionsMajor(new long[] { 34 });
-        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
-
-        settingsUnderTest.writeLPr(computer);
-
-        settingsUnderTest.mPackages.clear();
-        settingsUnderTest.mDisabledSysPackages.clear();
-
-        assertThat(settingsUnderTest.readLPw(computer, createFakeUsers()), is(true));
-
-        PackageSetting readPs1 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_1);
-        PackageSetting readPs2 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_2);
-
-        Truth.assertThat(readPs1).isNotNull();
-        Truth.assertThat(readPs1.getUsesSdkLibraries()).isNotNull();
-        Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).isNotNull();
-        Truth.assertThat(readPs2).isNotNull();
-        Truth.assertThat(readPs2.getUsesSdkLibraries()).isNotNull();
-        Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).isNotNull();
-
-        List<Long> ps1VersionsAsList = new ArrayList<>();
-        for (long version : ps1.getUsesSdkLibrariesVersionsMajor()) {
-            ps1VersionsAsList.add(version);
-        }
-
-        List<Long> ps2VersionsAsList = new ArrayList<>();
-        for (long version : ps2.getUsesSdkLibrariesVersionsMajor()) {
-            ps2VersionsAsList.add(version);
-        }
-
-        Truth.assertThat(readPs1.getUsesSdkLibraries()).asList()
-                .containsExactlyElementsIn(ps1.getUsesSdkLibraries()).inOrder();
-
-        Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).asList()
-                .containsExactlyElementsIn(ps1VersionsAsList).inOrder();
-
-        Truth.assertThat(readPs2.getUsesSdkLibraries()).asList()
-                .containsExactlyElementsIn(ps2.getUsesSdkLibraries()).inOrder();
-
-        Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).asList()
-                .containsExactlyElementsIn(ps2VersionsAsList).inOrder();
-    }
-
-    @Test
-    public void testPackageRestrictionsDistractionFlagsDefault() {
-        final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1);
-        assertThat(defaultSetting.getDistractionFlags(0), is(PackageManager.RESTRICTION_NONE));
-    }
-
-    @Test
-    public void testEnableDisable() {
-        // Write the package files and make sure they're parsed properly the first time
-        writeOldFiles();
-        Settings settings = makeSettings();
-        final WatchableTester watcher = new WatchableTester(settings, "testEnableDisable");
-        watcher.register();
-        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
-        watcher.verifyChangeReported("readLPw");
-
-        // Enable/Disable a package
-        PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_1);
-        watcher.verifyNoChangeReported("getPackageLPr");
-        assertThat(ps.getEnabled(0), is(not(COMPONENT_ENABLED_STATE_DISABLED)));
-        assertThat(ps.getEnabled(1), is(not(COMPONENT_ENABLED_STATE_ENABLED)));
-        ps.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, 0, null);
-        watcher.verifyChangeReported("setEnabled DISABLED");
-        ps.setEnabled(COMPONENT_ENABLED_STATE_ENABLED, 1, null);
-        watcher.verifyChangeReported("setEnabled ENABLED");
-        assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DISABLED));
-        assertThat(ps.getEnabled(1), is(COMPONENT_ENABLED_STATE_ENABLED));
-        watcher.verifyNoChangeReported("getEnabled");
-
-        // Enable/Disable a component
-        WatchedArraySet<String> components = new WatchedArraySet<String>();
-        String component1 = PACKAGE_NAME_1 + "/.Component1";
-        components.add(component1);
-        ps.setDisabledComponents(components, 0);
-        WatchedArraySet<String> componentsDisabled = ps.getDisabledComponents(0);
-        assertThat(componentsDisabled.size(), is(1));
-        assertThat(componentsDisabled.untrackedStorage().toArray()[0], is(component1));
-        boolean hasEnabled =
-                ps.getEnabledComponents(0) != null && ps.getEnabledComponents(1).size() > 0;
-        assertThat(hasEnabled, is(false));
-
-        // User 1 should not have any disabled components
-        boolean hasDisabled =
-                ps.getDisabledComponents(1) != null && ps.getDisabledComponents(1).size() > 0;
-        assertThat(hasDisabled, is(false));
-        ps.setEnabledComponents(components, 1);
-        assertThat(ps.getEnabledComponents(1).size(), is(1));
-        hasEnabled = ps.getEnabledComponents(0) != null && ps.getEnabledComponents(0).size() > 0;
-        assertThat(hasEnabled, is(false));
-    }
-
-    private static final String PACKAGE_NAME = "com.android.bar";
-    private static final String REAL_PACKAGE_NAME = "com.android.foo";
-    private static final File INITIAL_CODE_PATH =
-            new File(InstrumentationRegistry.getContext().getFilesDir(), "com.android.bar-1");
-    private static final File UPDATED_CODE_PATH =
-            new File(InstrumentationRegistry.getContext().getFilesDir(), "com.android.bar-2");
-    private static final long INITIAL_VERSION_CODE = 10023L;
-    private static final long UPDATED_VERSION_CODE = 10025L;
-
-    @Test
-    public void testPackageStateCopy01() {
-        final PackageSetting origPkgSetting01 = new PackageSetting(
-                PACKAGE_NAME,
-                REAL_PACKAGE_NAME,
-                INITIAL_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                "x86_64" /*primaryCpuAbiString*/,
-                "x86" /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                INITIAL_VERSION_CODE,
-                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
-                0,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
-        origPkgSetting01.setPkg(mockAndroidPackage(origPkgSetting01));
-        final PackageSetting testPkgSetting01 = new PackageSetting(origPkgSetting01);
-        verifySettingCopy(origPkgSetting01, testPkgSetting01);
-    }
-
-    @Test
-    public void testPackageStateCopy02() {
-        final PackageSetting origPkgSetting01 = new PackageSetting(
-                PACKAGE_NAME /*pkgName*/,
-                REAL_PACKAGE_NAME /*realPkgName*/,
-                INITIAL_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                "x86_64" /*primaryCpuAbiString*/,
-                "x86" /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                INITIAL_VERSION_CODE,
-                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
-                0,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
-        origPkgSetting01.setUserState(0, 100, 1, true, false, false, false, 0, null, false,
-                false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}),
-                new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning",
-                "splashScreenTheme", 1000L);
-        final PersistableBundle appExtras1 = createPersistableBundle(
-                PACKAGE_NAME_1, 1L, 0.01, true, "appString1");
-        final PersistableBundle launcherExtras1 = createPersistableBundle(
-                PACKAGE_NAME_1, 10L, 0.1, false, "launcherString1");
-        final SuspendDialogInfo dialogInfo1 = new SuspendDialogInfo.Builder()
-                .setIcon(0x11220001)
-                .setTitle("String Title")
-                .setMessage("1st message")
-                .setNeutralButtonText(0x11220003)
-                .setNeutralButtonAction(BUTTON_ACTION_MORE_DETAILS)
-                .build();
-        origPkgSetting01.modifyUserState(0).putSuspendParams("suspendingPackage1",
-                new SuspendParams(dialogInfo1, appExtras1, launcherExtras1));
-        origPkgSetting01.setPkg(mockAndroidPackage(origPkgSetting01));
-        final PackageSetting testPkgSetting01 = new PackageSetting(
-                PACKAGE_NAME /*pkgName*/,
-                REAL_PACKAGE_NAME /*realPkgName*/,
-                UPDATED_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                null /*primaryCpuAbiString*/,
-                null /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                UPDATED_VERSION_CODE,
-                0 /*pkgFlags*/,
-                0 /*pkgPrivateFlags*/,
-                0,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
-        testPkgSetting01.copyPackageSetting(origPkgSetting01, true);
-        verifySettingCopy(origPkgSetting01, testPkgSetting01);
-        verifyUserStatesCopy(origPkgSetting01.readUserState(0),
-                origPkgSetting01.getStateForUser(UserHandle.of(0)));
-        verifyUserStatesCopy(origPkgSetting01.readUserState(0),
-                testPkgSetting01.readUserState(0));
-    }
-
-    /** Update package */
-    @Test
-    public void testUpdatePackageSetting01() throws PackageManagerException {
-        final PackageSetting testPkgSetting01 =
-                createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
-        testPkgSetting01.setInstalled(false /*installed*/, 0 /*userId*/);
-        assertThat(testPkgSetting01.getFlags(), is(0));
-        assertThat(testPkgSetting01.getPrivateFlags(), is(0));
-        final PackageSetting oldPkgSetting01 = new PackageSetting(testPkgSetting01);
-        Settings.updatePackageSetting(
-                testPkgSetting01,
-                null /*disabledPkg*/,
-                null /*existingSharedUserSetting*/,
-                null /*sharedUser*/,
-                UPDATED_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPath*/,
-                "arm64-v8a" /*primaryCpuAbi*/,
-                "armeabi" /*secondaryCpuAbi*/,
-                0 /*pkgFlags*/,
-                0 /*pkgPrivateFlags*/,
-                UserManagerService.getInstance(),
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
-        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
-        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
-        assertThat(testPkgSetting01.getFlags(), is(0));
-        assertThat(testPkgSetting01.getPrivateFlags(), is(0));
-        final PackageUserState userState = testPkgSetting01.readUserState(0);
-        verifyUserState(userState, false /*notLaunched*/,
-                false /*stopped*/, false /*installed*/);
-    }
-
-    /** Update package; package now on /system, install for user '0' */
-    @Test
-    public void testUpdatePackageSetting02() throws PackageManagerException {
-        final PackageSetting testPkgSetting01 =
-                createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
-        testPkgSetting01.setInstalled(false /*installed*/, 0 /*userId*/);
-        assertThat(testPkgSetting01.getFlags(), is(0));
-        assertThat(testPkgSetting01.getPrivateFlags(), is(0));
-        final PackageSetting oldPkgSetting01 = new PackageSetting(testPkgSetting01);
-        Settings.updatePackageSetting(
-                testPkgSetting01,
-                null /*disabledPkg*/,
-                null /*existingSharedUserSetting*/,
-                null /*sharedUser*/,
-                UPDATED_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPath*/,
-                "arm64-v8a" /*primaryCpuAbi*/,
-                "armeabi" /*secondaryCpuAbi*/,
-                ApplicationInfo.FLAG_SYSTEM /*pkgFlags*/,
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED /*pkgPrivateFlags*/,
-                UserManagerService.getInstance(),
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
-        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
-        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
-        assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM));
-        assertThat(testPkgSetting01.isSystem(), is(true));
-        assertThat(testPkgSetting01.getPrivateFlags(), is(ApplicationInfo.PRIVATE_FLAG_PRIVILEGED));
-        assertThat(testPkgSetting01.isPrivileged(), is(true));
-        final PackageUserState userState = testPkgSetting01.readUserState(0);
-        verifyUserState(userState,  false /*notLaunched*/,
-                false /*stopped*/, true /*installed*/);
-    }
-
-    /** Update package; changing shared user throws exception */
-    @Test
-    public void testUpdatePackageSetting03() {
-        Settings settings = makeSettings();
-        final SharedUserSetting testUserSetting01 = createSharedUserSetting(
-                settings, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
-        final PackageSetting testPkgSetting01 =
-                createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
-        try {
-            Settings.updatePackageSetting(
-                    testPkgSetting01,
-                    null /*disabledPkg*/,
-                    null /*existingSharedUserSetting*/,
-                    testUserSetting01 /*sharedUser*/,
-                    UPDATED_CODE_PATH /*codePath*/,
-                    null /*legacyNativeLibraryPath*/,
-                    "arm64-v8a" /*primaryCpuAbi*/,
-                    "armeabi" /*secondaryCpuAbi*/,
-                    0 /*pkgFlags*/,
-                    0 /*pkgPrivateFlags*/,
-                    UserManagerService.getInstance(),
-                    null /*usesSdkLibraries*/,
-                    null /*usesSdkLibrariesVersions*/,
-                    null /*usesStaticLibraries*/,
-                    null /*usesStaticLibrariesVersions*/,
-                    null /*mimeGroups*/,
-                    UUID.randomUUID());
-            fail("Expected a PackageManagerException");
-        } catch (PackageManagerException expected) {
-        }
-    }
-
-    /** Create a new PackageSetting based on an original package setting */
-    @Test
-    public void testCreateNewSetting01() {
-        final PackageSetting originalPkgSetting01 =
-                createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
-        final PackageSignatures originalSignatures = originalPkgSetting01.getSignatures();
-        final PackageSetting testPkgSetting01 = Settings.createNewSetting(
-                REAL_PACKAGE_NAME,
-                originalPkgSetting01 /*originalPkg*/,
-                null /*disabledPkg*/,
-                null /*realPkgName*/,
-                null /*sharedUser*/,
-                UPDATED_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPath*/,
-                "arm64-v8a" /*primaryCpuAbi*/,
-                "armeabi" /*secondaryCpuAbi*/,
-                UPDATED_VERSION_CODE /*versionCode*/,
-                ApplicationInfo.FLAG_SYSTEM /*pkgFlags*/,
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED /*pkgPrivateFlags*/,
-                null /*installUser*/,
-                false /*allowInstall*/,
-                false /*instantApp*/,
-                false /*virtualPreload*/,
-                UserManagerService.getInstance(),
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
-        assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
-        assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
-        assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM));
-        assertThat(testPkgSetting01.isSystem(), is(true));
-        assertThat(testPkgSetting01.getPrivateFlags(), is(ApplicationInfo.PRIVATE_FLAG_PRIVILEGED));
-        assertThat(testPkgSetting01.isPrivileged(), is(true));
-        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
-        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
-        // signatures object must be different
-        assertNotSame(testPkgSetting01.getSignatures(), originalSignatures);
-        assertThat(testPkgSetting01.getVersionCode(), is(UPDATED_VERSION_CODE));
-        final PackageUserState userState = testPkgSetting01.readUserState(0);
-        verifyUserState(userState, false /*notLaunched*/, false /*stopped*/, true /*installed*/);
-    }
-
-    /** Create a new non-system PackageSetting */
-    @Test
-    public void testCreateNewSetting02() {
-        final PackageSetting testPkgSetting01 = Settings.createNewSetting(
-                PACKAGE_NAME,
-                null /*originalPkg*/,
-                null /*disabledPkg*/,
-                null /*realPkgName*/,
-                null /*sharedUser*/,
-                INITIAL_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPath*/,
-                "x86_64" /*primaryCpuAbiString*/,
-                "x86" /*secondaryCpuAbiString*/,
-                INITIAL_VERSION_CODE /*versionCode*/,
-                0 /*pkgFlags*/,
-                0 /*pkgPrivateFlags*/,
-                UserHandle.SYSTEM /*installUser*/,
-                true /*allowInstall*/,
-                false /*instantApp*/,
-                false /*virtualPreload*/,
-                UserManagerService.getInstance(),
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
-        assertThat(testPkgSetting01.getAppId(), is(0));
-        assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
-        assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
-        assertThat(testPkgSetting01.getFlags(), is(0));
-        assertThat(testPkgSetting01.getPrivateFlags(), is(0));
-        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("x86_64"));
-        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("x86_64"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("x86"));
-        assertThat(testPkgSetting01.getVersionCode(), is(INITIAL_VERSION_CODE));
-        // by default, the package is considered stopped
-        final PackageUserState userState = testPkgSetting01.readUserState(0);
-        verifyUserState(userState, true /*notLaunched*/, true /*stopped*/, true /*installed*/);
-    }
-
-    /** Create PackageSetting for a shared user */
-    @Test
-    public void testCreateNewSetting03() {
-        Settings settings = makeSettings();
-        final SharedUserSetting testUserSetting01 = createSharedUserSetting(
-                settings, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
-        final PackageSetting testPkgSetting01 = Settings.createNewSetting(
-                PACKAGE_NAME,
-                null /*originalPkg*/,
-                null /*disabledPkg*/,
-                null /*realPkgName*/,
-                testUserSetting01 /*sharedUser*/,
-                INITIAL_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPath*/,
-                "x86_64" /*primaryCpuAbiString*/,
-                "x86" /*secondaryCpuAbiString*/,
-                INITIAL_VERSION_CODE /*versionCode*/,
-                0 /*pkgFlags*/,
-                0 /*pkgPrivateFlags*/,
-                null /*installUser*/,
-                false /*allowInstall*/,
-                false /*instantApp*/,
-                false /*virtualPreload*/,
-                UserManagerService.getInstance(),
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
-        assertThat(testPkgSetting01.getAppId(), is(10064));
-        assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
-        assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
-        assertThat(testPkgSetting01.getFlags(), is(0));
-        assertThat(testPkgSetting01.getPrivateFlags(), is(0));
-        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("x86_64"));
-        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("x86_64"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("x86"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("x86"));
-        assertThat(testPkgSetting01.getVersionCode(), is(INITIAL_VERSION_CODE));
-        final PackageUserState userState = testPkgSetting01.readUserState(0);
-        verifyUserState(userState, false /*notLaunched*/, false /*stopped*/, true /*installed*/);
-    }
-
-    /** Create a new PackageSetting based on a disabled package setting */
-    @Test
-    public void testCreateNewSetting04() {
-        final PackageSetting disabledPkgSetting01 =
-                createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
-        disabledPkgSetting01.setAppId(10064);
-        final PackageSignatures disabledSignatures = disabledPkgSetting01.getSignatures();
-        final PackageSetting testPkgSetting01 = Settings.createNewSetting(
-                PACKAGE_NAME,
-                null /*originalPkg*/,
-                disabledPkgSetting01 /*disabledPkg*/,
-                null /*realPkgName*/,
-                null /*sharedUser*/,
-                UPDATED_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPath*/,
-                "arm64-v8a" /*primaryCpuAbi*/,
-                "armeabi" /*secondaryCpuAbi*/,
-                UPDATED_VERSION_CODE /*versionCode*/,
-                0 /*pkgFlags*/,
-                0 /*pkgPrivateFlags*/,
-                null /*installUser*/,
-                false /*allowInstall*/,
-                false /*instantApp*/,
-                false /*virtualPreload*/,
-                UserManagerService.getInstance(),
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
-        assertThat(testPkgSetting01.getAppId(), is(10064));
-        assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
-        assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
-        assertThat(testPkgSetting01.getFlags(), is(0));
-        assertThat(testPkgSetting01.getPrivateFlags(), is(0));
-        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
-        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
-        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
-        assertNotSame(testPkgSetting01.getSignatures(), disabledSignatures);
-        assertThat(testPkgSetting01.getVersionCode(), is(UPDATED_VERSION_CODE));
-        final PackageUserState userState = testPkgSetting01.readUserState(0);
-        verifyUserState(userState, false /*notLaunched*/, false /*stopped*/, true /*installed*/);
-    }
-
-    @Test
-    public void testSetPkgStateLibraryFiles_addNewFiles() {
-        final PackageSetting packageSetting = createPackageSetting("com.foo");
-        final CountDownLatch countDownLatch = new CountDownLatch(1);
-        packageSetting.registerObserver(new Watcher() {
-            @Override
-            public void onChange(Watchable what) {
-                countDownLatch.countDown();
-            }
-        });
-
-        final List<String> newUsesLibraryFiles = new ArrayList<>();
-        newUsesLibraryFiles.add("code/path/A.apk");
-        newUsesLibraryFiles.add("code/path/B.apk");
-        packageSetting.setPkgStateLibraryFiles(newUsesLibraryFiles);
-
-        assertThat(countDownLatch.getCount(), is(0L));
-    }
-
-    @Test
-    public void testSetPkgStateLibraryFiles_removeOneExistingFile() {
-        final PackageSetting packageSetting = createPackageSetting("com.foo");
-        final List<String> oldUsesLibraryFiles = new ArrayList<>();
-        oldUsesLibraryFiles.add("code/path/A.apk");
-        oldUsesLibraryFiles.add("code/path/B.apk");
-        oldUsesLibraryFiles.add("code/path/C.apk");
-        packageSetting.setPkgStateLibraryFiles(oldUsesLibraryFiles);
-        final CountDownLatch countDownLatch = new CountDownLatch(1);
-        packageSetting.registerObserver(new Watcher() {
-            @Override
-            public void onChange(Watchable what) {
-                countDownLatch.countDown();
-            }
-        });
-
-        final List<String> newUsesLibraryFiles = new ArrayList<>();
-        oldUsesLibraryFiles.add("code/path/A.apk");
-        oldUsesLibraryFiles.add("code/path/B.apk");
-        packageSetting.setPkgStateLibraryFiles(newUsesLibraryFiles);
-
-        assertThat(countDownLatch.getCount(), is(0L));
-    }
-
-    @Test
-    public void testSetPkgStateLibraryFiles_changeOneOfFile() {
-        final PackageSetting packageSetting = createPackageSetting("com.foo");
-        final List<String> oldUsesLibraryFiles = new ArrayList<>();
-        oldUsesLibraryFiles.add("code/path/A.apk");
-        oldUsesLibraryFiles.add("code/path/B.apk");
-        packageSetting.setPkgStateLibraryFiles(oldUsesLibraryFiles);
-        final CountDownLatch countDownLatch = new CountDownLatch(1);
-        packageSetting.registerObserver(new Watcher() {
-            @Override
-            public void onChange(Watchable what) {
-                countDownLatch.countDown();
-            }
-        });
-
-        final List<String> newUsesLibraryFiles = new ArrayList<>();
-        newUsesLibraryFiles.add("code/path/A.apk");
-        newUsesLibraryFiles.add("code/path/B-1.apk");
-        packageSetting.setPkgStateLibraryFiles(newUsesLibraryFiles);
-
-        assertThat(countDownLatch.getCount(), is(0L));
-    }
-
-    @Test
-    public void testSetPkgStateLibraryFiles_nothingChanged() {
-        final PackageSetting packageSetting = createPackageSetting("com.foo");
-        final List<String> oldUsesLibraryFiles = new ArrayList<>();
-        oldUsesLibraryFiles.add("code/path/A.apk");
-        oldUsesLibraryFiles.add("code/path/B.apk");
-        packageSetting.setPkgStateLibraryFiles(oldUsesLibraryFiles);
-        final CountDownLatch countDownLatch = new CountDownLatch(1);
-        packageSetting.registerObserver(new Watcher() {
-            @Override
-            public void onChange(Watchable what) {
-                countDownLatch.countDown();
-            }
-        });
-
-        final List<String> newUsesLibraryFiles = new ArrayList<>();
-        newUsesLibraryFiles.add("code/path/A.apk");
-        newUsesLibraryFiles.add("code/path/B.apk");
-        packageSetting.setPkgStateLibraryFiles(newUsesLibraryFiles);
-
-        assertThat(countDownLatch.getCount(), is(1L));
-    }
-
-    @Test
-    public void testSetPkgStateLibraryFiles_addNewSdks() {
-        final PackageSetting packageSetting = createPackageSetting("com.foo");
-        final CountDownLatch countDownLatch = new CountDownLatch(1);
-        packageSetting.registerObserver(new Watcher() {
-            @Override
-            public void onChange(Watchable what) {
-                countDownLatch.countDown();
-            }
-        });
-
-        final List<String> files = new ArrayList<>();
-        files.add("com.sdk1_123");
-        files.add("com.sdk9_876");
-        packageSetting.setUsesSdkLibraries(files.toArray(new String[files.size()]));
-
-        assertThat(countDownLatch.getCount(), is(0L));
-    }
-
-    @Test
-    public void testRegisterAndRemoveAppId() throws PackageManagerException {
-        // Test that the first new app UID should start from FIRST_APPLICATION_UID
-        final Settings settings = makeSettings();
-        final PackageSetting ps = createPackageSetting("com.foo");
-        assertTrue(settings.registerAppIdLPw(ps, false));
-        assertEquals(10000, ps.getAppId());
-        // Set up existing app IDs: 10000, 10001, 10003
-        final PackageSetting ps1 = createPackageSetting("com.foo1");
-        ps1.setAppId(10001);
-        final PackageSetting ps2 = createPackageSetting("com.foo2");
-        ps2.setAppId(10003);
-        final PackageSetting ps3 = createPackageSetting("com.foo3");
-        assertEquals(0, ps3.getAppId());
-        assertTrue(settings.registerAppIdLPw(ps1, false));
-        assertTrue(settings.registerAppIdLPw(ps2, false));
-        assertTrue(settings.registerAppIdLPw(ps3, false));
-        assertEquals(10001, ps1.getAppId());
-        assertEquals(10003, ps2.getAppId());
-        // Expecting the new one to start with the next available uid
-        assertEquals(10002, ps3.getAppId());
-        // Remove and insert a new one and the new one should not reuse the same uid
-        settings.removeAppIdLPw(10002);
-        final PackageSetting ps4 = createPackageSetting("com.foo4");
-        assertTrue(settings.registerAppIdLPw(ps4, false));
-        assertEquals(10004, ps4.getAppId());
-        // Keep adding more
-        final PackageSetting ps5 = createPackageSetting("com.foo5");
-        assertTrue(settings.registerAppIdLPw(ps5, false));
-        assertEquals(10005, ps5.getAppId());
-        // Remove the last one and the new one should use incremented uid
-        settings.removeAppIdLPw(10005);
-        final PackageSetting ps6 = createPackageSetting("com.foo6");
-        assertTrue(settings.registerAppIdLPw(ps6, false));
-        assertEquals(10006, ps6.getAppId());
-    }
-
-    /**
-     * Test replacing a PackageSetting with a SharedUserSetting in mAppIds
-     */
-    @Test
-    public void testAddPackageSetting() throws PackageManagerException {
-        final Settings settings = makeSettings();
-        final SharedUserSetting sus1 = new SharedUserSetting(
-                "TestUser", 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
-        sus1.mAppId = 10001;
-        final PackageSetting ps1 = createPackageSetting("com.foo");
-        ps1.setAppId(10001);
-        assertTrue(settings.registerAppIdLPw(ps1, false));
-        settings.addPackageSettingLPw(ps1, sus1);
-        assertSame(sus1, settings.getSharedUserSettingLPr(ps1));
-    }
-
-    private void verifyUserState(PackageUserState userState,
-            boolean notLaunched, boolean stopped, boolean installed) {
-        assertThat(userState.getEnabledState(), is(0));
-        assertThat(userState.isHidden(), is(false));
-        assertThat(userState.isInstalled(), is(installed));
-        assertThat(userState.isNotLaunched(), is(notLaunched));
-        assertThat(userState.isStopped(), is(stopped));
-        assertThat(userState.isSuspended(), is(false));
-        assertThat(userState.getDistractionFlags(), is(0));
-    }
-
-    private void verifyKeySetData(PackageKeySetData originalData, PackageKeySetData testData) {
-        assertThat(originalData.getProperSigningKeySet(),
-                equalTo(testData.getProperSigningKeySet()));
-        assertThat(originalData.getUpgradeKeySets(), is(testData.getUpgradeKeySets()));
-        assertThat(originalData.getAliases(), is(testData.getAliases()));
-    }
-
-    private void verifySettingCopy(PackageSetting origPkgSetting, PackageSetting testPkgSetting) {
-        assertThat(origPkgSetting, is(not(testPkgSetting)));
-        assertThat(origPkgSetting.getAppId(), is(testPkgSetting.getAppId()));
-        assertSame(origPkgSetting.getPath(), testPkgSetting.getPath());
-        assertThat(origPkgSetting.getPath(), is(testPkgSetting.getPath()));
-        assertSame(origPkgSetting.getPathString(), testPkgSetting.getPathString());
-        assertThat(origPkgSetting.getPathString(), is(testPkgSetting.getPathString()));
-        assertSame(origPkgSetting.getCpuAbiOverride(), testPkgSetting.getCpuAbiOverride());
-        assertThat(origPkgSetting.getCpuAbiOverride(), is(testPkgSetting.getCpuAbiOverride()));
-        assertThat(origPkgSetting.getDomainSetId(), is(testPkgSetting.getDomainSetId()));
-        assertSame(origPkgSetting.getInstallSource(), testPkgSetting.getInstallSource());
-        assertThat(origPkgSetting.isInstallPermissionsFixed(),
-                is(testPkgSetting.isInstallPermissionsFixed()));
-        verifyKeySetData(origPkgSetting.getKeySetData(), testPkgSetting.getKeySetData());
-        assertThat(origPkgSetting.getLastUpdateTime(), is(testPkgSetting.getLastUpdateTime()));
-        assertSame(origPkgSetting.getLegacyNativeLibraryPath(),
-                testPkgSetting.getLegacyNativeLibraryPath());
-        assertThat(origPkgSetting.getLegacyNativeLibraryPath(),
-                is(testPkgSetting.getLegacyNativeLibraryPath()));
-        if (origPkgSetting.getMimeGroups() != null
-                && origPkgSetting.getMimeGroups() != Collections.<String, Set<String>>emptyMap()) {
-            assertNotSame(origPkgSetting.getMimeGroups(), testPkgSetting.getMimeGroups());
-        }
-        assertThat(origPkgSetting.getMimeGroups(), is(testPkgSetting.getMimeGroups()));
-        assertNotSame(origPkgSetting.mLegacyPermissionsState,
-                testPkgSetting.mLegacyPermissionsState);
-        assertThat(origPkgSetting.mLegacyPermissionsState,
-                is(testPkgSetting.mLegacyPermissionsState));
-        assertThat(origPkgSetting.getPackageName(), is(testPkgSetting.getPackageName()));
-        // mOldCodePaths is _not_ copied
-        // assertNotSame(origPkgSetting.mOldCodePaths, testPkgSetting.mOldCodePaths);
-        // assertThat(origPkgSetting.mOldCodePaths, is(not(testPkgSetting.mOldCodePaths)));
-        assertSame(origPkgSetting.getPkg(), testPkgSetting.getPkg());
-        assertSame(origPkgSetting.getAndroidPackage(), origPkgSetting.getPkg());
-        assertThat(origPkgSetting.getAndroidPackage().getPackageName(),
-                is(origPkgSetting.getPackageName()));
-        // No equals() method for this object
-        // assertThat(origPkgSetting.pkg, is(testPkgSetting.pkg));
-        assertThat(origPkgSetting.getFlags(), is(testPkgSetting.getFlags()));
-        assertThat(origPkgSetting.getPrivateFlags(), is(testPkgSetting.getPrivateFlags()));
-        assertSame(origPkgSetting.getPrimaryCpuAbi(), testPkgSetting.getPrimaryCpuAbi());
-        assertThat(origPkgSetting.getPrimaryCpuAbi(), is(testPkgSetting.getPrimaryCpuAbi()));
-        assertSame(origPkgSetting.getPrimaryCpuAbiLegacy(), testPkgSetting.getPrimaryCpuAbiLegacy());
-        assertThat(origPkgSetting.getPrimaryCpuAbiLegacy(), is(testPkgSetting.getPrimaryCpuAbiLegacy()));
-        assertThat(origPkgSetting.getRealName(), is(testPkgSetting.getRealName()));
-        assertSame(origPkgSetting.getSecondaryCpuAbi(), testPkgSetting.getSecondaryCpuAbi());
-        assertThat(origPkgSetting.getSecondaryCpuAbi(), is(testPkgSetting.getSecondaryCpuAbi()));
-        assertSame(origPkgSetting.getSecondaryCpuAbiLegacy(), testPkgSetting.getSecondaryCpuAbiLegacy());
-        assertThat(origPkgSetting.getSecondaryCpuAbiLegacy(), is(testPkgSetting.getSecondaryCpuAbiLegacy()));
-        assertSame(origPkgSetting.getSignatures(), testPkgSetting.getSignatures());
-        assertThat(origPkgSetting.getSignatures(), is(testPkgSetting.getSignatures()));
-        assertThat(origPkgSetting.getLastModifiedTime(), is(testPkgSetting.getLastModifiedTime()));
-        assertNotSame(origPkgSetting.getUserStates(), is(testPkgSetting.getUserStates()));
-        // No equals() method for SparseArray object
-        // assertThat(origPkgSetting.getUserState(), is(testPkgSetting.getUserState()));
-        assertThat(origPkgSetting.getVersionCode(), is(testPkgSetting.getVersionCode()));
-        assertSame(origPkgSetting.getVolumeUuid(), testPkgSetting.getVolumeUuid());
-        assertThat(origPkgSetting.getVolumeUuid(), is(testPkgSetting.getVolumeUuid()));
-    }
-
-    private void verifyUserStatesCopy(PackageUserStateInternal origPus,
-            PackageUserStateInternal testPus) {
-        assertThat(userStateEquals(origPus, testPus), is(true));
-        // Verify suspendParams are copied over
-        assertThat(origPus.getSuspendParams(), is(notNullValue()));
-        assertThat(testPus.getSuspendParams(), is(notNullValue()));
-        SuspendParams origSuspendParams = origPus.getSuspendParams().valueAt(0);
-        SuspendParams testSuspendParams = testPus.getSuspendParams().valueAt(0);
-        assertThat(origSuspendParams.getDialogInfo().equals(testSuspendParams.getDialogInfo()),
-                is(true));
-        assertThat(BaseBundle.kindofEquals(
-                origSuspendParams.getAppExtras(), testSuspendParams.getAppExtras()), is(true));
-        assertThat(BaseBundle.kindofEquals(origSuspendParams.getLauncherExtras(),
-                testSuspendParams.getLauncherExtras()), is(true));
-    }
-
-    private void verifyUserStatesCopy(PackageUserState origPus,
-            PackageUserState testPus) {
-        assertThat(userStateEquals(origPus, testPus), is(true));
-        // Verify that disabledComponents and enabledComponents are copied
-        assertThat(origPus.getDisabledComponents(), is(notNullValue()));
-        assertThat(origPus.getDisabledComponents().equals(testPus.getDisabledComponents()),
-                is(true));
-        assertThat(origPus.getEnabledComponents(), is(notNullValue()));
-        assertThat(origPus.getEnabledComponents().equals(testPus.getEnabledComponents()),
-                is(true));
-    }
-
-    private boolean userStateEquals(PackageUserState userState, PackageUserState oldUserState) {
-        return userState.isHidden() == oldUserState.isHidden()
-                && userState.isStopped() == oldUserState.isStopped()
-                && userState.isInstalled() == oldUserState.isInstalled()
-                && userState.isSuspended() == oldUserState.isSuspended()
-                && userState.isNotLaunched() == oldUserState.isNotLaunched()
-                && userState.isInstantApp() == oldUserState.isInstantApp()
-                && userState.isVirtualPreload() == oldUserState.isVirtualPreload()
-                && (userState.getAllOverlayPaths() != null
-                ? userState.getAllOverlayPaths().equals(oldUserState.getAllOverlayPaths())
-                : oldUserState.getOverlayPaths() == null)
-                && userState.getCeDataInode() == oldUserState.getCeDataInode()
-                && userState.getDistractionFlags() == oldUserState.getDistractionFlags()
-                && userState.getFirstInstallTime() == oldUserState.getFirstInstallTime()
-                && userState.getEnabledState() == oldUserState.getEnabledState()
-                 && userState.getHarmfulAppWarning().equals(oldUserState.getHarmfulAppWarning())
-                && userState.getInstallReason() == oldUserState.getInstallReason()
-                && userState.getLastDisableAppCaller().equals(
-                        oldUserState.getLastDisableAppCaller())
-                && (userState.getSharedLibraryOverlayPaths() != null
-                ? userState.getSharedLibraryOverlayPaths().equals(
-                        oldUserState.getSharedLibraryOverlayPaths())
-                : oldUserState.getSharedLibraryOverlayPaths() == null)
-                && userState.getSplashScreenTheme().equals(
-                        oldUserState.getSplashScreenTheme())
-                && userState.getUninstallReason() == oldUserState.getUninstallReason();
-    }
-
-    private SharedUserSetting createSharedUserSetting(Settings settings, String userName,
-            int sharedUserId, int pkgFlags, int pkgPrivateFlags) {
-        return settings.addSharedUserLPw(
-                userName,
-                sharedUserId,
-                pkgFlags,
-                pkgPrivateFlags);
-    }
-    private PackageSetting createPackageSetting(int sharedUserId, int pkgFlags) {
-        return new PackageSetting(
-                PACKAGE_NAME,
-                REAL_PACKAGE_NAME,
-                INITIAL_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                "x86_64" /*primaryCpuAbiString*/,
-                "x86" /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                INITIAL_VERSION_CODE,
-                pkgFlags,
-                0 /*privateFlags*/,
-                sharedUserId,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
-    }
-
-    private PackageSetting createPackageSetting(String packageName) {
-        return new PackageSetting(
-                packageName,
-                packageName,
-                INITIAL_CODE_PATH /*codePath*/,
-                null /*legacyNativeLibraryPathString*/,
-                "x86_64" /*primaryCpuAbiString*/,
-                "x86" /*secondaryCpuAbiString*/,
-                null /*cpuAbiOverrideString*/,
-                INITIAL_VERSION_CODE,
-                0,
-                0 /*privateFlags*/,
-                0,
-                null /*usesSdkLibraries*/,
-                null /*usesSdkLibrariesVersions*/,
-                null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/,
-                UUID.randomUUID());
-    }
-
-    private @NonNull List<UserInfo> createFakeUsers() {
-        ArrayList<UserInfo> users = new ArrayList<>();
-        users.add(new UserInfo(UserHandle.USER_SYSTEM, "test user", UserInfo.FLAG_INITIALIZED));
-        return users;
-    }
-
-    private void writeFile(File file, byte[] data) {
-        file.mkdirs();
-        try {
-            AtomicFile aFile = new AtomicFile(file);
-            FileOutputStream fos = aFile.startWrite();
-            fos.write(data);
-            aFile.finishWrite(fos);
-        } catch (IOException ioe) {
-            Log.e(TAG, "Cannot write file " + file.getPath());
-        }
-    }
-
-    private void writePackagesXml() {
-        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages.xml"),
-                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
-                + "<packages>"
-                + "<last-platform-version internal=\"15\" external=\"0\" fingerprint=\"foo\" />"
-                + "<permission-trees>"
-                + "<item name=\"com.google.android.permtree\" package=\"com.google.android.permpackage\" />"
-                + "</permission-trees>"
-                + "<permissions>"
-                + "<item name=\"android.permission.WRITE_CALL_LOG\" package=\"android\" protection=\"1\" />"
-                + "<item name=\"android.permission.ASEC_ACCESS\" package=\"android\" protection=\"2\" />"
-                + "<item name=\"android.permission.REBOOT\" package=\"android\" protection=\"18\" />"
-                + "</permissions>"
-                + "<package name=\"com.android.app1\" codePath=\"/system/app/app1.apk\" nativeLibraryPath=\"/data/data/com.android.app1/lib\" flags=\"1\" ft=\"1360e2caa70\" it=\"135f2f80d08\" ut=\"1360e2caa70\" version=\"1109\" sharedUserId=\"11000\">"
-                + "<sigs count=\"1\">"
-                + "<cert index=\"0\" key=\"" + KeySetStrings.ctsKeySetCertA + "\" />"
-                + "</sigs>"
-                + "<proper-signing-keyset identifier=\"1\" />"
-                + "</package>"
-                + "<package name=\"com.android.app2\" codePath=\"/system/app/app2.apk\" nativeLibraryPath=\"/data/data/com.android.app2/lib\" flags=\"1\" ft=\"1360e578718\" it=\"135f2f80d08\" ut=\"1360e578718\" version=\"15\" enabled=\"3\" userId=\"11001\">"
-                + "<sigs count=\"1\">"
-                + "<cert index=\"0\" />"
-                + "</sigs>"
-                + "<proper-signing-keyset identifier=\"1\" />"
-                + "<defined-keyset alias=\"AB\" identifier=\"4\" />"
-                + "</package>"
-                + "<package name=\"com.android.app3\" codePath=\"/system/app/app3.apk\" nativeLibraryPath=\"/data/data/com.android.app3/lib\" flags=\"1\" ft=\"1360e577b60\" it=\"135f2f80d08\" ut=\"1360e577b60\" version=\"15\" userId=\"11030\">"
-                + "<sigs count=\"1\">"
-                + "<cert index=\"1\" key=\"" + KeySetStrings.ctsKeySetCertB + "\" />"
-                + "</sigs>"
-                + "<proper-signing-keyset identifier=\"2\" />"
-                + "<upgrade-keyset identifier=\"3\" />"
-                + "<defined-keyset alias=\"C\" identifier=\"3\" />"
-                + "</package>"
-                + "<shared-user name=\"com.android.shared1\" userId=\"11000\">"
-                + "<sigs count=\"1\">"
-                + "<cert index=\"1\" />"
-                + "</sigs>"
-                + "<perms>"
-                + "<item name=\"android.permission.REBOOT\" />"
-                + "</perms>"
-                + "</shared-user>"
-                + "<keyset-settings version=\"1\">"
-                + "<keys>"
-                + "<public-key identifier=\"1\" value=\"" + KeySetStrings.ctsKeySetPublicKeyA + "\" />"
-                + "<public-key identifier=\"2\" value=\"" + KeySetStrings.ctsKeySetPublicKeyB + "\" />"
-                + "<public-key identifier=\"3\" value=\"" + KeySetStrings.ctsKeySetPublicKeyC + "\" />"
-                + "</keys>"
-                + "<keysets>"
-                + "<keyset identifier=\"1\">"
-                + "<key-id identifier=\"1\" />"
-                + "</keyset>"
-                + "<keyset identifier=\"2\">"
-                + "<key-id identifier=\"2\" />"
-                + "</keyset>"
-                + "<keyset identifier=\"3\">"
-                + "<key-id identifier=\"3\" />"
-                + "</keyset>"
-                + "<keyset identifier=\"4\">"
-                + "<key-id identifier=\"1\" />"
-                + "<key-id identifier=\"2\" />"
-                + "</keyset>"
-                + "</keysets>"
-                + "<lastIssuedKeyId value=\"3\" />"
-                + "<lastIssuedKeySetId value=\"4\" />"
-                + "</keyset-settings>"
-                + "</packages>").getBytes());
-    }
-
-    private void writePackageRestrictions_noSuspendingPackageXml(final int userId) {
-        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/users/"
-                        + userId + "/package-restrictions.xml"),
-                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                        + "<package-restrictions>\n"
-                        + "    <pkg name=\"" + PACKAGE_NAME_1 + "\" suspended=\"true\" />"
-                        + "    <pkg name=\"" + PACKAGE_NAME_2 + "\" suspended=\"false\" />"
-                        + "    <preferred-activities />\n"
-                        + "    <persistent-preferred-activities />\n"
-                        + "    <crossProfile-intent-filters />\n"
-                        + "    <default-apps />\n"
-                        + "</package-restrictions>\n")
-                        .getBytes());
-    }
-
-    private void writePackageRestrictions_noSuspendParamsMapXml(final int userId) {
-        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/users/"
-                        + userId + "/package-restrictions.xml"),
-                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                        + "<package-restrictions>\n"
-                        + "    <pkg name=\"" + PACKAGE_NAME_1 + "\" "
-                        + "     suspended=\"true\" suspending-package=\"" + PACKAGE_NAME_3 + "\">\n"
-                        + "        <suspended-dialog-info dialogMessage=\"Dialog Message\""
-                        + "         iconResId=\"" + TEST_RESOURCE_ID + "\"/>\n"
-                        + "        <suspended-app-extras>\n"
-                        + "            <string name=\"app_extra_string\">value</string>\n"
-                        + "        </suspended-app-extras>\n"
-                        + "        <suspended-launcher-extras>\n"
-                        + "            <long name=\"launcher_extra_long\" value=\"4\" />\n"
-                        + "        </suspended-launcher-extras>\n"
-                        + "    </pkg>\n"
-                        + "    <preferred-activities />\n"
-                        + "    <persistent-preferred-activities />\n"
-                        + "    <crossProfile-intent-filters />\n"
-                        + "    <default-apps />\n"
-                        + "</package-restrictions>\n")
-                        .getBytes());
-    }
-
-    private void writeStoppedPackagesXml() {
-        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages-stopped.xml"),
-                ( "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
-                + "<stopped-packages>"
-                + "<pkg name=\"com.android.app1\" nl=\"1\" />"
-                + "<pkg name=\"com.android.app3\" nl=\"1\" />"
-                + "</stopped-packages>")
-                .getBytes());
-    }
-
-    private void writePackagesList() {
-        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages.list"),
-                ( "com.android.app1 11000 0 /data/data/com.android.app1 seinfo1"
-                + "com.android.app2 11001 0 /data/data/com.android.app2 seinfo2"
-                + "com.android.app3 11030 0 /data/data/com.android.app3 seinfo3")
-                .getBytes());
-    }
-
-    private void deleteSystemFolder() {
-        File systemFolder = new File(InstrumentationRegistry.getContext().getFilesDir(), "system");
-        deleteFolder(systemFolder);
-    }
-
-    private static void deleteFolder(File folder) {
-        File[] files = folder.listFiles();
-        if (files != null) {
-            for (File file : files) {
-                deleteFolder(file);
-            }
-        }
-        folder.delete();
-    }
-
-    private void writeOldFiles() {
-        deleteSystemFolder();
-        writePackagesXml();
-        writeStoppedPackagesXml();
-        writePackagesList();
-    }
-
-    @Before
-    public void createUserManagerServiceRef() throws ReflectiveOperationException {
-        InstrumentationRegistry.getInstrumentation().runOnMainSync((Runnable) () -> {
-            try {
-                // unregister the user manager from the local service
-                LocalServices.removeServiceForTest(UserManagerInternal.class);
-                new UserManagerService(InstrumentationRegistry.getContext());
-            } catch (Exception e) {
-                e.printStackTrace();
-                fail("Could not create user manager service; " + e);
-            }
-        });
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
-    }
-
-    private Settings makeSettings() {
-        return new Settings(InstrumentationRegistry.getContext().getFilesDir(),
-                mRuntimePermissionsPersistence, mPermissionDataProvider,
-                mDomainVerificationManager, null, new PackageManagerTracedLock());
-    }
-
-    private void verifyKeySetMetaData(Settings settings)
-            throws ReflectiveOperationException, IllegalAccessException {
-        WatchedArrayMap<String, PackageSetting> packages = settings.mPackages;
-        KeySetManagerService ksms = settings.getKeySetManagerService();
-
-        /* verify keyset and public key ref counts */
-        assertThat(KeySetUtils.getKeySetRefCount(ksms, 1), is(2));
-        assertThat(KeySetUtils.getKeySetRefCount(ksms, 2), is(1));
-        assertThat(KeySetUtils.getKeySetRefCount(ksms, 3), is(1));
-        assertThat(KeySetUtils.getKeySetRefCount(ksms, 4), is(1));
-        assertThat(KeySetUtils.getPubKeyRefCount(ksms, 1), is(2));
-        assertThat(KeySetUtils.getPubKeyRefCount(ksms, 2), is(2));
-        assertThat(KeySetUtils.getPubKeyRefCount(ksms, 3), is(1));
-
-        /* verify public keys properly read */
-        PublicKey keyA = parsePublicKey(KeySetStrings.ctsKeySetPublicKeyA);
-        PublicKey keyB = parsePublicKey(KeySetStrings.ctsKeySetPublicKeyB);
-        PublicKey keyC = parsePublicKey(KeySetStrings.ctsKeySetPublicKeyC);
-        assertThat(KeySetUtils.getPubKey(ksms, 1), is(keyA));
-        assertThat(KeySetUtils.getPubKey(ksms, 2), is(keyB));
-        assertThat(KeySetUtils.getPubKey(ksms, 3), is(keyC));
-
-        /* verify mapping is correct (ks -> pub keys) */
-        LongSparseArray<ArraySet<Long>> ksMapping = KeySetUtils.getKeySetMapping(ksms);
-        ArraySet<Long> mapping = ksMapping.get(1);
-        assertThat(mapping.size(), is(1));
-        assertThat(mapping.contains(new Long(1)), is(true));
-        mapping = ksMapping.get(2);
-        assertThat(mapping.size(), is(1));
-        assertThat(mapping.contains(new Long(2)), is(true));
-        mapping = ksMapping.get(3);
-        assertThat(mapping.size(), is(1));
-        assertThat(mapping.contains(new Long(3)), is(true));
-        mapping = ksMapping.get(4);
-        assertThat(mapping.size(), is(2));
-        assertThat(mapping.contains(new Long(1)), is(true));
-        assertThat(mapping.contains(new Long(2)), is(true));
-
-        /* verify lastIssuedIds are consistent */
-        assertThat(KeySetUtils.getLastIssuedKeyId(ksms), is(3L));
-        assertThat(KeySetUtils.getLastIssuedKeySetId(ksms), is(4L));
-
-        /* verify packages have been given the appropriate information */
-        PackageSetting ps = packages.get("com.android.app1");
-        assertThat(ps.getKeySetData().getProperSigningKeySet(), is(1L));
-        ps = packages.get("com.android.app2");
-        assertThat(ps.getKeySetData().getProperSigningKeySet(), is(1L));
-        assertThat(ps.getKeySetData().getAliases().get("AB"), is(4L));
-        ps = packages.get("com.android.app3");
-        assertThat(ps.getKeySetData().getProperSigningKeySet(), is(2L));
-        assertThat(ps.getKeySetData().getAliases().get("C"), is(3L));
-        assertThat(ps.getKeySetData().getUpgradeKeySets().length, is(1));
-        assertThat(ps.getKeySetData().getUpgradeKeySets()[0], is(3L));
-    }
-
-    @NonNull
-    private AndroidPackage mockAndroidPackage(PackageSetting pkgSetting) {
-        return PackageImpl.forTesting(pkgSetting.getPackageName()).hideAsParsed().hideAsFinal();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java
deleted file mode 100644
index a3f7ce5..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java
+++ /dev/null
@@ -1,3022 +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.system.OsConstants.S_IFDIR;
-import static android.system.OsConstants.S_IFMT;
-import static android.system.OsConstants.S_IRGRP;
-import static android.system.OsConstants.S_IROTH;
-import static android.system.OsConstants.S_IRWXU;
-import static android.system.OsConstants.S_ISDIR;
-import static android.system.OsConstants.S_IXGRP;
-import static android.system.OsConstants.S_IXOTH;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.KeySet;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionParams;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PermissionInfo;
-import android.content.pm.VerifierDeviceIdentity;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.FileUtils;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.StatFs;
-import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.StructStat;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SmallTest;
-import androidx.test.filters.Suppress;
-
-import com.android.frameworks.servicestests.R;
-import com.android.internal.content.InstallLocationUtils;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-
-import dalvik.system.VMRuntime;
-
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.TimeUnit;
-
-@Presubmit
-public class PackageManagerTests extends AndroidTestCase {
-    private static final boolean localLOGV = true;
-
-    public static final String TAG = "PackageManagerTests";
-
-    public static final long MAX_WAIT_TIME = 25 * 1000;
-
-    public static final long WAIT_TIME_INCR = 5 * 1000;
-
-    private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec";
-
-    private static final int APP_INSTALL_AUTO = InstallLocationUtils.APP_INSTALL_AUTO;
-
-    private static final int APP_INSTALL_DEVICE = InstallLocationUtils.APP_INSTALL_INTERNAL;
-
-    private static final int APP_INSTALL_SDCARD = InstallLocationUtils.APP_INSTALL_EXTERNAL;
-
-    void failStr(String errMsg) {
-        Log.w(TAG, "errMsg=" + errMsg);
-        fail(errMsg);
-    }
-
-    void failStr(Exception e) {
-        failStr(e.getMessage());
-    }
-
-    private abstract static class GenericReceiver extends BroadcastReceiver {
-        private boolean doneFlag = false;
-
-        boolean received = false;
-
-        Intent intent;
-
-        IntentFilter filter;
-
-        abstract boolean notifyNow(Intent intent);
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (notifyNow(intent)) {
-                synchronized (this) {
-                    received = true;
-                    doneFlag = true;
-                    this.intent = intent;
-                    notifyAll();
-                }
-            }
-        }
-
-        public boolean isDone() {
-            return doneFlag;
-        }
-
-        public void setFilter(IntentFilter filter) {
-            this.filter = filter;
-        }
-    }
-
-    private static class InstallReceiver extends GenericReceiver {
-        String pkgName;
-
-        InstallReceiver(String pkgName) {
-            this.pkgName = pkgName;
-            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
-            filter.addDataScheme("package");
-            super.setFilter(filter);
-        }
-
-        public boolean notifyNow(Intent intent) {
-            String action = intent.getAction();
-            if (!Intent.ACTION_PACKAGE_ADDED.equals(action)) {
-                return false;
-            }
-            Uri data = intent.getData();
-            String installedPkg = data.getEncodedSchemeSpecificPart();
-            if (pkgName.equals(installedPkg)) {
-                return true;
-            }
-            return false;
-        }
-    }
-
-    private static class LocalIntentReceiver {
-        private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
-
-        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
-            @Override
-            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
-                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
-                try {
-                    mResult.offer(intent, 5, TimeUnit.SECONDS);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        };
-
-        public IntentSender getIntentSender() {
-            return new IntentSender((IIntentSender) mLocalSender);
-        }
-
-        public Intent getResult() {
-            try {
-                return mResult.take();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    private PackageManager getPm() {
-        return mContext.getPackageManager();
-    }
-
-    private PackageInstaller getPi() {
-        return getPm().getPackageInstaller();
-    }
-
-    private void writeSplitToInstallSession(PackageInstaller.Session session, String inPath,
-            String splitName) throws RemoteException {
-        long sizeBytes = 0;
-        final File file = new File(inPath);
-        if (file.isFile()) {
-            sizeBytes = file.length();
-        } else {
-            return;
-        }
-
-        InputStream in = null;
-        OutputStream out = null;
-        try {
-            in = new FileInputStream(inPath);
-            out = session.openWrite(splitName, 0, sizeBytes);
-
-            int total = 0;
-            byte[] buffer = new byte[65536];
-            int c;
-            while ((c = in.read(buffer)) != -1) {
-                total += c;
-                out.write(buffer, 0, c);
-            }
-            session.fsync(out);
-        } catch (IOException e) {
-            fail("Error: failed to write; " + e.getMessage());
-        } finally {
-            IoUtils.closeQuietly(out);
-            IoUtils.closeQuietly(in);
-            IoUtils.closeQuietly(session);
-        }
-    }
-
-    private void invokeInstallPackage(Uri packageUri, int flags, GenericReceiver receiver,
-            boolean shouldSucceed) {
-        mContext.registerReceiver(receiver, receiver.filter);
-        synchronized (receiver) {
-            final String inPath = packageUri.getPath();
-            PackageInstaller.Session session = null;
-            try {
-                final SessionParams sessionParams =
-                        new SessionParams(SessionParams.MODE_FULL_INSTALL);
-                sessionParams.installFlags = flags;
-                final int sessionId = getPi().createSession(sessionParams);
-                session = getPi().openSession(sessionId);
-                writeSplitToInstallSession(session, inPath, "base.apk");
-                final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
-                session.commit(localReceiver.getIntentSender());
-                final Intent result = localReceiver.getResult();
-                final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                        PackageInstaller.STATUS_FAILURE);
-                if (shouldSucceed) {
-                    if (status != PackageInstaller.STATUS_SUCCESS) {
-                        fail("Installation should have succeeded, but got code " + status);
-                    }
-                } else {
-                    if (status == PackageInstaller.STATUS_SUCCESS) {
-                        fail("Installation should have failed");
-                    }
-                    // We'll never get a broadcast since the package failed to install
-                    return;
-                }
-                // Verify we received the broadcast
-                long waitTime = 0;
-                while ((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME)) {
-                    try {
-                        receiver.wait(WAIT_TIME_INCR);
-                        waitTime += WAIT_TIME_INCR;
-                    } catch (InterruptedException e) {
-                        Log.i(TAG, "Interrupted during sleep", e);
-                    }
-                }
-                if (!receiver.isDone()) {
-                    fail("Timed out waiting for PACKAGE_ADDED notification");
-                }
-            } catch (IllegalArgumentException | IOException | RemoteException e) {
-                Log.w(TAG, "Failed to install package; path=" + inPath, e);
-                fail("Failed to install package; path=" + inPath + ", e=" + e);
-            } finally {
-                IoUtils.closeQuietly(session);
-                mContext.unregisterReceiver(receiver);
-            }
-        }
-    }
-
-    private void invokeInstallPackageFail(Uri packageUri, int flags, int expectedResult) {
-        final String inPath = packageUri.getPath();
-        PackageInstaller.Session session = null;
-        try {
-            final SessionParams sessionParams =
-                    new SessionParams(SessionParams.MODE_FULL_INSTALL);
-            sessionParams.installFlags = flags;
-            final int sessionId = getPi().createSession(sessionParams);
-            session = getPi().openSession(sessionId);
-            writeSplitToInstallSession(session, inPath, "base.apk");
-            final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
-            session.commit(localReceiver.getIntentSender());
-            final Intent result = localReceiver.getResult();
-            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                    PackageInstaller.STATUS_SUCCESS);
-            String statusMessage = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
-            assertEquals(statusMessage, expectedResult, status);
-        } catch (IllegalArgumentException | IOException | RemoteException e) {
-            Log.w(TAG, "Failed to install package; path=" + inPath, e);
-            fail("Failed to install package; path=" + inPath + ", e=" + e);
-        } finally {
-            IoUtils.closeQuietly(session);
-        }
-    }
-
-    private Uri getInstallablePackage(int fileResId, File outFile) {
-        Resources res = mContext.getResources();
-        InputStream is = null;
-        try {
-            is = res.openRawResource(fileResId);
-        } catch (NotFoundException e) {
-            failStr("Failed to load resource with id: " + fileResId);
-        }
-        FileUtils.setPermissions(outFile.getPath(),
-                FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IRWXO,
-                -1, -1);
-        assertTrue(FileUtils.copyToFile(is, outFile));
-        FileUtils.setPermissions(outFile.getPath(),
-                FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IRWXO,
-                -1, -1);
-        return Uri.fromFile(outFile);
-    }
-
-    private ParsedPackage parsePackage(Uri packageURI) {
-        final String archiveFilePath = packageURI.getPath();
-        ParseResult<ParsedPackage> result = ParsingPackageUtils.parseDefaultOneTime(
-                new File(archiveFilePath), 0 /*flags*/, Collections.emptyList(),
-                false /*collectCertificates*/);
-        if (result.isError()) {
-            throw new IllegalStateException(result.getErrorMessage(), result.getException());
-        }
-        return result.getResult();
-    }
-
-    private boolean checkSd(long pkgLen) {
-        String status = Environment.getExternalStorageState();
-        if (!status.equals(Environment.MEDIA_MOUNTED)) {
-            return false;
-        }
-        long sdSize = -1;
-        StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath());
-        sdSize = (long) sdStats.getAvailableBlocks() * (long) sdStats.getBlockSize();
-        // TODO check for thresholds here
-        return pkgLen <= sdSize;
-
-    }
-
-    private boolean checkInt(long pkgLen) {
-        StatFs intStats = new StatFs(Environment.getDataDirectory().getPath());
-        long intSize = (long) intStats.getBlockCount() * (long) intStats.getBlockSize();
-        long iSize = (long) intStats.getAvailableBlocks() * (long) intStats.getBlockSize();
-        // TODO check for thresholds here?
-        return pkgLen <= iSize;
-    }
-
-    private static final int INSTALL_LOC_INT = 1;
-
-    private static final int INSTALL_LOC_SD = 2;
-
-    private static final int INSTALL_LOC_ERR = -1;
-
-    private int getInstallLoc(int flags, int expInstallLocation, long pkgLen) {
-        // Flags explicitly over ride everything else.
-        if ((flags & PackageManager.INSTALL_INTERNAL) != 0) {
-            return INSTALL_LOC_INT;
-        }
-        // Manifest option takes precedence next
-        if (expInstallLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
-            if (checkSd(pkgLen)) {
-                return INSTALL_LOC_SD;
-            }
-            if (checkInt(pkgLen)) {
-                return INSTALL_LOC_INT;
-            }
-            return INSTALL_LOC_ERR;
-        }
-        if (expInstallLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
-            if (checkInt(pkgLen)) {
-                return INSTALL_LOC_INT;
-            }
-            return INSTALL_LOC_ERR;
-        }
-        if (expInstallLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
-            // Check for free memory internally
-            if (checkInt(pkgLen)) {
-                return INSTALL_LOC_INT;
-            }
-            // Check for free memory externally
-            if (checkSd(pkgLen)) {
-                return INSTALL_LOC_SD;
-            }
-            return INSTALL_LOC_ERR;
-        }
-        // Check for settings preference.
-        boolean checkSd = false;
-        int userPref = getDefaultInstallLoc();
-        if (userPref == APP_INSTALL_DEVICE) {
-            if (checkInt(pkgLen)) {
-                return INSTALL_LOC_INT;
-            }
-            return INSTALL_LOC_ERR;
-        } else if (userPref == APP_INSTALL_SDCARD) {
-            if (checkSd(pkgLen)) {
-                return INSTALL_LOC_SD;
-            }
-            return INSTALL_LOC_ERR;
-        }
-        // Default system policy for apps with no manifest option specified.
-        // Check for free memory internally
-        if (checkInt(pkgLen)) {
-            return INSTALL_LOC_INT;
-        }
-        return INSTALL_LOC_ERR;
-    }
-
-    private void assertInstall(ParsedPackage pkg, int flags, int expInstallLocation) {
-        try {
-            String pkgName = pkg.getPackageName();
-            ApplicationInfo info = getPm().getApplicationInfo(pkgName, 0);
-            assertNotNull(info);
-            assertEquals(pkgName, info.packageName);
-            File dataDir = Environment.getDataDirectory();
-            String appInstallParent = new File(dataDir, "app").getPath();
-            File srcDir = new File(info.sourceDir);
-            String srcPathParent = srcDir.getParentFile().getParentFile().getParent();
-            File publicSrcDir = new File(info.publicSourceDir);
-            String publicSrcPath = publicSrcDir.getParentFile().getParentFile().getParent();
-            long pkgLen = new File(info.sourceDir).length();
-            String expectedLibPath = new File(new File(info.sourceDir).getParentFile(), "lib")
-                    .getPath();
-
-            int rLoc = getInstallLoc(flags, expInstallLocation, pkgLen);
-            if (rLoc == INSTALL_LOC_INT) {
-                assertEquals(appInstallParent, srcPathParent);
-                assertEquals(appInstallParent, publicSrcPath);
-                assertStartsWith("Native library should point to shared lib directory",
-                        expectedLibPath, info.nativeLibraryDir);
-                assertDirOwnerGroupPermsIfExists(
-                        "Native library directory should be owned by system:system and 0755",
-                        Process.SYSTEM_UID, Process.SYSTEM_UID,
-                        S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH,
-                        info.nativeLibraryDir);
-                assertFalse((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
-
-                // Make sure the native library dir is not a symlink
-                final File nativeLibDir = new File(info.nativeLibraryDir);
-                if (nativeLibDir.exists()) {
-                    try {
-                        assertEquals("Native library dir should not be a symlink",
-                                info.nativeLibraryDir, nativeLibDir.getCanonicalPath());
-                    } catch (IOException e) {
-                        fail("Can't read " + nativeLibDir.getPath());
-                    }
-                }
-            } else if (rLoc == INSTALL_LOC_SD) {
-                assertTrue("Application flags (" + info.flags
-                        + ") should contain FLAG_EXTERNAL_STORAGE",
-                        (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
-                // Might need to check:
-                // ((info.privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0)
-                assertStartsWith("The APK path should point to the ASEC",
-                        SECURE_CONTAINERS_PREFIX, srcPathParent);
-                assertStartsWith("The public APK path should point to the ASEC",
-                        SECURE_CONTAINERS_PREFIX, publicSrcPath);
-                assertStartsWith("The native library path should point to the ASEC",
-                        SECURE_CONTAINERS_PREFIX, info.nativeLibraryDir);
-
-                // Make sure the native library in /data/data/<app>/lib is a
-                // symlink to the ASEC
-                final File nativeLibSymLink = new File(info.dataDir, "lib");
-                assertTrue("Native library symlink should exist at " + nativeLibSymLink.getPath(),
-                        nativeLibSymLink.exists());
-                try {
-                    assertEquals(nativeLibSymLink.getPath() + " should be a symlink to "
-                            + info.nativeLibraryDir, info.nativeLibraryDir,
-                            nativeLibSymLink.getCanonicalPath());
-                } catch (IOException e) {
-                    fail("Can't read " + nativeLibSymLink.getPath());
-                }
-            } else {
-                // TODO handle error. Install should have failed.
-                fail("Install should have failed");
-            }
-        } catch (NameNotFoundException e) {
-            failStr("failed with exception : " + e);
-        }
-    }
-
-    private void assertDirOwnerGroupPermsIfExists(String reason, int uid, int gid, int perms,
-            String path) {
-        if (!new File(path).exists()) {
-            return;
-        }
-
-        final StructStat stat;
-        try {
-            stat = Os.lstat(path);
-        } catch (ErrnoException e) {
-            throw new AssertionError(reason + "\n" + "Got: " + path + " does not exist");
-        }
-
-        StringBuilder sb = new StringBuilder();
-
-        if (!S_ISDIR(stat.st_mode)) {
-            sb.append("\nExpected type: ");
-            sb.append(S_IFDIR);
-            sb.append("\ngot type: ");
-            sb.append((stat.st_mode & S_IFMT));
-        }
-
-        if (stat.st_uid != uid) {
-            sb.append("\nExpected owner: ");
-            sb.append(uid);
-            sb.append("\nGot owner: ");
-            sb.append(stat.st_uid);
-        }
-
-        if (stat.st_gid != gid) {
-            sb.append("\nExpected group: ");
-            sb.append(gid);
-            sb.append("\nGot group: ");
-            sb.append(stat.st_gid);
-        }
-
-        if ((stat.st_mode & ~S_IFMT) != perms) {
-            sb.append("\nExpected permissions: ");
-            sb.append(Integer.toOctalString(perms));
-            sb.append("\nGot permissions: ");
-            sb.append(Integer.toOctalString(stat.st_mode & ~S_IFMT));
-        }
-
-        if (sb.length() > 0) {
-            throw new AssertionError(reason + sb.toString());
-        }
-    }
-
-    private static void assertStartsWith(String prefix, String actual) {
-        assertStartsWith("", prefix, actual);
-    }
-
-    private static void assertStartsWith(String description, String prefix, String actual) {
-        if (!actual.startsWith(prefix)) {
-            StringBuilder sb = new StringBuilder(description);
-            sb.append("\nExpected prefix: ");
-            sb.append(prefix);
-            sb.append("\n     got: ");
-            sb.append(actual);
-            sb.append('\n');
-            throw new AssertionError(sb.toString());
-        }
-    }
-
-    private void assertNotInstalled(String pkgName) {
-        try {
-            ApplicationInfo info = getPm().getApplicationInfo(pkgName, 0);
-            fail(pkgName + " shouldnt be installed");
-        } catch (NameNotFoundException e) {
-        }
-    }
-
-    class InstallParams {
-        Uri packageURI;
-
-        ParsedPackage pkg;
-
-        InstallParams(String outFileName, int rawResId) {
-            this.pkg = getParsedPackage(outFileName, rawResId);
-            this.packageURI = Uri.fromFile(new File(pkg.getPath()));
-        }
-
-        InstallParams(ParsedPackage pkg) {
-            this.packageURI = Uri.fromFile(new File(pkg.getPath()));
-            this.pkg = pkg;
-        }
-
-        long getApkSize() {
-            File file = new File(pkg.getPath());
-            return file.length();
-        }
-    }
-
-    private InstallParams sampleInstallFromRawResource(int flags, boolean cleanUp)
-            throws Exception {
-        return installFromRawResource("install.apk", R.raw.install, flags, cleanUp, false, -1,
-                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-    }
-
-    static final String PERM_PACKAGE = "package";
-
-    static final String PERM_DEFINED = "defined";
-
-    static final String PERM_UNDEFINED = "undefined";
-
-    static final String PERM_USED = "used";
-
-    static final String PERM_NOTUSED = "notused";
-
-    private void assertPermissions(String[] cmds) {
-        final PackageManager pm = getPm();
-        String pkg = null;
-        PackageInfo pkgInfo = null;
-        String mode = PERM_DEFINED;
-        int i = 0;
-        while (i < cmds.length) {
-            String cmd = cmds[i++];
-            if (cmd == PERM_PACKAGE) {
-                pkg = cmds[i++];
-                try {
-                    pkgInfo = pm.getPackageInfo(pkg,
-                            PackageManager.GET_PERMISSIONS
-                            | PackageManager.MATCH_UNINSTALLED_PACKAGES);
-                } catch (NameNotFoundException e) {
-                    pkgInfo = null;
-                }
-            } else if (cmd == PERM_DEFINED || cmd == PERM_UNDEFINED
-                    || cmd == PERM_USED || cmd == PERM_NOTUSED) {
-                mode = cmds[i++];
-            } else {
-                if (mode == PERM_DEFINED) {
-                    try {
-                        PermissionInfo pi = pm.getPermissionInfo(cmd, 0);
-                        assertNotNull(pi);
-                        assertEquals(pi.packageName, pkg);
-                        assertEquals(pi.name, cmd);
-                        assertNotNull(pkgInfo);
-                        boolean found = false;
-                        for (int j = 0; j < pkgInfo.permissions.length && !found; j++) {
-                            if (pkgInfo.permissions[j].name.equals(cmd)) {
-                                found = true;
-                            }
-                        }
-                        if (!found) {
-                            fail("Permission not found: " + cmd);
-                        }
-                    } catch (NameNotFoundException e) {
-                        throw new RuntimeException(e);
-                    }
-                } else if (mode == PERM_UNDEFINED) {
-                    try {
-                        pm.getPermissionInfo(cmd, 0);
-                        throw new RuntimeException("Permission exists: " + cmd);
-                    } catch (NameNotFoundException e) {
-                    }
-                    if (pkgInfo != null) {
-                        boolean found = false;
-                        for (int j = 0; j < pkgInfo.permissions.length && !found; j++) {
-                            if (pkgInfo.permissions[j].name.equals(cmd)) {
-                                found = true;
-                            }
-                        }
-                        if (found) {
-                            fail("Permission still exists: " + cmd);
-                        }
-                    }
-                } else if (mode == PERM_USED || mode == PERM_NOTUSED) {
-                    boolean found = false;
-                    for (int j = 0; j < pkgInfo.requestedPermissions.length && !found; j++) {
-                        if (pkgInfo.requestedPermissions[j].equals(cmd)) {
-                            found = true;
-                        }
-                    }
-                    if (!found) {
-                        fail("Permission not requested: " + cmd);
-                    }
-                    if (mode == PERM_USED) {
-                        if (pm.checkPermission(cmd, pkg) != PackageManager.PERMISSION_GRANTED) {
-                            fail("Permission not granted: " + cmd);
-                        }
-                    } else {
-                        if (pm.checkPermission(cmd, pkg) != PackageManager.PERMISSION_DENIED) {
-                            fail("Permission granted: " + cmd);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private ParsedPackage getParsedPackage(String outFileName, int rawResId) {
-        PackageManager pm = mContext.getPackageManager();
-        File filesDir = mContext.getFilesDir();
-        File outFile = new File(filesDir, outFileName);
-        Uri packageURI = getInstallablePackage(rawResId, outFile);
-        return parsePackage(packageURI);
-    }
-
-    /*
-     * Utility function that reads a apk bundled as a raw resource
-     * copies it into own data directory and invokes
-     * PackageManager api to install it.
-     */
-    private void installFromRawResource(InstallParams ip, int flags, boolean cleanUp, boolean fail,
-            int result, int expInstallLocation) throws Exception {
-        PackageManager pm = mContext.getPackageManager();
-        ParsedPackage pkg = ip.pkg;
-        Uri packageURI = ip.packageURI;
-        if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) {
-            // Make sure the package doesn't exist
-            try {
-                ApplicationInfo appInfo = pm.getApplicationInfo(pkg.getPackageName(),
-                        PackageManager.MATCH_UNINSTALLED_PACKAGES);
-                GenericReceiver receiver = new DeleteReceiver(pkg.getPackageName());
-                invokeDeletePackage(pkg.getPackageName(), 0, receiver);
-            } catch (IllegalArgumentException | NameNotFoundException e) {
-            }
-        }
-        try {
-            if (fail) {
-                invokeInstallPackageFail(packageURI, flags, result);
-                if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) {
-                    assertNotInstalled(pkg.getPackageName());
-                }
-            } else {
-                InstallReceiver receiver = new InstallReceiver(pkg.getPackageName());
-                invokeInstallPackage(packageURI, flags, receiver, true);
-                // Verify installed information
-                assertInstall(pkg, flags, expInstallLocation);
-            }
-        } finally {
-            if (cleanUp) {
-                cleanUpInstall(ip);
-            }
-        }
-    }
-
-    /*
-     * Utility function that reads a apk bundled as a raw resource
-     * copies it into own data directory and invokes
-     * PackageManager api to install it.
-     */
-    private InstallParams installFromRawResource(String outFileName, int rawResId, int flags,
-            boolean cleanUp, boolean fail, int result, int expInstallLocation) throws Exception {
-        InstallParams ip = new InstallParams(outFileName, rawResId);
-        installFromRawResource(ip, flags, cleanUp, fail, result, expInstallLocation);
-        return ip;
-    }
-
-    @LargeTest
-    public void testInstallNormalInternal() throws Exception {
-        sampleInstallFromRawResource(0, true);
-    }
-
-    /* ------------------------- Test replacing packages -------------- */
-    class ReplaceReceiver extends GenericReceiver {
-        String pkgName;
-
-        final static int INVALID = -1;
-
-        final static int REMOVED = 1;
-
-        final static int ADDED = 2;
-
-        final static int REPLACED = 3;
-
-        int removed = INVALID;
-
-        // for updated system apps only
-        boolean update = false;
-
-        ReplaceReceiver(String pkgName) {
-            this.pkgName = pkgName;
-            filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
-            filter.addAction(Intent.ACTION_PACKAGE_ADDED);
-            if (update) {
-                filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-            }
-            filter.addDataScheme("package");
-            super.setFilter(filter);
-        }
-
-        public boolean notifyNow(Intent intent) {
-            String action = intent.getAction();
-            Uri data = intent.getData();
-            String installedPkg = data.getEncodedSchemeSpecificPart();
-            if (pkgName == null || !pkgName.equals(installedPkg)) {
-                return false;
-            }
-            if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-                removed = REMOVED;
-            } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
-                if (removed != REMOVED) {
-                    return false;
-                }
-                boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-                if (!replacing) {
-                    return false;
-                }
-                removed = ADDED;
-                if (!update) {
-                    return true;
-                }
-            } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
-                if (removed != ADDED) {
-                    return false;
-                }
-                removed = REPLACED;
-                return true;
-            }
-            return false;
-        }
-    }
-
-    /*
-     * Utility function that reads a apk bundled as a raw resource
-     * copies it into own data directory and invokes
-     * PackageManager api to install first and then replace it
-     * again.
-     */
-    private void sampleReplaceFromRawResource(int flags) throws Exception {
-        InstallParams ip = sampleInstallFromRawResource(flags, false);
-        boolean replace = ((flags & PackageManager.INSTALL_REPLACE_EXISTING) != 0);
-        Log.i(TAG, "replace=" + replace);
-        GenericReceiver receiver;
-        if (replace) {
-            receiver = new ReplaceReceiver(ip.pkg.getPackageName());
-            Log.i(TAG, "Creating replaceReceiver");
-        } else {
-            receiver = new InstallReceiver(ip.pkg.getPackageName());
-        }
-        try {
-            invokeInstallPackage(ip.packageURI, flags, receiver, true);
-            if (replace) {
-                assertInstall(ip.pkg, flags, ip.pkg.getInstallLocation());
-            }
-        } finally {
-            cleanUpInstall(ip);
-        }
-    }
-
-    @LargeTest
-    public void testReplaceFlagDoesNotNeedToBeSet() throws Exception {
-        sampleReplaceFromRawResource(0);
-    }
-
-    @LargeTest
-    public void testReplaceNormalInternal() throws Exception {
-        sampleReplaceFromRawResource(PackageManager.INSTALL_REPLACE_EXISTING);
-    }
-
-    /* -------------- Delete tests --- */
-    private static class DeleteObserver extends IPackageDeleteObserver.Stub {
-        private CountDownLatch mLatch = new CountDownLatch(1);
-
-        private int mReturnCode;
-
-        private final String mPackageName;
-
-        private String mObservedPackage;
-
-        public DeleteObserver(String packageName) {
-            mPackageName = packageName;
-        }
-
-        public boolean isSuccessful() {
-            return mReturnCode == PackageManager.DELETE_SUCCEEDED;
-        }
-
-        public void packageDeleted(String packageName, int returnCode) throws RemoteException {
-            mObservedPackage = packageName;
-
-            mReturnCode = returnCode;
-
-            mLatch.countDown();
-        }
-
-        public void waitForCompletion(long timeoutMillis) {
-            final long deadline = SystemClock.uptimeMillis() + timeoutMillis;
-
-            long waitTime = timeoutMillis;
-            while (waitTime > 0) {
-                try {
-                    boolean done = mLatch.await(waitTime, TimeUnit.MILLISECONDS);
-                    if (done) {
-                        assertEquals(mPackageName, mObservedPackage);
-                        return;
-                    }
-                } catch (InterruptedException e) {
-                    // TODO Auto-generated catch block
-                    e.printStackTrace();
-                }
-                waitTime = deadline - SystemClock.uptimeMillis();
-            }
-
-            throw new AssertionError("Timeout waiting for package deletion");
-        }
-    }
-
-    class DeleteReceiver extends GenericReceiver {
-        String pkgName;
-
-        DeleteReceiver(String pkgName) {
-            this.pkgName = pkgName;
-            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
-            filter.addDataScheme("package");
-            super.setFilter(filter);
-        }
-
-        public boolean notifyNow(Intent intent) {
-            String action = intent.getAction();
-            if (!Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-                return false;
-            }
-            Uri data = intent.getData();
-            String installedPkg = data.getEncodedSchemeSpecificPart();
-            if (pkgName.equals(installedPkg)) {
-                return true;
-            }
-            return false;
-        }
-    }
-
-    public boolean invokeDeletePackage(final String pkgName, int flags, GenericReceiver receiver)
-            throws Exception {
-        ApplicationInfo info = getPm().getApplicationInfo(pkgName,
-                PackageManager.MATCH_UNINSTALLED_PACKAGES);
-
-        mContext.registerReceiver(receiver, receiver.filter);
-        try {
-            final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
-            getPi().uninstall(pkgName,
-                    flags | PackageManager.DELETE_ALL_USERS,
-                    localReceiver.getIntentSender());
-            localReceiver.getResult();
-
-            assertUninstalled(info);
-
-            // Verify we received the broadcast
-            // TODO replace this with a CountDownLatch
-            synchronized (receiver) {
-                long waitTime = 0;
-                while ((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME)) {
-                    receiver.wait(WAIT_TIME_INCR);
-                    waitTime += WAIT_TIME_INCR;
-                }
-                if (!receiver.isDone()) {
-                    throw new Exception("Timed out waiting for PACKAGE_REMOVED notification");
-                }
-            }
-            return receiver.received;
-        } finally {
-            mContext.unregisterReceiver(receiver);
-        }
-    }
-
-    private static void assertUninstalled(ApplicationInfo info) throws Exception {
-        File nativeLibraryFile = new File(info.nativeLibraryDir);
-        assertFalse("Native library directory " + info.nativeLibraryDir
-                + " should be erased", nativeLibraryFile.exists());
-    }
-
-    public void deleteFromRawResource(int iFlags, int dFlags) throws Exception {
-        InstallParams ip = sampleInstallFromRawResource(iFlags, false);
-        boolean retainData = ((dFlags & PackageManager.DELETE_KEEP_DATA) != 0);
-        GenericReceiver receiver = new DeleteReceiver(ip.pkg.getPackageName());
-        try {
-            assertTrue(invokeDeletePackage(ip.pkg.getPackageName(), dFlags, receiver));
-            ApplicationInfo info = null;
-            Log.i(TAG, "okay4");
-            try {
-                info = getPm().getApplicationInfo(ip.pkg.getPackageName(),
-                        PackageManager.MATCH_UNINSTALLED_PACKAGES);
-            } catch (NameNotFoundException e) {
-                info = null;
-            }
-            if (retainData) {
-                assertNotNull(info);
-                assertEquals(info.packageName, ip.pkg.getPackageName());
-            } else {
-                assertNull(info);
-            }
-        } catch (Exception e) {
-            failStr(e);
-        } finally {
-            cleanUpInstall(ip);
-        }
-    }
-
-    @LargeTest
-    public void testDeleteNormalInternal() throws Exception {
-        deleteFromRawResource(0, 0);
-    }
-
-
-    @LargeTest
-    public void testDeleteNormalInternalRetainData() throws Exception {
-        deleteFromRawResource(0, PackageManager.DELETE_KEEP_DATA);
-    }
-
-    void cleanUpInstall(InstallParams ip) throws Exception {
-        if (ip == null) {
-            return;
-        }
-        Runtime.getRuntime().gc();
-        try {
-            cleanUpInstall(ip.pkg.getPackageName());
-        } finally {
-            File outFile = new File(ip.pkg.getPath());
-            if (outFile != null && outFile.exists()) {
-                outFile.delete();
-            }
-        }
-    }
-
-    private void cleanUpInstall(String pkgName) throws Exception {
-        if (pkgName == null) {
-            return;
-        }
-        Log.i(TAG, "Deleting package : " + pkgName);
-        try {
-            final ApplicationInfo info = getPm().getApplicationInfo(pkgName,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES);
-            if (info != null) {
-                final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
-                getPi().uninstall(pkgName,
-                        PackageManager.DELETE_ALL_USERS,
-                        localReceiver.getIntentSender());
-                localReceiver.getResult();
-                assertUninstalled(info);
-            }
-        } catch (IllegalArgumentException | NameNotFoundException e) {
-        }
-    }
-
-    @LargeTest
-    public void testManifestInstallLocationInternal() throws Exception {
-        installFromRawResource("install.apk", R.raw.install_loc_internal,
-                0, true, false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-    }
-
-    @LargeTest
-    public void testManifestInstallLocationSdcard() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        installFromRawResource("install.apk", R.raw.install_loc_sdcard,
-                0, true, false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
-    }
-
-    @LargeTest
-    public void testManifestInstallLocationAuto() throws Exception {
-        installFromRawResource("install.apk", R.raw.install_loc_auto,
-                0, true, false, -1, PackageInfo.INSTALL_LOCATION_AUTO);
-    }
-
-    @LargeTest
-    public void testManifestInstallLocationUnspecified() throws Exception {
-        installFromRawResource("install.apk", R.raw.install_loc_unspecified,
-                0, true, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-    }
-
-    @LargeTest
-    public void testManifestInstallLocationReplaceInternalSdcard() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        int iFlags = 0;
-        int iApk = R.raw.install_loc_internal;
-        int rFlags = 0;
-        int rApk = R.raw.install_loc_sdcard;
-        InstallParams ip = installFromRawResource("install.apk", iApk,
-                iFlags, false,
-                false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-        GenericReceiver receiver = new ReplaceReceiver(ip.pkg.getPackageName());
-        int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
-        try {
-            InstallParams rp = installFromRawResource("install.apk", rApk,
-                    replaceFlags, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
-            assertInstall(rp.pkg, replaceFlags, rp.pkg.getInstallLocation());
-        } catch (Exception e) {
-            failStr("Failed with exception : " + e);
-        } finally {
-            cleanUpInstall(ip);
-        }
-    }
-
-    @LargeTest
-    public void testManifestInstallLocationReplaceSdcardInternal() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        int iFlags = 0;
-        int iApk = R.raw.install_loc_sdcard;
-        int rFlags = 0;
-        int rApk = R.raw.install_loc_unspecified;
-        InstallParams ip = installFromRawResource("install.apk", iApk,
-                iFlags, false,
-                false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
-        int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
-        try {
-            InstallParams rp = installFromRawResource("install.apk", rApk,
-                    replaceFlags, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
-            assertInstall(rp.pkg, replaceFlags, ip.pkg.getInstallLocation());
-        } catch (Exception e) {
-            failStr("Failed with exception : " + e);
-        } finally {
-            cleanUpInstall(ip);
-        }
-    }
-
-    class MoveReceiver extends GenericReceiver {
-        String pkgName;
-
-        final static int INVALID = -1;
-
-        final static int REMOVED = 1;
-
-        final static int ADDED = 2;
-
-        int removed = INVALID;
-
-        MoveReceiver(String pkgName) {
-            this.pkgName = pkgName;
-            filter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
-            filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
-            super.setFilter(filter);
-        }
-
-        public boolean notifyNow(Intent intent) {
-            String action = intent.getAction();
-            Log.i(TAG, "MoveReceiver::" + action);
-            if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
-                String[] list = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                if (list != null) {
-                    for (String pkg : list) {
-                        if (pkg.equals(pkgName)) {
-                            removed = REMOVED;
-                            break;
-                        }
-                    }
-                }
-                removed = REMOVED;
-            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
-                if (removed != REMOVED) {
-                    return false;
-                }
-                String[] list = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                if (list != null) {
-                    for (String pkg : list) {
-                        if (pkg.equals(pkgName)) {
-                            removed = ADDED;
-                            return true;
-                        }
-                    }
-                }
-            }
-            return false;
-        }
-    }
-
-    public boolean invokeMovePackage(String pkgName, int flags, GenericReceiver receiver)
-            throws Exception {
-        throw new UnsupportedOperationException();
-    }
-
-    private boolean invokeMovePackageFail(String pkgName, int flags, int errCode) throws Exception {
-        throw new UnsupportedOperationException();
-    }
-
-    private int getDefaultInstallLoc() {
-        int origDefaultLoc = PackageInfo.INSTALL_LOCATION_AUTO;
-        try {
-            origDefaultLoc = Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.DEFAULT_INSTALL_LOCATION);
-        } catch (SettingNotFoundException e1) {
-        }
-        return origDefaultLoc;
-    }
-
-    private void setInstallLoc(int loc) {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.DEFAULT_INSTALL_LOCATION, loc);
-    }
-
-    /*
-     * Tests for moving apps between internal and external storage
-     */
-    /*
-     * Utility function that reads a apk bundled as a raw resource
-     * copies it into own data directory and invokes
-     * PackageManager api to install first and then replace it
-     * again.
-     */
-
-    private void moveFromRawResource(String outFileName, int rawResId, int installFlags,
-            int moveFlags, boolean cleanUp, boolean fail, int result) throws Exception {
-        int origDefaultLoc = getDefaultInstallLoc();
-        InstallParams ip = null;
-        try {
-            setInstallLoc(InstallLocationUtils.APP_INSTALL_AUTO);
-            // Install first
-            ip = installFromRawResource("install.apk", rawResId, installFlags, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-            ApplicationInfo oldAppInfo = getPm().getApplicationInfo(ip.pkg.getPackageName(), 0);
-            if (fail) {
-                assertTrue(invokeMovePackageFail(ip.pkg.getPackageName(), moveFlags, result));
-                ApplicationInfo info = getPm().getApplicationInfo(ip.pkg.getPackageName(), 0);
-                assertNotNull(info);
-                assertEquals(oldAppInfo.flags, info.flags);
-            } else {
-                // Create receiver based on expRetCode
-                MoveReceiver receiver = new MoveReceiver(ip.pkg.getPackageName());
-                boolean retCode = invokeMovePackage(ip.pkg.getPackageName(), moveFlags, receiver);
-                assertTrue(retCode);
-                ApplicationInfo info = getPm().getApplicationInfo(ip.pkg.getPackageName(), 0);
-                assertNotNull("ApplicationInfo for recently installed application should exist",
-                        info);
-                if ((moveFlags & PackageManager.MOVE_INTERNAL) != 0) {
-                    assertTrue("ApplicationInfo.FLAG_EXTERNAL_STORAGE flag should NOT be set",
-                            (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0);
-                    assertStartsWith("Native library dir should be in dataDir",
-                            info.dataDir, info.nativeLibraryDir);
-                } else if ((moveFlags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0) {
-                    assertTrue("ApplicationInfo.FLAG_EXTERNAL_STORAGE flag should be set",
-                            (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
-                    assertStartsWith("Native library dir should point to ASEC",
-                            SECURE_CONTAINERS_PREFIX, info.nativeLibraryDir);
-                }
-            }
-        } catch (NameNotFoundException e) {
-            failStr("Pkg hasnt been installed correctly");
-        } finally {
-            if (ip != null) {
-                cleanUpInstall(ip);
-            }
-            // Restore default install location
-            setInstallLoc(origDefaultLoc);
-        }
-    }
-
-    private void sampleMoveFromRawResource(int installFlags, int moveFlags, boolean fail,
-            int result) throws Exception {
-        moveFromRawResource("install.apk",
-                R.raw.install, installFlags, moveFlags, true,
-                fail, result);
-    }
-
-    @LargeTest
-    public void testMoveAppInternalToExternal() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        int installFlags = PackageManager.INSTALL_INTERNAL;
-        int moveFlags = PackageManager.MOVE_EXTERNAL_MEDIA;
-        boolean fail = false;
-        int result = PackageManager.MOVE_SUCCEEDED;
-        sampleMoveFromRawResource(installFlags, moveFlags, fail, result);
-    }
-
-    @Suppress
-    @LargeTest
-    public void testMoveAppInternalToInternal() throws Exception {
-        int installFlags = PackageManager.INSTALL_INTERNAL;
-        int moveFlags = PackageManager.MOVE_INTERNAL;
-        boolean fail = true;
-        int result = PackageManager.MOVE_FAILED_INVALID_LOCATION;
-        sampleMoveFromRawResource(installFlags, moveFlags, fail, result);
-    }
-
-    @LargeTest
-    public void testMoveAppFailInternalToExternalDelete() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        int installFlags = 0;
-        int moveFlags = PackageManager.MOVE_EXTERNAL_MEDIA;
-        boolean fail = true;
-        final int result = PackageManager.MOVE_FAILED_DOESNT_EXIST;
-
-        int rawResId = R.raw.install;
-        int origDefaultLoc = getDefaultInstallLoc();
-        InstallParams ip = null;
-        try {
-            PackageManager pm = getPm();
-            setInstallLoc(InstallLocationUtils.APP_INSTALL_AUTO);
-            // Install first
-            ip = installFromRawResource("install.apk", R.raw.install, installFlags, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-            // Delete the package now retaining data.
-            GenericReceiver receiver = new DeleteReceiver(ip.pkg.getPackageName());
-            invokeDeletePackage(ip.pkg.getPackageName(), PackageManager.DELETE_KEEP_DATA, receiver);
-            assertTrue(invokeMovePackageFail(ip.pkg.getPackageName(), moveFlags, result));
-        } catch (Exception e) {
-            failStr(e);
-        } finally {
-            if (ip != null) {
-                cleanUpInstall(ip);
-            }
-            // Restore default install location
-            setInstallLoc(origDefaultLoc);
-        }
-    }
-
-    /*---------- Recommended install location tests ----*/
-    /*
-     * PrecedenceSuffixes:
-     * Flag : FlagI, FlagE, FlagF
-     * I - internal, E - external, F - forward locked, Flag suffix absent if not using any option.
-     * Manifest: ManifestI, ManifestE, ManifestA, Manifest suffix absent if not using any option.
-     * Existing: Existing suffix absent if not existing.
-     * User: UserI, UserE, UserA, User suffix absent if not existing.
-     *
-     */
-
-    /*
-     * Install an app on internal flash
-     */
-    @LargeTest
-    public void testFlagI() throws Exception {
-        sampleInstallFromRawResource(PackageManager.INSTALL_INTERNAL, true);
-    }
-
-    /*
-     * Install an app with both internal and manifest option set.
-     * should install on internal.
-     */
-    @LargeTest
-    public void testFlagIManifestI() throws Exception {
-        installFromRawResource("install.apk", R.raw.install_loc_internal,
-                PackageManager.INSTALL_INTERNAL,
-                true,
-                false, -1,
-                PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-    }
-    /*
-     * Install an app with both internal and manifest preference for
-     * preferExternal. Should install on internal.
-     */
-    @LargeTest
-    public void testFlagIManifestE() throws Exception {
-        installFromRawResource("install.apk", R.raw.install_loc_sdcard,
-                PackageManager.INSTALL_INTERNAL,
-                true,
-                false, -1,
-                PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-    }
-    /*
-     * Install an app with both internal and manifest preference for
-     * auto. should install internal.
-     */
-    @LargeTest
-    public void testFlagIManifestA() throws Exception {
-        installFromRawResource("install.apk", R.raw.install_loc_auto,
-                PackageManager.INSTALL_INTERNAL,
-                true,
-                false, -1,
-                PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-    }
-
-    /*
-     * The following test functions verify install location for existing apps.
-     * ie existing app can be installed internally or externally. If install
-     * flag is explicitly set it should override current location. If manifest location
-     * is set, that should over ride current location too. if not the existing install
-     * location should be honoured.
-     * testFlagI/E/F/ExistingI/E -
-     */
-    @LargeTest
-    public void testFlagIExistingI() throws Exception {
-        int iFlags = PackageManager.INSTALL_INTERNAL;
-        int rFlags = PackageManager.INSTALL_INTERNAL | PackageManager.INSTALL_REPLACE_EXISTING;
-        // First install.
-        installFromRawResource("install.apk", R.raw.install,
-                iFlags,
-                false,
-                false, -1,
-                -1);
-        // Replace now
-        installFromRawResource("install.apk", R.raw.install,
-                rFlags,
-                true,
-                false, -1,
-                -1);
-    }
-
-    /*
-     * The following set of tests verify the installation of apps with
-     * install location attribute set to internalOnly, preferExternal and auto.
-     * The manifest option should dictate the install location.
-     * public void testManifestI/E/A
-     * TODO out of memory fall back behaviour.
-     */
-    @LargeTest
-    public void testManifestI() throws Exception {
-        installFromRawResource("install.apk", R.raw.install_loc_internal,
-                0,
-                true,
-                false, -1,
-                PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-    }
-
-    @LargeTest
-    public void testManifestE() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        installFromRawResource("install.apk", R.raw.install_loc_sdcard,
-                0,
-                true,
-                false, -1,
-                PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
-    }
-
-    @LargeTest
-    public void testManifestA() throws Exception {
-        installFromRawResource("install.apk", R.raw.install_loc_auto,
-                0,
-                true,
-                false, -1,
-                PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-    }
-
-    /*
-     * The following set of tests verify the installation of apps
-     * with install location attribute set to internalOnly, preferExternal and auto
-     * for already existing apps. The manifest option should take precedence.
-     * TODO add out of memory fall back behaviour.
-     * testManifestI/E/AExistingI/E
-     */
-    @LargeTest
-    public void testManifestIExistingI() throws Exception {
-        int iFlags = PackageManager.INSTALL_INTERNAL;
-        int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
-        // First install.
-        installFromRawResource("install.apk", R.raw.install,
-                iFlags,
-                false,
-                false, -1,
-                -1);
-        // Replace now
-        installFromRawResource("install.apk", R.raw.install_loc_internal,
-                rFlags,
-                true,
-                false, -1,
-                PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-    }
-
-    @LargeTest
-    public void testManifestEExistingI() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        int iFlags = PackageManager.INSTALL_INTERNAL;
-        int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
-        // First install.
-        installFromRawResource("install.apk", R.raw.install,
-                iFlags,
-                false,
-                false, -1,
-                -1);
-        // Replace now
-        installFromRawResource("install.apk", R.raw.install_loc_sdcard,
-                rFlags,
-                true,
-                false, -1,
-                PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
-    }
-
-    @LargeTest
-    public void testManifestAExistingI() throws Exception {
-        int iFlags = PackageManager.INSTALL_INTERNAL;
-        int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
-        // First install.
-        installFromRawResource("install.apk", R.raw.install,
-                iFlags,
-                false,
-                false, -1,
-                -1);
-        // Replace now
-        installFromRawResource("install.apk", R.raw.install_loc_auto,
-                rFlags,
-                true,
-                false, -1,
-                PackageInfo.INSTALL_LOCATION_AUTO);
-    }
-
-    /*
-     * The following set of tests check install location for existing
-     * application based on user setting.
-     */
-    private int getExpectedInstallLocation(int userSetting) {
-        int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
-        boolean enable = getUserSettingSetInstallLocation();
-        if (enable) {
-            if (userSetting == InstallLocationUtils.APP_INSTALL_AUTO) {
-                iloc = PackageInfo.INSTALL_LOCATION_AUTO;
-            } else if (userSetting == InstallLocationUtils.APP_INSTALL_EXTERNAL) {
-                iloc = PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL;
-            } else if (userSetting == InstallLocationUtils.APP_INSTALL_INTERNAL) {
-                iloc = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
-            }
-        }
-        return iloc;
-    }
-
-    private void setExistingXUserX(int userSetting, int iFlags, int iloc) throws Exception {
-        int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
-        // First install.
-        installFromRawResource("install.apk", R.raw.install,
-                iFlags,
-                false,
-                false, -1,
-                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        int origSetting = getDefaultInstallLoc();
-        try {
-            // Set user setting
-            setInstallLoc(userSetting);
-            // Replace now
-            installFromRawResource("install.apk", R.raw.install,
-                    rFlags,
-                    true,
-                    false, -1,
-                    iloc);
-        } finally {
-            setInstallLoc(origSetting);
-        }
-    }
-    @LargeTest
-    public void testExistingIUserI() throws Exception {
-        int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
-        int iFlags = PackageManager.INSTALL_INTERNAL;
-        setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-    }
-
-    @LargeTest
-    public void testExistingIUserE() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
-        int iFlags = PackageManager.INSTALL_INTERNAL;
-        setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-    }
-
-    @LargeTest
-    public void testExistingIUserA() throws Exception {
-        int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
-        int iFlags = PackageManager.INSTALL_INTERNAL;
-        setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-    }
-
-    /*
-     * The following set of tests verify that the user setting defines
-     * the install location.
-     *
-     */
-    private boolean getUserSettingSetInstallLocation() {
-        try {
-            return Settings.Global.getInt(
-                    mContext.getContentResolver(), Settings.Global.SET_INSTALL_LOCATION) != 0;
-        } catch (SettingNotFoundException e1) {
-        }
-        return false;
-    }
-
-    private void setUserSettingSetInstallLocation(boolean value) {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.SET_INSTALL_LOCATION, value ? 1 : 0);
-    }
-
-    private void setUserX(boolean enable, int userSetting, int iloc) throws Exception {
-        boolean origUserSetting = getUserSettingSetInstallLocation();
-        int origSetting = getDefaultInstallLoc();
-        try {
-            setUserSettingSetInstallLocation(enable);
-            // Set user setting
-            setInstallLoc(userSetting);
-            // Replace now
-            installFromRawResource("install.apk", R.raw.install,
-                    0,
-                    true,
-                    false, -1,
-                    iloc);
-        } finally {
-            // Restore original setting
-            setUserSettingSetInstallLocation(origUserSetting);
-            setInstallLoc(origSetting);
-        }
-    }
-    @LargeTest
-    public void testUserI() throws Exception {
-        int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
-        int iloc = getExpectedInstallLocation(userSetting);
-        setUserX(true, userSetting, iloc);
-    }
-
-    @LargeTest
-    public void testUserE() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
-        int iloc = getExpectedInstallLocation(userSetting);
-        setUserX(true, userSetting, iloc);
-    }
-
-    @LargeTest
-    public void testUserA() throws Exception {
-        int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
-        int iloc = getExpectedInstallLocation(userSetting);
-        setUserX(true, userSetting, iloc);
-    }
-
-    /*
-     * The following set of tests turn on/off the basic
-     * user setting for turning on install location.
-     */
-    @LargeTest
-    public void testUserPrefOffUserI() throws Exception {
-        int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
-        int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
-        setUserX(false, userSetting, iloc);
-    }
-
-    @LargeTest
-    public void testUserPrefOffUserE() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
-        int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
-        setUserX(false, userSetting, iloc);
-    }
-
-    @LargeTest
-    public void testUserPrefOffA() throws Exception {
-        int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
-        int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
-        setUserX(false, userSetting, iloc);
-    }
-
-    static final String BASE_PERMISSIONS_DEFINED[] = new String[] {
-        PERM_PACKAGE, "com.android.unit_tests.install_decl_perm",
-        PERM_DEFINED,
-        "com.android.frameworks.coretests.NORMAL",
-        "com.android.frameworks.coretests.DANGEROUS",
-        "com.android.frameworks.coretests.SIGNATURE",
-    };
-
-    static final String BASE_PERMISSIONS_UNDEFINED[] = new String[] {
-        PERM_PACKAGE, "com.android.frameworks.coretests.install_decl_perm",
-        PERM_UNDEFINED,
-        "com.android.frameworks.coretests.NORMAL",
-        "com.android.frameworks.coretests.DANGEROUS",
-        "com.android.frameworks.coretests.SIGNATURE",
-    };
-
-    static final String BASE_PERMISSIONS_USED[] = new String[] {
-        PERM_PACKAGE, "com.android.frameworks.coretests.install_use_perm_good",
-        PERM_USED,
-        "com.android.frameworks.coretests.NORMAL",
-        "com.android.frameworks.coretests.DANGEROUS",
-        "com.android.frameworks.coretests.SIGNATURE",
-    };
-
-    static final String BASE_PERMISSIONS_NOTUSED[] = new String[] {
-        PERM_PACKAGE, "com.android.frameworks.coretests.install_use_perm_good",
-        PERM_NOTUSED,
-        "com.android.frameworks.coretests.NORMAL",
-        "com.android.frameworks.coretests.DANGEROUS",
-        "com.android.frameworks.coretests.SIGNATURE",
-    };
-
-    static final String BASE_PERMISSIONS_SIGUSED[] = new String[] {
-        PERM_PACKAGE, "com.android.frameworks.coretests.install_use_perm_good",
-        PERM_USED,
-        "com.android.frameworks.coretests.SIGNATURE",
-        PERM_NOTUSED,
-        "com.android.frameworks.coretests.NORMAL",
-        "com.android.frameworks.coretests.DANGEROUS",
-    };
-
-    /*
-     * Ensure that permissions are properly declared.
-     */
-    @LargeTest
-    public void testInstallDeclaresPermissions() throws Exception {
-        InstallParams ip = null;
-        InstallParams ip2 = null;
-        try {
-            // **: Upon installing a package, are its declared permissions published?
-
-            int iFlags = PackageManager.INSTALL_INTERNAL;
-            int iApk = R.raw.install_decl_perm;
-            ip = installFromRawResource("install.apk", iApk,
-                    iFlags, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-            assertInstall(ip.pkg, iFlags, ip.pkg.getInstallLocation());
-            assertPermissions(BASE_PERMISSIONS_DEFINED);
-
-            // **: Upon installing package, are its permissions granted?
-
-            int i2Flags = PackageManager.INSTALL_INTERNAL;
-            int i2Apk = R.raw.install_use_perm_good;
-            ip2 = installFromRawResource("install2.apk", i2Apk,
-                    i2Flags, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-            assertInstall(ip2.pkg, i2Flags, ip2.pkg.getInstallLocation());
-            assertPermissions(BASE_PERMISSIONS_USED);
-
-            // **: Upon removing but not deleting, are permissions retained?
-
-            GenericReceiver receiver = new DeleteReceiver(ip.pkg.getPackageName());
-
-            try {
-                invokeDeletePackage(ip.pkg.getPackageName(), PackageManager.DELETE_KEEP_DATA, receiver);
-            } catch (Exception e) {
-                failStr(e);
-            }
-            assertPermissions(BASE_PERMISSIONS_DEFINED);
-            assertPermissions(BASE_PERMISSIONS_USED);
-
-            // **: Upon re-installing, are permissions retained?
-
-            ip = installFromRawResource("install.apk", iApk,
-                    iFlags | PackageManager.INSTALL_REPLACE_EXISTING, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-            assertInstall(ip.pkg, iFlags, ip.pkg.getInstallLocation());
-            assertPermissions(BASE_PERMISSIONS_DEFINED);
-            assertPermissions(BASE_PERMISSIONS_USED);
-
-            // **: Upon deleting package, are all permissions removed?
-
-            try {
-                invokeDeletePackage(ip.pkg.getPackageName(), 0, receiver);
-                ip = null;
-            } catch (Exception e) {
-                failStr(e);
-            }
-            assertPermissions(BASE_PERMISSIONS_UNDEFINED);
-            assertPermissions(BASE_PERMISSIONS_NOTUSED);
-
-            // **: Delete package using permissions; nothing to check here.
-
-            GenericReceiver receiver2 = new DeleteReceiver(ip2.pkg.getPackageName());
-            try {
-                invokeDeletePackage(ip2.pkg.getPackageName(), 0, receiver);
-                ip2 = null;
-            } catch (Exception e) {
-                failStr(e);
-            }
-
-            // **: Re-install package using permissions; no permissions can be granted.
-
-            ip2 = installFromRawResource("install2.apk", i2Apk,
-                    i2Flags, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-            assertInstall(ip2.pkg, i2Flags, ip2.pkg.getInstallLocation());
-            assertPermissions(BASE_PERMISSIONS_NOTUSED);
-
-            // **: Upon installing declaring package, are sig permissions granted
-            // to other apps (but not other perms)?
-
-            ip = installFromRawResource("install.apk", iApk,
-                    iFlags, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-            assertInstall(ip.pkg, iFlags, ip.pkg.getInstallLocation());
-            assertPermissions(BASE_PERMISSIONS_DEFINED);
-            assertPermissions(BASE_PERMISSIONS_SIGUSED);
-
-            // **: Re-install package using permissions; are all permissions granted?
-
-            ip2 = installFromRawResource("install2.apk", i2Apk,
-                    i2Flags | PackageManager.INSTALL_REPLACE_EXISTING, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-            assertInstall(ip2.pkg, i2Flags, ip2.pkg.getInstallLocation());
-            assertPermissions(BASE_PERMISSIONS_NOTUSED);
-
-            // **: Upon deleting package, are all permissions removed?
-
-            try {
-                invokeDeletePackage(ip.pkg.getPackageName(), 0, receiver);
-                ip = null;
-            } catch (Exception e) {
-                failStr(e);
-            }
-            assertPermissions(BASE_PERMISSIONS_UNDEFINED);
-            assertPermissions(BASE_PERMISSIONS_NOTUSED);
-
-            // **: Delete package using permissions; nothing to check here.
-
-            try {
-                invokeDeletePackage(ip2.pkg.getPackageName(), 0, receiver);
-                ip2 = null;
-            } catch (Exception e) {
-                failStr(e);
-            }
-
-        } finally {
-            if (ip2 != null) {
-                cleanUpInstall(ip2);
-            }
-            if (ip != null) {
-                cleanUpInstall(ip);
-            }
-        }
-    }
-
-    /*
-     * The following series of tests are related to upgrading apps with
-     * different certificates.
-     */
-    private static final int APP1_UNSIGNED = R.raw.install_app1_unsigned;
-
-    private static final int APP1_CERT1 = R.raw.install_app1_cert1;
-
-    private static final int APP1_CERT2 = R.raw.install_app1_cert2;
-
-    private static final int APP1_CERT1_CERT2 = R.raw.install_app1_cert1_cert2;
-
-    private static final int APP1_CERT3_CERT4 = R.raw.install_app1_cert3_cert4;
-
-    private static final int APP1_CERT3 = R.raw.install_app1_cert3;
-
-    private static final int APP1_CERT5 = R.raw.install_app1_cert5;
-
-    private static final int APP1_CERT5_ROTATED_CERT6 = R.raw.install_app1_cert5_rotated_cert6;
-
-    private static final int APP1_CERT6 = R.raw.install_app1_cert6;
-
-    private static final int APP2_UNSIGNED = R.raw.install_app2_unsigned;
-
-    private static final int APP2_CERT1 = R.raw.install_app2_cert1;
-
-    private static final int APP2_CERT2 = R.raw.install_app2_cert2;
-
-    private static final int APP2_CERT1_CERT2 = R.raw.install_app2_cert1_cert2;
-
-    private static final int APP2_CERT3 = R.raw.install_app2_cert3;
-
-    private static final int APP2_CERT5_ROTATED_CERT6 = R.raw.install_app2_cert5_rotated_cert6;
-
-    private InstallParams replaceCerts(int apk1, int apk2, boolean cleanUp, boolean fail,
-            int retCode) throws Exception {
-        int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
-        String apk1Name = "install1.apk";
-        String apk2Name = "install2.apk";
-        var pkg1 = getParsedPackage(apk1Name, apk1);
-        try {
-            InstallParams ip = installFromRawResource(apk1Name, apk1, 0, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-            installFromRawResource(apk2Name, apk2, rFlags, false,
-                    fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-            return ip;
-        } catch (Exception e) {
-            failStr(e.getMessage());
-        } finally {
-            if (cleanUp) {
-                cleanUpInstall(pkg1.getPackageName());
-            }
-        }
-        return null;
-    }
-
-    /*
-     * Test that an app signed with two certificates can be upgraded by the
-     * same app signed with two certificates.
-     */
-    @LargeTest
-    public void testReplaceMatchAllCerts() throws Exception {
-        replaceCerts(APP1_CERT1_CERT2, APP1_CERT1_CERT2, true, false, -1);
-    }
-
-    /*
-     * Test that an app signed with two certificates cannot be upgraded
-     * by an app signed with a different certificate.
-     */
-    @LargeTest
-    public void testReplaceMatchNoCerts1() throws Exception {
-        replaceCerts(APP1_CERT1_CERT2, APP1_CERT3, true, true,
-                PackageInstaller.STATUS_FAILURE_CONFLICT);
-    }
-
-    /*
-     * Test that an app signed with two certificates cannot be upgraded
-     * by an app signed with a different certificate.
-     */
-    @LargeTest
-    public void testReplaceMatchNoCerts2() throws Exception {
-        replaceCerts(APP1_CERT1_CERT2, APP1_CERT3_CERT4, true, true,
-                PackageInstaller.STATUS_FAILURE_CONFLICT);
-    }
-
-    /*
-     * Test that an app signed with two certificates cannot be upgraded by
-     * an app signed with a subset of initial certificates.
-     */
-    @LargeTest
-    public void testReplaceMatchSomeCerts1() throws Exception {
-        replaceCerts(APP1_CERT1_CERT2, APP1_CERT1, true, true,
-                PackageInstaller.STATUS_FAILURE_CONFLICT);
-    }
-
-    /*
-     * Test that an app signed with two certificates cannot be upgraded by
-     * an app signed with the last certificate.
-     */
-    @LargeTest
-    public void testReplaceMatchSomeCerts2() throws Exception {
-        replaceCerts(APP1_CERT1_CERT2, APP1_CERT2, true, true,
-                PackageInstaller.STATUS_FAILURE_CONFLICT);
-    }
-
-    /*
-     * Test that an app signed with a certificate can be upgraded by app
-     * signed with a superset of certificates.
-     */
-    @LargeTest
-    public void testReplaceMatchMoreCerts() throws Exception {
-        replaceCerts(APP1_CERT1, APP1_CERT1_CERT2, true, true,
-                PackageInstaller.STATUS_FAILURE_CONFLICT);
-    }
-
-    /*
-     * Test that an app signed with a certificate can be upgraded by app
-     * signed with a superset of certificates. Then verify that the an app
-     * signed with the original set of certs cannot upgrade the new one.
-     */
-    @LargeTest
-    public void testReplaceMatchMoreCertsReplaceSomeCerts() throws Exception {
-        InstallParams ip = replaceCerts(APP1_CERT1, APP1_CERT1_CERT2, false, true,
-                PackageInstaller.STATUS_FAILURE_CONFLICT);
-        try {
-            int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
-            installFromRawResource("install.apk", APP1_CERT1, rFlags, false,
-                    false, -1,
-                    PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        } catch (Exception e) {
-            failStr(e.getMessage());
-        } finally {
-            if (ip != null) {
-                cleanUpInstall(ip);
-            }
-        }
-    }
-
-    /**
-     * The following tests are related to testing KeySets-based key rotation
-     */
-    /*
-     * Check if an apk which does not specify an upgrade-keyset may be upgraded
-     * by an apk which does
-     */
-    public void testNoKSToUpgradeKS() throws Exception {
-        replaceCerts(R.raw.keyset_sa_unone, R.raw.keyset_sa_ua, true, false, -1);
-    }
-
-    /*
-     * Check if an apk which does specify an upgrade-keyset may be downgraded to
-     * an apk which does not
-     */
-    public void testUpgradeKSToNoKS() throws Exception {
-        replaceCerts(R.raw.keyset_sa_ua, R.raw.keyset_sa_unone, true, false, -1);
-    }
-
-    /*
-     * Check if an apk signed by a key other than the upgrade keyset can update
-     * an app
-     */
-    public void testUpgradeKSWithWrongKey() throws Exception {
-        replaceCerts(R.raw.keyset_sa_ua, R.raw.keyset_sb_ua, true, true,
-                PackageInstaller.STATUS_FAILURE_CONFLICT);
-    }
-
-    /*
-     * Check if an apk signed by its signing key, which is not an upgrade key,
-     * can upgrade an app.
-     */
-    public void testUpgradeKSWithWrongSigningKey() throws Exception {
-        replaceCerts(R.raw.keyset_sa_ub, R.raw.keyset_sa_ub, true, true,
-                PackageInstaller.STATUS_FAILURE_CONFLICT);
-    }
-
-    /*
-     * Check if an apk signed by its upgrade key, which is not its signing key,
-     * can upgrade an app.
-     */
-    public void testUpgradeKSWithUpgradeKey() throws Exception {
-        replaceCerts(R.raw.keyset_sa_ub, R.raw.keyset_sb_ub, true, false, -1);
-    }
-    /*
-     * Check if an apk signed by its upgrade key, which is its signing key, can
-     * upgrade an app.
-     */
-    public void testUpgradeKSWithSigningUpgradeKey() throws Exception {
-        replaceCerts(R.raw.keyset_sa_ua, R.raw.keyset_sa_ua, true, false, -1);
-    }
-
-    /*
-     * Check if an apk signed by multiple keys, one of which is its upgrade key,
-     * can upgrade an app.
-     */
-    public void testMultipleUpgradeKSWithUpgradeKey() throws Exception {
-        replaceCerts(R.raw.keyset_sa_ua, R.raw.keyset_sab_ua, true, false, -1);
-    }
-
-    /*
-     * Check if an apk signed by multiple keys, one of which is its signing key,
-     * but none of which is an upgrade key, can upgrade an app.
-     */
-    public void testMultipleUpgradeKSWithSigningKey() throws Exception {
-        replaceCerts(R.raw.keyset_sau_ub, R.raw.keyset_sa_ua, true, true,
-                PackageInstaller.STATUS_FAILURE_CONFLICT);
-    }
-
-    /*
-     * Check if an apk which defines multiple (two) upgrade keysets is
-     * upgrade-able by either.
-     */
-    public void testUpgradeKSWithMultipleUpgradeKeySets() throws Exception {
-        replaceCerts(R.raw.keyset_sa_ua_ub, R.raw.keyset_sa_ua, true, false, -1);
-        replaceCerts(R.raw.keyset_sa_ua_ub, R.raw.keyset_sb_ub, true, false, -1);
-    }
-
-    /*
-     * Check if an apk's sigs are changed after upgrading with a non-signing
-     * key.
-     *
-     * TODO: consider checking against hard-coded Signatures in the Sig-tests
-     */
-    public void testSigChangeAfterUpgrade() throws Exception {
-        // install original apk and grab sigs
-        installFromRawResource("tmp.apk", R.raw.keyset_sa_ub,
-                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        PackageManager pm = getPm();
-        String pkgName = "com.android.frameworks.coretests.keysets";
-        PackageInfo pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
-        assertTrue("Package should only have one signature, sig A",
-                pi.signatures.length == 1);
-        String sigBefore = pi.signatures[0].toCharsString();
-        // install apk signed by different upgrade KeySet
-        installFromRawResource("tmp2.apk", R.raw.keyset_sb_ub,
-                PackageManager.INSTALL_REPLACE_EXISTING, false, false, -1,
-                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
-        assertTrue("Package should only have one signature, sig B",
-                pi.signatures.length == 1);
-        String sigAfter = pi.signatures[0].toCharsString();
-        assertFalse("Package signatures did not change after upgrade!",
-                sigBefore.equals(sigAfter));
-        cleanUpInstall(pkgName);
-    }
-
-    /*
-     * Check if an apk's sig is the same  after upgrading with a signing
-     * key.
-     */
-    public void testSigSameAfterUpgrade() throws Exception {
-        // install original apk and grab sigs
-        installFromRawResource("tmp.apk", R.raw.keyset_sa_ua,
-                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        PackageManager pm = getPm();
-        String pkgName = "com.android.frameworks.coretests.keysets";
-        PackageInfo pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
-        assertTrue("Package should only have one signature, sig A",
-                pi.signatures.length == 1);
-        String sigBefore = pi.signatures[0].toCharsString();
-        // install apk signed by same upgrade KeySet
-        installFromRawResource("tmp2.apk", R.raw.keyset_sa_ua,
-                PackageManager.INSTALL_REPLACE_EXISTING, false, false, -1,
-                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
-        assertTrue("Package should only have one signature, sig A",
-                pi.signatures.length == 1);
-        String sigAfter = pi.signatures[0].toCharsString();
-        assertTrue("Package signatures changed after upgrade!",
-                sigBefore.equals(sigAfter));
-        cleanUpInstall(pkgName);
-    }
-
-    /*
-     * Check if an apk's sigs are the same after upgrading with an app with
-     * a subset of the original signing keys.
-     */
-    public void testSigRemovedAfterUpgrade() throws Exception {
-        // install original apk and grab sigs
-        installFromRawResource("tmp.apk", R.raw.keyset_sab_ua,
-                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        PackageManager pm = getPm();
-        String pkgName = "com.android.frameworks.coretests.keysets";
-        PackageInfo pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
-        assertTrue("Package should have two signatures, sig A and sig B",
-                pi.signatures.length == 2);
-        Set<String> sigsBefore = new HashSet<String>();
-        for (int i = 0; i < pi.signatures.length; i++) {
-            sigsBefore.add(pi.signatures[i].toCharsString());
-        }
-        // install apk signed subset upgrade KeySet
-        installFromRawResource("tmp2.apk", R.raw.keyset_sa_ua,
-                PackageManager.INSTALL_REPLACE_EXISTING, false, false, -1,
-                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
-        assertTrue("Package should only have one signature, sig A",
-                pi.signatures.length == 1);
-        String sigAfter = pi.signatures[0].toCharsString();
-        assertTrue("Original package signatures did not contain new sig",
-                sigsBefore.contains(sigAfter));
-        cleanUpInstall(pkgName);
-    }
-
-    /*
-     * Check if an apk's sigs are added to after upgrading with an app with
-     * a superset of the original signing keys.
-     */
-    public void testSigAddedAfterUpgrade() throws Exception {
-        // install original apk and grab sigs
-        installFromRawResource("tmp.apk", R.raw.keyset_sa_ua,
-                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        PackageManager pm = getPm();
-        String pkgName = "com.android.frameworks.coretests.keysets";
-        PackageInfo pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
-        assertTrue("Package should only have one signature, sig A",
-                pi.signatures.length == 1);
-        String sigBefore = pi.signatures[0].toCharsString();
-        // install apk signed subset upgrade KeySet
-        installFromRawResource("tmp2.apk", R.raw.keyset_sab_ua,
-                PackageManager.INSTALL_REPLACE_EXISTING, false, false, -1,
-                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        pi = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
-        assertTrue("Package should have two signatures, sig A and sig B",
-                pi.signatures.length == 2);
-        Set<String> sigsAfter = new HashSet<String>();
-        for (int i = 0; i < pi.signatures.length; i++) {
-            sigsAfter.add(pi.signatures[i].toCharsString());
-        }
-        assertTrue("Package signatures did not change after upgrade!",
-                sigsAfter.contains(sigBefore));
-        cleanUpInstall(pkgName);
-    }
-
-    /*
-     * Check if an apk gains signature-level permission after changing to the a
-     * new signature, for which a permission should be granted.
-     */
-    public void testUpgradeSigPermGained() throws Exception {
-        // install apk which defines permission
-        installFromRawResource("permDef.apk", R.raw.keyset_permdef_sa_unone,
-                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        // install apk which uses permission but does not have sig
-        installFromRawResource("permUse.apk", R.raw.keyset_permuse_sb_ua_ub,
-                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        // verify that package does not have perm before
-        PackageManager pm = getPm();
-        String permPkgName = "com.android.frameworks.coretests.keysets_permdef";
-        String pkgName = "com.android.frameworks.coretests.keysets";
-        String permName = "com.android.frameworks.coretests.keysets_permdef.keyset_perm";
-        assertFalse("keyset permission granted to app without same signature!",
-                    pm.checkPermission(permName, pkgName)
-                    == PackageManager.PERMISSION_GRANTED);
-        // upgrade to apk with perm signature
-        installFromRawResource("permUse2.apk", R.raw.keyset_permuse_sa_ua_ub,
-                PackageManager.INSTALL_REPLACE_EXISTING, false, false, -1,
-                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        assertTrue("keyset permission not granted to app after upgrade to same sig",
-                    pm.checkPermission(permName, pkgName)
-                    == PackageManager.PERMISSION_GRANTED);
-        cleanUpInstall(permPkgName);
-        cleanUpInstall(pkgName);
-    }
-
-    /*
-     * Check if an apk loses signature-level permission after changing to the a
-     * new signature, from one which a permission should be granted.
-     */
-    public void testUpgradeSigPermLost() throws Exception {
-        // install apk which defines permission
-        installFromRawResource("permDef.apk", R.raw.keyset_permdef_sa_unone,
-                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        // install apk which uses permission, signed by same sig
-        installFromRawResource("permUse.apk", R.raw.keyset_permuse_sa_ua_ub,
-                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        // verify that package does not have perm before
-        PackageManager pm = getPm();
-        String permPkgName = "com.android.frameworks.coretests.keysets_permdef";
-        String pkgName = "com.android.frameworks.coretests.keysets";
-        String permName = "com.android.frameworks.coretests.keysets_permdef.keyset_perm";
-        assertTrue("keyset permission not granted to app with same sig",
-                    pm.checkPermission(permName, pkgName)
-                    == PackageManager.PERMISSION_GRANTED);
-        // upgrade to apk without perm signature
-        installFromRawResource("permUse2.apk", R.raw.keyset_permuse_sb_ua_ub,
-                PackageManager.INSTALL_REPLACE_EXISTING, false, false, -1,
-                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-
-        assertFalse("keyset permission not revoked from app which upgraded to a "
-                    + "different signature",
-                    pm.checkPermission(permName, pkgName)
-                    == PackageManager.PERMISSION_GRANTED);
-        cleanUpInstall(permPkgName);
-        cleanUpInstall(pkgName);
-    }
-
-    /**
-     * The following tests are related to testing KeySets-based API
-     */
-
-    /*
-     * testGetSigningKeySetNull - ensure getSigningKeySet() returns null on null
-     * input and when calling a package other than that which made the call.
-     */
-    public void testGetSigningKeySet() throws Exception {
-        PackageManager pm = getPm();
-        String mPkgName = mContext.getPackageName();
-        String otherPkgName = "com.android.frameworks.coretests.keysets_api";
-        KeySet ks;
-        try {
-            ks = pm.getSigningKeySet(null);
-            assertTrue(false); // should have thrown
-        } catch (NullPointerException e) {
-        }
-        try {
-            ks = pm.getSigningKeySet("keysets.test.bogus.package");
-            assertTrue(false); // should have thrown
-        } catch (IllegalArgumentException e) {
-        }
-        final InstallParams ip = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
-                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        try {
-            ks = pm.getSigningKeySet(otherPkgName);
-            assertTrue(false); // should have thrown
-        } catch (SecurityException e) {
-        } finally {
-            cleanUpInstall(ip);
-        }
-        ks = pm.getSigningKeySet(mContext.getPackageName());
-        assertNotNull(ks);
-    }
-
-    /*
-     * testGetKeySetByAlias - same as getSigningKeySet, but for keysets defined
-     * by this package.
-     */
-    public void testGetKeySetByAlias() throws Exception {
-        PackageManager pm = getPm();
-        String mPkgName = mContext.getPackageName();
-        String otherPkgName = "com.android.frameworks.coretests.keysets_api";
-        KeySet ks;
-        try {
-            ks = pm.getKeySetByAlias(null, null);
-            assertTrue(false); // should have thrown
-        } catch (NullPointerException e) {
-        }
-        try {
-            ks = pm.getKeySetByAlias(null, "keysetBogus");
-            assertTrue(false); // should have thrown
-        } catch (NullPointerException e) {
-        }
-        try {
-            ks = pm.getKeySetByAlias("keysets.test.bogus.package", null);
-            assertTrue(false); // should have thrown
-        } catch (NullPointerException e) {
-        }
-        try {
-            ks = pm.getKeySetByAlias("keysets.test.bogus.package", "A");
-            assertTrue(false); // should have thrown
-        } catch(IllegalArgumentException e) {
-        }
-        try {
-            ks = pm.getKeySetByAlias(mPkgName, "keysetBogus");
-            assertTrue(false); // should have thrown
-        } catch(IllegalArgumentException e) {
-        }
-
-        // make sure we can get a KeySet from our pkg
-        ks = pm.getKeySetByAlias(mPkgName, "A");
-        assertNotNull(ks);
-
-        // and another
-        final InstallParams ip = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
-                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        try {
-            ks = pm.getKeySetByAlias(otherPkgName, "A");
-            assertNotNull(ks);
-        } finally {
-            cleanUpInstall(ip);
-        }
-    }
-
-    public void testIsSignedBy() throws Exception {
-        PackageManager pm = getPm();
-        String mPkgName = mContext.getPackageName();
-        String otherPkgName = "com.android.frameworks.coretests.keysets_api";
-        KeySet mSigningKS = pm.getSigningKeySet(mPkgName);
-        KeySet mDefinedKS = pm.getKeySetByAlias(mPkgName, "A");
-
-        try {
-            assertFalse(pm.isSignedBy(null, null));
-            assertTrue(false); // should have thrown
-        } catch (NullPointerException e) {
-        }
-        try {
-            assertFalse(pm.isSignedBy(null, mSigningKS));
-            assertTrue(false); // should have thrown
-        } catch (NullPointerException e) {
-        }
-        try {
-            assertFalse(pm.isSignedBy(mPkgName, null));
-            assertTrue(false); // should have thrown
-        } catch (NullPointerException e) {
-        }
-        try {
-            assertFalse(pm.isSignedBy("keysets.test.bogus.package", mDefinedKS));
-        } catch(IllegalArgumentException e) {
-        }
-        assertFalse(pm.isSignedBy(mPkgName, mDefinedKS));
-        assertFalse(pm.isSignedBy(mPkgName, new KeySet(new Binder())));
-        assertTrue(pm.isSignedBy(mPkgName, mSigningKS));
-
-        final InstallParams ip1 = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
-                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        try {
-            assertFalse(pm.isSignedBy(otherPkgName, mDefinedKS));
-            assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
-        } finally {
-            cleanUpInstall(ip1);
-        }
-
-        final InstallParams ip2 = installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
-                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        try {
-            assertTrue(pm.isSignedBy(otherPkgName, mDefinedKS));
-            assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
-        } finally {
-            cleanUpInstall(ip2);
-        }
-    }
-
-    public void testIsSignedByExactly() throws Exception {
-        PackageManager pm = getPm();
-        String mPkgName = mContext.getPackageName();
-        String otherPkgName = "com.android.frameworks.coretests.keysets_api";
-        KeySet mSigningKS = pm.getSigningKeySet(mPkgName);
-        KeySet mDefinedKS = pm.getKeySetByAlias(mPkgName, "A");
-        try {
-            assertFalse(pm.isSignedBy(null, null));
-            assertTrue(false); // should have thrown
-        } catch (NullPointerException e) {
-        }
-        try {
-            assertFalse(pm.isSignedBy(null, mSigningKS));
-            assertTrue(false); // should have thrown
-        } catch (NullPointerException e) {
-        }
-        try {
-            assertFalse(pm.isSignedBy(mPkgName, null));
-            assertTrue(false); // should have thrown
-        } catch (NullPointerException e) {
-        }
-        try {
-            assertFalse(pm.isSignedByExactly("keysets.test.bogus.package", mDefinedKS));
-        } catch(IllegalArgumentException e) {
-        }
-        assertFalse(pm.isSignedByExactly(mPkgName, mDefinedKS));
-        assertFalse(pm.isSignedByExactly(mPkgName, new KeySet(new Binder())));
-        assertTrue(pm.isSignedByExactly(mPkgName, mSigningKS));
-
-        final InstallParams ip1 = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
-                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        try {
-            assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
-            assertTrue(pm.isSignedByExactly(otherPkgName, mSigningKS));
-        } finally {
-            cleanUpInstall(ip1);
-        }
-
-        final InstallParams ip2 = installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
-                0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        try {
-            assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
-            assertFalse(pm.isSignedByExactly(otherPkgName, mSigningKS));
-        } finally {
-            cleanUpInstall(ip2);
-        }
-    }
-
-
-
-    /**
-     * The following tests are related to testing the checkSignatures api.
-     */
-    private void checkSignatures(int apk1, int apk2, int expMatchResult) throws Exception {
-        checkSharedSignatures(apk1, apk2, true, false, -1, expMatchResult);
-    }
-
-    @LargeTest
-    public void testCheckSignaturesAllMatch() throws Exception {
-        int apk1 = APP1_CERT1_CERT2;
-        int apk2 = APP2_CERT1_CERT2;
-        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
-    }
-
-    @LargeTest
-    public void testCheckSignaturesNoMatch() throws Exception {
-        int apk1 = APP1_CERT1;
-        int apk2 = APP2_CERT2;
-        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH);
-    }
-
-    @LargeTest
-    public void testCheckSignaturesSomeMatch1() throws Exception {
-        int apk1 = APP1_CERT1_CERT2;
-        int apk2 = APP2_CERT1;
-        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH);
-    }
-
-    @LargeTest
-    public void testCheckSignaturesSomeMatch2() throws Exception {
-        int apk1 = APP1_CERT1_CERT2;
-        int apk2 = APP2_CERT2;
-        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH);
-    }
-
-    @LargeTest
-    public void testCheckSignaturesMoreMatch() throws Exception {
-        int apk1 = APP1_CERT1;
-        int apk2 = APP2_CERT1_CERT2;
-        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH);
-    }
-
-    @LargeTest
-    public void testCheckSignaturesUnknown() throws Exception {
-        int apk1 = APP1_CERT1_CERT2;
-        int apk2 = APP2_CERT1_CERT2;
-        String apk1Name = "install1.apk";
-        String apk2Name = "install2.apk";
-
-        final InstallParams ip = installFromRawResource(apk1Name, apk1, 0, false,
-                false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        try {
-            PackageManager pm = mContext.getPackageManager();
-            // Delete app2
-            File filesDir = mContext.getFilesDir();
-            File outFile = new File(filesDir, apk2Name);
-            int rawResId = apk2;
-            Uri packageURI = getInstallablePackage(rawResId, outFile);
-            var pkg = parsePackage(packageURI);
-            try {
-                getPi().uninstall(pkg.getPackageName(),
-                        PackageManager.DELETE_ALL_USERS,
-                        null /*statusReceiver*/);
-            } catch (IllegalArgumentException ignore) {
-            }
-            // Check signatures now
-            int match = mContext.getPackageManager().checkSignatures(
-                    ip.pkg.getPackageName(), pkg.getPackageName());
-            assertEquals(PackageManager.SIGNATURE_UNKNOWN_PACKAGE, match);
-        } finally {
-            cleanUpInstall(ip);
-        }
-    }
-
-    @LargeTest
-    public void testCheckSignaturesRotatedAgainstOriginal() throws Exception {
-        // checkSignatures should be backwards compatible with pre-rotation behavior; this test
-        // verifies that an app signed with a rotated key results in a signature match with an app
-        // signed with the original key in the lineage.
-        int apk1 = APP1_CERT5;
-        int apk2 = APP2_CERT5_ROTATED_CERT6;
-        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
-    }
-
-    @LargeTest
-    public void testCheckSignaturesRotatedAgainstRotated() throws Exception {
-        // checkSignatures should be successful when both apps have been signed with the same
-        // rotated key since the initial signature comparison between the two apps should
-        // return a match.
-        int apk1 = APP1_CERT5_ROTATED_CERT6;
-        int apk2 = APP2_CERT5_ROTATED_CERT6;
-        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
-    }
-
-    @LargeTest
-    public void testInstallNoCertificates() throws Exception {
-        int apk1 = APP1_UNSIGNED;
-        String apk1Name = "install1.apk";
-
-        installFromRawResource(apk1Name, apk1, 0, false,
-                true, PackageInstaller.STATUS_FAILURE_INVALID,
-                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-    }
-
-    /*
-     * The following tests are related to apps using shared uids signed with
-     * different certs.
-     */
-    private int SHARED1_UNSIGNED = R.raw.install_shared1_unsigned;
-
-    private int SHARED1_CERT1 = R.raw.install_shared1_cert1;
-
-    private int SHARED1_CERT2 = R.raw.install_shared1_cert2;
-
-    private int SHARED1_CERT1_CERT2 = R.raw.install_shared1_cert1_cert2;
-
-    private int SHARED2_UNSIGNED = R.raw.install_shared2_unsigned;
-
-    private int SHARED2_CERT1 = R.raw.install_shared2_cert1;
-
-    private int SHARED2_CERT2 = R.raw.install_shared2_cert2;
-
-    private int SHARED2_CERT1_CERT2 = R.raw.install_shared2_cert1_cert2;
-
-    private void checkSharedSignatures(int apk1, int apk2, boolean cleanUp, boolean fail,
-            int retCode, int expMatchResult) throws Exception {
-        String apk1Name = "install1.apk";
-        String apk2Name = "install2.apk";
-        var pkg1 = getParsedPackage(apk1Name, apk1);
-        var pkg2 = getParsedPackage(apk2Name, apk2);
-
-        try {
-            // Clean up before testing first.
-            cleanUpInstall(pkg1.getPackageName());
-            cleanUpInstall(pkg2.getPackageName());
-            installFromRawResource(apk1Name, apk1, 0, false, false, -1,
-                    PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-            if (fail) {
-                installFromRawResource(apk2Name, apk2, 0, false, true, retCode,
-                        PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-            } else {
-                installFromRawResource(apk2Name, apk2, 0, false, false, -1,
-                        PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-                // TODO: All checkSignatures tests should return the same result regardless of
-                // querying by package name or uid; however if there are any edge cases where
-                // individual packages within a shareduid are compared with signatures that do not
-                // match the full lineage of the shareduid this method should be overloaded to
-                // accept the expected response for the uid query.
-                PackageManager pm = getPm();
-                int matchByName = pm.checkSignatures(pkg1.getPackageName(), pkg2.getPackageName());
-                int pkg1Uid = pm.getApplicationInfo(pkg1.getPackageName(), 0).uid;
-                int pkg2Uid = pm.getApplicationInfo(pkg2.getPackageName(), 0).uid;
-                int matchByUid = pm.checkSignatures(pkg1Uid, pkg2Uid);
-                assertEquals(expMatchResult, matchByName);
-                assertEquals(expMatchResult, matchByUid);
-            }
-        } finally {
-            if (cleanUp) {
-                cleanUpInstall(pkg1.getPackageName());
-                cleanUpInstall(pkg2.getPackageName());
-            }
-        }
-    }
-
-    @LargeTest
-    public void testCheckSignaturesSharedAllMatch() throws Exception {
-        int apk1 = SHARED1_CERT1_CERT2;
-        int apk2 = SHARED2_CERT1_CERT2;
-        boolean fail = false;
-        int retCode = -1;
-        int expMatchResult = PackageManager.SIGNATURE_MATCH;
-        checkSharedSignatures(apk1, apk2, true, fail, retCode, expMatchResult);
-    }
-
-    @LargeTest
-    public void testCheckSignaturesSharedNoMatch() throws Exception {
-        int apk1 = SHARED1_CERT1;
-        int apk2 = SHARED2_CERT2;
-        boolean fail = true;
-        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
-        int expMatchResult = -1;
-        checkSharedSignatures(apk1, apk2, true, fail, retCode, expMatchResult);
-    }
-
-    /*
-     * Test that an app signed with cert1 and cert2 cannot be replaced when
-     * signed with cert1 alone.
-     */
-    @LargeTest
-    public void testCheckSignaturesSharedSomeMatch1() throws Exception {
-        int apk1 = SHARED1_CERT1_CERT2;
-        int apk2 = SHARED2_CERT1;
-        boolean fail = true;
-        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
-        int expMatchResult = -1;
-        checkSharedSignatures(apk1, apk2, true, fail, retCode, expMatchResult);
-    }
-
-    /*
-     * Test that an app signed with cert1 and cert2 cannot be replaced when
-     * signed with cert2 alone.
-     */
-    @LargeTest
-    public void testCheckSignaturesSharedSomeMatch2() throws Exception {
-        int apk1 = SHARED1_CERT1_CERT2;
-        int apk2 = SHARED2_CERT2;
-        boolean fail = true;
-        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
-        int expMatchResult = -1;
-        checkSharedSignatures(apk1, apk2, true, fail, retCode, expMatchResult);
-    }
-
-    @LargeTest
-    public void testCheckSignaturesSharedUnknown() throws Exception {
-        int apk1 = SHARED1_CERT1_CERT2;
-        int apk2 = SHARED2_CERT1_CERT2;
-        String apk1Name = "install1.apk";
-        String apk2Name = "install2.apk";
-        InstallParams ip1 = null;
-
-        try {
-            ip1 = installFromRawResource(apk1Name, apk1, 0, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-            PackageManager pm = mContext.getPackageManager();
-            // Delete app2
-            var pkg = getParsedPackage(apk2Name, apk2);
-            try {
-                getPi().uninstall(pkg.getPackageName(), PackageManager.DELETE_ALL_USERS,
-                        null /*statusReceiver*/);
-            } catch (IllegalArgumentException ignore) {
-            }
-            // Check signatures now
-            int match = mContext.getPackageManager().checkSignatures(
-                    ip1.pkg.getPackageName(), pkg.getPackageName());
-            assertEquals(PackageManager.SIGNATURE_UNKNOWN_PACKAGE, match);
-        } finally {
-            if (ip1 != null) {
-                cleanUpInstall(ip1);
-            }
-        }
-    }
-
-    @LargeTest
-    public void testReplaceFirstSharedMatchAllCerts() throws Exception {
-        int apk1 = SHARED1_CERT1;
-        int apk2 = SHARED2_CERT1;
-        int rapk1 = SHARED1_CERT1;
-        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
-        replaceCerts(apk1, rapk1, true, false, -1);
-    }
-
-    @LargeTest
-    public void testReplaceSecondSharedMatchAllCerts() throws Exception {
-        int apk1 = SHARED1_CERT1;
-        int apk2 = SHARED2_CERT1;
-        int rapk2 = SHARED2_CERT1;
-        checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
-        replaceCerts(apk2, rapk2, true, false, -1);
-    }
-
-    @LargeTest
-    public void testReplaceFirstSharedMatchSomeCerts() throws Exception {
-        int apk1 = SHARED1_CERT1_CERT2;
-        int apk2 = SHARED2_CERT1_CERT2;
-        int rapk1 = SHARED1_CERT1;
-        boolean fail = true;
-        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
-        checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
-        installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true,
-                fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-    }
-
-    @LargeTest
-    public void testReplaceSecondSharedMatchSomeCerts() throws Exception {
-        int apk1 = SHARED1_CERT1_CERT2;
-        int apk2 = SHARED2_CERT1_CERT2;
-        int rapk2 = SHARED2_CERT1;
-        boolean fail = true;
-        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
-        checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
-        installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true,
-                fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-    }
-
-    @LargeTest
-    public void testReplaceFirstSharedMatchNoCerts() throws Exception {
-        int apk1 = SHARED1_CERT1;
-        int apk2 = SHARED2_CERT1;
-        int rapk1 = SHARED1_CERT2;
-        boolean fail = true;
-        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
-        checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
-        installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true,
-                fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-    }
-
-    @LargeTest
-    public void testReplaceSecondSharedMatchNoCerts() throws Exception {
-        int apk1 = SHARED1_CERT1;
-        int apk2 = SHARED2_CERT1;
-        int rapk2 = SHARED2_CERT2;
-        boolean fail = true;
-        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
-        checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
-        installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true,
-                fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-    }
-
-    @LargeTest
-    public void testReplaceFirstSharedMatchMoreCerts() throws Exception {
-        int apk1 = SHARED1_CERT1;
-        int apk2 = SHARED2_CERT1;
-        int rapk1 = SHARED1_CERT1_CERT2;
-        boolean fail = true;
-        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
-        checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
-        installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true,
-                fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-    }
-
-    @LargeTest
-    public void testReplaceSecondSharedMatchMoreCerts() throws Exception {
-        int apk1 = SHARED1_CERT1;
-        int apk2 = SHARED2_CERT1;
-        int rapk2 = SHARED2_CERT1_CERT2;
-        boolean fail = true;
-        int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
-        checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
-        installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true,
-                fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-    }
-
-    /**
-     * Unknown features should be allowed to install. This prevents older phones
-     * from rejecting new packages that specify features that didn't exist when
-     * an older phone existed. All older phones are assumed to have those
-     * features.
-     * <p>
-     * Right now we allow all packages to be installed regardless of their
-     * features.
-     */
-    @LargeTest
-    public void testUsesFeatureUnknownFeature() throws Exception {
-        int retCode = PackageManager.INSTALL_SUCCEEDED;
-        installFromRawResource("install.apk", R.raw.install_uses_feature, 0, true, false, retCode,
-                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-    }
-
-    @LargeTest
-    public void testInstallNonexistentFile() throws Exception {
-        int retCode = PackageInstaller.STATUS_FAILURE_INVALID;
-        File invalidFile = new File("/nonexistent-file.apk");
-        invokeInstallPackageFail(Uri.fromFile(invalidFile), 0, retCode);
-    }
-
-    @SmallTest
-    public void testGetVerifierDeviceIdentity() throws Exception {
-        PackageManager pm = getPm();
-        VerifierDeviceIdentity id = pm.getVerifierDeviceIdentity();
-
-        assertNotNull("Verifier device identity should not be null", id);
-    }
-
-    public void testGetInstalledPackages() throws Exception {
-        List<PackageInfo> packages = getPm().getInstalledPackages(0);
-        assertNotNull("installed packages cannot be null", packages);
-        assertTrue("installed packages cannot be empty", packages.size() > 0);
-    }
-
-    public void testGetUnInstalledPackages() throws Exception {
-        List<PackageInfo> packages = getPm().getInstalledPackages(
-                PackageManager.MATCH_UNINSTALLED_PACKAGES);
-        assertNotNull("installed packages cannot be null", packages);
-        assertTrue("installed packages cannot be empty", packages.size() > 0);
-    }
-
-    /**
-     * Test that getInstalledPackages returns all the data specified in flags.
-     */
-    public void testGetInstalledPackagesAll() throws Exception {
-        final int flags = PackageManager.GET_ACTIVITIES | PackageManager.GET_GIDS
-                | PackageManager.GET_CONFIGURATIONS | PackageManager.GET_INSTRUMENTATION
-                | PackageManager.GET_PERMISSIONS | PackageManager.GET_PROVIDERS
-                | PackageManager.GET_RECEIVERS | PackageManager.GET_SERVICES
-                | PackageManager.GET_SIGNATURES | PackageManager.MATCH_UNINSTALLED_PACKAGES;
-
-        final InstallParams ip =
-                installFromRawResource("install.apk", R.raw.install_complete_package_info,
-                        0 /*flags*/, false /*cleanUp*/, false /*fail*/, -1 /*result*/,
-                        PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-        try {
-            final List<PackageInfo> packages = getPm().getInstalledPackages(flags);
-            assertNotNull("installed packages cannot be null", packages);
-            assertTrue("installed packages cannot be empty", packages.size() > 0);
-
-            PackageInfo packageInfo = null;
-
-            // Find the package with all components specified in the AndroidManifest
-            // to ensure no null values
-            for (PackageInfo pi : packages) {
-                if ("com.android.frameworks.coretests.install_complete_package_info"
-                        .equals(pi.packageName)) {
-                    packageInfo = pi;
-                    break;
-                }
-            }
-            assertNotNull("activities should not be null", packageInfo.activities);
-            assertNotNull("configPreferences should not be null", packageInfo.configPreferences);
-            assertNotNull("instrumentation should not be null", packageInfo.instrumentation);
-            assertNotNull("permissions should not be null", packageInfo.permissions);
-            assertNotNull("providers should not be null", packageInfo.providers);
-            assertNotNull("receivers should not be null", packageInfo.receivers);
-            assertNotNull("services should not be null", packageInfo.services);
-            assertNotNull("signatures should not be null", packageInfo.signatures);
-        } finally {
-            cleanUpInstall(ip);
-        }
-    }
-
-    /**
-     * Test that getInstalledPackages returns all the data specified in
-     * flags when the GET_UNINSTALLED_PACKAGES flag is set.
-     */
-    public void testGetUnInstalledPackagesAll() throws Exception {
-        final int flags = PackageManager.MATCH_UNINSTALLED_PACKAGES
-                | PackageManager.GET_ACTIVITIES | PackageManager.GET_GIDS
-                | PackageManager.GET_CONFIGURATIONS | PackageManager.GET_INSTRUMENTATION
-                | PackageManager.GET_PERMISSIONS | PackageManager.GET_PROVIDERS
-                | PackageManager.GET_RECEIVERS | PackageManager.GET_SERVICES
-                | PackageManager.GET_SIGNATURES;
-
-        // first, install the package
-        final InstallParams ip =
-                installFromRawResource("install.apk", R.raw.install_complete_package_info,
-                        0 /*flags*/, false /*cleanUp*/, false /*fail*/, -1 /*result*/,
-                        PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-        try {
-            // then, remove it, keeping it's data around
-            final GenericReceiver receiver = new DeleteReceiver(ip.pkg.getPackageName());
-            invokeDeletePackage(ip.pkg.getPackageName(), PackageManager.DELETE_KEEP_DATA, receiver);
-
-            final List<PackageInfo> packages = getPm().getInstalledPackages(flags);
-            assertNotNull("installed packages cannot be null", packages);
-            assertTrue("installed packages cannot be empty", packages.size() > 0);
-
-            PackageInfo packageInfo = null;
-
-            // Find the package with all components specified in the AndroidManifest
-            // to ensure no null values
-            for (PackageInfo pi : packages) {
-                if ("com.android.frameworks.coretests.install_complete_package_info"
-                        .equals(pi.packageName)) {
-                    packageInfo = pi;
-                    break;
-                }
-            }
-            assertNotNull("activities should not be null", packageInfo.activities);
-            assertNotNull("configPreferences should not be null", packageInfo.configPreferences);
-            assertNotNull("instrumentation should not be null", packageInfo.instrumentation);
-            assertNotNull("permissions should not be null", packageInfo.permissions);
-            assertNotNull("providers should not be null", packageInfo.providers);
-            assertNotNull("receivers should not be null", packageInfo.receivers);
-            assertNotNull("services should not be null", packageInfo.services);
-            assertNotNull("signatures should not be null", packageInfo.signatures);
-        } finally {
-            cleanUpInstall(ip);
-        }
-    }
-
-    @Suppress
-    public void testInstall_BadDex_CleanUp() throws Exception {
-        int retCode = PackageInstaller.STATUS_FAILURE_INVALID;
-        installFromRawResource("install.apk", R.raw.install_bad_dex, 0, true, true, retCode,
-                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-    }
-
-    private static class TestDexModuleRegisterCallback
-            extends PackageManager.DexModuleRegisterCallback {
-        private String mDexModulePath = null;
-        private boolean mSuccess = false;
-        private String mMessage = null;
-        CountDownLatch doneSignal = new CountDownLatch(1);
-
-        @Override
-        public void onDexModuleRegistered(String dexModulePath, boolean success, String message) {
-            mDexModulePath = dexModulePath;
-            mSuccess = success;
-            mMessage = message;
-            doneSignal.countDown();
-        }
-
-        boolean waitTillDone() {
-            long startTime = System.currentTimeMillis();
-            while (System.currentTimeMillis() - startTime < MAX_WAIT_TIME) {
-                try {
-                    return doneSignal.await(MAX_WAIT_TIME, TimeUnit.MILLISECONDS);
-                } catch (InterruptedException e) {
-                    Log.i(TAG, "Interrupted during sleep", e);
-                }
-            }
-            return false;
-        }
-
-    }
-
-    // Verify that the base code path cannot be registered.
-    public void testRegisterDexModuleBaseCode() throws Exception {
-        PackageManager pm = getPm();
-        ApplicationInfo info = getContext().getApplicationInfo();
-        TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
-        pm.registerDexModule(info.getBaseCodePath(), callback);
-        assertTrue(callback.waitTillDone());
-        assertEquals(info.getBaseCodePath(), callback.mDexModulePath);
-        assertFalse("BaseCodePath should not be registered", callback.mSuccess);
-    }
-
-    // Verify that modules which are not own by the calling package are not registered.
-    public void testRegisterDexModuleNotOwningModule() throws Exception {
-        TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
-        String moduleBelongingToOtherPackage = "/data/user/0/com.google.android.gms/module.apk";
-        getPm().registerDexModule(moduleBelongingToOtherPackage, callback);
-        assertTrue(callback.waitTillDone());
-        assertEquals(moduleBelongingToOtherPackage, callback.mDexModulePath);
-        assertTrue(callback.waitTillDone());
-        assertFalse("Only modules belonging to the calling package can be registered",
-                callback.mSuccess);
-    }
-
-    // Verify that modules owned by the package are successfully registered.
-    public void testRegisterDexModuleSuccessfully() throws Exception {
-        ApplicationInfo info = getContext().getApplicationInfo();
-        // Copy the main apk into the data folder and use it as a "module".
-        File dexModuleDir = new File(info.dataDir, "module-dir");
-        File dexModule = new File(dexModuleDir, "module.apk");
-        try {
-            assertNotNull(FileUtils.createDir(
-                    dexModuleDir.getParentFile(), dexModuleDir.getName()));
-            Files.copy(Paths.get(info.getBaseCodePath()), dexModule.toPath(),
-                    StandardCopyOption.REPLACE_EXISTING);
-            TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
-            getPm().registerDexModule(dexModule.toString(), callback);
-            assertTrue(callback.waitTillDone());
-            assertEquals(dexModule.toString(), callback.mDexModulePath);
-            assertTrue(callback.waitTillDone());
-            assertTrue(callback.mMessage, callback.mSuccess);
-
-            // NOTE:
-            // This actually verifies internal behaviour which might change. It's not
-            // ideal but it's the best we can do since there's no other place we can currently
-            // write a better test.
-            for(String isa : getAppDexInstructionSets(info)) {
-                Files.exists(Paths.get(dexModuleDir.toString(), "oat", isa, "module.odex"));
-                Files.exists(Paths.get(dexModuleDir.toString(), "oat", isa, "module.vdex"));
-            }
-        } finally {
-            FileUtils.deleteContentsAndDir(dexModuleDir);
-        }
-    }
-
-    // If the module does not exist on disk we should get a failure.
-    public void testRegisterDexModuleNotExists() throws Exception {
-        ApplicationInfo info = getContext().getApplicationInfo();
-        String nonExistentApk = Paths.get(info.dataDir, "non-existent.apk").toString();
-        TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
-        getPm().registerDexModule(nonExistentApk, callback);
-        assertTrue(callback.waitTillDone());
-        assertEquals(nonExistentApk, callback.mDexModulePath);
-        assertTrue(callback.waitTillDone());
-        assertFalse("DexModule registration should fail", callback.mSuccess);
-    }
-
-    // If the module does not exist on disk we should get a failure.
-    public void testRegisterDexModuleNotExistsNoCallback() throws Exception {
-        ApplicationInfo info = getContext().getApplicationInfo();
-        String nonExistentApk = Paths.get(info.dataDir, "non-existent.apk").toString();
-        getPm().registerDexModule(nonExistentApk, null);
-    }
-
-    // Copied from com.android.server.pm.InstructionSets because we don't have access to it here.
-    private static String[] getAppDexInstructionSets(ApplicationInfo info) {
-        if (info.primaryCpuAbi != null) {
-            if (info.secondaryCpuAbi != null) {
-                return new String[] {
-                        VMRuntime.getInstructionSet(info.primaryCpuAbi),
-                        VMRuntime.getInstructionSet(info.secondaryCpuAbi) };
-            } else {
-                return new String[] {
-                        VMRuntime.getInstructionSet(info.primaryCpuAbi) };
-            }
-        }
-
-        return new String[] { VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]) };
-    }
-
-    /*---------- Recommended install location tests ----*/
-    /*
-     * TODO's
-     * check version numbers for upgrades
-     * check permissions of installed packages
-     * how to do tests on updated system apps?
-     * verify updates to system apps cannot be installed on the sdcard.
-     */
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
deleted file mode 100644
index 9ce99d6..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ /dev/null
@@ -1,1150 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.permission.CompatibilityPermissionInfo.COMPAT_PERMS;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import static java.lang.Boolean.TRUE;
-import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
-import static java.util.stream.Collectors.toList;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.ConfigurationInfo;
-import android.content.pm.FeatureGroupInfo;
-import android.content.pm.FeatureInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.Property;
-import android.content.pm.ServiceInfo;
-import android.content.pm.Signature;
-import android.content.pm.SigningDetails;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.platform.test.annotations.Presubmit;
-import android.util.ArraySet;
-
-import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.parsing.PackageCacher;
-import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.PackageParser2;
-import com.android.server.pm.parsing.TestPackageParser2;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
-import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.permission.CompatibilityPermissionInfo;
-import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.PackageUserStateInternal;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedActivityImpl;
-import com.android.server.pm.pkg.component.ParsedApexSystemService;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
-import com.android.server.pm.pkg.component.ParsedInstrumentationImpl;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
-import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl;
-import com.android.server.pm.pkg.component.ParsedPermissionImpl;
-import com.android.server.pm.pkg.component.ParsedPermissionUtils;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedProviderImpl;
-import com.android.server.pm.pkg.component.ParsedService;
-import com.android.server.pm.pkg.component.ParsedServiceImpl;
-import com.android.server.pm.pkg.component.ParsedUsesPermission;
-import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Array;
-import java.lang.reflect.Field;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class PackageParserTest {
-    // TODO(b/135203078): Update this test with all fields and validate equality. Initial change
-    //  was just migrating to new interfaces. Consider adding actual equals() methods.
-
-    @Rule
-    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
-
-    private File mTmpDir;
-    private static final File FRAMEWORK = new File("/system/framework/framework-res.apk");
-    private static final String TEST_APP1_APK = "PackageParserTestApp1.apk";
-    private static final String TEST_APP2_APK = "PackageParserTestApp2.apk";
-    private static final String TEST_APP3_APK = "PackageParserTestApp3.apk";
-    private static final String TEST_APP4_APK = "PackageParserTestApp4.apk";
-    private static final String TEST_APP5_APK = "PackageParserTestApp5.apk";
-    private static final String TEST_APP6_APK = "PackageParserTestApp6.apk";
-    private static final String PACKAGE_NAME = "com.android.servicestests.apps.packageparserapp";
-
-    @Before
-    public void setUp() throws IOException {
-        // Create a new temporary directory for each of our tests.
-        mTmpDir = mTemporaryFolder.newFolder("PackageParserTest");
-    }
-
-    @Test
-    public void testParse_noCache() throws Exception {
-        CachePackageNameParser pp = new CachePackageNameParser(null);
-        ParsedPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
-                false /* useCaches */);
-        assertNotNull(pkg);
-
-        pp.setCacheDir(mTmpDir);
-        pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
-                false /* useCaches */);
-        assertNotNull(pkg);
-
-        // Make sure that we always write out a cache entry for future reference,
-        // whether or not we're asked to use caches.
-        assertEquals(1, mTmpDir.list().length);
-    }
-
-    @Test
-    public void testParse_withCache() throws Exception {
-        CachePackageNameParser pp = new CachePackageNameParser(null);
-
-        pp.setCacheDir(mTmpDir);
-        // The first parse will write this package to the cache.
-        pp.parsePackage(FRAMEWORK, 0 /* parseFlags */, true /* useCaches */);
-
-        // Now attempt to parse the package again, should return the
-        // cached result.
-        ParsedPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
-                true /* useCaches */);
-        assertEquals("cache_android", pkg.getPackageName());
-
-        // Try again, with useCaches == false, shouldn't return the parsed
-        // result.
-        pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */, false /* useCaches */);
-        assertEquals("android", pkg.getPackageName());
-
-        // We haven't set a cache directory here : the parse should still succeed,
-        // just not using the cached results.
-        pp = new CachePackageNameParser(null);
-        pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */, true /* useCaches */);
-        assertEquals("android", pkg.getPackageName());
-
-        pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */, false /* useCaches */);
-        assertEquals("android", pkg.getPackageName());
-    }
-
-    @Test
-    public void test_serializePackage() throws Exception {
-        try (PackageParser2 pp = PackageParser2.forParsingFileWithDefaults()) {
-            AndroidPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
-                    true /* useCaches */).hideAsFinal();
-
-            Parcel p = Parcel.obtain();
-            ((Parcelable) pkg).writeToParcel(p, 0 /* flags */);
-
-            p.setDataPosition(0);
-            ParsedPackage deserialized = new PackageImpl(p);
-
-            assertPackagesEqual(pkg, deserialized);
-        }
-    }
-
-    @Test
-    @SmallTest
-    @Presubmit
-    public void test_roundTripKnownFields() throws Exception {
-        ParsingPackage pkg = PackageImpl.forTesting("foo");
-        setKnownFields(pkg);
-
-        Parcel p = Parcel.obtain();
-        ((Parcelable) pkg).writeToParcel(p, 0 /* flags */);
-
-        p.setDataPosition(0);
-        ParsedPackage deserialized = new PackageImpl(p);
-        assertAllFieldsExist(deserialized);
-    }
-
-    @Test
-    public void test_stringInterning() throws Exception {
-        ParsingPackage pkg = PackageImpl.forTesting("foo");
-        setKnownFields(pkg);
-
-        Parcel p = Parcel.obtain();
-        ((Parcelable) pkg.hideAsParsed().hideAsFinal()).writeToParcel(p, 0 /* flags */);
-
-        p.setDataPosition(0);
-        AndroidPackage deserialized = new PackageImpl(p);
-
-        p.setDataPosition(0);
-        AndroidPackage deserialized2 = new PackageImpl(p);
-
-        assertSame(deserialized.getPackageName(), deserialized2.getPackageName());
-        assertSame(deserialized.getPermission(),
-                deserialized2.getPermission());
-        assertSame(deserialized.getRequestedPermissions().get(0),
-                deserialized2.getRequestedPermissions().get(0));
-
-        List<String> protectedBroadcastsOne = new ArrayList<>(1);
-        protectedBroadcastsOne.addAll(deserialized.getProtectedBroadcasts());
-
-        List<String> protectedBroadcastsTwo = new ArrayList<>(1);
-        protectedBroadcastsTwo.addAll(deserialized2.getProtectedBroadcasts());
-
-        assertSame(protectedBroadcastsOne.get(0), protectedBroadcastsTwo.get(0));
-
-        assertSame(deserialized.getUsesLibraries().get(0),
-                deserialized2.getUsesLibraries().get(0));
-        assertSame(deserialized.getUsesOptionalLibraries().get(0),
-                deserialized2.getUsesOptionalLibraries().get(0));
-        assertSame(deserialized.getVersionName(), deserialized2.getVersionName());
-        assertSame(deserialized.getSharedUserId(), deserialized2.getSharedUserId());
-    }
-
-    private File extractFile(String filename) throws Exception {
-        final Context context = InstrumentationRegistry.getTargetContext();
-        final File tmpFile = File.createTempFile(filename, ".apk");
-        try (InputStream inputStream = context.getAssets().openNonAsset(filename)) {
-            Files.copy(inputStream, tmpFile.toPath(), REPLACE_EXISTING);
-        }
-        return tmpFile;
-    }
-
-    /**
-     * Extracts the asset file to $mTmpDir/$dirname/$filename.
-     */
-    private File extractFile(String filename, String dirname) throws Exception {
-        final Context context = InstrumentationRegistry.getTargetContext();
-        File dir = new File(mTmpDir, dirname);
-        dir.mkdir();
-        final File tmpFile = new File(dir, filename);
-        try (InputStream inputStream = context.getAssets().openNonAsset(filename)) {
-            Files.copy(inputStream, tmpFile.toPath(), REPLACE_EXISTING);
-        }
-        return tmpFile;
-    }
-
-    /**
-     * Tests the path of cached ParsedPackage.
-     */
-    @Test
-    public void testCache_SameFileName() throws Exception {
-        // Prepare 2 package files with the same name but different paths
-        TestPackageParser2 parser = new TestPackageParser2(mTmpDir);
-        final File f1 = extractFile(TEST_APP1_APK, "dir1");
-        final File f2 = extractFile(TEST_APP1_APK, "dir2");
-        // Sleep for a while so that the cache file will be newer and valid
-        Thread.sleep(1000);
-        ParsedPackage pr1 = parser.parsePackage(f1, 0, true);
-        ParsedPackage pr2 = parser.parsePackage(f2, 0, true);
-        // Check the path of cached ParsedPackage
-        assertThat(pr1.getPath()).isEqualTo(f1.getAbsolutePath());
-        assertThat(pr2.getPath()).isEqualTo(f2.getAbsolutePath());
-    }
-
-    /**
-     * Tests AndroidManifest.xml with no android:isolatedSplits attribute.
-     */
-    @Test
-    public void testParseIsolatedSplitsDefault() throws Exception {
-        final File testFile = extractFile(TEST_APP1_APK);
-        try {
-            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
-            assertFalse("isolatedSplits", pkg.isIsolatedSplitLoading());
-        } finally {
-            testFile.delete();
-        }
-    }
-
-    /**
-     * Tests AndroidManifest.xml with an android:isolatedSplits attribute set to a constant.
-     */
-    @Test
-    public void testParseIsolatedSplitsConstant() throws Exception {
-        final File testFile = extractFile(TEST_APP2_APK);
-        try {
-            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
-            assertTrue("isolatedSplits", pkg.isIsolatedSplitLoading());
-        } finally {
-            testFile.delete();
-        }
-    }
-
-    /**
-     * Tests AndroidManifest.xml with an android:isolatedSplits attribute set to a resource.
-     */
-    @Test
-    public void testParseIsolatedSplitsResource() throws Exception {
-        final File testFile = extractFile(TEST_APP3_APK);
-        try {
-            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
-            assertTrue("isolatedSplits", pkg.isIsolatedSplitLoading());
-        } finally {
-            testFile.delete();
-        }
-    }
-
-    @Test
-    public void testParseActivityTargetDisplayCategoryValid() throws Exception {
-        final File testFile = extractFile(TEST_APP4_APK);
-        String actualDisplayCategory = null;
-        try {
-            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
-            final List<ParsedActivity> activities = pkg.getActivities();
-            for (ParsedActivity activity : activities) {
-                if ((PACKAGE_NAME + ".MyActivity").equals(activity.getName())) {
-                    actualDisplayCategory = activity.getTargetDisplayCategory();
-                }
-            }
-        } finally {
-            testFile.delete();
-        }
-        assertEquals("automotive", actualDisplayCategory);
-    }
-
-    @Test
-    public void testParseActivityTargetDisplayCategoryInvalid() throws Exception {
-        final File testFile = extractFile(TEST_APP6_APK);
-        String actualDisplayCategory = null;
-        try {
-            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
-            final List<ParsedActivity> activities = pkg.getActivities();
-            for (ParsedActivity activity : activities) {
-                if ((PACKAGE_NAME + ".MyActivity").equals(activity.getName())) {
-                    actualDisplayCategory = activity.getTargetDisplayCategory();
-                }
-            }
-        } catch (PackageManagerException e) {
-            assertThat(e.getMessage()).contains(
-                    "targetDisplayCategory attribute can only consists"
-                            + " of alphanumeric characters, '_', and '.'");
-        } finally {
-            testFile.delete();
-        }
-        assertNotEquals("$automotive", actualDisplayCategory);
-    }
-
-    private static final int PROPERTY_TYPE_BOOLEAN = 1;
-    private static final int PROPERTY_TYPE_FLOAT = 2;
-    private static final int PROPERTY_TYPE_INTEGER = 3;
-    private static final int PROPERTY_TYPE_RESOURCE = 4;
-    private static final int PROPERTY_TYPE_STRING = 5;
-    public void assertProperty(Map<String, Property> properties, String propertyName,
-            int propertyType, Object propertyValue) {
-        assertTrue(properties.containsKey(propertyName));
-
-        final Property testProperty = properties.get(propertyName);
-        assertEquals(propertyType, testProperty.getType());
-
-        if (propertyType == PROPERTY_TYPE_BOOLEAN) {
-            assertTrue(testProperty.isBoolean());
-            assertFalse(testProperty.isFloat());
-            assertFalse(testProperty.isInteger());
-            assertFalse(testProperty.isResourceId());
-            assertFalse(testProperty.isString());
-
-            // assert the property's type is set correctly
-            final Boolean boolValue = (Boolean) propertyValue;
-            if (boolValue.booleanValue()) {
-                assertTrue(testProperty.getBoolean());
-            } else {
-                assertFalse(testProperty.getBoolean());
-            }
-            // assert the other values have an appropriate default
-            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
-            assertEquals(0, testProperty.getInteger());
-            assertEquals(0, testProperty.getResourceId());
-            assertEquals(null, testProperty.getString());
-        } else if (propertyType == PROPERTY_TYPE_FLOAT) {
-            assertFalse(testProperty.isBoolean());
-            assertTrue(testProperty.isFloat());
-            assertFalse(testProperty.isInteger());
-            assertFalse(testProperty.isResourceId());
-            assertFalse(testProperty.isString());
-
-            // assert the property's type is set correctly
-            final Float floatValue = (Float) propertyValue;
-            assertEquals(floatValue.floatValue(), testProperty.getFloat(), 0.0f);
-            // assert the other values have an appropriate default
-            assertFalse(testProperty.getBoolean());
-            assertEquals(0, testProperty.getInteger());
-            assertEquals(0, testProperty.getResourceId());
-            assertEquals(null, testProperty.getString());
-        } else if (propertyType == PROPERTY_TYPE_INTEGER) {
-            assertFalse(testProperty.isBoolean());
-            assertFalse(testProperty.isFloat());
-            assertTrue(testProperty.isInteger());
-            assertFalse(testProperty.isResourceId());
-            assertFalse(testProperty.isString());
-
-            // assert the property's type is set correctly
-            final Integer integerValue = (Integer) propertyValue;
-            assertEquals(integerValue.intValue(), testProperty.getInteger());
-            // assert the other values have an appropriate default
-            assertFalse(testProperty.getBoolean());
-            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
-            assertEquals(0, testProperty.getResourceId());
-            assertEquals(null, testProperty.getString());
-        } else if (propertyType == PROPERTY_TYPE_RESOURCE) {
-            assertFalse(testProperty.isBoolean());
-            assertFalse(testProperty.isFloat());
-            assertFalse(testProperty.isInteger());
-            assertTrue(testProperty.isResourceId());
-            assertFalse(testProperty.isString());
-
-            // assert the property's type is set correctly
-            final Integer resourceValue = (Integer) propertyValue;
-            assertEquals(resourceValue.intValue(), testProperty.getResourceId());
-            // assert the other values have an appropriate default
-            assertFalse(testProperty.getBoolean());
-            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
-            assertEquals(0, testProperty.getInteger());
-            assertEquals(null, testProperty.getString());
-        } else if (propertyType == PROPERTY_TYPE_STRING) {
-            assertFalse(testProperty.isBoolean());
-            assertFalse(testProperty.isFloat());
-            assertFalse(testProperty.isInteger());
-            assertFalse(testProperty.isResourceId());
-            assertTrue(testProperty.isString());
-
-            // assert the property's type is set correctly
-            final String stringValue = (String) propertyValue;
-            assertEquals(stringValue, testProperty.getString());
-            // assert the other values have an appropriate default
-            assertFalse(testProperty.getBoolean());
-            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
-            assertEquals(0, testProperty.getInteger());
-            assertEquals(0, testProperty.getResourceId());
-        } else {
-            fail("Unknown property type");
-        }
-    }
-
-    @Test
-    public void testParseApplicationProperties() throws Exception {
-        final File testFile = extractFile(TEST_APP4_APK);
-        try {
-            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
-            final Map<String, Property> properties = pkg.getProperties();
-            assertEquals(10, properties.size());
-            assertProperty(properties,
-                    "android.cts.PROPERTY_RESOURCE_XML", PROPERTY_TYPE_RESOURCE, 0x7f060000);
-            assertProperty(properties,
-                    "android.cts.PROPERTY_RESOURCE_INTEGER", PROPERTY_TYPE_RESOURCE, 0x7f040000);
-            assertProperty(properties,
-                    "android.cts.PROPERTY_BOOLEAN", PROPERTY_TYPE_BOOLEAN, TRUE);
-            assertProperty(properties,
-                    "android.cts.PROPERTY_BOOLEAN_VIA_RESOURCE", PROPERTY_TYPE_BOOLEAN, TRUE);
-            assertProperty(properties,
-                    "android.cts.PROPERTY_FLOAT", PROPERTY_TYPE_FLOAT, 3.14f);
-            assertProperty(properties,
-                    "android.cts.PROPERTY_FLOAT_VIA_RESOURCE", PROPERTY_TYPE_FLOAT, 2.718f);
-            assertProperty(properties,
-                    "android.cts.PROPERTY_INTEGER", PROPERTY_TYPE_INTEGER, 42);
-            assertProperty(properties,
-                    "android.cts.PROPERTY_INTEGER_VIA_RESOURCE", PROPERTY_TYPE_INTEGER, 123);
-            assertProperty(properties,
-                    "android.cts.PROPERTY_STRING", PROPERTY_TYPE_STRING, "koala");
-            assertProperty(properties,
-                    "android.cts.PROPERTY_STRING_VIA_RESOURCE", PROPERTY_TYPE_STRING, "giraffe");
-        } finally {
-            testFile.delete();
-        }
-    }
-
-    @Test
-    public void testParseActivityProperties() throws Exception {
-        final File testFile = extractFile(TEST_APP4_APK);
-        try {
-            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
-            final List<ParsedActivity> activities = pkg.getActivities();
-            for (ParsedActivity activity : activities) {
-                final Map<String, Property> properties = activity.getProperties();
-                if ((PACKAGE_NAME + ".MyActivityAlias").equals(activity.getName())) {
-                    assertEquals(2, properties.size());
-                    assertProperty(properties,
-                            "android.cts.PROPERTY_ACTIVITY_ALIAS", PROPERTY_TYPE_INTEGER, 123);
-                    assertProperty(properties,
-                            "android.cts.PROPERTY_COMPONENT", PROPERTY_TYPE_INTEGER, 123);
-                } else if ((PACKAGE_NAME + ".MyActivity").equals(activity.getName())) {
-                    assertEquals(3, properties.size());
-                    assertProperty(properties,
-                            "android.cts.PROPERTY_ACTIVITY", PROPERTY_TYPE_INTEGER, 123);
-                    assertProperty(properties,
-                            "android.cts.PROPERTY_COMPONENT", PROPERTY_TYPE_INTEGER, 123);
-                    assertProperty(properties,
-                            "android.cts.PROPERTY_STRING", PROPERTY_TYPE_STRING, "koala activity");
-                } else if ("android.app.AppDetailsActivity".equals(activity.getName())) {
-                    // ignore default added activity
-                } else {
-                    fail("Found unknown activity; name = " + activity.getName());
-                }
-            }
-        } finally {
-            testFile.delete();
-        }
-    }
-
-    @Test
-    public void testParseProviderProperties() throws Exception {
-        final File testFile = extractFile(TEST_APP4_APK);
-        try {
-            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
-            final List<ParsedProvider> providers = pkg.getProviders();
-            for (ParsedProvider provider : providers) {
-                final Map<String, Property> properties = provider.getProperties();
-                if ((PACKAGE_NAME + ".MyProvider").equals(provider.getName())) {
-                    assertEquals(1, properties.size());
-                    assertProperty(properties,
-                            "android.cts.PROPERTY_PROVIDER", PROPERTY_TYPE_INTEGER, 123);
-                } else {
-                    fail("Found unknown provider; name = " + provider.getName());
-                }
-            }
-        } finally {
-            testFile.delete();
-        }
-    }
-
-    @Test
-    public void testParseReceiverProperties() throws Exception {
-        final File testFile = extractFile(TEST_APP4_APK);
-        try {
-            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
-            final List<ParsedActivity> receivers = pkg.getReceivers();
-            for (ParsedActivity receiver : receivers) {
-                final Map<String, Property> properties = receiver.getProperties();
-                if ((PACKAGE_NAME + ".MyReceiver").equals(receiver.getName())) {
-                    assertEquals(2, properties.size());
-                    assertProperty(properties,
-                            "android.cts.PROPERTY_RECEIVER", PROPERTY_TYPE_INTEGER, 123);
-                    assertProperty(properties,
-                            "android.cts.PROPERTY_STRING", PROPERTY_TYPE_STRING, "koala receiver");
-                } else {
-                    fail("Found unknown receiver; name = " + receiver.getName());
-                }
-            }
-        } finally {
-            testFile.delete();
-        }
-    }
-
-    @Test
-    public void testParseServiceProperties() throws Exception {
-        final File testFile = extractFile(TEST_APP4_APK);
-        try {
-            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
-            final List<ParsedService> services = pkg.getServices();
-            for (ParsedService service : services) {
-                final Map<String, Property> properties = service.getProperties();
-                if ((PACKAGE_NAME + ".MyService").equals(service.getName())) {
-                    assertEquals(2, properties.size());
-                    assertProperty(properties,
-                            "android.cts.PROPERTY_SERVICE", PROPERTY_TYPE_INTEGER, 123);
-                    assertProperty(properties,
-                            "android.cts.PROPERTY_COMPONENT", PROPERTY_TYPE_RESOURCE, 0x7f040000);
-                } else {
-                    fail("Found unknown service; name = " + service.getName());
-                }
-            }
-        } finally {
-            testFile.delete();
-        }
-    }
-
-    @Test
-    public void testParseApexSystemService() throws Exception {
-        final File testFile = extractFile(TEST_APP4_APK);
-        try {
-            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
-            final List<ParsedApexSystemService> systemServices = pkg.getApexSystemServices();
-            for (ParsedApexSystemService systemService: systemServices) {
-                assertEquals(PACKAGE_NAME + ".SystemService", systemService.getName());
-                assertEquals("service-test.jar", systemService.getJarPath());
-                assertEquals("30", systemService.getMinSdkVersion());
-                assertEquals("31", systemService.getMaxSdkVersion());
-            }
-        } finally {
-            testFile.delete();
-        }
-    }
-
-    @Test
-    public void testParseModernPackageHasNoCompatPermissions() throws Exception {
-        final File testFile = extractFile(TEST_APP1_APK);
-        try {
-            final ParsedPackage pkg = new TestPackageParser2()
-                    .parsePackage(testFile, 0 /*flags*/, false /*useCaches*/);
-            final List<String> compatPermissions =
-                    Arrays.stream(COMPAT_PERMS).map(CompatibilityPermissionInfo::getName)
-                            .collect(toList());
-            assertWithMessage(
-                    "Compatibility permissions shouldn't be added into uses permissions.")
-                    .that(pkg.getUsesPermissions().stream().map(ParsedUsesPermission::getName)
-                            .collect(toList()))
-                    .containsNoneIn(compatPermissions);
-            assertWithMessage(
-                    "Compatibility permissions shouldn't be added into requested permissions.")
-                    .that(pkg.getRequestedPermissions()).containsNoneIn(compatPermissions);
-            assertWithMessage(
-                    "Compatibility permissions shouldn't be added into implicit permissions.")
-                    .that(pkg.getImplicitPermissions()).containsNoneIn(compatPermissions);
-        } finally {
-            testFile.delete();
-        }
-    }
-
-    @Test
-    public void testParseLegacyPackageHasCompatPermissions() throws Exception {
-        final File testFile = extractFile(TEST_APP5_APK);
-        try {
-            final ParsedPackage pkg = new TestPackageParser2()
-                    .parsePackage(testFile, 0 /*flags*/, false /*useCaches*/);
-            assertWithMessage(
-                    "Compatibility permissions should be added into uses permissions.")
-                    .that(Arrays.stream(COMPAT_PERMS).map(CompatibilityPermissionInfo::getName)
-                            .allMatch(pkg.getUsesPermissions().stream()
-                                    .map(ParsedUsesPermission::getName)
-                            .collect(toList())::contains))
-                    .isTrue();
-            assertWithMessage(
-                    "Compatibility permissions should be added into requested permissions.")
-                    .that(Arrays.stream(COMPAT_PERMS).map(CompatibilityPermissionInfo::getName)
-                            .allMatch(pkg.getRequestedPermissions()::contains))
-                    .isTrue();
-            assertWithMessage(
-                    "Compatibility permissions should be added into implicit permissions.")
-                    .that(Arrays.stream(COMPAT_PERMS).map(CompatibilityPermissionInfo::getName)
-                            .allMatch(pkg.getImplicitPermissions()::contains))
-                    .isTrue();
-        } finally {
-            testFile.delete();
-        }
-    }
-
-    @Test
-    public void testNoComponentMetadataIsCoercedToNullForInfoObject() throws Exception {
-        final File testFile = extractFile(TEST_APP4_APK);
-        try {
-            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
-            ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
-                    PackageUserStateInternal.DEFAULT, 0, null);
-            for (ParsedActivity activity : pkg.getActivities()) {
-                assertNotNull(activity.getMetaData());
-                assertNull(PackageInfoUtils.generateActivityInfo(pkg, activity, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
-            }
-            for (ParsedProvider provider : pkg.getProviders()) {
-                assertNotNull(provider.getMetaData());
-                assertNull(PackageInfoUtils.generateProviderInfo(pkg, provider, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
-            }
-            for (ParsedActivity receiver : pkg.getReceivers()) {
-                assertNotNull(receiver.getMetaData());
-                assertNull(PackageInfoUtils.generateActivityInfo(pkg, receiver, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
-            }
-            for (ParsedService service : pkg.getServices()) {
-                assertNotNull(service.getMetaData());
-                assertNull(PackageInfoUtils.generateServiceInfo(pkg, service, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
-            }
-        } finally {
-            testFile.delete();
-        }
-    }
-
-    /**
-     * A subclass of package parser that adds a "cache_" prefix to the package name for the cached
-     * results. This is used by tests to tell if a ParsedPackage is generated from the cache or not.
-     */
-    public static class CachePackageNameParser extends PackageParser2 {
-
-        CachePackageNameParser(@Nullable File cacheDir) {
-            super(null, null, null, new Callback() {
-                @Override
-                public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
-                    return true;
-                }
-
-                @Override
-                public boolean hasFeature(String feature) {
-                    return false;
-                }
-            });
-            if (cacheDir != null) {
-                setCacheDir(cacheDir);
-            }
-        }
-
-        void setCacheDir(@NonNull File cacheDir) {
-            this.mCacher = new PackageCacher(cacheDir) {
-                @Override
-                public ParsedPackage fromCacheEntry(byte[] cacheEntry) {
-                    ParsedPackage parsed = super.fromCacheEntry(cacheEntry);
-                    parsed.setPackageName("cache_" + parsed.getPackageName());
-                    return parsed;
-                }
-            };
-        }
-    }
-
-    private static PackageSetting mockPkgSetting(AndroidPackage pkg) {
-        return new PackageSettingBuilder()
-                .setName(pkg.getPackageName())
-                .setRealName(pkg.getManifestPackageName())
-                .setCodePath(pkg.getPath())
-                .setPrimaryCpuAbiString(AndroidPackageUtils.getRawPrimaryCpuAbi(pkg))
-                .setSecondaryCpuAbiString(AndroidPackageUtils.getRawSecondaryCpuAbi(pkg))
-                .setPVersionCode(pkg.getLongVersionCode())
-                .setPkgFlags(PackageInfoUtils.appInfoFlags(pkg, null))
-                .setPrivateFlags(PackageInfoUtils.appInfoPrivateFlags(pkg, null))
-                .setSharedUserId(pkg.getSharedUserLabel())
-                .build();
-    }
-
-    // NOTE: The equality assertions below are based on code autogenerated by IntelliJ.
-
-    public static void assertPackagesEqual(AndroidPackage a, AndroidPackage b) {
-        assertEquals(a.getBaseRevisionCode(), b.getBaseRevisionCode());
-        assertEquals(a.isBaseHardwareAccelerated(), b.isBaseHardwareAccelerated());
-        assertEquals(a.getLongVersionCode(), b.getLongVersionCode());
-        assertEquals(a.getSharedUserLabel(), b.getSharedUserLabel());
-        assertEquals(a.getInstallLocation(), b.getInstallLocation());
-        assertEquals(a.isCoreApp(), b.isCoreApp());
-        assertEquals(a.isRequiredForAllUsers(), b.isRequiredForAllUsers());
-        assertEquals(a.getCompileSdkVersion(), b.getCompileSdkVersion());
-        assertEquals(a.getCompileSdkVersionCodeName(), b.getCompileSdkVersionCodeName());
-        assertEquals(a.isUse32BitAbi(), b.isUse32BitAbi());
-        assertEquals(a.getPackageName(), b.getPackageName());
-        assertArrayEquals(a.getSplitNames(), b.getSplitNames());
-        assertEquals(a.getVolumeUuid(), b.getVolumeUuid());
-        assertEquals(a.getPath(), b.getPath());
-        assertEquals(a.getBaseApkPath(), b.getBaseApkPath());
-        assertArrayEquals(a.getSplitCodePaths(), b.getSplitCodePaths());
-        assertArrayEquals(a.getSplitRevisionCodes(), b.getSplitRevisionCodes());
-        assertArrayEquals(a.getSplitFlags(), b.getSplitFlags());
-
-        PackageInfo aInfo = PackageInfoUtils.generate(a, new int[]{}, 0, 0, 0,
-                Collections.emptySet(), PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(a));
-        PackageInfo bInfo = PackageInfoUtils.generate(b, new int[]{}, 0, 0, 0,
-                Collections.emptySet(), PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(b));
-        assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
-
-        assertEquals(ArrayUtils.size(a.getPermissions()), ArrayUtils.size(b.getPermissions()));
-        for (int i = 0; i < ArrayUtils.size(a.getPermissions()); ++i) {
-            assertPermissionsEqual(a.getPermissions().get(i), b.getPermissions().get(i));
-        }
-
-        assertEquals(ArrayUtils.size(a.getPermissionGroups()),
-                ArrayUtils.size(b.getPermissionGroups()));
-        for (int i = 0; i < a.getPermissionGroups().size(); ++i) {
-            assertPermissionGroupsEqual(a.getPermissionGroups().get(i),
-                    b.getPermissionGroups().get(i));
-        }
-
-        assertEquals(ArrayUtils.size(a.getActivities()), ArrayUtils.size(b.getActivities()));
-        for (int i = 0; i < ArrayUtils.size(a.getActivities()); ++i) {
-            assertActivitiesEqual(a, a.getActivities().get(i), b, b.getActivities().get(i));
-        }
-
-        assertEquals(ArrayUtils.size(a.getReceivers()), ArrayUtils.size(b.getReceivers()));
-        for (int i = 0; i < ArrayUtils.size(a.getReceivers()); ++i) {
-            assertActivitiesEqual(a, a.getReceivers().get(i), b, b.getReceivers().get(i));
-        }
-
-        assertEquals(ArrayUtils.size(a.getProviders()), ArrayUtils.size(b.getProviders()));
-        for (int i = 0; i < ArrayUtils.size(a.getProviders()); ++i) {
-            assertProvidersEqual(a, a.getProviders().get(i), b, b.getProviders().get(i));
-        }
-
-        assertEquals(ArrayUtils.size(a.getServices()), ArrayUtils.size(b.getServices()));
-        for (int i = 0; i < ArrayUtils.size(a.getServices()); ++i) {
-            assertServicesEqual(a, a.getServices().get(i), b, b.getServices().get(i));
-        }
-
-        assertEquals(ArrayUtils.size(a.getInstrumentations()),
-                ArrayUtils.size(b.getInstrumentations()));
-        for (int i = 0; i < ArrayUtils.size(a.getInstrumentations()); ++i) {
-            assertInstrumentationEqual(a.getInstrumentations().get(i),
-                    b.getInstrumentations().get(i));
-        }
-
-        assertEquals(a.getProperties().size(), b.getProperties().size());
-        final Iterator<String> iter = a.getProperties().keySet().iterator();
-        while (iter.hasNext()) {
-            final String key = iter.next();
-            assertEquals(a.getProperties().get(key), b.getProperties().get(key));
-        }
-
-        assertEquals(a.getRequestedPermissions(), b.getRequestedPermissions());
-        assertEquals(a.getProtectedBroadcasts(), b.getProtectedBroadcasts());
-        assertEquals(a.getLibraryNames(), b.getLibraryNames());
-        assertEquals(a.getUsesLibraries(), b.getUsesLibraries());
-        assertEquals(a.getUsesOptionalLibraries(), b.getUsesOptionalLibraries());
-        assertEquals(a.getOriginalPackages(), b.getOriginalPackages());
-        assertEquals(a.getManifestPackageName(), b.getManifestPackageName());
-        assertEquals(a.getAdoptPermissions(), b.getAdoptPermissions());
-        assertBundleApproximateEquals(a.getMetaData(), b.getMetaData());
-        assertEquals(a.getVersionName(), b.getVersionName());
-        assertEquals(a.getSharedUserId(), b.getSharedUserId());
-        assertArrayEquals(a.getSigningDetails().getSignatures(),
-                b.getSigningDetails().getSignatures());
-        assertEquals(a.getRestrictedAccountType(), b.getRestrictedAccountType());
-        assertEquals(a.getRequiredAccountType(), b.getRequiredAccountType());
-        assertEquals(a.getOverlayTarget(), b.getOverlayTarget());
-        assertEquals(a.getOverlayTargetOverlayableName(), b.getOverlayTargetOverlayableName());
-        assertEquals(a.getOverlayCategory(), b.getOverlayCategory());
-        assertEquals(a.getOverlayPriority(), b.getOverlayPriority());
-        assertEquals(a.isOverlayIsStatic(), b.isOverlayIsStatic());
-        assertEquals(a.getSigningDetails().getPublicKeys(), b.getSigningDetails().getPublicKeys());
-        assertEquals(a.getUpgradeKeySets(), b.getUpgradeKeySets());
-        assertEquals(a.getKeySetMapping(), b.getKeySetMapping());
-        assertArrayEquals(a.getRestrictUpdateHash(), b.getRestrictUpdateHash());
-    }
-
-    private static void assertBundleApproximateEquals(Bundle a, Bundle b) {
-        if (a == b) {
-            return;
-        }
-
-        // Force the bundles to be unparceled.
-        a.getBoolean("foo");
-        b.getBoolean("foo");
-
-        assertEquals(a.toString(), b.toString());
-    }
-
-    private static void assertComponentsEqual(ParsedComponent a, ParsedComponent b) {
-        assertEquals(a.getName(), b.getName());
-        assertBundleApproximateEquals(a.getMetaData(), b.getMetaData());
-        assertEquals(a.getComponentName(), b.getComponentName());
-
-        if (a.getIntents() != null && b.getIntents() != null) {
-            assertEquals(a.getIntents().size(), b.getIntents().size());
-        } else if (a.getIntents() == null || b.getIntents() == null) {
-            return;
-        }
-
-        for (int i = 0; i < a.getIntents().size(); ++i) {
-            ParsedIntentInfo aIntent = a.getIntents().get(i);
-            ParsedIntentInfo bIntent = b.getIntents().get(i);
-
-            assertEquals(aIntent.isHasDefault(), bIntent.isHasDefault());
-            assertEquals(aIntent.getLabelRes(), bIntent.getLabelRes());
-            assertEquals(aIntent.getNonLocalizedLabel(), bIntent.getNonLocalizedLabel());
-            assertEquals(aIntent.getIcon(), bIntent.getIcon());
-        }
-
-        assertEquals(a.getProperties().size(), b.getProperties().size());
-        final Iterator<String> iter = a.getProperties().keySet().iterator();
-        while (iter.hasNext()) {
-            final String key = iter.next();
-            assertEquals(a.getProperties().get(key), b.getProperties().get(key));
-        }
-    }
-
-    private static void assertPermissionsEqual(ParsedPermission a, ParsedPermission b) {
-        assertComponentsEqual(a, b);
-        assertEquals(a.isTree(), b.isTree());
-
-        // Verify basic flags in PermissionInfo to make sure they're consistent. We don't perform
-        // a full structural equality here because the code that serializes them isn't parser
-        // specific and is tested elsewhere.
-        assertEquals(ParsedPermissionUtils.getProtection(a),
-                ParsedPermissionUtils.getProtection(b));
-        assertEquals(a.getGroup(), b.getGroup());
-        assertEquals(a.getFlags(), b.getFlags());
-
-        if (a.getParsedPermissionGroup() != null && b.getParsedPermissionGroup() != null) {
-            assertPermissionGroupsEqual(a.getParsedPermissionGroup(), b.getParsedPermissionGroup());
-        } else if (a.getParsedPermissionGroup() != null || b.getParsedPermissionGroup() != null) {
-            throw new AssertionError();
-        }
-    }
-
-    private static void assertInstrumentationEqual(ParsedInstrumentation a,
-            ParsedInstrumentation b) {
-        assertComponentsEqual(a, b);
-
-        // Validity check for InstrumentationInfo.
-        assertEquals(a.getTargetPackage(), b.getTargetPackage());
-        assertEquals(a.getTargetProcesses(), b.getTargetProcesses());
-        assertEquals(a.isHandleProfiling(), b.isHandleProfiling());
-        assertEquals(a.isFunctionalTest(), b.isFunctionalTest());
-    }
-
-    private static void assertServicesEqual(
-            AndroidPackage aPkg,
-            ParsedService a,
-            AndroidPackage bPkg,
-            ParsedService b
-    ) {
-        assertComponentsEqual(a, b);
-
-        // Validity check for ServiceInfo.
-        ServiceInfo aInfo = PackageInfoUtils.generateServiceInfo(aPkg, a, 0,
-                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(aPkg));
-        ServiceInfo bInfo = PackageInfoUtils.generateServiceInfo(bPkg, b, 0,
-                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(bPkg));
-        assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
-        assertEquals(a.getName(), b.getName());
-    }
-
-    private static void assertProvidersEqual(
-            AndroidPackage aPkg,
-            ParsedProvider a,
-            AndroidPackage bPkg,
-            ParsedProvider b
-    ) {
-        assertComponentsEqual(a, b);
-        assertEquals(a.getName(), b.getName());
-    }
-
-    private static void assertActivitiesEqual(
-            AndroidPackage aPkg,
-            ParsedActivity a,
-            AndroidPackage bPkg,
-            ParsedActivity b
-    ) {
-        assertComponentsEqual(a, b);
-
-        // Validity check for ActivityInfo.
-        ActivityInfo aInfo = PackageInfoUtils.generateActivityInfo(aPkg, a, 0,
-                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(aPkg));
-        ActivityInfo bInfo = PackageInfoUtils.generateActivityInfo(bPkg, b, 0,
-                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(bPkg));
-        assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
-        assertEquals(a.getName(), b.getName());
-    }
-
-    private static void assertPermissionGroupsEqual(ParsedPermissionGroup a,
-            ParsedPermissionGroup b) {
-        assertComponentsEqual(a, b);
-
-        // Validity check for PermissionGroupInfo.
-        assertEquals(a.getName(), b.getName());
-        assertEquals(a.getDescriptionRes(), b.getDescriptionRes());
-    }
-
-    private static void assertApplicationInfoEqual(ApplicationInfo a, ApplicationInfo that) {
-        assertEquals(a.descriptionRes, that.descriptionRes);
-        assertEquals(a.theme, that.theme);
-        assertEquals(a.fullBackupContent, that.fullBackupContent);
-        assertEquals(a.uiOptions, that.uiOptions);
-        assertEquals(Integer.toBinaryString(a.flags), Integer.toBinaryString(that.flags));
-        assertEquals(a.privateFlags, that.privateFlags);
-        assertEquals(a.requiresSmallestWidthDp, that.requiresSmallestWidthDp);
-        assertEquals(a.compatibleWidthLimitDp, that.compatibleWidthLimitDp);
-        assertEquals(a.largestWidthLimitDp, that.largestWidthLimitDp);
-        assertEquals(a.nativeLibraryRootRequiresIsa, that.nativeLibraryRootRequiresIsa);
-        assertEquals(a.uid, that.uid);
-        assertEquals(a.minSdkVersion, that.minSdkVersion);
-        assertEquals(a.targetSdkVersion, that.targetSdkVersion);
-        assertEquals(a.versionCode, that.versionCode);
-        assertEquals(a.enabled, that.enabled);
-        assertEquals(a.enabledSetting, that.enabledSetting);
-        assertEquals(a.installLocation, that.installLocation);
-        assertEquals(a.networkSecurityConfigRes, that.networkSecurityConfigRes);
-        assertEquals(a.taskAffinity, that.taskAffinity);
-        assertEquals(a.permission, that.permission);
-        assertEquals(a.getKnownActivityEmbeddingCerts(), that.getKnownActivityEmbeddingCerts());
-        assertEquals(a.processName, that.processName);
-        assertEquals(a.className, that.className);
-        assertEquals(a.manageSpaceActivityName, that.manageSpaceActivityName);
-        assertEquals(a.backupAgentName, that.backupAgentName);
-        assertEquals(a.volumeUuid, that.volumeUuid);
-        assertEquals(a.scanSourceDir, that.scanSourceDir);
-        assertEquals(a.scanPublicSourceDir, that.scanPublicSourceDir);
-        assertEquals(a.sourceDir, that.sourceDir);
-        assertEquals(a.publicSourceDir, that.publicSourceDir);
-        assertArrayEquals(a.splitSourceDirs, that.splitSourceDirs);
-        assertArrayEquals(a.splitPublicSourceDirs, that.splitPublicSourceDirs);
-        assertArrayEquals(a.resourceDirs, that.resourceDirs);
-        assertArrayEquals(a.overlayPaths, that.overlayPaths);
-        assertEquals(a.seInfo, that.seInfo);
-        assertArrayEquals(a.sharedLibraryFiles, that.sharedLibraryFiles);
-        assertEquals(a.dataDir, that.dataDir);
-        assertEquals(a.deviceProtectedDataDir, that.deviceProtectedDataDir);
-        assertEquals(a.credentialProtectedDataDir, that.credentialProtectedDataDir);
-        assertEquals(a.nativeLibraryDir, that.nativeLibraryDir);
-        assertEquals(a.secondaryNativeLibraryDir, that.secondaryNativeLibraryDir);
-        assertEquals(a.nativeLibraryRootDir, that.nativeLibraryRootDir);
-        assertEquals(a.primaryCpuAbi, that.primaryCpuAbi);
-        assertEquals(a.secondaryCpuAbi, that.secondaryCpuAbi);
-    }
-
-    public static void setKnownFields(ParsingPackage pkg) {
-        Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-
-        ParsedPermissionImpl permission = new ParsedPermissionImpl();
-        permission.setParsedPermissionGroup(new ParsedPermissionGroupImpl());
-
-        ((ParsedPackage) pkg.setBaseRevisionCode(100)
-                .setBaseHardwareAccelerated(true)
-                .setSharedUserLabel(100)
-                .setInstallLocation(100)
-                .setRequiredForAllUsers(true)
-                .asSplit(
-                        new String[]{"foo2"},
-                        new String[]{"foo6"},
-                        new int[]{100},
-                        null
-                )
-                .setUse32BitAbi(true)
-                .setVolumeUuid("d52ef59a-7def-4541-bf21-4c28ed4b65a0")
-                .addPermission(permission)
-                .addPermissionGroup(new ParsedPermissionGroupImpl())
-                .addActivity(new ParsedActivityImpl())
-                .addReceiver(new ParsedActivityImpl())
-                .addProvider(new ParsedProviderImpl())
-                .addService(new ParsedServiceImpl())
-                .addInstrumentation(new ParsedInstrumentationImpl())
-                .addUsesPermission(new ParsedUsesPermissionImpl("foo7", 0))
-                .addImplicitPermission("foo25")
-                .addProtectedBroadcast("foo8")
-                .setSdkLibraryName("sdk12")
-                .setSdkLibVersionMajor(42)
-                .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"})
-                .setStaticSharedLibraryName("foo23")
-                .setStaticSharedLibVersion(100)
-                .addUsesStaticLibrary("foo23", 100, new String[]{"digest"})
-                .addLibraryName("foo10")
-                .addUsesLibrary("foo11")
-                .addUsesOptionalLibrary("foo12")
-                .addOriginalPackage("foo14")
-                .addAdoptPermission("foo16")
-                .setMetaData(bundle)
-                .setVersionName("foo17")
-                .setSharedUserId("foo18")
-                .setSigningDetails(
-                        new SigningDetails(
-                                new Signature[]{new Signature(new byte[16])},
-                                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
-                                new ArraySet<>(),
-                                null)
-                )
-                .setRestrictedAccountType("foo19")
-                .setRequiredAccountType("foo20")
-                .setOverlayTarget("foo21")
-                .setOverlayPriority(100)
-                .setUpgradeKeySets(new ArraySet<>())
-                .addPreferredActivityFilter("className", new ParsedIntentInfoImpl())
-                .addConfigPreference(new ConfigurationInfo())
-                .addReqFeature(new FeatureInfo())
-                .addFeatureGroup(new FeatureGroupInfo())
-                .setCompileSdkVersionCodeName("foo23")
-                .setCompileSdkVersion(100)
-                .setOverlayCategory("foo24")
-                .setOverlayIsStatic(true)
-                .setOverlayTargetOverlayableName("foo26")
-                .setVisibleToInstantApps(true)
-                .setSplitHasCode(0, true)
-                .hideAsParsed())
-                .setBaseApkPath("foo5")
-                .setPath("foo4")
-                .setVersionCode(100)
-                .setRestrictUpdateHash(new byte[16])
-                .setVersionCodeMajor(100)
-                .setCoreApp(true)
-                .hideAsFinal();
-    }
-
-    private static void assertAllFieldsExist(ParsedPackage pkg) throws Exception {
-        Field[] fields = ParsedPackage.class.getDeclaredFields();
-
-        Set<String> nonSerializedFields = new HashSet<>();
-        nonSerializedFields.add("mExtras");
-        nonSerializedFields.add("packageUsageTimeMillis");
-        nonSerializedFields.add("isStub");
-
-        for (Field f : fields) {
-            final Class<?> fieldType = f.getType();
-
-            if (nonSerializedFields.contains(f.getName())) {
-                continue;
-            }
-
-            if (List.class.isAssignableFrom(fieldType)) {
-                // Validity check for list fields: Assume they're non-null and contain precisely
-                // one element.
-                List<?> list = (List<?>) f.get(pkg);
-                assertNotNull("List was null: " + f, list);
-                assertEquals(1, list.size());
-            } else if (fieldType.getComponentType() != null) {
-                // Validity check for array fields: Assume they're non-null and contain precisely
-                // one element.
-                Object array = f.get(pkg);
-                assertNotNull(Array.get(array, 0));
-            } else if (fieldType == String.class) {
-                // String fields: Check that they're set to "foo".
-                String value = (String) f.get(pkg);
-
-                assertTrue("Bad value for field: " + f, value != null && value.startsWith("foo"));
-            } else if (fieldType == int.class) {
-                // int fields: Check that they're set to 100.
-                int value = (int) f.get(pkg);
-                assertEquals("Bad value for field: " + f, 100, value);
-            } else if (fieldType == boolean.class) {
-                // boolean fields: Check that they're set to true.
-                boolean value = (boolean) f.get(pkg);
-                assertTrue("Bad value for field: " + f, value);
-            } else {
-                // All other fields: Check that they're set.
-                Object o = f.get(pkg);
-                assertNotNull("Field was null: " + f, o);
-            }
-        }
-    }
-}
-
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
deleted file mode 100644
index 48d6d90..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ /dev/null
@@ -1,635 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import static android.content.pm.SharedLibraryInfo.TYPE_DYNAMIC;
-import static android.content.pm.SharedLibraryInfo.TYPE_SDK_PACKAGE;
-import static android.content.pm.SharedLibraryInfo.TYPE_STATIC;
-import static android.content.pm.SharedLibraryInfo.VERSION_UNDEFINED;
-
-import static com.android.server.pm.PackageManagerService.SCAN_AS_FULL_APP;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
-import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
-import static com.android.server.pm.PackageManagerService.SCAN_NEW_INSTALL;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.empty;
-import static org.hamcrest.Matchers.hasItems;
-import static org.hamcrest.Matchers.nullValue;
-import static org.hamcrest.collection.IsArrayContainingInOrder.arrayContaining;
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertNotSame;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.Manifest;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.SharedLibraryInfo;
-import android.content.res.TypedArray;
-import android.os.Environment;
-import android.os.UserHandle;
-import android.platform.test.annotations.Presubmit;
-import android.util.Pair;
-
-import com.android.server.compat.PlatformCompat;
-import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
-
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.io.File;
-import java.util.UUID;
-
-@RunWith(MockitoJUnitRunner.class)
-@Presubmit
-// TODO: shared user tests
-public class ScanTests {
-
-    private static final String DUMMY_PACKAGE_NAME = "some.app.to.test";
-
-    private static final UUID UUID_ONE = UUID.randomUUID();
-    private static final UUID UUID_TWO = UUID.randomUUID();
-
-    @Mock
-    PackageAbiHelper mMockPackageAbiHelper;
-    @Mock
-    UserManagerInternal mMockUserManager;
-    @Mock
-    PlatformCompat mMockCompatibility;
-    @Mock
-    PackageManagerServiceInjector mMockInjector;
-    @Mock
-    PackageManagerService mMockPackageManager;
-    @Mock
-    Installer mMockInstaller;
-
-    @Before
-    public void setupInjector() {
-        when(mMockInjector.getAbiHelper()).thenReturn(mMockPackageAbiHelper);
-        when(mMockInjector.getUserManagerInternal()).thenReturn(mMockUserManager);
-        when(mMockInjector.getCompatibility()).thenReturn(mMockCompatibility);
-
-        DomainVerificationManagerInternal domainVerificationManager =
-                mock(DomainVerificationManagerInternal.class);
-        when(domainVerificationManager.generateNewId())
-                .thenAnswer(invocation -> UUID.randomUUID());
-
-        when(mMockInjector.getDomainVerificationManagerInternal())
-                .thenReturn(domainVerificationManager);
-        when(mMockInjector.getInstaller()).thenReturn(mMockInstaller);
-    }
-
-    @Before
-    public void setupDefaultUser() {
-        when(mMockUserManager.getUserIds()).thenReturn(new int[]{0});
-    }
-
-    @Before
-    public void setupDefaultAbiBehavior() throws Exception {
-        when(mMockPackageAbiHelper.derivePackageAbi(
-                any(AndroidPackage.class), anyBoolean(), nullable(String.class),
-                any(File.class)))
-                .thenReturn(new Pair<>(
-                        new PackageAbiHelper.Abis("derivedPrimary", "derivedSecondary"),
-                        new PackageAbiHelper.NativeLibraryPaths(
-                                "derivedRootDir", true, "derivedNativeDir", "derivedNativeDir2")));
-        when(mMockPackageAbiHelper.deriveNativeLibraryPaths(
-                any(AndroidPackage.class), anyBoolean(), any(File.class)))
-                .thenReturn(new PackageAbiHelper.NativeLibraryPaths(
-                        "getRootDir", true, "getNativeDir", "getNativeDir2"
-                ));
-        when(mMockPackageAbiHelper.getBundledAppAbis(
-                any(AndroidPackage.class)))
-                .thenReturn(new PackageAbiHelper.Abis("bundledPrimary", "bundledSecondary"));
-    }
-
-    @Test
-    public void newInstallSimpleAllNominal() throws Exception {
-        final ScanRequest scanRequest =
-                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
-                        .addScanFlag(PackageManagerService.SCAN_NEW_INSTALL)
-                        .addScanFlag(PackageManagerService.SCAN_AS_FULL_APP)
-                        .build();
-
-        final ScanResult scanResult = executeScan(scanRequest);
-
-        assertBasicPackageScanResult(scanResult, DUMMY_PACKAGE_NAME, false /*isInstant*/);
-        assertThat(scanResult.mExistingSettingCopied, is(false));
-        assertPathsNotDerived(scanResult);
-    }
-
-    @Test
-    public void newInstallForAllUsers() throws Exception {
-        final int[] userIds = {0, 10, 11};
-        when(mMockUserManager.getUserIds()).thenReturn(userIds);
-
-        final ScanRequest scanRequest =
-                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
-                        .setRealPkgName(null)
-                        .addScanFlag(PackageManagerService.SCAN_NEW_INSTALL)
-                        .addScanFlag(PackageManagerService.SCAN_AS_FULL_APP)
-                        .build();
-        final ScanResult scanResult = executeScan(scanRequest);
-
-        for (int uid : userIds) {
-            assertThat(scanResult.mPkgSetting.readUserState(uid).isInstalled(), is(true));
-        }
-    }
-
-    @Test
-    public void installRealPackageName() throws Exception {
-        final ScanRequest scanRequest =
-                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
-                        .setRealPkgName("com.package.real")
-                        .build();
-
-        final ScanResult scanResult = executeScan(scanRequest);
-
-        assertThat(scanResult.mPkgSetting.getRealName(), is("com.package.real"));
-
-        final ScanRequest scanRequestNoRealPkg =
-                createBasicScanRequestBuilder(
-                        createBasicPackage(DUMMY_PACKAGE_NAME)
-                                .addOriginalPackage("com.package.real"))
-                        .build();
-
-        final ScanResult scanResultNoReal = executeScan(scanRequestNoRealPkg);
-        assertThat(scanResultNoReal.mPkgSetting.getRealName(), nullValue());
-    }
-
-    @Test
-    public void updateSimpleNominal() throws Exception {
-        when(mMockUserManager.getUserIds()).thenReturn(new int[]{0});
-
-        final PackageSetting pkgSetting = createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
-                .setPrimaryCpuAbiString("primaryCpuAbi")
-                .setSecondaryCpuAbiString("secondaryCpuAbi")
-                .build();
-        final ScanRequest scanRequest =
-                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
-                        .addScanFlag(PackageManagerService.SCAN_AS_FULL_APP)
-                        .setPkgSetting(pkgSetting)
-                        .build();
-
-
-        final ScanResult scanResult = executeScan(scanRequest);
-
-        assertThat(scanResult.mExistingSettingCopied, is(true));
-
-        // ensure we don't overwrite the existing pkgSetting, in case something post-scan fails
-        assertNotSame(pkgSetting, scanResult.mPkgSetting);
-
-        assertBasicPackageScanResult(scanResult, DUMMY_PACKAGE_NAME, false /*isInstant*/);
-
-        assertThat(scanResult.mPkgSetting.getPrimaryCpuAbiLegacy(), is("primaryCpuAbi"));
-        assertThat(scanResult.mPkgSetting.getSecondaryCpuAbiLegacy(), is("secondaryCpuAbi"));
-        assertThat(scanResult.mPkgSetting.getCpuAbiOverride(), nullValue());
-
-        assertPathsNotDerived(scanResult);
-    }
-
-    @Test
-    public void updateInstantSimpleNominal() throws Exception {
-        when(mMockUserManager.getUserIds()).thenReturn(new int[]{0});
-
-        final PackageSetting existingPkgSetting =
-                createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
-                        .setInstantAppUserState(0, true)
-                        .build();
-
-        final ScanRequest scanRequest =
-                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
-                        .setPkgSetting(existingPkgSetting)
-                        .build();
-
-
-        final ScanResult scanResult = executeScan(scanRequest);
-
-        assertBasicPackageScanResult(scanResult, DUMMY_PACKAGE_NAME, true /*isInstant*/);
-    }
-
-    @Test
-    public void installSdkLibrary() throws Exception {
-        final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("ogl.sdk_123")
-                .setSdkLibraryName("ogl.sdk")
-                .setSdkLibVersionMajor(123)
-                .hideAsParsed())
-                .setPackageName("ogl.sdk_123")
-                .setVersionCodeMajor(5)
-                .setVersionCode(678)
-                .setBaseApkPath("/some/path.apk")
-                .setSplitCodePaths(new String[] {"/some/other/path.apk"});
-
-        final ScanRequest scanRequest = new ScanRequestBuilder(pkg)
-                .setUser(UserHandle.of(0)).build();
-
-        final ScanResult scanResult = executeScan(scanRequest);
-
-        assertThat(scanResult.mSdkSharedLibraryInfo.getPackageName(), is("ogl.sdk_123"));
-        assertThat(scanResult.mSdkSharedLibraryInfo.getName(), is("ogl.sdk"));
-        assertThat(scanResult.mSdkSharedLibraryInfo.getLongVersion(), is(123L));
-        assertThat(scanResult.mSdkSharedLibraryInfo.getType(), is(TYPE_SDK_PACKAGE));
-        assertThat(scanResult.mSdkSharedLibraryInfo.getDeclaringPackage().getPackageName(),
-                is("ogl.sdk_123"));
-        assertThat(scanResult.mSdkSharedLibraryInfo.getDeclaringPackage().getLongVersionCode(),
-                is(pkg.getLongVersionCode()));
-        assertThat(scanResult.mSdkSharedLibraryInfo.getAllCodePaths(),
-                hasItems("/some/path.apk", "/some/other/path.apk"));
-        assertThat(scanResult.mSdkSharedLibraryInfo.getDependencies(), nullValue());
-        assertThat(scanResult.mSdkSharedLibraryInfo.getDependentPackages(), empty());
-    }
-
-    @Test
-    public void installStaticSharedLibrary() throws Exception {
-        final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("static.lib.pkg")
-                .setStaticSharedLibraryName("static.lib")
-                .setStaticSharedLibVersion(123L)
-                .hideAsParsed())
-                .setPackageName("static.lib.pkg.123")
-                .setVersionCodeMajor(1)
-                .setVersionCode(234)
-                .setBaseApkPath("/some/path.apk")
-                .setSplitCodePaths(new String[] {"/some/other/path.apk"});
-
-        final ScanRequest scanRequest = new ScanRequestBuilder(pkg)
-                .setUser(UserHandle.of(0)).build();
-
-
-        final ScanResult scanResult = executeScan(scanRequest);
-
-        assertThat(scanResult.mStaticSharedLibraryInfo.getPackageName(), is("static.lib.pkg.123"));
-        assertThat(scanResult.mStaticSharedLibraryInfo.getName(), is("static.lib"));
-        assertThat(scanResult.mStaticSharedLibraryInfo.getLongVersion(), is(123L));
-        assertThat(scanResult.mStaticSharedLibraryInfo.getType(), is(TYPE_STATIC));
-        assertThat(scanResult.mStaticSharedLibraryInfo.getDeclaringPackage().getPackageName(),
-                is("static.lib.pkg"));
-        assertThat(scanResult.mStaticSharedLibraryInfo.getDeclaringPackage().getLongVersionCode(),
-                is(pkg.getLongVersionCode()));
-        assertThat(scanResult.mStaticSharedLibraryInfo.getAllCodePaths(),
-                hasItems("/some/path.apk", "/some/other/path.apk"));
-        assertThat(scanResult.mStaticSharedLibraryInfo.getDependencies(), nullValue());
-        assertThat(scanResult.mStaticSharedLibraryInfo.getDependentPackages(), empty());
-    }
-
-    @Test
-    public void installDynamicLibraries() throws Exception {
-        final ParsedPackage pkg = ((ParsedPackage) createBasicPackage(
-                "dynamic.lib.pkg")
-                .addLibraryName("liba")
-                .addLibraryName("libb")
-                .hideAsParsed())
-                .setVersionCodeMajor(1)
-                .setVersionCode(234)
-                .setBaseApkPath("/some/path.apk")
-                .setSplitCodePaths(new String[] {"/some/other/path.apk"});
-
-        final ScanRequest scanRequest =
-                new ScanRequestBuilder(pkg).setUser(UserHandle.of(0)).build();
-
-
-        final ScanResult scanResult = executeScan(scanRequest);
-
-        final SharedLibraryInfo dynamicLib0 = scanResult.mDynamicSharedLibraryInfos.get(0);
-        assertThat(dynamicLib0.getPackageName(), is("dynamic.lib.pkg"));
-        assertThat(dynamicLib0.getName(), is("liba"));
-        assertThat(dynamicLib0.getLongVersion(), is((long) VERSION_UNDEFINED));
-        assertThat(dynamicLib0.getType(), is(TYPE_DYNAMIC));
-        assertThat(dynamicLib0.getDeclaringPackage().getPackageName(), is("dynamic.lib.pkg"));
-        assertThat(dynamicLib0.getDeclaringPackage().getLongVersionCode(),
-                is(pkg.getLongVersionCode()));
-        assertThat(dynamicLib0.getAllCodePaths(),
-                hasItems("/some/path.apk", "/some/other/path.apk"));
-        assertThat(dynamicLib0.getDependencies(), nullValue());
-        assertThat(dynamicLib0.getDependentPackages(), empty());
-
-        final SharedLibraryInfo dynamicLib1 = scanResult.mDynamicSharedLibraryInfos.get(1);
-        assertThat(dynamicLib1.getPackageName(), is("dynamic.lib.pkg"));
-        assertThat(dynamicLib1.getName(), is("libb"));
-        assertThat(dynamicLib1.getLongVersion(), is((long) VERSION_UNDEFINED));
-        assertThat(dynamicLib1.getType(), is(TYPE_DYNAMIC));
-        assertThat(dynamicLib1.getDeclaringPackage().getPackageName(), is("dynamic.lib.pkg"));
-        assertThat(dynamicLib1.getDeclaringPackage().getLongVersionCode(),
-                is(pkg.getLongVersionCode()));
-        assertThat(dynamicLib1.getAllCodePaths(),
-                hasItems("/some/path.apk", "/some/other/path.apk"));
-        assertThat(dynamicLib1.getDependencies(), nullValue());
-        assertThat(dynamicLib1.getDependentPackages(), empty());
-    }
-
-    @Test
-    public void volumeUuidChangesOnUpdate() throws Exception {
-        final PackageSetting pkgSetting =
-                createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
-                        .setVolumeUuid("someUuid")
-                        .build();
-
-        final ParsedPackage basicPackage = ((ParsedPackage) createBasicPackage(DUMMY_PACKAGE_NAME)
-                .setVolumeUuid(UUID_TWO.toString())
-                .hideAsParsed());
-
-
-        final ScanResult scanResult = executeScan(
-                new ScanRequestBuilder(basicPackage).setPkgSetting(pkgSetting).build());
-
-        assertThat(scanResult.mPkgSetting.getVolumeUuid(), is(UUID_TWO.toString()));
-    }
-
-    @Test
-    public void scanFirstBoot_derivesAbis() throws Exception {
-        final PackageSetting pkgSetting =
-                createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME).build();
-
-        final ParsedPackage basicPackage =
-                ((ParsedPackage) createBasicPackage(DUMMY_PACKAGE_NAME)
-                        .hideAsParsed());
-
-
-        final ScanResult scanResult = executeScan(new ScanRequestBuilder(
-                basicPackage)
-                .setPkgSetting(pkgSetting)
-                .addScanFlag(SCAN_FIRST_BOOT_OR_UPGRADE)
-                .build());
-
-        assertAbiAndPathssDerived(scanResult);
-    }
-
-    @Test
-    public void scanWithOriginalPkgSetting_packageNameChanges() throws Exception {
-        final PackageSetting originalPkgSetting =
-                createBasicPackageSettingBuilder("original.package").build();
-
-        final ParsedPackage basicPackage =
-                (ParsedPackage) createBasicPackage(DUMMY_PACKAGE_NAME)
-                        .hideAsParsed();
-
-
-        final ScanResult result =
-                executeScan(new ScanRequestBuilder(basicPackage)
-                        .setOriginalPkgSetting(originalPkgSetting)
-                        .build());
-
-        assertThat(result.mRequest.mParsedPackage.getPackageName(), is("original.package"));
-    }
-
-    @Test
-    public void updateInstant_changeToFull() throws Exception {
-        when(mMockUserManager.getUserIds()).thenReturn(new int[]{0});
-
-        final PackageSetting existingPkgSetting =
-                createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
-                        .setInstantAppUserState(0, true)
-                        .build();
-
-        final ScanRequest scanRequest =
-                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
-                        .setPkgSetting(existingPkgSetting)
-                        .addScanFlag(SCAN_AS_FULL_APP)
-                        .build();
-
-
-        final ScanResult scanResult = executeScan(scanRequest);
-
-        assertBasicPackageScanResult(scanResult, DUMMY_PACKAGE_NAME, false /*isInstant*/);
-    }
-
-    @Test
-    public void updateFull_changeToInstant() throws Exception {
-        when(mMockUserManager.getUserIds()).thenReturn(new int[]{0});
-
-        final PackageSetting existingPkgSetting =
-                createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
-                        .setInstantAppUserState(0, false)
-                        .build();
-
-        final ScanRequest scanRequest =
-                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
-                        .setPkgSetting(existingPkgSetting)
-                        .addScanFlag(SCAN_AS_INSTANT_APP)
-                        .build();
-
-
-        final ScanResult scanResult = executeScan(scanRequest);
-
-        assertBasicPackageScanResult(scanResult, DUMMY_PACKAGE_NAME, true /*isInstant*/);
-    }
-
-    @Test
-    public void updateSystemApp_applicationInfoFlagSet() throws Exception {
-        final PackageSetting existingPkgSetting =
-                createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
-                        .setPkgFlags(ApplicationInfo.FLAG_SYSTEM)
-                        .build();
-
-        final ScanRequest scanRequest =
-                createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
-                        .setPkgSetting(existingPkgSetting)
-                        .setDisabledPkgSetting(existingPkgSetting)
-                        .addScanFlag(SCAN_NEW_INSTALL)
-                        .build();
-
-        final ScanResult scanResult = executeScan(scanRequest);
-
-        int appInfoFlags = PackageInfoUtils.appInfoFlags(scanResult.mRequest.mParsedPackage,
-                scanResult.mPkgSetting);
-        assertThat(appInfoFlags, hasFlag(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP));
-    }
-
-    @Test
-    public void factoryTestFlagSet() throws Exception {
-        final ParsingPackage basicPackage = createBasicPackage(DUMMY_PACKAGE_NAME)
-                .addUsesPermission(
-                        new ParsedUsesPermissionImpl(Manifest.permission.FACTORY_TEST, 0));
-
-        final ScanResult scanResult = ScanPackageUtils.scanPackageOnlyLI(
-                createBasicScanRequestBuilder(basicPackage).build(),
-                mMockInjector,
-                true /*isUnderFactoryTest*/,
-                System.currentTimeMillis());
-
-        int appInfoFlags = PackageInfoUtils.appInfoFlags(scanResult.mRequest.mParsedPackage,
-                scanResult.mRequest.mPkgSetting);
-        assertThat(appInfoFlags, hasFlag(ApplicationInfo.FLAG_FACTORY_TEST));
-    }
-
-    @Test
-    public void scanSystemApp_isOrphanedTrue() throws Exception {
-        final ParsedPackage pkg = ((ParsedPackage) createBasicPackage(DUMMY_PACKAGE_NAME)
-                .hideAsParsed())
-                .setSystem(true);
-
-        final ScanRequest scanRequest =
-                createBasicScanRequestBuilder(pkg)
-                        .build();
-
-        final ScanResult scanResult = executeScan(scanRequest);
-
-        assertThat(scanResult.mPkgSetting.getInstallSource().isOrphaned, is(true));
-    }
-
-    private static Matcher<Integer> hasFlag(final int flag) {
-        return new BaseMatcher<Integer>() {
-            @Override public void describeTo(Description description) {
-                description.appendText("flags ");
-            }
-
-            @Override public boolean matches(Object item) {
-                return ((int) item & flag) != 0;
-            }
-
-            @Override
-            public void describeMismatch(Object item, Description mismatchDescription) {
-                mismatchDescription
-                        .appendValue(item)
-                        .appendText(" does not contain flag ")
-                        .appendValue(flag);
-            }
-        };
-    }
-
-    private ScanResult executeScan(
-            ScanRequest scanRequest) throws PackageManagerException {
-        ScanResult result = ScanPackageUtils.scanPackageOnlyLI(
-                scanRequest,
-                mMockInjector,
-                false /*isUnderFactoryTest*/,
-                System.currentTimeMillis());
-
-        // Need to call hideAsFinal to cache derived fields. This is normally done in PMS, but not
-        // in this cut down flow used for the test.
-        ((ParsedPackage) result.mPkgSetting.getPkg()).hideAsFinal();
-        return result;
-    }
-
-    private static String createCodePath(String packageName) {
-        return "/data/app/" + packageName + "-randompath";
-    }
-
-    private static PackageSettingBuilder createBasicPackageSettingBuilder(String packageName) {
-        return new PackageSettingBuilder()
-                .setName(packageName)
-                .setCodePath(createCodePath(packageName));
-    }
-
-    private static ScanRequestBuilder createBasicScanRequestBuilder(ParsingPackage pkg) {
-        return new ScanRequestBuilder((ParsedPackage) pkg.hideAsParsed())
-                .setUser(UserHandle.of(0));
-    }
-
-    private static ScanRequestBuilder createBasicScanRequestBuilder(ParsedPackage pkg) {
-        return new ScanRequestBuilder(pkg)
-                .setUser(UserHandle.of(0));
-    }
-
-    private static ParsingPackage createBasicPackage(String packageName) {
-        // TODO(b/135203078): Make this use PackageImpl.forParsing and separate the steps
-        return (ParsingPackage) ((ParsedPackage) new PackageImpl(packageName,
-                "/data/tmp/randompath/base.apk", createCodePath(packageName),
-                mock(TypedArray.class), false)
-                .setVolumeUuid(UUID_ONE.toString())
-                .addUsesStaticLibrary("some.static.library", 234L, new String[]{"testCert1"})
-                .addUsesStaticLibrary("some.other.static.library", 456L, new String[]{"testCert2"})
-                .addUsesSdkLibrary("some.sdk.library", 123L, new String[]{"testCert3"})
-                .addUsesSdkLibrary("some.other.sdk.library", 789L, new String[]{"testCert4"})
-                .hideAsParsed())
-                .setNativeLibraryRootDir("/data/tmp/randompath/base.apk:/lib")
-                .setVersionCodeMajor(1)
-                .setVersionCode(2345);
-    }
-
-    private static void assertBasicPackageScanResult(
-            ScanResult scanResult, String packageName, boolean isInstant) {
-
-        final PackageSetting pkgSetting = scanResult.mPkgSetting;
-        assertBasicPackageSetting(scanResult, packageName, isInstant, pkgSetting);
-
-        final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
-                pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
-        assertBasicApplicationInfo(scanResult, applicationInfo);
-    }
-
-    private static void assertBasicPackageSetting(ScanResult scanResult,
-            String packageName, boolean isInstant, PackageSetting pkgSetting) {
-        assertThat(pkgSetting.getPkg().getPackageName(), is(packageName));
-        assertThat(pkgSetting.getInstantApp(0), is(isInstant));
-        assertThat(pkgSetting.getUsesStaticLibraries(),
-                arrayContaining("some.static.library", "some.other.static.library"));
-        assertThat(pkgSetting.getUsesStaticLibrariesVersions(), is(new long[]{234L, 456L}));
-        assertThat(pkgSetting.getUsesSdkLibraries(),
-                arrayContaining("some.sdk.library", "some.other.sdk.library"));
-        assertThat(pkgSetting.getUsesSdkLibrariesVersionsMajor(), is(new long[]{123L, 789L}));
-        assertThat(pkgSetting.getPkg(), is(scanResult.mRequest.mParsedPackage));
-        assertThat(pkgSetting.getPath(), is(new File(createCodePath(packageName))));
-        assertThat(pkgSetting.getVersionCode(),
-                is(PackageInfo.composeLongVersionCode(1, 2345)));
-    }
-
-    private static void assertBasicApplicationInfo(ScanResult scanResult,
-            ApplicationInfo applicationInfo) {
-        assertThat(applicationInfo.processName,
-                is(scanResult.mRequest.mParsedPackage.getPackageName()));
-
-        final int uid = applicationInfo.uid;
-        assertThat(UserHandle.getUserId(uid), is(UserHandle.USER_SYSTEM));
-
-        final String calculatedCredentialId = Environment.getDataUserCePackageDirectory(
-                applicationInfo.volumeUuid, UserHandle.USER_SYSTEM,
-                scanResult.mRequest.mParsedPackage.getPackageName()).getAbsolutePath();
-        assertThat(applicationInfo.credentialProtectedDataDir, is(calculatedCredentialId));
-        assertThat(applicationInfo.dataDir, is(applicationInfo.credentialProtectedDataDir));
-    }
-
-    private static void assertAbiAndPathssDerived(ScanResult scanResult) {
-        PackageSetting pkgSetting = scanResult.mPkgSetting;
-        final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
-                pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
-        assertThat(applicationInfo.primaryCpuAbi, is("derivedPrimary"));
-        assertThat(applicationInfo.secondaryCpuAbi, is("derivedSecondary"));
-
-        assertThat(applicationInfo.nativeLibraryRootDir, is("derivedRootDir"));
-        assertThat(pkgSetting.getLegacyNativeLibraryPath(), is("derivedRootDir"));
-        assertThat(applicationInfo.nativeLibraryRootRequiresIsa, is(true));
-        assertThat(applicationInfo.nativeLibraryDir, is("derivedNativeDir"));
-        assertThat(applicationInfo.secondaryNativeLibraryDir, is("derivedNativeDir2"));
-    }
-
-    private static void assertPathsNotDerived(ScanResult scanResult) {
-        PackageSetting pkgSetting = scanResult.mPkgSetting;
-        final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
-                pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
-        assertThat(applicationInfo.nativeLibraryRootDir, is("getRootDir"));
-        assertThat(pkgSetting.getLegacyNativeLibraryPath(), is("getRootDir"));
-        assertThat(applicationInfo.nativeLibraryRootRequiresIsa, is(true));
-        assertThat(applicationInfo.nativeLibraryDir, is("getNativeDir"));
-        assertThat(applicationInfo.secondaryNativeLibraryDir, is("getNativeDir2"));
-    }
-}
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/UserDataPreparerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
deleted file mode 100644
index b2f9002..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
+++ /dev/null
@@ -1,266 +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.server.pm;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.os.FileUtils;
-import android.os.storage.StorageManager;
-import android.os.storage.VolumeInfo;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.Collections;
-
-/**
- * <p>Run with:<pre>
- * m FrameworksServicesTests &&
- * adb install \
- * -r out/target/product/hammerhead/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
- * adb shell am instrument -e class com.android.server.pm.UserDataPreparerTest \
- * -w com.android.frameworks.servicestests/androidx.test.runner.AndroidJUnitRunner
- * </pre>
- */
-@RunWith(AndroidJUnit4.class)
-@Presubmit
-@SmallTest
-public class UserDataPreparerTest {
-
-    private static final int TEST_USER_SERIAL = 1000;
-    private static final int TEST_USER_ID = 10;
-
-    private TestUserDataPreparer mUserDataPreparer;
-
-    @Mock
-    private StorageManager mStorageManagerMock;
-
-    @Mock
-    private Context mContextMock;
-
-    @Mock
-    private Installer mInstaller;
-
-    private Object mInstallLock;
-
-    @Before
-    public void setup() {
-        Context ctx = InstrumentationRegistry.getContext();
-        FileUtils.deleteContents(ctx.getCacheDir());
-        mInstallLock = new Object();
-        MockitoAnnotations.initMocks(this);
-        mUserDataPreparer = new TestUserDataPreparer(mInstaller, mInstallLock, mContextMock,
-                ctx.getCacheDir());
-        when(mContextMock.getSystemServiceName(StorageManager.class))
-                .thenReturn(Context.STORAGE_SERVICE);
-        when(mContextMock.getSystemService(eq(Context.STORAGE_SERVICE)))
-                .thenReturn(mStorageManagerMock);
-        VolumeInfo testVolume = new VolumeInfo("testuuid", VolumeInfo.TYPE_PRIVATE, null, null);
-        when(mStorageManagerMock.getWritablePrivateVolumes()).thenReturn(Arrays.asList(testVolume));
-    }
-
-    @Test
-    public void testPrepareUserData_De() throws Exception {
-        File userDeDir = mUserDataPreparer.getDataUserDeDirectory(null, TEST_USER_ID);
-        userDeDir.mkdirs();
-        File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID);
-        systemDeDir.mkdirs();
-        mUserDataPreparer
-                .prepareUserData(TEST_USER_ID, TEST_USER_SERIAL, StorageManager.FLAG_STORAGE_DE);
-        verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
-                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
-        verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
-                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
-        int serialNumber = UserDataPreparer.getSerialNumber(userDeDir);
-        assertEquals(TEST_USER_SERIAL, serialNumber);
-        serialNumber = UserDataPreparer.getSerialNumber(systemDeDir);
-        assertEquals(TEST_USER_SERIAL, serialNumber);
-    }
-
-    @Test
-    public void testPrepareUserData_Ce() throws Exception {
-        File userCeDir = mUserDataPreparer.getDataUserCeDirectory(null, TEST_USER_ID);
-        userCeDir.mkdirs();
-        File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
-        systemCeDir.mkdirs();
-        mUserDataPreparer
-                .prepareUserData(TEST_USER_ID, TEST_USER_SERIAL, StorageManager.FLAG_STORAGE_CE);
-        verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
-                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
-        verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
-                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
-        int serialNumber = UserDataPreparer.getSerialNumber(userCeDir);
-        assertEquals(TEST_USER_SERIAL, serialNumber);
-        serialNumber = UserDataPreparer.getSerialNumber(systemCeDir);
-        assertEquals(TEST_USER_SERIAL, serialNumber);
-    }
-
-    @Test
-    public void testDestroyUserData_De_DoesNotDestroyCe() throws Exception {
-        // Add file in CE storage
-        File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
-        systemCeDir.mkdirs();
-        File ceFile = new File(systemCeDir, "file");
-        writeFile(ceFile, "-----" );
-        // Destroy DE storage, then verify that CE storage wasn't destroyed too.
-        mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_DE);
-        assertEquals(Collections.singletonList(ceFile), Arrays.asList(FileUtils.listFilesOrEmpty(
-                systemCeDir)));
-    }
-
-    @Test
-    public void testDestroyUserData_De() throws Exception {
-        File systemDir = mUserDataPreparer.getUserSystemDirectory(TEST_USER_ID);
-        systemDir.mkdirs();
-        writeFile(new File(systemDir, "file"), "-----" );
-        File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID);
-        systemDeDir.mkdirs();
-        writeFile(new File(systemDeDir, "file"), "-----" );
-
-        mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_DE);
-
-        verify(mInstaller).destroyUserData(isNull(String.class), eq(TEST_USER_ID),
-                        eq(StorageManager.FLAG_STORAGE_DE));
-        verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
-                        eq(StorageManager.FLAG_STORAGE_DE));
-
-        // systemDir (normal path: /data/system/users/$userId) should have been deleted.
-        assertFalse(systemDir.exists());
-        // systemDeDir (normal path: /data/system_de/$userId) should still exist but be empty, since
-        // UserDataPreparer itself is responsible for deleting the contents of this directory, but
-        // it delegates to StorageManager.destroyUserStorage() for deleting the directory itself.
-        // We've mocked out StorageManager, so StorageManager.destroyUserStorage() will be a no-op.
-        assertTrue(systemDeDir.exists());
-        assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
-                systemDeDir)));
-    }
-
-    @Test
-    public void testDestroyUserData_Ce() throws Exception {
-        File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
-        systemCeDir.mkdirs();
-        writeFile(new File(systemCeDir, "file"), "-----" );
-
-        mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_CE);
-
-        verify(mInstaller).destroyUserData(isNull(String.class), eq(TEST_USER_ID),
-                eq(StorageManager.FLAG_STORAGE_CE));
-        verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
-                eq(StorageManager.FLAG_STORAGE_CE));
-
-        // systemCeDir (normal path: /data/system_ce/$userId) should still exist but be empty, since
-        // UserDataPreparer itself is responsible for deleting the contents of this directory, but
-        // it delegates to StorageManager.destroyUserStorage() for deleting the directory itself.
-        // We've mocked out StorageManager, so StorageManager.destroyUserStorage() will be a no-op.
-        assertTrue(systemCeDir.exists());
-        assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
-                systemCeDir)));
-    }
-
-    @Test
-    public void testReconcileUsers() throws Exception {
-        UserInfo u1 = new UserInfo(1, "u1", 0);
-        UserInfo u2 = new UserInfo(2, "u2", 0);
-        File testDir = mUserDataPreparer.testDir;
-        File dir1 = new File(testDir, "1");
-        dir1.mkdirs();
-        File dir2 = new File(testDir, "2");
-        dir2.mkdirs();
-        File dir3 = new File(testDir, "3");
-        dir3.mkdirs();
-
-        mUserDataPreparer
-                .reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL, Arrays.asList(u1, u2),
-                        Arrays.asList(dir1, dir2, dir3));
-        // Verify that user 3 data is removed
-        verify(mInstaller).destroyUserData(isNull(String.class), eq(3),
-                eq(StorageManager.FLAG_STORAGE_DE|StorageManager.FLAG_STORAGE_CE));
-    }
-
-    private static void writeFile(File file, String content) throws IOException {
-        try (FileOutputStream os = new FileOutputStream(file)) {
-            os.write(content.getBytes(Charset.defaultCharset()));
-        }
-    }
-
-    private static class TestUserDataPreparer extends UserDataPreparer {
-        File testDir;
-
-        TestUserDataPreparer(Installer installer, Object installLock, Context context,
-                File testDir) {
-            super(installer, installLock, context);
-            this.testDir = testDir;
-        }
-
-        @Override
-        protected File getDataMiscCeDirectory(int userId) {
-            return new File(testDir, "misc_ce_" + userId);
-        }
-
-        @Override
-        protected File getDataSystemCeDirectory(int userId) {
-            return new File(testDir, "system_ce_" + userId);
-        }
-
-        @Override
-        protected File getDataMiscDeDirectory(int userId) {
-            return new File(testDir, "misc_de_" + userId);
-        }
-
-        @Override
-        protected File getUserSystemDirectory(int userId) {
-            return new File(testDir, "user_system_" + userId);
-        }
-
-        @Override
-        protected File getDataUserCeDirectory(String volumeUuid, int userId) {
-            return new File(testDir, "user_ce_" + userId);
-        }
-
-        @Override
-        protected File getDataSystemDeDirectory(int userId) {
-            return new File(testDir, "system_de_" + userId);
-        }
-
-        @Override
-        protected File getDataUserDeDirectory(String volumeUuid, int userId) {
-            return new File(testDir, "user_de_" + userId);
-        }
-    }
-
-}
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..1f952c4 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,15 @@
         final UserProperties defaultProps = new UserProperties.Builder()
                 .setShowInLauncher(21)
                 .setStartWithParent(false)
+                .setShowInSettings(45)
+                .setInheritDevicePolicy(67)
+                .setUseParentsContacts(false)
                 .build();
         final UserProperties actualProps = new UserProperties(defaultProps);
         actualProps.setShowInLauncher(14);
+        actualProps.setShowInSettings(32);
+        actualProps.setInheritDevicePolicy(51);
+        actualProps.setUseParentsContacts(true);
 
         // Write the properties to xml.
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -99,10 +105,14 @@
         final UserProperties defaultProps = new UserProperties.Builder()
                 .setShowInLauncher(2145)
                 .setStartWithParent(true)
+                .setShowInSettings(3452)
+                .setInheritDevicePolicy(1732)
                 .build();
         final UserProperties orig = new UserProperties(defaultProps);
         orig.setShowInLauncher(2841);
         orig.setStartWithParent(false);
+        orig.setShowInSettings(1437);
+        orig.setInheritDevicePolicy(9456);
 
         // Test every permission level. (Currently, it's linear so it's easy.)
         for (int permLevel = 0; permLevel < 4; permLevel++) {
@@ -138,12 +148,20 @@
 
         // Items requiring exposeAll.
         assertEqualGetterOrThrows(orig::getStartWithParent, copy::getStartWithParent, exposeAll);
+        assertEqualGetterOrThrows(orig::getInheritDevicePolicy,
+                copy::getInheritDevicePolicy, exposeAll);
 
         // Items requiring hasManagePermission - put them here using hasManagePermission.
+        assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
+                hasManagePermission);
+        assertEqualGetterOrThrows(orig::getUseParentsContacts,
+                copy::getUseParentsContacts, hasManagePermission);
+
         // Items requiring hasQueryPermission - put them here using hasQueryPermission.
 
         // Items with no permission requirements.
         assertEqualGetterOrThrows(orig::getShowInLauncher, copy::getShowInLauncher, true);
+
     }
 
     /**
@@ -182,5 +200,8 @@
         assertThat(expected.getPropertiesPresent()).isEqualTo(actual.getPropertiesPresent());
         assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
         assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
+        assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings());
+        assertThat(expected.getInheritDevicePolicy()).isEqualTo(actual.getInheritDevicePolicy());
+        assertThat(expected.getUseParentsContacts()).isEqualTo(actual.getUseParentsContacts());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 5f48004..d7c1e37 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -83,7 +83,8 @@
                 /* flags= */0,
                 /* letsPersonalDataIntoProfile= */false).build());
         final UserProperties.Builder userProps = new UserProperties.Builder()
-                .setShowInLauncher(17);
+                .setShowInLauncher(17)
+                .setUseParentsContacts(true);
         final UserTypeDetails type = new UserTypeDetails.Builder()
                 .setName("a.name")
                 .setEnabled(1)
@@ -140,6 +141,7 @@
         }
 
         assertEquals(17, type.getDefaultUserPropertiesReference().getShowInLauncher());
+        assertTrue(type.getDefaultUserPropertiesReference().getUseParentsContacts());
 
         assertEquals(23, type.getBadgeLabel(0));
         assertEquals(24, type.getBadgeLabel(1));
@@ -182,6 +184,7 @@
         final UserProperties props = type.getDefaultUserPropertiesReference();
         assertNotNull(props);
         assertFalse(props.getStartWithParent());
+        assertFalse(props.getUseParentsContacts());
         assertEquals(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT, props.getShowInLauncher());
 
         assertFalse(type.hasBadge());
@@ -263,7 +266,8 @@
         final Bundle restrictions = makeRestrictionsBundle("no_config_vpn", "no_config_tethering");
         final UserProperties.Builder props = new UserProperties.Builder()
                 .setShowInLauncher(19)
-                .setStartWithParent(true);
+                .setStartWithParent(true)
+                .setUseParentsContacts(true);
         final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
         builders.put(userTypeAosp1, new UserTypeDetails.Builder()
                 .setName(userTypeAosp1)
@@ -289,7 +293,9 @@
         assertEquals(Resources.ID_NULL, aospType.getIconBadge());
         assertTrue(UserRestrictionsUtils.areEqual(restrictions, aospType.getDefaultRestrictions()));
         assertEquals(19, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
-        assertEquals(true, aospType.getDefaultUserPropertiesReference().getStartWithParent());
+        assertTrue(aospType.getDefaultUserPropertiesReference().getStartWithParent());
+        assertTrue(aospType.getDefaultUserPropertiesReference()
+                .getUseParentsContacts());
 
         // userTypeAosp2 should be modified.
         aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -319,7 +325,9 @@
                 makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
                 aospType.getDefaultRestrictions()));
         assertEquals(2020, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
-        assertEquals(false, aospType.getDefaultUserPropertiesReference().getStartWithParent());
+        assertFalse(aospType.getDefaultUserPropertiesReference().getStartWithParent());
+        assertFalse(aospType.getDefaultUserPropertiesReference()
+                .getUseParentsContacts());
 
         // userTypeOem1 should be created.
         UserTypeDetails.Builder customType = builders.get(userTypeOem1);
@@ -347,6 +355,7 @@
         UserTypeDetails details = builders.get(userTypeFull).createUserTypeDetails();
         assertEquals(UNLIMITED_NUMBER_OF_USERS, details.getMaxAllowedPerParent());
         assertFalse(details.isEnabled());
+        assertEquals(17, details.getMaxAllowed());
         assertTrue(UserRestrictionsUtils.areEqual(
                 makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
                 details.getDefaultRestrictions()));
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..2e7e583 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 import static org.testng.Assert.assertThrows;
@@ -164,6 +165,14 @@
 
     @Test
     public void testCloneUser() throws Exception {
+
+        // Get the default properties for clone user type.
+        final UserTypeDetails userTypeDetails =
+                UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_CLONE);
+        assertWithMessage("No %s type on device", UserManager.USER_TYPE_PROFILE_CLONE)
+                .that(userTypeDetails).isNotNull();
+        final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference();
+
         // Test that only one clone user can be created
         final int primaryUserId = mUserManager.getPrimaryUser().id;
         UserInfo userInfo = createProfileForUser("Clone user1",
@@ -187,6 +196,16 @@
                 .collect(Collectors.toList());
         assertThat(cloneUsers.size()).isEqualTo(1);
 
+        // Check that the new clone user has the expected properties (relative to the defaults)
+        // provided that the test caller has the necessary permissions.
+        UserProperties cloneUserProperties =
+                mUserManager.getUserProperties(UserHandle.of(userInfo.id));
+        assertThat(typeProps.getUseParentsContacts())
+                .isEqualTo(cloneUserProperties.getUseParentsContacts());
+        assertThat(typeProps.getShowInLauncher())
+                .isEqualTo(cloneUserProperties.getShowInLauncher());
+        assertThrows(SecurityException.class, cloneUserProperties::getStartWithParent);
+
         // Verify clone user parent
         assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
         UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
@@ -599,7 +618,10 @@
         // 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());
+        assertFalse(userProps.getUseParentsContacts());
         assertThrows(SecurityException.class, userProps::getStartWithParent);
+        assertThrows(SecurityException.class, userProps::getInheritDevicePolicy);
     }
 
     // Make sure only max managed profiles can be created
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
deleted file mode 100644
index 9cd97ff3..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ /dev/null
@@ -1,659 +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.pm.parsing;
-
-import static org.junit.Assert.assertEquals;
-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;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.frameworks.servicestests.R;
-import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.PackageManagerException;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedActivityUtils;
-import com.android.server.pm.pkg.component.ParsedComponent;
-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;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.InputStream;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-
-/**
- * {@link ParsedPackage} was moved to the server, so this test moved along with it.
- *
- * This should be eventually refactored to a comprehensive parsing test, combined with its
- * server variant in the parent package.
- *
- * TODO(b/135203078): Remove this test and replicate the cases in the actual com.android.server
- *  variant.
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class PackageParserLegacyCoreTest {
-    private static final String RELEASED = null;
-    private static final String OLDER_PRE_RELEASE = "Q";
-    private static final String PRE_RELEASE = "R";
-    private static final String NEWER_PRE_RELEASE = "Z";
-
-    // Codenames with a fingerprint attached to them. These may only be present in the apps
-    // declared min SDK and not as platform codenames.
-    private static final String OLDER_PRE_RELEASE_WITH_FINGERPRINT = "Q.fingerprint";
-    private static final String PRE_RELEASE_WITH_FINGERPRINT = "R.fingerprint";
-    private static final String NEWER_PRE_RELEASE_WITH_FINGERPRINT = "Z.fingerprint";
-
-    private static final String[] CODENAMES_RELEASED = { /* empty */};
-    private static final String[] CODENAMES_PRE_RELEASE = {PRE_RELEASE};
-
-    private static final int OLDER_VERSION = 10;
-    private static final int PLATFORM_VERSION = 20;
-    private static final int NEWER_VERSION = 30;
-
-    @Rule public final Expect expect = Expect.create();
-
-    private void verifyComputeMinSdkVersion(int minSdkVersion, String minSdkCodename,
-            boolean isPlatformReleased, int expectedMinSdk) {
-        final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat();
-        final ParseResult<Integer> result = FrameworkParsingPackageUtils.computeMinSdkVersion(
-                minSdkVersion,
-                minSdkCodename,
-                PLATFORM_VERSION,
-                isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE,
-                input);
-
-        if (expectedMinSdk == -1) {
-            assertTrue(result.isError());
-        } else {
-            assertTrue(result.isSuccess());
-            assertEquals(expectedMinSdk, (int) result.getResult());
-        }
-    }
-
-    @Test
-    public void testComputeMinSdkVersion_preReleasePlatform() {
-        // Do allow older release minSdkVersion on pre-release platform.
-        // APP: Released API 10
-        // DEV: Pre-release API 20
-        verifyComputeMinSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION);
-
-        // Do allow same release minSdkVersion on pre-release platform.
-        // APP: Released API 20
-        // DEV: Pre-release API 20
-        verifyComputeMinSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION);
-
-        // Don't allow newer release minSdkVersion on pre-release platform.
-        // APP: Released API 30
-        // DEV: Pre-release API 20
-        verifyComputeMinSdkVersion(NEWER_VERSION, RELEASED, false, -1);
-
-        // Don't allow older pre-release minSdkVersion on pre-release platform.
-        // APP: Pre-release API 10
-        // DEV: Pre-release API 20
-        verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1);
-        verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
-
-        // Do allow same pre-release minSdkVersion on pre-release platform,
-        // but overwrite the specified version with CUR_DEVELOPMENT.
-        // APP: Pre-release API 20
-        // DEV: Pre-release API 20
-        verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false,
-                Build.VERSION_CODES.CUR_DEVELOPMENT);
-        verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, false,
-                Build.VERSION_CODES.CUR_DEVELOPMENT);
-
-
-        // Don't allow newer pre-release minSdkVersion on pre-release platform.
-        // APP: Pre-release API 30
-        // DEV: Pre-release API 20
-        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1);
-        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
-    }
-
-    @Test
-    public void testComputeMinSdkVersion_releasedPlatform() {
-        // Do allow older release minSdkVersion on released platform.
-        // APP: Released API 10
-        // DEV: Released API 20
-        verifyComputeMinSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION);
-
-        // Do allow same release minSdkVersion on released platform.
-        // APP: Released API 20
-        // DEV: Released API 20
-        verifyComputeMinSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION);
-
-        // Don't allow newer release minSdkVersion on released platform.
-        // APP: Released API 30
-        // DEV: Released API 20
-        verifyComputeMinSdkVersion(NEWER_VERSION, RELEASED, true, -1);
-
-        // Don't allow older pre-release minSdkVersion on released platform.
-        // APP: Pre-release API 10
-        // DEV: Released API 20
-        verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1);
-        verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
-
-        // Don't allow same pre-release minSdkVersion on released platform.
-        // APP: Pre-release API 20
-        // DEV: Released API 20
-        verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1);
-        verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, -1);
-
-
-        // Don't allow newer pre-release minSdkVersion on released platform.
-        // APP: Pre-release API 30
-        // DEV: Released API 20
-        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1);
-        verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
-    }
-
-    private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename,
-            boolean isPlatformReleased, boolean allowUnknownCodenames, int expectedTargetSdk) {
-        final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat();
-        final ParseResult<Integer> result = FrameworkParsingPackageUtils.computeTargetSdkVersion(
-                targetSdkVersion,
-                targetSdkCodename,
-                isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE,
-                input,
-                allowUnknownCodenames);
-
-        if (expectedTargetSdk == -1) {
-            assertTrue(result.isError());
-        } else {
-            assertTrue(result.isSuccess());
-            assertEquals(expectedTargetSdk, (int) result.getResult());
-        }
-    }
-
-    @Test
-    public void testComputeTargetSdkVersion_preReleasePlatform() {
-        // Do allow older release targetSdkVersion on pre-release platform.
-        // APP: Released API 10
-        // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, false, OLDER_VERSION);
-
-        // Do allow same release targetSdkVersion on pre-release platform.
-        // APP: Released API 20
-        // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, false, PLATFORM_VERSION);
-
-        // Do allow newer release targetSdkVersion on pre-release platform.
-        // APP: Released API 30
-        // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, false, NEWER_VERSION);
-
-        // Don't allow older pre-release targetSdkVersion on pre-release platform.
-        // APP: Pre-release API 10
-        // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, false, -1);
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false,
-                false, -1
-        );
-
-        // Don't allow older pre-release targetSdkVersion on pre-release platform when
-        // allowUnknownCodenames is true.
-        // APP: Pre-release API 10
-        // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false,
-                true, -1);
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false,
-                true, -1);
-
-        // Do allow same pre-release targetSdkVersion on pre-release platform,
-        // but overwrite the specified version with CUR_DEVELOPMENT.
-        // APP: Pre-release API 20
-        // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false,
-                false, Build.VERSION_CODES.CUR_DEVELOPMENT);
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, false,
-                false, Build.VERSION_CODES.CUR_DEVELOPMENT);
-
-        // Don't allow newer pre-release targetSdkVersion on pre-release platform.
-        // APP: Pre-release API 30
-        // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, false, -1);
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
-                false, -1
-        );
-
-        // Do allow newer pre-release targetSdkVersion on pre-release platform when
-        // allowUnknownCodenames is true.
-        // APP: Pre-release API 30
-        // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false,
-                true, Build.VERSION_CODES.CUR_DEVELOPMENT);
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
-                true, Build.VERSION_CODES.CUR_DEVELOPMENT);
-
-    }
-
-    @Test
-    public void testComputeTargetSdkVersion_releasedPlatform() {
-        // Do allow older release targetSdkVersion on released platform.
-        // APP: Released API 10
-        // DEV: Released API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, false, OLDER_VERSION);
-
-        // Do allow same release targetSdkVersion on released platform.
-        // APP: Released API 20
-        // DEV: Released API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, false, PLATFORM_VERSION);
-
-        // Do allow newer release targetSdkVersion on released platform.
-        // APP: Released API 30
-        // DEV: Released API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, false, NEWER_VERSION);
-
-        // Don't allow older pre-release targetSdkVersion on released platform.
-        // APP: Pre-release API 10
-        // DEV: Released API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, false, -1);
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true,
-                false, -1
-        );
-
-        // Don't allow same pre-release targetSdkVersion on released platform.
-        // APP: Pre-release API 20
-        // DEV: Released API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, false, -1);
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, false,
-                -1
-        );
-
-        // Don't allow same pre-release targetSdkVersion on released platform when
-        // allowUnknownCodenames is true.
-        // APP: Pre-release API 20
-        // DEV: Released API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, true,
-                -1);
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, true,
-                -1);
-
-        // Don't allow newer pre-release targetSdkVersion on released platform.
-        // APP: Pre-release API 30
-        // DEV: Released API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, false, -1);
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true,
-                false, -1
-        );
-        // Do allow newer pre-release targetSdkVersion on released platform when
-        // allowUnknownCodenames is true.
-        // APP: Pre-release API 30
-        // DEV: Released API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, true,
-                Build.VERSION_CODES.CUR_DEVELOPMENT);
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true,
-                true, Build.VERSION_CODES.CUR_DEVELOPMENT);
-    }
-
-    /**
-     * Unit test for PackageParser.getActivityConfigChanges().
-     * If the bit is 1 in the original configChanges, it is still 1 in the final configChanges.
-     * If the bit is 0 in the original configChanges and the bit is not set to 1 in
-     * recreateOnConfigChanges, the bit is changed to 1 in the final configChanges by default.
-     */
-    @Test
-    public void testGetActivityConfigChanges() {
-        // Not set in either configChanges or recreateOnConfigChanges.
-        int configChanges = 0x0000; // 00000000.
-        int recreateOnConfigChanges = 0x0000; // 00000000.
-        int finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges,
-                recreateOnConfigChanges);
-        assertEquals(0x0003, finalConfigChanges); // Should be 00000011.
-
-        // Not set in configChanges, but set in recreateOnConfigChanges.
-        configChanges = 0x0000; // 00000000.
-        recreateOnConfigChanges = 0x0003; // 00000011.
-        finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges,
-                recreateOnConfigChanges);
-        assertEquals(0x0000, finalConfigChanges); // Should be 00000000.
-
-        // Set in configChanges.
-        configChanges = 0x0003; // 00000011.
-        recreateOnConfigChanges = 0X0000; // 00000000.
-        finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges,
-                recreateOnConfigChanges);
-        assertEquals(0x0003, finalConfigChanges); // Should be 00000011.
-
-        recreateOnConfigChanges = 0x0003; // 00000011.
-        finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges,
-                recreateOnConfigChanges);
-        assertEquals(0x0003, finalConfigChanges); // Should still be 00000011.
-
-        // Other bit set in configChanges.
-        configChanges = 0x0080; // 10000000, orientation.
-        recreateOnConfigChanges = 0x0000; // 00000000.
-        finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges,
-                recreateOnConfigChanges);
-        assertEquals(0x0083, finalConfigChanges); // Should be 10000011.
-    }
-
-    /**
-     * Copies a specified {@code resourceId} to a file. Returns a non-null file if the copy
-     * succeeded, or {@code null} otherwise.
-     */
-    File copyRawResourceToFile(String baseName, int resourceId) {
-        // Copy the resource to a file.
-        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        InputStream is = context.getResources().openRawResource(resourceId);
-        File outFile = null;
-        try {
-            outFile = new File(context.getFilesDir(), baseName);
-            assertTrue(FileUtils.copyToFile(is, outFile));
-            return outFile;
-        } catch (Exception e) {
-            if (outFile != null) {
-                outFile.delete();
-            }
-
-            return null;
-        }
-    }
-
-    /**
-     * Attempts to parse a package.
-     *
-     * APKs are put into coretests/apks/packageparser_*.
-     *
-     * @param apkFileName   temporary file name to store apk extracted from resources
-     * @param apkResourceId identifier of the apk as a resource
-     */
-    ParsedPackage parsePackage(String apkFileName, int apkResourceId,
-            Function<ParsedPackage, ParsedPackage> converter) throws Exception {
-        // Copy the resource to a file.
-        File outFile = null;
-        try {
-            outFile = copyRawResourceToFile(apkFileName, apkResourceId);
-            return converter.apply(new TestPackageParser2()
-                    .parsePackage(outFile, 0 /* flags */, false));
-        } finally {
-            if (outFile != null) {
-                outFile.delete();
-            }
-        }
-    }
-
-    /**
-     * Asserts basic properties about a component.
-     */
-    private void assertComponent(String className, int numIntents, ParsedComponent component) {
-        assertEquals(className, component.getName());
-        assertEquals(numIntents, component.getIntents().size());
-    }
-
-    /**
-     * Asserts four regularly-named components of each type: one Activity, one Service, one
-     * Provider, and one Receiver.
-     *
-     * @param template templated string with %s subbed with Activity, Service, Provider, Receiver
-     */
-    private void assertOneComponentOfEachType(String template, AndroidPackage p) {
-        assertEquals(1, p.getActivities().size());
-        assertComponent(String.format(template, "Activity"),
-                0 /* intents */, p.getActivities().get(0));
-        assertEquals(1, p.getServices().size());
-        assertComponent(String.format(template, "Service"),
-                0 /* intents */, p.getServices().get(0));
-        assertEquals(1, p.getProviders().size());
-        assertComponent(String.format(template, "Provider"),
-                0 /* intents */, p.getProviders().get(0));
-        assertEquals(1, p.getReceivers().size());
-        assertComponent(String.format(template, "Receiver"),
-                0 /* intents */, p.getReceivers().get(0));
-    }
-
-    private void assertPermission(String name, int protectionLevel, ParsedPermission permission) {
-        assertEquals(name, permission.getName());
-        assertEquals(protectionLevel, ParsedPermissionUtils.getProtection(permission));
-    }
-
-    private void assertMetadata(Bundle b, String... keysAndValues) {
-        assertTrue("Odd number of elements in keysAndValues", (keysAndValues.length % 2) == 0);
-
-        assertNotNull(b);
-        assertEquals(keysAndValues.length / 2, b.size());
-
-        for (int i = 0; i < keysAndValues.length; i += 2) {
-            final String key = keysAndValues[i];
-            final String value = keysAndValues[i + 1];
-
-            assertEquals(value, b.getString(key));
-        }
-    }
-
-    // TODO Add a "_cached" test for testMultiPackageComponents() too, after fixing b/64295061.
-    // Package.writeToParcel can't handle circular package references.
-
-    @Test
-    public void testPackageWithComponents_no_cache() throws Exception {
-        checkPackageWithComponents(p -> p);
-    }
-
-    @Test
-    public void testPackageWithComponents_cached() throws Exception {
-        checkPackageWithComponents(p ->
-                PackageCacher.fromCacheEntryStatic(PackageCacher.toCacheEntryStatic(p)));
-    }
-
-    private void checkPackageWithComponents(
-            Function<ParsedPackage, ParsedPackage> converter) throws Exception {
-        ParsedPackage p = parsePackage(
-                "install_complete_package_info.apk", R.raw.install_complete_package_info,
-                converter);
-        String packageName = "com.android.frameworks.coretests.install_complete_package_info";
-
-        assertEquals(packageName, p.getPackageName());
-        assertEquals(1, p.getPermissions().size());
-        assertPermission(
-                "com.android.frameworks.coretests.install_complete_package_info.test_permission",
-                PermissionInfo.PROTECTION_NORMAL, p.getPermissions().get(0));
-
-        findAndRemoveAppDetailsActivity(p);
-
-        assertOneComponentOfEachType("com.android.frameworks.coretests.Test%s", p);
-
-        assertMetadata(p.getMetaData(),
-                "key1", "value1",
-                "key2", "this_is_app");
-        assertMetadata(p.getActivities().get(0).getMetaData(),
-                "key1", "value1",
-                "key2", "this_is_activity");
-        assertMetadata(p.getServices().get(0).getMetaData(),
-                "key1", "value1",
-                "key2", "this_is_service");
-        assertMetadata(p.getReceivers().get(0).getMetaData(),
-                "key1", "value1",
-                "key2", "this_is_receiver");
-        assertMetadata(p.getProviders().get(0).getMetaData(),
-                "key1", "value1",
-                "key2", "this_is_provider");
-
-    }
-
-    private void findAndRemoveAppDetailsActivity(ParsedPackage p) {
-        // Hidden "app details" activity is added to every package.
-        boolean foundAppDetailsActivity = false;
-        for (int i = 0; i < ArrayUtils.size(p.getActivities()); i++) {
-            if (p.getActivities().get(i).getClassName().equals(
-                    PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME)) {
-                foundAppDetailsActivity = true;
-                p.getActivities().remove(i);
-                break;
-            }
-        }
-        assertTrue("Did not find app details activity", foundAppDetailsActivity);
-    }
-
-    @Test
-    public void testPackageWithIntentFilters_no_cache() throws Exception {
-        checkPackageWithIntentFilters(p -> p);
-    }
-
-    @Test
-    public void testPackageWithIntentFilters_cached() throws Exception {
-        checkPackageWithIntentFilters(p ->
-                PackageCacher.fromCacheEntryStatic(PackageCacher.toCacheEntryStatic(p)));
-    }
-
-    private void checkPackageWithIntentFilters(
-            Function<ParsedPackage, ParsedPackage> converter) throws Exception {
-        ParsedPackage p = parsePackage(
-                "install_intent_filters.apk", R.raw.install_intent_filters,
-                converter);
-        String packageName = "com.android.frameworks.servicestests.install_intent_filters";
-
-        assertEquals(packageName, p.getPackageName());
-
-        findAndRemoveAppDetailsActivity(p);
-
-        assertEquals("Expected exactly one activity", 1, p.getActivities().size());
-        List<ParsedIntentInfo> intentInfos = p.getActivities().get(0).getIntents();
-        assertEquals("Expected exactly one intent filter", 1, intentInfos.size());
-        IntentFilter intentFilter = intentInfos.get(0).getIntentFilter();
-        assertEquals("Expected exactly one mime group in intent filter", 1,
-                intentFilter.countMimeGroups());
-        assertTrue("Did not find expected mime group 'mime_group_1'",
-                intentFilter.hasMimeGroup("mime_group_1"));
-    }
-
-    @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;
-        pkg = parsePackage("install_uses_sdk.apk_r0", R.raw.install_uses_sdk_r0, x -> x);
-        minExtVers = pkg.getMinExtensionVersions();
-        assertEquals(1, minExtVers.size());
-        assertEquals(0, minExtVers.get(30, -1));
-
-        pkg = parsePackage("install_uses_sdk.apk_r0_s0", R.raw.install_uses_sdk_r0_s0, x -> x);
-        minExtVers = pkg.getMinExtensionVersions();
-        assertEquals(2, minExtVers.size());
-        assertEquals(0, minExtVers.get(30, -1));
-        assertEquals(0, minExtVers.get(31, -1));
-
-        Map<Pair<String, Integer>, Integer> appToError = new HashMap<>();
-        appToError.put(Pair.create("install_uses_sdk.apk_r5", R.raw.install_uses_sdk_r5),
-                       PackageManager.INSTALL_FAILED_OLDER_SDK);
-        appToError.put(Pair.create("install_uses_sdk.apk_r0_s5", R.raw.install_uses_sdk_r0_s5),
-                       PackageManager.INSTALL_FAILED_OLDER_SDK);
-
-        appToError.put(Pair.create("install_uses_sdk.apk_q0", R.raw.install_uses_sdk_q0),
-                       PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED);
-        appToError.put(Pair.create("install_uses_sdk.apk_q0_r0", R.raw.install_uses_sdk_q0_r0),
-                       PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED);
-        appToError.put(Pair.create("install_uses_sdk.apk_r_none", R.raw.install_uses_sdk_r_none),
-                       PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED);
-        appToError.put(Pair.create("install_uses_sdk.apk_0", R.raw.install_uses_sdk_0),
-                       PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED);
-
-        for (Map.Entry<Pair<String, Integer>, Integer> entry : appToError.entrySet()) {
-            String filename = entry.getKey().first;
-            int resId = entry.getKey().second;
-            int result = entry.getValue();
-            try {
-                parsePackage(filename, resId, x -> x);
-                expect.withMessage("Expected parsing error %d from %s", result, filename).fail();
-            } catch (PackageManagerException expected) {
-                expect.that(expected.error).isEqualTo(result);
-            }
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
deleted file mode 100644
index 9626fc3..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
+++ /dev/null
@@ -1,128 +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.pm.parsing
-
-import android.annotation.RawRes
-import android.content.Context
-import com.android.server.pm.pkg.parsing.ParsingPackage
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils
-import android.content.pm.parsing.result.ParseResult
-import android.platform.test.annotations.Presubmit
-import androidx.test.InstrumentationRegistry
-import com.android.frameworks.servicestests.R
-import com.android.server.pm.parsing.pkg.ParsedPackage
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TemporaryFolder
-
-/**
- * There are 2 known errors when parsing a manifest that were promoted to true failures in R:
- * 1. Missing an <application> or <instrumentation> tag
- * 2. An empty string action/category in an intent-filter
- *
- * This verifies these failures when the APK targets R.
- */
-@Presubmit
-class PackageParsingDeferErrorTest {
-
-    companion object {
-        private const val TEST_ACTIVITY =
-                "com.android.servicestests.pm.parsing.test.TestActivity"
-        private const val TEST_ACTION =
-                "com.android.servicestests.pm.parsing.test.TEST_ACTION"
-        private const val TEST_CATEGORY =
-                "com.android.servicestests.pm.parsing.test.TEST_CATEGORY"
-        private const val TEST_PERMISSION =
-                "com.android.servicestests.pm.parsing.missingapp.TEST_PERMISSION"
-    }
-
-    private val context: Context = InstrumentationRegistry.getContext()
-
-    @get:Rule
-    val tempFolder = TemporaryFolder(context.filesDir)
-
-    @Test
-    fun emptyIntentFilterActionSdkQ() {
-        val result = parseFile(R.raw.PackageParsingTestAppEmptyActionSdkQ)
-        assertWithMessage(result.errorMessage).that(result.isError).isFalse()
-        val activities = result.result.activities
-        // 2 because of AppDetailsActivity
-        assertThat(activities).hasSize(2)
-        val first = activities.first()
-        assertThat(first.name).isEqualTo(TEST_ACTIVITY)
-        val intents = first.intents
-        assertThat(intents).hasSize(1)
-        val intentFilter = intents.first().intentFilter
-        assertThat(intentFilter.hasCategory(TEST_CATEGORY)).isTrue()
-        assertThat(intentFilter.hasAction(TEST_ACTION)).isTrue()
-    }
-
-    @Test
-    fun emptyIntentFilterActionSdkR() {
-        val result = parseFile(R.raw.PackageParsingTestAppEmptyActionSdkR)
-        assertThat(result.isError).isTrue()
-    }
-
-    @Test
-    fun emptyIntentFilterCategorySdkQ() {
-        val result = parseFile(R.raw.PackageParsingTestAppEmptyCategorySdkQ)
-        assertWithMessage(result.errorMessage).that(result.isError).isFalse()
-        val activities = result.result.activities
-        // 2 because of AppDetailsActivity
-        assertThat(activities).hasSize(2)
-        val first = activities.first()
-        assertThat(first.name).isEqualTo(TEST_ACTIVITY)
-        val intents = first.intents
-        assertThat(intents).hasSize(1)
-        val intentFilter = intents.first().intentFilter
-        assertThat(intentFilter.hasAction(TEST_ACTION)).isTrue()
-    }
-
-    @Test
-    fun emptyIntentFilterCategorySdkR() {
-        val result = parseFile(R.raw.PackageParsingTestAppEmptyCategorySdkR)
-        assertThat(result.isError).isTrue()
-    }
-
-    @Test
-    fun missingAppTagSdkQ() {
-        val result = parseFile(R.raw.PackageParsingTestAppMissingAppSdkQ)
-        assertWithMessage(result.errorMessage).that(result.isError).isFalse()
-        val permissions = result.result.permissions
-        assertThat(permissions).hasSize(1)
-        assertThat(permissions.first().name).isEqualTo(TEST_PERMISSION)
-    }
-
-    @Test
-    fun missingAppTagSdkR() {
-        val result = parseFile(R.raw.PackageParsingTestAppMissingAppSdkR)
-        assertThat(result.isError).isTrue()
-    }
-
-    private fun parseFile(@RawRes id: Int): ParseResult<ParsedPackage> {
-        val file = tempFolder.newFile()
-        context.resources.openRawResource(id).use { input ->
-            file.outputStream().use { output ->
-                input.copyTo(output)
-            }
-        }
-        return ParsingPackageUtils.parseDefaultOneTime(file, 0 /*flags*/, emptyList(),
-                false /*collectCertificates*/)
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 04ba7d3..261156611 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -65,6 +65,7 @@
     private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor = ArgumentCaptor.forClass(
             DeviceState[].class);
     private final ArgumentCaptor<Integer> mIntegerCaptor = ArgumentCaptor.forClass(Integer.class);
+    private static final int MAX_HINGE_ANGLE_EXCLUSIVE = 360;
 
     private Context mContext;
     private SensorManager mSensorManager;
@@ -268,11 +269,7 @@
         assertEquals(1, mIntegerCaptor.getValue().intValue());
     }
 
-    @Test
-    public void create_sensor() throws Exception {
-        Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
-        when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
-
+    private DeviceStateProviderImpl create_sensorBasedProvider(Sensor sensor) {
         String configString = "<device-state-config>\n"
                 + "    <device-state>\n"
                 + "        <identifier>1</identifier>\n"
@@ -310,14 +307,22 @@
                 + "                <name>" + sensor.getName() + "</name>\n"
                 + "                <value>\n"
                 + "                    <min-inclusive>180</min-inclusive>\n"
+                + "                    <max>" + MAX_HINGE_ANGLE_EXCLUSIVE + "</max>\n"
                 + "                </value>\n"
                 + "            </sensor>\n"
                 + "        </conditions>\n"
                 + "    </device-state>\n"
                 + "</device-state-config>\n";
         DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
-        DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
+        return DeviceStateProviderImpl.createFromConfig(mContext,
                 config);
+    }
+
+    @Test
+    public void create_sensor() throws Exception {
+        Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+        when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+        DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
 
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
@@ -371,6 +376,40 @@
     }
 
     @Test
+    public void test_invalidSensorValues() throws Exception {
+        // onStateChanged() should not be triggered by invalid sensor values.
+
+        Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+        when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+        DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
+
+        DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+        provider.setListener(listener);
+        Mockito.clearInvocations(listener);
+
+        // First, switch to a non-default state.
+        SensorEvent event1 = mock(SensorEvent.class);
+        event1.sensor = sensor;
+        FieldSetter.setField(event1, event1.getClass().getField("values"), new float[]{90});
+        provider.onSensorChanged(event1);
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(2, mIntegerCaptor.getValue().intValue());
+
+        Mockito.clearInvocations(listener);
+
+        // Then, send an invalid sensor event, verify that onStateChanged() is not triggered.
+        SensorEvent event2 = mock(SensorEvent.class);
+        event2.sensor = sensor;
+        FieldSetter.setField(event2, event2.getClass().getField("values"),
+                new float[]{MAX_HINGE_ANGLE_EXCLUSIVE});
+
+        provider.onSensorChanged(event2);
+
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
+    }
+
+    @Test
     public void create_invalidSensor() throws Exception {
         Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
         when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of());
@@ -417,9 +456,8 @@
                         new DeviceState(1, "CLOSED", 0 /* flags */),
                         new DeviceState(2, "HALF_OPENED", 0 /* flags */)
                 }, mDeviceStateArrayCaptor.getValue());
-        // onStateChanged() should be called because the provider could not find the sensor.
-        verify(listener).onStateChanged(mIntegerCaptor.capture());
-        assertEquals(1, mIntegerCaptor.getValue().intValue());
+        // onStateChanged() should not be called because the provider could not find the sensor.
+        verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
     }
 
     private static Sensor newSensor(String name, String type) throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
index e3ca170..b034b0d 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
@@ -21,7 +21,6 @@
 import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
 import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
 import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
-import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_VR;
 import static android.os.PowerManager.GO_TO_SLEEP_REASON_APPLICATION;
 import static android.os.PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN;
 import static android.os.PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD;
@@ -267,7 +266,6 @@
                 powerSaveState,
                 /* quiescent= */ false,
                 /* dozeAfterScreenOff= */ false,
-                /* vrModeEnabled= */ false,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ false,
                 /* waitForNegativeProximity= */ false);
@@ -308,7 +306,6 @@
                 powerSaveState,
                 /* quiescent= */ false,
                 /* dozeAfterScreenOff= */ false,
-                /* vrModeEnabled= */ false,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ false,
                 /* waitForNegativeProximity= */ false);
@@ -348,7 +345,6 @@
                 powerSaveState,
                 /* quiescent= */ false,
                 /* dozeAfterScreenOff= */ true,
-                /* vrModeEnabled= */ false,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ false,
                 /* waitForNegativeProximity= */ false);
@@ -387,7 +383,6 @@
                 powerSaveState,
                 /* quiescent= */ true,
                 /* dozeAfterScreenOff= */ true,
-                /* vrModeEnabled= */ false,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ false,
                 /* waitForNegativeProximity= */ false);
@@ -407,83 +402,6 @@
     }
 
     @Test
-    public void testUpdateWhileAsleep_VrModeEnabled() {
-        final boolean batterySaverEnabled = false;
-        float brightnessFactor = 0.3f;
-        PowerSaveState powerSaveState = new PowerSaveState.Builder()
-                .setBatterySaverEnabled(batterySaverEnabled)
-                .setBrightnessFactor(brightnessFactor)
-                .build();
-        mPowerGroup.sleepLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
-        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
-                /* useProximitySensor= */ true,
-                /* boostScreenBrightness= */ true,
-                /* dozeScreenStateOverride= */ Display.STATE_ON,
-                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
-                /* overrideDrawWakeLock= */ false,
-                powerSaveState,
-                /* quiescent= */ false,
-                /* dozeAfterScreenOff= */ true,
-                /* vrModeEnabled= */ true,
-                /* bootCompleted= */ true,
-                /* screenBrightnessBoostInProgress= */ false,
-                /* waitForNegativeProximity= */ false);
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
-                mPowerGroup.mDisplayPowerRequest;
-        assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
-        assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
-        assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
-        assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
-        assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
-        assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
-                PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
-        assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
-                brightnessFactor);
-    }
-
-    @Test
-    public void testUpdateWhileAwake_VrModeEnabled() {
-        final boolean batterySaverEnabled = false;
-        float brightnessFactor = 0.3f;
-        PowerSaveState powerSaveState = new PowerSaveState.Builder()
-                .setBatterySaverEnabled(batterySaverEnabled)
-                .setBrightnessFactor(brightnessFactor)
-                .build();
-        assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
-                /* useProximitySensor= */ true,
-                /* boostScreenBrightness= */ true,
-                /* dozeScreenStateOverride= */ Display.STATE_ON,
-                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
-                /* overrideDrawWakeLock= */ false,
-                powerSaveState,
-                /* quiescent= */ false,
-                /* dozeAfterScreenOff= */ true,
-                /* vrModeEnabled= */ true,
-                /* bootCompleted= */ true,
-                /* screenBrightnessBoostInProgress= */ false,
-                /* waitForNegativeProximity= */ false);
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
-                mPowerGroup.mDisplayPowerRequest;
-        assertThat(displayPowerRequest.policy).isEqualTo(POLICY_VR);
-        assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
-        assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
-        assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
-        assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
-        assertThat(displayPowerRequest.dozeScreenBrightness).isEqualTo(
-                PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        assertThat(displayPowerRequest.lowPowerMode).isEqualTo(batterySaverEnabled);
-        assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
-                brightnessFactor);
-    }
-
-    @Test
     public void testUpdateWhileAsleep_UpdatesDisplayPowerRequest() {
         final boolean batterySaverEnabled = false;
         float brightnessFactor = 0.3f;
@@ -503,7 +421,6 @@
                 powerSaveState,
                 /* quiescent= */ false,
                 /* dozeAfterScreenOff= */ false,
-                /* vrModeEnabled= */ false,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ false,
                 /* waitForNegativeProximity= */ false);
@@ -543,7 +460,6 @@
                 powerSaveState,
                 /* quiescent= */ false,
                 /* dozeAfterScreenOff= */ false,
-                /* vrModeEnabled= */ false,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ false,
                 /* waitForNegativeProximity= */ false);
@@ -581,7 +497,6 @@
                 powerSaveState,
                 /* quiescent= */ false,
                 /* dozeAfterScreenOff= */ false,
-                /* vrModeEnabled= */ false,
                 /* bootCompleted= */ false,
                 /* screenBrightnessBoostInProgress= */ false,
                 /* waitForNegativeProximity= */ false);
@@ -620,7 +535,6 @@
                 powerSaveState,
                 /* quiescent= */ false,
                 /* dozeAfterScreenOff= */ false,
-                /* vrModeEnabled= */ false,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ false,
                 /* waitForNegativeProximity= */ false);
@@ -658,7 +572,6 @@
                 powerSaveState,
                 /* quiescent= */ false,
                 /* dozeAfterScreenOff= */ false,
-                /* vrModeEnabled= */ false,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ true,
                 /* waitForNegativeProximity= */ false);
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index db2630e2..d7ff553 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -389,6 +389,18 @@
         mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
     }
 
+    private void setBatteryLevel(int batteryLevel) {
+        when(mBatteryManagerInternalMock.getBatteryLevel())
+                .thenReturn(batteryLevel);
+        mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+    }
+
+    private void setBatteryHealth(int batteryHealth) {
+        when(mBatteryManagerInternalMock.getBatteryHealth())
+                .thenReturn(batteryHealth);
+        mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+    }
+
     private void setAttentiveTimeout(int attentiveTimeoutMillis) {
         Settings.Secure.putInt(
                 mContextSpy.getContentResolver(), Settings.Secure.ATTENTIVE_TIMEOUT,
@@ -413,6 +425,12 @@
                 .thenReturn(disable);
     }
 
+    private void setDreamsBatteryLevelDrainConfig(int threshold) {
+        when(mResourcesSpy.getInteger(
+                com.android.internal.R.integer.config_dreamsBatteryLevelDrainCutoff)).thenReturn(
+                threshold);
+    }
+
     private void advanceTime(long timeMs) {
         mClock.fastForward(timeMs);
         mTestLooper.dispatchAll();
@@ -436,39 +454,6 @@
     }
 
     @Test
-    public void testGetDesiredScreenPolicy_WithVR() {
-        createService();
-        startSystem();
-        // Brighten up the screen
-        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
-                null, null);
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
-                DisplayPowerRequest.POLICY_BRIGHT);
-
-        // Move to VR
-        mService.setVrModeEnabled(true);
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
-                DisplayPowerRequest.POLICY_VR);
-
-        // Then take a nap
-        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
-                null, null);
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
-                DisplayPowerRequest.POLICY_OFF);
-
-        // Wake up to VR
-        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
-                null, null);
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
-                DisplayPowerRequest.POLICY_VR);
-
-        // And back to normal
-        mService.setVrModeEnabled(false);
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
-                DisplayPowerRequest.POLICY_BRIGHT);
-    }
-
-    @Test
     public void testWakefulnessAwake_InitialValue() {
         createService();
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
@@ -658,17 +643,52 @@
     }
 
     /**
-     * Tests that dreaming continues when undocking and configured to do so.
+     * Tests that dreaming stops when undocking and not configured to keep dreaming.
      */
     @Test
-    public void testWakefulnessDream_shouldKeepDreamingWhenUndocked() {
+    public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenNotConfigured() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+
         createService();
         startSystem();
 
-        when(mResourcesSpy.getBoolean(
-                com.android.internal.R.bool.config_keepDreamingWhenUndocking))
+        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+                dreamManagerStateListener.capture());
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(false);
+
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    /**
+     * Tests that dreaming continues when undocking and configured to do so.
+     */
+    @Test
+    public void testWakefulnessDream_shouldKeepDreamingWhenUndocked_whenConfigured() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
                 .thenReturn(true);
-        mService.readConfigurationLocked();
+
+        createService();
+        startSystem();
+
+        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+                dreamManagerStateListener.capture());
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(true);
 
         when(mBatteryManagerInternalMock.getPlugType())
                 .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
@@ -682,6 +702,37 @@
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
     }
 
+    /**
+     * Tests that dreaming stops when undocking while showing a dream that prevents it.
+     */
+    @Test
+    public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenDreamPrevents() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+
+        createService();
+        startSystem();
+
+        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+                dreamManagerStateListener.capture());
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(true);
+
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(false);
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
     @Test
     public void testWakefulnessDoze_goToSleep() {
         createService();
@@ -921,6 +972,41 @@
     }
 
     @Test
+    public void testBatteryDrainDuringDream() {
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+        setMinimumScreenOffTimeoutConfig(100);
+        setDreamsBatteryLevelDrainConfig(5);
+        createService();
+        startSystem();
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        setBatteryLevel(100);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        advanceTime(10); // Allow async calls to happen
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        setBatteryLevel(90);
+        advanceTime(10); // Allow async calls to happen
+        assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
+
+        // If battery overheat protection is enabled, we shouldn't count battery drain
+        setBatteryHealth(BatteryManager.BATTERY_HEALTH_OVERHEAT);
+        setBatteryLevel(70);
+        advanceTime(10); // Allow async calls to happen
+        assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
+    }
+
+    @Test
     public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() {
         final String suspendBlockerName = "PowerManagerService.Display";
         final String tag = "acq_causes_wakeup";
@@ -1668,10 +1754,8 @@
         when(mNativeWrapperMock.nativeSetPowerMode(anyInt(), anyBoolean())).thenReturn(true);
 
         mService.getBinderServiceInstance().setPowerMode(Mode.LAUNCH, true);
-        mService.getBinderServiceInstance().setPowerMode(Mode.VR, false);
 
         verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.LAUNCH), eq(true));
-        verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.VR), eq(false));
     }
 
     @Test
@@ -1895,6 +1979,50 @@
     }
 
     @Test
+    public void testMultiDisplay_defaultDozing_addNewDisplayDefaultGoesBackToDoze() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+
+        forceDozing();
+        advanceTime(500);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
+
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+        advanceTime(500);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+        verify(mDreamManagerInternalMock, times(2)).startDream(eq(true), anyString());
+    }
+
+    @Test
     public void testLastSleepTime_notUpdatedWhenDreaming() {
         createService();
         startSystem();
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 397770b..dcbdcdc 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -42,6 +42,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.IHintSession;
+import android.os.PerformanceHintManager;
 import android.os.Process;
 
 import com.android.server.FgThread;
@@ -250,6 +251,32 @@
     }
 
     @Test
+    public void testSendHint() throws Exception {
+        HintManagerService service = createService();
+        IBinder token = new Binder();
+
+        AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+                .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+
+        a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
+        verify(mNativeWrapperMock, times(1)).halSendHint(anyLong(),
+                eq(PerformanceHintManager.Session.CPU_LOAD_RESET));
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            a.sendHint(-1);
+        });
+
+        reset(mNativeWrapperMock);
+        // Set session to background, then the duration would not be updated.
+        service.mUidObserver.onUidStateChanged(
+                a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+        FgThread.getHandler().runWithScissors(() -> { }, 500);
+        assertFalse(a.updateHintAllowed());
+        a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
+        verify(mNativeWrapperMock, never()).halSendHint(anyLong(), anyInt());
+    }
+
+    @Test
     public void testDoHintInBackground() throws Exception {
         HintManagerService service = createService();
         IBinder token = new Binder();
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 1815e2a..7c1962c 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.annotation.XmlRes;
 import android.content.Context;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
@@ -50,6 +51,7 @@
                     .powerProfileModeledOnly()
                     .includePowerModels()
                     .build();
+    private final Context mContext;
 
     private final PowerProfile mPowerProfile;
     private final MockClock mMockClock = new MockClock();
@@ -67,14 +69,19 @@
     }
 
     public BatteryUsageStatsRule(long currentTime, File historyDir) {
-        Context context = InstrumentationRegistry.getContext();
-        mPowerProfile = spy(new PowerProfile(context, true /* forTest */));
+        mContext = InstrumentationRegistry.getContext();
+        mPowerProfile = spy(new PowerProfile(mContext, true /* forTest */));
         mMockClock.currentTime = currentTime;
         mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir);
         mBatteryStats.setPowerProfile(mPowerProfile);
         mBatteryStats.onSystemReady();
     }
 
+    public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {
+        mPowerProfile.forceInitForTesting(mContext, xmlId);
+        return this;
+    }
+
     public BatteryUsageStatsRule setAveragePower(String key, double value) {
         when(mPowerProfile.getAveragePower(key)).thenReturn(value);
         when(mPowerProfile.getAveragePowerOrDefault(eq(key), anyDouble())).thenReturn(value);
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/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
index 8262fca..5bc73bd 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.usage.NetworkStatsManager;
+import android.hardware.radio.V1_5.AccessNetwork;
 import android.net.NetworkCapabilities;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
@@ -34,6 +35,8 @@
 import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
+import android.telephony.ActivityStatsTechSpecificInfo;
+import android.telephony.CellSignalStrength;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
 import android.telephony.ServiceState;
@@ -43,7 +46,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.PowerProfile;
+import com.android.frameworks.servicestests.R;
 
 import com.google.common.collect.Range;
 
@@ -52,28 +55,25 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+import java.util.ArrayList;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @SuppressWarnings("GuardedBy")
 public class MobileRadioPowerCalculatorTest {
     private static final double PRECISION = 0.00001;
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
     @Mock
     NetworkStatsManager mNetworkStatsManager;
 
     @Rule
-    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
-            .setAveragePowerUnspecified(PowerProfile.POWER_RADIO_ACTIVE)
-            .setAveragePowerUnspecified(PowerProfile.POWER_RADIO_ON)
-            .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE, 360.0)
-            .setAveragePower(PowerProfile.POWER_RADIO_SCANNING, 720.0)
-            .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX, 1440.0)
-            .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX,
-                    new double[]{720.0, 1080.0, 1440.0, 1800.0, 2160.0})
-            .initMeasuredEnergyStatsLocked();
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
 
     @Test
     public void testCounterBasedModel() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator)
+                .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
         // Scan for a cell
@@ -85,9 +85,15 @@
         stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
                 5000, 5000);
 
+        ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+        CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        perRatCellStrength.add(gsmSignalStrength);
+
         // Note cell signal strength
         SignalStrength signalStrength = mock(SignalStrength.class);
-        when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
         stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
 
         stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
@@ -97,10 +103,31 @@
         stats.noteNetworkInterfaceForTransports("cellular",
                 new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
 
+        // Spend some time in each signal strength level. It doesn't matter how long.
+        // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+        // timers.
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                9_500_000_000L, APP_UID2, 9500, 9500);
+
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
         // Note application network activity
         NetworkStats networkStats = new NetworkStats(10000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
         mStatsRule.setNetworkStats(networkStats);
 
         ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
@@ -108,34 +135,331 @@
         stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
                 mNetworkStatsManager);
 
-        mStatsRule.setTime(12_000, 12_000);
+        mStatsRule.setTime(10_000, 10_000);
 
         MobileRadioPowerCalculator calculator =
                 new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
 
         mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
 
-        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
-        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(0.8);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // +  360 mA * 3000 ms (idle drain rate * idle duration)
+        // +   70 mA * 2000 ms (sleep drain rate * sleep duration)
+        // _________________
+        // =    4604000 mA-ms or 1.27888 mA-h
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(2.2444);
+                .isWithin(PRECISION).of(1.27888);
         assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // _________________
+        // =    3384000 mA-ms or 0.94 mA-h
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(0.8);
+                .isWithin(PRECISION).of(0.94);
         assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // 3/4 of total packets were sent by APP_UID so 75% of total
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.705);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // Rest should go to the other app
+        UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.235);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+    }
+
+    @Test
+    public void testCounterBasedModel_multipleDefinedRat() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator_multiactive)
+                .initMeasuredEnergyStatsLocked();
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+        // Scan for a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+                TelephonyManager.SIM_STATE_READY,
+                2000, 2000);
+
+        // Found a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+                5000, 5000);
+
+        ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+        CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        perRatCellStrength.add(gsmSignalStrength);
+
+        // Note cell signal strength
+        SignalStrength signalStrength = mock(SignalStrength.class);
+        when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                8_000_000_000L, APP_UID, 8000, 8000);
+
+        // Note established network
+        stats.noteNetworkInterfaceForTransports("cellular",
+                new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+        // Spend some time in each signal strength level. It doesn't matter how long.
+        // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+        // timers.
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                9_500_000_000L, APP_UID2, 9500, 9500);
+
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
+        // Note application network activity
+        NetworkStats networkStats = new NetworkStats(10000, 1)
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
+        mStatsRule.setNetworkStats(networkStats);
+
+        ActivityStatsTechSpecificInfo cdmaInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.CDMA2000, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+                new int[]{10, 11, 12, 13, 14}, 15);
+        ActivityStatsTechSpecificInfo lteInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.EUTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+                new int[]{20, 21, 22, 23, 24}, 25);
+        ActivityStatsTechSpecificInfo nrLowFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
+                new int[]{30, 31, 32, 33, 34}, 35);
+        ActivityStatsTechSpecificInfo nrMidFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
+                new int[]{40, 41, 42, 43, 44}, 45);
+        ActivityStatsTechSpecificInfo nrHighFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
+                new int[]{50, 51, 52, 53, 54}, 55);
+        ActivityStatsTechSpecificInfo nrMmwaveFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
+                new int[]{60, 61, 62, 63, 64}, 65);
+
+        ActivityStatsTechSpecificInfo[] ratInfos =
+                new ActivityStatsTechSpecificInfo[]{cdmaInfo, lteInfo, nrLowFreqInfo, nrMidFreqInfo,
+                        nrHighFreqInfo, nrMmwaveFreqInfo};
+
+        ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, ratInfos);
+        stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
+                mNetworkStatsManager);
+
+        mStatsRule.setTime(10_000, 10_000);
+
+        MobileRadioPowerCalculator calculator =
+                new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        // CDMA2000 [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [720, 1080, 1440, 1800, 2160, 1440] mA . [10, 11, 12, 13, 14, 15] ms = 111600 mA-ms
+        // LTE [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [800, 1200, 1600, 2000, 2400, 2000] mA . [20, 21, 22, 23, 24, 25] ms = 230000 mA-ms
+        // 5G Low Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        // (nrFrequency="LOW" values was not defined so fall back to nrFrequency="DEFAULT")
+        //   [999, 1333, 1888, 2222, 2666, 2222] mA . [30, 31, 32, 33, 34, 35] ms = 373449 mA-ms
+        // 5G Mid Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        // (nrFrequency="MID" values was not defined so fall back to nrFrequency="DEFAULT")
+        //   [999, 1333, 1888, 2222, 2666, 2222] mA . [40, 41, 42, 43, 44, 45] ms = 486749 mA-ms
+        // 5G High Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [1818, 2727, 3636, 4545, 5454, 2727] mA . [50, 51, 52, 53, 54, 55] ms = 1104435 mA-ms
+        // 5G Mmwave Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [2345, 3456, 4567, 5678, 6789, 3456] mA . [60, 61, 62, 63, 64, 65] ms = 1651520 mA-ms
+        // _________________
+        // =    3957753 mA-ms or 1.09938 mA-h active consumption
+        //
+        // Idle drain rate * idle duration
+        //   360 mA * 3000 ms = 1080000 mA-ms
+        // Sleep drain rate * sleep duration
+        //   70 mA * 2000 ms = 140000 mA-ms
+        // _________________
+        // =    5177753 mA-ms or 1.43826 mA-h total consumption
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.43826);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // _________________
+        // =    3384000 mA-ms or 0.94 mA-h
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.09938);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // 3/4 of total packets were sent by APP_UID so 75% of total
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.82453);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // Rest should go to the other app
+        UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.27484);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+    }
+
+    @Test
+    public void testCounterBasedModel_legacyPowerProfile() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+                .initMeasuredEnergyStatsLocked();
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+        // Scan for a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+                TelephonyManager.SIM_STATE_READY,
+                2000, 2000);
+
+        // Found a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+                5000, 5000);
+
+        ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+        CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        perRatCellStrength.add(gsmSignalStrength);
+
+        // Note cell signal strength
+        SignalStrength signalStrength = mock(SignalStrength.class);
+        when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                8_000_000_000L, APP_UID, 8000, 8000);
+
+        // Note established network
+        stats.noteNetworkInterfaceForTransports("cellular",
+                new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+        // Spend some time in each signal strength level. It doesn't matter how long.
+        // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+        // timers.
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                9_500_000_000L, APP_UID2, 9500, 9500);
+
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
+        // Note application network activity
+        NetworkStats networkStats = new NetworkStats(10000, 1)
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
+        mStatsRule.setNetworkStats(networkStats);
+
+        ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+                new int[]{100, 200, 300, 400, 500}, 600);
+        stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
+                mNetworkStatsManager);
+
+        mStatsRule.setTime(10_000, 10_000);
+
+        MobileRadioPowerCalculator calculator =
+                new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // +  360 mA * 3000 ms (idle drain rate * idle duration)
+        // +   70 mA * 2000 ms (sleep drain rate * sleep duration)
+        // _________________
+        // =    4604000 mA-ms or 1.27888 mA-h
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.27888);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // _________________
+        // =    3384000 mA-ms or 0.94 mA-h
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.94);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // 3/4 of total packets were sent by APP_UID so 75% of total
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.705);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // Rest should go to the other app
+        UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.235);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
     public void testTimerBasedModel_byProcessState() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+                .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
         BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
 
@@ -148,13 +472,19 @@
                 TelephonyManager.SIM_STATE_READY,
                 2000, 2000);
 
+        ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+        CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        perRatCellStrength.add(gsmSignalStrength);
+
         // Found a cell
         stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
                 5000, 5000);
 
         // Note cell signal strength
         SignalStrength signalStrength = mock(SignalStrength.class);
-        when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
         stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
 
         stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
@@ -164,10 +494,25 @@
         stats.noteNetworkInterfaceForTransports("cellular",
                 new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
 
+        // Spend some time in each signal strength level. It doesn't matter how long.
+        // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+        // timers.
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
         // Note application network activity
         mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
 
         stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 10000, 10000,
                 mNetworkStatsManager);
@@ -177,7 +522,7 @@
 
         mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
 
         stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 12000, 12000,
                 mNetworkStatsManager);
@@ -199,6 +544,8 @@
         MobileRadioPowerCalculator calculator =
                 new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
 
+        mStatsRule.setTime(12_000, 12_000);
+
         mStatsRule.apply(new BatteryUsageStatsQuery.Builder()
                 .powerProfileModeledOnly()
                 .includePowerModels()
@@ -217,6 +564,10 @@
                 BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
                 BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
 
+
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.6);
+
         assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(1.2);
         assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.4);
         assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0);
@@ -224,6 +575,8 @@
 
     @Test
     public void testMeasuredEnergyBasedModel() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+                .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
         // Scan for a cell
@@ -264,12 +617,6 @@
 
         mStatsRule.apply(calculator);
 
-        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
-        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(1.53934);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
-
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
         // 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
@@ -282,10 +629,18 @@
                 .isWithin(PRECISION).of(1.53934);
         assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.53934);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
 
     @Test
     public void testMeasuredEnergyBasedModel_byProcessState() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+                .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
         BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
 
@@ -317,7 +672,7 @@
         // Note application network activity
         mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
 
         stats.noteModemControllerActivity(null, 10_000_000, 10000, 10000, mNetworkStatsManager);
 
@@ -326,7 +681,7 @@
 
         mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
 
         stats.noteModemControllerActivity(null, 15_000_000, 12000, 12000, mNetworkStatsManager);
 
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index 83139b0..5a482fc 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -44,6 +44,7 @@
 import android.content.Intent;
 import android.content.om.IOverlayManager;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
@@ -669,7 +670,10 @@
     }
 
     @Test
-    public void testSetNavBarMode_setsModeKids() throws RemoteException {
+    public void testSetNavBarMode_setsModeKids() throws Exception {
+        mContext.setMockPackageManager(mPackageManager);
+        when(mPackageManager.getPackageInfo(anyString(),
+                any(PackageManager.PackageInfoFlags.class))).thenReturn(new PackageInfo());
         int navBarModeKids = StatusBarManager.NAV_BAR_MODE_KIDS;
 
         mStatusBarManagerService.setNavBarMode(navBarModeKids);
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..0d6bb8a 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -24,6 +24,7 @@
 import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_GEO;
 import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_MANUAL;
 import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_TELEPHONY;
+import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_UNKNOWN;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -51,13 +52,14 @@
 
     /**
      * Tests {@link TimeCapabilitiesAndConfig} behavior in different scenarios when auto detection
-     * is supported (and geo detection is supported)
+     * is supported (both telephony and geo detection are supported)
      */
     @Test
     @Parameters({ "true,true", "true,false", "false,true", "false,false" })
-    public void test_autoDetectionSupported_capabilitiesAndConfiguration(
+    public void test_telephonyAndGeoSupported_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)
@@ -71,21 +73,20 @@
 
         boolean userRestrictionsExpected = !(userConfigAllowed || bypassUserPolicyChecks);
 
-        // Auto-detection enabled.
+        // Auto-detection enabled, location enabled.
         {
-            ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
                     .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(true)
                     .build();
-            assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
-            assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
-            assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
-            assertTrue(autoOnConfig.isGeoDetectionExecutionEnabled());
-            assertEquals(DETECTION_MODE_GEO, autoOnConfig.getDetectionMode());
+            assertTrue(config.getAutoDetectionEnabledSetting());
+            assertTrue(config.getLocationEnabledSetting());
+            assertTrue(config.getGeoDetectionEnabledSetting());
+            assertTrue(config.getAutoDetectionEnabledBehavior());
+            assertTrue(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_GEO, config.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -101,26 +102,58 @@
             assertEquals(CAPABILITY_POSSESSED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = config.asConfiguration();
+            assertTrue(configuration.isAutoDetectionEnabled());
+            assertTrue(configuration.isGeoDetectionEnabled());
+        }
+
+        // Auto-detection enabled, location disabled.
+        {
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(false)
+                    .build();
+            assertTrue(config.getAutoDetectionEnabledSetting());
+            assertFalse(config.getLocationEnabledSetting());
+            assertTrue(config.getGeoDetectionEnabledSetting());
+            assertTrue(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_TELEPHONY, config.getDetectionMode());
+
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
+            if (userRestrictionsExpected) {
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getSetManualTimeZoneCapability());
+            } else {
+                assertEquals(CAPABILITY_POSSESSED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_NOT_APPLICABLE,
+                        capabilities.getSetManualTimeZoneCapability());
+            }
+            // This has user privacy implications so it is not restricted in the same way as others.
+            assertEquals(CAPABILITY_NOT_APPLICABLE,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+
+            TimeZoneConfiguration configuration = config.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
 
         // Auto-detection disabled.
         {
-            ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
                     .setAutoDetectionEnabledSetting(false)
                     .build();
-            assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
-            assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
-            assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
-            assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
-            assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
+            assertFalse(config.getAutoDetectionEnabledSetting());
+            assertTrue(config.getLocationEnabledSetting());
+            assertTrue(config.getGeoDetectionEnabledSetting());
+            assertFalse(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, config.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -136,7 +169,7 @@
             assertEquals(CAPABILITY_NOT_APPLICABLE,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = config.asConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -150,7 +183,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)
@@ -164,21 +198,20 @@
 
         boolean userRestrictionsExpected = !(userConfigAllowed || bypassUserPolicyChecks);
 
-        // Auto-detection enabled.
+        // Auto-detection enabled, location enabled.
         {
-            ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
                     .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(true)
                     .build();
-            assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
-            assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
-            assertFalse(autoOnConfig.getAutoDetectionEnabledBehavior());
-            assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
-            assertEquals(DETECTION_MODE_MANUAL, autoOnConfig.getDetectionMode());
+            assertTrue(config.getAutoDetectionEnabledSetting());
+            assertTrue(config.getLocationEnabledSetting());
+            assertTrue(config.getGeoDetectionEnabledSetting());
+            assertFalse(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, config.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
             if (userRestrictionsExpected) {
@@ -189,7 +222,36 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = config.asConfiguration();
+            assertTrue(configuration.isAutoDetectionEnabled());
+            assertTrue(configuration.isGeoDetectionEnabled());
+        }
+
+        // Auto-detection enabled, location disabled.
+        {
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(false)
+                    .build();
+            assertTrue(config.getAutoDetectionEnabledSetting());
+            assertFalse(config.getLocationEnabledSetting());
+            assertTrue(config.getGeoDetectionEnabledSetting());
+            assertFalse(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, config.getDetectionMode());
+
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureAutoDetectionEnabledCapability());
+            if (userRestrictionsExpected) {
+                assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSetManualTimeZoneCapability());
+            } else {
+                assertEquals(CAPABILITY_POSSESSED, capabilities.getSetManualTimeZoneCapability());
+            }
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+
+            TimeZoneConfiguration configuration = config.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -205,10 +267,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 +279,7 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -227,13 +287,14 @@
 
     /**
      * Tests {@link TimeCapabilitiesAndConfig} behavior in different scenarios when auto detection
-     * is supported (and geo detection is not supported).
+     * is supported (telephony only).
      */
     @Test
     @Parameters({ "true,true", "true,false", "false,true", "false,false" })
-    public void test_geoDetectNotSupported_capabilitiesAndConfiguration(
+    public void test_onlyTelephonySupported_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)
@@ -249,19 +310,16 @@
 
         // Auto-detection enabled.
         {
-            ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
                     .setAutoDetectionEnabledSetting(true)
                     .build();
-            assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
-            assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
-            assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
-            assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
-            assertEquals(DETECTION_MODE_TELEPHONY, autoOnConfig.getDetectionMode());
+            assertTrue(config.getAutoDetectionEnabledSetting());
+            assertTrue(config.getGeoDetectionEnabledSetting());
+            assertTrue(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_TELEPHONY, config.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -276,26 +334,23 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = config.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
 
         // Auto-detection disabled.
         {
-            ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
                     .setAutoDetectionEnabledSetting(false)
                     .build();
-            assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
-            assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
-            assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
-            assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
-            assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
+            assertFalse(config.getAutoDetectionEnabledSetting());
+            assertTrue(config.getGeoDetectionEnabledSetting());
+            assertFalse(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, config.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -308,15 +363,141 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = config.asConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
     }
 
+    /**
+     * Tests {@link TimeCapabilitiesAndConfig} behavior in different scenarios when auto detection
+     * is supported (only geo detection)
+     */
+    @Test
+    @Parameters({ "true,true", "true,false", "false,true", "false,false" })
+    public void test_onlyGeoSupported_capabilitiesAndConfiguration(
+            boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
+                .setUserConfigAllowed(userConfigAllowed)
+                .setTelephonyDetectionFeatureSupported(false)
+                .setGeoDetectionFeatureSupported(true)
+                .setGeoDetectionRunInBackgroundEnabled(false)
+                .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
+                .setAutoDetectionEnabledSetting(true)
+                .setLocationEnabledSetting(true)
+                .setGeoDetectionEnabledSetting(false)
+                .build();
+
+        boolean userRestrictionsExpected = !(userConfigAllowed || bypassUserPolicyChecks);
+
+        // Auto-detection enabled, location enabled.
+        {
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(true)
+                    .build();
+            assertTrue(config.getAutoDetectionEnabledSetting());
+            assertTrue(config.getLocationEnabledSetting());
+            assertFalse(config.getGeoDetectionEnabledSetting());
+            assertTrue(config.getAutoDetectionEnabledBehavior());
+            assertTrue(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_GEO, config.getDetectionMode());
+
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
+            if (userRestrictionsExpected) {
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getSetManualTimeZoneCapability());
+            } else {
+                assertEquals(CAPABILITY_POSSESSED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_NOT_APPLICABLE,
+                        capabilities.getSetManualTimeZoneCapability());
+            }
+            // This capability is always "not supported" if geo detection is the only mechanism.
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+
+            TimeZoneConfiguration configuration = config.asConfiguration();
+            assertTrue(configuration.isAutoDetectionEnabled());
+            assertFalse(configuration.isGeoDetectionEnabled());
+        }
+
+        // Auto-detection enabled, location disabled.
+        {
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(false)
+                    .build();
+            assertTrue(config.getAutoDetectionEnabledSetting());
+            assertFalse(config.getLocationEnabledSetting());
+            assertFalse(config.getGeoDetectionEnabledSetting());
+            assertTrue(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_UNKNOWN, config.getDetectionMode());
+
+            TimeZoneCapabilities capabilities = config.asCapabilities(bypassUserPolicyChecks);
+            if (userRestrictionsExpected) {
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getSetManualTimeZoneCapability());
+            } else {
+                assertEquals(CAPABILITY_POSSESSED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_NOT_APPLICABLE,
+                        capabilities.getSetManualTimeZoneCapability());
+            }
+            // This capability is always "not supported" if geo detection is the only mechanism.
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+
+            TimeZoneConfiguration configuration = config.asConfiguration();
+            assertTrue(configuration.isAutoDetectionEnabled());
+            assertFalse(configuration.isGeoDetectionEnabled());
+        }
+
+        // Auto-detection disabled.
+        {
+            ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabledSetting(false)
+                    .build();
+            assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
+            assertFalse(autoOffConfig.getGeoDetectionEnabledSetting());
+            assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
+            assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
+
+            TimeZoneCapabilities capabilities =
+                    autoOffConfig.asCapabilities(bypassUserPolicyChecks);
+            if (userRestrictionsExpected) {
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_NOT_ALLOWED,
+                        capabilities.getSetManualTimeZoneCapability());
+            } else {
+                assertEquals(CAPABILITY_POSSESSED,
+                        capabilities.getConfigureAutoDetectionEnabledCapability());
+                assertEquals(CAPABILITY_POSSESSED,
+                        capabilities.getSetManualTimeZoneCapability());
+            }
+            // This capability is always "not supported" if geo detection is the only mechanism.
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+
+            TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
+            assertFalse(configuration.isAutoDetectionEnabled());
+            assertFalse(configuration.isGeoDetectionEnabled());
+        }
+    }
+
     @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)
@@ -328,10 +509,14 @@
         assertTrue(config.isTelephonyFallbackSupported());
     }
 
-    /** Tests when {@link ConfigurationInternal#getGeoDetectionRunInBackgroundEnabled()} is true. */
+    /**
+     * Tests when {@link ConfigurationInternal#getGeoDetectionRunInBackgroundEnabledSetting()}
+     * 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..1e72369 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -15,16 +15,39 @@
  */
 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.TimeZoneDetectorStatus;
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.util.IndentingPrintWriter;
 
+import java.util.ArrayList;
+import java.util.Objects;
+
 public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
 
+    private final FakeServiceConfigAccessor mFakeServiceConfigAccessor =
+            new FakeServiceConfigAccessor();
+    private final ArrayList<StateChangeListener> mListeners = new ArrayList<>();
     private TimeZoneState mTimeZoneState;
+    private TimeZoneDetectorStatus mStatus;
+
+    public FakeTimeZoneDetectorStrategy() {
+        mFakeServiceConfigAccessor.addConfigurationInternalChangeListener(
+                this::notifyChangeListeners);
+    }
+
+    public void initializeConfigurationAndStatus(
+            ConfigurationInternal configuration, TimeZoneDetectorStatus status) {
+        mFakeServiceConfigAccessor.initializeCurrentUserConfiguration(configuration);
+        mStatus = Objects.requireNonNull(status);
+    }
 
     @Override
     public boolean confirmTimeZone(String timeZoneId) {
@@ -32,6 +55,37 @@
     }
 
     @Override
+    public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(int userId,
+            boolean bypassUserPolicyChecks) {
+        ConfigurationInternal configurationInternal =
+                mFakeServiceConfigAccessor.getCurrentUserConfigurationInternal();
+        assertEquals("Multi-user testing not supported",
+                configurationInternal.getUserId(), userId);
+        return new TimeZoneCapabilitiesAndConfig(
+                mStatus,
+                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;
     }
@@ -42,7 +96,7 @@
     }
 
     @Override
-    public void suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion timeZoneSuggestion) {
+    public void handleLocationAlgorithmEvent(LocationAlgorithmEvent locationAlgorithmEvent) {
     }
 
     @Override
@@ -57,7 +111,7 @@
     }
 
     @Override
-    public void enableTelephonyTimeZoneFallback() {
+    public void enableTelephonyTimeZoneFallback(String reason) {
         throw new UnsupportedOperationException();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
index 0f667b3..602842a 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
@@ -16,13 +16,8 @@
 
 package com.android.server.timezonedetector;
 
-import static com.android.server.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
-
-import android.os.ShellCommand;
 
 import org.junit.Test;
 
@@ -49,11 +44,6 @@
         assertEquals(certain1v1, certain1v2);
         assertEquals(certain1v2, certain1v1);
 
-        // DebugInfo must not be considered in equals().
-        certain1v1.addDebugInfo("Debug info 1");
-        certain1v2.addDebugInfo("Debug info 2");
-        assertEquals(certain1v1, certain1v2);
-
         long time2 = 2222L;
         GeolocationTimeZoneSuggestion certain2 =
                 GeolocationTimeZoneSuggestion.createCertainSuggestion(time2, ARBITRARY_ZONE_IDS1);
@@ -71,40 +61,4 @@
         assertNotEquals(certain1v1, certain3);
         assertNotEquals(certain3, certain1v1);
     }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testParseCommandLineArg_noZoneIdsArg() {
-        ShellCommand testShellCommand =
-                createShellCommandWithArgsAndOptions(Collections.emptyList());
-        GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
-    }
-
-    @Test
-    public void testParseCommandLineArg_zoneIdsUncertain() {
-        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--zone_ids UNCERTAIN");
-        assertNull(GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand)
-                .getZoneIds());
-    }
-
-    @Test
-    public void testParseCommandLineArg_zoneIdsEmpty() {
-        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions("--zone_ids EMPTY");
-        assertEquals(Collections.emptyList(),
-                GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand).getZoneIds());
-    }
-
-    @Test
-    public void testParseCommandLineArg_zoneIdsPresent() {
-        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
-                "--zone_ids Europe/London,Europe/Paris");
-        assertEquals(Arrays.asList("Europe/London", "Europe/Paris"),
-                GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand).getZoneIds());
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testParseCommandLineArg_unknownArgument() {
-        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions("--bad_arg 0");
-        GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java
new file mode 100644
index 0000000..4c14014
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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 com.android.server.timezonedetector;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
+
+import static com.android.server.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.os.ShellCommand;
+import android.service.timezone.TimeZoneProviderStatus;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+public class LocationAlgorithmEventTest {
+
+    public static final TimeZoneProviderStatus ARBITRARY_PROVIDER_STATUS =
+            new TimeZoneProviderStatus.Builder()
+                    .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+                    .build();
+
+    public static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_ALGORITHM_STATUS =
+            new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                    PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_STATUS,
+                    PROVIDER_STATUS_NOT_PRESENT, null);
+
+    @Test
+    public void testEquals() {
+        GeolocationTimeZoneSuggestion suggestion1 =
+                GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L);
+        LocationTimeZoneAlgorithmStatus status1 = new LocationTimeZoneAlgorithmStatus(
+                DETECTION_ALGORITHM_STATUS_RUNNING,
+                PROVIDER_STATUS_NOT_PRESENT, null, PROVIDER_STATUS_NOT_PRESENT, null);
+        LocationAlgorithmEvent event1v1 = new LocationAlgorithmEvent(status1, suggestion1);
+        assertEqualsAndHashCode(event1v1, event1v1);
+
+        LocationAlgorithmEvent event1v2 = new LocationAlgorithmEvent(status1, suggestion1);
+        assertEqualsAndHashCode(event1v1, event1v2);
+
+        GeolocationTimeZoneSuggestion suggestion2 =
+                GeolocationTimeZoneSuggestion.createUncertainSuggestion(2222L);
+        LocationAlgorithmEvent event2 = new LocationAlgorithmEvent(status1, suggestion2);
+        assertNotEquals(event1v1, event2);
+
+        LocationTimeZoneAlgorithmStatus status2 = new LocationTimeZoneAlgorithmStatus(
+                DETECTION_ALGORITHM_STATUS_RUNNING,
+                PROVIDER_STATUS_NOT_PRESENT, null, PROVIDER_STATUS_NOT_READY, null);
+        LocationAlgorithmEvent event3 = new LocationAlgorithmEvent(status2, suggestion1);
+        assertNotEquals(event1v1, event3);
+
+        // DebugInfo must not be considered in equals().
+        event1v1.addDebugInfo("Debug info 1");
+        event1v2.addDebugInfo("Debug info 2");
+        assertEquals(event1v1, event1v2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseCommandLineArg_noStatus() {
+        GeolocationTimeZoneSuggestion suggestion =
+                GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L);
+        ShellCommand testShellCommand =
+                createShellCommandWithArgsAndOptions(
+                        Arrays.asList("--suggestion", suggestion.toString()));
+
+        LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+    }
+
+    @Test
+    public void testParseCommandLineArg_noSuggestion() {
+        GeolocationTimeZoneSuggestion suggestion = null;
+        LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+                ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                Arrays.asList("--status", event.getAlgorithmStatus().toString()));
+
+        assertEquals(event, LocationAlgorithmEvent.parseCommandLineArg(testShellCommand));
+    }
+
+    @Test
+    public void testParseCommandLineArg_suggestionUncertain() {
+        GeolocationTimeZoneSuggestion suggestion =
+                GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L);
+        LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+                ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+                        "--suggestion", "UNCERTAIN"));
+
+        LocationAlgorithmEvent parsedEvent =
+                LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+        assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus());
+        assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds());
+    }
+
+    @Test
+    public void testParseCommandLineArg_suggestionEmpty() {
+        GeolocationTimeZoneSuggestion suggestion =
+                GeolocationTimeZoneSuggestion.createCertainSuggestion(
+                        1111L, Collections.emptyList());
+        LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+                ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+                        "--suggestion", "EMPTY"));
+
+        LocationAlgorithmEvent parsedEvent =
+                LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+        assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus());
+        assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds());
+    }
+
+    @Test
+    public void testParseCommandLineArg_suggestionPresent() {
+        GeolocationTimeZoneSuggestion suggestion =
+                GeolocationTimeZoneSuggestion.createCertainSuggestion(
+                        1111L, Arrays.asList("Europe/London", "Europe/Paris"));
+        LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+                ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+                        "--suggestion", "Europe/London,Europe/Paris"));
+
+        LocationAlgorithmEvent parsedEvent =
+                LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+        assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus());
+        assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParseCommandLineArg_unknownArgument() {
+        GeolocationTimeZoneSuggestion suggestion =
+                GeolocationTimeZoneSuggestion.createCertainSuggestion(
+                        1111L, Arrays.asList("Europe/London", "Europe/Paris"));
+        LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+                ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+        ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+                Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+                        "--suggestion", "Europe/London,Europe/Paris", "--bad_arg"));
+        LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+    }
+
+    private static void assertEqualsAndHashCode(Object one, Object two) {
+        assertEquals(one, two);
+        assertEquals(two, one);
+        assertEquals(one.hashCode(), two.hashCode());
+    }
+}
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..8207c19 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
@@ -16,6 +16,10 @@
 
 package com.android.server.timezonedetector;
 
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+
 import static com.android.server.timezonedetector.MetricsTimeZoneDetectorState.DETECTION_MODE_GEO;
 
 import static org.junit.Assert.assertEquals;
@@ -23,6 +27,7 @@
 
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.UserIdInt;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 
@@ -31,6 +36,7 @@
 import org.junit.Test;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.function.Function;
 
 /** Tests for {@link MetricsTimeZoneDetectorState}. */
@@ -38,6 +44,9 @@
 
     private static final @UserIdInt int ARBITRARY_USER_ID = 1;
     private static final @ElapsedRealtimeLong long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
+    private static final LocationTimeZoneAlgorithmStatus ARBITRARY_CERTAIN_STATUS =
+            new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                    PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
     private static final String DEVICE_TIME_ZONE_ID = "DeviceTimeZoneId";
 
     private static final ManualTimeZoneSuggestion MANUAL_TIME_ZONE_SUGGESTION =
@@ -50,11 +59,14 @@
                     .setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE)
                     .build();
 
-    private static final GeolocationTimeZoneSuggestion GEOLOCATION_TIME_ZONE_SUGGESTION =
+    public static final GeolocationTimeZoneSuggestion GEOLOCATION_SUGGESTION_CERTAIN =
             GeolocationTimeZoneSuggestion.createCertainSuggestion(
                     ARBITRARY_ELAPSED_REALTIME_MILLIS,
                     Arrays.asList("GeoTimeZoneId1", "GeoTimeZoneId2"));
 
+    private static final LocationAlgorithmEvent LOCATION_ALGORITHM_EVENT =
+            new LocationAlgorithmEvent(ARBITRARY_CERTAIN_STATUS, GEOLOCATION_SUGGESTION_CERTAIN);
+
     private final OrdinalGenerator<String> mOrdinalGenerator =
             new OrdinalGenerator<>(Function.identity());
 
@@ -68,7 +80,7 @@
         MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
                 MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
                         DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
-                        TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+                        TELEPHONY_TIME_ZONE_SUGGESTION, LOCATION_ALGORITHM_EVENT);
 
         // Assert the content.
         assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
@@ -88,9 +100,10 @@
         assertEquals(expectedTelephonySuggestion,
                 metricsTimeZoneDetectorState.getLatestTelephonySuggestion());
 
+        List<String> expectedZoneIds = LOCATION_ALGORITHM_EVENT.getSuggestion().getZoneIds();
         MetricsTimeZoneSuggestion expectedGeoSuggestion =
                 MetricsTimeZoneSuggestion.createCertain(
-                        GEOLOCATION_TIME_ZONE_SUGGESTION.getZoneIds().toArray(new String[0]),
+                        expectedZoneIds.toArray(new String[0]),
                         new int[] { 3, 4 });
         assertEquals(expectedGeoSuggestion,
                 metricsTimeZoneDetectorState.getLatestGeolocationSuggestion());
@@ -106,7 +119,7 @@
         MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
                 MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
                         DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
-                        TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+                        TELEPHONY_TIME_ZONE_SUGGESTION, LOCATION_ALGORITHM_EVENT);
 
         // Assert the content.
         assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
@@ -145,7 +158,7 @@
                 metricsTimeZoneDetectorState.isGeoDetectionSupported());
         assertEquals(configurationInternal.isTelephonyFallbackSupported(),
                 metricsTimeZoneDetectorState.isTelephonyTimeZoneFallbackSupported());
-        assertEquals(configurationInternal.getGeoDetectionRunInBackgroundEnabled(),
+        assertEquals(configurationInternal.getGeoDetectionRunInBackgroundEnabledSetting(),
                 metricsTimeZoneDetectorState.getGeoDetectionRunInBackgroundEnabled());
         assertEquals(configurationInternal.isEnhancedMetricsCollectionEnabled(),
                 metricsTimeZoneDetectorState.isEnhancedMetricsCollectionEnabled());
@@ -161,7 +174,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..a02c8ca 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -16,14 +16,22 @@
 
 package com.android.server.timezonedetector;
 
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
 import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.content.Context;
 import android.os.HandlerThread;
@@ -41,6 +49,15 @@
 @RunWith(AndroidJUnit4.class)
 public class TimeZoneDetectorInternalImplTest {
 
+    private static final TelephonyTimeZoneAlgorithmStatus ARBITRARY_TELEPHONY_STATUS =
+            new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING);
+    private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_CERTAIN_STATUS =
+            new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                    PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
+    private static final TimeZoneDetectorStatus ARBITRARY_DETECTOR_STATUS =
+            new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING, ARBITRARY_TELEPHONY_STATUS,
+                    ARBITRARY_LOCATION_CERTAIN_STATUS);
+
     private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
     private static final String ARBITRARY_ZONE_ID = "TestZoneId";
     private static final List<String> ARBITRARY_ZONE_IDS = Arrays.asList(ARBITRARY_ZONE_ID);
@@ -50,7 +67,6 @@
     private HandlerThread mHandlerThread;
     private TestHandler mTestHandler;
     private TestCurrentUserIdentityInjector mTestCurrentUserIdentityInjector;
-    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
     private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategySpy;
 
     private TimeZoneDetectorInternalImpl mTimeZoneDetectorInternal;
@@ -65,12 +81,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 +98,22 @@
     public void testGetCapabilitiesAndConfigForDpm() throws Exception {
         final boolean autoDetectionEnabled = true;
         ConfigurationInternal testConfig = createConfigurationInternal(autoDetectionEnabled);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(testConfig);
+        TimeZoneDetectorStatus testStatus = ARBITRARY_DETECTOR_STATUS;
+        mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(testConfig, testStatus);
 
         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(
+                        testStatus,
+                        testConfig.asCapabilities(expectedBypassUserPolicyChecks),
+                        testConfig.asConfiguration());
         assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
     }
 
@@ -102,7 +122,9 @@
         final boolean autoDetectionEnabled = false;
         ConfigurationInternal initialConfigurationInternal =
                 createConfigurationInternal(autoDetectionEnabled);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfigurationInternal);
+        TimeZoneDetectorStatus testStatus = ARBITRARY_DETECTOR_STATUS;
+        mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(
+                initialConfigurationInternal, testStatus);
 
         TimeZoneConfiguration timeConfiguration = new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(true)
@@ -110,7 +132,7 @@
         assertTrue(mTimeZoneDetectorInternal.updateConfigurationForDpm(timeConfiguration));
 
         final boolean expectedBypassUserPolicyChecks = true;
-        verify(mFakeServiceConfigAccessorSpy).updateConfiguration(
+        verify(mFakeTimeZoneDetectorStrategySpy).updateConfiguration(
                 mTestCurrentUserIdentityInjector.getCurrentUserId(),
                 timeConfiguration,
                 expectedBypassUserPolicyChecks);
@@ -130,13 +152,15 @@
     }
 
     @Test
-    public void testSuggestGeolocationTimeZone() throws Exception {
+    public void testHandleLocationAlgorithmEvent() throws Exception {
         GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
-        mTimeZoneDetectorInternal.suggestGeolocationTimeZone(timeZoneSuggestion);
+        LocationAlgorithmEvent suggestionEvent = new LocationAlgorithmEvent(
+                ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion);
+        mTimeZoneDetectorInternal.handleLocationAlgorithmEvent(suggestionEvent);
         mTestHandler.assertTotalMessagesEnqueued(1);
 
         mTestHandler.waitForMessagesToBeProcessed();
-        verify(mFakeTimeZoneDetectorStrategySpy).suggestGeolocationTimeZone(timeZoneSuggestion);
+        verify(mFakeTimeZoneDetectorStrategySpy).handleLocationAlgorithmEvent(suggestionEvent);
     }
     private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() {
         return new ManualTimeZoneSuggestion(ARBITRARY_ZONE_ID);
@@ -148,7 +172,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..d9d8053 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -16,6 +16,11 @@
 
 package com.android.server.timezonedetector;
 
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
@@ -34,8 +39,11 @@
 import static org.mockito.Mockito.when;
 
 import android.app.time.ITimeZoneDetectorListener;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
 import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -59,6 +67,13 @@
 @RunWith(AndroidJUnit4.class)
 public class TimeZoneDetectorServiceTest {
 
+    private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_CERTAIN_STATUS =
+            new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                    PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
+    private static final TimeZoneDetectorStatus ARBITRARY_DETECTOR_STATUS =
+            new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING,
+                    new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING),
+                    ARBITRARY_LOCATION_CERTAIN_STATUS);
     private static final int ARBITRARY_USER_ID = 9999;
     private static final List<String> ARBITRARY_TIME_ZONE_IDS = Arrays.asList("TestZoneId");
     private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
@@ -69,7 +84,6 @@
     private HandlerThread mHandlerThread;
     private TestHandler mTestHandler;
     private TestCallerIdentityInjector mTestCallerIdentityInjector;
-    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
     private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategySpy;
 
 
@@ -85,12 +99,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 +128,8 @@
 
         ConfigurationInternal configuration =
                 createConfigurationInternal(true /* autoDetectionEnabled*/);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(configuration);
+        mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(configuration,
+                ARBITRARY_DETECTOR_STATUS);
 
         TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
                 mTimeZoneDetectorService.getCapabilitiesAndConfig();
@@ -124,11 +138,15 @@
                 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(
+                        ARBITRARY_DETECTOR_STATUS,
+                        configuration.asCapabilities(expectedBypassUserPolicyChecks),
+                        configuration.asConfiguration());
         assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
     }
 
@@ -160,7 +178,9 @@
     public void testListenerRegistrationAndCallbacks() throws Exception {
         ConfigurationInternal initialConfiguration =
                 createConfigurationInternal(false /* autoDetectionEnabled */);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfiguration);
+
+        mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(
+                initialConfiguration, ARBITRARY_DETECTOR_STATUS);
 
         IBinder mockListenerBinder = mock(IBinder.class);
         ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
@@ -230,31 +250,35 @@
     }
 
     @Test
-    public void testSuggestGeolocationTimeZone_withoutPermission() {
+    public void testHandleLocationAlgorithmEvent_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
         GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
+        LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+                ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion);
 
         assertThrows(SecurityException.class,
-                () -> mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion));
+                () -> mTimeZoneDetectorService.handleLocationAlgorithmEvent(event));
         verify(mMockContext).enforceCallingPermission(
                 eq(android.Manifest.permission.SET_TIME_ZONE), anyString());
     }
 
     @Test
-    public void testSuggestGeolocationTimeZone() throws Exception {
+    public void testHandleLocationAlgorithmEvent() throws Exception {
         doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
 
         GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
+        LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+                ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion);
 
-        mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion);
+        mTimeZoneDetectorService.handleLocationAlgorithmEvent(event);
         mTestHandler.assertTotalMessagesEnqueued(1);
 
         verify(mMockContext).enforceCallingPermission(
                 eq(android.Manifest.permission.SET_TIME_ZONE), anyString());
 
         mTestHandler.waitForMessagesToBeProcessed();
-        verify(mFakeTimeZoneDetectorStrategySpy).suggestGeolocationTimeZone(timeZoneSuggestion);
+        verify(mFakeTimeZoneDetectorStrategySpy).handleLocationAlgorithmEvent(event);
     }
 
     @Test
@@ -455,7 +479,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..1c014d1 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -16,6 +16,12 @@
 
 package com.android.server.timezonedetector;
 
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY;
@@ -23,6 +29,9 @@
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_UNKNOWN;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN;
 
 import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
 import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
@@ -35,22 +44,36 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 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.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
 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 android.service.timezone.TimeZoneProviderStatus;
 
 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 +120,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 +134,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 +148,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 +162,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 +176,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 +190,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 +203,216 @@
                     .setGeoDetectionEnabledSetting(true)
                     .build();
 
-    private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
+    private static final TelephonyTimeZoneAlgorithmStatus TELEPHONY_ALGORITHM_RUNNING_STATUS =
+            new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING);
+
+    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);
+            assertStateChangeNotificationsSent(stateChangeListener, 0);
+            assertEquals(CONFIG_AUTO_DISABLED_GEO_DISABLED,
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
+
+        // Report a config change that actually changes something.
+        {
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    CONFIG_AUTO_ENABLED_GEO_ENABLED);
+            assertStateChangeNotificationsSent(stateChangeListener, 1);
+            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);
+            assertStateChangeNotificationsSent(stateChangeListener, 1);
+        }
+    }
+
+    // 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);
+
+        // Only changes to the current user's config are notified.
+        assertStateChangeNotificationsSent(stateChangeListener, 0);
+    }
+
+    // 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
@@ -202,9 +433,9 @@
         QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex1ScoredSuggestion =
                 new QualifiedTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion,
                         TELEPHONY_SCORE_NONE);
-        assertEquals(expectedSlotIndex1ScoredSuggestion,
-                mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
-        assertNull(mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+        script.verifyLatestQualifiedTelephonySuggestionReceived(
+                SLOT_INDEX1, expectedSlotIndex1ScoredSuggestion)
+                .verifyLatestQualifiedTelephonySuggestionReceived(SLOT_INDEX2, null);
         assertEquals(expectedSlotIndex1ScoredSuggestion,
                 mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
@@ -215,10 +446,10 @@
         QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex2ScoredSuggestion =
                 new QualifiedTelephonyTimeZoneSuggestion(slotIndex2TimeZoneSuggestion,
                         TELEPHONY_SCORE_NONE);
-        assertEquals(expectedSlotIndex1ScoredSuggestion,
-                mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
-        assertEquals(expectedSlotIndex2ScoredSuggestion,
-                mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+        script.verifyLatestQualifiedTelephonySuggestionReceived(
+                        SLOT_INDEX1, expectedSlotIndex1ScoredSuggestion)
+                .verifyLatestQualifiedTelephonySuggestionReceived(
+                        SLOT_INDEX2, expectedSlotIndex2ScoredSuggestion);
         // SlotIndex1 should always beat slotIndex2, all other things being equal.
         assertEquals(expectedSlotIndex1ScoredSuggestion,
                 mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -253,8 +484,8 @@
             QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
                     new QualifiedTelephonyTimeZoneSuggestion(
                             lowQualitySuggestion, testCase.expectedScore);
-            assertEquals(expectedScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+            script.verifyLatestQualifiedTelephonySuggestionReceived(
+                    SLOT_INDEX1, expectedScoredSuggestion);
             assertEquals(expectedScoredSuggestion,
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
         }
@@ -270,8 +501,8 @@
             QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
                     new QualifiedTelephonyTimeZoneSuggestion(
                             goodQualitySuggestion, testCase2.expectedScore);
-            assertEquals(expectedScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+            script.verifyLatestQualifiedTelephonySuggestionReceived(
+                    SLOT_INDEX1, expectedScoredSuggestion);
             assertEquals(expectedScoredSuggestion,
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
         }
@@ -287,8 +518,8 @@
             QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
                     new QualifiedTelephonyTimeZoneSuggestion(
                             lowQualitySuggestion2, testCase.expectedScore);
-            assertEquals(expectedScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+            script.verifyLatestQualifiedTelephonySuggestionReceived(
+                    SLOT_INDEX1, expectedScoredSuggestion);
             assertEquals(expectedScoredSuggestion,
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
         }
@@ -319,8 +550,8 @@
             // Assert internal service state.
             QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
                     new QualifiedTelephonyTimeZoneSuggestion(suggestion, testCase.expectedScore);
-            assertEquals(expectedScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+            script.verifyLatestQualifiedTelephonySuggestionReceived(
+                    SLOT_INDEX1, expectedScoredSuggestion);
             assertEquals(expectedScoredSuggestion,
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
@@ -336,8 +567,8 @@
             }
 
             // Assert internal service state.
-            assertEquals(expectedScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+            script.verifyLatestQualifiedTelephonySuggestionReceived(
+                    SLOT_INDEX1, expectedScoredSuggestion);
             assertEquals(expectedScoredSuggestion,
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
@@ -346,8 +577,8 @@
                     .verifyTimeZoneNotChanged();
 
             // Assert internal service state.
-            assertEquals(expectedScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+            script.verifyLatestQualifiedTelephonySuggestionReceived(
+                    SLOT_INDEX1, expectedScoredSuggestion);
             assertEquals(expectedScoredSuggestion,
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
         }
@@ -398,8 +629,8 @@
         }
 
         // Assert internal service state.
-        assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
-                mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+        script.verifyLatestQualifiedTelephonySuggestionReceived(
+                SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion);
         assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
                 mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
     }
@@ -453,10 +684,10 @@
             }
 
             // Assert internal service state.
-            assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
-            assertEquals(expectedEmptySlotIndex2ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+            script.verifyLatestQualifiedTelephonySuggestionReceived(
+                    SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion)
+                    .verifyLatestQualifiedTelephonySuggestionReceived(
+                            SLOT_INDEX2, expectedEmptySlotIndex2ScoredSuggestion);
             assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
@@ -466,10 +697,10 @@
             script.verifyTimeZoneNotChanged();
 
             // Assert internal service state.
-            assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
-            assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+            script.verifyLatestQualifiedTelephonySuggestionReceived(
+                            SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion)
+                    .verifyLatestQualifiedTelephonySuggestionReceived(
+                            SLOT_INDEX2, expectedZoneSlotIndex2ScoredSuggestion);
             // SlotIndex1 should always beat slotIndex2, all other things being equal.
             assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -485,20 +716,20 @@
             }
 
             // Assert internal service state.
-            assertEquals(expectedEmptySlotIndex1ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
-            assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+            script.verifyLatestQualifiedTelephonySuggestionReceived(
+                            SLOT_INDEX1, expectedEmptySlotIndex1ScoredSuggestion)
+                    .verifyLatestQualifiedTelephonySuggestionReceived(
+                            SLOT_INDEX2, expectedZoneSlotIndex2ScoredSuggestion);
             assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
             // Reset the state for the next loop.
             script.simulateTelephonyTimeZoneSuggestion(emptySlotIndex2Suggestion)
                     .verifyTimeZoneNotChanged();
-            assertEquals(expectedEmptySlotIndex1ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
-            assertEquals(expectedEmptySlotIndex2ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+            script.verifyLatestQualifiedTelephonySuggestionReceived(
+                            SLOT_INDEX1, expectedEmptySlotIndex1ScoredSuggestion)
+                    .verifyLatestQualifiedTelephonySuggestionReceived(
+                            SLOT_INDEX2, expectedEmptySlotIndex2ScoredSuggestion);
         }
     }
 
@@ -642,53 +873,185 @@
     }
 
     @Test
-    public void testGeoSuggestion_uncertain() {
+    public void testLocationAlgorithmEvent_statusChangesOnly() {
+        TestStateChangeListener stateChangeListener = new TestStateChangeListener();
         Script script = new Script()
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
-                .resetConfigurationTracking();
+                .resetConfigurationTracking()
+                .registerStateChangeListener(stateChangeListener);
 
-        GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeolocationSuggestion();
+        TimeZoneDetectorStatus expectedInitialDetectorStatus = new TimeZoneDetectorStatus(
+                DETECTOR_STATUS_RUNNING,
+                TELEPHONY_ALGORITHM_RUNNING_STATUS,
+                LocationTimeZoneAlgorithmStatus.RUNNING_NOT_REPORTED);
+        script.verifyCachedDetectorStatus(expectedInitialDetectorStatus);
 
-        script.simulateGeolocationTimeZoneSuggestion(uncertainSuggestion)
-                .verifyTimeZoneNotChanged();
+        LocationTimeZoneAlgorithmStatus algorithmStatus1 = new LocationTimeZoneAlgorithmStatus(
+                DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_NOT_READY, null,
+                PROVIDER_STATUS_NOT_PRESENT, null);
+        LocationTimeZoneAlgorithmStatus algorithmStatus2 = new LocationTimeZoneAlgorithmStatus(
+                DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_NOT_PRESENT, null,
+                PROVIDER_STATUS_NOT_PRESENT, null);
+        assertNotEquals(algorithmStatus1, algorithmStatus2);
 
-        // Assert internal service state.
-        assertEquals(uncertainSuggestion,
-                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        {
+            LocationAlgorithmEvent locationAlgorithmEvent =
+                    new LocationAlgorithmEvent(algorithmStatus1, null);
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                    .verifyTimeZoneNotChanged();
+
+            assertStateChangeNotificationsSent(stateChangeListener, 1);
+
+            // Assert internal service state.
+            TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+                    DETECTOR_STATUS_RUNNING,
+                    TELEPHONY_ALGORITHM_RUNNING_STATUS,
+                    algorithmStatus1);
+            script.verifyCachedDetectorStatus(expectedDetectorStatus)
+                    .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+            // Repeat the event to demonstrate the state change notifier is not triggered.
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                    .verifyTimeZoneNotChanged();
+
+            assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+            // Assert internal service state.
+            script.verifyCachedDetectorStatus(expectedDetectorStatus)
+                    .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+        }
+
+        {
+            LocationAlgorithmEvent locationAlgorithmEvent =
+                    new LocationAlgorithmEvent(algorithmStatus2, null);
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                    .verifyTimeZoneNotChanged();
+
+            assertStateChangeNotificationsSent(stateChangeListener, 1);
+
+            // Assert internal service state.
+            TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+                    DETECTOR_STATUS_RUNNING,
+                    TELEPHONY_ALGORITHM_RUNNING_STATUS,
+                    algorithmStatus2);
+            script.verifyCachedDetectorStatus(expectedDetectorStatus)
+                    .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+            // Repeat the event to demonstrate the state change notifier is not triggered.
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                    .verifyTimeZoneNotChanged();
+
+            assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+            // Assert internal service state.
+            script.verifyCachedDetectorStatus(expectedDetectorStatus)
+                    .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+        }
     }
 
     @Test
-    public void testGeoSuggestion_noZones() {
+    public void testLocationAlgorithmEvent_uncertain() {
+        TestStateChangeListener stateChangeListener = new TestStateChangeListener();
         Script script = new Script()
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
-                .resetConfigurationTracking();
+                .resetConfigurationTracking()
+                .registerStateChangeListener(stateChangeListener);
 
-        GeolocationTimeZoneSuggestion noZonesSuggestion = createCertainGeolocationSuggestion();
-
-        script.simulateGeolocationTimeZoneSuggestion(noZonesSuggestion)
+        LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
+        script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                 .verifyTimeZoneNotChanged();
 
+        assertStateChangeNotificationsSent(stateChangeListener, 1);
+
         // Assert internal service state.
-        assertEquals(noZonesSuggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+                DETECTOR_STATUS_RUNNING,
+                TELEPHONY_ALGORITHM_RUNNING_STATUS,
+                locationAlgorithmEvent.getAlgorithmStatus());
+        script.verifyCachedDetectorStatus(expectedDetectorStatus)
+                .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+        // Repeat the event to demonstrate the state change notifier is not triggered.
+        script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                .verifyTimeZoneNotChanged();
+
+        // Detector remains running and location algorithm is still uncertain so nothing to report.
+        assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+        // Assert internal service state.
+        script.verifyCachedDetectorStatus(expectedDetectorStatus)
+                .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
     }
 
     @Test
-    public void testGeoSuggestion_oneZone() {
-        GeolocationTimeZoneSuggestion suggestion =
-                createCertainGeolocationSuggestion("Europe/London");
-
+    public void testLocationAlgorithmEvent_noZones() {
+        TestStateChangeListener stateChangeListener = new TestStateChangeListener();
         Script script = new Script()
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
-                .resetConfigurationTracking();
+                .resetConfigurationTracking()
+                .registerStateChangeListener(stateChangeListener);
 
-        script.simulateGeolocationTimeZoneSuggestion(suggestion)
-                .verifyTimeZoneChangedAndReset(suggestion);
+        LocationAlgorithmEvent locationAlgorithmEvent = createCertainLocationAlgorithmEvent();
+        script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                .verifyTimeZoneNotChanged();
+
+        assertStateChangeNotificationsSent(stateChangeListener, 1);
 
         // Assert internal service state.
-        assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+                DETECTOR_STATUS_RUNNING,
+                TELEPHONY_ALGORITHM_RUNNING_STATUS,
+                locationAlgorithmEvent.getAlgorithmStatus());
+        script.verifyCachedDetectorStatus(expectedDetectorStatus)
+                .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+        // Repeat the event to demonstrate the state change notifier is not triggered.
+        script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                .verifyTimeZoneNotChanged();
+
+        assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+        // Assert internal service state.
+        script.verifyCachedDetectorStatus(expectedDetectorStatus)
+                .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+    }
+
+    @Test
+    public void testLocationAlgorithmEvent_oneZone() {
+        TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+        Script script = new Script()
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
+                .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
+                .resetConfigurationTracking()
+                .registerStateChangeListener(stateChangeListener);
+
+        LocationAlgorithmEvent locationAlgorithmEvent =
+                createCertainLocationAlgorithmEvent("Europe/London");
+        script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                .verifyTimeZoneChangedAndReset(locationAlgorithmEvent);
+
+        assertStateChangeNotificationsSent(stateChangeListener, 1);
+
+        // Assert internal service state.
+        TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+                DETECTOR_STATUS_RUNNING,
+                TELEPHONY_ALGORITHM_RUNNING_STATUS,
+                locationAlgorithmEvent.getAlgorithmStatus());
+        script.verifyCachedDetectorStatus(expectedDetectorStatus)
+                .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+        // Repeat the event to demonstrate the state change notifier is not triggered.
+        script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                .verifyTimeZoneNotChanged();
+
+        assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+        // Assert internal service state.
+        script.verifyCachedDetectorStatus(expectedDetectorStatus)
+                .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
     }
 
     /**
@@ -697,41 +1060,35 @@
      * set to until that unambiguously can't be correct.
      */
     @Test
-    public void testGeoSuggestion_multiZone() {
-        GeolocationTimeZoneSuggestion londonOnlySuggestion =
-                createCertainGeolocationSuggestion("Europe/London");
-        GeolocationTimeZoneSuggestion londonOrParisSuggestion =
-                createCertainGeolocationSuggestion("Europe/Paris", "Europe/London");
-        GeolocationTimeZoneSuggestion parisOnlySuggestion =
-                createCertainGeolocationSuggestion("Europe/Paris");
-
+    public void testLocationAlgorithmEvent_multiZone() {
         Script script = new Script()
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
                 .resetConfigurationTracking();
 
-        script.simulateGeolocationTimeZoneSuggestion(londonOnlySuggestion)
-                .verifyTimeZoneChangedAndReset(londonOnlySuggestion);
-        assertEquals(londonOnlySuggestion,
-                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        LocationAlgorithmEvent londonOnlyEvent =
+                createCertainLocationAlgorithmEvent("Europe/London");
+        script.simulateLocationAlgorithmEvent(londonOnlyEvent)
+                .verifyTimeZoneChangedAndReset(londonOnlyEvent)
+                .verifyLatestLocationAlgorithmEventReceived(londonOnlyEvent);
 
         // Confirm bias towards the current device zone when there's multiple zones to choose from.
-        script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion)
-                .verifyTimeZoneNotChanged();
-        assertEquals(londonOrParisSuggestion,
-                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        LocationAlgorithmEvent londonOrParisEvent =
+                createCertainLocationAlgorithmEvent("Europe/Paris", "Europe/London");
+        script.simulateLocationAlgorithmEvent(londonOrParisEvent)
+                .verifyTimeZoneNotChanged()
+                .verifyLatestLocationAlgorithmEventReceived(londonOrParisEvent);
 
-        script.simulateGeolocationTimeZoneSuggestion(parisOnlySuggestion)
-                .verifyTimeZoneChangedAndReset(parisOnlySuggestion);
-        assertEquals(parisOnlySuggestion,
-                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        LocationAlgorithmEvent parisOnlyEvent = createCertainLocationAlgorithmEvent("Europe/Paris");
+        script.simulateLocationAlgorithmEvent(parisOnlyEvent)
+                .verifyTimeZoneChangedAndReset(parisOnlyEvent)
+                .verifyLatestLocationAlgorithmEventReceived(parisOnlyEvent);
 
         // Now the suggestion that previously left the device on Europe/London will leave the device
         // on Europe/Paris.
-        script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion)
-                .verifyTimeZoneNotChanged();
-        assertEquals(londonOrParisSuggestion,
-                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        script.simulateLocationAlgorithmEvent(londonOrParisEvent)
+                .verifyTimeZoneNotChanged()
+                .verifyLatestLocationAlgorithmEventReceived(londonOrParisEvent);
     }
 
     /**
@@ -740,8 +1097,9 @@
      */
     @Test
     public void testChangingGeoDetectionEnabled() {
-        GeolocationTimeZoneSuggestion geolocationSuggestion =
-                createCertainGeolocationSuggestion("Europe/London");
+        TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+        LocationAlgorithmEvent locationAlgorithmEvent =
+                createCertainLocationAlgorithmEvent("Europe/London");
         TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
                 SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
                 "Europe/Paris");
@@ -749,20 +1107,22 @@
         Script script = new Script()
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
-                .resetConfigurationTracking();
+                .resetConfigurationTracking()
+                .registerStateChangeListener(stateChangeListener);
 
         // Add suggestions. Nothing should happen as time zone detection is disabled.
-        script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
-                .verifyTimeZoneNotChanged();
+        script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                .verifyTimeZoneNotChanged()
+                .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
 
-        assertEquals(geolocationSuggestion,
-                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        // A detector status change is considered a "state change".
+        assertStateChangeNotificationsSent(stateChangeListener, 1);
 
         script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
-                .verifyTimeZoneNotChanged();
+                .verifyTimeZoneNotChanged()
+                .verifyLatestTelephonySuggestionReceived(SLOT_INDEX1, telephonySuggestion);
 
-        assertEquals(telephonySuggestion,
-                mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1).suggestion);
+        assertStateChangeNotificationsSent(stateChangeListener, 0);
 
         // Toggling the time zone detection enabled setting on should cause the device setting to be
         // set from the telephony signal, as we've started with geolocation time zone detection
@@ -770,22 +1130,29 @@
         script.simulateSetAutoMode(true)
                 .verifyTimeZoneChangedAndReset(telephonySuggestion);
 
+        // A configuration change is considered a "state change".
+        assertStateChangeNotificationsSent(stateChangeListener, 1);
+
         // Changing the detection to enable geo detection will cause the device tz setting to
         // change to use the latest geolocation suggestion.
         script.simulateSetGeoDetectionEnabled(true)
-                .verifyTimeZoneChangedAndReset(geolocationSuggestion);
+                .verifyTimeZoneChangedAndReset(locationAlgorithmEvent);
+
+        // A configuration change is considered a "state change".
+        assertStateChangeNotificationsSent(stateChangeListener, 1);
 
         // Changing the detection to disable geo detection should cause the device tz setting to
         // change to the telephony suggestion.
         script.simulateSetGeoDetectionEnabled(false)
-                .verifyTimeZoneChangedAndReset(telephonySuggestion);
+                .verifyTimeZoneChangedAndReset(telephonySuggestion)
+                .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
 
-        assertEquals(geolocationSuggestion,
-                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        // A configuration change is considered a "state change".
+        assertStateChangeNotificationsSent(stateChangeListener, 1);
     }
 
     @Test
-    public void testTelephonyFallback() {
+    public void testTelephonyFallback_enableTelephonyTimeZoneFallbackCalled() {
         ConfigurationInternal config = new ConfigurationInternal.Builder(
                 CONFIG_AUTO_ENABLED_GEO_ENABLED)
                 .setTelephonyFallbackSupported(true)
@@ -815,21 +1182,22 @@
 
         // Receiving an "uncertain" geolocation suggestion should have no effect.
         {
-            GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
-                    createUncertainGeolocationSuggestion();
-            script.simulateIncrementClock()
-                    .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                     .verifyTimeZoneNotChanged()
                     .verifyTelephonyFallbackIsEnabled(true);
         }
 
         // Receiving a "certain" geolocation suggestion should disable telephony fallback mode.
         {
-            GeolocationTimeZoneSuggestion geolocationSuggestion =
-                    createCertainGeolocationSuggestion("Europe/London");
-            script.simulateIncrementClock()
-                    .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
-                    .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent locationAlgorithmEvent =
+                    createCertainLocationAlgorithmEvent("Europe/London");
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                    .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
                     .verifyTelephonyFallbackIsEnabled(false);
         }
 
@@ -852,22 +1220,24 @@
         // Geolocation suggestions should continue to be used as normal (previous telephony
         // suggestions are not used, even when the geolocation suggestion is uncertain).
         {
-            GeolocationTimeZoneSuggestion geolocationSuggestion =
-                    createCertainGeolocationSuggestion("Europe/Rome");
-            script.simulateIncrementClock()
-                    .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
-                    .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent certainLocationAlgorithmEvent =
+                    createCertainLocationAlgorithmEvent("Europe/Rome");
+            script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
+                    .verifyTimeZoneChangedAndReset(certainLocationAlgorithmEvent)
                     .verifyTelephonyFallbackIsEnabled(false);
 
-            GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
-                    createUncertainGeolocationSuggestion();
-            script.simulateIncrementClock()
-                    .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent uncertainLocationAlgorithmEvent =
+                    createUncertainLocationAlgorithmEvent();
+            script.simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent)
                     .verifyTimeZoneNotChanged()
                     .verifyTelephonyFallbackIsEnabled(false);
 
             script.simulateIncrementClock()
-                    .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
+                    .simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
                     // No change needed, device will already be set to Europe/Rome.
                     .verifyTimeZoneNotChanged()
                     .verifyTelephonyFallbackIsEnabled(false);
@@ -884,21 +1254,22 @@
 
         // Make the geolocation algorithm uncertain.
         {
-            GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
-                    createUncertainGeolocationSuggestion();
-            script.simulateIncrementClock()
-                    .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                     .verifyTimeZoneChangedAndReset(lastTelephonySuggestion)
                     .verifyTelephonyFallbackIsEnabled(true);
         }
 
         // Make the geolocation algorithm certain, disabling telephony fallback.
         {
-            GeolocationTimeZoneSuggestion geolocationSuggestion =
-                    createCertainGeolocationSuggestion("Europe/Lisbon");
-            script.simulateIncrementClock()
-                    .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
-                    .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent locationAlgorithmEvent =
+                    createCertainLocationAlgorithmEvent("Europe/Lisbon");
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                    .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
                     .verifyTelephonyFallbackIsEnabled(false);
 
         }
@@ -906,10 +1277,10 @@
         // Demonstrate what happens when geolocation is uncertain when telephony fallback is
         // enabled.
         {
-            GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
-                    createUncertainGeolocationSuggestion();
-            script.simulateIncrementClock()
-                    .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                     .verifyTimeZoneNotChanged()
                     .verifyTelephonyFallbackIsEnabled(false)
                     .simulateEnableTelephonyFallback()
@@ -919,6 +1290,132 @@
     }
 
     @Test
+    public void testTelephonyFallback_locationAlgorithmEventSuggestsFallback() {
+        ConfigurationInternal config = new ConfigurationInternal.Builder(
+                CONFIG_AUTO_ENABLED_GEO_ENABLED)
+                .setTelephonyFallbackSupported(true)
+                .build();
+
+        Script script = new Script()
+                .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
+                .simulateConfigurationInternalChange(config)
+                .resetConfigurationTracking();
+
+        // Confirm initial state is as expected.
+        script.verifyTelephonyFallbackIsEnabled(true)
+                .verifyTimeZoneNotChanged();
+
+        // Although geolocation detection is enabled, telephony fallback should be used initially
+        // and until a suitable "certain" geolocation suggestion is received.
+        {
+            TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
+                    SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
+                    "Europe/Paris");
+            script.simulateIncrementClock()
+                    .simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
+                    .verifyTimeZoneChangedAndReset(telephonySuggestion)
+                    .verifyTelephonyFallbackIsEnabled(true);
+        }
+
+        // Receiving an "uncertain" geolocation suggestion without a status should have no effect.
+        {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                    .verifyTimeZoneNotChanged()
+                    .verifyTelephonyFallbackIsEnabled(true);
+        }
+
+        // Receiving a "certain" geolocation suggestion should disable telephony fallback mode.
+        {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent locationAlgorithmEvent =
+                    createCertainLocationAlgorithmEvent("Europe/London");
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                    .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
+                    .verifyTelephonyFallbackIsEnabled(false);
+        }
+
+        // Used to record the last telephony suggestion received, which will be used when fallback
+        // takes place.
+        TelephonyTimeZoneSuggestion lastTelephonySuggestion;
+
+        // Telephony suggestions should now be ignored and geolocation detection is "in control".
+        {
+            TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
+                    SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
+                    "Europe/Berlin");
+            script.simulateIncrementClock()
+                    .simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
+                    .verifyTimeZoneNotChanged()
+                    .verifyTelephonyFallbackIsEnabled(false);
+            lastTelephonySuggestion = telephonySuggestion;
+        }
+
+        // Geolocation suggestions should continue to be used as normal (previous telephony
+        // suggestions are not used, even when the geolocation suggestion is uncertain).
+        {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent certainLocationAlgorithmEvent =
+                    createCertainLocationAlgorithmEvent("Europe/Rome");
+            script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
+                    .verifyTimeZoneChangedAndReset(certainLocationAlgorithmEvent)
+                    .verifyTelephonyFallbackIsEnabled(false);
+
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent uncertainLocationAlgorithmEvent =
+                    createUncertainLocationAlgorithmEvent();
+            script.simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent)
+                    .verifyTimeZoneNotChanged()
+                    .verifyTelephonyFallbackIsEnabled(false);
+
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent certainLocationAlgorithmEvent2 =
+                    createCertainLocationAlgorithmEvent("Europe/Rome");
+            script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent2)
+                    // No change needed, device will already be set to Europe/Rome.
+                    .verifyTimeZoneNotChanged()
+                    .verifyTelephonyFallbackIsEnabled(false);
+        }
+
+        // Enable telephony fallback via a LocationAlgorithmEvent containing an "uncertain"
+        // suggestion.
+        {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            TimeZoneProviderStatus primaryProviderReportedStatus =
+                    new TimeZoneProviderStatus.Builder()
+                            .setLocationDetectionDependencyStatus(
+                                    DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS)
+                            .setConnectivityDependencyStatus(DEPENDENCY_STATUS_UNKNOWN)
+                            .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_UNKNOWN)
+                            .build();
+            LocationAlgorithmEvent uncertainEventBlockedBySettings =
+                    createUncertainLocationAlgorithmEvent(primaryProviderReportedStatus);
+            script.simulateLocationAlgorithmEvent(uncertainEventBlockedBySettings)
+                    .verifyTimeZoneChangedAndReset(lastTelephonySuggestion)
+                    .verifyTelephonyFallbackIsEnabled(true);
+        }
+
+        // Make the geolocation algorithm certain, disabling telephony fallback.
+        {
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent locationAlgorithmEvent =
+                    createCertainLocationAlgorithmEvent("Europe/Lisbon");
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+                    .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
+                    .verifyTelephonyFallbackIsEnabled(false);
+        }
+    }
+
+    @Test
     public void testTelephonyFallback_noTelephonySuggestionToFallBackTo() {
         ConfigurationInternal config = new ConfigurationInternal.Builder(
                 CONFIG_AUTO_ENABLED_GEO_ENABLED)
@@ -937,10 +1434,10 @@
 
         // Receiving an "uncertain" geolocation suggestion should have no effect.
         {
-            GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
-                    createUncertainGeolocationSuggestion();
-            script.simulateIncrementClock()
-                    .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                     .verifyTimeZoneNotChanged()
                     .verifyTelephonyFallbackIsEnabled(true);
         }
@@ -948,10 +1445,10 @@
         // Make an uncertain geolocation suggestion, there is no telephony suggestion to fall back
         // to
         {
-            GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
-                    createUncertainGeolocationSuggestion();
-            script.simulateIncrementClock()
-                    .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
+            script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                     .verifyTimeZoneNotChanged()
                     .verifyTelephonyFallbackIsEnabled(true);
         }
@@ -961,17 +1458,18 @@
         // Geolocation suggestions should continue to be used as normal (previous telephony
         // suggestions are not used, even when the geolocation suggestion is uncertain).
         {
-            GeolocationTimeZoneSuggestion geolocationSuggestion =
-                    createCertainGeolocationSuggestion("Europe/Rome");
-            script.simulateIncrementClock()
-                    .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
-                    .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent certainEvent =
+                    createCertainLocationAlgorithmEvent("Europe/Rome");
+            script.simulateLocationAlgorithmEvent(certainEvent)
+                    .verifyTimeZoneChangedAndReset(certainEvent)
                     .verifyTelephonyFallbackIsEnabled(false);
 
-            GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
-                    createUncertainGeolocationSuggestion();
-            script.simulateIncrementClock()
-                    .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+            // Increment the clock before creating the event: the clock's value is used by the event
+            script.simulateIncrementClock();
+            LocationAlgorithmEvent uncertainEvent = createUncertainLocationAlgorithmEvent();
+            script.simulateLocationAlgorithmEvent(uncertainEvent)
                     .verifyTimeZoneNotChanged()
                     .verifyTelephonyFallbackIsEnabled(false);
 
@@ -1095,15 +1593,15 @@
         TelephonyTimeZoneSuggestion telephonySuggestion =
                 createTelephonySuggestion(0 /* slotIndex */, MATCH_TYPE_NETWORK_COUNTRY_ONLY,
                         QUALITY_SINGLE_ZONE, "Zone2");
-        GeolocationTimeZoneSuggestion geolocationTimeZoneSuggestion =
-                createCertainGeolocationSuggestion("Zone3", "Zone2");
+        LocationAlgorithmEvent locationAlgorithmEvent =
+                createCertainLocationAlgorithmEvent("Zone3", "Zone2");
         script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
                 .verifyTimeZoneNotChanged()
-                .simulateGeolocationTimeZoneSuggestion(geolocationTimeZoneSuggestion)
+                .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                 .verifyTimeZoneNotChanged();
 
         assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
-                manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion,
+                manualSuggestion, telephonySuggestion, locationAlgorithmEvent,
                 MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL);
 
         // Update the config and confirm that the config metrics state updates also.
@@ -1112,11 +1610,11 @@
                 .setGeoDetectionEnabledSetting(true)
                 .build();
 
-        expectedDeviceTimeZoneId = geolocationTimeZoneSuggestion.getZoneIds().get(0);
+        expectedDeviceTimeZoneId = locationAlgorithmEvent.getSuggestion().getZoneIds().get(0);
         script.simulateConfigurationInternalChange(expectedInternalConfig)
                 .verifyTimeZoneChangedAndReset(expectedDeviceTimeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
         assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
-                manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion,
+                manualSuggestion, telephonySuggestion, locationAlgorithmEvent,
                 MetricsTimeZoneDetectorState.DETECTION_MODE_GEO);
     }
 
@@ -1128,7 +1626,7 @@
             ConfigurationInternal expectedInternalConfig,
             String expectedDeviceTimeZoneId, ManualTimeZoneSuggestion expectedManualSuggestion,
             TelephonyTimeZoneSuggestion expectedTelephonySuggestion,
-            GeolocationTimeZoneSuggestion expectedGeolocationTimeZoneSuggestion,
+            LocationAlgorithmEvent expectedLocationAlgorithmEvent,
             int expectedDetectionMode) {
 
         MetricsTimeZoneDetectorState actualState = mTimeZoneDetectorStrategy.generateMetricsState();
@@ -1141,7 +1639,7 @@
                 MetricsTimeZoneDetectorState.create(
                         tzIdOrdinalGenerator, expectedInternalConfig, expectedDeviceTimeZoneId,
                         expectedManualSuggestion, expectedTelephonySuggestion,
-                        expectedGeolocationTimeZoneSuggestion);
+                        expectedLocationAlgorithmEvent);
         // Rely on MetricsTimeZoneDetectorState.equals() for time zone ID / ID ordinal comparisons.
         assertEquals(expectedState, actualState);
     }
@@ -1181,39 +1679,57 @@
         return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX2).build();
     }
 
+    private LocationAlgorithmEvent createCertainLocationAlgorithmEvent(@NonNull String... zoneIds) {
+        GeolocationTimeZoneSuggestion suggestion = createCertainGeolocationSuggestion(zoneIds);
+        LocationTimeZoneAlgorithmStatus algorithmStatus = new LocationTimeZoneAlgorithmStatus(
+                DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_IS_CERTAIN, null,
+                PROVIDER_STATUS_NOT_PRESENT, null);
+        LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
+        event.addDebugInfo("Test certain event");
+        return event;
+    }
+
+    private LocationAlgorithmEvent createUncertainLocationAlgorithmEvent() {
+        TimeZoneProviderStatus primaryProviderReportedStatus = null;
+        return createUncertainLocationAlgorithmEvent(primaryProviderReportedStatus);
+    }
+
+    private LocationAlgorithmEvent createUncertainLocationAlgorithmEvent(
+            TimeZoneProviderStatus primaryProviderReportedStatus) {
+        GeolocationTimeZoneSuggestion suggestion = createUncertainGeolocationSuggestion();
+        LocationTimeZoneAlgorithmStatus algorithmStatus = new LocationTimeZoneAlgorithmStatus(
+                DETECTION_ALGORITHM_STATUS_RUNNING,
+                PROVIDER_STATUS_IS_UNCERTAIN, primaryProviderReportedStatus,
+                PROVIDER_STATUS_NOT_PRESENT, null);
+        LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
+        event.addDebugInfo("Test uncertain event");
+        return event;
+    }
+
     private GeolocationTimeZoneSuggestion createUncertainGeolocationSuggestion() {
-        return GeolocationTimeZoneSuggestion.createCertainSuggestion(
-                mFakeEnvironment.elapsedRealtimeMillis(), null);
+        return GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+                mFakeEnvironment.elapsedRealtimeMillis());
     }
 
     private GeolocationTimeZoneSuggestion createCertainGeolocationSuggestion(
             @NonNull String... zoneIds) {
         assertNotNull(zoneIds);
 
-        GeolocationTimeZoneSuggestion suggestion =
-                GeolocationTimeZoneSuggestion.createCertainSuggestion(
-                        mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds));
-        suggestion.addDebugInfo("Test suggestion");
-        return suggestion;
+        return GeolocationTimeZoneSuggestion.createCertainSuggestion(
+                mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds));
     }
 
     static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment {
 
         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 +1744,6 @@
         }
 
         @Override
-        public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
-            mConfigurationInternalChangeListener = listener;
-        }
-
-        @Override
-        public ConfigurationInternal getCurrentUserConfigurationInternal() {
-            return mConfigurationInternal;
-        }
-
-        @Override
         public String getDeviceTimeZone() {
             return mTimeZoneId.getLatest();
         }
@@ -1254,11 +1760,6 @@
             mTimeZoneConfidence.set(confidence);
         }
 
-        void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
-            mConfigurationInternal = configurationInternal;
-            mConfigurationInternalChangeListener.onChange();
-        }
-
         void assertTimeZoneNotChanged() {
             mTimeZoneId.assertHasNotBeenSet();
             mTimeZoneConfidence.assertHasNotBeenSet();
@@ -1296,6 +1797,14 @@
         }
     }
 
+    private void assertStateChangeNotificationsSent(
+            TestStateChangeListener stateChangeListener, int expectedCount) {
+        // State change notifications are asynchronous, so we have to wait.
+        mTestHandler.waitForMessagesToBeProcessed();
+
+        stateChangeListener.assertNotificationsReceivedAndReset(expectedCount);
+    }
+
     /**
      * A "fluent" class allows reuse of code in tests: initialization, simulation and verification
      * logic.
@@ -1313,6 +1822,11 @@
             return this;
         }
 
+        Script registerStateChangeListener(StateChangeListener stateChangeListener) {
+            mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
+            return this;
+        }
+
         Script simulateIncrementClock() {
             mFakeEnvironment.incrementClock();
             return this;
@@ -1322,7 +1836,8 @@
          * Simulates the user / user's configuration changing.
          */
         Script simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
-            mFakeEnvironment.simulateConfigurationInternalChange(configurationInternal);
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    configurationInternal);
             return this;
         }
 
@@ -1331,7 +1846,7 @@
          */
         Script simulateSetAutoMode(boolean autoDetectionEnabled) {
             ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
-                    mFakeEnvironment.getCurrentUserConfigurationInternal())
+                    mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal())
                     .setAutoDetectionEnabledSetting(autoDetectionEnabled)
                     .build();
             simulateConfigurationInternalChange(newConfig);
@@ -1343,7 +1858,7 @@
          */
         Script simulateSetGeoDetectionEnabled(boolean geoDetectionEnabled) {
             ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
-                    mFakeEnvironment.getCurrentUserConfigurationInternal())
+                    mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal())
                     .setGeoDetectionEnabledSetting(geoDetectionEnabled)
                     .build();
             simulateConfigurationInternalChange(newConfig);
@@ -1351,11 +1866,10 @@
         }
 
         /**
-         * Simulates the time zone detection strategy receiving a geolocation-originated
-         * suggestion.
+         * Simulates the time zone detection strategy receiving a location algorithm event.
          */
-        Script simulateGeolocationTimeZoneSuggestion(GeolocationTimeZoneSuggestion suggestion) {
-            mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(suggestion);
+        Script simulateLocationAlgorithmEvent(LocationAlgorithmEvent event) {
+            mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent(event);
             return this;
         }
 
@@ -1378,11 +1892,12 @@
         }
 
         /**
-         * Simulates the time zone detection strategty receiving a signal that allows it to do
+         * Simulates the time zone detection strategy receiving a signal that allows it to do
          * telephony fallback.
          */
         Script simulateEnableTelephonyFallback() {
-            mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
+            mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(
+                    "simulateEnableTelephonyFallback()");
             return this;
         }
 
@@ -1412,7 +1927,9 @@
             return this;
         }
 
-        Script verifyTimeZoneChangedAndReset(GeolocationTimeZoneSuggestion suggestion) {
+        Script verifyTimeZoneChangedAndReset(LocationAlgorithmEvent event) {
+            GeolocationTimeZoneSuggestion suggestion = event.getSuggestion();
+            assertNotNull("Only events with suggestions can change the time zone", suggestion);
             assertEquals("Only use this method with unambiguous geo suggestions",
                     1, suggestion.getZoneIds().size());
             verifyTimeZoneChangedAndReset(
@@ -1427,6 +1944,32 @@
             return this;
         }
 
+        Script verifyCachedDetectorStatus(TimeZoneDetectorStatus expectedStatus) {
+            assertEquals(expectedStatus,
+                    mTimeZoneDetectorStrategy.getCachedDetectorStatusForTests());
+            return this;
+        }
+
+        Script verifyLatestLocationAlgorithmEventReceived(LocationAlgorithmEvent expectedEvent) {
+            assertEquals(expectedEvent,
+                    mTimeZoneDetectorStrategy.getLatestLocationAlgorithmEvent());
+            return this;
+        }
+
+        Script verifyLatestTelephonySuggestionReceived(int slotIndex,
+                TelephonyTimeZoneSuggestion expectedSuggestion) {
+            assertEquals(expectedSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(slotIndex).suggestion);
+            return this;
+        }
+
+        Script verifyLatestQualifiedTelephonySuggestionReceived(int slotIndex,
+                QualifiedTelephonyTimeZoneSuggestion expectedQualifiedSuggestion) {
+            assertEquals(expectedQualifiedSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(slotIndex));
+            return this;
+        }
+
         Script resetConfigurationTracking() {
             mFakeEnvironment.commitAllChanges();
             return this;
@@ -1457,4 +2000,27 @@
             @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 assertNotificationsReceivedAndReset(int expectedCount) {
+            assertNotificationsReceived(expectedCount);
+            resetNotificationsReceivedCount();
+        }
+
+        private void resetNotificationsReceivedCount() {
+            mNotificationsReceived = 0;
+        }
+
+        private void assertNotificationsReceived(int expectedCount) {
+            assertEquals(expectedCount, mNotificationsReceived);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
index 34d0082..f8d169b 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
@@ -31,9 +31,9 @@
     @Override
     public TimeZoneProviderEvent preProcess(TimeZoneProviderEvent timeZoneProviderEvent) {
         if (mIsUncertain) {
+            TimeZoneProviderStatus timeZoneProviderStatus = null;
             return TimeZoneProviderEvent.createUncertainEvent(
-                    timeZoneProviderEvent.getCreationElapsedMillis(),
-                    TimeZoneProviderStatus.UNKNOWN);
+                    timeZoneProviderEvent.getCreationElapsedMillis(), timeZoneProviderStatus);
         }
         return timeZoneProviderEvent;
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
index ed426cd..7b1db95 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
@@ -15,11 +15,13 @@
  */
 package com.android.server.timezonedetector.location;
 
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
 import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
 
 import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_MANUAL;
 import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
@@ -42,6 +44,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -51,6 +54,7 @@
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.service.timezone.TimeZoneProviderEvent;
@@ -60,6 +64,7 @@
 
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
 import com.android.server.timezonedetector.TestState;
 import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderMetricsLogger;
 import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
@@ -87,9 +92,9 @@
             createSuggestionEvent(asList("Europe/Paris"));
     private static final TimeZoneProviderStatus UNCERTAIN_PROVIDER_STATUS =
             new TimeZoneProviderStatus.Builder()
-                    .setLocationDetectionStatus(DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE)
-                    .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                    .setTimeZoneResolutionStatus(OPERATION_STATUS_UNKNOWN)
+                    .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE)
+                    .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_UNKNOWN)
                     .build();
     private static final TimeZoneProviderEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT =
             TimeZoneProviderEvent.createUncertainEvent(
@@ -141,7 +146,7 @@
         mTestPrimaryLocationTimeZoneProvider.setFailDuringInitialization(true);
 
         // Initialize. After initialization the providers must be initialized and one should be
-        // started.
+        // started. They should report their status change via the callback.
         controller.initialize(testEnvironment, mTestCallback);
 
         mTestPrimaryLocationTimeZoneProvider.assertInitialized();
@@ -154,7 +159,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout);
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
 
@@ -184,7 +190,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
 
@@ -211,7 +218,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING, STATE_FAILED);
-        mTestCallback.assertUncertainSuggestionMadeAndCommit();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
 
@@ -239,7 +247,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
 
@@ -262,7 +271,8 @@
         mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
 
@@ -282,7 +292,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate time passing with no provider event being received from the primary.
@@ -296,7 +307,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
 
         // Simulate time passing with no provider event being received from either the primary or
@@ -311,7 +322,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
 
         // Finally, the uncertainty timeout should cause the controller to make an uncertain
@@ -324,7 +335,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN);
-        mTestCallback.assertUncertainSuggestionMadeAndCommit();
+        mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit();
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
 
@@ -345,7 +356,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate a location event being received from the primary provider. This should cause a
@@ -358,7 +370,7 @@
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
@@ -380,7 +392,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate time passing with no provider event being received from the primary.
@@ -392,7 +405,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
 
         // Simulate a location event being received from the primary provider. This should cause a
@@ -405,7 +418,7 @@
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
@@ -427,7 +440,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate time passing with no provider event being received from the primary.
@@ -439,7 +453,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
 
         // Simulate a location event being received from the secondary provider. This should cause a
@@ -453,7 +467,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
@@ -475,7 +489,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate a location event being received from the primary provider. This should cause a
@@ -488,7 +503,7 @@
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
@@ -501,7 +516,7 @@
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // And a third, different event should cause another suggestion.
@@ -513,7 +528,7 @@
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
@@ -535,7 +550,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate time passing with no provider event being received from the primary.
@@ -547,7 +563,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
 
         // Simulate a location event being received from the secondary provider. This should cause a
@@ -561,7 +577,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
@@ -575,7 +591,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // And a third, different event should cause another suggestion.
@@ -588,7 +604,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
@@ -610,7 +626,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate a location event being received from the primary provider. This should cause a
@@ -623,7 +640,7 @@
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
@@ -639,7 +656,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
 
         // Simulate a location event being received from the secondary provider. This should cause a
@@ -654,7 +671,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
@@ -670,7 +687,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
 
         // Simulate time passing. This means the uncertainty timeout should fire and the uncertain
@@ -683,7 +700,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN);
-        mTestCallback.assertUncertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit(
                 USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
@@ -705,7 +722,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate a location event being received from the primary provider. This should cause a
@@ -718,7 +736,7 @@
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
@@ -733,7 +751,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
 
         // And a success event from the primary provider should cause the controller to make another
@@ -747,7 +765,7 @@
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
@@ -767,7 +785,8 @@
         mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Now signal a config change so that geo detection is enabled.
@@ -778,7 +797,8 @@
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Now signal a config change so that geo detection is disabled.
@@ -788,7 +808,8 @@
         mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
 
@@ -807,7 +828,8 @@
         mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Now signal a config change so that geo detection is enabled.
@@ -818,7 +840,8 @@
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate a success event being received from the primary provider.
@@ -830,7 +853,7 @@
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
@@ -843,8 +866,9 @@
         assertControllerState(controller, STATE_STOPPED);
         mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
-        mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN, STATE_STOPPED);
-        mTestCallback.assertUncertainSuggestionMadeAndCommit();
+        mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
 
@@ -865,7 +889,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate the primary provider suggesting a time zone.
@@ -879,7 +904,7 @@
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
@@ -897,9 +922,9 @@
         mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfig(
                 PROVIDER_STATE_STARTED_INITIALIZING, USER2_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
-        mTestMetricsLogger.assertStateChangesAndCommit(
-                STATE_UNCERTAIN, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertUncertainSuggestionMadeAndCommit();
+        mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED, STATE_INITIALIZING);
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
 
@@ -920,7 +945,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate a failure location event being received from the primary provider. This should
@@ -933,7 +959,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate uncertainty from the secondary.
@@ -945,7 +971,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
 
         // And a success event from the secondary provider should cause the controller to make
@@ -958,7 +984,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
@@ -971,7 +997,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
     }
 
@@ -992,7 +1018,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate a failure location event being received from the primary provider. This should
@@ -1005,7 +1032,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Now signal a config change so that geo detection is disabled.
@@ -1015,7 +1042,8 @@
         mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Now signal a config change so that geo detection is enabled.
@@ -1026,7 +1054,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
 
@@ -1047,7 +1076,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate an uncertain event from the primary. This will start the secondary, which will
@@ -1062,7 +1092,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
 
         // Simulate failure event from the secondary. This should just affect the secondary's state.
@@ -1074,7 +1104,7 @@
                 PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
 
         // And a success event from the primary provider should cause the controller to make
@@ -1087,7 +1117,7 @@
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
@@ -1100,7 +1130,7 @@
                 PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
     }
 
@@ -1121,7 +1151,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate an uncertain event from the primary. This will start the secondary, which will
@@ -1136,7 +1167,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
 
         // Simulate failure event from the secondary. This should just affect the secondary's state.
@@ -1148,7 +1179,7 @@
                 PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertUncertaintyTimeoutSet(testEnvironment, controller);
 
         // Now signal a config change so that geo detection is disabled.
@@ -1158,7 +1189,8 @@
         mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Now signal a config change so that geo detection is enabled. Only the primary can be
@@ -1170,7 +1202,8 @@
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
 
@@ -1191,7 +1224,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate a failure event from the primary. This will start the secondary.
@@ -1203,7 +1237,7 @@
         mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
                 PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit();
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertNoEventReported();
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate failure event from the secondary.
@@ -1214,7 +1248,8 @@
         mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
         mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_FAILED);
-        mTestCallback.assertUncertainSuggestionMadeAndCommit();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
 
@@ -1233,7 +1268,7 @@
         {
             LocationTimeZoneManagerServiceState state = controller.getStateForTests();
             assertEquals(STATE_INITIALIZING, state.getControllerState());
-            assertNull(state.getLastSuggestion());
+            assertNull(state.getLastEvent().getSuggestion());
             assertControllerRecordedStates(state,
                     STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
             assertProviderStates(state.getPrimaryProviderStates(),
@@ -1251,7 +1286,7 @@
         {
             LocationTimeZoneManagerServiceState state = controller.getStateForTests();
             assertEquals(STATE_INITIALIZING, state.getControllerState());
-            assertNull(state.getLastSuggestion());
+            assertNull(state.getLastEvent().getSuggestion());
             assertControllerRecordedStates(state);
             assertProviderStates(
                     state.getPrimaryProviderStates(), PROVIDER_STATE_STARTED_UNCERTAIN);
@@ -1268,7 +1303,7 @@
             LocationTimeZoneManagerServiceState state = controller.getStateForTests();
             assertEquals(STATE_CERTAIN, state.getControllerState());
             assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
-                    state.getLastSuggestion().getZoneIds());
+                    state.getLastEvent().getSuggestion().getZoneIds());
             assertControllerRecordedStates(state, STATE_CERTAIN);
             assertProviderStates(state.getPrimaryProviderStates());
             assertProviderStates(
@@ -1280,7 +1315,7 @@
             LocationTimeZoneManagerServiceState state = controller.getStateForTests();
             assertEquals(STATE_CERTAIN, state.getControllerState());
             assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
-                    state.getLastSuggestion().getZoneIds());
+                    state.getLastEvent().getSuggestion().getZoneIds());
             assertControllerRecordedStates(state);
             assertProviderStates(state.getPrimaryProviderStates());
             assertProviderStates(state.getSecondaryProviderStates());
@@ -1313,7 +1348,8 @@
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(
                 STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
-        mTestCallback.assertNoSuggestionMade();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
         // Simulate the primary provider suggesting a time zone.
@@ -1327,7 +1363,7 @@
                 PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
-        mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
         assertFalse(controller.isUncertaintyTimeoutSet());
 
@@ -1335,11 +1371,11 @@
         controller.destroy();
 
         assertControllerState(controller, STATE_DESTROYED);
-        mTestMetricsLogger.assertStateChangesAndCommit(
-                STATE_UNCERTAIN, STATE_STOPPED, STATE_DESTROYED);
+        mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED, STATE_DESTROYED);
 
         // Confirm that the previous suggestion was overridden.
-        mTestCallback.assertUncertainSuggestionMadeAndCommit();
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
 
         mTestPrimaryLocationTimeZoneProvider.assertStateChangesAndCommit(
                 PROVIDER_STATE_STOPPED, PROVIDER_STATE_DESTROYED);
@@ -1405,9 +1441,9 @@
 
     private static TimeZoneProviderEvent createSuggestionEvent(@NonNull List<String> timeZoneIds) {
         TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
-                .setConnectivityStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
                 .build();
         TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
                 .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
@@ -1517,63 +1553,101 @@
 
     private static class TestCallback extends LocationTimeZoneProviderController.Callback {
 
-        private TestState<GeolocationTimeZoneSuggestion> mLatestSuggestion = new TestState<>();
+        private TestState<LocationAlgorithmEvent> mLatestEvent = new TestState<>();
 
         TestCallback(ThreadingDomain threadingDomain) {
             super(threadingDomain);
         }
 
         @Override
-        void suggest(GeolocationTimeZoneSuggestion suggestion) {
-            mLatestSuggestion.set(suggestion);
+        void sendEvent(LocationAlgorithmEvent event) {
+            mLatestEvent.set(event);
         }
 
-        void assertCertainSuggestionMadeFromEventAndCommit(TimeZoneProviderEvent event) {
+        void assertNoEventReported() {
+            mLatestEvent.assertHasNotBeenSet();
+        }
+
+        /**
+         * Asserts one or more events have been reported, and the most recent does not contain a
+         * suggestion.
+         */
+        void assertEventWithNoSuggestionReportedAndCommit(
+                @DetectionAlgorithmStatus int expectedAlgorithmStatus) {
+            mLatestEvent.assertHasBeenSet();
+
+            LocationAlgorithmEvent latest = mLatestEvent.getLatest();
+            assertEquals(expectedAlgorithmStatus, latest.getAlgorithmStatus().getStatus());
+            assertNull(latest.getSuggestion());
+            mLatestEvent.commitLatest();
+        }
+
+        void assertEventWithCertainSuggestionReportedAndCommit(TimeZoneProviderEvent event) {
             // Test coding error if this fails.
             assertEquals(TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION, event.getType());
 
+            // By definition, the algorithm has to be running to report a suggestion.
+            @DetectionAlgorithmStatus int expectedAlgorithmStatus =
+                    DETECTION_ALGORITHM_STATUS_RUNNING;
             TimeZoneProviderSuggestion suggestion = event.getSuggestion();
-            assertSuggestionMadeAndCommit(
+            assertEventWithSuggestionReportedAndCommit(
+                    expectedAlgorithmStatus,
                     suggestion.getElapsedRealtimeMillis(),
                     suggestion.getTimeZoneIds());
         }
 
-        void assertNoSuggestionMade() {
-            mLatestSuggestion.assertHasNotBeenSet();
-        }
-
-        /** Asserts that an uncertain suggestion has been made from the supplied event. */
-        void assertUncertainSuggestionMadeFromEventAndCommit(TimeZoneProviderEvent event) {
+        /**
+         * Asserts that one or more events have been reported, and the most recent contains an
+         * uncertain suggestion matching select details from the supplied provider event.
+         */
+        void assertEventWithUncertainSuggestionReportedAndCommit(TimeZoneProviderEvent event) {
             // Test coding error if this fails.
             assertEquals(TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN, event.getType());
 
-            assertSuggestionMadeAndCommit(event.getCreationElapsedMillis(), null);
+            // By definition, the algorithm has to be running to report a suggestion.
+            @DetectionAlgorithmStatus int expectedAlgorithmStatus =
+                    DETECTION_ALGORITHM_STATUS_RUNNING;
+            assertEventWithSuggestionReportedAndCommit(
+                    expectedAlgorithmStatus, event.getCreationElapsedMillis(), null);
         }
 
         /**
-         * Asserts that an uncertain suggestion has been made.
-         * Ignores the suggestion's effectiveFromElapsedMillis.
+         * Asserts that one or more events have been reported, and the most recent contains an
+         * uncertain suggestion. Ignores the suggestion's effectiveFromElapsedMillis.
          */
-        void assertUncertainSuggestionMadeAndCommit() {
+        void assertEventWithUncertainSuggestionReportedAndCommit() {
+            // By definition, the algorithm has to be running to report a suggestion.
+            @DetectionAlgorithmStatus int expectedAlgorithmStatus =
+                    DETECTION_ALGORITHM_STATUS_RUNNING;
+
             // An "uncertain" suggestion has null time zone IDs.
-            assertSuggestionMadeAndCommit(null, null);
+            assertEventWithSuggestionReportedAndCommit(expectedAlgorithmStatus, null, null);
         }
 
         /**
-         * Asserts that a suggestion has been made and some properties of that suggestion.
-         * When expectedEffectiveFromElapsedMillis is null then its value isn't checked.
+         * Asserts that an event has been reported containing a suggestion and some properties of
+         * that suggestion. When expectedEffectiveFromElapsedMillis is null then its value isn't
+         * checked.
          */
-        private void assertSuggestionMadeAndCommit(
+        private void assertEventWithSuggestionReportedAndCommit(
+                @DetectionAlgorithmStatus int expectedAlgorithmStatus,
                 @Nullable @ElapsedRealtimeLong Long expectedEffectiveFromElapsedMillis,
                 @Nullable List<String> expectedZoneIds) {
-            mLatestSuggestion.assertHasBeenSet();
+            mLatestEvent.assertHasBeenSet();
+
+            LocationAlgorithmEvent latestEvent = mLatestEvent.getLatest();
+            assertEquals(expectedAlgorithmStatus, latestEvent.getAlgorithmStatus().getStatus());
+
+            GeolocationTimeZoneSuggestion suggestion = latestEvent.getSuggestion();
+            assertNotNull("Latest event doesn't contain a suggestion: event=" + latestEvent,
+                    suggestion);
+
             if (expectedEffectiveFromElapsedMillis != null) {
-                assertEquals(
-                        expectedEffectiveFromElapsedMillis.longValue(),
-                        mLatestSuggestion.getLatest().getEffectiveFromElapsedMillis());
+                assertEquals(expectedEffectiveFromElapsedMillis.longValue(),
+                        suggestion.getEffectiveFromElapsedMillis());
             }
-            assertEquals(expectedZoneIds, mLatestSuggestion.getLatest().getZoneIds());
-            mLatestSuggestion.commitLatest();
+            assertEquals(expectedZoneIds, suggestion.getZoneIds());
+            mLatestEvent.commitLatest();
         }
     }
 
@@ -1598,11 +1672,9 @@
         }
 
         @Override
-        void onInitialize() {
+        boolean onInitialize() {
             mInitialized = true;
-            if (mFailDuringInitialization) {
-                throw new RuntimeException("Simulated initialization failure");
-            }
+            return !mFailDuringInitialization;
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
index 8429fa4..1ae74c6 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.server.timezonedetector.location;
 
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
+
 import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
 import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
 import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
@@ -57,6 +60,12 @@
 public class LocationTimeZoneProviderTest {
 
     private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 123456789L;
+    private static final TimeZoneProviderStatus ARBITRARY_PROVIDER_STATUS =
+            new TimeZoneProviderStatus.Builder()
+                    .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+                    .build();
 
     private TestThreadingDomain mTestThreadingDomain;
     private TestProviderListener mProviderListener;
@@ -80,91 +89,108 @@
                 mTimeZoneProviderEventPreProcessor);
 
         // initialize()
-        provider.initialize(mProviderListener);
-        provider.assertOnInitializeCalled();
+        {
+            provider.initialize(mProviderListener);
+            provider.assertOnInitializeCalled();
 
-        ProviderState currentState = assertAndReturnProviderState(
-                provider, providerMetricsLogger, PROVIDER_STATE_STOPPED);
-        assertNull(currentState.currentUserConfiguration);
-        assertSame(provider, currentState.provider);
-        mTestThreadingDomain.assertQueueEmpty();
+            ProviderState currentState = assertAndReturnProviderState(
+                    provider, providerMetricsLogger, PROVIDER_STATE_STOPPED,
+                    /*expectedReportedStatus=*/null);
+            assertNull(currentState.currentUserConfiguration);
+            assertSame(provider, currentState.provider);
+            mTestThreadingDomain.assertQueueEmpty();
+        }
+
+        ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
 
         // startUpdates()
-        ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
-        Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
-        Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
-        Duration arbitraryEventFilteringAgeThreshold = Duration.ofMinutes(3);
-        provider.startUpdates(config, arbitraryInitializationTimeout,
-                arbitraryInitializationTimeoutFuzz, arbitraryEventFilteringAgeThreshold);
+        {
+            Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
+            Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
+            Duration arbitraryEventFilteringAgeThreshold = Duration.ofMinutes(3);
+            provider.startUpdates(config, arbitraryInitializationTimeout,
+                    arbitraryInitializationTimeoutFuzz, arbitraryEventFilteringAgeThreshold);
 
-        provider.assertOnStartCalled(
-                arbitraryInitializationTimeout, arbitraryEventFilteringAgeThreshold);
+            provider.assertOnStartCalled(
+                    arbitraryInitializationTimeout, arbitraryEventFilteringAgeThreshold);
 
-        currentState = assertAndReturnProviderState(
-                provider, providerMetricsLogger, PROVIDER_STATE_STARTED_INITIALIZING);
-        assertSame(provider, currentState.provider);
-        assertEquals(config, currentState.currentUserConfiguration);
-        assertNull(currentState.event);
-        // The initialization timeout should be queued.
-        Duration expectedInitializationTimeout =
-                arbitraryInitializationTimeout.plus(arbitraryInitializationTimeoutFuzz);
-        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedInitializationTimeout);
-        // We don't intend to trigger the timeout, so clear it.
-        mTestThreadingDomain.removeAllQueuedRunnables();
+            ProviderState currentState = assertAndReturnProviderState(
+                    provider, providerMetricsLogger, PROVIDER_STATE_STARTED_INITIALIZING,
+                    /*expectedReportedStatus=*/null);
+            assertSame(provider, currentState.provider);
+            assertEquals(config, currentState.currentUserConfiguration);
+            assertNull(currentState.event);
+            // The initialization timeout should be queued.
+            Duration expectedInitializationTimeout =
+                    arbitraryInitializationTimeout.plus(arbitraryInitializationTimeoutFuzz);
+            mTestThreadingDomain.assertSingleDelayedQueueItem(expectedInitializationTimeout);
+            // We don't intend to trigger the timeout, so clear it.
+            mTestThreadingDomain.removeAllQueuedRunnables();
 
-        // Entering started does not trigger an onProviderStateChanged() as it is requested by the
-        // controller.
-        mProviderListener.assertProviderChangeNotReported();
+            // Entering started does not trigger an onProviderStateChanged() as it is requested by
+            // the controller.
+            mProviderListener.assertProviderChangeNotReported();
+        }
 
         // Simulate a suggestion event being received.
-        TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
-                .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
-                .setTimeZoneIds(Arrays.asList("Europe/London"))
-                .build();
-        TimeZoneProviderStatus providerStatus = TimeZoneProviderStatus.UNKNOWN;
-        TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
-                ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion, providerStatus);
-        provider.simulateProviderEventReceived(event);
+        {
+            TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+                    .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+                    .setTimeZoneIds(Arrays.asList("Europe/London"))
+                    .build();
+            TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
+                    ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion, ARBITRARY_PROVIDER_STATUS);
+            provider.simulateProviderEventReceived(event);
 
-        currentState = assertAndReturnProviderState(
-                provider, providerMetricsLogger, PROVIDER_STATE_STARTED_CERTAIN);
-        assertSame(provider, currentState.provider);
-        assertEquals(event, currentState.event);
-        assertEquals(config, currentState.currentUserConfiguration);
-        mTestThreadingDomain.assertQueueEmpty();
-        mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_CERTAIN);
+            ProviderState currentState = assertAndReturnProviderState(
+                    provider, providerMetricsLogger, PROVIDER_STATE_STARTED_CERTAIN,
+                    ARBITRARY_PROVIDER_STATUS);
+            assertSame(provider, currentState.provider);
+            assertEquals(event, currentState.event);
+            assertEquals(config, currentState.currentUserConfiguration);
+            mTestThreadingDomain.assertQueueEmpty();
+            mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_CERTAIN);
+        }
 
         // Simulate an uncertain event being received.
-        event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS,
-                TimeZoneProviderStatus.UNKNOWN);
-        provider.simulateProviderEventReceived(event);
+        {
+            TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(
+                    ARBITRARY_ELAPSED_REALTIME_MILLIS, ARBITRARY_PROVIDER_STATUS);
+            provider.simulateProviderEventReceived(event);
 
-        currentState = assertAndReturnProviderState(
-                provider, providerMetricsLogger, PROVIDER_STATE_STARTED_UNCERTAIN);
-        assertSame(provider, currentState.provider);
-        assertEquals(event, currentState.event);
-        assertEquals(config, currentState.currentUserConfiguration);
-        mTestThreadingDomain.assertQueueEmpty();
-        mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_UNCERTAIN);
+            ProviderState currentState = assertAndReturnProviderState(
+                    provider, providerMetricsLogger, PROVIDER_STATE_STARTED_UNCERTAIN,
+                    ARBITRARY_PROVIDER_STATUS);
+            assertSame(provider, currentState.provider);
+            assertEquals(event, currentState.event);
+            assertEquals(config, currentState.currentUserConfiguration);
+            mTestThreadingDomain.assertQueueEmpty();
+            mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_UNCERTAIN);
+        }
 
         // stopUpdates()
-        provider.stopUpdates();
-        provider.assertOnStopUpdatesCalled();
+        {
+            provider.stopUpdates();
+            provider.assertOnStopUpdatesCalled();
 
-        currentState = assertAndReturnProviderState(
-                provider, providerMetricsLogger, PROVIDER_STATE_STOPPED);
-        assertSame(provider, currentState.provider);
-        assertEquals(PROVIDER_STATE_STOPPED, currentState.stateEnum);
-        assertNull(currentState.event);
-        assertNull(currentState.currentUserConfiguration);
-        mTestThreadingDomain.assertQueueEmpty();
-        // Entering stopped does not trigger an onProviderStateChanged() as it is requested by the
-        // controller.
-        mProviderListener.assertProviderChangeNotReported();
+            ProviderState currentState = assertAndReturnProviderState(
+                    provider, providerMetricsLogger, PROVIDER_STATE_STOPPED,
+                    /*expectedReportedStatus=*/null);
+            assertSame(provider, currentState.provider);
+            assertEquals(PROVIDER_STATE_STOPPED, currentState.stateEnum);
+            assertNull(currentState.event);
+            assertNull(currentState.currentUserConfiguration);
+            mTestThreadingDomain.assertQueueEmpty();
+            // Entering stopped does not trigger an onProviderStateChanged() as it is requested by
+            // the controller.
+            mProviderListener.assertProviderChangeNotReported();
+        }
 
         // destroy()
-        provider.destroy();
-        provider.assertOnDestroyCalled();
+        {
+            provider.destroy();
+            provider.assertOnDestroyCalled();
+        }
     }
 
     @Test
@@ -196,13 +222,12 @@
                 .setTimeZoneIds(Arrays.asList("Europe/London"))
                 .build();
         TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
-                ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion, TimeZoneProviderStatus.UNKNOWN);
+                ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion, null);
         provider.simulateProviderEventReceived(event);
         provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_CERTAIN);
 
         // Simulate an uncertain event being received.
-        event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS,
-                TimeZoneProviderStatus.UNKNOWN);
+        event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS, null);
         provider.simulateProviderEventReceived(event);
         provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN);
 
@@ -239,7 +264,7 @@
                 .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
                 .setTimeZoneIds(invalidTimeZoneIds)
                 .build();
-        TimeZoneProviderStatus providerStatus = TimeZoneProviderStatus.UNKNOWN;
+        TimeZoneProviderStatus providerStatus = null;
         TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
                 ARBITRARY_ELAPSED_REALTIME_MILLIS, invalidIdSuggestion, providerStatus);
         provider.simulateProviderEventReceived(event);
@@ -275,9 +300,11 @@
      */
     private static ProviderState assertAndReturnProviderState(
             TestLocationTimeZoneProvider provider,
-            RecordingProviderMetricsLogger providerMetricsLogger, int expectedStateEnum) {
+            RecordingProviderMetricsLogger providerMetricsLogger, int expectedStateEnum,
+            TimeZoneProviderStatus expectedReportedStatus) {
         ProviderState currentState = provider.getCurrentState();
         assertEquals(expectedStateEnum, currentState.stateEnum);
+        assertEquals(expectedReportedStatus, currentState.getReportedStatus());
         providerMetricsLogger.assertChangeLoggedAndRemove(expectedStateEnum);
         providerMetricsLogger.assertNoMoreLogEntries();
         return currentState;
@@ -303,8 +330,9 @@
         }
 
         @Override
-        void onInitialize() {
+        boolean onInitialize() {
             mOnInitializeCalled = true;
+            return true;
         }
 
         @Override
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/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
index c478604..f3440f7 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
@@ -16,9 +16,9 @@
 
 package com.android.server.timezonedetector.location;
 
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
 import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -61,7 +61,7 @@
 
             TimeZoneProviderStatus expectedProviderStatus =
                     new TimeZoneProviderStatus.Builder(event.getTimeZoneProviderStatus())
-                            .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+                            .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
                             .build();
 
             TimeZoneProviderEvent expectedResultEvent =
@@ -75,9 +75,9 @@
 
     private static TimeZoneProviderEvent timeZoneProviderEvent(String... timeZoneIds) {
         TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
                 .build();
         TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
                 .setTimeZoneIds(Arrays.asList(timeZoneIds))
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 4762696..febbffe 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,19 +53,28 @@
         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 testThatPrintWriterProducesOnlyTitleFromEmptyLog() {
             mEventLogger.dump(mTestPrintWriter);
-            assertThat(mTestStringWriter.toString()).isEmpty();
+            assertThat(mTestStringWriter.toString())
+                    .isEqualTo(mEventLogger.getDumpTitle() + "\n");
         }
     }
 
@@ -77,35 +88,37 @@
                         // insertion order, max size is 3
                         new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_2 },
                         // expected events
-                        new EventLogger.Event[] { TEST_EVENT_2, TEST_EVENT_1 }
+                        new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_2 }
                     },
                     {
                         // insertion order, max size is 3
                         new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_3, TEST_EVENT_2 },
                         // expected events
-                        new EventLogger.Event[] { TEST_EVENT_2, TEST_EVENT_3, TEST_EVENT_1 }
+                        new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_3, TEST_EVENT_2 }
                     },
                     {
                         // insertion order, max size is 3
                         new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_2, TEST_EVENT_3,
                             TEST_EVENT_4 },
                         // expected events
-                        new EventLogger.Event[] { TEST_EVENT_4, TEST_EVENT_3, TEST_EVENT_2 }
+                        new EventLogger.Event[] { TEST_EVENT_2, TEST_EVENT_3, TEST_EVENT_4 }
                     },
                     {
                         // insertion order, max size is 3
                         new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_2, TEST_EVENT_3,
                             TEST_EVENT_4, TEST_EVENT_5 },
                         // expected events
-                        new EventLogger.Event[] { TEST_EVENT_5, TEST_EVENT_4, TEST_EVENT_3 }
+                        new EventLogger.Event[] { TEST_EVENT_3, TEST_EVENT_4, TEST_EVENT_5 }
                     }
             });
         }
 
+        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,11 +132,25 @@
 
         @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.enqueue(event);
+            }
+
+            mEventLogger.dump(mTestConsumer);
+
+            assertThat(mTestConsumer.getLastKnownConsumedEvents())
+                    .containsExactlyElementsIn(mExpectedEvents);
+        }
+
+
+        @Test
+        public void testThatPrintWriterDumpsEventsAsExpected() {
             for (EventLogger.Event event: mEventsToInsert) {
                 mEventLogger.enqueue(event);
             }
@@ -149,11 +176,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/servicestests/src/com/android/server/utils/OWNERS b/services/tests/servicestests/src/com/android/server/utils/OWNERS
index d1a36bb..5e24828 100644
--- a/services/tests/servicestests/src/com/android/server/utils/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/utils/OWNERS
@@ -1,6 +1,2 @@
-per-file WatchableTester.java = file:/services/core/java/com/android/server/pm/OWNERS
-per-file WatchableTester.java = shombert@google.com
-per-file WatcherTest.java = file:/services/core/java/com/android/server/pm/OWNERS
-per-file WatcherTest.java = shombert@google.com
 per-file EventLoggerTest.java = file:/platform/frameworks/av:/media/janitors/media_solutions_OWNERS
 per-file EventLoggerTest.java = jmtrivi@google.com
diff --git a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
deleted file mode 100644
index 37c95f7..0000000
--- a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
+++ /dev/null
@@ -1,1258 +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.utils;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.LongSparseArray;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Random;
-
-/**
- * Test class for various utility classes that support the Watchable or Snappable
- * features.  This covers {@link Watcher}, {@link Watchable}, {@link WatchableImpl},
- * {@link WatchedArrayMap}, {@link WatchedSparseArray}, and
- * {@link WatchedSparseBooleanArray}.
- *
- * Build/Install/Run:
- *  atest FrameworksServicesTests:WatcherTest
- */
-@SmallTest
-public class WatcherTest {
-
-    // A counter to generate unique IDs for Leaf elements.
-    private int mLeafId = 0;
-
-    // Useful indices used in the tests.
-    private static final int INDEX_A = 1;
-    private static final int INDEX_B = 2;
-    private static final int INDEX_C = 3;
-    private static final int INDEX_D = 4;
-
-    // A small Watchable leaf node
-    private class Leaf extends WatchableImpl implements Snappable {
-        private int mId;
-        private int mDatum;
-
-        Leaf() {
-            mDatum = 0;
-            mId = mLeafId++;
-        }
-
-        void set(int i) {
-            if (mDatum != i) {
-                mDatum = i;
-                dispatchChange(this);
-            }
-        }
-        int get() {
-            return mDatum;
-        }
-        void tick() {
-            set(mDatum + 1);
-        }
-        public Leaf snapshot() {
-            Leaf result = new Leaf();
-            result.mDatum = mDatum;
-            result.mId = mId;
-            result.seal();
-            return result;
-        }
-        @Override
-        public boolean equals(Object o) {
-            if (o instanceof Leaf) {
-                return mDatum == ((Leaf) o).mDatum && mId == ((Leaf) o).mId;
-            } else {
-                return false;
-            }
-        }
-        @Override
-        public String toString() {
-            return "Leaf(" + mDatum + "," + mId + ")";
-        }
-    }
-
-    // Execute the {@link Runnable} and if {@link UnsupportedOperationException} is
-    // thrown, do nothing.  If no exception is thrown, fail the test.
-    private void verifySealed(String msg, Runnable test) {
-        try {
-            test.run();
-            fail(msg + " should be sealed");
-        } catch (IllegalStateException e) {
-            // The exception was expected.
-        }
-    }
-
-    // Execute the {@link Runnable} and if {@link UnsupportedOperationException} is
-    // thrown, fail the test.  If no exception is thrown, do nothing.
-    private void verifyNotSealed(String msg, Runnable test) {
-        try {
-            test.run();
-        } catch (IllegalStateException e) {
-            fail(msg + " should be not sealed");
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-    }
-
-    @After
-    public void tearDown() throws Exception {
-    }
-
-    @Test
-    public void testBasicBehavior() {
-        WatchableTester tester;
-
-        // Create a few leaves
-        Leaf leafA = new Leaf();
-
-        // Basic test.  Create a leaf and verify that changes to the leaf get notified to
-        // the tester.
-        tester = new WatchableTester(leafA, "Leaf");
-        tester.verify(0, "Initial leaf - no registration");
-        leafA.tick();
-        tester.verify(0, "Updates with no registration");
-        tester.register();
-        leafA.tick();
-        tester.verify(1, "Updates with registration");
-        leafA.tick();
-        leafA.tick();
-        tester.verify(3, "Updates with registration");
-        // Create a snapshot.  Verify that the snapshot matches the
-        Leaf leafASnapshot = leafA.snapshot();
-        assertEquals("Leaf snapshot", leafA.get(), leafASnapshot.get());
-        leafA.tick();
-        assertTrue(leafA.get() != leafASnapshot.get());
-        tester.verify(4, "Tick after snapshot");
-        verifySealed("Leaf", ()->leafASnapshot.tick());
-
-        // Add the same leaf to more than one tester.  Verify that a change to the leaf is seen by
-        // all registered listeners.
-        tester.clear();
-        WatchableTester buddy1 = new WatchableTester(leafA, "Leaf2");
-        WatchableTester buddy2 = new WatchableTester(leafA, "Leaf3");
-        buddy1.verify(0, "Initial leaf - no registration");
-        buddy2.verify(0, "Initial leaf - no registration");
-        leafA.tick();
-        tester.verify(1, "Updates with buddies");
-        buddy1.verify(0, "Updates - no registration");
-        buddy2.verify(0, "Updates - no registration");
-        buddy1.register();
-        buddy2.register();
-        buddy1.verify(0, "No updates - registered");
-        buddy2.verify(0, "No updates - registered");
-        leafA.tick();
-        buddy1.verify(1, "First update");
-        buddy2.verify(1, "First update");
-        buddy1.unregister();
-        leafA.tick();
-        buddy1.verify(1, "Second update - unregistered");
-        buddy2.verify(2, "Second update");
-    }
-
-    @Test
-    public void testWatchedArrayMap() {
-        final String name = "WatchedArrayMap";
-        WatchableTester tester;
-
-        // Create a few leaves
-        Leaf leafA = new Leaf();
-        Leaf leafB = new Leaf();
-        Leaf leafC = new Leaf();
-        Leaf leafD = new Leaf();
-
-        // Test WatchedArrayMap
-        WatchedArrayMap<Integer, Leaf> array = new WatchedArrayMap<>();
-        array.put(INDEX_A, leafA);
-        array.put(INDEX_B, leafB);
-        tester = new WatchableTester(array, name);
-        tester.verify(0, "Initial array - no registration");
-        leafA.tick();
-        tester.verify(0, "Updates with no registration");
-        tester.register();
-        tester.verify(0, "Updates with no registration");
-        leafA.tick();
-        tester.verify(1, "Updates with registration");
-        leafB.tick();
-        tester.verify(2, "Updates with registration");
-        array.remove(INDEX_B);
-        tester.verify(3, "Removed b");
-        leafB.tick();
-        tester.verify(3, "Updates with b not watched");
-        array.put(INDEX_B, leafB);
-        array.put(INDEX_C, leafB);
-        tester.verify(5, "Added b twice");
-        leafB.tick();
-        tester.verify(6, "Changed b - single notification");
-        array.remove(INDEX_C);
-        tester.verify(7, "Removed first b");
-        leafB.tick();
-        tester.verify(8, "Changed b - single notification");
-        array.remove(INDEX_B);
-        tester.verify(9, "Removed second b");
-        leafB.tick();
-        tester.verify(9, "Updated b - no change");
-        array.clear();
-        tester.verify(10, "Cleared array");
-        leafB.tick();
-        tester.verify(10, "Change to b not in array");
-
-        // Special methods
-        array.put(INDEX_C, leafC);
-        tester.verify(11, "Added c");
-        leafC.tick();
-        tester.verify(12, "Ticked c");
-        array.setValueAt(array.indexOfKey(INDEX_C), leafD);
-        tester.verify(13, "Replaced c with d");
-        leafC.tick();
-        leafD.tick();
-        tester.verify(14, "Ticked d and c (c not registered)");
-
-        // Snapshot
-        {
-            final WatchedArrayMap<Integer, Leaf> arraySnap = array.snapshot();
-            tester.verify(14, "Generate snapshot (no changes)");
-            // Verify that the snapshot is a proper copy of the source.
-            assertEquals(name + " snap same size",
-                         array.size(), arraySnap.size());
-            for (int i = 0; i < array.size(); i++) {
-                for (int j = 0; j < arraySnap.size(); j++) {
-                    assertTrue(name + " elements differ",
-                               array.valueAt(i) != arraySnap.valueAt(j));
-                }
-                assertTrue(name + " element copy",
-                           array.valueAt(i).equals(arraySnap.valueAt(i)));
-            }
-            leafD.tick();
-            tester.verify(15, "Tick after snapshot");
-            // Verify that the snapshot is sealed
-            verifySealed(name, ()->arraySnap.put(INDEX_A, leafA));
-            assertTrue(!array.isSealed());
-            assertTrue(arraySnap.isSealed());
-        }
-        // Recreate the snapshot since the test corrupted it.
-        {
-            final WatchedArrayMap<Integer, Leaf> arraySnap = array.snapshot();
-            // Verify that elements are also snapshots
-            final Leaf arraySnapElement = arraySnap.valueAt(0);
-            verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick());
-        }
-        // Verify copy-in/out
-        {
-            final String msg = name + " copy-in/out failed";
-            ArrayMap<Integer, Leaf> base = new ArrayMap<>();
-            array.copyTo(base);
-            WatchedArrayMap<Integer, Leaf> copy = new WatchedArrayMap<>();
-            copy.copyFrom(base);
-            if (!array.equals(copy)) {
-                fail(msg);
-            }
-        }
-    }
-
-    @Test
-    public void testWatchedArraySet() {
-        final String name = "WatchedArraySet";
-        WatchableTester tester;
-
-        // Create a few leaves
-        Leaf leafA = new Leaf();
-        Leaf leafB = new Leaf();
-        Leaf leafC = new Leaf();
-        Leaf leafD = new Leaf();
-
-        // Test WatchedArraySet
-        WatchedArraySet<Leaf> array = new WatchedArraySet<>();
-        array.add(leafA);
-        array.add(leafB);
-        tester = new WatchableTester(array, name);
-        tester.verify(0, "Initial array - no registration");
-        leafA.tick();
-        tester.verify(0, "Updates with no registration");
-        tester.register();
-        tester.verify(0, "Updates with no registration");
-        leafA.tick();
-        tester.verify(1, "Updates with registration");
-        leafB.tick();
-        tester.verify(2, "Updates with registration");
-        array.remove(leafB);
-        tester.verify(3, "Removed b");
-        leafB.tick();
-        tester.verify(3, "Updates with b not watched");
-        array.add(leafB);
-        array.add(leafB);
-        tester.verify(5, "Added b once");
-        leafB.tick();
-        tester.verify(6, "Changed b - single notification");
-        array.remove(leafB);
-        tester.verify(7, "Removed b");
-        leafB.tick();
-        tester.verify(7, "Changed b - not watched");
-        array.remove(leafB);
-        tester.verify(7, "Removed non-existent b");
-        array.clear();
-        tester.verify(8, "Cleared array");
-        leafA.tick();
-        tester.verify(8, "Change to a not in array");
-
-        // Special methods
-        array.add(leafA);
-        array.add(leafB);
-        array.add(leafC);
-        tester.verify(11, "Added a, b, c");
-        leafC.tick();
-        tester.verify(12, "Ticked c");
-        array.removeAt(array.indexOf(leafC));
-        tester.verify(13, "Removed c");
-        leafC.tick();
-        tester.verify(13, "Ticked c, not registered");
-        array.append(leafC);
-        tester.verify(14, "Append c");
-        leafC.tick();
-        leafD.tick();
-        tester.verify(15, "Ticked d and c");
-        assertEquals("Verify three elements", 3, array.size());
-
-        // Snapshot
-        {
-            final WatchedArraySet<Leaf> arraySnap = array.snapshot();
-            tester.verify(15, "Generate snapshot (no changes)");
-            // Verify that the snapshot is a proper copy of the source.
-            assertEquals(name + " snap same size",
-                         array.size(), arraySnap.size());
-            for (int i = 0; i < array.size(); i++) {
-                for (int j = 0; j < arraySnap.size(); j++) {
-                    assertTrue(name + " elements differ",
-                               array.valueAt(i) != arraySnap.valueAt(j));
-                }
-            }
-            leafC.tick();
-            tester.verify(16, "Tick after snapshot");
-            // Verify that the array snapshot is sealed
-            verifySealed(name, ()->arraySnap.add(leafB));
-            assertTrue(!array.isSealed());
-            assertTrue(arraySnap.isSealed());
-        }
-        // Recreate the snapshot since the test corrupted it.
-        {
-            final WatchedArraySet<Leaf> arraySnap = array.snapshot();
-            // Verify that elements are also snapshots
-            final Leaf arraySnapElement = arraySnap.valueAt(0);
-            verifySealed(name + " snap element", ()->arraySnapElement.tick());
-        }
-        // Verify copy-in/out
-        {
-            final String msg = name + " copy-in/out";
-            ArraySet<Leaf> base = new ArraySet<>();
-            array.copyTo(base);
-            WatchedArraySet<Leaf> copy = new WatchedArraySet<>();
-            copy.copyFrom(base);
-            if (!array.equals(copy)) {
-                fail(msg);
-            }
-        }
-    }
-
-    @Test
-    public void testWatchedArrayList() {
-        final String name = "WatchedArrayList";
-        WatchableTester tester;
-
-        // Create a few leaves
-        Leaf leafA = new Leaf();
-        Leaf leafB = new Leaf();
-        Leaf leafC = new Leaf();
-        Leaf leafD = new Leaf();
-
-        // Redefine the indices used in the tests to be zero-based
-        final int indexA = 0;
-        final int indexB = 1;
-        final int indexC = 2;
-        final int indexD = 3;
-
-        // Test WatchedArrayList
-        WatchedArrayList<Leaf> array = new WatchedArrayList<>();
-        // A spacer that takes up index 0 (and is not Watchable).
-        array.add(indexA, leafA);
-        array.add(indexB, leafB);
-        tester = new WatchableTester(array, name);
-        tester.verify(0, "Initial array - no registration");
-        leafA.tick();
-        tester.verify(0, "Updates with no registration");
-        tester.register();
-        tester.verify(0, "Updates with no registration");
-        leafA.tick();
-        tester.verify(1, "Updates with registration");
-        leafB.tick();
-        tester.verify(2, "Updates with registration");
-        array.remove(indexB);
-        tester.verify(3, "Removed b");
-        leafB.tick();
-        tester.verify(3, "Updates with b not watched");
-        array.add(indexB, leafB);
-        array.add(indexC, leafB);
-        tester.verify(5, "Added b twice");
-        leafB.tick();
-        tester.verify(6, "Changed b - single notification");
-        array.remove(indexC);
-        tester.verify(7, "Removed first b");
-        leafB.tick();
-        tester.verify(8, "Changed b - single notification");
-        array.remove(indexB);
-        tester.verify(9, "Removed second b");
-        leafB.tick();
-        tester.verify(9, "Updated leafB - no change");
-        array.clear();
-        tester.verify(10, "Cleared array");
-        leafB.tick();
-        tester.verify(10, "Change to b not in array");
-
-        // Special methods
-        array.add(indexA, leafA);
-        array.add(indexB, leafB);
-        array.add(indexC, leafC);
-        tester.verify(13, "Added c");
-        leafC.tick();
-        tester.verify(14, "Ticked c");
-        array.set(array.indexOf(leafC), leafD);
-        tester.verify(15, "Replaced c with d");
-        leafC.tick();
-        leafD.tick();
-        tester.verify(16, "Ticked d and c (c not registered)");
-        array.add(leafC);
-        tester.verify(17, "Append c");
-        leafC.tick();
-        leafD.tick();
-        tester.verify(19, "Ticked d and c");
-
-        // Snapshot
-        {
-            final WatchedArrayList<Leaf> arraySnap = array.snapshot();
-            tester.verify(19, "Generate snapshot (no changes)");
-            // Verify that the snapshot is a proper copy of the source.
-            assertEquals(name + " snap same size",
-                         array.size(), arraySnap.size());
-            for (int i = 0; i < array.size(); i++) {
-                for (int j = 0; j < arraySnap.size(); j++) {
-                    assertTrue(name + " elements differ",
-                               array.get(i) != arraySnap.get(j));
-                }
-                assertTrue(name + " element copy",
-                           array.get(i).equals(arraySnap.get(i)));
-            }
-            leafD.tick();
-            tester.verify(20, "Tick after snapshot");
-            // Verify that the array snapshot is sealed
-            verifySealed(name, ()->arraySnap.add(indexA, leafB));
-            assertTrue(!array.isSealed());
-            assertTrue(arraySnap.isSealed());
-        }
-        // Recreate the snapshot since the test corrupted it.
-        {
-            final WatchedArrayList<Leaf> arraySnap = array.snapshot();
-            // Verify that elements are also snapshots
-            final Leaf arraySnapElement = arraySnap.get(0);
-            verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick());
-        }
-        // Verify copy-in/out
-        {
-            final String msg = name + " copy-in/out";
-            ArrayList<Leaf> base = new ArrayList<>();
-            array.copyTo(base);
-            WatchedArrayList<Leaf> copy = new WatchedArrayList<>();
-            copy.copyFrom(base);
-            if (!array.equals(copy)) {
-                fail(msg);
-            }
-        }
-    }
-
-    @Test
-    public void testWatchedSparseArray() {
-        final String name = "WatchedSparseArray";
-        WatchableTester tester;
-
-        // Create a few leaves
-        Leaf leafA = new Leaf();
-        Leaf leafB = new Leaf();
-        Leaf leafC = new Leaf();
-        Leaf leafD = new Leaf();
-
-        // Test WatchedSparseArray
-        WatchedSparseArray<Leaf> array = new WatchedSparseArray<>();
-        array.put(INDEX_A, leafA);
-        array.put(INDEX_B, leafB);
-        tester = new WatchableTester(array, name);
-        tester.verify(0, "Initial array - no registration");
-        leafA.tick();
-        tester.verify(0, "Updates with no registration");
-        tester.register();
-        tester.verify(0, "Updates with no registration");
-        leafA.tick();
-        tester.verify(1, "Updates with registration");
-        leafB.tick();
-        tester.verify(2, "Updates with registration");
-        array.remove(INDEX_B);
-        tester.verify(3, "Removed b");
-        leafB.tick();
-        tester.verify(3, "Updates with b not watched");
-        array.put(INDEX_B, leafB);
-        array.put(INDEX_C, leafB);
-        tester.verify(5, "Added b twice");
-        leafB.tick();
-        tester.verify(6, "Changed b - single notification");
-        array.remove(INDEX_C);
-        tester.verify(7, "Removed first b");
-        leafB.tick();
-        tester.verify(8, "Changed b - single notification");
-        array.remove(INDEX_B);
-        tester.verify(9, "Removed second b");
-        leafB.tick();
-        tester.verify(9, "Updated leafB - no change");
-        array.clear();
-        tester.verify(10, "Cleared array");
-        leafB.tick();
-        tester.verify(10, "Change to b not in array");
-
-        // Special methods
-        array.put(INDEX_A, leafA);
-        array.put(INDEX_B, leafB);
-        array.put(INDEX_C, leafC);
-        tester.verify(13, "Added c");
-        leafC.tick();
-        tester.verify(14, "Ticked c");
-        array.setValueAt(array.indexOfKey(INDEX_C), leafD);
-        tester.verify(15, "Replaced c with d");
-        leafC.tick();
-        leafD.tick();
-        tester.verify(16, "Ticked d and c (c not registered)");
-        array.append(INDEX_D, leafC);
-        tester.verify(17, "Append c");
-        leafC.tick();
-        leafD.tick();
-        tester.verify(19, "Ticked d and c");
-        assertEquals("Verify four elements", 4, array.size());
-        // Figure out which elements are at which indices.
-        Leaf[] x = new Leaf[4];
-        for (int i = 0; i < 4; i++) {
-            x[i] = array.valueAt(i);
-        }
-        array.removeAtRange(0, 2);
-        tester.verify(20, "Removed two elements in one operation");
-        x[0].tick();
-        x[1].tick();
-        tester.verify(20, "Ticked two removed elements");
-        x[2].tick();
-        x[3].tick();
-        tester.verify(22, "Ticked two remaining elements");
-
-        // Snapshot
-        {
-            final WatchedSparseArray<Leaf> arraySnap = array.snapshot();
-            tester.verify(22, "Generate snapshot (no changes)");
-            // Verify that the snapshot is a proper copy of the source.
-            assertEquals(name + " snap same size",
-                         array.size(), arraySnap.size());
-            for (int i = 0; i < array.size(); i++) {
-                for (int j = 0; j < arraySnap.size(); j++) {
-                    assertTrue(name + " elements differ",
-                               array.valueAt(i) != arraySnap.valueAt(j));
-                }
-                assertTrue(name + " element copy",
-                           array.valueAt(i).equals(arraySnap.valueAt(i)));
-            }
-            leafD.tick();
-            tester.verify(23, "Tick after snapshot");
-            // Verify that the array snapshot is sealed
-            verifySealed(name, ()->arraySnap.put(INDEX_A, leafB));
-            assertTrue(!array.isSealed());
-            assertTrue(arraySnap.isSealed());
-        }
-        // Recreate the snapshot since the test corrupted it.
-        {
-            final WatchedSparseArray<Leaf> arraySnap = array.snapshot();
-            // Verify that elements are also snapshots
-            final Leaf arraySnapElement = arraySnap.valueAt(0);
-            verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick());
-        }
-        // Verify copy-in/out
-        {
-            final String msg = name + " copy-in/out";
-            SparseArray<Leaf> base = new SparseArray<>();
-            array.copyTo(base);
-            WatchedSparseArray<Leaf> copy = new WatchedSparseArray<>();
-            copy.copyFrom(base);
-            final int end = array.size();
-            assertTrue(msg + " size mismatch " + end + " " + copy.size(), end == copy.size());
-            for (int i = 0; i < end; i++) {
-                final int key = array.keyAt(i);
-                assertTrue(msg, array.get(i) == copy.get(i));
-            }
-        }
-    }
-
-    @Test
-    public void testWatchedLongSparseArray() {
-        final String name = "WatchedLongSparseArray";
-        WatchableTester tester;
-
-        // Create a few leaves
-        Leaf leafA = new Leaf();
-        Leaf leafB = new Leaf();
-        Leaf leafC = new Leaf();
-        Leaf leafD = new Leaf();
-
-        // Test WatchedLongSparseArray
-        WatchedLongSparseArray<Leaf> array = new WatchedLongSparseArray<>();
-        array.put(INDEX_A, leafA);
-        array.put(INDEX_B, leafB);
-        tester = new WatchableTester(array, name);
-        tester.verify(0, "Initial array - no registration");
-        leafA.tick();
-        tester.verify(0, "Updates with no registration");
-        tester.register();
-        tester.verify(0, "Updates with no registration");
-        leafA.tick();
-        tester.verify(1, "Updates with registration");
-        leafB.tick();
-        tester.verify(2, "Updates with registration");
-        array.remove(INDEX_B);
-        tester.verify(3, "Removed b");
-        leafB.tick();
-        tester.verify(3, "Updates with b not watched");
-        array.put(INDEX_B, leafB);
-        array.put(INDEX_C, leafB);
-        tester.verify(5, "Added b twice");
-        leafB.tick();
-        tester.verify(6, "Changed b - single notification");
-        array.remove(INDEX_C);
-        tester.verify(7, "Removed first b");
-        leafB.tick();
-        tester.verify(8, "Changed b - single notification");
-        array.remove(INDEX_B);
-        tester.verify(9, "Removed second b");
-        leafB.tick();
-        tester.verify(9, "Updated leafB - no change");
-        array.clear();
-        tester.verify(10, "Cleared array");
-        leafB.tick();
-        tester.verify(10, "Change to b not in array");
-
-        // Special methods
-        array.put(INDEX_A, leafA);
-        array.put(INDEX_B, leafB);
-        array.put(INDEX_C, leafC);
-        tester.verify(13, "Added c");
-        leafC.tick();
-        tester.verify(14, "Ticked c");
-        array.setValueAt(array.indexOfKey(INDEX_C), leafD);
-        tester.verify(15, "Replaced c with d");
-        leafC.tick();
-        tester.verify(15, "Ticked c (c not registered)");
-        leafD.tick();
-        tester.verify(16, "Ticked d and c (c not registered)");
-        array.append(INDEX_D, leafC);
-        tester.verify(17, "Append c");
-        leafC.tick();
-        leafD.tick();
-        tester.verify(19, "Ticked d and c");
-        assertEquals("Verify four elements", 4, array.size());
-        // Figure out which elements are at which indices.
-        Leaf[] x = new Leaf[4];
-        for (int i = 0; i < 4; i++) {
-            x[i] = array.valueAt(i);
-        }
-        array.removeAt(1);
-        tester.verify(20, "Removed one element");
-        x[1].tick();
-        tester.verify(20, "Ticked one removed element");
-        x[2].tick();
-        tester.verify(21, "Ticked one remaining element");
-
-        // Snapshot
-        {
-            final WatchedLongSparseArray<Leaf> arraySnap = array.snapshot();
-            tester.verify(21, "Generate snapshot (no changes)");
-            // Verify that the snapshot is a proper copy of the source.
-            assertEquals(name + " snap same size",
-                         array.size(), arraySnap.size());
-            for (int i = 0; i < array.size(); i++) {
-                for (int j = 0; j < arraySnap.size(); j++) {
-                    assertTrue(name + " elements differ",
-                               array.valueAt(i) != arraySnap.valueAt(j));
-                }
-                assertTrue(name + " element copy",
-                           array.valueAt(i).equals(arraySnap.valueAt(i)));
-            }
-            leafD.tick();
-            tester.verify(22, "Tick after snapshot");
-            // Verify that the array snapshot is sealed
-            verifySealed(name, ()->arraySnap.put(INDEX_A, leafB));
-            assertTrue(!array.isSealed());
-            assertTrue(arraySnap.isSealed());
-        }
-        // Recreate the snapshot since the test corrupted it.
-        {
-            final WatchedLongSparseArray<Leaf> arraySnap = array.snapshot();
-            // Verify that elements are also snapshots
-            final Leaf arraySnapElement = arraySnap.valueAt(0);
-            verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick());
-        }
-        // Verify copy-in/out
-        {
-            final String msg = name + " copy-in/out";
-            LongSparseArray<Leaf> base = new LongSparseArray<>();
-            array.copyTo(base);
-            WatchedLongSparseArray<Leaf> copy = new WatchedLongSparseArray<>();
-            copy.copyFrom(base);
-            final int end = array.size();
-            assertTrue(msg + " size mismatch " + end + " " + copy.size(), end == copy.size());
-            for (int i = 0; i < end; i++) {
-                final long key = array.keyAt(i);
-                assertTrue(msg, array.get(i) == copy.get(i));
-            }
-        }
-    }
-
-    @Test
-    public void testWatchedSparseBooleanArray() {
-        final String name = "WatchedSparseBooleanArray";
-        WatchableTester tester;
-
-        // Test WatchedSparseBooleanArray
-        WatchedSparseBooleanArray array = new WatchedSparseBooleanArray();
-        tester = new WatchableTester(array, name);
-        tester.verify(0, "Initial array - no registration");
-        array.put(INDEX_A, true);
-        tester.verify(0, "Updates with no registration");
-        tester.register();
-        tester.verify(0, "Updates with no registration");
-        array.put(INDEX_B, true);
-        tester.verify(1, "Updates with registration");
-        array.put(INDEX_B, false);
-        array.put(INDEX_C, true);
-        tester.verify(3, "Updates with registration");
-        // Special methods
-        array.setValueAt(array.indexOfKey(INDEX_C), false);
-        tester.verify(4, "Replaced true with false");
-        array.append(INDEX_D, true);
-        tester.verify(5, "Append true");
-
-        // Snapshot
-        {
-            WatchedSparseBooleanArray arraySnap = array.snapshot();
-            tester.verify(5, "Generate snapshot");
-            // Verify that the snapshot is a proper copy of the source.
-            assertEquals("WatchedSparseBooleanArray snap same size",
-                         array.size(), arraySnap.size());
-            for (int i = 0; i < array.size(); i++) {
-                assertEquals("WatchedSparseArray element copy",
-                             array.valueAt(i), arraySnap.valueAt(i));
-            }
-            array.put(INDEX_D, false);
-            tester.verify(6, "Tick after snapshot");
-            // Verify that the array is sealed
-            verifySealed(name, ()->arraySnap.put(INDEX_D, false));
-            assertTrue(!array.isSealed());
-            assertTrue(arraySnap.isSealed());
-        }
-        // Verify copy-in/out
-        {
-            final String msg = name + " copy-in/out";
-            SparseBooleanArray base = new SparseBooleanArray();
-            array.copyTo(base);
-            WatchedSparseBooleanArray copy = new WatchedSparseBooleanArray();
-            copy.copyFrom(base);
-            final int end = array.size();
-            assertTrue(msg + " size mismatch/2 " + end + " " + copy.size(), end == copy.size());
-            for (int i = 0; i < end; i++) {
-                final int key = array.keyAt(i);
-                assertTrue(msg + " element", array.get(i) == copy.get(i));
-            }
-        }
-    }
-
-    @Test
-    public void testWatchedSparseIntArray() {
-        final String name = "WatchedSparseIntArray";
-        WatchableTester tester;
-
-        // Test WatchedSparseIntArray
-        WatchedSparseIntArray array = new WatchedSparseIntArray();
-        tester = new WatchableTester(array, name);
-        tester.verify(0, "Initial array - no registration");
-        array.put(INDEX_A, 1);
-        tester.verify(0, "Updates with no registration");
-        tester.register();
-        tester.verify(0, "Updates with no registration");
-        array.put(INDEX_B, 2);
-        tester.verify(1, "Updates with registration");
-        array.put(INDEX_B, 4);
-        array.put(INDEX_C, 5);
-        tester.verify(3, "Updates with registration");
-        // Special methods
-        array.setValueAt(array.indexOfKey(INDEX_C), 7);
-        tester.verify(4, "Replaced 6 with 7");
-        array.append(INDEX_D, 8);
-        tester.verify(5, "Append 8");
-
-        // Snapshot
-        {
-            WatchedSparseIntArray arraySnap = array.snapshot();
-            tester.verify(5, "Generate snapshot");
-            // Verify that the snapshot is a proper copy of the source.
-            assertEquals("WatchedSparseIntArray snap same size",
-                         array.size(), arraySnap.size());
-            for (int i = 0; i < array.size(); i++) {
-                assertEquals(name + " element copy",
-                             array.valueAt(i), arraySnap.valueAt(i));
-            }
-            array.put(INDEX_D, 9);
-            tester.verify(6, "Tick after snapshot");
-            // Verify that the array is sealed
-            verifySealed(name, ()->arraySnap.put(INDEX_D, 10));
-            assertTrue(!array.isSealed());
-            assertTrue(arraySnap.isSealed());
-        }
-        // Verify copy-in/out
-        {
-            final String msg = name + " copy-in/out";
-            SparseIntArray base = new SparseIntArray();
-            array.copyTo(base);
-            WatchedSparseIntArray copy = new WatchedSparseIntArray();
-            copy.copyFrom(base);
-            final int end = array.size();
-            assertTrue(msg + " size mismatch " + end + " " + copy.size(), end == copy.size());
-            for (int i = 0; i < end; i++) {
-                final int key = array.keyAt(i);
-                assertTrue(msg, array.get(i) == copy.get(i));
-            }
-        }
-    }
-
-    @Test
-    public void testWatchedSparseSetArray() {
-        final String name = "WatchedSparseSetArray";
-        WatchableTester tester;
-
-        // Test WatchedSparseSetArray
-        WatchedSparseSetArray array = new WatchedSparseSetArray();
-        tester = new WatchableTester(array, name);
-        tester.verify(0, "Initial array - no registration");
-        array.add(INDEX_A, 1);
-        tester.verify(0, "Updates with no registration");
-        tester.register();
-        tester.verify(0, "Updates with no registration");
-        array.add(INDEX_B, 2);
-        tester.verify(1, "Updates with registration");
-        array.add(INDEX_B, 4);
-        array.add(INDEX_C, 5);
-        tester.verify(3, "Updates with registration");
-        // Special methods
-        assertTrue(array.remove(INDEX_C, 5));
-        tester.verify(4, "Removed 5 from key 3");
-        array.remove(INDEX_B);
-        tester.verify(5, "Removed everything for key 2");
-
-        // Snapshot
-        {
-            WatchedSparseSetArray arraySnap = (WatchedSparseSetArray) array.snapshot();
-            tester.verify(5, "Generate snapshot");
-            // Verify that the snapshot is a proper copy of the source.
-            assertEquals("WatchedSparseSetArray snap same size",
-                    array.size(), arraySnap.size());
-            for (int i = 0; i < array.size(); i++) {
-                ArraySet set = array.get(array.keyAt(i));
-                ArraySet setSnap = arraySnap.get(arraySnap.keyAt(i));
-                assertNotNull(set);
-                assertTrue(set.equals(setSnap));
-            }
-            array.add(INDEX_D, 9);
-            tester.verify(6, "Tick after snapshot");
-            // Verify that the array is sealed
-            verifySealed(name, ()->arraySnap.add(INDEX_D, 10));
-            assertTrue(!array.isSealed());
-            assertTrue(arraySnap.isSealed());
-        }
-        array.clear();
-        tester.verify(7, "Cleared all entries");
-    }
-
-    private static class IndexGenerator {
-        private final int mSeed;
-        private final Random mRandom;
-        public IndexGenerator(int seed) {
-            mSeed = seed;
-            mRandom = new Random(mSeed);
-        }
-        public int next() {
-            return mRandom.nextInt(50000);
-        }
-        public void reset() {
-            mRandom.setSeed(mSeed);
-        }
-        // This is an inefficient way to know if a value appears in an array.
-        private boolean contains(int[] s, int length, int k) {
-            for (int i = 0; i < length; i++) {
-                if (s[i] == k) {
-                    return true;
-                }
-            }
-            return false;
-        }
-        public int[] indexes(int size) {
-            reset();
-            int[] r = new int[size];
-            for (int i = 0; i < size; i++) {
-                int key = next();
-                // Ensure the list of indices are unique.
-                while (contains(r, i, key)) {
-                    key = next();
-                }
-                r[i] = key;
-            }
-            return r;
-        }
-    }
-
-    // Return a value based on the row and column.  The algorithm tries to avoid simple
-    // patterns like checkerboard.
-    private final boolean cellValue(int row, int col) {
-        return (((row * 4 + col) % 3)& 1) == 1;
-    }
-
-    // Fill a matrix
-    private void fill(WatchedSparseBooleanMatrix matrix, int size, int[] indexes) {
-        for (int i = 0; i < size; i++) {
-            int row = indexes[i];
-            for (int j = 0; j < size; j++) {
-                int col = indexes[j];
-                boolean want = cellValue(i, j);
-                matrix.put(row, col, want);
-            }
-        }
-    }
-
-    // Fill new cells in the matrix which has enlarged capacity.
-    private void fillNew(WatchedSparseBooleanMatrix matrix, int initialCapacity,
-            int newCapacity, int[] indexes) {
-        final int size = newCapacity;
-        for (int i = 0; i < size; i++) {
-            for (int j = 0; j < size; j++) {
-                if (i < initialCapacity && j < initialCapacity) {
-                    // Do not touch old cells
-                    continue;
-                }
-                final int row = indexes[i];
-                final int col = indexes[j];
-                matrix.put(row, col, cellValue(i, j));
-            }
-        }
-    }
-
-    // Verify the content of a matrix.  This asserts on mismatch.  Selected indices may
-    // have been deleted.
-    private void verify(WatchedSparseBooleanMatrix matrix, int[] indexes, boolean[] absent) {
-        for (int i = 0; i < matrix.size(); i++) {
-            int row = indexes[i];
-            for (int j = 0; j < matrix.size(); j++) {
-                int col = indexes[j];
-                if (absent != null && (absent[i] || absent[j])) {
-                    boolean want = false;
-                    String msg = String.format("matrix(%d:%d, %d:%d) (deleted)", i, row, j, col);
-                    assertEquals(msg, matrix.get(row, col), false);
-                    assertEquals(msg, matrix.get(row, col, false), false);
-                    assertEquals(msg, matrix.get(row, col, true), true);
-                } else {
-                    boolean want = cellValue(i, j);
-                    String msg = String.format("matrix(%d:%d, %d:%d)", i, row, j, col);
-                    assertEquals(msg, matrix.get(row, col), want);
-                    assertEquals(msg, matrix.get(row, col, false), want);
-                    assertEquals(msg, matrix.get(row, col, true), want);
-                }
-            }
-        }
-    }
-
-    private void matrixGrow(WatchedSparseBooleanMatrix matrix, int size, IndexGenerator indexer) {
-        int[] indexes = indexer.indexes(size);
-
-        // Set values in the matrix, then read back and verify.
-        fill(matrix, size, indexes);
-        assertEquals(matrix.size(), size);
-        verify(matrix, indexes, null);
-
-        // Test the keyAt/indexOfKey methods
-        for (int i = 0; i < matrix.size(); i++) {
-            int key = indexes[i];
-            assertEquals(matrix.keyAt(matrix.indexOfKey(key)), key);
-        }
-    }
-
-    private void matrixDelete(WatchedSparseBooleanMatrix matrix, int size, IndexGenerator indexer) {
-        int[] indexes = indexer.indexes(size);
-        fill(matrix, size, indexes);
-
-        // Delete a bunch of rows.  Verify that reading back results in false and that
-        // contains() is false.  Recreate the rows and verify that all cells (other than
-        // the one just created) are false.
-        boolean[] absent = new boolean[size];
-        for (int i = 0; i < size; i += 13) {
-            matrix.deleteKey(indexes[i]);
-            absent[i] = true;
-        }
-        verify(matrix, indexes, absent);
-    }
-
-    private void matrixShrink(WatchedSparseBooleanMatrix matrix, int size, IndexGenerator indexer) {
-        int[] indexes = indexer.indexes(size);
-        fill(matrix, size, indexes);
-
-        int initialCapacity = matrix.capacity();
-
-        // Delete every other row, remembering which rows were deleted.  The goal is to
-        // make room for compaction.
-        boolean[] absent = new boolean[size];
-        for (int i = 0; i < size; i += 2) {
-            matrix.deleteKey(indexes[i]);
-            absent[i] = true;
-        }
-
-        matrix.compact();
-        int finalCapacity = matrix.capacity();
-        assertTrue("Matrix shrink", initialCapacity > finalCapacity);
-        assertTrue("Matrix shrink", finalCapacity - matrix.size() < matrix.STEP);
-    }
-
-    private void matrixSetCapacity(WatchedSparseBooleanMatrix matrix, int newCapacity,
-            IndexGenerator indexer) {
-        final int initialCapacity = matrix.capacity();
-        final int[] indexes = indexer.indexes(Math.max(initialCapacity, newCapacity));
-        fill(matrix, initialCapacity, indexes);
-
-        matrix.setCapacity(newCapacity);
-        fillNew(matrix, initialCapacity, newCapacity, indexes);
-
-        assertEquals(matrix.size(), indexes.length);
-        verify(matrix, indexes, null);
-        // Test the keyAt/indexOfKey methods
-        for (int i = 0; i < matrix.size(); i++) {
-            int key = indexes[i];
-            assertEquals(matrix.keyAt(matrix.indexOfKey(key)), key);
-        }
-    }
-
-    @Test
-    public void testWatchedSparseBooleanMatrix() {
-        final String name = "WatchedSparseBooleanMatrix";
-
-        // Test the core matrix functionality.  The three tess are meant to test various
-        // combinations of auto-grow.
-        IndexGenerator indexer = new IndexGenerator(3);
-        matrixGrow(new WatchedSparseBooleanMatrix(), 10, indexer);
-        matrixGrow(new WatchedSparseBooleanMatrix(1000), 500, indexer);
-        matrixGrow(new WatchedSparseBooleanMatrix(1000), 2000, indexer);
-        matrixDelete(new WatchedSparseBooleanMatrix(), 500, indexer);
-        matrixShrink(new WatchedSparseBooleanMatrix(), 500, indexer);
-
-        // Test Watchable behavior.
-        WatchedSparseBooleanMatrix matrix = new WatchedSparseBooleanMatrix();
-        WatchableTester tester = new WatchableTester(matrix, name);
-        tester.verify(0, "Initial array - no registration");
-        matrix.put(INDEX_A, INDEX_A, true);
-        tester.verify(0, "Updates with no registration");
-        tester.register();
-        tester.verify(0, "Updates with no registration");
-        matrix.put(INDEX_A, INDEX_B, true);
-        tester.verify(1, "Single cell assignment");
-        matrix.put(INDEX_A, INDEX_B, true);
-        tester.verify(2, "Single cell assignment - same value");
-        matrix.put(INDEX_C, INDEX_B, true);
-        tester.verify(3, "Single cell assignment");
-        matrix.deleteKey(INDEX_B);
-        tester.verify(4, "Delete key");
-        assertEquals(matrix.get(INDEX_B, INDEX_C), false);
-        assertEquals(matrix.get(INDEX_B, INDEX_C, false), false);
-        assertEquals(matrix.get(INDEX_B, INDEX_C, true), true);
-
-        matrix.clear();
-        tester.verify(5, "Clear");
-        assertEquals(matrix.size(), 0);
-        fill(matrix, 10, indexer.indexes(10));
-        int[] keys = matrix.keys();
-        assertEquals(keys.length, matrix.size());
-        for (int i = 0; i < matrix.size(); i++) {
-            assertEquals(matrix.keyAt(i), keys[i]);
-        }
-
-        WatchedSparseBooleanMatrix a = new WatchedSparseBooleanMatrix();
-        matrixGrow(a, 10, indexer);
-        assertEquals(a.size(), 10);
-        WatchedSparseBooleanMatrix b = new WatchedSparseBooleanMatrix();
-        matrixGrow(b, 10, indexer);
-        assertEquals(b.size(), 10);
-        assertEquals(a.equals(b), true);
-        int rowIndex = b.keyAt(3);
-        int colIndex = b.keyAt(4);
-        b.put(rowIndex, colIndex, !b.get(rowIndex, colIndex));
-        assertEquals(a.equals(b), false);
-
-        // Test Snappable behavior.
-        WatchedSparseBooleanMatrix s = a.snapshot();
-        assertEquals(a.equals(s), true);
-        a.put(rowIndex, colIndex, !a.get(rowIndex, colIndex));
-        assertEquals(a.equals(s), false);
-
-        // Verify copy-in/out
-        {
-            final String msg = name + " copy";
-            WatchedSparseBooleanMatrix copy = new WatchedSparseBooleanMatrix();
-            copy.copyFrom(matrix);
-            final int end = copy.size();
-            assertTrue(msg + " size mismatch " + end + " " + matrix.size(), end == matrix.size());
-            for (int i = 0; i < end; i++) {
-                assertEquals(copy.keyAt(i), keys[i]);
-            }
-        }
-    }
-
-    @Test
-    public void testWatchedSparseBooleanMatrix_setCapacity() {
-        final IndexGenerator indexer = new IndexGenerator(3);
-        matrixSetCapacity(new WatchedSparseBooleanMatrix(500), 1000, indexer);
-        matrixSetCapacity(new WatchedSparseBooleanMatrix(1000), 500, indexer);
-    }
-
-    @Test
-    public void testWatchedSparseBooleanMatrix_removeRangeAndShrink() {
-        final IndexGenerator indexer = new IndexGenerator(3);
-        final int initialCapacity = 500;
-        final int removeCounts = 33;
-        final WatchedSparseBooleanMatrix matrix = new WatchedSparseBooleanMatrix(initialCapacity);
-        final int[] indexes = indexer.indexes(initialCapacity);
-        final boolean[] absents = new boolean[initialCapacity];
-        fill(matrix, initialCapacity, indexes);
-        assertEquals(matrix.size(), initialCapacity);
-
-        for (int i = 0; i < initialCapacity / removeCounts; i++) {
-            final int size = matrix.size();
-            final int fromIndex = (size / 2 < removeCounts ? 0 : size / 2 - removeCounts);
-            final int toIndex = (fromIndex + removeCounts > size ? size : fromIndex + removeCounts);
-            for (int index = fromIndex; index < toIndex; index++) {
-                final int key = matrix.keyAt(index);
-                for (int j = 0; j < indexes.length; j++) {
-                    if (key == indexes[j]) {
-                        absents[j] = true;
-                        break;
-                    }
-                }
-            }
-            matrix.removeRange(fromIndex, toIndex);
-            assertEquals(matrix.size(), size - (toIndex - fromIndex));
-            verify(matrix, indexes, absents);
-
-            matrix.compact();
-            verify(matrix, indexes, absents);
-        }
-    }
-
-    @Test
-    public void testNestedArrays() {
-        final String name = "NestedArrays";
-        WatchableTester tester;
-
-        // Create a few leaves
-        Leaf leafA = new Leaf();
-        Leaf leafB = new Leaf();
-        Leaf leafC = new Leaf();
-        Leaf leafD = new Leaf();
-
-        // Test nested arrays.
-        WatchedLongSparseArray<Leaf> lsaA = new WatchedLongSparseArray<>();
-        lsaA.put(2, leafA);
-        WatchedLongSparseArray<Leaf> lsaB = new WatchedLongSparseArray<>();
-        lsaB.put(4, leafB);
-        WatchedLongSparseArray<Leaf> lsaC = new WatchedLongSparseArray<>();
-        lsaC.put(6, leafC);
-
-        WatchedArrayMap<String, WatchedLongSparseArray<Leaf>> array =
-                new WatchedArrayMap<>();
-        array.put("A", lsaA);
-        array.put("B", lsaB);
-
-        // Test WatchedSparseIntArray
-        tester = new WatchableTester(array, name);
-        tester.verify(0, "Initial array - no registration");
-        tester.register();
-        tester.verify(0, "Initial array - post registration");
-        leafA.tick();
-        tester.verify(1, "tick grand-leaf");
-        lsaA.put(2, leafD);
-        tester.verify(2, "replace leafA");
-        leafA.tick();
-        tester.verify(2, "tick unregistered leafA");
-        leafD.tick();
-        tester.verify(3, "tick leafD");
-    }
-
-    @Test
-    public void testSnapshotCache() {
-        final String name = "SnapshotCache";
-        WatchableTester tester;
-
-        Leaf leafA = new Leaf();
-        SnapshotCache<Leaf> cache = new SnapshotCache<>(leafA, leafA) {
-                @Override
-                public Leaf createSnapshot() {
-                    return mSource.snapshot();
-                }};
-
-        Leaf s1 = cache.snapshot();
-        assertTrue(s1 == cache.snapshot());
-        leafA.tick();
-        Leaf s2 = cache.snapshot();
-        assertTrue(s1 != s2);
-        assertTrue(leafA.get() == s1.get() + 1);
-        assertTrue(leafA.get() == s2.get());
-
-        // Test sealed snapshots
-        SnapshotCache<Leaf> sealed = new SnapshotCache.Sealed();
-        try {
-            Leaf x1 = sealed.snapshot();
-            fail(name + " sealed snapshot did not throw");
-        } catch (UnsupportedOperationException e) {
-            // This is the passing scenario - the exception is expected.
-        }
-    }
-}
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
index 3e79407..b8585f2 100644
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
+++ b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
@@ -34,7 +34,8 @@
     public boolean onStartJob(JobParameters params) {
         Log.i(TAG, "Test job executing: " + params.getJobId());
         Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED);
-        reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params);
+        reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         sendBroadcast(reportJobStartIntent);
         return true;
     }
@@ -43,7 +44,8 @@
     public boolean onStopJob(JobParameters params) {
         Log.i(TAG, "Test job stopped executing: " + params.getJobId());
         Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED);
-        reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params);
+        reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         sendBroadcast(reportJobStopIntent);
         // Deadline constraint is dropped on reschedule, so it's more reliable to use a new job.
         return false;
diff --git a/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/Android.bp b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/Android.bp
new file mode 100644
index 0000000..f376b6f
--- /dev/null
+++ b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/Android.bp
@@ -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 {
+    // 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_helper_app {
+    name: "MediaButtonReceiverHolderTestHelperApp",
+
+    sdk_version: "current",
+
+    srcs: ["**/*.java"],
+
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/AndroidManifest.xml
new file mode 100644
index 0000000..3ba3dc2
--- /dev/null
+++ b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.servicestests.apps.mediabuttonreceiverholdertesthelperapp">
+
+    <application>
+        <receiver
+            android:name=".FakeMediaButtonBroadcastReceiver"
+            android:enabled="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MEDIA_BUTTON" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+</manifest>
diff --git a/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/OWNERS b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/OWNERS
new file mode 100644
index 0000000..55ffde2
--- /dev/null
+++ b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137631
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/src/FakeMediaButtonBroadcastReceiver.java b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/src/FakeMediaButtonBroadcastReceiver.java
new file mode 100644
index 0000000..6fdd8be
--- /dev/null
+++ b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/src/FakeMediaButtonBroadcastReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * 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 com.android.servicestests.apps.mediabuttonreceiverholdertesthelperapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class FakeMediaButtonBroadcastReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "FakeMediaButtonBroadcastReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.v(TAG, "onReceive not expected");
+    }
+}
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml
index 4dcb442..5451735 100644
--- a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml
+++ b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml
@@ -32,7 +32,7 @@
 
 	    <activity android:name="com.android.servicestests.apps.packageparserapp.MyActivity"
 	              android:exported="true"
-	              android:targetDisplayCategory="automotive">
+	              android:requiredDisplayCategory="automotive">
 	        <property android:name="android.cts.PROPERTY_ACTIVITY" android:value="@integer/integer_property" />
 	        <property android:name="android.cts.PROPERTY_COMPONENT" android:value="@integer/integer_property" />
 	        <property android:name="android.cts.PROPERTY_STRING" android:value="koala activity" />
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp6.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp6.xml
index 8e694e1..601479d 100644
--- a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp6.xml
+++ b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp6.xml
@@ -21,7 +21,7 @@
     <application>
         <activity android:name="com.android.servicestests.apps.packageparserapp.MyActivity"
                   android:exported="true"
-                  android:targetDisplayCategory="$automotive">
+                  android:requiredDisplayCategory="$automotive">
         </activity>
     </application>
 </manifest>
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
index af10b9d..d758e71 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
@@ -52,9 +52,9 @@
 @RunWith(AndroidTestingRunner.class)
 public class NotificationHistoryJobServiceTest extends UiServiceTestCase {
     private NotificationHistoryJobService mJobService;
-    private JobParameters mJobParams = new JobParameters(null,
-            NotificationHistoryJobService.BASE_JOB_ID, null, null, null,
-            0, false, false, null, null, null);
+
+    @Mock
+    private JobParameters mJobParams;
 
     @Captor
     ArgumentCaptor<JobInfo> mJobInfoCaptor;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 2ed8b10..d54d1fe 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -7623,8 +7623,30 @@
     }
 
     @Test
+    public void testAddAutomaticZenRule_systemAppIdCallTakesPackageFromOwner() throws Exception {
+        // The multi-user case: where the calling uid doesn't match the system uid, but the calling
+        // *appid* is the system.
+        mService.isSystemUid = false;
+        mService.isSystemAppId = true;
+        ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+        when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+                .thenReturn(true);
+        mService.setZenHelper(mockZenModeHelper);
+        ComponentName owner = new ComponentName("android", "ProviderName");
+        ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+        boolean isEnabled = true;
+        AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+                zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+        mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+
+        // verify that zen mode helper gets passed in a package name of "android"
+        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString());
+    }
+
+    @Test
     public void testAddAutomaticZenRule_nonSystemCallTakesPackageFromArg() throws Exception {
         mService.isSystemUid = false;
+        mService.isSystemAppId = false;
         ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
         when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
                 .thenReturn(true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 0f93598..b64b281 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2727,7 +2727,7 @@
 
     @Test
     public void testCreateChannel_addToGroup() {
-        NotificationChannelGroup group = new NotificationChannelGroup("group", "");
+        NotificationChannelGroup group = new NotificationChannelGroup("group", "group");
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
         NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
         assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));
@@ -3177,8 +3177,8 @@
 
     @Test
     public void testGetNotificationChannelGroupWithChannels() throws Exception {
-        NotificationChannelGroup group = new NotificationChannelGroup("group", "");
-        NotificationChannelGroup other = new NotificationChannelGroup("something else", "");
+        NotificationChannelGroup group = new NotificationChannelGroup("group", "group");
+        NotificationChannelGroup other = new NotificationChannelGroup("something else", "name");
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, other, true);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
index 3a6c0eb..a83eb00 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
@@ -44,9 +44,9 @@
 @RunWith(AndroidTestingRunner.class)
 public class ReviewNotificationPermissionsJobServiceTest extends UiServiceTestCase {
     private ReviewNotificationPermissionsJobService mJobService;
-    private JobParameters mJobParams = new JobParameters(null,
-            ReviewNotificationPermissionsJobService.JOB_ID, null, null, null,
-            0, false, false, null, null, null);
+
+    @Mock
+    private JobParameters mJobParams;
 
     @Captor
     ArgumentCaptor<JobInfo> mJobInfoCaptor;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index a03a1b4..ebcb498 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -234,6 +234,37 @@
     }
 
     @Test
+    public void testScheduleRepostsForLongTagPersistedNotification() throws Exception {
+        String longTag = "A".repeat(66000);
+        NotificationRecord r = getNotificationRecord("pkg", 1, longTag, UserHandle.SYSTEM);
+        mSnoozeHelper.snooze(r, 0);
+
+        // We store the full key in temp storage.
+        ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
+        assertEquals(66010, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length());
+
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+        mSnoozeHelper.writeXml(serializer);
+        serializer.endDocument();
+        serializer.flush();
+
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), "utf-8");
+        mSnoozeHelper.readXml(parser, 4);
+
+        mSnoozeHelper.scheduleRepostsForPersistedNotifications(5);
+
+        // We trim the key in persistent storage.
+        verify(mAm, times(2)).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
+        assertEquals(1000, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length());
+    }
+
+    @Test
     public void testSnoozeForTime() throws Exception {
         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
         mSnoozeHelper.snooze(r, 1000);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 8cf74fb..61a6985 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -32,6 +32,7 @@
 public class TestableNotificationManagerService extends NotificationManagerService {
     int countSystemChecks = 0;
     boolean isSystemUid = true;
+    boolean isSystemAppId = true;
     int countLogSmartSuggestionsVisible = 0;
     Set<Integer> mChannelToastsSent = new HashSet<>();
 
@@ -58,6 +59,12 @@
     }
 
     @Override
+    protected boolean isCallingAppIdSystem() {
+        countSystemChecks++;
+        return isSystemUid || isSystemAppId;
+    }
+
+    @Override
     protected boolean isCallerSystemOrPhone() {
         countSystemChecks++;
         return isSystemUid;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 376399a..4e001fe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -106,7 +106,7 @@
         mTransition.mParticipants.add(mTopActivity);
         mTopActivity.mTransitionController.moveToCollecting(mTransition);
         // becomes invisible when covered by mTopActivity
-        mTrampolineActivity.mVisibleRequested = false;
+        mTrampolineActivity.setVisibleRequested(false);
     }
 
     private <T> T verifyAsync(T mock) {
@@ -235,7 +235,7 @@
     public void testOnActivityLaunchCancelled_hasDrawn() {
         onActivityLaunched(mTopActivity);
 
-        mTopActivity.mVisibleRequested = true;
+        mTopActivity.setVisibleRequested(true);
         doReturn(true).when(mTopActivity).isReportedDrawn();
 
         // Cannot time already-visible activities.
@@ -258,7 +258,7 @@
         notifyActivityLaunching(noDrawnActivity.intent);
         notifyAndVerifyActivityLaunched(noDrawnActivity);
 
-        noDrawnActivity.mVisibleRequested = false;
+        noDrawnActivity.setVisibleRequested(false);
         mActivityMetricsLogger.notifyVisibilityChanged(noDrawnActivity);
 
         verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqLastStartedId(noDrawnActivity));
@@ -286,7 +286,7 @@
 
         clearInvocations(mLaunchObserver);
         mLaunchTopByTrampoline = true;
-        mTopActivity.mVisibleRequested = false;
+        mTopActivity.setVisibleRequested(false);
         notifyActivityLaunching(mTopActivity.intent);
         // It should schedule a message with UNKNOWN_VISIBILITY_CHECK_DELAY_MS to check whether
         // the launch event is still valid.
@@ -314,7 +314,7 @@
         // Create an invisible event that should be cancelled after the next event starts.
         final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true).build();
         onActivityLaunched(prev);
-        prev.mVisibleRequested = false;
+        prev.setVisibleRequested(false);
 
         mActivityOptions = ActivityOptions.makeBasic();
         mActivityOptions.setSourceInfo(SourceInfo.TYPE_LAUNCHER, SystemClock.uptimeMillis() - 10);
@@ -324,7 +324,7 @@
 
         // The activity reports fully drawn before windows drawn, then the fully drawn event will
         // be pending (see {@link WindowingModeTransitionInfo#pendingFullyDrawn}).
-        mActivityMetricsLogger.logAppTransitionReportedDrawn(mTopActivity, false);
+        mActivityMetricsLogger.notifyFullyDrawn(mTopActivity, false /* restoredFromBundle */);
         notifyTransitionStarting(mTopActivity);
         // The pending fully drawn event should send when the actual windows drawn event occurs.
         final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity);
@@ -337,7 +337,7 @@
         verifyNoMoreInteractions(mLaunchObserver);
 
         final ActivityMetricsLogger.TransitionInfoSnapshot fullyDrawnInfo = mActivityMetricsLogger
-                .logAppTransitionReportedDrawn(mTopActivity, false /* restoredFromBundle */);
+                .notifyFullyDrawn(mTopActivity, false /* restoredFromBundle */);
         assertWithMessage("Invisible event must be dropped").that(fullyDrawnInfo).isNull();
     }
 
@@ -561,7 +561,7 @@
     @Test
     public void testConsecutiveLaunchWithDifferentWindowingMode() {
         mTopActivity.setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
-        mTrampolineActivity.mVisibleRequested = true;
+        mTrampolineActivity.setVisibleRequested(true);
         onActivityLaunched(mTrampolineActivity);
         mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent,
                 mTrampolineActivity /* caller */, mTrampolineActivity.getUid());
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..f983fa9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -917,7 +917,7 @@
         // Prepare the activity record to be ready for immediate removal. It should be invisible and
         // have no process. Otherwise, request to finish it will send a message to client first.
         activity.setState(STOPPED, "test");
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.nowVisible = false;
         // Set process to 'null' to allow immediate removal, but don't call mActivity.setProcess() -
         // this will cause NPE when updating task's process.
@@ -927,7 +927,7 @@
         // next activity reports idle to destroy it.
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.setState(RESUMED, "test");
 
@@ -1082,7 +1082,7 @@
         final ActivityRecord activity = createActivityWithTask();
         clearInvocations(activity.mDisplayContent);
         activity.finishing = false;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setState(RESUMED, "test");
         activity.finishIfPossible("test", false /* oomAdj */);
 
@@ -1099,7 +1099,7 @@
         final ActivityRecord activity = createActivityWithTask();
         clearInvocations(activity.mDisplayContent);
         activity.finishing = false;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setState(PAUSED, "test");
         activity.finishIfPossible("test", false /* oomAdj */);
 
@@ -1118,7 +1118,7 @@
         // Put an activity on top of test activity to make it invisible and prevent us from
         // accidentally resuming the topmost one again.
         new ActivityBuilder(mAtm).build();
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setState(STOPPED, "test");
 
         activity.finishIfPossible("test", false /* oomAdj */);
@@ -1136,7 +1136,7 @@
         final TestTransitionPlayer testPlayer = registerTestTransitionPlayer();
         final ActivityRecord activity = createActivityWithTask();
         activity.finishing = false;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setState(RESUMED, "test");
         activity.finishIfPossible("test", false /* oomAdj */);
 
@@ -1206,6 +1206,25 @@
         }
     }
 
+    @Test
+    public void testFinishActivityIfPossible_sendResultImmediatelyIfResumed() {
+        final Task task = new TaskBuilder(mSupervisor).build();
+        final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task);
+        final TaskFragment taskFragment2 = createTaskFragmentWithActivity(task);
+        final ActivityRecord resultToActivity = taskFragment1.getTopMostActivity();
+        final ActivityRecord targetActivity = taskFragment2.getTopMostActivity();
+        resultToActivity.setState(RESUMED, "test");
+        targetActivity.setState(RESUMED, "test");
+        targetActivity.resultTo = resultToActivity;
+
+        clearInvocations(mAtm.getLifecycleManager());
+        targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */);
+        waitUntilHandlersIdle();
+
+        verify(resultToActivity).sendResult(anyInt(), eq(null), anyInt(), anyInt(), any(), eq(null),
+                anyBoolean());
+    }
+
     /**
      * Verify that complete finish request for non-finishing activity is invalid.
      */
@@ -1254,7 +1273,7 @@
         final ActivityRecord currentTop = createActivityWithTask();
         final Task task = currentTop.getTask();
 
-        currentTop.mVisibleRequested = currentTop.nowVisible = true;
+        currentTop.setVisibleRequested(currentTop.nowVisible = true);
 
         // Simulates that {@code currentTop} starts an existing activity from background (so its
         // state is stopped) and the starting flow just goes to place it at top.
@@ -1281,7 +1300,7 @@
         final ActivityRecord bottomActivity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(bottomActivity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         // simulating bottomActivity as a trampoline activity.
         bottomActivity.setState(RESUMED, "test");
         bottomActivity.finishIfPossible("test", false);
@@ -1297,13 +1316,13 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.finishing = true;
         topActivity.setState(PAUSED, "true");
         // Mark the bottom activity as not visible, so that we will wait for it before removing
         // the top one.
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.nowVisible = false;
         activity.setState(STOPPED, "test");
 
@@ -1327,13 +1346,13 @@
         final ActivityRecord topActivity = createActivityWithTask();
         mDisplayContent.setIsSleeping(true);
         doReturn(true).when(activity).shouldBeVisible();
-        topActivity.mVisibleRequested = false;
+        topActivity.setVisibleRequested(false);
         topActivity.nowVisible = false;
         topActivity.finishing = true;
         topActivity.setState(STOPPED, "true");
 
         // Mark the activity behind (on a separate task) as not visible
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.nowVisible = false;
         activity.setState(STOPPED, "test");
 
@@ -1351,13 +1370,13 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = false;
+        topActivity.setVisibleRequested(false);
         topActivity.nowVisible = false;
         topActivity.finishing = true;
         topActivity.setState(STOPPED, "true");
         // Mark the bottom activity as not visible, so that we would wait for it before removing
         // the top one.
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.nowVisible = false;
         activity.setState(STOPPED, "test");
 
@@ -1375,12 +1394,12 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.finishing = true;
         topActivity.setState(PAUSED, "true");
         // Mark the bottom activity as already visible, so that there is no need to wait for it.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.nowVisible = true;
         activity.setState(RESUMED, "test");
 
@@ -1398,12 +1417,12 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = false;
+        topActivity.setVisibleRequested(false);
         topActivity.nowVisible = false;
         topActivity.finishing = true;
         topActivity.setState(STOPPED, "true");
         // Mark the bottom activity as already visible, so that there is no need to wait for it.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.nowVisible = true;
         activity.setState(RESUMED, "test");
 
@@ -1421,12 +1440,12 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.finishing = true;
         topActivity.setState(PAUSED, "true");
         // Mark the bottom activity as already visible, so that there is no need to wait for it.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.nowVisible = true;
         activity.setState(RESUMED, "test");
 
@@ -1435,7 +1454,7 @@
         final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
         final ActivityRecord focusedActivity = stack.getTopMostActivity();
         focusedActivity.nowVisible = true;
-        focusedActivity.mVisibleRequested = true;
+        focusedActivity.setVisibleRequested(true);
         focusedActivity.setState(RESUMED, "test");
         stack.setResumedActivity(focusedActivity, "test");
 
@@ -1457,7 +1476,7 @@
         int displayId = activity.getDisplayId();
         doReturn(true).when(keyguardController).isKeyguardLocked(eq(displayId));
         final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.setState(RESUMED, "true");
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
@@ -1496,12 +1515,12 @@
         final ActivityRecord activity = createActivityWithTask();
         final Task task = activity.getTask();
         final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
-        firstActivity.mVisibleRequested = false;
+        firstActivity.setVisibleRequested(false);
         firstActivity.nowVisible = false;
         firstActivity.setState(STOPPED, "test");
 
         final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task).build();
-        secondActivity.mVisibleRequested = true;
+        secondActivity.setVisibleRequested(true);
         secondActivity.nowVisible = true;
         secondActivity.setState(secondActivityState, "test");
 
@@ -1511,7 +1530,7 @@
         } else {
             translucentActivity = new ActivityBuilder(mAtm).setTask(task).build();
         }
-        translucentActivity.mVisibleRequested = true;
+        translucentActivity.setVisibleRequested(true);
         translucentActivity.nowVisible = true;
         translucentActivity.setState(RESUMED, "test");
 
@@ -1527,7 +1546,7 @@
 
         // Finish the first activity
         firstActivity.finishing = true;
-        firstActivity.mVisibleRequested = true;
+        firstActivity.setVisibleRequested(true);
         firstActivity.completeFinishing("test");
         verify(firstActivity.mDisplayContent, times(2)).ensureActivitiesVisible(null /* starting */,
                 0 /* configChanges */ , false /* preserveWindows */,
@@ -1595,7 +1614,7 @@
         }, true /* traverseTopToBottom */);
         activity.setState(STARTED, "test");
         activity.finishing = true;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
 
         // Try to finish the last activity above the home stack.
         activity.completeFinishing("test");
@@ -1890,7 +1909,7 @@
         // Simulate that the activity requests the same orientation as display.
         activity.setOrientation(display.getConfiguration().orientation);
         // Skip the real freezing.
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         clearInvocations(activity);
         activity.onCancelFixedRotationTransform(originalRotation);
         // The implementation of cancellation must be executed.
@@ -2284,8 +2303,7 @@
         doReturn(false).when(mAtm).shouldDisableNonVrUiLocked();
 
         spyOn(mDisplayContent.mDwpcHelper);
-        doReturn(false).when(mDisplayContent.mDwpcHelper).isWindowingModeSupported(
-                WINDOWING_MODE_PINNED);
+        doReturn(false).when(mDisplayContent.mDwpcHelper).isEnteringPipAllowed(anyInt());
 
         assertFalse(activity.checkEnterPictureInPictureState("TEST", false /* beforeStopping */));
     }
@@ -2517,7 +2535,7 @@
 
         activity.setOccludesParent(true);
         activity.setVisible(false);
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         // Can not specify orientation if app isn't visible even though it occludes parent.
         assertEquals(SCREEN_ORIENTATION_UNSET, activity.getOrientation());
         // Can specify orientation if the current orientation candidate is orientation behind.
@@ -2807,7 +2825,7 @@
         final Task task = activity.getTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
         topActivity.setVisible(false);
-        task.positionChildAt(topActivity, POSITION_TOP);
+        task.positionChildAt(POSITION_TOP, topActivity, false /* includeParents */);
         activity.addStartingWindow(mPackageName, android.R.style.Theme, null, true, true, false,
                 true, false, false, false);
         waitUntilHandlersIdle();
@@ -2884,6 +2902,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 */);
@@ -2891,7 +2910,7 @@
         task.addChild(taskFragment2, POSITION_TOP);
         final ActivityRecord activity2 = new ActivityBuilder(mAtm)
                 .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE).build();
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         taskFragment2.addChild(activity2);
         assertTrue(activity2.isResizeable());
         activity1.reparent(taskFragment1, POSITION_TOP);
@@ -2903,7 +2922,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 +2932,7 @@
         activity1.onFirstWindowDrawn(activityWindow);
         activity2.onFirstWindowDrawn(activityWindow);
         assertNull(activity1.mStartingWindow);
+        assertNull(task.mSharedStartingData);
     }
 
     @Test
@@ -2958,8 +2977,7 @@
         removeGlobalMinSizeRestriction();
         final Task task = new TaskBuilder(mSupervisor).setCreateParentTask(true).build();
         final Task rootTask = task.getRootTask();
-        final TaskFragment taskFragment = createTaskFragmentWithParentTask(task,
-                false /* createEmbeddedTask */);
+        final TaskFragment taskFragment = createTaskFragmentWithActivity(task);
         final ActivityRecord activity = taskFragment.getTopNonFinishingActivity();
         final Rect stackBounds = new Rect(0, 0, 1000, 600);
         final Rect taskBounds = new Rect(100, 400, 600, 800);
@@ -2989,10 +3007,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);
@@ -3037,7 +3055,7 @@
                 .setCreateTask(true).build();
         // By default, activity is visible.
         assertTrue(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
@@ -3046,7 +3064,7 @@
         // until we verify no logic relies on this behavior, we'll keep this as is.
         activity.setVisibility(true);
         assertTrue(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
     }
@@ -3057,7 +3075,7 @@
                 .setCreateTask(true).build();
         // By default, activity is visible.
         assertTrue(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
@@ -3065,7 +3083,7 @@
         // animation should be applied on this activity.
         activity.setVisibility(false);
         assertTrue(activity.isVisible());
-        assertFalse(activity.mVisibleRequested);
+        assertFalse(activity.isVisibleRequested());
         assertFalse(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertTrue(activity.mDisplayContent.mClosingApps.contains(activity));
     }
@@ -3077,7 +3095,7 @@
         // Activiby is invisible. However ATMS requests it to become visible, since this is a top
         // activity.
         assertFalse(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
@@ -3085,9 +3103,20 @@
         // animation should be applied on this activity.
         activity.setVisibility(true);
         assertFalse(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+
+        // There should still be animation (add to opening) if keyguard is going away while the
+        // screen is off because it will be visible after screen is turned on by unlocking.
+        mDisplayContent.mOpeningApps.remove(activity);
+        mDisplayContent.mClosingApps.remove(activity);
+        activity.commitVisibility(false /* visible */, false /* performLayout */);
+        mDisplayContent.getDisplayPolicy().screenTurnedOff();
+        final KeyguardController controller = mSupervisor.getKeyguardController();
+        doReturn(true).when(controller).isKeyguardGoingAway(anyInt());
+        activity.setVisibility(true);
+        assertTrue(mDisplayContent.mOpeningApps.contains(activity));
     }
 
     @Test
@@ -3097,7 +3126,7 @@
         // Activiby is invisible. However ATMS requests it to become visible, since this is a top
         // activity.
         assertFalse(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
@@ -3105,7 +3134,7 @@
         // transition should be applied on this activity.
         activity.setVisibility(false);
         assertFalse(activity.isVisible());
-        assertFalse(activity.mVisibleRequested);
+        assertFalse(activity.isVisibleRequested());
         assertFalse(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
     }
@@ -3520,12 +3549,12 @@
         activity.reparent(taskFragment, POSITION_TOP);
 
         // Ensure the activity visibility is updated even it is not shown to current user.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         doReturn(false).when(activity).showToCurrentUser();
         spyOn(taskFragment);
         doReturn(false).when(taskFragment).shouldBeVisible(any());
         display.ensureActivitiesVisible(null, 0, false, false);
-        assertFalse(activity.mVisibleRequested);
+        assertFalse(activity.isVisibleRequested());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 1575336..8a15c30 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -28,6 +28,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -275,11 +276,60 @@
         // THEN calling intercept returns true
         mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
 
-        // THEN the returned intent is the quiet mode intent
+        // THEN the returned intent is the confirm credentials intent
         assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent));
     }
 
     @Test
+    public void testLockedManagedProfileShowWhenLocked() {
+        Intent originalIntent = new Intent();
+        // GIVEN that the user is locked but its storage is unlocked and the activity has
+        // showWhenLocked flag
+        when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
+        when(mUserManager.isUserUnlocked(eq(TEST_USER_ID))).thenReturn(true);
+        mAInfo.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+
+        // THEN calling intercept returns true
+        mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null);
+
+        // THEN the returned intent is original intent
+        assertSame(originalIntent, mInterceptor.mIntent);
+    }
+
+    @Test
+    public void testLockedManagedProfileShowWhenLockedEncryptedStorage() {
+        // GIVEN that the user storage is locked, activity has showWhenLocked flag but no
+        // directBootAware flag
+        when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
+        when(mUserManager.isUserUnlocked(eq(TEST_USER_ID))).thenReturn(false);
+        mAInfo.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+        mAInfo.directBootAware = false;
+
+        // THEN calling intercept returns true
+        mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
+
+        // THEN the returned intent is the confirm credentials intent
+        assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent));
+    }
+
+    @Test
+    public void testLockedManagedProfileShowWhenLockedEncryptedStorageDirectBootAware() {
+        Intent originalIntent = new Intent();
+        // GIVEN that the user storage is locked, activity has showWhenLocked flag and
+        // directBootAware flag
+        when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
+        when(mUserManager.isUserUnlocked(eq(TEST_USER_ID))).thenReturn(false);
+        mAInfo.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+        mAInfo.directBootAware = true;
+
+        // THEN calling intercept returns true
+        mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null);
+
+        // THEN the returned intent is original intent
+        assertSame(originalIntent, mInterceptor.mIntent);
+    }
+
+    @Test
     public void testHarmfulAppWarning() throws RemoteException {
         // GIVEN the package we're about to launch has a harmful app warning set
         when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID))
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..ee31748 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.Activity.RESULT_CANCELED;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.START_ABORTED;
 import static android.app.ActivityManager.START_CANCELED;
@@ -98,6 +99,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;
@@ -503,16 +505,18 @@
         // The fullscreen windowing mode activity will be moved to split-secondary by
         // TestSplitOrganizer when a split-primary task appears.
         final ActivityRecord splitPrimaryActivity = new TaskBuilder(mSupervisor)
-                .setParentTaskFragment(splitOrg.mPrimary)
+                .setParentTask(splitOrg.mPrimary)
                 .setCreateActivity(true)
                 .build()
                 .getTopMostActivity();
         final ActivityRecord splitSecondActivity = new TaskBuilder(mSupervisor)
-                .setParentTaskFragment(splitOrg.mSecondary)
+                .setParentTask(splitOrg.mSecondary)
                 .setCreateActivity(true)
                 .build()
                 .getTopMostActivity();
-        splitPrimaryActivity.mVisibleRequested = splitSecondActivity.mVisibleRequested = true;
+
+        splitPrimaryActivity.setVisibleRequested(true);
+        splitSecondActivity.setVisibleRequested(true);
 
         assertEquals(splitOrg.mPrimary, splitPrimaryActivity.getRootTask());
         assertEquals(splitOrg.mSecondary, splitSecondActivity.getRootTask());
@@ -525,7 +529,7 @@
                 .setCreateActivity(true).build().getTopMostActivity();
         final ActivityRecord translucentActivity = new TaskBuilder(mSupervisor)
                 .setCreateActivity(true).build().getTopMostActivity();
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
 
         final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
                 false /* mockGetRootTask */);
@@ -608,10 +612,9 @@
     @Test
     public void testBackgroundActivityStartsAllowed_noStartsAborted() {
         doReturn(true).when(mAtm).isBackgroundActivityStartsEnabled();
-
         runAndVerifyBackgroundActivityStartsSubtest("allowed_noStartsAborted", false,
-                UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
                 false, false, false, false, false);
     }
 
@@ -620,97 +623,220 @@
      * disallowed.
      */
     @Test
-    public void testBackgroundActivityStartsDisallowed_unsupportedStartsAborted() {
+    public void testBackgroundActivityStartsDisallowed_unsupportedUsecaseAborted() {
         doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
-
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_unsupportedUsecase_aborted", true,
-                UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
                 false, false, false, false, false);
+    }
+
+    /**
+     * This test ensures that unsupported usecases are aborted when background starts are
+     * disallowed.
+     */
+    @Test
+    public void testBackgroundActivityStartsDisallowed_callingUidProcessStateTopAborted() {
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_callingUidProcessStateTop_aborted", true,
                 UNIMPORTANT_UID, false, PROCESS_STATE_TOP,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
                 false, false, false, false, false);
+    }
+
+    /**
+     * This test ensures that unsupported usecases are aborted when background starts are
+     * disallowed.
+     */
+    @Test
+    public void testBackgroundActivityStartsDisallowed_realCallingUidProcessStateTopAborted() {
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_realCallingUidProcessStateTop_aborted", true,
-                UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
+                UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP,
                 false, false, false, false, false);
+    }
+
+    /**
+     * This test ensures that unsupported usecases are aborted when background starts are
+     * disallowed.
+     */
+    @Test
+    public void testBackgroundActivityStartsDisallowed_hasForegroundActivitiesAborted() {
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_hasForegroundActivities_aborted", true,
-                UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
                 true, false, false, false, false);
+    }
+
+    /**
+     * This test ensures that unsupported usecases are aborted when background starts are
+     * disallowed.
+     */
+    @Test
+    public void testBackgroundActivityStartsDisallowed_pinnedSingleInstanceAborted() {
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_pinned_singleinstance_aborted", true,
-                UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
                 false, false, false, false, false, true);
-
     }
 
     /**
      * This test ensures that supported usecases aren't aborted when background starts are
-     * disallowed.
-     * The scenarios each have only one condition that makes them supported.
+     * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+     * this case the calling process runs as ROOT_UID.
      */
     @Test
-    public void testBackgroundActivityStartsDisallowed_supportedStartsNotAborted() {
+    public void testBackgroundActivityStartsDisallowed_rootUidNotAborted() {
         doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
-
         runAndVerifyBackgroundActivityStartsSubtest("disallowed_rootUid_notAborted", false,
-                Process.ROOT_UID, false, PROCESS_STATE_TOP + 1,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                Process.ROOT_UID, false, PROCESS_STATE_BOUND_TOP,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
                 false, false, false, false, false);
+    }
+
+    /**
+     * This test ensures that supported usecases aren't aborted when background starts are
+     * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+     * this case the calling process is running as SYSTEM_UID.
+     */
+    @Test
+    public void testBackgroundActivityStartsDisallowed_systemUidNotAborted() {
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
         runAndVerifyBackgroundActivityStartsSubtest("disallowed_systemUid_notAborted", false,
-                Process.SYSTEM_UID, false, PROCESS_STATE_TOP + 1,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                Process.SYSTEM_UID, false, PROCESS_STATE_BOUND_TOP,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
                 false, false, false, false, false);
+    }
+
+    /**
+     * This test ensures that supported usecases aren't aborted when background starts are
+     * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+     * this case the calling process is running as NFC_UID.
+     */
+    @Test
+    public void testBackgroundActivityStartsDisallowed_nfcUidNotAborted() {
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
         runAndVerifyBackgroundActivityStartsSubtest("disallowed_nfcUid_notAborted", false,
-                Process.NFC_UID, false, PROCESS_STATE_TOP + 1,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                Process.NFC_UID, false, PROCESS_STATE_BOUND_TOP,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
                 false, false, false, false, false);
+    }
+
+    /**
+     * This test ensures that supported usecases aren't aborted when background starts are
+     * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+     * this case the calling process has a visible window.
+     */
+    @Test
+    public void testBackgroundActivityStartsDisallowed_callingUidHasVisibleWindowNotAborted() {
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_callingUidHasVisibleWindow_notAborted", false,
-                UNIMPORTANT_UID, true, PROCESS_STATE_TOP + 1,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                UNIMPORTANT_UID, true, PROCESS_STATE_BOUND_TOP,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
                 false, false, false, false, false);
+    }
+
+    /**
+     * This test ensures that supported usecases aren't aborted when background starts are
+     * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+     * this case the real calling process (pending intent) has a visible window.
+     */
+    @Test
+    public void
+            testBackgroundActivityStartsDisallowed_realCallingUidHasVisibleWindowNotAborted() {
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_realCallingUidHasVisibleWindow_notAborted", false,
-                UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
-                UNIMPORTANT_UID2, true, PROCESS_STATE_TOP + 1,
-                false, false, false, false, false);
+                UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+                UNIMPORTANT_UID2, true, PROCESS_STATE_BOUND_TOP,
+                false, false, false, false, false, false);
+    }
+
+    /**
+     * This test ensures that supported usecases aren't aborted when background starts are
+     * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+     * this case the caller is in the recent activity list.
+     */
+    @Test
+    public void testBackgroundActivityStartsDisallowed_callerIsRecentsNotAborted() {
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_callerIsRecents_notAborted", false,
-                UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
                 false, true, false, false, false);
+    }
+
+    /**
+     * This test ensures that supported usecases aren't aborted when background starts are
+     * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+     * this case the caller is temporarily (10s) allowed to start.
+     */
+    @Test
+    public void testBackgroundActivityStartsDisallowed_callerIsAllowedNotAborted() {
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_callerIsAllowed_notAborted", false,
-                UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
                 false, false, true, false, false);
+    }
+
+    /**
+     * This test ensures that supported usecases aren't aborted when background starts are
+     * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+     * this case the caller explicitly has background activity start privilege.
+     */
+    @Test
+    public void testBackgroundActivityStartsDisallowed_callerIsInstrumentingWithBASPnotAborted() {
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_callerIsInstrumentingWithBackgroundActivityStartPrivileges_notAborted",
                 false,
-                UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
                 false, false, false, true, false);
+    }
+
+    /**
+     * This test ensures that supported usecases aren't aborted when background starts are
+     * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+     * this case the caller is a device owner.
+     */
+    @Test
+    public void
+            testBackgroundActivityStartsDisallowed_callingPackageNameIsDeviceOwnerNotAborted() {
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_callingPackageNameIsDeviceOwner_notAborted", false,
-                UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
                 false, false, false, false, true);
+    }
 
+    /**
+     * This test ensures that supported usecases aren't aborted when background starts are
+     * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+     * this case the caller is an IME.
+     */
+    @Test
+    public void testBackgroundActivityStartsDisallowed_callingPackageNameIsImeNotAborted() {
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
         setupImeWindow();
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_callingPackageNameIsIme_notAborted", false,
-                CURRENT_IME_UID, false, PROCESS_STATE_TOP + 1,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                CURRENT_IME_UID, false, PROCESS_STATE_BOUND_TOP,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
                 false, false, false, false, false);
-
     }
 
     private void runAndVerifyBackgroundActivityStartsSubtest(String name, boolean shouldHaveAborted,
@@ -926,12 +1052,12 @@
                         ACTIVITY_TYPE_STANDARD, false /* onTop */));
         // Activity should start invisible since we are bringing it to front.
         singleTaskActivity.setVisible(false);
-        singleTaskActivity.mVisibleRequested = false;
+        singleTaskActivity.setVisibleRequested(false);
 
         // Create another activity on top of the secondary display.
         final Task topStack = secondaryTaskContainer.createRootTask(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final Task topTask = new TaskBuilder(mSupervisor).setParentTaskFragment(topStack).build();
+        final Task topTask = new TaskBuilder(mSupervisor).setParentTask(topStack).build();
         new ActivityBuilder(mAtm).setTask(topTask).build();
 
         doReturn(mActivityMetricsLogger).when(mSupervisor).getActivityMetricsLogger();
@@ -1127,7 +1253,7 @@
         final ActivityStarter starter = prepareStarter(0 /* flags */);
         starter.mStartActivity = new ActivityBuilder(mAtm).build();
         final Task task = new TaskBuilder(mAtm.mTaskSupervisor)
-                .setParentTaskFragment(createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN,
+                .setParentTask(createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN,
                         ACTIVITY_TYPE_STANDARD))
                 .setUserId(10)
                 .build();
@@ -1138,6 +1264,26 @@
     }
 
     @Test
+    public void testRecycleTaskWakeUpWhenDreaming() {
+        doNothing().when(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
+        doReturn(true).when(mWm.mAtmService).isDreaming();
+        final ActivityStarter starter = prepareStarter(0 /* flags */);
+        final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        starter.mStartActivity = target;
+        target.setVisibleRequested(false);
+        target.setTurnScreenOn(true);
+        // Assume the flag was consumed by relayout.
+        target.setCurrentLaunchCanTurnScreenOn(false);
+        startActivityInner(starter, target, null /* source */, null /* options */,
+                null /* inTask */, null /* inTaskFragment */);
+        // The flag should be set again when resuming (from recycleTask) the target as top.
+        assertTrue(target.currentLaunchCanTurnScreenOn());
+        // In real case, dream activity has a higher priority (TaskDisplayArea#getPriority) that
+        // will be put at a higher z-order. So it relies on wakeUp() to be dismissed.
+        verify(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
+    }
+
+    @Test
     public void testTargetTaskInSplitScreen() {
         final ActivityStarter starter =
                 prepareStarter(FLAG_ACTIVITY_LAUNCH_ADJACENT, false /* mockGetRootTask */);
@@ -1323,6 +1469,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);
 
@@ -1419,10 +1591,10 @@
         final ActivityRecord activityTop = new ActivityBuilder(mAtm).setTask(task).build();
 
         activityBot.setVisible(false);
-        activityBot.mVisibleRequested = false;
+        activityBot.setVisibleRequested(false);
 
         assertTrue(activityTop.isVisible());
-        assertTrue(activityTop.mVisibleRequested);
+        assertTrue(activityTop.isVisibleRequested());
 
         final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_REORDER_TO_FRONT
                         | FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 2fccd64..368b750 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -344,7 +344,7 @@
 
         // Assume the activity is finishing and hidden because it was crashed.
         activity.finishing = true;
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setVisible(false);
         activity.getTask().setPausingActivity(activity);
         homeActivity.setState(PAUSED, "test");
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..c73237e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
@@ -56,6 +57,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.annotation.Nullable;
+import android.graphics.Rect;
 import android.gui.DropInputMode;
 import android.os.Binder;
 import android.os.IBinder;
@@ -120,7 +122,7 @@
         final ActivityRecord top = createActivityRecord(task);
         top.setState(ActivityRecord.State.RESUMED, "test");
         behind.setState(ActivityRecord.State.STARTED, "test");
-        behind.mVisibleRequested = true;
+        behind.setVisibleRequested(true);
 
         task.removeActivities("test", false /* excludingTaskOverlay */);
         assertFalse(mDisplayContent.mAppTransition.isReady());
@@ -292,7 +294,7 @@
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -317,12 +319,12 @@
         //                   +- [Task2] - [ActivityRecord2] (opening, visible)
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(true);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.mRequestForceTransition = true;
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
         activity2.mRequestForceTransition = true;
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -389,7 +391,7 @@
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
         attrs.setTitle("AppWindow2");
         final TestWindowState appWindow2 = createWindowState(attrs, activity2);
         appWindow2.mWillReplaceWindow = true;
@@ -422,17 +424,17 @@
         //                               +- [ActivityRecord4] (invisible)
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
                 activity1.getTask());
         activity2.setVisible(false);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
         final ActivityRecord activity4 = createActivityRecord(mDisplayContent,
                 activity3.getTask());
         activity4.setVisible(false);
-        activity4.mVisibleRequested = false;
+        activity4.setVisibleRequested(false);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -457,7 +459,7 @@
         //                            +- [ActivityRecord2] (closing, visible)
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
                 activity1.getTask());
 
@@ -488,7 +490,7 @@
 
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.setOccludesParent(false);
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
@@ -526,13 +528,13 @@
 
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.setOccludesParent(false);
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
                 activity1.getTask());
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
         activity3.setOccludesParent(false);
@@ -565,7 +567,7 @@
         final Task parentTask = createTask(mDisplayContent);
         final ActivityRecord activity1 = createActivityRecordWithParentTask(parentTask);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecordWithParentTask(parentTask);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -591,17 +593,17 @@
         final Task singleTopRoot = createTask(mDisplayContent);
         final TaskBuilder builder = new TaskBuilder(mSupervisor)
                 .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
-                .setParentTaskFragment(singleTopRoot)
+                .setParentTask(singleTopRoot)
                 .setCreatedByOrganizer(true);
         final Task splitRoot1 = builder.build();
         final Task splitRoot2 = builder.build();
         splitRoot1.setAdjacentTaskFragment(splitRoot2);
         final ActivityRecord activity1 = createActivityRecordWithParentTask(splitRoot1);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecordWithParentTask(splitRoot2);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -620,48 +622,15 @@
         // [DefaultTDA] - [Task] -+- [TaskFragment1] - [ActivityRecord1] (opening, invisible)
         //                        +- [TaskFragment2] - [ActivityRecord2] (closing, visible)
         final Task parentTask = createTask(mDisplayContent);
-        final TaskFragment taskFragment1 = createTaskFragmentWithParentTask(parentTask,
-                false /* createEmbeddedTask */);
+        final TaskFragment taskFragment1 = createTaskFragmentWithActivity(parentTask);
         final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
 
-        final TaskFragment taskFragment2 = createTaskFragmentWithParentTask(parentTask,
-                false /* createEmbeddedTask */);
+        final TaskFragment taskFragment2 = createTaskFragmentWithActivity(parentTask);
         final ActivityRecord activity2 = taskFragment2.getTopMostActivity();
         activity2.setVisible(true);
-        activity2.mVisibleRequested = false;
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity2);
-
-        // Promote animation targets up to TaskFragment level, not beyond.
-        assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment1}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment2}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-    @Test
-    public void testGetAnimationTargets_openingClosingTaskFragmentWithEmbeddedTask() {
-        // [DefaultTDA] - [Task] -+- [TaskFragment1] - [ActivityRecord1] (opening, invisible)
-        //                        +- [TaskFragment2] - [ActivityRecord2] (closing, visible)
-        final Task parentTask = createTask(mDisplayContent);
-        final TaskFragment taskFragment1 = createTaskFragmentWithParentTask(parentTask,
-                true /* createEmbeddedTask */);
-        final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
-        activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
-
-        final TaskFragment taskFragment2 = createTaskFragmentWithParentTask(parentTask,
-                true /* createEmbeddedTask */);
-        final ActivityRecord activity2 = taskFragment2.getTopMostActivity();
-        activity2.setVisible(true);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -682,15 +651,14 @@
         // [DefaultTDA] -+- [Task1] - [TaskFragment1] - [ActivityRecord1] (opening, invisible)
         //               +- [Task2] - [ActivityRecord2] (closing, visible)
         final Task task1 = createTask(mDisplayContent);
-        final TaskFragment taskFragment1 = createTaskFragmentWithParentTask(task1,
-                false /* createEmbeddedTask */);
+        final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
         final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(true);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -712,15 +680,14 @@
         // [DefaultTDA] -+- [Task1] - [TaskFragment1] - [ActivityRecord1] (closing, visible)
         //               +- [Task2] - [ActivityRecord2] (opening, invisible)
         final Task task1 = createTask(mDisplayContent);
-        final TaskFragment taskFragment1 = createTaskFragmentWithParentTask(task1,
-                false /* createEmbeddedTask */);
+        final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
         final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
         activity1.setVisible(true);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity2);
@@ -743,13 +710,13 @@
         //                   +- [Task2] (embedded) - [ActivityRecord2] (opening, invisible)
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
 
         final Task task2 = createTask(mDisplayContent);
         task2.mRemoveWithTaskOrganizer = true;
         final ActivityRecord activity2 = createActivityRecord(task2);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -777,7 +744,7 @@
 
         final ActivityRecord activity1 = createActivityRecord(task);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecord(task);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -918,7 +885,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Create a TaskFragment with embedded activity.
         final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -935,11 +902,77 @@
     }
 
     @Test
+    public void testOverrideTaskFragmentAdapter_noOverrideWithOnlyTaskFragmentFillingTask() {
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord closingActivity = createActivityRecord(task);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Create a TaskFragment with embedded activity.
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+
+        // Make sure the TaskFragment is not embedded.
+        assertFalse(taskFragment.isEmbeddedWithBoundsOverride());
+        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+        prepareActivityForAppTransition(closingActivity);
+        prepareActivityForAppTransition(openingActivity);
+        final int uid = 12345;
+        closingActivity.info.applicationInfo.uid = uid;
+        openingActivity.info.applicationInfo.uid = uid;
+        task.effectiveUid = uid;
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(openingActivity, closingActivity,
+                null /* changingTaskFragment */);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // Animation is not run by the remote handler because the activity is filling the Task.
+        assertFalse(remoteAnimationRunner.isAnimationStarted());
+    }
+
+    @Test
+    public void testOverrideTaskFragmentAdapter_overrideWithTaskFragmentNotFillingTask() {
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord closingActivity = createActivityRecord(task);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Create a TaskFragment with embedded activity.
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+
+        // Make sure the TaskFragment is embedded.
+        taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        final Rect embeddedBounds = new Rect(task.getBounds());
+        embeddedBounds.right = embeddedBounds.left + embeddedBounds.width() / 2;
+        taskFragment.setBounds(embeddedBounds);
+        assertTrue(taskFragment.isEmbeddedWithBoundsOverride());
+        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+        prepareActivityForAppTransition(closingActivity);
+        prepareActivityForAppTransition(openingActivity);
+        final int uid = 12345;
+        closingActivity.info.applicationInfo.uid = uid;
+        openingActivity.info.applicationInfo.uid = uid;
+        task.effectiveUid = uid;
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(openingActivity, closingActivity,
+                null /* changingTaskFragment */);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // Animation run by the remote handler.
+        assertTrue(remoteAnimationRunner.isAnimationStarted());
+    }
+
+    @Test
     public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Closing non-embedded activity.
         final ActivityRecord closingActivity = createActivityRecord(task);
@@ -964,7 +997,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Closing TaskFragment with embedded activity.
         final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -991,7 +1024,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Closing activity in Task1.
         final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
@@ -1015,7 +1048,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Closing TaskFragment with embedded activity.
         final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -1043,7 +1076,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Create a TaskFragment with embedded activity.
         final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -1069,7 +1102,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Create a TaskFragment with embedded activities, one is trusted embedded, and the other
         // one is untrusted embedded.
@@ -1128,7 +1161,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Create a TaskFragment with only trusted embedded activity
         final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
@@ -1168,7 +1201,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Create a TaskFragment with only trusted embedded activity
         final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
@@ -1227,6 +1260,8 @@
     @Test
     public void testTransitionGoodToGoForTaskFragments_detachedApp() {
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final ITaskFragmentOrganizer iOrganizer = getITaskFragmentOrganizer(organizer);
+        mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
         final Task task = createTask(mDisplayContent);
         final TaskFragment changeTaskFragment =
                 createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -1259,7 +1294,7 @@
     }
 
     /** Registers remote animation for the organizer. */
-    private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer, int taskId,
+    private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
             TestRemoteAnimationRunner remoteAnimationRunner) {
         final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
                 remoteAnimationRunner, 10, 1);
@@ -1268,9 +1303,10 @@
         definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
         definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
         definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
+        definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
+        definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, adapter);
         mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
-        mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, taskId,
-                definition);
+        mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
     }
 
     private static ITaskFragmentOrganizer getITaskFragmentOrganizer(
@@ -1300,6 +1336,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..6b814e6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -16,8 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -27,7 +25,6 @@
 import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
@@ -52,6 +49,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.mock;
@@ -70,11 +68,14 @@
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
+import android.view.animation.Animation;
 import android.window.ITaskFragmentOrganizer;
 import android.window.TaskFragmentOrganizer;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.policy.TransitionAnimation;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -227,23 +228,7 @@
 
     @Test
     public void testTaskFragmentOpeningTransition() {
-        final ActivityRecord activity = createHierarchyForTaskFragmentTest(
-                false /* createEmbeddedTask */);
-        activity.setVisible(false);
-
-        mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
-        mDisplayContent.mOpeningApps.add(activity);
-        assertEquals(TRANSIT_OLD_TASK_FRAGMENT_OPEN,
-                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
-                        null /* oldWallpaper */, false /* skipAppTransitionAnimation */));
-    }
-
-    @Test
-    public void testEmbeddedTaskOpeningTransition() {
-        final ActivityRecord activity = createHierarchyForTaskFragmentTest(
-                true /* createEmbeddedTask */);
+        final ActivityRecord activity = createHierarchyForTaskFragmentTest();
         activity.setVisible(false);
 
         mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
@@ -257,23 +242,7 @@
 
     @Test
     public void testTaskFragmentClosingTransition() {
-        final ActivityRecord activity = createHierarchyForTaskFragmentTest(
-                false /* createEmbeddedTask */);
-        activity.setVisible(true);
-
-        mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
-        mDisplayContent.mClosingApps.add(activity);
-        assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CLOSE,
-                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
-                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
-                        null /* oldWallpaper */, false /* skipAppTransitionAnimation */));
-    }
-
-    @Test
-    public void testEmbeddedTaskClosingTransition() {
-        final ActivityRecord activity = createHierarchyForTaskFragmentTest(
-                true /* createEmbeddedTask */);
+        final ActivityRecord activity = createHierarchyForTaskFragmentTest();
         activity.setVisible(true);
 
         mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
@@ -291,19 +260,16 @@
      * {@link AppTransitionController#getAnimationTargets(ArraySet, ArraySet, boolean) the animation
      * target} to promote to Task or above.
      *
-     * @param createEmbeddedTask {@code true} to create embedded Task for verified TaskFragment
      * @return The Activity to be put in either opening or closing Activity
      */
-    private ActivityRecord createHierarchyForTaskFragmentTest(boolean createEmbeddedTask) {
+    private ActivityRecord createHierarchyForTaskFragmentTest() {
         final Task parentTask = createTask(mDisplayContent);
-        final TaskFragment bottomTaskFragment = createTaskFragmentWithParentTask(parentTask,
-                false /* createEmbeddedTask */);
+        final TaskFragment bottomTaskFragment = createTaskFragmentWithActivity(parentTask);
         final ActivityRecord bottomActivity = bottomTaskFragment.getTopMostActivity();
         bottomActivity.setOccludesParent(true);
         bottomActivity.setVisible(true);
 
-        final TaskFragment verifiedTaskFragment = createTaskFragmentWithParentTask(parentTask,
-                createEmbeddedTask);
+        final TaskFragment verifiedTaskFragment = createTaskFragmentWithActivity(parentTask);
         final ActivityRecord activity = verifiedTaskFragment.getTopMostActivity();
         activity.setOccludesParent(true);
 
@@ -321,7 +287,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,
@@ -412,50 +377,38 @@
     }
 
     @Test
-    public void testExcludeLauncher() {
+    public void testDelayWhileRecents() {
         final DisplayContent dc = createNewDisplay(Display.STATE_ON);
         doReturn(false).when(dc).onDescendantOrientationChanged(any());
         final Task task = createTask(dc);
 
-        // Simulate activity1 launches activity2
+        // Simulate activity1 launches activity2.
         final ActivityRecord activity1 = createActivityRecord(task);
         activity1.setVisible(true);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
         activity1.allDrawn = true;
-        dc.mClosingApps.add(activity1);
         final ActivityRecord activity2 = createActivityRecord(task);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         activity2.allDrawn = true;
+
+        dc.mClosingApps.add(activity1);
         dc.mOpeningApps.add(activity2);
         dc.prepareAppTransition(TRANSIT_OPEN);
-
-        // Simulate start recents
-        final ActivityRecord homeActivity = createActivityRecord(dc, WINDOWING_MODE_FULLSCREEN,
-                ACTIVITY_TYPE_HOME);
-        homeActivity.setVisible(false);
-        homeActivity.mVisibleRequested = true;
-        homeActivity.allDrawn = true;
-        dc.mOpeningApps.add(homeActivity);
-        dc.prepareAppTransition(TRANSIT_NONE);
-        doReturn(true).when(task)
-                .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
+        assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
 
         // Wait until everything in animation handler get executed to prevent the exiting window
         // from being removed during WindowSurfacePlacer Traversal.
         waitUntilHandlersIdle();
 
+        // Start recents
+        doReturn(true).when(task)
+                .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
+
         dc.mAppTransitionController.handleAppTransitionReady();
 
-        verify(activity1).commitVisibility(eq(false), anyBoolean(), anyBoolean());
-        verify(activity1).applyAnimation(any(), eq(TRANSIT_OLD_ACTIVITY_OPEN), eq(false),
-                anyBoolean(), any());
-        verify(activity2).commitVisibility(eq(true), anyBoolean(), anyBoolean());
-        verify(activity2).applyAnimation(any(), eq(TRANSIT_OLD_ACTIVITY_OPEN), eq(true),
-                anyBoolean(), any());
-        verify(homeActivity).commitVisibility(eq(true), anyBoolean(), anyBoolean());
-        verify(homeActivity, never()).applyAnimation(any(), anyInt(), anyBoolean(), anyBoolean(),
-                any());
+        verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
+        verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
     }
 
     @Test
@@ -511,6 +464,80 @@
         assertEquals(startBounds, taskFragment.mSurfaceFreezer.mFreezeBounds);
     }
 
+    @Test
+    public void testGetNextAppTransitionBackgroundColor() {
+        assumeFalse(WindowManagerService.sEnableShellTransitions);
+
+        // No override by default.
+        assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+
+        // Override with a custom color.
+        mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
+        final int testColor = 123;
+        mDc.mAppTransition.overridePendingAppTransition("testPackage", 0 /* enterAnim */,
+                0 /* exitAnim */, testColor, null /* startedCallback */, null /* endedCallback */,
+                false /* overrideTaskTransaction */);
+
+        assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+        assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+        // Override with ActivityEmbedding remote animation. Background color should be kept.
+        mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
+                false /* sync */, true /* isActivityEmbedding */);
+
+        assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+        assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+        // Background color should not be cleared anymore after #clear().
+        mDc.mAppTransition.clear();
+        assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+        assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+    }
+
+    @Test
+    public void testGetNextAppRequestedAnimation() {
+        assumeFalse(WindowManagerService.sEnableShellTransitions);
+        final String packageName = "testPackage";
+        final int enterAnimResId = 1;
+        final int exitAnimResId = 2;
+        final int testColor = 123;
+        final Animation enterAnim = mock(Animation.class);
+        final Animation exitAnim = mock(Animation.class);
+        final TransitionAnimation transitionAnimation = mDc.mAppTransition.mTransitionAnimation;
+        spyOn(transitionAnimation);
+        doReturn(enterAnim).when(transitionAnimation)
+                .loadAppTransitionAnimation(packageName, enterAnimResId);
+        doReturn(exitAnim).when(transitionAnimation)
+                .loadAppTransitionAnimation(packageName, exitAnimResId);
+
+        // No override by default.
+        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+
+        // Override with a custom animation.
+        mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
+        mDc.mAppTransition.overridePendingAppTransition(packageName, enterAnimResId, exitAnimResId,
+                testColor, null /* startedCallback */, null /* endedCallback */,
+                false /* overrideTaskTransaction */);
+
+        assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+        assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+        assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+        // Override with ActivityEmbedding remote animation. Custom animation should be kept.
+        mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
+                false /* sync */, true /* isActivityEmbedding */);
+
+        assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+        assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+        assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+        // Custom animation should not be cleared anymore after #clear().
+        mDc.mAppTransition.clear();
+        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+    }
+
     private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
         boolean mCancelled = false;
         @Override
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..1b77c95 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -20,35 +20,33 @@
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.window.BackNavigationInfo.typeToString;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.hardware.HardwareBuffer;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.view.WindowManager;
 import android.window.BackAnimationAdapter;
-import android.window.BackEvent;
+import android.window.BackMotionEvent;
 import android.window.BackNavigationInfo;
 import android.window.IOnBackInvokedCallback;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedCallbackInfo;
 import android.window.OnBackInvokedDispatcher;
-import android.window.TaskSnapshot;
 import android.window.WindowOnBackInvokedDispatcher;
 
 import com.android.server.LocalServices;
@@ -67,6 +65,7 @@
     private BackNavigationController mBackNavigationController;
     private WindowManagerInternal mWindowManagerInternal;
     private BackAnimationAdapter mBackAnimationAdapter;
+    private Task mRootHomeTask;
 
     @Before
     public void setUp() throws Exception {
@@ -76,6 +75,7 @@
         LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
         mBackNavigationController.setWindowManager(mWm);
         mBackAnimationAdapter = mock(BackAnimationAdapter.class);
+        mRootHomeTask = initHomeActivity();
     }
 
     @Test
@@ -98,28 +98,74 @@
     @Test
     public void backTypeCrossTaskWhenBackToPreviousTask() {
         Task taskA = createTask(mDefaultDisplay);
-        createActivityRecord(taskA);
-        withSystemCallback(createTopTaskWithActivity());
+        ActivityRecord recordA = createActivityRecord(taskA);
+        Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any());
+
+        final Task topTask = createTopTaskWithActivity();
+        withSystemCallback(topTask);
         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());
+
+        // reset drawning status
+        topTask.forAllWindows(w -> {
+            makeWindowVisibleAndDrawn(w);
+        }, true);
+        setupKeyguardOccluded();
+        backNavigationInfo = startBackNavigation();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
+
+        doReturn(true).when(recordA).canShowWhenLocked();
+        backNavigationInfo = startBackNavigation();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK));
     }
 
     @Test
-    public void backTypeCrossActivityWhenBackToPreviousActivity() {
-        Task task = createTopTaskWithActivity();
-        WindowState window = createAppWindow(task, FIRST_APPLICATION_WINDOW, "window");
-        addToWindowMap(window, true);
-        IOnBackInvokedCallback callback = createOnBackInvokedCallback();
-        window.setOnBackInvokedCallbackInfo(
-                new OnBackInvokedCallbackInfo(callback, OnBackInvokedDispatcher.PRIORITY_SYSTEM));
+    public void backTypeBackToHomeDifferentUser() {
+        Task taskA = createTask(mDefaultDisplay);
+        ActivityRecord recordA = createActivityRecord(taskA);
+        Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any());
+        doReturn(false).when(taskA).showToCurrentUser();
+
+        withSystemCallback(createTopTaskWithActivity());
         BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
         assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME));
+    }
+
+    @Test
+    public void backTypeCrossActivityWhenBackToPreviousActivity() {
+        CrossActivityTestCase testCase = createTopTaskWithTwoActivities();
+        IOnBackInvokedCallback callback = withSystemCallback(testCase.task);
+
+        BackNavigationInfo backNavigationInfo = startBackNavigation();
+        assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
+        assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
+        assertThat(typeToString(backNavigationInfo.getType()))
                 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
-        assertWithMessage("Activity callback").that(
-                backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
+
+        // reset drawing status
+        testCase.recordFront.forAllWindows(w -> {
+            makeWindowVisibleAndDrawn(w);
+        }, true);
+        setupKeyguardOccluded();
+        backNavigationInfo = startBackNavigation();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
+
+        doReturn(true).when(testCase.recordBack).canShowWhenLocked();
+        backNavigationInfo = startBackNavigation();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
     }
 
     @Test
@@ -133,6 +179,7 @@
         WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_WALLPAPER,
                 "Wallpaper");
         addToWindowMap(window, true);
+        makeWindowVisibleAndDrawn(window);
 
         IOnBackInvokedCallback callback = createOnBackInvokedCallback();
         window.setOnBackInvokedCallbackInfo(
@@ -146,12 +193,17 @@
 
     @Test
     public void preparesForBackToHome() {
-        Task task = createTopTaskWithActivity();
-        withSystemCallback(task);
+        final Task topTask = createTopTaskWithActivity();
+        withSystemCallback(topTask);
 
         BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertThat(typeToString(backNavigationInfo.getType()))
                 .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME));
+
+        setupKeyguardOccluded();
+        backNavigationInfo = startBackNavigation();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
     }
 
     @Test
@@ -219,6 +271,20 @@
                 1, appLatch.getCount());
     }
 
+    @Test
+    public void backInfoWindowWithoutDrawn() {
+        WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_APPLICATION,
+                "TestWindow");
+        addToWindowMap(window, true);
+
+        IOnBackInvokedCallback callback = createOnBackInvokedCallback();
+        window.setOnBackInvokedCallbackInfo(
+                new OnBackInvokedCallbackInfo(callback, OnBackInvokedDispatcher.PRIORITY_DEFAULT));
+
+        BackNavigationInfo backNavigationInfo = startBackNavigation();
+        assertThat(backNavigationInfo).isNull();
+    }
+
     private IOnBackInvokedCallback withSystemCallback(Task task) {
         IOnBackInvokedCallback callback = createOnBackInvokedCallback();
         task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo(
@@ -242,11 +308,11 @@
     private IOnBackInvokedCallback createOnBackInvokedCallback() {
         return new IOnBackInvokedCallback.Stub() {
             @Override
-            public void onBackStarted(BackEvent backEvent) {
+            public void onBackStarted(BackMotionEvent backMotionEvent) {
             }
 
             @Override
-            public void onBackProgressed(BackEvent backEvent) {
+            public void onBackProgressed(BackMotionEvent backMotionEvent) {
             }
 
             @Override
@@ -270,14 +336,22 @@
         };
     }
 
-    @NonNull
-    private TaskSnapshotController createMockTaskSnapshotController() {
-        TaskSnapshotController taskSnapshotController = mock(TaskSnapshotController.class);
-        TaskSnapshot taskSnapshot = mock(TaskSnapshot.class);
-        when(taskSnapshot.getHardwareBuffer()).thenReturn(mock(HardwareBuffer.class));
-        when(taskSnapshotController.getSnapshot(anyInt(), anyInt(), anyBoolean(), anyBoolean()))
-                .thenReturn(taskSnapshot);
-        return taskSnapshotController;
+    private Task initHomeActivity() {
+        final Task task = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
+        task.forAllLeafTasks((t) -> {
+            if (t.getTopMostActivity() == null) {
+                final ActivityRecord r = createActivityRecord(t);
+                Mockito.doNothing().when(t).reparentSurfaceControl(any(), any());
+                Mockito.doNothing().when(r).reparentSurfaceControl(any(), any());
+            }
+        }, true);
+        return task;
+    }
+
+    private void setupKeyguardOccluded() {
+        final KeyguardController kc = mRootHomeTask.mTaskSupervisor.getKeyguardController();
+        doReturn(true).when(kc).isKeyguardLocked(anyInt());
+        doReturn(true).when(kc).isDisplayOccluded(anyInt());
     }
 
     @NonNull
@@ -292,9 +366,40 @@
         Mockito.doNothing().when(task).reparentSurfaceControl(any(), any());
         mAtm.setFocusedTask(task.mTaskId, record);
         addToWindowMap(window, true);
+        makeWindowVisibleAndDrawn(window);
         return task;
     }
 
+    @NonNull
+    private CrossActivityTestCase createTopTaskWithTwoActivities() {
+        Task task = createTask(mDefaultDisplay);
+        ActivityRecord record1 = createActivityRecord(task);
+        ActivityRecord record2 = createActivityRecord(task);
+        // enable OnBackInvokedCallbacks
+        record2.info.applicationInfo.privateFlagsExt |=
+                PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK;
+        WindowState window1 = createWindow(null, FIRST_APPLICATION_WINDOW, record1, "window1");
+        WindowState window2 = createWindow(null, FIRST_APPLICATION_WINDOW, record2, "window2");
+        when(task.mSurfaceControl.isValid()).thenReturn(true);
+        when(record1.mSurfaceControl.isValid()).thenReturn(true);
+        when(record2.mSurfaceControl.isValid()).thenReturn(true);
+        Mockito.doNothing().when(task).reparentSurfaceControl(any(), any());
+        Mockito.doNothing().when(record1).reparentSurfaceControl(any(), any());
+        Mockito.doNothing().when(record2).reparentSurfaceControl(any(), any());
+        mAtm.setFocusedTask(task.mTaskId, record1);
+        mAtm.setFocusedTask(task.mTaskId, record2);
+        addToWindowMap(window1, true);
+        addToWindowMap(window2, true);
+
+        makeWindowVisibleAndDrawn(window2);
+
+        CrossActivityTestCase testCase = new CrossActivityTestCase();
+        testCase.task = task;
+        testCase.recordBack = record1;
+        testCase.recordFront = record2;
+        return testCase;
+    }
+
     private void addToWindowMap(WindowState window, boolean focus) {
         mWm.mWindowMap.put(window.mClient.asBinder(), window);
         if (focus) {
@@ -303,4 +408,10 @@
             doReturn(window).when(mWm).getFocusedWindowLocked();
         }
     }
+
+    private class CrossActivityTestCase {
+        public Task task;
+        public ActivityRecord recordBack;
+        public ActivityRecord recordFront;
+    }
 }
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/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 37ab9a0..bc23fa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -590,7 +590,7 @@
         assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
 
         // Make sure top focused display not changed if there is a focused app.
-        window1.mActivityRecord.mVisibleRequested = false;
+        window1.mActivityRecord.setVisibleRequested(false);
         window1.getDisplayContent().setFocusedApp(window1.mActivityRecord);
         updateFocusedWindow();
         assertTrue(!window1.isFocused());
@@ -1106,7 +1106,7 @@
     public void testOrientationBehind() {
         final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setScreenOrientation(getRotatedOrientation(mDisplayContent)).build();
-        prev.mVisibleRequested = false;
+        prev.setVisibleRequested(false);
         final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setScreenOrientation(SCREEN_ORIENTATION_BEHIND).build();
         assertNotEquals(WindowConfiguration.ROTATION_UNDEFINED,
@@ -2693,10 +2693,10 @@
                 ACTIVITY_TYPE_STANDARD, ON_TOP);
         final Task rootTask4 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, ON_TOP);
-        final Task task1 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask1).build();
-        final Task task2 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask2).build();
-        final Task task3 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask3).build();
-        final Task task4 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask4).build();
+        final Task task1 = new TaskBuilder(mSupervisor).setParentTask(rootTask1).build();
+        final Task task2 = new TaskBuilder(mSupervisor).setParentTask(rootTask2).build();
+        final Task task3 = new TaskBuilder(mSupervisor).setParentTask(rootTask3).build();
+        final Task task4 = new TaskBuilder(mSupervisor).setParentTask(rootTask4).build();
 
         // Reordering root tasks while removing root tasks.
         doAnswer(invocation -> {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 70b68c7..6733470 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -133,8 +133,8 @@
         final RoundedCorners roundedCorners = mHasRoundedCorners
                 ? mDisplayContent.calculateRoundedCornersForRotation(mRotation)
                 : RoundedCorners.NO_ROUNDED_CORNERS;
-        return new DisplayFrames(insetsState, info,
-                info.displayCutout, roundedCorners, new PrivacyIndicatorBounds());
+        return new DisplayFrames(insetsState, info, info.displayCutout, roundedCorners,
+                new PrivacyIndicatorBounds(), info.displayShape);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 52af8ad..10f2270 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -45,6 +45,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
@@ -57,6 +58,7 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.view.DisplayInfo;
+import android.view.DisplayShape;
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.PrivacyIndicatorBounds;
@@ -320,7 +322,8 @@
         final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
         mImeWindow.mAboveInsetsState.set(state);
         mDisplayContent.mDisplayFrames = new DisplayFrames(
-                state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds());
+                state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds(),
+                DisplayShape.NONE);
 
         mDisplayContent.setInputMethodWindowLocked(mImeWindow);
         mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM);
@@ -375,10 +378,10 @@
 
         displayPolicy.setCanSystemBarsBeShownByUser(false);
         displayPolicy.requestTransientBars(windowState, true);
-        verify(controlTarget, never()).showInsets(anyInt(), anyBoolean());
+        verify(controlTarget, never()).showInsets(anyInt(), anyBoolean(), any() /* statsToken */);
 
         displayPolicy.setCanSystemBarsBeShownByUser(true);
         displayPolicy.requestTransientBars(windowState, true);
-        verify(controlTarget).showInsets(anyInt(), anyBoolean());
+        verify(controlTarget).showInsets(anyInt(), anyBoolean(), any() /* statsToken */);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index 21197ba..ba68a25 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -243,7 +243,12 @@
         }
 
         @Override
-        public boolean canShowTasksInRecents() {
+        public boolean canShowTasksInHostDeviceRecents() {
+            return true;
+        }
+
+        @Override
+        public boolean isEnteringPipAllowed(int uid) {
             return true;
         }
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index ac3d0f0..75c5b6e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -213,7 +213,7 @@
         assertThat(newTaskBounds).isEqualTo(newDagBounds);
 
         // Activity config bounds is unchanged, size compat bounds is (860x[860x860/1200=616])
-        assertThat(mFirstActivity.getSizeCompatScale()).isLessThan(1f);
+        assertThat(mFirstActivity.getCompatScale()).isLessThan(1f);
         assertThat(activityConfigBounds.width()).isEqualTo(activityBounds.width());
         assertThat(activityConfigBounds.height()).isEqualTo(activityBounds.height());
         assertThat(activitySizeCompatBounds.height()).isEqualTo(newTaskBounds.height());
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index 13ebc93..0568b38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -25,6 +25,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -36,6 +37,8 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
+import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,12 +53,18 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class FrameRateSelectionPriorityTests extends WindowTestsBase {
-    private static final float FLOAT_TOLERANCE = 0.01f;
     private static final int LOW_MODE_ID = 3;
 
     private DisplayPolicy mDisplayPolicy = mock(DisplayPolicy.class);
     private RefreshRatePolicy mRefreshRatePolicy;
     private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
+    private FrameRateVote mTempFrameRateVote = new FrameRateVote();
+
+    private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
+    private static final FrameRateVote FRAME_RATE_VOTE_60_EXACT =
+            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+    private static final FrameRateVote FRAME_RATE_VOTE_60_PREFERRED =
+            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
 
     WindowState createWindow(String name) {
         WindowState window = createWindow(null, TYPE_APPLICATION, name);
@@ -85,12 +94,12 @@
         assertNotNull("Window state is created", appWindow);
 
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority doesn't change.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         // Call the function a few times.
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -109,16 +118,15 @@
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
                 .getPreferredModeId(appWindow), 0);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
-        assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
-                .getPreferredRefreshRate(appWindow), 0, FLOAT_TOLERANCE);
-
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
+        assertFalse(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
+                .updateFrameRateVote(appWindow));
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority stays MAX_VALUE.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
         verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
 
@@ -127,7 +135,7 @@
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes to 1.
         assertEquals(appWindow.mFrameRateSelectionPriority, 1);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), 1);
         verify(appWindow.getPendingTransaction(), never()).setFrameRate(
@@ -138,27 +146,27 @@
     public void testApplicationInFocusWithModeId() {
         final WindowState appWindow = createWindow("appWindow");
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         // Application is in focus.
         appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 1);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
         // Update the mode ID to a requested number.
         appWindow.mAttrs.preferredDisplayModeId = 1;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 0);
-        assertEquals(appWindow.mAppPreferredFrameRate, 60, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_60_EXACT);
 
         // Remove the mode ID request.
         appWindow.mAttrs.preferredDisplayModeId = 0;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 1);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         // Verify we called actions on Transactions correctly.
         verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
@@ -175,7 +183,7 @@
     public void testApplicationNotInFocusWithModeId() {
         final WindowState appWindow = createWindow("appWindow");
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         final WindowState inFocusWindow = createWindow("inFocus");
         appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
@@ -183,14 +191,14 @@
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // The window is not in focus.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         // Update the mode ID to a requested number.
         appWindow.mAttrs.preferredDisplayModeId = 1;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 2);
-        assertEquals(appWindow.mAppPreferredFrameRate, 60, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_60_EXACT);
 
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -204,7 +212,7 @@
     public void testApplicationNotInFocusWithoutModeId() {
         final WindowState appWindow = createWindow("appWindow");
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         final WindowState inFocusWindow = createWindow("inFocus");
         appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
@@ -212,14 +220,14 @@
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // The window is not in focus.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         // Make sure that the mode ID is not set.
         appWindow.mAttrs.preferredDisplayModeId = 0;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority doesn't change.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -237,11 +245,10 @@
         when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
 
         assertEquals(0, mRefreshRatePolicy.getPreferredModeId(appWindow));
-        assertEquals(60, mRefreshRatePolicy.getPreferredRefreshRate(appWindow), FLOAT_TOLERANCE);
 
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         assertEquals(RefreshRatePolicy.LAYER_PRIORITY_UNSET, appWindow.mFrameRateSelectionPriority);
-        assertEquals(60, appWindow.mAppPreferredFrameRate, FLOAT_TOLERANCE);
+        assertEquals(FRAME_RATE_VOTE_60_EXACT, appWindow.mFrameRateVote);
 
         // Call the function a few times.
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -262,19 +269,19 @@
                 .thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
 
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         // Update the mode ID to a requested number.
         appWindow.mAttrs.preferredDisplayModeId = 1;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
 
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         // Remove the mode ID request.
         appWindow.mAttrs.preferredDisplayModeId = 0;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
 
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -292,11 +299,10 @@
         appWindow.mAttrs.preferredRefreshRate = 60;
 
         assertEquals(0, mRefreshRatePolicy.getPreferredModeId(appWindow));
-        assertEquals(60, mRefreshRatePolicy.getPreferredRefreshRate(appWindow), FLOAT_TOLERANCE);
 
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         assertEquals(RefreshRatePolicy.LAYER_PRIORITY_UNSET, appWindow.mFrameRateSelectionPriority);
-        assertEquals(60, appWindow.mAppPreferredFrameRate, FLOAT_TOLERANCE);
+        assertEquals(FRAME_RATE_VOTE_60_PREFERRED, appWindow.mFrameRateVote);
 
         // Call the function a few times.
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -307,6 +313,6 @@
                 any(SurfaceControl.class), anyInt());
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 appWindow.getSurfaceControl(), 60,
-                Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
+                Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index c839d12..a26cad9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -55,7 +55,7 @@
         mDisplayContent.setImeControlTarget(popup);
         mDisplayContent.setImeLayeringTarget(appWin);
         popup.mAttrs.format = PixelFormat.TRANSPARENT;
-        mImeProvider.scheduleShowImePostLayout(appWin);
+        mImeProvider.scheduleShowImePostLayout(appWin, null /* statsToken */);
         assertTrue(mImeProvider.isReadyToShowIme());
     }
 
@@ -64,7 +64,7 @@
         WindowState target = createWindow(null, TYPE_APPLICATION, "app");
         mDisplayContent.setImeLayeringTarget(target);
         mDisplayContent.updateImeInputAndControlTarget(target);
-        mImeProvider.scheduleShowImePostLayout(target);
+        mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */);
         assertTrue(mImeProvider.isReadyToShowIme());
     }
 
@@ -78,11 +78,33 @@
         mDisplayContent.setImeLayeringTarget(target);
         mDisplayContent.setImeControlTarget(target);
 
-        mImeProvider.scheduleShowImePostLayout(target);
+        mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */);
         assertFalse(mImeProvider.isImeShowing());
         mImeProvider.checkShowImePostLayout();
         assertTrue(mImeProvider.isImeShowing());
         mImeProvider.setImeShowing(false);
         assertFalse(mImeProvider.isImeShowing());
     }
+
+    @Test
+    public void testSetFrozen() {
+        WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+        makeWindowVisibleAndDrawn(ime);
+        mImeProvider.setWindowContainer(ime, null, null);
+        mImeProvider.setServerVisible(true);
+        mImeProvider.setClientVisible(true);
+        mImeProvider.updateVisibility();
+        assertTrue(mImeProvider.getSource().isVisible());
+
+        // Freezing IME states and set the server visible as false.
+        mImeProvider.setFrozen(true);
+        mImeProvider.setServerVisible(false);
+        // Expect the IME insets visible won't be changed.
+        assertTrue(mImeProvider.getSource().isVisible());
+
+        // Unfreeze IME states and expect the IME insets became invisible due to pending IME
+        // visible state updated.
+        mImeProvider.setFrozen(false);
+        assertFalse(mImeProvider.getSource().isVisible());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index c898119..cdb2642 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -443,6 +443,44 @@
     }
 
     @Test
+    public void testUpdateAboveInsetsState_imeTargetOnScreenBehavior() {
+        final WindowToken imeToken = createTestWindowToken(TYPE_INPUT_METHOD, mDisplayContent);
+        final WindowState ime = createWindow(null,  TYPE_INPUT_METHOD, imeToken, "ime");
+        final WindowState app = createTestWindow("app");
+
+        getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null);
+        ime.getControllableInsetProvider().setServerVisible(true);
+
+        app.mActivityRecord.setVisibility(true);
+        mDisplayContent.setImeLayeringTarget(app);
+        mDisplayContent.updateImeInputAndControlTarget(app);
+
+        app.setRequestedVisibleTypes(ime(), ime());
+        getController().onInsetsModified(app);
+        assertTrue(ime.getControllableInsetProvider().getSource().isVisible());
+
+        getController().updateAboveInsetsState(true /* notifyInsetsChange */);
+        assertNotNull(app.getInsetsState().peekSource(ITYPE_IME));
+        verify(app, atLeastOnce()).notifyInsetsChanged();
+
+        // Expect the app will still get IME insets even when the app was invisible.
+        // (i.e. app invisible after locking the device)
+        app.mActivityRecord.setVisible(false);
+        app.setHasSurface(false);
+        getController().updateAboveInsetsState(true /* notifyInsetsChange */);
+        assertNotNull(app.getInsetsState().peekSource(ITYPE_IME));
+        verify(app, atLeastOnce()).notifyInsetsChanged();
+
+        // Expect the app will get IME insets when the app is requesting visible.
+        // (i.e. app is going to visible when unlocking the device)
+        app.mActivityRecord.setVisibility(true);
+        assertTrue(app.isVisibleRequested());
+        getController().updateAboveInsetsState(true /* notifyInsetsChange */);
+        assertNotNull(app.getInsetsState().peekSource(ITYPE_IME));
+        verify(app, atLeastOnce()).notifyInsetsChanged();
+    }
+
+    @Test
     public void testDispatchGlobalInsets() {
         final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
         getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 8a6db2c..7cb7c79d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -117,7 +117,7 @@
         Task rootTask = mTestDisplay.getDefaultTaskDisplayArea()
                 .createRootTask(TEST_WINDOWING_MODE, ACTIVITY_TYPE_STANDARD, /* onTop */ true);
         mTestTask = new TaskBuilder(mSupervisor).setComponent(TEST_COMPONENT)
-                .setParentTaskFragment(rootTask).build();
+                .setParentTask(rootTask).build();
         mTestTask.mUserId = TEST_USER_ID;
         mTestTask.mLastNonFullscreenBounds = TEST_BOUNDS;
         mTestTask.setHasBeenVisible(true);
@@ -353,7 +353,7 @@
         final Task anotherTaskOfTheSameUser = new TaskBuilder(mSupervisor)
                 .setComponent(ALTERNATIVE_COMPONENT)
                 .setUserId(TEST_USER_ID)
-                .setParentTaskFragment(stack)
+                .setParentTask(stack)
                 .build();
         anotherTaskOfTheSameUser.setWindowingMode(WINDOWING_MODE_FREEFORM);
         anotherTaskOfTheSameUser.setBounds(200, 300, 400, 500);
@@ -365,7 +365,7 @@
         final Task anotherTaskOfDifferentUser = new TaskBuilder(mSupervisor)
                 .setComponent(TEST_COMPONENT)
                 .setUserId(ALTERNATIVE_USER_ID)
-                .setParentTaskFragment(stack)
+                .setParentTask(stack)
                 .build();
         anotherTaskOfDifferentUser.setWindowingMode(WINDOWING_MODE_FREEFORM);
         anotherTaskOfDifferentUser.setBounds(300, 400, 500, 600);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
index 1246d1e..1be9de7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -35,6 +35,7 @@
 
 import junit.framework.Assert;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -72,6 +73,7 @@
         mLetterboxConfigurationPersister.start();
     }
 
+    @After
     public void tearDown() throws InterruptedException {
         deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
         waitForCompletion(mPersisterQueue);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
index 578a43c..af0d32c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
@@ -50,17 +50,17 @@
         ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
         runWith(mockedProtoLog, this::testProtoLog);
         verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.ERROR), eq(ProtoLogGroup.TEST_GROUP),
-                anyInt(), eq(0b0010101001010111),
+                anyInt(), eq(0b0010010111),
                 eq(com.android.internal.protolog.ProtoLogGroup.TEST_GROUP.isLogToLogcat()
-                        ? "Test completed successfully: %b %d %o %x %e %g %f %% %s"
+                        ? "Test completed successfully: %b %d %x %f %% %s"
                         : null),
-                eq(new Object[]{true, 1L, 2L, 3L, 0.4, 0.5, 0.6, "ok"}));
+                eq(new Object[]{true, 1L, 2L, 0.3, "ok"}));
     }
 
     private void testProtoLog() {
         ProtoLog.e(ProtoLogGroup.TEST_GROUP,
-                "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
-                true, 1, 2, 3, 0.4, 0.5, 0.6, "ok");
+                "Test completed successfully: %b %d %x %f %% %s",
+                true, 1, 2, 0.3, "ok");
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 5def703..6f2e3f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -332,7 +332,7 @@
         // other task
         Task task1 = createTaskBuilder(".Task1")
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .setParentTaskFragment(mTaskContainer.getRootHomeTask()).build();
+                .setParentTask(mTaskContainer.getRootHomeTask()).build();
         Task task2 = createTaskBuilder(".Task1")
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
                 .build();
@@ -472,8 +472,8 @@
         final Task root = createTaskBuilder(".CreatedByOrganizerRoot").build();
         root.mCreatedByOrganizer = true;
         // Add organized and non-organized child.
-        final Task child1 = createTaskBuilder(".Task1").setParentTaskFragment(root).build();
-        final Task child2 = createTaskBuilder(".Task2").setParentTaskFragment(root).build();
+        final Task child1 = createTaskBuilder(".Task1").setParentTask(root).build();
+        final Task child2 = createTaskBuilder(".Task2").setParentTask(root).build();
         doReturn(true).when(child1).isOrganized();
         doReturn(false).when(child2).isOrganized();
         mRecentTasks.add(root);
@@ -510,7 +510,7 @@
         mRecentTasks.add(task1);
         // Go home to trigger the removal of untracked tasks.
         mRecentTasks.add(createTaskBuilder(".Home")
-                .setParentTaskFragment(mTaskContainer.getRootHomeTask())
+                .setParentTask(mTaskContainer.getRootHomeTask())
                 .build());
         triggerIdleToTrim();
 
@@ -677,7 +677,7 @@
     public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible() {
         // Create some set of tasks, some of which are visible and some are not
         Task homeTask = createTaskBuilder("com.android.pkg1", ".HomeTask")
-                .setParentTaskFragment(mTaskContainer.getRootHomeTask())
+                .setParentTask(mTaskContainer.getRootHomeTask())
                 .build();
         homeTask.mUserSetupComplete = true;
         mRecentTasks.add(homeTask);
@@ -712,7 +712,7 @@
         t1.mUserSetupComplete = true;
         mRecentTasks.add(t1);
         Task homeTask = createTaskBuilder("com.android.pkg1", ".HomeTask")
-                .setParentTaskFragment(mTaskContainer.getRootHomeTask())
+                .setParentTask(mTaskContainer.getRootHomeTask())
                 .build();
         homeTask.mUserSetupComplete = true;
         mRecentTasks.add(homeTask);
@@ -819,7 +819,7 @@
     @Test
     public void testVisibleTask_displayCanNotShowTaskFromRecents_expectNotVisible() {
         final DisplayContent displayContent = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
-        doReturn(false).when(displayContent).canShowTasksInRecents();
+        doReturn(false).when(displayContent).canShowTasksInHostDeviceRecents();
         final Task task = displayContent.getDefaultTaskDisplayArea().createRootTask(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
         mRecentTasks.add(task);
@@ -976,10 +976,10 @@
 
         // Add a number of tasks (beyond the max) but ensure that nothing is trimmed because all
         // the tasks belong in stacks above the home stack
-        mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTaskFragment(homeStack).build());
-        mRecentTasks.add(createTaskBuilder(".Task1").setParentTaskFragment(aboveHomeStack).build());
-        mRecentTasks.add(createTaskBuilder(".Task2").setParentTaskFragment(aboveHomeStack).build());
-        mRecentTasks.add(createTaskBuilder(".Task3").setParentTaskFragment(aboveHomeStack).build());
+        mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTask(homeStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task1").setParentTask(aboveHomeStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task2").setParentTask(aboveHomeStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task3").setParentTask(aboveHomeStack).build());
 
         triggerTrimAndAssertNoTasksTrimmed();
     }
@@ -997,11 +997,11 @@
         // Add a number of tasks (beyond the max) but ensure that only the task in the stack behind
         // the home stack is trimmed once a new task is added
         final Task behindHomeTask = createTaskBuilder(".Task1")
-                .setParentTaskFragment(behindHomeStack)
+                .setParentTask(behindHomeStack)
                 .build();
         mRecentTasks.add(behindHomeTask);
-        mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTaskFragment(homeStack).build());
-        mRecentTasks.add(createTaskBuilder(".Task2").setParentTaskFragment(aboveHomeStack).build());
+        mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTask(homeStack).build());
+        mRecentTasks.add(createTaskBuilder(".Task2").setParentTask(aboveHomeStack).build());
 
         triggerTrimAndAssertTrimmed(behindHomeTask);
     }
@@ -1017,12 +1017,12 @@
 
         // Add a number of tasks (beyond the max) on each display, ensure that the tasks are not
         // removed
-        mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTaskFragment(homeTask).build());
-        mRecentTasks.add(createTaskBuilder(".Task1").setParentTaskFragment(otherDisplayRootTask)
+        mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTask(homeTask).build());
+        mRecentTasks.add(createTaskBuilder(".Task1").setParentTask(otherDisplayRootTask)
                 .build());
-        mRecentTasks.add(createTaskBuilder(".Task2").setParentTaskFragment(otherDisplayRootTask)
+        mRecentTasks.add(createTaskBuilder(".Task2").setParentTask(otherDisplayRootTask)
                 .build());
-        mRecentTasks.add(createTaskBuilder(".HomeTask2").setParentTaskFragment(homeTask).build());
+        mRecentTasks.add(createTaskBuilder(".HomeTask2").setParentTask(homeTask).build());
 
         triggerTrimAndAssertNoTasksTrimmed();
     }
@@ -1052,7 +1052,7 @@
         Task t1 = createTaskBuilder("com.android.pkg1", ".Task1").build();
         mRecentTasks.add(t1);
         mRecentTasks.add(createTaskBuilder("com.android.pkg1", ".HomeTask")
-                .setParentTaskFragment(mTaskContainer.getRootHomeTask()).build());
+                .setParentTask(mTaskContainer.getRootHomeTask()).build());
         Task t2 = createTaskBuilder("com.android.pkg2", ".Task2").build();
         mRecentTasks.add(t2);
         mRecentTasks.add(createTaskBuilder("com.android.pkg1", ".PipTask")
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 88e58ea..08635ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -172,12 +172,12 @@
         // executed.
         final ActivityRecord activity1 = createActivityRecord(task);
         activity1.setVisible(true);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
         activity1.addWindow(createWindowState(new LayoutParams(TYPE_BASE_APPLICATION), activity1));
 
         final ActivityRecord activity2 = createActivityRecord(task);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
                 mDefaultDisplay.getRotation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index a2b4cb8..de3a526 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -111,7 +111,7 @@
         RecentsAnimationCallbacks recentsAnimation = startRecentsActivity(
                 mRecentsComponent, true /* getRecentsAnimation */);
         // The launch-behind state should make the recents activity visible.
-        assertTrue(recentActivity.mVisibleRequested);
+        assertTrue(recentActivity.isVisibleRequested());
         assertEquals(ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS,
                 mAtm.mDemoteTopAppReasons);
         assertFalse(mAtm.mInternal.useTopSchedGroupForTopProcess());
@@ -119,7 +119,7 @@
         // Simulate the animation is cancelled without changing the stack order.
         recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */);
         // The non-top recents activity should be invisible by the restored launch-behind state.
-        assertFalse(recentActivity.mVisibleRequested);
+        assertFalse(recentActivity.isVisibleRequested());
         assertEquals(0, mAtm.mDemoteTopAppReasons);
     }
 
@@ -164,7 +164,7 @@
         // The activity is started in background so it should be invisible and will be stopped.
         assertThat(recentsActivity).isNotNull();
         assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
-        assertFalse(recentsActivity.mVisibleRequested);
+        assertFalse(recentsActivity.isVisibleRequested());
 
         // Assume it is stopped to test next use case.
         recentsActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */,
@@ -360,7 +360,7 @@
                 true);
 
         // Ensure we find the task for the right user and it is made visible
-        assertTrue(otherUserHomeActivity.mVisibleRequested);
+        assertTrue(otherUserHomeActivity.isVisibleRequested());
     }
 
     private void startRecentsActivity() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 9d2eb26..bcaf886 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -16,22 +16,29 @@
 
 package com.android.server.wm;
 
+import static android.view.SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.hardware.display.DisplayManager;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display.Mode;
+import android.view.Surface;
 import android.view.WindowManager.LayoutParams;
 
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,7 +52,6 @@
 @RunWith(WindowTestRunner.class)
 @FlakyTest
 public class RefreshRatePolicyTest extends WindowTestsBase {
-    private static final float FLOAT_TOLERANCE = 0.01f;
     private static final int HI_MODE_ID = 1;
     private static final float HI_REFRESH_RATE = 90;
 
@@ -57,6 +63,19 @@
 
     private RefreshRatePolicy mPolicy;
     private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
+    private FrameRateVote mTempFrameRateVote = new FrameRateVote();
+
+    private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
+    private static final FrameRateVote FRAME_RATE_VOTE_DENY_LIST =
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+    private static final FrameRateVote FRAME_RATE_VOTE_LOW_EXACT =
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+    private static final FrameRateVote FRAME_RATE_VOTE_HI_EXACT =
+            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+    private static final FrameRateVote FRAME_RATE_VOTE_LOW_PREFERRED =
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+    private static final FrameRateVote FRAME_RATE_VOTE_HI_PREFERRED =
+            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
 
     // Parcel and Unparcel the LayoutParams in the window state to test the path the object
     // travels from the app's process to system server
@@ -89,6 +108,8 @@
     WindowState createWindow(String name) {
         WindowState window = createWindow(null, TYPE_BASE_APPLICATION, name);
         when(window.getDisplayInfo()).thenReturn(mDisplayInfo);
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
         return window;
     }
 
@@ -98,20 +119,23 @@
         cameraUsingWindow.mAttrs.packageName = "com.android.test";
         parcelLayoutParams(cameraUsingWindow);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.addRefreshRateRangeForPackage("com.android.test",
                 LOW_REFRESH_RATE, LOW_REFRESH_RATE);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.removeRefreshRateRangeForPackage("com.android.test");
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
     }
@@ -122,20 +146,23 @@
         cameraUsingWindow.mAttrs.packageName = "com.android.test";
         parcelLayoutParams(cameraUsingWindow);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.addRefreshRateRangeForPackage("com.android.test",
                 LOW_REFRESH_RATE, MID_REFRESH_RATE);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(MID_REFRESH_RATE,
                 mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.removeRefreshRateRangeForPackage("com.android.test");
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
     }
@@ -146,20 +173,23 @@
         cameraUsingWindow.mAttrs.packageName = "com.android.test";
         parcelLayoutParams(cameraUsingWindow);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.addRefreshRateRangeForPackage("com.android.test",
                 LOW_REFRESH_RATE - 10, HI_REFRESH_RATE + 10);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(HI_REFRESH_RATE,
                 mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.removeRefreshRateRangeForPackage("com.android.test");
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
     }
@@ -171,8 +201,8 @@
         parcelLayoutParams(denylistedWindow);
         when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
         assertEquals(0, mPolicy.getPreferredModeId(denylistedWindow));
-        assertEquals(LOW_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(denylistedWindow));
+        assertEquals(FRAME_RATE_VOTE_DENY_LIST, denylistedWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
     }
@@ -185,8 +215,8 @@
         parcelLayoutParams(overrideWindow);
         when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
         assertEquals(HI_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(HI_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_HI_EXACT, overrideWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
@@ -199,8 +229,8 @@
         parcelLayoutParams(overrideWindow);
         when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(HI_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, overrideWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
@@ -214,8 +244,8 @@
         mPolicy.addRefreshRateRangeForPackage("com.android.test",
                 LOW_REFRESH_RATE, LOW_REFRESH_RATE);
         assertEquals(HI_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(HI_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_HI_EXACT, overrideWindow.mFrameRateVote);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(LOW_REFRESH_RATE,
@@ -231,8 +261,8 @@
         mPolicy.addRefreshRateRangeForPackage("com.android.test",
                 LOW_REFRESH_RATE, LOW_REFRESH_RATE);
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(HI_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, overrideWindow.mFrameRateVote);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(LOW_REFRESH_RATE,
@@ -246,8 +276,8 @@
         overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
         parcelLayoutParams(overrideWindow);
         assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(LOW_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_LOW_EXACT, overrideWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
 
@@ -255,7 +285,8 @@
                 overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
@@ -267,8 +298,8 @@
         overrideWindow.mAttrs.preferredRefreshRate = LOW_REFRESH_RATE;
         parcelLayoutParams(overrideWindow);
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(LOW_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_LOW_PREFERRED, overrideWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
 
@@ -276,7 +307,8 @@
                 overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
@@ -288,8 +320,8 @@
         parcelLayoutParams(window);
         when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
         assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertEquals(LOW_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_DENY_LIST, window.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
 
@@ -297,7 +329,8 @@
                 window.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
     }
@@ -311,7 +344,8 @@
         mPolicy.addRefreshRateRangeForPackage("com.android.test",
                 LOW_REFRESH_RATE, LOW_REFRESH_RATE);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(LOW_REFRESH_RATE,
@@ -321,7 +355,8 @@
                 cameraUsingWindow.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
     }
@@ -332,7 +367,8 @@
         window.mAttrs.preferredMaxDisplayRefreshRate = LOW_REFRESH_RATE;
         parcelLayoutParams(window);
         assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(LOW_REFRESH_RATE, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
 
@@ -340,7 +376,8 @@
                 window.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
     }
@@ -351,7 +388,8 @@
         window.mAttrs.preferredMinDisplayRefreshRate = LOW_REFRESH_RATE;
         parcelLayoutParams(window);
         assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
         assertEquals(LOW_REFRESH_RATE, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
 
@@ -359,7 +397,8 @@
                 window.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
     }
@@ -370,8 +409,92 @@
         window.mAttrs.preferredRefreshRate = LOW_REFRESH_RATE;
         parcelLayoutParams(window);
         assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertEquals(LOW_REFRESH_RATE, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_LOW_PREFERRED, window.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
     }
+
+    @Test
+    public void testSwitchingTypeForExactVote() {
+        final WindowState window = createWindow("window");
+        window.mAttrs.preferredDisplayModeId = HI_MODE_ID;
+        parcelLayoutParams(window);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_HI_EXACT, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_HI_EXACT, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+    }
+
+    @Test
+    public void testSwitchingTypeForPreferredVote() {
+        final WindowState window = createWindow("window");
+        window.mAttrs.preferredRefreshRate = HI_REFRESH_RATE;
+        parcelLayoutParams(window);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, window.mFrameRateVote);
+    }
+
+    @Test
+    public void testSwitchingTypeForDenylist() {
+        when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
+
+        final WindowState window = createWindow("window");
+        window.mAttrs.packageName = "com.android.test";
+        parcelLayoutParams(window);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_LOW_EXACT, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_LOW_EXACT, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index c548dc3..eb26415 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -772,6 +772,7 @@
             // Simulating now win1 is being covered by the lockscreen which has no surface,
             // and then launching an activity win2 with the remote animation
             win1.mHasSurface = false;
+            win1.mActivityRecord.setVisibility(false);
             mDisplayContent.mOpeningApps.add(win2.mActivityRecord);
             final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
                     win2.mActivityRecord, new Point(50, 100), null,
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index b46e90d..fb29d3a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -422,7 +422,7 @@
         final ComponentName alias = new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME,
                 aliasActivity);
         final Task parentTask = new TaskBuilder(mSupervisor).build();
-        final Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(parentTask).build();
+        final Task task = new TaskBuilder(mSupervisor).setParentTask(parentTask).build();
         task.origActivity = alias;
         task.realActivity = target;
         new ActivityBuilder(mAtm).setComponent(target).setTask(task).setTargetActivity(
@@ -1068,7 +1068,7 @@
         activity.app = null;
         overlayActivity.app = null;
         // Simulate the process is dead
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setState(DESTROYED, "Test");
 
         assertEquals(2, task.getChildCount());
@@ -1205,7 +1205,7 @@
 
         // There is still an activity1 in rootTask1 so the activity2 should be added to finishing
         // list that will be destroyed until idle.
-        rootTask2.getTopNonFinishingActivity().mVisibleRequested = true;
+        rootTask2.getTopNonFinishingActivity().setVisibleRequested(true);
         final ActivityRecord activity2 = finishTopActivity(rootTask2);
         assertEquals(STOPPING, activity2.getState());
         assertThat(mSupervisor.mStoppingActivities).contains(activity2);
@@ -1410,7 +1410,7 @@
         new ActivityBuilder(mAtm).setTask(task).build();
         // The scenario we are testing is when the app isn't visible yet.
         nonTopVisibleActivity.setVisible(false);
-        nonTopVisibleActivity.mVisibleRequested = false;
+        nonTopVisibleActivity.setVisibleRequested(false);
         doReturn(false).when(nonTopVisibleActivity).attachedToProcess();
         doReturn(true).when(nonTopVisibleActivity).shouldBeVisibleUnchecked();
         doNothing().when(mSupervisor).startSpecificActivity(any(), anyBoolean(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index e0e1d73..a17e124 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -172,9 +172,9 @@
     @Test
     public void testTaskLayerRank() {
         final Task rootTask = new TaskBuilder(mSupervisor).build();
-        final Task task1 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build();
+        final Task task1 = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
         final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task1).build();
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         mWm.mRoot.rankTaskLayers();
 
         assertEquals(1, task1.mLayerRank);
@@ -183,7 +183,7 @@
 
         final Task task2 = new TaskBuilder(mSupervisor).build();
         final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task2).build();
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         mWm.mRoot.rankTaskLayers();
 
         // Note that ensureActivitiesVisible is disabled in SystemServicesTestRule, so both the
@@ -200,8 +200,8 @@
         assertEquals(2, task2.mLayerRank);
 
         // The rank should be updated to invisible when device went to sleep.
-        activity1.mVisibleRequested = false;
-        activity2.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
+        activity2.setVisibleRequested(false);
         doReturn(true).when(mAtm).isSleepingOrShuttingDownLocked();
         doReturn(true).when(mRootWindowContainer).putTasksToSleep(anyBoolean(), anyBoolean());
         mSupervisor.mGoingToSleepWakeLock = mock(PowerManager.WakeLock.class);
@@ -645,7 +645,7 @@
         final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         final Task targetRootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, false /* onTop */);
-        final Task targetTask = new TaskBuilder(mSupervisor).setParentTaskFragment(targetRootTask)
+        final Task targetTask = new TaskBuilder(mSupervisor).setParentTask(targetRootTask)
                 .build();
 
         // Create Recents on secondary display.
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..9aa747a 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)
+                .setParentTask(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..13ea99a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -25,6 +25,7 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
 import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
@@ -71,6 +72,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.times;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -87,7 +89,7 @@
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
 import android.view.InsetsFrameProvider;
-import android.view.InsetsVisibilities;
+import android.view.InsetsSource;
 import android.view.WindowManager;
 
 import androidx.test.filters.MediumTest;
@@ -106,6 +108,9 @@
 import org.junit.Test;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.List;
 
 /**
  * Tests for Size Compatibility mode.
@@ -164,7 +169,7 @@
     public void testRestartProcessIfVisible() {
         setUpDisplaySizeWithApp(1000, 2500);
         doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
-        mActivity.mVisibleRequested = true;
+        mActivity.setVisibleRequested(true);
         mActivity.setSavedState(null /* savedState */);
         mActivity.setState(RESUMED, "testRestart");
         prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
@@ -548,7 +553,7 @@
         resizeDisplay(display, 900, 1800);
 
         mActivity.setState(STOPPED, "testSizeCompatMode");
-        mActivity.mVisibleRequested = false;
+        mActivity.setVisibleRequested(false);
         mActivity.visibleIgnoringKeyguard = false;
         mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
         mActivity.app.computeProcessActivityState();
@@ -600,7 +605,7 @@
         // Make the activity resizable again by restarting it
         clearInvocations(mTask);
         mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
-        mActivity.mVisibleRequested = true;
+        mActivity.setVisibleRequested(true);
         mActivity.restartProcessIfVisible();
         // The full lifecycle isn't hooked up so manually set state to resumed
         mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged");
@@ -2302,8 +2307,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 +2335,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 +2370,49 @@
         // 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
+    public void testLetterboxDetailsForTaskBar_letterboxNotOverlappingTaskBar() {
+        mAtm.mDevEnableNonResizableMultiWindow = true;
+        final int screenHeight = 2200;
+        final int screenWidth = 1400;
+        final int taskbarHeight = 200;
+        setUpDisplaySizeWithApp(screenWidth, screenHeight);
+
+        final TestSplitOrganizer organizer =
+                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+        // Move first activity to split screen which takes half of the screen.
+        organizer.mPrimary.setBounds(0, screenHeight / 2, screenWidth, screenHeight);
+        organizer.putTaskToPrimary(mTask, true);
+
+        final InsetsSource navSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+        navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
+
+        mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15);
+
+        final WindowState w1 = addWindowToActivity(mActivity);
+        w1.mAboveInsetsState.addSource(navSource);
+
+        // Prepare unresizable activity with max aspect ratio
+        prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+        // Refresh the letterboxes
+        mActivity.mRootWindowContainer.performSurfacePlacement();
+
+        final ArgumentCaptor<Rect> cropCapturer = ArgumentCaptor.forClass(Rect.class);
+        verify(mTransaction, times(2)).setWindowCrop(
+                eq(w1.getSurfaceControl()),
+                cropCapturer.capture()
+        );
+        final List<Rect> capturedCrops = cropCapturer.getAllValues();
+
+        final int expectedHeight = screenHeight / 2 - taskbarHeight;
+        assertEquals(2, capturedCrops.size());
+        assertEquals(expectedHeight, capturedCrops.get(0).bottom);
+        assertEquals(expectedHeight, capturedCrops.get(1).bottom);
     }
 
     @Test
@@ -2420,8 +2464,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() {
@@ -3145,7 +3188,7 @@
             task.mResizeMode = activity.info.resizeMode;
             task.getRootActivity().info.resizeMode = activity.info.resizeMode;
         }
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         if (maxAspect >= 0) {
             activity.info.setMaxAspectRatio(maxAspect);
         }
@@ -3165,7 +3208,7 @@
     /** Asserts that the size of activity is larger than its parent so it is scaling. */
     private void assertScaled() {
         assertTrue(mActivity.inSizeCompatMode());
-        assertNotEquals(1f, mActivity.getSizeCompatScale(), 0.0001f /* delta */);
+        assertNotEquals(1f, mActivity.getCompatScale(), 0.0001f /* delta */);
     }
 
     /** Asserts that the activity is best fitted in the parent. */
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index 846a506..5e1fae0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -192,16 +192,17 @@
     }
 
     private static class SyncTarget implements SurfaceSyncGroup.SyncTarget {
-        private SurfaceSyncGroup.SyncBufferCallback mSyncBufferCallback;
+        private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
 
         @Override
-        public void onReadyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
-            mSyncBufferCallback = syncBufferCallback;
+        public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+                SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
+            mTransactionReadyCallback = transactionReadyCallback;
         }
 
         void onBufferReady() {
             SurfaceControl.Transaction t = new StubTransaction();
-            mSyncBufferCallback.onBufferReady(t);
+            mTransactionReadyCallback.onTransactionReady(t);
         }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index d3aa073..df7b3cd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -74,15 +74,15 @@
 
         int id = startSyncSet(bse, listener);
         bse.addToSyncSet(id, mockWC);
-        // Make sure a traversal is requested
-        verify(mWm.mWindowPlacerLocked, times(1)).requestTraversal();
+        // The traversal is not requested because ready is not set.
+        verify(mWm.mWindowPlacerLocked, times(0)).requestTraversal();
 
         bse.onSurfacePlacement();
         verify(listener, times(0)).onTransactionReady(anyInt(), any());
 
         bse.setReady(id);
         // Make sure a traversal is requested
-        verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal();
+        verify(mWm.mWindowPlacerLocked).requestTraversal();
         bse.onSurfacePlacement();
         verify(listener, times(1)).onTransactionReady(eq(id), notNull());
 
@@ -103,14 +103,14 @@
         int id = startSyncSet(bse, listener);
         bse.addToSyncSet(id, mockWC);
         bse.setReady(id);
-        // Make sure traversals requested (one for add and another for setReady)
-        verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal();
+        // Make sure traversals requested.
+        verify(mWm.mWindowPlacerLocked).requestTraversal();
         bse.onSurfacePlacement();
         verify(listener, times(0)).onTransactionReady(anyInt(), any());
 
         mockWC.onSyncFinishedDrawing();
-        // Make sure a (third) traversal is requested.
-        verify(mWm.mWindowPlacerLocked, times(3)).requestTraversal();
+        // Make sure the second traversal is requested.
+        verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal();
         bse.onSurfacePlacement();
         verify(listener, times(1)).onTransactionReady(eq(id), notNull());
     }
@@ -127,8 +127,8 @@
         int id = startSyncSet(bse, listener);
         bse.addToSyncSet(id, mockWC);
         bse.setReady(id);
-        // Make sure traversals requested (one for add and another for setReady)
-        verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal();
+        // Make sure traversals requested.
+        verify(mWm.mWindowPlacerLocked).requestTraversal();
         bse.onSurfacePlacement();
         verify(listener, times(0)).onTransactionReady(anyInt(), any());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 7f09606..91f8d8d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -398,7 +398,7 @@
                     .setParentTask(rootHomeTask).setCreateTask(true).build();
         }
         homeActivity.setVisible(false);
-        homeActivity.mVisibleRequested = true;
+        homeActivity.setVisibleRequested(true);
         assertFalse(rootHomeTask.isVisible());
 
         assertEquals(defaultTaskDisplayArea.getOrientation(), rootHomeTask.getOrientation());
@@ -614,7 +614,7 @@
         final Task pinnedRootTask = mRootWindowContainer.getDefaultTaskDisplayArea()
                 .createRootTask(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP);
         final Task pinnedTask = new TaskBuilder(mAtm.mTaskSupervisor)
-                .setParentTaskFragment(pinnedRootTask).build();
+                .setParentTask(pinnedRootTask).build();
         new ActivityBuilder(mAtm).setActivityFlags(FLAG_ALWAYS_FOCUSABLE)
                 .setTask(pinnedTask).build();
         pinnedRootTask.moveToFront("movePinnedRootTaskToFront");
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 4202f46..834302e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -62,10 +62,12 @@
 import static org.mockito.Mockito.verify;
 
 import android.annotation.NonNull;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -368,7 +370,8 @@
         mController.onActivityReparentedToTask(activity);
         mController.dispatchPendingEvents();
 
-        assertTaskFragmentParentInfoChangedTransaction(task);
+        // There will not be TaskFragmentParentInfoChanged because Task visible request is changed
+        // before the organized TaskFragment is added to the Task.
         assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
     }
 
@@ -403,7 +406,7 @@
         final TaskFragmentTransaction.Change change = changes.get(0);
         assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
         assertEquals(task.mTaskId, change.getTaskId());
-        assertEquals(activity.intent, change.getActivityIntent());
+        assertIntentsEqualForOrganizer(activity.intent, change.getActivityIntent());
         assertNotEquals(activity.token, change.getActivityToken());
         mTransaction.reparentActivityToTaskFragment(mFragmentToken, change.getActivityToken());
         assertApplyTransactionAllowed(mTransaction);
@@ -415,14 +418,70 @@
     }
 
     @Test
+    public void testOnActivityReparentedToTask_untrustedEmbed_notReported() {
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
+                DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
+        mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+        final Task task = createTask(mDisplayContent);
+        task.addChild(mTaskFragment, POSITION_TOP);
+        final ActivityRecord activity = createActivityRecord(task);
+
+        // Make sure the activity is embedded in untrusted mode.
+        activity.info.applicationInfo.uid = uid + 1;
+        doReturn(pid + 1).when(activity).getPid();
+        task.effectiveUid = uid;
+        doReturn(EMBEDDING_ALLOWED).when(task).isAllowedToEmbedActivity(activity, uid);
+        doReturn(false).when(task).isAllowedToEmbedActivityInTrustedMode(activity, uid);
+        doReturn(true).when(task).isAllowedToEmbedActivityInUntrustedMode(activity);
+
+        // Notify organizer if it was embedded before entered Pip.
+        // Create a temporary token since the activity doesn't belong to the same process.
+        clearInvocations(mOrganizer);
+        activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
+        mController.onActivityReparentedToTask(activity);
+        mController.dispatchPendingEvents();
+
+        // Disallow organizer to reparent activity that is untrusted embedded.
+        verify(mOrganizer, never()).onTransactionReady(mTransactionCaptor.capture());
+    }
+
+    @Test
+    public void testOnActivityReparentedToTask_trimReportedIntent() {
+        // Make sure the activity pid/uid is the same as the organizer caller.
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        final ActivityRecord activity = createActivityRecord(mDisplayContent);
+        final Task task = activity.getTask();
+        activity.info.applicationInfo.uid = uid;
+        doReturn(pid).when(activity).getPid();
+        task.effectiveUid = uid;
+        activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
+
+        // Test the Intent trim in #assertIntentTrimmed
+        activity.intent.setComponent(new ComponentName("TestPackage", "TestClass"))
+                .setPackage("TestPackage")
+                .setAction("TestAction")
+                .setData(mock(Uri.class))
+                .putExtra("Test", 123)
+                .setFlags(10);
+
+        mController.onActivityReparentedToTask(activity);
+        mController.dispatchPendingEvents();
+
+        assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
+    }
+
+    @Test
     public void testRegisterRemoteAnimations() {
-        mController.registerRemoteAnimations(mIOrganizer, TASK_ID, mDefinition);
+        mController.registerRemoteAnimations(mIOrganizer, mDefinition);
 
-        assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
+        assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer));
 
-        mController.unregisterRemoteAnimations(mIOrganizer, TASK_ID);
+        mController.unregisterRemoteAnimations(mIOrganizer);
 
-        assertNull(mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
+        assertNull(mController.getRemoteAnimationDefinition(mIOrganizer));
     }
 
     @Test
@@ -764,6 +823,21 @@
     }
 
     @Test
+    public void testOnTransactionHandled_skipTransactionForUnregisterOrganizer() {
+        mController.unregisterOrganizer(mIOrganizer);
+        final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
+        final IBinder fragmentToken = new Binder();
+
+        // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
+        createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
+        mController.onTransactionHandled(new Binder(), mTransaction,
+                getTransitionType(mTransaction), false /* shouldApplyIndependently */);
+
+        // Nothing should happen as the organizer is not registered.
+        assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
+    }
+
+    @Test
     public void testOrganizerRemovedWithPendingEvents() {
         final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
                 .setCreateParentTask()
@@ -1086,6 +1160,7 @@
         doReturn(false).when(task).shouldBeVisible(any());
 
         // Dispatch the initial event in the Task to update the Task visibility to the organizer.
+        clearInvocations(mOrganizer);
         mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
         mController.dispatchPendingEvents();
         verify(mOrganizer).onTransactionReady(any());
@@ -1425,7 +1500,8 @@
         final TaskFragmentTransaction.Change change = changes.remove(0);
         assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
         assertEquals(taskId, change.getTaskId());
-        assertEquals(intent, change.getActivityIntent());
+        assertIntentsEqualForOrganizer(intent, change.getActivityIntent());
+        assertIntentTrimmed(change.getActivityIntent());
         assertEquals(activityToken, change.getActivityToken());
     }
 
@@ -1452,4 +1528,17 @@
         mockParent.lastActiveTime = 100;
         doReturn(true).when(mockParent).shouldBeVisible(any());
     }
+
+    private static void assertIntentsEqualForOrganizer(@NonNull Intent expected,
+            @NonNull Intent actual) {
+        assertEquals(expected.getComponent(), actual.getComponent());
+        assertEquals(expected.getPackage(), actual.getPackage());
+        assertEquals(expected.getAction(), actual.getAction());
+    }
+
+    private static void assertIntentTrimmed(@NonNull Intent intent) {
+        assertNull(intent.getData());
+        assertNull(intent.getExtras());
+        assertEquals(0, intent.getFlags());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 83f1789..8fda191 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -23,7 +23,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -32,9 +32,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
-import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
-import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -118,10 +116,13 @@
         doReturn(true).when(mTaskFragment).isVisibleRequested();
 
         clearInvocations(mTransaction);
+        mTaskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
         mTaskFragment.setBounds(endBounds);
+        assertTrue(mTaskFragment.shouldStartChangeTransition(startBounds));
+        mTaskFragment.initializeChangeTransition(startBounds);
+        mTaskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
 
         // Surface reset when prepare transition.
-        verify(mTaskFragment).initializeChangeTransition(startBounds);
         verify(mTransaction).setPosition(mLeash, 0, 0);
         verify(mTransaction).setWindowCrop(mLeash, 0, 0);
 
@@ -166,7 +167,7 @@
 
         mTaskFragment.setBounds(endBounds);
 
-        verify(mTaskFragment, never()).initializeChangeTransition(any());
+        assertFalse(mTaskFragment.shouldStartChangeTransition(startBounds));
     }
 
     /**
@@ -215,10 +216,8 @@
         final ActivityRecord bottomActivity = createActivityRecord(bottomTask);
         final Task topTask = createTask(mDisplayContent);
         // First create primary TF, and then secondary TF, so that the secondary will be on the top.
-        final TaskFragment primaryTf = createTaskFragmentWithParentTask(
-                topTask, false /* createEmbeddedTask */);
-        final TaskFragment secondaryTf = createTaskFragmentWithParentTask(
-                topTask, false /* createEmbeddedTask */);
+        final TaskFragment primaryTf = createTaskFragmentWithActivity(topTask);
+        final TaskFragment secondaryTf = createTaskFragmentWithActivity(topTask);
         final ActivityRecord primaryActivity = primaryTf.getTopMostActivity();
         final ActivityRecord secondaryActivity = secondaryTf.getTopMostActivity();
         doReturn(true).when(primaryActivity).supportsPictureInPicture();
@@ -382,7 +381,7 @@
         final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
                 ACTIVITY_TYPE_STANDARD);
         final Task leafTask0 = new TaskBuilder(mSupervisor)
-                .setParentTaskFragment(rootTask)
+                .setParentTask(rootTask)
                 .build();
         final TaskFragment organizedTf = new TaskFragmentBuilder(mAtm)
                 .createActivityCount(2)
@@ -418,7 +417,7 @@
         // There is an activity in a different leaf task on top of activity0 and activity1.
         // None of the two has overlay over untrusted mode embedded because it is not the same Task.
         final Task leafTask1 = new TaskBuilder(mSupervisor)
-                .setParentTaskFragment(rootTask)
+                .setParentTask(rootTask)
                 .setOnTop(true)
                 .setCreateActivity(true)
                 .build();
@@ -473,23 +472,6 @@
         doReturn(true).when(taskFragment).smallerThanMinDimension(any());
         assertEquals(EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
                 taskFragment.isAllowedToEmbedActivity(activity));
-
-        // Not allow to start activity across TaskFragments for result.
-        final TaskFragment newTaskFragment = new TaskFragmentBuilder(mAtm)
-                .setParentTask(taskFragment.getTask())
-                .build();
-        final ActivityRecord newActivity = new ActivityBuilder(mAtm)
-                .setUid(FIRST_APPLICATION_UID)
-                .build();
-        doReturn(true).when(newTaskFragment).isAllowedToEmbedActivityInTrustedMode(any(), anyInt());
-        doReturn(false).when(newTaskFragment).smallerThanMinDimension(any());
-        newActivity.resultTo = activity;
-        assertEquals(EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT,
-                newTaskFragment.isAllowedToEmbedActivity(newActivity));
-
-        // Allow embedding if the resultTo activity is finishing.
-        activity.finishing = true;
-        assertEquals(EMBEDDING_ALLOWED, newTaskFragment.isAllowedToEmbedActivity(newActivity));
     }
 
     @Test
@@ -544,4 +526,31 @@
         activity0.moveFocusableActivityToTop("test");
         assertEquals(activity0, mDisplayContent.mFocusedApp);
     }
+
+    @Test
+    public void testIsVisibleWithAdjacent_reportOrientationUnspecified() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment tf0 = createTaskFragmentWithActivity(task);
+        final TaskFragment tf1 = createTaskFragmentWithActivity(task);
+        tf0.setAdjacentTaskFragment(tf1);
+        tf0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        tf1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        task.setBounds(0, 0, 1200, 1000);
+        tf0.setBounds(0, 0, 600, 1000);
+        tf1.setBounds(600, 0, 1200, 1000);
+        final ActivityRecord activity0 = tf0.getTopMostActivity();
+        final ActivityRecord activity1 = tf1.getTopMostActivity();
+        doReturn(true).when(activity0).isVisibleRequested();
+        doReturn(true).when(activity1).isVisibleRequested();
+
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf0.getOrientation(SCREEN_ORIENTATION_UNSET));
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf1.getOrientation(SCREEN_ORIENTATION_UNSET));
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, task.getOrientation(SCREEN_ORIENTATION_UNSET));
+
+        activity0.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf0.getOrientation(SCREEN_ORIENTATION_UNSET));
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf1.getOrientation(SCREEN_ORIENTATION_UNSET));
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, task.getOrientation(SCREEN_ORIENTATION_UNSET));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index aaf855f..e7813ff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -85,6 +85,11 @@
     private static final Rect DISPLAY_STABLE_BOUNDS = new Rect(/* left */ 100,
             /* top */ 200, /* right */ 1620, /* bottom */ 680);
 
+    private static final Rect SMALL_DISPLAY_BOUNDS = new Rect(/* left */ 0, /* top */ 0,
+            /* right */ 1000, /* bottom */ 500);
+    private static final Rect SMALL_DISPLAY_STABLE_BOUNDS = new Rect(/* left */ 100,
+            /* top */ 50, /* right */ 900, /* bottom */ 450);
+
     private ActivityRecord mActivity;
 
     private TaskLaunchParamsModifier mTarget;
@@ -639,6 +644,29 @@
     }
 
     @Test
+    public void testBoundsInOptionsInfersFullscreenWithBoundsOnFreeformSupportFullscreenDisplay() {
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FULLSCREEN);
+        mAtm.mTaskSupervisor.mService.mSupportsFreeformWindowManagement = true;
+
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        final Rect expectedBounds = new Rect(0, 0, 100, 100);
+        options.setLaunchBounds(expectedBounds);
+
+        mCurrent.mPreferredTaskDisplayArea = fullscreenDisplay.getDefaultTaskDisplayArea();
+
+        assertEquals(RESULT_CONTINUE,
+                new CalculateRequestBuilder().setOptions(options).calculate());
+
+        // Setting bounds shouldn't lead to freeform windowing mode on fullscreen display by
+        // default (even with freeform support), but we need to check here if the bounds is set even
+        // with fullscreen windowing mode in case it's restored later.
+        assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+                WINDOWING_MODE_FULLSCREEN);
+        assertEquals(expectedBounds, mResult.mBounds);
+    }
+
+    @Test
     public void testInheritsFreeformModeFromSourceOnFullscreenDisplay() {
         final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FULLSCREEN);
@@ -1020,6 +1048,8 @@
                 WINDOWING_MODE_FULLSCREEN);
         final ActivityRecord source = createSourceActivity(fullscreenDisplay);
         source.getTask().setWindowingMode(WINDOWING_MODE_FREEFORM);
+        // Set some bounds to avoid conflict with the other activity.
+        source.setBounds(100, 100, 200, 200);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
         final Rect expected = new Rect(0, 0, 150, 150);
@@ -1389,6 +1419,20 @@
     }
 
     @Test
+    public void testDefaultFreeformSizeShrinksOnSmallDisplay() {
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FREEFORM, SMALL_DISPLAY_BOUNDS, SMALL_DISPLAY_STABLE_BOUNDS);
+
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setOptions(options)
+                .calculate());
+
+        assertEquals(new Rect(414, 77, 587, 423), mResult.mBounds);
+    }
+
+    @Test
     public void testDefaultFreeformSizeRespectsMinAspectRatio() {
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
@@ -1578,16 +1622,15 @@
         options.setLaunchDisplayId(freeformDisplay.mDisplayId);
 
         mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
-        mCurrent.mBounds.set(100, 300, 1820, 1380);
+        mCurrent.mBounds.set(0, 0, 3000, 2000);
 
         mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
 
         assertEquals(RESULT_CONTINUE,
                 new CalculateRequestBuilder().setOptions(options).calculate());
 
-        assertTrue("Result bounds should start from app bounds's origin, but it's "
-                        + mResult.mBounds,
-                mResult.mBounds.left == 100 && mResult.mBounds.top == 200);
+        // Must shrink to fit the display while reserving aspect ratio.
+        assertEquals(new Rect(127, 227, 766, 653), mResult.mBounds);
     }
 
     @Test
@@ -1603,18 +1646,19 @@
 
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+        final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+                .setMinWidth(500).setMinHeight(500).build();
 
         mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
-        mCurrent.mBounds.set(100, 300, 1820, 1380);
+        mCurrent.mBounds.set(0, 0, 2000, 3000);
 
         mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
 
         assertEquals(RESULT_CONTINUE,
-                new CalculateRequestBuilder().setOptions(options).calculate());
+                new CalculateRequestBuilder().setOptions(options).setLayout(layout).calculate());
 
-        assertTrue("Result bounds should start from top-right corner of app bounds, but "
-                        + "it's " + mResult.mBounds,
-                mResult.mBounds.left == -100 && mResult.mBounds.top == 200);
+        // Must shrink to fit the display while reserving aspect ratio.
+        assertEquals(new Rect(1093, 227, 1593, 727), mResult.mBounds);
     }
 
     @Test
@@ -1789,7 +1833,7 @@
         assertEquals(RESULT_CONTINUE,
                 new CalculateRequestBuilder().setOptions(options).calculate());
 
-        assertEquals(new Rect(100, 200, 400, 500), mResult.mBounds);
+        assertEquals(new Rect(127, 227, 427, 527), mResult.mBounds);
     }
 
     @Test
@@ -1842,13 +1886,18 @@
     }
 
     private TestDisplayContent createNewDisplayContent(int windowingMode) {
+        return createNewDisplayContent(windowingMode, DISPLAY_BOUNDS, DISPLAY_STABLE_BOUNDS);
+    }
+
+    private TestDisplayContent createNewDisplayContent(int windowingMode, Rect displayBounds,
+                                                       Rect displayStableBounds) {
         final TestDisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
         display.getDefaultTaskDisplayArea().setWindowingMode(windowingMode);
-        display.setBounds(DISPLAY_BOUNDS);
+        display.setBounds(displayBounds);
         display.getConfiguration().densityDpi = DENSITY_DEFAULT;
         display.getConfiguration().orientation = ORIENTATION_LANDSCAPE;
         configInsetsState(display.getInsetsStateController().getRawInsetsState(), display,
-                DISPLAY_STABLE_BOUNDS);
+                displayStableBounds);
         return display;
     }
 
@@ -1901,7 +1950,7 @@
         final Task rootTask = display.getDefaultTaskDisplayArea()
                 .createRootTask(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true);
         rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        final Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build();
+        final Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
         // Just work around the unnecessary adjustments for bounds.
         task.getWindowConfiguration().setBounds(bounds);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 66bf78b..74d7884 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -36,6 +36,7 @@
 import static android.view.Surface.ROTATION_90;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 
+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.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
@@ -80,6 +81,7 @@
 import android.util.Xml;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.window.TaskFragmentOrganizer;
 
 import androidx.test.filters.MediumTest;
 
@@ -147,8 +149,8 @@
     public void testRemoveContainer_multipleNestedTasks() {
         final Task rootTask = createTask(mDisplayContent);
         rootTask.mCreatedByOrganizer = true;
-        final Task task1 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build();
-        final Task task2 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build();
+        final Task task1 = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
+        final Task task2 = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
         final ActivityRecord activity1 = createActivityRecord(task1);
         final ActivityRecord activity2 = createActivityRecord(task2);
         activity1.setVisible(false);
@@ -433,6 +435,24 @@
     }
 
     @Test
+    public void testPropagateFocusedStateToRootTask() {
+        final Task rootTask = createTask(mDefaultDisplay);
+        final Task leafTask = createTaskInRootTask(rootTask, 0 /* userId */);
+
+        final ActivityRecord activity = createActivityRecord(leafTask);
+
+        leafTask.getDisplayContent().setFocusedApp(activity);
+
+        assertTrue(leafTask.getTaskInfo().isFocused);
+        assertTrue(rootTask.getTaskInfo().isFocused);
+
+        leafTask.getDisplayContent().setFocusedApp(null);
+
+        assertFalse(leafTask.getTaskInfo().isFocused);
+        assertFalse(rootTask.getTaskInfo().isFocused);
+    }
+
+    @Test
     public void testReturnsToHomeRootTask() throws Exception {
         final Task task = createTask(1);
         spyOn(task);
@@ -478,7 +498,7 @@
         TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea();
         Task rootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FREEFORM,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build();
+        Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
         final Configuration parentConfig = rootTask.getConfiguration();
         parentConfig.windowConfiguration.setBounds(parentBounds);
         parentConfig.densityDpi = DisplayMetrics.DENSITY_DEFAULT;
@@ -780,7 +800,7 @@
         DisplayInfo displayInfo = new DisplayInfo();
         mAtm.mContext.getDisplay().getDisplayInfo(displayInfo);
         final int displayHeight = displayInfo.logicalHeight;
-        final Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build();
+        final Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
         final Configuration inOutConfig = new Configuration();
         final Configuration parentConfig = new Configuration();
         final int longSide = 1200;
@@ -1436,10 +1456,8 @@
     @Test
     public void testResumeTask_doNotResumeTaskFragmentBehindTranslucent() {
         final Task task = createTask(mDisplayContent);
-        final TaskFragment tfBehind = createTaskFragmentWithParentTask(
-                task, false /* createEmbeddedTask */);
-        final TaskFragment tfFront = createTaskFragmentWithParentTask(
-                task, false /* createEmbeddedTask */);
+        final TaskFragment tfBehind = createTaskFragmentWithActivity(task);
+        final TaskFragment tfFront = createTaskFragmentWithActivity(task);
         spyOn(tfFront);
         doReturn(true).when(tfFront).isTranslucent(any());
 
@@ -1459,8 +1477,8 @@
     @Test
     public void testGetTaskFragment() {
         final Task parentTask = createTask(mDisplayContent);
-        final TaskFragment tf0 = createTaskFragmentWithParentTask(parentTask);
-        final TaskFragment tf1 = createTaskFragmentWithParentTask(parentTask);
+        final TaskFragment tf0 = createTaskFragmentWithActivity(parentTask);
+        final TaskFragment tf1 = createTaskFragmentWithActivity(parentTask);
 
         assertNull("Could not find it because there's no organized TaskFragment",
                 parentTask.getTaskFragment(TaskFragment::isOrganizedTaskFragment));
@@ -1471,6 +1489,26 @@
                 tf0, parentTask.getTaskFragment(TaskFragment::isOrganizedTaskFragment));
     }
 
+    @Test
+    public void testReorderActivityToFront() {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final Task task =  new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+        doNothing().when(task).onActivityVisibleRequestedChanged();
+        final ActivityRecord activity = task.getTopMostActivity();
+
+        final TaskFragment fragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord embeddedActivity = fragment.getTopMostActivity();
+        task.moveActivityToFront(activity);
+        assertEquals("Activity must be moved to front", activity, task.getTopMostActivity());
+
+        doNothing().when(fragment).sendTaskFragmentInfoChanged();
+        task.moveActivityToFront(embeddedActivity);
+        assertEquals("Activity must be moved to front", embeddedActivity,
+                task.getTopMostActivity());
+        assertEquals("Activity must not be embedded", embeddedActivity,
+                task.getTopChild());
+    }
+
     private Task getTestTask() {
         final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
         return task.getBottomMostTask();
@@ -1482,7 +1520,7 @@
         TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea();
         Task rootTask = taskDisplayArea.createRootTask(windowingMode, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
-        Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build();
+        Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
 
         final Configuration parentConfig = rootTask.getConfiguration();
         parentConfig.windowConfiguration.setAppBounds(parentBounds);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index bb5aceb..6e72bf3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import android.annotation.Nullable;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -26,6 +27,7 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.ScrollCaptureResponse;
+import android.view.inputmethod.ImeTracker;
 import android.window.ClientWindowFrames;
 
 import com.android.internal.os.IResultReceiver;
@@ -117,10 +119,12 @@
     }
 
     @Override
-    public void showInsets(int types, boolean fromIme) throws RemoteException {
+    public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)
+            throws RemoteException {
     }
 
     @Override
-    public void hideInsets(int types, boolean fromIme) throws RemoteException {
+    public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)
+            throws RemoteException {
     }
 }
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..aaf07b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -135,8 +135,8 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
         fillChangeMap(changes, newTask);
         // End states.
-        closing.mVisibleRequested = false;
-        opening.mVisibleRequested = true;
+        closing.setVisibleRequested(false);
+        opening.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -199,9 +199,9 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
         fillChangeMap(changes, newTask);
         // End states.
-        closing.mVisibleRequested = false;
-        opening.mVisibleRequested = true;
-        opening2.mVisibleRequested = true;
+        closing.setVisibleRequested(false);
+        opening.setVisibleRequested(true);
+        opening2.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -250,8 +250,8 @@
         fillChangeMap(changes, tda);
 
         // End states.
-        showing.mVisibleRequested = true;
-        showing2.mVisibleRequested = true;
+        showing.setVisibleRequested(true);
+        showing2.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -286,16 +286,16 @@
 
         final Task openTask = createTask(mDisplayContent);
         final ActivityRecord opening = createActivityRecord(openTask);
-        opening.mVisibleRequested = false; // starts invisible
+        opening.setVisibleRequested(false); // starts invisible
         final Task closeTask = createTask(mDisplayContent);
         final ActivityRecord closing = createActivityRecord(closeTask);
-        closing.mVisibleRequested = true; // starts visible
+        closing.setVisibleRequested(true); // starts visible
 
         transition.collectExistenceChange(openTask);
         transition.collect(opening);
         transition.collect(closing);
-        opening.mVisibleRequested = true;
-        closing.mVisibleRequested = false;
+        opening.setVisibleRequested(true);
+        closing.setVisibleRequested(false);
 
         ArrayList<WindowContainer> targets = Transition.calculateTargets(
                 transition.mParticipants, transition.mChanges);
@@ -323,7 +323,7 @@
                     WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
             final ActivityRecord act = createActivityRecord(tasks[i]);
             // alternate so that the transition doesn't get promoted to the display area
-            act.mVisibleRequested = (i % 2) == 0; // starts invisible
+            act.setVisibleRequested((i % 2) == 0); // starts invisible
         }
 
         // doesn't matter which order collected since participants is a set
@@ -331,7 +331,7 @@
             transition.collectExistenceChange(tasks[i]);
             final ActivityRecord act = tasks[i].getTopMostActivity();
             transition.collect(act);
-            tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
+            tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
         }
 
         ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -360,7 +360,7 @@
                     WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
             final ActivityRecord act = createActivityRecord(tasks[i]);
             // alternate so that the transition doesn't get promoted to the display area
-            act.mVisibleRequested = (i % 2) == 0; // starts invisible
+            act.setVisibleRequested((i % 2) == 0); // starts invisible
             act.visibleIgnoringKeyguard = (i % 2) == 0;
             if (i == showWallpaperTask) {
                 doReturn(true).when(act).showWallpaper();
@@ -381,7 +381,7 @@
             transition.collectExistenceChange(tasks[i]);
             final ActivityRecord act = tasks[i].getTopMostActivity();
             transition.collect(act);
-            tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
+            tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
         }
 
         ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -417,9 +417,9 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
         fillChangeMap(changes, topTask);
         // End states.
-        showing.mVisibleRequested = true;
-        closing.mVisibleRequested = false;
-        hiding.mVisibleRequested = false;
+        showing.setVisibleRequested(true);
+        closing.setVisibleRequested(false);
+        hiding.setVisibleRequested(false);
 
         participants.add(belowTask);
         participants.add(hiding);
@@ -449,9 +449,9 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         fillChangeMap(changes, topTask);
         // End states.
-        showing.mVisibleRequested = true;
-        opening.mVisibleRequested = true;
-        closing.mVisibleRequested = false;
+        showing.setVisibleRequested(true);
+        opening.setVisibleRequested(true);
+        closing.setVisibleRequested(false);
 
         participants.add(belowTask);
         participants.add(showing);
@@ -479,6 +479,8 @@
         wallpaperWindow.mHasSurface = true;
         doReturn(true).when(mDisplayContent).isAttached();
         transition.collect(mDisplayContent);
+        assertFalse("The change of non-interesting window container should be skipped",
+                transition.mChanges.containsKey(mDisplayContent.getParent()));
         mDisplayContent.getWindowConfiguration().setRotation(
                 (mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4);
 
@@ -529,19 +531,19 @@
     @Test
     public void testOpenActivityInTheSameTaskWithDisplayChange() {
         final ActivityRecord closing = createActivityRecord(mDisplayContent);
-        closing.mVisibleRequested = true;
+        closing.setVisibleRequested(true);
         final Task task = closing.getTask();
         makeTaskOrganized(task);
         final ActivityRecord opening = createActivityRecord(task);
-        opening.mVisibleRequested = false;
+        opening.setVisibleRequested(false);
         makeDisplayAreaOrganized(mDisplayContent.getDefaultTaskDisplayArea(), mDisplayContent);
         final WindowContainer<?>[] wcs = { closing, opening, task, mDisplayContent };
         final Transition transition = createTestTransition(TRANSIT_OPEN);
         for (WindowContainer<?> wc : wcs) {
             transition.collect(wc);
         }
-        closing.mVisibleRequested = false;
-        opening.mVisibleRequested = true;
+        closing.setVisibleRequested(false);
+        opening.setVisibleRequested(true);
         final int newRotation = mDisplayContent.getWindowConfiguration().getRotation() + 1;
         for (WindowContainer<?> wc : wcs) {
             wc.getWindowConfiguration().setRotation(newRotation);
@@ -584,9 +586,9 @@
         changes.put(changeInChange, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         fillChangeMap(changes, openTask);
         // End states.
-        changeInChange.mVisibleRequested = true;
-        openInOpen.mVisibleRequested = true;
-        openInChange.mVisibleRequested = true;
+        changeInChange.setVisibleRequested(true);
+        openInOpen.setVisibleRequested(true);
+        openInChange.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -642,8 +644,8 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         fillChangeMap(changes, newTask);
         // End states.
-        closing.mVisibleRequested = true;
-        opening.mVisibleRequested = true;
+        closing.setVisibleRequested(true);
+        opening.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -683,8 +685,8 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         fillChangeMap(changes, newTask);
         // End states.
-        closing.mVisibleRequested = true;
-        opening.mVisibleRequested = true;
+        closing.setVisibleRequested(true);
+        opening.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -960,7 +962,7 @@
         home.mTransitionController.requestStartTransition(transition, home.getTask(),
                 null /* remoteTransition */, null /* displayChange */);
         transition.collectExistenceChange(home);
-        home.mVisibleRequested = true;
+        home.setVisibleRequested(true);
         mDisplayContent.setFixedRotationLaunchingAppUnchecked(home);
         doReturn(true).when(home).hasFixedRotationTransform(any());
         player.startTransition();
@@ -996,12 +998,12 @@
         // Start out with task2 visible and set up a transition that closes task2 and opens task1
         final Task task1 = createTask(mDisplayContent);
         final ActivityRecord activity1 = createActivityRecord(task1);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
         activity1.setVisible(false);
         final Task task2 = createTask(mDisplayContent);
         makeTaskOrganized(task1, task2);
         final ActivityRecord activity2 = createActivityRecord(task1);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         activity2.setVisible(true);
 
         openTransition.collectExistenceChange(task1);
@@ -1009,9 +1011,9 @@
         openTransition.collectExistenceChange(task2);
         openTransition.collectExistenceChange(activity2);
 
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.setVisible(true);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         // Using abort to force-finish the sync (since we can't wait for drawing in unit test).
         // We didn't call abort on the transition itself, so it will still run onTransactionReady
@@ -1027,8 +1029,8 @@
         closeTransition.collectExistenceChange(task2);
         closeTransition.collectExistenceChange(activity2);
 
-        activity1.mVisibleRequested = false;
-        activity2.mVisibleRequested = true;
+        activity1.setVisibleRequested(false);
+        activity2.setVisibleRequested(true);
 
         openTransition.finishTransition();
 
@@ -1070,12 +1072,12 @@
         // Start out with task2 visible and set up a transition that closes task2 and opens task1
         final Task task1 = createTask(mDisplayContent);
         final ActivityRecord activity1 = createActivityRecord(task1);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
         activity1.setVisible(false);
         final Task task2 = createTask(mDisplayContent);
         makeTaskOrganized(task1, task2);
         final ActivityRecord activity2 = createActivityRecord(task2);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         activity2.setVisible(true);
 
         openTransition.collectExistenceChange(task1);
@@ -1083,9 +1085,9 @@
         openTransition.collectExistenceChange(task2);
         openTransition.collectExistenceChange(activity2);
 
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.setVisible(true);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         // Using abort to force-finish the sync (since we can't wait for drawing in unit test).
         // We didn't call abort on the transition itself, so it will still run onTransactionReady
@@ -1105,8 +1107,8 @@
         closeTransition.collectExistenceChange(activity2);
         closeTransition.setTransientLaunch(activity2, null /* restoreBelow */);
 
-        activity1.mVisibleRequested = false;
-        activity2.mVisibleRequested = true;
+        activity1.setVisibleRequested(false);
+        activity2.setVisibleRequested(true);
         activity2.setVisible(true);
 
         // Using abort to force-finish the sync (since we obviously can't wait for drawing).
@@ -1164,8 +1166,8 @@
         changes.put(activity0, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         changes.put(activity1, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
         // End states.
-        activity0.mVisibleRequested = false;
-        activity1.mVisibleRequested = true;
+        activity0.setVisibleRequested(false);
+        activity1.setVisibleRequested(true);
 
         participants.add(activity0);
         participants.add(activity1);
@@ -1208,9 +1210,9 @@
         changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */,
                 false /* exChg */));
         // End states.
-        closingActivity.mVisibleRequested = false;
-        openingActivity.mVisibleRequested = true;
-        nonEmbeddedActivity.mVisibleRequested = false;
+        closingActivity.setVisibleRequested(false);
+        openingActivity.setVisibleRequested(true);
+        nonEmbeddedActivity.setVisibleRequested(false);
 
         participants.add(closingActivity);
         participants.add(openingActivity);
@@ -1253,8 +1255,8 @@
                 false /* exChg */));
         changes.put(embeddedTf, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
         // End states.
-        nonEmbeddedActivity.mVisibleRequested = false;
-        embeddedActivity.mVisibleRequested = true;
+        nonEmbeddedActivity.setVisibleRequested(false);
+        embeddedActivity.setVisibleRequested(true);
         embeddedTf.setBounds(new Rect(0, 0, 500, 500));
 
         participants.add(nonEmbeddedActivity);
@@ -1283,11 +1285,11 @@
         final ActivityRecord activity = createActivityRecord(task);
         // Start states: set bounds to make sure the start bounds is ignored if it is not visible.
         activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         changes.put(activity, new Transition.ChangeInfo(activity));
         // End states: reset bounds to fill Task.
         activity.getConfiguration().windowConfiguration.setBounds(taskBounds);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
 
         participants.add(activity);
         final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1311,11 +1313,11 @@
         task.getConfiguration().windowConfiguration.setBounds(taskBounds);
         final ActivityRecord activity = createActivityRecord(task);
         // Start states: fills Task without override.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         changes.put(activity, new Transition.ChangeInfo(activity));
         // End states: set bounds to make sure the start bounds is ignored if it is not visible.
         activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
 
         participants.add(activity);
         final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1338,12 +1340,12 @@
         final Task lastParent = createTask(mDisplayContent);
         final Task newParent = createTask(mDisplayContent);
         final ActivityRecord activity = createActivityRecord(lastParent);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         // Skip manipulate the SurfaceControl.
         doNothing().when(activity).setDropInputMode(anyInt());
         changes.put(activity, new Transition.ChangeInfo(activity));
         activity.reparent(newParent, POSITION_TOP);
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
 
         participants.add(activity);
         final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1363,7 +1365,7 @@
         final Task task = createTask(mDisplayContent);
         task.setBounds(new Rect(0, 0, 2000, 1000));
         final ActivityRecord activity = createActivityRecord(task);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         // Skip manipulate the SurfaceControl.
         doNothing().when(activity).setDropInputMode(anyInt());
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
@@ -1411,13 +1413,13 @@
         task.setTaskDescription(taskDescription);
 
         // Start states:
-        embeddedActivity.mVisibleRequested = true;
-        nonEmbeddedActivity.mVisibleRequested = false;
+        embeddedActivity.setVisibleRequested(true);
+        nonEmbeddedActivity.setVisibleRequested(false);
         changes.put(embeddedTf, new Transition.ChangeInfo(embeddedTf));
         changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(nonEmbeddedActivity));
         // End states:
-        embeddedActivity.mVisibleRequested = false;
-        nonEmbeddedActivity.mVisibleRequested = true;
+        embeddedActivity.setVisibleRequested(false);
+        nonEmbeddedActivity.setVisibleRequested(true);
 
         participants.add(embeddedTf);
         participants.add(nonEmbeddedActivity);
@@ -1520,6 +1522,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.setVisibleRequested(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/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index 45e1141..2fccb88a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -95,7 +95,7 @@
         final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
         activity.finishing = true;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setVisibility(false, false);
         assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 6333508..a100b9a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -54,6 +54,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
+import android.view.DisplayShape;
 import android.view.Gravity;
 import android.view.InsetsState;
 import android.view.PrivacyIndicatorBounds;
@@ -165,7 +166,8 @@
         final DisplayInfo info = dc.computeScreenConfiguration(config, Surface.ROTATION_0);
         final DisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0);
         final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(),
-                info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds());
+                info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds(),
+                DisplayShape.NONE);
         wallpaperWindow.mToken.applyFixedRotationTransform(info, displayFrames, config);
 
         // Check that the wallpaper has the same frame in landscape than in portrait
@@ -311,12 +313,12 @@
         r.applyFixedRotationTransform(mDisplayContent.getDisplayInfo(),
                 mDisplayContent.mDisplayFrames, mDisplayContent.getConfiguration());
         // Invisible requested activity should not share its rotation transform.
-        r.mVisibleRequested = false;
+        r.setVisibleRequested(false);
         mDisplayContent.mWallpaperController.adjustWallpaperWindows();
         assertFalse(wallpaperToken.hasFixedRotationTransform());
 
         // Wallpaper should link the transform of its target.
-        r.mVisibleRequested = true;
+        r.setVisibleRequested(true);
         mDisplayContent.mWallpaperController.adjustWallpaperWindows();
         assertEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
         assertTrue(r.hasFixedRotationTransform());
@@ -369,18 +371,9 @@
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         token.finishSync(t, false /* cancel */);
         transit.onTransactionReady(transit.getSyncId(), t);
-        dc.mTransitionController.finishTransition(transit);
+        dc.mTransitionController.finishTransition(transit.getToken());
         assertFalse(wallpaperWindow.isVisible());
         assertFalse(token.isVisible());
-
-        // Assume wallpaper was visible. When transaction is ready without wallpaper target,
-        // wallpaper should be requested to be invisible.
-        token.setVisibility(true);
-        transit = dc.mTransitionController.createTransition(TRANSIT_CLOSE);
-        dc.mTransitionController.collect(token);
-        transit.onTransactionReady(transit.getSyncId(), t);
-        assertFalse(token.isVisibleRequested());
-        assertTrue(token.isVisible());
     }
 
     private static void prepareSmallerSecondDisplay(DisplayContent dc, int width, int height) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index c8ea70c..50fcafc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -1150,8 +1150,8 @@
 
     @Test
     public void testStartChangeTransitionWhenPreviousIsNotFinished() {
-        final WindowContainer container = createTaskFragmentWithParentTask(
-                createTask(mDisplayContent), false);
+        final WindowContainer container = createTaskFragmentWithActivity(
+                createTask(mDisplayContent));
         container.mSurfaceControl = mock(SurfaceControl.class);
         final SurfaceAnimator surfaceAnimator = container.mSurfaceAnimator;
         final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer;
@@ -1214,8 +1214,8 @@
 
     @Test
     public void testUnfreezeWindow_removeWindowFromChanging() {
-        final WindowContainer container = createTaskFragmentWithParentTask(
-                createTask(mDisplayContent), false);
+        final WindowContainer container = createTaskFragmentWithActivity(
+                createTask(mDisplayContent));
         mockSurfaceFreezerSnapshot(container.mSurfaceFreezer);
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
 
@@ -1230,8 +1230,8 @@
 
     @Test
     public void testFailToTaskSnapshot_unfreezeWindow() {
-        final WindowContainer container = createTaskFragmentWithParentTask(
-                createTask(mDisplayContent), false);
+        final WindowContainer container = createTaskFragmentWithActivity(
+                createTask(mDisplayContent));
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         spyOn(container.mSurfaceFreezer);
 
@@ -1244,8 +1244,8 @@
 
     @Test
     public void testRemoveUnstartedFreezeSurfaceWhenFreezeAgain() {
-        final WindowContainer container = createTaskFragmentWithParentTask(
-                createTask(mDisplayContent), false);
+        final WindowContainer container = createTaskFragmentWithActivity(
+                createTask(mDisplayContent));
         container.mSurfaceControl = mock(SurfaceControl.class);
         final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer;
         mockSurfaceFreezerSnapshot(surfaceFreezer);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 4429aef..3d777f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -16,12 +16,16 @@
 
 package com.android.server.wm;
 
+import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_OWN_FOCUS;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -81,6 +85,9 @@
 import android.window.WindowContainerToken;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -99,6 +106,11 @@
     @Rule
     public ExpectedException mExpectedException = ExpectedException.none();
 
+    @Rule
+    public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+            InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+            ADD_TRUSTED_DISPLAY);
+
     @Test
     public void testAddWindowToken() {
         IBinder token = mock(IBinder.class);
@@ -193,7 +205,7 @@
         win.mViewVisibility = View.VISIBLE;
         win.mHasSurface = true;
         win.mActivityRecord.mAppStopped = true;
-        win.mActivityRecord.mVisibleRequested = false;
+        win.mActivityRecord.setVisibleRequested(false);
         win.mActivityRecord.setVisible(false);
         mWm.mWindowMap.put(win.mClient.asBinder(), win);
         final int w = 100;
@@ -396,9 +408,15 @@
     @Test
     public void testSetInTouchMode_multiDisplay_globalTouchModeUpdate() {
         // Create one extra display
-        final VirtualDisplay virtualDisplay = createVirtualDisplay();
+        final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
+        final VirtualDisplay virtualDisplayOwnTouchMode =
+                createVirtualDisplay(/* ownFocus= */ true);
         final int numberOfDisplays = mWm.mRoot.mChildren.size();
-        assertThat(numberOfDisplays).isAtLeast(2);
+        assertThat(numberOfDisplays).isAtLeast(3);
+        final int numberOfGlobalTouchModeDisplays = (int) mWm.mRoot.mChildren.stream()
+                        .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0)
+                        .count();
+        assertThat(numberOfGlobalTouchModeDisplays).isAtLeast(2);
 
         // Enable global touch mode (config_perDisplayFocusEnabled set to false)
         Resources mockResources = mock(Resources.class);
@@ -417,15 +435,15 @@
 
         mWm.setInTouchMode(!currentTouchMode, DEFAULT_DISPLAY);
 
-        verify(mWm.mInputManager, times(numberOfDisplays)).setInTouchMode(
+        verify(mWm.mInputManager, times(numberOfGlobalTouchModeDisplays)).setInTouchMode(
                 eq(!currentTouchMode), eq(callingPid), eq(callingUid),
                 /* hasPermission= */ eq(true), /* displayId= */ anyInt());
     }
 
     @Test
-    public void testSetInTouchMode_multiDisplay_singleDisplayTouchModeUpdate() {
+    public void testSetInTouchMode_multiDisplay_perDisplayFocus_singleDisplayTouchModeUpdate() {
         // Create one extra display
-        final VirtualDisplay virtualDisplay = createVirtualDisplay();
+        final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
         final int numberOfDisplays = mWm.mRoot.mChildren.size();
         assertThat(numberOfDisplays).isAtLeast(2);
 
@@ -452,14 +470,47 @@
                 virtualDisplay.getDisplay().getDisplayId());
     }
 
-    private VirtualDisplay createVirtualDisplay() {
+    @Test
+    public void testSetInTouchMode_multiDisplay_ownTouchMode_singleDisplayTouchModeUpdate() {
+        // Create one extra display
+        final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ true);
+        final int numberOfDisplays = mWm.mRoot.mChildren.size();
+        assertThat(numberOfDisplays).isAtLeast(2);
+
+        // Enable global touch mode (config_perDisplayFocusEnabled set to false)
+        Resources mockResources = mock(Resources.class);
+        spyOn(mContext);
+        when(mContext.getResources()).thenReturn(mockResources);
+        doReturn(false).when(mockResources).getBoolean(
+                com.android.internal.R.bool.config_perDisplayFocusEnabled);
+
+        // Get current touch mode state and setup WMS to run setInTouchMode
+        boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
+        int callingPid = Binder.getCallingPid();
+        int callingUid = Binder.getCallingUid();
+        doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
+        when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
+                android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
+
+        mWm.setInTouchMode(!currentTouchMode, virtualDisplay.getDisplay().getDisplayId());
+
+        // Ensure that new display touch mode state has changed.
+        verify(mWm.mInputManager).setInTouchMode(
+                !currentTouchMode, callingPid, callingUid, /* hasPermission= */ true,
+                virtualDisplay.getDisplay().getDisplayId());
+    }
+
+    private VirtualDisplay createVirtualDisplay(boolean ownFocus) {
         // Create virtual display
         Point surfaceSize = new Point(
                 mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
                 mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+        int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC;
+        if (ownFocus) {
+            flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS | VIRTUAL_DISPLAY_FLAG_TRUSTED;
+        }
         VirtualDisplay virtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay",
-                surfaceSize.x, surfaceSize.y,
-                DisplayMetrics.DENSITY_140, new Surface(), VIRTUAL_DISPLAY_FLAG_PUBLIC);
+                surfaceSize.x, surfaceSize.y, DisplayMetrics.DENSITY_140, new Surface(), flags);
         final int displayId = virtualDisplay.getDisplay().getDisplayId();
         mWm.mRoot.onDisplayAdded(displayId);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 3b64c51..e5e9f54 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -736,11 +736,12 @@
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, task1);
 
-        task1.setAdjacentTaskFragment(null);
-        task2.setAdjacentTaskFragment(null);
         wct = new WindowContainerTransaction();
+        wct.clearAdjacentRoots(info1.token);
         wct.clearLaunchAdjacentFlagRoot(info1.token);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+        assertEquals(task1.getAdjacentTaskFragment(), null);
+        assertEquals(task2.getAdjacentTaskFragment(), null);
         assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, null);
     }
 
@@ -991,7 +992,7 @@
         final Task task = createTask(rootTaskController1);
         final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window");
 
-        w.mActivityRecord.mVisibleRequested = true;
+        w.mActivityRecord.setVisibleRequested(true);
         w.mActivityRecord.setVisible(true);
 
         BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
@@ -1176,7 +1177,7 @@
         assertTrue(rootTask2.isOrganized());
 
         // Verify a back pressed does not call the organizer
-        mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
+        mWm.mAtmService.mActivityClientController.onBackPressed(activity.token,
                 new IRequestFinishCallback.Default());
         // Ensure events dispatch to organizer.
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -1187,7 +1188,7 @@
                 rootTask.mRemoteToken.toWindowContainerToken(), true);
 
         // Verify now that the back press does call the organizer
-        mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
+        mWm.mAtmService.mActivityClientController.onBackPressed(activity.token,
                 new IRequestFinishCallback.Default());
         // Ensure events dispatch to organizer.
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -1198,7 +1199,7 @@
                 rootTask.mRemoteToken.toWindowContainerToken(), false);
 
         // Verify now that the back press no longer calls the organizer
-        mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
+        mWm.mAtmService.mActivityClientController.onBackPressed(activity.token,
                 new IRequestFinishCallback.Default());
         // Ensure events dispatch to organizer.
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -1404,7 +1405,7 @@
         mWm.mWindowPlacerLocked.deferLayout();
 
         rootTask.removeImmediately();
-        mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(record.token,
+        mWm.mAtmService.mActivityClientController.onBackPressed(record.token,
                 new IRequestFinishCallback.Default());
         waitUntilHandlersIdle();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 3abf7ce..8bd4148 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -324,7 +324,7 @@
     @Test
     public void testComputeOomAdjFromActivities() {
         final ActivityRecord activity = createActivityRecord(mWpc);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         final int[] callbackResult = { 0 };
         final int visible = 1;
         final int paused = 2;
@@ -359,7 +359,7 @@
         assertEquals(visible, callbackResult[0]);
 
         callbackResult[0] = 0;
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setState(PAUSED, "test");
         mWpc.computeOomAdjFromActivities(callback);
         assertEquals(paused, callbackResult[0]);
@@ -380,7 +380,7 @@
         final VisibleActivityProcessTracker tracker = mAtm.mVisibleActivityProcessTracker;
         spyOn(tracker);
         final ActivityRecord activity = createActivityRecord(mWpc);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setState(STARTED, "test");
 
         verify(tracker).onAnyActivityVisible(mWpc);
@@ -398,7 +398,7 @@
         assertTrue(mWpc.hasForegroundActivities());
 
         activity.setVisibility(false);
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setState(STOPPED, "test");
 
         verify(tracker).onAllActivitiesInvisible(mWpc);
@@ -413,7 +413,7 @@
     @Test
     public void testTopActivityUiModeChangeScheduleConfigChange() {
         final ActivityRecord activity = createActivityRecord(mWpc);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any());
         mWpc.updateAppSpecificSettingsForAllActivitiesInPackage(DEFAULT_COMPONENT_PACKAGE_NAME,
                 Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
@@ -423,7 +423,7 @@
     @Test
     public void testTopActivityUiModeChangeForDifferentPackage_noScheduledConfigChange() {
         final ActivityRecord activity = createActivityRecord(mWpc);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         mWpc.updateAppSpecificSettingsForAllActivitiesInPackage("com.different.package",
                 Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
         verify(activity, never()).applyAppSpecificConfig(anyInt(), any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 0139f6a..69e3244 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -26,6 +26,7 @@
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
+import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -95,6 +96,8 @@
 import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentOrganizer;
 
 import androidx.test.filters.SmallTest;
 
@@ -261,7 +264,7 @@
 
         // Verify that app window can still be IME target as long as it is visible (even if
         // it is going to become invisible).
-        appWindow.mActivityRecord.mVisibleRequested = false;
+        appWindow.mActivityRecord.setVisibleRequested(false);
         assertTrue(appWindow.canBeImeTarget());
 
         // Make windows invisible
@@ -410,6 +413,16 @@
     }
 
     @Test
+    public void testCanAffectSystemUiFlags_starting() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION_STARTING, "app");
+        app.mActivityRecord.setVisible(true);
+        app.mStartingData = new SnapshotStartingData(mWm, null, 0);
+        assertFalse(app.canAffectSystemUiFlags());
+        app.mStartingData = new SplashScreenStartingData(mWm, 0, 0);
+        assertTrue(app.canAffectSystemUiFlags());
+    }
+
+    @Test
     public void testCanAffectSystemUiFlags_disallow() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         app.mActivityRecord.setVisible(true);
@@ -717,7 +730,7 @@
 
         // No need to wait for a window of invisible activity even if the window has surface.
         final WindowState invisibleApp = mAppWindow;
-        invisibleApp.mActivityRecord.mVisibleRequested = false;
+        invisibleApp.mActivityRecord.setVisibleRequested(false);
         invisibleApp.mActivityRecord.allDrawn = false;
         outWaitingForDrawn.clear();
         invisibleApp.requestDrawIfNeeded(outWaitingForDrawn);
@@ -735,7 +748,7 @@
         assertFalse(startingApp.getOrientationChanging());
 
         // Even if the display is frozen, invisible requested window should not be affected.
-        startingApp.mActivityRecord.mVisibleRequested = false;
+        startingApp.mActivityRecord.setVisibleRequested(false);
         mWm.startFreezingDisplay(0, 0, mDisplayContent);
         doReturn(true).when(mWm.mPolicy).isScreenOn();
         startingApp.getWindowFrames().setInsetsChanged(true);
@@ -800,9 +813,42 @@
     }
 
     @Test
+    public void testEmbeddedActivityResizing_clearAllDrawn() {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        mAtm.mTaskFragmentOrganizerController.registerOrganizer(
+                ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
+        final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION, embeddedActivity,
+                "App window");
+        doReturn(true).when(embeddedActivity).isVisible();
+        embeddedActivity.setVisibleRequested(true);
+        makeWindowVisible(win);
+        win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
+        // Set the bounds twice:
+        // 1. To make sure there is no orientation change after #reportResized, which can also cause
+        // #clearAllDrawn.
+        // 2. Make #isLastConfigReportedToClient to be false after #reportResized, so it can process
+        // to check if we need redraw.
+        embeddedTf.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        embeddedTf.setBounds(0, 0, 1000, 2000);
+        win.reportResized();
+        embeddedTf.setBounds(500, 0, 1000, 2000);
+
+        // Clear all drawn when the embedded TaskFragment is in mDisplayContent.mChangingContainers.
+        win.updateResizingWindowIfNeeded();
+        verify(embeddedActivity, never()).clearAllDrawn();
+
+        mDisplayContent.mChangingContainers.add(embeddedTf);
+        win.updateResizingWindowIfNeeded();
+        verify(embeddedActivity).clearAllDrawn();
+    }
+
+    @Test
     public void testCantReceiveTouchWhenAppTokenHiddenRequested() {
         final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
-        win0.mActivityRecord.mVisibleRequested = false;
+        win0.mActivityRecord.setVisibleRequested(false);
         assertFalse(win0.canReceiveTouchInput());
     }
 
@@ -999,8 +1045,9 @@
         // Simulate app requests IME with updating all windows Insets State when IME is above app.
         mDisplayContent.setImeLayeringTarget(app);
         mDisplayContent.setImeInputTarget(app);
+        app.setRequestedVisibleTypes(ime(), ime());
         assertTrue(mDisplayContent.shouldImeAttachedToApp());
-        controller.getImeSourceProvider().scheduleShowImePostLayout(app);
+        controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */);
         controller.getImeSourceProvider().getSource().setVisible(true);
         controller.updateAboveInsetsState(false);
 
@@ -1036,8 +1083,9 @@
         app2.mActivityRecord.mImeInsetsFrozenUntilStartInput = true;
         mDisplayContent.setImeLayeringTarget(app);
         mDisplayContent.setImeInputTarget(app);
+        app.setRequestedVisibleTypes(ime(), ime());
         assertTrue(mDisplayContent.shouldImeAttachedToApp());
-        controller.getImeSourceProvider().scheduleShowImePostLayout(app);
+        controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */);
         controller.getImeSourceProvider().getSource().setVisible(true);
         controller.updateAboveInsetsState(false);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index eca7cbb..019b14d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -100,6 +100,7 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManager.DisplayImePolicy;
+import android.view.inputmethod.ImeTracker;
 import android.window.ITransitionPlayer;
 import android.window.ScreenCapture;
 import android.window.StartingWindowInfo;
@@ -654,7 +655,7 @@
     Task createTaskInRootTask(Task rootTask, int userId) {
         final Task task = new TaskBuilder(rootTask.mTaskSupervisor)
                 .setUserId(userId)
-                .setParentTaskFragment(rootTask)
+                .setParentTask(rootTask)
                 .build();
         return task;
     }
@@ -737,31 +738,21 @@
         activity.onDisplayChanged(dc);
         activity.setOccludesParent(true);
         activity.setVisible(true);
-        activity.mVisibleRequested = true;
-    }
-
-    static TaskFragment createTaskFragmentWithParentTask(@NonNull Task parentTask) {
-        return createTaskFragmentWithParentTask(parentTask, false /* createEmbeddedTask */);
+        activity.setVisibleRequested(true);
     }
 
     /**
-     * Creates a {@link TaskFragment} and attach it to the {@code parentTask}.
+     * Creates a {@link TaskFragment} with {@link ActivityRecord} and attach it to the
+     * {@code parentTask}.
      *
      * @param parentTask the {@link Task} this TaskFragment is going to be attached
-     * @param createEmbeddedTask Sets to {@code true} to create an embedded Task for this
-     *                           TaskFragment. Otherwise, create a {@link ActivityRecord}.
      * @return the created TaskFragment
      */
-    static TaskFragment createTaskFragmentWithParentTask(@NonNull Task parentTask,
-            boolean createEmbeddedTask) {
-        final TaskFragmentBuilder builder = new TaskFragmentBuilder(parentTask.mAtmService)
-                .setParentTask(parentTask);
-        if (createEmbeddedTask) {
-            builder.createEmbeddedTask();
-        } else {
-            builder.createActivityCount(1);
-        }
-        return builder.build();
+    static TaskFragment createTaskFragmentWithActivity(@NonNull Task parentTask) {
+        return new TaskFragmentBuilder(parentTask.mAtmService)
+                .setParentTask(parentTask)
+                .createActivityCount(1)
+                .build();
     }
 
     static TaskFragment createTaskFragmentWithEmbeddedActivity(@NonNull Task parentTask,
@@ -848,11 +839,13 @@
             }
 
             @Override
-            public void showInsets(int i, boolean b) throws RemoteException {
+            public void showInsets(int i, boolean b, @Nullable ImeTracker.Token t)
+                    throws RemoteException {
             }
 
             @Override
-            public void hideInsets(int i, boolean b) throws RemoteException {
+            public void hideInsets(int i, boolean b, @Nullable ImeTracker.Token t)
+                    throws RemoteException {
             }
 
             @Override
@@ -1215,7 +1208,7 @@
                         // Apply the root activity info and intent
                         .setActivityInfo(aInfo)
                         .setIntent(intent)
-                        .setParentTaskFragment(mParentTask).build();
+                        .setParentTask(mParentTask).build();
             } else if (mTask == null && mParentTask != null && DisplayContent.alwaysCreateRootTask(
                     mParentTask.getWindowingMode(), mParentTask.getActivityType())) {
                 // The parent task can be the task root.
@@ -1247,7 +1240,7 @@
                     mTask.moveToFront("createActivity");
                 }
                 if (mVisible) {
-                    activity.mVisibleRequested = true;
+                    activity.setVisibleRequested(true);
                     activity.setVisible(true);
                 }
             }
@@ -1272,7 +1265,6 @@
         private final ActivityTaskManagerService mAtm;
         private Task mParentTask;
         private boolean mCreateParentTask;
-        private boolean mCreateEmbeddedTask;
         private int mCreateActivityCount = 0;
         @Nullable
         private TaskFragmentOrganizer mOrganizer;
@@ -1293,12 +1285,6 @@
             return this;
         }
 
-        /** Creates a child embedded Task and its Activity */
-        TaskFragmentBuilder createEmbeddedTask() {
-            mCreateEmbeddedTask = true;
-            return this;
-        }
-
         TaskFragmentBuilder createActivityCount(int count) {
             mCreateActivityCount = count;
             return this;
@@ -1330,12 +1316,6 @@
             if (mParentTask != null) {
                 mParentTask.addChild(taskFragment, POSITION_TOP);
             }
-            if (mCreateEmbeddedTask) {
-                new TaskBuilder(mAtm.mTaskSupervisor)
-                        .setParentTaskFragment(taskFragment)
-                        .setCreateActivity(true)
-                        .build();
-            }
             while (mCreateActivityCount > 0) {
                 final ActivityRecord activity = new ActivityBuilder(mAtm).build();
                 taskFragment.addChild(activity);
@@ -1374,7 +1354,7 @@
         private IVoiceInteractionSession mVoiceSession;
 
         private boolean mCreateParentTask = false;
-        private TaskFragment mParentTaskFragment;
+        private Task mParentTask;
 
         private boolean mCreateActivity = false;
         private boolean mCreatedByOrganizer = false;
@@ -1459,8 +1439,8 @@
             return this;
         }
 
-        TaskBuilder setParentTaskFragment(TaskFragment parentTaskFragment) {
-            mParentTaskFragment = parentTaskFragment;
+        TaskBuilder setParentTask(Task parentTask) {
+            mParentTask = parentTask;
             return this;
         }
 
@@ -1478,13 +1458,12 @@
             SystemServicesTestRule.checkHoldsLock(mSupervisor.mService.mGlobalLock);
 
             // Create parent task.
-            if (mParentTaskFragment == null && mCreateParentTask) {
-                mParentTaskFragment = mTaskDisplayArea.createRootTask(
+            if (mParentTask == null && mCreateParentTask) {
+                mParentTask = mTaskDisplayArea.createRootTask(
                         WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
             }
-            if (mParentTaskFragment != null
-                    && !Mockito.mockingDetails(mParentTaskFragment).isSpy()) {
-                spyOn(mParentTaskFragment);
+            if (mParentTask != null && !Mockito.mockingDetails(mParentTask).isSpy()) {
+                spyOn(mParentTask);
             }
 
             // Create task.
@@ -1512,15 +1491,13 @@
                     .setVoiceSession(mVoiceSession)
                     .setCreatedByOrganizer(mCreatedByOrganizer);
             final Task task;
-            if (mParentTaskFragment == null) {
+            if (mParentTask == null) {
                 task = builder.setActivityType(mActivityType)
                         .setParent(mTaskDisplayArea)
                         .build();
             } else {
-                task = builder.setParent(mParentTaskFragment).build();
-                if (mParentTaskFragment.asTask() != null) {
-                    mParentTaskFragment.asTask().moveToFront("build-task");
-                }
+                task = builder.setParent(mParentTask).build();
+                mParentTask.moveToFront("build-task");
             }
             spyOn(task);
             task.mUserId = mUserId;
@@ -1769,7 +1746,7 @@
         }
 
         void startTransition() {
-            mOrganizer.startTransition(mLastTransit, null);
+            mOrganizer.startTransition(mLastTransit.getToken(), null);
         }
 
         void onTransactionReady(SurfaceControl.Transaction t) {
@@ -1782,7 +1759,7 @@
         }
 
         public void finish() {
-            mController.finishTransition(mLastTransit);
+            mController.finishTransition(mLastTransit.getToken());
         }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java
new file mode 100644
index 0000000..e82a7c2
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.wm.utils;
+
+import static com.android.server.wm.utils.StateMachine.isIn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *  atest WmTests:StateMachineTest
+ */
+@SmallTest
+@Presubmit
+public class StateMachineTest {
+    static class LoggingHandler implements StateMachine.Handler {
+        final int mState;
+        final StringBuffer mStringBuffer;
+        // True if process #handle
+        final boolean mHandleSelf;
+
+        LoggingHandler(int state, StringBuffer sb, boolean handleSelf) {
+            mHandleSelf = handleSelf;
+            mState = state;
+            mStringBuffer = sb;
+        }
+
+        LoggingHandler(int state, StringBuffer sb) {
+            this(state, sb, true /* handleSelf */);
+        }
+
+        @Override
+        public void enter() {
+            mStringBuffer.append('i');
+            mStringBuffer.append(Integer.toHexString(mState));
+            mStringBuffer.append(';');
+        }
+
+        @Override
+        public void exit() {
+            mStringBuffer.append('o');
+            mStringBuffer.append(Integer.toHexString(mState));
+            mStringBuffer.append(';');
+        }
+
+        @Override
+        public boolean handle(int event, Object param) {
+            if (mHandleSelf) {
+                mStringBuffer.append('h');
+                mStringBuffer.append(Integer.toHexString(mState));
+                mStringBuffer.append(';');
+            }
+            return mHandleSelf;
+        }
+    }
+
+    static class LoggingHandlerTransferInExit extends LoggingHandler {
+        final StateMachine mStateMachine;
+        final int mStateToTransit;
+
+        LoggingHandlerTransferInExit(int state, StringBuffer sb, StateMachine stateMachine,
+                int stateToTransit) {
+            super(state, sb);
+            mStateMachine = stateMachine;
+            mStateToTransit = stateToTransit;
+        }
+
+        @Override
+        public void exit() {
+            super.exit();
+            mStateMachine.transit(mStateToTransit);
+        }
+    }
+
+    @Test
+    public void testStateMachineIsIn() {
+        assertTrue(isIn(0x112, 0x1));
+        assertTrue(isIn(0x112, 0x11));
+        assertTrue(isIn(0x112, 0x112));
+
+        assertFalse(isIn(0x1, 0x112));
+        assertFalse(isIn(0x12, 0x2));
+    }
+
+    @Test
+    public void testStateMachineInitialState() {
+        StateMachine stateMachine = new StateMachine();
+        assertEquals(0, stateMachine.getState());
+
+        stateMachine = new StateMachine(0x23);
+        assertEquals(0x23, stateMachine.getState());
+    }
+
+    @Test
+    public void testStateMachineTransitToChild() {
+        final StringBuffer log = new StringBuffer();
+
+        StateMachine stateMachine = new StateMachine();
+        stateMachine.addStateHandler(0x1, new LoggingHandler(0x1, log));
+        stateMachine.addStateHandler(0x12, new LoggingHandler(0x12, log));
+        stateMachine.addStateHandler(0x123, new LoggingHandler(0x123, log));
+        stateMachine.addStateHandler(0x1233, new LoggingHandler(0x1233, log));
+
+        // 0x0 -> 0x12
+        stateMachine.transit(0x12);
+        assertEquals("i1;i12;", log.toString());
+        assertEquals(0x12, stateMachine.getState());
+
+        // 0x12 -> 0x1233
+        log.setLength(0);
+        stateMachine.transit(0x1233);
+        assertEquals(0x1233, stateMachine.getState());
+        assertEquals("i123;i1233;", log.toString());
+    }
+
+    @Test
+    public void testStateMachineTransitToParent() {
+        final StringBuffer log = new StringBuffer();
+
+        StateMachine stateMachine = new StateMachine(0x253);
+        stateMachine.addStateHandler(0x2, new LoggingHandler(0x2, log));
+        stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+        stateMachine.addStateHandler(0x253, new LoggingHandler(0x253, log));
+
+        // 0x253 -> 0x2
+        stateMachine.transit(0x2);
+        assertEquals(0x2, stateMachine.getState());
+        assertEquals("o253;o25;", log.toString());
+    }
+
+    @Test
+    public void testStateMachineTransitSelf() {
+        final StringBuffer log = new StringBuffer();
+
+        StateMachine stateMachine = new StateMachine(0x253);
+        stateMachine.addStateHandler(0x2, new LoggingHandler(0x2, log));
+        stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+        stateMachine.addStateHandler(0x253, new LoggingHandler(0x253, log));
+
+        // 0x253 -> 0x253
+        stateMachine.transit(0x253);
+        assertEquals(0x253, stateMachine.getState());
+        assertEquals("o253;i253;", log.toString());
+    }
+
+    @Test
+    public void testStateMachineTransitGeneral() {
+        final StringBuffer log = new StringBuffer();
+
+        StateMachine stateMachine = new StateMachine(0x1351);
+        stateMachine.addStateHandler(0x1, new LoggingHandler(0x1, log));
+        stateMachine.addStateHandler(0x13, new LoggingHandler(0x13, log));
+        stateMachine.addStateHandler(0x132, new LoggingHandler(0x132, log));
+        stateMachine.addStateHandler(0x1322, new LoggingHandler(0x1322, log));
+        stateMachine.addStateHandler(0x1322, new LoggingHandler(0x1322, log));
+        stateMachine.addStateHandler(0x135, new LoggingHandler(0x135, log));
+        stateMachine.addStateHandler(0x1351, new LoggingHandler(0x1351, log));
+
+        // 0x1351 -> 0x1322
+        // least common ancestor = 0x13
+        stateMachine.transit(0x1322);
+        assertEquals(0x1322, stateMachine.getState());
+        assertEquals("o1351;o135;i132;i1322;", log.toString());
+    }
+
+    @Test
+    public void testStateMachineTriggerStateAction() {
+        final StringBuffer log = new StringBuffer();
+
+        StateMachine stateMachine = new StateMachine(0x253);
+        stateMachine.addStateHandler(0x2, new LoggingHandler(0x2, log));
+        stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+        stateMachine.addStateHandler(0x253, new LoggingHandler(0x253, log));
+
+        // state 0x253 handles the message itself
+        stateMachine.handle(0, null);
+        assertEquals("h253;", log.toString());
+    }
+
+    @Test
+    public void testStateMachineTriggerStateActionDelegate() {
+        final StringBuffer log = new StringBuffer();
+
+        StateMachine stateMachine = new StateMachine(0x253);
+        stateMachine.addStateHandler(0x2, new LoggingHandler(0x2, log));
+        stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+        stateMachine.addStateHandler(0x253,
+                new LoggingHandler(0x253, log, false /* handleSelf */));
+
+        // state 0x253 delegate the message handling to its parent state
+        stateMachine.handle(0, null);
+        assertEquals("h25;", log.toString());
+    }
+
+    @Test
+    public void testStateMachineNestedTransition() {
+        final StringBuffer log = new StringBuffer();
+
+        StateMachine stateMachine = new StateMachine(0x25);
+        stateMachine.addStateHandler(0x1, new LoggingHandler(0x1, log));
+
+        // Force transit to state 0x3 in exit()
+        stateMachine.addStateHandler(0x2,
+                new LoggingHandlerTransferInExit(0x2, log, stateMachine, 0x3));
+        stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+        stateMachine.addStateHandler(0x3, new LoggingHandler(0x3, log));
+
+        stateMachine.transit(0x1);
+        // Start transit to 0x1
+        //  0x25 -> 0x2 [transit(0x3) requested] -> 0x1
+        //  0x1 -> 0x3
+        // Immediately set the status to 0x1, no enter/exit
+        assertEquals("o25;o2;i1;o1;i3;", log.toString());
+    }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 7f5beb1..b3a1f2b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1864,6 +1864,21 @@
                         mResponseStatsTracker.dump(idpw);
                     }
                     return;
+                } else if ("app-component-usage".equals(arg)) {
+                    final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                    synchronized (mLock) {
+                        if (!mLastTimeComponentUsedGlobal.isEmpty()) {
+                            ipw.println("App Component Usages:");
+                            ipw.increaseIndent();
+                            for (String pkg : mLastTimeComponentUsedGlobal.keySet()) {
+                                ipw.println("package=" + pkg
+                                            + " lastUsed=" + UserUsageStatsService.formatDateTime(
+                                                    mLastTimeComponentUsedGlobal.get(pkg), true));
+                            }
+                            ipw.decreaseIndent();
+                        }
+                    }
+                    return;
                 } else if (arg != null && !arg.startsWith("-")) {
                     // Anything else that doesn't start with '-' is a pkg to filter
                     pkgs.add(arg);
@@ -2392,6 +2407,8 @@
         @Override
         public void setAppStandbyBucket(String packageName, int bucket, int userId) {
 
+            super.setAppStandbyBucket_enforcePermission();
+
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
             final long token = Binder.clearCallingIdentity();
@@ -2442,6 +2459,8 @@
         @Override
         public void setAppStandbyBuckets(ParceledListSlice appBuckets, int userId) {
 
+            super.setAppStandbyBuckets_enforcePermission();
+
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
             final long token = Binder.clearCallingIdentity();
@@ -2493,6 +2512,8 @@
         public void setEstimatedLaunchTime(String packageName, long estimatedLaunchTime,
                 int userId) {
 
+            super.setEstimatedLaunchTime_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
                 UsageStatsService.this
@@ -2506,6 +2527,8 @@
         @Override
         public void setEstimatedLaunchTimes(ParceledListSlice estimatedLaunchTimes, int userId) {
 
+            super.setEstimatedLaunchTimes_enforcePermission();
+
             final long token = Binder.clearCallingIdentity();
             try {
                 UsageStatsService.this
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 3b50fa4..133f924 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -29,9 +29,10 @@
         "android.hardware.usb-V1.1-java",
         "android.hardware.usb-V1.2-java",
         "android.hardware.usb-V1.3-java",
-	"android.hardware.usb-V1-java",
+        "android.hardware.usb-V2-java",
         "android.hardware.usb.gadget-V1.0-java",
         "android.hardware.usb.gadget-V1.1-java",
         "android.hardware.usb.gadget-V1.2-java",
+        "android.hardware.usb.gadget-V1-java",
     ],
 }
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..b6aed2db 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -44,6 +44,7 @@
 import android.debug.AdbNotifications;
 import android.debug.AdbTransportType;
 import android.debug.IAdbTransport;
+import android.hardware.usb.IUsbOperationInternal;
 import android.hardware.usb.ParcelableUsbPort;
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbConfiguration;
@@ -54,9 +55,7 @@
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
 import android.hardware.usb.gadget.V1_0.GadgetFunction;
-import android.hardware.usb.gadget.V1_0.IUsbGadget;
 import android.hardware.usb.gadget.V1_0.Status;
-import android.hardware.usb.gadget.V1_2.IUsbGadgetCallback;
 import android.hardware.usb.gadget.V1_2.UsbSpeed;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
@@ -88,9 +87,13 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.usb.hal.gadget.UsbGadgetHal;
+import com.android.server.usb.hal.gadget.UsbGadgetHalInstance;
+import com.android.server.utils.EventLogger;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import java.io.File;
@@ -105,6 +108,7 @@
 import java.util.NoSuchElementException;
 import java.util.Scanner;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * UsbDeviceManager manages USB state in device mode.
@@ -213,7 +217,14 @@
     private static Set<Integer> sDenyInterfaces;
     private HashMap<Long, FileDescriptor> mControlFds;
 
-    private static UsbDeviceLogger sEventLogger;
+    private static EventLogger sEventLogger;
+
+    private static UsbGadgetHal mUsbGadgetHal;
+
+    /**
+     * Counter for tracking UsbOperation operations.
+     */
+    private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
 
     static {
         sDenyInterfaces = new HashSet<>();
@@ -238,7 +249,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");
@@ -297,15 +308,11 @@
         mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
         initRndisAddress();
 
+        int operationId = sUsbOperationCount.incrementAndGet();
         boolean halNotPresent = false;
-        try {
-            IUsbGadget.getService(true);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "USB GADGET HAL present but exception thrown", e);
-        } catch (NoSuchElementException e) {
-            halNotPresent = true;
-            Slog.i(TAG, "USB GADGET HAL not present in the device", e);
-        }
+
+        mUsbGadgetHal = UsbGadgetHalInstance.getInstance(this, null);
+        Slog.d(TAG, "getInstance done");
 
         mControlFds = new HashMap<>();
         FileDescriptor mtpFd = nativeOpenControl(UsbManager.USB_FUNCTION_MTP);
@@ -319,7 +326,7 @@
         }
         mControlFds.put(UsbManager.FUNCTION_PTP, ptpFd);
 
-        if (halNotPresent) {
+        if (mUsbGadgetHal == null) {
             /**
              * Initialze the legacy UsbHandler
              */
@@ -333,6 +340,8 @@
                     alsaManager, permissionManager);
         }
 
+        mHandler.handlerInitDone(operationId);
+
         if (nativeIsStartRequested()) {
             if (DEBUG) Slog.d(TAG, "accessory attached at boot");
             startAccessoryMode();
@@ -395,7 +404,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() {
@@ -454,6 +463,8 @@
     private void startAccessoryMode() {
         if (!mHasUsbAccessory) return;
 
+        int operationId = sUsbOperationCount.incrementAndGet();
+
         mAccessoryStrings = nativeGetAccessoryStrings();
         boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE);
         // don't start accessory mode if our mandatory strings have not been set
@@ -474,7 +485,7 @@
                     ACCESSORY_REQUEST_TIMEOUT);
             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSORY_HANDSHAKE_TIMEOUT),
                     ACCESSORY_HANDSHAKE_TIMEOUT);
-            setCurrentFunctions(functions);
+            setCurrentFunctions(functions, operationId);
         }
     }
 
@@ -503,6 +514,20 @@
         }
     }
 
+    public static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
+        Slog.println(priority, TAG, msg);
+        if (pw != null) {
+            pw.println(msg);
+        }
+    }
+
+    public static void logAndPrintException(IndentingPrintWriter pw, String msg, Exception e) {
+        Slog.e(TAG, msg, e);
+        if (pw != null) {
+            pw.println(msg + e);
+        }
+    }
+
     abstract static class UsbHandler extends Handler {
 
         // current USB state
@@ -607,6 +632,19 @@
             sendMessage(m);
         }
 
+        public boolean sendMessage(int what) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            return sendMessageDelayed(m,0);
+        }
+
+        public void sendMessage(int what, int operationId) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.arg1 = operationId;
+            sendMessage(m);
+        }
+
         public void sendMessage(int what, Object arg) {
             removeMessages(what);
             Message m = Message.obtain(this, what);
@@ -614,6 +652,22 @@
             sendMessage(m);
         }
 
+        public void sendMessage(int what, Object arg, int operationId) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.obj = arg;
+            m.arg1 = operationId;
+            sendMessage(m);
+        }
+
+        public void sendMessage(int what, boolean arg, int operationId) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.arg1 = (arg ? 1 : 0);
+            m.arg2 = operationId;
+            sendMessage(m);
+        }
+
         public void sendMessage(int what, Object arg, boolean arg1) {
             removeMessages(what);
             Message m = Message.obtain(this, what);
@@ -622,6 +676,15 @@
             sendMessage(m);
         }
 
+        public void sendMessage(int what, long arg, boolean arg1, int operationId) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.obj = arg;
+            m.arg1 = (arg1 ? 1 : 0);
+            m.arg2 = operationId;
+            sendMessage(m);
+        }
+
         public void sendMessage(int what, boolean arg1, boolean arg2) {
             removeMessages(what);
             Message m = Message.obtain(this, what);
@@ -679,7 +742,7 @@
             sendMessageDelayed(msg, HOST_STATE_UPDATE_DELAY);
         }
 
-        private void setAdbEnabled(boolean enable) {
+        private void setAdbEnabled(boolean enable, int operationId) {
             if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable);
 
             if (enable) {
@@ -688,7 +751,7 @@
                 setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, "");
             }
 
-            setEnabledFunctions(mCurrentFunctions, true);
+            setEnabledFunctions(mCurrentFunctions, true, operationId);
             updateAdbNotification(false);
         }
 
@@ -700,6 +763,8 @@
         private void updateCurrentAccessory() {
             // We are entering accessory mode if we have received a request from the host
             // and the request has not timed out yet.
+            int operationId = sUsbOperationCount.incrementAndGet();
+
             boolean enteringAccessoryMode = hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT);
 
             if (mConfigured && enteringAccessoryMode) {
@@ -731,18 +796,18 @@
                 }
             } else {
                 if (!enteringAccessoryMode) {
-                    notifyAccessoryModeExit();
+                    notifyAccessoryModeExit(operationId);
                 } else if (DEBUG) {
                     Slog.v(TAG, "Debouncing accessory mode exit");
                 }
             }
         }
 
-        private void notifyAccessoryModeExit() {
+        private void notifyAccessoryModeExit(int operationId) {
             // make sure accessory mode is off
             // and restore default functions
             Slog.d(TAG, "exited USB accessory mode");
-            setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+            setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
 
             if (mCurrentAccessory != null) {
                 if (mBootCompleted) {
@@ -837,7 +902,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() {
@@ -868,8 +933,8 @@
                     mMidiEnabled && mConfigured, mMidiCard, mMidiDevice);
         }
 
-        private void setScreenUnlockedFunctions() {
-            setEnabledFunctions(mScreenUnlockedFunctions, false);
+        private void setScreenUnlockedFunctions(int operationId) {
+            setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
         }
 
         private static class AdbTransport extends IAdbTransport.Stub {
@@ -882,7 +947,8 @@
             @Override
             public void onAdbEnabled(boolean enabled, byte transportType) {
                 if (transportType == AdbTransportType.USB) {
-                    mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
+                    int operationId = sUsbOperationCount.incrementAndGet();
+                    mHandler.sendMessage(MSG_ENABLE_ADB, enabled, operationId);
                 }
             }
         }
@@ -905,6 +971,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_UPDATE_STATE:
+                    int operationId = sUsbOperationCount.incrementAndGet();
                     mConnected = (msg.arg1 == 1);
                     mConfigured = (msg.arg2 == 1);
 
@@ -922,9 +989,9 @@
                             // restore defaults when USB is disconnected
                             if (!mScreenLocked
                                     && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
-                                setScreenUnlockedFunctions();
+                                setScreenUnlockedFunctions(operationId);
                             } else {
-                                setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                                setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                             }
                         }
                         updateUsbFunctions();
@@ -1035,13 +1102,15 @@
                     updateUsbNotification(false);
                     break;
                 case MSG_ENABLE_ADB:
-                    setAdbEnabled(msg.arg1 == 1);
+                    setAdbEnabled(msg.arg1 == 1, msg.arg2);
                     break;
                 case MSG_SET_CURRENT_FUNCTIONS:
                     long functions = (Long) msg.obj;
-                    setEnabledFunctions(functions, false);
+                    operationId = (int) msg.arg1;
+                    setEnabledFunctions(functions, false, operationId);
                     break;
                 case MSG_SET_SCREEN_UNLOCKED_FUNCTIONS:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     mScreenUnlockedFunctions = (Long) msg.obj;
                     if (mSettings != null) {
                         SharedPreferences.Editor editor = mSettings.edit();
@@ -1052,12 +1121,13 @@
                     }
                     if (!mScreenLocked && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
                         // If the screen is unlocked, also set current functions.
-                        setScreenUnlockedFunctions();
+                        setScreenUnlockedFunctions(operationId);
                     } else {
-                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                     }
                     break;
                 case MSG_UPDATE_SCREEN_LOCK:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     if (msg.arg1 == 1 == mScreenLocked) {
                         break;
                     }
@@ -1067,23 +1137,25 @@
                     }
                     if (mScreenLocked) {
                         if (!mConnected) {
-                            setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                            setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                         }
                     } else {
                         if (mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE
                                 && mCurrentFunctions == UsbManager.FUNCTION_NONE) {
                             // Set the screen unlocked functions if current function is charging.
-                            setScreenUnlockedFunctions();
+                            setScreenUnlockedFunctions(operationId);
                         }
                     }
                     break;
                 case MSG_UPDATE_USER_RESTRICTIONS:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     // Restart the USB stack if USB transfer is enabled but no longer allowed.
                     if (isUsbDataTransferActive(mCurrentFunctions) && !isUsbTransferAllowed()) {
-                        setEnabledFunctions(UsbManager.FUNCTION_NONE, true);
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, true, operationId);
                     }
                     break;
                 case MSG_SYSTEM_READY:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     mNotificationManager = (NotificationManager)
                             mContext.getSystemService(Context.NOTIFICATION_SERVICE);
 
@@ -1101,17 +1173,19 @@
                                         NotificationManager.IMPORTANCE_HIGH));
                     }
                     mSystemReady = true;
-                    finishBoot();
+                    finishBoot(operationId);
                     break;
                 case MSG_LOCALE_CHANGED:
                     updateAdbNotification(true);
                     updateUsbNotification(true);
                     break;
                 case MSG_BOOT_COMPLETED:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     mBootCompleted = true;
-                    finishBoot();
+                    finishBoot(operationId);
                     break;
                 case MSG_USER_SWITCHED: {
+                    operationId = sUsbOperationCount.incrementAndGet();
                     if (mCurrentUser != msg.arg1) {
                         if (DEBUG) {
                             Slog.v(TAG, "Current user switched to " + msg.arg1);
@@ -1124,16 +1198,18 @@
                                     mSettings.getString(String.format(Locale.ENGLISH,
                                             UNLOCKED_CONFIG_PREF, mCurrentUser), ""));
                         }
-                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                     }
                     break;
                 }
                 case MSG_ACCESSORY_MODE_ENTER_TIMEOUT: {
+                    operationId = sUsbOperationCount.incrementAndGet();
                     if (DEBUG) {
-                        Slog.v(TAG, "Accessory mode enter timeout: " + mConnected);
+                        Slog.v(TAG, "Accessory mode enter timeout: " + mConnected
+                                    + " ,operationId: " + operationId);
                     }
                     if (!mConnected || (mCurrentFunctions & UsbManager.FUNCTION_ACCESSORY) == 0) {
-                        notifyAccessoryModeExit();
+                        notifyAccessoryModeExit(operationId);
                     }
                     break;
                 }
@@ -1156,7 +1232,9 @@
             }
         }
 
-        protected void finishBoot() {
+        public abstract void handlerInitDone(int operationId);
+
+        protected void finishBoot(int operationId) {
             if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) {
                 if (mPendingBootBroadcast) {
                     updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
@@ -1164,9 +1242,9 @@
                 }
                 if (!mScreenLocked
                         && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
-                    setScreenUnlockedFunctions();
+                    setScreenUnlockedFunctions(operationId);
                 } else {
-                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                 }
                 if (mCurrentAccessory != null) {
                     mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
@@ -1506,7 +1584,8 @@
         /**
          * Evaluates USB function policies and applies the change accordingly.
          */
-        protected abstract void setEnabledFunctions(long functions, boolean forceRestart);
+        protected abstract void setEnabledFunctions(long functions,
+                boolean forceRestart, int operationId);
 
         public void setAccessoryUEventTime(long accessoryConnectionStartTime) {
             mAccessoryConnectionStartTime = accessoryConnectionStartTime;
@@ -1521,6 +1600,11 @@
             mSendStringCount = 0;
             mStartAccessory = false;
         }
+
+        public abstract void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions);
+
+        public abstract void getUsbSpeedCb(int speed);
     }
 
     private static final class UsbHandlerLegacy extends UsbHandler {
@@ -1539,6 +1623,11 @@
         private String mCurrentFunctionsStr;
         private boolean mUsbDataUnlocked;
 
+        /**
+         * Keeps track of the latest setCurrentUsbFunctions request number.
+         */
+        private int mCurrentRequest = 0;
+
         UsbHandlerLegacy(Looper looper, Context context, UsbDeviceManager deviceManager,
                 UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) {
             super(looper, context, deviceManager, alsaManager, permissionManager);
@@ -1572,6 +1661,10 @@
             }
         }
 
+        @Override
+        public void handlerInitDone(int operationId) {
+        }
+
         private void readOemUsbOverrideConfig(Context context) {
             String[] configList = context.getResources().getStringArray(
                     com.android.internal.R.array.config_oemUsbModeOverride);
@@ -1674,11 +1767,14 @@
         }
 
         @Override
-        protected void setEnabledFunctions(long usbFunctions, boolean forceRestart) {
+        protected void setEnabledFunctions(long usbFunctions,
+                boolean forceRestart, int operationId) {
             boolean usbDataUnlocked = isUsbDataTransferActive(usbFunctions);
             if (DEBUG) {
-                Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions + ", "
-                        + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked);
+                Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions +
+                        " ,forceRestart=" + forceRestart +
+                        " ,usbDataUnlocked=" + usbDataUnlocked +
+                        " ,operationId=" + operationId);
             }
 
             if (usbDataUnlocked != mUsbDataUnlocked) {
@@ -1774,7 +1870,6 @@
                     || !mCurrentFunctionsStr.equals(functions)
                     || !mCurrentFunctionsApplied
                     || forceRestart) {
-                Slog.i(TAG, "Setting USB config to " + functions);
                 mCurrentFunctionsStr = functions;
                 mCurrentOemFunctions = oemFunctions;
                 mCurrentFunctionsApplied = false;
@@ -1870,16 +1965,19 @@
             if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
             return true;
         }
+
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions){
+        }
+
+        @Override
+        public void getUsbSpeedCb(int speed){
+        }
     }
 
     private static final class UsbHandlerHal extends UsbHandler {
 
-        /**
-         * Proxy object for the usb gadget hal daemon.
-         */
-        @GuardedBy("mGadgetProxyLock")
-        private IUsbGadget mGadgetProxy;
-
         private final Object mGadgetProxyLock = new Object();
 
         /**
@@ -1925,33 +2023,20 @@
         UsbHandlerHal(Looper looper, Context context, UsbDeviceManager deviceManager,
                 UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) {
             super(looper, context, deviceManager, alsaManager, permissionManager);
+            int operationId = sUsbOperationCount.incrementAndGet();
             try {
-                ServiceNotification serviceNotification = new ServiceNotification();
-
-                boolean ret = IServiceManager.getService()
-                        .registerForNotifications(GADGET_HAL_FQ_NAME, "", serviceNotification);
-                if (!ret) {
-                    Slog.e(TAG, "Failed to register usb gadget service start notification");
-                    return;
-                }
 
                 synchronized (mGadgetProxyLock) {
-                    mGadgetProxy = IUsbGadget.getService(true);
-                    mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
-                            USB_GADGET_HAL_DEATH_COOKIE);
                     mCurrentFunctions = UsbManager.FUNCTION_NONE;
                     mCurrentUsbFunctionsRequested = true;
                     mUsbSpeed = UsbSpeed.UNKNOWN;
                     mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_0;
-                    mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
+                    updateUsbGadgetHalVersion();
                 }
                 String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
                 updateState(state);
-                updateUsbGadgetHalVersion();
             } catch (NoSuchElementException e) {
                 Slog.e(TAG, "Usb gadget hal not found", e);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Usb Gadget hal not responding", e);
             } catch (Exception e) {
                 Slog.e(TAG, "Error initializing UsbHandler", e);
             }
@@ -1964,7 +2049,7 @@
                 if (cookie == USB_GADGET_HAL_DEATH_COOKIE) {
                     Slog.e(TAG, "Usb Gadget hal service died cookie: " + cookie);
                     synchronized (mGadgetProxyLock) {
-                        mGadgetProxy = null;
+                        mUsbGadgetHal = null;
                     }
                 }
             }
@@ -1987,18 +2072,22 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_SET_CHARGING_FUNCTIONS:
-                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+                    int operationId = sUsbOperationCount.incrementAndGet();
+                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                     break;
                 case MSG_SET_FUNCTIONS_TIMEOUT:
-                    Slog.e(TAG, "Set functions timed out! no reply from usb hal");
+                    operationId = sUsbOperationCount.incrementAndGet();
+                    Slog.e(TAG, "Set functions timed out! no reply from usb hal"
+                                + " ,operationId:" + operationId);
                     if (msg.arg1 != 1) {
                         // Set this since default function may be selected from Developer options
-                        setEnabledFunctions(mScreenUnlockedFunctions, false);
+                        setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
                     }
                     break;
                 case MSG_GET_CURRENT_USB_FUNCTIONS:
                     Slog.i(TAG, "processing MSG_GET_CURRENT_USB_FUNCTIONS");
                     mCurrentUsbFunctionsReceived = true;
+                    operationId = msg.arg2;
 
                     if (mCurrentUsbFunctionsRequested) {
                         Slog.i(TAG, "updating mCurrentFunctions");
@@ -2008,91 +2097,71 @@
                                 "mCurrentFunctions:" + mCurrentFunctions + "applied:" + msg.arg1);
                         mCurrentFunctionsApplied = msg.arg1 == 1;
                     }
-                    finishBoot();
+                    finishBoot(operationId);
                     break;
                 case MSG_FUNCTION_SWITCH_TIMEOUT:
                     /**
                      * Dont force to default when the configuration is already set to default.
                      */
+                    operationId = sUsbOperationCount.incrementAndGet();
                     if (msg.arg1 != 1) {
                         // Set this since default function may be selected from Developer options
-                        setEnabledFunctions(mScreenUnlockedFunctions, false);
+                        setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
                     }
                     break;
                 case MSG_GADGET_HAL_REGISTERED:
                     boolean preexisting = msg.arg1 == 1;
+                    operationId = sUsbOperationCount.incrementAndGet();
                     synchronized (mGadgetProxyLock) {
                         try {
-                            mGadgetProxy = IUsbGadget.getService();
-                            mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
-                                    USB_GADGET_HAL_DEATH_COOKIE);
+                            mUsbGadgetHal = UsbGadgetHalInstance.getInstance(mUsbDeviceManager,
+                                    null);
                             if (!mCurrentFunctionsApplied && !preexisting) {
-                                setEnabledFunctions(mCurrentFunctions, false);
+                                setEnabledFunctions(mCurrentFunctions, false, operationId);
                             }
                         } catch (NoSuchElementException e) {
                             Slog.e(TAG, "Usb gadget hal not found", e);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "Usb Gadget hal not responding", e);
                         }
                     }
                     break;
                 case MSG_RESET_USB_GADGET:
                     synchronized (mGadgetProxyLock) {
-                        if (mGadgetProxy == null) {
-                            Slog.e(TAG, "reset Usb Gadget mGadgetProxy is null");
+                        if (mUsbGadgetHal == null) {
+                            Slog.e(TAG, "reset Usb Gadget mUsbGadgetHal is null");
                             break;
                         }
 
                         try {
-                            android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxy =
-                                    android.hardware.usb.gadget.V1_1.IUsbGadget
-                                            .castFrom(mGadgetProxy);
-                            gadgetProxy.reset();
-                        } catch (RemoteException e) {
+                            mUsbGadgetHal.reset();
+                        } catch (Exception e) {
                             Slog.e(TAG, "reset Usb Gadget failed", e);
                         }
                     }
                     break;
                 case MSG_UPDATE_USB_SPEED:
-                    synchronized (mGadgetProxyLock) {
-                        if (mGadgetProxy == null) {
-                            Slog.e(TAG, "mGadgetProxy is null");
-                            break;
-                        }
+                    operationId = sUsbOperationCount.incrementAndGet();
+                    if (mUsbGadgetHal == null) {
+                        Slog.e(TAG, "mGadgetHal is null, operationId:" + operationId);
+                        break;
+                    }
 
-                        try {
-                            android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
-                                    android.hardware.usb.gadget.V1_2.IUsbGadget
-                                            .castFrom(mGadgetProxy);
-                            if (gadgetProxy != null) {
-                                gadgetProxy.getUsbSpeed(new UsbGadgetCallback());
-                            }
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "get UsbSpeed failed", e);
-                        }
+                    try {
+                        mUsbGadgetHal.getUsbSpeed(operationId);
+                    } catch (Exception e) {
+                        Slog.e(TAG, "get UsbSpeed failed", e);
                     }
                     break;
                 case MSG_UPDATE_HAL_VERSION:
-                    synchronized (mGadgetProxyLock) {
-                        if (mGadgetProxy == null) {
-                            Slog.e(TAG, "mGadgetProxy is null");
-                            break;
+                    if (mUsbGadgetHal == null) {
+                        Slog.e(TAG, "mUsbGadgetHal is null");
+                        break;
+                    }
+                    else {
+                        try {
+                            mCurrentGadgetHalVersion = mUsbGadgetHal.getGadgetHalVersion();
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "update Usb gadget version failed", e);
                         }
-
-                        android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
-                                android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
-                        if (gadgetProxy == null) {
-                            android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxyV1By1 =
-                                    android.hardware.usb.gadget.V1_1.IUsbGadget
-                                            .castFrom(mGadgetProxy);
-                            if (gadgetProxyV1By1 == null) {
-                                mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_0;
-                                break;
-                            }
-                            mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_1;
-                            break;
-                        }
-                        mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_2;
                     }
                     break;
                 default:
@@ -2100,56 +2169,31 @@
             }
         }
 
-        private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
-            int mRequest;
-            long mFunctions;
-            boolean mChargingFunctions;
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions) {
 
-            UsbGadgetCallback() {
+            if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT)
+                  || (mFunctions != functions)) {
+                return;
             }
 
-            UsbGadgetCallback(int request, long functions,
-                    boolean chargingFunctions) {
-                mRequest = request;
-                mFunctions = functions;
-                mChargingFunctions = chargingFunctions;
-            }
-
-            @Override
-            public void setCurrentUsbFunctionsCb(long functions,
-                    int status) {
-                /**
-                 * Callback called for a previous setCurrenUsbFunction
-                 */
-                if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT)
-                        || (mFunctions != functions)) {
-                    return;
-                }
-
-                removeMessages(MSG_SET_FUNCTIONS_TIMEOUT);
-                Slog.e(TAG, "notifyCurrentFunction request:" + mRequest + " status:" + status);
-                if (status == Status.SUCCESS) {
-                    mCurrentFunctionsApplied = true;
-                } else if (!mChargingFunctions) {
-                    Slog.e(TAG, "Setting default fuctions");
-                    sendEmptyMessage(MSG_SET_CHARGING_FUNCTIONS);
-                }
-            }
-
-            @Override
-            public void getCurrentUsbFunctionsCb(long functions,
-                    int status) {
-                sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions,
-                        status == Status.FUNCTIONS_APPLIED);
-            }
-
-            @Override
-            public void getUsbSpeedCb(int speed) {
-                mUsbSpeed = speed;
+            removeMessages(MSG_SET_FUNCTIONS_TIMEOUT);
+            Slog.i(TAG, "notifyCurrentFunction request:" + mRequest + " status:" + status);
+            if (status == Status.SUCCESS) {
+                mCurrentFunctionsApplied = true;
+            } else if (!mChargingFunctions) {
+                Slog.e(TAG, "Setting default fuctions");
+                sendEmptyMessage(MSG_SET_CHARGING_FUNCTIONS);
             }
         }
 
-        private void setUsbConfig(long config, boolean chargingFunctions) {
+        @Override
+        public void getUsbSpeedCb(int speed) {
+            mUsbSpeed = speed;
+        }
+
+        private void setUsbConfig(long config, boolean chargingFunctions, int operationId) {
             if (true) Slog.d(TAG, "setUsbConfig(" + config + ") request:" + ++mCurrentRequest);
             /**
              * Cancel any ongoing requests, if present.
@@ -2159,8 +2203,8 @@
             removeMessages(MSG_SET_CHARGING_FUNCTIONS);
 
             synchronized (mGadgetProxyLock) {
-                if (mGadgetProxy == null) {
-                    Slog.e(TAG, "setUsbConfig mGadgetProxy is null");
+                if (mUsbGadgetHal == null) {
+                    Slog.e(TAG, "setUsbConfig mUsbGadgetHal is null");
                     return;
                 }
                 try {
@@ -2177,10 +2221,9 @@
                         LocalServices.getService(AdbManagerInternal.class)
                                 .stopAdbdForTransport(AdbTransportType.USB);
                     }
-                    UsbGadgetCallback usbGadgetCallback = new UsbGadgetCallback(mCurrentRequest,
-                            config, chargingFunctions);
-                    mGadgetProxy.setCurrentUsbFunctions(config, usbGadgetCallback,
-                            SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS);
+                    mUsbGadgetHal.setCurrentUsbFunctions(mCurrentRequest,
+                            config, chargingFunctions,
+                            SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS, operationId);
                     sendMessageDelayed(MSG_SET_FUNCTIONS_TIMEOUT, chargingFunctions,
                             SET_FUNCTIONS_TIMEOUT_MS);
                     if (mConnected) {
@@ -2189,17 +2232,19 @@
                                 SET_FUNCTIONS_TIMEOUT_MS + ENUMERATION_TIME_OUT_MS);
                     }
                     if (DEBUG) Slog.d(TAG, "timeout message queued");
-                } catch (RemoteException e) {
+                } catch (Exception e) {//RemoteException e) {
                     Slog.e(TAG, "Remoteexception while calling setCurrentUsbFunctions", e);
                 }
             }
         }
 
         @Override
-        protected void setEnabledFunctions(long functions, boolean forceRestart) {
+        protected void setEnabledFunctions(long functions, boolean forceRestart, int operationId) {
             if (DEBUG) {
-                Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
-                        + "forceRestart=" + forceRestart);
+                Slog.d(TAG, "setEnabledFunctionsi " +
+                        "functions=" + functions +
+                        ", forceRestart=" + forceRestart +
+                        ", operationId=" + operationId);
             }
             if (mCurrentGadgetHalVersion < UsbManager.GADGET_HAL_V1_2) {
                 if ((functions & UsbManager.FUNCTION_NCM) != 0) {
@@ -2220,7 +2265,7 @@
                 functions = getAppliedFunctions(functions);
 
                 // Set the new USB configuration.
-                setUsbConfig(functions, chargingFunctions);
+                setUsbConfig(functions, chargingFunctions, operationId);
 
                 if (mBootCompleted && isUsbDataTransferActive(functions)) {
                     // Start up dependent services.
@@ -2228,6 +2273,11 @@
                 }
             }
         }
+
+        @Override
+        public void handlerInitDone(int operationId) {
+            mUsbGadgetHal.getCurrentUsbFunctions(operationId);
+        }
     }
 
     /* returns the currently attached USB accessory */
@@ -2269,6 +2319,21 @@
         return mHandler.getGadgetHalVersion();
     }
 
+    public void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions) {
+        mHandler.setCurrentUsbFunctionsCb(functions, status,
+                    mRequest, mFunctions, mChargingFunctions);
+    }
+
+    public void getCurrentUsbFunctionsCb(long functions, int status) {
+        mHandler.sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions,
+                    status == Status.FUNCTIONS_APPLIED);
+    }
+
+    public void getUsbSpeedCb(int speed) {
+        mHandler.getUsbSpeedCb(speed);
+    }
+
     /**
      * Returns a dup of the control file descriptor for the given function.
      */
@@ -2294,7 +2359,7 @@
      *
      * @param functions The functions to set, or empty to set the charging function.
      */
-    public void setCurrentFunctions(long functions) {
+    public void setCurrentFunctions(long functions, int operationId) {
         if (DEBUG) {
             Slog.d(TAG, "setCurrentFunctions(" + UsbManager.usbFunctionsToString(functions) + ")");
         }
@@ -2311,7 +2376,7 @@
         } else if (functions == UsbManager.FUNCTION_ACCESSORY) {
             MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_ACCESSORY);
         }
-        mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions);
+        mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, operationId);
     }
 
     /**
@@ -2339,7 +2404,8 @@
     }
 
     private void onAdbEnabled(boolean enabled) {
-        mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
+        int operationId = sUsbOperationCount.incrementAndGet();
+        mHandler.sendMessage(MSG_ENABLE_ADB, enabled, operationId);
     }
 
     /**
@@ -2350,7 +2416,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/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index f8df6c6..4bb9de5 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -73,6 +73,7 @@
 import android.service.usb.UsbPortInfoProto;
 import android.service.usb.UsbPortManagerProto;
 import android.util.ArrayMap;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Slog;
 
@@ -87,6 +88,7 @@
 import com.android.server.usb.hal.port.UsbPortHal;
 import com.android.server.usb.hal.port.UsbPortHalInstance;
 
+import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.NoSuchElementException;
 import java.util.Objects;
@@ -754,6 +756,31 @@
         }
     }
 
+    /**
+     * Sets Compliance Warnings for simulated USB port objects.
+     */
+    public void simulateComplianceWarnings(String portId, String complianceWarningsString,
+            IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            final RawPortInfo portInfo = mSimulatedPorts.get(portId);
+            if (portInfo == null) {
+                pw.println("Simulated port not found");
+                return;
+            }
+
+            IntArray complianceWarnings = new IntArray();
+            for (String s : complianceWarningsString.split("[, ]")) {
+                if (s.length() > 0) {
+                    complianceWarnings.add(Integer.parseInt(s));
+                }
+            }
+            pw.println("Simulating Compliance Warnings: portId=" + portId
+                    + " Warnings=" + complianceWarningsString);
+            portInfo.complianceWarnings = complianceWarnings.toArray();
+            updatePortsLocked(pw, null);
+        }
+    }
+
     public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) {
         synchronized (mLock) {
             final RawPortInfo portInfo = mSimulatedPorts.get(portId);
@@ -842,7 +869,10 @@
                         portInfo.contaminantDetectionStatus,
                         portInfo.usbDataStatus,
                         portInfo.powerTransferLimited,
-                        portInfo.powerBrickConnectionStatus, pw);
+                        portInfo.powerBrickConnectionStatus,
+                        portInfo.supportsComplianceWarnings,
+                        portInfo.complianceWarnings,
+                        pw);
             }
         } else {
             for (RawPortInfo currentPortInfo : newPortInfo) {
@@ -857,7 +887,10 @@
                         currentPortInfo.contaminantDetectionStatus,
                         currentPortInfo.usbDataStatus,
                         currentPortInfo.powerTransferLimited,
-                        currentPortInfo.powerBrickConnectionStatus, pw);
+                        currentPortInfo.powerBrickConnectionStatus,
+                        currentPortInfo.supportsComplianceWarnings,
+                        currentPortInfo.complianceWarnings,
+                        pw);
             }
         }
 
@@ -880,6 +913,9 @@
                     handlePortRemovedLocked(portInfo, pw);
                     break;
             }
+            if (portInfo.mComplianceWarningChange == portInfo.COMPLIANCE_WARNING_CHANGED) {
+                handlePortComplianceWarningLocked(portInfo, pw);
+            }
         }
     }
 
@@ -896,6 +932,8 @@
             int usbDataStatus,
             boolean powerTransferLimited,
             int powerBrickConnectionStatus,
+            boolean supportsComplianceWarnings,
+            @NonNull int[] complianceWarnings,
             IndentingPrintWriter pw) {
         // Only allow mode switch capability for dual role ports.
         // Validate that the current mode matches the supported modes we expect.
@@ -949,13 +987,15 @@
             portInfo = new PortInfo(mContext.getSystemService(UsbManager.class),
                 portId, supportedModes, supportedContaminantProtectionModes,
                 supportsEnableContaminantPresenceProtection,
-                supportsEnableContaminantPresenceDetection);
+                supportsEnableContaminantPresenceDetection,
+                supportsComplianceWarnings);
             portInfo.setStatus(currentMode, canChangeMode,
                     currentPowerRole, canChangePowerRole,
                     currentDataRole, canChangeDataRole,
                     supportedRoleCombinations, contaminantProtectionStatus,
                     contaminantDetectionStatus, usbDataStatus,
-                    powerTransferLimited, powerBrickConnectionStatus);
+                    powerTransferLimited, powerBrickConnectionStatus,
+                    complianceWarnings);
             mPorts.put(portId, portInfo);
         } else {
             // Validate that ports aren't changing definition out from under us.
@@ -987,13 +1027,13 @@
                         + ", current=" + supportsEnableContaminantPresenceDetection);
             }
 
-
             if (portInfo.setStatus(currentMode, canChangeMode,
                     currentPowerRole, canChangePowerRole,
                     currentDataRole, canChangeDataRole,
                     supportedRoleCombinations, contaminantProtectionStatus,
                     contaminantDetectionStatus, usbDataStatus,
-                    powerTransferLimited, powerBrickConnectionStatus)) {
+                    powerTransferLimited, powerBrickConnectionStatus,
+                    complianceWarnings)) {
                 portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED;
             } else {
                 portInfo.mDisposition = PortInfo.DISPOSITION_READY;
@@ -1019,6 +1059,11 @@
         handlePortLocked(portInfo, pw);
     }
 
+    private void handlePortComplianceWarningLocked(PortInfo portInfo, IndentingPrintWriter pw) {
+        logAndPrint(Log.INFO, pw, "USB port compliance warning changed: " + portInfo);
+        sendComplianceWarningBroadcastLocked(portInfo);
+    }
+
     private void handlePortRemovedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
         logAndPrint(Log.INFO, pw, "USB port removed: " + portInfo);
         handlePortLocked(portInfo, pw);
@@ -1056,6 +1101,23 @@
                 Manifest.permission.MANAGE_USB));
     }
 
+    private void sendComplianceWarningBroadcastLocked(PortInfo portInfo) {
+        if (portInfo.mComplianceWarningChange == portInfo.COMPLIANCE_WARNING_UNCHANGED) {
+            return;
+        }
+        final Intent intent = new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
+        intent.addFlags(
+                Intent.FLAG_RECEIVER_FOREGROUND |
+                        Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(portInfo.mUsbPort));
+        intent.putExtra(UsbManager.EXTRA_PORT_STATUS, portInfo.mUsbPortStatus);
+
+        // Guard against possible reentrance by posting the broadcast from the handler
+        // instead of from within the critical section.
+        mHandler.post(() -> mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                Manifest.permission.MANAGE_USB));
+    }
+
     private void enableContaminantDetectionIfNeeded(PortInfo portInfo, IndentingPrintWriter pw) {
         if (!mConnected.containsKey(portInfo.mUsbPort.getId())) {
             return;
@@ -1180,6 +1242,9 @@
         public static final int DISPOSITION_READY = 2;
         public static final int DISPOSITION_REMOVED = 3;
 
+        public static final int COMPLIANCE_WARNING_UNCHANGED = 0;
+        public static final int COMPLIANCE_WARNING_CHANGED = 1;
+
         public final UsbPort mUsbPort;
         public UsbPortStatus mUsbPortStatus;
         public boolean mCanChangeMode;
@@ -1191,15 +1256,29 @@
         public long mConnectedAtMillis;
         // 0 when port is connected. Else reports the last connected duration
         public long mLastConnectDurationMillis;
+        // default initialized to 0 which means no changes reported
+        public int mComplianceWarningChange;
 
         PortInfo(@NonNull UsbManager usbManager, @NonNull String portId, int supportedModes,
                 int supportedContaminantProtectionModes,
                 boolean supportsEnableContaminantPresenceDetection,
-                boolean supportsEnableContaminantPresenceProtection) {
+                boolean supportsEnableContaminantPresenceProtection,
+                boolean supportsComplianceWarnings) {
             mUsbPort = new UsbPort(usbManager, portId, supportedModes,
                     supportedContaminantProtectionModes,
                     supportsEnableContaminantPresenceDetection,
-                    supportsEnableContaminantPresenceProtection);
+                    supportsEnableContaminantPresenceProtection,
+                    supportsComplianceWarnings);
+            mComplianceWarningChange = COMPLIANCE_WARNING_UNCHANGED;
+        }
+
+        public boolean complianceWarningsChanged(@NonNull int[] complianceWarnings) {
+            if (Arrays.equals(complianceWarnings, mUsbPortStatus.getComplianceWarnings())) {
+                mComplianceWarningChange = COMPLIANCE_WARNING_UNCHANGED;
+                return false;
+            }
+            mComplianceWarningChange = COMPLIANCE_WARNING_CHANGED;
+            return true;
         }
 
         public boolean setStatus(int currentMode, boolean canChangeMode,
@@ -1221,7 +1300,8 @@
                         supportedRoleCombinations, UsbPortStatus.CONTAMINANT_PROTECTION_NONE,
                         UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED,
                         UsbPortStatus.DATA_STATUS_UNKNOWN, false,
-                        UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN);
+                        UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN,
+                        new int[] {});
                 dispositionChanged = true;
             }
 
@@ -1266,7 +1346,8 @@
                 mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
                         supportedRoleCombinations, contaminantProtectionStatus,
                         contaminantDetectionStatus, usbDataStatus,
-                        powerTransferLimited, powerBrickConnectionStatus);
+                        powerTransferLimited, powerBrickConnectionStatus,
+                        new int[] {});
                 dispositionChanged = true;
             }
 
@@ -1281,6 +1362,62 @@
             return dispositionChanged;
         }
 
+        public boolean setStatus(int currentMode, boolean canChangeMode,
+                int currentPowerRole, boolean canChangePowerRole,
+                int currentDataRole, boolean canChangeDataRole,
+                int supportedRoleCombinations, int contaminantProtectionStatus,
+                int contaminantDetectionStatus, int usbDataStatus,
+                boolean powerTransferLimited, int powerBrickConnectionStatus,
+                @NonNull int[] complianceWarnings) {
+            boolean dispositionChanged = false;
+
+            mCanChangeMode = canChangeMode;
+            mCanChangePowerRole = canChangePowerRole;
+            mCanChangeDataRole = canChangeDataRole;
+            if (mUsbPortStatus == null
+                    || mUsbPortStatus.getCurrentMode() != currentMode
+                    || mUsbPortStatus.getCurrentPowerRole() != currentPowerRole
+                    || mUsbPortStatus.getCurrentDataRole() != currentDataRole
+                    || mUsbPortStatus.getSupportedRoleCombinations()
+                    != supportedRoleCombinations
+                    || mUsbPortStatus.getContaminantProtectionStatus()
+                    != contaminantProtectionStatus
+                    || mUsbPortStatus.getContaminantDetectionStatus()
+                    != contaminantDetectionStatus
+                    || mUsbPortStatus.getUsbDataStatus()
+                    != usbDataStatus
+                    || mUsbPortStatus.isPowerTransferLimited()
+                    != powerTransferLimited
+                    || mUsbPortStatus.getPowerBrickConnectionStatus()
+                    != powerBrickConnectionStatus) {
+                if (mUsbPortStatus == null && complianceWarnings.length > 0) {
+                    mComplianceWarningChange = COMPLIANCE_WARNING_CHANGED;
+                }
+                mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
+                        supportedRoleCombinations, contaminantProtectionStatus,
+                        contaminantDetectionStatus, usbDataStatus,
+                        powerTransferLimited, powerBrickConnectionStatus,
+                        complianceWarnings);
+                dispositionChanged = true;
+            } else if (complianceWarningsChanged(complianceWarnings)) {
+                mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
+                        supportedRoleCombinations, contaminantProtectionStatus,
+                        contaminantDetectionStatus, usbDataStatus,
+                        powerTransferLimited, powerBrickConnectionStatus,
+                        complianceWarnings);
+            }
+
+            if (mUsbPortStatus.isConnected() && mConnectedAtMillis == 0) {
+                mConnectedAtMillis = SystemClock.elapsedRealtime();
+                mLastConnectDurationMillis = 0;
+            } else if (!mUsbPortStatus.isConnected() && mConnectedAtMillis != 0) {
+                mLastConnectDurationMillis = SystemClock.elapsedRealtime() - mConnectedAtMillis;
+                mConnectedAtMillis = 0;
+            }
+
+            return dispositionChanged;
+        }
+
         void dump(@NonNull DualDumpOutputStream dump, @NonNull String idName, long id) {
             long token = dump.start(idName, id);
 
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/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 86f877f..d09f729 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -504,6 +504,15 @@
     }
 
     @Override
+    public boolean hasDevicePermissionWithIdentity(UsbDevice device, String packageName,
+            int pid, int uid) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final int userId = UserHandle.getUserId(uid);
+        return getPermissionsForUser(userId).hasPermission(device, packageName, pid, uid);
+    }
+
+    @Override
     public boolean hasAccessoryPermission(UsbAccessory accessory) {
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
@@ -518,6 +527,14 @@
     }
 
     @Override
+    public boolean hasAccessoryPermissionWithIdentity(UsbAccessory accessory, int pid, int uid) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final int userId = UserHandle.getUserId(uid);
+        return getPermissionsForUser(userId).hasPermission(accessory, pid, uid);
+    }
+
+    @Override
     public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
@@ -605,16 +622,16 @@
     }
 
     @Override
-    public void setCurrentFunctions(long functions) {
+    public void setCurrentFunctions(long functions, int operationId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
         Preconditions.checkArgument(UsbManager.areSettableFunctions(functions));
         Preconditions.checkState(mDeviceManager != null);
-        mDeviceManager.setCurrentFunctions(functions);
+        mDeviceManager.setCurrentFunctions(functions, operationId);
     }
 
     @Override
-    public void setCurrentFunction(String functions, boolean usbDataUnlocked) {
-        setCurrentFunctions(UsbManager.usbFunctionsFromString(functions));
+    public void setCurrentFunction(String functions, boolean usbDataUnlocked, int operationId) {
+        setCurrentFunctions(UsbManager.usbFunctionsFromString(functions), operationId);
     }
 
     @Override
@@ -1076,6 +1093,23 @@
                     mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
                             "", 0);
                 }
+            } else if ("set-compliance-reasons".equals(args[0]) && args.length == 3) {
+                final String portId = args[1];
+                final String complianceWarnings = args[2];
+                if (mPortManager != null) {
+                    mPortManager.simulateComplianceWarnings(portId, complianceWarnings, pw);
+                    pw.println();
+                    mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
+                            "", 0);
+                }
+            } else if ("clear-compliance-reasons".equals(args[0]) && args.length == 2) {
+                final String portId = args[1];
+                if (mPortManager != null) {
+                    mPortManager.simulateComplianceWarnings(portId, "", pw);
+                    pw.println();
+                    mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
+                            "", 0);
+                }
             } else if ("ports".equals(args[0]) && args.length == 1) {
                 if (mPortManager != null) {
                     mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),
@@ -1125,6 +1159,17 @@
                 pw.println("  dumpsys usb set-contaminant-status \"matrix\" true");
                 pw.println("  dumpsys usb set-contaminant-status \"matrix\" false");
                 pw.println();
+                pw.println("Example simulate compliance warnings:");
+                pw.println("  dumpsys usb add-port \"matrix\" dual");
+                pw.println("  dumpsys usb set-compliance-reasons \"matrix\" <reason-list>");
+                pw.println("  dumpsys usb clear-compliance-reasons \"matrix\"");
+                pw.println("<reason-list> is expected to be formatted as \"1, ..., 4\"");
+                pw.println("with reasons that need to be simulated.");
+                pw.println("  1: debug accessory");
+                pw.println("  2: bc12");
+                pw.println("  3: missing rp");
+                pw.println("  4: type c");
+                pw.println();
                 pw.println("Example USB device descriptors:");
                 pw.println("  dumpsys usb dump-descriptors -dump-short");
                 pw.println("  dumpsys usb dump-descriptors -dump-tree");
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java
index df63795..7a41b50 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java
@@ -46,4 +46,4 @@
         // TODO Add reporting specific to this descriptor
         super.report(canvas);
     }
-};
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java
index 4aa8ca2..32275a6 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java
@@ -46,4 +46,4 @@
         super.report(canvas);
         // TODO Add reporting specific to this descriptor
     }
-};
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java
index 5ce842e..0692066 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java
@@ -47,4 +47,4 @@
         super.report(canvas);
         // TODO Add reporting specific to this descriptor
     }
-};
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java
index 8e9b0d8..604dd66 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java
@@ -47,4 +47,4 @@
         super.report(canvas);
         // TODO Add reporting specific to this descriptor
     }
-};
+}
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
new file mode 100644
index 0000000..bdfe60a
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
@@ -0,0 +1,220 @@
+/*
+ * 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.hal.gadget;
+
+import static android.hardware.usb.UsbManager.GADGET_HAL_V2_0;
+
+import static com.android.server.usb.UsbDeviceManager.logAndPrint;
+import static com.android.server.usb.UsbDeviceManager.logAndPrintException;
+
+import android.annotation.Nullable;
+import android.hardware.usb.gadget.IUsbGadget;
+import android.hardware.usb.gadget.IUsbGadgetCallback;
+import android.hardware.usb.UsbManager.UsbGadgetHalVersion;
+import android.os.ServiceManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.UsbDeviceManager;
+
+import java.util.ArrayList;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ * Implements the methods to interact with AIDL USB HAL.
+ */
+public final class UsbGadgetAidl implements UsbGadgetHal {
+    private static final String TAG = UsbGadgetAidl.class.getSimpleName();
+    private static final String USB_GADGET_AIDL_SERVICE = IUsbGadget.DESCRIPTOR + "/default";
+    // Proxy object for the usb gadget hal daemon.
+    @GuardedBy("mGadgetProxyLock")
+    private IUsbGadget mGadgetProxy;
+    private final UsbDeviceManager mDeviceManager;
+    public final IndentingPrintWriter mPw;
+    // Mutex for all mutable shared state.
+    private final Object mGadgetProxyLock = new Object();
+    // Callback when the UsbDevice status is changed by the kernel.
+    private UsbGadgetCallback mUsbGadgetCallback;
+
+    public @UsbGadgetHalVersion int getGadgetHalVersion() throws RemoteException {
+        synchronized (mGadgetProxyLock) {
+            if (mGadgetProxy == null) {
+                throw new RemoteException("IUsb not initialized yet");
+            }
+        }
+        Slog.i(TAG, "USB Gadget HAL AIDL version: GADGET_HAL_V2_0");
+        return GADGET_HAL_V2_0;
+    }
+
+    @Override
+    public void systemReady() {
+    }
+
+    public void serviceDied() {
+        logAndPrint(Log.ERROR, mPw, "Usb Gadget AIDL hal service died");
+        synchronized (mGadgetProxyLock) {
+            mGadgetProxy = null;
+        }
+        connectToProxy(null);
+    }
+
+    private void connectToProxy(IndentingPrintWriter pw) {
+        synchronized (mGadgetProxyLock) {
+            if (mGadgetProxy != null) {
+                return;
+            }
+
+            try {
+                mGadgetProxy = IUsbGadget.Stub.asInterface(
+                        ServiceManager.waitForService(USB_GADGET_AIDL_SERVICE));
+            } catch (NoSuchElementException e) {
+                logAndPrintException(pw, "connectToProxy: usb gadget hal service not found."
+                        + " Did the service fail to start?", e);
+            }
+        }
+    }
+
+    static boolean isServicePresent(IndentingPrintWriter pw) {
+        try {
+            return ServiceManager.isDeclared(USB_GADGET_AIDL_SERVICE);
+        } catch (NoSuchElementException e) {
+            logAndPrintException(pw, "connectToProxy: usb gadget Aidl hal service not found.", e);
+        }
+
+        return false;
+    }
+
+    public UsbGadgetAidl(UsbDeviceManager deviceManager, IndentingPrintWriter pw) {
+        mDeviceManager = Objects.requireNonNull(deviceManager);
+        mPw = pw;
+        connectToProxy(mPw);
+    }
+
+    @Override
+    public void getCurrentUsbFunctions(long operationId) {
+        synchronized (mGadgetProxyLock) {
+            try {
+                mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback(), operationId);
+            } catch (RemoteException e) {
+                logAndPrintException(mPw,
+                        "RemoteException while calling getCurrentUsbFunctions"
+                        + ", opID:" + operationId, e);
+                return;
+            }
+        }
+    }
+
+    @Override
+    public void getUsbSpeed(long operationId) {
+        try {
+            synchronized (mGadgetProxyLock) {
+                mGadgetProxy.getUsbSpeed(new UsbGadgetCallback(), operationId);
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling getUsbSpeed"
+                    + ", opID:" + operationId, e);
+            return;
+        }
+    }
+
+    @Override
+    public void reset() {
+        try {
+            synchronized (mGadgetProxyLock) {
+                mGadgetProxy.reset();
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling getUsbSpeed", e);
+            return;
+        }
+    }
+
+    @Override
+    public void setCurrentUsbFunctions(int mRequest, long mFunctions,
+            boolean mChargingFunctions, int timeout, long operationId) {
+        try {
+            mUsbGadgetCallback = new UsbGadgetCallback(mRequest,
+                                      mFunctions, mChargingFunctions);
+            synchronized (mGadgetProxyLock) {
+                mGadgetProxy.setCurrentUsbFunctions(mFunctions, mUsbGadgetCallback,
+                        timeout, operationId);
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling setCurrentUsbFunctions: "
+                    + "mRequest=" + mRequest
+                    + ", mFunctions=" + mFunctions
+                    + ", mChargingFunctions=" + mChargingFunctions
+                    + ", timeout=" + timeout
+                    + ", opID:" + operationId, e);
+            return;
+        }
+    }
+
+    private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
+        public int mRequest;
+        public long mFunctions;
+        public boolean mChargingFunctions;
+
+        UsbGadgetCallback() {
+        }
+
+        UsbGadgetCallback(int request, long functions,
+                boolean chargingFunctions) {
+            mRequest = request;
+            mFunctions = functions;
+            mChargingFunctions = chargingFunctions;
+        }
+
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                int status, long transactionId) {
+            mDeviceManager.setCurrentUsbFunctionsCb(functions, status,
+                    mRequest, mFunctions, mChargingFunctions);
+        }
+
+        @Override
+        public void getCurrentUsbFunctionsCb(long functions,
+                int status, long transactionId) {
+            mDeviceManager.getCurrentUsbFunctionsCb(functions, status);
+        }
+
+        @Override
+        public void getUsbSpeedCb(int speed, long transactionId) {
+            mDeviceManager.getUsbSpeedCb(speed);
+        }
+
+        @Override
+        public String getInterfaceHash() {
+            return IUsbGadgetCallback.HASH;
+        }
+
+        @Override
+        public int getInterfaceVersion() {
+            return IUsbGadgetCallback.VERSION;
+        }
+    }
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
new file mode 100644
index 0000000..267247b
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
@@ -0,0 +1,138 @@
+/*
+ * 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.hal.gadget;
+
+import android.annotation.IntDef;
+import android.hardware.usb.gadget.IUsbGadgetCallback;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.UsbManager.UsbHalVersion;
+import android.os.RemoteException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.String;
+
+/**
+ * @hide
+ */
+public interface UsbGadgetHal {
+    /**
+     * Power role: This USB port can act as a source (provide power).
+     * @hide
+     */
+    public static final int HAL_POWER_ROLE_SOURCE = 1;
+
+    /**
+     * Power role: This USB port can act as a sink (receive power).
+     * @hide
+     */
+    public static final int HAL_POWER_ROLE_SINK = 2;
+
+    @IntDef(prefix = { "HAL_POWER_ROLE_" }, value = {
+            HAL_POWER_ROLE_SOURCE,
+            HAL_POWER_ROLE_SINK
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface HalUsbPowerRole{}
+
+    /**
+     * Data role: This USB port can act as a host (access data services).
+     * @hide
+     */
+    public static final int HAL_DATA_ROLE_HOST = 1;
+
+    /**
+     * Data role: This USB port can act as a device (offer data services).
+     * @hide
+     */
+    public static final int HAL_DATA_ROLE_DEVICE = 2;
+
+    @IntDef(prefix = { "HAL_DATA_ROLE_" }, value = {
+            HAL_DATA_ROLE_HOST,
+            HAL_DATA_ROLE_DEVICE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface HalUsbDataRole{}
+
+    /**
+     * This USB port can act as a downstream facing port (host).
+     *
+     * @hide
+     */
+    public static final int HAL_MODE_DFP = 1;
+
+    /**
+     * This USB port can act as an upstream facing port (device).
+     *
+     * @hide
+     */
+    public static final int HAL_MODE_UFP = 2;
+    @IntDef(prefix = { "HAL_MODE_" }, value = {
+            HAL_MODE_DFP,
+            HAL_MODE_UFP,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface HalUsbPortMode{}
+
+    /**
+     * UsbPortManager would call this when the system is done booting.
+     */
+    public void systemReady();
+
+    /**
+     * This function is used to query the USB functions included in the
+     * current USB configuration.
+     *
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     */
+    public void getCurrentUsbFunctions(long transactionId);
+
+    /**
+     * The function is used to query current USB speed.
+     *
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     */
+    public void getUsbSpeed(long transactionId);
+
+    /**
+     * This function is used to reset USB gadget driver.
+     * Performs USB data connection reset. The connection will disconnect and
+     * reconnect.
+     */
+    public void reset();
+
+    /**
+     * Invoked to query the version of current gadget hal implementation.
+     */
+    public @UsbHalVersion int getGadgetHalVersion() throws RemoteException;
+
+    /**
+     * This function is used to set the current USB gadget configuration.
+     * The USB gadget needs to be torn down if a USB configuration is already
+     * active.
+     *
+     * @param functions list of functions defined by GadgetFunction to be
+     *                  included in the gadget composition.
+     * @param timeout The maximum time (in milliseconds) within which the
+     *                IUsbGadgetCallback needs to be returned.
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     */
+    public void setCurrentUsbFunctions(int request, long functions,
+        boolean chargingFunctions, int timeout, long transactionId);
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java
new file mode 100644
index 0000000..d268315
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java
@@ -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.server.usb.hal.gadget;
+
+import static com.android.server.usb.UsbPortManager.logAndPrint;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.hal.gadget.UsbGadgetHidl;
+import com.android.server.usb.hal.gadget.UsbGadgetAidl;
+import com.android.server.usb.UsbDeviceManager;
+
+import android.util.Log;
+/**
+ * Helper class that queries the underlying hal layer to populate UsbPortHal instance.
+ */
+public final class UsbGadgetHalInstance {
+
+    public static UsbGadgetHal getInstance(UsbDeviceManager deviceManager,
+            IndentingPrintWriter pw) {
+
+        logAndPrint(Log.DEBUG, pw, "Querying USB Gadget HAL version");
+        if (UsbGadgetAidl.isServicePresent(null)) {
+            logAndPrint(Log.INFO, pw, "USB Gadget HAL AIDL present");
+            return new UsbGadgetAidl(deviceManager, pw);
+        }
+        if (UsbGadgetHidl.isServicePresent(null)) {
+            logAndPrint(Log.INFO, pw, "USB Gadget HAL HIDL present");
+            return new UsbGadgetHidl(deviceManager, pw);
+        }
+
+        logAndPrint(Log.ERROR, pw, "USB Gadget HAL AIDL/HIDL not present");
+        return null;
+    }
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
new file mode 100644
index 0000000..3e5ecc5
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
@@ -0,0 +1,261 @@
+/*
+ * 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.hal.gadget;
+
+import static android.hardware.usb.UsbManager.GADGET_HAL_NOT_SUPPORTED;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_0;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_1;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_2;
+
+import static com.android.server.usb.UsbDeviceManager.logAndPrint;
+import static com.android.server.usb.UsbDeviceManager.logAndPrintException;
+
+import android.annotation.Nullable;
+import android.hardware.usb.gadget.V1_0.Status;
+import android.hardware.usb.gadget.V1_0.IUsbGadget;
+import android.hardware.usb.gadget.V1_2.IUsbGadgetCallback;
+import android.hardware.usb.gadget.V1_2.UsbSpeed;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbManager.UsbGadgetHalVersion;
+import android.hardware.usb.UsbManager.UsbHalVersion;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.UsbDeviceManager;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+/**
+ *
+ */
+public final class UsbGadgetHidl implements UsbGadgetHal {
+    // Cookie sent for usb gadget hal death notification.
+    private static final int USB_GADGET_HAL_DEATH_COOKIE = 2000;
+    // Proxy object for the usb gadget hal daemon.
+    @GuardedBy("mGadgetProxyLock")
+    private IUsbGadget mGadgetProxy;
+    private UsbDeviceManager mDeviceManager;
+    private final IndentingPrintWriter mPw;
+    // Mutex for all mutable shared state.
+    private final Object mGadgetProxyLock = new Object();
+    private UsbGadgetCallback mUsbGadgetCallback;
+
+    public @UsbGadgetHalVersion int getGadgetHalVersion() throws RemoteException {
+        int version;
+        synchronized(mGadgetProxyLock) {
+            if (mGadgetProxy == null) {
+                throw new RemoteException("IUsbGadget not initialized yet");
+            }
+            if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+                version = UsbManager.GADGET_HAL_V1_2;
+            } else if (android.hardware.usb.gadget.V1_1.IUsbGadget.castFrom(mGadgetProxy) != null) {
+                version = UsbManager.GADGET_HAL_V1_1;
+            } else {
+                version = UsbManager.GADGET_HAL_V1_0;
+            }
+            logAndPrint(Log.INFO, mPw, "USB Gadget HAL HIDL version: " + version);
+            return version;
+        }
+    }
+
+    final class DeathRecipient implements IHwBinder.DeathRecipient {
+        private final IndentingPrintWriter mPw;
+
+        DeathRecipient(IndentingPrintWriter pw) {
+            mPw = pw;
+        }
+
+        @Override
+        public void serviceDied(long cookie) {
+            if (cookie == USB_GADGET_HAL_DEATH_COOKIE) {
+                logAndPrint(Log.ERROR, mPw, "Usb Gadget hal service died cookie: " + cookie);
+                synchronized (mGadgetProxyLock) {
+                    mGadgetProxy = null;
+                }
+            }
+        }
+    }
+
+    final class ServiceNotification extends IServiceNotification.Stub {
+        @Override
+        public void onRegistration(String fqName, String name, boolean preexisting) {
+            logAndPrint(Log.INFO, mPw, "Usb gadget hal service started " + fqName + " " + name);
+            connectToProxy(null);
+        }
+    }
+
+    private void connectToProxy(IndentingPrintWriter pw) {
+        synchronized (mGadgetProxyLock) {
+            if (mGadgetProxy != null) {
+                return;
+            }
+
+            try {
+                mGadgetProxy = IUsbGadget.getService();
+                mGadgetProxy.linkToDeath(new DeathRecipient(pw), USB_GADGET_HAL_DEATH_COOKIE);
+            } catch (NoSuchElementException e) {
+                logAndPrintException(pw, "connectToProxy: usb gadget hal service not found."
+                        + " Did the service fail to start?", e);
+            } catch (RemoteException e) {
+                logAndPrintException(pw, "connectToProxy: usb gadget hal service not responding"
+                        , e);
+            }
+        }
+    }
+
+    @Override
+    public void systemReady() {
+    }
+
+    static boolean isServicePresent(IndentingPrintWriter pw) {
+        try {
+            IUsbGadget.getService(true);
+        } catch (NoSuchElementException e) {
+            logAndPrintException(pw, "connectToProxy: usb gadget hidl hal service not found.", e);
+            return false;
+        } catch (RemoteException e) {
+            logAndPrintException(pw, "IUSBGadget hal service present but failed to get service", e);
+        }
+
+        return true;
+    }
+
+    public UsbGadgetHidl(UsbDeviceManager deviceManager, IndentingPrintWriter pw) {
+        mDeviceManager = Objects.requireNonNull(deviceManager);
+        mPw = pw;
+        try {
+            ServiceNotification serviceNotification = new ServiceNotification();
+
+            boolean ret = IServiceManager.getService()
+                    .registerForNotifications("android.hardware.usb.gadget@1.0::IUsbGadget",
+                            "", serviceNotification);
+            if (!ret) {
+                logAndPrint(Log.ERROR, pw, "Failed to register service start notification");
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(pw, "Failed to register service start notification", e);
+            return;
+        }
+        connectToProxy(mPw);
+    }
+
+    @Override
+    public void getCurrentUsbFunctions(long transactionId) {
+        try {
+            synchronized(mGadgetProxyLock) {
+                mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling getCurrentUsbFunctions", e);
+            return;
+        }
+    }
+
+    @Override
+    public void getUsbSpeed(long transactionId) {
+        try {
+            synchronized(mGadgetProxyLock) {
+                if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+                    android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
+                    android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
+                    gadgetProxy.getUsbSpeed(new UsbGadgetCallback());
+                }
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw, "get UsbSpeed failed", e);
+        }
+    }
+
+    @Override
+    public void reset() {
+        try {
+            synchronized(mGadgetProxyLock) {
+                if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+                    android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
+                    android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
+                    gadgetProxy.reset();
+                }
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling getUsbSpeed", e);
+            return;
+        }
+    }
+
+    @Override
+    public void setCurrentUsbFunctions(int mRequest, long mFunctions,
+            boolean mChargingFunctions, int timeout, long operationId) {
+        try {
+            mUsbGadgetCallback = new UsbGadgetCallback(null, mRequest,
+                                      mFunctions, mChargingFunctions);
+            synchronized(mGadgetProxyLock) {
+                mGadgetProxy.setCurrentUsbFunctions(mFunctions, mUsbGadgetCallback, timeout);
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(mPw,
+                    "RemoteException while calling setCurrentUsbFunctions"
+                    + " mRequest = " + mRequest
+                    + ", mFunctions = " + mFunctions
+                    + ", timeout = " + timeout
+                    + ", mChargingFunctions = " + mChargingFunctions
+                    + ", operationId =" + operationId, e);
+            return;
+        }
+    }
+
+    private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
+        public int mRequest;
+        public long mFunctions;
+        public boolean mChargingFunctions;
+
+        UsbGadgetCallback() {
+        }
+        UsbGadgetCallback(IndentingPrintWriter pw, int request,
+                long functions, boolean chargingFunctions) {
+            mRequest = request;
+            mFunctions = functions;
+            mChargingFunctions = chargingFunctions;
+        }
+
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                int status) {
+            mDeviceManager.setCurrentUsbFunctionsCb(functions, status,
+                    mRequest, mFunctions, mChargingFunctions);
+        }
+
+        @Override
+        public void getCurrentUsbFunctionsCb(long functions,
+                int status) {
+            mDeviceManager.getCurrentUsbFunctionsCb(functions, status);
+        }
+
+        @Override
+        public void getUsbSpeedCb(int speed) {
+            mDeviceManager.getUsbSpeedCb(speed);
+        }
+    }
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
index 128a051..e6a3e53 100644
--- a/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
+++ b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
@@ -40,6 +40,8 @@
     public int usbDataStatus;
     public boolean powerTransferLimited;
     public int powerBrickConnectionStatus;
+    public final boolean supportsComplianceWarnings;
+    public int[] complianceWarnings;
 
     public RawPortInfo(String portId, int supportedModes) {
         this.portId = portId;
@@ -50,9 +52,10 @@
         this.supportsEnableContaminantPresenceDetection = false;
         this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
         this.usbDataStatus = UsbPortStatus.DATA_STATUS_UNKNOWN;
-
         this.powerTransferLimited = false;
         this.powerBrickConnectionStatus = UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN;
+        this.supportsComplianceWarnings = false;
+        this.complianceWarnings = new int[] {};
     }
 
     public RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes,
@@ -66,6 +69,29 @@
             int usbDataStatus,
             boolean powerTransferLimited,
             int powerBrickConnectionStatus) {
+        this(portId, supportedModes, supportedContaminantProtectionModes,
+                    currentMode, canChangeMode,
+                    currentPowerRole, canChangePowerRole,
+                    currentDataRole, canChangeDataRole,
+                    supportsEnableContaminantPresenceProtection, contaminantProtectionStatus,
+                    supportsEnableContaminantPresenceDetection, contaminantDetectionStatus,
+                    usbDataStatus, powerTransferLimited, powerBrickConnectionStatus,
+                    false, new int[] {});
+    }
+
+    public RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes,
+            int currentMode, boolean canChangeMode,
+            int currentPowerRole, boolean canChangePowerRole,
+            int currentDataRole, boolean canChangeDataRole,
+            boolean supportsEnableContaminantPresenceProtection,
+            int contaminantProtectionStatus,
+            boolean supportsEnableContaminantPresenceDetection,
+            int contaminantDetectionStatus,
+            int usbDataStatus,
+            boolean powerTransferLimited,
+            int powerBrickConnectionStatus,
+            boolean supportsComplianceWarnings,
+            int[] complianceWarnings) {
         this.portId = portId;
         this.supportedModes = supportedModes;
         this.supportedContaminantProtectionModes = supportedContaminantProtectionModes;
@@ -84,6 +110,8 @@
         this.usbDataStatus = usbDataStatus;
         this.powerTransferLimited = powerTransferLimited;
         this.powerBrickConnectionStatus = powerBrickConnectionStatus;
+        this.supportsComplianceWarnings = supportsComplianceWarnings;
+        this.complianceWarnings = complianceWarnings;
     }
 
     @Override
@@ -109,6 +137,8 @@
         dest.writeInt(usbDataStatus);
         dest.writeBoolean(powerTransferLimited);
         dest.writeInt(powerBrickConnectionStatus);
+        dest.writeBoolean(supportsComplianceWarnings);
+        dest.writeIntArray(complianceWarnings);
     }
 
     public static final Parcelable.Creator<RawPortInfo> CREATOR =
@@ -131,6 +161,8 @@
             int usbDataStatus = in.readInt();
             boolean powerTransferLimited = in.readBoolean();
             int powerBrickConnectionStatus = in.readInt();
+            boolean supportsComplianceWarnings = in.readBoolean();
+            int[] complianceWarnings = in.createIntArray();
             return new RawPortInfo(id, supportedModes,
                     supportedContaminantProtectionModes, currentMode, canChangeMode,
                     currentPowerRole, canChangePowerRole,
@@ -139,7 +171,8 @@
                     contaminantProtectionStatus,
                     supportsEnableContaminantPresenceDetection,
                     contaminantDetectionStatus, usbDataStatus,
-                    powerTransferLimited, powerBrickConnectionStatus);
+                    powerTransferLimited, powerBrickConnectionStatus,
+                    supportsComplianceWarnings, complianceWarnings);
         }
 
         @Override
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
index 94273a3..ca11629 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
@@ -34,9 +34,12 @@
 import android.hardware.usb.IUsbCallback;
 import android.hardware.usb.PortRole;
 import android.hardware.usb.PortStatus;
+import android.hardware.usb.ComplianceWarning;
+import android.os.Build;
 import android.os.ServiceManager;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.Slog;
@@ -46,6 +49,7 @@
 import com.android.server.usb.UsbPortManager;
 import com.android.server.usb.hal.port.RawPortInfo;
 
+import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.NoSuchElementException;
@@ -551,6 +555,24 @@
             return usbDataStatus;
         }
 
+        private int[] formatComplianceWarnings(int[] complianceWarnings) {
+            Objects.requireNonNull(complianceWarnings);
+            IntArray newComplianceWarnings = new IntArray();
+            Arrays.sort(complianceWarnings);
+            for (int warning : complianceWarnings) {
+                if (newComplianceWarnings.indexOf(warning) == -1
+                        && warning >= UsbPortStatus.COMPLIANCE_WARNING_OTHER) {
+                    // ComplianceWarnings range from [1, 4] in Android U
+                    if (warning > UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP) {
+                        newComplianceWarnings.add(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
+                    } else {
+                        newComplianceWarnings.add(warning);
+                    }
+                }
+            }
+            return newComplianceWarnings.toArray();
+        }
+
         @Override
         public void notifyPortStatusChange(
                android.hardware.usb.PortStatus[] currentPortStatus, int retval) {
@@ -584,7 +606,9 @@
                         current.contaminantDetectionStatus,
                         toUsbDataStatusInt(current.usbDataStatus),
                         current.powerTransferLimited,
-                        current.powerBrickStatus);
+                        current.powerBrickStatus,
+                        current.supportsComplianceWarnings,
+                        formatComplianceWarnings(current.complianceWarnings));
                 newPortInfo.add(temp);
                 UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback AIDL V1: "
                         + current.portName);
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
index 23d913c..10403c1 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
@@ -421,7 +421,8 @@
                         current.currentDataRole, current.canChangeDataRole,
                         false, CONTAMINANT_PROTECTION_NONE,
                         false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataStatus,
-                        false, POWER_BRICK_STATUS_UNKNOWN);
+                        false, POWER_BRICK_STATUS_UNKNOWN,
+                        false, new int[] {});
                 newPortInfo.add(temp);
                 UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_0: "
                         + current.portName);
@@ -455,7 +456,8 @@
                         current.status.currentDataRole, current.status.canChangeDataRole,
                         false, CONTAMINANT_PROTECTION_NONE,
                         false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataStatus,
-                        false, POWER_BRICK_STATUS_UNKNOWN);
+                        false, POWER_BRICK_STATUS_UNKNOWN,
+                        false, new int[] {});
                 newPortInfo.add(temp);
                 UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_1: "
                         + current.status.portName);
@@ -493,7 +495,8 @@
                         current.supportsEnableContaminantPresenceDetection,
                         current.contaminantDetectionStatus,
                         sUsbDataStatus,
-                        false, POWER_BRICK_STATUS_UNKNOWN);
+                        false, POWER_BRICK_STATUS_UNKNOWN,
+                        false, new int[] {});
                 newPortInfo.add(temp);
                 UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_2: "
                         + current.status_1_1.status.portName);
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/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerLogger.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerLogger.java
deleted file mode 100644
index 73b4ce7..0000000
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerLogger.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.soundtrigger;
-
-import android.util.Log;
-
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.LinkedList;
-
-/**
-* Constructor SoundTriggerLogger class
-*/
-public class SoundTriggerLogger {
-
-    // ring buffer of events to log.
-    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 long mTimestamp;
-
-        Event() {
-            mTimestamp = System.currentTimeMillis();
-        }
-
-    /**
-     * Convert event to String
-     * @return StringBuilder
-     */
-        public String toString() {
-            return (new StringBuilder(sFormat.format(new Date(mTimestamp))))
-                    .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 SoundTriggerLogger) 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 SoundTriggerLogger(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) {
-        if (mEvents.size() >= mMemSize) {
-            mEvents.removeFirst();
-        }
-        mEvents.add(evt);
-    }
-
-    /**
-     * Constructor for logger.
-     * @param pw the maximum number of events to keep in log
-     */
-    public synchronized void dump(PrintWriter pw) {
-        pw.println("ST Event log: " + mTitle);
-        for (Event evt : mEvents) {
-            pw.println(evt.toString());
-        }
-    }
-}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 5183e5b..81717f4 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -84,6 +84,7 @@
 import com.android.internal.app.ISoundTriggerService;
 import com.android.internal.app.ISoundTriggerSession;
 import com.android.server.SystemService;
+import com.android.server.utils.EventLogger;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -309,14 +310,14 @@
                     Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("startRecognition(): Uuid : "
-                        + parcelUuid));
+                sEventLogger.enqueue(new EventLogger.StringEvent(
+                        "startRecognition(): Uuid : " + parcelUuid));
 
                 GenericSoundModel model = getSoundModel(parcelUuid);
                 if (model == null) {
                     Slog.w(TAG, "Null model in database for id: " + parcelUuid);
 
-                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    sEventLogger.enqueue(new EventLogger.StringEvent(
                             "startRecognition(): Null model in database for id: " + parcelUuid));
 
                     return STATUS_ERROR;
@@ -339,7 +340,7 @@
                     Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("stopRecognition(): Uuid : "
+                sEventLogger.enqueue(new EventLogger.StringEvent("stopRecognition(): Uuid : "
                         + parcelUuid));
 
                 int ret = mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(),
@@ -359,7 +360,7 @@
                     Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("getSoundModel(): id = "
+                sEventLogger.enqueue(new EventLogger.StringEvent("getSoundModel(): id = "
                         + soundModelId));
 
                 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
@@ -376,7 +377,7 @@
                     Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("updateSoundModel(): model = "
+                sEventLogger.enqueue(new EventLogger.StringEvent("updateSoundModel(): model = "
                         + soundModel));
 
                 mDbHelper.updateGenericSoundModel(soundModel);
@@ -391,7 +392,7 @@
                     Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = "
+                sEventLogger.enqueue(new EventLogger.StringEvent("deleteSoundModel(): id = "
                         + soundModelId));
 
                 // Unload the model if it is loaded.
@@ -411,7 +412,7 @@
                 if (soundModel == null || soundModel.getUuid() == null) {
                     Slog.w(TAG, "Invalid sound model");
 
-                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    sEventLogger.enqueue(new EventLogger.StringEvent(
                             "loadGenericSoundModel(): Invalid sound model"));
 
                     return STATUS_ERROR;
@@ -420,7 +421,7 @@
                     Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.getUuid());
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("loadGenericSoundModel(): id = "
+                sEventLogger.enqueue(new EventLogger.StringEvent("loadGenericSoundModel(): id = "
                         + soundModel.getUuid()));
 
                 synchronized (mLock) {
@@ -447,7 +448,7 @@
                 if (soundModel == null || soundModel.getUuid() == null) {
                     Slog.w(TAG, "Invalid sound model");
 
-                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    sEventLogger.enqueue(new EventLogger.StringEvent(
                             "loadKeyphraseSoundModel(): Invalid sound model"));
 
                     return STATUS_ERROR;
@@ -455,7 +456,7 @@
                 if (soundModel.getKeyphrases() == null || soundModel.getKeyphrases().length != 1) {
                     Slog.w(TAG, "Only one keyphrase per model is currently supported.");
 
-                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    sEventLogger.enqueue(new EventLogger.StringEvent(
                             "loadKeyphraseSoundModel(): Only one keyphrase per model"
                                     + " is currently supported."));
 
@@ -465,8 +466,8 @@
                     Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.getUuid());
                 }
 
-                sEventLogger.log(
-                        new SoundTriggerLogger.StringEvent("loadKeyphraseSoundModel(): id = "
+                sEventLogger.enqueue(
+                        new EventLogger.StringEvent("loadKeyphraseSoundModel(): id = "
                                 + soundModel.getUuid()));
 
                 synchronized (mLock) {
@@ -503,7 +504,7 @@
                     Slog.i(TAG, "startRecognition(): id = " + soundModelId);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                sEventLogger.enqueue(new EventLogger.StringEvent(
                         "startRecognitionForService(): id = " + soundModelId));
 
                 IRecognitionStatusCallback callback =
@@ -515,7 +516,7 @@
                     if (soundModel == null) {
                         Slog.w(TAG, soundModelId + " is not loaded");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "startRecognitionForService():" + soundModelId + " is not loaded"));
 
                         return STATUS_ERROR;
@@ -527,7 +528,7 @@
                     if (existingCallback != null) {
                         Slog.w(TAG, soundModelId + " is already running");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "startRecognitionForService():"
                                         + soundModelId + " is already running"));
 
@@ -542,7 +543,7 @@
                         default:
                             Slog.e(TAG, "Unknown model type");
 
-                            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            sEventLogger.enqueue(new EventLogger.StringEvent(
                                     "startRecognitionForService(): Unknown model type"));
 
                             return STATUS_ERROR;
@@ -551,7 +552,7 @@
                     if (ret != STATUS_OK) {
                         Slog.e(TAG, "Failed to start model: " + ret);
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "startRecognitionForService(): Failed to start model:"));
 
                         return ret;
@@ -574,7 +575,7 @@
                     Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                sEventLogger.enqueue(new EventLogger.StringEvent(
                         "stopRecognitionForService(): id = " + soundModelId));
 
                 synchronized (mLock) {
@@ -582,7 +583,7 @@
                     if (soundModel == null) {
                         Slog.w(TAG, soundModelId + " is not loaded");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "stopRecognitionForService(): " + soundModelId
                                         + " is not loaded"));
 
@@ -595,7 +596,7 @@
                     if (callback == null) {
                         Slog.w(TAG, soundModelId + " is not running");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "stopRecognitionForService(): " + soundModelId
                                         + " is not running"));
 
@@ -610,7 +611,7 @@
                         default:
                             Slog.e(TAG, "Unknown model type");
 
-                            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            sEventLogger.enqueue(new EventLogger.StringEvent(
                                     "stopRecognitionForService(): Unknown model type"));
 
                             return STATUS_ERROR;
@@ -619,7 +620,7 @@
                     if (ret != STATUS_OK) {
                         Slog.e(TAG, "Failed to stop model: " + ret);
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "stopRecognitionForService(): Failed to stop model: " + ret));
 
                         return ret;
@@ -642,7 +643,7 @@
                     Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("unloadSoundModel(): id = "
+                sEventLogger.enqueue(new EventLogger.StringEvent("unloadSoundModel(): id = "
                         + soundModelId));
 
                 synchronized (mLock) {
@@ -650,7 +651,7 @@
                     if (soundModel == null) {
                         Slog.w(TAG, soundModelId + " is not loaded");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "unloadSoundModel(): " + soundModelId + " is not loaded"));
 
                         return STATUS_ERROR;
@@ -667,7 +668,7 @@
                         default:
                             Slog.e(TAG, "Unknown model type");
 
-                            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            sEventLogger.enqueue(new EventLogger.StringEvent(
                                     "unloadSoundModel(): Unknown model type"));
 
                             return STATUS_ERROR;
@@ -675,7 +676,7 @@
                     if (ret != STATUS_OK) {
                         Slog.e(TAG, "Failed to unload model");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "unloadSoundModel(): Failed to unload model"));
 
                         return ret;
@@ -709,7 +710,7 @@
                     Slog.i(TAG, "getModelState(): id = " + soundModelId);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): id = "
+                sEventLogger.enqueue(new EventLogger.StringEvent("getModelState(): id = "
                         + soundModelId));
 
                 synchronized (mLock) {
@@ -717,7 +718,7 @@
                     if (soundModel == null) {
                         Slog.w(TAG, soundModelId + " is not loaded");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): "
+                        sEventLogger.enqueue(new EventLogger.StringEvent("getModelState(): "
                                 + soundModelId + " is not loaded"));
 
                         return ret;
@@ -729,7 +730,7 @@
                         default:
                             // SoundModel.TYPE_KEYPHRASE is not supported to increase privacy.
                             Slog.e(TAG, "Unsupported model type, " + soundModel.getType());
-                            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            sEventLogger.enqueue(new EventLogger.StringEvent(
                                     "getModelState(): Unsupported model type, "
                                             + soundModel.getType()));
                             break;
@@ -751,7 +752,7 @@
 
                 synchronized (mLock) {
                     ModuleProperties properties = mSoundTriggerHelper.getModuleProperties();
-                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    sEventLogger.enqueue(new EventLogger.StringEvent(
                             "getModuleProperties(): " + properties));
                     return properties;
                 }
@@ -769,7 +770,7 @@
                             + ", value=" + value);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                sEventLogger.enqueue(new EventLogger.StringEvent(
                         "setParameter(): id=" + soundModelId
                                 + ", param=" + modelParam
                                 + ", value=" + value));
@@ -780,7 +781,7 @@
                         Slog.w(TAG, soundModelId + " is not loaded. Loaded models: "
                                 + mLoadedModels.toString());
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent("setParameter(): "
+                        sEventLogger.enqueue(new EventLogger.StringEvent("setParameter(): "
                                 + soundModelId + " is not loaded"));
 
                         return STATUS_BAD_VALUE;
@@ -803,7 +804,7 @@
                             + ", param=" + modelParam);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                sEventLogger.enqueue(new EventLogger.StringEvent(
                         "getParameter(): id=" + soundModelId
                                 + ", param=" + modelParam));
 
@@ -812,7 +813,7 @@
                     if (soundModel == null) {
                         Slog.w(TAG, soundModelId + " is not loaded");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent("getParameter(): "
+                        sEventLogger.enqueue(new EventLogger.StringEvent("getParameter(): "
                                 + soundModelId + " is not loaded"));
 
                         throw new IllegalArgumentException("sound model is not loaded");
@@ -834,7 +835,7 @@
                             + ", param=" + modelParam);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                sEventLogger.enqueue(new EventLogger.StringEvent(
                         "queryParameter(): id=" + soundModelId
                                 + ", param=" + modelParam));
 
@@ -843,7 +844,7 @@
                     if (soundModel == null) {
                         Slog.w(TAG, soundModelId + " is not loaded");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "queryParameter(): "
                                         + soundModelId + " is not loaded"));
 
@@ -857,7 +858,7 @@
 
         private void clientDied() {
             Slog.w(TAG, "Client died, cleaning up session.");
-            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+            sEventLogger.enqueue(new EventLogger.StringEvent(
                     "Client died, cleaning up session."));
             mSoundTriggerHelper.detach();
         }
@@ -1027,7 +1028,7 @@
                     } catch (Exception e) {
                         Slog.e(TAG, mPuuid + ": Cannot remove client", e);
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ": Cannot remove client"));
 
                     }
@@ -1052,7 +1053,7 @@
             private void destroy() {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + ": destroy"));
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid + ": destroy"));
 
                 synchronized (mRemoteServiceLock) {
                     disconnectLocked();
@@ -1086,7 +1087,7 @@
                                 Slog.e(TAG, mPuuid + ": Could not stop operation "
                                         + mRunningOpIds.valueAt(i), e);
 
-                                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                         + ": Could not stop operation " + mRunningOpIds.valueAt(
                                         i)));
 
@@ -1116,7 +1117,7 @@
                     if (ri == null) {
                         Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ": " + mServiceName + " not found"));
 
                         return;
@@ -1127,7 +1128,7 @@
                         Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
                                 + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ": " + mServiceName + " does not require "
                                 + BIND_SOUND_TRIGGER_DETECTION_SERVICE));
 
@@ -1143,7 +1144,7 @@
                     } else {
                         Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ": Could not bind to " + mServiceName));
 
                     }
@@ -1165,7 +1166,7 @@
                                 mPuuid + ": Dropped operation as already destroyed or marked for "
                                         + "destruction");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ":Dropped operation as already destroyed or marked for "
                                 + "destruction"));
 
@@ -1197,7 +1198,7 @@
                                             mPuuid + ": Dropped operation as too many operations "
                                                     + "were run in last 24 hours");
 
-                                    sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                    sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                             + ": Dropped operation as too many operations "
                                             + "were run in last 24 hours"));
 
@@ -1207,7 +1208,7 @@
                             } catch (Exception e) {
                                 Slog.e(TAG, mPuuid + ": Could not drop operation", e);
 
-                                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                         + ": Could not drop operation"));
 
                             }
@@ -1224,7 +1225,7 @@
                             try {
                                 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
 
-                                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                         + ": runOp " + opId));
 
                                 op.run(opId, mService);
@@ -1232,7 +1233,7 @@
                             } catch (Exception e) {
                                 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
 
-                                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                         + ": Could not run operation " + opId));
 
                             }
@@ -1265,7 +1266,7 @@
                 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
                         + ")");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + "->" + mServiceName
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid + "->" + mServiceName
                         + ": IGNORED onKeyphraseDetected(" + event + ")"));
             }
 
@@ -1282,9 +1283,9 @@
                 attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
                 AudioAttributes attributes = attributesBuilder.build();
 
-                    AudioFormat originalFormat = event.getCaptureFormat();
+                AudioFormat originalFormat = event.getCaptureFormat();
 
-                    sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
+                sEventLogger.enqueue(new EventLogger.StringEvent("createAudioRecordForEvent"));
 
                 return (new AudioRecord.Builder())
                             .setAudioAttributes(attributes)
@@ -1301,7 +1302,7 @@
             public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + ": Generic sound trigger event: " + event));
 
                 runOrAddOperation(new Operation(
@@ -1336,7 +1337,7 @@
             public void onError(int status) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + ": onError: " + status));
 
                 runOrAddOperation(
@@ -1359,7 +1360,7 @@
             public void onRecognitionPaused() {
                 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + "->" + mServiceName + ": IGNORED onRecognitionPaused"));
 
             }
@@ -1368,7 +1369,7 @@
             public void onRecognitionResumed() {
                 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + "->" + mServiceName + ": IGNORED onRecognitionResumed"));
 
             }
@@ -1377,7 +1378,7 @@
             public void onServiceConnected(ComponentName name, IBinder service) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + ": onServiceConnected(" + service + ")"));
 
                 synchronized (mRemoteServiceLock) {
@@ -1400,7 +1401,7 @@
             public void onServiceDisconnected(ComponentName name) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + ": onServiceDisconnected"));
 
                 synchronized (mRemoteServiceLock) {
@@ -1412,7 +1413,7 @@
             public void onBindingDied(ComponentName name) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + ": onBindingDied"));
 
                 synchronized (mRemoteServiceLock) {
@@ -1424,7 +1425,7 @@
             public void onNullBinding(ComponentName name) {
                 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(name + " for model "
+                sEventLogger.enqueue(new EventLogger.StringEvent(name + " for model "
                         + mPuuid + " returned a null binding"));
 
                 synchronized (mRemoteServiceLock) {
@@ -1610,7 +1611,7 @@
 
             private void clientDied() {
                 Slog.w(TAG, "Client died, cleaning up session.");
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                sEventLogger.enqueue(new EventLogger.StringEvent(
                         "Client died, cleaning up session."));
                 mSoundTriggerHelper.detach();
             }
@@ -1637,7 +1638,7 @@
     //=================================================================
     // For logging
 
-    private static final SoundTriggerLogger sEventLogger = new SoundTriggerLogger(200,
+    private static final EventLogger sEventLogger = new EventLogger(200,
             "SoundTrigger activity");
 
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java
new file mode 100644
index 0000000..81cd194
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java
@@ -0,0 +1,306 @@
+/*
+ * 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.voiceinteraction;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.service.voice.HotwordAudioStream.KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES;
+
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_CLOSE_ERROR_FROM_SYSTEM;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_EMPTY_AUDIO_STREAM_LIST;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_END;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_ILLEGAL_COPY_BUFFER_SIZE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_INTERRUPTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_NO_PERMISSION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_START;
+import static com.android.server.voiceinteraction.HotwordDetectionConnection.DEBUG;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.service.voice.HotwordAudioStream;
+import android.service.voice.HotwordDetectedResult;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Copies the audio streams in {@link HotwordDetectedResult}s. This allows the system to manage the
+ * lifetime of the {@link ParcelFileDescriptor}s and ensures that the flow of data is in the right
+ * direction from the {@link android.service.voice.HotwordDetectionService} to the client (i.e., the
+ * voice interactor).
+ *
+ * @hide
+ */
+final class HotwordAudioStreamCopier {
+
+    private static final String TAG = "HotwordAudioStreamCopier";
+    private static final String OP_MESSAGE = "Streaming hotword audio to VoiceInteractionService";
+    private static final String TASK_ID_PREFIX = "HotwordDetectedResult@";
+    private static final String THREAD_NAME_PREFIX = "Copy-";
+    private static final int DEFAULT_COPY_BUFFER_LENGTH_BYTES = 2_560;
+
+    // Corresponds to the OS pipe capacity in bytes
+    private static final int MAX_COPY_BUFFER_LENGTH_BYTES = 65_536;
+
+    private final AppOpsManager mAppOpsManager;
+    private final int mDetectorType;
+    private final int mVoiceInteractorUid;
+    private final String mVoiceInteractorPackageName;
+    private final String mVoiceInteractorAttributionTag;
+    private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
+
+    HotwordAudioStreamCopier(@NonNull AppOpsManager appOpsManager, int detectorType,
+            int voiceInteractorUid, @NonNull String voiceInteractorPackageName,
+            @NonNull String voiceInteractorAttributionTag) {
+        mAppOpsManager = appOpsManager;
+        mDetectorType = detectorType;
+        mVoiceInteractorUid = voiceInteractorUid;
+        mVoiceInteractorPackageName = voiceInteractorPackageName;
+        mVoiceInteractorAttributionTag = voiceInteractorAttributionTag;
+    }
+
+    /**
+     * Starts copying the audio streams in the given {@link HotwordDetectedResult}.
+     * <p>
+     * The returned {@link HotwordDetectedResult} is identical the one that was passed in, except
+     * that the {@link ParcelFileDescriptor}s within {@link HotwordDetectedResult#getAudioStreams()}
+     * are replaced with descriptors from pipes managed by {@link HotwordAudioStreamCopier}. The
+     * returned value should be passed on to the client (i.e., the voice interactor).
+     * </p>
+     *
+     * @throws IOException If there was an error creating the managed pipe.
+     */
+    @NonNull
+    public HotwordDetectedResult startCopyingAudioStreams(@NonNull HotwordDetectedResult result)
+            throws IOException {
+        List<HotwordAudioStream> audioStreams = result.getAudioStreams();
+        if (audioStreams.isEmpty()) {
+            HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                    HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_EMPTY_AUDIO_STREAM_LIST,
+                    mVoiceInteractorUid);
+            return result;
+        }
+
+        List<HotwordAudioStream> newAudioStreams = new ArrayList<>(audioStreams.size());
+        List<CopyTaskInfo> copyTaskInfos = new ArrayList<>(audioStreams.size());
+        for (HotwordAudioStream audioStream : audioStreams) {
+            ParcelFileDescriptor[] clientPipe = ParcelFileDescriptor.createReliablePipe();
+            ParcelFileDescriptor clientAudioSource = clientPipe[0];
+            ParcelFileDescriptor clientAudioSink = clientPipe[1];
+            HotwordAudioStream newAudioStream =
+                    audioStream.buildUpon().setAudioStreamParcelFileDescriptor(
+                            clientAudioSource).build();
+            newAudioStreams.add(newAudioStream);
+
+            int copyBufferLength = DEFAULT_COPY_BUFFER_LENGTH_BYTES;
+            PersistableBundle metadata = audioStream.getMetadata();
+            if (metadata.containsKey(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES)) {
+                copyBufferLength = metadata.getInt(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES, -1);
+                if (copyBufferLength < 1 || copyBufferLength > MAX_COPY_BUFFER_LENGTH_BYTES) {
+                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                            HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_ILLEGAL_COPY_BUFFER_SIZE,
+                            mVoiceInteractorUid);
+                    Slog.w(TAG, "Attempted to set an invalid copy buffer length ("
+                            + copyBufferLength + ") for: " + audioStream);
+                    copyBufferLength = DEFAULT_COPY_BUFFER_LENGTH_BYTES;
+                } else if (DEBUG) {
+                    Slog.i(TAG, "Copy buffer length set to " + copyBufferLength + " for: "
+                            + audioStream);
+                }
+            }
+
+            ParcelFileDescriptor serviceAudioSource =
+                    audioStream.getAudioStreamParcelFileDescriptor();
+            copyTaskInfos.add(new CopyTaskInfo(serviceAudioSource, clientAudioSink,
+                    copyBufferLength));
+        }
+
+        String resultTaskId = TASK_ID_PREFIX + System.identityHashCode(result);
+        mExecutorService.execute(new HotwordDetectedResultCopyTask(resultTaskId, copyTaskInfos));
+
+        return result.buildUpon().setAudioStreams(newAudioStreams).build();
+    }
+
+    private static class CopyTaskInfo {
+        private final ParcelFileDescriptor mSource;
+        private final ParcelFileDescriptor mSink;
+        private final int mCopyBufferLength;
+
+        CopyTaskInfo(ParcelFileDescriptor source, ParcelFileDescriptor sink, int copyBufferLength) {
+            mSource = source;
+            mSink = sink;
+            mCopyBufferLength = copyBufferLength;
+        }
+    }
+
+    private class HotwordDetectedResultCopyTask implements Runnable {
+        private final String mResultTaskId;
+        private final List<CopyTaskInfo> mCopyTaskInfos;
+        private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
+
+        HotwordDetectedResultCopyTask(String resultTaskId, List<CopyTaskInfo> copyTaskInfos) {
+            mResultTaskId = resultTaskId;
+            mCopyTaskInfos = copyTaskInfos;
+        }
+
+        @Override
+        public void run() {
+            Thread.currentThread().setName(THREAD_NAME_PREFIX + mResultTaskId);
+            int size = mCopyTaskInfos.size();
+            List<SingleAudioStreamCopyTask> tasks = new ArrayList<>(size);
+            for (int i = 0; i < size; i++) {
+                CopyTaskInfo copyTaskInfo = mCopyTaskInfos.get(i);
+                String streamTaskId = mResultTaskId + "@" + i;
+                tasks.add(new SingleAudioStreamCopyTask(streamTaskId, copyTaskInfo.mSource,
+                        copyTaskInfo.mSink, copyTaskInfo.mCopyBufferLength, mDetectorType,
+                        mVoiceInteractorUid));
+            }
+
+            if (mAppOpsManager.startOpNoThrow(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
+                    mVoiceInteractorUid, mVoiceInteractorPackageName,
+                    mVoiceInteractorAttributionTag, OP_MESSAGE) == MODE_ALLOWED) {
+                try {
+                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                            HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_START,
+                            mVoiceInteractorUid);
+                    // TODO(b/244599891): Set timeout, close after inactivity
+                    mExecutorService.invokeAll(tasks);
+                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                            HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_END, mVoiceInteractorUid);
+                } catch (InterruptedException e) {
+                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                            HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_INTERRUPTED_EXCEPTION,
+                            mVoiceInteractorUid);
+                    Slog.e(TAG, mResultTaskId + ": Task was interrupted", e);
+                    bestEffortPropagateError(e.getMessage());
+                } finally {
+                    mAppOpsManager.finishOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
+                            mVoiceInteractorUid, mVoiceInteractorPackageName,
+                            mVoiceInteractorAttributionTag);
+                }
+            } else {
+                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                        HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_NO_PERMISSION,
+                        mVoiceInteractorUid);
+                bestEffortPropagateError(
+                        "Failed to obtain RECORD_AUDIO_HOTWORD permission for voice interactor with"
+                                + " uid=" + mVoiceInteractorUid
+                                + " packageName=" + mVoiceInteractorPackageName
+                                + " attributionTag=" + mVoiceInteractorAttributionTag);
+            }
+        }
+
+        private void bestEffortPropagateError(@NonNull String errorMessage) {
+            try {
+                for (CopyTaskInfo copyTaskInfo : mCopyTaskInfos) {
+                    copyTaskInfo.mSource.closeWithError(errorMessage);
+                    copyTaskInfo.mSink.closeWithError(errorMessage);
+                }
+                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                        HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_CLOSE_ERROR_FROM_SYSTEM,
+                        mVoiceInteractorUid);
+            } catch (IOException e) {
+                Slog.e(TAG, mResultTaskId + ": Failed to propagate error", e);
+            }
+        }
+    }
+
+    private static class SingleAudioStreamCopyTask implements Callable<Void> {
+        private final String mStreamTaskId;
+        private final ParcelFileDescriptor mAudioSource;
+        private final ParcelFileDescriptor mAudioSink;
+        private final int mCopyBufferLength;
+
+        private final int mDetectorType;
+        private final int mUid;
+
+        SingleAudioStreamCopyTask(String streamTaskId, ParcelFileDescriptor audioSource,
+                ParcelFileDescriptor audioSink, int copyBufferLength, int detectorType, int uid) {
+            mStreamTaskId = streamTaskId;
+            mAudioSource = audioSource;
+            mAudioSink = audioSink;
+            mCopyBufferLength = copyBufferLength;
+            mDetectorType = detectorType;
+            mUid = uid;
+        }
+
+        @Override
+        public Void call() throws Exception {
+            Thread.currentThread().setName(THREAD_NAME_PREFIX + mStreamTaskId);
+
+            // Note: We are intentionally NOT using try-with-resources here. If we did,
+            // the ParcelFileDescriptors will be automatically closed WITHOUT errors before we go
+            // into the IOException-catch block. We want to propagate the error while closing the
+            // PFDs.
+            InputStream fis = null;
+            OutputStream fos = null;
+            try {
+                fis = new ParcelFileDescriptor.AutoCloseInputStream(mAudioSource);
+                fos = new ParcelFileDescriptor.AutoCloseOutputStream(mAudioSink);
+                byte[] buffer = new byte[mCopyBufferLength];
+                while (true) {
+                    if (Thread.interrupted()) {
+                        Slog.e(TAG,
+                                mStreamTaskId + ": SingleAudioStreamCopyTask task was interrupted");
+                        break;
+                    }
+
+                    int bytesRead = fis.read(buffer);
+                    if (bytesRead < 0) {
+                        Slog.i(TAG, mStreamTaskId + ": Reached end of audio stream");
+                        break;
+                    }
+                    if (bytesRead > 0) {
+                        if (DEBUG) {
+                            // TODO(b/244599440): Add proper logging
+                            Slog.d(TAG, mStreamTaskId + ": Copied " + bytesRead
+                                    + " bytes from audio stream. First 20 bytes=" + Arrays.toString(
+                                    Arrays.copyOfRange(buffer, 0, 20)));
+                        }
+                        fos.write(buffer, 0, bytesRead);
+                    }
+                    // TODO(b/244599891): Close PFDs after inactivity
+                }
+            } catch (IOException e) {
+                mAudioSource.closeWithError(e.getMessage());
+                mAudioSink.closeWithError(e.getMessage());
+                Slog.e(TAG, mStreamTaskId + ": Failed to copy audio stream", e);
+                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                        HOTWORD_DETECTOR_EVENTS__EVENT__AUDIO_EGRESS_CLOSE_ERROR_FROM_SYSTEM, mUid);
+            } finally {
+                if (fis != null) {
+                    fis.close();
+                }
+                if (fos != null) {
+                    fos.close();
+                }
+            }
+
+            return null;
+        }
+    }
+
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 372fdaf..2ac25b6 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -19,7 +19,6 @@
 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
 import static android.Manifest.permission.RECORD_AUDIO;
 import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
-import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS;
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
 import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
@@ -35,7 +34,12 @@
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
@@ -144,6 +148,7 @@
     private static final int HOTWORD_DETECTION_SERVICE_DIED = -1;
     private static final int CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION = -2;
     private static final int CALLBACK_DETECT_TIMEOUT = -3;
+    private static final int CALLBACK_ONDETECTED_STREAM_COPY_ERROR = -4;
 
     // Hotword metrics
     private static final int METRICS_INIT_UNKNOWN_TIMEOUT =
@@ -170,11 +175,15 @@
             HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
     private static final int METRICS_EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION =
             HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
+    private static final int METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION =
+            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION;
 
     private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool();
     // TODO: This may need to be a Handler(looper)
     private final ScheduledExecutorService mScheduledExecutorService =
             Executors.newSingleThreadScheduledExecutor();
+    private final AppOpsManager mAppOpsManager;
+    private final HotwordAudioStreamCopier mHotwordAudioStreamCopier;
     @Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
     private final AtomicBoolean mUpdateStateAfterStartFinished = new AtomicBoolean(false);
     private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
@@ -196,7 +205,7 @@
     @Nullable AttentionManagerInternal mAttentionManagerInternal = null;
 
     final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
-            this::setProximityMeters;
+            this::setProximityValue;
 
 
     volatile HotwordDetectionServiceIdentity mIdentity;
@@ -235,6 +244,10 @@
         mContext = context;
         mVoiceInteractionServiceUid = voiceInteractionServiceUid;
         mVoiceInteractorIdentity = voiceInteractorIdentity;
+        mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+        mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, detectorType,
+                mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+                mVoiceInteractorIdentity.attributionTag);
         mDetectionComponentName = serviceName;
         mUser = userId;
         mCallback = callback;
@@ -336,11 +349,10 @@
                         HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
                                 initResultMetricsResult);
                     } catch (RemoteException e) {
-                        // TODO: Add a new atom for RemoteException case, the error doesn't very
-                        // correct here
                         Slog.w(TAG, "Failed to report initialization status: " + e);
-                        HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
-                                METRICS_INIT_CALLBACK_STATE_ERROR);
+                        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                                METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
+                                mVoiceInteractionServiceUid);
                     }
                 }
             };
@@ -352,6 +364,9 @@
             } catch (RemoteException e) {
                 // TODO: (b/181842909) Report an error to voice interactor
                 Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
+                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                        HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION,
+                        mVoiceInteractionServiceUid);
             }
             return future.orTimeout(MAX_UPDATE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         }).whenComplete((res, err) -> {
@@ -366,8 +381,9 @@
                             METRICS_INIT_UNKNOWN_TIMEOUT);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed to report initialization status UNKNOWN", e);
-                    HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
-                            METRICS_INIT_CALLBACK_STATE_ERROR);
+                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                            METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
+                            mVoiceInteractionServiceUid);
                 }
             } else if (err != null) {
                 Slog.w(TAG, "Failed to update state: " + err);
@@ -488,14 +504,20 @@
                         mSoftwareCallback.onError();
                         return;
                     }
-                    saveProximityMetersToBundle(result);
-                    mSoftwareCallback.onDetected(result, null, null);
-                    if (result != null) {
-                        Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
-                                + " bits from hotword trusted process");
-                        if (mDebugHotwordLogging) {
-                            Slog.i(TAG, "Egressed detected result: " + result);
-                        }
+                    saveProximityValueToBundle(result);
+                    HotwordDetectedResult newResult;
+                    try {
+                        newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
+                    } catch (IOException e) {
+                        // TODO: Write event
+                        mSoftwareCallback.onError();
+                        return;
+                    }
+                    mSoftwareCallback.onDetected(newResult, null, null);
+                    Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+                            + " bits from hotword trusted process");
+                    if (mDebugHotwordLogging) {
+                        Slog.i(TAG, "Egressed detected result: " + newResult);
                     }
                 }
             }
@@ -574,7 +596,7 @@
         detectFromDspSource(event, callback);
     }
 
-    private void detectFromDspSource(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
+    void detectFromDspSource(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
             IHotwordRecognitionStatusCallback externalCallback) {
         if (DEBUG) {
             Slog.d(TAG, "detectFromDspSource");
@@ -610,20 +632,27 @@
                         enforcePermissionsForDataDelivery();
                         enforceExtraKeyphraseIdNotLeaked(result, recognitionEvent);
                     } catch (SecurityException e) {
+                        Slog.i(TAG, "Ignoring #onDetected due to a SecurityException", e);
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 mDetectorType,
                                 METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
                         externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
                         return;
                     }
-                    saveProximityMetersToBundle(result);
-                    externalCallback.onKeyphraseDetected(recognitionEvent, result);
-                    if (result != null) {
-                        Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
-                                + " bits from hotword trusted process");
-                        if (mDebugHotwordLogging) {
-                            Slog.i(TAG, "Egressed detected result: " + result);
-                        }
+                    saveProximityValueToBundle(result);
+                    HotwordDetectedResult newResult;
+                    try {
+                        newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
+                    } catch (IOException e) {
+                        // TODO: Write event
+                        externalCallback.onError(CALLBACK_ONDETECTED_STREAM_COPY_ERROR);
+                        return;
+                    }
+                    externalCallback.onKeyphraseDetected(recognitionEvent, newResult);
+                    Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+                            + " bits from hotword trusted process");
+                    if (mDebugHotwordLogging) {
+                        Slog.i(TAG, "Egressed detected result: " + newResult);
                     }
                 }
             }
@@ -678,6 +707,9 @@
                                 externalCallback.onError(CALLBACK_DETECT_TIMEOUT);
                             } catch (RemoteException e) {
                                 Slog.w(TAG, "Failed to report onError status: ", e);
+                                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                                        HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+                                        mVoiceInteractionServiceUid);
                             }
                         },
                         MAX_VALIDATION_TIMEOUT_MILLIS,
@@ -722,6 +754,7 @@
     }
 
     private void restartProcessLocked() {
+        // TODO(b/244598068): Check HotwordAudioStreamManager first
         Slog.v(TAG, "Restarting hotword detection process");
         ServiceConnection oldConnection = mRemoteHotwordDetectionService;
         HotwordDetectionServiceIdentity previousIdentity = mIdentity;
@@ -737,6 +770,9 @@
                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to call #rejected");
+                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                        HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION,
+                        mVoiceInteractionServiceUid);
             }
             mValidatingDspTrigger = false;
         }
@@ -752,6 +788,9 @@
             mCallback.onProcessRestarted();
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to communicate #onProcessRestarted", e);
+            HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                    HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION,
+                    mVoiceInteractionServiceUid);
         }
 
         // Restart listening from microphone if the hotword process has been restarted.
@@ -891,6 +930,9 @@
                     callback.onError();
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to report onError status: " + ex);
+                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+                            mVoiceInteractionServiceUid);
                 }
             } finally {
                 synchronized (mLock) {
@@ -956,16 +998,24 @@
                                         callback.onError();
                                         return;
                                     }
-                                    callback.onDetected(triggerResult, null /* audioFormat */,
+                                    HotwordDetectedResult newResult;
+                                    try {
+                                        newResult =
+                                                mHotwordAudioStreamCopier.startCopyingAudioStreams(
+                                                        triggerResult);
+                                    } catch (IOException e) {
+                                        // TODO: Write event
+                                        callback.onError();
+                                        return;
+                                    }
+                                    callback.onDetected(newResult, null /* audioFormat */,
                                             null /* audioStream */);
-                                    if (triggerResult != null) {
-                                        Slog.i(TAG, "Egressed "
-                                                + HotwordDetectedResult.getUsageSize(triggerResult)
-                                                + " bits from hotword trusted process");
-                                        if (mDebugHotwordLogging) {
-                                            Slog.i(TAG,
-                                                    "Egressed detected result: " + triggerResult);
-                                        }
+                                    Slog.i(TAG, "Egressed "
+                                            + HotwordDetectedResult.getUsageSize(newResult)
+                                            + " bits from hotword trusted process");
+                                    if (mDebugHotwordLogging) {
+                                        Slog.i(TAG,
+                                                "Egressed detected result: " + newResult);
                                     }
                                 }
                             });
@@ -1070,6 +1120,9 @@
                     mCallback.onError(HOTWORD_DETECTION_SERVICE_DIED);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed to report onError status: " + e);
+                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+                            mVoiceInteractionServiceUid);
                 }
             }
         }
@@ -1189,15 +1242,15 @@
         });
     }
 
-    private void saveProximityMetersToBundle(HotwordDetectedResult result) {
+    private void saveProximityValueToBundle(HotwordDetectedResult result) {
         synchronized (mLock) {
             if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) {
-                result.getExtras().putDouble(EXTRA_PROXIMITY_METERS, mProximityMeters);
+                result.setProximity(mProximityMeters);
             }
         }
     }
 
-    private void setProximityMeters(double proximityMeters) {
+    private void setProximityValue(double proximityMeters) {
         synchronized (mLock) {
             mProximityMeters = proximityMeters;
         }
@@ -1224,7 +1277,7 @@
         Binder.withCleanCallingIdentity(() -> {
             enforcePermissionForPreflight(mContext, mVoiceInteractorIdentity, RECORD_AUDIO);
             int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
-            mContext.getSystemService(AppOpsManager.class).noteOpNoThrow(hotwordOp,
+            mAppOpsManager.noteOpNoThrow(hotwordOp,
                     mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
                     mVoiceInteractorIdentity.attributionTag, OP_MESSAGE);
             enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
@@ -1269,4 +1322,4 @@
 
     private static final String OP_MESSAGE =
             "Providing hotword detection result to VoiceInteractionService";
-};
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java
new file mode 100644
index 0000000..02f5889
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java
@@ -0,0 +1,1325 @@
+/*
+ * 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.voiceinteraction;
+
+import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
+import static android.Manifest.permission.RECORD_AUDIO;
+import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
+import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
+import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
+import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
+import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS;
+import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN;
+import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS;
+
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_NO_VALUE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_OVER_MAX_CUSTOM_VALUE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_EXTERNAL_SOURCE_DETECTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_SOFTWARE_DETECTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_SECURITY_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
+import static com.android.server.voiceinteraction.SoundTriggerSessionPermissionsDecorator.enforcePermissionForPreflight;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.attention.AttentionManagerInternal;
+import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
+import android.content.Context;
+import android.content.Intent;
+import android.content.PermissionChecker;
+import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.AudioFormat;
+import android.media.AudioManagerInternal;
+import android.media.permission.Identity;
+import android.media.permission.PermissionUtil;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SharedMemory;
+import android.provider.DeviceConfig;
+import android.service.voice.HotwordDetectedResult;
+import android.service.voice.HotwordDetectionService;
+import android.service.voice.HotwordDetector;
+import android.service.voice.HotwordRejectedResult;
+import android.service.voice.IDspHotwordDetectionCallback;
+import android.service.voice.IHotwordDetectionService;
+import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
+import android.speech.IRecognitionServiceManager;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.util.Slog;
+import android.view.contentcapture.IContentCaptureManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+import com.android.server.LocalServices;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
+
+/**
+ * A class that provides the communication with the HotwordDetectionService.
+ */
+final class TrustedHotwordDetectorSession {
+    private static final String TAG = "TrustedHotwordDetectorSession";
+    static final boolean DEBUG = false;
+
+    private static final String KEY_RESTART_PERIOD_IN_SECONDS = "restart_period_in_seconds";
+    // TODO: These constants need to be refined.
+    // The validation timeout value is 3 seconds for onDetect of DSP trigger event.
+    private static final long VALIDATION_TIMEOUT_MILLIS = 3000;
+    // Write the onDetect timeout metric when it takes more time than MAX_VALIDATION_TIMEOUT_MILLIS.
+    private static final long MAX_VALIDATION_TIMEOUT_MILLIS = 4000;
+    private static final long MAX_UPDATE_TIMEOUT_MILLIS = 30000;
+    private static final long EXTERNAL_HOTWORD_CLEANUP_MILLIS = 2000;
+    private static final Duration MAX_UPDATE_TIMEOUT_DURATION =
+            Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS);
+    private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
+    private static final int MAX_ISOLATED_PROCESS_NUMBER = 10;
+
+    // The error codes are used for onError callback
+    private static final int HOTWORD_DETECTION_SERVICE_DIED = -1;
+    private static final int CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION = -2;
+    private static final int CALLBACK_DETECT_TIMEOUT = -3;
+    private static final int CALLBACK_ONDETECTED_STREAM_COPY_ERROR = -4;
+
+    // Hotword metrics
+    private static final int METRICS_INIT_UNKNOWN_TIMEOUT =
+            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT;
+    private static final int METRICS_INIT_UNKNOWN_NO_VALUE =
+            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_NO_VALUE;
+    private static final int METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE =
+            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_OVER_MAX_CUSTOM_VALUE;
+    private static final int METRICS_INIT_CALLBACK_STATE_ERROR =
+            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR;
+    private static final int METRICS_INIT_CALLBACK_STATE_SUCCESS =
+            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS;
+
+    private static final int METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION =
+            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_SECURITY_EXCEPTION;
+    private static final int METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK =
+            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK;
+    private static final int METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK =
+            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
+
+    private static final int METRICS_EXTERNAL_SOURCE_DETECTED =
+            HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED;
+    private static final int METRICS_EXTERNAL_SOURCE_REJECTED =
+            HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
+    private static final int METRICS_EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION =
+            HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
+    private static final int METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION =
+            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION;
+
+    private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool();
+    // TODO: This may need to be a Handler(looper)
+    private final ScheduledExecutorService mScheduledExecutorService =
+            Executors.newSingleThreadScheduledExecutor();
+    private final AppOpsManager mAppOpsManager;
+    private final HotwordAudioStreamCopier mHotwordAudioStreamCopier;
+    @Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
+    private final AtomicBoolean mUpdateStateAfterStartFinished = new AtomicBoolean(false);
+    private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
+    private final @NonNull ServiceConnectionFactory mServiceConnectionFactory;
+    private final IHotwordRecognitionStatusCallback mCallback;
+    private final int mDetectorType;
+    /**
+     * Time after which each HotwordDetectionService process is stopped and replaced by a new one.
+     * 0 indicates no restarts.
+     */
+    private final int mReStartPeriodSeconds;
+
+    final Object mLock;
+    final int mVoiceInteractionServiceUid;
+    final ComponentName mDetectionComponentName;
+    final int mUser;
+    final Context mContext;
+
+    @Nullable AttentionManagerInternal mAttentionManagerInternal = null;
+
+    final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
+            this::setProximityValue;
+
+
+    volatile HotwordDetectionServiceIdentity mIdentity;
+    private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback;
+    private Instant mLastRestartInstant;
+
+    private ScheduledFuture<?> mCancellationKeyPhraseDetectionFuture;
+    private ScheduledFuture<?> mDebugHotwordLoggingTimeoutFuture = null;
+
+    /** Identity used for attributing app ops when delivering data to the Interactor. */
+    @GuardedBy("mLock")
+    @Nullable
+    private final Identity mVoiceInteractorIdentity;
+    @GuardedBy("mLock")
+    private ParcelFileDescriptor mCurrentAudioSink;
+    @GuardedBy("mLock")
+    private boolean mValidatingDspTrigger = false;
+    @GuardedBy("mLock")
+    private boolean mPerformingSoftwareHotwordDetection;
+    private @NonNull ServiceConnection mRemoteHotwordDetectionService;
+    private IBinder mAudioFlinger;
+    private boolean mDebugHotwordLogging = false;
+    @GuardedBy("mLock")
+    private double mProximityMeters = PROXIMITY_UNKNOWN;
+
+    TrustedHotwordDetectorSession(Object lock, Context context, int voiceInteractionServiceUid,
+            Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
+            boolean bindInstantServiceAllowed, @Nullable PersistableBundle options,
+            @Nullable SharedMemory sharedMemory,
+            @NonNull IHotwordRecognitionStatusCallback callback, int detectorType) {
+        if (callback == null) {
+            Slog.w(TAG, "Callback is null while creating connection");
+            throw new IllegalArgumentException("Callback is null while creating connection");
+        }
+        mLock = lock;
+        mContext = context;
+        mVoiceInteractionServiceUid = voiceInteractionServiceUid;
+        mVoiceInteractorIdentity = voiceInteractorIdentity;
+        mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+        mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, detectorType,
+                mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+                mVoiceInteractorIdentity.attributionTag);
+        mDetectionComponentName = serviceName;
+        mUser = userId;
+        mCallback = callback;
+        mDetectorType = detectorType;
+        mReStartPeriodSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
+                KEY_RESTART_PERIOD_IN_SECONDS, 0);
+        final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE);
+        intent.setComponent(mDetectionComponentName);
+        initAudioFlingerLocked();
+
+        mServiceConnectionFactory = new ServiceConnectionFactory(intent, bindInstantServiceAllowed);
+
+        mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
+        if (ENABLE_PROXIMITY_RESULT) {
+            mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class);
+            if (mAttentionManagerInternal != null) {
+                mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal);
+            }
+        }
+
+        mLastRestartInstant = Instant.now();
+        updateStateAfterProcessStart(options, sharedMemory);
+
+        if (mReStartPeriodSeconds <= 0) {
+            mCancellationTaskFuture = null;
+        } else {
+            // TODO: we need to be smarter here, e.g. schedule it a bit more often,
+            //  but wait until the current session is closed.
+            mCancellationTaskFuture = mScheduledExecutorService.scheduleAtFixedRate(() -> {
+                Slog.v(TAG, "Time to restart the process, TTL has passed");
+                synchronized (mLock) {
+                    restartProcessLocked();
+                    HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
+                            HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE);
+                }
+            }, mReStartPeriodSeconds, mReStartPeriodSeconds, TimeUnit.SECONDS);
+        }
+    }
+
+    private void initAudioFlingerLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "initAudioFlingerLocked");
+        }
+        mAudioFlinger = ServiceManager.waitForService("media.audio_flinger");
+        if (mAudioFlinger == null) {
+            throw new IllegalStateException("Service media.audio_flinger wasn't found.");
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "Obtained audio_flinger binder.");
+        }
+        try {
+            mAudioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Audio server died before we registered a DeathRecipient; retrying init.",
+                    e);
+            initAudioFlingerLocked();
+        }
+    }
+
+    private void audioServerDied() {
+        Slog.w(TAG, "Audio server died; restarting the HotwordDetectionService.");
+        synchronized (mLock) {
+            // TODO: Check if this needs to be scheduled on a different thread.
+            initAudioFlingerLocked();
+            // We restart the process instead of simply sending over the new binder, to avoid race
+            // conditions with audio reading in the service.
+            restartProcessLocked();
+            HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
+                    HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED);
+        }
+    }
+
+    private void updateStateAfterProcessStart(
+            PersistableBundle options, SharedMemory sharedMemory) {
+        if (DEBUG) {
+            Slog.d(TAG, "updateStateAfterProcessStart");
+        }
+        mRemoteHotwordDetectionService.postAsync(service -> {
+            AndroidFuture<Void> future = new AndroidFuture<>();
+            IRemoteCallback statusCallback = new IRemoteCallback.Stub() {
+                @Override
+                public void sendResult(Bundle bundle) throws RemoteException {
+                    if (DEBUG) {
+                        Slog.d(TAG, "updateState finish");
+                    }
+                    future.complete(null);
+                    if (mUpdateStateAfterStartFinished.getAndSet(true)) {
+                        Slog.w(TAG, "call callback after timeout");
+                        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                                HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT,
+                                mVoiceInteractionServiceUid);
+                        return;
+                    }
+                    Pair<Integer, Integer> statusResultPair = getInitStatusAndMetricsResult(bundle);
+                    int status = statusResultPair.first;
+                    int initResultMetricsResult = statusResultPair.second;
+                    try {
+                        mCallback.onStatusReported(status);
+                        HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
+                                initResultMetricsResult);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failed to report initialization status: " + e);
+                        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                                METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
+                                mVoiceInteractionServiceUid);
+                    }
+                }
+            };
+            try {
+                service.updateState(options, sharedMemory, statusCallback);
+                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                        HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE,
+                        mVoiceInteractionServiceUid);
+            } catch (RemoteException e) {
+                // TODO: (b/181842909) Report an error to voice interactor
+                Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
+                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                        HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION,
+                        mVoiceInteractionServiceUid);
+            }
+            return future.orTimeout(MAX_UPDATE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        }).whenComplete((res, err) -> {
+            if (err instanceof TimeoutException) {
+                Slog.w(TAG, "updateState timed out");
+                if (mUpdateStateAfterStartFinished.getAndSet(true)) {
+                    return;
+                }
+                try {
+                    mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN);
+                    HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
+                            METRICS_INIT_UNKNOWN_TIMEOUT);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to report initialization status UNKNOWN", e);
+                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                            METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
+                            mVoiceInteractionServiceUid);
+                }
+            } else if (err != null) {
+                Slog.w(TAG, "Failed to update state: " + err);
+            } else {
+                // NOTE: so far we don't need to take any action.
+            }
+        });
+    }
+
+    private static Pair<Integer, Integer> getInitStatusAndMetricsResult(Bundle bundle) {
+        if (bundle == null) {
+            return new Pair<>(INITIALIZATION_STATUS_UNKNOWN, METRICS_INIT_UNKNOWN_NO_VALUE);
+        }
+        int status = bundle.getInt(KEY_INITIALIZATION_STATUS, INITIALIZATION_STATUS_UNKNOWN);
+        if (status > HotwordDetectionService.getMaxCustomInitializationStatus()) {
+            return new Pair<>(INITIALIZATION_STATUS_UNKNOWN,
+                    status == INITIALIZATION_STATUS_UNKNOWN
+                    ? METRICS_INIT_UNKNOWN_NO_VALUE
+                    : METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE);
+        }
+        // TODO: should guard against negative here
+        int metricsResult = status == INITIALIZATION_STATUS_SUCCESS
+                ? METRICS_INIT_CALLBACK_STATE_SUCCESS
+                : METRICS_INIT_CALLBACK_STATE_ERROR;
+        return new Pair<>(status, metricsResult);
+    }
+
+    private boolean isBound() {
+        synchronized (mLock) {
+            return mRemoteHotwordDetectionService.isBound();
+        }
+    }
+
+    void cancelLocked() {
+        Slog.v(TAG, "cancelLocked");
+        clearDebugHotwordLoggingTimeoutLocked();
+        mDebugHotwordLogging = false;
+        mRemoteHotwordDetectionService.unbind();
+        LocalServices.getService(PermissionManagerServiceInternal.class)
+                .setHotwordDetectionServiceProvider(null);
+        if (mIdentity != null) {
+            removeServiceUidForAudioPolicy(mIdentity.getIsolatedUid());
+        }
+        mIdentity = null;
+        if (mCancellationTaskFuture != null) {
+            mCancellationTaskFuture.cancel(/* may interrupt */ true);
+        }
+        if (mAudioFlinger != null) {
+            mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
+        }
+        if (mAttentionManagerInternal != null) {
+            mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal);
+        }
+    }
+
+    void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) {
+        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE,
+                mVoiceInteractionServiceUid);
+
+        // Prevent doing the init late, so restart is handled equally to a clean process start.
+        // TODO(b/191742511): this logic needs a test
+        if (!mUpdateStateAfterStartFinished.get()
+                && Instant.now().minus(MAX_UPDATE_TIMEOUT_DURATION).isBefore(mLastRestartInstant)) {
+            Slog.v(TAG, "call updateStateAfterProcessStart");
+            updateStateAfterProcessStart(options, sharedMemory);
+        } else {
+            mRemoteHotwordDetectionService.run(
+                    service -> service.updateState(options, sharedMemory, null /* callback */));
+        }
+    }
+
+    void startListeningFromMic(
+            AudioFormat audioFormat,
+            IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
+        if (DEBUG) {
+            Slog.d(TAG, "startListeningFromMic");
+        }
+        mSoftwareCallback = callback;
+
+        synchronized (mLock) {
+            if (mPerformingSoftwareHotwordDetection) {
+                Slog.i(TAG, "Hotword validation is already in progress, ignoring.");
+                return;
+            }
+            mPerformingSoftwareHotwordDetection = true;
+
+            startListeningFromMicLocked();
+        }
+    }
+
+    private void startListeningFromMicLocked() {
+        // TODO: consider making this a non-anonymous class.
+        IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
+            @Override
+            public void onDetected(HotwordDetectedResult result) throws RemoteException {
+                if (DEBUG) {
+                    Slog.d(TAG, "onDetected");
+                }
+                synchronized (mLock) {
+                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                            mDetectorType,
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
+                    if (!mPerformingSoftwareHotwordDetection) {
+                        Slog.i(TAG, "Hotword detection has already completed");
+                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                                mDetectorType,
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
+                        return;
+                    }
+                    mPerformingSoftwareHotwordDetection = false;
+                    try {
+                        enforcePermissionsForDataDelivery();
+                    } catch (SecurityException e) {
+                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                                mDetectorType,
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
+                        mSoftwareCallback.onError();
+                        return;
+                    }
+                    saveProximityValueToBundle(result);
+                    HotwordDetectedResult newResult;
+                    try {
+                        newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
+                    } catch (IOException e) {
+                        // TODO: Write event
+                        mSoftwareCallback.onError();
+                        return;
+                    }
+                    mSoftwareCallback.onDetected(newResult, null, null);
+                    Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+                            + " bits from hotword trusted process");
+                    if (mDebugHotwordLogging) {
+                        Slog.i(TAG, "Egressed detected result: " + newResult);
+                    }
+                }
+            }
+
+            @Override
+            public void onRejected(HotwordRejectedResult result) throws RemoteException {
+                if (DEBUG) {
+                    Slog.wtf(TAG, "onRejected");
+                }
+                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                        mDetectorType,
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
+                // onRejected isn't allowed here, and we are not expecting it.
+            }
+        };
+
+        mRemoteHotwordDetectionService.run(
+                service -> service.detectFromMicrophoneSource(
+                        null,
+                        AUDIO_SOURCE_MICROPHONE,
+                        null,
+                        null,
+                        internalCallback));
+        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                HOTWORD_DETECTOR_EVENTS__EVENT__START_SOFTWARE_DETECTION,
+                mVoiceInteractionServiceUid);
+    }
+
+    public void startListeningFromExternalSource(
+            ParcelFileDescriptor audioStream,
+            AudioFormat audioFormat,
+            @Nullable PersistableBundle options,
+            IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
+        if (DEBUG) {
+            Slog.d(TAG, "startListeningFromExternalSource");
+        }
+
+        handleExternalSourceHotwordDetection(
+                audioStream,
+                audioFormat,
+                options,
+                callback);
+    }
+
+    void stopListening() {
+        if (DEBUG) {
+            Slog.d(TAG, "stopListening");
+        }
+        synchronized (mLock) {
+            stopListeningLocked();
+        }
+    }
+
+    private void stopListeningLocked() {
+        if (!mPerformingSoftwareHotwordDetection) {
+            Slog.i(TAG, "Hotword detection is not running");
+            return;
+        }
+        mPerformingSoftwareHotwordDetection = false;
+
+        mRemoteHotwordDetectionService.run(IHotwordDetectionService::stopDetection);
+
+        if (mCurrentAudioSink != null) {
+            Slog.i(TAG, "Closing audio stream to hotword detector: stopping requested");
+            bestEffortClose(mCurrentAudioSink);
+        }
+        mCurrentAudioSink = null;
+    }
+
+    void triggerHardwareRecognitionEventForTestLocked(
+            SoundTrigger.KeyphraseRecognitionEvent event,
+            IHotwordRecognitionStatusCallback callback) {
+        if (DEBUG) {
+            Slog.d(TAG, "triggerHardwareRecognitionEventForTestLocked");
+        }
+        detectFromDspSource(event, callback);
+    }
+
+    private void detectFromDspSource(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
+            IHotwordRecognitionStatusCallback externalCallback) {
+        if (DEBUG) {
+            Slog.d(TAG, "detectFromDspSource");
+        }
+
+        AtomicBoolean timeoutDetected = new AtomicBoolean(false);
+        // TODO: consider making this a non-anonymous class.
+        IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
+            @Override
+            public void onDetected(HotwordDetectedResult result) throws RemoteException {
+                if (DEBUG) {
+                    Slog.d(TAG, "onDetected");
+                }
+                synchronized (mLock) {
+                    if (mCancellationKeyPhraseDetectionFuture != null) {
+                        mCancellationKeyPhraseDetectionFuture.cancel(true);
+                    }
+                    if (timeoutDetected.get()) {
+                        return;
+                    }
+                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                            mDetectorType,
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
+                    if (!mValidatingDspTrigger) {
+                        Slog.i(TAG, "Ignoring #onDetected due to a process restart");
+                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                                mDetectorType,
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
+                        return;
+                    }
+                    mValidatingDspTrigger = false;
+                    try {
+                        enforcePermissionsForDataDelivery();
+                        enforceExtraKeyphraseIdNotLeaked(result, recognitionEvent);
+                    } catch (SecurityException e) {
+                        Slog.i(TAG, "Ignoring #onDetected due to a SecurityException", e);
+                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                                mDetectorType,
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
+                        externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
+                        return;
+                    }
+                    saveProximityValueToBundle(result);
+                    HotwordDetectedResult newResult;
+                    try {
+                        newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
+                    } catch (IOException e) {
+                        // TODO: Write event
+                        externalCallback.onError(CALLBACK_ONDETECTED_STREAM_COPY_ERROR);
+                        return;
+                    }
+                    externalCallback.onKeyphraseDetected(recognitionEvent, newResult);
+                    Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+                            + " bits from hotword trusted process");
+                    if (mDebugHotwordLogging) {
+                        Slog.i(TAG, "Egressed detected result: " + newResult);
+                    }
+                }
+            }
+
+            @Override
+            public void onRejected(HotwordRejectedResult result) throws RemoteException {
+                if (DEBUG) {
+                    Slog.d(TAG, "onRejected");
+                }
+                synchronized (mLock) {
+                    if (mCancellationKeyPhraseDetectionFuture != null) {
+                        mCancellationKeyPhraseDetectionFuture.cancel(true);
+                    }
+                    if (timeoutDetected.get()) {
+                        return;
+                    }
+                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                            mDetectorType,
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
+                    if (!mValidatingDspTrigger) {
+                        Slog.i(TAG, "Ignoring #onRejected due to a process restart");
+                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                                mDetectorType,
+                                METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK);
+                        return;
+                    }
+                    mValidatingDspTrigger = false;
+                    externalCallback.onRejected(result);
+                    if (mDebugHotwordLogging && result != null) {
+                        Slog.i(TAG, "Egressed rejected result: " + result);
+                    }
+                }
+            }
+        };
+
+        synchronized (mLock) {
+            mValidatingDspTrigger = true;
+            mRemoteHotwordDetectionService.run(service -> {
+                // We use the VALIDATION_TIMEOUT_MILLIS to inform that the client needs to invoke
+                // the callback before timeout value. In order to reduce the latency impact between
+                // server side and client side, we need to use another timeout value
+                // MAX_VALIDATION_TIMEOUT_MILLIS to monitor it.
+                mCancellationKeyPhraseDetectionFuture = mScheduledExecutorService.schedule(
+                        () -> {
+                            // TODO: avoid allocate every time
+                            timeoutDetected.set(true);
+                            Slog.w(TAG, "Timed out on #detectFromDspSource");
+                            HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                                    mDetectorType,
+                                    HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT);
+                            try {
+                                externalCallback.onError(CALLBACK_DETECT_TIMEOUT);
+                            } catch (RemoteException e) {
+                                Slog.w(TAG, "Failed to report onError status: ", e);
+                                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                                        HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+                                        mVoiceInteractionServiceUid);
+                            }
+                        },
+                        MAX_VALIDATION_TIMEOUT_MILLIS,
+                        TimeUnit.MILLISECONDS);
+                service.detectFromDspSource(
+                        recognitionEvent,
+                        recognitionEvent.getCaptureFormat(),
+                        VALIDATION_TIMEOUT_MILLIS,
+                        internalCallback);
+            });
+        }
+    }
+
+    void forceRestart() {
+        Slog.v(TAG, "Requested to restart the service internally. Performing the restart");
+        synchronized (mLock) {
+            restartProcessLocked();
+        }
+    }
+
+    void setDebugHotwordLoggingLocked(boolean logging) {
+        Slog.v(TAG, "setDebugHotwordLoggingLocked: " + logging);
+        clearDebugHotwordLoggingTimeoutLocked();
+        mDebugHotwordLogging = logging;
+
+        if (logging) {
+            // Reset mDebugHotwordLogging to false after one hour
+            mDebugHotwordLoggingTimeoutFuture = mScheduledExecutorService.schedule(() -> {
+                Slog.v(TAG, "Timeout to reset mDebugHotwordLogging to false");
+                synchronized (mLock) {
+                    mDebugHotwordLogging = false;
+                }
+            }, RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    private void clearDebugHotwordLoggingTimeoutLocked() {
+        if (mDebugHotwordLoggingTimeoutFuture != null) {
+            mDebugHotwordLoggingTimeoutFuture.cancel(/* mayInterruptIfRunning= */true);
+            mDebugHotwordLoggingTimeoutFuture = null;
+        }
+    }
+
+    private void restartProcessLocked() {
+        // TODO(b/244598068): Check HotwordAudioStreamManager first
+        Slog.v(TAG, "Restarting hotword detection process");
+        ServiceConnection oldConnection = mRemoteHotwordDetectionService;
+        HotwordDetectionServiceIdentity previousIdentity = mIdentity;
+
+        // TODO(volnov): this can be done after connect() has been successful.
+        if (mValidatingDspTrigger) {
+            // We're restarting the process while it's processing a DSP trigger, so report a
+            // rejection. This also allows the Interactor to startReco again
+            try {
+                mCallback.onRejected(new HotwordRejectedResult.Builder().build());
+                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                        mDetectorType,
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to call #rejected");
+                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                        HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION,
+                        mVoiceInteractionServiceUid);
+            }
+            mValidatingDspTrigger = false;
+        }
+
+        mUpdateStateAfterStartFinished.set(false);
+        mLastRestartInstant = Instant.now();
+
+        // Recreate connection to reset the cache.
+        mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
+
+        Slog.v(TAG, "Started the new process, issuing #onProcessRestarted");
+        try {
+            mCallback.onProcessRestarted();
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to communicate #onProcessRestarted", e);
+            HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                    HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION,
+                    mVoiceInteractionServiceUid);
+        }
+
+        // Restart listening from microphone if the hotword process has been restarted.
+        if (mPerformingSoftwareHotwordDetection) {
+            Slog.i(TAG, "Process restarted: calling startRecognition() again");
+            startListeningFromMicLocked();
+        }
+
+        if (mCurrentAudioSink != null) {
+            Slog.i(TAG, "Closing external audio stream to hotword detector: process restarted");
+            bestEffortClose(mCurrentAudioSink);
+            mCurrentAudioSink = null;
+        }
+
+        if (DEBUG) {
+            Slog.i(TAG, "#onProcessRestarted called, unbinding from the old process");
+        }
+        oldConnection.ignoreConnectionStatusEvents();
+        oldConnection.unbind();
+        if (previousIdentity != null) {
+            removeServiceUidForAudioPolicy(previousIdentity.getIsolatedUid());
+        }
+    }
+
+    static final class SoundTriggerCallback extends IRecognitionStatusCallback.Stub {
+        private SoundTrigger.KeyphraseRecognitionEvent mRecognitionEvent;
+        private final HotwordDetectionConnection mHotwordDetectionConnection;
+        private final IHotwordRecognitionStatusCallback mExternalCallback;
+
+        SoundTriggerCallback(IHotwordRecognitionStatusCallback callback,
+                HotwordDetectionConnection connection) {
+            mHotwordDetectionConnection = connection;
+            mExternalCallback = callback;
+        }
+
+        @Override
+        public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent)
+                throws RemoteException {
+            if (DEBUG) {
+                Slog.d(TAG, "onKeyphraseDetected recognitionEvent : " + recognitionEvent);
+            }
+            final boolean useHotwordDetectionService = mHotwordDetectionConnection != null;
+            if (useHotwordDetectionService) {
+                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP,
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
+                mRecognitionEvent = recognitionEvent;
+                mHotwordDetectionConnection.detectFromDspSource(
+                        recognitionEvent, mExternalCallback);
+            } else {
+                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR,
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
+                mExternalCallback.onKeyphraseDetected(recognitionEvent, null);
+            }
+        }
+
+        @Override
+        public void onGenericSoundTriggerDetected(
+                SoundTrigger.GenericRecognitionEvent recognitionEvent)
+                throws RemoteException {
+            mExternalCallback.onGenericSoundTriggerDetected(recognitionEvent);
+        }
+
+        @Override
+        public void onError(int status) throws RemoteException {
+            mExternalCallback.onError(status);
+        }
+
+        @Override
+        public void onRecognitionPaused() throws RemoteException {
+            mExternalCallback.onRecognitionPaused();
+        }
+
+        @Override
+        public void onRecognitionResumed() throws RemoteException {
+            mExternalCallback.onRecognitionResumed();
+        }
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("mReStartPeriodSeconds="); pw.println(mReStartPeriodSeconds);
+        pw.print(prefix);
+        pw.print("mBound=" + mRemoteHotwordDetectionService.isBound());
+        pw.print(", mValidatingDspTrigger=" + mValidatingDspTrigger);
+        pw.print(", mPerformingSoftwareHotwordDetection=" + mPerformingSoftwareHotwordDetection);
+        pw.print(", mRestartCount=" + mServiceConnectionFactory.mRestartCount);
+        pw.print(", mLastRestartInstant=" + mLastRestartInstant);
+        pw.println(", mDetectorType=" + HotwordDetector.detectorTypeToString(mDetectorType));
+    }
+
+    private void handleExternalSourceHotwordDetection(
+            ParcelFileDescriptor audioStream,
+            AudioFormat audioFormat,
+            @Nullable PersistableBundle options,
+            IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
+        if (DEBUG) {
+            Slog.d(TAG, "#handleExternalSourceHotwordDetection");
+        }
+        InputStream audioSource = new ParcelFileDescriptor.AutoCloseInputStream(audioStream);
+
+        Pair<ParcelFileDescriptor, ParcelFileDescriptor> clientPipe = createPipe();
+        if (clientPipe == null) {
+            // TODO: Need to propagate as unknown error or something?
+            return;
+        }
+        ParcelFileDescriptor serviceAudioSink = clientPipe.second;
+        ParcelFileDescriptor serviceAudioSource = clientPipe.first;
+
+        synchronized (mLock) {
+            mCurrentAudioSink = serviceAudioSink;
+        }
+
+        mAudioCopyExecutor.execute(() -> {
+            try (InputStream source = audioSource;
+                 OutputStream fos =
+                         new ParcelFileDescriptor.AutoCloseOutputStream(serviceAudioSink)) {
+
+                byte[] buffer = new byte[1024];
+                while (true) {
+                    int bytesRead = source.read(buffer, 0, 1024);
+
+                    if (bytesRead < 0) {
+                        Slog.i(TAG, "Reached end of stream for external hotword");
+                        break;
+                    }
+
+                    // TODO: First write to ring buffer to make sure we don't lose data if the next
+                    // statement fails.
+                    // ringBuffer.append(buffer, bytesRead);
+                    fos.write(buffer, 0, bytesRead);
+                }
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed supplying audio data to validator", e);
+
+                try {
+                    callback.onError();
+                } catch (RemoteException ex) {
+                    Slog.w(TAG, "Failed to report onError status: " + ex);
+                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+                            mVoiceInteractionServiceUid);
+                }
+            } finally {
+                synchronized (mLock) {
+                    mCurrentAudioSink = null;
+                }
+            }
+        });
+
+        // TODO: handle cancellations well
+        // TODO: what if we cancelled and started a new one?
+        mRemoteHotwordDetectionService.run(
+                service -> {
+                    service.detectFromMicrophoneSource(
+                            serviceAudioSource,
+                            // TODO: consider making a proxy callback + copy of audio format
+                            AUDIO_SOURCE_EXTERNAL,
+                            audioFormat,
+                            options,
+                            new IDspHotwordDetectionCallback.Stub() {
+                                @Override
+                                public void onRejected(HotwordRejectedResult result)
+                                        throws RemoteException {
+                                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                                            METRICS_EXTERNAL_SOURCE_REJECTED,
+                                            mVoiceInteractionServiceUid);
+                                    mScheduledExecutorService.schedule(
+                                            () -> {
+                                                bestEffortClose(serviceAudioSink, audioSource);
+                                            },
+                                            EXTERNAL_HOTWORD_CLEANUP_MILLIS,
+                                            TimeUnit.MILLISECONDS);
+
+                                    callback.onRejected(result);
+
+                                    if (result != null) {
+                                        Slog.i(TAG, "Egressed 'hotword rejected result' "
+                                                + "from hotword trusted process");
+                                        if (mDebugHotwordLogging) {
+                                            Slog.i(TAG, "Egressed detected result: " + result);
+                                        }
+                                    }
+                                }
+
+                                @Override
+                                public void onDetected(HotwordDetectedResult triggerResult)
+                                        throws RemoteException {
+                                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                                            METRICS_EXTERNAL_SOURCE_DETECTED,
+                                            mVoiceInteractionServiceUid);
+                                    mScheduledExecutorService.schedule(
+                                            () -> {
+                                                bestEffortClose(serviceAudioSink, audioSource);
+                                            },
+                                            EXTERNAL_HOTWORD_CLEANUP_MILLIS,
+                                            TimeUnit.MILLISECONDS);
+
+                                    try {
+                                        enforcePermissionsForDataDelivery();
+                                    } catch (SecurityException e) {
+                                        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                                                METRICS_EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION,
+                                                mVoiceInteractionServiceUid);
+                                        callback.onError();
+                                        return;
+                                    }
+                                    HotwordDetectedResult newResult;
+                                    try {
+                                        newResult =
+                                                mHotwordAudioStreamCopier.startCopyingAudioStreams(
+                                                        triggerResult);
+                                    } catch (IOException e) {
+                                        // TODO: Write event
+                                        callback.onError();
+                                        return;
+                                    }
+                                    callback.onDetected(newResult, null /* audioFormat */,
+                                            null /* audioStream */);
+                                    Slog.i(TAG, "Egressed "
+                                            + HotwordDetectedResult.getUsageSize(newResult)
+                                            + " bits from hotword trusted process");
+                                    if (mDebugHotwordLogging) {
+                                        Slog.i(TAG,
+                                                "Egressed detected result: " + newResult);
+                                    }
+                                }
+                            });
+
+                    // A copy of this has been created and passed to the hotword validator
+                    bestEffortClose(serviceAudioSource);
+                });
+        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                HOTWORD_DETECTOR_EVENTS__EVENT__START_EXTERNAL_SOURCE_DETECTION,
+                mVoiceInteractionServiceUid);
+    }
+
+    private class ServiceConnectionFactory {
+        private final Intent mIntent;
+        private final int mBindingFlags;
+
+        private int mRestartCount = 0;
+
+        ServiceConnectionFactory(@NonNull Intent intent, boolean bindInstantServiceAllowed) {
+            mIntent = intent;
+            mBindingFlags = bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0;
+        }
+
+        ServiceConnection createLocked() {
+            ServiceConnection connection =
+                    new ServiceConnection(mContext, mIntent, mBindingFlags, mUser,
+                            IHotwordDetectionService.Stub::asInterface,
+                            mRestartCount++ % MAX_ISOLATED_PROCESS_NUMBER);
+            connection.connect();
+
+            updateAudioFlinger(connection, mAudioFlinger);
+            updateContentCaptureManager(connection);
+            updateSpeechService(connection);
+            updateServiceIdentity(connection);
+            return connection;
+        }
+    }
+
+    private class ServiceConnection extends ServiceConnector.Impl<IHotwordDetectionService> {
+        private final Object mLock = new Object();
+
+        private final Intent mIntent;
+        private final int mBindingFlags;
+        private final int mInstanceNumber;
+
+        private boolean mRespectServiceConnectionStatusChanged = true;
+        private boolean mIsBound = false;
+        private boolean mIsLoggedFirstConnect = false;
+
+        ServiceConnection(@NonNull Context context,
+                @NonNull Intent intent, int bindingFlags, int userId,
+                @Nullable Function<IBinder, IHotwordDetectionService> binderAsInterface,
+                int instanceNumber) {
+            super(context, intent, bindingFlags, userId, binderAsInterface);
+            this.mIntent = intent;
+            this.mBindingFlags = bindingFlags;
+            this.mInstanceNumber = instanceNumber;
+        }
+
+        @Override // from ServiceConnector.Impl
+        protected void onServiceConnectionStatusChanged(IHotwordDetectionService service,
+                boolean connected) {
+            if (DEBUG) {
+                Slog.d(TAG, "onServiceConnectionStatusChanged connected = " + connected);
+            }
+            synchronized (mLock) {
+                if (!mRespectServiceConnectionStatusChanged) {
+                    Slog.v(TAG, "Ignored onServiceConnectionStatusChanged event");
+                    return;
+                }
+                mIsBound = connected;
+
+                if (!connected) {
+                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                            HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED,
+                            mVoiceInteractionServiceUid);
+                } else if (!mIsLoggedFirstConnect) {
+                    mIsLoggedFirstConnect = true;
+                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                            HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED,
+                            mVoiceInteractionServiceUid);
+                }
+            }
+        }
+
+        @Override
+        protected long getAutoDisconnectTimeoutMs() {
+            return -1;
+        }
+
+        @Override
+        public void binderDied() {
+            super.binderDied();
+            synchronized (mLock) {
+                if (!mRespectServiceConnectionStatusChanged) {
+                    Slog.v(TAG, "Ignored #binderDied event");
+                    return;
+                }
+
+                Slog.w(TAG, "binderDied");
+                try {
+                    mCallback.onError(HOTWORD_DETECTION_SERVICE_DIED);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to report onError status: " + e);
+                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+                            mVoiceInteractionServiceUid);
+                }
+            }
+        }
+
+        @Override
+        protected boolean bindService(
+                @NonNull android.content.ServiceConnection serviceConnection) {
+            try {
+                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                        HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE,
+                        mVoiceInteractionServiceUid);
+                boolean bindResult = mContext.bindIsolatedService(
+                        mIntent,
+                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | mBindingFlags,
+                        "hotword_detector_" + mInstanceNumber,
+                        mExecutor,
+                        serviceConnection);
+                if (!bindResult) {
+                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                            HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
+                            mVoiceInteractionServiceUid);
+                }
+                return bindResult;
+            } catch (IllegalArgumentException e) {
+                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+                        HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
+                        mVoiceInteractionServiceUid);
+                Slog.wtf(TAG, "Can't bind to the hotword detection service!", e);
+                return false;
+            }
+        }
+
+        boolean isBound() {
+            synchronized (mLock) {
+                return mIsBound;
+            }
+        }
+
+        void ignoreConnectionStatusEvents() {
+            synchronized (mLock) {
+                mRespectServiceConnectionStatusChanged = false;
+            }
+        }
+    }
+
+    private static Pair<ParcelFileDescriptor, ParcelFileDescriptor> createPipe() {
+        ParcelFileDescriptor[] fileDescriptors;
+        try {
+            fileDescriptors = ParcelFileDescriptor.createPipe();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to create audio stream pipe", e);
+            return null;
+        }
+
+        return Pair.create(fileDescriptors[0], fileDescriptors[1]);
+    }
+
+    private static void updateAudioFlinger(ServiceConnection connection, IBinder audioFlinger) {
+        // TODO: Consider using a proxy that limits the exposed API surface.
+        connection.run(service -> service.updateAudioFlinger(audioFlinger));
+    }
+
+    private static void updateContentCaptureManager(ServiceConnection connection) {
+        IBinder b = ServiceManager
+                .getService(Context.CONTENT_CAPTURE_MANAGER_SERVICE);
+        IContentCaptureManager binderService = IContentCaptureManager.Stub.asInterface(b);
+        connection.run(
+                service -> service.updateContentCaptureManager(binderService,
+                        new ContentCaptureOptions(null)));
+    }
+
+    private static void updateSpeechService(ServiceConnection connection) {
+        IBinder b = ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE);
+        IRecognitionServiceManager binderService = IRecognitionServiceManager.Stub.asInterface(b);
+        connection.run(service -> {
+            service.updateRecognitionServiceManager(binderService);
+        });
+    }
+
+    private void updateServiceIdentity(ServiceConnection connection) {
+        connection.run(service -> service.ping(new IRemoteCallback.Stub() {
+            @Override
+            public void sendResult(Bundle bundle) throws RemoteException {
+                // TODO: Exit if the service has been unbound already (though there's a very low
+                // chance this happens).
+                if (DEBUG) {
+                    Slog.d(TAG, "updating hotword UID " + Binder.getCallingUid());
+                }
+                // TODO: Have the provider point to the current state stored in
+                // VoiceInteractionManagerServiceImpl.
+                final int uid = Binder.getCallingUid();
+                LocalServices.getService(PermissionManagerServiceInternal.class)
+                        .setHotwordDetectionServiceProvider(() -> uid);
+                mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid);
+                addServiceUidForAudioPolicy(uid);
+            }
+        }));
+    }
+
+    private void addServiceUidForAudioPolicy(int uid) {
+        mScheduledExecutorService.execute(() -> {
+            AudioManagerInternal audioManager =
+                    LocalServices.getService(AudioManagerInternal.class);
+            if (audioManager != null) {
+                audioManager.addAssistantServiceUid(uid);
+            }
+        });
+    }
+
+    private void removeServiceUidForAudioPolicy(int uid) {
+        mScheduledExecutorService.execute(() -> {
+            AudioManagerInternal audioManager =
+                    LocalServices.getService(AudioManagerInternal.class);
+            if (audioManager != null) {
+                audioManager.removeAssistantServiceUid(uid);
+            }
+        });
+    }
+
+    private void saveProximityValueToBundle(HotwordDetectedResult result) {
+        synchronized (mLock) {
+            if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) {
+                result.setProximity(mProximityMeters);
+            }
+        }
+    }
+
+    private void setProximityValue(double proximityMeters) {
+        synchronized (mLock) {
+            mProximityMeters = proximityMeters;
+        }
+    }
+
+    private static void bestEffortClose(Closeable... closeables) {
+        for (Closeable closeable : closeables) {
+            bestEffortClose(closeable);
+        }
+    }
+
+    private static void bestEffortClose(Closeable closeable) {
+        try {
+            closeable.close();
+        } catch (IOException e) {
+            if (DEBUG) {
+                Slog.w(TAG, "Failed closing", e);
+            }
+        }
+    }
+
+    // TODO: Share this code with SoundTriggerMiddlewarePermission.
+    private void enforcePermissionsForDataDelivery() {
+        Binder.withCleanCallingIdentity(() -> {
+            enforcePermissionForPreflight(mContext, mVoiceInteractorIdentity, RECORD_AUDIO);
+            int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
+            mAppOpsManager.noteOpNoThrow(hotwordOp,
+                    mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+                    mVoiceInteractorIdentity.attributionTag, OP_MESSAGE);
+            enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
+                    CAPTURE_AUDIO_HOTWORD, OP_MESSAGE);
+        });
+    }
+
+    /**
+     * Throws a {@link SecurityException} iff the given identity has given permission to receive
+     * data.
+     *
+     * @param context    A {@link Context}, used for permission checks.
+     * @param identity   The identity to check.
+     * @param permission The identifier of the permission we want to check.
+     * @param reason     The reason why we're requesting the permission, for auditing purposes.
+     */
+    private static void enforcePermissionForDataDelivery(@NonNull Context context,
+            @NonNull Identity identity,
+            @NonNull String permission, @NonNull String reason) {
+        final int status = PermissionUtil.checkPermissionForDataDelivery(context, identity,
+                permission, reason);
+        if (status != PermissionChecker.PERMISSION_GRANTED) {
+            throw new SecurityException(
+                    TextUtils.formatSimple("Failed to obtain permission %s for identity %s",
+                            permission,
+                            SoundTriggerSessionPermissionsDecorator.toString(identity)));
+        }
+    }
+
+    private static void enforceExtraKeyphraseIdNotLeaked(HotwordDetectedResult result,
+            SoundTrigger.KeyphraseRecognitionEvent recognitionEvent) {
+        // verify the phrase ID in HotwordDetectedResult is not exposing extra phrases
+        // the DSP did not detect
+        for (SoundTrigger.KeyphraseRecognitionExtra keyphrase : recognitionEvent.keyphraseExtras) {
+            if (keyphrase.getKeyphraseId() == result.getHotwordPhraseId()) {
+                return;
+            }
+        }
+        throw new SecurityException("Ignoring #onDetected due to trusted service "
+                + "sharing a keyphrase ID which the DSP did not detect");
+    }
+
+    private static final String OP_MESSAGE =
+            "Providing hotword detection result to VoiceInteractionService";
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 151ff80..7207e373 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1174,6 +1174,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
         public void setDisabled(boolean disabled) {
+            super.setDisabled_enforcePermission();
+
             synchronized (this) {
                 if (mTemporarilyDisabled == disabled) {
                     if (DEBUG) Slog.d(TAG, "setDisabled(): already " + disabled);
@@ -1244,6 +1246,8 @@
         public void updateState(
                 @Nullable PersistableBundle options,
                 @Nullable SharedMemory sharedMemory) {
+            super.updateState_enforcePermission();
+
             synchronized (this) {
                 enforceIsCurrentVoiceInteractionService();
 
@@ -1260,6 +1264,8 @@
                 @Nullable SharedMemory sharedMemory,
                 IHotwordRecognitionStatusCallback callback,
                 int detectorType) {
+            super.initAndVerifyDetector_enforcePermission();
+
             synchronized (this) {
                 enforceIsCurrentVoiceInteractionService();
 
@@ -1711,6 +1717,8 @@
                 @Nullable String attributionTag,
                 @Nullable IVoiceInteractionSessionShowCallback showCallback,
                 @Nullable IBinder activityToken) {
+            super.showSessionForActiveService_enforcePermission();
+
             if (DEBUG_USER) Slog.d(TAG, "showSessionForActiveService()");
 
             synchronized (this) {
@@ -1742,6 +1750,8 @@
         @Override
         public void hideCurrentSession() throws RemoteException {
 
+            super.hideCurrentSession_enforcePermission();
+
             if (mImpl == null) {
                 return;
             }
@@ -1762,6 +1772,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
         public void launchVoiceAssistFromKeyguard() {
+            super.launchVoiceAssistFromKeyguard_enforcePermission();
+
             synchronized (this) {
                 if (mImpl == null) {
                     Slog.w(TAG, "launchVoiceAssistFromKeyguard without running voice interaction"
@@ -1780,6 +1792,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
         public boolean isSessionRunning() {
+            super.isSessionRunning_enforcePermission();
+
             synchronized (this) {
                 return mImpl != null && mImpl.mActiveSession != null;
             }
@@ -1788,6 +1802,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
         public boolean activeServiceSupportsAssist() {
+            super.activeServiceSupportsAssist_enforcePermission();
+
             synchronized (this) {
                 return mImpl != null && mImpl.mInfo != null && mImpl.mInfo.getSupportsAssist();
             }
@@ -1796,6 +1812,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
         public boolean activeServiceSupportsLaunchFromKeyguard() throws RemoteException {
+            super.activeServiceSupportsLaunchFromKeyguard_enforcePermission();
+
             synchronized (this) {
                 return mImpl != null && mImpl.mInfo != null
                         && mImpl.mInfo.getSupportsLaunchFromKeyguard();
@@ -1805,6 +1823,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
         public void onLockscreenShown() {
+            super.onLockscreenShown_enforcePermission();
+
             synchronized (this) {
                 if (mImpl == null) {
                     return;
@@ -1828,6 +1848,8 @@
         @Override
         public void registerVoiceInteractionSessionListener(
                 IVoiceInteractionSessionListener listener) {
+            super.registerVoiceInteractionSessionListener_enforcePermission();
+
             synchronized (this) {
                 mVoiceInteractionSessionListeners.register(listener);
             }
@@ -1837,6 +1859,8 @@
         @Override
         public void getActiveServiceSupportedActions(List<String> voiceActions,
                 IVoiceActionCheckCallback callback) {
+            super.getActiveServiceSupportedActions_enforcePermission();
+
             synchronized (this) {
                 if (mImpl == null) {
                     try {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 5d1901d..0a660b0 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -639,7 +639,7 @@
     private void logDetectorCreateEventIfNeeded(IHotwordRecognitionStatusCallback callback,
             int detectorType, boolean isCreated, int voiceInteractionServiceUid) {
         if (callback != null) {
-            HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, true,
+            HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, isCreated,
                     voiceInteractionServiceUid);
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index f8bc499..763024f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -913,4 +913,4 @@
             }
         }
     };
-};
+}
diff --git a/startop/view_compiler/TEST_MAPPING b/startop/view_compiler/TEST_MAPPING
deleted file mode 100644
index 791e471..0000000
--- a/startop/view_compiler/TEST_MAPPING
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "dex-builder-test"
-    },
-    {
-      "name": "CtsViewTestCases",
-      "options": [
-        {
-          "include-filter": "android.view.cts.PrecompiledLayoutTest"
-        }
-      ]
-    }
-  ]
-}
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc
index 5cb0c17..1d3b6481 100644
--- a/startop/view_compiler/apk_layout_compiler.cc
+++ b/startop/view_compiler/apk_layout_compiler.cc
@@ -100,56 +100,60 @@
       dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))};
   std::vector<dex::MethodBuilder> methods;
 
-  assets->GetAssetsProvider()->ForEachFile("res/", [&](const android::StringPiece& s,
-                                                       android::FileType) {
-    if (s == "layout") {
-      auto path = StringPrintf("res/%s/", s.to_string().c_str());
-      assets->GetAssetsProvider()->ForEachFile(path, [&](const android::StringPiece& layout_file,
-                                                         android::FileType) {
-        auto layout_path = StringPrintf("%s%s", path.c_str(), layout_file.to_string().c_str());
-        android::ApkAssetsCookie cookie = android::kInvalidCookie;
-        auto asset = resources.OpenNonAsset(layout_path, android::Asset::ACCESS_RANDOM, &cookie);
-        CHECK(asset);
-        CHECK(android::kInvalidCookie != cookie);
-        const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
-        CHECK(nullptr != dynamic_ref_table);
-        android::ResXMLTree xml_tree{dynamic_ref_table};
-        xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true),
-                       asset->getLength(),
-                       /*copy_data=*/true);
-        android::ResXMLParser parser{xml_tree};
-        parser.restart();
-        if (CanCompileLayout(&parser)) {
-          parser.restart();
-          const std::string layout_name = startop::util::FindLayoutNameFromFilename(layout_path);
-          ResXmlVisitorAdapter adapter{&parser};
-          switch (target) {
-            case CompilationTarget::kDex: {
-              methods.push_back(compiled_view.CreateMethod(
-                  layout_name,
-                  dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"),
-                                 dex::TypeDescriptor::FromClassname("android.content.Context"),
-                                 dex::TypeDescriptor::Int()}));
-              DexViewBuilder builder(&methods.back());
-              builder.Start();
-              LayoutCompilerVisitor visitor{&builder};
-              adapter.Accept(&visitor);
-              builder.Finish();
-              methods.back().Encode();
-              break;
-            }
-            case CompilationTarget::kJavaLanguage: {
-              JavaLangViewBuilder builder{package_name, layout_name, target_out};
-              builder.Start();
-              LayoutCompilerVisitor visitor{&builder};
-              adapter.Accept(&visitor);
-              builder.Finish();
-              break;
-            }
-          }
-        }
-      });
-    }
+  assets->GetAssetsProvider()->ForEachFile("res/", [&](android::StringPiece s, android::FileType) {
+      if (s == "layout") {
+          auto path = StringPrintf("res/%.*s/", (int)s.size(), s.data());
+          assets->GetAssetsProvider()
+                  ->ForEachFile(path, [&](android::StringPiece layout_file, android::FileType) {
+                      auto layout_path = StringPrintf("%s%.*s", path.c_str(),
+                                                      (int)layout_file.size(), layout_file.data());
+                      android::ApkAssetsCookie cookie = android::kInvalidCookie;
+                      auto asset = resources.OpenNonAsset(layout_path,
+                                                          android::Asset::ACCESS_RANDOM, &cookie);
+                      CHECK(asset);
+                      CHECK(android::kInvalidCookie != cookie);
+                      const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
+                      CHECK(nullptr != dynamic_ref_table);
+                      android::ResXMLTree xml_tree{dynamic_ref_table};
+                      xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true), asset->getLength(),
+                                     /*copy_data=*/true);
+                      android::ResXMLParser parser{xml_tree};
+                      parser.restart();
+                      if (CanCompileLayout(&parser)) {
+                          parser.restart();
+                          const std::string layout_name =
+                                  startop::util::FindLayoutNameFromFilename(layout_path);
+                          ResXmlVisitorAdapter adapter{&parser};
+                          switch (target) {
+                              case CompilationTarget::kDex: {
+                                  methods.push_back(compiled_view.CreateMethod(
+                                          layout_name,
+                                          dex::Prototype{dex::TypeDescriptor::FromClassname(
+                                                                 "android.view.View"),
+                                                         dex::TypeDescriptor::FromClassname(
+                                                                 "android.content.Context"),
+                                                         dex::TypeDescriptor::Int()}));
+                                  DexViewBuilder builder(&methods.back());
+                                  builder.Start();
+                                  LayoutCompilerVisitor visitor{&builder};
+                                  adapter.Accept(&visitor);
+                                  builder.Finish();
+                                  methods.back().Encode();
+                                  break;
+                              }
+                              case CompilationTarget::kJavaLanguage: {
+                                  JavaLangViewBuilder builder{package_name, layout_name,
+                                                              target_out};
+                                  builder.Start();
+                                  LayoutCompilerVisitor visitor{&builder};
+                                  adapter.Accept(&visitor);
+                                  builder.Finish();
+                                  break;
+                              }
+                          }
+                      }
+                  });
+      }
   });
 
   if (target == CompilationTarget::kDex) {
diff --git a/telecomm/OWNERS b/telecomm/OWNERS
index eb0c432..dcaf858 100644
--- a/telecomm/OWNERS
+++ b/telecomm/OWNERS
@@ -4,3 +4,7 @@
 tgunn@google.com
 xiaotonj@google.com
 rgreenwalt@google.com
+chinmayd@google.com
+grantmenke@google.com
+pmadapurmath@google.com
+tjstuart@google.com
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 432af3a..bbdc890 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -168,6 +168,18 @@
     public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
 
     /**
+     * Extra key intended for {@link InCallService}s that notify the user of an incoming call. When
+     * EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB returns true, the {@link InCallService} should not
+     * interrupt the user of the incoming call because the call is being suppressed by Do Not
+     * Disturb settings.
+     *
+     * This extra will be removed from the {@link Call} object for {@link InCallService}s that do
+     * not hold the {@link android.Manifest.permission#READ_CONTACTS} permission.
+     */
+    public static final String EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB =
+            "android.telecom.extra.IS_SUPPRESSED_BY_DO_NOT_DISTURB";
+
+    /**
      * Key for extra used to pass along a list of {@link PhoneAccountSuggestion}s to the in-call
      * UI when a call enters the {@link #STATE_SELECT_PHONE_ACCOUNT} state. The list included here
      * will have the same length and be in the same order as the list passed with
@@ -726,6 +738,7 @@
         private final String mContactDisplayName;
         private final @CallDirection int mCallDirection;
         private final @Connection.VerificationStatus int mCallerNumberVerificationStatus;
+        private final Uri mContactPhotoUri;
 
         /**
          * Whether the supplied capabilities  supports the specified capability.
@@ -933,6 +946,17 @@
         }
 
         /**
+         * @return The contact photo URI which corresponds to
+         * {@link android.provider.ContactsContract.PhoneLookup#PHOTO_URI}, or {@code null} if the
+         * lookup is not yet complete, if there's no contacts entry for the caller,
+         * or if the {@link InCallService} does not hold the
+         * {@link android.Manifest.permission#READ_CONTACTS} permission.
+         */
+        public @Nullable Uri getContactPhotoUri() {
+            return mContactPhotoUri;
+        }
+
+        /**
          * The display name for the caller.
          * <p>
          * This is the name as reported by the {@link ConnectionService} associated with this call.
@@ -1131,7 +1155,8 @@
                         Objects.equals(mContactDisplayName, d.mContactDisplayName) &&
                         Objects.equals(mCallDirection, d.mCallDirection) &&
                         Objects.equals(mCallerNumberVerificationStatus,
-                                d.mCallerNumberVerificationStatus);
+                                d.mCallerNumberVerificationStatus) &&
+                        Objects.equals(mContactPhotoUri, d.mContactPhotoUri);
             }
             return false;
         }
@@ -1156,7 +1181,8 @@
                             mCreationTimeMillis,
                             mContactDisplayName,
                             mCallDirection,
-                            mCallerNumberVerificationStatus);
+                            mCallerNumberVerificationStatus,
+                    mContactPhotoUri);
         }
 
         /** {@hide} */
@@ -1180,7 +1206,8 @@
                 long creationTimeMillis,
                 String contactDisplayName,
                 int callDirection,
-                int callerNumberVerificationStatus) {
+                int callerNumberVerificationStatus,
+                Uri contactPhotoUri) {
             mState = state;
             mTelecomCallId = telecomCallId;
             mHandle = handle;
@@ -1201,6 +1228,7 @@
             mContactDisplayName = contactDisplayName;
             mCallDirection = callDirection;
             mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+            mContactPhotoUri = contactPhotoUri;
         }
 
         /** {@hide} */
@@ -1225,7 +1253,9 @@
                     parcelableCall.getCreationTimeMillis(),
                     parcelableCall.getContactDisplayName(),
                     parcelableCall.getCallDirection(),
-                    parcelableCall.getCallerNumberVerificationStatus());
+                    parcelableCall.getCallerNumberVerificationStatus(),
+                    parcelableCall.getContactPhotoUri()
+            );
         }
 
         @Override
@@ -2632,7 +2662,8 @@
                         mDetails.getCreationTimeMillis(),
                         mDetails.getContactDisplayName(),
                         mDetails.getCallDirection(),
-                        mDetails.getCallerNumberVerificationStatus()
+                        mDetails.getCallerNumberVerificationStatus(),
+                        mDetails.getContactPhotoUri()
                         );
                 fireDetailsChanged(mDetails);
             }
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 7c736e3..568c8ab 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -66,6 +66,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -2531,11 +2532,21 @@
      */
     public final void setCallerDisplayName(String callerDisplayName, int presentation) {
         checkImmutable();
-        Log.d(this, "setCallerDisplayName %s", callerDisplayName);
-        mCallerDisplayName = callerDisplayName;
-        mCallerDisplayNamePresentation = presentation;
-        for (Listener l : mListeners) {
-            l.onCallerDisplayNameChanged(this, callerDisplayName, presentation);
+        boolean nameChanged = !Objects.equals(mCallerDisplayName, callerDisplayName);
+        boolean presentationChanged = mCallerDisplayNamePresentation != presentation;
+        if (nameChanged) {
+            // Ensure the new name is not clobbering the old one with a null value due to the caller
+            // wanting to only set the presentation and not knowing the display name.
+            mCallerDisplayName = callerDisplayName;
+        }
+        if (presentationChanged) {
+            mCallerDisplayNamePresentation = presentation;
+        }
+        if (nameChanged || presentationChanged) {
+            for (Listener l : mListeners) {
+                l.onCallerDisplayNameChanged(this, mCallerDisplayName,
+                        mCallerDisplayNamePresentation);
+            }
         }
     }
 
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index f412a18..6a13189 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -69,6 +69,7 @@
         private int mCallerNumberVerificationStatus;
         private String mContactDisplayName;
         private String mActiveChildCallId;
+        private Uri mContactPhotoUri;
 
         public ParcelableCallBuilder setId(String id) {
             mId = id;
@@ -224,6 +225,11 @@
             return this;
         }
 
+        public ParcelableCallBuilder setContactPhotoUri(Uri contactPhotoUri) {
+            mContactPhotoUri = contactPhotoUri;
+            return this;
+        }
+
         public ParcelableCall createParcelableCall() {
             return new ParcelableCall(
                     mId,
@@ -255,7 +261,8 @@
                     mCallDirection,
                     mCallerNumberVerificationStatus,
                     mContactDisplayName,
-                    mActiveChildCallId);
+                    mActiveChildCallId,
+                    mContactPhotoUri);
         }
 
         public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) {
@@ -292,6 +299,7 @@
                     parcelableCall.mCallerNumberVerificationStatus;
             newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName;
             newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId;
+            newBuilder.mContactPhotoUri = parcelableCall.mContactPhotoUri;
             return newBuilder;
         }
     }
@@ -327,6 +335,7 @@
     private final int mCallerNumberVerificationStatus;
     private final String mContactDisplayName;
     private final String mActiveChildCallId; // Only valid for CDMA conferences
+    private final Uri mContactPhotoUri;
 
     public ParcelableCall(
             String id,
@@ -358,7 +367,8 @@
             int callDirection,
             int callerNumberVerificationStatus,
             String contactDisplayName,
-            String activeChildCallId
+            String activeChildCallId,
+            Uri contactPhotoUri
     ) {
         mId = id;
         mState = state;
@@ -390,6 +400,7 @@
         mCallerNumberVerificationStatus = callerNumberVerificationStatus;
         mContactDisplayName = contactDisplayName;
         mActiveChildCallId = activeChildCallId;
+        mContactPhotoUri = contactPhotoUri;
     }
 
     /** The unique ID of the call. */
@@ -607,6 +618,14 @@
     }
 
     /**
+     * @return the caller photo URI.
+     */
+    public @Nullable Uri getContactPhotoUri() {
+        return mContactPhotoUri;
+    }
+
+
+    /**
      * @return On a CDMA conference with two participants, returns the ID of the child call that's
      *         currently active.
      */
@@ -655,6 +674,7 @@
             int callerNumberVerificationStatus = source.readInt();
             String contactDisplayName = source.readString();
             String activeChildCallId = source.readString();
+            Uri contactPhotoUri = source.readParcelable(classLoader, Uri.class);
             return new ParcelableCallBuilder()
                     .setId(id)
                     .setState(state)
@@ -686,6 +706,7 @@
                     .setCallerNumberVerificationStatus(callerNumberVerificationStatus)
                     .setContactDisplayName(contactDisplayName)
                     .setActiveChildCallId(activeChildCallId)
+                    .setContactPhotoUri(contactPhotoUri)
                     .createParcelableCall();
         }
 
@@ -735,6 +756,7 @@
         destination.writeInt(mCallerNumberVerificationStatus);
         destination.writeString(mContactDisplayName);
         destination.writeString(mActiveChildCallId);
+        destination.writeParcelable(mContactPhotoUri, 0);
     }
 
     @Override
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.java b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
index ff87ab0..a69dfb0b 100644
--- a/telecomm/java/android/telecom/ParcelableCallAnalytics.java
+++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
@@ -111,6 +111,8 @@
         public static final int FILTERING_INITIATED = 106;
         public static final int FILTERING_COMPLETED = 107;
         public static final int FILTERING_TIMED_OUT = 108;
+        public static final int DND_CHECK_INITIATED = 109;
+        public static final int DND_CHECK_COMPLETED = 110;
 
         public static final int SKIP_RINGING = 200;
         public static final int SILENCE = 201;
@@ -195,6 +197,7 @@
         public static final int BLOCK_CHECK_FINISHED_TIMING = 9;
         public static final int FILTERING_COMPLETED_TIMING = 10;
         public static final int FILTERING_TIMED_OUT_TIMING = 11;
+        public static final int DND_PRE_CALL_PRE_CHECK_TIMING = 12;
         /** {@hide} */
         public static final int START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING = 12;
 
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index ccec67e..af37ed5 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1851,7 +1851,7 @@
         ITelecomService service = getTelecomService();
         if (service != null) {
             try {
-                return service.getCallStateUsingPackage(mContext.getPackageName(),
+                return service.getCallStateUsingPackage(mContext.getOpPackageName(),
                         mContext.getAttributionTag());
             } catch (RemoteException e) {
                 Log.d(TAG, "RemoteException calling getCallState().", e);
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index f848c40..a9cdf7e 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -210,6 +210,15 @@
     }
 
     /**
+     * Returns the userHandle of the current process, if called from a system app,
+     * otherwise it returns the caller's userHandle
+     * @return userHandle of the caller.
+     */
+    private static UserHandle getIncomingUserHandle() {
+        return UserHandle.of(getIncomingUserId());
+    }
+
+    /**
      * Returns the list of available SMS apps defined as apps that are registered for both the
      * SMS_RECEIVED_ACTION (SMS) and WAP_PUSH_RECEIVED_ACTION (MMS) broadcasts (and their broadcast
      * receivers are enabled)
@@ -951,24 +960,28 @@
      */
     @UnsupportedAppUsage
     public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) {
-        return getDefaultSmsApplicationAsUser(context, updateIfNeeded, getIncomingUserId());
+        return getDefaultSmsApplicationAsUser(context, updateIfNeeded, getIncomingUserHandle());
     }
 
     /**
      * Gets the default SMS application on a given user
      * @param context context from the calling app
      * @param updateIfNeeded update the default app if there is no valid default app configured.
-     * @param userId target user ID.
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
      * @return component name of the app and class to deliver SMS messages to
      */
-    @VisibleForTesting
     public static ComponentName getDefaultSmsApplicationAsUser(Context context,
-            boolean updateIfNeeded, int userId) {
+            boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+        if (userHandle == null) {
+            userHandle = getIncomingUserHandle();
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
             SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
-                    userId);
+                    userHandle.getIdentifier());
             if (smsApplicationData != null) {
                 component = new ComponentName(smsApplicationData.mPackageName,
                         smsApplicationData.mSmsReceiverClass);
@@ -987,23 +1000,28 @@
      */
     @UnsupportedAppUsage
     public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) {
-        return getDefaultMmsApplicationAsUser(context, updateIfNeeded, getIncomingUserId());
+        return getDefaultMmsApplicationAsUser(context, updateIfNeeded, getIncomingUserHandle());
     }
 
     /**
      * Gets the default MMS application on a given user
      * @param context context from the calling app
      * @param updateIfNeeded update the default app if there is no valid default app configured.
-     * @param userId target user ID.
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
      * @return component name of the app and class to deliver MMS messages to.
      */
     public static ComponentName getDefaultMmsApplicationAsUser(Context context,
-            boolean updateIfNeeded, int userId) {
+            boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+        if (userHandle == null) {
+            userHandle = getIncomingUserHandle();
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
             SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
-                    userId);
+                    userHandle.getIdentifier());
             if (smsApplicationData != null) {
                 component = new ComponentName(smsApplicationData.mPackageName,
                         smsApplicationData.mMmsReceiverClass);
@@ -1024,23 +1042,28 @@
     public static ComponentName getDefaultRespondViaMessageApplication(Context context,
             boolean updateIfNeeded) {
         return getDefaultRespondViaMessageApplicationAsUser(context, updateIfNeeded,
-                getIncomingUserId());
+                getIncomingUserHandle());
     }
 
     /**
      * Gets the default Respond Via Message application on a given user
      * @param context context from the calling app
      * @param updateIfNeeded update the default app if there is no valid default app configured
-     * @param userId target user ID.
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
      * @return component name of the app and class to direct Respond Via Message intent to
      */
     public static ComponentName getDefaultRespondViaMessageApplicationAsUser(Context context,
-            boolean updateIfNeeded, int userId) {
+            boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+        if (userHandle == null) {
+            userHandle = getIncomingUserHandle();
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
             SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
-                    userId);
+                    userHandle.getIdentifier());
             if (smsApplicationData != null) {
                 component = new ComponentName(smsApplicationData.mPackageName,
                         smsApplicationData.mRespondViaMessageClass);
@@ -1062,6 +1085,7 @@
     public static ComponentName getDefaultSendToApplication(Context context,
             boolean updateIfNeeded) {
         int userId = getIncomingUserId();
+
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
@@ -1087,7 +1111,7 @@
     public static ComponentName getDefaultExternalTelephonyProviderChangedApplication(
             Context context, boolean updateIfNeeded) {
         return getDefaultExternalTelephonyProviderChangedApplicationAsUser(context, updateIfNeeded,
-                getIncomingUserId());
+                getIncomingUserHandle());
     }
 
     /**
@@ -1095,16 +1119,21 @@
      * MmsProvider on a given user.
      * @param context context from the calling app
      * @param updateIfNeeded update the default app if there is no valid default app configured
-     * @param userId target user ID.
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
      * @return component name of the app and class to deliver change intents to.
      */
     public static ComponentName getDefaultExternalTelephonyProviderChangedApplicationAsUser(
-            Context context, boolean updateIfNeeded, int userId) {
+            Context context, boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+        if (userHandle == null) {
+            userHandle = getIncomingUserHandle();
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
             SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
-                    userId);
+                    userHandle.getIdentifier());
             if (smsApplicationData != null
                     && smsApplicationData.mProviderChangedReceiverClass != null) {
                 component = new ComponentName(smsApplicationData.mPackageName,
@@ -1124,23 +1153,28 @@
      */
     public static ComponentName getDefaultSimFullApplication(
             Context context, boolean updateIfNeeded) {
-        return getDefaultSimFullApplicationAsUser(context, updateIfNeeded, getIncomingUserId());
+        return getDefaultSimFullApplicationAsUser(context, updateIfNeeded, getIncomingUserHandle());
     }
 
     /**
      * Gets the default application that handles sim full event on a given user.
      * @param context context from the calling app
      * @param updateIfNeeded update the default app if there is no valid default app configured
-     * @param userId target user ID.
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
      * @return component name of the app and class to deliver change intents to
      */
     public static ComponentName getDefaultSimFullApplicationAsUser(Context context,
-            boolean updateIfNeeded, int userId) {
+            boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+        if (userHandle == null) {
+            userHandle = getIncomingUserHandle();
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             ComponentName component = null;
             SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
-                    userId);
+                    userHandle.getIdentifier());
             if (smsApplicationData != null
                     && smsApplicationData.mSimFullReceiverClass != null) {
                 component = new ComponentName(smsApplicationData.mPackageName,
@@ -1153,19 +1187,35 @@
     }
 
     /**
-     * Returns whether need to wrgetIncomingUserIdite the SMS message to SMS database for this
-     * package.
+     * Returns whether it is required to write the SMS message to SMS database for this package.
+     *
+     * @param packageName the name of the package to be checked
+     * @param context context from the calling app
+     * @return true if it is required to write SMS message to SMS database for this package.
+     *
      * <p>
      * Caller must pass in the correct user context if calling from a singleton service.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static boolean shouldWriteMessageForPackage(String packageName, Context context) {
-        return !shouldWriteMessageForPackageAsUser(packageName, context, getIncomingUserId());
+        return !shouldWriteMessageForPackageAsUser(packageName, context, getIncomingUserHandle());
     }
 
+    /**
+     * Returns whether it is required to write the SMS message to SMS database for this package.
+     *
+     * @param packageName the name of the package to be checked
+     * @param context context from the calling app
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
+     * @return true if it is required to write SMS message to SMS database for this package.
+     *
+     * <p>
+     * Caller must pass in the correct user context if calling from a singleton service.
+     */
     public static boolean shouldWriteMessageForPackageAsUser(String packageName, Context context,
-            int userId) {
-        return !isDefaultSmsApplicationAsUser(context, packageName, userId);
+            @Nullable UserHandle userHandle) {
+        return !isDefaultSmsApplicationAsUser(context, packageName, userHandle);
     }
 
     /**
@@ -1177,7 +1227,7 @@
      */
     @UnsupportedAppUsage
     public static boolean isDefaultSmsApplication(Context context, String packageName) {
-        return isDefaultSmsApplicationAsUser(context, packageName, getIncomingUserId());
+        return isDefaultSmsApplicationAsUser(context, packageName, getIncomingUserHandle());
     }
 
     /**
@@ -1185,16 +1235,22 @@
      *
      * @param context context from the calling app
      * @param packageName the name of the package to be checked
-     * @param userId target user ID.
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
      * @return true if the package is default sms app or bluetooth
      */
     public static boolean isDefaultSmsApplicationAsUser(Context context, String packageName,
-            int userId) {
+            @Nullable UserHandle userHandle) {
         if (packageName == null) {
             return false;
         }
+
+        if (userHandle == null) {
+            userHandle = getIncomingUserHandle();
+        }
+
         ComponentName component = getDefaultSmsApplicationAsUser(context, false,
-                userId);
+                userHandle);
         if (component == null) {
             return false;
         }
@@ -1222,7 +1278,7 @@
      */
     @UnsupportedAppUsage
     public static boolean isDefaultMmsApplication(Context context, String packageName) {
-        return isDefaultMmsApplicationAsUser(context, packageName, getIncomingUserId());
+        return isDefaultMmsApplicationAsUser(context, packageName, getIncomingUserHandle());
     }
 
     /**
@@ -1230,17 +1286,22 @@
      *
      * @param context context from the calling app
      * @param packageName the name of the package to be checked
-     * @param userId target user ID.
+     * @param userHandle target user handle
+     * if {@code null} is passed in then calling package uid is used to find out target user handle.
      * @return true if the package is default mms app or bluetooth
      */
     public static boolean isDefaultMmsApplicationAsUser(Context context, String packageName,
-            int userId) {
+            @Nullable UserHandle userHandle) {
         if (packageName == null) {
             return false;
         }
 
+        if (userHandle == null) {
+            userHandle = getIncomingUserHandle();
+        }
+
         ComponentName component = getDefaultMmsApplicationAsUser(context, false,
-                userId);
+                userHandle);
         if (component == null) {
             return false;
         }
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 76d2b7d..3dc7111 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -27,6 +27,8 @@
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
 import java.io.PrintWriter;
@@ -212,4 +214,30 @@
                 return "UNKNOWN(" + mobileDataPolicy + ")";
         }
     }
-}
+
+    /**
+     * Utility method to get user handle associated with this subscription.
+     *
+     * This method should be used internally as it returns null instead of throwing
+     * IllegalArgumentException or IllegalStateException.
+     *
+     * @param context Context object
+     * @param subId the subId of the subscription.
+     * @return userHandle associated with this subscription
+     * or {@code null} if:
+     * 1. subscription is not associated with any user
+     * 2. subId is invalid.
+     * 3. subscription service is not available.
+     *
+     * @throws SecurityException if the caller doesn't have permissions required.
+     */
+    @Nullable
+    public static UserHandle getSubscriptionUserHandle(Context context, int subId) {
+        UserHandle userHandle = null;
+        SubscriptionManager subManager =  context.getSystemService(SubscriptionManager.class);
+        if ((subManager != null) && (SubscriptionManager.isValidSubscriptionId(subId))) {
+            userHandle = subManager.getSubscriptionUserHandle(subId);
+        }
+        return userHandle;
+    }
+}
\ No newline at end of file
diff --git a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
index 8b01cb3..2787d83 100644
--- a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
+++ b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
@@ -199,7 +199,6 @@
      */
     @Override
     public Object clone() throws CloneNotSupportedException {
-        super.clone();
         int len = mData.length;
         byte[] dstBytes = new byte[len];
         System.arraycopy(mData, 0, dstBytes, 0, len);
diff --git a/telephony/java/android/service/carrier/CarrierService.java b/telephony/java/android/service/carrier/CarrierService.java
index ae91d4d..1c53148 100644
--- a/telephony/java/android/service/carrier/CarrierService.java
+++ b/telephony/java/android/service/carrier/CarrierService.java
@@ -28,8 +28,6 @@
 import android.telephony.TelephonyRegistryManager;
 import android.util.Log;
 
-import com.android.internal.util.ArrayUtils;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
@@ -237,12 +235,7 @@
         @Override
         public void getCarrierConfig(int phoneId, CarrierIdentifier id, ResultReceiver result) {
             try {
-                int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-                int[] subIds = SubscriptionManager.getSubId(phoneId);
-                if (!ArrayUtils.isEmpty(subIds)) {
-                    // There should be at most one active subscription mapping to the phoneId.
-                    subId = subIds[0];
-                }
+                int subId = SubscriptionManager.getSubscriptionId(phoneId);
                 Bundle data = new Bundle();
                 data.putParcelable(KEY_CONFIG_BUNDLE, CarrierService.this.onLoadConfig(subId, id));
                 result.send(RESULT_OK, data);
diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java
index 8ec500b..7eccd1a 100644
--- a/telephony/java/android/service/euicc/EuiccProfileInfo.java
+++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java
@@ -49,7 +49,6 @@
             POLICY_RULE_DO_NOT_DELETE,
             POLICY_RULE_DELETE_AFTER_DISABLING
     })
-    /** @hide */
     public @interface PolicyRule {}
     /** Once this profile is enabled, it cannot be disabled. */
     public static final int POLICY_RULE_DO_NOT_DISABLE = 1;
@@ -66,7 +65,6 @@
             PROFILE_CLASS_OPERATIONAL,
             PROFILE_CLASS_UNSET
     })
-    /** @hide */
     public @interface ProfileClass {}
     /** Testing profiles */
     public static final int PROFILE_CLASS_TESTING = 0;
@@ -87,7 +85,6 @@
             PROFILE_STATE_ENABLED,
             PROFILE_STATE_UNSET
     })
-    /** @hide */
     public @interface ProfileState {}
     /** Disabled profiles */
     public static final int PROFILE_STATE_DISABLED = 0;
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/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 86b98f1..2435243 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -5,6 +5,7 @@
 import android.net.NetworkCapabilities;
 import android.telecom.Connection;
 import android.telephony.data.ApnSetting;
+import android.telephony.ims.ImsCallProfile;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -494,7 +495,7 @@
             PreciseCallState.PRECISE_CALL_STATE_HOLDING,
             PreciseCallState.PRECISE_CALL_STATE_DIALING,
             PreciseCallState.PRECISE_CALL_STATE_ALERTING,
-            PreciseCallState. PRECISE_CALL_STATE_INCOMING,
+            PreciseCallState.PRECISE_CALL_STATE_INCOMING,
             PreciseCallState.PRECISE_CALL_STATE_WAITING,
             PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED,
             PreciseCallState.PRECISE_CALL_STATE_DISCONNECTING})
@@ -727,6 +728,36 @@
     })
     public @interface ValidationStatus {}
 
+    /**
+     * IMS call Service types
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "SERVICE_TYPE_" }, value = {
+            ImsCallProfile.SERVICE_TYPE_NONE,
+            ImsCallProfile.SERVICE_TYPE_NORMAL,
+            ImsCallProfile.SERVICE_TYPE_EMERGENCY,
+    })
+    public @interface ImsCallServiceType {}
+
+    /**
+     * IMS call types
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "CALL_TYPE_" }, value = {
+            ImsCallProfile.CALL_TYPE_NONE,
+            ImsCallProfile.CALL_TYPE_VOICE_N_VIDEO,
+            ImsCallProfile.CALL_TYPE_VOICE,
+            ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE,
+            ImsCallProfile.CALL_TYPE_VT,
+            ImsCallProfile.CALL_TYPE_VT_TX,
+            ImsCallProfile.CALL_TYPE_VT_RX,
+            ImsCallProfile.CALL_TYPE_VT_NODIR,
+            ImsCallProfile.CALL_TYPE_VS,
+            ImsCallProfile.CALL_TYPE_VS_TX,
+            ImsCallProfile.CALL_TYPE_VS_RX,
+    })
+    public @interface ImsCallType {}
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "NET_CAPABILITY_ENTERPRISE_SUB_LEVEL" }, value = {
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index b7bef39..1dc64a9 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -29,8 +29,10 @@
  * Contains information about a call's attributes as passed up from the HAL. If there are multiple
  * ongoing calls, the CallAttributes will pertain to the call in the foreground.
  * @hide
+ * @deprecated use {@link CallState} for call information for each call.
  */
 @SystemApi
+@Deprecated
 public final class CallAttributes implements Parcelable {
     private PreciseCallState mPreciseCallState;
     @NetworkType
diff --git a/telephony/java/android/telephony/CallState.aidl b/telephony/java/android/telephony/CallState.aidl
new file mode 100644
index 0000000..dd5af8e
--- /dev/null
+++ b/telephony/java/android/telephony/CallState.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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 android.telephony;
+
+parcelable CallState;
+
diff --git a/telephony/java/android/telephony/CallState.java b/telephony/java/android/telephony/CallState.java
new file mode 100644
index 0000000..51ecfb0
--- /dev/null
+++ b/telephony/java/android/telephony/CallState.java
@@ -0,0 +1,409 @@
+/*
+ * 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.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.Annotation.ImsCallServiceType;
+import android.telephony.Annotation.ImsCallType;
+import android.telephony.Annotation.NetworkType;
+import android.telephony.Annotation.PreciseCallStates;
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsCallSession;
+
+import java.util.Objects;
+
+/**
+ * Contains information about various states for a call.
+ * @hide
+ */
+@SystemApi
+public final class CallState implements Parcelable {
+
+    /**
+     * Call classifications are just used for backward compatibility of deprecated API {@link
+     * TelephonyCallback#CallAttributesListener#onCallAttributesChanged}, Since these will be
+     * removed when the deprecated API is removed, they should not be opened.
+     */
+    /**
+     * Call classification is not valid. It should not be opened.
+     * @hide
+     */
+    public static final int CALL_CLASSIFICATION_UNKNOWN = -1;
+
+    /**
+     * Call classification indicating foreground call
+     * @hide
+     */
+    public static final int CALL_CLASSIFICATION_RINGING = 0;
+
+    /**
+     * Call classification indicating background call
+     * @hide
+     */
+    public static final int CALL_CLASSIFICATION_FOREGROUND = 1;
+
+    /**
+     * Call classification indicating ringing call
+     * @hide
+     */
+    public static final int CALL_CLASSIFICATION_BACKGROUND = 2;
+
+    /**
+     * Call classification Max value.
+     * @hide
+     */
+    public static final int CALL_CLASSIFICATION_MAX = CALL_CLASSIFICATION_BACKGROUND + 1;
+
+    @PreciseCallStates
+    private final int mPreciseCallState;
+
+    @NetworkType
+    private final int mNetworkType; // TelephonyManager.NETWORK_TYPE_* ints
+    private final CallQuality mCallQuality;
+
+    private final int mCallClassification;
+    /**
+     * IMS call session ID. {@link ImsCallSession#getCallId()}
+     */
+    @Nullable
+    private String mImsCallId;
+
+    /**
+     * IMS call service type of this call
+     */
+    @ImsCallServiceType
+    private int mImsCallServiceType;
+
+    /**
+     * IMS call type of this call.
+     */
+    @ImsCallType
+    private int mImsCallType;
+
+    /**
+     * Constructor of CallAttributes
+     *
+     * @param callState call state defined in {@link PreciseCallState}
+     * @param networkType network type for this call attributes
+     * @param callQuality call quality for this call attributes, only CallState in
+     *                    {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE} will have valid call
+     *                    quality.
+     * @param callClassification call classification
+     * @param imsCallId IMS call session ID for this call attributes
+     * @param imsCallServiceType IMS call service type for this call attributes
+     * @param imsCallType IMS call type for this call attributes
+     */
+    private CallState(@PreciseCallStates int callState, @NetworkType int networkType,
+            @NonNull CallQuality callQuality, int callClassification, @Nullable String imsCallId,
+            @ImsCallServiceType int imsCallServiceType, @ImsCallType int imsCallType) {
+        this.mPreciseCallState = callState;
+        this.mNetworkType = networkType;
+        this.mCallQuality = callQuality;
+        this.mCallClassification = callClassification;
+        this.mImsCallId = imsCallId;
+        this.mImsCallServiceType = imsCallServiceType;
+        this.mImsCallType = imsCallType;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "mPreciseCallState=" + mPreciseCallState + " mNetworkType=" + mNetworkType
+                + " mCallQuality=" + mCallQuality + " mCallClassification" + mCallClassification
+                + " mImsCallId=" + mImsCallId + " mImsCallServiceType=" + mImsCallServiceType
+                + " mImsCallType=" + mImsCallType;
+    }
+
+    private CallState(Parcel in) {
+        this.mPreciseCallState = in.readInt();
+        this.mNetworkType = in.readInt();
+        this.mCallQuality = in.readParcelable(
+                CallQuality.class.getClassLoader(), CallQuality.class);
+        this.mCallClassification = in.readInt();
+        this.mImsCallId = in.readString();
+        this.mImsCallServiceType = in.readInt();
+        this.mImsCallType = in.readInt();
+    }
+
+    // getters
+    /**
+     * Returns the precise call state of the call.
+     */
+    @PreciseCallStates
+    public int getCallState() {
+        return mPreciseCallState;
+    }
+
+    /**
+     * Returns the {@link TelephonyManager#NetworkType} of the call.
+     *
+     * @see TelephonyManager#NETWORK_TYPE_UNKNOWN
+     * @see TelephonyManager#NETWORK_TYPE_GPRS
+     * @see TelephonyManager#NETWORK_TYPE_EDGE
+     * @see TelephonyManager#NETWORK_TYPE_UMTS
+     * @see TelephonyManager#NETWORK_TYPE_CDMA
+     * @see TelephonyManager#NETWORK_TYPE_EVDO_0
+     * @see TelephonyManager#NETWORK_TYPE_EVDO_A
+     * @see TelephonyManager#NETWORK_TYPE_1xRTT
+     * @see TelephonyManager#NETWORK_TYPE_HSDPA
+     * @see TelephonyManager#NETWORK_TYPE_HSUPA
+     * @see TelephonyManager#NETWORK_TYPE_HSPA
+     * @see TelephonyManager#NETWORK_TYPE_IDEN
+     * @see TelephonyManager#NETWORK_TYPE_EVDO_B
+     * @see TelephonyManager#NETWORK_TYPE_LTE
+     * @see TelephonyManager#NETWORK_TYPE_EHRPD
+     * @see TelephonyManager#NETWORK_TYPE_HSPAP
+     * @see TelephonyManager#NETWORK_TYPE_GSM
+     * @see TelephonyManager#NETWORK_TYPE_TD_SCDMA
+     * @see TelephonyManager#NETWORK_TYPE_IWLAN
+     * @see TelephonyManager#NETWORK_TYPE_LTE_CA
+     * @see TelephonyManager#NETWORK_TYPE_NR
+     */
+    @NetworkType
+    public int getNetworkType() {
+        return mNetworkType;
+    }
+
+    /**
+     * Returns the {#link CallQuality} of the call.
+     * @return call quality for this call attributes, only CallState in {@link
+     *         PreciseCallState#PRECISE_CALL_STATE_ACTIVE} will have valid call quality. It will be
+     *         null for the call which is not in {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE}.
+     */
+    @Nullable
+    public CallQuality getCallQuality() {
+        return mCallQuality;
+    }
+
+    /**
+     * Returns the call classification.
+     * @hide
+     */
+    public int getCallClassification() {
+        return mCallClassification;
+    }
+
+    /**
+     * Returns the IMS call session ID.
+     */
+    @Nullable
+    public String getImsCallSessionId() {
+        return mImsCallId;
+    }
+
+    /**
+     * Returns the IMS call service type.
+     */
+    @ImsCallServiceType
+    public int getImsCallServiceType() {
+        return mImsCallServiceType;
+    }
+
+    /**
+     * Returns the IMS call type.
+     */
+    @ImsCallType
+    public int getImsCallType() {
+        return mImsCallType;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPreciseCallState, mNetworkType, mCallQuality, mCallClassification,
+                mImsCallId, mImsCallServiceType, mImsCallType);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (o == null || !(o instanceof CallState) || hashCode() != o.hashCode()) {
+            return false;
+        }
+
+        if (this == o) {
+            return true;
+        }
+
+        CallState s = (CallState) o;
+
+        return (mPreciseCallState == s.mPreciseCallState
+                && mNetworkType == s.mNetworkType
+                && Objects.equals(mCallQuality, s.mCallQuality)
+                && mCallClassification == s.mCallClassification
+                && Objects.equals(mImsCallId, s.mImsCallId)
+                && mImsCallType == s.mImsCallType
+                && mImsCallServiceType == s.mImsCallServiceType);
+    }
+
+    /**
+     * {@link Parcelable#describeContents}
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@link Parcelable#writeToParcel}
+     */
+    public void writeToParcel(@Nullable Parcel dest, int flags) {
+        dest.writeInt(mPreciseCallState);
+        dest.writeInt(mNetworkType);
+        dest.writeParcelable(mCallQuality, flags);
+        dest.writeInt(mCallClassification);
+        dest.writeString(mImsCallId);
+        dest.writeInt(mImsCallServiceType);
+        dest.writeInt(mImsCallType);
+    }
+
+    public static final @NonNull Creator<CallState> CREATOR = new Creator() {
+        public CallState createFromParcel(Parcel in) {
+            return new CallState(in);
+        }
+
+        public CallState[] newArray(int size) {
+            return new CallState[size];
+        }
+    };
+
+    /**
+     * Builder of {@link CallState}
+     *
+     * <p>The example below shows how you might create a new {@code CallState}:
+     *
+     * <pre><code>
+     *
+     * CallState = new CallState.Builder()
+     *     .setCallState(3)
+     *     .setNetworkType({@link TelephonyManager#NETWORK_TYPE_LTE})
+     *     .setCallQuality({@link CallQuality})
+     *     .setImsCallSessionId({@link String})
+     *     .setImsCallServiceType({@link ImsCallProfile#SERVICE_TYPE_NORMAL})
+     *     .setImsCallType({@link ImsCallProfile#CALL_TYPE_VOICE})
+     *     .build();
+     * </code></pre>
+     */
+    public static final class Builder {
+        private @PreciseCallStates int mPreciseCallState;
+        private @NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        private CallQuality mCallQuality = null;
+        private int mCallClassification = CALL_CLASSIFICATION_UNKNOWN;
+        private String mImsCallId;
+        private @ImsCallServiceType int mImsCallServiceType = ImsCallProfile.SERVICE_TYPE_NONE;
+        private @ImsCallType int mImsCallType = ImsCallProfile.CALL_TYPE_NONE;
+
+
+        /**
+         * Default constructor for the Builder.
+         */
+        public Builder(@PreciseCallStates int preciseCallState) {
+            mPreciseCallState = preciseCallState;
+        }
+
+        /**
+         * Set network type of this call.
+         *
+         * @param networkType the transport type.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public CallState.Builder setNetworkType(@NetworkType int networkType) {
+            this.mNetworkType = networkType;
+            return this;
+        }
+
+        /**
+         * Set the call quality {@link CallQuality} of this call.
+         *
+         * @param callQuality call quality of active call.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public CallState.Builder setCallQuality(@Nullable CallQuality callQuality) {
+            this.mCallQuality = callQuality;
+            return this;
+        }
+
+        /**
+         * Set call classification for this call.
+         *
+         * @param classification call classification type defined in this class.
+         * @return The same instance of the builder.
+         * @hide
+         */
+        @NonNull
+        public CallState.Builder setCallClassification(int classification) {
+            this.mCallClassification = classification;
+            return this;
+        }
+
+        /**
+         * Set IMS call session ID of this call.
+         *
+         * @param imsCallId  IMS call session ID.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public CallState.Builder setImsCallSessionId(@Nullable String imsCallId) {
+            this.mImsCallId = imsCallId;
+            return this;
+        }
+
+        /**
+         * Set IMS call service type of this call.
+         *
+         * @param serviceType IMS call service type defined in {@link ImsCallProfile}.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public CallState.Builder setImsCallServiceType(@ImsCallServiceType int serviceType) {
+            this.mImsCallServiceType = serviceType;
+            return this;
+        }
+
+        /**
+         * Set IMS call type of this call.
+         *
+         * @param callType IMS call type defined in {@link ImsCallProfile}.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public CallState.Builder setImsCallType(@ImsCallType int callType) {
+            this.mImsCallType = callType;
+            return this;
+        }
+
+        /**
+         * Build the {@link CallState}
+         *
+         * @return the {@link CallState} object
+         */
+        @NonNull
+        public CallState build() {
+            return new CallState(
+                    mPreciseCallState,
+                    mNetworkType,
+                    mCallQuality,
+                    mCallClassification,
+                    mImsCallId,
+                    mImsCallServiceType,
+                    mImsCallType);
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d314a65..22cd31a 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";
 
@@ -1992,6 +1996,15 @@
             "nr_advanced_threshold_bandwidth_khz_int";
 
     /**
+     * Indicating whether to include LTE cell bandwidths when determining whether the aggregated
+     * cell bandwidth meets the required threshold for NR advanced.
+     *
+     * @see TelephonyDisplayInfo#OVERRIDE_NETWORK_TYPE_NR_ADVANCED
+     */
+    public static final String KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL =
+            "include_lte_for_nr_advanced_threshold_bandwidth_bool";
+
+    /**
      * Boolean indicating if operator name should be shown in the status bar
      * @hide
      */
@@ -2103,6 +2116,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
@@ -7081,6 +7104,274 @@
         public static final String KEY_REFRESH_GEOLOCATION_TIMEOUT_MILLIS_INT =
                 KEY_PREFIX + "refresh_geolocation_timeout_millis_int";
 
+        /**
+         * List of 3GPP access network technologies where e911 over IMS is supported
+         * in the home network and domestic 3rd-party networks. The order in the list represents
+         * the preference. The domain selection service shall scan the network type in the order
+         * of the preference.
+         *
+         * <p>Possible values are,
+         * {@link AccessNetworkConstants.AccessNetworkType#NGRAN}
+         * {@link AccessNetworkConstants.AccessNetworkType#EUTRAN}
+         *
+         * The default value for this key is
+         * {{@link AccessNetworkConstants.AccessNetworkType#EUTRAN},
+         * @hide
+         */
+        public static final String
+                KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
+                        + "emergency_over_ims_supported_3gpp_network_types_int_array";
+
+        /**
+         * List of 3GPP access network technologies where e911 over IMS is supported
+         * in the roaming network and non-domestic 3rd-party networks. The order in the list
+         * represents the preference. The domain selection service shall scan the network type
+         * in the order of the preference.
+         *
+         * <p>Possible values are,
+         * {@link AccessNetworkConstants.AccessNetworkType#NGRAN}
+         * {@link AccessNetworkConstants.AccessNetworkType#EUTRAN}
+         *
+         * The default value for this key is
+         * {{@link AccessNetworkConstants.AccessNetworkType#EUTRAN},
+         * @hide
+         */
+        public static final String
+                KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
+                        + "emergency_over_ims_roaming_supported_3gpp_network_types_int_array";
+
+        /**
+         * List of CS access network technologies where circuit-switched emergency calls are
+         * supported in the home network and domestic 3rd-party networks. The order in the list
+         * represents the preference. The domain selection service shall scan the network type
+         * in the order of the preference.
+         *
+         * <p>Possible values are,
+         * {@link AccessNetworkConstants.AccessNetworkType#GERAN}
+         * {@link AccessNetworkConstants.AccessNetworkType#UTRAN}
+         * {@link AccessNetworkConstants.AccessNetworkType#CDMA2000}
+         *
+         * The default value for this key is
+         * {{@link AccessNetworkConstants.AccessNetworkType#UTRAN},
+         * {@link AccessNetworkConstants.AccessNetworkType#GERAN}}.
+         * @hide
+         */
+        public static final String KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY =
+                KEY_PREFIX + "emergency_over_cs_supported_access_network_types_int_array";
+
+        /**
+         * List of CS access network technologies where circuit-switched emergency calls are
+         * supported in the roaming network and non-domestic 3rd-party networks. The order
+         * in the list represents the preference. The domain selection service shall scan
+         * the network type in the order of the preference.
+         *
+         * <p>Possible values are,
+         * {@link AccessNetworkConstants.AccessNetworkType#GERAN}
+         * {@link AccessNetworkConstants.AccessNetworkType#UTRAN}
+         * {@link AccessNetworkConstants.AccessNetworkType#CDMA2000}
+         *
+         * The default value for this key is
+         * {{@link AccessNetworkConstants.AccessNetworkType#UTRAN},
+         * {@link AccessNetworkConstants.AccessNetworkType#GERAN}}.
+         * @hide
+         */
+        public static final String
+                KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
+                        + "emergency_over_cs_roaming_supported_access_network_types_int_array";
+
+        /** @hide */
+        @IntDef({
+            DOMAIN_CS,
+            DOMAIN_PS_3GPP,
+            DOMAIN_PS_NON_3GPP
+        })
+        public @interface EmergencyDomain {}
+
+        /**
+         * Circuit switched domain.
+         * @hide
+         */
+        public static final int DOMAIN_CS = 1;
+
+        /**
+         * Packet switched domain over 3GPP networks.
+         * @hide
+         */
+        public static final int DOMAIN_PS_3GPP = 2;
+
+        /**
+         * Packet switched domain over non-3GPP networks such as Wi-Fi.
+         * @hide
+         */
+        public static final int DOMAIN_PS_NON_3GPP = 3;
+
+        /**
+         * Specifies the emergency call domain preference for the home network.
+         * The domain selection service shall choose the domain in the order
+         * for attempting the emergency call
+         *
+         * <p>Possible values are,
+         * {@link #DOMAIN_CS}
+         * {@link #DOMAIN_PS_3GPP}
+         * {@link #DOMAIN_PS_NON_3GPP}.
+         *
+         * The default value for this key is
+         * {{@link #DOMAIN_PS_3GPP},
+         * {@link #DOMAIN_CS},
+         * {@link #DOMAIN_PS_NON_3GPP}}.
+         * @hide
+         */
+        public static final String KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY =
+                KEY_PREFIX + "emergency_domain_preference_int_array";
+
+        /**
+         * Specifies the emergency call domain preference for the roaming network.
+         * The domain selection service shall choose the domain in the order
+         * for attempting the emergency call.
+         *
+         * <p>Possible values are,
+         * {@link #DOMAIN_CS}
+         * {@link #DOMAIN_PS_3GPP}
+         * {@link #DOMAIN_PS_NON_3GPP}.
+         *
+         * The default value for this key is
+         * {{@link #DOMAIN_PS_3GPP},
+         * {@link #DOMAIN_CS},
+         * {@link #DOMAIN_PS_NON_3GPP}}.
+         * @hide
+         */
+        public static final String KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY =
+                KEY_PREFIX + "emergency_domain_preference_roaming_int_array";
+
+        /**
+         * Specifies if emergency call shall be attempted on IMS, if PS is attached even though IMS
+         * is not registered and normal calls fallback to the CS networks.
+         *
+         * The default value for this key is {@code false}.
+         * @hide
+         */
+        public static final String KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL =
+                KEY_PREFIX + "prefer_ims_emergency_when_voice_calls_on_cs_bool";
+
+        /**
+         * Specifies maximum number of emergency call retries over Wi-Fi.
+         * This is valid only when {@link #DOMAIN_PS_NON_3GPP} is included in
+         * {@link #KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY} or
+         * {@link #KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY}.
+         *
+         * The default value for this key is 1.
+         * @hide
+         */
+        public static final String KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT =
+                KEY_PREFIX + "maximum_number_of_emergency_tries_over_vowifi_int";
+
+        /**
+         * Emergency scan timer to wait for scan results from radio before attempting the call
+         * over Wi-Fi. On timer expiry, if emergency call on Wi-Fi is allowed and possible,
+         * telephony shall cancel the scan and place the call on Wi-Fi. If emergency call on Wi-Fi
+         * is not possible, then domain seleciton continues to wait for the scan result from the
+         * radio. If an emergency scan result is received before the timer expires, the timer shall
+         * be stopped and no dialing over Wi-Fi will be tried. If this value is set to 0, then
+         * the timer is never started and domain selection waits for the scan result from the radio.
+         *
+         * The default value for the timer is 10 seconds.
+         * @hide
+         */
+        public static final String KEY_EMERGENCY_SCAN_TIMER_SEC_INT =
+                KEY_PREFIX + "emergency_scan_timer_sec_int";
+
+        /** @hide */
+        @IntDef(prefix = "SCAN_TYPE_",
+            value = {
+                SCAN_TYPE_NO_PREFERENCE,
+                SCAN_TYPE_FULL_SERVICE,
+                SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE})
+        public @interface EmergencyScanType {}
+
+        /**
+         * No specific preference given to the modem. Modem can return an emergency
+         * capable network either with limited service or full service.
+         * @hide
+         */
+        public static final int SCAN_TYPE_NO_PREFERENCE = 0;
+
+        /**
+         * Modem will attempt to camp on a network with full service only.
+         * @hide
+         */
+        public static final int SCAN_TYPE_FULL_SERVICE = 1;
+
+        /**
+         * Telephony shall attempt full service scan first.
+         * If a full service network is not found, telephony shall attempt a limited service scan.
+         * @hide
+         */
+        public static final int SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE = 2;
+
+        /**
+         * Specifies the preferred emergency network scanning type.
+         *
+         * <p>Possible values are,
+         * {@link #SCAN_TYPE_NO_PREFERENCE}
+         * {@link #SCAN_TYPE_FULL_SERVICE}
+         * {@link #SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE}
+         *
+         * The default value for this key is {@link #SCAN_TYPE_NO_PREFERENCE}.
+         * @hide
+         */
+        public static final String KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT =
+                KEY_PREFIX + "emergency_network_scan_type_int";
+
+        /**
+         * Specifies the time by which a call should be set up on the current network
+         * once the call is routed on the network. If the call cannot be set up by timer expiry,
+         * call shall be re-dialed on the next available network.
+         * If this value is set to 0, the timer shall be disabled.
+         *
+         * The default value for this key is 0.
+         * @hide
+         */
+        public static final String KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT =
+                KEY_PREFIX + "emergency_call_setup_timer_on_current_network_sec_int";
+
+        /**
+         * Specifies if emergency call shall be attempted on IMS only when IMS is registered.
+         * This is applicable only for the case PS is in service.
+         *
+         * The default value for this key is {@code false}.
+         * @hide
+         */
+        public static final String KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL =
+                KEY_PREFIX + "emergency_requires_ims_registration_bool";
+
+        /**
+         * Specifies if LTE is preferred when re-scanning networks after the failure of dialing
+         * over NR. If not, CS will be preferred.
+         *
+         * The default value for this key is {@code false}.
+         * @hide
+         */
+        public static final String KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL =
+                KEY_PREFIX + "emergency_lte_preferred_after_nr_failed_bool";
+
+        /**
+         * Specifies the numbers to be dialed over CDMA network in case of dialing over CS network.
+         *
+         * The default value for this key is an empty string array.
+         * @hide
+         */
+        public static final String KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY =
+                KEY_PREFIX + "emergency_cdma_preferred_numbers_string_array";
+
+        /**
+         * Specifies if emergency call shall be attempted on IMS only when VoLTE is enabled.
+         *
+         * The default value for this key is {@code false}.
+         * @hide
+         */
+        public static final String KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL =
+                KEY_PREFIX + "emergency_requires_volte_enabled_bool";
+
         private static PersistableBundle getDefaults() {
             PersistableBundle defaults = new PersistableBundle();
             defaults.putBoolean(KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL, false);
@@ -7097,6 +7388,56 @@
             defaults.putInt(KEY_EMERGENCY_REGISTRATION_TIMER_MILLIS_INT, 10000);
             defaults.putInt(KEY_REFRESH_GEOLOCATION_TIMEOUT_MILLIS_INT, 5000);
 
+            defaults.putIntArray(
+                    KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY,
+                    new int[] {
+                        AccessNetworkType.EUTRAN,
+                    });
+
+            defaults.putIntArray(
+                    KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY,
+                    new int[] {
+                        AccessNetworkType.EUTRAN,
+                    });
+
+            defaults.putIntArray(
+                    KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY,
+                    new int[] {
+                        AccessNetworkType.UTRAN,
+                        AccessNetworkType.GERAN,
+                    });
+
+            defaults.putIntArray(
+                    KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY,
+                    new int[] {
+                        AccessNetworkType.UTRAN,
+                        AccessNetworkType.GERAN,
+                    });
+
+            defaults.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY,
+                    new int[] {
+                        DOMAIN_PS_3GPP,
+                        DOMAIN_CS,
+                        DOMAIN_PS_NON_3GPP
+                    });
+            defaults.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY,
+                    new int[] {
+                        DOMAIN_PS_3GPP,
+                        DOMAIN_CS,
+                        DOMAIN_PS_NON_3GPP
+                    });
+
+            defaults.putBoolean(KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL, false);
+            defaults.putInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT, 1);
+            defaults.putInt(KEY_EMERGENCY_SCAN_TIMER_SEC_INT, 10);
+            defaults.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, SCAN_TYPE_NO_PREFERENCE);
+            defaults.putInt(KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT, 0);
+            defaults.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, false);
+            defaults.putBoolean(KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL, false);
+            defaults.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, false);
+            defaults.putStringArray(KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY,
+                    new String[] {});
+
             return defaults;
         }
     }
@@ -7906,7 +8247,8 @@
                     KEY_XCAP_OVER_UT_SUPPORTED_RATS_INT_ARRAY,
                     new int[] {
                         AccessNetworkType.EUTRAN,
-                        AccessNetworkType.IWLAN
+                        AccessNetworkType.IWLAN,
+                        AccessNetworkType.NGRAN
                     });
             defaults.putString(KEY_UT_AS_SERVER_FQDN_STRING, "");
             defaults.putBoolean(KEY_TERMINAL_BASED_CALL_WAITING_DEFAULT_ENABLED_BOOL, true);
@@ -8436,7 +8778,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 +8790,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,
@@ -8599,7 +8943,12 @@
      *
      * Used to trade privacy/security against potentially reduced carrier coverage for some
      * carriers.
+     *
+     * @deprecated Future versions of Android will disallow carriers from hiding this toggle
+     * because disabling 2g is a security feature that users should always have access to at
+     * their discretion.
      */
+    @Deprecated
     public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool";
 
     /**
@@ -8677,6 +9026,15 @@
     public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool";
 
     /**
+     * Boolean indicating the default VoNR user preference setting.
+     * If true, the VoNR setting will be enabled. If false, it will be disabled initially.
+     *
+     * Enabled by default.
+     *
+     */
+    public static final String KEY_VONR_ON_BY_DEFAULT_BOOL = "vonr_on_by_default_bool";
+
+    /**
      * Determine whether unthrottle data retry when tracking area code (TAC/LAC) from cell changes
      *
      * @hide
@@ -8716,7 +9074,8 @@
      * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
      * returns a failure due to user action or timeout.
      * The maximum number of network boost notifications to show the user are defined in
-     * {@link #KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY}.
+     * {@link #KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT} and
+     * {@link #KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT}.
      *
      * The default value is 30 minutes.
      *
@@ -8728,20 +9087,32 @@
             "premium_capability_notification_backoff_hysteresis_time_millis_long";
 
     /**
-     * The maximum number of times that we display the notification for a network boost via premium
-     * capabilities when {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
+     * The maximum number of times in a day that we display the notification for a network boost
+     * via premium capabilities when
+     * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
      * returns a failure due to user action or timeout.
      *
-     * An int array with 2 values: {max_notifications_per_day, max_notifications_per_month}.
-     *
-     * The default value is {2, 10}, meaning we display a maximum of 2 network boost notifications
-     * per day and 10 notifications per month.
+     * The default value is 2 times.
      *
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
      */
-    public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY =
-            "premium_capability_maximum_notification_count_int_array";
+    public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT =
+            "premium_capability_maximum_daily_notification_count_int";
+
+    /**
+     * The maximum number of times in a month that we display the notification for a network boost
+     * via premium capabilities when
+     * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
+     * returns a failure due to user action or timeout.
+     *
+     * The default value is 10 times.
+     *
+     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
+     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
+     */
+    public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT =
+            "premium_capability_maximum_monthly_notification_count_int";
 
     /**
      * The amount of time in milliseconds that the purchase request should be throttled when
@@ -8751,13 +9122,29 @@
      * The default value is 30 minutes.
      *
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR
-     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED
+     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED
      */
     public static final String
             KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
             "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 to 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)}.
@@ -8776,7 +9163,7 @@
      * when connected to {@link TelephonyManager#NETWORK_TYPE_LTE} to purchase and use
      * premium capabilities.
      * If this is {@code false}, applications can only purchase and use premium capabilities when
-     * conencted to {@link TelephonyManager#NETWORK_TYPE_NR}.
+     * connected to {@link TelephonyManager#NETWORK_TYPE_NR}.
      *
      * This is {@code false} by default.
      */
@@ -9069,6 +9456,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, "");
@@ -9198,6 +9586,7 @@
         sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
         sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
         sDefaults.putInt(KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 0);
+        sDefaults.putBoolean(KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL, false);
         sDefaults.putIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
                 new int[]{CARRIER_NR_AVAILABILITY_NSA, CARRIER_NR_AVAILABILITY_SA});
         sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true);
@@ -9432,8 +9821,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|"
@@ -9464,16 +9858,19 @@
         sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, true);
         sDefaults.putBoolean(KEY_VONR_ENABLED_BOOL, false);
+        sDefaults.putBoolean(KEY_VONR_ON_BY_DEFAULT_BOOL, true);
         sDefaults.putIntArray(KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY, new int[] {});
         sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG,
                 TimeUnit.MINUTES.toMillis(30));
         sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
                 TimeUnit.MINUTES.toMillis(30));
-        sDefaults.putIntArray(KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY,
-                new int[] {2, 10});
+        sDefaults.putInt(KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT, 2);
+        sDefaults.putInt(KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT, 10);
         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/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 06cfd67..6e3cfac 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -107,7 +107,7 @@
 
         if ((mMccStr != null && mMncStr == null) || (mMccStr == null && mMncStr != null)) {
             AnomalyReporter.reportAnomaly(
-                    UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+                    UUID.fromString("e257ae06-ac0a-44c0-ba63-823b9f07b3e4"),
                     "CellIdentity Missing Half of PLMN ID");
         }
 
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 23835a7..b83b400 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1620,29 +1620,26 @@
                 // If we are not able to find the configuration from carrier config, use the default
                 // ones.
                 if (permanentFailureSet == null) {
-                    permanentFailureSet = new HashSet<Integer>() {
-                        {
-                            add(OPERATOR_BARRED);
-                            add(MISSING_UNKNOWN_APN);
-                            add(UNKNOWN_PDP_ADDRESS_TYPE);
-                            add(USER_AUTHENTICATION);
-                            add(ACTIVATION_REJECT_GGSN);
-                            add(SERVICE_OPTION_NOT_SUPPORTED);
-                            add(SERVICE_OPTION_NOT_SUBSCRIBED);
-                            add(NSAPI_IN_USE);
-                            add(ONLY_IPV4_ALLOWED);
-                            add(ONLY_IPV6_ALLOWED);
-                            add(PROTOCOL_ERRORS);
-                            add(RADIO_POWER_OFF);
-                            add(TETHERED_CALL_ACTIVE);
-                            add(RADIO_NOT_AVAILABLE);
-                            add(UNACCEPTABLE_NETWORK_PARAMETER);
-                            add(SIGNAL_LOST);
-                            add(DUPLICATE_CID);
-                            add(MATCH_ALL_RULE_NOT_ALLOWED);
-                            add(ALL_MATCHING_RULES_FAILED);
-                        }
-                    };
+                    permanentFailureSet = new HashSet<Integer>();
+                    permanentFailureSet.add(OPERATOR_BARRED);
+                    permanentFailureSet.add(MISSING_UNKNOWN_APN);
+                    permanentFailureSet.add(UNKNOWN_PDP_ADDRESS_TYPE);
+                    permanentFailureSet.add(USER_AUTHENTICATION);
+                    permanentFailureSet.add(ACTIVATION_REJECT_GGSN);
+                    permanentFailureSet.add(SERVICE_OPTION_NOT_SUPPORTED);
+                    permanentFailureSet.add(SERVICE_OPTION_NOT_SUBSCRIBED);
+                    permanentFailureSet.add(NSAPI_IN_USE);
+                    permanentFailureSet.add(ONLY_IPV4_ALLOWED);
+                    permanentFailureSet.add(ONLY_IPV6_ALLOWED);
+                    permanentFailureSet.add(PROTOCOL_ERRORS);
+                    permanentFailureSet.add(RADIO_POWER_OFF);
+                    permanentFailureSet.add(TETHERED_CALL_ACTIVE);
+                    permanentFailureSet.add(RADIO_NOT_AVAILABLE);
+                    permanentFailureSet.add(UNACCEPTABLE_NETWORK_PARAMETER);
+                    permanentFailureSet.add(SIGNAL_LOST);
+                    permanentFailureSet.add(DUPLICATE_CID);
+                    permanentFailureSet.add(MATCH_ALL_RULE_NOT_ALLOWED);
+                    permanentFailureSet.add(ALL_MATCHING_RULES_FAILED);
                 }
 
                 permanentFailureSet.add(NO_RETRY_FAILURE);
diff --git a/telephony/java/android/telephony/DomainSelectionService.aidl b/telephony/java/android/telephony/DomainSelectionService.aidl
new file mode 100644
index 0000000..b9d2ba8
--- /dev/null
+++ b/telephony/java/android/telephony/DomainSelectionService.aidl
@@ -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
+ */
+
+package android.telephony;
+
+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..c352f2b
--- /dev/null
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -0,0 +1,863 @@
+/*
+ * 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.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(@NetworkRegistrationInfo.Domain int domain) {
+            try {
+                mCallback.onDomainSelected(domain);
+            } 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;
+    }
+
+    /**
+     * Gets the {@link Executor} which executes methods of this service.
+     * This method should be private when this service is implemented in a separated process
+     * other than telephony framework.
+     * @return {@link Executor} instance.
+     * @hide
+     */
+    public @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/telephony/java/android/telephony/EmergencyRegResult.aidl b/telephony/java/android/telephony/EmergencyRegResult.aidl
new file mode 100644
index 0000000..f722962
--- /dev/null
+++ b/telephony/java/android/telephony/EmergencyRegResult.aidl
@@ -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
+ */
+
+package android.telephony;
+
+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/PreciseCallState.java b/telephony/java/android/telephony/PreciseCallState.java
index 98eeacf..f433609 100644
--- a/telephony/java/android/telephony/PreciseCallState.java
+++ b/telephony/java/android/telephony/PreciseCallState.java
@@ -66,6 +66,11 @@
     public static final int PRECISE_CALL_STATE_DISCONNECTED =   7;
     /** Call state: Disconnecting. */
     public static final int PRECISE_CALL_STATE_DISCONNECTING =  8;
+    /**
+     * Call state: Incoming in pre-alerting state i.e. prior to entering
+     * {@link #PRECISE_CALL_STATE_INCOMING}.
+     */
+    public static final int PRECISE_CALL_STATE_INCOMING_SETUP = 9;
 
     private @PreciseCallStates int mRingingCallState = PRECISE_CALL_STATE_NOT_VALID;
     private @PreciseCallStates int mForegroundCallState = PRECISE_CALL_STATE_NOT_VALID;
diff --git a/telephony/java/android/telephony/RadioAccessSpecifier.java b/telephony/java/android/telephony/RadioAccessSpecifier.java
index a403095..9511db6 100644
--- a/telephony/java/android/telephony/RadioAccessSpecifier.java
+++ b/telephony/java/android/telephony/RadioAccessSpecifier.java
@@ -161,9 +161,17 @@
     }
 
     @Override
-    public int hashCode () {
+    public int hashCode() {
         return ((mRadioAccessNetwork * 31)
                 + (Arrays.hashCode(mBands) * 37)
                 + (Arrays.hashCode(mChannels)) * 39);
     }
+
+    @Override
+    public String toString() {
+        return "RadioAccessSpecifier[mRadioAccessNetwork="
+                + AccessNetworkConstants.AccessNetworkType.toString(mRadioAccessNetwork)
+                + ", mBands=" + Arrays.toString(mBands)
+                + ", mChannels=" + Arrays.toString(mChannels) + "]";
+    }
 }
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index bfa60ba..7c600b1 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -138,12 +138,11 @@
      */
     public static final int FREQUENCY_RANGE_MMWAVE = 4;
 
-    private static final List<Integer> FREQUENCY_RANGE_ORDER = Arrays.asList(
-            FREQUENCY_RANGE_UNKNOWN,
-            FREQUENCY_RANGE_LOW,
-            FREQUENCY_RANGE_MID,
-            FREQUENCY_RANGE_HIGH,
-            FREQUENCY_RANGE_MMWAVE);
+    /**
+     * Number of frequency ranges.
+     * @hide
+     */
+    public static final int FREQUENCY_RANGE_COUNT = 5;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -2108,15 +2107,6 @@
     }
 
     /**
-     * @hide
-     */
-    public static final int getBetterNRFrequencyRange(int range1, int range2) {
-        return FREQUENCY_RANGE_ORDER.indexOf(range1) > FREQUENCY_RANGE_ORDER.indexOf(range2)
-                ? range1
-                : range2;
-    }
-
-    /**
      * Returns a copy of self with location-identifying information removed.
      * Always clears the NetworkRegistrationInfo's CellIdentity fields, but if removeCoarseLocation
      * is true, clears other info as well.
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/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index c2b65f8..95d5136 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -1020,6 +1020,17 @@
     }
 
     /**
+     * Return the encoding type of a received SMS message, which is specified using ENCODING_*
+     * GSM: defined in android.telephony.SmsConstants
+     * CDMA: defined in android.telephony.cdma.UserData
+     *
+     * @hide
+     */
+    public int getReceivedEncodingType() {
+        return mWrappedSmsMessage.getReceivedEncodingType();
+    }
+
+    /**
      * Determines whether or not to use CDMA format for MO SMS.
      * If SMS over IMS is supported, then format is based on IMS SMS format,
      * otherwise format is based on current phone type.
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index e055f63..3b84b65 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -649,6 +649,15 @@
     }
 
     /**
+     * @return {@code true} if the subscription is from the actively used SIM.
+     *
+     * @hide
+     */
+    public boolean isActive() {
+        return mSimSlotIndex >= 0 || mType == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM;
+    }
+
+    /**
      * Used in scenarios where different subscriptions are bundled as a group.
      * It's typically a primary and an opportunistic subscription. (see {@link #isOpportunistic()})
      * Such that those subscriptions will have some affiliated behaviors such as opportunistic
@@ -1527,7 +1536,7 @@
          */
         @NonNull
         public Builder setGroupUuid(@Nullable String groupUuid) {
-            mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid);
+            mGroupUuid = TextUtils.isEmpty(groupUuid) ? null : ParcelUuid.fromString(groupUuid);
             return this;
         }
 
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index ef693b5..5244f41 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -155,6 +155,10 @@
     private static final String CACHE_KEY_SLOT_INDEX_PROPERTY =
             "cache_key.telephony.get_slot_index";
 
+    /** The IPC cache key shared by all subscription manager service cacheable properties. */
+    private static final String CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY =
+            "cache_key.telephony.subscription_manager_service";
+
     /** @hide */
     public static final String GET_SIM_SPECIFIC_SETTINGS_METHOD_NAME = "getSimSpecificSettings";
 
@@ -269,32 +273,72 @@
                     CACHE_KEY_DEFAULT_SUB_ID_PROPERTY,
                     INVALID_SUBSCRIPTION_ID);
 
+    private static VoidPropertyInvalidatedCache<Integer> sGetDefaultSubIdCache =
+            new VoidPropertyInvalidatedCache<>(ISub::getDefaultSubId,
+                    CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+                    INVALID_SUBSCRIPTION_ID);
+
     private static VoidPropertyInvalidatedCache<Integer> sDefaultDataSubIdCache =
             new VoidPropertyInvalidatedCache<>(ISub::getDefaultDataSubId,
                     CACHE_KEY_DEFAULT_DATA_SUB_ID_PROPERTY,
                     INVALID_SUBSCRIPTION_ID);
 
+    private static VoidPropertyInvalidatedCache<Integer> sGetDefaultDataSubIdCache =
+            new VoidPropertyInvalidatedCache<>(ISub::getDefaultDataSubId,
+                    CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+                    INVALID_SUBSCRIPTION_ID);
+
     private static VoidPropertyInvalidatedCache<Integer> sDefaultSmsSubIdCache =
             new VoidPropertyInvalidatedCache<>(ISub::getDefaultSmsSubId,
                     CACHE_KEY_DEFAULT_SMS_SUB_ID_PROPERTY,
                     INVALID_SUBSCRIPTION_ID);
 
+    private static VoidPropertyInvalidatedCache<Integer> sGetDefaultSmsSubIdCache =
+            new VoidPropertyInvalidatedCache<>(ISub::getDefaultSmsSubId,
+                    CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+                    INVALID_SUBSCRIPTION_ID);
+
     private static VoidPropertyInvalidatedCache<Integer> sActiveDataSubIdCache =
             new VoidPropertyInvalidatedCache<>(ISub::getActiveDataSubscriptionId,
                     CACHE_KEY_ACTIVE_DATA_SUB_ID_PROPERTY,
                     INVALID_SUBSCRIPTION_ID);
 
+    private static VoidPropertyInvalidatedCache<Integer> sGetActiveDataSubscriptionIdCache =
+            new VoidPropertyInvalidatedCache<>(ISub::getActiveDataSubscriptionId,
+                    CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+                    INVALID_SUBSCRIPTION_ID);
+
     private static IntegerPropertyInvalidatedCache<Integer> sSlotIndexCache =
             new IntegerPropertyInvalidatedCache<>(ISub::getSlotIndex,
                     CACHE_KEY_SLOT_INDEX_PROPERTY,
                     INVALID_SIM_SLOT_INDEX);
 
+    private static IntegerPropertyInvalidatedCache<Integer> sGetSlotIndexCache =
+            new IntegerPropertyInvalidatedCache<>(ISub::getSlotIndex,
+                    CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+                    INVALID_SIM_SLOT_INDEX);
+
+    private static IntegerPropertyInvalidatedCache<Integer> sSubIdCache =
+            new IntegerPropertyInvalidatedCache<>(ISub::getSubId,
+                    CACHE_KEY_SLOT_INDEX_PROPERTY,
+                    INVALID_SUBSCRIPTION_ID);
+
+    private static IntegerPropertyInvalidatedCache<Integer> sGetSubIdCache =
+            new IntegerPropertyInvalidatedCache<>(ISub::getSubId,
+                    CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+                    INVALID_SUBSCRIPTION_ID);
+
     /** Cache depends on getDefaultSubId, so we use the defaultSubId cache key */
     private static IntegerPropertyInvalidatedCache<Integer> sPhoneIdCache =
             new IntegerPropertyInvalidatedCache<>(ISub::getPhoneId,
                     CACHE_KEY_DEFAULT_SUB_ID_PROPERTY,
                     INVALID_PHONE_INDEX);
 
+    private static IntegerPropertyInvalidatedCache<Integer> sGetPhoneIdCache =
+            new IntegerPropertyInvalidatedCache<>(ISub::getPhoneId,
+                    CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+                    INVALID_PHONE_INDEX);
+
     /**
      * Generates a content {@link Uri} used to receive updates on simInfo change
      * on the given subscriptionId
@@ -1293,6 +1337,8 @@
 
     private final Context mContext;
 
+    private static boolean sIsSubscriptionManagerServiceEnabled = false;
+
     // Cache of Resource that has been created in getResourcesForSubId. Key is a Pair containing
     // the Context and subId.
     private static final Map<Pair<Context, Integer>, Resources> sResourcesCache =
@@ -1378,6 +1424,19 @@
     public SubscriptionManager(Context context) {
         if (DBG) logd("SubscriptionManager created");
         mContext = context;
+
+        sIsSubscriptionManagerServiceEnabled = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_using_subscription_manager_service);
+    }
+
+    /**
+     * @return {@code true} if the new subscription manager service is used. This is temporary and
+     * will be removed before Android 14 release.
+     *
+     * @hide
+     */
+    public static boolean isSubscriptionManagerServiceEnabled() {
+        return sIsSubscriptionManagerServiceEnabled;
     }
 
     private NetworkPolicyManager getNetworkPolicyManager() {
@@ -1658,17 +1717,33 @@
     }
 
     /**
-     * @return List of all SubscriptionInfo records in database,
-     * include those that were inserted before, maybe empty but not null.
+     * Get all subscription info records from SIMs that are inserted now or were inserted before.
+     *
+     * <p>
+     * If the caller does not have {@link Manifest.permission#READ_PHONE_NUMBERS} permission,
+     * {@link SubscriptionInfo#getNumber()} will return empty string.
+     * If the caller does not have {@link Manifest.permission#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER},
+     * {@link SubscriptionInfo#getIccId()} and {@link SubscriptionInfo#getCardString()} will return
+     * empty string, and {@link SubscriptionInfo#getGroupUuid()} will return {@code null}.
+     *
+     * <p>
+     * The carrier app will always have full {@link SubscriptionInfo} for the subscriptions
+     * that it has carrier privilege.
+     *
+     * @return List of all {@link SubscriptionInfo} records from SIMs that are inserted or
+     * inserted before. Sorted by {@link SubscriptionInfo#getSimSlotIndex()}, then
+     * {@link SubscriptionInfo#getSubscriptionId()}.
+     *
      * @hide
      */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.READ_PHONE_STATE,
+            Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            "carrier privileges",
+    })
     @NonNull
-    @UnsupportedAppUsage
     public List<SubscriptionInfo> getAllSubscriptionInfoList() {
-        if (VDBG) logd("[getAllSubscriptionInfoList]+");
-
         List<SubscriptionInfo> result = null;
-
         try {
             ISub iSub = TelephonyManager.getSubscriptionService();
             if (iSub != null) {
@@ -1691,8 +1766,7 @@
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see
-     * {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, only records accessible
-     * to the calling app are returned.
+     * {@link TelephonyManager#hasCarrierPrivileges}).
      *
      * @return Sorted list of the currently {@link SubscriptionInfo} records available on the device.
      * <ul>
@@ -1710,7 +1784,6 @@
      * </li>
      * </ul>
      */
-    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
         return getActiveSubscriptionInfoList(/* userVisibleonly */true);
@@ -1914,17 +1987,12 @@
     }
 
     /**
+     * Get the active subscription count.
      *
-     * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
-     * or that the calling app has carrier privileges (see
-     * {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, the count will include
-     * only those subscriptions accessible to the caller.
+     * @return The current number of active subscriptions.
      *
-     * @return the current number of active subscriptions. There is no guarantee the value
-     * returned by this method will be the same as the length of the list returned by
-     * {@link #getActiveSubscriptionInfoList}.
+     * @see #getActiveSubscriptionInfoList()
      */
-    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public int getActiveSubscriptionInfoCount() {
         int result = 0;
@@ -2064,7 +2132,7 @@
     /**
      * Set SIM icon tint color for subscription ID
      * @param tint the RGB value of icon tint color of the SIM
-     * @param subId the unique Subscritpion ID in database
+     * @param subId the unique subscription ID in database
      * @return the number of records updated
      * @hide
      */
@@ -2072,7 +2140,7 @@
     public int setIconTint(@ColorInt int tint, int subId) {
         if (VDBG) logd("[setIconTint]+ tint:" + tint + " subId:" + subId);
         return setSubscriptionPropertyHelper(subId, "setIconTint",
-                (iSub)-> iSub.setIconTint(tint, subId)
+                (iSub)-> iSub.setIconTint(subId, tint)
         );
     }
 
@@ -2137,6 +2205,7 @@
      * subscriptionId doesn't have an associated slot index.
      */
     public static int getSlotIndex(int subscriptionId) {
+        if (isSubscriptionManagerServiceEnabled()) return sGetSlotIndexCache.query(subscriptionId);
         return sSlotIndexCache.query(subscriptionId);
     }
 
@@ -2148,7 +2217,7 @@
      */
     @Nullable
     public int[] getSubscriptionIds(int slotIndex) {
-        return getSubId(slotIndex);
+        return new int[]{getSubscriptionId(slotIndex)};
     }
 
     /** @hide */
@@ -2164,7 +2233,7 @@
         try {
             ISub iSub = TelephonyManager.getSubscriptionService();
             if (iSub != null) {
-                subId = iSub.getSubId(slotIndex);
+                subId = iSub.getSubIds(slotIndex);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -2173,9 +2242,27 @@
         return subId;
     }
 
+    /**
+     * Get the subscription id for specified slot index.
+     *
+     * @param slotIndex Logical SIM slot index.
+     * @return The subscription id. {@link #INVALID_SUBSCRIPTION_ID} if SIM is absent.
+     *
+     * @hide
+     */
+    public static int getSubscriptionId(int slotIndex) {
+        if (!isValidSlotIndex(slotIndex)) {
+            return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        }
+
+        if (isSubscriptionManagerServiceEnabled()) return sGetSubIdCache.query(slotIndex);
+        return sSubIdCache.query(slotIndex);
+    }
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public static int getPhoneId(int subId) {
+        if (isSubscriptionManagerServiceEnabled()) return sGetPhoneIdCache.query(subId);
         return sPhoneIdCache.query(subId);
     }
 
@@ -2197,6 +2284,7 @@
      * @return the "system" default subscription id.
      */
     public static int getDefaultSubscriptionId() {
+        if (isSubscriptionManagerServiceEnabled()) return sGetDefaultSubIdCache.query(null);
         return sDefaultSubIdCache.query(null);
     }
 
@@ -2285,6 +2373,7 @@
      * @return the default SMS subscription Id.
      */
     public static int getDefaultSmsSubscriptionId() {
+        if (isSubscriptionManagerServiceEnabled()) return sGetDefaultSmsSubIdCache.query(null);
         return sDefaultSmsSubIdCache.query(null);
     }
 
@@ -2319,6 +2408,7 @@
      * @return the default data subscription Id.
      */
     public static int getDefaultDataSubscriptionId() {
+        if (isSubscriptionManagerServiceEnabled()) return sGetDefaultDataSubIdCache.query(null);
         return sDefaultDataSubIdCache.query(null);
     }
 
@@ -2427,9 +2517,9 @@
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId) {
-        int[] subIds = SubscriptionManager.getSubId(phoneId);
-        if (subIds != null && subIds.length > 0) {
-            putPhoneIdAndSubIdExtra(intent, phoneId, subIds[0]);
+        int subId = SubscriptionManager.getSubscriptionId(phoneId);
+        if (isValidSubscriptionId(subId)) {
+            putPhoneIdAndSubIdExtra(intent, phoneId, subId);
         } else {
             logd("putPhoneIdAndSubIdExtra: no valid subs");
             intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);
@@ -3403,7 +3493,6 @@
 
     /**
      * Get subscriptionInfo list of subscriptions that are in the same group of given subId.
-     * See {@link #createSubscriptionGroup(List)} for more details.
      *
      * Caller must have {@link android.Manifest.permission#READ_PHONE_STATE}
      * or carrier privilege permission on the subscription.
@@ -3783,6 +3872,9 @@
      * SubscriptionManager.INVALID_SUBSCRIPTION_ID if not.
      */
     public static int getActiveDataSubscriptionId() {
+        if (isSubscriptionManagerServiceEnabled()) {
+            return sGetActiveDataSubscriptionIdCache.query(null);
+        }
         return sActiveDataSubIdCache.query(null);
     }
 
@@ -3826,6 +3918,11 @@
         PropertyInvalidatedCache.invalidateCache(CACHE_KEY_SLOT_INDEX_PROPERTY);
     }
 
+    /** @hide */
+    public static void invalidateSubscriptionManagerServiceCaches() {
+        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY);
+    }
+
     /**
      * Allows a test process to disable client-side caching operations.
      *
@@ -3837,7 +3934,16 @@
         sActiveDataSubIdCache.disableLocal();
         sDefaultSmsSubIdCache.disableLocal();
         sSlotIndexCache.disableLocal();
+        sSubIdCache.disableLocal();
         sPhoneIdCache.disableLocal();
+
+        sGetDefaultSubIdCache.disableLocal();
+        sGetDefaultDataSubIdCache.disableLocal();
+        sGetActiveDataSubscriptionIdCache.disableLocal();
+        sGetDefaultSmsSubIdCache.disableLocal();
+        sGetSlotIndexCache.disableLocal();
+        sGetSubIdCache.disableLocal();
+        sGetPhoneIdCache.disableLocal();
     }
 
     /**
@@ -3850,7 +3956,16 @@
         sActiveDataSubIdCache.clear();
         sDefaultSmsSubIdCache.clear();
         sSlotIndexCache.clear();
+        sSubIdCache.clear();
         sPhoneIdCache.clear();
+
+        sGetDefaultSubIdCache.clear();
+        sGetDefaultDataSubIdCache.clear();
+        sGetActiveDataSubscriptionIdCache.clear();
+        sGetDefaultSmsSubIdCache.clear();
+        sGetSlotIndexCache.clear();
+        sGetSubIdCache.clear();
+        sGetPhoneIdCache.clear();
     }
 
     /**
@@ -4002,6 +4117,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.
@@ -4100,6 +4219,26 @@
     }
 
     /**
+     * Convert phone number source to string.
+     *
+     * @param source The phone name source.
+     *
+     * @return The phone name source in string format.
+     *
+     * @hide
+     */
+    @NonNull
+    public static String phoneNumberSourceToString(@PhoneNumberSource int source) {
+        switch (source) {
+            case SubscriptionManager.PHONE_NUMBER_SOURCE_UICC: return "UICC";
+            case SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER: return "CARRIER";
+            case SubscriptionManager.PHONE_NUMBER_SOURCE_IMS: return "IMS";
+            default:
+                return "UNKNOWN(" + source + ")";
+        }
+    }
+
+    /**
      * Convert display name source to string.
      *
      * @param source The display name source.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ff1b1c0..3024b89 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -71,6 +71,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.os.WorkSource;
 import android.provider.Settings.SettingNotFoundException;
 import android.service.carrier.CarrierIdentifier;
@@ -2425,47 +2426,6 @@
         return getNaiBySubscriberId(getSubId());
     }
 
-    /**
-     * Returns the NAI. Return null if NAI is not available.
-     *
-     * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
-     * restrictions, and apps are recommended to use resettable identifiers (see <a
-     * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
-     * method can be invoked if one of the following requirements is met:
-     * <ul>
-     *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
-     *     is a privileged permission that can only be granted to apps preloaded on the device.
-     *     <li>If the calling app is the device owner of a fully-managed device, a profile
-     *     owner of an organization-owned device, or their delegates (see {@link
-     *     android.app.admin.DevicePolicyManager#getEnrollmentSpecificId()}).
-     *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
-     *     <li>If the calling app is the default SMS role holder (see {@link
-     *     RoleManager#isRoleHeld(String)}).
-     * </ul>
-     *
-     * <p>If the calling app does not meet one of these requirements then this method will behave
-     * as follows:
-     *
-     * <ul>
-     *     <li>If the calling app's target SDK is API level 28 or lower and the app has the
-     *     READ_PHONE_STATE permission then null is returned.</li>
-     *     <li>If the calling app's target SDK is API level 28 or lower and the app does not have
-     *     the READ_PHONE_STATE permission, or if the calling app is targeting API level 29 or
-     *     higher, then a SecurityException is thrown.</li>
-     * </ul>
-     *
-     *  @param slotIndex of which Nai is returned
-     */
-    /** {@hide}*/
-    @UnsupportedAppUsage
-    public String getNai(int slotIndex) {
-        int[] subId = SubscriptionManager.getSubId(slotIndex);
-        if (subId == null) {
-            return null;
-        }
-        return getNaiBySubscriberId(subId[0]);
-    }
-
     private String getNaiBySubscriberId(int subId) {
         try {
             IPhoneSubInfo info = getSubscriberInfoService();
@@ -2975,7 +2935,11 @@
     public static final int NETWORK_TYPE_HSUPA = TelephonyProtoEnums.NETWORK_TYPE_HSUPA; // = 9.
     /** Current network is HSPA */
     public static final int NETWORK_TYPE_HSPA = TelephonyProtoEnums.NETWORK_TYPE_HSPA; // = 10.
-    /** Current network is iDen */
+    /**
+     * Current network is iDen
+     * @deprecated Legacy network type no longer being used.
+     */
+    @Deprecated
     public static final int NETWORK_TYPE_IDEN = TelephonyProtoEnums.NETWORK_TYPE_IDEN; // = 11.
     /** Current network is EVDO revision B*/
     public static final int NETWORK_TYPE_EVDO_B = TelephonyProtoEnums.NETWORK_TYPE_EVDO_B; // = 12.
@@ -6179,46 +6143,6 @@
         }
     }
 
-    /**
-    * @hide
-    */
-    @UnsupportedAppUsage
-    private IPhoneSubInfo getSubscriberInfo() {
-        return getSubscriberInfoService();
-    }
-
-    /**
-     * Returns the Telephony call state for calls on a specific SIM slot.
-     * <p>
-     * Note: This method considers ONLY telephony/mobile calls, where {@link #getCallState()}
-     * considers the state of calls from other {@link android.telecom.ConnectionService}
-     * implementations.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} for applications
-     * targeting API level 31+ or that the calling application has carrier privileges
-     * (see {@link #hasCarrierPrivileges()}).
-     *
-     * @param slotIndex the SIM slot index to check call state for.
-     * @hide
-     */
-    @RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
-    public @CallState int getCallStateForSlot(int slotIndex) {
-        try {
-            int[] subId = SubscriptionManager.getSubId(slotIndex);
-            ITelephony telephony = getITelephony();
-            if (telephony == null || subId == null || subId.length  == 0) {
-                return CALL_STATE_IDLE;
-            }
-            return telephony.getCallStateForSubscription(subId[0], mContext.getPackageName(),
-                    mContext.getAttributionTag());
-        } catch (RemoteException | NullPointerException ex) {
-            // the phone process is restarting.
-            return CALL_STATE_IDLE;
-        }
-    }
-
-
     /** Data connection activity: No traffic. */
     public static final int DATA_ACTIVITY_NONE = 0x00000000;
     /** Data connection activity: Currently receiving IP PPP traffic. */
@@ -8294,16 +8218,23 @@
     /** Authentication type for UICC challenge is EAP AKA. See RFC 4187 for details. */
     public static final int AUTHTYPE_EAP_AKA = PhoneConstants.AUTH_CONTEXT_EAP_AKA;
     /**
-     * Authentication type for GBA Bootstrap Challenge is GBA_BOOTSTRAP.
-     * See 3GPP 33.220 Section 5.3.2.
-     * @hide
+     * Authentication type for GBA Bootstrap Challenge.
+     * Pass this authentication type into the {@link #getIccAuthentication} API to perform a GBA
+     * Bootstrap challenge (BSF), with {@code data} (generated according to the procedure defined in
+     * 3GPP 33.220 Section 5.3.2 step.4) in base64 encoding.
+     * This method will return the Bootstrapping response in base64 encoding when ICC authentication
+     * is completed.
+     * Ref 3GPP 33.220 Section 5.3.2.
      */
     public static final int AUTHTYPE_GBA_BOOTSTRAP = PhoneConstants.AUTH_CONTEXT_GBA_BOOTSTRAP;
     /**
-     * Authentication type for GBA Network Application Functions (NAF) key
-     * External Challenge is AUTHTYPE_GBA_NAF_KEY_EXTERNAL.
-     * See 3GPP 33.220 Section 5.3.2.
-     * @hide
+     * Authentication type for GBA Network Application Functions (NAF) key External Challenge.
+     * Pass this authentication type into the {@link #getIccAuthentication} API to perform a GBA
+     * Network Applications Functions (NAF) key External challenge using the NAF_ID parameter
+     * as the {@code data} in base64 encoding.
+     * This method will return the Ks_Ext_Naf key in base64 encoding when ICC authentication
+     * is completed.
+     * Ref 3GPP 33.220 Section 5.3.2.
      */
     public static final int AUTHTYPE_GBA_NAF_KEY_EXTERNAL =
             PhoneConstants.AUTHTYPE_GBA_NAF_KEY_EXTERNAL;
@@ -8332,7 +8263,8 @@
      *
      * @param appType the icc application type, like {@link #APPTYPE_USIM}
      * @param authType the authentication type, any one of {@link #AUTHTYPE_EAP_AKA} or
-     * {@link #AUTHTYPE_EAP_SIM}
+     * {@link #AUTHTYPE_EAP_SIM} or {@link #AUTHTYPE_GBA_BOOTSTRAP} or
+     * {@link #AUTHTYPE_GBA_NAF_KEY_EXTERNAL}
      * @param data authentication challenge data, base64 encoded.
      * See 3GPP TS 31.102 7.1.2 for more details.
      * @return the response of authentication. This value will be null in the following cases:
@@ -8360,7 +8292,8 @@
      * @param subId subscription ID used for authentication
      * @param appType the icc application type, like {@link #APPTYPE_USIM}
      * @param authType the authentication type, any one of {@link #AUTHTYPE_EAP_AKA} or
-     * {@link #AUTHTYPE_EAP_SIM}
+     * {@link #AUTHTYPE_EAP_SIM} or {@link #AUTHTYPE_GBA_BOOTSTRAP} or
+     * {@link #AUTHTYPE_GBA_NAF_KEY_EXTERNAL}
      * @param data authentication challenge data, base64 encoded.
      * See 3GPP TS 31.102 7.1.2 for more details.
      * @return the response of authentication. This value will be null in the following cases only
@@ -9476,7 +9409,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 {
@@ -9515,14 +9449,24 @@
     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
+     * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or
      * that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
-     * This can be called for following reasons
+     * This can be called for following reasons:
      * <ol>
-     * <li>Allowed network types control by USER {@link #ALLOWED_NETWORK_TYPES_REASON_USER}
+     * <li>Allowed network types control by USER
+     * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER}
      * <li>Allowed network types control by carrier {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER}
      * </ol>
      * This API will result in allowing an intersection of allowed network types for all reasons,
@@ -9532,7 +9476,13 @@
      * @param allowedNetworkTypes The bitmask of allowed network type
      * @throws IllegalStateException if the Telephony process is not currently available.
      * @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed.
-     * @throws SecurityException if the caller does not have the required privileges
+     * @throws SecurityException if the caller does not have the required privileges or if the
+     * caller tries to use one of the following security-based reasons without
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE} permissions.
+     * <ol>
+     *     <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}</li>
+     *     <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS}</li>
+     * </ol>
      */
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(
@@ -9606,6 +9556,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;
@@ -11984,8 +11935,9 @@
     }
 
     /**
-     * Gets the default Respond Via Message application, updating the cache if there is no
-     * respond-via-message application currently configured.
+     * Get the component name of the default app to direct respond-via-message intent for the
+     * user associated with this subscription, update the cache if there is no respond-via-message
+     * application currently configured for this user.
      * @return component name of the app and class to direct Respond Via Message intent to, or
      * {@code null} if the functionality is not supported.
      * @hide
@@ -11994,11 +11946,20 @@
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public @Nullable ComponentName getAndUpdateDefaultRespondViaMessageApplication() {
-        return SmsApplication.getDefaultRespondViaMessageApplication(mContext, true);
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getDefaultRespondViaMessageApplication(getSubId(), true);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error in getAndUpdateDefaultRespondViaMessageApplication: " + e);
+        }
+        return null;
     }
 
     /**
-     * Gets the default Respond Via Message application.
+     * Get the component name of the default app to direct respond-via-message intent for the
+     * user associated with this subscription.
      * @return component name of the app and class to direct Respond Via Message intent to, or
      * {@code null} if the functionality is not supported.
      * @hide
@@ -12007,7 +11968,15 @@
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public @Nullable ComponentName getDefaultRespondViaMessageApplication() {
-        return SmsApplication.getDefaultRespondViaMessageApplication(mContext, false);
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getDefaultRespondViaMessageApplication(getSubId(), false);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error in getDefaultRespondViaMessageApplication: " + e);
+        }
+        return null;
     }
 
     /**
@@ -12515,7 +12484,7 @@
             Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
         } catch (NullPointerException e) {
             AnomalyReporter.reportAnomaly(
-                    UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+                    UUID.fromString("e2bed88e-def9-476e-bd71-3e572a8de6d1"),
                     "getServiceStateForSubscriber " + subId + " NPE");
         }
         return null;
@@ -13972,6 +13941,7 @@
     public static final long NETWORK_TYPE_BITMASK_HSPA = (1 << (NETWORK_TYPE_HSPA -1));
     /**
      * network type bitmask indicating the support of radio tech iDen.
+     * @hide
      */
     public static final long NETWORK_TYPE_BITMASK_IDEN = (1 << (NETWORK_TYPE_IDEN - 1));
     /**
@@ -14941,21 +14911,132 @@
      * @return a Pair of (major version, minor version) or (-1,-1) if unknown.
      *
      * @hide
+     *
+     * @deprecated Use {@link #getHalVersion} instead.
      */
+    @Deprecated
     @UnsupportedAppUsage
     @TestApi
     public Pair<Integer, Integer> getRadioHalVersion() {
+        return getHalVersion(HAL_SERVICE_RADIO);
+    }
+
+    /** @hide */
+    public static final int HAL_SERVICE_RADIO = 0;
+
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioData
+     * {@link RadioDataProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_DATA = 1;
+
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioMessaging
+     * {@link RadioMessagingProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_MESSAGING = 2;
+
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioModem
+     * {@link RadioModemProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_MODEM = 3;
+
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioNetwork
+     * {@link RadioNetworkProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_NETWORK = 4;
+
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioSim
+     * {@link RadioSimProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_SIM = 5;
+
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioVoice
+     * {@link RadioVoiceProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_VOICE = 6;
+
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioIms
+     * {@link RadioImsProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_IMS = 7;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"HAL_SERVICE_"},
+            value = {
+                    HAL_SERVICE_RADIO,
+                    HAL_SERVICE_DATA,
+                    HAL_SERVICE_MESSAGING,
+                    HAL_SERVICE_MODEM,
+                    HAL_SERVICE_NETWORK,
+                    HAL_SERVICE_SIM,
+                    HAL_SERVICE_VOICE,
+                    HAL_SERVICE_IMS,
+            })
+    public @interface HalService {}
+
+    /**
+     * The HAL Version indicating that the version is unknown or invalid.
+     * @hide
+     */
+    @TestApi
+    public static final Pair HAL_VERSION_UNKNOWN = new Pair(-1, -1);
+
+    /**
+     * The HAL Version indicating that the version is unsupported.
+     * @hide
+     */
+    @TestApi
+    public static final Pair HAL_VERSION_UNSUPPORTED = new Pair(-2, -2);
+
+    /**
+     * Retrieve the HAL Version of a specific service for this device.
+     *
+     * Get the HAL version for a specific HAL interface for test purposes.
+     *
+     * @param halService the service id to query.
+     * @return a Pair of (major version, minor version), HAL_VERSION_UNKNOWN if unknown
+     * or HAL_VERSION_UNSUPPORTED if unsupported.
+     *
+     * @hide
+     */
+    @TestApi
+    public @NonNull Pair<Integer, Integer> getHalVersion(@HalService int halService) {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
-                int version = service.getRadioHalVersion();
-                if (version == -1) return new Pair<Integer, Integer>(-1, -1);
-                return new Pair<Integer, Integer>(version / 100, version % 100);
+                int version = service.getHalVersion(halService);
+                if (version != -1) {
+                    return new Pair<Integer, Integer>(version / 100, version % 100);
+                }
+            } else {
+                throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "getRadioHalVersion() RemoteException", e);
+            Log.e(TAG, "getHalVersion() RemoteException", e);
+            e.rethrowAsRuntimeException();
         }
-        return new Pair<Integer, Integer>(-1, -1);
+        return HAL_VERSION_UNKNOWN;
     }
 
     /**
@@ -17195,7 +17276,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}.
@@ -17209,12 +17296,13 @@
      * #KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}.
      * If displaying the network boost notification is throttled, it will be for the amount of time
      * specified by {@link CarrierConfigManager
-     * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_INT_ARRAY}.
+     * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}.
      * If a foreground application requests premium capabilities, the network boost notification
      * will be displayed to the user regardless of the throttled status.
      * We will show the network boost notification to the user up to the daily and monthly maximum
-     * number of times specified by {@link CarrierConfigManager
-     * #KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY}.
+     * number of times specified by
+     * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT} and
+     * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT}.
      * Subsequent attempts will return the same error until the request is no longer throttled
      * or throttling conditions change.
      */
@@ -17301,14 +17389,14 @@
     public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12;
 
     /**
-     * Purchase premium capability failed because the network is congested.
+     * Purchase premium capability failed because the entitlement check failed.
      * Subsequent attempts will be throttled for the amount of time specified by
      * {@link CarrierConfigManager
      * #KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
      * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
      * Throttling will be reevaluated when the network is no longer congested.
      */
-    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13;
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED = 13;
 
     /**
      * Purchase premium capability failed because the request was not made on the default data
@@ -17316,7 +17404,17 @@
      * Subsequent attempts will return the same error until the request is made on the default
      * data subscription.
      */
-    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14;
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION = 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 request the premium capability again.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15;
 
     /**
      * Results of the purchase premium capability request.
@@ -17335,8 +17433,9 @@
             PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT,
             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_ENTITLEMENT_CHECK_FAILED,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
     public @interface PurchasePremiumCapabilityResult {}
 
     /**
@@ -17373,10 +17472,12 @@
                 return "REQUEST_FAILED";
             case PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE:
                 return "NETWORK_NOT_AVAILABLE";
-            case PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED:
-                return "NETWORK_CONGESTED";
-            case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA:
-                return "NOT_DEFAULT_DATA";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED:
+                return "ENTITLEMENT_CHECK_FAILED";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION:
+                return "NOT_DEFAULT_DATA_SUBSCRIPTION";
+            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..b3682ca
--- /dev/null
+++ b/telephony/java/android/telephony/WwanSelectorCallback.java
@@ -0,0 +1,51 @@
+/*
+ * 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.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 domain The selected domain.
+     */
+    void onDomainSelected(@NetworkRegistrationInfo.Domain int domain);
+}
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 73aff43..a834e2bb 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -468,14 +468,14 @@
         final boolean isQosBearerSessionsSame =
                 (mQosBearerSessions == null || other.mQosBearerSessions == null)
                 ? mQosBearerSessions == other.mQosBearerSessions
-                : mQosBearerSessions.size() == other.mQosBearerSessions.size()
-                && mQosBearerSessions.containsAll(other.mQosBearerSessions);
+                : (mQosBearerSessions.size() == other.mQosBearerSessions.size()
+                        && mQosBearerSessions.containsAll(other.mQosBearerSessions));
 
         final boolean isTrafficDescriptorsSame =
                 (mTrafficDescriptors == null || other.mTrafficDescriptors == null)
                 ? mTrafficDescriptors == other.mTrafficDescriptors
-                : mTrafficDescriptors.size() == other.mTrafficDescriptors.size()
-                && mTrafficDescriptors.containsAll(other.mTrafficDescriptors);
+                : (mTrafficDescriptors.size() == other.mTrafficDescriptors.size()
+                        && mTrafficDescriptors.containsAll(other.mTrafficDescriptors));
 
         return mCause == other.mCause
                 && mSuggestedRetryTime == other.mSuggestedRetryTime
@@ -504,10 +504,35 @@
 
     @Override
     public int hashCode() {
+        // Generate order-independent hashes for lists
+        int addressesHash = mAddresses.stream()
+                .map(LinkAddress::hashCode)
+                .mapToInt(Integer::intValue)
+                .sum();
+        int dnsAddressesHash = mDnsAddresses.stream()
+                .map(InetAddress::hashCode)
+                .mapToInt(Integer::intValue)
+                .sum();
+        int gatewayAddressesHash = mGatewayAddresses.stream()
+                .map(InetAddress::hashCode)
+                .mapToInt(Integer::intValue)
+                .sum();
+        int pcscfAddressesHash = mPcscfAddresses.stream()
+                .map(InetAddress::hashCode)
+                .mapToInt(Integer::intValue)
+                .sum();
+        int qosBearerSessionsHash = mQosBearerSessions.stream()
+                .map(QosBearerSession::hashCode)
+                .mapToInt(Integer::intValue)
+                .sum();
+        int trafficDescriptorsHash = mTrafficDescriptors.stream()
+                .map(TrafficDescriptor::hashCode)
+                .mapToInt(Integer::intValue)
+                .sum();
         return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType,
-                mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses,
-                mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, mDefaultQos,
-                mQosBearerSessions, mSliceInfo, mTrafficDescriptors);
+                mInterfaceName, addressesHash, dnsAddressesHash, gatewayAddressesHash,
+                pcscfAddressesHash, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId,
+                mDefaultQos, qosBearerSessionsHash, mSliceInfo, trafficDescriptorsHash);
     }
 
     @Override
@@ -816,8 +841,8 @@
         /**
          * Set pdu session id.
          * <p/>
-         * The id must be between 1 and 15 when linked to a pdu session.  If no pdu session
-         * exists for the current data call, the id must be set to {@link PDU_SESSION_ID_NOT_SET}.
+         * The id must be between 1 and 15 when linked to a pdu session. If no pdu session
+         * exists for the current data call, the id must be set to {@link #PDU_SESSION_ID_NOT_SET}.
          *
          * @param pduSessionId Pdu Session Id of the data call.
          * @return The same instance of the builder.
@@ -858,6 +883,7 @@
          */
         public @NonNull Builder setQosBearerSessions(
                 @NonNull List<QosBearerSession> qosBearerSessions) {
+            Objects.requireNonNull(qosBearerSessions);
             mQosBearerSessions = qosBearerSessions;
             return this;
         }
@@ -891,6 +917,7 @@
          */
         public @NonNull Builder setTrafficDescriptors(
                 @NonNull List<TrafficDescriptor> trafficDescriptors) {
+            Objects.requireNonNull(trafficDescriptors);
             mTrafficDescriptors = trafficDescriptors;
             return this;
         }
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index 5e11163..f346b92 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -147,7 +147,7 @@
         if (mApnSetting != null) {
             return mApnSetting.getProtocol();
         }
-        return ApnSetting.PROTOCOL_IP;
+        return ApnSetting.PROTOCOL_IPV4V6;
     }
 
     /**
diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
index ba2b62d..8e27077 100644
--- a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
+++ b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
@@ -27,4 +27,5 @@
     oneway void createNetworkAvailabilityProvider(int slotId, IQualifiedNetworksServiceCallback callback);
     oneway void removeNetworkAvailabilityProvider(int slotId);
     oneway void reportThrottleStatusChanged(int slotId, in List<ThrottleStatus> statuses);
+    oneway void reportEmergencyDataNetworkPreferredTransportChanged (int slotId, int transportType);
 }
diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java
index 0ab7b61..a0d9c1bd 100644
--- a/telephony/java/android/telephony/data/QosBearerFilter.java
+++ b/telephony/java/android/telephony/data/QosBearerFilter.java
@@ -130,6 +130,10 @@
         return precedence;
     }
 
+    public int getProtocol() {
+        return protocol;
+    }
+
     public static class PortRange implements Parcelable {
         int start;
         int end;
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index fb97336..56f0f9f 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -68,6 +68,7 @@
     private static final int QNS_REMOVE_ALL_NETWORK_AVAILABILITY_PROVIDERS          = 3;
     private static final int QNS_UPDATE_QUALIFIED_NETWORKS                          = 4;
     private static final int QNS_APN_THROTTLE_STATUS_CHANGED                        = 5;
+    private static final int QNS_EMERGENCY_DATA_NETWORK_PREFERRED_TRANSPORT_CHANGED = 6;
 
     private final HandlerThread mHandlerThread;
 
@@ -193,6 +194,20 @@
         }
 
         /**
+         * The framework calls this method when the preferred transport type used to set up
+         * emergency data network is changed.
+         *
+         * This method is meant to be overridden.
+         *
+         * @param transportType transport type changed to be preferred
+         */
+        public void reportEmergencyDataNetworkPreferredTransportChanged(
+                @AccessNetworkConstants.TransportType int transportType) {
+            Log.d(TAG, "reportEmergencyDataNetworkPreferredTransportChanged: "
+                    + AccessNetworkConstants.transportTypeToString(transportType));
+        }
+
+        /**
          * Called when the qualified networks provider is removed. The extended class should
          * implement this method to perform cleanup works.
          */
@@ -237,6 +252,13 @@
                     }
                     break;
 
+                case QNS_EMERGENCY_DATA_NETWORK_PREFERRED_TRANSPORT_CHANGED:
+                    if (provider != null) {
+                        int transportType = (int) message.arg2;
+                        provider.reportEmergencyDataNetworkPreferredTransportChanged(transportType);
+                    }
+                    break;
+
                 case QNS_REMOVE_NETWORK_AVAILABILITY_PROVIDER:
                     if (provider != null) {
                         provider.close();
@@ -332,6 +354,14 @@
             mHandler.obtainMessage(QNS_APN_THROTTLE_STATUS_CHANGED, slotIndex, 0, statuses)
                     .sendToTarget();
         }
+
+        @Override
+        public void reportEmergencyDataNetworkPreferredTransportChanged(int slotIndex,
+                @AccessNetworkConstants.TransportType int transportType) {
+            mHandler.obtainMessage(
+                    QNS_EMERGENCY_DATA_NETWORK_PREFERRED_TRANSPORT_CHANGED,
+                            slotIndex, transportType).sendToTarget();
+        }
     }
 
     private void log(String s) {
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index d9d5c14..e78a1e1 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -660,9 +660,6 @@
         if (!first.getEmergencyUrns().equals(second.getEmergencyUrns())) {
             return false;
         }
-        if (first.getEmergencyCallRouting() != second.getEmergencyCallRouting()) {
-            return false;
-        }
         // Never merge two numbers if one of them is from test mode but the other one is not;
         // This supports to remove a number from the test mode.
         if (first.isFromSources(EMERGENCY_NUMBER_SOURCE_TEST)
@@ -685,12 +682,18 @@
     public static EmergencyNumber mergeSameEmergencyNumbers(@NonNull EmergencyNumber first,
                                                             @NonNull EmergencyNumber second) {
         if (areSameEmergencyNumbers(first, second)) {
+            int routing = first.getEmergencyCallRouting();
+
+            if (second.isFromSources(EMERGENCY_NUMBER_SOURCE_DATABASE)) {
+                routing = second.getEmergencyCallRouting();
+            }
+
             return new EmergencyNumber(first.getNumber(), first.getCountryIso(), first.getMnc(),
                     first.getEmergencyServiceCategoryBitmask(),
                     first.getEmergencyUrns(),
                     first.getEmergencyNumberSourceBitmask()
                             | second.getEmergencyNumberSourceBitmask(),
-                    first.getEmergencyCallRouting());
+                    routing);
         }
         return null;
     }
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index 0a2bb3d..b18eaa53 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -75,7 +75,6 @@
             CANCEL_REASON_TIMEOUT,
             CANCEL_REASON_PPR_NOT_ALLOWED
     })
-    /** @hide */
     public @interface CancelReason {
     }
 
@@ -105,7 +104,6 @@
             RESET_OPTION_DELETE_FIELD_LOADED_TEST_PROFILES,
             RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS
     })
-    /** @hide */
     public @interface ResetOption {
     }
 
@@ -118,7 +116,10 @@
     /** Resets the default SM-DP+ address. */
     public static final int RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS = 1 << 2;
 
-    /** Result code when the requested profile is not found */
+    /** Result code when the requested profile is not found
+     * @deprecated use {@link #RESULT_PROFILE_DOES_NOT_EXIST}
+     **/
+    @Deprecated
     public static final int RESULT_PROFILE_NOT_FOUND = 1;
 
     /** Result code of execution with no error. */
@@ -133,6 +134,9 @@
     /** Result code indicating the caller is not the active LPA. */
     public static final int RESULT_CALLER_NOT_ALLOWED = -3;
 
+    /** Result code when the requested profile does not exist */
+    public static final int RESULT_PROFILE_DOES_NOT_EXIST = -4;
+
     /**
      * Callback to receive the result of an eUICC card API.
      *
@@ -224,7 +228,7 @@
 
     /**
      * Requests the enabled profile for a given port on an eUicc. Callback with result code
-     * {@link RESULT_PROFILE_NOT_FOUND} and {@code NULL} EuiccProfile if there is no enabled
+     * {@link RESULT_PROFILE_DOES_NOT_EXIST} and {@code NULL} EuiccProfile if there is no enabled
      * profile on the target port.
      *
      * @param cardId    The Id of the eUICC.
diff --git a/telephony/java/android/telephony/euicc/EuiccNotification.java b/telephony/java/android/telephony/euicc/EuiccNotification.java
index c348cff..be0048f 100644
--- a/telephony/java/android/telephony/euicc/EuiccNotification.java
+++ b/telephony/java/android/telephony/euicc/EuiccNotification.java
@@ -44,7 +44,6 @@
             EVENT_DISABLE,
             EVENT_DELETE
     })
-    /** @hide */
     public @interface Event {}
 
     /** A profile is downloaded and installed. */
diff --git a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
index d5a05ae..1c6b6b6 100644
--- a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
+++ b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
@@ -42,7 +42,6 @@
     @IntDef(flag = true, prefix = { "POLICY_RULE_FLAG_" }, value = {
             POLICY_RULE_FLAG_CONSENT_REQUIRED
     })
-    /** @hide */
     public @interface PolicyRuleFlag {}
 
     /** User consent is required to install the profile. */
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index e6d7df3..1ea7fdc 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -78,8 +78,9 @@
     public static final int SERVICE_TYPE_EMERGENCY = 2;
 
     /**
-     * Call types
+     * Call type none
      */
+    public static final int CALL_TYPE_NONE = 0;
     /**
      * IMSPhone to support IR.92 & IR.94 (voice + video upgrade/downgrade)
      */
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index e8642fe..33c86d8 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -50,7 +50,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.concurrent.CancellationException;
@@ -179,10 +178,9 @@
      * Used for logging purposes, see {@link #getCapabilitiesString(long)}
      * @hide
      */
-    private static final Map<Long, String> CAPABILITIES_LOG_MAP = new HashMap<Long, String>() {{
-            put(CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL");
-            put(CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION");
-        }};
+    private static final Map<Long, String> CAPABILITIES_LOG_MAP = Map.of(
+            CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL",
+            CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION");
 
     /**
      * The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService.
@@ -458,8 +456,8 @@
         }
     }
 
-    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(getCachedExecutor());
             setupFeature(f, slotId, ImsFeature.FEATURE_RCS);
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index 090d413..9996b86 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -78,24 +78,22 @@
     /**@hide*/
     // Translate ImsRegistrationImplBase API to new AccessNetworkConstant because WLAN
     // and WWAN are more accurate constants.
-    Map<Integer, Integer> IMS_REG_TO_ACCESS_TYPE_MAP =
-            new HashMap<Integer, Integer>() {{
-                // Map NONE to -1 to make sure that we handle the REGISTRATION_TECH_NONE
-                // case, since it is defined.
-                put(ImsRegistrationImplBase.REGISTRATION_TECH_NONE,
-                        AccessNetworkConstants.TRANSPORT_TYPE_INVALID);
-                put(ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
-                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-                put(ImsRegistrationImplBase.REGISTRATION_TECH_NR,
-                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-                put(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
-                        AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-                /* As the cross sim will be using ePDG tunnel over internet, it behaves
-                   like IWLAN in most cases. Hence setting the access type as IWLAN
-                 */
-                put(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
-                        AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-            }};
+    Map<Integer, Integer> IMS_REG_TO_ACCESS_TYPE_MAP = Map.of(
+            // Map NONE to -1 to make sure that we handle the REGISTRATION_TECH_NONE
+            // case, since it is defined.
+            ImsRegistrationImplBase.REGISTRATION_TECH_NONE,
+                    AccessNetworkConstants.TRANSPORT_TYPE_INVALID,
+            ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+            ImsRegistrationImplBase.REGISTRATION_TECH_NR,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+            ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+            /* As the cross sim will be using ePDG tunnel over internet, it behaves
+               like IWLAN in most cases. Hence setting the access type as IWLAN
+             */
+            ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
 
     /** @hide */
     @NonNull
diff --git a/telephony/java/android/telephony/ims/SrvccCall.aidl b/telephony/java/android/telephony/ims/SrvccCall.aidl
new file mode 100644
index 0000000..0f0a079
--- /dev/null
+++ b/telephony/java/android/telephony/ims/SrvccCall.aidl
@@ -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.
+ */
+
+package android.telephony.ims;
+
+parcelable SrvccCall;
diff --git a/telephony/java/android/telephony/ims/SrvccCall.java b/telephony/java/android/telephony/ims/SrvccCall.java
new file mode 100644
index 0000000..cdc271e
--- /dev/null
+++ b/telephony/java/android/telephony/ims/SrvccCall.java
@@ -0,0 +1,153 @@
+/*
+ * 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.ims;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.Annotation.PreciseCallStates;
+
+import java.util.Objects;
+
+/**
+ * A Parcelable object to represent the current state of an IMS call that is being tracked
+ * in the ImsService when an SRVCC begins. This information will be delivered to modem.
+ * @see SrvccStartedCallback
+ *
+ * @hide
+ */
+@SystemApi
+public final class SrvccCall implements Parcelable {
+    private static final String TAG = "SrvccCall";
+
+    /** The IMS call profile */
+    private ImsCallProfile mImsCallProfile;
+
+    /** The IMS call id */
+    private String mCallId;
+
+    /** The call state */
+    private @PreciseCallStates int mCallState;
+
+    private SrvccCall(Parcel in) {
+        readFromParcel(in);
+    }
+
+    /**
+     * Constructs an instance of SrvccCall.
+     *
+     * @param callId the call ID associated with the IMS call
+     * @param callState the state of this IMS call
+     * @param imsCallProfile the profile associated with this IMS call
+     * @throws IllegalArgumentException if the callId or the imsCallProfile is null
+     */
+    public SrvccCall(@NonNull String callId, @PreciseCallStates int callState,
+            @NonNull ImsCallProfile imsCallProfile) {
+        if (callId == null) throw new IllegalArgumentException("callId is null");
+        if (imsCallProfile == null) throw new IllegalArgumentException("imsCallProfile is null");
+
+        mCallId = callId;
+        mCallState = callState;
+        mImsCallProfile = imsCallProfile;
+    }
+
+    /**
+     * @return the {@link ImsCallProfile} associated with this IMS call,
+     * which will be used to get the address, the name, and the audio direction
+     * including the call in pre-alerting state.
+     */
+    @NonNull
+    public ImsCallProfile getImsCallProfile() {
+        return mImsCallProfile;
+    }
+
+    /**
+     * @return the call ID associated with this IMS call.
+     *
+     * @see android.telephony.ims.stub.ImsCallSessionImplBase#getCallId().
+     */
+    @NonNull
+    public String getCallId() {
+        return mCallId;
+    }
+
+    /**
+     * @return the call state of the associated IMS call.
+     */
+    public @PreciseCallStates int getPreciseCallState() {
+        return mCallState;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "{ callId=" + mCallId
+                + ", callState=" + mCallState
+                + ", imsCallProfile=" + mImsCallProfile
+                + " }";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SrvccCall that = (SrvccCall) o;
+        return mImsCallProfile.equals(that.mImsCallProfile)
+                && mCallId.equals(that.mCallId)
+                && mCallState == that.mCallState;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Objects.hash(mImsCallProfile, mCallId);
+        result = 31 * result + mCallState;
+        return result;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeString(mCallId);
+        out.writeInt(mCallState);
+        out.writeParcelable(mImsCallProfile, 0);
+    }
+
+    private void readFromParcel(Parcel in) {
+        mCallId = in.readString();
+        mCallState = in.readInt();
+        mImsCallProfile = in.readParcelable(ImsCallProfile.class.getClassLoader(),
+                android.telephony.ims.ImsCallProfile.class);
+    }
+
+    public static final @android.annotation.NonNull Creator<SrvccCall> CREATOR =
+            new Creator<SrvccCall>() {
+        @Override
+        public SrvccCall createFromParcel(Parcel in) {
+            return new SrvccCall(in);
+        }
+
+        @Override
+        public SrvccCall[] newArray(int size) {
+            return new SrvccCall[size];
+        }
+    };
+}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
index 801b81c..ea4480d 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
@@ -20,6 +20,7 @@
 import android.telephony.ims.aidl.IImsMmTelListener;
 import android.telephony.ims.aidl.IImsSmsListener;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
+import android.telephony.ims.aidl.ISrvccStartedCallback;
 import android.telephony.ims.feature.CapabilityChangeRequest;
 import android.telephony.ims.RtpHeaderExtensionType;
 
@@ -55,11 +56,16 @@
             IImsCapabilityCallback c);
     oneway void queryCapabilityConfiguration(int capability, int radioTech,
             IImsCapabilityCallback c);
+    oneway void notifySrvccStarted(in ISrvccStartedCallback cb);
+    oneway void notifySrvccCompleted();
+    oneway void notifySrvccFailed();
+    oneway void notifySrvccCanceled();
     // SMS APIs
     void setSmsListener(IImsSmsListener l);
     oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry,
             in byte[] pdu);
     oneway void acknowledgeSms(int token, int messageRef, int result);
+    oneway void acknowledgeSmsWithPdu(int token, int messageRef, int result, in byte[] pdu);
     oneway void acknowledgeSmsReport(int token, int messageRef, int result);
     String getSmsFormat();
     oneway void onSmsReady();
diff --git a/telephony/java/android/telephony/ims/aidl/ISrvccStartedCallback.aidl b/telephony/java/android/telephony/ims/aidl/ISrvccStartedCallback.aidl
new file mode 100644
index 0000000..a173abf
--- /dev/null
+++ b/telephony/java/android/telephony/ims/aidl/ISrvccStartedCallback.aidl
@@ -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 android.telephony.ims.aidl;
+
+import android.telephony.ims.SrvccCall;
+
+import java.util.List;
+
+/**
+ * {@hide}
+ */
+oneway interface ISrvccStartedCallback {
+    void onSrvccCallNotified(in List<SrvccCall> profiles);
+}
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index f5b158f..174675f 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -34,7 +34,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -85,11 +84,10 @@
      * Used for logging purposes.
      * @hide
      */
-    public static final Map<Integer, String> FEATURE_LOG_MAP = new HashMap<Integer, String>() {{
-            put(FEATURE_EMERGENCY_MMTEL, "EMERGENCY_MMTEL");
-            put(FEATURE_MMTEL, "MMTEL");
-            put(FEATURE_RCS, "RCS");
-        }};
+    public static final Map<Integer, String> FEATURE_LOG_MAP = Map.of(
+            FEATURE_EMERGENCY_MMTEL, "EMERGENCY_MMTEL",
+            FEATURE_MMTEL, "MMTEL",
+            FEATURE_RCS, "RCS");
 
     /**
      * Integer values defining IMS features that are supported in ImsFeature.
@@ -145,11 +143,10 @@
      * Used for logging purposes.
      * @hide
      */
-    public static final Map<Integer, String> STATE_LOG_MAP = new HashMap<Integer, String>() {{
-            put(STATE_UNAVAILABLE, "UNAVAILABLE");
-            put(STATE_INITIALIZING, "INITIALIZING");
-            put(STATE_READY, "READY");
-        }};
+    public static final Map<Integer, String> STATE_LOG_MAP = Map.of(
+            STATE_UNAVAILABLE, "UNAVAILABLE",
+            STATE_INITIALIZING, "INITIALIZING",
+            STATE_READY, "READY");
 
     /**
      * Integer values defining the result codes that should be returned from
@@ -394,10 +391,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 +408,9 @@
      */
     @VisibleForTesting
     public void removeImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) {
-        mStatusCallbacks.unregister(c);
+        synchronized (mStatusCallbacks) {
+            mStatusCallbacks.unregister(c);
+        }
     }
 
     /**
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index c0ff12e..d776928 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -31,10 +31,12 @@
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsService;
 import android.telephony.ims.RtpHeaderExtensionType;
+import android.telephony.ims.SrvccCall;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsMmTelFeature;
 import android.telephony.ims.aidl.IImsMmTelListener;
 import android.telephony.ims.aidl.IImsSmsListener;
+import android.telephony.ims.aidl.ISrvccStartedCallback;
 import android.telephony.ims.stub.ImsCallSessionImplBase;
 import android.telephony.ims.stub.ImsEcbmImplBase;
 import android.telephony.ims.stub.ImsMultiEndpointImplBase;
@@ -60,6 +62,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -272,6 +275,12 @@
         }
 
         @Override
+        public void acknowledgeSmsWithPdu(int token, int messageRef, int result, byte[] pdu) {
+            executeMethodAsyncNoException(() -> MmTelFeature.this
+                    .acknowledgeSms(token, messageRef, result, pdu), "acknowledgeSms");
+        }
+
+        @Override
         public void acknowledgeSmsReport(int token, int messageRef, int result) {
             executeMethodAsyncNoException(() -> MmTelFeature.this
                     .acknowledgeSmsReport(token, messageRef, result), "acknowledgeSmsReport");
@@ -289,6 +298,38 @@
                     "onSmsReady");
         }
 
+        @Override
+        public void notifySrvccStarted(final ISrvccStartedCallback cb) {
+            executeMethodAsyncNoException(
+                    () -> MmTelFeature.this.notifySrvccStarted(
+                            (profiles) -> {
+                                try {
+                                    cb.onSrvccCallNotified(profiles);
+                                } catch (Exception e) {
+                                    Log.e(LOG_TAG, "onSrvccCallNotified e=" + e);
+                                }
+                            }),
+                    "notifySrvccStarted");
+        }
+
+        @Override
+        public void notifySrvccCompleted() {
+            executeMethodAsyncNoException(
+                    () -> MmTelFeature.this.notifySrvccCompleted(), "notifySrvccCompleted");
+        }
+
+        @Override
+        public void notifySrvccFailed() {
+            executeMethodAsyncNoException(
+                    () -> MmTelFeature.this.notifySrvccFailed(), "notifySrvccFailed");
+        }
+
+        @Override
+        public void notifySrvccCanceled() {
+            executeMethodAsyncNoException(
+                    () -> MmTelFeature.this.notifySrvccCanceled(), "notifySrvccCanceled");
+        }
+
         // Call the methods with a clean calling identity on the executor and wait indefinitely for
         // the future to return.
         private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
@@ -969,6 +1010,75 @@
                 "Not implemented on device.");
     }
 
+    /**
+     * Notifies the MmTelFeature that the network has initiated an SRVCC (Single radio voice
+     * call continuity) for all IMS calls. When the network initiates an SRVCC, calls from
+     * the LTE domain are handed over to the legacy circuit switched domain. The modem requires
+     * knowledge of ongoing calls in the IMS domain in order to complete the SRVCC operation.
+     * <p>
+     * @param consumer The callback used to notify the framework of the list of IMS calls and their
+     * state at the time of the SRVCC.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void notifySrvccStarted(@NonNull Consumer<List<SrvccCall>> consumer) {
+        // Base Implementation - Should be overridden by IMS service
+    }
+
+    /**
+     * Notifies the MmTelFeature that the SRVCC is completed and the calls have been moved
+     * over to the circuit-switched domain.
+     * {@link android.telephony.CarrierConfigManager.ImsVoice#KEY_SRVCC_TYPE_INT_ARRAY}
+     * specifies the calls can be moved. Other calls will be disconnected.
+     * <p>
+     * The MmTelFeature may now release all resources related to the IMS calls.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void notifySrvccCompleted() {
+        // Base Implementation - Should be overridden by IMS service
+    }
+
+    /**
+     * Notifies the MmTelFeature that the SRVCC has failed.
+     *
+     * The handover can fail by encountering a failure at the radio level
+     * or temporary MSC server internal errors in handover procedure.
+     * Refer to 3GPP TS 23.216 section 8 Handover Failure.
+     * <p>
+     * IMS service will recover and continue calls over IMS.
+     * Per TS 24.237 12.2.4.2, UE shall send SIP UPDATE request containing the reason-text
+     * set to "failure to transition to CS domain".
+     *
+     * @hide
+     */
+    @SystemApi
+    public void notifySrvccFailed() {
+        // Base Implementation - Should be overridden by IMS service
+    }
+
+    /**
+     * Notifies the MmTelFeature that the SRVCC has been canceled.
+     *
+     * Since the state of network can be changed, the network can decide to terminate
+     * the handover procedure before its completion and to return to its state before the handover
+     * procedure was triggered.
+     * Refer to 3GPP TS 23.216 section 8.1.3 Handover Cancellation.
+     *
+     * <p>
+     * IMS service will recover and continue calls over IMS.
+     * Per TS 24.237 12.2.4.2, UE shall send SIP UPDATE request containing the reason-text
+     * set to "handover canceled".
+     *
+     * @hide
+     */
+    @SystemApi
+    public void notifySrvccCanceled() {
+        // Base Implementation - Should be overridden by IMS service
+    }
+
     private void setSmsListener(IImsSmsListener listener) {
         getSmsImplementation().registerSmsListener(listener);
     }
@@ -983,6 +1093,11 @@
         getSmsImplementation().acknowledgeSms(token, messageRef, result);
     }
 
+    private void acknowledgeSms(int token, int messageRef,
+            @ImsSmsImplBase.DeliverStatusResult int result, byte[] pdu) {
+        getSmsImplementation().acknowledgeSms(token, messageRef, result, pdu);
+    }
+
     private void acknowledgeSmsReport(int token, int messageRef,
             @ImsSmsImplBase.StatusReportResult int result) {
         getSmsImplementation().acknowledgeSmsReport(token, messageRef, result);
diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index fb997d1..66833d1 100644
--- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.RemoteException;
 import android.telephony.SmsManager;
@@ -174,6 +175,9 @@
      * {@link #onSmsReceived(int, String, byte[])} has been called to deliver the result to the IMS
      * provider.
      *
+     * If the framework needs to provide the PDU used to acknowledge the SMS,
+     * {@link #acknowledgeSms(int, int, int, byte[])} will be called.
+     *
      * @param token token provided in {@link #onSmsReceived(int, String, byte[])}
      * @param messageRef the message reference, which may be 1 byte if it is in
      *     {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
@@ -186,6 +190,27 @@
     }
 
     /**
+     * This method will be called by the platform after
+     * {@link #onSmsReceived(int, String, byte[])} has been called to acknowledge an incoming SMS.
+     *
+     * This method is only called in cases where the framework needs to provide the PDU such as the
+     * case where we provide the Short Message Transfer Layer PDU (see 3GPP TS 23.040). Otherwise,
+     * {@link #acknowledgeSms(int, int, int)} will be used.
+     *
+     * @param token token provided in {@link #onSmsReceived(int, String, byte[])}
+     * @param messageRef the message reference, which may be 1 byte if it is in
+     *     {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+     *     {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+     * @param result result of delivering the message.
+     * @param pdu PDU representing the contents of the message.
+     */
+    public void acknowledgeSms(int token, @IntRange(from = 0, to = 65535)  int messageRef,
+            @DeliverStatusResult int result, @NonNull byte[] pdu) {
+        Log.e(LOG_TAG, "acknowledgeSms() not implemented. acknowledgeSms(int, int, int) called.");
+        acknowledgeSms(token, messageRef, result);
+    }
+
+    /**
      * This method will be triggered by the platform after
      * {@link #onSmsStatusReportReceived(int, int, String, byte[])} or
      * {@link #onSmsStatusReportReceived(int, String, byte[])} has been called to provide the
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/telephony/java/com/android/internal/telephony/IDomainSelector.aidl b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
new file mode 100644
index 0000000..d94840b
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.DomainSelectionService.SelectionAttributes;
+
+oneway interface IDomainSelector {
+    void cancelSelection();
+    void reselectDomain(in SelectionAttributes attr);
+    void finishSelection();
+}
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
old mode 100755
new mode 100644
index 4752cca..e9cea68
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -114,14 +114,6 @@
     oneway void requestEmbeddedSubscriptionInfoListRefresh(int cardId);
 
     /**
-     * Add a new SubscriptionInfo to subinfo database if needed
-     * @param iccId the IccId of the SIM card
-     * @param slotIndex the slot which the SIM is inserted
-     * @return the URL of the newly created row or the updated row
-     */
-    int addSubInfoRecord(String iccId, int slotIndex);
-
-    /**
      * Add a new subscription info record, if needed
      * @param uniqueId This is the unique identifier for the subscription within the specific
      *                 subscription type.
@@ -143,11 +135,11 @@
 
     /**
      * Set SIM icon tint color by simInfo index
-     * @param tint the icon tint color of the SIM
      * @param subId the unique SubscriptionInfo index in database
+     * @param tint the icon tint color of the SIM
      * @return the number of records updated
      */
-    int setIconTint(int tint, int subId);
+    int setIconTint(int subId, int tint);
 
     /**
      * Set display name by simInfo index with name source
@@ -244,7 +236,9 @@
 
     int getSlotIndex(int subId);
 
-    int[] getSubId(int slotIndex);
+    int[] getSubIds(int slotIndex);
+
+    int getSubId(int slotIndex);
 
     int getDefaultSubId();
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 648866b..616ea50 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.os.Bundle;
@@ -2159,6 +2160,12 @@
     int getRadioHalVersion();
 
     /**
+     * Get the HAL Version of a specific service
+     * encoded as 100 * MAJOR_VERSION + MINOR_VERSION or -1 if unknown
+     */
+    int getHalVersion(int service);
+
+    /**
      * Get the current calling package name.
      */
     String getCurrentPackageName();
@@ -2612,4 +2619,14 @@
      * @hide
      */
     boolean isRemovableEsimDefaultEuicc(String callingPackage);
+
+     /**
+      * Get the component name of the default app to direct respond-via-message intent for the
+      * user associated with this subscription, update the cache if there is no respond-via-message
+      * application currently configured for this user.
+      * @return component name of the app and class to direct Respond Via Message intent to, or
+      * {@code null} if the functionality is not supported.
+      * @hide
+      */
+      ComponentName getDefaultRespondViaMessageApplication(int subId, boolean updateIfNeeded);
 }
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/telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
new file mode 100644
index 0000000..1460b45
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.IWwanSelectorCallback;
+
+oneway interface ITransportSelectorResultCallback {
+    void onCompleted(in IWwanSelectorCallback cb);
+}
diff --git a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
new file mode 100644
index 0000000..339fbee
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.IWwanSelectorResultCallback;
+
+oneway interface IWwanSelectorCallback {
+    void onRequestEmergencyNetworkScan(in int[] preferredNetworks,
+            int scanType, in IWwanSelectorResultCallback cb);
+    void onDomainSelected(int domain);
+    void onCancel();
+}
diff --git a/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
new file mode 100644
index 0000000..0d61fcb
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.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..a1257e3 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -536,6 +536,18 @@
     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;
+    int RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED = 239;
+    int RIL_REQUEST_UPDATE_IMS_CALL_STATUS = 240;
+    int RIL_REQUEST_SET_N1_MODE_ENABLED = 241;
+    int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
+    int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243;
+    int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -607,4 +619,8 @@
     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;
+    int RIL_UNSOL_ON_NETWORK_INITIATED_LOCATION_RESULT = 1110;
 }
diff --git a/telephony/java/com/android/internal/telephony/SmsMessageBase.java b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
index 6d46ed3..0cc1e98 100644
--- a/telephony/java/com/android/internal/telephony/SmsMessageBase.java
+++ b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.telephony.SmsMessage;
@@ -94,6 +96,15 @@
     protected boolean mMwiDontStore;
 
     /**
+     * The encoding type of a received SMS message, which is specified using ENCODING_*
+     * GSM: defined in android.telephony.SmsConstants
+     * CDMA: defined in android.telephony.cdma.UserData
+     *
+     * @hide
+     */
+    protected int mReceivedEncodingType = ENCODING_UNKNOWN;
+
+    /**
      * Indicates status for messages stored on the ICC.
      */
     protected int mStatusOnIcc = -1;
@@ -512,4 +523,8 @@
 
         return mRecipientAddress.getAddressString();
     }
+
+    public int getReceivedEncodingType() {
+        return mReceivedEncodingType;
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index f636276..b51ba31 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -780,6 +780,7 @@
             mUserData = mBearerData.userData.payload;
             mUserDataHeader = mBearerData.userData.userDataHeader;
             mMessageBody = mBearerData.userData.payloadStr;
+            mReceivedEncodingType = mBearerData.userData.msgEncoding;
         }
 
         if (mOriginatingAddress != null) {
@@ -860,6 +861,9 @@
             Rlog.w(LOG_TAG, "BearerData.decode() returned null");
             return null;
         }
+        if (bData.userData != null) {
+            mReceivedEncodingType = bData.userData.msgEncoding;
+        }
 
         if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
             Rlog.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData));
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index 7a5bf06..09ead31 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -1462,28 +1462,28 @@
             } else {
                 switch ((mDataCodingScheme >> 2) & 0x3) {
                 case 0: // GSM 7 bit default alphabet
-                    encodingType = ENCODING_7BIT;
-                    break;
+                        encodingType = ENCODING_7BIT;
+                        break;
 
                 case 2: // UCS 2 (16bit)
-                    encodingType = ENCODING_16BIT;
-                    break;
+                        encodingType = ENCODING_16BIT;
+                        break;
 
                 case 1: // 8 bit data
-                    //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
-                    //that's stored in 8-bit unpacked format) characters.
-                    if (r.getBoolean(com.android.internal.
-                            R.bool.config_sms_decode_gsm_8bit_data)) {
-                        encodingType = ENCODING_8BIT;
-                        break;
-                    }
+                        // Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet
+                        // string that's stored in 8-bit unpacked format) characters.
+                        if (r.getBoolean(com.android.internal
+                                .R.bool.config_sms_decode_gsm_8bit_data)) {
+                            encodingType = ENCODING_8BIT;
+                            break;
+                        }
 
                 case 3: // reserved
-                    Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
-                            + (mDataCodingScheme & 0xff));
-                    encodingType = r.getInteger(
-                            com.android.internal.R.integer.default_reserved_data_coding_scheme);
-                    break;
+                        Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
+                                + (mDataCodingScheme & 0xff));
+                        encodingType = r.getInteger(
+                                com.android.internal.R.integer.default_reserved_data_coding_scheme);
+                        break;
                 }
             }
         } else if ((mDataCodingScheme & 0xf0) == 0xf0) {
@@ -1558,6 +1558,7 @@
                 encodingType == ENCODING_7BIT);
         this.mUserData = p.getUserData();
         this.mUserDataHeader = p.getUserDataHeader();
+        this.mReceivedEncodingType = encodingType;
 
         /*
          * Look for voice mail indication in TP_UDH TS23.040 9.2.3.24
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index d96005b..591ffeb 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -25,7 +25,6 @@
 import android.platform.test.annotations.RootPermissionTest;
 
 import com.android.blockdevicewriter.BlockDeviceWriter;
-import com.android.fsverity.AddFsVerityCertRule;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -36,7 +35,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -44,6 +42,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Set;
 
 /**
  * This test makes sure app installs with fs-verity signature, and on-access verification works.
@@ -90,10 +89,6 @@
     /** Only 4K page is supported by fs-verity currently. */
     private static final int FSVERITY_PAGE_SIZE = 4096;
 
-    @Rule
-    public final AddFsVerityCertRule mAddFsVerityCertRule =
-            new AddFsVerityCertRule(this, CERT_PATH);
-
     private ITestDevice mDevice;
     private boolean mDmRequireFsVerity;
 
@@ -103,11 +98,13 @@
         mDmRequireFsVerity = "true".equals(
                 mDevice.getProperty("pm.dexopt.dm.require_fsverity"));
 
+        expectRemoteCommandToSucceed("cmd file_integrity append-cert " + CERT_PATH);
         uninstallPackage(TARGET_PACKAGE);
     }
 
     @After
     public void tearDown() throws DeviceNotAvailableException {
+        expectRemoteCommandToSucceed("cmd file_integrity remove-last-cert");
         uninstallPackage(TARGET_PACKAGE);
     }
 
@@ -469,10 +466,10 @@
                     break;
                 }
                 try {
-                    CLog.d("lsof: " + expectRemoteCommandToSucceed("lsof " + apkPath));
+                    String openFiles = expectRemoteCommandToSucceed("lsof " + apkPath);
+                    CLog.d("lsof: " + openFiles);
                     Thread.sleep(1000);
-                    String pid = expectRemoteCommandToSucceed("pidof system_server");
-                    mDevice.executeShellV2Command("kill -10 " + pid);  // force GC
+                    forceGCOnOpenFilesProcess(getOpenFilesPIDs(openFiles));
                 } catch (InterruptedException e) {
                     Thread.currentThread().interrupt();
                     return;
@@ -482,6 +479,35 @@
         }
     }
 
+    /**
+     * This is a helper method that parses the lsof output to get PIDs of process holding FD.
+     * Here is an example output of lsof. This method extracts the second columns(PID).
+     *
+     * Example lsof output:
+     *  COMMAND      PID     USER    FD     TYPE   DEVICE   SIZE/OFF  NODE   NAME
+     * .example.app  1063    u0_a38  mem    REG    253,6    8599      12826  example.apk
+     * .example.app  1063    u0_a38  99r    REG    253,6    8599      12826  example.apk
+     */
+    private Set<String> getOpenFilesPIDs(String lsof) {
+        Set<String> openFilesPIDs = new HashSet<>();
+        String[] lines = lsof.split("\n");
+        for (int i = 1; i < lines.length; i++) {
+            openFilesPIDs.add(lines[i].split("\\s+")[1]);
+        }
+        return openFilesPIDs;
+    }
+
+    /**
+     * This is a helper method that forces GC on processes given their PIDs.
+     * That is to execute shell command "kill -10" on PIDs.
+     */
+    private void forceGCOnOpenFilesProcess(Set<String> openFilesPIDs)
+            throws DeviceNotAvailableException {
+        for (String openFilePID : openFilesPIDs) {
+            mDevice.executeShellV2Command("kill -10 " + openFilePID);
+        }
+    }
+
     private void verifyInstalledFiles(String... filenames) throws DeviceNotAvailableException {
         String apkPath = getApkPath(TARGET_PACKAGE);
         String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java
index 0f4e122..4bcf5a4 100644
--- a/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java
+++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java
@@ -16,14 +16,16 @@
 
 package com.android.test.hwuicompare;
 
-import java.util.LinkedHashMap;
-import java.util.Map.Entry;
+import static java.util.Map.entry;
 
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.RectF;
 import android.util.Log;
 
+import java.util.Map;
+import java.util.Map.Entry;
+
 public abstract class DisplayModifier {
 
     // automated tests ignore any combination of operations that don't together return TOTAL_MASK
@@ -76,41 +78,36 @@
     };
 
     @SuppressWarnings("serial")
-    private static final LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>> gMaps = new LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>>() {
-        {
-            put("aa", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("true", new DisplayModifier() {
+    private static final Map<String, Map<String, DisplayModifier>> gMaps = Map.of(
+            "aa", Map.of(
+                    "true", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setAntiAlias(true);
                         }
-                    });
-                    put("false", new DisplayModifier() {
+                    },
+                    "false", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setAntiAlias(false);
                         }
-                    });
-                }
-            });
-            put("style", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("fill", new DisplayModifier() {
+                    }),
+            "style", Map.of(
+                    "fill", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStyle(Paint.Style.FILL);
                         }
-                    });
-                    put("stroke", new DisplayModifier() {
+                    },
+                    "stroke", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStyle(Paint.Style.STROKE);
                         }
                         @Override
                         protected int mask() { return SWEEP_STROKE_WIDTH_BIT; }
-                    });
-                    put("fillAndStroke", new DisplayModifier() {
+                    },
+                    "fillAndStroke", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStyle(Paint.Style.FILL_AND_STROKE);
@@ -118,131 +115,118 @@
 
                         @Override
                         protected int mask() { return SWEEP_STROKE_WIDTH_BIT; }
-                    });
-                }
-            });
-            put("strokeWidth", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("hair", new DisplayModifier() {
+                    }),
+            "strokeWidth", Map.of(
+                    "hair", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeWidth(0);
                         }
                         @Override
                         protected int mask() { return SWEEP_STROKE_WIDTH_BIT; }
-                    });
-                    put("0.3", new DisplayModifier() {
+                    },
+                    "0.3", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeWidth(0.3f);
                         }
-                    });
-                    put("1", new DisplayModifier() {
+                    },
+                    "1", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeWidth(1);
                         }
-                    });
-                    put("5", new DisplayModifier() {
+                    },
+                    "5", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeWidth(5);
                         }
-                    });
-                    put("30", new DisplayModifier() {
+                    },
+                    "30", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeWidth(30);
                         }
-                    });
-                }
-            });
-            put("strokeCap", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("butt", new DisplayModifier() {
+                    }),
+            "strokeCap", Map.of(
+                    "butt", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeCap(Paint.Cap.BUTT);
                         }
                         @Override
                         protected int mask() { return SWEEP_STROKE_CAP_BIT; }
-                    });
-                    put("round", new DisplayModifier() {
+                    },
+                    "round", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeCap(Paint.Cap.ROUND);
                         }
-                    });
-                    put("square", new DisplayModifier() {
+                    },
+                    "square", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeCap(Paint.Cap.SQUARE);
                         }
-                    });
-                }
-            });
-            put("strokeJoin", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("bevel", new DisplayModifier() {
+                    }),
+            "strokeJoin", Map.of(
+                    "bevel", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeJoin(Paint.Join.BEVEL);
                         }
                         @Override
                         protected int mask() { return SWEEP_STROKE_JOIN_BIT; }
-                    });
-                    put("round", new DisplayModifier() {
+                    },
+                    "round", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeJoin(Paint.Join.ROUND);
                         }
-                    });
-                    put("miter", new DisplayModifier() {
+                    },
+                    "miter", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeJoin(Paint.Join.MITER);
                         }
-                    });
+                    }),
                     // TODO: add miter0, miter1 etc to test miter distances
-                }
-            });
-
-            put("transform", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("noTransform", new DisplayModifier() {
+            "transform", Map.of(
+                    "noTransform", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {}
                         @Override
                         protected int mask() { return SWEEP_TRANSFORM_BIT; };
-                    });
-                    put("rotate5", new DisplayModifier() {
+                    },
+                    "rotate5", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.rotate(5);
                         }
-                    });
-                    put("rotate45", new DisplayModifier() {
+                    },
+                    "rotate45", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.rotate(45);
                         }
-                    });
-                    put("rotate90", new DisplayModifier() {
+                    },
+                    "rotate90", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.rotate(90);
                             canvas.translate(0, -200);
                         }
-                    });
-                    put("scale2x2", new DisplayModifier() {
+                    },
+                    "scale2x2", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.scale(2, 2);
                         }
                         @Override
                         protected int mask() { return SWEEP_TRANSFORM_BIT; };
-                    });
-                    put("rot20scl1x4", new DisplayModifier() {
+                    },
+                    "rot20scl1x4", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.rotate(20);
@@ -250,180 +234,167 @@
                         }
                         @Override
                         protected int mask() { return SWEEP_TRANSFORM_BIT; };
-                    });
-                }
-            });
-
-            put("shader", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("noShader", new DisplayModifier() {
+                    }),
+            "shader", Map.ofEntries(
+                    entry("noShader", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {}
                         @Override
                         protected int mask() { return SWEEP_SHADER_BIT; };
-                    });
-                    put("repeatShader", new DisplayModifier() {
+                    }),
+                    entry("repeatShader", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mRepeatShader);
                         }
                         @Override
                         protected int mask() { return SWEEP_SHADER_BIT; };
-                    });
-                    put("translatedShader", new DisplayModifier() {
+                    }),
+                    entry("translatedShader", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mTranslatedShader);
                         }
-                    });
-                    put("scaledShader", new DisplayModifier() {
+                    }),
+                    entry("scaledShader", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mScaledShader);
                         }
-                    });
-                    put("horGradient", new DisplayModifier() {
+                    }),
+                    entry("horGradient", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mHorGradient);
                         }
-                    });
-                    put("diagGradient", new DisplayModifier() {
+                    }),
+                    entry("diagGradient", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mDiagGradient);
                         }
                         @Override
                         protected int mask() { return SWEEP_SHADER_BIT; };
-                    });
-                    put("vertGradient", new DisplayModifier() {
+                    }),
+                    entry("vertGradient", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mVertGradient);
                         }
-                    });
-                    put("radGradient", new DisplayModifier() {
+                    }),
+                    entry("radGradient", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mRadGradient);
                         }
-                    });
-                    put("sweepGradient", new DisplayModifier() {
+                    }),
+                    entry("sweepGradient", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mSweepGradient);
                         }
-                    });
-                    put("composeShader", new DisplayModifier() {
+                    }),
+                    entry("composeShader", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mComposeShader);
                         }
-                    });
-                    put("bad composeShader", new DisplayModifier() {
+                    }),
+                    entry("bad composeShader", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mBadComposeShader);
                         }
-                    });
-                    put("bad composeShader 2", new DisplayModifier() {
+                    }),
+                    entry("bad composeShader 2", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mAnotherBadComposeShader);
                         }
-                    });
-                }
-            });
-
-            // FINAL MAP: DOES ACTUAL DRAWING
-            put("drawing", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("roundRect", new DisplayModifier() {
+                    })),
+            "drawing", Map.ofEntries(
+                    entry("roundRect", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawRoundRect(gRect, 20, 20, paint);
                         }
-                    });
-                    put("rect", new DisplayModifier() {
+                    }),
+                    entry("rect", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawRect(gRect, paint);
                         }
                         @Override
                         protected int mask() { return SWEEP_SHADER_BIT | SWEEP_STROKE_CAP_BIT; };
-                    });
-                    put("circle", new DisplayModifier() {
+                    }),
+                    entry("circle", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawCircle(100, 100, 75, paint);
                         }
-                    });
-                    put("oval", new DisplayModifier() {
+                    }),
+                    entry("oval", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawOval(gRect, paint);
                         }
-                    });
-                    put("lines", new DisplayModifier() {
+                    }),
+                    entry("lines", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawLines(gLinePts, paint);
                         }
                         @Override
                         protected int mask() { return SWEEP_STROKE_CAP_BIT; };
-                    });
-                    put("plusPoints", new DisplayModifier() {
+                    }),
+                    entry("plusPoints", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawPoints(gPts, paint);
                         }
-                    });
-                    put("text", new DisplayModifier() {
+                    }),
+                    entry("text", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setTextSize(36);
                             canvas.drawText("TEXTTEST", 0, 50, paint);
                         }
-                    });
-                    put("shadowtext", new DisplayModifier() {
+                    }),
+                    entry("shadowtext", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setTextSize(36);
                             paint.setShadowLayer(3.0f, 0.0f, 3.0f, 0xffff00ff);
                             canvas.drawText("TEXTTEST", 0, 50, paint);
                         }
-                    });
-                    put("bitmapMesh", new DisplayModifier() {
+                    }),
+                    entry("bitmapMesh", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawBitmapMesh(ResourceModifiers.instance().mBitmap, 3, 3,
                                     ResourceModifiers.instance().mBitmapVertices, 0, null, 0, null);
                         }
-                    });
-                    put("arc", new DisplayModifier() {
+                    }),
+                    entry("arc", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawArc(gRect, 260, 285, false, paint);
                         }
                         @Override
                         protected int mask() { return SWEEP_STROKE_CAP_BIT; };
-                    });
-                    put("arcFromCenter", new DisplayModifier() {
+                    }),
+                    entry("arcFromCenter", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawArc(gRect, 260, 285, true, paint);
                         }
                         @Override
                         protected int mask() { return SWEEP_STROKE_JOIN_BIT; };
-                    });
-                }
-            });
+                    })));
             // WARNING: DON'T PUT MORE MAPS BELOW THIS
-        }
-    };
 
-    private static LinkedHashMap<String, DisplayModifier> getMapAtIndex(int index) {
-        for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+    private static Map<String, DisplayModifier> getMapAtIndex(int index) {
+        for (Map<String, DisplayModifier> map : gMaps.values()) {
             if (index == 0) {
                 return map;
             }
@@ -439,7 +410,7 @@
     private static boolean stepInternal(boolean forward) {
         int modifierMapIndex = gMaps.size() - 1;
         while (modifierMapIndex >= 0) {
-            LinkedHashMap<String, DisplayModifier> map = getMapAtIndex(modifierMapIndex);
+            Map<String, DisplayModifier> map = getMapAtIndex(modifierMapIndex);
             mIndices[modifierMapIndex] += (forward ? 1 : -1);
 
             if (mIndices[modifierMapIndex] >= 0 && mIndices[modifierMapIndex] < map.size()) {
@@ -471,7 +442,7 @@
     private static boolean checkModificationStateMask() {
         int operatorMask = 0x0;
         int mapIndex = 0;
-        for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+        for (Map<String, DisplayModifier> map : gMaps.values()) {
             int displayModifierIndex = mIndices[mapIndex];
             for (Entry<String, DisplayModifier> modifierEntry : map.entrySet()) {
                 if (displayModifierIndex == 0) {
@@ -488,7 +459,7 @@
 
     public static void apply(Paint paint, Canvas canvas) {
         int mapIndex = 0;
-        for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+        for (Map<String, DisplayModifier> map : gMaps.values()) {
             int displayModifierIndex = mIndices[mapIndex];
             for (Entry<String, DisplayModifier> modifierEntry : map.entrySet()) {
                 if (displayModifierIndex == 0) {
@@ -510,7 +481,7 @@
         String[][] keys = new String[gMaps.size()][];
 
         int i = 0;
-        for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+        for (Map<String, DisplayModifier> map : gMaps.values()) {
             keys[i] = new String[map.size()];
             int j = 0;
             for (String key : map.keySet()) {
diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
index 5430dee..cfebf34 100644
--- a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
+++ b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
@@ -19,8 +19,11 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assume.assumeFalse;
+
 import android.app.UiAutomation;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
@@ -96,7 +99,12 @@
     }
 
     @Before
-    public void primeEventLog() {
+    public void setup() {
+        assumeFalse(sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
+        primeEventLog();
+    }
+
+    private void primeEventLog() {
         // Force a round trip to logd to make sure everything is up to date.
         // Without this the first test passes and others don't - we don't see new events in the
         // log. The exact reason is unclear.
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index d91aa1e..84781b4 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -15,8 +15,12 @@
         <option name="run-command" value="cmd window tracing frame" />
         <!-- ensure lock screen mode is swipe -->
         <option name="run-command" value="locksettings set-disabled false" />
+        <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+        <option name="run-command" value="pm disable com.google.android.internal.betterbug" />
         <!-- restart launcher to activate TAPL -->
         <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
+        <!-- Ensure output directory is empty at the start -->
+        <option name="run-command" value="rm -rf /sdcard/flicker" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 8a1e1fa..c100a9c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -20,7 +20,7 @@
 import android.platform.test.annotations.Presubmit
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.junit.FlickerBuilderProvider
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import org.junit.Assume
@@ -34,12 +34,12 @@
 abstract class BaseTest
 @JvmOverloads
 constructor(
-    protected val testSpec: FlickerTestParameter,
+    protected val flicker: FlickerTest,
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
     protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
 ) {
     init {
-        testSpec.setIsTablet(
+        flicker.scenario.setIsTablet(
             WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false)
                 .currentState
                 .wmState
@@ -58,13 +58,13 @@
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            setup { testSpec.setIsTablet(wmHelper.currentState.wmState.isTablet) }
+            setup { flicker.scenario.setIsTablet(wmHelper.currentState.wmState.isTablet) }
             transition()
         }
     }
 
     /** Checks that all parts of the screen are covered during the transition */
-    @Presubmit @Test open fun entireScreenCovered() = testSpec.entireScreenCovered()
+    @Presubmit @Test open fun entireScreenCovered() = flicker.entireScreenCovered()
 
     /**
      * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
@@ -74,8 +74,8 @@
     @Presubmit
     @Test
     open fun navBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.navBarLayerIsVisibleAtStartAndEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerIsVisibleAtStartAndEnd()
     }
 
     /**
@@ -87,8 +87,8 @@
     @Presubmit
     @Test
     open fun navBarLayerPositionAtStartAndEnd() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.navBarLayerPositionAtStartAndEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerPositionAtStartAndEnd()
     }
 
     /**
@@ -99,8 +99,8 @@
     @Presubmit
     @Test
     open fun navBarWindowIsAlwaysVisible() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.navBarWindowIsAlwaysVisible()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarWindowIsAlwaysVisible()
     }
 
     /**
@@ -112,8 +112,8 @@
     @Presubmit
     @Test
     open fun taskBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeTrue(testSpec.isTablet)
-        testSpec.taskBarLayerIsVisibleAtStartAndEnd()
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarLayerIsVisibleAtStartAndEnd()
     }
 
     /**
@@ -124,8 +124,8 @@
     @Presubmit
     @Test
     open fun taskBarWindowIsAlwaysVisible() {
-        Assume.assumeTrue(testSpec.isTablet)
-        testSpec.taskBarWindowIsAlwaysVisible()
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarWindowIsAlwaysVisible()
     }
 
     /**
@@ -134,8 +134,7 @@
      */
     @Presubmit
     @Test
-    open fun statusBarLayerIsVisibleAtStartAndEnd() =
-        testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+    open fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
 
     /**
      * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
@@ -143,7 +142,7 @@
      */
     @Presubmit
     @Test
-    open fun statusBarLayerPositionAtStartAndEnd() = testSpec.statusBarLayerPositionAtStartAndEnd()
+    open fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
 
     /**
      * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
@@ -151,7 +150,7 @@
      */
     @Presubmit
     @Test
-    open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+    open fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
 
     /**
      * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
@@ -160,7 +159,7 @@
     @Presubmit
     @Test
     open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
+        flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
     }
 
     /**
@@ -170,6 +169,19 @@
     @Presubmit
     @Test
     open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
+        flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
+    }
+
+    open fun cujCompleted() {
+        entireScreenCovered()
+        navBarLayerIsVisibleAtStartAndEnd()
+        navBarWindowIsAlwaysVisible()
+        taskBarLayerIsVisibleAtStartAndEnd()
+        taskBarWindowIsAlwaysVisible()
+        statusBarLayerIsVisibleAtStartAndEnd()
+        statusBarLayerPositionAtStartAndEnd()
+        statusBarWindowIsAlwaysVisible()
+        visibleLayersShownMoreThanOneConsecutiveEntry()
+        visibleWindowsShownMoreThanOneConsecutiveEntry()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index bbffd08..f9a245a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -27,7 +27,7 @@
  * Checks that [ComponentNameMatcher.STATUS_BAR] window is visible and above the app windows in all
  * WM trace entries
  */
-fun FlickerTestParameter.statusBarWindowIsAlwaysVisible() {
+fun FlickerTest.statusBarWindowIsAlwaysVisible() {
     assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.STATUS_BAR) }
 }
 
@@ -35,7 +35,7 @@
  * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows in all WM
  * trace entries
  */
-fun FlickerTestParameter.navBarWindowIsAlwaysVisible() {
+fun FlickerTest.navBarWindowIsAlwaysVisible() {
     assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
@@ -43,7 +43,7 @@
  * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
  * start and end of the WM trace
  */
-fun FlickerTestParameter.navBarWindowIsVisibleAtStartAndEnd() {
+fun FlickerTest.navBarWindowIsVisibleAtStartAndEnd() {
     this.navBarWindowIsVisibleAtStart()
     this.navBarWindowIsVisibleAtEnd()
 }
@@ -52,7 +52,7 @@
  * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
  * start of the WM trace
  */
-fun FlickerTestParameter.navBarWindowIsVisibleAtStart() {
+fun FlickerTest.navBarWindowIsVisibleAtStart() {
     assertWmStart { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
@@ -60,7 +60,7 @@
  * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the end
  * of the WM trace
  */
-fun FlickerTestParameter.navBarWindowIsVisibleAtEnd() {
+fun FlickerTest.navBarWindowIsVisibleAtEnd() {
     assertWmEnd { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
@@ -68,7 +68,7 @@
  * Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in all WM
  * trace entries
  */
-fun FlickerTestParameter.taskBarWindowIsAlwaysVisible() {
+fun FlickerTest.taskBarWindowIsAlwaysVisible() {
     assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
@@ -76,7 +76,7 @@
  * Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in all WM
  * trace entries
  */
-fun FlickerTestParameter.taskBarWindowIsVisibleAtEnd() {
+fun FlickerTest.taskBarWindowIsVisibleAtEnd() {
     assertWmEnd { this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
@@ -90,7 +90,7 @@
  * @param allStates if all states should be checked, othersie, just initial and final
  */
 @JvmOverloads
-fun FlickerTestParameter.entireScreenCovered(allStates: Boolean = true) {
+fun FlickerTest.entireScreenCovered(allStates: Boolean = true) {
     if (allStates) {
         assertLayers {
             this.invoke("entireScreenCovered") { entry ->
@@ -114,19 +114,19 @@
 }
 
 /** Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start of the SF trace */
-fun FlickerTestParameter.navBarLayerIsVisibleAtStart() {
+fun FlickerTest.navBarLayerIsVisibleAtStart() {
     assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
 /** Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the end of the SF trace */
-fun FlickerTestParameter.navBarLayerIsVisibleAtEnd() {
+fun FlickerTest.navBarLayerIsVisibleAtEnd() {
     assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
 /**
  * Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start and end of the SF trace
  */
-fun FlickerTestParameter.navBarLayerIsVisibleAtStartAndEnd() {
+fun FlickerTest.navBarLayerIsVisibleAtStartAndEnd() {
     this.navBarLayerIsVisibleAtStart()
     this.navBarLayerIsVisibleAtEnd()
 }
@@ -134,18 +134,18 @@
 /**
  * Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start and end of the SF trace
  */
-fun FlickerTestParameter.taskBarLayerIsVisibleAtStartAndEnd() {
+fun FlickerTest.taskBarLayerIsVisibleAtStartAndEnd() {
     this.taskBarLayerIsVisibleAtStart()
     this.taskBarLayerIsVisibleAtEnd()
 }
 
 /** Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start of the SF trace */
-fun FlickerTestParameter.taskBarLayerIsVisibleAtStart() {
+fun FlickerTest.taskBarLayerIsVisibleAtStart() {
     assertLayersStart { this.isVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
 /** Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the SF trace */
-fun FlickerTestParameter.taskBarLayerIsVisibleAtEnd() {
+fun FlickerTest.taskBarLayerIsVisibleAtEnd() {
     assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
@@ -153,7 +153,7 @@
  * Checks that [ComponentNameMatcher.STATUS_BAR] layer is visible at the start and end of the SF
  * trace
  */
-fun FlickerTestParameter.statusBarLayerIsVisibleAtStartAndEnd() {
+fun FlickerTest.statusBarLayerIsVisibleAtStartAndEnd() {
     assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
     assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
 }
@@ -162,12 +162,14 @@
  * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start of
  * the SF trace
  */
-fun FlickerTestParameter.navBarLayerPositionAtStart() {
+fun FlickerTest.navBarLayerPositionAtStart() {
     assertLayersStart {
         val display =
             this.entry.displays.firstOrNull { !it.isVirtual } ?: error("There is no display!")
         this.visibleRegion(ComponentNameMatcher.NAV_BAR)
-            .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
+            .coversExactly(
+                WindowUtils.getNavigationBarPosition(display, scenario.isGesturalNavigation)
+            )
     }
 }
 
@@ -175,13 +177,15 @@
  * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the end of
  * the SF trace
  */
-fun FlickerTestParameter.navBarLayerPositionAtEnd() {
+fun FlickerTest.navBarLayerPositionAtEnd() {
     assertLayersEnd {
         val display =
             this.entry.displays.minByOrNull { it.id }
                 ?: throw RuntimeException("There is no display!")
         this.visibleRegion(ComponentNameMatcher.NAV_BAR)
-            .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
+            .coversExactly(
+                WindowUtils.getNavigationBarPosition(display, scenario.isGesturalNavigation)
+            )
     }
 }
 
@@ -189,7 +193,7 @@
  * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start and
  * end of the SF trace
  */
-fun FlickerTestParameter.navBarLayerPositionAtStartAndEnd() {
+fun FlickerTest.navBarLayerPositionAtStartAndEnd() {
     navBarLayerPositionAtStart()
     navBarLayerPositionAtEnd()
 }
@@ -198,7 +202,7 @@
  * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the start
  * of the SF trace
  */
-fun FlickerTestParameter.statusBarLayerPositionAtStart() {
+fun FlickerTest.statusBarLayerPositionAtStart() {
     assertLayersStart {
         val display =
             this.entry.displays.minByOrNull { it.id }
@@ -212,7 +216,7 @@
  * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the end of
  * the SF trace
  */
-fun FlickerTestParameter.statusBarLayerPositionAtEnd() {
+fun FlickerTest.statusBarLayerPositionAtEnd() {
     assertLayersEnd {
         val display =
             this.entry.displays.minByOrNull { it.id }
@@ -226,7 +230,7 @@
  * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the start
  * and end of the SF trace
  */
-fun FlickerTestParameter.statusBarLayerPositionAtStartAndEnd() {
+fun FlickerTest.statusBarLayerPositionAtStartAndEnd() {
     statusBarLayerPositionAtStart()
     statusBarLayerPositionAtEnd()
 }
@@ -235,9 +239,7 @@
  * Asserts that the visibleRegion of the [ComponentNameMatcher.SNAPSHOT] layer can cover the
  * visibleRegion of the given app component exactly
  */
-fun FlickerTestParameter.snapshotStartingWindowLayerCoversExactlyOnApp(
-    component: IComponentNameMatcher
-) {
+fun FlickerTest.snapshotStartingWindowLayerCoversExactlyOnApp(component: IComponentNameMatcher) {
     assertLayers {
         invoke("snapshotStartingWindowLayerCoversExactlyOnApp") {
             val snapshotLayers =
@@ -291,7 +293,7 @@
  *      otherwise we won't and the layer must appear immediately.
  * ```
  */
-fun FlickerTestParameter.replacesLayer(
+fun FlickerTest.replacesLayer(
     originalLayer: IComponentNameMatcher,
     newLayer: IComponentNameMatcher,
     ignoreEntriesWithRotationLayer: Boolean = false,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING b/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING
new file mode 100644
index 0000000..945de33
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "ironwood-postsubmit": [
+    {
+      "name": "FlickerTests",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.IwTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
index 4cf6691..b7bdeeb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
@@ -17,11 +17,11 @@
 package com.android.server.wm.flicker.activityembedding
 
 import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
 import org.junit.Before
 
-abstract class ActivityEmbeddingTestBase(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class ActivityEmbeddingTestBase(flicker: FlickerTest) : BaseTest(flicker) {
     val testApp = ActivityEmbeddingAppHelper(instrumentation)
 
     @Before
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
index b23fb5a..ea67729 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
@@ -17,14 +17,12 @@
 package com.android.server.wm.flicker.activityembedding
 
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -41,8 +39,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenActivityEmbeddingPlaceholderSplit(testSpec: FlickerTestParameter) :
-    ActivityEmbeddingTestBase(testSpec) {
+class OpenActivityEmbeddingPlaceholderSplit(flicker: FlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
@@ -60,7 +58,7 @@
     @Presubmit
     @Test
     fun mainActivityBecomesInvisible() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
                 .then()
                 .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
@@ -70,12 +68,12 @@
     @Presubmit
     @Test
     fun placeholderSplitBecomesVisible() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
                 .then()
                 .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
         }
-        testSpec.assertLayers {
+        flicker.assertLayers {
             isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
                 .then()
                 .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
@@ -142,21 +140,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
-                    supportedNavigationModes =
-                        listOf(
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                        )
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index b16bfe0..d891714 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -18,11 +18,11 @@
 
 import android.platform.test.annotations.FlakyTest
 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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -69,7 +69,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) {
+class CloseAppBackButtonTest(flicker: FlickerTest) : CloseAppTransition(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -89,13 +89,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index 78d0860..cc8ef1d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -18,11 +18,11 @@
 
 import android.platform.test.annotations.FlakyTest
 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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -69,7 +69,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) {
+class CloseAppHomeButtonTest(flicker: FlickerTest) : CloseAppTransition(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -91,16 +91,11 @@
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
-         */
+        /** Creates the test configurations. */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index 5bb227f..23503d2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -18,8 +18,8 @@
 
 import android.platform.test.annotations.Presubmit
 import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.StandardAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
@@ -28,15 +28,15 @@
 import org.junit.Test
 
 /** Base test class for transitions that close an app back to the launcher screen */
-abstract class CloseAppTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class CloseAppTransition(flicker: FlickerTest) : BaseTest(flicker) {
     protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
-            tapl.setExpectedRotation(testSpec.startRotation)
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
             testApp.launchViaIntent(wmHelper)
-            this.setRotation(testSpec.startRotation)
+            this.setRotation(flicker.scenario.startRotation)
         }
         teardown { testApp.exit(wmHelper) }
     }
@@ -48,7 +48,7 @@
     @Presubmit
     @Test
     open fun launcherReplacesAppWindowAsTopWindow() {
-        testSpec.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowOnTop(LAUNCHER) }
+        flicker.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowOnTop(LAUNCHER) }
     }
 
     /**
@@ -58,17 +58,17 @@
     @Presubmit
     @Test
     open fun launcherWindowBecomesVisible() {
-        testSpec.assertWm { this.isAppWindowNotOnTop(LAUNCHER).then().isAppWindowOnTop(LAUNCHER) }
+        flicker.assertWm { this.isAppWindowNotOnTop(LAUNCHER).then().isAppWindowOnTop(LAUNCHER) }
     }
 
     /** Checks that [LAUNCHER] layer becomes visible when [testApp] becomes invisible */
     @Presubmit
     @Test
     open fun launcherLayerReplacesApp() {
-        testSpec.replacesLayer(
+        flicker.replacesLayer(
             testApp,
             LAUNCHER,
-            ignoreEntriesWithRotationLayer = testSpec.isLandscapeOrSeascapeAtStart
+            ignoreEntriesWithRotationLayer = flicker.scenario.isLandscapeOrSeascapeAtStart
         )
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index 48e1e64..368cc56 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -17,8 +17,6 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import android.util.Log
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
@@ -37,10 +35,8 @@
 constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.ActivityEmbedding.MainActivity.LABEL,
-    component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT,
-    launcherStrategy: ILauncherStrategy =
-        LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+    component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT
+) : StandardAppHelper(instr, launcherName, component) {
 
     /**
      * Clicks the button to launch the placeholder primary activity, which should launch the
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
index efb92f2..18563ff 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
@@ -25,45 +25,52 @@
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import org.junit.Assert.assertNotNull
 
-class AssistantAppHelper @JvmOverloads constructor(
+class AssistantAppHelper
+@JvmOverloads
+constructor(
     val instr: Instrumentation,
     val component: ComponentName = ActivityOptions.ASSISTANT_SERVICE_COMPONENT_NAME,
 ) {
     protected val uiDevice: UiDevice = UiDevice.getInstance(instr)
-    protected val defaultAssistant: String? = Settings.Secure.getString(
-        instr.targetContext.contentResolver,
-        Settings.Secure.ASSISTANT)
-    protected val defaultVoiceInteractionService: String? = Settings.Secure.getString(
-        instr.targetContext.contentResolver,
-        Settings.Secure.VOICE_INTERACTION_SERVICE)
+    protected val defaultAssistant: String? =
+        Settings.Secure.getString(instr.targetContext.contentResolver, Settings.Secure.ASSISTANT)
+    protected val defaultVoiceInteractionService: String? =
+        Settings.Secure.getString(
+            instr.targetContext.contentResolver,
+            Settings.Secure.VOICE_INTERACTION_SERVICE
+        )
 
     fun setDefaultAssistant() {
         Settings.Secure.putString(
             instr.targetContext.contentResolver,
             Settings.Secure.VOICE_INTERACTION_SERVICE,
-            component.flattenToString())
+            component.flattenToString()
+        )
         Settings.Secure.putString(
             instr.targetContext.contentResolver,
             Settings.Secure.ASSISTANT,
-            component.flattenToString())
+            component.flattenToString()
+        )
     }
 
     fun resetDefaultAssistant() {
         Settings.Secure.putString(
             instr.targetContext.contentResolver,
             Settings.Secure.VOICE_INTERACTION_SERVICE,
-            defaultVoiceInteractionService)
+            defaultVoiceInteractionService
+        )
         Settings.Secure.putString(
             instr.targetContext.contentResolver,
             Settings.Secure.ASSISTANT,
-            defaultAssistant)
+            defaultAssistant
+        )
     }
 
     /**
      * Open Assistance UI.
      *
-     * @param longpress open the UI by long pressing power button.
-     *  Otherwise open the UI through vioceinteraction shell command directly.
+     * @param longpress open the UI by long pressing power button. Otherwise open the UI through
+     * vioceinteraction shell command directly.
      */
     @JvmOverloads
     fun openUI(longpress: Boolean = false) {
@@ -72,9 +79,11 @@
         } else {
             uiDevice.executeShellCommand("cmd voiceinteraction show")
         }
-        val ui = uiDevice.wait(
-            Until.findObject(By.res(ActivityOptions.FLICKER_APP_PACKAGE, "vis_frame")),
-            FIND_TIMEOUT)
+        val ui =
+            uiDevice.wait(
+                Until.findObject(By.res(ActivityOptions.FLICKER_APP_PACKAGE, "vis_frame")),
+                FIND_TIMEOUT
+            )
         assertNotNull("Can't find Assistant UI after long pressing power button.", ui)
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
index 4340bd7..05b50f0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
@@ -17,8 +17,6 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@
     instr: Instrumentation,
     launcherName: String = ActivityOptions.PortraitOnlyActivity.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.PortraitOnlyActivity.COMPONENT.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy =
-        LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+        ActivityOptions.PortraitOnlyActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index 73cb862..2ae8e1d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -18,15 +18,16 @@
 
 package com.android.server.wm.flicker.helpers
 
-import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.IFlickerTestData
 import com.android.server.wm.flicker.rules.ChangeDisplayOrientationRule
+import com.android.server.wm.traces.common.service.PlatformConsts
 
 /**
  * Changes the device [rotation] and wait for the rotation animation to complete
  *
  * @param rotation New device rotation
  */
-fun Flicker.setRotation(rotation: Int) =
+fun IFlickerTestData.setRotation(rotation: PlatformConsts.Rotation) =
     ChangeDisplayOrientationRule.setRotation(
         rotation,
         instrumentation,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
index d45315e..d583bba 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
@@ -17,8 +17,6 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Direction
 import androidx.test.uiautomator.Until
@@ -32,10 +30,8 @@
 constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.Game.LABEL,
-    component: ComponentNameMatcher = ActivityOptions.Game.COMPONENT.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy =
-        LauncherStrategyFactory.getInstance(instr).launcherStrategy,
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+    component: ComponentNameMatcher = ActivityOptions.Game.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
 
     /**
      * Swipes down in the mock game app.
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 ca5b2af..b7eea1b 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
@@ -26,6 +26,7 @@
 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.common.service.PlatformConsts
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import java.util.regex.Pattern
@@ -34,7 +35,7 @@
 @JvmOverloads
 constructor(
     instr: Instrumentation,
-    private val rotation: Int,
+    private val rotation: PlatformConsts.Rotation,
     private val imePackageName: String = IME_PACKAGE,
     launcherName: String = ActivityOptions.Ime.AutoFocusActivity.LABEL,
     component: ComponentNameMatcher =
@@ -63,7 +64,7 @@
             } else {
                 getPackage()
             }
-        launcherStrategy.launch(appName, expectedPackage)
+        open(expectedPackage)
     }
 
     fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
index cefbf18..3bb7f4e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -17,8 +17,6 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -31,10 +29,8 @@
 constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.Ime.Default.LABEL,
-    component: ComponentNameMatcher = ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy =
-        LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+    component: ComponentNameMatcher = ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
     /**
      * Opens the IME and wait for it to be displayed
      *
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
index 1502ad5..69d6a47 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
@@ -17,8 +17,6 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@
     instr: Instrumentation,
     launcherName: String = ActivityOptions.Ime.StateInitializeActivity.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.Ime.StateInitializeActivity.COMPONENT.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy =
-        LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+        ActivityOptions.Ime.StateInitializeActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
index f00904b..d0935ef 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
@@ -17,8 +17,6 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Direction
 import androidx.test.uiautomator.UiObject2
@@ -32,10 +30,8 @@
 constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.Mail.LABEL,
-    component: ComponentNameMatcher = ActivityOptions.Mail.COMPONENT.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy =
-        LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+    component: ComponentNameMatcher = ActivityOptions.Mail.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
 
     fun openMail(rowIdx: Int) {
         val rowSel =
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
index 34294a6..8b3fa18 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
@@ -17,8 +17,6 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiDevice
 import androidx.test.uiautomator.Until
@@ -32,10 +30,8 @@
 constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.LaunchNewTask.LABEL,
-    component: ComponentNameMatcher = ActivityOptions.LaunchNewTask.COMPONENT.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy =
-        LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+    component: ComponentNameMatcher = ActivityOptions.LaunchNewTask.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
     fun openNewTask(device: UiDevice, wmHelper: WindowManagerStateHelper) {
         val button =
             device.wait(Until.findObject(By.res(getPackage(), "launch_new_task")), FIND_TIMEOUT)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
index bbb782d..992a1a1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
@@ -17,8 +17,6 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@
     instr: Instrumentation,
     launcherName: String = ActivityOptions.NonResizeableActivity.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.NonResizeableActivity.COMPONENT.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy =
-        LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+        ActivityOptions.NonResizeableActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
index a3e32e5..c29c752 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
@@ -17,8 +17,6 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -31,10 +29,8 @@
 constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.Notification.LABEL,
-    component: ComponentNameMatcher = ActivityOptions.Notification.COMPONENT.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy =
-        LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+    component: ComponentNameMatcher = ActivityOptions.Notification.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
     fun postNotification(wmHelper: WindowManagerStateHelper) {
         val button =
             uiDevice.wait(Until.findObject(By.res(getPackage(), "post_notification")), FIND_TIMEOUT)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 19ee09a..8fe6aac 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -92,8 +92,12 @@
 
         // if the distance per step is less than 1, carry out the animation in two steps
         gestureHelper.pinch(
-                Tuple(initLeftX, yCoord), Tuple(initRightX, yCoord),
-                Tuple(finalLeftX, yCoord), Tuple(finalRightX, yCoord), adjustedSteps)
+            Tuple(initLeftX, yCoord),
+            Tuple(initRightX, yCoord),
+            Tuple(finalLeftX, yCoord),
+            Tuple(finalRightX, yCoord),
+            adjustedSteps
+        )
 
         waitForPipWindowToExpandFrom(wmHelper, Region.from(windowRect))
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
index c904352..c51754c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
@@ -17,8 +17,6 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@
     instr: Instrumentation,
     launcherName: String = ActivityOptions.SeamlessRotation.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.SeamlessRotation.COMPONENT.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy =
-        LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+        ActivityOptions.SeamlessRotation.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
index de152cb5..9318f20 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
@@ -17,8 +17,6 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@
     instr: Instrumentation,
     launcherName: String = ActivityOptions.ShowWhenLockedActivity.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.ShowWhenLockedActivity.COMPONENT.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy =
-        LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+        ActivityOptions.ShowWhenLockedActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
index e415990..b46ff2c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
@@ -17,8 +17,6 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
@@ -28,7 +26,5 @@
 constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.SimpleActivity.LABEL,
-    component: ComponentNameMatcher = ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy =
-        LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+    component: ComponentNameMatcher = ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
index 1330190..720d962 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
@@ -17,8 +17,6 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiDevice
 import androidx.test.uiautomator.Until
@@ -33,10 +31,8 @@
     instr: Instrumentation,
     launcherName: String = ActivityOptions.LaunchNewActivity.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy =
-        LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+        ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
 
     private val secondActivityComponent =
         ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 1a49595..c735be0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -17,16 +17,15 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,8 +48,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeAutoOpenWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
-    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class CloseImeAutoOpenWindowToAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+    private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
@@ -62,43 +61,37 @@
     @Presubmit
     @Test
     fun imeAppWindowIsAlwaysVisible() {
-        testSpec.assertWm { this.isAppWindowOnTop(testApp) }
+        flicker.assertWm { this.isAppWindowOnTop(testApp) }
     }
 
     @Presubmit
     @Test
     fun imeLayerVisibleStart() {
-        testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
+        flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
     }
 
     @Presubmit
     @Test
     fun imeLayerInvisibleEnd() {
-        testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) }
+        flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) }
     }
 
-    @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+    @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
 
     @Presubmit
     @Test
     fun imeAppLayerIsAlwaysVisible() {
-        testSpec.assertLayers { this.isVisible(testApp) }
+        flicker.assertLayers { this.isVisible(testApp) }
     }
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    // b/190352379 (IME doesn't show on app launch in 90 degrees)
-                    supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes =
-                        listOf(
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                        )
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // b/190352379 (IME doesn't show on app launch in 90 degrees)
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 463efe8..4024f56 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -17,16 +17,15 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,8 +48,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeAutoOpenWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
-    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class CloseImeAutoOpenWindowToHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
+    private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
@@ -68,43 +67,37 @@
     @Presubmit
     @Test
     fun imeAppWindowBecomesInvisible() {
-        testSpec.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowNotOnTop(testApp) }
+        flicker.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowNotOnTop(testApp) }
     }
 
     @Presubmit
     @Test
     fun imeLayerVisibleStart() {
-        testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
+        flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
     }
 
     @Presubmit
     @Test
     fun imeLayerInvisibleEnd() {
-        testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) }
+        flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) }
     }
 
-    @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+    @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
 
     @Presubmit
     @Test
     fun imeAppLayerBecomesInvisible() {
-        testSpec.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
+        flicker.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
     }
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    // b/190352379 (IME doesn't show on app launch in 90 degrees)
-                    supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes =
-                        listOf(
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                        )
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // b/190352379 (IME doesn't show on app launch in 90 degrees)
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
index 91d9a1f..c72405c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
@@ -17,17 +17,16 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.traces.region.RegionSubject
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,7 +37,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeEditorPopupDialogTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class CloseImeEditorPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -59,14 +58,12 @@
         }
     }
 
-    @Presubmit
-    @Test
-    fun imeWindowBecameInvisible() = testSpec.imeWindowBecomesInvisible()
+    @Presubmit @Test fun imeWindowBecameInvisible() = flicker.imeWindowBecomesInvisible()
 
     @Presubmit
     @Test
     fun imeLayerAndImeSnapshotVisibleOnScreen() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isVisible(ComponentNameMatcher.IME)
                 .then()
                 .isVisible(ComponentNameMatcher.IME_SNAPSHOT)
@@ -79,7 +76,7 @@
     @Presubmit
     @Test
     fun imeSnapshotAssociatedOnAppVisibleRegion() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.invoke("imeSnapshotAssociatedOnAppVisibleRegion") {
                 val imeSnapshotLayers =
                     it.subjects.filter { subject ->
@@ -106,16 +103,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedNavigationModes =
-                    listOf(
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                    ),
-                    supportedRotations = listOf(Surface.ROTATION_0)
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index b9c875a..afc5f65 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -17,14 +17,15 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Assume
@@ -42,7 +43,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class CloseImeWindowToAppTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val testApp = ImeAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -59,7 +60,7 @@
     @Presubmit
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.visibleWindowsShownMoreThanOneConsecutiveEntry(
                 listOf(
                     ComponentNameMatcher.IME,
@@ -74,38 +75,48 @@
     @Presubmit
     @Test
     override fun navBarLayerPositionAtStartAndEnd() {
-        Assume.assumeFalse(testSpec.isTablet)
-        Assume.assumeFalse(testSpec.isLandscapeOrSeascapeAtStart)
-        testSpec.navBarLayerPositionAtStartAndEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart)
+        flicker.navBarLayerPositionAtStartAndEnd()
     }
 
     @FlakyTest
     @Test
     fun navBarLayerPositionAtStartAndEndLandscapeOrSeascapeAtStart() {
-        Assume.assumeFalse(testSpec.isTablet)
-        Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
-        testSpec.navBarLayerPositionAtStartAndEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+        flicker.navBarLayerPositionAtStartAndEnd()
     }
 
-    @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+    @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
 
     @Presubmit
     @Test
     fun imeAppLayerIsAlwaysVisible() {
-        testSpec.assertLayers { this.isVisible(testApp) }
+        flicker.assertLayers { this.isVisible(testApp) }
     }
 
     @Presubmit
     @Test
     fun imeAppWindowIsAlwaysVisible() {
-        testSpec.assertWm { this.isAppWindowOnTop(testApp) }
+        flicker.assertWm { this.isAppWindowOnTop(testApp) }
+    }
+
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        navBarLayerPositionAtStartAndEnd()
+        imeLayerBecomesInvisible()
+        imeAppLayerIsAlwaysVisible()
+        imeAppWindowIsAlwaysVisible()
     }
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 1dc3ca5..aedf965 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -16,17 +16,17 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -41,7 +41,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class CloseImeWindowToHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val testApp = ImeAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -62,7 +62,7 @@
     @Presubmit
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.visibleWindowsShownMoreThanOneConsecutiveEntry(
                 listOf(
                     ComponentNameMatcher.IME,
@@ -77,42 +77,47 @@
     @Presubmit
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.visibleLayersShownMoreThanOneConsecutiveEntry(
                 listOf(ComponentNameMatcher.IME, ComponentNameMatcher.SPLASH_SCREEN)
             )
         }
     }
 
-    @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+    @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
 
-    @Presubmit @Test fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible()
+    @Presubmit @Test fun imeWindowBecomesInvisible() = flicker.imeWindowBecomesInvisible()
 
     @Presubmit
     @Test
     fun imeAppWindowBecomesInvisible() {
-        testSpec.assertWm { this.isAppWindowVisible(testApp).then().isAppWindowInvisible(testApp) }
+        flicker.assertWm { this.isAppWindowVisible(testApp).then().isAppWindowInvisible(testApp) }
     }
 
     @Presubmit
     @Test
     fun imeAppLayerBecomesInvisible() {
-        testSpec.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
+        flicker.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
+    }
+
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        navBarLayerPositionAtStartAndEnd()
+        imeLayerBecomesInvisible()
+        imeAppWindowBecomesInvisible()
+        imeWindowBecomesInvisible()
+        imeLayerBecomesInvisible()
     }
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes =
-                        listOf(
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                        )
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
index e0c5edc..3edc15f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
@@ -18,22 +18,22 @@
 
 package com.android.server.wm.flicker.ime
 
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.traces.common.ComponentNameMatcher
 
-fun FlickerTestParameter.imeLayerBecomesVisible() {
+fun FlickerTest.imeLayerBecomesVisible() {
     assertLayers {
         this.isInvisible(ComponentNameMatcher.IME).then().isVisible(ComponentNameMatcher.IME)
     }
 }
 
-fun FlickerTestParameter.imeLayerBecomesInvisible() {
+fun FlickerTest.imeLayerBecomesInvisible() {
     assertLayers {
         this.isVisible(ComponentNameMatcher.IME).then().isInvisible(ComponentNameMatcher.IME)
     }
 }
 
-fun FlickerTestParameter.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) {
+fun FlickerTest.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) {
     if (rotatesScreen) {
         assertWm {
             this.isNonAppWindowVisible(ComponentNameMatcher.IME)
@@ -47,7 +47,7 @@
     }
 }
 
-fun FlickerTestParameter.imeWindowBecomesVisible() {
+fun FlickerTest.imeWindowBecomesVisible() {
     assertWm {
         this.isNonAppWindowInvisible(ComponentNameMatcher.IME)
             .then()
@@ -55,7 +55,7 @@
     }
 }
 
-fun FlickerTestParameter.imeWindowBecomesInvisible() {
+fun FlickerTest.imeWindowBecomesInvisible() {
     assertWm {
         this.isNonAppWindowVisible(ComponentNameMatcher.IME)
             .then()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
index 073da4f..da3c62d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
@@ -18,19 +18,18 @@
 
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
 import android.view.WindowInsets.Type.ime
 import android.view.WindowInsets.Type.navigationBars
 import android.view.WindowInsets.Type.statusBars
-import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.FixMethodOrder
@@ -47,8 +46,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LaunchAppShowImeAndDialogThemeAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
-    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class LaunchAppShowImeAndDialogThemeAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+    private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
@@ -75,42 +74,36 @@
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
-    /** Checks that [ComponentMatcher.IME] layer becomes visible during the transition */
-    @Presubmit @Test fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
+    /** Checks that [ComponentNameMatcher.IME] layer becomes visible during the transition */
+    @Presubmit @Test fun imeWindowIsAlwaysVisible() = flicker.imeWindowIsAlwaysVisible()
 
-    /** Checks that [ComponentMatcher.IME] layer is visible at the end of the transition */
+    /** Checks that [ComponentNameMatcher.IME] layer is visible at the end of the transition */
     @Presubmit
     @Test
     fun imeLayerExistsEnd() {
-        testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
     }
 
-    /** Checks that [ComponentMatcher.IME_SNAPSHOT] layer is invisible always. */
+    /** Checks that [ComponentNameMatcher.IME_SNAPSHOT] layer is invisible always. */
     @Presubmit
     @Test
     fun imeSnapshotNotVisible() {
-        testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.IME_SNAPSHOT) }
+        flicker.assertLayers { this.isInvisible(ComponentNameMatcher.IME_SNAPSHOT) }
     }
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes =
-                        listOf(
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                        )
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
index a93f176..4891901 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
@@ -17,18 +17,17 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
 import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -73,15 +72,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LaunchAppShowImeOnStartTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
-    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class LaunchAppShowImeOnStartTest(flicker: FlickerTest) : BaseTest(flicker) {
+    private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
     private val initializeApp = ImeStateInitializeHelper(instrumentation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
             initializeApp.launchViaIntent(wmHelper)
-            this.setRotation(testSpec.startRotation)
+            this.setRotation(flicker.scenario.startRotation)
         }
         teardown {
             initializeApp.exit(wmHelper)
@@ -93,45 +92,39 @@
         }
     }
 
-    /** Checks that [ComponentMatcher.IME] window becomes visible during the transition */
-    @Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
+    /** Checks that [ComponentNameMatcher.IME] window becomes visible during the transition */
+    @Presubmit @Test fun imeWindowBecomesVisible() = flicker.imeWindowBecomesVisible()
 
-    /** Checks that [ComponentMatcher.IME] layer becomes visible during the transition */
-    @Presubmit @Test fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+    /** Checks that [ComponentNameMatcher.IME] layer becomes visible during the transition */
+    @Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible()
 
-    /** Checks that [ComponentMatcher.IME] layer is invisible at the start of the transition */
+    /** Checks that [ComponentNameMatcher.IME] layer is invisible at the start of the transition */
     @Presubmit
     @Test
     fun imeLayerNotExistsStart() {
-        testSpec.assertLayersStart { this.isInvisible(ComponentNameMatcher.IME) }
+        flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.IME) }
     }
 
-    /** Checks that [ComponentMatcher.IME] layer is visible at the end of the transition */
+    /** Checks that [ComponentNameMatcher.IME] layer is visible at the end of the transition */
     @Presubmit
     @Test
     fun imeLayerExistsEnd() {
-        testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
     }
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes =
-                        listOf(
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                        )
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
index a6bd791..33e9574 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
@@ -17,18 +17,18 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -46,7 +46,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowAndCloseTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class OpenImeWindowAndCloseTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val simpleApp = SimpleAppHelper(instrumentation)
     private val testApp = ImeAppHelper(instrumentation)
 
@@ -61,9 +61,9 @@
         teardown { simpleApp.exit(wmHelper) }
     }
 
-    @Presubmit @Test fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible()
+    @Presubmit @Test fun imeWindowBecomesInvisible() = flicker.imeWindowBecomesInvisible()
 
-    @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+    @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
 
     @Presubmit
     @Test
@@ -79,19 +79,21 @@
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
     }
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        imeLayerBecomesInvisible()
+        imeWindowBecomesInvisible()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes =
-                        listOf(
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                        )
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
index 3e18d59..197564a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
@@ -19,17 +19,16 @@
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.snapshotStartingWindowLayerCoversExactlyOnApp
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -46,9 +45,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowFromFixedOrientationAppTest(testSpec: FlickerTestParameter) :
-    BaseTest(testSpec) {
-    private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class OpenImeWindowFromFixedOrientationAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+    private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
@@ -63,61 +61,58 @@
             wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
         }
         transitions {
-            // Bring the exist IME activity to the front in landscape mode device rotation.
-            setRotation(Surface.ROTATION_90)
+            // Bring the existing IME activity to the front in landscape mode device rotation.
+            setRotation(PlatformConsts.Rotation.ROTATION_90)
             imeTestApp.launchViaIntent(wmHelper)
         }
         teardown { imeTestApp.exit(wmHelper) }
     }
 
     /** {@inheritDoc} */
-    @Presubmit
+    @Postsubmit
     @Test
     override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
     /** {@inheritDoc} */
     @Postsubmit
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
-    @Presubmit
-    @Test
-    fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
+    @Presubmit @Test fun imeWindowBecomesVisible() = flicker.imeWindowBecomesVisible()
 
-    @Presubmit
-    @Test
-    fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+    @Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible()
 
     @Postsubmit
     @Test
     fun snapshotStartingWindowLayerCoversExactlyOnApp() {
         Assume.assumeFalse(isShellTransitionsEnabled)
-        testSpec.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
+        flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
     }
 
     @Presubmit
     @Test
     fun snapshotStartingWindowLayerCoversExactlyOnApp_ShellTransit() {
         Assume.assumeTrue(isShellTransitionsEnabled)
-        testSpec.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
+        flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
     }
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_90),
-                    supportedNavigationModes =
-                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_90)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index b43efea..c097511 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -16,16 +16,16 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -37,7 +37,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class OpenImeWindowTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val testApp = ImeAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -50,35 +50,38 @@
         }
     }
 
-    @Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        imeWindowBecomesVisible()
+        appWindowAlwaysVisibleOnTop()
+        layerAlwaysVisible()
+    }
+
+    @Presubmit @Test fun imeWindowBecomesVisible() = flicker.imeWindowBecomesVisible()
 
     @Presubmit
     @Test
     fun appWindowAlwaysVisibleOnTop() {
-        testSpec.assertWm { this.isAppWindowOnTop(testApp) }
+        flicker.assertWm { this.isAppWindowOnTop(testApp) }
     }
 
-    @Presubmit @Test fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+    @Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible()
 
     @Presubmit
     @Test
     fun layerAlwaysVisible() {
-        testSpec.assertLayers { this.isVisible(testApp) }
+        flicker.assertLayers { this.isVisible(testApp) }
     }
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes =
-                        listOf(
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                            WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                        )
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
index 0a7701e..209eb0c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -18,15 +18,13 @@
 
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
 import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
 import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -48,8 +46,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
-    private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class OpenImeWindowToOverViewTest(flicker: FlickerTest) : BaseTest(flicker) {
+    private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
@@ -82,7 +80,7 @@
      */
     private fun waitNavStatusBarVisibility(stateSync: WindowManagerStateHelper.StateSyncBuilder) {
         when {
-            testSpec.isLandscapeOrSeascapeAtStart && !testSpec.isTablet ->
+            flicker.scenario.isLandscapeOrSeascapeAtStart && !flicker.scenario.isTablet ->
                 stateSync.add(WindowManagerConditionsFactory.isStatusBarVisible().negate())
             else -> stateSync.withNavOrTaskBarVisible().withStatusBarVisible()
         }
@@ -91,25 +89,25 @@
     @Presubmit
     @Test
     fun imeWindowIsAlwaysVisible() {
-        testSpec.imeWindowIsAlwaysVisible()
+        flicker.imeWindowIsAlwaysVisible()
     }
 
     @Presubmit
     @Test
     fun navBarLayerIsVisibleAtStartAndEnd3Button() {
-        Assume.assumeFalse(testSpec.isTablet)
-        Assume.assumeFalse(testSpec.isGesturalNavigation)
-        testSpec.navBarLayerIsVisibleAtStartAndEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+        flicker.navBarLayerIsVisibleAtStartAndEnd()
     }
 
     /** Bars are expected to be hidden while entering overview in landscape (b/227189877) */
     @Presubmit
     @Test
     fun navBarLayerIsVisibleAtStartAndEndGestural() {
-        Assume.assumeFalse(testSpec.isTablet)
-        Assume.assumeTrue(testSpec.isGesturalNavigation)
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
         Assume.assumeFalse(isShellTransitionsEnabled)
-        testSpec.navBarLayerIsVisibleAtStartAndEnd()
+        flicker.navBarLayerIsVisibleAtStartAndEnd()
     }
 
     /**
@@ -119,12 +117,12 @@
     @Presubmit
     @Test
     fun navBarLayerIsInvisibleInLandscapeGestural() {
-        Assume.assumeFalse(testSpec.isTablet)
-        Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
-        Assume.assumeTrue(testSpec.isGesturalNavigation)
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+        Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
         Assume.assumeTrue(isShellTransitionsEnabled)
-        testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
-        testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.NAV_BAR) }
+        flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
+        flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.NAV_BAR) }
     }
 
     /**
@@ -134,11 +132,11 @@
     @Presubmit
     @Test
     fun statusBarLayerIsInvisibleInLandscapePhone() {
-        Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
-        Assume.assumeTrue(testSpec.isGesturalNavigation)
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
-        testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+        Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+        Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+        flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
     }
 
     /**
@@ -148,35 +146,31 @@
     @Presubmit
     @Test
     fun statusBarLayerIsInvisibleInLandscapeTablet() {
-        Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
-        Assume.assumeTrue(testSpec.isGesturalNavigation)
-        Assume.assumeTrue(testSpec.isTablet)
-        testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+        Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+        Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.statusBarLayerIsVisibleAtStartAndEnd()
     }
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Visibility changes depending on orientation and navigation mode")
-    override fun navBarLayerIsVisibleAtStartAndEnd() {
-    }
+    override fun navBarLayerIsVisibleAtStartAndEnd() {}
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Visibility changes depending on orientation and navigation mode")
-    override fun navBarLayerPositionAtStartAndEnd() {
-    }
+    override fun navBarLayerPositionAtStartAndEnd() {}
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Visibility changes depending on orientation and navigation mode")
-    override fun statusBarLayerPositionAtStartAndEnd() {
-    }
+    override fun statusBarLayerPositionAtStartAndEnd() {}
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Visibility changes depending on orientation and navigation mode")
-    override fun statusBarLayerIsVisibleAtStartAndEnd() {
-    }
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {}
 
     @Presubmit
     @Test
@@ -185,38 +179,38 @@
     @Presubmit
     @Test
     fun statusBarLayerIsVisibleInPortrait() {
-        Assume.assumeFalse(testSpec.isLandscapeOrSeascapeAtStart)
-        testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+        Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart)
+        flicker.statusBarLayerIsVisibleAtStartAndEnd()
     }
 
     @Presubmit
     @Test
     fun statusBarLayerIsInvisibleInLandscapeShell() {
-        Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
-        Assume.assumeFalse(testSpec.isTablet)
+        Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+        Assume.assumeFalse(flicker.scenario.isTablet)
         Assume.assumeTrue(isShellTransitionsEnabled)
-        testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
-        testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+        flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+        flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
     }
 
     @Presubmit
     @Test
     fun statusBarLayerIsVisibleInLandscapeLegacy() {
-        Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
-        Assume.assumeTrue(testSpec.isTablet)
+        Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+        Assume.assumeTrue(flicker.scenario.isTablet)
         Assume.assumeFalse(isShellTransitionsEnabled)
-        testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+        flicker.statusBarLayerIsVisibleAtStartAndEnd()
     }
 
     @Presubmit
     @Test
     fun imeLayerIsVisibleAndAssociatedWithAppWidow() {
-        testSpec.assertLayersStart {
+        flicker.assertLayersStart {
             isVisible(ComponentNameMatcher.IME)
                 .visibleRegion(ComponentNameMatcher.IME)
                 .coversAtMost(isVisible(imeTestApp).visibleRegion(imeTestApp).region)
         }
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.invoke("imeLayerIsVisibleAndAlignAppWidow") {
                 val imeVisibleRegion = it.visibleRegion(ComponentNameMatcher.IME)
                 val appVisibleRegion = it.visibleRegion(imeTestApp)
@@ -232,21 +226,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
-                    supportedNavigationModes =
-                    listOf(
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                    )
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index f810fbb..38791a2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -17,17 +17,17 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
 import com.android.server.wm.flicker.helpers.reopenAppFromOverview
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -41,8 +41,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
-    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+open class ReOpenImeWindowTest(flicker: FlickerTest) : BaseTest(flicker) {
+    private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
@@ -50,7 +50,7 @@
             tapl.workspace.switchToOverview().dismissAllTasks()
             testApp.launchViaIntent(wmHelper)
             testApp.openIME(wmHelper)
-            this.setRotation(testSpec.startRotation)
+            this.setRotation(flicker.scenario.startRotation)
             device.pressRecentApps()
             wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify()
         }
@@ -68,7 +68,7 @@
         // depends on how much of the animation transactions are sent to SF at once
         // sometimes this layer appears for 2-3 frames, sometimes for only 1
         val recentTaskComponent = ComponentNameMatcher("", "RecentTaskScreenshotSurface")
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.visibleLayersShownMoreThanOneConsecutiveEntry(
                 listOf(
                     ComponentNameMatcher.SPLASH_SCREEN,
@@ -84,14 +84,14 @@
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
         val component = ComponentNameMatcher("", "RecentTaskScreenshotSurface")
-        testSpec.assertWm {
+        flicker.assertWm {
             this.visibleWindowsShownMoreThanOneConsecutiveEntry(
                 ignoreWindows =
-                listOf(
-                    ComponentNameMatcher.SPLASH_SCREEN,
-                    ComponentNameMatcher.SNAPSHOT,
-                    component
-                )
+                    listOf(
+                        ComponentNameMatcher.SPLASH_SCREEN,
+                        ComponentNameMatcher.SNAPSHOT,
+                        component
+                    )
             )
         }
     }
@@ -99,16 +99,14 @@
     @Presubmit
     @Test
     fun launcherWindowBecomesInvisible() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.isAppWindowVisible(ComponentNameMatcher.LAUNCHER)
                 .then()
                 .isAppWindowInvisible(ComponentNameMatcher.LAUNCHER)
         }
     }
 
-    @Presubmit
-    @Test
-    fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
+    @Presubmit @Test fun imeWindowIsAlwaysVisible() = flicker.imeWindowIsAlwaysVisible()
 
     @Presubmit
     @Test
@@ -117,19 +115,19 @@
         // and exiting overview. Since we log 1x per frame, sometimes the activity visibility
         // and the app visibility are updated together, sometimes not, thus ignore activity
         // check at the start
-        testSpec.assertWm { this.isAppWindowVisible(testApp) }
+        flicker.assertWm { this.isAppWindowVisible(testApp) }
     }
 
     @Presubmit
     @Test
     fun imeLayerBecomesVisible() {
-        testSpec.assertLayers { this.isVisible(ComponentNameMatcher.IME) }
+        flicker.assertLayers { this.isVisible(ComponentNameMatcher.IME) }
     }
 
     @Presubmit
     @Test
     fun appLayerReplacesLauncher() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isVisible(ComponentNameMatcher.LAUNCHER)
                 .then()
                 .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -141,9 +139,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index 0ca6457..a6bbf54 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -19,19 +19,18 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -49,9 +48,9 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Presubmit
-open class SwitchImeWindowsFromGestureNavTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+open class SwitchImeWindowsFromGestureNavTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val testApp = SimpleAppHelper(instrumentation)
-    private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+    private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
 
     @Before
     open fun before() {
@@ -63,7 +62,7 @@
         setup {
             tapl.setExpectedRotationCheckEnabled(false)
             tapl.setIgnoreTaskbarVisibility(true)
-            this.setRotation(testSpec.startRotation)
+            this.setRotation(flicker.scenario.startRotation)
             testApp.launchViaIntent(wmHelper)
             wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
 
@@ -143,7 +142,7 @@
     @Presubmit
     @Test
     fun imeAppWindowVisibility() {
-        testSpec.assertWm {
+        flicker.assertWm {
             isAppWindowVisible(imeTestApp)
                 .then()
                 .isAppSnapshotStartingWindowVisibleFor(testApp, isOptional = true)
@@ -159,27 +158,25 @@
     @FlakyTest(bugId = 244414110)
     @Test
     open fun imeLayerIsVisibleWhenSwitchingToImeApp() {
-        testSpec.assertLayersStart { isVisible(ComponentNameMatcher.IME) }
-        testSpec.assertLayersTag(TAG_IME_VISIBLE) { isVisible(ComponentNameMatcher.IME) }
-        testSpec.assertLayersEnd { isVisible(ComponentNameMatcher.IME) }
+        flicker.assertLayersStart { isVisible(ComponentNameMatcher.IME) }
+        flicker.assertLayersTag(TAG_IME_VISIBLE) { isVisible(ComponentNameMatcher.IME) }
+        flicker.assertLayersEnd { isVisible(ComponentNameMatcher.IME) }
     }
 
     @Presubmit
     @Test
     fun imeLayerIsInvisibleWhenSwitchingToTestApp() {
-        testSpec.assertLayersTag(TAG_IME_INVISIBLE) { isInvisible(ComponentNameMatcher.IME) }
+        flicker.assertLayersTag(TAG_IME_INVISIBLE) { isInvisible(ComponentNameMatcher.IME) }
     }
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedNavigationModes =
-                        listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
-                    supportedRotations = listOf(Surface.ROTATION_0)
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL),
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
 
         private const val TAG_IME_VISIBLE = "imeVisible"
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
index 80ab016..c599b10 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
@@ -18,10 +18,11 @@
 
 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.FlickerTest
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -39,16 +40,14 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchImeWindowsFromGestureNavTest_ShellTransit(testSpec: FlickerTestParameter) :
-    SwitchImeWindowsFromGestureNavTest(testSpec) {
+class SwitchImeWindowsFromGestureNavTest_ShellTransit(flicker: FlickerTest) :
+    SwitchImeWindowsFromGestureNavTest(flicker) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
     }
 
-    @Presubmit
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
+    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     @Presubmit
     @Test
@@ -71,13 +70,13 @@
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /**
-     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the
+     * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
      * start and end of the WM trace
      */
     @Presubmit
     @Test
     fun navBarWindowIsVisibleAtStartAndEnd() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.navBarWindowIsVisibleAtStartAndEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarWindowIsVisibleAtStartAndEnd()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index 49bf86d0..5e50b45 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -20,11 +20,11 @@
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
@@ -57,13 +57,13 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ActivitiesTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class ActivitiesTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
-            tapl.setExpectedRotation(testSpec.startRotation)
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
             testApp.launchViaIntent(wmHelper)
         }
         teardown { testApp.exit(wmHelper) }
@@ -91,7 +91,7 @@
             ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
         val imeAutoFocusActivityComponent =
             ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
-        testSpec.assertWm {
+        flicker.assertWm {
             this.isAppWindowOnTop(buttonActivityComponent)
                 .then()
                 .isAppWindowOnTop(imeAutoFocusActivityComponent)
@@ -108,7 +108,7 @@
     @Presubmit
     @Test
     fun launcherWindowNotOnTop() {
-        testSpec.assertWm { this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) }
+        flicker.assertWm { this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) }
     }
 
     /**
@@ -117,20 +117,20 @@
     @Presubmit
     @Test
     fun launcherLayerNotVisible() {
-        testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
+        flicker.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
     }
 
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
index d0d7bbb..14a6668 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
@@ -17,12 +17,12 @@
 package com.android.server.wm.flicker.launch
 
 import android.platform.test.annotations.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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.CameraAppHelper
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -41,8 +41,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppAfterCameraTest(testSpec: FlickerTestParameter) :
-    OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppAfterCameraTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
     @Before
     open fun before() {
         Assume.assumeFalse(isShellTransitionsEnabled)
@@ -69,13 +68,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
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 5686965..99574ef 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
@@ -18,9 +18,9 @@
 
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -40,8 +40,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppAfterCameraTest_ShellTransit(testSpec: FlickerTestParameter) :
-    OpenAppAfterCameraTest(testSpec) {
+class OpenAppAfterCameraTest_ShellTransit(flicker: FlickerTest) : OpenAppAfterCameraTest(flicker) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
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..e0df5be 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
@@ -18,13 +18,12 @@
 
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -55,20 +54,18 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppColdFromIcon(testSpec: FlickerTestParameter) :
-    OpenAppFromLauncherTransition(testSpec) {
+class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
             setup {
-                if (testSpec.isTablet) {
-                    tapl.setExpectedRotation(testSpec.startRotation)
+                if (flicker.scenario.isTablet) {
+                    tapl.setExpectedRotation(flicker.scenario.startRotation.value)
                 } else {
-                    tapl.setExpectedRotation(Surface.ROTATION_0)
+                    tapl.setExpectedRotation(PlatformConsts.Rotation.ROTATION_0.value)
                 }
                 RemoveAllTasksButHomeRule.removeAllTasksButHome()
-                this.setRotation(testSpec.startRotation)
             }
             transitions {
                 tapl
@@ -181,13 +178,16 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            // TAPL fails on landscape mode b/240916028
+            return FlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_3BUTTON)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 7576ab9..66af72e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -19,12 +19,12 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -57,15 +57,14 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppColdTest(testSpec: FlickerTestParameter) :
-    OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppColdTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
             setup {
                 removeAllTasksButHome()
-                this.setRotation(testSpec.startRotation)
+                this.setRotation(flicker.scenario.startRotation)
             }
             teardown { testApp.exit(wmHelper) }
             transitions { testApp.launchViaIntent(wmHelper) }
@@ -83,13 +82,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
index 23748be..b234ec7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
@@ -17,28 +17,27 @@
 package com.android.server.wm.flicker.launch
 
 import android.platform.test.annotations.Presubmit
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.replacesLayer
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Test
 
 /** Base class for app launch tests */
-abstract class OpenAppFromLauncherTransition(testSpec: FlickerTestParameter) :
-    OpenAppTransition(testSpec) {
+abstract class OpenAppFromLauncherTransition(flicker: FlickerTest) : OpenAppTransition(flicker) {
 
-    /** Checks that the focus changes from the [ComponentMatcher.LAUNCHER] to [testApp] */
+    /** Checks that the focus changes from the [ComponentNameMatcher.LAUNCHER] to [testApp] */
     @Presubmit
     @Test
     open fun focusChanges() {
-        testSpec.assertEventLog { this.focusChanges("NexusLauncherActivity", testApp.`package`) }
+        flicker.assertEventLog { this.focusChanges("NexusLauncherActivity", testApp.`package`) }
     }
 
     /**
-     * Checks that [ComponentMatcher.LAUNCHER] layer is visible at the start of the transition, and
-     * is replaced by [testApp], which remains visible until the end
+     * Checks that [ComponentNameMatcher.LAUNCHER] layer is visible at the start of the transition,
+     * and is replaced by [testApp], which remains visible until the end
      */
     open fun appLayerReplacesLauncher() {
-        testSpec.replacesLayer(
+        flicker.replacesLayer(
             ComponentNameMatcher.LAUNCHER,
             testApp,
             ignoreEntriesWithRotationLayer = true,
@@ -48,14 +47,14 @@
     }
 
     /**
-     * Checks that [ComponentMatcher.LAUNCHER] window is the top window at the start of the
-     * transition, and is replaced by a [ComponentMatcher.SNAPSHOT] or
-     * [ComponentMatcher.SPLASH_SCREEN], or [testApp], which remains visible until the end
+     * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the
+     * transition, and is replaced by a [ComponentNameMatcher.SNAPSHOT] or
+     * [ComponentNameMatcher.SPLASH_SCREEN], or [testApp], which remains visible until the end
      */
     @Presubmit
     @Test
     open fun appWindowReplacesLauncherAsTopWindow() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
                 .then()
                 .isAppWindowOnTop(
@@ -68,6 +67,6 @@
     @Presubmit
     @Test
     open fun appWindowAsTopWindowAtEnd() {
-        testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
+        flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
     }
 }
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..f5f7190 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
@@ -20,10 +20,10 @@
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
 import org.junit.Ignore
@@ -44,8 +44,8 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Postsubmit
-open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) :
-    OpenAppFromNotificationCold(testSpec) {
+open class OpenAppFromLockNotificationCold(flicker: FlickerTest) :
+    OpenAppFromNotificationCold(flicker) {
 
     override val openingNotificationsFromLockScreen = true
 
@@ -93,12 +93,13 @@
      * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
      * transition
      */
-    @Presubmit @Test override fun statusBarLayerPositionAtEnd() =
-        super.statusBarLayerPositionAtEnd()
+    @Presubmit
+    @Test
+    override fun statusBarLayerPositionAtEnd() = 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} */
@@ -119,13 +120,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
index 5a7b8b9..fe49c61 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
@@ -19,10 +19,10 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
@@ -43,8 +43,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) :
-    OpenAppFromNotificationWarm(testSpec) {
+class OpenAppFromLockNotificationWarm(flicker: FlickerTest) : OpenAppFromNotificationWarm(flicker) {
 
     override val openingNotificationsFromLockScreen = true
 
@@ -70,7 +69,7 @@
     @Test
     @Presubmit
     fun appWindowBecomesFirstAndOnlyTopWindow() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.hasNoVisibleAppWindow()
                 .then()
                 .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -85,7 +84,7 @@
     @Test
     @Presubmit
     fun screenLockedStart() {
-        testSpec.assertWmStart { isKeyguardShowing() }
+        flicker.assertWmStart { isKeyguardShowing() }
     }
 
     /** {@inheritDoc} */
@@ -108,7 +107,7 @@
      * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
      * transition
      */
-    @Presubmit @Test fun statusBarLayerPositionAtEnd() = testSpec.statusBarLayerPositionAtEnd()
+    @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
 
     /** {@inheritDoc} */
     @Test
@@ -133,13 +132,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
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..d9a3ad2 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,15 +16,16 @@
 
 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
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -43,8 +44,8 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Postsubmit
-class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParameter) :
-    OpenAppFromLockNotificationCold(testSpec) {
+class OpenAppFromLockNotificationWithLockOverlayApp(flicker: FlickerTest) :
+    OpenAppFromLockNotificationCold(flicker) {
     private val showWhenLockedApp: ShowWhenLockedAppHelper =
         ShowWhenLockedAppHelper(instrumentation)
 
@@ -71,9 +72,9 @@
         }
 
     @Test
-    @Postsubmit
+    @FlakyTest(bugId = 227143265)
     fun showWhenLockedAppWindowBecomesVisible() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.hasNoVisibleAppWindow()
                 .then()
                 .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -83,9 +84,9 @@
     }
 
     @Test
-    @Postsubmit
+    @FlakyTest(bugId = 227143265)
     fun showWhenLockedAppLayerBecomesVisible() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isInvisible(showWhenLockedApp)
                 .then()
                 .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -98,22 +99,27 @@
     @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.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
index 3cc2390..718c6e9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
@@ -18,8 +18,8 @@
 
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.navBarLayerPositionAtEnd
 import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
 import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -28,8 +28,7 @@
 import org.junit.Test
 
 /** Base class for app launch tests from lock screen */
-abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) :
-    OpenAppTransition(testSpec) {
+abstract class OpenAppFromLockTransition(flicker: FlickerTest) : OpenAppTransition(flicker) {
 
     /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit = {
@@ -46,7 +45,7 @@
     @Presubmit
     @Test
     open fun focusChanges() {
-        testSpec.assertEventLog { this.focusChanges("", testApp.`package`) }
+        flicker.assertEventLog { this.focusChanges("", testApp.`package`) }
     }
 
     /**
@@ -56,7 +55,7 @@
     @FlakyTest(bugId = 203538234)
     @Test
     open fun appWindowBecomesFirstAndOnlyTopWindow() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.hasNoVisibleAppWindow()
                 .then()
                 .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -71,7 +70,7 @@
     @Presubmit
     @Test
     fun screenLockedStart() {
-        testSpec.assertLayersStart { isEmpty() }
+        flicker.assertLayersStart { isEmpty() }
     }
 
     /** {@inheritDoc} */
@@ -99,16 +98,16 @@
     @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
     override fun taskBarWindowIsAlwaysVisible() {}
 
-    /** Checks the position of the [ComponentMatcher.NAV_BAR] at the end of the transition */
+    /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
     @Presubmit
     @Test
     open fun navBarLayerPositionAtEnd() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.navBarLayerPositionAtEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerPositionAtEnd()
     }
 
-    /** Checks the position of the [ComponentMatcher.STATUS_BAR] at the end of the transition */
-    @Presubmit @Test fun statusBarLayerPositionAtEnd() = testSpec.statusBarLayerPositionAtEnd()
+    /** Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the end of the transition */
+    @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
 
     /** {@inheritDoc} */
     @Test
@@ -116,13 +115,13 @@
     override fun statusBarLayerIsVisibleAtStartAndEnd() {}
 
     /**
-     * Checks that the [ComponentMatcher.STATUS_BAR] layer is visible at the end of the trace
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the end of the trace
      *
      * It is not possible to check at the start because the screen is off
      */
     @Presubmit
     @Test
     fun statusBarLayerIsVisibleAtEnd() {
-        testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
index 8dd94cd..240e90b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
@@ -19,10 +19,10 @@
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
@@ -44,8 +44,8 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Postsubmit
-open class OpenAppFromNotificationCold(testSpec: FlickerTestParameter) :
-    OpenAppFromNotificationWarm(testSpec) {
+open class OpenAppFromNotificationCold(flicker: FlickerTest) :
+    OpenAppFromNotificationWarm(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -61,13 +61,9 @@
             }
         }
 
-    @Postsubmit
-    @Test
-    override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
+    @Postsubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
 
-    @Postsubmit
-    @Test
-    override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
+    @Postsubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
 
     /** {@inheritDoc} */
     @Test
@@ -89,9 +85,7 @@
      * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
      * transition
      */
-    @Presubmit
-    @Test
-    open fun statusBarLayerPositionAtEnd() = testSpec.statusBarLayerPositionAtEnd()
+    @Presubmit @Test open fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
 
     /** {@inheritDoc} */
     @Test
@@ -107,13 +101,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
index db48b3f..6388a5a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
@@ -24,13 +24,13 @@
 import android.view.WindowManager
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.NotificationAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
 import com.android.server.wm.flicker.navBarLayerPositionAtEnd
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtEnd
@@ -56,8 +56,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) :
-    OpenAppTransition(testSpec) {
+open class OpenAppFromNotificationWarm(flicker: FlickerTest) : OpenAppTransition(flicker) {
     override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
 
     open val openingNotificationsFromLockScreen = false
@@ -67,7 +66,7 @@
         get() = {
             setup {
                 device.wakeUpAndGoToHomeScreen()
-                this.setRotation(testSpec.startRotation)
+                this.setRotation(flicker.scenario.startRotation)
                 testApp.launchViaIntent(wmHelper)
                 wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
                 testApp.postNotification(wmHelper)
@@ -120,19 +119,19 @@
     @Presubmit
     @Test
     open fun notificationAppWindowVisibleAtEnd() {
-        testSpec.assertWmEnd { this.isAppWindowVisible(testApp) }
+        flicker.assertWmEnd { this.isAppWindowVisible(testApp) }
     }
 
     @Presubmit
     @Test
     open fun notificationAppWindowOnTopAtEnd() {
-        testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
+        flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
     }
 
     @Presubmit
     @Test
     open fun notificationAppLayerVisibleAtEnd() {
-        testSpec.assertLayersEnd { this.isVisible(testApp) }
+        flicker.assertLayersEnd { this.isVisible(testApp) }
     }
 
     /**
@@ -144,8 +143,8 @@
     @Presubmit
     @Test
     open fun taskBarWindowIsVisibleAtEnd() {
-        Assume.assumeTrue(testSpec.isTablet)
-        testSpec.taskBarWindowIsVisibleAtEnd()
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarWindowIsVisibleAtEnd()
     }
 
     /**
@@ -156,31 +155,31 @@
     @Presubmit
     @Test
     open fun taskBarLayerIsVisibleAtEnd() {
-        Assume.assumeTrue(testSpec.isTablet)
-        testSpec.taskBarLayerIsVisibleAtEnd()
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarLayerIsVisibleAtEnd()
     }
 
     /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
     @Presubmit
     @Test
     open fun navBarLayerPositionAtEnd() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.navBarLayerPositionAtEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerPositionAtEnd()
     }
 
     /** {@inheritDoc} */
     @Presubmit
     @Test
     open fun navBarLayerIsVisibleAtEnd() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.navBarLayerIsVisibleAtEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerIsVisibleAtEnd()
     }
 
     @Presubmit
     @Test
     open fun navBarWindowIsVisibleAtEnd() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.navBarWindowIsVisibleAtEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarWindowIsVisibleAtEnd()
     }
 
     /** {@inheritDoc} */
@@ -203,13 +202,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index fd8a38c..9106835 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -19,13 +19,13 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -59,8 +59,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) :
-    OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppFromOverviewTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
 
     /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
@@ -72,14 +71,14 @@
                 tapl.goHome()
                 wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
                 // By default, launcher doesn't rotate on phones, but rotates on tablets
-                if (testSpec.isTablet) {
-                    tapl.setExpectedRotation(testSpec.startRotation)
+                if (flicker.scenario.isTablet) {
+                    tapl.setExpectedRotation(flicker.scenario.startRotation.value)
                 } else {
-                    tapl.setExpectedRotation(Surface.ROTATION_0)
+                    tapl.setExpectedRotation(PlatformConsts.Rotation.ROTATION_0.value)
                 }
                 tapl.workspace.switchToOverview()
                 wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify()
-                this.setRotation(testSpec.startRotation)
+                this.setRotation(flicker.scenario.startRotation)
             }
             transitions {
                 tapl.overview.currentTask.open()
@@ -109,13 +108,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 1ecde46..f295ce3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -20,14 +20,13 @@
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 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.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Ignore
@@ -63,8 +62,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) :
-    OpenAppFromLockTransition(testSpec) {
+open class OpenAppNonResizeableTest(flicker: FlickerTest) : OpenAppFromLockTransition(flicker) {
     override val testApp = NonResizeableAppHelper(instrumentation)
 
     /**
@@ -74,8 +72,8 @@
     @FlakyTest(bugId = 227083463)
     @Test
     fun navBarLayerVisibilityChanges() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.assertLayers {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.assertLayers {
             this.isInvisible(ComponentNameMatcher.NAV_BAR)
                 .then()
                 .isVisible(ComponentNameMatcher.NAV_BAR)
@@ -86,7 +84,7 @@
     @Presubmit
     @Test
     fun appWindowBecomesVisibleAtEnd() {
-        testSpec.assertWmEnd { this.isAppWindowVisible(testApp) }
+        flicker.assertWmEnd { this.isAppWindowVisible(testApp) }
     }
 
     /**
@@ -96,8 +94,8 @@
     @Presubmit
     @Test
     fun navBarWindowsVisibilityChanges() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.assertWm {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.assertWm {
             this.isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR)
                 .then()
                 .isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
@@ -111,8 +109,8 @@
     @Presubmit
     @Test
     fun taskBarLayerIsVisibleAtEnd() {
-        Assume.assumeTrue(testSpec.isTablet)
-        testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
     }
 
     /**
@@ -123,45 +121,40 @@
     @Presubmit
     @Test
     override fun statusBarLayerIsVisibleAtStartAndEnd() {
-        testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
     }
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun taskBarLayerIsVisibleAtStartAndEnd() {
-    }
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun navBarLayerIsVisibleAtStartAndEnd() {
-    }
+    override fun navBarLayerIsVisibleAtStartAndEnd() {}
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun taskBarWindowIsAlwaysVisible() {
-    }
+    override fun taskBarWindowIsAlwaysVisible() {}
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun navBarWindowIsAlwaysVisible() {
-    }
+    override fun navBarWindowIsAlwaysVisible() {}
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarWindowIsAlwaysVisible() {
-    }
+    override fun statusBarWindowIsAlwaysVisible() {}
 
     /** Checks the [ComponentNameMatcher.NAV_BAR] is visible at the end of the transition */
     @Postsubmit
     @Test
     fun navBarLayerIsVisibleAtEnd() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
     }
 
     /** {@inheritDoc} */
@@ -174,7 +167,7 @@
     @Presubmit
     @Test
     override fun appLayerBecomesVisible() {
-        Assume.assumeFalse(testSpec.isTablet)
+        Assume.assumeFalse(flicker.scenario.isTablet)
         super.appLayerBecomesVisible()
     }
 
@@ -182,14 +175,12 @@
     @FlakyTest(bugId = 227143265)
     @Test
     fun appLayerBecomesVisibleTablet() {
-        Assume.assumeTrue(testSpec.isTablet)
+        Assume.assumeTrue(flicker.scenario.isTablet)
         super.appLayerBecomesVisible()
     }
 
     /** {@inheritDoc} */
-    @FlakyTest
-    @Test
-    override fun entireScreenCovered() = super.entireScreenCovered()
+    @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
     @FlakyTest(bugId = 218470989)
     @Test
@@ -210,18 +201,16 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedNavigationModes =
-                        listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
-                    supportedRotations = listOf(Surface.ROTATION_0)
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL),
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 4fd251a..2adb0b4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -18,8 +18,8 @@
 
 import android.platform.test.annotations.Presubmit
 import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.StandardAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
@@ -28,15 +28,15 @@
 import org.junit.Test
 
 /** Base class for app launch tests */
-abstract class OpenAppTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class OpenAppTransition(flicker: FlickerTest) : BaseTest(flicker) {
     protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
-            tapl.setExpectedRotation(testSpec.startRotation)
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
             device.wakeUpAndGoToHomeScreen()
-            this.setRotation(testSpec.startRotation)
+            this.setRotation(flicker.scenario.startRotation)
         }
         teardown { testApp.exit(wmHelper) }
     }
@@ -52,7 +52,7 @@
     }
 
     protected fun appLayerBecomesVisible_coldStart() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.notContains(testApp)
                 .then()
                 .isInvisible(testApp, isOptional = true)
@@ -66,7 +66,7 @@
     }
 
     protected fun appLayerBecomesVisible_warmStart() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isInvisible(testApp)
                 .then()
                 .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -87,7 +87,7 @@
     @Presubmit @Test open fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
 
     protected fun appWindowBecomesVisible_coldStart() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.notContains(testApp)
                 .then()
                 .isAppWindowInvisible(testApp, isOptional = true)
@@ -97,7 +97,7 @@
     }
 
     protected fun appWindowBecomesVisible_warmStart() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.isAppWindowInvisible(testApp)
                 .then()
                 .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -115,7 +115,7 @@
     @Presubmit
     @Test
     open fun appWindowBecomesTopWindow() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.isAppWindowNotOnTop(testApp)
                 .then()
                 .isAppWindowOnTop(
@@ -131,6 +131,6 @@
     @Presubmit
     @Test
     open fun appWindowIsTopWindowAtEnd() {
-        testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
+        flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 03741c8..62d7cc0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -19,12 +19,12 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -57,8 +57,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppWarmTest(testSpec: FlickerTestParameter) :
-    OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppWarmTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
     /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -68,7 +67,7 @@
                 testApp.launchViaIntent(wmHelper)
                 tapl.goHome()
                 wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-                this.setRotation(testSpec.startRotation)
+                this.setRotation(flicker.scenario.startRotation)
             }
             teardown { testApp.exit(wmHelper) }
             transitions { testApp.launchViaIntent(wmHelper) }
@@ -96,13 +95,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
index e1fd5a7..b9594a1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -22,16 +22,16 @@
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.FlickerBuilderProvider
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.R
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.StandardAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerBuilderProvider
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.WindowManagerConditionsFactory
@@ -55,7 +55,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OverrideTaskTransitionTest(val testSpec: FlickerTestParameter) {
+class OverrideTaskTransitionTest(val flicker: FlickerTest) {
 
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
@@ -66,7 +66,7 @@
             setup {
                 device.wakeUpAndGoToHomeScreen()
                 RemoveAllTasksButHomeRule.removeAllTasksButHome()
-                setRotation(testSpec.startRotation)
+                setRotation(flicker.scenario.startRotation)
             }
             transitions {
                 instrumentation.context.startActivity(
@@ -87,24 +87,24 @@
     @Presubmit
     @Test
     fun testSimpleActivityIsShownDirectly() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             // Before the app launches, only the launcher is visible.
             isVisible(ComponentNameMatcher.LAUNCHER)
-                    .isInvisible(testApp)
-                    .then()
-                    // Animation starts, but the app may not be drawn yet which means the Splash
-                    // may be visible.
-                    .isInvisible(testApp, isOptional = true)
-                    .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
-                    .then()
-                    // App shows up with the custom animation starting at alpha=1.
-                    .isVisible(testApp)
-                    .then()
-                    // App custom animation continues to alpha=0 (invisible).
-                    .isInvisible(testApp)
-                    .then()
-                    // App custom animation ends with it being visible.
-                    .isVisible(testApp)
+                .isInvisible(testApp)
+                .then()
+                // Animation starts, but the app may not be drawn yet which means the Splash
+                // may be visible.
+                .isInvisible(testApp, isOptional = true)
+                .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+                .then()
+                // App shows up with the custom animation starting at alpha=1.
+                .isVisible(testApp)
+                .then()
+                // App custom animation continues to alpha=0 (invisible).
+                .isInvisible(testApp)
+                .then()
+                // App custom animation ends with it being visible.
+                .isVisible(testApp)
         }
     }
 
@@ -123,8 +123,8 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 08624ee..4e7ab7a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -22,13 +22,13 @@
 import android.platform.test.annotations.Postsubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.NewTasksAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.SPLASH_SCREEN
 import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
@@ -56,7 +56,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val testApp = NewTasksAppHelper(instrumentation)
     private val simpleApp = SimpleAppHelper(instrumentation)
     private val wallpaper by lazy {
@@ -81,7 +81,7 @@
     @FlakyTest(bugId = 253617416)
     @Test
     fun wallpaperWindowIsNeverVisible() {
-        testSpec.assertWm { this.isNonAppWindowInvisible(wallpaper) }
+        flicker.assertWm { this.isNonAppWindowInvisible(wallpaper) }
     }
 
     /**
@@ -91,7 +91,7 @@
     @FlakyTest(bugId = 253617416)
     @Test
     fun wallpaperLayerIsNeverVisible() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isInvisible(wallpaper)
             this.isInvisible(WALLPAPER_BBQ_WRAPPER)
         }
@@ -104,7 +104,7 @@
     @Postsubmit
     @Test
     fun launcherWindowIsNeverVisible() {
-        testSpec.assertWm { this.isAppWindowInvisible(ComponentNameMatcher.LAUNCHER) }
+        flicker.assertWm { this.isAppWindowInvisible(ComponentNameMatcher.LAUNCHER) }
     }
 
     /**
@@ -114,7 +114,7 @@
     @Postsubmit
     @Test
     fun launcherLayerIsNeverVisible() {
-        testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
+        flicker.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
     }
 
     /** Checks that a color background is visible while the task transition is occurring. */
@@ -122,9 +122,9 @@
     @Test
     fun colorLayerIsVisibleDuringTransition() {
         val bgColorLayer = ComponentNameMatcher("", "colorBackgroundLayer")
-        val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+        val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
 
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
                     it.visibleRegion(testApp.componentMatcher).coversExactly(displayBounds)
                 }
@@ -157,7 +157,7 @@
     @Postsubmit
     @Test
     fun newTaskOpensOnTopAndThenCloses() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.isAppWindowOnTop(testApp.componentMatcher)
                 .then()
                 .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true)
@@ -235,8 +235,8 @@
 
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index bc1f0d1..b4a67ef 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -18,18 +18,17 @@
 
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.Rect
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -54,7 +53,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsBackTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
 
@@ -66,7 +65,7 @@
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
-            tapl.setExpectedRotation(testSpec.startRotation)
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
             tapl.setIgnoreTaskbarVisibility(true)
             testApp1.launchViaIntent(wmHelper)
             testApp2.launchViaIntent(wmHelper)
@@ -96,7 +95,7 @@
     @Presubmit
     @Test
     open fun startsWithApp2WindowsCoverFullScreen() {
-        testSpec.assertWmStart { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
+        flicker.assertWmStart { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
     }
 
     /**
@@ -106,16 +105,14 @@
     @Presubmit
     @Test
     open fun startsWithApp2LayersCoverFullScreen() {
-        testSpec.assertLayersStart {
-            this.visibleRegion(testApp2).coversExactly(startDisplayBounds)
-        }
+        flicker.assertLayersStart { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
     }
 
     /** Checks that the transition starts with [testApp2] being the top window. */
     @Presubmit
     @Test
     open fun startsWithApp2WindowBeingOnTop() {
-        testSpec.assertWmStart { this.isAppWindowOnTop(testApp2) }
+        flicker.assertWmStart { this.isAppWindowOnTop(testApp2) }
     }
 
     /**
@@ -125,7 +122,7 @@
     @Presubmit
     @Test
     open fun endsWithApp1WindowsCoveringFullScreen() {
-        testSpec.assertWmEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
+        flicker.assertWmEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
     }
 
     /**
@@ -135,7 +132,7 @@
     @Presubmit
     @Test
     fun endsWithApp1LayersCoveringFullScreen() {
-        testSpec.assertLayersEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
+        flicker.assertLayersEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
     }
 
     /**
@@ -145,7 +142,7 @@
     @Presubmit
     @Test
     open fun endsWithApp1BeingOnTop() {
-        testSpec.assertWmEnd { this.isAppWindowOnTop(testApp1) }
+        flicker.assertWmEnd { this.isAppWindowOnTop(testApp1) }
     }
 
     /**
@@ -155,7 +152,7 @@
     @Presubmit
     @Test
     open fun app1WindowBecomesAndStaysVisible() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.isAppWindowInvisible(testApp1)
                 .then()
                 .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -171,7 +168,7 @@
     @Presubmit
     @Test
     open fun app1LayerBecomesAndStaysVisible() {
-        testSpec.assertLayers { this.isInvisible(testApp1).then().isVisible(testApp1) }
+        flicker.assertLayers { this.isInvisible(testApp1).then().isVisible(testApp1) }
     }
 
     /**
@@ -181,9 +178,7 @@
     @Presubmit
     @Test
     open fun app2WindowBecomesAndStaysInvisible() {
-        testSpec.assertWm {
-            this.isAppWindowVisible(testApp2).then().isAppWindowInvisible(testApp2)
-        }
+        flicker.assertWm { this.isAppWindowVisible(testApp2).then().isAppWindowInvisible(testApp2) }
     }
 
     /**
@@ -193,7 +188,7 @@
     @Presubmit
     @Test
     open fun app2LayerBecomesAndStaysInvisible() {
-        testSpec.assertLayers { this.isVisible(testApp2).then().isInvisible(testApp2) }
+        flicker.assertLayers { this.isVisible(testApp2).then().isInvisible(testApp2) }
     }
 
     /**
@@ -204,7 +199,7 @@
     @Presubmit
     @Test
     open fun app1WindowIsVisibleOnceApp2WindowIsInvisible() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.isAppWindowVisible(testApp2)
                 .then()
                 // TODO: Do we actually want to test this? Seems too implementation specific...
@@ -224,7 +219,7 @@
     @Presubmit
     @Test
     open fun app1LayerIsVisibleOnceApp2LayerIsInvisible() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isVisible(testApp2)
                 .then()
                 .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
@@ -240,13 +235,10 @@
 
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedNavigationModes =
-                        listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
-                    supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
index f988bb2..ec4e35c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -19,9 +19,9 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Assume
@@ -49,8 +49,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(testSpec: FlickerTestParameter) :
-    QuickSwitchBetweenTwoAppsBackTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(flicker: FlickerTest) :
+    QuickSwitchBetweenTwoAppsBackTest(flicker) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
@@ -62,21 +62,20 @@
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /**
-     * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at
-     * the start and end of the WM trace
+     * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
+     * start and end of the WM trace
      */
     @Presubmit
     @Test
     fun navBarWindowIsVisibleAtStartAndEnd() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.navBarWindowIsVisibleAtStartAndEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarWindowIsVisibleAtStartAndEnd()
     }
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 250520840)
     @Test
-    override fun startsWithApp2LayersCoverFullScreen() =
-        super.startsWithApp2LayersCoverFullScreen()
+    override fun startsWithApp2LayersCoverFullScreen() = super.startsWithApp2LayersCoverFullScreen()
 
     @FlakyTest(bugId = 246284708)
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 7e4504b..593481c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -18,18 +18,17 @@
 
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.Rect
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -55,8 +54,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter) :
-    BaseTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsForwardTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
 
@@ -68,7 +66,7 @@
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
-            tapl.setExpectedRotation(testSpec.startRotation)
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
 
             testApp1.launchViaIntent(wmHelper)
             testApp2.launchViaIntent(wmHelper)
@@ -105,7 +103,7 @@
     @Presubmit
     @Test
     open fun startsWithApp1WindowsCoverFullScreen() {
-        testSpec.assertWmStart {
+        flicker.assertWmStart {
             this.visibleRegion(testApp1.or(ComponentNameMatcher.LETTERBOX))
                 .coversExactly(startDisplayBounds)
         }
@@ -118,16 +116,14 @@
     @Presubmit
     @Test
     open fun startsWithApp1LayersCoverFullScreen() {
-        testSpec.assertLayersStart {
-            this.visibleRegion(testApp1).coversExactly(startDisplayBounds)
-        }
+        flicker.assertLayersStart { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
     }
 
     /** Checks that the transition starts with [testApp1] being the top window. */
     @Presubmit
     @Test
     open fun startsWithApp1WindowBeingOnTop() {
-        testSpec.assertWmStart { this.isAppWindowOnTop(testApp1) }
+        flicker.assertWmStart { this.isAppWindowOnTop(testApp1) }
     }
 
     /**
@@ -137,7 +133,7 @@
     @Presubmit
     @Test
     open fun endsWithApp2WindowsCoveringFullScreen() {
-        testSpec.assertWmEnd { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
+        flicker.assertWmEnd { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
     }
 
     /**
@@ -147,7 +143,7 @@
     @Presubmit
     @Test
     open fun endsWithApp2LayersCoveringFullScreen() {
-        testSpec.assertLayersEnd {
+        flicker.assertLayersEnd {
             this.visibleRegion(testApp2.or(ComponentNameMatcher.LETTERBOX))
                 .coversExactly(startDisplayBounds)
         }
@@ -160,7 +156,7 @@
     @Presubmit
     @Test
     open fun endsWithApp2BeingOnTop() {
-        testSpec.assertWmEnd { this.isAppWindowOnTop(testApp2) }
+        flicker.assertWmEnd { this.isAppWindowOnTop(testApp2) }
     }
 
     /**
@@ -170,7 +166,7 @@
     @Presubmit
     @Test
     open fun app2WindowBecomesAndStaysVisible() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.isAppWindowInvisible(testApp2)
                 .then()
                 .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -186,7 +182,7 @@
     @Presubmit
     @Test
     open fun app2LayerBecomesAndStaysVisible() {
-        testSpec.assertLayers { this.isInvisible(testApp2).then().isVisible(testApp2) }
+        flicker.assertLayers { this.isInvisible(testApp2).then().isVisible(testApp2) }
     }
 
     /**
@@ -196,9 +192,7 @@
     @Presubmit
     @Test
     open fun app1WindowBecomesAndStaysInvisible() {
-        testSpec.assertWm {
-            this.isAppWindowVisible(testApp1).then().isAppWindowInvisible(testApp1)
-        }
+        flicker.assertWm { this.isAppWindowVisible(testApp1).then().isAppWindowInvisible(testApp1) }
     }
 
     /**
@@ -208,7 +202,7 @@
     @Presubmit
     @Test
     open fun app1LayerBecomesAndStaysInvisible() {
-        testSpec.assertLayers { this.isVisible(testApp1).then().isInvisible(testApp1) }
+        flicker.assertLayers { this.isVisible(testApp1).then().isInvisible(testApp1) }
     }
 
     /**
@@ -219,7 +213,7 @@
     @Presubmit
     @Test
     open fun app2WindowIsVisibleOnceApp1WindowIsInvisible() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.isAppWindowVisible(testApp1)
                 .then()
                 .isAppWindowVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
@@ -238,7 +232,7 @@
     @Presubmit
     @Test
     open fun app2LayerIsVisibleOnceApp1LayerIsInvisible() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isVisible(testApp1)
                 .then()
                 .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
@@ -259,13 +253,10 @@
 
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedNavigationModes =
-                        listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
-                    supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
index cc954ab..477b419 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
@@ -19,9 +19,9 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Assume
@@ -50,8 +50,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(testSpec: FlickerTestParameter) :
-    QuickSwitchBetweenTwoAppsForwardTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(flicker: FlickerTest) :
+    QuickSwitchBetweenTwoAppsForwardTest(flicker) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
@@ -63,14 +63,14 @@
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /**
-     * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at
-     * the start and end of the WM trace
+     * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
+     * start and end of the WM trace
      */
     @Presubmit
     @Test
     fun navBarWindowIsVisibleAtStartAndEnd() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.navBarWindowIsVisibleAtStartAndEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarWindowIsVisibleAtStartAndEnd()
     }
 
     @FlakyTest(bugId = 246284708)
@@ -84,6 +84,5 @@
 
     @FlakyTest(bugId = 250522691)
     @Test
-    override fun startsWithApp1LayersCoverFullScreen() =
-        super.startsWithApp1LayersCoverFullScreen()
+    override fun startsWithApp1LayersCoverFullScreen() = super.startsWithApp1LayersCoverFullScreen()
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 3cb985a..8c8220f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -19,18 +19,17 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 import com.android.server.wm.flicker.BaseTest
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.Rect
+import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Ignore
@@ -55,7 +54,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class QuickSwitchFromLauncherTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val testApp = SimpleAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -63,7 +62,7 @@
         setup {
             tapl.setExpectedRotationCheckEnabled(false)
 
-            tapl.setExpectedRotation(testSpec.startRotation)
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
 
             testApp.launchViaIntent(wmHelper)
             tapl.goHome()
@@ -95,7 +94,7 @@
     @Presubmit
     @Test
     fun endsWithAppWindowsCoveringFullScreen() {
-        testSpec.assertWmEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) }
+        flicker.assertWmEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) }
     }
 
     /**
@@ -105,7 +104,7 @@
     @Presubmit
     @Test
     fun endsWithAppLayersCoveringFullScreen() {
-        testSpec.assertLayersEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) }
+        flicker.assertLayersEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) }
     }
 
     /**
@@ -115,47 +114,48 @@
     @Presubmit
     @Test
     fun endsWithAppBeingOnTop() {
-        testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
+        flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
     }
 
     /** Checks that the transition starts with the home activity being tagged as visible. */
     @Presubmit
     @Test
     fun startsWithHomeActivityFlaggedVisible() {
-        testSpec.assertWmStart { this.isHomeActivityVisible() }
+        flicker.assertWmStart { this.isHomeActivityVisible() }
     }
 
     /**
-     * Checks that the transition starts with the [ComponentMatcher.LAUNCHER] windows
+     * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] windows
      * filling/covering exactly display size
      */
     @Presubmit
     @Test
     fun startsWithLauncherWindowsCoverFullScreen() {
-        testSpec.assertWmStart {
+        flicker.assertWmStart {
             this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
         }
     }
 
     /**
-     * Checks that the transition starts with the [ComponentMatcher.LAUNCHER] layers
+     * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] layers
      * filling/covering exactly the display size.
      */
     @Presubmit
     @Test
     fun startsWithLauncherLayersCoverFullScreen() {
-        testSpec.assertLayersStart {
+        flicker.assertLayersStart {
             this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
         }
     }
 
     /**
-     * Checks that the transition starts with the [ComponentMatcher.LAUNCHER] being the top window.
+     * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] being the top
+     * window.
      */
     @Presubmit
     @Test
     fun startsWithLauncherBeingOnTop() {
-        testSpec.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) }
+        flicker.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) }
     }
 
     /**
@@ -165,7 +165,7 @@
     @Presubmit
     @Test
     fun endsWithHomeActivityFlaggedInvisible() {
-        testSpec.assertWmEnd { this.isHomeActivityInvisible() }
+        flicker.assertWmEnd { this.isHomeActivityInvisible() }
     }
 
     /**
@@ -175,7 +175,7 @@
     @Presubmit
     @Test
     fun appWindowBecomesAndStaysVisible() {
-        testSpec.assertWm { this.isAppWindowInvisible(testApp).then().isAppWindowVisible(testApp) }
+        flicker.assertWm { this.isAppWindowInvisible(testApp).then().isAppWindowVisible(testApp) }
     }
 
     /**
@@ -185,18 +185,18 @@
     @Presubmit
     @Test
     fun appLayerBecomesAndStaysVisible() {
-        testSpec.assertLayers { this.isInvisible(testApp).then().isVisible(testApp) }
+        flicker.assertLayers { this.isInvisible(testApp).then().isVisible(testApp) }
     }
 
     /**
-     * Checks that the [ComponentMatcher.LAUNCHER] window starts off visible and becomes invisible
-     * at some point before the end of the transition and then stays invisible until the end of the
-     * transition.
+     * Checks that the [ComponentNameMatcher.LAUNCHER] window starts off visible and becomes
+     * invisible at some point before the end of the transition and then stays invisible until the
+     * end of the transition.
      */
     @Presubmit
     @Test
     fun launcherWindowBecomesAndStaysInvisible() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
                 .then()
                 .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER)
@@ -204,14 +204,14 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.LAUNCHER] layer starts off visible and becomes invisible at
-     * some point before the end of the transition and then stays invisible until the end of the
-     * transition.
+     * Checks that the [ComponentNameMatcher.LAUNCHER] layer starts off visible and becomes
+     * invisible at some point before the end of the transition and then stays invisible until the
+     * end of the transition.
      */
     @Presubmit
     @Test
     fun launcherLayerBecomesAndStaysInvisible() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isVisible(ComponentNameMatcher.LAUNCHER)
                 .then()
                 .isInvisible(ComponentNameMatcher.LAUNCHER)
@@ -219,14 +219,14 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.LAUNCHER] window is visible at least until the app window
-     * is visible. Ensures that at any point, either the launcher or [testApp] windows are at least
-     * partially visible.
+     * Checks that the [ComponentNameMatcher.LAUNCHER] window is visible at least until the app
+     * window is visible. Ensures that at any point, either the launcher or [testApp] windows are at
+     * least partially visible.
      */
     @Presubmit
     @Test
     fun appWindowIsVisibleOnceLauncherWindowIsInvisible() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
                 .then()
                 .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -236,14 +236,14 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.LAUNCHER] layer is visible at least until the app layer is
-     * visible. Ensures that at any point, either the launcher or [testApp] layers are at least
+     * Checks that the [ComponentNameMatcher.LAUNCHER] layer is visible at least until the app layer
+     * is visible. Ensures that at any point, either the launcher or [testApp] layers are at least
      * partially visible.
      */
     @Presubmit
     @Test
     fun appLayerIsVisibleOnceLauncherLayerIsInvisible() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isVisible(ComponentNameMatcher.LAUNCHER)
                 .then()
                 .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -263,14 +263,14 @@
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /**
-     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the
+     * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
      * start and end of the WM trace
      */
     @Presubmit
     @Test
     fun navBarWindowIsVisibleAtStartAndEnd() {
-        Assume.assumeFalse(testSpec.isTablet)
-        testSpec.navBarWindowIsVisibleAtStartAndEnd()
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarWindowIsVisibleAtStartAndEnd()
     }
 
     @Presubmit
@@ -293,14 +293,12 @@
 
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(
-                    supportedNavigationModes =
-                        listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
-                    // TODO: Test with 90 rotation
-                    supportedRotations = listOf(Surface.ROTATION_0)
-                )
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL),
+                // TODO: Test with 90 rotation
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 1973ec0..5b52c75 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -17,13 +17,14 @@
 package com.android.server.wm.flicker.rotation
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 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.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -77,7 +78,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ChangeAppRotationTest(testSpec: FlickerTestParameter) : RotationTransition(testSpec) {
+class ChangeAppRotationTest(flicker: FlickerTest) : RotationTransition(flicker) {
     override val testApp = SimpleAppHelper(instrumentation)
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -92,15 +93,15 @@
     @Presubmit
     @Test
     fun focusChanges() {
-        testSpec.assertEventLog { this.focusChanges(testApp.`package`) }
+        flicker.assertEventLog { this.focusChanges(testApp.`package`) }
     }
 
     /**
-     * Checks that the [ComponentMatcher.ROTATION] layer appears during the transition, doesn't
+     * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
      * flicker, and disappears before the transition is complete
      */
     fun rotationLayerAppearsAndVanishesAssertion() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.isVisible(testApp)
                 .then()
                 .isVisible(ComponentNameMatcher.ROTATION)
@@ -111,7 +112,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.ROTATION] layer appears during the transition, doesn't
+     * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
      * flicker, and disappears before the transition is complete
      */
     @Presubmit
@@ -125,17 +126,25 @@
     @Test
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        focusChanges()
+        rotationLayerAppearsAndVanishes()
+    }
+
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
+         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation
+         * modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigRotationTests()
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.rotationTests()
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 4faeb24..4ef9eaf 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -18,29 +18,29 @@
 
 import android.platform.test.annotations.Presubmit
 import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.StandardAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Test
 
 /** Base class for app rotation tests */
-abstract class RotationTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class RotationTransition(flicker: FlickerTest) : BaseTest(flicker) {
     protected abstract val testApp: StandardAppHelper
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
-        setup { this.setRotation(testSpec.startRotation) }
+        setup { this.setRotation(flicker.scenario.startRotation) }
         teardown { testApp.exit(wmHelper) }
-        transitions { this.setRotation(testSpec.endRotation) }
+        transitions { this.setRotation(flicker.scenario.endRotation) }
     }
 
     /** {@inheritDoc} */
     @Presubmit
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.visibleLayersShownMoreThanOneConsecutiveEntry(
                 ignoreLayers =
                     listOf(
@@ -56,7 +56,7 @@
     @Presubmit
     @Test
     open fun appLayerRotates_StartingPos() {
-        testSpec.assertLayersStart {
+        flicker.assertLayersStart {
             this.entry.displays.map { display ->
                 this.visibleRegion(testApp).coversExactly(display.layerStackSpace)
             }
@@ -67,10 +67,16 @@
     @Presubmit
     @Test
     open fun appLayerRotates_EndingPos() {
-        testSpec.assertLayersEnd {
+        flicker.assertLayersEnd {
             this.entry.displays.map { display ->
                 this.visibleRegion(testApp).coversExactly(display.layerStackSpace)
             }
         }
     }
+
+    override fun cujCompleted() {
+        super.cujCompleted()
+        appLayerRotates_StartingPos()
+        appLayerRotates_EndingPos()
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index a08db29..54f38c3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -17,14 +17,16 @@
 package com.android.server.wm.flicker.rotation
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import android.view.WindowManager
-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.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.ScenarioBuilder
 import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
@@ -37,7 +39,7 @@
 /**
  * Test opening an app and cycling through app rotations using seamless rotations
  *
- * Currently runs:
+ * Currently, runs:
  * ```
  *      0 -> 90 degrees
  *      0 -> 90 degrees (with starved UI thread)
@@ -82,7 +84,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SeamlessAppRotationTest(testSpec: FlickerTestParameter) : RotationTransition(testSpec) {
+open class SeamlessAppRotationTest(flicker: FlickerTest) : RotationTransition(flicker) {
     override val testApp = SeamlessRotationAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -95,7 +97,7 @@
                     stringExtras =
                         mapOf(
                             ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD to
-                                testSpec.starveUiThread.toString()
+                                flicker.starveUiThread.toString()
                         )
                 )
             }
@@ -105,7 +107,7 @@
     @Presubmit
     @Test
     fun appWindowFullScreen() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.invoke("isFullScreen") {
                 val appWindow = it.windowState(testApp.`package`)
                 val flags = appWindow.windowState?.attributes?.flags ?: 0
@@ -121,7 +123,7 @@
     @Presubmit
     @Test
     fun appWindowSeamlessRotation() {
-        testSpec.assertWm {
+        flicker.assertWm {
             this.invoke("isRotationSeamless") {
                 val appWindow = it.windowState(testApp.`package`)
                 val rotationAnimation = appWindow.windowState?.attributes?.rotationAnimation ?: 0
@@ -141,14 +143,14 @@
     @Presubmit
     @Test
     fun appLayerAlwaysVisible() {
-        testSpec.assertLayers { isVisible(testApp) }
+        flicker.assertLayers { isVisible(testApp) }
     }
 
     /** Checks that [testApp] layer covers the entire screen during the whole transition */
     @Presubmit
     @Test
     fun appLayerRotates() {
-        testSpec.assertLayers {
+        flicker.assertLayers {
             this.invoke("entireScreenCovered") { entry ->
                 entry.entry.displays.map { display ->
                     entry.visibleRegion(testApp).coversExactly(display.layerStackSpace)
@@ -179,7 +181,7 @@
     @Presubmit
     @Test
     fun statusBarWindowIsAlwaysInvisible() {
-        testSpec.assertWm { this.isAboveAppWindowInvisible(ComponentNameMatcher.STATUS_BAR) }
+        flicker.assertWm { this.isAboveAppWindowInvisible(ComponentNameMatcher.STATUS_BAR) }
     }
 
     /**
@@ -189,14 +191,14 @@
     @Presubmit
     @Test
     fun statusBarLayerIsAlwaysInvisible() {
-        testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+        flicker.assertLayers { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
     }
 
     /** Checks that the focus doesn't change during animation */
     @Presubmit
     @Test
     fun focusDoesNotChange() {
-        testSpec.assertEventLog { this.focusDoesNotChange() }
+        flicker.assertEventLog { this.focusDoesNotChange() }
     }
 
     /** {@inheritDoc} */
@@ -204,50 +206,65 @@
     @Test
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
-    companion object {
-        private val FlickerTestParameter.starveUiThread
-            get() =
-                config.getOrDefault(ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD, false)
-                    as Boolean
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        if (!flicker.scenario.isTablet) {
+            // not yet tablet compatible
+            appLayerRotates()
+            appLayerAlwaysVisible()
+        }
 
-        private fun createConfig(
-            sourceConfig: FlickerTestParameter,
-            starveUiThread: Boolean
-        ): FlickerTestParameter {
-            val newConfig =
-                sourceConfig.config.toMutableMap().also {
-                    it[ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD] = starveUiThread
-                }
+        appWindowFullScreen()
+        appWindowSeamlessRotation()
+        focusDoesNotChange()
+        statusBarLayerIsAlwaysInvisible()
+        statusBarWindowIsAlwaysInvisible()
+        appLayerRotates_StartingPos()
+        appLayerRotates_EndingPos()
+        entireScreenCovered()
+        navBarLayerIsVisibleAtStartAndEnd()
+        navBarWindowIsAlwaysVisible()
+        taskBarLayerIsVisibleAtStartAndEnd()
+        taskBarWindowIsAlwaysVisible()
+        visibleLayersShownMoreThanOneConsecutiveEntry()
+        visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
+
+    companion object {
+        private val FlickerTest.starveUiThread
+            get() =
+                getConfigValue<Boolean>(ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD)
+                    ?: false
+
+        private fun createConfig(sourceConfig: FlickerTest, starveUiThread: Boolean): FlickerTest {
+            val originalScenario = sourceConfig.initialize("createConfig")
             val nameExt = if (starveUiThread) "_BUSY_UI_THREAD" else ""
-            return FlickerTestParameter(newConfig, nameOverride = "$sourceConfig$nameExt")
+            val newConfig =
+                ScenarioBuilder()
+                    .fromScenario(originalScenario)
+                    .withExtraConfig(
+                        ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD,
+                        starveUiThread
+                    )
+                    .withDescriptionOverride("${originalScenario.description}$nameExt")
+            return FlickerTest(newConfig)
         }
 
         /**
          * Creates the test configurations for seamless rotation based on the default rotation tests
-         * from [FlickerTestParameterFactory.getConfigRotationTests], but adding an additional flag
-         * ([ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should
+         * from [FlickerTestFactory.rotationTests], but adding a flag (
+         * [ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should
          * starve the UI thread of not
-         */
+        */
+        @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        private fun getConfigurations(): List<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigRotationTests().flatMap {
-                sourceConfig ->
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.rotationTests().flatMap { sourceConfig ->
                 val defaultRun = createConfig(sourceConfig, starveUiThread = false)
                 val busyUiRun = createConfig(sourceConfig, starveUiThread = true)
                 listOf(defaultRun, busyUiRun)
             }
         }
-
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring repetitions,
-         * screen orientation and navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTestParameter> {
-            return getConfigurations()
-        }
     }
 }
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/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
index 2ad0da9..8b9c020 100644
--- a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
+++ b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
@@ -58,7 +58,7 @@
         Object hash = getProperty(props, "__hash__");
 
         if (name instanceof String && hash instanceof Integer) {
-            return String.format(Locale.US, "%s@%x", name, hash);
+            return String.format(Locale.US, "%s@%x", name, (Integer) hash);
         } else {
             return null;
         }
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 939c7de..7383d6a 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -1146,5 +1146,13 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="MeshActivity"
+                  android:label="Mesh/SimpleMesh"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.android.test.hwui.TEST"/>
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
new file mode 100644
index 0000000..efe242c
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.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.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Mesh;
+import android.graphics.MeshSpecification;
+import android.graphics.MeshSpecification.Attribute;
+import android.graphics.MeshSpecification.Varying;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.View;
+
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+public class MeshActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(new MeshView(this));
+    }
+
+    static class MeshView extends View {
+        MeshView(Context c) {
+            super(c);
+            this.setOnTouchListener((v, event) -> {
+                invalidate();
+                return true;
+            });
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            MeshSpecification meshSpec = createMeshSpecification();
+            FloatBuffer vertexBuffer = FloatBuffer.allocate(6);
+            vertexBuffer.put(0, 100.0f);
+            vertexBuffer.put(1, 100.0f);
+            vertexBuffer.put(2, 400.0f);
+            vertexBuffer.put(3, 0.0f);
+            vertexBuffer.put(4, 0.0f);
+            vertexBuffer.put(5, 400.0f);
+            vertexBuffer.rewind();
+            Mesh mesh = Mesh.make(
+                    meshSpec, Mesh.Mode.Triangles, vertexBuffer, 3, new Rect(0, 0, 1000, 1000));
+
+            int numTriangles = 100;
+            // number of triangles plus first 2 vertices
+            FloatBuffer iVertexBuffer = FloatBuffer.allocate(numTriangles * 2 + 4);
+            ShortBuffer indexBuffer = ShortBuffer.allocate(300);
+
+            int radius = 200;
+            // origin
+            iVertexBuffer.put(0, 500.0f);
+            iVertexBuffer.put(1, 500.0f);
+
+            // first point
+            iVertexBuffer.put(2, 500.0f + radius);
+            iVertexBuffer.put(3, 500.0f);
+            int nVert = 2;
+            int nInd = 0;
+            for (int i = 1; i <= numTriangles; i++) {
+                double angle = (Math.PI * i) / numTriangles;
+                double x = radius * Math.cos(angle);
+                double y = radius * Math.sin(angle);
+                iVertexBuffer.put((i + 1) * 2, 500 + (float) x);
+                iVertexBuffer.put((i + 1) * 2 + 1, 500 + (float) y);
+
+                indexBuffer.put(nInd++, (short) 0);
+                indexBuffer.put(nInd++, (short) (nVert - 1));
+                indexBuffer.put(nInd++, (short) nVert);
+                nVert++;
+            }
+            iVertexBuffer.rewind();
+            indexBuffer.rewind();
+            Mesh mesh2 = Mesh.makeIndexed(meshSpec, Mesh.Mode.Triangles, iVertexBuffer, 102,
+                    indexBuffer, new Rect(0, 0, 1000, 1000));
+
+            Paint paint = new Paint();
+            paint.setColor(Color.RED);
+            canvas.drawMesh(mesh, BlendMode.COLOR, new Paint());
+            canvas.drawMesh(mesh2, BlendMode.COLOR, paint);
+        }
+
+        private MeshSpecification createMeshSpecification() {
+            String vs = "Varyings main(const Attributes attributes) { "
+                    + "     Varyings varyings;"
+                    + "     varyings.position = attributes.position;"
+                    + "     return varyings;"
+                    + "}";
+            String fs = "float2 main(const Varyings varyings, out float4 color) {\n"
+                    + "      color = vec4(1.0, 0.0, 0.0, 1.0);"
+                    + "      return varyings.position;\n"
+                    + "}";
+            Attribute[] attList =
+                    new Attribute[] {new Attribute(MeshSpecification.FLOAT2, 0, "position")};
+            Varying[] varyList =
+                    new MeshSpecification.Varying[] {};
+            return MeshSpecification.make(attList, 8, varyList, vs, fs);
+        }
+    }
+}
diff --git a/tests/InputMethodStressTest/AndroidManifest.xml b/tests/InputMethodStressTest/AndroidManifest.xml
index f5fe8f2..2d183bc 100644
--- a/tests/InputMethodStressTest/AndroidManifest.xml
+++ b/tests/InputMethodStressTest/AndroidManifest.xml
@@ -16,11 +16,11 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.inputmethod.stresstest">
+          package="com.android.inputmethod.stresstest">
 
     <application>
-        <activity android:name=".AutoShowTest$TestActivity"/>
-        <activity android:name=".ImeOpenCloseStressTest$TestActivity"/>
+        <activity android:name=".ImeStressTestUtil$TestActivity"
+                  android:configChanges="orientation|screenSize"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/InputMethodStressTest/TEST_MAPPING b/tests/InputMethodStressTest/TEST_MAPPING
index ad07205..06e2ce8 100644
--- a/tests/InputMethodStressTest/TEST_MAPPING
+++ b/tests/InputMethodStressTest/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-  "presubmit": [
+  "presubmit-large": [
     {
       "name": "InputMethodStressTest"
     }
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
index 92ea029..8e4ecf1 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -16,24 +16,32 @@
 
 package com.android.inputmethod.stresstest;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.UNFOCUSABLE_VIEW;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden;
-import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
 
-import android.app.Activity;
+import static com.google.common.truth.Truth.assertThat;
+
 import android.app.Instrumentation;
 import android.content.Intent;
-import android.os.Bundle;
+import android.os.SystemClock;
 import android.platform.test.annotations.RootPermissionTest;
 import android.platform.test.rule.UnlockScreenRule;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
 import android.view.WindowManager;
 import android.widget.EditText;
-import android.widget.LinearLayout;
 
-import androidx.annotation.Nullable;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Rule;
@@ -41,135 +49,428 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
-import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
- * Tests to verify the "auto show" behavior in {@code InputMethodManagerService} when the window
+ * Tests to verify the "auto-show" behavior in {@code InputMethodManagerService} when the window
  * gaining the focus to start the input.
  */
 @RootPermissionTest
 @RunWith(Parameterized.class)
 public final class AutoShowTest {
 
-    @Rule
-    public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
+    @Rule public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
 
     @Rule
     public ScreenCaptureRule mScreenCaptureRule =
             new ScreenCaptureRule("/sdcard/InputMethodStressTest");
 
-    private static final int[] SOFT_INPUT_VISIBILITY_FLAGS =
-            new int[] {
-                WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED,
-                WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN,
-                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN,
-                WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE,
-                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE,
-            };
-
-    private static final int[] SOFT_INPUT_ADJUST_FLAGS =
-            new int[] {
-                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED,
-                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
-                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN,
-                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
-            };
-
     // TODO(b/240359838): add test case {@code Configuration.SCREENLAYOUT_SIZE_LARGE}.
     @Parameterized.Parameters(
-            name =
-                    "softInputVisibility={0}, softInputAdjustment={1},"
-                            + " softInputModeIsForwardNavigation={2}")
-    public static List<Object[]> softInputModeConfigs() {
-        ArrayList<Object[]> params = new ArrayList<>();
-        for (int softInputVisibility : SOFT_INPUT_VISIBILITY_FLAGS) {
-            for (int softInputAdjust : SOFT_INPUT_ADJUST_FLAGS) {
-                params.add(new Object[] {softInputVisibility, softInputAdjust, true});
-                params.add(new Object[] {softInputVisibility, softInputAdjust, false});
-            }
-        }
-        return params;
+            name = "windowFocusFlags={0}, softInputVisibility={1}, softInputAdjustment={2}")
+    public static List<Object[]> windowAndSoftInputFlagParameters() {
+        return getWindowAndSoftInputFlagParameters();
     }
 
-    private static final String SOFT_INPUT_FLAGS = "soft_input_flags";
+    private final int mSoftInputFlags;
+    private final int mWindowFocusFlags;
+    private final Instrumentation mInstrumentation;
 
-    private final int mSoftInputVisibility;
-    private final int mSoftInputAdjustment;
-    private final boolean mSoftInputIsForwardNavigation;
+    public AutoShowTest(int windowFocusFlags, int softInputVisibility, int softInputAdjustment) {
+        mSoftInputFlags = softInputVisibility | softInputAdjustment;
+        mWindowFocusFlags = windowFocusFlags;
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    }
 
-    public AutoShowTest(
-            int softInputVisibility,
-            int softInputAdjustment,
-            boolean softInputIsForwardNavigation) {
-        mSoftInputVisibility = softInputVisibility;
-        mSoftInputAdjustment = softInputAdjustment;
-        mSoftInputIsForwardNavigation = softInputIsForwardNavigation;
+    /**
+     * Test auto-show IME behavior when the {@link EditText} is focusable ({@link
+     * EditText#isFocusableInTouchMode} is {@code true}) and has called {@link
+     * EditText#requestFocus}.
+     */
+    @Test
+    public void autoShow_hasFocusedView_requestFocus() {
+        // request focus at onCreate()
+        Intent intent =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+        TestActivity activity = TestActivity.start(intent);
+
+        verifyAutoShowBehavior_forwardWithKeyboardOff(activity);
+    }
+
+    /**
+     * Test auto-show IME behavior when the {@link EditText} is focusable ({@link
+     * EditText#isFocusableInTouchMode} is {@code true}) and {@link EditText#requestFocus} is not
+     * called. The IME should never be shown because there is no focused editor in the window.
+     */
+    @Test
+    public void autoShow_hasFocusedView_notRequestFocus() {
+        // request focus not set
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        EditText editText = activity.getEditText();
+
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus.
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false);
+        } else {
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+        }
+        // IME is always hidden because there is no view focus.
+        verifyImeIsAlwaysHidden(editText);
+    }
+
+    /**
+     * Test auto-show IME behavior when the {@link EditText} is not focusable ({@link
+     * EditText#isFocusableInTouchMode} is {@code false}) and {@link EditText#requestFocus} is not
+     * called. The IME should never be shown because there is no focusable editor in the window.
+     */
+    @Test
+    public void autoShow_notFocusedView_notRequestFocus() {
+        // Unfocusable view, request focus not set
+        Intent intent =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Collections.singletonList(UNFOCUSABLE_VIEW));
+        TestActivity activity = TestActivity.start(intent);
+        EditText editText = activity.getEditText();
+
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus.
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false);
+        } else {
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+        }
+        // IME is always hidden because there is no focused view.
+        verifyImeIsAlwaysHidden(editText);
+    }
+
+    /**
+     * Test auto-show IME behavior when the activity is navigated forward from another activity with
+     * keyboard off.
+     */
+    @Test
+    public void autoShow_forwardWithKeyboardOff() {
+        // Create first activity with keyboard off
+        Intent intent1 =
+                createIntent(
+                        0x0 /* No window focus flags */,
+                        WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+                                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+                        Collections.emptyList());
+        TestActivity firstActivity = TestActivity.start(intent1);
+
+        // Create second activity with parameterized flags:
+        Intent intent2 =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+        TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2);
+
+        // The auto-show behavior should be the same as opening the app
+        verifyAutoShowBehavior_forwardWithKeyboardOff(secondActivity);
+    }
+
+    /**
+     * Test auto-show IME behavior when the activity is navigated forward from another activity with
+     * keyboard on.
+     */
+    @Test
+    public void autoShow_forwardWithKeyboardOn() {
+        // Create first activity with keyboard on
+        Intent intent1 =
+                createIntent(
+                        0x0 /* No window focus flags */,
+                        WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+                                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+                        Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+        TestActivity firstActivity = TestActivity.start(intent1);
+        // Show Ime with InputMethodManager to ensure the keyboard is on.
+        boolean succ = callOnMainSync(firstActivity::showImeWithInputMethodManager);
+        assertThat(succ).isTrue();
+        SystemClock.sleep(1000);
+        mInstrumentation.waitForIdleSync();
+
+        // Create second activity with parameterized flags:
+        Intent intent2 =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+        TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2);
+
+        // The auto-show behavior should be the same as open app
+        verifyAutoShowBehavior_forwardWithKeyboardOn(secondActivity);
+    }
+
+    /**
+     * Test auto-show IME behavior when the activity is navigated back from another activity with
+     * keyboard off.
+     */
+    @Test
+    public void autoShow_backwardWithKeyboardOff() {
+        // Not request focus at onCreate() to avoid triggering auto-show behavior
+        Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity firstActivity = TestActivity.start(intent1);
+        // Request view focus after app starts
+        mInstrumentation.runOnMainSync(firstActivity::requestFocus);
+
+        Intent intent2 =
+                createIntent(
+                        0x0 /* No window focus flags */,
+                        WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+                                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+                        Collections.emptyList());
+        TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2);
+        secondActivity.finish();
+        mInstrumentation.waitForIdleSync();
+
+        // When activity is navigated back from another activity with keyboard off, the keyboard
+        // will not show except when soft input visibility flag is SOFT_INPUT_STATE_ALWAYS_VISIBLE.
+        verifyAutoShowBehavior_backwardWithKeyboardOff(firstActivity);
+    }
+
+    /**
+     * Test auto-show IME behavior when the activity is navigated back from another activity with
+     * keyboard on.
+     */
+    @Test
+    public void autoShow_backwardWithKeyboardOn() {
+        // Not request focus at onCreate() to avoid triggering auto-show behavior
+        Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent1);
+        // Request view focus after app starts
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+
+        // Create second TestActivity
+        Intent intent2 =
+                createIntent(
+                        0x0 /* No window focus flags */,
+                        WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+                                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+                        Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+        ImeStressTestUtil.TestActivity secondActivity = activity.startSecondTestActivity(intent2);
+        // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
+        boolean succ = callOnMainSync(secondActivity::showImeWithInputMethodManager);
+        assertThat(succ).isTrue();
+        SystemClock.sleep(1000);
+        mInstrumentation.waitForIdleSync();
+        // Close the second activity
+        secondActivity.finish();
+        SystemClock.sleep(1000);
+        mInstrumentation.waitForIdleSync();
+        // When activity is navigated back from another activity with keyboard on, the keyboard
+        // will not hide except when soft input visibility flag is SOFT_INPUT_STATE_ALWAYS_HIDDEN.
+        verifyAutoShowBehavior_backwardWithKeyboardOn(activity);
     }
 
     @Test
-    public void autoShow() {
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        int flags = mSoftInputVisibility | mSoftInputAdjustment;
-        if (mSoftInputIsForwardNavigation) {
-            flags |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+    public void clickFocusableView_requestFocus() {
+        if ((mWindowFocusFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            // UiAutomator cannot get UiObject if FLAG_NOT_FOCUSABLE is set
+            return;
+        }
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request view focus after app starts
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+
+        // Find the editText and click it
+        UiObject2 editTextUiObject =
+                UiDevice.getInstance(mInstrumentation)
+                        .wait(Until.findObject(By.clazz(EditText.class)), 5000);
+        assertThat(editTextUiObject).isNotNull();
+        editTextUiObject.click();
+
+        // Ime will show unless window flag is set
+        verifyClickBehavior(activity);
+    }
+
+    @Test
+    public void clickFocusableView_notRequestFocus() {
+        if ((mWindowFocusFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            // UiAutomator cannot get UiObject if FLAG_NOT_FOCUSABLE is set
+            return;
+        }
+        // Not request focus
+        Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent1);
+
+        // Find the editText and click it
+        UiObject2 editTextUiObject =
+                UiDevice.getInstance(mInstrumentation)
+                        .wait(Until.findObject(By.clazz(EditText.class)), 5000);
+        assertThat(editTextUiObject).isNotNull();
+        editTextUiObject.click();
+
+        // Ime will show unless window flag is set
+        verifyClickBehavior(activity);
+    }
+
+    public static void verifyAutoShowBehavior_forwardWithKeyboardOff(TestActivity activity) {
+        // public: also used by ImeOpenCloseStressTest
+        if (hasUnfocusableWindowFlags(activity)) {
+            verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+            return;
         }
 
-        Intent intent =
-                new Intent()
-                        .setAction(Intent.ACTION_MAIN)
-                        .setClass(instrumentation.getContext(), TestActivity.class)
-                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                        .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
-                        .putExtra(SOFT_INPUT_FLAGS, flags);
-        TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent);
+        int softInputMode = activity.getWindow().getAttributes().softInputMode;
+        int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
+        int softInputAdjustment = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
         EditText editText = activity.getEditText();
-        waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
 
-        if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
-                || mSoftInputVisibility
-                        == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) {
-            // IME will be auto-shown if softInputMode is set with flag:
-            // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE
-            waitOnMainUntilImeIsShown(editText);
-        } else if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
-                || mSoftInputVisibility
-                        == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) {
-            // IME will be not be shown if softInputMode is set with flag:
-            // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN
-            verifyImeIsAlwaysHidden(editText);
-        } else {
-            // The current system behavior will choose to show IME automatically when navigating
-            // forward to an app that has no visibility state specified  (i.e.
-            // SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE flag.
-            if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
-                    && mSoftInputAdjustment == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
-                    && mSoftInputIsForwardNavigation) {
+        verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+        switch (softInputVisibility) {
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: {
+                // IME will be auto-shown if softInputMode is set with flag:
+                // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE
                 waitOnMainUntilImeIsShown(editText);
+                break;
             }
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: {
+                // IME will be not be auto-shown if softInputMode is set with flag:
+                // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN,
+                // or stay unchanged if set SOFT_INPUT_STATE_UNCHANGED
+                verifyImeIsAlwaysHidden(editText);
+                break;
+            }
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: {
+                if (softInputAdjustment
+                        == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
+                    // The current system behavior will choose to show IME automatically when
+                    // navigating forward to an app that has no visibility state specified
+                    // (i.e. SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE
+                    // flag.
+                    waitOnMainUntilImeIsShown(editText);
+                } else {
+                    verifyImeIsAlwaysHidden(editText);
+                }
+                break;
+            }
+            default:
+                break;
         }
     }
 
-    public static class TestActivity extends Activity {
-        private EditText mEditText;
+    private static void verifyAutoShowBehavior_forwardWithKeyboardOn(TestActivity activity) {
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        int softInputMode = activity.getWindow().getAttributes().softInputMode;
+        int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
+        int softInputAdjustment = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+        EditText editText = activity.getEditText();
 
-        @Override
-        protected void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            int flags = getIntent().getIntExtra(SOFT_INPUT_FLAGS, 0);
-            getWindow().setSoftInputMode(flags);
-            LinearLayout rootView = new LinearLayout(this);
-            rootView.setOrientation(LinearLayout.VERTICAL);
-            mEditText = new EditText(this);
-            rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
-            setContentView(rootView);
-            // Ensure the focused view is a text editor (View#onCheckIsTextEditor() returns true) to
-            // automatically display a soft input window.
-            mEditText.requestFocus();
+        if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus. The IME
+            // will always be hidden even though the view can get focus itself.
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ true);
+            // TODO(b/252192121): Ime should be hidden but is shown.
+            // waitOnMainUntilImeIsHidden(editText);
+            return;
+        } else if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+                || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) {
+            // When FLAG_ALT_FOCUSABLE_IM or FLAG_LOCAL_FOCUS_MODE is set, the view can gain both
+            // window focus and view focus but not IME focus. The IME will always be hidden.
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+            // TODO(b/252192121): Ime should be hidden but is shown.
+            // waitOnMainUntilImeIsHidden(editText);
+            return;
         }
+        verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+        switch (softInputVisibility) {
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: {
+                // IME will be auto-shown if softInputMode is set with flag:
+                // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE
+                waitOnMainUntilImeIsShown(editText);
+                break;
+            }
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: {
+                // IME will be not be auto-shown if softInputMode is set with flag:
+                // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN
+                // or stay unchanged if set SOFT_INPUT_STATE_UNCHANGED
+                verifyImeIsAlwaysHidden(editText);
+                break;
+            }
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: {
+                if (softInputAdjustment
+                        == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
+                    // The current system behavior will choose to show IME automatically when
+                    // navigating
+                    // forward to an app that has no visibility state specified  (i.e.
+                    // SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE flag.
+                    waitOnMainUntilImeIsShown(editText);
+                } else {
+                    verifyImeIsAlwaysHidden(editText);
+                }
+                break;
+            }
+            default:
+                break;
+        }
+    }
 
-        public EditText getEditText() {
-            return mEditText;
+    private static void verifyAutoShowBehavior_backwardWithKeyboardOff(TestActivity activity) {
+        if (hasUnfocusableWindowFlags(activity)) {
+            verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+            return;
+        }
+        int softInputMode = activity.getWindow().getAttributes().softInputMode;
+        int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
+        EditText editText = activity.getEditText();
+
+        verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+        if (softInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) {
+            waitOnMainUntilImeIsShown(editText);
+        } else {
+            verifyImeIsAlwaysHidden(editText);
+        }
+    }
+
+    private static void verifyAutoShowBehavior_backwardWithKeyboardOn(TestActivity activity) {
+        if (hasUnfocusableWindowFlags(activity)) {
+            verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+            return;
+        }
+        int softInputMode = activity.getWindow().getAttributes().softInputMode;
+        int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
+        EditText editText = activity.getEditText();
+
+        verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+        if (softInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) {
+            verifyImeIsAlwaysHidden(editText);
+        } else {
+            waitOnMainUntilImeIsShown(editText);
+        }
+    }
+
+    private static void verifyClickBehavior(TestActivity activity) {
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        EditText editText = activity.getEditText();
+
+        verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+        if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+                || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) {
+            verifyImeIsAlwaysHidden(editText);
+        } else {
+            waitOnMainUntilImeIsShown(editText);
         }
     }
 }
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
index 8419276..82acfb6 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
@@ -16,224 +16,535 @@
 
 package com.android.inputmethod.stresstest;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
-
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.INPUT_METHOD_MANAGER_HIDE_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.INPUT_METHOD_MANAGER_SHOW_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.isImeShown;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
 
-import android.app.Activity;
+import static com.google.common.truth.Truth.assertThat;
+
 import android.app.Instrumentation;
 import android.content.Intent;
-import android.os.Bundle;
+import android.os.Build;
 import android.os.SystemClock;
 import android.platform.test.annotations.RootPermissionTest;
 import android.platform.test.rule.UnlockScreenRule;
+import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
-import android.view.WindowInsets;
-import android.view.WindowInsetsAnimation;
-import android.view.inputmethod.InputMethodManager;
+import android.view.WindowManager;
 import android.widget.EditText;
-import android.widget.LinearLayout;
 
-import androidx.annotation.Nullable;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 @RootPermissionTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public final class ImeOpenCloseStressTest {
 
     private static final String TAG = "ImeOpenCloseStressTest";
     private static final int NUM_TEST_ITERATIONS = 10;
 
-    @Rule
-    public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
+    @Rule public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
 
     @Rule
     public ScreenCaptureRule mScreenCaptureRule =
             new ScreenCaptureRule("/sdcard/InputMethodStressTest");
-    private Instrumentation mInstrumentation;
 
-    @Before
-    public void setUp() {
+    private final Instrumentation mInstrumentation;
+    private final int mSoftInputFlags;
+    private final int mWindowFocusFlags;
+
+    @Parameterized.Parameters(
+            name = "windowFocusFlags={0}, softInputVisibility={1}, softInputAdjustment={2}")
+    public static List<Object[]> windowAndSoftInputFlagParameters() {
+        return getWindowAndSoftInputFlagParameters();
+    }
+
+    public ImeOpenCloseStressTest(
+            int windowFocusFlags, int softInputVisibility, int softInputAdjustment) {
+        mSoftInputFlags = softInputVisibility | softInputAdjustment;
+        mWindowFocusFlags = windowFocusFlags;
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
     }
 
     @Test
-    public void testShowHide_waitingVisibilityChange() {
-        TestActivity activity = TestActivity.start();
-        EditText editText = activity.getEditText();
-        waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
-        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+    public void testShowHideWithInputMethodManager_waitingVisibilityChange() {
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+        // Test only once if window flags set to save time.
+        int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS;
+        for (int i = 0; i < iterNum; i++) {
             String msgPrefix = "Iteration #" + i + " ";
             Log.i(TAG, msgPrefix + "start");
-            mInstrumentation.runOnMainSync(activity::showIme);
-            waitOnMainUntil(msgPrefix + "IME should be visible", () -> isImeShown(editText));
-            mInstrumentation.runOnMainSync(activity::hideIme);
-            waitOnMainUntil(msgPrefix + "IME should be hidden", () -> !isImeShown(editText));
+            boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+            assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+            verifyShowBehavior(activity);
+
+            boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
+            assertThat(hideResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+
+            verifyHideBehavior(activity);
         }
     }
 
     @Test
-    public void testShowHide_waitingAnimationEnd() {
-        TestActivity activity = TestActivity.start();
+    public void testShowHideWithInputMethodManager_waitingAnimationEnd() {
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+
+        if (hasUnfocusableWindowFlags(activity)) {
+            return; // Skip to save time.
+        }
         activity.enableAnimationMonitoring();
         EditText editText = activity.getEditText();
-        waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
             String msgPrefix = "Iteration #" + i + " ";
             Log.i(TAG, msgPrefix + "start");
-            mInstrumentation.runOnMainSync(activity::showIme);
-            waitOnMainUntil(msgPrefix + "IME should be visible",
+            boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+            assertThat(showResult).isTrue();
+            waitOnMainUntil(
+                    msgPrefix + "IME should be visible",
                     () -> !activity.isAnimating() && isImeShown(editText));
-            mInstrumentation.runOnMainSync(activity::hideIme);
-            waitOnMainUntil(msgPrefix + "IME should be hidden",
+
+            boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
+            assertThat(hideResult).isTrue();
+            waitOnMainUntil(
+                    msgPrefix + "IME should be hidden",
                     () -> !activity.isAnimating() && !isImeShown(editText));
         }
     }
 
     @Test
-    public void testShowHide_intervalAfterHide() {
+    public void testShowHideWithInputMethodManager_intervalAfterHide() {
         // Regression test for b/221483132
-        TestActivity activity = TestActivity.start();
-        EditText editText = activity.getEditText();
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+        if (hasUnfocusableWindowFlags(activity)) {
+            return; // Skip to save time.
+        }
         // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
         List<Integer> intervals = new ArrayList<>();
         for (int i = 10; i < 100; i += 10) intervals.add(i);
         for (int i = 100; i < 1000; i += 50) intervals.add(i);
-        waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
+        boolean firstHide = false;
         for (int intervalMillis : intervals) {
             String msgPrefix = "Interval = " + intervalMillis + " ";
             Log.i(TAG, msgPrefix + " start");
-            mInstrumentation.runOnMainSync(activity::hideIme);
+            boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
+            assertThat(hideResult).isEqualTo(firstHide);
+            firstHide = true;
             SystemClock.sleep(intervalMillis);
-            mInstrumentation.runOnMainSync(activity::showIme);
-            waitOnMainUntil(msgPrefix + "IME should be visible",
-                    () -> isImeShown(editText));
+
+            boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+            assertThat(showResult).isTrue();
+            verifyShowBehavior(activity);
         }
     }
 
     @Test
-    public void testShowHideInSameFrame() {
-        TestActivity activity = TestActivity.start();
-        activity.enableAnimationMonitoring();
-        EditText editText = activity.getEditText();
-        waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
+    public void testShowHideWithInputMethodManager_inSameFrame() {
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
 
+        if (hasUnfocusableWindowFlags(activity)) {
+            return; // Skip to save time.
+        }
         // hidden -> show -> hide
-        mInstrumentation.runOnMainSync(() -> {
-            Log.i(TAG, "Calling showIme() and hideIme()");
-            activity.showIme();
-            activity.hideIme();
-        });
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    Log.i(TAG, "Calling showIme() and hideIme()");
+                    activity.showImeWithInputMethodManager();
+                    activity.hideImeWithInputMethodManager();
+                });
         // Wait until IMMS / IMS handles messages.
         SystemClock.sleep(1000);
         mInstrumentation.waitForIdleSync();
-        waitOnMainUntil("IME should be invisible after show/hide", () -> !isImeShown(editText));
+        verifyHideBehavior(activity);
 
-        mInstrumentation.runOnMainSync(activity::showIme);
-        waitOnMainUntil("IME should be visible",
-                () -> !activity.isAnimating() && isImeShown(editText));
+        mInstrumentation.runOnMainSync(activity::showImeWithInputMethodManager);
+        verifyShowBehavior(activity);
         mInstrumentation.waitForIdleSync();
 
         // shown -> hide -> show
-        mInstrumentation.runOnMainSync(() -> {
-            Log.i(TAG, "Calling hideIme() and showIme()");
-            activity.hideIme();
-            activity.showIme();
-        });
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    Log.i(TAG, "Calling hideIme() and showIme()");
+                    activity.hideImeWithInputMethodManager();
+                    activity.showImeWithInputMethodManager();
+                });
         // Wait until IMMS / IMS handles messages.
         SystemClock.sleep(1000);
         mInstrumentation.waitForIdleSync();
-        waitOnMainUntil("IME should be visible after hide/show",
-                () -> !activity.isAnimating() && isImeShown(editText));
+        verifyShowBehavior(activity);
     }
 
-    public static class TestActivity extends Activity {
+    @Test
+    public void testShowHideWithInputMethodManager_onCreate() {
+        // Show and hide with InputMethodManager at onCreate()
+        Intent intent =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Arrays.asList(
+                                REQUEST_FOCUS_ON_CREATE,
+                                INPUT_METHOD_MANAGER_SHOW_ON_CREATE,
+                                INPUT_METHOD_MANAGER_HIDE_ON_CREATE));
+        TestActivity activity = TestActivity.start(intent);
 
-        private EditText mEditText;
-        private boolean mIsAnimating;
+        // TODO: The Ime is expected to show first and then hide. But show or hide
+        // with InputMethodManager at onCreate() would always fail because the window
+        // has not gained focus, so the actual behavior will be the same as auto-show.
+        // verifyHideBehavior(activity);
+    }
 
-        private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
-                new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
-                    @Override
-                    public WindowInsetsAnimation.Bounds onStart(WindowInsetsAnimation animation,
-                            WindowInsetsAnimation.Bounds bounds) {
-                        mIsAnimating = true;
-                        return super.onStart(animation, bounds);
-                    }
+    @Test
+    public void testShowWithInputMethodManager_notRequestFocus() {
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
 
-                    @Override
-                    public void onEnd(WindowInsetsAnimation animation) {
-                        super.onEnd(animation);
-                        mIsAnimating = false;
-                    }
+        // Show InputMethodManager without requesting focus
+        boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+        assertThat(showResult).isFalse();
 
-                    @Override
-                    public WindowInsets onProgress(WindowInsets insets,
-                            List<WindowInsetsAnimation> runningAnimations) {
-                        return insets;
-                    }
-                };
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        EditText editText = activity.getEditText();
+        if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false);
+        } else {
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+        }
+        // The Ime should always be hidden because view never gains focus.
+        verifyImeIsAlwaysHidden(editText);
+    }
 
-        public static TestActivity start() {
-            Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-            Intent intent = new Intent()
-                    .setAction(Intent.ACTION_MAIN)
-                    .setClass(instrumentation.getContext(), TestActivity.class)
-                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            return (TestActivity) instrumentation.startActivitySync(intent);
+    @Test
+    public void testShowHideWithWindowInsetsController_waitingVisibilityChange() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+        // Test only once if window flags set to save time.
+        int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS;
+        for (int i = 0; i < iterNum; i++) {
+            String msgPrefix = "Iteration #" + i + " ";
+            Log.i(TAG, msgPrefix + "start");
+            mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+            verifyShowBehavior(activity);
+            mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController);
+            verifyHideBehavior(activity);
+        }
+    }
+
+    @Test
+    public void testShowHideWithWindowInsetsController_waitingAnimationEnd() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+
+        if (hasUnfocusableWindowFlags(activity)) {
+            return; // Skip to save time.
+        }
+        activity.enableAnimationMonitoring();
+        EditText editText = activity.getEditText();
+        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+            String msgPrefix = "Iteration #" + i + " ";
+            Log.i(TAG, msgPrefix + "start");
+            mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+            waitOnMainUntil(
+                    msgPrefix + "IME should be visible",
+                    () -> !activity.isAnimating() && isImeShown(editText));
+
+            mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController);
+            waitOnMainUntil(
+                    msgPrefix + "IME should be hidden",
+                    () -> !activity.isAnimating() && !isImeShown(editText));
+        }
+    }
+
+    @Test
+    public void testShowHideWithWindowInsetsController_intervalAfterHide() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+
+        if (hasUnfocusableWindowFlags(activity)) {
+            return; // Skip to save time.
+        }
+        // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
+        List<Integer> intervals = new ArrayList<>();
+        for (int i = 10; i < 100; i += 10) intervals.add(i);
+        for (int i = 100; i < 1000; i += 50) intervals.add(i);
+        for (int intervalMillis : intervals) {
+            String msgPrefix = "Interval = " + intervalMillis + " ";
+            Log.i(TAG, msgPrefix + " start");
+            mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController);
+            SystemClock.sleep(intervalMillis);
+
+            mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+            verifyShowBehavior(activity);
+        }
+    }
+
+    @Test
+    public void testShowHideWithWindowInsetsController_inSameFrame() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+
+        if (hasUnfocusableWindowFlags(activity)) {
+            return; // Skip to save time.
+        }
+        // hidden -> show -> hide
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    Log.i(TAG, "Calling showIme() and hideIme()");
+                    activity.showImeWithWindowInsetsController();
+                    activity.hideImeWithWindowInsetsController();
+                });
+        // Wait until IMMS / IMS handles messages.
+        SystemClock.sleep(1000);
+        mInstrumentation.waitForIdleSync();
+        // TODO(b/248456059): Ime should be hidden but is shown.
+        // verifyHideBehavior(activity);
+
+        mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+        verifyShowBehavior(activity);
+        mInstrumentation.waitForIdleSync();
+
+        // shown -> hide -> show
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    Log.i(TAG, "Calling hideIme() and showIme()");
+                    activity.hideImeWithWindowInsetsController();
+                    activity.showImeWithWindowInsetsController();
+                });
+        // Wait until IMMS / IMS handles messages.
+        SystemClock.sleep(1000);
+        mInstrumentation.waitForIdleSync();
+        verifyShowBehavior(activity);
+    }
+
+    @Test
+    public void testShowWithWindowInsetsController_onCreate_requestFocus() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        // Show with InputMethodManager at onCreate()
+        Intent intent =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Arrays.asList(
+                                REQUEST_FOCUS_ON_CREATE, WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE));
+        TestActivity activity = TestActivity.start(intent);
+
+        verifyShowBehavior(activity);
+    }
+
+    @Test
+    public void testShowWithWindowInsetsController_onCreate_notRequestFocus() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        // Show and hide with InputMethodManager at onCreate()
+        Intent intent =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Collections.singletonList(WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE));
+        TestActivity activity = TestActivity.start(intent);
+
+        // Ime is shown but with a fallback InputConnection
+        verifyShowBehaviorNotRequestFocus(activity);
+    }
+
+    @Test
+    public void testShowWithWindowInsetsController_afterStart_notRequestFocus() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        // Show and hide with InputMethodManager at onCreate()
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+
+        // Ime is shown but with a fallback InputConnection
+        verifyShowBehaviorNotRequestFocus(activity);
+    }
+
+    @Test
+    public void testHideWithWindowInsetsController_onCreate_requestFocus() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        // Show and hide with InputMethodManager at onCreate()
+        Intent intent =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Arrays.asList(
+                                REQUEST_FOCUS_ON_CREATE,
+                                WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE,
+                                WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE));
+        TestActivity activity = TestActivity.start(intent);
+
+        // TODO(b/248456059): Ime should be hidden but is shown.
+        //verifyHideBehavior(activity);
+    }
+
+    @Test
+    public void testScreenOffOn() throws Exception {
+        Intent intent1 =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+        TestActivity activity = TestActivity.start(intent1);
+        // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
+        boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+        assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+
+        Thread.sleep(1000);
+        verifyShowBehavior(activity);
+
+        UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
+
+        if (uiDevice.isScreenOn()) {
+            uiDevice.sleep();
+        }
+        Thread.sleep(1000);
+        if (!uiDevice.isScreenOn()) {
+            uiDevice.wakeUp();
         }
 
-        @Override
-        protected void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            LinearLayout rootView = new LinearLayout(this);
-            rootView.setOrientation(LinearLayout.VERTICAL);
-            mEditText = new EditText(this);
-            rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
-            setContentView(rootView);
-        }
+        verifyShowBehavior(activity);
+    }
 
-        public EditText getEditText() {
-            return mEditText;
-        }
+    @Test
+    public void testRotateScreenWithKeyboardOn() throws Exception {
+        // TODO(b/256739702): Keyboard disappears after rotating screen to landscape mode if
+        // android:configChanges="orientation|screenSize" is not set
+        Intent intent =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+        TestActivity activity = TestActivity.start(intent);
+        // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
+        boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+        assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+        Thread.sleep(2000);
+        verifyShowBehavior(activity);
 
-        public void showIme() {
-            Log.i(TAG, "TestActivity.showIme");
-            mEditText.requestFocus();
-            InputMethodManager imm = getSystemService(InputMethodManager.class);
-            imm.showSoftInput(mEditText, 0);
-        }
+        UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
 
-        public void hideIme() {
-            Log.i(TAG, "TestActivity.hideIme");
-            InputMethodManager imm = getSystemService(InputMethodManager.class);
-            imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
-        }
+        uiDevice.freezeRotation();
+        uiDevice.setOrientationRight();
+        uiDevice.waitForIdle();
+        Thread.sleep(1000);
+        Log.i(TAG, "Rotate screen right");
+        assertThat(uiDevice.isNaturalOrientation()).isFalse();
+        verifyShowBehavior(activity);
 
-        public void enableAnimationMonitoring() {
-            // Enable WindowInsetsAnimation.
-            // Note that this has a side effect of disabling InsetsAnimationThreadControlRunner.
-            InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-                getWindow().setDecorFitsSystemWindows(false);
-                mEditText.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
-            });
-        }
+        uiDevice.setOrientationLeft();
+        uiDevice.waitForIdle();
+        Thread.sleep(1000);
+        Log.i(TAG, "Rotate screen left");
+        assertThat(uiDevice.isNaturalOrientation()).isFalse();
+        verifyShowBehavior(activity);
 
-        public boolean isAnimating() {
-            return mIsAnimating;
+        uiDevice.setOrientationNatural();
+        uiDevice.waitForIdle();
+        uiDevice.unfreezeRotation();
+    }
+
+    private static void verifyShowBehavior(TestActivity activity) {
+        if (hasUnfocusableWindowFlags(activity)) {
+            verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+            return;
+        }
+        EditText editText = activity.getEditText();
+
+        verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+        waitOnMainUntilImeIsShown(editText);
+    }
+
+    private static void verifyHideBehavior(TestActivity activity) {
+        if (hasUnfocusableWindowFlags(activity)) {
+            verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+            return;
+        }
+        EditText editText = activity.getEditText();
+
+        verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+        waitOnMainUntilImeIsHidden(editText);
+    }
+
+    private static void verifyShowBehaviorNotRequestFocus(TestActivity activity) {
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        EditText editText = activity.getEditText();
+
+        if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false);
+            verifyImeIsAlwaysHidden(editText);
+        } else if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+                || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) {
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+            verifyImeIsAlwaysHidden(editText);
+        } else {
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+            // Ime is shown but with a fallback InputConnection
+            waitOnMainUntilImeIsShown(editText);
         }
     }
 }
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
index b6d462c..e16c915 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -16,17 +16,37 @@
 
 package com.android.inputmethod.stresstest;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
+
 import static com.android.compatibility.common.util.SystemUtil.eventually;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
 import android.view.View;
 import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
 
+import androidx.annotation.Nullable;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.ThrowingRunnable;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -34,28 +54,96 @@
 /** Utility methods for IME stress test. */
 public final class ImeStressTestUtil {
 
-    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
-    private static final long VERIFY_DURATION = TimeUnit.SECONDS.toMillis(2);
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(3);
 
-    private ImeStressTestUtil() {
+    private ImeStressTestUtil() {}
+
+    private static final int[] WINDOW_FOCUS_FLAGS =
+            new int[] {
+                LayoutParams.FLAG_NOT_FOCUSABLE,
+                LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                LayoutParams.FLAG_LOCAL_FOCUS_MODE
+            };
+
+    private static final int[] SOFT_INPUT_VISIBILITY_FLAGS =
+            new int[] {
+                LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED,
+                LayoutParams.SOFT_INPUT_STATE_UNCHANGED,
+                LayoutParams.SOFT_INPUT_STATE_HIDDEN,
+                LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN,
+                LayoutParams.SOFT_INPUT_STATE_VISIBLE,
+                LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE,
+            };
+
+    private static final int[] SOFT_INPUT_ADJUST_FLAGS =
+            new int[] {
+                LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED,
+                LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+                LayoutParams.SOFT_INPUT_ADJUST_PAN,
+                LayoutParams.SOFT_INPUT_ADJUST_NOTHING
+            };
+
+    public static final String SOFT_INPUT_FLAGS = "soft_input_flags";
+    public static final String WINDOW_FLAGS = "window_flags";
+    public static final String UNFOCUSABLE_VIEW = "unfocusable_view";
+    public static final String REQUEST_FOCUS_ON_CREATE = "request_focus_on_create";
+    public static final String INPUT_METHOD_MANAGER_SHOW_ON_CREATE =
+            "input_method_manager_show_on_create";
+    public static final String INPUT_METHOD_MANAGER_HIDE_ON_CREATE =
+            "input_method_manager_hide_on_create";
+    public static final String WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE =
+            "window_insets_controller_show_on_create";
+    public static final String WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE =
+            "window_insets_controller_hide_on_create";
+
+    /** Parameters for show/hide ime parameterized tests. */
+    public static ArrayList<Object[]> getWindowAndSoftInputFlagParameters() {
+        ArrayList<Object[]> params = new ArrayList<>();
+
+        // Set different window focus flags and keep soft input flags as default values (4 cases)
+        for (int windowFocusFlags : WINDOW_FOCUS_FLAGS) {
+            params.add(
+                    new Object[] {
+                        windowFocusFlags,
+                        LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED,
+                        LayoutParams.SOFT_INPUT_ADJUST_RESIZE
+                    });
+        }
+        // Set the combinations of different softInputVisibility, softInputAdjustment flags,
+        // keep the window focus flag as default value ( 6 * 4 = 24 cases)
+        for (int softInputVisibility : SOFT_INPUT_VISIBILITY_FLAGS) {
+            for (int softInputAdjust : SOFT_INPUT_ADJUST_FLAGS) {
+                params.add(
+                        new Object[] {
+                            0x0 /* No window focus flags */, softInputVisibility, softInputAdjust
+                        });
+            }
+        }
+        return params;
     }
 
     /** Checks if the IME is shown on the window that the given view belongs to. */
     public static boolean isImeShown(View view) {
         WindowInsets insets = view.getRootWindowInsets();
+        if (insets == null) {
+            return false;
+        }
         return insets.isVisible(WindowInsets.Type.ime());
     }
 
     /** Calls the callable on the main thread and returns the result. */
     public static <V> V callOnMainSync(Callable<V> callable) {
         AtomicReference<V> result = new AtomicReference<>();
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            try {
-                result.set(callable.call());
-            } catch (Exception e) {
-                throw new RuntimeException("Exception was thrown", e);
-            }
-        });
+        InstrumentationRegistry.getInstrumentation()
+                .runOnMainSync(
+                        () -> {
+                            try {
+                                result.set(callable.call());
+                            } catch (Exception e) {
+                                throw new RuntimeException("Exception was thrown", e);
+                            }
+                        });
         return result.get();
     }
 
@@ -70,15 +158,42 @@
 
     /** Waits until IME is shown, or throws on timeout. */
     public static void waitOnMainUntilImeIsShown(View view) {
-        eventually(() -> assertWithMessage("IME should be shown").that(
-                callOnMainSync(() -> isImeShown(view))).isTrue(), TIMEOUT);
+        eventually(
+                () ->
+                        assertWithMessage("IME should be shown")
+                                .that(callOnMainSync(() -> isImeShown(view)))
+                                .isTrue(),
+                TIMEOUT);
     }
 
     /** Waits until IME is hidden, or throws on timeout. */
     public static void waitOnMainUntilImeIsHidden(View view) {
-        //eventually(() -> assertThat(callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT);
-        eventually(() -> assertWithMessage("IME should be hidden").that(
-                callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT);
+        eventually(
+                () ->
+                        assertWithMessage("IME should be hidden")
+                                .that(callOnMainSync(() -> isImeShown(view)))
+                                .isFalse(),
+                TIMEOUT);
+    }
+
+    /** Waits until window get focus, or throws on timeout. */
+    public static void waitOnMainUntilWindowGainsFocus(View view) {
+        eventually(
+                () ->
+                        assertWithMessage("Window should gain focus")
+                                .that(callOnMainSync(view::hasWindowFocus))
+                                .isTrue(),
+                TIMEOUT);
+    }
+
+    /** Waits until view get focus, or throws on timeout. */
+    public static void waitOnMainUntilViewGainsFocus(View view) {
+        eventually(
+                () ->
+                        assertWithMessage("View should gain focus")
+                                .that(callOnMainSync(view::hasFocus))
+                                .isTrue(),
+                TIMEOUT);
     }
 
     /** Verify IME is always hidden within the given time duration. */
@@ -88,7 +203,27 @@
                         assertWithMessage("IME should be hidden")
                                 .that(callOnMainSync(() -> isImeShown(view)))
                                 .isFalse(),
-                VERIFY_DURATION);
+                TIMEOUT);
+    }
+
+    /** Verify the window never gains focus within the given time duration. */
+    public static void verifyWindowNeverGainsFocus(View view) {
+        always(
+                () ->
+                        assertWithMessage("window should never gain focus")
+                                .that(callOnMainSync(view::hasWindowFocus))
+                                .isFalse(),
+                TIMEOUT);
+    }
+
+    /** Verify the view never gains focus within the given time duration. */
+    public static void verifyViewNeverGainsFocus(View view) {
+        always(
+                () ->
+                        assertWithMessage("view should never gain ime focus")
+                                .that(callOnMainSync(view::hasFocus))
+                                .isFalse(),
+                TIMEOUT);
     }
 
     /**
@@ -117,4 +252,232 @@
             }
         }
     }
+
+    public static boolean hasUnfocusableWindowFlags(Activity activity) {
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        return (windowFlags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0
+                || (windowFlags & LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+                || (windowFlags & LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
+    }
+
+    public static void verifyWindowAndViewFocus(
+            View view, boolean expectWindowFocus, boolean expectViewFocus) {
+        if (expectWindowFocus) {
+            waitOnMainUntilWindowGainsFocus(view);
+        } else {
+            verifyWindowNeverGainsFocus(view);
+        }
+        if (expectViewFocus) {
+            waitOnMainUntilViewGainsFocus(view);
+        } else {
+            verifyViewNeverGainsFocus(view);
+        }
+    }
+
+    public static void verifyImeAlwaysHiddenWithWindowFlagSet(TestActivity activity) {
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        View view = activity.getEditText();
+        if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus. The IME
+            // will always be hidden even though the view can get focus itself.
+            verifyWindowAndViewFocus(view, /*expectWindowFocus*/ false, /*expectViewFocus*/ true);
+            verifyImeIsAlwaysHidden(view);
+        } else if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+                || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) {
+            // When FLAG_ALT_FOCUSABLE_IM or FLAG_LOCAL_FOCUS_MODE is set, the view can gain both
+            // window focus and view focus but not IME focus. The IME will always be hidden.
+            verifyWindowAndViewFocus(view, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+            verifyImeIsAlwaysHidden(view);
+        }
+    }
+
+    /** Activity to help test show/hide behavior of IME. */
+    public static class TestActivity extends Activity {
+        private static final String TAG = "ImeStressTestUtil.TestActivity";
+        private EditText mEditText;
+        private boolean mIsAnimating;
+
+        private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
+                new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+                    @Override
+                    public WindowInsetsAnimation.Bounds onStart(
+                            WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds) {
+                        mIsAnimating = true;
+                        return super.onStart(animation, bounds);
+                    }
+
+                    @Override
+                    public void onEnd(WindowInsetsAnimation animation) {
+                        super.onEnd(animation);
+                        mIsAnimating = false;
+                    }
+
+                    @Override
+                    public WindowInsets onProgress(
+                            WindowInsets insets, List<WindowInsetsAnimation> runningAnimations) {
+                        return insets;
+                    }
+                };
+
+        /** Create intent with extras. */
+        public static Intent createIntent(
+                int windowFlags, int softInputFlags, List<String> extras) {
+            Intent intent =
+                    new Intent()
+                            .putExtra(WINDOW_FLAGS, windowFlags)
+                            .putExtra(SOFT_INPUT_FLAGS, softInputFlags);
+            for (String extra : extras) {
+                intent.putExtra(extra, true);
+            }
+            return intent;
+        }
+
+        @Override
+        protected void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            Log.i(TAG, "onCreate()");
+            boolean isUnfocusableView = getIntent().getBooleanExtra(UNFOCUSABLE_VIEW, false);
+            boolean requestFocus = getIntent().getBooleanExtra(REQUEST_FOCUS_ON_CREATE, false);
+            int softInputFlags = getIntent().getIntExtra(SOFT_INPUT_FLAGS, 0);
+            int windowFlags = getIntent().getIntExtra(WINDOW_FLAGS, 0);
+            boolean showWithInputMethodManagerOnCreate =
+                    getIntent().getBooleanExtra(INPUT_METHOD_MANAGER_SHOW_ON_CREATE, false);
+            boolean hideWithInputMethodManagerOnCreate =
+                    getIntent().getBooleanExtra(INPUT_METHOD_MANAGER_HIDE_ON_CREATE, false);
+            boolean showWithWindowInsetsControllerOnCreate =
+                    getIntent().getBooleanExtra(WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE, false);
+            boolean hideWithWindowInsetsControllerOnCreate =
+                    getIntent().getBooleanExtra(WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE, false);
+
+            getWindow().addFlags(windowFlags);
+            getWindow().setSoftInputMode(softInputFlags);
+
+            LinearLayout rootView = new LinearLayout(this);
+            rootView.setOrientation(LinearLayout.VERTICAL);
+            mEditText = new EditText(this);
+            if (isUnfocusableView) {
+                mEditText.setFocusableInTouchMode(false);
+            }
+            rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+            setContentView(rootView);
+
+            if (requestFocus) {
+                requestFocus();
+            }
+            if (showWithInputMethodManagerOnCreate) {
+                showImeWithInputMethodManager();
+            }
+            if (hideWithInputMethodManagerOnCreate) {
+                hideImeWithInputMethodManager();
+            }
+            if (showWithWindowInsetsControllerOnCreate) {
+                showImeWithWindowInsetsController();
+            }
+            if (hideWithWindowInsetsControllerOnCreate) {
+                hideImeWithWindowInsetsController();
+            }
+        }
+
+        /** Show IME with InputMethodManager. */
+        public boolean showImeWithInputMethodManager() {
+            boolean showResult =
+                    getInputMethodManager()
+                            .showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
+            if (showResult) {
+                Log.i(TAG, "IMM#showSoftInput successfully");
+            } else {
+                Log.i(TAG, "IMM#showSoftInput failed");
+            }
+            return showResult;
+        }
+
+        /** Hide IME with InputMethodManager. */
+        public boolean hideImeWithInputMethodManager() {
+            boolean hideResult =
+                    getInputMethodManager().hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
+            if (hideResult) {
+                Log.i(TAG, "IMM#hideSoftInput successfully");
+            } else {
+                Log.i(TAG, "IMM#hideSoftInput failed");
+            }
+            return hideResult;
+        }
+
+        /** Show IME with WindowInsetsController */
+        public void showImeWithWindowInsetsController() {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+                return;
+            }
+            Log.i(TAG, "showImeWithWIC()");
+            WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
+            assertWithMessage("WindowInsetsController shouldn't be null.")
+                    .that(windowInsetsController)
+                    .isNotNull();
+            windowInsetsController.show(WindowInsets.Type.ime());
+        }
+
+        /** Hide IME with WindowInsetsController. */
+        public void hideImeWithWindowInsetsController() {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+                return;
+            }
+            Log.i(TAG, "hideImeWithWIC()");
+            WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
+            assertWithMessage("WindowInsetsController shouldn't be null.")
+                    .that(windowInsetsController)
+                    .isNotNull();
+            windowInsetsController.hide(WindowInsets.Type.ime());
+        }
+
+        private InputMethodManager getInputMethodManager() {
+            return getSystemService(InputMethodManager.class);
+        }
+
+        public EditText getEditText() {
+            return mEditText;
+        }
+
+        /** Start TestActivity with intent. */
+        public static TestActivity start(Intent intent) {
+            Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+            intent.setAction(Intent.ACTION_MAIN)
+                    .setClass(instrumentation.getContext(), TestActivity.class)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            return (TestActivity) instrumentation.startActivitySync(intent);
+        }
+
+        /** Start the second TestActivity with intent. */
+        public TestActivity startSecondTestActivity(Intent intent) {
+            Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+            intent.setClass(TestActivity.this, TestActivity.class);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            return (TestActivity) instrumentation.startActivitySync(intent);
+        }
+
+        public void enableAnimationMonitoring() {
+            // Enable WindowInsetsAnimation.
+            // Note that this has a side effect of disabling InsetsAnimationThreadControlRunner.
+            InstrumentationRegistry.getInstrumentation()
+                    .runOnMainSync(
+                            () -> {
+                                getWindow().setDecorFitsSystemWindows(false);
+                                mEditText.setWindowInsetsAnimationCallback(
+                                        mWindowInsetsAnimationCallback);
+                            });
+        }
+
+        public boolean isAnimating() {
+            return mIsAnimating;
+        }
+
+        public void requestFocus() {
+            boolean requestFocusResult = getEditText().requestFocus();
+            if (requestFocusResult) {
+                Log.i(TAG, "Request focus successfully");
+            } else {
+                Log.i(TAG, "Request focus failed");
+            }
+        }
+    }
 }
diff --git a/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
new file mode 100644
index 0000000..0f96634
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.os;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link TimeoutRecord}. */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class TimeoutRecordTest {
+
+    @Test
+    public void forBroadcastReceiver_returnsCorrectTimeoutRecord() {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setComponent(ComponentName.createRelative("com.example.app", "ExampleClass"));
+
+        TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent);
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER);
+        assertEquals(record.mReason,
+                "Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example"
+                        + ".app/ExampleClass }");
+        assertTrue(record.mEndTakenBeforeLocks);
+    }
+
+    @Test
+    public void forBroadcastReceiver_withTimeoutDurationMs_returnsCorrectTimeoutRecord() {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setComponent(ComponentName.createRelative("com.example.app", "ExampleClass"));
+
+        TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent, 1000L);
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER);
+        assertEquals(record.mReason,
+                "Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example"
+                        + ".app/ExampleClass }, waited 1000ms");
+        assertTrue(record.mEndTakenBeforeLocks);
+    }
+
+    @Test
+    public void forInputDispatchNoFocusedWindow_returnsCorrectTimeoutRecord() {
+        TimeoutRecord record = TimeoutRecord.forInputDispatchNoFocusedWindow("Test ANR reason");
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW);
+        assertEquals(record.mReason,
+                "Test ANR reason");
+        assertTrue(record.mEndTakenBeforeLocks);
+    }
+
+    @Test
+    public void forInputDispatchWindowUnresponsive_returnsCorrectTimeoutRecord() {
+        TimeoutRecord record = TimeoutRecord.forInputDispatchWindowUnresponsive("Test ANR reason");
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE);
+        assertEquals(record.mReason, "Test ANR reason");
+        assertTrue(record.mEndTakenBeforeLocks);
+    }
+
+    @Test
+    public void forServiceExec_returnsCorrectTimeoutRecord() {
+        TimeoutRecord record = TimeoutRecord.forServiceExec("Test ANR reason");
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.SERVICE_EXEC);
+        assertEquals(record.mReason, "Test ANR reason");
+        assertTrue(record.mEndTakenBeforeLocks);
+    }
+
+    @Test
+    public void forServiceStartWithEndTime_returnsCorrectTimeoutRecord() {
+        TimeoutRecord record = TimeoutRecord.forServiceStartWithEndTime("Test ANR reason", 1000L);
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.SERVICE_START);
+        assertEquals(record.mReason, "Test ANR reason");
+        assertEquals(record.mEndUptimeMillis, 1000L);
+        assertTrue(record.mEndTakenBeforeLocks);
+    }
+
+    @Test
+    public void forContentProvider_returnsCorrectTimeoutRecord() {
+        TimeoutRecord record = TimeoutRecord.forContentProvider("Test ANR reason");
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.CONTENT_PROVIDER);
+        assertEquals(record.mReason, "Test ANR reason");
+        assertFalse(record.mEndTakenBeforeLocks);
+    }
+
+    @Test
+    public void forApp_returnsCorrectTimeoutRecord() {
+        TimeoutRecord record = TimeoutRecord.forApp("Test ANR reason");
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.APP_REGISTERED);
+        assertEquals(record.mReason, "Test ANR reason");
+        assertFalse(record.mEndTakenBeforeLocks);
+    }
+}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
index 3db0116..2cdb945 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
@@ -201,24 +201,24 @@
 
     @Test
     public void log_logcatEnabledExternalMessage() {
-        when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% %o %x %e %g %s %f");
+        when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% 0x%x %s %f");
         ProtoLogImpl implSpy = Mockito.spy(mProtoLog);
         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
         TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
 
         implSpy.log(
                 ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
-                new Object[]{true, 10000, 20000, 30000, 0.0001, 0.00002, "test", 0.000003});
+                new Object[]{true, 10000, 30000, "test", 0.000003});
 
         verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
                 ProtoLogImpl.LogLevel.INFO),
-                eq("test true 10000 % 47040 7530 1.000000e-04 2.00000e-05 test 0.000003"));
+                eq("test true 10000 % 0x7530 test 3.0E-6"));
         verify(mReader).getViewerString(eq(1234));
     }
 
     @Test
     public void log_logcatEnabledInvalidMessage() {
-        when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% %o %x %e %g %s %f");
+        when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% %x %s %f");
         ProtoLogImpl implSpy = Mockito.spy(mProtoLog);
         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
         TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
diff --git a/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java b/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java
index e20ca3d..9c2f74ea 100644
--- a/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java
@@ -36,15 +36,12 @@
 public class LogDataTypeTest {
     @Test
     public void parseFormatString() {
-        String str = "%b %d %o %x %f %e %g %s %%";
+        String str = "%b %d %x %f %s %%";
         List<Integer> out = LogDataType.parseFormatString(str);
         assertEquals(Arrays.asList(
                 LogDataType.BOOLEAN,
                 LogDataType.LONG,
                 LogDataType.LONG,
-                LogDataType.LONG,
-                LogDataType.DOUBLE,
-                LogDataType.DOUBLE,
                 LogDataType.DOUBLE,
                 LogDataType.STRING
         ), out);
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
index 4de51fb..43dc9de 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
@@ -140,9 +140,9 @@
         handleNextBenchmark();
     }
 
+    @SuppressWarnings("MissingSuperCall") // TODO: Fix me
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-
     }
 
     private void handleNextBenchmark() {
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
index c16efbd..d015a56 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
@@ -367,6 +367,7 @@
         }
     }
 
+    @SuppressWarnings("MissingSuperCall") // TODO: Fix me
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         switch (requestCode) {
diff --git a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
index dd9b294..afaeca1 100644
--- a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
+++ b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
@@ -15,7 +15,6 @@
  */
 package com.android.frameworks.perftests.job;
 
-
 import android.app.job.JobInfo;
 import android.content.ComponentName;
 import android.content.Context;
@@ -46,7 +45,8 @@
 public class JobStorePerfTests {
     private static final String SOURCE_PACKAGE = "com.android.frameworks.perftests.job";
     private static final int SOURCE_USER_ID = 0;
-    private static final int CALLING_UID = 10079;
+    private static final int BASE_CALLING_UID = 10079;
+    private static final int MAX_UID_COUNT = 10;
 
     private static Context sContext;
     private static File sTestDir;
@@ -65,10 +65,10 @@
         sJobStore = JobStore.initAndGetForTesting(sContext, sTestDir);
 
         for (int i = 0; i < 50; i++) {
-            sFewJobs.add(createJobStatus("fewJobs", i));
+            sFewJobs.add(createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % MAX_UID_COUNT)));
         }
         for (int i = 0; i < 500; i++) {
-            sManyJobs.add(createJobStatus("manyJobs", i));
+            sManyJobs.add(createJobStatus("manyJobs", i, BASE_CALLING_UID + (i % MAX_UID_COUNT)));
         }
     }
 
@@ -104,6 +104,64 @@
         runPersistedJobWriting(sManyJobs);
     }
 
+    private void runPersistedJobWriting_delta(List<JobStatus> jobList,
+            List<JobStatus> jobAdditions, List<JobStatus> jobRemovals) {
+        final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
+
+        long elapsedTimeNs = 0;
+        while (benchmarkState.keepRunning(elapsedTimeNs)) {
+            sJobStore.clearForTesting();
+            for (JobStatus job : jobList) {
+                sJobStore.addForTesting(job);
+            }
+            sJobStore.writeStatusToDiskForTesting();
+
+            for (JobStatus job : jobAdditions) {
+                sJobStore.addForTesting(job);
+            }
+            for (JobStatus job : jobRemovals) {
+                sJobStore.removeForTesting(job);
+            }
+
+            final long startTime = SystemClock.elapsedRealtimeNanos();
+            sJobStore.writeStatusToDiskForTesting();
+            final long endTime = SystemClock.elapsedRealtimeNanos();
+            elapsedTimeNs = endTime - startTime;
+        }
+    }
+
+    @Test
+    public void testPersistedJobWriting_delta_fewJobs() {
+        List<JobStatus> additions = new ArrayList<>();
+        List<JobStatus> removals = new ArrayList<>();
+        final int numModifiedUids = MAX_UID_COUNT / 2;
+        for (int i = 0; i < sFewJobs.size() / 3; ++i) {
+            JobStatus job = createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % numModifiedUids));
+            if (i % 2 == 0) {
+                additions.add(job);
+            } else {
+                removals.add(job);
+            }
+        }
+        runPersistedJobWriting_delta(sFewJobs, additions, removals);
+    }
+
+    @Test
+    public void testPersistedJobWriting_delta_manyJobs() {
+        List<JobStatus> additions = new ArrayList<>();
+        List<JobStatus> removals = new ArrayList<>();
+        final int numModifiedUids = MAX_UID_COUNT / 2;
+        for (int i = 0; i < sManyJobs.size() / 3; ++i) {
+            JobStatus job = createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % numModifiedUids));
+            if (i % 2 == 0) {
+                additions.add(job);
+            } else {
+                removals.add(job);
+            }
+        }
+        runPersistedJobWriting_delta(sManyJobs, additions, removals);
+    }
+
     private void runPersistedJobReading(List<JobStatus> jobList, boolean rtcIsGood) {
         final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
 
@@ -144,12 +202,12 @@
         runPersistedJobReading(sManyJobs, false);
     }
 
-    private static JobStatus createJobStatus(String testTag, int jobId) {
+    private static JobStatus createJobStatus(String testTag, int jobId, int callingUid) {
         JobInfo jobInfo = new JobInfo.Builder(jobId,
                 new ComponentName(sContext, "JobStorePerfTestJobService"))
                 .setPersisted(true)
                 .build();
         return JobStatus.createFromJobInfo(
-                jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+                jobInfo, callingUid, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
     }
 }
diff --git a/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java b/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java
index 8afe841..17fa210 100644
--- a/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java
+++ b/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java
@@ -295,8 +295,8 @@
     private void updateMirror(Rect displayFrame, float scale) {
         if (displayFrame.isEmpty()) {
             Rect bounds = mWindowBounds;
-            int defaultCropW = Math.round(bounds.width() / 2);
-            int defaultCropH = Math.round(bounds.height() / 2);
+            int defaultCropW = bounds.width() / 2;
+            int defaultCropH = bounds.height() / 2;
             displayFrame.set(0, 0, defaultCropW, defaultCropH);
         }
 
diff --git a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
index 241206d..65b7549 100644
--- a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
+++ b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
@@ -24,18 +24,14 @@
     static final String KEY_NAME = "name";
     static final String KEY_CLASS = "clazz";
 
-    static Map<String,?> make(String name) {
-        Map<String,Object> ret = new HashMap<String,Object>();
-        ret.put(KEY_NAME, name);
-        return ret;
-    }
-
-    @SuppressWarnings("serial")
-    static final ArrayList<Map<String,?>> SAMPLES = new ArrayList<Map<String,?>>() {{
+    static final ArrayList<Map<String, ?>> SAMPLES = new ArrayList<>();
+    static {
         for (int i = 1; i < 25; i++) {
-            add(make("List Item: " + i));
+            Map<String, Object> sample = new HashMap<String, Object>();
+            sample.put(KEY_NAME, "List Item: " + i);
+            SAMPLES.add(sample);
         }
-    }};
+    }
 
     Handler mHandler = new Handler();
 
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",
 }
diff --git a/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java
index c11b0f3..f85fb0f 100644
--- a/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java
+++ b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java
@@ -30,6 +30,7 @@
         setContentView(tv);
     }
 
+    @SuppressWarnings("ReturnValueIgnored")
     @Override
     public void onResume() {
         ((String) null).length();
diff --git a/tests/TelephonyCommonTests/Android.bp b/tests/TelephonyCommonTests/Android.bp
index a9fbfd9..81ec265 100644
--- a/tests/TelephonyCommonTests/Android.bp
+++ b/tests/TelephonyCommonTests/Android.bp
@@ -47,7 +47,7 @@
 
     // Uncomment this and comment out the jarjar rule if you want to attach a debugger and step
     // through the renamed classes.
-    // platform_apis: true,
+    platform_apis: true,
 
     libs: [
         "android.test.runner",
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
index 7a2af72..adefac6 100644
--- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
@@ -44,6 +44,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -75,9 +76,12 @@
 public class SmsApplicationTest {
     private static final ComponentName TEST_COMPONENT_NAME =
             ComponentName.unflattenFromString("com.android.test/.TestSmsApp");
+    public static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth.services";
     private static final String MMS_RECEIVER_NAME = "TestMmsReceiver";
     private static final String RESPOND_VIA_SMS_NAME = "TestRespondViaSmsHandler";
     private static final String SEND_TO_NAME = "TestSendTo";
+    private static final String EXTERNAL_PROVIDER_CHANGE_NAME = "TestExternalProviderChangeHandler";
+    private static final String SIM_FULL_NAME = "TestSimFullHandler";
     private static final int SMS_APP_UID = 10001;
 
     private static final int FAKE_PHONE_UID = 10002;
@@ -102,6 +106,7 @@
     }).collect(Collectors.toSet());
 
     @Mock private Context mContext;
+    @Mock private Resources mResources;
     @Mock private TelephonyManager mTelephonyManager;
     @Mock private RoleManager mRoleManager;
     @Mock private PackageManager mPackageManager;
@@ -118,6 +123,9 @@
         when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
         when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOpsManager);
         when(mContext.createContextAsUser(isNotNull(), anyInt())).thenReturn(mContext);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getString(eq(com.android.internal.R.string.config_systemBluetoothStack)))
+                .thenReturn(BLUETOOTH_PACKAGE_NAME);
 
         doAnswer(invocation -> getResolveInfosForIntent(invocation.getArgument(0)))
                 .when(mPackageManager)
@@ -146,24 +154,46 @@
         }
     }
 
+
     @Test
-    public void testGetDefaultSmsApplication() {
+    public void testGetDefaultSmsApplicationAsUser() {
         assertEquals(TEST_COMPONENT_NAME,
-                SmsApplication.getDefaultSmsApplicationAsUser(mContext, false, 0));
+                SmsApplication.getDefaultSmsApplicationAsUser(mContext, false,
+                        UserHandle.SYSTEM));
+    }
+
+
+    @Test
+    public void testGetDefaultMmsApplicationAsUser() {
+        ComponentName componentName = SmsApplication.getDefaultMmsApplicationAsUser(mContext,
+                        false, UserHandle.SYSTEM);
+        assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName());
+        assertEquals(MMS_RECEIVER_NAME, componentName.getClassName());
     }
 
     @Test
-    public void testGetDefaultMmsApplication() {
-        assertEquals(TEST_COMPONENT_NAME,
-                SmsApplication.getDefaultMmsApplicationAsUser(mContext, false,
-                        UserHandle.USER_SYSTEM));
+    public void testGetDefaultExternalTelephonyProviderChangedApplicationAsUser() {
+        ComponentName componentName = SmsApplication
+                .getDefaultExternalTelephonyProviderChangedApplicationAsUser(mContext,
+                        false, UserHandle.SYSTEM);
+        assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName());
+        assertEquals(EXTERNAL_PROVIDER_CHANGE_NAME, componentName.getClassName());
     }
 
     @Test
-    public void testGetDefaultExternalTelephonyProviderChangedApplication() {
-        assertEquals(TEST_COMPONENT_NAME,
-                SmsApplication.getDefaultExternalTelephonyProviderChangedApplicationAsUser(mContext,
-                        false, UserHandle.USER_SYSTEM));
+    public void testGetDefaultRespondViaMessageApplicationAsUserAsUser() {
+        ComponentName componentName = SmsApplication.getDefaultRespondViaMessageApplicationAsUser(
+                mContext, false, UserHandle.SYSTEM);
+        assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName());
+        assertEquals(RESPOND_VIA_SMS_NAME, componentName.getClassName());
+    }
+
+    @Test
+    public void testGetDefaultSimFullApplicationAsUser() {
+        ComponentName componentName = SmsApplication.getDefaultSimFullApplicationAsUser(mContext,
+                false, UserHandle.SYSTEM);
+        assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName());
+        assertEquals(SIM_FULL_NAME, componentName.getClassName());
     }
 
     @Test
@@ -174,7 +204,8 @@
         setupPackageInfosForCoreApps();
 
         assertEquals(TEST_COMPONENT_NAME,
-                SmsApplication.getDefaultSmsApplicationAsUser(mContext, true, 0));
+                SmsApplication.getDefaultSmsApplicationAsUser(mContext, true,
+                        UserHandle.SYSTEM));
         verify(mAppOpsManager, atLeastOnce()).setUidMode(AppOpsManager.OPSTR_READ_SMS, SMS_APP_UID,
                 AppOpsManager.MODE_ALLOWED);
     }
@@ -251,6 +282,10 @@
                 return Collections.singletonList(makeRespondViaMessageResolveInfo());
             case Intent.ACTION_SENDTO:
                 return Collections.singletonList(makeSendToResolveInfo());
+            case Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE:
+                return Collections.singletonList(makeExternalProviderChangeResolveInfo());
+            case Telephony.Sms.Intents.SIM_FULL_ACTION:
+                return Collections.singletonList(makeSimFullResolveInfo());
         }
         return Collections.emptyList();
     }
@@ -308,4 +343,26 @@
         info.activityInfo = activityInfo;
         return info;
     }
+
+    private ResolveInfo makeExternalProviderChangeResolveInfo() {
+        ResolveInfo info = new ResolveInfo();
+        ActivityInfo activityInfo = new ActivityInfo();
+
+        activityInfo.packageName = TEST_COMPONENT_NAME.getPackageName();
+        activityInfo.name = EXTERNAL_PROVIDER_CHANGE_NAME;
+
+        info.activityInfo = activityInfo;
+        return info;
+    }
+
+    private ResolveInfo makeSimFullResolveInfo() {
+        ResolveInfo info = new ResolveInfo();
+        ActivityInfo activityInfo = new ActivityInfo();
+
+        activityInfo.packageName = TEST_COMPONENT_NAME.getPackageName();
+        activityInfo.name = SIM_FULL_NAME;
+
+        info.activityInfo = activityInfo;
+        return info;
+    }
 }
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
new file mode 100644
index 0000000..a62103e
--- /dev/null
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.tests;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+
+import com.android.internal.telephony.util.TelephonyUtils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class TelephonyUtilsTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    // Mocked classes
+    @Mock
+    private Context mContext;
+    @Mock
+    private SubscriptionManager mSubscriptionManager;
+
+    @Before
+    public void setup() {
+        doReturn(mSubscriptionManager).when(mContext)
+                .getSystemService(eq(SubscriptionManager.class));
+    }
+
+
+    @Test
+    public void getSubscriptionUserHandle_subId_invalid() {
+        int invalidSubId = -10;
+        doReturn(false).when(mSubscriptionManager).isActiveSubscriptionId(eq(invalidSubId));
+
+        TelephonyUtils.getSubscriptionUserHandle(mContext, invalidSubId);
+
+        // getSubscriptionUserHandle should not be called if subID is inactive.
+        verify(mSubscriptionManager, never()).getSubscriptionUserHandle(eq(invalidSubId));
+    }
+
+    @Test
+    public void getSubscriptionUserHandle_subId_valid() {
+        int activeSubId = 1;
+        doReturn(true).when(mSubscriptionManager).isActiveSubscriptionId(eq(activeSubId));
+
+        TelephonyUtils.getSubscriptionUserHandle(mContext, activeSubId);
+
+        // getSubscriptionUserHandle should be called if subID is active.
+        verify(mSubscriptionManager, times(1)).getSubscriptionUserHandle(eq(activeSubId));
+    }
+}
+
+
diff --git a/tests/TouchLatency/Android.bp b/tests/TouchLatency/Android.bp
index 3a9e240..4ef1ead 100644
--- a/tests/TouchLatency/Android.bp
+++ b/tests/TouchLatency/Android.bp
@@ -12,6 +12,7 @@
     manifest: "app/src/main/AndroidManifest.xml",
     // omit gradle 'build' dir
     srcs: ["app/src/main/java/**/*.java"],
+    static_libs: ["com.google.android.material_material"],
     resource_dirs: ["app/src/main/res"],
     aaptflags: ["--auto-add-overlay"],
     sdk_version: "current",
diff --git a/tests/TouchLatency/app/build.gradle b/tests/TouchLatency/app/build.gradle
index f5ae6f4..129baab 100644
--- a/tests/TouchLatency/app/build.gradle
+++ b/tests/TouchLatency/app/build.gradle
@@ -6,7 +6,7 @@
 
     defaultConfig {
         applicationId "com.prefabulated.touchlatency"
-        minSdkVersion 28
+        minSdkVersion 30
         targetSdkVersion 33
         versionCode 1
         versionName "1.0"
@@ -17,4 +17,9 @@
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
         }
     }
+
+    dependencies {
+        implementation 'androidx.appcompat:appcompat:1.5.1'
+        implementation 'com.google.android.material:material:1.6.0'
+    }
 }
diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
index 6ab3b3e..2e93c87 100644
--- a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
+++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
@@ -16,7 +16,6 @@
 
 package com.prefabulated.touchlatency;
 
-import android.app.Activity;
 import android.app.ActivityOptions;
 import android.content.Intent;
 import android.hardware.display.DisplayManager;
@@ -30,25 +29,49 @@
 import android.view.Window;
 import android.view.WindowManager;
 
-public class TouchLatencyActivity extends Activity {
-    private Mode mDisplayModes[];
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.android.material.slider.RangeSlider;
+import com.google.android.material.slider.RangeSlider.OnChangeListener;
+
+public class TouchLatencyActivity extends AppCompatActivity {
+    private static final int REFRESH_RATE_SLIDER_MIN = 20;
+    private static final int REFRESH_RATE_SLIDER_STEP = 5;
+
+    private Menu mMenu;
+    private Mode[] mDisplayModes;
     private int mCurrentModeIndex;
+    private float mSliderPreferredRefreshRate;
     private DisplayManager mDisplayManager;
+
     private final DisplayManager.DisplayListener mDisplayListener =
             new DisplayManager.DisplayListener() {
         @Override
         public void onDisplayAdded(int i) {
-            invalidateOptionsMenu();
+            updateOptionsMenu();
         }
 
         @Override
         public void onDisplayRemoved(int i) {
-            invalidateOptionsMenu();
+            updateOptionsMenu();
         }
 
         @Override
         public void onDisplayChanged(int i) {
-            invalidateOptionsMenu();
+            updateOptionsMenu();
+        }
+    };
+
+    private final RangeSlider.OnChangeListener mRefreshRateSliderListener = new OnChangeListener() {
+        @Override
+        public void onValueChange(@NonNull RangeSlider slider, float value, boolean fromUser) {
+            if (value == mSliderPreferredRefreshRate) return;
+
+            mSliderPreferredRefreshRate = value;
+            WindowManager.LayoutParams w = getWindow().getAttributes();
+            w.preferredRefreshRate = mSliderPreferredRefreshRate;
+            getWindow().setAttributes(w);
         }
     };
 
@@ -75,17 +98,23 @@
         Trace.endSection();
     }
 
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        Trace.beginSection("TouchLatencyActivity onCreateOptionsMenu");
-        // Inflate the menu; this adds items to the action bar if it is present.
-        getMenuInflater().inflate(R.menu.menu_touch_latency, menu);
+    public void updateOptionsMenu() {
         if (mDisplayModes.length > 1) {
-            MenuItem menuItem = menu.findItem(R.id.display_mode);
+            MenuItem menuItem = mMenu.findItem(R.id.display_mode);
             Mode currentMode = getWindowManager().getDefaultDisplay().getMode();
             updateDisplayMode(menuItem, currentMode);
         }
-        updateMultiDisplayMenu(menu.findItem(R.id.multi_display));
+        updateRefreshRateMenu(mMenu.findItem(R.id.frame_rate));
+        updateMultiDisplayMenu(mMenu.findItem(R.id.multi_display));
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        Trace.beginSection("TouchLatencyActivity onCreateOptionsMenu");
+        mMenu = menu;
+        // Inflate the menu; this adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.menu_touch_latency, mMenu);
+        updateOptionsMenu();
         Trace.endSection();
         return true;
     }
@@ -96,6 +125,32 @@
         menuItem.setVisible(true);
     }
 
+    private float getHighestRefreshRate() {
+        float maxRefreshRate = 0;
+        for (Display.Mode mode : getDisplay().getSupportedModes()) {
+            if (sameSizeMode(mode) && mode.getRefreshRate() > maxRefreshRate) {
+                maxRefreshRate = mode.getRefreshRate();
+            }
+        }
+        return maxRefreshRate;
+    }
+
+    private void updateRefreshRateMenu(MenuItem item) {
+        item.setActionView(R.layout.refresh_rate_layout);
+        RangeSlider slider = item.getActionView().findViewById(R.id.slider_from_layout);
+        slider.addOnChangeListener(mRefreshRateSliderListener);
+
+        float highestRefreshRate = getHighestRefreshRate();
+        slider.setValueFrom(REFRESH_RATE_SLIDER_MIN);
+        slider.setValueTo(highestRefreshRate);
+        slider.setStepSize(REFRESH_RATE_SLIDER_STEP);
+        if (mSliderPreferredRefreshRate < REFRESH_RATE_SLIDER_MIN
+                || mSliderPreferredRefreshRate > highestRefreshRate) {
+            mSliderPreferredRefreshRate = highestRefreshRate;
+        }
+        slider.setValues(mSliderPreferredRefreshRate);
+    }
+
     private void updateMultiDisplayMenu(MenuItem item) {
         item.setVisible(mDisplayManager.getDisplays().length > 1);
     }
@@ -105,6 +160,12 @@
         mDisplayManager.registerDisplayListener(mDisplayListener, new Handler());
     }
 
+    private boolean sameSizeMode(Display.Mode mode) {
+        Mode currentMode = mDisplayModes[mCurrentModeIndex];
+        return currentMode.getPhysicalHeight() == mode.getPhysicalHeight()
+            && currentMode.getPhysicalWidth() == mode.getPhysicalWidth();
+    }
+
     public void changeDisplayMode(MenuItem item) {
         Window w = getWindow();
         WindowManager.LayoutParams params = w.getAttributes();
@@ -112,10 +173,7 @@
         int modeIndex = (mCurrentModeIndex + 1) % mDisplayModes.length;
         while (modeIndex != mCurrentModeIndex) {
             // skip modes with different resolutions
-            Mode currentMode = mDisplayModes[mCurrentModeIndex];
-            Mode nextMode = mDisplayModes[modeIndex];
-            if (currentMode.getPhysicalHeight() == nextMode.getPhysicalHeight()
-                    && currentMode.getPhysicalWidth() == nextMode.getPhysicalWidth()) {
+            if (sameSizeMode(mDisplayModes[modeIndex])) {
                 break;
             }
             modeIndex = (modeIndex + 1) % mDisplayModes.length;
diff --git a/tests/TouchLatency/app/src/main/res/layout/refresh_rate_layout.xml b/tests/TouchLatency/app/src/main/res/layout/refresh_rate_layout.xml
new file mode 100644
index 0000000..bb9ce60
--- /dev/null
+++ b/tests/TouchLatency/app/src/main/res/layout/refresh_rate_layout.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+        <com.google.android.material.slider.RangeSlider
+            android:id="@+id/slider_from_layout"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:tickColor="@color/cardview_light_background"
+            app:trackColor="@color/cardview_light_background"
+            app:thumbColor="@color/cardview_dark_background"
+            android:visibility="visible"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml b/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml
index abc7fd5..7169021 100644
--- a/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml
+++ b/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml
@@ -14,21 +14,25 @@
      limitations under the License.
 -->
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools" tools:context=".TouchLatencyActivity">
     <item
         android:id="@+id/action_settings"
         android:orderInCategory="101"
-        android:showAsAction="always"
-        android:title="@string/mode"/>
+        android:title="@string/mode"
+        app:showAsAction="always" />
+    <item
+        android:id="@+id/frame_rate"
+        android:title="@string/frame_rate"
+        app:showAsAction="collapseActionView" />
     <item
         android:id="@+id/display_mode"
-        android:showAsAction="ifRoom"
         android:title="@string/display_mode"
-        android:visible="false"/>
-
+        android:visible="false"
+        app:showAsAction="always" />
     <item
         android:id="@+id/multi_display"
-        android:showAsAction="ifRoom"
         android:title="@string/multi_display"
-        android:visible="false"/>
+        android:visible="false"
+        app:showAsAction="ifRoom" />
 </menu>
diff --git a/tests/TouchLatency/app/src/main/res/values/strings.xml b/tests/TouchLatency/app/src/main/res/values/strings.xml
index 5ee86d8..cad2df7 100644
--- a/tests/TouchLatency/app/src/main/res/values/strings.xml
+++ b/tests/TouchLatency/app/src/main/res/values/strings.xml
@@ -18,5 +18,6 @@
 
     <string name="mode">Touch</string>
     <string name="display_mode">Mode</string>
+    <string name="frame_rate">Frame Rate</string>
     <string name="multi_display">multi-display</string>
 </resources>
diff --git a/tests/TouchLatency/app/src/main/res/values/styles.xml b/tests/TouchLatency/app/src/main/res/values/styles.xml
index 22da7c1..b23a87e 100644
--- a/tests/TouchLatency/app/src/main/res/values/styles.xml
+++ b/tests/TouchLatency/app/src/main/res/values/styles.xml
@@ -16,7 +16,7 @@
 <resources>
 
     <!-- Base application theme. -->
-    <style name="AppTheme" parent="@android:style/Theme.Material.Light.DarkActionBar">
+    <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
         <!-- Customize your theme here. -->
     </style>
 
diff --git a/tests/TouchLatency/gradle.properties b/tests/TouchLatency/gradle.properties
index 1d3591c..ccd5dda 100644
--- a/tests/TouchLatency/gradle.properties
+++ b/tests/TouchLatency/gradle.properties
@@ -15,4 +15,5 @@
 # When configured, Gradle will run in incubating parallel mode.
 # This option should only be used with decoupled projects. More details, visit
 # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
\ No newline at end of file
+# org.gradle.parallel=true
+android.useAndroidX=true
\ No newline at end of file
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index cbe13d9..650686f 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -373,6 +373,10 @@
         try (InputStream is = new FileInputStream(certPath)) {
             result = runShellCommand("mini-keyctl padd asymmetric fsv_test .fs-verity", is);
         }
+        // /data/local/tmp is not readable by system server. Copy a cert file to /data/fonts
+        final String copiedCert = "/data/fonts/debug_cert.der";
+        runShellCommand("cp " + certPath + " " + copiedCert, null);
+        runShellCommand("cmd font install-debug-cert " + copiedCert, null);
         // Assert that there are no errors.
         assertThat(result.second).isEmpty();
         String keyId = result.first.trim();
diff --git a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
index d133f6f..e2099e6 100644
--- a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
+++ b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
@@ -24,12 +24,15 @@
 
 import android.content.Context;
 import android.hardware.usb.UsbManager;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.util.Log;
 
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.atomic.AtomicInteger;
+
 /**
  * Unit tests lib for {@link android.hardware.usb.UsbManager}.
  */
@@ -42,6 +45,11 @@
     private UsbManager mUsbManagerMock;
     @Mock private android.hardware.usb.IUsbManager mMockUsbService;
 
+    /**
+     * Counter for tracking UsbOperation operations.
+     */
+    private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
     public UsbManagerTestLib(Context context) {
         MockitoAnnotations.initMocks(this);
         mContext = context;
@@ -82,10 +90,11 @@
     }
 
     private void testSetCurrentFunctionsMock_Matched(long functions) {
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
         try {
             setCurrentFunctions(functions);
 
-            verify(mMockUsbService).setCurrentFunctions(eq(functions));
+            verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
         } catch (RemoteException remEx) {
             Log.w(TAG, "RemoteException");
         }
@@ -106,9 +115,10 @@
     }
 
     public void testSetCurrentFunctionsEx(long functions) throws Exception {
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
         setCurrentFunctions(functions);
 
-        verify(mMockUsbService).setCurrentFunctions(eq(functions));
+        verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
     }
 
     public void testGetCurrentFunctions_shouldMatched() {
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
index 86bcb72..4103ca7 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -98,7 +98,7 @@
         }
 
         @Override
-        protected void setEnabledFunctions(long functions, boolean force) {
+        protected void setEnabledFunctions(long functions, boolean force, int operationId) {
             mCurrentFunctions = functions;
         }
 
@@ -134,6 +134,20 @@
         protected void sendStickyBroadcast(Intent intent) {
             mBroadcastedIntent = intent;
         }
+
+        @Override
+        public void handlerInitDone(int operationId) {
+        }
+
+        @Override
+        public void setCurrentUsbFunctionsCb(long functions,
+                    int status, int mRequest, long mFunctions, boolean mChargingFunctions){
+        }
+
+        @Override
+        public void getUsbSpeedCb(int speed){
+        }
+
     }
 
     @Before
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
index e6b60cf..167d560 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
@@ -88,46 +88,7 @@
     }
 
     private static String insetsTypesToString(int types) {
-        if (types == 0) {
-            return "none";
-        }
-        final StringBuilder sb = new StringBuilder();
-        if ((types & Type.statusBars()) != 0) {
-            types &= ~Type.statusBars();
-            sb.append("statusBars ");
-        }
-        if ((types & Type.navigationBars()) != 0) {
-            types &= ~Type.navigationBars();
-            sb.append("navigationBars ");
-        }
-        if ((types & Type.captionBar()) != 0) {
-            types &= ~Type.captionBar();
-            sb.append("captionBar ");
-        }
-        if ((types & Type.ime()) != 0) {
-            types &= ~Type.ime();
-            sb.append("ime ");
-        }
-        if ((types & Type.systemGestures()) != 0) {
-            types &= ~Type.systemGestures();
-            sb.append("systemGestures ");
-        }
-        if ((types & Type.mandatorySystemGestures()) != 0) {
-            types &= ~Type.mandatorySystemGestures();
-            sb.append("mandatorySystemGestures ");
-        }
-        if ((types & Type.tappableElement()) != 0) {
-            types &= ~Type.tappableElement();
-            sb.append("tappableElement ");
-        }
-        if ((types & Type.displayCutout()) != 0) {
-            types &= ~Type.displayCutout();
-            sb.append("displayCutout ");
-        }
-        if (types != 0) {
-            sb.append("unknownTypes:").append(types);
-        }
-        return sb.toString();
+        return types == 0 ? "none" : WindowInsets.Type.toString(types);
     }
 
     @Override
diff --git a/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java b/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java
deleted file mode 100644
index 5ab4dc6..0000000
--- a/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java
+++ /dev/null
@@ -1,77 +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.fsverity;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
-
-import org.junit.rules.ExternalResource;
-
-public final class AddFsVerityCertRule extends ExternalResource {
-
-    private static final String APK_VERITY_STANDARD_MODE = "2";
-
-    private final BaseHostJUnit4Test mHost;
-    private final String mCertPath;
-    private String mKeyId;
-
-    public AddFsVerityCertRule(BaseHostJUnit4Test host, String certPath) {
-        mHost = host;
-        mCertPath = certPath;
-    }
-
-    @Override
-    protected void before() throws Throwable {
-        ITestDevice device = mHost.getDevice();
-        String apkVerityMode = device.getProperty("ro.apk_verity.mode");
-        assumeTrue(device.getLaunchApiLevel() >= 30
-                || APK_VERITY_STANDARD_MODE.equals(apkVerityMode));
-
-        String keyId = executeCommand(
-                "mini-keyctl padd asymmetric fsv_test .fs-verity < " + mCertPath).trim();
-        assertThat(keyId).matches("^\\d+$");
-        mKeyId = keyId;
-    }
-
-    @Override
-    protected void after() {
-        if (mKeyId == null) return;
-        try {
-            executeCommand("mini-keyctl unlink " + mKeyId + " .fs-verity");
-        } catch (DeviceNotAvailableException e) {
-            LogUtil.CLog.e(e);
-        }
-        mKeyId = null;
-    }
-
-    private String executeCommand(String cmd) throws DeviceNotAvailableException {
-        CommandResult result = mHost.getDevice().executeShellV2Command(cmd);
-        assertWithMessage("`" + cmd + "` failed: " + result.getStderr())
-                .that(result.getStatus())
-                .isEqualTo(CommandStatus.SUCCESS);
-        return result.getStdout();
-    }
-}
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
index 3da8b46..cc3781a 100644
--- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
@@ -147,12 +147,39 @@
 
     @Override
     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-        return registerReceiver(receiver, filter, null, null);
+        return registerReceiver(receiver, filter, null, null, 0);
+    }
+
+    /**
+     * Registers the specified {@code receiver} to listen for broadcasts that match the {@code
+     * filter} in the current process.
+     *
+     * <p>Since this method only listens for broadcasts in the current process, the provided {@code
+     * flags} are ignored; this method is primarily intended to allow receivers that register with
+     * flags to register in the current process during tests.
+     */
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
+        return registerReceiver(receiver, filter, null, null, flags);
     }
 
     @Override
     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
             String broadcastPermission, Handler scheduler) {
+        return registerReceiver(receiver, filter, broadcastPermission, scheduler, 0);
+    }
+
+    /**
+     * Registers the specified {@code receiver} to listen for broadcasts that match the {@code
+     * filter} to run in the context of the specified {@code scheduler} in the current process.
+     *
+     * <p>Since this method only listens for broadcasts in the current process, the provided {@code
+     * flags} are ignored; this method is primarily intended to allow receivers that register with
+     * flags to register in the current process during tests.
+     */
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+            String broadcastPermission, Handler scheduler, int flags) {
         synchronized (mInterceptors) {
             mInterceptors.add(new BroadcastInterceptor(receiver, filter, scheduler));
         }
@@ -219,6 +246,12 @@
     }
 
     @Override
+    public void sendBroadcastAsUser(Intent intent, UserHandle user,
+            String receiverPermission, Bundle options) {
+        sendBroadcast(intent);
+    }
+
+    @Override
     public void sendStickyBroadcast(Intent intent) {
         sendBroadcast(intent);
     }
diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index 15a6afc..7c5dcf8 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -50,6 +50,7 @@
             "android.app.servertransaction.", // all tests under the package.
             "android.view.CutoutSpecificationTest",
             "android.view.DisplayCutoutTest",
+            "android.view.DisplayShapeTest",
             "android.view.InsetsAnimationControlImplTest",
             "android.view.InsetsControllerTest",
             "android.view.InsetsFlagsTest",
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index f924b2e..258642ac 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -17,10 +17,12 @@
 package com.android.server;
 
 import static android.net.ConnectivityManager.NetworkCallback;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnManager.VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -95,6 +97,7 @@
 import com.android.server.vcn.VcnContext;
 import com.android.server.vcn.VcnNetworkProvider;
 import com.android.server.vcn.util.PersistableBundleUtils;
+import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -252,6 +255,10 @@
                 .when(mMockContext)
                 .enforceCallingOrSelfPermission(
                         eq(android.Manifest.permission.NETWORK_FACTORY), any());
+
+        doReturn(Collections.singleton(TRANSPORT_WIFI))
+                .when(mMockDeps)
+                .getRestrictedTransports(any(), any());
     }
 
 
@@ -637,8 +644,7 @@
         final BroadcastReceiver receiver = getPackageChangeReceiver();
 
         verify(mMockContext).registerReceiver(any(), argThat(filter -> {
-            return filter.hasAction(Intent.ACTION_PACKAGE_REMOVED)
-                    && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED);
+            return filter.hasAction(Intent.ACTION_PACKAGE_REMOVED);
         }), any(), any());
 
         receiver.onReceive(mMockContext, new Intent(Intent.ACTION_PACKAGE_REMOVED));
@@ -1032,63 +1038,188 @@
                 new LinkProperties());
     }
 
+    private void checkGetRestrictedTransports(
+            ParcelUuid subGrp,
+            TelephonySubscriptionSnapshot lastSnapshot,
+            Set<Integer> expectedTransports) {
+        Set<Integer> result =
+                new VcnManagementService.Dependencies()
+                        .getRestrictedTransports(subGrp, lastSnapshot);
+        assertEquals(expectedTransports, result);
+    }
+
     @Test
-    public void testGetUnderlyingNetworkPolicyCellular() throws Exception {
+    public void testGetRestrictedTransports() {
+        final Set<Integer> restrictedTransports = new ArraySet<>();
+        restrictedTransports.add(TRANSPORT_CELLULAR);
+        restrictedTransports.add(TRANSPORT_WIFI);
+
+        PersistableBundle carrierConfigBundle = new PersistableBundle();
+        carrierConfigBundle.putIntArray(
+                VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
+                restrictedTransports.stream().mapToInt(i -> i).toArray());
+        final PersistableBundleWrapper carrierConfig =
+                new PersistableBundleWrapper(carrierConfigBundle);
+
+        final TelephonySubscriptionSnapshot lastSnapshot =
+                mock(TelephonySubscriptionSnapshot.class);
+        doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
+
+        checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+    }
+
+    @Test
+    public void testGetRestrictedTransports_noRestrictPolicyConfigured() {
+        final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
+
+        final PersistableBundleWrapper carrierConfig =
+                new PersistableBundleWrapper(new PersistableBundle());
+        final TelephonySubscriptionSnapshot lastSnapshot =
+                mock(TelephonySubscriptionSnapshot.class);
+        doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
+
+        checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+    }
+
+    @Test
+    public void testGetRestrictedTransports_noCarrierConfig() {
+        final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
+
+        final TelephonySubscriptionSnapshot lastSnapshot =
+                mock(TelephonySubscriptionSnapshot.class);
+
+        checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+    }
+
+    private void checkGetUnderlyingNetworkPolicy(
+            int transportType,
+            boolean isTransportRestricted,
+            boolean isActive,
+            boolean expectVcnManaged,
+            boolean expectRestricted)
+            throws Exception {
+
+        final Set<Integer> restrictedTransports = new ArraySet();
+        if (isTransportRestricted) {
+            restrictedTransports.add(transportType);
+        }
+        doReturn(restrictedTransports).when(mMockDeps).getRestrictedTransports(any(), any());
+
         final VcnUnderlyingNetworkPolicy policy =
                 startVcnAndGetPolicyForTransport(
-                        TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */, TRANSPORT_CELLULAR);
+                        TEST_SUBSCRIPTION_ID, TEST_UUID_2, isActive, transportType);
+
+        assertFalse(policy.isTeardownRequested());
+        verifyMergedNetworkCapabilities(
+                policy.getMergedNetworkCapabilities(),
+                transportType,
+                expectVcnManaged,
+                expectRestricted);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicy_unrestrictCell() throws Exception {
+        checkGetUnderlyingNetworkPolicy(
+                TRANSPORT_CELLULAR,
+                false /* isTransportRestricted */,
+                true /* isActive */,
+                true /* expectVcnManaged */,
+                false /* expectRestricted */);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicy_unrestrictCellSafeMode() throws Exception {
+        checkGetUnderlyingNetworkPolicy(
+                TRANSPORT_CELLULAR,
+                false /* isTransportRestricted */,
+                false /* isActive */,
+                false /* expectVcnManaged */,
+                false /* expectRestricted */);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicy_restrictCell() throws Exception {
+        checkGetUnderlyingNetworkPolicy(
+                TRANSPORT_CELLULAR,
+                true /* isTransportRestricted */,
+                true /* isActive */,
+                true /* expectVcnManaged */,
+                true /* expectRestricted */);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicy_restrictCellSafeMode() throws Exception {
+        checkGetUnderlyingNetworkPolicy(
+                TRANSPORT_CELLULAR,
+                true /* isTransportRestricted */,
+                false /* isActive */,
+                false /* expectVcnManaged */,
+                false /* expectRestricted */);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicy_unrestrictWifi() throws Exception {
+        checkGetUnderlyingNetworkPolicy(
+                TRANSPORT_WIFI,
+                false /* isTransportRestricted */,
+                true /* isActive */,
+                true /* expectVcnManaged */,
+                false /* expectRestricted */);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicy_unrestrictWifiSafeMode() throws Exception {
+        checkGetUnderlyingNetworkPolicy(
+                TRANSPORT_WIFI,
+                false /* isTransportRestricted */,
+                false /* isActive */,
+                false /* expectVcnManaged */,
+                false /* expectRestricted */);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicy_restrictWifi() throws Exception {
+        checkGetUnderlyingNetworkPolicy(
+                TRANSPORT_WIFI,
+                true /* isTransportRestricted */,
+                true /* isActive */,
+                true /* expectVcnManaged */,
+                true /* expectRestricted */);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicy_restrictWifiSafeMode() throws Exception {
+        checkGetUnderlyingNetworkPolicy(
+                TRANSPORT_WIFI,
+                true /* isTransportRestricted */,
+                false /* isActive */,
+                false /* expectVcnManaged */,
+                true /* expectRestricted */);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicyCell_restrictWifi() throws Exception {
+        doReturn(Collections.singleton(TRANSPORT_WIFI))
+                .when(mMockDeps)
+                .getRestrictedTransports(any(), any());
+
+        setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isVcnActive */);
+
+        // Get the policy for a cellular network and expect it won't be affected by the wifi
+        // restriction policy
+        final VcnUnderlyingNetworkPolicy policy =
+                mVcnMgmtSvc.getUnderlyingNetworkPolicy(
+                        getNetworkCapabilitiesBuilderForTransport(
+                                        TEST_SUBSCRIPTION_ID, TRANSPORT_CELLULAR)
+                                .build(),
+                        new LinkProperties());
 
         assertFalse(policy.isTeardownRequested());
         verifyMergedNetworkCapabilities(
                 policy.getMergedNetworkCapabilities(),
                 TRANSPORT_CELLULAR,
-                true /* isVcnManaged */,
-                false /* isRestricted */);
-    }
-
-    @Test
-    public void testGetUnderlyingNetworkPolicyCellular_safeMode() throws Exception {
-        final VcnUnderlyingNetworkPolicy policy =
-                startVcnAndGetPolicyForTransport(
-                        TEST_SUBSCRIPTION_ID,
-                        TEST_UUID_2,
-                        false /* isActive */,
-                        TRANSPORT_CELLULAR);
-
-        assertFalse(policy.isTeardownRequested());
-        verifyMergedNetworkCapabilities(
-                policy.getMergedNetworkCapabilities(),
-                NetworkCapabilities.TRANSPORT_CELLULAR,
-                false /* isVcnManaged */,
-                false /* isRestricted */);
-    }
-
-    @Test
-    public void testGetUnderlyingNetworkPolicyWifi() throws Exception {
-        final VcnUnderlyingNetworkPolicy policy =
-                startVcnAndGetPolicyForTransport(
-                        TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */, TRANSPORT_WIFI);
-
-        assertFalse(policy.isTeardownRequested());
-        verifyMergedNetworkCapabilities(
-                policy.getMergedNetworkCapabilities(),
-                NetworkCapabilities.TRANSPORT_WIFI,
-                true /* isVcnManaged */,
-                true /* isRestricted */);
-    }
-
-    @Test
-    public void testGetUnderlyingNetworkPolicyVcnWifi_safeMode() throws Exception {
-        final VcnUnderlyingNetworkPolicy policy =
-                startVcnAndGetPolicyForTransport(
-                        TEST_SUBSCRIPTION_ID, TEST_UUID_2, false /* isActive */, TRANSPORT_WIFI);
-
-        assertFalse(policy.isTeardownRequested());
-        verifyMergedNetworkCapabilities(
-                policy.getMergedNetworkCapabilities(),
-                NetworkCapabilities.TRANSPORT_WIFI,
-                false /* isVcnManaged */,
-                true /* isRestricted */);
+                true /* expectVcnManaged */,
+                false /* expectRestricted */);
     }
 
     private void setupTrackedCarrierWifiNetwork(NetworkCapabilities caps) {
@@ -1139,6 +1270,27 @@
     }
 
     @Test
+    public void testGetUnderlyingNetworkPolicyForRestrictedImsWhenUnrestrictingCell()
+            throws Exception {
+        final NetworkCapabilities existingNetworkCaps =
+                getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_CELLULAR)
+                        .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                        .removeCapability(NET_CAPABILITY_IMS)
+                        .build();
+        setupTrackedCarrierWifiNetwork(existingNetworkCaps);
+
+        final VcnUnderlyingNetworkPolicy policy =
+                mVcnMgmtSvc.getUnderlyingNetworkPolicy(
+                        getNetworkCapabilitiesBuilderForTransport(
+                                        TEST_SUBSCRIPTION_ID, TRANSPORT_CELLULAR)
+                                .addCapability(NET_CAPABILITY_IMS)
+                                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                                .build(),
+                        new LinkProperties());
+        assertFalse(policy.isTeardownRequested());
+    }
+
+    @Test
     public void testGetUnderlyingNetworkPolicyNonVcnNetwork() throws Exception {
         setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_1, true /* isActive */);
 
@@ -1218,6 +1370,30 @@
         verify(mMockPolicyListener).onPolicyChanged();
     }
 
+    @Test
+    public void testVcnCarrierConfigChangeUpdatesPolicyListener() throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListenerForTest(mMockPolicyListener);
+
+        final TelephonySubscriptionSnapshot snapshot =
+                buildSubscriptionSnapshot(
+                        TEST_SUBSCRIPTION_ID,
+                        TEST_UUID_2,
+                        Collections.singleton(TEST_UUID_2),
+                        Collections.emptyMap(),
+                        true /* hasCarrierPrivileges */);
+
+        final PersistableBundleWrapper mockCarrierConfig = mock(PersistableBundleWrapper.class);
+        doReturn(mockCarrierConfig).when(snapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
+
+        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+        cb.onNewSnapshot(snapshot);
+
+        verify(mMockPolicyListener).onPolicyChanged();
+    }
+
     private void triggerVcnSafeMode(
             @NonNull ParcelUuid subGroup,
             @NonNull TelephonySubscriptionSnapshot snapshot,
diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
index 09080be..965b073 100644
--- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.vcn;
 
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnManager.VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY;
 import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
 import static android.telephony.CarrierConfigManager.EXTRA_SLOT_INDEX;
 import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX;
@@ -39,6 +42,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -494,6 +498,37 @@
     }
 
     @Test
+    public void testCarrierConfigUpdatedAfterValidTriggersCallbacks() throws Exception {
+        mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+        mTestLooper.dispatchAll();
+        verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
+        reset(mCallback);
+
+        final PersistableBundle updatedConfig = new PersistableBundle();
+        updatedConfig.putIntArray(
+                VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
+                new int[] {TRANSPORT_WIFI, TRANSPORT_CELLULAR});
+        doReturn(updatedConfig)
+                .when(mCarrierConfigManager)
+                .getConfigForSubId(eq(TEST_SUBSCRIPTION_ID_1));
+
+        Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap = new HashMap<>();
+        subIdToCarrierConfigMap.put(
+                TEST_SUBSCRIPTION_ID_1, new PersistableBundleWrapper(updatedConfig));
+        mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+        mTestLooper.dispatchAll();
+
+        verify(mCallback)
+                .onNewSnapshot(
+                        eq(
+                                buildExpectedSnapshot(
+                                        0,
+                                        TEST_SUBID_TO_INFO_MAP,
+                                        subIdToCarrierConfigMap,
+                                        TEST_PRIVILEGED_PACKAGES)));
+    }
+
+    @Test
     public void testSlotClearedAfterValidTriggersCallbacks() throws Exception {
         mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
         mTestLooper.dispatchAll();
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 47750fc..4e597fb 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -3743,15 +3743,15 @@
     size_t amt = 0;
     ResTable_entry header;
     memset(&header, 0, sizeof(header));
-    header.size = htods(sizeof(header));
+    header.full.size = htods(sizeof(header));
     const type ty = mType;
     if (ty == TYPE_BAG) {
-        header.flags |= htods(header.FLAG_COMPLEX);
+        header.full.flags |= htods(header.FLAG_COMPLEX);
     }
     if (isPublic) {
-        header.flags |= htods(header.FLAG_PUBLIC);
+        header.full.flags |= htods(header.FLAG_PUBLIC);
     }
-    header.key.index = htodl(mNameIndex);
+    header.full.key.index = htodl(mNameIndex);
     if (ty != TYPE_BAG) {
         status_t err = data->writeData(&header, sizeof(header));
         if (err != NO_ERROR) {
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index f9e52b4..df87889 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -687,32 +687,32 @@
         continue;
       }
 
-      printer_->Print((entry->flags & ResTable_entry::FLAG_COMPLEX) ? "[ResTable_map_entry]"
-                                                                    : "[ResTable_entry]");
+      if (entry->is_complex()) {
+        printer_->Print("[ResTable_map_entry]");
+      } else if (entry->is_compact()) {
+        printer_->Print("[ResTable_entry_compact]");
+      } else {
+        printer_->Print("[ResTable_entry]");
+      }
+
       printer_->Print(StringPrintf(" id: 0x%04x", it.index()));
       printer_->Print(StringPrintf(
-          " name: %s",
-          android::util::GetString(key_pool_, android::util::DeviceToHost32(entry->key.index))
-              .c_str()));
-      printer_->Print(
-          StringPrintf(" keyIndex: %u", android::util::DeviceToHost32(entry->key.index)));
-      printer_->Print(StringPrintf(" size: %u", android::util::DeviceToHost32(entry->size)));
-      printer_->Print(StringPrintf(" flags: 0x%04x", android::util::DeviceToHost32(entry->flags)));
+          " name: %s", android::util::GetString(key_pool_, entry->key()).c_str()));
+      printer_->Print(StringPrintf(" keyIndex: %u", entry->key()));
+      printer_->Print(StringPrintf(" size: %zu", entry->size()));
+      printer_->Print(StringPrintf(" flags: 0x%04x", entry->flags()));
 
       printer_->Indent();
 
-      if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
-        auto map_entry = (const ResTable_map_entry*)entry;
-        printer_->Print(
-            StringPrintf(" count: 0x%04x", android::util::DeviceToHost32(map_entry->count)));
+      if (auto map_entry = entry->map_entry()) {
+        uint32_t map_entry_count = android::util::DeviceToHost32(map_entry->count);
+        printer_->Print(StringPrintf(" count: 0x%04x", map_entry_count));
         printer_->Print(StringPrintf(" parent: 0x%08x\n",
                                      android::util::DeviceToHost32(map_entry->parent.ident)));
 
         // Print the name and value mappings
-        auto maps = (const ResTable_map*)((const uint8_t*)entry +
-                                          android::util::DeviceToHost32(entry->size));
-        for (size_t i = 0, count = android::util::DeviceToHost32(map_entry->count); i < count;
-             i++) {
+        auto maps = (const ResTable_map*)((const uint8_t*)entry + entry->size());
+        for (size_t i = 0; i < map_entry_count; i++) {
           PrintResValue(&(maps[i].value), config, type);
 
           printer_->Print(StringPrintf(
@@ -725,9 +725,8 @@
         printer_->Print("\n");
 
         // Print the value of the entry
-        auto value =
-            (const Res_value*)((const uint8_t*)entry + android::util::DeviceToHost32(entry->size));
-        PrintResValue(value, config, type);
+        Res_value value = entry->value();
+        PrintResValue(&value, config, type);
       }
 
       printer_->Undent();
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index 9b9cde2..6b1fd9f 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -72,7 +72,7 @@
   }
 }
 
-std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path,
+std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(StringPiece path,
                                                       android::IDiagnostics* diag) {
   android::Source source(path);
   std::string error;
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index a4aff3f..4cd7eae 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -45,7 +45,7 @@
   virtual ~LoadedApk() = default;
 
   // Loads both binary and proto APKs from disk.
-  static std::unique_ptr<LoadedApk> LoadApkFromPath(const ::android::StringPiece& path,
+  static std::unique_ptr<LoadedApk> LoadApkFromPath(android::StringPiece path,
                                                     android::IDiagnostics* diag);
 
   // Loads a proto APK from the given file collection.
diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h
index 0b49052..0b08c32 100644
--- a/tools/aapt2/NameMangler.h
+++ b/tools/aapt2/NameMangler.h
@@ -36,7 +36,7 @@
    * We must know which references to mangle, and which to keep (android vs.
    * com.android.support).
    */
-  std::set<std::string> packages_to_mangle;
+  std::set<std::string, std::less<>> packages_to_mangle;
 };
 
 class NameMangler {
@@ -54,7 +54,7 @@
                         mangled_entry_name);
   }
 
-  bool ShouldMangle(const std::string& package) const {
+  bool ShouldMangle(std::string_view package) const {
     if (package.empty() || policy_.target_package_name == package) {
       return false;
     }
@@ -68,8 +68,8 @@
    * The mangled name should contain symbols that are illegal to define in XML,
    * so that there will never be name mangling collisions.
    */
-  static std::string MangleEntry(const std::string& package, const std::string& name) {
-    return package + "$" + name;
+  static std::string MangleEntry(std::string_view package, std::string_view name) {
+    return (std::string(package) += '$') += name;
   }
 
   /**
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index df8c3b9..cfcb2bb 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -138,11 +138,11 @@
   return {to_string(t), t};
 }
 
-std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::StringPiece& s) {
+std::optional<ResourceNamedTypeRef> ParseResourceNamedType(android::StringPiece s) {
   auto dot = std::find(s.begin(), s.end(), '.');
   const ResourceType* parsedType;
   if (dot != s.end() && dot != std::prev(s.end())) {
-    parsedType = ParseResourceType(s.substr(s.begin(), dot));
+    parsedType = ParseResourceType(android::StringPiece(s.begin(), dot - s.begin()));
   } else {
     parsedType = ParseResourceType(s);
   }
@@ -152,7 +152,7 @@
   return ResourceNamedTypeRef(s, *parsedType);
 }
 
-const ResourceType* ParseResourceType(const StringPiece& str) {
+const ResourceType* ParseResourceType(StringPiece str) {
   auto iter = sResourceTypeMap.find(str);
   if (iter == std::end(sResourceTypeMap)) {
     return nullptr;
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 9cfaf47..7ba3277 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -74,7 +74,7 @@
 /**
  * Returns a pointer to a valid ResourceType, or nullptr if the string was invalid.
  */
-const ResourceType* ParseResourceType(const android::StringPiece& str);
+const ResourceType* ParseResourceType(android::StringPiece str);
 
 /**
  * Pair of type name as in ResourceTable and actual resource type.
@@ -87,7 +87,7 @@
   ResourceType type = ResourceType::kRaw;
 
   ResourceNamedType() = default;
-  ResourceNamedType(const android::StringPiece& n, ResourceType t);
+  ResourceNamedType(android::StringPiece n, ResourceType t);
 
   int compare(const ResourceNamedType& other) const;
 
@@ -108,19 +108,19 @@
   ResourceNamedTypeRef(const ResourceNamedTypeRef&) = default;
   ResourceNamedTypeRef(ResourceNamedTypeRef&&) = default;
   ResourceNamedTypeRef(const ResourceNamedType& rhs);  // NOLINT(google-explicit-constructor)
-  ResourceNamedTypeRef(const android::StringPiece& n, ResourceType t);
+  ResourceNamedTypeRef(android::StringPiece n, ResourceType t);
   ResourceNamedTypeRef& operator=(const ResourceNamedTypeRef& rhs) = default;
   ResourceNamedTypeRef& operator=(ResourceNamedTypeRef&& rhs) = default;
   ResourceNamedTypeRef& operator=(const ResourceNamedType& rhs);
 
   ResourceNamedType ToResourceNamedType() const;
 
-  std::string to_string() const;
+  std::string_view to_string() const;
 };
 
 ResourceNamedTypeRef ResourceNamedTypeWithDefaultName(ResourceType t);
 
-std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::StringPiece& s);
+std::optional<ResourceNamedTypeRef> ParseResourceNamedType(android::StringPiece s);
 
 /**
  * A resource's name. This can uniquely identify
@@ -132,9 +132,8 @@
   std::string entry;
 
   ResourceName() = default;
-  ResourceName(const android::StringPiece& p, const ResourceNamedTypeRef& t,
-               const android::StringPiece& e);
-  ResourceName(const android::StringPiece& p, ResourceType t, const android::StringPiece& e);
+  ResourceName(android::StringPiece p, const ResourceNamedTypeRef& t, android::StringPiece e);
+  ResourceName(android::StringPiece p, ResourceType t, android::StringPiece e);
 
   int compare(const ResourceName& other) const;
 
@@ -157,9 +156,8 @@
   ResourceNameRef(const ResourceNameRef&) = default;
   ResourceNameRef(ResourceNameRef&&) = default;
   ResourceNameRef(const ResourceName& rhs);  // NOLINT(google-explicit-constructor)
-  ResourceNameRef(const android::StringPiece& p, const ResourceNamedTypeRef& t,
-                  const android::StringPiece& e);
-  ResourceNameRef(const android::StringPiece& p, ResourceType t, const android::StringPiece& e);
+  ResourceNameRef(android::StringPiece p, const ResourceNamedTypeRef& t, android::StringPiece e);
+  ResourceNameRef(android::StringPiece p, ResourceType t, android::StringPiece e);
   ResourceNameRef& operator=(const ResourceNameRef& rhs) = default;
   ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
   ResourceNameRef& operator=(const ResourceName& rhs);
@@ -346,8 +344,8 @@
 //
 // ResourceNamedType implementation.
 //
-inline ResourceNamedType::ResourceNamedType(const android::StringPiece& n, ResourceType t)
-    : name(n.to_string()), type(t) {
+inline ResourceNamedType::ResourceNamedType(android::StringPiece n, ResourceType t)
+    : name(n), type(t) {
 }
 
 inline int ResourceNamedType::compare(const ResourceNamedType& other) const {
@@ -380,7 +378,7 @@
 //
 // ResourceNamedTypeRef implementation.
 //
-inline ResourceNamedTypeRef::ResourceNamedTypeRef(const android::StringPiece& n, ResourceType t)
+inline ResourceNamedTypeRef::ResourceNamedTypeRef(android::StringPiece n, ResourceType t)
     : name(n), type(t) {
 }
 
@@ -398,8 +396,8 @@
   return ResourceNamedType(name, type);
 }
 
-inline std::string ResourceNamedTypeRef::to_string() const {
-  return name.to_string();
+inline std::string_view ResourceNamedTypeRef::to_string() const {
+  return name;
 }
 
 inline bool operator<(const ResourceNamedTypeRef& lhs, const ResourceNamedTypeRef& rhs) {
@@ -422,13 +420,12 @@
 // ResourceName implementation.
 //
 
-inline ResourceName::ResourceName(const android::StringPiece& p, const ResourceNamedTypeRef& t,
-                                  const android::StringPiece& e)
-    : package(p.to_string()), type(t.ToResourceNamedType()), entry(e.to_string()) {
+inline ResourceName::ResourceName(android::StringPiece p, const ResourceNamedTypeRef& t,
+                                  android::StringPiece e)
+    : package(p), type(t.ToResourceNamedType()), entry(e) {
 }
 
-inline ResourceName::ResourceName(const android::StringPiece& p, ResourceType t,
-                                  const android::StringPiece& e)
+inline ResourceName::ResourceName(android::StringPiece p, ResourceType t, android::StringPiece e)
     : ResourceName(p, ResourceNamedTypeWithDefaultName(t), e) {
 }
 
@@ -471,14 +468,13 @@
 inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs)
     : package(rhs.package), type(rhs.type), entry(rhs.entry) {}
 
-inline ResourceNameRef::ResourceNameRef(const android::StringPiece& p,
-                                        const ResourceNamedTypeRef& t,
-                                        const android::StringPiece& e)
+inline ResourceNameRef::ResourceNameRef(android::StringPiece p, const ResourceNamedTypeRef& t,
+                                        android::StringPiece e)
     : package(p), type(t), entry(e) {
 }
 
-inline ResourceNameRef::ResourceNameRef(const android::StringPiece& p, ResourceType t,
-                                        const android::StringPiece& e)
+inline ResourceNameRef::ResourceNameRef(android::StringPiece p, ResourceType t,
+                                        android::StringPiece e)
     : ResourceNameRef(p, ResourceNamedTypeWithDefaultName(t), e) {
 }
 
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 19fd306..fa9a98f 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -50,11 +50,11 @@
 constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
 
 // Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
-static bool ShouldIgnoreElement(const StringPiece& ns, const StringPiece& name) {
+static bool ShouldIgnoreElement(StringPiece ns, StringPiece name) {
   return ns.empty() && (name == "skip" || name == "eat-comment");
 }
 
-static uint32_t ParseFormatTypeNoEnumsOrFlags(const StringPiece& piece) {
+static uint32_t ParseFormatTypeNoEnumsOrFlags(StringPiece piece) {
   if (piece == "reference") {
     return android::ResTable_map::TYPE_REFERENCE;
   } else if (piece == "string") {
@@ -75,7 +75,7 @@
   return 0;
 }
 
-static uint32_t ParseFormatType(const StringPiece& piece) {
+static uint32_t ParseFormatType(StringPiece piece) {
   if (piece == "enum") {
     return android::ResTable_map::TYPE_ENUM;
   } else if (piece == "flags") {
@@ -84,9 +84,9 @@
   return ParseFormatTypeNoEnumsOrFlags(piece);
 }
 
-static uint32_t ParseFormatAttribute(const StringPiece& str) {
+static uint32_t ParseFormatAttribute(StringPiece str) {
   uint32_t mask = 0;
-  for (const StringPiece& part : util::Tokenize(str, '|')) {
+  for (StringPiece part : util::Tokenize(str, '|')) {
     StringPiece trimmed_part = util::TrimWhitespace(part);
     uint32_t type = ParseFormatType(trimmed_part);
     if (type == 0) {
@@ -122,7 +122,7 @@
   StringPiece trimmed_comment = util::TrimWhitespace(res->comment);
   if (trimmed_comment.size() != res->comment.size()) {
     // Only if there was a change do we re-assign.
-    res->comment = trimmed_comment.to_string();
+    res->comment = std::string(trimmed_comment);
   }
 
   NewResourceBuilder res_builder(res->name);
@@ -362,7 +362,7 @@
       // Trim leading whitespace.
       StringPiece trimmed = util::TrimLeadingWhitespace(first_segment->data);
       if (trimmed.size() != first_segment->data.size()) {
-        first_segment->data = trimmed.to_string();
+        first_segment->data = std::string(trimmed);
       }
     }
 
@@ -370,7 +370,7 @@
       // Trim trailing whitespace.
       StringPiece trimmed = util::TrimTrailingWhitespace(last_segment->data);
       if (trimmed.size() != last_segment->data.size()) {
-        last_segment->data = trimmed.to_string();
+        last_segment->data = std::string(trimmed);
       }
     }
   }
@@ -466,7 +466,7 @@
 
     // Extract the product name if it exists.
     if (std::optional<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) {
-      parsed_resource.product = maybe_product.value().to_string();
+      parsed_resource.product = std::string(maybe_product.value());
     }
 
     // Parse the resource regardless of product.
@@ -559,7 +559,7 @@
 
     // Items have their type encoded in the type attribute.
     if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
-      resource_type = maybe_type.value().to_string();
+      resource_type = std::string(maybe_type.value());
     } else {
       diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
                    << "<item> must have a 'type' attribute");
@@ -582,7 +582,7 @@
 
     // Bags have their type encoded in the type attribute.
     if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
-      resource_type = maybe_type.value().to_string();
+      resource_type = std::string(maybe_type.value());
     } else {
       diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
                    << "<bag> must have a 'type' attribute");
@@ -603,7 +603,7 @@
 
     out_resource->name.type =
         ResourceNamedTypeWithDefaultName(ResourceType::kId).ToResourceNamedType();
-    out_resource->name.entry = maybe_name.value().to_string();
+    out_resource->name.entry = std::string(maybe_name.value());
 
     // Ids either represent a unique resource id or reference another resource id
     auto item = ParseItem(parser, out_resource, resource_format);
@@ -640,7 +640,7 @@
 
     out_resource->name.type =
         ResourceNamedTypeWithDefaultName(ResourceType::kMacro).ToResourceNamedType();
-    out_resource->name.entry = maybe_name.value().to_string();
+    out_resource->name.entry = std::string(maybe_name.value());
     return ParseMacro(parser, out_resource);
   }
 
@@ -657,7 +657,7 @@
 
       out_resource->name.type =
           ResourceNamedTypeWithDefaultName(item_iter->second.type).ToResourceNamedType();
-      out_resource->name.entry = maybe_name.value().to_string();
+      out_resource->name.entry = std::string(maybe_name.value());
 
       // Only use the implied format of the type when there is no explicit format.
       if (resource_format == 0u) {
@@ -684,7 +684,7 @@
           return false;
         }
 
-        out_resource->name.entry = maybe_name.value().to_string();
+        out_resource->name.entry = std::string(maybe_name.value());
       }
 
       // Call the associated parse method. The type will be filled in by the
@@ -708,7 +708,7 @@
       }
 
       out_resource->name.type = parsed_type->ToResourceNamedType();
-      out_resource->name.entry = maybe_name.value().to_string();
+      out_resource->name.entry = std::string(maybe_name.value());
       out_resource->value = ParseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
       if (!out_resource->value) {
         diag_->Error(android::DiagMessage(out_resource->source)
@@ -1005,7 +1005,7 @@
   const size_t depth = parser->depth();
   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
     if (parser->event() == xml::XmlPullParser::Event::kComment) {
-      comment = util::TrimWhitespace(parser->comment()).to_string();
+      comment = std::string(util::TrimWhitespace(parser->comment()));
       continue;
     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
       // Skip text.
@@ -1045,7 +1045,7 @@
       }
 
       ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{
-          .name = ResourceName{{}, parsed_type, maybe_name.value().to_string()},
+          .name = ResourceName{{}, parsed_type, std::string(maybe_name.value())},
           .source = item_source,
           .comment = std::move(comment),
       });
@@ -1231,7 +1231,7 @@
 
       ParsedResource child_resource{};
       child_resource.name.type = type->ToResourceNamedType();
-      child_resource.name.entry = item_name.value().to_string();
+      child_resource.name.entry = std::string(item_name.value());
       child_resource.overlayable_item = overlayable_item;
       out_resource->child_resources.push_back(std::move(child_resource));
 
@@ -1246,7 +1246,7 @@
                      xml::FindNonEmptyAttribute(parser, "type")) {
         // Parse the polices separated by vertical bar characters to allow for specifying multiple
         // policies. Items within the policy tag will have the specified policy.
-        for (const StringPiece& part : util::Tokenize(maybe_type.value(), '|')) {
+        for (StringPiece part : util::Tokenize(maybe_type.value(), '|')) {
           StringPiece trimmed_part = util::TrimWhitespace(part);
           const auto policy = std::find_if(kPolicyStringToFlag.begin(),
                                            kPolicyStringToFlag.end(),
@@ -1377,7 +1377,7 @@
   const size_t depth = parser->depth();
   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
     if (parser->event() == xml::XmlPullParser::Event::kComment) {
-      comment = util::TrimWhitespace(parser->comment()).to_string();
+      comment = std::string(util::TrimWhitespace(parser->comment()));
       continue;
     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
       // Skip text.
@@ -1457,7 +1457,7 @@
 }
 
 std::optional<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(xml::XmlPullParser* parser,
-                                                                     const StringPiece& tag) {
+                                                                     StringPiece tag) {
   const android::Source source = source_.WithLine(parser->line_number());
 
   std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
@@ -1764,7 +1764,7 @@
   const size_t depth = parser->depth();
   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
     if (parser->event() == xml::XmlPullParser::Event::kComment) {
-      comment = util::TrimWhitespace(parser->comment()).to_string();
+      comment = std::string(util::TrimWhitespace(parser->comment()));
       continue;
     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
       // Ignore text.
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 396ce97..012a056 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -122,7 +122,7 @@
   bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak);
   std::optional<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser,
-                                                       const android::StringPiece& tag);
+                                                       android::StringPiece tag);
   bool ParseStyle(ResourceType type, xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseStyleItem(xml::XmlPullParser* parser, Style* style);
   bool ParseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* out_resource);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index fe7eb96..b59b165 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -65,11 +65,11 @@
     context_ = test::ContextBuilder().Build();
   }
 
-  ::testing::AssertionResult TestParse(const StringPiece& str) {
+  ::testing::AssertionResult TestParse(StringPiece str) {
     return TestParse(str, ConfigDescription{});
   }
 
-  ::testing::AssertionResult TestParse(const StringPiece& str, const ConfigDescription& config) {
+  ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) {
     ResourceParserOptions parserOptions;
     ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config,
                           parserOptions);
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index cb48114..a3b0b45 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -49,21 +49,21 @@
 }
 
 template <typename T>
-bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const StringPiece& rhs) {
+bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, StringPiece rhs) {
   return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
 }
 
 template <typename T>
-bool greater_than_struct_with_name(const StringPiece& lhs, const std::unique_ptr<T>& rhs) {
+bool greater_than_struct_with_name(StringPiece lhs, const std::unique_ptr<T>& rhs) {
   return rhs->name.compare(0, rhs->name.size(), lhs.data(), lhs.size()) > 0;
 }
 
 template <typename T>
 struct NameEqualRange {
-  bool operator()(const std::unique_ptr<T>& lhs, const StringPiece& rhs) const {
+  bool operator()(const std::unique_ptr<T>& lhs, StringPiece rhs) const {
     return less_than_struct_with_name<T>(lhs, rhs);
   }
-  bool operator()(const StringPiece& lhs, const std::unique_ptr<T>& rhs) const {
+  bool operator()(StringPiece lhs, const std::unique_ptr<T>& rhs) const {
     return greater_than_struct_with_name<T>(lhs, rhs);
   }
 };
@@ -78,7 +78,7 @@
 }
 
 template <typename T, typename Func, typename Elements>
-T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Func action) {
+T* FindElementsRunAction(android::StringPiece name, Elements& entries, Func action) {
   const auto iter =
       std::lower_bound(entries.begin(), entries.end(), name, less_than_struct_with_name<T>);
   const bool found = iter != entries.end() && name == (*iter)->name;
@@ -87,7 +87,7 @@
 
 struct ConfigKey {
   const ConfigDescription* config;
-  const StringPiece& product;
+  StringPiece product;
 };
 
 template <typename T>
@@ -104,12 +104,12 @@
 ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) {
 }
 
-ResourceTablePackage* ResourceTable::FindPackage(const android::StringPiece& name) const {
+ResourceTablePackage* ResourceTable::FindPackage(android::StringPiece name) const {
   return FindElementsRunAction<ResourceTablePackage>(
       name, packages, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; });
 }
 
-ResourceTablePackage* ResourceTable::FindOrCreatePackage(const android::StringPiece& name) {
+ResourceTablePackage* ResourceTable::FindOrCreatePackage(android::StringPiece name) {
   return FindElementsRunAction<ResourceTablePackage>(name, packages, [&](bool found, auto& iter) {
     return found ? iter->get() : packages.emplace(iter, new ResourceTablePackage(name))->get();
   });
@@ -139,18 +139,18 @@
   });
 }
 
-ResourceEntry* ResourceTableType::CreateEntry(const android::StringPiece& name) {
+ResourceEntry* ResourceTableType::CreateEntry(android::StringPiece name) {
   return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) {
     return entries.emplace(iter, new ResourceEntry(name))->get();
   });
 }
 
-ResourceEntry* ResourceTableType::FindEntry(const android::StringPiece& name) const {
+ResourceEntry* ResourceTableType::FindEntry(android::StringPiece name) const {
   return FindElementsRunAction<ResourceEntry>(
       name, entries, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; });
 }
 
-ResourceEntry* ResourceTableType::FindOrCreateEntry(const android::StringPiece& name) {
+ResourceEntry* ResourceTableType::FindOrCreateEntry(android::StringPiece name) {
   return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) {
     return found ? iter->get() : entries.emplace(iter, new ResourceEntry(name))->get();
   });
@@ -183,7 +183,7 @@
 }
 
 ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config,
-                                                      const StringPiece& product) {
+                                                      StringPiece product) {
   auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
                                lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
   if (iter != values.end()) {
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index f49ce81..bb286a8 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -71,12 +71,11 @@
 
 struct Overlayable {
   Overlayable() = default;
-   Overlayable(const android::StringPiece& name, const android::StringPiece& actor)
-       : name(name.to_string()), actor(actor.to_string()) {}
-   Overlayable(const android::StringPiece& name, const android::StringPiece& actor,
-               const android::Source& source)
-       : name(name.to_string()), actor(actor.to_string()), source(source) {
-   }
+  Overlayable(android::StringPiece name, android::StringPiece actor) : name(name), actor(actor) {
+  }
+  Overlayable(android::StringPiece name, android::StringPiece actor, const android::Source& source)
+      : name(name), actor(actor), source(source) {
+  }
 
   static const char* kActorScheme;
   std::string name;
@@ -105,8 +104,9 @@
   // The actual Value.
   std::unique_ptr<Value> value;
 
-  ResourceConfigValue(const android::ConfigDescription& config, const android::StringPiece& product)
-      : config(config), product(product.to_string()) {}
+  ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product)
+      : config(config), product(product) {
+  }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
@@ -136,7 +136,8 @@
   // The resource's values for each configuration.
   std::vector<std::unique_ptr<ResourceConfigValue>> values;
 
-  explicit ResourceEntry(const android::StringPiece& name) : name(name.to_string()) {}
+  explicit ResourceEntry(android::StringPiece name) : name(name) {
+  }
 
   ResourceConfigValue* FindValue(const android::ConfigDescription& config,
                                  android::StringPiece product = {});
@@ -144,7 +145,7 @@
                                        android::StringPiece product = {}) const;
 
   ResourceConfigValue* FindOrCreateValue(const android::ConfigDescription& config,
-                                         const android::StringPiece& product);
+                                         android::StringPiece product);
   std::vector<ResourceConfigValue*> FindAllValues(const android::ConfigDescription& config);
 
   template <typename Func>
@@ -180,9 +181,9 @@
       : named_type(type.ToResourceNamedType()) {
   }
 
-  ResourceEntry* CreateEntry(const android::StringPiece& name);
-  ResourceEntry* FindEntry(const android::StringPiece& name) const;
-  ResourceEntry* FindOrCreateEntry(const android::StringPiece& name);
+  ResourceEntry* CreateEntry(android::StringPiece name);
+  ResourceEntry* FindEntry(android::StringPiece name) const;
+  ResourceEntry* FindOrCreateEntry(android::StringPiece name);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
@@ -194,7 +195,7 @@
 
   std::vector<std::unique_ptr<ResourceTableType>> types;
 
-  explicit ResourceTablePackage(const android::StringPiece& name) : name(name.to_string()) {
+  explicit ResourceTablePackage(android::StringPiece name) : name(name) {
   }
 
   ResourceTablePackage() = default;
@@ -319,8 +320,8 @@
   // Returns the package struct with the given name, or nullptr if such a package does not
   // exist. The empty string is a valid package and typically is used to represent the
   // 'current' package before it is known to the ResourceTable.
-  ResourceTablePackage* FindPackage(const android::StringPiece& name) const;
-  ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name);
+  ResourceTablePackage* FindPackage(android::StringPiece name) const;
+  ResourceTablePackage* FindOrCreatePackage(android::StringPiece name);
 
   std::unique_ptr<ResourceTable> Clone() const;
 
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 0cf8473..54b98d1 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -187,7 +187,7 @@
 static ::testing::AssertionResult VisibilityOfResource(const ResourceTable& table,
                                                        const ResourceNameRef& name,
                                                        Visibility::Level level,
-                                                       const StringPiece& comment) {
+                                                       StringPiece comment) {
   std::optional<ResourceTable::SearchResult> result = table.FindResource(name);
   if (!result) {
     return ::testing::AssertionFailure() << "no resource '" << name << "' found in table";
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 41c7435..5a118a9 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -109,8 +109,7 @@
   return name_out;
 }
 
-bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref,
-                       bool* out_private) {
+bool ParseResourceName(StringPiece str, ResourceNameRef* out_ref, bool* out_private) {
   if (str.empty()) {
     return false;
   }
@@ -151,8 +150,8 @@
   return true;
 }
 
-bool ParseReference(const StringPiece& str, ResourceNameRef* out_ref,
-                    bool* out_create, bool* out_private) {
+bool ParseReference(StringPiece str, ResourceNameRef* out_ref, bool* out_create,
+                    bool* out_private) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
   if (trimmed_str.empty()) {
     return false;
@@ -198,11 +197,11 @@
   return false;
 }
 
-bool IsReference(const StringPiece& str) {
+bool IsReference(StringPiece str) {
   return ParseReference(str, nullptr, nullptr, nullptr);
 }
 
-bool ParseAttributeReference(const StringPiece& str, ResourceNameRef* out_ref) {
+bool ParseAttributeReference(StringPiece str, ResourceNameRef* out_ref) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
   if (trimmed_str.empty()) {
     return false;
@@ -235,7 +234,7 @@
   return false;
 }
 
-bool IsAttributeReference(const StringPiece& str) {
+bool IsAttributeReference(StringPiece str) {
   return ParseAttributeReference(str, nullptr);
 }
 
@@ -247,7 +246,7 @@
  * <[*]package>:[style/]<entry>
  * [[*]package:style/]<entry>
  */
-std::optional<Reference> ParseStyleParentReference(const StringPiece& str, std::string* out_error) {
+std::optional<Reference> ParseStyleParentReference(StringPiece str, std::string* out_error) {
   if (str.empty()) {
     return {};
   }
@@ -296,7 +295,7 @@
   return result;
 }
 
-std::optional<Reference> ParseXmlAttributeName(const StringPiece& str) {
+std::optional<Reference> ParseXmlAttributeName(StringPiece str) {
   StringPiece trimmed_str = util::TrimWhitespace(str);
   const char* start = trimmed_str.data();
   const char* const end = start + trimmed_str.size();
@@ -325,8 +324,7 @@
   return std::optional<Reference>(std::move(ref));
 }
 
-std::unique_ptr<Reference> TryParseReference(const StringPiece& str,
-                                             bool* out_create) {
+std::unique_ptr<Reference> TryParseReference(StringPiece str, bool* out_create) {
   ResourceNameRef ref;
   bool private_ref = false;
   if (ParseReference(str, &ref, out_create, &private_ref)) {
@@ -344,7 +342,7 @@
   return {};
 }
 
-std::unique_ptr<Item> TryParseNullOrEmpty(const StringPiece& str) {
+std::unique_ptr<Item> TryParseNullOrEmpty(StringPiece str) {
   const StringPiece trimmed_str(util::TrimWhitespace(str));
   if (trimmed_str == "@null") {
     return MakeNull();
@@ -365,8 +363,7 @@
                                             android::Res_value::DATA_NULL_EMPTY);
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
-                                                    const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, StringPiece str) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
   for (const Attribute::Symbol& symbol : enum_attr->symbols) {
     // Enum symbols are stored as @package:id/symbol resources,
@@ -382,8 +379,7 @@
   return {};
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr,
-                                                    const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr, StringPiece str) {
   android::Res_value flags = {};
   flags.dataType = android::Res_value::TYPE_INT_HEX;
   flags.data = 0u;
@@ -393,7 +389,7 @@
     return util::make_unique<BinaryPrimitive>(flags);
   }
 
-  for (const StringPiece& part : util::Tokenize(str, '|')) {
+  for (StringPiece part : util::Tokenize(str, '|')) {
     StringPiece trimmed_part = util::TrimWhitespace(part);
 
     bool flag_set = false;
@@ -429,7 +425,7 @@
   }
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseColor(StringPiece str) {
   StringPiece color_str(util::TrimWhitespace(str));
   const char* start = color_str.data();
   const size_t len = color_str.size();
@@ -484,7 +480,7 @@
                : util::make_unique<BinaryPrimitive>(value);
 }
 
-std::optional<bool> ParseBool(const StringPiece& str) {
+std::optional<bool> ParseBool(StringPiece str) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
   if (trimmed_str == "true" || trimmed_str == "TRUE" || trimmed_str == "True") {
     return std::optional<bool>(true);
@@ -495,7 +491,7 @@
   return {};
 }
 
-std::optional<uint32_t> ParseInt(const StringPiece& str) {
+std::optional<uint32_t> ParseInt(StringPiece str) {
   std::u16string str16 = android::util::Utf8ToUtf16(str);
   android::Res_value value;
   if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
@@ -504,7 +500,7 @@
   return {};
 }
 
-std::optional<ResourceId> ParseResourceId(const StringPiece& str) {
+std::optional<ResourceId> ParseResourceId(StringPiece str) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
 
   std::u16string str16 = android::util::Utf8ToUtf16(trimmed_str);
@@ -520,7 +516,7 @@
   return {};
 }
 
-std::optional<int> ParseSdkVersion(const StringPiece& str) {
+std::optional<int> ParseSdkVersion(StringPiece str) {
   StringPiece trimmed_str(util::TrimWhitespace(str));
 
   std::u16string str16 = android::util::Utf8ToUtf16(trimmed_str);
@@ -539,14 +535,14 @@
   const StringPiece::const_iterator begin = std::begin(trimmed_str);
   const StringPiece::const_iterator end = std::end(trimmed_str);
   const StringPiece::const_iterator codename_end = std::find(begin, end, '.');
-  entry = GetDevelopmentSdkCodeNameVersion(trimmed_str.substr(begin, codename_end));
+  entry = GetDevelopmentSdkCodeNameVersion(StringPiece(begin, codename_end - begin));
   if (entry) {
     return entry.value();
   }
   return {};
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseBool(StringPiece str) {
   if (std::optional<bool> maybe_result = ParseBool(str)) {
     const uint32_t data = maybe_result.value() ? 0xffffffffu : 0u;
     return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_BOOLEAN, data);
@@ -559,7 +555,7 @@
                                             val ? 0xffffffffu : 0u);
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseInt(StringPiece str) {
   std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(str));
   android::Res_value value;
   if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
@@ -572,7 +568,7 @@
   return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_DEC, val);
 }
 
-std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseFloat(StringPiece str) {
   std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(str));
   android::Res_value value;
   if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) {
@@ -623,7 +619,7 @@
 }
 
 std::unique_ptr<Item> TryParseItemForAttribute(
-    const StringPiece& value, uint32_t type_mask,
+    StringPiece value, uint32_t type_mask,
     const std::function<bool(const ResourceName&)>& on_create_reference) {
   using android::ResTable_map;
 
@@ -687,7 +683,7 @@
  * allows.
  */
 std::unique_ptr<Item> TryParseItemForAttribute(
-    const StringPiece& str, const Attribute* attr,
+    StringPiece str, const Attribute* attr,
     const std::function<bool(const ResourceName&)>& on_create_reference) {
   using android::ResTable_map;
 
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 22cf345..f30f4ac 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -38,7 +38,7 @@
  * `out_resource` set to the parsed resource name and `out_private` set to true
  * if a '*' prefix was present.
  */
-bool ParseResourceName(const android::StringPiece& str, ResourceNameRef* out_resource,
+bool ParseResourceName(android::StringPiece str, ResourceNameRef* out_resource,
                        bool* out_private = nullptr);
 
 /*
@@ -49,27 +49,27 @@
  * If '+' was present in the reference, `out_create` is set to true.
  * If '*' was present in the reference, `out_private` is set to true.
  */
-bool ParseReference(const android::StringPiece& str, ResourceNameRef* out_reference,
+bool ParseReference(android::StringPiece str, ResourceNameRef* out_reference,
                     bool* out_create = nullptr, bool* out_private = nullptr);
 
 /*
  * Returns true if the string is in the form of a resource reference
  * (@[+][package:]type/name).
  */
-bool IsReference(const android::StringPiece& str);
+bool IsReference(android::StringPiece str);
 
 /*
  * Returns true if the string was parsed as an attribute reference
  * (?[package:][type/]name),
  * with `out_reference` set to the parsed reference.
  */
-bool ParseAttributeReference(const android::StringPiece& str, ResourceNameRef* out_reference);
+bool ParseAttributeReference(android::StringPiece str, ResourceNameRef* out_reference);
 
 /**
  * Returns true if the string is in the form of an attribute
  * reference(?[package:][type/]name).
  */
-bool IsAttributeReference(const android::StringPiece& str);
+bool IsAttributeReference(android::StringPiece str);
 
 /**
  * Convert an android::ResTable::resource_name to an aapt::ResourceName struct.
@@ -85,22 +85,22 @@
  * Returns a boolean value if the string is equal to TRUE, true, True, FALSE,
  * false, or False.
  */
-std::optional<bool> ParseBool(const android::StringPiece& str);
+std::optional<bool> ParseBool(android::StringPiece str);
 
 /**
  * Returns a uint32_t if the string is an integer.
  */
-std::optional<uint32_t> ParseInt(const android::StringPiece& str);
+std::optional<uint32_t> ParseInt(android::StringPiece str);
 
 /**
  * Returns an ID if it the string represented a valid ID.
  */
-std::optional<ResourceId> ParseResourceId(const android::StringPiece& str);
+std::optional<ResourceId> ParseResourceId(android::StringPiece str);
 
 /**
  * Parses an SDK version, which can be an integer, or a letter from A-Z.
  */
-std::optional<int> ParseSdkVersion(const android::StringPiece& str);
+std::optional<int> ParseSdkVersion(android::StringPiece str);
 
 /*
  * Returns a Reference, or None Maybe instance if the string `str` was parsed as
@@ -113,7 +113,7 @@
  * ?[package:]style/<entry> or
  * <package>:[style/]<entry>
  */
-std::optional<Reference> ParseStyleParentReference(const android::StringPiece& str,
+std::optional<Reference> ParseStyleParentReference(android::StringPiece str,
                                                    std::string* out_error);
 
 /*
@@ -123,7 +123,7 @@
  *
  * package:entry
  */
-std::optional<Reference> ParseXmlAttributeName(const android::StringPiece& str);
+std::optional<Reference> ParseXmlAttributeName(android::StringPiece str);
 
 /*
  * Returns a Reference object if the string was parsed as a resource or
@@ -132,14 +132,13 @@
  * if
  * the '+' was present in the string.
  */
-std::unique_ptr<Reference> TryParseReference(const android::StringPiece& str,
-                                             bool* out_create = nullptr);
+std::unique_ptr<Reference> TryParseReference(android::StringPiece str, bool* out_create = nullptr);
 
 /*
  * Returns a BinaryPrimitve object representing @null or @empty if the string
  * was parsed as one.
  */
-std::unique_ptr<Item> TryParseNullOrEmpty(const android::StringPiece& str);
+std::unique_ptr<Item> TryParseNullOrEmpty(android::StringPiece str);
 
 // Returns a Reference representing @null.
 // Due to runtime compatibility issues, this is encoded as a reference with ID 0.
@@ -154,13 +153,13 @@
  * Returns a BinaryPrimitve object representing a color if the string was parsed
  * as one.
  */
-std::unique_ptr<BinaryPrimitive> TryParseColor(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseColor(android::StringPiece str);
 
 /*
  * Returns a BinaryPrimitve object representing a boolean if the string was
  * parsed as one.
  */
-std::unique_ptr<BinaryPrimitive> TryParseBool(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseBool(android::StringPiece str);
 
 // Returns a boolean BinaryPrimitive.
 std::unique_ptr<BinaryPrimitive> MakeBool(bool val);
@@ -169,7 +168,7 @@
  * Returns a BinaryPrimitve object representing an integer if the string was
  * parsed as one.
  */
-std::unique_ptr<BinaryPrimitive> TryParseInt(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseInt(android::StringPiece str);
 
 // Returns an integer BinaryPrimitive.
 std::unique_ptr<BinaryPrimitive> MakeInt(uint32_t value);
@@ -178,21 +177,21 @@
  * Returns a BinaryPrimitve object representing a floating point number
  * (float, dimension, etc) if the string was parsed as one.
  */
-std::unique_ptr<BinaryPrimitive> TryParseFloat(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseFloat(android::StringPiece str);
 
 /*
  * Returns a BinaryPrimitve object representing an enum symbol if the string was
  * parsed as one.
  */
 std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
-                                                    const android::StringPiece& str);
+                                                    android::StringPiece str);
 
 /*
  * Returns a BinaryPrimitve object representing a flag symbol if the string was
  * parsed as one.
  */
 std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr,
-                                                    const android::StringPiece& str);
+                                                    android::StringPiece str);
 /*
  * Try to convert a string to an Item for the given attribute. The attribute
  * will
@@ -201,11 +200,11 @@
  * reference to an ID that must be created (@+id/foo).
  */
 std::unique_ptr<Item> TryParseItemForAttribute(
-    const android::StringPiece& value, const Attribute* attr,
+    android::StringPiece value, const Attribute* attr,
     const std::function<bool(const ResourceName&)>& on_create_reference = {});
 
 std::unique_ptr<Item> TryParseItemForAttribute(
-    const android::StringPiece& value, uint32_t type_mask,
+    android::StringPiece value, uint32_t type_mask,
     const std::function<bool(const ResourceName&)>& on_create_reference = {});
 
 uint32_t AndroidTypeToAttributeTypeMask(uint16_t type);
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index c4d54be..a5754e0 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -206,7 +206,7 @@
   PrettyPrintReferenceImpl(*this, true /*print_package*/, printer);
 }
 
-void Reference::PrettyPrint(const StringPiece& package, Printer* printer) const {
+void Reference::PrettyPrint(StringPiece package, Printer* printer) const {
   const bool print_package = name ? package != name.value().package : true;
   PrettyPrintReferenceImpl(*this, print_package, printer);
 }
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index f5167a1..6f9dccb 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -83,8 +83,8 @@
     return comment_;
   }
 
-  void SetComment(const android::StringPiece& str) {
-    comment_ = str.to_string();
+  void SetComment(android::StringPiece str) {
+    comment_.assign(str);
   }
 
   void SetComment(std::string&& str) {
@@ -176,7 +176,7 @@
   void PrettyPrint(text::Printer* printer) const override;
 
   // Prints the reference without a package name if the package name matches the one given.
-  void PrettyPrint(const android::StringPiece& package, text::Printer* printer) const;
+  void PrettyPrint(android::StringPiece package, text::Printer* printer) const;
 };
 
 bool operator<(const Reference&, const Reference&);
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 2a450ba..1d7fd1d 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -46,6 +46,13 @@
   string version = 2;
 }
 
+// References to non local resources
+message DynamicRefTable {
+  PackageId package_id = 1;
+  string package_name = 2;
+}
+
+
 // Top level message representing a resource table.
 message ResourceTable {
   // The string pool containing source paths referenced throughout the resource table. This does
@@ -60,6 +67,8 @@
 
   // The version fingerprints of the tools that built the resource table.
   repeated ToolFingerprint tool_fingerprint = 4;
+
+  repeated DynamicRefTable dynamic_ref_table = 5;
 }
 
 // A package ID in the range [0x00, 0xff].
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index 34e8edb..a7c5479 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -77,7 +77,7 @@
   return iter->second;
 }
 
-std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(const StringPiece& code_name) {
+std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(StringPiece code_name) {
   return (sDevelopmentSdkCodeNames.find(code_name) == sDevelopmentSdkCodeNames.end())
              ? std::optional<ApiVersion>()
              : sDevelopmentSdkLevel;
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 0bd61c04..40bcef7 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -63,7 +63,7 @@
 };
 
 ApiVersion FindAttributeSdkLevel(const ResourceId& id);
-std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(const android::StringPiece& code_name);
+std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(android::StringPiece code_name);
 
 }  // namespace aapt
 
diff --git a/tools/aapt2/cmd/ApkInfo.cpp b/tools/aapt2/cmd/ApkInfo.cpp
index 697b110..3c0831c 100644
--- a/tools/aapt2/cmd/ApkInfo.cpp
+++ b/tools/aapt2/cmd/ApkInfo.cpp
@@ -64,7 +64,7 @@
     Usage(&std::cerr);
     return 1;
   }
-  const StringPiece& path = args[0];
+  StringPiece path = args[0];
   std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, diag_);
   if (!apk) {
     return 1;
diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp
index b1452fa..514651e 100644
--- a/tools/aapt2/cmd/Command.cpp
+++ b/tools/aapt2/cmd/Command.cpp
@@ -33,7 +33,7 @@
 
 namespace aapt {
 
-std::string GetSafePath(const StringPiece& arg) {
+std::string GetSafePath(StringPiece arg) {
 #ifdef _WIN32
   // If the path exceeds the maximum path length for Windows, encode the path using the
   // extended-length prefix
@@ -47,63 +47,62 @@
 
   return path8;
 #else
-  return arg.to_string();
+  return std::string(arg);
 #endif
 }
 
-void Command::AddRequiredFlag(const StringPiece& name, const StringPiece& description,
-                              std::string* value, uint32_t flags) {
-  auto func = [value, flags](const StringPiece& arg) -> bool {
-    *value = (flags & Command::kPath) ? GetSafePath(arg) : arg.to_string();
+void Command::AddRequiredFlag(StringPiece name, StringPiece description, std::string* value,
+                              uint32_t flags) {
+  auto func = [value, flags](StringPiece arg) -> bool {
+    *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func));
 }
 
-void Command::AddRequiredFlagList(const StringPiece& name, const StringPiece& description,
+void Command::AddRequiredFlagList(StringPiece name, StringPiece description,
                                   std::vector<std::string>* value, uint32_t flags) {
-  auto func = [value, flags](const StringPiece& arg) -> bool {
-    value->push_back((flags & Command::kPath) ? GetSafePath(arg) : arg.to_string());
+  auto func = [value, flags](StringPiece arg) -> bool {
+    value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func));
 }
 
-void Command::AddOptionalFlag(const StringPiece& name, const StringPiece& description,
+void Command::AddOptionalFlag(StringPiece name, StringPiece description,
                               std::optional<std::string>* value, uint32_t flags) {
-  auto func = [value, flags](const StringPiece& arg) -> bool {
-    *value = (flags & Command::kPath) ? GetSafePath(arg) : arg.to_string();
+  auto func = [value, flags](StringPiece arg) -> bool {
+    *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
 }
 
-void Command::AddOptionalFlagList(const StringPiece& name, const StringPiece& description,
+void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
                                   std::vector<std::string>* value, uint32_t flags) {
-  auto func = [value, flags](const StringPiece& arg) -> bool {
-    value->push_back((flags & Command::kPath) ? GetSafePath(arg) : arg.to_string());
+  auto func = [value, flags](StringPiece arg) -> bool {
+    value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
 }
 
-void Command::AddOptionalFlagList(const StringPiece& name, const StringPiece& description,
+void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
                                   std::unordered_set<std::string>* value) {
-  auto func = [value](const StringPiece& arg) -> bool {
-    value->insert(arg.to_string());
+  auto func = [value](StringPiece arg) -> bool {
+    value->emplace(arg);
     return true;
   };
 
   flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
 }
 
-void Command::AddOptionalSwitch(const StringPiece& name, const StringPiece& description,
-                                bool* value) {
-  auto func = [value](const StringPiece& arg) -> bool {
+void Command::AddOptionalSwitch(StringPiece name, StringPiece description, bool* value) {
+  auto func = [value](StringPiece arg) -> bool {
     *value = true;
     return true;
   };
@@ -120,8 +119,8 @@
   }
 }
 
-void Command::SetDescription(const StringPiece& description) {
-  description_ = description.to_string();
+void Command::SetDescription(StringPiece description) {
+  description_ = std::string(description);
 }
 
 void Command::Usage(std::ostream* out) {
@@ -183,7 +182,7 @@
   std::vector<std::string> file_args;
 
   for (size_t i = 0; i < args.size(); i++) {
-    const StringPiece& arg = args[i];
+    StringPiece arg = args[i];
     if (*(arg.data()) != '-') {
       // Continue parsing as the subcommand if the first argument matches one of the subcommands
       if (i == 0) {
diff --git a/tools/aapt2/cmd/Command.h b/tools/aapt2/cmd/Command.h
index 8678cda..1416e98 100644
--- a/tools/aapt2/cmd/Command.h
+++ b/tools/aapt2/cmd/Command.h
@@ -30,13 +30,10 @@
 
 class Command {
  public:
-  explicit Command(const android::StringPiece& name)
-      : name_(name.to_string()), full_subcommand_name_(name.to_string()){};
+  explicit Command(android::StringPiece name) : name_(name), full_subcommand_name_(name){};
 
-  explicit Command(const android::StringPiece& name, const android::StringPiece& short_name)
-      : name_(name.to_string()),
-        short_name_(short_name.to_string()),
-        full_subcommand_name_(name.to_string()){};
+  explicit Command(android::StringPiece name, android::StringPiece short_name)
+      : name_(name), short_name_(short_name), full_subcommand_name_(name){};
 
   Command(Command&&) = default;
   Command& operator=(Command&&) = default;
@@ -52,30 +49,26 @@
     kPath = 1 << 0,
   };
 
-  void AddRequiredFlag(const android::StringPiece& name, const android::StringPiece& description,
+  void AddRequiredFlag(android::StringPiece name, android::StringPiece description,
                        std::string* value, uint32_t flags = 0);
 
-  void AddRequiredFlagList(const android::StringPiece& name,
-                           const android::StringPiece& description, std::vector<std::string>* value,
-                           uint32_t flags = 0);
+  void AddRequiredFlagList(android::StringPiece name, android::StringPiece description,
+                           std::vector<std::string>* value, uint32_t flags = 0);
 
-  void AddOptionalFlag(const android::StringPiece& name, const android::StringPiece& description,
+  void AddOptionalFlag(android::StringPiece name, android::StringPiece description,
                        std::optional<std::string>* value, uint32_t flags = 0);
 
-  void AddOptionalFlagList(const android::StringPiece& name,
-                           const android::StringPiece& description, std::vector<std::string>* value,
-                           uint32_t flags = 0);
+  void AddOptionalFlagList(android::StringPiece name, android::StringPiece description,
+                           std::vector<std::string>* value, uint32_t flags = 0);
 
-  void AddOptionalFlagList(const android::StringPiece& name,
-                           const android::StringPiece& description,
+  void AddOptionalFlagList(android::StringPiece name, android::StringPiece description,
                            std::unordered_set<std::string>* value);
 
-  void AddOptionalSwitch(const android::StringPiece& name, const android::StringPiece& description,
-                         bool* value);
+  void AddOptionalSwitch(android::StringPiece name, android::StringPiece description, bool* value);
 
   void AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool experimental = false);
 
-  void SetDescription(const android::StringPiece& name);
+  void SetDescription(android::StringPiece name);
 
   // Prints the help menu of the command.
   void Usage(std::ostream* out);
@@ -90,17 +83,21 @@
 
  private:
   struct Flag {
-    explicit Flag(const android::StringPiece& name, const android::StringPiece& description,
+    explicit Flag(android::StringPiece name, android::StringPiece description,
                   const bool is_required, const size_t num_args,
-                  std::function<bool(const android::StringPiece& value)>&& action)
-        : name(name.to_string()), description(description.to_string()), is_required(is_required),
-          num_args(num_args), action(std::move(action)) {}
+                  std::function<bool(android::StringPiece value)>&& action)
+        : name(name),
+          description(description),
+          is_required(is_required),
+          num_args(num_args),
+          action(std::move(action)) {
+    }
 
     const std::string name;
     const std::string description;
     const bool is_required;
     const size_t num_args;
-    const std::function<bool(const android::StringPiece& value)> action;
+    const std::function<bool(android::StringPiece value)> action;
     bool found = false;
   };
 
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 0409f73..03f9715 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -125,8 +125,12 @@
   const android::Source res_path =
       options.source_path ? StringPiece(options.source_path.value()) : StringPiece(path);
 
-  return ResourcePathData{res_path, dir_str.to_string(), name.to_string(),
-                          extension.to_string(), config_str.to_string(), config};
+  return ResourcePathData{res_path,
+                          std::string(dir_str),
+                          std::string(name),
+                          std::string(extension),
+                          std::string(config_str),
+                          config};
 }
 
 static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) {
@@ -279,7 +283,7 @@
   return true;
 }
 
-static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const ResourceFile& file,
+static bool WriteHeaderAndDataToWriter(StringPiece output_path, const ResourceFile& file,
                                        io::KnownSizeInputStream* in, IArchiveWriter* writer,
                                        android::IDiagnostics* diag) {
   TRACE_CALL();
@@ -311,7 +315,7 @@
   return true;
 }
 
-static bool FlattenXmlToOutStream(const StringPiece& output_path, const xml::XmlResource& xmlres,
+static bool FlattenXmlToOutStream(StringPiece output_path, const xml::XmlResource& xmlres,
                                   ContainerWriter* container_writer, android::IDiagnostics* diag) {
   pb::internal::CompiledFile pb_compiled_file;
   SerializeCompiledFileToPb(xmlres.file, &pb_compiled_file);
@@ -538,7 +542,7 @@
     if (context->IsVerbose()) {
       // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
       // This will help catch exotic cases where the new code may generate larger PNGs.
-      std::stringstream legacy_stream(content.to_string());
+      std::stringstream legacy_stream{std::string(content)};
       android::BigBuffer legacy_buffer(4096);
       Png png(context->GetDiagnostics());
       if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 52e113e..612e3a6 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -387,7 +387,7 @@
   }
 
   Context context;
-  const StringPiece& path = args[0];
+  StringPiece path = args[0];
   unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
   if (apk == nullptr) {
     context.GetDiagnostics()->Error(android::DiagMessage(path) << "failed to load APK");
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 423e939..5bfc732 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -78,7 +78,7 @@
   SymbolTable symbol_table_;
 };
 
-static void EmitDiffLine(const android::Source& source, const StringPiece& message) {
+static void EmitDiffLine(const android::Source& source, StringPiece message) {
   std::cerr << source << ": " << message << "\n";
 }
 
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 116dcd6..97404fc 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -126,8 +126,8 @@
     return compilation_package_;
   }
 
-  void SetCompilationPackage(const StringPiece& package_name) {
-    compilation_package_ = package_name.to_string();
+  void SetCompilationPackage(StringPiece package_name) {
+    compilation_package_ = std::string(package_name);
   }
 
   uint8_t GetPackageId() override {
@@ -240,9 +240,9 @@
   IAaptContext* context_;
 };
 
-static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res,
-                       const StringPiece& path, bool keep_raw_values, bool utf16,
-                       OutputFormat format, IArchiveWriter* writer) {
+static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, StringPiece path,
+                       bool keep_raw_values, bool utf16, OutputFormat format,
+                       IArchiveWriter* writer) {
   TRACE_CALL();
   if (context->IsVerbose()) {
     context->GetDiagnostics()->Note(android::DiagMessage(path)
@@ -262,8 +262,8 @@
       }
 
       io::BigBufferInputStream input_stream(&buffer);
-      return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(),
-                                          ArchiveEntry::kCompress, writer);
+      return io::CopyInputStreamToArchive(context, &input_stream, path, ArchiveEntry::kCompress,
+                                          writer);
     } break;
 
     case OutputFormat::kProto: {
@@ -272,8 +272,7 @@
       SerializeXmlOptions options;
       options.remove_empty_text_nodes = (path == kAndroidManifestPath);
       SerializeXmlResourceToPb(xml_res, &pb_node);
-      return io::CopyProtoToArchive(context, &pb_node, path.to_string(), ArchiveEntry::kCompress,
-                                    writer);
+      return io::CopyProtoToArchive(context, &pb_node, path, ArchiveEntry::kCompress, writer);
     } break;
   }
   return false;
@@ -329,13 +328,13 @@
 };
 
 template <typename T>
-uint32_t GetCompressionFlags(const StringPiece& str, T options) {
+uint32_t GetCompressionFlags(StringPiece str, T options) {
   if (options.do_not_compress_anything) {
     return 0;
   }
 
-  if (options.regex_to_not_compress
-      && std::regex_search(str.to_string(), options.regex_to_not_compress.value())) {
+  if (options.regex_to_not_compress &&
+      std::regex_search(str.begin(), str.end(), options.regex_to_not_compress.value())) {
     return 0;
   }
 
@@ -1085,6 +1084,10 @@
       const auto localeconfig_entry =
           ResolveTableEntry(context_, &final_table_, localeconfig_reference);
       if (!localeconfig_entry) {
+        // If locale config is resolved from external symbols - skip validation.
+        if (context_->GetExternalSymbols()->FindByReference(*localeconfig_reference)) {
+          return true;
+        }
         context_->GetDiagnostics()->Error(
             android::DiagMessage(localeConfig->compiled_value->GetSource())
             << "no localeConfig entry");
@@ -1172,7 +1175,7 @@
     return bcp47tag;
   }
 
-  std::unique_ptr<IArchiveWriter> MakeArchiveWriter(const StringPiece& out) {
+  std::unique_ptr<IArchiveWriter> MakeArchiveWriter(StringPiece out) {
     if (options_.output_to_directory) {
       return CreateDirectoryArchiveWriter(context_->GetDiagnostics(), out);
     } else {
@@ -1208,8 +1211,8 @@
     return false;
   }
 
-  bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate,
-                     const StringPiece& out_package, const JavaClassGeneratorOptions& java_options,
+  bool WriteJavaFile(ResourceTable* table, StringPiece package_name_to_generate,
+                     StringPiece out_package, const JavaClassGeneratorOptions& java_options,
                      const std::optional<std::string>& out_text_symbols_path = {}) {
     if (!options_.generate_java_class_path && !out_text_symbols_path) {
       return true;
@@ -2469,14 +2472,14 @@
   for (std::string& extra_package : extra_java_packages_) {
     // A given package can actually be a colon separated list of packages.
     for (StringPiece package : util::Split(extra_package, ':')) {
-      options_.extra_java_packages.insert(package.to_string());
+      options_.extra_java_packages.emplace(package);
     }
   }
 
   if (product_list_) {
     for (StringPiece product : util::Tokenize(product_list_.value(), ',')) {
       if (product != "" && product != "default") {
-        options_.products.insert(product.to_string());
+        options_.products.emplace(product);
       }
     }
   }
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index cffcdf2..5fdfb66 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -159,6 +159,9 @@
     AddOptionalSwitch("--enable-sparse-encoding",
                       "This decreases APK size at the cost of resource retrieval performance.",
                       &options_.use_sparse_encoding);
+    AddOptionalSwitch("--enable-compact-entries",
+        "This decreases APK size by using compact resource entries for simple data types.",
+        &options_.table_flattener_options.use_compact_entries);
     AddOptionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01.",
         &legacy_x_flag_);
     AddOptionalSwitch("-z", "Require localization of strings marked 'suggested'.",
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 254f3a5..28fcc1a 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -840,6 +840,43 @@
   ASSERT_TRUE(Link(link1_args, &diag));
 }
 
+TEST_F(LinkTest, LocaleConfigVerificationExternalSymbol) {
+  StdErrDiagnostics diag;
+  const std::string base_files_dir = GetTestPath("base");
+  ASSERT_TRUE(CompileFile(GetTestPath("res/xml/locales_config.xml"), R"(
+    <locale-config xmlns:android="http://schemas.android.com/apk/res/android">
+      <locale android:name="en-US"/>
+      <locale android:name="pt"/>
+      <locale android:name="es-419"/>
+      <locale android:name="zh-Hans-SG"/>
+    </locale-config>)",
+                          base_files_dir, &diag));
+  const std::string base_apk = GetTestPath("base.apk");
+  std::vector<std::string> link_args = {
+      "--manifest",
+      GetDefaultManifest("com.aapt2.app"),
+      "-o",
+      base_apk,
+  };
+  ASSERT_TRUE(Link(link_args, base_files_dir, &diag));
+
+  const std::string localeconfig_manifest = GetTestPath("localeconfig_manifest.xml");
+  const std::string out_apk = GetTestPath("out.apk");
+  WriteFile(localeconfig_manifest, android::base::StringPrintf(R"(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.aapt2.app">
+
+      <application
+        android:localeConfig="@xml/locales_config">
+      </application>
+    </manifest>)"));
+  link_args = LinkCommandBuilder(this)
+                  .SetManifestFile(localeconfig_manifest)
+                  .AddParameter("-I", base_apk)
+                  .Build(out_apk);
+  ASSERT_TRUE(Link(link_args, &diag));
+}
+
 TEST_F(LinkTest, LocaleConfigWrongTag) {
   StdErrDiagnostics diag;
   const std::string compiled_files_dir = GetTestPath("compiled");
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 042926c..d7a39bf 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -154,13 +154,22 @@
       return 1;
     }
 
-    if (options_.shorten_resource_paths) {
-      Obfuscator obfuscator(options_.table_flattener_options.shortened_path_map);
+    Obfuscator obfuscator(options_);
+    if (obfuscator.IsEnabled()) {
       if (!obfuscator.Consume(context_, apk->GetResourceTable())) {
         context_->GetDiagnostics()->Error(android::DiagMessage()
                                           << "failed shortening resource paths");
         return 1;
       }
+
+      if (options_.obfuscation_map_path &&
+          !obfuscator.WriteObfuscationMap(options_.obfuscation_map_path.value())) {
+        context_->GetDiagnostics()->Error(android::DiagMessage()
+                                          << "failed to write the obfuscation map to file");
+        return 1;
+      }
+
+      // TODO(b/246489170): keep the old option and format until transform to the new one
       if (options_.shortened_paths_map_path
           && !WriteShortenedPathsMap(options_.table_flattener_options.shortened_path_map,
                                       options_.shortened_paths_map_path.value())) {
@@ -292,6 +301,7 @@
                                         ArchiveEntry::kAlign, writer);
   }
 
+  // TODO(b/246489170): keep the old option and format until transform to the new one
   bool WriteShortenedPathsMap(const std::map<std::string, std::string> &path_map,
                                const std::string &file_path) {
     std::stringstream ss;
@@ -370,8 +380,8 @@
 
     if (!kept_artifacts_.empty()) {
       for (const std::string& artifact_str : kept_artifacts_) {
-        for (const StringPiece& artifact : util::Tokenize(artifact_str, ',')) {
-          options_.kept_artifacts.insert(artifact.to_string());
+        for (StringPiece artifact : util::Tokenize(artifact_str, ',')) {
+          options_.kept_artifacts.emplace(artifact);
         }
       }
     }
@@ -403,7 +413,7 @@
 
   if (target_densities_) {
     // Parse the target screen densities.
-    for (const StringPiece& config_str : util::Tokenize(target_densities_.value(), ',')) {
+    for (StringPiece config_str : util::Tokenize(target_densities_.value(), ',')) {
       std::optional<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
       if (!target_density) {
         return 1;
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 794a87b..1879f25 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -58,6 +58,7 @@
   bool shorten_resource_paths = false;
 
   // Path to the output map of original resource paths to shortened paths.
+  // TODO(b/246489170): keep the old option and format until transform to the new one
   std::optional<std::string> shortened_paths_map_path;
 
   // Whether sparse encoding should be used for O+ resources.
@@ -65,6 +66,9 @@
 
   // Whether sparse encoding should be used for all resources.
   bool force_sparse_encoding = false;
+
+  // Path to the output map of original resource paths/names to obfuscated paths/names.
+  std::optional<std::string> obfuscation_map_path;
 };
 
 class OptimizeCommand : public Command {
@@ -120,9 +124,13 @@
     AddOptionalSwitch("--shorten-resource-paths",
         "Shortens the paths of resources inside the APK.",
         &options_.shorten_resource_paths);
+    // TODO(b/246489170): keep the old option and format until transform to the new one
     AddOptionalFlag("--resource-path-shortening-map",
-        "Path to output the map of old resource paths to shortened paths.",
-        &options_.shortened_paths_map_path);
+                    "[Deprecated]Path to output the map of old resource paths to shortened paths.",
+                    &options_.shortened_paths_map_path);
+    AddOptionalFlag("--save-obfuscation-map",
+                    "Path to output the map of original paths/names to obfuscated paths/names.",
+                    &options_.obfuscation_map_path);
     AddOptionalSwitch(
         "--deduplicate-entry-values",
         "Whether to deduplicate pairs of resource entry and value for simple resources.\n"
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 56e2f52..92849cf 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -34,8 +34,7 @@
 
 namespace aapt {
 
-std::optional<uint16_t> ParseTargetDensityParameter(const StringPiece& arg,
-                                                    android::IDiagnostics* diag) {
+std::optional<uint16_t> ParseTargetDensityParameter(StringPiece arg, android::IDiagnostics* diag) {
   ConfigDescription preferred_density_config;
   if (!ConfigDescription::Parse(arg, &preferred_density_config)) {
     diag->Error(android::DiagMessage()
@@ -55,7 +54,7 @@
   return preferred_density_config.density;
 }
 
-bool ParseSplitParameter(const StringPiece& arg, android::IDiagnostics* diag, std::string* out_path,
+bool ParseSplitParameter(StringPiece arg, android::IDiagnostics* diag, std::string* out_path,
                          SplitConstraints* out_split) {
   CHECK(diag != nullptr);
   CHECK(out_path != nullptr);
@@ -77,7 +76,7 @@
 
   *out_path = parts[0];
   out_split->name = parts[1];
-  for (const StringPiece& config_str : util::Tokenize(parts[1], ',')) {
+  for (StringPiece config_str : util::Tokenize(parts[1], ',')) {
     ConfigDescription config;
     if (!ConfigDescription::Parse(config_str, &config)) {
       diag->Error(android::DiagMessage()
@@ -93,7 +92,7 @@
                                                            android::IDiagnostics* diag) {
   std::unique_ptr<AxisConfigFilter> filter = util::make_unique<AxisConfigFilter>();
   for (const std::string& config_arg : args) {
-    for (const StringPiece& config_str : util::Tokenize(config_arg, ',')) {
+    for (StringPiece config_str : util::Tokenize(config_arg, ',')) {
       ConfigDescription config;
       LocaleValue lv;
       if (lv.InitFromFilterString(config_str)) {
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index 3d4ca24..169d5f9 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -34,13 +34,13 @@
 
 // Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc).
 // Returns Nothing and logs a human friendly error message if the string was not legal.
-std::optional<uint16_t> ParseTargetDensityParameter(const android::StringPiece& arg,
+std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg,
                                                     android::IDiagnostics* diag);
 
 // Parses a string of the form 'path/to/output.apk:<config>[,<config>...]' and fills in
 // `out_path` with the path and `out_split` with the set of ConfigDescriptions.
 // Returns false and logs a human friendly error message if the string was not legal.
-bool ParseSplitParameter(const android::StringPiece& arg, android::IDiagnostics* diag,
+bool ParseSplitParameter(android::StringPiece arg, android::IDiagnostics* diag,
                          std::string* out_path, SplitConstraints* out_split);
 
 // Parses a set of config filter strings of the form 'en,fr-rFR' and returns an IConfigFilter.
diff --git a/tools/aapt2/compile/NinePatch.cpp b/tools/aapt2/compile/NinePatch.cpp
index c931da4..4538ecc 100644
--- a/tools/aapt2/compile/NinePatch.cpp
+++ b/tools/aapt2/compile/NinePatch.cpp
@@ -218,11 +218,9 @@
 
 static bool PopulateBounds(const std::vector<Range>& padding,
                            const std::vector<Range>& layout_bounds,
-                           const std::vector<Range>& stretch_regions,
-                           const int32_t length, int32_t* padding_start,
-                           int32_t* padding_end, int32_t* layout_start,
-                           int32_t* layout_end, const StringPiece& edge_name,
-                           std::string* out_err) {
+                           const std::vector<Range>& stretch_regions, const int32_t length,
+                           int32_t* padding_start, int32_t* padding_end, int32_t* layout_start,
+                           int32_t* layout_end, StringPiece edge_name, std::string* out_err) {
   if (padding.size() > 1) {
     std::stringstream err_stream;
     err_stream << "too many padding sections on " << edge_name << " border";
diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h
index 7f8d923..a8b7dd1 100644
--- a/tools/aapt2/compile/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -59,7 +59,7 @@
  */
 class PngChunkFilter : public io::InputStream {
  public:
-  explicit PngChunkFilter(const android::StringPiece& data);
+  explicit PngChunkFilter(android::StringPiece data);
   virtual ~PngChunkFilter() = default;
 
   bool Next(const void** buffer, size_t* len) override;
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
index 4db2392..2e55d0c 100644
--- a/tools/aapt2/compile/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -70,7 +70,7 @@
   }
 }
 
-PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) {
+PngChunkFilter::PngChunkFilter(StringPiece data) : data_(data) {
   if (util::StartsWith(data_, kPngSignature)) {
     window_start_ = 0;
     window_end_ = kPngSignatureSize;
diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp
index 3a515fa..463ce78 100644
--- a/tools/aapt2/compile/Pseudolocalizer.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer.cpp
@@ -20,36 +20,42 @@
 
 using android::StringPiece;
 
+using namespace std::literals;
+
 namespace aapt {
 
 // String basis to generate expansion
-static const std::string kExpansionString =
+static constexpr auto kExpansionString =
     "one two three "
     "four five six seven eight nine ten eleven twelve thirteen "
-    "fourteen fiveteen sixteen seventeen nineteen twenty";
+    "fourteen fiveteen sixteen seventeen nineteen twenty"sv;
 
 // Special unicode characters to override directionality of the words
-static const std::string kRlm = "\u200f";
-static const std::string kRlo = "\u202e";
-static const std::string kPdf = "\u202c";
+static constexpr auto kRlm = "\u200f"sv;
+static constexpr auto kRlo = "\u202e"sv;
+static constexpr auto kPdf = "\u202c"sv;
 
 // Placeholder marks
-static const std::string kPlaceholderOpen = "\u00bb";
-static const std::string kPlaceholderClose = "\u00ab";
+static constexpr auto kPlaceholderOpen = "\u00bb"sv;
+static constexpr auto kPlaceholderClose = "\u00ab"sv;
 
 static const char kArgStart = '{';
 static const char kArgEnd = '}';
 
 class PseudoMethodNone : public PseudoMethodImpl {
  public:
-  std::string Text(const StringPiece& text) override { return text.to_string(); }
-  std::string Placeholder(const StringPiece& text) override { return text.to_string(); }
+  std::string Text(StringPiece text) override {
+    return std::string(text);
+  }
+  std::string Placeholder(StringPiece text) override {
+    return std::string(text);
+  }
 };
 
 class PseudoMethodBidi : public PseudoMethodImpl {
  public:
-  std::string Text(const StringPiece& text) override;
-  std::string Placeholder(const StringPiece& text) override;
+  std::string Text(StringPiece text) override;
+  std::string Placeholder(StringPiece text) override;
 };
 
 class PseudoMethodAccent : public PseudoMethodImpl {
@@ -57,8 +63,8 @@
   PseudoMethodAccent() : depth_(0), word_count_(0), length_(0) {}
   std::string Start() override;
   std::string End() override;
-  std::string Text(const StringPiece& text) override;
-  std::string Placeholder(const StringPiece& text) override;
+  std::string Text(StringPiece text) override;
+  std::string Placeholder(StringPiece text) override;
 
  private:
   size_t depth_;
@@ -84,7 +90,7 @@
   }
 }
 
-std::string Pseudolocalizer::Text(const StringPiece& text) {
+std::string Pseudolocalizer::Text(StringPiece text) {
   std::string out;
   size_t depth = last_depth_;
   size_t lastpos, pos;
@@ -116,7 +122,7 @@
       }
       size_t size = nextpos - lastpos;
       if (size) {
-        std::string chunk = text.substr(lastpos, size).to_string();
+        std::string chunk(text.substr(lastpos, size));
         if (pseudo) {
           chunk = impl_->Text(chunk);
         } else if (str[lastpos] == kArgStart && str[nextpos - 1] == kArgEnd) {
@@ -301,21 +307,23 @@
 }
 
 static std::string PseudoGenerateExpansion(const unsigned int length) {
-  std::string result = kExpansionString;
-  const char* s = result.data();
+  std::string result(kExpansionString);
   if (result.size() < length) {
     result += " ";
     result += PseudoGenerateExpansion(length - result.size());
   } else {
     int ext = 0;
     // Should contain only whole words, so looking for a space
-    for (unsigned int i = length + 1; i < result.size(); ++i) {
-      ++ext;
-      if (s[i] == ' ') {
-        break;
+    {
+      const char* const s = result.data();
+      for (unsigned int i = length + 1; i < result.size(); ++i) {
+        ++ext;
+        if (s[i] == ' ') {
+          break;
+        }
       }
     }
-    result = result.substr(0, length + ext);
+    result.resize(length + ext);
   }
   return result;
 }
@@ -349,7 +357,7 @@
  *
  * Note: This leaves placeholder syntax untouched.
  */
-std::string PseudoMethodAccent::Text(const StringPiece& source) {
+std::string PseudoMethodAccent::Text(StringPiece source) {
   const char* s = source.data();
   std::string result;
   const size_t I = source.size();
@@ -435,12 +443,12 @@
   return result;
 }
 
-std::string PseudoMethodAccent::Placeholder(const StringPiece& source) {
+std::string PseudoMethodAccent::Placeholder(StringPiece source) {
   // Surround a placeholder with brackets
-  return kPlaceholderOpen + source.to_string() + kPlaceholderClose;
+  return (std::string(kPlaceholderOpen) += source) += kPlaceholderClose;
 }
 
-std::string PseudoMethodBidi::Text(const StringPiece& source) {
+std::string PseudoMethodBidi::Text(StringPiece source) {
   const char* s = source.data();
   std::string result;
   bool lastspace = true;
@@ -456,10 +464,10 @@
     space = (!escape && isspace(c)) || (escape && (c == 'n' || c == 't'));
     if (lastspace && !space) {
       // Word start
-      result += kRlm + kRlo;
+      (result += kRlm) += kRlo;
     } else if (!lastspace && space) {
       // Word end
-      result += kPdf + kRlm;
+      (result += kPdf) += kRlm;
     }
     lastspace = space;
     if (escape) {
@@ -470,14 +478,14 @@
   }
   if (!lastspace) {
     // End of last word
-    result += kPdf + kRlm;
+    (result += kPdf) += kRlm;
   }
   return result;
 }
 
-std::string PseudoMethodBidi::Placeholder(const StringPiece& source) {
+std::string PseudoMethodBidi::Placeholder(StringPiece source) {
   // Surround a placeholder with directionality change sequence
-  return kRlm + kRlo + source.to_string() + kPdf + kRlm;
+  return (((std::string(kRlm) += kRlo) += source) += kPdf) += kRlm;
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h
index 4dedc70..2b94bcc 100644
--- a/tools/aapt2/compile/Pseudolocalizer.h
+++ b/tools/aapt2/compile/Pseudolocalizer.h
@@ -31,8 +31,8 @@
   virtual ~PseudoMethodImpl() {}
   virtual std::string Start() { return {}; }
   virtual std::string End() { return {}; }
-  virtual std::string Text(const android::StringPiece& text) = 0;
-  virtual std::string Placeholder(const android::StringPiece& text) = 0;
+  virtual std::string Text(android::StringPiece text) = 0;
+  virtual std::string Placeholder(android::StringPiece text) = 0;
 };
 
 class Pseudolocalizer {
@@ -47,7 +47,7 @@
   void SetMethod(Method method);
   std::string Start() { return impl_->Start(); }
   std::string End() { return impl_->End(); }
-  std::string Text(const android::StringPiece& text);
+  std::string Text(android::StringPiece text);
 
  private:
   std::unique_ptr<PseudoMethodImpl> impl_;
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index 6bba11e..1b03253 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -152,7 +152,7 @@
  * success, or false if the either the placeholder is not found in the name, or the value is not
  * present and the placeholder was.
  */
-bool ReplacePlaceholder(const StringPiece& placeholder, const std::optional<StringPiece>& value,
+bool ReplacePlaceholder(StringPiece placeholder, const std::optional<StringPiece>& value,
                         std::string* name, android::IDiagnostics* diag) {
   size_t offset = name->find(placeholder.data());
   bool found = (offset != std::string::npos);
@@ -338,17 +338,17 @@
   return {config};
 }
 
-const StringPiece& AbiToString(Abi abi) {
+StringPiece AbiToString(Abi abi) {
   return kAbiToStringMap.at(static_cast<size_t>(abi));
 }
 
 /**
  * Returns the common artifact base name from a template string.
  */
-std::optional<std::string> ToBaseName(std::string result, const StringPiece& apk_name,
+std::optional<std::string> ToBaseName(std::string result, StringPiece apk_name,
                                       android::IDiagnostics* diag) {
   const StringPiece ext = file::GetExtension(apk_name);
-  size_t end_index = apk_name.to_string().rfind(ext.to_string());
+  size_t end_index = apk_name.rfind(ext);
   const std::string base_name =
       (end_index != std::string::npos) ? std::string{apk_name.begin(), end_index} : "";
 
@@ -371,17 +371,17 @@
     // If no extension is specified, and the name template does not end in the current extension,
     // add the existing extension.
     if (!util::EndsWith(result, ext)) {
-      result.append(ext.to_string());
+      result.append(ext);
     }
   }
 
   return result;
 }
 
-std::optional<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& format,
-                                                              const StringPiece& apk_name,
+std::optional<std::string> ConfiguredArtifact::ToArtifactName(StringPiece format,
+                                                              StringPiece apk_name,
                                                               android::IDiagnostics* diag) const {
-  std::optional<std::string> base = ToBaseName(format.to_string(), apk_name, diag);
+  std::optional<std::string> base = ToBaseName(std::string(format), apk_name, diag);
   if (!base) {
     return {};
   }
@@ -414,7 +414,7 @@
   return result;
 }
 
-std::optional<std::string> ConfiguredArtifact::Name(const StringPiece& apk_name,
+std::optional<std::string> ConfiguredArtifact::Name(StringPiece apk_name,
                                                     android::IDiagnostics* diag) const {
   if (!name) {
     return {};
@@ -439,7 +439,7 @@
 }
 
 std::optional<std::vector<OutputArtifact>> ConfigurationParser::Parse(
-    const android::StringPiece& apk_path) {
+    android::StringPiece apk_path) {
   std::optional<PostProcessingConfiguration> maybe_config =
       ExtractConfiguration(contents_, config_path_, diag_);
   if (!maybe_config) {
@@ -447,7 +447,7 @@
   }
 
   // Convert from a parsed configuration to a list of artifacts for processing.
-  const std::string& apk_name = file::GetFilename(apk_path).to_string();
+  const std::string apk_name(file::GetFilename(apk_path));
   std::vector<OutputArtifact> output_artifacts;
 
   PostProcessingConfiguration& config = maybe_config.value();
@@ -519,7 +519,7 @@
   for (auto& node : root_element->children) {
     xml::Text* t;
     if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-      config->artifact_format = TrimWhitespace(t->text).to_string();
+      config->artifact_format.emplace(TrimWhitespace(t->text));
       break;
     }
   }
@@ -561,7 +561,7 @@
       for (auto& node : child->children) {
         xml::Text* t;
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-          auto abi = kStringToAbiMap.find(TrimWhitespace(t->text).to_string());
+          auto abi = kStringToAbiMap.find(TrimWhitespace(t->text));
           if (abi != kStringToAbiMap.end()) {
             group.push_back(abi->second);
           } else {
@@ -622,7 +622,7 @@
         xml::Text* t;
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
           ConfigDescription config_descriptor;
-          const android::StringPiece& text = TrimWhitespace(t->text);
+          android::StringPiece text = TrimWhitespace(t->text);
           bool parsed = ConfigDescription::Parse(text, &config_descriptor);
           if (parsed &&
               (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
@@ -688,7 +688,7 @@
         xml::Text* t;
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
           ConfigDescription config_descriptor;
-          const android::StringPiece& text = TrimWhitespace(t->text);
+          android::StringPiece text = TrimWhitespace(t->text);
           bool parsed = ConfigDescription::Parse(text, &config_descriptor);
           if (parsed &&
               (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
@@ -806,7 +806,7 @@
         for (auto& node : element->children) {
           xml::Text* t;
           if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-            result.texture_paths.push_back(TrimWhitespace(t->text).to_string());
+            result.texture_paths.emplace_back(TrimWhitespace(t->text));
           }
         }
       }
@@ -843,7 +843,7 @@
       for (auto& node : child->children) {
         xml::Text* t;
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-          group.push_back(TrimWhitespace(t->text).to_string());
+          group.emplace_back(TrimWhitespace(t->text));
           break;
         }
       }
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index 2c8221d..d66f4ab 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -43,7 +43,7 @@
 };
 
 /** Helper method to convert an ABI to a string representing the path within the APK. */
-const android::StringPiece& AbiToString(Abi abi);
+android::StringPiece AbiToString(Abi abi);
 
 /**
  * Represents an individual locale. When a locale is included, it must be
@@ -150,8 +150,7 @@
    * Parses the configuration file and returns the results. If the configuration could not be parsed
    * the result is empty and any errors will be displayed with the provided diagnostics context.
    */
-  std::optional<std::vector<configuration::OutputArtifact>> Parse(
-      const android::StringPiece& apk_path);
+  std::optional<std::vector<configuration::OutputArtifact>> Parse(android::StringPiece apk_path);
 
  protected:
   /**
diff --git a/tools/aapt2/configuration/ConfigurationParser.internal.h b/tools/aapt2/configuration/ConfigurationParser.internal.h
index 3028c3f..198f730 100644
--- a/tools/aapt2/configuration/ConfigurationParser.internal.h
+++ b/tools/aapt2/configuration/ConfigurationParser.internal.h
@@ -138,13 +138,12 @@
   std::optional<std::string> gl_texture_group;
 
   /** Convert an artifact name template into a name string based on configuration contents. */
-  std::optional<std::string> ToArtifactName(const android::StringPiece& format,
-                                            const android::StringPiece& apk_name,
+  std::optional<std::string> ToArtifactName(android::StringPiece format,
+                                            android::StringPiece apk_name,
                                             android::IDiagnostics* diag) const;
 
   /** Convert an artifact name template into a name string based on configuration contents. */
-  std::optional<std::string> Name(const android::StringPiece& apk_name,
-                                  android::IDiagnostics* diag) const;
+  std::optional<std::string> Name(android::StringPiece apk_name, android::IDiagnostics* diag) const;
 };
 
 /** AAPT2 XML configuration file binary representation. */
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index c4c002d..d60869a 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -1076,7 +1076,7 @@
 
   /** Adds a feature to the feature group. */
   void AddFeature(const std::string& name, bool required = true, int32_t version = -1) {
-    features_.insert(std::make_pair(name, Feature{ required, version }));
+    features_.insert_or_assign(name, Feature{required, version});
     if (required) {
       if (name == "android.hardware.camera.autofocus" ||
           name == "android.hardware.camera.flash") {
@@ -1348,6 +1348,11 @@
   std::string impliedReason;
 
   void Extract(xml::Element* element) override {
+    const auto parent_stack = extractor()->parent_stack();
+    if (!extractor()->options_.only_permissions &&
+        (parent_stack.size() != 1 || !ElementCast<Manifest>(parent_stack[0]))) {
+      return;
+    }
     name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
     std::string feature =
         GetAttributeStringDefault(FindAttribute(element, REQUIRED_FEATURE_ATTR), "");
@@ -1472,6 +1477,11 @@
   const int32_t* maxSdkVersion = nullptr;
 
   void Extract(xml::Element* element) override {
+    const auto parent_stack = extractor()->parent_stack();
+    if (!extractor()->options_.only_permissions &&
+        (parent_stack.size() != 1 || !ElementCast<Manifest>(parent_stack[0]))) {
+      return;
+    }
     name = GetAttributeString(FindAttribute(element, NAME_ATTR));
     maxSdkVersion = GetAttributeInteger(FindAttribute(element, MAX_SDK_VERSION_ATTR));
 
diff --git a/tools/aapt2/filter/AbiFilter.cpp b/tools/aapt2/filter/AbiFilter.cpp
index 9ace82a..908b171 100644
--- a/tools/aapt2/filter/AbiFilter.cpp
+++ b/tools/aapt2/filter/AbiFilter.cpp
@@ -23,15 +23,15 @@
 namespace aapt {
 
 std::unique_ptr<AbiFilter> AbiFilter::FromAbiList(const std::vector<configuration::Abi>& abi_list) {
-  std::unordered_set<std::string> abi_set;
+  std::unordered_set<std::string_view> abi_set;
   for (auto& abi : abi_list) {
-    abi_set.insert(configuration::AbiToString(abi).to_string());
+    abi_set.insert(configuration::AbiToString(abi));
   }
   // Make unique by hand as the constructor is private.
-  return std::unique_ptr<AbiFilter>(new AbiFilter(abi_set));
+  return std::unique_ptr<AbiFilter>(new AbiFilter(std::move(abi_set)));
 }
 
-bool AbiFilter::Keep(const std::string& path) {
+bool AbiFilter::Keep(std::string_view path) {
   // We only care about libraries.
   if (!util::StartsWith(path, kLibPrefix)) {
     return true;
@@ -44,7 +44,7 @@
   }
 
   // Strip the lib/ prefix.
-  const std::string& path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen);
+  const auto path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen);
   return (abis_.find(path_abi) != abis_.end());
 }
 
diff --git a/tools/aapt2/filter/AbiFilter.h b/tools/aapt2/filter/AbiFilter.h
index 2832711..7380f3f 100644
--- a/tools/aapt2/filter/AbiFilter.h
+++ b/tools/aapt2/filter/AbiFilter.h
@@ -18,7 +18,7 @@
 #define AAPT2_ABISPLITTER_H
 
 #include <memory>
-#include <string>
+#include <string_view>
 #include <unordered_set>
 #include <vector>
 
@@ -39,16 +39,16 @@
   static std::unique_ptr<AbiFilter> FromAbiList(const std::vector<configuration::Abi>& abi_list);
 
   /** Returns true if the path is for a native library in the list of desired ABIs. */
-  bool Keep(const std::string& path) override;
+  bool Keep(std::string_view path) override;
 
  private:
-  explicit AbiFilter(std::unordered_set<std::string> abis) : abis_(std::move(abis)) {
+  explicit AbiFilter(std::unordered_set<std::string_view> abis) : abis_(std::move(abis)) {
   }
 
   /** The path prefix to where all native libs end up inside an APK file. */
   static constexpr const char* kLibPrefix = "lib/";
   static constexpr size_t kLibPrefixLen = 4;
-  const std::unordered_set<std::string> abis_;
+  const std::unordered_set<std::string_view> abis_;
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/filter/Filter.h b/tools/aapt2/filter/Filter.h
index f932f9c..baf4791 100644
--- a/tools/aapt2/filter/Filter.h
+++ b/tools/aapt2/filter/Filter.h
@@ -18,6 +18,7 @@
 #define AAPT2_FILTER_H
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "util/Util.h"
@@ -30,7 +31,7 @@
   virtual ~IPathFilter() = default;
 
   /** Returns true if the path should be kept. */
-  virtual bool Keep(const std::string& path) = 0;
+  virtual bool Keep(std::string_view path) = 0;
 };
 
 /**
@@ -42,7 +43,7 @@
   }
 
   /** Returns true if the provided path matches the prefix. */
-  bool Keep(const std::string& path) override {
+  bool Keep(std::string_view path) override {
     return util::StartsWith(path, prefix_);
   }
 
@@ -59,7 +60,7 @@
   }
 
   /** Returns true if all filters keep the path. */
-  bool Keep(const std::string& path) override {
+  bool Keep(std::string_view path) override {
     for (auto& filter : filters_) {
       if (!filter->Keep(path)) {
         return false;
diff --git a/tools/aapt2/format/Archive.cpp b/tools/aapt2/format/Archive.cpp
index 80c1618..e9a93d8 100644
--- a/tools/aapt2/format/Archive.cpp
+++ b/tools/aapt2/format/Archive.cpp
@@ -40,8 +40,8 @@
  public:
   DirectoryWriter() = default;
 
-  bool Open(const StringPiece& out_dir) {
-    dir_ = out_dir.to_string();
+  bool Open(StringPiece out_dir) {
+    dir_ = std::string(out_dir);
     file::FileType type = file::GetFileType(dir_);
     if (type == file::FileType::kNonExistant) {
       error_ = "directory does not exist";
@@ -53,14 +53,14 @@
     return true;
   }
 
-  bool StartEntry(const StringPiece& path, uint32_t flags) override {
+  bool StartEntry(StringPiece path, uint32_t flags) override {
     if (file_) {
       return false;
     }
 
     std::string full_path = dir_;
     file::AppendPath(&full_path, path);
-    file::mkdirs(file::GetStem(full_path).to_string());
+    file::mkdirs(std::string(file::GetStem(full_path)));
 
     file_ = {::android::base::utf8::fopen(full_path.c_str(), "wb"), fclose};
     if (!file_) {
@@ -91,7 +91,7 @@
     return true;
   }
 
-  bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+  bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override {
     if (!StartEntry(path, flags)) {
       return false;
     }
@@ -132,8 +132,8 @@
  public:
   ZipFileWriter() = default;
 
-  bool Open(const StringPiece& path) {
-    file_ = {::android::base::utf8::fopen(path.to_string().c_str(), "w+b"), fclose};
+  bool Open(StringPiece path) {
+    file_ = {::android::base::utf8::fopen(path.data(), "w+b"), fclose};
     if (!file_) {
       error_ = SystemErrorCodeToString(errno);
       return false;
@@ -142,7 +142,7 @@
     return true;
   }
 
-  bool StartEntry(const StringPiece& path, uint32_t flags) override {
+  bool StartEntry(StringPiece path, uint32_t flags) override {
     if (!writer_) {
       return false;
     }
@@ -182,7 +182,7 @@
     return true;
   }
 
-  bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+  bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override {
     while (true) {
       if (!StartEntry(path, flags)) {
         return false;
@@ -257,7 +257,7 @@
 }  // namespace
 
 std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(android::IDiagnostics* diag,
-                                                             const StringPiece& path) {
+                                                             StringPiece path) {
   std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
   if (!writer->Open(path)) {
     diag->Error(android::DiagMessage(path) << writer->GetError());
@@ -267,7 +267,7 @@
 }
 
 std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(android::IDiagnostics* diag,
-                                                           const StringPiece& path) {
+                                                           StringPiece path) {
   std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
   if (!writer->Open(path)) {
     diag->Error(android::DiagMessage(path) << writer->GetError());
diff --git a/tools/aapt2/format/Archive.h b/tools/aapt2/format/Archive.h
index 55b0b2f..6cde753 100644
--- a/tools/aapt2/format/Archive.h
+++ b/tools/aapt2/format/Archive.h
@@ -46,12 +46,12 @@
  public:
   virtual ~IArchiveWriter() = default;
 
-  virtual bool WriteFile(const android::StringPiece& path, uint32_t flags, io::InputStream* in) = 0;
+  virtual bool WriteFile(android::StringPiece path, uint32_t flags, io::InputStream* in) = 0;
 
   // Starts a new entry and allows caller to write bytes to it sequentially.
   // Only use StartEntry if code you do not control needs to write to a CopyingOutputStream.
   // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
-  virtual bool StartEntry(const android::StringPiece& path, uint32_t flags) = 0;
+  virtual bool StartEntry(android::StringPiece path, uint32_t flags) = 0;
 
   // Called to finish writing an entry previously started by StartEntry.
   // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
@@ -70,10 +70,10 @@
 };
 
 std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(android::IDiagnostics* diag,
-                                                             const android::StringPiece& path);
+                                                             android::StringPiece path);
 
 std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(android::IDiagnostics* diag,
-                                                           const android::StringPiece& path);
+                                                           android::StringPiece path);
 
 }  // namespace aapt
 
diff --git a/tools/aapt2/format/Archive_test.cpp b/tools/aapt2/format/Archive_test.cpp
index ceed374..3c44da7 100644
--- a/tools/aapt2/format/Archive_test.cpp
+++ b/tools/aapt2/format/Archive_test.cpp
@@ -50,7 +50,7 @@
 }
 
 std::unique_ptr<IArchiveWriter> MakeZipFileWriter(const std::string& output_path) {
-  file::mkdirs(file::GetStem(output_path).to_string());
+  file::mkdirs(std::string(file::GetStem(output_path)));
   std::remove(output_path.c_str());
 
   StdErrDiagnostics diag;
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index d9e379d..75dcba5 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -373,7 +373,7 @@
   std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(type_str);
   if (!parsed_type) {
     diag_->Warn(android::DiagMessage(source_)
-                << "invalid type name '" << type_str << "' for type with ID " << type->id);
+                << "invalid type name '" << type_str << "' for type with ID " << int(type->id));
     return true;
   }
 
@@ -384,21 +384,16 @@
       continue;
     }
 
-    const ResourceName name(
-        package->name, *parsed_type,
-        android::util::GetString(key_pool_, android::util::DeviceToHost32(entry->key.index)));
+    const ResourceName name(package->name, *parsed_type,
+        android::util::GetString(key_pool_, entry->key()));
     const ResourceId res_id(package_id, type->id, static_cast<uint16_t>(it.index()));
 
     std::unique_ptr<Value> resource_value;
-    if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
-      const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
-
+    if (auto mapEntry = entry->map_entry()) {
       // TODO(adamlesinski): Check that the entry count is valid.
       resource_value = ParseMapEntry(name, config, mapEntry);
     } else {
-      const Res_value* value =
-          (const Res_value*)((const uint8_t*)entry + android::util::DeviceToHost32(entry->size));
-      resource_value = ParseValue(name, config, *value);
+      resource_value = ParseValue(name, config, entry->value());
     }
 
     if (!resource_value) {
@@ -419,7 +414,7 @@
         .SetId(res_id, OnIdConflict::CREATE_ENTRY)
         .SetAllowMangled(true);
 
-    if (entry->flags & ResTable_entry::FLAG_PUBLIC) {
+    if (entry->flags() & ResTable_entry::FLAG_PUBLIC) {
       Visibility visibility{Visibility::Level::kPublic};
 
       auto spec_flags = entry_type_spec_flags_.find(res_id);
diff --git a/tools/aapt2/format/binary/ResEntryWriter.cpp b/tools/aapt2/format/binary/ResEntryWriter.cpp
index 8832c24..9dc205f 100644
--- a/tools/aapt2/format/binary/ResEntryWriter.cpp
+++ b/tools/aapt2/format/binary/ResEntryWriter.cpp
@@ -24,11 +24,6 @@
 
 namespace aapt {
 
-using android::BigBuffer;
-using android::Res_value;
-using android::ResTable_entry;
-using android::ResTable_map;
-
 struct less_style_entries {
   bool operator()(const Style::Entry* a, const Style::Entry* b) const {
     if (a->key.id) {
@@ -189,26 +184,40 @@
 };
 
 template <typename T>
-void WriteEntry(const FlatEntry* entry, T* out_result) {
+void WriteEntry(const FlatEntry* entry, T* out_result, bool compact = false) {
   static_assert(std::is_same_v<ResTable_entry, T> || std::is_same_v<ResTable_entry_ext, T>,
                 "T must be ResTable_entry or ResTable_entry_ext");
 
   ResTable_entry* out_entry = (ResTable_entry*)out_result;
+  uint16_t flags = 0;
+
   if (entry->entry->visibility.level == Visibility::Level::kPublic) {
-    out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
+    flags |= ResTable_entry::FLAG_PUBLIC;
   }
 
   if (entry->value->IsWeak()) {
-    out_entry->flags |= ResTable_entry::FLAG_WEAK;
+    flags |= ResTable_entry::FLAG_WEAK;
   }
 
   if constexpr (std::is_same_v<ResTable_entry_ext, T>) {
-    out_entry->flags |= ResTable_entry::FLAG_COMPLEX;
+    flags |= ResTable_entry::FLAG_COMPLEX;
   }
 
-  out_entry->flags = android::util::HostToDevice16(out_entry->flags);
-  out_entry->key.index = android::util::HostToDevice32(entry->entry_key);
-  out_entry->size = android::util::HostToDevice16(sizeof(T));
+  if (!compact) {
+    out_entry->full.flags = android::util::HostToDevice16(flags);
+    out_entry->full.key.index = android::util::HostToDevice32(entry->entry_key);
+    out_entry->full.size = android::util::HostToDevice16(sizeof(T));
+  } else {
+    Res_value value;
+    CHECK(entry->entry_key < 0xffffu) << "cannot encode key in 16-bit";
+    CHECK(compact && (std::is_same_v<ResTable_entry, T>)) << "cannot encode complex entry";
+    CHECK(ValueCast<Item>(entry->value)->Flatten(&value)) << "flatten failed";
+
+    flags |= ResTable_entry::FLAG_COMPACT | (value.dataType << 8);
+    out_entry->compact.flags = android::util::HostToDevice16(flags);
+    out_entry->compact.key = android::util::HostToDevice16(entry->entry_key);
+    out_entry->compact.data = value.data;
+  }
 }
 
 int32_t WriteMapToBuffer(const FlatEntry* map_entry, BigBuffer* buffer) {
@@ -222,57 +231,26 @@
   return offset;
 }
 
-void WriteItemToPair(const FlatEntry* item_entry, ResEntryValuePair* out_pair) {
-  static_assert(sizeof(ResEntryValuePair) == sizeof(ResTable_entry) + sizeof(Res_value),
-                "ResEntryValuePair must not have padding between entry and value.");
+template <bool compact_entry, typename T>
+std::pair<int32_t, T*> WriteItemToBuffer(const FlatEntry* item_entry, BigBuffer* buffer) {
+  int32_t offset = buffer->size();
+  T* out_entry = buffer->NextBlock<T>();
 
-  WriteEntry<ResTable_entry>(item_entry, &out_pair->entry);
-
-  CHECK(ValueCast<Item>(item_entry->value)->Flatten(&out_pair->value)) << "flatten failed";
-  out_pair->value.size = android::util::HostToDevice16(sizeof(out_pair->value));
-}
-
-int32_t SequentialResEntryWriter::WriteMap(const FlatEntry* entry) {
-  return WriteMapToBuffer(entry, entries_buffer_);
-}
-
-int32_t SequentialResEntryWriter::WriteItem(const FlatEntry* entry) {
-  int32_t offset = entries_buffer_->size();
-  auto* out_pair = entries_buffer_->NextBlock<ResEntryValuePair>();
-  WriteItemToPair(entry, out_pair);
-  return offset;
-}
-
-std::size_t ResEntryValuePairContentHasher::operator()(const ResEntryValuePairRef& ref) const {
-  return android::JenkinsHashMixBytes(0, ref.ptr, sizeof(ResEntryValuePair));
-}
-
-bool ResEntryValuePairContentEqualTo::operator()(const ResEntryValuePairRef& a,
-                                                 const ResEntryValuePairRef& b) const {
-  return std::memcmp(a.ptr, b.ptr, sizeof(ResEntryValuePair)) == 0;
-}
-
-int32_t DeduplicateItemsResEntryWriter::WriteMap(const FlatEntry* entry) {
-  return WriteMapToBuffer(entry, entries_buffer_);
-}
-
-int32_t DeduplicateItemsResEntryWriter::WriteItem(const FlatEntry* entry) {
-  int32_t initial_offset = entries_buffer_->size();
-
-  auto* out_pair = entries_buffer_->NextBlock<ResEntryValuePair>();
-  WriteItemToPair(entry, out_pair);
-
-  auto ref = ResEntryValuePairRef{*out_pair};
-  auto [it, inserted] = entry_offsets.insert({ref, initial_offset});
-  if (inserted) {
-    // If inserted just return a new offset as this is a first time we store
-    // this entry.
-    return initial_offset;
+  if constexpr (compact_entry) {
+    WriteEntry(item_entry, out_entry, true);
+  } else {
+    WriteEntry(item_entry, &out_entry->entry);
+    CHECK(ValueCast<Item>(item_entry->value)->Flatten(&out_entry->value)) << "flatten failed";
+    out_entry->value.size = android::util::HostToDevice16(sizeof(out_entry->value));
   }
-  // If not inserted this means that this is a duplicate, backup allocated block to the buffer
-  // and return offset of previously stored entry.
-  entries_buffer_->BackUp(sizeof(ResEntryValuePair));
-  return it->second;
+  return {offset, out_entry};
 }
 
-}  // namespace aapt
\ No newline at end of file
+// explicitly specialize both versions
+template std::pair<int32_t, ResEntryValue<false>*> WriteItemToBuffer<false>(
+        const FlatEntry* item_entry, BigBuffer* buffer);
+
+template std::pair<int32_t, ResEntryValue<true>*> WriteItemToBuffer<true>(
+        const FlatEntry* item_entry, BigBuffer* buffer);
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/binary/ResEntryWriter.h b/tools/aapt2/format/binary/ResEntryWriter.h
index a36ceec..c11598e 100644
--- a/tools/aapt2/format/binary/ResEntryWriter.h
+++ b/tools/aapt2/format/binary/ResEntryWriter.h
@@ -27,6 +27,11 @@
 
 namespace aapt {
 
+using android::BigBuffer;
+using android::Res_value;
+using android::ResTable_entry;
+using android::ResTable_map;
+
 struct FlatEntry {
   const ResourceTableEntryView* entry;
   const Value* value;
@@ -39,28 +44,42 @@
 // We introduce this structure for ResEntryWriter to a have single allocation using
 // BigBuffer::NextBlock which allows to return it back with BigBuffer::Backup.
 struct ResEntryValuePair {
-  android::ResTable_entry entry;
-  android::Res_value value;
+  ResTable_entry entry;
+  Res_value value;
 };
 
-// References ResEntryValuePair object stored in BigBuffer used as a key in std::unordered_map.
-// Allows access to memory address where ResEntryValuePair is stored.
-union ResEntryValuePairRef {
-  const std::reference_wrapper<const ResEntryValuePair> pair;
+static_assert(sizeof(ResEntryValuePair) == sizeof(ResTable_entry) + sizeof(Res_value),
+              "ResEntryValuePair must not have padding between entry and value.");
+
+template <bool compact>
+using ResEntryValue = std::conditional_t<compact, ResTable_entry, ResEntryValuePair>;
+
+// References ResEntryValue object stored in BigBuffer used as a key in std::unordered_map.
+// Allows access to memory address where ResEntryValue is stored.
+template <bool compact>
+union ResEntryValueRef {
+  using T = ResEntryValue<compact>;
+  const std::reference_wrapper<const T> ref;
   const u_char* ptr;
 
-  explicit ResEntryValuePairRef(const ResEntryValuePair& ref) : pair(ref) {
+  explicit ResEntryValueRef(const T& rev) : ref(rev) {
   }
 };
 
-// Hasher which computes hash of ResEntryValuePair using its bytes representation in memory.
-struct ResEntryValuePairContentHasher {
-  std::size_t operator()(const ResEntryValuePairRef& ref) const;
+// Hasher which computes hash of ResEntryValue using its bytes representation in memory.
+struct ResEntryValueContentHasher {
+  template <typename R>
+  std::size_t operator()(const R& ref) const {
+    return android::JenkinsHashMixBytes(0, ref.ptr, sizeof(typename R::T));
+  }
 };
 
 // Equaler which compares ResEntryValuePairs using theirs bytes representation in memory.
-struct ResEntryValuePairContentEqualTo {
-  bool operator()(const ResEntryValuePairRef& a, const ResEntryValuePairRef& b) const;
+struct ResEntryValueContentEqualTo {
+  template <typename R>
+  bool operator()(const R& a, const R& b) const {
+    return std::memcmp(a.ptr, b.ptr, sizeof(typename R::T)) == 0;
+  }
 };
 
 // Base class that allows to write FlatEntries into entries_buffer.
@@ -79,9 +98,9 @@
   }
 
  protected:
-  ResEntryWriter(android::BigBuffer* entries_buffer) : entries_buffer_(entries_buffer) {
+  ResEntryWriter(BigBuffer* entries_buffer) : entries_buffer_(entries_buffer) {
   }
-  android::BigBuffer* entries_buffer_;
+  BigBuffer* entries_buffer_;
 
   virtual int32_t WriteItem(const FlatEntry* entry) = 0;
 
@@ -91,18 +110,29 @@
   DISALLOW_COPY_AND_ASSIGN(ResEntryWriter);
 };
 
+int32_t WriteMapToBuffer(const FlatEntry* map_entry, BigBuffer* buffer);
+
+template <bool compact_entry, typename T=ResEntryValue<compact_entry>>
+std::pair<int32_t, T*> WriteItemToBuffer(const FlatEntry* item_entry, BigBuffer* buffer);
+
 // ResEntryWriter which writes FlatEntries sequentially into entries_buffer.
 // Next entry is always written right after previous one in the buffer.
+template <bool compact_entry = false>
 class SequentialResEntryWriter : public ResEntryWriter {
  public:
-  explicit SequentialResEntryWriter(android::BigBuffer* entries_buffer)
+  explicit SequentialResEntryWriter(BigBuffer* entries_buffer)
       : ResEntryWriter(entries_buffer) {
   }
   ~SequentialResEntryWriter() override = default;
 
-  int32_t WriteItem(const FlatEntry* entry) override;
+  int32_t WriteItem(const FlatEntry* entry) override {
+      auto result = WriteItemToBuffer<compact_entry>(entry, entries_buffer_);
+      return result.first;
+  }
 
-  int32_t WriteMap(const FlatEntry* entry) override;
+  int32_t WriteMap(const FlatEntry* entry) override {
+      return WriteMapToBuffer(entry, entries_buffer_);
+  }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(SequentialResEntryWriter);
@@ -111,25 +141,44 @@
 // ResEntryWriter that writes only unique entry and value pairs into entries_buffer.
 // Next entry is written into buffer only if there is no entry with the same bytes representation
 // in memory written before. Otherwise returns offset of already written entry.
+template <bool compact_entry = false>
 class DeduplicateItemsResEntryWriter : public ResEntryWriter {
  public:
-  explicit DeduplicateItemsResEntryWriter(android::BigBuffer* entries_buffer)
+  explicit DeduplicateItemsResEntryWriter(BigBuffer* entries_buffer)
       : ResEntryWriter(entries_buffer) {
   }
   ~DeduplicateItemsResEntryWriter() override = default;
 
-  int32_t WriteItem(const FlatEntry* entry) override;
+  int32_t WriteItem(const FlatEntry* entry) override {
+    const auto& [offset, out_entry] = WriteItemToBuffer<compact_entry>(entry, entries_buffer_);
 
-  int32_t WriteMap(const FlatEntry* entry) override;
+    auto [it, inserted] = entry_offsets.insert({Ref{*out_entry}, offset});
+    if (inserted) {
+      // If inserted just return a new offset as this is a first time we store
+      // this entry
+      return offset;
+    }
+
+    // If not inserted this means that this is a duplicate, backup allocated block to the buffer
+    // and return offset of previously stored entry
+    entries_buffer_->BackUp(sizeof(*out_entry));
+    return it->second;
+  }
+
+  int32_t WriteMap(const FlatEntry* entry) override {
+      return WriteMapToBuffer(entry, entries_buffer_);
+  }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(DeduplicateItemsResEntryWriter);
 
-  std::unordered_map<ResEntryValuePairRef, int32_t, ResEntryValuePairContentHasher,
-                     ResEntryValuePairContentEqualTo>
-      entry_offsets;
+  using Ref = ResEntryValueRef<compact_entry>;
+  using Map = std::unordered_map<Ref, int32_t,
+                        ResEntryValueContentHasher,
+                        ResEntryValueContentEqualTo>;
+  Map entry_offsets;
 };
 
 }  // namespace aapt
 
-#endif
\ No newline at end of file
+#endif
diff --git a/tools/aapt2/format/binary/ResEntryWriter_test.cpp b/tools/aapt2/format/binary/ResEntryWriter_test.cpp
index 56ca133..4cb17c3 100644
--- a/tools/aapt2/format/binary/ResEntryWriter_test.cpp
+++ b/tools/aapt2/format/binary/ResEntryWriter_test.cpp
@@ -56,14 +56,28 @@
           .AddSimple("com.app.test:id/id3", ResourceId(0x7f010002))
           .Build();
 
-  BigBuffer out(512);
-  SequentialResEntryWriter writer(&out);
-  auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+  {
+    BigBuffer out(512);
+    SequentialResEntryWriter<false> writer(&out);
+    auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
 
-  std::vector<int32_t> expected_offsets{0, sizeof(ResEntryValuePair),
-                                        2 * sizeof(ResEntryValuePair)};
-  EXPECT_EQ(out.size(), 3 * sizeof(ResEntryValuePair));
-  EXPECT_EQ(offsets, expected_offsets);
+    std::vector<int32_t> expected_offsets{0, sizeof(ResEntryValuePair),
+                                          2 * sizeof(ResEntryValuePair)};
+    EXPECT_EQ(out.size(), 3 * sizeof(ResEntryValuePair));
+    EXPECT_EQ(offsets, expected_offsets);
+  }
+
+  {
+    /* expect a compact entry to only take sizeof(ResTable_entry) */
+    BigBuffer out(512);
+    SequentialResEntryWriter<true> writer(&out);
+    auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+    std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry),
+                                          2 * sizeof(ResTable_entry)};
+    EXPECT_EQ(out.size(), 3 * sizeof(ResTable_entry));
+    EXPECT_EQ(offsets, expected_offsets);
+  }
 };
 
 TEST_F(SequentialResEntryWriterTest, WriteMapEntriesOneByOne) {
@@ -83,13 +97,26 @@
                                              .AddValue("com.app.test:array/arr2", std::move(array2))
                                              .Build();
 
-  BigBuffer out(512);
-  SequentialResEntryWriter writer(&out);
-  auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+  {
+    BigBuffer out(512);
+    SequentialResEntryWriter<false> writer(&out);
+    auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
 
-  std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
-  EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
-  EXPECT_EQ(offsets, expected_offsets);
+    std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+    EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+    EXPECT_EQ(offsets, expected_offsets);
+  }
+
+  {
+    /* compact_entry should have no impact to map items */
+    BigBuffer out(512);
+    SequentialResEntryWriter<true> writer(&out);
+    auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+    std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+    EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+    EXPECT_EQ(offsets, expected_offsets);
+  }
 };
 
 TEST_F(DeduplicateItemsResEntryWriterTest, DeduplicateItemEntries) {
@@ -100,13 +127,26 @@
           .AddSimple("com.app.test:id/id3", ResourceId(0x7f010002))
           .Build();
 
-  BigBuffer out(512);
-  DeduplicateItemsResEntryWriter writer(&out);
-  auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+  {
+    BigBuffer out(512);
+    DeduplicateItemsResEntryWriter<false> writer(&out);
+    auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
 
-  std::vector<int32_t> expected_offsets{0, 0, 0};
-  EXPECT_EQ(out.size(), sizeof(ResEntryValuePair));
-  EXPECT_EQ(offsets, expected_offsets);
+    std::vector<int32_t> expected_offsets{0, 0, 0};
+    EXPECT_EQ(out.size(), sizeof(ResEntryValuePair));
+    EXPECT_EQ(offsets, expected_offsets);
+  }
+
+  {
+    /* expect a compact entry to only take sizeof(ResTable_entry) */
+    BigBuffer out(512);
+    DeduplicateItemsResEntryWriter<true> writer(&out);
+    auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+    std::vector<int32_t> expected_offsets{0, 0, 0};
+    EXPECT_EQ(out.size(), sizeof(ResTable_entry));
+    EXPECT_EQ(offsets, expected_offsets);
+  }
 };
 
 TEST_F(DeduplicateItemsResEntryWriterTest, WriteMapEntriesOneByOne) {
@@ -126,13 +166,26 @@
                                              .AddValue("com.app.test:array/arr2", std::move(array2))
                                              .Build();
 
-  BigBuffer out(512);
-  DeduplicateItemsResEntryWriter writer(&out);
-  auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+  {
+    BigBuffer out(512);
+    DeduplicateItemsResEntryWriter<false> writer(&out);
+    auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
 
-  std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
-  EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
-  EXPECT_EQ(offsets, expected_offsets);
-};
+    std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+    EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+    EXPECT_EQ(offsets, expected_offsets);
+  }
+
+  {
+    /* compact_entry should have no impact to map items */
+    BigBuffer out(512);
+    DeduplicateItemsResEntryWriter<true> writer(&out);
+    auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+    std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+    EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+    EXPECT_EQ(offsets, expected_offsets);
+  }
+ };
 
 }  // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 318b8b6..8c594ba 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -16,6 +16,7 @@
 
 #include "format/binary/TableFlattener.h"
 
+#include <limits>
 #include <sstream>
 #include <type_traits>
 #include <variant>
@@ -31,6 +32,7 @@
 #include "format/binary/ChunkWriter.h"
 #include "format/binary/ResEntryWriter.h"
 #include "format/binary/ResourceTypeExtensions.h"
+#include "optimize/Obfuscator.h"
 #include "trace/TraceBuffer.h"
 
 using namespace android;
@@ -67,7 +69,9 @@
  public:
   PackageFlattener(IAaptContext* context, const ResourceTablePackageView& package,
                    const std::map<size_t, std::string>* shared_libs,
-                   SparseEntriesMode sparse_entries, bool collapse_key_stringpool,
+                   SparseEntriesMode sparse_entries,
+                   bool compact_entries,
+                   bool collapse_key_stringpool,
                    const std::set<ResourceName>& name_collapse_exemptions,
                    bool deduplicate_entry_values)
       : context_(context),
@@ -75,6 +79,7 @@
         package_(package),
         shared_libs_(shared_libs),
         sparse_entries_(sparse_entries),
+        compact_entries_(compact_entries),
         collapse_key_stringpool_(collapse_key_stringpool),
         name_collapse_exemptions_(name_collapse_exemptions),
         deduplicate_entry_values_(deduplicate_entry_values) {
@@ -135,6 +140,33 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(PackageFlattener);
 
+  // Use compact entries only if
+  // 1) it is enabled, and that
+  // 2) the entries will be accessed on platforms U+, and
+  // 3) all entry keys can be encoded in 16 bits
+  bool UseCompactEntries(const ConfigDescription& config, std::vector<FlatEntry>* entries) const {
+    return compact_entries_ &&
+        (context_->GetMinSdkVersion() > SDK_TIRAMISU || config.sdkVersion > SDK_TIRAMISU) &&
+        std::none_of(entries->cbegin(), entries->cend(),
+          [](const auto& e) { return e.entry_key >= std::numeric_limits<uint16_t>::max(); });
+  }
+
+  std::unique_ptr<ResEntryWriter> GetResEntryWriter(bool dedup, bool compact, BigBuffer* buffer) {
+    if (dedup) {
+      if (compact) {
+        return std::make_unique<DeduplicateItemsResEntryWriter<true>>(buffer);
+      } else {
+        return std::make_unique<DeduplicateItemsResEntryWriter<false>>(buffer);
+      }
+    } else {
+      if (compact) {
+        return std::make_unique<SequentialResEntryWriter<true>>(buffer);
+      } else {
+        return std::make_unique<SequentialResEntryWriter<false>>(buffer);
+      }
+    }
+  }
+
   bool FlattenConfig(const ResourceTableTypeView& type, const ConfigDescription& config,
                      const size_t num_total_entries, std::vector<FlatEntry>* entries,
                      BigBuffer* buffer) {
@@ -150,21 +182,20 @@
     std::vector<uint32_t> offsets;
     offsets.resize(num_total_entries, 0xffffffffu);
 
+    bool compact_entry = UseCompactEntries(config, entries);
+
     android::BigBuffer values_buffer(512);
-    std::variant<std::monostate, DeduplicateItemsResEntryWriter, SequentialResEntryWriter>
-        writer_variant;
-    ResEntryWriter* res_entry_writer;
-    if (deduplicate_entry_values_) {
-      res_entry_writer = &writer_variant.emplace<DeduplicateItemsResEntryWriter>(&values_buffer);
-    } else {
-      res_entry_writer = &writer_variant.emplace<SequentialResEntryWriter>(&values_buffer);
-    }
+    auto res_entry_writer = GetResEntryWriter(deduplicate_entry_values_,
+                                              compact_entry, &values_buffer);
 
     for (FlatEntry& flat_entry : *entries) {
       CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
       offsets[flat_entry.entry->id.value()] = res_entry_writer->Write(&flat_entry);
     }
 
+    // whether the offsets can be represented in 2 bytes
+    bool short_offsets = (values_buffer.size() / 4u) < std::numeric_limits<uint16_t>::max();
+
     bool sparse_encode = sparse_entries_ == SparseEntriesMode::Enabled ||
                          sparse_entries_ == SparseEntriesMode::Forced;
 
@@ -177,8 +208,7 @@
     }
 
     // Only sparse encode if the offsets are representable in 2 bytes.
-    sparse_encode =
-        sparse_encode && (values_buffer.size() / 4u) <= std::numeric_limits<uint16_t>::max();
+    sparse_encode = sparse_encode && short_offsets;
 
     // Only sparse encode if the ratio of populated entries to total entries is below some
     // threshold.
@@ -200,12 +230,22 @@
       }
     } else {
       type_header->entryCount = android::util::HostToDevice32(num_total_entries);
-      uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries);
-      for (size_t i = 0; i < num_total_entries; i++) {
-        indices[i] = android::util::HostToDevice32(offsets[i]);
+      if (compact_entry && short_offsets) {
+        // use 16-bit offset only when compact_entry is true
+        type_header->flags |= ResTable_type::FLAG_OFFSET16;
+        uint16_t* indices = type_writer.NextBlock<uint16_t>(num_total_entries);
+        for (size_t i = 0; i < num_total_entries; i++) {
+          indices[i] = android::util::HostToDevice16(offsets[i] / 4u);
+        }
+      } else {
+        uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries);
+        for (size_t i = 0; i < num_total_entries; i++) {
+          indices[i] = android::util::HostToDevice32(offsets[i]);
+        }
       }
     }
 
+    type_writer.buffer()->Align4();
     type_header->entriesStart = android::util::HostToDevice32(type_writer.size());
     type_writer.buffer()->AppendBuffer(std::move(values_buffer));
     type_writer.Finish();
@@ -427,9 +467,6 @@
       // table.
       std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map;
 
-      // hardcoded string uses characters which make it an invalid resource name
-      const std::string obfuscated_resource_name = "0_resource_name_obfuscated";
-
       for (const ResourceTableEntryView& entry : type.entries) {
         if (entry.staged_id) {
           aliases_.insert(std::make_pair(
@@ -438,30 +475,31 @@
         }
 
         uint32_t local_key_index;
-        ResourceName resource_name({}, type.named_type, entry.name);
-        if (!collapse_key_stringpool_ ||
-            name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) {
-          local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
-        } else {
-          // resource isn't exempt from collapse, add it as obfuscated value
-          if (entry.overlayable_item) {
+        auto onObfuscate = [this, &local_key_index, &entry](Obfuscator::Result obfuscatedResult,
+                                                            const ResourceName& resource_name) {
+          if (obfuscatedResult == Obfuscator::Result::Keep_ExemptionList) {
+            local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
+          } else if (obfuscatedResult == Obfuscator::Result::Keep_Overlayable) {
             // if the resource name of the specific entry is obfuscated and this
             // entry is in the overlayable list, the overlay can't work on this
             // overlayable at runtime because the name has been obfuscated in
             // resources.arsc during flatten operation.
             const OverlayableItem& item = entry.overlayable_item.value();
             context_->GetDiagnostics()->Warn(android::DiagMessage(item.overlayable->source)
-                                             << "The resource name of overlayable entry "
-                                             << resource_name.to_string() << "'"
-                                             << " shouldn't be obfuscated in resources.arsc");
+                                             << "The resource name of overlayable entry '"
+                                             << resource_name.to_string()
+                                             << "' shouldn't be obfuscated in resources.arsc");
 
             local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
           } else {
-            // TODO(b/228192695): output the entry.name and Resource id to make
-            //  de-obfuscated possible.
-            local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
+            local_key_index =
+                (uint32_t)key_pool_.MakeRef(Obfuscator::kObfuscatedResourceName).index();
           }
-        }
+        };
+
+        Obfuscator::ObfuscateResourceName(collapse_key_stringpool_, name_collapse_exemptions_,
+                                          type.named_type, entry, onObfuscate);
+
         // Group values by configuration.
         for (auto& config_value : entry.values) {
           config_to_entry_list_map[config_value->config].push_back(
@@ -512,6 +550,7 @@
   const ResourceTablePackageView package_;
   const std::map<size_t, std::string>* shared_libs_;
   SparseEntriesMode sparse_entries_;
+  bool compact_entries_;
   android::StringPool type_pool_;
   android::StringPool key_pool_;
   bool collapse_key_stringpool_;
@@ -568,7 +607,9 @@
     }
 
     PackageFlattener flattener(context, package, &table->included_packages_,
-                               options_.sparse_entries, options_.collapse_key_stringpool,
+                               options_.sparse_entries,
+                               options_.use_compact_entries,
+                               options_.collapse_key_stringpool,
                                options_.name_collapse_exemptions,
                                options_.deduplicate_entry_values);
     if (!flattener.FlattenPackage(&package_buffer)) {
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 6151b7e..60605d2 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -14,8 +14,13 @@
  * limitations under the License.
  */
 
-#ifndef AAPT_FORMAT_BINARY_TABLEFLATTENER_H
-#define AAPT_FORMAT_BINARY_TABLEFLATTENER_H
+#ifndef TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
+#define TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_map>
 
 #include "Resource.h"
 #include "ResourceTable.h"
@@ -44,6 +49,9 @@
   // as a sparse map of entry ID and offset to actual data.
   SparseEntriesMode sparse_entries = SparseEntriesMode::Disabled;
 
+  // When true, use compact entries for simple data
+  bool use_compact_entries = false;
+
   // When true, the key string pool in the final ResTable
   // is collapsed to a single entry. All resource entries
   // have name indices that point to this single value
@@ -68,6 +76,9 @@
   //
   // This applies only to simple entries (entry->flags & ResTable_entry::FLAG_COMPLEX == 0).
   bool deduplicate_entry_values = false;
+
+  // Map from original resource ids to obfuscated names.
+  std::unordered_map<uint32_t, std::string> id_resource_map;
 };
 
 class TableFlattener : public IResourceTableConsumer {
@@ -79,12 +90,12 @@
   bool Consume(IAaptContext* context, ResourceTable* table) override;
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(TableFlattener);
-
   TableFlattenerOptions options_;
   android::BigBuffer* buffer_;
+
+  DISALLOW_COPY_AND_ASSIGN(TableFlattener);
 };
 
 }  // namespace aapt
 
-#endif /* AAPT_FORMAT_BINARY_TABLEFLATTENER_H */
+#endif  // TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index d08b4a3..0f11685 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -84,7 +84,7 @@
     return ::testing::AssertionSuccess();
   }
 
-  ::testing::AssertionResult Exists(ResTable* table, const StringPiece& expected_name,
+  ::testing::AssertionResult Exists(ResTable* table, StringPiece expected_name,
                                     const ResourceId& expected_id,
                                     const ConfigDescription& expected_config,
                                     const uint8_t expected_data_type, const uint32_t expected_data,
diff --git a/tools/aapt2/format/binary/XmlFlattener.cpp b/tools/aapt2/format/binary/XmlFlattener.cpp
index 983e646..05f9751 100644
--- a/tools/aapt2/format/binary/XmlFlattener.cpp
+++ b/tools/aapt2/format/binary/XmlFlattener.cpp
@@ -79,7 +79,7 @@
   }
 
   void Visit(const xml::Text* node) override {
-    std::string text = util::TrimWhitespace(node->text).to_string();
+    std::string text(util::TrimWhitespace(node->text));
 
     // Skip whitespace only text nodes.
     if (text.empty()) {
@@ -88,10 +88,10 @@
 
     // Compact leading and trailing whitespace into a single space
     if (isspace(node->text[0])) {
-      text = ' ' + text;
+      text.insert(text.begin(), ' ');
     }
-    if (isspace(node->text[node->text.length() - 1])) {
-      text = text + ' ';
+    if (isspace(node->text.back())) {
+      text += ' ';
     }
 
     ChunkWriter writer(buffer_);
@@ -165,7 +165,7 @@
   // We are adding strings to a StringPool whose strings will be sorted and merged with other
   // string pools. That means we can't encode the ID of a string directly. Instead, we defer the
   // writing of the ID here, until after the StringPool is merged and sorted.
-  void AddString(const StringPiece& str, uint32_t priority, android::ResStringPool_ref* dest,
+  void AddString(StringPiece str, uint32_t priority, android::ResStringPool_ref* dest,
                  bool treat_empty_string_as_null = false) {
     if (str.empty() && treat_empty_string_as_null) {
       // Some parts of the runtime treat null differently than empty string.
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 6a1e8c1..e39f327 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -562,6 +562,11 @@
     }
   }
 
+  for (const pb::DynamicRefTable& dynamic_ref : pb_table.dynamic_ref_table()) {
+    out_table->included_packages_.insert(
+        {dynamic_ref.package_id().id(), dynamic_ref.package_name()});
+  }
+
   // Deserialize the overlayable groups of the table
   std::vector<std::shared_ptr<Overlayable>> overlayables;
   for (const pb::Overlayable& pb_overlayable : pb_table.overlayable()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 163a60a..0e40124 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -18,6 +18,7 @@
 
 #include "ValueVisitor.h"
 #include "androidfw/BigBuffer.h"
+#include "optimize/Obfuscator.h"
 
 using android::ConfigDescription;
 
@@ -345,7 +346,11 @@
   pb::ToolFingerprint* pb_fingerprint = out_table->add_tool_fingerprint();
   pb_fingerprint->set_tool(util::GetToolName());
   pb_fingerprint->set_version(util::GetToolFingerprint());
-
+  for (auto it = table.included_packages_.begin(); it != table.included_packages_.end(); ++it) {
+    pb::DynamicRefTable* pb_dynamic_ref = out_table->add_dynamic_ref_table();
+    pb_dynamic_ref->mutable_package_id()->set_id(it->first);
+    pb_dynamic_ref->set_package_name(it->second);
+  }
   std::vector<Overlayable*> overlayables;
   auto table_view = table.GetPartitionedView();
   for (const auto& package : table_view.packages) {
@@ -362,21 +367,21 @@
       }
       pb_type->set_name(type.named_type.to_string());
 
-      // hardcoded string uses characters which make it an invalid resource name
-      static const char* obfuscated_resource_name = "0_resource_name_obfuscated";
       for (const auto& entry : type.entries) {
         pb::Entry* pb_entry = pb_type->add_entry();
         if (entry.id) {
           pb_entry->mutable_entry_id()->set_id(entry.id.value());
         }
-        ResourceName resource_name({}, type.named_type, entry.name);
-        if (options.collapse_key_stringpool &&
-            options.name_collapse_exemptions.find(resource_name) ==
-            options.name_collapse_exemptions.end()) {
-          pb_entry->set_name(obfuscated_resource_name);
-        } else {
-          pb_entry->set_name(entry.name);
-        }
+        auto onObfuscate = [pb_entry, &entry](Obfuscator::Result obfuscatedResult,
+                                              const ResourceName& resource_name) {
+          pb_entry->set_name(obfuscatedResult == Obfuscator::Result::Obfuscated
+                                 ? Obfuscator::kObfuscatedResourceName
+                                 : entry.name);
+        };
+
+        Obfuscator::ObfuscateResourceName(options.collapse_key_stringpool,
+                                          options.name_collapse_exemptions, type.named_type, entry,
+                                          onObfuscate);
 
         // Write the Visibility struct.
         pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 692fa42..ecfdba8 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -35,7 +35,7 @@
 
 class MockFileCollection : public io::IFileCollection {
  public:
-  MOCK_METHOD1(FindFile, io::IFile*(const StringPiece& path));
+  MOCK_METHOD1(FindFile, io::IFile*(StringPiece path));
   MOCK_METHOD0(Iterator, std::unique_ptr<io::IFileCollectionIterator>());
   MOCK_METHOD0(GetDirSeparator, char());
 };
@@ -491,7 +491,7 @@
   EXPECT_THAT(bp->value.data, Eq(ResourceUtils::MakeEmpty()->value.data));
 }
 
-static void ExpectConfigSerializes(const StringPiece& config_str) {
+static void ExpectConfigSerializes(StringPiece config_str) {
   const ConfigDescription expected_config = test::ParseConfigOrDie(config_str);
   pb::Configuration pb_config;
   SerializeConfig(expected_config, &pb_config);
@@ -1024,4 +1024,28 @@
   EXPECT_THAT(*(custom_layout->path), Eq("res/layout/bar.xml"));
 }
 
+TEST(ProtoSerializeTest, SerializeDynamicRef) {
+  std::unique_ptr<IAaptContext> context =
+      test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build();
+  std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder().Build();
+  table->included_packages_.insert({20, "foobar"});
+  table->included_packages_.insert({30, "barfoo"});
+
+  ResourceTable new_table;
+  pb::ResourceTable pb_table;
+  MockFileCollection files;
+  std::string error;
+  SerializeTableToPb(*table, &pb_table, context->GetDiagnostics());
+  ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
+  EXPECT_THAT(error, IsEmpty());
+
+  int result = new_table.included_packages_.size();
+  EXPECT_THAT(result, Eq(2));
+  auto it = new_table.included_packages_.begin();
+  EXPECT_THAT(it->first, Eq(20));
+  EXPECT_THAT(it->second, Eq("foobar"));
+  it++;
+  EXPECT_THAT(it->first, Eq(30));
+  EXPECT_THAT(it->second, Eq("barfoo"));
+}
 }  // namespace aapt
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 422658a..08d497d 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -101,7 +101,7 @@
  public:
   virtual ~IFileCollection() = default;
 
-  virtual IFile* FindFile(const android::StringPiece& path) = 0;
+  virtual IFile* FindFile(android::StringPiece path) = 0;
   virtual std::unique_ptr<IFileCollectionIterator> Iterator() = 0;
   virtual char GetDirSeparator() = 0;
 };
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index 3f071af..a64982a 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -67,8 +67,8 @@
   return result;
 }
 
-std::unique_ptr<FileCollection> FileCollection::Create(const android::StringPiece& root,
-                                                        std::string* outError) {
+std::unique_ptr<FileCollection> FileCollection::Create(android::StringPiece root,
+                                                       std::string* outError) {
   std::unique_ptr<FileCollection> collection =
       std::unique_ptr<FileCollection>(new FileCollection());
 
@@ -80,7 +80,7 @@
 
   std::vector<std::string> sorted_files;
   while (struct dirent *entry = readdir(d.get())) {
-    std::string prefix_path = root.to_string();
+    std::string prefix_path(root);
     file::AppendPath(&prefix_path, entry->d_name);
 
     // The directory to iterate over looking for files
@@ -117,12 +117,19 @@
   return collection;
 }
 
-IFile* FileCollection::InsertFile(const StringPiece& path) {
-  return (files_[path.to_string()] = util::make_unique<RegularFile>(android::Source(path))).get();
+IFile* FileCollection::InsertFile(StringPiece path) {
+  auto file = util::make_unique<RegularFile>(android::Source(path));
+  auto it = files_.lower_bound(path);
+  if (it != files_.end() && it->first == path) {
+    it->second = std::move(file);
+  } else {
+    it = files_.emplace_hint(it, path, std::move(file));
+  }
+  return it->second.get();
 }
 
-IFile* FileCollection::FindFile(const StringPiece& path) {
-  auto iter = files_.find(path.to_string());
+IFile* FileCollection::FindFile(StringPiece path) {
+  auto iter = files_.find(path);
   if (iter != files_.end()) {
     return iter->second.get();
   }
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index bc03b9b..0e798fc 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -60,12 +60,11 @@
   FileCollection() = default;
 
   /** Creates a file collection containing all files contained in the specified root directory. */
-  static std::unique_ptr<FileCollection> Create(const android::StringPiece& path,
-                                                std::string* outError);
+  static std::unique_ptr<FileCollection> Create(android::StringPiece path, std::string* outError);
 
   // Adds a file located at path. Returns the IFile representation of that file.
-  IFile* InsertFile(const android::StringPiece& path);
-  IFile* FindFile(const android::StringPiece& path) override;
+  IFile* InsertFile(android::StringPiece path);
+  IFile* FindFile(android::StringPiece path) override;
   std::unique_ptr<IFileCollectionIterator> Iterator() override;
   char GetDirSeparator() override;
 
@@ -74,7 +73,7 @@
 
   friend class FileCollectionIterator;
 
-  std::map<std::string, std::unique_ptr<IFile>> files_;
+  std::map<std::string, std::unique_ptr<IFile>, std::less<>> files_;
 };
 
 }  // namespace io
diff --git a/tools/aapt2/io/StringStream.cpp b/tools/aapt2/io/StringStream.cpp
index 4ca04a8..9c49788 100644
--- a/tools/aapt2/io/StringStream.cpp
+++ b/tools/aapt2/io/StringStream.cpp
@@ -21,7 +21,7 @@
 namespace aapt {
 namespace io {
 
-StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) {
+StringInputStream::StringInputStream(StringPiece str) : str_(str), offset_(0u) {
 }
 
 bool StringInputStream::Next(const void** data, size_t* size) {
diff --git a/tools/aapt2/io/StringStream.h b/tools/aapt2/io/StringStream.h
index f29890a..f7bdecca 100644
--- a/tools/aapt2/io/StringStream.h
+++ b/tools/aapt2/io/StringStream.h
@@ -29,7 +29,7 @@
 
 class StringInputStream : public KnownSizeInputStream {
  public:
-  explicit StringInputStream(const android::StringPiece& str);
+  explicit StringInputStream(android::StringPiece str);
 
   bool Next(const void** data, size_t* size) override;
 
diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp
index afe54d4..79d8d52 100644
--- a/tools/aapt2/io/Util.cpp
+++ b/tools/aapt2/io/Util.cpp
@@ -26,7 +26,7 @@
 namespace aapt {
 namespace io {
 
-bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std::string& out_path,
+bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path,
                               uint32_t compression_flags, IArchiveWriter* writer) {
   TRACE_CALL();
   if (context->IsVerbose()) {
@@ -43,7 +43,7 @@
   return true;
 }
 
-bool CopyFileToArchive(IAaptContext* context, io::IFile* file, const std::string& out_path,
+bool CopyFileToArchive(IAaptContext* context, io::IFile* file, std::string_view out_path,
                        uint32_t compression_flags, IArchiveWriter* writer) {
   TRACE_CALL();
   std::unique_ptr<io::IData> data = file->OpenAsData();
@@ -56,13 +56,13 @@
 }
 
 bool CopyFileToArchivePreserveCompression(IAaptContext* context, io::IFile* file,
-                                          const std::string& out_path, IArchiveWriter* writer) {
+                                          std::string_view out_path, IArchiveWriter* writer) {
   uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
   return CopyFileToArchive(context, file, out_path, compression_flags, writer);
 }
 
 bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg,
-                        const std::string& out_path, uint32_t compression_flags,
+                        std::string_view out_path, uint32_t compression_flags,
                         IArchiveWriter* writer) {
   TRACE_CALL();
   if (context->IsVerbose()) {
@@ -110,7 +110,7 @@
   return !in->HadError();
 }
 
-bool Copy(OutputStream* out, const StringPiece& in) {
+bool Copy(OutputStream* out, StringPiece in) {
   const char* in_buffer = in.data();
   size_t in_len = in.size();
   while (in_len != 0) {
diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h
index 1b48a28..685f522 100644
--- a/tools/aapt2/io/Util.h
+++ b/tools/aapt2/io/Util.h
@@ -17,12 +17,11 @@
 #ifndef AAPT_IO_UTIL_H
 #define AAPT_IO_UTIL_H
 
-#include <string>
-
-#include "google/protobuf/message.h"
-#include "google/protobuf/io/coded_stream.h"
+#include <string_view>
 
 #include "format/Archive.h"
+#include "google/protobuf/io/coded_stream.h"
+#include "google/protobuf/message.h"
 #include "io/File.h"
 #include "io/Io.h"
 #include "process/IResourceTableConsumer.h"
@@ -30,23 +29,23 @@
 namespace aapt {
 namespace io {
 
-bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std::string& out_path,
+bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path,
                               uint32_t compression_flags, IArchiveWriter* writer);
 
-bool CopyFileToArchive(IAaptContext* context, IFile* file, const std::string& out_path,
+bool CopyFileToArchive(IAaptContext* context, IFile* file, std::string_view out_path,
                        uint32_t compression_flags, IArchiveWriter* writer);
 
 bool CopyFileToArchivePreserveCompression(IAaptContext* context, IFile* file,
-                                          const std::string& out_path, IArchiveWriter* writer);
+                                          std::string_view out_path, IArchiveWriter* writer);
 
 bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg,
-                        const std::string& out_path, uint32_t compression_flags,
+                        std::string_view out_path, uint32_t compression_flags,
                         IArchiveWriter* writer);
 
 // Copies the data from in to out. Returns false if there was an error.
 // If there was an error, check the individual streams' HadError/GetError methods.
 bool Copy(OutputStream* out, InputStream* in);
-bool Copy(OutputStream* out, const ::android::StringPiece& in);
+bool Copy(OutputStream* out, android::StringPiece in);
 bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in);
 
 class OutputStreamAdaptor : public io::OutputStream {
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index 400269c..4a5385d 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -91,8 +91,8 @@
 
 ZipFileCollection::ZipFileCollection() : handle_(nullptr) {}
 
-std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(
-    const StringPiece& path, std::string* out_error) {
+std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(StringPiece path,
+                                                             std::string* out_error) {
   TRACE_CALL();
   constexpr static const int32_t kEmptyArchive = -6;
 
@@ -130,8 +130,8 @@
       continue;
     }
 
-    std::unique_ptr<IFile> file = util::make_unique<ZipFile>(
-        collection->handle_, zip_data, android::Source(zip_entry_path, path.to_string()));
+    std::unique_ptr<IFile> file = util::make_unique<ZipFile>(collection->handle_, zip_data,
+                                                             android::Source(zip_entry_path, path));
     collection->files_by_name_[zip_entry_path] = file.get();
     collection->files_.push_back(std::move(file));
   }
@@ -144,8 +144,8 @@
   return collection;
 }
 
-IFile* ZipFileCollection::FindFile(const StringPiece& path) {
-  auto iter = files_by_name_.find(path.to_string());
+IFile* ZipFileCollection::FindFile(StringPiece path) {
+  auto iter = files_by_name_.find(path);
   if (iter != files_by_name_.end()) {
     return iter->second;
   }
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index 78c9c21..c263aa4 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -61,10 +61,10 @@
 // An IFileCollection that represents a ZIP archive and the entries within it.
 class ZipFileCollection : public IFileCollection {
  public:
-  static std::unique_ptr<ZipFileCollection> Create(const android::StringPiece& path,
+  static std::unique_ptr<ZipFileCollection> Create(android::StringPiece path,
                                                    std::string* outError);
 
-  io::IFile* FindFile(const android::StringPiece& path) override;
+  io::IFile* FindFile(android::StringPiece path) override;
   std::unique_ptr<IFileCollectionIterator> Iterator() override;
   char GetDirSeparator() override;
 
@@ -76,7 +76,7 @@
 
   ZipArchiveHandle handle_;
   std::vector<std::unique_ptr<IFile>> files_;
-  std::map<std::string, IFile*> files_by_name_;
+  std::map<std::string, IFile*, std::less<>> files_by_name_;
 };
 
 }  // namespace io
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index 482d91a..87da09a 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -30,7 +30,7 @@
 
 namespace aapt {
 
-StringPiece AnnotationProcessor::ExtractFirstSentence(const StringPiece& comment) {
+StringPiece AnnotationProcessor::ExtractFirstSentence(StringPiece comment) {
   Utf8Iterator iter(comment);
   while (iter.HasNext()) {
     const char32_t codepoint = iter.Next();
@@ -62,7 +62,7 @@
 }};
 
 void AnnotationProcessor::AppendCommentLine(std::string comment) {
-  static const std::string sDeprecated = "@deprecated";
+  static constexpr std::string_view sDeprecated = "@deprecated";
 
   // Treat deprecated specially, since we don't remove it from the source comment.
   if (comment.find(sDeprecated) != std::string::npos) {
@@ -74,7 +74,7 @@
     if (idx != std::string::npos) {
       // Captures all parameters associated with the specified annotation rule
       // by matching the first pair of parantheses after the rule.
-      std::regex re(rule.doc_str.to_string() + "\\s*\\((.+)\\)");
+      std::regex re(std::string(rule.doc_str) += "\\s*\\((.+)\\)");
       std::smatch match_result;
       const bool is_match = std::regex_search(comment, match_result, re);
       // We currently only capture and preserve parameters for SystemApi.
@@ -97,7 +97,7 @@
 
   // If there was trimming to do, copy the string.
   if (trimmed.size() != comment.size()) {
-    comment = trimmed.to_string();
+    comment = std::string(trimmed);
   }
 
   if (!has_comments_) {
@@ -107,12 +107,12 @@
   comment_ << "\n * " << std::move(comment);
 }
 
-void AnnotationProcessor::AppendComment(const StringPiece& comment) {
+void AnnotationProcessor::AppendComment(StringPiece comment) {
   // We need to process line by line to clean-up whitespace and append prefixes.
   for (StringPiece line : util::Tokenize(comment, '\n')) {
     line = util::TrimWhitespace(line);
     if (!line.empty()) {
-      AppendCommentLine(line.to_string());
+      AppendCommentLine(std::string(line));
     }
   }
 }
@@ -126,7 +126,7 @@
 void AnnotationProcessor::Print(Printer* printer, bool strip_api_annotations) const {
   if (has_comments_) {
     std::string result = comment_.str();
-    for (const StringPiece& line : util::Tokenize(result, '\n')) {
+    for (StringPiece line : util::Tokenize(result, '\n')) {
       printer->Println(line);
     }
     printer->Println(" */");
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index f217afb..db3437e 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -56,11 +56,11 @@
   // Extracts the first sentence of a comment. The algorithm selects the substring starting from
   // the beginning of the string, and ending at the first '.' character that is followed by a
   // whitespace character. If these requirements are not met, the whole string is returned.
-  static android::StringPiece ExtractFirstSentence(const android::StringPiece& comment);
+  static android::StringPiece ExtractFirstSentence(android::StringPiece comment);
 
   // Adds more comments. Resources can have value definitions for various configurations, and
   // each of the definitions may have comments that need to be processed.
-  void AppendComment(const android::StringPiece& comment);
+  void AppendComment(android::StringPiece comment);
 
   void AppendNewLine();
 
diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp
index 3163497..98f3bd2 100644
--- a/tools/aapt2/java/ClassDefinition.cpp
+++ b/tools/aapt2/java/ClassDefinition.cpp
@@ -27,8 +27,8 @@
   processor_.Print(printer, strip_api_annotations);
 }
 
-void MethodDefinition::AppendStatement(const StringPiece& statement) {
-  statements_.push_back(statement.to_string());
+void MethodDefinition::AppendStatement(StringPiece statement) {
+  statements_.emplace_back(statement);
 }
 
 void MethodDefinition::Print(bool final, Printer* printer, bool) const {
@@ -110,8 +110,8 @@
     " * should not be modified by hand.\n"
     " */\n\n";
 
-void ClassDefinition::WriteJavaFile(const ClassDefinition* def, const StringPiece& package,
-                                    bool final, bool strip_api_annotations, io::OutputStream* out) {
+void ClassDefinition::WriteJavaFile(const ClassDefinition* def, StringPiece package, bool final,
+                                    bool strip_api_annotations, io::OutputStream* out) {
   Printer printer(out);
   printer.Print(sWarningHeader).Print("package ").Print(package).Println(";");
   printer.Println();
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index 2acdadb..63c9982 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -59,8 +59,8 @@
 template <typename T>
 class PrimitiveMember : public ClassMember {
  public:
-  PrimitiveMember(const android::StringPiece& name, const T& val, bool staged_api = false)
-      : name_(name.to_string()), val_(val), staged_api_(staged_api) {
+  PrimitiveMember(android::StringPiece name, const T& val, bool staged_api = false)
+      : name_(name), val_(val), staged_api_(staged_api) {
   }
 
   bool empty() const override {
@@ -104,8 +104,8 @@
 template <>
 class PrimitiveMember<std::string> : public ClassMember {
  public:
-  PrimitiveMember(const android::StringPiece& name, const std::string& val, bool staged_api = false)
-      : name_(name.to_string()), val_(val) {
+  PrimitiveMember(android::StringPiece name, const std::string& val, bool staged_api = false)
+      : name_(name), val_(val) {
   }
 
   bool empty() const override {
@@ -141,7 +141,8 @@
 template <typename T, typename StringConverter>
 class PrimitiveArrayMember : public ClassMember {
  public:
-  explicit PrimitiveArrayMember(const android::StringPiece& name) : name_(name.to_string()) {}
+  explicit PrimitiveArrayMember(android::StringPiece name) : name_(name) {
+  }
 
   void AddElement(const T& val) {
     elements_.emplace_back(val);
@@ -209,12 +210,12 @@
 class MethodDefinition : public ClassMember {
  public:
   // Expected method signature example: 'public static void onResourcesLoaded(int p)'.
-  explicit MethodDefinition(const android::StringPiece& signature)
-      : signature_(signature.to_string()) {}
+  explicit MethodDefinition(android::StringPiece signature) : signature_(signature) {
+  }
 
   // Appends a single statement to the method. It should include no newlines or else
   // formatting may be broken.
-  void AppendStatement(const android::StringPiece& statement);
+  void AppendStatement(android::StringPiece statement);
 
   // Not quite the same as a name, but good enough.
   const std::string& GetName() const override {
@@ -239,11 +240,12 @@
 
 class ClassDefinition : public ClassMember {
  public:
-  static void WriteJavaFile(const ClassDefinition* def, const android::StringPiece& package,
-                            bool final, bool strip_api_annotations, io::OutputStream* out);
+  static void WriteJavaFile(const ClassDefinition* def, android::StringPiece package, bool final,
+                            bool strip_api_annotations, io::OutputStream* out);
 
-  ClassDefinition(const android::StringPiece& name, ClassQualifier qualifier, bool createIfEmpty)
-      : name_(name.to_string()), qualifier_(qualifier), create_if_empty_(createIfEmpty) {}
+  ClassDefinition(android::StringPiece name, ClassQualifier qualifier, bool createIfEmpty)
+      : name_(name), qualifier_(qualifier), create_if_empty_(createIfEmpty) {
+  }
 
   enum class Result {
     kAdded,
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index a25ca22..7665d0e 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -57,14 +57,14 @@
     "transient",  "try",          "void",      "volatile",   "while",
     "true",       "false",        "null"};
 
-static bool IsValidSymbol(const StringPiece& symbol) {
+static bool IsValidSymbol(StringPiece symbol) {
   return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
 }
 
 // Java symbols can not contain . or -, but those are valid in a resource name.
 // Replace those with '_'.
-std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) {
-  std::string output = symbol.to_string();
+std::string JavaClassGenerator::TransformToFieldName(StringPiece symbol) {
+  std::string output(symbol);
   for (char& c : output) {
     if (c == '.' || c == '-') {
       c = '_';
@@ -84,7 +84,7 @@
 // Foo_bar
 static std::string TransformNestedAttr(const ResourceNameRef& attr_name,
                                        const std::string& styleable_class_name,
-                                       const StringPiece& package_name_to_generate) {
+                                       StringPiece package_name_to_generate) {
   std::string output = styleable_class_name;
 
   // We may reference IDs from other packages, so prefix the entry name with
@@ -226,16 +226,15 @@
 
 static FieldReference GetRFieldReference(const ResourceName& name,
                                          StringPiece fallback_package_name) {
-  const std::string package_name =
-      name.package.empty() ? fallback_package_name.to_string() : name.package;
+  const std::string_view package_name = name.package.empty() ? fallback_package_name : name.package;
   const std::string entry = JavaClassGenerator::TransformToFieldName(name.entry);
-  return FieldReference(StringPrintf("%s.R.%s.%s", package_name.c_str(),
-                                     name.type.to_string().data(), entry.c_str()));
+  return FieldReference(
+      StringPrintf("%s.R.%s.%s", package_name.data(), name.type.to_string().data(), entry.c_str()));
 }
 
 bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
                                           const Styleable& styleable,
-                                          const StringPiece& package_name_to_generate,
+                                          StringPiece package_name_to_generate,
                                           ClassDefinition* out_class_def,
                                           MethodDefinition* out_rewrite_method,
                                           Printer* r_txt_printer) {
@@ -314,7 +313,8 @@
         return true;
       }
       const StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
-      return attr_comment_line.contains("@removed") || attr_comment_line.contains("@hide");
+      return attr_comment_line.find("@removed") != std::string::npos ||
+             attr_comment_line.find("@hide") != std::string::npos;
     });
     documentation_attrs.erase(documentation_remove_iter, documentation_attrs.end());
 
@@ -397,7 +397,7 @@
         comment = styleable_attr.symbol.value().attribute->GetComment();
       }
 
-      if (comment.contains("@removed")) {
+      if (comment.find("@removed") != std::string::npos) {
         // Removed attributes are public but hidden from the documentation, so
         // don't emit them as part of the class documentation.
         continue;
@@ -497,7 +497,7 @@
   }
 
   if (out_rewrite_method != nullptr) {
-    const std::string type_str = name.type.to_string();
+    const auto type_str = name.type.to_string();
     out_rewrite_method->AppendStatement(
         StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | packageIdBits;", type_str.data(),
                      field_name.data(), type_str.data(), field_name.data()));
@@ -505,8 +505,7 @@
 }
 
 std::optional<std::string> JavaClassGenerator::UnmangleResource(
-    const StringPiece& package_name, const StringPiece& package_name_to_generate,
-    const ResourceEntry& entry) {
+    StringPiece package_name, StringPiece package_name_to_generate, const ResourceEntry& entry) {
   if (SkipSymbol(entry.visibility.level)) {
     return {};
   }
@@ -528,7 +527,7 @@
   return {std::move(unmangled_name)};
 }
 
-bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate,
+bool JavaClassGenerator::ProcessType(StringPiece package_name_to_generate,
                                      const ResourceTablePackage& package,
                                      const ResourceTableType& type,
                                      ClassDefinition* out_type_class_def,
@@ -577,7 +576,7 @@
   return true;
 }
 
-bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out,
+bool JavaClassGenerator::Generate(StringPiece package_name_to_generate, OutputStream* out,
                                   OutputStream* out_r_txt) {
   return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt);
 }
@@ -591,8 +590,8 @@
   }
 }
 
-bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
-                                  const StringPiece& out_package_name, OutputStream* out,
+bool JavaClassGenerator::Generate(StringPiece package_name_to_generate,
+                                  StringPiece out_package_name, OutputStream* out,
                                   OutputStream* out_r_txt) {
   ClassDefinition r_class("R", ClassQualifier::kNone, true);
   std::unique_ptr<MethodDefinition> rewrite_method;
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index b45a2f1..234df04 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -70,16 +70,16 @@
   // All symbols technically belong to a single package, but linked libraries will
   // have their names mangled, denoting that they came from a different package.
   // We need to generate these symbols in a separate file. Returns true on success.
-  bool Generate(const android::StringPiece& package_name_to_generate, io::OutputStream* out,
+  bool Generate(android::StringPiece package_name_to_generate, io::OutputStream* out,
                 io::OutputStream* out_r_txt = nullptr);
 
-  bool Generate(const android::StringPiece& package_name_to_generate,
-                const android::StringPiece& output_package_name, io::OutputStream* out,
+  bool Generate(android::StringPiece package_name_to_generate,
+                android::StringPiece output_package_name, io::OutputStream* out,
                 io::OutputStream* out_r_txt = nullptr);
 
   const std::string& GetError() const;
 
-  static std::string TransformToFieldName(const android::StringPiece& symbol);
+  static std::string TransformToFieldName(android::StringPiece symbol);
 
  private:
   bool SkipSymbol(Visibility::Level state);
@@ -87,11 +87,11 @@
 
   // Returns the unmangled resource entry name if the unmangled package is the same as
   // package_name_to_generate. Returns nothing if the resource should be skipped.
-  std::optional<std::string> UnmangleResource(const android::StringPiece& package_name,
-                                              const android::StringPiece& package_name_to_generate,
+  std::optional<std::string> UnmangleResource(android::StringPiece package_name,
+                                              android::StringPiece package_name_to_generate,
                                               const ResourceEntry& entry);
 
-  bool ProcessType(const android::StringPiece& package_name_to_generate,
+  bool ProcessType(android::StringPiece package_name_to_generate,
                    const ResourceTablePackage& package, const ResourceTableType& type,
                    ClassDefinition* out_type_class_def, MethodDefinition* out_rewrite_method_def,
                    text::Printer* r_txt_printer);
@@ -106,8 +106,7 @@
   // its package ID if `out_rewrite_method` is not nullptr.
   // `package_name_to_generate` is the package
   bool ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
-                        const Styleable& styleable,
-                        const android::StringPiece& package_name_to_generate,
+                        const Styleable& styleable, android::StringPiece package_name_to_generate,
                         ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method,
                         text::Printer* r_txt_printer);
 
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index df09e47..56d9075 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -69,7 +69,9 @@
     StringPiece attr_value = attr->value;
     const char* startChar = attr_value.begin();
     if (attr_name == "pathPattern") {
-      if (*startChar == '/' || *startChar == '.' || *startChar == '*') {
+      // pathPattern starts with '.' or '*' does not need leading slash.
+      // Reference starts with @ does not need leading slash.
+      if (*startChar == '/' || *startChar == '.' || *startChar == '*' || *startChar == '@') {
         return true;
       } else {
         diag->Error(android::DiagMessage(data_el->line_number)
@@ -80,7 +82,8 @@
         return false;
       }
     } else {
-      if (*startChar == '/') {
+      // Reference starts with @ does not need leading slash.
+      if (*startChar == '/' || *startChar == '@') {
         return true;
       } else {
         diag->Error(android::DiagMessage(data_el->line_number)
@@ -643,8 +646,8 @@
   return true;
 }
 
-static void FullyQualifyClassName(const StringPiece& package, const StringPiece& attr_ns,
-                                  const StringPiece& attr_name, xml::Element* el) {
+static void FullyQualifyClassName(StringPiece package, StringPiece attr_ns, StringPiece attr_name,
+                                  xml::Element* el) {
   xml::Attribute* attr = el->FindAttribute(attr_ns, attr_name);
   if (attr != nullptr) {
     if (std::optional<std::string> new_value =
@@ -654,7 +657,7 @@
   }
 }
 
-static bool RenameManifestPackage(const StringPiece& package_override, xml::Element* manifest_el) {
+static bool RenameManifestPackage(StringPiece package_override, xml::Element* manifest_el) {
   xml::Attribute* attr = manifest_el->FindAttribute({}, "package");
 
   // We've already verified that the manifest element is present, with a package
@@ -662,7 +665,7 @@
   CHECK(attr != nullptr);
 
   std::string original_package = std::move(attr->value);
-  attr->value = package_override.to_string();
+  attr->value.assign(package_override);
 
   xml::Element* application_el = manifest_el->FindChild({}, "application");
   if (application_el != nullptr) {
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index cec9a1a..7180ae6 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -61,12 +61,12 @@
             .Build();
   }
 
-  std::unique_ptr<xml::XmlResource> Verify(const StringPiece& str) {
+  std::unique_ptr<xml::XmlResource> Verify(StringPiece str) {
     return VerifyWithOptions(str, {});
   }
 
-  std::unique_ptr<xml::XmlResource> VerifyWithOptions(
-      const StringPiece& str, const ManifestFixerOptions& options) {
+  std::unique_ptr<xml::XmlResource> VerifyWithOptions(StringPiece str,
+                                                      const ManifestFixerOptions& options) {
     std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(str);
     ManifestFixer fixer(options);
     if (fixer.Consume(mContext.get(), doc.get())) {
@@ -1408,5 +1408,24 @@
       </application>
     </manifest>)";
   EXPECT_THAT(Verify(input), NotNull());
+
+  // DeepLink with string reference as a path.
+  input = R"(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android">
+      <application>
+        <activity android:name=".MainActivity">
+          <intent-filter>
+            <action android:name="android.intent.action.VIEW" />
+            <category android:name="android.intent.category.DEFAULT" />
+            <category android:name="android.intent.category.BROWSABLE" />
+            <data android:scheme="http"
+                          android:host="www.example.com"
+                          android:path="@string/startup_uri" />
+          </intent-filter>
+        </activity>
+      </application>
+    </manifest>)";
+  EXPECT_THAT(Verify(input), NotNull());
 }
 }  // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index f2a93a8..9dadfb2 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -189,8 +189,7 @@
  public:
   EmptyDeclStack() = default;
 
-  std::optional<xml::ExtractedPackage> TransformPackageAlias(
-      const StringPiece& alias) const override {
+  std::optional<xml::ExtractedPackage> TransformPackageAlias(StringPiece alias) const override {
     if (alias.empty()) {
       return xml::ExtractedPackage{{}, true /*private*/};
     }
@@ -206,8 +205,7 @@
       : alias_namespaces_(std::move(namespaces)) {
   }
 
-  std::optional<xml::ExtractedPackage> TransformPackageAlias(
-      const StringPiece& alias) const override {
+  std::optional<xml::ExtractedPackage> TransformPackageAlias(StringPiece alias) const override {
     if (alias.empty()) {
       return xml::ExtractedPackage{{}, true /*private*/};
     }
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index c9f0964..67a4828 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -66,7 +66,7 @@
 
 // This will merge and mangle resources from a static library. It is assumed that all FileReferences
 // have correctly set their io::IFile*.
-bool TableMerger::MergeAndMangle(const android::Source& src, const StringPiece& package_name,
+bool TableMerger::MergeAndMangle(const android::Source& src, StringPiece package_name,
                                  ResourceTable* table) {
   bool error = false;
   for (auto& package : table->packages) {
@@ -326,8 +326,8 @@
     const std::string& package, const FileReference& file_ref) {
   StringPiece prefix, entry, suffix;
   if (util::ExtractResFilePathParts(*file_ref.path, &prefix, &entry, &suffix)) {
-    std::string mangled_entry = NameMangler::MangleEntry(package, entry.to_string());
-    std::string newPath = prefix.to_string() + mangled_entry + suffix.to_string();
+    std::string mangled_entry = NameMangler::MangleEntry(package, entry);
+    std::string newPath = (std::string(prefix) += mangled_entry) += suffix;
     std::unique_ptr<FileReference> new_file_ref =
         util::make_unique<FileReference>(main_table_->string_pool.MakeRef(newPath));
     new_file_ref->SetComment(file_ref.GetComment());
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 2ba2123..37daf42 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -61,7 +61,7 @@
   // References are made to this ResourceTable for efficiency reasons.
   TableMerger(IAaptContext* context, ResourceTable* out_table, const TableMergerOptions& options);
 
-  inline const std::set<std::string>& merged_packages() const {
+  inline const std::set<std::string, std::less<>>& merged_packages() const {
     return merged_packages_;
   }
 
@@ -71,7 +71,7 @@
 
   // Merges resources from the given package, mangling the name. This is for static libraries.
   // All FileReference values must have their io::IFile set.
-  bool MergeAndMangle(const android::Source& src, const android::StringPiece& package,
+  bool MergeAndMangle(const android::Source& src, android::StringPiece package,
                       ResourceTable* table);
 
   // Merges a compiled file that belongs to this same or empty package.
@@ -84,7 +84,7 @@
   ResourceTable* main_table_;
   TableMergerOptions options_;
   ResourceTablePackage* main_package_;
-  std::set<std::string> merged_packages_;
+  std::set<std::string, std::less<>> merged_packages_;
 
   bool MergeImpl(const android::Source& src, ResourceTable* src_table, bool overlay,
                  bool allow_new);
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index f994e27..f01db3d 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -113,12 +113,12 @@
 };
 
 class SignatureFilter : public IPathFilter {
-  bool Keep(const std::string& path) override {
+  bool Keep(std::string_view path) override {
     static std::regex signature_regex(R"regex(^META-INF/.*\.(RSA|DSA|EC|SF)$)regex");
-    if (std::regex_search(path, signature_regex)) {
+    if (std::regex_search(path.begin(), path.end(), signature_regex)) {
       return false;
     }
-    return !(path == "META-INF/MANIFEST.MF");
+    return path != "META-INF/MANIFEST.MF";
   }
 };
 
diff --git a/tools/aapt2/optimize/Obfuscator.cpp b/tools/aapt2/optimize/Obfuscator.cpp
index f704f26..cc21093 100644
--- a/tools/aapt2/optimize/Obfuscator.cpp
+++ b/tools/aapt2/optimize/Obfuscator.cpp
@@ -16,6 +16,8 @@
 
 #include "optimize/Obfuscator.h"
 
+#include <fstream>
+#include <map>
 #include <set>
 #include <string>
 #include <unordered_set>
@@ -32,10 +34,13 @@
 
 namespace aapt {
 
-Obfuscator::Obfuscator(std::map<std::string, std::string>& path_map_out) : path_map_(path_map_out) {
+Obfuscator::Obfuscator(OptimizeOptions& optimizeOptions)
+    : options_(optimizeOptions.table_flattener_options),
+      shorten_resource_paths_(optimizeOptions.shorten_resource_paths),
+      collapse_key_stringpool_(optimizeOptions.table_flattener_options.collapse_key_stringpool) {
 }
 
-std::string ShortenFileName(const android::StringPiece& file_path, int output_length) {
+std::string ShortenFileName(android::StringPiece file_path, int output_length) {
   std::size_t hash_num = std::hash<android::StringPiece>{}(file_path);
   std::string result = "";
   // Convert to (modified) base64 so that it is a proper file path.
@@ -58,9 +63,9 @@
   }
 }
 
-std::string GetShortenedPath(const android::StringPiece& shortened_filename,
-                             const android::StringPiece& extension, int collision_count) {
-  std::string shortened_path = "res/" + shortened_filename.to_string();
+std::string GetShortenedPath(android::StringPiece shortened_filename,
+                             android::StringPiece extension, int collision_count) {
+  std::string shortened_path = std::string("res/") += shortened_filename;
   if (collision_count > 0) {
     shortened_path += std::to_string(collision_count);
   }
@@ -77,7 +82,8 @@
   }
 };
 
-bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) {
+static bool HandleShortenFilePaths(ResourceTable* table,
+                                   std::map<std::string, std::string>& shortened_path_map) {
   // used to detect collisions
   std::unordered_set<std::string> shortened_paths;
   std::set<FileReference*, PathComparator> file_refs;
@@ -109,10 +115,117 @@
       shortened_path = GetShortenedPath(shortened_filename, extension, collision_count);
     }
     shortened_paths.insert(shortened_path);
-    path_map_.insert({*file_ref->path, shortened_path});
+    shortened_path_map.insert({*file_ref->path, shortened_path});
     file_ref->path = table->string_pool.MakeRef(shortened_path, file_ref->path.GetContext());
   }
   return true;
 }
 
+void Obfuscator::ObfuscateResourceName(
+    const bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions,
+    const ResourceNamedType& type_name, const ResourceTableEntryView& entry,
+    const android::base::function_ref<void(Result obfuscatedResult, const ResourceName&)>
+        onObfuscate) {
+  ResourceName resource_name({}, type_name, entry.name);
+  if (!collapse_key_stringpool ||
+      name_collapse_exemptions.find(resource_name) != name_collapse_exemptions.end()) {
+    onObfuscate(Result::Keep_ExemptionList, resource_name);
+  } else {
+    // resource isn't exempt from collapse, add it as obfuscated value
+    if (entry.overlayable_item) {
+      // if the resource name of the specific entry is obfuscated and this
+      // entry is in the overlayable list, the overlay can't work on this
+      // overlayable at runtime because the name has been obfuscated in
+      // resources.arsc during flatten operation.
+      onObfuscate(Result::Keep_Overlayable, resource_name);
+    } else {
+      onObfuscate(Result::Obfuscated, resource_name);
+    }
+  }
+}
+
+static bool HandleCollapseKeyStringPool(
+    const ResourceTable* table, const bool collapse_key_string_pool,
+    const std::set<ResourceName>& name_collapse_exemptions,
+    std::unordered_map<uint32_t, std::string>& id_resource_map) {
+  if (!collapse_key_string_pool) {
+    return true;
+  }
+
+  int entryResId = 0;
+  auto onObfuscate = [&entryResId, &id_resource_map](const Obfuscator::Result obfuscatedResult,
+                                                     const ResourceName& resource_name) {
+    if (obfuscatedResult == Obfuscator::Result::Obfuscated) {
+      id_resource_map.insert({entryResId, resource_name.entry});
+    }
+  };
+
+  for (auto& package : table->packages) {
+    for (auto& type : package->types) {
+      for (auto& entry : type->entries) {
+        if (!entry->id.has_value() || entry->name.empty()) {
+          continue;
+        }
+        entryResId = entry->id->id;
+        ResourceTableEntryView entry_view{
+            .name = entry->name,
+            .id = entry->id ? entry->id.value().entry_id() : (std::optional<uint16_t>)std::nullopt,
+            .visibility = entry->visibility,
+            .allow_new = entry->allow_new,
+            .overlayable_item = entry->overlayable_item,
+            .staged_id = entry->staged_id};
+
+        Obfuscator::ObfuscateResourceName(collapse_key_string_pool, name_collapse_exemptions,
+                                          type->named_type, entry_view, onObfuscate);
+      }
+    }
+  }
+
+  return true;
+}
+
+bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) {
+  HandleCollapseKeyStringPool(table, options_.collapse_key_stringpool,
+                              options_.name_collapse_exemptions, options_.id_resource_map);
+  if (shorten_resource_paths_) {
+    return HandleShortenFilePaths(table, options_.shortened_path_map);
+  }
+  return true;
+}
+
+bool Obfuscator::WriteObfuscationMap(const std::string& file_path) const {
+  pb::ResourceMappings resourceMappings;
+  for (const auto& [id, name] : options_.id_resource_map) {
+    auto* collapsedNameMapping = resourceMappings.mutable_collapsed_names()->add_resource_names();
+    collapsedNameMapping->set_id(id);
+    collapsedNameMapping->set_name(name);
+  }
+
+  for (const auto& [original_path, shortened_path] : options_.shortened_path_map) {
+    auto* resource_path = resourceMappings.mutable_shortened_paths()->add_resource_paths();
+    resource_path->set_original_path(original_path);
+    resource_path->set_shortened_path(shortened_path);
+  }
+
+  {  // RAII style, output the pb content to file and close fout in destructor
+    std::ofstream fout(file_path, std::ios::out | std::ios::trunc | std::ios::binary);
+    if (!fout.is_open()) {
+      return false;
+    }
+    return resourceMappings.SerializeToOstream(&fout);
+  }
+}
+
+/**
+ * Tell the optimizer whether it's needed to dump information for de-obfuscating.
+ *
+ * There are two conditions need to dump the information for de-obfuscating.
+ * * the option of shortening file paths is enabled.
+ * * the option of collapsing resource names is enabled.
+ * @return true if the information needed for de-obfuscating, otherwise false
+ */
+bool Obfuscator::IsEnabled() const {
+  return shorten_resource_paths_ || collapse_key_stringpool_;
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/optimize/Obfuscator.h b/tools/aapt2/optimize/Obfuscator.h
index 1ea32db..5ccf5438 100644
--- a/tools/aapt2/optimize/Obfuscator.h
+++ b/tools/aapt2/optimize/Obfuscator.h
@@ -17,10 +17,15 @@
 #ifndef TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
 #define TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
 
-#include <map>
+#include <set>
 #include <string>
 
+#include "ResourceMetadata.pb.h"
+#include "ResourceTable.h"
+#include "android-base/function_ref.h"
 #include "android-base/macros.h"
+#include "cmd/Optimize.h"
+#include "format/binary/TableFlattener.h"
 #include "process/IResourceTableConsumer.h"
 
 namespace aapt {
@@ -30,12 +35,28 @@
 // Maps resources in the apk to shortened paths.
 class Obfuscator : public IResourceTableConsumer {
  public:
-  explicit Obfuscator(std::map<std::string, std::string>& path_map_out);
+  explicit Obfuscator(OptimizeOptions& optimizeOptions);
 
   bool Consume(IAaptContext* context, ResourceTable* table) override;
 
+  bool WriteObfuscationMap(const std::string& file_path) const;
+
+  bool IsEnabled() const;
+
+  enum class Result { Obfuscated, Keep_ExemptionList, Keep_Overlayable };
+
+  // hardcoded string uses characters which make it an invalid resource name
+  static constexpr char kObfuscatedResourceName[] = "0_resource_name_obfuscated";
+
+  static void ObfuscateResourceName(
+      const bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions,
+      const ResourceNamedType& type_name, const ResourceTableEntryView& entry,
+      const android::base::function_ref<void(Result, const ResourceName&)> onObfuscate);
+
  private:
-  std::map<std::string, std::string>& path_map_;
+  TableFlattenerOptions& options_;
+  const bool shorten_resource_paths_;
+  const bool collapse_key_stringpool_;
   DISALLOW_COPY_AND_ASSIGN(Obfuscator);
 };
 
diff --git a/tools/aapt2/optimize/Obfuscator_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp
index a3339d4..7f57b71 100644
--- a/tools/aapt2/optimize/Obfuscator_test.cpp
+++ b/tools/aapt2/optimize/Obfuscator_test.cpp
@@ -16,14 +16,19 @@
 
 #include "optimize/Obfuscator.h"
 
+#include <map>
 #include <memory>
 #include <string>
 
 #include "ResourceTable.h"
+#include "android-base/file.h"
 #include "test/Test.h"
 
 using ::aapt::test::GetValue;
+using ::testing::AnyOf;
 using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::IsTrue;
 using ::testing::Not;
 using ::testing::NotNull;
 
@@ -51,8 +56,9 @@
           .AddString("android:string/string", "res/should/still/be/the/same.png")
           .Build();
 
-  std::map<std::string, std::string> path_map;
-  ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+  OptimizeOptions options{.shorten_resource_paths = true};
+  std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+  ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
 
   // Expect that the path map is populated
   ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -87,8 +93,9 @@
                             test::ParseConfigOrDie("mdp-v21"))
           .Build();
 
-  std::map<std::string, std::string> path_map;
-  ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+  OptimizeOptions options{.shorten_resource_paths = true};
+  std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+  ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
 
   // Expect that the path map to not contain the ColorStateList
   ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end()));
@@ -107,8 +114,9 @@
           .AddFileReference("android:color/pngfile", original_png_path)
           .Build();
 
-  std::map<std::string, std::string> path_map;
-  ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+  OptimizeOptions options{.shorten_resource_paths = true};
+  std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+  ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
 
   // Expect that the path map is populated
   ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -133,8 +141,10 @@
   test::ResourceTableBuilder builder1;
   FillTable(builder1, 0, kNumResources);
   std::unique_ptr<ResourceTable> table1 = builder1.Build();
-  std::map<std::string, std::string> expected_mapping;
-  ASSERT_TRUE(Obfuscator(expected_mapping).Consume(context.get(), table1.get()));
+  OptimizeOptions options{.shorten_resource_paths = true};
+  std::map<std::string, std::string>& expected_mapping =
+      options.table_flattener_options.shortened_path_map;
+  ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table1.get()));
 
   // We are trying to ensure lack of non-determinism, it is not simple to prove
   // a negative, thus we must try the test a few times so that the test itself
@@ -153,8 +163,10 @@
     FillTable(builder2, 0, start_index);
     std::unique_ptr<ResourceTable> table2 = builder2.Build();
 
-    std::map<std::string, std::string> actual_mapping;
-    ASSERT_TRUE(Obfuscator(actual_mapping).Consume(context.get(), table2.get()));
+    OptimizeOptions actualOptimizerOptions{.shorten_resource_paths = true};
+    TableFlattenerOptions& actual_options = actualOptimizerOptions.table_flattener_options;
+    std::map<std::string, std::string>& actual_mapping = actual_options.shortened_path_map;
+    ASSERT_TRUE(Obfuscator(actualOptimizerOptions).Consume(context.get(), table2.get()));
 
     for (auto& item : actual_mapping) {
       ASSERT_THAT(expected_mapping[item.first], Eq(item.second));
@@ -162,4 +174,126 @@
   }
 }
 
+TEST(ObfuscatorTest, DumpIdResourceMap) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+  OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme"));
+  overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION;
+  overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION;
+  overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION;
+
+  std::string original_xml_path = "res/drawable/xmlfile.xml";
+  std::string original_png_path = "res/drawable/pngfile.png";
+
+  std::string name = "com.app.test:string/overlayable";
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .AddFileReference("android:color/xmlfile", original_xml_path)
+          .AddFileReference("android:color/pngfile", original_png_path)
+          .AddValue("com.app.test:color/mycolor", aapt::ResourceId(0x7f020000),
+                    aapt::util::make_unique<aapt::BinaryPrimitive>(
+                        uint8_t(android::Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc))
+          .AddString("com.app.test:string/mystring", ResourceId(0x7f030000), "hi")
+          .AddString("com.app.test:string/in_exemption", ResourceId(0x7f030001), "Hi")
+          .AddString(name, ResourceId(0x7f030002), "HI")
+          .SetOverlayable(name, overlayable_item)
+          .Build();
+
+  OptimizeOptions options{.shorten_resource_paths = true};
+  TableFlattenerOptions& flattenerOptions = options.table_flattener_options;
+  flattenerOptions.collapse_key_stringpool = true;
+  flattenerOptions.name_collapse_exemptions.insert(
+      ResourceName({}, ResourceType::kString, "in_exemption"));
+  auto& id_resource_map = flattenerOptions.id_resource_map;
+  ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
+
+  // Expect that the id resource name map is populated
+  EXPECT_THAT(id_resource_map.at(0x7f020000), Eq("mycolor"));
+  EXPECT_THAT(id_resource_map.at(0x7f030000), Eq("mystring"));
+  EXPECT_THAT(id_resource_map.find(0x7f030001), Eq(id_resource_map.end()));
+  EXPECT_THAT(id_resource_map.find(0x7f030002), Eq(id_resource_map.end()));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithDefaultOption) {
+  OptimizeOptions options;
+  Obfuscator obfuscatorWithDefaultOption(options);
+  ASSERT_THAT(obfuscatorWithDefaultOption.IsEnabled(), Eq(false));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithShortenPathOption) {
+  OptimizeOptions options{.shorten_resource_paths = true};
+  Obfuscator obfuscatorWithShortenPathOption(options);
+  ASSERT_THAT(obfuscatorWithShortenPathOption.IsEnabled(), Eq(true));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithCollapseStringPoolOption) {
+  OptimizeOptions options;
+  options.table_flattener_options.collapse_key_stringpool = true;
+  Obfuscator obfuscatorWithCollapseStringPoolOption(options);
+  ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithShortenPathAndCollapseStringPoolOption) {
+  OptimizeOptions options{.shorten_resource_paths = true};
+  options.table_flattener_options.collapse_key_stringpool = true;
+  Obfuscator obfuscatorWithCollapseStringPoolOption(options);
+  ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true));
+}
+
+static std::unique_ptr<ResourceTable> getProtocolBufferTableUnderTest() {
+  std::string original_xml_path = "res/drawable/xmlfile.xml";
+  std::string original_png_path = "res/drawable/pngfile.png";
+
+  return test::ResourceTableBuilder()
+      .AddFileReference("com.app.test:drawable/xmlfile", original_xml_path)
+      .AddFileReference("com.app.test:drawable/pngfile", original_png_path)
+      .AddValue("com.app.test:color/mycolor", aapt::ResourceId(0x7f020000),
+                aapt::util::make_unique<aapt::BinaryPrimitive>(
+                    uint8_t(android::Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc))
+      .AddString("com.app.test:string/mystring", ResourceId(0x7f030000), "hello world")
+      .Build();
+}
+
+TEST(ObfuscatorTest, WriteObfuscationMapInProtocolBufferFormat) {
+  OptimizeOptions options{.shorten_resource_paths = true};
+  options.table_flattener_options.collapse_key_stringpool = true;
+  Obfuscator obfuscator(options);
+  ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(),
+                                 getProtocolBufferTableUnderTest().get()));
+
+  obfuscator.WriteObfuscationMap("obfuscated_map.pb");
+
+  std::string pbOut;
+  android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */);
+  EXPECT_THAT(pbOut, HasSubstr("drawable/xmlfile.xml"));
+  EXPECT_THAT(pbOut, HasSubstr("drawable/pngfile.png"));
+  EXPECT_THAT(pbOut, HasSubstr("mycolor"));
+  EXPECT_THAT(pbOut, HasSubstr("mystring"));
+  pb::ResourceMappings resourceMappings;
+  EXPECT_THAT(resourceMappings.ParseFromString(pbOut), IsTrue());
+  EXPECT_THAT(resourceMappings.collapsed_names().resource_names_size(), Eq(2));
+  auto& resource_names = resourceMappings.collapsed_names().resource_names();
+  EXPECT_THAT(resource_names.at(0).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
+  EXPECT_THAT(resource_names.at(1).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
+  auto& shortened_paths = resourceMappings.shortened_paths();
+  EXPECT_THAT(shortened_paths.resource_paths_size(), Eq(2));
+  EXPECT_THAT(shortened_paths.resource_paths(0).original_path(),
+              AnyOf(Eq("res/drawable/pngfile.png"), Eq("res/drawable/xmlfile.xml")));
+  EXPECT_THAT(shortened_paths.resource_paths(1).original_path(),
+              AnyOf(Eq("res/drawable/pngfile.png"), Eq("res/drawable/xmlfile.xml")));
+}
+
+TEST(ObfuscatorTest, WriteObfuscatingMapWithNonEnabledOption) {
+  OptimizeOptions options;
+  Obfuscator obfuscator(options);
+  ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(),
+                                 getProtocolBufferTableUnderTest().get()));
+
+  obfuscator.WriteObfuscationMap("obfuscated_map.pb");
+
+  std::string pbOut;
+  android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */);
+  ASSERT_THAT(pbOut, Eq(""));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/optimize/VersionCollapser_test.cpp b/tools/aapt2/optimize/VersionCollapser_test.cpp
index aa0d0c0..18dcd6b 100644
--- a/tools/aapt2/optimize/VersionCollapser_test.cpp
+++ b/tools/aapt2/optimize/VersionCollapser_test.cpp
@@ -23,7 +23,7 @@
 namespace aapt {
 
 static std::unique_ptr<ResourceTable> BuildTableWithConfigs(
-    const StringPiece& name, std::initializer_list<std::string> list) {
+    StringPiece name, std::initializer_list<std::string> list) {
   test::ResourceTableBuilder builder;
   for (const std::string& item : list) {
     builder.AddSimple(name, test::ParseConfigOrDie(item));
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 92b45c3..bca62da 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -218,7 +218,7 @@
   return symbol;
 }
 
-bool AssetManagerSymbolSource::AddAssetPath(const StringPiece& path) {
+bool AssetManagerSymbolSource::AddAssetPath(StringPiece path) {
   TRACE_CALL();
   if (std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path.data())) {
     apk_assets_.push_back(std::move(apk));
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index c17837c..b09ff70 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -192,7 +192,7 @@
  public:
   AssetManagerSymbolSource() = default;
 
-  bool AddAssetPath(const android::StringPiece& path);
+  bool AddAssetPath(android::StringPiece path);
   std::map<size_t, std::string> GetAssignedPackageIds() const;
   bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const;
 
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 30336e2..65f63dc 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -34,61 +34,53 @@
 namespace aapt {
 namespace test {
 
-ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name,
-                                                      const ResourceId& id) {
+ResourceTableBuilder& ResourceTableBuilder::AddSimple(StringPiece name, const ResourceId& id) {
   return AddValue(name, id, util::make_unique<Id>());
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::AddSimple(StringPiece name,
                                                       const ConfigDescription& config,
                                                       const ResourceId& id) {
   return AddValue(name, config, id, util::make_unique<Id>());
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name,
-                                                         const StringPiece& ref) {
+ResourceTableBuilder& ResourceTableBuilder::AddReference(StringPiece name, StringPiece ref) {
   return AddReference(name, {}, ref);
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name,
-                                                         const ResourceId& id,
-                                                         const StringPiece& ref) {
+ResourceTableBuilder& ResourceTableBuilder::AddReference(StringPiece name, const ResourceId& id,
+                                                         StringPiece ref) {
   return AddValue(name, id, util::make_unique<Reference>(ParseNameOrDie(ref)));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name,
-                                                      const StringPiece& str) {
+ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, StringPiece str) {
   return AddString(name, {}, str);
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id,
-                                                      const StringPiece& str) {
+ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, const ResourceId& id,
+                                                      StringPiece str) {
   return AddValue(name, id, util::make_unique<String>(table_->string_pool.MakeRef(str)));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id,
+ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, const ResourceId& id,
                                                       const ConfigDescription& config,
-                                                      const StringPiece& str) {
+                                                      StringPiece str) {
   return AddValue(name, config, id, util::make_unique<String>(table_->string_pool.MakeRef(str)));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
-                                                             const StringPiece& path,
+ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, StringPiece path,
                                                              io::IFile* file) {
   return AddFileReference(name, {}, path, file);
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
-                                                             const ResourceId& id,
-                                                             const StringPiece& path,
-                                                             io::IFile* file) {
+ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, const ResourceId& id,
+                                                             StringPiece path, io::IFile* file) {
   auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
   file_ref->file = file;
   return AddValue(name, id, std::move(file_ref));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
-                                                             const StringPiece& path,
+ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, StringPiece path,
                                                              const ConfigDescription& config,
                                                              io::IFile* file) {
   auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
@@ -96,17 +88,17 @@
   return AddValue(name, config, {}, std::move(file_ref));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name,
                                                      std::unique_ptr<Value> value) {
   return AddValue(name, {}, std::move(value));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, const ResourceId& id,
+ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name, const ResourceId& id,
                                                      std::unique_ptr<Value> value) {
   return AddValue(name, {}, id, std::move(value));
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name,
                                                      const ConfigDescription& config,
                                                      const ResourceId& id,
                                                      std::unique_ptr<Value> value) {
@@ -121,8 +113,7 @@
   return *this;
 }
 
-ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& name,
-                                                           const ResourceId& id,
+ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(StringPiece name, const ResourceId& id,
                                                            Visibility::Level level,
                                                            bool allow_new) {
   ResourceName res_name = ParseNameOrDie(name);
@@ -136,9 +127,8 @@
   return *this;
 }
 
-ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(StringPiece name,
                                                            const OverlayableItem& overlayable) {
-
   ResourceName res_name = ParseNameOrDie(name);
   CHECK(table_->AddResource(
       NewResourceBuilder(res_name).SetOverlayable(overlayable).SetAllowMangled(true).Build(),
@@ -159,8 +149,7 @@
   return std::move(table_);
 }
 
-std::unique_ptr<Reference> BuildReference(const StringPiece& ref,
-                                          const std::optional<ResourceId>& id) {
+std::unique_ptr<Reference> BuildReference(StringPiece ref, const std::optional<ResourceId>& id) {
   std::unique_ptr<Reference> reference = util::make_unique<Reference>(ParseNameOrDie(ref));
   reference->id = id;
   return reference;
@@ -188,7 +177,7 @@
   return *this;
 }
 
-AttributeBuilder& AttributeBuilder::AddItem(const StringPiece& name, uint32_t value) {
+AttributeBuilder& AttributeBuilder::AddItem(StringPiece name, uint32_t value) {
   attr_->symbols.push_back(
       Attribute::Symbol{Reference(ResourceName({}, ResourceType::kId, name)), value});
   return *this;
@@ -198,17 +187,17 @@
   return std::move(attr_);
 }
 
-StyleBuilder& StyleBuilder::SetParent(const StringPiece& str) {
+StyleBuilder& StyleBuilder::SetParent(StringPiece str) {
   style_->parent = Reference(ParseNameOrDie(str));
   return *this;
 }
 
-StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, std::unique_ptr<Item> value) {
+StyleBuilder& StyleBuilder::AddItem(StringPiece str, std::unique_ptr<Item> value) {
   style_->entries.push_back(Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)});
   return *this;
 }
 
-StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, const ResourceId& id,
+StyleBuilder& StyleBuilder::AddItem(StringPiece str, const ResourceId& id,
                                     std::unique_ptr<Item> value) {
   AddItem(str, std::move(value));
   style_->entries.back().key.id = id;
@@ -219,8 +208,7 @@
   return std::move(style_);
 }
 
-StyleableBuilder& StyleableBuilder::AddItem(const StringPiece& str,
-                                            const std::optional<ResourceId>& id) {
+StyleableBuilder& StyleableBuilder::AddItem(StringPiece str, const std::optional<ResourceId>& id) {
   styleable_->entries.push_back(Reference(ParseNameOrDie(str)));
   styleable_->entries.back().id = id;
   return *this;
@@ -230,7 +218,7 @@
   return std::move(styleable_);
 }
 
-std::unique_ptr<xml::XmlResource> BuildXmlDom(const StringPiece& str) {
+std::unique_ptr<xml::XmlResource> BuildXmlDom(StringPiece str) {
   std::string input = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
   input.append(str.data(), str.size());
   StringInputStream in(input);
@@ -241,7 +229,7 @@
 }
 
 std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context,
-                                                            const StringPiece& str) {
+                                                            StringPiece str) {
   std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str);
   doc->file.name.package = context->GetCompilationPackage();
   return doc;
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 780bd0d..f03d6fc 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -38,40 +38,35 @@
  public:
   ResourceTableBuilder() = default;
 
-  ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {});
-  ResourceTableBuilder& AddSimple(const android::StringPiece& name,
+  ResourceTableBuilder& AddSimple(android::StringPiece name, const ResourceId& id = {});
+  ResourceTableBuilder& AddSimple(android::StringPiece name,
                                   const android::ConfigDescription& config,
                                   const ResourceId& id = {});
-  ResourceTableBuilder& AddReference(const android::StringPiece& name,
-                                     const android::StringPiece& ref);
-  ResourceTableBuilder& AddReference(const android::StringPiece& name, const ResourceId& id,
-                                     const android::StringPiece& ref);
-  ResourceTableBuilder& AddString(const android::StringPiece& name,
-                                  const android::StringPiece& str);
-  ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
-                                  const android::StringPiece& str);
-  ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
+  ResourceTableBuilder& AddReference(android::StringPiece name, android::StringPiece ref);
+  ResourceTableBuilder& AddReference(android::StringPiece name, const ResourceId& id,
+                                     android::StringPiece ref);
+  ResourceTableBuilder& AddString(android::StringPiece name, android::StringPiece str);
+  ResourceTableBuilder& AddString(android::StringPiece name, const ResourceId& id,
+                                  android::StringPiece str);
+  ResourceTableBuilder& AddString(android::StringPiece name, const ResourceId& id,
                                   const android::ConfigDescription& config,
-                                  const android::StringPiece& str);
-  ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
-                                         const android::StringPiece& path,
+                                  android::StringPiece str);
+  ResourceTableBuilder& AddFileReference(android::StringPiece name, android::StringPiece path,
                                          io::IFile* file = nullptr);
-  ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id,
-                                         const android::StringPiece& path,
-                                         io::IFile* file = nullptr);
-  ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
-                                         const android::StringPiece& path,
+  ResourceTableBuilder& AddFileReference(android::StringPiece name, const ResourceId& id,
+                                         android::StringPiece path, io::IFile* file = nullptr);
+  ResourceTableBuilder& AddFileReference(android::StringPiece name, android::StringPiece path,
                                          const android::ConfigDescription& config,
                                          io::IFile* file = nullptr);
-  ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value);
-  ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id,
+  ResourceTableBuilder& AddValue(android::StringPiece name, std::unique_ptr<Value> value);
+  ResourceTableBuilder& AddValue(android::StringPiece name, const ResourceId& id,
                                  std::unique_ptr<Value> value);
-  ResourceTableBuilder& AddValue(const android::StringPiece& name,
-                                 const android::ConfigDescription& config,
-                                 const ResourceId& id, std::unique_ptr<Value> value);
-  ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id,
+  ResourceTableBuilder& AddValue(android::StringPiece name,
+                                 const android::ConfigDescription& config, const ResourceId& id,
+                                 std::unique_ptr<Value> value);
+  ResourceTableBuilder& SetSymbolState(android::StringPiece name, const ResourceId& id,
                                        Visibility::Level level, bool allow_new = false);
-  ResourceTableBuilder& SetOverlayable(const android::StringPiece& name,
+  ResourceTableBuilder& SetOverlayable(android::StringPiece name,
                                        const OverlayableItem& overlayable);
   ResourceTableBuilder& Add(NewResource&& res);
 
@@ -84,7 +79,7 @@
   std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>();
 };
 
-std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref,
+std::unique_ptr<Reference> BuildReference(android::StringPiece ref,
                                           const std::optional<ResourceId>& id = {});
 std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, uint32_t data);
 
@@ -101,7 +96,7 @@
     return *this;
   }
 
-  ValueBuilder& SetComment(const android::StringPiece& str) {
+  ValueBuilder& SetComment(android::StringPiece str) {
     value_->SetComment(str);
     return *this;
   }
@@ -121,7 +116,7 @@
   AttributeBuilder();
   AttributeBuilder& SetTypeMask(uint32_t typeMask);
   AttributeBuilder& SetWeak(bool weak);
-  AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value);
+  AttributeBuilder& AddItem(android::StringPiece name, uint32_t value);
   std::unique_ptr<Attribute> Build();
 
  private:
@@ -133,9 +128,9 @@
 class StyleBuilder {
  public:
   StyleBuilder() = default;
-  StyleBuilder& SetParent(const android::StringPiece& str);
-  StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value);
-  StyleBuilder& AddItem(const android::StringPiece& str, const ResourceId& id,
+  StyleBuilder& SetParent(android::StringPiece str);
+  StyleBuilder& AddItem(android::StringPiece str, std::unique_ptr<Item> value);
+  StyleBuilder& AddItem(android::StringPiece str, const ResourceId& id,
                         std::unique_ptr<Item> value);
   std::unique_ptr<Style> Build();
 
@@ -148,8 +143,7 @@
 class StyleableBuilder {
  public:
   StyleableBuilder() = default;
-  StyleableBuilder& AddItem(const android::StringPiece& str,
-                            const std::optional<ResourceId>& id = {});
+  StyleableBuilder& AddItem(android::StringPiece str, const std::optional<ResourceId>& id = {});
   std::unique_ptr<Styleable> Build();
 
  private:
@@ -158,9 +152,9 @@
   std::unique_ptr<Styleable> styleable_ = util::make_unique<Styleable>();
 };
 
-std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str);
+std::unique_ptr<xml::XmlResource> BuildXmlDom(android::StringPiece str);
 std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context,
-                                                            const android::StringPiece& str);
+                                                            android::StringPiece str);
 
 class ArtifactBuilder {
  public:
diff --git a/tools/aapt2/test/Common.cpp b/tools/aapt2/test/Common.cpp
index eca0c1c..cdf24534 100644
--- a/tools/aapt2/test/Common.cpp
+++ b/tools/aapt2/test/Common.cpp
@@ -44,10 +44,9 @@
 }
 
 template <>
-Value* GetValueForConfigAndProduct<Value>(ResourceTable* table,
-                                          const android::StringPiece& res_name,
+Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, android::StringPiece res_name,
                                           const ConfigDescription& config,
-                                          const android::StringPiece& product) {
+                                          android::StringPiece product) {
   std::optional<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name));
   if (result) {
     ResourceConfigValue* config_value = result.value().entry->FindValue(config, product);
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 3f28361..83a0f3f 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -39,22 +39,22 @@
 
 android::IDiagnostics* GetDiagnostics();
 
-inline ResourceName ParseNameOrDie(const android::StringPiece& str) {
+inline ResourceName ParseNameOrDie(android::StringPiece str) {
   ResourceNameRef ref;
   CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name: " << str;
   return ref.ToResourceName();
 }
 
-inline android::ConfigDescription ParseConfigOrDie(const android::StringPiece& str) {
-    android::ConfigDescription config;
+inline android::ConfigDescription ParseConfigOrDie(android::StringPiece str) {
+  android::ConfigDescription config;
   CHECK(android::ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str;
   return config;
 }
 
 template <typename T = Value>
-T* GetValueForConfigAndProduct(ResourceTable* table, const android::StringPiece& res_name,
+T* GetValueForConfigAndProduct(ResourceTable* table, android::StringPiece res_name,
                                const android::ConfigDescription& config,
-                               const android::StringPiece& product) {
+                               android::StringPiece product) {
   std::optional<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name));
   if (result) {
     ResourceConfigValue* config_value = result.value().entry->FindValue(config, product);
@@ -66,25 +66,25 @@
 }
 
 template <>
-Value* GetValueForConfigAndProduct<Value>(ResourceTable* table,
-                                          const android::StringPiece& res_name,
+Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, android::StringPiece res_name,
                                           const android::ConfigDescription& config,
-                                          const android::StringPiece& product);
+                                          android::StringPiece product);
 
 template <typename T = Value>
-T* GetValueForConfig(ResourceTable* table, const android::StringPiece& res_name,
+T* GetValueForConfig(ResourceTable* table, android::StringPiece res_name,
                      const android::ConfigDescription& config) {
   return GetValueForConfigAndProduct<T>(table, res_name, config, {});
 }
 
 template <typename T = Value>
-T* GetValue(ResourceTable* table, const android::StringPiece& res_name) {
+T* GetValue(ResourceTable* table, android::StringPiece res_name) {
   return GetValueForConfig<T>(table, res_name, {});
 }
 
 class TestFile : public io::IFile {
  public:
-  explicit TestFile(const android::StringPiece& path) : source_(path) {}
+  explicit TestFile(android::StringPiece path) : source_(path) {
+  }
 
   std::unique_ptr<io::IData> OpenAsData() override {
     return {};
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 4e4973e..c5331fb 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -52,8 +52,8 @@
     return compilation_package_.value();
   }
 
-  void SetCompilationPackage(const android::StringPiece& package) {
-    compilation_package_ = package.to_string();
+  void SetCompilationPackage(android::StringPiece package) {
+    compilation_package_ = std::string(package);
   }
 
   uint8_t GetPackageId() override {
@@ -111,8 +111,8 @@
     return *this;
   }
 
-  ContextBuilder& SetCompilationPackage(const android::StringPiece& package) {
-    context_->compilation_package_ = package.to_string();
+  ContextBuilder& SetCompilationPackage(android::StringPiece package) {
+    context_->compilation_package_ = std::string(package);
     return *this;
   }
 
@@ -149,7 +149,7 @@
 
 class StaticSymbolSourceBuilder {
  public:
-  StaticSymbolSourceBuilder& AddPublicSymbol(const android::StringPiece& name, ResourceId id,
+  StaticSymbolSourceBuilder& AddPublicSymbol(android::StringPiece name, ResourceId id,
                                              std::unique_ptr<Attribute> attr = {}) {
     std::unique_ptr<SymbolTable::Symbol> symbol =
         util::make_unique<SymbolTable::Symbol>(id, std::move(attr), true);
@@ -159,7 +159,7 @@
     return *this;
   }
 
-  StaticSymbolSourceBuilder& AddSymbol(const android::StringPiece& name, ResourceId id,
+  StaticSymbolSourceBuilder& AddSymbol(android::StringPiece name, ResourceId id,
                                        std::unique_ptr<Attribute> attr = {}) {
     std::unique_ptr<SymbolTable::Symbol> symbol =
         util::make_unique<SymbolTable::Symbol>(id, std::move(attr), false);
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index dbc0e36..428372f 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -38,8 +38,8 @@
 
 const char* CommandTestFixture::kDefaultPackageName = "com.aapt.command.test";
 
-void ClearDirectory(const android::StringPiece& path) {
-  const std::string root_dir = path.to_string();
+void ClearDirectory(android::StringPiece path) {
+  const std::string root_dir(path);
   std::unique_ptr<DIR, decltype(closedir)*> dir(opendir(root_dir.data()), closedir);
   if (!dir) {
     StdErrDiagnostics().Error(android::DiagMessage()
@@ -91,8 +91,7 @@
 }
 
 bool CommandTestFixture::CompileFile(const std::string& path, const std::string& contents,
-                                     const android::StringPiece& out_dir,
-                                     android::IDiagnostics* diag) {
+                                     android::StringPiece out_dir, android::IDiagnostics* diag) {
   WriteFile(path, contents);
   CHECK(file::mkdirs(out_dir.data()));
   return CompileCommand(diag).Execute({path, "-o", out_dir, "-v"}, &std::cerr) == 0;
@@ -113,8 +112,8 @@
   return LinkCommand(diag).Execute(link_args, &std::cerr) == 0;
 }
 
-bool CommandTestFixture::Link(const std::vector<std::string>& args,
-                              const android::StringPiece& flat_dir, android::IDiagnostics* diag) {
+bool CommandTestFixture::Link(const std::vector<std::string>& args, android::StringPiece flat_dir,
+                              android::IDiagnostics* diag) {
   std::vector<android::StringPiece> link_args;
   for(const std::string& arg : args) {
     link_args.emplace_back(arg);
@@ -148,7 +147,7 @@
 }
 
 std::unique_ptr<io::IData> CommandTestFixture::OpenFileAsData(LoadedApk* apk,
-                                                              const android::StringPiece& path) {
+                                                              android::StringPiece path) {
   return apk
       ->GetFileCollection()
       ->FindFile(path)
diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h
index 61403b7..ba4a734 100644
--- a/tools/aapt2/test/Fixture.h
+++ b/tools/aapt2/test/Fixture.h
@@ -48,7 +48,7 @@
   // Retrieves the absolute path of the specified relative path in the test directory. Directories
   // should be separated using forward slashes ('/'), and these slashes will be translated to
   // backslashes when running Windows tests.
-  std::string GetTestPath(const android::StringPiece& path) {
+  std::string GetTestPath(android::StringPiece path) {
     std::string base = temp_dir_;
     for (android::StringPiece part : util::Split(path, '/')) {
       file::AppendPath(&base, part);
@@ -73,22 +73,21 @@
   // Wries the contents of the file to the specified path. The file is compiled and the flattened
   // file is written to the out directory.
   bool CompileFile(const std::string& path, const std::string& contents,
-                   const android::StringPiece& flat_out_dir, android::IDiagnostics* diag);
+                   android::StringPiece flat_out_dir, android::IDiagnostics* diag);
 
   // Executes the link command with the specified arguments.
   bool Link(const std::vector<std::string>& args, android::IDiagnostics* diag);
 
   // Executes the link command with the specified arguments. The flattened files residing in the
   // flat directory will be added to the link command as file arguments.
-  bool Link(const std::vector<std::string>& args, const android::StringPiece& flat_dir,
+  bool Link(const std::vector<std::string>& args, android::StringPiece flat_dir,
             android::IDiagnostics* diag);
 
   // Creates a minimal android manifest within the test directory and returns the file path.
   std::string GetDefaultManifest(const char* package_name = kDefaultPackageName);
 
   // Returns pointer to data inside APK files
-  std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk,
-                                            const android::StringPiece& path);
+  std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk, android::StringPiece path);
 
   // Asserts that loading the tree from the specified file in the apk succeeds.
   void AssertLoadXml(LoadedApk* apk, const io::IData* data,
diff --git a/tools/aapt2/text/Printer.cpp b/tools/aapt2/text/Printer.cpp
index 243800c..8e491ac 100644
--- a/tools/aapt2/text/Printer.cpp
+++ b/tools/aapt2/text/Printer.cpp
@@ -26,7 +26,7 @@
 namespace aapt {
 namespace text {
 
-Printer& Printer::Println(const StringPiece& str) {
+Printer& Printer::Println(StringPiece str) {
   Print(str);
   return Print("\n");
 }
@@ -35,7 +35,7 @@
   return Print("\n");
 }
 
-Printer& Printer::Print(const StringPiece& str) {
+Printer& Printer::Print(StringPiece str) {
   if (error_) {
     return *this;
   }
@@ -47,7 +47,7 @@
     const auto new_line_iter = std::find(remaining_str_begin, remaining_str_end, '\n');
 
     // We will copy the string up until the next new-line (or end of string).
-    const StringPiece str_to_copy = str.substr(remaining_str_begin, new_line_iter);
+    const StringPiece str_to_copy(remaining_str_begin, new_line_iter - remaining_str_begin);
     if (!str_to_copy.empty()) {
       if (needs_indent_) {
         for (int i = 0; i < indent_level_; i++) {
diff --git a/tools/aapt2/text/Printer.h b/tools/aapt2/text/Printer.h
index f399f8e..f7ad98b 100644
--- a/tools/aapt2/text/Printer.h
+++ b/tools/aapt2/text/Printer.h
@@ -31,8 +31,8 @@
   explicit Printer(::aapt::io::OutputStream* out) : out_(out) {
   }
 
-  Printer& Print(const ::android::StringPiece& str);
-  Printer& Println(const ::android::StringPiece& str);
+  Printer& Print(android::StringPiece str);
+  Printer& Println(android::StringPiece str);
   Printer& Println();
 
   void Indent();
diff --git a/tools/aapt2/text/Unicode.cpp b/tools/aapt2/text/Unicode.cpp
index 3735b3e..5e25be3 100644
--- a/tools/aapt2/text/Unicode.cpp
+++ b/tools/aapt2/text/Unicode.cpp
@@ -77,7 +77,7 @@
          (codepoint == 0x3000);
 }
 
-bool IsJavaIdentifier(const StringPiece& str) {
+bool IsJavaIdentifier(StringPiece str) {
   Utf8Iterator iter(str);
 
   // Check the first character.
@@ -99,7 +99,7 @@
   return true;
 }
 
-bool IsValidResourceEntryName(const StringPiece& str) {
+bool IsValidResourceEntryName(StringPiece str) {
   Utf8Iterator iter(str);
 
   // Check the first character.
diff --git a/tools/aapt2/text/Unicode.h b/tools/aapt2/text/Unicode.h
index 546714e..ab3e82b 100644
--- a/tools/aapt2/text/Unicode.h
+++ b/tools/aapt2/text/Unicode.h
@@ -46,11 +46,11 @@
 
 // Returns true if the UTF8 string can be used as a Java identifier.
 // NOTE: This does not check against the set of reserved Java keywords.
-bool IsJavaIdentifier(const android::StringPiece& str);
+bool IsJavaIdentifier(android::StringPiece str);
 
 // Returns true if the UTF8 string can be used as the entry name of a resource name.
 // This is the `entry` part of package:type/entry.
-bool IsValidResourceEntryName(const android::StringPiece& str);
+bool IsValidResourceEntryName(android::StringPiece str);
 
 }  // namespace text
 }  // namespace aapt
diff --git a/tools/aapt2/text/Utf8Iterator.cpp b/tools/aapt2/text/Utf8Iterator.cpp
index 20b9073..0bd8a37 100644
--- a/tools/aapt2/text/Utf8Iterator.cpp
+++ b/tools/aapt2/text/Utf8Iterator.cpp
@@ -24,7 +24,7 @@
 namespace aapt {
 namespace text {
 
-Utf8Iterator::Utf8Iterator(const StringPiece& str)
+Utf8Iterator::Utf8Iterator(StringPiece str)
     : str_(str), current_pos_(0), next_pos_(0), current_codepoint_(0) {
   DoNext();
 }
diff --git a/tools/aapt2/text/Utf8Iterator.h b/tools/aapt2/text/Utf8Iterator.h
index 9318401..2bba198 100644
--- a/tools/aapt2/text/Utf8Iterator.h
+++ b/tools/aapt2/text/Utf8Iterator.h
@@ -25,7 +25,7 @@
 
 class Utf8Iterator {
  public:
-  explicit Utf8Iterator(const android::StringPiece& str);
+  explicit Utf8Iterator(android::StringPiece str);
 
   bool HasNext() const;
 
diff --git a/tools/aapt2/trace/TraceBuffer.cpp b/tools/aapt2/trace/TraceBuffer.cpp
index b4b31d9..da53739 100644
--- a/tools/aapt2/trace/TraceBuffer.cpp
+++ b/tools/aapt2/trace/TraceBuffer.cpp
@@ -103,7 +103,7 @@
   s << tag;
   s << " ";
   for (auto& arg : args) {
-    s << arg.to_string();
+    s << arg;
     s << " ";
   }
   tracebuffer::Add(s.str(), tracebuffer::kBegin);
@@ -124,7 +124,7 @@
   s << tag;
   s << " ";
   for (auto& arg : args) {
-    s << arg.to_string();
+    s << arg;
     s << " ";
   }
   tracebuffer::Add(s.str(), tracebuffer::kBegin);
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 5d5b7cd..93c1b61 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -139,7 +139,7 @@
   return ::android::base::utf8::mkdir(path.c_str(), mode) == 0 || errno == EEXIST;
 }
 
-StringPiece GetStem(const StringPiece& path) {
+StringPiece GetStem(StringPiece path) {
   const char* start = path.begin();
   const char* end = path.end();
   for (const char* current = end - 1; current != start - 1; --current) {
@@ -150,7 +150,7 @@
   return {};
 }
 
-StringPiece GetFilename(const StringPiece& path) {
+StringPiece GetFilename(StringPiece path) {
   const char* end = path.end();
   const char* last_dir_sep = path.begin();
   for (const char* c = path.begin(); c != end; ++c) {
@@ -161,7 +161,7 @@
   return StringPiece(last_dir_sep, end - last_dir_sep);
 }
 
-StringPiece GetExtension(const StringPiece& path) {
+StringPiece GetExtension(StringPiece path) {
   StringPiece filename = GetFilename(path);
   const char* const end = filename.end();
   const char* c = std::find(filename.begin(), end, '.');
@@ -171,7 +171,7 @@
   return {};
 }
 
-bool IsHidden(const android::StringPiece& path) {
+bool IsHidden(android::StringPiece path) {
   return util::StartsWith(GetFilename(path), ".");
 }
 
@@ -193,16 +193,16 @@
   if (args.empty()) {
     return "";
   }
-  std::string out = args[0].to_string();
+  std::string out{args[0]};
   for (int i = 1; i < args.size(); i++) {
     file::AppendPath(&out, args[i]);
   }
   return out;
 }
 
-std::string PackageToPath(const StringPiece& package) {
+std::string PackageToPath(StringPiece package) {
   std::string out_path;
-  for (const StringPiece& part : util::Tokenize(package, '.')) {
+  for (StringPiece part : util::Tokenize(package, '.')) {
     AppendPath(&out_path, part);
   }
   return out_path;
@@ -241,10 +241,10 @@
   return std::move(filemap);
 }
 
-bool AppendArgsFromFile(const StringPiece& path, std::vector<std::string>* out_arglist,
+bool AppendArgsFromFile(StringPiece path, std::vector<std::string>* out_arglist,
                         std::string* out_error) {
   std::string contents;
-  if (!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) {
+  if (!ReadFileToString(std::string(path), &contents, true /*follow_symlinks*/)) {
     if (out_error) {
       *out_error = "failed to read argument-list file";
     }
@@ -254,16 +254,16 @@
   for (StringPiece line : util::Tokenize(contents, ' ')) {
     line = util::TrimWhitespace(line);
     if (!line.empty()) {
-      out_arglist->push_back(line.to_string());
+      out_arglist->emplace_back(line);
     }
   }
   return true;
 }
 
-bool AppendSetArgsFromFile(const StringPiece& path, std::unordered_set<std::string>* out_argset,
+bool AppendSetArgsFromFile(StringPiece path, std::unordered_set<std::string>* out_argset,
                            std::string* out_error) {
   std::string contents;
-  if(!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) {
+  if (!ReadFileToString(std::string(path), &contents, true /*follow_symlinks*/)) {
     if (out_error) {
       *out_error = "failed to read argument-list file";
     }
@@ -273,13 +273,13 @@
   for (StringPiece line : util::Tokenize(contents, ' ')) {
     line = util::TrimWhitespace(line);
     if (!line.empty()) {
-      out_argset->insert(line.to_string());
+      out_argset->emplace(line);
     }
   }
   return true;
 }
 
-bool FileFilter::SetPattern(const StringPiece& pattern) {
+bool FileFilter::SetPattern(StringPiece pattern) {
   pattern_tokens_ = util::SplitAndLowercase(pattern, ':');
   return true;
 }
@@ -343,10 +343,10 @@
   return true;
 }
 
-std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& path,
+std::optional<std::vector<std::string>> FindFiles(android::StringPiece path,
                                                   android::IDiagnostics* diag,
                                                   const FileFilter* filter) {
-  const std::string root_dir = path.to_string();
+  const auto& root_dir = path;
   std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
   if (!d) {
     diag->Error(android::DiagMessage() << SystemErrorCodeToString(errno) << ": " << root_dir);
@@ -361,7 +361,7 @@
     }
 
     std::string file_name = entry->d_name;
-    std::string full_path = root_dir;
+    std::string full_path{root_dir};
     AppendPath(&full_path, file_name);
     const FileType file_type = GetFileType(full_path);
 
@@ -380,7 +380,7 @@
 
   // Now process subdirs.
   for (const std::string& subdir : subdirs) {
-    std::string full_subdir = root_dir;
+    std::string full_subdir{root_dir};
     AppendPath(&full_subdir, subdir);
     std::optional<std::vector<std::string>> subfiles = FindFiles(full_subdir, diag, filter);
     if (!subfiles) {
diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h
index ee95712..42eeaf2 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -66,31 +66,31 @@
 bool mkdirs(const std::string& path);
 
 // Returns all but the last part of the path.
-android::StringPiece GetStem(const android::StringPiece& path);
+android::StringPiece GetStem(android::StringPiece path);
 
 // Returns the last part of the path with extension.
-android::StringPiece GetFilename(const android::StringPiece& path);
+android::StringPiece GetFilename(android::StringPiece path);
 
 // Returns the extension of the path. This is the entire string after the first '.' of the last part
 // of the path.
-android::StringPiece GetExtension(const android::StringPiece& path);
+android::StringPiece GetExtension(android::StringPiece path);
 
 // Returns whether or not the name of the file or directory is a hidden file name
-bool IsHidden(const android::StringPiece& path);
+bool IsHidden(android::StringPiece path);
 
 // Converts a package name (com.android.app) to a path: com/android/app
-std::string PackageToPath(const android::StringPiece& package);
+std::string PackageToPath(android::StringPiece package);
 
 // Creates a FileMap for the file at path.
 std::optional<android::FileMap> MmapPath(const std::string& path, std::string* out_error);
 
 // Reads the file at path and appends each line to the outArgList vector.
-bool AppendArgsFromFile(const android::StringPiece& path, std::vector<std::string>* out_arglist,
+bool AppendArgsFromFile(android::StringPiece path, std::vector<std::string>* out_arglist,
                         std::string* out_error);
 
 // Reads the file at path and appends each line to the outargset set.
-bool AppendSetArgsFromFile(const android::StringPiece& path,
-                        std::unordered_set<std::string>* out_argset, std::string* out_error);
+bool AppendSetArgsFromFile(android::StringPiece path, std::unordered_set<std::string>* out_argset,
+                           std::string* out_error);
 
 // Filter that determines which resource files/directories are
 // processed by AAPT. Takes a pattern string supplied by the user.
@@ -112,7 +112,7 @@
   // - The special filenames "." and ".." are always ignored.
   // - Otherwise the full string is matched.
   // - match is not case-sensitive.
-  bool SetPattern(const android::StringPiece& pattern);
+  bool SetPattern(android::StringPiece pattern);
 
   // Applies the filter, returning true for pass, false for fail.
   bool operator()(const std::string& filename, FileType type) const;
@@ -126,7 +126,7 @@
 
 // Returns a list of files relative to the directory identified by `path`.
 // An optional FileFilter filters out any files that don't pass.
-std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& path,
+std::optional<std::vector<std::string>> FindFiles(android::StringPiece path,
                                                   android::IDiagnostics* diag,
                                                   const FileFilter* filter = nullptr);
 
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index 9b7ebdd..be87766 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -43,15 +43,14 @@
 // See frameworks/base/core/java/android/content/pm/parsing/ParsingPackageUtils.java
 constexpr static const size_t kMaxPackageNameSize = 223;
 
-static std::vector<std::string> SplitAndTransform(
-    const StringPiece& str, char sep, const std::function<char(char)>& f) {
+static std::vector<std::string> SplitAndTransform(StringPiece str, char sep, char (*f)(char)) {
   std::vector<std::string> parts;
   const StringPiece::const_iterator end = std::end(str);
   StringPiece::const_iterator start = std::begin(str);
   StringPiece::const_iterator current;
   do {
     current = std::find(start, end, sep);
-    parts.emplace_back(str.substr(start, current).to_string());
+    parts.emplace_back(start, current);
     if (f) {
       std::string& part = parts.back();
       std::transform(part.begin(), part.end(), part.begin(), f);
@@ -61,29 +60,29 @@
   return parts;
 }
 
-std::vector<std::string> Split(const StringPiece& str, char sep) {
+std::vector<std::string> Split(StringPiece str, char sep) {
   return SplitAndTransform(str, sep, nullptr);
 }
 
-std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) {
-  return SplitAndTransform(str, sep, ::tolower);
+std::vector<std::string> SplitAndLowercase(StringPiece str, char sep) {
+  return SplitAndTransform(str, sep, [](char c) -> char { return ::tolower(c); });
 }
 
-bool StartsWith(const StringPiece& str, const StringPiece& prefix) {
+bool StartsWith(StringPiece str, StringPiece prefix) {
   if (str.size() < prefix.size()) {
     return false;
   }
   return str.substr(0, prefix.size()) == prefix;
 }
 
-bool EndsWith(const StringPiece& str, const StringPiece& suffix) {
+bool EndsWith(StringPiece str, StringPiece suffix) {
   if (str.size() < suffix.size()) {
     return false;
   }
   return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
 }
 
-StringPiece TrimLeadingWhitespace(const StringPiece& str) {
+StringPiece TrimLeadingWhitespace(StringPiece str) {
   if (str.size() == 0 || str.data() == nullptr) {
     return str;
   }
@@ -97,7 +96,7 @@
   return StringPiece(start, end - start);
 }
 
-StringPiece TrimTrailingWhitespace(const StringPiece& str) {
+StringPiece TrimTrailingWhitespace(StringPiece str) {
   if (str.size() == 0 || str.data() == nullptr) {
     return str;
   }
@@ -111,7 +110,7 @@
   return StringPiece(start, end - start);
 }
 
-StringPiece TrimWhitespace(const StringPiece& str) {
+StringPiece TrimWhitespace(StringPiece str) {
   if (str.size() == 0 || str.data() == nullptr) {
     return str;
   }
@@ -130,9 +129,9 @@
   return StringPiece(start, end - start);
 }
 
-static int IsJavaNameImpl(const StringPiece& str) {
+static int IsJavaNameImpl(StringPiece str) {
   int pieces = 0;
-  for (const StringPiece& piece : Tokenize(str, '.')) {
+  for (StringPiece piece : Tokenize(str, '.')) {
     pieces++;
     if (!text::IsJavaIdentifier(piece)) {
       return -1;
@@ -141,17 +140,17 @@
   return pieces;
 }
 
-bool IsJavaClassName(const StringPiece& str) {
+bool IsJavaClassName(StringPiece str) {
   return IsJavaNameImpl(str) >= 2;
 }
 
-bool IsJavaPackageName(const StringPiece& str) {
+bool IsJavaPackageName(StringPiece str) {
   return IsJavaNameImpl(str) >= 1;
 }
 
-static int IsAndroidNameImpl(const StringPiece& str) {
+static int IsAndroidNameImpl(StringPiece str) {
   int pieces = 0;
-  for (const StringPiece& piece : Tokenize(str, '.')) {
+  for (StringPiece piece : Tokenize(str, '.')) {
     if (piece.empty()) {
       return -1;
     }
@@ -173,15 +172,14 @@
   return pieces;
 }
 
-bool IsAndroidPackageName(const StringPiece& str) {
+bool IsAndroidPackageName(StringPiece str) {
   if (str.size() > kMaxPackageNameSize) {
     return false;
   }
   return IsAndroidNameImpl(str) > 1 || str == "android";
 }
 
-bool IsAndroidSharedUserId(const android::StringPiece& package_name,
-                           const android::StringPiece& shared_user_id) {
+bool IsAndroidSharedUserId(android::StringPiece package_name, android::StringPiece shared_user_id) {
   if (shared_user_id.size() > kMaxPackageNameSize) {
     return false;
   }
@@ -189,25 +187,24 @@
          package_name == "android";
 }
 
-bool IsAndroidSplitName(const StringPiece& str) {
+bool IsAndroidSplitName(StringPiece str) {
   return IsAndroidNameImpl(str) > 0;
 }
 
-std::optional<std::string> GetFullyQualifiedClassName(const StringPiece& package,
-                                                      const StringPiece& classname) {
+std::optional<std::string> GetFullyQualifiedClassName(StringPiece package, StringPiece classname) {
   if (classname.empty()) {
     return {};
   }
 
   if (util::IsJavaClassName(classname)) {
-    return classname.to_string();
+    return std::string(classname);
   }
 
   if (package.empty()) {
     return {};
   }
 
-  std::string result = package.to_string();
+  std::string result{package};
   if (classname.data()[0] != '.') {
     result += '.';
   }
@@ -251,7 +248,7 @@
   return static_cast<size_t>(c - start);
 }
 
-bool VerifyJavaStringFormat(const StringPiece& str) {
+bool VerifyJavaStringFormat(StringPiece str) {
   const char* c = str.begin();
   const char* const end = str.end();
 
@@ -341,7 +338,7 @@
   return true;
 }
 
-std::u16string Utf8ToUtf16(const StringPiece& utf8) {
+std::u16string Utf8ToUtf16(StringPiece utf8) {
   ssize_t utf16_length = utf8_to_utf16_length(
       reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
   if (utf16_length <= 0) {
@@ -381,7 +378,7 @@
   const char* end = str_.end();
   if (start == end) {
     end_ = true;
-    token_.assign(token_.end(), 0);
+    token_ = StringPiece(token_.end(), 0);
     return *this;
   }
 
@@ -389,12 +386,12 @@
   const char* current = start;
   while (current != end) {
     if (*current == separator_) {
-      token_.assign(start, current - start);
+      token_ = StringPiece(start, current - start);
       return *this;
     }
     ++current;
   }
-  token_.assign(start, end - start);
+  token_ = StringPiece(start, end - start);
   return *this;
 }
 
@@ -409,15 +406,17 @@
   return !(*this == rhs);
 }
 
-Tokenizer::iterator::iterator(const StringPiece& s, char sep, const StringPiece& tok, bool end)
-    : str_(s), separator_(sep), token_(tok), end_(end) {}
+Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok, bool end)
+    : str_(s), separator_(sep), token_(tok), end_(end) {
+}
 
-Tokenizer::Tokenizer(const StringPiece& str, char sep)
+Tokenizer::Tokenizer(StringPiece str, char sep)
     : begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)),
-      end_(str, sep, StringPiece(str.end(), 0), true) {}
+      end_(str, sep, StringPiece(str.end(), 0), true) {
+}
 
-bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix,
-                             StringPiece* out_entry, StringPiece* out_suffix) {
+bool ExtractResFilePathParts(StringPiece path, StringPiece* out_prefix, StringPiece* out_entry,
+                             StringPiece* out_suffix) {
   const StringPiece res_prefix("res/");
   if (!StartsWith(path, res_prefix)) {
     return false;
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 8d3b413..40ff5b6 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -48,44 +48,44 @@
   T end;
 };
 
-std::vector<std::string> Split(const android::StringPiece& str, char sep);
-std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);
+std::vector<std::string> Split(android::StringPiece str, char sep);
+std::vector<std::string> SplitAndLowercase(android::StringPiece str, char sep);
 
 // Returns true if the string starts with prefix.
-bool StartsWith(const android::StringPiece& str, const android::StringPiece& prefix);
+bool StartsWith(android::StringPiece str, android::StringPiece prefix);
 
 // Returns true if the string ends with suffix.
-bool EndsWith(const android::StringPiece& str, const android::StringPiece& suffix);
+bool EndsWith(android::StringPiece str, android::StringPiece suffix);
 
 // Creates a new StringPiece that points to a substring of the original string without leading
 // whitespace.
-android::StringPiece TrimLeadingWhitespace(const android::StringPiece& str);
+android::StringPiece TrimLeadingWhitespace(android::StringPiece str);
 
 // Creates a new StringPiece that points to a substring of the original string without trailing
 // whitespace.
-android::StringPiece TrimTrailingWhitespace(const android::StringPiece& str);
+android::StringPiece TrimTrailingWhitespace(android::StringPiece str);
 
 // Creates a new StringPiece that points to a substring of the original string without leading or
 // trailing whitespace.
-android::StringPiece TrimWhitespace(const android::StringPiece& str);
+android::StringPiece TrimWhitespace(android::StringPiece str);
 
 // Tests that the string is a valid Java class name.
-bool IsJavaClassName(const android::StringPiece& str);
+bool IsJavaClassName(android::StringPiece str);
 
 // Tests that the string is a valid Java package name.
-bool IsJavaPackageName(const android::StringPiece& str);
+bool IsJavaPackageName(android::StringPiece str);
 
 // Tests that the string is a valid Android package name. More strict than a Java package name.
 // - First character of each component (separated by '.') must be an ASCII letter.
 // - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
 // - Package must contain at least two components, unless it is 'android'.
 // - The maximum package name length is 223.
-bool IsAndroidPackageName(const android::StringPiece& str);
+bool IsAndroidPackageName(android::StringPiece str);
 
 // Tests that the string is a valid Android split name.
 // - First character of each component (separated by '.') must be an ASCII letter.
 // - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
-bool IsAndroidSplitName(const android::StringPiece& str);
+bool IsAndroidSplitName(android::StringPiece str);
 
 // Tests that the string is a valid Android shared user id.
 // - First character of each component (separated by '.') must be an ASCII letter.
@@ -93,8 +93,7 @@
 // - Must contain at least two components, unless package name is 'android'.
 // - The maximum shared user id length is 223.
 // - Treat empty string as valid, it's the case of no shared user id.
-bool IsAndroidSharedUserId(const android::StringPiece& package_name,
-                           const android::StringPiece& shared_user_id);
+bool IsAndroidSharedUserId(android::StringPiece package_name, android::StringPiece shared_user_id);
 
 // Converts the class name to a fully qualified class name from the given
 // `package`. Ex:
@@ -103,8 +102,8 @@
 // .asdf        --> package.asdf
 // .a.b         --> package.a.b
 // asdf.adsf    --> asdf.adsf
-std::optional<std::string> GetFullyQualifiedClassName(const android::StringPiece& package,
-                                                      const android::StringPiece& class_name);
+std::optional<std::string> GetFullyQualifiedClassName(android::StringPiece package,
+                                                      android::StringPiece class_name);
 
 // Retrieves the formatted name of aapt2.
 const char* GetToolName();
@@ -152,16 +151,16 @@
 // explicitly specifying an index) when there are more than one argument. This is an error
 // because translations may rearrange the order of the arguments in the string, which will
 // break the string interpolation.
-bool VerifyJavaStringFormat(const android::StringPiece& str);
+bool VerifyJavaStringFormat(android::StringPiece str);
 
-bool AppendStyledString(const android::StringPiece& input, bool preserve_spaces,
-                        std::string* out_str, std::string* out_error);
+bool AppendStyledString(android::StringPiece input, bool preserve_spaces, std::string* out_str,
+                        std::string* out_error);
 
 class StringBuilder {
  public:
   StringBuilder() = default;
 
-  StringBuilder& Append(const android::StringPiece& str);
+  StringBuilder& Append(android::StringPiece str);
   const std::string& ToString() const;
   const std::string& Error() const;
   bool IsEmpty() const;
@@ -229,7 +228,7 @@
    private:
     friend class Tokenizer;
 
-    iterator(const android::StringPiece& s, char sep, const android::StringPiece& tok, bool end);
+    iterator(android::StringPiece s, char sep, android::StringPiece tok, bool end);
 
     android::StringPiece str_;
     char separator_;
@@ -237,7 +236,7 @@
     bool end_;
   };
 
-  Tokenizer(const android::StringPiece& str, char sep);
+  Tokenizer(android::StringPiece str, char sep);
 
   iterator begin() const {
     return begin_;
@@ -252,7 +251,7 @@
   const iterator end_;
 };
 
-inline Tokenizer Tokenize(const android::StringPiece& str, char sep) {
+inline Tokenizer Tokenize(android::StringPiece str, char sep) {
   return Tokenizer(str, sep);
 }
 
@@ -263,7 +262,7 @@
 // Extracts ".xml" into outSuffix.
 //
 // Returns true if successful.
-bool ExtractResFilePathParts(const android::StringPiece& path, android::StringPiece* out_prefix,
+bool ExtractResFilePathParts(android::StringPiece path, android::StringPiece* out_prefix,
                              android::StringPiece* out_entry, android::StringPiece* out_suffix);
 
 }  // namespace util
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index 4ebcb11..15135690 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -84,6 +84,14 @@
   ASSERT_THAT(*iter, Eq(StringPiece()));
 }
 
+TEST(UtilTest, TokenizeNone) {
+  auto tokenizer = util::Tokenize(StringPiece("none"), '.');
+  auto iter = tokenizer.begin();
+  ASSERT_THAT(*iter, Eq("none"));
+  ++iter;
+  ASSERT_THAT(iter, Eq(tokenizer.end()));
+}
+
 TEST(UtilTest, IsJavaClassName) {
   EXPECT_TRUE(util::IsJavaClassName("android.test.Class"));
   EXPECT_TRUE(util::IsJavaClassName("android.test.Class$Inner"));
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
index 9bdbd22..3ccbaa2 100644
--- a/tools/aapt2/xml/XmlActionExecutor.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -84,7 +84,7 @@
         error_msg << "unexpected element ";
         PrintElementToDiagMessage(child_el, &error_msg);
         error_msg << " found in ";
-        for (const StringPiece& element : *bread_crumb) {
+        for (StringPiece element : *bread_crumb) {
           error_msg << "<" << element << ">";
         }
         if (policy == XmlActionExecutorPolicy::kAllowListWarning) {
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index f51e8a4..8dea8ea 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -169,7 +169,7 @@
   stack->last_text_node = util::make_unique<Text>();
   stack->last_text_node->line_number = XML_GetCurrentLineNumber(parser);
   stack->last_text_node->column_number = XML_GetCurrentColumnNumber(parser);
-  stack->last_text_node->text = str.to_string();
+  stack->last_text_node->text.assign(str);
 }
 
 static void XMLCALL CommentDataHandler(void* user_data, const char* comment) {
@@ -417,11 +417,11 @@
   children.insert(children.begin() + index, std::move(child));
 }
 
-Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) {
+Attribute* Element::FindAttribute(StringPiece ns, StringPiece name) {
   return const_cast<Attribute*>(static_cast<const Element*>(this)->FindAttribute(ns, name));
 }
 
-const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) const {
+const Attribute* Element::FindAttribute(StringPiece ns, StringPiece name) const {
   for (const auto& attr : attributes) {
     if (ns == attr.namespace_uri && name == attr.name) {
       return &attr;
@@ -430,7 +430,7 @@
   return nullptr;
 }
 
-void Element::RemoveAttribute(const StringPiece& ns, const StringPiece& name) {
+void Element::RemoveAttribute(StringPiece ns, StringPiece name) {
   auto new_attr_end = std::remove_if(attributes.begin(), attributes.end(),
     [&](const Attribute& attr) -> bool {
       return ns == attr.namespace_uri && name == attr.name;
@@ -439,34 +439,32 @@
   attributes.erase(new_attr_end, attributes.end());
 }
 
-Attribute* Element::FindOrCreateAttribute(const StringPiece& ns, const StringPiece& name) {
+Attribute* Element::FindOrCreateAttribute(StringPiece ns, StringPiece name) {
   Attribute* attr = FindAttribute(ns, name);
   if (attr == nullptr) {
-    attributes.push_back(Attribute{ns.to_string(), name.to_string()});
+    attributes.push_back(Attribute{std::string(ns), std::string(name)});
     attr = &attributes.back();
   }
   return attr;
 }
 
-Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) {
+Element* Element::FindChild(StringPiece ns, StringPiece name) {
   return FindChildWithAttribute(ns, name, {}, {}, {});
 }
 
-const Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) const {
+const Element* Element::FindChild(StringPiece ns, StringPiece name) const {
   return FindChildWithAttribute(ns, name, {}, {}, {});
 }
 
-Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
-                                         const StringPiece& attr_ns, const StringPiece& attr_name,
-                                         const StringPiece& attr_value) {
+Element* Element::FindChildWithAttribute(StringPiece ns, StringPiece name, StringPiece attr_ns,
+                                         StringPiece attr_name, StringPiece attr_value) {
   return const_cast<Element*>(static_cast<const Element*>(this)->FindChildWithAttribute(
       ns, name, attr_ns, attr_name, attr_value));
 }
 
-const Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
-                                               const StringPiece& attr_ns,
-                                               const StringPiece& attr_name,
-                                               const StringPiece& attr_value) const {
+const Element* Element::FindChildWithAttribute(StringPiece ns, StringPiece name,
+                                               StringPiece attr_ns, StringPiece attr_name,
+                                               StringPiece attr_value) const {
   for (const auto& child : children) {
     if (const Element* el = NodeCast<Element>(child.get())) {
       if (ns == el->namespace_uri && name == el->name) {
@@ -559,7 +557,7 @@
 }
 
 std::optional<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias(
-    const StringPiece& alias) const {
+    StringPiece alias) const {
   if (alias.empty()) {
     return ExtractedPackage{{}, false /*private*/};
   }
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 5bc55b6..c253b0a 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -96,27 +96,22 @@
   void AppendChild(std::unique_ptr<Node> child);
   void InsertChild(size_t index, std::unique_ptr<Node> child);
 
-  Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name);
-  const Attribute* FindAttribute(const android::StringPiece& ns,
-                                 const android::StringPiece& name) const;
-  Attribute* FindOrCreateAttribute(const android::StringPiece& ns,
-                                   const android::StringPiece& name);
-  void RemoveAttribute(const android::StringPiece& ns,
-                       const android::StringPiece& name);
+  Attribute* FindAttribute(android::StringPiece ns, android::StringPiece name);
+  const Attribute* FindAttribute(android::StringPiece ns, android::StringPiece name) const;
+  Attribute* FindOrCreateAttribute(android::StringPiece ns, android::StringPiece name);
+  void RemoveAttribute(android::StringPiece ns, android::StringPiece name);
 
-  Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name);
-  const Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name) const;
+  Element* FindChild(android::StringPiece ns, android::StringPiece name);
+  const Element* FindChild(android::StringPiece ns, android::StringPiece name) const;
 
-  Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name,
-                                  const android::StringPiece& attr_ns,
-                                  const android::StringPiece& attr_name,
-                                  const android::StringPiece& attr_value);
+  Element* FindChildWithAttribute(android::StringPiece ns, android::StringPiece name,
+                                  android::StringPiece attr_ns, android::StringPiece attr_name,
+                                  android::StringPiece attr_value);
 
-  const Element* FindChildWithAttribute(const android::StringPiece& ns,
-                                        const android::StringPiece& name,
-                                        const android::StringPiece& attr_ns,
-                                        const android::StringPiece& attr_name,
-                                        const android::StringPiece& attr_value) const;
+  const Element* FindChildWithAttribute(android::StringPiece ns, android::StringPiece name,
+                                        android::StringPiece attr_ns,
+                                        android::StringPiece attr_name,
+                                        android::StringPiece attr_value) const;
 
   std::vector<Element*> GetChildElements();
 
@@ -235,8 +230,7 @@
  public:
   using Visitor::Visit;
 
-  std::optional<ExtractedPackage> TransformPackageAlias(
-      const android::StringPiece& alias) const override;
+  std::optional<ExtractedPackage> TransformPackageAlias(android::StringPiece alias) const override;
 
  protected:
   PackageAwareVisitor() = default;
diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index bfa0749..d79446b 100644
--- a/tools/aapt2/xml/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -140,8 +140,7 @@
   return event_queue_.front().data2;
 }
 
-std::optional<ExtractedPackage> XmlPullParser::TransformPackageAlias(
-    const StringPiece& alias) const {
+std::optional<ExtractedPackage> XmlPullParser::TransformPackageAlias(StringPiece alias) const {
   if (alias.empty()) {
     return ExtractedPackage{{}, false /*private*/};
   }
@@ -307,7 +306,7 @@
                                       parser->depth_ });
 }
 
-std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, const StringPiece& name) {
+std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, StringPiece name) {
   auto iter = parser->FindAttribute("", name);
   if (iter != parser->end_attributes()) {
     return StringPiece(util::TrimWhitespace(iter->value));
@@ -315,8 +314,7 @@
   return {};
 }
 
-std::optional<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser,
-                                                 const StringPiece& name) {
+std::optional<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, StringPiece name) {
   auto iter = parser->FindAttribute("", name);
   if (iter != parser->end_attributes()) {
     StringPiece trimmed = util::TrimWhitespace(iter->value);
diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index ab34772..fe4cd01 100644
--- a/tools/aapt2/xml/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -120,8 +120,7 @@
    * If xmlns:app="http://schemas.android.com/apk/res-auto", then
    * 'package' will be set to 'defaultPackage'.
    */
-  std::optional<ExtractedPackage> TransformPackageAlias(
-      const android::StringPiece& alias) const override;
+  std::optional<ExtractedPackage> TransformPackageAlias(android::StringPiece alias) const override;
 
   struct PackageDecl {
     std::string prefix;
@@ -194,7 +193,7 @@
  * Finds the attribute in the current element within the global namespace.
  */
 std::optional<android::StringPiece> FindAttribute(const XmlPullParser* parser,
-                                                  const android::StringPiece& name);
+                                                  android::StringPiece name);
 
 /**
  * Finds the attribute in the current element within the global namespace. The
@@ -202,7 +201,7 @@
  * must not be the empty string.
  */
 std::optional<android::StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser,
-                                                          const android::StringPiece& name);
+                                                          android::StringPiece name);
 
 //
 // Implementation
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
index 114b5ba..709755e 100644
--- a/tools/aapt2/xml/XmlUtil.cpp
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -27,7 +27,7 @@
 namespace aapt {
 namespace xml {
 
-std::string BuildPackageNamespace(const StringPiece& package, bool private_reference) {
+std::string BuildPackageNamespace(StringPiece package, bool private_reference) {
   std::string result = private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix;
   result.append(package.data(), package.size());
   return result;
@@ -41,7 +41,7 @@
     if (package.empty()) {
       return {};
     }
-    return ExtractedPackage{package.to_string(), false /* is_private */};
+    return ExtractedPackage{std::string(package), false /* is_private */};
 
   } else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) {
     StringPiece schema_prefix = kSchemaPrivatePrefix;
@@ -50,7 +50,7 @@
     if (package.empty()) {
       return {};
     }
-    return ExtractedPackage{package.to_string(), true /* is_private */};
+    return ExtractedPackage{std::string(package), true /* is_private */};
 
   } else if (namespace_uri == kSchemaAuto) {
     return ExtractedPackage{std::string(), true /* is_private */};
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
index 1ab05a9..ad676ca 100644
--- a/tools/aapt2/xml/XmlUtil.h
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -59,8 +59,7 @@
 //
 // If privateReference == true, the package will be of the form:
 //   http://schemas.android.com/apk/prv/res/<package>
-std::string BuildPackageNamespace(const android::StringPiece& package,
-                                  bool private_reference = false);
+std::string BuildPackageNamespace(android::StringPiece package, bool private_reference = false);
 
 // Interface representing a stack of XML namespace declarations. When looking up the package for a
 // namespace prefix, the stack is checked from top to bottom.
@@ -69,7 +68,7 @@
 
   // Returns an ExtractedPackage struct if the alias given corresponds with a package declaration.
   virtual std::optional<ExtractedPackage> TransformPackageAlias(
-      const android::StringPiece& alias) const = 0;
+      android::StringPiece alias) const = 0;
 };
 
 // Helper function for transforming the original Reference inRef to a fully qualified reference
diff --git a/tools/fonts/font-scaling-array-generator.js b/tools/fonts/font-scaling-array-generator.js
new file mode 100644
index 0000000..9754697
--- /dev/null
+++ b/tools/fonts/font-scaling-array-generator.js
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+
+/**
+  Generates arrays for non-linear font scaling, to be pasted into
+  frameworks/base/core/java/android/content/res/FontScaleConverterFactory.java
+
+  To use:
+    `node font-scaling-array-generator.js`
+    or just open a browser, open DevTools, and paste into the Console.
+*/
+
+/**
+ * Modify this to match your packages/apps/Settings/res/arrays.xml#entryvalues_font_size
+ * array so that all possible scales are generated.
+ */
+const scales = [1.15, 1.30, 1.5, 1.8, 2];
+
+const commonSpSizes = [8, 10, 12, 14, 18, 20, 24, 30, 100];
+
+/**
+ * Enum for GENERATION_STYLE which determines how to generate the arrays.
+ */
+const GenerationStyle = {
+  /**
+   * Interpolates between hand-tweaked curves. This is the best option and
+   * shouldn't require any additional tweaking.
+   */
+  CUSTOM_TWEAKED: 'CUSTOM_TWEAKED',
+
+  /**
+   * Uses a curve equation that is mostly correct, but will need manual tweaking
+   * at some scales.
+   */
+  CURVE: 'CURVE',
+
+  /**
+   * Uses straight linear multiplication. Good starting point for manual
+   * tweaking.
+   */
+  LINEAR: 'LINEAR'
+}
+
+/**
+ * Determines how arrays are generated. Must be one of the GenerationStyle
+ * values.
+ */
+const GENERATION_STYLE = GenerationStyle.CUSTOM_TWEAKED;
+
+// These are hand-tweaked curves from which we will derive the other
+// interstitial curves using linear interpolation, in the case of using
+// GenerationStyle.CUSTOM_TWEAKED.
+const interpolationTargets = {
+  1.0: commonSpSizes,
+  1.5: [12, 15, 18, 22, 24, 26, 28, 30, 100],
+  2.0: [16, 20, 24, 26, 30, 34, 36, 38, 100]
+};
+
+/**
+ * Interpolate a value with specified extrema, to a new value between new
+ * extrema.
+ *
+ * @param value the current value
+ * @param inputMin minimum the input value can reach
+ * @param inputMax maximum the input value can reach
+ * @param outputMin minimum the output value can reach
+ * @param outputMax maximum the output value can reach
+ */
+function map(value, inputMin, inputMax, outputMin, outputMax) {
+  return outputMin + (outputMax - outputMin) * ((value - inputMin) / (inputMax - inputMin));
+}
+
+/***
+ * Interpolate between values a and b.
+ */
+function lerp(a, b, fraction) {
+  return (a * (1.0 - fraction)) + (b * fraction);
+}
+
+function generateRatios(scale) {
+  // Find the best two arrays to interpolate between.
+  let startTarget, endTarget;
+  let startTargetScale, endTargetScale;
+  const targetScales = Object.keys(interpolationTargets).sort();
+  for (let i = 0; i < targetScales.length - 1; i++) {
+    const targetScaleKey = targetScales[i];
+    const targetScale = parseFloat(targetScaleKey, 10);
+    const startTargetScaleKey = targetScaleKey;
+    const endTargetScaleKey = targetScales[i + 1];
+
+    if (scale < parseFloat(startTargetScaleKey, 10)) {
+      break;
+    }
+
+    startTargetScale = parseFloat(startTargetScaleKey, 10);
+    endTargetScale = parseFloat(endTargetScaleKey, 10);
+    startTarget = interpolationTargets[startTargetScaleKey];
+    endTarget = interpolationTargets[endTargetScaleKey];
+  }
+  const interpolationProgress = map(scale, startTargetScale, endTargetScale, 0, 1);
+
+  return commonSpSizes.map((sp, i) => {
+    const originalSizeDp = sp;
+    let newSizeDp;
+    switch (GENERATION_STYLE) {
+      case GenerationStyle.CUSTOM_TWEAKED:
+        newSizeDp = lerp(startTarget[i], endTarget[i], interpolationProgress);
+        break;
+      case GenerationStyle.CURVE: {
+        let coeff1;
+        let coeff2;
+        if (scale < 1) {
+          // \left(1.22^{-\left(x+5\right)}+0.5\right)\cdot x
+          coeff1 = -5;
+          coeff2 = scale;
+        } else {
+          // (1.22^{-\left(x-10\right)}+1\right)\cdot x
+          coeff1 = map(scale, 1, 2, 2, 8);
+          coeff2 = 1;
+        }
+        newSizeDp = ((Math.pow(1.22, (-(originalSizeDp - coeff1))) + coeff2) * originalSizeDp);
+        break;
+      }
+      case GenerationStyle.LINEAR:
+        newSizeDp = originalSizeDp * scale;
+        break;
+      default:
+        throw new Error('Invalid GENERATION_STYLE');
+    }
+    return {
+      fromSp: sp,
+      toDp: newSizeDp
+    }
+  });
+}
+
+const scaleArrays =
+    scales
+        .map(scale => {
+          const scaleString = (scale * 100).toFixed(0);
+          return {
+            scale,
+            name: `font_size_original_sp_to_scaled_dp_${scaleString}_percent`
+          }
+        })
+        .map(scaleArray => {
+          const items = generateRatios(scaleArray.scale);
+
+          return {
+            ...scaleArray,
+            items
+          }
+        });
+
+function formatDigit(d) {
+  const twoSignificantDigits = Math.round(d * 100) / 100;
+  return String(twoSignificantDigits).padStart(4, ' ');
+}
+
+console.log(
+    '' +
+    scaleArrays.reduce(
+        (previousScaleArray, currentScaleArray) => {
+          const itemsFromSp = currentScaleArray.items.map(d => d.fromSp)
+                                .map(formatDigit)
+                                .join('f, ');
+          const itemsToDp = currentScaleArray.items.map(d => d.toDp)
+                                .map(formatDigit)
+                                .join('f, ');
+
+          return previousScaleArray + `
+        put(
+                /* scaleKey= */ ${currentScaleArray.scale}f,
+                new FontScaleConverter(
+                        /* fromSp= */
+                        new float[] {${itemsFromSp}},
+                        /* toDp=   */
+                        new float[] {${itemsToDp}})
+        );
+     `;
+        },
+        ''));
diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py
index 35a0ce6..006a0290 100755
--- a/tools/fonts/fontchain_linter.py
+++ b/tools/fonts/fontchain_linter.py
@@ -54,6 +54,7 @@
     'or': 'Orya',
     'pa': 'Guru',
     'pt': 'Latn',
+    'pl': 'Latn',
     'ru': 'Latn',
     'sk': 'Latn',
     'sl': 'Latn',
@@ -381,62 +382,14 @@
         return tuple(f"{s:X}" for s in sequence)
     return hex(sequence)
 
-def check_plausible_compat_pua(coverage, all_emoji, equivalent_emoji):
-    # A PUA should point to every RGI emoji and that PUA should be unique to the
-    # set of equivalent sequences for the emoji.
-    problems = []
-    for seq in all_emoji:
-        # We're looking to match not-PUA with PUA so filter out existing PUA
-        if contains_pua(seq):
-            continue
-
-        # Filter out non-RGI things that end up in all_emoji
-        if only_tags(seq) or seq in {ZWJ, COMBINING_KEYCAP, EMPTY_FLAG_SEQUENCE}:
-            continue
-
-        equivalents = [seq]
-        if seq in equivalent_emoji:
-            equivalents.append(equivalent_emoji[seq])
-
-        # If there are problems the hex code is much more useful
-        log_equivalents = [hex_strs(s) for s in equivalents]
-
-        # The system compat font should NOT include regional indicators as these have been split out
-        if contains_regional_indicator(seq):
-            assert not any(s in coverage for s in equivalents), f"Regional indicators not expected in compat font, found {log_equivalents}"
-            continue
-
-        glyph = {coverage[e] for e in equivalents}
-        if len(glyph) != 1:
-            problems.append(f"{log_equivalents} should all point to the same glyph")
-            continue
-        glyph = next(iter(glyph))
-
-        pua = {s for s, g in coverage.items() if contains_pua(s) and g == glyph}
-        if not pua:
-            problems.append(f"Expected PUA for {log_equivalents} but none exist")
-            continue
-
-    assert not problems, "\n".join(sorted(problems)) + f"\n{len(problems)} PUA problems"
-
-def check_emoji_compat(all_emoji, equivalent_emoji):
+def check_emoji_not_compat(all_emoji, equivalent_emoji):
     compat_psnames = set()
     for emoji_font in get_emoji_fonts():
         ttf = open_font(emoji_font)
         psname = get_psname(ttf)
 
-        is_compat_font = "meta" in ttf and 'Emji' in ttf["meta"].data
-        if not is_compat_font:
-            continue
-        compat_psnames.add(psname)
-
-        # If the font has compat metadata it should have PUAs for emoji sequences
-        coverage = get_emoji_map(emoji_font)
-        check_plausible_compat_pua(coverage, all_emoji, equivalent_emoji)
-
-
-    # NotoColorEmoji must be a Compat font.
-    assert 'NotoColorEmoji' in compat_psnames, 'NotoColorEmoji MUST be a compat font'
+        if "meta" in ttf:
+            assert 'Emji' not in ttf["meta"].data, 'NotoColorEmoji MUST be a compat font'
 
 
 def check_emoji_font_coverage(emoji_fonts, all_emoji, equivalent_emoji):
@@ -847,7 +800,7 @@
         ucd_path = sys.argv[3]
         parse_ucd(ucd_path)
         all_emoji, default_emoji, equivalent_emoji = compute_expected_emoji()
-        check_emoji_compat(all_emoji, equivalent_emoji)
+        check_emoji_not_compat(all_emoji, equivalent_emoji)
         check_emoji_coverage(all_emoji, equivalent_emoji)
         check_emoji_defaults(default_emoji)
 
diff --git a/tools/lint/Android.bp b/tools/lint/Android.bp
deleted file mode 100644
index 96618f4..0000000
--- a/tools/lint/Android.bp
+++ /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 {
-    // 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"],
-}
-
-java_library_host {
-    name: "AndroidFrameworkLintChecker",
-    srcs: ["checks/src/main/java/**/*.kt"],
-    plugins: ["auto_service_plugin"],
-    libs: [
-        "auto_service_annotations",
-        "lint_api",
-    ],
-    kotlincflags: ["-Xjvm-default=all"],
-}
-
-java_test_host {
-    name: "AndroidFrameworkLintCheckerTest",
-    // TODO(b/239881504): Since this test was written, Android
-    // Lint was updated, and now includes classes that were
-    // compiled for java 15. The soong build doesn't support
-    // java 15 yet, so we can't compile against "lint". Disable
-    // the test until java 15 is supported.
-    enabled: false,
-    srcs: ["checks/src/test/java/**/*.kt"],
-    static_libs: [
-        "AndroidFrameworkLintChecker",
-        "junit",
-        "lint",
-        "lint_tests",
-    ],
-    test_options: {
-        unit_test: true,
-    },
-}
-
-python_binary_host {
-    name: "lint_fix",
-    main: "fix/lint_fix.py",
-    srcs: ["fix/lint_fix.py"],
-}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
deleted file mode 100644
index 4d69d26..0000000
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ /dev/null
@@ -1,60 +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.google.android.lint
-
-import com.android.tools.lint.client.api.IssueRegistry
-import com.android.tools.lint.client.api.Vendor
-import com.android.tools.lint.detector.api.CURRENT_API
-import com.google.android.lint.aidl.EnforcePermissionDetector
-import com.google.android.lint.aidl.ManualPermissionCheckDetector
-import com.google.android.lint.parcel.SaferParcelChecker
-import com.google.auto.service.AutoService
-
-@AutoService(IssueRegistry::class)
-@Suppress("UnstableApiUsage")
-class AndroidFrameworkIssueRegistry : IssueRegistry() {
-    override val issues = listOf(
-        CallingIdentityTokenDetector.ISSUE_UNUSED_TOKEN,
-        CallingIdentityTokenDetector.ISSUE_NON_FINAL_TOKEN,
-        CallingIdentityTokenDetector.ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
-        CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
-        CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
-        CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY,
-        CallingIdentityTokenDetector.ISSUE_RESULT_OF_CLEAR_IDENTITY_CALL_NOT_STORED_IN_VARIABLE,
-        CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
-        EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
-        EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
-        ManualPermissionCheckDetector.ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
-        SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
-        PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
-        RegisterReceiverFlagDetector.ISSUE_RECEIVER_EXPORTED_FLAG,
-        PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE,
-        PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD,
-    )
-
-    override val api: Int
-        get() = CURRENT_API
-
-    override val minApi: Int
-        get() = 8
-
-    override val vendor: Vendor = Vendor(
-        vendorName = "Android",
-        feedbackUrl = "http://b/issues/new?component=315013",
-        contact = "brufino@google.com"
-    )
-}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
deleted file mode 100644
index 1b0f035..0000000
--- a/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
+++ /dev/null
@@ -1,197 +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.google.android.lint
-
-import com.android.tools.lint.client.api.UElementHandler
-import com.android.tools.lint.detector.api.Category
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Implementation
-import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.SourceCodeScanner
-import com.android.tools.lint.detector.api.getUMethod
-import com.google.android.lint.aidl.hasPermissionMethodAnnotation
-import com.intellij.psi.PsiType
-import org.jetbrains.uast.UAnnotation
-import org.jetbrains.uast.UBlockExpression
-import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.UExpression
-import org.jetbrains.uast.UIfExpression
-import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.UQualifiedReferenceExpression
-import org.jetbrains.uast.UReturnExpression
-import org.jetbrains.uast.getContainingUMethod
-
-/**
- * Stops incorrect usage of {@link PermissionMethod}
- * TODO: add tests once re-enabled (b/240445172, b/247542171)
- */
-class PermissionMethodDetector : Detector(), SourceCodeScanner {
-
-    override fun getApplicableUastTypes(): List<Class<out UElement>> =
-        listOf(UAnnotation::class.java, UMethod::class.java)
-
-    override fun createUastHandler(context: JavaContext): UElementHandler =
-        PermissionMethodHandler(context)
-
-    private inner class PermissionMethodHandler(val context: JavaContext) : UElementHandler() {
-        override fun visitMethod(node: UMethod) {
-            if (hasPermissionMethodAnnotation(node)) return
-            if (onlyCallsPermissionMethod(node)) {
-                val location = context.getLocation(node.javaPsi.modifierList)
-                val fix = fix()
-                    .annotate(ANNOTATION_PERMISSION_METHOD)
-                    .range(location)
-                    .autoFix()
-                    .build()
-
-                context.report(
-                    ISSUE_CAN_BE_PERMISSION_METHOD,
-                    location,
-                    "Annotate method with @PermissionMethod",
-                    fix
-                )
-            }
-        }
-
-        override fun visitAnnotation(node: UAnnotation) {
-            if (node.qualifiedName != ANNOTATION_PERMISSION_METHOD) return
-            val method = node.getContainingUMethod() ?: return
-
-            if (!isPermissionMethodReturnType(method)) {
-                context.report(
-                    ISSUE_PERMISSION_METHOD_USAGE,
-                    context.getLocation(node),
-                    """
-                            Methods annotated with `@PermissionMethod` should return `void`, \
-                            `boolean`, or `@PackageManager.PermissionResult int`."
-                    """.trimIndent()
-                )
-            }
-
-            if (method.returnType == PsiType.INT &&
-                method.annotations.none { it.hasQualifiedName(ANNOTATION_PERMISSION_RESULT) }
-            ) {
-                context.report(
-                    ISSUE_PERMISSION_METHOD_USAGE,
-                    context.getLocation(node),
-                    """
-                            Methods annotated with `@PermissionMethod` that return `int` should \
-                            also be annotated with `@PackageManager.PermissionResult.`"
-                    """.trimIndent()
-                )
-            }
-        }
-    }
-
-    companion object {
-
-        private val EXPLANATION_PERMISSION_METHOD_USAGE = """
-            `@PermissionMethod` should annotate methods that ONLY perform permission lookups. \
-            Said methods should return `boolean`, `@PackageManager.PermissionResult int`, or return \
-            `void` and potentially throw `SecurityException`.
-        """.trimIndent()
-
-        @JvmField
-        val ISSUE_PERMISSION_METHOD_USAGE = Issue.create(
-            id = "PermissionMethodUsage",
-            briefDescription = "@PermissionMethod used incorrectly",
-            explanation = EXPLANATION_PERMISSION_METHOD_USAGE,
-            category = Category.CORRECTNESS,
-            priority = 5,
-            severity = Severity.ERROR,
-            implementation = Implementation(
-                PermissionMethodDetector::class.java,
-                Scope.JAVA_FILE_SCOPE
-            ),
-            enabledByDefault = true
-        )
-
-        private val EXPLANATION_CAN_BE_PERMISSION_METHOD = """
-            Methods that only call other methods annotated with @PermissionMethod (and do NOTHING else) can themselves \
-            be annotated with @PermissionMethod.  For example:
-            ```
-            void wrapperHelper() {
-              // Context.enforceCallingPermission is annotated with @PermissionMethod
-              context.enforceCallingPermission(SOME_PERMISSION)
-            }
-            ```
-        """.trimIndent()
-
-        @JvmField
-        val ISSUE_CAN_BE_PERMISSION_METHOD = Issue.create(
-            id = "CanBePermissionMethod",
-            briefDescription = "Method can be annotated with @PermissionMethod",
-            explanation = EXPLANATION_CAN_BE_PERMISSION_METHOD,
-            category = Category.SECURITY,
-            priority = 5,
-            severity = Severity.WARNING,
-            implementation = Implementation(
-                PermissionMethodDetector::class.java,
-                Scope.JAVA_FILE_SCOPE
-            ),
-            enabledByDefault = false
-        )
-
-        private fun isPermissionMethodReturnType(method: UMethod): Boolean =
-            listOf(PsiType.VOID, PsiType.INT, PsiType.BOOLEAN).contains(method.returnType)
-
-        /**
-         * Identifies methods that...
-         * DO call other methods annotated with @PermissionMethod
-         * DO NOT do anything else
-         */
-        private fun onlyCallsPermissionMethod(method: UMethod): Boolean {
-            val body = method.uastBody as? UBlockExpression ?: return false
-            if (body.expressions.isEmpty()) return false
-            for (expression in body.expressions) {
-                when (expression) {
-                    is UQualifiedReferenceExpression -> {
-                        if (!isPermissionMethodCall(expression.selector)) return false
-                    }
-                    is UReturnExpression -> {
-                        if (!isPermissionMethodCall(expression.returnExpression)) return false
-                    }
-                    is UCallExpression -> {
-                        if (!isPermissionMethodCall(expression)) return false
-                    }
-                    is UIfExpression -> {
-                        if (expression.thenExpression !is UReturnExpression) return false
-                        if (!isPermissionMethodCall(expression.condition)) return false
-                    }
-                    else -> return false
-                }
-            }
-            return true
-        }
-
-        private fun isPermissionMethodCall(expression: UExpression?): Boolean {
-            return when (expression) {
-                is UQualifiedReferenceExpression ->
-                    return isPermissionMethodCall(expression.selector)
-                is UCallExpression -> {
-                    val calledMethod = expression.resolve()?.getUMethod() ?: return false
-                    return hasPermissionMethodAnnotation(calledMethod)
-                }
-                else -> false
-            }
-        }
-    }
-}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
deleted file mode 100644
index d120e1d..0000000
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
+++ /dev/null
@@ -1,137 +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.google.android.lint.aidl
-
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Location
-import com.android.tools.lint.detector.api.getUMethod
-import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
-import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.evaluateString
-import org.jetbrains.uast.visitor.AbstractUastVisitor
-
-/**
- * Helper class that facilitates the creation of lint auto fixes
- *
- * Handles "Single" permission checks that should be migrated to @EnforcePermission(...), as well as consecutive checks
- * that should be migrated to @EnforcePermission(allOf={...})
- *
- * TODO: handle anyOf style annotations
- */
-data class EnforcePermissionFix(
-    val locations: List<Location>,
-    val permissionNames: List<String>
-) {
-    val annotation: String
-        get() {
-            val quotedPermissions = permissionNames.joinToString(", ") { """"$it"""" }
-            val annotationParameter =
-                if (permissionNames.size > 1) "allOf={$quotedPermissions}" else quotedPermissions
-            return "@$ANNOTATION_ENFORCE_PERMISSION($annotationParameter)"
-        }
-
-    companion object {
-        /**
-         * conditionally constructs EnforcePermissionFix from a UCallExpression
-         * @return EnforcePermissionFix if the called method is annotated with @PermissionMethod, else null
-         */
-        fun fromCallExpression(
-            context: JavaContext,
-            callExpression: UCallExpression
-        ): EnforcePermissionFix? =
-            if (isPermissionMethodCall(callExpression)) {
-                EnforcePermissionFix(
-                    listOf(getPermissionCheckLocation(context, callExpression)),
-                    getPermissionCheckValues(callExpression)
-                )
-            } else null
-
-
-        fun compose(individuals: List<EnforcePermissionFix>): EnforcePermissionFix =
-            EnforcePermissionFix(
-                individuals.flatMap { it.locations },
-                individuals.flatMap { it.permissionNames }
-            )
-
-        /**
-         * Given a permission check, get its proper location
-         * so that a lint fix can remove the entire expression
-         */
-        private fun getPermissionCheckLocation(
-            context: JavaContext,
-            callExpression: UCallExpression
-        ):
-                Location {
-            val javaPsi = callExpression.javaPsi!!
-            return Location.create(
-                context.file,
-                javaPsi.containingFile?.text,
-                javaPsi.textRange.startOffset,
-                // unfortunately the element doesn't include the ending semicolon
-                javaPsi.textRange.endOffset + 1
-            )
-        }
-
-        /**
-         * Given a @PermissionMethod, find arguments annotated with @PermissionName
-         * and pull out the permission value(s) being used.  Also evaluates nested calls
-         * to @PermissionMethod(s) in the given method's body.
-         */
-        private fun getPermissionCheckValues(
-            callExpression: UCallExpression
-        ): List<String> {
-            if (!isPermissionMethodCall(callExpression)) return emptyList()
-
-            val result = mutableSetOf<String>() // protect against duplicate permission values
-            val visitedCalls = mutableSetOf<UCallExpression>() // don't visit the same call twice
-            val bfsQueue = ArrayDeque(listOf(callExpression))
-
-            // Breadth First Search - evalutaing nested @PermissionMethod(s) in the available
-            // source code for @PermissionName(s).
-            while (bfsQueue.isNotEmpty()) {
-                val current = bfsQueue.removeFirst()
-                visitedCalls.add(current)
-                result.addAll(findPermissions(current))
-
-                current.resolve()?.getUMethod()?.accept(object : AbstractUastVisitor() {
-                    override fun visitCallExpression(node: UCallExpression): Boolean {
-                        if (isPermissionMethodCall(node) && node !in visitedCalls) {
-                            bfsQueue.add(node)
-                        }
-                        return false
-                    }
-                })
-            }
-
-            return result.toList()
-        }
-
-        private fun findPermissions(
-            callExpression: UCallExpression,
-        ): List<String> {
-            val indices = callExpression.resolve()?.getUMethod()
-                ?.uastParameters
-                ?.filter(::hasPermissionNameAnnotation)
-                ?.mapNotNull { it.sourcePsi?.parameterIndex() }
-                ?: emptyList()
-
-            return indices.mapNotNull {
-                callExpression.getArgumentForParameter(it)?.evaluateString()
-            }
-        }
-    }
-}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
deleted file mode 100644
index edbdd8d..0000000
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
+++ /dev/null
@@ -1,67 +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.google.android.lint.aidl
-
-import com.android.tools.lint.detector.api.getUMethod
-import com.google.android.lint.ANNOTATION_PERMISSION_METHOD
-import com.google.android.lint.ANNOTATION_PERMISSION_NAME
-import com.google.android.lint.CLASS_STUB
-import com.intellij.psi.PsiAnonymousClass
-import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.UParameter
-
-/**
- * Given a UMethod, determine if this method is
- * an entrypoint to an interface generated by AIDL,
- * returning the interface name if so
- */
-fun getContainingAidlInterface(node: UMethod): String? {
-    if (!isInClassCalledStub(node)) return null
-    for (superMethod in node.findSuperMethods()) {
-        for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
-            ?: continue) {
-            if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) {
-                return superMethod.containingClass?.name
-            }
-        }
-    }
-    return null
-}
-
-private fun isInClassCalledStub(node: UMethod): Boolean {
-    (node.containingClass as? PsiAnonymousClass)?.let {
-        return it.baseClassReference.referenceName == CLASS_STUB
-    }
-    return node.containingClass?.extendsList?.referenceElements?.any {
-        it.referenceName == CLASS_STUB
-    } ?: false
-}
-
-fun isPermissionMethodCall(callExpression: UCallExpression): Boolean {
-    val method = callExpression.resolve()?.getUMethod() ?: return false
-    return hasPermissionMethodAnnotation(method)
-}
-
-fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
-    .any {
-        it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD)
-    }
-
-fun hasPermissionNameAnnotation(parameter: UParameter) = parameter.annotations.any {
-    it.hasQualifiedName(ANNOTATION_PERMISSION_NAME)
-}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
deleted file mode 100644
index 2c53f39..0000000
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
+++ /dev/null
@@ -1,158 +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.google.android.lint.aidl
-
-import com.android.tools.lint.client.api.UElementHandler
-import com.android.tools.lint.detector.api.Category
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Implementation
-import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.SourceCodeScanner
-import org.jetbrains.uast.UBlockExpression
-import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.UIfExpression
-import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.UQualifiedReferenceExpression
-
-/**
- * Looks for methods implementing generated AIDL interface stubs
- * that can have simple permission checks migrated to
- * @EnforcePermission annotations
- *
- * TODO: b/242564870 (enable parse and autoFix of .aidl files)
- */
-@Suppress("UnstableApiUsage")
-class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
-    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
-        listOf(UMethod::class.java)
-
-    override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
-
-    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
-        override fun visitMethod(node: UMethod) {
-            val interfaceName = getContainingAidlInterface(node)
-                .takeUnless(EXCLUDED_CPP_INTERFACES::contains) ?: return
-            val body = (node.uastBody as? UBlockExpression) ?: return
-            val fix = accumulateSimplePermissionCheckFixes(body) ?: return
-
-            val javaRemoveFixes = fix.locations.map {
-                fix()
-                    .replace()
-                    .reformat(true)
-                    .range(it)
-                    .with("")
-                    .autoFix()
-                    .build()
-            }
-
-            val javaAnnotateFix = fix()
-                .annotate(fix.annotation)
-                .range(context.getLocation(node))
-                .autoFix()
-                .build()
-
-            val message =
-                "$interfaceName permission check can be converted to @EnforcePermission annotation"
-
-            context.report(
-                ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
-                fix.locations.last(),
-                message,
-                fix().composite(*javaRemoveFixes.toTypedArray(), javaAnnotateFix)
-            )
-        }
-
-        /**
-         * Walk the expressions in the method, looking for simple permission checks.
-         *
-         * If a single permission check is found at the beginning of the method,
-         * this should be migrated to @EnforcePermission(value).
-         *
-         * If multiple consecutive permission checks are found,
-         * these should be migrated to @EnforcePermission(allOf={value1, value2, ...})
-         *
-         * As soon as something other than a permission check is encountered, stop looking,
-         * as some other business logic is happening that prevents an automated fix.
-         */
-        private fun accumulateSimplePermissionCheckFixes(methodBody: UBlockExpression):
-                EnforcePermissionFix? {
-            val singleFixes = mutableListOf<EnforcePermissionFix>()
-            for (expression in methodBody.expressions) {
-                singleFixes.add(getPermissionCheckFix(expression) ?: break)
-            }
-            return when (singleFixes.size) {
-                0 -> null
-                1 -> singleFixes[0]
-                else -> EnforcePermissionFix.compose(singleFixes)
-            }
-        }
-
-        /**
-         * If an expression boils down to a permission check, return
-         * the helper for creating a lint auto fix to @EnforcePermission
-         */
-        private fun getPermissionCheckFix(startingExpression: UElement?):
-                EnforcePermissionFix? {
-            return when (startingExpression) {
-                is UQualifiedReferenceExpression -> getPermissionCheckFix(
-                    startingExpression.selector
-                )
-
-                is UIfExpression -> getPermissionCheckFix(startingExpression.condition)
-
-                is UCallExpression -> return EnforcePermissionFix
-                            .fromCallExpression(context, startingExpression)
-
-                else -> null
-            }
-        }
-    }
-
-    companion object {
-
-        private val EXPLANATION = """
-            Whenever possible, method implementations of AIDL interfaces should use the @EnforcePermission
-            annotation to declare the permissions to be enforced.  The verification code is then
-            generated by the AIDL compiler, which also takes care of annotating the generated java
-            code.
-
-            This reduces the risk of bugs around these permission checks (that often become vulnerabilities).
-            It also enables easier auditing and review.
-
-            Please migrate to an @EnforcePermission annotation. (See: go/aidl-enforce-howto)
-        """.trimIndent()
-
-        @JvmField
-        val ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION = Issue.create(
-            id = "UseEnforcePermissionAnnotation",
-            briefDescription = "Manual permission check can be @EnforcePermission annotation",
-            explanation = EXPLANATION,
-            category = Category.SECURITY,
-            priority = 5,
-            severity = Severity.WARNING,
-            implementation = Implementation(
-                ManualPermissionCheckDetector::class.java,
-                Scope.JAVA_FILE_SCOPE
-            ),
-            enabledByDefault = false, // TODO: enable once b/241171714 is resolved
-        )
-    }
-}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
deleted file mode 100644
index a968f5e..0000000
--- a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
+++ /dev/null
@@ -1,448 +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.google.android.lint.aidl
-
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
-import com.android.tools.lint.checks.infrastructure.TestLintTask
-import com.android.tools.lint.checks.infrastructure.TestMode
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Issue
-
-@Suppress("UnstableApiUsage")
-class ManualPermissionCheckDetectorTest : LintDetectorTest() {
-    override fun getDetector(): Detector = ManualPermissionCheckDetector()
-    override fun getIssues(): List<Issue> = listOf(
-        ManualPermissionCheckDetector
-            .ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION
-    )
-
-    override fun lint(): TestLintTask = super.lint().allowMissingSdk()
-
-    fun testClass() {
-        lint().files(
-            java(
-                """
-                    import android.content.Context;
-                    import android.test.ITest;
-                    public class Foo extends ITest.Stub {
-                        private Context mContext;
-                        @Override
-                        public void test() throws android.os.RemoteException {
-                            mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
-                        }
-                    }
-                """
-            ).indented(),
-            *stubs
-        )
-            .run()
-            .expect(
-                """
-                src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
-                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
-                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-                0 errors, 1 warnings
-                """
-            )
-            .expectFixDiffs(
-                """
-                Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
-                @@ -5 +5
-                +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
-                @@ -7 +8
-                -         mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
-                """
-            )
-    }
-
-    fun testAnonClass() {
-        lint().files(
-            java(
-                """
-                    import android.content.Context;
-                    import android.test.ITest;
-                    public class Foo {
-                        private Context mContext;
-                        private ITest itest = new ITest.Stub() {
-                            @Override
-                            public void test() throws android.os.RemoteException {
-                                mContext.enforceCallingOrSelfPermission(
-                                    "android.permission.READ_CONTACTS", "foo");
-                            }
-                        };
-                    }
-                """
-            ).indented(),
-            *stubs
-        )
-            .run()
-            .expect(
-                """
-                src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
-                            mContext.enforceCallingOrSelfPermission(
-                            ^
-                0 errors, 1 warnings
-                """
-            )
-            .expectFixDiffs(
-                """
-                Fix for src/Foo.java line 8: Annotate with @EnforcePermission:
-                @@ -6 +6
-                +         @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
-                @@ -8 +9
-                -             mContext.enforceCallingOrSelfPermission(
-                -                 "android.permission.READ_CONTACTS", "foo");
-                """
-            )
-    }
-
-    fun testConstantEvaluation() {
-        lint().files(
-            java(
-                """
-                    import android.content.Context;
-                    import android.test.ITest;
-
-                    public class Foo extends ITest.Stub {
-                        private Context mContext;
-                        @Override
-                        public void test() throws android.os.RemoteException {
-                            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
-                        }
-                    }
-                """
-            ).indented(),
-            *stubs,
-            manifestStub
-        )
-            .run()
-            .expect(
-                """
-                src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
-                        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
-                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-                0 errors, 1 warnings
-                """
-            )
-            .expectFixDiffs(
-                """
-                Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
-                @@ -6 +6
-                +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
-                @@ -8 +9
-                -         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
-                """
-            )
-    }
-
-    fun testAllOf() {
-        lint().files(
-            java(
-                """
-                    import android.content.Context;
-                    import android.test.ITest;
-                    public class Foo {
-                        private Context mContext;
-                        private ITest itest = new ITest.Stub() {
-                            @Override
-                            public void test() throws android.os.RemoteException {
-                                mContext.enforceCallingOrSelfPermission(
-                                    "android.permission.READ_CONTACTS", "foo");
-                                mContext.enforceCallingOrSelfPermission(
-                                    "android.permission.WRITE_CONTACTS", "foo");
-                            }
-                        };
-                    }
-                """
-            ).indented(),
-            *stubs
-        )
-            .run()
-            .expect(
-                """
-                src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
-                            mContext.enforceCallingOrSelfPermission(
-                            ^
-                0 errors, 1 warnings
-                """
-            )
-            .expectFixDiffs(
-                """
-                Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
-                @@ -6 +6
-                +         @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"})
-                @@ -8 +9
-                -             mContext.enforceCallingOrSelfPermission(
-                -                 "android.permission.READ_CONTACTS", "foo");
-                -             mContext.enforceCallingOrSelfPermission(
-                -                 "android.permission.WRITE_CONTACTS", "foo");
-                """
-            )
-    }
-
-    fun testPrecedingExpressions() {
-        lint().files(
-            java(
-                """
-                    import android.os.Binder;
-                    import android.test.ITest;
-                    public class Foo extends ITest.Stub {
-                        private mContext Context;
-                        @Override
-                        public void test() throws android.os.RemoteException {
-                            long uid = Binder.getCallingUid();
-                            mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
-                        }
-                    }
-                """
-            ).indented(),
-            *stubs
-        )
-            .run()
-            .expectClean()
-    }
-
-    fun testPermissionHelper() {
-        lint().skipTestModes(TestMode.PARENTHESIZED).files(
-            java(
-                """
-                    import android.content.Context;
-                    import android.test.ITest;
-
-                    public class Foo extends ITest.Stub {
-                        private Context mContext;
-
-                        @android.content.pm.PermissionMethod
-                        private void helper() {
-                            mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
-                        }
-
-                        @Override
-                        public void test() throws android.os.RemoteException {
-                            helper();
-                        }
-                    }
-                """
-            ).indented(),
-            *stubs
-        )
-            .run()
-            .expect(
-                """
-                src/Foo.java:14: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
-                        helper();
-                        ~~~~~~~~~
-                0 errors, 1 warnings
-                """
-            )
-            .expectFixDiffs(
-                """
-                Fix for src/Foo.java line 14: Annotate with @EnforcePermission:
-                @@ -12 +12
-                +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
-                @@ -14 +15
-                -         helper();
-                """
-            )
-    }
-
-    fun testPermissionHelperAllOf() {
-        lint().skipTestModes(TestMode.PARENTHESIZED).files(
-            java(
-                """
-                import android.content.Context;
-                import android.test.ITest;
-
-                public class Foo extends ITest.Stub {
-                    private Context mContext;
-
-                    @android.content.pm.PermissionMethod
-                    private void helper() {
-                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
-                        mContext.enforceCallingOrSelfPermission("android.permission.WRITE_CONTACTS", "foo");
-                    }
-
-                    @Override
-                    public void test() throws android.os.RemoteException {
-                        helper();
-                        mContext.enforceCallingOrSelfPermission("FOO", "foo");
-                    }
-                }
-                """
-            ).indented(),
-            *stubs
-        )
-            .run()
-            .expect(
-                """
-                src/Foo.java:16: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
-                        mContext.enforceCallingOrSelfPermission("FOO", "foo");
-                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-                0 errors, 1 warnings
-                """
-            )
-            .expectFixDiffs(
-                """
-                Fix for src/Foo.java line 16: Annotate with @EnforcePermission:
-                @@ -13 +13
-                +     @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS", "FOO"})
-                @@ -15 +16
-                -         helper();
-                -         mContext.enforceCallingOrSelfPermission("FOO", "foo");
-                """
-            )
-    }
-
-
-    fun testPermissionHelperNested() {
-        lint().skipTestModes(TestMode.PARENTHESIZED).files(
-            java(
-                """
-                import android.content.Context;
-                import android.test.ITest;
-
-                public class Foo extends ITest.Stub {
-                    private Context mContext;
-
-                    @android.content.pm.PermissionMethod
-                    private void helperHelper() {
-                        helper("android.permission.WRITE_CONTACTS");
-                    }
-
-                    @android.content.pm.PermissionMethod
-                    private void helper(@android.content.pm.PermissionName String extraPermission) {
-                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
-                    }
-
-                    @Override
-                    public void test() throws android.os.RemoteException {
-                        helperHelper();
-                    }
-                }
-                """
-            ).indented(),
-            *stubs
-        )
-            .run()
-            .expect(
-                """
-                src/Foo.java:19: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
-                        helperHelper();
-                        ~~~~~~~~~~~~~~~
-                0 errors, 1 warnings
-                """
-            )
-            .expectFixDiffs(
-                """
-                Fix for src/Foo.java line 19: Annotate with @EnforcePermission:
-                @@ -17 +17
-                +     @android.annotation.EnforcePermission(allOf={"android.permission.WRITE_CONTACTS", "android.permission.READ_CONTACTS"})
-                @@ -19 +20
-                -         helperHelper();
-                """
-            )
-    }
-
-
-
-    companion object {
-        private val aidlStub: TestFile = java(
-            """
-               package android.test;
-               public interface ITest extends android.os.IInterface {
-                    public static abstract class Stub extends android.os.Binder implements android.test.ITest {}
-                    public void test() throws android.os.RemoteException;
-               }
-            """
-        ).indented()
-
-        private val contextStub: TestFile = java(
-            """
-                package android.content;
-                public class Context {
-                    @android.content.pm.PermissionMethod
-                    public void enforceCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}
-                }
-            """
-        ).indented()
-
-        private val binderStub: TestFile = java(
-            """
-                package android.os;
-                public class Binder {
-                    public static int getCallingUid() {}
-                }
-            """
-        ).indented()
-
-        private val permissionMethodStub: TestFile = java(
-            """
-                package android.content.pm;
-
-                import static java.lang.annotation.ElementType.METHOD;
-                import static java.lang.annotation.RetentionPolicy.CLASS;
-
-                import java.lang.annotation.Retention;
-                import java.lang.annotation.Target;
-
-                @Retention(CLASS)
-                @Target({METHOD})
-                public @interface PermissionMethod {}
-            """
-        ).indented()
-
-        private val permissionNameStub: TestFile = java(
-            """
-                package android.content.pm;
-
-                import static java.lang.annotation.ElementType.FIELD;
-                import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
-                import static java.lang.annotation.ElementType.METHOD;
-                import static java.lang.annotation.ElementType.PARAMETER;
-                import static java.lang.annotation.RetentionPolicy.CLASS;
-
-                import java.lang.annotation.Retention;
-                import java.lang.annotation.Target;
-
-                @Retention(CLASS)
-                @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
-                public @interface PermissionName {}
-            """
-        ).indented()
-
-        private val manifestStub: TestFile = java(
-            """
-                package android;
-
-                public final class Manifest {
-                    public static final class permission {
-                        public static final String READ_CONTACTS="android.permission.READ_CONTACTS";
-                    }
-                }
-            """.trimIndent()
-        )
-
-        val stubs = arrayOf(
-            aidlStub,
-            contextStub,
-            binderStub,
-            permissionMethodStub,
-            permissionNameStub
-        )
-    }
-}
diff --git a/tools/lint/common/Android.bp b/tools/lint/common/Android.bp
new file mode 100644
index 0000000..898f88b
--- /dev/null
+++ b/tools/lint/common/Android.bp
@@ -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 {
+    // 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"],
+}
+
+java_library_host {
+    name: "AndroidCommonLint",
+    srcs: ["src/main/java/**/*.kt"],
+    libs: ["lint_api"],
+    kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt b/tools/lint/common/src/main/java/com/google/android/lint/Constants.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt
rename to tools/lint/common/src/main/java/com/google/android/lint/Constants.kt
diff --git a/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt b/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt
new file mode 100644
index 0000000..0157596
--- /dev/null
+++ b/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.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.google.android.lint
+
+import com.android.tools.lint.detector.api.getUMethod
+import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UParameter
+
+fun isPermissionMethodCall(callExpression: UCallExpression): Boolean {
+    val method = callExpression.resolve()?.getUMethod() ?: return false
+    return hasPermissionMethodAnnotation(method)
+}
+
+fun hasPermissionMethodAnnotation(method: UMethod): Boolean =
+        getPermissionMethodAnnotation(method) != null
+
+fun getPermissionMethodAnnotation(method: UMethod?): UAnnotation? = method?.uAnnotations
+        ?.firstOrNull { it.qualifiedName == ANNOTATION_PERMISSION_METHOD }
+
+fun hasPermissionNameAnnotation(parameter: UParameter) = parameter.annotations.any {
+    it.hasQualifiedName(ANNOTATION_PERMISSION_NAME)
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt b/tools/lint/common/src/main/java/com/google/android/lint/model/Method.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt
rename to tools/lint/common/src/main/java/com/google/android/lint/model/Method.kt
diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp
new file mode 100644
index 0000000..5f6c6f7
--- /dev/null
+++ b/tools/lint/fix/Android.bp
@@ -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 {
+    // 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"],
+}
+
+python_binary_host {
+    name: "lint_fix",
+    main: "lint_fix.py",
+    srcs: ["lint_fix.py"],
+}
diff --git a/tools/lint/framework/Android.bp b/tools/lint/framework/Android.bp
new file mode 100644
index 0000000..7f27e8a
--- /dev/null
+++ b/tools/lint/framework/Android.bp
@@ -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 {
+    // 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"],
+}
+
+java_library_host {
+    name: "AndroidFrameworkLintChecker",
+    srcs: ["checks/src/main/java/**/*.kt"],
+    plugins: ["auto_service_plugin"],
+    libs: [
+        "auto_service_annotations",
+        "lint_api",
+    ],
+    static_libs: [
+        "AndroidCommonLint",
+        // TODO: remove once b/236558918 is resolved and the below checks actually run globally
+        "AndroidGlobalLintChecker",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+}
+
+java_test_host {
+    name: "AndroidFrameworkLintCheckerTest",
+    // TODO(b/239881504): Since this test was written, Android
+    // Lint was updated, and now includes classes that were
+    // compiled for java 15. The soong build doesn't support
+    // java 15 yet, so we can't compile against "lint". Disable
+    // the test until java 15 is supported.
+    enabled: false,
+    srcs: ["checks/src/test/java/**/*.kt"],
+    static_libs: [
+        "AndroidFrameworkLintChecker",
+        "junit",
+        "lint",
+        "lint_tests",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+}
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
new file mode 100644
index 0000000..c5cf0fb
--- /dev/null
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.google.android.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.android.lint.aidl.EnforcePermissionDetector
+import com.google.android.lint.aidl.EnforcePermissionHelperDetector
+import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector
+import com.google.android.lint.parcel.SaferParcelChecker
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+@Suppress("UnstableApiUsage")
+class AndroidFrameworkIssueRegistry : IssueRegistry() {
+    override val issues = listOf(
+        CallingIdentityTokenDetector.ISSUE_UNUSED_TOKEN,
+        CallingIdentityTokenDetector.ISSUE_NON_FINAL_TOKEN,
+        CallingIdentityTokenDetector.ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
+        CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
+        CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
+        CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY,
+        CallingIdentityTokenDetector.ISSUE_RESULT_OF_CLEAR_IDENTITY_CALL_NOT_STORED_IN_VARIABLE,
+        CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
+        EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
+        EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
+        EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+        SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT,
+        SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
+        PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
+        RegisterReceiverFlagDetector.ISSUE_RECEIVER_EXPORTED_FLAG,
+        PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE,
+        PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD,
+    )
+
+    override val api: Int
+        get() = CURRENT_API
+
+    override val minApi: Int
+        get() = 8
+
+    override val vendor: Vendor = Vendor(
+        vendorName = "Android",
+        feedbackUrl = "http://b/issues/new?component=315013",
+        contact = "brufino@google.com"
+    )
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
new file mode 100644
index 0000000..e12ec3d
--- /dev/null
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.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.google.android.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.android.tools.lint.detector.api.getUMethod
+import com.intellij.psi.PsiType
+import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.UIfExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UQualifiedReferenceExpression
+import org.jetbrains.uast.UReturnExpression
+import org.jetbrains.uast.getContainingUMethod
+
+/**
+ * Stops incorrect usage of {@link PermissionMethod}
+ * TODO: add tests once re-enabled (b/240445172, b/247542171)
+ */
+class PermissionMethodDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableUastTypes(): List<Class<out UElement>> =
+        listOf(UAnnotation::class.java, UMethod::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler =
+        PermissionMethodHandler(context)
+
+    private inner class PermissionMethodHandler(val context: JavaContext) : UElementHandler() {
+        override fun visitMethod(node: UMethod) {
+            if (hasPermissionMethodAnnotation(node)) return
+            if (onlyCallsPermissionMethod(node)) {
+                val location = context.getLocation(node.javaPsi.modifierList)
+                val fix = fix()
+                    .annotate(ANNOTATION_PERMISSION_METHOD)
+                    .range(location)
+                    .autoFix()
+                    .build()
+
+                context.report(
+                    ISSUE_CAN_BE_PERMISSION_METHOD,
+                    location,
+                    "Annotate method with @PermissionMethod",
+                    fix
+                )
+            }
+        }
+
+        override fun visitAnnotation(node: UAnnotation) {
+            if (node.qualifiedName != ANNOTATION_PERMISSION_METHOD) return
+            val method = node.getContainingUMethod() ?: return
+
+            if (!isPermissionMethodReturnType(method)) {
+                context.report(
+                    ISSUE_PERMISSION_METHOD_USAGE,
+                    context.getLocation(node),
+                    """
+                            Methods annotated with `@PermissionMethod` should return `void`, \
+                            `boolean`, or `@PackageManager.PermissionResult int`."
+                    """.trimIndent()
+                )
+            }
+
+            if (method.returnType == PsiType.INT &&
+                method.annotations.none { it.hasQualifiedName(ANNOTATION_PERMISSION_RESULT) }
+            ) {
+                context.report(
+                    ISSUE_PERMISSION_METHOD_USAGE,
+                    context.getLocation(node),
+                    """
+                            Methods annotated with `@PermissionMethod` that return `int` should \
+                            also be annotated with `@PackageManager.PermissionResult.`"
+                    """.trimIndent()
+                )
+            }
+        }
+    }
+
+    companion object {
+
+        private val EXPLANATION_PERMISSION_METHOD_USAGE = """
+            `@PermissionMethod` should annotate methods that ONLY perform permission lookups. \
+            Said methods should return `boolean`, `@PackageManager.PermissionResult int`, or return \
+            `void` and potentially throw `SecurityException`.
+        """.trimIndent()
+
+        @JvmField
+        val ISSUE_PERMISSION_METHOD_USAGE = Issue.create(
+            id = "PermissionMethodUsage",
+            briefDescription = "@PermissionMethod used incorrectly",
+            explanation = EXPLANATION_PERMISSION_METHOD_USAGE,
+            category = Category.CORRECTNESS,
+            priority = 5,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                PermissionMethodDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            ),
+            enabledByDefault = true
+        )
+
+        private val EXPLANATION_CAN_BE_PERMISSION_METHOD = """
+            Methods that only call other methods annotated with @PermissionMethod (and do NOTHING else) can themselves \
+            be annotated with @PermissionMethod.  For example:
+            ```
+            void wrapperHelper() {
+              // Context.enforceCallingPermission is annotated with @PermissionMethod
+              context.enforceCallingPermission(SOME_PERMISSION)
+            }
+            ```
+        """.trimIndent()
+
+        @JvmField
+        val ISSUE_CAN_BE_PERMISSION_METHOD = Issue.create(
+            id = "CanBePermissionMethod",
+            briefDescription = "Method can be annotated with @PermissionMethod",
+            explanation = EXPLANATION_CAN_BE_PERMISSION_METHOD,
+            category = Category.SECURITY,
+            priority = 5,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                PermissionMethodDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            ),
+            enabledByDefault = false
+        )
+
+        private fun isPermissionMethodReturnType(method: UMethod): Boolean =
+            listOf(PsiType.VOID, PsiType.INT, PsiType.BOOLEAN).contains(method.returnType)
+
+        /**
+         * Identifies methods that...
+         * DO call other methods annotated with @PermissionMethod
+         * DO NOT do anything else
+         */
+        private fun onlyCallsPermissionMethod(method: UMethod): Boolean {
+            val body = method.uastBody as? UBlockExpression ?: return false
+            if (body.expressions.isEmpty()) return false
+            for (expression in body.expressions) {
+                when (expression) {
+                    is UQualifiedReferenceExpression -> {
+                        if (!isPermissionMethodCall(expression.selector)) return false
+                    }
+                    is UReturnExpression -> {
+                        if (!isPermissionMethodCall(expression.returnExpression)) return false
+                    }
+                    is UCallExpression -> {
+                        if (!isPermissionMethodCall(expression)) return false
+                    }
+                    is UIfExpression -> {
+                        if (expression.thenExpression !is UReturnExpression) return false
+                        if (!isPermissionMethodCall(expression.condition)) return false
+                    }
+                    else -> return false
+                }
+            }
+            return true
+        }
+
+        private fun isPermissionMethodCall(expression: UExpression?): Boolean {
+            return when (expression) {
+                is UQualifiedReferenceExpression ->
+                    return isPermissionMethodCall(expression.selector)
+                is UCallExpression -> {
+                    val calledMethod = expression.resolve()?.getUMethod() ?: return false
+                    return hasPermissionMethodAnnotation(calledMethod)
+                }
+                else -> false
+            }
+        }
+
+        private fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
+                .any { it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD) }
+    }
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/Method.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/Method.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
diff --git a/tools/lint/global/Android.bp b/tools/lint/global/Android.bp
new file mode 100644
index 0000000..3756abe
--- /dev/null
+++ b/tools/lint/global/Android.bp
@@ -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 {
+    // 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"],
+}
+
+java_library_host {
+    name: "AndroidGlobalLintChecker",
+    srcs: ["checks/src/main/java/**/*.kt"],
+    plugins: ["auto_service_plugin"],
+    libs: [
+        "auto_service_annotations",
+        "lint_api",
+    ],
+    static_libs: ["AndroidCommonLint"],
+    kotlincflags: ["-Xjvm-default=all"],
+    dist: {
+        targets: ["droid"],
+    },
+}
+
+java_test_host {
+    name: "AndroidGlobalLintCheckerTest",
+    // TODO(b/239881504): Since this test was written, Android
+    // Lint was updated, and now includes classes that were
+    // compiled for java 15. The soong build doesn't support
+    // java 15 yet, so we can't compile against "lint". Disable
+    // the test until java 15 is supported.
+    enabled: false,
+    srcs: ["checks/src/test/java/**/*.kt"],
+    static_libs: [
+        "AndroidGlobalLintChecker",
+        "junit",
+        "lint",
+        "lint_tests",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
new file mode 100644
index 0000000..a20266a
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.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.google.android.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.android.lint.aidl.EnforcePermissionDetector
+import com.google.android.lint.aidl.EnforcePermissionHelperDetector
+import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+@Suppress("UnstableApiUsage")
+class AndroidGlobalIssueRegistry : IssueRegistry() {
+    override val issues = listOf(
+            EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
+            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
+            EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+            SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT,
+    )
+
+    override val api: Int
+        get() = CURRENT_API
+
+    override val minApi: Int
+        get() = 8
+
+    override val vendor: Vendor = Vendor(
+            vendorName = "Android",
+            feedbackUrl = "http://b/issues/new?component=315013",
+            contact = "repsonsible-apis@google.com"
+    )
+}
\ No newline at end of file
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt
new file mode 100644
index 0000000..227cdcd
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.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.google.android.lint.aidl
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+/**
+ * Abstract class for detectors that look for methods implementing
+ * generated AIDL interface stubs
+ */
+abstract class AidlImplementationDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+            listOf(UMethod::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+        override fun visitMethod(node: UMethod) {
+            val interfaceName = getContainingAidlInterface(node)
+                    .takeUnless(EXCLUDED_CPP_INTERFACES::contains) ?: return
+            val body = (node.uastBody as? UBlockExpression) ?: return
+            visitAidlMethod(context, node, interfaceName, body)
+        }
+    }
+
+    abstract fun visitAidlMethod(
+            context: JavaContext,
+            node: UMethod,
+            interfaceName: String,
+            body: UBlockExpression,
+    )
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
rename to tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
rename to tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
new file mode 100644
index 0000000..ee7dd62
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -0,0 +1,176 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Location
+import com.android.tools.lint.detector.api.UastLintUtils.Companion.getAnnotationBooleanValue
+import com.android.tools.lint.detector.api.getUMethod
+import com.google.android.lint.getPermissionMethodAnnotation
+import com.google.android.lint.hasPermissionNameAnnotation
+import com.google.android.lint.isPermissionMethodCall
+import com.intellij.psi.PsiType
+import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.evaluateString
+import org.jetbrains.uast.visitor.AbstractUastVisitor
+
+/**
+ * Helper class that facilitates the creation of lint auto fixes
+ *
+ * Handles "Single" permission checks that should be migrated to @EnforcePermission(...), as well as consecutive checks
+ * that should be migrated to @EnforcePermission(allOf={...})
+ *
+ * TODO: handle anyOf style annotations
+ */
+data class EnforcePermissionFix(
+    val locations: List<Location>,
+    val permissionNames: List<String>,
+    val errorLevel: Boolean,
+) {
+    fun toLintFix(annotationLocation: Location): LintFix {
+        val removeFixes = this.locations.map {
+            LintFix.create()
+                .replace()
+                .reformat(true)
+                .range(it)
+                .with("")
+                .autoFix()
+                .build()
+        }
+
+        val annotateFix = LintFix.create()
+            .annotate(this.annotation)
+            .range(annotationLocation)
+            .autoFix()
+            .build()
+
+        return LintFix.create().composite(annotateFix, *removeFixes.toTypedArray())
+    }
+
+    private val annotation: String
+        get() {
+            val quotedPermissions = permissionNames.joinToString(", ") { """"$it"""" }
+
+            val annotationParameter =
+                if (permissionNames.size > 1) "allOf={$quotedPermissions}"
+                else quotedPermissions
+
+            return "@$ANNOTATION_ENFORCE_PERMISSION($annotationParameter)"
+        }
+
+    companion object {
+        /**
+         * conditionally constructs EnforcePermissionFix from a UCallExpression
+         * @return EnforcePermissionFix if the called method is annotated with @PermissionMethod, else null
+         */
+        fun fromCallExpression(
+            context: JavaContext,
+            callExpression: UCallExpression
+        ): EnforcePermissionFix? {
+            val method = callExpression.resolve()?.getUMethod() ?: return null
+            val annotation = getPermissionMethodAnnotation(method) ?: return null
+            val enforces = method.returnType == PsiType.VOID
+            val orSelf = getAnnotationBooleanValue(annotation, "orSelf") ?: false
+            return EnforcePermissionFix(
+                    listOf(getPermissionCheckLocation(context, callExpression)),
+                    getPermissionCheckValues(callExpression),
+                    // If we detect that the PermissionMethod enforces that permission is granted,
+                    // AND is of the "orSelf" variety, we are very confident that this is a behavior
+                    // preserving migration to @EnforcePermission.  Thus, the incident should be ERROR
+                    // level.
+                    errorLevel = enforces && orSelf
+            )
+        }
+
+
+        fun compose(individuals: List<EnforcePermissionFix>): EnforcePermissionFix =
+            EnforcePermissionFix(
+                individuals.flatMap { it.locations },
+                individuals.flatMap { it.permissionNames },
+                errorLevel = individuals.all(EnforcePermissionFix::errorLevel)
+            )
+
+        /**
+         * Given a permission check, get its proper location
+         * so that a lint fix can remove the entire expression
+         */
+        private fun getPermissionCheckLocation(
+            context: JavaContext,
+            callExpression: UCallExpression
+        ):
+                Location {
+            val javaPsi = callExpression.javaPsi!!
+            return Location.create(
+                context.file,
+                javaPsi.containingFile?.text,
+                javaPsi.textRange.startOffset,
+                // unfortunately the element doesn't include the ending semicolon
+                javaPsi.textRange.endOffset + 1
+            )
+        }
+
+        /**
+         * Given a @PermissionMethod, find arguments annotated with @PermissionName
+         * and pull out the permission value(s) being used.  Also evaluates nested calls
+         * to @PermissionMethod(s) in the given method's body.
+         */
+        private fun getPermissionCheckValues(
+            callExpression: UCallExpression
+        ): List<String> {
+            if (!isPermissionMethodCall(callExpression)) return emptyList()
+
+            val result = mutableSetOf<String>() // protect against duplicate permission values
+            val visitedCalls = mutableSetOf<UCallExpression>() // don't visit the same call twice
+            val bfsQueue = ArrayDeque(listOf(callExpression))
+
+            // Breadth First Search - evalutaing nested @PermissionMethod(s) in the available
+            // source code for @PermissionName(s).
+            while (bfsQueue.isNotEmpty()) {
+                val current = bfsQueue.removeFirst()
+                visitedCalls.add(current)
+                result.addAll(findPermissions(current))
+
+                current.resolve()?.getUMethod()?.accept(object : AbstractUastVisitor() {
+                    override fun visitCallExpression(node: UCallExpression): Boolean {
+                        if (isPermissionMethodCall(node) && node !in visitedCalls) {
+                            bfsQueue.add(node)
+                        }
+                        return false
+                    }
+                })
+            }
+
+            return result.toList()
+        }
+
+        private fun findPermissions(
+            callExpression: UCallExpression,
+        ): List<String> {
+            val indices = callExpression.resolve()?.getUMethod()
+                ?.uastParameters
+                ?.filter(::hasPermissionNameAnnotation)
+                ?.mapNotNull { it.sourcePsi?.parameterIndex() }
+                ?: emptyList()
+
+            return indices.mapNotNull {
+                callExpression.getArgumentForParameter(it)?.evaluateString()
+            }
+        }
+    }
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
new file mode 100644
index 0000000..3c2ea1d
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
@@ -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.google.android.lint.aidl
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiElement
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UDeclarationsExpression
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+            listOf(UMethod::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+        override fun visitMethod(node: UMethod) {
+            if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
+
+            val targetExpression = "super.${node.name}$HELPER_SUFFIX()"
+
+            val body = node.uastBody as? UBlockExpression
+            if (body == null) {
+                context.report(
+                        ISSUE_ENFORCE_PERMISSION_HELPER,
+                        context.getLocation(node),
+                        "Method must start with $targetExpression",
+                )
+                return
+            }
+
+            val firstExpression = body.expressions.firstOrNull()
+            if (firstExpression == null) {
+                context.report(
+                    ISSUE_ENFORCE_PERMISSION_HELPER,
+                    context.getLocation(node),
+                    "Method must start with $targetExpression",
+                )
+                return
+            }
+
+            val firstExpressionSource = firstExpression.asSourceString()
+                    .filterNot(Char::isWhitespace)
+
+            if (firstExpressionSource != targetExpression) {
+                val locationTarget = getLocationTarget(firstExpression)
+                val expressionLocation = context.getLocation(locationTarget)
+                val indent = " ".repeat(expressionLocation.start?.column ?: 0)
+
+                val fix = fix()
+                    .replace()
+                    .range(expressionLocation)
+                    .beginning()
+                    .with("$targetExpression;\n\n$indent")
+                    .reformat(true)
+                    .autoFix()
+                    .build()
+
+                context.report(
+                    ISSUE_ENFORCE_PERMISSION_HELPER,
+                    context.getLocation(node),
+                    "Method must start with $targetExpression",
+                    fix
+                )
+            }
+        }
+    }
+
+    companion object {
+        private const val HELPER_SUFFIX = "_enforcePermission"
+
+        private const val EXPLANATION = """
+            When @EnforcePermission is applied, the AIDL compiler generates a Stub method to do the
+            permission check called yourMethodName$HELPER_SUFFIX.
+
+            You must call this method as the first expression in your implementation.
+            """
+
+        val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
+                id = "MissingEnforcePermissionHelper",
+                briefDescription = """Missing permission-enforcing method call in AIDL method 
+                    |annotated with @EnforcePermission""".trimMargin(),
+                explanation = EXPLANATION,
+                category = Category.SECURITY,
+                priority = 6,
+                severity = Severity.ERROR,
+                implementation = Implementation(
+                        EnforcePermissionHelperDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                )
+        )
+
+        /**
+         * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
+         * resulting in an incorrect Location if used directly
+         */
+        private fun getLocationTarget(firstExpression: UExpression): PsiElement? {
+            if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi
+            if (firstExpression is UDeclarationsExpression) {
+                return firstExpression.declarations.firstOrNull()?.sourcePsi
+            }
+            return null
+        }
+    }
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
new file mode 100644
index 0000000..250ca78
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.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.google.android.lint.aidl
+
+import com.google.android.lint.CLASS_STUB
+import com.intellij.psi.PsiAnonymousClass
+import org.jetbrains.uast.UMethod
+
+/**
+ * Given a UMethod, determine if this method is
+ * an entrypoint to an interface generated by AIDL,
+ * returning the interface name if so
+ */
+fun getContainingAidlInterface(node: UMethod): String? {
+    if (!isInClassCalledStub(node)) return null
+    for (superMethod in node.findSuperMethods()) {
+        for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
+            ?: continue) {
+            if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) {
+                return superMethod.containingClass?.name
+            }
+        }
+    }
+    return null
+}
+
+private fun isInClassCalledStub(node: UMethod): Boolean {
+    (node.containingClass as? PsiAnonymousClass)?.let {
+        return it.baseClassReference.referenceName == CLASS_STUB
+    }
+    return node.containingClass?.extendsList?.referenceElements?.any {
+        it.referenceName == CLASS_STUB
+    } ?: false
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
new file mode 100644
index 0000000..9999a0b
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
@@ -0,0 +1,148 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UIfExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UQualifiedReferenceExpression
+import org.jetbrains.uast.skipParenthesizedExprDown
+
+/**
+ * Looks for methods implementing generated AIDL interface stubs
+ * that can have simple permission checks migrated to
+ * @EnforcePermission annotations
+ *
+ * TODO: b/242564870 (enable parse and autoFix of .aidl files)
+ */
+@Suppress("UnstableApiUsage")
+class SimpleManualPermissionEnforcementDetector : AidlImplementationDetector() {
+    override fun visitAidlMethod(
+            context: JavaContext,
+            node: UMethod,
+            interfaceName: String,
+            body: UBlockExpression
+    ) {
+        val enforcePermissionFix = accumulateSimplePermissionCheckFixes(body, context) ?: return
+        val lintFix = enforcePermissionFix.toLintFix(context.getLocation(node))
+        val message =
+                "$interfaceName permission check ${
+                    if (enforcePermissionFix.errorLevel) "should" else "can"
+                } be converted to @EnforcePermission annotation"
+
+        val incident = Incident(
+                ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT,
+                enforcePermissionFix.locations.last(),
+                message,
+                lintFix
+        )
+
+        if (enforcePermissionFix.errorLevel) {
+            incident.overrideSeverity(Severity.ERROR)
+        }
+
+        context.report(incident)
+    }
+
+    /**
+     * Walk the expressions in the method, looking for simple permission checks.
+     *
+     * If a single permission check is found at the beginning of the method,
+     * this should be migrated to @EnforcePermission(value).
+     *
+     * If multiple consecutive permission checks are found,
+     * these should be migrated to @EnforcePermission(allOf={value1, value2, ...})
+     *
+     * As soon as something other than a permission check is encountered, stop looking,
+     * as some other business logic is happening that prevents an automated fix.
+     */
+    private fun accumulateSimplePermissionCheckFixes(
+            methodBody: UBlockExpression,
+            context: JavaContext
+    ):
+            EnforcePermissionFix? {
+        val singleFixes = mutableListOf<EnforcePermissionFix>()
+        for (expression in methodBody.expressions) {
+            singleFixes.add(getPermissionCheckFix(expression.skipParenthesizedExprDown(), context)
+                    ?: break)
+        }
+        return when (singleFixes.size) {
+            0 -> null
+            1 -> singleFixes[0]
+            else -> EnforcePermissionFix.compose(singleFixes)
+        }
+    }
+
+    /**
+     * If an expression boils down to a permission check, return
+     * the helper for creating a lint auto fix to @EnforcePermission
+     */
+    private fun getPermissionCheckFix(startingExpression: UElement?, context: JavaContext):
+            EnforcePermissionFix? {
+        return when (startingExpression) {
+            is UQualifiedReferenceExpression -> getPermissionCheckFix(
+                    startingExpression.selector, context
+            )
+
+            is UIfExpression -> getPermissionCheckFix(startingExpression.condition, context)
+
+            is UCallExpression -> return EnforcePermissionFix
+                    .fromCallExpression(context, startingExpression)
+
+            else -> null
+        }
+    }
+
+    companion object {
+
+        private val EXPLANATION = """
+            Whenever possible, method implementations of AIDL interfaces should use the @EnforcePermission
+            annotation to declare the permissions to be enforced.  The verification code is then
+            generated by the AIDL compiler, which also takes care of annotating the generated java
+            code.
+
+            This reduces the risk of bugs around these permission checks (that often become vulnerabilities).
+            It also enables easier auditing and review.
+
+            Please migrate to an @EnforcePermission annotation. (See: go/aidl-enforce-howto)
+        """.trimIndent()
+
+        @JvmField
+        val ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT = Issue.create(
+                id = "SimpleManualPermissionEnforcement",
+                briefDescription = "Manual permission check can be @EnforcePermission annotation",
+                explanation = EXPLANATION,
+                category = Category.SECURITY,
+                priority = 5,
+                severity = Severity.WARNING,
+                implementation = Implementation(
+                        SimpleManualPermissionEnforcementDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                ),
+                enabledByDefault = false, // TODO: enable once b/241171714 is resolved
+        )
+    }
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
rename to tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
new file mode 100644
index 0000000..31e4846
--- /dev/null
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
@@ -0,0 +1,159 @@
+/*
+* 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+
+class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
+    override fun getDetector() = EnforcePermissionHelperDetector()
+    override fun getIssues() = listOf(
+        EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER)
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk()
+
+    fun testFirstExpressionIsFunctionCall() {
+        lint().files(
+            java(
+                """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+                        @Override
+                        @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                        public void test() throws android.os.RemoteException {
+                            Binder.getCallingUid();
+                        }
+                    }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+                    @Override
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Autofix for src/Foo.java line 5: Replace with super.test_enforcePermission();...:
+                @@ -8 +8
+                +         super.test_enforcePermission();
+                +
+                """
+            )
+    }
+
+    fun testFirstExpressionIsVariableDeclaration() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        String foo = "bar";
+                        Binder.getCallingUid();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+                    @Override
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Autofix for src/Foo.java line 5: Replace with super.test_enforcePermission();...:
+                @@ -8 +8
+                +         super.test_enforcePermission();
+                +
+                """
+            )
+    }
+
+    fun testMethodIsEmpty() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {}
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+                    @Override
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+    }
+
+    fun testOkay() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        super.test_enforcePermission();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    companion object {
+        val stubs = arrayOf(aidlStub, contextStub, binderStub)
+    }
+}
+
+
+
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
new file mode 100644
index 0000000..bdf9c89
--- /dev/null
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
@@ -0,0 +1,578 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = SimpleManualPermissionEnforcementDetector()
+    override fun getIssues(): List<Issue> = listOf(
+            SimpleManualPermissionEnforcementDetector
+            .ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk()
+
+    fun testClass() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:7: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+                @@ -5 +5
+                +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                @@ -7 +8
+                -         mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                """
+            )
+    }
+
+    fun testClass_orSelfFalse_warning() {
+        lint().files(
+                java(
+                    """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            mContext.enforceCallingPermission("android.permission.READ_CONTACTS", "foo");
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                    """
+                    src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                            mContext.enforceCallingPermission("android.permission.READ_CONTACTS", "foo");
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                    """
+                )
+                .expectFixDiffs(
+                    """
+                    Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+                    @@ -5 +5
+                    +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                    @@ -7 +8
+                    -         mContext.enforceCallingPermission("android.permission.READ_CONTACTS", "foo");
+                    """
+                )
+    }
+
+    fun testClass_enforcesFalse_warning() {
+        lint().files(
+                java(
+                    """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                    """
+                    src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                            mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                    """
+                )
+                .expectFixDiffs(
+                    """
+                    Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+                    @@ -5 +5
+                    +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                    @@ -7 +8
+                    -         mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                    """
+                )
+    }
+
+    fun testAnonClass() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo {
+                    private Context mContext;
+                    private ITest itest = new ITest.Stub() {
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            mContext.enforceCallingOrSelfPermission(
+                                "android.permission.READ_CONTACTS", "foo");
+                        }
+                    };
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:8: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                            mContext.enforceCallingOrSelfPermission(
+                            ^
+                1 errors, 0 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 8: Annotate with @EnforcePermission:
+                @@ -6 +6
+                +         @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                @@ -8 +9
+                -             mContext.enforceCallingOrSelfPermission(
+                -                 "android.permission.READ_CONTACTS", "foo");
+                """
+            )
+    }
+
+    fun testConstantEvaluation() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
+                    }
+                }
+                """
+            ).indented(),
+            *stubs,
+            manifestStub
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:8: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
+                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+                @@ -6 +6
+                +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                @@ -8 +9
+                -         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
+                """
+            )
+    }
+
+    fun testAllOf() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo {
+                    private Context mContext;
+                    private ITest itest = new ITest.Stub() {
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            mContext.enforceCallingOrSelfPermission(
+                                "android.permission.READ_CONTACTS", "foo");
+                            mContext.enforceCallingOrSelfPermission(
+                                "android.permission.WRITE_CONTACTS", "foo");
+                        }
+                    };
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:10: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                            mContext.enforceCallingOrSelfPermission(
+                            ^
+                1 errors, 0 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
+                @@ -6 +6
+                +         @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"})
+                @@ -8 +9
+                -             mContext.enforceCallingOrSelfPermission(
+                -                 "android.permission.READ_CONTACTS", "foo");
+                -             mContext.enforceCallingOrSelfPermission(
+                -                 "android.permission.WRITE_CONTACTS", "foo");
+                """
+            )
+    }
+
+    fun testAllOf_mixedOrSelf_warning() {
+        lint().files(
+                java(
+                    """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo {
+                        private Context mContext;
+                        private ITest itest = new ITest.Stub() {
+                            @Override
+                            public void test() throws android.os.RemoteException {
+                                mContext.enforceCallingOrSelfPermission(
+                                    "android.permission.READ_CONTACTS", "foo");
+                                mContext.enforceCallingPermission(
+                                    "android.permission.WRITE_CONTACTS", "foo");
+                            }
+                        };
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                    """
+                    src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                                mContext.enforceCallingPermission(
+                                ^
+                    0 errors, 1 warnings
+                    """
+                )
+                .expectFixDiffs(
+                    """
+                    Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
+                    @@ -6 +6
+                    +         @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"})
+                    @@ -8 +9
+                    -             mContext.enforceCallingOrSelfPermission(
+                    -                 "android.permission.READ_CONTACTS", "foo");
+                    -             mContext.enforceCallingPermission(
+                    -                 "android.permission.WRITE_CONTACTS", "foo");
+                    """
+                )
+    }
+
+    fun testAllOf_mixedEnforces_warning() {
+        lint().files(
+                java(
+                    """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo {
+                        private Context mContext;
+                        private ITest itest = new ITest.Stub() {
+                            @Override
+                            public void test() throws android.os.RemoteException {
+                                mContext.enforceCallingOrSelfPermission(
+                                    "android.permission.READ_CONTACTS", "foo");
+                                mContext.checkCallingOrSelfPermission(
+                                    "android.permission.WRITE_CONTACTS", "foo");
+                            }
+                        };
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                    """
+                    src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                                mContext.checkCallingOrSelfPermission(
+                                ^
+                    0 errors, 1 warnings
+                    """
+                )
+                .expectFixDiffs(
+                    """
+                    Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
+                    @@ -6 +6
+                    +         @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"})
+                    @@ -8 +9
+                    -             mContext.enforceCallingOrSelfPermission(
+                    -                 "android.permission.READ_CONTACTS", "foo");
+                    -             mContext.checkCallingOrSelfPermission(
+                    -                 "android.permission.WRITE_CONTACTS", "foo");
+                    """
+                )
+    }
+
+    fun testPrecedingExpressions() {
+        lint().files(
+            java(
+                """
+                import android.os.Binder;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private mContext Context;
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        long uid = Binder.getCallingUid();
+                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun testPermissionHelper() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+
+                    @android.content.pm.PermissionMethod(orSelf = true)
+                    private void helper() {
+                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                    }
+
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        helper();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:14: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                        helper();
+                        ~~~~~~~~~
+                1 errors, 0 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 14: Annotate with @EnforcePermission:
+                @@ -12 +12
+                +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                @@ -14 +15
+                -         helper();
+                """
+            )
+    }
+
+    fun testPermissionHelper_orSelfNotBubbledUp_warning() {
+        lint().files(
+                java(
+                    """
+                    import android.content.Context;
+                    import android.test.ITest;
+
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+
+                        @android.content.pm.PermissionMethod
+                    private void helper() {
+                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                    }
+
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        helper();
+                    }
+                }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                    """
+                    src/Foo.java:14: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                            helper();
+                            ~~~~~~~~~
+                    0 errors, 1 warnings
+                    """
+                )
+                .expectFixDiffs(
+                    """
+                    Fix for src/Foo.java line 14: Annotate with @EnforcePermission:
+                    @@ -12 +12
+                    +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                    @@ -14 +15
+                    -         helper();
+                    """
+                )
+    }
+
+    fun testPermissionHelperAllOf() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+
+                    @android.content.pm.PermissionMethod(orSelf = true)
+                    private void helper() {
+                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                        mContext.enforceCallingOrSelfPermission("android.permission.WRITE_CONTACTS", "foo");
+                    }
+
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        helper();
+                        mContext.enforceCallingOrSelfPermission("FOO", "foo");
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:16: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                        mContext.enforceCallingOrSelfPermission("FOO", "foo");
+                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 16: Annotate with @EnforcePermission:
+                @@ -13 +13
+                +     @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS", "FOO"})
+                @@ -15 +16
+                -         helper();
+                -         mContext.enforceCallingOrSelfPermission("FOO", "foo");
+                """
+            )
+    }
+
+
+    fun testPermissionHelperNested() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+
+                    @android.content.pm.PermissionMethod(orSelf = true)
+                    private void helperHelper() {
+                        helper("android.permission.WRITE_CONTACTS");
+                    }
+
+                    @android.content.pm.PermissionMethod(orSelf = true)
+                    private void helper(@android.content.pm.PermissionName String extraPermission) {
+                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                    }
+
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        helperHelper();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:19: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                        helperHelper();
+                        ~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 19: Annotate with @EnforcePermission:
+                @@ -17 +17
+                +     @android.annotation.EnforcePermission(allOf={"android.permission.WRITE_CONTACTS", "android.permission.READ_CONTACTS"})
+                @@ -19 +20
+                -         helperHelper();
+                """
+            )
+    }
+
+
+
+    companion object {
+        val stubs = arrayOf(
+            aidlStub,
+            contextStub,
+            binderStub,
+            permissionMethodStub,
+            permissionNameStub
+        )
+    }
+}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
new file mode 100644
index 0000000..5ac8a0b
--- /dev/null
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
@@ -0,0 +1,84 @@
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
+import com.android.tools.lint.checks.infrastructure.TestFile
+
+val aidlStub: TestFile = java(
+    """
+        package android.test;
+        public interface ITest extends android.os.IInterface {
+            public static abstract class Stub extends android.os.Binder implements android.test.ITest {}
+            public void test() throws android.os.RemoteException;
+        }
+    """
+).indented()
+
+val contextStub: TestFile = java(
+    """
+        package android.content;
+        public class Context {
+            @android.content.pm.PermissionMethod(orSelf = true)
+            public void enforceCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}
+            @android.content.pm.PermissionMethod
+            public void enforceCallingPermission(@android.content.pm.PermissionName String permission, String message) {}
+            @android.content.pm.PermissionMethod(orSelf = true)
+            public int checkCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}
+        }
+    """
+).indented()
+
+val binderStub: TestFile = java(
+    """
+        package android.os;
+        public class Binder {
+            public static int getCallingUid() {}
+        }
+    """
+).indented()
+
+val permissionMethodStub: TestFile = java(
+"""
+        package android.content.pm;
+
+        import static java.lang.annotation.ElementType.METHOD;
+        import static java.lang.annotation.RetentionPolicy.CLASS;
+
+        import java.lang.annotation.Retention;
+        import java.lang.annotation.Target;
+
+        @Retention(CLASS)
+        @Target({METHOD})
+        public @interface PermissionMethod {}
+    """
+).indented()
+
+val permissionNameStub: TestFile = java(
+"""
+        package android.content.pm;
+
+        import static java.lang.annotation.ElementType.FIELD;
+        import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+        import static java.lang.annotation.ElementType.METHOD;
+        import static java.lang.annotation.ElementType.PARAMETER;
+        import static java.lang.annotation.RetentionPolicy.CLASS;
+
+        import java.lang.annotation.Retention;
+        import java.lang.annotation.Target;
+
+        @Retention(CLASS)
+        @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+        public @interface PermissionName {}
+    """
+).indented()
+
+val manifestStub: TestFile = java(
+    """
+        package android;
+
+        public final class Manifest {
+            public static final class permission {
+                public static final String READ_CONTACTS="android.permission.READ_CONTACTS";
+            }
+        }
+    """.trimIndent()
+)
\ No newline at end of file
diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp
index 6efd1f6..a0cc446 100644
--- a/tools/locked_region_code_injection/Android.bp
+++ b/tools/locked_region_code_injection/Android.bp
@@ -12,10 +12,10 @@
     manifest: "manifest.txt",
     srcs: ["src/**/*.java"],
     static_libs: [
-        "asm-9.2",
-        "asm-commons-9.2",
-        "asm-tree-9.2",
-        "asm-analysis-9.2",
-        "guava-21.0",
+        "guava",
+        "ow2-asm",
+        "ow2-asm-analysis",
+        "ow2-asm-commons",
+        "ow2-asm-tree",
     ],
 }
diff --git a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
index f29d9b2..c6f6d45 100644
--- a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
+++ b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
@@ -54,6 +54,7 @@
             "java.lang.Short",
             "java.lang.String",
             "java.lang.Void",
+            "java.util.UUID",
             "android.os.Parcelable.Creator",
         )
 
diff --git a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
index 67a31da..512d90c 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
@@ -84,8 +84,8 @@
 
     @Test
     fun parse_formatting() {
-        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" +
-                " %x %e %g %s %f", "ERROR", "WindowManager")
+        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %%" +
+                " %x %s %f", "ERROR", "WindowManager")
 
         val logBuilder = ProtoLogFileProto.newBuilder()
         val logMessageBuilder = ProtoLogMessage.newBuilder()
@@ -93,21 +93,21 @@
                 .setMessageHash(123)
                 .setElapsedRealtimeNanos(0)
                 .addBooleanParams(true)
-                .addAllSint64Params(listOf(1000, 20000, 300000))
-                .addAllDoubleParams(listOf(0.1, 0.00001, 1000.1))
+                .addAllSint64Params(listOf(1000, 20000))
+                .addDoubleParams(1000.1)
                 .addStrParams("test")
         logBuilder.addLog(logMessageBuilder.build())
 
         parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
 
         assertEquals("${testDate(0)} ERROR WindowManager: Test completed successfully: " +
-                "true 1000 % 47040 493e0 1.000000e-01 1.00000e-05 test 1000.100000\n",
+                "true 1000 % 4e20 test 1000.100000\n",
                 outStream.toString())
     }
 
     @Test
     fun parse_invalidParamsTooMany() {
-        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o",
+        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %%",
                 "ERROR", "WindowManager")
 
         val logBuilder = ProtoLogFileProto.newBuilder()
@@ -129,8 +129,8 @@
 
     @Test
     fun parse_invalidParamsNotEnough() {
-        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" +
-                " %x %e %g %s %f", "ERROR", "WindowManager")
+        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %%" +
+                " %x %s %f", "ERROR", "WindowManager")
 
         val logBuilder = ProtoLogFileProto.newBuilder()
         val logMessageBuilder = ProtoLogMessage.newBuilder()
diff --git a/tools/sdkparcelables/Android.bp b/tools/sdkparcelables/Android.bp
index 6ebacd8..6503a1f 100644
--- a/tools/sdkparcelables/Android.bp
+++ b/tools/sdkparcelables/Android.bp
@@ -14,7 +14,7 @@
         "src/**/*.kt",
     ],
     static_libs: [
-        "asm-9.2",
+        "ow2-asm",
     ],
 }
 
diff --git a/tools/traceinjection/Android.bp b/tools/traceinjection/Android.bp
index 39d1b1c..d627fb9 100644
--- a/tools/traceinjection/Android.bp
+++ b/tools/traceinjection/Android.bp
@@ -12,11 +12,11 @@
     manifest: "manifest.txt",
     srcs: ["src/**/*.java"],
     static_libs: [
-        "asm-9.2",
-        "asm-commons-9.2",
-        "asm-tree-9.2",
-        "asm-analysis-9.2",
-        "guava-21.0",
+        "ow2-asm",
+        "ow2-asm-commons",
+        "ow2-asm-tree",
+        "ow2-asm-analysis",
+        "guava",
     ],
 }
 
diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp
index 0d7d5f9..0fa13b8 100644
--- a/tools/validatekeymaps/Main.cpp
+++ b/tools/validatekeymaps/Main.cpp
@@ -167,8 +167,8 @@
             android::base::Result<std::unique_ptr<PropertyMap>> propertyMap =
                     PropertyMap::load(String8(filename));
             if (!propertyMap.ok()) {
-                error("Error %d parsing input device configuration file.\n\n",
-                      propertyMap.error().code());
+                error("Error parsing input device configuration file: %s.\n\n",
+                      propertyMap.error().message().c_str());
                 return false;
             }
             break;
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index d85a5bd..5da18dc 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -393,6 +393,21 @@
         mEventHandler = new Handler(context.getMainLooper());
     }
 
+    /**
+     * Construct WifiNl80211Manager with giving context and binder which is an interface of
+     * IWificond.
+     *
+     * @param context Android context.
+     * @param binder a binder of IWificond.
+     */
+    public WifiNl80211Manager(@NonNull Context context, @NonNull IBinder binder) {
+        this(context);
+        mWificond = IWificond.Stub.asInterface(binder);
+        if (mWificond == null) {
+            Log.e(TAG, "Failed to get reference to wificond");
+        }
+    }
+
     /** @hide */
     @VisibleForTesting
     public WifiNl80211Manager(Context context, IWificond wificond) {
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index a750696..5012622 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -117,13 +117,12 @@
     private static final byte[] TEST_PSK =
             new byte[]{'T', 'e', 's', 't'};
 
-    private static final Set<Integer> SCAN_FREQ_SET =
-            new HashSet<Integer>() {{
-                add(2410);
-                add(2450);
-                add(5050);
-                add(5200);
-            }};
+    private static final Set<Integer> SCAN_FREQ_SET = Set.of(
+            2410,
+            2450,
+            5050,
+            5200);
+
     private static final String TEST_QUOTED_SSID_1 = "\"testSsid1\"";
     private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\"";
     private static final int[] TEST_FREQUENCIES_1 = {};
@@ -131,13 +130,11 @@
     private static final MacAddress TEST_RAW_MAC_BYTES = MacAddress.fromBytes(
             new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05});
 
-    private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST =
-            new ArrayList<byte[]>() {{
-                add(LocalNativeUtil.byteArrayFromArrayList(
-                        LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)));
-                add(LocalNativeUtil.byteArrayFromArrayList(
-                        LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
-            }};
+    private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST = List.of(
+            LocalNativeUtil.byteArrayFromArrayList(
+                    LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)),
+            LocalNativeUtil.byteArrayFromArrayList(
+                    LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
 
     private static final PnoSettings TEST_PNO_SETTINGS = new PnoSettings();
     static {